diff --git a/packages/core/src/AbstractRenderer.js b/packages/core/src/AbstractRenderer.js new file mode 100644 index 0000000..64b2eb1 --- /dev/null +++ b/packages/core/src/AbstractRenderer.js @@ -0,0 +1,353 @@ +import { hex2string, hex2rgb } from '@pixi/utils'; +import { Matrix, Rectangle } from '@pixi/math'; +import { RENDERER_TYPE } from '@pixi/constants'; +import { settings } from '@pixi/settings'; +import { Container } from '@pixi/display'; +import RenderTexture from './renderTexture/RenderTexture'; +import EventEmitter from 'eventemitter3'; + +const tempMatrix = new Matrix(); + +/** + * 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 EventEmitter + * @memberof PIXI + */ +export default class AbstractRenderer extends EventEmitter +{ + // eslint-disable-next-line valid-jsdoc + /** + * @param {string} system - The name of the system this renderer is for. + * @param {object} [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.transparent=false] - If the render view is transparent, default false + * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false + * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. The + * resolution of the renderer retina would be 2. + * @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 {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when rendering, + * stopping pixel interpolation. + */ + constructor(system, options, arg2, arg3) + { + super(); + + // Support for constructor(system, screenWidth, screenHeight, options) + if (typeof options === 'number') + { + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + }, arg3); + } + + // 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 = RENDERER_TYPE.UNKNOWN; + + /** + * Measurements of the screen. (0, 0, screenWidth, screenHeight) + * + * Its safe to use as filterArea or hitArea for 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 1 + */ + this.resolution = options.resolution || settings.RESOLUTION; + + /** + * Whether the render view is transparent + * + * @member {boolean} + */ + this.transparent = options.transparent; + + /** + * Whether css dimensions of canvas view should be resized to screen dimensions automatically + * + * @member {boolean} + */ + this.autoResize = options.autoResize || false; + + /** + * Tracks the blend modes useful for this renderer. + * + * @member {object} + */ + this.blendModes = null; + + /** + * 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; + + /** + * If true PixiJS will Math.floor() x/y values when rendering, stopping pixel interpolation. + * Handy for crisp pixel art and speed on legacy devices. + * + * @member {boolean} + */ + this.roundPixels = options.roundPixels; + + /** + * The background color as a number. + * + * @member {number} + * @private + */ + this._backgroundColor = 0x000000; + + /** + * The background color as an [R, G, B] array. + * + * @member {number[]} + * @private + */ + this._backgroundColorRgba = [0, 0, 0, 0]; + + /** + * The background color as a string. + * + * @member {string} + * @private + */ + this._backgroundColorString = '#000000'; + + this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter + + /** + * This temporary display object used as the parent of the currently being rendered item + * + * @member {PIXI.DisplayObject} + * @private + */ + this._tempDisplayObjectParent = new Container(); + + /** + * The last root object that the renderer tried to render. + * + * @member {PIXI.DisplayObject} + * @private + */ + this._lastObjectRendered = this._tempDisplayObjectParent; + + /** + * Collection of plugins. + * @readonly + * @member {object} + */ + this.plugins = {}; + } + + /** + * Initialize the plugins. + * + * @protected + * @param {object} staticMap - The dictionary of staticly saved plugins. + */ + initPlugins(staticMap) + { + 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() + { + return this.view.width; + } + + /** + * Same as view.height, actual number of pixels in the canvas by vertical + * + * @member {number} + * @readonly + * @default 600 + */ + get height() + { + return this.view.height; + } + + /** + * Resizes the screen and canvas to the specified width and height + * Canvas dimensions are multiplied by resolution + * + * @param {number} screenWidth - the new width of the screen + * @param {number} screenHeight - the new height of the screen + */ + resize(screenWidth, screenHeight) + { + this.screen.width = screenWidth; + this.screen.height = screenHeight; + + this.view.width = screenWidth * this.resolution; + this.view.height = screenHeight * this.resolution; + + if (this.autoResize) + { + this.view.style.width = `${screenWidth}px`; + this.view.style.height = `${screenHeight}px`; + } + } + + /** + * 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 {PIXI.DisplayObject} displayObject - The displayObject the object will be generated from + * @param {number} scaleMode - Should be one of the scaleMode consts + * @param {number} resolution - The resolution / device pixel ratio of the texture being generated + * @param {PIXI.Rectangle} [region] - The region of the displayObject, that shall be rendered, + * if no region is specified, defaults to the local bounds of the displayObject. + * @return {PIXI.Texture} a texture of the graphics object + */ + generateTexture(displayObject, scaleMode, resolution, region) + { + region = region || displayObject.getLocalBounds(); + + // 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(region.width | 0, region.height | 0, scaleMode, resolution); + + tempMatrix.tx = -region.x; + tempMatrix.ty = -region.y; + + this.render(displayObject, renderTexture, false, tempMatrix, true); + + return renderTexture; + } + + /** + * Removes everything from the renderer and optionally removes the Canvas DOM element. + * + * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. + */ + destroy(removeView) + { + 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); + } + + this.plugins = null; + + this.type = RENDERER_TYPE.UNKNOWN; + + this.view = null; + + this.screen = null; + + this.resolution = 0; + + this.transparent = false; + + this.autoResize = false; + + this.blendModes = null; + + this.options = null; + + this.preserveDrawingBuffer = false; + this.clearBeforeRender = false; + + this.roundPixels = false; + + this._backgroundColor = 0; + this._backgroundColorRgba = null; + this._backgroundColorString = null; + + this._tempDisplayObjectParent = null; + this._lastObjectRendered = null; + } + + /** + * The background color to fill if not transparent + * + * @member {number} + */ + get backgroundColor() + { + return this._backgroundColor; + } + + set backgroundColor(value) // eslint-disable-line require-jsdoc + { + this._backgroundColor = value; + this._backgroundColorString = hex2string(value); + hex2rgb(value, this._backgroundColorRgba); + } +} diff --git a/packages/core/src/AbstractRenderer.js b/packages/core/src/AbstractRenderer.js new file mode 100644 index 0000000..64b2eb1 --- /dev/null +++ b/packages/core/src/AbstractRenderer.js @@ -0,0 +1,353 @@ +import { hex2string, hex2rgb } from '@pixi/utils'; +import { Matrix, Rectangle } from '@pixi/math'; +import { RENDERER_TYPE } from '@pixi/constants'; +import { settings } from '@pixi/settings'; +import { Container } from '@pixi/display'; +import RenderTexture from './renderTexture/RenderTexture'; +import EventEmitter from 'eventemitter3'; + +const tempMatrix = new Matrix(); + +/** + * 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 EventEmitter + * @memberof PIXI + */ +export default class AbstractRenderer extends EventEmitter +{ + // eslint-disable-next-line valid-jsdoc + /** + * @param {string} system - The name of the system this renderer is for. + * @param {object} [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.transparent=false] - If the render view is transparent, default false + * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false + * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. The + * resolution of the renderer retina would be 2. + * @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 {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when rendering, + * stopping pixel interpolation. + */ + constructor(system, options, arg2, arg3) + { + super(); + + // Support for constructor(system, screenWidth, screenHeight, options) + if (typeof options === 'number') + { + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + }, arg3); + } + + // 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 = RENDERER_TYPE.UNKNOWN; + + /** + * Measurements of the screen. (0, 0, screenWidth, screenHeight) + * + * Its safe to use as filterArea or hitArea for 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 1 + */ + this.resolution = options.resolution || settings.RESOLUTION; + + /** + * Whether the render view is transparent + * + * @member {boolean} + */ + this.transparent = options.transparent; + + /** + * Whether css dimensions of canvas view should be resized to screen dimensions automatically + * + * @member {boolean} + */ + this.autoResize = options.autoResize || false; + + /** + * Tracks the blend modes useful for this renderer. + * + * @member {object} + */ + this.blendModes = null; + + /** + * 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; + + /** + * If true PixiJS will Math.floor() x/y values when rendering, stopping pixel interpolation. + * Handy for crisp pixel art and speed on legacy devices. + * + * @member {boolean} + */ + this.roundPixels = options.roundPixels; + + /** + * The background color as a number. + * + * @member {number} + * @private + */ + this._backgroundColor = 0x000000; + + /** + * The background color as an [R, G, B] array. + * + * @member {number[]} + * @private + */ + this._backgroundColorRgba = [0, 0, 0, 0]; + + /** + * The background color as a string. + * + * @member {string} + * @private + */ + this._backgroundColorString = '#000000'; + + this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter + + /** + * This temporary display object used as the parent of the currently being rendered item + * + * @member {PIXI.DisplayObject} + * @private + */ + this._tempDisplayObjectParent = new Container(); + + /** + * The last root object that the renderer tried to render. + * + * @member {PIXI.DisplayObject} + * @private + */ + this._lastObjectRendered = this._tempDisplayObjectParent; + + /** + * Collection of plugins. + * @readonly + * @member {object} + */ + this.plugins = {}; + } + + /** + * Initialize the plugins. + * + * @protected + * @param {object} staticMap - The dictionary of staticly saved plugins. + */ + initPlugins(staticMap) + { + 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() + { + return this.view.width; + } + + /** + * Same as view.height, actual number of pixels in the canvas by vertical + * + * @member {number} + * @readonly + * @default 600 + */ + get height() + { + return this.view.height; + } + + /** + * Resizes the screen and canvas to the specified width and height + * Canvas dimensions are multiplied by resolution + * + * @param {number} screenWidth - the new width of the screen + * @param {number} screenHeight - the new height of the screen + */ + resize(screenWidth, screenHeight) + { + this.screen.width = screenWidth; + this.screen.height = screenHeight; + + this.view.width = screenWidth * this.resolution; + this.view.height = screenHeight * this.resolution; + + if (this.autoResize) + { + this.view.style.width = `${screenWidth}px`; + this.view.style.height = `${screenHeight}px`; + } + } + + /** + * 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 {PIXI.DisplayObject} displayObject - The displayObject the object will be generated from + * @param {number} scaleMode - Should be one of the scaleMode consts + * @param {number} resolution - The resolution / device pixel ratio of the texture being generated + * @param {PIXI.Rectangle} [region] - The region of the displayObject, that shall be rendered, + * if no region is specified, defaults to the local bounds of the displayObject. + * @return {PIXI.Texture} a texture of the graphics object + */ + generateTexture(displayObject, scaleMode, resolution, region) + { + region = region || displayObject.getLocalBounds(); + + // 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(region.width | 0, region.height | 0, scaleMode, resolution); + + tempMatrix.tx = -region.x; + tempMatrix.ty = -region.y; + + this.render(displayObject, renderTexture, false, tempMatrix, true); + + return renderTexture; + } + + /** + * Removes everything from the renderer and optionally removes the Canvas DOM element. + * + * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. + */ + destroy(removeView) + { + 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); + } + + this.plugins = null; + + this.type = RENDERER_TYPE.UNKNOWN; + + this.view = null; + + this.screen = null; + + this.resolution = 0; + + this.transparent = false; + + this.autoResize = false; + + this.blendModes = null; + + this.options = null; + + this.preserveDrawingBuffer = false; + this.clearBeforeRender = false; + + this.roundPixels = false; + + this._backgroundColor = 0; + this._backgroundColorRgba = null; + this._backgroundColorString = null; + + this._tempDisplayObjectParent = null; + this._lastObjectRendered = null; + } + + /** + * The background color to fill if not transparent + * + * @member {number} + */ + get backgroundColor() + { + return this._backgroundColor; + } + + set backgroundColor(value) // eslint-disable-line require-jsdoc + { + this._backgroundColor = value; + this._backgroundColorString = hex2string(value); + hex2rgb(value, this._backgroundColorRgba); + } +} diff --git a/packages/core/src/Renderer.js b/packages/core/src/Renderer.js new file mode 100644 index 0000000..2227e78 --- /dev/null +++ b/packages/core/src/Renderer.js @@ -0,0 +1,338 @@ +import AbstractRenderer from './AbstractRenderer'; +import { sayHello } from '@pixi/utils'; +import MaskSystem from './mask/MaskSystem'; +import StencilSystem from './mask/StencilSystem'; +import FilterSystem from './filters/FilterSystem'; +import FramebufferSystem from './framebuffer/FramebufferSystem'; +import RenderTextureSystem from './renderTexture/RenderTextureSystem'; +import TextureSystem from './textures/TextureSystem'; +import ProjectionSystem from './projection/ProjectionSystem'; +import StateSystem from './state/StateSystem'; +import GeometrySystem from './geometry/GeometrySystem'; +import ShaderSystem from './shader/ShaderSystem'; +import ContextSystem from './context/ContextSystem'; +import BatchSystem from './batch/BatchSystem'; +import TextureGCSystem from './textures/TextureGCSystem'; +import { RENDERER_TYPE } from '@pixi/constants'; +import UniformGroup from './shader/UniformGroup'; +import { Matrix } from '@pixi/math'; +import Runner from 'mini-runner'; + +/** + * 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 Render works by automatically managing webGLBatchs. + * 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 :) + * + * @class + * @memberof PIXI + * @extends PIXI.AbstractRenderer + */ +export default class Renderer extends AbstractRenderer +{ + // eslint-disable-next-line valid-jsdoc + /** + * + * @param {object} [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.transparent=false] - If the render view is transparent, default false + * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false + * @param {boolean} [options.antialias=false] - sets antialias. If not available natively then FXAA + * antialiasing is used + * @param {boolean} [options.forceFXAA=false] - forces FXAA antialiasing to be used over native. + * FXAA is faster, but may not always look as great + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. + * The resolution of the renderer retina would be 2. + * @param {boolean} [options.clearBeforeRender=true] - This sets if the renderer will clear + * the canvas or not before the new render pass. If you wish to set this to false, you *must* set + * preserveDrawingBuffer to `true`. + * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, + * enable this if you need to call toDataUrl on the webgl context. + * @param {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when + * rendering, stopping pixel interpolation. + * @param {number} [options.backgroundColor=0x000000] - The background color of the rendered area + * (shown if not transparent). + * @param {boolean} [options.legacy=false] - If true PixiJS will aim to ensure compatibility + * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. + * @param {string} [options.powerPreference] - Parameter passed to webgl context, set to "high-performance" + * for devices with dual graphics card + */ + constructor(options, arg2, arg3) + { + super('WebGL', options, arg2, arg3); + + /** + * The type of this renderer as a standardised const + * + * @member {number} + * @see PIXI.RENDERER_TYPE + */ + this.type = RENDERER_TYPE.WEBGL; + + // this will be set by the contextSystem (this.context) + this.gl = null; + this.CONTEXT_UID = 0; + this.legacy = !!options.legacy; + + // TODO legacy! + + // runners! + this.runners = { + destroy: new Runner('destroy'), + contextChange: new Runner('contextChange', 1), + reset: new Runner('reset'), + update: new Runner('update'), + postrender: new Runner('postrender'), + prerender: new Runner('prerender'), + resize: new Runner('resize', 2), + }; + + 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(GeometrySystem, 'geometry') + .addSystem(FramebufferSystem, 'framebuffer') + .addSystem(StencilSystem, 'stencil') + .addSystem(ProjectionSystem, 'projection') + .addSystem(TextureGCSystem, 'textureGC') + .addSystem(FilterSystem, 'filter') + .addSystem(RenderTextureSystem, 'renderTexture') + .addSystem(BatchSystem, 'batch'); + + this.initPlugins(Renderer.__plugins); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ + if (options.context) + { + this.context.initFromContext(options.context); + } + else + { + this.context.initFromOptions({ + alpha: this.transparent, + antialias: options.antialias, + premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', + stencil: true, + 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); + } + + /** + * Add a new system to the renderer. + * @param {class} ClassRef - Class reference + * @param {string} [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. + * @return {PIXI.Renderer} Return instance of renderer + */ + addSystem(ClassRef, name) + { + if (!name) + { + name = ClassRef.name; + } + + // TODO - read name from class.name.. + + /* + if(name.includes('System')) + { + name = name.replace('System', ''); + name = name.charAt(0).toLowerCase() + name.slice(1); + } + */ + + const system = new ClassRef(this); + + if (this[name]) + { + throw new Error(`Whoops! ${name} is already a manger`); + } + + this[name] = system; + + for (const i in this.runners) + { + this.runners[i].add(system); + } + + return this; + + /** + * 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. + */ + } + + /** + * Renders the object to its webGL view + * + * @param {PIXI.DisplayObject} displayObject - the object to be rendered + * @param {PIXI.RenderTexture} renderTexture - The render texture to render to. + * @param {boolean} [clear] - Should the canvas be cleared before the new render + * @param {PIXI.Transform} [transform] - A transform to apply to the render texture before rendering. + * @param {boolean} [skipUpdateTransform] - Should we skip the update transform pass? + */ + render(displayObject, renderTexture, clear, transform, skipUpdateTransform) + { + // can be handy to know! + this.renderingToScreen = !renderTexture; + + this.runners.prerender.run(); + this.emit('prerender'); + + // 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.parent; + + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = 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.run(); + + this.emit('postrender'); + } + + /** + * Resizes the webGL view to the specified width and height. + * + * @param {number} screenWidth - the new width of the screen + * @param {number} screenHeight - the new height of the screen + */ + resize(screenWidth, screenHeight) + { + AbstractRenderer.prototype.resize.call(this, screenWidth, screenHeight); + + this.runners.resize.run(screenWidth, screenHeight); + } + + /** + * Resets the WebGL state so you can render things however you fancy! + * + * @return {PIXI.Renderer} Returns itself. + */ + reset() + { + this.runners.reset.run(); + + return this; + } + + clear() + { + this.framebuffer.bind(); + this.framebuffer.clear(); + } + + /** + * Removes everything from the renderer (event listeners, spritebatch, etc...) + * + * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. + * See: https://github.com/pixijs/pixi.js/issues/2233 + */ + destroy(removeView) + { + this.runners.destroy.run(); + + // call base destroy + super.destroy(removeView); + + // TODO nullify all the managers.. + this.gl = null; + } + + /** + * 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 + * builds and excluding plugins. + * @name PIXI.Renderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. + */ + + /** + * Adds a plugin to the renderer. + * + * @method + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + static registerPlugin(pluginName, ctor) + { + Renderer.__plugins = Renderer.__plugins || {}; + Renderer.__plugins[pluginName] = ctor; + } +} diff --git a/packages/core/src/AbstractRenderer.js b/packages/core/src/AbstractRenderer.js new file mode 100644 index 0000000..64b2eb1 --- /dev/null +++ b/packages/core/src/AbstractRenderer.js @@ -0,0 +1,353 @@ +import { hex2string, hex2rgb } from '@pixi/utils'; +import { Matrix, Rectangle } from '@pixi/math'; +import { RENDERER_TYPE } from '@pixi/constants'; +import { settings } from '@pixi/settings'; +import { Container } from '@pixi/display'; +import RenderTexture from './renderTexture/RenderTexture'; +import EventEmitter from 'eventemitter3'; + +const tempMatrix = new Matrix(); + +/** + * 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 EventEmitter + * @memberof PIXI + */ +export default class AbstractRenderer extends EventEmitter +{ + // eslint-disable-next-line valid-jsdoc + /** + * @param {string} system - The name of the system this renderer is for. + * @param {object} [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.transparent=false] - If the render view is transparent, default false + * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false + * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. The + * resolution of the renderer retina would be 2. + * @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 {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when rendering, + * stopping pixel interpolation. + */ + constructor(system, options, arg2, arg3) + { + super(); + + // Support for constructor(system, screenWidth, screenHeight, options) + if (typeof options === 'number') + { + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + }, arg3); + } + + // 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 = RENDERER_TYPE.UNKNOWN; + + /** + * Measurements of the screen. (0, 0, screenWidth, screenHeight) + * + * Its safe to use as filterArea or hitArea for 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 1 + */ + this.resolution = options.resolution || settings.RESOLUTION; + + /** + * Whether the render view is transparent + * + * @member {boolean} + */ + this.transparent = options.transparent; + + /** + * Whether css dimensions of canvas view should be resized to screen dimensions automatically + * + * @member {boolean} + */ + this.autoResize = options.autoResize || false; + + /** + * Tracks the blend modes useful for this renderer. + * + * @member {object} + */ + this.blendModes = null; + + /** + * 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; + + /** + * If true PixiJS will Math.floor() x/y values when rendering, stopping pixel interpolation. + * Handy for crisp pixel art and speed on legacy devices. + * + * @member {boolean} + */ + this.roundPixels = options.roundPixels; + + /** + * The background color as a number. + * + * @member {number} + * @private + */ + this._backgroundColor = 0x000000; + + /** + * The background color as an [R, G, B] array. + * + * @member {number[]} + * @private + */ + this._backgroundColorRgba = [0, 0, 0, 0]; + + /** + * The background color as a string. + * + * @member {string} + * @private + */ + this._backgroundColorString = '#000000'; + + this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter + + /** + * This temporary display object used as the parent of the currently being rendered item + * + * @member {PIXI.DisplayObject} + * @private + */ + this._tempDisplayObjectParent = new Container(); + + /** + * The last root object that the renderer tried to render. + * + * @member {PIXI.DisplayObject} + * @private + */ + this._lastObjectRendered = this._tempDisplayObjectParent; + + /** + * Collection of plugins. + * @readonly + * @member {object} + */ + this.plugins = {}; + } + + /** + * Initialize the plugins. + * + * @protected + * @param {object} staticMap - The dictionary of staticly saved plugins. + */ + initPlugins(staticMap) + { + 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() + { + return this.view.width; + } + + /** + * Same as view.height, actual number of pixels in the canvas by vertical + * + * @member {number} + * @readonly + * @default 600 + */ + get height() + { + return this.view.height; + } + + /** + * Resizes the screen and canvas to the specified width and height + * Canvas dimensions are multiplied by resolution + * + * @param {number} screenWidth - the new width of the screen + * @param {number} screenHeight - the new height of the screen + */ + resize(screenWidth, screenHeight) + { + this.screen.width = screenWidth; + this.screen.height = screenHeight; + + this.view.width = screenWidth * this.resolution; + this.view.height = screenHeight * this.resolution; + + if (this.autoResize) + { + this.view.style.width = `${screenWidth}px`; + this.view.style.height = `${screenHeight}px`; + } + } + + /** + * 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 {PIXI.DisplayObject} displayObject - The displayObject the object will be generated from + * @param {number} scaleMode - Should be one of the scaleMode consts + * @param {number} resolution - The resolution / device pixel ratio of the texture being generated + * @param {PIXI.Rectangle} [region] - The region of the displayObject, that shall be rendered, + * if no region is specified, defaults to the local bounds of the displayObject. + * @return {PIXI.Texture} a texture of the graphics object + */ + generateTexture(displayObject, scaleMode, resolution, region) + { + region = region || displayObject.getLocalBounds(); + + // 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(region.width | 0, region.height | 0, scaleMode, resolution); + + tempMatrix.tx = -region.x; + tempMatrix.ty = -region.y; + + this.render(displayObject, renderTexture, false, tempMatrix, true); + + return renderTexture; + } + + /** + * Removes everything from the renderer and optionally removes the Canvas DOM element. + * + * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. + */ + destroy(removeView) + { + 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); + } + + this.plugins = null; + + this.type = RENDERER_TYPE.UNKNOWN; + + this.view = null; + + this.screen = null; + + this.resolution = 0; + + this.transparent = false; + + this.autoResize = false; + + this.blendModes = null; + + this.options = null; + + this.preserveDrawingBuffer = false; + this.clearBeforeRender = false; + + this.roundPixels = false; + + this._backgroundColor = 0; + this._backgroundColorRgba = null; + this._backgroundColorString = null; + + this._tempDisplayObjectParent = null; + this._lastObjectRendered = null; + } + + /** + * The background color to fill if not transparent + * + * @member {number} + */ + get backgroundColor() + { + return this._backgroundColor; + } + + set backgroundColor(value) // eslint-disable-line require-jsdoc + { + this._backgroundColor = value; + this._backgroundColorString = hex2string(value); + hex2rgb(value, this._backgroundColorRgba); + } +} diff --git a/packages/core/src/Renderer.js b/packages/core/src/Renderer.js new file mode 100644 index 0000000..2227e78 --- /dev/null +++ b/packages/core/src/Renderer.js @@ -0,0 +1,338 @@ +import AbstractRenderer from './AbstractRenderer'; +import { sayHello } from '@pixi/utils'; +import MaskSystem from './mask/MaskSystem'; +import StencilSystem from './mask/StencilSystem'; +import FilterSystem from './filters/FilterSystem'; +import FramebufferSystem from './framebuffer/FramebufferSystem'; +import RenderTextureSystem from './renderTexture/RenderTextureSystem'; +import TextureSystem from './textures/TextureSystem'; +import ProjectionSystem from './projection/ProjectionSystem'; +import StateSystem from './state/StateSystem'; +import GeometrySystem from './geometry/GeometrySystem'; +import ShaderSystem from './shader/ShaderSystem'; +import ContextSystem from './context/ContextSystem'; +import BatchSystem from './batch/BatchSystem'; +import TextureGCSystem from './textures/TextureGCSystem'; +import { RENDERER_TYPE } from '@pixi/constants'; +import UniformGroup from './shader/UniformGroup'; +import { Matrix } from '@pixi/math'; +import Runner from 'mini-runner'; + +/** + * 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 Render works by automatically managing webGLBatchs. + * 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 :) + * + * @class + * @memberof PIXI + * @extends PIXI.AbstractRenderer + */ +export default class Renderer extends AbstractRenderer +{ + // eslint-disable-next-line valid-jsdoc + /** + * + * @param {object} [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.transparent=false] - If the render view is transparent, default false + * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false + * @param {boolean} [options.antialias=false] - sets antialias. If not available natively then FXAA + * antialiasing is used + * @param {boolean} [options.forceFXAA=false] - forces FXAA antialiasing to be used over native. + * FXAA is faster, but may not always look as great + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. + * The resolution of the renderer retina would be 2. + * @param {boolean} [options.clearBeforeRender=true] - This sets if the renderer will clear + * the canvas or not before the new render pass. If you wish to set this to false, you *must* set + * preserveDrawingBuffer to `true`. + * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, + * enable this if you need to call toDataUrl on the webgl context. + * @param {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when + * rendering, stopping pixel interpolation. + * @param {number} [options.backgroundColor=0x000000] - The background color of the rendered area + * (shown if not transparent). + * @param {boolean} [options.legacy=false] - If true PixiJS will aim to ensure compatibility + * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. + * @param {string} [options.powerPreference] - Parameter passed to webgl context, set to "high-performance" + * for devices with dual graphics card + */ + constructor(options, arg2, arg3) + { + super('WebGL', options, arg2, arg3); + + /** + * The type of this renderer as a standardised const + * + * @member {number} + * @see PIXI.RENDERER_TYPE + */ + this.type = RENDERER_TYPE.WEBGL; + + // this will be set by the contextSystem (this.context) + this.gl = null; + this.CONTEXT_UID = 0; + this.legacy = !!options.legacy; + + // TODO legacy! + + // runners! + this.runners = { + destroy: new Runner('destroy'), + contextChange: new Runner('contextChange', 1), + reset: new Runner('reset'), + update: new Runner('update'), + postrender: new Runner('postrender'), + prerender: new Runner('prerender'), + resize: new Runner('resize', 2), + }; + + 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(GeometrySystem, 'geometry') + .addSystem(FramebufferSystem, 'framebuffer') + .addSystem(StencilSystem, 'stencil') + .addSystem(ProjectionSystem, 'projection') + .addSystem(TextureGCSystem, 'textureGC') + .addSystem(FilterSystem, 'filter') + .addSystem(RenderTextureSystem, 'renderTexture') + .addSystem(BatchSystem, 'batch'); + + this.initPlugins(Renderer.__plugins); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ + if (options.context) + { + this.context.initFromContext(options.context); + } + else + { + this.context.initFromOptions({ + alpha: this.transparent, + antialias: options.antialias, + premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', + stencil: true, + 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); + } + + /** + * Add a new system to the renderer. + * @param {class} ClassRef - Class reference + * @param {string} [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. + * @return {PIXI.Renderer} Return instance of renderer + */ + addSystem(ClassRef, name) + { + if (!name) + { + name = ClassRef.name; + } + + // TODO - read name from class.name.. + + /* + if(name.includes('System')) + { + name = name.replace('System', ''); + name = name.charAt(0).toLowerCase() + name.slice(1); + } + */ + + const system = new ClassRef(this); + + if (this[name]) + { + throw new Error(`Whoops! ${name} is already a manger`); + } + + this[name] = system; + + for (const i in this.runners) + { + this.runners[i].add(system); + } + + return this; + + /** + * 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. + */ + } + + /** + * Renders the object to its webGL view + * + * @param {PIXI.DisplayObject} displayObject - the object to be rendered + * @param {PIXI.RenderTexture} renderTexture - The render texture to render to. + * @param {boolean} [clear] - Should the canvas be cleared before the new render + * @param {PIXI.Transform} [transform] - A transform to apply to the render texture before rendering. + * @param {boolean} [skipUpdateTransform] - Should we skip the update transform pass? + */ + render(displayObject, renderTexture, clear, transform, skipUpdateTransform) + { + // can be handy to know! + this.renderingToScreen = !renderTexture; + + this.runners.prerender.run(); + this.emit('prerender'); + + // 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.parent; + + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = 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.run(); + + this.emit('postrender'); + } + + /** + * Resizes the webGL view to the specified width and height. + * + * @param {number} screenWidth - the new width of the screen + * @param {number} screenHeight - the new height of the screen + */ + resize(screenWidth, screenHeight) + { + AbstractRenderer.prototype.resize.call(this, screenWidth, screenHeight); + + this.runners.resize.run(screenWidth, screenHeight); + } + + /** + * Resets the WebGL state so you can render things however you fancy! + * + * @return {PIXI.Renderer} Returns itself. + */ + reset() + { + this.runners.reset.run(); + + return this; + } + + clear() + { + this.framebuffer.bind(); + this.framebuffer.clear(); + } + + /** + * Removes everything from the renderer (event listeners, spritebatch, etc...) + * + * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. + * See: https://github.com/pixijs/pixi.js/issues/2233 + */ + destroy(removeView) + { + this.runners.destroy.run(); + + // call base destroy + super.destroy(removeView); + + // TODO nullify all the managers.. + this.gl = null; + } + + /** + * 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 + * builds and excluding plugins. + * @name PIXI.Renderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. + */ + + /** + * Adds a plugin to the renderer. + * + * @method + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + static registerPlugin(pluginName, ctor) + { + Renderer.__plugins = Renderer.__plugins || {}; + Renderer.__plugins[pluginName] = ctor; + } +} diff --git a/packages/core/src/System.js b/packages/core/src/System.js new file mode 100644 index 0000000..05a10c8 --- /dev/null +++ b/packages/core/src/System.js @@ -0,0 +1,42 @@ +/** + * System is a base class used for extending systems used by the {@link PIXI.Renderer} + * @see PIXI.Renderer#addSystem + * @class + * @memberof PIXI + */ +export default class System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + /** + * The renderer this manager works for. + * + * @member {PIXI.Renderer} + */ + this.renderer = renderer; + + this.renderer.runners.contextChange.add(this); + } + + /** + * Generic method called when there is a WebGL context change. + * + */ + contextChange() + { + // do some codes init! + } + + /** + * Generic destroy methods to be overridden by the subclass + * + */ + destroy() + { + this.renderer.runners.contextChange.remove(this); + this.renderer = null; + } +} diff --git a/packages/core/src/AbstractRenderer.js b/packages/core/src/AbstractRenderer.js new file mode 100644 index 0000000..64b2eb1 --- /dev/null +++ b/packages/core/src/AbstractRenderer.js @@ -0,0 +1,353 @@ +import { hex2string, hex2rgb } from '@pixi/utils'; +import { Matrix, Rectangle } from '@pixi/math'; +import { RENDERER_TYPE } from '@pixi/constants'; +import { settings } from '@pixi/settings'; +import { Container } from '@pixi/display'; +import RenderTexture from './renderTexture/RenderTexture'; +import EventEmitter from 'eventemitter3'; + +const tempMatrix = new Matrix(); + +/** + * 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 EventEmitter + * @memberof PIXI + */ +export default class AbstractRenderer extends EventEmitter +{ + // eslint-disable-next-line valid-jsdoc + /** + * @param {string} system - The name of the system this renderer is for. + * @param {object} [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.transparent=false] - If the render view is transparent, default false + * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false + * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. The + * resolution of the renderer retina would be 2. + * @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 {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when rendering, + * stopping pixel interpolation. + */ + constructor(system, options, arg2, arg3) + { + super(); + + // Support for constructor(system, screenWidth, screenHeight, options) + if (typeof options === 'number') + { + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + }, arg3); + } + + // 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 = RENDERER_TYPE.UNKNOWN; + + /** + * Measurements of the screen. (0, 0, screenWidth, screenHeight) + * + * Its safe to use as filterArea or hitArea for 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 1 + */ + this.resolution = options.resolution || settings.RESOLUTION; + + /** + * Whether the render view is transparent + * + * @member {boolean} + */ + this.transparent = options.transparent; + + /** + * Whether css dimensions of canvas view should be resized to screen dimensions automatically + * + * @member {boolean} + */ + this.autoResize = options.autoResize || false; + + /** + * Tracks the blend modes useful for this renderer. + * + * @member {object} + */ + this.blendModes = null; + + /** + * 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; + + /** + * If true PixiJS will Math.floor() x/y values when rendering, stopping pixel interpolation. + * Handy for crisp pixel art and speed on legacy devices. + * + * @member {boolean} + */ + this.roundPixels = options.roundPixels; + + /** + * The background color as a number. + * + * @member {number} + * @private + */ + this._backgroundColor = 0x000000; + + /** + * The background color as an [R, G, B] array. + * + * @member {number[]} + * @private + */ + this._backgroundColorRgba = [0, 0, 0, 0]; + + /** + * The background color as a string. + * + * @member {string} + * @private + */ + this._backgroundColorString = '#000000'; + + this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter + + /** + * This temporary display object used as the parent of the currently being rendered item + * + * @member {PIXI.DisplayObject} + * @private + */ + this._tempDisplayObjectParent = new Container(); + + /** + * The last root object that the renderer tried to render. + * + * @member {PIXI.DisplayObject} + * @private + */ + this._lastObjectRendered = this._tempDisplayObjectParent; + + /** + * Collection of plugins. + * @readonly + * @member {object} + */ + this.plugins = {}; + } + + /** + * Initialize the plugins. + * + * @protected + * @param {object} staticMap - The dictionary of staticly saved plugins. + */ + initPlugins(staticMap) + { + 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() + { + return this.view.width; + } + + /** + * Same as view.height, actual number of pixels in the canvas by vertical + * + * @member {number} + * @readonly + * @default 600 + */ + get height() + { + return this.view.height; + } + + /** + * Resizes the screen and canvas to the specified width and height + * Canvas dimensions are multiplied by resolution + * + * @param {number} screenWidth - the new width of the screen + * @param {number} screenHeight - the new height of the screen + */ + resize(screenWidth, screenHeight) + { + this.screen.width = screenWidth; + this.screen.height = screenHeight; + + this.view.width = screenWidth * this.resolution; + this.view.height = screenHeight * this.resolution; + + if (this.autoResize) + { + this.view.style.width = `${screenWidth}px`; + this.view.style.height = `${screenHeight}px`; + } + } + + /** + * 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 {PIXI.DisplayObject} displayObject - The displayObject the object will be generated from + * @param {number} scaleMode - Should be one of the scaleMode consts + * @param {number} resolution - The resolution / device pixel ratio of the texture being generated + * @param {PIXI.Rectangle} [region] - The region of the displayObject, that shall be rendered, + * if no region is specified, defaults to the local bounds of the displayObject. + * @return {PIXI.Texture} a texture of the graphics object + */ + generateTexture(displayObject, scaleMode, resolution, region) + { + region = region || displayObject.getLocalBounds(); + + // 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(region.width | 0, region.height | 0, scaleMode, resolution); + + tempMatrix.tx = -region.x; + tempMatrix.ty = -region.y; + + this.render(displayObject, renderTexture, false, tempMatrix, true); + + return renderTexture; + } + + /** + * Removes everything from the renderer and optionally removes the Canvas DOM element. + * + * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. + */ + destroy(removeView) + { + 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); + } + + this.plugins = null; + + this.type = RENDERER_TYPE.UNKNOWN; + + this.view = null; + + this.screen = null; + + this.resolution = 0; + + this.transparent = false; + + this.autoResize = false; + + this.blendModes = null; + + this.options = null; + + this.preserveDrawingBuffer = false; + this.clearBeforeRender = false; + + this.roundPixels = false; + + this._backgroundColor = 0; + this._backgroundColorRgba = null; + this._backgroundColorString = null; + + this._tempDisplayObjectParent = null; + this._lastObjectRendered = null; + } + + /** + * The background color to fill if not transparent + * + * @member {number} + */ + get backgroundColor() + { + return this._backgroundColor; + } + + set backgroundColor(value) // eslint-disable-line require-jsdoc + { + this._backgroundColor = value; + this._backgroundColorString = hex2string(value); + hex2rgb(value, this._backgroundColorRgba); + } +} diff --git a/packages/core/src/Renderer.js b/packages/core/src/Renderer.js new file mode 100644 index 0000000..2227e78 --- /dev/null +++ b/packages/core/src/Renderer.js @@ -0,0 +1,338 @@ +import AbstractRenderer from './AbstractRenderer'; +import { sayHello } from '@pixi/utils'; +import MaskSystem from './mask/MaskSystem'; +import StencilSystem from './mask/StencilSystem'; +import FilterSystem from './filters/FilterSystem'; +import FramebufferSystem from './framebuffer/FramebufferSystem'; +import RenderTextureSystem from './renderTexture/RenderTextureSystem'; +import TextureSystem from './textures/TextureSystem'; +import ProjectionSystem from './projection/ProjectionSystem'; +import StateSystem from './state/StateSystem'; +import GeometrySystem from './geometry/GeometrySystem'; +import ShaderSystem from './shader/ShaderSystem'; +import ContextSystem from './context/ContextSystem'; +import BatchSystem from './batch/BatchSystem'; +import TextureGCSystem from './textures/TextureGCSystem'; +import { RENDERER_TYPE } from '@pixi/constants'; +import UniformGroup from './shader/UniformGroup'; +import { Matrix } from '@pixi/math'; +import Runner from 'mini-runner'; + +/** + * 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 Render works by automatically managing webGLBatchs. + * 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 :) + * + * @class + * @memberof PIXI + * @extends PIXI.AbstractRenderer + */ +export default class Renderer extends AbstractRenderer +{ + // eslint-disable-next-line valid-jsdoc + /** + * + * @param {object} [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.transparent=false] - If the render view is transparent, default false + * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false + * @param {boolean} [options.antialias=false] - sets antialias. If not available natively then FXAA + * antialiasing is used + * @param {boolean} [options.forceFXAA=false] - forces FXAA antialiasing to be used over native. + * FXAA is faster, but may not always look as great + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. + * The resolution of the renderer retina would be 2. + * @param {boolean} [options.clearBeforeRender=true] - This sets if the renderer will clear + * the canvas or not before the new render pass. If you wish to set this to false, you *must* set + * preserveDrawingBuffer to `true`. + * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, + * enable this if you need to call toDataUrl on the webgl context. + * @param {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when + * rendering, stopping pixel interpolation. + * @param {number} [options.backgroundColor=0x000000] - The background color of the rendered area + * (shown if not transparent). + * @param {boolean} [options.legacy=false] - If true PixiJS will aim to ensure compatibility + * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. + * @param {string} [options.powerPreference] - Parameter passed to webgl context, set to "high-performance" + * for devices with dual graphics card + */ + constructor(options, arg2, arg3) + { + super('WebGL', options, arg2, arg3); + + /** + * The type of this renderer as a standardised const + * + * @member {number} + * @see PIXI.RENDERER_TYPE + */ + this.type = RENDERER_TYPE.WEBGL; + + // this will be set by the contextSystem (this.context) + this.gl = null; + this.CONTEXT_UID = 0; + this.legacy = !!options.legacy; + + // TODO legacy! + + // runners! + this.runners = { + destroy: new Runner('destroy'), + contextChange: new Runner('contextChange', 1), + reset: new Runner('reset'), + update: new Runner('update'), + postrender: new Runner('postrender'), + prerender: new Runner('prerender'), + resize: new Runner('resize', 2), + }; + + 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(GeometrySystem, 'geometry') + .addSystem(FramebufferSystem, 'framebuffer') + .addSystem(StencilSystem, 'stencil') + .addSystem(ProjectionSystem, 'projection') + .addSystem(TextureGCSystem, 'textureGC') + .addSystem(FilterSystem, 'filter') + .addSystem(RenderTextureSystem, 'renderTexture') + .addSystem(BatchSystem, 'batch'); + + this.initPlugins(Renderer.__plugins); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ + if (options.context) + { + this.context.initFromContext(options.context); + } + else + { + this.context.initFromOptions({ + alpha: this.transparent, + antialias: options.antialias, + premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', + stencil: true, + 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); + } + + /** + * Add a new system to the renderer. + * @param {class} ClassRef - Class reference + * @param {string} [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. + * @return {PIXI.Renderer} Return instance of renderer + */ + addSystem(ClassRef, name) + { + if (!name) + { + name = ClassRef.name; + } + + // TODO - read name from class.name.. + + /* + if(name.includes('System')) + { + name = name.replace('System', ''); + name = name.charAt(0).toLowerCase() + name.slice(1); + } + */ + + const system = new ClassRef(this); + + if (this[name]) + { + throw new Error(`Whoops! ${name} is already a manger`); + } + + this[name] = system; + + for (const i in this.runners) + { + this.runners[i].add(system); + } + + return this; + + /** + * 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. + */ + } + + /** + * Renders the object to its webGL view + * + * @param {PIXI.DisplayObject} displayObject - the object to be rendered + * @param {PIXI.RenderTexture} renderTexture - The render texture to render to. + * @param {boolean} [clear] - Should the canvas be cleared before the new render + * @param {PIXI.Transform} [transform] - A transform to apply to the render texture before rendering. + * @param {boolean} [skipUpdateTransform] - Should we skip the update transform pass? + */ + render(displayObject, renderTexture, clear, transform, skipUpdateTransform) + { + // can be handy to know! + this.renderingToScreen = !renderTexture; + + this.runners.prerender.run(); + this.emit('prerender'); + + // 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.parent; + + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = 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.run(); + + this.emit('postrender'); + } + + /** + * Resizes the webGL view to the specified width and height. + * + * @param {number} screenWidth - the new width of the screen + * @param {number} screenHeight - the new height of the screen + */ + resize(screenWidth, screenHeight) + { + AbstractRenderer.prototype.resize.call(this, screenWidth, screenHeight); + + this.runners.resize.run(screenWidth, screenHeight); + } + + /** + * Resets the WebGL state so you can render things however you fancy! + * + * @return {PIXI.Renderer} Returns itself. + */ + reset() + { + this.runners.reset.run(); + + return this; + } + + clear() + { + this.framebuffer.bind(); + this.framebuffer.clear(); + } + + /** + * Removes everything from the renderer (event listeners, spritebatch, etc...) + * + * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. + * See: https://github.com/pixijs/pixi.js/issues/2233 + */ + destroy(removeView) + { + this.runners.destroy.run(); + + // call base destroy + super.destroy(removeView); + + // TODO nullify all the managers.. + this.gl = null; + } + + /** + * 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 + * builds and excluding plugins. + * @name PIXI.Renderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. + */ + + /** + * Adds a plugin to the renderer. + * + * @method + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + static registerPlugin(pluginName, ctor) + { + Renderer.__plugins = Renderer.__plugins || {}; + Renderer.__plugins[pluginName] = ctor; + } +} diff --git a/packages/core/src/System.js b/packages/core/src/System.js new file mode 100644 index 0000000..05a10c8 --- /dev/null +++ b/packages/core/src/System.js @@ -0,0 +1,42 @@ +/** + * System is a base class used for extending systems used by the {@link PIXI.Renderer} + * @see PIXI.Renderer#addSystem + * @class + * @memberof PIXI + */ +export default class System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + /** + * The renderer this manager works for. + * + * @member {PIXI.Renderer} + */ + this.renderer = renderer; + + this.renderer.runners.contextChange.add(this); + } + + /** + * Generic method called when there is a WebGL context change. + * + */ + contextChange() + { + // do some codes init! + } + + /** + * Generic destroy methods to be overridden by the subclass + * + */ + destroy() + { + this.renderer.runners.contextChange.remove(this); + this.renderer = null; + } +} diff --git a/packages/core/src/batch/BatchSystem.js b/packages/core/src/batch/BatchSystem.js new file mode 100644 index 0000000..b04c249 --- /dev/null +++ b/packages/core/src/batch/BatchSystem.js @@ -0,0 +1,68 @@ +import System from '../System'; +import ObjectRenderer from './ObjectRenderer'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class BatchSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + super(renderer); + + /** + * An empty renderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.emptyRenderer = new ObjectRenderer(renderer); + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.currentRenderer = this.emptyRenderer; + } + + /** + * Changes the current renderer to the one given in parameter + * + * @param {PIXI.ObjectRenderer} objectRenderer - The object renderer to use. + */ + setObjectRenderer(objectRenderer) + { + if (this.currentRenderer === objectRenderer) + { + return; + } + + this.currentRenderer.stop(); + this.currentRenderer = objectRenderer; + + this.renderer.state.setState(objectRenderer.state); + + this.currentRenderer.start(); + } + + /** + * This should be called if you wish to do some custom rendering + * It will basically render anything that may be batched up such as sprites + * + */ + flush() + { + this.setObjectRenderer(this.emptyRenderer); + } + + reset() + { + this.setObjectRenderer(this.emptyRenderer); + } +} diff --git a/packages/core/src/AbstractRenderer.js b/packages/core/src/AbstractRenderer.js new file mode 100644 index 0000000..64b2eb1 --- /dev/null +++ b/packages/core/src/AbstractRenderer.js @@ -0,0 +1,353 @@ +import { hex2string, hex2rgb } from '@pixi/utils'; +import { Matrix, Rectangle } from '@pixi/math'; +import { RENDERER_TYPE } from '@pixi/constants'; +import { settings } from '@pixi/settings'; +import { Container } from '@pixi/display'; +import RenderTexture from './renderTexture/RenderTexture'; +import EventEmitter from 'eventemitter3'; + +const tempMatrix = new Matrix(); + +/** + * 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 EventEmitter + * @memberof PIXI + */ +export default class AbstractRenderer extends EventEmitter +{ + // eslint-disable-next-line valid-jsdoc + /** + * @param {string} system - The name of the system this renderer is for. + * @param {object} [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.transparent=false] - If the render view is transparent, default false + * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false + * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. The + * resolution of the renderer retina would be 2. + * @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 {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when rendering, + * stopping pixel interpolation. + */ + constructor(system, options, arg2, arg3) + { + super(); + + // Support for constructor(system, screenWidth, screenHeight, options) + if (typeof options === 'number') + { + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + }, arg3); + } + + // 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 = RENDERER_TYPE.UNKNOWN; + + /** + * Measurements of the screen. (0, 0, screenWidth, screenHeight) + * + * Its safe to use as filterArea or hitArea for 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 1 + */ + this.resolution = options.resolution || settings.RESOLUTION; + + /** + * Whether the render view is transparent + * + * @member {boolean} + */ + this.transparent = options.transparent; + + /** + * Whether css dimensions of canvas view should be resized to screen dimensions automatically + * + * @member {boolean} + */ + this.autoResize = options.autoResize || false; + + /** + * Tracks the blend modes useful for this renderer. + * + * @member {object} + */ + this.blendModes = null; + + /** + * 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; + + /** + * If true PixiJS will Math.floor() x/y values when rendering, stopping pixel interpolation. + * Handy for crisp pixel art and speed on legacy devices. + * + * @member {boolean} + */ + this.roundPixels = options.roundPixels; + + /** + * The background color as a number. + * + * @member {number} + * @private + */ + this._backgroundColor = 0x000000; + + /** + * The background color as an [R, G, B] array. + * + * @member {number[]} + * @private + */ + this._backgroundColorRgba = [0, 0, 0, 0]; + + /** + * The background color as a string. + * + * @member {string} + * @private + */ + this._backgroundColorString = '#000000'; + + this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter + + /** + * This temporary display object used as the parent of the currently being rendered item + * + * @member {PIXI.DisplayObject} + * @private + */ + this._tempDisplayObjectParent = new Container(); + + /** + * The last root object that the renderer tried to render. + * + * @member {PIXI.DisplayObject} + * @private + */ + this._lastObjectRendered = this._tempDisplayObjectParent; + + /** + * Collection of plugins. + * @readonly + * @member {object} + */ + this.plugins = {}; + } + + /** + * Initialize the plugins. + * + * @protected + * @param {object} staticMap - The dictionary of staticly saved plugins. + */ + initPlugins(staticMap) + { + 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() + { + return this.view.width; + } + + /** + * Same as view.height, actual number of pixels in the canvas by vertical + * + * @member {number} + * @readonly + * @default 600 + */ + get height() + { + return this.view.height; + } + + /** + * Resizes the screen and canvas to the specified width and height + * Canvas dimensions are multiplied by resolution + * + * @param {number} screenWidth - the new width of the screen + * @param {number} screenHeight - the new height of the screen + */ + resize(screenWidth, screenHeight) + { + this.screen.width = screenWidth; + this.screen.height = screenHeight; + + this.view.width = screenWidth * this.resolution; + this.view.height = screenHeight * this.resolution; + + if (this.autoResize) + { + this.view.style.width = `${screenWidth}px`; + this.view.style.height = `${screenHeight}px`; + } + } + + /** + * 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 {PIXI.DisplayObject} displayObject - The displayObject the object will be generated from + * @param {number} scaleMode - Should be one of the scaleMode consts + * @param {number} resolution - The resolution / device pixel ratio of the texture being generated + * @param {PIXI.Rectangle} [region] - The region of the displayObject, that shall be rendered, + * if no region is specified, defaults to the local bounds of the displayObject. + * @return {PIXI.Texture} a texture of the graphics object + */ + generateTexture(displayObject, scaleMode, resolution, region) + { + region = region || displayObject.getLocalBounds(); + + // 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(region.width | 0, region.height | 0, scaleMode, resolution); + + tempMatrix.tx = -region.x; + tempMatrix.ty = -region.y; + + this.render(displayObject, renderTexture, false, tempMatrix, true); + + return renderTexture; + } + + /** + * Removes everything from the renderer and optionally removes the Canvas DOM element. + * + * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. + */ + destroy(removeView) + { + 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); + } + + this.plugins = null; + + this.type = RENDERER_TYPE.UNKNOWN; + + this.view = null; + + this.screen = null; + + this.resolution = 0; + + this.transparent = false; + + this.autoResize = false; + + this.blendModes = null; + + this.options = null; + + this.preserveDrawingBuffer = false; + this.clearBeforeRender = false; + + this.roundPixels = false; + + this._backgroundColor = 0; + this._backgroundColorRgba = null; + this._backgroundColorString = null; + + this._tempDisplayObjectParent = null; + this._lastObjectRendered = null; + } + + /** + * The background color to fill if not transparent + * + * @member {number} + */ + get backgroundColor() + { + return this._backgroundColor; + } + + set backgroundColor(value) // eslint-disable-line require-jsdoc + { + this._backgroundColor = value; + this._backgroundColorString = hex2string(value); + hex2rgb(value, this._backgroundColorRgba); + } +} diff --git a/packages/core/src/Renderer.js b/packages/core/src/Renderer.js new file mode 100644 index 0000000..2227e78 --- /dev/null +++ b/packages/core/src/Renderer.js @@ -0,0 +1,338 @@ +import AbstractRenderer from './AbstractRenderer'; +import { sayHello } from '@pixi/utils'; +import MaskSystem from './mask/MaskSystem'; +import StencilSystem from './mask/StencilSystem'; +import FilterSystem from './filters/FilterSystem'; +import FramebufferSystem from './framebuffer/FramebufferSystem'; +import RenderTextureSystem from './renderTexture/RenderTextureSystem'; +import TextureSystem from './textures/TextureSystem'; +import ProjectionSystem from './projection/ProjectionSystem'; +import StateSystem from './state/StateSystem'; +import GeometrySystem from './geometry/GeometrySystem'; +import ShaderSystem from './shader/ShaderSystem'; +import ContextSystem from './context/ContextSystem'; +import BatchSystem from './batch/BatchSystem'; +import TextureGCSystem from './textures/TextureGCSystem'; +import { RENDERER_TYPE } from '@pixi/constants'; +import UniformGroup from './shader/UniformGroup'; +import { Matrix } from '@pixi/math'; +import Runner from 'mini-runner'; + +/** + * 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 Render works by automatically managing webGLBatchs. + * 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 :) + * + * @class + * @memberof PIXI + * @extends PIXI.AbstractRenderer + */ +export default class Renderer extends AbstractRenderer +{ + // eslint-disable-next-line valid-jsdoc + /** + * + * @param {object} [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.transparent=false] - If the render view is transparent, default false + * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false + * @param {boolean} [options.antialias=false] - sets antialias. If not available natively then FXAA + * antialiasing is used + * @param {boolean} [options.forceFXAA=false] - forces FXAA antialiasing to be used over native. + * FXAA is faster, but may not always look as great + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. + * The resolution of the renderer retina would be 2. + * @param {boolean} [options.clearBeforeRender=true] - This sets if the renderer will clear + * the canvas or not before the new render pass. If you wish to set this to false, you *must* set + * preserveDrawingBuffer to `true`. + * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, + * enable this if you need to call toDataUrl on the webgl context. + * @param {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when + * rendering, stopping pixel interpolation. + * @param {number} [options.backgroundColor=0x000000] - The background color of the rendered area + * (shown if not transparent). + * @param {boolean} [options.legacy=false] - If true PixiJS will aim to ensure compatibility + * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. + * @param {string} [options.powerPreference] - Parameter passed to webgl context, set to "high-performance" + * for devices with dual graphics card + */ + constructor(options, arg2, arg3) + { + super('WebGL', options, arg2, arg3); + + /** + * The type of this renderer as a standardised const + * + * @member {number} + * @see PIXI.RENDERER_TYPE + */ + this.type = RENDERER_TYPE.WEBGL; + + // this will be set by the contextSystem (this.context) + this.gl = null; + this.CONTEXT_UID = 0; + this.legacy = !!options.legacy; + + // TODO legacy! + + // runners! + this.runners = { + destroy: new Runner('destroy'), + contextChange: new Runner('contextChange', 1), + reset: new Runner('reset'), + update: new Runner('update'), + postrender: new Runner('postrender'), + prerender: new Runner('prerender'), + resize: new Runner('resize', 2), + }; + + 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(GeometrySystem, 'geometry') + .addSystem(FramebufferSystem, 'framebuffer') + .addSystem(StencilSystem, 'stencil') + .addSystem(ProjectionSystem, 'projection') + .addSystem(TextureGCSystem, 'textureGC') + .addSystem(FilterSystem, 'filter') + .addSystem(RenderTextureSystem, 'renderTexture') + .addSystem(BatchSystem, 'batch'); + + this.initPlugins(Renderer.__plugins); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ + if (options.context) + { + this.context.initFromContext(options.context); + } + else + { + this.context.initFromOptions({ + alpha: this.transparent, + antialias: options.antialias, + premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', + stencil: true, + 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); + } + + /** + * Add a new system to the renderer. + * @param {class} ClassRef - Class reference + * @param {string} [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. + * @return {PIXI.Renderer} Return instance of renderer + */ + addSystem(ClassRef, name) + { + if (!name) + { + name = ClassRef.name; + } + + // TODO - read name from class.name.. + + /* + if(name.includes('System')) + { + name = name.replace('System', ''); + name = name.charAt(0).toLowerCase() + name.slice(1); + } + */ + + const system = new ClassRef(this); + + if (this[name]) + { + throw new Error(`Whoops! ${name} is already a manger`); + } + + this[name] = system; + + for (const i in this.runners) + { + this.runners[i].add(system); + } + + return this; + + /** + * 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. + */ + } + + /** + * Renders the object to its webGL view + * + * @param {PIXI.DisplayObject} displayObject - the object to be rendered + * @param {PIXI.RenderTexture} renderTexture - The render texture to render to. + * @param {boolean} [clear] - Should the canvas be cleared before the new render + * @param {PIXI.Transform} [transform] - A transform to apply to the render texture before rendering. + * @param {boolean} [skipUpdateTransform] - Should we skip the update transform pass? + */ + render(displayObject, renderTexture, clear, transform, skipUpdateTransform) + { + // can be handy to know! + this.renderingToScreen = !renderTexture; + + this.runners.prerender.run(); + this.emit('prerender'); + + // 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.parent; + + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = 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.run(); + + this.emit('postrender'); + } + + /** + * Resizes the webGL view to the specified width and height. + * + * @param {number} screenWidth - the new width of the screen + * @param {number} screenHeight - the new height of the screen + */ + resize(screenWidth, screenHeight) + { + AbstractRenderer.prototype.resize.call(this, screenWidth, screenHeight); + + this.runners.resize.run(screenWidth, screenHeight); + } + + /** + * Resets the WebGL state so you can render things however you fancy! + * + * @return {PIXI.Renderer} Returns itself. + */ + reset() + { + this.runners.reset.run(); + + return this; + } + + clear() + { + this.framebuffer.bind(); + this.framebuffer.clear(); + } + + /** + * Removes everything from the renderer (event listeners, spritebatch, etc...) + * + * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. + * See: https://github.com/pixijs/pixi.js/issues/2233 + */ + destroy(removeView) + { + this.runners.destroy.run(); + + // call base destroy + super.destroy(removeView); + + // TODO nullify all the managers.. + this.gl = null; + } + + /** + * 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 + * builds and excluding plugins. + * @name PIXI.Renderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. + */ + + /** + * Adds a plugin to the renderer. + * + * @method + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + static registerPlugin(pluginName, ctor) + { + Renderer.__plugins = Renderer.__plugins || {}; + Renderer.__plugins[pluginName] = ctor; + } +} diff --git a/packages/core/src/System.js b/packages/core/src/System.js new file mode 100644 index 0000000..05a10c8 --- /dev/null +++ b/packages/core/src/System.js @@ -0,0 +1,42 @@ +/** + * System is a base class used for extending systems used by the {@link PIXI.Renderer} + * @see PIXI.Renderer#addSystem + * @class + * @memberof PIXI + */ +export default class System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + /** + * The renderer this manager works for. + * + * @member {PIXI.Renderer} + */ + this.renderer = renderer; + + this.renderer.runners.contextChange.add(this); + } + + /** + * Generic method called when there is a WebGL context change. + * + */ + contextChange() + { + // do some codes init! + } + + /** + * Generic destroy methods to be overridden by the subclass + * + */ + destroy() + { + this.renderer.runners.contextChange.remove(this); + this.renderer = null; + } +} diff --git a/packages/core/src/batch/BatchSystem.js b/packages/core/src/batch/BatchSystem.js new file mode 100644 index 0000000..b04c249 --- /dev/null +++ b/packages/core/src/batch/BatchSystem.js @@ -0,0 +1,68 @@ +import System from '../System'; +import ObjectRenderer from './ObjectRenderer'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class BatchSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + super(renderer); + + /** + * An empty renderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.emptyRenderer = new ObjectRenderer(renderer); + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.currentRenderer = this.emptyRenderer; + } + + /** + * Changes the current renderer to the one given in parameter + * + * @param {PIXI.ObjectRenderer} objectRenderer - The object renderer to use. + */ + setObjectRenderer(objectRenderer) + { + if (this.currentRenderer === objectRenderer) + { + return; + } + + this.currentRenderer.stop(); + this.currentRenderer = objectRenderer; + + this.renderer.state.setState(objectRenderer.state); + + this.currentRenderer.start(); + } + + /** + * This should be called if you wish to do some custom rendering + * It will basically render anything that may be batched up such as sprites + * + */ + flush() + { + this.setObjectRenderer(this.emptyRenderer); + } + + reset() + { + this.setObjectRenderer(this.emptyRenderer); + } +} diff --git a/packages/core/src/batch/ObjectRenderer.js b/packages/core/src/batch/ObjectRenderer.js new file mode 100644 index 0000000..0c851a9 --- /dev/null +++ b/packages/core/src/batch/ObjectRenderer.js @@ -0,0 +1,48 @@ +import System from '../System'; + +/** + * Base for a common object renderer that can be used as a system renderer plugin. + * + * @class + * @extends PIXI.System + * @memberof PIXI + */ +export default class ObjectRenderer extends System +{ + /** + * Starts the renderer and sets the shader + * + */ + start() + { + // set the shader.. + } + + /** + * Stops the renderer + * + */ + stop() + { + this.flush(); + } + + /** + * Stub method for rendering content and emptying the current batch. + * + */ + flush() + { + // flush! + } + + /** + * Renders an object + * + * @param {PIXI.DisplayObject} object - The object to render. + */ + render(object) // eslint-disable-line no-unused-vars + { + // render the object + } +} diff --git a/packages/core/src/AbstractRenderer.js b/packages/core/src/AbstractRenderer.js new file mode 100644 index 0000000..64b2eb1 --- /dev/null +++ b/packages/core/src/AbstractRenderer.js @@ -0,0 +1,353 @@ +import { hex2string, hex2rgb } from '@pixi/utils'; +import { Matrix, Rectangle } from '@pixi/math'; +import { RENDERER_TYPE } from '@pixi/constants'; +import { settings } from '@pixi/settings'; +import { Container } from '@pixi/display'; +import RenderTexture from './renderTexture/RenderTexture'; +import EventEmitter from 'eventemitter3'; + +const tempMatrix = new Matrix(); + +/** + * 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 EventEmitter + * @memberof PIXI + */ +export default class AbstractRenderer extends EventEmitter +{ + // eslint-disable-next-line valid-jsdoc + /** + * @param {string} system - The name of the system this renderer is for. + * @param {object} [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.transparent=false] - If the render view is transparent, default false + * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false + * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. The + * resolution of the renderer retina would be 2. + * @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 {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when rendering, + * stopping pixel interpolation. + */ + constructor(system, options, arg2, arg3) + { + super(); + + // Support for constructor(system, screenWidth, screenHeight, options) + if (typeof options === 'number') + { + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + }, arg3); + } + + // 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 = RENDERER_TYPE.UNKNOWN; + + /** + * Measurements of the screen. (0, 0, screenWidth, screenHeight) + * + * Its safe to use as filterArea or hitArea for 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 1 + */ + this.resolution = options.resolution || settings.RESOLUTION; + + /** + * Whether the render view is transparent + * + * @member {boolean} + */ + this.transparent = options.transparent; + + /** + * Whether css dimensions of canvas view should be resized to screen dimensions automatically + * + * @member {boolean} + */ + this.autoResize = options.autoResize || false; + + /** + * Tracks the blend modes useful for this renderer. + * + * @member {object} + */ + this.blendModes = null; + + /** + * 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; + + /** + * If true PixiJS will Math.floor() x/y values when rendering, stopping pixel interpolation. + * Handy for crisp pixel art and speed on legacy devices. + * + * @member {boolean} + */ + this.roundPixels = options.roundPixels; + + /** + * The background color as a number. + * + * @member {number} + * @private + */ + this._backgroundColor = 0x000000; + + /** + * The background color as an [R, G, B] array. + * + * @member {number[]} + * @private + */ + this._backgroundColorRgba = [0, 0, 0, 0]; + + /** + * The background color as a string. + * + * @member {string} + * @private + */ + this._backgroundColorString = '#000000'; + + this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter + + /** + * This temporary display object used as the parent of the currently being rendered item + * + * @member {PIXI.DisplayObject} + * @private + */ + this._tempDisplayObjectParent = new Container(); + + /** + * The last root object that the renderer tried to render. + * + * @member {PIXI.DisplayObject} + * @private + */ + this._lastObjectRendered = this._tempDisplayObjectParent; + + /** + * Collection of plugins. + * @readonly + * @member {object} + */ + this.plugins = {}; + } + + /** + * Initialize the plugins. + * + * @protected + * @param {object} staticMap - The dictionary of staticly saved plugins. + */ + initPlugins(staticMap) + { + 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() + { + return this.view.width; + } + + /** + * Same as view.height, actual number of pixels in the canvas by vertical + * + * @member {number} + * @readonly + * @default 600 + */ + get height() + { + return this.view.height; + } + + /** + * Resizes the screen and canvas to the specified width and height + * Canvas dimensions are multiplied by resolution + * + * @param {number} screenWidth - the new width of the screen + * @param {number} screenHeight - the new height of the screen + */ + resize(screenWidth, screenHeight) + { + this.screen.width = screenWidth; + this.screen.height = screenHeight; + + this.view.width = screenWidth * this.resolution; + this.view.height = screenHeight * this.resolution; + + if (this.autoResize) + { + this.view.style.width = `${screenWidth}px`; + this.view.style.height = `${screenHeight}px`; + } + } + + /** + * 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 {PIXI.DisplayObject} displayObject - The displayObject the object will be generated from + * @param {number} scaleMode - Should be one of the scaleMode consts + * @param {number} resolution - The resolution / device pixel ratio of the texture being generated + * @param {PIXI.Rectangle} [region] - The region of the displayObject, that shall be rendered, + * if no region is specified, defaults to the local bounds of the displayObject. + * @return {PIXI.Texture} a texture of the graphics object + */ + generateTexture(displayObject, scaleMode, resolution, region) + { + region = region || displayObject.getLocalBounds(); + + // 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(region.width | 0, region.height | 0, scaleMode, resolution); + + tempMatrix.tx = -region.x; + tempMatrix.ty = -region.y; + + this.render(displayObject, renderTexture, false, tempMatrix, true); + + return renderTexture; + } + + /** + * Removes everything from the renderer and optionally removes the Canvas DOM element. + * + * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. + */ + destroy(removeView) + { + 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); + } + + this.plugins = null; + + this.type = RENDERER_TYPE.UNKNOWN; + + this.view = null; + + this.screen = null; + + this.resolution = 0; + + this.transparent = false; + + this.autoResize = false; + + this.blendModes = null; + + this.options = null; + + this.preserveDrawingBuffer = false; + this.clearBeforeRender = false; + + this.roundPixels = false; + + this._backgroundColor = 0; + this._backgroundColorRgba = null; + this._backgroundColorString = null; + + this._tempDisplayObjectParent = null; + this._lastObjectRendered = null; + } + + /** + * The background color to fill if not transparent + * + * @member {number} + */ + get backgroundColor() + { + return this._backgroundColor; + } + + set backgroundColor(value) // eslint-disable-line require-jsdoc + { + this._backgroundColor = value; + this._backgroundColorString = hex2string(value); + hex2rgb(value, this._backgroundColorRgba); + } +} diff --git a/packages/core/src/Renderer.js b/packages/core/src/Renderer.js new file mode 100644 index 0000000..2227e78 --- /dev/null +++ b/packages/core/src/Renderer.js @@ -0,0 +1,338 @@ +import AbstractRenderer from './AbstractRenderer'; +import { sayHello } from '@pixi/utils'; +import MaskSystem from './mask/MaskSystem'; +import StencilSystem from './mask/StencilSystem'; +import FilterSystem from './filters/FilterSystem'; +import FramebufferSystem from './framebuffer/FramebufferSystem'; +import RenderTextureSystem from './renderTexture/RenderTextureSystem'; +import TextureSystem from './textures/TextureSystem'; +import ProjectionSystem from './projection/ProjectionSystem'; +import StateSystem from './state/StateSystem'; +import GeometrySystem from './geometry/GeometrySystem'; +import ShaderSystem from './shader/ShaderSystem'; +import ContextSystem from './context/ContextSystem'; +import BatchSystem from './batch/BatchSystem'; +import TextureGCSystem from './textures/TextureGCSystem'; +import { RENDERER_TYPE } from '@pixi/constants'; +import UniformGroup from './shader/UniformGroup'; +import { Matrix } from '@pixi/math'; +import Runner from 'mini-runner'; + +/** + * 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 Render works by automatically managing webGLBatchs. + * 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 :) + * + * @class + * @memberof PIXI + * @extends PIXI.AbstractRenderer + */ +export default class Renderer extends AbstractRenderer +{ + // eslint-disable-next-line valid-jsdoc + /** + * + * @param {object} [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.transparent=false] - If the render view is transparent, default false + * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false + * @param {boolean} [options.antialias=false] - sets antialias. If not available natively then FXAA + * antialiasing is used + * @param {boolean} [options.forceFXAA=false] - forces FXAA antialiasing to be used over native. + * FXAA is faster, but may not always look as great + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. + * The resolution of the renderer retina would be 2. + * @param {boolean} [options.clearBeforeRender=true] - This sets if the renderer will clear + * the canvas or not before the new render pass. If you wish to set this to false, you *must* set + * preserveDrawingBuffer to `true`. + * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, + * enable this if you need to call toDataUrl on the webgl context. + * @param {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when + * rendering, stopping pixel interpolation. + * @param {number} [options.backgroundColor=0x000000] - The background color of the rendered area + * (shown if not transparent). + * @param {boolean} [options.legacy=false] - If true PixiJS will aim to ensure compatibility + * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. + * @param {string} [options.powerPreference] - Parameter passed to webgl context, set to "high-performance" + * for devices with dual graphics card + */ + constructor(options, arg2, arg3) + { + super('WebGL', options, arg2, arg3); + + /** + * The type of this renderer as a standardised const + * + * @member {number} + * @see PIXI.RENDERER_TYPE + */ + this.type = RENDERER_TYPE.WEBGL; + + // this will be set by the contextSystem (this.context) + this.gl = null; + this.CONTEXT_UID = 0; + this.legacy = !!options.legacy; + + // TODO legacy! + + // runners! + this.runners = { + destroy: new Runner('destroy'), + contextChange: new Runner('contextChange', 1), + reset: new Runner('reset'), + update: new Runner('update'), + postrender: new Runner('postrender'), + prerender: new Runner('prerender'), + resize: new Runner('resize', 2), + }; + + 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(GeometrySystem, 'geometry') + .addSystem(FramebufferSystem, 'framebuffer') + .addSystem(StencilSystem, 'stencil') + .addSystem(ProjectionSystem, 'projection') + .addSystem(TextureGCSystem, 'textureGC') + .addSystem(FilterSystem, 'filter') + .addSystem(RenderTextureSystem, 'renderTexture') + .addSystem(BatchSystem, 'batch'); + + this.initPlugins(Renderer.__plugins); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ + if (options.context) + { + this.context.initFromContext(options.context); + } + else + { + this.context.initFromOptions({ + alpha: this.transparent, + antialias: options.antialias, + premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', + stencil: true, + 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); + } + + /** + * Add a new system to the renderer. + * @param {class} ClassRef - Class reference + * @param {string} [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. + * @return {PIXI.Renderer} Return instance of renderer + */ + addSystem(ClassRef, name) + { + if (!name) + { + name = ClassRef.name; + } + + // TODO - read name from class.name.. + + /* + if(name.includes('System')) + { + name = name.replace('System', ''); + name = name.charAt(0).toLowerCase() + name.slice(1); + } + */ + + const system = new ClassRef(this); + + if (this[name]) + { + throw new Error(`Whoops! ${name} is already a manger`); + } + + this[name] = system; + + for (const i in this.runners) + { + this.runners[i].add(system); + } + + return this; + + /** + * 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. + */ + } + + /** + * Renders the object to its webGL view + * + * @param {PIXI.DisplayObject} displayObject - the object to be rendered + * @param {PIXI.RenderTexture} renderTexture - The render texture to render to. + * @param {boolean} [clear] - Should the canvas be cleared before the new render + * @param {PIXI.Transform} [transform] - A transform to apply to the render texture before rendering. + * @param {boolean} [skipUpdateTransform] - Should we skip the update transform pass? + */ + render(displayObject, renderTexture, clear, transform, skipUpdateTransform) + { + // can be handy to know! + this.renderingToScreen = !renderTexture; + + this.runners.prerender.run(); + this.emit('prerender'); + + // 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.parent; + + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = 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.run(); + + this.emit('postrender'); + } + + /** + * Resizes the webGL view to the specified width and height. + * + * @param {number} screenWidth - the new width of the screen + * @param {number} screenHeight - the new height of the screen + */ + resize(screenWidth, screenHeight) + { + AbstractRenderer.prototype.resize.call(this, screenWidth, screenHeight); + + this.runners.resize.run(screenWidth, screenHeight); + } + + /** + * Resets the WebGL state so you can render things however you fancy! + * + * @return {PIXI.Renderer} Returns itself. + */ + reset() + { + this.runners.reset.run(); + + return this; + } + + clear() + { + this.framebuffer.bind(); + this.framebuffer.clear(); + } + + /** + * Removes everything from the renderer (event listeners, spritebatch, etc...) + * + * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. + * See: https://github.com/pixijs/pixi.js/issues/2233 + */ + destroy(removeView) + { + this.runners.destroy.run(); + + // call base destroy + super.destroy(removeView); + + // TODO nullify all the managers.. + this.gl = null; + } + + /** + * 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 + * builds and excluding plugins. + * @name PIXI.Renderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. + */ + + /** + * Adds a plugin to the renderer. + * + * @method + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + static registerPlugin(pluginName, ctor) + { + Renderer.__plugins = Renderer.__plugins || {}; + Renderer.__plugins[pluginName] = ctor; + } +} diff --git a/packages/core/src/System.js b/packages/core/src/System.js new file mode 100644 index 0000000..05a10c8 --- /dev/null +++ b/packages/core/src/System.js @@ -0,0 +1,42 @@ +/** + * System is a base class used for extending systems used by the {@link PIXI.Renderer} + * @see PIXI.Renderer#addSystem + * @class + * @memberof PIXI + */ +export default class System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + /** + * The renderer this manager works for. + * + * @member {PIXI.Renderer} + */ + this.renderer = renderer; + + this.renderer.runners.contextChange.add(this); + } + + /** + * Generic method called when there is a WebGL context change. + * + */ + contextChange() + { + // do some codes init! + } + + /** + * Generic destroy methods to be overridden by the subclass + * + */ + destroy() + { + this.renderer.runners.contextChange.remove(this); + this.renderer = null; + } +} diff --git a/packages/core/src/batch/BatchSystem.js b/packages/core/src/batch/BatchSystem.js new file mode 100644 index 0000000..b04c249 --- /dev/null +++ b/packages/core/src/batch/BatchSystem.js @@ -0,0 +1,68 @@ +import System from '../System'; +import ObjectRenderer from './ObjectRenderer'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class BatchSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + super(renderer); + + /** + * An empty renderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.emptyRenderer = new ObjectRenderer(renderer); + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.currentRenderer = this.emptyRenderer; + } + + /** + * Changes the current renderer to the one given in parameter + * + * @param {PIXI.ObjectRenderer} objectRenderer - The object renderer to use. + */ + setObjectRenderer(objectRenderer) + { + if (this.currentRenderer === objectRenderer) + { + return; + } + + this.currentRenderer.stop(); + this.currentRenderer = objectRenderer; + + this.renderer.state.setState(objectRenderer.state); + + this.currentRenderer.start(); + } + + /** + * This should be called if you wish to do some custom rendering + * It will basically render anything that may be batched up such as sprites + * + */ + flush() + { + this.setObjectRenderer(this.emptyRenderer); + } + + reset() + { + this.setObjectRenderer(this.emptyRenderer); + } +} diff --git a/packages/core/src/batch/ObjectRenderer.js b/packages/core/src/batch/ObjectRenderer.js new file mode 100644 index 0000000..0c851a9 --- /dev/null +++ b/packages/core/src/batch/ObjectRenderer.js @@ -0,0 +1,48 @@ +import System from '../System'; + +/** + * Base for a common object renderer that can be used as a system renderer plugin. + * + * @class + * @extends PIXI.System + * @memberof PIXI + */ +export default class ObjectRenderer extends System +{ + /** + * Starts the renderer and sets the shader + * + */ + start() + { + // set the shader.. + } + + /** + * Stops the renderer + * + */ + stop() + { + this.flush(); + } + + /** + * Stub method for rendering content and emptying the current batch. + * + */ + flush() + { + // flush! + } + + /** + * Renders an object + * + * @param {PIXI.DisplayObject} object - The object to render. + */ + render(object) // eslint-disable-line no-unused-vars + { + // render the object + } +} diff --git a/packages/core/src/context/ContextSystem.js b/packages/core/src/context/ContextSystem.js new file mode 100644 index 0000000..301aab4 --- /dev/null +++ b/packages/core/src/context/ContextSystem.js @@ -0,0 +1,186 @@ +import System from '../System'; +import { settings } from '@pixi/settings'; + +let CONTEXT_UID = 0; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class ContextSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.webGLVersion = 1; + + this.handleContextLost = this.handleContextLost.bind(this); + this.handleContextRestored = this.handleContextRestored.bind(this); + + this.extensions = {}; + + renderer.view.addEventListener('webglcontextlost', this.handleContextLost, false); + renderer.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + } + + get isLost() + { + return (!this.gl || this.gl.isContextLost()); + } + + contextChange(gl) + { + this.gl = gl; + + // restore a context if it was previously lost + if (gl.isContextLost() && gl.getExtension('WEBGL_lose_context')) + { + gl.getExtension('WEBGL_lose_context').restoreContext(); + } + } + + initFromContext(gl) + { + this.gl = gl; + this.validateContext(gl); + this.renderer.gl = gl; + this.renderer.CONTEXT_UID = CONTEXT_UID++; + this.renderer.runners.contextChange.run(gl); + } + + initFromOptions(options) + { + const gl = this.createContext(this.renderer.view, options); + + this.initFromContext(gl); + } + + /** + * Helper class to create a webGL Context + * + * @class + * @memberof PIXI.glCore + * @param canvas {HTMLCanvasElement} the canvas element that we will get the context from + * @param options {Object} An options object that gets passed in to the canvas element containing the context attributes, + * see https://developer.mozilla.org/en/docs/Web/API/HTMLCanvasElement/getContext for the options available + * @return {WebGLRenderingContext} the WebGL context + */ + createContext(canvas, options) + { + let gl; + + if (settings.PREFER_WEBGL_2) + { + gl = canvas.getContext('webgl2', options); + } + + if (gl) + { + this.webGLVersion = 2; + } + else + { + this.webGLVersion = 1; + + gl = canvas.getContext('webgl', options) + || canvas.getContext('experimental-webgl', options); + + if (!gl) + { + // fail, not able to get a context + throw new Error('This browser does not support webGL. Try using the canvas renderer'); + } + } + + this.gl = gl; + + this.getExtensions(); + + return gl; + } + + getExtensions() + { + // time to set up default etensions that pixi uses.. + const gl = this.gl; + const extensions = this.extensions; + + if (this.webGLVersion === 1) + { + extensions.drawBuffers = gl.getExtension('WEBGL_draw_buffers'); + extensions.depthTexture = gl.getExtension('WEBKIT_WEBGL_depth_texture'); + extensions.floatTexture = gl.getExtension('OES_texture_float'); + extensions.loseContext = gl.getExtension('WEBGL_lose_context'); + + extensions.vertexArrayObject = gl.getExtension('OES_vertex_array_object') + || gl.getExtension('MOZ_OES_vertex_array_object') + || gl.getExtension('WEBKIT_OES_vertex_array_object'); + } + + // we don't use any specific WebGL 2 ones yet! + } + + /** + * Handles a lost webgl context + * + * @private + * @param {WebGLContextEvent} event - The context lost event. + */ + handleContextLost(event) + { + event.preventDefault(); + } + + /** + * Handles a restored webgl context + * + * @private + */ + handleContextRestored() + { + this.renderer.runners.contextChange.run(this.gl); + } + + destroy() + { + const view = this.renderer.view; + + // remove listeners + view.removeEventListener('webglcontextlost', this.handleContextLost); + view.removeEventListener('webglcontextrestored', this.handleContextRestored); + + this.gl.useProgram(null); + + if (this.extensions.loseContext) + { + this.extensions.loseContext.loseContext(); + } + } + + postrender() + { + this.gl.flush(); + } + + validateContext(gl) + { + const attributes = gl.getContextAttributes(); + + // this is going to be fairly simple for now.. but at least we have room to grow! + if (!attributes.stencil) + { + /* eslint-disable max-len */ + + /* eslint-disable no-console */ + console.warn('Provided WebGL context does not have a stencil buffer, masks may not render correctly'); + /* eslint-enable no-console */ + + /* eslint-enable max-len */ + } + } +} diff --git a/packages/core/src/AbstractRenderer.js b/packages/core/src/AbstractRenderer.js new file mode 100644 index 0000000..64b2eb1 --- /dev/null +++ b/packages/core/src/AbstractRenderer.js @@ -0,0 +1,353 @@ +import { hex2string, hex2rgb } from '@pixi/utils'; +import { Matrix, Rectangle } from '@pixi/math'; +import { RENDERER_TYPE } from '@pixi/constants'; +import { settings } from '@pixi/settings'; +import { Container } from '@pixi/display'; +import RenderTexture from './renderTexture/RenderTexture'; +import EventEmitter from 'eventemitter3'; + +const tempMatrix = new Matrix(); + +/** + * 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 EventEmitter + * @memberof PIXI + */ +export default class AbstractRenderer extends EventEmitter +{ + // eslint-disable-next-line valid-jsdoc + /** + * @param {string} system - The name of the system this renderer is for. + * @param {object} [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.transparent=false] - If the render view is transparent, default false + * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false + * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. The + * resolution of the renderer retina would be 2. + * @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 {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when rendering, + * stopping pixel interpolation. + */ + constructor(system, options, arg2, arg3) + { + super(); + + // Support for constructor(system, screenWidth, screenHeight, options) + if (typeof options === 'number') + { + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + }, arg3); + } + + // 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 = RENDERER_TYPE.UNKNOWN; + + /** + * Measurements of the screen. (0, 0, screenWidth, screenHeight) + * + * Its safe to use as filterArea or hitArea for 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 1 + */ + this.resolution = options.resolution || settings.RESOLUTION; + + /** + * Whether the render view is transparent + * + * @member {boolean} + */ + this.transparent = options.transparent; + + /** + * Whether css dimensions of canvas view should be resized to screen dimensions automatically + * + * @member {boolean} + */ + this.autoResize = options.autoResize || false; + + /** + * Tracks the blend modes useful for this renderer. + * + * @member {object} + */ + this.blendModes = null; + + /** + * 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; + + /** + * If true PixiJS will Math.floor() x/y values when rendering, stopping pixel interpolation. + * Handy for crisp pixel art and speed on legacy devices. + * + * @member {boolean} + */ + this.roundPixels = options.roundPixels; + + /** + * The background color as a number. + * + * @member {number} + * @private + */ + this._backgroundColor = 0x000000; + + /** + * The background color as an [R, G, B] array. + * + * @member {number[]} + * @private + */ + this._backgroundColorRgba = [0, 0, 0, 0]; + + /** + * The background color as a string. + * + * @member {string} + * @private + */ + this._backgroundColorString = '#000000'; + + this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter + + /** + * This temporary display object used as the parent of the currently being rendered item + * + * @member {PIXI.DisplayObject} + * @private + */ + this._tempDisplayObjectParent = new Container(); + + /** + * The last root object that the renderer tried to render. + * + * @member {PIXI.DisplayObject} + * @private + */ + this._lastObjectRendered = this._tempDisplayObjectParent; + + /** + * Collection of plugins. + * @readonly + * @member {object} + */ + this.plugins = {}; + } + + /** + * Initialize the plugins. + * + * @protected + * @param {object} staticMap - The dictionary of staticly saved plugins. + */ + initPlugins(staticMap) + { + 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() + { + return this.view.width; + } + + /** + * Same as view.height, actual number of pixels in the canvas by vertical + * + * @member {number} + * @readonly + * @default 600 + */ + get height() + { + return this.view.height; + } + + /** + * Resizes the screen and canvas to the specified width and height + * Canvas dimensions are multiplied by resolution + * + * @param {number} screenWidth - the new width of the screen + * @param {number} screenHeight - the new height of the screen + */ + resize(screenWidth, screenHeight) + { + this.screen.width = screenWidth; + this.screen.height = screenHeight; + + this.view.width = screenWidth * this.resolution; + this.view.height = screenHeight * this.resolution; + + if (this.autoResize) + { + this.view.style.width = `${screenWidth}px`; + this.view.style.height = `${screenHeight}px`; + } + } + + /** + * 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 {PIXI.DisplayObject} displayObject - The displayObject the object will be generated from + * @param {number} scaleMode - Should be one of the scaleMode consts + * @param {number} resolution - The resolution / device pixel ratio of the texture being generated + * @param {PIXI.Rectangle} [region] - The region of the displayObject, that shall be rendered, + * if no region is specified, defaults to the local bounds of the displayObject. + * @return {PIXI.Texture} a texture of the graphics object + */ + generateTexture(displayObject, scaleMode, resolution, region) + { + region = region || displayObject.getLocalBounds(); + + // 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(region.width | 0, region.height | 0, scaleMode, resolution); + + tempMatrix.tx = -region.x; + tempMatrix.ty = -region.y; + + this.render(displayObject, renderTexture, false, tempMatrix, true); + + return renderTexture; + } + + /** + * Removes everything from the renderer and optionally removes the Canvas DOM element. + * + * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. + */ + destroy(removeView) + { + 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); + } + + this.plugins = null; + + this.type = RENDERER_TYPE.UNKNOWN; + + this.view = null; + + this.screen = null; + + this.resolution = 0; + + this.transparent = false; + + this.autoResize = false; + + this.blendModes = null; + + this.options = null; + + this.preserveDrawingBuffer = false; + this.clearBeforeRender = false; + + this.roundPixels = false; + + this._backgroundColor = 0; + this._backgroundColorRgba = null; + this._backgroundColorString = null; + + this._tempDisplayObjectParent = null; + this._lastObjectRendered = null; + } + + /** + * The background color to fill if not transparent + * + * @member {number} + */ + get backgroundColor() + { + return this._backgroundColor; + } + + set backgroundColor(value) // eslint-disable-line require-jsdoc + { + this._backgroundColor = value; + this._backgroundColorString = hex2string(value); + hex2rgb(value, this._backgroundColorRgba); + } +} diff --git a/packages/core/src/Renderer.js b/packages/core/src/Renderer.js new file mode 100644 index 0000000..2227e78 --- /dev/null +++ b/packages/core/src/Renderer.js @@ -0,0 +1,338 @@ +import AbstractRenderer from './AbstractRenderer'; +import { sayHello } from '@pixi/utils'; +import MaskSystem from './mask/MaskSystem'; +import StencilSystem from './mask/StencilSystem'; +import FilterSystem from './filters/FilterSystem'; +import FramebufferSystem from './framebuffer/FramebufferSystem'; +import RenderTextureSystem from './renderTexture/RenderTextureSystem'; +import TextureSystem from './textures/TextureSystem'; +import ProjectionSystem from './projection/ProjectionSystem'; +import StateSystem from './state/StateSystem'; +import GeometrySystem from './geometry/GeometrySystem'; +import ShaderSystem from './shader/ShaderSystem'; +import ContextSystem from './context/ContextSystem'; +import BatchSystem from './batch/BatchSystem'; +import TextureGCSystem from './textures/TextureGCSystem'; +import { RENDERER_TYPE } from '@pixi/constants'; +import UniformGroup from './shader/UniformGroup'; +import { Matrix } from '@pixi/math'; +import Runner from 'mini-runner'; + +/** + * 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 Render works by automatically managing webGLBatchs. + * 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 :) + * + * @class + * @memberof PIXI + * @extends PIXI.AbstractRenderer + */ +export default class Renderer extends AbstractRenderer +{ + // eslint-disable-next-line valid-jsdoc + /** + * + * @param {object} [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.transparent=false] - If the render view is transparent, default false + * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false + * @param {boolean} [options.antialias=false] - sets antialias. If not available natively then FXAA + * antialiasing is used + * @param {boolean} [options.forceFXAA=false] - forces FXAA antialiasing to be used over native. + * FXAA is faster, but may not always look as great + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. + * The resolution of the renderer retina would be 2. + * @param {boolean} [options.clearBeforeRender=true] - This sets if the renderer will clear + * the canvas or not before the new render pass. If you wish to set this to false, you *must* set + * preserveDrawingBuffer to `true`. + * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, + * enable this if you need to call toDataUrl on the webgl context. + * @param {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when + * rendering, stopping pixel interpolation. + * @param {number} [options.backgroundColor=0x000000] - The background color of the rendered area + * (shown if not transparent). + * @param {boolean} [options.legacy=false] - If true PixiJS will aim to ensure compatibility + * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. + * @param {string} [options.powerPreference] - Parameter passed to webgl context, set to "high-performance" + * for devices with dual graphics card + */ + constructor(options, arg2, arg3) + { + super('WebGL', options, arg2, arg3); + + /** + * The type of this renderer as a standardised const + * + * @member {number} + * @see PIXI.RENDERER_TYPE + */ + this.type = RENDERER_TYPE.WEBGL; + + // this will be set by the contextSystem (this.context) + this.gl = null; + this.CONTEXT_UID = 0; + this.legacy = !!options.legacy; + + // TODO legacy! + + // runners! + this.runners = { + destroy: new Runner('destroy'), + contextChange: new Runner('contextChange', 1), + reset: new Runner('reset'), + update: new Runner('update'), + postrender: new Runner('postrender'), + prerender: new Runner('prerender'), + resize: new Runner('resize', 2), + }; + + 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(GeometrySystem, 'geometry') + .addSystem(FramebufferSystem, 'framebuffer') + .addSystem(StencilSystem, 'stencil') + .addSystem(ProjectionSystem, 'projection') + .addSystem(TextureGCSystem, 'textureGC') + .addSystem(FilterSystem, 'filter') + .addSystem(RenderTextureSystem, 'renderTexture') + .addSystem(BatchSystem, 'batch'); + + this.initPlugins(Renderer.__plugins); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ + if (options.context) + { + this.context.initFromContext(options.context); + } + else + { + this.context.initFromOptions({ + alpha: this.transparent, + antialias: options.antialias, + premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', + stencil: true, + 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); + } + + /** + * Add a new system to the renderer. + * @param {class} ClassRef - Class reference + * @param {string} [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. + * @return {PIXI.Renderer} Return instance of renderer + */ + addSystem(ClassRef, name) + { + if (!name) + { + name = ClassRef.name; + } + + // TODO - read name from class.name.. + + /* + if(name.includes('System')) + { + name = name.replace('System', ''); + name = name.charAt(0).toLowerCase() + name.slice(1); + } + */ + + const system = new ClassRef(this); + + if (this[name]) + { + throw new Error(`Whoops! ${name} is already a manger`); + } + + this[name] = system; + + for (const i in this.runners) + { + this.runners[i].add(system); + } + + return this; + + /** + * 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. + */ + } + + /** + * Renders the object to its webGL view + * + * @param {PIXI.DisplayObject} displayObject - the object to be rendered + * @param {PIXI.RenderTexture} renderTexture - The render texture to render to. + * @param {boolean} [clear] - Should the canvas be cleared before the new render + * @param {PIXI.Transform} [transform] - A transform to apply to the render texture before rendering. + * @param {boolean} [skipUpdateTransform] - Should we skip the update transform pass? + */ + render(displayObject, renderTexture, clear, transform, skipUpdateTransform) + { + // can be handy to know! + this.renderingToScreen = !renderTexture; + + this.runners.prerender.run(); + this.emit('prerender'); + + // 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.parent; + + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = 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.run(); + + this.emit('postrender'); + } + + /** + * Resizes the webGL view to the specified width and height. + * + * @param {number} screenWidth - the new width of the screen + * @param {number} screenHeight - the new height of the screen + */ + resize(screenWidth, screenHeight) + { + AbstractRenderer.prototype.resize.call(this, screenWidth, screenHeight); + + this.runners.resize.run(screenWidth, screenHeight); + } + + /** + * Resets the WebGL state so you can render things however you fancy! + * + * @return {PIXI.Renderer} Returns itself. + */ + reset() + { + this.runners.reset.run(); + + return this; + } + + clear() + { + this.framebuffer.bind(); + this.framebuffer.clear(); + } + + /** + * Removes everything from the renderer (event listeners, spritebatch, etc...) + * + * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. + * See: https://github.com/pixijs/pixi.js/issues/2233 + */ + destroy(removeView) + { + this.runners.destroy.run(); + + // call base destroy + super.destroy(removeView); + + // TODO nullify all the managers.. + this.gl = null; + } + + /** + * 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 + * builds and excluding plugins. + * @name PIXI.Renderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. + */ + + /** + * Adds a plugin to the renderer. + * + * @method + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + static registerPlugin(pluginName, ctor) + { + Renderer.__plugins = Renderer.__plugins || {}; + Renderer.__plugins[pluginName] = ctor; + } +} diff --git a/packages/core/src/System.js b/packages/core/src/System.js new file mode 100644 index 0000000..05a10c8 --- /dev/null +++ b/packages/core/src/System.js @@ -0,0 +1,42 @@ +/** + * System is a base class used for extending systems used by the {@link PIXI.Renderer} + * @see PIXI.Renderer#addSystem + * @class + * @memberof PIXI + */ +export default class System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + /** + * The renderer this manager works for. + * + * @member {PIXI.Renderer} + */ + this.renderer = renderer; + + this.renderer.runners.contextChange.add(this); + } + + /** + * Generic method called when there is a WebGL context change. + * + */ + contextChange() + { + // do some codes init! + } + + /** + * Generic destroy methods to be overridden by the subclass + * + */ + destroy() + { + this.renderer.runners.contextChange.remove(this); + this.renderer = null; + } +} diff --git a/packages/core/src/batch/BatchSystem.js b/packages/core/src/batch/BatchSystem.js new file mode 100644 index 0000000..b04c249 --- /dev/null +++ b/packages/core/src/batch/BatchSystem.js @@ -0,0 +1,68 @@ +import System from '../System'; +import ObjectRenderer from './ObjectRenderer'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class BatchSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + super(renderer); + + /** + * An empty renderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.emptyRenderer = new ObjectRenderer(renderer); + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.currentRenderer = this.emptyRenderer; + } + + /** + * Changes the current renderer to the one given in parameter + * + * @param {PIXI.ObjectRenderer} objectRenderer - The object renderer to use. + */ + setObjectRenderer(objectRenderer) + { + if (this.currentRenderer === objectRenderer) + { + return; + } + + this.currentRenderer.stop(); + this.currentRenderer = objectRenderer; + + this.renderer.state.setState(objectRenderer.state); + + this.currentRenderer.start(); + } + + /** + * This should be called if you wish to do some custom rendering + * It will basically render anything that may be batched up such as sprites + * + */ + flush() + { + this.setObjectRenderer(this.emptyRenderer); + } + + reset() + { + this.setObjectRenderer(this.emptyRenderer); + } +} diff --git a/packages/core/src/batch/ObjectRenderer.js b/packages/core/src/batch/ObjectRenderer.js new file mode 100644 index 0000000..0c851a9 --- /dev/null +++ b/packages/core/src/batch/ObjectRenderer.js @@ -0,0 +1,48 @@ +import System from '../System'; + +/** + * Base for a common object renderer that can be used as a system renderer plugin. + * + * @class + * @extends PIXI.System + * @memberof PIXI + */ +export default class ObjectRenderer extends System +{ + /** + * Starts the renderer and sets the shader + * + */ + start() + { + // set the shader.. + } + + /** + * Stops the renderer + * + */ + stop() + { + this.flush(); + } + + /** + * Stub method for rendering content and emptying the current batch. + * + */ + flush() + { + // flush! + } + + /** + * Renders an object + * + * @param {PIXI.DisplayObject} object - The object to render. + */ + render(object) // eslint-disable-line no-unused-vars + { + // render the object + } +} diff --git a/packages/core/src/context/ContextSystem.js b/packages/core/src/context/ContextSystem.js new file mode 100644 index 0000000..301aab4 --- /dev/null +++ b/packages/core/src/context/ContextSystem.js @@ -0,0 +1,186 @@ +import System from '../System'; +import { settings } from '@pixi/settings'; + +let CONTEXT_UID = 0; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class ContextSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.webGLVersion = 1; + + this.handleContextLost = this.handleContextLost.bind(this); + this.handleContextRestored = this.handleContextRestored.bind(this); + + this.extensions = {}; + + renderer.view.addEventListener('webglcontextlost', this.handleContextLost, false); + renderer.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + } + + get isLost() + { + return (!this.gl || this.gl.isContextLost()); + } + + contextChange(gl) + { + this.gl = gl; + + // restore a context if it was previously lost + if (gl.isContextLost() && gl.getExtension('WEBGL_lose_context')) + { + gl.getExtension('WEBGL_lose_context').restoreContext(); + } + } + + initFromContext(gl) + { + this.gl = gl; + this.validateContext(gl); + this.renderer.gl = gl; + this.renderer.CONTEXT_UID = CONTEXT_UID++; + this.renderer.runners.contextChange.run(gl); + } + + initFromOptions(options) + { + const gl = this.createContext(this.renderer.view, options); + + this.initFromContext(gl); + } + + /** + * Helper class to create a webGL Context + * + * @class + * @memberof PIXI.glCore + * @param canvas {HTMLCanvasElement} the canvas element that we will get the context from + * @param options {Object} An options object that gets passed in to the canvas element containing the context attributes, + * see https://developer.mozilla.org/en/docs/Web/API/HTMLCanvasElement/getContext for the options available + * @return {WebGLRenderingContext} the WebGL context + */ + createContext(canvas, options) + { + let gl; + + if (settings.PREFER_WEBGL_2) + { + gl = canvas.getContext('webgl2', options); + } + + if (gl) + { + this.webGLVersion = 2; + } + else + { + this.webGLVersion = 1; + + gl = canvas.getContext('webgl', options) + || canvas.getContext('experimental-webgl', options); + + if (!gl) + { + // fail, not able to get a context + throw new Error('This browser does not support webGL. Try using the canvas renderer'); + } + } + + this.gl = gl; + + this.getExtensions(); + + return gl; + } + + getExtensions() + { + // time to set up default etensions that pixi uses.. + const gl = this.gl; + const extensions = this.extensions; + + if (this.webGLVersion === 1) + { + extensions.drawBuffers = gl.getExtension('WEBGL_draw_buffers'); + extensions.depthTexture = gl.getExtension('WEBKIT_WEBGL_depth_texture'); + extensions.floatTexture = gl.getExtension('OES_texture_float'); + extensions.loseContext = gl.getExtension('WEBGL_lose_context'); + + extensions.vertexArrayObject = gl.getExtension('OES_vertex_array_object') + || gl.getExtension('MOZ_OES_vertex_array_object') + || gl.getExtension('WEBKIT_OES_vertex_array_object'); + } + + // we don't use any specific WebGL 2 ones yet! + } + + /** + * Handles a lost webgl context + * + * @private + * @param {WebGLContextEvent} event - The context lost event. + */ + handleContextLost(event) + { + event.preventDefault(); + } + + /** + * Handles a restored webgl context + * + * @private + */ + handleContextRestored() + { + this.renderer.runners.contextChange.run(this.gl); + } + + destroy() + { + const view = this.renderer.view; + + // remove listeners + view.removeEventListener('webglcontextlost', this.handleContextLost); + view.removeEventListener('webglcontextrestored', this.handleContextRestored); + + this.gl.useProgram(null); + + if (this.extensions.loseContext) + { + this.extensions.loseContext.loseContext(); + } + } + + postrender() + { + this.gl.flush(); + } + + validateContext(gl) + { + const attributes = gl.getContextAttributes(); + + // this is going to be fairly simple for now.. but at least we have room to grow! + if (!attributes.stencil) + { + /* eslint-disable max-len */ + + /* eslint-disable no-console */ + console.warn('Provided WebGL context does not have a stencil buffer, masks may not render correctly'); + /* eslint-enable no-console */ + + /* eslint-enable max-len */ + } + } +} diff --git a/packages/core/src/filters/Filter.js b/packages/core/src/filters/Filter.js new file mode 100644 index 0000000..9d8cad8 --- /dev/null +++ b/packages/core/src/filters/Filter.js @@ -0,0 +1,140 @@ +import Shader from '../shader/Shader'; +import Program from '../shader/Program'; +import State from '../state/State'; +import { settings } from '@pixi/settings'; +// import extractUniformsFromSrc from './extractUniformsFromSrc'; +import defaultVertex from './defaultFilter.vert'; +import defaultFragment from './defaultFilter.frag'; + +// let math = require('../../math'); +/** + * @class + * @memberof PIXI + * @extends PIXI.Shader + */ +export default class Filter extends Shader +{ + /** + * @param {string} [vertexSrc] - The source of the vertex shader. + * @param {string} [fragmentSrc] - The source of the fragment shader. + * @param {object} [uniforms] - Custom uniforms to use to augment the built-in ones. + */ + constructor(vertexSrc, fragmentSrc, uniforms) + { + const program = Program.from(vertexSrc, fragmentSrc); + + super(program, uniforms); + + /** + * The padding of the filter. Some filters require extra space to breath such as a blur. + * Increasing this will add extra width and height to the bounds of the object that the + * filter is applied to. + * + * @member {number} + */ + this.padding = 0; + + /** + * The resolution of the filter. Setting this to be lower will lower the quality but + * increase the performance of the filter. + * + * @member {number} + */ + this.resolution = settings.RESOLUTION; + + /** + * If enabled is true the filter is applied, if false it will not. + * + * @member {boolean} + */ + this.enabled = true; + + /** + * If enabled, PixiJS will fit the filter area into boundaries for better performance. + * Switch it off if it does not work for specific shader. + * + * @member {boolean} + */ + this.autoFit = true; + + /** + * Legacy filters use position and uvs from attributes + * @member {boolean} + * @readonly + */ + this.legacy = !!this.program.attributeData.aTextureCoord; + + /** + * the webGL state the filter requires to render + * @member {PIXI.State} + */ + this.state = new State(); + } + + /** + * Applies the filter + * + * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + * @param {boolean} clear - Should the output be cleared before rendering to it + * @param {object} [currentState] - It's current state of filter. + * There are some useful properties in the currentState : + * target, filters, sourceFrame, destinationFrame, renderTarget, resolution + */ + apply(filterManager, input, output, clear, currentState, derp) // eslint-disable-line no-unused-vars + { + // do as you please! + + filterManager.applyFilter(this, input, output, clear, currentState, derp); + + // or just do a regular render.. + } + + /** + * Sets the blendmode of the filter + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL + */ + get blendMode() + { + return this.state.blendMode; + } + + set blendMode(value) // eslint-disable-line require-jsdoc + { + this.state.blendMode = value; + } + + /** + * The default vertex shader source + * + * @static + * @constant + */ + static get defaultVertexSrc() + { + return defaultVertex; + } + + /** + * The default fragment shader source + * + * @static + * @constant + */ + static get defaultFragmentSrc() + { + return defaultFragment; + } +} + +/** + * Used for caching shader IDs + * + * @static + * @private + */ +Filter.SOURCE_KEY_MAP = {}; + diff --git a/packages/core/src/AbstractRenderer.js b/packages/core/src/AbstractRenderer.js new file mode 100644 index 0000000..64b2eb1 --- /dev/null +++ b/packages/core/src/AbstractRenderer.js @@ -0,0 +1,353 @@ +import { hex2string, hex2rgb } from '@pixi/utils'; +import { Matrix, Rectangle } from '@pixi/math'; +import { RENDERER_TYPE } from '@pixi/constants'; +import { settings } from '@pixi/settings'; +import { Container } from '@pixi/display'; +import RenderTexture from './renderTexture/RenderTexture'; +import EventEmitter from 'eventemitter3'; + +const tempMatrix = new Matrix(); + +/** + * 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 EventEmitter + * @memberof PIXI + */ +export default class AbstractRenderer extends EventEmitter +{ + // eslint-disable-next-line valid-jsdoc + /** + * @param {string} system - The name of the system this renderer is for. + * @param {object} [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.transparent=false] - If the render view is transparent, default false + * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false + * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. The + * resolution of the renderer retina would be 2. + * @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 {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when rendering, + * stopping pixel interpolation. + */ + constructor(system, options, arg2, arg3) + { + super(); + + // Support for constructor(system, screenWidth, screenHeight, options) + if (typeof options === 'number') + { + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + }, arg3); + } + + // 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 = RENDERER_TYPE.UNKNOWN; + + /** + * Measurements of the screen. (0, 0, screenWidth, screenHeight) + * + * Its safe to use as filterArea or hitArea for 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 1 + */ + this.resolution = options.resolution || settings.RESOLUTION; + + /** + * Whether the render view is transparent + * + * @member {boolean} + */ + this.transparent = options.transparent; + + /** + * Whether css dimensions of canvas view should be resized to screen dimensions automatically + * + * @member {boolean} + */ + this.autoResize = options.autoResize || false; + + /** + * Tracks the blend modes useful for this renderer. + * + * @member {object} + */ + this.blendModes = null; + + /** + * 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; + + /** + * If true PixiJS will Math.floor() x/y values when rendering, stopping pixel interpolation. + * Handy for crisp pixel art and speed on legacy devices. + * + * @member {boolean} + */ + this.roundPixels = options.roundPixels; + + /** + * The background color as a number. + * + * @member {number} + * @private + */ + this._backgroundColor = 0x000000; + + /** + * The background color as an [R, G, B] array. + * + * @member {number[]} + * @private + */ + this._backgroundColorRgba = [0, 0, 0, 0]; + + /** + * The background color as a string. + * + * @member {string} + * @private + */ + this._backgroundColorString = '#000000'; + + this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter + + /** + * This temporary display object used as the parent of the currently being rendered item + * + * @member {PIXI.DisplayObject} + * @private + */ + this._tempDisplayObjectParent = new Container(); + + /** + * The last root object that the renderer tried to render. + * + * @member {PIXI.DisplayObject} + * @private + */ + this._lastObjectRendered = this._tempDisplayObjectParent; + + /** + * Collection of plugins. + * @readonly + * @member {object} + */ + this.plugins = {}; + } + + /** + * Initialize the plugins. + * + * @protected + * @param {object} staticMap - The dictionary of staticly saved plugins. + */ + initPlugins(staticMap) + { + 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() + { + return this.view.width; + } + + /** + * Same as view.height, actual number of pixels in the canvas by vertical + * + * @member {number} + * @readonly + * @default 600 + */ + get height() + { + return this.view.height; + } + + /** + * Resizes the screen and canvas to the specified width and height + * Canvas dimensions are multiplied by resolution + * + * @param {number} screenWidth - the new width of the screen + * @param {number} screenHeight - the new height of the screen + */ + resize(screenWidth, screenHeight) + { + this.screen.width = screenWidth; + this.screen.height = screenHeight; + + this.view.width = screenWidth * this.resolution; + this.view.height = screenHeight * this.resolution; + + if (this.autoResize) + { + this.view.style.width = `${screenWidth}px`; + this.view.style.height = `${screenHeight}px`; + } + } + + /** + * 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 {PIXI.DisplayObject} displayObject - The displayObject the object will be generated from + * @param {number} scaleMode - Should be one of the scaleMode consts + * @param {number} resolution - The resolution / device pixel ratio of the texture being generated + * @param {PIXI.Rectangle} [region] - The region of the displayObject, that shall be rendered, + * if no region is specified, defaults to the local bounds of the displayObject. + * @return {PIXI.Texture} a texture of the graphics object + */ + generateTexture(displayObject, scaleMode, resolution, region) + { + region = region || displayObject.getLocalBounds(); + + // 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(region.width | 0, region.height | 0, scaleMode, resolution); + + tempMatrix.tx = -region.x; + tempMatrix.ty = -region.y; + + this.render(displayObject, renderTexture, false, tempMatrix, true); + + return renderTexture; + } + + /** + * Removes everything from the renderer and optionally removes the Canvas DOM element. + * + * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. + */ + destroy(removeView) + { + 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); + } + + this.plugins = null; + + this.type = RENDERER_TYPE.UNKNOWN; + + this.view = null; + + this.screen = null; + + this.resolution = 0; + + this.transparent = false; + + this.autoResize = false; + + this.blendModes = null; + + this.options = null; + + this.preserveDrawingBuffer = false; + this.clearBeforeRender = false; + + this.roundPixels = false; + + this._backgroundColor = 0; + this._backgroundColorRgba = null; + this._backgroundColorString = null; + + this._tempDisplayObjectParent = null; + this._lastObjectRendered = null; + } + + /** + * The background color to fill if not transparent + * + * @member {number} + */ + get backgroundColor() + { + return this._backgroundColor; + } + + set backgroundColor(value) // eslint-disable-line require-jsdoc + { + this._backgroundColor = value; + this._backgroundColorString = hex2string(value); + hex2rgb(value, this._backgroundColorRgba); + } +} diff --git a/packages/core/src/Renderer.js b/packages/core/src/Renderer.js new file mode 100644 index 0000000..2227e78 --- /dev/null +++ b/packages/core/src/Renderer.js @@ -0,0 +1,338 @@ +import AbstractRenderer from './AbstractRenderer'; +import { sayHello } from '@pixi/utils'; +import MaskSystem from './mask/MaskSystem'; +import StencilSystem from './mask/StencilSystem'; +import FilterSystem from './filters/FilterSystem'; +import FramebufferSystem from './framebuffer/FramebufferSystem'; +import RenderTextureSystem from './renderTexture/RenderTextureSystem'; +import TextureSystem from './textures/TextureSystem'; +import ProjectionSystem from './projection/ProjectionSystem'; +import StateSystem from './state/StateSystem'; +import GeometrySystem from './geometry/GeometrySystem'; +import ShaderSystem from './shader/ShaderSystem'; +import ContextSystem from './context/ContextSystem'; +import BatchSystem from './batch/BatchSystem'; +import TextureGCSystem from './textures/TextureGCSystem'; +import { RENDERER_TYPE } from '@pixi/constants'; +import UniformGroup from './shader/UniformGroup'; +import { Matrix } from '@pixi/math'; +import Runner from 'mini-runner'; + +/** + * 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 Render works by automatically managing webGLBatchs. + * 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 :) + * + * @class + * @memberof PIXI + * @extends PIXI.AbstractRenderer + */ +export default class Renderer extends AbstractRenderer +{ + // eslint-disable-next-line valid-jsdoc + /** + * + * @param {object} [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.transparent=false] - If the render view is transparent, default false + * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false + * @param {boolean} [options.antialias=false] - sets antialias. If not available natively then FXAA + * antialiasing is used + * @param {boolean} [options.forceFXAA=false] - forces FXAA antialiasing to be used over native. + * FXAA is faster, but may not always look as great + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. + * The resolution of the renderer retina would be 2. + * @param {boolean} [options.clearBeforeRender=true] - This sets if the renderer will clear + * the canvas or not before the new render pass. If you wish to set this to false, you *must* set + * preserveDrawingBuffer to `true`. + * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, + * enable this if you need to call toDataUrl on the webgl context. + * @param {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when + * rendering, stopping pixel interpolation. + * @param {number} [options.backgroundColor=0x000000] - The background color of the rendered area + * (shown if not transparent). + * @param {boolean} [options.legacy=false] - If true PixiJS will aim to ensure compatibility + * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. + * @param {string} [options.powerPreference] - Parameter passed to webgl context, set to "high-performance" + * for devices with dual graphics card + */ + constructor(options, arg2, arg3) + { + super('WebGL', options, arg2, arg3); + + /** + * The type of this renderer as a standardised const + * + * @member {number} + * @see PIXI.RENDERER_TYPE + */ + this.type = RENDERER_TYPE.WEBGL; + + // this will be set by the contextSystem (this.context) + this.gl = null; + this.CONTEXT_UID = 0; + this.legacy = !!options.legacy; + + // TODO legacy! + + // runners! + this.runners = { + destroy: new Runner('destroy'), + contextChange: new Runner('contextChange', 1), + reset: new Runner('reset'), + update: new Runner('update'), + postrender: new Runner('postrender'), + prerender: new Runner('prerender'), + resize: new Runner('resize', 2), + }; + + 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(GeometrySystem, 'geometry') + .addSystem(FramebufferSystem, 'framebuffer') + .addSystem(StencilSystem, 'stencil') + .addSystem(ProjectionSystem, 'projection') + .addSystem(TextureGCSystem, 'textureGC') + .addSystem(FilterSystem, 'filter') + .addSystem(RenderTextureSystem, 'renderTexture') + .addSystem(BatchSystem, 'batch'); + + this.initPlugins(Renderer.__plugins); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ + if (options.context) + { + this.context.initFromContext(options.context); + } + else + { + this.context.initFromOptions({ + alpha: this.transparent, + antialias: options.antialias, + premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', + stencil: true, + 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); + } + + /** + * Add a new system to the renderer. + * @param {class} ClassRef - Class reference + * @param {string} [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. + * @return {PIXI.Renderer} Return instance of renderer + */ + addSystem(ClassRef, name) + { + if (!name) + { + name = ClassRef.name; + } + + // TODO - read name from class.name.. + + /* + if(name.includes('System')) + { + name = name.replace('System', ''); + name = name.charAt(0).toLowerCase() + name.slice(1); + } + */ + + const system = new ClassRef(this); + + if (this[name]) + { + throw new Error(`Whoops! ${name} is already a manger`); + } + + this[name] = system; + + for (const i in this.runners) + { + this.runners[i].add(system); + } + + return this; + + /** + * 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. + */ + } + + /** + * Renders the object to its webGL view + * + * @param {PIXI.DisplayObject} displayObject - the object to be rendered + * @param {PIXI.RenderTexture} renderTexture - The render texture to render to. + * @param {boolean} [clear] - Should the canvas be cleared before the new render + * @param {PIXI.Transform} [transform] - A transform to apply to the render texture before rendering. + * @param {boolean} [skipUpdateTransform] - Should we skip the update transform pass? + */ + render(displayObject, renderTexture, clear, transform, skipUpdateTransform) + { + // can be handy to know! + this.renderingToScreen = !renderTexture; + + this.runners.prerender.run(); + this.emit('prerender'); + + // 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.parent; + + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = 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.run(); + + this.emit('postrender'); + } + + /** + * Resizes the webGL view to the specified width and height. + * + * @param {number} screenWidth - the new width of the screen + * @param {number} screenHeight - the new height of the screen + */ + resize(screenWidth, screenHeight) + { + AbstractRenderer.prototype.resize.call(this, screenWidth, screenHeight); + + this.runners.resize.run(screenWidth, screenHeight); + } + + /** + * Resets the WebGL state so you can render things however you fancy! + * + * @return {PIXI.Renderer} Returns itself. + */ + reset() + { + this.runners.reset.run(); + + return this; + } + + clear() + { + this.framebuffer.bind(); + this.framebuffer.clear(); + } + + /** + * Removes everything from the renderer (event listeners, spritebatch, etc...) + * + * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. + * See: https://github.com/pixijs/pixi.js/issues/2233 + */ + destroy(removeView) + { + this.runners.destroy.run(); + + // call base destroy + super.destroy(removeView); + + // TODO nullify all the managers.. + this.gl = null; + } + + /** + * 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 + * builds and excluding plugins. + * @name PIXI.Renderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. + */ + + /** + * Adds a plugin to the renderer. + * + * @method + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + static registerPlugin(pluginName, ctor) + { + Renderer.__plugins = Renderer.__plugins || {}; + Renderer.__plugins[pluginName] = ctor; + } +} diff --git a/packages/core/src/System.js b/packages/core/src/System.js new file mode 100644 index 0000000..05a10c8 --- /dev/null +++ b/packages/core/src/System.js @@ -0,0 +1,42 @@ +/** + * System is a base class used for extending systems used by the {@link PIXI.Renderer} + * @see PIXI.Renderer#addSystem + * @class + * @memberof PIXI + */ +export default class System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + /** + * The renderer this manager works for. + * + * @member {PIXI.Renderer} + */ + this.renderer = renderer; + + this.renderer.runners.contextChange.add(this); + } + + /** + * Generic method called when there is a WebGL context change. + * + */ + contextChange() + { + // do some codes init! + } + + /** + * Generic destroy methods to be overridden by the subclass + * + */ + destroy() + { + this.renderer.runners.contextChange.remove(this); + this.renderer = null; + } +} diff --git a/packages/core/src/batch/BatchSystem.js b/packages/core/src/batch/BatchSystem.js new file mode 100644 index 0000000..b04c249 --- /dev/null +++ b/packages/core/src/batch/BatchSystem.js @@ -0,0 +1,68 @@ +import System from '../System'; +import ObjectRenderer from './ObjectRenderer'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class BatchSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + super(renderer); + + /** + * An empty renderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.emptyRenderer = new ObjectRenderer(renderer); + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.currentRenderer = this.emptyRenderer; + } + + /** + * Changes the current renderer to the one given in parameter + * + * @param {PIXI.ObjectRenderer} objectRenderer - The object renderer to use. + */ + setObjectRenderer(objectRenderer) + { + if (this.currentRenderer === objectRenderer) + { + return; + } + + this.currentRenderer.stop(); + this.currentRenderer = objectRenderer; + + this.renderer.state.setState(objectRenderer.state); + + this.currentRenderer.start(); + } + + /** + * This should be called if you wish to do some custom rendering + * It will basically render anything that may be batched up such as sprites + * + */ + flush() + { + this.setObjectRenderer(this.emptyRenderer); + } + + reset() + { + this.setObjectRenderer(this.emptyRenderer); + } +} diff --git a/packages/core/src/batch/ObjectRenderer.js b/packages/core/src/batch/ObjectRenderer.js new file mode 100644 index 0000000..0c851a9 --- /dev/null +++ b/packages/core/src/batch/ObjectRenderer.js @@ -0,0 +1,48 @@ +import System from '../System'; + +/** + * Base for a common object renderer that can be used as a system renderer plugin. + * + * @class + * @extends PIXI.System + * @memberof PIXI + */ +export default class ObjectRenderer extends System +{ + /** + * Starts the renderer and sets the shader + * + */ + start() + { + // set the shader.. + } + + /** + * Stops the renderer + * + */ + stop() + { + this.flush(); + } + + /** + * Stub method for rendering content and emptying the current batch. + * + */ + flush() + { + // flush! + } + + /** + * Renders an object + * + * @param {PIXI.DisplayObject} object - The object to render. + */ + render(object) // eslint-disable-line no-unused-vars + { + // render the object + } +} diff --git a/packages/core/src/context/ContextSystem.js b/packages/core/src/context/ContextSystem.js new file mode 100644 index 0000000..301aab4 --- /dev/null +++ b/packages/core/src/context/ContextSystem.js @@ -0,0 +1,186 @@ +import System from '../System'; +import { settings } from '@pixi/settings'; + +let CONTEXT_UID = 0; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class ContextSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.webGLVersion = 1; + + this.handleContextLost = this.handleContextLost.bind(this); + this.handleContextRestored = this.handleContextRestored.bind(this); + + this.extensions = {}; + + renderer.view.addEventListener('webglcontextlost', this.handleContextLost, false); + renderer.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + } + + get isLost() + { + return (!this.gl || this.gl.isContextLost()); + } + + contextChange(gl) + { + this.gl = gl; + + // restore a context if it was previously lost + if (gl.isContextLost() && gl.getExtension('WEBGL_lose_context')) + { + gl.getExtension('WEBGL_lose_context').restoreContext(); + } + } + + initFromContext(gl) + { + this.gl = gl; + this.validateContext(gl); + this.renderer.gl = gl; + this.renderer.CONTEXT_UID = CONTEXT_UID++; + this.renderer.runners.contextChange.run(gl); + } + + initFromOptions(options) + { + const gl = this.createContext(this.renderer.view, options); + + this.initFromContext(gl); + } + + /** + * Helper class to create a webGL Context + * + * @class + * @memberof PIXI.glCore + * @param canvas {HTMLCanvasElement} the canvas element that we will get the context from + * @param options {Object} An options object that gets passed in to the canvas element containing the context attributes, + * see https://developer.mozilla.org/en/docs/Web/API/HTMLCanvasElement/getContext for the options available + * @return {WebGLRenderingContext} the WebGL context + */ + createContext(canvas, options) + { + let gl; + + if (settings.PREFER_WEBGL_2) + { + gl = canvas.getContext('webgl2', options); + } + + if (gl) + { + this.webGLVersion = 2; + } + else + { + this.webGLVersion = 1; + + gl = canvas.getContext('webgl', options) + || canvas.getContext('experimental-webgl', options); + + if (!gl) + { + // fail, not able to get a context + throw new Error('This browser does not support webGL. Try using the canvas renderer'); + } + } + + this.gl = gl; + + this.getExtensions(); + + return gl; + } + + getExtensions() + { + // time to set up default etensions that pixi uses.. + const gl = this.gl; + const extensions = this.extensions; + + if (this.webGLVersion === 1) + { + extensions.drawBuffers = gl.getExtension('WEBGL_draw_buffers'); + extensions.depthTexture = gl.getExtension('WEBKIT_WEBGL_depth_texture'); + extensions.floatTexture = gl.getExtension('OES_texture_float'); + extensions.loseContext = gl.getExtension('WEBGL_lose_context'); + + extensions.vertexArrayObject = gl.getExtension('OES_vertex_array_object') + || gl.getExtension('MOZ_OES_vertex_array_object') + || gl.getExtension('WEBKIT_OES_vertex_array_object'); + } + + // we don't use any specific WebGL 2 ones yet! + } + + /** + * Handles a lost webgl context + * + * @private + * @param {WebGLContextEvent} event - The context lost event. + */ + handleContextLost(event) + { + event.preventDefault(); + } + + /** + * Handles a restored webgl context + * + * @private + */ + handleContextRestored() + { + this.renderer.runners.contextChange.run(this.gl); + } + + destroy() + { + const view = this.renderer.view; + + // remove listeners + view.removeEventListener('webglcontextlost', this.handleContextLost); + view.removeEventListener('webglcontextrestored', this.handleContextRestored); + + this.gl.useProgram(null); + + if (this.extensions.loseContext) + { + this.extensions.loseContext.loseContext(); + } + } + + postrender() + { + this.gl.flush(); + } + + validateContext(gl) + { + const attributes = gl.getContextAttributes(); + + // this is going to be fairly simple for now.. but at least we have room to grow! + if (!attributes.stencil) + { + /* eslint-disable max-len */ + + /* eslint-disable no-console */ + console.warn('Provided WebGL context does not have a stencil buffer, masks may not render correctly'); + /* eslint-enable no-console */ + + /* eslint-enable max-len */ + } + } +} diff --git a/packages/core/src/filters/Filter.js b/packages/core/src/filters/Filter.js new file mode 100644 index 0000000..9d8cad8 --- /dev/null +++ b/packages/core/src/filters/Filter.js @@ -0,0 +1,140 @@ +import Shader from '../shader/Shader'; +import Program from '../shader/Program'; +import State from '../state/State'; +import { settings } from '@pixi/settings'; +// import extractUniformsFromSrc from './extractUniformsFromSrc'; +import defaultVertex from './defaultFilter.vert'; +import defaultFragment from './defaultFilter.frag'; + +// let math = require('../../math'); +/** + * @class + * @memberof PIXI + * @extends PIXI.Shader + */ +export default class Filter extends Shader +{ + /** + * @param {string} [vertexSrc] - The source of the vertex shader. + * @param {string} [fragmentSrc] - The source of the fragment shader. + * @param {object} [uniforms] - Custom uniforms to use to augment the built-in ones. + */ + constructor(vertexSrc, fragmentSrc, uniforms) + { + const program = Program.from(vertexSrc, fragmentSrc); + + super(program, uniforms); + + /** + * The padding of the filter. Some filters require extra space to breath such as a blur. + * Increasing this will add extra width and height to the bounds of the object that the + * filter is applied to. + * + * @member {number} + */ + this.padding = 0; + + /** + * The resolution of the filter. Setting this to be lower will lower the quality but + * increase the performance of the filter. + * + * @member {number} + */ + this.resolution = settings.RESOLUTION; + + /** + * If enabled is true the filter is applied, if false it will not. + * + * @member {boolean} + */ + this.enabled = true; + + /** + * If enabled, PixiJS will fit the filter area into boundaries for better performance. + * Switch it off if it does not work for specific shader. + * + * @member {boolean} + */ + this.autoFit = true; + + /** + * Legacy filters use position and uvs from attributes + * @member {boolean} + * @readonly + */ + this.legacy = !!this.program.attributeData.aTextureCoord; + + /** + * the webGL state the filter requires to render + * @member {PIXI.State} + */ + this.state = new State(); + } + + /** + * Applies the filter + * + * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + * @param {boolean} clear - Should the output be cleared before rendering to it + * @param {object} [currentState] - It's current state of filter. + * There are some useful properties in the currentState : + * target, filters, sourceFrame, destinationFrame, renderTarget, resolution + */ + apply(filterManager, input, output, clear, currentState, derp) // eslint-disable-line no-unused-vars + { + // do as you please! + + filterManager.applyFilter(this, input, output, clear, currentState, derp); + + // or just do a regular render.. + } + + /** + * Sets the blendmode of the filter + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL + */ + get blendMode() + { + return this.state.blendMode; + } + + set blendMode(value) // eslint-disable-line require-jsdoc + { + this.state.blendMode = value; + } + + /** + * The default vertex shader source + * + * @static + * @constant + */ + static get defaultVertexSrc() + { + return defaultVertex; + } + + /** + * The default fragment shader source + * + * @static + * @constant + */ + static get defaultFragmentSrc() + { + return defaultFragment; + } +} + +/** + * Used for caching shader IDs + * + * @static + * @private + */ +Filter.SOURCE_KEY_MAP = {}; + diff --git a/packages/core/src/filters/FilterSystem.js b/packages/core/src/filters/FilterSystem.js new file mode 100644 index 0000000..3174318 --- /dev/null +++ b/packages/core/src/filters/FilterSystem.js @@ -0,0 +1,431 @@ +import System from '../System'; + +import RenderTexture from '../renderTexture/RenderTexture'; +import Quad from '../utils/Quad'; +import QuadUv from '../utils/QuadUv'; +import { Rectangle } from '@pixi/math'; +import * as filterTransforms from './filterTransforms'; +import bitTwiddle from 'bit-twiddle'; +import UniformGroup from '../shader/UniformGroup'; +import { DRAW_MODES } from '@pixi/constants'; + +// +/** + * @ignore + * @class + */ +class FilterState +{ + /** + * + */ + constructor() + { + this.renderTexture = null; + this.sourceFrame = new Rectangle(); + this.destinationFrame = new Rectangle(); + this.filters = []; + this.target = null; + this.legacy = false; + this.resolution = 1; + } +} + +/** + * @class + * @memberof PIXI.systems + * @extends PIXI.System + */ +export default class FilterSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + /** + * stores a bunch of PO2 textures used for filtering + * @type {Object} + */ + this.texturePool = {}; + + /** + * a pool for storing filter states, save us creating new ones each tick + * @type {Array} + */ + this.statePool = []; + + /** + * A very simple geometry used when drawing a filter effect to the screen + * @type {Quad} + */ + this.quad = new Quad(); + + this.quadUv = new QuadUv(); + + /** + * Temporary rect for maths + * @type {PIXI.Rectangle} + */ + this.tempRect = new Rectangle(); + + this.activeState = {}; + + /** + * this uniform group is attached to filter uniforms when used + * @type {UniformGroup} + */ + this.globalUniforms = new UniformGroup({ + sourceFrame: this.tempRect, + destinationFrame: this.tempRect, + + // legacy variables + filterArea: new Float32Array(4), + filterClamp: new Float32Array(4), + }, true); + } + + /** + * Adds a new filter to the System. + * + * @param {PIXI.DisplayObject} target - The target of the filter to render. + * @param {PIXI.Filter[]} filters - The filters to apply. + */ + push(target, filters) + { + const renderer = this.renderer; + const filterStack = this.renderer.renderTexture.defaultFilterStack; + const state = this.statePool.pop() || new FilterState(); + + let resolution = filters[0].resolution; + let padding = filters[0].padding; + let autoFit = filters[0].autoFit; + let legacy = filters[0].legacy; + + for (let i = 1; i < filters.length; i++) + { + const filter = filters[i]; + + // lets use the lowest resolution.. + resolution = Math.min(resolution, filter.resolution); + // and the largest amount of padding! + padding = Math.max(padding, filter.padding); + // only auto fit if all filters are autofit + autoFit = autoFit || filter.autoFit; + + legacy = legacy || filter.legacy; + } + + filterStack.push(state); + + state.resolution = resolution; + + state.legacy = legacy; + + // round to whole number based on resolution + // TODO move that to the shader too? + state.sourceFrame = target.filterArea || target.getBounds(true); + + state.sourceFrame.pad(padding); + + if (autoFit) + { + state.sourceFrame.fit(this.renderer.renderTexture.destinationFrame); + } + + state.sourceFrame.round(resolution); + + state.renderTexture = this.getPotFilterTexture(state.sourceFrame.width, state.sourceFrame.height, resolution); + state.filters = filters; + + state.destinationFrame.width = state.renderTexture.width; + state.destinationFrame.height = state.renderTexture.height; + + state.renderTexture.filterFrame = state.sourceFrame; + renderer.renderTexture.bind(state.renderTexture, state.sourceFrame);// /, state.destinationFrame); + renderer.renderTexture.clear(); + } + + /** + * Pops off the filter and applies it. + * + */ + pop() + { + const renderer = this.renderer; + const filterStack = renderer.renderTexture.defaultFilterStack; + const state = filterStack.pop(); + const filters = state.filters; + + this.activeState = state; + + const globalUniforms = this.globalUniforms.uniforms; + + globalUniforms.sourceFrame = state.sourceFrame; + globalUniforms.destinationFrame = state.destinationFrame; + globalUniforms.resolution = state.resolution; + + // only update the rect if its legacy.. + if (state.legacy) + { + const filterArea = globalUniforms.filterArea; + const filterClamp = globalUniforms.filterClamp; + + filterArea[0] = state.destinationFrame.width; + filterArea[1] = state.destinationFrame.height; + filterArea[2] = state.sourceFrame.x; + filterArea[3] = state.sourceFrame.y; + + filterClamp[0] = 0.5 / state.resolution / state.destinationFrame.width; + filterClamp[1] = 0.5 / state.resolution / state.destinationFrame.height; + filterClamp[2] = (state.sourceFrame.width - 0.5) / state.resolution / state.destinationFrame.width; + filterClamp[3] = (state.sourceFrame.height - 0.5) / state.resolution / state.destinationFrame.height; + } + + this.globalUniforms.update(); + + const lastState = filterStack[filterStack.length - 1]; + + if (filters.length === 1) + { + filters[0].apply(this, state.renderTexture, lastState.renderTexture, false, state); + + this.returnFilterTexture(state.renderTexture); + } + else + { + let flip = state.renderTexture; + let flop = this.getPotFilterTexture( + flip.width, + flip.height, + state.resolution + ); + + flop.filterFrame = flip.filterFrame; + + let i = 0; + + for (i = 0; i < filters.length - 1; ++i) + { + filters[i].apply(this, flip, flop, true, state); + + const t = flip; + + flip = flop; + flop = t; + } + + filters[i].apply(this, flip, lastState.renderTexture, false, state); + + this.returnFilterTexture(flip); + this.returnFilterTexture(flop); + } + + this.statePool.push(state); + } + + /** + * Draws a filter. + * + * @param {PIXI.Filter} filter - The filter to draw. + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + * @param {boolean} clear - Should the output be cleared before rendering to it + */ + applyFilter(filter, input, output, clear) + { + const renderer = this.renderer; + + renderer.renderTexture.bind(output, output ? output.filterFrame : null); + + if (clear) + { + // gl.disable(gl.SCISSOR_TEST); + renderer.renderTexture.clear(); + // gl.enable(gl.SCISSOR_TEST); + } + + // set the uniforms.. + filter.uniforms.uSampler = input; + filter.uniforms.filterGlobals = this.globalUniforms; + + // TODO make it so that the order of this does not matter.. + // because it does at the moment cos of global uniforms. + // they need to get resynced + + renderer.state.setState(filter.state); + renderer.shader.bind(filter); + + if (filter.legacy) + { + this.quadUv.map(input._frame, input.filterFrame); + + renderer.geometry.bind(this.quadUv); + renderer.geometry.draw(DRAW_MODES.TRIANGLES); + } + else + { + renderer.geometry.bind(this.quad); + renderer.geometry.draw(DRAW_MODES.TRIANGLE_STRIP); + } + } + + /** + * Calculates the mapped matrix. + * + * TODO playing around here.. this is temporary - (will end up in the shader) + * this returns a matrix that will normalise map filter cords in the filter to screen space + * + * @param {PIXI.Matrix} outputMatrix - the matrix to output to. + * @return {PIXI.Matrix} The mapped matrix. + */ + calculateScreenSpaceMatrix(outputMatrix) + { + const currentState = this.activeState; + + return filterTransforms.calculateScreenSpaceMatrix( + outputMatrix, + currentState.sourceFrame, + currentState.destinationFrame + ); + } + + /** + * This will map the filter coord so that a texture can be used based on the transform of a sprite + * + * @param {PIXI.Matrix} outputMatrix - The matrix to output to. + * @param {PIXI.Sprite} sprite - The sprite to map to. + * @return {PIXI.Matrix} The mapped matrix. + */ + calculateSpriteMatrix(outputMatrix, sprite) + { + const currentState = this.activeState; + + return filterTransforms.calculateSpriteMatrix( + outputMatrix, + currentState.sourceFrame, + currentState.destinationFrame, + sprite + ); + } + + /** + * Destroys this Filter System. + * + * @param {boolean} [contextLost=false] context was lost, do not free shaders + * + */ + destroy(contextLost = false) + { + if (!contextLost) + { + this.emptyPool(); + } + else + { + this.texturePool = {}; + } + } + + /** + * Gets a Power-of-Two render texture. + * + * TODO move to a seperate class could be on renderer? + * also - could cause issue with multiple contexts? + * + * @private + * @param {WebGLRenderingContext} gl - The webgl rendering context + * @param {number} minWidth - The minimum width of the render target. + * @param {number} minHeight - The minimum height of the render target. + * @param {number} resolution - The resolution of the render target. + * @return {PIXI.RenderTarget} The new render target. + */ + getPotFilterTexture(minWidth, minHeight, resolution) + { + minWidth = bitTwiddle.nextPow2(minWidth); + minHeight = bitTwiddle.nextPow2(minHeight); + resolution = resolution || 1; + + const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + + if (!this.texturePool[key]) + { + this.texturePool[key] = []; + } + + let renderTexture = this.texturePool[key].pop(); + + if (!renderTexture) + { + // temporary bypass cache.. + // internally - this will cause a texture to be bound.. + renderTexture = RenderTexture.create({ + width: minWidth, + height: minHeight, + resolution, + }); + } + + return renderTexture; + } + + /** + * Gets extra render texture to use inside current filter + * + * @param {number} resolution resolution of the renderTexture + * @returns {PIXI.RenderTexture} + */ + getFilterTexture(resolution) + { + const rt = this.activeState.renderTexture; + + const filterTexture = this.getPotFilterTexture(rt.width, rt.height, resolution || rt.baseTexture.resolution); + + filterTexture.filterFrame = rt.filterFrame; + + return filterTexture; + } + + /** + * Frees a render texture back into the pool. + * + * @param {PIXI.RenderTarget} renderTexture - The renderTarget to free + */ + returnFilterTexture(renderTexture) + { + renderTexture.filterFrame = null; + + const base = renderTexture.baseTexture; + + const minWidth = base.width; + const minHeight = base.height; + + const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + + this.texturePool[key].push(renderTexture); + } + + /** + * Empties the texture pool. + * + */ + emptyPool() + { + for (const i in this.texturePool) + { + const textures = this.texturePool[i]; + + if (textures) + { + for (let j = 0; j < textures.length; j++) + { + textures[j].destroy(true); + } + } + } + + this.texturePool = {}; + } +} diff --git a/packages/core/src/AbstractRenderer.js b/packages/core/src/AbstractRenderer.js new file mode 100644 index 0000000..64b2eb1 --- /dev/null +++ b/packages/core/src/AbstractRenderer.js @@ -0,0 +1,353 @@ +import { hex2string, hex2rgb } from '@pixi/utils'; +import { Matrix, Rectangle } from '@pixi/math'; +import { RENDERER_TYPE } from '@pixi/constants'; +import { settings } from '@pixi/settings'; +import { Container } from '@pixi/display'; +import RenderTexture from './renderTexture/RenderTexture'; +import EventEmitter from 'eventemitter3'; + +const tempMatrix = new Matrix(); + +/** + * 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 EventEmitter + * @memberof PIXI + */ +export default class AbstractRenderer extends EventEmitter +{ + // eslint-disable-next-line valid-jsdoc + /** + * @param {string} system - The name of the system this renderer is for. + * @param {object} [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.transparent=false] - If the render view is transparent, default false + * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false + * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. The + * resolution of the renderer retina would be 2. + * @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 {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when rendering, + * stopping pixel interpolation. + */ + constructor(system, options, arg2, arg3) + { + super(); + + // Support for constructor(system, screenWidth, screenHeight, options) + if (typeof options === 'number') + { + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + }, arg3); + } + + // 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 = RENDERER_TYPE.UNKNOWN; + + /** + * Measurements of the screen. (0, 0, screenWidth, screenHeight) + * + * Its safe to use as filterArea or hitArea for 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 1 + */ + this.resolution = options.resolution || settings.RESOLUTION; + + /** + * Whether the render view is transparent + * + * @member {boolean} + */ + this.transparent = options.transparent; + + /** + * Whether css dimensions of canvas view should be resized to screen dimensions automatically + * + * @member {boolean} + */ + this.autoResize = options.autoResize || false; + + /** + * Tracks the blend modes useful for this renderer. + * + * @member {object} + */ + this.blendModes = null; + + /** + * 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; + + /** + * If true PixiJS will Math.floor() x/y values when rendering, stopping pixel interpolation. + * Handy for crisp pixel art and speed on legacy devices. + * + * @member {boolean} + */ + this.roundPixels = options.roundPixels; + + /** + * The background color as a number. + * + * @member {number} + * @private + */ + this._backgroundColor = 0x000000; + + /** + * The background color as an [R, G, B] array. + * + * @member {number[]} + * @private + */ + this._backgroundColorRgba = [0, 0, 0, 0]; + + /** + * The background color as a string. + * + * @member {string} + * @private + */ + this._backgroundColorString = '#000000'; + + this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter + + /** + * This temporary display object used as the parent of the currently being rendered item + * + * @member {PIXI.DisplayObject} + * @private + */ + this._tempDisplayObjectParent = new Container(); + + /** + * The last root object that the renderer tried to render. + * + * @member {PIXI.DisplayObject} + * @private + */ + this._lastObjectRendered = this._tempDisplayObjectParent; + + /** + * Collection of plugins. + * @readonly + * @member {object} + */ + this.plugins = {}; + } + + /** + * Initialize the plugins. + * + * @protected + * @param {object} staticMap - The dictionary of staticly saved plugins. + */ + initPlugins(staticMap) + { + 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() + { + return this.view.width; + } + + /** + * Same as view.height, actual number of pixels in the canvas by vertical + * + * @member {number} + * @readonly + * @default 600 + */ + get height() + { + return this.view.height; + } + + /** + * Resizes the screen and canvas to the specified width and height + * Canvas dimensions are multiplied by resolution + * + * @param {number} screenWidth - the new width of the screen + * @param {number} screenHeight - the new height of the screen + */ + resize(screenWidth, screenHeight) + { + this.screen.width = screenWidth; + this.screen.height = screenHeight; + + this.view.width = screenWidth * this.resolution; + this.view.height = screenHeight * this.resolution; + + if (this.autoResize) + { + this.view.style.width = `${screenWidth}px`; + this.view.style.height = `${screenHeight}px`; + } + } + + /** + * 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 {PIXI.DisplayObject} displayObject - The displayObject the object will be generated from + * @param {number} scaleMode - Should be one of the scaleMode consts + * @param {number} resolution - The resolution / device pixel ratio of the texture being generated + * @param {PIXI.Rectangle} [region] - The region of the displayObject, that shall be rendered, + * if no region is specified, defaults to the local bounds of the displayObject. + * @return {PIXI.Texture} a texture of the graphics object + */ + generateTexture(displayObject, scaleMode, resolution, region) + { + region = region || displayObject.getLocalBounds(); + + // 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(region.width | 0, region.height | 0, scaleMode, resolution); + + tempMatrix.tx = -region.x; + tempMatrix.ty = -region.y; + + this.render(displayObject, renderTexture, false, tempMatrix, true); + + return renderTexture; + } + + /** + * Removes everything from the renderer and optionally removes the Canvas DOM element. + * + * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. + */ + destroy(removeView) + { + 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); + } + + this.plugins = null; + + this.type = RENDERER_TYPE.UNKNOWN; + + this.view = null; + + this.screen = null; + + this.resolution = 0; + + this.transparent = false; + + this.autoResize = false; + + this.blendModes = null; + + this.options = null; + + this.preserveDrawingBuffer = false; + this.clearBeforeRender = false; + + this.roundPixels = false; + + this._backgroundColor = 0; + this._backgroundColorRgba = null; + this._backgroundColorString = null; + + this._tempDisplayObjectParent = null; + this._lastObjectRendered = null; + } + + /** + * The background color to fill if not transparent + * + * @member {number} + */ + get backgroundColor() + { + return this._backgroundColor; + } + + set backgroundColor(value) // eslint-disable-line require-jsdoc + { + this._backgroundColor = value; + this._backgroundColorString = hex2string(value); + hex2rgb(value, this._backgroundColorRgba); + } +} diff --git a/packages/core/src/Renderer.js b/packages/core/src/Renderer.js new file mode 100644 index 0000000..2227e78 --- /dev/null +++ b/packages/core/src/Renderer.js @@ -0,0 +1,338 @@ +import AbstractRenderer from './AbstractRenderer'; +import { sayHello } from '@pixi/utils'; +import MaskSystem from './mask/MaskSystem'; +import StencilSystem from './mask/StencilSystem'; +import FilterSystem from './filters/FilterSystem'; +import FramebufferSystem from './framebuffer/FramebufferSystem'; +import RenderTextureSystem from './renderTexture/RenderTextureSystem'; +import TextureSystem from './textures/TextureSystem'; +import ProjectionSystem from './projection/ProjectionSystem'; +import StateSystem from './state/StateSystem'; +import GeometrySystem from './geometry/GeometrySystem'; +import ShaderSystem from './shader/ShaderSystem'; +import ContextSystem from './context/ContextSystem'; +import BatchSystem from './batch/BatchSystem'; +import TextureGCSystem from './textures/TextureGCSystem'; +import { RENDERER_TYPE } from '@pixi/constants'; +import UniformGroup from './shader/UniformGroup'; +import { Matrix } from '@pixi/math'; +import Runner from 'mini-runner'; + +/** + * 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 Render works by automatically managing webGLBatchs. + * 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 :) + * + * @class + * @memberof PIXI + * @extends PIXI.AbstractRenderer + */ +export default class Renderer extends AbstractRenderer +{ + // eslint-disable-next-line valid-jsdoc + /** + * + * @param {object} [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.transparent=false] - If the render view is transparent, default false + * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false + * @param {boolean} [options.antialias=false] - sets antialias. If not available natively then FXAA + * antialiasing is used + * @param {boolean} [options.forceFXAA=false] - forces FXAA antialiasing to be used over native. + * FXAA is faster, but may not always look as great + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. + * The resolution of the renderer retina would be 2. + * @param {boolean} [options.clearBeforeRender=true] - This sets if the renderer will clear + * the canvas or not before the new render pass. If you wish to set this to false, you *must* set + * preserveDrawingBuffer to `true`. + * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, + * enable this if you need to call toDataUrl on the webgl context. + * @param {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when + * rendering, stopping pixel interpolation. + * @param {number} [options.backgroundColor=0x000000] - The background color of the rendered area + * (shown if not transparent). + * @param {boolean} [options.legacy=false] - If true PixiJS will aim to ensure compatibility + * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. + * @param {string} [options.powerPreference] - Parameter passed to webgl context, set to "high-performance" + * for devices with dual graphics card + */ + constructor(options, arg2, arg3) + { + super('WebGL', options, arg2, arg3); + + /** + * The type of this renderer as a standardised const + * + * @member {number} + * @see PIXI.RENDERER_TYPE + */ + this.type = RENDERER_TYPE.WEBGL; + + // this will be set by the contextSystem (this.context) + this.gl = null; + this.CONTEXT_UID = 0; + this.legacy = !!options.legacy; + + // TODO legacy! + + // runners! + this.runners = { + destroy: new Runner('destroy'), + contextChange: new Runner('contextChange', 1), + reset: new Runner('reset'), + update: new Runner('update'), + postrender: new Runner('postrender'), + prerender: new Runner('prerender'), + resize: new Runner('resize', 2), + }; + + 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(GeometrySystem, 'geometry') + .addSystem(FramebufferSystem, 'framebuffer') + .addSystem(StencilSystem, 'stencil') + .addSystem(ProjectionSystem, 'projection') + .addSystem(TextureGCSystem, 'textureGC') + .addSystem(FilterSystem, 'filter') + .addSystem(RenderTextureSystem, 'renderTexture') + .addSystem(BatchSystem, 'batch'); + + this.initPlugins(Renderer.__plugins); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ + if (options.context) + { + this.context.initFromContext(options.context); + } + else + { + this.context.initFromOptions({ + alpha: this.transparent, + antialias: options.antialias, + premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', + stencil: true, + 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); + } + + /** + * Add a new system to the renderer. + * @param {class} ClassRef - Class reference + * @param {string} [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. + * @return {PIXI.Renderer} Return instance of renderer + */ + addSystem(ClassRef, name) + { + if (!name) + { + name = ClassRef.name; + } + + // TODO - read name from class.name.. + + /* + if(name.includes('System')) + { + name = name.replace('System', ''); + name = name.charAt(0).toLowerCase() + name.slice(1); + } + */ + + const system = new ClassRef(this); + + if (this[name]) + { + throw new Error(`Whoops! ${name} is already a manger`); + } + + this[name] = system; + + for (const i in this.runners) + { + this.runners[i].add(system); + } + + return this; + + /** + * 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. + */ + } + + /** + * Renders the object to its webGL view + * + * @param {PIXI.DisplayObject} displayObject - the object to be rendered + * @param {PIXI.RenderTexture} renderTexture - The render texture to render to. + * @param {boolean} [clear] - Should the canvas be cleared before the new render + * @param {PIXI.Transform} [transform] - A transform to apply to the render texture before rendering. + * @param {boolean} [skipUpdateTransform] - Should we skip the update transform pass? + */ + render(displayObject, renderTexture, clear, transform, skipUpdateTransform) + { + // can be handy to know! + this.renderingToScreen = !renderTexture; + + this.runners.prerender.run(); + this.emit('prerender'); + + // 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.parent; + + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = 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.run(); + + this.emit('postrender'); + } + + /** + * Resizes the webGL view to the specified width and height. + * + * @param {number} screenWidth - the new width of the screen + * @param {number} screenHeight - the new height of the screen + */ + resize(screenWidth, screenHeight) + { + AbstractRenderer.prototype.resize.call(this, screenWidth, screenHeight); + + this.runners.resize.run(screenWidth, screenHeight); + } + + /** + * Resets the WebGL state so you can render things however you fancy! + * + * @return {PIXI.Renderer} Returns itself. + */ + reset() + { + this.runners.reset.run(); + + return this; + } + + clear() + { + this.framebuffer.bind(); + this.framebuffer.clear(); + } + + /** + * Removes everything from the renderer (event listeners, spritebatch, etc...) + * + * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. + * See: https://github.com/pixijs/pixi.js/issues/2233 + */ + destroy(removeView) + { + this.runners.destroy.run(); + + // call base destroy + super.destroy(removeView); + + // TODO nullify all the managers.. + this.gl = null; + } + + /** + * 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 + * builds and excluding plugins. + * @name PIXI.Renderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. + */ + + /** + * Adds a plugin to the renderer. + * + * @method + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + static registerPlugin(pluginName, ctor) + { + Renderer.__plugins = Renderer.__plugins || {}; + Renderer.__plugins[pluginName] = ctor; + } +} diff --git a/packages/core/src/System.js b/packages/core/src/System.js new file mode 100644 index 0000000..05a10c8 --- /dev/null +++ b/packages/core/src/System.js @@ -0,0 +1,42 @@ +/** + * System is a base class used for extending systems used by the {@link PIXI.Renderer} + * @see PIXI.Renderer#addSystem + * @class + * @memberof PIXI + */ +export default class System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + /** + * The renderer this manager works for. + * + * @member {PIXI.Renderer} + */ + this.renderer = renderer; + + this.renderer.runners.contextChange.add(this); + } + + /** + * Generic method called when there is a WebGL context change. + * + */ + contextChange() + { + // do some codes init! + } + + /** + * Generic destroy methods to be overridden by the subclass + * + */ + destroy() + { + this.renderer.runners.contextChange.remove(this); + this.renderer = null; + } +} diff --git a/packages/core/src/batch/BatchSystem.js b/packages/core/src/batch/BatchSystem.js new file mode 100644 index 0000000..b04c249 --- /dev/null +++ b/packages/core/src/batch/BatchSystem.js @@ -0,0 +1,68 @@ +import System from '../System'; +import ObjectRenderer from './ObjectRenderer'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class BatchSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + super(renderer); + + /** + * An empty renderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.emptyRenderer = new ObjectRenderer(renderer); + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.currentRenderer = this.emptyRenderer; + } + + /** + * Changes the current renderer to the one given in parameter + * + * @param {PIXI.ObjectRenderer} objectRenderer - The object renderer to use. + */ + setObjectRenderer(objectRenderer) + { + if (this.currentRenderer === objectRenderer) + { + return; + } + + this.currentRenderer.stop(); + this.currentRenderer = objectRenderer; + + this.renderer.state.setState(objectRenderer.state); + + this.currentRenderer.start(); + } + + /** + * This should be called if you wish to do some custom rendering + * It will basically render anything that may be batched up such as sprites + * + */ + flush() + { + this.setObjectRenderer(this.emptyRenderer); + } + + reset() + { + this.setObjectRenderer(this.emptyRenderer); + } +} diff --git a/packages/core/src/batch/ObjectRenderer.js b/packages/core/src/batch/ObjectRenderer.js new file mode 100644 index 0000000..0c851a9 --- /dev/null +++ b/packages/core/src/batch/ObjectRenderer.js @@ -0,0 +1,48 @@ +import System from '../System'; + +/** + * Base for a common object renderer that can be used as a system renderer plugin. + * + * @class + * @extends PIXI.System + * @memberof PIXI + */ +export default class ObjectRenderer extends System +{ + /** + * Starts the renderer and sets the shader + * + */ + start() + { + // set the shader.. + } + + /** + * Stops the renderer + * + */ + stop() + { + this.flush(); + } + + /** + * Stub method for rendering content and emptying the current batch. + * + */ + flush() + { + // flush! + } + + /** + * Renders an object + * + * @param {PIXI.DisplayObject} object - The object to render. + */ + render(object) // eslint-disable-line no-unused-vars + { + // render the object + } +} diff --git a/packages/core/src/context/ContextSystem.js b/packages/core/src/context/ContextSystem.js new file mode 100644 index 0000000..301aab4 --- /dev/null +++ b/packages/core/src/context/ContextSystem.js @@ -0,0 +1,186 @@ +import System from '../System'; +import { settings } from '@pixi/settings'; + +let CONTEXT_UID = 0; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class ContextSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.webGLVersion = 1; + + this.handleContextLost = this.handleContextLost.bind(this); + this.handleContextRestored = this.handleContextRestored.bind(this); + + this.extensions = {}; + + renderer.view.addEventListener('webglcontextlost', this.handleContextLost, false); + renderer.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + } + + get isLost() + { + return (!this.gl || this.gl.isContextLost()); + } + + contextChange(gl) + { + this.gl = gl; + + // restore a context if it was previously lost + if (gl.isContextLost() && gl.getExtension('WEBGL_lose_context')) + { + gl.getExtension('WEBGL_lose_context').restoreContext(); + } + } + + initFromContext(gl) + { + this.gl = gl; + this.validateContext(gl); + this.renderer.gl = gl; + this.renderer.CONTEXT_UID = CONTEXT_UID++; + this.renderer.runners.contextChange.run(gl); + } + + initFromOptions(options) + { + const gl = this.createContext(this.renderer.view, options); + + this.initFromContext(gl); + } + + /** + * Helper class to create a webGL Context + * + * @class + * @memberof PIXI.glCore + * @param canvas {HTMLCanvasElement} the canvas element that we will get the context from + * @param options {Object} An options object that gets passed in to the canvas element containing the context attributes, + * see https://developer.mozilla.org/en/docs/Web/API/HTMLCanvasElement/getContext for the options available + * @return {WebGLRenderingContext} the WebGL context + */ + createContext(canvas, options) + { + let gl; + + if (settings.PREFER_WEBGL_2) + { + gl = canvas.getContext('webgl2', options); + } + + if (gl) + { + this.webGLVersion = 2; + } + else + { + this.webGLVersion = 1; + + gl = canvas.getContext('webgl', options) + || canvas.getContext('experimental-webgl', options); + + if (!gl) + { + // fail, not able to get a context + throw new Error('This browser does not support webGL. Try using the canvas renderer'); + } + } + + this.gl = gl; + + this.getExtensions(); + + return gl; + } + + getExtensions() + { + // time to set up default etensions that pixi uses.. + const gl = this.gl; + const extensions = this.extensions; + + if (this.webGLVersion === 1) + { + extensions.drawBuffers = gl.getExtension('WEBGL_draw_buffers'); + extensions.depthTexture = gl.getExtension('WEBKIT_WEBGL_depth_texture'); + extensions.floatTexture = gl.getExtension('OES_texture_float'); + extensions.loseContext = gl.getExtension('WEBGL_lose_context'); + + extensions.vertexArrayObject = gl.getExtension('OES_vertex_array_object') + || gl.getExtension('MOZ_OES_vertex_array_object') + || gl.getExtension('WEBKIT_OES_vertex_array_object'); + } + + // we don't use any specific WebGL 2 ones yet! + } + + /** + * Handles a lost webgl context + * + * @private + * @param {WebGLContextEvent} event - The context lost event. + */ + handleContextLost(event) + { + event.preventDefault(); + } + + /** + * Handles a restored webgl context + * + * @private + */ + handleContextRestored() + { + this.renderer.runners.contextChange.run(this.gl); + } + + destroy() + { + const view = this.renderer.view; + + // remove listeners + view.removeEventListener('webglcontextlost', this.handleContextLost); + view.removeEventListener('webglcontextrestored', this.handleContextRestored); + + this.gl.useProgram(null); + + if (this.extensions.loseContext) + { + this.extensions.loseContext.loseContext(); + } + } + + postrender() + { + this.gl.flush(); + } + + validateContext(gl) + { + const attributes = gl.getContextAttributes(); + + // this is going to be fairly simple for now.. but at least we have room to grow! + if (!attributes.stencil) + { + /* eslint-disable max-len */ + + /* eslint-disable no-console */ + console.warn('Provided WebGL context does not have a stencil buffer, masks may not render correctly'); + /* eslint-enable no-console */ + + /* eslint-enable max-len */ + } + } +} diff --git a/packages/core/src/filters/Filter.js b/packages/core/src/filters/Filter.js new file mode 100644 index 0000000..9d8cad8 --- /dev/null +++ b/packages/core/src/filters/Filter.js @@ -0,0 +1,140 @@ +import Shader from '../shader/Shader'; +import Program from '../shader/Program'; +import State from '../state/State'; +import { settings } from '@pixi/settings'; +// import extractUniformsFromSrc from './extractUniformsFromSrc'; +import defaultVertex from './defaultFilter.vert'; +import defaultFragment from './defaultFilter.frag'; + +// let math = require('../../math'); +/** + * @class + * @memberof PIXI + * @extends PIXI.Shader + */ +export default class Filter extends Shader +{ + /** + * @param {string} [vertexSrc] - The source of the vertex shader. + * @param {string} [fragmentSrc] - The source of the fragment shader. + * @param {object} [uniforms] - Custom uniforms to use to augment the built-in ones. + */ + constructor(vertexSrc, fragmentSrc, uniforms) + { + const program = Program.from(vertexSrc, fragmentSrc); + + super(program, uniforms); + + /** + * The padding of the filter. Some filters require extra space to breath such as a blur. + * Increasing this will add extra width and height to the bounds of the object that the + * filter is applied to. + * + * @member {number} + */ + this.padding = 0; + + /** + * The resolution of the filter. Setting this to be lower will lower the quality but + * increase the performance of the filter. + * + * @member {number} + */ + this.resolution = settings.RESOLUTION; + + /** + * If enabled is true the filter is applied, if false it will not. + * + * @member {boolean} + */ + this.enabled = true; + + /** + * If enabled, PixiJS will fit the filter area into boundaries for better performance. + * Switch it off if it does not work for specific shader. + * + * @member {boolean} + */ + this.autoFit = true; + + /** + * Legacy filters use position and uvs from attributes + * @member {boolean} + * @readonly + */ + this.legacy = !!this.program.attributeData.aTextureCoord; + + /** + * the webGL state the filter requires to render + * @member {PIXI.State} + */ + this.state = new State(); + } + + /** + * Applies the filter + * + * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + * @param {boolean} clear - Should the output be cleared before rendering to it + * @param {object} [currentState] - It's current state of filter. + * There are some useful properties in the currentState : + * target, filters, sourceFrame, destinationFrame, renderTarget, resolution + */ + apply(filterManager, input, output, clear, currentState, derp) // eslint-disable-line no-unused-vars + { + // do as you please! + + filterManager.applyFilter(this, input, output, clear, currentState, derp); + + // or just do a regular render.. + } + + /** + * Sets the blendmode of the filter + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL + */ + get blendMode() + { + return this.state.blendMode; + } + + set blendMode(value) // eslint-disable-line require-jsdoc + { + this.state.blendMode = value; + } + + /** + * The default vertex shader source + * + * @static + * @constant + */ + static get defaultVertexSrc() + { + return defaultVertex; + } + + /** + * The default fragment shader source + * + * @static + * @constant + */ + static get defaultFragmentSrc() + { + return defaultFragment; + } +} + +/** + * Used for caching shader IDs + * + * @static + * @private + */ +Filter.SOURCE_KEY_MAP = {}; + diff --git a/packages/core/src/filters/FilterSystem.js b/packages/core/src/filters/FilterSystem.js new file mode 100644 index 0000000..3174318 --- /dev/null +++ b/packages/core/src/filters/FilterSystem.js @@ -0,0 +1,431 @@ +import System from '../System'; + +import RenderTexture from '../renderTexture/RenderTexture'; +import Quad from '../utils/Quad'; +import QuadUv from '../utils/QuadUv'; +import { Rectangle } from '@pixi/math'; +import * as filterTransforms from './filterTransforms'; +import bitTwiddle from 'bit-twiddle'; +import UniformGroup from '../shader/UniformGroup'; +import { DRAW_MODES } from '@pixi/constants'; + +// +/** + * @ignore + * @class + */ +class FilterState +{ + /** + * + */ + constructor() + { + this.renderTexture = null; + this.sourceFrame = new Rectangle(); + this.destinationFrame = new Rectangle(); + this.filters = []; + this.target = null; + this.legacy = false; + this.resolution = 1; + } +} + +/** + * @class + * @memberof PIXI.systems + * @extends PIXI.System + */ +export default class FilterSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + /** + * stores a bunch of PO2 textures used for filtering + * @type {Object} + */ + this.texturePool = {}; + + /** + * a pool for storing filter states, save us creating new ones each tick + * @type {Array} + */ + this.statePool = []; + + /** + * A very simple geometry used when drawing a filter effect to the screen + * @type {Quad} + */ + this.quad = new Quad(); + + this.quadUv = new QuadUv(); + + /** + * Temporary rect for maths + * @type {PIXI.Rectangle} + */ + this.tempRect = new Rectangle(); + + this.activeState = {}; + + /** + * this uniform group is attached to filter uniforms when used + * @type {UniformGroup} + */ + this.globalUniforms = new UniformGroup({ + sourceFrame: this.tempRect, + destinationFrame: this.tempRect, + + // legacy variables + filterArea: new Float32Array(4), + filterClamp: new Float32Array(4), + }, true); + } + + /** + * Adds a new filter to the System. + * + * @param {PIXI.DisplayObject} target - The target of the filter to render. + * @param {PIXI.Filter[]} filters - The filters to apply. + */ + push(target, filters) + { + const renderer = this.renderer; + const filterStack = this.renderer.renderTexture.defaultFilterStack; + const state = this.statePool.pop() || new FilterState(); + + let resolution = filters[0].resolution; + let padding = filters[0].padding; + let autoFit = filters[0].autoFit; + let legacy = filters[0].legacy; + + for (let i = 1; i < filters.length; i++) + { + const filter = filters[i]; + + // lets use the lowest resolution.. + resolution = Math.min(resolution, filter.resolution); + // and the largest amount of padding! + padding = Math.max(padding, filter.padding); + // only auto fit if all filters are autofit + autoFit = autoFit || filter.autoFit; + + legacy = legacy || filter.legacy; + } + + filterStack.push(state); + + state.resolution = resolution; + + state.legacy = legacy; + + // round to whole number based on resolution + // TODO move that to the shader too? + state.sourceFrame = target.filterArea || target.getBounds(true); + + state.sourceFrame.pad(padding); + + if (autoFit) + { + state.sourceFrame.fit(this.renderer.renderTexture.destinationFrame); + } + + state.sourceFrame.round(resolution); + + state.renderTexture = this.getPotFilterTexture(state.sourceFrame.width, state.sourceFrame.height, resolution); + state.filters = filters; + + state.destinationFrame.width = state.renderTexture.width; + state.destinationFrame.height = state.renderTexture.height; + + state.renderTexture.filterFrame = state.sourceFrame; + renderer.renderTexture.bind(state.renderTexture, state.sourceFrame);// /, state.destinationFrame); + renderer.renderTexture.clear(); + } + + /** + * Pops off the filter and applies it. + * + */ + pop() + { + const renderer = this.renderer; + const filterStack = renderer.renderTexture.defaultFilterStack; + const state = filterStack.pop(); + const filters = state.filters; + + this.activeState = state; + + const globalUniforms = this.globalUniforms.uniforms; + + globalUniforms.sourceFrame = state.sourceFrame; + globalUniforms.destinationFrame = state.destinationFrame; + globalUniforms.resolution = state.resolution; + + // only update the rect if its legacy.. + if (state.legacy) + { + const filterArea = globalUniforms.filterArea; + const filterClamp = globalUniforms.filterClamp; + + filterArea[0] = state.destinationFrame.width; + filterArea[1] = state.destinationFrame.height; + filterArea[2] = state.sourceFrame.x; + filterArea[3] = state.sourceFrame.y; + + filterClamp[0] = 0.5 / state.resolution / state.destinationFrame.width; + filterClamp[1] = 0.5 / state.resolution / state.destinationFrame.height; + filterClamp[2] = (state.sourceFrame.width - 0.5) / state.resolution / state.destinationFrame.width; + filterClamp[3] = (state.sourceFrame.height - 0.5) / state.resolution / state.destinationFrame.height; + } + + this.globalUniforms.update(); + + const lastState = filterStack[filterStack.length - 1]; + + if (filters.length === 1) + { + filters[0].apply(this, state.renderTexture, lastState.renderTexture, false, state); + + this.returnFilterTexture(state.renderTexture); + } + else + { + let flip = state.renderTexture; + let flop = this.getPotFilterTexture( + flip.width, + flip.height, + state.resolution + ); + + flop.filterFrame = flip.filterFrame; + + let i = 0; + + for (i = 0; i < filters.length - 1; ++i) + { + filters[i].apply(this, flip, flop, true, state); + + const t = flip; + + flip = flop; + flop = t; + } + + filters[i].apply(this, flip, lastState.renderTexture, false, state); + + this.returnFilterTexture(flip); + this.returnFilterTexture(flop); + } + + this.statePool.push(state); + } + + /** + * Draws a filter. + * + * @param {PIXI.Filter} filter - The filter to draw. + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + * @param {boolean} clear - Should the output be cleared before rendering to it + */ + applyFilter(filter, input, output, clear) + { + const renderer = this.renderer; + + renderer.renderTexture.bind(output, output ? output.filterFrame : null); + + if (clear) + { + // gl.disable(gl.SCISSOR_TEST); + renderer.renderTexture.clear(); + // gl.enable(gl.SCISSOR_TEST); + } + + // set the uniforms.. + filter.uniforms.uSampler = input; + filter.uniforms.filterGlobals = this.globalUniforms; + + // TODO make it so that the order of this does not matter.. + // because it does at the moment cos of global uniforms. + // they need to get resynced + + renderer.state.setState(filter.state); + renderer.shader.bind(filter); + + if (filter.legacy) + { + this.quadUv.map(input._frame, input.filterFrame); + + renderer.geometry.bind(this.quadUv); + renderer.geometry.draw(DRAW_MODES.TRIANGLES); + } + else + { + renderer.geometry.bind(this.quad); + renderer.geometry.draw(DRAW_MODES.TRIANGLE_STRIP); + } + } + + /** + * Calculates the mapped matrix. + * + * TODO playing around here.. this is temporary - (will end up in the shader) + * this returns a matrix that will normalise map filter cords in the filter to screen space + * + * @param {PIXI.Matrix} outputMatrix - the matrix to output to. + * @return {PIXI.Matrix} The mapped matrix. + */ + calculateScreenSpaceMatrix(outputMatrix) + { + const currentState = this.activeState; + + return filterTransforms.calculateScreenSpaceMatrix( + outputMatrix, + currentState.sourceFrame, + currentState.destinationFrame + ); + } + + /** + * This will map the filter coord so that a texture can be used based on the transform of a sprite + * + * @param {PIXI.Matrix} outputMatrix - The matrix to output to. + * @param {PIXI.Sprite} sprite - The sprite to map to. + * @return {PIXI.Matrix} The mapped matrix. + */ + calculateSpriteMatrix(outputMatrix, sprite) + { + const currentState = this.activeState; + + return filterTransforms.calculateSpriteMatrix( + outputMatrix, + currentState.sourceFrame, + currentState.destinationFrame, + sprite + ); + } + + /** + * Destroys this Filter System. + * + * @param {boolean} [contextLost=false] context was lost, do not free shaders + * + */ + destroy(contextLost = false) + { + if (!contextLost) + { + this.emptyPool(); + } + else + { + this.texturePool = {}; + } + } + + /** + * Gets a Power-of-Two render texture. + * + * TODO move to a seperate class could be on renderer? + * also - could cause issue with multiple contexts? + * + * @private + * @param {WebGLRenderingContext} gl - The webgl rendering context + * @param {number} minWidth - The minimum width of the render target. + * @param {number} minHeight - The minimum height of the render target. + * @param {number} resolution - The resolution of the render target. + * @return {PIXI.RenderTarget} The new render target. + */ + getPotFilterTexture(minWidth, minHeight, resolution) + { + minWidth = bitTwiddle.nextPow2(minWidth); + minHeight = bitTwiddle.nextPow2(minHeight); + resolution = resolution || 1; + + const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + + if (!this.texturePool[key]) + { + this.texturePool[key] = []; + } + + let renderTexture = this.texturePool[key].pop(); + + if (!renderTexture) + { + // temporary bypass cache.. + // internally - this will cause a texture to be bound.. + renderTexture = RenderTexture.create({ + width: minWidth, + height: minHeight, + resolution, + }); + } + + return renderTexture; + } + + /** + * Gets extra render texture to use inside current filter + * + * @param {number} resolution resolution of the renderTexture + * @returns {PIXI.RenderTexture} + */ + getFilterTexture(resolution) + { + const rt = this.activeState.renderTexture; + + const filterTexture = this.getPotFilterTexture(rt.width, rt.height, resolution || rt.baseTexture.resolution); + + filterTexture.filterFrame = rt.filterFrame; + + return filterTexture; + } + + /** + * Frees a render texture back into the pool. + * + * @param {PIXI.RenderTarget} renderTexture - The renderTarget to free + */ + returnFilterTexture(renderTexture) + { + renderTexture.filterFrame = null; + + const base = renderTexture.baseTexture; + + const minWidth = base.width; + const minHeight = base.height; + + const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + + this.texturePool[key].push(renderTexture); + } + + /** + * Empties the texture pool. + * + */ + emptyPool() + { + for (const i in this.texturePool) + { + const textures = this.texturePool[i]; + + if (textures) + { + for (let j = 0; j < textures.length; j++) + { + textures[j].destroy(true); + } + } + } + + this.texturePool = {}; + } +} diff --git a/packages/core/src/filters/defaultFilter.frag b/packages/core/src/filters/defaultFilter.frag new file mode 100644 index 0000000..a14c1be --- /dev/null +++ b/packages/core/src/filters/defaultFilter.frag @@ -0,0 +1,22 @@ +varying vec2 vTextureCoord; +varying vec2 vFilterCoord; + +uniform sampler2D uSampler; +uniform sampler2D filterSampler; + +void main(void){ + vec4 masky = texture2D(filterSampler, vFilterCoord); + vec4 sample = texture2D(uSampler, vTextureCoord); + vec4 color; + if(mod(vFilterCoord.x, 1.0) > 0.5) + { + color = vec4(1.0, 0.0, 0.0, 1.0); + } + else + { + color = vec4(0.0, 1.0, 0.0, 1.0); + } + // gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0); + gl_FragColor = mix(sample, masky, 0.5); + gl_FragColor *= sample.a; +} diff --git a/packages/core/src/AbstractRenderer.js b/packages/core/src/AbstractRenderer.js new file mode 100644 index 0000000..64b2eb1 --- /dev/null +++ b/packages/core/src/AbstractRenderer.js @@ -0,0 +1,353 @@ +import { hex2string, hex2rgb } from '@pixi/utils'; +import { Matrix, Rectangle } from '@pixi/math'; +import { RENDERER_TYPE } from '@pixi/constants'; +import { settings } from '@pixi/settings'; +import { Container } from '@pixi/display'; +import RenderTexture from './renderTexture/RenderTexture'; +import EventEmitter from 'eventemitter3'; + +const tempMatrix = new Matrix(); + +/** + * 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 EventEmitter + * @memberof PIXI + */ +export default class AbstractRenderer extends EventEmitter +{ + // eslint-disable-next-line valid-jsdoc + /** + * @param {string} system - The name of the system this renderer is for. + * @param {object} [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.transparent=false] - If the render view is transparent, default false + * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false + * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. The + * resolution of the renderer retina would be 2. + * @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 {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when rendering, + * stopping pixel interpolation. + */ + constructor(system, options, arg2, arg3) + { + super(); + + // Support for constructor(system, screenWidth, screenHeight, options) + if (typeof options === 'number') + { + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + }, arg3); + } + + // 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 = RENDERER_TYPE.UNKNOWN; + + /** + * Measurements of the screen. (0, 0, screenWidth, screenHeight) + * + * Its safe to use as filterArea or hitArea for 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 1 + */ + this.resolution = options.resolution || settings.RESOLUTION; + + /** + * Whether the render view is transparent + * + * @member {boolean} + */ + this.transparent = options.transparent; + + /** + * Whether css dimensions of canvas view should be resized to screen dimensions automatically + * + * @member {boolean} + */ + this.autoResize = options.autoResize || false; + + /** + * Tracks the blend modes useful for this renderer. + * + * @member {object} + */ + this.blendModes = null; + + /** + * 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; + + /** + * If true PixiJS will Math.floor() x/y values when rendering, stopping pixel interpolation. + * Handy for crisp pixel art and speed on legacy devices. + * + * @member {boolean} + */ + this.roundPixels = options.roundPixels; + + /** + * The background color as a number. + * + * @member {number} + * @private + */ + this._backgroundColor = 0x000000; + + /** + * The background color as an [R, G, B] array. + * + * @member {number[]} + * @private + */ + this._backgroundColorRgba = [0, 0, 0, 0]; + + /** + * The background color as a string. + * + * @member {string} + * @private + */ + this._backgroundColorString = '#000000'; + + this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter + + /** + * This temporary display object used as the parent of the currently being rendered item + * + * @member {PIXI.DisplayObject} + * @private + */ + this._tempDisplayObjectParent = new Container(); + + /** + * The last root object that the renderer tried to render. + * + * @member {PIXI.DisplayObject} + * @private + */ + this._lastObjectRendered = this._tempDisplayObjectParent; + + /** + * Collection of plugins. + * @readonly + * @member {object} + */ + this.plugins = {}; + } + + /** + * Initialize the plugins. + * + * @protected + * @param {object} staticMap - The dictionary of staticly saved plugins. + */ + initPlugins(staticMap) + { + 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() + { + return this.view.width; + } + + /** + * Same as view.height, actual number of pixels in the canvas by vertical + * + * @member {number} + * @readonly + * @default 600 + */ + get height() + { + return this.view.height; + } + + /** + * Resizes the screen and canvas to the specified width and height + * Canvas dimensions are multiplied by resolution + * + * @param {number} screenWidth - the new width of the screen + * @param {number} screenHeight - the new height of the screen + */ + resize(screenWidth, screenHeight) + { + this.screen.width = screenWidth; + this.screen.height = screenHeight; + + this.view.width = screenWidth * this.resolution; + this.view.height = screenHeight * this.resolution; + + if (this.autoResize) + { + this.view.style.width = `${screenWidth}px`; + this.view.style.height = `${screenHeight}px`; + } + } + + /** + * 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 {PIXI.DisplayObject} displayObject - The displayObject the object will be generated from + * @param {number} scaleMode - Should be one of the scaleMode consts + * @param {number} resolution - The resolution / device pixel ratio of the texture being generated + * @param {PIXI.Rectangle} [region] - The region of the displayObject, that shall be rendered, + * if no region is specified, defaults to the local bounds of the displayObject. + * @return {PIXI.Texture} a texture of the graphics object + */ + generateTexture(displayObject, scaleMode, resolution, region) + { + region = region || displayObject.getLocalBounds(); + + // 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(region.width | 0, region.height | 0, scaleMode, resolution); + + tempMatrix.tx = -region.x; + tempMatrix.ty = -region.y; + + this.render(displayObject, renderTexture, false, tempMatrix, true); + + return renderTexture; + } + + /** + * Removes everything from the renderer and optionally removes the Canvas DOM element. + * + * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. + */ + destroy(removeView) + { + 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); + } + + this.plugins = null; + + this.type = RENDERER_TYPE.UNKNOWN; + + this.view = null; + + this.screen = null; + + this.resolution = 0; + + this.transparent = false; + + this.autoResize = false; + + this.blendModes = null; + + this.options = null; + + this.preserveDrawingBuffer = false; + this.clearBeforeRender = false; + + this.roundPixels = false; + + this._backgroundColor = 0; + this._backgroundColorRgba = null; + this._backgroundColorString = null; + + this._tempDisplayObjectParent = null; + this._lastObjectRendered = null; + } + + /** + * The background color to fill if not transparent + * + * @member {number} + */ + get backgroundColor() + { + return this._backgroundColor; + } + + set backgroundColor(value) // eslint-disable-line require-jsdoc + { + this._backgroundColor = value; + this._backgroundColorString = hex2string(value); + hex2rgb(value, this._backgroundColorRgba); + } +} diff --git a/packages/core/src/Renderer.js b/packages/core/src/Renderer.js new file mode 100644 index 0000000..2227e78 --- /dev/null +++ b/packages/core/src/Renderer.js @@ -0,0 +1,338 @@ +import AbstractRenderer from './AbstractRenderer'; +import { sayHello } from '@pixi/utils'; +import MaskSystem from './mask/MaskSystem'; +import StencilSystem from './mask/StencilSystem'; +import FilterSystem from './filters/FilterSystem'; +import FramebufferSystem from './framebuffer/FramebufferSystem'; +import RenderTextureSystem from './renderTexture/RenderTextureSystem'; +import TextureSystem from './textures/TextureSystem'; +import ProjectionSystem from './projection/ProjectionSystem'; +import StateSystem from './state/StateSystem'; +import GeometrySystem from './geometry/GeometrySystem'; +import ShaderSystem from './shader/ShaderSystem'; +import ContextSystem from './context/ContextSystem'; +import BatchSystem from './batch/BatchSystem'; +import TextureGCSystem from './textures/TextureGCSystem'; +import { RENDERER_TYPE } from '@pixi/constants'; +import UniformGroup from './shader/UniformGroup'; +import { Matrix } from '@pixi/math'; +import Runner from 'mini-runner'; + +/** + * 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 Render works by automatically managing webGLBatchs. + * 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 :) + * + * @class + * @memberof PIXI + * @extends PIXI.AbstractRenderer + */ +export default class Renderer extends AbstractRenderer +{ + // eslint-disable-next-line valid-jsdoc + /** + * + * @param {object} [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.transparent=false] - If the render view is transparent, default false + * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false + * @param {boolean} [options.antialias=false] - sets antialias. If not available natively then FXAA + * antialiasing is used + * @param {boolean} [options.forceFXAA=false] - forces FXAA antialiasing to be used over native. + * FXAA is faster, but may not always look as great + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. + * The resolution of the renderer retina would be 2. + * @param {boolean} [options.clearBeforeRender=true] - This sets if the renderer will clear + * the canvas or not before the new render pass. If you wish to set this to false, you *must* set + * preserveDrawingBuffer to `true`. + * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, + * enable this if you need to call toDataUrl on the webgl context. + * @param {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when + * rendering, stopping pixel interpolation. + * @param {number} [options.backgroundColor=0x000000] - The background color of the rendered area + * (shown if not transparent). + * @param {boolean} [options.legacy=false] - If true PixiJS will aim to ensure compatibility + * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. + * @param {string} [options.powerPreference] - Parameter passed to webgl context, set to "high-performance" + * for devices with dual graphics card + */ + constructor(options, arg2, arg3) + { + super('WebGL', options, arg2, arg3); + + /** + * The type of this renderer as a standardised const + * + * @member {number} + * @see PIXI.RENDERER_TYPE + */ + this.type = RENDERER_TYPE.WEBGL; + + // this will be set by the contextSystem (this.context) + this.gl = null; + this.CONTEXT_UID = 0; + this.legacy = !!options.legacy; + + // TODO legacy! + + // runners! + this.runners = { + destroy: new Runner('destroy'), + contextChange: new Runner('contextChange', 1), + reset: new Runner('reset'), + update: new Runner('update'), + postrender: new Runner('postrender'), + prerender: new Runner('prerender'), + resize: new Runner('resize', 2), + }; + + 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(GeometrySystem, 'geometry') + .addSystem(FramebufferSystem, 'framebuffer') + .addSystem(StencilSystem, 'stencil') + .addSystem(ProjectionSystem, 'projection') + .addSystem(TextureGCSystem, 'textureGC') + .addSystem(FilterSystem, 'filter') + .addSystem(RenderTextureSystem, 'renderTexture') + .addSystem(BatchSystem, 'batch'); + + this.initPlugins(Renderer.__plugins); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ + if (options.context) + { + this.context.initFromContext(options.context); + } + else + { + this.context.initFromOptions({ + alpha: this.transparent, + antialias: options.antialias, + premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', + stencil: true, + 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); + } + + /** + * Add a new system to the renderer. + * @param {class} ClassRef - Class reference + * @param {string} [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. + * @return {PIXI.Renderer} Return instance of renderer + */ + addSystem(ClassRef, name) + { + if (!name) + { + name = ClassRef.name; + } + + // TODO - read name from class.name.. + + /* + if(name.includes('System')) + { + name = name.replace('System', ''); + name = name.charAt(0).toLowerCase() + name.slice(1); + } + */ + + const system = new ClassRef(this); + + if (this[name]) + { + throw new Error(`Whoops! ${name} is already a manger`); + } + + this[name] = system; + + for (const i in this.runners) + { + this.runners[i].add(system); + } + + return this; + + /** + * 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. + */ + } + + /** + * Renders the object to its webGL view + * + * @param {PIXI.DisplayObject} displayObject - the object to be rendered + * @param {PIXI.RenderTexture} renderTexture - The render texture to render to. + * @param {boolean} [clear] - Should the canvas be cleared before the new render + * @param {PIXI.Transform} [transform] - A transform to apply to the render texture before rendering. + * @param {boolean} [skipUpdateTransform] - Should we skip the update transform pass? + */ + render(displayObject, renderTexture, clear, transform, skipUpdateTransform) + { + // can be handy to know! + this.renderingToScreen = !renderTexture; + + this.runners.prerender.run(); + this.emit('prerender'); + + // 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.parent; + + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = 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.run(); + + this.emit('postrender'); + } + + /** + * Resizes the webGL view to the specified width and height. + * + * @param {number} screenWidth - the new width of the screen + * @param {number} screenHeight - the new height of the screen + */ + resize(screenWidth, screenHeight) + { + AbstractRenderer.prototype.resize.call(this, screenWidth, screenHeight); + + this.runners.resize.run(screenWidth, screenHeight); + } + + /** + * Resets the WebGL state so you can render things however you fancy! + * + * @return {PIXI.Renderer} Returns itself. + */ + reset() + { + this.runners.reset.run(); + + return this; + } + + clear() + { + this.framebuffer.bind(); + this.framebuffer.clear(); + } + + /** + * Removes everything from the renderer (event listeners, spritebatch, etc...) + * + * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. + * See: https://github.com/pixijs/pixi.js/issues/2233 + */ + destroy(removeView) + { + this.runners.destroy.run(); + + // call base destroy + super.destroy(removeView); + + // TODO nullify all the managers.. + this.gl = null; + } + + /** + * 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 + * builds and excluding plugins. + * @name PIXI.Renderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. + */ + + /** + * Adds a plugin to the renderer. + * + * @method + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + static registerPlugin(pluginName, ctor) + { + Renderer.__plugins = Renderer.__plugins || {}; + Renderer.__plugins[pluginName] = ctor; + } +} diff --git a/packages/core/src/System.js b/packages/core/src/System.js new file mode 100644 index 0000000..05a10c8 --- /dev/null +++ b/packages/core/src/System.js @@ -0,0 +1,42 @@ +/** + * System is a base class used for extending systems used by the {@link PIXI.Renderer} + * @see PIXI.Renderer#addSystem + * @class + * @memberof PIXI + */ +export default class System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + /** + * The renderer this manager works for. + * + * @member {PIXI.Renderer} + */ + this.renderer = renderer; + + this.renderer.runners.contextChange.add(this); + } + + /** + * Generic method called when there is a WebGL context change. + * + */ + contextChange() + { + // do some codes init! + } + + /** + * Generic destroy methods to be overridden by the subclass + * + */ + destroy() + { + this.renderer.runners.contextChange.remove(this); + this.renderer = null; + } +} diff --git a/packages/core/src/batch/BatchSystem.js b/packages/core/src/batch/BatchSystem.js new file mode 100644 index 0000000..b04c249 --- /dev/null +++ b/packages/core/src/batch/BatchSystem.js @@ -0,0 +1,68 @@ +import System from '../System'; +import ObjectRenderer from './ObjectRenderer'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class BatchSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + super(renderer); + + /** + * An empty renderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.emptyRenderer = new ObjectRenderer(renderer); + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.currentRenderer = this.emptyRenderer; + } + + /** + * Changes the current renderer to the one given in parameter + * + * @param {PIXI.ObjectRenderer} objectRenderer - The object renderer to use. + */ + setObjectRenderer(objectRenderer) + { + if (this.currentRenderer === objectRenderer) + { + return; + } + + this.currentRenderer.stop(); + this.currentRenderer = objectRenderer; + + this.renderer.state.setState(objectRenderer.state); + + this.currentRenderer.start(); + } + + /** + * This should be called if you wish to do some custom rendering + * It will basically render anything that may be batched up such as sprites + * + */ + flush() + { + this.setObjectRenderer(this.emptyRenderer); + } + + reset() + { + this.setObjectRenderer(this.emptyRenderer); + } +} diff --git a/packages/core/src/batch/ObjectRenderer.js b/packages/core/src/batch/ObjectRenderer.js new file mode 100644 index 0000000..0c851a9 --- /dev/null +++ b/packages/core/src/batch/ObjectRenderer.js @@ -0,0 +1,48 @@ +import System from '../System'; + +/** + * Base for a common object renderer that can be used as a system renderer plugin. + * + * @class + * @extends PIXI.System + * @memberof PIXI + */ +export default class ObjectRenderer extends System +{ + /** + * Starts the renderer and sets the shader + * + */ + start() + { + // set the shader.. + } + + /** + * Stops the renderer + * + */ + stop() + { + this.flush(); + } + + /** + * Stub method for rendering content and emptying the current batch. + * + */ + flush() + { + // flush! + } + + /** + * Renders an object + * + * @param {PIXI.DisplayObject} object - The object to render. + */ + render(object) // eslint-disable-line no-unused-vars + { + // render the object + } +} diff --git a/packages/core/src/context/ContextSystem.js b/packages/core/src/context/ContextSystem.js new file mode 100644 index 0000000..301aab4 --- /dev/null +++ b/packages/core/src/context/ContextSystem.js @@ -0,0 +1,186 @@ +import System from '../System'; +import { settings } from '@pixi/settings'; + +let CONTEXT_UID = 0; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class ContextSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.webGLVersion = 1; + + this.handleContextLost = this.handleContextLost.bind(this); + this.handleContextRestored = this.handleContextRestored.bind(this); + + this.extensions = {}; + + renderer.view.addEventListener('webglcontextlost', this.handleContextLost, false); + renderer.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + } + + get isLost() + { + return (!this.gl || this.gl.isContextLost()); + } + + contextChange(gl) + { + this.gl = gl; + + // restore a context if it was previously lost + if (gl.isContextLost() && gl.getExtension('WEBGL_lose_context')) + { + gl.getExtension('WEBGL_lose_context').restoreContext(); + } + } + + initFromContext(gl) + { + this.gl = gl; + this.validateContext(gl); + this.renderer.gl = gl; + this.renderer.CONTEXT_UID = CONTEXT_UID++; + this.renderer.runners.contextChange.run(gl); + } + + initFromOptions(options) + { + const gl = this.createContext(this.renderer.view, options); + + this.initFromContext(gl); + } + + /** + * Helper class to create a webGL Context + * + * @class + * @memberof PIXI.glCore + * @param canvas {HTMLCanvasElement} the canvas element that we will get the context from + * @param options {Object} An options object that gets passed in to the canvas element containing the context attributes, + * see https://developer.mozilla.org/en/docs/Web/API/HTMLCanvasElement/getContext for the options available + * @return {WebGLRenderingContext} the WebGL context + */ + createContext(canvas, options) + { + let gl; + + if (settings.PREFER_WEBGL_2) + { + gl = canvas.getContext('webgl2', options); + } + + if (gl) + { + this.webGLVersion = 2; + } + else + { + this.webGLVersion = 1; + + gl = canvas.getContext('webgl', options) + || canvas.getContext('experimental-webgl', options); + + if (!gl) + { + // fail, not able to get a context + throw new Error('This browser does not support webGL. Try using the canvas renderer'); + } + } + + this.gl = gl; + + this.getExtensions(); + + return gl; + } + + getExtensions() + { + // time to set up default etensions that pixi uses.. + const gl = this.gl; + const extensions = this.extensions; + + if (this.webGLVersion === 1) + { + extensions.drawBuffers = gl.getExtension('WEBGL_draw_buffers'); + extensions.depthTexture = gl.getExtension('WEBKIT_WEBGL_depth_texture'); + extensions.floatTexture = gl.getExtension('OES_texture_float'); + extensions.loseContext = gl.getExtension('WEBGL_lose_context'); + + extensions.vertexArrayObject = gl.getExtension('OES_vertex_array_object') + || gl.getExtension('MOZ_OES_vertex_array_object') + || gl.getExtension('WEBKIT_OES_vertex_array_object'); + } + + // we don't use any specific WebGL 2 ones yet! + } + + /** + * Handles a lost webgl context + * + * @private + * @param {WebGLContextEvent} event - The context lost event. + */ + handleContextLost(event) + { + event.preventDefault(); + } + + /** + * Handles a restored webgl context + * + * @private + */ + handleContextRestored() + { + this.renderer.runners.contextChange.run(this.gl); + } + + destroy() + { + const view = this.renderer.view; + + // remove listeners + view.removeEventListener('webglcontextlost', this.handleContextLost); + view.removeEventListener('webglcontextrestored', this.handleContextRestored); + + this.gl.useProgram(null); + + if (this.extensions.loseContext) + { + this.extensions.loseContext.loseContext(); + } + } + + postrender() + { + this.gl.flush(); + } + + validateContext(gl) + { + const attributes = gl.getContextAttributes(); + + // this is going to be fairly simple for now.. but at least we have room to grow! + if (!attributes.stencil) + { + /* eslint-disable max-len */ + + /* eslint-disable no-console */ + console.warn('Provided WebGL context does not have a stencil buffer, masks may not render correctly'); + /* eslint-enable no-console */ + + /* eslint-enable max-len */ + } + } +} diff --git a/packages/core/src/filters/Filter.js b/packages/core/src/filters/Filter.js new file mode 100644 index 0000000..9d8cad8 --- /dev/null +++ b/packages/core/src/filters/Filter.js @@ -0,0 +1,140 @@ +import Shader from '../shader/Shader'; +import Program from '../shader/Program'; +import State from '../state/State'; +import { settings } from '@pixi/settings'; +// import extractUniformsFromSrc from './extractUniformsFromSrc'; +import defaultVertex from './defaultFilter.vert'; +import defaultFragment from './defaultFilter.frag'; + +// let math = require('../../math'); +/** + * @class + * @memberof PIXI + * @extends PIXI.Shader + */ +export default class Filter extends Shader +{ + /** + * @param {string} [vertexSrc] - The source of the vertex shader. + * @param {string} [fragmentSrc] - The source of the fragment shader. + * @param {object} [uniforms] - Custom uniforms to use to augment the built-in ones. + */ + constructor(vertexSrc, fragmentSrc, uniforms) + { + const program = Program.from(vertexSrc, fragmentSrc); + + super(program, uniforms); + + /** + * The padding of the filter. Some filters require extra space to breath such as a blur. + * Increasing this will add extra width and height to the bounds of the object that the + * filter is applied to. + * + * @member {number} + */ + this.padding = 0; + + /** + * The resolution of the filter. Setting this to be lower will lower the quality but + * increase the performance of the filter. + * + * @member {number} + */ + this.resolution = settings.RESOLUTION; + + /** + * If enabled is true the filter is applied, if false it will not. + * + * @member {boolean} + */ + this.enabled = true; + + /** + * If enabled, PixiJS will fit the filter area into boundaries for better performance. + * Switch it off if it does not work for specific shader. + * + * @member {boolean} + */ + this.autoFit = true; + + /** + * Legacy filters use position and uvs from attributes + * @member {boolean} + * @readonly + */ + this.legacy = !!this.program.attributeData.aTextureCoord; + + /** + * the webGL state the filter requires to render + * @member {PIXI.State} + */ + this.state = new State(); + } + + /** + * Applies the filter + * + * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + * @param {boolean} clear - Should the output be cleared before rendering to it + * @param {object} [currentState] - It's current state of filter. + * There are some useful properties in the currentState : + * target, filters, sourceFrame, destinationFrame, renderTarget, resolution + */ + apply(filterManager, input, output, clear, currentState, derp) // eslint-disable-line no-unused-vars + { + // do as you please! + + filterManager.applyFilter(this, input, output, clear, currentState, derp); + + // or just do a regular render.. + } + + /** + * Sets the blendmode of the filter + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL + */ + get blendMode() + { + return this.state.blendMode; + } + + set blendMode(value) // eslint-disable-line require-jsdoc + { + this.state.blendMode = value; + } + + /** + * The default vertex shader source + * + * @static + * @constant + */ + static get defaultVertexSrc() + { + return defaultVertex; + } + + /** + * The default fragment shader source + * + * @static + * @constant + */ + static get defaultFragmentSrc() + { + return defaultFragment; + } +} + +/** + * Used for caching shader IDs + * + * @static + * @private + */ +Filter.SOURCE_KEY_MAP = {}; + diff --git a/packages/core/src/filters/FilterSystem.js b/packages/core/src/filters/FilterSystem.js new file mode 100644 index 0000000..3174318 --- /dev/null +++ b/packages/core/src/filters/FilterSystem.js @@ -0,0 +1,431 @@ +import System from '../System'; + +import RenderTexture from '../renderTexture/RenderTexture'; +import Quad from '../utils/Quad'; +import QuadUv from '../utils/QuadUv'; +import { Rectangle } from '@pixi/math'; +import * as filterTransforms from './filterTransforms'; +import bitTwiddle from 'bit-twiddle'; +import UniformGroup from '../shader/UniformGroup'; +import { DRAW_MODES } from '@pixi/constants'; + +// +/** + * @ignore + * @class + */ +class FilterState +{ + /** + * + */ + constructor() + { + this.renderTexture = null; + this.sourceFrame = new Rectangle(); + this.destinationFrame = new Rectangle(); + this.filters = []; + this.target = null; + this.legacy = false; + this.resolution = 1; + } +} + +/** + * @class + * @memberof PIXI.systems + * @extends PIXI.System + */ +export default class FilterSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + /** + * stores a bunch of PO2 textures used for filtering + * @type {Object} + */ + this.texturePool = {}; + + /** + * a pool for storing filter states, save us creating new ones each tick + * @type {Array} + */ + this.statePool = []; + + /** + * A very simple geometry used when drawing a filter effect to the screen + * @type {Quad} + */ + this.quad = new Quad(); + + this.quadUv = new QuadUv(); + + /** + * Temporary rect for maths + * @type {PIXI.Rectangle} + */ + this.tempRect = new Rectangle(); + + this.activeState = {}; + + /** + * this uniform group is attached to filter uniforms when used + * @type {UniformGroup} + */ + this.globalUniforms = new UniformGroup({ + sourceFrame: this.tempRect, + destinationFrame: this.tempRect, + + // legacy variables + filterArea: new Float32Array(4), + filterClamp: new Float32Array(4), + }, true); + } + + /** + * Adds a new filter to the System. + * + * @param {PIXI.DisplayObject} target - The target of the filter to render. + * @param {PIXI.Filter[]} filters - The filters to apply. + */ + push(target, filters) + { + const renderer = this.renderer; + const filterStack = this.renderer.renderTexture.defaultFilterStack; + const state = this.statePool.pop() || new FilterState(); + + let resolution = filters[0].resolution; + let padding = filters[0].padding; + let autoFit = filters[0].autoFit; + let legacy = filters[0].legacy; + + for (let i = 1; i < filters.length; i++) + { + const filter = filters[i]; + + // lets use the lowest resolution.. + resolution = Math.min(resolution, filter.resolution); + // and the largest amount of padding! + padding = Math.max(padding, filter.padding); + // only auto fit if all filters are autofit + autoFit = autoFit || filter.autoFit; + + legacy = legacy || filter.legacy; + } + + filterStack.push(state); + + state.resolution = resolution; + + state.legacy = legacy; + + // round to whole number based on resolution + // TODO move that to the shader too? + state.sourceFrame = target.filterArea || target.getBounds(true); + + state.sourceFrame.pad(padding); + + if (autoFit) + { + state.sourceFrame.fit(this.renderer.renderTexture.destinationFrame); + } + + state.sourceFrame.round(resolution); + + state.renderTexture = this.getPotFilterTexture(state.sourceFrame.width, state.sourceFrame.height, resolution); + state.filters = filters; + + state.destinationFrame.width = state.renderTexture.width; + state.destinationFrame.height = state.renderTexture.height; + + state.renderTexture.filterFrame = state.sourceFrame; + renderer.renderTexture.bind(state.renderTexture, state.sourceFrame);// /, state.destinationFrame); + renderer.renderTexture.clear(); + } + + /** + * Pops off the filter and applies it. + * + */ + pop() + { + const renderer = this.renderer; + const filterStack = renderer.renderTexture.defaultFilterStack; + const state = filterStack.pop(); + const filters = state.filters; + + this.activeState = state; + + const globalUniforms = this.globalUniforms.uniforms; + + globalUniforms.sourceFrame = state.sourceFrame; + globalUniforms.destinationFrame = state.destinationFrame; + globalUniforms.resolution = state.resolution; + + // only update the rect if its legacy.. + if (state.legacy) + { + const filterArea = globalUniforms.filterArea; + const filterClamp = globalUniforms.filterClamp; + + filterArea[0] = state.destinationFrame.width; + filterArea[1] = state.destinationFrame.height; + filterArea[2] = state.sourceFrame.x; + filterArea[3] = state.sourceFrame.y; + + filterClamp[0] = 0.5 / state.resolution / state.destinationFrame.width; + filterClamp[1] = 0.5 / state.resolution / state.destinationFrame.height; + filterClamp[2] = (state.sourceFrame.width - 0.5) / state.resolution / state.destinationFrame.width; + filterClamp[3] = (state.sourceFrame.height - 0.5) / state.resolution / state.destinationFrame.height; + } + + this.globalUniforms.update(); + + const lastState = filterStack[filterStack.length - 1]; + + if (filters.length === 1) + { + filters[0].apply(this, state.renderTexture, lastState.renderTexture, false, state); + + this.returnFilterTexture(state.renderTexture); + } + else + { + let flip = state.renderTexture; + let flop = this.getPotFilterTexture( + flip.width, + flip.height, + state.resolution + ); + + flop.filterFrame = flip.filterFrame; + + let i = 0; + + for (i = 0; i < filters.length - 1; ++i) + { + filters[i].apply(this, flip, flop, true, state); + + const t = flip; + + flip = flop; + flop = t; + } + + filters[i].apply(this, flip, lastState.renderTexture, false, state); + + this.returnFilterTexture(flip); + this.returnFilterTexture(flop); + } + + this.statePool.push(state); + } + + /** + * Draws a filter. + * + * @param {PIXI.Filter} filter - The filter to draw. + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + * @param {boolean} clear - Should the output be cleared before rendering to it + */ + applyFilter(filter, input, output, clear) + { + const renderer = this.renderer; + + renderer.renderTexture.bind(output, output ? output.filterFrame : null); + + if (clear) + { + // gl.disable(gl.SCISSOR_TEST); + renderer.renderTexture.clear(); + // gl.enable(gl.SCISSOR_TEST); + } + + // set the uniforms.. + filter.uniforms.uSampler = input; + filter.uniforms.filterGlobals = this.globalUniforms; + + // TODO make it so that the order of this does not matter.. + // because it does at the moment cos of global uniforms. + // they need to get resynced + + renderer.state.setState(filter.state); + renderer.shader.bind(filter); + + if (filter.legacy) + { + this.quadUv.map(input._frame, input.filterFrame); + + renderer.geometry.bind(this.quadUv); + renderer.geometry.draw(DRAW_MODES.TRIANGLES); + } + else + { + renderer.geometry.bind(this.quad); + renderer.geometry.draw(DRAW_MODES.TRIANGLE_STRIP); + } + } + + /** + * Calculates the mapped matrix. + * + * TODO playing around here.. this is temporary - (will end up in the shader) + * this returns a matrix that will normalise map filter cords in the filter to screen space + * + * @param {PIXI.Matrix} outputMatrix - the matrix to output to. + * @return {PIXI.Matrix} The mapped matrix. + */ + calculateScreenSpaceMatrix(outputMatrix) + { + const currentState = this.activeState; + + return filterTransforms.calculateScreenSpaceMatrix( + outputMatrix, + currentState.sourceFrame, + currentState.destinationFrame + ); + } + + /** + * This will map the filter coord so that a texture can be used based on the transform of a sprite + * + * @param {PIXI.Matrix} outputMatrix - The matrix to output to. + * @param {PIXI.Sprite} sprite - The sprite to map to. + * @return {PIXI.Matrix} The mapped matrix. + */ + calculateSpriteMatrix(outputMatrix, sprite) + { + const currentState = this.activeState; + + return filterTransforms.calculateSpriteMatrix( + outputMatrix, + currentState.sourceFrame, + currentState.destinationFrame, + sprite + ); + } + + /** + * Destroys this Filter System. + * + * @param {boolean} [contextLost=false] context was lost, do not free shaders + * + */ + destroy(contextLost = false) + { + if (!contextLost) + { + this.emptyPool(); + } + else + { + this.texturePool = {}; + } + } + + /** + * Gets a Power-of-Two render texture. + * + * TODO move to a seperate class could be on renderer? + * also - could cause issue with multiple contexts? + * + * @private + * @param {WebGLRenderingContext} gl - The webgl rendering context + * @param {number} minWidth - The minimum width of the render target. + * @param {number} minHeight - The minimum height of the render target. + * @param {number} resolution - The resolution of the render target. + * @return {PIXI.RenderTarget} The new render target. + */ + getPotFilterTexture(minWidth, minHeight, resolution) + { + minWidth = bitTwiddle.nextPow2(minWidth); + minHeight = bitTwiddle.nextPow2(minHeight); + resolution = resolution || 1; + + const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + + if (!this.texturePool[key]) + { + this.texturePool[key] = []; + } + + let renderTexture = this.texturePool[key].pop(); + + if (!renderTexture) + { + // temporary bypass cache.. + // internally - this will cause a texture to be bound.. + renderTexture = RenderTexture.create({ + width: minWidth, + height: minHeight, + resolution, + }); + } + + return renderTexture; + } + + /** + * Gets extra render texture to use inside current filter + * + * @param {number} resolution resolution of the renderTexture + * @returns {PIXI.RenderTexture} + */ + getFilterTexture(resolution) + { + const rt = this.activeState.renderTexture; + + const filterTexture = this.getPotFilterTexture(rt.width, rt.height, resolution || rt.baseTexture.resolution); + + filterTexture.filterFrame = rt.filterFrame; + + return filterTexture; + } + + /** + * Frees a render texture back into the pool. + * + * @param {PIXI.RenderTarget} renderTexture - The renderTarget to free + */ + returnFilterTexture(renderTexture) + { + renderTexture.filterFrame = null; + + const base = renderTexture.baseTexture; + + const minWidth = base.width; + const minHeight = base.height; + + const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + + this.texturePool[key].push(renderTexture); + } + + /** + * Empties the texture pool. + * + */ + emptyPool() + { + for (const i in this.texturePool) + { + const textures = this.texturePool[i]; + + if (textures) + { + for (let j = 0; j < textures.length; j++) + { + textures[j].destroy(true); + } + } + } + + this.texturePool = {}; + } +} diff --git a/packages/core/src/filters/defaultFilter.frag b/packages/core/src/filters/defaultFilter.frag new file mode 100644 index 0000000..a14c1be --- /dev/null +++ b/packages/core/src/filters/defaultFilter.frag @@ -0,0 +1,22 @@ +varying vec2 vTextureCoord; +varying vec2 vFilterCoord; + +uniform sampler2D uSampler; +uniform sampler2D filterSampler; + +void main(void){ + vec4 masky = texture2D(filterSampler, vFilterCoord); + vec4 sample = texture2D(uSampler, vTextureCoord); + vec4 color; + if(mod(vFilterCoord.x, 1.0) > 0.5) + { + color = vec4(1.0, 0.0, 0.0, 1.0); + } + else + { + color = vec4(0.0, 1.0, 0.0, 1.0); + } + // gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0); + gl_FragColor = mix(sample, masky, 0.5); + gl_FragColor *= sample.a; +} diff --git a/packages/core/src/filters/defaultFilter.vert b/packages/core/src/filters/defaultFilter.vert new file mode 100644 index 0000000..e1febca --- /dev/null +++ b/packages/core/src/filters/defaultFilter.vert @@ -0,0 +1,14 @@ +attribute vec2 aVertexPosition; +attribute vec2 aTextureCoord; + +uniform mat3 projectionMatrix; +uniform mat3 filterMatrix; + +varying vec2 vTextureCoord; +varying vec2 vFilterCoord; + +void main(void){ + gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); + vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy; + vTextureCoord = aTextureCoord ; +} diff --git a/packages/core/src/AbstractRenderer.js b/packages/core/src/AbstractRenderer.js new file mode 100644 index 0000000..64b2eb1 --- /dev/null +++ b/packages/core/src/AbstractRenderer.js @@ -0,0 +1,353 @@ +import { hex2string, hex2rgb } from '@pixi/utils'; +import { Matrix, Rectangle } from '@pixi/math'; +import { RENDERER_TYPE } from '@pixi/constants'; +import { settings } from '@pixi/settings'; +import { Container } from '@pixi/display'; +import RenderTexture from './renderTexture/RenderTexture'; +import EventEmitter from 'eventemitter3'; + +const tempMatrix = new Matrix(); + +/** + * 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 EventEmitter + * @memberof PIXI + */ +export default class AbstractRenderer extends EventEmitter +{ + // eslint-disable-next-line valid-jsdoc + /** + * @param {string} system - The name of the system this renderer is for. + * @param {object} [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.transparent=false] - If the render view is transparent, default false + * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false + * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. The + * resolution of the renderer retina would be 2. + * @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 {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when rendering, + * stopping pixel interpolation. + */ + constructor(system, options, arg2, arg3) + { + super(); + + // Support for constructor(system, screenWidth, screenHeight, options) + if (typeof options === 'number') + { + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + }, arg3); + } + + // 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 = RENDERER_TYPE.UNKNOWN; + + /** + * Measurements of the screen. (0, 0, screenWidth, screenHeight) + * + * Its safe to use as filterArea or hitArea for 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 1 + */ + this.resolution = options.resolution || settings.RESOLUTION; + + /** + * Whether the render view is transparent + * + * @member {boolean} + */ + this.transparent = options.transparent; + + /** + * Whether css dimensions of canvas view should be resized to screen dimensions automatically + * + * @member {boolean} + */ + this.autoResize = options.autoResize || false; + + /** + * Tracks the blend modes useful for this renderer. + * + * @member {object} + */ + this.blendModes = null; + + /** + * 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; + + /** + * If true PixiJS will Math.floor() x/y values when rendering, stopping pixel interpolation. + * Handy for crisp pixel art and speed on legacy devices. + * + * @member {boolean} + */ + this.roundPixels = options.roundPixels; + + /** + * The background color as a number. + * + * @member {number} + * @private + */ + this._backgroundColor = 0x000000; + + /** + * The background color as an [R, G, B] array. + * + * @member {number[]} + * @private + */ + this._backgroundColorRgba = [0, 0, 0, 0]; + + /** + * The background color as a string. + * + * @member {string} + * @private + */ + this._backgroundColorString = '#000000'; + + this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter + + /** + * This temporary display object used as the parent of the currently being rendered item + * + * @member {PIXI.DisplayObject} + * @private + */ + this._tempDisplayObjectParent = new Container(); + + /** + * The last root object that the renderer tried to render. + * + * @member {PIXI.DisplayObject} + * @private + */ + this._lastObjectRendered = this._tempDisplayObjectParent; + + /** + * Collection of plugins. + * @readonly + * @member {object} + */ + this.plugins = {}; + } + + /** + * Initialize the plugins. + * + * @protected + * @param {object} staticMap - The dictionary of staticly saved plugins. + */ + initPlugins(staticMap) + { + 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() + { + return this.view.width; + } + + /** + * Same as view.height, actual number of pixels in the canvas by vertical + * + * @member {number} + * @readonly + * @default 600 + */ + get height() + { + return this.view.height; + } + + /** + * Resizes the screen and canvas to the specified width and height + * Canvas dimensions are multiplied by resolution + * + * @param {number} screenWidth - the new width of the screen + * @param {number} screenHeight - the new height of the screen + */ + resize(screenWidth, screenHeight) + { + this.screen.width = screenWidth; + this.screen.height = screenHeight; + + this.view.width = screenWidth * this.resolution; + this.view.height = screenHeight * this.resolution; + + if (this.autoResize) + { + this.view.style.width = `${screenWidth}px`; + this.view.style.height = `${screenHeight}px`; + } + } + + /** + * 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 {PIXI.DisplayObject} displayObject - The displayObject the object will be generated from + * @param {number} scaleMode - Should be one of the scaleMode consts + * @param {number} resolution - The resolution / device pixel ratio of the texture being generated + * @param {PIXI.Rectangle} [region] - The region of the displayObject, that shall be rendered, + * if no region is specified, defaults to the local bounds of the displayObject. + * @return {PIXI.Texture} a texture of the graphics object + */ + generateTexture(displayObject, scaleMode, resolution, region) + { + region = region || displayObject.getLocalBounds(); + + // 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(region.width | 0, region.height | 0, scaleMode, resolution); + + tempMatrix.tx = -region.x; + tempMatrix.ty = -region.y; + + this.render(displayObject, renderTexture, false, tempMatrix, true); + + return renderTexture; + } + + /** + * Removes everything from the renderer and optionally removes the Canvas DOM element. + * + * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. + */ + destroy(removeView) + { + 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); + } + + this.plugins = null; + + this.type = RENDERER_TYPE.UNKNOWN; + + this.view = null; + + this.screen = null; + + this.resolution = 0; + + this.transparent = false; + + this.autoResize = false; + + this.blendModes = null; + + this.options = null; + + this.preserveDrawingBuffer = false; + this.clearBeforeRender = false; + + this.roundPixels = false; + + this._backgroundColor = 0; + this._backgroundColorRgba = null; + this._backgroundColorString = null; + + this._tempDisplayObjectParent = null; + this._lastObjectRendered = null; + } + + /** + * The background color to fill if not transparent + * + * @member {number} + */ + get backgroundColor() + { + return this._backgroundColor; + } + + set backgroundColor(value) // eslint-disable-line require-jsdoc + { + this._backgroundColor = value; + this._backgroundColorString = hex2string(value); + hex2rgb(value, this._backgroundColorRgba); + } +} diff --git a/packages/core/src/Renderer.js b/packages/core/src/Renderer.js new file mode 100644 index 0000000..2227e78 --- /dev/null +++ b/packages/core/src/Renderer.js @@ -0,0 +1,338 @@ +import AbstractRenderer from './AbstractRenderer'; +import { sayHello } from '@pixi/utils'; +import MaskSystem from './mask/MaskSystem'; +import StencilSystem from './mask/StencilSystem'; +import FilterSystem from './filters/FilterSystem'; +import FramebufferSystem from './framebuffer/FramebufferSystem'; +import RenderTextureSystem from './renderTexture/RenderTextureSystem'; +import TextureSystem from './textures/TextureSystem'; +import ProjectionSystem from './projection/ProjectionSystem'; +import StateSystem from './state/StateSystem'; +import GeometrySystem from './geometry/GeometrySystem'; +import ShaderSystem from './shader/ShaderSystem'; +import ContextSystem from './context/ContextSystem'; +import BatchSystem from './batch/BatchSystem'; +import TextureGCSystem from './textures/TextureGCSystem'; +import { RENDERER_TYPE } from '@pixi/constants'; +import UniformGroup from './shader/UniformGroup'; +import { Matrix } from '@pixi/math'; +import Runner from 'mini-runner'; + +/** + * 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 Render works by automatically managing webGLBatchs. + * 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 :) + * + * @class + * @memberof PIXI + * @extends PIXI.AbstractRenderer + */ +export default class Renderer extends AbstractRenderer +{ + // eslint-disable-next-line valid-jsdoc + /** + * + * @param {object} [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.transparent=false] - If the render view is transparent, default false + * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false + * @param {boolean} [options.antialias=false] - sets antialias. If not available natively then FXAA + * antialiasing is used + * @param {boolean} [options.forceFXAA=false] - forces FXAA antialiasing to be used over native. + * FXAA is faster, but may not always look as great + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. + * The resolution of the renderer retina would be 2. + * @param {boolean} [options.clearBeforeRender=true] - This sets if the renderer will clear + * the canvas or not before the new render pass. If you wish to set this to false, you *must* set + * preserveDrawingBuffer to `true`. + * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, + * enable this if you need to call toDataUrl on the webgl context. + * @param {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when + * rendering, stopping pixel interpolation. + * @param {number} [options.backgroundColor=0x000000] - The background color of the rendered area + * (shown if not transparent). + * @param {boolean} [options.legacy=false] - If true PixiJS will aim to ensure compatibility + * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. + * @param {string} [options.powerPreference] - Parameter passed to webgl context, set to "high-performance" + * for devices with dual graphics card + */ + constructor(options, arg2, arg3) + { + super('WebGL', options, arg2, arg3); + + /** + * The type of this renderer as a standardised const + * + * @member {number} + * @see PIXI.RENDERER_TYPE + */ + this.type = RENDERER_TYPE.WEBGL; + + // this will be set by the contextSystem (this.context) + this.gl = null; + this.CONTEXT_UID = 0; + this.legacy = !!options.legacy; + + // TODO legacy! + + // runners! + this.runners = { + destroy: new Runner('destroy'), + contextChange: new Runner('contextChange', 1), + reset: new Runner('reset'), + update: new Runner('update'), + postrender: new Runner('postrender'), + prerender: new Runner('prerender'), + resize: new Runner('resize', 2), + }; + + 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(GeometrySystem, 'geometry') + .addSystem(FramebufferSystem, 'framebuffer') + .addSystem(StencilSystem, 'stencil') + .addSystem(ProjectionSystem, 'projection') + .addSystem(TextureGCSystem, 'textureGC') + .addSystem(FilterSystem, 'filter') + .addSystem(RenderTextureSystem, 'renderTexture') + .addSystem(BatchSystem, 'batch'); + + this.initPlugins(Renderer.__plugins); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ + if (options.context) + { + this.context.initFromContext(options.context); + } + else + { + this.context.initFromOptions({ + alpha: this.transparent, + antialias: options.antialias, + premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', + stencil: true, + 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); + } + + /** + * Add a new system to the renderer. + * @param {class} ClassRef - Class reference + * @param {string} [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. + * @return {PIXI.Renderer} Return instance of renderer + */ + addSystem(ClassRef, name) + { + if (!name) + { + name = ClassRef.name; + } + + // TODO - read name from class.name.. + + /* + if(name.includes('System')) + { + name = name.replace('System', ''); + name = name.charAt(0).toLowerCase() + name.slice(1); + } + */ + + const system = new ClassRef(this); + + if (this[name]) + { + throw new Error(`Whoops! ${name} is already a manger`); + } + + this[name] = system; + + for (const i in this.runners) + { + this.runners[i].add(system); + } + + return this; + + /** + * 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. + */ + } + + /** + * Renders the object to its webGL view + * + * @param {PIXI.DisplayObject} displayObject - the object to be rendered + * @param {PIXI.RenderTexture} renderTexture - The render texture to render to. + * @param {boolean} [clear] - Should the canvas be cleared before the new render + * @param {PIXI.Transform} [transform] - A transform to apply to the render texture before rendering. + * @param {boolean} [skipUpdateTransform] - Should we skip the update transform pass? + */ + render(displayObject, renderTexture, clear, transform, skipUpdateTransform) + { + // can be handy to know! + this.renderingToScreen = !renderTexture; + + this.runners.prerender.run(); + this.emit('prerender'); + + // 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.parent; + + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = 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.run(); + + this.emit('postrender'); + } + + /** + * Resizes the webGL view to the specified width and height. + * + * @param {number} screenWidth - the new width of the screen + * @param {number} screenHeight - the new height of the screen + */ + resize(screenWidth, screenHeight) + { + AbstractRenderer.prototype.resize.call(this, screenWidth, screenHeight); + + this.runners.resize.run(screenWidth, screenHeight); + } + + /** + * Resets the WebGL state so you can render things however you fancy! + * + * @return {PIXI.Renderer} Returns itself. + */ + reset() + { + this.runners.reset.run(); + + return this; + } + + clear() + { + this.framebuffer.bind(); + this.framebuffer.clear(); + } + + /** + * Removes everything from the renderer (event listeners, spritebatch, etc...) + * + * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. + * See: https://github.com/pixijs/pixi.js/issues/2233 + */ + destroy(removeView) + { + this.runners.destroy.run(); + + // call base destroy + super.destroy(removeView); + + // TODO nullify all the managers.. + this.gl = null; + } + + /** + * 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 + * builds and excluding plugins. + * @name PIXI.Renderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. + */ + + /** + * Adds a plugin to the renderer. + * + * @method + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + static registerPlugin(pluginName, ctor) + { + Renderer.__plugins = Renderer.__plugins || {}; + Renderer.__plugins[pluginName] = ctor; + } +} diff --git a/packages/core/src/System.js b/packages/core/src/System.js new file mode 100644 index 0000000..05a10c8 --- /dev/null +++ b/packages/core/src/System.js @@ -0,0 +1,42 @@ +/** + * System is a base class used for extending systems used by the {@link PIXI.Renderer} + * @see PIXI.Renderer#addSystem + * @class + * @memberof PIXI + */ +export default class System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + /** + * The renderer this manager works for. + * + * @member {PIXI.Renderer} + */ + this.renderer = renderer; + + this.renderer.runners.contextChange.add(this); + } + + /** + * Generic method called when there is a WebGL context change. + * + */ + contextChange() + { + // do some codes init! + } + + /** + * Generic destroy methods to be overridden by the subclass + * + */ + destroy() + { + this.renderer.runners.contextChange.remove(this); + this.renderer = null; + } +} diff --git a/packages/core/src/batch/BatchSystem.js b/packages/core/src/batch/BatchSystem.js new file mode 100644 index 0000000..b04c249 --- /dev/null +++ b/packages/core/src/batch/BatchSystem.js @@ -0,0 +1,68 @@ +import System from '../System'; +import ObjectRenderer from './ObjectRenderer'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class BatchSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + super(renderer); + + /** + * An empty renderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.emptyRenderer = new ObjectRenderer(renderer); + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.currentRenderer = this.emptyRenderer; + } + + /** + * Changes the current renderer to the one given in parameter + * + * @param {PIXI.ObjectRenderer} objectRenderer - The object renderer to use. + */ + setObjectRenderer(objectRenderer) + { + if (this.currentRenderer === objectRenderer) + { + return; + } + + this.currentRenderer.stop(); + this.currentRenderer = objectRenderer; + + this.renderer.state.setState(objectRenderer.state); + + this.currentRenderer.start(); + } + + /** + * This should be called if you wish to do some custom rendering + * It will basically render anything that may be batched up such as sprites + * + */ + flush() + { + this.setObjectRenderer(this.emptyRenderer); + } + + reset() + { + this.setObjectRenderer(this.emptyRenderer); + } +} diff --git a/packages/core/src/batch/ObjectRenderer.js b/packages/core/src/batch/ObjectRenderer.js new file mode 100644 index 0000000..0c851a9 --- /dev/null +++ b/packages/core/src/batch/ObjectRenderer.js @@ -0,0 +1,48 @@ +import System from '../System'; + +/** + * Base for a common object renderer that can be used as a system renderer plugin. + * + * @class + * @extends PIXI.System + * @memberof PIXI + */ +export default class ObjectRenderer extends System +{ + /** + * Starts the renderer and sets the shader + * + */ + start() + { + // set the shader.. + } + + /** + * Stops the renderer + * + */ + stop() + { + this.flush(); + } + + /** + * Stub method for rendering content and emptying the current batch. + * + */ + flush() + { + // flush! + } + + /** + * Renders an object + * + * @param {PIXI.DisplayObject} object - The object to render. + */ + render(object) // eslint-disable-line no-unused-vars + { + // render the object + } +} diff --git a/packages/core/src/context/ContextSystem.js b/packages/core/src/context/ContextSystem.js new file mode 100644 index 0000000..301aab4 --- /dev/null +++ b/packages/core/src/context/ContextSystem.js @@ -0,0 +1,186 @@ +import System from '../System'; +import { settings } from '@pixi/settings'; + +let CONTEXT_UID = 0; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class ContextSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.webGLVersion = 1; + + this.handleContextLost = this.handleContextLost.bind(this); + this.handleContextRestored = this.handleContextRestored.bind(this); + + this.extensions = {}; + + renderer.view.addEventListener('webglcontextlost', this.handleContextLost, false); + renderer.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + } + + get isLost() + { + return (!this.gl || this.gl.isContextLost()); + } + + contextChange(gl) + { + this.gl = gl; + + // restore a context if it was previously lost + if (gl.isContextLost() && gl.getExtension('WEBGL_lose_context')) + { + gl.getExtension('WEBGL_lose_context').restoreContext(); + } + } + + initFromContext(gl) + { + this.gl = gl; + this.validateContext(gl); + this.renderer.gl = gl; + this.renderer.CONTEXT_UID = CONTEXT_UID++; + this.renderer.runners.contextChange.run(gl); + } + + initFromOptions(options) + { + const gl = this.createContext(this.renderer.view, options); + + this.initFromContext(gl); + } + + /** + * Helper class to create a webGL Context + * + * @class + * @memberof PIXI.glCore + * @param canvas {HTMLCanvasElement} the canvas element that we will get the context from + * @param options {Object} An options object that gets passed in to the canvas element containing the context attributes, + * see https://developer.mozilla.org/en/docs/Web/API/HTMLCanvasElement/getContext for the options available + * @return {WebGLRenderingContext} the WebGL context + */ + createContext(canvas, options) + { + let gl; + + if (settings.PREFER_WEBGL_2) + { + gl = canvas.getContext('webgl2', options); + } + + if (gl) + { + this.webGLVersion = 2; + } + else + { + this.webGLVersion = 1; + + gl = canvas.getContext('webgl', options) + || canvas.getContext('experimental-webgl', options); + + if (!gl) + { + // fail, not able to get a context + throw new Error('This browser does not support webGL. Try using the canvas renderer'); + } + } + + this.gl = gl; + + this.getExtensions(); + + return gl; + } + + getExtensions() + { + // time to set up default etensions that pixi uses.. + const gl = this.gl; + const extensions = this.extensions; + + if (this.webGLVersion === 1) + { + extensions.drawBuffers = gl.getExtension('WEBGL_draw_buffers'); + extensions.depthTexture = gl.getExtension('WEBKIT_WEBGL_depth_texture'); + extensions.floatTexture = gl.getExtension('OES_texture_float'); + extensions.loseContext = gl.getExtension('WEBGL_lose_context'); + + extensions.vertexArrayObject = gl.getExtension('OES_vertex_array_object') + || gl.getExtension('MOZ_OES_vertex_array_object') + || gl.getExtension('WEBKIT_OES_vertex_array_object'); + } + + // we don't use any specific WebGL 2 ones yet! + } + + /** + * Handles a lost webgl context + * + * @private + * @param {WebGLContextEvent} event - The context lost event. + */ + handleContextLost(event) + { + event.preventDefault(); + } + + /** + * Handles a restored webgl context + * + * @private + */ + handleContextRestored() + { + this.renderer.runners.contextChange.run(this.gl); + } + + destroy() + { + const view = this.renderer.view; + + // remove listeners + view.removeEventListener('webglcontextlost', this.handleContextLost); + view.removeEventListener('webglcontextrestored', this.handleContextRestored); + + this.gl.useProgram(null); + + if (this.extensions.loseContext) + { + this.extensions.loseContext.loseContext(); + } + } + + postrender() + { + this.gl.flush(); + } + + validateContext(gl) + { + const attributes = gl.getContextAttributes(); + + // this is going to be fairly simple for now.. but at least we have room to grow! + if (!attributes.stencil) + { + /* eslint-disable max-len */ + + /* eslint-disable no-console */ + console.warn('Provided WebGL context does not have a stencil buffer, masks may not render correctly'); + /* eslint-enable no-console */ + + /* eslint-enable max-len */ + } + } +} diff --git a/packages/core/src/filters/Filter.js b/packages/core/src/filters/Filter.js new file mode 100644 index 0000000..9d8cad8 --- /dev/null +++ b/packages/core/src/filters/Filter.js @@ -0,0 +1,140 @@ +import Shader from '../shader/Shader'; +import Program from '../shader/Program'; +import State from '../state/State'; +import { settings } from '@pixi/settings'; +// import extractUniformsFromSrc from './extractUniformsFromSrc'; +import defaultVertex from './defaultFilter.vert'; +import defaultFragment from './defaultFilter.frag'; + +// let math = require('../../math'); +/** + * @class + * @memberof PIXI + * @extends PIXI.Shader + */ +export default class Filter extends Shader +{ + /** + * @param {string} [vertexSrc] - The source of the vertex shader. + * @param {string} [fragmentSrc] - The source of the fragment shader. + * @param {object} [uniforms] - Custom uniforms to use to augment the built-in ones. + */ + constructor(vertexSrc, fragmentSrc, uniforms) + { + const program = Program.from(vertexSrc, fragmentSrc); + + super(program, uniforms); + + /** + * The padding of the filter. Some filters require extra space to breath such as a blur. + * Increasing this will add extra width and height to the bounds of the object that the + * filter is applied to. + * + * @member {number} + */ + this.padding = 0; + + /** + * The resolution of the filter. Setting this to be lower will lower the quality but + * increase the performance of the filter. + * + * @member {number} + */ + this.resolution = settings.RESOLUTION; + + /** + * If enabled is true the filter is applied, if false it will not. + * + * @member {boolean} + */ + this.enabled = true; + + /** + * If enabled, PixiJS will fit the filter area into boundaries for better performance. + * Switch it off if it does not work for specific shader. + * + * @member {boolean} + */ + this.autoFit = true; + + /** + * Legacy filters use position and uvs from attributes + * @member {boolean} + * @readonly + */ + this.legacy = !!this.program.attributeData.aTextureCoord; + + /** + * the webGL state the filter requires to render + * @member {PIXI.State} + */ + this.state = new State(); + } + + /** + * Applies the filter + * + * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + * @param {boolean} clear - Should the output be cleared before rendering to it + * @param {object} [currentState] - It's current state of filter. + * There are some useful properties in the currentState : + * target, filters, sourceFrame, destinationFrame, renderTarget, resolution + */ + apply(filterManager, input, output, clear, currentState, derp) // eslint-disable-line no-unused-vars + { + // do as you please! + + filterManager.applyFilter(this, input, output, clear, currentState, derp); + + // or just do a regular render.. + } + + /** + * Sets the blendmode of the filter + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL + */ + get blendMode() + { + return this.state.blendMode; + } + + set blendMode(value) // eslint-disable-line require-jsdoc + { + this.state.blendMode = value; + } + + /** + * The default vertex shader source + * + * @static + * @constant + */ + static get defaultVertexSrc() + { + return defaultVertex; + } + + /** + * The default fragment shader source + * + * @static + * @constant + */ + static get defaultFragmentSrc() + { + return defaultFragment; + } +} + +/** + * Used for caching shader IDs + * + * @static + * @private + */ +Filter.SOURCE_KEY_MAP = {}; + diff --git a/packages/core/src/filters/FilterSystem.js b/packages/core/src/filters/FilterSystem.js new file mode 100644 index 0000000..3174318 --- /dev/null +++ b/packages/core/src/filters/FilterSystem.js @@ -0,0 +1,431 @@ +import System from '../System'; + +import RenderTexture from '../renderTexture/RenderTexture'; +import Quad from '../utils/Quad'; +import QuadUv from '../utils/QuadUv'; +import { Rectangle } from '@pixi/math'; +import * as filterTransforms from './filterTransforms'; +import bitTwiddle from 'bit-twiddle'; +import UniformGroup from '../shader/UniformGroup'; +import { DRAW_MODES } from '@pixi/constants'; + +// +/** + * @ignore + * @class + */ +class FilterState +{ + /** + * + */ + constructor() + { + this.renderTexture = null; + this.sourceFrame = new Rectangle(); + this.destinationFrame = new Rectangle(); + this.filters = []; + this.target = null; + this.legacy = false; + this.resolution = 1; + } +} + +/** + * @class + * @memberof PIXI.systems + * @extends PIXI.System + */ +export default class FilterSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + /** + * stores a bunch of PO2 textures used for filtering + * @type {Object} + */ + this.texturePool = {}; + + /** + * a pool for storing filter states, save us creating new ones each tick + * @type {Array} + */ + this.statePool = []; + + /** + * A very simple geometry used when drawing a filter effect to the screen + * @type {Quad} + */ + this.quad = new Quad(); + + this.quadUv = new QuadUv(); + + /** + * Temporary rect for maths + * @type {PIXI.Rectangle} + */ + this.tempRect = new Rectangle(); + + this.activeState = {}; + + /** + * this uniform group is attached to filter uniforms when used + * @type {UniformGroup} + */ + this.globalUniforms = new UniformGroup({ + sourceFrame: this.tempRect, + destinationFrame: this.tempRect, + + // legacy variables + filterArea: new Float32Array(4), + filterClamp: new Float32Array(4), + }, true); + } + + /** + * Adds a new filter to the System. + * + * @param {PIXI.DisplayObject} target - The target of the filter to render. + * @param {PIXI.Filter[]} filters - The filters to apply. + */ + push(target, filters) + { + const renderer = this.renderer; + const filterStack = this.renderer.renderTexture.defaultFilterStack; + const state = this.statePool.pop() || new FilterState(); + + let resolution = filters[0].resolution; + let padding = filters[0].padding; + let autoFit = filters[0].autoFit; + let legacy = filters[0].legacy; + + for (let i = 1; i < filters.length; i++) + { + const filter = filters[i]; + + // lets use the lowest resolution.. + resolution = Math.min(resolution, filter.resolution); + // and the largest amount of padding! + padding = Math.max(padding, filter.padding); + // only auto fit if all filters are autofit + autoFit = autoFit || filter.autoFit; + + legacy = legacy || filter.legacy; + } + + filterStack.push(state); + + state.resolution = resolution; + + state.legacy = legacy; + + // round to whole number based on resolution + // TODO move that to the shader too? + state.sourceFrame = target.filterArea || target.getBounds(true); + + state.sourceFrame.pad(padding); + + if (autoFit) + { + state.sourceFrame.fit(this.renderer.renderTexture.destinationFrame); + } + + state.sourceFrame.round(resolution); + + state.renderTexture = this.getPotFilterTexture(state.sourceFrame.width, state.sourceFrame.height, resolution); + state.filters = filters; + + state.destinationFrame.width = state.renderTexture.width; + state.destinationFrame.height = state.renderTexture.height; + + state.renderTexture.filterFrame = state.sourceFrame; + renderer.renderTexture.bind(state.renderTexture, state.sourceFrame);// /, state.destinationFrame); + renderer.renderTexture.clear(); + } + + /** + * Pops off the filter and applies it. + * + */ + pop() + { + const renderer = this.renderer; + const filterStack = renderer.renderTexture.defaultFilterStack; + const state = filterStack.pop(); + const filters = state.filters; + + this.activeState = state; + + const globalUniforms = this.globalUniforms.uniforms; + + globalUniforms.sourceFrame = state.sourceFrame; + globalUniforms.destinationFrame = state.destinationFrame; + globalUniforms.resolution = state.resolution; + + // only update the rect if its legacy.. + if (state.legacy) + { + const filterArea = globalUniforms.filterArea; + const filterClamp = globalUniforms.filterClamp; + + filterArea[0] = state.destinationFrame.width; + filterArea[1] = state.destinationFrame.height; + filterArea[2] = state.sourceFrame.x; + filterArea[3] = state.sourceFrame.y; + + filterClamp[0] = 0.5 / state.resolution / state.destinationFrame.width; + filterClamp[1] = 0.5 / state.resolution / state.destinationFrame.height; + filterClamp[2] = (state.sourceFrame.width - 0.5) / state.resolution / state.destinationFrame.width; + filterClamp[3] = (state.sourceFrame.height - 0.5) / state.resolution / state.destinationFrame.height; + } + + this.globalUniforms.update(); + + const lastState = filterStack[filterStack.length - 1]; + + if (filters.length === 1) + { + filters[0].apply(this, state.renderTexture, lastState.renderTexture, false, state); + + this.returnFilterTexture(state.renderTexture); + } + else + { + let flip = state.renderTexture; + let flop = this.getPotFilterTexture( + flip.width, + flip.height, + state.resolution + ); + + flop.filterFrame = flip.filterFrame; + + let i = 0; + + for (i = 0; i < filters.length - 1; ++i) + { + filters[i].apply(this, flip, flop, true, state); + + const t = flip; + + flip = flop; + flop = t; + } + + filters[i].apply(this, flip, lastState.renderTexture, false, state); + + this.returnFilterTexture(flip); + this.returnFilterTexture(flop); + } + + this.statePool.push(state); + } + + /** + * Draws a filter. + * + * @param {PIXI.Filter} filter - The filter to draw. + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + * @param {boolean} clear - Should the output be cleared before rendering to it + */ + applyFilter(filter, input, output, clear) + { + const renderer = this.renderer; + + renderer.renderTexture.bind(output, output ? output.filterFrame : null); + + if (clear) + { + // gl.disable(gl.SCISSOR_TEST); + renderer.renderTexture.clear(); + // gl.enable(gl.SCISSOR_TEST); + } + + // set the uniforms.. + filter.uniforms.uSampler = input; + filter.uniforms.filterGlobals = this.globalUniforms; + + // TODO make it so that the order of this does not matter.. + // because it does at the moment cos of global uniforms. + // they need to get resynced + + renderer.state.setState(filter.state); + renderer.shader.bind(filter); + + if (filter.legacy) + { + this.quadUv.map(input._frame, input.filterFrame); + + renderer.geometry.bind(this.quadUv); + renderer.geometry.draw(DRAW_MODES.TRIANGLES); + } + else + { + renderer.geometry.bind(this.quad); + renderer.geometry.draw(DRAW_MODES.TRIANGLE_STRIP); + } + } + + /** + * Calculates the mapped matrix. + * + * TODO playing around here.. this is temporary - (will end up in the shader) + * this returns a matrix that will normalise map filter cords in the filter to screen space + * + * @param {PIXI.Matrix} outputMatrix - the matrix to output to. + * @return {PIXI.Matrix} The mapped matrix. + */ + calculateScreenSpaceMatrix(outputMatrix) + { + const currentState = this.activeState; + + return filterTransforms.calculateScreenSpaceMatrix( + outputMatrix, + currentState.sourceFrame, + currentState.destinationFrame + ); + } + + /** + * This will map the filter coord so that a texture can be used based on the transform of a sprite + * + * @param {PIXI.Matrix} outputMatrix - The matrix to output to. + * @param {PIXI.Sprite} sprite - The sprite to map to. + * @return {PIXI.Matrix} The mapped matrix. + */ + calculateSpriteMatrix(outputMatrix, sprite) + { + const currentState = this.activeState; + + return filterTransforms.calculateSpriteMatrix( + outputMatrix, + currentState.sourceFrame, + currentState.destinationFrame, + sprite + ); + } + + /** + * Destroys this Filter System. + * + * @param {boolean} [contextLost=false] context was lost, do not free shaders + * + */ + destroy(contextLost = false) + { + if (!contextLost) + { + this.emptyPool(); + } + else + { + this.texturePool = {}; + } + } + + /** + * Gets a Power-of-Two render texture. + * + * TODO move to a seperate class could be on renderer? + * also - could cause issue with multiple contexts? + * + * @private + * @param {WebGLRenderingContext} gl - The webgl rendering context + * @param {number} minWidth - The minimum width of the render target. + * @param {number} minHeight - The minimum height of the render target. + * @param {number} resolution - The resolution of the render target. + * @return {PIXI.RenderTarget} The new render target. + */ + getPotFilterTexture(minWidth, minHeight, resolution) + { + minWidth = bitTwiddle.nextPow2(minWidth); + minHeight = bitTwiddle.nextPow2(minHeight); + resolution = resolution || 1; + + const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + + if (!this.texturePool[key]) + { + this.texturePool[key] = []; + } + + let renderTexture = this.texturePool[key].pop(); + + if (!renderTexture) + { + // temporary bypass cache.. + // internally - this will cause a texture to be bound.. + renderTexture = RenderTexture.create({ + width: minWidth, + height: minHeight, + resolution, + }); + } + + return renderTexture; + } + + /** + * Gets extra render texture to use inside current filter + * + * @param {number} resolution resolution of the renderTexture + * @returns {PIXI.RenderTexture} + */ + getFilterTexture(resolution) + { + const rt = this.activeState.renderTexture; + + const filterTexture = this.getPotFilterTexture(rt.width, rt.height, resolution || rt.baseTexture.resolution); + + filterTexture.filterFrame = rt.filterFrame; + + return filterTexture; + } + + /** + * Frees a render texture back into the pool. + * + * @param {PIXI.RenderTarget} renderTexture - The renderTarget to free + */ + returnFilterTexture(renderTexture) + { + renderTexture.filterFrame = null; + + const base = renderTexture.baseTexture; + + const minWidth = base.width; + const minHeight = base.height; + + const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + + this.texturePool[key].push(renderTexture); + } + + /** + * Empties the texture pool. + * + */ + emptyPool() + { + for (const i in this.texturePool) + { + const textures = this.texturePool[i]; + + if (textures) + { + for (let j = 0; j < textures.length; j++) + { + textures[j].destroy(true); + } + } + } + + this.texturePool = {}; + } +} diff --git a/packages/core/src/filters/defaultFilter.frag b/packages/core/src/filters/defaultFilter.frag new file mode 100644 index 0000000..a14c1be --- /dev/null +++ b/packages/core/src/filters/defaultFilter.frag @@ -0,0 +1,22 @@ +varying vec2 vTextureCoord; +varying vec2 vFilterCoord; + +uniform sampler2D uSampler; +uniform sampler2D filterSampler; + +void main(void){ + vec4 masky = texture2D(filterSampler, vFilterCoord); + vec4 sample = texture2D(uSampler, vTextureCoord); + vec4 color; + if(mod(vFilterCoord.x, 1.0) > 0.5) + { + color = vec4(1.0, 0.0, 0.0, 1.0); + } + else + { + color = vec4(0.0, 1.0, 0.0, 1.0); + } + // gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0); + gl_FragColor = mix(sample, masky, 0.5); + gl_FragColor *= sample.a; +} diff --git a/packages/core/src/filters/defaultFilter.vert b/packages/core/src/filters/defaultFilter.vert new file mode 100644 index 0000000..e1febca --- /dev/null +++ b/packages/core/src/filters/defaultFilter.vert @@ -0,0 +1,14 @@ +attribute vec2 aVertexPosition; +attribute vec2 aTextureCoord; + +uniform mat3 projectionMatrix; +uniform mat3 filterMatrix; + +varying vec2 vTextureCoord; +varying vec2 vFilterCoord; + +void main(void){ + gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); + vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy; + vTextureCoord = aTextureCoord ; +} diff --git a/packages/core/src/filters/filterTransforms.js b/packages/core/src/filters/filterTransforms.js new file mode 100644 index 0000000..87a3cd6 --- /dev/null +++ b/packages/core/src/filters/filterTransforms.js @@ -0,0 +1,50 @@ +import { Matrix } from '@pixi/math'; + +/** + * Calculates the mapped matrix + * @param filterArea {Rectangle} The filter area + * @param sprite {Sprite} the target sprite + * @param outputMatrix {Matrix} @alvin + * @private + */ +// this returns a matrix that will normalise map filter cords in the filter to screen space +export function calculateScreenSpaceMatrix(outputMatrix, filterArea, textureSize) +{ + // TODO unwrap? + const mappedMatrix = outputMatrix.identity(); + + mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); + + mappedMatrix.scale(textureSize.width, textureSize.height); + + return mappedMatrix; +} + +export function calculateNormalizedScreenSpaceMatrix(outputMatrix, filterArea, textureSize) +{ + const mappedMatrix = outputMatrix.identity(); + + mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); + + const translateScaleX = (textureSize.width / filterArea.width); + const translateScaleY = (textureSize.height / filterArea.height); + + mappedMatrix.scale(translateScaleX, translateScaleY); + + return mappedMatrix; +} + +// this will map the filter coord so that a texture can be used based on the transform of a sprite +export function calculateSpriteMatrix(outputMatrix, filterArea, textureSize, sprite) +{ + const orig = sprite._texture.orig; + const mappedMatrix = outputMatrix.set(textureSize.width, 0, 0, textureSize.height, filterArea.x, filterArea.y); + const worldTransform = sprite.worldTransform.copyTo(Matrix.TEMP_MATRIX); + + worldTransform.invert(); + mappedMatrix.prepend(worldTransform); + mappedMatrix.scale(1.0 / orig.width, 1.0 / orig.height); + mappedMatrix.translate(sprite.anchor.x, sprite.anchor.y); + + return mappedMatrix; +} diff --git a/packages/core/src/AbstractRenderer.js b/packages/core/src/AbstractRenderer.js new file mode 100644 index 0000000..64b2eb1 --- /dev/null +++ b/packages/core/src/AbstractRenderer.js @@ -0,0 +1,353 @@ +import { hex2string, hex2rgb } from '@pixi/utils'; +import { Matrix, Rectangle } from '@pixi/math'; +import { RENDERER_TYPE } from '@pixi/constants'; +import { settings } from '@pixi/settings'; +import { Container } from '@pixi/display'; +import RenderTexture from './renderTexture/RenderTexture'; +import EventEmitter from 'eventemitter3'; + +const tempMatrix = new Matrix(); + +/** + * 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 EventEmitter + * @memberof PIXI + */ +export default class AbstractRenderer extends EventEmitter +{ + // eslint-disable-next-line valid-jsdoc + /** + * @param {string} system - The name of the system this renderer is for. + * @param {object} [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.transparent=false] - If the render view is transparent, default false + * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false + * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. The + * resolution of the renderer retina would be 2. + * @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 {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when rendering, + * stopping pixel interpolation. + */ + constructor(system, options, arg2, arg3) + { + super(); + + // Support for constructor(system, screenWidth, screenHeight, options) + if (typeof options === 'number') + { + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + }, arg3); + } + + // 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 = RENDERER_TYPE.UNKNOWN; + + /** + * Measurements of the screen. (0, 0, screenWidth, screenHeight) + * + * Its safe to use as filterArea or hitArea for 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 1 + */ + this.resolution = options.resolution || settings.RESOLUTION; + + /** + * Whether the render view is transparent + * + * @member {boolean} + */ + this.transparent = options.transparent; + + /** + * Whether css dimensions of canvas view should be resized to screen dimensions automatically + * + * @member {boolean} + */ + this.autoResize = options.autoResize || false; + + /** + * Tracks the blend modes useful for this renderer. + * + * @member {object} + */ + this.blendModes = null; + + /** + * 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; + + /** + * If true PixiJS will Math.floor() x/y values when rendering, stopping pixel interpolation. + * Handy for crisp pixel art and speed on legacy devices. + * + * @member {boolean} + */ + this.roundPixels = options.roundPixels; + + /** + * The background color as a number. + * + * @member {number} + * @private + */ + this._backgroundColor = 0x000000; + + /** + * The background color as an [R, G, B] array. + * + * @member {number[]} + * @private + */ + this._backgroundColorRgba = [0, 0, 0, 0]; + + /** + * The background color as a string. + * + * @member {string} + * @private + */ + this._backgroundColorString = '#000000'; + + this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter + + /** + * This temporary display object used as the parent of the currently being rendered item + * + * @member {PIXI.DisplayObject} + * @private + */ + this._tempDisplayObjectParent = new Container(); + + /** + * The last root object that the renderer tried to render. + * + * @member {PIXI.DisplayObject} + * @private + */ + this._lastObjectRendered = this._tempDisplayObjectParent; + + /** + * Collection of plugins. + * @readonly + * @member {object} + */ + this.plugins = {}; + } + + /** + * Initialize the plugins. + * + * @protected + * @param {object} staticMap - The dictionary of staticly saved plugins. + */ + initPlugins(staticMap) + { + 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() + { + return this.view.width; + } + + /** + * Same as view.height, actual number of pixels in the canvas by vertical + * + * @member {number} + * @readonly + * @default 600 + */ + get height() + { + return this.view.height; + } + + /** + * Resizes the screen and canvas to the specified width and height + * Canvas dimensions are multiplied by resolution + * + * @param {number} screenWidth - the new width of the screen + * @param {number} screenHeight - the new height of the screen + */ + resize(screenWidth, screenHeight) + { + this.screen.width = screenWidth; + this.screen.height = screenHeight; + + this.view.width = screenWidth * this.resolution; + this.view.height = screenHeight * this.resolution; + + if (this.autoResize) + { + this.view.style.width = `${screenWidth}px`; + this.view.style.height = `${screenHeight}px`; + } + } + + /** + * 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 {PIXI.DisplayObject} displayObject - The displayObject the object will be generated from + * @param {number} scaleMode - Should be one of the scaleMode consts + * @param {number} resolution - The resolution / device pixel ratio of the texture being generated + * @param {PIXI.Rectangle} [region] - The region of the displayObject, that shall be rendered, + * if no region is specified, defaults to the local bounds of the displayObject. + * @return {PIXI.Texture} a texture of the graphics object + */ + generateTexture(displayObject, scaleMode, resolution, region) + { + region = region || displayObject.getLocalBounds(); + + // 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(region.width | 0, region.height | 0, scaleMode, resolution); + + tempMatrix.tx = -region.x; + tempMatrix.ty = -region.y; + + this.render(displayObject, renderTexture, false, tempMatrix, true); + + return renderTexture; + } + + /** + * Removes everything from the renderer and optionally removes the Canvas DOM element. + * + * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. + */ + destroy(removeView) + { + 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); + } + + this.plugins = null; + + this.type = RENDERER_TYPE.UNKNOWN; + + this.view = null; + + this.screen = null; + + this.resolution = 0; + + this.transparent = false; + + this.autoResize = false; + + this.blendModes = null; + + this.options = null; + + this.preserveDrawingBuffer = false; + this.clearBeforeRender = false; + + this.roundPixels = false; + + this._backgroundColor = 0; + this._backgroundColorRgba = null; + this._backgroundColorString = null; + + this._tempDisplayObjectParent = null; + this._lastObjectRendered = null; + } + + /** + * The background color to fill if not transparent + * + * @member {number} + */ + get backgroundColor() + { + return this._backgroundColor; + } + + set backgroundColor(value) // eslint-disable-line require-jsdoc + { + this._backgroundColor = value; + this._backgroundColorString = hex2string(value); + hex2rgb(value, this._backgroundColorRgba); + } +} diff --git a/packages/core/src/Renderer.js b/packages/core/src/Renderer.js new file mode 100644 index 0000000..2227e78 --- /dev/null +++ b/packages/core/src/Renderer.js @@ -0,0 +1,338 @@ +import AbstractRenderer from './AbstractRenderer'; +import { sayHello } from '@pixi/utils'; +import MaskSystem from './mask/MaskSystem'; +import StencilSystem from './mask/StencilSystem'; +import FilterSystem from './filters/FilterSystem'; +import FramebufferSystem from './framebuffer/FramebufferSystem'; +import RenderTextureSystem from './renderTexture/RenderTextureSystem'; +import TextureSystem from './textures/TextureSystem'; +import ProjectionSystem from './projection/ProjectionSystem'; +import StateSystem from './state/StateSystem'; +import GeometrySystem from './geometry/GeometrySystem'; +import ShaderSystem from './shader/ShaderSystem'; +import ContextSystem from './context/ContextSystem'; +import BatchSystem from './batch/BatchSystem'; +import TextureGCSystem from './textures/TextureGCSystem'; +import { RENDERER_TYPE } from '@pixi/constants'; +import UniformGroup from './shader/UniformGroup'; +import { Matrix } from '@pixi/math'; +import Runner from 'mini-runner'; + +/** + * 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 Render works by automatically managing webGLBatchs. + * 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 :) + * + * @class + * @memberof PIXI + * @extends PIXI.AbstractRenderer + */ +export default class Renderer extends AbstractRenderer +{ + // eslint-disable-next-line valid-jsdoc + /** + * + * @param {object} [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.transparent=false] - If the render view is transparent, default false + * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false + * @param {boolean} [options.antialias=false] - sets antialias. If not available natively then FXAA + * antialiasing is used + * @param {boolean} [options.forceFXAA=false] - forces FXAA antialiasing to be used over native. + * FXAA is faster, but may not always look as great + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. + * The resolution of the renderer retina would be 2. + * @param {boolean} [options.clearBeforeRender=true] - This sets if the renderer will clear + * the canvas or not before the new render pass. If you wish to set this to false, you *must* set + * preserveDrawingBuffer to `true`. + * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, + * enable this if you need to call toDataUrl on the webgl context. + * @param {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when + * rendering, stopping pixel interpolation. + * @param {number} [options.backgroundColor=0x000000] - The background color of the rendered area + * (shown if not transparent). + * @param {boolean} [options.legacy=false] - If true PixiJS will aim to ensure compatibility + * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. + * @param {string} [options.powerPreference] - Parameter passed to webgl context, set to "high-performance" + * for devices with dual graphics card + */ + constructor(options, arg2, arg3) + { + super('WebGL', options, arg2, arg3); + + /** + * The type of this renderer as a standardised const + * + * @member {number} + * @see PIXI.RENDERER_TYPE + */ + this.type = RENDERER_TYPE.WEBGL; + + // this will be set by the contextSystem (this.context) + this.gl = null; + this.CONTEXT_UID = 0; + this.legacy = !!options.legacy; + + // TODO legacy! + + // runners! + this.runners = { + destroy: new Runner('destroy'), + contextChange: new Runner('contextChange', 1), + reset: new Runner('reset'), + update: new Runner('update'), + postrender: new Runner('postrender'), + prerender: new Runner('prerender'), + resize: new Runner('resize', 2), + }; + + 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(GeometrySystem, 'geometry') + .addSystem(FramebufferSystem, 'framebuffer') + .addSystem(StencilSystem, 'stencil') + .addSystem(ProjectionSystem, 'projection') + .addSystem(TextureGCSystem, 'textureGC') + .addSystem(FilterSystem, 'filter') + .addSystem(RenderTextureSystem, 'renderTexture') + .addSystem(BatchSystem, 'batch'); + + this.initPlugins(Renderer.__plugins); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ + if (options.context) + { + this.context.initFromContext(options.context); + } + else + { + this.context.initFromOptions({ + alpha: this.transparent, + antialias: options.antialias, + premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', + stencil: true, + 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); + } + + /** + * Add a new system to the renderer. + * @param {class} ClassRef - Class reference + * @param {string} [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. + * @return {PIXI.Renderer} Return instance of renderer + */ + addSystem(ClassRef, name) + { + if (!name) + { + name = ClassRef.name; + } + + // TODO - read name from class.name.. + + /* + if(name.includes('System')) + { + name = name.replace('System', ''); + name = name.charAt(0).toLowerCase() + name.slice(1); + } + */ + + const system = new ClassRef(this); + + if (this[name]) + { + throw new Error(`Whoops! ${name} is already a manger`); + } + + this[name] = system; + + for (const i in this.runners) + { + this.runners[i].add(system); + } + + return this; + + /** + * 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. + */ + } + + /** + * Renders the object to its webGL view + * + * @param {PIXI.DisplayObject} displayObject - the object to be rendered + * @param {PIXI.RenderTexture} renderTexture - The render texture to render to. + * @param {boolean} [clear] - Should the canvas be cleared before the new render + * @param {PIXI.Transform} [transform] - A transform to apply to the render texture before rendering. + * @param {boolean} [skipUpdateTransform] - Should we skip the update transform pass? + */ + render(displayObject, renderTexture, clear, transform, skipUpdateTransform) + { + // can be handy to know! + this.renderingToScreen = !renderTexture; + + this.runners.prerender.run(); + this.emit('prerender'); + + // 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.parent; + + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = 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.run(); + + this.emit('postrender'); + } + + /** + * Resizes the webGL view to the specified width and height. + * + * @param {number} screenWidth - the new width of the screen + * @param {number} screenHeight - the new height of the screen + */ + resize(screenWidth, screenHeight) + { + AbstractRenderer.prototype.resize.call(this, screenWidth, screenHeight); + + this.runners.resize.run(screenWidth, screenHeight); + } + + /** + * Resets the WebGL state so you can render things however you fancy! + * + * @return {PIXI.Renderer} Returns itself. + */ + reset() + { + this.runners.reset.run(); + + return this; + } + + clear() + { + this.framebuffer.bind(); + this.framebuffer.clear(); + } + + /** + * Removes everything from the renderer (event listeners, spritebatch, etc...) + * + * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. + * See: https://github.com/pixijs/pixi.js/issues/2233 + */ + destroy(removeView) + { + this.runners.destroy.run(); + + // call base destroy + super.destroy(removeView); + + // TODO nullify all the managers.. + this.gl = null; + } + + /** + * 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 + * builds and excluding plugins. + * @name PIXI.Renderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. + */ + + /** + * Adds a plugin to the renderer. + * + * @method + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + static registerPlugin(pluginName, ctor) + { + Renderer.__plugins = Renderer.__plugins || {}; + Renderer.__plugins[pluginName] = ctor; + } +} diff --git a/packages/core/src/System.js b/packages/core/src/System.js new file mode 100644 index 0000000..05a10c8 --- /dev/null +++ b/packages/core/src/System.js @@ -0,0 +1,42 @@ +/** + * System is a base class used for extending systems used by the {@link PIXI.Renderer} + * @see PIXI.Renderer#addSystem + * @class + * @memberof PIXI + */ +export default class System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + /** + * The renderer this manager works for. + * + * @member {PIXI.Renderer} + */ + this.renderer = renderer; + + this.renderer.runners.contextChange.add(this); + } + + /** + * Generic method called when there is a WebGL context change. + * + */ + contextChange() + { + // do some codes init! + } + + /** + * Generic destroy methods to be overridden by the subclass + * + */ + destroy() + { + this.renderer.runners.contextChange.remove(this); + this.renderer = null; + } +} diff --git a/packages/core/src/batch/BatchSystem.js b/packages/core/src/batch/BatchSystem.js new file mode 100644 index 0000000..b04c249 --- /dev/null +++ b/packages/core/src/batch/BatchSystem.js @@ -0,0 +1,68 @@ +import System from '../System'; +import ObjectRenderer from './ObjectRenderer'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class BatchSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + super(renderer); + + /** + * An empty renderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.emptyRenderer = new ObjectRenderer(renderer); + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.currentRenderer = this.emptyRenderer; + } + + /** + * Changes the current renderer to the one given in parameter + * + * @param {PIXI.ObjectRenderer} objectRenderer - The object renderer to use. + */ + setObjectRenderer(objectRenderer) + { + if (this.currentRenderer === objectRenderer) + { + return; + } + + this.currentRenderer.stop(); + this.currentRenderer = objectRenderer; + + this.renderer.state.setState(objectRenderer.state); + + this.currentRenderer.start(); + } + + /** + * This should be called if you wish to do some custom rendering + * It will basically render anything that may be batched up such as sprites + * + */ + flush() + { + this.setObjectRenderer(this.emptyRenderer); + } + + reset() + { + this.setObjectRenderer(this.emptyRenderer); + } +} diff --git a/packages/core/src/batch/ObjectRenderer.js b/packages/core/src/batch/ObjectRenderer.js new file mode 100644 index 0000000..0c851a9 --- /dev/null +++ b/packages/core/src/batch/ObjectRenderer.js @@ -0,0 +1,48 @@ +import System from '../System'; + +/** + * Base for a common object renderer that can be used as a system renderer plugin. + * + * @class + * @extends PIXI.System + * @memberof PIXI + */ +export default class ObjectRenderer extends System +{ + /** + * Starts the renderer and sets the shader + * + */ + start() + { + // set the shader.. + } + + /** + * Stops the renderer + * + */ + stop() + { + this.flush(); + } + + /** + * Stub method for rendering content and emptying the current batch. + * + */ + flush() + { + // flush! + } + + /** + * Renders an object + * + * @param {PIXI.DisplayObject} object - The object to render. + */ + render(object) // eslint-disable-line no-unused-vars + { + // render the object + } +} diff --git a/packages/core/src/context/ContextSystem.js b/packages/core/src/context/ContextSystem.js new file mode 100644 index 0000000..301aab4 --- /dev/null +++ b/packages/core/src/context/ContextSystem.js @@ -0,0 +1,186 @@ +import System from '../System'; +import { settings } from '@pixi/settings'; + +let CONTEXT_UID = 0; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class ContextSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.webGLVersion = 1; + + this.handleContextLost = this.handleContextLost.bind(this); + this.handleContextRestored = this.handleContextRestored.bind(this); + + this.extensions = {}; + + renderer.view.addEventListener('webglcontextlost', this.handleContextLost, false); + renderer.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + } + + get isLost() + { + return (!this.gl || this.gl.isContextLost()); + } + + contextChange(gl) + { + this.gl = gl; + + // restore a context if it was previously lost + if (gl.isContextLost() && gl.getExtension('WEBGL_lose_context')) + { + gl.getExtension('WEBGL_lose_context').restoreContext(); + } + } + + initFromContext(gl) + { + this.gl = gl; + this.validateContext(gl); + this.renderer.gl = gl; + this.renderer.CONTEXT_UID = CONTEXT_UID++; + this.renderer.runners.contextChange.run(gl); + } + + initFromOptions(options) + { + const gl = this.createContext(this.renderer.view, options); + + this.initFromContext(gl); + } + + /** + * Helper class to create a webGL Context + * + * @class + * @memberof PIXI.glCore + * @param canvas {HTMLCanvasElement} the canvas element that we will get the context from + * @param options {Object} An options object that gets passed in to the canvas element containing the context attributes, + * see https://developer.mozilla.org/en/docs/Web/API/HTMLCanvasElement/getContext for the options available + * @return {WebGLRenderingContext} the WebGL context + */ + createContext(canvas, options) + { + let gl; + + if (settings.PREFER_WEBGL_2) + { + gl = canvas.getContext('webgl2', options); + } + + if (gl) + { + this.webGLVersion = 2; + } + else + { + this.webGLVersion = 1; + + gl = canvas.getContext('webgl', options) + || canvas.getContext('experimental-webgl', options); + + if (!gl) + { + // fail, not able to get a context + throw new Error('This browser does not support webGL. Try using the canvas renderer'); + } + } + + this.gl = gl; + + this.getExtensions(); + + return gl; + } + + getExtensions() + { + // time to set up default etensions that pixi uses.. + const gl = this.gl; + const extensions = this.extensions; + + if (this.webGLVersion === 1) + { + extensions.drawBuffers = gl.getExtension('WEBGL_draw_buffers'); + extensions.depthTexture = gl.getExtension('WEBKIT_WEBGL_depth_texture'); + extensions.floatTexture = gl.getExtension('OES_texture_float'); + extensions.loseContext = gl.getExtension('WEBGL_lose_context'); + + extensions.vertexArrayObject = gl.getExtension('OES_vertex_array_object') + || gl.getExtension('MOZ_OES_vertex_array_object') + || gl.getExtension('WEBKIT_OES_vertex_array_object'); + } + + // we don't use any specific WebGL 2 ones yet! + } + + /** + * Handles a lost webgl context + * + * @private + * @param {WebGLContextEvent} event - The context lost event. + */ + handleContextLost(event) + { + event.preventDefault(); + } + + /** + * Handles a restored webgl context + * + * @private + */ + handleContextRestored() + { + this.renderer.runners.contextChange.run(this.gl); + } + + destroy() + { + const view = this.renderer.view; + + // remove listeners + view.removeEventListener('webglcontextlost', this.handleContextLost); + view.removeEventListener('webglcontextrestored', this.handleContextRestored); + + this.gl.useProgram(null); + + if (this.extensions.loseContext) + { + this.extensions.loseContext.loseContext(); + } + } + + postrender() + { + this.gl.flush(); + } + + validateContext(gl) + { + const attributes = gl.getContextAttributes(); + + // this is going to be fairly simple for now.. but at least we have room to grow! + if (!attributes.stencil) + { + /* eslint-disable max-len */ + + /* eslint-disable no-console */ + console.warn('Provided WebGL context does not have a stencil buffer, masks may not render correctly'); + /* eslint-enable no-console */ + + /* eslint-enable max-len */ + } + } +} diff --git a/packages/core/src/filters/Filter.js b/packages/core/src/filters/Filter.js new file mode 100644 index 0000000..9d8cad8 --- /dev/null +++ b/packages/core/src/filters/Filter.js @@ -0,0 +1,140 @@ +import Shader from '../shader/Shader'; +import Program from '../shader/Program'; +import State from '../state/State'; +import { settings } from '@pixi/settings'; +// import extractUniformsFromSrc from './extractUniformsFromSrc'; +import defaultVertex from './defaultFilter.vert'; +import defaultFragment from './defaultFilter.frag'; + +// let math = require('../../math'); +/** + * @class + * @memberof PIXI + * @extends PIXI.Shader + */ +export default class Filter extends Shader +{ + /** + * @param {string} [vertexSrc] - The source of the vertex shader. + * @param {string} [fragmentSrc] - The source of the fragment shader. + * @param {object} [uniforms] - Custom uniforms to use to augment the built-in ones. + */ + constructor(vertexSrc, fragmentSrc, uniforms) + { + const program = Program.from(vertexSrc, fragmentSrc); + + super(program, uniforms); + + /** + * The padding of the filter. Some filters require extra space to breath such as a blur. + * Increasing this will add extra width and height to the bounds of the object that the + * filter is applied to. + * + * @member {number} + */ + this.padding = 0; + + /** + * The resolution of the filter. Setting this to be lower will lower the quality but + * increase the performance of the filter. + * + * @member {number} + */ + this.resolution = settings.RESOLUTION; + + /** + * If enabled is true the filter is applied, if false it will not. + * + * @member {boolean} + */ + this.enabled = true; + + /** + * If enabled, PixiJS will fit the filter area into boundaries for better performance. + * Switch it off if it does not work for specific shader. + * + * @member {boolean} + */ + this.autoFit = true; + + /** + * Legacy filters use position and uvs from attributes + * @member {boolean} + * @readonly + */ + this.legacy = !!this.program.attributeData.aTextureCoord; + + /** + * the webGL state the filter requires to render + * @member {PIXI.State} + */ + this.state = new State(); + } + + /** + * Applies the filter + * + * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + * @param {boolean} clear - Should the output be cleared before rendering to it + * @param {object} [currentState] - It's current state of filter. + * There are some useful properties in the currentState : + * target, filters, sourceFrame, destinationFrame, renderTarget, resolution + */ + apply(filterManager, input, output, clear, currentState, derp) // eslint-disable-line no-unused-vars + { + // do as you please! + + filterManager.applyFilter(this, input, output, clear, currentState, derp); + + // or just do a regular render.. + } + + /** + * Sets the blendmode of the filter + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL + */ + get blendMode() + { + return this.state.blendMode; + } + + set blendMode(value) // eslint-disable-line require-jsdoc + { + this.state.blendMode = value; + } + + /** + * The default vertex shader source + * + * @static + * @constant + */ + static get defaultVertexSrc() + { + return defaultVertex; + } + + /** + * The default fragment shader source + * + * @static + * @constant + */ + static get defaultFragmentSrc() + { + return defaultFragment; + } +} + +/** + * Used for caching shader IDs + * + * @static + * @private + */ +Filter.SOURCE_KEY_MAP = {}; + diff --git a/packages/core/src/filters/FilterSystem.js b/packages/core/src/filters/FilterSystem.js new file mode 100644 index 0000000..3174318 --- /dev/null +++ b/packages/core/src/filters/FilterSystem.js @@ -0,0 +1,431 @@ +import System from '../System'; + +import RenderTexture from '../renderTexture/RenderTexture'; +import Quad from '../utils/Quad'; +import QuadUv from '../utils/QuadUv'; +import { Rectangle } from '@pixi/math'; +import * as filterTransforms from './filterTransforms'; +import bitTwiddle from 'bit-twiddle'; +import UniformGroup from '../shader/UniformGroup'; +import { DRAW_MODES } from '@pixi/constants'; + +// +/** + * @ignore + * @class + */ +class FilterState +{ + /** + * + */ + constructor() + { + this.renderTexture = null; + this.sourceFrame = new Rectangle(); + this.destinationFrame = new Rectangle(); + this.filters = []; + this.target = null; + this.legacy = false; + this.resolution = 1; + } +} + +/** + * @class + * @memberof PIXI.systems + * @extends PIXI.System + */ +export default class FilterSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + /** + * stores a bunch of PO2 textures used for filtering + * @type {Object} + */ + this.texturePool = {}; + + /** + * a pool for storing filter states, save us creating new ones each tick + * @type {Array} + */ + this.statePool = []; + + /** + * A very simple geometry used when drawing a filter effect to the screen + * @type {Quad} + */ + this.quad = new Quad(); + + this.quadUv = new QuadUv(); + + /** + * Temporary rect for maths + * @type {PIXI.Rectangle} + */ + this.tempRect = new Rectangle(); + + this.activeState = {}; + + /** + * this uniform group is attached to filter uniforms when used + * @type {UniformGroup} + */ + this.globalUniforms = new UniformGroup({ + sourceFrame: this.tempRect, + destinationFrame: this.tempRect, + + // legacy variables + filterArea: new Float32Array(4), + filterClamp: new Float32Array(4), + }, true); + } + + /** + * Adds a new filter to the System. + * + * @param {PIXI.DisplayObject} target - The target of the filter to render. + * @param {PIXI.Filter[]} filters - The filters to apply. + */ + push(target, filters) + { + const renderer = this.renderer; + const filterStack = this.renderer.renderTexture.defaultFilterStack; + const state = this.statePool.pop() || new FilterState(); + + let resolution = filters[0].resolution; + let padding = filters[0].padding; + let autoFit = filters[0].autoFit; + let legacy = filters[0].legacy; + + for (let i = 1; i < filters.length; i++) + { + const filter = filters[i]; + + // lets use the lowest resolution.. + resolution = Math.min(resolution, filter.resolution); + // and the largest amount of padding! + padding = Math.max(padding, filter.padding); + // only auto fit if all filters are autofit + autoFit = autoFit || filter.autoFit; + + legacy = legacy || filter.legacy; + } + + filterStack.push(state); + + state.resolution = resolution; + + state.legacy = legacy; + + // round to whole number based on resolution + // TODO move that to the shader too? + state.sourceFrame = target.filterArea || target.getBounds(true); + + state.sourceFrame.pad(padding); + + if (autoFit) + { + state.sourceFrame.fit(this.renderer.renderTexture.destinationFrame); + } + + state.sourceFrame.round(resolution); + + state.renderTexture = this.getPotFilterTexture(state.sourceFrame.width, state.sourceFrame.height, resolution); + state.filters = filters; + + state.destinationFrame.width = state.renderTexture.width; + state.destinationFrame.height = state.renderTexture.height; + + state.renderTexture.filterFrame = state.sourceFrame; + renderer.renderTexture.bind(state.renderTexture, state.sourceFrame);// /, state.destinationFrame); + renderer.renderTexture.clear(); + } + + /** + * Pops off the filter and applies it. + * + */ + pop() + { + const renderer = this.renderer; + const filterStack = renderer.renderTexture.defaultFilterStack; + const state = filterStack.pop(); + const filters = state.filters; + + this.activeState = state; + + const globalUniforms = this.globalUniforms.uniforms; + + globalUniforms.sourceFrame = state.sourceFrame; + globalUniforms.destinationFrame = state.destinationFrame; + globalUniforms.resolution = state.resolution; + + // only update the rect if its legacy.. + if (state.legacy) + { + const filterArea = globalUniforms.filterArea; + const filterClamp = globalUniforms.filterClamp; + + filterArea[0] = state.destinationFrame.width; + filterArea[1] = state.destinationFrame.height; + filterArea[2] = state.sourceFrame.x; + filterArea[3] = state.sourceFrame.y; + + filterClamp[0] = 0.5 / state.resolution / state.destinationFrame.width; + filterClamp[1] = 0.5 / state.resolution / state.destinationFrame.height; + filterClamp[2] = (state.sourceFrame.width - 0.5) / state.resolution / state.destinationFrame.width; + filterClamp[3] = (state.sourceFrame.height - 0.5) / state.resolution / state.destinationFrame.height; + } + + this.globalUniforms.update(); + + const lastState = filterStack[filterStack.length - 1]; + + if (filters.length === 1) + { + filters[0].apply(this, state.renderTexture, lastState.renderTexture, false, state); + + this.returnFilterTexture(state.renderTexture); + } + else + { + let flip = state.renderTexture; + let flop = this.getPotFilterTexture( + flip.width, + flip.height, + state.resolution + ); + + flop.filterFrame = flip.filterFrame; + + let i = 0; + + for (i = 0; i < filters.length - 1; ++i) + { + filters[i].apply(this, flip, flop, true, state); + + const t = flip; + + flip = flop; + flop = t; + } + + filters[i].apply(this, flip, lastState.renderTexture, false, state); + + this.returnFilterTexture(flip); + this.returnFilterTexture(flop); + } + + this.statePool.push(state); + } + + /** + * Draws a filter. + * + * @param {PIXI.Filter} filter - The filter to draw. + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + * @param {boolean} clear - Should the output be cleared before rendering to it + */ + applyFilter(filter, input, output, clear) + { + const renderer = this.renderer; + + renderer.renderTexture.bind(output, output ? output.filterFrame : null); + + if (clear) + { + // gl.disable(gl.SCISSOR_TEST); + renderer.renderTexture.clear(); + // gl.enable(gl.SCISSOR_TEST); + } + + // set the uniforms.. + filter.uniforms.uSampler = input; + filter.uniforms.filterGlobals = this.globalUniforms; + + // TODO make it so that the order of this does not matter.. + // because it does at the moment cos of global uniforms. + // they need to get resynced + + renderer.state.setState(filter.state); + renderer.shader.bind(filter); + + if (filter.legacy) + { + this.quadUv.map(input._frame, input.filterFrame); + + renderer.geometry.bind(this.quadUv); + renderer.geometry.draw(DRAW_MODES.TRIANGLES); + } + else + { + renderer.geometry.bind(this.quad); + renderer.geometry.draw(DRAW_MODES.TRIANGLE_STRIP); + } + } + + /** + * Calculates the mapped matrix. + * + * TODO playing around here.. this is temporary - (will end up in the shader) + * this returns a matrix that will normalise map filter cords in the filter to screen space + * + * @param {PIXI.Matrix} outputMatrix - the matrix to output to. + * @return {PIXI.Matrix} The mapped matrix. + */ + calculateScreenSpaceMatrix(outputMatrix) + { + const currentState = this.activeState; + + return filterTransforms.calculateScreenSpaceMatrix( + outputMatrix, + currentState.sourceFrame, + currentState.destinationFrame + ); + } + + /** + * This will map the filter coord so that a texture can be used based on the transform of a sprite + * + * @param {PIXI.Matrix} outputMatrix - The matrix to output to. + * @param {PIXI.Sprite} sprite - The sprite to map to. + * @return {PIXI.Matrix} The mapped matrix. + */ + calculateSpriteMatrix(outputMatrix, sprite) + { + const currentState = this.activeState; + + return filterTransforms.calculateSpriteMatrix( + outputMatrix, + currentState.sourceFrame, + currentState.destinationFrame, + sprite + ); + } + + /** + * Destroys this Filter System. + * + * @param {boolean} [contextLost=false] context was lost, do not free shaders + * + */ + destroy(contextLost = false) + { + if (!contextLost) + { + this.emptyPool(); + } + else + { + this.texturePool = {}; + } + } + + /** + * Gets a Power-of-Two render texture. + * + * TODO move to a seperate class could be on renderer? + * also - could cause issue with multiple contexts? + * + * @private + * @param {WebGLRenderingContext} gl - The webgl rendering context + * @param {number} minWidth - The minimum width of the render target. + * @param {number} minHeight - The minimum height of the render target. + * @param {number} resolution - The resolution of the render target. + * @return {PIXI.RenderTarget} The new render target. + */ + getPotFilterTexture(minWidth, minHeight, resolution) + { + minWidth = bitTwiddle.nextPow2(minWidth); + minHeight = bitTwiddle.nextPow2(minHeight); + resolution = resolution || 1; + + const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + + if (!this.texturePool[key]) + { + this.texturePool[key] = []; + } + + let renderTexture = this.texturePool[key].pop(); + + if (!renderTexture) + { + // temporary bypass cache.. + // internally - this will cause a texture to be bound.. + renderTexture = RenderTexture.create({ + width: minWidth, + height: minHeight, + resolution, + }); + } + + return renderTexture; + } + + /** + * Gets extra render texture to use inside current filter + * + * @param {number} resolution resolution of the renderTexture + * @returns {PIXI.RenderTexture} + */ + getFilterTexture(resolution) + { + const rt = this.activeState.renderTexture; + + const filterTexture = this.getPotFilterTexture(rt.width, rt.height, resolution || rt.baseTexture.resolution); + + filterTexture.filterFrame = rt.filterFrame; + + return filterTexture; + } + + /** + * Frees a render texture back into the pool. + * + * @param {PIXI.RenderTarget} renderTexture - The renderTarget to free + */ + returnFilterTexture(renderTexture) + { + renderTexture.filterFrame = null; + + const base = renderTexture.baseTexture; + + const minWidth = base.width; + const minHeight = base.height; + + const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + + this.texturePool[key].push(renderTexture); + } + + /** + * Empties the texture pool. + * + */ + emptyPool() + { + for (const i in this.texturePool) + { + const textures = this.texturePool[i]; + + if (textures) + { + for (let j = 0; j < textures.length; j++) + { + textures[j].destroy(true); + } + } + } + + this.texturePool = {}; + } +} diff --git a/packages/core/src/filters/defaultFilter.frag b/packages/core/src/filters/defaultFilter.frag new file mode 100644 index 0000000..a14c1be --- /dev/null +++ b/packages/core/src/filters/defaultFilter.frag @@ -0,0 +1,22 @@ +varying vec2 vTextureCoord; +varying vec2 vFilterCoord; + +uniform sampler2D uSampler; +uniform sampler2D filterSampler; + +void main(void){ + vec4 masky = texture2D(filterSampler, vFilterCoord); + vec4 sample = texture2D(uSampler, vTextureCoord); + vec4 color; + if(mod(vFilterCoord.x, 1.0) > 0.5) + { + color = vec4(1.0, 0.0, 0.0, 1.0); + } + else + { + color = vec4(0.0, 1.0, 0.0, 1.0); + } + // gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0); + gl_FragColor = mix(sample, masky, 0.5); + gl_FragColor *= sample.a; +} diff --git a/packages/core/src/filters/defaultFilter.vert b/packages/core/src/filters/defaultFilter.vert new file mode 100644 index 0000000..e1febca --- /dev/null +++ b/packages/core/src/filters/defaultFilter.vert @@ -0,0 +1,14 @@ +attribute vec2 aVertexPosition; +attribute vec2 aTextureCoord; + +uniform mat3 projectionMatrix; +uniform mat3 filterMatrix; + +varying vec2 vTextureCoord; +varying vec2 vFilterCoord; + +void main(void){ + gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); + vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy; + vTextureCoord = aTextureCoord ; +} diff --git a/packages/core/src/filters/filterTransforms.js b/packages/core/src/filters/filterTransforms.js new file mode 100644 index 0000000..87a3cd6 --- /dev/null +++ b/packages/core/src/filters/filterTransforms.js @@ -0,0 +1,50 @@ +import { Matrix } from '@pixi/math'; + +/** + * Calculates the mapped matrix + * @param filterArea {Rectangle} The filter area + * @param sprite {Sprite} the target sprite + * @param outputMatrix {Matrix} @alvin + * @private + */ +// this returns a matrix that will normalise map filter cords in the filter to screen space +export function calculateScreenSpaceMatrix(outputMatrix, filterArea, textureSize) +{ + // TODO unwrap? + const mappedMatrix = outputMatrix.identity(); + + mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); + + mappedMatrix.scale(textureSize.width, textureSize.height); + + return mappedMatrix; +} + +export function calculateNormalizedScreenSpaceMatrix(outputMatrix, filterArea, textureSize) +{ + const mappedMatrix = outputMatrix.identity(); + + mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); + + const translateScaleX = (textureSize.width / filterArea.width); + const translateScaleY = (textureSize.height / filterArea.height); + + mappedMatrix.scale(translateScaleX, translateScaleY); + + return mappedMatrix; +} + +// this will map the filter coord so that a texture can be used based on the transform of a sprite +export function calculateSpriteMatrix(outputMatrix, filterArea, textureSize, sprite) +{ + const orig = sprite._texture.orig; + const mappedMatrix = outputMatrix.set(textureSize.width, 0, 0, textureSize.height, filterArea.x, filterArea.y); + const worldTransform = sprite.worldTransform.copyTo(Matrix.TEMP_MATRIX); + + worldTransform.invert(); + mappedMatrix.prepend(worldTransform); + mappedMatrix.scale(1.0 / orig.width, 1.0 / orig.height); + mappedMatrix.translate(sprite.anchor.x, sprite.anchor.y); + + return mappedMatrix; +} diff --git a/packages/core/src/filters/spriteMask/SpriteMaskFilter.js b/packages/core/src/filters/spriteMask/SpriteMaskFilter.js new file mode 100644 index 0000000..91697b7 --- /dev/null +++ b/packages/core/src/filters/spriteMask/SpriteMaskFilter.js @@ -0,0 +1,63 @@ +import Filter from '../Filter'; +import { Matrix } from '@pixi/math'; +import vertex from './spriteMaskFilter.vert'; +import fragment from './spriteMaskFilter.frag'; +import { default as TextureMatrix } from '../../textures/TextureMatrix'; + +/** + * The SpriteMaskFilter class + * + * @class + * @extends PIXI.Filter + * @memberof PIXI + */ +export default class SpriteMaskFilter extends Filter +{ + /** + * @param {PIXI.Sprite} sprite - the target sprite + */ + constructor(sprite) + { + const maskMatrix = new Matrix(); + + super(vertex, fragment); + + sprite.renderable = false; + + this.maskSprite = sprite; + this.maskMatrix = maskMatrix; + } + + /** + * Applies the filter + * + * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + */ + apply(filterManager, input, output) + { + const maskSprite = this.maskSprite; + const tex = this.maskSprite.texture; + + if (!tex.valid) + { + return; + } + if (!tex.transform) + { + // margin = 0.0, let it bleed a bit, shader code becomes easier + // assuming that atlas textures were made with 1-pixel padding + tex.transform = new TextureMatrix(tex, 0.0); + } + tex.transform.update(); + + this.uniforms.mask = tex; + this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite) + .prepend(tex.transform.mapCoord); + this.uniforms.alpha = maskSprite.worldAlpha; + this.uniforms.maskClamp = tex.transform.uClampFrame; + + filterManager.applyFilter(this, input, output); + } +} diff --git a/packages/core/src/AbstractRenderer.js b/packages/core/src/AbstractRenderer.js new file mode 100644 index 0000000..64b2eb1 --- /dev/null +++ b/packages/core/src/AbstractRenderer.js @@ -0,0 +1,353 @@ +import { hex2string, hex2rgb } from '@pixi/utils'; +import { Matrix, Rectangle } from '@pixi/math'; +import { RENDERER_TYPE } from '@pixi/constants'; +import { settings } from '@pixi/settings'; +import { Container } from '@pixi/display'; +import RenderTexture from './renderTexture/RenderTexture'; +import EventEmitter from 'eventemitter3'; + +const tempMatrix = new Matrix(); + +/** + * 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 EventEmitter + * @memberof PIXI + */ +export default class AbstractRenderer extends EventEmitter +{ + // eslint-disable-next-line valid-jsdoc + /** + * @param {string} system - The name of the system this renderer is for. + * @param {object} [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.transparent=false] - If the render view is transparent, default false + * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false + * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. The + * resolution of the renderer retina would be 2. + * @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 {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when rendering, + * stopping pixel interpolation. + */ + constructor(system, options, arg2, arg3) + { + super(); + + // Support for constructor(system, screenWidth, screenHeight, options) + if (typeof options === 'number') + { + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + }, arg3); + } + + // 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 = RENDERER_TYPE.UNKNOWN; + + /** + * Measurements of the screen. (0, 0, screenWidth, screenHeight) + * + * Its safe to use as filterArea or hitArea for 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 1 + */ + this.resolution = options.resolution || settings.RESOLUTION; + + /** + * Whether the render view is transparent + * + * @member {boolean} + */ + this.transparent = options.transparent; + + /** + * Whether css dimensions of canvas view should be resized to screen dimensions automatically + * + * @member {boolean} + */ + this.autoResize = options.autoResize || false; + + /** + * Tracks the blend modes useful for this renderer. + * + * @member {object} + */ + this.blendModes = null; + + /** + * 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; + + /** + * If true PixiJS will Math.floor() x/y values when rendering, stopping pixel interpolation. + * Handy for crisp pixel art and speed on legacy devices. + * + * @member {boolean} + */ + this.roundPixels = options.roundPixels; + + /** + * The background color as a number. + * + * @member {number} + * @private + */ + this._backgroundColor = 0x000000; + + /** + * The background color as an [R, G, B] array. + * + * @member {number[]} + * @private + */ + this._backgroundColorRgba = [0, 0, 0, 0]; + + /** + * The background color as a string. + * + * @member {string} + * @private + */ + this._backgroundColorString = '#000000'; + + this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter + + /** + * This temporary display object used as the parent of the currently being rendered item + * + * @member {PIXI.DisplayObject} + * @private + */ + this._tempDisplayObjectParent = new Container(); + + /** + * The last root object that the renderer tried to render. + * + * @member {PIXI.DisplayObject} + * @private + */ + this._lastObjectRendered = this._tempDisplayObjectParent; + + /** + * Collection of plugins. + * @readonly + * @member {object} + */ + this.plugins = {}; + } + + /** + * Initialize the plugins. + * + * @protected + * @param {object} staticMap - The dictionary of staticly saved plugins. + */ + initPlugins(staticMap) + { + 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() + { + return this.view.width; + } + + /** + * Same as view.height, actual number of pixels in the canvas by vertical + * + * @member {number} + * @readonly + * @default 600 + */ + get height() + { + return this.view.height; + } + + /** + * Resizes the screen and canvas to the specified width and height + * Canvas dimensions are multiplied by resolution + * + * @param {number} screenWidth - the new width of the screen + * @param {number} screenHeight - the new height of the screen + */ + resize(screenWidth, screenHeight) + { + this.screen.width = screenWidth; + this.screen.height = screenHeight; + + this.view.width = screenWidth * this.resolution; + this.view.height = screenHeight * this.resolution; + + if (this.autoResize) + { + this.view.style.width = `${screenWidth}px`; + this.view.style.height = `${screenHeight}px`; + } + } + + /** + * 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 {PIXI.DisplayObject} displayObject - The displayObject the object will be generated from + * @param {number} scaleMode - Should be one of the scaleMode consts + * @param {number} resolution - The resolution / device pixel ratio of the texture being generated + * @param {PIXI.Rectangle} [region] - The region of the displayObject, that shall be rendered, + * if no region is specified, defaults to the local bounds of the displayObject. + * @return {PIXI.Texture} a texture of the graphics object + */ + generateTexture(displayObject, scaleMode, resolution, region) + { + region = region || displayObject.getLocalBounds(); + + // 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(region.width | 0, region.height | 0, scaleMode, resolution); + + tempMatrix.tx = -region.x; + tempMatrix.ty = -region.y; + + this.render(displayObject, renderTexture, false, tempMatrix, true); + + return renderTexture; + } + + /** + * Removes everything from the renderer and optionally removes the Canvas DOM element. + * + * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. + */ + destroy(removeView) + { + 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); + } + + this.plugins = null; + + this.type = RENDERER_TYPE.UNKNOWN; + + this.view = null; + + this.screen = null; + + this.resolution = 0; + + this.transparent = false; + + this.autoResize = false; + + this.blendModes = null; + + this.options = null; + + this.preserveDrawingBuffer = false; + this.clearBeforeRender = false; + + this.roundPixels = false; + + this._backgroundColor = 0; + this._backgroundColorRgba = null; + this._backgroundColorString = null; + + this._tempDisplayObjectParent = null; + this._lastObjectRendered = null; + } + + /** + * The background color to fill if not transparent + * + * @member {number} + */ + get backgroundColor() + { + return this._backgroundColor; + } + + set backgroundColor(value) // eslint-disable-line require-jsdoc + { + this._backgroundColor = value; + this._backgroundColorString = hex2string(value); + hex2rgb(value, this._backgroundColorRgba); + } +} diff --git a/packages/core/src/Renderer.js b/packages/core/src/Renderer.js new file mode 100644 index 0000000..2227e78 --- /dev/null +++ b/packages/core/src/Renderer.js @@ -0,0 +1,338 @@ +import AbstractRenderer from './AbstractRenderer'; +import { sayHello } from '@pixi/utils'; +import MaskSystem from './mask/MaskSystem'; +import StencilSystem from './mask/StencilSystem'; +import FilterSystem from './filters/FilterSystem'; +import FramebufferSystem from './framebuffer/FramebufferSystem'; +import RenderTextureSystem from './renderTexture/RenderTextureSystem'; +import TextureSystem from './textures/TextureSystem'; +import ProjectionSystem from './projection/ProjectionSystem'; +import StateSystem from './state/StateSystem'; +import GeometrySystem from './geometry/GeometrySystem'; +import ShaderSystem from './shader/ShaderSystem'; +import ContextSystem from './context/ContextSystem'; +import BatchSystem from './batch/BatchSystem'; +import TextureGCSystem from './textures/TextureGCSystem'; +import { RENDERER_TYPE } from '@pixi/constants'; +import UniformGroup from './shader/UniformGroup'; +import { Matrix } from '@pixi/math'; +import Runner from 'mini-runner'; + +/** + * 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 Render works by automatically managing webGLBatchs. + * 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 :) + * + * @class + * @memberof PIXI + * @extends PIXI.AbstractRenderer + */ +export default class Renderer extends AbstractRenderer +{ + // eslint-disable-next-line valid-jsdoc + /** + * + * @param {object} [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.transparent=false] - If the render view is transparent, default false + * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false + * @param {boolean} [options.antialias=false] - sets antialias. If not available natively then FXAA + * antialiasing is used + * @param {boolean} [options.forceFXAA=false] - forces FXAA antialiasing to be used over native. + * FXAA is faster, but may not always look as great + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. + * The resolution of the renderer retina would be 2. + * @param {boolean} [options.clearBeforeRender=true] - This sets if the renderer will clear + * the canvas or not before the new render pass. If you wish to set this to false, you *must* set + * preserveDrawingBuffer to `true`. + * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, + * enable this if you need to call toDataUrl on the webgl context. + * @param {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when + * rendering, stopping pixel interpolation. + * @param {number} [options.backgroundColor=0x000000] - The background color of the rendered area + * (shown if not transparent). + * @param {boolean} [options.legacy=false] - If true PixiJS will aim to ensure compatibility + * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. + * @param {string} [options.powerPreference] - Parameter passed to webgl context, set to "high-performance" + * for devices with dual graphics card + */ + constructor(options, arg2, arg3) + { + super('WebGL', options, arg2, arg3); + + /** + * The type of this renderer as a standardised const + * + * @member {number} + * @see PIXI.RENDERER_TYPE + */ + this.type = RENDERER_TYPE.WEBGL; + + // this will be set by the contextSystem (this.context) + this.gl = null; + this.CONTEXT_UID = 0; + this.legacy = !!options.legacy; + + // TODO legacy! + + // runners! + this.runners = { + destroy: new Runner('destroy'), + contextChange: new Runner('contextChange', 1), + reset: new Runner('reset'), + update: new Runner('update'), + postrender: new Runner('postrender'), + prerender: new Runner('prerender'), + resize: new Runner('resize', 2), + }; + + 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(GeometrySystem, 'geometry') + .addSystem(FramebufferSystem, 'framebuffer') + .addSystem(StencilSystem, 'stencil') + .addSystem(ProjectionSystem, 'projection') + .addSystem(TextureGCSystem, 'textureGC') + .addSystem(FilterSystem, 'filter') + .addSystem(RenderTextureSystem, 'renderTexture') + .addSystem(BatchSystem, 'batch'); + + this.initPlugins(Renderer.__plugins); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ + if (options.context) + { + this.context.initFromContext(options.context); + } + else + { + this.context.initFromOptions({ + alpha: this.transparent, + antialias: options.antialias, + premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', + stencil: true, + 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); + } + + /** + * Add a new system to the renderer. + * @param {class} ClassRef - Class reference + * @param {string} [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. + * @return {PIXI.Renderer} Return instance of renderer + */ + addSystem(ClassRef, name) + { + if (!name) + { + name = ClassRef.name; + } + + // TODO - read name from class.name.. + + /* + if(name.includes('System')) + { + name = name.replace('System', ''); + name = name.charAt(0).toLowerCase() + name.slice(1); + } + */ + + const system = new ClassRef(this); + + if (this[name]) + { + throw new Error(`Whoops! ${name} is already a manger`); + } + + this[name] = system; + + for (const i in this.runners) + { + this.runners[i].add(system); + } + + return this; + + /** + * 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. + */ + } + + /** + * Renders the object to its webGL view + * + * @param {PIXI.DisplayObject} displayObject - the object to be rendered + * @param {PIXI.RenderTexture} renderTexture - The render texture to render to. + * @param {boolean} [clear] - Should the canvas be cleared before the new render + * @param {PIXI.Transform} [transform] - A transform to apply to the render texture before rendering. + * @param {boolean} [skipUpdateTransform] - Should we skip the update transform pass? + */ + render(displayObject, renderTexture, clear, transform, skipUpdateTransform) + { + // can be handy to know! + this.renderingToScreen = !renderTexture; + + this.runners.prerender.run(); + this.emit('prerender'); + + // 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.parent; + + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = 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.run(); + + this.emit('postrender'); + } + + /** + * Resizes the webGL view to the specified width and height. + * + * @param {number} screenWidth - the new width of the screen + * @param {number} screenHeight - the new height of the screen + */ + resize(screenWidth, screenHeight) + { + AbstractRenderer.prototype.resize.call(this, screenWidth, screenHeight); + + this.runners.resize.run(screenWidth, screenHeight); + } + + /** + * Resets the WebGL state so you can render things however you fancy! + * + * @return {PIXI.Renderer} Returns itself. + */ + reset() + { + this.runners.reset.run(); + + return this; + } + + clear() + { + this.framebuffer.bind(); + this.framebuffer.clear(); + } + + /** + * Removes everything from the renderer (event listeners, spritebatch, etc...) + * + * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. + * See: https://github.com/pixijs/pixi.js/issues/2233 + */ + destroy(removeView) + { + this.runners.destroy.run(); + + // call base destroy + super.destroy(removeView); + + // TODO nullify all the managers.. + this.gl = null; + } + + /** + * 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 + * builds and excluding plugins. + * @name PIXI.Renderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. + */ + + /** + * Adds a plugin to the renderer. + * + * @method + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + static registerPlugin(pluginName, ctor) + { + Renderer.__plugins = Renderer.__plugins || {}; + Renderer.__plugins[pluginName] = ctor; + } +} diff --git a/packages/core/src/System.js b/packages/core/src/System.js new file mode 100644 index 0000000..05a10c8 --- /dev/null +++ b/packages/core/src/System.js @@ -0,0 +1,42 @@ +/** + * System is a base class used for extending systems used by the {@link PIXI.Renderer} + * @see PIXI.Renderer#addSystem + * @class + * @memberof PIXI + */ +export default class System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + /** + * The renderer this manager works for. + * + * @member {PIXI.Renderer} + */ + this.renderer = renderer; + + this.renderer.runners.contextChange.add(this); + } + + /** + * Generic method called when there is a WebGL context change. + * + */ + contextChange() + { + // do some codes init! + } + + /** + * Generic destroy methods to be overridden by the subclass + * + */ + destroy() + { + this.renderer.runners.contextChange.remove(this); + this.renderer = null; + } +} diff --git a/packages/core/src/batch/BatchSystem.js b/packages/core/src/batch/BatchSystem.js new file mode 100644 index 0000000..b04c249 --- /dev/null +++ b/packages/core/src/batch/BatchSystem.js @@ -0,0 +1,68 @@ +import System from '../System'; +import ObjectRenderer from './ObjectRenderer'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class BatchSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + super(renderer); + + /** + * An empty renderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.emptyRenderer = new ObjectRenderer(renderer); + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.currentRenderer = this.emptyRenderer; + } + + /** + * Changes the current renderer to the one given in parameter + * + * @param {PIXI.ObjectRenderer} objectRenderer - The object renderer to use. + */ + setObjectRenderer(objectRenderer) + { + if (this.currentRenderer === objectRenderer) + { + return; + } + + this.currentRenderer.stop(); + this.currentRenderer = objectRenderer; + + this.renderer.state.setState(objectRenderer.state); + + this.currentRenderer.start(); + } + + /** + * This should be called if you wish to do some custom rendering + * It will basically render anything that may be batched up such as sprites + * + */ + flush() + { + this.setObjectRenderer(this.emptyRenderer); + } + + reset() + { + this.setObjectRenderer(this.emptyRenderer); + } +} diff --git a/packages/core/src/batch/ObjectRenderer.js b/packages/core/src/batch/ObjectRenderer.js new file mode 100644 index 0000000..0c851a9 --- /dev/null +++ b/packages/core/src/batch/ObjectRenderer.js @@ -0,0 +1,48 @@ +import System from '../System'; + +/** + * Base for a common object renderer that can be used as a system renderer plugin. + * + * @class + * @extends PIXI.System + * @memberof PIXI + */ +export default class ObjectRenderer extends System +{ + /** + * Starts the renderer and sets the shader + * + */ + start() + { + // set the shader.. + } + + /** + * Stops the renderer + * + */ + stop() + { + this.flush(); + } + + /** + * Stub method for rendering content and emptying the current batch. + * + */ + flush() + { + // flush! + } + + /** + * Renders an object + * + * @param {PIXI.DisplayObject} object - The object to render. + */ + render(object) // eslint-disable-line no-unused-vars + { + // render the object + } +} diff --git a/packages/core/src/context/ContextSystem.js b/packages/core/src/context/ContextSystem.js new file mode 100644 index 0000000..301aab4 --- /dev/null +++ b/packages/core/src/context/ContextSystem.js @@ -0,0 +1,186 @@ +import System from '../System'; +import { settings } from '@pixi/settings'; + +let CONTEXT_UID = 0; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class ContextSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.webGLVersion = 1; + + this.handleContextLost = this.handleContextLost.bind(this); + this.handleContextRestored = this.handleContextRestored.bind(this); + + this.extensions = {}; + + renderer.view.addEventListener('webglcontextlost', this.handleContextLost, false); + renderer.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + } + + get isLost() + { + return (!this.gl || this.gl.isContextLost()); + } + + contextChange(gl) + { + this.gl = gl; + + // restore a context if it was previously lost + if (gl.isContextLost() && gl.getExtension('WEBGL_lose_context')) + { + gl.getExtension('WEBGL_lose_context').restoreContext(); + } + } + + initFromContext(gl) + { + this.gl = gl; + this.validateContext(gl); + this.renderer.gl = gl; + this.renderer.CONTEXT_UID = CONTEXT_UID++; + this.renderer.runners.contextChange.run(gl); + } + + initFromOptions(options) + { + const gl = this.createContext(this.renderer.view, options); + + this.initFromContext(gl); + } + + /** + * Helper class to create a webGL Context + * + * @class + * @memberof PIXI.glCore + * @param canvas {HTMLCanvasElement} the canvas element that we will get the context from + * @param options {Object} An options object that gets passed in to the canvas element containing the context attributes, + * see https://developer.mozilla.org/en/docs/Web/API/HTMLCanvasElement/getContext for the options available + * @return {WebGLRenderingContext} the WebGL context + */ + createContext(canvas, options) + { + let gl; + + if (settings.PREFER_WEBGL_2) + { + gl = canvas.getContext('webgl2', options); + } + + if (gl) + { + this.webGLVersion = 2; + } + else + { + this.webGLVersion = 1; + + gl = canvas.getContext('webgl', options) + || canvas.getContext('experimental-webgl', options); + + if (!gl) + { + // fail, not able to get a context + throw new Error('This browser does not support webGL. Try using the canvas renderer'); + } + } + + this.gl = gl; + + this.getExtensions(); + + return gl; + } + + getExtensions() + { + // time to set up default etensions that pixi uses.. + const gl = this.gl; + const extensions = this.extensions; + + if (this.webGLVersion === 1) + { + extensions.drawBuffers = gl.getExtension('WEBGL_draw_buffers'); + extensions.depthTexture = gl.getExtension('WEBKIT_WEBGL_depth_texture'); + extensions.floatTexture = gl.getExtension('OES_texture_float'); + extensions.loseContext = gl.getExtension('WEBGL_lose_context'); + + extensions.vertexArrayObject = gl.getExtension('OES_vertex_array_object') + || gl.getExtension('MOZ_OES_vertex_array_object') + || gl.getExtension('WEBKIT_OES_vertex_array_object'); + } + + // we don't use any specific WebGL 2 ones yet! + } + + /** + * Handles a lost webgl context + * + * @private + * @param {WebGLContextEvent} event - The context lost event. + */ + handleContextLost(event) + { + event.preventDefault(); + } + + /** + * Handles a restored webgl context + * + * @private + */ + handleContextRestored() + { + this.renderer.runners.contextChange.run(this.gl); + } + + destroy() + { + const view = this.renderer.view; + + // remove listeners + view.removeEventListener('webglcontextlost', this.handleContextLost); + view.removeEventListener('webglcontextrestored', this.handleContextRestored); + + this.gl.useProgram(null); + + if (this.extensions.loseContext) + { + this.extensions.loseContext.loseContext(); + } + } + + postrender() + { + this.gl.flush(); + } + + validateContext(gl) + { + const attributes = gl.getContextAttributes(); + + // this is going to be fairly simple for now.. but at least we have room to grow! + if (!attributes.stencil) + { + /* eslint-disable max-len */ + + /* eslint-disable no-console */ + console.warn('Provided WebGL context does not have a stencil buffer, masks may not render correctly'); + /* eslint-enable no-console */ + + /* eslint-enable max-len */ + } + } +} diff --git a/packages/core/src/filters/Filter.js b/packages/core/src/filters/Filter.js new file mode 100644 index 0000000..9d8cad8 --- /dev/null +++ b/packages/core/src/filters/Filter.js @@ -0,0 +1,140 @@ +import Shader from '../shader/Shader'; +import Program from '../shader/Program'; +import State from '../state/State'; +import { settings } from '@pixi/settings'; +// import extractUniformsFromSrc from './extractUniformsFromSrc'; +import defaultVertex from './defaultFilter.vert'; +import defaultFragment from './defaultFilter.frag'; + +// let math = require('../../math'); +/** + * @class + * @memberof PIXI + * @extends PIXI.Shader + */ +export default class Filter extends Shader +{ + /** + * @param {string} [vertexSrc] - The source of the vertex shader. + * @param {string} [fragmentSrc] - The source of the fragment shader. + * @param {object} [uniforms] - Custom uniforms to use to augment the built-in ones. + */ + constructor(vertexSrc, fragmentSrc, uniforms) + { + const program = Program.from(vertexSrc, fragmentSrc); + + super(program, uniforms); + + /** + * The padding of the filter. Some filters require extra space to breath such as a blur. + * Increasing this will add extra width and height to the bounds of the object that the + * filter is applied to. + * + * @member {number} + */ + this.padding = 0; + + /** + * The resolution of the filter. Setting this to be lower will lower the quality but + * increase the performance of the filter. + * + * @member {number} + */ + this.resolution = settings.RESOLUTION; + + /** + * If enabled is true the filter is applied, if false it will not. + * + * @member {boolean} + */ + this.enabled = true; + + /** + * If enabled, PixiJS will fit the filter area into boundaries for better performance. + * Switch it off if it does not work for specific shader. + * + * @member {boolean} + */ + this.autoFit = true; + + /** + * Legacy filters use position and uvs from attributes + * @member {boolean} + * @readonly + */ + this.legacy = !!this.program.attributeData.aTextureCoord; + + /** + * the webGL state the filter requires to render + * @member {PIXI.State} + */ + this.state = new State(); + } + + /** + * Applies the filter + * + * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + * @param {boolean} clear - Should the output be cleared before rendering to it + * @param {object} [currentState] - It's current state of filter. + * There are some useful properties in the currentState : + * target, filters, sourceFrame, destinationFrame, renderTarget, resolution + */ + apply(filterManager, input, output, clear, currentState, derp) // eslint-disable-line no-unused-vars + { + // do as you please! + + filterManager.applyFilter(this, input, output, clear, currentState, derp); + + // or just do a regular render.. + } + + /** + * Sets the blendmode of the filter + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL + */ + get blendMode() + { + return this.state.blendMode; + } + + set blendMode(value) // eslint-disable-line require-jsdoc + { + this.state.blendMode = value; + } + + /** + * The default vertex shader source + * + * @static + * @constant + */ + static get defaultVertexSrc() + { + return defaultVertex; + } + + /** + * The default fragment shader source + * + * @static + * @constant + */ + static get defaultFragmentSrc() + { + return defaultFragment; + } +} + +/** + * Used for caching shader IDs + * + * @static + * @private + */ +Filter.SOURCE_KEY_MAP = {}; + diff --git a/packages/core/src/filters/FilterSystem.js b/packages/core/src/filters/FilterSystem.js new file mode 100644 index 0000000..3174318 --- /dev/null +++ b/packages/core/src/filters/FilterSystem.js @@ -0,0 +1,431 @@ +import System from '../System'; + +import RenderTexture from '../renderTexture/RenderTexture'; +import Quad from '../utils/Quad'; +import QuadUv from '../utils/QuadUv'; +import { Rectangle } from '@pixi/math'; +import * as filterTransforms from './filterTransforms'; +import bitTwiddle from 'bit-twiddle'; +import UniformGroup from '../shader/UniformGroup'; +import { DRAW_MODES } from '@pixi/constants'; + +// +/** + * @ignore + * @class + */ +class FilterState +{ + /** + * + */ + constructor() + { + this.renderTexture = null; + this.sourceFrame = new Rectangle(); + this.destinationFrame = new Rectangle(); + this.filters = []; + this.target = null; + this.legacy = false; + this.resolution = 1; + } +} + +/** + * @class + * @memberof PIXI.systems + * @extends PIXI.System + */ +export default class FilterSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + /** + * stores a bunch of PO2 textures used for filtering + * @type {Object} + */ + this.texturePool = {}; + + /** + * a pool for storing filter states, save us creating new ones each tick + * @type {Array} + */ + this.statePool = []; + + /** + * A very simple geometry used when drawing a filter effect to the screen + * @type {Quad} + */ + this.quad = new Quad(); + + this.quadUv = new QuadUv(); + + /** + * Temporary rect for maths + * @type {PIXI.Rectangle} + */ + this.tempRect = new Rectangle(); + + this.activeState = {}; + + /** + * this uniform group is attached to filter uniforms when used + * @type {UniformGroup} + */ + this.globalUniforms = new UniformGroup({ + sourceFrame: this.tempRect, + destinationFrame: this.tempRect, + + // legacy variables + filterArea: new Float32Array(4), + filterClamp: new Float32Array(4), + }, true); + } + + /** + * Adds a new filter to the System. + * + * @param {PIXI.DisplayObject} target - The target of the filter to render. + * @param {PIXI.Filter[]} filters - The filters to apply. + */ + push(target, filters) + { + const renderer = this.renderer; + const filterStack = this.renderer.renderTexture.defaultFilterStack; + const state = this.statePool.pop() || new FilterState(); + + let resolution = filters[0].resolution; + let padding = filters[0].padding; + let autoFit = filters[0].autoFit; + let legacy = filters[0].legacy; + + for (let i = 1; i < filters.length; i++) + { + const filter = filters[i]; + + // lets use the lowest resolution.. + resolution = Math.min(resolution, filter.resolution); + // and the largest amount of padding! + padding = Math.max(padding, filter.padding); + // only auto fit if all filters are autofit + autoFit = autoFit || filter.autoFit; + + legacy = legacy || filter.legacy; + } + + filterStack.push(state); + + state.resolution = resolution; + + state.legacy = legacy; + + // round to whole number based on resolution + // TODO move that to the shader too? + state.sourceFrame = target.filterArea || target.getBounds(true); + + state.sourceFrame.pad(padding); + + if (autoFit) + { + state.sourceFrame.fit(this.renderer.renderTexture.destinationFrame); + } + + state.sourceFrame.round(resolution); + + state.renderTexture = this.getPotFilterTexture(state.sourceFrame.width, state.sourceFrame.height, resolution); + state.filters = filters; + + state.destinationFrame.width = state.renderTexture.width; + state.destinationFrame.height = state.renderTexture.height; + + state.renderTexture.filterFrame = state.sourceFrame; + renderer.renderTexture.bind(state.renderTexture, state.sourceFrame);// /, state.destinationFrame); + renderer.renderTexture.clear(); + } + + /** + * Pops off the filter and applies it. + * + */ + pop() + { + const renderer = this.renderer; + const filterStack = renderer.renderTexture.defaultFilterStack; + const state = filterStack.pop(); + const filters = state.filters; + + this.activeState = state; + + const globalUniforms = this.globalUniforms.uniforms; + + globalUniforms.sourceFrame = state.sourceFrame; + globalUniforms.destinationFrame = state.destinationFrame; + globalUniforms.resolution = state.resolution; + + // only update the rect if its legacy.. + if (state.legacy) + { + const filterArea = globalUniforms.filterArea; + const filterClamp = globalUniforms.filterClamp; + + filterArea[0] = state.destinationFrame.width; + filterArea[1] = state.destinationFrame.height; + filterArea[2] = state.sourceFrame.x; + filterArea[3] = state.sourceFrame.y; + + filterClamp[0] = 0.5 / state.resolution / state.destinationFrame.width; + filterClamp[1] = 0.5 / state.resolution / state.destinationFrame.height; + filterClamp[2] = (state.sourceFrame.width - 0.5) / state.resolution / state.destinationFrame.width; + filterClamp[3] = (state.sourceFrame.height - 0.5) / state.resolution / state.destinationFrame.height; + } + + this.globalUniforms.update(); + + const lastState = filterStack[filterStack.length - 1]; + + if (filters.length === 1) + { + filters[0].apply(this, state.renderTexture, lastState.renderTexture, false, state); + + this.returnFilterTexture(state.renderTexture); + } + else + { + let flip = state.renderTexture; + let flop = this.getPotFilterTexture( + flip.width, + flip.height, + state.resolution + ); + + flop.filterFrame = flip.filterFrame; + + let i = 0; + + for (i = 0; i < filters.length - 1; ++i) + { + filters[i].apply(this, flip, flop, true, state); + + const t = flip; + + flip = flop; + flop = t; + } + + filters[i].apply(this, flip, lastState.renderTexture, false, state); + + this.returnFilterTexture(flip); + this.returnFilterTexture(flop); + } + + this.statePool.push(state); + } + + /** + * Draws a filter. + * + * @param {PIXI.Filter} filter - The filter to draw. + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + * @param {boolean} clear - Should the output be cleared before rendering to it + */ + applyFilter(filter, input, output, clear) + { + const renderer = this.renderer; + + renderer.renderTexture.bind(output, output ? output.filterFrame : null); + + if (clear) + { + // gl.disable(gl.SCISSOR_TEST); + renderer.renderTexture.clear(); + // gl.enable(gl.SCISSOR_TEST); + } + + // set the uniforms.. + filter.uniforms.uSampler = input; + filter.uniforms.filterGlobals = this.globalUniforms; + + // TODO make it so that the order of this does not matter.. + // because it does at the moment cos of global uniforms. + // they need to get resynced + + renderer.state.setState(filter.state); + renderer.shader.bind(filter); + + if (filter.legacy) + { + this.quadUv.map(input._frame, input.filterFrame); + + renderer.geometry.bind(this.quadUv); + renderer.geometry.draw(DRAW_MODES.TRIANGLES); + } + else + { + renderer.geometry.bind(this.quad); + renderer.geometry.draw(DRAW_MODES.TRIANGLE_STRIP); + } + } + + /** + * Calculates the mapped matrix. + * + * TODO playing around here.. this is temporary - (will end up in the shader) + * this returns a matrix that will normalise map filter cords in the filter to screen space + * + * @param {PIXI.Matrix} outputMatrix - the matrix to output to. + * @return {PIXI.Matrix} The mapped matrix. + */ + calculateScreenSpaceMatrix(outputMatrix) + { + const currentState = this.activeState; + + return filterTransforms.calculateScreenSpaceMatrix( + outputMatrix, + currentState.sourceFrame, + currentState.destinationFrame + ); + } + + /** + * This will map the filter coord so that a texture can be used based on the transform of a sprite + * + * @param {PIXI.Matrix} outputMatrix - The matrix to output to. + * @param {PIXI.Sprite} sprite - The sprite to map to. + * @return {PIXI.Matrix} The mapped matrix. + */ + calculateSpriteMatrix(outputMatrix, sprite) + { + const currentState = this.activeState; + + return filterTransforms.calculateSpriteMatrix( + outputMatrix, + currentState.sourceFrame, + currentState.destinationFrame, + sprite + ); + } + + /** + * Destroys this Filter System. + * + * @param {boolean} [contextLost=false] context was lost, do not free shaders + * + */ + destroy(contextLost = false) + { + if (!contextLost) + { + this.emptyPool(); + } + else + { + this.texturePool = {}; + } + } + + /** + * Gets a Power-of-Two render texture. + * + * TODO move to a seperate class could be on renderer? + * also - could cause issue with multiple contexts? + * + * @private + * @param {WebGLRenderingContext} gl - The webgl rendering context + * @param {number} minWidth - The minimum width of the render target. + * @param {number} minHeight - The minimum height of the render target. + * @param {number} resolution - The resolution of the render target. + * @return {PIXI.RenderTarget} The new render target. + */ + getPotFilterTexture(minWidth, minHeight, resolution) + { + minWidth = bitTwiddle.nextPow2(minWidth); + minHeight = bitTwiddle.nextPow2(minHeight); + resolution = resolution || 1; + + const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + + if (!this.texturePool[key]) + { + this.texturePool[key] = []; + } + + let renderTexture = this.texturePool[key].pop(); + + if (!renderTexture) + { + // temporary bypass cache.. + // internally - this will cause a texture to be bound.. + renderTexture = RenderTexture.create({ + width: minWidth, + height: minHeight, + resolution, + }); + } + + return renderTexture; + } + + /** + * Gets extra render texture to use inside current filter + * + * @param {number} resolution resolution of the renderTexture + * @returns {PIXI.RenderTexture} + */ + getFilterTexture(resolution) + { + const rt = this.activeState.renderTexture; + + const filterTexture = this.getPotFilterTexture(rt.width, rt.height, resolution || rt.baseTexture.resolution); + + filterTexture.filterFrame = rt.filterFrame; + + return filterTexture; + } + + /** + * Frees a render texture back into the pool. + * + * @param {PIXI.RenderTarget} renderTexture - The renderTarget to free + */ + returnFilterTexture(renderTexture) + { + renderTexture.filterFrame = null; + + const base = renderTexture.baseTexture; + + const minWidth = base.width; + const minHeight = base.height; + + const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + + this.texturePool[key].push(renderTexture); + } + + /** + * Empties the texture pool. + * + */ + emptyPool() + { + for (const i in this.texturePool) + { + const textures = this.texturePool[i]; + + if (textures) + { + for (let j = 0; j < textures.length; j++) + { + textures[j].destroy(true); + } + } + } + + this.texturePool = {}; + } +} diff --git a/packages/core/src/filters/defaultFilter.frag b/packages/core/src/filters/defaultFilter.frag new file mode 100644 index 0000000..a14c1be --- /dev/null +++ b/packages/core/src/filters/defaultFilter.frag @@ -0,0 +1,22 @@ +varying vec2 vTextureCoord; +varying vec2 vFilterCoord; + +uniform sampler2D uSampler; +uniform sampler2D filterSampler; + +void main(void){ + vec4 masky = texture2D(filterSampler, vFilterCoord); + vec4 sample = texture2D(uSampler, vTextureCoord); + vec4 color; + if(mod(vFilterCoord.x, 1.0) > 0.5) + { + color = vec4(1.0, 0.0, 0.0, 1.0); + } + else + { + color = vec4(0.0, 1.0, 0.0, 1.0); + } + // gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0); + gl_FragColor = mix(sample, masky, 0.5); + gl_FragColor *= sample.a; +} diff --git a/packages/core/src/filters/defaultFilter.vert b/packages/core/src/filters/defaultFilter.vert new file mode 100644 index 0000000..e1febca --- /dev/null +++ b/packages/core/src/filters/defaultFilter.vert @@ -0,0 +1,14 @@ +attribute vec2 aVertexPosition; +attribute vec2 aTextureCoord; + +uniform mat3 projectionMatrix; +uniform mat3 filterMatrix; + +varying vec2 vTextureCoord; +varying vec2 vFilterCoord; + +void main(void){ + gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); + vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy; + vTextureCoord = aTextureCoord ; +} diff --git a/packages/core/src/filters/filterTransforms.js b/packages/core/src/filters/filterTransforms.js new file mode 100644 index 0000000..87a3cd6 --- /dev/null +++ b/packages/core/src/filters/filterTransforms.js @@ -0,0 +1,50 @@ +import { Matrix } from '@pixi/math'; + +/** + * Calculates the mapped matrix + * @param filterArea {Rectangle} The filter area + * @param sprite {Sprite} the target sprite + * @param outputMatrix {Matrix} @alvin + * @private + */ +// this returns a matrix that will normalise map filter cords in the filter to screen space +export function calculateScreenSpaceMatrix(outputMatrix, filterArea, textureSize) +{ + // TODO unwrap? + const mappedMatrix = outputMatrix.identity(); + + mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); + + mappedMatrix.scale(textureSize.width, textureSize.height); + + return mappedMatrix; +} + +export function calculateNormalizedScreenSpaceMatrix(outputMatrix, filterArea, textureSize) +{ + const mappedMatrix = outputMatrix.identity(); + + mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); + + const translateScaleX = (textureSize.width / filterArea.width); + const translateScaleY = (textureSize.height / filterArea.height); + + mappedMatrix.scale(translateScaleX, translateScaleY); + + return mappedMatrix; +} + +// this will map the filter coord so that a texture can be used based on the transform of a sprite +export function calculateSpriteMatrix(outputMatrix, filterArea, textureSize, sprite) +{ + const orig = sprite._texture.orig; + const mappedMatrix = outputMatrix.set(textureSize.width, 0, 0, textureSize.height, filterArea.x, filterArea.y); + const worldTransform = sprite.worldTransform.copyTo(Matrix.TEMP_MATRIX); + + worldTransform.invert(); + mappedMatrix.prepend(worldTransform); + mappedMatrix.scale(1.0 / orig.width, 1.0 / orig.height); + mappedMatrix.translate(sprite.anchor.x, sprite.anchor.y); + + return mappedMatrix; +} diff --git a/packages/core/src/filters/spriteMask/SpriteMaskFilter.js b/packages/core/src/filters/spriteMask/SpriteMaskFilter.js new file mode 100644 index 0000000..91697b7 --- /dev/null +++ b/packages/core/src/filters/spriteMask/SpriteMaskFilter.js @@ -0,0 +1,63 @@ +import Filter from '../Filter'; +import { Matrix } from '@pixi/math'; +import vertex from './spriteMaskFilter.vert'; +import fragment from './spriteMaskFilter.frag'; +import { default as TextureMatrix } from '../../textures/TextureMatrix'; + +/** + * The SpriteMaskFilter class + * + * @class + * @extends PIXI.Filter + * @memberof PIXI + */ +export default class SpriteMaskFilter extends Filter +{ + /** + * @param {PIXI.Sprite} sprite - the target sprite + */ + constructor(sprite) + { + const maskMatrix = new Matrix(); + + super(vertex, fragment); + + sprite.renderable = false; + + this.maskSprite = sprite; + this.maskMatrix = maskMatrix; + } + + /** + * Applies the filter + * + * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + */ + apply(filterManager, input, output) + { + const maskSprite = this.maskSprite; + const tex = this.maskSprite.texture; + + if (!tex.valid) + { + return; + } + if (!tex.transform) + { + // margin = 0.0, let it bleed a bit, shader code becomes easier + // assuming that atlas textures were made with 1-pixel padding + tex.transform = new TextureMatrix(tex, 0.0); + } + tex.transform.update(); + + this.uniforms.mask = tex; + this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite) + .prepend(tex.transform.mapCoord); + this.uniforms.alpha = maskSprite.worldAlpha; + this.uniforms.maskClamp = tex.transform.uClampFrame; + + filterManager.applyFilter(this, input, output); + } +} diff --git a/packages/core/src/filters/spriteMask/spriteMaskFilter.frag b/packages/core/src/filters/spriteMask/spriteMaskFilter.frag new file mode 100644 index 0000000..0e4aef8 --- /dev/null +++ b/packages/core/src/filters/spriteMask/spriteMaskFilter.frag @@ -0,0 +1,23 @@ +varying vec2 vMaskCoord; +varying vec2 vTextureCoord; + +uniform sampler2D uSampler; +uniform sampler2D mask; +uniform float alpha; +uniform vec4 maskClamp; + +void main(void) +{ + float clip = step(3.5, + step(maskClamp.x, vMaskCoord.x) + + step(maskClamp.y, vMaskCoord.y) + + step(vMaskCoord.x, maskClamp.z) + + step(vMaskCoord.y, maskClamp.w)); + + vec4 original = texture2D(uSampler, vTextureCoord); + vec4 masky = texture2D(mask, vMaskCoord); + + original *= (masky.r * masky.a * alpha * clip); + + gl_FragColor = original; +} diff --git a/packages/core/src/AbstractRenderer.js b/packages/core/src/AbstractRenderer.js new file mode 100644 index 0000000..64b2eb1 --- /dev/null +++ b/packages/core/src/AbstractRenderer.js @@ -0,0 +1,353 @@ +import { hex2string, hex2rgb } from '@pixi/utils'; +import { Matrix, Rectangle } from '@pixi/math'; +import { RENDERER_TYPE } from '@pixi/constants'; +import { settings } from '@pixi/settings'; +import { Container } from '@pixi/display'; +import RenderTexture from './renderTexture/RenderTexture'; +import EventEmitter from 'eventemitter3'; + +const tempMatrix = new Matrix(); + +/** + * 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 EventEmitter + * @memberof PIXI + */ +export default class AbstractRenderer extends EventEmitter +{ + // eslint-disable-next-line valid-jsdoc + /** + * @param {string} system - The name of the system this renderer is for. + * @param {object} [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.transparent=false] - If the render view is transparent, default false + * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false + * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. The + * resolution of the renderer retina would be 2. + * @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 {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when rendering, + * stopping pixel interpolation. + */ + constructor(system, options, arg2, arg3) + { + super(); + + // Support for constructor(system, screenWidth, screenHeight, options) + if (typeof options === 'number') + { + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + }, arg3); + } + + // 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 = RENDERER_TYPE.UNKNOWN; + + /** + * Measurements of the screen. (0, 0, screenWidth, screenHeight) + * + * Its safe to use as filterArea or hitArea for 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 1 + */ + this.resolution = options.resolution || settings.RESOLUTION; + + /** + * Whether the render view is transparent + * + * @member {boolean} + */ + this.transparent = options.transparent; + + /** + * Whether css dimensions of canvas view should be resized to screen dimensions automatically + * + * @member {boolean} + */ + this.autoResize = options.autoResize || false; + + /** + * Tracks the blend modes useful for this renderer. + * + * @member {object} + */ + this.blendModes = null; + + /** + * 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; + + /** + * If true PixiJS will Math.floor() x/y values when rendering, stopping pixel interpolation. + * Handy for crisp pixel art and speed on legacy devices. + * + * @member {boolean} + */ + this.roundPixels = options.roundPixels; + + /** + * The background color as a number. + * + * @member {number} + * @private + */ + this._backgroundColor = 0x000000; + + /** + * The background color as an [R, G, B] array. + * + * @member {number[]} + * @private + */ + this._backgroundColorRgba = [0, 0, 0, 0]; + + /** + * The background color as a string. + * + * @member {string} + * @private + */ + this._backgroundColorString = '#000000'; + + this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter + + /** + * This temporary display object used as the parent of the currently being rendered item + * + * @member {PIXI.DisplayObject} + * @private + */ + this._tempDisplayObjectParent = new Container(); + + /** + * The last root object that the renderer tried to render. + * + * @member {PIXI.DisplayObject} + * @private + */ + this._lastObjectRendered = this._tempDisplayObjectParent; + + /** + * Collection of plugins. + * @readonly + * @member {object} + */ + this.plugins = {}; + } + + /** + * Initialize the plugins. + * + * @protected + * @param {object} staticMap - The dictionary of staticly saved plugins. + */ + initPlugins(staticMap) + { + 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() + { + return this.view.width; + } + + /** + * Same as view.height, actual number of pixels in the canvas by vertical + * + * @member {number} + * @readonly + * @default 600 + */ + get height() + { + return this.view.height; + } + + /** + * Resizes the screen and canvas to the specified width and height + * Canvas dimensions are multiplied by resolution + * + * @param {number} screenWidth - the new width of the screen + * @param {number} screenHeight - the new height of the screen + */ + resize(screenWidth, screenHeight) + { + this.screen.width = screenWidth; + this.screen.height = screenHeight; + + this.view.width = screenWidth * this.resolution; + this.view.height = screenHeight * this.resolution; + + if (this.autoResize) + { + this.view.style.width = `${screenWidth}px`; + this.view.style.height = `${screenHeight}px`; + } + } + + /** + * 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 {PIXI.DisplayObject} displayObject - The displayObject the object will be generated from + * @param {number} scaleMode - Should be one of the scaleMode consts + * @param {number} resolution - The resolution / device pixel ratio of the texture being generated + * @param {PIXI.Rectangle} [region] - The region of the displayObject, that shall be rendered, + * if no region is specified, defaults to the local bounds of the displayObject. + * @return {PIXI.Texture} a texture of the graphics object + */ + generateTexture(displayObject, scaleMode, resolution, region) + { + region = region || displayObject.getLocalBounds(); + + // 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(region.width | 0, region.height | 0, scaleMode, resolution); + + tempMatrix.tx = -region.x; + tempMatrix.ty = -region.y; + + this.render(displayObject, renderTexture, false, tempMatrix, true); + + return renderTexture; + } + + /** + * Removes everything from the renderer and optionally removes the Canvas DOM element. + * + * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. + */ + destroy(removeView) + { + 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); + } + + this.plugins = null; + + this.type = RENDERER_TYPE.UNKNOWN; + + this.view = null; + + this.screen = null; + + this.resolution = 0; + + this.transparent = false; + + this.autoResize = false; + + this.blendModes = null; + + this.options = null; + + this.preserveDrawingBuffer = false; + this.clearBeforeRender = false; + + this.roundPixels = false; + + this._backgroundColor = 0; + this._backgroundColorRgba = null; + this._backgroundColorString = null; + + this._tempDisplayObjectParent = null; + this._lastObjectRendered = null; + } + + /** + * The background color to fill if not transparent + * + * @member {number} + */ + get backgroundColor() + { + return this._backgroundColor; + } + + set backgroundColor(value) // eslint-disable-line require-jsdoc + { + this._backgroundColor = value; + this._backgroundColorString = hex2string(value); + hex2rgb(value, this._backgroundColorRgba); + } +} diff --git a/packages/core/src/Renderer.js b/packages/core/src/Renderer.js new file mode 100644 index 0000000..2227e78 --- /dev/null +++ b/packages/core/src/Renderer.js @@ -0,0 +1,338 @@ +import AbstractRenderer from './AbstractRenderer'; +import { sayHello } from '@pixi/utils'; +import MaskSystem from './mask/MaskSystem'; +import StencilSystem from './mask/StencilSystem'; +import FilterSystem from './filters/FilterSystem'; +import FramebufferSystem from './framebuffer/FramebufferSystem'; +import RenderTextureSystem from './renderTexture/RenderTextureSystem'; +import TextureSystem from './textures/TextureSystem'; +import ProjectionSystem from './projection/ProjectionSystem'; +import StateSystem from './state/StateSystem'; +import GeometrySystem from './geometry/GeometrySystem'; +import ShaderSystem from './shader/ShaderSystem'; +import ContextSystem from './context/ContextSystem'; +import BatchSystem from './batch/BatchSystem'; +import TextureGCSystem from './textures/TextureGCSystem'; +import { RENDERER_TYPE } from '@pixi/constants'; +import UniformGroup from './shader/UniformGroup'; +import { Matrix } from '@pixi/math'; +import Runner from 'mini-runner'; + +/** + * 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 Render works by automatically managing webGLBatchs. + * 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 :) + * + * @class + * @memberof PIXI + * @extends PIXI.AbstractRenderer + */ +export default class Renderer extends AbstractRenderer +{ + // eslint-disable-next-line valid-jsdoc + /** + * + * @param {object} [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.transparent=false] - If the render view is transparent, default false + * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false + * @param {boolean} [options.antialias=false] - sets antialias. If not available natively then FXAA + * antialiasing is used + * @param {boolean} [options.forceFXAA=false] - forces FXAA antialiasing to be used over native. + * FXAA is faster, but may not always look as great + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. + * The resolution of the renderer retina would be 2. + * @param {boolean} [options.clearBeforeRender=true] - This sets if the renderer will clear + * the canvas or not before the new render pass. If you wish to set this to false, you *must* set + * preserveDrawingBuffer to `true`. + * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, + * enable this if you need to call toDataUrl on the webgl context. + * @param {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when + * rendering, stopping pixel interpolation. + * @param {number} [options.backgroundColor=0x000000] - The background color of the rendered area + * (shown if not transparent). + * @param {boolean} [options.legacy=false] - If true PixiJS will aim to ensure compatibility + * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. + * @param {string} [options.powerPreference] - Parameter passed to webgl context, set to "high-performance" + * for devices with dual graphics card + */ + constructor(options, arg2, arg3) + { + super('WebGL', options, arg2, arg3); + + /** + * The type of this renderer as a standardised const + * + * @member {number} + * @see PIXI.RENDERER_TYPE + */ + this.type = RENDERER_TYPE.WEBGL; + + // this will be set by the contextSystem (this.context) + this.gl = null; + this.CONTEXT_UID = 0; + this.legacy = !!options.legacy; + + // TODO legacy! + + // runners! + this.runners = { + destroy: new Runner('destroy'), + contextChange: new Runner('contextChange', 1), + reset: new Runner('reset'), + update: new Runner('update'), + postrender: new Runner('postrender'), + prerender: new Runner('prerender'), + resize: new Runner('resize', 2), + }; + + 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(GeometrySystem, 'geometry') + .addSystem(FramebufferSystem, 'framebuffer') + .addSystem(StencilSystem, 'stencil') + .addSystem(ProjectionSystem, 'projection') + .addSystem(TextureGCSystem, 'textureGC') + .addSystem(FilterSystem, 'filter') + .addSystem(RenderTextureSystem, 'renderTexture') + .addSystem(BatchSystem, 'batch'); + + this.initPlugins(Renderer.__plugins); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ + if (options.context) + { + this.context.initFromContext(options.context); + } + else + { + this.context.initFromOptions({ + alpha: this.transparent, + antialias: options.antialias, + premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', + stencil: true, + 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); + } + + /** + * Add a new system to the renderer. + * @param {class} ClassRef - Class reference + * @param {string} [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. + * @return {PIXI.Renderer} Return instance of renderer + */ + addSystem(ClassRef, name) + { + if (!name) + { + name = ClassRef.name; + } + + // TODO - read name from class.name.. + + /* + if(name.includes('System')) + { + name = name.replace('System', ''); + name = name.charAt(0).toLowerCase() + name.slice(1); + } + */ + + const system = new ClassRef(this); + + if (this[name]) + { + throw new Error(`Whoops! ${name} is already a manger`); + } + + this[name] = system; + + for (const i in this.runners) + { + this.runners[i].add(system); + } + + return this; + + /** + * 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. + */ + } + + /** + * Renders the object to its webGL view + * + * @param {PIXI.DisplayObject} displayObject - the object to be rendered + * @param {PIXI.RenderTexture} renderTexture - The render texture to render to. + * @param {boolean} [clear] - Should the canvas be cleared before the new render + * @param {PIXI.Transform} [transform] - A transform to apply to the render texture before rendering. + * @param {boolean} [skipUpdateTransform] - Should we skip the update transform pass? + */ + render(displayObject, renderTexture, clear, transform, skipUpdateTransform) + { + // can be handy to know! + this.renderingToScreen = !renderTexture; + + this.runners.prerender.run(); + this.emit('prerender'); + + // 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.parent; + + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = 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.run(); + + this.emit('postrender'); + } + + /** + * Resizes the webGL view to the specified width and height. + * + * @param {number} screenWidth - the new width of the screen + * @param {number} screenHeight - the new height of the screen + */ + resize(screenWidth, screenHeight) + { + AbstractRenderer.prototype.resize.call(this, screenWidth, screenHeight); + + this.runners.resize.run(screenWidth, screenHeight); + } + + /** + * Resets the WebGL state so you can render things however you fancy! + * + * @return {PIXI.Renderer} Returns itself. + */ + reset() + { + this.runners.reset.run(); + + return this; + } + + clear() + { + this.framebuffer.bind(); + this.framebuffer.clear(); + } + + /** + * Removes everything from the renderer (event listeners, spritebatch, etc...) + * + * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. + * See: https://github.com/pixijs/pixi.js/issues/2233 + */ + destroy(removeView) + { + this.runners.destroy.run(); + + // call base destroy + super.destroy(removeView); + + // TODO nullify all the managers.. + this.gl = null; + } + + /** + * 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 + * builds and excluding plugins. + * @name PIXI.Renderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. + */ + + /** + * Adds a plugin to the renderer. + * + * @method + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + static registerPlugin(pluginName, ctor) + { + Renderer.__plugins = Renderer.__plugins || {}; + Renderer.__plugins[pluginName] = ctor; + } +} diff --git a/packages/core/src/System.js b/packages/core/src/System.js new file mode 100644 index 0000000..05a10c8 --- /dev/null +++ b/packages/core/src/System.js @@ -0,0 +1,42 @@ +/** + * System is a base class used for extending systems used by the {@link PIXI.Renderer} + * @see PIXI.Renderer#addSystem + * @class + * @memberof PIXI + */ +export default class System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + /** + * The renderer this manager works for. + * + * @member {PIXI.Renderer} + */ + this.renderer = renderer; + + this.renderer.runners.contextChange.add(this); + } + + /** + * Generic method called when there is a WebGL context change. + * + */ + contextChange() + { + // do some codes init! + } + + /** + * Generic destroy methods to be overridden by the subclass + * + */ + destroy() + { + this.renderer.runners.contextChange.remove(this); + this.renderer = null; + } +} diff --git a/packages/core/src/batch/BatchSystem.js b/packages/core/src/batch/BatchSystem.js new file mode 100644 index 0000000..b04c249 --- /dev/null +++ b/packages/core/src/batch/BatchSystem.js @@ -0,0 +1,68 @@ +import System from '../System'; +import ObjectRenderer from './ObjectRenderer'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class BatchSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + super(renderer); + + /** + * An empty renderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.emptyRenderer = new ObjectRenderer(renderer); + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.currentRenderer = this.emptyRenderer; + } + + /** + * Changes the current renderer to the one given in parameter + * + * @param {PIXI.ObjectRenderer} objectRenderer - The object renderer to use. + */ + setObjectRenderer(objectRenderer) + { + if (this.currentRenderer === objectRenderer) + { + return; + } + + this.currentRenderer.stop(); + this.currentRenderer = objectRenderer; + + this.renderer.state.setState(objectRenderer.state); + + this.currentRenderer.start(); + } + + /** + * This should be called if you wish to do some custom rendering + * It will basically render anything that may be batched up such as sprites + * + */ + flush() + { + this.setObjectRenderer(this.emptyRenderer); + } + + reset() + { + this.setObjectRenderer(this.emptyRenderer); + } +} diff --git a/packages/core/src/batch/ObjectRenderer.js b/packages/core/src/batch/ObjectRenderer.js new file mode 100644 index 0000000..0c851a9 --- /dev/null +++ b/packages/core/src/batch/ObjectRenderer.js @@ -0,0 +1,48 @@ +import System from '../System'; + +/** + * Base for a common object renderer that can be used as a system renderer plugin. + * + * @class + * @extends PIXI.System + * @memberof PIXI + */ +export default class ObjectRenderer extends System +{ + /** + * Starts the renderer and sets the shader + * + */ + start() + { + // set the shader.. + } + + /** + * Stops the renderer + * + */ + stop() + { + this.flush(); + } + + /** + * Stub method for rendering content and emptying the current batch. + * + */ + flush() + { + // flush! + } + + /** + * Renders an object + * + * @param {PIXI.DisplayObject} object - The object to render. + */ + render(object) // eslint-disable-line no-unused-vars + { + // render the object + } +} diff --git a/packages/core/src/context/ContextSystem.js b/packages/core/src/context/ContextSystem.js new file mode 100644 index 0000000..301aab4 --- /dev/null +++ b/packages/core/src/context/ContextSystem.js @@ -0,0 +1,186 @@ +import System from '../System'; +import { settings } from '@pixi/settings'; + +let CONTEXT_UID = 0; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class ContextSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.webGLVersion = 1; + + this.handleContextLost = this.handleContextLost.bind(this); + this.handleContextRestored = this.handleContextRestored.bind(this); + + this.extensions = {}; + + renderer.view.addEventListener('webglcontextlost', this.handleContextLost, false); + renderer.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + } + + get isLost() + { + return (!this.gl || this.gl.isContextLost()); + } + + contextChange(gl) + { + this.gl = gl; + + // restore a context if it was previously lost + if (gl.isContextLost() && gl.getExtension('WEBGL_lose_context')) + { + gl.getExtension('WEBGL_lose_context').restoreContext(); + } + } + + initFromContext(gl) + { + this.gl = gl; + this.validateContext(gl); + this.renderer.gl = gl; + this.renderer.CONTEXT_UID = CONTEXT_UID++; + this.renderer.runners.contextChange.run(gl); + } + + initFromOptions(options) + { + const gl = this.createContext(this.renderer.view, options); + + this.initFromContext(gl); + } + + /** + * Helper class to create a webGL Context + * + * @class + * @memberof PIXI.glCore + * @param canvas {HTMLCanvasElement} the canvas element that we will get the context from + * @param options {Object} An options object that gets passed in to the canvas element containing the context attributes, + * see https://developer.mozilla.org/en/docs/Web/API/HTMLCanvasElement/getContext for the options available + * @return {WebGLRenderingContext} the WebGL context + */ + createContext(canvas, options) + { + let gl; + + if (settings.PREFER_WEBGL_2) + { + gl = canvas.getContext('webgl2', options); + } + + if (gl) + { + this.webGLVersion = 2; + } + else + { + this.webGLVersion = 1; + + gl = canvas.getContext('webgl', options) + || canvas.getContext('experimental-webgl', options); + + if (!gl) + { + // fail, not able to get a context + throw new Error('This browser does not support webGL. Try using the canvas renderer'); + } + } + + this.gl = gl; + + this.getExtensions(); + + return gl; + } + + getExtensions() + { + // time to set up default etensions that pixi uses.. + const gl = this.gl; + const extensions = this.extensions; + + if (this.webGLVersion === 1) + { + extensions.drawBuffers = gl.getExtension('WEBGL_draw_buffers'); + extensions.depthTexture = gl.getExtension('WEBKIT_WEBGL_depth_texture'); + extensions.floatTexture = gl.getExtension('OES_texture_float'); + extensions.loseContext = gl.getExtension('WEBGL_lose_context'); + + extensions.vertexArrayObject = gl.getExtension('OES_vertex_array_object') + || gl.getExtension('MOZ_OES_vertex_array_object') + || gl.getExtension('WEBKIT_OES_vertex_array_object'); + } + + // we don't use any specific WebGL 2 ones yet! + } + + /** + * Handles a lost webgl context + * + * @private + * @param {WebGLContextEvent} event - The context lost event. + */ + handleContextLost(event) + { + event.preventDefault(); + } + + /** + * Handles a restored webgl context + * + * @private + */ + handleContextRestored() + { + this.renderer.runners.contextChange.run(this.gl); + } + + destroy() + { + const view = this.renderer.view; + + // remove listeners + view.removeEventListener('webglcontextlost', this.handleContextLost); + view.removeEventListener('webglcontextrestored', this.handleContextRestored); + + this.gl.useProgram(null); + + if (this.extensions.loseContext) + { + this.extensions.loseContext.loseContext(); + } + } + + postrender() + { + this.gl.flush(); + } + + validateContext(gl) + { + const attributes = gl.getContextAttributes(); + + // this is going to be fairly simple for now.. but at least we have room to grow! + if (!attributes.stencil) + { + /* eslint-disable max-len */ + + /* eslint-disable no-console */ + console.warn('Provided WebGL context does not have a stencil buffer, masks may not render correctly'); + /* eslint-enable no-console */ + + /* eslint-enable max-len */ + } + } +} diff --git a/packages/core/src/filters/Filter.js b/packages/core/src/filters/Filter.js new file mode 100644 index 0000000..9d8cad8 --- /dev/null +++ b/packages/core/src/filters/Filter.js @@ -0,0 +1,140 @@ +import Shader from '../shader/Shader'; +import Program from '../shader/Program'; +import State from '../state/State'; +import { settings } from '@pixi/settings'; +// import extractUniformsFromSrc from './extractUniformsFromSrc'; +import defaultVertex from './defaultFilter.vert'; +import defaultFragment from './defaultFilter.frag'; + +// let math = require('../../math'); +/** + * @class + * @memberof PIXI + * @extends PIXI.Shader + */ +export default class Filter extends Shader +{ + /** + * @param {string} [vertexSrc] - The source of the vertex shader. + * @param {string} [fragmentSrc] - The source of the fragment shader. + * @param {object} [uniforms] - Custom uniforms to use to augment the built-in ones. + */ + constructor(vertexSrc, fragmentSrc, uniforms) + { + const program = Program.from(vertexSrc, fragmentSrc); + + super(program, uniforms); + + /** + * The padding of the filter. Some filters require extra space to breath such as a blur. + * Increasing this will add extra width and height to the bounds of the object that the + * filter is applied to. + * + * @member {number} + */ + this.padding = 0; + + /** + * The resolution of the filter. Setting this to be lower will lower the quality but + * increase the performance of the filter. + * + * @member {number} + */ + this.resolution = settings.RESOLUTION; + + /** + * If enabled is true the filter is applied, if false it will not. + * + * @member {boolean} + */ + this.enabled = true; + + /** + * If enabled, PixiJS will fit the filter area into boundaries for better performance. + * Switch it off if it does not work for specific shader. + * + * @member {boolean} + */ + this.autoFit = true; + + /** + * Legacy filters use position and uvs from attributes + * @member {boolean} + * @readonly + */ + this.legacy = !!this.program.attributeData.aTextureCoord; + + /** + * the webGL state the filter requires to render + * @member {PIXI.State} + */ + this.state = new State(); + } + + /** + * Applies the filter + * + * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + * @param {boolean} clear - Should the output be cleared before rendering to it + * @param {object} [currentState] - It's current state of filter. + * There are some useful properties in the currentState : + * target, filters, sourceFrame, destinationFrame, renderTarget, resolution + */ + apply(filterManager, input, output, clear, currentState, derp) // eslint-disable-line no-unused-vars + { + // do as you please! + + filterManager.applyFilter(this, input, output, clear, currentState, derp); + + // or just do a regular render.. + } + + /** + * Sets the blendmode of the filter + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL + */ + get blendMode() + { + return this.state.blendMode; + } + + set blendMode(value) // eslint-disable-line require-jsdoc + { + this.state.blendMode = value; + } + + /** + * The default vertex shader source + * + * @static + * @constant + */ + static get defaultVertexSrc() + { + return defaultVertex; + } + + /** + * The default fragment shader source + * + * @static + * @constant + */ + static get defaultFragmentSrc() + { + return defaultFragment; + } +} + +/** + * Used for caching shader IDs + * + * @static + * @private + */ +Filter.SOURCE_KEY_MAP = {}; + diff --git a/packages/core/src/filters/FilterSystem.js b/packages/core/src/filters/FilterSystem.js new file mode 100644 index 0000000..3174318 --- /dev/null +++ b/packages/core/src/filters/FilterSystem.js @@ -0,0 +1,431 @@ +import System from '../System'; + +import RenderTexture from '../renderTexture/RenderTexture'; +import Quad from '../utils/Quad'; +import QuadUv from '../utils/QuadUv'; +import { Rectangle } from '@pixi/math'; +import * as filterTransforms from './filterTransforms'; +import bitTwiddle from 'bit-twiddle'; +import UniformGroup from '../shader/UniformGroup'; +import { DRAW_MODES } from '@pixi/constants'; + +// +/** + * @ignore + * @class + */ +class FilterState +{ + /** + * + */ + constructor() + { + this.renderTexture = null; + this.sourceFrame = new Rectangle(); + this.destinationFrame = new Rectangle(); + this.filters = []; + this.target = null; + this.legacy = false; + this.resolution = 1; + } +} + +/** + * @class + * @memberof PIXI.systems + * @extends PIXI.System + */ +export default class FilterSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + /** + * stores a bunch of PO2 textures used for filtering + * @type {Object} + */ + this.texturePool = {}; + + /** + * a pool for storing filter states, save us creating new ones each tick + * @type {Array} + */ + this.statePool = []; + + /** + * A very simple geometry used when drawing a filter effect to the screen + * @type {Quad} + */ + this.quad = new Quad(); + + this.quadUv = new QuadUv(); + + /** + * Temporary rect for maths + * @type {PIXI.Rectangle} + */ + this.tempRect = new Rectangle(); + + this.activeState = {}; + + /** + * this uniform group is attached to filter uniforms when used + * @type {UniformGroup} + */ + this.globalUniforms = new UniformGroup({ + sourceFrame: this.tempRect, + destinationFrame: this.tempRect, + + // legacy variables + filterArea: new Float32Array(4), + filterClamp: new Float32Array(4), + }, true); + } + + /** + * Adds a new filter to the System. + * + * @param {PIXI.DisplayObject} target - The target of the filter to render. + * @param {PIXI.Filter[]} filters - The filters to apply. + */ + push(target, filters) + { + const renderer = this.renderer; + const filterStack = this.renderer.renderTexture.defaultFilterStack; + const state = this.statePool.pop() || new FilterState(); + + let resolution = filters[0].resolution; + let padding = filters[0].padding; + let autoFit = filters[0].autoFit; + let legacy = filters[0].legacy; + + for (let i = 1; i < filters.length; i++) + { + const filter = filters[i]; + + // lets use the lowest resolution.. + resolution = Math.min(resolution, filter.resolution); + // and the largest amount of padding! + padding = Math.max(padding, filter.padding); + // only auto fit if all filters are autofit + autoFit = autoFit || filter.autoFit; + + legacy = legacy || filter.legacy; + } + + filterStack.push(state); + + state.resolution = resolution; + + state.legacy = legacy; + + // round to whole number based on resolution + // TODO move that to the shader too? + state.sourceFrame = target.filterArea || target.getBounds(true); + + state.sourceFrame.pad(padding); + + if (autoFit) + { + state.sourceFrame.fit(this.renderer.renderTexture.destinationFrame); + } + + state.sourceFrame.round(resolution); + + state.renderTexture = this.getPotFilterTexture(state.sourceFrame.width, state.sourceFrame.height, resolution); + state.filters = filters; + + state.destinationFrame.width = state.renderTexture.width; + state.destinationFrame.height = state.renderTexture.height; + + state.renderTexture.filterFrame = state.sourceFrame; + renderer.renderTexture.bind(state.renderTexture, state.sourceFrame);// /, state.destinationFrame); + renderer.renderTexture.clear(); + } + + /** + * Pops off the filter and applies it. + * + */ + pop() + { + const renderer = this.renderer; + const filterStack = renderer.renderTexture.defaultFilterStack; + const state = filterStack.pop(); + const filters = state.filters; + + this.activeState = state; + + const globalUniforms = this.globalUniforms.uniforms; + + globalUniforms.sourceFrame = state.sourceFrame; + globalUniforms.destinationFrame = state.destinationFrame; + globalUniforms.resolution = state.resolution; + + // only update the rect if its legacy.. + if (state.legacy) + { + const filterArea = globalUniforms.filterArea; + const filterClamp = globalUniforms.filterClamp; + + filterArea[0] = state.destinationFrame.width; + filterArea[1] = state.destinationFrame.height; + filterArea[2] = state.sourceFrame.x; + filterArea[3] = state.sourceFrame.y; + + filterClamp[0] = 0.5 / state.resolution / state.destinationFrame.width; + filterClamp[1] = 0.5 / state.resolution / state.destinationFrame.height; + filterClamp[2] = (state.sourceFrame.width - 0.5) / state.resolution / state.destinationFrame.width; + filterClamp[3] = (state.sourceFrame.height - 0.5) / state.resolution / state.destinationFrame.height; + } + + this.globalUniforms.update(); + + const lastState = filterStack[filterStack.length - 1]; + + if (filters.length === 1) + { + filters[0].apply(this, state.renderTexture, lastState.renderTexture, false, state); + + this.returnFilterTexture(state.renderTexture); + } + else + { + let flip = state.renderTexture; + let flop = this.getPotFilterTexture( + flip.width, + flip.height, + state.resolution + ); + + flop.filterFrame = flip.filterFrame; + + let i = 0; + + for (i = 0; i < filters.length - 1; ++i) + { + filters[i].apply(this, flip, flop, true, state); + + const t = flip; + + flip = flop; + flop = t; + } + + filters[i].apply(this, flip, lastState.renderTexture, false, state); + + this.returnFilterTexture(flip); + this.returnFilterTexture(flop); + } + + this.statePool.push(state); + } + + /** + * Draws a filter. + * + * @param {PIXI.Filter} filter - The filter to draw. + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + * @param {boolean} clear - Should the output be cleared before rendering to it + */ + applyFilter(filter, input, output, clear) + { + const renderer = this.renderer; + + renderer.renderTexture.bind(output, output ? output.filterFrame : null); + + if (clear) + { + // gl.disable(gl.SCISSOR_TEST); + renderer.renderTexture.clear(); + // gl.enable(gl.SCISSOR_TEST); + } + + // set the uniforms.. + filter.uniforms.uSampler = input; + filter.uniforms.filterGlobals = this.globalUniforms; + + // TODO make it so that the order of this does not matter.. + // because it does at the moment cos of global uniforms. + // they need to get resynced + + renderer.state.setState(filter.state); + renderer.shader.bind(filter); + + if (filter.legacy) + { + this.quadUv.map(input._frame, input.filterFrame); + + renderer.geometry.bind(this.quadUv); + renderer.geometry.draw(DRAW_MODES.TRIANGLES); + } + else + { + renderer.geometry.bind(this.quad); + renderer.geometry.draw(DRAW_MODES.TRIANGLE_STRIP); + } + } + + /** + * Calculates the mapped matrix. + * + * TODO playing around here.. this is temporary - (will end up in the shader) + * this returns a matrix that will normalise map filter cords in the filter to screen space + * + * @param {PIXI.Matrix} outputMatrix - the matrix to output to. + * @return {PIXI.Matrix} The mapped matrix. + */ + calculateScreenSpaceMatrix(outputMatrix) + { + const currentState = this.activeState; + + return filterTransforms.calculateScreenSpaceMatrix( + outputMatrix, + currentState.sourceFrame, + currentState.destinationFrame + ); + } + + /** + * This will map the filter coord so that a texture can be used based on the transform of a sprite + * + * @param {PIXI.Matrix} outputMatrix - The matrix to output to. + * @param {PIXI.Sprite} sprite - The sprite to map to. + * @return {PIXI.Matrix} The mapped matrix. + */ + calculateSpriteMatrix(outputMatrix, sprite) + { + const currentState = this.activeState; + + return filterTransforms.calculateSpriteMatrix( + outputMatrix, + currentState.sourceFrame, + currentState.destinationFrame, + sprite + ); + } + + /** + * Destroys this Filter System. + * + * @param {boolean} [contextLost=false] context was lost, do not free shaders + * + */ + destroy(contextLost = false) + { + if (!contextLost) + { + this.emptyPool(); + } + else + { + this.texturePool = {}; + } + } + + /** + * Gets a Power-of-Two render texture. + * + * TODO move to a seperate class could be on renderer? + * also - could cause issue with multiple contexts? + * + * @private + * @param {WebGLRenderingContext} gl - The webgl rendering context + * @param {number} minWidth - The minimum width of the render target. + * @param {number} minHeight - The minimum height of the render target. + * @param {number} resolution - The resolution of the render target. + * @return {PIXI.RenderTarget} The new render target. + */ + getPotFilterTexture(minWidth, minHeight, resolution) + { + minWidth = bitTwiddle.nextPow2(minWidth); + minHeight = bitTwiddle.nextPow2(minHeight); + resolution = resolution || 1; + + const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + + if (!this.texturePool[key]) + { + this.texturePool[key] = []; + } + + let renderTexture = this.texturePool[key].pop(); + + if (!renderTexture) + { + // temporary bypass cache.. + // internally - this will cause a texture to be bound.. + renderTexture = RenderTexture.create({ + width: minWidth, + height: minHeight, + resolution, + }); + } + + return renderTexture; + } + + /** + * Gets extra render texture to use inside current filter + * + * @param {number} resolution resolution of the renderTexture + * @returns {PIXI.RenderTexture} + */ + getFilterTexture(resolution) + { + const rt = this.activeState.renderTexture; + + const filterTexture = this.getPotFilterTexture(rt.width, rt.height, resolution || rt.baseTexture.resolution); + + filterTexture.filterFrame = rt.filterFrame; + + return filterTexture; + } + + /** + * Frees a render texture back into the pool. + * + * @param {PIXI.RenderTarget} renderTexture - The renderTarget to free + */ + returnFilterTexture(renderTexture) + { + renderTexture.filterFrame = null; + + const base = renderTexture.baseTexture; + + const minWidth = base.width; + const minHeight = base.height; + + const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + + this.texturePool[key].push(renderTexture); + } + + /** + * Empties the texture pool. + * + */ + emptyPool() + { + for (const i in this.texturePool) + { + const textures = this.texturePool[i]; + + if (textures) + { + for (let j = 0; j < textures.length; j++) + { + textures[j].destroy(true); + } + } + } + + this.texturePool = {}; + } +} diff --git a/packages/core/src/filters/defaultFilter.frag b/packages/core/src/filters/defaultFilter.frag new file mode 100644 index 0000000..a14c1be --- /dev/null +++ b/packages/core/src/filters/defaultFilter.frag @@ -0,0 +1,22 @@ +varying vec2 vTextureCoord; +varying vec2 vFilterCoord; + +uniform sampler2D uSampler; +uniform sampler2D filterSampler; + +void main(void){ + vec4 masky = texture2D(filterSampler, vFilterCoord); + vec4 sample = texture2D(uSampler, vTextureCoord); + vec4 color; + if(mod(vFilterCoord.x, 1.0) > 0.5) + { + color = vec4(1.0, 0.0, 0.0, 1.0); + } + else + { + color = vec4(0.0, 1.0, 0.0, 1.0); + } + // gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0); + gl_FragColor = mix(sample, masky, 0.5); + gl_FragColor *= sample.a; +} diff --git a/packages/core/src/filters/defaultFilter.vert b/packages/core/src/filters/defaultFilter.vert new file mode 100644 index 0000000..e1febca --- /dev/null +++ b/packages/core/src/filters/defaultFilter.vert @@ -0,0 +1,14 @@ +attribute vec2 aVertexPosition; +attribute vec2 aTextureCoord; + +uniform mat3 projectionMatrix; +uniform mat3 filterMatrix; + +varying vec2 vTextureCoord; +varying vec2 vFilterCoord; + +void main(void){ + gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); + vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy; + vTextureCoord = aTextureCoord ; +} diff --git a/packages/core/src/filters/filterTransforms.js b/packages/core/src/filters/filterTransforms.js new file mode 100644 index 0000000..87a3cd6 --- /dev/null +++ b/packages/core/src/filters/filterTransforms.js @@ -0,0 +1,50 @@ +import { Matrix } from '@pixi/math'; + +/** + * Calculates the mapped matrix + * @param filterArea {Rectangle} The filter area + * @param sprite {Sprite} the target sprite + * @param outputMatrix {Matrix} @alvin + * @private + */ +// this returns a matrix that will normalise map filter cords in the filter to screen space +export function calculateScreenSpaceMatrix(outputMatrix, filterArea, textureSize) +{ + // TODO unwrap? + const mappedMatrix = outputMatrix.identity(); + + mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); + + mappedMatrix.scale(textureSize.width, textureSize.height); + + return mappedMatrix; +} + +export function calculateNormalizedScreenSpaceMatrix(outputMatrix, filterArea, textureSize) +{ + const mappedMatrix = outputMatrix.identity(); + + mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); + + const translateScaleX = (textureSize.width / filterArea.width); + const translateScaleY = (textureSize.height / filterArea.height); + + mappedMatrix.scale(translateScaleX, translateScaleY); + + return mappedMatrix; +} + +// this will map the filter coord so that a texture can be used based on the transform of a sprite +export function calculateSpriteMatrix(outputMatrix, filterArea, textureSize, sprite) +{ + const orig = sprite._texture.orig; + const mappedMatrix = outputMatrix.set(textureSize.width, 0, 0, textureSize.height, filterArea.x, filterArea.y); + const worldTransform = sprite.worldTransform.copyTo(Matrix.TEMP_MATRIX); + + worldTransform.invert(); + mappedMatrix.prepend(worldTransform); + mappedMatrix.scale(1.0 / orig.width, 1.0 / orig.height); + mappedMatrix.translate(sprite.anchor.x, sprite.anchor.y); + + return mappedMatrix; +} diff --git a/packages/core/src/filters/spriteMask/SpriteMaskFilter.js b/packages/core/src/filters/spriteMask/SpriteMaskFilter.js new file mode 100644 index 0000000..91697b7 --- /dev/null +++ b/packages/core/src/filters/spriteMask/SpriteMaskFilter.js @@ -0,0 +1,63 @@ +import Filter from '../Filter'; +import { Matrix } from '@pixi/math'; +import vertex from './spriteMaskFilter.vert'; +import fragment from './spriteMaskFilter.frag'; +import { default as TextureMatrix } from '../../textures/TextureMatrix'; + +/** + * The SpriteMaskFilter class + * + * @class + * @extends PIXI.Filter + * @memberof PIXI + */ +export default class SpriteMaskFilter extends Filter +{ + /** + * @param {PIXI.Sprite} sprite - the target sprite + */ + constructor(sprite) + { + const maskMatrix = new Matrix(); + + super(vertex, fragment); + + sprite.renderable = false; + + this.maskSprite = sprite; + this.maskMatrix = maskMatrix; + } + + /** + * Applies the filter + * + * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + */ + apply(filterManager, input, output) + { + const maskSprite = this.maskSprite; + const tex = this.maskSprite.texture; + + if (!tex.valid) + { + return; + } + if (!tex.transform) + { + // margin = 0.0, let it bleed a bit, shader code becomes easier + // assuming that atlas textures were made with 1-pixel padding + tex.transform = new TextureMatrix(tex, 0.0); + } + tex.transform.update(); + + this.uniforms.mask = tex; + this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite) + .prepend(tex.transform.mapCoord); + this.uniforms.alpha = maskSprite.worldAlpha; + this.uniforms.maskClamp = tex.transform.uClampFrame; + + filterManager.applyFilter(this, input, output); + } +} diff --git a/packages/core/src/filters/spriteMask/spriteMaskFilter.frag b/packages/core/src/filters/spriteMask/spriteMaskFilter.frag new file mode 100644 index 0000000..0e4aef8 --- /dev/null +++ b/packages/core/src/filters/spriteMask/spriteMaskFilter.frag @@ -0,0 +1,23 @@ +varying vec2 vMaskCoord; +varying vec2 vTextureCoord; + +uniform sampler2D uSampler; +uniform sampler2D mask; +uniform float alpha; +uniform vec4 maskClamp; + +void main(void) +{ + float clip = step(3.5, + step(maskClamp.x, vMaskCoord.x) + + step(maskClamp.y, vMaskCoord.y) + + step(vMaskCoord.x, maskClamp.z) + + step(vMaskCoord.y, maskClamp.w)); + + vec4 original = texture2D(uSampler, vTextureCoord); + vec4 masky = texture2D(mask, vMaskCoord); + + original *= (masky.r * masky.a * alpha * clip); + + gl_FragColor = original; +} diff --git a/packages/core/src/filters/spriteMask/spriteMaskFilter.vert b/packages/core/src/filters/spriteMask/spriteMaskFilter.vert new file mode 100644 index 0000000..d23a30c --- /dev/null +++ b/packages/core/src/filters/spriteMask/spriteMaskFilter.vert @@ -0,0 +1,16 @@ +attribute vec2 aVertexPosition; +attribute vec2 aTextureCoord; + +uniform mat3 projectionMatrix; +uniform mat3 otherMatrix; + +varying vec2 vMaskCoord; +varying vec2 vTextureCoord; + +void main(void) +{ + gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); + + vTextureCoord = aTextureCoord; + vMaskCoord = ( otherMatrix * vec3( aTextureCoord, 1.0) ).xy; +} diff --git a/packages/core/src/AbstractRenderer.js b/packages/core/src/AbstractRenderer.js new file mode 100644 index 0000000..64b2eb1 --- /dev/null +++ b/packages/core/src/AbstractRenderer.js @@ -0,0 +1,353 @@ +import { hex2string, hex2rgb } from '@pixi/utils'; +import { Matrix, Rectangle } from '@pixi/math'; +import { RENDERER_TYPE } from '@pixi/constants'; +import { settings } from '@pixi/settings'; +import { Container } from '@pixi/display'; +import RenderTexture from './renderTexture/RenderTexture'; +import EventEmitter from 'eventemitter3'; + +const tempMatrix = new Matrix(); + +/** + * 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 EventEmitter + * @memberof PIXI + */ +export default class AbstractRenderer extends EventEmitter +{ + // eslint-disable-next-line valid-jsdoc + /** + * @param {string} system - The name of the system this renderer is for. + * @param {object} [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.transparent=false] - If the render view is transparent, default false + * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false + * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. The + * resolution of the renderer retina would be 2. + * @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 {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when rendering, + * stopping pixel interpolation. + */ + constructor(system, options, arg2, arg3) + { + super(); + + // Support for constructor(system, screenWidth, screenHeight, options) + if (typeof options === 'number') + { + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + }, arg3); + } + + // 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 = RENDERER_TYPE.UNKNOWN; + + /** + * Measurements of the screen. (0, 0, screenWidth, screenHeight) + * + * Its safe to use as filterArea or hitArea for 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 1 + */ + this.resolution = options.resolution || settings.RESOLUTION; + + /** + * Whether the render view is transparent + * + * @member {boolean} + */ + this.transparent = options.transparent; + + /** + * Whether css dimensions of canvas view should be resized to screen dimensions automatically + * + * @member {boolean} + */ + this.autoResize = options.autoResize || false; + + /** + * Tracks the blend modes useful for this renderer. + * + * @member {object} + */ + this.blendModes = null; + + /** + * 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; + + /** + * If true PixiJS will Math.floor() x/y values when rendering, stopping pixel interpolation. + * Handy for crisp pixel art and speed on legacy devices. + * + * @member {boolean} + */ + this.roundPixels = options.roundPixels; + + /** + * The background color as a number. + * + * @member {number} + * @private + */ + this._backgroundColor = 0x000000; + + /** + * The background color as an [R, G, B] array. + * + * @member {number[]} + * @private + */ + this._backgroundColorRgba = [0, 0, 0, 0]; + + /** + * The background color as a string. + * + * @member {string} + * @private + */ + this._backgroundColorString = '#000000'; + + this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter + + /** + * This temporary display object used as the parent of the currently being rendered item + * + * @member {PIXI.DisplayObject} + * @private + */ + this._tempDisplayObjectParent = new Container(); + + /** + * The last root object that the renderer tried to render. + * + * @member {PIXI.DisplayObject} + * @private + */ + this._lastObjectRendered = this._tempDisplayObjectParent; + + /** + * Collection of plugins. + * @readonly + * @member {object} + */ + this.plugins = {}; + } + + /** + * Initialize the plugins. + * + * @protected + * @param {object} staticMap - The dictionary of staticly saved plugins. + */ + initPlugins(staticMap) + { + 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() + { + return this.view.width; + } + + /** + * Same as view.height, actual number of pixels in the canvas by vertical + * + * @member {number} + * @readonly + * @default 600 + */ + get height() + { + return this.view.height; + } + + /** + * Resizes the screen and canvas to the specified width and height + * Canvas dimensions are multiplied by resolution + * + * @param {number} screenWidth - the new width of the screen + * @param {number} screenHeight - the new height of the screen + */ + resize(screenWidth, screenHeight) + { + this.screen.width = screenWidth; + this.screen.height = screenHeight; + + this.view.width = screenWidth * this.resolution; + this.view.height = screenHeight * this.resolution; + + if (this.autoResize) + { + this.view.style.width = `${screenWidth}px`; + this.view.style.height = `${screenHeight}px`; + } + } + + /** + * 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 {PIXI.DisplayObject} displayObject - The displayObject the object will be generated from + * @param {number} scaleMode - Should be one of the scaleMode consts + * @param {number} resolution - The resolution / device pixel ratio of the texture being generated + * @param {PIXI.Rectangle} [region] - The region of the displayObject, that shall be rendered, + * if no region is specified, defaults to the local bounds of the displayObject. + * @return {PIXI.Texture} a texture of the graphics object + */ + generateTexture(displayObject, scaleMode, resolution, region) + { + region = region || displayObject.getLocalBounds(); + + // 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(region.width | 0, region.height | 0, scaleMode, resolution); + + tempMatrix.tx = -region.x; + tempMatrix.ty = -region.y; + + this.render(displayObject, renderTexture, false, tempMatrix, true); + + return renderTexture; + } + + /** + * Removes everything from the renderer and optionally removes the Canvas DOM element. + * + * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. + */ + destroy(removeView) + { + 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); + } + + this.plugins = null; + + this.type = RENDERER_TYPE.UNKNOWN; + + this.view = null; + + this.screen = null; + + this.resolution = 0; + + this.transparent = false; + + this.autoResize = false; + + this.blendModes = null; + + this.options = null; + + this.preserveDrawingBuffer = false; + this.clearBeforeRender = false; + + this.roundPixels = false; + + this._backgroundColor = 0; + this._backgroundColorRgba = null; + this._backgroundColorString = null; + + this._tempDisplayObjectParent = null; + this._lastObjectRendered = null; + } + + /** + * The background color to fill if not transparent + * + * @member {number} + */ + get backgroundColor() + { + return this._backgroundColor; + } + + set backgroundColor(value) // eslint-disable-line require-jsdoc + { + this._backgroundColor = value; + this._backgroundColorString = hex2string(value); + hex2rgb(value, this._backgroundColorRgba); + } +} diff --git a/packages/core/src/Renderer.js b/packages/core/src/Renderer.js new file mode 100644 index 0000000..2227e78 --- /dev/null +++ b/packages/core/src/Renderer.js @@ -0,0 +1,338 @@ +import AbstractRenderer from './AbstractRenderer'; +import { sayHello } from '@pixi/utils'; +import MaskSystem from './mask/MaskSystem'; +import StencilSystem from './mask/StencilSystem'; +import FilterSystem from './filters/FilterSystem'; +import FramebufferSystem from './framebuffer/FramebufferSystem'; +import RenderTextureSystem from './renderTexture/RenderTextureSystem'; +import TextureSystem from './textures/TextureSystem'; +import ProjectionSystem from './projection/ProjectionSystem'; +import StateSystem from './state/StateSystem'; +import GeometrySystem from './geometry/GeometrySystem'; +import ShaderSystem from './shader/ShaderSystem'; +import ContextSystem from './context/ContextSystem'; +import BatchSystem from './batch/BatchSystem'; +import TextureGCSystem from './textures/TextureGCSystem'; +import { RENDERER_TYPE } from '@pixi/constants'; +import UniformGroup from './shader/UniformGroup'; +import { Matrix } from '@pixi/math'; +import Runner from 'mini-runner'; + +/** + * 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 Render works by automatically managing webGLBatchs. + * 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 :) + * + * @class + * @memberof PIXI + * @extends PIXI.AbstractRenderer + */ +export default class Renderer extends AbstractRenderer +{ + // eslint-disable-next-line valid-jsdoc + /** + * + * @param {object} [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.transparent=false] - If the render view is transparent, default false + * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false + * @param {boolean} [options.antialias=false] - sets antialias. If not available natively then FXAA + * antialiasing is used + * @param {boolean} [options.forceFXAA=false] - forces FXAA antialiasing to be used over native. + * FXAA is faster, but may not always look as great + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. + * The resolution of the renderer retina would be 2. + * @param {boolean} [options.clearBeforeRender=true] - This sets if the renderer will clear + * the canvas or not before the new render pass. If you wish to set this to false, you *must* set + * preserveDrawingBuffer to `true`. + * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, + * enable this if you need to call toDataUrl on the webgl context. + * @param {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when + * rendering, stopping pixel interpolation. + * @param {number} [options.backgroundColor=0x000000] - The background color of the rendered area + * (shown if not transparent). + * @param {boolean} [options.legacy=false] - If true PixiJS will aim to ensure compatibility + * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. + * @param {string} [options.powerPreference] - Parameter passed to webgl context, set to "high-performance" + * for devices with dual graphics card + */ + constructor(options, arg2, arg3) + { + super('WebGL', options, arg2, arg3); + + /** + * The type of this renderer as a standardised const + * + * @member {number} + * @see PIXI.RENDERER_TYPE + */ + this.type = RENDERER_TYPE.WEBGL; + + // this will be set by the contextSystem (this.context) + this.gl = null; + this.CONTEXT_UID = 0; + this.legacy = !!options.legacy; + + // TODO legacy! + + // runners! + this.runners = { + destroy: new Runner('destroy'), + contextChange: new Runner('contextChange', 1), + reset: new Runner('reset'), + update: new Runner('update'), + postrender: new Runner('postrender'), + prerender: new Runner('prerender'), + resize: new Runner('resize', 2), + }; + + 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(GeometrySystem, 'geometry') + .addSystem(FramebufferSystem, 'framebuffer') + .addSystem(StencilSystem, 'stencil') + .addSystem(ProjectionSystem, 'projection') + .addSystem(TextureGCSystem, 'textureGC') + .addSystem(FilterSystem, 'filter') + .addSystem(RenderTextureSystem, 'renderTexture') + .addSystem(BatchSystem, 'batch'); + + this.initPlugins(Renderer.__plugins); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ + if (options.context) + { + this.context.initFromContext(options.context); + } + else + { + this.context.initFromOptions({ + alpha: this.transparent, + antialias: options.antialias, + premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', + stencil: true, + 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); + } + + /** + * Add a new system to the renderer. + * @param {class} ClassRef - Class reference + * @param {string} [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. + * @return {PIXI.Renderer} Return instance of renderer + */ + addSystem(ClassRef, name) + { + if (!name) + { + name = ClassRef.name; + } + + // TODO - read name from class.name.. + + /* + if(name.includes('System')) + { + name = name.replace('System', ''); + name = name.charAt(0).toLowerCase() + name.slice(1); + } + */ + + const system = new ClassRef(this); + + if (this[name]) + { + throw new Error(`Whoops! ${name} is already a manger`); + } + + this[name] = system; + + for (const i in this.runners) + { + this.runners[i].add(system); + } + + return this; + + /** + * 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. + */ + } + + /** + * Renders the object to its webGL view + * + * @param {PIXI.DisplayObject} displayObject - the object to be rendered + * @param {PIXI.RenderTexture} renderTexture - The render texture to render to. + * @param {boolean} [clear] - Should the canvas be cleared before the new render + * @param {PIXI.Transform} [transform] - A transform to apply to the render texture before rendering. + * @param {boolean} [skipUpdateTransform] - Should we skip the update transform pass? + */ + render(displayObject, renderTexture, clear, transform, skipUpdateTransform) + { + // can be handy to know! + this.renderingToScreen = !renderTexture; + + this.runners.prerender.run(); + this.emit('prerender'); + + // 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.parent; + + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = 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.run(); + + this.emit('postrender'); + } + + /** + * Resizes the webGL view to the specified width and height. + * + * @param {number} screenWidth - the new width of the screen + * @param {number} screenHeight - the new height of the screen + */ + resize(screenWidth, screenHeight) + { + AbstractRenderer.prototype.resize.call(this, screenWidth, screenHeight); + + this.runners.resize.run(screenWidth, screenHeight); + } + + /** + * Resets the WebGL state so you can render things however you fancy! + * + * @return {PIXI.Renderer} Returns itself. + */ + reset() + { + this.runners.reset.run(); + + return this; + } + + clear() + { + this.framebuffer.bind(); + this.framebuffer.clear(); + } + + /** + * Removes everything from the renderer (event listeners, spritebatch, etc...) + * + * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. + * See: https://github.com/pixijs/pixi.js/issues/2233 + */ + destroy(removeView) + { + this.runners.destroy.run(); + + // call base destroy + super.destroy(removeView); + + // TODO nullify all the managers.. + this.gl = null; + } + + /** + * 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 + * builds and excluding plugins. + * @name PIXI.Renderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. + */ + + /** + * Adds a plugin to the renderer. + * + * @method + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + static registerPlugin(pluginName, ctor) + { + Renderer.__plugins = Renderer.__plugins || {}; + Renderer.__plugins[pluginName] = ctor; + } +} diff --git a/packages/core/src/System.js b/packages/core/src/System.js new file mode 100644 index 0000000..05a10c8 --- /dev/null +++ b/packages/core/src/System.js @@ -0,0 +1,42 @@ +/** + * System is a base class used for extending systems used by the {@link PIXI.Renderer} + * @see PIXI.Renderer#addSystem + * @class + * @memberof PIXI + */ +export default class System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + /** + * The renderer this manager works for. + * + * @member {PIXI.Renderer} + */ + this.renderer = renderer; + + this.renderer.runners.contextChange.add(this); + } + + /** + * Generic method called when there is a WebGL context change. + * + */ + contextChange() + { + // do some codes init! + } + + /** + * Generic destroy methods to be overridden by the subclass + * + */ + destroy() + { + this.renderer.runners.contextChange.remove(this); + this.renderer = null; + } +} diff --git a/packages/core/src/batch/BatchSystem.js b/packages/core/src/batch/BatchSystem.js new file mode 100644 index 0000000..b04c249 --- /dev/null +++ b/packages/core/src/batch/BatchSystem.js @@ -0,0 +1,68 @@ +import System from '../System'; +import ObjectRenderer from './ObjectRenderer'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class BatchSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + super(renderer); + + /** + * An empty renderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.emptyRenderer = new ObjectRenderer(renderer); + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.currentRenderer = this.emptyRenderer; + } + + /** + * Changes the current renderer to the one given in parameter + * + * @param {PIXI.ObjectRenderer} objectRenderer - The object renderer to use. + */ + setObjectRenderer(objectRenderer) + { + if (this.currentRenderer === objectRenderer) + { + return; + } + + this.currentRenderer.stop(); + this.currentRenderer = objectRenderer; + + this.renderer.state.setState(objectRenderer.state); + + this.currentRenderer.start(); + } + + /** + * This should be called if you wish to do some custom rendering + * It will basically render anything that may be batched up such as sprites + * + */ + flush() + { + this.setObjectRenderer(this.emptyRenderer); + } + + reset() + { + this.setObjectRenderer(this.emptyRenderer); + } +} diff --git a/packages/core/src/batch/ObjectRenderer.js b/packages/core/src/batch/ObjectRenderer.js new file mode 100644 index 0000000..0c851a9 --- /dev/null +++ b/packages/core/src/batch/ObjectRenderer.js @@ -0,0 +1,48 @@ +import System from '../System'; + +/** + * Base for a common object renderer that can be used as a system renderer plugin. + * + * @class + * @extends PIXI.System + * @memberof PIXI + */ +export default class ObjectRenderer extends System +{ + /** + * Starts the renderer and sets the shader + * + */ + start() + { + // set the shader.. + } + + /** + * Stops the renderer + * + */ + stop() + { + this.flush(); + } + + /** + * Stub method for rendering content and emptying the current batch. + * + */ + flush() + { + // flush! + } + + /** + * Renders an object + * + * @param {PIXI.DisplayObject} object - The object to render. + */ + render(object) // eslint-disable-line no-unused-vars + { + // render the object + } +} diff --git a/packages/core/src/context/ContextSystem.js b/packages/core/src/context/ContextSystem.js new file mode 100644 index 0000000..301aab4 --- /dev/null +++ b/packages/core/src/context/ContextSystem.js @@ -0,0 +1,186 @@ +import System from '../System'; +import { settings } from '@pixi/settings'; + +let CONTEXT_UID = 0; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class ContextSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.webGLVersion = 1; + + this.handleContextLost = this.handleContextLost.bind(this); + this.handleContextRestored = this.handleContextRestored.bind(this); + + this.extensions = {}; + + renderer.view.addEventListener('webglcontextlost', this.handleContextLost, false); + renderer.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + } + + get isLost() + { + return (!this.gl || this.gl.isContextLost()); + } + + contextChange(gl) + { + this.gl = gl; + + // restore a context if it was previously lost + if (gl.isContextLost() && gl.getExtension('WEBGL_lose_context')) + { + gl.getExtension('WEBGL_lose_context').restoreContext(); + } + } + + initFromContext(gl) + { + this.gl = gl; + this.validateContext(gl); + this.renderer.gl = gl; + this.renderer.CONTEXT_UID = CONTEXT_UID++; + this.renderer.runners.contextChange.run(gl); + } + + initFromOptions(options) + { + const gl = this.createContext(this.renderer.view, options); + + this.initFromContext(gl); + } + + /** + * Helper class to create a webGL Context + * + * @class + * @memberof PIXI.glCore + * @param canvas {HTMLCanvasElement} the canvas element that we will get the context from + * @param options {Object} An options object that gets passed in to the canvas element containing the context attributes, + * see https://developer.mozilla.org/en/docs/Web/API/HTMLCanvasElement/getContext for the options available + * @return {WebGLRenderingContext} the WebGL context + */ + createContext(canvas, options) + { + let gl; + + if (settings.PREFER_WEBGL_2) + { + gl = canvas.getContext('webgl2', options); + } + + if (gl) + { + this.webGLVersion = 2; + } + else + { + this.webGLVersion = 1; + + gl = canvas.getContext('webgl', options) + || canvas.getContext('experimental-webgl', options); + + if (!gl) + { + // fail, not able to get a context + throw new Error('This browser does not support webGL. Try using the canvas renderer'); + } + } + + this.gl = gl; + + this.getExtensions(); + + return gl; + } + + getExtensions() + { + // time to set up default etensions that pixi uses.. + const gl = this.gl; + const extensions = this.extensions; + + if (this.webGLVersion === 1) + { + extensions.drawBuffers = gl.getExtension('WEBGL_draw_buffers'); + extensions.depthTexture = gl.getExtension('WEBKIT_WEBGL_depth_texture'); + extensions.floatTexture = gl.getExtension('OES_texture_float'); + extensions.loseContext = gl.getExtension('WEBGL_lose_context'); + + extensions.vertexArrayObject = gl.getExtension('OES_vertex_array_object') + || gl.getExtension('MOZ_OES_vertex_array_object') + || gl.getExtension('WEBKIT_OES_vertex_array_object'); + } + + // we don't use any specific WebGL 2 ones yet! + } + + /** + * Handles a lost webgl context + * + * @private + * @param {WebGLContextEvent} event - The context lost event. + */ + handleContextLost(event) + { + event.preventDefault(); + } + + /** + * Handles a restored webgl context + * + * @private + */ + handleContextRestored() + { + this.renderer.runners.contextChange.run(this.gl); + } + + destroy() + { + const view = this.renderer.view; + + // remove listeners + view.removeEventListener('webglcontextlost', this.handleContextLost); + view.removeEventListener('webglcontextrestored', this.handleContextRestored); + + this.gl.useProgram(null); + + if (this.extensions.loseContext) + { + this.extensions.loseContext.loseContext(); + } + } + + postrender() + { + this.gl.flush(); + } + + validateContext(gl) + { + const attributes = gl.getContextAttributes(); + + // this is going to be fairly simple for now.. but at least we have room to grow! + if (!attributes.stencil) + { + /* eslint-disable max-len */ + + /* eslint-disable no-console */ + console.warn('Provided WebGL context does not have a stencil buffer, masks may not render correctly'); + /* eslint-enable no-console */ + + /* eslint-enable max-len */ + } + } +} diff --git a/packages/core/src/filters/Filter.js b/packages/core/src/filters/Filter.js new file mode 100644 index 0000000..9d8cad8 --- /dev/null +++ b/packages/core/src/filters/Filter.js @@ -0,0 +1,140 @@ +import Shader from '../shader/Shader'; +import Program from '../shader/Program'; +import State from '../state/State'; +import { settings } from '@pixi/settings'; +// import extractUniformsFromSrc from './extractUniformsFromSrc'; +import defaultVertex from './defaultFilter.vert'; +import defaultFragment from './defaultFilter.frag'; + +// let math = require('../../math'); +/** + * @class + * @memberof PIXI + * @extends PIXI.Shader + */ +export default class Filter extends Shader +{ + /** + * @param {string} [vertexSrc] - The source of the vertex shader. + * @param {string} [fragmentSrc] - The source of the fragment shader. + * @param {object} [uniforms] - Custom uniforms to use to augment the built-in ones. + */ + constructor(vertexSrc, fragmentSrc, uniforms) + { + const program = Program.from(vertexSrc, fragmentSrc); + + super(program, uniforms); + + /** + * The padding of the filter. Some filters require extra space to breath such as a blur. + * Increasing this will add extra width and height to the bounds of the object that the + * filter is applied to. + * + * @member {number} + */ + this.padding = 0; + + /** + * The resolution of the filter. Setting this to be lower will lower the quality but + * increase the performance of the filter. + * + * @member {number} + */ + this.resolution = settings.RESOLUTION; + + /** + * If enabled is true the filter is applied, if false it will not. + * + * @member {boolean} + */ + this.enabled = true; + + /** + * If enabled, PixiJS will fit the filter area into boundaries for better performance. + * Switch it off if it does not work for specific shader. + * + * @member {boolean} + */ + this.autoFit = true; + + /** + * Legacy filters use position and uvs from attributes + * @member {boolean} + * @readonly + */ + this.legacy = !!this.program.attributeData.aTextureCoord; + + /** + * the webGL state the filter requires to render + * @member {PIXI.State} + */ + this.state = new State(); + } + + /** + * Applies the filter + * + * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + * @param {boolean} clear - Should the output be cleared before rendering to it + * @param {object} [currentState] - It's current state of filter. + * There are some useful properties in the currentState : + * target, filters, sourceFrame, destinationFrame, renderTarget, resolution + */ + apply(filterManager, input, output, clear, currentState, derp) // eslint-disable-line no-unused-vars + { + // do as you please! + + filterManager.applyFilter(this, input, output, clear, currentState, derp); + + // or just do a regular render.. + } + + /** + * Sets the blendmode of the filter + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL + */ + get blendMode() + { + return this.state.blendMode; + } + + set blendMode(value) // eslint-disable-line require-jsdoc + { + this.state.blendMode = value; + } + + /** + * The default vertex shader source + * + * @static + * @constant + */ + static get defaultVertexSrc() + { + return defaultVertex; + } + + /** + * The default fragment shader source + * + * @static + * @constant + */ + static get defaultFragmentSrc() + { + return defaultFragment; + } +} + +/** + * Used for caching shader IDs + * + * @static + * @private + */ +Filter.SOURCE_KEY_MAP = {}; + diff --git a/packages/core/src/filters/FilterSystem.js b/packages/core/src/filters/FilterSystem.js new file mode 100644 index 0000000..3174318 --- /dev/null +++ b/packages/core/src/filters/FilterSystem.js @@ -0,0 +1,431 @@ +import System from '../System'; + +import RenderTexture from '../renderTexture/RenderTexture'; +import Quad from '../utils/Quad'; +import QuadUv from '../utils/QuadUv'; +import { Rectangle } from '@pixi/math'; +import * as filterTransforms from './filterTransforms'; +import bitTwiddle from 'bit-twiddle'; +import UniformGroup from '../shader/UniformGroup'; +import { DRAW_MODES } from '@pixi/constants'; + +// +/** + * @ignore + * @class + */ +class FilterState +{ + /** + * + */ + constructor() + { + this.renderTexture = null; + this.sourceFrame = new Rectangle(); + this.destinationFrame = new Rectangle(); + this.filters = []; + this.target = null; + this.legacy = false; + this.resolution = 1; + } +} + +/** + * @class + * @memberof PIXI.systems + * @extends PIXI.System + */ +export default class FilterSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + /** + * stores a bunch of PO2 textures used for filtering + * @type {Object} + */ + this.texturePool = {}; + + /** + * a pool for storing filter states, save us creating new ones each tick + * @type {Array} + */ + this.statePool = []; + + /** + * A very simple geometry used when drawing a filter effect to the screen + * @type {Quad} + */ + this.quad = new Quad(); + + this.quadUv = new QuadUv(); + + /** + * Temporary rect for maths + * @type {PIXI.Rectangle} + */ + this.tempRect = new Rectangle(); + + this.activeState = {}; + + /** + * this uniform group is attached to filter uniforms when used + * @type {UniformGroup} + */ + this.globalUniforms = new UniformGroup({ + sourceFrame: this.tempRect, + destinationFrame: this.tempRect, + + // legacy variables + filterArea: new Float32Array(4), + filterClamp: new Float32Array(4), + }, true); + } + + /** + * Adds a new filter to the System. + * + * @param {PIXI.DisplayObject} target - The target of the filter to render. + * @param {PIXI.Filter[]} filters - The filters to apply. + */ + push(target, filters) + { + const renderer = this.renderer; + const filterStack = this.renderer.renderTexture.defaultFilterStack; + const state = this.statePool.pop() || new FilterState(); + + let resolution = filters[0].resolution; + let padding = filters[0].padding; + let autoFit = filters[0].autoFit; + let legacy = filters[0].legacy; + + for (let i = 1; i < filters.length; i++) + { + const filter = filters[i]; + + // lets use the lowest resolution.. + resolution = Math.min(resolution, filter.resolution); + // and the largest amount of padding! + padding = Math.max(padding, filter.padding); + // only auto fit if all filters are autofit + autoFit = autoFit || filter.autoFit; + + legacy = legacy || filter.legacy; + } + + filterStack.push(state); + + state.resolution = resolution; + + state.legacy = legacy; + + // round to whole number based on resolution + // TODO move that to the shader too? + state.sourceFrame = target.filterArea || target.getBounds(true); + + state.sourceFrame.pad(padding); + + if (autoFit) + { + state.sourceFrame.fit(this.renderer.renderTexture.destinationFrame); + } + + state.sourceFrame.round(resolution); + + state.renderTexture = this.getPotFilterTexture(state.sourceFrame.width, state.sourceFrame.height, resolution); + state.filters = filters; + + state.destinationFrame.width = state.renderTexture.width; + state.destinationFrame.height = state.renderTexture.height; + + state.renderTexture.filterFrame = state.sourceFrame; + renderer.renderTexture.bind(state.renderTexture, state.sourceFrame);// /, state.destinationFrame); + renderer.renderTexture.clear(); + } + + /** + * Pops off the filter and applies it. + * + */ + pop() + { + const renderer = this.renderer; + const filterStack = renderer.renderTexture.defaultFilterStack; + const state = filterStack.pop(); + const filters = state.filters; + + this.activeState = state; + + const globalUniforms = this.globalUniforms.uniforms; + + globalUniforms.sourceFrame = state.sourceFrame; + globalUniforms.destinationFrame = state.destinationFrame; + globalUniforms.resolution = state.resolution; + + // only update the rect if its legacy.. + if (state.legacy) + { + const filterArea = globalUniforms.filterArea; + const filterClamp = globalUniforms.filterClamp; + + filterArea[0] = state.destinationFrame.width; + filterArea[1] = state.destinationFrame.height; + filterArea[2] = state.sourceFrame.x; + filterArea[3] = state.sourceFrame.y; + + filterClamp[0] = 0.5 / state.resolution / state.destinationFrame.width; + filterClamp[1] = 0.5 / state.resolution / state.destinationFrame.height; + filterClamp[2] = (state.sourceFrame.width - 0.5) / state.resolution / state.destinationFrame.width; + filterClamp[3] = (state.sourceFrame.height - 0.5) / state.resolution / state.destinationFrame.height; + } + + this.globalUniforms.update(); + + const lastState = filterStack[filterStack.length - 1]; + + if (filters.length === 1) + { + filters[0].apply(this, state.renderTexture, lastState.renderTexture, false, state); + + this.returnFilterTexture(state.renderTexture); + } + else + { + let flip = state.renderTexture; + let flop = this.getPotFilterTexture( + flip.width, + flip.height, + state.resolution + ); + + flop.filterFrame = flip.filterFrame; + + let i = 0; + + for (i = 0; i < filters.length - 1; ++i) + { + filters[i].apply(this, flip, flop, true, state); + + const t = flip; + + flip = flop; + flop = t; + } + + filters[i].apply(this, flip, lastState.renderTexture, false, state); + + this.returnFilterTexture(flip); + this.returnFilterTexture(flop); + } + + this.statePool.push(state); + } + + /** + * Draws a filter. + * + * @param {PIXI.Filter} filter - The filter to draw. + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + * @param {boolean} clear - Should the output be cleared before rendering to it + */ + applyFilter(filter, input, output, clear) + { + const renderer = this.renderer; + + renderer.renderTexture.bind(output, output ? output.filterFrame : null); + + if (clear) + { + // gl.disable(gl.SCISSOR_TEST); + renderer.renderTexture.clear(); + // gl.enable(gl.SCISSOR_TEST); + } + + // set the uniforms.. + filter.uniforms.uSampler = input; + filter.uniforms.filterGlobals = this.globalUniforms; + + // TODO make it so that the order of this does not matter.. + // because it does at the moment cos of global uniforms. + // they need to get resynced + + renderer.state.setState(filter.state); + renderer.shader.bind(filter); + + if (filter.legacy) + { + this.quadUv.map(input._frame, input.filterFrame); + + renderer.geometry.bind(this.quadUv); + renderer.geometry.draw(DRAW_MODES.TRIANGLES); + } + else + { + renderer.geometry.bind(this.quad); + renderer.geometry.draw(DRAW_MODES.TRIANGLE_STRIP); + } + } + + /** + * Calculates the mapped matrix. + * + * TODO playing around here.. this is temporary - (will end up in the shader) + * this returns a matrix that will normalise map filter cords in the filter to screen space + * + * @param {PIXI.Matrix} outputMatrix - the matrix to output to. + * @return {PIXI.Matrix} The mapped matrix. + */ + calculateScreenSpaceMatrix(outputMatrix) + { + const currentState = this.activeState; + + return filterTransforms.calculateScreenSpaceMatrix( + outputMatrix, + currentState.sourceFrame, + currentState.destinationFrame + ); + } + + /** + * This will map the filter coord so that a texture can be used based on the transform of a sprite + * + * @param {PIXI.Matrix} outputMatrix - The matrix to output to. + * @param {PIXI.Sprite} sprite - The sprite to map to. + * @return {PIXI.Matrix} The mapped matrix. + */ + calculateSpriteMatrix(outputMatrix, sprite) + { + const currentState = this.activeState; + + return filterTransforms.calculateSpriteMatrix( + outputMatrix, + currentState.sourceFrame, + currentState.destinationFrame, + sprite + ); + } + + /** + * Destroys this Filter System. + * + * @param {boolean} [contextLost=false] context was lost, do not free shaders + * + */ + destroy(contextLost = false) + { + if (!contextLost) + { + this.emptyPool(); + } + else + { + this.texturePool = {}; + } + } + + /** + * Gets a Power-of-Two render texture. + * + * TODO move to a seperate class could be on renderer? + * also - could cause issue with multiple contexts? + * + * @private + * @param {WebGLRenderingContext} gl - The webgl rendering context + * @param {number} minWidth - The minimum width of the render target. + * @param {number} minHeight - The minimum height of the render target. + * @param {number} resolution - The resolution of the render target. + * @return {PIXI.RenderTarget} The new render target. + */ + getPotFilterTexture(minWidth, minHeight, resolution) + { + minWidth = bitTwiddle.nextPow2(minWidth); + minHeight = bitTwiddle.nextPow2(minHeight); + resolution = resolution || 1; + + const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + + if (!this.texturePool[key]) + { + this.texturePool[key] = []; + } + + let renderTexture = this.texturePool[key].pop(); + + if (!renderTexture) + { + // temporary bypass cache.. + // internally - this will cause a texture to be bound.. + renderTexture = RenderTexture.create({ + width: minWidth, + height: minHeight, + resolution, + }); + } + + return renderTexture; + } + + /** + * Gets extra render texture to use inside current filter + * + * @param {number} resolution resolution of the renderTexture + * @returns {PIXI.RenderTexture} + */ + getFilterTexture(resolution) + { + const rt = this.activeState.renderTexture; + + const filterTexture = this.getPotFilterTexture(rt.width, rt.height, resolution || rt.baseTexture.resolution); + + filterTexture.filterFrame = rt.filterFrame; + + return filterTexture; + } + + /** + * Frees a render texture back into the pool. + * + * @param {PIXI.RenderTarget} renderTexture - The renderTarget to free + */ + returnFilterTexture(renderTexture) + { + renderTexture.filterFrame = null; + + const base = renderTexture.baseTexture; + + const minWidth = base.width; + const minHeight = base.height; + + const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + + this.texturePool[key].push(renderTexture); + } + + /** + * Empties the texture pool. + * + */ + emptyPool() + { + for (const i in this.texturePool) + { + const textures = this.texturePool[i]; + + if (textures) + { + for (let j = 0; j < textures.length; j++) + { + textures[j].destroy(true); + } + } + } + + this.texturePool = {}; + } +} diff --git a/packages/core/src/filters/defaultFilter.frag b/packages/core/src/filters/defaultFilter.frag new file mode 100644 index 0000000..a14c1be --- /dev/null +++ b/packages/core/src/filters/defaultFilter.frag @@ -0,0 +1,22 @@ +varying vec2 vTextureCoord; +varying vec2 vFilterCoord; + +uniform sampler2D uSampler; +uniform sampler2D filterSampler; + +void main(void){ + vec4 masky = texture2D(filterSampler, vFilterCoord); + vec4 sample = texture2D(uSampler, vTextureCoord); + vec4 color; + if(mod(vFilterCoord.x, 1.0) > 0.5) + { + color = vec4(1.0, 0.0, 0.0, 1.0); + } + else + { + color = vec4(0.0, 1.0, 0.0, 1.0); + } + // gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0); + gl_FragColor = mix(sample, masky, 0.5); + gl_FragColor *= sample.a; +} diff --git a/packages/core/src/filters/defaultFilter.vert b/packages/core/src/filters/defaultFilter.vert new file mode 100644 index 0000000..e1febca --- /dev/null +++ b/packages/core/src/filters/defaultFilter.vert @@ -0,0 +1,14 @@ +attribute vec2 aVertexPosition; +attribute vec2 aTextureCoord; + +uniform mat3 projectionMatrix; +uniform mat3 filterMatrix; + +varying vec2 vTextureCoord; +varying vec2 vFilterCoord; + +void main(void){ + gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); + vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy; + vTextureCoord = aTextureCoord ; +} diff --git a/packages/core/src/filters/filterTransforms.js b/packages/core/src/filters/filterTransforms.js new file mode 100644 index 0000000..87a3cd6 --- /dev/null +++ b/packages/core/src/filters/filterTransforms.js @@ -0,0 +1,50 @@ +import { Matrix } from '@pixi/math'; + +/** + * Calculates the mapped matrix + * @param filterArea {Rectangle} The filter area + * @param sprite {Sprite} the target sprite + * @param outputMatrix {Matrix} @alvin + * @private + */ +// this returns a matrix that will normalise map filter cords in the filter to screen space +export function calculateScreenSpaceMatrix(outputMatrix, filterArea, textureSize) +{ + // TODO unwrap? + const mappedMatrix = outputMatrix.identity(); + + mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); + + mappedMatrix.scale(textureSize.width, textureSize.height); + + return mappedMatrix; +} + +export function calculateNormalizedScreenSpaceMatrix(outputMatrix, filterArea, textureSize) +{ + const mappedMatrix = outputMatrix.identity(); + + mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); + + const translateScaleX = (textureSize.width / filterArea.width); + const translateScaleY = (textureSize.height / filterArea.height); + + mappedMatrix.scale(translateScaleX, translateScaleY); + + return mappedMatrix; +} + +// this will map the filter coord so that a texture can be used based on the transform of a sprite +export function calculateSpriteMatrix(outputMatrix, filterArea, textureSize, sprite) +{ + const orig = sprite._texture.orig; + const mappedMatrix = outputMatrix.set(textureSize.width, 0, 0, textureSize.height, filterArea.x, filterArea.y); + const worldTransform = sprite.worldTransform.copyTo(Matrix.TEMP_MATRIX); + + worldTransform.invert(); + mappedMatrix.prepend(worldTransform); + mappedMatrix.scale(1.0 / orig.width, 1.0 / orig.height); + mappedMatrix.translate(sprite.anchor.x, sprite.anchor.y); + + return mappedMatrix; +} diff --git a/packages/core/src/filters/spriteMask/SpriteMaskFilter.js b/packages/core/src/filters/spriteMask/SpriteMaskFilter.js new file mode 100644 index 0000000..91697b7 --- /dev/null +++ b/packages/core/src/filters/spriteMask/SpriteMaskFilter.js @@ -0,0 +1,63 @@ +import Filter from '../Filter'; +import { Matrix } from '@pixi/math'; +import vertex from './spriteMaskFilter.vert'; +import fragment from './spriteMaskFilter.frag'; +import { default as TextureMatrix } from '../../textures/TextureMatrix'; + +/** + * The SpriteMaskFilter class + * + * @class + * @extends PIXI.Filter + * @memberof PIXI + */ +export default class SpriteMaskFilter extends Filter +{ + /** + * @param {PIXI.Sprite} sprite - the target sprite + */ + constructor(sprite) + { + const maskMatrix = new Matrix(); + + super(vertex, fragment); + + sprite.renderable = false; + + this.maskSprite = sprite; + this.maskMatrix = maskMatrix; + } + + /** + * Applies the filter + * + * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + */ + apply(filterManager, input, output) + { + const maskSprite = this.maskSprite; + const tex = this.maskSprite.texture; + + if (!tex.valid) + { + return; + } + if (!tex.transform) + { + // margin = 0.0, let it bleed a bit, shader code becomes easier + // assuming that atlas textures were made with 1-pixel padding + tex.transform = new TextureMatrix(tex, 0.0); + } + tex.transform.update(); + + this.uniforms.mask = tex; + this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite) + .prepend(tex.transform.mapCoord); + this.uniforms.alpha = maskSprite.worldAlpha; + this.uniforms.maskClamp = tex.transform.uClampFrame; + + filterManager.applyFilter(this, input, output); + } +} diff --git a/packages/core/src/filters/spriteMask/spriteMaskFilter.frag b/packages/core/src/filters/spriteMask/spriteMaskFilter.frag new file mode 100644 index 0000000..0e4aef8 --- /dev/null +++ b/packages/core/src/filters/spriteMask/spriteMaskFilter.frag @@ -0,0 +1,23 @@ +varying vec2 vMaskCoord; +varying vec2 vTextureCoord; + +uniform sampler2D uSampler; +uniform sampler2D mask; +uniform float alpha; +uniform vec4 maskClamp; + +void main(void) +{ + float clip = step(3.5, + step(maskClamp.x, vMaskCoord.x) + + step(maskClamp.y, vMaskCoord.y) + + step(vMaskCoord.x, maskClamp.z) + + step(vMaskCoord.y, maskClamp.w)); + + vec4 original = texture2D(uSampler, vTextureCoord); + vec4 masky = texture2D(mask, vMaskCoord); + + original *= (masky.r * masky.a * alpha * clip); + + gl_FragColor = original; +} diff --git a/packages/core/src/filters/spriteMask/spriteMaskFilter.vert b/packages/core/src/filters/spriteMask/spriteMaskFilter.vert new file mode 100644 index 0000000..d23a30c --- /dev/null +++ b/packages/core/src/filters/spriteMask/spriteMaskFilter.vert @@ -0,0 +1,16 @@ +attribute vec2 aVertexPosition; +attribute vec2 aTextureCoord; + +uniform mat3 projectionMatrix; +uniform mat3 otherMatrix; + +varying vec2 vMaskCoord; +varying vec2 vTextureCoord; + +void main(void) +{ + gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); + + vTextureCoord = aTextureCoord; + vMaskCoord = ( otherMatrix * vec3( aTextureCoord, 1.0) ).xy; +} diff --git a/packages/core/src/framebuffer/FrameBuffer.js b/packages/core/src/framebuffer/FrameBuffer.js new file mode 100644 index 0000000..84e9fcc --- /dev/null +++ b/packages/core/src/framebuffer/FrameBuffer.js @@ -0,0 +1,106 @@ +import Texture from '../textures/BaseTexture'; +import { FORMATS, TYPES } from '@pixi/constants'; + +/** + * Frame buffer + * @class + * @memberof PIXI + */ +export default class FrameBuffer +{ + constructor(width, height) + { + this.width = width || 100; + this.height = height || 100; + + this.stencil = false; + this.depth = false; + + this.dirtyId = 0; + this.dirtyFormat = 0; + this.dirtySize = 0; + + this.depthTexture = null; + this.colorTextures = []; + + this.glFrameBuffers = {}; + } + + get colorTexture() + { + return this.colorTextures[0]; + } + + addColorTexture(index, texture) + { + // TODO add some validation to the texture - same width / height etc? + this.colorTextures[index || 0] = texture || new Texture(null, { scaleMode: 0, + resolution: 1, + mipmap: false, + width: this.width, + height: this.height });// || new Texture(); + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + addDepthTexture(texture) + { + /* eslint-disable max-len */ + this.depthTexture = texture || new Texture(null, { scaleMode: 0, + resolution: 1, + width: this.width, + height: this.height, + mipmap: false, + format: FORMATS.DEPTH_COMPONENT, + type: TYPES.UNSIGNED_SHORT });// UNSIGNED_SHORT; + /* eslint-disable max-len */ + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + enableDepth() + { + this.depth = true; + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + enableStencil() + { + this.stencil = true; + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + resize(width, height) + { + if (width === this.width && height === this.height) return; + + this.width = width; + this.height = height; + + this.dirtyId++; + this.dirtySize++; + + for (let i = 0; i < this.colorTextures.length; i++) + { + this.colorTextures[i].setSize(width, height); + } + + if (this.depthTexture) + { + this.depthTexture.setSize(width, height); + } + } +} diff --git a/packages/core/src/AbstractRenderer.js b/packages/core/src/AbstractRenderer.js new file mode 100644 index 0000000..64b2eb1 --- /dev/null +++ b/packages/core/src/AbstractRenderer.js @@ -0,0 +1,353 @@ +import { hex2string, hex2rgb } from '@pixi/utils'; +import { Matrix, Rectangle } from '@pixi/math'; +import { RENDERER_TYPE } from '@pixi/constants'; +import { settings } from '@pixi/settings'; +import { Container } from '@pixi/display'; +import RenderTexture from './renderTexture/RenderTexture'; +import EventEmitter from 'eventemitter3'; + +const tempMatrix = new Matrix(); + +/** + * 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 EventEmitter + * @memberof PIXI + */ +export default class AbstractRenderer extends EventEmitter +{ + // eslint-disable-next-line valid-jsdoc + /** + * @param {string} system - The name of the system this renderer is for. + * @param {object} [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.transparent=false] - If the render view is transparent, default false + * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false + * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. The + * resolution of the renderer retina would be 2. + * @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 {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when rendering, + * stopping pixel interpolation. + */ + constructor(system, options, arg2, arg3) + { + super(); + + // Support for constructor(system, screenWidth, screenHeight, options) + if (typeof options === 'number') + { + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + }, arg3); + } + + // 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 = RENDERER_TYPE.UNKNOWN; + + /** + * Measurements of the screen. (0, 0, screenWidth, screenHeight) + * + * Its safe to use as filterArea or hitArea for 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 1 + */ + this.resolution = options.resolution || settings.RESOLUTION; + + /** + * Whether the render view is transparent + * + * @member {boolean} + */ + this.transparent = options.transparent; + + /** + * Whether css dimensions of canvas view should be resized to screen dimensions automatically + * + * @member {boolean} + */ + this.autoResize = options.autoResize || false; + + /** + * Tracks the blend modes useful for this renderer. + * + * @member {object} + */ + this.blendModes = null; + + /** + * 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; + + /** + * If true PixiJS will Math.floor() x/y values when rendering, stopping pixel interpolation. + * Handy for crisp pixel art and speed on legacy devices. + * + * @member {boolean} + */ + this.roundPixels = options.roundPixels; + + /** + * The background color as a number. + * + * @member {number} + * @private + */ + this._backgroundColor = 0x000000; + + /** + * The background color as an [R, G, B] array. + * + * @member {number[]} + * @private + */ + this._backgroundColorRgba = [0, 0, 0, 0]; + + /** + * The background color as a string. + * + * @member {string} + * @private + */ + this._backgroundColorString = '#000000'; + + this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter + + /** + * This temporary display object used as the parent of the currently being rendered item + * + * @member {PIXI.DisplayObject} + * @private + */ + this._tempDisplayObjectParent = new Container(); + + /** + * The last root object that the renderer tried to render. + * + * @member {PIXI.DisplayObject} + * @private + */ + this._lastObjectRendered = this._tempDisplayObjectParent; + + /** + * Collection of plugins. + * @readonly + * @member {object} + */ + this.plugins = {}; + } + + /** + * Initialize the plugins. + * + * @protected + * @param {object} staticMap - The dictionary of staticly saved plugins. + */ + initPlugins(staticMap) + { + 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() + { + return this.view.width; + } + + /** + * Same as view.height, actual number of pixels in the canvas by vertical + * + * @member {number} + * @readonly + * @default 600 + */ + get height() + { + return this.view.height; + } + + /** + * Resizes the screen and canvas to the specified width and height + * Canvas dimensions are multiplied by resolution + * + * @param {number} screenWidth - the new width of the screen + * @param {number} screenHeight - the new height of the screen + */ + resize(screenWidth, screenHeight) + { + this.screen.width = screenWidth; + this.screen.height = screenHeight; + + this.view.width = screenWidth * this.resolution; + this.view.height = screenHeight * this.resolution; + + if (this.autoResize) + { + this.view.style.width = `${screenWidth}px`; + this.view.style.height = `${screenHeight}px`; + } + } + + /** + * 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 {PIXI.DisplayObject} displayObject - The displayObject the object will be generated from + * @param {number} scaleMode - Should be one of the scaleMode consts + * @param {number} resolution - The resolution / device pixel ratio of the texture being generated + * @param {PIXI.Rectangle} [region] - The region of the displayObject, that shall be rendered, + * if no region is specified, defaults to the local bounds of the displayObject. + * @return {PIXI.Texture} a texture of the graphics object + */ + generateTexture(displayObject, scaleMode, resolution, region) + { + region = region || displayObject.getLocalBounds(); + + // 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(region.width | 0, region.height | 0, scaleMode, resolution); + + tempMatrix.tx = -region.x; + tempMatrix.ty = -region.y; + + this.render(displayObject, renderTexture, false, tempMatrix, true); + + return renderTexture; + } + + /** + * Removes everything from the renderer and optionally removes the Canvas DOM element. + * + * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. + */ + destroy(removeView) + { + 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); + } + + this.plugins = null; + + this.type = RENDERER_TYPE.UNKNOWN; + + this.view = null; + + this.screen = null; + + this.resolution = 0; + + this.transparent = false; + + this.autoResize = false; + + this.blendModes = null; + + this.options = null; + + this.preserveDrawingBuffer = false; + this.clearBeforeRender = false; + + this.roundPixels = false; + + this._backgroundColor = 0; + this._backgroundColorRgba = null; + this._backgroundColorString = null; + + this._tempDisplayObjectParent = null; + this._lastObjectRendered = null; + } + + /** + * The background color to fill if not transparent + * + * @member {number} + */ + get backgroundColor() + { + return this._backgroundColor; + } + + set backgroundColor(value) // eslint-disable-line require-jsdoc + { + this._backgroundColor = value; + this._backgroundColorString = hex2string(value); + hex2rgb(value, this._backgroundColorRgba); + } +} diff --git a/packages/core/src/Renderer.js b/packages/core/src/Renderer.js new file mode 100644 index 0000000..2227e78 --- /dev/null +++ b/packages/core/src/Renderer.js @@ -0,0 +1,338 @@ +import AbstractRenderer from './AbstractRenderer'; +import { sayHello } from '@pixi/utils'; +import MaskSystem from './mask/MaskSystem'; +import StencilSystem from './mask/StencilSystem'; +import FilterSystem from './filters/FilterSystem'; +import FramebufferSystem from './framebuffer/FramebufferSystem'; +import RenderTextureSystem from './renderTexture/RenderTextureSystem'; +import TextureSystem from './textures/TextureSystem'; +import ProjectionSystem from './projection/ProjectionSystem'; +import StateSystem from './state/StateSystem'; +import GeometrySystem from './geometry/GeometrySystem'; +import ShaderSystem from './shader/ShaderSystem'; +import ContextSystem from './context/ContextSystem'; +import BatchSystem from './batch/BatchSystem'; +import TextureGCSystem from './textures/TextureGCSystem'; +import { RENDERER_TYPE } from '@pixi/constants'; +import UniformGroup from './shader/UniformGroup'; +import { Matrix } from '@pixi/math'; +import Runner from 'mini-runner'; + +/** + * 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 Render works by automatically managing webGLBatchs. + * 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 :) + * + * @class + * @memberof PIXI + * @extends PIXI.AbstractRenderer + */ +export default class Renderer extends AbstractRenderer +{ + // eslint-disable-next-line valid-jsdoc + /** + * + * @param {object} [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.transparent=false] - If the render view is transparent, default false + * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false + * @param {boolean} [options.antialias=false] - sets antialias. If not available natively then FXAA + * antialiasing is used + * @param {boolean} [options.forceFXAA=false] - forces FXAA antialiasing to be used over native. + * FXAA is faster, but may not always look as great + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. + * The resolution of the renderer retina would be 2. + * @param {boolean} [options.clearBeforeRender=true] - This sets if the renderer will clear + * the canvas or not before the new render pass. If you wish to set this to false, you *must* set + * preserveDrawingBuffer to `true`. + * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, + * enable this if you need to call toDataUrl on the webgl context. + * @param {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when + * rendering, stopping pixel interpolation. + * @param {number} [options.backgroundColor=0x000000] - The background color of the rendered area + * (shown if not transparent). + * @param {boolean} [options.legacy=false] - If true PixiJS will aim to ensure compatibility + * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. + * @param {string} [options.powerPreference] - Parameter passed to webgl context, set to "high-performance" + * for devices with dual graphics card + */ + constructor(options, arg2, arg3) + { + super('WebGL', options, arg2, arg3); + + /** + * The type of this renderer as a standardised const + * + * @member {number} + * @see PIXI.RENDERER_TYPE + */ + this.type = RENDERER_TYPE.WEBGL; + + // this will be set by the contextSystem (this.context) + this.gl = null; + this.CONTEXT_UID = 0; + this.legacy = !!options.legacy; + + // TODO legacy! + + // runners! + this.runners = { + destroy: new Runner('destroy'), + contextChange: new Runner('contextChange', 1), + reset: new Runner('reset'), + update: new Runner('update'), + postrender: new Runner('postrender'), + prerender: new Runner('prerender'), + resize: new Runner('resize', 2), + }; + + 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(GeometrySystem, 'geometry') + .addSystem(FramebufferSystem, 'framebuffer') + .addSystem(StencilSystem, 'stencil') + .addSystem(ProjectionSystem, 'projection') + .addSystem(TextureGCSystem, 'textureGC') + .addSystem(FilterSystem, 'filter') + .addSystem(RenderTextureSystem, 'renderTexture') + .addSystem(BatchSystem, 'batch'); + + this.initPlugins(Renderer.__plugins); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ + if (options.context) + { + this.context.initFromContext(options.context); + } + else + { + this.context.initFromOptions({ + alpha: this.transparent, + antialias: options.antialias, + premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', + stencil: true, + 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); + } + + /** + * Add a new system to the renderer. + * @param {class} ClassRef - Class reference + * @param {string} [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. + * @return {PIXI.Renderer} Return instance of renderer + */ + addSystem(ClassRef, name) + { + if (!name) + { + name = ClassRef.name; + } + + // TODO - read name from class.name.. + + /* + if(name.includes('System')) + { + name = name.replace('System', ''); + name = name.charAt(0).toLowerCase() + name.slice(1); + } + */ + + const system = new ClassRef(this); + + if (this[name]) + { + throw new Error(`Whoops! ${name} is already a manger`); + } + + this[name] = system; + + for (const i in this.runners) + { + this.runners[i].add(system); + } + + return this; + + /** + * 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. + */ + } + + /** + * Renders the object to its webGL view + * + * @param {PIXI.DisplayObject} displayObject - the object to be rendered + * @param {PIXI.RenderTexture} renderTexture - The render texture to render to. + * @param {boolean} [clear] - Should the canvas be cleared before the new render + * @param {PIXI.Transform} [transform] - A transform to apply to the render texture before rendering. + * @param {boolean} [skipUpdateTransform] - Should we skip the update transform pass? + */ + render(displayObject, renderTexture, clear, transform, skipUpdateTransform) + { + // can be handy to know! + this.renderingToScreen = !renderTexture; + + this.runners.prerender.run(); + this.emit('prerender'); + + // 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.parent; + + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = 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.run(); + + this.emit('postrender'); + } + + /** + * Resizes the webGL view to the specified width and height. + * + * @param {number} screenWidth - the new width of the screen + * @param {number} screenHeight - the new height of the screen + */ + resize(screenWidth, screenHeight) + { + AbstractRenderer.prototype.resize.call(this, screenWidth, screenHeight); + + this.runners.resize.run(screenWidth, screenHeight); + } + + /** + * Resets the WebGL state so you can render things however you fancy! + * + * @return {PIXI.Renderer} Returns itself. + */ + reset() + { + this.runners.reset.run(); + + return this; + } + + clear() + { + this.framebuffer.bind(); + this.framebuffer.clear(); + } + + /** + * Removes everything from the renderer (event listeners, spritebatch, etc...) + * + * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. + * See: https://github.com/pixijs/pixi.js/issues/2233 + */ + destroy(removeView) + { + this.runners.destroy.run(); + + // call base destroy + super.destroy(removeView); + + // TODO nullify all the managers.. + this.gl = null; + } + + /** + * 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 + * builds and excluding plugins. + * @name PIXI.Renderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. + */ + + /** + * Adds a plugin to the renderer. + * + * @method + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + static registerPlugin(pluginName, ctor) + { + Renderer.__plugins = Renderer.__plugins || {}; + Renderer.__plugins[pluginName] = ctor; + } +} diff --git a/packages/core/src/System.js b/packages/core/src/System.js new file mode 100644 index 0000000..05a10c8 --- /dev/null +++ b/packages/core/src/System.js @@ -0,0 +1,42 @@ +/** + * System is a base class used for extending systems used by the {@link PIXI.Renderer} + * @see PIXI.Renderer#addSystem + * @class + * @memberof PIXI + */ +export default class System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + /** + * The renderer this manager works for. + * + * @member {PIXI.Renderer} + */ + this.renderer = renderer; + + this.renderer.runners.contextChange.add(this); + } + + /** + * Generic method called when there is a WebGL context change. + * + */ + contextChange() + { + // do some codes init! + } + + /** + * Generic destroy methods to be overridden by the subclass + * + */ + destroy() + { + this.renderer.runners.contextChange.remove(this); + this.renderer = null; + } +} diff --git a/packages/core/src/batch/BatchSystem.js b/packages/core/src/batch/BatchSystem.js new file mode 100644 index 0000000..b04c249 --- /dev/null +++ b/packages/core/src/batch/BatchSystem.js @@ -0,0 +1,68 @@ +import System from '../System'; +import ObjectRenderer from './ObjectRenderer'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class BatchSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + super(renderer); + + /** + * An empty renderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.emptyRenderer = new ObjectRenderer(renderer); + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.currentRenderer = this.emptyRenderer; + } + + /** + * Changes the current renderer to the one given in parameter + * + * @param {PIXI.ObjectRenderer} objectRenderer - The object renderer to use. + */ + setObjectRenderer(objectRenderer) + { + if (this.currentRenderer === objectRenderer) + { + return; + } + + this.currentRenderer.stop(); + this.currentRenderer = objectRenderer; + + this.renderer.state.setState(objectRenderer.state); + + this.currentRenderer.start(); + } + + /** + * This should be called if you wish to do some custom rendering + * It will basically render anything that may be batched up such as sprites + * + */ + flush() + { + this.setObjectRenderer(this.emptyRenderer); + } + + reset() + { + this.setObjectRenderer(this.emptyRenderer); + } +} diff --git a/packages/core/src/batch/ObjectRenderer.js b/packages/core/src/batch/ObjectRenderer.js new file mode 100644 index 0000000..0c851a9 --- /dev/null +++ b/packages/core/src/batch/ObjectRenderer.js @@ -0,0 +1,48 @@ +import System from '../System'; + +/** + * Base for a common object renderer that can be used as a system renderer plugin. + * + * @class + * @extends PIXI.System + * @memberof PIXI + */ +export default class ObjectRenderer extends System +{ + /** + * Starts the renderer and sets the shader + * + */ + start() + { + // set the shader.. + } + + /** + * Stops the renderer + * + */ + stop() + { + this.flush(); + } + + /** + * Stub method for rendering content and emptying the current batch. + * + */ + flush() + { + // flush! + } + + /** + * Renders an object + * + * @param {PIXI.DisplayObject} object - The object to render. + */ + render(object) // eslint-disable-line no-unused-vars + { + // render the object + } +} diff --git a/packages/core/src/context/ContextSystem.js b/packages/core/src/context/ContextSystem.js new file mode 100644 index 0000000..301aab4 --- /dev/null +++ b/packages/core/src/context/ContextSystem.js @@ -0,0 +1,186 @@ +import System from '../System'; +import { settings } from '@pixi/settings'; + +let CONTEXT_UID = 0; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class ContextSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.webGLVersion = 1; + + this.handleContextLost = this.handleContextLost.bind(this); + this.handleContextRestored = this.handleContextRestored.bind(this); + + this.extensions = {}; + + renderer.view.addEventListener('webglcontextlost', this.handleContextLost, false); + renderer.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + } + + get isLost() + { + return (!this.gl || this.gl.isContextLost()); + } + + contextChange(gl) + { + this.gl = gl; + + // restore a context if it was previously lost + if (gl.isContextLost() && gl.getExtension('WEBGL_lose_context')) + { + gl.getExtension('WEBGL_lose_context').restoreContext(); + } + } + + initFromContext(gl) + { + this.gl = gl; + this.validateContext(gl); + this.renderer.gl = gl; + this.renderer.CONTEXT_UID = CONTEXT_UID++; + this.renderer.runners.contextChange.run(gl); + } + + initFromOptions(options) + { + const gl = this.createContext(this.renderer.view, options); + + this.initFromContext(gl); + } + + /** + * Helper class to create a webGL Context + * + * @class + * @memberof PIXI.glCore + * @param canvas {HTMLCanvasElement} the canvas element that we will get the context from + * @param options {Object} An options object that gets passed in to the canvas element containing the context attributes, + * see https://developer.mozilla.org/en/docs/Web/API/HTMLCanvasElement/getContext for the options available + * @return {WebGLRenderingContext} the WebGL context + */ + createContext(canvas, options) + { + let gl; + + if (settings.PREFER_WEBGL_2) + { + gl = canvas.getContext('webgl2', options); + } + + if (gl) + { + this.webGLVersion = 2; + } + else + { + this.webGLVersion = 1; + + gl = canvas.getContext('webgl', options) + || canvas.getContext('experimental-webgl', options); + + if (!gl) + { + // fail, not able to get a context + throw new Error('This browser does not support webGL. Try using the canvas renderer'); + } + } + + this.gl = gl; + + this.getExtensions(); + + return gl; + } + + getExtensions() + { + // time to set up default etensions that pixi uses.. + const gl = this.gl; + const extensions = this.extensions; + + if (this.webGLVersion === 1) + { + extensions.drawBuffers = gl.getExtension('WEBGL_draw_buffers'); + extensions.depthTexture = gl.getExtension('WEBKIT_WEBGL_depth_texture'); + extensions.floatTexture = gl.getExtension('OES_texture_float'); + extensions.loseContext = gl.getExtension('WEBGL_lose_context'); + + extensions.vertexArrayObject = gl.getExtension('OES_vertex_array_object') + || gl.getExtension('MOZ_OES_vertex_array_object') + || gl.getExtension('WEBKIT_OES_vertex_array_object'); + } + + // we don't use any specific WebGL 2 ones yet! + } + + /** + * Handles a lost webgl context + * + * @private + * @param {WebGLContextEvent} event - The context lost event. + */ + handleContextLost(event) + { + event.preventDefault(); + } + + /** + * Handles a restored webgl context + * + * @private + */ + handleContextRestored() + { + this.renderer.runners.contextChange.run(this.gl); + } + + destroy() + { + const view = this.renderer.view; + + // remove listeners + view.removeEventListener('webglcontextlost', this.handleContextLost); + view.removeEventListener('webglcontextrestored', this.handleContextRestored); + + this.gl.useProgram(null); + + if (this.extensions.loseContext) + { + this.extensions.loseContext.loseContext(); + } + } + + postrender() + { + this.gl.flush(); + } + + validateContext(gl) + { + const attributes = gl.getContextAttributes(); + + // this is going to be fairly simple for now.. but at least we have room to grow! + if (!attributes.stencil) + { + /* eslint-disable max-len */ + + /* eslint-disable no-console */ + console.warn('Provided WebGL context does not have a stencil buffer, masks may not render correctly'); + /* eslint-enable no-console */ + + /* eslint-enable max-len */ + } + } +} diff --git a/packages/core/src/filters/Filter.js b/packages/core/src/filters/Filter.js new file mode 100644 index 0000000..9d8cad8 --- /dev/null +++ b/packages/core/src/filters/Filter.js @@ -0,0 +1,140 @@ +import Shader from '../shader/Shader'; +import Program from '../shader/Program'; +import State from '../state/State'; +import { settings } from '@pixi/settings'; +// import extractUniformsFromSrc from './extractUniformsFromSrc'; +import defaultVertex from './defaultFilter.vert'; +import defaultFragment from './defaultFilter.frag'; + +// let math = require('../../math'); +/** + * @class + * @memberof PIXI + * @extends PIXI.Shader + */ +export default class Filter extends Shader +{ + /** + * @param {string} [vertexSrc] - The source of the vertex shader. + * @param {string} [fragmentSrc] - The source of the fragment shader. + * @param {object} [uniforms] - Custom uniforms to use to augment the built-in ones. + */ + constructor(vertexSrc, fragmentSrc, uniforms) + { + const program = Program.from(vertexSrc, fragmentSrc); + + super(program, uniforms); + + /** + * The padding of the filter. Some filters require extra space to breath such as a blur. + * Increasing this will add extra width and height to the bounds of the object that the + * filter is applied to. + * + * @member {number} + */ + this.padding = 0; + + /** + * The resolution of the filter. Setting this to be lower will lower the quality but + * increase the performance of the filter. + * + * @member {number} + */ + this.resolution = settings.RESOLUTION; + + /** + * If enabled is true the filter is applied, if false it will not. + * + * @member {boolean} + */ + this.enabled = true; + + /** + * If enabled, PixiJS will fit the filter area into boundaries for better performance. + * Switch it off if it does not work for specific shader. + * + * @member {boolean} + */ + this.autoFit = true; + + /** + * Legacy filters use position and uvs from attributes + * @member {boolean} + * @readonly + */ + this.legacy = !!this.program.attributeData.aTextureCoord; + + /** + * the webGL state the filter requires to render + * @member {PIXI.State} + */ + this.state = new State(); + } + + /** + * Applies the filter + * + * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + * @param {boolean} clear - Should the output be cleared before rendering to it + * @param {object} [currentState] - It's current state of filter. + * There are some useful properties in the currentState : + * target, filters, sourceFrame, destinationFrame, renderTarget, resolution + */ + apply(filterManager, input, output, clear, currentState, derp) // eslint-disable-line no-unused-vars + { + // do as you please! + + filterManager.applyFilter(this, input, output, clear, currentState, derp); + + // or just do a regular render.. + } + + /** + * Sets the blendmode of the filter + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL + */ + get blendMode() + { + return this.state.blendMode; + } + + set blendMode(value) // eslint-disable-line require-jsdoc + { + this.state.blendMode = value; + } + + /** + * The default vertex shader source + * + * @static + * @constant + */ + static get defaultVertexSrc() + { + return defaultVertex; + } + + /** + * The default fragment shader source + * + * @static + * @constant + */ + static get defaultFragmentSrc() + { + return defaultFragment; + } +} + +/** + * Used for caching shader IDs + * + * @static + * @private + */ +Filter.SOURCE_KEY_MAP = {}; + diff --git a/packages/core/src/filters/FilterSystem.js b/packages/core/src/filters/FilterSystem.js new file mode 100644 index 0000000..3174318 --- /dev/null +++ b/packages/core/src/filters/FilterSystem.js @@ -0,0 +1,431 @@ +import System from '../System'; + +import RenderTexture from '../renderTexture/RenderTexture'; +import Quad from '../utils/Quad'; +import QuadUv from '../utils/QuadUv'; +import { Rectangle } from '@pixi/math'; +import * as filterTransforms from './filterTransforms'; +import bitTwiddle from 'bit-twiddle'; +import UniformGroup from '../shader/UniformGroup'; +import { DRAW_MODES } from '@pixi/constants'; + +// +/** + * @ignore + * @class + */ +class FilterState +{ + /** + * + */ + constructor() + { + this.renderTexture = null; + this.sourceFrame = new Rectangle(); + this.destinationFrame = new Rectangle(); + this.filters = []; + this.target = null; + this.legacy = false; + this.resolution = 1; + } +} + +/** + * @class + * @memberof PIXI.systems + * @extends PIXI.System + */ +export default class FilterSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + /** + * stores a bunch of PO2 textures used for filtering + * @type {Object} + */ + this.texturePool = {}; + + /** + * a pool for storing filter states, save us creating new ones each tick + * @type {Array} + */ + this.statePool = []; + + /** + * A very simple geometry used when drawing a filter effect to the screen + * @type {Quad} + */ + this.quad = new Quad(); + + this.quadUv = new QuadUv(); + + /** + * Temporary rect for maths + * @type {PIXI.Rectangle} + */ + this.tempRect = new Rectangle(); + + this.activeState = {}; + + /** + * this uniform group is attached to filter uniforms when used + * @type {UniformGroup} + */ + this.globalUniforms = new UniformGroup({ + sourceFrame: this.tempRect, + destinationFrame: this.tempRect, + + // legacy variables + filterArea: new Float32Array(4), + filterClamp: new Float32Array(4), + }, true); + } + + /** + * Adds a new filter to the System. + * + * @param {PIXI.DisplayObject} target - The target of the filter to render. + * @param {PIXI.Filter[]} filters - The filters to apply. + */ + push(target, filters) + { + const renderer = this.renderer; + const filterStack = this.renderer.renderTexture.defaultFilterStack; + const state = this.statePool.pop() || new FilterState(); + + let resolution = filters[0].resolution; + let padding = filters[0].padding; + let autoFit = filters[0].autoFit; + let legacy = filters[0].legacy; + + for (let i = 1; i < filters.length; i++) + { + const filter = filters[i]; + + // lets use the lowest resolution.. + resolution = Math.min(resolution, filter.resolution); + // and the largest amount of padding! + padding = Math.max(padding, filter.padding); + // only auto fit if all filters are autofit + autoFit = autoFit || filter.autoFit; + + legacy = legacy || filter.legacy; + } + + filterStack.push(state); + + state.resolution = resolution; + + state.legacy = legacy; + + // round to whole number based on resolution + // TODO move that to the shader too? + state.sourceFrame = target.filterArea || target.getBounds(true); + + state.sourceFrame.pad(padding); + + if (autoFit) + { + state.sourceFrame.fit(this.renderer.renderTexture.destinationFrame); + } + + state.sourceFrame.round(resolution); + + state.renderTexture = this.getPotFilterTexture(state.sourceFrame.width, state.sourceFrame.height, resolution); + state.filters = filters; + + state.destinationFrame.width = state.renderTexture.width; + state.destinationFrame.height = state.renderTexture.height; + + state.renderTexture.filterFrame = state.sourceFrame; + renderer.renderTexture.bind(state.renderTexture, state.sourceFrame);// /, state.destinationFrame); + renderer.renderTexture.clear(); + } + + /** + * Pops off the filter and applies it. + * + */ + pop() + { + const renderer = this.renderer; + const filterStack = renderer.renderTexture.defaultFilterStack; + const state = filterStack.pop(); + const filters = state.filters; + + this.activeState = state; + + const globalUniforms = this.globalUniforms.uniforms; + + globalUniforms.sourceFrame = state.sourceFrame; + globalUniforms.destinationFrame = state.destinationFrame; + globalUniforms.resolution = state.resolution; + + // only update the rect if its legacy.. + if (state.legacy) + { + const filterArea = globalUniforms.filterArea; + const filterClamp = globalUniforms.filterClamp; + + filterArea[0] = state.destinationFrame.width; + filterArea[1] = state.destinationFrame.height; + filterArea[2] = state.sourceFrame.x; + filterArea[3] = state.sourceFrame.y; + + filterClamp[0] = 0.5 / state.resolution / state.destinationFrame.width; + filterClamp[1] = 0.5 / state.resolution / state.destinationFrame.height; + filterClamp[2] = (state.sourceFrame.width - 0.5) / state.resolution / state.destinationFrame.width; + filterClamp[3] = (state.sourceFrame.height - 0.5) / state.resolution / state.destinationFrame.height; + } + + this.globalUniforms.update(); + + const lastState = filterStack[filterStack.length - 1]; + + if (filters.length === 1) + { + filters[0].apply(this, state.renderTexture, lastState.renderTexture, false, state); + + this.returnFilterTexture(state.renderTexture); + } + else + { + let flip = state.renderTexture; + let flop = this.getPotFilterTexture( + flip.width, + flip.height, + state.resolution + ); + + flop.filterFrame = flip.filterFrame; + + let i = 0; + + for (i = 0; i < filters.length - 1; ++i) + { + filters[i].apply(this, flip, flop, true, state); + + const t = flip; + + flip = flop; + flop = t; + } + + filters[i].apply(this, flip, lastState.renderTexture, false, state); + + this.returnFilterTexture(flip); + this.returnFilterTexture(flop); + } + + this.statePool.push(state); + } + + /** + * Draws a filter. + * + * @param {PIXI.Filter} filter - The filter to draw. + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + * @param {boolean} clear - Should the output be cleared before rendering to it + */ + applyFilter(filter, input, output, clear) + { + const renderer = this.renderer; + + renderer.renderTexture.bind(output, output ? output.filterFrame : null); + + if (clear) + { + // gl.disable(gl.SCISSOR_TEST); + renderer.renderTexture.clear(); + // gl.enable(gl.SCISSOR_TEST); + } + + // set the uniforms.. + filter.uniforms.uSampler = input; + filter.uniforms.filterGlobals = this.globalUniforms; + + // TODO make it so that the order of this does not matter.. + // because it does at the moment cos of global uniforms. + // they need to get resynced + + renderer.state.setState(filter.state); + renderer.shader.bind(filter); + + if (filter.legacy) + { + this.quadUv.map(input._frame, input.filterFrame); + + renderer.geometry.bind(this.quadUv); + renderer.geometry.draw(DRAW_MODES.TRIANGLES); + } + else + { + renderer.geometry.bind(this.quad); + renderer.geometry.draw(DRAW_MODES.TRIANGLE_STRIP); + } + } + + /** + * Calculates the mapped matrix. + * + * TODO playing around here.. this is temporary - (will end up in the shader) + * this returns a matrix that will normalise map filter cords in the filter to screen space + * + * @param {PIXI.Matrix} outputMatrix - the matrix to output to. + * @return {PIXI.Matrix} The mapped matrix. + */ + calculateScreenSpaceMatrix(outputMatrix) + { + const currentState = this.activeState; + + return filterTransforms.calculateScreenSpaceMatrix( + outputMatrix, + currentState.sourceFrame, + currentState.destinationFrame + ); + } + + /** + * This will map the filter coord so that a texture can be used based on the transform of a sprite + * + * @param {PIXI.Matrix} outputMatrix - The matrix to output to. + * @param {PIXI.Sprite} sprite - The sprite to map to. + * @return {PIXI.Matrix} The mapped matrix. + */ + calculateSpriteMatrix(outputMatrix, sprite) + { + const currentState = this.activeState; + + return filterTransforms.calculateSpriteMatrix( + outputMatrix, + currentState.sourceFrame, + currentState.destinationFrame, + sprite + ); + } + + /** + * Destroys this Filter System. + * + * @param {boolean} [contextLost=false] context was lost, do not free shaders + * + */ + destroy(contextLost = false) + { + if (!contextLost) + { + this.emptyPool(); + } + else + { + this.texturePool = {}; + } + } + + /** + * Gets a Power-of-Two render texture. + * + * TODO move to a seperate class could be on renderer? + * also - could cause issue with multiple contexts? + * + * @private + * @param {WebGLRenderingContext} gl - The webgl rendering context + * @param {number} minWidth - The minimum width of the render target. + * @param {number} minHeight - The minimum height of the render target. + * @param {number} resolution - The resolution of the render target. + * @return {PIXI.RenderTarget} The new render target. + */ + getPotFilterTexture(minWidth, minHeight, resolution) + { + minWidth = bitTwiddle.nextPow2(minWidth); + minHeight = bitTwiddle.nextPow2(minHeight); + resolution = resolution || 1; + + const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + + if (!this.texturePool[key]) + { + this.texturePool[key] = []; + } + + let renderTexture = this.texturePool[key].pop(); + + if (!renderTexture) + { + // temporary bypass cache.. + // internally - this will cause a texture to be bound.. + renderTexture = RenderTexture.create({ + width: minWidth, + height: minHeight, + resolution, + }); + } + + return renderTexture; + } + + /** + * Gets extra render texture to use inside current filter + * + * @param {number} resolution resolution of the renderTexture + * @returns {PIXI.RenderTexture} + */ + getFilterTexture(resolution) + { + const rt = this.activeState.renderTexture; + + const filterTexture = this.getPotFilterTexture(rt.width, rt.height, resolution || rt.baseTexture.resolution); + + filterTexture.filterFrame = rt.filterFrame; + + return filterTexture; + } + + /** + * Frees a render texture back into the pool. + * + * @param {PIXI.RenderTarget} renderTexture - The renderTarget to free + */ + returnFilterTexture(renderTexture) + { + renderTexture.filterFrame = null; + + const base = renderTexture.baseTexture; + + const minWidth = base.width; + const minHeight = base.height; + + const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + + this.texturePool[key].push(renderTexture); + } + + /** + * Empties the texture pool. + * + */ + emptyPool() + { + for (const i in this.texturePool) + { + const textures = this.texturePool[i]; + + if (textures) + { + for (let j = 0; j < textures.length; j++) + { + textures[j].destroy(true); + } + } + } + + this.texturePool = {}; + } +} diff --git a/packages/core/src/filters/defaultFilter.frag b/packages/core/src/filters/defaultFilter.frag new file mode 100644 index 0000000..a14c1be --- /dev/null +++ b/packages/core/src/filters/defaultFilter.frag @@ -0,0 +1,22 @@ +varying vec2 vTextureCoord; +varying vec2 vFilterCoord; + +uniform sampler2D uSampler; +uniform sampler2D filterSampler; + +void main(void){ + vec4 masky = texture2D(filterSampler, vFilterCoord); + vec4 sample = texture2D(uSampler, vTextureCoord); + vec4 color; + if(mod(vFilterCoord.x, 1.0) > 0.5) + { + color = vec4(1.0, 0.0, 0.0, 1.0); + } + else + { + color = vec4(0.0, 1.0, 0.0, 1.0); + } + // gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0); + gl_FragColor = mix(sample, masky, 0.5); + gl_FragColor *= sample.a; +} diff --git a/packages/core/src/filters/defaultFilter.vert b/packages/core/src/filters/defaultFilter.vert new file mode 100644 index 0000000..e1febca --- /dev/null +++ b/packages/core/src/filters/defaultFilter.vert @@ -0,0 +1,14 @@ +attribute vec2 aVertexPosition; +attribute vec2 aTextureCoord; + +uniform mat3 projectionMatrix; +uniform mat3 filterMatrix; + +varying vec2 vTextureCoord; +varying vec2 vFilterCoord; + +void main(void){ + gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); + vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy; + vTextureCoord = aTextureCoord ; +} diff --git a/packages/core/src/filters/filterTransforms.js b/packages/core/src/filters/filterTransforms.js new file mode 100644 index 0000000..87a3cd6 --- /dev/null +++ b/packages/core/src/filters/filterTransforms.js @@ -0,0 +1,50 @@ +import { Matrix } from '@pixi/math'; + +/** + * Calculates the mapped matrix + * @param filterArea {Rectangle} The filter area + * @param sprite {Sprite} the target sprite + * @param outputMatrix {Matrix} @alvin + * @private + */ +// this returns a matrix that will normalise map filter cords in the filter to screen space +export function calculateScreenSpaceMatrix(outputMatrix, filterArea, textureSize) +{ + // TODO unwrap? + const mappedMatrix = outputMatrix.identity(); + + mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); + + mappedMatrix.scale(textureSize.width, textureSize.height); + + return mappedMatrix; +} + +export function calculateNormalizedScreenSpaceMatrix(outputMatrix, filterArea, textureSize) +{ + const mappedMatrix = outputMatrix.identity(); + + mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); + + const translateScaleX = (textureSize.width / filterArea.width); + const translateScaleY = (textureSize.height / filterArea.height); + + mappedMatrix.scale(translateScaleX, translateScaleY); + + return mappedMatrix; +} + +// this will map the filter coord so that a texture can be used based on the transform of a sprite +export function calculateSpriteMatrix(outputMatrix, filterArea, textureSize, sprite) +{ + const orig = sprite._texture.orig; + const mappedMatrix = outputMatrix.set(textureSize.width, 0, 0, textureSize.height, filterArea.x, filterArea.y); + const worldTransform = sprite.worldTransform.copyTo(Matrix.TEMP_MATRIX); + + worldTransform.invert(); + mappedMatrix.prepend(worldTransform); + mappedMatrix.scale(1.0 / orig.width, 1.0 / orig.height); + mappedMatrix.translate(sprite.anchor.x, sprite.anchor.y); + + return mappedMatrix; +} diff --git a/packages/core/src/filters/spriteMask/SpriteMaskFilter.js b/packages/core/src/filters/spriteMask/SpriteMaskFilter.js new file mode 100644 index 0000000..91697b7 --- /dev/null +++ b/packages/core/src/filters/spriteMask/SpriteMaskFilter.js @@ -0,0 +1,63 @@ +import Filter from '../Filter'; +import { Matrix } from '@pixi/math'; +import vertex from './spriteMaskFilter.vert'; +import fragment from './spriteMaskFilter.frag'; +import { default as TextureMatrix } from '../../textures/TextureMatrix'; + +/** + * The SpriteMaskFilter class + * + * @class + * @extends PIXI.Filter + * @memberof PIXI + */ +export default class SpriteMaskFilter extends Filter +{ + /** + * @param {PIXI.Sprite} sprite - the target sprite + */ + constructor(sprite) + { + const maskMatrix = new Matrix(); + + super(vertex, fragment); + + sprite.renderable = false; + + this.maskSprite = sprite; + this.maskMatrix = maskMatrix; + } + + /** + * Applies the filter + * + * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + */ + apply(filterManager, input, output) + { + const maskSprite = this.maskSprite; + const tex = this.maskSprite.texture; + + if (!tex.valid) + { + return; + } + if (!tex.transform) + { + // margin = 0.0, let it bleed a bit, shader code becomes easier + // assuming that atlas textures were made with 1-pixel padding + tex.transform = new TextureMatrix(tex, 0.0); + } + tex.transform.update(); + + this.uniforms.mask = tex; + this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite) + .prepend(tex.transform.mapCoord); + this.uniforms.alpha = maskSprite.worldAlpha; + this.uniforms.maskClamp = tex.transform.uClampFrame; + + filterManager.applyFilter(this, input, output); + } +} diff --git a/packages/core/src/filters/spriteMask/spriteMaskFilter.frag b/packages/core/src/filters/spriteMask/spriteMaskFilter.frag new file mode 100644 index 0000000..0e4aef8 --- /dev/null +++ b/packages/core/src/filters/spriteMask/spriteMaskFilter.frag @@ -0,0 +1,23 @@ +varying vec2 vMaskCoord; +varying vec2 vTextureCoord; + +uniform sampler2D uSampler; +uniform sampler2D mask; +uniform float alpha; +uniform vec4 maskClamp; + +void main(void) +{ + float clip = step(3.5, + step(maskClamp.x, vMaskCoord.x) + + step(maskClamp.y, vMaskCoord.y) + + step(vMaskCoord.x, maskClamp.z) + + step(vMaskCoord.y, maskClamp.w)); + + vec4 original = texture2D(uSampler, vTextureCoord); + vec4 masky = texture2D(mask, vMaskCoord); + + original *= (masky.r * masky.a * alpha * clip); + + gl_FragColor = original; +} diff --git a/packages/core/src/filters/spriteMask/spriteMaskFilter.vert b/packages/core/src/filters/spriteMask/spriteMaskFilter.vert new file mode 100644 index 0000000..d23a30c --- /dev/null +++ b/packages/core/src/filters/spriteMask/spriteMaskFilter.vert @@ -0,0 +1,16 @@ +attribute vec2 aVertexPosition; +attribute vec2 aTextureCoord; + +uniform mat3 projectionMatrix; +uniform mat3 otherMatrix; + +varying vec2 vMaskCoord; +varying vec2 vTextureCoord; + +void main(void) +{ + gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); + + vTextureCoord = aTextureCoord; + vMaskCoord = ( otherMatrix * vec3( aTextureCoord, 1.0) ).xy; +} diff --git a/packages/core/src/framebuffer/FrameBuffer.js b/packages/core/src/framebuffer/FrameBuffer.js new file mode 100644 index 0000000..84e9fcc --- /dev/null +++ b/packages/core/src/framebuffer/FrameBuffer.js @@ -0,0 +1,106 @@ +import Texture from '../textures/BaseTexture'; +import { FORMATS, TYPES } from '@pixi/constants'; + +/** + * Frame buffer + * @class + * @memberof PIXI + */ +export default class FrameBuffer +{ + constructor(width, height) + { + this.width = width || 100; + this.height = height || 100; + + this.stencil = false; + this.depth = false; + + this.dirtyId = 0; + this.dirtyFormat = 0; + this.dirtySize = 0; + + this.depthTexture = null; + this.colorTextures = []; + + this.glFrameBuffers = {}; + } + + get colorTexture() + { + return this.colorTextures[0]; + } + + addColorTexture(index, texture) + { + // TODO add some validation to the texture - same width / height etc? + this.colorTextures[index || 0] = texture || new Texture(null, { scaleMode: 0, + resolution: 1, + mipmap: false, + width: this.width, + height: this.height });// || new Texture(); + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + addDepthTexture(texture) + { + /* eslint-disable max-len */ + this.depthTexture = texture || new Texture(null, { scaleMode: 0, + resolution: 1, + width: this.width, + height: this.height, + mipmap: false, + format: FORMATS.DEPTH_COMPONENT, + type: TYPES.UNSIGNED_SHORT });// UNSIGNED_SHORT; + /* eslint-disable max-len */ + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + enableDepth() + { + this.depth = true; + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + enableStencil() + { + this.stencil = true; + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + resize(width, height) + { + if (width === this.width && height === this.height) return; + + this.width = width; + this.height = height; + + this.dirtyId++; + this.dirtySize++; + + for (let i = 0; i < this.colorTextures.length; i++) + { + this.colorTextures[i].setSize(width, height); + } + + if (this.depthTexture) + { + this.depthTexture.setSize(width, height); + } + } +} diff --git a/packages/core/src/framebuffer/FramebufferSystem.js b/packages/core/src/framebuffer/FramebufferSystem.js new file mode 100644 index 0000000..2cc2437 --- /dev/null +++ b/packages/core/src/framebuffer/FramebufferSystem.js @@ -0,0 +1,246 @@ +import System from '../System'; +import { Rectangle } from '@pixi/math'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class FramebufferSystem extends System +{ + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + contextChange() + { + this.gl = this.renderer.gl; + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + this.current = null; + this.viewport = new Rectangle(); + + this.drawBufferExtension = this.renderer.context.extensions.drawBuffers; + } + + bind(framebuffer, frame) + { + const gl = this.gl; + + this.current = framebuffer; + + if (framebuffer) + { + // TODO cacheing layer! + + const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID] || this.initFramebuffer(framebuffer); + + gl.bindFramebuffer(gl.FRAMEBUFFER, fbo.framebuffer); + // makesure all textures are unbound.. + + // now check for updates... + if (fbo.dirtyId !== framebuffer.dirtyId) + { + fbo.dirtyId = framebuffer.dirtyId; + + if (fbo.dirtyFormat !== framebuffer.dirtyFormat) + { + fbo.dirtyFormat = framebuffer.dirtyFormat; + this.updateFramebuffer(framebuffer); + } + else if (fbo.dirtySize !== framebuffer.dirtySize) + { + fbo.dirtySize = framebuffer.dirtySize; + this.resizeFramebuffer(framebuffer); + } + } + + for (let i = 0; i < framebuffer.colorTextures.length; i++) + { + if (framebuffer.colorTextures[i].texturePart) + { + this.renderer.texture.unbind(framebuffer.colorTextures[i].texture); + } + else + { + this.renderer.texture.unbind(framebuffer.colorTextures[i]); + } + } + + if (framebuffer.depthTexture) + { + this.renderer.texture.unbind(framebuffer.depthTexture); + } + + if (frame) + { + this.setViewport(frame.x, frame.y, frame.width, frame.height); + } + else + { + this.setViewport(0, 0, framebuffer.width, framebuffer.height); + } + } + else + { + gl.bindFramebuffer(gl.FRAMEBUFFER, null); + + if (frame) + { + this.setViewport(frame.x, frame.y, frame.width, frame.height); + } + else + { + this.setViewport(0, 0, this.renderer.width, this.renderer.height); + } + } + } + + setViewport(x, y, width, height) + { + const v = this.viewport; + + if (v.width !== width || v.height !== height || v.x !== x || v.y !== y) + { + v.x = x; + v.y = y; + v.width = width; + v.height = height; + + this.gl.viewport(x, y, width, height); + } + } + + get size() + { + if (this.current) + { + // TODO store temp + return { x: 0, y: 0, width: this.current.width, height: this.current.height }; + } + + return { x: 0, y: 0, width: this.renderer.width, height: this.renderer.height }; + } + + clear(r, g, b, a) + { + const gl = this.gl; + + // TODO clear color can be set only one right? + gl.clearColor(r, g, b, a); + gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); + } + + // private functions... + + initFramebuffer(framebuffer) + { + const gl = this.gl; + + // TODO - make this a class? + const fbo = { + framebuffer: gl.createFramebuffer(), + stencil: null, + dirtyId: 0, + dirtyFormat: 0, + dirtySize: 0, + }; + + framebuffer.glFrameBuffers[this.CONTEXT_UID] = fbo; + + return fbo; + } + + resizeFramebuffer(framebuffer) + { + const gl = this.gl; + + if (framebuffer.stencil || framebuffer.depth) + { + gl.bindRenderbuffer(gl.RENDERBUFFER, this.stencil); + gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); + } + } + + updateFramebuffer(framebuffer) + { + const gl = this.gl; + + const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID]; + + // bind the color texture + const colorTextures = framebuffer.colorTextures; + + let count = colorTextures.length; + + if (!this.drawBufferExtension) + { + count = Math.min(count, 1); + } + + const activeTextures = []; + + for (let i = 0; i < count; i++) + { + const texture = framebuffer.colorTextures[i]; + + if (texture.texturePart) + { + this.renderer.texture.bind(texture.texture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.COLOR_ATTACHMENT0 + i, + gl.TEXTURE_CUBE_MAP_NEGATIVE_X + texture.side, + texture.texture._glTextures[this.CONTEXT_UID].texture, + 0); + } + else + { + this.renderer.texture.bind(texture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.COLOR_ATTACHMENT0 + i, + gl.TEXTURE_2D, + texture._glTextures[this.CONTEXT_UID].texture, + 0); + } + + activeTextures.push(gl.COLOR_ATTACHMENT0 + i); + } + + if (this.drawBufferExtension && activeTextures.length > 1) + { + this.drawBufferExtension.drawBuffersWEBGL(activeTextures); + } + + if (framebuffer.depthTexture) + { + const depthTextureExt = this.renderer.context.extensions.depthTexture; + + if (depthTextureExt) + { + const depthTexture = framebuffer.depthTexture; + + this.renderer.texture.bind(depthTexture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.DEPTH_ATTACHMENT, + gl.TEXTURE_2D, + depthTexture._glTextures[this.CONTEXT_UID].texture, + 0); + } + } + + if (framebuffer.stencil || framebuffer.depth) + { + fbo.stencil = gl.createRenderbuffer(); + + gl.bindRenderbuffer(gl.RENDERBUFFER, fbo.stencil); + + // TODO.. this is depth AND stencil? + gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, fbo.stencil); + gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); + // fbo.enableStencil(); + } + } +} diff --git a/packages/core/src/AbstractRenderer.js b/packages/core/src/AbstractRenderer.js new file mode 100644 index 0000000..64b2eb1 --- /dev/null +++ b/packages/core/src/AbstractRenderer.js @@ -0,0 +1,353 @@ +import { hex2string, hex2rgb } from '@pixi/utils'; +import { Matrix, Rectangle } from '@pixi/math'; +import { RENDERER_TYPE } from '@pixi/constants'; +import { settings } from '@pixi/settings'; +import { Container } from '@pixi/display'; +import RenderTexture from './renderTexture/RenderTexture'; +import EventEmitter from 'eventemitter3'; + +const tempMatrix = new Matrix(); + +/** + * 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 EventEmitter + * @memberof PIXI + */ +export default class AbstractRenderer extends EventEmitter +{ + // eslint-disable-next-line valid-jsdoc + /** + * @param {string} system - The name of the system this renderer is for. + * @param {object} [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.transparent=false] - If the render view is transparent, default false + * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false + * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. The + * resolution of the renderer retina would be 2. + * @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 {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when rendering, + * stopping pixel interpolation. + */ + constructor(system, options, arg2, arg3) + { + super(); + + // Support for constructor(system, screenWidth, screenHeight, options) + if (typeof options === 'number') + { + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + }, arg3); + } + + // 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 = RENDERER_TYPE.UNKNOWN; + + /** + * Measurements of the screen. (0, 0, screenWidth, screenHeight) + * + * Its safe to use as filterArea or hitArea for 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 1 + */ + this.resolution = options.resolution || settings.RESOLUTION; + + /** + * Whether the render view is transparent + * + * @member {boolean} + */ + this.transparent = options.transparent; + + /** + * Whether css dimensions of canvas view should be resized to screen dimensions automatically + * + * @member {boolean} + */ + this.autoResize = options.autoResize || false; + + /** + * Tracks the blend modes useful for this renderer. + * + * @member {object} + */ + this.blendModes = null; + + /** + * 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; + + /** + * If true PixiJS will Math.floor() x/y values when rendering, stopping pixel interpolation. + * Handy for crisp pixel art and speed on legacy devices. + * + * @member {boolean} + */ + this.roundPixels = options.roundPixels; + + /** + * The background color as a number. + * + * @member {number} + * @private + */ + this._backgroundColor = 0x000000; + + /** + * The background color as an [R, G, B] array. + * + * @member {number[]} + * @private + */ + this._backgroundColorRgba = [0, 0, 0, 0]; + + /** + * The background color as a string. + * + * @member {string} + * @private + */ + this._backgroundColorString = '#000000'; + + this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter + + /** + * This temporary display object used as the parent of the currently being rendered item + * + * @member {PIXI.DisplayObject} + * @private + */ + this._tempDisplayObjectParent = new Container(); + + /** + * The last root object that the renderer tried to render. + * + * @member {PIXI.DisplayObject} + * @private + */ + this._lastObjectRendered = this._tempDisplayObjectParent; + + /** + * Collection of plugins. + * @readonly + * @member {object} + */ + this.plugins = {}; + } + + /** + * Initialize the plugins. + * + * @protected + * @param {object} staticMap - The dictionary of staticly saved plugins. + */ + initPlugins(staticMap) + { + 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() + { + return this.view.width; + } + + /** + * Same as view.height, actual number of pixels in the canvas by vertical + * + * @member {number} + * @readonly + * @default 600 + */ + get height() + { + return this.view.height; + } + + /** + * Resizes the screen and canvas to the specified width and height + * Canvas dimensions are multiplied by resolution + * + * @param {number} screenWidth - the new width of the screen + * @param {number} screenHeight - the new height of the screen + */ + resize(screenWidth, screenHeight) + { + this.screen.width = screenWidth; + this.screen.height = screenHeight; + + this.view.width = screenWidth * this.resolution; + this.view.height = screenHeight * this.resolution; + + if (this.autoResize) + { + this.view.style.width = `${screenWidth}px`; + this.view.style.height = `${screenHeight}px`; + } + } + + /** + * 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 {PIXI.DisplayObject} displayObject - The displayObject the object will be generated from + * @param {number} scaleMode - Should be one of the scaleMode consts + * @param {number} resolution - The resolution / device pixel ratio of the texture being generated + * @param {PIXI.Rectangle} [region] - The region of the displayObject, that shall be rendered, + * if no region is specified, defaults to the local bounds of the displayObject. + * @return {PIXI.Texture} a texture of the graphics object + */ + generateTexture(displayObject, scaleMode, resolution, region) + { + region = region || displayObject.getLocalBounds(); + + // 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(region.width | 0, region.height | 0, scaleMode, resolution); + + tempMatrix.tx = -region.x; + tempMatrix.ty = -region.y; + + this.render(displayObject, renderTexture, false, tempMatrix, true); + + return renderTexture; + } + + /** + * Removes everything from the renderer and optionally removes the Canvas DOM element. + * + * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. + */ + destroy(removeView) + { + 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); + } + + this.plugins = null; + + this.type = RENDERER_TYPE.UNKNOWN; + + this.view = null; + + this.screen = null; + + this.resolution = 0; + + this.transparent = false; + + this.autoResize = false; + + this.blendModes = null; + + this.options = null; + + this.preserveDrawingBuffer = false; + this.clearBeforeRender = false; + + this.roundPixels = false; + + this._backgroundColor = 0; + this._backgroundColorRgba = null; + this._backgroundColorString = null; + + this._tempDisplayObjectParent = null; + this._lastObjectRendered = null; + } + + /** + * The background color to fill if not transparent + * + * @member {number} + */ + get backgroundColor() + { + return this._backgroundColor; + } + + set backgroundColor(value) // eslint-disable-line require-jsdoc + { + this._backgroundColor = value; + this._backgroundColorString = hex2string(value); + hex2rgb(value, this._backgroundColorRgba); + } +} diff --git a/packages/core/src/Renderer.js b/packages/core/src/Renderer.js new file mode 100644 index 0000000..2227e78 --- /dev/null +++ b/packages/core/src/Renderer.js @@ -0,0 +1,338 @@ +import AbstractRenderer from './AbstractRenderer'; +import { sayHello } from '@pixi/utils'; +import MaskSystem from './mask/MaskSystem'; +import StencilSystem from './mask/StencilSystem'; +import FilterSystem from './filters/FilterSystem'; +import FramebufferSystem from './framebuffer/FramebufferSystem'; +import RenderTextureSystem from './renderTexture/RenderTextureSystem'; +import TextureSystem from './textures/TextureSystem'; +import ProjectionSystem from './projection/ProjectionSystem'; +import StateSystem from './state/StateSystem'; +import GeometrySystem from './geometry/GeometrySystem'; +import ShaderSystem from './shader/ShaderSystem'; +import ContextSystem from './context/ContextSystem'; +import BatchSystem from './batch/BatchSystem'; +import TextureGCSystem from './textures/TextureGCSystem'; +import { RENDERER_TYPE } from '@pixi/constants'; +import UniformGroup from './shader/UniformGroup'; +import { Matrix } from '@pixi/math'; +import Runner from 'mini-runner'; + +/** + * 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 Render works by automatically managing webGLBatchs. + * 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 :) + * + * @class + * @memberof PIXI + * @extends PIXI.AbstractRenderer + */ +export default class Renderer extends AbstractRenderer +{ + // eslint-disable-next-line valid-jsdoc + /** + * + * @param {object} [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.transparent=false] - If the render view is transparent, default false + * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false + * @param {boolean} [options.antialias=false] - sets antialias. If not available natively then FXAA + * antialiasing is used + * @param {boolean} [options.forceFXAA=false] - forces FXAA antialiasing to be used over native. + * FXAA is faster, but may not always look as great + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. + * The resolution of the renderer retina would be 2. + * @param {boolean} [options.clearBeforeRender=true] - This sets if the renderer will clear + * the canvas or not before the new render pass. If you wish to set this to false, you *must* set + * preserveDrawingBuffer to `true`. + * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, + * enable this if you need to call toDataUrl on the webgl context. + * @param {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when + * rendering, stopping pixel interpolation. + * @param {number} [options.backgroundColor=0x000000] - The background color of the rendered area + * (shown if not transparent). + * @param {boolean} [options.legacy=false] - If true PixiJS will aim to ensure compatibility + * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. + * @param {string} [options.powerPreference] - Parameter passed to webgl context, set to "high-performance" + * for devices with dual graphics card + */ + constructor(options, arg2, arg3) + { + super('WebGL', options, arg2, arg3); + + /** + * The type of this renderer as a standardised const + * + * @member {number} + * @see PIXI.RENDERER_TYPE + */ + this.type = RENDERER_TYPE.WEBGL; + + // this will be set by the contextSystem (this.context) + this.gl = null; + this.CONTEXT_UID = 0; + this.legacy = !!options.legacy; + + // TODO legacy! + + // runners! + this.runners = { + destroy: new Runner('destroy'), + contextChange: new Runner('contextChange', 1), + reset: new Runner('reset'), + update: new Runner('update'), + postrender: new Runner('postrender'), + prerender: new Runner('prerender'), + resize: new Runner('resize', 2), + }; + + 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(GeometrySystem, 'geometry') + .addSystem(FramebufferSystem, 'framebuffer') + .addSystem(StencilSystem, 'stencil') + .addSystem(ProjectionSystem, 'projection') + .addSystem(TextureGCSystem, 'textureGC') + .addSystem(FilterSystem, 'filter') + .addSystem(RenderTextureSystem, 'renderTexture') + .addSystem(BatchSystem, 'batch'); + + this.initPlugins(Renderer.__plugins); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ + if (options.context) + { + this.context.initFromContext(options.context); + } + else + { + this.context.initFromOptions({ + alpha: this.transparent, + antialias: options.antialias, + premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', + stencil: true, + 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); + } + + /** + * Add a new system to the renderer. + * @param {class} ClassRef - Class reference + * @param {string} [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. + * @return {PIXI.Renderer} Return instance of renderer + */ + addSystem(ClassRef, name) + { + if (!name) + { + name = ClassRef.name; + } + + // TODO - read name from class.name.. + + /* + if(name.includes('System')) + { + name = name.replace('System', ''); + name = name.charAt(0).toLowerCase() + name.slice(1); + } + */ + + const system = new ClassRef(this); + + if (this[name]) + { + throw new Error(`Whoops! ${name} is already a manger`); + } + + this[name] = system; + + for (const i in this.runners) + { + this.runners[i].add(system); + } + + return this; + + /** + * 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. + */ + } + + /** + * Renders the object to its webGL view + * + * @param {PIXI.DisplayObject} displayObject - the object to be rendered + * @param {PIXI.RenderTexture} renderTexture - The render texture to render to. + * @param {boolean} [clear] - Should the canvas be cleared before the new render + * @param {PIXI.Transform} [transform] - A transform to apply to the render texture before rendering. + * @param {boolean} [skipUpdateTransform] - Should we skip the update transform pass? + */ + render(displayObject, renderTexture, clear, transform, skipUpdateTransform) + { + // can be handy to know! + this.renderingToScreen = !renderTexture; + + this.runners.prerender.run(); + this.emit('prerender'); + + // 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.parent; + + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = 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.run(); + + this.emit('postrender'); + } + + /** + * Resizes the webGL view to the specified width and height. + * + * @param {number} screenWidth - the new width of the screen + * @param {number} screenHeight - the new height of the screen + */ + resize(screenWidth, screenHeight) + { + AbstractRenderer.prototype.resize.call(this, screenWidth, screenHeight); + + this.runners.resize.run(screenWidth, screenHeight); + } + + /** + * Resets the WebGL state so you can render things however you fancy! + * + * @return {PIXI.Renderer} Returns itself. + */ + reset() + { + this.runners.reset.run(); + + return this; + } + + clear() + { + this.framebuffer.bind(); + this.framebuffer.clear(); + } + + /** + * Removes everything from the renderer (event listeners, spritebatch, etc...) + * + * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. + * See: https://github.com/pixijs/pixi.js/issues/2233 + */ + destroy(removeView) + { + this.runners.destroy.run(); + + // call base destroy + super.destroy(removeView); + + // TODO nullify all the managers.. + this.gl = null; + } + + /** + * 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 + * builds and excluding plugins. + * @name PIXI.Renderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. + */ + + /** + * Adds a plugin to the renderer. + * + * @method + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + static registerPlugin(pluginName, ctor) + { + Renderer.__plugins = Renderer.__plugins || {}; + Renderer.__plugins[pluginName] = ctor; + } +} diff --git a/packages/core/src/System.js b/packages/core/src/System.js new file mode 100644 index 0000000..05a10c8 --- /dev/null +++ b/packages/core/src/System.js @@ -0,0 +1,42 @@ +/** + * System is a base class used for extending systems used by the {@link PIXI.Renderer} + * @see PIXI.Renderer#addSystem + * @class + * @memberof PIXI + */ +export default class System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + /** + * The renderer this manager works for. + * + * @member {PIXI.Renderer} + */ + this.renderer = renderer; + + this.renderer.runners.contextChange.add(this); + } + + /** + * Generic method called when there is a WebGL context change. + * + */ + contextChange() + { + // do some codes init! + } + + /** + * Generic destroy methods to be overridden by the subclass + * + */ + destroy() + { + this.renderer.runners.contextChange.remove(this); + this.renderer = null; + } +} diff --git a/packages/core/src/batch/BatchSystem.js b/packages/core/src/batch/BatchSystem.js new file mode 100644 index 0000000..b04c249 --- /dev/null +++ b/packages/core/src/batch/BatchSystem.js @@ -0,0 +1,68 @@ +import System from '../System'; +import ObjectRenderer from './ObjectRenderer'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class BatchSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + super(renderer); + + /** + * An empty renderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.emptyRenderer = new ObjectRenderer(renderer); + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.currentRenderer = this.emptyRenderer; + } + + /** + * Changes the current renderer to the one given in parameter + * + * @param {PIXI.ObjectRenderer} objectRenderer - The object renderer to use. + */ + setObjectRenderer(objectRenderer) + { + if (this.currentRenderer === objectRenderer) + { + return; + } + + this.currentRenderer.stop(); + this.currentRenderer = objectRenderer; + + this.renderer.state.setState(objectRenderer.state); + + this.currentRenderer.start(); + } + + /** + * This should be called if you wish to do some custom rendering + * It will basically render anything that may be batched up such as sprites + * + */ + flush() + { + this.setObjectRenderer(this.emptyRenderer); + } + + reset() + { + this.setObjectRenderer(this.emptyRenderer); + } +} diff --git a/packages/core/src/batch/ObjectRenderer.js b/packages/core/src/batch/ObjectRenderer.js new file mode 100644 index 0000000..0c851a9 --- /dev/null +++ b/packages/core/src/batch/ObjectRenderer.js @@ -0,0 +1,48 @@ +import System from '../System'; + +/** + * Base for a common object renderer that can be used as a system renderer plugin. + * + * @class + * @extends PIXI.System + * @memberof PIXI + */ +export default class ObjectRenderer extends System +{ + /** + * Starts the renderer and sets the shader + * + */ + start() + { + // set the shader.. + } + + /** + * Stops the renderer + * + */ + stop() + { + this.flush(); + } + + /** + * Stub method for rendering content and emptying the current batch. + * + */ + flush() + { + // flush! + } + + /** + * Renders an object + * + * @param {PIXI.DisplayObject} object - The object to render. + */ + render(object) // eslint-disable-line no-unused-vars + { + // render the object + } +} diff --git a/packages/core/src/context/ContextSystem.js b/packages/core/src/context/ContextSystem.js new file mode 100644 index 0000000..301aab4 --- /dev/null +++ b/packages/core/src/context/ContextSystem.js @@ -0,0 +1,186 @@ +import System from '../System'; +import { settings } from '@pixi/settings'; + +let CONTEXT_UID = 0; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class ContextSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.webGLVersion = 1; + + this.handleContextLost = this.handleContextLost.bind(this); + this.handleContextRestored = this.handleContextRestored.bind(this); + + this.extensions = {}; + + renderer.view.addEventListener('webglcontextlost', this.handleContextLost, false); + renderer.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + } + + get isLost() + { + return (!this.gl || this.gl.isContextLost()); + } + + contextChange(gl) + { + this.gl = gl; + + // restore a context if it was previously lost + if (gl.isContextLost() && gl.getExtension('WEBGL_lose_context')) + { + gl.getExtension('WEBGL_lose_context').restoreContext(); + } + } + + initFromContext(gl) + { + this.gl = gl; + this.validateContext(gl); + this.renderer.gl = gl; + this.renderer.CONTEXT_UID = CONTEXT_UID++; + this.renderer.runners.contextChange.run(gl); + } + + initFromOptions(options) + { + const gl = this.createContext(this.renderer.view, options); + + this.initFromContext(gl); + } + + /** + * Helper class to create a webGL Context + * + * @class + * @memberof PIXI.glCore + * @param canvas {HTMLCanvasElement} the canvas element that we will get the context from + * @param options {Object} An options object that gets passed in to the canvas element containing the context attributes, + * see https://developer.mozilla.org/en/docs/Web/API/HTMLCanvasElement/getContext for the options available + * @return {WebGLRenderingContext} the WebGL context + */ + createContext(canvas, options) + { + let gl; + + if (settings.PREFER_WEBGL_2) + { + gl = canvas.getContext('webgl2', options); + } + + if (gl) + { + this.webGLVersion = 2; + } + else + { + this.webGLVersion = 1; + + gl = canvas.getContext('webgl', options) + || canvas.getContext('experimental-webgl', options); + + if (!gl) + { + // fail, not able to get a context + throw new Error('This browser does not support webGL. Try using the canvas renderer'); + } + } + + this.gl = gl; + + this.getExtensions(); + + return gl; + } + + getExtensions() + { + // time to set up default etensions that pixi uses.. + const gl = this.gl; + const extensions = this.extensions; + + if (this.webGLVersion === 1) + { + extensions.drawBuffers = gl.getExtension('WEBGL_draw_buffers'); + extensions.depthTexture = gl.getExtension('WEBKIT_WEBGL_depth_texture'); + extensions.floatTexture = gl.getExtension('OES_texture_float'); + extensions.loseContext = gl.getExtension('WEBGL_lose_context'); + + extensions.vertexArrayObject = gl.getExtension('OES_vertex_array_object') + || gl.getExtension('MOZ_OES_vertex_array_object') + || gl.getExtension('WEBKIT_OES_vertex_array_object'); + } + + // we don't use any specific WebGL 2 ones yet! + } + + /** + * Handles a lost webgl context + * + * @private + * @param {WebGLContextEvent} event - The context lost event. + */ + handleContextLost(event) + { + event.preventDefault(); + } + + /** + * Handles a restored webgl context + * + * @private + */ + handleContextRestored() + { + this.renderer.runners.contextChange.run(this.gl); + } + + destroy() + { + const view = this.renderer.view; + + // remove listeners + view.removeEventListener('webglcontextlost', this.handleContextLost); + view.removeEventListener('webglcontextrestored', this.handleContextRestored); + + this.gl.useProgram(null); + + if (this.extensions.loseContext) + { + this.extensions.loseContext.loseContext(); + } + } + + postrender() + { + this.gl.flush(); + } + + validateContext(gl) + { + const attributes = gl.getContextAttributes(); + + // this is going to be fairly simple for now.. but at least we have room to grow! + if (!attributes.stencil) + { + /* eslint-disable max-len */ + + /* eslint-disable no-console */ + console.warn('Provided WebGL context does not have a stencil buffer, masks may not render correctly'); + /* eslint-enable no-console */ + + /* eslint-enable max-len */ + } + } +} diff --git a/packages/core/src/filters/Filter.js b/packages/core/src/filters/Filter.js new file mode 100644 index 0000000..9d8cad8 --- /dev/null +++ b/packages/core/src/filters/Filter.js @@ -0,0 +1,140 @@ +import Shader from '../shader/Shader'; +import Program from '../shader/Program'; +import State from '../state/State'; +import { settings } from '@pixi/settings'; +// import extractUniformsFromSrc from './extractUniformsFromSrc'; +import defaultVertex from './defaultFilter.vert'; +import defaultFragment from './defaultFilter.frag'; + +// let math = require('../../math'); +/** + * @class + * @memberof PIXI + * @extends PIXI.Shader + */ +export default class Filter extends Shader +{ + /** + * @param {string} [vertexSrc] - The source of the vertex shader. + * @param {string} [fragmentSrc] - The source of the fragment shader. + * @param {object} [uniforms] - Custom uniforms to use to augment the built-in ones. + */ + constructor(vertexSrc, fragmentSrc, uniforms) + { + const program = Program.from(vertexSrc, fragmentSrc); + + super(program, uniforms); + + /** + * The padding of the filter. Some filters require extra space to breath such as a blur. + * Increasing this will add extra width and height to the bounds of the object that the + * filter is applied to. + * + * @member {number} + */ + this.padding = 0; + + /** + * The resolution of the filter. Setting this to be lower will lower the quality but + * increase the performance of the filter. + * + * @member {number} + */ + this.resolution = settings.RESOLUTION; + + /** + * If enabled is true the filter is applied, if false it will not. + * + * @member {boolean} + */ + this.enabled = true; + + /** + * If enabled, PixiJS will fit the filter area into boundaries for better performance. + * Switch it off if it does not work for specific shader. + * + * @member {boolean} + */ + this.autoFit = true; + + /** + * Legacy filters use position and uvs from attributes + * @member {boolean} + * @readonly + */ + this.legacy = !!this.program.attributeData.aTextureCoord; + + /** + * the webGL state the filter requires to render + * @member {PIXI.State} + */ + this.state = new State(); + } + + /** + * Applies the filter + * + * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + * @param {boolean} clear - Should the output be cleared before rendering to it + * @param {object} [currentState] - It's current state of filter. + * There are some useful properties in the currentState : + * target, filters, sourceFrame, destinationFrame, renderTarget, resolution + */ + apply(filterManager, input, output, clear, currentState, derp) // eslint-disable-line no-unused-vars + { + // do as you please! + + filterManager.applyFilter(this, input, output, clear, currentState, derp); + + // or just do a regular render.. + } + + /** + * Sets the blendmode of the filter + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL + */ + get blendMode() + { + return this.state.blendMode; + } + + set blendMode(value) // eslint-disable-line require-jsdoc + { + this.state.blendMode = value; + } + + /** + * The default vertex shader source + * + * @static + * @constant + */ + static get defaultVertexSrc() + { + return defaultVertex; + } + + /** + * The default fragment shader source + * + * @static + * @constant + */ + static get defaultFragmentSrc() + { + return defaultFragment; + } +} + +/** + * Used for caching shader IDs + * + * @static + * @private + */ +Filter.SOURCE_KEY_MAP = {}; + diff --git a/packages/core/src/filters/FilterSystem.js b/packages/core/src/filters/FilterSystem.js new file mode 100644 index 0000000..3174318 --- /dev/null +++ b/packages/core/src/filters/FilterSystem.js @@ -0,0 +1,431 @@ +import System from '../System'; + +import RenderTexture from '../renderTexture/RenderTexture'; +import Quad from '../utils/Quad'; +import QuadUv from '../utils/QuadUv'; +import { Rectangle } from '@pixi/math'; +import * as filterTransforms from './filterTransforms'; +import bitTwiddle from 'bit-twiddle'; +import UniformGroup from '../shader/UniformGroup'; +import { DRAW_MODES } from '@pixi/constants'; + +// +/** + * @ignore + * @class + */ +class FilterState +{ + /** + * + */ + constructor() + { + this.renderTexture = null; + this.sourceFrame = new Rectangle(); + this.destinationFrame = new Rectangle(); + this.filters = []; + this.target = null; + this.legacy = false; + this.resolution = 1; + } +} + +/** + * @class + * @memberof PIXI.systems + * @extends PIXI.System + */ +export default class FilterSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + /** + * stores a bunch of PO2 textures used for filtering + * @type {Object} + */ + this.texturePool = {}; + + /** + * a pool for storing filter states, save us creating new ones each tick + * @type {Array} + */ + this.statePool = []; + + /** + * A very simple geometry used when drawing a filter effect to the screen + * @type {Quad} + */ + this.quad = new Quad(); + + this.quadUv = new QuadUv(); + + /** + * Temporary rect for maths + * @type {PIXI.Rectangle} + */ + this.tempRect = new Rectangle(); + + this.activeState = {}; + + /** + * this uniform group is attached to filter uniforms when used + * @type {UniformGroup} + */ + this.globalUniforms = new UniformGroup({ + sourceFrame: this.tempRect, + destinationFrame: this.tempRect, + + // legacy variables + filterArea: new Float32Array(4), + filterClamp: new Float32Array(4), + }, true); + } + + /** + * Adds a new filter to the System. + * + * @param {PIXI.DisplayObject} target - The target of the filter to render. + * @param {PIXI.Filter[]} filters - The filters to apply. + */ + push(target, filters) + { + const renderer = this.renderer; + const filterStack = this.renderer.renderTexture.defaultFilterStack; + const state = this.statePool.pop() || new FilterState(); + + let resolution = filters[0].resolution; + let padding = filters[0].padding; + let autoFit = filters[0].autoFit; + let legacy = filters[0].legacy; + + for (let i = 1; i < filters.length; i++) + { + const filter = filters[i]; + + // lets use the lowest resolution.. + resolution = Math.min(resolution, filter.resolution); + // and the largest amount of padding! + padding = Math.max(padding, filter.padding); + // only auto fit if all filters are autofit + autoFit = autoFit || filter.autoFit; + + legacy = legacy || filter.legacy; + } + + filterStack.push(state); + + state.resolution = resolution; + + state.legacy = legacy; + + // round to whole number based on resolution + // TODO move that to the shader too? + state.sourceFrame = target.filterArea || target.getBounds(true); + + state.sourceFrame.pad(padding); + + if (autoFit) + { + state.sourceFrame.fit(this.renderer.renderTexture.destinationFrame); + } + + state.sourceFrame.round(resolution); + + state.renderTexture = this.getPotFilterTexture(state.sourceFrame.width, state.sourceFrame.height, resolution); + state.filters = filters; + + state.destinationFrame.width = state.renderTexture.width; + state.destinationFrame.height = state.renderTexture.height; + + state.renderTexture.filterFrame = state.sourceFrame; + renderer.renderTexture.bind(state.renderTexture, state.sourceFrame);// /, state.destinationFrame); + renderer.renderTexture.clear(); + } + + /** + * Pops off the filter and applies it. + * + */ + pop() + { + const renderer = this.renderer; + const filterStack = renderer.renderTexture.defaultFilterStack; + const state = filterStack.pop(); + const filters = state.filters; + + this.activeState = state; + + const globalUniforms = this.globalUniforms.uniforms; + + globalUniforms.sourceFrame = state.sourceFrame; + globalUniforms.destinationFrame = state.destinationFrame; + globalUniforms.resolution = state.resolution; + + // only update the rect if its legacy.. + if (state.legacy) + { + const filterArea = globalUniforms.filterArea; + const filterClamp = globalUniforms.filterClamp; + + filterArea[0] = state.destinationFrame.width; + filterArea[1] = state.destinationFrame.height; + filterArea[2] = state.sourceFrame.x; + filterArea[3] = state.sourceFrame.y; + + filterClamp[0] = 0.5 / state.resolution / state.destinationFrame.width; + filterClamp[1] = 0.5 / state.resolution / state.destinationFrame.height; + filterClamp[2] = (state.sourceFrame.width - 0.5) / state.resolution / state.destinationFrame.width; + filterClamp[3] = (state.sourceFrame.height - 0.5) / state.resolution / state.destinationFrame.height; + } + + this.globalUniforms.update(); + + const lastState = filterStack[filterStack.length - 1]; + + if (filters.length === 1) + { + filters[0].apply(this, state.renderTexture, lastState.renderTexture, false, state); + + this.returnFilterTexture(state.renderTexture); + } + else + { + let flip = state.renderTexture; + let flop = this.getPotFilterTexture( + flip.width, + flip.height, + state.resolution + ); + + flop.filterFrame = flip.filterFrame; + + let i = 0; + + for (i = 0; i < filters.length - 1; ++i) + { + filters[i].apply(this, flip, flop, true, state); + + const t = flip; + + flip = flop; + flop = t; + } + + filters[i].apply(this, flip, lastState.renderTexture, false, state); + + this.returnFilterTexture(flip); + this.returnFilterTexture(flop); + } + + this.statePool.push(state); + } + + /** + * Draws a filter. + * + * @param {PIXI.Filter} filter - The filter to draw. + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + * @param {boolean} clear - Should the output be cleared before rendering to it + */ + applyFilter(filter, input, output, clear) + { + const renderer = this.renderer; + + renderer.renderTexture.bind(output, output ? output.filterFrame : null); + + if (clear) + { + // gl.disable(gl.SCISSOR_TEST); + renderer.renderTexture.clear(); + // gl.enable(gl.SCISSOR_TEST); + } + + // set the uniforms.. + filter.uniforms.uSampler = input; + filter.uniforms.filterGlobals = this.globalUniforms; + + // TODO make it so that the order of this does not matter.. + // because it does at the moment cos of global uniforms. + // they need to get resynced + + renderer.state.setState(filter.state); + renderer.shader.bind(filter); + + if (filter.legacy) + { + this.quadUv.map(input._frame, input.filterFrame); + + renderer.geometry.bind(this.quadUv); + renderer.geometry.draw(DRAW_MODES.TRIANGLES); + } + else + { + renderer.geometry.bind(this.quad); + renderer.geometry.draw(DRAW_MODES.TRIANGLE_STRIP); + } + } + + /** + * Calculates the mapped matrix. + * + * TODO playing around here.. this is temporary - (will end up in the shader) + * this returns a matrix that will normalise map filter cords in the filter to screen space + * + * @param {PIXI.Matrix} outputMatrix - the matrix to output to. + * @return {PIXI.Matrix} The mapped matrix. + */ + calculateScreenSpaceMatrix(outputMatrix) + { + const currentState = this.activeState; + + return filterTransforms.calculateScreenSpaceMatrix( + outputMatrix, + currentState.sourceFrame, + currentState.destinationFrame + ); + } + + /** + * This will map the filter coord so that a texture can be used based on the transform of a sprite + * + * @param {PIXI.Matrix} outputMatrix - The matrix to output to. + * @param {PIXI.Sprite} sprite - The sprite to map to. + * @return {PIXI.Matrix} The mapped matrix. + */ + calculateSpriteMatrix(outputMatrix, sprite) + { + const currentState = this.activeState; + + return filterTransforms.calculateSpriteMatrix( + outputMatrix, + currentState.sourceFrame, + currentState.destinationFrame, + sprite + ); + } + + /** + * Destroys this Filter System. + * + * @param {boolean} [contextLost=false] context was lost, do not free shaders + * + */ + destroy(contextLost = false) + { + if (!contextLost) + { + this.emptyPool(); + } + else + { + this.texturePool = {}; + } + } + + /** + * Gets a Power-of-Two render texture. + * + * TODO move to a seperate class could be on renderer? + * also - could cause issue with multiple contexts? + * + * @private + * @param {WebGLRenderingContext} gl - The webgl rendering context + * @param {number} minWidth - The minimum width of the render target. + * @param {number} minHeight - The minimum height of the render target. + * @param {number} resolution - The resolution of the render target. + * @return {PIXI.RenderTarget} The new render target. + */ + getPotFilterTexture(minWidth, minHeight, resolution) + { + minWidth = bitTwiddle.nextPow2(minWidth); + minHeight = bitTwiddle.nextPow2(minHeight); + resolution = resolution || 1; + + const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + + if (!this.texturePool[key]) + { + this.texturePool[key] = []; + } + + let renderTexture = this.texturePool[key].pop(); + + if (!renderTexture) + { + // temporary bypass cache.. + // internally - this will cause a texture to be bound.. + renderTexture = RenderTexture.create({ + width: minWidth, + height: minHeight, + resolution, + }); + } + + return renderTexture; + } + + /** + * Gets extra render texture to use inside current filter + * + * @param {number} resolution resolution of the renderTexture + * @returns {PIXI.RenderTexture} + */ + getFilterTexture(resolution) + { + const rt = this.activeState.renderTexture; + + const filterTexture = this.getPotFilterTexture(rt.width, rt.height, resolution || rt.baseTexture.resolution); + + filterTexture.filterFrame = rt.filterFrame; + + return filterTexture; + } + + /** + * Frees a render texture back into the pool. + * + * @param {PIXI.RenderTarget} renderTexture - The renderTarget to free + */ + returnFilterTexture(renderTexture) + { + renderTexture.filterFrame = null; + + const base = renderTexture.baseTexture; + + const minWidth = base.width; + const minHeight = base.height; + + const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + + this.texturePool[key].push(renderTexture); + } + + /** + * Empties the texture pool. + * + */ + emptyPool() + { + for (const i in this.texturePool) + { + const textures = this.texturePool[i]; + + if (textures) + { + for (let j = 0; j < textures.length; j++) + { + textures[j].destroy(true); + } + } + } + + this.texturePool = {}; + } +} diff --git a/packages/core/src/filters/defaultFilter.frag b/packages/core/src/filters/defaultFilter.frag new file mode 100644 index 0000000..a14c1be --- /dev/null +++ b/packages/core/src/filters/defaultFilter.frag @@ -0,0 +1,22 @@ +varying vec2 vTextureCoord; +varying vec2 vFilterCoord; + +uniform sampler2D uSampler; +uniform sampler2D filterSampler; + +void main(void){ + vec4 masky = texture2D(filterSampler, vFilterCoord); + vec4 sample = texture2D(uSampler, vTextureCoord); + vec4 color; + if(mod(vFilterCoord.x, 1.0) > 0.5) + { + color = vec4(1.0, 0.0, 0.0, 1.0); + } + else + { + color = vec4(0.0, 1.0, 0.0, 1.0); + } + // gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0); + gl_FragColor = mix(sample, masky, 0.5); + gl_FragColor *= sample.a; +} diff --git a/packages/core/src/filters/defaultFilter.vert b/packages/core/src/filters/defaultFilter.vert new file mode 100644 index 0000000..e1febca --- /dev/null +++ b/packages/core/src/filters/defaultFilter.vert @@ -0,0 +1,14 @@ +attribute vec2 aVertexPosition; +attribute vec2 aTextureCoord; + +uniform mat3 projectionMatrix; +uniform mat3 filterMatrix; + +varying vec2 vTextureCoord; +varying vec2 vFilterCoord; + +void main(void){ + gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); + vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy; + vTextureCoord = aTextureCoord ; +} diff --git a/packages/core/src/filters/filterTransforms.js b/packages/core/src/filters/filterTransforms.js new file mode 100644 index 0000000..87a3cd6 --- /dev/null +++ b/packages/core/src/filters/filterTransforms.js @@ -0,0 +1,50 @@ +import { Matrix } from '@pixi/math'; + +/** + * Calculates the mapped matrix + * @param filterArea {Rectangle} The filter area + * @param sprite {Sprite} the target sprite + * @param outputMatrix {Matrix} @alvin + * @private + */ +// this returns a matrix that will normalise map filter cords in the filter to screen space +export function calculateScreenSpaceMatrix(outputMatrix, filterArea, textureSize) +{ + // TODO unwrap? + const mappedMatrix = outputMatrix.identity(); + + mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); + + mappedMatrix.scale(textureSize.width, textureSize.height); + + return mappedMatrix; +} + +export function calculateNormalizedScreenSpaceMatrix(outputMatrix, filterArea, textureSize) +{ + const mappedMatrix = outputMatrix.identity(); + + mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); + + const translateScaleX = (textureSize.width / filterArea.width); + const translateScaleY = (textureSize.height / filterArea.height); + + mappedMatrix.scale(translateScaleX, translateScaleY); + + return mappedMatrix; +} + +// this will map the filter coord so that a texture can be used based on the transform of a sprite +export function calculateSpriteMatrix(outputMatrix, filterArea, textureSize, sprite) +{ + const orig = sprite._texture.orig; + const mappedMatrix = outputMatrix.set(textureSize.width, 0, 0, textureSize.height, filterArea.x, filterArea.y); + const worldTransform = sprite.worldTransform.copyTo(Matrix.TEMP_MATRIX); + + worldTransform.invert(); + mappedMatrix.prepend(worldTransform); + mappedMatrix.scale(1.0 / orig.width, 1.0 / orig.height); + mappedMatrix.translate(sprite.anchor.x, sprite.anchor.y); + + return mappedMatrix; +} diff --git a/packages/core/src/filters/spriteMask/SpriteMaskFilter.js b/packages/core/src/filters/spriteMask/SpriteMaskFilter.js new file mode 100644 index 0000000..91697b7 --- /dev/null +++ b/packages/core/src/filters/spriteMask/SpriteMaskFilter.js @@ -0,0 +1,63 @@ +import Filter from '../Filter'; +import { Matrix } from '@pixi/math'; +import vertex from './spriteMaskFilter.vert'; +import fragment from './spriteMaskFilter.frag'; +import { default as TextureMatrix } from '../../textures/TextureMatrix'; + +/** + * The SpriteMaskFilter class + * + * @class + * @extends PIXI.Filter + * @memberof PIXI + */ +export default class SpriteMaskFilter extends Filter +{ + /** + * @param {PIXI.Sprite} sprite - the target sprite + */ + constructor(sprite) + { + const maskMatrix = new Matrix(); + + super(vertex, fragment); + + sprite.renderable = false; + + this.maskSprite = sprite; + this.maskMatrix = maskMatrix; + } + + /** + * Applies the filter + * + * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + */ + apply(filterManager, input, output) + { + const maskSprite = this.maskSprite; + const tex = this.maskSprite.texture; + + if (!tex.valid) + { + return; + } + if (!tex.transform) + { + // margin = 0.0, let it bleed a bit, shader code becomes easier + // assuming that atlas textures were made with 1-pixel padding + tex.transform = new TextureMatrix(tex, 0.0); + } + tex.transform.update(); + + this.uniforms.mask = tex; + this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite) + .prepend(tex.transform.mapCoord); + this.uniforms.alpha = maskSprite.worldAlpha; + this.uniforms.maskClamp = tex.transform.uClampFrame; + + filterManager.applyFilter(this, input, output); + } +} diff --git a/packages/core/src/filters/spriteMask/spriteMaskFilter.frag b/packages/core/src/filters/spriteMask/spriteMaskFilter.frag new file mode 100644 index 0000000..0e4aef8 --- /dev/null +++ b/packages/core/src/filters/spriteMask/spriteMaskFilter.frag @@ -0,0 +1,23 @@ +varying vec2 vMaskCoord; +varying vec2 vTextureCoord; + +uniform sampler2D uSampler; +uniform sampler2D mask; +uniform float alpha; +uniform vec4 maskClamp; + +void main(void) +{ + float clip = step(3.5, + step(maskClamp.x, vMaskCoord.x) + + step(maskClamp.y, vMaskCoord.y) + + step(vMaskCoord.x, maskClamp.z) + + step(vMaskCoord.y, maskClamp.w)); + + vec4 original = texture2D(uSampler, vTextureCoord); + vec4 masky = texture2D(mask, vMaskCoord); + + original *= (masky.r * masky.a * alpha * clip); + + gl_FragColor = original; +} diff --git a/packages/core/src/filters/spriteMask/spriteMaskFilter.vert b/packages/core/src/filters/spriteMask/spriteMaskFilter.vert new file mode 100644 index 0000000..d23a30c --- /dev/null +++ b/packages/core/src/filters/spriteMask/spriteMaskFilter.vert @@ -0,0 +1,16 @@ +attribute vec2 aVertexPosition; +attribute vec2 aTextureCoord; + +uniform mat3 projectionMatrix; +uniform mat3 otherMatrix; + +varying vec2 vMaskCoord; +varying vec2 vTextureCoord; + +void main(void) +{ + gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); + + vTextureCoord = aTextureCoord; + vMaskCoord = ( otherMatrix * vec3( aTextureCoord, 1.0) ).xy; +} diff --git a/packages/core/src/framebuffer/FrameBuffer.js b/packages/core/src/framebuffer/FrameBuffer.js new file mode 100644 index 0000000..84e9fcc --- /dev/null +++ b/packages/core/src/framebuffer/FrameBuffer.js @@ -0,0 +1,106 @@ +import Texture from '../textures/BaseTexture'; +import { FORMATS, TYPES } from '@pixi/constants'; + +/** + * Frame buffer + * @class + * @memberof PIXI + */ +export default class FrameBuffer +{ + constructor(width, height) + { + this.width = width || 100; + this.height = height || 100; + + this.stencil = false; + this.depth = false; + + this.dirtyId = 0; + this.dirtyFormat = 0; + this.dirtySize = 0; + + this.depthTexture = null; + this.colorTextures = []; + + this.glFrameBuffers = {}; + } + + get colorTexture() + { + return this.colorTextures[0]; + } + + addColorTexture(index, texture) + { + // TODO add some validation to the texture - same width / height etc? + this.colorTextures[index || 0] = texture || new Texture(null, { scaleMode: 0, + resolution: 1, + mipmap: false, + width: this.width, + height: this.height });// || new Texture(); + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + addDepthTexture(texture) + { + /* eslint-disable max-len */ + this.depthTexture = texture || new Texture(null, { scaleMode: 0, + resolution: 1, + width: this.width, + height: this.height, + mipmap: false, + format: FORMATS.DEPTH_COMPONENT, + type: TYPES.UNSIGNED_SHORT });// UNSIGNED_SHORT; + /* eslint-disable max-len */ + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + enableDepth() + { + this.depth = true; + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + enableStencil() + { + this.stencil = true; + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + resize(width, height) + { + if (width === this.width && height === this.height) return; + + this.width = width; + this.height = height; + + this.dirtyId++; + this.dirtySize++; + + for (let i = 0; i < this.colorTextures.length; i++) + { + this.colorTextures[i].setSize(width, height); + } + + if (this.depthTexture) + { + this.depthTexture.setSize(width, height); + } + } +} diff --git a/packages/core/src/framebuffer/FramebufferSystem.js b/packages/core/src/framebuffer/FramebufferSystem.js new file mode 100644 index 0000000..2cc2437 --- /dev/null +++ b/packages/core/src/framebuffer/FramebufferSystem.js @@ -0,0 +1,246 @@ +import System from '../System'; +import { Rectangle } from '@pixi/math'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class FramebufferSystem extends System +{ + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + contextChange() + { + this.gl = this.renderer.gl; + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + this.current = null; + this.viewport = new Rectangle(); + + this.drawBufferExtension = this.renderer.context.extensions.drawBuffers; + } + + bind(framebuffer, frame) + { + const gl = this.gl; + + this.current = framebuffer; + + if (framebuffer) + { + // TODO cacheing layer! + + const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID] || this.initFramebuffer(framebuffer); + + gl.bindFramebuffer(gl.FRAMEBUFFER, fbo.framebuffer); + // makesure all textures are unbound.. + + // now check for updates... + if (fbo.dirtyId !== framebuffer.dirtyId) + { + fbo.dirtyId = framebuffer.dirtyId; + + if (fbo.dirtyFormat !== framebuffer.dirtyFormat) + { + fbo.dirtyFormat = framebuffer.dirtyFormat; + this.updateFramebuffer(framebuffer); + } + else if (fbo.dirtySize !== framebuffer.dirtySize) + { + fbo.dirtySize = framebuffer.dirtySize; + this.resizeFramebuffer(framebuffer); + } + } + + for (let i = 0; i < framebuffer.colorTextures.length; i++) + { + if (framebuffer.colorTextures[i].texturePart) + { + this.renderer.texture.unbind(framebuffer.colorTextures[i].texture); + } + else + { + this.renderer.texture.unbind(framebuffer.colorTextures[i]); + } + } + + if (framebuffer.depthTexture) + { + this.renderer.texture.unbind(framebuffer.depthTexture); + } + + if (frame) + { + this.setViewport(frame.x, frame.y, frame.width, frame.height); + } + else + { + this.setViewport(0, 0, framebuffer.width, framebuffer.height); + } + } + else + { + gl.bindFramebuffer(gl.FRAMEBUFFER, null); + + if (frame) + { + this.setViewport(frame.x, frame.y, frame.width, frame.height); + } + else + { + this.setViewport(0, 0, this.renderer.width, this.renderer.height); + } + } + } + + setViewport(x, y, width, height) + { + const v = this.viewport; + + if (v.width !== width || v.height !== height || v.x !== x || v.y !== y) + { + v.x = x; + v.y = y; + v.width = width; + v.height = height; + + this.gl.viewport(x, y, width, height); + } + } + + get size() + { + if (this.current) + { + // TODO store temp + return { x: 0, y: 0, width: this.current.width, height: this.current.height }; + } + + return { x: 0, y: 0, width: this.renderer.width, height: this.renderer.height }; + } + + clear(r, g, b, a) + { + const gl = this.gl; + + // TODO clear color can be set only one right? + gl.clearColor(r, g, b, a); + gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); + } + + // private functions... + + initFramebuffer(framebuffer) + { + const gl = this.gl; + + // TODO - make this a class? + const fbo = { + framebuffer: gl.createFramebuffer(), + stencil: null, + dirtyId: 0, + dirtyFormat: 0, + dirtySize: 0, + }; + + framebuffer.glFrameBuffers[this.CONTEXT_UID] = fbo; + + return fbo; + } + + resizeFramebuffer(framebuffer) + { + const gl = this.gl; + + if (framebuffer.stencil || framebuffer.depth) + { + gl.bindRenderbuffer(gl.RENDERBUFFER, this.stencil); + gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); + } + } + + updateFramebuffer(framebuffer) + { + const gl = this.gl; + + const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID]; + + // bind the color texture + const colorTextures = framebuffer.colorTextures; + + let count = colorTextures.length; + + if (!this.drawBufferExtension) + { + count = Math.min(count, 1); + } + + const activeTextures = []; + + for (let i = 0; i < count; i++) + { + const texture = framebuffer.colorTextures[i]; + + if (texture.texturePart) + { + this.renderer.texture.bind(texture.texture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.COLOR_ATTACHMENT0 + i, + gl.TEXTURE_CUBE_MAP_NEGATIVE_X + texture.side, + texture.texture._glTextures[this.CONTEXT_UID].texture, + 0); + } + else + { + this.renderer.texture.bind(texture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.COLOR_ATTACHMENT0 + i, + gl.TEXTURE_2D, + texture._glTextures[this.CONTEXT_UID].texture, + 0); + } + + activeTextures.push(gl.COLOR_ATTACHMENT0 + i); + } + + if (this.drawBufferExtension && activeTextures.length > 1) + { + this.drawBufferExtension.drawBuffersWEBGL(activeTextures); + } + + if (framebuffer.depthTexture) + { + const depthTextureExt = this.renderer.context.extensions.depthTexture; + + if (depthTextureExt) + { + const depthTexture = framebuffer.depthTexture; + + this.renderer.texture.bind(depthTexture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.DEPTH_ATTACHMENT, + gl.TEXTURE_2D, + depthTexture._glTextures[this.CONTEXT_UID].texture, + 0); + } + } + + if (framebuffer.stencil || framebuffer.depth) + { + fbo.stencil = gl.createRenderbuffer(); + + gl.bindRenderbuffer(gl.RENDERBUFFER, fbo.stencil); + + // TODO.. this is depth AND stencil? + gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, fbo.stencil); + gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); + // fbo.enableStencil(); + } + } +} diff --git a/packages/core/src/geometry/GLBuffer.js b/packages/core/src/geometry/GLBuffer.js new file mode 100644 index 0000000..0477e8a --- /dev/null +++ b/packages/core/src/geometry/GLBuffer.js @@ -0,0 +1,9 @@ +export default class GLBuffer +{ + constructor(buffer) + { + this.buffer = buffer; + this.updateID = -1; + this.byteLength = -1; + } +} diff --git a/packages/core/src/AbstractRenderer.js b/packages/core/src/AbstractRenderer.js new file mode 100644 index 0000000..64b2eb1 --- /dev/null +++ b/packages/core/src/AbstractRenderer.js @@ -0,0 +1,353 @@ +import { hex2string, hex2rgb } from '@pixi/utils'; +import { Matrix, Rectangle } from '@pixi/math'; +import { RENDERER_TYPE } from '@pixi/constants'; +import { settings } from '@pixi/settings'; +import { Container } from '@pixi/display'; +import RenderTexture from './renderTexture/RenderTexture'; +import EventEmitter from 'eventemitter3'; + +const tempMatrix = new Matrix(); + +/** + * 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 EventEmitter + * @memberof PIXI + */ +export default class AbstractRenderer extends EventEmitter +{ + // eslint-disable-next-line valid-jsdoc + /** + * @param {string} system - The name of the system this renderer is for. + * @param {object} [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.transparent=false] - If the render view is transparent, default false + * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false + * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. The + * resolution of the renderer retina would be 2. + * @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 {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when rendering, + * stopping pixel interpolation. + */ + constructor(system, options, arg2, arg3) + { + super(); + + // Support for constructor(system, screenWidth, screenHeight, options) + if (typeof options === 'number') + { + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + }, arg3); + } + + // 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 = RENDERER_TYPE.UNKNOWN; + + /** + * Measurements of the screen. (0, 0, screenWidth, screenHeight) + * + * Its safe to use as filterArea or hitArea for 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 1 + */ + this.resolution = options.resolution || settings.RESOLUTION; + + /** + * Whether the render view is transparent + * + * @member {boolean} + */ + this.transparent = options.transparent; + + /** + * Whether css dimensions of canvas view should be resized to screen dimensions automatically + * + * @member {boolean} + */ + this.autoResize = options.autoResize || false; + + /** + * Tracks the blend modes useful for this renderer. + * + * @member {object} + */ + this.blendModes = null; + + /** + * 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; + + /** + * If true PixiJS will Math.floor() x/y values when rendering, stopping pixel interpolation. + * Handy for crisp pixel art and speed on legacy devices. + * + * @member {boolean} + */ + this.roundPixels = options.roundPixels; + + /** + * The background color as a number. + * + * @member {number} + * @private + */ + this._backgroundColor = 0x000000; + + /** + * The background color as an [R, G, B] array. + * + * @member {number[]} + * @private + */ + this._backgroundColorRgba = [0, 0, 0, 0]; + + /** + * The background color as a string. + * + * @member {string} + * @private + */ + this._backgroundColorString = '#000000'; + + this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter + + /** + * This temporary display object used as the parent of the currently being rendered item + * + * @member {PIXI.DisplayObject} + * @private + */ + this._tempDisplayObjectParent = new Container(); + + /** + * The last root object that the renderer tried to render. + * + * @member {PIXI.DisplayObject} + * @private + */ + this._lastObjectRendered = this._tempDisplayObjectParent; + + /** + * Collection of plugins. + * @readonly + * @member {object} + */ + this.plugins = {}; + } + + /** + * Initialize the plugins. + * + * @protected + * @param {object} staticMap - The dictionary of staticly saved plugins. + */ + initPlugins(staticMap) + { + 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() + { + return this.view.width; + } + + /** + * Same as view.height, actual number of pixels in the canvas by vertical + * + * @member {number} + * @readonly + * @default 600 + */ + get height() + { + return this.view.height; + } + + /** + * Resizes the screen and canvas to the specified width and height + * Canvas dimensions are multiplied by resolution + * + * @param {number} screenWidth - the new width of the screen + * @param {number} screenHeight - the new height of the screen + */ + resize(screenWidth, screenHeight) + { + this.screen.width = screenWidth; + this.screen.height = screenHeight; + + this.view.width = screenWidth * this.resolution; + this.view.height = screenHeight * this.resolution; + + if (this.autoResize) + { + this.view.style.width = `${screenWidth}px`; + this.view.style.height = `${screenHeight}px`; + } + } + + /** + * 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 {PIXI.DisplayObject} displayObject - The displayObject the object will be generated from + * @param {number} scaleMode - Should be one of the scaleMode consts + * @param {number} resolution - The resolution / device pixel ratio of the texture being generated + * @param {PIXI.Rectangle} [region] - The region of the displayObject, that shall be rendered, + * if no region is specified, defaults to the local bounds of the displayObject. + * @return {PIXI.Texture} a texture of the graphics object + */ + generateTexture(displayObject, scaleMode, resolution, region) + { + region = region || displayObject.getLocalBounds(); + + // 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(region.width | 0, region.height | 0, scaleMode, resolution); + + tempMatrix.tx = -region.x; + tempMatrix.ty = -region.y; + + this.render(displayObject, renderTexture, false, tempMatrix, true); + + return renderTexture; + } + + /** + * Removes everything from the renderer and optionally removes the Canvas DOM element. + * + * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. + */ + destroy(removeView) + { + 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); + } + + this.plugins = null; + + this.type = RENDERER_TYPE.UNKNOWN; + + this.view = null; + + this.screen = null; + + this.resolution = 0; + + this.transparent = false; + + this.autoResize = false; + + this.blendModes = null; + + this.options = null; + + this.preserveDrawingBuffer = false; + this.clearBeforeRender = false; + + this.roundPixels = false; + + this._backgroundColor = 0; + this._backgroundColorRgba = null; + this._backgroundColorString = null; + + this._tempDisplayObjectParent = null; + this._lastObjectRendered = null; + } + + /** + * The background color to fill if not transparent + * + * @member {number} + */ + get backgroundColor() + { + return this._backgroundColor; + } + + set backgroundColor(value) // eslint-disable-line require-jsdoc + { + this._backgroundColor = value; + this._backgroundColorString = hex2string(value); + hex2rgb(value, this._backgroundColorRgba); + } +} diff --git a/packages/core/src/Renderer.js b/packages/core/src/Renderer.js new file mode 100644 index 0000000..2227e78 --- /dev/null +++ b/packages/core/src/Renderer.js @@ -0,0 +1,338 @@ +import AbstractRenderer from './AbstractRenderer'; +import { sayHello } from '@pixi/utils'; +import MaskSystem from './mask/MaskSystem'; +import StencilSystem from './mask/StencilSystem'; +import FilterSystem from './filters/FilterSystem'; +import FramebufferSystem from './framebuffer/FramebufferSystem'; +import RenderTextureSystem from './renderTexture/RenderTextureSystem'; +import TextureSystem from './textures/TextureSystem'; +import ProjectionSystem from './projection/ProjectionSystem'; +import StateSystem from './state/StateSystem'; +import GeometrySystem from './geometry/GeometrySystem'; +import ShaderSystem from './shader/ShaderSystem'; +import ContextSystem from './context/ContextSystem'; +import BatchSystem from './batch/BatchSystem'; +import TextureGCSystem from './textures/TextureGCSystem'; +import { RENDERER_TYPE } from '@pixi/constants'; +import UniformGroup from './shader/UniformGroup'; +import { Matrix } from '@pixi/math'; +import Runner from 'mini-runner'; + +/** + * 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 Render works by automatically managing webGLBatchs. + * 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 :) + * + * @class + * @memberof PIXI + * @extends PIXI.AbstractRenderer + */ +export default class Renderer extends AbstractRenderer +{ + // eslint-disable-next-line valid-jsdoc + /** + * + * @param {object} [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.transparent=false] - If the render view is transparent, default false + * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false + * @param {boolean} [options.antialias=false] - sets antialias. If not available natively then FXAA + * antialiasing is used + * @param {boolean} [options.forceFXAA=false] - forces FXAA antialiasing to be used over native. + * FXAA is faster, but may not always look as great + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. + * The resolution of the renderer retina would be 2. + * @param {boolean} [options.clearBeforeRender=true] - This sets if the renderer will clear + * the canvas or not before the new render pass. If you wish to set this to false, you *must* set + * preserveDrawingBuffer to `true`. + * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, + * enable this if you need to call toDataUrl on the webgl context. + * @param {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when + * rendering, stopping pixel interpolation. + * @param {number} [options.backgroundColor=0x000000] - The background color of the rendered area + * (shown if not transparent). + * @param {boolean} [options.legacy=false] - If true PixiJS will aim to ensure compatibility + * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. + * @param {string} [options.powerPreference] - Parameter passed to webgl context, set to "high-performance" + * for devices with dual graphics card + */ + constructor(options, arg2, arg3) + { + super('WebGL', options, arg2, arg3); + + /** + * The type of this renderer as a standardised const + * + * @member {number} + * @see PIXI.RENDERER_TYPE + */ + this.type = RENDERER_TYPE.WEBGL; + + // this will be set by the contextSystem (this.context) + this.gl = null; + this.CONTEXT_UID = 0; + this.legacy = !!options.legacy; + + // TODO legacy! + + // runners! + this.runners = { + destroy: new Runner('destroy'), + contextChange: new Runner('contextChange', 1), + reset: new Runner('reset'), + update: new Runner('update'), + postrender: new Runner('postrender'), + prerender: new Runner('prerender'), + resize: new Runner('resize', 2), + }; + + 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(GeometrySystem, 'geometry') + .addSystem(FramebufferSystem, 'framebuffer') + .addSystem(StencilSystem, 'stencil') + .addSystem(ProjectionSystem, 'projection') + .addSystem(TextureGCSystem, 'textureGC') + .addSystem(FilterSystem, 'filter') + .addSystem(RenderTextureSystem, 'renderTexture') + .addSystem(BatchSystem, 'batch'); + + this.initPlugins(Renderer.__plugins); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ + if (options.context) + { + this.context.initFromContext(options.context); + } + else + { + this.context.initFromOptions({ + alpha: this.transparent, + antialias: options.antialias, + premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', + stencil: true, + 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); + } + + /** + * Add a new system to the renderer. + * @param {class} ClassRef - Class reference + * @param {string} [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. + * @return {PIXI.Renderer} Return instance of renderer + */ + addSystem(ClassRef, name) + { + if (!name) + { + name = ClassRef.name; + } + + // TODO - read name from class.name.. + + /* + if(name.includes('System')) + { + name = name.replace('System', ''); + name = name.charAt(0).toLowerCase() + name.slice(1); + } + */ + + const system = new ClassRef(this); + + if (this[name]) + { + throw new Error(`Whoops! ${name} is already a manger`); + } + + this[name] = system; + + for (const i in this.runners) + { + this.runners[i].add(system); + } + + return this; + + /** + * 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. + */ + } + + /** + * Renders the object to its webGL view + * + * @param {PIXI.DisplayObject} displayObject - the object to be rendered + * @param {PIXI.RenderTexture} renderTexture - The render texture to render to. + * @param {boolean} [clear] - Should the canvas be cleared before the new render + * @param {PIXI.Transform} [transform] - A transform to apply to the render texture before rendering. + * @param {boolean} [skipUpdateTransform] - Should we skip the update transform pass? + */ + render(displayObject, renderTexture, clear, transform, skipUpdateTransform) + { + // can be handy to know! + this.renderingToScreen = !renderTexture; + + this.runners.prerender.run(); + this.emit('prerender'); + + // 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.parent; + + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = 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.run(); + + this.emit('postrender'); + } + + /** + * Resizes the webGL view to the specified width and height. + * + * @param {number} screenWidth - the new width of the screen + * @param {number} screenHeight - the new height of the screen + */ + resize(screenWidth, screenHeight) + { + AbstractRenderer.prototype.resize.call(this, screenWidth, screenHeight); + + this.runners.resize.run(screenWidth, screenHeight); + } + + /** + * Resets the WebGL state so you can render things however you fancy! + * + * @return {PIXI.Renderer} Returns itself. + */ + reset() + { + this.runners.reset.run(); + + return this; + } + + clear() + { + this.framebuffer.bind(); + this.framebuffer.clear(); + } + + /** + * Removes everything from the renderer (event listeners, spritebatch, etc...) + * + * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. + * See: https://github.com/pixijs/pixi.js/issues/2233 + */ + destroy(removeView) + { + this.runners.destroy.run(); + + // call base destroy + super.destroy(removeView); + + // TODO nullify all the managers.. + this.gl = null; + } + + /** + * 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 + * builds and excluding plugins. + * @name PIXI.Renderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. + */ + + /** + * Adds a plugin to the renderer. + * + * @method + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + static registerPlugin(pluginName, ctor) + { + Renderer.__plugins = Renderer.__plugins || {}; + Renderer.__plugins[pluginName] = ctor; + } +} diff --git a/packages/core/src/System.js b/packages/core/src/System.js new file mode 100644 index 0000000..05a10c8 --- /dev/null +++ b/packages/core/src/System.js @@ -0,0 +1,42 @@ +/** + * System is a base class used for extending systems used by the {@link PIXI.Renderer} + * @see PIXI.Renderer#addSystem + * @class + * @memberof PIXI + */ +export default class System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + /** + * The renderer this manager works for. + * + * @member {PIXI.Renderer} + */ + this.renderer = renderer; + + this.renderer.runners.contextChange.add(this); + } + + /** + * Generic method called when there is a WebGL context change. + * + */ + contextChange() + { + // do some codes init! + } + + /** + * Generic destroy methods to be overridden by the subclass + * + */ + destroy() + { + this.renderer.runners.contextChange.remove(this); + this.renderer = null; + } +} diff --git a/packages/core/src/batch/BatchSystem.js b/packages/core/src/batch/BatchSystem.js new file mode 100644 index 0000000..b04c249 --- /dev/null +++ b/packages/core/src/batch/BatchSystem.js @@ -0,0 +1,68 @@ +import System from '../System'; +import ObjectRenderer from './ObjectRenderer'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class BatchSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + super(renderer); + + /** + * An empty renderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.emptyRenderer = new ObjectRenderer(renderer); + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.currentRenderer = this.emptyRenderer; + } + + /** + * Changes the current renderer to the one given in parameter + * + * @param {PIXI.ObjectRenderer} objectRenderer - The object renderer to use. + */ + setObjectRenderer(objectRenderer) + { + if (this.currentRenderer === objectRenderer) + { + return; + } + + this.currentRenderer.stop(); + this.currentRenderer = objectRenderer; + + this.renderer.state.setState(objectRenderer.state); + + this.currentRenderer.start(); + } + + /** + * This should be called if you wish to do some custom rendering + * It will basically render anything that may be batched up such as sprites + * + */ + flush() + { + this.setObjectRenderer(this.emptyRenderer); + } + + reset() + { + this.setObjectRenderer(this.emptyRenderer); + } +} diff --git a/packages/core/src/batch/ObjectRenderer.js b/packages/core/src/batch/ObjectRenderer.js new file mode 100644 index 0000000..0c851a9 --- /dev/null +++ b/packages/core/src/batch/ObjectRenderer.js @@ -0,0 +1,48 @@ +import System from '../System'; + +/** + * Base for a common object renderer that can be used as a system renderer plugin. + * + * @class + * @extends PIXI.System + * @memberof PIXI + */ +export default class ObjectRenderer extends System +{ + /** + * Starts the renderer and sets the shader + * + */ + start() + { + // set the shader.. + } + + /** + * Stops the renderer + * + */ + stop() + { + this.flush(); + } + + /** + * Stub method for rendering content and emptying the current batch. + * + */ + flush() + { + // flush! + } + + /** + * Renders an object + * + * @param {PIXI.DisplayObject} object - The object to render. + */ + render(object) // eslint-disable-line no-unused-vars + { + // render the object + } +} diff --git a/packages/core/src/context/ContextSystem.js b/packages/core/src/context/ContextSystem.js new file mode 100644 index 0000000..301aab4 --- /dev/null +++ b/packages/core/src/context/ContextSystem.js @@ -0,0 +1,186 @@ +import System from '../System'; +import { settings } from '@pixi/settings'; + +let CONTEXT_UID = 0; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class ContextSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.webGLVersion = 1; + + this.handleContextLost = this.handleContextLost.bind(this); + this.handleContextRestored = this.handleContextRestored.bind(this); + + this.extensions = {}; + + renderer.view.addEventListener('webglcontextlost', this.handleContextLost, false); + renderer.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + } + + get isLost() + { + return (!this.gl || this.gl.isContextLost()); + } + + contextChange(gl) + { + this.gl = gl; + + // restore a context if it was previously lost + if (gl.isContextLost() && gl.getExtension('WEBGL_lose_context')) + { + gl.getExtension('WEBGL_lose_context').restoreContext(); + } + } + + initFromContext(gl) + { + this.gl = gl; + this.validateContext(gl); + this.renderer.gl = gl; + this.renderer.CONTEXT_UID = CONTEXT_UID++; + this.renderer.runners.contextChange.run(gl); + } + + initFromOptions(options) + { + const gl = this.createContext(this.renderer.view, options); + + this.initFromContext(gl); + } + + /** + * Helper class to create a webGL Context + * + * @class + * @memberof PIXI.glCore + * @param canvas {HTMLCanvasElement} the canvas element that we will get the context from + * @param options {Object} An options object that gets passed in to the canvas element containing the context attributes, + * see https://developer.mozilla.org/en/docs/Web/API/HTMLCanvasElement/getContext for the options available + * @return {WebGLRenderingContext} the WebGL context + */ + createContext(canvas, options) + { + let gl; + + if (settings.PREFER_WEBGL_2) + { + gl = canvas.getContext('webgl2', options); + } + + if (gl) + { + this.webGLVersion = 2; + } + else + { + this.webGLVersion = 1; + + gl = canvas.getContext('webgl', options) + || canvas.getContext('experimental-webgl', options); + + if (!gl) + { + // fail, not able to get a context + throw new Error('This browser does not support webGL. Try using the canvas renderer'); + } + } + + this.gl = gl; + + this.getExtensions(); + + return gl; + } + + getExtensions() + { + // time to set up default etensions that pixi uses.. + const gl = this.gl; + const extensions = this.extensions; + + if (this.webGLVersion === 1) + { + extensions.drawBuffers = gl.getExtension('WEBGL_draw_buffers'); + extensions.depthTexture = gl.getExtension('WEBKIT_WEBGL_depth_texture'); + extensions.floatTexture = gl.getExtension('OES_texture_float'); + extensions.loseContext = gl.getExtension('WEBGL_lose_context'); + + extensions.vertexArrayObject = gl.getExtension('OES_vertex_array_object') + || gl.getExtension('MOZ_OES_vertex_array_object') + || gl.getExtension('WEBKIT_OES_vertex_array_object'); + } + + // we don't use any specific WebGL 2 ones yet! + } + + /** + * Handles a lost webgl context + * + * @private + * @param {WebGLContextEvent} event - The context lost event. + */ + handleContextLost(event) + { + event.preventDefault(); + } + + /** + * Handles a restored webgl context + * + * @private + */ + handleContextRestored() + { + this.renderer.runners.contextChange.run(this.gl); + } + + destroy() + { + const view = this.renderer.view; + + // remove listeners + view.removeEventListener('webglcontextlost', this.handleContextLost); + view.removeEventListener('webglcontextrestored', this.handleContextRestored); + + this.gl.useProgram(null); + + if (this.extensions.loseContext) + { + this.extensions.loseContext.loseContext(); + } + } + + postrender() + { + this.gl.flush(); + } + + validateContext(gl) + { + const attributes = gl.getContextAttributes(); + + // this is going to be fairly simple for now.. but at least we have room to grow! + if (!attributes.stencil) + { + /* eslint-disable max-len */ + + /* eslint-disable no-console */ + console.warn('Provided WebGL context does not have a stencil buffer, masks may not render correctly'); + /* eslint-enable no-console */ + + /* eslint-enable max-len */ + } + } +} diff --git a/packages/core/src/filters/Filter.js b/packages/core/src/filters/Filter.js new file mode 100644 index 0000000..9d8cad8 --- /dev/null +++ b/packages/core/src/filters/Filter.js @@ -0,0 +1,140 @@ +import Shader from '../shader/Shader'; +import Program from '../shader/Program'; +import State from '../state/State'; +import { settings } from '@pixi/settings'; +// import extractUniformsFromSrc from './extractUniformsFromSrc'; +import defaultVertex from './defaultFilter.vert'; +import defaultFragment from './defaultFilter.frag'; + +// let math = require('../../math'); +/** + * @class + * @memberof PIXI + * @extends PIXI.Shader + */ +export default class Filter extends Shader +{ + /** + * @param {string} [vertexSrc] - The source of the vertex shader. + * @param {string} [fragmentSrc] - The source of the fragment shader. + * @param {object} [uniforms] - Custom uniforms to use to augment the built-in ones. + */ + constructor(vertexSrc, fragmentSrc, uniforms) + { + const program = Program.from(vertexSrc, fragmentSrc); + + super(program, uniforms); + + /** + * The padding of the filter. Some filters require extra space to breath such as a blur. + * Increasing this will add extra width and height to the bounds of the object that the + * filter is applied to. + * + * @member {number} + */ + this.padding = 0; + + /** + * The resolution of the filter. Setting this to be lower will lower the quality but + * increase the performance of the filter. + * + * @member {number} + */ + this.resolution = settings.RESOLUTION; + + /** + * If enabled is true the filter is applied, if false it will not. + * + * @member {boolean} + */ + this.enabled = true; + + /** + * If enabled, PixiJS will fit the filter area into boundaries for better performance. + * Switch it off if it does not work for specific shader. + * + * @member {boolean} + */ + this.autoFit = true; + + /** + * Legacy filters use position and uvs from attributes + * @member {boolean} + * @readonly + */ + this.legacy = !!this.program.attributeData.aTextureCoord; + + /** + * the webGL state the filter requires to render + * @member {PIXI.State} + */ + this.state = new State(); + } + + /** + * Applies the filter + * + * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + * @param {boolean} clear - Should the output be cleared before rendering to it + * @param {object} [currentState] - It's current state of filter. + * There are some useful properties in the currentState : + * target, filters, sourceFrame, destinationFrame, renderTarget, resolution + */ + apply(filterManager, input, output, clear, currentState, derp) // eslint-disable-line no-unused-vars + { + // do as you please! + + filterManager.applyFilter(this, input, output, clear, currentState, derp); + + // or just do a regular render.. + } + + /** + * Sets the blendmode of the filter + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL + */ + get blendMode() + { + return this.state.blendMode; + } + + set blendMode(value) // eslint-disable-line require-jsdoc + { + this.state.blendMode = value; + } + + /** + * The default vertex shader source + * + * @static + * @constant + */ + static get defaultVertexSrc() + { + return defaultVertex; + } + + /** + * The default fragment shader source + * + * @static + * @constant + */ + static get defaultFragmentSrc() + { + return defaultFragment; + } +} + +/** + * Used for caching shader IDs + * + * @static + * @private + */ +Filter.SOURCE_KEY_MAP = {}; + diff --git a/packages/core/src/filters/FilterSystem.js b/packages/core/src/filters/FilterSystem.js new file mode 100644 index 0000000..3174318 --- /dev/null +++ b/packages/core/src/filters/FilterSystem.js @@ -0,0 +1,431 @@ +import System from '../System'; + +import RenderTexture from '../renderTexture/RenderTexture'; +import Quad from '../utils/Quad'; +import QuadUv from '../utils/QuadUv'; +import { Rectangle } from '@pixi/math'; +import * as filterTransforms from './filterTransforms'; +import bitTwiddle from 'bit-twiddle'; +import UniformGroup from '../shader/UniformGroup'; +import { DRAW_MODES } from '@pixi/constants'; + +// +/** + * @ignore + * @class + */ +class FilterState +{ + /** + * + */ + constructor() + { + this.renderTexture = null; + this.sourceFrame = new Rectangle(); + this.destinationFrame = new Rectangle(); + this.filters = []; + this.target = null; + this.legacy = false; + this.resolution = 1; + } +} + +/** + * @class + * @memberof PIXI.systems + * @extends PIXI.System + */ +export default class FilterSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + /** + * stores a bunch of PO2 textures used for filtering + * @type {Object} + */ + this.texturePool = {}; + + /** + * a pool for storing filter states, save us creating new ones each tick + * @type {Array} + */ + this.statePool = []; + + /** + * A very simple geometry used when drawing a filter effect to the screen + * @type {Quad} + */ + this.quad = new Quad(); + + this.quadUv = new QuadUv(); + + /** + * Temporary rect for maths + * @type {PIXI.Rectangle} + */ + this.tempRect = new Rectangle(); + + this.activeState = {}; + + /** + * this uniform group is attached to filter uniforms when used + * @type {UniformGroup} + */ + this.globalUniforms = new UniformGroup({ + sourceFrame: this.tempRect, + destinationFrame: this.tempRect, + + // legacy variables + filterArea: new Float32Array(4), + filterClamp: new Float32Array(4), + }, true); + } + + /** + * Adds a new filter to the System. + * + * @param {PIXI.DisplayObject} target - The target of the filter to render. + * @param {PIXI.Filter[]} filters - The filters to apply. + */ + push(target, filters) + { + const renderer = this.renderer; + const filterStack = this.renderer.renderTexture.defaultFilterStack; + const state = this.statePool.pop() || new FilterState(); + + let resolution = filters[0].resolution; + let padding = filters[0].padding; + let autoFit = filters[0].autoFit; + let legacy = filters[0].legacy; + + for (let i = 1; i < filters.length; i++) + { + const filter = filters[i]; + + // lets use the lowest resolution.. + resolution = Math.min(resolution, filter.resolution); + // and the largest amount of padding! + padding = Math.max(padding, filter.padding); + // only auto fit if all filters are autofit + autoFit = autoFit || filter.autoFit; + + legacy = legacy || filter.legacy; + } + + filterStack.push(state); + + state.resolution = resolution; + + state.legacy = legacy; + + // round to whole number based on resolution + // TODO move that to the shader too? + state.sourceFrame = target.filterArea || target.getBounds(true); + + state.sourceFrame.pad(padding); + + if (autoFit) + { + state.sourceFrame.fit(this.renderer.renderTexture.destinationFrame); + } + + state.sourceFrame.round(resolution); + + state.renderTexture = this.getPotFilterTexture(state.sourceFrame.width, state.sourceFrame.height, resolution); + state.filters = filters; + + state.destinationFrame.width = state.renderTexture.width; + state.destinationFrame.height = state.renderTexture.height; + + state.renderTexture.filterFrame = state.sourceFrame; + renderer.renderTexture.bind(state.renderTexture, state.sourceFrame);// /, state.destinationFrame); + renderer.renderTexture.clear(); + } + + /** + * Pops off the filter and applies it. + * + */ + pop() + { + const renderer = this.renderer; + const filterStack = renderer.renderTexture.defaultFilterStack; + const state = filterStack.pop(); + const filters = state.filters; + + this.activeState = state; + + const globalUniforms = this.globalUniforms.uniforms; + + globalUniforms.sourceFrame = state.sourceFrame; + globalUniforms.destinationFrame = state.destinationFrame; + globalUniforms.resolution = state.resolution; + + // only update the rect if its legacy.. + if (state.legacy) + { + const filterArea = globalUniforms.filterArea; + const filterClamp = globalUniforms.filterClamp; + + filterArea[0] = state.destinationFrame.width; + filterArea[1] = state.destinationFrame.height; + filterArea[2] = state.sourceFrame.x; + filterArea[3] = state.sourceFrame.y; + + filterClamp[0] = 0.5 / state.resolution / state.destinationFrame.width; + filterClamp[1] = 0.5 / state.resolution / state.destinationFrame.height; + filterClamp[2] = (state.sourceFrame.width - 0.5) / state.resolution / state.destinationFrame.width; + filterClamp[3] = (state.sourceFrame.height - 0.5) / state.resolution / state.destinationFrame.height; + } + + this.globalUniforms.update(); + + const lastState = filterStack[filterStack.length - 1]; + + if (filters.length === 1) + { + filters[0].apply(this, state.renderTexture, lastState.renderTexture, false, state); + + this.returnFilterTexture(state.renderTexture); + } + else + { + let flip = state.renderTexture; + let flop = this.getPotFilterTexture( + flip.width, + flip.height, + state.resolution + ); + + flop.filterFrame = flip.filterFrame; + + let i = 0; + + for (i = 0; i < filters.length - 1; ++i) + { + filters[i].apply(this, flip, flop, true, state); + + const t = flip; + + flip = flop; + flop = t; + } + + filters[i].apply(this, flip, lastState.renderTexture, false, state); + + this.returnFilterTexture(flip); + this.returnFilterTexture(flop); + } + + this.statePool.push(state); + } + + /** + * Draws a filter. + * + * @param {PIXI.Filter} filter - The filter to draw. + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + * @param {boolean} clear - Should the output be cleared before rendering to it + */ + applyFilter(filter, input, output, clear) + { + const renderer = this.renderer; + + renderer.renderTexture.bind(output, output ? output.filterFrame : null); + + if (clear) + { + // gl.disable(gl.SCISSOR_TEST); + renderer.renderTexture.clear(); + // gl.enable(gl.SCISSOR_TEST); + } + + // set the uniforms.. + filter.uniforms.uSampler = input; + filter.uniforms.filterGlobals = this.globalUniforms; + + // TODO make it so that the order of this does not matter.. + // because it does at the moment cos of global uniforms. + // they need to get resynced + + renderer.state.setState(filter.state); + renderer.shader.bind(filter); + + if (filter.legacy) + { + this.quadUv.map(input._frame, input.filterFrame); + + renderer.geometry.bind(this.quadUv); + renderer.geometry.draw(DRAW_MODES.TRIANGLES); + } + else + { + renderer.geometry.bind(this.quad); + renderer.geometry.draw(DRAW_MODES.TRIANGLE_STRIP); + } + } + + /** + * Calculates the mapped matrix. + * + * TODO playing around here.. this is temporary - (will end up in the shader) + * this returns a matrix that will normalise map filter cords in the filter to screen space + * + * @param {PIXI.Matrix} outputMatrix - the matrix to output to. + * @return {PIXI.Matrix} The mapped matrix. + */ + calculateScreenSpaceMatrix(outputMatrix) + { + const currentState = this.activeState; + + return filterTransforms.calculateScreenSpaceMatrix( + outputMatrix, + currentState.sourceFrame, + currentState.destinationFrame + ); + } + + /** + * This will map the filter coord so that a texture can be used based on the transform of a sprite + * + * @param {PIXI.Matrix} outputMatrix - The matrix to output to. + * @param {PIXI.Sprite} sprite - The sprite to map to. + * @return {PIXI.Matrix} The mapped matrix. + */ + calculateSpriteMatrix(outputMatrix, sprite) + { + const currentState = this.activeState; + + return filterTransforms.calculateSpriteMatrix( + outputMatrix, + currentState.sourceFrame, + currentState.destinationFrame, + sprite + ); + } + + /** + * Destroys this Filter System. + * + * @param {boolean} [contextLost=false] context was lost, do not free shaders + * + */ + destroy(contextLost = false) + { + if (!contextLost) + { + this.emptyPool(); + } + else + { + this.texturePool = {}; + } + } + + /** + * Gets a Power-of-Two render texture. + * + * TODO move to a seperate class could be on renderer? + * also - could cause issue with multiple contexts? + * + * @private + * @param {WebGLRenderingContext} gl - The webgl rendering context + * @param {number} minWidth - The minimum width of the render target. + * @param {number} minHeight - The minimum height of the render target. + * @param {number} resolution - The resolution of the render target. + * @return {PIXI.RenderTarget} The new render target. + */ + getPotFilterTexture(minWidth, minHeight, resolution) + { + minWidth = bitTwiddle.nextPow2(minWidth); + minHeight = bitTwiddle.nextPow2(minHeight); + resolution = resolution || 1; + + const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + + if (!this.texturePool[key]) + { + this.texturePool[key] = []; + } + + let renderTexture = this.texturePool[key].pop(); + + if (!renderTexture) + { + // temporary bypass cache.. + // internally - this will cause a texture to be bound.. + renderTexture = RenderTexture.create({ + width: minWidth, + height: minHeight, + resolution, + }); + } + + return renderTexture; + } + + /** + * Gets extra render texture to use inside current filter + * + * @param {number} resolution resolution of the renderTexture + * @returns {PIXI.RenderTexture} + */ + getFilterTexture(resolution) + { + const rt = this.activeState.renderTexture; + + const filterTexture = this.getPotFilterTexture(rt.width, rt.height, resolution || rt.baseTexture.resolution); + + filterTexture.filterFrame = rt.filterFrame; + + return filterTexture; + } + + /** + * Frees a render texture back into the pool. + * + * @param {PIXI.RenderTarget} renderTexture - The renderTarget to free + */ + returnFilterTexture(renderTexture) + { + renderTexture.filterFrame = null; + + const base = renderTexture.baseTexture; + + const minWidth = base.width; + const minHeight = base.height; + + const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + + this.texturePool[key].push(renderTexture); + } + + /** + * Empties the texture pool. + * + */ + emptyPool() + { + for (const i in this.texturePool) + { + const textures = this.texturePool[i]; + + if (textures) + { + for (let j = 0; j < textures.length; j++) + { + textures[j].destroy(true); + } + } + } + + this.texturePool = {}; + } +} diff --git a/packages/core/src/filters/defaultFilter.frag b/packages/core/src/filters/defaultFilter.frag new file mode 100644 index 0000000..a14c1be --- /dev/null +++ b/packages/core/src/filters/defaultFilter.frag @@ -0,0 +1,22 @@ +varying vec2 vTextureCoord; +varying vec2 vFilterCoord; + +uniform sampler2D uSampler; +uniform sampler2D filterSampler; + +void main(void){ + vec4 masky = texture2D(filterSampler, vFilterCoord); + vec4 sample = texture2D(uSampler, vTextureCoord); + vec4 color; + if(mod(vFilterCoord.x, 1.0) > 0.5) + { + color = vec4(1.0, 0.0, 0.0, 1.0); + } + else + { + color = vec4(0.0, 1.0, 0.0, 1.0); + } + // gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0); + gl_FragColor = mix(sample, masky, 0.5); + gl_FragColor *= sample.a; +} diff --git a/packages/core/src/filters/defaultFilter.vert b/packages/core/src/filters/defaultFilter.vert new file mode 100644 index 0000000..e1febca --- /dev/null +++ b/packages/core/src/filters/defaultFilter.vert @@ -0,0 +1,14 @@ +attribute vec2 aVertexPosition; +attribute vec2 aTextureCoord; + +uniform mat3 projectionMatrix; +uniform mat3 filterMatrix; + +varying vec2 vTextureCoord; +varying vec2 vFilterCoord; + +void main(void){ + gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); + vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy; + vTextureCoord = aTextureCoord ; +} diff --git a/packages/core/src/filters/filterTransforms.js b/packages/core/src/filters/filterTransforms.js new file mode 100644 index 0000000..87a3cd6 --- /dev/null +++ b/packages/core/src/filters/filterTransforms.js @@ -0,0 +1,50 @@ +import { Matrix } from '@pixi/math'; + +/** + * Calculates the mapped matrix + * @param filterArea {Rectangle} The filter area + * @param sprite {Sprite} the target sprite + * @param outputMatrix {Matrix} @alvin + * @private + */ +// this returns a matrix that will normalise map filter cords in the filter to screen space +export function calculateScreenSpaceMatrix(outputMatrix, filterArea, textureSize) +{ + // TODO unwrap? + const mappedMatrix = outputMatrix.identity(); + + mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); + + mappedMatrix.scale(textureSize.width, textureSize.height); + + return mappedMatrix; +} + +export function calculateNormalizedScreenSpaceMatrix(outputMatrix, filterArea, textureSize) +{ + const mappedMatrix = outputMatrix.identity(); + + mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); + + const translateScaleX = (textureSize.width / filterArea.width); + const translateScaleY = (textureSize.height / filterArea.height); + + mappedMatrix.scale(translateScaleX, translateScaleY); + + return mappedMatrix; +} + +// this will map the filter coord so that a texture can be used based on the transform of a sprite +export function calculateSpriteMatrix(outputMatrix, filterArea, textureSize, sprite) +{ + const orig = sprite._texture.orig; + const mappedMatrix = outputMatrix.set(textureSize.width, 0, 0, textureSize.height, filterArea.x, filterArea.y); + const worldTransform = sprite.worldTransform.copyTo(Matrix.TEMP_MATRIX); + + worldTransform.invert(); + mappedMatrix.prepend(worldTransform); + mappedMatrix.scale(1.0 / orig.width, 1.0 / orig.height); + mappedMatrix.translate(sprite.anchor.x, sprite.anchor.y); + + return mappedMatrix; +} diff --git a/packages/core/src/filters/spriteMask/SpriteMaskFilter.js b/packages/core/src/filters/spriteMask/SpriteMaskFilter.js new file mode 100644 index 0000000..91697b7 --- /dev/null +++ b/packages/core/src/filters/spriteMask/SpriteMaskFilter.js @@ -0,0 +1,63 @@ +import Filter from '../Filter'; +import { Matrix } from '@pixi/math'; +import vertex from './spriteMaskFilter.vert'; +import fragment from './spriteMaskFilter.frag'; +import { default as TextureMatrix } from '../../textures/TextureMatrix'; + +/** + * The SpriteMaskFilter class + * + * @class + * @extends PIXI.Filter + * @memberof PIXI + */ +export default class SpriteMaskFilter extends Filter +{ + /** + * @param {PIXI.Sprite} sprite - the target sprite + */ + constructor(sprite) + { + const maskMatrix = new Matrix(); + + super(vertex, fragment); + + sprite.renderable = false; + + this.maskSprite = sprite; + this.maskMatrix = maskMatrix; + } + + /** + * Applies the filter + * + * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + */ + apply(filterManager, input, output) + { + const maskSprite = this.maskSprite; + const tex = this.maskSprite.texture; + + if (!tex.valid) + { + return; + } + if (!tex.transform) + { + // margin = 0.0, let it bleed a bit, shader code becomes easier + // assuming that atlas textures were made with 1-pixel padding + tex.transform = new TextureMatrix(tex, 0.0); + } + tex.transform.update(); + + this.uniforms.mask = tex; + this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite) + .prepend(tex.transform.mapCoord); + this.uniforms.alpha = maskSprite.worldAlpha; + this.uniforms.maskClamp = tex.transform.uClampFrame; + + filterManager.applyFilter(this, input, output); + } +} diff --git a/packages/core/src/filters/spriteMask/spriteMaskFilter.frag b/packages/core/src/filters/spriteMask/spriteMaskFilter.frag new file mode 100644 index 0000000..0e4aef8 --- /dev/null +++ b/packages/core/src/filters/spriteMask/spriteMaskFilter.frag @@ -0,0 +1,23 @@ +varying vec2 vMaskCoord; +varying vec2 vTextureCoord; + +uniform sampler2D uSampler; +uniform sampler2D mask; +uniform float alpha; +uniform vec4 maskClamp; + +void main(void) +{ + float clip = step(3.5, + step(maskClamp.x, vMaskCoord.x) + + step(maskClamp.y, vMaskCoord.y) + + step(vMaskCoord.x, maskClamp.z) + + step(vMaskCoord.y, maskClamp.w)); + + vec4 original = texture2D(uSampler, vTextureCoord); + vec4 masky = texture2D(mask, vMaskCoord); + + original *= (masky.r * masky.a * alpha * clip); + + gl_FragColor = original; +} diff --git a/packages/core/src/filters/spriteMask/spriteMaskFilter.vert b/packages/core/src/filters/spriteMask/spriteMaskFilter.vert new file mode 100644 index 0000000..d23a30c --- /dev/null +++ b/packages/core/src/filters/spriteMask/spriteMaskFilter.vert @@ -0,0 +1,16 @@ +attribute vec2 aVertexPosition; +attribute vec2 aTextureCoord; + +uniform mat3 projectionMatrix; +uniform mat3 otherMatrix; + +varying vec2 vMaskCoord; +varying vec2 vTextureCoord; + +void main(void) +{ + gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); + + vTextureCoord = aTextureCoord; + vMaskCoord = ( otherMatrix * vec3( aTextureCoord, 1.0) ).xy; +} diff --git a/packages/core/src/framebuffer/FrameBuffer.js b/packages/core/src/framebuffer/FrameBuffer.js new file mode 100644 index 0000000..84e9fcc --- /dev/null +++ b/packages/core/src/framebuffer/FrameBuffer.js @@ -0,0 +1,106 @@ +import Texture from '../textures/BaseTexture'; +import { FORMATS, TYPES } from '@pixi/constants'; + +/** + * Frame buffer + * @class + * @memberof PIXI + */ +export default class FrameBuffer +{ + constructor(width, height) + { + this.width = width || 100; + this.height = height || 100; + + this.stencil = false; + this.depth = false; + + this.dirtyId = 0; + this.dirtyFormat = 0; + this.dirtySize = 0; + + this.depthTexture = null; + this.colorTextures = []; + + this.glFrameBuffers = {}; + } + + get colorTexture() + { + return this.colorTextures[0]; + } + + addColorTexture(index, texture) + { + // TODO add some validation to the texture - same width / height etc? + this.colorTextures[index || 0] = texture || new Texture(null, { scaleMode: 0, + resolution: 1, + mipmap: false, + width: this.width, + height: this.height });// || new Texture(); + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + addDepthTexture(texture) + { + /* eslint-disable max-len */ + this.depthTexture = texture || new Texture(null, { scaleMode: 0, + resolution: 1, + width: this.width, + height: this.height, + mipmap: false, + format: FORMATS.DEPTH_COMPONENT, + type: TYPES.UNSIGNED_SHORT });// UNSIGNED_SHORT; + /* eslint-disable max-len */ + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + enableDepth() + { + this.depth = true; + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + enableStencil() + { + this.stencil = true; + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + resize(width, height) + { + if (width === this.width && height === this.height) return; + + this.width = width; + this.height = height; + + this.dirtyId++; + this.dirtySize++; + + for (let i = 0; i < this.colorTextures.length; i++) + { + this.colorTextures[i].setSize(width, height); + } + + if (this.depthTexture) + { + this.depthTexture.setSize(width, height); + } + } +} diff --git a/packages/core/src/framebuffer/FramebufferSystem.js b/packages/core/src/framebuffer/FramebufferSystem.js new file mode 100644 index 0000000..2cc2437 --- /dev/null +++ b/packages/core/src/framebuffer/FramebufferSystem.js @@ -0,0 +1,246 @@ +import System from '../System'; +import { Rectangle } from '@pixi/math'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class FramebufferSystem extends System +{ + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + contextChange() + { + this.gl = this.renderer.gl; + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + this.current = null; + this.viewport = new Rectangle(); + + this.drawBufferExtension = this.renderer.context.extensions.drawBuffers; + } + + bind(framebuffer, frame) + { + const gl = this.gl; + + this.current = framebuffer; + + if (framebuffer) + { + // TODO cacheing layer! + + const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID] || this.initFramebuffer(framebuffer); + + gl.bindFramebuffer(gl.FRAMEBUFFER, fbo.framebuffer); + // makesure all textures are unbound.. + + // now check for updates... + if (fbo.dirtyId !== framebuffer.dirtyId) + { + fbo.dirtyId = framebuffer.dirtyId; + + if (fbo.dirtyFormat !== framebuffer.dirtyFormat) + { + fbo.dirtyFormat = framebuffer.dirtyFormat; + this.updateFramebuffer(framebuffer); + } + else if (fbo.dirtySize !== framebuffer.dirtySize) + { + fbo.dirtySize = framebuffer.dirtySize; + this.resizeFramebuffer(framebuffer); + } + } + + for (let i = 0; i < framebuffer.colorTextures.length; i++) + { + if (framebuffer.colorTextures[i].texturePart) + { + this.renderer.texture.unbind(framebuffer.colorTextures[i].texture); + } + else + { + this.renderer.texture.unbind(framebuffer.colorTextures[i]); + } + } + + if (framebuffer.depthTexture) + { + this.renderer.texture.unbind(framebuffer.depthTexture); + } + + if (frame) + { + this.setViewport(frame.x, frame.y, frame.width, frame.height); + } + else + { + this.setViewport(0, 0, framebuffer.width, framebuffer.height); + } + } + else + { + gl.bindFramebuffer(gl.FRAMEBUFFER, null); + + if (frame) + { + this.setViewport(frame.x, frame.y, frame.width, frame.height); + } + else + { + this.setViewport(0, 0, this.renderer.width, this.renderer.height); + } + } + } + + setViewport(x, y, width, height) + { + const v = this.viewport; + + if (v.width !== width || v.height !== height || v.x !== x || v.y !== y) + { + v.x = x; + v.y = y; + v.width = width; + v.height = height; + + this.gl.viewport(x, y, width, height); + } + } + + get size() + { + if (this.current) + { + // TODO store temp + return { x: 0, y: 0, width: this.current.width, height: this.current.height }; + } + + return { x: 0, y: 0, width: this.renderer.width, height: this.renderer.height }; + } + + clear(r, g, b, a) + { + const gl = this.gl; + + // TODO clear color can be set only one right? + gl.clearColor(r, g, b, a); + gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); + } + + // private functions... + + initFramebuffer(framebuffer) + { + const gl = this.gl; + + // TODO - make this a class? + const fbo = { + framebuffer: gl.createFramebuffer(), + stencil: null, + dirtyId: 0, + dirtyFormat: 0, + dirtySize: 0, + }; + + framebuffer.glFrameBuffers[this.CONTEXT_UID] = fbo; + + return fbo; + } + + resizeFramebuffer(framebuffer) + { + const gl = this.gl; + + if (framebuffer.stencil || framebuffer.depth) + { + gl.bindRenderbuffer(gl.RENDERBUFFER, this.stencil); + gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); + } + } + + updateFramebuffer(framebuffer) + { + const gl = this.gl; + + const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID]; + + // bind the color texture + const colorTextures = framebuffer.colorTextures; + + let count = colorTextures.length; + + if (!this.drawBufferExtension) + { + count = Math.min(count, 1); + } + + const activeTextures = []; + + for (let i = 0; i < count; i++) + { + const texture = framebuffer.colorTextures[i]; + + if (texture.texturePart) + { + this.renderer.texture.bind(texture.texture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.COLOR_ATTACHMENT0 + i, + gl.TEXTURE_CUBE_MAP_NEGATIVE_X + texture.side, + texture.texture._glTextures[this.CONTEXT_UID].texture, + 0); + } + else + { + this.renderer.texture.bind(texture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.COLOR_ATTACHMENT0 + i, + gl.TEXTURE_2D, + texture._glTextures[this.CONTEXT_UID].texture, + 0); + } + + activeTextures.push(gl.COLOR_ATTACHMENT0 + i); + } + + if (this.drawBufferExtension && activeTextures.length > 1) + { + this.drawBufferExtension.drawBuffersWEBGL(activeTextures); + } + + if (framebuffer.depthTexture) + { + const depthTextureExt = this.renderer.context.extensions.depthTexture; + + if (depthTextureExt) + { + const depthTexture = framebuffer.depthTexture; + + this.renderer.texture.bind(depthTexture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.DEPTH_ATTACHMENT, + gl.TEXTURE_2D, + depthTexture._glTextures[this.CONTEXT_UID].texture, + 0); + } + } + + if (framebuffer.stencil || framebuffer.depth) + { + fbo.stencil = gl.createRenderbuffer(); + + gl.bindRenderbuffer(gl.RENDERBUFFER, fbo.stencil); + + // TODO.. this is depth AND stencil? + gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, fbo.stencil); + gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); + // fbo.enableStencil(); + } + } +} diff --git a/packages/core/src/geometry/GLBuffer.js b/packages/core/src/geometry/GLBuffer.js new file mode 100644 index 0000000..0477e8a --- /dev/null +++ b/packages/core/src/geometry/GLBuffer.js @@ -0,0 +1,9 @@ +export default class GLBuffer +{ + constructor(buffer) + { + this.buffer = buffer; + this.updateID = -1; + this.byteLength = -1; + } +} diff --git a/packages/core/src/geometry/GeometrySystem.js b/packages/core/src/geometry/GeometrySystem.js new file mode 100644 index 0000000..959f827 --- /dev/null +++ b/packages/core/src/geometry/GeometrySystem.js @@ -0,0 +1,388 @@ +import System from '../System'; +import GLBuffer from './GLBuffer'; + +const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class GeometrySystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this._activeGeometry = null; + this._activeVao = null; + + this.hasVao = true; + this.hasInstance = true; + } + + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + contextChange() + { + const gl = this.gl = this.renderer.gl; + + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + + // webgl2 + if (!gl.createVertexArray) + { + // webgl 1! + let nativeVaoExtension = this.renderer.context.extensions.vertexArrayObject; + + if (this.renderer.legacy) + { + nativeVaoExtension = null; + } + + if (nativeVaoExtension) + { + gl.createVertexArray = () => + nativeVaoExtension.createVertexArrayOES(); + + gl.bindVertexArray = (vao) => + nativeVaoExtension.bindVertexArrayOES(vao); + + gl.deleteVertexArray = (vao) => + nativeVaoExtension.deleteVertexArrayOES(vao); + } + else + { + this.hasVao = false; + gl.createVertexArray = () => + { + // empty + }; + + gl.bindVertexArray = () => + { + // empty + }; + + gl.deleteVertexArray = () => + { + // empty + }; + } + } + + if (!gl.vertexAttribDivisor) + { + const instanceExt = gl.getExtension('ANGLE_instanced_arrays'); + + if (instanceExt) + { + gl.vertexAttribDivisor = (a, b) => + instanceExt.vertexAttribDivisorANGLE(a, b); + + gl.drawElementsInstanced = (a, b, c, d, e) => + instanceExt.drawElementsInstancedANGLE(a, b, c, d, e); + + gl.drawArraysInstanced = (a, b, c, d) => + instanceExt.drawArraysInstancedANGLE(a, b, c, d); + } + else + { + this.hasInstance = false; + } + } + } + + /** + * Binds geometry so that is can be drawn. Creating a Vao if required + * @private + * @param {PIXI.Geometry} geometry instance of geometry to bind + */ + bind(geometry, shader) + { + shader = shader || this.renderer.shader.shader; + + const gl = this.gl; + + // not sure the best way to address this.. + // currently different shaders require different VAOs for the same geometry + // Still mulling over the best way to solve this one.. + // will likely need to modify the shader attribute locations at run time! + let vaos = geometry.glVertexArrayObjects[this.CONTEXT_UID]; + + if (!vaos) + { + geometry.glVertexArrayObjects[this.CONTEXT_UID] = vaos = {}; + } + + const vao = vaos[shader.program.id] || this.initGeometryVao(geometry, shader.program); + + this._activeGeometry = geometry; + + if (this._activeVao !== vao) + { + this._activeVao = vao; + + if (this.hasVao) + { + gl.bindVertexArray(vao); + } + else + { + this.activateVao(geometry, shader.program); + } + } + + // TODO - optimise later! + // don't need to loop through if nothing changed! + // maybe look to add an 'autoupdate' to geometry? + this.updateBuffers(); + } + + reset() + { + this.unbind(); + } + + updateBuffers() + { + const geometry = this._activeGeometry; + const gl = this.gl; + + for (let i = 0; i < geometry.buffers.length; i++) + { + const buffer = geometry.buffers[i]; + + const glBuffer = buffer._glBuffers[this.CONTEXT_UID]; + + if (buffer._updateID !== glBuffer.updateID) + { + glBuffer.updateID = buffer._updateID; + + // TODO can cache this on buffer! maybe added a getter / setter? + const type = buffer.index ? gl.ELEMENT_ARRAY_BUFFER : gl.ARRAY_BUFFER; + const drawType = buffer.static ? gl.STATIC_DRAW : gl.DYNAMIC_DRAW; + + gl.bindBuffer(type, glBuffer.buffer); + + if (glBuffer.byteLength >= buffer.data.byteLength) + { + // offset is always zero for now! + gl.bufferSubData(type, 0, buffer.data); + } + else + { + gl.bufferData(type, buffer.data, drawType); + } + } + } + } + + checkCompatability(geometry, program) + { + // geometry must have at least all the attributes that the shader requires. + const geometryAttributes = geometry.attributes; + const shaderAttributes = program.attributeData; + + for (const j in shaderAttributes) + { + if (!geometryAttributes[j]) + { + throw new Error(`shader and geometry incompatible, geometry missing the "${j}" attribute`); + } + } + } + + /** + * Creates a Vao with the same structure as the geometry and stores it on the geometry. + * @private + * @param {PIXI.Geometry} geometry instance of geometry to to generate Vao for + */ + initGeometryVao(geometry, program) + { + this.checkCompatability(geometry, program); + + const gl = this.gl; + const CONTEXT_UID = this.CONTEXT_UID; + const buffers = geometry.buffers; + const attributes = geometry.attributes; + + const tempStride = {}; + const tempStart = {}; + + for (const j in buffers) + { + tempStride[j] = 0; + tempStart[j] = 0; + } + + for (const j in attributes) + { + if (!attributes[j].size && program.attributeData[j]) + { + attributes[j].size = program.attributeData[j].size; + } + + tempStride[attributes[j].buffer] += attributes[j].size * byteSizeMap[attributes[j].type]; + } + + for (const j in attributes) + { + const attribute = attributes[j]; + const attribSize = attribute.size; + + if (attribute.stride === undefined) + { + if (tempStride[attribute.buffer] === attribSize * byteSizeMap[attribute.type]) + { + attribute.stride = 0; + } + else + { + attribute.stride = tempStride[attribute.buffer]; + } + } + + if (attribute.start === undefined) + { + attribute.start = tempStart[attribute.buffer]; + + tempStart[attribute.buffer] += attribSize * byteSizeMap[attribute.type]; + } + } + + // first update - and create the buffers! + // only create a gl buffer if it actually gets + for (let i = 0; i < buffers.length; i++) + { + const buffer = buffers[i]; + + if (!buffer._glBuffers[CONTEXT_UID]) + { + buffer._glBuffers[CONTEXT_UID] = new GLBuffer(gl.createBuffer()); + } + } + + // TODO - maybe make this a data object? + // lets wait to see if we need to first! + const vao = gl.createVertexArray(); + + gl.bindVertexArray(vao); + + this.activateVao(geometry, program); + + gl.bindVertexArray(null); + + // add it to the cache! + geometry.glVertexArrayObjects[this.CONTEXT_UID][program.id] = vao; + + return vao; + } + + activateVao(geometry, program) + { + const gl = this.gl; + const CONTEXT_UID = this.CONTEXT_UID; + const buffers = geometry.buffers; + const attributes = geometry.attributes; + + if (geometry.indexBuffer) + { + // first update the index buffer if we have one.. + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, geometry.indexBuffer._glBuffers[CONTEXT_UID].buffer); + } + + let lastBuffer = null; + + // add a new one! + for (const j in attributes) + { + const attribute = attributes[j]; + const buffer = buffers[attribute.buffer]; + const glBuffer = buffer._glBuffers[CONTEXT_UID]; + + if (program.attributeData[j]) + { + if (lastBuffer !== glBuffer) + { + gl.bindBuffer(gl.ARRAY_BUFFER, glBuffer.buffer); + + lastBuffer = glBuffer; + } + + const location = program.attributeData[j].location; + + // TODO introduce state again + // we can optimise this for older devices that have no VAOs + gl.enableVertexAttribArray(location); + + gl.vertexAttribPointer(location, + attribute.size, + attribute.type || gl.FLOAT, + attribute.normalized, + attribute.stride, + attribute.start); + + if (attribute.instance) + { + // TODO calculate instance count based of this... + if (this.hasInstance) + { + gl.vertexAttribDivisor(location, 1); + } + else + { + throw new Error('geometry error, GPU Instancing is not supported on this device'); + } + } + } + } + } + + draw(type, size, start, instanceCount) + { + const gl = this.gl; + const geometry = this._activeGeometry; + + // TODO.. this should not change so maybe cache the function? + + if (geometry.indexBuffer) + { + if (geometry.instanced) + { + /* eslint-disable max-len */ + gl.drawElementsInstanced(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2, instanceCount || 1); + /* eslint-enable max-len */ + } + else + { + gl.drawElements(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2); + } + } + else + if (geometry.instanced) + { + // TODO need a better way to calculate size.. + gl.drawArraysInstanced(type, start, size || geometry.getSize(), instanceCount || 1); + } + else + { + gl.drawArrays(type, start, size || geometry.getSize()); + } + + return this; + } + + unbind() + { + this.gl.bindVertexArray(null); + this._activeVao = null; + this._activeGeometry = null; + } +} diff --git a/packages/core/src/AbstractRenderer.js b/packages/core/src/AbstractRenderer.js new file mode 100644 index 0000000..64b2eb1 --- /dev/null +++ b/packages/core/src/AbstractRenderer.js @@ -0,0 +1,353 @@ +import { hex2string, hex2rgb } from '@pixi/utils'; +import { Matrix, Rectangle } from '@pixi/math'; +import { RENDERER_TYPE } from '@pixi/constants'; +import { settings } from '@pixi/settings'; +import { Container } from '@pixi/display'; +import RenderTexture from './renderTexture/RenderTexture'; +import EventEmitter from 'eventemitter3'; + +const tempMatrix = new Matrix(); + +/** + * 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 EventEmitter + * @memberof PIXI + */ +export default class AbstractRenderer extends EventEmitter +{ + // eslint-disable-next-line valid-jsdoc + /** + * @param {string} system - The name of the system this renderer is for. + * @param {object} [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.transparent=false] - If the render view is transparent, default false + * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false + * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. The + * resolution of the renderer retina would be 2. + * @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 {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when rendering, + * stopping pixel interpolation. + */ + constructor(system, options, arg2, arg3) + { + super(); + + // Support for constructor(system, screenWidth, screenHeight, options) + if (typeof options === 'number') + { + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + }, arg3); + } + + // 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 = RENDERER_TYPE.UNKNOWN; + + /** + * Measurements of the screen. (0, 0, screenWidth, screenHeight) + * + * Its safe to use as filterArea or hitArea for 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 1 + */ + this.resolution = options.resolution || settings.RESOLUTION; + + /** + * Whether the render view is transparent + * + * @member {boolean} + */ + this.transparent = options.transparent; + + /** + * Whether css dimensions of canvas view should be resized to screen dimensions automatically + * + * @member {boolean} + */ + this.autoResize = options.autoResize || false; + + /** + * Tracks the blend modes useful for this renderer. + * + * @member {object} + */ + this.blendModes = null; + + /** + * 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; + + /** + * If true PixiJS will Math.floor() x/y values when rendering, stopping pixel interpolation. + * Handy for crisp pixel art and speed on legacy devices. + * + * @member {boolean} + */ + this.roundPixels = options.roundPixels; + + /** + * The background color as a number. + * + * @member {number} + * @private + */ + this._backgroundColor = 0x000000; + + /** + * The background color as an [R, G, B] array. + * + * @member {number[]} + * @private + */ + this._backgroundColorRgba = [0, 0, 0, 0]; + + /** + * The background color as a string. + * + * @member {string} + * @private + */ + this._backgroundColorString = '#000000'; + + this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter + + /** + * This temporary display object used as the parent of the currently being rendered item + * + * @member {PIXI.DisplayObject} + * @private + */ + this._tempDisplayObjectParent = new Container(); + + /** + * The last root object that the renderer tried to render. + * + * @member {PIXI.DisplayObject} + * @private + */ + this._lastObjectRendered = this._tempDisplayObjectParent; + + /** + * Collection of plugins. + * @readonly + * @member {object} + */ + this.plugins = {}; + } + + /** + * Initialize the plugins. + * + * @protected + * @param {object} staticMap - The dictionary of staticly saved plugins. + */ + initPlugins(staticMap) + { + 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() + { + return this.view.width; + } + + /** + * Same as view.height, actual number of pixels in the canvas by vertical + * + * @member {number} + * @readonly + * @default 600 + */ + get height() + { + return this.view.height; + } + + /** + * Resizes the screen and canvas to the specified width and height + * Canvas dimensions are multiplied by resolution + * + * @param {number} screenWidth - the new width of the screen + * @param {number} screenHeight - the new height of the screen + */ + resize(screenWidth, screenHeight) + { + this.screen.width = screenWidth; + this.screen.height = screenHeight; + + this.view.width = screenWidth * this.resolution; + this.view.height = screenHeight * this.resolution; + + if (this.autoResize) + { + this.view.style.width = `${screenWidth}px`; + this.view.style.height = `${screenHeight}px`; + } + } + + /** + * 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 {PIXI.DisplayObject} displayObject - The displayObject the object will be generated from + * @param {number} scaleMode - Should be one of the scaleMode consts + * @param {number} resolution - The resolution / device pixel ratio of the texture being generated + * @param {PIXI.Rectangle} [region] - The region of the displayObject, that shall be rendered, + * if no region is specified, defaults to the local bounds of the displayObject. + * @return {PIXI.Texture} a texture of the graphics object + */ + generateTexture(displayObject, scaleMode, resolution, region) + { + region = region || displayObject.getLocalBounds(); + + // 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(region.width | 0, region.height | 0, scaleMode, resolution); + + tempMatrix.tx = -region.x; + tempMatrix.ty = -region.y; + + this.render(displayObject, renderTexture, false, tempMatrix, true); + + return renderTexture; + } + + /** + * Removes everything from the renderer and optionally removes the Canvas DOM element. + * + * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. + */ + destroy(removeView) + { + 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); + } + + this.plugins = null; + + this.type = RENDERER_TYPE.UNKNOWN; + + this.view = null; + + this.screen = null; + + this.resolution = 0; + + this.transparent = false; + + this.autoResize = false; + + this.blendModes = null; + + this.options = null; + + this.preserveDrawingBuffer = false; + this.clearBeforeRender = false; + + this.roundPixels = false; + + this._backgroundColor = 0; + this._backgroundColorRgba = null; + this._backgroundColorString = null; + + this._tempDisplayObjectParent = null; + this._lastObjectRendered = null; + } + + /** + * The background color to fill if not transparent + * + * @member {number} + */ + get backgroundColor() + { + return this._backgroundColor; + } + + set backgroundColor(value) // eslint-disable-line require-jsdoc + { + this._backgroundColor = value; + this._backgroundColorString = hex2string(value); + hex2rgb(value, this._backgroundColorRgba); + } +} diff --git a/packages/core/src/Renderer.js b/packages/core/src/Renderer.js new file mode 100644 index 0000000..2227e78 --- /dev/null +++ b/packages/core/src/Renderer.js @@ -0,0 +1,338 @@ +import AbstractRenderer from './AbstractRenderer'; +import { sayHello } from '@pixi/utils'; +import MaskSystem from './mask/MaskSystem'; +import StencilSystem from './mask/StencilSystem'; +import FilterSystem from './filters/FilterSystem'; +import FramebufferSystem from './framebuffer/FramebufferSystem'; +import RenderTextureSystem from './renderTexture/RenderTextureSystem'; +import TextureSystem from './textures/TextureSystem'; +import ProjectionSystem from './projection/ProjectionSystem'; +import StateSystem from './state/StateSystem'; +import GeometrySystem from './geometry/GeometrySystem'; +import ShaderSystem from './shader/ShaderSystem'; +import ContextSystem from './context/ContextSystem'; +import BatchSystem from './batch/BatchSystem'; +import TextureGCSystem from './textures/TextureGCSystem'; +import { RENDERER_TYPE } from '@pixi/constants'; +import UniformGroup from './shader/UniformGroup'; +import { Matrix } from '@pixi/math'; +import Runner from 'mini-runner'; + +/** + * 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 Render works by automatically managing webGLBatchs. + * 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 :) + * + * @class + * @memberof PIXI + * @extends PIXI.AbstractRenderer + */ +export default class Renderer extends AbstractRenderer +{ + // eslint-disable-next-line valid-jsdoc + /** + * + * @param {object} [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.transparent=false] - If the render view is transparent, default false + * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false + * @param {boolean} [options.antialias=false] - sets antialias. If not available natively then FXAA + * antialiasing is used + * @param {boolean} [options.forceFXAA=false] - forces FXAA antialiasing to be used over native. + * FXAA is faster, but may not always look as great + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. + * The resolution of the renderer retina would be 2. + * @param {boolean} [options.clearBeforeRender=true] - This sets if the renderer will clear + * the canvas or not before the new render pass. If you wish to set this to false, you *must* set + * preserveDrawingBuffer to `true`. + * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, + * enable this if you need to call toDataUrl on the webgl context. + * @param {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when + * rendering, stopping pixel interpolation. + * @param {number} [options.backgroundColor=0x000000] - The background color of the rendered area + * (shown if not transparent). + * @param {boolean} [options.legacy=false] - If true PixiJS will aim to ensure compatibility + * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. + * @param {string} [options.powerPreference] - Parameter passed to webgl context, set to "high-performance" + * for devices with dual graphics card + */ + constructor(options, arg2, arg3) + { + super('WebGL', options, arg2, arg3); + + /** + * The type of this renderer as a standardised const + * + * @member {number} + * @see PIXI.RENDERER_TYPE + */ + this.type = RENDERER_TYPE.WEBGL; + + // this will be set by the contextSystem (this.context) + this.gl = null; + this.CONTEXT_UID = 0; + this.legacy = !!options.legacy; + + // TODO legacy! + + // runners! + this.runners = { + destroy: new Runner('destroy'), + contextChange: new Runner('contextChange', 1), + reset: new Runner('reset'), + update: new Runner('update'), + postrender: new Runner('postrender'), + prerender: new Runner('prerender'), + resize: new Runner('resize', 2), + }; + + 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(GeometrySystem, 'geometry') + .addSystem(FramebufferSystem, 'framebuffer') + .addSystem(StencilSystem, 'stencil') + .addSystem(ProjectionSystem, 'projection') + .addSystem(TextureGCSystem, 'textureGC') + .addSystem(FilterSystem, 'filter') + .addSystem(RenderTextureSystem, 'renderTexture') + .addSystem(BatchSystem, 'batch'); + + this.initPlugins(Renderer.__plugins); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ + if (options.context) + { + this.context.initFromContext(options.context); + } + else + { + this.context.initFromOptions({ + alpha: this.transparent, + antialias: options.antialias, + premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', + stencil: true, + 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); + } + + /** + * Add a new system to the renderer. + * @param {class} ClassRef - Class reference + * @param {string} [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. + * @return {PIXI.Renderer} Return instance of renderer + */ + addSystem(ClassRef, name) + { + if (!name) + { + name = ClassRef.name; + } + + // TODO - read name from class.name.. + + /* + if(name.includes('System')) + { + name = name.replace('System', ''); + name = name.charAt(0).toLowerCase() + name.slice(1); + } + */ + + const system = new ClassRef(this); + + if (this[name]) + { + throw new Error(`Whoops! ${name} is already a manger`); + } + + this[name] = system; + + for (const i in this.runners) + { + this.runners[i].add(system); + } + + return this; + + /** + * 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. + */ + } + + /** + * Renders the object to its webGL view + * + * @param {PIXI.DisplayObject} displayObject - the object to be rendered + * @param {PIXI.RenderTexture} renderTexture - The render texture to render to. + * @param {boolean} [clear] - Should the canvas be cleared before the new render + * @param {PIXI.Transform} [transform] - A transform to apply to the render texture before rendering. + * @param {boolean} [skipUpdateTransform] - Should we skip the update transform pass? + */ + render(displayObject, renderTexture, clear, transform, skipUpdateTransform) + { + // can be handy to know! + this.renderingToScreen = !renderTexture; + + this.runners.prerender.run(); + this.emit('prerender'); + + // 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.parent; + + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = 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.run(); + + this.emit('postrender'); + } + + /** + * Resizes the webGL view to the specified width and height. + * + * @param {number} screenWidth - the new width of the screen + * @param {number} screenHeight - the new height of the screen + */ + resize(screenWidth, screenHeight) + { + AbstractRenderer.prototype.resize.call(this, screenWidth, screenHeight); + + this.runners.resize.run(screenWidth, screenHeight); + } + + /** + * Resets the WebGL state so you can render things however you fancy! + * + * @return {PIXI.Renderer} Returns itself. + */ + reset() + { + this.runners.reset.run(); + + return this; + } + + clear() + { + this.framebuffer.bind(); + this.framebuffer.clear(); + } + + /** + * Removes everything from the renderer (event listeners, spritebatch, etc...) + * + * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. + * See: https://github.com/pixijs/pixi.js/issues/2233 + */ + destroy(removeView) + { + this.runners.destroy.run(); + + // call base destroy + super.destroy(removeView); + + // TODO nullify all the managers.. + this.gl = null; + } + + /** + * 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 + * builds and excluding plugins. + * @name PIXI.Renderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. + */ + + /** + * Adds a plugin to the renderer. + * + * @method + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + static registerPlugin(pluginName, ctor) + { + Renderer.__plugins = Renderer.__plugins || {}; + Renderer.__plugins[pluginName] = ctor; + } +} diff --git a/packages/core/src/System.js b/packages/core/src/System.js new file mode 100644 index 0000000..05a10c8 --- /dev/null +++ b/packages/core/src/System.js @@ -0,0 +1,42 @@ +/** + * System is a base class used for extending systems used by the {@link PIXI.Renderer} + * @see PIXI.Renderer#addSystem + * @class + * @memberof PIXI + */ +export default class System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + /** + * The renderer this manager works for. + * + * @member {PIXI.Renderer} + */ + this.renderer = renderer; + + this.renderer.runners.contextChange.add(this); + } + + /** + * Generic method called when there is a WebGL context change. + * + */ + contextChange() + { + // do some codes init! + } + + /** + * Generic destroy methods to be overridden by the subclass + * + */ + destroy() + { + this.renderer.runners.contextChange.remove(this); + this.renderer = null; + } +} diff --git a/packages/core/src/batch/BatchSystem.js b/packages/core/src/batch/BatchSystem.js new file mode 100644 index 0000000..b04c249 --- /dev/null +++ b/packages/core/src/batch/BatchSystem.js @@ -0,0 +1,68 @@ +import System from '../System'; +import ObjectRenderer from './ObjectRenderer'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class BatchSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + super(renderer); + + /** + * An empty renderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.emptyRenderer = new ObjectRenderer(renderer); + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.currentRenderer = this.emptyRenderer; + } + + /** + * Changes the current renderer to the one given in parameter + * + * @param {PIXI.ObjectRenderer} objectRenderer - The object renderer to use. + */ + setObjectRenderer(objectRenderer) + { + if (this.currentRenderer === objectRenderer) + { + return; + } + + this.currentRenderer.stop(); + this.currentRenderer = objectRenderer; + + this.renderer.state.setState(objectRenderer.state); + + this.currentRenderer.start(); + } + + /** + * This should be called if you wish to do some custom rendering + * It will basically render anything that may be batched up such as sprites + * + */ + flush() + { + this.setObjectRenderer(this.emptyRenderer); + } + + reset() + { + this.setObjectRenderer(this.emptyRenderer); + } +} diff --git a/packages/core/src/batch/ObjectRenderer.js b/packages/core/src/batch/ObjectRenderer.js new file mode 100644 index 0000000..0c851a9 --- /dev/null +++ b/packages/core/src/batch/ObjectRenderer.js @@ -0,0 +1,48 @@ +import System from '../System'; + +/** + * Base for a common object renderer that can be used as a system renderer plugin. + * + * @class + * @extends PIXI.System + * @memberof PIXI + */ +export default class ObjectRenderer extends System +{ + /** + * Starts the renderer and sets the shader + * + */ + start() + { + // set the shader.. + } + + /** + * Stops the renderer + * + */ + stop() + { + this.flush(); + } + + /** + * Stub method for rendering content and emptying the current batch. + * + */ + flush() + { + // flush! + } + + /** + * Renders an object + * + * @param {PIXI.DisplayObject} object - The object to render. + */ + render(object) // eslint-disable-line no-unused-vars + { + // render the object + } +} diff --git a/packages/core/src/context/ContextSystem.js b/packages/core/src/context/ContextSystem.js new file mode 100644 index 0000000..301aab4 --- /dev/null +++ b/packages/core/src/context/ContextSystem.js @@ -0,0 +1,186 @@ +import System from '../System'; +import { settings } from '@pixi/settings'; + +let CONTEXT_UID = 0; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class ContextSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.webGLVersion = 1; + + this.handleContextLost = this.handleContextLost.bind(this); + this.handleContextRestored = this.handleContextRestored.bind(this); + + this.extensions = {}; + + renderer.view.addEventListener('webglcontextlost', this.handleContextLost, false); + renderer.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + } + + get isLost() + { + return (!this.gl || this.gl.isContextLost()); + } + + contextChange(gl) + { + this.gl = gl; + + // restore a context if it was previously lost + if (gl.isContextLost() && gl.getExtension('WEBGL_lose_context')) + { + gl.getExtension('WEBGL_lose_context').restoreContext(); + } + } + + initFromContext(gl) + { + this.gl = gl; + this.validateContext(gl); + this.renderer.gl = gl; + this.renderer.CONTEXT_UID = CONTEXT_UID++; + this.renderer.runners.contextChange.run(gl); + } + + initFromOptions(options) + { + const gl = this.createContext(this.renderer.view, options); + + this.initFromContext(gl); + } + + /** + * Helper class to create a webGL Context + * + * @class + * @memberof PIXI.glCore + * @param canvas {HTMLCanvasElement} the canvas element that we will get the context from + * @param options {Object} An options object that gets passed in to the canvas element containing the context attributes, + * see https://developer.mozilla.org/en/docs/Web/API/HTMLCanvasElement/getContext for the options available + * @return {WebGLRenderingContext} the WebGL context + */ + createContext(canvas, options) + { + let gl; + + if (settings.PREFER_WEBGL_2) + { + gl = canvas.getContext('webgl2', options); + } + + if (gl) + { + this.webGLVersion = 2; + } + else + { + this.webGLVersion = 1; + + gl = canvas.getContext('webgl', options) + || canvas.getContext('experimental-webgl', options); + + if (!gl) + { + // fail, not able to get a context + throw new Error('This browser does not support webGL. Try using the canvas renderer'); + } + } + + this.gl = gl; + + this.getExtensions(); + + return gl; + } + + getExtensions() + { + // time to set up default etensions that pixi uses.. + const gl = this.gl; + const extensions = this.extensions; + + if (this.webGLVersion === 1) + { + extensions.drawBuffers = gl.getExtension('WEBGL_draw_buffers'); + extensions.depthTexture = gl.getExtension('WEBKIT_WEBGL_depth_texture'); + extensions.floatTexture = gl.getExtension('OES_texture_float'); + extensions.loseContext = gl.getExtension('WEBGL_lose_context'); + + extensions.vertexArrayObject = gl.getExtension('OES_vertex_array_object') + || gl.getExtension('MOZ_OES_vertex_array_object') + || gl.getExtension('WEBKIT_OES_vertex_array_object'); + } + + // we don't use any specific WebGL 2 ones yet! + } + + /** + * Handles a lost webgl context + * + * @private + * @param {WebGLContextEvent} event - The context lost event. + */ + handleContextLost(event) + { + event.preventDefault(); + } + + /** + * Handles a restored webgl context + * + * @private + */ + handleContextRestored() + { + this.renderer.runners.contextChange.run(this.gl); + } + + destroy() + { + const view = this.renderer.view; + + // remove listeners + view.removeEventListener('webglcontextlost', this.handleContextLost); + view.removeEventListener('webglcontextrestored', this.handleContextRestored); + + this.gl.useProgram(null); + + if (this.extensions.loseContext) + { + this.extensions.loseContext.loseContext(); + } + } + + postrender() + { + this.gl.flush(); + } + + validateContext(gl) + { + const attributes = gl.getContextAttributes(); + + // this is going to be fairly simple for now.. but at least we have room to grow! + if (!attributes.stencil) + { + /* eslint-disable max-len */ + + /* eslint-disable no-console */ + console.warn('Provided WebGL context does not have a stencil buffer, masks may not render correctly'); + /* eslint-enable no-console */ + + /* eslint-enable max-len */ + } + } +} diff --git a/packages/core/src/filters/Filter.js b/packages/core/src/filters/Filter.js new file mode 100644 index 0000000..9d8cad8 --- /dev/null +++ b/packages/core/src/filters/Filter.js @@ -0,0 +1,140 @@ +import Shader from '../shader/Shader'; +import Program from '../shader/Program'; +import State from '../state/State'; +import { settings } from '@pixi/settings'; +// import extractUniformsFromSrc from './extractUniformsFromSrc'; +import defaultVertex from './defaultFilter.vert'; +import defaultFragment from './defaultFilter.frag'; + +// let math = require('../../math'); +/** + * @class + * @memberof PIXI + * @extends PIXI.Shader + */ +export default class Filter extends Shader +{ + /** + * @param {string} [vertexSrc] - The source of the vertex shader. + * @param {string} [fragmentSrc] - The source of the fragment shader. + * @param {object} [uniforms] - Custom uniforms to use to augment the built-in ones. + */ + constructor(vertexSrc, fragmentSrc, uniforms) + { + const program = Program.from(vertexSrc, fragmentSrc); + + super(program, uniforms); + + /** + * The padding of the filter. Some filters require extra space to breath such as a blur. + * Increasing this will add extra width and height to the bounds of the object that the + * filter is applied to. + * + * @member {number} + */ + this.padding = 0; + + /** + * The resolution of the filter. Setting this to be lower will lower the quality but + * increase the performance of the filter. + * + * @member {number} + */ + this.resolution = settings.RESOLUTION; + + /** + * If enabled is true the filter is applied, if false it will not. + * + * @member {boolean} + */ + this.enabled = true; + + /** + * If enabled, PixiJS will fit the filter area into boundaries for better performance. + * Switch it off if it does not work for specific shader. + * + * @member {boolean} + */ + this.autoFit = true; + + /** + * Legacy filters use position and uvs from attributes + * @member {boolean} + * @readonly + */ + this.legacy = !!this.program.attributeData.aTextureCoord; + + /** + * the webGL state the filter requires to render + * @member {PIXI.State} + */ + this.state = new State(); + } + + /** + * Applies the filter + * + * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + * @param {boolean} clear - Should the output be cleared before rendering to it + * @param {object} [currentState] - It's current state of filter. + * There are some useful properties in the currentState : + * target, filters, sourceFrame, destinationFrame, renderTarget, resolution + */ + apply(filterManager, input, output, clear, currentState, derp) // eslint-disable-line no-unused-vars + { + // do as you please! + + filterManager.applyFilter(this, input, output, clear, currentState, derp); + + // or just do a regular render.. + } + + /** + * Sets the blendmode of the filter + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL + */ + get blendMode() + { + return this.state.blendMode; + } + + set blendMode(value) // eslint-disable-line require-jsdoc + { + this.state.blendMode = value; + } + + /** + * The default vertex shader source + * + * @static + * @constant + */ + static get defaultVertexSrc() + { + return defaultVertex; + } + + /** + * The default fragment shader source + * + * @static + * @constant + */ + static get defaultFragmentSrc() + { + return defaultFragment; + } +} + +/** + * Used for caching shader IDs + * + * @static + * @private + */ +Filter.SOURCE_KEY_MAP = {}; + diff --git a/packages/core/src/filters/FilterSystem.js b/packages/core/src/filters/FilterSystem.js new file mode 100644 index 0000000..3174318 --- /dev/null +++ b/packages/core/src/filters/FilterSystem.js @@ -0,0 +1,431 @@ +import System from '../System'; + +import RenderTexture from '../renderTexture/RenderTexture'; +import Quad from '../utils/Quad'; +import QuadUv from '../utils/QuadUv'; +import { Rectangle } from '@pixi/math'; +import * as filterTransforms from './filterTransforms'; +import bitTwiddle from 'bit-twiddle'; +import UniformGroup from '../shader/UniformGroup'; +import { DRAW_MODES } from '@pixi/constants'; + +// +/** + * @ignore + * @class + */ +class FilterState +{ + /** + * + */ + constructor() + { + this.renderTexture = null; + this.sourceFrame = new Rectangle(); + this.destinationFrame = new Rectangle(); + this.filters = []; + this.target = null; + this.legacy = false; + this.resolution = 1; + } +} + +/** + * @class + * @memberof PIXI.systems + * @extends PIXI.System + */ +export default class FilterSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + /** + * stores a bunch of PO2 textures used for filtering + * @type {Object} + */ + this.texturePool = {}; + + /** + * a pool for storing filter states, save us creating new ones each tick + * @type {Array} + */ + this.statePool = []; + + /** + * A very simple geometry used when drawing a filter effect to the screen + * @type {Quad} + */ + this.quad = new Quad(); + + this.quadUv = new QuadUv(); + + /** + * Temporary rect for maths + * @type {PIXI.Rectangle} + */ + this.tempRect = new Rectangle(); + + this.activeState = {}; + + /** + * this uniform group is attached to filter uniforms when used + * @type {UniformGroup} + */ + this.globalUniforms = new UniformGroup({ + sourceFrame: this.tempRect, + destinationFrame: this.tempRect, + + // legacy variables + filterArea: new Float32Array(4), + filterClamp: new Float32Array(4), + }, true); + } + + /** + * Adds a new filter to the System. + * + * @param {PIXI.DisplayObject} target - The target of the filter to render. + * @param {PIXI.Filter[]} filters - The filters to apply. + */ + push(target, filters) + { + const renderer = this.renderer; + const filterStack = this.renderer.renderTexture.defaultFilterStack; + const state = this.statePool.pop() || new FilterState(); + + let resolution = filters[0].resolution; + let padding = filters[0].padding; + let autoFit = filters[0].autoFit; + let legacy = filters[0].legacy; + + for (let i = 1; i < filters.length; i++) + { + const filter = filters[i]; + + // lets use the lowest resolution.. + resolution = Math.min(resolution, filter.resolution); + // and the largest amount of padding! + padding = Math.max(padding, filter.padding); + // only auto fit if all filters are autofit + autoFit = autoFit || filter.autoFit; + + legacy = legacy || filter.legacy; + } + + filterStack.push(state); + + state.resolution = resolution; + + state.legacy = legacy; + + // round to whole number based on resolution + // TODO move that to the shader too? + state.sourceFrame = target.filterArea || target.getBounds(true); + + state.sourceFrame.pad(padding); + + if (autoFit) + { + state.sourceFrame.fit(this.renderer.renderTexture.destinationFrame); + } + + state.sourceFrame.round(resolution); + + state.renderTexture = this.getPotFilterTexture(state.sourceFrame.width, state.sourceFrame.height, resolution); + state.filters = filters; + + state.destinationFrame.width = state.renderTexture.width; + state.destinationFrame.height = state.renderTexture.height; + + state.renderTexture.filterFrame = state.sourceFrame; + renderer.renderTexture.bind(state.renderTexture, state.sourceFrame);// /, state.destinationFrame); + renderer.renderTexture.clear(); + } + + /** + * Pops off the filter and applies it. + * + */ + pop() + { + const renderer = this.renderer; + const filterStack = renderer.renderTexture.defaultFilterStack; + const state = filterStack.pop(); + const filters = state.filters; + + this.activeState = state; + + const globalUniforms = this.globalUniforms.uniforms; + + globalUniforms.sourceFrame = state.sourceFrame; + globalUniforms.destinationFrame = state.destinationFrame; + globalUniforms.resolution = state.resolution; + + // only update the rect if its legacy.. + if (state.legacy) + { + const filterArea = globalUniforms.filterArea; + const filterClamp = globalUniforms.filterClamp; + + filterArea[0] = state.destinationFrame.width; + filterArea[1] = state.destinationFrame.height; + filterArea[2] = state.sourceFrame.x; + filterArea[3] = state.sourceFrame.y; + + filterClamp[0] = 0.5 / state.resolution / state.destinationFrame.width; + filterClamp[1] = 0.5 / state.resolution / state.destinationFrame.height; + filterClamp[2] = (state.sourceFrame.width - 0.5) / state.resolution / state.destinationFrame.width; + filterClamp[3] = (state.sourceFrame.height - 0.5) / state.resolution / state.destinationFrame.height; + } + + this.globalUniforms.update(); + + const lastState = filterStack[filterStack.length - 1]; + + if (filters.length === 1) + { + filters[0].apply(this, state.renderTexture, lastState.renderTexture, false, state); + + this.returnFilterTexture(state.renderTexture); + } + else + { + let flip = state.renderTexture; + let flop = this.getPotFilterTexture( + flip.width, + flip.height, + state.resolution + ); + + flop.filterFrame = flip.filterFrame; + + let i = 0; + + for (i = 0; i < filters.length - 1; ++i) + { + filters[i].apply(this, flip, flop, true, state); + + const t = flip; + + flip = flop; + flop = t; + } + + filters[i].apply(this, flip, lastState.renderTexture, false, state); + + this.returnFilterTexture(flip); + this.returnFilterTexture(flop); + } + + this.statePool.push(state); + } + + /** + * Draws a filter. + * + * @param {PIXI.Filter} filter - The filter to draw. + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + * @param {boolean} clear - Should the output be cleared before rendering to it + */ + applyFilter(filter, input, output, clear) + { + const renderer = this.renderer; + + renderer.renderTexture.bind(output, output ? output.filterFrame : null); + + if (clear) + { + // gl.disable(gl.SCISSOR_TEST); + renderer.renderTexture.clear(); + // gl.enable(gl.SCISSOR_TEST); + } + + // set the uniforms.. + filter.uniforms.uSampler = input; + filter.uniforms.filterGlobals = this.globalUniforms; + + // TODO make it so that the order of this does not matter.. + // because it does at the moment cos of global uniforms. + // they need to get resynced + + renderer.state.setState(filter.state); + renderer.shader.bind(filter); + + if (filter.legacy) + { + this.quadUv.map(input._frame, input.filterFrame); + + renderer.geometry.bind(this.quadUv); + renderer.geometry.draw(DRAW_MODES.TRIANGLES); + } + else + { + renderer.geometry.bind(this.quad); + renderer.geometry.draw(DRAW_MODES.TRIANGLE_STRIP); + } + } + + /** + * Calculates the mapped matrix. + * + * TODO playing around here.. this is temporary - (will end up in the shader) + * this returns a matrix that will normalise map filter cords in the filter to screen space + * + * @param {PIXI.Matrix} outputMatrix - the matrix to output to. + * @return {PIXI.Matrix} The mapped matrix. + */ + calculateScreenSpaceMatrix(outputMatrix) + { + const currentState = this.activeState; + + return filterTransforms.calculateScreenSpaceMatrix( + outputMatrix, + currentState.sourceFrame, + currentState.destinationFrame + ); + } + + /** + * This will map the filter coord so that a texture can be used based on the transform of a sprite + * + * @param {PIXI.Matrix} outputMatrix - The matrix to output to. + * @param {PIXI.Sprite} sprite - The sprite to map to. + * @return {PIXI.Matrix} The mapped matrix. + */ + calculateSpriteMatrix(outputMatrix, sprite) + { + const currentState = this.activeState; + + return filterTransforms.calculateSpriteMatrix( + outputMatrix, + currentState.sourceFrame, + currentState.destinationFrame, + sprite + ); + } + + /** + * Destroys this Filter System. + * + * @param {boolean} [contextLost=false] context was lost, do not free shaders + * + */ + destroy(contextLost = false) + { + if (!contextLost) + { + this.emptyPool(); + } + else + { + this.texturePool = {}; + } + } + + /** + * Gets a Power-of-Two render texture. + * + * TODO move to a seperate class could be on renderer? + * also - could cause issue with multiple contexts? + * + * @private + * @param {WebGLRenderingContext} gl - The webgl rendering context + * @param {number} minWidth - The minimum width of the render target. + * @param {number} minHeight - The minimum height of the render target. + * @param {number} resolution - The resolution of the render target. + * @return {PIXI.RenderTarget} The new render target. + */ + getPotFilterTexture(minWidth, minHeight, resolution) + { + minWidth = bitTwiddle.nextPow2(minWidth); + minHeight = bitTwiddle.nextPow2(minHeight); + resolution = resolution || 1; + + const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + + if (!this.texturePool[key]) + { + this.texturePool[key] = []; + } + + let renderTexture = this.texturePool[key].pop(); + + if (!renderTexture) + { + // temporary bypass cache.. + // internally - this will cause a texture to be bound.. + renderTexture = RenderTexture.create({ + width: minWidth, + height: minHeight, + resolution, + }); + } + + return renderTexture; + } + + /** + * Gets extra render texture to use inside current filter + * + * @param {number} resolution resolution of the renderTexture + * @returns {PIXI.RenderTexture} + */ + getFilterTexture(resolution) + { + const rt = this.activeState.renderTexture; + + const filterTexture = this.getPotFilterTexture(rt.width, rt.height, resolution || rt.baseTexture.resolution); + + filterTexture.filterFrame = rt.filterFrame; + + return filterTexture; + } + + /** + * Frees a render texture back into the pool. + * + * @param {PIXI.RenderTarget} renderTexture - The renderTarget to free + */ + returnFilterTexture(renderTexture) + { + renderTexture.filterFrame = null; + + const base = renderTexture.baseTexture; + + const minWidth = base.width; + const minHeight = base.height; + + const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + + this.texturePool[key].push(renderTexture); + } + + /** + * Empties the texture pool. + * + */ + emptyPool() + { + for (const i in this.texturePool) + { + const textures = this.texturePool[i]; + + if (textures) + { + for (let j = 0; j < textures.length; j++) + { + textures[j].destroy(true); + } + } + } + + this.texturePool = {}; + } +} diff --git a/packages/core/src/filters/defaultFilter.frag b/packages/core/src/filters/defaultFilter.frag new file mode 100644 index 0000000..a14c1be --- /dev/null +++ b/packages/core/src/filters/defaultFilter.frag @@ -0,0 +1,22 @@ +varying vec2 vTextureCoord; +varying vec2 vFilterCoord; + +uniform sampler2D uSampler; +uniform sampler2D filterSampler; + +void main(void){ + vec4 masky = texture2D(filterSampler, vFilterCoord); + vec4 sample = texture2D(uSampler, vTextureCoord); + vec4 color; + if(mod(vFilterCoord.x, 1.0) > 0.5) + { + color = vec4(1.0, 0.0, 0.0, 1.0); + } + else + { + color = vec4(0.0, 1.0, 0.0, 1.0); + } + // gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0); + gl_FragColor = mix(sample, masky, 0.5); + gl_FragColor *= sample.a; +} diff --git a/packages/core/src/filters/defaultFilter.vert b/packages/core/src/filters/defaultFilter.vert new file mode 100644 index 0000000..e1febca --- /dev/null +++ b/packages/core/src/filters/defaultFilter.vert @@ -0,0 +1,14 @@ +attribute vec2 aVertexPosition; +attribute vec2 aTextureCoord; + +uniform mat3 projectionMatrix; +uniform mat3 filterMatrix; + +varying vec2 vTextureCoord; +varying vec2 vFilterCoord; + +void main(void){ + gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); + vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy; + vTextureCoord = aTextureCoord ; +} diff --git a/packages/core/src/filters/filterTransforms.js b/packages/core/src/filters/filterTransforms.js new file mode 100644 index 0000000..87a3cd6 --- /dev/null +++ b/packages/core/src/filters/filterTransforms.js @@ -0,0 +1,50 @@ +import { Matrix } from '@pixi/math'; + +/** + * Calculates the mapped matrix + * @param filterArea {Rectangle} The filter area + * @param sprite {Sprite} the target sprite + * @param outputMatrix {Matrix} @alvin + * @private + */ +// this returns a matrix that will normalise map filter cords in the filter to screen space +export function calculateScreenSpaceMatrix(outputMatrix, filterArea, textureSize) +{ + // TODO unwrap? + const mappedMatrix = outputMatrix.identity(); + + mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); + + mappedMatrix.scale(textureSize.width, textureSize.height); + + return mappedMatrix; +} + +export function calculateNormalizedScreenSpaceMatrix(outputMatrix, filterArea, textureSize) +{ + const mappedMatrix = outputMatrix.identity(); + + mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); + + const translateScaleX = (textureSize.width / filterArea.width); + const translateScaleY = (textureSize.height / filterArea.height); + + mappedMatrix.scale(translateScaleX, translateScaleY); + + return mappedMatrix; +} + +// this will map the filter coord so that a texture can be used based on the transform of a sprite +export function calculateSpriteMatrix(outputMatrix, filterArea, textureSize, sprite) +{ + const orig = sprite._texture.orig; + const mappedMatrix = outputMatrix.set(textureSize.width, 0, 0, textureSize.height, filterArea.x, filterArea.y); + const worldTransform = sprite.worldTransform.copyTo(Matrix.TEMP_MATRIX); + + worldTransform.invert(); + mappedMatrix.prepend(worldTransform); + mappedMatrix.scale(1.0 / orig.width, 1.0 / orig.height); + mappedMatrix.translate(sprite.anchor.x, sprite.anchor.y); + + return mappedMatrix; +} diff --git a/packages/core/src/filters/spriteMask/SpriteMaskFilter.js b/packages/core/src/filters/spriteMask/SpriteMaskFilter.js new file mode 100644 index 0000000..91697b7 --- /dev/null +++ b/packages/core/src/filters/spriteMask/SpriteMaskFilter.js @@ -0,0 +1,63 @@ +import Filter from '../Filter'; +import { Matrix } from '@pixi/math'; +import vertex from './spriteMaskFilter.vert'; +import fragment from './spriteMaskFilter.frag'; +import { default as TextureMatrix } from '../../textures/TextureMatrix'; + +/** + * The SpriteMaskFilter class + * + * @class + * @extends PIXI.Filter + * @memberof PIXI + */ +export default class SpriteMaskFilter extends Filter +{ + /** + * @param {PIXI.Sprite} sprite - the target sprite + */ + constructor(sprite) + { + const maskMatrix = new Matrix(); + + super(vertex, fragment); + + sprite.renderable = false; + + this.maskSprite = sprite; + this.maskMatrix = maskMatrix; + } + + /** + * Applies the filter + * + * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + */ + apply(filterManager, input, output) + { + const maskSprite = this.maskSprite; + const tex = this.maskSprite.texture; + + if (!tex.valid) + { + return; + } + if (!tex.transform) + { + // margin = 0.0, let it bleed a bit, shader code becomes easier + // assuming that atlas textures were made with 1-pixel padding + tex.transform = new TextureMatrix(tex, 0.0); + } + tex.transform.update(); + + this.uniforms.mask = tex; + this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite) + .prepend(tex.transform.mapCoord); + this.uniforms.alpha = maskSprite.worldAlpha; + this.uniforms.maskClamp = tex.transform.uClampFrame; + + filterManager.applyFilter(this, input, output); + } +} diff --git a/packages/core/src/filters/spriteMask/spriteMaskFilter.frag b/packages/core/src/filters/spriteMask/spriteMaskFilter.frag new file mode 100644 index 0000000..0e4aef8 --- /dev/null +++ b/packages/core/src/filters/spriteMask/spriteMaskFilter.frag @@ -0,0 +1,23 @@ +varying vec2 vMaskCoord; +varying vec2 vTextureCoord; + +uniform sampler2D uSampler; +uniform sampler2D mask; +uniform float alpha; +uniform vec4 maskClamp; + +void main(void) +{ + float clip = step(3.5, + step(maskClamp.x, vMaskCoord.x) + + step(maskClamp.y, vMaskCoord.y) + + step(vMaskCoord.x, maskClamp.z) + + step(vMaskCoord.y, maskClamp.w)); + + vec4 original = texture2D(uSampler, vTextureCoord); + vec4 masky = texture2D(mask, vMaskCoord); + + original *= (masky.r * masky.a * alpha * clip); + + gl_FragColor = original; +} diff --git a/packages/core/src/filters/spriteMask/spriteMaskFilter.vert b/packages/core/src/filters/spriteMask/spriteMaskFilter.vert new file mode 100644 index 0000000..d23a30c --- /dev/null +++ b/packages/core/src/filters/spriteMask/spriteMaskFilter.vert @@ -0,0 +1,16 @@ +attribute vec2 aVertexPosition; +attribute vec2 aTextureCoord; + +uniform mat3 projectionMatrix; +uniform mat3 otherMatrix; + +varying vec2 vMaskCoord; +varying vec2 vTextureCoord; + +void main(void) +{ + gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); + + vTextureCoord = aTextureCoord; + vMaskCoord = ( otherMatrix * vec3( aTextureCoord, 1.0) ).xy; +} diff --git a/packages/core/src/framebuffer/FrameBuffer.js b/packages/core/src/framebuffer/FrameBuffer.js new file mode 100644 index 0000000..84e9fcc --- /dev/null +++ b/packages/core/src/framebuffer/FrameBuffer.js @@ -0,0 +1,106 @@ +import Texture from '../textures/BaseTexture'; +import { FORMATS, TYPES } from '@pixi/constants'; + +/** + * Frame buffer + * @class + * @memberof PIXI + */ +export default class FrameBuffer +{ + constructor(width, height) + { + this.width = width || 100; + this.height = height || 100; + + this.stencil = false; + this.depth = false; + + this.dirtyId = 0; + this.dirtyFormat = 0; + this.dirtySize = 0; + + this.depthTexture = null; + this.colorTextures = []; + + this.glFrameBuffers = {}; + } + + get colorTexture() + { + return this.colorTextures[0]; + } + + addColorTexture(index, texture) + { + // TODO add some validation to the texture - same width / height etc? + this.colorTextures[index || 0] = texture || new Texture(null, { scaleMode: 0, + resolution: 1, + mipmap: false, + width: this.width, + height: this.height });// || new Texture(); + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + addDepthTexture(texture) + { + /* eslint-disable max-len */ + this.depthTexture = texture || new Texture(null, { scaleMode: 0, + resolution: 1, + width: this.width, + height: this.height, + mipmap: false, + format: FORMATS.DEPTH_COMPONENT, + type: TYPES.UNSIGNED_SHORT });// UNSIGNED_SHORT; + /* eslint-disable max-len */ + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + enableDepth() + { + this.depth = true; + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + enableStencil() + { + this.stencil = true; + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + resize(width, height) + { + if (width === this.width && height === this.height) return; + + this.width = width; + this.height = height; + + this.dirtyId++; + this.dirtySize++; + + for (let i = 0; i < this.colorTextures.length; i++) + { + this.colorTextures[i].setSize(width, height); + } + + if (this.depthTexture) + { + this.depthTexture.setSize(width, height); + } + } +} diff --git a/packages/core/src/framebuffer/FramebufferSystem.js b/packages/core/src/framebuffer/FramebufferSystem.js new file mode 100644 index 0000000..2cc2437 --- /dev/null +++ b/packages/core/src/framebuffer/FramebufferSystem.js @@ -0,0 +1,246 @@ +import System from '../System'; +import { Rectangle } from '@pixi/math'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class FramebufferSystem extends System +{ + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + contextChange() + { + this.gl = this.renderer.gl; + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + this.current = null; + this.viewport = new Rectangle(); + + this.drawBufferExtension = this.renderer.context.extensions.drawBuffers; + } + + bind(framebuffer, frame) + { + const gl = this.gl; + + this.current = framebuffer; + + if (framebuffer) + { + // TODO cacheing layer! + + const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID] || this.initFramebuffer(framebuffer); + + gl.bindFramebuffer(gl.FRAMEBUFFER, fbo.framebuffer); + // makesure all textures are unbound.. + + // now check for updates... + if (fbo.dirtyId !== framebuffer.dirtyId) + { + fbo.dirtyId = framebuffer.dirtyId; + + if (fbo.dirtyFormat !== framebuffer.dirtyFormat) + { + fbo.dirtyFormat = framebuffer.dirtyFormat; + this.updateFramebuffer(framebuffer); + } + else if (fbo.dirtySize !== framebuffer.dirtySize) + { + fbo.dirtySize = framebuffer.dirtySize; + this.resizeFramebuffer(framebuffer); + } + } + + for (let i = 0; i < framebuffer.colorTextures.length; i++) + { + if (framebuffer.colorTextures[i].texturePart) + { + this.renderer.texture.unbind(framebuffer.colorTextures[i].texture); + } + else + { + this.renderer.texture.unbind(framebuffer.colorTextures[i]); + } + } + + if (framebuffer.depthTexture) + { + this.renderer.texture.unbind(framebuffer.depthTexture); + } + + if (frame) + { + this.setViewport(frame.x, frame.y, frame.width, frame.height); + } + else + { + this.setViewport(0, 0, framebuffer.width, framebuffer.height); + } + } + else + { + gl.bindFramebuffer(gl.FRAMEBUFFER, null); + + if (frame) + { + this.setViewport(frame.x, frame.y, frame.width, frame.height); + } + else + { + this.setViewport(0, 0, this.renderer.width, this.renderer.height); + } + } + } + + setViewport(x, y, width, height) + { + const v = this.viewport; + + if (v.width !== width || v.height !== height || v.x !== x || v.y !== y) + { + v.x = x; + v.y = y; + v.width = width; + v.height = height; + + this.gl.viewport(x, y, width, height); + } + } + + get size() + { + if (this.current) + { + // TODO store temp + return { x: 0, y: 0, width: this.current.width, height: this.current.height }; + } + + return { x: 0, y: 0, width: this.renderer.width, height: this.renderer.height }; + } + + clear(r, g, b, a) + { + const gl = this.gl; + + // TODO clear color can be set only one right? + gl.clearColor(r, g, b, a); + gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); + } + + // private functions... + + initFramebuffer(framebuffer) + { + const gl = this.gl; + + // TODO - make this a class? + const fbo = { + framebuffer: gl.createFramebuffer(), + stencil: null, + dirtyId: 0, + dirtyFormat: 0, + dirtySize: 0, + }; + + framebuffer.glFrameBuffers[this.CONTEXT_UID] = fbo; + + return fbo; + } + + resizeFramebuffer(framebuffer) + { + const gl = this.gl; + + if (framebuffer.stencil || framebuffer.depth) + { + gl.bindRenderbuffer(gl.RENDERBUFFER, this.stencil); + gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); + } + } + + updateFramebuffer(framebuffer) + { + const gl = this.gl; + + const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID]; + + // bind the color texture + const colorTextures = framebuffer.colorTextures; + + let count = colorTextures.length; + + if (!this.drawBufferExtension) + { + count = Math.min(count, 1); + } + + const activeTextures = []; + + for (let i = 0; i < count; i++) + { + const texture = framebuffer.colorTextures[i]; + + if (texture.texturePart) + { + this.renderer.texture.bind(texture.texture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.COLOR_ATTACHMENT0 + i, + gl.TEXTURE_CUBE_MAP_NEGATIVE_X + texture.side, + texture.texture._glTextures[this.CONTEXT_UID].texture, + 0); + } + else + { + this.renderer.texture.bind(texture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.COLOR_ATTACHMENT0 + i, + gl.TEXTURE_2D, + texture._glTextures[this.CONTEXT_UID].texture, + 0); + } + + activeTextures.push(gl.COLOR_ATTACHMENT0 + i); + } + + if (this.drawBufferExtension && activeTextures.length > 1) + { + this.drawBufferExtension.drawBuffersWEBGL(activeTextures); + } + + if (framebuffer.depthTexture) + { + const depthTextureExt = this.renderer.context.extensions.depthTexture; + + if (depthTextureExt) + { + const depthTexture = framebuffer.depthTexture; + + this.renderer.texture.bind(depthTexture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.DEPTH_ATTACHMENT, + gl.TEXTURE_2D, + depthTexture._glTextures[this.CONTEXT_UID].texture, + 0); + } + } + + if (framebuffer.stencil || framebuffer.depth) + { + fbo.stencil = gl.createRenderbuffer(); + + gl.bindRenderbuffer(gl.RENDERBUFFER, fbo.stencil); + + // TODO.. this is depth AND stencil? + gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, fbo.stencil); + gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); + // fbo.enableStencil(); + } + } +} diff --git a/packages/core/src/geometry/GLBuffer.js b/packages/core/src/geometry/GLBuffer.js new file mode 100644 index 0000000..0477e8a --- /dev/null +++ b/packages/core/src/geometry/GLBuffer.js @@ -0,0 +1,9 @@ +export default class GLBuffer +{ + constructor(buffer) + { + this.buffer = buffer; + this.updateID = -1; + this.byteLength = -1; + } +} diff --git a/packages/core/src/geometry/GeometrySystem.js b/packages/core/src/geometry/GeometrySystem.js new file mode 100644 index 0000000..959f827 --- /dev/null +++ b/packages/core/src/geometry/GeometrySystem.js @@ -0,0 +1,388 @@ +import System from '../System'; +import GLBuffer from './GLBuffer'; + +const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class GeometrySystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this._activeGeometry = null; + this._activeVao = null; + + this.hasVao = true; + this.hasInstance = true; + } + + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + contextChange() + { + const gl = this.gl = this.renderer.gl; + + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + + // webgl2 + if (!gl.createVertexArray) + { + // webgl 1! + let nativeVaoExtension = this.renderer.context.extensions.vertexArrayObject; + + if (this.renderer.legacy) + { + nativeVaoExtension = null; + } + + if (nativeVaoExtension) + { + gl.createVertexArray = () => + nativeVaoExtension.createVertexArrayOES(); + + gl.bindVertexArray = (vao) => + nativeVaoExtension.bindVertexArrayOES(vao); + + gl.deleteVertexArray = (vao) => + nativeVaoExtension.deleteVertexArrayOES(vao); + } + else + { + this.hasVao = false; + gl.createVertexArray = () => + { + // empty + }; + + gl.bindVertexArray = () => + { + // empty + }; + + gl.deleteVertexArray = () => + { + // empty + }; + } + } + + if (!gl.vertexAttribDivisor) + { + const instanceExt = gl.getExtension('ANGLE_instanced_arrays'); + + if (instanceExt) + { + gl.vertexAttribDivisor = (a, b) => + instanceExt.vertexAttribDivisorANGLE(a, b); + + gl.drawElementsInstanced = (a, b, c, d, e) => + instanceExt.drawElementsInstancedANGLE(a, b, c, d, e); + + gl.drawArraysInstanced = (a, b, c, d) => + instanceExt.drawArraysInstancedANGLE(a, b, c, d); + } + else + { + this.hasInstance = false; + } + } + } + + /** + * Binds geometry so that is can be drawn. Creating a Vao if required + * @private + * @param {PIXI.Geometry} geometry instance of geometry to bind + */ + bind(geometry, shader) + { + shader = shader || this.renderer.shader.shader; + + const gl = this.gl; + + // not sure the best way to address this.. + // currently different shaders require different VAOs for the same geometry + // Still mulling over the best way to solve this one.. + // will likely need to modify the shader attribute locations at run time! + let vaos = geometry.glVertexArrayObjects[this.CONTEXT_UID]; + + if (!vaos) + { + geometry.glVertexArrayObjects[this.CONTEXT_UID] = vaos = {}; + } + + const vao = vaos[shader.program.id] || this.initGeometryVao(geometry, shader.program); + + this._activeGeometry = geometry; + + if (this._activeVao !== vao) + { + this._activeVao = vao; + + if (this.hasVao) + { + gl.bindVertexArray(vao); + } + else + { + this.activateVao(geometry, shader.program); + } + } + + // TODO - optimise later! + // don't need to loop through if nothing changed! + // maybe look to add an 'autoupdate' to geometry? + this.updateBuffers(); + } + + reset() + { + this.unbind(); + } + + updateBuffers() + { + const geometry = this._activeGeometry; + const gl = this.gl; + + for (let i = 0; i < geometry.buffers.length; i++) + { + const buffer = geometry.buffers[i]; + + const glBuffer = buffer._glBuffers[this.CONTEXT_UID]; + + if (buffer._updateID !== glBuffer.updateID) + { + glBuffer.updateID = buffer._updateID; + + // TODO can cache this on buffer! maybe added a getter / setter? + const type = buffer.index ? gl.ELEMENT_ARRAY_BUFFER : gl.ARRAY_BUFFER; + const drawType = buffer.static ? gl.STATIC_DRAW : gl.DYNAMIC_DRAW; + + gl.bindBuffer(type, glBuffer.buffer); + + if (glBuffer.byteLength >= buffer.data.byteLength) + { + // offset is always zero for now! + gl.bufferSubData(type, 0, buffer.data); + } + else + { + gl.bufferData(type, buffer.data, drawType); + } + } + } + } + + checkCompatability(geometry, program) + { + // geometry must have at least all the attributes that the shader requires. + const geometryAttributes = geometry.attributes; + const shaderAttributes = program.attributeData; + + for (const j in shaderAttributes) + { + if (!geometryAttributes[j]) + { + throw new Error(`shader and geometry incompatible, geometry missing the "${j}" attribute`); + } + } + } + + /** + * Creates a Vao with the same structure as the geometry and stores it on the geometry. + * @private + * @param {PIXI.Geometry} geometry instance of geometry to to generate Vao for + */ + initGeometryVao(geometry, program) + { + this.checkCompatability(geometry, program); + + const gl = this.gl; + const CONTEXT_UID = this.CONTEXT_UID; + const buffers = geometry.buffers; + const attributes = geometry.attributes; + + const tempStride = {}; + const tempStart = {}; + + for (const j in buffers) + { + tempStride[j] = 0; + tempStart[j] = 0; + } + + for (const j in attributes) + { + if (!attributes[j].size && program.attributeData[j]) + { + attributes[j].size = program.attributeData[j].size; + } + + tempStride[attributes[j].buffer] += attributes[j].size * byteSizeMap[attributes[j].type]; + } + + for (const j in attributes) + { + const attribute = attributes[j]; + const attribSize = attribute.size; + + if (attribute.stride === undefined) + { + if (tempStride[attribute.buffer] === attribSize * byteSizeMap[attribute.type]) + { + attribute.stride = 0; + } + else + { + attribute.stride = tempStride[attribute.buffer]; + } + } + + if (attribute.start === undefined) + { + attribute.start = tempStart[attribute.buffer]; + + tempStart[attribute.buffer] += attribSize * byteSizeMap[attribute.type]; + } + } + + // first update - and create the buffers! + // only create a gl buffer if it actually gets + for (let i = 0; i < buffers.length; i++) + { + const buffer = buffers[i]; + + if (!buffer._glBuffers[CONTEXT_UID]) + { + buffer._glBuffers[CONTEXT_UID] = new GLBuffer(gl.createBuffer()); + } + } + + // TODO - maybe make this a data object? + // lets wait to see if we need to first! + const vao = gl.createVertexArray(); + + gl.bindVertexArray(vao); + + this.activateVao(geometry, program); + + gl.bindVertexArray(null); + + // add it to the cache! + geometry.glVertexArrayObjects[this.CONTEXT_UID][program.id] = vao; + + return vao; + } + + activateVao(geometry, program) + { + const gl = this.gl; + const CONTEXT_UID = this.CONTEXT_UID; + const buffers = geometry.buffers; + const attributes = geometry.attributes; + + if (geometry.indexBuffer) + { + // first update the index buffer if we have one.. + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, geometry.indexBuffer._glBuffers[CONTEXT_UID].buffer); + } + + let lastBuffer = null; + + // add a new one! + for (const j in attributes) + { + const attribute = attributes[j]; + const buffer = buffers[attribute.buffer]; + const glBuffer = buffer._glBuffers[CONTEXT_UID]; + + if (program.attributeData[j]) + { + if (lastBuffer !== glBuffer) + { + gl.bindBuffer(gl.ARRAY_BUFFER, glBuffer.buffer); + + lastBuffer = glBuffer; + } + + const location = program.attributeData[j].location; + + // TODO introduce state again + // we can optimise this for older devices that have no VAOs + gl.enableVertexAttribArray(location); + + gl.vertexAttribPointer(location, + attribute.size, + attribute.type || gl.FLOAT, + attribute.normalized, + attribute.stride, + attribute.start); + + if (attribute.instance) + { + // TODO calculate instance count based of this... + if (this.hasInstance) + { + gl.vertexAttribDivisor(location, 1); + } + else + { + throw new Error('geometry error, GPU Instancing is not supported on this device'); + } + } + } + } + } + + draw(type, size, start, instanceCount) + { + const gl = this.gl; + const geometry = this._activeGeometry; + + // TODO.. this should not change so maybe cache the function? + + if (geometry.indexBuffer) + { + if (geometry.instanced) + { + /* eslint-disable max-len */ + gl.drawElementsInstanced(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2, instanceCount || 1); + /* eslint-enable max-len */ + } + else + { + gl.drawElements(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2); + } + } + else + if (geometry.instanced) + { + // TODO need a better way to calculate size.. + gl.drawArraysInstanced(type, start, size || geometry.getSize(), instanceCount || 1); + } + else + { + gl.drawArrays(type, start, size || geometry.getSize()); + } + + return this; + } + + unbind() + { + this.gl.bindVertexArray(null); + this._activeVao = null; + this._activeGeometry = null; + } +} diff --git a/packages/core/src/geometry/utils/setVertexAttribArrays.js b/packages/core/src/geometry/utils/setVertexAttribArrays.js new file mode 100644 index 0000000..23b6959 --- /dev/null +++ b/packages/core/src/geometry/utils/setVertexAttribArrays.js @@ -0,0 +1,54 @@ +// var GL_MAP = {}; + +/** + * @param gl {WebGLRenderingContext} The current WebGL context + * @param attribs {*} + * @param state {*} + */ +export default function setVertexAttribArrays(gl, attribs, state) +{ + let i; + + if (state) + { + const tempAttribState = state.tempAttribState; + const attribState = state.attribState; + + for (i = 0; i < tempAttribState.length; i++) + { + tempAttribState[i] = false; + } + + // set the new attribs + for (i = 0; i < attribs.length; i++) + { + tempAttribState[attribs[i].attribute.location] = true; + } + + for (i = 0; i < attribState.length; i++) + { + if (attribState[i] !== tempAttribState[i]) + { + attribState[i] = tempAttribState[i]; + + if (state.attribState[i]) + { + gl.enableVertexAttribArray(i); + } + else + { + gl.disableVertexAttribArray(i); + } + } + } + } + else + { + for (i = 0; i < attribs.length; i++) + { + const attrib = attribs[i]; + + gl.enableVertexAttribArray(attrib.attribute.location); + } + } +} diff --git a/packages/core/src/AbstractRenderer.js b/packages/core/src/AbstractRenderer.js new file mode 100644 index 0000000..64b2eb1 --- /dev/null +++ b/packages/core/src/AbstractRenderer.js @@ -0,0 +1,353 @@ +import { hex2string, hex2rgb } from '@pixi/utils'; +import { Matrix, Rectangle } from '@pixi/math'; +import { RENDERER_TYPE } from '@pixi/constants'; +import { settings } from '@pixi/settings'; +import { Container } from '@pixi/display'; +import RenderTexture from './renderTexture/RenderTexture'; +import EventEmitter from 'eventemitter3'; + +const tempMatrix = new Matrix(); + +/** + * 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 EventEmitter + * @memberof PIXI + */ +export default class AbstractRenderer extends EventEmitter +{ + // eslint-disable-next-line valid-jsdoc + /** + * @param {string} system - The name of the system this renderer is for. + * @param {object} [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.transparent=false] - If the render view is transparent, default false + * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false + * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. The + * resolution of the renderer retina would be 2. + * @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 {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when rendering, + * stopping pixel interpolation. + */ + constructor(system, options, arg2, arg3) + { + super(); + + // Support for constructor(system, screenWidth, screenHeight, options) + if (typeof options === 'number') + { + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + }, arg3); + } + + // 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 = RENDERER_TYPE.UNKNOWN; + + /** + * Measurements of the screen. (0, 0, screenWidth, screenHeight) + * + * Its safe to use as filterArea or hitArea for 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 1 + */ + this.resolution = options.resolution || settings.RESOLUTION; + + /** + * Whether the render view is transparent + * + * @member {boolean} + */ + this.transparent = options.transparent; + + /** + * Whether css dimensions of canvas view should be resized to screen dimensions automatically + * + * @member {boolean} + */ + this.autoResize = options.autoResize || false; + + /** + * Tracks the blend modes useful for this renderer. + * + * @member {object} + */ + this.blendModes = null; + + /** + * 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; + + /** + * If true PixiJS will Math.floor() x/y values when rendering, stopping pixel interpolation. + * Handy for crisp pixel art and speed on legacy devices. + * + * @member {boolean} + */ + this.roundPixels = options.roundPixels; + + /** + * The background color as a number. + * + * @member {number} + * @private + */ + this._backgroundColor = 0x000000; + + /** + * The background color as an [R, G, B] array. + * + * @member {number[]} + * @private + */ + this._backgroundColorRgba = [0, 0, 0, 0]; + + /** + * The background color as a string. + * + * @member {string} + * @private + */ + this._backgroundColorString = '#000000'; + + this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter + + /** + * This temporary display object used as the parent of the currently being rendered item + * + * @member {PIXI.DisplayObject} + * @private + */ + this._tempDisplayObjectParent = new Container(); + + /** + * The last root object that the renderer tried to render. + * + * @member {PIXI.DisplayObject} + * @private + */ + this._lastObjectRendered = this._tempDisplayObjectParent; + + /** + * Collection of plugins. + * @readonly + * @member {object} + */ + this.plugins = {}; + } + + /** + * Initialize the plugins. + * + * @protected + * @param {object} staticMap - The dictionary of staticly saved plugins. + */ + initPlugins(staticMap) + { + 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() + { + return this.view.width; + } + + /** + * Same as view.height, actual number of pixels in the canvas by vertical + * + * @member {number} + * @readonly + * @default 600 + */ + get height() + { + return this.view.height; + } + + /** + * Resizes the screen and canvas to the specified width and height + * Canvas dimensions are multiplied by resolution + * + * @param {number} screenWidth - the new width of the screen + * @param {number} screenHeight - the new height of the screen + */ + resize(screenWidth, screenHeight) + { + this.screen.width = screenWidth; + this.screen.height = screenHeight; + + this.view.width = screenWidth * this.resolution; + this.view.height = screenHeight * this.resolution; + + if (this.autoResize) + { + this.view.style.width = `${screenWidth}px`; + this.view.style.height = `${screenHeight}px`; + } + } + + /** + * 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 {PIXI.DisplayObject} displayObject - The displayObject the object will be generated from + * @param {number} scaleMode - Should be one of the scaleMode consts + * @param {number} resolution - The resolution / device pixel ratio of the texture being generated + * @param {PIXI.Rectangle} [region] - The region of the displayObject, that shall be rendered, + * if no region is specified, defaults to the local bounds of the displayObject. + * @return {PIXI.Texture} a texture of the graphics object + */ + generateTexture(displayObject, scaleMode, resolution, region) + { + region = region || displayObject.getLocalBounds(); + + // 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(region.width | 0, region.height | 0, scaleMode, resolution); + + tempMatrix.tx = -region.x; + tempMatrix.ty = -region.y; + + this.render(displayObject, renderTexture, false, tempMatrix, true); + + return renderTexture; + } + + /** + * Removes everything from the renderer and optionally removes the Canvas DOM element. + * + * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. + */ + destroy(removeView) + { + 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); + } + + this.plugins = null; + + this.type = RENDERER_TYPE.UNKNOWN; + + this.view = null; + + this.screen = null; + + this.resolution = 0; + + this.transparent = false; + + this.autoResize = false; + + this.blendModes = null; + + this.options = null; + + this.preserveDrawingBuffer = false; + this.clearBeforeRender = false; + + this.roundPixels = false; + + this._backgroundColor = 0; + this._backgroundColorRgba = null; + this._backgroundColorString = null; + + this._tempDisplayObjectParent = null; + this._lastObjectRendered = null; + } + + /** + * The background color to fill if not transparent + * + * @member {number} + */ + get backgroundColor() + { + return this._backgroundColor; + } + + set backgroundColor(value) // eslint-disable-line require-jsdoc + { + this._backgroundColor = value; + this._backgroundColorString = hex2string(value); + hex2rgb(value, this._backgroundColorRgba); + } +} diff --git a/packages/core/src/Renderer.js b/packages/core/src/Renderer.js new file mode 100644 index 0000000..2227e78 --- /dev/null +++ b/packages/core/src/Renderer.js @@ -0,0 +1,338 @@ +import AbstractRenderer from './AbstractRenderer'; +import { sayHello } from '@pixi/utils'; +import MaskSystem from './mask/MaskSystem'; +import StencilSystem from './mask/StencilSystem'; +import FilterSystem from './filters/FilterSystem'; +import FramebufferSystem from './framebuffer/FramebufferSystem'; +import RenderTextureSystem from './renderTexture/RenderTextureSystem'; +import TextureSystem from './textures/TextureSystem'; +import ProjectionSystem from './projection/ProjectionSystem'; +import StateSystem from './state/StateSystem'; +import GeometrySystem from './geometry/GeometrySystem'; +import ShaderSystem from './shader/ShaderSystem'; +import ContextSystem from './context/ContextSystem'; +import BatchSystem from './batch/BatchSystem'; +import TextureGCSystem from './textures/TextureGCSystem'; +import { RENDERER_TYPE } from '@pixi/constants'; +import UniformGroup from './shader/UniformGroup'; +import { Matrix } from '@pixi/math'; +import Runner from 'mini-runner'; + +/** + * 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 Render works by automatically managing webGLBatchs. + * 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 :) + * + * @class + * @memberof PIXI + * @extends PIXI.AbstractRenderer + */ +export default class Renderer extends AbstractRenderer +{ + // eslint-disable-next-line valid-jsdoc + /** + * + * @param {object} [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.transparent=false] - If the render view is transparent, default false + * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false + * @param {boolean} [options.antialias=false] - sets antialias. If not available natively then FXAA + * antialiasing is used + * @param {boolean} [options.forceFXAA=false] - forces FXAA antialiasing to be used over native. + * FXAA is faster, but may not always look as great + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. + * The resolution of the renderer retina would be 2. + * @param {boolean} [options.clearBeforeRender=true] - This sets if the renderer will clear + * the canvas or not before the new render pass. If you wish to set this to false, you *must* set + * preserveDrawingBuffer to `true`. + * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, + * enable this if you need to call toDataUrl on the webgl context. + * @param {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when + * rendering, stopping pixel interpolation. + * @param {number} [options.backgroundColor=0x000000] - The background color of the rendered area + * (shown if not transparent). + * @param {boolean} [options.legacy=false] - If true PixiJS will aim to ensure compatibility + * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. + * @param {string} [options.powerPreference] - Parameter passed to webgl context, set to "high-performance" + * for devices with dual graphics card + */ + constructor(options, arg2, arg3) + { + super('WebGL', options, arg2, arg3); + + /** + * The type of this renderer as a standardised const + * + * @member {number} + * @see PIXI.RENDERER_TYPE + */ + this.type = RENDERER_TYPE.WEBGL; + + // this will be set by the contextSystem (this.context) + this.gl = null; + this.CONTEXT_UID = 0; + this.legacy = !!options.legacy; + + // TODO legacy! + + // runners! + this.runners = { + destroy: new Runner('destroy'), + contextChange: new Runner('contextChange', 1), + reset: new Runner('reset'), + update: new Runner('update'), + postrender: new Runner('postrender'), + prerender: new Runner('prerender'), + resize: new Runner('resize', 2), + }; + + 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(GeometrySystem, 'geometry') + .addSystem(FramebufferSystem, 'framebuffer') + .addSystem(StencilSystem, 'stencil') + .addSystem(ProjectionSystem, 'projection') + .addSystem(TextureGCSystem, 'textureGC') + .addSystem(FilterSystem, 'filter') + .addSystem(RenderTextureSystem, 'renderTexture') + .addSystem(BatchSystem, 'batch'); + + this.initPlugins(Renderer.__plugins); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ + if (options.context) + { + this.context.initFromContext(options.context); + } + else + { + this.context.initFromOptions({ + alpha: this.transparent, + antialias: options.antialias, + premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', + stencil: true, + 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); + } + + /** + * Add a new system to the renderer. + * @param {class} ClassRef - Class reference + * @param {string} [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. + * @return {PIXI.Renderer} Return instance of renderer + */ + addSystem(ClassRef, name) + { + if (!name) + { + name = ClassRef.name; + } + + // TODO - read name from class.name.. + + /* + if(name.includes('System')) + { + name = name.replace('System', ''); + name = name.charAt(0).toLowerCase() + name.slice(1); + } + */ + + const system = new ClassRef(this); + + if (this[name]) + { + throw new Error(`Whoops! ${name} is already a manger`); + } + + this[name] = system; + + for (const i in this.runners) + { + this.runners[i].add(system); + } + + return this; + + /** + * 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. + */ + } + + /** + * Renders the object to its webGL view + * + * @param {PIXI.DisplayObject} displayObject - the object to be rendered + * @param {PIXI.RenderTexture} renderTexture - The render texture to render to. + * @param {boolean} [clear] - Should the canvas be cleared before the new render + * @param {PIXI.Transform} [transform] - A transform to apply to the render texture before rendering. + * @param {boolean} [skipUpdateTransform] - Should we skip the update transform pass? + */ + render(displayObject, renderTexture, clear, transform, skipUpdateTransform) + { + // can be handy to know! + this.renderingToScreen = !renderTexture; + + this.runners.prerender.run(); + this.emit('prerender'); + + // 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.parent; + + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = 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.run(); + + this.emit('postrender'); + } + + /** + * Resizes the webGL view to the specified width and height. + * + * @param {number} screenWidth - the new width of the screen + * @param {number} screenHeight - the new height of the screen + */ + resize(screenWidth, screenHeight) + { + AbstractRenderer.prototype.resize.call(this, screenWidth, screenHeight); + + this.runners.resize.run(screenWidth, screenHeight); + } + + /** + * Resets the WebGL state so you can render things however you fancy! + * + * @return {PIXI.Renderer} Returns itself. + */ + reset() + { + this.runners.reset.run(); + + return this; + } + + clear() + { + this.framebuffer.bind(); + this.framebuffer.clear(); + } + + /** + * Removes everything from the renderer (event listeners, spritebatch, etc...) + * + * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. + * See: https://github.com/pixijs/pixi.js/issues/2233 + */ + destroy(removeView) + { + this.runners.destroy.run(); + + // call base destroy + super.destroy(removeView); + + // TODO nullify all the managers.. + this.gl = null; + } + + /** + * 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 + * builds and excluding plugins. + * @name PIXI.Renderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. + */ + + /** + * Adds a plugin to the renderer. + * + * @method + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + static registerPlugin(pluginName, ctor) + { + Renderer.__plugins = Renderer.__plugins || {}; + Renderer.__plugins[pluginName] = ctor; + } +} diff --git a/packages/core/src/System.js b/packages/core/src/System.js new file mode 100644 index 0000000..05a10c8 --- /dev/null +++ b/packages/core/src/System.js @@ -0,0 +1,42 @@ +/** + * System is a base class used for extending systems used by the {@link PIXI.Renderer} + * @see PIXI.Renderer#addSystem + * @class + * @memberof PIXI + */ +export default class System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + /** + * The renderer this manager works for. + * + * @member {PIXI.Renderer} + */ + this.renderer = renderer; + + this.renderer.runners.contextChange.add(this); + } + + /** + * Generic method called when there is a WebGL context change. + * + */ + contextChange() + { + // do some codes init! + } + + /** + * Generic destroy methods to be overridden by the subclass + * + */ + destroy() + { + this.renderer.runners.contextChange.remove(this); + this.renderer = null; + } +} diff --git a/packages/core/src/batch/BatchSystem.js b/packages/core/src/batch/BatchSystem.js new file mode 100644 index 0000000..b04c249 --- /dev/null +++ b/packages/core/src/batch/BatchSystem.js @@ -0,0 +1,68 @@ +import System from '../System'; +import ObjectRenderer from './ObjectRenderer'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class BatchSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + super(renderer); + + /** + * An empty renderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.emptyRenderer = new ObjectRenderer(renderer); + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.currentRenderer = this.emptyRenderer; + } + + /** + * Changes the current renderer to the one given in parameter + * + * @param {PIXI.ObjectRenderer} objectRenderer - The object renderer to use. + */ + setObjectRenderer(objectRenderer) + { + if (this.currentRenderer === objectRenderer) + { + return; + } + + this.currentRenderer.stop(); + this.currentRenderer = objectRenderer; + + this.renderer.state.setState(objectRenderer.state); + + this.currentRenderer.start(); + } + + /** + * This should be called if you wish to do some custom rendering + * It will basically render anything that may be batched up such as sprites + * + */ + flush() + { + this.setObjectRenderer(this.emptyRenderer); + } + + reset() + { + this.setObjectRenderer(this.emptyRenderer); + } +} diff --git a/packages/core/src/batch/ObjectRenderer.js b/packages/core/src/batch/ObjectRenderer.js new file mode 100644 index 0000000..0c851a9 --- /dev/null +++ b/packages/core/src/batch/ObjectRenderer.js @@ -0,0 +1,48 @@ +import System from '../System'; + +/** + * Base for a common object renderer that can be used as a system renderer plugin. + * + * @class + * @extends PIXI.System + * @memberof PIXI + */ +export default class ObjectRenderer extends System +{ + /** + * Starts the renderer and sets the shader + * + */ + start() + { + // set the shader.. + } + + /** + * Stops the renderer + * + */ + stop() + { + this.flush(); + } + + /** + * Stub method for rendering content and emptying the current batch. + * + */ + flush() + { + // flush! + } + + /** + * Renders an object + * + * @param {PIXI.DisplayObject} object - The object to render. + */ + render(object) // eslint-disable-line no-unused-vars + { + // render the object + } +} diff --git a/packages/core/src/context/ContextSystem.js b/packages/core/src/context/ContextSystem.js new file mode 100644 index 0000000..301aab4 --- /dev/null +++ b/packages/core/src/context/ContextSystem.js @@ -0,0 +1,186 @@ +import System from '../System'; +import { settings } from '@pixi/settings'; + +let CONTEXT_UID = 0; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class ContextSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.webGLVersion = 1; + + this.handleContextLost = this.handleContextLost.bind(this); + this.handleContextRestored = this.handleContextRestored.bind(this); + + this.extensions = {}; + + renderer.view.addEventListener('webglcontextlost', this.handleContextLost, false); + renderer.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + } + + get isLost() + { + return (!this.gl || this.gl.isContextLost()); + } + + contextChange(gl) + { + this.gl = gl; + + // restore a context if it was previously lost + if (gl.isContextLost() && gl.getExtension('WEBGL_lose_context')) + { + gl.getExtension('WEBGL_lose_context').restoreContext(); + } + } + + initFromContext(gl) + { + this.gl = gl; + this.validateContext(gl); + this.renderer.gl = gl; + this.renderer.CONTEXT_UID = CONTEXT_UID++; + this.renderer.runners.contextChange.run(gl); + } + + initFromOptions(options) + { + const gl = this.createContext(this.renderer.view, options); + + this.initFromContext(gl); + } + + /** + * Helper class to create a webGL Context + * + * @class + * @memberof PIXI.glCore + * @param canvas {HTMLCanvasElement} the canvas element that we will get the context from + * @param options {Object} An options object that gets passed in to the canvas element containing the context attributes, + * see https://developer.mozilla.org/en/docs/Web/API/HTMLCanvasElement/getContext for the options available + * @return {WebGLRenderingContext} the WebGL context + */ + createContext(canvas, options) + { + let gl; + + if (settings.PREFER_WEBGL_2) + { + gl = canvas.getContext('webgl2', options); + } + + if (gl) + { + this.webGLVersion = 2; + } + else + { + this.webGLVersion = 1; + + gl = canvas.getContext('webgl', options) + || canvas.getContext('experimental-webgl', options); + + if (!gl) + { + // fail, not able to get a context + throw new Error('This browser does not support webGL. Try using the canvas renderer'); + } + } + + this.gl = gl; + + this.getExtensions(); + + return gl; + } + + getExtensions() + { + // time to set up default etensions that pixi uses.. + const gl = this.gl; + const extensions = this.extensions; + + if (this.webGLVersion === 1) + { + extensions.drawBuffers = gl.getExtension('WEBGL_draw_buffers'); + extensions.depthTexture = gl.getExtension('WEBKIT_WEBGL_depth_texture'); + extensions.floatTexture = gl.getExtension('OES_texture_float'); + extensions.loseContext = gl.getExtension('WEBGL_lose_context'); + + extensions.vertexArrayObject = gl.getExtension('OES_vertex_array_object') + || gl.getExtension('MOZ_OES_vertex_array_object') + || gl.getExtension('WEBKIT_OES_vertex_array_object'); + } + + // we don't use any specific WebGL 2 ones yet! + } + + /** + * Handles a lost webgl context + * + * @private + * @param {WebGLContextEvent} event - The context lost event. + */ + handleContextLost(event) + { + event.preventDefault(); + } + + /** + * Handles a restored webgl context + * + * @private + */ + handleContextRestored() + { + this.renderer.runners.contextChange.run(this.gl); + } + + destroy() + { + const view = this.renderer.view; + + // remove listeners + view.removeEventListener('webglcontextlost', this.handleContextLost); + view.removeEventListener('webglcontextrestored', this.handleContextRestored); + + this.gl.useProgram(null); + + if (this.extensions.loseContext) + { + this.extensions.loseContext.loseContext(); + } + } + + postrender() + { + this.gl.flush(); + } + + validateContext(gl) + { + const attributes = gl.getContextAttributes(); + + // this is going to be fairly simple for now.. but at least we have room to grow! + if (!attributes.stencil) + { + /* eslint-disable max-len */ + + /* eslint-disable no-console */ + console.warn('Provided WebGL context does not have a stencil buffer, masks may not render correctly'); + /* eslint-enable no-console */ + + /* eslint-enable max-len */ + } + } +} diff --git a/packages/core/src/filters/Filter.js b/packages/core/src/filters/Filter.js new file mode 100644 index 0000000..9d8cad8 --- /dev/null +++ b/packages/core/src/filters/Filter.js @@ -0,0 +1,140 @@ +import Shader from '../shader/Shader'; +import Program from '../shader/Program'; +import State from '../state/State'; +import { settings } from '@pixi/settings'; +// import extractUniformsFromSrc from './extractUniformsFromSrc'; +import defaultVertex from './defaultFilter.vert'; +import defaultFragment from './defaultFilter.frag'; + +// let math = require('../../math'); +/** + * @class + * @memberof PIXI + * @extends PIXI.Shader + */ +export default class Filter extends Shader +{ + /** + * @param {string} [vertexSrc] - The source of the vertex shader. + * @param {string} [fragmentSrc] - The source of the fragment shader. + * @param {object} [uniforms] - Custom uniforms to use to augment the built-in ones. + */ + constructor(vertexSrc, fragmentSrc, uniforms) + { + const program = Program.from(vertexSrc, fragmentSrc); + + super(program, uniforms); + + /** + * The padding of the filter. Some filters require extra space to breath such as a blur. + * Increasing this will add extra width and height to the bounds of the object that the + * filter is applied to. + * + * @member {number} + */ + this.padding = 0; + + /** + * The resolution of the filter. Setting this to be lower will lower the quality but + * increase the performance of the filter. + * + * @member {number} + */ + this.resolution = settings.RESOLUTION; + + /** + * If enabled is true the filter is applied, if false it will not. + * + * @member {boolean} + */ + this.enabled = true; + + /** + * If enabled, PixiJS will fit the filter area into boundaries for better performance. + * Switch it off if it does not work for specific shader. + * + * @member {boolean} + */ + this.autoFit = true; + + /** + * Legacy filters use position and uvs from attributes + * @member {boolean} + * @readonly + */ + this.legacy = !!this.program.attributeData.aTextureCoord; + + /** + * the webGL state the filter requires to render + * @member {PIXI.State} + */ + this.state = new State(); + } + + /** + * Applies the filter + * + * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + * @param {boolean} clear - Should the output be cleared before rendering to it + * @param {object} [currentState] - It's current state of filter. + * There are some useful properties in the currentState : + * target, filters, sourceFrame, destinationFrame, renderTarget, resolution + */ + apply(filterManager, input, output, clear, currentState, derp) // eslint-disable-line no-unused-vars + { + // do as you please! + + filterManager.applyFilter(this, input, output, clear, currentState, derp); + + // or just do a regular render.. + } + + /** + * Sets the blendmode of the filter + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL + */ + get blendMode() + { + return this.state.blendMode; + } + + set blendMode(value) // eslint-disable-line require-jsdoc + { + this.state.blendMode = value; + } + + /** + * The default vertex shader source + * + * @static + * @constant + */ + static get defaultVertexSrc() + { + return defaultVertex; + } + + /** + * The default fragment shader source + * + * @static + * @constant + */ + static get defaultFragmentSrc() + { + return defaultFragment; + } +} + +/** + * Used for caching shader IDs + * + * @static + * @private + */ +Filter.SOURCE_KEY_MAP = {}; + diff --git a/packages/core/src/filters/FilterSystem.js b/packages/core/src/filters/FilterSystem.js new file mode 100644 index 0000000..3174318 --- /dev/null +++ b/packages/core/src/filters/FilterSystem.js @@ -0,0 +1,431 @@ +import System from '../System'; + +import RenderTexture from '../renderTexture/RenderTexture'; +import Quad from '../utils/Quad'; +import QuadUv from '../utils/QuadUv'; +import { Rectangle } from '@pixi/math'; +import * as filterTransforms from './filterTransforms'; +import bitTwiddle from 'bit-twiddle'; +import UniformGroup from '../shader/UniformGroup'; +import { DRAW_MODES } from '@pixi/constants'; + +// +/** + * @ignore + * @class + */ +class FilterState +{ + /** + * + */ + constructor() + { + this.renderTexture = null; + this.sourceFrame = new Rectangle(); + this.destinationFrame = new Rectangle(); + this.filters = []; + this.target = null; + this.legacy = false; + this.resolution = 1; + } +} + +/** + * @class + * @memberof PIXI.systems + * @extends PIXI.System + */ +export default class FilterSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + /** + * stores a bunch of PO2 textures used for filtering + * @type {Object} + */ + this.texturePool = {}; + + /** + * a pool for storing filter states, save us creating new ones each tick + * @type {Array} + */ + this.statePool = []; + + /** + * A very simple geometry used when drawing a filter effect to the screen + * @type {Quad} + */ + this.quad = new Quad(); + + this.quadUv = new QuadUv(); + + /** + * Temporary rect for maths + * @type {PIXI.Rectangle} + */ + this.tempRect = new Rectangle(); + + this.activeState = {}; + + /** + * this uniform group is attached to filter uniforms when used + * @type {UniformGroup} + */ + this.globalUniforms = new UniformGroup({ + sourceFrame: this.tempRect, + destinationFrame: this.tempRect, + + // legacy variables + filterArea: new Float32Array(4), + filterClamp: new Float32Array(4), + }, true); + } + + /** + * Adds a new filter to the System. + * + * @param {PIXI.DisplayObject} target - The target of the filter to render. + * @param {PIXI.Filter[]} filters - The filters to apply. + */ + push(target, filters) + { + const renderer = this.renderer; + const filterStack = this.renderer.renderTexture.defaultFilterStack; + const state = this.statePool.pop() || new FilterState(); + + let resolution = filters[0].resolution; + let padding = filters[0].padding; + let autoFit = filters[0].autoFit; + let legacy = filters[0].legacy; + + for (let i = 1; i < filters.length; i++) + { + const filter = filters[i]; + + // lets use the lowest resolution.. + resolution = Math.min(resolution, filter.resolution); + // and the largest amount of padding! + padding = Math.max(padding, filter.padding); + // only auto fit if all filters are autofit + autoFit = autoFit || filter.autoFit; + + legacy = legacy || filter.legacy; + } + + filterStack.push(state); + + state.resolution = resolution; + + state.legacy = legacy; + + // round to whole number based on resolution + // TODO move that to the shader too? + state.sourceFrame = target.filterArea || target.getBounds(true); + + state.sourceFrame.pad(padding); + + if (autoFit) + { + state.sourceFrame.fit(this.renderer.renderTexture.destinationFrame); + } + + state.sourceFrame.round(resolution); + + state.renderTexture = this.getPotFilterTexture(state.sourceFrame.width, state.sourceFrame.height, resolution); + state.filters = filters; + + state.destinationFrame.width = state.renderTexture.width; + state.destinationFrame.height = state.renderTexture.height; + + state.renderTexture.filterFrame = state.sourceFrame; + renderer.renderTexture.bind(state.renderTexture, state.sourceFrame);// /, state.destinationFrame); + renderer.renderTexture.clear(); + } + + /** + * Pops off the filter and applies it. + * + */ + pop() + { + const renderer = this.renderer; + const filterStack = renderer.renderTexture.defaultFilterStack; + const state = filterStack.pop(); + const filters = state.filters; + + this.activeState = state; + + const globalUniforms = this.globalUniforms.uniforms; + + globalUniforms.sourceFrame = state.sourceFrame; + globalUniforms.destinationFrame = state.destinationFrame; + globalUniforms.resolution = state.resolution; + + // only update the rect if its legacy.. + if (state.legacy) + { + const filterArea = globalUniforms.filterArea; + const filterClamp = globalUniforms.filterClamp; + + filterArea[0] = state.destinationFrame.width; + filterArea[1] = state.destinationFrame.height; + filterArea[2] = state.sourceFrame.x; + filterArea[3] = state.sourceFrame.y; + + filterClamp[0] = 0.5 / state.resolution / state.destinationFrame.width; + filterClamp[1] = 0.5 / state.resolution / state.destinationFrame.height; + filterClamp[2] = (state.sourceFrame.width - 0.5) / state.resolution / state.destinationFrame.width; + filterClamp[3] = (state.sourceFrame.height - 0.5) / state.resolution / state.destinationFrame.height; + } + + this.globalUniforms.update(); + + const lastState = filterStack[filterStack.length - 1]; + + if (filters.length === 1) + { + filters[0].apply(this, state.renderTexture, lastState.renderTexture, false, state); + + this.returnFilterTexture(state.renderTexture); + } + else + { + let flip = state.renderTexture; + let flop = this.getPotFilterTexture( + flip.width, + flip.height, + state.resolution + ); + + flop.filterFrame = flip.filterFrame; + + let i = 0; + + for (i = 0; i < filters.length - 1; ++i) + { + filters[i].apply(this, flip, flop, true, state); + + const t = flip; + + flip = flop; + flop = t; + } + + filters[i].apply(this, flip, lastState.renderTexture, false, state); + + this.returnFilterTexture(flip); + this.returnFilterTexture(flop); + } + + this.statePool.push(state); + } + + /** + * Draws a filter. + * + * @param {PIXI.Filter} filter - The filter to draw. + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + * @param {boolean} clear - Should the output be cleared before rendering to it + */ + applyFilter(filter, input, output, clear) + { + const renderer = this.renderer; + + renderer.renderTexture.bind(output, output ? output.filterFrame : null); + + if (clear) + { + // gl.disable(gl.SCISSOR_TEST); + renderer.renderTexture.clear(); + // gl.enable(gl.SCISSOR_TEST); + } + + // set the uniforms.. + filter.uniforms.uSampler = input; + filter.uniforms.filterGlobals = this.globalUniforms; + + // TODO make it so that the order of this does not matter.. + // because it does at the moment cos of global uniforms. + // they need to get resynced + + renderer.state.setState(filter.state); + renderer.shader.bind(filter); + + if (filter.legacy) + { + this.quadUv.map(input._frame, input.filterFrame); + + renderer.geometry.bind(this.quadUv); + renderer.geometry.draw(DRAW_MODES.TRIANGLES); + } + else + { + renderer.geometry.bind(this.quad); + renderer.geometry.draw(DRAW_MODES.TRIANGLE_STRIP); + } + } + + /** + * Calculates the mapped matrix. + * + * TODO playing around here.. this is temporary - (will end up in the shader) + * this returns a matrix that will normalise map filter cords in the filter to screen space + * + * @param {PIXI.Matrix} outputMatrix - the matrix to output to. + * @return {PIXI.Matrix} The mapped matrix. + */ + calculateScreenSpaceMatrix(outputMatrix) + { + const currentState = this.activeState; + + return filterTransforms.calculateScreenSpaceMatrix( + outputMatrix, + currentState.sourceFrame, + currentState.destinationFrame + ); + } + + /** + * This will map the filter coord so that a texture can be used based on the transform of a sprite + * + * @param {PIXI.Matrix} outputMatrix - The matrix to output to. + * @param {PIXI.Sprite} sprite - The sprite to map to. + * @return {PIXI.Matrix} The mapped matrix. + */ + calculateSpriteMatrix(outputMatrix, sprite) + { + const currentState = this.activeState; + + return filterTransforms.calculateSpriteMatrix( + outputMatrix, + currentState.sourceFrame, + currentState.destinationFrame, + sprite + ); + } + + /** + * Destroys this Filter System. + * + * @param {boolean} [contextLost=false] context was lost, do not free shaders + * + */ + destroy(contextLost = false) + { + if (!contextLost) + { + this.emptyPool(); + } + else + { + this.texturePool = {}; + } + } + + /** + * Gets a Power-of-Two render texture. + * + * TODO move to a seperate class could be on renderer? + * also - could cause issue with multiple contexts? + * + * @private + * @param {WebGLRenderingContext} gl - The webgl rendering context + * @param {number} minWidth - The minimum width of the render target. + * @param {number} minHeight - The minimum height of the render target. + * @param {number} resolution - The resolution of the render target. + * @return {PIXI.RenderTarget} The new render target. + */ + getPotFilterTexture(minWidth, minHeight, resolution) + { + minWidth = bitTwiddle.nextPow2(minWidth); + minHeight = bitTwiddle.nextPow2(minHeight); + resolution = resolution || 1; + + const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + + if (!this.texturePool[key]) + { + this.texturePool[key] = []; + } + + let renderTexture = this.texturePool[key].pop(); + + if (!renderTexture) + { + // temporary bypass cache.. + // internally - this will cause a texture to be bound.. + renderTexture = RenderTexture.create({ + width: minWidth, + height: minHeight, + resolution, + }); + } + + return renderTexture; + } + + /** + * Gets extra render texture to use inside current filter + * + * @param {number} resolution resolution of the renderTexture + * @returns {PIXI.RenderTexture} + */ + getFilterTexture(resolution) + { + const rt = this.activeState.renderTexture; + + const filterTexture = this.getPotFilterTexture(rt.width, rt.height, resolution || rt.baseTexture.resolution); + + filterTexture.filterFrame = rt.filterFrame; + + return filterTexture; + } + + /** + * Frees a render texture back into the pool. + * + * @param {PIXI.RenderTarget} renderTexture - The renderTarget to free + */ + returnFilterTexture(renderTexture) + { + renderTexture.filterFrame = null; + + const base = renderTexture.baseTexture; + + const minWidth = base.width; + const minHeight = base.height; + + const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + + this.texturePool[key].push(renderTexture); + } + + /** + * Empties the texture pool. + * + */ + emptyPool() + { + for (const i in this.texturePool) + { + const textures = this.texturePool[i]; + + if (textures) + { + for (let j = 0; j < textures.length; j++) + { + textures[j].destroy(true); + } + } + } + + this.texturePool = {}; + } +} diff --git a/packages/core/src/filters/defaultFilter.frag b/packages/core/src/filters/defaultFilter.frag new file mode 100644 index 0000000..a14c1be --- /dev/null +++ b/packages/core/src/filters/defaultFilter.frag @@ -0,0 +1,22 @@ +varying vec2 vTextureCoord; +varying vec2 vFilterCoord; + +uniform sampler2D uSampler; +uniform sampler2D filterSampler; + +void main(void){ + vec4 masky = texture2D(filterSampler, vFilterCoord); + vec4 sample = texture2D(uSampler, vTextureCoord); + vec4 color; + if(mod(vFilterCoord.x, 1.0) > 0.5) + { + color = vec4(1.0, 0.0, 0.0, 1.0); + } + else + { + color = vec4(0.0, 1.0, 0.0, 1.0); + } + // gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0); + gl_FragColor = mix(sample, masky, 0.5); + gl_FragColor *= sample.a; +} diff --git a/packages/core/src/filters/defaultFilter.vert b/packages/core/src/filters/defaultFilter.vert new file mode 100644 index 0000000..e1febca --- /dev/null +++ b/packages/core/src/filters/defaultFilter.vert @@ -0,0 +1,14 @@ +attribute vec2 aVertexPosition; +attribute vec2 aTextureCoord; + +uniform mat3 projectionMatrix; +uniform mat3 filterMatrix; + +varying vec2 vTextureCoord; +varying vec2 vFilterCoord; + +void main(void){ + gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); + vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy; + vTextureCoord = aTextureCoord ; +} diff --git a/packages/core/src/filters/filterTransforms.js b/packages/core/src/filters/filterTransforms.js new file mode 100644 index 0000000..87a3cd6 --- /dev/null +++ b/packages/core/src/filters/filterTransforms.js @@ -0,0 +1,50 @@ +import { Matrix } from '@pixi/math'; + +/** + * Calculates the mapped matrix + * @param filterArea {Rectangle} The filter area + * @param sprite {Sprite} the target sprite + * @param outputMatrix {Matrix} @alvin + * @private + */ +// this returns a matrix that will normalise map filter cords in the filter to screen space +export function calculateScreenSpaceMatrix(outputMatrix, filterArea, textureSize) +{ + // TODO unwrap? + const mappedMatrix = outputMatrix.identity(); + + mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); + + mappedMatrix.scale(textureSize.width, textureSize.height); + + return mappedMatrix; +} + +export function calculateNormalizedScreenSpaceMatrix(outputMatrix, filterArea, textureSize) +{ + const mappedMatrix = outputMatrix.identity(); + + mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); + + const translateScaleX = (textureSize.width / filterArea.width); + const translateScaleY = (textureSize.height / filterArea.height); + + mappedMatrix.scale(translateScaleX, translateScaleY); + + return mappedMatrix; +} + +// this will map the filter coord so that a texture can be used based on the transform of a sprite +export function calculateSpriteMatrix(outputMatrix, filterArea, textureSize, sprite) +{ + const orig = sprite._texture.orig; + const mappedMatrix = outputMatrix.set(textureSize.width, 0, 0, textureSize.height, filterArea.x, filterArea.y); + const worldTransform = sprite.worldTransform.copyTo(Matrix.TEMP_MATRIX); + + worldTransform.invert(); + mappedMatrix.prepend(worldTransform); + mappedMatrix.scale(1.0 / orig.width, 1.0 / orig.height); + mappedMatrix.translate(sprite.anchor.x, sprite.anchor.y); + + return mappedMatrix; +} diff --git a/packages/core/src/filters/spriteMask/SpriteMaskFilter.js b/packages/core/src/filters/spriteMask/SpriteMaskFilter.js new file mode 100644 index 0000000..91697b7 --- /dev/null +++ b/packages/core/src/filters/spriteMask/SpriteMaskFilter.js @@ -0,0 +1,63 @@ +import Filter from '../Filter'; +import { Matrix } from '@pixi/math'; +import vertex from './spriteMaskFilter.vert'; +import fragment from './spriteMaskFilter.frag'; +import { default as TextureMatrix } from '../../textures/TextureMatrix'; + +/** + * The SpriteMaskFilter class + * + * @class + * @extends PIXI.Filter + * @memberof PIXI + */ +export default class SpriteMaskFilter extends Filter +{ + /** + * @param {PIXI.Sprite} sprite - the target sprite + */ + constructor(sprite) + { + const maskMatrix = new Matrix(); + + super(vertex, fragment); + + sprite.renderable = false; + + this.maskSprite = sprite; + this.maskMatrix = maskMatrix; + } + + /** + * Applies the filter + * + * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + */ + apply(filterManager, input, output) + { + const maskSprite = this.maskSprite; + const tex = this.maskSprite.texture; + + if (!tex.valid) + { + return; + } + if (!tex.transform) + { + // margin = 0.0, let it bleed a bit, shader code becomes easier + // assuming that atlas textures were made with 1-pixel padding + tex.transform = new TextureMatrix(tex, 0.0); + } + tex.transform.update(); + + this.uniforms.mask = tex; + this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite) + .prepend(tex.transform.mapCoord); + this.uniforms.alpha = maskSprite.worldAlpha; + this.uniforms.maskClamp = tex.transform.uClampFrame; + + filterManager.applyFilter(this, input, output); + } +} diff --git a/packages/core/src/filters/spriteMask/spriteMaskFilter.frag b/packages/core/src/filters/spriteMask/spriteMaskFilter.frag new file mode 100644 index 0000000..0e4aef8 --- /dev/null +++ b/packages/core/src/filters/spriteMask/spriteMaskFilter.frag @@ -0,0 +1,23 @@ +varying vec2 vMaskCoord; +varying vec2 vTextureCoord; + +uniform sampler2D uSampler; +uniform sampler2D mask; +uniform float alpha; +uniform vec4 maskClamp; + +void main(void) +{ + float clip = step(3.5, + step(maskClamp.x, vMaskCoord.x) + + step(maskClamp.y, vMaskCoord.y) + + step(vMaskCoord.x, maskClamp.z) + + step(vMaskCoord.y, maskClamp.w)); + + vec4 original = texture2D(uSampler, vTextureCoord); + vec4 masky = texture2D(mask, vMaskCoord); + + original *= (masky.r * masky.a * alpha * clip); + + gl_FragColor = original; +} diff --git a/packages/core/src/filters/spriteMask/spriteMaskFilter.vert b/packages/core/src/filters/spriteMask/spriteMaskFilter.vert new file mode 100644 index 0000000..d23a30c --- /dev/null +++ b/packages/core/src/filters/spriteMask/spriteMaskFilter.vert @@ -0,0 +1,16 @@ +attribute vec2 aVertexPosition; +attribute vec2 aTextureCoord; + +uniform mat3 projectionMatrix; +uniform mat3 otherMatrix; + +varying vec2 vMaskCoord; +varying vec2 vTextureCoord; + +void main(void) +{ + gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); + + vTextureCoord = aTextureCoord; + vMaskCoord = ( otherMatrix * vec3( aTextureCoord, 1.0) ).xy; +} diff --git a/packages/core/src/framebuffer/FrameBuffer.js b/packages/core/src/framebuffer/FrameBuffer.js new file mode 100644 index 0000000..84e9fcc --- /dev/null +++ b/packages/core/src/framebuffer/FrameBuffer.js @@ -0,0 +1,106 @@ +import Texture from '../textures/BaseTexture'; +import { FORMATS, TYPES } from '@pixi/constants'; + +/** + * Frame buffer + * @class + * @memberof PIXI + */ +export default class FrameBuffer +{ + constructor(width, height) + { + this.width = width || 100; + this.height = height || 100; + + this.stencil = false; + this.depth = false; + + this.dirtyId = 0; + this.dirtyFormat = 0; + this.dirtySize = 0; + + this.depthTexture = null; + this.colorTextures = []; + + this.glFrameBuffers = {}; + } + + get colorTexture() + { + return this.colorTextures[0]; + } + + addColorTexture(index, texture) + { + // TODO add some validation to the texture - same width / height etc? + this.colorTextures[index || 0] = texture || new Texture(null, { scaleMode: 0, + resolution: 1, + mipmap: false, + width: this.width, + height: this.height });// || new Texture(); + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + addDepthTexture(texture) + { + /* eslint-disable max-len */ + this.depthTexture = texture || new Texture(null, { scaleMode: 0, + resolution: 1, + width: this.width, + height: this.height, + mipmap: false, + format: FORMATS.DEPTH_COMPONENT, + type: TYPES.UNSIGNED_SHORT });// UNSIGNED_SHORT; + /* eslint-disable max-len */ + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + enableDepth() + { + this.depth = true; + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + enableStencil() + { + this.stencil = true; + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + resize(width, height) + { + if (width === this.width && height === this.height) return; + + this.width = width; + this.height = height; + + this.dirtyId++; + this.dirtySize++; + + for (let i = 0; i < this.colorTextures.length; i++) + { + this.colorTextures[i].setSize(width, height); + } + + if (this.depthTexture) + { + this.depthTexture.setSize(width, height); + } + } +} diff --git a/packages/core/src/framebuffer/FramebufferSystem.js b/packages/core/src/framebuffer/FramebufferSystem.js new file mode 100644 index 0000000..2cc2437 --- /dev/null +++ b/packages/core/src/framebuffer/FramebufferSystem.js @@ -0,0 +1,246 @@ +import System from '../System'; +import { Rectangle } from '@pixi/math'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class FramebufferSystem extends System +{ + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + contextChange() + { + this.gl = this.renderer.gl; + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + this.current = null; + this.viewport = new Rectangle(); + + this.drawBufferExtension = this.renderer.context.extensions.drawBuffers; + } + + bind(framebuffer, frame) + { + const gl = this.gl; + + this.current = framebuffer; + + if (framebuffer) + { + // TODO cacheing layer! + + const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID] || this.initFramebuffer(framebuffer); + + gl.bindFramebuffer(gl.FRAMEBUFFER, fbo.framebuffer); + // makesure all textures are unbound.. + + // now check for updates... + if (fbo.dirtyId !== framebuffer.dirtyId) + { + fbo.dirtyId = framebuffer.dirtyId; + + if (fbo.dirtyFormat !== framebuffer.dirtyFormat) + { + fbo.dirtyFormat = framebuffer.dirtyFormat; + this.updateFramebuffer(framebuffer); + } + else if (fbo.dirtySize !== framebuffer.dirtySize) + { + fbo.dirtySize = framebuffer.dirtySize; + this.resizeFramebuffer(framebuffer); + } + } + + for (let i = 0; i < framebuffer.colorTextures.length; i++) + { + if (framebuffer.colorTextures[i].texturePart) + { + this.renderer.texture.unbind(framebuffer.colorTextures[i].texture); + } + else + { + this.renderer.texture.unbind(framebuffer.colorTextures[i]); + } + } + + if (framebuffer.depthTexture) + { + this.renderer.texture.unbind(framebuffer.depthTexture); + } + + if (frame) + { + this.setViewport(frame.x, frame.y, frame.width, frame.height); + } + else + { + this.setViewport(0, 0, framebuffer.width, framebuffer.height); + } + } + else + { + gl.bindFramebuffer(gl.FRAMEBUFFER, null); + + if (frame) + { + this.setViewport(frame.x, frame.y, frame.width, frame.height); + } + else + { + this.setViewport(0, 0, this.renderer.width, this.renderer.height); + } + } + } + + setViewport(x, y, width, height) + { + const v = this.viewport; + + if (v.width !== width || v.height !== height || v.x !== x || v.y !== y) + { + v.x = x; + v.y = y; + v.width = width; + v.height = height; + + this.gl.viewport(x, y, width, height); + } + } + + get size() + { + if (this.current) + { + // TODO store temp + return { x: 0, y: 0, width: this.current.width, height: this.current.height }; + } + + return { x: 0, y: 0, width: this.renderer.width, height: this.renderer.height }; + } + + clear(r, g, b, a) + { + const gl = this.gl; + + // TODO clear color can be set only one right? + gl.clearColor(r, g, b, a); + gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); + } + + // private functions... + + initFramebuffer(framebuffer) + { + const gl = this.gl; + + // TODO - make this a class? + const fbo = { + framebuffer: gl.createFramebuffer(), + stencil: null, + dirtyId: 0, + dirtyFormat: 0, + dirtySize: 0, + }; + + framebuffer.glFrameBuffers[this.CONTEXT_UID] = fbo; + + return fbo; + } + + resizeFramebuffer(framebuffer) + { + const gl = this.gl; + + if (framebuffer.stencil || framebuffer.depth) + { + gl.bindRenderbuffer(gl.RENDERBUFFER, this.stencil); + gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); + } + } + + updateFramebuffer(framebuffer) + { + const gl = this.gl; + + const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID]; + + // bind the color texture + const colorTextures = framebuffer.colorTextures; + + let count = colorTextures.length; + + if (!this.drawBufferExtension) + { + count = Math.min(count, 1); + } + + const activeTextures = []; + + for (let i = 0; i < count; i++) + { + const texture = framebuffer.colorTextures[i]; + + if (texture.texturePart) + { + this.renderer.texture.bind(texture.texture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.COLOR_ATTACHMENT0 + i, + gl.TEXTURE_CUBE_MAP_NEGATIVE_X + texture.side, + texture.texture._glTextures[this.CONTEXT_UID].texture, + 0); + } + else + { + this.renderer.texture.bind(texture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.COLOR_ATTACHMENT0 + i, + gl.TEXTURE_2D, + texture._glTextures[this.CONTEXT_UID].texture, + 0); + } + + activeTextures.push(gl.COLOR_ATTACHMENT0 + i); + } + + if (this.drawBufferExtension && activeTextures.length > 1) + { + this.drawBufferExtension.drawBuffersWEBGL(activeTextures); + } + + if (framebuffer.depthTexture) + { + const depthTextureExt = this.renderer.context.extensions.depthTexture; + + if (depthTextureExt) + { + const depthTexture = framebuffer.depthTexture; + + this.renderer.texture.bind(depthTexture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.DEPTH_ATTACHMENT, + gl.TEXTURE_2D, + depthTexture._glTextures[this.CONTEXT_UID].texture, + 0); + } + } + + if (framebuffer.stencil || framebuffer.depth) + { + fbo.stencil = gl.createRenderbuffer(); + + gl.bindRenderbuffer(gl.RENDERBUFFER, fbo.stencil); + + // TODO.. this is depth AND stencil? + gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, fbo.stencil); + gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); + // fbo.enableStencil(); + } + } +} diff --git a/packages/core/src/geometry/GLBuffer.js b/packages/core/src/geometry/GLBuffer.js new file mode 100644 index 0000000..0477e8a --- /dev/null +++ b/packages/core/src/geometry/GLBuffer.js @@ -0,0 +1,9 @@ +export default class GLBuffer +{ + constructor(buffer) + { + this.buffer = buffer; + this.updateID = -1; + this.byteLength = -1; + } +} diff --git a/packages/core/src/geometry/GeometrySystem.js b/packages/core/src/geometry/GeometrySystem.js new file mode 100644 index 0000000..959f827 --- /dev/null +++ b/packages/core/src/geometry/GeometrySystem.js @@ -0,0 +1,388 @@ +import System from '../System'; +import GLBuffer from './GLBuffer'; + +const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class GeometrySystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this._activeGeometry = null; + this._activeVao = null; + + this.hasVao = true; + this.hasInstance = true; + } + + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + contextChange() + { + const gl = this.gl = this.renderer.gl; + + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + + // webgl2 + if (!gl.createVertexArray) + { + // webgl 1! + let nativeVaoExtension = this.renderer.context.extensions.vertexArrayObject; + + if (this.renderer.legacy) + { + nativeVaoExtension = null; + } + + if (nativeVaoExtension) + { + gl.createVertexArray = () => + nativeVaoExtension.createVertexArrayOES(); + + gl.bindVertexArray = (vao) => + nativeVaoExtension.bindVertexArrayOES(vao); + + gl.deleteVertexArray = (vao) => + nativeVaoExtension.deleteVertexArrayOES(vao); + } + else + { + this.hasVao = false; + gl.createVertexArray = () => + { + // empty + }; + + gl.bindVertexArray = () => + { + // empty + }; + + gl.deleteVertexArray = () => + { + // empty + }; + } + } + + if (!gl.vertexAttribDivisor) + { + const instanceExt = gl.getExtension('ANGLE_instanced_arrays'); + + if (instanceExt) + { + gl.vertexAttribDivisor = (a, b) => + instanceExt.vertexAttribDivisorANGLE(a, b); + + gl.drawElementsInstanced = (a, b, c, d, e) => + instanceExt.drawElementsInstancedANGLE(a, b, c, d, e); + + gl.drawArraysInstanced = (a, b, c, d) => + instanceExt.drawArraysInstancedANGLE(a, b, c, d); + } + else + { + this.hasInstance = false; + } + } + } + + /** + * Binds geometry so that is can be drawn. Creating a Vao if required + * @private + * @param {PIXI.Geometry} geometry instance of geometry to bind + */ + bind(geometry, shader) + { + shader = shader || this.renderer.shader.shader; + + const gl = this.gl; + + // not sure the best way to address this.. + // currently different shaders require different VAOs for the same geometry + // Still mulling over the best way to solve this one.. + // will likely need to modify the shader attribute locations at run time! + let vaos = geometry.glVertexArrayObjects[this.CONTEXT_UID]; + + if (!vaos) + { + geometry.glVertexArrayObjects[this.CONTEXT_UID] = vaos = {}; + } + + const vao = vaos[shader.program.id] || this.initGeometryVao(geometry, shader.program); + + this._activeGeometry = geometry; + + if (this._activeVao !== vao) + { + this._activeVao = vao; + + if (this.hasVao) + { + gl.bindVertexArray(vao); + } + else + { + this.activateVao(geometry, shader.program); + } + } + + // TODO - optimise later! + // don't need to loop through if nothing changed! + // maybe look to add an 'autoupdate' to geometry? + this.updateBuffers(); + } + + reset() + { + this.unbind(); + } + + updateBuffers() + { + const geometry = this._activeGeometry; + const gl = this.gl; + + for (let i = 0; i < geometry.buffers.length; i++) + { + const buffer = geometry.buffers[i]; + + const glBuffer = buffer._glBuffers[this.CONTEXT_UID]; + + if (buffer._updateID !== glBuffer.updateID) + { + glBuffer.updateID = buffer._updateID; + + // TODO can cache this on buffer! maybe added a getter / setter? + const type = buffer.index ? gl.ELEMENT_ARRAY_BUFFER : gl.ARRAY_BUFFER; + const drawType = buffer.static ? gl.STATIC_DRAW : gl.DYNAMIC_DRAW; + + gl.bindBuffer(type, glBuffer.buffer); + + if (glBuffer.byteLength >= buffer.data.byteLength) + { + // offset is always zero for now! + gl.bufferSubData(type, 0, buffer.data); + } + else + { + gl.bufferData(type, buffer.data, drawType); + } + } + } + } + + checkCompatability(geometry, program) + { + // geometry must have at least all the attributes that the shader requires. + const geometryAttributes = geometry.attributes; + const shaderAttributes = program.attributeData; + + for (const j in shaderAttributes) + { + if (!geometryAttributes[j]) + { + throw new Error(`shader and geometry incompatible, geometry missing the "${j}" attribute`); + } + } + } + + /** + * Creates a Vao with the same structure as the geometry and stores it on the geometry. + * @private + * @param {PIXI.Geometry} geometry instance of geometry to to generate Vao for + */ + initGeometryVao(geometry, program) + { + this.checkCompatability(geometry, program); + + const gl = this.gl; + const CONTEXT_UID = this.CONTEXT_UID; + const buffers = geometry.buffers; + const attributes = geometry.attributes; + + const tempStride = {}; + const tempStart = {}; + + for (const j in buffers) + { + tempStride[j] = 0; + tempStart[j] = 0; + } + + for (const j in attributes) + { + if (!attributes[j].size && program.attributeData[j]) + { + attributes[j].size = program.attributeData[j].size; + } + + tempStride[attributes[j].buffer] += attributes[j].size * byteSizeMap[attributes[j].type]; + } + + for (const j in attributes) + { + const attribute = attributes[j]; + const attribSize = attribute.size; + + if (attribute.stride === undefined) + { + if (tempStride[attribute.buffer] === attribSize * byteSizeMap[attribute.type]) + { + attribute.stride = 0; + } + else + { + attribute.stride = tempStride[attribute.buffer]; + } + } + + if (attribute.start === undefined) + { + attribute.start = tempStart[attribute.buffer]; + + tempStart[attribute.buffer] += attribSize * byteSizeMap[attribute.type]; + } + } + + // first update - and create the buffers! + // only create a gl buffer if it actually gets + for (let i = 0; i < buffers.length; i++) + { + const buffer = buffers[i]; + + if (!buffer._glBuffers[CONTEXT_UID]) + { + buffer._glBuffers[CONTEXT_UID] = new GLBuffer(gl.createBuffer()); + } + } + + // TODO - maybe make this a data object? + // lets wait to see if we need to first! + const vao = gl.createVertexArray(); + + gl.bindVertexArray(vao); + + this.activateVao(geometry, program); + + gl.bindVertexArray(null); + + // add it to the cache! + geometry.glVertexArrayObjects[this.CONTEXT_UID][program.id] = vao; + + return vao; + } + + activateVao(geometry, program) + { + const gl = this.gl; + const CONTEXT_UID = this.CONTEXT_UID; + const buffers = geometry.buffers; + const attributes = geometry.attributes; + + if (geometry.indexBuffer) + { + // first update the index buffer if we have one.. + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, geometry.indexBuffer._glBuffers[CONTEXT_UID].buffer); + } + + let lastBuffer = null; + + // add a new one! + for (const j in attributes) + { + const attribute = attributes[j]; + const buffer = buffers[attribute.buffer]; + const glBuffer = buffer._glBuffers[CONTEXT_UID]; + + if (program.attributeData[j]) + { + if (lastBuffer !== glBuffer) + { + gl.bindBuffer(gl.ARRAY_BUFFER, glBuffer.buffer); + + lastBuffer = glBuffer; + } + + const location = program.attributeData[j].location; + + // TODO introduce state again + // we can optimise this for older devices that have no VAOs + gl.enableVertexAttribArray(location); + + gl.vertexAttribPointer(location, + attribute.size, + attribute.type || gl.FLOAT, + attribute.normalized, + attribute.stride, + attribute.start); + + if (attribute.instance) + { + // TODO calculate instance count based of this... + if (this.hasInstance) + { + gl.vertexAttribDivisor(location, 1); + } + else + { + throw new Error('geometry error, GPU Instancing is not supported on this device'); + } + } + } + } + } + + draw(type, size, start, instanceCount) + { + const gl = this.gl; + const geometry = this._activeGeometry; + + // TODO.. this should not change so maybe cache the function? + + if (geometry.indexBuffer) + { + if (geometry.instanced) + { + /* eslint-disable max-len */ + gl.drawElementsInstanced(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2, instanceCount || 1); + /* eslint-enable max-len */ + } + else + { + gl.drawElements(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2); + } + } + else + if (geometry.instanced) + { + // TODO need a better way to calculate size.. + gl.drawArraysInstanced(type, start, size || geometry.getSize(), instanceCount || 1); + } + else + { + gl.drawArrays(type, start, size || geometry.getSize()); + } + + return this; + } + + unbind() + { + this.gl.bindVertexArray(null); + this._activeVao = null; + this._activeGeometry = null; + } +} diff --git a/packages/core/src/geometry/utils/setVertexAttribArrays.js b/packages/core/src/geometry/utils/setVertexAttribArrays.js new file mode 100644 index 0000000..23b6959 --- /dev/null +++ b/packages/core/src/geometry/utils/setVertexAttribArrays.js @@ -0,0 +1,54 @@ +// var GL_MAP = {}; + +/** + * @param gl {WebGLRenderingContext} The current WebGL context + * @param attribs {*} + * @param state {*} + */ +export default function setVertexAttribArrays(gl, attribs, state) +{ + let i; + + if (state) + { + const tempAttribState = state.tempAttribState; + const attribState = state.attribState; + + for (i = 0; i < tempAttribState.length; i++) + { + tempAttribState[i] = false; + } + + // set the new attribs + for (i = 0; i < attribs.length; i++) + { + tempAttribState[attribs[i].attribute.location] = true; + } + + for (i = 0; i < attribState.length; i++) + { + if (attribState[i] !== tempAttribState[i]) + { + attribState[i] = tempAttribState[i]; + + if (state.attribState[i]) + { + gl.enableVertexAttribArray(i); + } + else + { + gl.disableVertexAttribArray(i); + } + } + } + } + else + { + for (i = 0; i < attribs.length; i++) + { + const attrib = attribs[i]; + + gl.enableVertexAttribArray(attrib.attribute.location); + } + } +} diff --git a/packages/core/src/index.js b/packages/core/src/index.js index 58c456a..fd1f547 100644 --- a/packages/core/src/index.js +++ b/packages/core/src/index.js @@ -1,30 +1,30 @@ import * as resources from './textures/resources'; -import * as systems from './renderers/systems'; +import * as systems from './systems'; -export { resources }; export { systems }; +export { resources }; -export { default as Renderer } from './renderers/Renderer'; -export { default as AbstractRenderer } from './renderers/AbstractRenderer'; -export { default as FrameBuffer } from './textures/FrameBuffer'; +export { default as System } from './System'; +export { default as Renderer } from './Renderer'; +export { default as AbstractRenderer } from './AbstractRenderer'; +export { default as FrameBuffer } from './framebuffer/FrameBuffer'; export { default as CubeTexture } from './textures/CubeTexture'; export { default as BaseTexture } from './textures/BaseTexture'; export { default as Texture } from './textures/Texture'; export { default as TextureMatrix } from './textures/TextureMatrix'; -export { default as RenderTexture } from './textures/RenderTexture'; -export { default as BaseRenderTexture } from './textures/BaseRenderTexture'; +export { default as RenderTexture } from './renderTexture/RenderTexture'; +export { default as BaseRenderTexture } from './renderTexture/BaseRenderTexture'; export { default as TextureUvs } from './textures/TextureUvs'; -export { default as State } from './renderers/State'; -export { default as ObjectRenderer } from './renderers/utils/ObjectRenderer'; -export { default as Quad } from './renderers/utils/Quad'; -export { default as QuadUv } from './renderers/utils/QuadUv'; -export { default as checkMaxIfStatmentsInShader } from './renderers/utils/checkMaxIfStatmentsInShader'; +export { default as State } from './state/State'; +export { default as ObjectRenderer } from './batch/ObjectRenderer'; +export { default as Quad } from './utils/Quad'; +export { default as QuadUv } from './utils/QuadUv'; +export { default as checkMaxIfStatmentsInShader } from './shader/utils/checkMaxIfStatmentsInShader'; export { default as Shader } from './shader/Shader'; export { default as Program } from './shader/Program'; export { default as UniformGroup } from './shader/UniformGroup'; -export { default as SpriteMaskFilter } from './renderers/filters/spriteMask/SpriteMaskFilter'; -export { default as Filter } from './renderers/filters/Filter'; +export { default as SpriteMaskFilter } from './filters/spriteMask/SpriteMaskFilter'; +export { default as Filter } from './filters/Filter'; export { default as Attribute } from './geometry/Attribute'; export { default as Buffer } from './geometry/Buffer'; export { default as Geometry } from './geometry/Geometry'; - diff --git a/packages/core/src/AbstractRenderer.js b/packages/core/src/AbstractRenderer.js new file mode 100644 index 0000000..64b2eb1 --- /dev/null +++ b/packages/core/src/AbstractRenderer.js @@ -0,0 +1,353 @@ +import { hex2string, hex2rgb } from '@pixi/utils'; +import { Matrix, Rectangle } from '@pixi/math'; +import { RENDERER_TYPE } from '@pixi/constants'; +import { settings } from '@pixi/settings'; +import { Container } from '@pixi/display'; +import RenderTexture from './renderTexture/RenderTexture'; +import EventEmitter from 'eventemitter3'; + +const tempMatrix = new Matrix(); + +/** + * 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 EventEmitter + * @memberof PIXI + */ +export default class AbstractRenderer extends EventEmitter +{ + // eslint-disable-next-line valid-jsdoc + /** + * @param {string} system - The name of the system this renderer is for. + * @param {object} [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.transparent=false] - If the render view is transparent, default false + * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false + * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. The + * resolution of the renderer retina would be 2. + * @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 {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when rendering, + * stopping pixel interpolation. + */ + constructor(system, options, arg2, arg3) + { + super(); + + // Support for constructor(system, screenWidth, screenHeight, options) + if (typeof options === 'number') + { + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + }, arg3); + } + + // 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 = RENDERER_TYPE.UNKNOWN; + + /** + * Measurements of the screen. (0, 0, screenWidth, screenHeight) + * + * Its safe to use as filterArea or hitArea for 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 1 + */ + this.resolution = options.resolution || settings.RESOLUTION; + + /** + * Whether the render view is transparent + * + * @member {boolean} + */ + this.transparent = options.transparent; + + /** + * Whether css dimensions of canvas view should be resized to screen dimensions automatically + * + * @member {boolean} + */ + this.autoResize = options.autoResize || false; + + /** + * Tracks the blend modes useful for this renderer. + * + * @member {object} + */ + this.blendModes = null; + + /** + * 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; + + /** + * If true PixiJS will Math.floor() x/y values when rendering, stopping pixel interpolation. + * Handy for crisp pixel art and speed on legacy devices. + * + * @member {boolean} + */ + this.roundPixels = options.roundPixels; + + /** + * The background color as a number. + * + * @member {number} + * @private + */ + this._backgroundColor = 0x000000; + + /** + * The background color as an [R, G, B] array. + * + * @member {number[]} + * @private + */ + this._backgroundColorRgba = [0, 0, 0, 0]; + + /** + * The background color as a string. + * + * @member {string} + * @private + */ + this._backgroundColorString = '#000000'; + + this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter + + /** + * This temporary display object used as the parent of the currently being rendered item + * + * @member {PIXI.DisplayObject} + * @private + */ + this._tempDisplayObjectParent = new Container(); + + /** + * The last root object that the renderer tried to render. + * + * @member {PIXI.DisplayObject} + * @private + */ + this._lastObjectRendered = this._tempDisplayObjectParent; + + /** + * Collection of plugins. + * @readonly + * @member {object} + */ + this.plugins = {}; + } + + /** + * Initialize the plugins. + * + * @protected + * @param {object} staticMap - The dictionary of staticly saved plugins. + */ + initPlugins(staticMap) + { + 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() + { + return this.view.width; + } + + /** + * Same as view.height, actual number of pixels in the canvas by vertical + * + * @member {number} + * @readonly + * @default 600 + */ + get height() + { + return this.view.height; + } + + /** + * Resizes the screen and canvas to the specified width and height + * Canvas dimensions are multiplied by resolution + * + * @param {number} screenWidth - the new width of the screen + * @param {number} screenHeight - the new height of the screen + */ + resize(screenWidth, screenHeight) + { + this.screen.width = screenWidth; + this.screen.height = screenHeight; + + this.view.width = screenWidth * this.resolution; + this.view.height = screenHeight * this.resolution; + + if (this.autoResize) + { + this.view.style.width = `${screenWidth}px`; + this.view.style.height = `${screenHeight}px`; + } + } + + /** + * 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 {PIXI.DisplayObject} displayObject - The displayObject the object will be generated from + * @param {number} scaleMode - Should be one of the scaleMode consts + * @param {number} resolution - The resolution / device pixel ratio of the texture being generated + * @param {PIXI.Rectangle} [region] - The region of the displayObject, that shall be rendered, + * if no region is specified, defaults to the local bounds of the displayObject. + * @return {PIXI.Texture} a texture of the graphics object + */ + generateTexture(displayObject, scaleMode, resolution, region) + { + region = region || displayObject.getLocalBounds(); + + // 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(region.width | 0, region.height | 0, scaleMode, resolution); + + tempMatrix.tx = -region.x; + tempMatrix.ty = -region.y; + + this.render(displayObject, renderTexture, false, tempMatrix, true); + + return renderTexture; + } + + /** + * Removes everything from the renderer and optionally removes the Canvas DOM element. + * + * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. + */ + destroy(removeView) + { + 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); + } + + this.plugins = null; + + this.type = RENDERER_TYPE.UNKNOWN; + + this.view = null; + + this.screen = null; + + this.resolution = 0; + + this.transparent = false; + + this.autoResize = false; + + this.blendModes = null; + + this.options = null; + + this.preserveDrawingBuffer = false; + this.clearBeforeRender = false; + + this.roundPixels = false; + + this._backgroundColor = 0; + this._backgroundColorRgba = null; + this._backgroundColorString = null; + + this._tempDisplayObjectParent = null; + this._lastObjectRendered = null; + } + + /** + * The background color to fill if not transparent + * + * @member {number} + */ + get backgroundColor() + { + return this._backgroundColor; + } + + set backgroundColor(value) // eslint-disable-line require-jsdoc + { + this._backgroundColor = value; + this._backgroundColorString = hex2string(value); + hex2rgb(value, this._backgroundColorRgba); + } +} diff --git a/packages/core/src/Renderer.js b/packages/core/src/Renderer.js new file mode 100644 index 0000000..2227e78 --- /dev/null +++ b/packages/core/src/Renderer.js @@ -0,0 +1,338 @@ +import AbstractRenderer from './AbstractRenderer'; +import { sayHello } from '@pixi/utils'; +import MaskSystem from './mask/MaskSystem'; +import StencilSystem from './mask/StencilSystem'; +import FilterSystem from './filters/FilterSystem'; +import FramebufferSystem from './framebuffer/FramebufferSystem'; +import RenderTextureSystem from './renderTexture/RenderTextureSystem'; +import TextureSystem from './textures/TextureSystem'; +import ProjectionSystem from './projection/ProjectionSystem'; +import StateSystem from './state/StateSystem'; +import GeometrySystem from './geometry/GeometrySystem'; +import ShaderSystem from './shader/ShaderSystem'; +import ContextSystem from './context/ContextSystem'; +import BatchSystem from './batch/BatchSystem'; +import TextureGCSystem from './textures/TextureGCSystem'; +import { RENDERER_TYPE } from '@pixi/constants'; +import UniformGroup from './shader/UniformGroup'; +import { Matrix } from '@pixi/math'; +import Runner from 'mini-runner'; + +/** + * 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 Render works by automatically managing webGLBatchs. + * 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 :) + * + * @class + * @memberof PIXI + * @extends PIXI.AbstractRenderer + */ +export default class Renderer extends AbstractRenderer +{ + // eslint-disable-next-line valid-jsdoc + /** + * + * @param {object} [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.transparent=false] - If the render view is transparent, default false + * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false + * @param {boolean} [options.antialias=false] - sets antialias. If not available natively then FXAA + * antialiasing is used + * @param {boolean} [options.forceFXAA=false] - forces FXAA antialiasing to be used over native. + * FXAA is faster, but may not always look as great + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. + * The resolution of the renderer retina would be 2. + * @param {boolean} [options.clearBeforeRender=true] - This sets if the renderer will clear + * the canvas or not before the new render pass. If you wish to set this to false, you *must* set + * preserveDrawingBuffer to `true`. + * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, + * enable this if you need to call toDataUrl on the webgl context. + * @param {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when + * rendering, stopping pixel interpolation. + * @param {number} [options.backgroundColor=0x000000] - The background color of the rendered area + * (shown if not transparent). + * @param {boolean} [options.legacy=false] - If true PixiJS will aim to ensure compatibility + * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. + * @param {string} [options.powerPreference] - Parameter passed to webgl context, set to "high-performance" + * for devices with dual graphics card + */ + constructor(options, arg2, arg3) + { + super('WebGL', options, arg2, arg3); + + /** + * The type of this renderer as a standardised const + * + * @member {number} + * @see PIXI.RENDERER_TYPE + */ + this.type = RENDERER_TYPE.WEBGL; + + // this will be set by the contextSystem (this.context) + this.gl = null; + this.CONTEXT_UID = 0; + this.legacy = !!options.legacy; + + // TODO legacy! + + // runners! + this.runners = { + destroy: new Runner('destroy'), + contextChange: new Runner('contextChange', 1), + reset: new Runner('reset'), + update: new Runner('update'), + postrender: new Runner('postrender'), + prerender: new Runner('prerender'), + resize: new Runner('resize', 2), + }; + + 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(GeometrySystem, 'geometry') + .addSystem(FramebufferSystem, 'framebuffer') + .addSystem(StencilSystem, 'stencil') + .addSystem(ProjectionSystem, 'projection') + .addSystem(TextureGCSystem, 'textureGC') + .addSystem(FilterSystem, 'filter') + .addSystem(RenderTextureSystem, 'renderTexture') + .addSystem(BatchSystem, 'batch'); + + this.initPlugins(Renderer.__plugins); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ + if (options.context) + { + this.context.initFromContext(options.context); + } + else + { + this.context.initFromOptions({ + alpha: this.transparent, + antialias: options.antialias, + premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', + stencil: true, + 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); + } + + /** + * Add a new system to the renderer. + * @param {class} ClassRef - Class reference + * @param {string} [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. + * @return {PIXI.Renderer} Return instance of renderer + */ + addSystem(ClassRef, name) + { + if (!name) + { + name = ClassRef.name; + } + + // TODO - read name from class.name.. + + /* + if(name.includes('System')) + { + name = name.replace('System', ''); + name = name.charAt(0).toLowerCase() + name.slice(1); + } + */ + + const system = new ClassRef(this); + + if (this[name]) + { + throw new Error(`Whoops! ${name} is already a manger`); + } + + this[name] = system; + + for (const i in this.runners) + { + this.runners[i].add(system); + } + + return this; + + /** + * 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. + */ + } + + /** + * Renders the object to its webGL view + * + * @param {PIXI.DisplayObject} displayObject - the object to be rendered + * @param {PIXI.RenderTexture} renderTexture - The render texture to render to. + * @param {boolean} [clear] - Should the canvas be cleared before the new render + * @param {PIXI.Transform} [transform] - A transform to apply to the render texture before rendering. + * @param {boolean} [skipUpdateTransform] - Should we skip the update transform pass? + */ + render(displayObject, renderTexture, clear, transform, skipUpdateTransform) + { + // can be handy to know! + this.renderingToScreen = !renderTexture; + + this.runners.prerender.run(); + this.emit('prerender'); + + // 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.parent; + + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = 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.run(); + + this.emit('postrender'); + } + + /** + * Resizes the webGL view to the specified width and height. + * + * @param {number} screenWidth - the new width of the screen + * @param {number} screenHeight - the new height of the screen + */ + resize(screenWidth, screenHeight) + { + AbstractRenderer.prototype.resize.call(this, screenWidth, screenHeight); + + this.runners.resize.run(screenWidth, screenHeight); + } + + /** + * Resets the WebGL state so you can render things however you fancy! + * + * @return {PIXI.Renderer} Returns itself. + */ + reset() + { + this.runners.reset.run(); + + return this; + } + + clear() + { + this.framebuffer.bind(); + this.framebuffer.clear(); + } + + /** + * Removes everything from the renderer (event listeners, spritebatch, etc...) + * + * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. + * See: https://github.com/pixijs/pixi.js/issues/2233 + */ + destroy(removeView) + { + this.runners.destroy.run(); + + // call base destroy + super.destroy(removeView); + + // TODO nullify all the managers.. + this.gl = null; + } + + /** + * 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 + * builds and excluding plugins. + * @name PIXI.Renderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. + */ + + /** + * Adds a plugin to the renderer. + * + * @method + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + static registerPlugin(pluginName, ctor) + { + Renderer.__plugins = Renderer.__plugins || {}; + Renderer.__plugins[pluginName] = ctor; + } +} diff --git a/packages/core/src/System.js b/packages/core/src/System.js new file mode 100644 index 0000000..05a10c8 --- /dev/null +++ b/packages/core/src/System.js @@ -0,0 +1,42 @@ +/** + * System is a base class used for extending systems used by the {@link PIXI.Renderer} + * @see PIXI.Renderer#addSystem + * @class + * @memberof PIXI + */ +export default class System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + /** + * The renderer this manager works for. + * + * @member {PIXI.Renderer} + */ + this.renderer = renderer; + + this.renderer.runners.contextChange.add(this); + } + + /** + * Generic method called when there is a WebGL context change. + * + */ + contextChange() + { + // do some codes init! + } + + /** + * Generic destroy methods to be overridden by the subclass + * + */ + destroy() + { + this.renderer.runners.contextChange.remove(this); + this.renderer = null; + } +} diff --git a/packages/core/src/batch/BatchSystem.js b/packages/core/src/batch/BatchSystem.js new file mode 100644 index 0000000..b04c249 --- /dev/null +++ b/packages/core/src/batch/BatchSystem.js @@ -0,0 +1,68 @@ +import System from '../System'; +import ObjectRenderer from './ObjectRenderer'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class BatchSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + super(renderer); + + /** + * An empty renderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.emptyRenderer = new ObjectRenderer(renderer); + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.currentRenderer = this.emptyRenderer; + } + + /** + * Changes the current renderer to the one given in parameter + * + * @param {PIXI.ObjectRenderer} objectRenderer - The object renderer to use. + */ + setObjectRenderer(objectRenderer) + { + if (this.currentRenderer === objectRenderer) + { + return; + } + + this.currentRenderer.stop(); + this.currentRenderer = objectRenderer; + + this.renderer.state.setState(objectRenderer.state); + + this.currentRenderer.start(); + } + + /** + * This should be called if you wish to do some custom rendering + * It will basically render anything that may be batched up such as sprites + * + */ + flush() + { + this.setObjectRenderer(this.emptyRenderer); + } + + reset() + { + this.setObjectRenderer(this.emptyRenderer); + } +} diff --git a/packages/core/src/batch/ObjectRenderer.js b/packages/core/src/batch/ObjectRenderer.js new file mode 100644 index 0000000..0c851a9 --- /dev/null +++ b/packages/core/src/batch/ObjectRenderer.js @@ -0,0 +1,48 @@ +import System from '../System'; + +/** + * Base for a common object renderer that can be used as a system renderer plugin. + * + * @class + * @extends PIXI.System + * @memberof PIXI + */ +export default class ObjectRenderer extends System +{ + /** + * Starts the renderer and sets the shader + * + */ + start() + { + // set the shader.. + } + + /** + * Stops the renderer + * + */ + stop() + { + this.flush(); + } + + /** + * Stub method for rendering content and emptying the current batch. + * + */ + flush() + { + // flush! + } + + /** + * Renders an object + * + * @param {PIXI.DisplayObject} object - The object to render. + */ + render(object) // eslint-disable-line no-unused-vars + { + // render the object + } +} diff --git a/packages/core/src/context/ContextSystem.js b/packages/core/src/context/ContextSystem.js new file mode 100644 index 0000000..301aab4 --- /dev/null +++ b/packages/core/src/context/ContextSystem.js @@ -0,0 +1,186 @@ +import System from '../System'; +import { settings } from '@pixi/settings'; + +let CONTEXT_UID = 0; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class ContextSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.webGLVersion = 1; + + this.handleContextLost = this.handleContextLost.bind(this); + this.handleContextRestored = this.handleContextRestored.bind(this); + + this.extensions = {}; + + renderer.view.addEventListener('webglcontextlost', this.handleContextLost, false); + renderer.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + } + + get isLost() + { + return (!this.gl || this.gl.isContextLost()); + } + + contextChange(gl) + { + this.gl = gl; + + // restore a context if it was previously lost + if (gl.isContextLost() && gl.getExtension('WEBGL_lose_context')) + { + gl.getExtension('WEBGL_lose_context').restoreContext(); + } + } + + initFromContext(gl) + { + this.gl = gl; + this.validateContext(gl); + this.renderer.gl = gl; + this.renderer.CONTEXT_UID = CONTEXT_UID++; + this.renderer.runners.contextChange.run(gl); + } + + initFromOptions(options) + { + const gl = this.createContext(this.renderer.view, options); + + this.initFromContext(gl); + } + + /** + * Helper class to create a webGL Context + * + * @class + * @memberof PIXI.glCore + * @param canvas {HTMLCanvasElement} the canvas element that we will get the context from + * @param options {Object} An options object that gets passed in to the canvas element containing the context attributes, + * see https://developer.mozilla.org/en/docs/Web/API/HTMLCanvasElement/getContext for the options available + * @return {WebGLRenderingContext} the WebGL context + */ + createContext(canvas, options) + { + let gl; + + if (settings.PREFER_WEBGL_2) + { + gl = canvas.getContext('webgl2', options); + } + + if (gl) + { + this.webGLVersion = 2; + } + else + { + this.webGLVersion = 1; + + gl = canvas.getContext('webgl', options) + || canvas.getContext('experimental-webgl', options); + + if (!gl) + { + // fail, not able to get a context + throw new Error('This browser does not support webGL. Try using the canvas renderer'); + } + } + + this.gl = gl; + + this.getExtensions(); + + return gl; + } + + getExtensions() + { + // time to set up default etensions that pixi uses.. + const gl = this.gl; + const extensions = this.extensions; + + if (this.webGLVersion === 1) + { + extensions.drawBuffers = gl.getExtension('WEBGL_draw_buffers'); + extensions.depthTexture = gl.getExtension('WEBKIT_WEBGL_depth_texture'); + extensions.floatTexture = gl.getExtension('OES_texture_float'); + extensions.loseContext = gl.getExtension('WEBGL_lose_context'); + + extensions.vertexArrayObject = gl.getExtension('OES_vertex_array_object') + || gl.getExtension('MOZ_OES_vertex_array_object') + || gl.getExtension('WEBKIT_OES_vertex_array_object'); + } + + // we don't use any specific WebGL 2 ones yet! + } + + /** + * Handles a lost webgl context + * + * @private + * @param {WebGLContextEvent} event - The context lost event. + */ + handleContextLost(event) + { + event.preventDefault(); + } + + /** + * Handles a restored webgl context + * + * @private + */ + handleContextRestored() + { + this.renderer.runners.contextChange.run(this.gl); + } + + destroy() + { + const view = this.renderer.view; + + // remove listeners + view.removeEventListener('webglcontextlost', this.handleContextLost); + view.removeEventListener('webglcontextrestored', this.handleContextRestored); + + this.gl.useProgram(null); + + if (this.extensions.loseContext) + { + this.extensions.loseContext.loseContext(); + } + } + + postrender() + { + this.gl.flush(); + } + + validateContext(gl) + { + const attributes = gl.getContextAttributes(); + + // this is going to be fairly simple for now.. but at least we have room to grow! + if (!attributes.stencil) + { + /* eslint-disable max-len */ + + /* eslint-disable no-console */ + console.warn('Provided WebGL context does not have a stencil buffer, masks may not render correctly'); + /* eslint-enable no-console */ + + /* eslint-enable max-len */ + } + } +} diff --git a/packages/core/src/filters/Filter.js b/packages/core/src/filters/Filter.js new file mode 100644 index 0000000..9d8cad8 --- /dev/null +++ b/packages/core/src/filters/Filter.js @@ -0,0 +1,140 @@ +import Shader from '../shader/Shader'; +import Program from '../shader/Program'; +import State from '../state/State'; +import { settings } from '@pixi/settings'; +// import extractUniformsFromSrc from './extractUniformsFromSrc'; +import defaultVertex from './defaultFilter.vert'; +import defaultFragment from './defaultFilter.frag'; + +// let math = require('../../math'); +/** + * @class + * @memberof PIXI + * @extends PIXI.Shader + */ +export default class Filter extends Shader +{ + /** + * @param {string} [vertexSrc] - The source of the vertex shader. + * @param {string} [fragmentSrc] - The source of the fragment shader. + * @param {object} [uniforms] - Custom uniforms to use to augment the built-in ones. + */ + constructor(vertexSrc, fragmentSrc, uniforms) + { + const program = Program.from(vertexSrc, fragmentSrc); + + super(program, uniforms); + + /** + * The padding of the filter. Some filters require extra space to breath such as a blur. + * Increasing this will add extra width and height to the bounds of the object that the + * filter is applied to. + * + * @member {number} + */ + this.padding = 0; + + /** + * The resolution of the filter. Setting this to be lower will lower the quality but + * increase the performance of the filter. + * + * @member {number} + */ + this.resolution = settings.RESOLUTION; + + /** + * If enabled is true the filter is applied, if false it will not. + * + * @member {boolean} + */ + this.enabled = true; + + /** + * If enabled, PixiJS will fit the filter area into boundaries for better performance. + * Switch it off if it does not work for specific shader. + * + * @member {boolean} + */ + this.autoFit = true; + + /** + * Legacy filters use position and uvs from attributes + * @member {boolean} + * @readonly + */ + this.legacy = !!this.program.attributeData.aTextureCoord; + + /** + * the webGL state the filter requires to render + * @member {PIXI.State} + */ + this.state = new State(); + } + + /** + * Applies the filter + * + * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + * @param {boolean} clear - Should the output be cleared before rendering to it + * @param {object} [currentState] - It's current state of filter. + * There are some useful properties in the currentState : + * target, filters, sourceFrame, destinationFrame, renderTarget, resolution + */ + apply(filterManager, input, output, clear, currentState, derp) // eslint-disable-line no-unused-vars + { + // do as you please! + + filterManager.applyFilter(this, input, output, clear, currentState, derp); + + // or just do a regular render.. + } + + /** + * Sets the blendmode of the filter + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL + */ + get blendMode() + { + return this.state.blendMode; + } + + set blendMode(value) // eslint-disable-line require-jsdoc + { + this.state.blendMode = value; + } + + /** + * The default vertex shader source + * + * @static + * @constant + */ + static get defaultVertexSrc() + { + return defaultVertex; + } + + /** + * The default fragment shader source + * + * @static + * @constant + */ + static get defaultFragmentSrc() + { + return defaultFragment; + } +} + +/** + * Used for caching shader IDs + * + * @static + * @private + */ +Filter.SOURCE_KEY_MAP = {}; + diff --git a/packages/core/src/filters/FilterSystem.js b/packages/core/src/filters/FilterSystem.js new file mode 100644 index 0000000..3174318 --- /dev/null +++ b/packages/core/src/filters/FilterSystem.js @@ -0,0 +1,431 @@ +import System from '../System'; + +import RenderTexture from '../renderTexture/RenderTexture'; +import Quad from '../utils/Quad'; +import QuadUv from '../utils/QuadUv'; +import { Rectangle } from '@pixi/math'; +import * as filterTransforms from './filterTransforms'; +import bitTwiddle from 'bit-twiddle'; +import UniformGroup from '../shader/UniformGroup'; +import { DRAW_MODES } from '@pixi/constants'; + +// +/** + * @ignore + * @class + */ +class FilterState +{ + /** + * + */ + constructor() + { + this.renderTexture = null; + this.sourceFrame = new Rectangle(); + this.destinationFrame = new Rectangle(); + this.filters = []; + this.target = null; + this.legacy = false; + this.resolution = 1; + } +} + +/** + * @class + * @memberof PIXI.systems + * @extends PIXI.System + */ +export default class FilterSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + /** + * stores a bunch of PO2 textures used for filtering + * @type {Object} + */ + this.texturePool = {}; + + /** + * a pool for storing filter states, save us creating new ones each tick + * @type {Array} + */ + this.statePool = []; + + /** + * A very simple geometry used when drawing a filter effect to the screen + * @type {Quad} + */ + this.quad = new Quad(); + + this.quadUv = new QuadUv(); + + /** + * Temporary rect for maths + * @type {PIXI.Rectangle} + */ + this.tempRect = new Rectangle(); + + this.activeState = {}; + + /** + * this uniform group is attached to filter uniforms when used + * @type {UniformGroup} + */ + this.globalUniforms = new UniformGroup({ + sourceFrame: this.tempRect, + destinationFrame: this.tempRect, + + // legacy variables + filterArea: new Float32Array(4), + filterClamp: new Float32Array(4), + }, true); + } + + /** + * Adds a new filter to the System. + * + * @param {PIXI.DisplayObject} target - The target of the filter to render. + * @param {PIXI.Filter[]} filters - The filters to apply. + */ + push(target, filters) + { + const renderer = this.renderer; + const filterStack = this.renderer.renderTexture.defaultFilterStack; + const state = this.statePool.pop() || new FilterState(); + + let resolution = filters[0].resolution; + let padding = filters[0].padding; + let autoFit = filters[0].autoFit; + let legacy = filters[0].legacy; + + for (let i = 1; i < filters.length; i++) + { + const filter = filters[i]; + + // lets use the lowest resolution.. + resolution = Math.min(resolution, filter.resolution); + // and the largest amount of padding! + padding = Math.max(padding, filter.padding); + // only auto fit if all filters are autofit + autoFit = autoFit || filter.autoFit; + + legacy = legacy || filter.legacy; + } + + filterStack.push(state); + + state.resolution = resolution; + + state.legacy = legacy; + + // round to whole number based on resolution + // TODO move that to the shader too? + state.sourceFrame = target.filterArea || target.getBounds(true); + + state.sourceFrame.pad(padding); + + if (autoFit) + { + state.sourceFrame.fit(this.renderer.renderTexture.destinationFrame); + } + + state.sourceFrame.round(resolution); + + state.renderTexture = this.getPotFilterTexture(state.sourceFrame.width, state.sourceFrame.height, resolution); + state.filters = filters; + + state.destinationFrame.width = state.renderTexture.width; + state.destinationFrame.height = state.renderTexture.height; + + state.renderTexture.filterFrame = state.sourceFrame; + renderer.renderTexture.bind(state.renderTexture, state.sourceFrame);// /, state.destinationFrame); + renderer.renderTexture.clear(); + } + + /** + * Pops off the filter and applies it. + * + */ + pop() + { + const renderer = this.renderer; + const filterStack = renderer.renderTexture.defaultFilterStack; + const state = filterStack.pop(); + const filters = state.filters; + + this.activeState = state; + + const globalUniforms = this.globalUniforms.uniforms; + + globalUniforms.sourceFrame = state.sourceFrame; + globalUniforms.destinationFrame = state.destinationFrame; + globalUniforms.resolution = state.resolution; + + // only update the rect if its legacy.. + if (state.legacy) + { + const filterArea = globalUniforms.filterArea; + const filterClamp = globalUniforms.filterClamp; + + filterArea[0] = state.destinationFrame.width; + filterArea[1] = state.destinationFrame.height; + filterArea[2] = state.sourceFrame.x; + filterArea[3] = state.sourceFrame.y; + + filterClamp[0] = 0.5 / state.resolution / state.destinationFrame.width; + filterClamp[1] = 0.5 / state.resolution / state.destinationFrame.height; + filterClamp[2] = (state.sourceFrame.width - 0.5) / state.resolution / state.destinationFrame.width; + filterClamp[3] = (state.sourceFrame.height - 0.5) / state.resolution / state.destinationFrame.height; + } + + this.globalUniforms.update(); + + const lastState = filterStack[filterStack.length - 1]; + + if (filters.length === 1) + { + filters[0].apply(this, state.renderTexture, lastState.renderTexture, false, state); + + this.returnFilterTexture(state.renderTexture); + } + else + { + let flip = state.renderTexture; + let flop = this.getPotFilterTexture( + flip.width, + flip.height, + state.resolution + ); + + flop.filterFrame = flip.filterFrame; + + let i = 0; + + for (i = 0; i < filters.length - 1; ++i) + { + filters[i].apply(this, flip, flop, true, state); + + const t = flip; + + flip = flop; + flop = t; + } + + filters[i].apply(this, flip, lastState.renderTexture, false, state); + + this.returnFilterTexture(flip); + this.returnFilterTexture(flop); + } + + this.statePool.push(state); + } + + /** + * Draws a filter. + * + * @param {PIXI.Filter} filter - The filter to draw. + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + * @param {boolean} clear - Should the output be cleared before rendering to it + */ + applyFilter(filter, input, output, clear) + { + const renderer = this.renderer; + + renderer.renderTexture.bind(output, output ? output.filterFrame : null); + + if (clear) + { + // gl.disable(gl.SCISSOR_TEST); + renderer.renderTexture.clear(); + // gl.enable(gl.SCISSOR_TEST); + } + + // set the uniforms.. + filter.uniforms.uSampler = input; + filter.uniforms.filterGlobals = this.globalUniforms; + + // TODO make it so that the order of this does not matter.. + // because it does at the moment cos of global uniforms. + // they need to get resynced + + renderer.state.setState(filter.state); + renderer.shader.bind(filter); + + if (filter.legacy) + { + this.quadUv.map(input._frame, input.filterFrame); + + renderer.geometry.bind(this.quadUv); + renderer.geometry.draw(DRAW_MODES.TRIANGLES); + } + else + { + renderer.geometry.bind(this.quad); + renderer.geometry.draw(DRAW_MODES.TRIANGLE_STRIP); + } + } + + /** + * Calculates the mapped matrix. + * + * TODO playing around here.. this is temporary - (will end up in the shader) + * this returns a matrix that will normalise map filter cords in the filter to screen space + * + * @param {PIXI.Matrix} outputMatrix - the matrix to output to. + * @return {PIXI.Matrix} The mapped matrix. + */ + calculateScreenSpaceMatrix(outputMatrix) + { + const currentState = this.activeState; + + return filterTransforms.calculateScreenSpaceMatrix( + outputMatrix, + currentState.sourceFrame, + currentState.destinationFrame + ); + } + + /** + * This will map the filter coord so that a texture can be used based on the transform of a sprite + * + * @param {PIXI.Matrix} outputMatrix - The matrix to output to. + * @param {PIXI.Sprite} sprite - The sprite to map to. + * @return {PIXI.Matrix} The mapped matrix. + */ + calculateSpriteMatrix(outputMatrix, sprite) + { + const currentState = this.activeState; + + return filterTransforms.calculateSpriteMatrix( + outputMatrix, + currentState.sourceFrame, + currentState.destinationFrame, + sprite + ); + } + + /** + * Destroys this Filter System. + * + * @param {boolean} [contextLost=false] context was lost, do not free shaders + * + */ + destroy(contextLost = false) + { + if (!contextLost) + { + this.emptyPool(); + } + else + { + this.texturePool = {}; + } + } + + /** + * Gets a Power-of-Two render texture. + * + * TODO move to a seperate class could be on renderer? + * also - could cause issue with multiple contexts? + * + * @private + * @param {WebGLRenderingContext} gl - The webgl rendering context + * @param {number} minWidth - The minimum width of the render target. + * @param {number} minHeight - The minimum height of the render target. + * @param {number} resolution - The resolution of the render target. + * @return {PIXI.RenderTarget} The new render target. + */ + getPotFilterTexture(minWidth, minHeight, resolution) + { + minWidth = bitTwiddle.nextPow2(minWidth); + minHeight = bitTwiddle.nextPow2(minHeight); + resolution = resolution || 1; + + const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + + if (!this.texturePool[key]) + { + this.texturePool[key] = []; + } + + let renderTexture = this.texturePool[key].pop(); + + if (!renderTexture) + { + // temporary bypass cache.. + // internally - this will cause a texture to be bound.. + renderTexture = RenderTexture.create({ + width: minWidth, + height: minHeight, + resolution, + }); + } + + return renderTexture; + } + + /** + * Gets extra render texture to use inside current filter + * + * @param {number} resolution resolution of the renderTexture + * @returns {PIXI.RenderTexture} + */ + getFilterTexture(resolution) + { + const rt = this.activeState.renderTexture; + + const filterTexture = this.getPotFilterTexture(rt.width, rt.height, resolution || rt.baseTexture.resolution); + + filterTexture.filterFrame = rt.filterFrame; + + return filterTexture; + } + + /** + * Frees a render texture back into the pool. + * + * @param {PIXI.RenderTarget} renderTexture - The renderTarget to free + */ + returnFilterTexture(renderTexture) + { + renderTexture.filterFrame = null; + + const base = renderTexture.baseTexture; + + const minWidth = base.width; + const minHeight = base.height; + + const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + + this.texturePool[key].push(renderTexture); + } + + /** + * Empties the texture pool. + * + */ + emptyPool() + { + for (const i in this.texturePool) + { + const textures = this.texturePool[i]; + + if (textures) + { + for (let j = 0; j < textures.length; j++) + { + textures[j].destroy(true); + } + } + } + + this.texturePool = {}; + } +} diff --git a/packages/core/src/filters/defaultFilter.frag b/packages/core/src/filters/defaultFilter.frag new file mode 100644 index 0000000..a14c1be --- /dev/null +++ b/packages/core/src/filters/defaultFilter.frag @@ -0,0 +1,22 @@ +varying vec2 vTextureCoord; +varying vec2 vFilterCoord; + +uniform sampler2D uSampler; +uniform sampler2D filterSampler; + +void main(void){ + vec4 masky = texture2D(filterSampler, vFilterCoord); + vec4 sample = texture2D(uSampler, vTextureCoord); + vec4 color; + if(mod(vFilterCoord.x, 1.0) > 0.5) + { + color = vec4(1.0, 0.0, 0.0, 1.0); + } + else + { + color = vec4(0.0, 1.0, 0.0, 1.0); + } + // gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0); + gl_FragColor = mix(sample, masky, 0.5); + gl_FragColor *= sample.a; +} diff --git a/packages/core/src/filters/defaultFilter.vert b/packages/core/src/filters/defaultFilter.vert new file mode 100644 index 0000000..e1febca --- /dev/null +++ b/packages/core/src/filters/defaultFilter.vert @@ -0,0 +1,14 @@ +attribute vec2 aVertexPosition; +attribute vec2 aTextureCoord; + +uniform mat3 projectionMatrix; +uniform mat3 filterMatrix; + +varying vec2 vTextureCoord; +varying vec2 vFilterCoord; + +void main(void){ + gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); + vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy; + vTextureCoord = aTextureCoord ; +} diff --git a/packages/core/src/filters/filterTransforms.js b/packages/core/src/filters/filterTransforms.js new file mode 100644 index 0000000..87a3cd6 --- /dev/null +++ b/packages/core/src/filters/filterTransforms.js @@ -0,0 +1,50 @@ +import { Matrix } from '@pixi/math'; + +/** + * Calculates the mapped matrix + * @param filterArea {Rectangle} The filter area + * @param sprite {Sprite} the target sprite + * @param outputMatrix {Matrix} @alvin + * @private + */ +// this returns a matrix that will normalise map filter cords in the filter to screen space +export function calculateScreenSpaceMatrix(outputMatrix, filterArea, textureSize) +{ + // TODO unwrap? + const mappedMatrix = outputMatrix.identity(); + + mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); + + mappedMatrix.scale(textureSize.width, textureSize.height); + + return mappedMatrix; +} + +export function calculateNormalizedScreenSpaceMatrix(outputMatrix, filterArea, textureSize) +{ + const mappedMatrix = outputMatrix.identity(); + + mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); + + const translateScaleX = (textureSize.width / filterArea.width); + const translateScaleY = (textureSize.height / filterArea.height); + + mappedMatrix.scale(translateScaleX, translateScaleY); + + return mappedMatrix; +} + +// this will map the filter coord so that a texture can be used based on the transform of a sprite +export function calculateSpriteMatrix(outputMatrix, filterArea, textureSize, sprite) +{ + const orig = sprite._texture.orig; + const mappedMatrix = outputMatrix.set(textureSize.width, 0, 0, textureSize.height, filterArea.x, filterArea.y); + const worldTransform = sprite.worldTransform.copyTo(Matrix.TEMP_MATRIX); + + worldTransform.invert(); + mappedMatrix.prepend(worldTransform); + mappedMatrix.scale(1.0 / orig.width, 1.0 / orig.height); + mappedMatrix.translate(sprite.anchor.x, sprite.anchor.y); + + return mappedMatrix; +} diff --git a/packages/core/src/filters/spriteMask/SpriteMaskFilter.js b/packages/core/src/filters/spriteMask/SpriteMaskFilter.js new file mode 100644 index 0000000..91697b7 --- /dev/null +++ b/packages/core/src/filters/spriteMask/SpriteMaskFilter.js @@ -0,0 +1,63 @@ +import Filter from '../Filter'; +import { Matrix } from '@pixi/math'; +import vertex from './spriteMaskFilter.vert'; +import fragment from './spriteMaskFilter.frag'; +import { default as TextureMatrix } from '../../textures/TextureMatrix'; + +/** + * The SpriteMaskFilter class + * + * @class + * @extends PIXI.Filter + * @memberof PIXI + */ +export default class SpriteMaskFilter extends Filter +{ + /** + * @param {PIXI.Sprite} sprite - the target sprite + */ + constructor(sprite) + { + const maskMatrix = new Matrix(); + + super(vertex, fragment); + + sprite.renderable = false; + + this.maskSprite = sprite; + this.maskMatrix = maskMatrix; + } + + /** + * Applies the filter + * + * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + */ + apply(filterManager, input, output) + { + const maskSprite = this.maskSprite; + const tex = this.maskSprite.texture; + + if (!tex.valid) + { + return; + } + if (!tex.transform) + { + // margin = 0.0, let it bleed a bit, shader code becomes easier + // assuming that atlas textures were made with 1-pixel padding + tex.transform = new TextureMatrix(tex, 0.0); + } + tex.transform.update(); + + this.uniforms.mask = tex; + this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite) + .prepend(tex.transform.mapCoord); + this.uniforms.alpha = maskSprite.worldAlpha; + this.uniforms.maskClamp = tex.transform.uClampFrame; + + filterManager.applyFilter(this, input, output); + } +} diff --git a/packages/core/src/filters/spriteMask/spriteMaskFilter.frag b/packages/core/src/filters/spriteMask/spriteMaskFilter.frag new file mode 100644 index 0000000..0e4aef8 --- /dev/null +++ b/packages/core/src/filters/spriteMask/spriteMaskFilter.frag @@ -0,0 +1,23 @@ +varying vec2 vMaskCoord; +varying vec2 vTextureCoord; + +uniform sampler2D uSampler; +uniform sampler2D mask; +uniform float alpha; +uniform vec4 maskClamp; + +void main(void) +{ + float clip = step(3.5, + step(maskClamp.x, vMaskCoord.x) + + step(maskClamp.y, vMaskCoord.y) + + step(vMaskCoord.x, maskClamp.z) + + step(vMaskCoord.y, maskClamp.w)); + + vec4 original = texture2D(uSampler, vTextureCoord); + vec4 masky = texture2D(mask, vMaskCoord); + + original *= (masky.r * masky.a * alpha * clip); + + gl_FragColor = original; +} diff --git a/packages/core/src/filters/spriteMask/spriteMaskFilter.vert b/packages/core/src/filters/spriteMask/spriteMaskFilter.vert new file mode 100644 index 0000000..d23a30c --- /dev/null +++ b/packages/core/src/filters/spriteMask/spriteMaskFilter.vert @@ -0,0 +1,16 @@ +attribute vec2 aVertexPosition; +attribute vec2 aTextureCoord; + +uniform mat3 projectionMatrix; +uniform mat3 otherMatrix; + +varying vec2 vMaskCoord; +varying vec2 vTextureCoord; + +void main(void) +{ + gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); + + vTextureCoord = aTextureCoord; + vMaskCoord = ( otherMatrix * vec3( aTextureCoord, 1.0) ).xy; +} diff --git a/packages/core/src/framebuffer/FrameBuffer.js b/packages/core/src/framebuffer/FrameBuffer.js new file mode 100644 index 0000000..84e9fcc --- /dev/null +++ b/packages/core/src/framebuffer/FrameBuffer.js @@ -0,0 +1,106 @@ +import Texture from '../textures/BaseTexture'; +import { FORMATS, TYPES } from '@pixi/constants'; + +/** + * Frame buffer + * @class + * @memberof PIXI + */ +export default class FrameBuffer +{ + constructor(width, height) + { + this.width = width || 100; + this.height = height || 100; + + this.stencil = false; + this.depth = false; + + this.dirtyId = 0; + this.dirtyFormat = 0; + this.dirtySize = 0; + + this.depthTexture = null; + this.colorTextures = []; + + this.glFrameBuffers = {}; + } + + get colorTexture() + { + return this.colorTextures[0]; + } + + addColorTexture(index, texture) + { + // TODO add some validation to the texture - same width / height etc? + this.colorTextures[index || 0] = texture || new Texture(null, { scaleMode: 0, + resolution: 1, + mipmap: false, + width: this.width, + height: this.height });// || new Texture(); + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + addDepthTexture(texture) + { + /* eslint-disable max-len */ + this.depthTexture = texture || new Texture(null, { scaleMode: 0, + resolution: 1, + width: this.width, + height: this.height, + mipmap: false, + format: FORMATS.DEPTH_COMPONENT, + type: TYPES.UNSIGNED_SHORT });// UNSIGNED_SHORT; + /* eslint-disable max-len */ + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + enableDepth() + { + this.depth = true; + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + enableStencil() + { + this.stencil = true; + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + resize(width, height) + { + if (width === this.width && height === this.height) return; + + this.width = width; + this.height = height; + + this.dirtyId++; + this.dirtySize++; + + for (let i = 0; i < this.colorTextures.length; i++) + { + this.colorTextures[i].setSize(width, height); + } + + if (this.depthTexture) + { + this.depthTexture.setSize(width, height); + } + } +} diff --git a/packages/core/src/framebuffer/FramebufferSystem.js b/packages/core/src/framebuffer/FramebufferSystem.js new file mode 100644 index 0000000..2cc2437 --- /dev/null +++ b/packages/core/src/framebuffer/FramebufferSystem.js @@ -0,0 +1,246 @@ +import System from '../System'; +import { Rectangle } from '@pixi/math'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class FramebufferSystem extends System +{ + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + contextChange() + { + this.gl = this.renderer.gl; + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + this.current = null; + this.viewport = new Rectangle(); + + this.drawBufferExtension = this.renderer.context.extensions.drawBuffers; + } + + bind(framebuffer, frame) + { + const gl = this.gl; + + this.current = framebuffer; + + if (framebuffer) + { + // TODO cacheing layer! + + const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID] || this.initFramebuffer(framebuffer); + + gl.bindFramebuffer(gl.FRAMEBUFFER, fbo.framebuffer); + // makesure all textures are unbound.. + + // now check for updates... + if (fbo.dirtyId !== framebuffer.dirtyId) + { + fbo.dirtyId = framebuffer.dirtyId; + + if (fbo.dirtyFormat !== framebuffer.dirtyFormat) + { + fbo.dirtyFormat = framebuffer.dirtyFormat; + this.updateFramebuffer(framebuffer); + } + else if (fbo.dirtySize !== framebuffer.dirtySize) + { + fbo.dirtySize = framebuffer.dirtySize; + this.resizeFramebuffer(framebuffer); + } + } + + for (let i = 0; i < framebuffer.colorTextures.length; i++) + { + if (framebuffer.colorTextures[i].texturePart) + { + this.renderer.texture.unbind(framebuffer.colorTextures[i].texture); + } + else + { + this.renderer.texture.unbind(framebuffer.colorTextures[i]); + } + } + + if (framebuffer.depthTexture) + { + this.renderer.texture.unbind(framebuffer.depthTexture); + } + + if (frame) + { + this.setViewport(frame.x, frame.y, frame.width, frame.height); + } + else + { + this.setViewport(0, 0, framebuffer.width, framebuffer.height); + } + } + else + { + gl.bindFramebuffer(gl.FRAMEBUFFER, null); + + if (frame) + { + this.setViewport(frame.x, frame.y, frame.width, frame.height); + } + else + { + this.setViewport(0, 0, this.renderer.width, this.renderer.height); + } + } + } + + setViewport(x, y, width, height) + { + const v = this.viewport; + + if (v.width !== width || v.height !== height || v.x !== x || v.y !== y) + { + v.x = x; + v.y = y; + v.width = width; + v.height = height; + + this.gl.viewport(x, y, width, height); + } + } + + get size() + { + if (this.current) + { + // TODO store temp + return { x: 0, y: 0, width: this.current.width, height: this.current.height }; + } + + return { x: 0, y: 0, width: this.renderer.width, height: this.renderer.height }; + } + + clear(r, g, b, a) + { + const gl = this.gl; + + // TODO clear color can be set only one right? + gl.clearColor(r, g, b, a); + gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); + } + + // private functions... + + initFramebuffer(framebuffer) + { + const gl = this.gl; + + // TODO - make this a class? + const fbo = { + framebuffer: gl.createFramebuffer(), + stencil: null, + dirtyId: 0, + dirtyFormat: 0, + dirtySize: 0, + }; + + framebuffer.glFrameBuffers[this.CONTEXT_UID] = fbo; + + return fbo; + } + + resizeFramebuffer(framebuffer) + { + const gl = this.gl; + + if (framebuffer.stencil || framebuffer.depth) + { + gl.bindRenderbuffer(gl.RENDERBUFFER, this.stencil); + gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); + } + } + + updateFramebuffer(framebuffer) + { + const gl = this.gl; + + const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID]; + + // bind the color texture + const colorTextures = framebuffer.colorTextures; + + let count = colorTextures.length; + + if (!this.drawBufferExtension) + { + count = Math.min(count, 1); + } + + const activeTextures = []; + + for (let i = 0; i < count; i++) + { + const texture = framebuffer.colorTextures[i]; + + if (texture.texturePart) + { + this.renderer.texture.bind(texture.texture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.COLOR_ATTACHMENT0 + i, + gl.TEXTURE_CUBE_MAP_NEGATIVE_X + texture.side, + texture.texture._glTextures[this.CONTEXT_UID].texture, + 0); + } + else + { + this.renderer.texture.bind(texture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.COLOR_ATTACHMENT0 + i, + gl.TEXTURE_2D, + texture._glTextures[this.CONTEXT_UID].texture, + 0); + } + + activeTextures.push(gl.COLOR_ATTACHMENT0 + i); + } + + if (this.drawBufferExtension && activeTextures.length > 1) + { + this.drawBufferExtension.drawBuffersWEBGL(activeTextures); + } + + if (framebuffer.depthTexture) + { + const depthTextureExt = this.renderer.context.extensions.depthTexture; + + if (depthTextureExt) + { + const depthTexture = framebuffer.depthTexture; + + this.renderer.texture.bind(depthTexture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.DEPTH_ATTACHMENT, + gl.TEXTURE_2D, + depthTexture._glTextures[this.CONTEXT_UID].texture, + 0); + } + } + + if (framebuffer.stencil || framebuffer.depth) + { + fbo.stencil = gl.createRenderbuffer(); + + gl.bindRenderbuffer(gl.RENDERBUFFER, fbo.stencil); + + // TODO.. this is depth AND stencil? + gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, fbo.stencil); + gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); + // fbo.enableStencil(); + } + } +} diff --git a/packages/core/src/geometry/GLBuffer.js b/packages/core/src/geometry/GLBuffer.js new file mode 100644 index 0000000..0477e8a --- /dev/null +++ b/packages/core/src/geometry/GLBuffer.js @@ -0,0 +1,9 @@ +export default class GLBuffer +{ + constructor(buffer) + { + this.buffer = buffer; + this.updateID = -1; + this.byteLength = -1; + } +} diff --git a/packages/core/src/geometry/GeometrySystem.js b/packages/core/src/geometry/GeometrySystem.js new file mode 100644 index 0000000..959f827 --- /dev/null +++ b/packages/core/src/geometry/GeometrySystem.js @@ -0,0 +1,388 @@ +import System from '../System'; +import GLBuffer from './GLBuffer'; + +const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class GeometrySystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this._activeGeometry = null; + this._activeVao = null; + + this.hasVao = true; + this.hasInstance = true; + } + + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + contextChange() + { + const gl = this.gl = this.renderer.gl; + + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + + // webgl2 + if (!gl.createVertexArray) + { + // webgl 1! + let nativeVaoExtension = this.renderer.context.extensions.vertexArrayObject; + + if (this.renderer.legacy) + { + nativeVaoExtension = null; + } + + if (nativeVaoExtension) + { + gl.createVertexArray = () => + nativeVaoExtension.createVertexArrayOES(); + + gl.bindVertexArray = (vao) => + nativeVaoExtension.bindVertexArrayOES(vao); + + gl.deleteVertexArray = (vao) => + nativeVaoExtension.deleteVertexArrayOES(vao); + } + else + { + this.hasVao = false; + gl.createVertexArray = () => + { + // empty + }; + + gl.bindVertexArray = () => + { + // empty + }; + + gl.deleteVertexArray = () => + { + // empty + }; + } + } + + if (!gl.vertexAttribDivisor) + { + const instanceExt = gl.getExtension('ANGLE_instanced_arrays'); + + if (instanceExt) + { + gl.vertexAttribDivisor = (a, b) => + instanceExt.vertexAttribDivisorANGLE(a, b); + + gl.drawElementsInstanced = (a, b, c, d, e) => + instanceExt.drawElementsInstancedANGLE(a, b, c, d, e); + + gl.drawArraysInstanced = (a, b, c, d) => + instanceExt.drawArraysInstancedANGLE(a, b, c, d); + } + else + { + this.hasInstance = false; + } + } + } + + /** + * Binds geometry so that is can be drawn. Creating a Vao if required + * @private + * @param {PIXI.Geometry} geometry instance of geometry to bind + */ + bind(geometry, shader) + { + shader = shader || this.renderer.shader.shader; + + const gl = this.gl; + + // not sure the best way to address this.. + // currently different shaders require different VAOs for the same geometry + // Still mulling over the best way to solve this one.. + // will likely need to modify the shader attribute locations at run time! + let vaos = geometry.glVertexArrayObjects[this.CONTEXT_UID]; + + if (!vaos) + { + geometry.glVertexArrayObjects[this.CONTEXT_UID] = vaos = {}; + } + + const vao = vaos[shader.program.id] || this.initGeometryVao(geometry, shader.program); + + this._activeGeometry = geometry; + + if (this._activeVao !== vao) + { + this._activeVao = vao; + + if (this.hasVao) + { + gl.bindVertexArray(vao); + } + else + { + this.activateVao(geometry, shader.program); + } + } + + // TODO - optimise later! + // don't need to loop through if nothing changed! + // maybe look to add an 'autoupdate' to geometry? + this.updateBuffers(); + } + + reset() + { + this.unbind(); + } + + updateBuffers() + { + const geometry = this._activeGeometry; + const gl = this.gl; + + for (let i = 0; i < geometry.buffers.length; i++) + { + const buffer = geometry.buffers[i]; + + const glBuffer = buffer._glBuffers[this.CONTEXT_UID]; + + if (buffer._updateID !== glBuffer.updateID) + { + glBuffer.updateID = buffer._updateID; + + // TODO can cache this on buffer! maybe added a getter / setter? + const type = buffer.index ? gl.ELEMENT_ARRAY_BUFFER : gl.ARRAY_BUFFER; + const drawType = buffer.static ? gl.STATIC_DRAW : gl.DYNAMIC_DRAW; + + gl.bindBuffer(type, glBuffer.buffer); + + if (glBuffer.byteLength >= buffer.data.byteLength) + { + // offset is always zero for now! + gl.bufferSubData(type, 0, buffer.data); + } + else + { + gl.bufferData(type, buffer.data, drawType); + } + } + } + } + + checkCompatability(geometry, program) + { + // geometry must have at least all the attributes that the shader requires. + const geometryAttributes = geometry.attributes; + const shaderAttributes = program.attributeData; + + for (const j in shaderAttributes) + { + if (!geometryAttributes[j]) + { + throw new Error(`shader and geometry incompatible, geometry missing the "${j}" attribute`); + } + } + } + + /** + * Creates a Vao with the same structure as the geometry and stores it on the geometry. + * @private + * @param {PIXI.Geometry} geometry instance of geometry to to generate Vao for + */ + initGeometryVao(geometry, program) + { + this.checkCompatability(geometry, program); + + const gl = this.gl; + const CONTEXT_UID = this.CONTEXT_UID; + const buffers = geometry.buffers; + const attributes = geometry.attributes; + + const tempStride = {}; + const tempStart = {}; + + for (const j in buffers) + { + tempStride[j] = 0; + tempStart[j] = 0; + } + + for (const j in attributes) + { + if (!attributes[j].size && program.attributeData[j]) + { + attributes[j].size = program.attributeData[j].size; + } + + tempStride[attributes[j].buffer] += attributes[j].size * byteSizeMap[attributes[j].type]; + } + + for (const j in attributes) + { + const attribute = attributes[j]; + const attribSize = attribute.size; + + if (attribute.stride === undefined) + { + if (tempStride[attribute.buffer] === attribSize * byteSizeMap[attribute.type]) + { + attribute.stride = 0; + } + else + { + attribute.stride = tempStride[attribute.buffer]; + } + } + + if (attribute.start === undefined) + { + attribute.start = tempStart[attribute.buffer]; + + tempStart[attribute.buffer] += attribSize * byteSizeMap[attribute.type]; + } + } + + // first update - and create the buffers! + // only create a gl buffer if it actually gets + for (let i = 0; i < buffers.length; i++) + { + const buffer = buffers[i]; + + if (!buffer._glBuffers[CONTEXT_UID]) + { + buffer._glBuffers[CONTEXT_UID] = new GLBuffer(gl.createBuffer()); + } + } + + // TODO - maybe make this a data object? + // lets wait to see if we need to first! + const vao = gl.createVertexArray(); + + gl.bindVertexArray(vao); + + this.activateVao(geometry, program); + + gl.bindVertexArray(null); + + // add it to the cache! + geometry.glVertexArrayObjects[this.CONTEXT_UID][program.id] = vao; + + return vao; + } + + activateVao(geometry, program) + { + const gl = this.gl; + const CONTEXT_UID = this.CONTEXT_UID; + const buffers = geometry.buffers; + const attributes = geometry.attributes; + + if (geometry.indexBuffer) + { + // first update the index buffer if we have one.. + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, geometry.indexBuffer._glBuffers[CONTEXT_UID].buffer); + } + + let lastBuffer = null; + + // add a new one! + for (const j in attributes) + { + const attribute = attributes[j]; + const buffer = buffers[attribute.buffer]; + const glBuffer = buffer._glBuffers[CONTEXT_UID]; + + if (program.attributeData[j]) + { + if (lastBuffer !== glBuffer) + { + gl.bindBuffer(gl.ARRAY_BUFFER, glBuffer.buffer); + + lastBuffer = glBuffer; + } + + const location = program.attributeData[j].location; + + // TODO introduce state again + // we can optimise this for older devices that have no VAOs + gl.enableVertexAttribArray(location); + + gl.vertexAttribPointer(location, + attribute.size, + attribute.type || gl.FLOAT, + attribute.normalized, + attribute.stride, + attribute.start); + + if (attribute.instance) + { + // TODO calculate instance count based of this... + if (this.hasInstance) + { + gl.vertexAttribDivisor(location, 1); + } + else + { + throw new Error('geometry error, GPU Instancing is not supported on this device'); + } + } + } + } + } + + draw(type, size, start, instanceCount) + { + const gl = this.gl; + const geometry = this._activeGeometry; + + // TODO.. this should not change so maybe cache the function? + + if (geometry.indexBuffer) + { + if (geometry.instanced) + { + /* eslint-disable max-len */ + gl.drawElementsInstanced(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2, instanceCount || 1); + /* eslint-enable max-len */ + } + else + { + gl.drawElements(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2); + } + } + else + if (geometry.instanced) + { + // TODO need a better way to calculate size.. + gl.drawArraysInstanced(type, start, size || geometry.getSize(), instanceCount || 1); + } + else + { + gl.drawArrays(type, start, size || geometry.getSize()); + } + + return this; + } + + unbind() + { + this.gl.bindVertexArray(null); + this._activeVao = null; + this._activeGeometry = null; + } +} diff --git a/packages/core/src/geometry/utils/setVertexAttribArrays.js b/packages/core/src/geometry/utils/setVertexAttribArrays.js new file mode 100644 index 0000000..23b6959 --- /dev/null +++ b/packages/core/src/geometry/utils/setVertexAttribArrays.js @@ -0,0 +1,54 @@ +// var GL_MAP = {}; + +/** + * @param gl {WebGLRenderingContext} The current WebGL context + * @param attribs {*} + * @param state {*} + */ +export default function setVertexAttribArrays(gl, attribs, state) +{ + let i; + + if (state) + { + const tempAttribState = state.tempAttribState; + const attribState = state.attribState; + + for (i = 0; i < tempAttribState.length; i++) + { + tempAttribState[i] = false; + } + + // set the new attribs + for (i = 0; i < attribs.length; i++) + { + tempAttribState[attribs[i].attribute.location] = true; + } + + for (i = 0; i < attribState.length; i++) + { + if (attribState[i] !== tempAttribState[i]) + { + attribState[i] = tempAttribState[i]; + + if (state.attribState[i]) + { + gl.enableVertexAttribArray(i); + } + else + { + gl.disableVertexAttribArray(i); + } + } + } + } + else + { + for (i = 0; i < attribs.length; i++) + { + const attrib = attribs[i]; + + gl.enableVertexAttribArray(attrib.attribute.location); + } + } +} diff --git a/packages/core/src/index.js b/packages/core/src/index.js index 58c456a..fd1f547 100644 --- a/packages/core/src/index.js +++ b/packages/core/src/index.js @@ -1,30 +1,30 @@ import * as resources from './textures/resources'; -import * as systems from './renderers/systems'; +import * as systems from './systems'; -export { resources }; export { systems }; +export { resources }; -export { default as Renderer } from './renderers/Renderer'; -export { default as AbstractRenderer } from './renderers/AbstractRenderer'; -export { default as FrameBuffer } from './textures/FrameBuffer'; +export { default as System } from './System'; +export { default as Renderer } from './Renderer'; +export { default as AbstractRenderer } from './AbstractRenderer'; +export { default as FrameBuffer } from './framebuffer/FrameBuffer'; export { default as CubeTexture } from './textures/CubeTexture'; export { default as BaseTexture } from './textures/BaseTexture'; export { default as Texture } from './textures/Texture'; export { default as TextureMatrix } from './textures/TextureMatrix'; -export { default as RenderTexture } from './textures/RenderTexture'; -export { default as BaseRenderTexture } from './textures/BaseRenderTexture'; +export { default as RenderTexture } from './renderTexture/RenderTexture'; +export { default as BaseRenderTexture } from './renderTexture/BaseRenderTexture'; export { default as TextureUvs } from './textures/TextureUvs'; -export { default as State } from './renderers/State'; -export { default as ObjectRenderer } from './renderers/utils/ObjectRenderer'; -export { default as Quad } from './renderers/utils/Quad'; -export { default as QuadUv } from './renderers/utils/QuadUv'; -export { default as checkMaxIfStatmentsInShader } from './renderers/utils/checkMaxIfStatmentsInShader'; +export { default as State } from './state/State'; +export { default as ObjectRenderer } from './batch/ObjectRenderer'; +export { default as Quad } from './utils/Quad'; +export { default as QuadUv } from './utils/QuadUv'; +export { default as checkMaxIfStatmentsInShader } from './shader/utils/checkMaxIfStatmentsInShader'; export { default as Shader } from './shader/Shader'; export { default as Program } from './shader/Program'; export { default as UniformGroup } from './shader/UniformGroup'; -export { default as SpriteMaskFilter } from './renderers/filters/spriteMask/SpriteMaskFilter'; -export { default as Filter } from './renderers/filters/Filter'; +export { default as SpriteMaskFilter } from './filters/spriteMask/SpriteMaskFilter'; +export { default as Filter } from './filters/Filter'; export { default as Attribute } from './geometry/Attribute'; export { default as Buffer } from './geometry/Buffer'; export { default as Geometry } from './geometry/Geometry'; - diff --git a/packages/core/src/mask/MaskSystem.js b/packages/core/src/mask/MaskSystem.js new file mode 100644 index 0000000..99174c2 --- /dev/null +++ b/packages/core/src/mask/MaskSystem.js @@ -0,0 +1,199 @@ +import System from '../System'; +import AlphaMaskFilter from '../filters/spriteMask/SpriteMaskFilter'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class MaskSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + // TODO - we don't need both! + this.scissor = false; + this.scissorData = null; + this.scissorRenderTarget = null; + + this.enableScissor = false; + + this.alphaMaskPool = []; + this.alphaMaskIndex = 0; + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.DisplayObject} target - Display Object to push the mask to + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + push(target, maskData) + { + // TODO the root check means scissor rect will not + // be used on render textures more info here: + // https://github.com/pixijs/pixi.js/pull/3545 + + if (maskData.texture) + { + this.pushSpriteMask(target, maskData); + } + else if (this.enableScissor + && !this.scissor + && this.renderer._activeRenderTarget.root + && !this.renderer.stencil.stencilMaskStack.length + && maskData.isFastRect()) + { + const matrix = maskData.worldTransform; + + let rot = Math.atan2(matrix.b, matrix.a); + + // use the nearest degree! + rot = Math.round(rot * (180 / Math.PI)); + + if (rot % 90) + { + this.pushStencilMask(maskData); + } + else + { + this.pushScissorMask(target, maskData); + } + } + else + { + this.pushStencilMask(maskData); + } + } + + /** + * Removes the last mask from the mask stack and doesn't return it. + * + * @param {PIXI.DisplayObject} target - Display Object to pop the mask from + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + pop(target, maskData) + { + if (maskData.texture) + { + this.popSpriteMask(target, maskData); + } + else if (this.enableScissor && !this.renderer.stencil.stencilMaskStack.length) + { + this.popScissorMask(target, maskData); + } + else + { + this.popStencilMask(target, maskData); + } + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.RenderTarget} target - Display Object to push the sprite mask to + * @param {PIXI.Sprite} maskData - Sprite to be used as the mask + */ + pushSpriteMask(target, maskData) + { + let alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; + + if (!alphaMaskFilter) + { + alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; + } + + alphaMaskFilter[0].resolution = this.renderer.resolution; + alphaMaskFilter[0].maskSprite = maskData; + + // TODO - may cause issues! + target.filterArea = maskData.getBounds(true); + + this.renderer.filterSystem.pushFilter(target, alphaMaskFilter); + + this.alphaMaskIndex++; + } + + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popSpriteMask() + { + this.renderer.filterSystem.popFilter(); + this.alphaMaskIndex--; + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + pushStencilMask(maskData) + { + this.renderer.batch.flush(); + this.renderer.stencil.pushStencil(maskData); + } + + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popStencilMask() + { + // this.renderer.currentRenderer.stop(); + this.renderer.stencil.popStencil(); + } + + /** + * + * @param {PIXI.DisplayObject} target - Display Object to push the mask to + * @param {PIXI.Graphics} maskData - The masking data. + */ + pushScissorMask(target, maskData) + { + maskData.renderable = true; + + const renderTarget = this.renderer._activeRenderTarget; + + const bounds = maskData.getBounds(); + + bounds.fit(renderTarget.size); + maskData.renderable = false; + + this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); + + const resolution = this.renderer.resolution; + + this.renderer.gl.scissor( + bounds.x * resolution, + (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, + bounds.width * resolution, + bounds.height * resolution + ); + + this.scissorRenderTarget = renderTarget; + this.scissorData = maskData; + this.scissor = true; + } + + /** + * + * + */ + popScissorMask() + { + this.scissorRenderTarget = null; + this.scissorData = null; + this.scissor = false; + + // must be scissor! + const gl = this.renderer.gl; + + gl.disable(gl.SCISSOR_TEST); + } +} diff --git a/packages/core/src/AbstractRenderer.js b/packages/core/src/AbstractRenderer.js new file mode 100644 index 0000000..64b2eb1 --- /dev/null +++ b/packages/core/src/AbstractRenderer.js @@ -0,0 +1,353 @@ +import { hex2string, hex2rgb } from '@pixi/utils'; +import { Matrix, Rectangle } from '@pixi/math'; +import { RENDERER_TYPE } from '@pixi/constants'; +import { settings } from '@pixi/settings'; +import { Container } from '@pixi/display'; +import RenderTexture from './renderTexture/RenderTexture'; +import EventEmitter from 'eventemitter3'; + +const tempMatrix = new Matrix(); + +/** + * 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 EventEmitter + * @memberof PIXI + */ +export default class AbstractRenderer extends EventEmitter +{ + // eslint-disable-next-line valid-jsdoc + /** + * @param {string} system - The name of the system this renderer is for. + * @param {object} [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.transparent=false] - If the render view is transparent, default false + * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false + * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. The + * resolution of the renderer retina would be 2. + * @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 {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when rendering, + * stopping pixel interpolation. + */ + constructor(system, options, arg2, arg3) + { + super(); + + // Support for constructor(system, screenWidth, screenHeight, options) + if (typeof options === 'number') + { + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + }, arg3); + } + + // 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 = RENDERER_TYPE.UNKNOWN; + + /** + * Measurements of the screen. (0, 0, screenWidth, screenHeight) + * + * Its safe to use as filterArea or hitArea for 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 1 + */ + this.resolution = options.resolution || settings.RESOLUTION; + + /** + * Whether the render view is transparent + * + * @member {boolean} + */ + this.transparent = options.transparent; + + /** + * Whether css dimensions of canvas view should be resized to screen dimensions automatically + * + * @member {boolean} + */ + this.autoResize = options.autoResize || false; + + /** + * Tracks the blend modes useful for this renderer. + * + * @member {object} + */ + this.blendModes = null; + + /** + * 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; + + /** + * If true PixiJS will Math.floor() x/y values when rendering, stopping pixel interpolation. + * Handy for crisp pixel art and speed on legacy devices. + * + * @member {boolean} + */ + this.roundPixels = options.roundPixels; + + /** + * The background color as a number. + * + * @member {number} + * @private + */ + this._backgroundColor = 0x000000; + + /** + * The background color as an [R, G, B] array. + * + * @member {number[]} + * @private + */ + this._backgroundColorRgba = [0, 0, 0, 0]; + + /** + * The background color as a string. + * + * @member {string} + * @private + */ + this._backgroundColorString = '#000000'; + + this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter + + /** + * This temporary display object used as the parent of the currently being rendered item + * + * @member {PIXI.DisplayObject} + * @private + */ + this._tempDisplayObjectParent = new Container(); + + /** + * The last root object that the renderer tried to render. + * + * @member {PIXI.DisplayObject} + * @private + */ + this._lastObjectRendered = this._tempDisplayObjectParent; + + /** + * Collection of plugins. + * @readonly + * @member {object} + */ + this.plugins = {}; + } + + /** + * Initialize the plugins. + * + * @protected + * @param {object} staticMap - The dictionary of staticly saved plugins. + */ + initPlugins(staticMap) + { + 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() + { + return this.view.width; + } + + /** + * Same as view.height, actual number of pixels in the canvas by vertical + * + * @member {number} + * @readonly + * @default 600 + */ + get height() + { + return this.view.height; + } + + /** + * Resizes the screen and canvas to the specified width and height + * Canvas dimensions are multiplied by resolution + * + * @param {number} screenWidth - the new width of the screen + * @param {number} screenHeight - the new height of the screen + */ + resize(screenWidth, screenHeight) + { + this.screen.width = screenWidth; + this.screen.height = screenHeight; + + this.view.width = screenWidth * this.resolution; + this.view.height = screenHeight * this.resolution; + + if (this.autoResize) + { + this.view.style.width = `${screenWidth}px`; + this.view.style.height = `${screenHeight}px`; + } + } + + /** + * 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 {PIXI.DisplayObject} displayObject - The displayObject the object will be generated from + * @param {number} scaleMode - Should be one of the scaleMode consts + * @param {number} resolution - The resolution / device pixel ratio of the texture being generated + * @param {PIXI.Rectangle} [region] - The region of the displayObject, that shall be rendered, + * if no region is specified, defaults to the local bounds of the displayObject. + * @return {PIXI.Texture} a texture of the graphics object + */ + generateTexture(displayObject, scaleMode, resolution, region) + { + region = region || displayObject.getLocalBounds(); + + // 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(region.width | 0, region.height | 0, scaleMode, resolution); + + tempMatrix.tx = -region.x; + tempMatrix.ty = -region.y; + + this.render(displayObject, renderTexture, false, tempMatrix, true); + + return renderTexture; + } + + /** + * Removes everything from the renderer and optionally removes the Canvas DOM element. + * + * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. + */ + destroy(removeView) + { + 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); + } + + this.plugins = null; + + this.type = RENDERER_TYPE.UNKNOWN; + + this.view = null; + + this.screen = null; + + this.resolution = 0; + + this.transparent = false; + + this.autoResize = false; + + this.blendModes = null; + + this.options = null; + + this.preserveDrawingBuffer = false; + this.clearBeforeRender = false; + + this.roundPixels = false; + + this._backgroundColor = 0; + this._backgroundColorRgba = null; + this._backgroundColorString = null; + + this._tempDisplayObjectParent = null; + this._lastObjectRendered = null; + } + + /** + * The background color to fill if not transparent + * + * @member {number} + */ + get backgroundColor() + { + return this._backgroundColor; + } + + set backgroundColor(value) // eslint-disable-line require-jsdoc + { + this._backgroundColor = value; + this._backgroundColorString = hex2string(value); + hex2rgb(value, this._backgroundColorRgba); + } +} diff --git a/packages/core/src/Renderer.js b/packages/core/src/Renderer.js new file mode 100644 index 0000000..2227e78 --- /dev/null +++ b/packages/core/src/Renderer.js @@ -0,0 +1,338 @@ +import AbstractRenderer from './AbstractRenderer'; +import { sayHello } from '@pixi/utils'; +import MaskSystem from './mask/MaskSystem'; +import StencilSystem from './mask/StencilSystem'; +import FilterSystem from './filters/FilterSystem'; +import FramebufferSystem from './framebuffer/FramebufferSystem'; +import RenderTextureSystem from './renderTexture/RenderTextureSystem'; +import TextureSystem from './textures/TextureSystem'; +import ProjectionSystem from './projection/ProjectionSystem'; +import StateSystem from './state/StateSystem'; +import GeometrySystem from './geometry/GeometrySystem'; +import ShaderSystem from './shader/ShaderSystem'; +import ContextSystem from './context/ContextSystem'; +import BatchSystem from './batch/BatchSystem'; +import TextureGCSystem from './textures/TextureGCSystem'; +import { RENDERER_TYPE } from '@pixi/constants'; +import UniformGroup from './shader/UniformGroup'; +import { Matrix } from '@pixi/math'; +import Runner from 'mini-runner'; + +/** + * 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 Render works by automatically managing webGLBatchs. + * 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 :) + * + * @class + * @memberof PIXI + * @extends PIXI.AbstractRenderer + */ +export default class Renderer extends AbstractRenderer +{ + // eslint-disable-next-line valid-jsdoc + /** + * + * @param {object} [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.transparent=false] - If the render view is transparent, default false + * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false + * @param {boolean} [options.antialias=false] - sets antialias. If not available natively then FXAA + * antialiasing is used + * @param {boolean} [options.forceFXAA=false] - forces FXAA antialiasing to be used over native. + * FXAA is faster, but may not always look as great + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. + * The resolution of the renderer retina would be 2. + * @param {boolean} [options.clearBeforeRender=true] - This sets if the renderer will clear + * the canvas or not before the new render pass. If you wish to set this to false, you *must* set + * preserveDrawingBuffer to `true`. + * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, + * enable this if you need to call toDataUrl on the webgl context. + * @param {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when + * rendering, stopping pixel interpolation. + * @param {number} [options.backgroundColor=0x000000] - The background color of the rendered area + * (shown if not transparent). + * @param {boolean} [options.legacy=false] - If true PixiJS will aim to ensure compatibility + * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. + * @param {string} [options.powerPreference] - Parameter passed to webgl context, set to "high-performance" + * for devices with dual graphics card + */ + constructor(options, arg2, arg3) + { + super('WebGL', options, arg2, arg3); + + /** + * The type of this renderer as a standardised const + * + * @member {number} + * @see PIXI.RENDERER_TYPE + */ + this.type = RENDERER_TYPE.WEBGL; + + // this will be set by the contextSystem (this.context) + this.gl = null; + this.CONTEXT_UID = 0; + this.legacy = !!options.legacy; + + // TODO legacy! + + // runners! + this.runners = { + destroy: new Runner('destroy'), + contextChange: new Runner('contextChange', 1), + reset: new Runner('reset'), + update: new Runner('update'), + postrender: new Runner('postrender'), + prerender: new Runner('prerender'), + resize: new Runner('resize', 2), + }; + + 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(GeometrySystem, 'geometry') + .addSystem(FramebufferSystem, 'framebuffer') + .addSystem(StencilSystem, 'stencil') + .addSystem(ProjectionSystem, 'projection') + .addSystem(TextureGCSystem, 'textureGC') + .addSystem(FilterSystem, 'filter') + .addSystem(RenderTextureSystem, 'renderTexture') + .addSystem(BatchSystem, 'batch'); + + this.initPlugins(Renderer.__plugins); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ + if (options.context) + { + this.context.initFromContext(options.context); + } + else + { + this.context.initFromOptions({ + alpha: this.transparent, + antialias: options.antialias, + premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', + stencil: true, + 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); + } + + /** + * Add a new system to the renderer. + * @param {class} ClassRef - Class reference + * @param {string} [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. + * @return {PIXI.Renderer} Return instance of renderer + */ + addSystem(ClassRef, name) + { + if (!name) + { + name = ClassRef.name; + } + + // TODO - read name from class.name.. + + /* + if(name.includes('System')) + { + name = name.replace('System', ''); + name = name.charAt(0).toLowerCase() + name.slice(1); + } + */ + + const system = new ClassRef(this); + + if (this[name]) + { + throw new Error(`Whoops! ${name} is already a manger`); + } + + this[name] = system; + + for (const i in this.runners) + { + this.runners[i].add(system); + } + + return this; + + /** + * 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. + */ + } + + /** + * Renders the object to its webGL view + * + * @param {PIXI.DisplayObject} displayObject - the object to be rendered + * @param {PIXI.RenderTexture} renderTexture - The render texture to render to. + * @param {boolean} [clear] - Should the canvas be cleared before the new render + * @param {PIXI.Transform} [transform] - A transform to apply to the render texture before rendering. + * @param {boolean} [skipUpdateTransform] - Should we skip the update transform pass? + */ + render(displayObject, renderTexture, clear, transform, skipUpdateTransform) + { + // can be handy to know! + this.renderingToScreen = !renderTexture; + + this.runners.prerender.run(); + this.emit('prerender'); + + // 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.parent; + + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = 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.run(); + + this.emit('postrender'); + } + + /** + * Resizes the webGL view to the specified width and height. + * + * @param {number} screenWidth - the new width of the screen + * @param {number} screenHeight - the new height of the screen + */ + resize(screenWidth, screenHeight) + { + AbstractRenderer.prototype.resize.call(this, screenWidth, screenHeight); + + this.runners.resize.run(screenWidth, screenHeight); + } + + /** + * Resets the WebGL state so you can render things however you fancy! + * + * @return {PIXI.Renderer} Returns itself. + */ + reset() + { + this.runners.reset.run(); + + return this; + } + + clear() + { + this.framebuffer.bind(); + this.framebuffer.clear(); + } + + /** + * Removes everything from the renderer (event listeners, spritebatch, etc...) + * + * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. + * See: https://github.com/pixijs/pixi.js/issues/2233 + */ + destroy(removeView) + { + this.runners.destroy.run(); + + // call base destroy + super.destroy(removeView); + + // TODO nullify all the managers.. + this.gl = null; + } + + /** + * 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 + * builds and excluding plugins. + * @name PIXI.Renderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. + */ + + /** + * Adds a plugin to the renderer. + * + * @method + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + static registerPlugin(pluginName, ctor) + { + Renderer.__plugins = Renderer.__plugins || {}; + Renderer.__plugins[pluginName] = ctor; + } +} diff --git a/packages/core/src/System.js b/packages/core/src/System.js new file mode 100644 index 0000000..05a10c8 --- /dev/null +++ b/packages/core/src/System.js @@ -0,0 +1,42 @@ +/** + * System is a base class used for extending systems used by the {@link PIXI.Renderer} + * @see PIXI.Renderer#addSystem + * @class + * @memberof PIXI + */ +export default class System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + /** + * The renderer this manager works for. + * + * @member {PIXI.Renderer} + */ + this.renderer = renderer; + + this.renderer.runners.contextChange.add(this); + } + + /** + * Generic method called when there is a WebGL context change. + * + */ + contextChange() + { + // do some codes init! + } + + /** + * Generic destroy methods to be overridden by the subclass + * + */ + destroy() + { + this.renderer.runners.contextChange.remove(this); + this.renderer = null; + } +} diff --git a/packages/core/src/batch/BatchSystem.js b/packages/core/src/batch/BatchSystem.js new file mode 100644 index 0000000..b04c249 --- /dev/null +++ b/packages/core/src/batch/BatchSystem.js @@ -0,0 +1,68 @@ +import System from '../System'; +import ObjectRenderer from './ObjectRenderer'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class BatchSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + super(renderer); + + /** + * An empty renderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.emptyRenderer = new ObjectRenderer(renderer); + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.currentRenderer = this.emptyRenderer; + } + + /** + * Changes the current renderer to the one given in parameter + * + * @param {PIXI.ObjectRenderer} objectRenderer - The object renderer to use. + */ + setObjectRenderer(objectRenderer) + { + if (this.currentRenderer === objectRenderer) + { + return; + } + + this.currentRenderer.stop(); + this.currentRenderer = objectRenderer; + + this.renderer.state.setState(objectRenderer.state); + + this.currentRenderer.start(); + } + + /** + * This should be called if you wish to do some custom rendering + * It will basically render anything that may be batched up such as sprites + * + */ + flush() + { + this.setObjectRenderer(this.emptyRenderer); + } + + reset() + { + this.setObjectRenderer(this.emptyRenderer); + } +} diff --git a/packages/core/src/batch/ObjectRenderer.js b/packages/core/src/batch/ObjectRenderer.js new file mode 100644 index 0000000..0c851a9 --- /dev/null +++ b/packages/core/src/batch/ObjectRenderer.js @@ -0,0 +1,48 @@ +import System from '../System'; + +/** + * Base for a common object renderer that can be used as a system renderer plugin. + * + * @class + * @extends PIXI.System + * @memberof PIXI + */ +export default class ObjectRenderer extends System +{ + /** + * Starts the renderer and sets the shader + * + */ + start() + { + // set the shader.. + } + + /** + * Stops the renderer + * + */ + stop() + { + this.flush(); + } + + /** + * Stub method for rendering content and emptying the current batch. + * + */ + flush() + { + // flush! + } + + /** + * Renders an object + * + * @param {PIXI.DisplayObject} object - The object to render. + */ + render(object) // eslint-disable-line no-unused-vars + { + // render the object + } +} diff --git a/packages/core/src/context/ContextSystem.js b/packages/core/src/context/ContextSystem.js new file mode 100644 index 0000000..301aab4 --- /dev/null +++ b/packages/core/src/context/ContextSystem.js @@ -0,0 +1,186 @@ +import System from '../System'; +import { settings } from '@pixi/settings'; + +let CONTEXT_UID = 0; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class ContextSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.webGLVersion = 1; + + this.handleContextLost = this.handleContextLost.bind(this); + this.handleContextRestored = this.handleContextRestored.bind(this); + + this.extensions = {}; + + renderer.view.addEventListener('webglcontextlost', this.handleContextLost, false); + renderer.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + } + + get isLost() + { + return (!this.gl || this.gl.isContextLost()); + } + + contextChange(gl) + { + this.gl = gl; + + // restore a context if it was previously lost + if (gl.isContextLost() && gl.getExtension('WEBGL_lose_context')) + { + gl.getExtension('WEBGL_lose_context').restoreContext(); + } + } + + initFromContext(gl) + { + this.gl = gl; + this.validateContext(gl); + this.renderer.gl = gl; + this.renderer.CONTEXT_UID = CONTEXT_UID++; + this.renderer.runners.contextChange.run(gl); + } + + initFromOptions(options) + { + const gl = this.createContext(this.renderer.view, options); + + this.initFromContext(gl); + } + + /** + * Helper class to create a webGL Context + * + * @class + * @memberof PIXI.glCore + * @param canvas {HTMLCanvasElement} the canvas element that we will get the context from + * @param options {Object} An options object that gets passed in to the canvas element containing the context attributes, + * see https://developer.mozilla.org/en/docs/Web/API/HTMLCanvasElement/getContext for the options available + * @return {WebGLRenderingContext} the WebGL context + */ + createContext(canvas, options) + { + let gl; + + if (settings.PREFER_WEBGL_2) + { + gl = canvas.getContext('webgl2', options); + } + + if (gl) + { + this.webGLVersion = 2; + } + else + { + this.webGLVersion = 1; + + gl = canvas.getContext('webgl', options) + || canvas.getContext('experimental-webgl', options); + + if (!gl) + { + // fail, not able to get a context + throw new Error('This browser does not support webGL. Try using the canvas renderer'); + } + } + + this.gl = gl; + + this.getExtensions(); + + return gl; + } + + getExtensions() + { + // time to set up default etensions that pixi uses.. + const gl = this.gl; + const extensions = this.extensions; + + if (this.webGLVersion === 1) + { + extensions.drawBuffers = gl.getExtension('WEBGL_draw_buffers'); + extensions.depthTexture = gl.getExtension('WEBKIT_WEBGL_depth_texture'); + extensions.floatTexture = gl.getExtension('OES_texture_float'); + extensions.loseContext = gl.getExtension('WEBGL_lose_context'); + + extensions.vertexArrayObject = gl.getExtension('OES_vertex_array_object') + || gl.getExtension('MOZ_OES_vertex_array_object') + || gl.getExtension('WEBKIT_OES_vertex_array_object'); + } + + // we don't use any specific WebGL 2 ones yet! + } + + /** + * Handles a lost webgl context + * + * @private + * @param {WebGLContextEvent} event - The context lost event. + */ + handleContextLost(event) + { + event.preventDefault(); + } + + /** + * Handles a restored webgl context + * + * @private + */ + handleContextRestored() + { + this.renderer.runners.contextChange.run(this.gl); + } + + destroy() + { + const view = this.renderer.view; + + // remove listeners + view.removeEventListener('webglcontextlost', this.handleContextLost); + view.removeEventListener('webglcontextrestored', this.handleContextRestored); + + this.gl.useProgram(null); + + if (this.extensions.loseContext) + { + this.extensions.loseContext.loseContext(); + } + } + + postrender() + { + this.gl.flush(); + } + + validateContext(gl) + { + const attributes = gl.getContextAttributes(); + + // this is going to be fairly simple for now.. but at least we have room to grow! + if (!attributes.stencil) + { + /* eslint-disable max-len */ + + /* eslint-disable no-console */ + console.warn('Provided WebGL context does not have a stencil buffer, masks may not render correctly'); + /* eslint-enable no-console */ + + /* eslint-enable max-len */ + } + } +} diff --git a/packages/core/src/filters/Filter.js b/packages/core/src/filters/Filter.js new file mode 100644 index 0000000..9d8cad8 --- /dev/null +++ b/packages/core/src/filters/Filter.js @@ -0,0 +1,140 @@ +import Shader from '../shader/Shader'; +import Program from '../shader/Program'; +import State from '../state/State'; +import { settings } from '@pixi/settings'; +// import extractUniformsFromSrc from './extractUniformsFromSrc'; +import defaultVertex from './defaultFilter.vert'; +import defaultFragment from './defaultFilter.frag'; + +// let math = require('../../math'); +/** + * @class + * @memberof PIXI + * @extends PIXI.Shader + */ +export default class Filter extends Shader +{ + /** + * @param {string} [vertexSrc] - The source of the vertex shader. + * @param {string} [fragmentSrc] - The source of the fragment shader. + * @param {object} [uniforms] - Custom uniforms to use to augment the built-in ones. + */ + constructor(vertexSrc, fragmentSrc, uniforms) + { + const program = Program.from(vertexSrc, fragmentSrc); + + super(program, uniforms); + + /** + * The padding of the filter. Some filters require extra space to breath such as a blur. + * Increasing this will add extra width and height to the bounds of the object that the + * filter is applied to. + * + * @member {number} + */ + this.padding = 0; + + /** + * The resolution of the filter. Setting this to be lower will lower the quality but + * increase the performance of the filter. + * + * @member {number} + */ + this.resolution = settings.RESOLUTION; + + /** + * If enabled is true the filter is applied, if false it will not. + * + * @member {boolean} + */ + this.enabled = true; + + /** + * If enabled, PixiJS will fit the filter area into boundaries for better performance. + * Switch it off if it does not work for specific shader. + * + * @member {boolean} + */ + this.autoFit = true; + + /** + * Legacy filters use position and uvs from attributes + * @member {boolean} + * @readonly + */ + this.legacy = !!this.program.attributeData.aTextureCoord; + + /** + * the webGL state the filter requires to render + * @member {PIXI.State} + */ + this.state = new State(); + } + + /** + * Applies the filter + * + * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + * @param {boolean} clear - Should the output be cleared before rendering to it + * @param {object} [currentState] - It's current state of filter. + * There are some useful properties in the currentState : + * target, filters, sourceFrame, destinationFrame, renderTarget, resolution + */ + apply(filterManager, input, output, clear, currentState, derp) // eslint-disable-line no-unused-vars + { + // do as you please! + + filterManager.applyFilter(this, input, output, clear, currentState, derp); + + // or just do a regular render.. + } + + /** + * Sets the blendmode of the filter + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL + */ + get blendMode() + { + return this.state.blendMode; + } + + set blendMode(value) // eslint-disable-line require-jsdoc + { + this.state.blendMode = value; + } + + /** + * The default vertex shader source + * + * @static + * @constant + */ + static get defaultVertexSrc() + { + return defaultVertex; + } + + /** + * The default fragment shader source + * + * @static + * @constant + */ + static get defaultFragmentSrc() + { + return defaultFragment; + } +} + +/** + * Used for caching shader IDs + * + * @static + * @private + */ +Filter.SOURCE_KEY_MAP = {}; + diff --git a/packages/core/src/filters/FilterSystem.js b/packages/core/src/filters/FilterSystem.js new file mode 100644 index 0000000..3174318 --- /dev/null +++ b/packages/core/src/filters/FilterSystem.js @@ -0,0 +1,431 @@ +import System from '../System'; + +import RenderTexture from '../renderTexture/RenderTexture'; +import Quad from '../utils/Quad'; +import QuadUv from '../utils/QuadUv'; +import { Rectangle } from '@pixi/math'; +import * as filterTransforms from './filterTransforms'; +import bitTwiddle from 'bit-twiddle'; +import UniformGroup from '../shader/UniformGroup'; +import { DRAW_MODES } from '@pixi/constants'; + +// +/** + * @ignore + * @class + */ +class FilterState +{ + /** + * + */ + constructor() + { + this.renderTexture = null; + this.sourceFrame = new Rectangle(); + this.destinationFrame = new Rectangle(); + this.filters = []; + this.target = null; + this.legacy = false; + this.resolution = 1; + } +} + +/** + * @class + * @memberof PIXI.systems + * @extends PIXI.System + */ +export default class FilterSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + /** + * stores a bunch of PO2 textures used for filtering + * @type {Object} + */ + this.texturePool = {}; + + /** + * a pool for storing filter states, save us creating new ones each tick + * @type {Array} + */ + this.statePool = []; + + /** + * A very simple geometry used when drawing a filter effect to the screen + * @type {Quad} + */ + this.quad = new Quad(); + + this.quadUv = new QuadUv(); + + /** + * Temporary rect for maths + * @type {PIXI.Rectangle} + */ + this.tempRect = new Rectangle(); + + this.activeState = {}; + + /** + * this uniform group is attached to filter uniforms when used + * @type {UniformGroup} + */ + this.globalUniforms = new UniformGroup({ + sourceFrame: this.tempRect, + destinationFrame: this.tempRect, + + // legacy variables + filterArea: new Float32Array(4), + filterClamp: new Float32Array(4), + }, true); + } + + /** + * Adds a new filter to the System. + * + * @param {PIXI.DisplayObject} target - The target of the filter to render. + * @param {PIXI.Filter[]} filters - The filters to apply. + */ + push(target, filters) + { + const renderer = this.renderer; + const filterStack = this.renderer.renderTexture.defaultFilterStack; + const state = this.statePool.pop() || new FilterState(); + + let resolution = filters[0].resolution; + let padding = filters[0].padding; + let autoFit = filters[0].autoFit; + let legacy = filters[0].legacy; + + for (let i = 1; i < filters.length; i++) + { + const filter = filters[i]; + + // lets use the lowest resolution.. + resolution = Math.min(resolution, filter.resolution); + // and the largest amount of padding! + padding = Math.max(padding, filter.padding); + // only auto fit if all filters are autofit + autoFit = autoFit || filter.autoFit; + + legacy = legacy || filter.legacy; + } + + filterStack.push(state); + + state.resolution = resolution; + + state.legacy = legacy; + + // round to whole number based on resolution + // TODO move that to the shader too? + state.sourceFrame = target.filterArea || target.getBounds(true); + + state.sourceFrame.pad(padding); + + if (autoFit) + { + state.sourceFrame.fit(this.renderer.renderTexture.destinationFrame); + } + + state.sourceFrame.round(resolution); + + state.renderTexture = this.getPotFilterTexture(state.sourceFrame.width, state.sourceFrame.height, resolution); + state.filters = filters; + + state.destinationFrame.width = state.renderTexture.width; + state.destinationFrame.height = state.renderTexture.height; + + state.renderTexture.filterFrame = state.sourceFrame; + renderer.renderTexture.bind(state.renderTexture, state.sourceFrame);// /, state.destinationFrame); + renderer.renderTexture.clear(); + } + + /** + * Pops off the filter and applies it. + * + */ + pop() + { + const renderer = this.renderer; + const filterStack = renderer.renderTexture.defaultFilterStack; + const state = filterStack.pop(); + const filters = state.filters; + + this.activeState = state; + + const globalUniforms = this.globalUniforms.uniforms; + + globalUniforms.sourceFrame = state.sourceFrame; + globalUniforms.destinationFrame = state.destinationFrame; + globalUniforms.resolution = state.resolution; + + // only update the rect if its legacy.. + if (state.legacy) + { + const filterArea = globalUniforms.filterArea; + const filterClamp = globalUniforms.filterClamp; + + filterArea[0] = state.destinationFrame.width; + filterArea[1] = state.destinationFrame.height; + filterArea[2] = state.sourceFrame.x; + filterArea[3] = state.sourceFrame.y; + + filterClamp[0] = 0.5 / state.resolution / state.destinationFrame.width; + filterClamp[1] = 0.5 / state.resolution / state.destinationFrame.height; + filterClamp[2] = (state.sourceFrame.width - 0.5) / state.resolution / state.destinationFrame.width; + filterClamp[3] = (state.sourceFrame.height - 0.5) / state.resolution / state.destinationFrame.height; + } + + this.globalUniforms.update(); + + const lastState = filterStack[filterStack.length - 1]; + + if (filters.length === 1) + { + filters[0].apply(this, state.renderTexture, lastState.renderTexture, false, state); + + this.returnFilterTexture(state.renderTexture); + } + else + { + let flip = state.renderTexture; + let flop = this.getPotFilterTexture( + flip.width, + flip.height, + state.resolution + ); + + flop.filterFrame = flip.filterFrame; + + let i = 0; + + for (i = 0; i < filters.length - 1; ++i) + { + filters[i].apply(this, flip, flop, true, state); + + const t = flip; + + flip = flop; + flop = t; + } + + filters[i].apply(this, flip, lastState.renderTexture, false, state); + + this.returnFilterTexture(flip); + this.returnFilterTexture(flop); + } + + this.statePool.push(state); + } + + /** + * Draws a filter. + * + * @param {PIXI.Filter} filter - The filter to draw. + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + * @param {boolean} clear - Should the output be cleared before rendering to it + */ + applyFilter(filter, input, output, clear) + { + const renderer = this.renderer; + + renderer.renderTexture.bind(output, output ? output.filterFrame : null); + + if (clear) + { + // gl.disable(gl.SCISSOR_TEST); + renderer.renderTexture.clear(); + // gl.enable(gl.SCISSOR_TEST); + } + + // set the uniforms.. + filter.uniforms.uSampler = input; + filter.uniforms.filterGlobals = this.globalUniforms; + + // TODO make it so that the order of this does not matter.. + // because it does at the moment cos of global uniforms. + // they need to get resynced + + renderer.state.setState(filter.state); + renderer.shader.bind(filter); + + if (filter.legacy) + { + this.quadUv.map(input._frame, input.filterFrame); + + renderer.geometry.bind(this.quadUv); + renderer.geometry.draw(DRAW_MODES.TRIANGLES); + } + else + { + renderer.geometry.bind(this.quad); + renderer.geometry.draw(DRAW_MODES.TRIANGLE_STRIP); + } + } + + /** + * Calculates the mapped matrix. + * + * TODO playing around here.. this is temporary - (will end up in the shader) + * this returns a matrix that will normalise map filter cords in the filter to screen space + * + * @param {PIXI.Matrix} outputMatrix - the matrix to output to. + * @return {PIXI.Matrix} The mapped matrix. + */ + calculateScreenSpaceMatrix(outputMatrix) + { + const currentState = this.activeState; + + return filterTransforms.calculateScreenSpaceMatrix( + outputMatrix, + currentState.sourceFrame, + currentState.destinationFrame + ); + } + + /** + * This will map the filter coord so that a texture can be used based on the transform of a sprite + * + * @param {PIXI.Matrix} outputMatrix - The matrix to output to. + * @param {PIXI.Sprite} sprite - The sprite to map to. + * @return {PIXI.Matrix} The mapped matrix. + */ + calculateSpriteMatrix(outputMatrix, sprite) + { + const currentState = this.activeState; + + return filterTransforms.calculateSpriteMatrix( + outputMatrix, + currentState.sourceFrame, + currentState.destinationFrame, + sprite + ); + } + + /** + * Destroys this Filter System. + * + * @param {boolean} [contextLost=false] context was lost, do not free shaders + * + */ + destroy(contextLost = false) + { + if (!contextLost) + { + this.emptyPool(); + } + else + { + this.texturePool = {}; + } + } + + /** + * Gets a Power-of-Two render texture. + * + * TODO move to a seperate class could be on renderer? + * also - could cause issue with multiple contexts? + * + * @private + * @param {WebGLRenderingContext} gl - The webgl rendering context + * @param {number} minWidth - The minimum width of the render target. + * @param {number} minHeight - The minimum height of the render target. + * @param {number} resolution - The resolution of the render target. + * @return {PIXI.RenderTarget} The new render target. + */ + getPotFilterTexture(minWidth, minHeight, resolution) + { + minWidth = bitTwiddle.nextPow2(minWidth); + minHeight = bitTwiddle.nextPow2(minHeight); + resolution = resolution || 1; + + const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + + if (!this.texturePool[key]) + { + this.texturePool[key] = []; + } + + let renderTexture = this.texturePool[key].pop(); + + if (!renderTexture) + { + // temporary bypass cache.. + // internally - this will cause a texture to be bound.. + renderTexture = RenderTexture.create({ + width: minWidth, + height: minHeight, + resolution, + }); + } + + return renderTexture; + } + + /** + * Gets extra render texture to use inside current filter + * + * @param {number} resolution resolution of the renderTexture + * @returns {PIXI.RenderTexture} + */ + getFilterTexture(resolution) + { + const rt = this.activeState.renderTexture; + + const filterTexture = this.getPotFilterTexture(rt.width, rt.height, resolution || rt.baseTexture.resolution); + + filterTexture.filterFrame = rt.filterFrame; + + return filterTexture; + } + + /** + * Frees a render texture back into the pool. + * + * @param {PIXI.RenderTarget} renderTexture - The renderTarget to free + */ + returnFilterTexture(renderTexture) + { + renderTexture.filterFrame = null; + + const base = renderTexture.baseTexture; + + const minWidth = base.width; + const minHeight = base.height; + + const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + + this.texturePool[key].push(renderTexture); + } + + /** + * Empties the texture pool. + * + */ + emptyPool() + { + for (const i in this.texturePool) + { + const textures = this.texturePool[i]; + + if (textures) + { + for (let j = 0; j < textures.length; j++) + { + textures[j].destroy(true); + } + } + } + + this.texturePool = {}; + } +} diff --git a/packages/core/src/filters/defaultFilter.frag b/packages/core/src/filters/defaultFilter.frag new file mode 100644 index 0000000..a14c1be --- /dev/null +++ b/packages/core/src/filters/defaultFilter.frag @@ -0,0 +1,22 @@ +varying vec2 vTextureCoord; +varying vec2 vFilterCoord; + +uniform sampler2D uSampler; +uniform sampler2D filterSampler; + +void main(void){ + vec4 masky = texture2D(filterSampler, vFilterCoord); + vec4 sample = texture2D(uSampler, vTextureCoord); + vec4 color; + if(mod(vFilterCoord.x, 1.0) > 0.5) + { + color = vec4(1.0, 0.0, 0.0, 1.0); + } + else + { + color = vec4(0.0, 1.0, 0.0, 1.0); + } + // gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0); + gl_FragColor = mix(sample, masky, 0.5); + gl_FragColor *= sample.a; +} diff --git a/packages/core/src/filters/defaultFilter.vert b/packages/core/src/filters/defaultFilter.vert new file mode 100644 index 0000000..e1febca --- /dev/null +++ b/packages/core/src/filters/defaultFilter.vert @@ -0,0 +1,14 @@ +attribute vec2 aVertexPosition; +attribute vec2 aTextureCoord; + +uniform mat3 projectionMatrix; +uniform mat3 filterMatrix; + +varying vec2 vTextureCoord; +varying vec2 vFilterCoord; + +void main(void){ + gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); + vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy; + vTextureCoord = aTextureCoord ; +} diff --git a/packages/core/src/filters/filterTransforms.js b/packages/core/src/filters/filterTransforms.js new file mode 100644 index 0000000..87a3cd6 --- /dev/null +++ b/packages/core/src/filters/filterTransforms.js @@ -0,0 +1,50 @@ +import { Matrix } from '@pixi/math'; + +/** + * Calculates the mapped matrix + * @param filterArea {Rectangle} The filter area + * @param sprite {Sprite} the target sprite + * @param outputMatrix {Matrix} @alvin + * @private + */ +// this returns a matrix that will normalise map filter cords in the filter to screen space +export function calculateScreenSpaceMatrix(outputMatrix, filterArea, textureSize) +{ + // TODO unwrap? + const mappedMatrix = outputMatrix.identity(); + + mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); + + mappedMatrix.scale(textureSize.width, textureSize.height); + + return mappedMatrix; +} + +export function calculateNormalizedScreenSpaceMatrix(outputMatrix, filterArea, textureSize) +{ + const mappedMatrix = outputMatrix.identity(); + + mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); + + const translateScaleX = (textureSize.width / filterArea.width); + const translateScaleY = (textureSize.height / filterArea.height); + + mappedMatrix.scale(translateScaleX, translateScaleY); + + return mappedMatrix; +} + +// this will map the filter coord so that a texture can be used based on the transform of a sprite +export function calculateSpriteMatrix(outputMatrix, filterArea, textureSize, sprite) +{ + const orig = sprite._texture.orig; + const mappedMatrix = outputMatrix.set(textureSize.width, 0, 0, textureSize.height, filterArea.x, filterArea.y); + const worldTransform = sprite.worldTransform.copyTo(Matrix.TEMP_MATRIX); + + worldTransform.invert(); + mappedMatrix.prepend(worldTransform); + mappedMatrix.scale(1.0 / orig.width, 1.0 / orig.height); + mappedMatrix.translate(sprite.anchor.x, sprite.anchor.y); + + return mappedMatrix; +} diff --git a/packages/core/src/filters/spriteMask/SpriteMaskFilter.js b/packages/core/src/filters/spriteMask/SpriteMaskFilter.js new file mode 100644 index 0000000..91697b7 --- /dev/null +++ b/packages/core/src/filters/spriteMask/SpriteMaskFilter.js @@ -0,0 +1,63 @@ +import Filter from '../Filter'; +import { Matrix } from '@pixi/math'; +import vertex from './spriteMaskFilter.vert'; +import fragment from './spriteMaskFilter.frag'; +import { default as TextureMatrix } from '../../textures/TextureMatrix'; + +/** + * The SpriteMaskFilter class + * + * @class + * @extends PIXI.Filter + * @memberof PIXI + */ +export default class SpriteMaskFilter extends Filter +{ + /** + * @param {PIXI.Sprite} sprite - the target sprite + */ + constructor(sprite) + { + const maskMatrix = new Matrix(); + + super(vertex, fragment); + + sprite.renderable = false; + + this.maskSprite = sprite; + this.maskMatrix = maskMatrix; + } + + /** + * Applies the filter + * + * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + */ + apply(filterManager, input, output) + { + const maskSprite = this.maskSprite; + const tex = this.maskSprite.texture; + + if (!tex.valid) + { + return; + } + if (!tex.transform) + { + // margin = 0.0, let it bleed a bit, shader code becomes easier + // assuming that atlas textures were made with 1-pixel padding + tex.transform = new TextureMatrix(tex, 0.0); + } + tex.transform.update(); + + this.uniforms.mask = tex; + this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite) + .prepend(tex.transform.mapCoord); + this.uniforms.alpha = maskSprite.worldAlpha; + this.uniforms.maskClamp = tex.transform.uClampFrame; + + filterManager.applyFilter(this, input, output); + } +} diff --git a/packages/core/src/filters/spriteMask/spriteMaskFilter.frag b/packages/core/src/filters/spriteMask/spriteMaskFilter.frag new file mode 100644 index 0000000..0e4aef8 --- /dev/null +++ b/packages/core/src/filters/spriteMask/spriteMaskFilter.frag @@ -0,0 +1,23 @@ +varying vec2 vMaskCoord; +varying vec2 vTextureCoord; + +uniform sampler2D uSampler; +uniform sampler2D mask; +uniform float alpha; +uniform vec4 maskClamp; + +void main(void) +{ + float clip = step(3.5, + step(maskClamp.x, vMaskCoord.x) + + step(maskClamp.y, vMaskCoord.y) + + step(vMaskCoord.x, maskClamp.z) + + step(vMaskCoord.y, maskClamp.w)); + + vec4 original = texture2D(uSampler, vTextureCoord); + vec4 masky = texture2D(mask, vMaskCoord); + + original *= (masky.r * masky.a * alpha * clip); + + gl_FragColor = original; +} diff --git a/packages/core/src/filters/spriteMask/spriteMaskFilter.vert b/packages/core/src/filters/spriteMask/spriteMaskFilter.vert new file mode 100644 index 0000000..d23a30c --- /dev/null +++ b/packages/core/src/filters/spriteMask/spriteMaskFilter.vert @@ -0,0 +1,16 @@ +attribute vec2 aVertexPosition; +attribute vec2 aTextureCoord; + +uniform mat3 projectionMatrix; +uniform mat3 otherMatrix; + +varying vec2 vMaskCoord; +varying vec2 vTextureCoord; + +void main(void) +{ + gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); + + vTextureCoord = aTextureCoord; + vMaskCoord = ( otherMatrix * vec3( aTextureCoord, 1.0) ).xy; +} diff --git a/packages/core/src/framebuffer/FrameBuffer.js b/packages/core/src/framebuffer/FrameBuffer.js new file mode 100644 index 0000000..84e9fcc --- /dev/null +++ b/packages/core/src/framebuffer/FrameBuffer.js @@ -0,0 +1,106 @@ +import Texture from '../textures/BaseTexture'; +import { FORMATS, TYPES } from '@pixi/constants'; + +/** + * Frame buffer + * @class + * @memberof PIXI + */ +export default class FrameBuffer +{ + constructor(width, height) + { + this.width = width || 100; + this.height = height || 100; + + this.stencil = false; + this.depth = false; + + this.dirtyId = 0; + this.dirtyFormat = 0; + this.dirtySize = 0; + + this.depthTexture = null; + this.colorTextures = []; + + this.glFrameBuffers = {}; + } + + get colorTexture() + { + return this.colorTextures[0]; + } + + addColorTexture(index, texture) + { + // TODO add some validation to the texture - same width / height etc? + this.colorTextures[index || 0] = texture || new Texture(null, { scaleMode: 0, + resolution: 1, + mipmap: false, + width: this.width, + height: this.height });// || new Texture(); + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + addDepthTexture(texture) + { + /* eslint-disable max-len */ + this.depthTexture = texture || new Texture(null, { scaleMode: 0, + resolution: 1, + width: this.width, + height: this.height, + mipmap: false, + format: FORMATS.DEPTH_COMPONENT, + type: TYPES.UNSIGNED_SHORT });// UNSIGNED_SHORT; + /* eslint-disable max-len */ + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + enableDepth() + { + this.depth = true; + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + enableStencil() + { + this.stencil = true; + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + resize(width, height) + { + if (width === this.width && height === this.height) return; + + this.width = width; + this.height = height; + + this.dirtyId++; + this.dirtySize++; + + for (let i = 0; i < this.colorTextures.length; i++) + { + this.colorTextures[i].setSize(width, height); + } + + if (this.depthTexture) + { + this.depthTexture.setSize(width, height); + } + } +} diff --git a/packages/core/src/framebuffer/FramebufferSystem.js b/packages/core/src/framebuffer/FramebufferSystem.js new file mode 100644 index 0000000..2cc2437 --- /dev/null +++ b/packages/core/src/framebuffer/FramebufferSystem.js @@ -0,0 +1,246 @@ +import System from '../System'; +import { Rectangle } from '@pixi/math'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class FramebufferSystem extends System +{ + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + contextChange() + { + this.gl = this.renderer.gl; + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + this.current = null; + this.viewport = new Rectangle(); + + this.drawBufferExtension = this.renderer.context.extensions.drawBuffers; + } + + bind(framebuffer, frame) + { + const gl = this.gl; + + this.current = framebuffer; + + if (framebuffer) + { + // TODO cacheing layer! + + const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID] || this.initFramebuffer(framebuffer); + + gl.bindFramebuffer(gl.FRAMEBUFFER, fbo.framebuffer); + // makesure all textures are unbound.. + + // now check for updates... + if (fbo.dirtyId !== framebuffer.dirtyId) + { + fbo.dirtyId = framebuffer.dirtyId; + + if (fbo.dirtyFormat !== framebuffer.dirtyFormat) + { + fbo.dirtyFormat = framebuffer.dirtyFormat; + this.updateFramebuffer(framebuffer); + } + else if (fbo.dirtySize !== framebuffer.dirtySize) + { + fbo.dirtySize = framebuffer.dirtySize; + this.resizeFramebuffer(framebuffer); + } + } + + for (let i = 0; i < framebuffer.colorTextures.length; i++) + { + if (framebuffer.colorTextures[i].texturePart) + { + this.renderer.texture.unbind(framebuffer.colorTextures[i].texture); + } + else + { + this.renderer.texture.unbind(framebuffer.colorTextures[i]); + } + } + + if (framebuffer.depthTexture) + { + this.renderer.texture.unbind(framebuffer.depthTexture); + } + + if (frame) + { + this.setViewport(frame.x, frame.y, frame.width, frame.height); + } + else + { + this.setViewport(0, 0, framebuffer.width, framebuffer.height); + } + } + else + { + gl.bindFramebuffer(gl.FRAMEBUFFER, null); + + if (frame) + { + this.setViewport(frame.x, frame.y, frame.width, frame.height); + } + else + { + this.setViewport(0, 0, this.renderer.width, this.renderer.height); + } + } + } + + setViewport(x, y, width, height) + { + const v = this.viewport; + + if (v.width !== width || v.height !== height || v.x !== x || v.y !== y) + { + v.x = x; + v.y = y; + v.width = width; + v.height = height; + + this.gl.viewport(x, y, width, height); + } + } + + get size() + { + if (this.current) + { + // TODO store temp + return { x: 0, y: 0, width: this.current.width, height: this.current.height }; + } + + return { x: 0, y: 0, width: this.renderer.width, height: this.renderer.height }; + } + + clear(r, g, b, a) + { + const gl = this.gl; + + // TODO clear color can be set only one right? + gl.clearColor(r, g, b, a); + gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); + } + + // private functions... + + initFramebuffer(framebuffer) + { + const gl = this.gl; + + // TODO - make this a class? + const fbo = { + framebuffer: gl.createFramebuffer(), + stencil: null, + dirtyId: 0, + dirtyFormat: 0, + dirtySize: 0, + }; + + framebuffer.glFrameBuffers[this.CONTEXT_UID] = fbo; + + return fbo; + } + + resizeFramebuffer(framebuffer) + { + const gl = this.gl; + + if (framebuffer.stencil || framebuffer.depth) + { + gl.bindRenderbuffer(gl.RENDERBUFFER, this.stencil); + gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); + } + } + + updateFramebuffer(framebuffer) + { + const gl = this.gl; + + const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID]; + + // bind the color texture + const colorTextures = framebuffer.colorTextures; + + let count = colorTextures.length; + + if (!this.drawBufferExtension) + { + count = Math.min(count, 1); + } + + const activeTextures = []; + + for (let i = 0; i < count; i++) + { + const texture = framebuffer.colorTextures[i]; + + if (texture.texturePart) + { + this.renderer.texture.bind(texture.texture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.COLOR_ATTACHMENT0 + i, + gl.TEXTURE_CUBE_MAP_NEGATIVE_X + texture.side, + texture.texture._glTextures[this.CONTEXT_UID].texture, + 0); + } + else + { + this.renderer.texture.bind(texture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.COLOR_ATTACHMENT0 + i, + gl.TEXTURE_2D, + texture._glTextures[this.CONTEXT_UID].texture, + 0); + } + + activeTextures.push(gl.COLOR_ATTACHMENT0 + i); + } + + if (this.drawBufferExtension && activeTextures.length > 1) + { + this.drawBufferExtension.drawBuffersWEBGL(activeTextures); + } + + if (framebuffer.depthTexture) + { + const depthTextureExt = this.renderer.context.extensions.depthTexture; + + if (depthTextureExt) + { + const depthTexture = framebuffer.depthTexture; + + this.renderer.texture.bind(depthTexture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.DEPTH_ATTACHMENT, + gl.TEXTURE_2D, + depthTexture._glTextures[this.CONTEXT_UID].texture, + 0); + } + } + + if (framebuffer.stencil || framebuffer.depth) + { + fbo.stencil = gl.createRenderbuffer(); + + gl.bindRenderbuffer(gl.RENDERBUFFER, fbo.stencil); + + // TODO.. this is depth AND stencil? + gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, fbo.stencil); + gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); + // fbo.enableStencil(); + } + } +} diff --git a/packages/core/src/geometry/GLBuffer.js b/packages/core/src/geometry/GLBuffer.js new file mode 100644 index 0000000..0477e8a --- /dev/null +++ b/packages/core/src/geometry/GLBuffer.js @@ -0,0 +1,9 @@ +export default class GLBuffer +{ + constructor(buffer) + { + this.buffer = buffer; + this.updateID = -1; + this.byteLength = -1; + } +} diff --git a/packages/core/src/geometry/GeometrySystem.js b/packages/core/src/geometry/GeometrySystem.js new file mode 100644 index 0000000..959f827 --- /dev/null +++ b/packages/core/src/geometry/GeometrySystem.js @@ -0,0 +1,388 @@ +import System from '../System'; +import GLBuffer from './GLBuffer'; + +const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class GeometrySystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this._activeGeometry = null; + this._activeVao = null; + + this.hasVao = true; + this.hasInstance = true; + } + + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + contextChange() + { + const gl = this.gl = this.renderer.gl; + + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + + // webgl2 + if (!gl.createVertexArray) + { + // webgl 1! + let nativeVaoExtension = this.renderer.context.extensions.vertexArrayObject; + + if (this.renderer.legacy) + { + nativeVaoExtension = null; + } + + if (nativeVaoExtension) + { + gl.createVertexArray = () => + nativeVaoExtension.createVertexArrayOES(); + + gl.bindVertexArray = (vao) => + nativeVaoExtension.bindVertexArrayOES(vao); + + gl.deleteVertexArray = (vao) => + nativeVaoExtension.deleteVertexArrayOES(vao); + } + else + { + this.hasVao = false; + gl.createVertexArray = () => + { + // empty + }; + + gl.bindVertexArray = () => + { + // empty + }; + + gl.deleteVertexArray = () => + { + // empty + }; + } + } + + if (!gl.vertexAttribDivisor) + { + const instanceExt = gl.getExtension('ANGLE_instanced_arrays'); + + if (instanceExt) + { + gl.vertexAttribDivisor = (a, b) => + instanceExt.vertexAttribDivisorANGLE(a, b); + + gl.drawElementsInstanced = (a, b, c, d, e) => + instanceExt.drawElementsInstancedANGLE(a, b, c, d, e); + + gl.drawArraysInstanced = (a, b, c, d) => + instanceExt.drawArraysInstancedANGLE(a, b, c, d); + } + else + { + this.hasInstance = false; + } + } + } + + /** + * Binds geometry so that is can be drawn. Creating a Vao if required + * @private + * @param {PIXI.Geometry} geometry instance of geometry to bind + */ + bind(geometry, shader) + { + shader = shader || this.renderer.shader.shader; + + const gl = this.gl; + + // not sure the best way to address this.. + // currently different shaders require different VAOs for the same geometry + // Still mulling over the best way to solve this one.. + // will likely need to modify the shader attribute locations at run time! + let vaos = geometry.glVertexArrayObjects[this.CONTEXT_UID]; + + if (!vaos) + { + geometry.glVertexArrayObjects[this.CONTEXT_UID] = vaos = {}; + } + + const vao = vaos[shader.program.id] || this.initGeometryVao(geometry, shader.program); + + this._activeGeometry = geometry; + + if (this._activeVao !== vao) + { + this._activeVao = vao; + + if (this.hasVao) + { + gl.bindVertexArray(vao); + } + else + { + this.activateVao(geometry, shader.program); + } + } + + // TODO - optimise later! + // don't need to loop through if nothing changed! + // maybe look to add an 'autoupdate' to geometry? + this.updateBuffers(); + } + + reset() + { + this.unbind(); + } + + updateBuffers() + { + const geometry = this._activeGeometry; + const gl = this.gl; + + for (let i = 0; i < geometry.buffers.length; i++) + { + const buffer = geometry.buffers[i]; + + const glBuffer = buffer._glBuffers[this.CONTEXT_UID]; + + if (buffer._updateID !== glBuffer.updateID) + { + glBuffer.updateID = buffer._updateID; + + // TODO can cache this on buffer! maybe added a getter / setter? + const type = buffer.index ? gl.ELEMENT_ARRAY_BUFFER : gl.ARRAY_BUFFER; + const drawType = buffer.static ? gl.STATIC_DRAW : gl.DYNAMIC_DRAW; + + gl.bindBuffer(type, glBuffer.buffer); + + if (glBuffer.byteLength >= buffer.data.byteLength) + { + // offset is always zero for now! + gl.bufferSubData(type, 0, buffer.data); + } + else + { + gl.bufferData(type, buffer.data, drawType); + } + } + } + } + + checkCompatability(geometry, program) + { + // geometry must have at least all the attributes that the shader requires. + const geometryAttributes = geometry.attributes; + const shaderAttributes = program.attributeData; + + for (const j in shaderAttributes) + { + if (!geometryAttributes[j]) + { + throw new Error(`shader and geometry incompatible, geometry missing the "${j}" attribute`); + } + } + } + + /** + * Creates a Vao with the same structure as the geometry and stores it on the geometry. + * @private + * @param {PIXI.Geometry} geometry instance of geometry to to generate Vao for + */ + initGeometryVao(geometry, program) + { + this.checkCompatability(geometry, program); + + const gl = this.gl; + const CONTEXT_UID = this.CONTEXT_UID; + const buffers = geometry.buffers; + const attributes = geometry.attributes; + + const tempStride = {}; + const tempStart = {}; + + for (const j in buffers) + { + tempStride[j] = 0; + tempStart[j] = 0; + } + + for (const j in attributes) + { + if (!attributes[j].size && program.attributeData[j]) + { + attributes[j].size = program.attributeData[j].size; + } + + tempStride[attributes[j].buffer] += attributes[j].size * byteSizeMap[attributes[j].type]; + } + + for (const j in attributes) + { + const attribute = attributes[j]; + const attribSize = attribute.size; + + if (attribute.stride === undefined) + { + if (tempStride[attribute.buffer] === attribSize * byteSizeMap[attribute.type]) + { + attribute.stride = 0; + } + else + { + attribute.stride = tempStride[attribute.buffer]; + } + } + + if (attribute.start === undefined) + { + attribute.start = tempStart[attribute.buffer]; + + tempStart[attribute.buffer] += attribSize * byteSizeMap[attribute.type]; + } + } + + // first update - and create the buffers! + // only create a gl buffer if it actually gets + for (let i = 0; i < buffers.length; i++) + { + const buffer = buffers[i]; + + if (!buffer._glBuffers[CONTEXT_UID]) + { + buffer._glBuffers[CONTEXT_UID] = new GLBuffer(gl.createBuffer()); + } + } + + // TODO - maybe make this a data object? + // lets wait to see if we need to first! + const vao = gl.createVertexArray(); + + gl.bindVertexArray(vao); + + this.activateVao(geometry, program); + + gl.bindVertexArray(null); + + // add it to the cache! + geometry.glVertexArrayObjects[this.CONTEXT_UID][program.id] = vao; + + return vao; + } + + activateVao(geometry, program) + { + const gl = this.gl; + const CONTEXT_UID = this.CONTEXT_UID; + const buffers = geometry.buffers; + const attributes = geometry.attributes; + + if (geometry.indexBuffer) + { + // first update the index buffer if we have one.. + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, geometry.indexBuffer._glBuffers[CONTEXT_UID].buffer); + } + + let lastBuffer = null; + + // add a new one! + for (const j in attributes) + { + const attribute = attributes[j]; + const buffer = buffers[attribute.buffer]; + const glBuffer = buffer._glBuffers[CONTEXT_UID]; + + if (program.attributeData[j]) + { + if (lastBuffer !== glBuffer) + { + gl.bindBuffer(gl.ARRAY_BUFFER, glBuffer.buffer); + + lastBuffer = glBuffer; + } + + const location = program.attributeData[j].location; + + // TODO introduce state again + // we can optimise this for older devices that have no VAOs + gl.enableVertexAttribArray(location); + + gl.vertexAttribPointer(location, + attribute.size, + attribute.type || gl.FLOAT, + attribute.normalized, + attribute.stride, + attribute.start); + + if (attribute.instance) + { + // TODO calculate instance count based of this... + if (this.hasInstance) + { + gl.vertexAttribDivisor(location, 1); + } + else + { + throw new Error('geometry error, GPU Instancing is not supported on this device'); + } + } + } + } + } + + draw(type, size, start, instanceCount) + { + const gl = this.gl; + const geometry = this._activeGeometry; + + // TODO.. this should not change so maybe cache the function? + + if (geometry.indexBuffer) + { + if (geometry.instanced) + { + /* eslint-disable max-len */ + gl.drawElementsInstanced(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2, instanceCount || 1); + /* eslint-enable max-len */ + } + else + { + gl.drawElements(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2); + } + } + else + if (geometry.instanced) + { + // TODO need a better way to calculate size.. + gl.drawArraysInstanced(type, start, size || geometry.getSize(), instanceCount || 1); + } + else + { + gl.drawArrays(type, start, size || geometry.getSize()); + } + + return this; + } + + unbind() + { + this.gl.bindVertexArray(null); + this._activeVao = null; + this._activeGeometry = null; + } +} diff --git a/packages/core/src/geometry/utils/setVertexAttribArrays.js b/packages/core/src/geometry/utils/setVertexAttribArrays.js new file mode 100644 index 0000000..23b6959 --- /dev/null +++ b/packages/core/src/geometry/utils/setVertexAttribArrays.js @@ -0,0 +1,54 @@ +// var GL_MAP = {}; + +/** + * @param gl {WebGLRenderingContext} The current WebGL context + * @param attribs {*} + * @param state {*} + */ +export default function setVertexAttribArrays(gl, attribs, state) +{ + let i; + + if (state) + { + const tempAttribState = state.tempAttribState; + const attribState = state.attribState; + + for (i = 0; i < tempAttribState.length; i++) + { + tempAttribState[i] = false; + } + + // set the new attribs + for (i = 0; i < attribs.length; i++) + { + tempAttribState[attribs[i].attribute.location] = true; + } + + for (i = 0; i < attribState.length; i++) + { + if (attribState[i] !== tempAttribState[i]) + { + attribState[i] = tempAttribState[i]; + + if (state.attribState[i]) + { + gl.enableVertexAttribArray(i); + } + else + { + gl.disableVertexAttribArray(i); + } + } + } + } + else + { + for (i = 0; i < attribs.length; i++) + { + const attrib = attribs[i]; + + gl.enableVertexAttribArray(attrib.attribute.location); + } + } +} diff --git a/packages/core/src/index.js b/packages/core/src/index.js index 58c456a..fd1f547 100644 --- a/packages/core/src/index.js +++ b/packages/core/src/index.js @@ -1,30 +1,30 @@ import * as resources from './textures/resources'; -import * as systems from './renderers/systems'; +import * as systems from './systems'; -export { resources }; export { systems }; +export { resources }; -export { default as Renderer } from './renderers/Renderer'; -export { default as AbstractRenderer } from './renderers/AbstractRenderer'; -export { default as FrameBuffer } from './textures/FrameBuffer'; +export { default as System } from './System'; +export { default as Renderer } from './Renderer'; +export { default as AbstractRenderer } from './AbstractRenderer'; +export { default as FrameBuffer } from './framebuffer/FrameBuffer'; export { default as CubeTexture } from './textures/CubeTexture'; export { default as BaseTexture } from './textures/BaseTexture'; export { default as Texture } from './textures/Texture'; export { default as TextureMatrix } from './textures/TextureMatrix'; -export { default as RenderTexture } from './textures/RenderTexture'; -export { default as BaseRenderTexture } from './textures/BaseRenderTexture'; +export { default as RenderTexture } from './renderTexture/RenderTexture'; +export { default as BaseRenderTexture } from './renderTexture/BaseRenderTexture'; export { default as TextureUvs } from './textures/TextureUvs'; -export { default as State } from './renderers/State'; -export { default as ObjectRenderer } from './renderers/utils/ObjectRenderer'; -export { default as Quad } from './renderers/utils/Quad'; -export { default as QuadUv } from './renderers/utils/QuadUv'; -export { default as checkMaxIfStatmentsInShader } from './renderers/utils/checkMaxIfStatmentsInShader'; +export { default as State } from './state/State'; +export { default as ObjectRenderer } from './batch/ObjectRenderer'; +export { default as Quad } from './utils/Quad'; +export { default as QuadUv } from './utils/QuadUv'; +export { default as checkMaxIfStatmentsInShader } from './shader/utils/checkMaxIfStatmentsInShader'; export { default as Shader } from './shader/Shader'; export { default as Program } from './shader/Program'; export { default as UniformGroup } from './shader/UniformGroup'; -export { default as SpriteMaskFilter } from './renderers/filters/spriteMask/SpriteMaskFilter'; -export { default as Filter } from './renderers/filters/Filter'; +export { default as SpriteMaskFilter } from './filters/spriteMask/SpriteMaskFilter'; +export { default as Filter } from './filters/Filter'; export { default as Attribute } from './geometry/Attribute'; export { default as Buffer } from './geometry/Buffer'; export { default as Geometry } from './geometry/Geometry'; - diff --git a/packages/core/src/mask/MaskSystem.js b/packages/core/src/mask/MaskSystem.js new file mode 100644 index 0000000..99174c2 --- /dev/null +++ b/packages/core/src/mask/MaskSystem.js @@ -0,0 +1,199 @@ +import System from '../System'; +import AlphaMaskFilter from '../filters/spriteMask/SpriteMaskFilter'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class MaskSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + // TODO - we don't need both! + this.scissor = false; + this.scissorData = null; + this.scissorRenderTarget = null; + + this.enableScissor = false; + + this.alphaMaskPool = []; + this.alphaMaskIndex = 0; + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.DisplayObject} target - Display Object to push the mask to + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + push(target, maskData) + { + // TODO the root check means scissor rect will not + // be used on render textures more info here: + // https://github.com/pixijs/pixi.js/pull/3545 + + if (maskData.texture) + { + this.pushSpriteMask(target, maskData); + } + else if (this.enableScissor + && !this.scissor + && this.renderer._activeRenderTarget.root + && !this.renderer.stencil.stencilMaskStack.length + && maskData.isFastRect()) + { + const matrix = maskData.worldTransform; + + let rot = Math.atan2(matrix.b, matrix.a); + + // use the nearest degree! + rot = Math.round(rot * (180 / Math.PI)); + + if (rot % 90) + { + this.pushStencilMask(maskData); + } + else + { + this.pushScissorMask(target, maskData); + } + } + else + { + this.pushStencilMask(maskData); + } + } + + /** + * Removes the last mask from the mask stack and doesn't return it. + * + * @param {PIXI.DisplayObject} target - Display Object to pop the mask from + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + pop(target, maskData) + { + if (maskData.texture) + { + this.popSpriteMask(target, maskData); + } + else if (this.enableScissor && !this.renderer.stencil.stencilMaskStack.length) + { + this.popScissorMask(target, maskData); + } + else + { + this.popStencilMask(target, maskData); + } + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.RenderTarget} target - Display Object to push the sprite mask to + * @param {PIXI.Sprite} maskData - Sprite to be used as the mask + */ + pushSpriteMask(target, maskData) + { + let alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; + + if (!alphaMaskFilter) + { + alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; + } + + alphaMaskFilter[0].resolution = this.renderer.resolution; + alphaMaskFilter[0].maskSprite = maskData; + + // TODO - may cause issues! + target.filterArea = maskData.getBounds(true); + + this.renderer.filterSystem.pushFilter(target, alphaMaskFilter); + + this.alphaMaskIndex++; + } + + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popSpriteMask() + { + this.renderer.filterSystem.popFilter(); + this.alphaMaskIndex--; + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + pushStencilMask(maskData) + { + this.renderer.batch.flush(); + this.renderer.stencil.pushStencil(maskData); + } + + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popStencilMask() + { + // this.renderer.currentRenderer.stop(); + this.renderer.stencil.popStencil(); + } + + /** + * + * @param {PIXI.DisplayObject} target - Display Object to push the mask to + * @param {PIXI.Graphics} maskData - The masking data. + */ + pushScissorMask(target, maskData) + { + maskData.renderable = true; + + const renderTarget = this.renderer._activeRenderTarget; + + const bounds = maskData.getBounds(); + + bounds.fit(renderTarget.size); + maskData.renderable = false; + + this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); + + const resolution = this.renderer.resolution; + + this.renderer.gl.scissor( + bounds.x * resolution, + (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, + bounds.width * resolution, + bounds.height * resolution + ); + + this.scissorRenderTarget = renderTarget; + this.scissorData = maskData; + this.scissor = true; + } + + /** + * + * + */ + popScissorMask() + { + this.scissorRenderTarget = null; + this.scissorData = null; + this.scissor = false; + + // must be scissor! + const gl = this.renderer.gl; + + gl.disable(gl.SCISSOR_TEST); + } +} diff --git a/packages/core/src/mask/StencilSystem.js b/packages/core/src/mask/StencilSystem.js new file mode 100644 index 0000000..62796a5 --- /dev/null +++ b/packages/core/src/mask/StencilSystem.js @@ -0,0 +1,133 @@ +import System from '../System'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class StencilSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + this.stencilMaskStack = []; + } + + /** + * Changes the mask stack that is used by this System. + * + * @param {PIXI.Graphics[]} stencilMaskStack - The mask stack + */ + setMaskStack(stencilMaskStack) + { + const gl = this.renderer.gl; + + if (stencilMaskStack.length !== this.stencilMaskStack.length) + { + if (stencilMaskStack.length === 0) + { + gl.disable(gl.STENCIL_TEST); + } + else + { + gl.enable(gl.STENCIL_TEST); + } + } + + this.stencilMaskStack = stencilMaskStack; + } + + /** + * Applies the Mask and adds it to the current stencil stack. @alvin + * + * @param {PIXI.Graphics} graphics - The mask + */ + pushStencil(graphics) + { + this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); + + // this.renderer._activeRenderTarget.attachStencilBuffer(); + + const gl = this.renderer.gl; + const prevMaskCount = this.stencilMaskStack.length; + + if (prevMaskCount === 0) + { + gl.enable(gl.STENCIL_TEST); + } + + this.stencilMaskStack.push(graphics); + + // Increment the refference stencil value where the new mask overlaps with the old ones. + gl.colorMask(false, false, false, false); + gl.stencilFunc(gl.EQUAL, prevMaskCount, this._getBitwiseMask()); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.INCR); + this.renderer.plugins.graphics.render(graphics); + + this._useCurrent(); + } + + /** + * Removes the last mask from the stencil stack. @alvin + */ + popStencil() + { + this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); + + const gl = this.renderer.gl; + const graphics = this.stencilMaskStack.pop(); + + if (this.stencilMaskStack.length === 0) + { + // the stack is empty! + gl.disable(gl.STENCIL_TEST); + gl.clear(gl.STENCIL_BUFFER_BIT); + gl.clearStencil(0); + } + else + { + // Decrement the refference stencil value where the popped mask overlaps with the other ones + gl.colorMask(false, false, false, false); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.DECR); + this.renderer.plugins.graphics.render(graphics); + + this._useCurrent(); + } + } + + /** + * Setup renderer to use the current stencil data. + */ + _useCurrent() + { + const gl = this.renderer.gl; + + gl.colorMask(true, true, true, true); + gl.stencilFunc(gl.EQUAL, this.stencilMaskStack.length, this._getBitwiseMask()); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP); + } + + /** + * Fill 1s equal to the number of acitve stencil masks. + * + * @return {number} The bitwise mask. + */ + _getBitwiseMask() + { + return (1 << this.stencilMaskStack.length) - 1; + } + + /** + * Destroys the mask stack. + * + */ + destroy() + { + super.destroy(this); + + this.stencilMaskStack = null; + } +} diff --git a/packages/core/src/AbstractRenderer.js b/packages/core/src/AbstractRenderer.js new file mode 100644 index 0000000..64b2eb1 --- /dev/null +++ b/packages/core/src/AbstractRenderer.js @@ -0,0 +1,353 @@ +import { hex2string, hex2rgb } from '@pixi/utils'; +import { Matrix, Rectangle } from '@pixi/math'; +import { RENDERER_TYPE } from '@pixi/constants'; +import { settings } from '@pixi/settings'; +import { Container } from '@pixi/display'; +import RenderTexture from './renderTexture/RenderTexture'; +import EventEmitter from 'eventemitter3'; + +const tempMatrix = new Matrix(); + +/** + * 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 EventEmitter + * @memberof PIXI + */ +export default class AbstractRenderer extends EventEmitter +{ + // eslint-disable-next-line valid-jsdoc + /** + * @param {string} system - The name of the system this renderer is for. + * @param {object} [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.transparent=false] - If the render view is transparent, default false + * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false + * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. The + * resolution of the renderer retina would be 2. + * @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 {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when rendering, + * stopping pixel interpolation. + */ + constructor(system, options, arg2, arg3) + { + super(); + + // Support for constructor(system, screenWidth, screenHeight, options) + if (typeof options === 'number') + { + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + }, arg3); + } + + // 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 = RENDERER_TYPE.UNKNOWN; + + /** + * Measurements of the screen. (0, 0, screenWidth, screenHeight) + * + * Its safe to use as filterArea or hitArea for 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 1 + */ + this.resolution = options.resolution || settings.RESOLUTION; + + /** + * Whether the render view is transparent + * + * @member {boolean} + */ + this.transparent = options.transparent; + + /** + * Whether css dimensions of canvas view should be resized to screen dimensions automatically + * + * @member {boolean} + */ + this.autoResize = options.autoResize || false; + + /** + * Tracks the blend modes useful for this renderer. + * + * @member {object} + */ + this.blendModes = null; + + /** + * 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; + + /** + * If true PixiJS will Math.floor() x/y values when rendering, stopping pixel interpolation. + * Handy for crisp pixel art and speed on legacy devices. + * + * @member {boolean} + */ + this.roundPixels = options.roundPixels; + + /** + * The background color as a number. + * + * @member {number} + * @private + */ + this._backgroundColor = 0x000000; + + /** + * The background color as an [R, G, B] array. + * + * @member {number[]} + * @private + */ + this._backgroundColorRgba = [0, 0, 0, 0]; + + /** + * The background color as a string. + * + * @member {string} + * @private + */ + this._backgroundColorString = '#000000'; + + this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter + + /** + * This temporary display object used as the parent of the currently being rendered item + * + * @member {PIXI.DisplayObject} + * @private + */ + this._tempDisplayObjectParent = new Container(); + + /** + * The last root object that the renderer tried to render. + * + * @member {PIXI.DisplayObject} + * @private + */ + this._lastObjectRendered = this._tempDisplayObjectParent; + + /** + * Collection of plugins. + * @readonly + * @member {object} + */ + this.plugins = {}; + } + + /** + * Initialize the plugins. + * + * @protected + * @param {object} staticMap - The dictionary of staticly saved plugins. + */ + initPlugins(staticMap) + { + 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() + { + return this.view.width; + } + + /** + * Same as view.height, actual number of pixels in the canvas by vertical + * + * @member {number} + * @readonly + * @default 600 + */ + get height() + { + return this.view.height; + } + + /** + * Resizes the screen and canvas to the specified width and height + * Canvas dimensions are multiplied by resolution + * + * @param {number} screenWidth - the new width of the screen + * @param {number} screenHeight - the new height of the screen + */ + resize(screenWidth, screenHeight) + { + this.screen.width = screenWidth; + this.screen.height = screenHeight; + + this.view.width = screenWidth * this.resolution; + this.view.height = screenHeight * this.resolution; + + if (this.autoResize) + { + this.view.style.width = `${screenWidth}px`; + this.view.style.height = `${screenHeight}px`; + } + } + + /** + * 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 {PIXI.DisplayObject} displayObject - The displayObject the object will be generated from + * @param {number} scaleMode - Should be one of the scaleMode consts + * @param {number} resolution - The resolution / device pixel ratio of the texture being generated + * @param {PIXI.Rectangle} [region] - The region of the displayObject, that shall be rendered, + * if no region is specified, defaults to the local bounds of the displayObject. + * @return {PIXI.Texture} a texture of the graphics object + */ + generateTexture(displayObject, scaleMode, resolution, region) + { + region = region || displayObject.getLocalBounds(); + + // 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(region.width | 0, region.height | 0, scaleMode, resolution); + + tempMatrix.tx = -region.x; + tempMatrix.ty = -region.y; + + this.render(displayObject, renderTexture, false, tempMatrix, true); + + return renderTexture; + } + + /** + * Removes everything from the renderer and optionally removes the Canvas DOM element. + * + * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. + */ + destroy(removeView) + { + 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); + } + + this.plugins = null; + + this.type = RENDERER_TYPE.UNKNOWN; + + this.view = null; + + this.screen = null; + + this.resolution = 0; + + this.transparent = false; + + this.autoResize = false; + + this.blendModes = null; + + this.options = null; + + this.preserveDrawingBuffer = false; + this.clearBeforeRender = false; + + this.roundPixels = false; + + this._backgroundColor = 0; + this._backgroundColorRgba = null; + this._backgroundColorString = null; + + this._tempDisplayObjectParent = null; + this._lastObjectRendered = null; + } + + /** + * The background color to fill if not transparent + * + * @member {number} + */ + get backgroundColor() + { + return this._backgroundColor; + } + + set backgroundColor(value) // eslint-disable-line require-jsdoc + { + this._backgroundColor = value; + this._backgroundColorString = hex2string(value); + hex2rgb(value, this._backgroundColorRgba); + } +} diff --git a/packages/core/src/Renderer.js b/packages/core/src/Renderer.js new file mode 100644 index 0000000..2227e78 --- /dev/null +++ b/packages/core/src/Renderer.js @@ -0,0 +1,338 @@ +import AbstractRenderer from './AbstractRenderer'; +import { sayHello } from '@pixi/utils'; +import MaskSystem from './mask/MaskSystem'; +import StencilSystem from './mask/StencilSystem'; +import FilterSystem from './filters/FilterSystem'; +import FramebufferSystem from './framebuffer/FramebufferSystem'; +import RenderTextureSystem from './renderTexture/RenderTextureSystem'; +import TextureSystem from './textures/TextureSystem'; +import ProjectionSystem from './projection/ProjectionSystem'; +import StateSystem from './state/StateSystem'; +import GeometrySystem from './geometry/GeometrySystem'; +import ShaderSystem from './shader/ShaderSystem'; +import ContextSystem from './context/ContextSystem'; +import BatchSystem from './batch/BatchSystem'; +import TextureGCSystem from './textures/TextureGCSystem'; +import { RENDERER_TYPE } from '@pixi/constants'; +import UniformGroup from './shader/UniformGroup'; +import { Matrix } from '@pixi/math'; +import Runner from 'mini-runner'; + +/** + * 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 Render works by automatically managing webGLBatchs. + * 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 :) + * + * @class + * @memberof PIXI + * @extends PIXI.AbstractRenderer + */ +export default class Renderer extends AbstractRenderer +{ + // eslint-disable-next-line valid-jsdoc + /** + * + * @param {object} [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.transparent=false] - If the render view is transparent, default false + * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false + * @param {boolean} [options.antialias=false] - sets antialias. If not available natively then FXAA + * antialiasing is used + * @param {boolean} [options.forceFXAA=false] - forces FXAA antialiasing to be used over native. + * FXAA is faster, but may not always look as great + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. + * The resolution of the renderer retina would be 2. + * @param {boolean} [options.clearBeforeRender=true] - This sets if the renderer will clear + * the canvas or not before the new render pass. If you wish to set this to false, you *must* set + * preserveDrawingBuffer to `true`. + * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, + * enable this if you need to call toDataUrl on the webgl context. + * @param {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when + * rendering, stopping pixel interpolation. + * @param {number} [options.backgroundColor=0x000000] - The background color of the rendered area + * (shown if not transparent). + * @param {boolean} [options.legacy=false] - If true PixiJS will aim to ensure compatibility + * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. + * @param {string} [options.powerPreference] - Parameter passed to webgl context, set to "high-performance" + * for devices with dual graphics card + */ + constructor(options, arg2, arg3) + { + super('WebGL', options, arg2, arg3); + + /** + * The type of this renderer as a standardised const + * + * @member {number} + * @see PIXI.RENDERER_TYPE + */ + this.type = RENDERER_TYPE.WEBGL; + + // this will be set by the contextSystem (this.context) + this.gl = null; + this.CONTEXT_UID = 0; + this.legacy = !!options.legacy; + + // TODO legacy! + + // runners! + this.runners = { + destroy: new Runner('destroy'), + contextChange: new Runner('contextChange', 1), + reset: new Runner('reset'), + update: new Runner('update'), + postrender: new Runner('postrender'), + prerender: new Runner('prerender'), + resize: new Runner('resize', 2), + }; + + 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(GeometrySystem, 'geometry') + .addSystem(FramebufferSystem, 'framebuffer') + .addSystem(StencilSystem, 'stencil') + .addSystem(ProjectionSystem, 'projection') + .addSystem(TextureGCSystem, 'textureGC') + .addSystem(FilterSystem, 'filter') + .addSystem(RenderTextureSystem, 'renderTexture') + .addSystem(BatchSystem, 'batch'); + + this.initPlugins(Renderer.__plugins); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ + if (options.context) + { + this.context.initFromContext(options.context); + } + else + { + this.context.initFromOptions({ + alpha: this.transparent, + antialias: options.antialias, + premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', + stencil: true, + 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); + } + + /** + * Add a new system to the renderer. + * @param {class} ClassRef - Class reference + * @param {string} [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. + * @return {PIXI.Renderer} Return instance of renderer + */ + addSystem(ClassRef, name) + { + if (!name) + { + name = ClassRef.name; + } + + // TODO - read name from class.name.. + + /* + if(name.includes('System')) + { + name = name.replace('System', ''); + name = name.charAt(0).toLowerCase() + name.slice(1); + } + */ + + const system = new ClassRef(this); + + if (this[name]) + { + throw new Error(`Whoops! ${name} is already a manger`); + } + + this[name] = system; + + for (const i in this.runners) + { + this.runners[i].add(system); + } + + return this; + + /** + * 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. + */ + } + + /** + * Renders the object to its webGL view + * + * @param {PIXI.DisplayObject} displayObject - the object to be rendered + * @param {PIXI.RenderTexture} renderTexture - The render texture to render to. + * @param {boolean} [clear] - Should the canvas be cleared before the new render + * @param {PIXI.Transform} [transform] - A transform to apply to the render texture before rendering. + * @param {boolean} [skipUpdateTransform] - Should we skip the update transform pass? + */ + render(displayObject, renderTexture, clear, transform, skipUpdateTransform) + { + // can be handy to know! + this.renderingToScreen = !renderTexture; + + this.runners.prerender.run(); + this.emit('prerender'); + + // 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.parent; + + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = 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.run(); + + this.emit('postrender'); + } + + /** + * Resizes the webGL view to the specified width and height. + * + * @param {number} screenWidth - the new width of the screen + * @param {number} screenHeight - the new height of the screen + */ + resize(screenWidth, screenHeight) + { + AbstractRenderer.prototype.resize.call(this, screenWidth, screenHeight); + + this.runners.resize.run(screenWidth, screenHeight); + } + + /** + * Resets the WebGL state so you can render things however you fancy! + * + * @return {PIXI.Renderer} Returns itself. + */ + reset() + { + this.runners.reset.run(); + + return this; + } + + clear() + { + this.framebuffer.bind(); + this.framebuffer.clear(); + } + + /** + * Removes everything from the renderer (event listeners, spritebatch, etc...) + * + * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. + * See: https://github.com/pixijs/pixi.js/issues/2233 + */ + destroy(removeView) + { + this.runners.destroy.run(); + + // call base destroy + super.destroy(removeView); + + // TODO nullify all the managers.. + this.gl = null; + } + + /** + * 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 + * builds and excluding plugins. + * @name PIXI.Renderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. + */ + + /** + * Adds a plugin to the renderer. + * + * @method + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + static registerPlugin(pluginName, ctor) + { + Renderer.__plugins = Renderer.__plugins || {}; + Renderer.__plugins[pluginName] = ctor; + } +} diff --git a/packages/core/src/System.js b/packages/core/src/System.js new file mode 100644 index 0000000..05a10c8 --- /dev/null +++ b/packages/core/src/System.js @@ -0,0 +1,42 @@ +/** + * System is a base class used for extending systems used by the {@link PIXI.Renderer} + * @see PIXI.Renderer#addSystem + * @class + * @memberof PIXI + */ +export default class System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + /** + * The renderer this manager works for. + * + * @member {PIXI.Renderer} + */ + this.renderer = renderer; + + this.renderer.runners.contextChange.add(this); + } + + /** + * Generic method called when there is a WebGL context change. + * + */ + contextChange() + { + // do some codes init! + } + + /** + * Generic destroy methods to be overridden by the subclass + * + */ + destroy() + { + this.renderer.runners.contextChange.remove(this); + this.renderer = null; + } +} diff --git a/packages/core/src/batch/BatchSystem.js b/packages/core/src/batch/BatchSystem.js new file mode 100644 index 0000000..b04c249 --- /dev/null +++ b/packages/core/src/batch/BatchSystem.js @@ -0,0 +1,68 @@ +import System from '../System'; +import ObjectRenderer from './ObjectRenderer'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class BatchSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + super(renderer); + + /** + * An empty renderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.emptyRenderer = new ObjectRenderer(renderer); + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.currentRenderer = this.emptyRenderer; + } + + /** + * Changes the current renderer to the one given in parameter + * + * @param {PIXI.ObjectRenderer} objectRenderer - The object renderer to use. + */ + setObjectRenderer(objectRenderer) + { + if (this.currentRenderer === objectRenderer) + { + return; + } + + this.currentRenderer.stop(); + this.currentRenderer = objectRenderer; + + this.renderer.state.setState(objectRenderer.state); + + this.currentRenderer.start(); + } + + /** + * This should be called if you wish to do some custom rendering + * It will basically render anything that may be batched up such as sprites + * + */ + flush() + { + this.setObjectRenderer(this.emptyRenderer); + } + + reset() + { + this.setObjectRenderer(this.emptyRenderer); + } +} diff --git a/packages/core/src/batch/ObjectRenderer.js b/packages/core/src/batch/ObjectRenderer.js new file mode 100644 index 0000000..0c851a9 --- /dev/null +++ b/packages/core/src/batch/ObjectRenderer.js @@ -0,0 +1,48 @@ +import System from '../System'; + +/** + * Base for a common object renderer that can be used as a system renderer plugin. + * + * @class + * @extends PIXI.System + * @memberof PIXI + */ +export default class ObjectRenderer extends System +{ + /** + * Starts the renderer and sets the shader + * + */ + start() + { + // set the shader.. + } + + /** + * Stops the renderer + * + */ + stop() + { + this.flush(); + } + + /** + * Stub method for rendering content and emptying the current batch. + * + */ + flush() + { + // flush! + } + + /** + * Renders an object + * + * @param {PIXI.DisplayObject} object - The object to render. + */ + render(object) // eslint-disable-line no-unused-vars + { + // render the object + } +} diff --git a/packages/core/src/context/ContextSystem.js b/packages/core/src/context/ContextSystem.js new file mode 100644 index 0000000..301aab4 --- /dev/null +++ b/packages/core/src/context/ContextSystem.js @@ -0,0 +1,186 @@ +import System from '../System'; +import { settings } from '@pixi/settings'; + +let CONTEXT_UID = 0; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class ContextSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.webGLVersion = 1; + + this.handleContextLost = this.handleContextLost.bind(this); + this.handleContextRestored = this.handleContextRestored.bind(this); + + this.extensions = {}; + + renderer.view.addEventListener('webglcontextlost', this.handleContextLost, false); + renderer.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + } + + get isLost() + { + return (!this.gl || this.gl.isContextLost()); + } + + contextChange(gl) + { + this.gl = gl; + + // restore a context if it was previously lost + if (gl.isContextLost() && gl.getExtension('WEBGL_lose_context')) + { + gl.getExtension('WEBGL_lose_context').restoreContext(); + } + } + + initFromContext(gl) + { + this.gl = gl; + this.validateContext(gl); + this.renderer.gl = gl; + this.renderer.CONTEXT_UID = CONTEXT_UID++; + this.renderer.runners.contextChange.run(gl); + } + + initFromOptions(options) + { + const gl = this.createContext(this.renderer.view, options); + + this.initFromContext(gl); + } + + /** + * Helper class to create a webGL Context + * + * @class + * @memberof PIXI.glCore + * @param canvas {HTMLCanvasElement} the canvas element that we will get the context from + * @param options {Object} An options object that gets passed in to the canvas element containing the context attributes, + * see https://developer.mozilla.org/en/docs/Web/API/HTMLCanvasElement/getContext for the options available + * @return {WebGLRenderingContext} the WebGL context + */ + createContext(canvas, options) + { + let gl; + + if (settings.PREFER_WEBGL_2) + { + gl = canvas.getContext('webgl2', options); + } + + if (gl) + { + this.webGLVersion = 2; + } + else + { + this.webGLVersion = 1; + + gl = canvas.getContext('webgl', options) + || canvas.getContext('experimental-webgl', options); + + if (!gl) + { + // fail, not able to get a context + throw new Error('This browser does not support webGL. Try using the canvas renderer'); + } + } + + this.gl = gl; + + this.getExtensions(); + + return gl; + } + + getExtensions() + { + // time to set up default etensions that pixi uses.. + const gl = this.gl; + const extensions = this.extensions; + + if (this.webGLVersion === 1) + { + extensions.drawBuffers = gl.getExtension('WEBGL_draw_buffers'); + extensions.depthTexture = gl.getExtension('WEBKIT_WEBGL_depth_texture'); + extensions.floatTexture = gl.getExtension('OES_texture_float'); + extensions.loseContext = gl.getExtension('WEBGL_lose_context'); + + extensions.vertexArrayObject = gl.getExtension('OES_vertex_array_object') + || gl.getExtension('MOZ_OES_vertex_array_object') + || gl.getExtension('WEBKIT_OES_vertex_array_object'); + } + + // we don't use any specific WebGL 2 ones yet! + } + + /** + * Handles a lost webgl context + * + * @private + * @param {WebGLContextEvent} event - The context lost event. + */ + handleContextLost(event) + { + event.preventDefault(); + } + + /** + * Handles a restored webgl context + * + * @private + */ + handleContextRestored() + { + this.renderer.runners.contextChange.run(this.gl); + } + + destroy() + { + const view = this.renderer.view; + + // remove listeners + view.removeEventListener('webglcontextlost', this.handleContextLost); + view.removeEventListener('webglcontextrestored', this.handleContextRestored); + + this.gl.useProgram(null); + + if (this.extensions.loseContext) + { + this.extensions.loseContext.loseContext(); + } + } + + postrender() + { + this.gl.flush(); + } + + validateContext(gl) + { + const attributes = gl.getContextAttributes(); + + // this is going to be fairly simple for now.. but at least we have room to grow! + if (!attributes.stencil) + { + /* eslint-disable max-len */ + + /* eslint-disable no-console */ + console.warn('Provided WebGL context does not have a stencil buffer, masks may not render correctly'); + /* eslint-enable no-console */ + + /* eslint-enable max-len */ + } + } +} diff --git a/packages/core/src/filters/Filter.js b/packages/core/src/filters/Filter.js new file mode 100644 index 0000000..9d8cad8 --- /dev/null +++ b/packages/core/src/filters/Filter.js @@ -0,0 +1,140 @@ +import Shader from '../shader/Shader'; +import Program from '../shader/Program'; +import State from '../state/State'; +import { settings } from '@pixi/settings'; +// import extractUniformsFromSrc from './extractUniformsFromSrc'; +import defaultVertex from './defaultFilter.vert'; +import defaultFragment from './defaultFilter.frag'; + +// let math = require('../../math'); +/** + * @class + * @memberof PIXI + * @extends PIXI.Shader + */ +export default class Filter extends Shader +{ + /** + * @param {string} [vertexSrc] - The source of the vertex shader. + * @param {string} [fragmentSrc] - The source of the fragment shader. + * @param {object} [uniforms] - Custom uniforms to use to augment the built-in ones. + */ + constructor(vertexSrc, fragmentSrc, uniforms) + { + const program = Program.from(vertexSrc, fragmentSrc); + + super(program, uniforms); + + /** + * The padding of the filter. Some filters require extra space to breath such as a blur. + * Increasing this will add extra width and height to the bounds of the object that the + * filter is applied to. + * + * @member {number} + */ + this.padding = 0; + + /** + * The resolution of the filter. Setting this to be lower will lower the quality but + * increase the performance of the filter. + * + * @member {number} + */ + this.resolution = settings.RESOLUTION; + + /** + * If enabled is true the filter is applied, if false it will not. + * + * @member {boolean} + */ + this.enabled = true; + + /** + * If enabled, PixiJS will fit the filter area into boundaries for better performance. + * Switch it off if it does not work for specific shader. + * + * @member {boolean} + */ + this.autoFit = true; + + /** + * Legacy filters use position and uvs from attributes + * @member {boolean} + * @readonly + */ + this.legacy = !!this.program.attributeData.aTextureCoord; + + /** + * the webGL state the filter requires to render + * @member {PIXI.State} + */ + this.state = new State(); + } + + /** + * Applies the filter + * + * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + * @param {boolean} clear - Should the output be cleared before rendering to it + * @param {object} [currentState] - It's current state of filter. + * There are some useful properties in the currentState : + * target, filters, sourceFrame, destinationFrame, renderTarget, resolution + */ + apply(filterManager, input, output, clear, currentState, derp) // eslint-disable-line no-unused-vars + { + // do as you please! + + filterManager.applyFilter(this, input, output, clear, currentState, derp); + + // or just do a regular render.. + } + + /** + * Sets the blendmode of the filter + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL + */ + get blendMode() + { + return this.state.blendMode; + } + + set blendMode(value) // eslint-disable-line require-jsdoc + { + this.state.blendMode = value; + } + + /** + * The default vertex shader source + * + * @static + * @constant + */ + static get defaultVertexSrc() + { + return defaultVertex; + } + + /** + * The default fragment shader source + * + * @static + * @constant + */ + static get defaultFragmentSrc() + { + return defaultFragment; + } +} + +/** + * Used for caching shader IDs + * + * @static + * @private + */ +Filter.SOURCE_KEY_MAP = {}; + diff --git a/packages/core/src/filters/FilterSystem.js b/packages/core/src/filters/FilterSystem.js new file mode 100644 index 0000000..3174318 --- /dev/null +++ b/packages/core/src/filters/FilterSystem.js @@ -0,0 +1,431 @@ +import System from '../System'; + +import RenderTexture from '../renderTexture/RenderTexture'; +import Quad from '../utils/Quad'; +import QuadUv from '../utils/QuadUv'; +import { Rectangle } from '@pixi/math'; +import * as filterTransforms from './filterTransforms'; +import bitTwiddle from 'bit-twiddle'; +import UniformGroup from '../shader/UniformGroup'; +import { DRAW_MODES } from '@pixi/constants'; + +// +/** + * @ignore + * @class + */ +class FilterState +{ + /** + * + */ + constructor() + { + this.renderTexture = null; + this.sourceFrame = new Rectangle(); + this.destinationFrame = new Rectangle(); + this.filters = []; + this.target = null; + this.legacy = false; + this.resolution = 1; + } +} + +/** + * @class + * @memberof PIXI.systems + * @extends PIXI.System + */ +export default class FilterSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + /** + * stores a bunch of PO2 textures used for filtering + * @type {Object} + */ + this.texturePool = {}; + + /** + * a pool for storing filter states, save us creating new ones each tick + * @type {Array} + */ + this.statePool = []; + + /** + * A very simple geometry used when drawing a filter effect to the screen + * @type {Quad} + */ + this.quad = new Quad(); + + this.quadUv = new QuadUv(); + + /** + * Temporary rect for maths + * @type {PIXI.Rectangle} + */ + this.tempRect = new Rectangle(); + + this.activeState = {}; + + /** + * this uniform group is attached to filter uniforms when used + * @type {UniformGroup} + */ + this.globalUniforms = new UniformGroup({ + sourceFrame: this.tempRect, + destinationFrame: this.tempRect, + + // legacy variables + filterArea: new Float32Array(4), + filterClamp: new Float32Array(4), + }, true); + } + + /** + * Adds a new filter to the System. + * + * @param {PIXI.DisplayObject} target - The target of the filter to render. + * @param {PIXI.Filter[]} filters - The filters to apply. + */ + push(target, filters) + { + const renderer = this.renderer; + const filterStack = this.renderer.renderTexture.defaultFilterStack; + const state = this.statePool.pop() || new FilterState(); + + let resolution = filters[0].resolution; + let padding = filters[0].padding; + let autoFit = filters[0].autoFit; + let legacy = filters[0].legacy; + + for (let i = 1; i < filters.length; i++) + { + const filter = filters[i]; + + // lets use the lowest resolution.. + resolution = Math.min(resolution, filter.resolution); + // and the largest amount of padding! + padding = Math.max(padding, filter.padding); + // only auto fit if all filters are autofit + autoFit = autoFit || filter.autoFit; + + legacy = legacy || filter.legacy; + } + + filterStack.push(state); + + state.resolution = resolution; + + state.legacy = legacy; + + // round to whole number based on resolution + // TODO move that to the shader too? + state.sourceFrame = target.filterArea || target.getBounds(true); + + state.sourceFrame.pad(padding); + + if (autoFit) + { + state.sourceFrame.fit(this.renderer.renderTexture.destinationFrame); + } + + state.sourceFrame.round(resolution); + + state.renderTexture = this.getPotFilterTexture(state.sourceFrame.width, state.sourceFrame.height, resolution); + state.filters = filters; + + state.destinationFrame.width = state.renderTexture.width; + state.destinationFrame.height = state.renderTexture.height; + + state.renderTexture.filterFrame = state.sourceFrame; + renderer.renderTexture.bind(state.renderTexture, state.sourceFrame);// /, state.destinationFrame); + renderer.renderTexture.clear(); + } + + /** + * Pops off the filter and applies it. + * + */ + pop() + { + const renderer = this.renderer; + const filterStack = renderer.renderTexture.defaultFilterStack; + const state = filterStack.pop(); + const filters = state.filters; + + this.activeState = state; + + const globalUniforms = this.globalUniforms.uniforms; + + globalUniforms.sourceFrame = state.sourceFrame; + globalUniforms.destinationFrame = state.destinationFrame; + globalUniforms.resolution = state.resolution; + + // only update the rect if its legacy.. + if (state.legacy) + { + const filterArea = globalUniforms.filterArea; + const filterClamp = globalUniforms.filterClamp; + + filterArea[0] = state.destinationFrame.width; + filterArea[1] = state.destinationFrame.height; + filterArea[2] = state.sourceFrame.x; + filterArea[3] = state.sourceFrame.y; + + filterClamp[0] = 0.5 / state.resolution / state.destinationFrame.width; + filterClamp[1] = 0.5 / state.resolution / state.destinationFrame.height; + filterClamp[2] = (state.sourceFrame.width - 0.5) / state.resolution / state.destinationFrame.width; + filterClamp[3] = (state.sourceFrame.height - 0.5) / state.resolution / state.destinationFrame.height; + } + + this.globalUniforms.update(); + + const lastState = filterStack[filterStack.length - 1]; + + if (filters.length === 1) + { + filters[0].apply(this, state.renderTexture, lastState.renderTexture, false, state); + + this.returnFilterTexture(state.renderTexture); + } + else + { + let flip = state.renderTexture; + let flop = this.getPotFilterTexture( + flip.width, + flip.height, + state.resolution + ); + + flop.filterFrame = flip.filterFrame; + + let i = 0; + + for (i = 0; i < filters.length - 1; ++i) + { + filters[i].apply(this, flip, flop, true, state); + + const t = flip; + + flip = flop; + flop = t; + } + + filters[i].apply(this, flip, lastState.renderTexture, false, state); + + this.returnFilterTexture(flip); + this.returnFilterTexture(flop); + } + + this.statePool.push(state); + } + + /** + * Draws a filter. + * + * @param {PIXI.Filter} filter - The filter to draw. + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + * @param {boolean} clear - Should the output be cleared before rendering to it + */ + applyFilter(filter, input, output, clear) + { + const renderer = this.renderer; + + renderer.renderTexture.bind(output, output ? output.filterFrame : null); + + if (clear) + { + // gl.disable(gl.SCISSOR_TEST); + renderer.renderTexture.clear(); + // gl.enable(gl.SCISSOR_TEST); + } + + // set the uniforms.. + filter.uniforms.uSampler = input; + filter.uniforms.filterGlobals = this.globalUniforms; + + // TODO make it so that the order of this does not matter.. + // because it does at the moment cos of global uniforms. + // they need to get resynced + + renderer.state.setState(filter.state); + renderer.shader.bind(filter); + + if (filter.legacy) + { + this.quadUv.map(input._frame, input.filterFrame); + + renderer.geometry.bind(this.quadUv); + renderer.geometry.draw(DRAW_MODES.TRIANGLES); + } + else + { + renderer.geometry.bind(this.quad); + renderer.geometry.draw(DRAW_MODES.TRIANGLE_STRIP); + } + } + + /** + * Calculates the mapped matrix. + * + * TODO playing around here.. this is temporary - (will end up in the shader) + * this returns a matrix that will normalise map filter cords in the filter to screen space + * + * @param {PIXI.Matrix} outputMatrix - the matrix to output to. + * @return {PIXI.Matrix} The mapped matrix. + */ + calculateScreenSpaceMatrix(outputMatrix) + { + const currentState = this.activeState; + + return filterTransforms.calculateScreenSpaceMatrix( + outputMatrix, + currentState.sourceFrame, + currentState.destinationFrame + ); + } + + /** + * This will map the filter coord so that a texture can be used based on the transform of a sprite + * + * @param {PIXI.Matrix} outputMatrix - The matrix to output to. + * @param {PIXI.Sprite} sprite - The sprite to map to. + * @return {PIXI.Matrix} The mapped matrix. + */ + calculateSpriteMatrix(outputMatrix, sprite) + { + const currentState = this.activeState; + + return filterTransforms.calculateSpriteMatrix( + outputMatrix, + currentState.sourceFrame, + currentState.destinationFrame, + sprite + ); + } + + /** + * Destroys this Filter System. + * + * @param {boolean} [contextLost=false] context was lost, do not free shaders + * + */ + destroy(contextLost = false) + { + if (!contextLost) + { + this.emptyPool(); + } + else + { + this.texturePool = {}; + } + } + + /** + * Gets a Power-of-Two render texture. + * + * TODO move to a seperate class could be on renderer? + * also - could cause issue with multiple contexts? + * + * @private + * @param {WebGLRenderingContext} gl - The webgl rendering context + * @param {number} minWidth - The minimum width of the render target. + * @param {number} minHeight - The minimum height of the render target. + * @param {number} resolution - The resolution of the render target. + * @return {PIXI.RenderTarget} The new render target. + */ + getPotFilterTexture(minWidth, minHeight, resolution) + { + minWidth = bitTwiddle.nextPow2(minWidth); + minHeight = bitTwiddle.nextPow2(minHeight); + resolution = resolution || 1; + + const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + + if (!this.texturePool[key]) + { + this.texturePool[key] = []; + } + + let renderTexture = this.texturePool[key].pop(); + + if (!renderTexture) + { + // temporary bypass cache.. + // internally - this will cause a texture to be bound.. + renderTexture = RenderTexture.create({ + width: minWidth, + height: minHeight, + resolution, + }); + } + + return renderTexture; + } + + /** + * Gets extra render texture to use inside current filter + * + * @param {number} resolution resolution of the renderTexture + * @returns {PIXI.RenderTexture} + */ + getFilterTexture(resolution) + { + const rt = this.activeState.renderTexture; + + const filterTexture = this.getPotFilterTexture(rt.width, rt.height, resolution || rt.baseTexture.resolution); + + filterTexture.filterFrame = rt.filterFrame; + + return filterTexture; + } + + /** + * Frees a render texture back into the pool. + * + * @param {PIXI.RenderTarget} renderTexture - The renderTarget to free + */ + returnFilterTexture(renderTexture) + { + renderTexture.filterFrame = null; + + const base = renderTexture.baseTexture; + + const minWidth = base.width; + const minHeight = base.height; + + const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + + this.texturePool[key].push(renderTexture); + } + + /** + * Empties the texture pool. + * + */ + emptyPool() + { + for (const i in this.texturePool) + { + const textures = this.texturePool[i]; + + if (textures) + { + for (let j = 0; j < textures.length; j++) + { + textures[j].destroy(true); + } + } + } + + this.texturePool = {}; + } +} diff --git a/packages/core/src/filters/defaultFilter.frag b/packages/core/src/filters/defaultFilter.frag new file mode 100644 index 0000000..a14c1be --- /dev/null +++ b/packages/core/src/filters/defaultFilter.frag @@ -0,0 +1,22 @@ +varying vec2 vTextureCoord; +varying vec2 vFilterCoord; + +uniform sampler2D uSampler; +uniform sampler2D filterSampler; + +void main(void){ + vec4 masky = texture2D(filterSampler, vFilterCoord); + vec4 sample = texture2D(uSampler, vTextureCoord); + vec4 color; + if(mod(vFilterCoord.x, 1.0) > 0.5) + { + color = vec4(1.0, 0.0, 0.0, 1.0); + } + else + { + color = vec4(0.0, 1.0, 0.0, 1.0); + } + // gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0); + gl_FragColor = mix(sample, masky, 0.5); + gl_FragColor *= sample.a; +} diff --git a/packages/core/src/filters/defaultFilter.vert b/packages/core/src/filters/defaultFilter.vert new file mode 100644 index 0000000..e1febca --- /dev/null +++ b/packages/core/src/filters/defaultFilter.vert @@ -0,0 +1,14 @@ +attribute vec2 aVertexPosition; +attribute vec2 aTextureCoord; + +uniform mat3 projectionMatrix; +uniform mat3 filterMatrix; + +varying vec2 vTextureCoord; +varying vec2 vFilterCoord; + +void main(void){ + gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); + vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy; + vTextureCoord = aTextureCoord ; +} diff --git a/packages/core/src/filters/filterTransforms.js b/packages/core/src/filters/filterTransforms.js new file mode 100644 index 0000000..87a3cd6 --- /dev/null +++ b/packages/core/src/filters/filterTransforms.js @@ -0,0 +1,50 @@ +import { Matrix } from '@pixi/math'; + +/** + * Calculates the mapped matrix + * @param filterArea {Rectangle} The filter area + * @param sprite {Sprite} the target sprite + * @param outputMatrix {Matrix} @alvin + * @private + */ +// this returns a matrix that will normalise map filter cords in the filter to screen space +export function calculateScreenSpaceMatrix(outputMatrix, filterArea, textureSize) +{ + // TODO unwrap? + const mappedMatrix = outputMatrix.identity(); + + mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); + + mappedMatrix.scale(textureSize.width, textureSize.height); + + return mappedMatrix; +} + +export function calculateNormalizedScreenSpaceMatrix(outputMatrix, filterArea, textureSize) +{ + const mappedMatrix = outputMatrix.identity(); + + mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); + + const translateScaleX = (textureSize.width / filterArea.width); + const translateScaleY = (textureSize.height / filterArea.height); + + mappedMatrix.scale(translateScaleX, translateScaleY); + + return mappedMatrix; +} + +// this will map the filter coord so that a texture can be used based on the transform of a sprite +export function calculateSpriteMatrix(outputMatrix, filterArea, textureSize, sprite) +{ + const orig = sprite._texture.orig; + const mappedMatrix = outputMatrix.set(textureSize.width, 0, 0, textureSize.height, filterArea.x, filterArea.y); + const worldTransform = sprite.worldTransform.copyTo(Matrix.TEMP_MATRIX); + + worldTransform.invert(); + mappedMatrix.prepend(worldTransform); + mappedMatrix.scale(1.0 / orig.width, 1.0 / orig.height); + mappedMatrix.translate(sprite.anchor.x, sprite.anchor.y); + + return mappedMatrix; +} diff --git a/packages/core/src/filters/spriteMask/SpriteMaskFilter.js b/packages/core/src/filters/spriteMask/SpriteMaskFilter.js new file mode 100644 index 0000000..91697b7 --- /dev/null +++ b/packages/core/src/filters/spriteMask/SpriteMaskFilter.js @@ -0,0 +1,63 @@ +import Filter from '../Filter'; +import { Matrix } from '@pixi/math'; +import vertex from './spriteMaskFilter.vert'; +import fragment from './spriteMaskFilter.frag'; +import { default as TextureMatrix } from '../../textures/TextureMatrix'; + +/** + * The SpriteMaskFilter class + * + * @class + * @extends PIXI.Filter + * @memberof PIXI + */ +export default class SpriteMaskFilter extends Filter +{ + /** + * @param {PIXI.Sprite} sprite - the target sprite + */ + constructor(sprite) + { + const maskMatrix = new Matrix(); + + super(vertex, fragment); + + sprite.renderable = false; + + this.maskSprite = sprite; + this.maskMatrix = maskMatrix; + } + + /** + * Applies the filter + * + * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + */ + apply(filterManager, input, output) + { + const maskSprite = this.maskSprite; + const tex = this.maskSprite.texture; + + if (!tex.valid) + { + return; + } + if (!tex.transform) + { + // margin = 0.0, let it bleed a bit, shader code becomes easier + // assuming that atlas textures were made with 1-pixel padding + tex.transform = new TextureMatrix(tex, 0.0); + } + tex.transform.update(); + + this.uniforms.mask = tex; + this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite) + .prepend(tex.transform.mapCoord); + this.uniforms.alpha = maskSprite.worldAlpha; + this.uniforms.maskClamp = tex.transform.uClampFrame; + + filterManager.applyFilter(this, input, output); + } +} diff --git a/packages/core/src/filters/spriteMask/spriteMaskFilter.frag b/packages/core/src/filters/spriteMask/spriteMaskFilter.frag new file mode 100644 index 0000000..0e4aef8 --- /dev/null +++ b/packages/core/src/filters/spriteMask/spriteMaskFilter.frag @@ -0,0 +1,23 @@ +varying vec2 vMaskCoord; +varying vec2 vTextureCoord; + +uniform sampler2D uSampler; +uniform sampler2D mask; +uniform float alpha; +uniform vec4 maskClamp; + +void main(void) +{ + float clip = step(3.5, + step(maskClamp.x, vMaskCoord.x) + + step(maskClamp.y, vMaskCoord.y) + + step(vMaskCoord.x, maskClamp.z) + + step(vMaskCoord.y, maskClamp.w)); + + vec4 original = texture2D(uSampler, vTextureCoord); + vec4 masky = texture2D(mask, vMaskCoord); + + original *= (masky.r * masky.a * alpha * clip); + + gl_FragColor = original; +} diff --git a/packages/core/src/filters/spriteMask/spriteMaskFilter.vert b/packages/core/src/filters/spriteMask/spriteMaskFilter.vert new file mode 100644 index 0000000..d23a30c --- /dev/null +++ b/packages/core/src/filters/spriteMask/spriteMaskFilter.vert @@ -0,0 +1,16 @@ +attribute vec2 aVertexPosition; +attribute vec2 aTextureCoord; + +uniform mat3 projectionMatrix; +uniform mat3 otherMatrix; + +varying vec2 vMaskCoord; +varying vec2 vTextureCoord; + +void main(void) +{ + gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); + + vTextureCoord = aTextureCoord; + vMaskCoord = ( otherMatrix * vec3( aTextureCoord, 1.0) ).xy; +} diff --git a/packages/core/src/framebuffer/FrameBuffer.js b/packages/core/src/framebuffer/FrameBuffer.js new file mode 100644 index 0000000..84e9fcc --- /dev/null +++ b/packages/core/src/framebuffer/FrameBuffer.js @@ -0,0 +1,106 @@ +import Texture from '../textures/BaseTexture'; +import { FORMATS, TYPES } from '@pixi/constants'; + +/** + * Frame buffer + * @class + * @memberof PIXI + */ +export default class FrameBuffer +{ + constructor(width, height) + { + this.width = width || 100; + this.height = height || 100; + + this.stencil = false; + this.depth = false; + + this.dirtyId = 0; + this.dirtyFormat = 0; + this.dirtySize = 0; + + this.depthTexture = null; + this.colorTextures = []; + + this.glFrameBuffers = {}; + } + + get colorTexture() + { + return this.colorTextures[0]; + } + + addColorTexture(index, texture) + { + // TODO add some validation to the texture - same width / height etc? + this.colorTextures[index || 0] = texture || new Texture(null, { scaleMode: 0, + resolution: 1, + mipmap: false, + width: this.width, + height: this.height });// || new Texture(); + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + addDepthTexture(texture) + { + /* eslint-disable max-len */ + this.depthTexture = texture || new Texture(null, { scaleMode: 0, + resolution: 1, + width: this.width, + height: this.height, + mipmap: false, + format: FORMATS.DEPTH_COMPONENT, + type: TYPES.UNSIGNED_SHORT });// UNSIGNED_SHORT; + /* eslint-disable max-len */ + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + enableDepth() + { + this.depth = true; + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + enableStencil() + { + this.stencil = true; + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + resize(width, height) + { + if (width === this.width && height === this.height) return; + + this.width = width; + this.height = height; + + this.dirtyId++; + this.dirtySize++; + + for (let i = 0; i < this.colorTextures.length; i++) + { + this.colorTextures[i].setSize(width, height); + } + + if (this.depthTexture) + { + this.depthTexture.setSize(width, height); + } + } +} diff --git a/packages/core/src/framebuffer/FramebufferSystem.js b/packages/core/src/framebuffer/FramebufferSystem.js new file mode 100644 index 0000000..2cc2437 --- /dev/null +++ b/packages/core/src/framebuffer/FramebufferSystem.js @@ -0,0 +1,246 @@ +import System from '../System'; +import { Rectangle } from '@pixi/math'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class FramebufferSystem extends System +{ + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + contextChange() + { + this.gl = this.renderer.gl; + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + this.current = null; + this.viewport = new Rectangle(); + + this.drawBufferExtension = this.renderer.context.extensions.drawBuffers; + } + + bind(framebuffer, frame) + { + const gl = this.gl; + + this.current = framebuffer; + + if (framebuffer) + { + // TODO cacheing layer! + + const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID] || this.initFramebuffer(framebuffer); + + gl.bindFramebuffer(gl.FRAMEBUFFER, fbo.framebuffer); + // makesure all textures are unbound.. + + // now check for updates... + if (fbo.dirtyId !== framebuffer.dirtyId) + { + fbo.dirtyId = framebuffer.dirtyId; + + if (fbo.dirtyFormat !== framebuffer.dirtyFormat) + { + fbo.dirtyFormat = framebuffer.dirtyFormat; + this.updateFramebuffer(framebuffer); + } + else if (fbo.dirtySize !== framebuffer.dirtySize) + { + fbo.dirtySize = framebuffer.dirtySize; + this.resizeFramebuffer(framebuffer); + } + } + + for (let i = 0; i < framebuffer.colorTextures.length; i++) + { + if (framebuffer.colorTextures[i].texturePart) + { + this.renderer.texture.unbind(framebuffer.colorTextures[i].texture); + } + else + { + this.renderer.texture.unbind(framebuffer.colorTextures[i]); + } + } + + if (framebuffer.depthTexture) + { + this.renderer.texture.unbind(framebuffer.depthTexture); + } + + if (frame) + { + this.setViewport(frame.x, frame.y, frame.width, frame.height); + } + else + { + this.setViewport(0, 0, framebuffer.width, framebuffer.height); + } + } + else + { + gl.bindFramebuffer(gl.FRAMEBUFFER, null); + + if (frame) + { + this.setViewport(frame.x, frame.y, frame.width, frame.height); + } + else + { + this.setViewport(0, 0, this.renderer.width, this.renderer.height); + } + } + } + + setViewport(x, y, width, height) + { + const v = this.viewport; + + if (v.width !== width || v.height !== height || v.x !== x || v.y !== y) + { + v.x = x; + v.y = y; + v.width = width; + v.height = height; + + this.gl.viewport(x, y, width, height); + } + } + + get size() + { + if (this.current) + { + // TODO store temp + return { x: 0, y: 0, width: this.current.width, height: this.current.height }; + } + + return { x: 0, y: 0, width: this.renderer.width, height: this.renderer.height }; + } + + clear(r, g, b, a) + { + const gl = this.gl; + + // TODO clear color can be set only one right? + gl.clearColor(r, g, b, a); + gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); + } + + // private functions... + + initFramebuffer(framebuffer) + { + const gl = this.gl; + + // TODO - make this a class? + const fbo = { + framebuffer: gl.createFramebuffer(), + stencil: null, + dirtyId: 0, + dirtyFormat: 0, + dirtySize: 0, + }; + + framebuffer.glFrameBuffers[this.CONTEXT_UID] = fbo; + + return fbo; + } + + resizeFramebuffer(framebuffer) + { + const gl = this.gl; + + if (framebuffer.stencil || framebuffer.depth) + { + gl.bindRenderbuffer(gl.RENDERBUFFER, this.stencil); + gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); + } + } + + updateFramebuffer(framebuffer) + { + const gl = this.gl; + + const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID]; + + // bind the color texture + const colorTextures = framebuffer.colorTextures; + + let count = colorTextures.length; + + if (!this.drawBufferExtension) + { + count = Math.min(count, 1); + } + + const activeTextures = []; + + for (let i = 0; i < count; i++) + { + const texture = framebuffer.colorTextures[i]; + + if (texture.texturePart) + { + this.renderer.texture.bind(texture.texture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.COLOR_ATTACHMENT0 + i, + gl.TEXTURE_CUBE_MAP_NEGATIVE_X + texture.side, + texture.texture._glTextures[this.CONTEXT_UID].texture, + 0); + } + else + { + this.renderer.texture.bind(texture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.COLOR_ATTACHMENT0 + i, + gl.TEXTURE_2D, + texture._glTextures[this.CONTEXT_UID].texture, + 0); + } + + activeTextures.push(gl.COLOR_ATTACHMENT0 + i); + } + + if (this.drawBufferExtension && activeTextures.length > 1) + { + this.drawBufferExtension.drawBuffersWEBGL(activeTextures); + } + + if (framebuffer.depthTexture) + { + const depthTextureExt = this.renderer.context.extensions.depthTexture; + + if (depthTextureExt) + { + const depthTexture = framebuffer.depthTexture; + + this.renderer.texture.bind(depthTexture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.DEPTH_ATTACHMENT, + gl.TEXTURE_2D, + depthTexture._glTextures[this.CONTEXT_UID].texture, + 0); + } + } + + if (framebuffer.stencil || framebuffer.depth) + { + fbo.stencil = gl.createRenderbuffer(); + + gl.bindRenderbuffer(gl.RENDERBUFFER, fbo.stencil); + + // TODO.. this is depth AND stencil? + gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, fbo.stencil); + gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); + // fbo.enableStencil(); + } + } +} diff --git a/packages/core/src/geometry/GLBuffer.js b/packages/core/src/geometry/GLBuffer.js new file mode 100644 index 0000000..0477e8a --- /dev/null +++ b/packages/core/src/geometry/GLBuffer.js @@ -0,0 +1,9 @@ +export default class GLBuffer +{ + constructor(buffer) + { + this.buffer = buffer; + this.updateID = -1; + this.byteLength = -1; + } +} diff --git a/packages/core/src/geometry/GeometrySystem.js b/packages/core/src/geometry/GeometrySystem.js new file mode 100644 index 0000000..959f827 --- /dev/null +++ b/packages/core/src/geometry/GeometrySystem.js @@ -0,0 +1,388 @@ +import System from '../System'; +import GLBuffer from './GLBuffer'; + +const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class GeometrySystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this._activeGeometry = null; + this._activeVao = null; + + this.hasVao = true; + this.hasInstance = true; + } + + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + contextChange() + { + const gl = this.gl = this.renderer.gl; + + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + + // webgl2 + if (!gl.createVertexArray) + { + // webgl 1! + let nativeVaoExtension = this.renderer.context.extensions.vertexArrayObject; + + if (this.renderer.legacy) + { + nativeVaoExtension = null; + } + + if (nativeVaoExtension) + { + gl.createVertexArray = () => + nativeVaoExtension.createVertexArrayOES(); + + gl.bindVertexArray = (vao) => + nativeVaoExtension.bindVertexArrayOES(vao); + + gl.deleteVertexArray = (vao) => + nativeVaoExtension.deleteVertexArrayOES(vao); + } + else + { + this.hasVao = false; + gl.createVertexArray = () => + { + // empty + }; + + gl.bindVertexArray = () => + { + // empty + }; + + gl.deleteVertexArray = () => + { + // empty + }; + } + } + + if (!gl.vertexAttribDivisor) + { + const instanceExt = gl.getExtension('ANGLE_instanced_arrays'); + + if (instanceExt) + { + gl.vertexAttribDivisor = (a, b) => + instanceExt.vertexAttribDivisorANGLE(a, b); + + gl.drawElementsInstanced = (a, b, c, d, e) => + instanceExt.drawElementsInstancedANGLE(a, b, c, d, e); + + gl.drawArraysInstanced = (a, b, c, d) => + instanceExt.drawArraysInstancedANGLE(a, b, c, d); + } + else + { + this.hasInstance = false; + } + } + } + + /** + * Binds geometry so that is can be drawn. Creating a Vao if required + * @private + * @param {PIXI.Geometry} geometry instance of geometry to bind + */ + bind(geometry, shader) + { + shader = shader || this.renderer.shader.shader; + + const gl = this.gl; + + // not sure the best way to address this.. + // currently different shaders require different VAOs for the same geometry + // Still mulling over the best way to solve this one.. + // will likely need to modify the shader attribute locations at run time! + let vaos = geometry.glVertexArrayObjects[this.CONTEXT_UID]; + + if (!vaos) + { + geometry.glVertexArrayObjects[this.CONTEXT_UID] = vaos = {}; + } + + const vao = vaos[shader.program.id] || this.initGeometryVao(geometry, shader.program); + + this._activeGeometry = geometry; + + if (this._activeVao !== vao) + { + this._activeVao = vao; + + if (this.hasVao) + { + gl.bindVertexArray(vao); + } + else + { + this.activateVao(geometry, shader.program); + } + } + + // TODO - optimise later! + // don't need to loop through if nothing changed! + // maybe look to add an 'autoupdate' to geometry? + this.updateBuffers(); + } + + reset() + { + this.unbind(); + } + + updateBuffers() + { + const geometry = this._activeGeometry; + const gl = this.gl; + + for (let i = 0; i < geometry.buffers.length; i++) + { + const buffer = geometry.buffers[i]; + + const glBuffer = buffer._glBuffers[this.CONTEXT_UID]; + + if (buffer._updateID !== glBuffer.updateID) + { + glBuffer.updateID = buffer._updateID; + + // TODO can cache this on buffer! maybe added a getter / setter? + const type = buffer.index ? gl.ELEMENT_ARRAY_BUFFER : gl.ARRAY_BUFFER; + const drawType = buffer.static ? gl.STATIC_DRAW : gl.DYNAMIC_DRAW; + + gl.bindBuffer(type, glBuffer.buffer); + + if (glBuffer.byteLength >= buffer.data.byteLength) + { + // offset is always zero for now! + gl.bufferSubData(type, 0, buffer.data); + } + else + { + gl.bufferData(type, buffer.data, drawType); + } + } + } + } + + checkCompatability(geometry, program) + { + // geometry must have at least all the attributes that the shader requires. + const geometryAttributes = geometry.attributes; + const shaderAttributes = program.attributeData; + + for (const j in shaderAttributes) + { + if (!geometryAttributes[j]) + { + throw new Error(`shader and geometry incompatible, geometry missing the "${j}" attribute`); + } + } + } + + /** + * Creates a Vao with the same structure as the geometry and stores it on the geometry. + * @private + * @param {PIXI.Geometry} geometry instance of geometry to to generate Vao for + */ + initGeometryVao(geometry, program) + { + this.checkCompatability(geometry, program); + + const gl = this.gl; + const CONTEXT_UID = this.CONTEXT_UID; + const buffers = geometry.buffers; + const attributes = geometry.attributes; + + const tempStride = {}; + const tempStart = {}; + + for (const j in buffers) + { + tempStride[j] = 0; + tempStart[j] = 0; + } + + for (const j in attributes) + { + if (!attributes[j].size && program.attributeData[j]) + { + attributes[j].size = program.attributeData[j].size; + } + + tempStride[attributes[j].buffer] += attributes[j].size * byteSizeMap[attributes[j].type]; + } + + for (const j in attributes) + { + const attribute = attributes[j]; + const attribSize = attribute.size; + + if (attribute.stride === undefined) + { + if (tempStride[attribute.buffer] === attribSize * byteSizeMap[attribute.type]) + { + attribute.stride = 0; + } + else + { + attribute.stride = tempStride[attribute.buffer]; + } + } + + if (attribute.start === undefined) + { + attribute.start = tempStart[attribute.buffer]; + + tempStart[attribute.buffer] += attribSize * byteSizeMap[attribute.type]; + } + } + + // first update - and create the buffers! + // only create a gl buffer if it actually gets + for (let i = 0; i < buffers.length; i++) + { + const buffer = buffers[i]; + + if (!buffer._glBuffers[CONTEXT_UID]) + { + buffer._glBuffers[CONTEXT_UID] = new GLBuffer(gl.createBuffer()); + } + } + + // TODO - maybe make this a data object? + // lets wait to see if we need to first! + const vao = gl.createVertexArray(); + + gl.bindVertexArray(vao); + + this.activateVao(geometry, program); + + gl.bindVertexArray(null); + + // add it to the cache! + geometry.glVertexArrayObjects[this.CONTEXT_UID][program.id] = vao; + + return vao; + } + + activateVao(geometry, program) + { + const gl = this.gl; + const CONTEXT_UID = this.CONTEXT_UID; + const buffers = geometry.buffers; + const attributes = geometry.attributes; + + if (geometry.indexBuffer) + { + // first update the index buffer if we have one.. + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, geometry.indexBuffer._glBuffers[CONTEXT_UID].buffer); + } + + let lastBuffer = null; + + // add a new one! + for (const j in attributes) + { + const attribute = attributes[j]; + const buffer = buffers[attribute.buffer]; + const glBuffer = buffer._glBuffers[CONTEXT_UID]; + + if (program.attributeData[j]) + { + if (lastBuffer !== glBuffer) + { + gl.bindBuffer(gl.ARRAY_BUFFER, glBuffer.buffer); + + lastBuffer = glBuffer; + } + + const location = program.attributeData[j].location; + + // TODO introduce state again + // we can optimise this for older devices that have no VAOs + gl.enableVertexAttribArray(location); + + gl.vertexAttribPointer(location, + attribute.size, + attribute.type || gl.FLOAT, + attribute.normalized, + attribute.stride, + attribute.start); + + if (attribute.instance) + { + // TODO calculate instance count based of this... + if (this.hasInstance) + { + gl.vertexAttribDivisor(location, 1); + } + else + { + throw new Error('geometry error, GPU Instancing is not supported on this device'); + } + } + } + } + } + + draw(type, size, start, instanceCount) + { + const gl = this.gl; + const geometry = this._activeGeometry; + + // TODO.. this should not change so maybe cache the function? + + if (geometry.indexBuffer) + { + if (geometry.instanced) + { + /* eslint-disable max-len */ + gl.drawElementsInstanced(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2, instanceCount || 1); + /* eslint-enable max-len */ + } + else + { + gl.drawElements(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2); + } + } + else + if (geometry.instanced) + { + // TODO need a better way to calculate size.. + gl.drawArraysInstanced(type, start, size || geometry.getSize(), instanceCount || 1); + } + else + { + gl.drawArrays(type, start, size || geometry.getSize()); + } + + return this; + } + + unbind() + { + this.gl.bindVertexArray(null); + this._activeVao = null; + this._activeGeometry = null; + } +} diff --git a/packages/core/src/geometry/utils/setVertexAttribArrays.js b/packages/core/src/geometry/utils/setVertexAttribArrays.js new file mode 100644 index 0000000..23b6959 --- /dev/null +++ b/packages/core/src/geometry/utils/setVertexAttribArrays.js @@ -0,0 +1,54 @@ +// var GL_MAP = {}; + +/** + * @param gl {WebGLRenderingContext} The current WebGL context + * @param attribs {*} + * @param state {*} + */ +export default function setVertexAttribArrays(gl, attribs, state) +{ + let i; + + if (state) + { + const tempAttribState = state.tempAttribState; + const attribState = state.attribState; + + for (i = 0; i < tempAttribState.length; i++) + { + tempAttribState[i] = false; + } + + // set the new attribs + for (i = 0; i < attribs.length; i++) + { + tempAttribState[attribs[i].attribute.location] = true; + } + + for (i = 0; i < attribState.length; i++) + { + if (attribState[i] !== tempAttribState[i]) + { + attribState[i] = tempAttribState[i]; + + if (state.attribState[i]) + { + gl.enableVertexAttribArray(i); + } + else + { + gl.disableVertexAttribArray(i); + } + } + } + } + else + { + for (i = 0; i < attribs.length; i++) + { + const attrib = attribs[i]; + + gl.enableVertexAttribArray(attrib.attribute.location); + } + } +} diff --git a/packages/core/src/index.js b/packages/core/src/index.js index 58c456a..fd1f547 100644 --- a/packages/core/src/index.js +++ b/packages/core/src/index.js @@ -1,30 +1,30 @@ import * as resources from './textures/resources'; -import * as systems from './renderers/systems'; +import * as systems from './systems'; -export { resources }; export { systems }; +export { resources }; -export { default as Renderer } from './renderers/Renderer'; -export { default as AbstractRenderer } from './renderers/AbstractRenderer'; -export { default as FrameBuffer } from './textures/FrameBuffer'; +export { default as System } from './System'; +export { default as Renderer } from './Renderer'; +export { default as AbstractRenderer } from './AbstractRenderer'; +export { default as FrameBuffer } from './framebuffer/FrameBuffer'; export { default as CubeTexture } from './textures/CubeTexture'; export { default as BaseTexture } from './textures/BaseTexture'; export { default as Texture } from './textures/Texture'; export { default as TextureMatrix } from './textures/TextureMatrix'; -export { default as RenderTexture } from './textures/RenderTexture'; -export { default as BaseRenderTexture } from './textures/BaseRenderTexture'; +export { default as RenderTexture } from './renderTexture/RenderTexture'; +export { default as BaseRenderTexture } from './renderTexture/BaseRenderTexture'; export { default as TextureUvs } from './textures/TextureUvs'; -export { default as State } from './renderers/State'; -export { default as ObjectRenderer } from './renderers/utils/ObjectRenderer'; -export { default as Quad } from './renderers/utils/Quad'; -export { default as QuadUv } from './renderers/utils/QuadUv'; -export { default as checkMaxIfStatmentsInShader } from './renderers/utils/checkMaxIfStatmentsInShader'; +export { default as State } from './state/State'; +export { default as ObjectRenderer } from './batch/ObjectRenderer'; +export { default as Quad } from './utils/Quad'; +export { default as QuadUv } from './utils/QuadUv'; +export { default as checkMaxIfStatmentsInShader } from './shader/utils/checkMaxIfStatmentsInShader'; export { default as Shader } from './shader/Shader'; export { default as Program } from './shader/Program'; export { default as UniformGroup } from './shader/UniformGroup'; -export { default as SpriteMaskFilter } from './renderers/filters/spriteMask/SpriteMaskFilter'; -export { default as Filter } from './renderers/filters/Filter'; +export { default as SpriteMaskFilter } from './filters/spriteMask/SpriteMaskFilter'; +export { default as Filter } from './filters/Filter'; export { default as Attribute } from './geometry/Attribute'; export { default as Buffer } from './geometry/Buffer'; export { default as Geometry } from './geometry/Geometry'; - diff --git a/packages/core/src/mask/MaskSystem.js b/packages/core/src/mask/MaskSystem.js new file mode 100644 index 0000000..99174c2 --- /dev/null +++ b/packages/core/src/mask/MaskSystem.js @@ -0,0 +1,199 @@ +import System from '../System'; +import AlphaMaskFilter from '../filters/spriteMask/SpriteMaskFilter'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class MaskSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + // TODO - we don't need both! + this.scissor = false; + this.scissorData = null; + this.scissorRenderTarget = null; + + this.enableScissor = false; + + this.alphaMaskPool = []; + this.alphaMaskIndex = 0; + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.DisplayObject} target - Display Object to push the mask to + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + push(target, maskData) + { + // TODO the root check means scissor rect will not + // be used on render textures more info here: + // https://github.com/pixijs/pixi.js/pull/3545 + + if (maskData.texture) + { + this.pushSpriteMask(target, maskData); + } + else if (this.enableScissor + && !this.scissor + && this.renderer._activeRenderTarget.root + && !this.renderer.stencil.stencilMaskStack.length + && maskData.isFastRect()) + { + const matrix = maskData.worldTransform; + + let rot = Math.atan2(matrix.b, matrix.a); + + // use the nearest degree! + rot = Math.round(rot * (180 / Math.PI)); + + if (rot % 90) + { + this.pushStencilMask(maskData); + } + else + { + this.pushScissorMask(target, maskData); + } + } + else + { + this.pushStencilMask(maskData); + } + } + + /** + * Removes the last mask from the mask stack and doesn't return it. + * + * @param {PIXI.DisplayObject} target - Display Object to pop the mask from + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + pop(target, maskData) + { + if (maskData.texture) + { + this.popSpriteMask(target, maskData); + } + else if (this.enableScissor && !this.renderer.stencil.stencilMaskStack.length) + { + this.popScissorMask(target, maskData); + } + else + { + this.popStencilMask(target, maskData); + } + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.RenderTarget} target - Display Object to push the sprite mask to + * @param {PIXI.Sprite} maskData - Sprite to be used as the mask + */ + pushSpriteMask(target, maskData) + { + let alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; + + if (!alphaMaskFilter) + { + alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; + } + + alphaMaskFilter[0].resolution = this.renderer.resolution; + alphaMaskFilter[0].maskSprite = maskData; + + // TODO - may cause issues! + target.filterArea = maskData.getBounds(true); + + this.renderer.filterSystem.pushFilter(target, alphaMaskFilter); + + this.alphaMaskIndex++; + } + + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popSpriteMask() + { + this.renderer.filterSystem.popFilter(); + this.alphaMaskIndex--; + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + pushStencilMask(maskData) + { + this.renderer.batch.flush(); + this.renderer.stencil.pushStencil(maskData); + } + + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popStencilMask() + { + // this.renderer.currentRenderer.stop(); + this.renderer.stencil.popStencil(); + } + + /** + * + * @param {PIXI.DisplayObject} target - Display Object to push the mask to + * @param {PIXI.Graphics} maskData - The masking data. + */ + pushScissorMask(target, maskData) + { + maskData.renderable = true; + + const renderTarget = this.renderer._activeRenderTarget; + + const bounds = maskData.getBounds(); + + bounds.fit(renderTarget.size); + maskData.renderable = false; + + this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); + + const resolution = this.renderer.resolution; + + this.renderer.gl.scissor( + bounds.x * resolution, + (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, + bounds.width * resolution, + bounds.height * resolution + ); + + this.scissorRenderTarget = renderTarget; + this.scissorData = maskData; + this.scissor = true; + } + + /** + * + * + */ + popScissorMask() + { + this.scissorRenderTarget = null; + this.scissorData = null; + this.scissor = false; + + // must be scissor! + const gl = this.renderer.gl; + + gl.disable(gl.SCISSOR_TEST); + } +} diff --git a/packages/core/src/mask/StencilSystem.js b/packages/core/src/mask/StencilSystem.js new file mode 100644 index 0000000..62796a5 --- /dev/null +++ b/packages/core/src/mask/StencilSystem.js @@ -0,0 +1,133 @@ +import System from '../System'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class StencilSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + this.stencilMaskStack = []; + } + + /** + * Changes the mask stack that is used by this System. + * + * @param {PIXI.Graphics[]} stencilMaskStack - The mask stack + */ + setMaskStack(stencilMaskStack) + { + const gl = this.renderer.gl; + + if (stencilMaskStack.length !== this.stencilMaskStack.length) + { + if (stencilMaskStack.length === 0) + { + gl.disable(gl.STENCIL_TEST); + } + else + { + gl.enable(gl.STENCIL_TEST); + } + } + + this.stencilMaskStack = stencilMaskStack; + } + + /** + * Applies the Mask and adds it to the current stencil stack. @alvin + * + * @param {PIXI.Graphics} graphics - The mask + */ + pushStencil(graphics) + { + this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); + + // this.renderer._activeRenderTarget.attachStencilBuffer(); + + const gl = this.renderer.gl; + const prevMaskCount = this.stencilMaskStack.length; + + if (prevMaskCount === 0) + { + gl.enable(gl.STENCIL_TEST); + } + + this.stencilMaskStack.push(graphics); + + // Increment the refference stencil value where the new mask overlaps with the old ones. + gl.colorMask(false, false, false, false); + gl.stencilFunc(gl.EQUAL, prevMaskCount, this._getBitwiseMask()); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.INCR); + this.renderer.plugins.graphics.render(graphics); + + this._useCurrent(); + } + + /** + * Removes the last mask from the stencil stack. @alvin + */ + popStencil() + { + this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); + + const gl = this.renderer.gl; + const graphics = this.stencilMaskStack.pop(); + + if (this.stencilMaskStack.length === 0) + { + // the stack is empty! + gl.disable(gl.STENCIL_TEST); + gl.clear(gl.STENCIL_BUFFER_BIT); + gl.clearStencil(0); + } + else + { + // Decrement the refference stencil value where the popped mask overlaps with the other ones + gl.colorMask(false, false, false, false); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.DECR); + this.renderer.plugins.graphics.render(graphics); + + this._useCurrent(); + } + } + + /** + * Setup renderer to use the current stencil data. + */ + _useCurrent() + { + const gl = this.renderer.gl; + + gl.colorMask(true, true, true, true); + gl.stencilFunc(gl.EQUAL, this.stencilMaskStack.length, this._getBitwiseMask()); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP); + } + + /** + * Fill 1s equal to the number of acitve stencil masks. + * + * @return {number} The bitwise mask. + */ + _getBitwiseMask() + { + return (1 << this.stencilMaskStack.length) - 1; + } + + /** + * Destroys the mask stack. + * + */ + destroy() + { + super.destroy(this); + + this.stencilMaskStack = null; + } +} diff --git a/packages/core/src/projection/ProjectionSystem.js b/packages/core/src/projection/ProjectionSystem.js new file mode 100644 index 0000000..6e63b73 --- /dev/null +++ b/packages/core/src/projection/ProjectionSystem.js @@ -0,0 +1,73 @@ +import System from '../System'; +import { Matrix } from '@pixi/math'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class ProjectionSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.projectionMatrix = new Matrix(); + } + + update(destinationFrame, sourceFrame, resolution, root) + { + this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; + this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; + + this.calculateProjection(this.destinationFrame, this.sourceFrame, resolution, root); + + this.renderer.globalUniforms.uniforms.projectionMatrix = this.projectionMatrix; + this.renderer.globalUniforms.update(); + } + + /** + * Updates the projection matrix based on a projection frame (which is a rectangle) + * + * @param {Rectangle} destinationFrame - The destination frame. + * @param {Rectangle} sourceFrame - The source frame. + */ + calculateProjection(destinationFrame, sourceFrame, resolution, root) + { + const pm = this.projectionMatrix; + + // I don't think we will need this line.. + // pm.identity(); + + if (!root) + { + pm.a = (1 / destinationFrame.width * 2) * resolution; + pm.d = (1 / destinationFrame.height * 2) * resolution; + + pm.tx = -1 - (sourceFrame.x * pm.a); + pm.ty = -1 - (sourceFrame.y * pm.d); + } + else + { + pm.a = (1 / destinationFrame.width * 2) * resolution; + pm.d = (-1 / destinationFrame.height * 2) * resolution; + + pm.tx = -1 - (sourceFrame.x * pm.a); + pm.ty = 1 - (sourceFrame.y * pm.d); + } + } + + /** + * Sets the transform of the active render target to the given matrix + * + * @param {PIXI.Matrix} matrix - The transformation matrix + */ + setTransform()// matrix) + { + // this._activeRenderTarget.transform = matrix; + } +} diff --git a/packages/core/src/AbstractRenderer.js b/packages/core/src/AbstractRenderer.js new file mode 100644 index 0000000..64b2eb1 --- /dev/null +++ b/packages/core/src/AbstractRenderer.js @@ -0,0 +1,353 @@ +import { hex2string, hex2rgb } from '@pixi/utils'; +import { Matrix, Rectangle } from '@pixi/math'; +import { RENDERER_TYPE } from '@pixi/constants'; +import { settings } from '@pixi/settings'; +import { Container } from '@pixi/display'; +import RenderTexture from './renderTexture/RenderTexture'; +import EventEmitter from 'eventemitter3'; + +const tempMatrix = new Matrix(); + +/** + * 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 EventEmitter + * @memberof PIXI + */ +export default class AbstractRenderer extends EventEmitter +{ + // eslint-disable-next-line valid-jsdoc + /** + * @param {string} system - The name of the system this renderer is for. + * @param {object} [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.transparent=false] - If the render view is transparent, default false + * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false + * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. The + * resolution of the renderer retina would be 2. + * @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 {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when rendering, + * stopping pixel interpolation. + */ + constructor(system, options, arg2, arg3) + { + super(); + + // Support for constructor(system, screenWidth, screenHeight, options) + if (typeof options === 'number') + { + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + }, arg3); + } + + // 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 = RENDERER_TYPE.UNKNOWN; + + /** + * Measurements of the screen. (0, 0, screenWidth, screenHeight) + * + * Its safe to use as filterArea or hitArea for 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 1 + */ + this.resolution = options.resolution || settings.RESOLUTION; + + /** + * Whether the render view is transparent + * + * @member {boolean} + */ + this.transparent = options.transparent; + + /** + * Whether css dimensions of canvas view should be resized to screen dimensions automatically + * + * @member {boolean} + */ + this.autoResize = options.autoResize || false; + + /** + * Tracks the blend modes useful for this renderer. + * + * @member {object} + */ + this.blendModes = null; + + /** + * 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; + + /** + * If true PixiJS will Math.floor() x/y values when rendering, stopping pixel interpolation. + * Handy for crisp pixel art and speed on legacy devices. + * + * @member {boolean} + */ + this.roundPixels = options.roundPixels; + + /** + * The background color as a number. + * + * @member {number} + * @private + */ + this._backgroundColor = 0x000000; + + /** + * The background color as an [R, G, B] array. + * + * @member {number[]} + * @private + */ + this._backgroundColorRgba = [0, 0, 0, 0]; + + /** + * The background color as a string. + * + * @member {string} + * @private + */ + this._backgroundColorString = '#000000'; + + this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter + + /** + * This temporary display object used as the parent of the currently being rendered item + * + * @member {PIXI.DisplayObject} + * @private + */ + this._tempDisplayObjectParent = new Container(); + + /** + * The last root object that the renderer tried to render. + * + * @member {PIXI.DisplayObject} + * @private + */ + this._lastObjectRendered = this._tempDisplayObjectParent; + + /** + * Collection of plugins. + * @readonly + * @member {object} + */ + this.plugins = {}; + } + + /** + * Initialize the plugins. + * + * @protected + * @param {object} staticMap - The dictionary of staticly saved plugins. + */ + initPlugins(staticMap) + { + 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() + { + return this.view.width; + } + + /** + * Same as view.height, actual number of pixels in the canvas by vertical + * + * @member {number} + * @readonly + * @default 600 + */ + get height() + { + return this.view.height; + } + + /** + * Resizes the screen and canvas to the specified width and height + * Canvas dimensions are multiplied by resolution + * + * @param {number} screenWidth - the new width of the screen + * @param {number} screenHeight - the new height of the screen + */ + resize(screenWidth, screenHeight) + { + this.screen.width = screenWidth; + this.screen.height = screenHeight; + + this.view.width = screenWidth * this.resolution; + this.view.height = screenHeight * this.resolution; + + if (this.autoResize) + { + this.view.style.width = `${screenWidth}px`; + this.view.style.height = `${screenHeight}px`; + } + } + + /** + * 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 {PIXI.DisplayObject} displayObject - The displayObject the object will be generated from + * @param {number} scaleMode - Should be one of the scaleMode consts + * @param {number} resolution - The resolution / device pixel ratio of the texture being generated + * @param {PIXI.Rectangle} [region] - The region of the displayObject, that shall be rendered, + * if no region is specified, defaults to the local bounds of the displayObject. + * @return {PIXI.Texture} a texture of the graphics object + */ + generateTexture(displayObject, scaleMode, resolution, region) + { + region = region || displayObject.getLocalBounds(); + + // 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(region.width | 0, region.height | 0, scaleMode, resolution); + + tempMatrix.tx = -region.x; + tempMatrix.ty = -region.y; + + this.render(displayObject, renderTexture, false, tempMatrix, true); + + return renderTexture; + } + + /** + * Removes everything from the renderer and optionally removes the Canvas DOM element. + * + * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. + */ + destroy(removeView) + { + 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); + } + + this.plugins = null; + + this.type = RENDERER_TYPE.UNKNOWN; + + this.view = null; + + this.screen = null; + + this.resolution = 0; + + this.transparent = false; + + this.autoResize = false; + + this.blendModes = null; + + this.options = null; + + this.preserveDrawingBuffer = false; + this.clearBeforeRender = false; + + this.roundPixels = false; + + this._backgroundColor = 0; + this._backgroundColorRgba = null; + this._backgroundColorString = null; + + this._tempDisplayObjectParent = null; + this._lastObjectRendered = null; + } + + /** + * The background color to fill if not transparent + * + * @member {number} + */ + get backgroundColor() + { + return this._backgroundColor; + } + + set backgroundColor(value) // eslint-disable-line require-jsdoc + { + this._backgroundColor = value; + this._backgroundColorString = hex2string(value); + hex2rgb(value, this._backgroundColorRgba); + } +} diff --git a/packages/core/src/Renderer.js b/packages/core/src/Renderer.js new file mode 100644 index 0000000..2227e78 --- /dev/null +++ b/packages/core/src/Renderer.js @@ -0,0 +1,338 @@ +import AbstractRenderer from './AbstractRenderer'; +import { sayHello } from '@pixi/utils'; +import MaskSystem from './mask/MaskSystem'; +import StencilSystem from './mask/StencilSystem'; +import FilterSystem from './filters/FilterSystem'; +import FramebufferSystem from './framebuffer/FramebufferSystem'; +import RenderTextureSystem from './renderTexture/RenderTextureSystem'; +import TextureSystem from './textures/TextureSystem'; +import ProjectionSystem from './projection/ProjectionSystem'; +import StateSystem from './state/StateSystem'; +import GeometrySystem from './geometry/GeometrySystem'; +import ShaderSystem from './shader/ShaderSystem'; +import ContextSystem from './context/ContextSystem'; +import BatchSystem from './batch/BatchSystem'; +import TextureGCSystem from './textures/TextureGCSystem'; +import { RENDERER_TYPE } from '@pixi/constants'; +import UniformGroup from './shader/UniformGroup'; +import { Matrix } from '@pixi/math'; +import Runner from 'mini-runner'; + +/** + * 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 Render works by automatically managing webGLBatchs. + * 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 :) + * + * @class + * @memberof PIXI + * @extends PIXI.AbstractRenderer + */ +export default class Renderer extends AbstractRenderer +{ + // eslint-disable-next-line valid-jsdoc + /** + * + * @param {object} [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.transparent=false] - If the render view is transparent, default false + * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false + * @param {boolean} [options.antialias=false] - sets antialias. If not available natively then FXAA + * antialiasing is used + * @param {boolean} [options.forceFXAA=false] - forces FXAA antialiasing to be used over native. + * FXAA is faster, but may not always look as great + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. + * The resolution of the renderer retina would be 2. + * @param {boolean} [options.clearBeforeRender=true] - This sets if the renderer will clear + * the canvas or not before the new render pass. If you wish to set this to false, you *must* set + * preserveDrawingBuffer to `true`. + * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, + * enable this if you need to call toDataUrl on the webgl context. + * @param {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when + * rendering, stopping pixel interpolation. + * @param {number} [options.backgroundColor=0x000000] - The background color of the rendered area + * (shown if not transparent). + * @param {boolean} [options.legacy=false] - If true PixiJS will aim to ensure compatibility + * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. + * @param {string} [options.powerPreference] - Parameter passed to webgl context, set to "high-performance" + * for devices with dual graphics card + */ + constructor(options, arg2, arg3) + { + super('WebGL', options, arg2, arg3); + + /** + * The type of this renderer as a standardised const + * + * @member {number} + * @see PIXI.RENDERER_TYPE + */ + this.type = RENDERER_TYPE.WEBGL; + + // this will be set by the contextSystem (this.context) + this.gl = null; + this.CONTEXT_UID = 0; + this.legacy = !!options.legacy; + + // TODO legacy! + + // runners! + this.runners = { + destroy: new Runner('destroy'), + contextChange: new Runner('contextChange', 1), + reset: new Runner('reset'), + update: new Runner('update'), + postrender: new Runner('postrender'), + prerender: new Runner('prerender'), + resize: new Runner('resize', 2), + }; + + 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(GeometrySystem, 'geometry') + .addSystem(FramebufferSystem, 'framebuffer') + .addSystem(StencilSystem, 'stencil') + .addSystem(ProjectionSystem, 'projection') + .addSystem(TextureGCSystem, 'textureGC') + .addSystem(FilterSystem, 'filter') + .addSystem(RenderTextureSystem, 'renderTexture') + .addSystem(BatchSystem, 'batch'); + + this.initPlugins(Renderer.__plugins); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ + if (options.context) + { + this.context.initFromContext(options.context); + } + else + { + this.context.initFromOptions({ + alpha: this.transparent, + antialias: options.antialias, + premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', + stencil: true, + 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); + } + + /** + * Add a new system to the renderer. + * @param {class} ClassRef - Class reference + * @param {string} [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. + * @return {PIXI.Renderer} Return instance of renderer + */ + addSystem(ClassRef, name) + { + if (!name) + { + name = ClassRef.name; + } + + // TODO - read name from class.name.. + + /* + if(name.includes('System')) + { + name = name.replace('System', ''); + name = name.charAt(0).toLowerCase() + name.slice(1); + } + */ + + const system = new ClassRef(this); + + if (this[name]) + { + throw new Error(`Whoops! ${name} is already a manger`); + } + + this[name] = system; + + for (const i in this.runners) + { + this.runners[i].add(system); + } + + return this; + + /** + * 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. + */ + } + + /** + * Renders the object to its webGL view + * + * @param {PIXI.DisplayObject} displayObject - the object to be rendered + * @param {PIXI.RenderTexture} renderTexture - The render texture to render to. + * @param {boolean} [clear] - Should the canvas be cleared before the new render + * @param {PIXI.Transform} [transform] - A transform to apply to the render texture before rendering. + * @param {boolean} [skipUpdateTransform] - Should we skip the update transform pass? + */ + render(displayObject, renderTexture, clear, transform, skipUpdateTransform) + { + // can be handy to know! + this.renderingToScreen = !renderTexture; + + this.runners.prerender.run(); + this.emit('prerender'); + + // 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.parent; + + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = 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.run(); + + this.emit('postrender'); + } + + /** + * Resizes the webGL view to the specified width and height. + * + * @param {number} screenWidth - the new width of the screen + * @param {number} screenHeight - the new height of the screen + */ + resize(screenWidth, screenHeight) + { + AbstractRenderer.prototype.resize.call(this, screenWidth, screenHeight); + + this.runners.resize.run(screenWidth, screenHeight); + } + + /** + * Resets the WebGL state so you can render things however you fancy! + * + * @return {PIXI.Renderer} Returns itself. + */ + reset() + { + this.runners.reset.run(); + + return this; + } + + clear() + { + this.framebuffer.bind(); + this.framebuffer.clear(); + } + + /** + * Removes everything from the renderer (event listeners, spritebatch, etc...) + * + * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. + * See: https://github.com/pixijs/pixi.js/issues/2233 + */ + destroy(removeView) + { + this.runners.destroy.run(); + + // call base destroy + super.destroy(removeView); + + // TODO nullify all the managers.. + this.gl = null; + } + + /** + * 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 + * builds and excluding plugins. + * @name PIXI.Renderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. + */ + + /** + * Adds a plugin to the renderer. + * + * @method + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + static registerPlugin(pluginName, ctor) + { + Renderer.__plugins = Renderer.__plugins || {}; + Renderer.__plugins[pluginName] = ctor; + } +} diff --git a/packages/core/src/System.js b/packages/core/src/System.js new file mode 100644 index 0000000..05a10c8 --- /dev/null +++ b/packages/core/src/System.js @@ -0,0 +1,42 @@ +/** + * System is a base class used for extending systems used by the {@link PIXI.Renderer} + * @see PIXI.Renderer#addSystem + * @class + * @memberof PIXI + */ +export default class System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + /** + * The renderer this manager works for. + * + * @member {PIXI.Renderer} + */ + this.renderer = renderer; + + this.renderer.runners.contextChange.add(this); + } + + /** + * Generic method called when there is a WebGL context change. + * + */ + contextChange() + { + // do some codes init! + } + + /** + * Generic destroy methods to be overridden by the subclass + * + */ + destroy() + { + this.renderer.runners.contextChange.remove(this); + this.renderer = null; + } +} diff --git a/packages/core/src/batch/BatchSystem.js b/packages/core/src/batch/BatchSystem.js new file mode 100644 index 0000000..b04c249 --- /dev/null +++ b/packages/core/src/batch/BatchSystem.js @@ -0,0 +1,68 @@ +import System from '../System'; +import ObjectRenderer from './ObjectRenderer'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class BatchSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + super(renderer); + + /** + * An empty renderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.emptyRenderer = new ObjectRenderer(renderer); + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.currentRenderer = this.emptyRenderer; + } + + /** + * Changes the current renderer to the one given in parameter + * + * @param {PIXI.ObjectRenderer} objectRenderer - The object renderer to use. + */ + setObjectRenderer(objectRenderer) + { + if (this.currentRenderer === objectRenderer) + { + return; + } + + this.currentRenderer.stop(); + this.currentRenderer = objectRenderer; + + this.renderer.state.setState(objectRenderer.state); + + this.currentRenderer.start(); + } + + /** + * This should be called if you wish to do some custom rendering + * It will basically render anything that may be batched up such as sprites + * + */ + flush() + { + this.setObjectRenderer(this.emptyRenderer); + } + + reset() + { + this.setObjectRenderer(this.emptyRenderer); + } +} diff --git a/packages/core/src/batch/ObjectRenderer.js b/packages/core/src/batch/ObjectRenderer.js new file mode 100644 index 0000000..0c851a9 --- /dev/null +++ b/packages/core/src/batch/ObjectRenderer.js @@ -0,0 +1,48 @@ +import System from '../System'; + +/** + * Base for a common object renderer that can be used as a system renderer plugin. + * + * @class + * @extends PIXI.System + * @memberof PIXI + */ +export default class ObjectRenderer extends System +{ + /** + * Starts the renderer and sets the shader + * + */ + start() + { + // set the shader.. + } + + /** + * Stops the renderer + * + */ + stop() + { + this.flush(); + } + + /** + * Stub method for rendering content and emptying the current batch. + * + */ + flush() + { + // flush! + } + + /** + * Renders an object + * + * @param {PIXI.DisplayObject} object - The object to render. + */ + render(object) // eslint-disable-line no-unused-vars + { + // render the object + } +} diff --git a/packages/core/src/context/ContextSystem.js b/packages/core/src/context/ContextSystem.js new file mode 100644 index 0000000..301aab4 --- /dev/null +++ b/packages/core/src/context/ContextSystem.js @@ -0,0 +1,186 @@ +import System from '../System'; +import { settings } from '@pixi/settings'; + +let CONTEXT_UID = 0; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class ContextSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.webGLVersion = 1; + + this.handleContextLost = this.handleContextLost.bind(this); + this.handleContextRestored = this.handleContextRestored.bind(this); + + this.extensions = {}; + + renderer.view.addEventListener('webglcontextlost', this.handleContextLost, false); + renderer.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + } + + get isLost() + { + return (!this.gl || this.gl.isContextLost()); + } + + contextChange(gl) + { + this.gl = gl; + + // restore a context if it was previously lost + if (gl.isContextLost() && gl.getExtension('WEBGL_lose_context')) + { + gl.getExtension('WEBGL_lose_context').restoreContext(); + } + } + + initFromContext(gl) + { + this.gl = gl; + this.validateContext(gl); + this.renderer.gl = gl; + this.renderer.CONTEXT_UID = CONTEXT_UID++; + this.renderer.runners.contextChange.run(gl); + } + + initFromOptions(options) + { + const gl = this.createContext(this.renderer.view, options); + + this.initFromContext(gl); + } + + /** + * Helper class to create a webGL Context + * + * @class + * @memberof PIXI.glCore + * @param canvas {HTMLCanvasElement} the canvas element that we will get the context from + * @param options {Object} An options object that gets passed in to the canvas element containing the context attributes, + * see https://developer.mozilla.org/en/docs/Web/API/HTMLCanvasElement/getContext for the options available + * @return {WebGLRenderingContext} the WebGL context + */ + createContext(canvas, options) + { + let gl; + + if (settings.PREFER_WEBGL_2) + { + gl = canvas.getContext('webgl2', options); + } + + if (gl) + { + this.webGLVersion = 2; + } + else + { + this.webGLVersion = 1; + + gl = canvas.getContext('webgl', options) + || canvas.getContext('experimental-webgl', options); + + if (!gl) + { + // fail, not able to get a context + throw new Error('This browser does not support webGL. Try using the canvas renderer'); + } + } + + this.gl = gl; + + this.getExtensions(); + + return gl; + } + + getExtensions() + { + // time to set up default etensions that pixi uses.. + const gl = this.gl; + const extensions = this.extensions; + + if (this.webGLVersion === 1) + { + extensions.drawBuffers = gl.getExtension('WEBGL_draw_buffers'); + extensions.depthTexture = gl.getExtension('WEBKIT_WEBGL_depth_texture'); + extensions.floatTexture = gl.getExtension('OES_texture_float'); + extensions.loseContext = gl.getExtension('WEBGL_lose_context'); + + extensions.vertexArrayObject = gl.getExtension('OES_vertex_array_object') + || gl.getExtension('MOZ_OES_vertex_array_object') + || gl.getExtension('WEBKIT_OES_vertex_array_object'); + } + + // we don't use any specific WebGL 2 ones yet! + } + + /** + * Handles a lost webgl context + * + * @private + * @param {WebGLContextEvent} event - The context lost event. + */ + handleContextLost(event) + { + event.preventDefault(); + } + + /** + * Handles a restored webgl context + * + * @private + */ + handleContextRestored() + { + this.renderer.runners.contextChange.run(this.gl); + } + + destroy() + { + const view = this.renderer.view; + + // remove listeners + view.removeEventListener('webglcontextlost', this.handleContextLost); + view.removeEventListener('webglcontextrestored', this.handleContextRestored); + + this.gl.useProgram(null); + + if (this.extensions.loseContext) + { + this.extensions.loseContext.loseContext(); + } + } + + postrender() + { + this.gl.flush(); + } + + validateContext(gl) + { + const attributes = gl.getContextAttributes(); + + // this is going to be fairly simple for now.. but at least we have room to grow! + if (!attributes.stencil) + { + /* eslint-disable max-len */ + + /* eslint-disable no-console */ + console.warn('Provided WebGL context does not have a stencil buffer, masks may not render correctly'); + /* eslint-enable no-console */ + + /* eslint-enable max-len */ + } + } +} diff --git a/packages/core/src/filters/Filter.js b/packages/core/src/filters/Filter.js new file mode 100644 index 0000000..9d8cad8 --- /dev/null +++ b/packages/core/src/filters/Filter.js @@ -0,0 +1,140 @@ +import Shader from '../shader/Shader'; +import Program from '../shader/Program'; +import State from '../state/State'; +import { settings } from '@pixi/settings'; +// import extractUniformsFromSrc from './extractUniformsFromSrc'; +import defaultVertex from './defaultFilter.vert'; +import defaultFragment from './defaultFilter.frag'; + +// let math = require('../../math'); +/** + * @class + * @memberof PIXI + * @extends PIXI.Shader + */ +export default class Filter extends Shader +{ + /** + * @param {string} [vertexSrc] - The source of the vertex shader. + * @param {string} [fragmentSrc] - The source of the fragment shader. + * @param {object} [uniforms] - Custom uniforms to use to augment the built-in ones. + */ + constructor(vertexSrc, fragmentSrc, uniforms) + { + const program = Program.from(vertexSrc, fragmentSrc); + + super(program, uniforms); + + /** + * The padding of the filter. Some filters require extra space to breath such as a blur. + * Increasing this will add extra width and height to the bounds of the object that the + * filter is applied to. + * + * @member {number} + */ + this.padding = 0; + + /** + * The resolution of the filter. Setting this to be lower will lower the quality but + * increase the performance of the filter. + * + * @member {number} + */ + this.resolution = settings.RESOLUTION; + + /** + * If enabled is true the filter is applied, if false it will not. + * + * @member {boolean} + */ + this.enabled = true; + + /** + * If enabled, PixiJS will fit the filter area into boundaries for better performance. + * Switch it off if it does not work for specific shader. + * + * @member {boolean} + */ + this.autoFit = true; + + /** + * Legacy filters use position and uvs from attributes + * @member {boolean} + * @readonly + */ + this.legacy = !!this.program.attributeData.aTextureCoord; + + /** + * the webGL state the filter requires to render + * @member {PIXI.State} + */ + this.state = new State(); + } + + /** + * Applies the filter + * + * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + * @param {boolean} clear - Should the output be cleared before rendering to it + * @param {object} [currentState] - It's current state of filter. + * There are some useful properties in the currentState : + * target, filters, sourceFrame, destinationFrame, renderTarget, resolution + */ + apply(filterManager, input, output, clear, currentState, derp) // eslint-disable-line no-unused-vars + { + // do as you please! + + filterManager.applyFilter(this, input, output, clear, currentState, derp); + + // or just do a regular render.. + } + + /** + * Sets the blendmode of the filter + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL + */ + get blendMode() + { + return this.state.blendMode; + } + + set blendMode(value) // eslint-disable-line require-jsdoc + { + this.state.blendMode = value; + } + + /** + * The default vertex shader source + * + * @static + * @constant + */ + static get defaultVertexSrc() + { + return defaultVertex; + } + + /** + * The default fragment shader source + * + * @static + * @constant + */ + static get defaultFragmentSrc() + { + return defaultFragment; + } +} + +/** + * Used for caching shader IDs + * + * @static + * @private + */ +Filter.SOURCE_KEY_MAP = {}; + diff --git a/packages/core/src/filters/FilterSystem.js b/packages/core/src/filters/FilterSystem.js new file mode 100644 index 0000000..3174318 --- /dev/null +++ b/packages/core/src/filters/FilterSystem.js @@ -0,0 +1,431 @@ +import System from '../System'; + +import RenderTexture from '../renderTexture/RenderTexture'; +import Quad from '../utils/Quad'; +import QuadUv from '../utils/QuadUv'; +import { Rectangle } from '@pixi/math'; +import * as filterTransforms from './filterTransforms'; +import bitTwiddle from 'bit-twiddle'; +import UniformGroup from '../shader/UniformGroup'; +import { DRAW_MODES } from '@pixi/constants'; + +// +/** + * @ignore + * @class + */ +class FilterState +{ + /** + * + */ + constructor() + { + this.renderTexture = null; + this.sourceFrame = new Rectangle(); + this.destinationFrame = new Rectangle(); + this.filters = []; + this.target = null; + this.legacy = false; + this.resolution = 1; + } +} + +/** + * @class + * @memberof PIXI.systems + * @extends PIXI.System + */ +export default class FilterSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + /** + * stores a bunch of PO2 textures used for filtering + * @type {Object} + */ + this.texturePool = {}; + + /** + * a pool for storing filter states, save us creating new ones each tick + * @type {Array} + */ + this.statePool = []; + + /** + * A very simple geometry used when drawing a filter effect to the screen + * @type {Quad} + */ + this.quad = new Quad(); + + this.quadUv = new QuadUv(); + + /** + * Temporary rect for maths + * @type {PIXI.Rectangle} + */ + this.tempRect = new Rectangle(); + + this.activeState = {}; + + /** + * this uniform group is attached to filter uniforms when used + * @type {UniformGroup} + */ + this.globalUniforms = new UniformGroup({ + sourceFrame: this.tempRect, + destinationFrame: this.tempRect, + + // legacy variables + filterArea: new Float32Array(4), + filterClamp: new Float32Array(4), + }, true); + } + + /** + * Adds a new filter to the System. + * + * @param {PIXI.DisplayObject} target - The target of the filter to render. + * @param {PIXI.Filter[]} filters - The filters to apply. + */ + push(target, filters) + { + const renderer = this.renderer; + const filterStack = this.renderer.renderTexture.defaultFilterStack; + const state = this.statePool.pop() || new FilterState(); + + let resolution = filters[0].resolution; + let padding = filters[0].padding; + let autoFit = filters[0].autoFit; + let legacy = filters[0].legacy; + + for (let i = 1; i < filters.length; i++) + { + const filter = filters[i]; + + // lets use the lowest resolution.. + resolution = Math.min(resolution, filter.resolution); + // and the largest amount of padding! + padding = Math.max(padding, filter.padding); + // only auto fit if all filters are autofit + autoFit = autoFit || filter.autoFit; + + legacy = legacy || filter.legacy; + } + + filterStack.push(state); + + state.resolution = resolution; + + state.legacy = legacy; + + // round to whole number based on resolution + // TODO move that to the shader too? + state.sourceFrame = target.filterArea || target.getBounds(true); + + state.sourceFrame.pad(padding); + + if (autoFit) + { + state.sourceFrame.fit(this.renderer.renderTexture.destinationFrame); + } + + state.sourceFrame.round(resolution); + + state.renderTexture = this.getPotFilterTexture(state.sourceFrame.width, state.sourceFrame.height, resolution); + state.filters = filters; + + state.destinationFrame.width = state.renderTexture.width; + state.destinationFrame.height = state.renderTexture.height; + + state.renderTexture.filterFrame = state.sourceFrame; + renderer.renderTexture.bind(state.renderTexture, state.sourceFrame);// /, state.destinationFrame); + renderer.renderTexture.clear(); + } + + /** + * Pops off the filter and applies it. + * + */ + pop() + { + const renderer = this.renderer; + const filterStack = renderer.renderTexture.defaultFilterStack; + const state = filterStack.pop(); + const filters = state.filters; + + this.activeState = state; + + const globalUniforms = this.globalUniforms.uniforms; + + globalUniforms.sourceFrame = state.sourceFrame; + globalUniforms.destinationFrame = state.destinationFrame; + globalUniforms.resolution = state.resolution; + + // only update the rect if its legacy.. + if (state.legacy) + { + const filterArea = globalUniforms.filterArea; + const filterClamp = globalUniforms.filterClamp; + + filterArea[0] = state.destinationFrame.width; + filterArea[1] = state.destinationFrame.height; + filterArea[2] = state.sourceFrame.x; + filterArea[3] = state.sourceFrame.y; + + filterClamp[0] = 0.5 / state.resolution / state.destinationFrame.width; + filterClamp[1] = 0.5 / state.resolution / state.destinationFrame.height; + filterClamp[2] = (state.sourceFrame.width - 0.5) / state.resolution / state.destinationFrame.width; + filterClamp[3] = (state.sourceFrame.height - 0.5) / state.resolution / state.destinationFrame.height; + } + + this.globalUniforms.update(); + + const lastState = filterStack[filterStack.length - 1]; + + if (filters.length === 1) + { + filters[0].apply(this, state.renderTexture, lastState.renderTexture, false, state); + + this.returnFilterTexture(state.renderTexture); + } + else + { + let flip = state.renderTexture; + let flop = this.getPotFilterTexture( + flip.width, + flip.height, + state.resolution + ); + + flop.filterFrame = flip.filterFrame; + + let i = 0; + + for (i = 0; i < filters.length - 1; ++i) + { + filters[i].apply(this, flip, flop, true, state); + + const t = flip; + + flip = flop; + flop = t; + } + + filters[i].apply(this, flip, lastState.renderTexture, false, state); + + this.returnFilterTexture(flip); + this.returnFilterTexture(flop); + } + + this.statePool.push(state); + } + + /** + * Draws a filter. + * + * @param {PIXI.Filter} filter - The filter to draw. + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + * @param {boolean} clear - Should the output be cleared before rendering to it + */ + applyFilter(filter, input, output, clear) + { + const renderer = this.renderer; + + renderer.renderTexture.bind(output, output ? output.filterFrame : null); + + if (clear) + { + // gl.disable(gl.SCISSOR_TEST); + renderer.renderTexture.clear(); + // gl.enable(gl.SCISSOR_TEST); + } + + // set the uniforms.. + filter.uniforms.uSampler = input; + filter.uniforms.filterGlobals = this.globalUniforms; + + // TODO make it so that the order of this does not matter.. + // because it does at the moment cos of global uniforms. + // they need to get resynced + + renderer.state.setState(filter.state); + renderer.shader.bind(filter); + + if (filter.legacy) + { + this.quadUv.map(input._frame, input.filterFrame); + + renderer.geometry.bind(this.quadUv); + renderer.geometry.draw(DRAW_MODES.TRIANGLES); + } + else + { + renderer.geometry.bind(this.quad); + renderer.geometry.draw(DRAW_MODES.TRIANGLE_STRIP); + } + } + + /** + * Calculates the mapped matrix. + * + * TODO playing around here.. this is temporary - (will end up in the shader) + * this returns a matrix that will normalise map filter cords in the filter to screen space + * + * @param {PIXI.Matrix} outputMatrix - the matrix to output to. + * @return {PIXI.Matrix} The mapped matrix. + */ + calculateScreenSpaceMatrix(outputMatrix) + { + const currentState = this.activeState; + + return filterTransforms.calculateScreenSpaceMatrix( + outputMatrix, + currentState.sourceFrame, + currentState.destinationFrame + ); + } + + /** + * This will map the filter coord so that a texture can be used based on the transform of a sprite + * + * @param {PIXI.Matrix} outputMatrix - The matrix to output to. + * @param {PIXI.Sprite} sprite - The sprite to map to. + * @return {PIXI.Matrix} The mapped matrix. + */ + calculateSpriteMatrix(outputMatrix, sprite) + { + const currentState = this.activeState; + + return filterTransforms.calculateSpriteMatrix( + outputMatrix, + currentState.sourceFrame, + currentState.destinationFrame, + sprite + ); + } + + /** + * Destroys this Filter System. + * + * @param {boolean} [contextLost=false] context was lost, do not free shaders + * + */ + destroy(contextLost = false) + { + if (!contextLost) + { + this.emptyPool(); + } + else + { + this.texturePool = {}; + } + } + + /** + * Gets a Power-of-Two render texture. + * + * TODO move to a seperate class could be on renderer? + * also - could cause issue with multiple contexts? + * + * @private + * @param {WebGLRenderingContext} gl - The webgl rendering context + * @param {number} minWidth - The minimum width of the render target. + * @param {number} minHeight - The minimum height of the render target. + * @param {number} resolution - The resolution of the render target. + * @return {PIXI.RenderTarget} The new render target. + */ + getPotFilterTexture(minWidth, minHeight, resolution) + { + minWidth = bitTwiddle.nextPow2(minWidth); + minHeight = bitTwiddle.nextPow2(minHeight); + resolution = resolution || 1; + + const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + + if (!this.texturePool[key]) + { + this.texturePool[key] = []; + } + + let renderTexture = this.texturePool[key].pop(); + + if (!renderTexture) + { + // temporary bypass cache.. + // internally - this will cause a texture to be bound.. + renderTexture = RenderTexture.create({ + width: minWidth, + height: minHeight, + resolution, + }); + } + + return renderTexture; + } + + /** + * Gets extra render texture to use inside current filter + * + * @param {number} resolution resolution of the renderTexture + * @returns {PIXI.RenderTexture} + */ + getFilterTexture(resolution) + { + const rt = this.activeState.renderTexture; + + const filterTexture = this.getPotFilterTexture(rt.width, rt.height, resolution || rt.baseTexture.resolution); + + filterTexture.filterFrame = rt.filterFrame; + + return filterTexture; + } + + /** + * Frees a render texture back into the pool. + * + * @param {PIXI.RenderTarget} renderTexture - The renderTarget to free + */ + returnFilterTexture(renderTexture) + { + renderTexture.filterFrame = null; + + const base = renderTexture.baseTexture; + + const minWidth = base.width; + const minHeight = base.height; + + const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + + this.texturePool[key].push(renderTexture); + } + + /** + * Empties the texture pool. + * + */ + emptyPool() + { + for (const i in this.texturePool) + { + const textures = this.texturePool[i]; + + if (textures) + { + for (let j = 0; j < textures.length; j++) + { + textures[j].destroy(true); + } + } + } + + this.texturePool = {}; + } +} diff --git a/packages/core/src/filters/defaultFilter.frag b/packages/core/src/filters/defaultFilter.frag new file mode 100644 index 0000000..a14c1be --- /dev/null +++ b/packages/core/src/filters/defaultFilter.frag @@ -0,0 +1,22 @@ +varying vec2 vTextureCoord; +varying vec2 vFilterCoord; + +uniform sampler2D uSampler; +uniform sampler2D filterSampler; + +void main(void){ + vec4 masky = texture2D(filterSampler, vFilterCoord); + vec4 sample = texture2D(uSampler, vTextureCoord); + vec4 color; + if(mod(vFilterCoord.x, 1.0) > 0.5) + { + color = vec4(1.0, 0.0, 0.0, 1.0); + } + else + { + color = vec4(0.0, 1.0, 0.0, 1.0); + } + // gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0); + gl_FragColor = mix(sample, masky, 0.5); + gl_FragColor *= sample.a; +} diff --git a/packages/core/src/filters/defaultFilter.vert b/packages/core/src/filters/defaultFilter.vert new file mode 100644 index 0000000..e1febca --- /dev/null +++ b/packages/core/src/filters/defaultFilter.vert @@ -0,0 +1,14 @@ +attribute vec2 aVertexPosition; +attribute vec2 aTextureCoord; + +uniform mat3 projectionMatrix; +uniform mat3 filterMatrix; + +varying vec2 vTextureCoord; +varying vec2 vFilterCoord; + +void main(void){ + gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); + vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy; + vTextureCoord = aTextureCoord ; +} diff --git a/packages/core/src/filters/filterTransforms.js b/packages/core/src/filters/filterTransforms.js new file mode 100644 index 0000000..87a3cd6 --- /dev/null +++ b/packages/core/src/filters/filterTransforms.js @@ -0,0 +1,50 @@ +import { Matrix } from '@pixi/math'; + +/** + * Calculates the mapped matrix + * @param filterArea {Rectangle} The filter area + * @param sprite {Sprite} the target sprite + * @param outputMatrix {Matrix} @alvin + * @private + */ +// this returns a matrix that will normalise map filter cords in the filter to screen space +export function calculateScreenSpaceMatrix(outputMatrix, filterArea, textureSize) +{ + // TODO unwrap? + const mappedMatrix = outputMatrix.identity(); + + mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); + + mappedMatrix.scale(textureSize.width, textureSize.height); + + return mappedMatrix; +} + +export function calculateNormalizedScreenSpaceMatrix(outputMatrix, filterArea, textureSize) +{ + const mappedMatrix = outputMatrix.identity(); + + mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); + + const translateScaleX = (textureSize.width / filterArea.width); + const translateScaleY = (textureSize.height / filterArea.height); + + mappedMatrix.scale(translateScaleX, translateScaleY); + + return mappedMatrix; +} + +// this will map the filter coord so that a texture can be used based on the transform of a sprite +export function calculateSpriteMatrix(outputMatrix, filterArea, textureSize, sprite) +{ + const orig = sprite._texture.orig; + const mappedMatrix = outputMatrix.set(textureSize.width, 0, 0, textureSize.height, filterArea.x, filterArea.y); + const worldTransform = sprite.worldTransform.copyTo(Matrix.TEMP_MATRIX); + + worldTransform.invert(); + mappedMatrix.prepend(worldTransform); + mappedMatrix.scale(1.0 / orig.width, 1.0 / orig.height); + mappedMatrix.translate(sprite.anchor.x, sprite.anchor.y); + + return mappedMatrix; +} diff --git a/packages/core/src/filters/spriteMask/SpriteMaskFilter.js b/packages/core/src/filters/spriteMask/SpriteMaskFilter.js new file mode 100644 index 0000000..91697b7 --- /dev/null +++ b/packages/core/src/filters/spriteMask/SpriteMaskFilter.js @@ -0,0 +1,63 @@ +import Filter from '../Filter'; +import { Matrix } from '@pixi/math'; +import vertex from './spriteMaskFilter.vert'; +import fragment from './spriteMaskFilter.frag'; +import { default as TextureMatrix } from '../../textures/TextureMatrix'; + +/** + * The SpriteMaskFilter class + * + * @class + * @extends PIXI.Filter + * @memberof PIXI + */ +export default class SpriteMaskFilter extends Filter +{ + /** + * @param {PIXI.Sprite} sprite - the target sprite + */ + constructor(sprite) + { + const maskMatrix = new Matrix(); + + super(vertex, fragment); + + sprite.renderable = false; + + this.maskSprite = sprite; + this.maskMatrix = maskMatrix; + } + + /** + * Applies the filter + * + * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + */ + apply(filterManager, input, output) + { + const maskSprite = this.maskSprite; + const tex = this.maskSprite.texture; + + if (!tex.valid) + { + return; + } + if (!tex.transform) + { + // margin = 0.0, let it bleed a bit, shader code becomes easier + // assuming that atlas textures were made with 1-pixel padding + tex.transform = new TextureMatrix(tex, 0.0); + } + tex.transform.update(); + + this.uniforms.mask = tex; + this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite) + .prepend(tex.transform.mapCoord); + this.uniforms.alpha = maskSprite.worldAlpha; + this.uniforms.maskClamp = tex.transform.uClampFrame; + + filterManager.applyFilter(this, input, output); + } +} diff --git a/packages/core/src/filters/spriteMask/spriteMaskFilter.frag b/packages/core/src/filters/spriteMask/spriteMaskFilter.frag new file mode 100644 index 0000000..0e4aef8 --- /dev/null +++ b/packages/core/src/filters/spriteMask/spriteMaskFilter.frag @@ -0,0 +1,23 @@ +varying vec2 vMaskCoord; +varying vec2 vTextureCoord; + +uniform sampler2D uSampler; +uniform sampler2D mask; +uniform float alpha; +uniform vec4 maskClamp; + +void main(void) +{ + float clip = step(3.5, + step(maskClamp.x, vMaskCoord.x) + + step(maskClamp.y, vMaskCoord.y) + + step(vMaskCoord.x, maskClamp.z) + + step(vMaskCoord.y, maskClamp.w)); + + vec4 original = texture2D(uSampler, vTextureCoord); + vec4 masky = texture2D(mask, vMaskCoord); + + original *= (masky.r * masky.a * alpha * clip); + + gl_FragColor = original; +} diff --git a/packages/core/src/filters/spriteMask/spriteMaskFilter.vert b/packages/core/src/filters/spriteMask/spriteMaskFilter.vert new file mode 100644 index 0000000..d23a30c --- /dev/null +++ b/packages/core/src/filters/spriteMask/spriteMaskFilter.vert @@ -0,0 +1,16 @@ +attribute vec2 aVertexPosition; +attribute vec2 aTextureCoord; + +uniform mat3 projectionMatrix; +uniform mat3 otherMatrix; + +varying vec2 vMaskCoord; +varying vec2 vTextureCoord; + +void main(void) +{ + gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); + + vTextureCoord = aTextureCoord; + vMaskCoord = ( otherMatrix * vec3( aTextureCoord, 1.0) ).xy; +} diff --git a/packages/core/src/framebuffer/FrameBuffer.js b/packages/core/src/framebuffer/FrameBuffer.js new file mode 100644 index 0000000..84e9fcc --- /dev/null +++ b/packages/core/src/framebuffer/FrameBuffer.js @@ -0,0 +1,106 @@ +import Texture from '../textures/BaseTexture'; +import { FORMATS, TYPES } from '@pixi/constants'; + +/** + * Frame buffer + * @class + * @memberof PIXI + */ +export default class FrameBuffer +{ + constructor(width, height) + { + this.width = width || 100; + this.height = height || 100; + + this.stencil = false; + this.depth = false; + + this.dirtyId = 0; + this.dirtyFormat = 0; + this.dirtySize = 0; + + this.depthTexture = null; + this.colorTextures = []; + + this.glFrameBuffers = {}; + } + + get colorTexture() + { + return this.colorTextures[0]; + } + + addColorTexture(index, texture) + { + // TODO add some validation to the texture - same width / height etc? + this.colorTextures[index || 0] = texture || new Texture(null, { scaleMode: 0, + resolution: 1, + mipmap: false, + width: this.width, + height: this.height });// || new Texture(); + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + addDepthTexture(texture) + { + /* eslint-disable max-len */ + this.depthTexture = texture || new Texture(null, { scaleMode: 0, + resolution: 1, + width: this.width, + height: this.height, + mipmap: false, + format: FORMATS.DEPTH_COMPONENT, + type: TYPES.UNSIGNED_SHORT });// UNSIGNED_SHORT; + /* eslint-disable max-len */ + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + enableDepth() + { + this.depth = true; + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + enableStencil() + { + this.stencil = true; + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + resize(width, height) + { + if (width === this.width && height === this.height) return; + + this.width = width; + this.height = height; + + this.dirtyId++; + this.dirtySize++; + + for (let i = 0; i < this.colorTextures.length; i++) + { + this.colorTextures[i].setSize(width, height); + } + + if (this.depthTexture) + { + this.depthTexture.setSize(width, height); + } + } +} diff --git a/packages/core/src/framebuffer/FramebufferSystem.js b/packages/core/src/framebuffer/FramebufferSystem.js new file mode 100644 index 0000000..2cc2437 --- /dev/null +++ b/packages/core/src/framebuffer/FramebufferSystem.js @@ -0,0 +1,246 @@ +import System from '../System'; +import { Rectangle } from '@pixi/math'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class FramebufferSystem extends System +{ + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + contextChange() + { + this.gl = this.renderer.gl; + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + this.current = null; + this.viewport = new Rectangle(); + + this.drawBufferExtension = this.renderer.context.extensions.drawBuffers; + } + + bind(framebuffer, frame) + { + const gl = this.gl; + + this.current = framebuffer; + + if (framebuffer) + { + // TODO cacheing layer! + + const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID] || this.initFramebuffer(framebuffer); + + gl.bindFramebuffer(gl.FRAMEBUFFER, fbo.framebuffer); + // makesure all textures are unbound.. + + // now check for updates... + if (fbo.dirtyId !== framebuffer.dirtyId) + { + fbo.dirtyId = framebuffer.dirtyId; + + if (fbo.dirtyFormat !== framebuffer.dirtyFormat) + { + fbo.dirtyFormat = framebuffer.dirtyFormat; + this.updateFramebuffer(framebuffer); + } + else if (fbo.dirtySize !== framebuffer.dirtySize) + { + fbo.dirtySize = framebuffer.dirtySize; + this.resizeFramebuffer(framebuffer); + } + } + + for (let i = 0; i < framebuffer.colorTextures.length; i++) + { + if (framebuffer.colorTextures[i].texturePart) + { + this.renderer.texture.unbind(framebuffer.colorTextures[i].texture); + } + else + { + this.renderer.texture.unbind(framebuffer.colorTextures[i]); + } + } + + if (framebuffer.depthTexture) + { + this.renderer.texture.unbind(framebuffer.depthTexture); + } + + if (frame) + { + this.setViewport(frame.x, frame.y, frame.width, frame.height); + } + else + { + this.setViewport(0, 0, framebuffer.width, framebuffer.height); + } + } + else + { + gl.bindFramebuffer(gl.FRAMEBUFFER, null); + + if (frame) + { + this.setViewport(frame.x, frame.y, frame.width, frame.height); + } + else + { + this.setViewport(0, 0, this.renderer.width, this.renderer.height); + } + } + } + + setViewport(x, y, width, height) + { + const v = this.viewport; + + if (v.width !== width || v.height !== height || v.x !== x || v.y !== y) + { + v.x = x; + v.y = y; + v.width = width; + v.height = height; + + this.gl.viewport(x, y, width, height); + } + } + + get size() + { + if (this.current) + { + // TODO store temp + return { x: 0, y: 0, width: this.current.width, height: this.current.height }; + } + + return { x: 0, y: 0, width: this.renderer.width, height: this.renderer.height }; + } + + clear(r, g, b, a) + { + const gl = this.gl; + + // TODO clear color can be set only one right? + gl.clearColor(r, g, b, a); + gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); + } + + // private functions... + + initFramebuffer(framebuffer) + { + const gl = this.gl; + + // TODO - make this a class? + const fbo = { + framebuffer: gl.createFramebuffer(), + stencil: null, + dirtyId: 0, + dirtyFormat: 0, + dirtySize: 0, + }; + + framebuffer.glFrameBuffers[this.CONTEXT_UID] = fbo; + + return fbo; + } + + resizeFramebuffer(framebuffer) + { + const gl = this.gl; + + if (framebuffer.stencil || framebuffer.depth) + { + gl.bindRenderbuffer(gl.RENDERBUFFER, this.stencil); + gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); + } + } + + updateFramebuffer(framebuffer) + { + const gl = this.gl; + + const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID]; + + // bind the color texture + const colorTextures = framebuffer.colorTextures; + + let count = colorTextures.length; + + if (!this.drawBufferExtension) + { + count = Math.min(count, 1); + } + + const activeTextures = []; + + for (let i = 0; i < count; i++) + { + const texture = framebuffer.colorTextures[i]; + + if (texture.texturePart) + { + this.renderer.texture.bind(texture.texture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.COLOR_ATTACHMENT0 + i, + gl.TEXTURE_CUBE_MAP_NEGATIVE_X + texture.side, + texture.texture._glTextures[this.CONTEXT_UID].texture, + 0); + } + else + { + this.renderer.texture.bind(texture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.COLOR_ATTACHMENT0 + i, + gl.TEXTURE_2D, + texture._glTextures[this.CONTEXT_UID].texture, + 0); + } + + activeTextures.push(gl.COLOR_ATTACHMENT0 + i); + } + + if (this.drawBufferExtension && activeTextures.length > 1) + { + this.drawBufferExtension.drawBuffersWEBGL(activeTextures); + } + + if (framebuffer.depthTexture) + { + const depthTextureExt = this.renderer.context.extensions.depthTexture; + + if (depthTextureExt) + { + const depthTexture = framebuffer.depthTexture; + + this.renderer.texture.bind(depthTexture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.DEPTH_ATTACHMENT, + gl.TEXTURE_2D, + depthTexture._glTextures[this.CONTEXT_UID].texture, + 0); + } + } + + if (framebuffer.stencil || framebuffer.depth) + { + fbo.stencil = gl.createRenderbuffer(); + + gl.bindRenderbuffer(gl.RENDERBUFFER, fbo.stencil); + + // TODO.. this is depth AND stencil? + gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, fbo.stencil); + gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); + // fbo.enableStencil(); + } + } +} diff --git a/packages/core/src/geometry/GLBuffer.js b/packages/core/src/geometry/GLBuffer.js new file mode 100644 index 0000000..0477e8a --- /dev/null +++ b/packages/core/src/geometry/GLBuffer.js @@ -0,0 +1,9 @@ +export default class GLBuffer +{ + constructor(buffer) + { + this.buffer = buffer; + this.updateID = -1; + this.byteLength = -1; + } +} diff --git a/packages/core/src/geometry/GeometrySystem.js b/packages/core/src/geometry/GeometrySystem.js new file mode 100644 index 0000000..959f827 --- /dev/null +++ b/packages/core/src/geometry/GeometrySystem.js @@ -0,0 +1,388 @@ +import System from '../System'; +import GLBuffer from './GLBuffer'; + +const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class GeometrySystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this._activeGeometry = null; + this._activeVao = null; + + this.hasVao = true; + this.hasInstance = true; + } + + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + contextChange() + { + const gl = this.gl = this.renderer.gl; + + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + + // webgl2 + if (!gl.createVertexArray) + { + // webgl 1! + let nativeVaoExtension = this.renderer.context.extensions.vertexArrayObject; + + if (this.renderer.legacy) + { + nativeVaoExtension = null; + } + + if (nativeVaoExtension) + { + gl.createVertexArray = () => + nativeVaoExtension.createVertexArrayOES(); + + gl.bindVertexArray = (vao) => + nativeVaoExtension.bindVertexArrayOES(vao); + + gl.deleteVertexArray = (vao) => + nativeVaoExtension.deleteVertexArrayOES(vao); + } + else + { + this.hasVao = false; + gl.createVertexArray = () => + { + // empty + }; + + gl.bindVertexArray = () => + { + // empty + }; + + gl.deleteVertexArray = () => + { + // empty + }; + } + } + + if (!gl.vertexAttribDivisor) + { + const instanceExt = gl.getExtension('ANGLE_instanced_arrays'); + + if (instanceExt) + { + gl.vertexAttribDivisor = (a, b) => + instanceExt.vertexAttribDivisorANGLE(a, b); + + gl.drawElementsInstanced = (a, b, c, d, e) => + instanceExt.drawElementsInstancedANGLE(a, b, c, d, e); + + gl.drawArraysInstanced = (a, b, c, d) => + instanceExt.drawArraysInstancedANGLE(a, b, c, d); + } + else + { + this.hasInstance = false; + } + } + } + + /** + * Binds geometry so that is can be drawn. Creating a Vao if required + * @private + * @param {PIXI.Geometry} geometry instance of geometry to bind + */ + bind(geometry, shader) + { + shader = shader || this.renderer.shader.shader; + + const gl = this.gl; + + // not sure the best way to address this.. + // currently different shaders require different VAOs for the same geometry + // Still mulling over the best way to solve this one.. + // will likely need to modify the shader attribute locations at run time! + let vaos = geometry.glVertexArrayObjects[this.CONTEXT_UID]; + + if (!vaos) + { + geometry.glVertexArrayObjects[this.CONTEXT_UID] = vaos = {}; + } + + const vao = vaos[shader.program.id] || this.initGeometryVao(geometry, shader.program); + + this._activeGeometry = geometry; + + if (this._activeVao !== vao) + { + this._activeVao = vao; + + if (this.hasVao) + { + gl.bindVertexArray(vao); + } + else + { + this.activateVao(geometry, shader.program); + } + } + + // TODO - optimise later! + // don't need to loop through if nothing changed! + // maybe look to add an 'autoupdate' to geometry? + this.updateBuffers(); + } + + reset() + { + this.unbind(); + } + + updateBuffers() + { + const geometry = this._activeGeometry; + const gl = this.gl; + + for (let i = 0; i < geometry.buffers.length; i++) + { + const buffer = geometry.buffers[i]; + + const glBuffer = buffer._glBuffers[this.CONTEXT_UID]; + + if (buffer._updateID !== glBuffer.updateID) + { + glBuffer.updateID = buffer._updateID; + + // TODO can cache this on buffer! maybe added a getter / setter? + const type = buffer.index ? gl.ELEMENT_ARRAY_BUFFER : gl.ARRAY_BUFFER; + const drawType = buffer.static ? gl.STATIC_DRAW : gl.DYNAMIC_DRAW; + + gl.bindBuffer(type, glBuffer.buffer); + + if (glBuffer.byteLength >= buffer.data.byteLength) + { + // offset is always zero for now! + gl.bufferSubData(type, 0, buffer.data); + } + else + { + gl.bufferData(type, buffer.data, drawType); + } + } + } + } + + checkCompatability(geometry, program) + { + // geometry must have at least all the attributes that the shader requires. + const geometryAttributes = geometry.attributes; + const shaderAttributes = program.attributeData; + + for (const j in shaderAttributes) + { + if (!geometryAttributes[j]) + { + throw new Error(`shader and geometry incompatible, geometry missing the "${j}" attribute`); + } + } + } + + /** + * Creates a Vao with the same structure as the geometry and stores it on the geometry. + * @private + * @param {PIXI.Geometry} geometry instance of geometry to to generate Vao for + */ + initGeometryVao(geometry, program) + { + this.checkCompatability(geometry, program); + + const gl = this.gl; + const CONTEXT_UID = this.CONTEXT_UID; + const buffers = geometry.buffers; + const attributes = geometry.attributes; + + const tempStride = {}; + const tempStart = {}; + + for (const j in buffers) + { + tempStride[j] = 0; + tempStart[j] = 0; + } + + for (const j in attributes) + { + if (!attributes[j].size && program.attributeData[j]) + { + attributes[j].size = program.attributeData[j].size; + } + + tempStride[attributes[j].buffer] += attributes[j].size * byteSizeMap[attributes[j].type]; + } + + for (const j in attributes) + { + const attribute = attributes[j]; + const attribSize = attribute.size; + + if (attribute.stride === undefined) + { + if (tempStride[attribute.buffer] === attribSize * byteSizeMap[attribute.type]) + { + attribute.stride = 0; + } + else + { + attribute.stride = tempStride[attribute.buffer]; + } + } + + if (attribute.start === undefined) + { + attribute.start = tempStart[attribute.buffer]; + + tempStart[attribute.buffer] += attribSize * byteSizeMap[attribute.type]; + } + } + + // first update - and create the buffers! + // only create a gl buffer if it actually gets + for (let i = 0; i < buffers.length; i++) + { + const buffer = buffers[i]; + + if (!buffer._glBuffers[CONTEXT_UID]) + { + buffer._glBuffers[CONTEXT_UID] = new GLBuffer(gl.createBuffer()); + } + } + + // TODO - maybe make this a data object? + // lets wait to see if we need to first! + const vao = gl.createVertexArray(); + + gl.bindVertexArray(vao); + + this.activateVao(geometry, program); + + gl.bindVertexArray(null); + + // add it to the cache! + geometry.glVertexArrayObjects[this.CONTEXT_UID][program.id] = vao; + + return vao; + } + + activateVao(geometry, program) + { + const gl = this.gl; + const CONTEXT_UID = this.CONTEXT_UID; + const buffers = geometry.buffers; + const attributes = geometry.attributes; + + if (geometry.indexBuffer) + { + // first update the index buffer if we have one.. + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, geometry.indexBuffer._glBuffers[CONTEXT_UID].buffer); + } + + let lastBuffer = null; + + // add a new one! + for (const j in attributes) + { + const attribute = attributes[j]; + const buffer = buffers[attribute.buffer]; + const glBuffer = buffer._glBuffers[CONTEXT_UID]; + + if (program.attributeData[j]) + { + if (lastBuffer !== glBuffer) + { + gl.bindBuffer(gl.ARRAY_BUFFER, glBuffer.buffer); + + lastBuffer = glBuffer; + } + + const location = program.attributeData[j].location; + + // TODO introduce state again + // we can optimise this for older devices that have no VAOs + gl.enableVertexAttribArray(location); + + gl.vertexAttribPointer(location, + attribute.size, + attribute.type || gl.FLOAT, + attribute.normalized, + attribute.stride, + attribute.start); + + if (attribute.instance) + { + // TODO calculate instance count based of this... + if (this.hasInstance) + { + gl.vertexAttribDivisor(location, 1); + } + else + { + throw new Error('geometry error, GPU Instancing is not supported on this device'); + } + } + } + } + } + + draw(type, size, start, instanceCount) + { + const gl = this.gl; + const geometry = this._activeGeometry; + + // TODO.. this should not change so maybe cache the function? + + if (geometry.indexBuffer) + { + if (geometry.instanced) + { + /* eslint-disable max-len */ + gl.drawElementsInstanced(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2, instanceCount || 1); + /* eslint-enable max-len */ + } + else + { + gl.drawElements(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2); + } + } + else + if (geometry.instanced) + { + // TODO need a better way to calculate size.. + gl.drawArraysInstanced(type, start, size || geometry.getSize(), instanceCount || 1); + } + else + { + gl.drawArrays(type, start, size || geometry.getSize()); + } + + return this; + } + + unbind() + { + this.gl.bindVertexArray(null); + this._activeVao = null; + this._activeGeometry = null; + } +} diff --git a/packages/core/src/geometry/utils/setVertexAttribArrays.js b/packages/core/src/geometry/utils/setVertexAttribArrays.js new file mode 100644 index 0000000..23b6959 --- /dev/null +++ b/packages/core/src/geometry/utils/setVertexAttribArrays.js @@ -0,0 +1,54 @@ +// var GL_MAP = {}; + +/** + * @param gl {WebGLRenderingContext} The current WebGL context + * @param attribs {*} + * @param state {*} + */ +export default function setVertexAttribArrays(gl, attribs, state) +{ + let i; + + if (state) + { + const tempAttribState = state.tempAttribState; + const attribState = state.attribState; + + for (i = 0; i < tempAttribState.length; i++) + { + tempAttribState[i] = false; + } + + // set the new attribs + for (i = 0; i < attribs.length; i++) + { + tempAttribState[attribs[i].attribute.location] = true; + } + + for (i = 0; i < attribState.length; i++) + { + if (attribState[i] !== tempAttribState[i]) + { + attribState[i] = tempAttribState[i]; + + if (state.attribState[i]) + { + gl.enableVertexAttribArray(i); + } + else + { + gl.disableVertexAttribArray(i); + } + } + } + } + else + { + for (i = 0; i < attribs.length; i++) + { + const attrib = attribs[i]; + + gl.enableVertexAttribArray(attrib.attribute.location); + } + } +} diff --git a/packages/core/src/index.js b/packages/core/src/index.js index 58c456a..fd1f547 100644 --- a/packages/core/src/index.js +++ b/packages/core/src/index.js @@ -1,30 +1,30 @@ import * as resources from './textures/resources'; -import * as systems from './renderers/systems'; +import * as systems from './systems'; -export { resources }; export { systems }; +export { resources }; -export { default as Renderer } from './renderers/Renderer'; -export { default as AbstractRenderer } from './renderers/AbstractRenderer'; -export { default as FrameBuffer } from './textures/FrameBuffer'; +export { default as System } from './System'; +export { default as Renderer } from './Renderer'; +export { default as AbstractRenderer } from './AbstractRenderer'; +export { default as FrameBuffer } from './framebuffer/FrameBuffer'; export { default as CubeTexture } from './textures/CubeTexture'; export { default as BaseTexture } from './textures/BaseTexture'; export { default as Texture } from './textures/Texture'; export { default as TextureMatrix } from './textures/TextureMatrix'; -export { default as RenderTexture } from './textures/RenderTexture'; -export { default as BaseRenderTexture } from './textures/BaseRenderTexture'; +export { default as RenderTexture } from './renderTexture/RenderTexture'; +export { default as BaseRenderTexture } from './renderTexture/BaseRenderTexture'; export { default as TextureUvs } from './textures/TextureUvs'; -export { default as State } from './renderers/State'; -export { default as ObjectRenderer } from './renderers/utils/ObjectRenderer'; -export { default as Quad } from './renderers/utils/Quad'; -export { default as QuadUv } from './renderers/utils/QuadUv'; -export { default as checkMaxIfStatmentsInShader } from './renderers/utils/checkMaxIfStatmentsInShader'; +export { default as State } from './state/State'; +export { default as ObjectRenderer } from './batch/ObjectRenderer'; +export { default as Quad } from './utils/Quad'; +export { default as QuadUv } from './utils/QuadUv'; +export { default as checkMaxIfStatmentsInShader } from './shader/utils/checkMaxIfStatmentsInShader'; export { default as Shader } from './shader/Shader'; export { default as Program } from './shader/Program'; export { default as UniformGroup } from './shader/UniformGroup'; -export { default as SpriteMaskFilter } from './renderers/filters/spriteMask/SpriteMaskFilter'; -export { default as Filter } from './renderers/filters/Filter'; +export { default as SpriteMaskFilter } from './filters/spriteMask/SpriteMaskFilter'; +export { default as Filter } from './filters/Filter'; export { default as Attribute } from './geometry/Attribute'; export { default as Buffer } from './geometry/Buffer'; export { default as Geometry } from './geometry/Geometry'; - diff --git a/packages/core/src/mask/MaskSystem.js b/packages/core/src/mask/MaskSystem.js new file mode 100644 index 0000000..99174c2 --- /dev/null +++ b/packages/core/src/mask/MaskSystem.js @@ -0,0 +1,199 @@ +import System from '../System'; +import AlphaMaskFilter from '../filters/spriteMask/SpriteMaskFilter'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class MaskSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + // TODO - we don't need both! + this.scissor = false; + this.scissorData = null; + this.scissorRenderTarget = null; + + this.enableScissor = false; + + this.alphaMaskPool = []; + this.alphaMaskIndex = 0; + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.DisplayObject} target - Display Object to push the mask to + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + push(target, maskData) + { + // TODO the root check means scissor rect will not + // be used on render textures more info here: + // https://github.com/pixijs/pixi.js/pull/3545 + + if (maskData.texture) + { + this.pushSpriteMask(target, maskData); + } + else if (this.enableScissor + && !this.scissor + && this.renderer._activeRenderTarget.root + && !this.renderer.stencil.stencilMaskStack.length + && maskData.isFastRect()) + { + const matrix = maskData.worldTransform; + + let rot = Math.atan2(matrix.b, matrix.a); + + // use the nearest degree! + rot = Math.round(rot * (180 / Math.PI)); + + if (rot % 90) + { + this.pushStencilMask(maskData); + } + else + { + this.pushScissorMask(target, maskData); + } + } + else + { + this.pushStencilMask(maskData); + } + } + + /** + * Removes the last mask from the mask stack and doesn't return it. + * + * @param {PIXI.DisplayObject} target - Display Object to pop the mask from + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + pop(target, maskData) + { + if (maskData.texture) + { + this.popSpriteMask(target, maskData); + } + else if (this.enableScissor && !this.renderer.stencil.stencilMaskStack.length) + { + this.popScissorMask(target, maskData); + } + else + { + this.popStencilMask(target, maskData); + } + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.RenderTarget} target - Display Object to push the sprite mask to + * @param {PIXI.Sprite} maskData - Sprite to be used as the mask + */ + pushSpriteMask(target, maskData) + { + let alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; + + if (!alphaMaskFilter) + { + alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; + } + + alphaMaskFilter[0].resolution = this.renderer.resolution; + alphaMaskFilter[0].maskSprite = maskData; + + // TODO - may cause issues! + target.filterArea = maskData.getBounds(true); + + this.renderer.filterSystem.pushFilter(target, alphaMaskFilter); + + this.alphaMaskIndex++; + } + + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popSpriteMask() + { + this.renderer.filterSystem.popFilter(); + this.alphaMaskIndex--; + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + pushStencilMask(maskData) + { + this.renderer.batch.flush(); + this.renderer.stencil.pushStencil(maskData); + } + + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popStencilMask() + { + // this.renderer.currentRenderer.stop(); + this.renderer.stencil.popStencil(); + } + + /** + * + * @param {PIXI.DisplayObject} target - Display Object to push the mask to + * @param {PIXI.Graphics} maskData - The masking data. + */ + pushScissorMask(target, maskData) + { + maskData.renderable = true; + + const renderTarget = this.renderer._activeRenderTarget; + + const bounds = maskData.getBounds(); + + bounds.fit(renderTarget.size); + maskData.renderable = false; + + this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); + + const resolution = this.renderer.resolution; + + this.renderer.gl.scissor( + bounds.x * resolution, + (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, + bounds.width * resolution, + bounds.height * resolution + ); + + this.scissorRenderTarget = renderTarget; + this.scissorData = maskData; + this.scissor = true; + } + + /** + * + * + */ + popScissorMask() + { + this.scissorRenderTarget = null; + this.scissorData = null; + this.scissor = false; + + // must be scissor! + const gl = this.renderer.gl; + + gl.disable(gl.SCISSOR_TEST); + } +} diff --git a/packages/core/src/mask/StencilSystem.js b/packages/core/src/mask/StencilSystem.js new file mode 100644 index 0000000..62796a5 --- /dev/null +++ b/packages/core/src/mask/StencilSystem.js @@ -0,0 +1,133 @@ +import System from '../System'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class StencilSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + this.stencilMaskStack = []; + } + + /** + * Changes the mask stack that is used by this System. + * + * @param {PIXI.Graphics[]} stencilMaskStack - The mask stack + */ + setMaskStack(stencilMaskStack) + { + const gl = this.renderer.gl; + + if (stencilMaskStack.length !== this.stencilMaskStack.length) + { + if (stencilMaskStack.length === 0) + { + gl.disable(gl.STENCIL_TEST); + } + else + { + gl.enable(gl.STENCIL_TEST); + } + } + + this.stencilMaskStack = stencilMaskStack; + } + + /** + * Applies the Mask and adds it to the current stencil stack. @alvin + * + * @param {PIXI.Graphics} graphics - The mask + */ + pushStencil(graphics) + { + this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); + + // this.renderer._activeRenderTarget.attachStencilBuffer(); + + const gl = this.renderer.gl; + const prevMaskCount = this.stencilMaskStack.length; + + if (prevMaskCount === 0) + { + gl.enable(gl.STENCIL_TEST); + } + + this.stencilMaskStack.push(graphics); + + // Increment the refference stencil value where the new mask overlaps with the old ones. + gl.colorMask(false, false, false, false); + gl.stencilFunc(gl.EQUAL, prevMaskCount, this._getBitwiseMask()); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.INCR); + this.renderer.plugins.graphics.render(graphics); + + this._useCurrent(); + } + + /** + * Removes the last mask from the stencil stack. @alvin + */ + popStencil() + { + this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); + + const gl = this.renderer.gl; + const graphics = this.stencilMaskStack.pop(); + + if (this.stencilMaskStack.length === 0) + { + // the stack is empty! + gl.disable(gl.STENCIL_TEST); + gl.clear(gl.STENCIL_BUFFER_BIT); + gl.clearStencil(0); + } + else + { + // Decrement the refference stencil value where the popped mask overlaps with the other ones + gl.colorMask(false, false, false, false); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.DECR); + this.renderer.plugins.graphics.render(graphics); + + this._useCurrent(); + } + } + + /** + * Setup renderer to use the current stencil data. + */ + _useCurrent() + { + const gl = this.renderer.gl; + + gl.colorMask(true, true, true, true); + gl.stencilFunc(gl.EQUAL, this.stencilMaskStack.length, this._getBitwiseMask()); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP); + } + + /** + * Fill 1s equal to the number of acitve stencil masks. + * + * @return {number} The bitwise mask. + */ + _getBitwiseMask() + { + return (1 << this.stencilMaskStack.length) - 1; + } + + /** + * Destroys the mask stack. + * + */ + destroy() + { + super.destroy(this); + + this.stencilMaskStack = null; + } +} diff --git a/packages/core/src/projection/ProjectionSystem.js b/packages/core/src/projection/ProjectionSystem.js new file mode 100644 index 0000000..6e63b73 --- /dev/null +++ b/packages/core/src/projection/ProjectionSystem.js @@ -0,0 +1,73 @@ +import System from '../System'; +import { Matrix } from '@pixi/math'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class ProjectionSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.projectionMatrix = new Matrix(); + } + + update(destinationFrame, sourceFrame, resolution, root) + { + this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; + this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; + + this.calculateProjection(this.destinationFrame, this.sourceFrame, resolution, root); + + this.renderer.globalUniforms.uniforms.projectionMatrix = this.projectionMatrix; + this.renderer.globalUniforms.update(); + } + + /** + * Updates the projection matrix based on a projection frame (which is a rectangle) + * + * @param {Rectangle} destinationFrame - The destination frame. + * @param {Rectangle} sourceFrame - The source frame. + */ + calculateProjection(destinationFrame, sourceFrame, resolution, root) + { + const pm = this.projectionMatrix; + + // I don't think we will need this line.. + // pm.identity(); + + if (!root) + { + pm.a = (1 / destinationFrame.width * 2) * resolution; + pm.d = (1 / destinationFrame.height * 2) * resolution; + + pm.tx = -1 - (sourceFrame.x * pm.a); + pm.ty = -1 - (sourceFrame.y * pm.d); + } + else + { + pm.a = (1 / destinationFrame.width * 2) * resolution; + pm.d = (-1 / destinationFrame.height * 2) * resolution; + + pm.tx = -1 - (sourceFrame.x * pm.a); + pm.ty = 1 - (sourceFrame.y * pm.d); + } + } + + /** + * Sets the transform of the active render target to the given matrix + * + * @param {PIXI.Matrix} matrix - The transformation matrix + */ + setTransform()// matrix) + { + // this._activeRenderTarget.transform = matrix; + } +} diff --git a/packages/core/src/renderTexture/BaseRenderTexture.js b/packages/core/src/renderTexture/BaseRenderTexture.js new file mode 100644 index 0000000..88eb83b --- /dev/null +++ b/packages/core/src/renderTexture/BaseRenderTexture.js @@ -0,0 +1,137 @@ +import BaseTexture from '../textures/BaseTexture'; +import FrameBuffer from '../framebuffer/FrameBuffer'; + +/** + * A BaseRenderTexture is a special texture that allows any PixiJS display object to be rendered to it. + * + * __Hint__: All DisplayObjects (i.e. Sprites) that render to a BaseRenderTexture should be preloaded + * otherwise black rectangles will be drawn instead. + * + * A BaseRenderTexture takes a snapshot of any Display Object given to its render method. The position + * and rotation of the given Display Objects is ignored. For example: + * + * ```js + * let renderer = PIXI.autoDetectRenderer(1024, 1024, { view: canvas, ratio: 1 }); + * let baseRenderTexture = new PIXI.BaseRenderTexture(renderer, 800, 600); + * let sprite = PIXI.Sprite.fromImage("spinObj_01.png"); + * + * sprite.position.x = 800/2; + * sprite.position.y = 600/2; + * sprite.anchor.x = 0.5; + * sprite.anchor.y = 0.5; + * + * baseRenderTexture.render(sprite); + * ``` + * + * The Sprite in this case will be rendered using its local transform. To render this sprite at 0,0 + * you can clear the transform + * + * ```js + * + * sprite.setTransform() + * + * let baseRenderTexture = new PIXI.BaseRenderTexture(100, 100); + * let renderTexture = new PIXI.RenderTexture(baseRenderTexture); + * + * renderer.render(sprite, renderTexture); // Renders to center of RenderTexture + * ``` + * + * @class + * @extends PIXI.BaseTexture + * @memberof PIXI + */ +export default class BaseRenderTexture extends BaseTexture +{ + /** + * @param {object} [options] + * @param {number} [options.width=100] - The width of the base render texture + * @param {number} [options.height=100] - The height of the base render texture + * @param {PIXI.SCALE_MODES} [options.scaleMode] - See {@link PIXI.SCALE_MODES} for possible values + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the texture being generated + */ + constructor(options) + { + if (typeof options === 'number') + { + /* eslint-disable prefer-rest-params */ + // Backward compatibility of signature + const width = arguments[0]; + const height = arguments[1]; + const scaleMode = arguments[2]; + const resolution = arguments[3]; + + options = { width, height, scaleMode, resolution }; + /* eslint-enable prefer-rest-params */ + } + + super(null, options); + + const { width, height } = options || {}; + + // Set defaults + this.mipmap = false; + this.width = Math.ceil(width) || 100; + this.height = Math.ceil(height) || 100; + this.valid = true; + + /** + * A map of renderer IDs to webgl renderTargets + * + * @private + * @member {object} + */ + // this._glRenderTargets = {}; + + /** + * A reference to the canvas render target (we only need one as this can be shared across renderers) + * + * @private + * @member {object} + */ + this._canvasRenderTarget = null; + + this.clearColor = [0, 0, 0, 0]; + + this.frameBuffer = new FrameBuffer(width * this.resolution, height * this.resolution) + .addColorTexture(0, this); + + // TODO - could this be added the systems? + + /** + * The data structure for the stencil masks + * + * @member {PIXI.Graphics[]} + */ + this.stencilMaskStack = []; + + /** + * The data structure for the filters + * + * @member {PIXI.Graphics[]} + */ + this.filterStack = [{}]; + } + + /** + * Resizes the BaseRenderTexture. + * + * @param {number} width - The width to resize to. + * @param {number} height - The height to resize to. + */ + resize(width, height) + { + width = Math.ceil(width); + height = Math.ceil(height); + this.frameBuffer.resize(width * this.resolution, height * this.resolution); + } + + /** + * Destroys this texture + * + */ + destroy() + { + super.destroy(true); + this.renderer = null; + } +} diff --git a/packages/core/src/AbstractRenderer.js b/packages/core/src/AbstractRenderer.js new file mode 100644 index 0000000..64b2eb1 --- /dev/null +++ b/packages/core/src/AbstractRenderer.js @@ -0,0 +1,353 @@ +import { hex2string, hex2rgb } from '@pixi/utils'; +import { Matrix, Rectangle } from '@pixi/math'; +import { RENDERER_TYPE } from '@pixi/constants'; +import { settings } from '@pixi/settings'; +import { Container } from '@pixi/display'; +import RenderTexture from './renderTexture/RenderTexture'; +import EventEmitter from 'eventemitter3'; + +const tempMatrix = new Matrix(); + +/** + * 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 EventEmitter + * @memberof PIXI + */ +export default class AbstractRenderer extends EventEmitter +{ + // eslint-disable-next-line valid-jsdoc + /** + * @param {string} system - The name of the system this renderer is for. + * @param {object} [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.transparent=false] - If the render view is transparent, default false + * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false + * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. The + * resolution of the renderer retina would be 2. + * @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 {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when rendering, + * stopping pixel interpolation. + */ + constructor(system, options, arg2, arg3) + { + super(); + + // Support for constructor(system, screenWidth, screenHeight, options) + if (typeof options === 'number') + { + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + }, arg3); + } + + // 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 = RENDERER_TYPE.UNKNOWN; + + /** + * Measurements of the screen. (0, 0, screenWidth, screenHeight) + * + * Its safe to use as filterArea or hitArea for 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 1 + */ + this.resolution = options.resolution || settings.RESOLUTION; + + /** + * Whether the render view is transparent + * + * @member {boolean} + */ + this.transparent = options.transparent; + + /** + * Whether css dimensions of canvas view should be resized to screen dimensions automatically + * + * @member {boolean} + */ + this.autoResize = options.autoResize || false; + + /** + * Tracks the blend modes useful for this renderer. + * + * @member {object} + */ + this.blendModes = null; + + /** + * 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; + + /** + * If true PixiJS will Math.floor() x/y values when rendering, stopping pixel interpolation. + * Handy for crisp pixel art and speed on legacy devices. + * + * @member {boolean} + */ + this.roundPixels = options.roundPixels; + + /** + * The background color as a number. + * + * @member {number} + * @private + */ + this._backgroundColor = 0x000000; + + /** + * The background color as an [R, G, B] array. + * + * @member {number[]} + * @private + */ + this._backgroundColorRgba = [0, 0, 0, 0]; + + /** + * The background color as a string. + * + * @member {string} + * @private + */ + this._backgroundColorString = '#000000'; + + this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter + + /** + * This temporary display object used as the parent of the currently being rendered item + * + * @member {PIXI.DisplayObject} + * @private + */ + this._tempDisplayObjectParent = new Container(); + + /** + * The last root object that the renderer tried to render. + * + * @member {PIXI.DisplayObject} + * @private + */ + this._lastObjectRendered = this._tempDisplayObjectParent; + + /** + * Collection of plugins. + * @readonly + * @member {object} + */ + this.plugins = {}; + } + + /** + * Initialize the plugins. + * + * @protected + * @param {object} staticMap - The dictionary of staticly saved plugins. + */ + initPlugins(staticMap) + { + 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() + { + return this.view.width; + } + + /** + * Same as view.height, actual number of pixels in the canvas by vertical + * + * @member {number} + * @readonly + * @default 600 + */ + get height() + { + return this.view.height; + } + + /** + * Resizes the screen and canvas to the specified width and height + * Canvas dimensions are multiplied by resolution + * + * @param {number} screenWidth - the new width of the screen + * @param {number} screenHeight - the new height of the screen + */ + resize(screenWidth, screenHeight) + { + this.screen.width = screenWidth; + this.screen.height = screenHeight; + + this.view.width = screenWidth * this.resolution; + this.view.height = screenHeight * this.resolution; + + if (this.autoResize) + { + this.view.style.width = `${screenWidth}px`; + this.view.style.height = `${screenHeight}px`; + } + } + + /** + * 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 {PIXI.DisplayObject} displayObject - The displayObject the object will be generated from + * @param {number} scaleMode - Should be one of the scaleMode consts + * @param {number} resolution - The resolution / device pixel ratio of the texture being generated + * @param {PIXI.Rectangle} [region] - The region of the displayObject, that shall be rendered, + * if no region is specified, defaults to the local bounds of the displayObject. + * @return {PIXI.Texture} a texture of the graphics object + */ + generateTexture(displayObject, scaleMode, resolution, region) + { + region = region || displayObject.getLocalBounds(); + + // 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(region.width | 0, region.height | 0, scaleMode, resolution); + + tempMatrix.tx = -region.x; + tempMatrix.ty = -region.y; + + this.render(displayObject, renderTexture, false, tempMatrix, true); + + return renderTexture; + } + + /** + * Removes everything from the renderer and optionally removes the Canvas DOM element. + * + * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. + */ + destroy(removeView) + { + 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); + } + + this.plugins = null; + + this.type = RENDERER_TYPE.UNKNOWN; + + this.view = null; + + this.screen = null; + + this.resolution = 0; + + this.transparent = false; + + this.autoResize = false; + + this.blendModes = null; + + this.options = null; + + this.preserveDrawingBuffer = false; + this.clearBeforeRender = false; + + this.roundPixels = false; + + this._backgroundColor = 0; + this._backgroundColorRgba = null; + this._backgroundColorString = null; + + this._tempDisplayObjectParent = null; + this._lastObjectRendered = null; + } + + /** + * The background color to fill if not transparent + * + * @member {number} + */ + get backgroundColor() + { + return this._backgroundColor; + } + + set backgroundColor(value) // eslint-disable-line require-jsdoc + { + this._backgroundColor = value; + this._backgroundColorString = hex2string(value); + hex2rgb(value, this._backgroundColorRgba); + } +} diff --git a/packages/core/src/Renderer.js b/packages/core/src/Renderer.js new file mode 100644 index 0000000..2227e78 --- /dev/null +++ b/packages/core/src/Renderer.js @@ -0,0 +1,338 @@ +import AbstractRenderer from './AbstractRenderer'; +import { sayHello } from '@pixi/utils'; +import MaskSystem from './mask/MaskSystem'; +import StencilSystem from './mask/StencilSystem'; +import FilterSystem from './filters/FilterSystem'; +import FramebufferSystem from './framebuffer/FramebufferSystem'; +import RenderTextureSystem from './renderTexture/RenderTextureSystem'; +import TextureSystem from './textures/TextureSystem'; +import ProjectionSystem from './projection/ProjectionSystem'; +import StateSystem from './state/StateSystem'; +import GeometrySystem from './geometry/GeometrySystem'; +import ShaderSystem from './shader/ShaderSystem'; +import ContextSystem from './context/ContextSystem'; +import BatchSystem from './batch/BatchSystem'; +import TextureGCSystem from './textures/TextureGCSystem'; +import { RENDERER_TYPE } from '@pixi/constants'; +import UniformGroup from './shader/UniformGroup'; +import { Matrix } from '@pixi/math'; +import Runner from 'mini-runner'; + +/** + * 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 Render works by automatically managing webGLBatchs. + * 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 :) + * + * @class + * @memberof PIXI + * @extends PIXI.AbstractRenderer + */ +export default class Renderer extends AbstractRenderer +{ + // eslint-disable-next-line valid-jsdoc + /** + * + * @param {object} [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.transparent=false] - If the render view is transparent, default false + * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false + * @param {boolean} [options.antialias=false] - sets antialias. If not available natively then FXAA + * antialiasing is used + * @param {boolean} [options.forceFXAA=false] - forces FXAA antialiasing to be used over native. + * FXAA is faster, but may not always look as great + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. + * The resolution of the renderer retina would be 2. + * @param {boolean} [options.clearBeforeRender=true] - This sets if the renderer will clear + * the canvas or not before the new render pass. If you wish to set this to false, you *must* set + * preserveDrawingBuffer to `true`. + * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, + * enable this if you need to call toDataUrl on the webgl context. + * @param {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when + * rendering, stopping pixel interpolation. + * @param {number} [options.backgroundColor=0x000000] - The background color of the rendered area + * (shown if not transparent). + * @param {boolean} [options.legacy=false] - If true PixiJS will aim to ensure compatibility + * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. + * @param {string} [options.powerPreference] - Parameter passed to webgl context, set to "high-performance" + * for devices with dual graphics card + */ + constructor(options, arg2, arg3) + { + super('WebGL', options, arg2, arg3); + + /** + * The type of this renderer as a standardised const + * + * @member {number} + * @see PIXI.RENDERER_TYPE + */ + this.type = RENDERER_TYPE.WEBGL; + + // this will be set by the contextSystem (this.context) + this.gl = null; + this.CONTEXT_UID = 0; + this.legacy = !!options.legacy; + + // TODO legacy! + + // runners! + this.runners = { + destroy: new Runner('destroy'), + contextChange: new Runner('contextChange', 1), + reset: new Runner('reset'), + update: new Runner('update'), + postrender: new Runner('postrender'), + prerender: new Runner('prerender'), + resize: new Runner('resize', 2), + }; + + 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(GeometrySystem, 'geometry') + .addSystem(FramebufferSystem, 'framebuffer') + .addSystem(StencilSystem, 'stencil') + .addSystem(ProjectionSystem, 'projection') + .addSystem(TextureGCSystem, 'textureGC') + .addSystem(FilterSystem, 'filter') + .addSystem(RenderTextureSystem, 'renderTexture') + .addSystem(BatchSystem, 'batch'); + + this.initPlugins(Renderer.__plugins); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ + if (options.context) + { + this.context.initFromContext(options.context); + } + else + { + this.context.initFromOptions({ + alpha: this.transparent, + antialias: options.antialias, + premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', + stencil: true, + 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); + } + + /** + * Add a new system to the renderer. + * @param {class} ClassRef - Class reference + * @param {string} [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. + * @return {PIXI.Renderer} Return instance of renderer + */ + addSystem(ClassRef, name) + { + if (!name) + { + name = ClassRef.name; + } + + // TODO - read name from class.name.. + + /* + if(name.includes('System')) + { + name = name.replace('System', ''); + name = name.charAt(0).toLowerCase() + name.slice(1); + } + */ + + const system = new ClassRef(this); + + if (this[name]) + { + throw new Error(`Whoops! ${name} is already a manger`); + } + + this[name] = system; + + for (const i in this.runners) + { + this.runners[i].add(system); + } + + return this; + + /** + * 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. + */ + } + + /** + * Renders the object to its webGL view + * + * @param {PIXI.DisplayObject} displayObject - the object to be rendered + * @param {PIXI.RenderTexture} renderTexture - The render texture to render to. + * @param {boolean} [clear] - Should the canvas be cleared before the new render + * @param {PIXI.Transform} [transform] - A transform to apply to the render texture before rendering. + * @param {boolean} [skipUpdateTransform] - Should we skip the update transform pass? + */ + render(displayObject, renderTexture, clear, transform, skipUpdateTransform) + { + // can be handy to know! + this.renderingToScreen = !renderTexture; + + this.runners.prerender.run(); + this.emit('prerender'); + + // 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.parent; + + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = 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.run(); + + this.emit('postrender'); + } + + /** + * Resizes the webGL view to the specified width and height. + * + * @param {number} screenWidth - the new width of the screen + * @param {number} screenHeight - the new height of the screen + */ + resize(screenWidth, screenHeight) + { + AbstractRenderer.prototype.resize.call(this, screenWidth, screenHeight); + + this.runners.resize.run(screenWidth, screenHeight); + } + + /** + * Resets the WebGL state so you can render things however you fancy! + * + * @return {PIXI.Renderer} Returns itself. + */ + reset() + { + this.runners.reset.run(); + + return this; + } + + clear() + { + this.framebuffer.bind(); + this.framebuffer.clear(); + } + + /** + * Removes everything from the renderer (event listeners, spritebatch, etc...) + * + * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. + * See: https://github.com/pixijs/pixi.js/issues/2233 + */ + destroy(removeView) + { + this.runners.destroy.run(); + + // call base destroy + super.destroy(removeView); + + // TODO nullify all the managers.. + this.gl = null; + } + + /** + * 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 + * builds and excluding plugins. + * @name PIXI.Renderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. + */ + + /** + * Adds a plugin to the renderer. + * + * @method + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + static registerPlugin(pluginName, ctor) + { + Renderer.__plugins = Renderer.__plugins || {}; + Renderer.__plugins[pluginName] = ctor; + } +} diff --git a/packages/core/src/System.js b/packages/core/src/System.js new file mode 100644 index 0000000..05a10c8 --- /dev/null +++ b/packages/core/src/System.js @@ -0,0 +1,42 @@ +/** + * System is a base class used for extending systems used by the {@link PIXI.Renderer} + * @see PIXI.Renderer#addSystem + * @class + * @memberof PIXI + */ +export default class System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + /** + * The renderer this manager works for. + * + * @member {PIXI.Renderer} + */ + this.renderer = renderer; + + this.renderer.runners.contextChange.add(this); + } + + /** + * Generic method called when there is a WebGL context change. + * + */ + contextChange() + { + // do some codes init! + } + + /** + * Generic destroy methods to be overridden by the subclass + * + */ + destroy() + { + this.renderer.runners.contextChange.remove(this); + this.renderer = null; + } +} diff --git a/packages/core/src/batch/BatchSystem.js b/packages/core/src/batch/BatchSystem.js new file mode 100644 index 0000000..b04c249 --- /dev/null +++ b/packages/core/src/batch/BatchSystem.js @@ -0,0 +1,68 @@ +import System from '../System'; +import ObjectRenderer from './ObjectRenderer'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class BatchSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + super(renderer); + + /** + * An empty renderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.emptyRenderer = new ObjectRenderer(renderer); + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.currentRenderer = this.emptyRenderer; + } + + /** + * Changes the current renderer to the one given in parameter + * + * @param {PIXI.ObjectRenderer} objectRenderer - The object renderer to use. + */ + setObjectRenderer(objectRenderer) + { + if (this.currentRenderer === objectRenderer) + { + return; + } + + this.currentRenderer.stop(); + this.currentRenderer = objectRenderer; + + this.renderer.state.setState(objectRenderer.state); + + this.currentRenderer.start(); + } + + /** + * This should be called if you wish to do some custom rendering + * It will basically render anything that may be batched up such as sprites + * + */ + flush() + { + this.setObjectRenderer(this.emptyRenderer); + } + + reset() + { + this.setObjectRenderer(this.emptyRenderer); + } +} diff --git a/packages/core/src/batch/ObjectRenderer.js b/packages/core/src/batch/ObjectRenderer.js new file mode 100644 index 0000000..0c851a9 --- /dev/null +++ b/packages/core/src/batch/ObjectRenderer.js @@ -0,0 +1,48 @@ +import System from '../System'; + +/** + * Base for a common object renderer that can be used as a system renderer plugin. + * + * @class + * @extends PIXI.System + * @memberof PIXI + */ +export default class ObjectRenderer extends System +{ + /** + * Starts the renderer and sets the shader + * + */ + start() + { + // set the shader.. + } + + /** + * Stops the renderer + * + */ + stop() + { + this.flush(); + } + + /** + * Stub method for rendering content and emptying the current batch. + * + */ + flush() + { + // flush! + } + + /** + * Renders an object + * + * @param {PIXI.DisplayObject} object - The object to render. + */ + render(object) // eslint-disable-line no-unused-vars + { + // render the object + } +} diff --git a/packages/core/src/context/ContextSystem.js b/packages/core/src/context/ContextSystem.js new file mode 100644 index 0000000..301aab4 --- /dev/null +++ b/packages/core/src/context/ContextSystem.js @@ -0,0 +1,186 @@ +import System from '../System'; +import { settings } from '@pixi/settings'; + +let CONTEXT_UID = 0; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class ContextSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.webGLVersion = 1; + + this.handleContextLost = this.handleContextLost.bind(this); + this.handleContextRestored = this.handleContextRestored.bind(this); + + this.extensions = {}; + + renderer.view.addEventListener('webglcontextlost', this.handleContextLost, false); + renderer.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + } + + get isLost() + { + return (!this.gl || this.gl.isContextLost()); + } + + contextChange(gl) + { + this.gl = gl; + + // restore a context if it was previously lost + if (gl.isContextLost() && gl.getExtension('WEBGL_lose_context')) + { + gl.getExtension('WEBGL_lose_context').restoreContext(); + } + } + + initFromContext(gl) + { + this.gl = gl; + this.validateContext(gl); + this.renderer.gl = gl; + this.renderer.CONTEXT_UID = CONTEXT_UID++; + this.renderer.runners.contextChange.run(gl); + } + + initFromOptions(options) + { + const gl = this.createContext(this.renderer.view, options); + + this.initFromContext(gl); + } + + /** + * Helper class to create a webGL Context + * + * @class + * @memberof PIXI.glCore + * @param canvas {HTMLCanvasElement} the canvas element that we will get the context from + * @param options {Object} An options object that gets passed in to the canvas element containing the context attributes, + * see https://developer.mozilla.org/en/docs/Web/API/HTMLCanvasElement/getContext for the options available + * @return {WebGLRenderingContext} the WebGL context + */ + createContext(canvas, options) + { + let gl; + + if (settings.PREFER_WEBGL_2) + { + gl = canvas.getContext('webgl2', options); + } + + if (gl) + { + this.webGLVersion = 2; + } + else + { + this.webGLVersion = 1; + + gl = canvas.getContext('webgl', options) + || canvas.getContext('experimental-webgl', options); + + if (!gl) + { + // fail, not able to get a context + throw new Error('This browser does not support webGL. Try using the canvas renderer'); + } + } + + this.gl = gl; + + this.getExtensions(); + + return gl; + } + + getExtensions() + { + // time to set up default etensions that pixi uses.. + const gl = this.gl; + const extensions = this.extensions; + + if (this.webGLVersion === 1) + { + extensions.drawBuffers = gl.getExtension('WEBGL_draw_buffers'); + extensions.depthTexture = gl.getExtension('WEBKIT_WEBGL_depth_texture'); + extensions.floatTexture = gl.getExtension('OES_texture_float'); + extensions.loseContext = gl.getExtension('WEBGL_lose_context'); + + extensions.vertexArrayObject = gl.getExtension('OES_vertex_array_object') + || gl.getExtension('MOZ_OES_vertex_array_object') + || gl.getExtension('WEBKIT_OES_vertex_array_object'); + } + + // we don't use any specific WebGL 2 ones yet! + } + + /** + * Handles a lost webgl context + * + * @private + * @param {WebGLContextEvent} event - The context lost event. + */ + handleContextLost(event) + { + event.preventDefault(); + } + + /** + * Handles a restored webgl context + * + * @private + */ + handleContextRestored() + { + this.renderer.runners.contextChange.run(this.gl); + } + + destroy() + { + const view = this.renderer.view; + + // remove listeners + view.removeEventListener('webglcontextlost', this.handleContextLost); + view.removeEventListener('webglcontextrestored', this.handleContextRestored); + + this.gl.useProgram(null); + + if (this.extensions.loseContext) + { + this.extensions.loseContext.loseContext(); + } + } + + postrender() + { + this.gl.flush(); + } + + validateContext(gl) + { + const attributes = gl.getContextAttributes(); + + // this is going to be fairly simple for now.. but at least we have room to grow! + if (!attributes.stencil) + { + /* eslint-disable max-len */ + + /* eslint-disable no-console */ + console.warn('Provided WebGL context does not have a stencil buffer, masks may not render correctly'); + /* eslint-enable no-console */ + + /* eslint-enable max-len */ + } + } +} diff --git a/packages/core/src/filters/Filter.js b/packages/core/src/filters/Filter.js new file mode 100644 index 0000000..9d8cad8 --- /dev/null +++ b/packages/core/src/filters/Filter.js @@ -0,0 +1,140 @@ +import Shader from '../shader/Shader'; +import Program from '../shader/Program'; +import State from '../state/State'; +import { settings } from '@pixi/settings'; +// import extractUniformsFromSrc from './extractUniformsFromSrc'; +import defaultVertex from './defaultFilter.vert'; +import defaultFragment from './defaultFilter.frag'; + +// let math = require('../../math'); +/** + * @class + * @memberof PIXI + * @extends PIXI.Shader + */ +export default class Filter extends Shader +{ + /** + * @param {string} [vertexSrc] - The source of the vertex shader. + * @param {string} [fragmentSrc] - The source of the fragment shader. + * @param {object} [uniforms] - Custom uniforms to use to augment the built-in ones. + */ + constructor(vertexSrc, fragmentSrc, uniforms) + { + const program = Program.from(vertexSrc, fragmentSrc); + + super(program, uniforms); + + /** + * The padding of the filter. Some filters require extra space to breath such as a blur. + * Increasing this will add extra width and height to the bounds of the object that the + * filter is applied to. + * + * @member {number} + */ + this.padding = 0; + + /** + * The resolution of the filter. Setting this to be lower will lower the quality but + * increase the performance of the filter. + * + * @member {number} + */ + this.resolution = settings.RESOLUTION; + + /** + * If enabled is true the filter is applied, if false it will not. + * + * @member {boolean} + */ + this.enabled = true; + + /** + * If enabled, PixiJS will fit the filter area into boundaries for better performance. + * Switch it off if it does not work for specific shader. + * + * @member {boolean} + */ + this.autoFit = true; + + /** + * Legacy filters use position and uvs from attributes + * @member {boolean} + * @readonly + */ + this.legacy = !!this.program.attributeData.aTextureCoord; + + /** + * the webGL state the filter requires to render + * @member {PIXI.State} + */ + this.state = new State(); + } + + /** + * Applies the filter + * + * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + * @param {boolean} clear - Should the output be cleared before rendering to it + * @param {object} [currentState] - It's current state of filter. + * There are some useful properties in the currentState : + * target, filters, sourceFrame, destinationFrame, renderTarget, resolution + */ + apply(filterManager, input, output, clear, currentState, derp) // eslint-disable-line no-unused-vars + { + // do as you please! + + filterManager.applyFilter(this, input, output, clear, currentState, derp); + + // or just do a regular render.. + } + + /** + * Sets the blendmode of the filter + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL + */ + get blendMode() + { + return this.state.blendMode; + } + + set blendMode(value) // eslint-disable-line require-jsdoc + { + this.state.blendMode = value; + } + + /** + * The default vertex shader source + * + * @static + * @constant + */ + static get defaultVertexSrc() + { + return defaultVertex; + } + + /** + * The default fragment shader source + * + * @static + * @constant + */ + static get defaultFragmentSrc() + { + return defaultFragment; + } +} + +/** + * Used for caching shader IDs + * + * @static + * @private + */ +Filter.SOURCE_KEY_MAP = {}; + diff --git a/packages/core/src/filters/FilterSystem.js b/packages/core/src/filters/FilterSystem.js new file mode 100644 index 0000000..3174318 --- /dev/null +++ b/packages/core/src/filters/FilterSystem.js @@ -0,0 +1,431 @@ +import System from '../System'; + +import RenderTexture from '../renderTexture/RenderTexture'; +import Quad from '../utils/Quad'; +import QuadUv from '../utils/QuadUv'; +import { Rectangle } from '@pixi/math'; +import * as filterTransforms from './filterTransforms'; +import bitTwiddle from 'bit-twiddle'; +import UniformGroup from '../shader/UniformGroup'; +import { DRAW_MODES } from '@pixi/constants'; + +// +/** + * @ignore + * @class + */ +class FilterState +{ + /** + * + */ + constructor() + { + this.renderTexture = null; + this.sourceFrame = new Rectangle(); + this.destinationFrame = new Rectangle(); + this.filters = []; + this.target = null; + this.legacy = false; + this.resolution = 1; + } +} + +/** + * @class + * @memberof PIXI.systems + * @extends PIXI.System + */ +export default class FilterSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + /** + * stores a bunch of PO2 textures used for filtering + * @type {Object} + */ + this.texturePool = {}; + + /** + * a pool for storing filter states, save us creating new ones each tick + * @type {Array} + */ + this.statePool = []; + + /** + * A very simple geometry used when drawing a filter effect to the screen + * @type {Quad} + */ + this.quad = new Quad(); + + this.quadUv = new QuadUv(); + + /** + * Temporary rect for maths + * @type {PIXI.Rectangle} + */ + this.tempRect = new Rectangle(); + + this.activeState = {}; + + /** + * this uniform group is attached to filter uniforms when used + * @type {UniformGroup} + */ + this.globalUniforms = new UniformGroup({ + sourceFrame: this.tempRect, + destinationFrame: this.tempRect, + + // legacy variables + filterArea: new Float32Array(4), + filterClamp: new Float32Array(4), + }, true); + } + + /** + * Adds a new filter to the System. + * + * @param {PIXI.DisplayObject} target - The target of the filter to render. + * @param {PIXI.Filter[]} filters - The filters to apply. + */ + push(target, filters) + { + const renderer = this.renderer; + const filterStack = this.renderer.renderTexture.defaultFilterStack; + const state = this.statePool.pop() || new FilterState(); + + let resolution = filters[0].resolution; + let padding = filters[0].padding; + let autoFit = filters[0].autoFit; + let legacy = filters[0].legacy; + + for (let i = 1; i < filters.length; i++) + { + const filter = filters[i]; + + // lets use the lowest resolution.. + resolution = Math.min(resolution, filter.resolution); + // and the largest amount of padding! + padding = Math.max(padding, filter.padding); + // only auto fit if all filters are autofit + autoFit = autoFit || filter.autoFit; + + legacy = legacy || filter.legacy; + } + + filterStack.push(state); + + state.resolution = resolution; + + state.legacy = legacy; + + // round to whole number based on resolution + // TODO move that to the shader too? + state.sourceFrame = target.filterArea || target.getBounds(true); + + state.sourceFrame.pad(padding); + + if (autoFit) + { + state.sourceFrame.fit(this.renderer.renderTexture.destinationFrame); + } + + state.sourceFrame.round(resolution); + + state.renderTexture = this.getPotFilterTexture(state.sourceFrame.width, state.sourceFrame.height, resolution); + state.filters = filters; + + state.destinationFrame.width = state.renderTexture.width; + state.destinationFrame.height = state.renderTexture.height; + + state.renderTexture.filterFrame = state.sourceFrame; + renderer.renderTexture.bind(state.renderTexture, state.sourceFrame);// /, state.destinationFrame); + renderer.renderTexture.clear(); + } + + /** + * Pops off the filter and applies it. + * + */ + pop() + { + const renderer = this.renderer; + const filterStack = renderer.renderTexture.defaultFilterStack; + const state = filterStack.pop(); + const filters = state.filters; + + this.activeState = state; + + const globalUniforms = this.globalUniforms.uniforms; + + globalUniforms.sourceFrame = state.sourceFrame; + globalUniforms.destinationFrame = state.destinationFrame; + globalUniforms.resolution = state.resolution; + + // only update the rect if its legacy.. + if (state.legacy) + { + const filterArea = globalUniforms.filterArea; + const filterClamp = globalUniforms.filterClamp; + + filterArea[0] = state.destinationFrame.width; + filterArea[1] = state.destinationFrame.height; + filterArea[2] = state.sourceFrame.x; + filterArea[3] = state.sourceFrame.y; + + filterClamp[0] = 0.5 / state.resolution / state.destinationFrame.width; + filterClamp[1] = 0.5 / state.resolution / state.destinationFrame.height; + filterClamp[2] = (state.sourceFrame.width - 0.5) / state.resolution / state.destinationFrame.width; + filterClamp[3] = (state.sourceFrame.height - 0.5) / state.resolution / state.destinationFrame.height; + } + + this.globalUniforms.update(); + + const lastState = filterStack[filterStack.length - 1]; + + if (filters.length === 1) + { + filters[0].apply(this, state.renderTexture, lastState.renderTexture, false, state); + + this.returnFilterTexture(state.renderTexture); + } + else + { + let flip = state.renderTexture; + let flop = this.getPotFilterTexture( + flip.width, + flip.height, + state.resolution + ); + + flop.filterFrame = flip.filterFrame; + + let i = 0; + + for (i = 0; i < filters.length - 1; ++i) + { + filters[i].apply(this, flip, flop, true, state); + + const t = flip; + + flip = flop; + flop = t; + } + + filters[i].apply(this, flip, lastState.renderTexture, false, state); + + this.returnFilterTexture(flip); + this.returnFilterTexture(flop); + } + + this.statePool.push(state); + } + + /** + * Draws a filter. + * + * @param {PIXI.Filter} filter - The filter to draw. + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + * @param {boolean} clear - Should the output be cleared before rendering to it + */ + applyFilter(filter, input, output, clear) + { + const renderer = this.renderer; + + renderer.renderTexture.bind(output, output ? output.filterFrame : null); + + if (clear) + { + // gl.disable(gl.SCISSOR_TEST); + renderer.renderTexture.clear(); + // gl.enable(gl.SCISSOR_TEST); + } + + // set the uniforms.. + filter.uniforms.uSampler = input; + filter.uniforms.filterGlobals = this.globalUniforms; + + // TODO make it so that the order of this does not matter.. + // because it does at the moment cos of global uniforms. + // they need to get resynced + + renderer.state.setState(filter.state); + renderer.shader.bind(filter); + + if (filter.legacy) + { + this.quadUv.map(input._frame, input.filterFrame); + + renderer.geometry.bind(this.quadUv); + renderer.geometry.draw(DRAW_MODES.TRIANGLES); + } + else + { + renderer.geometry.bind(this.quad); + renderer.geometry.draw(DRAW_MODES.TRIANGLE_STRIP); + } + } + + /** + * Calculates the mapped matrix. + * + * TODO playing around here.. this is temporary - (will end up in the shader) + * this returns a matrix that will normalise map filter cords in the filter to screen space + * + * @param {PIXI.Matrix} outputMatrix - the matrix to output to. + * @return {PIXI.Matrix} The mapped matrix. + */ + calculateScreenSpaceMatrix(outputMatrix) + { + const currentState = this.activeState; + + return filterTransforms.calculateScreenSpaceMatrix( + outputMatrix, + currentState.sourceFrame, + currentState.destinationFrame + ); + } + + /** + * This will map the filter coord so that a texture can be used based on the transform of a sprite + * + * @param {PIXI.Matrix} outputMatrix - The matrix to output to. + * @param {PIXI.Sprite} sprite - The sprite to map to. + * @return {PIXI.Matrix} The mapped matrix. + */ + calculateSpriteMatrix(outputMatrix, sprite) + { + const currentState = this.activeState; + + return filterTransforms.calculateSpriteMatrix( + outputMatrix, + currentState.sourceFrame, + currentState.destinationFrame, + sprite + ); + } + + /** + * Destroys this Filter System. + * + * @param {boolean} [contextLost=false] context was lost, do not free shaders + * + */ + destroy(contextLost = false) + { + if (!contextLost) + { + this.emptyPool(); + } + else + { + this.texturePool = {}; + } + } + + /** + * Gets a Power-of-Two render texture. + * + * TODO move to a seperate class could be on renderer? + * also - could cause issue with multiple contexts? + * + * @private + * @param {WebGLRenderingContext} gl - The webgl rendering context + * @param {number} minWidth - The minimum width of the render target. + * @param {number} minHeight - The minimum height of the render target. + * @param {number} resolution - The resolution of the render target. + * @return {PIXI.RenderTarget} The new render target. + */ + getPotFilterTexture(minWidth, minHeight, resolution) + { + minWidth = bitTwiddle.nextPow2(minWidth); + minHeight = bitTwiddle.nextPow2(minHeight); + resolution = resolution || 1; + + const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + + if (!this.texturePool[key]) + { + this.texturePool[key] = []; + } + + let renderTexture = this.texturePool[key].pop(); + + if (!renderTexture) + { + // temporary bypass cache.. + // internally - this will cause a texture to be bound.. + renderTexture = RenderTexture.create({ + width: minWidth, + height: minHeight, + resolution, + }); + } + + return renderTexture; + } + + /** + * Gets extra render texture to use inside current filter + * + * @param {number} resolution resolution of the renderTexture + * @returns {PIXI.RenderTexture} + */ + getFilterTexture(resolution) + { + const rt = this.activeState.renderTexture; + + const filterTexture = this.getPotFilterTexture(rt.width, rt.height, resolution || rt.baseTexture.resolution); + + filterTexture.filterFrame = rt.filterFrame; + + return filterTexture; + } + + /** + * Frees a render texture back into the pool. + * + * @param {PIXI.RenderTarget} renderTexture - The renderTarget to free + */ + returnFilterTexture(renderTexture) + { + renderTexture.filterFrame = null; + + const base = renderTexture.baseTexture; + + const minWidth = base.width; + const minHeight = base.height; + + const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + + this.texturePool[key].push(renderTexture); + } + + /** + * Empties the texture pool. + * + */ + emptyPool() + { + for (const i in this.texturePool) + { + const textures = this.texturePool[i]; + + if (textures) + { + for (let j = 0; j < textures.length; j++) + { + textures[j].destroy(true); + } + } + } + + this.texturePool = {}; + } +} diff --git a/packages/core/src/filters/defaultFilter.frag b/packages/core/src/filters/defaultFilter.frag new file mode 100644 index 0000000..a14c1be --- /dev/null +++ b/packages/core/src/filters/defaultFilter.frag @@ -0,0 +1,22 @@ +varying vec2 vTextureCoord; +varying vec2 vFilterCoord; + +uniform sampler2D uSampler; +uniform sampler2D filterSampler; + +void main(void){ + vec4 masky = texture2D(filterSampler, vFilterCoord); + vec4 sample = texture2D(uSampler, vTextureCoord); + vec4 color; + if(mod(vFilterCoord.x, 1.0) > 0.5) + { + color = vec4(1.0, 0.0, 0.0, 1.0); + } + else + { + color = vec4(0.0, 1.0, 0.0, 1.0); + } + // gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0); + gl_FragColor = mix(sample, masky, 0.5); + gl_FragColor *= sample.a; +} diff --git a/packages/core/src/filters/defaultFilter.vert b/packages/core/src/filters/defaultFilter.vert new file mode 100644 index 0000000..e1febca --- /dev/null +++ b/packages/core/src/filters/defaultFilter.vert @@ -0,0 +1,14 @@ +attribute vec2 aVertexPosition; +attribute vec2 aTextureCoord; + +uniform mat3 projectionMatrix; +uniform mat3 filterMatrix; + +varying vec2 vTextureCoord; +varying vec2 vFilterCoord; + +void main(void){ + gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); + vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy; + vTextureCoord = aTextureCoord ; +} diff --git a/packages/core/src/filters/filterTransforms.js b/packages/core/src/filters/filterTransforms.js new file mode 100644 index 0000000..87a3cd6 --- /dev/null +++ b/packages/core/src/filters/filterTransforms.js @@ -0,0 +1,50 @@ +import { Matrix } from '@pixi/math'; + +/** + * Calculates the mapped matrix + * @param filterArea {Rectangle} The filter area + * @param sprite {Sprite} the target sprite + * @param outputMatrix {Matrix} @alvin + * @private + */ +// this returns a matrix that will normalise map filter cords in the filter to screen space +export function calculateScreenSpaceMatrix(outputMatrix, filterArea, textureSize) +{ + // TODO unwrap? + const mappedMatrix = outputMatrix.identity(); + + mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); + + mappedMatrix.scale(textureSize.width, textureSize.height); + + return mappedMatrix; +} + +export function calculateNormalizedScreenSpaceMatrix(outputMatrix, filterArea, textureSize) +{ + const mappedMatrix = outputMatrix.identity(); + + mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); + + const translateScaleX = (textureSize.width / filterArea.width); + const translateScaleY = (textureSize.height / filterArea.height); + + mappedMatrix.scale(translateScaleX, translateScaleY); + + return mappedMatrix; +} + +// this will map the filter coord so that a texture can be used based on the transform of a sprite +export function calculateSpriteMatrix(outputMatrix, filterArea, textureSize, sprite) +{ + const orig = sprite._texture.orig; + const mappedMatrix = outputMatrix.set(textureSize.width, 0, 0, textureSize.height, filterArea.x, filterArea.y); + const worldTransform = sprite.worldTransform.copyTo(Matrix.TEMP_MATRIX); + + worldTransform.invert(); + mappedMatrix.prepend(worldTransform); + mappedMatrix.scale(1.0 / orig.width, 1.0 / orig.height); + mappedMatrix.translate(sprite.anchor.x, sprite.anchor.y); + + return mappedMatrix; +} diff --git a/packages/core/src/filters/spriteMask/SpriteMaskFilter.js b/packages/core/src/filters/spriteMask/SpriteMaskFilter.js new file mode 100644 index 0000000..91697b7 --- /dev/null +++ b/packages/core/src/filters/spriteMask/SpriteMaskFilter.js @@ -0,0 +1,63 @@ +import Filter from '../Filter'; +import { Matrix } from '@pixi/math'; +import vertex from './spriteMaskFilter.vert'; +import fragment from './spriteMaskFilter.frag'; +import { default as TextureMatrix } from '../../textures/TextureMatrix'; + +/** + * The SpriteMaskFilter class + * + * @class + * @extends PIXI.Filter + * @memberof PIXI + */ +export default class SpriteMaskFilter extends Filter +{ + /** + * @param {PIXI.Sprite} sprite - the target sprite + */ + constructor(sprite) + { + const maskMatrix = new Matrix(); + + super(vertex, fragment); + + sprite.renderable = false; + + this.maskSprite = sprite; + this.maskMatrix = maskMatrix; + } + + /** + * Applies the filter + * + * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + */ + apply(filterManager, input, output) + { + const maskSprite = this.maskSprite; + const tex = this.maskSprite.texture; + + if (!tex.valid) + { + return; + } + if (!tex.transform) + { + // margin = 0.0, let it bleed a bit, shader code becomes easier + // assuming that atlas textures were made with 1-pixel padding + tex.transform = new TextureMatrix(tex, 0.0); + } + tex.transform.update(); + + this.uniforms.mask = tex; + this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite) + .prepend(tex.transform.mapCoord); + this.uniforms.alpha = maskSprite.worldAlpha; + this.uniforms.maskClamp = tex.transform.uClampFrame; + + filterManager.applyFilter(this, input, output); + } +} diff --git a/packages/core/src/filters/spriteMask/spriteMaskFilter.frag b/packages/core/src/filters/spriteMask/spriteMaskFilter.frag new file mode 100644 index 0000000..0e4aef8 --- /dev/null +++ b/packages/core/src/filters/spriteMask/spriteMaskFilter.frag @@ -0,0 +1,23 @@ +varying vec2 vMaskCoord; +varying vec2 vTextureCoord; + +uniform sampler2D uSampler; +uniform sampler2D mask; +uniform float alpha; +uniform vec4 maskClamp; + +void main(void) +{ + float clip = step(3.5, + step(maskClamp.x, vMaskCoord.x) + + step(maskClamp.y, vMaskCoord.y) + + step(vMaskCoord.x, maskClamp.z) + + step(vMaskCoord.y, maskClamp.w)); + + vec4 original = texture2D(uSampler, vTextureCoord); + vec4 masky = texture2D(mask, vMaskCoord); + + original *= (masky.r * masky.a * alpha * clip); + + gl_FragColor = original; +} diff --git a/packages/core/src/filters/spriteMask/spriteMaskFilter.vert b/packages/core/src/filters/spriteMask/spriteMaskFilter.vert new file mode 100644 index 0000000..d23a30c --- /dev/null +++ b/packages/core/src/filters/spriteMask/spriteMaskFilter.vert @@ -0,0 +1,16 @@ +attribute vec2 aVertexPosition; +attribute vec2 aTextureCoord; + +uniform mat3 projectionMatrix; +uniform mat3 otherMatrix; + +varying vec2 vMaskCoord; +varying vec2 vTextureCoord; + +void main(void) +{ + gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); + + vTextureCoord = aTextureCoord; + vMaskCoord = ( otherMatrix * vec3( aTextureCoord, 1.0) ).xy; +} diff --git a/packages/core/src/framebuffer/FrameBuffer.js b/packages/core/src/framebuffer/FrameBuffer.js new file mode 100644 index 0000000..84e9fcc --- /dev/null +++ b/packages/core/src/framebuffer/FrameBuffer.js @@ -0,0 +1,106 @@ +import Texture from '../textures/BaseTexture'; +import { FORMATS, TYPES } from '@pixi/constants'; + +/** + * Frame buffer + * @class + * @memberof PIXI + */ +export default class FrameBuffer +{ + constructor(width, height) + { + this.width = width || 100; + this.height = height || 100; + + this.stencil = false; + this.depth = false; + + this.dirtyId = 0; + this.dirtyFormat = 0; + this.dirtySize = 0; + + this.depthTexture = null; + this.colorTextures = []; + + this.glFrameBuffers = {}; + } + + get colorTexture() + { + return this.colorTextures[0]; + } + + addColorTexture(index, texture) + { + // TODO add some validation to the texture - same width / height etc? + this.colorTextures[index || 0] = texture || new Texture(null, { scaleMode: 0, + resolution: 1, + mipmap: false, + width: this.width, + height: this.height });// || new Texture(); + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + addDepthTexture(texture) + { + /* eslint-disable max-len */ + this.depthTexture = texture || new Texture(null, { scaleMode: 0, + resolution: 1, + width: this.width, + height: this.height, + mipmap: false, + format: FORMATS.DEPTH_COMPONENT, + type: TYPES.UNSIGNED_SHORT });// UNSIGNED_SHORT; + /* eslint-disable max-len */ + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + enableDepth() + { + this.depth = true; + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + enableStencil() + { + this.stencil = true; + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + resize(width, height) + { + if (width === this.width && height === this.height) return; + + this.width = width; + this.height = height; + + this.dirtyId++; + this.dirtySize++; + + for (let i = 0; i < this.colorTextures.length; i++) + { + this.colorTextures[i].setSize(width, height); + } + + if (this.depthTexture) + { + this.depthTexture.setSize(width, height); + } + } +} diff --git a/packages/core/src/framebuffer/FramebufferSystem.js b/packages/core/src/framebuffer/FramebufferSystem.js new file mode 100644 index 0000000..2cc2437 --- /dev/null +++ b/packages/core/src/framebuffer/FramebufferSystem.js @@ -0,0 +1,246 @@ +import System from '../System'; +import { Rectangle } from '@pixi/math'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class FramebufferSystem extends System +{ + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + contextChange() + { + this.gl = this.renderer.gl; + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + this.current = null; + this.viewport = new Rectangle(); + + this.drawBufferExtension = this.renderer.context.extensions.drawBuffers; + } + + bind(framebuffer, frame) + { + const gl = this.gl; + + this.current = framebuffer; + + if (framebuffer) + { + // TODO cacheing layer! + + const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID] || this.initFramebuffer(framebuffer); + + gl.bindFramebuffer(gl.FRAMEBUFFER, fbo.framebuffer); + // makesure all textures are unbound.. + + // now check for updates... + if (fbo.dirtyId !== framebuffer.dirtyId) + { + fbo.dirtyId = framebuffer.dirtyId; + + if (fbo.dirtyFormat !== framebuffer.dirtyFormat) + { + fbo.dirtyFormat = framebuffer.dirtyFormat; + this.updateFramebuffer(framebuffer); + } + else if (fbo.dirtySize !== framebuffer.dirtySize) + { + fbo.dirtySize = framebuffer.dirtySize; + this.resizeFramebuffer(framebuffer); + } + } + + for (let i = 0; i < framebuffer.colorTextures.length; i++) + { + if (framebuffer.colorTextures[i].texturePart) + { + this.renderer.texture.unbind(framebuffer.colorTextures[i].texture); + } + else + { + this.renderer.texture.unbind(framebuffer.colorTextures[i]); + } + } + + if (framebuffer.depthTexture) + { + this.renderer.texture.unbind(framebuffer.depthTexture); + } + + if (frame) + { + this.setViewport(frame.x, frame.y, frame.width, frame.height); + } + else + { + this.setViewport(0, 0, framebuffer.width, framebuffer.height); + } + } + else + { + gl.bindFramebuffer(gl.FRAMEBUFFER, null); + + if (frame) + { + this.setViewport(frame.x, frame.y, frame.width, frame.height); + } + else + { + this.setViewport(0, 0, this.renderer.width, this.renderer.height); + } + } + } + + setViewport(x, y, width, height) + { + const v = this.viewport; + + if (v.width !== width || v.height !== height || v.x !== x || v.y !== y) + { + v.x = x; + v.y = y; + v.width = width; + v.height = height; + + this.gl.viewport(x, y, width, height); + } + } + + get size() + { + if (this.current) + { + // TODO store temp + return { x: 0, y: 0, width: this.current.width, height: this.current.height }; + } + + return { x: 0, y: 0, width: this.renderer.width, height: this.renderer.height }; + } + + clear(r, g, b, a) + { + const gl = this.gl; + + // TODO clear color can be set only one right? + gl.clearColor(r, g, b, a); + gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); + } + + // private functions... + + initFramebuffer(framebuffer) + { + const gl = this.gl; + + // TODO - make this a class? + const fbo = { + framebuffer: gl.createFramebuffer(), + stencil: null, + dirtyId: 0, + dirtyFormat: 0, + dirtySize: 0, + }; + + framebuffer.glFrameBuffers[this.CONTEXT_UID] = fbo; + + return fbo; + } + + resizeFramebuffer(framebuffer) + { + const gl = this.gl; + + if (framebuffer.stencil || framebuffer.depth) + { + gl.bindRenderbuffer(gl.RENDERBUFFER, this.stencil); + gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); + } + } + + updateFramebuffer(framebuffer) + { + const gl = this.gl; + + const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID]; + + // bind the color texture + const colorTextures = framebuffer.colorTextures; + + let count = colorTextures.length; + + if (!this.drawBufferExtension) + { + count = Math.min(count, 1); + } + + const activeTextures = []; + + for (let i = 0; i < count; i++) + { + const texture = framebuffer.colorTextures[i]; + + if (texture.texturePart) + { + this.renderer.texture.bind(texture.texture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.COLOR_ATTACHMENT0 + i, + gl.TEXTURE_CUBE_MAP_NEGATIVE_X + texture.side, + texture.texture._glTextures[this.CONTEXT_UID].texture, + 0); + } + else + { + this.renderer.texture.bind(texture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.COLOR_ATTACHMENT0 + i, + gl.TEXTURE_2D, + texture._glTextures[this.CONTEXT_UID].texture, + 0); + } + + activeTextures.push(gl.COLOR_ATTACHMENT0 + i); + } + + if (this.drawBufferExtension && activeTextures.length > 1) + { + this.drawBufferExtension.drawBuffersWEBGL(activeTextures); + } + + if (framebuffer.depthTexture) + { + const depthTextureExt = this.renderer.context.extensions.depthTexture; + + if (depthTextureExt) + { + const depthTexture = framebuffer.depthTexture; + + this.renderer.texture.bind(depthTexture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.DEPTH_ATTACHMENT, + gl.TEXTURE_2D, + depthTexture._glTextures[this.CONTEXT_UID].texture, + 0); + } + } + + if (framebuffer.stencil || framebuffer.depth) + { + fbo.stencil = gl.createRenderbuffer(); + + gl.bindRenderbuffer(gl.RENDERBUFFER, fbo.stencil); + + // TODO.. this is depth AND stencil? + gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, fbo.stencil); + gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); + // fbo.enableStencil(); + } + } +} diff --git a/packages/core/src/geometry/GLBuffer.js b/packages/core/src/geometry/GLBuffer.js new file mode 100644 index 0000000..0477e8a --- /dev/null +++ b/packages/core/src/geometry/GLBuffer.js @@ -0,0 +1,9 @@ +export default class GLBuffer +{ + constructor(buffer) + { + this.buffer = buffer; + this.updateID = -1; + this.byteLength = -1; + } +} diff --git a/packages/core/src/geometry/GeometrySystem.js b/packages/core/src/geometry/GeometrySystem.js new file mode 100644 index 0000000..959f827 --- /dev/null +++ b/packages/core/src/geometry/GeometrySystem.js @@ -0,0 +1,388 @@ +import System from '../System'; +import GLBuffer from './GLBuffer'; + +const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class GeometrySystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this._activeGeometry = null; + this._activeVao = null; + + this.hasVao = true; + this.hasInstance = true; + } + + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + contextChange() + { + const gl = this.gl = this.renderer.gl; + + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + + // webgl2 + if (!gl.createVertexArray) + { + // webgl 1! + let nativeVaoExtension = this.renderer.context.extensions.vertexArrayObject; + + if (this.renderer.legacy) + { + nativeVaoExtension = null; + } + + if (nativeVaoExtension) + { + gl.createVertexArray = () => + nativeVaoExtension.createVertexArrayOES(); + + gl.bindVertexArray = (vao) => + nativeVaoExtension.bindVertexArrayOES(vao); + + gl.deleteVertexArray = (vao) => + nativeVaoExtension.deleteVertexArrayOES(vao); + } + else + { + this.hasVao = false; + gl.createVertexArray = () => + { + // empty + }; + + gl.bindVertexArray = () => + { + // empty + }; + + gl.deleteVertexArray = () => + { + // empty + }; + } + } + + if (!gl.vertexAttribDivisor) + { + const instanceExt = gl.getExtension('ANGLE_instanced_arrays'); + + if (instanceExt) + { + gl.vertexAttribDivisor = (a, b) => + instanceExt.vertexAttribDivisorANGLE(a, b); + + gl.drawElementsInstanced = (a, b, c, d, e) => + instanceExt.drawElementsInstancedANGLE(a, b, c, d, e); + + gl.drawArraysInstanced = (a, b, c, d) => + instanceExt.drawArraysInstancedANGLE(a, b, c, d); + } + else + { + this.hasInstance = false; + } + } + } + + /** + * Binds geometry so that is can be drawn. Creating a Vao if required + * @private + * @param {PIXI.Geometry} geometry instance of geometry to bind + */ + bind(geometry, shader) + { + shader = shader || this.renderer.shader.shader; + + const gl = this.gl; + + // not sure the best way to address this.. + // currently different shaders require different VAOs for the same geometry + // Still mulling over the best way to solve this one.. + // will likely need to modify the shader attribute locations at run time! + let vaos = geometry.glVertexArrayObjects[this.CONTEXT_UID]; + + if (!vaos) + { + geometry.glVertexArrayObjects[this.CONTEXT_UID] = vaos = {}; + } + + const vao = vaos[shader.program.id] || this.initGeometryVao(geometry, shader.program); + + this._activeGeometry = geometry; + + if (this._activeVao !== vao) + { + this._activeVao = vao; + + if (this.hasVao) + { + gl.bindVertexArray(vao); + } + else + { + this.activateVao(geometry, shader.program); + } + } + + // TODO - optimise later! + // don't need to loop through if nothing changed! + // maybe look to add an 'autoupdate' to geometry? + this.updateBuffers(); + } + + reset() + { + this.unbind(); + } + + updateBuffers() + { + const geometry = this._activeGeometry; + const gl = this.gl; + + for (let i = 0; i < geometry.buffers.length; i++) + { + const buffer = geometry.buffers[i]; + + const glBuffer = buffer._glBuffers[this.CONTEXT_UID]; + + if (buffer._updateID !== glBuffer.updateID) + { + glBuffer.updateID = buffer._updateID; + + // TODO can cache this on buffer! maybe added a getter / setter? + const type = buffer.index ? gl.ELEMENT_ARRAY_BUFFER : gl.ARRAY_BUFFER; + const drawType = buffer.static ? gl.STATIC_DRAW : gl.DYNAMIC_DRAW; + + gl.bindBuffer(type, glBuffer.buffer); + + if (glBuffer.byteLength >= buffer.data.byteLength) + { + // offset is always zero for now! + gl.bufferSubData(type, 0, buffer.data); + } + else + { + gl.bufferData(type, buffer.data, drawType); + } + } + } + } + + checkCompatability(geometry, program) + { + // geometry must have at least all the attributes that the shader requires. + const geometryAttributes = geometry.attributes; + const shaderAttributes = program.attributeData; + + for (const j in shaderAttributes) + { + if (!geometryAttributes[j]) + { + throw new Error(`shader and geometry incompatible, geometry missing the "${j}" attribute`); + } + } + } + + /** + * Creates a Vao with the same structure as the geometry and stores it on the geometry. + * @private + * @param {PIXI.Geometry} geometry instance of geometry to to generate Vao for + */ + initGeometryVao(geometry, program) + { + this.checkCompatability(geometry, program); + + const gl = this.gl; + const CONTEXT_UID = this.CONTEXT_UID; + const buffers = geometry.buffers; + const attributes = geometry.attributes; + + const tempStride = {}; + const tempStart = {}; + + for (const j in buffers) + { + tempStride[j] = 0; + tempStart[j] = 0; + } + + for (const j in attributes) + { + if (!attributes[j].size && program.attributeData[j]) + { + attributes[j].size = program.attributeData[j].size; + } + + tempStride[attributes[j].buffer] += attributes[j].size * byteSizeMap[attributes[j].type]; + } + + for (const j in attributes) + { + const attribute = attributes[j]; + const attribSize = attribute.size; + + if (attribute.stride === undefined) + { + if (tempStride[attribute.buffer] === attribSize * byteSizeMap[attribute.type]) + { + attribute.stride = 0; + } + else + { + attribute.stride = tempStride[attribute.buffer]; + } + } + + if (attribute.start === undefined) + { + attribute.start = tempStart[attribute.buffer]; + + tempStart[attribute.buffer] += attribSize * byteSizeMap[attribute.type]; + } + } + + // first update - and create the buffers! + // only create a gl buffer if it actually gets + for (let i = 0; i < buffers.length; i++) + { + const buffer = buffers[i]; + + if (!buffer._glBuffers[CONTEXT_UID]) + { + buffer._glBuffers[CONTEXT_UID] = new GLBuffer(gl.createBuffer()); + } + } + + // TODO - maybe make this a data object? + // lets wait to see if we need to first! + const vao = gl.createVertexArray(); + + gl.bindVertexArray(vao); + + this.activateVao(geometry, program); + + gl.bindVertexArray(null); + + // add it to the cache! + geometry.glVertexArrayObjects[this.CONTEXT_UID][program.id] = vao; + + return vao; + } + + activateVao(geometry, program) + { + const gl = this.gl; + const CONTEXT_UID = this.CONTEXT_UID; + const buffers = geometry.buffers; + const attributes = geometry.attributes; + + if (geometry.indexBuffer) + { + // first update the index buffer if we have one.. + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, geometry.indexBuffer._glBuffers[CONTEXT_UID].buffer); + } + + let lastBuffer = null; + + // add a new one! + for (const j in attributes) + { + const attribute = attributes[j]; + const buffer = buffers[attribute.buffer]; + const glBuffer = buffer._glBuffers[CONTEXT_UID]; + + if (program.attributeData[j]) + { + if (lastBuffer !== glBuffer) + { + gl.bindBuffer(gl.ARRAY_BUFFER, glBuffer.buffer); + + lastBuffer = glBuffer; + } + + const location = program.attributeData[j].location; + + // TODO introduce state again + // we can optimise this for older devices that have no VAOs + gl.enableVertexAttribArray(location); + + gl.vertexAttribPointer(location, + attribute.size, + attribute.type || gl.FLOAT, + attribute.normalized, + attribute.stride, + attribute.start); + + if (attribute.instance) + { + // TODO calculate instance count based of this... + if (this.hasInstance) + { + gl.vertexAttribDivisor(location, 1); + } + else + { + throw new Error('geometry error, GPU Instancing is not supported on this device'); + } + } + } + } + } + + draw(type, size, start, instanceCount) + { + const gl = this.gl; + const geometry = this._activeGeometry; + + // TODO.. this should not change so maybe cache the function? + + if (geometry.indexBuffer) + { + if (geometry.instanced) + { + /* eslint-disable max-len */ + gl.drawElementsInstanced(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2, instanceCount || 1); + /* eslint-enable max-len */ + } + else + { + gl.drawElements(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2); + } + } + else + if (geometry.instanced) + { + // TODO need a better way to calculate size.. + gl.drawArraysInstanced(type, start, size || geometry.getSize(), instanceCount || 1); + } + else + { + gl.drawArrays(type, start, size || geometry.getSize()); + } + + return this; + } + + unbind() + { + this.gl.bindVertexArray(null); + this._activeVao = null; + this._activeGeometry = null; + } +} diff --git a/packages/core/src/geometry/utils/setVertexAttribArrays.js b/packages/core/src/geometry/utils/setVertexAttribArrays.js new file mode 100644 index 0000000..23b6959 --- /dev/null +++ b/packages/core/src/geometry/utils/setVertexAttribArrays.js @@ -0,0 +1,54 @@ +// var GL_MAP = {}; + +/** + * @param gl {WebGLRenderingContext} The current WebGL context + * @param attribs {*} + * @param state {*} + */ +export default function setVertexAttribArrays(gl, attribs, state) +{ + let i; + + if (state) + { + const tempAttribState = state.tempAttribState; + const attribState = state.attribState; + + for (i = 0; i < tempAttribState.length; i++) + { + tempAttribState[i] = false; + } + + // set the new attribs + for (i = 0; i < attribs.length; i++) + { + tempAttribState[attribs[i].attribute.location] = true; + } + + for (i = 0; i < attribState.length; i++) + { + if (attribState[i] !== tempAttribState[i]) + { + attribState[i] = tempAttribState[i]; + + if (state.attribState[i]) + { + gl.enableVertexAttribArray(i); + } + else + { + gl.disableVertexAttribArray(i); + } + } + } + } + else + { + for (i = 0; i < attribs.length; i++) + { + const attrib = attribs[i]; + + gl.enableVertexAttribArray(attrib.attribute.location); + } + } +} diff --git a/packages/core/src/index.js b/packages/core/src/index.js index 58c456a..fd1f547 100644 --- a/packages/core/src/index.js +++ b/packages/core/src/index.js @@ -1,30 +1,30 @@ import * as resources from './textures/resources'; -import * as systems from './renderers/systems'; +import * as systems from './systems'; -export { resources }; export { systems }; +export { resources }; -export { default as Renderer } from './renderers/Renderer'; -export { default as AbstractRenderer } from './renderers/AbstractRenderer'; -export { default as FrameBuffer } from './textures/FrameBuffer'; +export { default as System } from './System'; +export { default as Renderer } from './Renderer'; +export { default as AbstractRenderer } from './AbstractRenderer'; +export { default as FrameBuffer } from './framebuffer/FrameBuffer'; export { default as CubeTexture } from './textures/CubeTexture'; export { default as BaseTexture } from './textures/BaseTexture'; export { default as Texture } from './textures/Texture'; export { default as TextureMatrix } from './textures/TextureMatrix'; -export { default as RenderTexture } from './textures/RenderTexture'; -export { default as BaseRenderTexture } from './textures/BaseRenderTexture'; +export { default as RenderTexture } from './renderTexture/RenderTexture'; +export { default as BaseRenderTexture } from './renderTexture/BaseRenderTexture'; export { default as TextureUvs } from './textures/TextureUvs'; -export { default as State } from './renderers/State'; -export { default as ObjectRenderer } from './renderers/utils/ObjectRenderer'; -export { default as Quad } from './renderers/utils/Quad'; -export { default as QuadUv } from './renderers/utils/QuadUv'; -export { default as checkMaxIfStatmentsInShader } from './renderers/utils/checkMaxIfStatmentsInShader'; +export { default as State } from './state/State'; +export { default as ObjectRenderer } from './batch/ObjectRenderer'; +export { default as Quad } from './utils/Quad'; +export { default as QuadUv } from './utils/QuadUv'; +export { default as checkMaxIfStatmentsInShader } from './shader/utils/checkMaxIfStatmentsInShader'; export { default as Shader } from './shader/Shader'; export { default as Program } from './shader/Program'; export { default as UniformGroup } from './shader/UniformGroup'; -export { default as SpriteMaskFilter } from './renderers/filters/spriteMask/SpriteMaskFilter'; -export { default as Filter } from './renderers/filters/Filter'; +export { default as SpriteMaskFilter } from './filters/spriteMask/SpriteMaskFilter'; +export { default as Filter } from './filters/Filter'; export { default as Attribute } from './geometry/Attribute'; export { default as Buffer } from './geometry/Buffer'; export { default as Geometry } from './geometry/Geometry'; - diff --git a/packages/core/src/mask/MaskSystem.js b/packages/core/src/mask/MaskSystem.js new file mode 100644 index 0000000..99174c2 --- /dev/null +++ b/packages/core/src/mask/MaskSystem.js @@ -0,0 +1,199 @@ +import System from '../System'; +import AlphaMaskFilter from '../filters/spriteMask/SpriteMaskFilter'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class MaskSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + // TODO - we don't need both! + this.scissor = false; + this.scissorData = null; + this.scissorRenderTarget = null; + + this.enableScissor = false; + + this.alphaMaskPool = []; + this.alphaMaskIndex = 0; + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.DisplayObject} target - Display Object to push the mask to + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + push(target, maskData) + { + // TODO the root check means scissor rect will not + // be used on render textures more info here: + // https://github.com/pixijs/pixi.js/pull/3545 + + if (maskData.texture) + { + this.pushSpriteMask(target, maskData); + } + else if (this.enableScissor + && !this.scissor + && this.renderer._activeRenderTarget.root + && !this.renderer.stencil.stencilMaskStack.length + && maskData.isFastRect()) + { + const matrix = maskData.worldTransform; + + let rot = Math.atan2(matrix.b, matrix.a); + + // use the nearest degree! + rot = Math.round(rot * (180 / Math.PI)); + + if (rot % 90) + { + this.pushStencilMask(maskData); + } + else + { + this.pushScissorMask(target, maskData); + } + } + else + { + this.pushStencilMask(maskData); + } + } + + /** + * Removes the last mask from the mask stack and doesn't return it. + * + * @param {PIXI.DisplayObject} target - Display Object to pop the mask from + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + pop(target, maskData) + { + if (maskData.texture) + { + this.popSpriteMask(target, maskData); + } + else if (this.enableScissor && !this.renderer.stencil.stencilMaskStack.length) + { + this.popScissorMask(target, maskData); + } + else + { + this.popStencilMask(target, maskData); + } + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.RenderTarget} target - Display Object to push the sprite mask to + * @param {PIXI.Sprite} maskData - Sprite to be used as the mask + */ + pushSpriteMask(target, maskData) + { + let alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; + + if (!alphaMaskFilter) + { + alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; + } + + alphaMaskFilter[0].resolution = this.renderer.resolution; + alphaMaskFilter[0].maskSprite = maskData; + + // TODO - may cause issues! + target.filterArea = maskData.getBounds(true); + + this.renderer.filterSystem.pushFilter(target, alphaMaskFilter); + + this.alphaMaskIndex++; + } + + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popSpriteMask() + { + this.renderer.filterSystem.popFilter(); + this.alphaMaskIndex--; + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + pushStencilMask(maskData) + { + this.renderer.batch.flush(); + this.renderer.stencil.pushStencil(maskData); + } + + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popStencilMask() + { + // this.renderer.currentRenderer.stop(); + this.renderer.stencil.popStencil(); + } + + /** + * + * @param {PIXI.DisplayObject} target - Display Object to push the mask to + * @param {PIXI.Graphics} maskData - The masking data. + */ + pushScissorMask(target, maskData) + { + maskData.renderable = true; + + const renderTarget = this.renderer._activeRenderTarget; + + const bounds = maskData.getBounds(); + + bounds.fit(renderTarget.size); + maskData.renderable = false; + + this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); + + const resolution = this.renderer.resolution; + + this.renderer.gl.scissor( + bounds.x * resolution, + (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, + bounds.width * resolution, + bounds.height * resolution + ); + + this.scissorRenderTarget = renderTarget; + this.scissorData = maskData; + this.scissor = true; + } + + /** + * + * + */ + popScissorMask() + { + this.scissorRenderTarget = null; + this.scissorData = null; + this.scissor = false; + + // must be scissor! + const gl = this.renderer.gl; + + gl.disable(gl.SCISSOR_TEST); + } +} diff --git a/packages/core/src/mask/StencilSystem.js b/packages/core/src/mask/StencilSystem.js new file mode 100644 index 0000000..62796a5 --- /dev/null +++ b/packages/core/src/mask/StencilSystem.js @@ -0,0 +1,133 @@ +import System from '../System'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class StencilSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + this.stencilMaskStack = []; + } + + /** + * Changes the mask stack that is used by this System. + * + * @param {PIXI.Graphics[]} stencilMaskStack - The mask stack + */ + setMaskStack(stencilMaskStack) + { + const gl = this.renderer.gl; + + if (stencilMaskStack.length !== this.stencilMaskStack.length) + { + if (stencilMaskStack.length === 0) + { + gl.disable(gl.STENCIL_TEST); + } + else + { + gl.enable(gl.STENCIL_TEST); + } + } + + this.stencilMaskStack = stencilMaskStack; + } + + /** + * Applies the Mask and adds it to the current stencil stack. @alvin + * + * @param {PIXI.Graphics} graphics - The mask + */ + pushStencil(graphics) + { + this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); + + // this.renderer._activeRenderTarget.attachStencilBuffer(); + + const gl = this.renderer.gl; + const prevMaskCount = this.stencilMaskStack.length; + + if (prevMaskCount === 0) + { + gl.enable(gl.STENCIL_TEST); + } + + this.stencilMaskStack.push(graphics); + + // Increment the refference stencil value where the new mask overlaps with the old ones. + gl.colorMask(false, false, false, false); + gl.stencilFunc(gl.EQUAL, prevMaskCount, this._getBitwiseMask()); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.INCR); + this.renderer.plugins.graphics.render(graphics); + + this._useCurrent(); + } + + /** + * Removes the last mask from the stencil stack. @alvin + */ + popStencil() + { + this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); + + const gl = this.renderer.gl; + const graphics = this.stencilMaskStack.pop(); + + if (this.stencilMaskStack.length === 0) + { + // the stack is empty! + gl.disable(gl.STENCIL_TEST); + gl.clear(gl.STENCIL_BUFFER_BIT); + gl.clearStencil(0); + } + else + { + // Decrement the refference stencil value where the popped mask overlaps with the other ones + gl.colorMask(false, false, false, false); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.DECR); + this.renderer.plugins.graphics.render(graphics); + + this._useCurrent(); + } + } + + /** + * Setup renderer to use the current stencil data. + */ + _useCurrent() + { + const gl = this.renderer.gl; + + gl.colorMask(true, true, true, true); + gl.stencilFunc(gl.EQUAL, this.stencilMaskStack.length, this._getBitwiseMask()); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP); + } + + /** + * Fill 1s equal to the number of acitve stencil masks. + * + * @return {number} The bitwise mask. + */ + _getBitwiseMask() + { + return (1 << this.stencilMaskStack.length) - 1; + } + + /** + * Destroys the mask stack. + * + */ + destroy() + { + super.destroy(this); + + this.stencilMaskStack = null; + } +} diff --git a/packages/core/src/projection/ProjectionSystem.js b/packages/core/src/projection/ProjectionSystem.js new file mode 100644 index 0000000..6e63b73 --- /dev/null +++ b/packages/core/src/projection/ProjectionSystem.js @@ -0,0 +1,73 @@ +import System from '../System'; +import { Matrix } from '@pixi/math'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class ProjectionSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.projectionMatrix = new Matrix(); + } + + update(destinationFrame, sourceFrame, resolution, root) + { + this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; + this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; + + this.calculateProjection(this.destinationFrame, this.sourceFrame, resolution, root); + + this.renderer.globalUniforms.uniforms.projectionMatrix = this.projectionMatrix; + this.renderer.globalUniforms.update(); + } + + /** + * Updates the projection matrix based on a projection frame (which is a rectangle) + * + * @param {Rectangle} destinationFrame - The destination frame. + * @param {Rectangle} sourceFrame - The source frame. + */ + calculateProjection(destinationFrame, sourceFrame, resolution, root) + { + const pm = this.projectionMatrix; + + // I don't think we will need this line.. + // pm.identity(); + + if (!root) + { + pm.a = (1 / destinationFrame.width * 2) * resolution; + pm.d = (1 / destinationFrame.height * 2) * resolution; + + pm.tx = -1 - (sourceFrame.x * pm.a); + pm.ty = -1 - (sourceFrame.y * pm.d); + } + else + { + pm.a = (1 / destinationFrame.width * 2) * resolution; + pm.d = (-1 / destinationFrame.height * 2) * resolution; + + pm.tx = -1 - (sourceFrame.x * pm.a); + pm.ty = 1 - (sourceFrame.y * pm.d); + } + } + + /** + * Sets the transform of the active render target to the given matrix + * + * @param {PIXI.Matrix} matrix - The transformation matrix + */ + setTransform()// matrix) + { + // this._activeRenderTarget.transform = matrix; + } +} diff --git a/packages/core/src/renderTexture/BaseRenderTexture.js b/packages/core/src/renderTexture/BaseRenderTexture.js new file mode 100644 index 0000000..88eb83b --- /dev/null +++ b/packages/core/src/renderTexture/BaseRenderTexture.js @@ -0,0 +1,137 @@ +import BaseTexture from '../textures/BaseTexture'; +import FrameBuffer from '../framebuffer/FrameBuffer'; + +/** + * A BaseRenderTexture is a special texture that allows any PixiJS display object to be rendered to it. + * + * __Hint__: All DisplayObjects (i.e. Sprites) that render to a BaseRenderTexture should be preloaded + * otherwise black rectangles will be drawn instead. + * + * A BaseRenderTexture takes a snapshot of any Display Object given to its render method. The position + * and rotation of the given Display Objects is ignored. For example: + * + * ```js + * let renderer = PIXI.autoDetectRenderer(1024, 1024, { view: canvas, ratio: 1 }); + * let baseRenderTexture = new PIXI.BaseRenderTexture(renderer, 800, 600); + * let sprite = PIXI.Sprite.fromImage("spinObj_01.png"); + * + * sprite.position.x = 800/2; + * sprite.position.y = 600/2; + * sprite.anchor.x = 0.5; + * sprite.anchor.y = 0.5; + * + * baseRenderTexture.render(sprite); + * ``` + * + * The Sprite in this case will be rendered using its local transform. To render this sprite at 0,0 + * you can clear the transform + * + * ```js + * + * sprite.setTransform() + * + * let baseRenderTexture = new PIXI.BaseRenderTexture(100, 100); + * let renderTexture = new PIXI.RenderTexture(baseRenderTexture); + * + * renderer.render(sprite, renderTexture); // Renders to center of RenderTexture + * ``` + * + * @class + * @extends PIXI.BaseTexture + * @memberof PIXI + */ +export default class BaseRenderTexture extends BaseTexture +{ + /** + * @param {object} [options] + * @param {number} [options.width=100] - The width of the base render texture + * @param {number} [options.height=100] - The height of the base render texture + * @param {PIXI.SCALE_MODES} [options.scaleMode] - See {@link PIXI.SCALE_MODES} for possible values + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the texture being generated + */ + constructor(options) + { + if (typeof options === 'number') + { + /* eslint-disable prefer-rest-params */ + // Backward compatibility of signature + const width = arguments[0]; + const height = arguments[1]; + const scaleMode = arguments[2]; + const resolution = arguments[3]; + + options = { width, height, scaleMode, resolution }; + /* eslint-enable prefer-rest-params */ + } + + super(null, options); + + const { width, height } = options || {}; + + // Set defaults + this.mipmap = false; + this.width = Math.ceil(width) || 100; + this.height = Math.ceil(height) || 100; + this.valid = true; + + /** + * A map of renderer IDs to webgl renderTargets + * + * @private + * @member {object} + */ + // this._glRenderTargets = {}; + + /** + * A reference to the canvas render target (we only need one as this can be shared across renderers) + * + * @private + * @member {object} + */ + this._canvasRenderTarget = null; + + this.clearColor = [0, 0, 0, 0]; + + this.frameBuffer = new FrameBuffer(width * this.resolution, height * this.resolution) + .addColorTexture(0, this); + + // TODO - could this be added the systems? + + /** + * The data structure for the stencil masks + * + * @member {PIXI.Graphics[]} + */ + this.stencilMaskStack = []; + + /** + * The data structure for the filters + * + * @member {PIXI.Graphics[]} + */ + this.filterStack = [{}]; + } + + /** + * Resizes the BaseRenderTexture. + * + * @param {number} width - The width to resize to. + * @param {number} height - The height to resize to. + */ + resize(width, height) + { + width = Math.ceil(width); + height = Math.ceil(height); + this.frameBuffer.resize(width * this.resolution, height * this.resolution); + } + + /** + * Destroys this texture + * + */ + destroy() + { + super.destroy(true); + this.renderer = null; + } +} diff --git a/packages/core/src/renderTexture/RenderTexture.js b/packages/core/src/renderTexture/RenderTexture.js new file mode 100644 index 0000000..f1ecf1f --- /dev/null +++ b/packages/core/src/renderTexture/RenderTexture.js @@ -0,0 +1,153 @@ +import BaseRenderTexture from './BaseRenderTexture'; +import Texture from '../textures/Texture'; + +/** + * A RenderTexture is a special texture that allows any PixiJS display object to be rendered to it. + * + * __Hint__: All DisplayObjects (i.e. Sprites) that render to a RenderTexture should be preloaded + * otherwise black rectangles will be drawn instead. + * + * A RenderTexture takes a snapshot of any Display Object given to its render method. For example: + * + * ```js + * let renderer = PIXI.autoDetectRenderer(1024, 1024, { view: canvas, ratio: 1 }); + * let renderTexture = PIXI.RenderTexture.create(800, 600); + * let sprite = PIXI.Sprite.fromImage("spinObj_01.png"); + * + * sprite.position.x = 800/2; + * sprite.position.y = 600/2; + * sprite.anchor.x = 0.5; + * sprite.anchor.y = 0.5; + * + * renderer.render(sprite, renderTexture); + * ``` + * + * The Sprite in this case will be rendered using its local transform. To render this sprite at 0,0 + * you can clear the transform + * + * ```js + * + * sprite.setTransform() + * + * let renderTexture = new PIXI.RenderTexture.create(100, 100); + * + * renderer.render(sprite, renderTexture); // Renders to center of RenderTexture + * ``` + * + * @class + * @extends PIXI.Texture + * @memberof PIXI + */ +export default class RenderTexture extends Texture +{ + /** + * @param {PIXI.BaseRenderTexture} baseRenderTexture - The renderer used for this RenderTexture + * @param {PIXI.Rectangle} [frame] - The rectangle frame of the texture to show + */ + constructor(baseRenderTexture, frame) + { + // support for legacy.. + let _legacyRenderer = null; + + if (!(baseRenderTexture instanceof BaseRenderTexture)) + { + /* eslint-disable prefer-rest-params, no-console */ + const width = arguments[1]; + const height = arguments[2]; + const scaleMode = arguments[3]; + const resolution = arguments[4]; + + // we have an old render texture.. + console.warn(`Please use RenderTexture.create(${width}, ${height}) instead of the ctor directly.`); + _legacyRenderer = arguments[0]; + /* eslint-enable prefer-rest-params, no-console */ + + frame = null; + baseRenderTexture = new BaseRenderTexture({ + width, + height, + scaleMode, + resolution, + }); + } + + /** + * The base texture object that this texture uses + * + * @member {BaseTexture} + */ + super(baseRenderTexture, frame); + + this.legacyRenderer = _legacyRenderer; + + /** + * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. + * + * @member {boolean} + */ + this.valid = true; + + /** + * FilterSystem temporary storage + * @private + * @member {PIXI.Rectangle} + */ + this.filterFrame = null; + + this._updateUvs(); + } + + /** + * Resizes the RenderTexture. + * + * @param {number} width - The width to resize to. + * @param {number} height - The height to resize to. + * @param {boolean} [resizeBaseTexture=true] - Should the baseTexture.width and height values be resized as well? + */ + resize(width, height, resizeBaseTexture = true) + { + width = Math.ceil(width); + height = Math.ceil(height); + + // TODO - could be not required.. + this.valid = (width > 0 && height > 0); + + this._frame.width = this.orig.width = width; + this._frame.height = this.orig.height = height; + + if (resizeBaseTexture) + { + this.baseTexture.resize(width, height); + } + + this._updateUvs(); + } + + /** + * A short hand way of creating a render texture. + * + * @param {object} [options] - Options + * @param {number} [options.width=100] - The width of the render texture + * @param {number} [options.height=100] - The height of the render texture + * @param {number} [options.scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the texture being generated + * @return {PIXI.RenderTexture} The new render texture + */ + static create(options) + { + // fallback, old-style: create(width, height, scaleMode, resolution) + if (typeof options === 'number') + { + /* eslint-disable prefer-rest-params */ + options = { + width: options, + height: arguments[1], + scaleMode: arguments[2], + resolution: arguments[3], + }; + /* eslint-enable prefer-rest-params */ + } + + return new RenderTexture(new BaseRenderTexture(options)); + } +} diff --git a/packages/core/src/AbstractRenderer.js b/packages/core/src/AbstractRenderer.js new file mode 100644 index 0000000..64b2eb1 --- /dev/null +++ b/packages/core/src/AbstractRenderer.js @@ -0,0 +1,353 @@ +import { hex2string, hex2rgb } from '@pixi/utils'; +import { Matrix, Rectangle } from '@pixi/math'; +import { RENDERER_TYPE } from '@pixi/constants'; +import { settings } from '@pixi/settings'; +import { Container } from '@pixi/display'; +import RenderTexture from './renderTexture/RenderTexture'; +import EventEmitter from 'eventemitter3'; + +const tempMatrix = new Matrix(); + +/** + * 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 EventEmitter + * @memberof PIXI + */ +export default class AbstractRenderer extends EventEmitter +{ + // eslint-disable-next-line valid-jsdoc + /** + * @param {string} system - The name of the system this renderer is for. + * @param {object} [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.transparent=false] - If the render view is transparent, default false + * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false + * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. The + * resolution of the renderer retina would be 2. + * @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 {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when rendering, + * stopping pixel interpolation. + */ + constructor(system, options, arg2, arg3) + { + super(); + + // Support for constructor(system, screenWidth, screenHeight, options) + if (typeof options === 'number') + { + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + }, arg3); + } + + // 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 = RENDERER_TYPE.UNKNOWN; + + /** + * Measurements of the screen. (0, 0, screenWidth, screenHeight) + * + * Its safe to use as filterArea or hitArea for 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 1 + */ + this.resolution = options.resolution || settings.RESOLUTION; + + /** + * Whether the render view is transparent + * + * @member {boolean} + */ + this.transparent = options.transparent; + + /** + * Whether css dimensions of canvas view should be resized to screen dimensions automatically + * + * @member {boolean} + */ + this.autoResize = options.autoResize || false; + + /** + * Tracks the blend modes useful for this renderer. + * + * @member {object} + */ + this.blendModes = null; + + /** + * 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; + + /** + * If true PixiJS will Math.floor() x/y values when rendering, stopping pixel interpolation. + * Handy for crisp pixel art and speed on legacy devices. + * + * @member {boolean} + */ + this.roundPixels = options.roundPixels; + + /** + * The background color as a number. + * + * @member {number} + * @private + */ + this._backgroundColor = 0x000000; + + /** + * The background color as an [R, G, B] array. + * + * @member {number[]} + * @private + */ + this._backgroundColorRgba = [0, 0, 0, 0]; + + /** + * The background color as a string. + * + * @member {string} + * @private + */ + this._backgroundColorString = '#000000'; + + this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter + + /** + * This temporary display object used as the parent of the currently being rendered item + * + * @member {PIXI.DisplayObject} + * @private + */ + this._tempDisplayObjectParent = new Container(); + + /** + * The last root object that the renderer tried to render. + * + * @member {PIXI.DisplayObject} + * @private + */ + this._lastObjectRendered = this._tempDisplayObjectParent; + + /** + * Collection of plugins. + * @readonly + * @member {object} + */ + this.plugins = {}; + } + + /** + * Initialize the plugins. + * + * @protected + * @param {object} staticMap - The dictionary of staticly saved plugins. + */ + initPlugins(staticMap) + { + 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() + { + return this.view.width; + } + + /** + * Same as view.height, actual number of pixels in the canvas by vertical + * + * @member {number} + * @readonly + * @default 600 + */ + get height() + { + return this.view.height; + } + + /** + * Resizes the screen and canvas to the specified width and height + * Canvas dimensions are multiplied by resolution + * + * @param {number} screenWidth - the new width of the screen + * @param {number} screenHeight - the new height of the screen + */ + resize(screenWidth, screenHeight) + { + this.screen.width = screenWidth; + this.screen.height = screenHeight; + + this.view.width = screenWidth * this.resolution; + this.view.height = screenHeight * this.resolution; + + if (this.autoResize) + { + this.view.style.width = `${screenWidth}px`; + this.view.style.height = `${screenHeight}px`; + } + } + + /** + * 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 {PIXI.DisplayObject} displayObject - The displayObject the object will be generated from + * @param {number} scaleMode - Should be one of the scaleMode consts + * @param {number} resolution - The resolution / device pixel ratio of the texture being generated + * @param {PIXI.Rectangle} [region] - The region of the displayObject, that shall be rendered, + * if no region is specified, defaults to the local bounds of the displayObject. + * @return {PIXI.Texture} a texture of the graphics object + */ + generateTexture(displayObject, scaleMode, resolution, region) + { + region = region || displayObject.getLocalBounds(); + + // 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(region.width | 0, region.height | 0, scaleMode, resolution); + + tempMatrix.tx = -region.x; + tempMatrix.ty = -region.y; + + this.render(displayObject, renderTexture, false, tempMatrix, true); + + return renderTexture; + } + + /** + * Removes everything from the renderer and optionally removes the Canvas DOM element. + * + * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. + */ + destroy(removeView) + { + 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); + } + + this.plugins = null; + + this.type = RENDERER_TYPE.UNKNOWN; + + this.view = null; + + this.screen = null; + + this.resolution = 0; + + this.transparent = false; + + this.autoResize = false; + + this.blendModes = null; + + this.options = null; + + this.preserveDrawingBuffer = false; + this.clearBeforeRender = false; + + this.roundPixels = false; + + this._backgroundColor = 0; + this._backgroundColorRgba = null; + this._backgroundColorString = null; + + this._tempDisplayObjectParent = null; + this._lastObjectRendered = null; + } + + /** + * The background color to fill if not transparent + * + * @member {number} + */ + get backgroundColor() + { + return this._backgroundColor; + } + + set backgroundColor(value) // eslint-disable-line require-jsdoc + { + this._backgroundColor = value; + this._backgroundColorString = hex2string(value); + hex2rgb(value, this._backgroundColorRgba); + } +} diff --git a/packages/core/src/Renderer.js b/packages/core/src/Renderer.js new file mode 100644 index 0000000..2227e78 --- /dev/null +++ b/packages/core/src/Renderer.js @@ -0,0 +1,338 @@ +import AbstractRenderer from './AbstractRenderer'; +import { sayHello } from '@pixi/utils'; +import MaskSystem from './mask/MaskSystem'; +import StencilSystem from './mask/StencilSystem'; +import FilterSystem from './filters/FilterSystem'; +import FramebufferSystem from './framebuffer/FramebufferSystem'; +import RenderTextureSystem from './renderTexture/RenderTextureSystem'; +import TextureSystem from './textures/TextureSystem'; +import ProjectionSystem from './projection/ProjectionSystem'; +import StateSystem from './state/StateSystem'; +import GeometrySystem from './geometry/GeometrySystem'; +import ShaderSystem from './shader/ShaderSystem'; +import ContextSystem from './context/ContextSystem'; +import BatchSystem from './batch/BatchSystem'; +import TextureGCSystem from './textures/TextureGCSystem'; +import { RENDERER_TYPE } from '@pixi/constants'; +import UniformGroup from './shader/UniformGroup'; +import { Matrix } from '@pixi/math'; +import Runner from 'mini-runner'; + +/** + * 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 Render works by automatically managing webGLBatchs. + * 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 :) + * + * @class + * @memberof PIXI + * @extends PIXI.AbstractRenderer + */ +export default class Renderer extends AbstractRenderer +{ + // eslint-disable-next-line valid-jsdoc + /** + * + * @param {object} [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.transparent=false] - If the render view is transparent, default false + * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false + * @param {boolean} [options.antialias=false] - sets antialias. If not available natively then FXAA + * antialiasing is used + * @param {boolean} [options.forceFXAA=false] - forces FXAA antialiasing to be used over native. + * FXAA is faster, but may not always look as great + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. + * The resolution of the renderer retina would be 2. + * @param {boolean} [options.clearBeforeRender=true] - This sets if the renderer will clear + * the canvas or not before the new render pass. If you wish to set this to false, you *must* set + * preserveDrawingBuffer to `true`. + * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, + * enable this if you need to call toDataUrl on the webgl context. + * @param {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when + * rendering, stopping pixel interpolation. + * @param {number} [options.backgroundColor=0x000000] - The background color of the rendered area + * (shown if not transparent). + * @param {boolean} [options.legacy=false] - If true PixiJS will aim to ensure compatibility + * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. + * @param {string} [options.powerPreference] - Parameter passed to webgl context, set to "high-performance" + * for devices with dual graphics card + */ + constructor(options, arg2, arg3) + { + super('WebGL', options, arg2, arg3); + + /** + * The type of this renderer as a standardised const + * + * @member {number} + * @see PIXI.RENDERER_TYPE + */ + this.type = RENDERER_TYPE.WEBGL; + + // this will be set by the contextSystem (this.context) + this.gl = null; + this.CONTEXT_UID = 0; + this.legacy = !!options.legacy; + + // TODO legacy! + + // runners! + this.runners = { + destroy: new Runner('destroy'), + contextChange: new Runner('contextChange', 1), + reset: new Runner('reset'), + update: new Runner('update'), + postrender: new Runner('postrender'), + prerender: new Runner('prerender'), + resize: new Runner('resize', 2), + }; + + 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(GeometrySystem, 'geometry') + .addSystem(FramebufferSystem, 'framebuffer') + .addSystem(StencilSystem, 'stencil') + .addSystem(ProjectionSystem, 'projection') + .addSystem(TextureGCSystem, 'textureGC') + .addSystem(FilterSystem, 'filter') + .addSystem(RenderTextureSystem, 'renderTexture') + .addSystem(BatchSystem, 'batch'); + + this.initPlugins(Renderer.__plugins); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ + if (options.context) + { + this.context.initFromContext(options.context); + } + else + { + this.context.initFromOptions({ + alpha: this.transparent, + antialias: options.antialias, + premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', + stencil: true, + 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); + } + + /** + * Add a new system to the renderer. + * @param {class} ClassRef - Class reference + * @param {string} [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. + * @return {PIXI.Renderer} Return instance of renderer + */ + addSystem(ClassRef, name) + { + if (!name) + { + name = ClassRef.name; + } + + // TODO - read name from class.name.. + + /* + if(name.includes('System')) + { + name = name.replace('System', ''); + name = name.charAt(0).toLowerCase() + name.slice(1); + } + */ + + const system = new ClassRef(this); + + if (this[name]) + { + throw new Error(`Whoops! ${name} is already a manger`); + } + + this[name] = system; + + for (const i in this.runners) + { + this.runners[i].add(system); + } + + return this; + + /** + * 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. + */ + } + + /** + * Renders the object to its webGL view + * + * @param {PIXI.DisplayObject} displayObject - the object to be rendered + * @param {PIXI.RenderTexture} renderTexture - The render texture to render to. + * @param {boolean} [clear] - Should the canvas be cleared before the new render + * @param {PIXI.Transform} [transform] - A transform to apply to the render texture before rendering. + * @param {boolean} [skipUpdateTransform] - Should we skip the update transform pass? + */ + render(displayObject, renderTexture, clear, transform, skipUpdateTransform) + { + // can be handy to know! + this.renderingToScreen = !renderTexture; + + this.runners.prerender.run(); + this.emit('prerender'); + + // 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.parent; + + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = 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.run(); + + this.emit('postrender'); + } + + /** + * Resizes the webGL view to the specified width and height. + * + * @param {number} screenWidth - the new width of the screen + * @param {number} screenHeight - the new height of the screen + */ + resize(screenWidth, screenHeight) + { + AbstractRenderer.prototype.resize.call(this, screenWidth, screenHeight); + + this.runners.resize.run(screenWidth, screenHeight); + } + + /** + * Resets the WebGL state so you can render things however you fancy! + * + * @return {PIXI.Renderer} Returns itself. + */ + reset() + { + this.runners.reset.run(); + + return this; + } + + clear() + { + this.framebuffer.bind(); + this.framebuffer.clear(); + } + + /** + * Removes everything from the renderer (event listeners, spritebatch, etc...) + * + * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. + * See: https://github.com/pixijs/pixi.js/issues/2233 + */ + destroy(removeView) + { + this.runners.destroy.run(); + + // call base destroy + super.destroy(removeView); + + // TODO nullify all the managers.. + this.gl = null; + } + + /** + * 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 + * builds and excluding plugins. + * @name PIXI.Renderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. + */ + + /** + * Adds a plugin to the renderer. + * + * @method + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + static registerPlugin(pluginName, ctor) + { + Renderer.__plugins = Renderer.__plugins || {}; + Renderer.__plugins[pluginName] = ctor; + } +} diff --git a/packages/core/src/System.js b/packages/core/src/System.js new file mode 100644 index 0000000..05a10c8 --- /dev/null +++ b/packages/core/src/System.js @@ -0,0 +1,42 @@ +/** + * System is a base class used for extending systems used by the {@link PIXI.Renderer} + * @see PIXI.Renderer#addSystem + * @class + * @memberof PIXI + */ +export default class System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + /** + * The renderer this manager works for. + * + * @member {PIXI.Renderer} + */ + this.renderer = renderer; + + this.renderer.runners.contextChange.add(this); + } + + /** + * Generic method called when there is a WebGL context change. + * + */ + contextChange() + { + // do some codes init! + } + + /** + * Generic destroy methods to be overridden by the subclass + * + */ + destroy() + { + this.renderer.runners.contextChange.remove(this); + this.renderer = null; + } +} diff --git a/packages/core/src/batch/BatchSystem.js b/packages/core/src/batch/BatchSystem.js new file mode 100644 index 0000000..b04c249 --- /dev/null +++ b/packages/core/src/batch/BatchSystem.js @@ -0,0 +1,68 @@ +import System from '../System'; +import ObjectRenderer from './ObjectRenderer'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class BatchSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + super(renderer); + + /** + * An empty renderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.emptyRenderer = new ObjectRenderer(renderer); + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.currentRenderer = this.emptyRenderer; + } + + /** + * Changes the current renderer to the one given in parameter + * + * @param {PIXI.ObjectRenderer} objectRenderer - The object renderer to use. + */ + setObjectRenderer(objectRenderer) + { + if (this.currentRenderer === objectRenderer) + { + return; + } + + this.currentRenderer.stop(); + this.currentRenderer = objectRenderer; + + this.renderer.state.setState(objectRenderer.state); + + this.currentRenderer.start(); + } + + /** + * This should be called if you wish to do some custom rendering + * It will basically render anything that may be batched up such as sprites + * + */ + flush() + { + this.setObjectRenderer(this.emptyRenderer); + } + + reset() + { + this.setObjectRenderer(this.emptyRenderer); + } +} diff --git a/packages/core/src/batch/ObjectRenderer.js b/packages/core/src/batch/ObjectRenderer.js new file mode 100644 index 0000000..0c851a9 --- /dev/null +++ b/packages/core/src/batch/ObjectRenderer.js @@ -0,0 +1,48 @@ +import System from '../System'; + +/** + * Base for a common object renderer that can be used as a system renderer plugin. + * + * @class + * @extends PIXI.System + * @memberof PIXI + */ +export default class ObjectRenderer extends System +{ + /** + * Starts the renderer and sets the shader + * + */ + start() + { + // set the shader.. + } + + /** + * Stops the renderer + * + */ + stop() + { + this.flush(); + } + + /** + * Stub method for rendering content and emptying the current batch. + * + */ + flush() + { + // flush! + } + + /** + * Renders an object + * + * @param {PIXI.DisplayObject} object - The object to render. + */ + render(object) // eslint-disable-line no-unused-vars + { + // render the object + } +} diff --git a/packages/core/src/context/ContextSystem.js b/packages/core/src/context/ContextSystem.js new file mode 100644 index 0000000..301aab4 --- /dev/null +++ b/packages/core/src/context/ContextSystem.js @@ -0,0 +1,186 @@ +import System from '../System'; +import { settings } from '@pixi/settings'; + +let CONTEXT_UID = 0; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class ContextSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.webGLVersion = 1; + + this.handleContextLost = this.handleContextLost.bind(this); + this.handleContextRestored = this.handleContextRestored.bind(this); + + this.extensions = {}; + + renderer.view.addEventListener('webglcontextlost', this.handleContextLost, false); + renderer.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + } + + get isLost() + { + return (!this.gl || this.gl.isContextLost()); + } + + contextChange(gl) + { + this.gl = gl; + + // restore a context if it was previously lost + if (gl.isContextLost() && gl.getExtension('WEBGL_lose_context')) + { + gl.getExtension('WEBGL_lose_context').restoreContext(); + } + } + + initFromContext(gl) + { + this.gl = gl; + this.validateContext(gl); + this.renderer.gl = gl; + this.renderer.CONTEXT_UID = CONTEXT_UID++; + this.renderer.runners.contextChange.run(gl); + } + + initFromOptions(options) + { + const gl = this.createContext(this.renderer.view, options); + + this.initFromContext(gl); + } + + /** + * Helper class to create a webGL Context + * + * @class + * @memberof PIXI.glCore + * @param canvas {HTMLCanvasElement} the canvas element that we will get the context from + * @param options {Object} An options object that gets passed in to the canvas element containing the context attributes, + * see https://developer.mozilla.org/en/docs/Web/API/HTMLCanvasElement/getContext for the options available + * @return {WebGLRenderingContext} the WebGL context + */ + createContext(canvas, options) + { + let gl; + + if (settings.PREFER_WEBGL_2) + { + gl = canvas.getContext('webgl2', options); + } + + if (gl) + { + this.webGLVersion = 2; + } + else + { + this.webGLVersion = 1; + + gl = canvas.getContext('webgl', options) + || canvas.getContext('experimental-webgl', options); + + if (!gl) + { + // fail, not able to get a context + throw new Error('This browser does not support webGL. Try using the canvas renderer'); + } + } + + this.gl = gl; + + this.getExtensions(); + + return gl; + } + + getExtensions() + { + // time to set up default etensions that pixi uses.. + const gl = this.gl; + const extensions = this.extensions; + + if (this.webGLVersion === 1) + { + extensions.drawBuffers = gl.getExtension('WEBGL_draw_buffers'); + extensions.depthTexture = gl.getExtension('WEBKIT_WEBGL_depth_texture'); + extensions.floatTexture = gl.getExtension('OES_texture_float'); + extensions.loseContext = gl.getExtension('WEBGL_lose_context'); + + extensions.vertexArrayObject = gl.getExtension('OES_vertex_array_object') + || gl.getExtension('MOZ_OES_vertex_array_object') + || gl.getExtension('WEBKIT_OES_vertex_array_object'); + } + + // we don't use any specific WebGL 2 ones yet! + } + + /** + * Handles a lost webgl context + * + * @private + * @param {WebGLContextEvent} event - The context lost event. + */ + handleContextLost(event) + { + event.preventDefault(); + } + + /** + * Handles a restored webgl context + * + * @private + */ + handleContextRestored() + { + this.renderer.runners.contextChange.run(this.gl); + } + + destroy() + { + const view = this.renderer.view; + + // remove listeners + view.removeEventListener('webglcontextlost', this.handleContextLost); + view.removeEventListener('webglcontextrestored', this.handleContextRestored); + + this.gl.useProgram(null); + + if (this.extensions.loseContext) + { + this.extensions.loseContext.loseContext(); + } + } + + postrender() + { + this.gl.flush(); + } + + validateContext(gl) + { + const attributes = gl.getContextAttributes(); + + // this is going to be fairly simple for now.. but at least we have room to grow! + if (!attributes.stencil) + { + /* eslint-disable max-len */ + + /* eslint-disable no-console */ + console.warn('Provided WebGL context does not have a stencil buffer, masks may not render correctly'); + /* eslint-enable no-console */ + + /* eslint-enable max-len */ + } + } +} diff --git a/packages/core/src/filters/Filter.js b/packages/core/src/filters/Filter.js new file mode 100644 index 0000000..9d8cad8 --- /dev/null +++ b/packages/core/src/filters/Filter.js @@ -0,0 +1,140 @@ +import Shader from '../shader/Shader'; +import Program from '../shader/Program'; +import State from '../state/State'; +import { settings } from '@pixi/settings'; +// import extractUniformsFromSrc from './extractUniformsFromSrc'; +import defaultVertex from './defaultFilter.vert'; +import defaultFragment from './defaultFilter.frag'; + +// let math = require('../../math'); +/** + * @class + * @memberof PIXI + * @extends PIXI.Shader + */ +export default class Filter extends Shader +{ + /** + * @param {string} [vertexSrc] - The source of the vertex shader. + * @param {string} [fragmentSrc] - The source of the fragment shader. + * @param {object} [uniforms] - Custom uniforms to use to augment the built-in ones. + */ + constructor(vertexSrc, fragmentSrc, uniforms) + { + const program = Program.from(vertexSrc, fragmentSrc); + + super(program, uniforms); + + /** + * The padding of the filter. Some filters require extra space to breath such as a blur. + * Increasing this will add extra width and height to the bounds of the object that the + * filter is applied to. + * + * @member {number} + */ + this.padding = 0; + + /** + * The resolution of the filter. Setting this to be lower will lower the quality but + * increase the performance of the filter. + * + * @member {number} + */ + this.resolution = settings.RESOLUTION; + + /** + * If enabled is true the filter is applied, if false it will not. + * + * @member {boolean} + */ + this.enabled = true; + + /** + * If enabled, PixiJS will fit the filter area into boundaries for better performance. + * Switch it off if it does not work for specific shader. + * + * @member {boolean} + */ + this.autoFit = true; + + /** + * Legacy filters use position and uvs from attributes + * @member {boolean} + * @readonly + */ + this.legacy = !!this.program.attributeData.aTextureCoord; + + /** + * the webGL state the filter requires to render + * @member {PIXI.State} + */ + this.state = new State(); + } + + /** + * Applies the filter + * + * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + * @param {boolean} clear - Should the output be cleared before rendering to it + * @param {object} [currentState] - It's current state of filter. + * There are some useful properties in the currentState : + * target, filters, sourceFrame, destinationFrame, renderTarget, resolution + */ + apply(filterManager, input, output, clear, currentState, derp) // eslint-disable-line no-unused-vars + { + // do as you please! + + filterManager.applyFilter(this, input, output, clear, currentState, derp); + + // or just do a regular render.. + } + + /** + * Sets the blendmode of the filter + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL + */ + get blendMode() + { + return this.state.blendMode; + } + + set blendMode(value) // eslint-disable-line require-jsdoc + { + this.state.blendMode = value; + } + + /** + * The default vertex shader source + * + * @static + * @constant + */ + static get defaultVertexSrc() + { + return defaultVertex; + } + + /** + * The default fragment shader source + * + * @static + * @constant + */ + static get defaultFragmentSrc() + { + return defaultFragment; + } +} + +/** + * Used for caching shader IDs + * + * @static + * @private + */ +Filter.SOURCE_KEY_MAP = {}; + diff --git a/packages/core/src/filters/FilterSystem.js b/packages/core/src/filters/FilterSystem.js new file mode 100644 index 0000000..3174318 --- /dev/null +++ b/packages/core/src/filters/FilterSystem.js @@ -0,0 +1,431 @@ +import System from '../System'; + +import RenderTexture from '../renderTexture/RenderTexture'; +import Quad from '../utils/Quad'; +import QuadUv from '../utils/QuadUv'; +import { Rectangle } from '@pixi/math'; +import * as filterTransforms from './filterTransforms'; +import bitTwiddle from 'bit-twiddle'; +import UniformGroup from '../shader/UniformGroup'; +import { DRAW_MODES } from '@pixi/constants'; + +// +/** + * @ignore + * @class + */ +class FilterState +{ + /** + * + */ + constructor() + { + this.renderTexture = null; + this.sourceFrame = new Rectangle(); + this.destinationFrame = new Rectangle(); + this.filters = []; + this.target = null; + this.legacy = false; + this.resolution = 1; + } +} + +/** + * @class + * @memberof PIXI.systems + * @extends PIXI.System + */ +export default class FilterSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + /** + * stores a bunch of PO2 textures used for filtering + * @type {Object} + */ + this.texturePool = {}; + + /** + * a pool for storing filter states, save us creating new ones each tick + * @type {Array} + */ + this.statePool = []; + + /** + * A very simple geometry used when drawing a filter effect to the screen + * @type {Quad} + */ + this.quad = new Quad(); + + this.quadUv = new QuadUv(); + + /** + * Temporary rect for maths + * @type {PIXI.Rectangle} + */ + this.tempRect = new Rectangle(); + + this.activeState = {}; + + /** + * this uniform group is attached to filter uniforms when used + * @type {UniformGroup} + */ + this.globalUniforms = new UniformGroup({ + sourceFrame: this.tempRect, + destinationFrame: this.tempRect, + + // legacy variables + filterArea: new Float32Array(4), + filterClamp: new Float32Array(4), + }, true); + } + + /** + * Adds a new filter to the System. + * + * @param {PIXI.DisplayObject} target - The target of the filter to render. + * @param {PIXI.Filter[]} filters - The filters to apply. + */ + push(target, filters) + { + const renderer = this.renderer; + const filterStack = this.renderer.renderTexture.defaultFilterStack; + const state = this.statePool.pop() || new FilterState(); + + let resolution = filters[0].resolution; + let padding = filters[0].padding; + let autoFit = filters[0].autoFit; + let legacy = filters[0].legacy; + + for (let i = 1; i < filters.length; i++) + { + const filter = filters[i]; + + // lets use the lowest resolution.. + resolution = Math.min(resolution, filter.resolution); + // and the largest amount of padding! + padding = Math.max(padding, filter.padding); + // only auto fit if all filters are autofit + autoFit = autoFit || filter.autoFit; + + legacy = legacy || filter.legacy; + } + + filterStack.push(state); + + state.resolution = resolution; + + state.legacy = legacy; + + // round to whole number based on resolution + // TODO move that to the shader too? + state.sourceFrame = target.filterArea || target.getBounds(true); + + state.sourceFrame.pad(padding); + + if (autoFit) + { + state.sourceFrame.fit(this.renderer.renderTexture.destinationFrame); + } + + state.sourceFrame.round(resolution); + + state.renderTexture = this.getPotFilterTexture(state.sourceFrame.width, state.sourceFrame.height, resolution); + state.filters = filters; + + state.destinationFrame.width = state.renderTexture.width; + state.destinationFrame.height = state.renderTexture.height; + + state.renderTexture.filterFrame = state.sourceFrame; + renderer.renderTexture.bind(state.renderTexture, state.sourceFrame);// /, state.destinationFrame); + renderer.renderTexture.clear(); + } + + /** + * Pops off the filter and applies it. + * + */ + pop() + { + const renderer = this.renderer; + const filterStack = renderer.renderTexture.defaultFilterStack; + const state = filterStack.pop(); + const filters = state.filters; + + this.activeState = state; + + const globalUniforms = this.globalUniforms.uniforms; + + globalUniforms.sourceFrame = state.sourceFrame; + globalUniforms.destinationFrame = state.destinationFrame; + globalUniforms.resolution = state.resolution; + + // only update the rect if its legacy.. + if (state.legacy) + { + const filterArea = globalUniforms.filterArea; + const filterClamp = globalUniforms.filterClamp; + + filterArea[0] = state.destinationFrame.width; + filterArea[1] = state.destinationFrame.height; + filterArea[2] = state.sourceFrame.x; + filterArea[3] = state.sourceFrame.y; + + filterClamp[0] = 0.5 / state.resolution / state.destinationFrame.width; + filterClamp[1] = 0.5 / state.resolution / state.destinationFrame.height; + filterClamp[2] = (state.sourceFrame.width - 0.5) / state.resolution / state.destinationFrame.width; + filterClamp[3] = (state.sourceFrame.height - 0.5) / state.resolution / state.destinationFrame.height; + } + + this.globalUniforms.update(); + + const lastState = filterStack[filterStack.length - 1]; + + if (filters.length === 1) + { + filters[0].apply(this, state.renderTexture, lastState.renderTexture, false, state); + + this.returnFilterTexture(state.renderTexture); + } + else + { + let flip = state.renderTexture; + let flop = this.getPotFilterTexture( + flip.width, + flip.height, + state.resolution + ); + + flop.filterFrame = flip.filterFrame; + + let i = 0; + + for (i = 0; i < filters.length - 1; ++i) + { + filters[i].apply(this, flip, flop, true, state); + + const t = flip; + + flip = flop; + flop = t; + } + + filters[i].apply(this, flip, lastState.renderTexture, false, state); + + this.returnFilterTexture(flip); + this.returnFilterTexture(flop); + } + + this.statePool.push(state); + } + + /** + * Draws a filter. + * + * @param {PIXI.Filter} filter - The filter to draw. + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + * @param {boolean} clear - Should the output be cleared before rendering to it + */ + applyFilter(filter, input, output, clear) + { + const renderer = this.renderer; + + renderer.renderTexture.bind(output, output ? output.filterFrame : null); + + if (clear) + { + // gl.disable(gl.SCISSOR_TEST); + renderer.renderTexture.clear(); + // gl.enable(gl.SCISSOR_TEST); + } + + // set the uniforms.. + filter.uniforms.uSampler = input; + filter.uniforms.filterGlobals = this.globalUniforms; + + // TODO make it so that the order of this does not matter.. + // because it does at the moment cos of global uniforms. + // they need to get resynced + + renderer.state.setState(filter.state); + renderer.shader.bind(filter); + + if (filter.legacy) + { + this.quadUv.map(input._frame, input.filterFrame); + + renderer.geometry.bind(this.quadUv); + renderer.geometry.draw(DRAW_MODES.TRIANGLES); + } + else + { + renderer.geometry.bind(this.quad); + renderer.geometry.draw(DRAW_MODES.TRIANGLE_STRIP); + } + } + + /** + * Calculates the mapped matrix. + * + * TODO playing around here.. this is temporary - (will end up in the shader) + * this returns a matrix that will normalise map filter cords in the filter to screen space + * + * @param {PIXI.Matrix} outputMatrix - the matrix to output to. + * @return {PIXI.Matrix} The mapped matrix. + */ + calculateScreenSpaceMatrix(outputMatrix) + { + const currentState = this.activeState; + + return filterTransforms.calculateScreenSpaceMatrix( + outputMatrix, + currentState.sourceFrame, + currentState.destinationFrame + ); + } + + /** + * This will map the filter coord so that a texture can be used based on the transform of a sprite + * + * @param {PIXI.Matrix} outputMatrix - The matrix to output to. + * @param {PIXI.Sprite} sprite - The sprite to map to. + * @return {PIXI.Matrix} The mapped matrix. + */ + calculateSpriteMatrix(outputMatrix, sprite) + { + const currentState = this.activeState; + + return filterTransforms.calculateSpriteMatrix( + outputMatrix, + currentState.sourceFrame, + currentState.destinationFrame, + sprite + ); + } + + /** + * Destroys this Filter System. + * + * @param {boolean} [contextLost=false] context was lost, do not free shaders + * + */ + destroy(contextLost = false) + { + if (!contextLost) + { + this.emptyPool(); + } + else + { + this.texturePool = {}; + } + } + + /** + * Gets a Power-of-Two render texture. + * + * TODO move to a seperate class could be on renderer? + * also - could cause issue with multiple contexts? + * + * @private + * @param {WebGLRenderingContext} gl - The webgl rendering context + * @param {number} minWidth - The minimum width of the render target. + * @param {number} minHeight - The minimum height of the render target. + * @param {number} resolution - The resolution of the render target. + * @return {PIXI.RenderTarget} The new render target. + */ + getPotFilterTexture(minWidth, minHeight, resolution) + { + minWidth = bitTwiddle.nextPow2(minWidth); + minHeight = bitTwiddle.nextPow2(minHeight); + resolution = resolution || 1; + + const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + + if (!this.texturePool[key]) + { + this.texturePool[key] = []; + } + + let renderTexture = this.texturePool[key].pop(); + + if (!renderTexture) + { + // temporary bypass cache.. + // internally - this will cause a texture to be bound.. + renderTexture = RenderTexture.create({ + width: minWidth, + height: minHeight, + resolution, + }); + } + + return renderTexture; + } + + /** + * Gets extra render texture to use inside current filter + * + * @param {number} resolution resolution of the renderTexture + * @returns {PIXI.RenderTexture} + */ + getFilterTexture(resolution) + { + const rt = this.activeState.renderTexture; + + const filterTexture = this.getPotFilterTexture(rt.width, rt.height, resolution || rt.baseTexture.resolution); + + filterTexture.filterFrame = rt.filterFrame; + + return filterTexture; + } + + /** + * Frees a render texture back into the pool. + * + * @param {PIXI.RenderTarget} renderTexture - The renderTarget to free + */ + returnFilterTexture(renderTexture) + { + renderTexture.filterFrame = null; + + const base = renderTexture.baseTexture; + + const minWidth = base.width; + const minHeight = base.height; + + const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + + this.texturePool[key].push(renderTexture); + } + + /** + * Empties the texture pool. + * + */ + emptyPool() + { + for (const i in this.texturePool) + { + const textures = this.texturePool[i]; + + if (textures) + { + for (let j = 0; j < textures.length; j++) + { + textures[j].destroy(true); + } + } + } + + this.texturePool = {}; + } +} diff --git a/packages/core/src/filters/defaultFilter.frag b/packages/core/src/filters/defaultFilter.frag new file mode 100644 index 0000000..a14c1be --- /dev/null +++ b/packages/core/src/filters/defaultFilter.frag @@ -0,0 +1,22 @@ +varying vec2 vTextureCoord; +varying vec2 vFilterCoord; + +uniform sampler2D uSampler; +uniform sampler2D filterSampler; + +void main(void){ + vec4 masky = texture2D(filterSampler, vFilterCoord); + vec4 sample = texture2D(uSampler, vTextureCoord); + vec4 color; + if(mod(vFilterCoord.x, 1.0) > 0.5) + { + color = vec4(1.0, 0.0, 0.0, 1.0); + } + else + { + color = vec4(0.0, 1.0, 0.0, 1.0); + } + // gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0); + gl_FragColor = mix(sample, masky, 0.5); + gl_FragColor *= sample.a; +} diff --git a/packages/core/src/filters/defaultFilter.vert b/packages/core/src/filters/defaultFilter.vert new file mode 100644 index 0000000..e1febca --- /dev/null +++ b/packages/core/src/filters/defaultFilter.vert @@ -0,0 +1,14 @@ +attribute vec2 aVertexPosition; +attribute vec2 aTextureCoord; + +uniform mat3 projectionMatrix; +uniform mat3 filterMatrix; + +varying vec2 vTextureCoord; +varying vec2 vFilterCoord; + +void main(void){ + gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); + vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy; + vTextureCoord = aTextureCoord ; +} diff --git a/packages/core/src/filters/filterTransforms.js b/packages/core/src/filters/filterTransforms.js new file mode 100644 index 0000000..87a3cd6 --- /dev/null +++ b/packages/core/src/filters/filterTransforms.js @@ -0,0 +1,50 @@ +import { Matrix } from '@pixi/math'; + +/** + * Calculates the mapped matrix + * @param filterArea {Rectangle} The filter area + * @param sprite {Sprite} the target sprite + * @param outputMatrix {Matrix} @alvin + * @private + */ +// this returns a matrix that will normalise map filter cords in the filter to screen space +export function calculateScreenSpaceMatrix(outputMatrix, filterArea, textureSize) +{ + // TODO unwrap? + const mappedMatrix = outputMatrix.identity(); + + mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); + + mappedMatrix.scale(textureSize.width, textureSize.height); + + return mappedMatrix; +} + +export function calculateNormalizedScreenSpaceMatrix(outputMatrix, filterArea, textureSize) +{ + const mappedMatrix = outputMatrix.identity(); + + mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); + + const translateScaleX = (textureSize.width / filterArea.width); + const translateScaleY = (textureSize.height / filterArea.height); + + mappedMatrix.scale(translateScaleX, translateScaleY); + + return mappedMatrix; +} + +// this will map the filter coord so that a texture can be used based on the transform of a sprite +export function calculateSpriteMatrix(outputMatrix, filterArea, textureSize, sprite) +{ + const orig = sprite._texture.orig; + const mappedMatrix = outputMatrix.set(textureSize.width, 0, 0, textureSize.height, filterArea.x, filterArea.y); + const worldTransform = sprite.worldTransform.copyTo(Matrix.TEMP_MATRIX); + + worldTransform.invert(); + mappedMatrix.prepend(worldTransform); + mappedMatrix.scale(1.0 / orig.width, 1.0 / orig.height); + mappedMatrix.translate(sprite.anchor.x, sprite.anchor.y); + + return mappedMatrix; +} diff --git a/packages/core/src/filters/spriteMask/SpriteMaskFilter.js b/packages/core/src/filters/spriteMask/SpriteMaskFilter.js new file mode 100644 index 0000000..91697b7 --- /dev/null +++ b/packages/core/src/filters/spriteMask/SpriteMaskFilter.js @@ -0,0 +1,63 @@ +import Filter from '../Filter'; +import { Matrix } from '@pixi/math'; +import vertex from './spriteMaskFilter.vert'; +import fragment from './spriteMaskFilter.frag'; +import { default as TextureMatrix } from '../../textures/TextureMatrix'; + +/** + * The SpriteMaskFilter class + * + * @class + * @extends PIXI.Filter + * @memberof PIXI + */ +export default class SpriteMaskFilter extends Filter +{ + /** + * @param {PIXI.Sprite} sprite - the target sprite + */ + constructor(sprite) + { + const maskMatrix = new Matrix(); + + super(vertex, fragment); + + sprite.renderable = false; + + this.maskSprite = sprite; + this.maskMatrix = maskMatrix; + } + + /** + * Applies the filter + * + * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + */ + apply(filterManager, input, output) + { + const maskSprite = this.maskSprite; + const tex = this.maskSprite.texture; + + if (!tex.valid) + { + return; + } + if (!tex.transform) + { + // margin = 0.0, let it bleed a bit, shader code becomes easier + // assuming that atlas textures were made with 1-pixel padding + tex.transform = new TextureMatrix(tex, 0.0); + } + tex.transform.update(); + + this.uniforms.mask = tex; + this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite) + .prepend(tex.transform.mapCoord); + this.uniforms.alpha = maskSprite.worldAlpha; + this.uniforms.maskClamp = tex.transform.uClampFrame; + + filterManager.applyFilter(this, input, output); + } +} diff --git a/packages/core/src/filters/spriteMask/spriteMaskFilter.frag b/packages/core/src/filters/spriteMask/spriteMaskFilter.frag new file mode 100644 index 0000000..0e4aef8 --- /dev/null +++ b/packages/core/src/filters/spriteMask/spriteMaskFilter.frag @@ -0,0 +1,23 @@ +varying vec2 vMaskCoord; +varying vec2 vTextureCoord; + +uniform sampler2D uSampler; +uniform sampler2D mask; +uniform float alpha; +uniform vec4 maskClamp; + +void main(void) +{ + float clip = step(3.5, + step(maskClamp.x, vMaskCoord.x) + + step(maskClamp.y, vMaskCoord.y) + + step(vMaskCoord.x, maskClamp.z) + + step(vMaskCoord.y, maskClamp.w)); + + vec4 original = texture2D(uSampler, vTextureCoord); + vec4 masky = texture2D(mask, vMaskCoord); + + original *= (masky.r * masky.a * alpha * clip); + + gl_FragColor = original; +} diff --git a/packages/core/src/filters/spriteMask/spriteMaskFilter.vert b/packages/core/src/filters/spriteMask/spriteMaskFilter.vert new file mode 100644 index 0000000..d23a30c --- /dev/null +++ b/packages/core/src/filters/spriteMask/spriteMaskFilter.vert @@ -0,0 +1,16 @@ +attribute vec2 aVertexPosition; +attribute vec2 aTextureCoord; + +uniform mat3 projectionMatrix; +uniform mat3 otherMatrix; + +varying vec2 vMaskCoord; +varying vec2 vTextureCoord; + +void main(void) +{ + gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); + + vTextureCoord = aTextureCoord; + vMaskCoord = ( otherMatrix * vec3( aTextureCoord, 1.0) ).xy; +} diff --git a/packages/core/src/framebuffer/FrameBuffer.js b/packages/core/src/framebuffer/FrameBuffer.js new file mode 100644 index 0000000..84e9fcc --- /dev/null +++ b/packages/core/src/framebuffer/FrameBuffer.js @@ -0,0 +1,106 @@ +import Texture from '../textures/BaseTexture'; +import { FORMATS, TYPES } from '@pixi/constants'; + +/** + * Frame buffer + * @class + * @memberof PIXI + */ +export default class FrameBuffer +{ + constructor(width, height) + { + this.width = width || 100; + this.height = height || 100; + + this.stencil = false; + this.depth = false; + + this.dirtyId = 0; + this.dirtyFormat = 0; + this.dirtySize = 0; + + this.depthTexture = null; + this.colorTextures = []; + + this.glFrameBuffers = {}; + } + + get colorTexture() + { + return this.colorTextures[0]; + } + + addColorTexture(index, texture) + { + // TODO add some validation to the texture - same width / height etc? + this.colorTextures[index || 0] = texture || new Texture(null, { scaleMode: 0, + resolution: 1, + mipmap: false, + width: this.width, + height: this.height });// || new Texture(); + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + addDepthTexture(texture) + { + /* eslint-disable max-len */ + this.depthTexture = texture || new Texture(null, { scaleMode: 0, + resolution: 1, + width: this.width, + height: this.height, + mipmap: false, + format: FORMATS.DEPTH_COMPONENT, + type: TYPES.UNSIGNED_SHORT });// UNSIGNED_SHORT; + /* eslint-disable max-len */ + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + enableDepth() + { + this.depth = true; + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + enableStencil() + { + this.stencil = true; + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + resize(width, height) + { + if (width === this.width && height === this.height) return; + + this.width = width; + this.height = height; + + this.dirtyId++; + this.dirtySize++; + + for (let i = 0; i < this.colorTextures.length; i++) + { + this.colorTextures[i].setSize(width, height); + } + + if (this.depthTexture) + { + this.depthTexture.setSize(width, height); + } + } +} diff --git a/packages/core/src/framebuffer/FramebufferSystem.js b/packages/core/src/framebuffer/FramebufferSystem.js new file mode 100644 index 0000000..2cc2437 --- /dev/null +++ b/packages/core/src/framebuffer/FramebufferSystem.js @@ -0,0 +1,246 @@ +import System from '../System'; +import { Rectangle } from '@pixi/math'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class FramebufferSystem extends System +{ + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + contextChange() + { + this.gl = this.renderer.gl; + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + this.current = null; + this.viewport = new Rectangle(); + + this.drawBufferExtension = this.renderer.context.extensions.drawBuffers; + } + + bind(framebuffer, frame) + { + const gl = this.gl; + + this.current = framebuffer; + + if (framebuffer) + { + // TODO cacheing layer! + + const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID] || this.initFramebuffer(framebuffer); + + gl.bindFramebuffer(gl.FRAMEBUFFER, fbo.framebuffer); + // makesure all textures are unbound.. + + // now check for updates... + if (fbo.dirtyId !== framebuffer.dirtyId) + { + fbo.dirtyId = framebuffer.dirtyId; + + if (fbo.dirtyFormat !== framebuffer.dirtyFormat) + { + fbo.dirtyFormat = framebuffer.dirtyFormat; + this.updateFramebuffer(framebuffer); + } + else if (fbo.dirtySize !== framebuffer.dirtySize) + { + fbo.dirtySize = framebuffer.dirtySize; + this.resizeFramebuffer(framebuffer); + } + } + + for (let i = 0; i < framebuffer.colorTextures.length; i++) + { + if (framebuffer.colorTextures[i].texturePart) + { + this.renderer.texture.unbind(framebuffer.colorTextures[i].texture); + } + else + { + this.renderer.texture.unbind(framebuffer.colorTextures[i]); + } + } + + if (framebuffer.depthTexture) + { + this.renderer.texture.unbind(framebuffer.depthTexture); + } + + if (frame) + { + this.setViewport(frame.x, frame.y, frame.width, frame.height); + } + else + { + this.setViewport(0, 0, framebuffer.width, framebuffer.height); + } + } + else + { + gl.bindFramebuffer(gl.FRAMEBUFFER, null); + + if (frame) + { + this.setViewport(frame.x, frame.y, frame.width, frame.height); + } + else + { + this.setViewport(0, 0, this.renderer.width, this.renderer.height); + } + } + } + + setViewport(x, y, width, height) + { + const v = this.viewport; + + if (v.width !== width || v.height !== height || v.x !== x || v.y !== y) + { + v.x = x; + v.y = y; + v.width = width; + v.height = height; + + this.gl.viewport(x, y, width, height); + } + } + + get size() + { + if (this.current) + { + // TODO store temp + return { x: 0, y: 0, width: this.current.width, height: this.current.height }; + } + + return { x: 0, y: 0, width: this.renderer.width, height: this.renderer.height }; + } + + clear(r, g, b, a) + { + const gl = this.gl; + + // TODO clear color can be set only one right? + gl.clearColor(r, g, b, a); + gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); + } + + // private functions... + + initFramebuffer(framebuffer) + { + const gl = this.gl; + + // TODO - make this a class? + const fbo = { + framebuffer: gl.createFramebuffer(), + stencil: null, + dirtyId: 0, + dirtyFormat: 0, + dirtySize: 0, + }; + + framebuffer.glFrameBuffers[this.CONTEXT_UID] = fbo; + + return fbo; + } + + resizeFramebuffer(framebuffer) + { + const gl = this.gl; + + if (framebuffer.stencil || framebuffer.depth) + { + gl.bindRenderbuffer(gl.RENDERBUFFER, this.stencil); + gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); + } + } + + updateFramebuffer(framebuffer) + { + const gl = this.gl; + + const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID]; + + // bind the color texture + const colorTextures = framebuffer.colorTextures; + + let count = colorTextures.length; + + if (!this.drawBufferExtension) + { + count = Math.min(count, 1); + } + + const activeTextures = []; + + for (let i = 0; i < count; i++) + { + const texture = framebuffer.colorTextures[i]; + + if (texture.texturePart) + { + this.renderer.texture.bind(texture.texture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.COLOR_ATTACHMENT0 + i, + gl.TEXTURE_CUBE_MAP_NEGATIVE_X + texture.side, + texture.texture._glTextures[this.CONTEXT_UID].texture, + 0); + } + else + { + this.renderer.texture.bind(texture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.COLOR_ATTACHMENT0 + i, + gl.TEXTURE_2D, + texture._glTextures[this.CONTEXT_UID].texture, + 0); + } + + activeTextures.push(gl.COLOR_ATTACHMENT0 + i); + } + + if (this.drawBufferExtension && activeTextures.length > 1) + { + this.drawBufferExtension.drawBuffersWEBGL(activeTextures); + } + + if (framebuffer.depthTexture) + { + const depthTextureExt = this.renderer.context.extensions.depthTexture; + + if (depthTextureExt) + { + const depthTexture = framebuffer.depthTexture; + + this.renderer.texture.bind(depthTexture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.DEPTH_ATTACHMENT, + gl.TEXTURE_2D, + depthTexture._glTextures[this.CONTEXT_UID].texture, + 0); + } + } + + if (framebuffer.stencil || framebuffer.depth) + { + fbo.stencil = gl.createRenderbuffer(); + + gl.bindRenderbuffer(gl.RENDERBUFFER, fbo.stencil); + + // TODO.. this is depth AND stencil? + gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, fbo.stencil); + gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); + // fbo.enableStencil(); + } + } +} diff --git a/packages/core/src/geometry/GLBuffer.js b/packages/core/src/geometry/GLBuffer.js new file mode 100644 index 0000000..0477e8a --- /dev/null +++ b/packages/core/src/geometry/GLBuffer.js @@ -0,0 +1,9 @@ +export default class GLBuffer +{ + constructor(buffer) + { + this.buffer = buffer; + this.updateID = -1; + this.byteLength = -1; + } +} diff --git a/packages/core/src/geometry/GeometrySystem.js b/packages/core/src/geometry/GeometrySystem.js new file mode 100644 index 0000000..959f827 --- /dev/null +++ b/packages/core/src/geometry/GeometrySystem.js @@ -0,0 +1,388 @@ +import System from '../System'; +import GLBuffer from './GLBuffer'; + +const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class GeometrySystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this._activeGeometry = null; + this._activeVao = null; + + this.hasVao = true; + this.hasInstance = true; + } + + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + contextChange() + { + const gl = this.gl = this.renderer.gl; + + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + + // webgl2 + if (!gl.createVertexArray) + { + // webgl 1! + let nativeVaoExtension = this.renderer.context.extensions.vertexArrayObject; + + if (this.renderer.legacy) + { + nativeVaoExtension = null; + } + + if (nativeVaoExtension) + { + gl.createVertexArray = () => + nativeVaoExtension.createVertexArrayOES(); + + gl.bindVertexArray = (vao) => + nativeVaoExtension.bindVertexArrayOES(vao); + + gl.deleteVertexArray = (vao) => + nativeVaoExtension.deleteVertexArrayOES(vao); + } + else + { + this.hasVao = false; + gl.createVertexArray = () => + { + // empty + }; + + gl.bindVertexArray = () => + { + // empty + }; + + gl.deleteVertexArray = () => + { + // empty + }; + } + } + + if (!gl.vertexAttribDivisor) + { + const instanceExt = gl.getExtension('ANGLE_instanced_arrays'); + + if (instanceExt) + { + gl.vertexAttribDivisor = (a, b) => + instanceExt.vertexAttribDivisorANGLE(a, b); + + gl.drawElementsInstanced = (a, b, c, d, e) => + instanceExt.drawElementsInstancedANGLE(a, b, c, d, e); + + gl.drawArraysInstanced = (a, b, c, d) => + instanceExt.drawArraysInstancedANGLE(a, b, c, d); + } + else + { + this.hasInstance = false; + } + } + } + + /** + * Binds geometry so that is can be drawn. Creating a Vao if required + * @private + * @param {PIXI.Geometry} geometry instance of geometry to bind + */ + bind(geometry, shader) + { + shader = shader || this.renderer.shader.shader; + + const gl = this.gl; + + // not sure the best way to address this.. + // currently different shaders require different VAOs for the same geometry + // Still mulling over the best way to solve this one.. + // will likely need to modify the shader attribute locations at run time! + let vaos = geometry.glVertexArrayObjects[this.CONTEXT_UID]; + + if (!vaos) + { + geometry.glVertexArrayObjects[this.CONTEXT_UID] = vaos = {}; + } + + const vao = vaos[shader.program.id] || this.initGeometryVao(geometry, shader.program); + + this._activeGeometry = geometry; + + if (this._activeVao !== vao) + { + this._activeVao = vao; + + if (this.hasVao) + { + gl.bindVertexArray(vao); + } + else + { + this.activateVao(geometry, shader.program); + } + } + + // TODO - optimise later! + // don't need to loop through if nothing changed! + // maybe look to add an 'autoupdate' to geometry? + this.updateBuffers(); + } + + reset() + { + this.unbind(); + } + + updateBuffers() + { + const geometry = this._activeGeometry; + const gl = this.gl; + + for (let i = 0; i < geometry.buffers.length; i++) + { + const buffer = geometry.buffers[i]; + + const glBuffer = buffer._glBuffers[this.CONTEXT_UID]; + + if (buffer._updateID !== glBuffer.updateID) + { + glBuffer.updateID = buffer._updateID; + + // TODO can cache this on buffer! maybe added a getter / setter? + const type = buffer.index ? gl.ELEMENT_ARRAY_BUFFER : gl.ARRAY_BUFFER; + const drawType = buffer.static ? gl.STATIC_DRAW : gl.DYNAMIC_DRAW; + + gl.bindBuffer(type, glBuffer.buffer); + + if (glBuffer.byteLength >= buffer.data.byteLength) + { + // offset is always zero for now! + gl.bufferSubData(type, 0, buffer.data); + } + else + { + gl.bufferData(type, buffer.data, drawType); + } + } + } + } + + checkCompatability(geometry, program) + { + // geometry must have at least all the attributes that the shader requires. + const geometryAttributes = geometry.attributes; + const shaderAttributes = program.attributeData; + + for (const j in shaderAttributes) + { + if (!geometryAttributes[j]) + { + throw new Error(`shader and geometry incompatible, geometry missing the "${j}" attribute`); + } + } + } + + /** + * Creates a Vao with the same structure as the geometry and stores it on the geometry. + * @private + * @param {PIXI.Geometry} geometry instance of geometry to to generate Vao for + */ + initGeometryVao(geometry, program) + { + this.checkCompatability(geometry, program); + + const gl = this.gl; + const CONTEXT_UID = this.CONTEXT_UID; + const buffers = geometry.buffers; + const attributes = geometry.attributes; + + const tempStride = {}; + const tempStart = {}; + + for (const j in buffers) + { + tempStride[j] = 0; + tempStart[j] = 0; + } + + for (const j in attributes) + { + if (!attributes[j].size && program.attributeData[j]) + { + attributes[j].size = program.attributeData[j].size; + } + + tempStride[attributes[j].buffer] += attributes[j].size * byteSizeMap[attributes[j].type]; + } + + for (const j in attributes) + { + const attribute = attributes[j]; + const attribSize = attribute.size; + + if (attribute.stride === undefined) + { + if (tempStride[attribute.buffer] === attribSize * byteSizeMap[attribute.type]) + { + attribute.stride = 0; + } + else + { + attribute.stride = tempStride[attribute.buffer]; + } + } + + if (attribute.start === undefined) + { + attribute.start = tempStart[attribute.buffer]; + + tempStart[attribute.buffer] += attribSize * byteSizeMap[attribute.type]; + } + } + + // first update - and create the buffers! + // only create a gl buffer if it actually gets + for (let i = 0; i < buffers.length; i++) + { + const buffer = buffers[i]; + + if (!buffer._glBuffers[CONTEXT_UID]) + { + buffer._glBuffers[CONTEXT_UID] = new GLBuffer(gl.createBuffer()); + } + } + + // TODO - maybe make this a data object? + // lets wait to see if we need to first! + const vao = gl.createVertexArray(); + + gl.bindVertexArray(vao); + + this.activateVao(geometry, program); + + gl.bindVertexArray(null); + + // add it to the cache! + geometry.glVertexArrayObjects[this.CONTEXT_UID][program.id] = vao; + + return vao; + } + + activateVao(geometry, program) + { + const gl = this.gl; + const CONTEXT_UID = this.CONTEXT_UID; + const buffers = geometry.buffers; + const attributes = geometry.attributes; + + if (geometry.indexBuffer) + { + // first update the index buffer if we have one.. + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, geometry.indexBuffer._glBuffers[CONTEXT_UID].buffer); + } + + let lastBuffer = null; + + // add a new one! + for (const j in attributes) + { + const attribute = attributes[j]; + const buffer = buffers[attribute.buffer]; + const glBuffer = buffer._glBuffers[CONTEXT_UID]; + + if (program.attributeData[j]) + { + if (lastBuffer !== glBuffer) + { + gl.bindBuffer(gl.ARRAY_BUFFER, glBuffer.buffer); + + lastBuffer = glBuffer; + } + + const location = program.attributeData[j].location; + + // TODO introduce state again + // we can optimise this for older devices that have no VAOs + gl.enableVertexAttribArray(location); + + gl.vertexAttribPointer(location, + attribute.size, + attribute.type || gl.FLOAT, + attribute.normalized, + attribute.stride, + attribute.start); + + if (attribute.instance) + { + // TODO calculate instance count based of this... + if (this.hasInstance) + { + gl.vertexAttribDivisor(location, 1); + } + else + { + throw new Error('geometry error, GPU Instancing is not supported on this device'); + } + } + } + } + } + + draw(type, size, start, instanceCount) + { + const gl = this.gl; + const geometry = this._activeGeometry; + + // TODO.. this should not change so maybe cache the function? + + if (geometry.indexBuffer) + { + if (geometry.instanced) + { + /* eslint-disable max-len */ + gl.drawElementsInstanced(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2, instanceCount || 1); + /* eslint-enable max-len */ + } + else + { + gl.drawElements(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2); + } + } + else + if (geometry.instanced) + { + // TODO need a better way to calculate size.. + gl.drawArraysInstanced(type, start, size || geometry.getSize(), instanceCount || 1); + } + else + { + gl.drawArrays(type, start, size || geometry.getSize()); + } + + return this; + } + + unbind() + { + this.gl.bindVertexArray(null); + this._activeVao = null; + this._activeGeometry = null; + } +} diff --git a/packages/core/src/geometry/utils/setVertexAttribArrays.js b/packages/core/src/geometry/utils/setVertexAttribArrays.js new file mode 100644 index 0000000..23b6959 --- /dev/null +++ b/packages/core/src/geometry/utils/setVertexAttribArrays.js @@ -0,0 +1,54 @@ +// var GL_MAP = {}; + +/** + * @param gl {WebGLRenderingContext} The current WebGL context + * @param attribs {*} + * @param state {*} + */ +export default function setVertexAttribArrays(gl, attribs, state) +{ + let i; + + if (state) + { + const tempAttribState = state.tempAttribState; + const attribState = state.attribState; + + for (i = 0; i < tempAttribState.length; i++) + { + tempAttribState[i] = false; + } + + // set the new attribs + for (i = 0; i < attribs.length; i++) + { + tempAttribState[attribs[i].attribute.location] = true; + } + + for (i = 0; i < attribState.length; i++) + { + if (attribState[i] !== tempAttribState[i]) + { + attribState[i] = tempAttribState[i]; + + if (state.attribState[i]) + { + gl.enableVertexAttribArray(i); + } + else + { + gl.disableVertexAttribArray(i); + } + } + } + } + else + { + for (i = 0; i < attribs.length; i++) + { + const attrib = attribs[i]; + + gl.enableVertexAttribArray(attrib.attribute.location); + } + } +} diff --git a/packages/core/src/index.js b/packages/core/src/index.js index 58c456a..fd1f547 100644 --- a/packages/core/src/index.js +++ b/packages/core/src/index.js @@ -1,30 +1,30 @@ import * as resources from './textures/resources'; -import * as systems from './renderers/systems'; +import * as systems from './systems'; -export { resources }; export { systems }; +export { resources }; -export { default as Renderer } from './renderers/Renderer'; -export { default as AbstractRenderer } from './renderers/AbstractRenderer'; -export { default as FrameBuffer } from './textures/FrameBuffer'; +export { default as System } from './System'; +export { default as Renderer } from './Renderer'; +export { default as AbstractRenderer } from './AbstractRenderer'; +export { default as FrameBuffer } from './framebuffer/FrameBuffer'; export { default as CubeTexture } from './textures/CubeTexture'; export { default as BaseTexture } from './textures/BaseTexture'; export { default as Texture } from './textures/Texture'; export { default as TextureMatrix } from './textures/TextureMatrix'; -export { default as RenderTexture } from './textures/RenderTexture'; -export { default as BaseRenderTexture } from './textures/BaseRenderTexture'; +export { default as RenderTexture } from './renderTexture/RenderTexture'; +export { default as BaseRenderTexture } from './renderTexture/BaseRenderTexture'; export { default as TextureUvs } from './textures/TextureUvs'; -export { default as State } from './renderers/State'; -export { default as ObjectRenderer } from './renderers/utils/ObjectRenderer'; -export { default as Quad } from './renderers/utils/Quad'; -export { default as QuadUv } from './renderers/utils/QuadUv'; -export { default as checkMaxIfStatmentsInShader } from './renderers/utils/checkMaxIfStatmentsInShader'; +export { default as State } from './state/State'; +export { default as ObjectRenderer } from './batch/ObjectRenderer'; +export { default as Quad } from './utils/Quad'; +export { default as QuadUv } from './utils/QuadUv'; +export { default as checkMaxIfStatmentsInShader } from './shader/utils/checkMaxIfStatmentsInShader'; export { default as Shader } from './shader/Shader'; export { default as Program } from './shader/Program'; export { default as UniformGroup } from './shader/UniformGroup'; -export { default as SpriteMaskFilter } from './renderers/filters/spriteMask/SpriteMaskFilter'; -export { default as Filter } from './renderers/filters/Filter'; +export { default as SpriteMaskFilter } from './filters/spriteMask/SpriteMaskFilter'; +export { default as Filter } from './filters/Filter'; export { default as Attribute } from './geometry/Attribute'; export { default as Buffer } from './geometry/Buffer'; export { default as Geometry } from './geometry/Geometry'; - diff --git a/packages/core/src/mask/MaskSystem.js b/packages/core/src/mask/MaskSystem.js new file mode 100644 index 0000000..99174c2 --- /dev/null +++ b/packages/core/src/mask/MaskSystem.js @@ -0,0 +1,199 @@ +import System from '../System'; +import AlphaMaskFilter from '../filters/spriteMask/SpriteMaskFilter'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class MaskSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + // TODO - we don't need both! + this.scissor = false; + this.scissorData = null; + this.scissorRenderTarget = null; + + this.enableScissor = false; + + this.alphaMaskPool = []; + this.alphaMaskIndex = 0; + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.DisplayObject} target - Display Object to push the mask to + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + push(target, maskData) + { + // TODO the root check means scissor rect will not + // be used on render textures more info here: + // https://github.com/pixijs/pixi.js/pull/3545 + + if (maskData.texture) + { + this.pushSpriteMask(target, maskData); + } + else if (this.enableScissor + && !this.scissor + && this.renderer._activeRenderTarget.root + && !this.renderer.stencil.stencilMaskStack.length + && maskData.isFastRect()) + { + const matrix = maskData.worldTransform; + + let rot = Math.atan2(matrix.b, matrix.a); + + // use the nearest degree! + rot = Math.round(rot * (180 / Math.PI)); + + if (rot % 90) + { + this.pushStencilMask(maskData); + } + else + { + this.pushScissorMask(target, maskData); + } + } + else + { + this.pushStencilMask(maskData); + } + } + + /** + * Removes the last mask from the mask stack and doesn't return it. + * + * @param {PIXI.DisplayObject} target - Display Object to pop the mask from + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + pop(target, maskData) + { + if (maskData.texture) + { + this.popSpriteMask(target, maskData); + } + else if (this.enableScissor && !this.renderer.stencil.stencilMaskStack.length) + { + this.popScissorMask(target, maskData); + } + else + { + this.popStencilMask(target, maskData); + } + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.RenderTarget} target - Display Object to push the sprite mask to + * @param {PIXI.Sprite} maskData - Sprite to be used as the mask + */ + pushSpriteMask(target, maskData) + { + let alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; + + if (!alphaMaskFilter) + { + alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; + } + + alphaMaskFilter[0].resolution = this.renderer.resolution; + alphaMaskFilter[0].maskSprite = maskData; + + // TODO - may cause issues! + target.filterArea = maskData.getBounds(true); + + this.renderer.filterSystem.pushFilter(target, alphaMaskFilter); + + this.alphaMaskIndex++; + } + + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popSpriteMask() + { + this.renderer.filterSystem.popFilter(); + this.alphaMaskIndex--; + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + pushStencilMask(maskData) + { + this.renderer.batch.flush(); + this.renderer.stencil.pushStencil(maskData); + } + + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popStencilMask() + { + // this.renderer.currentRenderer.stop(); + this.renderer.stencil.popStencil(); + } + + /** + * + * @param {PIXI.DisplayObject} target - Display Object to push the mask to + * @param {PIXI.Graphics} maskData - The masking data. + */ + pushScissorMask(target, maskData) + { + maskData.renderable = true; + + const renderTarget = this.renderer._activeRenderTarget; + + const bounds = maskData.getBounds(); + + bounds.fit(renderTarget.size); + maskData.renderable = false; + + this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); + + const resolution = this.renderer.resolution; + + this.renderer.gl.scissor( + bounds.x * resolution, + (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, + bounds.width * resolution, + bounds.height * resolution + ); + + this.scissorRenderTarget = renderTarget; + this.scissorData = maskData; + this.scissor = true; + } + + /** + * + * + */ + popScissorMask() + { + this.scissorRenderTarget = null; + this.scissorData = null; + this.scissor = false; + + // must be scissor! + const gl = this.renderer.gl; + + gl.disable(gl.SCISSOR_TEST); + } +} diff --git a/packages/core/src/mask/StencilSystem.js b/packages/core/src/mask/StencilSystem.js new file mode 100644 index 0000000..62796a5 --- /dev/null +++ b/packages/core/src/mask/StencilSystem.js @@ -0,0 +1,133 @@ +import System from '../System'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class StencilSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + this.stencilMaskStack = []; + } + + /** + * Changes the mask stack that is used by this System. + * + * @param {PIXI.Graphics[]} stencilMaskStack - The mask stack + */ + setMaskStack(stencilMaskStack) + { + const gl = this.renderer.gl; + + if (stencilMaskStack.length !== this.stencilMaskStack.length) + { + if (stencilMaskStack.length === 0) + { + gl.disable(gl.STENCIL_TEST); + } + else + { + gl.enable(gl.STENCIL_TEST); + } + } + + this.stencilMaskStack = stencilMaskStack; + } + + /** + * Applies the Mask and adds it to the current stencil stack. @alvin + * + * @param {PIXI.Graphics} graphics - The mask + */ + pushStencil(graphics) + { + this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); + + // this.renderer._activeRenderTarget.attachStencilBuffer(); + + const gl = this.renderer.gl; + const prevMaskCount = this.stencilMaskStack.length; + + if (prevMaskCount === 0) + { + gl.enable(gl.STENCIL_TEST); + } + + this.stencilMaskStack.push(graphics); + + // Increment the refference stencil value where the new mask overlaps with the old ones. + gl.colorMask(false, false, false, false); + gl.stencilFunc(gl.EQUAL, prevMaskCount, this._getBitwiseMask()); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.INCR); + this.renderer.plugins.graphics.render(graphics); + + this._useCurrent(); + } + + /** + * Removes the last mask from the stencil stack. @alvin + */ + popStencil() + { + this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); + + const gl = this.renderer.gl; + const graphics = this.stencilMaskStack.pop(); + + if (this.stencilMaskStack.length === 0) + { + // the stack is empty! + gl.disable(gl.STENCIL_TEST); + gl.clear(gl.STENCIL_BUFFER_BIT); + gl.clearStencil(0); + } + else + { + // Decrement the refference stencil value where the popped mask overlaps with the other ones + gl.colorMask(false, false, false, false); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.DECR); + this.renderer.plugins.graphics.render(graphics); + + this._useCurrent(); + } + } + + /** + * Setup renderer to use the current stencil data. + */ + _useCurrent() + { + const gl = this.renderer.gl; + + gl.colorMask(true, true, true, true); + gl.stencilFunc(gl.EQUAL, this.stencilMaskStack.length, this._getBitwiseMask()); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP); + } + + /** + * Fill 1s equal to the number of acitve stencil masks. + * + * @return {number} The bitwise mask. + */ + _getBitwiseMask() + { + return (1 << this.stencilMaskStack.length) - 1; + } + + /** + * Destroys the mask stack. + * + */ + destroy() + { + super.destroy(this); + + this.stencilMaskStack = null; + } +} diff --git a/packages/core/src/projection/ProjectionSystem.js b/packages/core/src/projection/ProjectionSystem.js new file mode 100644 index 0000000..6e63b73 --- /dev/null +++ b/packages/core/src/projection/ProjectionSystem.js @@ -0,0 +1,73 @@ +import System from '../System'; +import { Matrix } from '@pixi/math'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class ProjectionSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.projectionMatrix = new Matrix(); + } + + update(destinationFrame, sourceFrame, resolution, root) + { + this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; + this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; + + this.calculateProjection(this.destinationFrame, this.sourceFrame, resolution, root); + + this.renderer.globalUniforms.uniforms.projectionMatrix = this.projectionMatrix; + this.renderer.globalUniforms.update(); + } + + /** + * Updates the projection matrix based on a projection frame (which is a rectangle) + * + * @param {Rectangle} destinationFrame - The destination frame. + * @param {Rectangle} sourceFrame - The source frame. + */ + calculateProjection(destinationFrame, sourceFrame, resolution, root) + { + const pm = this.projectionMatrix; + + // I don't think we will need this line.. + // pm.identity(); + + if (!root) + { + pm.a = (1 / destinationFrame.width * 2) * resolution; + pm.d = (1 / destinationFrame.height * 2) * resolution; + + pm.tx = -1 - (sourceFrame.x * pm.a); + pm.ty = -1 - (sourceFrame.y * pm.d); + } + else + { + pm.a = (1 / destinationFrame.width * 2) * resolution; + pm.d = (-1 / destinationFrame.height * 2) * resolution; + + pm.tx = -1 - (sourceFrame.x * pm.a); + pm.ty = 1 - (sourceFrame.y * pm.d); + } + } + + /** + * Sets the transform of the active render target to the given matrix + * + * @param {PIXI.Matrix} matrix - The transformation matrix + */ + setTransform()// matrix) + { + // this._activeRenderTarget.transform = matrix; + } +} diff --git a/packages/core/src/renderTexture/BaseRenderTexture.js b/packages/core/src/renderTexture/BaseRenderTexture.js new file mode 100644 index 0000000..88eb83b --- /dev/null +++ b/packages/core/src/renderTexture/BaseRenderTexture.js @@ -0,0 +1,137 @@ +import BaseTexture from '../textures/BaseTexture'; +import FrameBuffer from '../framebuffer/FrameBuffer'; + +/** + * A BaseRenderTexture is a special texture that allows any PixiJS display object to be rendered to it. + * + * __Hint__: All DisplayObjects (i.e. Sprites) that render to a BaseRenderTexture should be preloaded + * otherwise black rectangles will be drawn instead. + * + * A BaseRenderTexture takes a snapshot of any Display Object given to its render method. The position + * and rotation of the given Display Objects is ignored. For example: + * + * ```js + * let renderer = PIXI.autoDetectRenderer(1024, 1024, { view: canvas, ratio: 1 }); + * let baseRenderTexture = new PIXI.BaseRenderTexture(renderer, 800, 600); + * let sprite = PIXI.Sprite.fromImage("spinObj_01.png"); + * + * sprite.position.x = 800/2; + * sprite.position.y = 600/2; + * sprite.anchor.x = 0.5; + * sprite.anchor.y = 0.5; + * + * baseRenderTexture.render(sprite); + * ``` + * + * The Sprite in this case will be rendered using its local transform. To render this sprite at 0,0 + * you can clear the transform + * + * ```js + * + * sprite.setTransform() + * + * let baseRenderTexture = new PIXI.BaseRenderTexture(100, 100); + * let renderTexture = new PIXI.RenderTexture(baseRenderTexture); + * + * renderer.render(sprite, renderTexture); // Renders to center of RenderTexture + * ``` + * + * @class + * @extends PIXI.BaseTexture + * @memberof PIXI + */ +export default class BaseRenderTexture extends BaseTexture +{ + /** + * @param {object} [options] + * @param {number} [options.width=100] - The width of the base render texture + * @param {number} [options.height=100] - The height of the base render texture + * @param {PIXI.SCALE_MODES} [options.scaleMode] - See {@link PIXI.SCALE_MODES} for possible values + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the texture being generated + */ + constructor(options) + { + if (typeof options === 'number') + { + /* eslint-disable prefer-rest-params */ + // Backward compatibility of signature + const width = arguments[0]; + const height = arguments[1]; + const scaleMode = arguments[2]; + const resolution = arguments[3]; + + options = { width, height, scaleMode, resolution }; + /* eslint-enable prefer-rest-params */ + } + + super(null, options); + + const { width, height } = options || {}; + + // Set defaults + this.mipmap = false; + this.width = Math.ceil(width) || 100; + this.height = Math.ceil(height) || 100; + this.valid = true; + + /** + * A map of renderer IDs to webgl renderTargets + * + * @private + * @member {object} + */ + // this._glRenderTargets = {}; + + /** + * A reference to the canvas render target (we only need one as this can be shared across renderers) + * + * @private + * @member {object} + */ + this._canvasRenderTarget = null; + + this.clearColor = [0, 0, 0, 0]; + + this.frameBuffer = new FrameBuffer(width * this.resolution, height * this.resolution) + .addColorTexture(0, this); + + // TODO - could this be added the systems? + + /** + * The data structure for the stencil masks + * + * @member {PIXI.Graphics[]} + */ + this.stencilMaskStack = []; + + /** + * The data structure for the filters + * + * @member {PIXI.Graphics[]} + */ + this.filterStack = [{}]; + } + + /** + * Resizes the BaseRenderTexture. + * + * @param {number} width - The width to resize to. + * @param {number} height - The height to resize to. + */ + resize(width, height) + { + width = Math.ceil(width); + height = Math.ceil(height); + this.frameBuffer.resize(width * this.resolution, height * this.resolution); + } + + /** + * Destroys this texture + * + */ + destroy() + { + super.destroy(true); + this.renderer = null; + } +} diff --git a/packages/core/src/renderTexture/RenderTexture.js b/packages/core/src/renderTexture/RenderTexture.js new file mode 100644 index 0000000..f1ecf1f --- /dev/null +++ b/packages/core/src/renderTexture/RenderTexture.js @@ -0,0 +1,153 @@ +import BaseRenderTexture from './BaseRenderTexture'; +import Texture from '../textures/Texture'; + +/** + * A RenderTexture is a special texture that allows any PixiJS display object to be rendered to it. + * + * __Hint__: All DisplayObjects (i.e. Sprites) that render to a RenderTexture should be preloaded + * otherwise black rectangles will be drawn instead. + * + * A RenderTexture takes a snapshot of any Display Object given to its render method. For example: + * + * ```js + * let renderer = PIXI.autoDetectRenderer(1024, 1024, { view: canvas, ratio: 1 }); + * let renderTexture = PIXI.RenderTexture.create(800, 600); + * let sprite = PIXI.Sprite.fromImage("spinObj_01.png"); + * + * sprite.position.x = 800/2; + * sprite.position.y = 600/2; + * sprite.anchor.x = 0.5; + * sprite.anchor.y = 0.5; + * + * renderer.render(sprite, renderTexture); + * ``` + * + * The Sprite in this case will be rendered using its local transform. To render this sprite at 0,0 + * you can clear the transform + * + * ```js + * + * sprite.setTransform() + * + * let renderTexture = new PIXI.RenderTexture.create(100, 100); + * + * renderer.render(sprite, renderTexture); // Renders to center of RenderTexture + * ``` + * + * @class + * @extends PIXI.Texture + * @memberof PIXI + */ +export default class RenderTexture extends Texture +{ + /** + * @param {PIXI.BaseRenderTexture} baseRenderTexture - The renderer used for this RenderTexture + * @param {PIXI.Rectangle} [frame] - The rectangle frame of the texture to show + */ + constructor(baseRenderTexture, frame) + { + // support for legacy.. + let _legacyRenderer = null; + + if (!(baseRenderTexture instanceof BaseRenderTexture)) + { + /* eslint-disable prefer-rest-params, no-console */ + const width = arguments[1]; + const height = arguments[2]; + const scaleMode = arguments[3]; + const resolution = arguments[4]; + + // we have an old render texture.. + console.warn(`Please use RenderTexture.create(${width}, ${height}) instead of the ctor directly.`); + _legacyRenderer = arguments[0]; + /* eslint-enable prefer-rest-params, no-console */ + + frame = null; + baseRenderTexture = new BaseRenderTexture({ + width, + height, + scaleMode, + resolution, + }); + } + + /** + * The base texture object that this texture uses + * + * @member {BaseTexture} + */ + super(baseRenderTexture, frame); + + this.legacyRenderer = _legacyRenderer; + + /** + * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. + * + * @member {boolean} + */ + this.valid = true; + + /** + * FilterSystem temporary storage + * @private + * @member {PIXI.Rectangle} + */ + this.filterFrame = null; + + this._updateUvs(); + } + + /** + * Resizes the RenderTexture. + * + * @param {number} width - The width to resize to. + * @param {number} height - The height to resize to. + * @param {boolean} [resizeBaseTexture=true] - Should the baseTexture.width and height values be resized as well? + */ + resize(width, height, resizeBaseTexture = true) + { + width = Math.ceil(width); + height = Math.ceil(height); + + // TODO - could be not required.. + this.valid = (width > 0 && height > 0); + + this._frame.width = this.orig.width = width; + this._frame.height = this.orig.height = height; + + if (resizeBaseTexture) + { + this.baseTexture.resize(width, height); + } + + this._updateUvs(); + } + + /** + * A short hand way of creating a render texture. + * + * @param {object} [options] - Options + * @param {number} [options.width=100] - The width of the render texture + * @param {number} [options.height=100] - The height of the render texture + * @param {number} [options.scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the texture being generated + * @return {PIXI.RenderTexture} The new render texture + */ + static create(options) + { + // fallback, old-style: create(width, height, scaleMode, resolution) + if (typeof options === 'number') + { + /* eslint-disable prefer-rest-params */ + options = { + width: options, + height: arguments[1], + scaleMode: arguments[2], + resolution: arguments[3], + }; + /* eslint-enable prefer-rest-params */ + } + + return new RenderTexture(new BaseRenderTexture(options)); + } +} diff --git a/packages/core/src/renderTexture/RenderTextureSystem.js b/packages/core/src/renderTexture/RenderTextureSystem.js new file mode 100644 index 0000000..8d0ca61 --- /dev/null +++ b/packages/core/src/renderTexture/RenderTextureSystem.js @@ -0,0 +1,115 @@ +import System from '../System'; +import { Rectangle } from '@pixi/math'; + +const tempRect = new Rectangle(); + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class RenderTextureSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.clearColor = renderer._backgroundColorRgba; + + // TODO moe this property somewhere else! + this.defaultMaskStack = []; + this.defaultFilterStack = [{}]; + + // empty render texture? + this.renderTexture = null; + + this.destinationFrame = new Rectangle(); + } + + bind(renderTexture, sourceFrame, destinationFrame) + { + // TODO - do we want this?? + if (this.renderTexture === renderTexture) return; + this.renderTexture = renderTexture; + + const renderer = this.renderer; + + if (renderTexture) + { + const baseTexture = renderTexture.baseTexture; + + if (!destinationFrame) + { + tempRect.width = baseTexture.realWidth; + tempRect.height = baseTexture.realHeight; + + destinationFrame = tempRect; + } + + if (!sourceFrame) + { + sourceFrame = destinationFrame; + } + + this.renderer.framebuffer.bind(baseTexture.frameBuffer, destinationFrame); + + this.renderer.projection.update(destinationFrame, sourceFrame, baseTexture.resolution, false); + this.renderer.stencil.setMaskStack(baseTexture.stencilMaskStack); + } + else + { + // TODO these validation checks happen deeper down.. + // thing they can be avoided.. + if (!destinationFrame) + { + tempRect.width = renderer.width; + tempRect.height = renderer.height; + + destinationFrame = tempRect; + } + + if (!sourceFrame) + { + sourceFrame = destinationFrame; + } + + renderer.framebuffer.bind(null, destinationFrame); + + // TODO store this.. + this.renderer.projection.update(destinationFrame, sourceFrame, this.renderer.resolution, true); + this.renderer.stencil.setMaskStack(this.defaultMaskStack); + } + + this.destinationFrame.copyFrom(destinationFrame); + } + + /** + * Erases the render texture and fills the drawing area with a colour + * + * @param {number} [clearColor] - The colour + * @return {PIXI.Renderer} Returns itself. + */ + clear(clearColor) + { + if (this.renderTexture) + { + clearColor = clearColor || this.renderTexture.baseTexture.clearColor; + } + else + { + clearColor = clearColor || this.clearColor; + } + + this.renderer.framebuffer.clear(clearColor[0], clearColor[1], clearColor[2], clearColor[3]); + } + + resize()// screenWidth, screenHeight) + { + // resize the root only! + this.bind(null); + } +} diff --git a/packages/core/src/AbstractRenderer.js b/packages/core/src/AbstractRenderer.js new file mode 100644 index 0000000..64b2eb1 --- /dev/null +++ b/packages/core/src/AbstractRenderer.js @@ -0,0 +1,353 @@ +import { hex2string, hex2rgb } from '@pixi/utils'; +import { Matrix, Rectangle } from '@pixi/math'; +import { RENDERER_TYPE } from '@pixi/constants'; +import { settings } from '@pixi/settings'; +import { Container } from '@pixi/display'; +import RenderTexture from './renderTexture/RenderTexture'; +import EventEmitter from 'eventemitter3'; + +const tempMatrix = new Matrix(); + +/** + * 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 EventEmitter + * @memberof PIXI + */ +export default class AbstractRenderer extends EventEmitter +{ + // eslint-disable-next-line valid-jsdoc + /** + * @param {string} system - The name of the system this renderer is for. + * @param {object} [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.transparent=false] - If the render view is transparent, default false + * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false + * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. The + * resolution of the renderer retina would be 2. + * @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 {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when rendering, + * stopping pixel interpolation. + */ + constructor(system, options, arg2, arg3) + { + super(); + + // Support for constructor(system, screenWidth, screenHeight, options) + if (typeof options === 'number') + { + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + }, arg3); + } + + // 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 = RENDERER_TYPE.UNKNOWN; + + /** + * Measurements of the screen. (0, 0, screenWidth, screenHeight) + * + * Its safe to use as filterArea or hitArea for 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 1 + */ + this.resolution = options.resolution || settings.RESOLUTION; + + /** + * Whether the render view is transparent + * + * @member {boolean} + */ + this.transparent = options.transparent; + + /** + * Whether css dimensions of canvas view should be resized to screen dimensions automatically + * + * @member {boolean} + */ + this.autoResize = options.autoResize || false; + + /** + * Tracks the blend modes useful for this renderer. + * + * @member {object} + */ + this.blendModes = null; + + /** + * 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; + + /** + * If true PixiJS will Math.floor() x/y values when rendering, stopping pixel interpolation. + * Handy for crisp pixel art and speed on legacy devices. + * + * @member {boolean} + */ + this.roundPixels = options.roundPixels; + + /** + * The background color as a number. + * + * @member {number} + * @private + */ + this._backgroundColor = 0x000000; + + /** + * The background color as an [R, G, B] array. + * + * @member {number[]} + * @private + */ + this._backgroundColorRgba = [0, 0, 0, 0]; + + /** + * The background color as a string. + * + * @member {string} + * @private + */ + this._backgroundColorString = '#000000'; + + this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter + + /** + * This temporary display object used as the parent of the currently being rendered item + * + * @member {PIXI.DisplayObject} + * @private + */ + this._tempDisplayObjectParent = new Container(); + + /** + * The last root object that the renderer tried to render. + * + * @member {PIXI.DisplayObject} + * @private + */ + this._lastObjectRendered = this._tempDisplayObjectParent; + + /** + * Collection of plugins. + * @readonly + * @member {object} + */ + this.plugins = {}; + } + + /** + * Initialize the plugins. + * + * @protected + * @param {object} staticMap - The dictionary of staticly saved plugins. + */ + initPlugins(staticMap) + { + 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() + { + return this.view.width; + } + + /** + * Same as view.height, actual number of pixels in the canvas by vertical + * + * @member {number} + * @readonly + * @default 600 + */ + get height() + { + return this.view.height; + } + + /** + * Resizes the screen and canvas to the specified width and height + * Canvas dimensions are multiplied by resolution + * + * @param {number} screenWidth - the new width of the screen + * @param {number} screenHeight - the new height of the screen + */ + resize(screenWidth, screenHeight) + { + this.screen.width = screenWidth; + this.screen.height = screenHeight; + + this.view.width = screenWidth * this.resolution; + this.view.height = screenHeight * this.resolution; + + if (this.autoResize) + { + this.view.style.width = `${screenWidth}px`; + this.view.style.height = `${screenHeight}px`; + } + } + + /** + * 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 {PIXI.DisplayObject} displayObject - The displayObject the object will be generated from + * @param {number} scaleMode - Should be one of the scaleMode consts + * @param {number} resolution - The resolution / device pixel ratio of the texture being generated + * @param {PIXI.Rectangle} [region] - The region of the displayObject, that shall be rendered, + * if no region is specified, defaults to the local bounds of the displayObject. + * @return {PIXI.Texture} a texture of the graphics object + */ + generateTexture(displayObject, scaleMode, resolution, region) + { + region = region || displayObject.getLocalBounds(); + + // 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(region.width | 0, region.height | 0, scaleMode, resolution); + + tempMatrix.tx = -region.x; + tempMatrix.ty = -region.y; + + this.render(displayObject, renderTexture, false, tempMatrix, true); + + return renderTexture; + } + + /** + * Removes everything from the renderer and optionally removes the Canvas DOM element. + * + * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. + */ + destroy(removeView) + { + 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); + } + + this.plugins = null; + + this.type = RENDERER_TYPE.UNKNOWN; + + this.view = null; + + this.screen = null; + + this.resolution = 0; + + this.transparent = false; + + this.autoResize = false; + + this.blendModes = null; + + this.options = null; + + this.preserveDrawingBuffer = false; + this.clearBeforeRender = false; + + this.roundPixels = false; + + this._backgroundColor = 0; + this._backgroundColorRgba = null; + this._backgroundColorString = null; + + this._tempDisplayObjectParent = null; + this._lastObjectRendered = null; + } + + /** + * The background color to fill if not transparent + * + * @member {number} + */ + get backgroundColor() + { + return this._backgroundColor; + } + + set backgroundColor(value) // eslint-disable-line require-jsdoc + { + this._backgroundColor = value; + this._backgroundColorString = hex2string(value); + hex2rgb(value, this._backgroundColorRgba); + } +} diff --git a/packages/core/src/Renderer.js b/packages/core/src/Renderer.js new file mode 100644 index 0000000..2227e78 --- /dev/null +++ b/packages/core/src/Renderer.js @@ -0,0 +1,338 @@ +import AbstractRenderer from './AbstractRenderer'; +import { sayHello } from '@pixi/utils'; +import MaskSystem from './mask/MaskSystem'; +import StencilSystem from './mask/StencilSystem'; +import FilterSystem from './filters/FilterSystem'; +import FramebufferSystem from './framebuffer/FramebufferSystem'; +import RenderTextureSystem from './renderTexture/RenderTextureSystem'; +import TextureSystem from './textures/TextureSystem'; +import ProjectionSystem from './projection/ProjectionSystem'; +import StateSystem from './state/StateSystem'; +import GeometrySystem from './geometry/GeometrySystem'; +import ShaderSystem from './shader/ShaderSystem'; +import ContextSystem from './context/ContextSystem'; +import BatchSystem from './batch/BatchSystem'; +import TextureGCSystem from './textures/TextureGCSystem'; +import { RENDERER_TYPE } from '@pixi/constants'; +import UniformGroup from './shader/UniformGroup'; +import { Matrix } from '@pixi/math'; +import Runner from 'mini-runner'; + +/** + * 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 Render works by automatically managing webGLBatchs. + * 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 :) + * + * @class + * @memberof PIXI + * @extends PIXI.AbstractRenderer + */ +export default class Renderer extends AbstractRenderer +{ + // eslint-disable-next-line valid-jsdoc + /** + * + * @param {object} [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.transparent=false] - If the render view is transparent, default false + * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false + * @param {boolean} [options.antialias=false] - sets antialias. If not available natively then FXAA + * antialiasing is used + * @param {boolean} [options.forceFXAA=false] - forces FXAA antialiasing to be used over native. + * FXAA is faster, but may not always look as great + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. + * The resolution of the renderer retina would be 2. + * @param {boolean} [options.clearBeforeRender=true] - This sets if the renderer will clear + * the canvas or not before the new render pass. If you wish to set this to false, you *must* set + * preserveDrawingBuffer to `true`. + * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, + * enable this if you need to call toDataUrl on the webgl context. + * @param {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when + * rendering, stopping pixel interpolation. + * @param {number} [options.backgroundColor=0x000000] - The background color of the rendered area + * (shown if not transparent). + * @param {boolean} [options.legacy=false] - If true PixiJS will aim to ensure compatibility + * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. + * @param {string} [options.powerPreference] - Parameter passed to webgl context, set to "high-performance" + * for devices with dual graphics card + */ + constructor(options, arg2, arg3) + { + super('WebGL', options, arg2, arg3); + + /** + * The type of this renderer as a standardised const + * + * @member {number} + * @see PIXI.RENDERER_TYPE + */ + this.type = RENDERER_TYPE.WEBGL; + + // this will be set by the contextSystem (this.context) + this.gl = null; + this.CONTEXT_UID = 0; + this.legacy = !!options.legacy; + + // TODO legacy! + + // runners! + this.runners = { + destroy: new Runner('destroy'), + contextChange: new Runner('contextChange', 1), + reset: new Runner('reset'), + update: new Runner('update'), + postrender: new Runner('postrender'), + prerender: new Runner('prerender'), + resize: new Runner('resize', 2), + }; + + 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(GeometrySystem, 'geometry') + .addSystem(FramebufferSystem, 'framebuffer') + .addSystem(StencilSystem, 'stencil') + .addSystem(ProjectionSystem, 'projection') + .addSystem(TextureGCSystem, 'textureGC') + .addSystem(FilterSystem, 'filter') + .addSystem(RenderTextureSystem, 'renderTexture') + .addSystem(BatchSystem, 'batch'); + + this.initPlugins(Renderer.__plugins); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ + if (options.context) + { + this.context.initFromContext(options.context); + } + else + { + this.context.initFromOptions({ + alpha: this.transparent, + antialias: options.antialias, + premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', + stencil: true, + 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); + } + + /** + * Add a new system to the renderer. + * @param {class} ClassRef - Class reference + * @param {string} [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. + * @return {PIXI.Renderer} Return instance of renderer + */ + addSystem(ClassRef, name) + { + if (!name) + { + name = ClassRef.name; + } + + // TODO - read name from class.name.. + + /* + if(name.includes('System')) + { + name = name.replace('System', ''); + name = name.charAt(0).toLowerCase() + name.slice(1); + } + */ + + const system = new ClassRef(this); + + if (this[name]) + { + throw new Error(`Whoops! ${name} is already a manger`); + } + + this[name] = system; + + for (const i in this.runners) + { + this.runners[i].add(system); + } + + return this; + + /** + * 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. + */ + } + + /** + * Renders the object to its webGL view + * + * @param {PIXI.DisplayObject} displayObject - the object to be rendered + * @param {PIXI.RenderTexture} renderTexture - The render texture to render to. + * @param {boolean} [clear] - Should the canvas be cleared before the new render + * @param {PIXI.Transform} [transform] - A transform to apply to the render texture before rendering. + * @param {boolean} [skipUpdateTransform] - Should we skip the update transform pass? + */ + render(displayObject, renderTexture, clear, transform, skipUpdateTransform) + { + // can be handy to know! + this.renderingToScreen = !renderTexture; + + this.runners.prerender.run(); + this.emit('prerender'); + + // 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.parent; + + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = 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.run(); + + this.emit('postrender'); + } + + /** + * Resizes the webGL view to the specified width and height. + * + * @param {number} screenWidth - the new width of the screen + * @param {number} screenHeight - the new height of the screen + */ + resize(screenWidth, screenHeight) + { + AbstractRenderer.prototype.resize.call(this, screenWidth, screenHeight); + + this.runners.resize.run(screenWidth, screenHeight); + } + + /** + * Resets the WebGL state so you can render things however you fancy! + * + * @return {PIXI.Renderer} Returns itself. + */ + reset() + { + this.runners.reset.run(); + + return this; + } + + clear() + { + this.framebuffer.bind(); + this.framebuffer.clear(); + } + + /** + * Removes everything from the renderer (event listeners, spritebatch, etc...) + * + * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. + * See: https://github.com/pixijs/pixi.js/issues/2233 + */ + destroy(removeView) + { + this.runners.destroy.run(); + + // call base destroy + super.destroy(removeView); + + // TODO nullify all the managers.. + this.gl = null; + } + + /** + * 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 + * builds and excluding plugins. + * @name PIXI.Renderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. + */ + + /** + * Adds a plugin to the renderer. + * + * @method + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + static registerPlugin(pluginName, ctor) + { + Renderer.__plugins = Renderer.__plugins || {}; + Renderer.__plugins[pluginName] = ctor; + } +} diff --git a/packages/core/src/System.js b/packages/core/src/System.js new file mode 100644 index 0000000..05a10c8 --- /dev/null +++ b/packages/core/src/System.js @@ -0,0 +1,42 @@ +/** + * System is a base class used for extending systems used by the {@link PIXI.Renderer} + * @see PIXI.Renderer#addSystem + * @class + * @memberof PIXI + */ +export default class System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + /** + * The renderer this manager works for. + * + * @member {PIXI.Renderer} + */ + this.renderer = renderer; + + this.renderer.runners.contextChange.add(this); + } + + /** + * Generic method called when there is a WebGL context change. + * + */ + contextChange() + { + // do some codes init! + } + + /** + * Generic destroy methods to be overridden by the subclass + * + */ + destroy() + { + this.renderer.runners.contextChange.remove(this); + this.renderer = null; + } +} diff --git a/packages/core/src/batch/BatchSystem.js b/packages/core/src/batch/BatchSystem.js new file mode 100644 index 0000000..b04c249 --- /dev/null +++ b/packages/core/src/batch/BatchSystem.js @@ -0,0 +1,68 @@ +import System from '../System'; +import ObjectRenderer from './ObjectRenderer'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class BatchSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + super(renderer); + + /** + * An empty renderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.emptyRenderer = new ObjectRenderer(renderer); + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.currentRenderer = this.emptyRenderer; + } + + /** + * Changes the current renderer to the one given in parameter + * + * @param {PIXI.ObjectRenderer} objectRenderer - The object renderer to use. + */ + setObjectRenderer(objectRenderer) + { + if (this.currentRenderer === objectRenderer) + { + return; + } + + this.currentRenderer.stop(); + this.currentRenderer = objectRenderer; + + this.renderer.state.setState(objectRenderer.state); + + this.currentRenderer.start(); + } + + /** + * This should be called if you wish to do some custom rendering + * It will basically render anything that may be batched up such as sprites + * + */ + flush() + { + this.setObjectRenderer(this.emptyRenderer); + } + + reset() + { + this.setObjectRenderer(this.emptyRenderer); + } +} diff --git a/packages/core/src/batch/ObjectRenderer.js b/packages/core/src/batch/ObjectRenderer.js new file mode 100644 index 0000000..0c851a9 --- /dev/null +++ b/packages/core/src/batch/ObjectRenderer.js @@ -0,0 +1,48 @@ +import System from '../System'; + +/** + * Base for a common object renderer that can be used as a system renderer plugin. + * + * @class + * @extends PIXI.System + * @memberof PIXI + */ +export default class ObjectRenderer extends System +{ + /** + * Starts the renderer and sets the shader + * + */ + start() + { + // set the shader.. + } + + /** + * Stops the renderer + * + */ + stop() + { + this.flush(); + } + + /** + * Stub method for rendering content and emptying the current batch. + * + */ + flush() + { + // flush! + } + + /** + * Renders an object + * + * @param {PIXI.DisplayObject} object - The object to render. + */ + render(object) // eslint-disable-line no-unused-vars + { + // render the object + } +} diff --git a/packages/core/src/context/ContextSystem.js b/packages/core/src/context/ContextSystem.js new file mode 100644 index 0000000..301aab4 --- /dev/null +++ b/packages/core/src/context/ContextSystem.js @@ -0,0 +1,186 @@ +import System from '../System'; +import { settings } from '@pixi/settings'; + +let CONTEXT_UID = 0; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class ContextSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.webGLVersion = 1; + + this.handleContextLost = this.handleContextLost.bind(this); + this.handleContextRestored = this.handleContextRestored.bind(this); + + this.extensions = {}; + + renderer.view.addEventListener('webglcontextlost', this.handleContextLost, false); + renderer.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + } + + get isLost() + { + return (!this.gl || this.gl.isContextLost()); + } + + contextChange(gl) + { + this.gl = gl; + + // restore a context if it was previously lost + if (gl.isContextLost() && gl.getExtension('WEBGL_lose_context')) + { + gl.getExtension('WEBGL_lose_context').restoreContext(); + } + } + + initFromContext(gl) + { + this.gl = gl; + this.validateContext(gl); + this.renderer.gl = gl; + this.renderer.CONTEXT_UID = CONTEXT_UID++; + this.renderer.runners.contextChange.run(gl); + } + + initFromOptions(options) + { + const gl = this.createContext(this.renderer.view, options); + + this.initFromContext(gl); + } + + /** + * Helper class to create a webGL Context + * + * @class + * @memberof PIXI.glCore + * @param canvas {HTMLCanvasElement} the canvas element that we will get the context from + * @param options {Object} An options object that gets passed in to the canvas element containing the context attributes, + * see https://developer.mozilla.org/en/docs/Web/API/HTMLCanvasElement/getContext for the options available + * @return {WebGLRenderingContext} the WebGL context + */ + createContext(canvas, options) + { + let gl; + + if (settings.PREFER_WEBGL_2) + { + gl = canvas.getContext('webgl2', options); + } + + if (gl) + { + this.webGLVersion = 2; + } + else + { + this.webGLVersion = 1; + + gl = canvas.getContext('webgl', options) + || canvas.getContext('experimental-webgl', options); + + if (!gl) + { + // fail, not able to get a context + throw new Error('This browser does not support webGL. Try using the canvas renderer'); + } + } + + this.gl = gl; + + this.getExtensions(); + + return gl; + } + + getExtensions() + { + // time to set up default etensions that pixi uses.. + const gl = this.gl; + const extensions = this.extensions; + + if (this.webGLVersion === 1) + { + extensions.drawBuffers = gl.getExtension('WEBGL_draw_buffers'); + extensions.depthTexture = gl.getExtension('WEBKIT_WEBGL_depth_texture'); + extensions.floatTexture = gl.getExtension('OES_texture_float'); + extensions.loseContext = gl.getExtension('WEBGL_lose_context'); + + extensions.vertexArrayObject = gl.getExtension('OES_vertex_array_object') + || gl.getExtension('MOZ_OES_vertex_array_object') + || gl.getExtension('WEBKIT_OES_vertex_array_object'); + } + + // we don't use any specific WebGL 2 ones yet! + } + + /** + * Handles a lost webgl context + * + * @private + * @param {WebGLContextEvent} event - The context lost event. + */ + handleContextLost(event) + { + event.preventDefault(); + } + + /** + * Handles a restored webgl context + * + * @private + */ + handleContextRestored() + { + this.renderer.runners.contextChange.run(this.gl); + } + + destroy() + { + const view = this.renderer.view; + + // remove listeners + view.removeEventListener('webglcontextlost', this.handleContextLost); + view.removeEventListener('webglcontextrestored', this.handleContextRestored); + + this.gl.useProgram(null); + + if (this.extensions.loseContext) + { + this.extensions.loseContext.loseContext(); + } + } + + postrender() + { + this.gl.flush(); + } + + validateContext(gl) + { + const attributes = gl.getContextAttributes(); + + // this is going to be fairly simple for now.. but at least we have room to grow! + if (!attributes.stencil) + { + /* eslint-disable max-len */ + + /* eslint-disable no-console */ + console.warn('Provided WebGL context does not have a stencil buffer, masks may not render correctly'); + /* eslint-enable no-console */ + + /* eslint-enable max-len */ + } + } +} diff --git a/packages/core/src/filters/Filter.js b/packages/core/src/filters/Filter.js new file mode 100644 index 0000000..9d8cad8 --- /dev/null +++ b/packages/core/src/filters/Filter.js @@ -0,0 +1,140 @@ +import Shader from '../shader/Shader'; +import Program from '../shader/Program'; +import State from '../state/State'; +import { settings } from '@pixi/settings'; +// import extractUniformsFromSrc from './extractUniformsFromSrc'; +import defaultVertex from './defaultFilter.vert'; +import defaultFragment from './defaultFilter.frag'; + +// let math = require('../../math'); +/** + * @class + * @memberof PIXI + * @extends PIXI.Shader + */ +export default class Filter extends Shader +{ + /** + * @param {string} [vertexSrc] - The source of the vertex shader. + * @param {string} [fragmentSrc] - The source of the fragment shader. + * @param {object} [uniforms] - Custom uniforms to use to augment the built-in ones. + */ + constructor(vertexSrc, fragmentSrc, uniforms) + { + const program = Program.from(vertexSrc, fragmentSrc); + + super(program, uniforms); + + /** + * The padding of the filter. Some filters require extra space to breath such as a blur. + * Increasing this will add extra width and height to the bounds of the object that the + * filter is applied to. + * + * @member {number} + */ + this.padding = 0; + + /** + * The resolution of the filter. Setting this to be lower will lower the quality but + * increase the performance of the filter. + * + * @member {number} + */ + this.resolution = settings.RESOLUTION; + + /** + * If enabled is true the filter is applied, if false it will not. + * + * @member {boolean} + */ + this.enabled = true; + + /** + * If enabled, PixiJS will fit the filter area into boundaries for better performance. + * Switch it off if it does not work for specific shader. + * + * @member {boolean} + */ + this.autoFit = true; + + /** + * Legacy filters use position and uvs from attributes + * @member {boolean} + * @readonly + */ + this.legacy = !!this.program.attributeData.aTextureCoord; + + /** + * the webGL state the filter requires to render + * @member {PIXI.State} + */ + this.state = new State(); + } + + /** + * Applies the filter + * + * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + * @param {boolean} clear - Should the output be cleared before rendering to it + * @param {object} [currentState] - It's current state of filter. + * There are some useful properties in the currentState : + * target, filters, sourceFrame, destinationFrame, renderTarget, resolution + */ + apply(filterManager, input, output, clear, currentState, derp) // eslint-disable-line no-unused-vars + { + // do as you please! + + filterManager.applyFilter(this, input, output, clear, currentState, derp); + + // or just do a regular render.. + } + + /** + * Sets the blendmode of the filter + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL + */ + get blendMode() + { + return this.state.blendMode; + } + + set blendMode(value) // eslint-disable-line require-jsdoc + { + this.state.blendMode = value; + } + + /** + * The default vertex shader source + * + * @static + * @constant + */ + static get defaultVertexSrc() + { + return defaultVertex; + } + + /** + * The default fragment shader source + * + * @static + * @constant + */ + static get defaultFragmentSrc() + { + return defaultFragment; + } +} + +/** + * Used for caching shader IDs + * + * @static + * @private + */ +Filter.SOURCE_KEY_MAP = {}; + diff --git a/packages/core/src/filters/FilterSystem.js b/packages/core/src/filters/FilterSystem.js new file mode 100644 index 0000000..3174318 --- /dev/null +++ b/packages/core/src/filters/FilterSystem.js @@ -0,0 +1,431 @@ +import System from '../System'; + +import RenderTexture from '../renderTexture/RenderTexture'; +import Quad from '../utils/Quad'; +import QuadUv from '../utils/QuadUv'; +import { Rectangle } from '@pixi/math'; +import * as filterTransforms from './filterTransforms'; +import bitTwiddle from 'bit-twiddle'; +import UniformGroup from '../shader/UniformGroup'; +import { DRAW_MODES } from '@pixi/constants'; + +// +/** + * @ignore + * @class + */ +class FilterState +{ + /** + * + */ + constructor() + { + this.renderTexture = null; + this.sourceFrame = new Rectangle(); + this.destinationFrame = new Rectangle(); + this.filters = []; + this.target = null; + this.legacy = false; + this.resolution = 1; + } +} + +/** + * @class + * @memberof PIXI.systems + * @extends PIXI.System + */ +export default class FilterSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + /** + * stores a bunch of PO2 textures used for filtering + * @type {Object} + */ + this.texturePool = {}; + + /** + * a pool for storing filter states, save us creating new ones each tick + * @type {Array} + */ + this.statePool = []; + + /** + * A very simple geometry used when drawing a filter effect to the screen + * @type {Quad} + */ + this.quad = new Quad(); + + this.quadUv = new QuadUv(); + + /** + * Temporary rect for maths + * @type {PIXI.Rectangle} + */ + this.tempRect = new Rectangle(); + + this.activeState = {}; + + /** + * this uniform group is attached to filter uniforms when used + * @type {UniformGroup} + */ + this.globalUniforms = new UniformGroup({ + sourceFrame: this.tempRect, + destinationFrame: this.tempRect, + + // legacy variables + filterArea: new Float32Array(4), + filterClamp: new Float32Array(4), + }, true); + } + + /** + * Adds a new filter to the System. + * + * @param {PIXI.DisplayObject} target - The target of the filter to render. + * @param {PIXI.Filter[]} filters - The filters to apply. + */ + push(target, filters) + { + const renderer = this.renderer; + const filterStack = this.renderer.renderTexture.defaultFilterStack; + const state = this.statePool.pop() || new FilterState(); + + let resolution = filters[0].resolution; + let padding = filters[0].padding; + let autoFit = filters[0].autoFit; + let legacy = filters[0].legacy; + + for (let i = 1; i < filters.length; i++) + { + const filter = filters[i]; + + // lets use the lowest resolution.. + resolution = Math.min(resolution, filter.resolution); + // and the largest amount of padding! + padding = Math.max(padding, filter.padding); + // only auto fit if all filters are autofit + autoFit = autoFit || filter.autoFit; + + legacy = legacy || filter.legacy; + } + + filterStack.push(state); + + state.resolution = resolution; + + state.legacy = legacy; + + // round to whole number based on resolution + // TODO move that to the shader too? + state.sourceFrame = target.filterArea || target.getBounds(true); + + state.sourceFrame.pad(padding); + + if (autoFit) + { + state.sourceFrame.fit(this.renderer.renderTexture.destinationFrame); + } + + state.sourceFrame.round(resolution); + + state.renderTexture = this.getPotFilterTexture(state.sourceFrame.width, state.sourceFrame.height, resolution); + state.filters = filters; + + state.destinationFrame.width = state.renderTexture.width; + state.destinationFrame.height = state.renderTexture.height; + + state.renderTexture.filterFrame = state.sourceFrame; + renderer.renderTexture.bind(state.renderTexture, state.sourceFrame);// /, state.destinationFrame); + renderer.renderTexture.clear(); + } + + /** + * Pops off the filter and applies it. + * + */ + pop() + { + const renderer = this.renderer; + const filterStack = renderer.renderTexture.defaultFilterStack; + const state = filterStack.pop(); + const filters = state.filters; + + this.activeState = state; + + const globalUniforms = this.globalUniforms.uniforms; + + globalUniforms.sourceFrame = state.sourceFrame; + globalUniforms.destinationFrame = state.destinationFrame; + globalUniforms.resolution = state.resolution; + + // only update the rect if its legacy.. + if (state.legacy) + { + const filterArea = globalUniforms.filterArea; + const filterClamp = globalUniforms.filterClamp; + + filterArea[0] = state.destinationFrame.width; + filterArea[1] = state.destinationFrame.height; + filterArea[2] = state.sourceFrame.x; + filterArea[3] = state.sourceFrame.y; + + filterClamp[0] = 0.5 / state.resolution / state.destinationFrame.width; + filterClamp[1] = 0.5 / state.resolution / state.destinationFrame.height; + filterClamp[2] = (state.sourceFrame.width - 0.5) / state.resolution / state.destinationFrame.width; + filterClamp[3] = (state.sourceFrame.height - 0.5) / state.resolution / state.destinationFrame.height; + } + + this.globalUniforms.update(); + + const lastState = filterStack[filterStack.length - 1]; + + if (filters.length === 1) + { + filters[0].apply(this, state.renderTexture, lastState.renderTexture, false, state); + + this.returnFilterTexture(state.renderTexture); + } + else + { + let flip = state.renderTexture; + let flop = this.getPotFilterTexture( + flip.width, + flip.height, + state.resolution + ); + + flop.filterFrame = flip.filterFrame; + + let i = 0; + + for (i = 0; i < filters.length - 1; ++i) + { + filters[i].apply(this, flip, flop, true, state); + + const t = flip; + + flip = flop; + flop = t; + } + + filters[i].apply(this, flip, lastState.renderTexture, false, state); + + this.returnFilterTexture(flip); + this.returnFilterTexture(flop); + } + + this.statePool.push(state); + } + + /** + * Draws a filter. + * + * @param {PIXI.Filter} filter - The filter to draw. + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + * @param {boolean} clear - Should the output be cleared before rendering to it + */ + applyFilter(filter, input, output, clear) + { + const renderer = this.renderer; + + renderer.renderTexture.bind(output, output ? output.filterFrame : null); + + if (clear) + { + // gl.disable(gl.SCISSOR_TEST); + renderer.renderTexture.clear(); + // gl.enable(gl.SCISSOR_TEST); + } + + // set the uniforms.. + filter.uniforms.uSampler = input; + filter.uniforms.filterGlobals = this.globalUniforms; + + // TODO make it so that the order of this does not matter.. + // because it does at the moment cos of global uniforms. + // they need to get resynced + + renderer.state.setState(filter.state); + renderer.shader.bind(filter); + + if (filter.legacy) + { + this.quadUv.map(input._frame, input.filterFrame); + + renderer.geometry.bind(this.quadUv); + renderer.geometry.draw(DRAW_MODES.TRIANGLES); + } + else + { + renderer.geometry.bind(this.quad); + renderer.geometry.draw(DRAW_MODES.TRIANGLE_STRIP); + } + } + + /** + * Calculates the mapped matrix. + * + * TODO playing around here.. this is temporary - (will end up in the shader) + * this returns a matrix that will normalise map filter cords in the filter to screen space + * + * @param {PIXI.Matrix} outputMatrix - the matrix to output to. + * @return {PIXI.Matrix} The mapped matrix. + */ + calculateScreenSpaceMatrix(outputMatrix) + { + const currentState = this.activeState; + + return filterTransforms.calculateScreenSpaceMatrix( + outputMatrix, + currentState.sourceFrame, + currentState.destinationFrame + ); + } + + /** + * This will map the filter coord so that a texture can be used based on the transform of a sprite + * + * @param {PIXI.Matrix} outputMatrix - The matrix to output to. + * @param {PIXI.Sprite} sprite - The sprite to map to. + * @return {PIXI.Matrix} The mapped matrix. + */ + calculateSpriteMatrix(outputMatrix, sprite) + { + const currentState = this.activeState; + + return filterTransforms.calculateSpriteMatrix( + outputMatrix, + currentState.sourceFrame, + currentState.destinationFrame, + sprite + ); + } + + /** + * Destroys this Filter System. + * + * @param {boolean} [contextLost=false] context was lost, do not free shaders + * + */ + destroy(contextLost = false) + { + if (!contextLost) + { + this.emptyPool(); + } + else + { + this.texturePool = {}; + } + } + + /** + * Gets a Power-of-Two render texture. + * + * TODO move to a seperate class could be on renderer? + * also - could cause issue with multiple contexts? + * + * @private + * @param {WebGLRenderingContext} gl - The webgl rendering context + * @param {number} minWidth - The minimum width of the render target. + * @param {number} minHeight - The minimum height of the render target. + * @param {number} resolution - The resolution of the render target. + * @return {PIXI.RenderTarget} The new render target. + */ + getPotFilterTexture(minWidth, minHeight, resolution) + { + minWidth = bitTwiddle.nextPow2(minWidth); + minHeight = bitTwiddle.nextPow2(minHeight); + resolution = resolution || 1; + + const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + + if (!this.texturePool[key]) + { + this.texturePool[key] = []; + } + + let renderTexture = this.texturePool[key].pop(); + + if (!renderTexture) + { + // temporary bypass cache.. + // internally - this will cause a texture to be bound.. + renderTexture = RenderTexture.create({ + width: minWidth, + height: minHeight, + resolution, + }); + } + + return renderTexture; + } + + /** + * Gets extra render texture to use inside current filter + * + * @param {number} resolution resolution of the renderTexture + * @returns {PIXI.RenderTexture} + */ + getFilterTexture(resolution) + { + const rt = this.activeState.renderTexture; + + const filterTexture = this.getPotFilterTexture(rt.width, rt.height, resolution || rt.baseTexture.resolution); + + filterTexture.filterFrame = rt.filterFrame; + + return filterTexture; + } + + /** + * Frees a render texture back into the pool. + * + * @param {PIXI.RenderTarget} renderTexture - The renderTarget to free + */ + returnFilterTexture(renderTexture) + { + renderTexture.filterFrame = null; + + const base = renderTexture.baseTexture; + + const minWidth = base.width; + const minHeight = base.height; + + const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + + this.texturePool[key].push(renderTexture); + } + + /** + * Empties the texture pool. + * + */ + emptyPool() + { + for (const i in this.texturePool) + { + const textures = this.texturePool[i]; + + if (textures) + { + for (let j = 0; j < textures.length; j++) + { + textures[j].destroy(true); + } + } + } + + this.texturePool = {}; + } +} diff --git a/packages/core/src/filters/defaultFilter.frag b/packages/core/src/filters/defaultFilter.frag new file mode 100644 index 0000000..a14c1be --- /dev/null +++ b/packages/core/src/filters/defaultFilter.frag @@ -0,0 +1,22 @@ +varying vec2 vTextureCoord; +varying vec2 vFilterCoord; + +uniform sampler2D uSampler; +uniform sampler2D filterSampler; + +void main(void){ + vec4 masky = texture2D(filterSampler, vFilterCoord); + vec4 sample = texture2D(uSampler, vTextureCoord); + vec4 color; + if(mod(vFilterCoord.x, 1.0) > 0.5) + { + color = vec4(1.0, 0.0, 0.0, 1.0); + } + else + { + color = vec4(0.0, 1.0, 0.0, 1.0); + } + // gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0); + gl_FragColor = mix(sample, masky, 0.5); + gl_FragColor *= sample.a; +} diff --git a/packages/core/src/filters/defaultFilter.vert b/packages/core/src/filters/defaultFilter.vert new file mode 100644 index 0000000..e1febca --- /dev/null +++ b/packages/core/src/filters/defaultFilter.vert @@ -0,0 +1,14 @@ +attribute vec2 aVertexPosition; +attribute vec2 aTextureCoord; + +uniform mat3 projectionMatrix; +uniform mat3 filterMatrix; + +varying vec2 vTextureCoord; +varying vec2 vFilterCoord; + +void main(void){ + gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); + vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy; + vTextureCoord = aTextureCoord ; +} diff --git a/packages/core/src/filters/filterTransforms.js b/packages/core/src/filters/filterTransforms.js new file mode 100644 index 0000000..87a3cd6 --- /dev/null +++ b/packages/core/src/filters/filterTransforms.js @@ -0,0 +1,50 @@ +import { Matrix } from '@pixi/math'; + +/** + * Calculates the mapped matrix + * @param filterArea {Rectangle} The filter area + * @param sprite {Sprite} the target sprite + * @param outputMatrix {Matrix} @alvin + * @private + */ +// this returns a matrix that will normalise map filter cords in the filter to screen space +export function calculateScreenSpaceMatrix(outputMatrix, filterArea, textureSize) +{ + // TODO unwrap? + const mappedMatrix = outputMatrix.identity(); + + mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); + + mappedMatrix.scale(textureSize.width, textureSize.height); + + return mappedMatrix; +} + +export function calculateNormalizedScreenSpaceMatrix(outputMatrix, filterArea, textureSize) +{ + const mappedMatrix = outputMatrix.identity(); + + mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); + + const translateScaleX = (textureSize.width / filterArea.width); + const translateScaleY = (textureSize.height / filterArea.height); + + mappedMatrix.scale(translateScaleX, translateScaleY); + + return mappedMatrix; +} + +// this will map the filter coord so that a texture can be used based on the transform of a sprite +export function calculateSpriteMatrix(outputMatrix, filterArea, textureSize, sprite) +{ + const orig = sprite._texture.orig; + const mappedMatrix = outputMatrix.set(textureSize.width, 0, 0, textureSize.height, filterArea.x, filterArea.y); + const worldTransform = sprite.worldTransform.copyTo(Matrix.TEMP_MATRIX); + + worldTransform.invert(); + mappedMatrix.prepend(worldTransform); + mappedMatrix.scale(1.0 / orig.width, 1.0 / orig.height); + mappedMatrix.translate(sprite.anchor.x, sprite.anchor.y); + + return mappedMatrix; +} diff --git a/packages/core/src/filters/spriteMask/SpriteMaskFilter.js b/packages/core/src/filters/spriteMask/SpriteMaskFilter.js new file mode 100644 index 0000000..91697b7 --- /dev/null +++ b/packages/core/src/filters/spriteMask/SpriteMaskFilter.js @@ -0,0 +1,63 @@ +import Filter from '../Filter'; +import { Matrix } from '@pixi/math'; +import vertex from './spriteMaskFilter.vert'; +import fragment from './spriteMaskFilter.frag'; +import { default as TextureMatrix } from '../../textures/TextureMatrix'; + +/** + * The SpriteMaskFilter class + * + * @class + * @extends PIXI.Filter + * @memberof PIXI + */ +export default class SpriteMaskFilter extends Filter +{ + /** + * @param {PIXI.Sprite} sprite - the target sprite + */ + constructor(sprite) + { + const maskMatrix = new Matrix(); + + super(vertex, fragment); + + sprite.renderable = false; + + this.maskSprite = sprite; + this.maskMatrix = maskMatrix; + } + + /** + * Applies the filter + * + * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + */ + apply(filterManager, input, output) + { + const maskSprite = this.maskSprite; + const tex = this.maskSprite.texture; + + if (!tex.valid) + { + return; + } + if (!tex.transform) + { + // margin = 0.0, let it bleed a bit, shader code becomes easier + // assuming that atlas textures were made with 1-pixel padding + tex.transform = new TextureMatrix(tex, 0.0); + } + tex.transform.update(); + + this.uniforms.mask = tex; + this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite) + .prepend(tex.transform.mapCoord); + this.uniforms.alpha = maskSprite.worldAlpha; + this.uniforms.maskClamp = tex.transform.uClampFrame; + + filterManager.applyFilter(this, input, output); + } +} diff --git a/packages/core/src/filters/spriteMask/spriteMaskFilter.frag b/packages/core/src/filters/spriteMask/spriteMaskFilter.frag new file mode 100644 index 0000000..0e4aef8 --- /dev/null +++ b/packages/core/src/filters/spriteMask/spriteMaskFilter.frag @@ -0,0 +1,23 @@ +varying vec2 vMaskCoord; +varying vec2 vTextureCoord; + +uniform sampler2D uSampler; +uniform sampler2D mask; +uniform float alpha; +uniform vec4 maskClamp; + +void main(void) +{ + float clip = step(3.5, + step(maskClamp.x, vMaskCoord.x) + + step(maskClamp.y, vMaskCoord.y) + + step(vMaskCoord.x, maskClamp.z) + + step(vMaskCoord.y, maskClamp.w)); + + vec4 original = texture2D(uSampler, vTextureCoord); + vec4 masky = texture2D(mask, vMaskCoord); + + original *= (masky.r * masky.a * alpha * clip); + + gl_FragColor = original; +} diff --git a/packages/core/src/filters/spriteMask/spriteMaskFilter.vert b/packages/core/src/filters/spriteMask/spriteMaskFilter.vert new file mode 100644 index 0000000..d23a30c --- /dev/null +++ b/packages/core/src/filters/spriteMask/spriteMaskFilter.vert @@ -0,0 +1,16 @@ +attribute vec2 aVertexPosition; +attribute vec2 aTextureCoord; + +uniform mat3 projectionMatrix; +uniform mat3 otherMatrix; + +varying vec2 vMaskCoord; +varying vec2 vTextureCoord; + +void main(void) +{ + gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); + + vTextureCoord = aTextureCoord; + vMaskCoord = ( otherMatrix * vec3( aTextureCoord, 1.0) ).xy; +} diff --git a/packages/core/src/framebuffer/FrameBuffer.js b/packages/core/src/framebuffer/FrameBuffer.js new file mode 100644 index 0000000..84e9fcc --- /dev/null +++ b/packages/core/src/framebuffer/FrameBuffer.js @@ -0,0 +1,106 @@ +import Texture from '../textures/BaseTexture'; +import { FORMATS, TYPES } from '@pixi/constants'; + +/** + * Frame buffer + * @class + * @memberof PIXI + */ +export default class FrameBuffer +{ + constructor(width, height) + { + this.width = width || 100; + this.height = height || 100; + + this.stencil = false; + this.depth = false; + + this.dirtyId = 0; + this.dirtyFormat = 0; + this.dirtySize = 0; + + this.depthTexture = null; + this.colorTextures = []; + + this.glFrameBuffers = {}; + } + + get colorTexture() + { + return this.colorTextures[0]; + } + + addColorTexture(index, texture) + { + // TODO add some validation to the texture - same width / height etc? + this.colorTextures[index || 0] = texture || new Texture(null, { scaleMode: 0, + resolution: 1, + mipmap: false, + width: this.width, + height: this.height });// || new Texture(); + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + addDepthTexture(texture) + { + /* eslint-disable max-len */ + this.depthTexture = texture || new Texture(null, { scaleMode: 0, + resolution: 1, + width: this.width, + height: this.height, + mipmap: false, + format: FORMATS.DEPTH_COMPONENT, + type: TYPES.UNSIGNED_SHORT });// UNSIGNED_SHORT; + /* eslint-disable max-len */ + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + enableDepth() + { + this.depth = true; + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + enableStencil() + { + this.stencil = true; + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + resize(width, height) + { + if (width === this.width && height === this.height) return; + + this.width = width; + this.height = height; + + this.dirtyId++; + this.dirtySize++; + + for (let i = 0; i < this.colorTextures.length; i++) + { + this.colorTextures[i].setSize(width, height); + } + + if (this.depthTexture) + { + this.depthTexture.setSize(width, height); + } + } +} diff --git a/packages/core/src/framebuffer/FramebufferSystem.js b/packages/core/src/framebuffer/FramebufferSystem.js new file mode 100644 index 0000000..2cc2437 --- /dev/null +++ b/packages/core/src/framebuffer/FramebufferSystem.js @@ -0,0 +1,246 @@ +import System from '../System'; +import { Rectangle } from '@pixi/math'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class FramebufferSystem extends System +{ + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + contextChange() + { + this.gl = this.renderer.gl; + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + this.current = null; + this.viewport = new Rectangle(); + + this.drawBufferExtension = this.renderer.context.extensions.drawBuffers; + } + + bind(framebuffer, frame) + { + const gl = this.gl; + + this.current = framebuffer; + + if (framebuffer) + { + // TODO cacheing layer! + + const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID] || this.initFramebuffer(framebuffer); + + gl.bindFramebuffer(gl.FRAMEBUFFER, fbo.framebuffer); + // makesure all textures are unbound.. + + // now check for updates... + if (fbo.dirtyId !== framebuffer.dirtyId) + { + fbo.dirtyId = framebuffer.dirtyId; + + if (fbo.dirtyFormat !== framebuffer.dirtyFormat) + { + fbo.dirtyFormat = framebuffer.dirtyFormat; + this.updateFramebuffer(framebuffer); + } + else if (fbo.dirtySize !== framebuffer.dirtySize) + { + fbo.dirtySize = framebuffer.dirtySize; + this.resizeFramebuffer(framebuffer); + } + } + + for (let i = 0; i < framebuffer.colorTextures.length; i++) + { + if (framebuffer.colorTextures[i].texturePart) + { + this.renderer.texture.unbind(framebuffer.colorTextures[i].texture); + } + else + { + this.renderer.texture.unbind(framebuffer.colorTextures[i]); + } + } + + if (framebuffer.depthTexture) + { + this.renderer.texture.unbind(framebuffer.depthTexture); + } + + if (frame) + { + this.setViewport(frame.x, frame.y, frame.width, frame.height); + } + else + { + this.setViewport(0, 0, framebuffer.width, framebuffer.height); + } + } + else + { + gl.bindFramebuffer(gl.FRAMEBUFFER, null); + + if (frame) + { + this.setViewport(frame.x, frame.y, frame.width, frame.height); + } + else + { + this.setViewport(0, 0, this.renderer.width, this.renderer.height); + } + } + } + + setViewport(x, y, width, height) + { + const v = this.viewport; + + if (v.width !== width || v.height !== height || v.x !== x || v.y !== y) + { + v.x = x; + v.y = y; + v.width = width; + v.height = height; + + this.gl.viewport(x, y, width, height); + } + } + + get size() + { + if (this.current) + { + // TODO store temp + return { x: 0, y: 0, width: this.current.width, height: this.current.height }; + } + + return { x: 0, y: 0, width: this.renderer.width, height: this.renderer.height }; + } + + clear(r, g, b, a) + { + const gl = this.gl; + + // TODO clear color can be set only one right? + gl.clearColor(r, g, b, a); + gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); + } + + // private functions... + + initFramebuffer(framebuffer) + { + const gl = this.gl; + + // TODO - make this a class? + const fbo = { + framebuffer: gl.createFramebuffer(), + stencil: null, + dirtyId: 0, + dirtyFormat: 0, + dirtySize: 0, + }; + + framebuffer.glFrameBuffers[this.CONTEXT_UID] = fbo; + + return fbo; + } + + resizeFramebuffer(framebuffer) + { + const gl = this.gl; + + if (framebuffer.stencil || framebuffer.depth) + { + gl.bindRenderbuffer(gl.RENDERBUFFER, this.stencil); + gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); + } + } + + updateFramebuffer(framebuffer) + { + const gl = this.gl; + + const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID]; + + // bind the color texture + const colorTextures = framebuffer.colorTextures; + + let count = colorTextures.length; + + if (!this.drawBufferExtension) + { + count = Math.min(count, 1); + } + + const activeTextures = []; + + for (let i = 0; i < count; i++) + { + const texture = framebuffer.colorTextures[i]; + + if (texture.texturePart) + { + this.renderer.texture.bind(texture.texture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.COLOR_ATTACHMENT0 + i, + gl.TEXTURE_CUBE_MAP_NEGATIVE_X + texture.side, + texture.texture._glTextures[this.CONTEXT_UID].texture, + 0); + } + else + { + this.renderer.texture.bind(texture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.COLOR_ATTACHMENT0 + i, + gl.TEXTURE_2D, + texture._glTextures[this.CONTEXT_UID].texture, + 0); + } + + activeTextures.push(gl.COLOR_ATTACHMENT0 + i); + } + + if (this.drawBufferExtension && activeTextures.length > 1) + { + this.drawBufferExtension.drawBuffersWEBGL(activeTextures); + } + + if (framebuffer.depthTexture) + { + const depthTextureExt = this.renderer.context.extensions.depthTexture; + + if (depthTextureExt) + { + const depthTexture = framebuffer.depthTexture; + + this.renderer.texture.bind(depthTexture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.DEPTH_ATTACHMENT, + gl.TEXTURE_2D, + depthTexture._glTextures[this.CONTEXT_UID].texture, + 0); + } + } + + if (framebuffer.stencil || framebuffer.depth) + { + fbo.stencil = gl.createRenderbuffer(); + + gl.bindRenderbuffer(gl.RENDERBUFFER, fbo.stencil); + + // TODO.. this is depth AND stencil? + gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, fbo.stencil); + gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); + // fbo.enableStencil(); + } + } +} diff --git a/packages/core/src/geometry/GLBuffer.js b/packages/core/src/geometry/GLBuffer.js new file mode 100644 index 0000000..0477e8a --- /dev/null +++ b/packages/core/src/geometry/GLBuffer.js @@ -0,0 +1,9 @@ +export default class GLBuffer +{ + constructor(buffer) + { + this.buffer = buffer; + this.updateID = -1; + this.byteLength = -1; + } +} diff --git a/packages/core/src/geometry/GeometrySystem.js b/packages/core/src/geometry/GeometrySystem.js new file mode 100644 index 0000000..959f827 --- /dev/null +++ b/packages/core/src/geometry/GeometrySystem.js @@ -0,0 +1,388 @@ +import System from '../System'; +import GLBuffer from './GLBuffer'; + +const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class GeometrySystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this._activeGeometry = null; + this._activeVao = null; + + this.hasVao = true; + this.hasInstance = true; + } + + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + contextChange() + { + const gl = this.gl = this.renderer.gl; + + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + + // webgl2 + if (!gl.createVertexArray) + { + // webgl 1! + let nativeVaoExtension = this.renderer.context.extensions.vertexArrayObject; + + if (this.renderer.legacy) + { + nativeVaoExtension = null; + } + + if (nativeVaoExtension) + { + gl.createVertexArray = () => + nativeVaoExtension.createVertexArrayOES(); + + gl.bindVertexArray = (vao) => + nativeVaoExtension.bindVertexArrayOES(vao); + + gl.deleteVertexArray = (vao) => + nativeVaoExtension.deleteVertexArrayOES(vao); + } + else + { + this.hasVao = false; + gl.createVertexArray = () => + { + // empty + }; + + gl.bindVertexArray = () => + { + // empty + }; + + gl.deleteVertexArray = () => + { + // empty + }; + } + } + + if (!gl.vertexAttribDivisor) + { + const instanceExt = gl.getExtension('ANGLE_instanced_arrays'); + + if (instanceExt) + { + gl.vertexAttribDivisor = (a, b) => + instanceExt.vertexAttribDivisorANGLE(a, b); + + gl.drawElementsInstanced = (a, b, c, d, e) => + instanceExt.drawElementsInstancedANGLE(a, b, c, d, e); + + gl.drawArraysInstanced = (a, b, c, d) => + instanceExt.drawArraysInstancedANGLE(a, b, c, d); + } + else + { + this.hasInstance = false; + } + } + } + + /** + * Binds geometry so that is can be drawn. Creating a Vao if required + * @private + * @param {PIXI.Geometry} geometry instance of geometry to bind + */ + bind(geometry, shader) + { + shader = shader || this.renderer.shader.shader; + + const gl = this.gl; + + // not sure the best way to address this.. + // currently different shaders require different VAOs for the same geometry + // Still mulling over the best way to solve this one.. + // will likely need to modify the shader attribute locations at run time! + let vaos = geometry.glVertexArrayObjects[this.CONTEXT_UID]; + + if (!vaos) + { + geometry.glVertexArrayObjects[this.CONTEXT_UID] = vaos = {}; + } + + const vao = vaos[shader.program.id] || this.initGeometryVao(geometry, shader.program); + + this._activeGeometry = geometry; + + if (this._activeVao !== vao) + { + this._activeVao = vao; + + if (this.hasVao) + { + gl.bindVertexArray(vao); + } + else + { + this.activateVao(geometry, shader.program); + } + } + + // TODO - optimise later! + // don't need to loop through if nothing changed! + // maybe look to add an 'autoupdate' to geometry? + this.updateBuffers(); + } + + reset() + { + this.unbind(); + } + + updateBuffers() + { + const geometry = this._activeGeometry; + const gl = this.gl; + + for (let i = 0; i < geometry.buffers.length; i++) + { + const buffer = geometry.buffers[i]; + + const glBuffer = buffer._glBuffers[this.CONTEXT_UID]; + + if (buffer._updateID !== glBuffer.updateID) + { + glBuffer.updateID = buffer._updateID; + + // TODO can cache this on buffer! maybe added a getter / setter? + const type = buffer.index ? gl.ELEMENT_ARRAY_BUFFER : gl.ARRAY_BUFFER; + const drawType = buffer.static ? gl.STATIC_DRAW : gl.DYNAMIC_DRAW; + + gl.bindBuffer(type, glBuffer.buffer); + + if (glBuffer.byteLength >= buffer.data.byteLength) + { + // offset is always zero for now! + gl.bufferSubData(type, 0, buffer.data); + } + else + { + gl.bufferData(type, buffer.data, drawType); + } + } + } + } + + checkCompatability(geometry, program) + { + // geometry must have at least all the attributes that the shader requires. + const geometryAttributes = geometry.attributes; + const shaderAttributes = program.attributeData; + + for (const j in shaderAttributes) + { + if (!geometryAttributes[j]) + { + throw new Error(`shader and geometry incompatible, geometry missing the "${j}" attribute`); + } + } + } + + /** + * Creates a Vao with the same structure as the geometry and stores it on the geometry. + * @private + * @param {PIXI.Geometry} geometry instance of geometry to to generate Vao for + */ + initGeometryVao(geometry, program) + { + this.checkCompatability(geometry, program); + + const gl = this.gl; + const CONTEXT_UID = this.CONTEXT_UID; + const buffers = geometry.buffers; + const attributes = geometry.attributes; + + const tempStride = {}; + const tempStart = {}; + + for (const j in buffers) + { + tempStride[j] = 0; + tempStart[j] = 0; + } + + for (const j in attributes) + { + if (!attributes[j].size && program.attributeData[j]) + { + attributes[j].size = program.attributeData[j].size; + } + + tempStride[attributes[j].buffer] += attributes[j].size * byteSizeMap[attributes[j].type]; + } + + for (const j in attributes) + { + const attribute = attributes[j]; + const attribSize = attribute.size; + + if (attribute.stride === undefined) + { + if (tempStride[attribute.buffer] === attribSize * byteSizeMap[attribute.type]) + { + attribute.stride = 0; + } + else + { + attribute.stride = tempStride[attribute.buffer]; + } + } + + if (attribute.start === undefined) + { + attribute.start = tempStart[attribute.buffer]; + + tempStart[attribute.buffer] += attribSize * byteSizeMap[attribute.type]; + } + } + + // first update - and create the buffers! + // only create a gl buffer if it actually gets + for (let i = 0; i < buffers.length; i++) + { + const buffer = buffers[i]; + + if (!buffer._glBuffers[CONTEXT_UID]) + { + buffer._glBuffers[CONTEXT_UID] = new GLBuffer(gl.createBuffer()); + } + } + + // TODO - maybe make this a data object? + // lets wait to see if we need to first! + const vao = gl.createVertexArray(); + + gl.bindVertexArray(vao); + + this.activateVao(geometry, program); + + gl.bindVertexArray(null); + + // add it to the cache! + geometry.glVertexArrayObjects[this.CONTEXT_UID][program.id] = vao; + + return vao; + } + + activateVao(geometry, program) + { + const gl = this.gl; + const CONTEXT_UID = this.CONTEXT_UID; + const buffers = geometry.buffers; + const attributes = geometry.attributes; + + if (geometry.indexBuffer) + { + // first update the index buffer if we have one.. + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, geometry.indexBuffer._glBuffers[CONTEXT_UID].buffer); + } + + let lastBuffer = null; + + // add a new one! + for (const j in attributes) + { + const attribute = attributes[j]; + const buffer = buffers[attribute.buffer]; + const glBuffer = buffer._glBuffers[CONTEXT_UID]; + + if (program.attributeData[j]) + { + if (lastBuffer !== glBuffer) + { + gl.bindBuffer(gl.ARRAY_BUFFER, glBuffer.buffer); + + lastBuffer = glBuffer; + } + + const location = program.attributeData[j].location; + + // TODO introduce state again + // we can optimise this for older devices that have no VAOs + gl.enableVertexAttribArray(location); + + gl.vertexAttribPointer(location, + attribute.size, + attribute.type || gl.FLOAT, + attribute.normalized, + attribute.stride, + attribute.start); + + if (attribute.instance) + { + // TODO calculate instance count based of this... + if (this.hasInstance) + { + gl.vertexAttribDivisor(location, 1); + } + else + { + throw new Error('geometry error, GPU Instancing is not supported on this device'); + } + } + } + } + } + + draw(type, size, start, instanceCount) + { + const gl = this.gl; + const geometry = this._activeGeometry; + + // TODO.. this should not change so maybe cache the function? + + if (geometry.indexBuffer) + { + if (geometry.instanced) + { + /* eslint-disable max-len */ + gl.drawElementsInstanced(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2, instanceCount || 1); + /* eslint-enable max-len */ + } + else + { + gl.drawElements(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2); + } + } + else + if (geometry.instanced) + { + // TODO need a better way to calculate size.. + gl.drawArraysInstanced(type, start, size || geometry.getSize(), instanceCount || 1); + } + else + { + gl.drawArrays(type, start, size || geometry.getSize()); + } + + return this; + } + + unbind() + { + this.gl.bindVertexArray(null); + this._activeVao = null; + this._activeGeometry = null; + } +} diff --git a/packages/core/src/geometry/utils/setVertexAttribArrays.js b/packages/core/src/geometry/utils/setVertexAttribArrays.js new file mode 100644 index 0000000..23b6959 --- /dev/null +++ b/packages/core/src/geometry/utils/setVertexAttribArrays.js @@ -0,0 +1,54 @@ +// var GL_MAP = {}; + +/** + * @param gl {WebGLRenderingContext} The current WebGL context + * @param attribs {*} + * @param state {*} + */ +export default function setVertexAttribArrays(gl, attribs, state) +{ + let i; + + if (state) + { + const tempAttribState = state.tempAttribState; + const attribState = state.attribState; + + for (i = 0; i < tempAttribState.length; i++) + { + tempAttribState[i] = false; + } + + // set the new attribs + for (i = 0; i < attribs.length; i++) + { + tempAttribState[attribs[i].attribute.location] = true; + } + + for (i = 0; i < attribState.length; i++) + { + if (attribState[i] !== tempAttribState[i]) + { + attribState[i] = tempAttribState[i]; + + if (state.attribState[i]) + { + gl.enableVertexAttribArray(i); + } + else + { + gl.disableVertexAttribArray(i); + } + } + } + } + else + { + for (i = 0; i < attribs.length; i++) + { + const attrib = attribs[i]; + + gl.enableVertexAttribArray(attrib.attribute.location); + } + } +} diff --git a/packages/core/src/index.js b/packages/core/src/index.js index 58c456a..fd1f547 100644 --- a/packages/core/src/index.js +++ b/packages/core/src/index.js @@ -1,30 +1,30 @@ import * as resources from './textures/resources'; -import * as systems from './renderers/systems'; +import * as systems from './systems'; -export { resources }; export { systems }; +export { resources }; -export { default as Renderer } from './renderers/Renderer'; -export { default as AbstractRenderer } from './renderers/AbstractRenderer'; -export { default as FrameBuffer } from './textures/FrameBuffer'; +export { default as System } from './System'; +export { default as Renderer } from './Renderer'; +export { default as AbstractRenderer } from './AbstractRenderer'; +export { default as FrameBuffer } from './framebuffer/FrameBuffer'; export { default as CubeTexture } from './textures/CubeTexture'; export { default as BaseTexture } from './textures/BaseTexture'; export { default as Texture } from './textures/Texture'; export { default as TextureMatrix } from './textures/TextureMatrix'; -export { default as RenderTexture } from './textures/RenderTexture'; -export { default as BaseRenderTexture } from './textures/BaseRenderTexture'; +export { default as RenderTexture } from './renderTexture/RenderTexture'; +export { default as BaseRenderTexture } from './renderTexture/BaseRenderTexture'; export { default as TextureUvs } from './textures/TextureUvs'; -export { default as State } from './renderers/State'; -export { default as ObjectRenderer } from './renderers/utils/ObjectRenderer'; -export { default as Quad } from './renderers/utils/Quad'; -export { default as QuadUv } from './renderers/utils/QuadUv'; -export { default as checkMaxIfStatmentsInShader } from './renderers/utils/checkMaxIfStatmentsInShader'; +export { default as State } from './state/State'; +export { default as ObjectRenderer } from './batch/ObjectRenderer'; +export { default as Quad } from './utils/Quad'; +export { default as QuadUv } from './utils/QuadUv'; +export { default as checkMaxIfStatmentsInShader } from './shader/utils/checkMaxIfStatmentsInShader'; export { default as Shader } from './shader/Shader'; export { default as Program } from './shader/Program'; export { default as UniformGroup } from './shader/UniformGroup'; -export { default as SpriteMaskFilter } from './renderers/filters/spriteMask/SpriteMaskFilter'; -export { default as Filter } from './renderers/filters/Filter'; +export { default as SpriteMaskFilter } from './filters/spriteMask/SpriteMaskFilter'; +export { default as Filter } from './filters/Filter'; export { default as Attribute } from './geometry/Attribute'; export { default as Buffer } from './geometry/Buffer'; export { default as Geometry } from './geometry/Geometry'; - diff --git a/packages/core/src/mask/MaskSystem.js b/packages/core/src/mask/MaskSystem.js new file mode 100644 index 0000000..99174c2 --- /dev/null +++ b/packages/core/src/mask/MaskSystem.js @@ -0,0 +1,199 @@ +import System from '../System'; +import AlphaMaskFilter from '../filters/spriteMask/SpriteMaskFilter'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class MaskSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + // TODO - we don't need both! + this.scissor = false; + this.scissorData = null; + this.scissorRenderTarget = null; + + this.enableScissor = false; + + this.alphaMaskPool = []; + this.alphaMaskIndex = 0; + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.DisplayObject} target - Display Object to push the mask to + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + push(target, maskData) + { + // TODO the root check means scissor rect will not + // be used on render textures more info here: + // https://github.com/pixijs/pixi.js/pull/3545 + + if (maskData.texture) + { + this.pushSpriteMask(target, maskData); + } + else if (this.enableScissor + && !this.scissor + && this.renderer._activeRenderTarget.root + && !this.renderer.stencil.stencilMaskStack.length + && maskData.isFastRect()) + { + const matrix = maskData.worldTransform; + + let rot = Math.atan2(matrix.b, matrix.a); + + // use the nearest degree! + rot = Math.round(rot * (180 / Math.PI)); + + if (rot % 90) + { + this.pushStencilMask(maskData); + } + else + { + this.pushScissorMask(target, maskData); + } + } + else + { + this.pushStencilMask(maskData); + } + } + + /** + * Removes the last mask from the mask stack and doesn't return it. + * + * @param {PIXI.DisplayObject} target - Display Object to pop the mask from + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + pop(target, maskData) + { + if (maskData.texture) + { + this.popSpriteMask(target, maskData); + } + else if (this.enableScissor && !this.renderer.stencil.stencilMaskStack.length) + { + this.popScissorMask(target, maskData); + } + else + { + this.popStencilMask(target, maskData); + } + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.RenderTarget} target - Display Object to push the sprite mask to + * @param {PIXI.Sprite} maskData - Sprite to be used as the mask + */ + pushSpriteMask(target, maskData) + { + let alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; + + if (!alphaMaskFilter) + { + alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; + } + + alphaMaskFilter[0].resolution = this.renderer.resolution; + alphaMaskFilter[0].maskSprite = maskData; + + // TODO - may cause issues! + target.filterArea = maskData.getBounds(true); + + this.renderer.filterSystem.pushFilter(target, alphaMaskFilter); + + this.alphaMaskIndex++; + } + + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popSpriteMask() + { + this.renderer.filterSystem.popFilter(); + this.alphaMaskIndex--; + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + pushStencilMask(maskData) + { + this.renderer.batch.flush(); + this.renderer.stencil.pushStencil(maskData); + } + + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popStencilMask() + { + // this.renderer.currentRenderer.stop(); + this.renderer.stencil.popStencil(); + } + + /** + * + * @param {PIXI.DisplayObject} target - Display Object to push the mask to + * @param {PIXI.Graphics} maskData - The masking data. + */ + pushScissorMask(target, maskData) + { + maskData.renderable = true; + + const renderTarget = this.renderer._activeRenderTarget; + + const bounds = maskData.getBounds(); + + bounds.fit(renderTarget.size); + maskData.renderable = false; + + this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); + + const resolution = this.renderer.resolution; + + this.renderer.gl.scissor( + bounds.x * resolution, + (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, + bounds.width * resolution, + bounds.height * resolution + ); + + this.scissorRenderTarget = renderTarget; + this.scissorData = maskData; + this.scissor = true; + } + + /** + * + * + */ + popScissorMask() + { + this.scissorRenderTarget = null; + this.scissorData = null; + this.scissor = false; + + // must be scissor! + const gl = this.renderer.gl; + + gl.disable(gl.SCISSOR_TEST); + } +} diff --git a/packages/core/src/mask/StencilSystem.js b/packages/core/src/mask/StencilSystem.js new file mode 100644 index 0000000..62796a5 --- /dev/null +++ b/packages/core/src/mask/StencilSystem.js @@ -0,0 +1,133 @@ +import System from '../System'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class StencilSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + this.stencilMaskStack = []; + } + + /** + * Changes the mask stack that is used by this System. + * + * @param {PIXI.Graphics[]} stencilMaskStack - The mask stack + */ + setMaskStack(stencilMaskStack) + { + const gl = this.renderer.gl; + + if (stencilMaskStack.length !== this.stencilMaskStack.length) + { + if (stencilMaskStack.length === 0) + { + gl.disable(gl.STENCIL_TEST); + } + else + { + gl.enable(gl.STENCIL_TEST); + } + } + + this.stencilMaskStack = stencilMaskStack; + } + + /** + * Applies the Mask and adds it to the current stencil stack. @alvin + * + * @param {PIXI.Graphics} graphics - The mask + */ + pushStencil(graphics) + { + this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); + + // this.renderer._activeRenderTarget.attachStencilBuffer(); + + const gl = this.renderer.gl; + const prevMaskCount = this.stencilMaskStack.length; + + if (prevMaskCount === 0) + { + gl.enable(gl.STENCIL_TEST); + } + + this.stencilMaskStack.push(graphics); + + // Increment the refference stencil value where the new mask overlaps with the old ones. + gl.colorMask(false, false, false, false); + gl.stencilFunc(gl.EQUAL, prevMaskCount, this._getBitwiseMask()); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.INCR); + this.renderer.plugins.graphics.render(graphics); + + this._useCurrent(); + } + + /** + * Removes the last mask from the stencil stack. @alvin + */ + popStencil() + { + this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); + + const gl = this.renderer.gl; + const graphics = this.stencilMaskStack.pop(); + + if (this.stencilMaskStack.length === 0) + { + // the stack is empty! + gl.disable(gl.STENCIL_TEST); + gl.clear(gl.STENCIL_BUFFER_BIT); + gl.clearStencil(0); + } + else + { + // Decrement the refference stencil value where the popped mask overlaps with the other ones + gl.colorMask(false, false, false, false); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.DECR); + this.renderer.plugins.graphics.render(graphics); + + this._useCurrent(); + } + } + + /** + * Setup renderer to use the current stencil data. + */ + _useCurrent() + { + const gl = this.renderer.gl; + + gl.colorMask(true, true, true, true); + gl.stencilFunc(gl.EQUAL, this.stencilMaskStack.length, this._getBitwiseMask()); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP); + } + + /** + * Fill 1s equal to the number of acitve stencil masks. + * + * @return {number} The bitwise mask. + */ + _getBitwiseMask() + { + return (1 << this.stencilMaskStack.length) - 1; + } + + /** + * Destroys the mask stack. + * + */ + destroy() + { + super.destroy(this); + + this.stencilMaskStack = null; + } +} diff --git a/packages/core/src/projection/ProjectionSystem.js b/packages/core/src/projection/ProjectionSystem.js new file mode 100644 index 0000000..6e63b73 --- /dev/null +++ b/packages/core/src/projection/ProjectionSystem.js @@ -0,0 +1,73 @@ +import System from '../System'; +import { Matrix } from '@pixi/math'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class ProjectionSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.projectionMatrix = new Matrix(); + } + + update(destinationFrame, sourceFrame, resolution, root) + { + this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; + this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; + + this.calculateProjection(this.destinationFrame, this.sourceFrame, resolution, root); + + this.renderer.globalUniforms.uniforms.projectionMatrix = this.projectionMatrix; + this.renderer.globalUniforms.update(); + } + + /** + * Updates the projection matrix based on a projection frame (which is a rectangle) + * + * @param {Rectangle} destinationFrame - The destination frame. + * @param {Rectangle} sourceFrame - The source frame. + */ + calculateProjection(destinationFrame, sourceFrame, resolution, root) + { + const pm = this.projectionMatrix; + + // I don't think we will need this line.. + // pm.identity(); + + if (!root) + { + pm.a = (1 / destinationFrame.width * 2) * resolution; + pm.d = (1 / destinationFrame.height * 2) * resolution; + + pm.tx = -1 - (sourceFrame.x * pm.a); + pm.ty = -1 - (sourceFrame.y * pm.d); + } + else + { + pm.a = (1 / destinationFrame.width * 2) * resolution; + pm.d = (-1 / destinationFrame.height * 2) * resolution; + + pm.tx = -1 - (sourceFrame.x * pm.a); + pm.ty = 1 - (sourceFrame.y * pm.d); + } + } + + /** + * Sets the transform of the active render target to the given matrix + * + * @param {PIXI.Matrix} matrix - The transformation matrix + */ + setTransform()// matrix) + { + // this._activeRenderTarget.transform = matrix; + } +} diff --git a/packages/core/src/renderTexture/BaseRenderTexture.js b/packages/core/src/renderTexture/BaseRenderTexture.js new file mode 100644 index 0000000..88eb83b --- /dev/null +++ b/packages/core/src/renderTexture/BaseRenderTexture.js @@ -0,0 +1,137 @@ +import BaseTexture from '../textures/BaseTexture'; +import FrameBuffer from '../framebuffer/FrameBuffer'; + +/** + * A BaseRenderTexture is a special texture that allows any PixiJS display object to be rendered to it. + * + * __Hint__: All DisplayObjects (i.e. Sprites) that render to a BaseRenderTexture should be preloaded + * otherwise black rectangles will be drawn instead. + * + * A BaseRenderTexture takes a snapshot of any Display Object given to its render method. The position + * and rotation of the given Display Objects is ignored. For example: + * + * ```js + * let renderer = PIXI.autoDetectRenderer(1024, 1024, { view: canvas, ratio: 1 }); + * let baseRenderTexture = new PIXI.BaseRenderTexture(renderer, 800, 600); + * let sprite = PIXI.Sprite.fromImage("spinObj_01.png"); + * + * sprite.position.x = 800/2; + * sprite.position.y = 600/2; + * sprite.anchor.x = 0.5; + * sprite.anchor.y = 0.5; + * + * baseRenderTexture.render(sprite); + * ``` + * + * The Sprite in this case will be rendered using its local transform. To render this sprite at 0,0 + * you can clear the transform + * + * ```js + * + * sprite.setTransform() + * + * let baseRenderTexture = new PIXI.BaseRenderTexture(100, 100); + * let renderTexture = new PIXI.RenderTexture(baseRenderTexture); + * + * renderer.render(sprite, renderTexture); // Renders to center of RenderTexture + * ``` + * + * @class + * @extends PIXI.BaseTexture + * @memberof PIXI + */ +export default class BaseRenderTexture extends BaseTexture +{ + /** + * @param {object} [options] + * @param {number} [options.width=100] - The width of the base render texture + * @param {number} [options.height=100] - The height of the base render texture + * @param {PIXI.SCALE_MODES} [options.scaleMode] - See {@link PIXI.SCALE_MODES} for possible values + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the texture being generated + */ + constructor(options) + { + if (typeof options === 'number') + { + /* eslint-disable prefer-rest-params */ + // Backward compatibility of signature + const width = arguments[0]; + const height = arguments[1]; + const scaleMode = arguments[2]; + const resolution = arguments[3]; + + options = { width, height, scaleMode, resolution }; + /* eslint-enable prefer-rest-params */ + } + + super(null, options); + + const { width, height } = options || {}; + + // Set defaults + this.mipmap = false; + this.width = Math.ceil(width) || 100; + this.height = Math.ceil(height) || 100; + this.valid = true; + + /** + * A map of renderer IDs to webgl renderTargets + * + * @private + * @member {object} + */ + // this._glRenderTargets = {}; + + /** + * A reference to the canvas render target (we only need one as this can be shared across renderers) + * + * @private + * @member {object} + */ + this._canvasRenderTarget = null; + + this.clearColor = [0, 0, 0, 0]; + + this.frameBuffer = new FrameBuffer(width * this.resolution, height * this.resolution) + .addColorTexture(0, this); + + // TODO - could this be added the systems? + + /** + * The data structure for the stencil masks + * + * @member {PIXI.Graphics[]} + */ + this.stencilMaskStack = []; + + /** + * The data structure for the filters + * + * @member {PIXI.Graphics[]} + */ + this.filterStack = [{}]; + } + + /** + * Resizes the BaseRenderTexture. + * + * @param {number} width - The width to resize to. + * @param {number} height - The height to resize to. + */ + resize(width, height) + { + width = Math.ceil(width); + height = Math.ceil(height); + this.frameBuffer.resize(width * this.resolution, height * this.resolution); + } + + /** + * Destroys this texture + * + */ + destroy() + { + super.destroy(true); + this.renderer = null; + } +} diff --git a/packages/core/src/renderTexture/RenderTexture.js b/packages/core/src/renderTexture/RenderTexture.js new file mode 100644 index 0000000..f1ecf1f --- /dev/null +++ b/packages/core/src/renderTexture/RenderTexture.js @@ -0,0 +1,153 @@ +import BaseRenderTexture from './BaseRenderTexture'; +import Texture from '../textures/Texture'; + +/** + * A RenderTexture is a special texture that allows any PixiJS display object to be rendered to it. + * + * __Hint__: All DisplayObjects (i.e. Sprites) that render to a RenderTexture should be preloaded + * otherwise black rectangles will be drawn instead. + * + * A RenderTexture takes a snapshot of any Display Object given to its render method. For example: + * + * ```js + * let renderer = PIXI.autoDetectRenderer(1024, 1024, { view: canvas, ratio: 1 }); + * let renderTexture = PIXI.RenderTexture.create(800, 600); + * let sprite = PIXI.Sprite.fromImage("spinObj_01.png"); + * + * sprite.position.x = 800/2; + * sprite.position.y = 600/2; + * sprite.anchor.x = 0.5; + * sprite.anchor.y = 0.5; + * + * renderer.render(sprite, renderTexture); + * ``` + * + * The Sprite in this case will be rendered using its local transform. To render this sprite at 0,0 + * you can clear the transform + * + * ```js + * + * sprite.setTransform() + * + * let renderTexture = new PIXI.RenderTexture.create(100, 100); + * + * renderer.render(sprite, renderTexture); // Renders to center of RenderTexture + * ``` + * + * @class + * @extends PIXI.Texture + * @memberof PIXI + */ +export default class RenderTexture extends Texture +{ + /** + * @param {PIXI.BaseRenderTexture} baseRenderTexture - The renderer used for this RenderTexture + * @param {PIXI.Rectangle} [frame] - The rectangle frame of the texture to show + */ + constructor(baseRenderTexture, frame) + { + // support for legacy.. + let _legacyRenderer = null; + + if (!(baseRenderTexture instanceof BaseRenderTexture)) + { + /* eslint-disable prefer-rest-params, no-console */ + const width = arguments[1]; + const height = arguments[2]; + const scaleMode = arguments[3]; + const resolution = arguments[4]; + + // we have an old render texture.. + console.warn(`Please use RenderTexture.create(${width}, ${height}) instead of the ctor directly.`); + _legacyRenderer = arguments[0]; + /* eslint-enable prefer-rest-params, no-console */ + + frame = null; + baseRenderTexture = new BaseRenderTexture({ + width, + height, + scaleMode, + resolution, + }); + } + + /** + * The base texture object that this texture uses + * + * @member {BaseTexture} + */ + super(baseRenderTexture, frame); + + this.legacyRenderer = _legacyRenderer; + + /** + * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. + * + * @member {boolean} + */ + this.valid = true; + + /** + * FilterSystem temporary storage + * @private + * @member {PIXI.Rectangle} + */ + this.filterFrame = null; + + this._updateUvs(); + } + + /** + * Resizes the RenderTexture. + * + * @param {number} width - The width to resize to. + * @param {number} height - The height to resize to. + * @param {boolean} [resizeBaseTexture=true] - Should the baseTexture.width and height values be resized as well? + */ + resize(width, height, resizeBaseTexture = true) + { + width = Math.ceil(width); + height = Math.ceil(height); + + // TODO - could be not required.. + this.valid = (width > 0 && height > 0); + + this._frame.width = this.orig.width = width; + this._frame.height = this.orig.height = height; + + if (resizeBaseTexture) + { + this.baseTexture.resize(width, height); + } + + this._updateUvs(); + } + + /** + * A short hand way of creating a render texture. + * + * @param {object} [options] - Options + * @param {number} [options.width=100] - The width of the render texture + * @param {number} [options.height=100] - The height of the render texture + * @param {number} [options.scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the texture being generated + * @return {PIXI.RenderTexture} The new render texture + */ + static create(options) + { + // fallback, old-style: create(width, height, scaleMode, resolution) + if (typeof options === 'number') + { + /* eslint-disable prefer-rest-params */ + options = { + width: options, + height: arguments[1], + scaleMode: arguments[2], + resolution: arguments[3], + }; + /* eslint-enable prefer-rest-params */ + } + + return new RenderTexture(new BaseRenderTexture(options)); + } +} diff --git a/packages/core/src/renderTexture/RenderTextureSystem.js b/packages/core/src/renderTexture/RenderTextureSystem.js new file mode 100644 index 0000000..8d0ca61 --- /dev/null +++ b/packages/core/src/renderTexture/RenderTextureSystem.js @@ -0,0 +1,115 @@ +import System from '../System'; +import { Rectangle } from '@pixi/math'; + +const tempRect = new Rectangle(); + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class RenderTextureSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.clearColor = renderer._backgroundColorRgba; + + // TODO moe this property somewhere else! + this.defaultMaskStack = []; + this.defaultFilterStack = [{}]; + + // empty render texture? + this.renderTexture = null; + + this.destinationFrame = new Rectangle(); + } + + bind(renderTexture, sourceFrame, destinationFrame) + { + // TODO - do we want this?? + if (this.renderTexture === renderTexture) return; + this.renderTexture = renderTexture; + + const renderer = this.renderer; + + if (renderTexture) + { + const baseTexture = renderTexture.baseTexture; + + if (!destinationFrame) + { + tempRect.width = baseTexture.realWidth; + tempRect.height = baseTexture.realHeight; + + destinationFrame = tempRect; + } + + if (!sourceFrame) + { + sourceFrame = destinationFrame; + } + + this.renderer.framebuffer.bind(baseTexture.frameBuffer, destinationFrame); + + this.renderer.projection.update(destinationFrame, sourceFrame, baseTexture.resolution, false); + this.renderer.stencil.setMaskStack(baseTexture.stencilMaskStack); + } + else + { + // TODO these validation checks happen deeper down.. + // thing they can be avoided.. + if (!destinationFrame) + { + tempRect.width = renderer.width; + tempRect.height = renderer.height; + + destinationFrame = tempRect; + } + + if (!sourceFrame) + { + sourceFrame = destinationFrame; + } + + renderer.framebuffer.bind(null, destinationFrame); + + // TODO store this.. + this.renderer.projection.update(destinationFrame, sourceFrame, this.renderer.resolution, true); + this.renderer.stencil.setMaskStack(this.defaultMaskStack); + } + + this.destinationFrame.copyFrom(destinationFrame); + } + + /** + * Erases the render texture and fills the drawing area with a colour + * + * @param {number} [clearColor] - The colour + * @return {PIXI.Renderer} Returns itself. + */ + clear(clearColor) + { + if (this.renderTexture) + { + clearColor = clearColor || this.renderTexture.baseTexture.clearColor; + } + else + { + clearColor = clearColor || this.clearColor; + } + + this.renderer.framebuffer.clear(clearColor[0], clearColor[1], clearColor[2], clearColor[3]); + } + + resize()// screenWidth, screenHeight) + { + // resize the root only! + this.bind(null); + } +} diff --git a/packages/core/src/renderers/AbstractRenderer.js b/packages/core/src/renderers/AbstractRenderer.js deleted file mode 100644 index 928670f..0000000 --- a/packages/core/src/renderers/AbstractRenderer.js +++ /dev/null @@ -1,353 +0,0 @@ -import { hex2string, hex2rgb } from '@pixi/utils'; -import { Matrix, Rectangle } from '@pixi/math'; -import { RENDERER_TYPE } from '@pixi/constants'; -import { settings } from '@pixi/settings'; -import { Container } from '@pixi/display'; -import RenderTexture from '../textures/RenderTexture'; -import EventEmitter from 'eventemitter3'; - -const tempMatrix = new Matrix(); - -/** - * 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 EventEmitter - * @memberof PIXI - */ -export default class AbstractRenderer extends EventEmitter -{ - // eslint-disable-next-line valid-jsdoc - /** - * @param {string} system - The name of the system this renderer is for. - * @param {object} [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.transparent=false] - If the render view is transparent, default false - * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false - * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) - * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. The - * resolution of the renderer retina would be 2. - * @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 {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when rendering, - * stopping pixel interpolation. - */ - constructor(system, options, arg2, arg3) - { - super(); - - // Support for constructor(system, screenWidth, screenHeight, options) - if (typeof options === 'number') - { - options = Object.assign({ - width: options, - height: arg2 || settings.RENDER_OPTIONS.height, - }, arg3); - } - - // 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 = RENDERER_TYPE.UNKNOWN; - - /** - * Measurements of the screen. (0, 0, screenWidth, screenHeight) - * - * Its safe to use as filterArea or hitArea for 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 1 - */ - this.resolution = options.resolution || settings.RESOLUTION; - - /** - * Whether the render view is transparent - * - * @member {boolean} - */ - this.transparent = options.transparent; - - /** - * Whether css dimensions of canvas view should be resized to screen dimensions automatically - * - * @member {boolean} - */ - this.autoResize = options.autoResize || false; - - /** - * Tracks the blend modes useful for this renderer. - * - * @member {object} - */ - this.blendModes = null; - - /** - * 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; - - /** - * If true PixiJS will Math.floor() x/y values when rendering, stopping pixel interpolation. - * Handy for crisp pixel art and speed on legacy devices. - * - * @member {boolean} - */ - this.roundPixels = options.roundPixels; - - /** - * The background color as a number. - * - * @member {number} - * @private - */ - this._backgroundColor = 0x000000; - - /** - * The background color as an [R, G, B] array. - * - * @member {number[]} - * @private - */ - this._backgroundColorRgba = [0, 0, 0, 0]; - - /** - * The background color as a string. - * - * @member {string} - * @private - */ - this._backgroundColorString = '#000000'; - - this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter - - /** - * This temporary display object used as the parent of the currently being rendered item - * - * @member {PIXI.DisplayObject} - * @private - */ - this._tempDisplayObjectParent = new Container(); - - /** - * The last root object that the renderer tried to render. - * - * @member {PIXI.DisplayObject} - * @private - */ - this._lastObjectRendered = this._tempDisplayObjectParent; - - /** - * Collection of plugins. - * @readonly - * @member {object} - */ - this.plugins = {}; - } - - /** - * Initialize the plugins. - * - * @protected - * @param {object} staticMap - The dictionary of staticly saved plugins. - */ - initPlugins(staticMap) - { - 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() - { - return this.view.width; - } - - /** - * Same as view.height, actual number of pixels in the canvas by vertical - * - * @member {number} - * @readonly - * @default 600 - */ - get height() - { - return this.view.height; - } - - /** - * Resizes the screen and canvas to the specified width and height - * Canvas dimensions are multiplied by resolution - * - * @param {number} screenWidth - the new width of the screen - * @param {number} screenHeight - the new height of the screen - */ - resize(screenWidth, screenHeight) - { - this.screen.width = screenWidth; - this.screen.height = screenHeight; - - this.view.width = screenWidth * this.resolution; - this.view.height = screenHeight * this.resolution; - - if (this.autoResize) - { - this.view.style.width = `${screenWidth}px`; - this.view.style.height = `${screenHeight}px`; - } - } - - /** - * 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 {PIXI.DisplayObject} displayObject - The displayObject the object will be generated from - * @param {number} scaleMode - Should be one of the scaleMode consts - * @param {number} resolution - The resolution / device pixel ratio of the texture being generated - * @param {PIXI.Rectangle} [region] - The region of the displayObject, that shall be rendered, - * if no region is specified, defaults to the local bounds of the displayObject. - * @return {PIXI.Texture} a texture of the graphics object - */ - generateTexture(displayObject, scaleMode, resolution, region) - { - region = region || displayObject.getLocalBounds(); - - // 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(region.width | 0, region.height | 0, scaleMode, resolution); - - tempMatrix.tx = -region.x; - tempMatrix.ty = -region.y; - - this.render(displayObject, renderTexture, false, tempMatrix, true); - - return renderTexture; - } - - /** - * Removes everything from the renderer and optionally removes the Canvas DOM element. - * - * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. - */ - destroy(removeView) - { - 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); - } - - this.plugins = null; - - this.type = RENDERER_TYPE.UNKNOWN; - - this.view = null; - - this.screen = null; - - this.resolution = 0; - - this.transparent = false; - - this.autoResize = false; - - this.blendModes = null; - - this.options = null; - - this.preserveDrawingBuffer = false; - this.clearBeforeRender = false; - - this.roundPixels = false; - - this._backgroundColor = 0; - this._backgroundColorRgba = null; - this._backgroundColorString = null; - - this._tempDisplayObjectParent = null; - this._lastObjectRendered = null; - } - - /** - * The background color to fill if not transparent - * - * @member {number} - */ - get backgroundColor() - { - return this._backgroundColor; - } - - set backgroundColor(value) // eslint-disable-line require-jsdoc - { - this._backgroundColor = value; - this._backgroundColorString = hex2string(value); - hex2rgb(value, this._backgroundColorRgba); - } -} diff --git a/packages/core/src/AbstractRenderer.js b/packages/core/src/AbstractRenderer.js new file mode 100644 index 0000000..64b2eb1 --- /dev/null +++ b/packages/core/src/AbstractRenderer.js @@ -0,0 +1,353 @@ +import { hex2string, hex2rgb } from '@pixi/utils'; +import { Matrix, Rectangle } from '@pixi/math'; +import { RENDERER_TYPE } from '@pixi/constants'; +import { settings } from '@pixi/settings'; +import { Container } from '@pixi/display'; +import RenderTexture from './renderTexture/RenderTexture'; +import EventEmitter from 'eventemitter3'; + +const tempMatrix = new Matrix(); + +/** + * 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 EventEmitter + * @memberof PIXI + */ +export default class AbstractRenderer extends EventEmitter +{ + // eslint-disable-next-line valid-jsdoc + /** + * @param {string} system - The name of the system this renderer is for. + * @param {object} [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.transparent=false] - If the render view is transparent, default false + * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false + * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. The + * resolution of the renderer retina would be 2. + * @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 {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when rendering, + * stopping pixel interpolation. + */ + constructor(system, options, arg2, arg3) + { + super(); + + // Support for constructor(system, screenWidth, screenHeight, options) + if (typeof options === 'number') + { + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + }, arg3); + } + + // 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 = RENDERER_TYPE.UNKNOWN; + + /** + * Measurements of the screen. (0, 0, screenWidth, screenHeight) + * + * Its safe to use as filterArea or hitArea for 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 1 + */ + this.resolution = options.resolution || settings.RESOLUTION; + + /** + * Whether the render view is transparent + * + * @member {boolean} + */ + this.transparent = options.transparent; + + /** + * Whether css dimensions of canvas view should be resized to screen dimensions automatically + * + * @member {boolean} + */ + this.autoResize = options.autoResize || false; + + /** + * Tracks the blend modes useful for this renderer. + * + * @member {object} + */ + this.blendModes = null; + + /** + * 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; + + /** + * If true PixiJS will Math.floor() x/y values when rendering, stopping pixel interpolation. + * Handy for crisp pixel art and speed on legacy devices. + * + * @member {boolean} + */ + this.roundPixels = options.roundPixels; + + /** + * The background color as a number. + * + * @member {number} + * @private + */ + this._backgroundColor = 0x000000; + + /** + * The background color as an [R, G, B] array. + * + * @member {number[]} + * @private + */ + this._backgroundColorRgba = [0, 0, 0, 0]; + + /** + * The background color as a string. + * + * @member {string} + * @private + */ + this._backgroundColorString = '#000000'; + + this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter + + /** + * This temporary display object used as the parent of the currently being rendered item + * + * @member {PIXI.DisplayObject} + * @private + */ + this._tempDisplayObjectParent = new Container(); + + /** + * The last root object that the renderer tried to render. + * + * @member {PIXI.DisplayObject} + * @private + */ + this._lastObjectRendered = this._tempDisplayObjectParent; + + /** + * Collection of plugins. + * @readonly + * @member {object} + */ + this.plugins = {}; + } + + /** + * Initialize the plugins. + * + * @protected + * @param {object} staticMap - The dictionary of staticly saved plugins. + */ + initPlugins(staticMap) + { + 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() + { + return this.view.width; + } + + /** + * Same as view.height, actual number of pixels in the canvas by vertical + * + * @member {number} + * @readonly + * @default 600 + */ + get height() + { + return this.view.height; + } + + /** + * Resizes the screen and canvas to the specified width and height + * Canvas dimensions are multiplied by resolution + * + * @param {number} screenWidth - the new width of the screen + * @param {number} screenHeight - the new height of the screen + */ + resize(screenWidth, screenHeight) + { + this.screen.width = screenWidth; + this.screen.height = screenHeight; + + this.view.width = screenWidth * this.resolution; + this.view.height = screenHeight * this.resolution; + + if (this.autoResize) + { + this.view.style.width = `${screenWidth}px`; + this.view.style.height = `${screenHeight}px`; + } + } + + /** + * 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 {PIXI.DisplayObject} displayObject - The displayObject the object will be generated from + * @param {number} scaleMode - Should be one of the scaleMode consts + * @param {number} resolution - The resolution / device pixel ratio of the texture being generated + * @param {PIXI.Rectangle} [region] - The region of the displayObject, that shall be rendered, + * if no region is specified, defaults to the local bounds of the displayObject. + * @return {PIXI.Texture} a texture of the graphics object + */ + generateTexture(displayObject, scaleMode, resolution, region) + { + region = region || displayObject.getLocalBounds(); + + // 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(region.width | 0, region.height | 0, scaleMode, resolution); + + tempMatrix.tx = -region.x; + tempMatrix.ty = -region.y; + + this.render(displayObject, renderTexture, false, tempMatrix, true); + + return renderTexture; + } + + /** + * Removes everything from the renderer and optionally removes the Canvas DOM element. + * + * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. + */ + destroy(removeView) + { + 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); + } + + this.plugins = null; + + this.type = RENDERER_TYPE.UNKNOWN; + + this.view = null; + + this.screen = null; + + this.resolution = 0; + + this.transparent = false; + + this.autoResize = false; + + this.blendModes = null; + + this.options = null; + + this.preserveDrawingBuffer = false; + this.clearBeforeRender = false; + + this.roundPixels = false; + + this._backgroundColor = 0; + this._backgroundColorRgba = null; + this._backgroundColorString = null; + + this._tempDisplayObjectParent = null; + this._lastObjectRendered = null; + } + + /** + * The background color to fill if not transparent + * + * @member {number} + */ + get backgroundColor() + { + return this._backgroundColor; + } + + set backgroundColor(value) // eslint-disable-line require-jsdoc + { + this._backgroundColor = value; + this._backgroundColorString = hex2string(value); + hex2rgb(value, this._backgroundColorRgba); + } +} diff --git a/packages/core/src/Renderer.js b/packages/core/src/Renderer.js new file mode 100644 index 0000000..2227e78 --- /dev/null +++ b/packages/core/src/Renderer.js @@ -0,0 +1,338 @@ +import AbstractRenderer from './AbstractRenderer'; +import { sayHello } from '@pixi/utils'; +import MaskSystem from './mask/MaskSystem'; +import StencilSystem from './mask/StencilSystem'; +import FilterSystem from './filters/FilterSystem'; +import FramebufferSystem from './framebuffer/FramebufferSystem'; +import RenderTextureSystem from './renderTexture/RenderTextureSystem'; +import TextureSystem from './textures/TextureSystem'; +import ProjectionSystem from './projection/ProjectionSystem'; +import StateSystem from './state/StateSystem'; +import GeometrySystem from './geometry/GeometrySystem'; +import ShaderSystem from './shader/ShaderSystem'; +import ContextSystem from './context/ContextSystem'; +import BatchSystem from './batch/BatchSystem'; +import TextureGCSystem from './textures/TextureGCSystem'; +import { RENDERER_TYPE } from '@pixi/constants'; +import UniformGroup from './shader/UniformGroup'; +import { Matrix } from '@pixi/math'; +import Runner from 'mini-runner'; + +/** + * 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 Render works by automatically managing webGLBatchs. + * 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 :) + * + * @class + * @memberof PIXI + * @extends PIXI.AbstractRenderer + */ +export default class Renderer extends AbstractRenderer +{ + // eslint-disable-next-line valid-jsdoc + /** + * + * @param {object} [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.transparent=false] - If the render view is transparent, default false + * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false + * @param {boolean} [options.antialias=false] - sets antialias. If not available natively then FXAA + * antialiasing is used + * @param {boolean} [options.forceFXAA=false] - forces FXAA antialiasing to be used over native. + * FXAA is faster, but may not always look as great + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. + * The resolution of the renderer retina would be 2. + * @param {boolean} [options.clearBeforeRender=true] - This sets if the renderer will clear + * the canvas or not before the new render pass. If you wish to set this to false, you *must* set + * preserveDrawingBuffer to `true`. + * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, + * enable this if you need to call toDataUrl on the webgl context. + * @param {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when + * rendering, stopping pixel interpolation. + * @param {number} [options.backgroundColor=0x000000] - The background color of the rendered area + * (shown if not transparent). + * @param {boolean} [options.legacy=false] - If true PixiJS will aim to ensure compatibility + * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. + * @param {string} [options.powerPreference] - Parameter passed to webgl context, set to "high-performance" + * for devices with dual graphics card + */ + constructor(options, arg2, arg3) + { + super('WebGL', options, arg2, arg3); + + /** + * The type of this renderer as a standardised const + * + * @member {number} + * @see PIXI.RENDERER_TYPE + */ + this.type = RENDERER_TYPE.WEBGL; + + // this will be set by the contextSystem (this.context) + this.gl = null; + this.CONTEXT_UID = 0; + this.legacy = !!options.legacy; + + // TODO legacy! + + // runners! + this.runners = { + destroy: new Runner('destroy'), + contextChange: new Runner('contextChange', 1), + reset: new Runner('reset'), + update: new Runner('update'), + postrender: new Runner('postrender'), + prerender: new Runner('prerender'), + resize: new Runner('resize', 2), + }; + + 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(GeometrySystem, 'geometry') + .addSystem(FramebufferSystem, 'framebuffer') + .addSystem(StencilSystem, 'stencil') + .addSystem(ProjectionSystem, 'projection') + .addSystem(TextureGCSystem, 'textureGC') + .addSystem(FilterSystem, 'filter') + .addSystem(RenderTextureSystem, 'renderTexture') + .addSystem(BatchSystem, 'batch'); + + this.initPlugins(Renderer.__plugins); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ + if (options.context) + { + this.context.initFromContext(options.context); + } + else + { + this.context.initFromOptions({ + alpha: this.transparent, + antialias: options.antialias, + premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', + stencil: true, + 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); + } + + /** + * Add a new system to the renderer. + * @param {class} ClassRef - Class reference + * @param {string} [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. + * @return {PIXI.Renderer} Return instance of renderer + */ + addSystem(ClassRef, name) + { + if (!name) + { + name = ClassRef.name; + } + + // TODO - read name from class.name.. + + /* + if(name.includes('System')) + { + name = name.replace('System', ''); + name = name.charAt(0).toLowerCase() + name.slice(1); + } + */ + + const system = new ClassRef(this); + + if (this[name]) + { + throw new Error(`Whoops! ${name} is already a manger`); + } + + this[name] = system; + + for (const i in this.runners) + { + this.runners[i].add(system); + } + + return this; + + /** + * 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. + */ + } + + /** + * Renders the object to its webGL view + * + * @param {PIXI.DisplayObject} displayObject - the object to be rendered + * @param {PIXI.RenderTexture} renderTexture - The render texture to render to. + * @param {boolean} [clear] - Should the canvas be cleared before the new render + * @param {PIXI.Transform} [transform] - A transform to apply to the render texture before rendering. + * @param {boolean} [skipUpdateTransform] - Should we skip the update transform pass? + */ + render(displayObject, renderTexture, clear, transform, skipUpdateTransform) + { + // can be handy to know! + this.renderingToScreen = !renderTexture; + + this.runners.prerender.run(); + this.emit('prerender'); + + // 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.parent; + + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = 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.run(); + + this.emit('postrender'); + } + + /** + * Resizes the webGL view to the specified width and height. + * + * @param {number} screenWidth - the new width of the screen + * @param {number} screenHeight - the new height of the screen + */ + resize(screenWidth, screenHeight) + { + AbstractRenderer.prototype.resize.call(this, screenWidth, screenHeight); + + this.runners.resize.run(screenWidth, screenHeight); + } + + /** + * Resets the WebGL state so you can render things however you fancy! + * + * @return {PIXI.Renderer} Returns itself. + */ + reset() + { + this.runners.reset.run(); + + return this; + } + + clear() + { + this.framebuffer.bind(); + this.framebuffer.clear(); + } + + /** + * Removes everything from the renderer (event listeners, spritebatch, etc...) + * + * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. + * See: https://github.com/pixijs/pixi.js/issues/2233 + */ + destroy(removeView) + { + this.runners.destroy.run(); + + // call base destroy + super.destroy(removeView); + + // TODO nullify all the managers.. + this.gl = null; + } + + /** + * 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 + * builds and excluding plugins. + * @name PIXI.Renderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. + */ + + /** + * Adds a plugin to the renderer. + * + * @method + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + static registerPlugin(pluginName, ctor) + { + Renderer.__plugins = Renderer.__plugins || {}; + Renderer.__plugins[pluginName] = ctor; + } +} diff --git a/packages/core/src/System.js b/packages/core/src/System.js new file mode 100644 index 0000000..05a10c8 --- /dev/null +++ b/packages/core/src/System.js @@ -0,0 +1,42 @@ +/** + * System is a base class used for extending systems used by the {@link PIXI.Renderer} + * @see PIXI.Renderer#addSystem + * @class + * @memberof PIXI + */ +export default class System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + /** + * The renderer this manager works for. + * + * @member {PIXI.Renderer} + */ + this.renderer = renderer; + + this.renderer.runners.contextChange.add(this); + } + + /** + * Generic method called when there is a WebGL context change. + * + */ + contextChange() + { + // do some codes init! + } + + /** + * Generic destroy methods to be overridden by the subclass + * + */ + destroy() + { + this.renderer.runners.contextChange.remove(this); + this.renderer = null; + } +} diff --git a/packages/core/src/batch/BatchSystem.js b/packages/core/src/batch/BatchSystem.js new file mode 100644 index 0000000..b04c249 --- /dev/null +++ b/packages/core/src/batch/BatchSystem.js @@ -0,0 +1,68 @@ +import System from '../System'; +import ObjectRenderer from './ObjectRenderer'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class BatchSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + super(renderer); + + /** + * An empty renderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.emptyRenderer = new ObjectRenderer(renderer); + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.currentRenderer = this.emptyRenderer; + } + + /** + * Changes the current renderer to the one given in parameter + * + * @param {PIXI.ObjectRenderer} objectRenderer - The object renderer to use. + */ + setObjectRenderer(objectRenderer) + { + if (this.currentRenderer === objectRenderer) + { + return; + } + + this.currentRenderer.stop(); + this.currentRenderer = objectRenderer; + + this.renderer.state.setState(objectRenderer.state); + + this.currentRenderer.start(); + } + + /** + * This should be called if you wish to do some custom rendering + * It will basically render anything that may be batched up such as sprites + * + */ + flush() + { + this.setObjectRenderer(this.emptyRenderer); + } + + reset() + { + this.setObjectRenderer(this.emptyRenderer); + } +} diff --git a/packages/core/src/batch/ObjectRenderer.js b/packages/core/src/batch/ObjectRenderer.js new file mode 100644 index 0000000..0c851a9 --- /dev/null +++ b/packages/core/src/batch/ObjectRenderer.js @@ -0,0 +1,48 @@ +import System from '../System'; + +/** + * Base for a common object renderer that can be used as a system renderer plugin. + * + * @class + * @extends PIXI.System + * @memberof PIXI + */ +export default class ObjectRenderer extends System +{ + /** + * Starts the renderer and sets the shader + * + */ + start() + { + // set the shader.. + } + + /** + * Stops the renderer + * + */ + stop() + { + this.flush(); + } + + /** + * Stub method for rendering content and emptying the current batch. + * + */ + flush() + { + // flush! + } + + /** + * Renders an object + * + * @param {PIXI.DisplayObject} object - The object to render. + */ + render(object) // eslint-disable-line no-unused-vars + { + // render the object + } +} diff --git a/packages/core/src/context/ContextSystem.js b/packages/core/src/context/ContextSystem.js new file mode 100644 index 0000000..301aab4 --- /dev/null +++ b/packages/core/src/context/ContextSystem.js @@ -0,0 +1,186 @@ +import System from '../System'; +import { settings } from '@pixi/settings'; + +let CONTEXT_UID = 0; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class ContextSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.webGLVersion = 1; + + this.handleContextLost = this.handleContextLost.bind(this); + this.handleContextRestored = this.handleContextRestored.bind(this); + + this.extensions = {}; + + renderer.view.addEventListener('webglcontextlost', this.handleContextLost, false); + renderer.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + } + + get isLost() + { + return (!this.gl || this.gl.isContextLost()); + } + + contextChange(gl) + { + this.gl = gl; + + // restore a context if it was previously lost + if (gl.isContextLost() && gl.getExtension('WEBGL_lose_context')) + { + gl.getExtension('WEBGL_lose_context').restoreContext(); + } + } + + initFromContext(gl) + { + this.gl = gl; + this.validateContext(gl); + this.renderer.gl = gl; + this.renderer.CONTEXT_UID = CONTEXT_UID++; + this.renderer.runners.contextChange.run(gl); + } + + initFromOptions(options) + { + const gl = this.createContext(this.renderer.view, options); + + this.initFromContext(gl); + } + + /** + * Helper class to create a webGL Context + * + * @class + * @memberof PIXI.glCore + * @param canvas {HTMLCanvasElement} the canvas element that we will get the context from + * @param options {Object} An options object that gets passed in to the canvas element containing the context attributes, + * see https://developer.mozilla.org/en/docs/Web/API/HTMLCanvasElement/getContext for the options available + * @return {WebGLRenderingContext} the WebGL context + */ + createContext(canvas, options) + { + let gl; + + if (settings.PREFER_WEBGL_2) + { + gl = canvas.getContext('webgl2', options); + } + + if (gl) + { + this.webGLVersion = 2; + } + else + { + this.webGLVersion = 1; + + gl = canvas.getContext('webgl', options) + || canvas.getContext('experimental-webgl', options); + + if (!gl) + { + // fail, not able to get a context + throw new Error('This browser does not support webGL. Try using the canvas renderer'); + } + } + + this.gl = gl; + + this.getExtensions(); + + return gl; + } + + getExtensions() + { + // time to set up default etensions that pixi uses.. + const gl = this.gl; + const extensions = this.extensions; + + if (this.webGLVersion === 1) + { + extensions.drawBuffers = gl.getExtension('WEBGL_draw_buffers'); + extensions.depthTexture = gl.getExtension('WEBKIT_WEBGL_depth_texture'); + extensions.floatTexture = gl.getExtension('OES_texture_float'); + extensions.loseContext = gl.getExtension('WEBGL_lose_context'); + + extensions.vertexArrayObject = gl.getExtension('OES_vertex_array_object') + || gl.getExtension('MOZ_OES_vertex_array_object') + || gl.getExtension('WEBKIT_OES_vertex_array_object'); + } + + // we don't use any specific WebGL 2 ones yet! + } + + /** + * Handles a lost webgl context + * + * @private + * @param {WebGLContextEvent} event - The context lost event. + */ + handleContextLost(event) + { + event.preventDefault(); + } + + /** + * Handles a restored webgl context + * + * @private + */ + handleContextRestored() + { + this.renderer.runners.contextChange.run(this.gl); + } + + destroy() + { + const view = this.renderer.view; + + // remove listeners + view.removeEventListener('webglcontextlost', this.handleContextLost); + view.removeEventListener('webglcontextrestored', this.handleContextRestored); + + this.gl.useProgram(null); + + if (this.extensions.loseContext) + { + this.extensions.loseContext.loseContext(); + } + } + + postrender() + { + this.gl.flush(); + } + + validateContext(gl) + { + const attributes = gl.getContextAttributes(); + + // this is going to be fairly simple for now.. but at least we have room to grow! + if (!attributes.stencil) + { + /* eslint-disable max-len */ + + /* eslint-disable no-console */ + console.warn('Provided WebGL context does not have a stencil buffer, masks may not render correctly'); + /* eslint-enable no-console */ + + /* eslint-enable max-len */ + } + } +} diff --git a/packages/core/src/filters/Filter.js b/packages/core/src/filters/Filter.js new file mode 100644 index 0000000..9d8cad8 --- /dev/null +++ b/packages/core/src/filters/Filter.js @@ -0,0 +1,140 @@ +import Shader from '../shader/Shader'; +import Program from '../shader/Program'; +import State from '../state/State'; +import { settings } from '@pixi/settings'; +// import extractUniformsFromSrc from './extractUniformsFromSrc'; +import defaultVertex from './defaultFilter.vert'; +import defaultFragment from './defaultFilter.frag'; + +// let math = require('../../math'); +/** + * @class + * @memberof PIXI + * @extends PIXI.Shader + */ +export default class Filter extends Shader +{ + /** + * @param {string} [vertexSrc] - The source of the vertex shader. + * @param {string} [fragmentSrc] - The source of the fragment shader. + * @param {object} [uniforms] - Custom uniforms to use to augment the built-in ones. + */ + constructor(vertexSrc, fragmentSrc, uniforms) + { + const program = Program.from(vertexSrc, fragmentSrc); + + super(program, uniforms); + + /** + * The padding of the filter. Some filters require extra space to breath such as a blur. + * Increasing this will add extra width and height to the bounds of the object that the + * filter is applied to. + * + * @member {number} + */ + this.padding = 0; + + /** + * The resolution of the filter. Setting this to be lower will lower the quality but + * increase the performance of the filter. + * + * @member {number} + */ + this.resolution = settings.RESOLUTION; + + /** + * If enabled is true the filter is applied, if false it will not. + * + * @member {boolean} + */ + this.enabled = true; + + /** + * If enabled, PixiJS will fit the filter area into boundaries for better performance. + * Switch it off if it does not work for specific shader. + * + * @member {boolean} + */ + this.autoFit = true; + + /** + * Legacy filters use position and uvs from attributes + * @member {boolean} + * @readonly + */ + this.legacy = !!this.program.attributeData.aTextureCoord; + + /** + * the webGL state the filter requires to render + * @member {PIXI.State} + */ + this.state = new State(); + } + + /** + * Applies the filter + * + * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + * @param {boolean} clear - Should the output be cleared before rendering to it + * @param {object} [currentState] - It's current state of filter. + * There are some useful properties in the currentState : + * target, filters, sourceFrame, destinationFrame, renderTarget, resolution + */ + apply(filterManager, input, output, clear, currentState, derp) // eslint-disable-line no-unused-vars + { + // do as you please! + + filterManager.applyFilter(this, input, output, clear, currentState, derp); + + // or just do a regular render.. + } + + /** + * Sets the blendmode of the filter + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL + */ + get blendMode() + { + return this.state.blendMode; + } + + set blendMode(value) // eslint-disable-line require-jsdoc + { + this.state.blendMode = value; + } + + /** + * The default vertex shader source + * + * @static + * @constant + */ + static get defaultVertexSrc() + { + return defaultVertex; + } + + /** + * The default fragment shader source + * + * @static + * @constant + */ + static get defaultFragmentSrc() + { + return defaultFragment; + } +} + +/** + * Used for caching shader IDs + * + * @static + * @private + */ +Filter.SOURCE_KEY_MAP = {}; + diff --git a/packages/core/src/filters/FilterSystem.js b/packages/core/src/filters/FilterSystem.js new file mode 100644 index 0000000..3174318 --- /dev/null +++ b/packages/core/src/filters/FilterSystem.js @@ -0,0 +1,431 @@ +import System from '../System'; + +import RenderTexture from '../renderTexture/RenderTexture'; +import Quad from '../utils/Quad'; +import QuadUv from '../utils/QuadUv'; +import { Rectangle } from '@pixi/math'; +import * as filterTransforms from './filterTransforms'; +import bitTwiddle from 'bit-twiddle'; +import UniformGroup from '../shader/UniformGroup'; +import { DRAW_MODES } from '@pixi/constants'; + +// +/** + * @ignore + * @class + */ +class FilterState +{ + /** + * + */ + constructor() + { + this.renderTexture = null; + this.sourceFrame = new Rectangle(); + this.destinationFrame = new Rectangle(); + this.filters = []; + this.target = null; + this.legacy = false; + this.resolution = 1; + } +} + +/** + * @class + * @memberof PIXI.systems + * @extends PIXI.System + */ +export default class FilterSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + /** + * stores a bunch of PO2 textures used for filtering + * @type {Object} + */ + this.texturePool = {}; + + /** + * a pool for storing filter states, save us creating new ones each tick + * @type {Array} + */ + this.statePool = []; + + /** + * A very simple geometry used when drawing a filter effect to the screen + * @type {Quad} + */ + this.quad = new Quad(); + + this.quadUv = new QuadUv(); + + /** + * Temporary rect for maths + * @type {PIXI.Rectangle} + */ + this.tempRect = new Rectangle(); + + this.activeState = {}; + + /** + * this uniform group is attached to filter uniforms when used + * @type {UniformGroup} + */ + this.globalUniforms = new UniformGroup({ + sourceFrame: this.tempRect, + destinationFrame: this.tempRect, + + // legacy variables + filterArea: new Float32Array(4), + filterClamp: new Float32Array(4), + }, true); + } + + /** + * Adds a new filter to the System. + * + * @param {PIXI.DisplayObject} target - The target of the filter to render. + * @param {PIXI.Filter[]} filters - The filters to apply. + */ + push(target, filters) + { + const renderer = this.renderer; + const filterStack = this.renderer.renderTexture.defaultFilterStack; + const state = this.statePool.pop() || new FilterState(); + + let resolution = filters[0].resolution; + let padding = filters[0].padding; + let autoFit = filters[0].autoFit; + let legacy = filters[0].legacy; + + for (let i = 1; i < filters.length; i++) + { + const filter = filters[i]; + + // lets use the lowest resolution.. + resolution = Math.min(resolution, filter.resolution); + // and the largest amount of padding! + padding = Math.max(padding, filter.padding); + // only auto fit if all filters are autofit + autoFit = autoFit || filter.autoFit; + + legacy = legacy || filter.legacy; + } + + filterStack.push(state); + + state.resolution = resolution; + + state.legacy = legacy; + + // round to whole number based on resolution + // TODO move that to the shader too? + state.sourceFrame = target.filterArea || target.getBounds(true); + + state.sourceFrame.pad(padding); + + if (autoFit) + { + state.sourceFrame.fit(this.renderer.renderTexture.destinationFrame); + } + + state.sourceFrame.round(resolution); + + state.renderTexture = this.getPotFilterTexture(state.sourceFrame.width, state.sourceFrame.height, resolution); + state.filters = filters; + + state.destinationFrame.width = state.renderTexture.width; + state.destinationFrame.height = state.renderTexture.height; + + state.renderTexture.filterFrame = state.sourceFrame; + renderer.renderTexture.bind(state.renderTexture, state.sourceFrame);// /, state.destinationFrame); + renderer.renderTexture.clear(); + } + + /** + * Pops off the filter and applies it. + * + */ + pop() + { + const renderer = this.renderer; + const filterStack = renderer.renderTexture.defaultFilterStack; + const state = filterStack.pop(); + const filters = state.filters; + + this.activeState = state; + + const globalUniforms = this.globalUniforms.uniforms; + + globalUniforms.sourceFrame = state.sourceFrame; + globalUniforms.destinationFrame = state.destinationFrame; + globalUniforms.resolution = state.resolution; + + // only update the rect if its legacy.. + if (state.legacy) + { + const filterArea = globalUniforms.filterArea; + const filterClamp = globalUniforms.filterClamp; + + filterArea[0] = state.destinationFrame.width; + filterArea[1] = state.destinationFrame.height; + filterArea[2] = state.sourceFrame.x; + filterArea[3] = state.sourceFrame.y; + + filterClamp[0] = 0.5 / state.resolution / state.destinationFrame.width; + filterClamp[1] = 0.5 / state.resolution / state.destinationFrame.height; + filterClamp[2] = (state.sourceFrame.width - 0.5) / state.resolution / state.destinationFrame.width; + filterClamp[3] = (state.sourceFrame.height - 0.5) / state.resolution / state.destinationFrame.height; + } + + this.globalUniforms.update(); + + const lastState = filterStack[filterStack.length - 1]; + + if (filters.length === 1) + { + filters[0].apply(this, state.renderTexture, lastState.renderTexture, false, state); + + this.returnFilterTexture(state.renderTexture); + } + else + { + let flip = state.renderTexture; + let flop = this.getPotFilterTexture( + flip.width, + flip.height, + state.resolution + ); + + flop.filterFrame = flip.filterFrame; + + let i = 0; + + for (i = 0; i < filters.length - 1; ++i) + { + filters[i].apply(this, flip, flop, true, state); + + const t = flip; + + flip = flop; + flop = t; + } + + filters[i].apply(this, flip, lastState.renderTexture, false, state); + + this.returnFilterTexture(flip); + this.returnFilterTexture(flop); + } + + this.statePool.push(state); + } + + /** + * Draws a filter. + * + * @param {PIXI.Filter} filter - The filter to draw. + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + * @param {boolean} clear - Should the output be cleared before rendering to it + */ + applyFilter(filter, input, output, clear) + { + const renderer = this.renderer; + + renderer.renderTexture.bind(output, output ? output.filterFrame : null); + + if (clear) + { + // gl.disable(gl.SCISSOR_TEST); + renderer.renderTexture.clear(); + // gl.enable(gl.SCISSOR_TEST); + } + + // set the uniforms.. + filter.uniforms.uSampler = input; + filter.uniforms.filterGlobals = this.globalUniforms; + + // TODO make it so that the order of this does not matter.. + // because it does at the moment cos of global uniforms. + // they need to get resynced + + renderer.state.setState(filter.state); + renderer.shader.bind(filter); + + if (filter.legacy) + { + this.quadUv.map(input._frame, input.filterFrame); + + renderer.geometry.bind(this.quadUv); + renderer.geometry.draw(DRAW_MODES.TRIANGLES); + } + else + { + renderer.geometry.bind(this.quad); + renderer.geometry.draw(DRAW_MODES.TRIANGLE_STRIP); + } + } + + /** + * Calculates the mapped matrix. + * + * TODO playing around here.. this is temporary - (will end up in the shader) + * this returns a matrix that will normalise map filter cords in the filter to screen space + * + * @param {PIXI.Matrix} outputMatrix - the matrix to output to. + * @return {PIXI.Matrix} The mapped matrix. + */ + calculateScreenSpaceMatrix(outputMatrix) + { + const currentState = this.activeState; + + return filterTransforms.calculateScreenSpaceMatrix( + outputMatrix, + currentState.sourceFrame, + currentState.destinationFrame + ); + } + + /** + * This will map the filter coord so that a texture can be used based on the transform of a sprite + * + * @param {PIXI.Matrix} outputMatrix - The matrix to output to. + * @param {PIXI.Sprite} sprite - The sprite to map to. + * @return {PIXI.Matrix} The mapped matrix. + */ + calculateSpriteMatrix(outputMatrix, sprite) + { + const currentState = this.activeState; + + return filterTransforms.calculateSpriteMatrix( + outputMatrix, + currentState.sourceFrame, + currentState.destinationFrame, + sprite + ); + } + + /** + * Destroys this Filter System. + * + * @param {boolean} [contextLost=false] context was lost, do not free shaders + * + */ + destroy(contextLost = false) + { + if (!contextLost) + { + this.emptyPool(); + } + else + { + this.texturePool = {}; + } + } + + /** + * Gets a Power-of-Two render texture. + * + * TODO move to a seperate class could be on renderer? + * also - could cause issue with multiple contexts? + * + * @private + * @param {WebGLRenderingContext} gl - The webgl rendering context + * @param {number} minWidth - The minimum width of the render target. + * @param {number} minHeight - The minimum height of the render target. + * @param {number} resolution - The resolution of the render target. + * @return {PIXI.RenderTarget} The new render target. + */ + getPotFilterTexture(minWidth, minHeight, resolution) + { + minWidth = bitTwiddle.nextPow2(minWidth); + minHeight = bitTwiddle.nextPow2(minHeight); + resolution = resolution || 1; + + const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + + if (!this.texturePool[key]) + { + this.texturePool[key] = []; + } + + let renderTexture = this.texturePool[key].pop(); + + if (!renderTexture) + { + // temporary bypass cache.. + // internally - this will cause a texture to be bound.. + renderTexture = RenderTexture.create({ + width: minWidth, + height: minHeight, + resolution, + }); + } + + return renderTexture; + } + + /** + * Gets extra render texture to use inside current filter + * + * @param {number} resolution resolution of the renderTexture + * @returns {PIXI.RenderTexture} + */ + getFilterTexture(resolution) + { + const rt = this.activeState.renderTexture; + + const filterTexture = this.getPotFilterTexture(rt.width, rt.height, resolution || rt.baseTexture.resolution); + + filterTexture.filterFrame = rt.filterFrame; + + return filterTexture; + } + + /** + * Frees a render texture back into the pool. + * + * @param {PIXI.RenderTarget} renderTexture - The renderTarget to free + */ + returnFilterTexture(renderTexture) + { + renderTexture.filterFrame = null; + + const base = renderTexture.baseTexture; + + const minWidth = base.width; + const minHeight = base.height; + + const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + + this.texturePool[key].push(renderTexture); + } + + /** + * Empties the texture pool. + * + */ + emptyPool() + { + for (const i in this.texturePool) + { + const textures = this.texturePool[i]; + + if (textures) + { + for (let j = 0; j < textures.length; j++) + { + textures[j].destroy(true); + } + } + } + + this.texturePool = {}; + } +} diff --git a/packages/core/src/filters/defaultFilter.frag b/packages/core/src/filters/defaultFilter.frag new file mode 100644 index 0000000..a14c1be --- /dev/null +++ b/packages/core/src/filters/defaultFilter.frag @@ -0,0 +1,22 @@ +varying vec2 vTextureCoord; +varying vec2 vFilterCoord; + +uniform sampler2D uSampler; +uniform sampler2D filterSampler; + +void main(void){ + vec4 masky = texture2D(filterSampler, vFilterCoord); + vec4 sample = texture2D(uSampler, vTextureCoord); + vec4 color; + if(mod(vFilterCoord.x, 1.0) > 0.5) + { + color = vec4(1.0, 0.0, 0.0, 1.0); + } + else + { + color = vec4(0.0, 1.0, 0.0, 1.0); + } + // gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0); + gl_FragColor = mix(sample, masky, 0.5); + gl_FragColor *= sample.a; +} diff --git a/packages/core/src/filters/defaultFilter.vert b/packages/core/src/filters/defaultFilter.vert new file mode 100644 index 0000000..e1febca --- /dev/null +++ b/packages/core/src/filters/defaultFilter.vert @@ -0,0 +1,14 @@ +attribute vec2 aVertexPosition; +attribute vec2 aTextureCoord; + +uniform mat3 projectionMatrix; +uniform mat3 filterMatrix; + +varying vec2 vTextureCoord; +varying vec2 vFilterCoord; + +void main(void){ + gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); + vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy; + vTextureCoord = aTextureCoord ; +} diff --git a/packages/core/src/filters/filterTransforms.js b/packages/core/src/filters/filterTransforms.js new file mode 100644 index 0000000..87a3cd6 --- /dev/null +++ b/packages/core/src/filters/filterTransforms.js @@ -0,0 +1,50 @@ +import { Matrix } from '@pixi/math'; + +/** + * Calculates the mapped matrix + * @param filterArea {Rectangle} The filter area + * @param sprite {Sprite} the target sprite + * @param outputMatrix {Matrix} @alvin + * @private + */ +// this returns a matrix that will normalise map filter cords in the filter to screen space +export function calculateScreenSpaceMatrix(outputMatrix, filterArea, textureSize) +{ + // TODO unwrap? + const mappedMatrix = outputMatrix.identity(); + + mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); + + mappedMatrix.scale(textureSize.width, textureSize.height); + + return mappedMatrix; +} + +export function calculateNormalizedScreenSpaceMatrix(outputMatrix, filterArea, textureSize) +{ + const mappedMatrix = outputMatrix.identity(); + + mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); + + const translateScaleX = (textureSize.width / filterArea.width); + const translateScaleY = (textureSize.height / filterArea.height); + + mappedMatrix.scale(translateScaleX, translateScaleY); + + return mappedMatrix; +} + +// this will map the filter coord so that a texture can be used based on the transform of a sprite +export function calculateSpriteMatrix(outputMatrix, filterArea, textureSize, sprite) +{ + const orig = sprite._texture.orig; + const mappedMatrix = outputMatrix.set(textureSize.width, 0, 0, textureSize.height, filterArea.x, filterArea.y); + const worldTransform = sprite.worldTransform.copyTo(Matrix.TEMP_MATRIX); + + worldTransform.invert(); + mappedMatrix.prepend(worldTransform); + mappedMatrix.scale(1.0 / orig.width, 1.0 / orig.height); + mappedMatrix.translate(sprite.anchor.x, sprite.anchor.y); + + return mappedMatrix; +} diff --git a/packages/core/src/filters/spriteMask/SpriteMaskFilter.js b/packages/core/src/filters/spriteMask/SpriteMaskFilter.js new file mode 100644 index 0000000..91697b7 --- /dev/null +++ b/packages/core/src/filters/spriteMask/SpriteMaskFilter.js @@ -0,0 +1,63 @@ +import Filter from '../Filter'; +import { Matrix } from '@pixi/math'; +import vertex from './spriteMaskFilter.vert'; +import fragment from './spriteMaskFilter.frag'; +import { default as TextureMatrix } from '../../textures/TextureMatrix'; + +/** + * The SpriteMaskFilter class + * + * @class + * @extends PIXI.Filter + * @memberof PIXI + */ +export default class SpriteMaskFilter extends Filter +{ + /** + * @param {PIXI.Sprite} sprite - the target sprite + */ + constructor(sprite) + { + const maskMatrix = new Matrix(); + + super(vertex, fragment); + + sprite.renderable = false; + + this.maskSprite = sprite; + this.maskMatrix = maskMatrix; + } + + /** + * Applies the filter + * + * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + */ + apply(filterManager, input, output) + { + const maskSprite = this.maskSprite; + const tex = this.maskSprite.texture; + + if (!tex.valid) + { + return; + } + if (!tex.transform) + { + // margin = 0.0, let it bleed a bit, shader code becomes easier + // assuming that atlas textures were made with 1-pixel padding + tex.transform = new TextureMatrix(tex, 0.0); + } + tex.transform.update(); + + this.uniforms.mask = tex; + this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite) + .prepend(tex.transform.mapCoord); + this.uniforms.alpha = maskSprite.worldAlpha; + this.uniforms.maskClamp = tex.transform.uClampFrame; + + filterManager.applyFilter(this, input, output); + } +} diff --git a/packages/core/src/filters/spriteMask/spriteMaskFilter.frag b/packages/core/src/filters/spriteMask/spriteMaskFilter.frag new file mode 100644 index 0000000..0e4aef8 --- /dev/null +++ b/packages/core/src/filters/spriteMask/spriteMaskFilter.frag @@ -0,0 +1,23 @@ +varying vec2 vMaskCoord; +varying vec2 vTextureCoord; + +uniform sampler2D uSampler; +uniform sampler2D mask; +uniform float alpha; +uniform vec4 maskClamp; + +void main(void) +{ + float clip = step(3.5, + step(maskClamp.x, vMaskCoord.x) + + step(maskClamp.y, vMaskCoord.y) + + step(vMaskCoord.x, maskClamp.z) + + step(vMaskCoord.y, maskClamp.w)); + + vec4 original = texture2D(uSampler, vTextureCoord); + vec4 masky = texture2D(mask, vMaskCoord); + + original *= (masky.r * masky.a * alpha * clip); + + gl_FragColor = original; +} diff --git a/packages/core/src/filters/spriteMask/spriteMaskFilter.vert b/packages/core/src/filters/spriteMask/spriteMaskFilter.vert new file mode 100644 index 0000000..d23a30c --- /dev/null +++ b/packages/core/src/filters/spriteMask/spriteMaskFilter.vert @@ -0,0 +1,16 @@ +attribute vec2 aVertexPosition; +attribute vec2 aTextureCoord; + +uniform mat3 projectionMatrix; +uniform mat3 otherMatrix; + +varying vec2 vMaskCoord; +varying vec2 vTextureCoord; + +void main(void) +{ + gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); + + vTextureCoord = aTextureCoord; + vMaskCoord = ( otherMatrix * vec3( aTextureCoord, 1.0) ).xy; +} diff --git a/packages/core/src/framebuffer/FrameBuffer.js b/packages/core/src/framebuffer/FrameBuffer.js new file mode 100644 index 0000000..84e9fcc --- /dev/null +++ b/packages/core/src/framebuffer/FrameBuffer.js @@ -0,0 +1,106 @@ +import Texture from '../textures/BaseTexture'; +import { FORMATS, TYPES } from '@pixi/constants'; + +/** + * Frame buffer + * @class + * @memberof PIXI + */ +export default class FrameBuffer +{ + constructor(width, height) + { + this.width = width || 100; + this.height = height || 100; + + this.stencil = false; + this.depth = false; + + this.dirtyId = 0; + this.dirtyFormat = 0; + this.dirtySize = 0; + + this.depthTexture = null; + this.colorTextures = []; + + this.glFrameBuffers = {}; + } + + get colorTexture() + { + return this.colorTextures[0]; + } + + addColorTexture(index, texture) + { + // TODO add some validation to the texture - same width / height etc? + this.colorTextures[index || 0] = texture || new Texture(null, { scaleMode: 0, + resolution: 1, + mipmap: false, + width: this.width, + height: this.height });// || new Texture(); + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + addDepthTexture(texture) + { + /* eslint-disable max-len */ + this.depthTexture = texture || new Texture(null, { scaleMode: 0, + resolution: 1, + width: this.width, + height: this.height, + mipmap: false, + format: FORMATS.DEPTH_COMPONENT, + type: TYPES.UNSIGNED_SHORT });// UNSIGNED_SHORT; + /* eslint-disable max-len */ + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + enableDepth() + { + this.depth = true; + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + enableStencil() + { + this.stencil = true; + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + resize(width, height) + { + if (width === this.width && height === this.height) return; + + this.width = width; + this.height = height; + + this.dirtyId++; + this.dirtySize++; + + for (let i = 0; i < this.colorTextures.length; i++) + { + this.colorTextures[i].setSize(width, height); + } + + if (this.depthTexture) + { + this.depthTexture.setSize(width, height); + } + } +} diff --git a/packages/core/src/framebuffer/FramebufferSystem.js b/packages/core/src/framebuffer/FramebufferSystem.js new file mode 100644 index 0000000..2cc2437 --- /dev/null +++ b/packages/core/src/framebuffer/FramebufferSystem.js @@ -0,0 +1,246 @@ +import System from '../System'; +import { Rectangle } from '@pixi/math'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class FramebufferSystem extends System +{ + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + contextChange() + { + this.gl = this.renderer.gl; + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + this.current = null; + this.viewport = new Rectangle(); + + this.drawBufferExtension = this.renderer.context.extensions.drawBuffers; + } + + bind(framebuffer, frame) + { + const gl = this.gl; + + this.current = framebuffer; + + if (framebuffer) + { + // TODO cacheing layer! + + const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID] || this.initFramebuffer(framebuffer); + + gl.bindFramebuffer(gl.FRAMEBUFFER, fbo.framebuffer); + // makesure all textures are unbound.. + + // now check for updates... + if (fbo.dirtyId !== framebuffer.dirtyId) + { + fbo.dirtyId = framebuffer.dirtyId; + + if (fbo.dirtyFormat !== framebuffer.dirtyFormat) + { + fbo.dirtyFormat = framebuffer.dirtyFormat; + this.updateFramebuffer(framebuffer); + } + else if (fbo.dirtySize !== framebuffer.dirtySize) + { + fbo.dirtySize = framebuffer.dirtySize; + this.resizeFramebuffer(framebuffer); + } + } + + for (let i = 0; i < framebuffer.colorTextures.length; i++) + { + if (framebuffer.colorTextures[i].texturePart) + { + this.renderer.texture.unbind(framebuffer.colorTextures[i].texture); + } + else + { + this.renderer.texture.unbind(framebuffer.colorTextures[i]); + } + } + + if (framebuffer.depthTexture) + { + this.renderer.texture.unbind(framebuffer.depthTexture); + } + + if (frame) + { + this.setViewport(frame.x, frame.y, frame.width, frame.height); + } + else + { + this.setViewport(0, 0, framebuffer.width, framebuffer.height); + } + } + else + { + gl.bindFramebuffer(gl.FRAMEBUFFER, null); + + if (frame) + { + this.setViewport(frame.x, frame.y, frame.width, frame.height); + } + else + { + this.setViewport(0, 0, this.renderer.width, this.renderer.height); + } + } + } + + setViewport(x, y, width, height) + { + const v = this.viewport; + + if (v.width !== width || v.height !== height || v.x !== x || v.y !== y) + { + v.x = x; + v.y = y; + v.width = width; + v.height = height; + + this.gl.viewport(x, y, width, height); + } + } + + get size() + { + if (this.current) + { + // TODO store temp + return { x: 0, y: 0, width: this.current.width, height: this.current.height }; + } + + return { x: 0, y: 0, width: this.renderer.width, height: this.renderer.height }; + } + + clear(r, g, b, a) + { + const gl = this.gl; + + // TODO clear color can be set only one right? + gl.clearColor(r, g, b, a); + gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); + } + + // private functions... + + initFramebuffer(framebuffer) + { + const gl = this.gl; + + // TODO - make this a class? + const fbo = { + framebuffer: gl.createFramebuffer(), + stencil: null, + dirtyId: 0, + dirtyFormat: 0, + dirtySize: 0, + }; + + framebuffer.glFrameBuffers[this.CONTEXT_UID] = fbo; + + return fbo; + } + + resizeFramebuffer(framebuffer) + { + const gl = this.gl; + + if (framebuffer.stencil || framebuffer.depth) + { + gl.bindRenderbuffer(gl.RENDERBUFFER, this.stencil); + gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); + } + } + + updateFramebuffer(framebuffer) + { + const gl = this.gl; + + const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID]; + + // bind the color texture + const colorTextures = framebuffer.colorTextures; + + let count = colorTextures.length; + + if (!this.drawBufferExtension) + { + count = Math.min(count, 1); + } + + const activeTextures = []; + + for (let i = 0; i < count; i++) + { + const texture = framebuffer.colorTextures[i]; + + if (texture.texturePart) + { + this.renderer.texture.bind(texture.texture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.COLOR_ATTACHMENT0 + i, + gl.TEXTURE_CUBE_MAP_NEGATIVE_X + texture.side, + texture.texture._glTextures[this.CONTEXT_UID].texture, + 0); + } + else + { + this.renderer.texture.bind(texture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.COLOR_ATTACHMENT0 + i, + gl.TEXTURE_2D, + texture._glTextures[this.CONTEXT_UID].texture, + 0); + } + + activeTextures.push(gl.COLOR_ATTACHMENT0 + i); + } + + if (this.drawBufferExtension && activeTextures.length > 1) + { + this.drawBufferExtension.drawBuffersWEBGL(activeTextures); + } + + if (framebuffer.depthTexture) + { + const depthTextureExt = this.renderer.context.extensions.depthTexture; + + if (depthTextureExt) + { + const depthTexture = framebuffer.depthTexture; + + this.renderer.texture.bind(depthTexture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.DEPTH_ATTACHMENT, + gl.TEXTURE_2D, + depthTexture._glTextures[this.CONTEXT_UID].texture, + 0); + } + } + + if (framebuffer.stencil || framebuffer.depth) + { + fbo.stencil = gl.createRenderbuffer(); + + gl.bindRenderbuffer(gl.RENDERBUFFER, fbo.stencil); + + // TODO.. this is depth AND stencil? + gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, fbo.stencil); + gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); + // fbo.enableStencil(); + } + } +} diff --git a/packages/core/src/geometry/GLBuffer.js b/packages/core/src/geometry/GLBuffer.js new file mode 100644 index 0000000..0477e8a --- /dev/null +++ b/packages/core/src/geometry/GLBuffer.js @@ -0,0 +1,9 @@ +export default class GLBuffer +{ + constructor(buffer) + { + this.buffer = buffer; + this.updateID = -1; + this.byteLength = -1; + } +} diff --git a/packages/core/src/geometry/GeometrySystem.js b/packages/core/src/geometry/GeometrySystem.js new file mode 100644 index 0000000..959f827 --- /dev/null +++ b/packages/core/src/geometry/GeometrySystem.js @@ -0,0 +1,388 @@ +import System from '../System'; +import GLBuffer from './GLBuffer'; + +const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class GeometrySystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this._activeGeometry = null; + this._activeVao = null; + + this.hasVao = true; + this.hasInstance = true; + } + + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + contextChange() + { + const gl = this.gl = this.renderer.gl; + + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + + // webgl2 + if (!gl.createVertexArray) + { + // webgl 1! + let nativeVaoExtension = this.renderer.context.extensions.vertexArrayObject; + + if (this.renderer.legacy) + { + nativeVaoExtension = null; + } + + if (nativeVaoExtension) + { + gl.createVertexArray = () => + nativeVaoExtension.createVertexArrayOES(); + + gl.bindVertexArray = (vao) => + nativeVaoExtension.bindVertexArrayOES(vao); + + gl.deleteVertexArray = (vao) => + nativeVaoExtension.deleteVertexArrayOES(vao); + } + else + { + this.hasVao = false; + gl.createVertexArray = () => + { + // empty + }; + + gl.bindVertexArray = () => + { + // empty + }; + + gl.deleteVertexArray = () => + { + // empty + }; + } + } + + if (!gl.vertexAttribDivisor) + { + const instanceExt = gl.getExtension('ANGLE_instanced_arrays'); + + if (instanceExt) + { + gl.vertexAttribDivisor = (a, b) => + instanceExt.vertexAttribDivisorANGLE(a, b); + + gl.drawElementsInstanced = (a, b, c, d, e) => + instanceExt.drawElementsInstancedANGLE(a, b, c, d, e); + + gl.drawArraysInstanced = (a, b, c, d) => + instanceExt.drawArraysInstancedANGLE(a, b, c, d); + } + else + { + this.hasInstance = false; + } + } + } + + /** + * Binds geometry so that is can be drawn. Creating a Vao if required + * @private + * @param {PIXI.Geometry} geometry instance of geometry to bind + */ + bind(geometry, shader) + { + shader = shader || this.renderer.shader.shader; + + const gl = this.gl; + + // not sure the best way to address this.. + // currently different shaders require different VAOs for the same geometry + // Still mulling over the best way to solve this one.. + // will likely need to modify the shader attribute locations at run time! + let vaos = geometry.glVertexArrayObjects[this.CONTEXT_UID]; + + if (!vaos) + { + geometry.glVertexArrayObjects[this.CONTEXT_UID] = vaos = {}; + } + + const vao = vaos[shader.program.id] || this.initGeometryVao(geometry, shader.program); + + this._activeGeometry = geometry; + + if (this._activeVao !== vao) + { + this._activeVao = vao; + + if (this.hasVao) + { + gl.bindVertexArray(vao); + } + else + { + this.activateVao(geometry, shader.program); + } + } + + // TODO - optimise later! + // don't need to loop through if nothing changed! + // maybe look to add an 'autoupdate' to geometry? + this.updateBuffers(); + } + + reset() + { + this.unbind(); + } + + updateBuffers() + { + const geometry = this._activeGeometry; + const gl = this.gl; + + for (let i = 0; i < geometry.buffers.length; i++) + { + const buffer = geometry.buffers[i]; + + const glBuffer = buffer._glBuffers[this.CONTEXT_UID]; + + if (buffer._updateID !== glBuffer.updateID) + { + glBuffer.updateID = buffer._updateID; + + // TODO can cache this on buffer! maybe added a getter / setter? + const type = buffer.index ? gl.ELEMENT_ARRAY_BUFFER : gl.ARRAY_BUFFER; + const drawType = buffer.static ? gl.STATIC_DRAW : gl.DYNAMIC_DRAW; + + gl.bindBuffer(type, glBuffer.buffer); + + if (glBuffer.byteLength >= buffer.data.byteLength) + { + // offset is always zero for now! + gl.bufferSubData(type, 0, buffer.data); + } + else + { + gl.bufferData(type, buffer.data, drawType); + } + } + } + } + + checkCompatability(geometry, program) + { + // geometry must have at least all the attributes that the shader requires. + const geometryAttributes = geometry.attributes; + const shaderAttributes = program.attributeData; + + for (const j in shaderAttributes) + { + if (!geometryAttributes[j]) + { + throw new Error(`shader and geometry incompatible, geometry missing the "${j}" attribute`); + } + } + } + + /** + * Creates a Vao with the same structure as the geometry and stores it on the geometry. + * @private + * @param {PIXI.Geometry} geometry instance of geometry to to generate Vao for + */ + initGeometryVao(geometry, program) + { + this.checkCompatability(geometry, program); + + const gl = this.gl; + const CONTEXT_UID = this.CONTEXT_UID; + const buffers = geometry.buffers; + const attributes = geometry.attributes; + + const tempStride = {}; + const tempStart = {}; + + for (const j in buffers) + { + tempStride[j] = 0; + tempStart[j] = 0; + } + + for (const j in attributes) + { + if (!attributes[j].size && program.attributeData[j]) + { + attributes[j].size = program.attributeData[j].size; + } + + tempStride[attributes[j].buffer] += attributes[j].size * byteSizeMap[attributes[j].type]; + } + + for (const j in attributes) + { + const attribute = attributes[j]; + const attribSize = attribute.size; + + if (attribute.stride === undefined) + { + if (tempStride[attribute.buffer] === attribSize * byteSizeMap[attribute.type]) + { + attribute.stride = 0; + } + else + { + attribute.stride = tempStride[attribute.buffer]; + } + } + + if (attribute.start === undefined) + { + attribute.start = tempStart[attribute.buffer]; + + tempStart[attribute.buffer] += attribSize * byteSizeMap[attribute.type]; + } + } + + // first update - and create the buffers! + // only create a gl buffer if it actually gets + for (let i = 0; i < buffers.length; i++) + { + const buffer = buffers[i]; + + if (!buffer._glBuffers[CONTEXT_UID]) + { + buffer._glBuffers[CONTEXT_UID] = new GLBuffer(gl.createBuffer()); + } + } + + // TODO - maybe make this a data object? + // lets wait to see if we need to first! + const vao = gl.createVertexArray(); + + gl.bindVertexArray(vao); + + this.activateVao(geometry, program); + + gl.bindVertexArray(null); + + // add it to the cache! + geometry.glVertexArrayObjects[this.CONTEXT_UID][program.id] = vao; + + return vao; + } + + activateVao(geometry, program) + { + const gl = this.gl; + const CONTEXT_UID = this.CONTEXT_UID; + const buffers = geometry.buffers; + const attributes = geometry.attributes; + + if (geometry.indexBuffer) + { + // first update the index buffer if we have one.. + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, geometry.indexBuffer._glBuffers[CONTEXT_UID].buffer); + } + + let lastBuffer = null; + + // add a new one! + for (const j in attributes) + { + const attribute = attributes[j]; + const buffer = buffers[attribute.buffer]; + const glBuffer = buffer._glBuffers[CONTEXT_UID]; + + if (program.attributeData[j]) + { + if (lastBuffer !== glBuffer) + { + gl.bindBuffer(gl.ARRAY_BUFFER, glBuffer.buffer); + + lastBuffer = glBuffer; + } + + const location = program.attributeData[j].location; + + // TODO introduce state again + // we can optimise this for older devices that have no VAOs + gl.enableVertexAttribArray(location); + + gl.vertexAttribPointer(location, + attribute.size, + attribute.type || gl.FLOAT, + attribute.normalized, + attribute.stride, + attribute.start); + + if (attribute.instance) + { + // TODO calculate instance count based of this... + if (this.hasInstance) + { + gl.vertexAttribDivisor(location, 1); + } + else + { + throw new Error('geometry error, GPU Instancing is not supported on this device'); + } + } + } + } + } + + draw(type, size, start, instanceCount) + { + const gl = this.gl; + const geometry = this._activeGeometry; + + // TODO.. this should not change so maybe cache the function? + + if (geometry.indexBuffer) + { + if (geometry.instanced) + { + /* eslint-disable max-len */ + gl.drawElementsInstanced(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2, instanceCount || 1); + /* eslint-enable max-len */ + } + else + { + gl.drawElements(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2); + } + } + else + if (geometry.instanced) + { + // TODO need a better way to calculate size.. + gl.drawArraysInstanced(type, start, size || geometry.getSize(), instanceCount || 1); + } + else + { + gl.drawArrays(type, start, size || geometry.getSize()); + } + + return this; + } + + unbind() + { + this.gl.bindVertexArray(null); + this._activeVao = null; + this._activeGeometry = null; + } +} diff --git a/packages/core/src/geometry/utils/setVertexAttribArrays.js b/packages/core/src/geometry/utils/setVertexAttribArrays.js new file mode 100644 index 0000000..23b6959 --- /dev/null +++ b/packages/core/src/geometry/utils/setVertexAttribArrays.js @@ -0,0 +1,54 @@ +// var GL_MAP = {}; + +/** + * @param gl {WebGLRenderingContext} The current WebGL context + * @param attribs {*} + * @param state {*} + */ +export default function setVertexAttribArrays(gl, attribs, state) +{ + let i; + + if (state) + { + const tempAttribState = state.tempAttribState; + const attribState = state.attribState; + + for (i = 0; i < tempAttribState.length; i++) + { + tempAttribState[i] = false; + } + + // set the new attribs + for (i = 0; i < attribs.length; i++) + { + tempAttribState[attribs[i].attribute.location] = true; + } + + for (i = 0; i < attribState.length; i++) + { + if (attribState[i] !== tempAttribState[i]) + { + attribState[i] = tempAttribState[i]; + + if (state.attribState[i]) + { + gl.enableVertexAttribArray(i); + } + else + { + gl.disableVertexAttribArray(i); + } + } + } + } + else + { + for (i = 0; i < attribs.length; i++) + { + const attrib = attribs[i]; + + gl.enableVertexAttribArray(attrib.attribute.location); + } + } +} diff --git a/packages/core/src/index.js b/packages/core/src/index.js index 58c456a..fd1f547 100644 --- a/packages/core/src/index.js +++ b/packages/core/src/index.js @@ -1,30 +1,30 @@ import * as resources from './textures/resources'; -import * as systems from './renderers/systems'; +import * as systems from './systems'; -export { resources }; export { systems }; +export { resources }; -export { default as Renderer } from './renderers/Renderer'; -export { default as AbstractRenderer } from './renderers/AbstractRenderer'; -export { default as FrameBuffer } from './textures/FrameBuffer'; +export { default as System } from './System'; +export { default as Renderer } from './Renderer'; +export { default as AbstractRenderer } from './AbstractRenderer'; +export { default as FrameBuffer } from './framebuffer/FrameBuffer'; export { default as CubeTexture } from './textures/CubeTexture'; export { default as BaseTexture } from './textures/BaseTexture'; export { default as Texture } from './textures/Texture'; export { default as TextureMatrix } from './textures/TextureMatrix'; -export { default as RenderTexture } from './textures/RenderTexture'; -export { default as BaseRenderTexture } from './textures/BaseRenderTexture'; +export { default as RenderTexture } from './renderTexture/RenderTexture'; +export { default as BaseRenderTexture } from './renderTexture/BaseRenderTexture'; export { default as TextureUvs } from './textures/TextureUvs'; -export { default as State } from './renderers/State'; -export { default as ObjectRenderer } from './renderers/utils/ObjectRenderer'; -export { default as Quad } from './renderers/utils/Quad'; -export { default as QuadUv } from './renderers/utils/QuadUv'; -export { default as checkMaxIfStatmentsInShader } from './renderers/utils/checkMaxIfStatmentsInShader'; +export { default as State } from './state/State'; +export { default as ObjectRenderer } from './batch/ObjectRenderer'; +export { default as Quad } from './utils/Quad'; +export { default as QuadUv } from './utils/QuadUv'; +export { default as checkMaxIfStatmentsInShader } from './shader/utils/checkMaxIfStatmentsInShader'; export { default as Shader } from './shader/Shader'; export { default as Program } from './shader/Program'; export { default as UniformGroup } from './shader/UniformGroup'; -export { default as SpriteMaskFilter } from './renderers/filters/spriteMask/SpriteMaskFilter'; -export { default as Filter } from './renderers/filters/Filter'; +export { default as SpriteMaskFilter } from './filters/spriteMask/SpriteMaskFilter'; +export { default as Filter } from './filters/Filter'; export { default as Attribute } from './geometry/Attribute'; export { default as Buffer } from './geometry/Buffer'; export { default as Geometry } from './geometry/Geometry'; - diff --git a/packages/core/src/mask/MaskSystem.js b/packages/core/src/mask/MaskSystem.js new file mode 100644 index 0000000..99174c2 --- /dev/null +++ b/packages/core/src/mask/MaskSystem.js @@ -0,0 +1,199 @@ +import System from '../System'; +import AlphaMaskFilter from '../filters/spriteMask/SpriteMaskFilter'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class MaskSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + // TODO - we don't need both! + this.scissor = false; + this.scissorData = null; + this.scissorRenderTarget = null; + + this.enableScissor = false; + + this.alphaMaskPool = []; + this.alphaMaskIndex = 0; + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.DisplayObject} target - Display Object to push the mask to + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + push(target, maskData) + { + // TODO the root check means scissor rect will not + // be used on render textures more info here: + // https://github.com/pixijs/pixi.js/pull/3545 + + if (maskData.texture) + { + this.pushSpriteMask(target, maskData); + } + else if (this.enableScissor + && !this.scissor + && this.renderer._activeRenderTarget.root + && !this.renderer.stencil.stencilMaskStack.length + && maskData.isFastRect()) + { + const matrix = maskData.worldTransform; + + let rot = Math.atan2(matrix.b, matrix.a); + + // use the nearest degree! + rot = Math.round(rot * (180 / Math.PI)); + + if (rot % 90) + { + this.pushStencilMask(maskData); + } + else + { + this.pushScissorMask(target, maskData); + } + } + else + { + this.pushStencilMask(maskData); + } + } + + /** + * Removes the last mask from the mask stack and doesn't return it. + * + * @param {PIXI.DisplayObject} target - Display Object to pop the mask from + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + pop(target, maskData) + { + if (maskData.texture) + { + this.popSpriteMask(target, maskData); + } + else if (this.enableScissor && !this.renderer.stencil.stencilMaskStack.length) + { + this.popScissorMask(target, maskData); + } + else + { + this.popStencilMask(target, maskData); + } + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.RenderTarget} target - Display Object to push the sprite mask to + * @param {PIXI.Sprite} maskData - Sprite to be used as the mask + */ + pushSpriteMask(target, maskData) + { + let alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; + + if (!alphaMaskFilter) + { + alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; + } + + alphaMaskFilter[0].resolution = this.renderer.resolution; + alphaMaskFilter[0].maskSprite = maskData; + + // TODO - may cause issues! + target.filterArea = maskData.getBounds(true); + + this.renderer.filterSystem.pushFilter(target, alphaMaskFilter); + + this.alphaMaskIndex++; + } + + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popSpriteMask() + { + this.renderer.filterSystem.popFilter(); + this.alphaMaskIndex--; + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + pushStencilMask(maskData) + { + this.renderer.batch.flush(); + this.renderer.stencil.pushStencil(maskData); + } + + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popStencilMask() + { + // this.renderer.currentRenderer.stop(); + this.renderer.stencil.popStencil(); + } + + /** + * + * @param {PIXI.DisplayObject} target - Display Object to push the mask to + * @param {PIXI.Graphics} maskData - The masking data. + */ + pushScissorMask(target, maskData) + { + maskData.renderable = true; + + const renderTarget = this.renderer._activeRenderTarget; + + const bounds = maskData.getBounds(); + + bounds.fit(renderTarget.size); + maskData.renderable = false; + + this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); + + const resolution = this.renderer.resolution; + + this.renderer.gl.scissor( + bounds.x * resolution, + (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, + bounds.width * resolution, + bounds.height * resolution + ); + + this.scissorRenderTarget = renderTarget; + this.scissorData = maskData; + this.scissor = true; + } + + /** + * + * + */ + popScissorMask() + { + this.scissorRenderTarget = null; + this.scissorData = null; + this.scissor = false; + + // must be scissor! + const gl = this.renderer.gl; + + gl.disable(gl.SCISSOR_TEST); + } +} diff --git a/packages/core/src/mask/StencilSystem.js b/packages/core/src/mask/StencilSystem.js new file mode 100644 index 0000000..62796a5 --- /dev/null +++ b/packages/core/src/mask/StencilSystem.js @@ -0,0 +1,133 @@ +import System from '../System'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class StencilSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + this.stencilMaskStack = []; + } + + /** + * Changes the mask stack that is used by this System. + * + * @param {PIXI.Graphics[]} stencilMaskStack - The mask stack + */ + setMaskStack(stencilMaskStack) + { + const gl = this.renderer.gl; + + if (stencilMaskStack.length !== this.stencilMaskStack.length) + { + if (stencilMaskStack.length === 0) + { + gl.disable(gl.STENCIL_TEST); + } + else + { + gl.enable(gl.STENCIL_TEST); + } + } + + this.stencilMaskStack = stencilMaskStack; + } + + /** + * Applies the Mask and adds it to the current stencil stack. @alvin + * + * @param {PIXI.Graphics} graphics - The mask + */ + pushStencil(graphics) + { + this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); + + // this.renderer._activeRenderTarget.attachStencilBuffer(); + + const gl = this.renderer.gl; + const prevMaskCount = this.stencilMaskStack.length; + + if (prevMaskCount === 0) + { + gl.enable(gl.STENCIL_TEST); + } + + this.stencilMaskStack.push(graphics); + + // Increment the refference stencil value where the new mask overlaps with the old ones. + gl.colorMask(false, false, false, false); + gl.stencilFunc(gl.EQUAL, prevMaskCount, this._getBitwiseMask()); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.INCR); + this.renderer.plugins.graphics.render(graphics); + + this._useCurrent(); + } + + /** + * Removes the last mask from the stencil stack. @alvin + */ + popStencil() + { + this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); + + const gl = this.renderer.gl; + const graphics = this.stencilMaskStack.pop(); + + if (this.stencilMaskStack.length === 0) + { + // the stack is empty! + gl.disable(gl.STENCIL_TEST); + gl.clear(gl.STENCIL_BUFFER_BIT); + gl.clearStencil(0); + } + else + { + // Decrement the refference stencil value where the popped mask overlaps with the other ones + gl.colorMask(false, false, false, false); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.DECR); + this.renderer.plugins.graphics.render(graphics); + + this._useCurrent(); + } + } + + /** + * Setup renderer to use the current stencil data. + */ + _useCurrent() + { + const gl = this.renderer.gl; + + gl.colorMask(true, true, true, true); + gl.stencilFunc(gl.EQUAL, this.stencilMaskStack.length, this._getBitwiseMask()); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP); + } + + /** + * Fill 1s equal to the number of acitve stencil masks. + * + * @return {number} The bitwise mask. + */ + _getBitwiseMask() + { + return (1 << this.stencilMaskStack.length) - 1; + } + + /** + * Destroys the mask stack. + * + */ + destroy() + { + super.destroy(this); + + this.stencilMaskStack = null; + } +} diff --git a/packages/core/src/projection/ProjectionSystem.js b/packages/core/src/projection/ProjectionSystem.js new file mode 100644 index 0000000..6e63b73 --- /dev/null +++ b/packages/core/src/projection/ProjectionSystem.js @@ -0,0 +1,73 @@ +import System from '../System'; +import { Matrix } from '@pixi/math'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class ProjectionSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.projectionMatrix = new Matrix(); + } + + update(destinationFrame, sourceFrame, resolution, root) + { + this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; + this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; + + this.calculateProjection(this.destinationFrame, this.sourceFrame, resolution, root); + + this.renderer.globalUniforms.uniforms.projectionMatrix = this.projectionMatrix; + this.renderer.globalUniforms.update(); + } + + /** + * Updates the projection matrix based on a projection frame (which is a rectangle) + * + * @param {Rectangle} destinationFrame - The destination frame. + * @param {Rectangle} sourceFrame - The source frame. + */ + calculateProjection(destinationFrame, sourceFrame, resolution, root) + { + const pm = this.projectionMatrix; + + // I don't think we will need this line.. + // pm.identity(); + + if (!root) + { + pm.a = (1 / destinationFrame.width * 2) * resolution; + pm.d = (1 / destinationFrame.height * 2) * resolution; + + pm.tx = -1 - (sourceFrame.x * pm.a); + pm.ty = -1 - (sourceFrame.y * pm.d); + } + else + { + pm.a = (1 / destinationFrame.width * 2) * resolution; + pm.d = (-1 / destinationFrame.height * 2) * resolution; + + pm.tx = -1 - (sourceFrame.x * pm.a); + pm.ty = 1 - (sourceFrame.y * pm.d); + } + } + + /** + * Sets the transform of the active render target to the given matrix + * + * @param {PIXI.Matrix} matrix - The transformation matrix + */ + setTransform()// matrix) + { + // this._activeRenderTarget.transform = matrix; + } +} diff --git a/packages/core/src/renderTexture/BaseRenderTexture.js b/packages/core/src/renderTexture/BaseRenderTexture.js new file mode 100644 index 0000000..88eb83b --- /dev/null +++ b/packages/core/src/renderTexture/BaseRenderTexture.js @@ -0,0 +1,137 @@ +import BaseTexture from '../textures/BaseTexture'; +import FrameBuffer from '../framebuffer/FrameBuffer'; + +/** + * A BaseRenderTexture is a special texture that allows any PixiJS display object to be rendered to it. + * + * __Hint__: All DisplayObjects (i.e. Sprites) that render to a BaseRenderTexture should be preloaded + * otherwise black rectangles will be drawn instead. + * + * A BaseRenderTexture takes a snapshot of any Display Object given to its render method. The position + * and rotation of the given Display Objects is ignored. For example: + * + * ```js + * let renderer = PIXI.autoDetectRenderer(1024, 1024, { view: canvas, ratio: 1 }); + * let baseRenderTexture = new PIXI.BaseRenderTexture(renderer, 800, 600); + * let sprite = PIXI.Sprite.fromImage("spinObj_01.png"); + * + * sprite.position.x = 800/2; + * sprite.position.y = 600/2; + * sprite.anchor.x = 0.5; + * sprite.anchor.y = 0.5; + * + * baseRenderTexture.render(sprite); + * ``` + * + * The Sprite in this case will be rendered using its local transform. To render this sprite at 0,0 + * you can clear the transform + * + * ```js + * + * sprite.setTransform() + * + * let baseRenderTexture = new PIXI.BaseRenderTexture(100, 100); + * let renderTexture = new PIXI.RenderTexture(baseRenderTexture); + * + * renderer.render(sprite, renderTexture); // Renders to center of RenderTexture + * ``` + * + * @class + * @extends PIXI.BaseTexture + * @memberof PIXI + */ +export default class BaseRenderTexture extends BaseTexture +{ + /** + * @param {object} [options] + * @param {number} [options.width=100] - The width of the base render texture + * @param {number} [options.height=100] - The height of the base render texture + * @param {PIXI.SCALE_MODES} [options.scaleMode] - See {@link PIXI.SCALE_MODES} for possible values + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the texture being generated + */ + constructor(options) + { + if (typeof options === 'number') + { + /* eslint-disable prefer-rest-params */ + // Backward compatibility of signature + const width = arguments[0]; + const height = arguments[1]; + const scaleMode = arguments[2]; + const resolution = arguments[3]; + + options = { width, height, scaleMode, resolution }; + /* eslint-enable prefer-rest-params */ + } + + super(null, options); + + const { width, height } = options || {}; + + // Set defaults + this.mipmap = false; + this.width = Math.ceil(width) || 100; + this.height = Math.ceil(height) || 100; + this.valid = true; + + /** + * A map of renderer IDs to webgl renderTargets + * + * @private + * @member {object} + */ + // this._glRenderTargets = {}; + + /** + * A reference to the canvas render target (we only need one as this can be shared across renderers) + * + * @private + * @member {object} + */ + this._canvasRenderTarget = null; + + this.clearColor = [0, 0, 0, 0]; + + this.frameBuffer = new FrameBuffer(width * this.resolution, height * this.resolution) + .addColorTexture(0, this); + + // TODO - could this be added the systems? + + /** + * The data structure for the stencil masks + * + * @member {PIXI.Graphics[]} + */ + this.stencilMaskStack = []; + + /** + * The data structure for the filters + * + * @member {PIXI.Graphics[]} + */ + this.filterStack = [{}]; + } + + /** + * Resizes the BaseRenderTexture. + * + * @param {number} width - The width to resize to. + * @param {number} height - The height to resize to. + */ + resize(width, height) + { + width = Math.ceil(width); + height = Math.ceil(height); + this.frameBuffer.resize(width * this.resolution, height * this.resolution); + } + + /** + * Destroys this texture + * + */ + destroy() + { + super.destroy(true); + this.renderer = null; + } +} diff --git a/packages/core/src/renderTexture/RenderTexture.js b/packages/core/src/renderTexture/RenderTexture.js new file mode 100644 index 0000000..f1ecf1f --- /dev/null +++ b/packages/core/src/renderTexture/RenderTexture.js @@ -0,0 +1,153 @@ +import BaseRenderTexture from './BaseRenderTexture'; +import Texture from '../textures/Texture'; + +/** + * A RenderTexture is a special texture that allows any PixiJS display object to be rendered to it. + * + * __Hint__: All DisplayObjects (i.e. Sprites) that render to a RenderTexture should be preloaded + * otherwise black rectangles will be drawn instead. + * + * A RenderTexture takes a snapshot of any Display Object given to its render method. For example: + * + * ```js + * let renderer = PIXI.autoDetectRenderer(1024, 1024, { view: canvas, ratio: 1 }); + * let renderTexture = PIXI.RenderTexture.create(800, 600); + * let sprite = PIXI.Sprite.fromImage("spinObj_01.png"); + * + * sprite.position.x = 800/2; + * sprite.position.y = 600/2; + * sprite.anchor.x = 0.5; + * sprite.anchor.y = 0.5; + * + * renderer.render(sprite, renderTexture); + * ``` + * + * The Sprite in this case will be rendered using its local transform. To render this sprite at 0,0 + * you can clear the transform + * + * ```js + * + * sprite.setTransform() + * + * let renderTexture = new PIXI.RenderTexture.create(100, 100); + * + * renderer.render(sprite, renderTexture); // Renders to center of RenderTexture + * ``` + * + * @class + * @extends PIXI.Texture + * @memberof PIXI + */ +export default class RenderTexture extends Texture +{ + /** + * @param {PIXI.BaseRenderTexture} baseRenderTexture - The renderer used for this RenderTexture + * @param {PIXI.Rectangle} [frame] - The rectangle frame of the texture to show + */ + constructor(baseRenderTexture, frame) + { + // support for legacy.. + let _legacyRenderer = null; + + if (!(baseRenderTexture instanceof BaseRenderTexture)) + { + /* eslint-disable prefer-rest-params, no-console */ + const width = arguments[1]; + const height = arguments[2]; + const scaleMode = arguments[3]; + const resolution = arguments[4]; + + // we have an old render texture.. + console.warn(`Please use RenderTexture.create(${width}, ${height}) instead of the ctor directly.`); + _legacyRenderer = arguments[0]; + /* eslint-enable prefer-rest-params, no-console */ + + frame = null; + baseRenderTexture = new BaseRenderTexture({ + width, + height, + scaleMode, + resolution, + }); + } + + /** + * The base texture object that this texture uses + * + * @member {BaseTexture} + */ + super(baseRenderTexture, frame); + + this.legacyRenderer = _legacyRenderer; + + /** + * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. + * + * @member {boolean} + */ + this.valid = true; + + /** + * FilterSystem temporary storage + * @private + * @member {PIXI.Rectangle} + */ + this.filterFrame = null; + + this._updateUvs(); + } + + /** + * Resizes the RenderTexture. + * + * @param {number} width - The width to resize to. + * @param {number} height - The height to resize to. + * @param {boolean} [resizeBaseTexture=true] - Should the baseTexture.width and height values be resized as well? + */ + resize(width, height, resizeBaseTexture = true) + { + width = Math.ceil(width); + height = Math.ceil(height); + + // TODO - could be not required.. + this.valid = (width > 0 && height > 0); + + this._frame.width = this.orig.width = width; + this._frame.height = this.orig.height = height; + + if (resizeBaseTexture) + { + this.baseTexture.resize(width, height); + } + + this._updateUvs(); + } + + /** + * A short hand way of creating a render texture. + * + * @param {object} [options] - Options + * @param {number} [options.width=100] - The width of the render texture + * @param {number} [options.height=100] - The height of the render texture + * @param {number} [options.scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the texture being generated + * @return {PIXI.RenderTexture} The new render texture + */ + static create(options) + { + // fallback, old-style: create(width, height, scaleMode, resolution) + if (typeof options === 'number') + { + /* eslint-disable prefer-rest-params */ + options = { + width: options, + height: arguments[1], + scaleMode: arguments[2], + resolution: arguments[3], + }; + /* eslint-enable prefer-rest-params */ + } + + return new RenderTexture(new BaseRenderTexture(options)); + } +} diff --git a/packages/core/src/renderTexture/RenderTextureSystem.js b/packages/core/src/renderTexture/RenderTextureSystem.js new file mode 100644 index 0000000..8d0ca61 --- /dev/null +++ b/packages/core/src/renderTexture/RenderTextureSystem.js @@ -0,0 +1,115 @@ +import System from '../System'; +import { Rectangle } from '@pixi/math'; + +const tempRect = new Rectangle(); + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class RenderTextureSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.clearColor = renderer._backgroundColorRgba; + + // TODO moe this property somewhere else! + this.defaultMaskStack = []; + this.defaultFilterStack = [{}]; + + // empty render texture? + this.renderTexture = null; + + this.destinationFrame = new Rectangle(); + } + + bind(renderTexture, sourceFrame, destinationFrame) + { + // TODO - do we want this?? + if (this.renderTexture === renderTexture) return; + this.renderTexture = renderTexture; + + const renderer = this.renderer; + + if (renderTexture) + { + const baseTexture = renderTexture.baseTexture; + + if (!destinationFrame) + { + tempRect.width = baseTexture.realWidth; + tempRect.height = baseTexture.realHeight; + + destinationFrame = tempRect; + } + + if (!sourceFrame) + { + sourceFrame = destinationFrame; + } + + this.renderer.framebuffer.bind(baseTexture.frameBuffer, destinationFrame); + + this.renderer.projection.update(destinationFrame, sourceFrame, baseTexture.resolution, false); + this.renderer.stencil.setMaskStack(baseTexture.stencilMaskStack); + } + else + { + // TODO these validation checks happen deeper down.. + // thing they can be avoided.. + if (!destinationFrame) + { + tempRect.width = renderer.width; + tempRect.height = renderer.height; + + destinationFrame = tempRect; + } + + if (!sourceFrame) + { + sourceFrame = destinationFrame; + } + + renderer.framebuffer.bind(null, destinationFrame); + + // TODO store this.. + this.renderer.projection.update(destinationFrame, sourceFrame, this.renderer.resolution, true); + this.renderer.stencil.setMaskStack(this.defaultMaskStack); + } + + this.destinationFrame.copyFrom(destinationFrame); + } + + /** + * Erases the render texture and fills the drawing area with a colour + * + * @param {number} [clearColor] - The colour + * @return {PIXI.Renderer} Returns itself. + */ + clear(clearColor) + { + if (this.renderTexture) + { + clearColor = clearColor || this.renderTexture.baseTexture.clearColor; + } + else + { + clearColor = clearColor || this.clearColor; + } + + this.renderer.framebuffer.clear(clearColor[0], clearColor[1], clearColor[2], clearColor[3]); + } + + resize()// screenWidth, screenHeight) + { + // resize the root only! + this.bind(null); + } +} diff --git a/packages/core/src/renderers/AbstractRenderer.js b/packages/core/src/renderers/AbstractRenderer.js deleted file mode 100644 index 928670f..0000000 --- a/packages/core/src/renderers/AbstractRenderer.js +++ /dev/null @@ -1,353 +0,0 @@ -import { hex2string, hex2rgb } from '@pixi/utils'; -import { Matrix, Rectangle } from '@pixi/math'; -import { RENDERER_TYPE } from '@pixi/constants'; -import { settings } from '@pixi/settings'; -import { Container } from '@pixi/display'; -import RenderTexture from '../textures/RenderTexture'; -import EventEmitter from 'eventemitter3'; - -const tempMatrix = new Matrix(); - -/** - * 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 EventEmitter - * @memberof PIXI - */ -export default class AbstractRenderer extends EventEmitter -{ - // eslint-disable-next-line valid-jsdoc - /** - * @param {string} system - The name of the system this renderer is for. - * @param {object} [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.transparent=false] - If the render view is transparent, default false - * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false - * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) - * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. The - * resolution of the renderer retina would be 2. - * @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 {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when rendering, - * stopping pixel interpolation. - */ - constructor(system, options, arg2, arg3) - { - super(); - - // Support for constructor(system, screenWidth, screenHeight, options) - if (typeof options === 'number') - { - options = Object.assign({ - width: options, - height: arg2 || settings.RENDER_OPTIONS.height, - }, arg3); - } - - // 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 = RENDERER_TYPE.UNKNOWN; - - /** - * Measurements of the screen. (0, 0, screenWidth, screenHeight) - * - * Its safe to use as filterArea or hitArea for 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 1 - */ - this.resolution = options.resolution || settings.RESOLUTION; - - /** - * Whether the render view is transparent - * - * @member {boolean} - */ - this.transparent = options.transparent; - - /** - * Whether css dimensions of canvas view should be resized to screen dimensions automatically - * - * @member {boolean} - */ - this.autoResize = options.autoResize || false; - - /** - * Tracks the blend modes useful for this renderer. - * - * @member {object} - */ - this.blendModes = null; - - /** - * 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; - - /** - * If true PixiJS will Math.floor() x/y values when rendering, stopping pixel interpolation. - * Handy for crisp pixel art and speed on legacy devices. - * - * @member {boolean} - */ - this.roundPixels = options.roundPixels; - - /** - * The background color as a number. - * - * @member {number} - * @private - */ - this._backgroundColor = 0x000000; - - /** - * The background color as an [R, G, B] array. - * - * @member {number[]} - * @private - */ - this._backgroundColorRgba = [0, 0, 0, 0]; - - /** - * The background color as a string. - * - * @member {string} - * @private - */ - this._backgroundColorString = '#000000'; - - this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter - - /** - * This temporary display object used as the parent of the currently being rendered item - * - * @member {PIXI.DisplayObject} - * @private - */ - this._tempDisplayObjectParent = new Container(); - - /** - * The last root object that the renderer tried to render. - * - * @member {PIXI.DisplayObject} - * @private - */ - this._lastObjectRendered = this._tempDisplayObjectParent; - - /** - * Collection of plugins. - * @readonly - * @member {object} - */ - this.plugins = {}; - } - - /** - * Initialize the plugins. - * - * @protected - * @param {object} staticMap - The dictionary of staticly saved plugins. - */ - initPlugins(staticMap) - { - 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() - { - return this.view.width; - } - - /** - * Same as view.height, actual number of pixels in the canvas by vertical - * - * @member {number} - * @readonly - * @default 600 - */ - get height() - { - return this.view.height; - } - - /** - * Resizes the screen and canvas to the specified width and height - * Canvas dimensions are multiplied by resolution - * - * @param {number} screenWidth - the new width of the screen - * @param {number} screenHeight - the new height of the screen - */ - resize(screenWidth, screenHeight) - { - this.screen.width = screenWidth; - this.screen.height = screenHeight; - - this.view.width = screenWidth * this.resolution; - this.view.height = screenHeight * this.resolution; - - if (this.autoResize) - { - this.view.style.width = `${screenWidth}px`; - this.view.style.height = `${screenHeight}px`; - } - } - - /** - * 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 {PIXI.DisplayObject} displayObject - The displayObject the object will be generated from - * @param {number} scaleMode - Should be one of the scaleMode consts - * @param {number} resolution - The resolution / device pixel ratio of the texture being generated - * @param {PIXI.Rectangle} [region] - The region of the displayObject, that shall be rendered, - * if no region is specified, defaults to the local bounds of the displayObject. - * @return {PIXI.Texture} a texture of the graphics object - */ - generateTexture(displayObject, scaleMode, resolution, region) - { - region = region || displayObject.getLocalBounds(); - - // 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(region.width | 0, region.height | 0, scaleMode, resolution); - - tempMatrix.tx = -region.x; - tempMatrix.ty = -region.y; - - this.render(displayObject, renderTexture, false, tempMatrix, true); - - return renderTexture; - } - - /** - * Removes everything from the renderer and optionally removes the Canvas DOM element. - * - * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. - */ - destroy(removeView) - { - 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); - } - - this.plugins = null; - - this.type = RENDERER_TYPE.UNKNOWN; - - this.view = null; - - this.screen = null; - - this.resolution = 0; - - this.transparent = false; - - this.autoResize = false; - - this.blendModes = null; - - this.options = null; - - this.preserveDrawingBuffer = false; - this.clearBeforeRender = false; - - this.roundPixels = false; - - this._backgroundColor = 0; - this._backgroundColorRgba = null; - this._backgroundColorString = null; - - this._tempDisplayObjectParent = null; - this._lastObjectRendered = null; - } - - /** - * The background color to fill if not transparent - * - * @member {number} - */ - get backgroundColor() - { - return this._backgroundColor; - } - - set backgroundColor(value) // eslint-disable-line require-jsdoc - { - this._backgroundColor = value; - this._backgroundColorString = hex2string(value); - hex2rgb(value, this._backgroundColorRgba); - } -} diff --git a/packages/core/src/renderers/Renderer.js b/packages/core/src/renderers/Renderer.js deleted file mode 100644 index a2020b1..0000000 --- a/packages/core/src/renderers/Renderer.js +++ /dev/null @@ -1,329 +0,0 @@ -import AbstractRenderer from './AbstractRenderer'; -import { sayHello } from '@pixi/utils'; -import MaskSystem from './systems/MaskSystem'; -import StencilSystem from './systems/StencilSystem'; -import FilterSystem from './systems/filter/FilterSystem'; -import FramebufferSystem from './systems/FramebufferSystem'; -import RenderTextureSystem from './systems/RenderTextureSystem'; -import TextureSystem from './systems/textures/TextureSystem'; -import ProjectionSystem from './systems/ProjectionSystem'; -import StateSystem from './systems/StateSystem'; -import GeometrySystem from './systems/geometry/GeometrySystem'; -import ShaderSystem from './systems/shader/ShaderSystem'; -import ContextSystem from './systems/ContextSystem'; -import BatchSystem from './systems/BatchSystem'; -import TextureGCSystem from './systems/textures/TextureGCSystem'; -import { RENDERER_TYPE } from '@pixi/constants'; -import UniformGroup from '../shader/UniformGroup'; -import { Matrix } from '@pixi/math'; -import Runner from 'mini-runner'; - -/** - * 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 Render works by automatically managing webGLBatchs. - * 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 :) - * - * @class - * @memberof PIXI - * @extends PIXI.AbstractRenderer - */ -export default class Renderer extends AbstractRenderer -{ - // eslint-disable-next-line valid-jsdoc - /** - * - * @param {object} [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.transparent=false] - If the render view is transparent, default false - * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false - * @param {boolean} [options.antialias=false] - sets antialias. If not available natively then FXAA - * antialiasing is used - * @param {boolean} [options.forceFXAA=false] - forces FXAA antialiasing to be used over native. - * FXAA is faster, but may not always look as great - * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. - * The resolution of the renderer retina would be 2. - * @param {boolean} [options.clearBeforeRender=true] - This sets if the renderer will clear - * the canvas or not before the new render pass. If you wish to set this to false, you *must* set - * preserveDrawingBuffer to `true`. - * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, - * enable this if you need to call toDataUrl on the webgl context. - * @param {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when - * rendering, stopping pixel interpolation. - * @param {number} [options.backgroundColor=0x000000] - The background color of the rendered area - * (shown if not transparent). - * @param {boolean} [options.legacy=false] - If true PixiJS will aim to ensure compatibility - * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. - * @param {string} [options.powerPreference] - Parameter passed to webgl context, set to "high-performance" - * for devices with dual graphics card - */ - constructor(options, arg2, arg3) - { - super('WebGL', options, arg2, arg3); - - /** - * The type of this renderer as a standardised const - * - * @member {number} - * @see PIXI.RENDERER_TYPE - */ - this.type = RENDERER_TYPE.WEBGL; - - // this will be set by the contextSystem (this.context) - this.gl = null; - this.CONTEXT_UID = 0; - this.legacy = !!options.legacy; - - // TODO legacy! - - // runners! - this.runners = { - destroy: new Runner('destroy'), - contextChange: new Runner('contextChange', 1), - reset: new Runner('reset'), - update: new Runner('update'), - postrender: new Runner('postrender'), - prerender: new Runner('prerender'), - resize: new Runner('resize', 2), - }; - - 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(GeometrySystem, 'geometry') - .addSystem(FramebufferSystem, 'framebuffer') - .addSystem(StencilSystem, 'stencil') - .addSystem(ProjectionSystem, 'projection') - .addSystem(TextureGCSystem, 'textureGC') - .addSystem(FilterSystem, 'filter') - .addSystem(RenderTextureSystem, 'renderTexture') - .addSystem(BatchSystem, 'batch'); - - this.initPlugins(Renderer.__plugins); - - /** - * The options passed in to create a new webgl context. - * - * @member {object} - * @private - */ - if (options.context) - { - this.context.initFromContext(options.context); - } - else - { - this.context.initFromOptions({ - alpha: this.transparent, - antialias: options.antialias, - premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', - stencil: true, - 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); - } - - addSystem(_class, name) - { - if (!name) - { - name = _class.name; - } - - // TODO - read name from class.name.. - - /* - if(name.includes('System')) - { - name = name.replace('System', ''); - name = name.charAt(0).toLowerCase() + name.slice(1); - } - */ - - const system = new _class(this); - - if (this[name]) - { - throw new Error(`Whoops! ${name} is already a manger`); - } - - this[name] = system; - - for (const i in this.runners) - { - this.runners[i].add(system); - } - - return this; - - /** - * 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. - */ - } - - /** - * Renders the object to its webGL view - * - * @param {PIXI.DisplayObject} displayObject - the object to be rendered - * @param {PIXI.RenderTexture} renderTexture - The render texture to render to. - * @param {boolean} [clear] - Should the canvas be cleared before the new render - * @param {PIXI.Transform} [transform] - A transform to apply to the render texture before rendering. - * @param {boolean} [skipUpdateTransform] - Should we skip the update transform pass? - */ - render(displayObject, renderTexture, clear, transform, skipUpdateTransform) - { - // can be handy to know! - this.renderingToScreen = !renderTexture; - - this.runners.prerender.run(); - this.emit('prerender'); - - // 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.parent; - - displayObject.parent = this._tempDisplayObjectParent; - displayObject.updateTransform(); - displayObject.parent = 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.run(); - - this.emit('postrender'); - } - - /** - * Resizes the webGL view to the specified width and height. - * - * @param {number} screenWidth - the new width of the screen - * @param {number} screenHeight - the new height of the screen - */ - resize(screenWidth, screenHeight) - { - AbstractRenderer.prototype.resize.call(this, screenWidth, screenHeight); - - this.runners.resize.run(screenWidth, screenHeight); - } - - /** - * Resets the WebGL state so you can render things however you fancy! - * - * @return {PIXI.Renderer} Returns itself. - */ - reset() - { - this.runners.reset.run(); - - return this; - } - - clear() - { - this.framebuffer.bind(); - this.framebuffer.clear(); - } - - /** - * Removes everything from the renderer (event listeners, spritebatch, etc...) - * - * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. - * See: https://github.com/pixijs/pixi.js/issues/2233 - */ - destroy(removeView) - { - this.runners.destroy.run(); - - // call base destroy - super.destroy(removeView); - - // TODO nullify all the managers.. - this.gl = null; - } - - /** - * 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 - * builds and excluding plugins. - * @name PIXI.Renderer#plugins - * @type {object} - * @readonly - * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. - * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. - * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. - * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. - */ - - /** - * Adds a plugin to the renderer. - * - * @method - * @param {string} pluginName - The name of the plugin. - * @param {Function} ctor - The constructor function or class for the plugin. - */ - static registerPlugin(pluginName, ctor) - { - Renderer.__plugins = Renderer.__plugins || {}; - Renderer.__plugins[pluginName] = ctor; - } -} diff --git a/packages/core/src/AbstractRenderer.js b/packages/core/src/AbstractRenderer.js new file mode 100644 index 0000000..64b2eb1 --- /dev/null +++ b/packages/core/src/AbstractRenderer.js @@ -0,0 +1,353 @@ +import { hex2string, hex2rgb } from '@pixi/utils'; +import { Matrix, Rectangle } from '@pixi/math'; +import { RENDERER_TYPE } from '@pixi/constants'; +import { settings } from '@pixi/settings'; +import { Container } from '@pixi/display'; +import RenderTexture from './renderTexture/RenderTexture'; +import EventEmitter from 'eventemitter3'; + +const tempMatrix = new Matrix(); + +/** + * 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 EventEmitter + * @memberof PIXI + */ +export default class AbstractRenderer extends EventEmitter +{ + // eslint-disable-next-line valid-jsdoc + /** + * @param {string} system - The name of the system this renderer is for. + * @param {object} [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.transparent=false] - If the render view is transparent, default false + * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false + * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. The + * resolution of the renderer retina would be 2. + * @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 {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when rendering, + * stopping pixel interpolation. + */ + constructor(system, options, arg2, arg3) + { + super(); + + // Support for constructor(system, screenWidth, screenHeight, options) + if (typeof options === 'number') + { + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + }, arg3); + } + + // 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 = RENDERER_TYPE.UNKNOWN; + + /** + * Measurements of the screen. (0, 0, screenWidth, screenHeight) + * + * Its safe to use as filterArea or hitArea for 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 1 + */ + this.resolution = options.resolution || settings.RESOLUTION; + + /** + * Whether the render view is transparent + * + * @member {boolean} + */ + this.transparent = options.transparent; + + /** + * Whether css dimensions of canvas view should be resized to screen dimensions automatically + * + * @member {boolean} + */ + this.autoResize = options.autoResize || false; + + /** + * Tracks the blend modes useful for this renderer. + * + * @member {object} + */ + this.blendModes = null; + + /** + * 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; + + /** + * If true PixiJS will Math.floor() x/y values when rendering, stopping pixel interpolation. + * Handy for crisp pixel art and speed on legacy devices. + * + * @member {boolean} + */ + this.roundPixels = options.roundPixels; + + /** + * The background color as a number. + * + * @member {number} + * @private + */ + this._backgroundColor = 0x000000; + + /** + * The background color as an [R, G, B] array. + * + * @member {number[]} + * @private + */ + this._backgroundColorRgba = [0, 0, 0, 0]; + + /** + * The background color as a string. + * + * @member {string} + * @private + */ + this._backgroundColorString = '#000000'; + + this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter + + /** + * This temporary display object used as the parent of the currently being rendered item + * + * @member {PIXI.DisplayObject} + * @private + */ + this._tempDisplayObjectParent = new Container(); + + /** + * The last root object that the renderer tried to render. + * + * @member {PIXI.DisplayObject} + * @private + */ + this._lastObjectRendered = this._tempDisplayObjectParent; + + /** + * Collection of plugins. + * @readonly + * @member {object} + */ + this.plugins = {}; + } + + /** + * Initialize the plugins. + * + * @protected + * @param {object} staticMap - The dictionary of staticly saved plugins. + */ + initPlugins(staticMap) + { + 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() + { + return this.view.width; + } + + /** + * Same as view.height, actual number of pixels in the canvas by vertical + * + * @member {number} + * @readonly + * @default 600 + */ + get height() + { + return this.view.height; + } + + /** + * Resizes the screen and canvas to the specified width and height + * Canvas dimensions are multiplied by resolution + * + * @param {number} screenWidth - the new width of the screen + * @param {number} screenHeight - the new height of the screen + */ + resize(screenWidth, screenHeight) + { + this.screen.width = screenWidth; + this.screen.height = screenHeight; + + this.view.width = screenWidth * this.resolution; + this.view.height = screenHeight * this.resolution; + + if (this.autoResize) + { + this.view.style.width = `${screenWidth}px`; + this.view.style.height = `${screenHeight}px`; + } + } + + /** + * 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 {PIXI.DisplayObject} displayObject - The displayObject the object will be generated from + * @param {number} scaleMode - Should be one of the scaleMode consts + * @param {number} resolution - The resolution / device pixel ratio of the texture being generated + * @param {PIXI.Rectangle} [region] - The region of the displayObject, that shall be rendered, + * if no region is specified, defaults to the local bounds of the displayObject. + * @return {PIXI.Texture} a texture of the graphics object + */ + generateTexture(displayObject, scaleMode, resolution, region) + { + region = region || displayObject.getLocalBounds(); + + // 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(region.width | 0, region.height | 0, scaleMode, resolution); + + tempMatrix.tx = -region.x; + tempMatrix.ty = -region.y; + + this.render(displayObject, renderTexture, false, tempMatrix, true); + + return renderTexture; + } + + /** + * Removes everything from the renderer and optionally removes the Canvas DOM element. + * + * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. + */ + destroy(removeView) + { + 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); + } + + this.plugins = null; + + this.type = RENDERER_TYPE.UNKNOWN; + + this.view = null; + + this.screen = null; + + this.resolution = 0; + + this.transparent = false; + + this.autoResize = false; + + this.blendModes = null; + + this.options = null; + + this.preserveDrawingBuffer = false; + this.clearBeforeRender = false; + + this.roundPixels = false; + + this._backgroundColor = 0; + this._backgroundColorRgba = null; + this._backgroundColorString = null; + + this._tempDisplayObjectParent = null; + this._lastObjectRendered = null; + } + + /** + * The background color to fill if not transparent + * + * @member {number} + */ + get backgroundColor() + { + return this._backgroundColor; + } + + set backgroundColor(value) // eslint-disable-line require-jsdoc + { + this._backgroundColor = value; + this._backgroundColorString = hex2string(value); + hex2rgb(value, this._backgroundColorRgba); + } +} diff --git a/packages/core/src/Renderer.js b/packages/core/src/Renderer.js new file mode 100644 index 0000000..2227e78 --- /dev/null +++ b/packages/core/src/Renderer.js @@ -0,0 +1,338 @@ +import AbstractRenderer from './AbstractRenderer'; +import { sayHello } from '@pixi/utils'; +import MaskSystem from './mask/MaskSystem'; +import StencilSystem from './mask/StencilSystem'; +import FilterSystem from './filters/FilterSystem'; +import FramebufferSystem from './framebuffer/FramebufferSystem'; +import RenderTextureSystem from './renderTexture/RenderTextureSystem'; +import TextureSystem from './textures/TextureSystem'; +import ProjectionSystem from './projection/ProjectionSystem'; +import StateSystem from './state/StateSystem'; +import GeometrySystem from './geometry/GeometrySystem'; +import ShaderSystem from './shader/ShaderSystem'; +import ContextSystem from './context/ContextSystem'; +import BatchSystem from './batch/BatchSystem'; +import TextureGCSystem from './textures/TextureGCSystem'; +import { RENDERER_TYPE } from '@pixi/constants'; +import UniformGroup from './shader/UniformGroup'; +import { Matrix } from '@pixi/math'; +import Runner from 'mini-runner'; + +/** + * 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 Render works by automatically managing webGLBatchs. + * 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 :) + * + * @class + * @memberof PIXI + * @extends PIXI.AbstractRenderer + */ +export default class Renderer extends AbstractRenderer +{ + // eslint-disable-next-line valid-jsdoc + /** + * + * @param {object} [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.transparent=false] - If the render view is transparent, default false + * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false + * @param {boolean} [options.antialias=false] - sets antialias. If not available natively then FXAA + * antialiasing is used + * @param {boolean} [options.forceFXAA=false] - forces FXAA antialiasing to be used over native. + * FXAA is faster, but may not always look as great + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. + * The resolution of the renderer retina would be 2. + * @param {boolean} [options.clearBeforeRender=true] - This sets if the renderer will clear + * the canvas or not before the new render pass. If you wish to set this to false, you *must* set + * preserveDrawingBuffer to `true`. + * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, + * enable this if you need to call toDataUrl on the webgl context. + * @param {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when + * rendering, stopping pixel interpolation. + * @param {number} [options.backgroundColor=0x000000] - The background color of the rendered area + * (shown if not transparent). + * @param {boolean} [options.legacy=false] - If true PixiJS will aim to ensure compatibility + * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. + * @param {string} [options.powerPreference] - Parameter passed to webgl context, set to "high-performance" + * for devices with dual graphics card + */ + constructor(options, arg2, arg3) + { + super('WebGL', options, arg2, arg3); + + /** + * The type of this renderer as a standardised const + * + * @member {number} + * @see PIXI.RENDERER_TYPE + */ + this.type = RENDERER_TYPE.WEBGL; + + // this will be set by the contextSystem (this.context) + this.gl = null; + this.CONTEXT_UID = 0; + this.legacy = !!options.legacy; + + // TODO legacy! + + // runners! + this.runners = { + destroy: new Runner('destroy'), + contextChange: new Runner('contextChange', 1), + reset: new Runner('reset'), + update: new Runner('update'), + postrender: new Runner('postrender'), + prerender: new Runner('prerender'), + resize: new Runner('resize', 2), + }; + + 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(GeometrySystem, 'geometry') + .addSystem(FramebufferSystem, 'framebuffer') + .addSystem(StencilSystem, 'stencil') + .addSystem(ProjectionSystem, 'projection') + .addSystem(TextureGCSystem, 'textureGC') + .addSystem(FilterSystem, 'filter') + .addSystem(RenderTextureSystem, 'renderTexture') + .addSystem(BatchSystem, 'batch'); + + this.initPlugins(Renderer.__plugins); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ + if (options.context) + { + this.context.initFromContext(options.context); + } + else + { + this.context.initFromOptions({ + alpha: this.transparent, + antialias: options.antialias, + premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', + stencil: true, + 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); + } + + /** + * Add a new system to the renderer. + * @param {class} ClassRef - Class reference + * @param {string} [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. + * @return {PIXI.Renderer} Return instance of renderer + */ + addSystem(ClassRef, name) + { + if (!name) + { + name = ClassRef.name; + } + + // TODO - read name from class.name.. + + /* + if(name.includes('System')) + { + name = name.replace('System', ''); + name = name.charAt(0).toLowerCase() + name.slice(1); + } + */ + + const system = new ClassRef(this); + + if (this[name]) + { + throw new Error(`Whoops! ${name} is already a manger`); + } + + this[name] = system; + + for (const i in this.runners) + { + this.runners[i].add(system); + } + + return this; + + /** + * 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. + */ + } + + /** + * Renders the object to its webGL view + * + * @param {PIXI.DisplayObject} displayObject - the object to be rendered + * @param {PIXI.RenderTexture} renderTexture - The render texture to render to. + * @param {boolean} [clear] - Should the canvas be cleared before the new render + * @param {PIXI.Transform} [transform] - A transform to apply to the render texture before rendering. + * @param {boolean} [skipUpdateTransform] - Should we skip the update transform pass? + */ + render(displayObject, renderTexture, clear, transform, skipUpdateTransform) + { + // can be handy to know! + this.renderingToScreen = !renderTexture; + + this.runners.prerender.run(); + this.emit('prerender'); + + // 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.parent; + + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = 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.run(); + + this.emit('postrender'); + } + + /** + * Resizes the webGL view to the specified width and height. + * + * @param {number} screenWidth - the new width of the screen + * @param {number} screenHeight - the new height of the screen + */ + resize(screenWidth, screenHeight) + { + AbstractRenderer.prototype.resize.call(this, screenWidth, screenHeight); + + this.runners.resize.run(screenWidth, screenHeight); + } + + /** + * Resets the WebGL state so you can render things however you fancy! + * + * @return {PIXI.Renderer} Returns itself. + */ + reset() + { + this.runners.reset.run(); + + return this; + } + + clear() + { + this.framebuffer.bind(); + this.framebuffer.clear(); + } + + /** + * Removes everything from the renderer (event listeners, spritebatch, etc...) + * + * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. + * See: https://github.com/pixijs/pixi.js/issues/2233 + */ + destroy(removeView) + { + this.runners.destroy.run(); + + // call base destroy + super.destroy(removeView); + + // TODO nullify all the managers.. + this.gl = null; + } + + /** + * 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 + * builds and excluding plugins. + * @name PIXI.Renderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. + */ + + /** + * Adds a plugin to the renderer. + * + * @method + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + static registerPlugin(pluginName, ctor) + { + Renderer.__plugins = Renderer.__plugins || {}; + Renderer.__plugins[pluginName] = ctor; + } +} diff --git a/packages/core/src/System.js b/packages/core/src/System.js new file mode 100644 index 0000000..05a10c8 --- /dev/null +++ b/packages/core/src/System.js @@ -0,0 +1,42 @@ +/** + * System is a base class used for extending systems used by the {@link PIXI.Renderer} + * @see PIXI.Renderer#addSystem + * @class + * @memberof PIXI + */ +export default class System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + /** + * The renderer this manager works for. + * + * @member {PIXI.Renderer} + */ + this.renderer = renderer; + + this.renderer.runners.contextChange.add(this); + } + + /** + * Generic method called when there is a WebGL context change. + * + */ + contextChange() + { + // do some codes init! + } + + /** + * Generic destroy methods to be overridden by the subclass + * + */ + destroy() + { + this.renderer.runners.contextChange.remove(this); + this.renderer = null; + } +} diff --git a/packages/core/src/batch/BatchSystem.js b/packages/core/src/batch/BatchSystem.js new file mode 100644 index 0000000..b04c249 --- /dev/null +++ b/packages/core/src/batch/BatchSystem.js @@ -0,0 +1,68 @@ +import System from '../System'; +import ObjectRenderer from './ObjectRenderer'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class BatchSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + super(renderer); + + /** + * An empty renderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.emptyRenderer = new ObjectRenderer(renderer); + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.currentRenderer = this.emptyRenderer; + } + + /** + * Changes the current renderer to the one given in parameter + * + * @param {PIXI.ObjectRenderer} objectRenderer - The object renderer to use. + */ + setObjectRenderer(objectRenderer) + { + if (this.currentRenderer === objectRenderer) + { + return; + } + + this.currentRenderer.stop(); + this.currentRenderer = objectRenderer; + + this.renderer.state.setState(objectRenderer.state); + + this.currentRenderer.start(); + } + + /** + * This should be called if you wish to do some custom rendering + * It will basically render anything that may be batched up such as sprites + * + */ + flush() + { + this.setObjectRenderer(this.emptyRenderer); + } + + reset() + { + this.setObjectRenderer(this.emptyRenderer); + } +} diff --git a/packages/core/src/batch/ObjectRenderer.js b/packages/core/src/batch/ObjectRenderer.js new file mode 100644 index 0000000..0c851a9 --- /dev/null +++ b/packages/core/src/batch/ObjectRenderer.js @@ -0,0 +1,48 @@ +import System from '../System'; + +/** + * Base for a common object renderer that can be used as a system renderer plugin. + * + * @class + * @extends PIXI.System + * @memberof PIXI + */ +export default class ObjectRenderer extends System +{ + /** + * Starts the renderer and sets the shader + * + */ + start() + { + // set the shader.. + } + + /** + * Stops the renderer + * + */ + stop() + { + this.flush(); + } + + /** + * Stub method for rendering content and emptying the current batch. + * + */ + flush() + { + // flush! + } + + /** + * Renders an object + * + * @param {PIXI.DisplayObject} object - The object to render. + */ + render(object) // eslint-disable-line no-unused-vars + { + // render the object + } +} diff --git a/packages/core/src/context/ContextSystem.js b/packages/core/src/context/ContextSystem.js new file mode 100644 index 0000000..301aab4 --- /dev/null +++ b/packages/core/src/context/ContextSystem.js @@ -0,0 +1,186 @@ +import System from '../System'; +import { settings } from '@pixi/settings'; + +let CONTEXT_UID = 0; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class ContextSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.webGLVersion = 1; + + this.handleContextLost = this.handleContextLost.bind(this); + this.handleContextRestored = this.handleContextRestored.bind(this); + + this.extensions = {}; + + renderer.view.addEventListener('webglcontextlost', this.handleContextLost, false); + renderer.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + } + + get isLost() + { + return (!this.gl || this.gl.isContextLost()); + } + + contextChange(gl) + { + this.gl = gl; + + // restore a context if it was previously lost + if (gl.isContextLost() && gl.getExtension('WEBGL_lose_context')) + { + gl.getExtension('WEBGL_lose_context').restoreContext(); + } + } + + initFromContext(gl) + { + this.gl = gl; + this.validateContext(gl); + this.renderer.gl = gl; + this.renderer.CONTEXT_UID = CONTEXT_UID++; + this.renderer.runners.contextChange.run(gl); + } + + initFromOptions(options) + { + const gl = this.createContext(this.renderer.view, options); + + this.initFromContext(gl); + } + + /** + * Helper class to create a webGL Context + * + * @class + * @memberof PIXI.glCore + * @param canvas {HTMLCanvasElement} the canvas element that we will get the context from + * @param options {Object} An options object that gets passed in to the canvas element containing the context attributes, + * see https://developer.mozilla.org/en/docs/Web/API/HTMLCanvasElement/getContext for the options available + * @return {WebGLRenderingContext} the WebGL context + */ + createContext(canvas, options) + { + let gl; + + if (settings.PREFER_WEBGL_2) + { + gl = canvas.getContext('webgl2', options); + } + + if (gl) + { + this.webGLVersion = 2; + } + else + { + this.webGLVersion = 1; + + gl = canvas.getContext('webgl', options) + || canvas.getContext('experimental-webgl', options); + + if (!gl) + { + // fail, not able to get a context + throw new Error('This browser does not support webGL. Try using the canvas renderer'); + } + } + + this.gl = gl; + + this.getExtensions(); + + return gl; + } + + getExtensions() + { + // time to set up default etensions that pixi uses.. + const gl = this.gl; + const extensions = this.extensions; + + if (this.webGLVersion === 1) + { + extensions.drawBuffers = gl.getExtension('WEBGL_draw_buffers'); + extensions.depthTexture = gl.getExtension('WEBKIT_WEBGL_depth_texture'); + extensions.floatTexture = gl.getExtension('OES_texture_float'); + extensions.loseContext = gl.getExtension('WEBGL_lose_context'); + + extensions.vertexArrayObject = gl.getExtension('OES_vertex_array_object') + || gl.getExtension('MOZ_OES_vertex_array_object') + || gl.getExtension('WEBKIT_OES_vertex_array_object'); + } + + // we don't use any specific WebGL 2 ones yet! + } + + /** + * Handles a lost webgl context + * + * @private + * @param {WebGLContextEvent} event - The context lost event. + */ + handleContextLost(event) + { + event.preventDefault(); + } + + /** + * Handles a restored webgl context + * + * @private + */ + handleContextRestored() + { + this.renderer.runners.contextChange.run(this.gl); + } + + destroy() + { + const view = this.renderer.view; + + // remove listeners + view.removeEventListener('webglcontextlost', this.handleContextLost); + view.removeEventListener('webglcontextrestored', this.handleContextRestored); + + this.gl.useProgram(null); + + if (this.extensions.loseContext) + { + this.extensions.loseContext.loseContext(); + } + } + + postrender() + { + this.gl.flush(); + } + + validateContext(gl) + { + const attributes = gl.getContextAttributes(); + + // this is going to be fairly simple for now.. but at least we have room to grow! + if (!attributes.stencil) + { + /* eslint-disable max-len */ + + /* eslint-disable no-console */ + console.warn('Provided WebGL context does not have a stencil buffer, masks may not render correctly'); + /* eslint-enable no-console */ + + /* eslint-enable max-len */ + } + } +} diff --git a/packages/core/src/filters/Filter.js b/packages/core/src/filters/Filter.js new file mode 100644 index 0000000..9d8cad8 --- /dev/null +++ b/packages/core/src/filters/Filter.js @@ -0,0 +1,140 @@ +import Shader from '../shader/Shader'; +import Program from '../shader/Program'; +import State from '../state/State'; +import { settings } from '@pixi/settings'; +// import extractUniformsFromSrc from './extractUniformsFromSrc'; +import defaultVertex from './defaultFilter.vert'; +import defaultFragment from './defaultFilter.frag'; + +// let math = require('../../math'); +/** + * @class + * @memberof PIXI + * @extends PIXI.Shader + */ +export default class Filter extends Shader +{ + /** + * @param {string} [vertexSrc] - The source of the vertex shader. + * @param {string} [fragmentSrc] - The source of the fragment shader. + * @param {object} [uniforms] - Custom uniforms to use to augment the built-in ones. + */ + constructor(vertexSrc, fragmentSrc, uniforms) + { + const program = Program.from(vertexSrc, fragmentSrc); + + super(program, uniforms); + + /** + * The padding of the filter. Some filters require extra space to breath such as a blur. + * Increasing this will add extra width and height to the bounds of the object that the + * filter is applied to. + * + * @member {number} + */ + this.padding = 0; + + /** + * The resolution of the filter. Setting this to be lower will lower the quality but + * increase the performance of the filter. + * + * @member {number} + */ + this.resolution = settings.RESOLUTION; + + /** + * If enabled is true the filter is applied, if false it will not. + * + * @member {boolean} + */ + this.enabled = true; + + /** + * If enabled, PixiJS will fit the filter area into boundaries for better performance. + * Switch it off if it does not work for specific shader. + * + * @member {boolean} + */ + this.autoFit = true; + + /** + * Legacy filters use position and uvs from attributes + * @member {boolean} + * @readonly + */ + this.legacy = !!this.program.attributeData.aTextureCoord; + + /** + * the webGL state the filter requires to render + * @member {PIXI.State} + */ + this.state = new State(); + } + + /** + * Applies the filter + * + * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + * @param {boolean} clear - Should the output be cleared before rendering to it + * @param {object} [currentState] - It's current state of filter. + * There are some useful properties in the currentState : + * target, filters, sourceFrame, destinationFrame, renderTarget, resolution + */ + apply(filterManager, input, output, clear, currentState, derp) // eslint-disable-line no-unused-vars + { + // do as you please! + + filterManager.applyFilter(this, input, output, clear, currentState, derp); + + // or just do a regular render.. + } + + /** + * Sets the blendmode of the filter + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL + */ + get blendMode() + { + return this.state.blendMode; + } + + set blendMode(value) // eslint-disable-line require-jsdoc + { + this.state.blendMode = value; + } + + /** + * The default vertex shader source + * + * @static + * @constant + */ + static get defaultVertexSrc() + { + return defaultVertex; + } + + /** + * The default fragment shader source + * + * @static + * @constant + */ + static get defaultFragmentSrc() + { + return defaultFragment; + } +} + +/** + * Used for caching shader IDs + * + * @static + * @private + */ +Filter.SOURCE_KEY_MAP = {}; + diff --git a/packages/core/src/filters/FilterSystem.js b/packages/core/src/filters/FilterSystem.js new file mode 100644 index 0000000..3174318 --- /dev/null +++ b/packages/core/src/filters/FilterSystem.js @@ -0,0 +1,431 @@ +import System from '../System'; + +import RenderTexture from '../renderTexture/RenderTexture'; +import Quad from '../utils/Quad'; +import QuadUv from '../utils/QuadUv'; +import { Rectangle } from '@pixi/math'; +import * as filterTransforms from './filterTransforms'; +import bitTwiddle from 'bit-twiddle'; +import UniformGroup from '../shader/UniformGroup'; +import { DRAW_MODES } from '@pixi/constants'; + +// +/** + * @ignore + * @class + */ +class FilterState +{ + /** + * + */ + constructor() + { + this.renderTexture = null; + this.sourceFrame = new Rectangle(); + this.destinationFrame = new Rectangle(); + this.filters = []; + this.target = null; + this.legacy = false; + this.resolution = 1; + } +} + +/** + * @class + * @memberof PIXI.systems + * @extends PIXI.System + */ +export default class FilterSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + /** + * stores a bunch of PO2 textures used for filtering + * @type {Object} + */ + this.texturePool = {}; + + /** + * a pool for storing filter states, save us creating new ones each tick + * @type {Array} + */ + this.statePool = []; + + /** + * A very simple geometry used when drawing a filter effect to the screen + * @type {Quad} + */ + this.quad = new Quad(); + + this.quadUv = new QuadUv(); + + /** + * Temporary rect for maths + * @type {PIXI.Rectangle} + */ + this.tempRect = new Rectangle(); + + this.activeState = {}; + + /** + * this uniform group is attached to filter uniforms when used + * @type {UniformGroup} + */ + this.globalUniforms = new UniformGroup({ + sourceFrame: this.tempRect, + destinationFrame: this.tempRect, + + // legacy variables + filterArea: new Float32Array(4), + filterClamp: new Float32Array(4), + }, true); + } + + /** + * Adds a new filter to the System. + * + * @param {PIXI.DisplayObject} target - The target of the filter to render. + * @param {PIXI.Filter[]} filters - The filters to apply. + */ + push(target, filters) + { + const renderer = this.renderer; + const filterStack = this.renderer.renderTexture.defaultFilterStack; + const state = this.statePool.pop() || new FilterState(); + + let resolution = filters[0].resolution; + let padding = filters[0].padding; + let autoFit = filters[0].autoFit; + let legacy = filters[0].legacy; + + for (let i = 1; i < filters.length; i++) + { + const filter = filters[i]; + + // lets use the lowest resolution.. + resolution = Math.min(resolution, filter.resolution); + // and the largest amount of padding! + padding = Math.max(padding, filter.padding); + // only auto fit if all filters are autofit + autoFit = autoFit || filter.autoFit; + + legacy = legacy || filter.legacy; + } + + filterStack.push(state); + + state.resolution = resolution; + + state.legacy = legacy; + + // round to whole number based on resolution + // TODO move that to the shader too? + state.sourceFrame = target.filterArea || target.getBounds(true); + + state.sourceFrame.pad(padding); + + if (autoFit) + { + state.sourceFrame.fit(this.renderer.renderTexture.destinationFrame); + } + + state.sourceFrame.round(resolution); + + state.renderTexture = this.getPotFilterTexture(state.sourceFrame.width, state.sourceFrame.height, resolution); + state.filters = filters; + + state.destinationFrame.width = state.renderTexture.width; + state.destinationFrame.height = state.renderTexture.height; + + state.renderTexture.filterFrame = state.sourceFrame; + renderer.renderTexture.bind(state.renderTexture, state.sourceFrame);// /, state.destinationFrame); + renderer.renderTexture.clear(); + } + + /** + * Pops off the filter and applies it. + * + */ + pop() + { + const renderer = this.renderer; + const filterStack = renderer.renderTexture.defaultFilterStack; + const state = filterStack.pop(); + const filters = state.filters; + + this.activeState = state; + + const globalUniforms = this.globalUniforms.uniforms; + + globalUniforms.sourceFrame = state.sourceFrame; + globalUniforms.destinationFrame = state.destinationFrame; + globalUniforms.resolution = state.resolution; + + // only update the rect if its legacy.. + if (state.legacy) + { + const filterArea = globalUniforms.filterArea; + const filterClamp = globalUniforms.filterClamp; + + filterArea[0] = state.destinationFrame.width; + filterArea[1] = state.destinationFrame.height; + filterArea[2] = state.sourceFrame.x; + filterArea[3] = state.sourceFrame.y; + + filterClamp[0] = 0.5 / state.resolution / state.destinationFrame.width; + filterClamp[1] = 0.5 / state.resolution / state.destinationFrame.height; + filterClamp[2] = (state.sourceFrame.width - 0.5) / state.resolution / state.destinationFrame.width; + filterClamp[3] = (state.sourceFrame.height - 0.5) / state.resolution / state.destinationFrame.height; + } + + this.globalUniforms.update(); + + const lastState = filterStack[filterStack.length - 1]; + + if (filters.length === 1) + { + filters[0].apply(this, state.renderTexture, lastState.renderTexture, false, state); + + this.returnFilterTexture(state.renderTexture); + } + else + { + let flip = state.renderTexture; + let flop = this.getPotFilterTexture( + flip.width, + flip.height, + state.resolution + ); + + flop.filterFrame = flip.filterFrame; + + let i = 0; + + for (i = 0; i < filters.length - 1; ++i) + { + filters[i].apply(this, flip, flop, true, state); + + const t = flip; + + flip = flop; + flop = t; + } + + filters[i].apply(this, flip, lastState.renderTexture, false, state); + + this.returnFilterTexture(flip); + this.returnFilterTexture(flop); + } + + this.statePool.push(state); + } + + /** + * Draws a filter. + * + * @param {PIXI.Filter} filter - The filter to draw. + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + * @param {boolean} clear - Should the output be cleared before rendering to it + */ + applyFilter(filter, input, output, clear) + { + const renderer = this.renderer; + + renderer.renderTexture.bind(output, output ? output.filterFrame : null); + + if (clear) + { + // gl.disable(gl.SCISSOR_TEST); + renderer.renderTexture.clear(); + // gl.enable(gl.SCISSOR_TEST); + } + + // set the uniforms.. + filter.uniforms.uSampler = input; + filter.uniforms.filterGlobals = this.globalUniforms; + + // TODO make it so that the order of this does not matter.. + // because it does at the moment cos of global uniforms. + // they need to get resynced + + renderer.state.setState(filter.state); + renderer.shader.bind(filter); + + if (filter.legacy) + { + this.quadUv.map(input._frame, input.filterFrame); + + renderer.geometry.bind(this.quadUv); + renderer.geometry.draw(DRAW_MODES.TRIANGLES); + } + else + { + renderer.geometry.bind(this.quad); + renderer.geometry.draw(DRAW_MODES.TRIANGLE_STRIP); + } + } + + /** + * Calculates the mapped matrix. + * + * TODO playing around here.. this is temporary - (will end up in the shader) + * this returns a matrix that will normalise map filter cords in the filter to screen space + * + * @param {PIXI.Matrix} outputMatrix - the matrix to output to. + * @return {PIXI.Matrix} The mapped matrix. + */ + calculateScreenSpaceMatrix(outputMatrix) + { + const currentState = this.activeState; + + return filterTransforms.calculateScreenSpaceMatrix( + outputMatrix, + currentState.sourceFrame, + currentState.destinationFrame + ); + } + + /** + * This will map the filter coord so that a texture can be used based on the transform of a sprite + * + * @param {PIXI.Matrix} outputMatrix - The matrix to output to. + * @param {PIXI.Sprite} sprite - The sprite to map to. + * @return {PIXI.Matrix} The mapped matrix. + */ + calculateSpriteMatrix(outputMatrix, sprite) + { + const currentState = this.activeState; + + return filterTransforms.calculateSpriteMatrix( + outputMatrix, + currentState.sourceFrame, + currentState.destinationFrame, + sprite + ); + } + + /** + * Destroys this Filter System. + * + * @param {boolean} [contextLost=false] context was lost, do not free shaders + * + */ + destroy(contextLost = false) + { + if (!contextLost) + { + this.emptyPool(); + } + else + { + this.texturePool = {}; + } + } + + /** + * Gets a Power-of-Two render texture. + * + * TODO move to a seperate class could be on renderer? + * also - could cause issue with multiple contexts? + * + * @private + * @param {WebGLRenderingContext} gl - The webgl rendering context + * @param {number} minWidth - The minimum width of the render target. + * @param {number} minHeight - The minimum height of the render target. + * @param {number} resolution - The resolution of the render target. + * @return {PIXI.RenderTarget} The new render target. + */ + getPotFilterTexture(minWidth, minHeight, resolution) + { + minWidth = bitTwiddle.nextPow2(minWidth); + minHeight = bitTwiddle.nextPow2(minHeight); + resolution = resolution || 1; + + const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + + if (!this.texturePool[key]) + { + this.texturePool[key] = []; + } + + let renderTexture = this.texturePool[key].pop(); + + if (!renderTexture) + { + // temporary bypass cache.. + // internally - this will cause a texture to be bound.. + renderTexture = RenderTexture.create({ + width: minWidth, + height: minHeight, + resolution, + }); + } + + return renderTexture; + } + + /** + * Gets extra render texture to use inside current filter + * + * @param {number} resolution resolution of the renderTexture + * @returns {PIXI.RenderTexture} + */ + getFilterTexture(resolution) + { + const rt = this.activeState.renderTexture; + + const filterTexture = this.getPotFilterTexture(rt.width, rt.height, resolution || rt.baseTexture.resolution); + + filterTexture.filterFrame = rt.filterFrame; + + return filterTexture; + } + + /** + * Frees a render texture back into the pool. + * + * @param {PIXI.RenderTarget} renderTexture - The renderTarget to free + */ + returnFilterTexture(renderTexture) + { + renderTexture.filterFrame = null; + + const base = renderTexture.baseTexture; + + const minWidth = base.width; + const minHeight = base.height; + + const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + + this.texturePool[key].push(renderTexture); + } + + /** + * Empties the texture pool. + * + */ + emptyPool() + { + for (const i in this.texturePool) + { + const textures = this.texturePool[i]; + + if (textures) + { + for (let j = 0; j < textures.length; j++) + { + textures[j].destroy(true); + } + } + } + + this.texturePool = {}; + } +} diff --git a/packages/core/src/filters/defaultFilter.frag b/packages/core/src/filters/defaultFilter.frag new file mode 100644 index 0000000..a14c1be --- /dev/null +++ b/packages/core/src/filters/defaultFilter.frag @@ -0,0 +1,22 @@ +varying vec2 vTextureCoord; +varying vec2 vFilterCoord; + +uniform sampler2D uSampler; +uniform sampler2D filterSampler; + +void main(void){ + vec4 masky = texture2D(filterSampler, vFilterCoord); + vec4 sample = texture2D(uSampler, vTextureCoord); + vec4 color; + if(mod(vFilterCoord.x, 1.0) > 0.5) + { + color = vec4(1.0, 0.0, 0.0, 1.0); + } + else + { + color = vec4(0.0, 1.0, 0.0, 1.0); + } + // gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0); + gl_FragColor = mix(sample, masky, 0.5); + gl_FragColor *= sample.a; +} diff --git a/packages/core/src/filters/defaultFilter.vert b/packages/core/src/filters/defaultFilter.vert new file mode 100644 index 0000000..e1febca --- /dev/null +++ b/packages/core/src/filters/defaultFilter.vert @@ -0,0 +1,14 @@ +attribute vec2 aVertexPosition; +attribute vec2 aTextureCoord; + +uniform mat3 projectionMatrix; +uniform mat3 filterMatrix; + +varying vec2 vTextureCoord; +varying vec2 vFilterCoord; + +void main(void){ + gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); + vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy; + vTextureCoord = aTextureCoord ; +} diff --git a/packages/core/src/filters/filterTransforms.js b/packages/core/src/filters/filterTransforms.js new file mode 100644 index 0000000..87a3cd6 --- /dev/null +++ b/packages/core/src/filters/filterTransforms.js @@ -0,0 +1,50 @@ +import { Matrix } from '@pixi/math'; + +/** + * Calculates the mapped matrix + * @param filterArea {Rectangle} The filter area + * @param sprite {Sprite} the target sprite + * @param outputMatrix {Matrix} @alvin + * @private + */ +// this returns a matrix that will normalise map filter cords in the filter to screen space +export function calculateScreenSpaceMatrix(outputMatrix, filterArea, textureSize) +{ + // TODO unwrap? + const mappedMatrix = outputMatrix.identity(); + + mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); + + mappedMatrix.scale(textureSize.width, textureSize.height); + + return mappedMatrix; +} + +export function calculateNormalizedScreenSpaceMatrix(outputMatrix, filterArea, textureSize) +{ + const mappedMatrix = outputMatrix.identity(); + + mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); + + const translateScaleX = (textureSize.width / filterArea.width); + const translateScaleY = (textureSize.height / filterArea.height); + + mappedMatrix.scale(translateScaleX, translateScaleY); + + return mappedMatrix; +} + +// this will map the filter coord so that a texture can be used based on the transform of a sprite +export function calculateSpriteMatrix(outputMatrix, filterArea, textureSize, sprite) +{ + const orig = sprite._texture.orig; + const mappedMatrix = outputMatrix.set(textureSize.width, 0, 0, textureSize.height, filterArea.x, filterArea.y); + const worldTransform = sprite.worldTransform.copyTo(Matrix.TEMP_MATRIX); + + worldTransform.invert(); + mappedMatrix.prepend(worldTransform); + mappedMatrix.scale(1.0 / orig.width, 1.0 / orig.height); + mappedMatrix.translate(sprite.anchor.x, sprite.anchor.y); + + return mappedMatrix; +} diff --git a/packages/core/src/filters/spriteMask/SpriteMaskFilter.js b/packages/core/src/filters/spriteMask/SpriteMaskFilter.js new file mode 100644 index 0000000..91697b7 --- /dev/null +++ b/packages/core/src/filters/spriteMask/SpriteMaskFilter.js @@ -0,0 +1,63 @@ +import Filter from '../Filter'; +import { Matrix } from '@pixi/math'; +import vertex from './spriteMaskFilter.vert'; +import fragment from './spriteMaskFilter.frag'; +import { default as TextureMatrix } from '../../textures/TextureMatrix'; + +/** + * The SpriteMaskFilter class + * + * @class + * @extends PIXI.Filter + * @memberof PIXI + */ +export default class SpriteMaskFilter extends Filter +{ + /** + * @param {PIXI.Sprite} sprite - the target sprite + */ + constructor(sprite) + { + const maskMatrix = new Matrix(); + + super(vertex, fragment); + + sprite.renderable = false; + + this.maskSprite = sprite; + this.maskMatrix = maskMatrix; + } + + /** + * Applies the filter + * + * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + */ + apply(filterManager, input, output) + { + const maskSprite = this.maskSprite; + const tex = this.maskSprite.texture; + + if (!tex.valid) + { + return; + } + if (!tex.transform) + { + // margin = 0.0, let it bleed a bit, shader code becomes easier + // assuming that atlas textures were made with 1-pixel padding + tex.transform = new TextureMatrix(tex, 0.0); + } + tex.transform.update(); + + this.uniforms.mask = tex; + this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite) + .prepend(tex.transform.mapCoord); + this.uniforms.alpha = maskSprite.worldAlpha; + this.uniforms.maskClamp = tex.transform.uClampFrame; + + filterManager.applyFilter(this, input, output); + } +} diff --git a/packages/core/src/filters/spriteMask/spriteMaskFilter.frag b/packages/core/src/filters/spriteMask/spriteMaskFilter.frag new file mode 100644 index 0000000..0e4aef8 --- /dev/null +++ b/packages/core/src/filters/spriteMask/spriteMaskFilter.frag @@ -0,0 +1,23 @@ +varying vec2 vMaskCoord; +varying vec2 vTextureCoord; + +uniform sampler2D uSampler; +uniform sampler2D mask; +uniform float alpha; +uniform vec4 maskClamp; + +void main(void) +{ + float clip = step(3.5, + step(maskClamp.x, vMaskCoord.x) + + step(maskClamp.y, vMaskCoord.y) + + step(vMaskCoord.x, maskClamp.z) + + step(vMaskCoord.y, maskClamp.w)); + + vec4 original = texture2D(uSampler, vTextureCoord); + vec4 masky = texture2D(mask, vMaskCoord); + + original *= (masky.r * masky.a * alpha * clip); + + gl_FragColor = original; +} diff --git a/packages/core/src/filters/spriteMask/spriteMaskFilter.vert b/packages/core/src/filters/spriteMask/spriteMaskFilter.vert new file mode 100644 index 0000000..d23a30c --- /dev/null +++ b/packages/core/src/filters/spriteMask/spriteMaskFilter.vert @@ -0,0 +1,16 @@ +attribute vec2 aVertexPosition; +attribute vec2 aTextureCoord; + +uniform mat3 projectionMatrix; +uniform mat3 otherMatrix; + +varying vec2 vMaskCoord; +varying vec2 vTextureCoord; + +void main(void) +{ + gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); + + vTextureCoord = aTextureCoord; + vMaskCoord = ( otherMatrix * vec3( aTextureCoord, 1.0) ).xy; +} diff --git a/packages/core/src/framebuffer/FrameBuffer.js b/packages/core/src/framebuffer/FrameBuffer.js new file mode 100644 index 0000000..84e9fcc --- /dev/null +++ b/packages/core/src/framebuffer/FrameBuffer.js @@ -0,0 +1,106 @@ +import Texture from '../textures/BaseTexture'; +import { FORMATS, TYPES } from '@pixi/constants'; + +/** + * Frame buffer + * @class + * @memberof PIXI + */ +export default class FrameBuffer +{ + constructor(width, height) + { + this.width = width || 100; + this.height = height || 100; + + this.stencil = false; + this.depth = false; + + this.dirtyId = 0; + this.dirtyFormat = 0; + this.dirtySize = 0; + + this.depthTexture = null; + this.colorTextures = []; + + this.glFrameBuffers = {}; + } + + get colorTexture() + { + return this.colorTextures[0]; + } + + addColorTexture(index, texture) + { + // TODO add some validation to the texture - same width / height etc? + this.colorTextures[index || 0] = texture || new Texture(null, { scaleMode: 0, + resolution: 1, + mipmap: false, + width: this.width, + height: this.height });// || new Texture(); + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + addDepthTexture(texture) + { + /* eslint-disable max-len */ + this.depthTexture = texture || new Texture(null, { scaleMode: 0, + resolution: 1, + width: this.width, + height: this.height, + mipmap: false, + format: FORMATS.DEPTH_COMPONENT, + type: TYPES.UNSIGNED_SHORT });// UNSIGNED_SHORT; + /* eslint-disable max-len */ + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + enableDepth() + { + this.depth = true; + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + enableStencil() + { + this.stencil = true; + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + resize(width, height) + { + if (width === this.width && height === this.height) return; + + this.width = width; + this.height = height; + + this.dirtyId++; + this.dirtySize++; + + for (let i = 0; i < this.colorTextures.length; i++) + { + this.colorTextures[i].setSize(width, height); + } + + if (this.depthTexture) + { + this.depthTexture.setSize(width, height); + } + } +} diff --git a/packages/core/src/framebuffer/FramebufferSystem.js b/packages/core/src/framebuffer/FramebufferSystem.js new file mode 100644 index 0000000..2cc2437 --- /dev/null +++ b/packages/core/src/framebuffer/FramebufferSystem.js @@ -0,0 +1,246 @@ +import System from '../System'; +import { Rectangle } from '@pixi/math'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class FramebufferSystem extends System +{ + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + contextChange() + { + this.gl = this.renderer.gl; + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + this.current = null; + this.viewport = new Rectangle(); + + this.drawBufferExtension = this.renderer.context.extensions.drawBuffers; + } + + bind(framebuffer, frame) + { + const gl = this.gl; + + this.current = framebuffer; + + if (framebuffer) + { + // TODO cacheing layer! + + const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID] || this.initFramebuffer(framebuffer); + + gl.bindFramebuffer(gl.FRAMEBUFFER, fbo.framebuffer); + // makesure all textures are unbound.. + + // now check for updates... + if (fbo.dirtyId !== framebuffer.dirtyId) + { + fbo.dirtyId = framebuffer.dirtyId; + + if (fbo.dirtyFormat !== framebuffer.dirtyFormat) + { + fbo.dirtyFormat = framebuffer.dirtyFormat; + this.updateFramebuffer(framebuffer); + } + else if (fbo.dirtySize !== framebuffer.dirtySize) + { + fbo.dirtySize = framebuffer.dirtySize; + this.resizeFramebuffer(framebuffer); + } + } + + for (let i = 0; i < framebuffer.colorTextures.length; i++) + { + if (framebuffer.colorTextures[i].texturePart) + { + this.renderer.texture.unbind(framebuffer.colorTextures[i].texture); + } + else + { + this.renderer.texture.unbind(framebuffer.colorTextures[i]); + } + } + + if (framebuffer.depthTexture) + { + this.renderer.texture.unbind(framebuffer.depthTexture); + } + + if (frame) + { + this.setViewport(frame.x, frame.y, frame.width, frame.height); + } + else + { + this.setViewport(0, 0, framebuffer.width, framebuffer.height); + } + } + else + { + gl.bindFramebuffer(gl.FRAMEBUFFER, null); + + if (frame) + { + this.setViewport(frame.x, frame.y, frame.width, frame.height); + } + else + { + this.setViewport(0, 0, this.renderer.width, this.renderer.height); + } + } + } + + setViewport(x, y, width, height) + { + const v = this.viewport; + + if (v.width !== width || v.height !== height || v.x !== x || v.y !== y) + { + v.x = x; + v.y = y; + v.width = width; + v.height = height; + + this.gl.viewport(x, y, width, height); + } + } + + get size() + { + if (this.current) + { + // TODO store temp + return { x: 0, y: 0, width: this.current.width, height: this.current.height }; + } + + return { x: 0, y: 0, width: this.renderer.width, height: this.renderer.height }; + } + + clear(r, g, b, a) + { + const gl = this.gl; + + // TODO clear color can be set only one right? + gl.clearColor(r, g, b, a); + gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); + } + + // private functions... + + initFramebuffer(framebuffer) + { + const gl = this.gl; + + // TODO - make this a class? + const fbo = { + framebuffer: gl.createFramebuffer(), + stencil: null, + dirtyId: 0, + dirtyFormat: 0, + dirtySize: 0, + }; + + framebuffer.glFrameBuffers[this.CONTEXT_UID] = fbo; + + return fbo; + } + + resizeFramebuffer(framebuffer) + { + const gl = this.gl; + + if (framebuffer.stencil || framebuffer.depth) + { + gl.bindRenderbuffer(gl.RENDERBUFFER, this.stencil); + gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); + } + } + + updateFramebuffer(framebuffer) + { + const gl = this.gl; + + const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID]; + + // bind the color texture + const colorTextures = framebuffer.colorTextures; + + let count = colorTextures.length; + + if (!this.drawBufferExtension) + { + count = Math.min(count, 1); + } + + const activeTextures = []; + + for (let i = 0; i < count; i++) + { + const texture = framebuffer.colorTextures[i]; + + if (texture.texturePart) + { + this.renderer.texture.bind(texture.texture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.COLOR_ATTACHMENT0 + i, + gl.TEXTURE_CUBE_MAP_NEGATIVE_X + texture.side, + texture.texture._glTextures[this.CONTEXT_UID].texture, + 0); + } + else + { + this.renderer.texture.bind(texture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.COLOR_ATTACHMENT0 + i, + gl.TEXTURE_2D, + texture._glTextures[this.CONTEXT_UID].texture, + 0); + } + + activeTextures.push(gl.COLOR_ATTACHMENT0 + i); + } + + if (this.drawBufferExtension && activeTextures.length > 1) + { + this.drawBufferExtension.drawBuffersWEBGL(activeTextures); + } + + if (framebuffer.depthTexture) + { + const depthTextureExt = this.renderer.context.extensions.depthTexture; + + if (depthTextureExt) + { + const depthTexture = framebuffer.depthTexture; + + this.renderer.texture.bind(depthTexture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.DEPTH_ATTACHMENT, + gl.TEXTURE_2D, + depthTexture._glTextures[this.CONTEXT_UID].texture, + 0); + } + } + + if (framebuffer.stencil || framebuffer.depth) + { + fbo.stencil = gl.createRenderbuffer(); + + gl.bindRenderbuffer(gl.RENDERBUFFER, fbo.stencil); + + // TODO.. this is depth AND stencil? + gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, fbo.stencil); + gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); + // fbo.enableStencil(); + } + } +} diff --git a/packages/core/src/geometry/GLBuffer.js b/packages/core/src/geometry/GLBuffer.js new file mode 100644 index 0000000..0477e8a --- /dev/null +++ b/packages/core/src/geometry/GLBuffer.js @@ -0,0 +1,9 @@ +export default class GLBuffer +{ + constructor(buffer) + { + this.buffer = buffer; + this.updateID = -1; + this.byteLength = -1; + } +} diff --git a/packages/core/src/geometry/GeometrySystem.js b/packages/core/src/geometry/GeometrySystem.js new file mode 100644 index 0000000..959f827 --- /dev/null +++ b/packages/core/src/geometry/GeometrySystem.js @@ -0,0 +1,388 @@ +import System from '../System'; +import GLBuffer from './GLBuffer'; + +const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class GeometrySystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this._activeGeometry = null; + this._activeVao = null; + + this.hasVao = true; + this.hasInstance = true; + } + + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + contextChange() + { + const gl = this.gl = this.renderer.gl; + + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + + // webgl2 + if (!gl.createVertexArray) + { + // webgl 1! + let nativeVaoExtension = this.renderer.context.extensions.vertexArrayObject; + + if (this.renderer.legacy) + { + nativeVaoExtension = null; + } + + if (nativeVaoExtension) + { + gl.createVertexArray = () => + nativeVaoExtension.createVertexArrayOES(); + + gl.bindVertexArray = (vao) => + nativeVaoExtension.bindVertexArrayOES(vao); + + gl.deleteVertexArray = (vao) => + nativeVaoExtension.deleteVertexArrayOES(vao); + } + else + { + this.hasVao = false; + gl.createVertexArray = () => + { + // empty + }; + + gl.bindVertexArray = () => + { + // empty + }; + + gl.deleteVertexArray = () => + { + // empty + }; + } + } + + if (!gl.vertexAttribDivisor) + { + const instanceExt = gl.getExtension('ANGLE_instanced_arrays'); + + if (instanceExt) + { + gl.vertexAttribDivisor = (a, b) => + instanceExt.vertexAttribDivisorANGLE(a, b); + + gl.drawElementsInstanced = (a, b, c, d, e) => + instanceExt.drawElementsInstancedANGLE(a, b, c, d, e); + + gl.drawArraysInstanced = (a, b, c, d) => + instanceExt.drawArraysInstancedANGLE(a, b, c, d); + } + else + { + this.hasInstance = false; + } + } + } + + /** + * Binds geometry so that is can be drawn. Creating a Vao if required + * @private + * @param {PIXI.Geometry} geometry instance of geometry to bind + */ + bind(geometry, shader) + { + shader = shader || this.renderer.shader.shader; + + const gl = this.gl; + + // not sure the best way to address this.. + // currently different shaders require different VAOs for the same geometry + // Still mulling over the best way to solve this one.. + // will likely need to modify the shader attribute locations at run time! + let vaos = geometry.glVertexArrayObjects[this.CONTEXT_UID]; + + if (!vaos) + { + geometry.glVertexArrayObjects[this.CONTEXT_UID] = vaos = {}; + } + + const vao = vaos[shader.program.id] || this.initGeometryVao(geometry, shader.program); + + this._activeGeometry = geometry; + + if (this._activeVao !== vao) + { + this._activeVao = vao; + + if (this.hasVao) + { + gl.bindVertexArray(vao); + } + else + { + this.activateVao(geometry, shader.program); + } + } + + // TODO - optimise later! + // don't need to loop through if nothing changed! + // maybe look to add an 'autoupdate' to geometry? + this.updateBuffers(); + } + + reset() + { + this.unbind(); + } + + updateBuffers() + { + const geometry = this._activeGeometry; + const gl = this.gl; + + for (let i = 0; i < geometry.buffers.length; i++) + { + const buffer = geometry.buffers[i]; + + const glBuffer = buffer._glBuffers[this.CONTEXT_UID]; + + if (buffer._updateID !== glBuffer.updateID) + { + glBuffer.updateID = buffer._updateID; + + // TODO can cache this on buffer! maybe added a getter / setter? + const type = buffer.index ? gl.ELEMENT_ARRAY_BUFFER : gl.ARRAY_BUFFER; + const drawType = buffer.static ? gl.STATIC_DRAW : gl.DYNAMIC_DRAW; + + gl.bindBuffer(type, glBuffer.buffer); + + if (glBuffer.byteLength >= buffer.data.byteLength) + { + // offset is always zero for now! + gl.bufferSubData(type, 0, buffer.data); + } + else + { + gl.bufferData(type, buffer.data, drawType); + } + } + } + } + + checkCompatability(geometry, program) + { + // geometry must have at least all the attributes that the shader requires. + const geometryAttributes = geometry.attributes; + const shaderAttributes = program.attributeData; + + for (const j in shaderAttributes) + { + if (!geometryAttributes[j]) + { + throw new Error(`shader and geometry incompatible, geometry missing the "${j}" attribute`); + } + } + } + + /** + * Creates a Vao with the same structure as the geometry and stores it on the geometry. + * @private + * @param {PIXI.Geometry} geometry instance of geometry to to generate Vao for + */ + initGeometryVao(geometry, program) + { + this.checkCompatability(geometry, program); + + const gl = this.gl; + const CONTEXT_UID = this.CONTEXT_UID; + const buffers = geometry.buffers; + const attributes = geometry.attributes; + + const tempStride = {}; + const tempStart = {}; + + for (const j in buffers) + { + tempStride[j] = 0; + tempStart[j] = 0; + } + + for (const j in attributes) + { + if (!attributes[j].size && program.attributeData[j]) + { + attributes[j].size = program.attributeData[j].size; + } + + tempStride[attributes[j].buffer] += attributes[j].size * byteSizeMap[attributes[j].type]; + } + + for (const j in attributes) + { + const attribute = attributes[j]; + const attribSize = attribute.size; + + if (attribute.stride === undefined) + { + if (tempStride[attribute.buffer] === attribSize * byteSizeMap[attribute.type]) + { + attribute.stride = 0; + } + else + { + attribute.stride = tempStride[attribute.buffer]; + } + } + + if (attribute.start === undefined) + { + attribute.start = tempStart[attribute.buffer]; + + tempStart[attribute.buffer] += attribSize * byteSizeMap[attribute.type]; + } + } + + // first update - and create the buffers! + // only create a gl buffer if it actually gets + for (let i = 0; i < buffers.length; i++) + { + const buffer = buffers[i]; + + if (!buffer._glBuffers[CONTEXT_UID]) + { + buffer._glBuffers[CONTEXT_UID] = new GLBuffer(gl.createBuffer()); + } + } + + // TODO - maybe make this a data object? + // lets wait to see if we need to first! + const vao = gl.createVertexArray(); + + gl.bindVertexArray(vao); + + this.activateVao(geometry, program); + + gl.bindVertexArray(null); + + // add it to the cache! + geometry.glVertexArrayObjects[this.CONTEXT_UID][program.id] = vao; + + return vao; + } + + activateVao(geometry, program) + { + const gl = this.gl; + const CONTEXT_UID = this.CONTEXT_UID; + const buffers = geometry.buffers; + const attributes = geometry.attributes; + + if (geometry.indexBuffer) + { + // first update the index buffer if we have one.. + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, geometry.indexBuffer._glBuffers[CONTEXT_UID].buffer); + } + + let lastBuffer = null; + + // add a new one! + for (const j in attributes) + { + const attribute = attributes[j]; + const buffer = buffers[attribute.buffer]; + const glBuffer = buffer._glBuffers[CONTEXT_UID]; + + if (program.attributeData[j]) + { + if (lastBuffer !== glBuffer) + { + gl.bindBuffer(gl.ARRAY_BUFFER, glBuffer.buffer); + + lastBuffer = glBuffer; + } + + const location = program.attributeData[j].location; + + // TODO introduce state again + // we can optimise this for older devices that have no VAOs + gl.enableVertexAttribArray(location); + + gl.vertexAttribPointer(location, + attribute.size, + attribute.type || gl.FLOAT, + attribute.normalized, + attribute.stride, + attribute.start); + + if (attribute.instance) + { + // TODO calculate instance count based of this... + if (this.hasInstance) + { + gl.vertexAttribDivisor(location, 1); + } + else + { + throw new Error('geometry error, GPU Instancing is not supported on this device'); + } + } + } + } + } + + draw(type, size, start, instanceCount) + { + const gl = this.gl; + const geometry = this._activeGeometry; + + // TODO.. this should not change so maybe cache the function? + + if (geometry.indexBuffer) + { + if (geometry.instanced) + { + /* eslint-disable max-len */ + gl.drawElementsInstanced(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2, instanceCount || 1); + /* eslint-enable max-len */ + } + else + { + gl.drawElements(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2); + } + } + else + if (geometry.instanced) + { + // TODO need a better way to calculate size.. + gl.drawArraysInstanced(type, start, size || geometry.getSize(), instanceCount || 1); + } + else + { + gl.drawArrays(type, start, size || geometry.getSize()); + } + + return this; + } + + unbind() + { + this.gl.bindVertexArray(null); + this._activeVao = null; + this._activeGeometry = null; + } +} diff --git a/packages/core/src/geometry/utils/setVertexAttribArrays.js b/packages/core/src/geometry/utils/setVertexAttribArrays.js new file mode 100644 index 0000000..23b6959 --- /dev/null +++ b/packages/core/src/geometry/utils/setVertexAttribArrays.js @@ -0,0 +1,54 @@ +// var GL_MAP = {}; + +/** + * @param gl {WebGLRenderingContext} The current WebGL context + * @param attribs {*} + * @param state {*} + */ +export default function setVertexAttribArrays(gl, attribs, state) +{ + let i; + + if (state) + { + const tempAttribState = state.tempAttribState; + const attribState = state.attribState; + + for (i = 0; i < tempAttribState.length; i++) + { + tempAttribState[i] = false; + } + + // set the new attribs + for (i = 0; i < attribs.length; i++) + { + tempAttribState[attribs[i].attribute.location] = true; + } + + for (i = 0; i < attribState.length; i++) + { + if (attribState[i] !== tempAttribState[i]) + { + attribState[i] = tempAttribState[i]; + + if (state.attribState[i]) + { + gl.enableVertexAttribArray(i); + } + else + { + gl.disableVertexAttribArray(i); + } + } + } + } + else + { + for (i = 0; i < attribs.length; i++) + { + const attrib = attribs[i]; + + gl.enableVertexAttribArray(attrib.attribute.location); + } + } +} diff --git a/packages/core/src/index.js b/packages/core/src/index.js index 58c456a..fd1f547 100644 --- a/packages/core/src/index.js +++ b/packages/core/src/index.js @@ -1,30 +1,30 @@ import * as resources from './textures/resources'; -import * as systems from './renderers/systems'; +import * as systems from './systems'; -export { resources }; export { systems }; +export { resources }; -export { default as Renderer } from './renderers/Renderer'; -export { default as AbstractRenderer } from './renderers/AbstractRenderer'; -export { default as FrameBuffer } from './textures/FrameBuffer'; +export { default as System } from './System'; +export { default as Renderer } from './Renderer'; +export { default as AbstractRenderer } from './AbstractRenderer'; +export { default as FrameBuffer } from './framebuffer/FrameBuffer'; export { default as CubeTexture } from './textures/CubeTexture'; export { default as BaseTexture } from './textures/BaseTexture'; export { default as Texture } from './textures/Texture'; export { default as TextureMatrix } from './textures/TextureMatrix'; -export { default as RenderTexture } from './textures/RenderTexture'; -export { default as BaseRenderTexture } from './textures/BaseRenderTexture'; +export { default as RenderTexture } from './renderTexture/RenderTexture'; +export { default as BaseRenderTexture } from './renderTexture/BaseRenderTexture'; export { default as TextureUvs } from './textures/TextureUvs'; -export { default as State } from './renderers/State'; -export { default as ObjectRenderer } from './renderers/utils/ObjectRenderer'; -export { default as Quad } from './renderers/utils/Quad'; -export { default as QuadUv } from './renderers/utils/QuadUv'; -export { default as checkMaxIfStatmentsInShader } from './renderers/utils/checkMaxIfStatmentsInShader'; +export { default as State } from './state/State'; +export { default as ObjectRenderer } from './batch/ObjectRenderer'; +export { default as Quad } from './utils/Quad'; +export { default as QuadUv } from './utils/QuadUv'; +export { default as checkMaxIfStatmentsInShader } from './shader/utils/checkMaxIfStatmentsInShader'; export { default as Shader } from './shader/Shader'; export { default as Program } from './shader/Program'; export { default as UniformGroup } from './shader/UniformGroup'; -export { default as SpriteMaskFilter } from './renderers/filters/spriteMask/SpriteMaskFilter'; -export { default as Filter } from './renderers/filters/Filter'; +export { default as SpriteMaskFilter } from './filters/spriteMask/SpriteMaskFilter'; +export { default as Filter } from './filters/Filter'; export { default as Attribute } from './geometry/Attribute'; export { default as Buffer } from './geometry/Buffer'; export { default as Geometry } from './geometry/Geometry'; - diff --git a/packages/core/src/mask/MaskSystem.js b/packages/core/src/mask/MaskSystem.js new file mode 100644 index 0000000..99174c2 --- /dev/null +++ b/packages/core/src/mask/MaskSystem.js @@ -0,0 +1,199 @@ +import System from '../System'; +import AlphaMaskFilter from '../filters/spriteMask/SpriteMaskFilter'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class MaskSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + // TODO - we don't need both! + this.scissor = false; + this.scissorData = null; + this.scissorRenderTarget = null; + + this.enableScissor = false; + + this.alphaMaskPool = []; + this.alphaMaskIndex = 0; + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.DisplayObject} target - Display Object to push the mask to + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + push(target, maskData) + { + // TODO the root check means scissor rect will not + // be used on render textures more info here: + // https://github.com/pixijs/pixi.js/pull/3545 + + if (maskData.texture) + { + this.pushSpriteMask(target, maskData); + } + else if (this.enableScissor + && !this.scissor + && this.renderer._activeRenderTarget.root + && !this.renderer.stencil.stencilMaskStack.length + && maskData.isFastRect()) + { + const matrix = maskData.worldTransform; + + let rot = Math.atan2(matrix.b, matrix.a); + + // use the nearest degree! + rot = Math.round(rot * (180 / Math.PI)); + + if (rot % 90) + { + this.pushStencilMask(maskData); + } + else + { + this.pushScissorMask(target, maskData); + } + } + else + { + this.pushStencilMask(maskData); + } + } + + /** + * Removes the last mask from the mask stack and doesn't return it. + * + * @param {PIXI.DisplayObject} target - Display Object to pop the mask from + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + pop(target, maskData) + { + if (maskData.texture) + { + this.popSpriteMask(target, maskData); + } + else if (this.enableScissor && !this.renderer.stencil.stencilMaskStack.length) + { + this.popScissorMask(target, maskData); + } + else + { + this.popStencilMask(target, maskData); + } + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.RenderTarget} target - Display Object to push the sprite mask to + * @param {PIXI.Sprite} maskData - Sprite to be used as the mask + */ + pushSpriteMask(target, maskData) + { + let alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; + + if (!alphaMaskFilter) + { + alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; + } + + alphaMaskFilter[0].resolution = this.renderer.resolution; + alphaMaskFilter[0].maskSprite = maskData; + + // TODO - may cause issues! + target.filterArea = maskData.getBounds(true); + + this.renderer.filterSystem.pushFilter(target, alphaMaskFilter); + + this.alphaMaskIndex++; + } + + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popSpriteMask() + { + this.renderer.filterSystem.popFilter(); + this.alphaMaskIndex--; + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + pushStencilMask(maskData) + { + this.renderer.batch.flush(); + this.renderer.stencil.pushStencil(maskData); + } + + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popStencilMask() + { + // this.renderer.currentRenderer.stop(); + this.renderer.stencil.popStencil(); + } + + /** + * + * @param {PIXI.DisplayObject} target - Display Object to push the mask to + * @param {PIXI.Graphics} maskData - The masking data. + */ + pushScissorMask(target, maskData) + { + maskData.renderable = true; + + const renderTarget = this.renderer._activeRenderTarget; + + const bounds = maskData.getBounds(); + + bounds.fit(renderTarget.size); + maskData.renderable = false; + + this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); + + const resolution = this.renderer.resolution; + + this.renderer.gl.scissor( + bounds.x * resolution, + (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, + bounds.width * resolution, + bounds.height * resolution + ); + + this.scissorRenderTarget = renderTarget; + this.scissorData = maskData; + this.scissor = true; + } + + /** + * + * + */ + popScissorMask() + { + this.scissorRenderTarget = null; + this.scissorData = null; + this.scissor = false; + + // must be scissor! + const gl = this.renderer.gl; + + gl.disable(gl.SCISSOR_TEST); + } +} diff --git a/packages/core/src/mask/StencilSystem.js b/packages/core/src/mask/StencilSystem.js new file mode 100644 index 0000000..62796a5 --- /dev/null +++ b/packages/core/src/mask/StencilSystem.js @@ -0,0 +1,133 @@ +import System from '../System'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class StencilSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + this.stencilMaskStack = []; + } + + /** + * Changes the mask stack that is used by this System. + * + * @param {PIXI.Graphics[]} stencilMaskStack - The mask stack + */ + setMaskStack(stencilMaskStack) + { + const gl = this.renderer.gl; + + if (stencilMaskStack.length !== this.stencilMaskStack.length) + { + if (stencilMaskStack.length === 0) + { + gl.disable(gl.STENCIL_TEST); + } + else + { + gl.enable(gl.STENCIL_TEST); + } + } + + this.stencilMaskStack = stencilMaskStack; + } + + /** + * Applies the Mask and adds it to the current stencil stack. @alvin + * + * @param {PIXI.Graphics} graphics - The mask + */ + pushStencil(graphics) + { + this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); + + // this.renderer._activeRenderTarget.attachStencilBuffer(); + + const gl = this.renderer.gl; + const prevMaskCount = this.stencilMaskStack.length; + + if (prevMaskCount === 0) + { + gl.enable(gl.STENCIL_TEST); + } + + this.stencilMaskStack.push(graphics); + + // Increment the refference stencil value where the new mask overlaps with the old ones. + gl.colorMask(false, false, false, false); + gl.stencilFunc(gl.EQUAL, prevMaskCount, this._getBitwiseMask()); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.INCR); + this.renderer.plugins.graphics.render(graphics); + + this._useCurrent(); + } + + /** + * Removes the last mask from the stencil stack. @alvin + */ + popStencil() + { + this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); + + const gl = this.renderer.gl; + const graphics = this.stencilMaskStack.pop(); + + if (this.stencilMaskStack.length === 0) + { + // the stack is empty! + gl.disable(gl.STENCIL_TEST); + gl.clear(gl.STENCIL_BUFFER_BIT); + gl.clearStencil(0); + } + else + { + // Decrement the refference stencil value where the popped mask overlaps with the other ones + gl.colorMask(false, false, false, false); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.DECR); + this.renderer.plugins.graphics.render(graphics); + + this._useCurrent(); + } + } + + /** + * Setup renderer to use the current stencil data. + */ + _useCurrent() + { + const gl = this.renderer.gl; + + gl.colorMask(true, true, true, true); + gl.stencilFunc(gl.EQUAL, this.stencilMaskStack.length, this._getBitwiseMask()); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP); + } + + /** + * Fill 1s equal to the number of acitve stencil masks. + * + * @return {number} The bitwise mask. + */ + _getBitwiseMask() + { + return (1 << this.stencilMaskStack.length) - 1; + } + + /** + * Destroys the mask stack. + * + */ + destroy() + { + super.destroy(this); + + this.stencilMaskStack = null; + } +} diff --git a/packages/core/src/projection/ProjectionSystem.js b/packages/core/src/projection/ProjectionSystem.js new file mode 100644 index 0000000..6e63b73 --- /dev/null +++ b/packages/core/src/projection/ProjectionSystem.js @@ -0,0 +1,73 @@ +import System from '../System'; +import { Matrix } from '@pixi/math'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class ProjectionSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.projectionMatrix = new Matrix(); + } + + update(destinationFrame, sourceFrame, resolution, root) + { + this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; + this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; + + this.calculateProjection(this.destinationFrame, this.sourceFrame, resolution, root); + + this.renderer.globalUniforms.uniforms.projectionMatrix = this.projectionMatrix; + this.renderer.globalUniforms.update(); + } + + /** + * Updates the projection matrix based on a projection frame (which is a rectangle) + * + * @param {Rectangle} destinationFrame - The destination frame. + * @param {Rectangle} sourceFrame - The source frame. + */ + calculateProjection(destinationFrame, sourceFrame, resolution, root) + { + const pm = this.projectionMatrix; + + // I don't think we will need this line.. + // pm.identity(); + + if (!root) + { + pm.a = (1 / destinationFrame.width * 2) * resolution; + pm.d = (1 / destinationFrame.height * 2) * resolution; + + pm.tx = -1 - (sourceFrame.x * pm.a); + pm.ty = -1 - (sourceFrame.y * pm.d); + } + else + { + pm.a = (1 / destinationFrame.width * 2) * resolution; + pm.d = (-1 / destinationFrame.height * 2) * resolution; + + pm.tx = -1 - (sourceFrame.x * pm.a); + pm.ty = 1 - (sourceFrame.y * pm.d); + } + } + + /** + * Sets the transform of the active render target to the given matrix + * + * @param {PIXI.Matrix} matrix - The transformation matrix + */ + setTransform()// matrix) + { + // this._activeRenderTarget.transform = matrix; + } +} diff --git a/packages/core/src/renderTexture/BaseRenderTexture.js b/packages/core/src/renderTexture/BaseRenderTexture.js new file mode 100644 index 0000000..88eb83b --- /dev/null +++ b/packages/core/src/renderTexture/BaseRenderTexture.js @@ -0,0 +1,137 @@ +import BaseTexture from '../textures/BaseTexture'; +import FrameBuffer from '../framebuffer/FrameBuffer'; + +/** + * A BaseRenderTexture is a special texture that allows any PixiJS display object to be rendered to it. + * + * __Hint__: All DisplayObjects (i.e. Sprites) that render to a BaseRenderTexture should be preloaded + * otherwise black rectangles will be drawn instead. + * + * A BaseRenderTexture takes a snapshot of any Display Object given to its render method. The position + * and rotation of the given Display Objects is ignored. For example: + * + * ```js + * let renderer = PIXI.autoDetectRenderer(1024, 1024, { view: canvas, ratio: 1 }); + * let baseRenderTexture = new PIXI.BaseRenderTexture(renderer, 800, 600); + * let sprite = PIXI.Sprite.fromImage("spinObj_01.png"); + * + * sprite.position.x = 800/2; + * sprite.position.y = 600/2; + * sprite.anchor.x = 0.5; + * sprite.anchor.y = 0.5; + * + * baseRenderTexture.render(sprite); + * ``` + * + * The Sprite in this case will be rendered using its local transform. To render this sprite at 0,0 + * you can clear the transform + * + * ```js + * + * sprite.setTransform() + * + * let baseRenderTexture = new PIXI.BaseRenderTexture(100, 100); + * let renderTexture = new PIXI.RenderTexture(baseRenderTexture); + * + * renderer.render(sprite, renderTexture); // Renders to center of RenderTexture + * ``` + * + * @class + * @extends PIXI.BaseTexture + * @memberof PIXI + */ +export default class BaseRenderTexture extends BaseTexture +{ + /** + * @param {object} [options] + * @param {number} [options.width=100] - The width of the base render texture + * @param {number} [options.height=100] - The height of the base render texture + * @param {PIXI.SCALE_MODES} [options.scaleMode] - See {@link PIXI.SCALE_MODES} for possible values + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the texture being generated + */ + constructor(options) + { + if (typeof options === 'number') + { + /* eslint-disable prefer-rest-params */ + // Backward compatibility of signature + const width = arguments[0]; + const height = arguments[1]; + const scaleMode = arguments[2]; + const resolution = arguments[3]; + + options = { width, height, scaleMode, resolution }; + /* eslint-enable prefer-rest-params */ + } + + super(null, options); + + const { width, height } = options || {}; + + // Set defaults + this.mipmap = false; + this.width = Math.ceil(width) || 100; + this.height = Math.ceil(height) || 100; + this.valid = true; + + /** + * A map of renderer IDs to webgl renderTargets + * + * @private + * @member {object} + */ + // this._glRenderTargets = {}; + + /** + * A reference to the canvas render target (we only need one as this can be shared across renderers) + * + * @private + * @member {object} + */ + this._canvasRenderTarget = null; + + this.clearColor = [0, 0, 0, 0]; + + this.frameBuffer = new FrameBuffer(width * this.resolution, height * this.resolution) + .addColorTexture(0, this); + + // TODO - could this be added the systems? + + /** + * The data structure for the stencil masks + * + * @member {PIXI.Graphics[]} + */ + this.stencilMaskStack = []; + + /** + * The data structure for the filters + * + * @member {PIXI.Graphics[]} + */ + this.filterStack = [{}]; + } + + /** + * Resizes the BaseRenderTexture. + * + * @param {number} width - The width to resize to. + * @param {number} height - The height to resize to. + */ + resize(width, height) + { + width = Math.ceil(width); + height = Math.ceil(height); + this.frameBuffer.resize(width * this.resolution, height * this.resolution); + } + + /** + * Destroys this texture + * + */ + destroy() + { + super.destroy(true); + this.renderer = null; + } +} diff --git a/packages/core/src/renderTexture/RenderTexture.js b/packages/core/src/renderTexture/RenderTexture.js new file mode 100644 index 0000000..f1ecf1f --- /dev/null +++ b/packages/core/src/renderTexture/RenderTexture.js @@ -0,0 +1,153 @@ +import BaseRenderTexture from './BaseRenderTexture'; +import Texture from '../textures/Texture'; + +/** + * A RenderTexture is a special texture that allows any PixiJS display object to be rendered to it. + * + * __Hint__: All DisplayObjects (i.e. Sprites) that render to a RenderTexture should be preloaded + * otherwise black rectangles will be drawn instead. + * + * A RenderTexture takes a snapshot of any Display Object given to its render method. For example: + * + * ```js + * let renderer = PIXI.autoDetectRenderer(1024, 1024, { view: canvas, ratio: 1 }); + * let renderTexture = PIXI.RenderTexture.create(800, 600); + * let sprite = PIXI.Sprite.fromImage("spinObj_01.png"); + * + * sprite.position.x = 800/2; + * sprite.position.y = 600/2; + * sprite.anchor.x = 0.5; + * sprite.anchor.y = 0.5; + * + * renderer.render(sprite, renderTexture); + * ``` + * + * The Sprite in this case will be rendered using its local transform. To render this sprite at 0,0 + * you can clear the transform + * + * ```js + * + * sprite.setTransform() + * + * let renderTexture = new PIXI.RenderTexture.create(100, 100); + * + * renderer.render(sprite, renderTexture); // Renders to center of RenderTexture + * ``` + * + * @class + * @extends PIXI.Texture + * @memberof PIXI + */ +export default class RenderTexture extends Texture +{ + /** + * @param {PIXI.BaseRenderTexture} baseRenderTexture - The renderer used for this RenderTexture + * @param {PIXI.Rectangle} [frame] - The rectangle frame of the texture to show + */ + constructor(baseRenderTexture, frame) + { + // support for legacy.. + let _legacyRenderer = null; + + if (!(baseRenderTexture instanceof BaseRenderTexture)) + { + /* eslint-disable prefer-rest-params, no-console */ + const width = arguments[1]; + const height = arguments[2]; + const scaleMode = arguments[3]; + const resolution = arguments[4]; + + // we have an old render texture.. + console.warn(`Please use RenderTexture.create(${width}, ${height}) instead of the ctor directly.`); + _legacyRenderer = arguments[0]; + /* eslint-enable prefer-rest-params, no-console */ + + frame = null; + baseRenderTexture = new BaseRenderTexture({ + width, + height, + scaleMode, + resolution, + }); + } + + /** + * The base texture object that this texture uses + * + * @member {BaseTexture} + */ + super(baseRenderTexture, frame); + + this.legacyRenderer = _legacyRenderer; + + /** + * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. + * + * @member {boolean} + */ + this.valid = true; + + /** + * FilterSystem temporary storage + * @private + * @member {PIXI.Rectangle} + */ + this.filterFrame = null; + + this._updateUvs(); + } + + /** + * Resizes the RenderTexture. + * + * @param {number} width - The width to resize to. + * @param {number} height - The height to resize to. + * @param {boolean} [resizeBaseTexture=true] - Should the baseTexture.width and height values be resized as well? + */ + resize(width, height, resizeBaseTexture = true) + { + width = Math.ceil(width); + height = Math.ceil(height); + + // TODO - could be not required.. + this.valid = (width > 0 && height > 0); + + this._frame.width = this.orig.width = width; + this._frame.height = this.orig.height = height; + + if (resizeBaseTexture) + { + this.baseTexture.resize(width, height); + } + + this._updateUvs(); + } + + /** + * A short hand way of creating a render texture. + * + * @param {object} [options] - Options + * @param {number} [options.width=100] - The width of the render texture + * @param {number} [options.height=100] - The height of the render texture + * @param {number} [options.scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the texture being generated + * @return {PIXI.RenderTexture} The new render texture + */ + static create(options) + { + // fallback, old-style: create(width, height, scaleMode, resolution) + if (typeof options === 'number') + { + /* eslint-disable prefer-rest-params */ + options = { + width: options, + height: arguments[1], + scaleMode: arguments[2], + resolution: arguments[3], + }; + /* eslint-enable prefer-rest-params */ + } + + return new RenderTexture(new BaseRenderTexture(options)); + } +} diff --git a/packages/core/src/renderTexture/RenderTextureSystem.js b/packages/core/src/renderTexture/RenderTextureSystem.js new file mode 100644 index 0000000..8d0ca61 --- /dev/null +++ b/packages/core/src/renderTexture/RenderTextureSystem.js @@ -0,0 +1,115 @@ +import System from '../System'; +import { Rectangle } from '@pixi/math'; + +const tempRect = new Rectangle(); + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class RenderTextureSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.clearColor = renderer._backgroundColorRgba; + + // TODO moe this property somewhere else! + this.defaultMaskStack = []; + this.defaultFilterStack = [{}]; + + // empty render texture? + this.renderTexture = null; + + this.destinationFrame = new Rectangle(); + } + + bind(renderTexture, sourceFrame, destinationFrame) + { + // TODO - do we want this?? + if (this.renderTexture === renderTexture) return; + this.renderTexture = renderTexture; + + const renderer = this.renderer; + + if (renderTexture) + { + const baseTexture = renderTexture.baseTexture; + + if (!destinationFrame) + { + tempRect.width = baseTexture.realWidth; + tempRect.height = baseTexture.realHeight; + + destinationFrame = tempRect; + } + + if (!sourceFrame) + { + sourceFrame = destinationFrame; + } + + this.renderer.framebuffer.bind(baseTexture.frameBuffer, destinationFrame); + + this.renderer.projection.update(destinationFrame, sourceFrame, baseTexture.resolution, false); + this.renderer.stencil.setMaskStack(baseTexture.stencilMaskStack); + } + else + { + // TODO these validation checks happen deeper down.. + // thing they can be avoided.. + if (!destinationFrame) + { + tempRect.width = renderer.width; + tempRect.height = renderer.height; + + destinationFrame = tempRect; + } + + if (!sourceFrame) + { + sourceFrame = destinationFrame; + } + + renderer.framebuffer.bind(null, destinationFrame); + + // TODO store this.. + this.renderer.projection.update(destinationFrame, sourceFrame, this.renderer.resolution, true); + this.renderer.stencil.setMaskStack(this.defaultMaskStack); + } + + this.destinationFrame.copyFrom(destinationFrame); + } + + /** + * Erases the render texture and fills the drawing area with a colour + * + * @param {number} [clearColor] - The colour + * @return {PIXI.Renderer} Returns itself. + */ + clear(clearColor) + { + if (this.renderTexture) + { + clearColor = clearColor || this.renderTexture.baseTexture.clearColor; + } + else + { + clearColor = clearColor || this.clearColor; + } + + this.renderer.framebuffer.clear(clearColor[0], clearColor[1], clearColor[2], clearColor[3]); + } + + resize()// screenWidth, screenHeight) + { + // resize the root only! + this.bind(null); + } +} diff --git a/packages/core/src/renderers/AbstractRenderer.js b/packages/core/src/renderers/AbstractRenderer.js deleted file mode 100644 index 928670f..0000000 --- a/packages/core/src/renderers/AbstractRenderer.js +++ /dev/null @@ -1,353 +0,0 @@ -import { hex2string, hex2rgb } from '@pixi/utils'; -import { Matrix, Rectangle } from '@pixi/math'; -import { RENDERER_TYPE } from '@pixi/constants'; -import { settings } from '@pixi/settings'; -import { Container } from '@pixi/display'; -import RenderTexture from '../textures/RenderTexture'; -import EventEmitter from 'eventemitter3'; - -const tempMatrix = new Matrix(); - -/** - * 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 EventEmitter - * @memberof PIXI - */ -export default class AbstractRenderer extends EventEmitter -{ - // eslint-disable-next-line valid-jsdoc - /** - * @param {string} system - The name of the system this renderer is for. - * @param {object} [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.transparent=false] - If the render view is transparent, default false - * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false - * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) - * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. The - * resolution of the renderer retina would be 2. - * @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 {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when rendering, - * stopping pixel interpolation. - */ - constructor(system, options, arg2, arg3) - { - super(); - - // Support for constructor(system, screenWidth, screenHeight, options) - if (typeof options === 'number') - { - options = Object.assign({ - width: options, - height: arg2 || settings.RENDER_OPTIONS.height, - }, arg3); - } - - // 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 = RENDERER_TYPE.UNKNOWN; - - /** - * Measurements of the screen. (0, 0, screenWidth, screenHeight) - * - * Its safe to use as filterArea or hitArea for 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 1 - */ - this.resolution = options.resolution || settings.RESOLUTION; - - /** - * Whether the render view is transparent - * - * @member {boolean} - */ - this.transparent = options.transparent; - - /** - * Whether css dimensions of canvas view should be resized to screen dimensions automatically - * - * @member {boolean} - */ - this.autoResize = options.autoResize || false; - - /** - * Tracks the blend modes useful for this renderer. - * - * @member {object} - */ - this.blendModes = null; - - /** - * 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; - - /** - * If true PixiJS will Math.floor() x/y values when rendering, stopping pixel interpolation. - * Handy for crisp pixel art and speed on legacy devices. - * - * @member {boolean} - */ - this.roundPixels = options.roundPixels; - - /** - * The background color as a number. - * - * @member {number} - * @private - */ - this._backgroundColor = 0x000000; - - /** - * The background color as an [R, G, B] array. - * - * @member {number[]} - * @private - */ - this._backgroundColorRgba = [0, 0, 0, 0]; - - /** - * The background color as a string. - * - * @member {string} - * @private - */ - this._backgroundColorString = '#000000'; - - this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter - - /** - * This temporary display object used as the parent of the currently being rendered item - * - * @member {PIXI.DisplayObject} - * @private - */ - this._tempDisplayObjectParent = new Container(); - - /** - * The last root object that the renderer tried to render. - * - * @member {PIXI.DisplayObject} - * @private - */ - this._lastObjectRendered = this._tempDisplayObjectParent; - - /** - * Collection of plugins. - * @readonly - * @member {object} - */ - this.plugins = {}; - } - - /** - * Initialize the plugins. - * - * @protected - * @param {object} staticMap - The dictionary of staticly saved plugins. - */ - initPlugins(staticMap) - { - 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() - { - return this.view.width; - } - - /** - * Same as view.height, actual number of pixels in the canvas by vertical - * - * @member {number} - * @readonly - * @default 600 - */ - get height() - { - return this.view.height; - } - - /** - * Resizes the screen and canvas to the specified width and height - * Canvas dimensions are multiplied by resolution - * - * @param {number} screenWidth - the new width of the screen - * @param {number} screenHeight - the new height of the screen - */ - resize(screenWidth, screenHeight) - { - this.screen.width = screenWidth; - this.screen.height = screenHeight; - - this.view.width = screenWidth * this.resolution; - this.view.height = screenHeight * this.resolution; - - if (this.autoResize) - { - this.view.style.width = `${screenWidth}px`; - this.view.style.height = `${screenHeight}px`; - } - } - - /** - * 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 {PIXI.DisplayObject} displayObject - The displayObject the object will be generated from - * @param {number} scaleMode - Should be one of the scaleMode consts - * @param {number} resolution - The resolution / device pixel ratio of the texture being generated - * @param {PIXI.Rectangle} [region] - The region of the displayObject, that shall be rendered, - * if no region is specified, defaults to the local bounds of the displayObject. - * @return {PIXI.Texture} a texture of the graphics object - */ - generateTexture(displayObject, scaleMode, resolution, region) - { - region = region || displayObject.getLocalBounds(); - - // 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(region.width | 0, region.height | 0, scaleMode, resolution); - - tempMatrix.tx = -region.x; - tempMatrix.ty = -region.y; - - this.render(displayObject, renderTexture, false, tempMatrix, true); - - return renderTexture; - } - - /** - * Removes everything from the renderer and optionally removes the Canvas DOM element. - * - * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. - */ - destroy(removeView) - { - 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); - } - - this.plugins = null; - - this.type = RENDERER_TYPE.UNKNOWN; - - this.view = null; - - this.screen = null; - - this.resolution = 0; - - this.transparent = false; - - this.autoResize = false; - - this.blendModes = null; - - this.options = null; - - this.preserveDrawingBuffer = false; - this.clearBeforeRender = false; - - this.roundPixels = false; - - this._backgroundColor = 0; - this._backgroundColorRgba = null; - this._backgroundColorString = null; - - this._tempDisplayObjectParent = null; - this._lastObjectRendered = null; - } - - /** - * The background color to fill if not transparent - * - * @member {number} - */ - get backgroundColor() - { - return this._backgroundColor; - } - - set backgroundColor(value) // eslint-disable-line require-jsdoc - { - this._backgroundColor = value; - this._backgroundColorString = hex2string(value); - hex2rgb(value, this._backgroundColorRgba); - } -} diff --git a/packages/core/src/renderers/Renderer.js b/packages/core/src/renderers/Renderer.js deleted file mode 100644 index a2020b1..0000000 --- a/packages/core/src/renderers/Renderer.js +++ /dev/null @@ -1,329 +0,0 @@ -import AbstractRenderer from './AbstractRenderer'; -import { sayHello } from '@pixi/utils'; -import MaskSystem from './systems/MaskSystem'; -import StencilSystem from './systems/StencilSystem'; -import FilterSystem from './systems/filter/FilterSystem'; -import FramebufferSystem from './systems/FramebufferSystem'; -import RenderTextureSystem from './systems/RenderTextureSystem'; -import TextureSystem from './systems/textures/TextureSystem'; -import ProjectionSystem from './systems/ProjectionSystem'; -import StateSystem from './systems/StateSystem'; -import GeometrySystem from './systems/geometry/GeometrySystem'; -import ShaderSystem from './systems/shader/ShaderSystem'; -import ContextSystem from './systems/ContextSystem'; -import BatchSystem from './systems/BatchSystem'; -import TextureGCSystem from './systems/textures/TextureGCSystem'; -import { RENDERER_TYPE } from '@pixi/constants'; -import UniformGroup from '../shader/UniformGroup'; -import { Matrix } from '@pixi/math'; -import Runner from 'mini-runner'; - -/** - * 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 Render works by automatically managing webGLBatchs. - * 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 :) - * - * @class - * @memberof PIXI - * @extends PIXI.AbstractRenderer - */ -export default class Renderer extends AbstractRenderer -{ - // eslint-disable-next-line valid-jsdoc - /** - * - * @param {object} [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.transparent=false] - If the render view is transparent, default false - * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false - * @param {boolean} [options.antialias=false] - sets antialias. If not available natively then FXAA - * antialiasing is used - * @param {boolean} [options.forceFXAA=false] - forces FXAA antialiasing to be used over native. - * FXAA is faster, but may not always look as great - * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. - * The resolution of the renderer retina would be 2. - * @param {boolean} [options.clearBeforeRender=true] - This sets if the renderer will clear - * the canvas or not before the new render pass. If you wish to set this to false, you *must* set - * preserveDrawingBuffer to `true`. - * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, - * enable this if you need to call toDataUrl on the webgl context. - * @param {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when - * rendering, stopping pixel interpolation. - * @param {number} [options.backgroundColor=0x000000] - The background color of the rendered area - * (shown if not transparent). - * @param {boolean} [options.legacy=false] - If true PixiJS will aim to ensure compatibility - * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. - * @param {string} [options.powerPreference] - Parameter passed to webgl context, set to "high-performance" - * for devices with dual graphics card - */ - constructor(options, arg2, arg3) - { - super('WebGL', options, arg2, arg3); - - /** - * The type of this renderer as a standardised const - * - * @member {number} - * @see PIXI.RENDERER_TYPE - */ - this.type = RENDERER_TYPE.WEBGL; - - // this will be set by the contextSystem (this.context) - this.gl = null; - this.CONTEXT_UID = 0; - this.legacy = !!options.legacy; - - // TODO legacy! - - // runners! - this.runners = { - destroy: new Runner('destroy'), - contextChange: new Runner('contextChange', 1), - reset: new Runner('reset'), - update: new Runner('update'), - postrender: new Runner('postrender'), - prerender: new Runner('prerender'), - resize: new Runner('resize', 2), - }; - - 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(GeometrySystem, 'geometry') - .addSystem(FramebufferSystem, 'framebuffer') - .addSystem(StencilSystem, 'stencil') - .addSystem(ProjectionSystem, 'projection') - .addSystem(TextureGCSystem, 'textureGC') - .addSystem(FilterSystem, 'filter') - .addSystem(RenderTextureSystem, 'renderTexture') - .addSystem(BatchSystem, 'batch'); - - this.initPlugins(Renderer.__plugins); - - /** - * The options passed in to create a new webgl context. - * - * @member {object} - * @private - */ - if (options.context) - { - this.context.initFromContext(options.context); - } - else - { - this.context.initFromOptions({ - alpha: this.transparent, - antialias: options.antialias, - premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', - stencil: true, - 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); - } - - addSystem(_class, name) - { - if (!name) - { - name = _class.name; - } - - // TODO - read name from class.name.. - - /* - if(name.includes('System')) - { - name = name.replace('System', ''); - name = name.charAt(0).toLowerCase() + name.slice(1); - } - */ - - const system = new _class(this); - - if (this[name]) - { - throw new Error(`Whoops! ${name} is already a manger`); - } - - this[name] = system; - - for (const i in this.runners) - { - this.runners[i].add(system); - } - - return this; - - /** - * 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. - */ - } - - /** - * Renders the object to its webGL view - * - * @param {PIXI.DisplayObject} displayObject - the object to be rendered - * @param {PIXI.RenderTexture} renderTexture - The render texture to render to. - * @param {boolean} [clear] - Should the canvas be cleared before the new render - * @param {PIXI.Transform} [transform] - A transform to apply to the render texture before rendering. - * @param {boolean} [skipUpdateTransform] - Should we skip the update transform pass? - */ - render(displayObject, renderTexture, clear, transform, skipUpdateTransform) - { - // can be handy to know! - this.renderingToScreen = !renderTexture; - - this.runners.prerender.run(); - this.emit('prerender'); - - // 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.parent; - - displayObject.parent = this._tempDisplayObjectParent; - displayObject.updateTransform(); - displayObject.parent = 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.run(); - - this.emit('postrender'); - } - - /** - * Resizes the webGL view to the specified width and height. - * - * @param {number} screenWidth - the new width of the screen - * @param {number} screenHeight - the new height of the screen - */ - resize(screenWidth, screenHeight) - { - AbstractRenderer.prototype.resize.call(this, screenWidth, screenHeight); - - this.runners.resize.run(screenWidth, screenHeight); - } - - /** - * Resets the WebGL state so you can render things however you fancy! - * - * @return {PIXI.Renderer} Returns itself. - */ - reset() - { - this.runners.reset.run(); - - return this; - } - - clear() - { - this.framebuffer.bind(); - this.framebuffer.clear(); - } - - /** - * Removes everything from the renderer (event listeners, spritebatch, etc...) - * - * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. - * See: https://github.com/pixijs/pixi.js/issues/2233 - */ - destroy(removeView) - { - this.runners.destroy.run(); - - // call base destroy - super.destroy(removeView); - - // TODO nullify all the managers.. - this.gl = null; - } - - /** - * 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 - * builds and excluding plugins. - * @name PIXI.Renderer#plugins - * @type {object} - * @readonly - * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. - * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. - * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. - * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. - */ - - /** - * Adds a plugin to the renderer. - * - * @method - * @param {string} pluginName - The name of the plugin. - * @param {Function} ctor - The constructor function or class for the plugin. - */ - static registerPlugin(pluginName, ctor) - { - Renderer.__plugins = Renderer.__plugins || {}; - Renderer.__plugins[pluginName] = ctor; - } -} diff --git a/packages/core/src/renderers/State.js b/packages/core/src/renderers/State.js deleted file mode 100644 index fa3f221..0000000 --- a/packages/core/src/renderers/State.js +++ /dev/null @@ -1,163 +0,0 @@ -/* eslint-disable max-len */ - -const BLEND = 0; -const OFFSET = 1; -const CULLING = 2; -const DEPTH_TEST = 3; -const WINDING = 4; - -/** - * This is a webGL state. It is passed The WebGL StateManager. - * Each mesh renderered may require webGL to be in a different state. - * For example you may want different blend mode or to enable polygon offsets - * - * @class - * @memberof PIXI - */ -export default class State -{ - /** - * - */ - constructor() - { - this.data = 0; - - this.blendMode = 0; - this.polygonOffset = 0; - - this.blend = true; - // this.depthTest = true; - } - - /** - * Activates blending of the computed fragment color values - * - * @member {boolean} - */ - get blend() - { - return !!(this.data & (1 << BLEND)); - } - - set blend(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << BLEND)) !== value) - { - this.data ^= (1 << BLEND); - } - } - - /** - * Activates adding an offset to depth values of polygon's fragments - * - * @member {boolean} - * @default false - */ - get offsets() - { - return !!(this.data & (1 << OFFSET)); - } - - set offsets(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << OFFSET)) !== value) - { - this.data ^= (1 << OFFSET); - } - } - - /** - * Activates culling of polygons. - * - * @member {boolean} - * @default false - */ - get culling() - { - return !!(this.data & (1 << CULLING)); - } - - set culling(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << CULLING)) !== value) - { - this.data ^= (1 << CULLING); - } - } - - /** - * Activates depth comparisons and updates to the depth buffer. - * - * @member {boolean} - * @default false - */ - get depthTest() - { - return !!(this.data & (1 << DEPTH_TEST)); - } - - set depthTest(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << DEPTH_TEST)) !== value) - { - this.data ^= (1 << DEPTH_TEST); - } - } - - /** - * Specifies whether or not front or back-facing polygons can be culled. - * @member {boolean} - * @default false - */ - get clockwiseFrontFace() - { - return !!(this.data & (1 << WINDING)); - } - - set clockwiseFrontFace(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << WINDING)) !== value) - { - this.data ^= (1 << WINDING); - } - } - - /** - * The blend mode to be applied when this state is set. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. - * Setting this mode to anything other than NO_BLEND will automatically switch blending on. - * - * @member {boolean} - * @default PIXI.BLEND_MODES.NORMAL - * @see PIXI.BLEND_MODES - */ - get blendMode() - { - return this._blendMode; - } - - set blendMode(value) // eslint-disable-line require-jsdoc - { - // 17 is NO BLEND - this.blend = (value !== 17); - this._blendMode = value; - } - - /** - * The polygon offset. Setting this property to anything other than 0 will automatically enable poygon offset fill. - * - * @member {number} - * @default 0 - */ - get polygonOffset() - { - return this._polygonOffset; - } - - set polygonOffset(value) // eslint-disable-line require-jsdoc - { - this.offsets = !!value; - this._polygonOffset = value; - } -} - diff --git a/packages/core/src/AbstractRenderer.js b/packages/core/src/AbstractRenderer.js new file mode 100644 index 0000000..64b2eb1 --- /dev/null +++ b/packages/core/src/AbstractRenderer.js @@ -0,0 +1,353 @@ +import { hex2string, hex2rgb } from '@pixi/utils'; +import { Matrix, Rectangle } from '@pixi/math'; +import { RENDERER_TYPE } from '@pixi/constants'; +import { settings } from '@pixi/settings'; +import { Container } from '@pixi/display'; +import RenderTexture from './renderTexture/RenderTexture'; +import EventEmitter from 'eventemitter3'; + +const tempMatrix = new Matrix(); + +/** + * 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 EventEmitter + * @memberof PIXI + */ +export default class AbstractRenderer extends EventEmitter +{ + // eslint-disable-next-line valid-jsdoc + /** + * @param {string} system - The name of the system this renderer is for. + * @param {object} [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.transparent=false] - If the render view is transparent, default false + * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false + * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. The + * resolution of the renderer retina would be 2. + * @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 {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when rendering, + * stopping pixel interpolation. + */ + constructor(system, options, arg2, arg3) + { + super(); + + // Support for constructor(system, screenWidth, screenHeight, options) + if (typeof options === 'number') + { + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + }, arg3); + } + + // 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 = RENDERER_TYPE.UNKNOWN; + + /** + * Measurements of the screen. (0, 0, screenWidth, screenHeight) + * + * Its safe to use as filterArea or hitArea for 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 1 + */ + this.resolution = options.resolution || settings.RESOLUTION; + + /** + * Whether the render view is transparent + * + * @member {boolean} + */ + this.transparent = options.transparent; + + /** + * Whether css dimensions of canvas view should be resized to screen dimensions automatically + * + * @member {boolean} + */ + this.autoResize = options.autoResize || false; + + /** + * Tracks the blend modes useful for this renderer. + * + * @member {object} + */ + this.blendModes = null; + + /** + * 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; + + /** + * If true PixiJS will Math.floor() x/y values when rendering, stopping pixel interpolation. + * Handy for crisp pixel art and speed on legacy devices. + * + * @member {boolean} + */ + this.roundPixels = options.roundPixels; + + /** + * The background color as a number. + * + * @member {number} + * @private + */ + this._backgroundColor = 0x000000; + + /** + * The background color as an [R, G, B] array. + * + * @member {number[]} + * @private + */ + this._backgroundColorRgba = [0, 0, 0, 0]; + + /** + * The background color as a string. + * + * @member {string} + * @private + */ + this._backgroundColorString = '#000000'; + + this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter + + /** + * This temporary display object used as the parent of the currently being rendered item + * + * @member {PIXI.DisplayObject} + * @private + */ + this._tempDisplayObjectParent = new Container(); + + /** + * The last root object that the renderer tried to render. + * + * @member {PIXI.DisplayObject} + * @private + */ + this._lastObjectRendered = this._tempDisplayObjectParent; + + /** + * Collection of plugins. + * @readonly + * @member {object} + */ + this.plugins = {}; + } + + /** + * Initialize the plugins. + * + * @protected + * @param {object} staticMap - The dictionary of staticly saved plugins. + */ + initPlugins(staticMap) + { + 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() + { + return this.view.width; + } + + /** + * Same as view.height, actual number of pixels in the canvas by vertical + * + * @member {number} + * @readonly + * @default 600 + */ + get height() + { + return this.view.height; + } + + /** + * Resizes the screen and canvas to the specified width and height + * Canvas dimensions are multiplied by resolution + * + * @param {number} screenWidth - the new width of the screen + * @param {number} screenHeight - the new height of the screen + */ + resize(screenWidth, screenHeight) + { + this.screen.width = screenWidth; + this.screen.height = screenHeight; + + this.view.width = screenWidth * this.resolution; + this.view.height = screenHeight * this.resolution; + + if (this.autoResize) + { + this.view.style.width = `${screenWidth}px`; + this.view.style.height = `${screenHeight}px`; + } + } + + /** + * 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 {PIXI.DisplayObject} displayObject - The displayObject the object will be generated from + * @param {number} scaleMode - Should be one of the scaleMode consts + * @param {number} resolution - The resolution / device pixel ratio of the texture being generated + * @param {PIXI.Rectangle} [region] - The region of the displayObject, that shall be rendered, + * if no region is specified, defaults to the local bounds of the displayObject. + * @return {PIXI.Texture} a texture of the graphics object + */ + generateTexture(displayObject, scaleMode, resolution, region) + { + region = region || displayObject.getLocalBounds(); + + // 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(region.width | 0, region.height | 0, scaleMode, resolution); + + tempMatrix.tx = -region.x; + tempMatrix.ty = -region.y; + + this.render(displayObject, renderTexture, false, tempMatrix, true); + + return renderTexture; + } + + /** + * Removes everything from the renderer and optionally removes the Canvas DOM element. + * + * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. + */ + destroy(removeView) + { + 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); + } + + this.plugins = null; + + this.type = RENDERER_TYPE.UNKNOWN; + + this.view = null; + + this.screen = null; + + this.resolution = 0; + + this.transparent = false; + + this.autoResize = false; + + this.blendModes = null; + + this.options = null; + + this.preserveDrawingBuffer = false; + this.clearBeforeRender = false; + + this.roundPixels = false; + + this._backgroundColor = 0; + this._backgroundColorRgba = null; + this._backgroundColorString = null; + + this._tempDisplayObjectParent = null; + this._lastObjectRendered = null; + } + + /** + * The background color to fill if not transparent + * + * @member {number} + */ + get backgroundColor() + { + return this._backgroundColor; + } + + set backgroundColor(value) // eslint-disable-line require-jsdoc + { + this._backgroundColor = value; + this._backgroundColorString = hex2string(value); + hex2rgb(value, this._backgroundColorRgba); + } +} diff --git a/packages/core/src/Renderer.js b/packages/core/src/Renderer.js new file mode 100644 index 0000000..2227e78 --- /dev/null +++ b/packages/core/src/Renderer.js @@ -0,0 +1,338 @@ +import AbstractRenderer from './AbstractRenderer'; +import { sayHello } from '@pixi/utils'; +import MaskSystem from './mask/MaskSystem'; +import StencilSystem from './mask/StencilSystem'; +import FilterSystem from './filters/FilterSystem'; +import FramebufferSystem from './framebuffer/FramebufferSystem'; +import RenderTextureSystem from './renderTexture/RenderTextureSystem'; +import TextureSystem from './textures/TextureSystem'; +import ProjectionSystem from './projection/ProjectionSystem'; +import StateSystem from './state/StateSystem'; +import GeometrySystem from './geometry/GeometrySystem'; +import ShaderSystem from './shader/ShaderSystem'; +import ContextSystem from './context/ContextSystem'; +import BatchSystem from './batch/BatchSystem'; +import TextureGCSystem from './textures/TextureGCSystem'; +import { RENDERER_TYPE } from '@pixi/constants'; +import UniformGroup from './shader/UniformGroup'; +import { Matrix } from '@pixi/math'; +import Runner from 'mini-runner'; + +/** + * 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 Render works by automatically managing webGLBatchs. + * 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 :) + * + * @class + * @memberof PIXI + * @extends PIXI.AbstractRenderer + */ +export default class Renderer extends AbstractRenderer +{ + // eslint-disable-next-line valid-jsdoc + /** + * + * @param {object} [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.transparent=false] - If the render view is transparent, default false + * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false + * @param {boolean} [options.antialias=false] - sets antialias. If not available natively then FXAA + * antialiasing is used + * @param {boolean} [options.forceFXAA=false] - forces FXAA antialiasing to be used over native. + * FXAA is faster, but may not always look as great + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. + * The resolution of the renderer retina would be 2. + * @param {boolean} [options.clearBeforeRender=true] - This sets if the renderer will clear + * the canvas or not before the new render pass. If you wish to set this to false, you *must* set + * preserveDrawingBuffer to `true`. + * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, + * enable this if you need to call toDataUrl on the webgl context. + * @param {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when + * rendering, stopping pixel interpolation. + * @param {number} [options.backgroundColor=0x000000] - The background color of the rendered area + * (shown if not transparent). + * @param {boolean} [options.legacy=false] - If true PixiJS will aim to ensure compatibility + * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. + * @param {string} [options.powerPreference] - Parameter passed to webgl context, set to "high-performance" + * for devices with dual graphics card + */ + constructor(options, arg2, arg3) + { + super('WebGL', options, arg2, arg3); + + /** + * The type of this renderer as a standardised const + * + * @member {number} + * @see PIXI.RENDERER_TYPE + */ + this.type = RENDERER_TYPE.WEBGL; + + // this will be set by the contextSystem (this.context) + this.gl = null; + this.CONTEXT_UID = 0; + this.legacy = !!options.legacy; + + // TODO legacy! + + // runners! + this.runners = { + destroy: new Runner('destroy'), + contextChange: new Runner('contextChange', 1), + reset: new Runner('reset'), + update: new Runner('update'), + postrender: new Runner('postrender'), + prerender: new Runner('prerender'), + resize: new Runner('resize', 2), + }; + + 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(GeometrySystem, 'geometry') + .addSystem(FramebufferSystem, 'framebuffer') + .addSystem(StencilSystem, 'stencil') + .addSystem(ProjectionSystem, 'projection') + .addSystem(TextureGCSystem, 'textureGC') + .addSystem(FilterSystem, 'filter') + .addSystem(RenderTextureSystem, 'renderTexture') + .addSystem(BatchSystem, 'batch'); + + this.initPlugins(Renderer.__plugins); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ + if (options.context) + { + this.context.initFromContext(options.context); + } + else + { + this.context.initFromOptions({ + alpha: this.transparent, + antialias: options.antialias, + premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', + stencil: true, + 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); + } + + /** + * Add a new system to the renderer. + * @param {class} ClassRef - Class reference + * @param {string} [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. + * @return {PIXI.Renderer} Return instance of renderer + */ + addSystem(ClassRef, name) + { + if (!name) + { + name = ClassRef.name; + } + + // TODO - read name from class.name.. + + /* + if(name.includes('System')) + { + name = name.replace('System', ''); + name = name.charAt(0).toLowerCase() + name.slice(1); + } + */ + + const system = new ClassRef(this); + + if (this[name]) + { + throw new Error(`Whoops! ${name} is already a manger`); + } + + this[name] = system; + + for (const i in this.runners) + { + this.runners[i].add(system); + } + + return this; + + /** + * 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. + */ + } + + /** + * Renders the object to its webGL view + * + * @param {PIXI.DisplayObject} displayObject - the object to be rendered + * @param {PIXI.RenderTexture} renderTexture - The render texture to render to. + * @param {boolean} [clear] - Should the canvas be cleared before the new render + * @param {PIXI.Transform} [transform] - A transform to apply to the render texture before rendering. + * @param {boolean} [skipUpdateTransform] - Should we skip the update transform pass? + */ + render(displayObject, renderTexture, clear, transform, skipUpdateTransform) + { + // can be handy to know! + this.renderingToScreen = !renderTexture; + + this.runners.prerender.run(); + this.emit('prerender'); + + // 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.parent; + + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = 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.run(); + + this.emit('postrender'); + } + + /** + * Resizes the webGL view to the specified width and height. + * + * @param {number} screenWidth - the new width of the screen + * @param {number} screenHeight - the new height of the screen + */ + resize(screenWidth, screenHeight) + { + AbstractRenderer.prototype.resize.call(this, screenWidth, screenHeight); + + this.runners.resize.run(screenWidth, screenHeight); + } + + /** + * Resets the WebGL state so you can render things however you fancy! + * + * @return {PIXI.Renderer} Returns itself. + */ + reset() + { + this.runners.reset.run(); + + return this; + } + + clear() + { + this.framebuffer.bind(); + this.framebuffer.clear(); + } + + /** + * Removes everything from the renderer (event listeners, spritebatch, etc...) + * + * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. + * See: https://github.com/pixijs/pixi.js/issues/2233 + */ + destroy(removeView) + { + this.runners.destroy.run(); + + // call base destroy + super.destroy(removeView); + + // TODO nullify all the managers.. + this.gl = null; + } + + /** + * 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 + * builds and excluding plugins. + * @name PIXI.Renderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. + */ + + /** + * Adds a plugin to the renderer. + * + * @method + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + static registerPlugin(pluginName, ctor) + { + Renderer.__plugins = Renderer.__plugins || {}; + Renderer.__plugins[pluginName] = ctor; + } +} diff --git a/packages/core/src/System.js b/packages/core/src/System.js new file mode 100644 index 0000000..05a10c8 --- /dev/null +++ b/packages/core/src/System.js @@ -0,0 +1,42 @@ +/** + * System is a base class used for extending systems used by the {@link PIXI.Renderer} + * @see PIXI.Renderer#addSystem + * @class + * @memberof PIXI + */ +export default class System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + /** + * The renderer this manager works for. + * + * @member {PIXI.Renderer} + */ + this.renderer = renderer; + + this.renderer.runners.contextChange.add(this); + } + + /** + * Generic method called when there is a WebGL context change. + * + */ + contextChange() + { + // do some codes init! + } + + /** + * Generic destroy methods to be overridden by the subclass + * + */ + destroy() + { + this.renderer.runners.contextChange.remove(this); + this.renderer = null; + } +} diff --git a/packages/core/src/batch/BatchSystem.js b/packages/core/src/batch/BatchSystem.js new file mode 100644 index 0000000..b04c249 --- /dev/null +++ b/packages/core/src/batch/BatchSystem.js @@ -0,0 +1,68 @@ +import System from '../System'; +import ObjectRenderer from './ObjectRenderer'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class BatchSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + super(renderer); + + /** + * An empty renderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.emptyRenderer = new ObjectRenderer(renderer); + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.currentRenderer = this.emptyRenderer; + } + + /** + * Changes the current renderer to the one given in parameter + * + * @param {PIXI.ObjectRenderer} objectRenderer - The object renderer to use. + */ + setObjectRenderer(objectRenderer) + { + if (this.currentRenderer === objectRenderer) + { + return; + } + + this.currentRenderer.stop(); + this.currentRenderer = objectRenderer; + + this.renderer.state.setState(objectRenderer.state); + + this.currentRenderer.start(); + } + + /** + * This should be called if you wish to do some custom rendering + * It will basically render anything that may be batched up such as sprites + * + */ + flush() + { + this.setObjectRenderer(this.emptyRenderer); + } + + reset() + { + this.setObjectRenderer(this.emptyRenderer); + } +} diff --git a/packages/core/src/batch/ObjectRenderer.js b/packages/core/src/batch/ObjectRenderer.js new file mode 100644 index 0000000..0c851a9 --- /dev/null +++ b/packages/core/src/batch/ObjectRenderer.js @@ -0,0 +1,48 @@ +import System from '../System'; + +/** + * Base for a common object renderer that can be used as a system renderer plugin. + * + * @class + * @extends PIXI.System + * @memberof PIXI + */ +export default class ObjectRenderer extends System +{ + /** + * Starts the renderer and sets the shader + * + */ + start() + { + // set the shader.. + } + + /** + * Stops the renderer + * + */ + stop() + { + this.flush(); + } + + /** + * Stub method for rendering content and emptying the current batch. + * + */ + flush() + { + // flush! + } + + /** + * Renders an object + * + * @param {PIXI.DisplayObject} object - The object to render. + */ + render(object) // eslint-disable-line no-unused-vars + { + // render the object + } +} diff --git a/packages/core/src/context/ContextSystem.js b/packages/core/src/context/ContextSystem.js new file mode 100644 index 0000000..301aab4 --- /dev/null +++ b/packages/core/src/context/ContextSystem.js @@ -0,0 +1,186 @@ +import System from '../System'; +import { settings } from '@pixi/settings'; + +let CONTEXT_UID = 0; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class ContextSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.webGLVersion = 1; + + this.handleContextLost = this.handleContextLost.bind(this); + this.handleContextRestored = this.handleContextRestored.bind(this); + + this.extensions = {}; + + renderer.view.addEventListener('webglcontextlost', this.handleContextLost, false); + renderer.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + } + + get isLost() + { + return (!this.gl || this.gl.isContextLost()); + } + + contextChange(gl) + { + this.gl = gl; + + // restore a context if it was previously lost + if (gl.isContextLost() && gl.getExtension('WEBGL_lose_context')) + { + gl.getExtension('WEBGL_lose_context').restoreContext(); + } + } + + initFromContext(gl) + { + this.gl = gl; + this.validateContext(gl); + this.renderer.gl = gl; + this.renderer.CONTEXT_UID = CONTEXT_UID++; + this.renderer.runners.contextChange.run(gl); + } + + initFromOptions(options) + { + const gl = this.createContext(this.renderer.view, options); + + this.initFromContext(gl); + } + + /** + * Helper class to create a webGL Context + * + * @class + * @memberof PIXI.glCore + * @param canvas {HTMLCanvasElement} the canvas element that we will get the context from + * @param options {Object} An options object that gets passed in to the canvas element containing the context attributes, + * see https://developer.mozilla.org/en/docs/Web/API/HTMLCanvasElement/getContext for the options available + * @return {WebGLRenderingContext} the WebGL context + */ + createContext(canvas, options) + { + let gl; + + if (settings.PREFER_WEBGL_2) + { + gl = canvas.getContext('webgl2', options); + } + + if (gl) + { + this.webGLVersion = 2; + } + else + { + this.webGLVersion = 1; + + gl = canvas.getContext('webgl', options) + || canvas.getContext('experimental-webgl', options); + + if (!gl) + { + // fail, not able to get a context + throw new Error('This browser does not support webGL. Try using the canvas renderer'); + } + } + + this.gl = gl; + + this.getExtensions(); + + return gl; + } + + getExtensions() + { + // time to set up default etensions that pixi uses.. + const gl = this.gl; + const extensions = this.extensions; + + if (this.webGLVersion === 1) + { + extensions.drawBuffers = gl.getExtension('WEBGL_draw_buffers'); + extensions.depthTexture = gl.getExtension('WEBKIT_WEBGL_depth_texture'); + extensions.floatTexture = gl.getExtension('OES_texture_float'); + extensions.loseContext = gl.getExtension('WEBGL_lose_context'); + + extensions.vertexArrayObject = gl.getExtension('OES_vertex_array_object') + || gl.getExtension('MOZ_OES_vertex_array_object') + || gl.getExtension('WEBKIT_OES_vertex_array_object'); + } + + // we don't use any specific WebGL 2 ones yet! + } + + /** + * Handles a lost webgl context + * + * @private + * @param {WebGLContextEvent} event - The context lost event. + */ + handleContextLost(event) + { + event.preventDefault(); + } + + /** + * Handles a restored webgl context + * + * @private + */ + handleContextRestored() + { + this.renderer.runners.contextChange.run(this.gl); + } + + destroy() + { + const view = this.renderer.view; + + // remove listeners + view.removeEventListener('webglcontextlost', this.handleContextLost); + view.removeEventListener('webglcontextrestored', this.handleContextRestored); + + this.gl.useProgram(null); + + if (this.extensions.loseContext) + { + this.extensions.loseContext.loseContext(); + } + } + + postrender() + { + this.gl.flush(); + } + + validateContext(gl) + { + const attributes = gl.getContextAttributes(); + + // this is going to be fairly simple for now.. but at least we have room to grow! + if (!attributes.stencil) + { + /* eslint-disable max-len */ + + /* eslint-disable no-console */ + console.warn('Provided WebGL context does not have a stencil buffer, masks may not render correctly'); + /* eslint-enable no-console */ + + /* eslint-enable max-len */ + } + } +} diff --git a/packages/core/src/filters/Filter.js b/packages/core/src/filters/Filter.js new file mode 100644 index 0000000..9d8cad8 --- /dev/null +++ b/packages/core/src/filters/Filter.js @@ -0,0 +1,140 @@ +import Shader from '../shader/Shader'; +import Program from '../shader/Program'; +import State from '../state/State'; +import { settings } from '@pixi/settings'; +// import extractUniformsFromSrc from './extractUniformsFromSrc'; +import defaultVertex from './defaultFilter.vert'; +import defaultFragment from './defaultFilter.frag'; + +// let math = require('../../math'); +/** + * @class + * @memberof PIXI + * @extends PIXI.Shader + */ +export default class Filter extends Shader +{ + /** + * @param {string} [vertexSrc] - The source of the vertex shader. + * @param {string} [fragmentSrc] - The source of the fragment shader. + * @param {object} [uniforms] - Custom uniforms to use to augment the built-in ones. + */ + constructor(vertexSrc, fragmentSrc, uniforms) + { + const program = Program.from(vertexSrc, fragmentSrc); + + super(program, uniforms); + + /** + * The padding of the filter. Some filters require extra space to breath such as a blur. + * Increasing this will add extra width and height to the bounds of the object that the + * filter is applied to. + * + * @member {number} + */ + this.padding = 0; + + /** + * The resolution of the filter. Setting this to be lower will lower the quality but + * increase the performance of the filter. + * + * @member {number} + */ + this.resolution = settings.RESOLUTION; + + /** + * If enabled is true the filter is applied, if false it will not. + * + * @member {boolean} + */ + this.enabled = true; + + /** + * If enabled, PixiJS will fit the filter area into boundaries for better performance. + * Switch it off if it does not work for specific shader. + * + * @member {boolean} + */ + this.autoFit = true; + + /** + * Legacy filters use position and uvs from attributes + * @member {boolean} + * @readonly + */ + this.legacy = !!this.program.attributeData.aTextureCoord; + + /** + * the webGL state the filter requires to render + * @member {PIXI.State} + */ + this.state = new State(); + } + + /** + * Applies the filter + * + * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + * @param {boolean} clear - Should the output be cleared before rendering to it + * @param {object} [currentState] - It's current state of filter. + * There are some useful properties in the currentState : + * target, filters, sourceFrame, destinationFrame, renderTarget, resolution + */ + apply(filterManager, input, output, clear, currentState, derp) // eslint-disable-line no-unused-vars + { + // do as you please! + + filterManager.applyFilter(this, input, output, clear, currentState, derp); + + // or just do a regular render.. + } + + /** + * Sets the blendmode of the filter + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL + */ + get blendMode() + { + return this.state.blendMode; + } + + set blendMode(value) // eslint-disable-line require-jsdoc + { + this.state.blendMode = value; + } + + /** + * The default vertex shader source + * + * @static + * @constant + */ + static get defaultVertexSrc() + { + return defaultVertex; + } + + /** + * The default fragment shader source + * + * @static + * @constant + */ + static get defaultFragmentSrc() + { + return defaultFragment; + } +} + +/** + * Used for caching shader IDs + * + * @static + * @private + */ +Filter.SOURCE_KEY_MAP = {}; + diff --git a/packages/core/src/filters/FilterSystem.js b/packages/core/src/filters/FilterSystem.js new file mode 100644 index 0000000..3174318 --- /dev/null +++ b/packages/core/src/filters/FilterSystem.js @@ -0,0 +1,431 @@ +import System from '../System'; + +import RenderTexture from '../renderTexture/RenderTexture'; +import Quad from '../utils/Quad'; +import QuadUv from '../utils/QuadUv'; +import { Rectangle } from '@pixi/math'; +import * as filterTransforms from './filterTransforms'; +import bitTwiddle from 'bit-twiddle'; +import UniformGroup from '../shader/UniformGroup'; +import { DRAW_MODES } from '@pixi/constants'; + +// +/** + * @ignore + * @class + */ +class FilterState +{ + /** + * + */ + constructor() + { + this.renderTexture = null; + this.sourceFrame = new Rectangle(); + this.destinationFrame = new Rectangle(); + this.filters = []; + this.target = null; + this.legacy = false; + this.resolution = 1; + } +} + +/** + * @class + * @memberof PIXI.systems + * @extends PIXI.System + */ +export default class FilterSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + /** + * stores a bunch of PO2 textures used for filtering + * @type {Object} + */ + this.texturePool = {}; + + /** + * a pool for storing filter states, save us creating new ones each tick + * @type {Array} + */ + this.statePool = []; + + /** + * A very simple geometry used when drawing a filter effect to the screen + * @type {Quad} + */ + this.quad = new Quad(); + + this.quadUv = new QuadUv(); + + /** + * Temporary rect for maths + * @type {PIXI.Rectangle} + */ + this.tempRect = new Rectangle(); + + this.activeState = {}; + + /** + * this uniform group is attached to filter uniforms when used + * @type {UniformGroup} + */ + this.globalUniforms = new UniformGroup({ + sourceFrame: this.tempRect, + destinationFrame: this.tempRect, + + // legacy variables + filterArea: new Float32Array(4), + filterClamp: new Float32Array(4), + }, true); + } + + /** + * Adds a new filter to the System. + * + * @param {PIXI.DisplayObject} target - The target of the filter to render. + * @param {PIXI.Filter[]} filters - The filters to apply. + */ + push(target, filters) + { + const renderer = this.renderer; + const filterStack = this.renderer.renderTexture.defaultFilterStack; + const state = this.statePool.pop() || new FilterState(); + + let resolution = filters[0].resolution; + let padding = filters[0].padding; + let autoFit = filters[0].autoFit; + let legacy = filters[0].legacy; + + for (let i = 1; i < filters.length; i++) + { + const filter = filters[i]; + + // lets use the lowest resolution.. + resolution = Math.min(resolution, filter.resolution); + // and the largest amount of padding! + padding = Math.max(padding, filter.padding); + // only auto fit if all filters are autofit + autoFit = autoFit || filter.autoFit; + + legacy = legacy || filter.legacy; + } + + filterStack.push(state); + + state.resolution = resolution; + + state.legacy = legacy; + + // round to whole number based on resolution + // TODO move that to the shader too? + state.sourceFrame = target.filterArea || target.getBounds(true); + + state.sourceFrame.pad(padding); + + if (autoFit) + { + state.sourceFrame.fit(this.renderer.renderTexture.destinationFrame); + } + + state.sourceFrame.round(resolution); + + state.renderTexture = this.getPotFilterTexture(state.sourceFrame.width, state.sourceFrame.height, resolution); + state.filters = filters; + + state.destinationFrame.width = state.renderTexture.width; + state.destinationFrame.height = state.renderTexture.height; + + state.renderTexture.filterFrame = state.sourceFrame; + renderer.renderTexture.bind(state.renderTexture, state.sourceFrame);// /, state.destinationFrame); + renderer.renderTexture.clear(); + } + + /** + * Pops off the filter and applies it. + * + */ + pop() + { + const renderer = this.renderer; + const filterStack = renderer.renderTexture.defaultFilterStack; + const state = filterStack.pop(); + const filters = state.filters; + + this.activeState = state; + + const globalUniforms = this.globalUniforms.uniforms; + + globalUniforms.sourceFrame = state.sourceFrame; + globalUniforms.destinationFrame = state.destinationFrame; + globalUniforms.resolution = state.resolution; + + // only update the rect if its legacy.. + if (state.legacy) + { + const filterArea = globalUniforms.filterArea; + const filterClamp = globalUniforms.filterClamp; + + filterArea[0] = state.destinationFrame.width; + filterArea[1] = state.destinationFrame.height; + filterArea[2] = state.sourceFrame.x; + filterArea[3] = state.sourceFrame.y; + + filterClamp[0] = 0.5 / state.resolution / state.destinationFrame.width; + filterClamp[1] = 0.5 / state.resolution / state.destinationFrame.height; + filterClamp[2] = (state.sourceFrame.width - 0.5) / state.resolution / state.destinationFrame.width; + filterClamp[3] = (state.sourceFrame.height - 0.5) / state.resolution / state.destinationFrame.height; + } + + this.globalUniforms.update(); + + const lastState = filterStack[filterStack.length - 1]; + + if (filters.length === 1) + { + filters[0].apply(this, state.renderTexture, lastState.renderTexture, false, state); + + this.returnFilterTexture(state.renderTexture); + } + else + { + let flip = state.renderTexture; + let flop = this.getPotFilterTexture( + flip.width, + flip.height, + state.resolution + ); + + flop.filterFrame = flip.filterFrame; + + let i = 0; + + for (i = 0; i < filters.length - 1; ++i) + { + filters[i].apply(this, flip, flop, true, state); + + const t = flip; + + flip = flop; + flop = t; + } + + filters[i].apply(this, flip, lastState.renderTexture, false, state); + + this.returnFilterTexture(flip); + this.returnFilterTexture(flop); + } + + this.statePool.push(state); + } + + /** + * Draws a filter. + * + * @param {PIXI.Filter} filter - The filter to draw. + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + * @param {boolean} clear - Should the output be cleared before rendering to it + */ + applyFilter(filter, input, output, clear) + { + const renderer = this.renderer; + + renderer.renderTexture.bind(output, output ? output.filterFrame : null); + + if (clear) + { + // gl.disable(gl.SCISSOR_TEST); + renderer.renderTexture.clear(); + // gl.enable(gl.SCISSOR_TEST); + } + + // set the uniforms.. + filter.uniforms.uSampler = input; + filter.uniforms.filterGlobals = this.globalUniforms; + + // TODO make it so that the order of this does not matter.. + // because it does at the moment cos of global uniforms. + // they need to get resynced + + renderer.state.setState(filter.state); + renderer.shader.bind(filter); + + if (filter.legacy) + { + this.quadUv.map(input._frame, input.filterFrame); + + renderer.geometry.bind(this.quadUv); + renderer.geometry.draw(DRAW_MODES.TRIANGLES); + } + else + { + renderer.geometry.bind(this.quad); + renderer.geometry.draw(DRAW_MODES.TRIANGLE_STRIP); + } + } + + /** + * Calculates the mapped matrix. + * + * TODO playing around here.. this is temporary - (will end up in the shader) + * this returns a matrix that will normalise map filter cords in the filter to screen space + * + * @param {PIXI.Matrix} outputMatrix - the matrix to output to. + * @return {PIXI.Matrix} The mapped matrix. + */ + calculateScreenSpaceMatrix(outputMatrix) + { + const currentState = this.activeState; + + return filterTransforms.calculateScreenSpaceMatrix( + outputMatrix, + currentState.sourceFrame, + currentState.destinationFrame + ); + } + + /** + * This will map the filter coord so that a texture can be used based on the transform of a sprite + * + * @param {PIXI.Matrix} outputMatrix - The matrix to output to. + * @param {PIXI.Sprite} sprite - The sprite to map to. + * @return {PIXI.Matrix} The mapped matrix. + */ + calculateSpriteMatrix(outputMatrix, sprite) + { + const currentState = this.activeState; + + return filterTransforms.calculateSpriteMatrix( + outputMatrix, + currentState.sourceFrame, + currentState.destinationFrame, + sprite + ); + } + + /** + * Destroys this Filter System. + * + * @param {boolean} [contextLost=false] context was lost, do not free shaders + * + */ + destroy(contextLost = false) + { + if (!contextLost) + { + this.emptyPool(); + } + else + { + this.texturePool = {}; + } + } + + /** + * Gets a Power-of-Two render texture. + * + * TODO move to a seperate class could be on renderer? + * also - could cause issue with multiple contexts? + * + * @private + * @param {WebGLRenderingContext} gl - The webgl rendering context + * @param {number} minWidth - The minimum width of the render target. + * @param {number} minHeight - The minimum height of the render target. + * @param {number} resolution - The resolution of the render target. + * @return {PIXI.RenderTarget} The new render target. + */ + getPotFilterTexture(minWidth, minHeight, resolution) + { + minWidth = bitTwiddle.nextPow2(minWidth); + minHeight = bitTwiddle.nextPow2(minHeight); + resolution = resolution || 1; + + const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + + if (!this.texturePool[key]) + { + this.texturePool[key] = []; + } + + let renderTexture = this.texturePool[key].pop(); + + if (!renderTexture) + { + // temporary bypass cache.. + // internally - this will cause a texture to be bound.. + renderTexture = RenderTexture.create({ + width: minWidth, + height: minHeight, + resolution, + }); + } + + return renderTexture; + } + + /** + * Gets extra render texture to use inside current filter + * + * @param {number} resolution resolution of the renderTexture + * @returns {PIXI.RenderTexture} + */ + getFilterTexture(resolution) + { + const rt = this.activeState.renderTexture; + + const filterTexture = this.getPotFilterTexture(rt.width, rt.height, resolution || rt.baseTexture.resolution); + + filterTexture.filterFrame = rt.filterFrame; + + return filterTexture; + } + + /** + * Frees a render texture back into the pool. + * + * @param {PIXI.RenderTarget} renderTexture - The renderTarget to free + */ + returnFilterTexture(renderTexture) + { + renderTexture.filterFrame = null; + + const base = renderTexture.baseTexture; + + const minWidth = base.width; + const minHeight = base.height; + + const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + + this.texturePool[key].push(renderTexture); + } + + /** + * Empties the texture pool. + * + */ + emptyPool() + { + for (const i in this.texturePool) + { + const textures = this.texturePool[i]; + + if (textures) + { + for (let j = 0; j < textures.length; j++) + { + textures[j].destroy(true); + } + } + } + + this.texturePool = {}; + } +} diff --git a/packages/core/src/filters/defaultFilter.frag b/packages/core/src/filters/defaultFilter.frag new file mode 100644 index 0000000..a14c1be --- /dev/null +++ b/packages/core/src/filters/defaultFilter.frag @@ -0,0 +1,22 @@ +varying vec2 vTextureCoord; +varying vec2 vFilterCoord; + +uniform sampler2D uSampler; +uniform sampler2D filterSampler; + +void main(void){ + vec4 masky = texture2D(filterSampler, vFilterCoord); + vec4 sample = texture2D(uSampler, vTextureCoord); + vec4 color; + if(mod(vFilterCoord.x, 1.0) > 0.5) + { + color = vec4(1.0, 0.0, 0.0, 1.0); + } + else + { + color = vec4(0.0, 1.0, 0.0, 1.0); + } + // gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0); + gl_FragColor = mix(sample, masky, 0.5); + gl_FragColor *= sample.a; +} diff --git a/packages/core/src/filters/defaultFilter.vert b/packages/core/src/filters/defaultFilter.vert new file mode 100644 index 0000000..e1febca --- /dev/null +++ b/packages/core/src/filters/defaultFilter.vert @@ -0,0 +1,14 @@ +attribute vec2 aVertexPosition; +attribute vec2 aTextureCoord; + +uniform mat3 projectionMatrix; +uniform mat3 filterMatrix; + +varying vec2 vTextureCoord; +varying vec2 vFilterCoord; + +void main(void){ + gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); + vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy; + vTextureCoord = aTextureCoord ; +} diff --git a/packages/core/src/filters/filterTransforms.js b/packages/core/src/filters/filterTransforms.js new file mode 100644 index 0000000..87a3cd6 --- /dev/null +++ b/packages/core/src/filters/filterTransforms.js @@ -0,0 +1,50 @@ +import { Matrix } from '@pixi/math'; + +/** + * Calculates the mapped matrix + * @param filterArea {Rectangle} The filter area + * @param sprite {Sprite} the target sprite + * @param outputMatrix {Matrix} @alvin + * @private + */ +// this returns a matrix that will normalise map filter cords in the filter to screen space +export function calculateScreenSpaceMatrix(outputMatrix, filterArea, textureSize) +{ + // TODO unwrap? + const mappedMatrix = outputMatrix.identity(); + + mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); + + mappedMatrix.scale(textureSize.width, textureSize.height); + + return mappedMatrix; +} + +export function calculateNormalizedScreenSpaceMatrix(outputMatrix, filterArea, textureSize) +{ + const mappedMatrix = outputMatrix.identity(); + + mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); + + const translateScaleX = (textureSize.width / filterArea.width); + const translateScaleY = (textureSize.height / filterArea.height); + + mappedMatrix.scale(translateScaleX, translateScaleY); + + return mappedMatrix; +} + +// this will map the filter coord so that a texture can be used based on the transform of a sprite +export function calculateSpriteMatrix(outputMatrix, filterArea, textureSize, sprite) +{ + const orig = sprite._texture.orig; + const mappedMatrix = outputMatrix.set(textureSize.width, 0, 0, textureSize.height, filterArea.x, filterArea.y); + const worldTransform = sprite.worldTransform.copyTo(Matrix.TEMP_MATRIX); + + worldTransform.invert(); + mappedMatrix.prepend(worldTransform); + mappedMatrix.scale(1.0 / orig.width, 1.0 / orig.height); + mappedMatrix.translate(sprite.anchor.x, sprite.anchor.y); + + return mappedMatrix; +} diff --git a/packages/core/src/filters/spriteMask/SpriteMaskFilter.js b/packages/core/src/filters/spriteMask/SpriteMaskFilter.js new file mode 100644 index 0000000..91697b7 --- /dev/null +++ b/packages/core/src/filters/spriteMask/SpriteMaskFilter.js @@ -0,0 +1,63 @@ +import Filter from '../Filter'; +import { Matrix } from '@pixi/math'; +import vertex from './spriteMaskFilter.vert'; +import fragment from './spriteMaskFilter.frag'; +import { default as TextureMatrix } from '../../textures/TextureMatrix'; + +/** + * The SpriteMaskFilter class + * + * @class + * @extends PIXI.Filter + * @memberof PIXI + */ +export default class SpriteMaskFilter extends Filter +{ + /** + * @param {PIXI.Sprite} sprite - the target sprite + */ + constructor(sprite) + { + const maskMatrix = new Matrix(); + + super(vertex, fragment); + + sprite.renderable = false; + + this.maskSprite = sprite; + this.maskMatrix = maskMatrix; + } + + /** + * Applies the filter + * + * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + */ + apply(filterManager, input, output) + { + const maskSprite = this.maskSprite; + const tex = this.maskSprite.texture; + + if (!tex.valid) + { + return; + } + if (!tex.transform) + { + // margin = 0.0, let it bleed a bit, shader code becomes easier + // assuming that atlas textures were made with 1-pixel padding + tex.transform = new TextureMatrix(tex, 0.0); + } + tex.transform.update(); + + this.uniforms.mask = tex; + this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite) + .prepend(tex.transform.mapCoord); + this.uniforms.alpha = maskSprite.worldAlpha; + this.uniforms.maskClamp = tex.transform.uClampFrame; + + filterManager.applyFilter(this, input, output); + } +} diff --git a/packages/core/src/filters/spriteMask/spriteMaskFilter.frag b/packages/core/src/filters/spriteMask/spriteMaskFilter.frag new file mode 100644 index 0000000..0e4aef8 --- /dev/null +++ b/packages/core/src/filters/spriteMask/spriteMaskFilter.frag @@ -0,0 +1,23 @@ +varying vec2 vMaskCoord; +varying vec2 vTextureCoord; + +uniform sampler2D uSampler; +uniform sampler2D mask; +uniform float alpha; +uniform vec4 maskClamp; + +void main(void) +{ + float clip = step(3.5, + step(maskClamp.x, vMaskCoord.x) + + step(maskClamp.y, vMaskCoord.y) + + step(vMaskCoord.x, maskClamp.z) + + step(vMaskCoord.y, maskClamp.w)); + + vec4 original = texture2D(uSampler, vTextureCoord); + vec4 masky = texture2D(mask, vMaskCoord); + + original *= (masky.r * masky.a * alpha * clip); + + gl_FragColor = original; +} diff --git a/packages/core/src/filters/spriteMask/spriteMaskFilter.vert b/packages/core/src/filters/spriteMask/spriteMaskFilter.vert new file mode 100644 index 0000000..d23a30c --- /dev/null +++ b/packages/core/src/filters/spriteMask/spriteMaskFilter.vert @@ -0,0 +1,16 @@ +attribute vec2 aVertexPosition; +attribute vec2 aTextureCoord; + +uniform mat3 projectionMatrix; +uniform mat3 otherMatrix; + +varying vec2 vMaskCoord; +varying vec2 vTextureCoord; + +void main(void) +{ + gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); + + vTextureCoord = aTextureCoord; + vMaskCoord = ( otherMatrix * vec3( aTextureCoord, 1.0) ).xy; +} diff --git a/packages/core/src/framebuffer/FrameBuffer.js b/packages/core/src/framebuffer/FrameBuffer.js new file mode 100644 index 0000000..84e9fcc --- /dev/null +++ b/packages/core/src/framebuffer/FrameBuffer.js @@ -0,0 +1,106 @@ +import Texture from '../textures/BaseTexture'; +import { FORMATS, TYPES } from '@pixi/constants'; + +/** + * Frame buffer + * @class + * @memberof PIXI + */ +export default class FrameBuffer +{ + constructor(width, height) + { + this.width = width || 100; + this.height = height || 100; + + this.stencil = false; + this.depth = false; + + this.dirtyId = 0; + this.dirtyFormat = 0; + this.dirtySize = 0; + + this.depthTexture = null; + this.colorTextures = []; + + this.glFrameBuffers = {}; + } + + get colorTexture() + { + return this.colorTextures[0]; + } + + addColorTexture(index, texture) + { + // TODO add some validation to the texture - same width / height etc? + this.colorTextures[index || 0] = texture || new Texture(null, { scaleMode: 0, + resolution: 1, + mipmap: false, + width: this.width, + height: this.height });// || new Texture(); + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + addDepthTexture(texture) + { + /* eslint-disable max-len */ + this.depthTexture = texture || new Texture(null, { scaleMode: 0, + resolution: 1, + width: this.width, + height: this.height, + mipmap: false, + format: FORMATS.DEPTH_COMPONENT, + type: TYPES.UNSIGNED_SHORT });// UNSIGNED_SHORT; + /* eslint-disable max-len */ + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + enableDepth() + { + this.depth = true; + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + enableStencil() + { + this.stencil = true; + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + resize(width, height) + { + if (width === this.width && height === this.height) return; + + this.width = width; + this.height = height; + + this.dirtyId++; + this.dirtySize++; + + for (let i = 0; i < this.colorTextures.length; i++) + { + this.colorTextures[i].setSize(width, height); + } + + if (this.depthTexture) + { + this.depthTexture.setSize(width, height); + } + } +} diff --git a/packages/core/src/framebuffer/FramebufferSystem.js b/packages/core/src/framebuffer/FramebufferSystem.js new file mode 100644 index 0000000..2cc2437 --- /dev/null +++ b/packages/core/src/framebuffer/FramebufferSystem.js @@ -0,0 +1,246 @@ +import System from '../System'; +import { Rectangle } from '@pixi/math'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class FramebufferSystem extends System +{ + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + contextChange() + { + this.gl = this.renderer.gl; + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + this.current = null; + this.viewport = new Rectangle(); + + this.drawBufferExtension = this.renderer.context.extensions.drawBuffers; + } + + bind(framebuffer, frame) + { + const gl = this.gl; + + this.current = framebuffer; + + if (framebuffer) + { + // TODO cacheing layer! + + const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID] || this.initFramebuffer(framebuffer); + + gl.bindFramebuffer(gl.FRAMEBUFFER, fbo.framebuffer); + // makesure all textures are unbound.. + + // now check for updates... + if (fbo.dirtyId !== framebuffer.dirtyId) + { + fbo.dirtyId = framebuffer.dirtyId; + + if (fbo.dirtyFormat !== framebuffer.dirtyFormat) + { + fbo.dirtyFormat = framebuffer.dirtyFormat; + this.updateFramebuffer(framebuffer); + } + else if (fbo.dirtySize !== framebuffer.dirtySize) + { + fbo.dirtySize = framebuffer.dirtySize; + this.resizeFramebuffer(framebuffer); + } + } + + for (let i = 0; i < framebuffer.colorTextures.length; i++) + { + if (framebuffer.colorTextures[i].texturePart) + { + this.renderer.texture.unbind(framebuffer.colorTextures[i].texture); + } + else + { + this.renderer.texture.unbind(framebuffer.colorTextures[i]); + } + } + + if (framebuffer.depthTexture) + { + this.renderer.texture.unbind(framebuffer.depthTexture); + } + + if (frame) + { + this.setViewport(frame.x, frame.y, frame.width, frame.height); + } + else + { + this.setViewport(0, 0, framebuffer.width, framebuffer.height); + } + } + else + { + gl.bindFramebuffer(gl.FRAMEBUFFER, null); + + if (frame) + { + this.setViewport(frame.x, frame.y, frame.width, frame.height); + } + else + { + this.setViewport(0, 0, this.renderer.width, this.renderer.height); + } + } + } + + setViewport(x, y, width, height) + { + const v = this.viewport; + + if (v.width !== width || v.height !== height || v.x !== x || v.y !== y) + { + v.x = x; + v.y = y; + v.width = width; + v.height = height; + + this.gl.viewport(x, y, width, height); + } + } + + get size() + { + if (this.current) + { + // TODO store temp + return { x: 0, y: 0, width: this.current.width, height: this.current.height }; + } + + return { x: 0, y: 0, width: this.renderer.width, height: this.renderer.height }; + } + + clear(r, g, b, a) + { + const gl = this.gl; + + // TODO clear color can be set only one right? + gl.clearColor(r, g, b, a); + gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); + } + + // private functions... + + initFramebuffer(framebuffer) + { + const gl = this.gl; + + // TODO - make this a class? + const fbo = { + framebuffer: gl.createFramebuffer(), + stencil: null, + dirtyId: 0, + dirtyFormat: 0, + dirtySize: 0, + }; + + framebuffer.glFrameBuffers[this.CONTEXT_UID] = fbo; + + return fbo; + } + + resizeFramebuffer(framebuffer) + { + const gl = this.gl; + + if (framebuffer.stencil || framebuffer.depth) + { + gl.bindRenderbuffer(gl.RENDERBUFFER, this.stencil); + gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); + } + } + + updateFramebuffer(framebuffer) + { + const gl = this.gl; + + const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID]; + + // bind the color texture + const colorTextures = framebuffer.colorTextures; + + let count = colorTextures.length; + + if (!this.drawBufferExtension) + { + count = Math.min(count, 1); + } + + const activeTextures = []; + + for (let i = 0; i < count; i++) + { + const texture = framebuffer.colorTextures[i]; + + if (texture.texturePart) + { + this.renderer.texture.bind(texture.texture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.COLOR_ATTACHMENT0 + i, + gl.TEXTURE_CUBE_MAP_NEGATIVE_X + texture.side, + texture.texture._glTextures[this.CONTEXT_UID].texture, + 0); + } + else + { + this.renderer.texture.bind(texture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.COLOR_ATTACHMENT0 + i, + gl.TEXTURE_2D, + texture._glTextures[this.CONTEXT_UID].texture, + 0); + } + + activeTextures.push(gl.COLOR_ATTACHMENT0 + i); + } + + if (this.drawBufferExtension && activeTextures.length > 1) + { + this.drawBufferExtension.drawBuffersWEBGL(activeTextures); + } + + if (framebuffer.depthTexture) + { + const depthTextureExt = this.renderer.context.extensions.depthTexture; + + if (depthTextureExt) + { + const depthTexture = framebuffer.depthTexture; + + this.renderer.texture.bind(depthTexture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.DEPTH_ATTACHMENT, + gl.TEXTURE_2D, + depthTexture._glTextures[this.CONTEXT_UID].texture, + 0); + } + } + + if (framebuffer.stencil || framebuffer.depth) + { + fbo.stencil = gl.createRenderbuffer(); + + gl.bindRenderbuffer(gl.RENDERBUFFER, fbo.stencil); + + // TODO.. this is depth AND stencil? + gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, fbo.stencil); + gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); + // fbo.enableStencil(); + } + } +} diff --git a/packages/core/src/geometry/GLBuffer.js b/packages/core/src/geometry/GLBuffer.js new file mode 100644 index 0000000..0477e8a --- /dev/null +++ b/packages/core/src/geometry/GLBuffer.js @@ -0,0 +1,9 @@ +export default class GLBuffer +{ + constructor(buffer) + { + this.buffer = buffer; + this.updateID = -1; + this.byteLength = -1; + } +} diff --git a/packages/core/src/geometry/GeometrySystem.js b/packages/core/src/geometry/GeometrySystem.js new file mode 100644 index 0000000..959f827 --- /dev/null +++ b/packages/core/src/geometry/GeometrySystem.js @@ -0,0 +1,388 @@ +import System from '../System'; +import GLBuffer from './GLBuffer'; + +const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class GeometrySystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this._activeGeometry = null; + this._activeVao = null; + + this.hasVao = true; + this.hasInstance = true; + } + + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + contextChange() + { + const gl = this.gl = this.renderer.gl; + + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + + // webgl2 + if (!gl.createVertexArray) + { + // webgl 1! + let nativeVaoExtension = this.renderer.context.extensions.vertexArrayObject; + + if (this.renderer.legacy) + { + nativeVaoExtension = null; + } + + if (nativeVaoExtension) + { + gl.createVertexArray = () => + nativeVaoExtension.createVertexArrayOES(); + + gl.bindVertexArray = (vao) => + nativeVaoExtension.bindVertexArrayOES(vao); + + gl.deleteVertexArray = (vao) => + nativeVaoExtension.deleteVertexArrayOES(vao); + } + else + { + this.hasVao = false; + gl.createVertexArray = () => + { + // empty + }; + + gl.bindVertexArray = () => + { + // empty + }; + + gl.deleteVertexArray = () => + { + // empty + }; + } + } + + if (!gl.vertexAttribDivisor) + { + const instanceExt = gl.getExtension('ANGLE_instanced_arrays'); + + if (instanceExt) + { + gl.vertexAttribDivisor = (a, b) => + instanceExt.vertexAttribDivisorANGLE(a, b); + + gl.drawElementsInstanced = (a, b, c, d, e) => + instanceExt.drawElementsInstancedANGLE(a, b, c, d, e); + + gl.drawArraysInstanced = (a, b, c, d) => + instanceExt.drawArraysInstancedANGLE(a, b, c, d); + } + else + { + this.hasInstance = false; + } + } + } + + /** + * Binds geometry so that is can be drawn. Creating a Vao if required + * @private + * @param {PIXI.Geometry} geometry instance of geometry to bind + */ + bind(geometry, shader) + { + shader = shader || this.renderer.shader.shader; + + const gl = this.gl; + + // not sure the best way to address this.. + // currently different shaders require different VAOs for the same geometry + // Still mulling over the best way to solve this one.. + // will likely need to modify the shader attribute locations at run time! + let vaos = geometry.glVertexArrayObjects[this.CONTEXT_UID]; + + if (!vaos) + { + geometry.glVertexArrayObjects[this.CONTEXT_UID] = vaos = {}; + } + + const vao = vaos[shader.program.id] || this.initGeometryVao(geometry, shader.program); + + this._activeGeometry = geometry; + + if (this._activeVao !== vao) + { + this._activeVao = vao; + + if (this.hasVao) + { + gl.bindVertexArray(vao); + } + else + { + this.activateVao(geometry, shader.program); + } + } + + // TODO - optimise later! + // don't need to loop through if nothing changed! + // maybe look to add an 'autoupdate' to geometry? + this.updateBuffers(); + } + + reset() + { + this.unbind(); + } + + updateBuffers() + { + const geometry = this._activeGeometry; + const gl = this.gl; + + for (let i = 0; i < geometry.buffers.length; i++) + { + const buffer = geometry.buffers[i]; + + const glBuffer = buffer._glBuffers[this.CONTEXT_UID]; + + if (buffer._updateID !== glBuffer.updateID) + { + glBuffer.updateID = buffer._updateID; + + // TODO can cache this on buffer! maybe added a getter / setter? + const type = buffer.index ? gl.ELEMENT_ARRAY_BUFFER : gl.ARRAY_BUFFER; + const drawType = buffer.static ? gl.STATIC_DRAW : gl.DYNAMIC_DRAW; + + gl.bindBuffer(type, glBuffer.buffer); + + if (glBuffer.byteLength >= buffer.data.byteLength) + { + // offset is always zero for now! + gl.bufferSubData(type, 0, buffer.data); + } + else + { + gl.bufferData(type, buffer.data, drawType); + } + } + } + } + + checkCompatability(geometry, program) + { + // geometry must have at least all the attributes that the shader requires. + const geometryAttributes = geometry.attributes; + const shaderAttributes = program.attributeData; + + for (const j in shaderAttributes) + { + if (!geometryAttributes[j]) + { + throw new Error(`shader and geometry incompatible, geometry missing the "${j}" attribute`); + } + } + } + + /** + * Creates a Vao with the same structure as the geometry and stores it on the geometry. + * @private + * @param {PIXI.Geometry} geometry instance of geometry to to generate Vao for + */ + initGeometryVao(geometry, program) + { + this.checkCompatability(geometry, program); + + const gl = this.gl; + const CONTEXT_UID = this.CONTEXT_UID; + const buffers = geometry.buffers; + const attributes = geometry.attributes; + + const tempStride = {}; + const tempStart = {}; + + for (const j in buffers) + { + tempStride[j] = 0; + tempStart[j] = 0; + } + + for (const j in attributes) + { + if (!attributes[j].size && program.attributeData[j]) + { + attributes[j].size = program.attributeData[j].size; + } + + tempStride[attributes[j].buffer] += attributes[j].size * byteSizeMap[attributes[j].type]; + } + + for (const j in attributes) + { + const attribute = attributes[j]; + const attribSize = attribute.size; + + if (attribute.stride === undefined) + { + if (tempStride[attribute.buffer] === attribSize * byteSizeMap[attribute.type]) + { + attribute.stride = 0; + } + else + { + attribute.stride = tempStride[attribute.buffer]; + } + } + + if (attribute.start === undefined) + { + attribute.start = tempStart[attribute.buffer]; + + tempStart[attribute.buffer] += attribSize * byteSizeMap[attribute.type]; + } + } + + // first update - and create the buffers! + // only create a gl buffer if it actually gets + for (let i = 0; i < buffers.length; i++) + { + const buffer = buffers[i]; + + if (!buffer._glBuffers[CONTEXT_UID]) + { + buffer._glBuffers[CONTEXT_UID] = new GLBuffer(gl.createBuffer()); + } + } + + // TODO - maybe make this a data object? + // lets wait to see if we need to first! + const vao = gl.createVertexArray(); + + gl.bindVertexArray(vao); + + this.activateVao(geometry, program); + + gl.bindVertexArray(null); + + // add it to the cache! + geometry.glVertexArrayObjects[this.CONTEXT_UID][program.id] = vao; + + return vao; + } + + activateVao(geometry, program) + { + const gl = this.gl; + const CONTEXT_UID = this.CONTEXT_UID; + const buffers = geometry.buffers; + const attributes = geometry.attributes; + + if (geometry.indexBuffer) + { + // first update the index buffer if we have one.. + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, geometry.indexBuffer._glBuffers[CONTEXT_UID].buffer); + } + + let lastBuffer = null; + + // add a new one! + for (const j in attributes) + { + const attribute = attributes[j]; + const buffer = buffers[attribute.buffer]; + const glBuffer = buffer._glBuffers[CONTEXT_UID]; + + if (program.attributeData[j]) + { + if (lastBuffer !== glBuffer) + { + gl.bindBuffer(gl.ARRAY_BUFFER, glBuffer.buffer); + + lastBuffer = glBuffer; + } + + const location = program.attributeData[j].location; + + // TODO introduce state again + // we can optimise this for older devices that have no VAOs + gl.enableVertexAttribArray(location); + + gl.vertexAttribPointer(location, + attribute.size, + attribute.type || gl.FLOAT, + attribute.normalized, + attribute.stride, + attribute.start); + + if (attribute.instance) + { + // TODO calculate instance count based of this... + if (this.hasInstance) + { + gl.vertexAttribDivisor(location, 1); + } + else + { + throw new Error('geometry error, GPU Instancing is not supported on this device'); + } + } + } + } + } + + draw(type, size, start, instanceCount) + { + const gl = this.gl; + const geometry = this._activeGeometry; + + // TODO.. this should not change so maybe cache the function? + + if (geometry.indexBuffer) + { + if (geometry.instanced) + { + /* eslint-disable max-len */ + gl.drawElementsInstanced(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2, instanceCount || 1); + /* eslint-enable max-len */ + } + else + { + gl.drawElements(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2); + } + } + else + if (geometry.instanced) + { + // TODO need a better way to calculate size.. + gl.drawArraysInstanced(type, start, size || geometry.getSize(), instanceCount || 1); + } + else + { + gl.drawArrays(type, start, size || geometry.getSize()); + } + + return this; + } + + unbind() + { + this.gl.bindVertexArray(null); + this._activeVao = null; + this._activeGeometry = null; + } +} diff --git a/packages/core/src/geometry/utils/setVertexAttribArrays.js b/packages/core/src/geometry/utils/setVertexAttribArrays.js new file mode 100644 index 0000000..23b6959 --- /dev/null +++ b/packages/core/src/geometry/utils/setVertexAttribArrays.js @@ -0,0 +1,54 @@ +// var GL_MAP = {}; + +/** + * @param gl {WebGLRenderingContext} The current WebGL context + * @param attribs {*} + * @param state {*} + */ +export default function setVertexAttribArrays(gl, attribs, state) +{ + let i; + + if (state) + { + const tempAttribState = state.tempAttribState; + const attribState = state.attribState; + + for (i = 0; i < tempAttribState.length; i++) + { + tempAttribState[i] = false; + } + + // set the new attribs + for (i = 0; i < attribs.length; i++) + { + tempAttribState[attribs[i].attribute.location] = true; + } + + for (i = 0; i < attribState.length; i++) + { + if (attribState[i] !== tempAttribState[i]) + { + attribState[i] = tempAttribState[i]; + + if (state.attribState[i]) + { + gl.enableVertexAttribArray(i); + } + else + { + gl.disableVertexAttribArray(i); + } + } + } + } + else + { + for (i = 0; i < attribs.length; i++) + { + const attrib = attribs[i]; + + gl.enableVertexAttribArray(attrib.attribute.location); + } + } +} diff --git a/packages/core/src/index.js b/packages/core/src/index.js index 58c456a..fd1f547 100644 --- a/packages/core/src/index.js +++ b/packages/core/src/index.js @@ -1,30 +1,30 @@ import * as resources from './textures/resources'; -import * as systems from './renderers/systems'; +import * as systems from './systems'; -export { resources }; export { systems }; +export { resources }; -export { default as Renderer } from './renderers/Renderer'; -export { default as AbstractRenderer } from './renderers/AbstractRenderer'; -export { default as FrameBuffer } from './textures/FrameBuffer'; +export { default as System } from './System'; +export { default as Renderer } from './Renderer'; +export { default as AbstractRenderer } from './AbstractRenderer'; +export { default as FrameBuffer } from './framebuffer/FrameBuffer'; export { default as CubeTexture } from './textures/CubeTexture'; export { default as BaseTexture } from './textures/BaseTexture'; export { default as Texture } from './textures/Texture'; export { default as TextureMatrix } from './textures/TextureMatrix'; -export { default as RenderTexture } from './textures/RenderTexture'; -export { default as BaseRenderTexture } from './textures/BaseRenderTexture'; +export { default as RenderTexture } from './renderTexture/RenderTexture'; +export { default as BaseRenderTexture } from './renderTexture/BaseRenderTexture'; export { default as TextureUvs } from './textures/TextureUvs'; -export { default as State } from './renderers/State'; -export { default as ObjectRenderer } from './renderers/utils/ObjectRenderer'; -export { default as Quad } from './renderers/utils/Quad'; -export { default as QuadUv } from './renderers/utils/QuadUv'; -export { default as checkMaxIfStatmentsInShader } from './renderers/utils/checkMaxIfStatmentsInShader'; +export { default as State } from './state/State'; +export { default as ObjectRenderer } from './batch/ObjectRenderer'; +export { default as Quad } from './utils/Quad'; +export { default as QuadUv } from './utils/QuadUv'; +export { default as checkMaxIfStatmentsInShader } from './shader/utils/checkMaxIfStatmentsInShader'; export { default as Shader } from './shader/Shader'; export { default as Program } from './shader/Program'; export { default as UniformGroup } from './shader/UniformGroup'; -export { default as SpriteMaskFilter } from './renderers/filters/spriteMask/SpriteMaskFilter'; -export { default as Filter } from './renderers/filters/Filter'; +export { default as SpriteMaskFilter } from './filters/spriteMask/SpriteMaskFilter'; +export { default as Filter } from './filters/Filter'; export { default as Attribute } from './geometry/Attribute'; export { default as Buffer } from './geometry/Buffer'; export { default as Geometry } from './geometry/Geometry'; - diff --git a/packages/core/src/mask/MaskSystem.js b/packages/core/src/mask/MaskSystem.js new file mode 100644 index 0000000..99174c2 --- /dev/null +++ b/packages/core/src/mask/MaskSystem.js @@ -0,0 +1,199 @@ +import System from '../System'; +import AlphaMaskFilter from '../filters/spriteMask/SpriteMaskFilter'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class MaskSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + // TODO - we don't need both! + this.scissor = false; + this.scissorData = null; + this.scissorRenderTarget = null; + + this.enableScissor = false; + + this.alphaMaskPool = []; + this.alphaMaskIndex = 0; + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.DisplayObject} target - Display Object to push the mask to + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + push(target, maskData) + { + // TODO the root check means scissor rect will not + // be used on render textures more info here: + // https://github.com/pixijs/pixi.js/pull/3545 + + if (maskData.texture) + { + this.pushSpriteMask(target, maskData); + } + else if (this.enableScissor + && !this.scissor + && this.renderer._activeRenderTarget.root + && !this.renderer.stencil.stencilMaskStack.length + && maskData.isFastRect()) + { + const matrix = maskData.worldTransform; + + let rot = Math.atan2(matrix.b, matrix.a); + + // use the nearest degree! + rot = Math.round(rot * (180 / Math.PI)); + + if (rot % 90) + { + this.pushStencilMask(maskData); + } + else + { + this.pushScissorMask(target, maskData); + } + } + else + { + this.pushStencilMask(maskData); + } + } + + /** + * Removes the last mask from the mask stack and doesn't return it. + * + * @param {PIXI.DisplayObject} target - Display Object to pop the mask from + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + pop(target, maskData) + { + if (maskData.texture) + { + this.popSpriteMask(target, maskData); + } + else if (this.enableScissor && !this.renderer.stencil.stencilMaskStack.length) + { + this.popScissorMask(target, maskData); + } + else + { + this.popStencilMask(target, maskData); + } + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.RenderTarget} target - Display Object to push the sprite mask to + * @param {PIXI.Sprite} maskData - Sprite to be used as the mask + */ + pushSpriteMask(target, maskData) + { + let alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; + + if (!alphaMaskFilter) + { + alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; + } + + alphaMaskFilter[0].resolution = this.renderer.resolution; + alphaMaskFilter[0].maskSprite = maskData; + + // TODO - may cause issues! + target.filterArea = maskData.getBounds(true); + + this.renderer.filterSystem.pushFilter(target, alphaMaskFilter); + + this.alphaMaskIndex++; + } + + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popSpriteMask() + { + this.renderer.filterSystem.popFilter(); + this.alphaMaskIndex--; + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + pushStencilMask(maskData) + { + this.renderer.batch.flush(); + this.renderer.stencil.pushStencil(maskData); + } + + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popStencilMask() + { + // this.renderer.currentRenderer.stop(); + this.renderer.stencil.popStencil(); + } + + /** + * + * @param {PIXI.DisplayObject} target - Display Object to push the mask to + * @param {PIXI.Graphics} maskData - The masking data. + */ + pushScissorMask(target, maskData) + { + maskData.renderable = true; + + const renderTarget = this.renderer._activeRenderTarget; + + const bounds = maskData.getBounds(); + + bounds.fit(renderTarget.size); + maskData.renderable = false; + + this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); + + const resolution = this.renderer.resolution; + + this.renderer.gl.scissor( + bounds.x * resolution, + (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, + bounds.width * resolution, + bounds.height * resolution + ); + + this.scissorRenderTarget = renderTarget; + this.scissorData = maskData; + this.scissor = true; + } + + /** + * + * + */ + popScissorMask() + { + this.scissorRenderTarget = null; + this.scissorData = null; + this.scissor = false; + + // must be scissor! + const gl = this.renderer.gl; + + gl.disable(gl.SCISSOR_TEST); + } +} diff --git a/packages/core/src/mask/StencilSystem.js b/packages/core/src/mask/StencilSystem.js new file mode 100644 index 0000000..62796a5 --- /dev/null +++ b/packages/core/src/mask/StencilSystem.js @@ -0,0 +1,133 @@ +import System from '../System'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class StencilSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + this.stencilMaskStack = []; + } + + /** + * Changes the mask stack that is used by this System. + * + * @param {PIXI.Graphics[]} stencilMaskStack - The mask stack + */ + setMaskStack(stencilMaskStack) + { + const gl = this.renderer.gl; + + if (stencilMaskStack.length !== this.stencilMaskStack.length) + { + if (stencilMaskStack.length === 0) + { + gl.disable(gl.STENCIL_TEST); + } + else + { + gl.enable(gl.STENCIL_TEST); + } + } + + this.stencilMaskStack = stencilMaskStack; + } + + /** + * Applies the Mask and adds it to the current stencil stack. @alvin + * + * @param {PIXI.Graphics} graphics - The mask + */ + pushStencil(graphics) + { + this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); + + // this.renderer._activeRenderTarget.attachStencilBuffer(); + + const gl = this.renderer.gl; + const prevMaskCount = this.stencilMaskStack.length; + + if (prevMaskCount === 0) + { + gl.enable(gl.STENCIL_TEST); + } + + this.stencilMaskStack.push(graphics); + + // Increment the refference stencil value where the new mask overlaps with the old ones. + gl.colorMask(false, false, false, false); + gl.stencilFunc(gl.EQUAL, prevMaskCount, this._getBitwiseMask()); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.INCR); + this.renderer.plugins.graphics.render(graphics); + + this._useCurrent(); + } + + /** + * Removes the last mask from the stencil stack. @alvin + */ + popStencil() + { + this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); + + const gl = this.renderer.gl; + const graphics = this.stencilMaskStack.pop(); + + if (this.stencilMaskStack.length === 0) + { + // the stack is empty! + gl.disable(gl.STENCIL_TEST); + gl.clear(gl.STENCIL_BUFFER_BIT); + gl.clearStencil(0); + } + else + { + // Decrement the refference stencil value where the popped mask overlaps with the other ones + gl.colorMask(false, false, false, false); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.DECR); + this.renderer.plugins.graphics.render(graphics); + + this._useCurrent(); + } + } + + /** + * Setup renderer to use the current stencil data. + */ + _useCurrent() + { + const gl = this.renderer.gl; + + gl.colorMask(true, true, true, true); + gl.stencilFunc(gl.EQUAL, this.stencilMaskStack.length, this._getBitwiseMask()); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP); + } + + /** + * Fill 1s equal to the number of acitve stencil masks. + * + * @return {number} The bitwise mask. + */ + _getBitwiseMask() + { + return (1 << this.stencilMaskStack.length) - 1; + } + + /** + * Destroys the mask stack. + * + */ + destroy() + { + super.destroy(this); + + this.stencilMaskStack = null; + } +} diff --git a/packages/core/src/projection/ProjectionSystem.js b/packages/core/src/projection/ProjectionSystem.js new file mode 100644 index 0000000..6e63b73 --- /dev/null +++ b/packages/core/src/projection/ProjectionSystem.js @@ -0,0 +1,73 @@ +import System from '../System'; +import { Matrix } from '@pixi/math'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class ProjectionSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.projectionMatrix = new Matrix(); + } + + update(destinationFrame, sourceFrame, resolution, root) + { + this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; + this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; + + this.calculateProjection(this.destinationFrame, this.sourceFrame, resolution, root); + + this.renderer.globalUniforms.uniforms.projectionMatrix = this.projectionMatrix; + this.renderer.globalUniforms.update(); + } + + /** + * Updates the projection matrix based on a projection frame (which is a rectangle) + * + * @param {Rectangle} destinationFrame - The destination frame. + * @param {Rectangle} sourceFrame - The source frame. + */ + calculateProjection(destinationFrame, sourceFrame, resolution, root) + { + const pm = this.projectionMatrix; + + // I don't think we will need this line.. + // pm.identity(); + + if (!root) + { + pm.a = (1 / destinationFrame.width * 2) * resolution; + pm.d = (1 / destinationFrame.height * 2) * resolution; + + pm.tx = -1 - (sourceFrame.x * pm.a); + pm.ty = -1 - (sourceFrame.y * pm.d); + } + else + { + pm.a = (1 / destinationFrame.width * 2) * resolution; + pm.d = (-1 / destinationFrame.height * 2) * resolution; + + pm.tx = -1 - (sourceFrame.x * pm.a); + pm.ty = 1 - (sourceFrame.y * pm.d); + } + } + + /** + * Sets the transform of the active render target to the given matrix + * + * @param {PIXI.Matrix} matrix - The transformation matrix + */ + setTransform()// matrix) + { + // this._activeRenderTarget.transform = matrix; + } +} diff --git a/packages/core/src/renderTexture/BaseRenderTexture.js b/packages/core/src/renderTexture/BaseRenderTexture.js new file mode 100644 index 0000000..88eb83b --- /dev/null +++ b/packages/core/src/renderTexture/BaseRenderTexture.js @@ -0,0 +1,137 @@ +import BaseTexture from '../textures/BaseTexture'; +import FrameBuffer from '../framebuffer/FrameBuffer'; + +/** + * A BaseRenderTexture is a special texture that allows any PixiJS display object to be rendered to it. + * + * __Hint__: All DisplayObjects (i.e. Sprites) that render to a BaseRenderTexture should be preloaded + * otherwise black rectangles will be drawn instead. + * + * A BaseRenderTexture takes a snapshot of any Display Object given to its render method. The position + * and rotation of the given Display Objects is ignored. For example: + * + * ```js + * let renderer = PIXI.autoDetectRenderer(1024, 1024, { view: canvas, ratio: 1 }); + * let baseRenderTexture = new PIXI.BaseRenderTexture(renderer, 800, 600); + * let sprite = PIXI.Sprite.fromImage("spinObj_01.png"); + * + * sprite.position.x = 800/2; + * sprite.position.y = 600/2; + * sprite.anchor.x = 0.5; + * sprite.anchor.y = 0.5; + * + * baseRenderTexture.render(sprite); + * ``` + * + * The Sprite in this case will be rendered using its local transform. To render this sprite at 0,0 + * you can clear the transform + * + * ```js + * + * sprite.setTransform() + * + * let baseRenderTexture = new PIXI.BaseRenderTexture(100, 100); + * let renderTexture = new PIXI.RenderTexture(baseRenderTexture); + * + * renderer.render(sprite, renderTexture); // Renders to center of RenderTexture + * ``` + * + * @class + * @extends PIXI.BaseTexture + * @memberof PIXI + */ +export default class BaseRenderTexture extends BaseTexture +{ + /** + * @param {object} [options] + * @param {number} [options.width=100] - The width of the base render texture + * @param {number} [options.height=100] - The height of the base render texture + * @param {PIXI.SCALE_MODES} [options.scaleMode] - See {@link PIXI.SCALE_MODES} for possible values + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the texture being generated + */ + constructor(options) + { + if (typeof options === 'number') + { + /* eslint-disable prefer-rest-params */ + // Backward compatibility of signature + const width = arguments[0]; + const height = arguments[1]; + const scaleMode = arguments[2]; + const resolution = arguments[3]; + + options = { width, height, scaleMode, resolution }; + /* eslint-enable prefer-rest-params */ + } + + super(null, options); + + const { width, height } = options || {}; + + // Set defaults + this.mipmap = false; + this.width = Math.ceil(width) || 100; + this.height = Math.ceil(height) || 100; + this.valid = true; + + /** + * A map of renderer IDs to webgl renderTargets + * + * @private + * @member {object} + */ + // this._glRenderTargets = {}; + + /** + * A reference to the canvas render target (we only need one as this can be shared across renderers) + * + * @private + * @member {object} + */ + this._canvasRenderTarget = null; + + this.clearColor = [0, 0, 0, 0]; + + this.frameBuffer = new FrameBuffer(width * this.resolution, height * this.resolution) + .addColorTexture(0, this); + + // TODO - could this be added the systems? + + /** + * The data structure for the stencil masks + * + * @member {PIXI.Graphics[]} + */ + this.stencilMaskStack = []; + + /** + * The data structure for the filters + * + * @member {PIXI.Graphics[]} + */ + this.filterStack = [{}]; + } + + /** + * Resizes the BaseRenderTexture. + * + * @param {number} width - The width to resize to. + * @param {number} height - The height to resize to. + */ + resize(width, height) + { + width = Math.ceil(width); + height = Math.ceil(height); + this.frameBuffer.resize(width * this.resolution, height * this.resolution); + } + + /** + * Destroys this texture + * + */ + destroy() + { + super.destroy(true); + this.renderer = null; + } +} diff --git a/packages/core/src/renderTexture/RenderTexture.js b/packages/core/src/renderTexture/RenderTexture.js new file mode 100644 index 0000000..f1ecf1f --- /dev/null +++ b/packages/core/src/renderTexture/RenderTexture.js @@ -0,0 +1,153 @@ +import BaseRenderTexture from './BaseRenderTexture'; +import Texture from '../textures/Texture'; + +/** + * A RenderTexture is a special texture that allows any PixiJS display object to be rendered to it. + * + * __Hint__: All DisplayObjects (i.e. Sprites) that render to a RenderTexture should be preloaded + * otherwise black rectangles will be drawn instead. + * + * A RenderTexture takes a snapshot of any Display Object given to its render method. For example: + * + * ```js + * let renderer = PIXI.autoDetectRenderer(1024, 1024, { view: canvas, ratio: 1 }); + * let renderTexture = PIXI.RenderTexture.create(800, 600); + * let sprite = PIXI.Sprite.fromImage("spinObj_01.png"); + * + * sprite.position.x = 800/2; + * sprite.position.y = 600/2; + * sprite.anchor.x = 0.5; + * sprite.anchor.y = 0.5; + * + * renderer.render(sprite, renderTexture); + * ``` + * + * The Sprite in this case will be rendered using its local transform. To render this sprite at 0,0 + * you can clear the transform + * + * ```js + * + * sprite.setTransform() + * + * let renderTexture = new PIXI.RenderTexture.create(100, 100); + * + * renderer.render(sprite, renderTexture); // Renders to center of RenderTexture + * ``` + * + * @class + * @extends PIXI.Texture + * @memberof PIXI + */ +export default class RenderTexture extends Texture +{ + /** + * @param {PIXI.BaseRenderTexture} baseRenderTexture - The renderer used for this RenderTexture + * @param {PIXI.Rectangle} [frame] - The rectangle frame of the texture to show + */ + constructor(baseRenderTexture, frame) + { + // support for legacy.. + let _legacyRenderer = null; + + if (!(baseRenderTexture instanceof BaseRenderTexture)) + { + /* eslint-disable prefer-rest-params, no-console */ + const width = arguments[1]; + const height = arguments[2]; + const scaleMode = arguments[3]; + const resolution = arguments[4]; + + // we have an old render texture.. + console.warn(`Please use RenderTexture.create(${width}, ${height}) instead of the ctor directly.`); + _legacyRenderer = arguments[0]; + /* eslint-enable prefer-rest-params, no-console */ + + frame = null; + baseRenderTexture = new BaseRenderTexture({ + width, + height, + scaleMode, + resolution, + }); + } + + /** + * The base texture object that this texture uses + * + * @member {BaseTexture} + */ + super(baseRenderTexture, frame); + + this.legacyRenderer = _legacyRenderer; + + /** + * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. + * + * @member {boolean} + */ + this.valid = true; + + /** + * FilterSystem temporary storage + * @private + * @member {PIXI.Rectangle} + */ + this.filterFrame = null; + + this._updateUvs(); + } + + /** + * Resizes the RenderTexture. + * + * @param {number} width - The width to resize to. + * @param {number} height - The height to resize to. + * @param {boolean} [resizeBaseTexture=true] - Should the baseTexture.width and height values be resized as well? + */ + resize(width, height, resizeBaseTexture = true) + { + width = Math.ceil(width); + height = Math.ceil(height); + + // TODO - could be not required.. + this.valid = (width > 0 && height > 0); + + this._frame.width = this.orig.width = width; + this._frame.height = this.orig.height = height; + + if (resizeBaseTexture) + { + this.baseTexture.resize(width, height); + } + + this._updateUvs(); + } + + /** + * A short hand way of creating a render texture. + * + * @param {object} [options] - Options + * @param {number} [options.width=100] - The width of the render texture + * @param {number} [options.height=100] - The height of the render texture + * @param {number} [options.scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the texture being generated + * @return {PIXI.RenderTexture} The new render texture + */ + static create(options) + { + // fallback, old-style: create(width, height, scaleMode, resolution) + if (typeof options === 'number') + { + /* eslint-disable prefer-rest-params */ + options = { + width: options, + height: arguments[1], + scaleMode: arguments[2], + resolution: arguments[3], + }; + /* eslint-enable prefer-rest-params */ + } + + return new RenderTexture(new BaseRenderTexture(options)); + } +} diff --git a/packages/core/src/renderTexture/RenderTextureSystem.js b/packages/core/src/renderTexture/RenderTextureSystem.js new file mode 100644 index 0000000..8d0ca61 --- /dev/null +++ b/packages/core/src/renderTexture/RenderTextureSystem.js @@ -0,0 +1,115 @@ +import System from '../System'; +import { Rectangle } from '@pixi/math'; + +const tempRect = new Rectangle(); + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class RenderTextureSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.clearColor = renderer._backgroundColorRgba; + + // TODO moe this property somewhere else! + this.defaultMaskStack = []; + this.defaultFilterStack = [{}]; + + // empty render texture? + this.renderTexture = null; + + this.destinationFrame = new Rectangle(); + } + + bind(renderTexture, sourceFrame, destinationFrame) + { + // TODO - do we want this?? + if (this.renderTexture === renderTexture) return; + this.renderTexture = renderTexture; + + const renderer = this.renderer; + + if (renderTexture) + { + const baseTexture = renderTexture.baseTexture; + + if (!destinationFrame) + { + tempRect.width = baseTexture.realWidth; + tempRect.height = baseTexture.realHeight; + + destinationFrame = tempRect; + } + + if (!sourceFrame) + { + sourceFrame = destinationFrame; + } + + this.renderer.framebuffer.bind(baseTexture.frameBuffer, destinationFrame); + + this.renderer.projection.update(destinationFrame, sourceFrame, baseTexture.resolution, false); + this.renderer.stencil.setMaskStack(baseTexture.stencilMaskStack); + } + else + { + // TODO these validation checks happen deeper down.. + // thing they can be avoided.. + if (!destinationFrame) + { + tempRect.width = renderer.width; + tempRect.height = renderer.height; + + destinationFrame = tempRect; + } + + if (!sourceFrame) + { + sourceFrame = destinationFrame; + } + + renderer.framebuffer.bind(null, destinationFrame); + + // TODO store this.. + this.renderer.projection.update(destinationFrame, sourceFrame, this.renderer.resolution, true); + this.renderer.stencil.setMaskStack(this.defaultMaskStack); + } + + this.destinationFrame.copyFrom(destinationFrame); + } + + /** + * Erases the render texture and fills the drawing area with a colour + * + * @param {number} [clearColor] - The colour + * @return {PIXI.Renderer} Returns itself. + */ + clear(clearColor) + { + if (this.renderTexture) + { + clearColor = clearColor || this.renderTexture.baseTexture.clearColor; + } + else + { + clearColor = clearColor || this.clearColor; + } + + this.renderer.framebuffer.clear(clearColor[0], clearColor[1], clearColor[2], clearColor[3]); + } + + resize()// screenWidth, screenHeight) + { + // resize the root only! + this.bind(null); + } +} diff --git a/packages/core/src/renderers/AbstractRenderer.js b/packages/core/src/renderers/AbstractRenderer.js deleted file mode 100644 index 928670f..0000000 --- a/packages/core/src/renderers/AbstractRenderer.js +++ /dev/null @@ -1,353 +0,0 @@ -import { hex2string, hex2rgb } from '@pixi/utils'; -import { Matrix, Rectangle } from '@pixi/math'; -import { RENDERER_TYPE } from '@pixi/constants'; -import { settings } from '@pixi/settings'; -import { Container } from '@pixi/display'; -import RenderTexture from '../textures/RenderTexture'; -import EventEmitter from 'eventemitter3'; - -const tempMatrix = new Matrix(); - -/** - * 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 EventEmitter - * @memberof PIXI - */ -export default class AbstractRenderer extends EventEmitter -{ - // eslint-disable-next-line valid-jsdoc - /** - * @param {string} system - The name of the system this renderer is for. - * @param {object} [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.transparent=false] - If the render view is transparent, default false - * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false - * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) - * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. The - * resolution of the renderer retina would be 2. - * @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 {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when rendering, - * stopping pixel interpolation. - */ - constructor(system, options, arg2, arg3) - { - super(); - - // Support for constructor(system, screenWidth, screenHeight, options) - if (typeof options === 'number') - { - options = Object.assign({ - width: options, - height: arg2 || settings.RENDER_OPTIONS.height, - }, arg3); - } - - // 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 = RENDERER_TYPE.UNKNOWN; - - /** - * Measurements of the screen. (0, 0, screenWidth, screenHeight) - * - * Its safe to use as filterArea or hitArea for 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 1 - */ - this.resolution = options.resolution || settings.RESOLUTION; - - /** - * Whether the render view is transparent - * - * @member {boolean} - */ - this.transparent = options.transparent; - - /** - * Whether css dimensions of canvas view should be resized to screen dimensions automatically - * - * @member {boolean} - */ - this.autoResize = options.autoResize || false; - - /** - * Tracks the blend modes useful for this renderer. - * - * @member {object} - */ - this.blendModes = null; - - /** - * 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; - - /** - * If true PixiJS will Math.floor() x/y values when rendering, stopping pixel interpolation. - * Handy for crisp pixel art and speed on legacy devices. - * - * @member {boolean} - */ - this.roundPixels = options.roundPixels; - - /** - * The background color as a number. - * - * @member {number} - * @private - */ - this._backgroundColor = 0x000000; - - /** - * The background color as an [R, G, B] array. - * - * @member {number[]} - * @private - */ - this._backgroundColorRgba = [0, 0, 0, 0]; - - /** - * The background color as a string. - * - * @member {string} - * @private - */ - this._backgroundColorString = '#000000'; - - this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter - - /** - * This temporary display object used as the parent of the currently being rendered item - * - * @member {PIXI.DisplayObject} - * @private - */ - this._tempDisplayObjectParent = new Container(); - - /** - * The last root object that the renderer tried to render. - * - * @member {PIXI.DisplayObject} - * @private - */ - this._lastObjectRendered = this._tempDisplayObjectParent; - - /** - * Collection of plugins. - * @readonly - * @member {object} - */ - this.plugins = {}; - } - - /** - * Initialize the plugins. - * - * @protected - * @param {object} staticMap - The dictionary of staticly saved plugins. - */ - initPlugins(staticMap) - { - 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() - { - return this.view.width; - } - - /** - * Same as view.height, actual number of pixels in the canvas by vertical - * - * @member {number} - * @readonly - * @default 600 - */ - get height() - { - return this.view.height; - } - - /** - * Resizes the screen and canvas to the specified width and height - * Canvas dimensions are multiplied by resolution - * - * @param {number} screenWidth - the new width of the screen - * @param {number} screenHeight - the new height of the screen - */ - resize(screenWidth, screenHeight) - { - this.screen.width = screenWidth; - this.screen.height = screenHeight; - - this.view.width = screenWidth * this.resolution; - this.view.height = screenHeight * this.resolution; - - if (this.autoResize) - { - this.view.style.width = `${screenWidth}px`; - this.view.style.height = `${screenHeight}px`; - } - } - - /** - * 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 {PIXI.DisplayObject} displayObject - The displayObject the object will be generated from - * @param {number} scaleMode - Should be one of the scaleMode consts - * @param {number} resolution - The resolution / device pixel ratio of the texture being generated - * @param {PIXI.Rectangle} [region] - The region of the displayObject, that shall be rendered, - * if no region is specified, defaults to the local bounds of the displayObject. - * @return {PIXI.Texture} a texture of the graphics object - */ - generateTexture(displayObject, scaleMode, resolution, region) - { - region = region || displayObject.getLocalBounds(); - - // 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(region.width | 0, region.height | 0, scaleMode, resolution); - - tempMatrix.tx = -region.x; - tempMatrix.ty = -region.y; - - this.render(displayObject, renderTexture, false, tempMatrix, true); - - return renderTexture; - } - - /** - * Removes everything from the renderer and optionally removes the Canvas DOM element. - * - * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. - */ - destroy(removeView) - { - 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); - } - - this.plugins = null; - - this.type = RENDERER_TYPE.UNKNOWN; - - this.view = null; - - this.screen = null; - - this.resolution = 0; - - this.transparent = false; - - this.autoResize = false; - - this.blendModes = null; - - this.options = null; - - this.preserveDrawingBuffer = false; - this.clearBeforeRender = false; - - this.roundPixels = false; - - this._backgroundColor = 0; - this._backgroundColorRgba = null; - this._backgroundColorString = null; - - this._tempDisplayObjectParent = null; - this._lastObjectRendered = null; - } - - /** - * The background color to fill if not transparent - * - * @member {number} - */ - get backgroundColor() - { - return this._backgroundColor; - } - - set backgroundColor(value) // eslint-disable-line require-jsdoc - { - this._backgroundColor = value; - this._backgroundColorString = hex2string(value); - hex2rgb(value, this._backgroundColorRgba); - } -} diff --git a/packages/core/src/renderers/Renderer.js b/packages/core/src/renderers/Renderer.js deleted file mode 100644 index a2020b1..0000000 --- a/packages/core/src/renderers/Renderer.js +++ /dev/null @@ -1,329 +0,0 @@ -import AbstractRenderer from './AbstractRenderer'; -import { sayHello } from '@pixi/utils'; -import MaskSystem from './systems/MaskSystem'; -import StencilSystem from './systems/StencilSystem'; -import FilterSystem from './systems/filter/FilterSystem'; -import FramebufferSystem from './systems/FramebufferSystem'; -import RenderTextureSystem from './systems/RenderTextureSystem'; -import TextureSystem from './systems/textures/TextureSystem'; -import ProjectionSystem from './systems/ProjectionSystem'; -import StateSystem from './systems/StateSystem'; -import GeometrySystem from './systems/geometry/GeometrySystem'; -import ShaderSystem from './systems/shader/ShaderSystem'; -import ContextSystem from './systems/ContextSystem'; -import BatchSystem from './systems/BatchSystem'; -import TextureGCSystem from './systems/textures/TextureGCSystem'; -import { RENDERER_TYPE } from '@pixi/constants'; -import UniformGroup from '../shader/UniformGroup'; -import { Matrix } from '@pixi/math'; -import Runner from 'mini-runner'; - -/** - * 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 Render works by automatically managing webGLBatchs. - * 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 :) - * - * @class - * @memberof PIXI - * @extends PIXI.AbstractRenderer - */ -export default class Renderer extends AbstractRenderer -{ - // eslint-disable-next-line valid-jsdoc - /** - * - * @param {object} [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.transparent=false] - If the render view is transparent, default false - * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false - * @param {boolean} [options.antialias=false] - sets antialias. If not available natively then FXAA - * antialiasing is used - * @param {boolean} [options.forceFXAA=false] - forces FXAA antialiasing to be used over native. - * FXAA is faster, but may not always look as great - * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. - * The resolution of the renderer retina would be 2. - * @param {boolean} [options.clearBeforeRender=true] - This sets if the renderer will clear - * the canvas or not before the new render pass. If you wish to set this to false, you *must* set - * preserveDrawingBuffer to `true`. - * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, - * enable this if you need to call toDataUrl on the webgl context. - * @param {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when - * rendering, stopping pixel interpolation. - * @param {number} [options.backgroundColor=0x000000] - The background color of the rendered area - * (shown if not transparent). - * @param {boolean} [options.legacy=false] - If true PixiJS will aim to ensure compatibility - * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. - * @param {string} [options.powerPreference] - Parameter passed to webgl context, set to "high-performance" - * for devices with dual graphics card - */ - constructor(options, arg2, arg3) - { - super('WebGL', options, arg2, arg3); - - /** - * The type of this renderer as a standardised const - * - * @member {number} - * @see PIXI.RENDERER_TYPE - */ - this.type = RENDERER_TYPE.WEBGL; - - // this will be set by the contextSystem (this.context) - this.gl = null; - this.CONTEXT_UID = 0; - this.legacy = !!options.legacy; - - // TODO legacy! - - // runners! - this.runners = { - destroy: new Runner('destroy'), - contextChange: new Runner('contextChange', 1), - reset: new Runner('reset'), - update: new Runner('update'), - postrender: new Runner('postrender'), - prerender: new Runner('prerender'), - resize: new Runner('resize', 2), - }; - - 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(GeometrySystem, 'geometry') - .addSystem(FramebufferSystem, 'framebuffer') - .addSystem(StencilSystem, 'stencil') - .addSystem(ProjectionSystem, 'projection') - .addSystem(TextureGCSystem, 'textureGC') - .addSystem(FilterSystem, 'filter') - .addSystem(RenderTextureSystem, 'renderTexture') - .addSystem(BatchSystem, 'batch'); - - this.initPlugins(Renderer.__plugins); - - /** - * The options passed in to create a new webgl context. - * - * @member {object} - * @private - */ - if (options.context) - { - this.context.initFromContext(options.context); - } - else - { - this.context.initFromOptions({ - alpha: this.transparent, - antialias: options.antialias, - premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', - stencil: true, - 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); - } - - addSystem(_class, name) - { - if (!name) - { - name = _class.name; - } - - // TODO - read name from class.name.. - - /* - if(name.includes('System')) - { - name = name.replace('System', ''); - name = name.charAt(0).toLowerCase() + name.slice(1); - } - */ - - const system = new _class(this); - - if (this[name]) - { - throw new Error(`Whoops! ${name} is already a manger`); - } - - this[name] = system; - - for (const i in this.runners) - { - this.runners[i].add(system); - } - - return this; - - /** - * 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. - */ - } - - /** - * Renders the object to its webGL view - * - * @param {PIXI.DisplayObject} displayObject - the object to be rendered - * @param {PIXI.RenderTexture} renderTexture - The render texture to render to. - * @param {boolean} [clear] - Should the canvas be cleared before the new render - * @param {PIXI.Transform} [transform] - A transform to apply to the render texture before rendering. - * @param {boolean} [skipUpdateTransform] - Should we skip the update transform pass? - */ - render(displayObject, renderTexture, clear, transform, skipUpdateTransform) - { - // can be handy to know! - this.renderingToScreen = !renderTexture; - - this.runners.prerender.run(); - this.emit('prerender'); - - // 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.parent; - - displayObject.parent = this._tempDisplayObjectParent; - displayObject.updateTransform(); - displayObject.parent = 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.run(); - - this.emit('postrender'); - } - - /** - * Resizes the webGL view to the specified width and height. - * - * @param {number} screenWidth - the new width of the screen - * @param {number} screenHeight - the new height of the screen - */ - resize(screenWidth, screenHeight) - { - AbstractRenderer.prototype.resize.call(this, screenWidth, screenHeight); - - this.runners.resize.run(screenWidth, screenHeight); - } - - /** - * Resets the WebGL state so you can render things however you fancy! - * - * @return {PIXI.Renderer} Returns itself. - */ - reset() - { - this.runners.reset.run(); - - return this; - } - - clear() - { - this.framebuffer.bind(); - this.framebuffer.clear(); - } - - /** - * Removes everything from the renderer (event listeners, spritebatch, etc...) - * - * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. - * See: https://github.com/pixijs/pixi.js/issues/2233 - */ - destroy(removeView) - { - this.runners.destroy.run(); - - // call base destroy - super.destroy(removeView); - - // TODO nullify all the managers.. - this.gl = null; - } - - /** - * 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 - * builds and excluding plugins. - * @name PIXI.Renderer#plugins - * @type {object} - * @readonly - * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. - * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. - * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. - * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. - */ - - /** - * Adds a plugin to the renderer. - * - * @method - * @param {string} pluginName - The name of the plugin. - * @param {Function} ctor - The constructor function or class for the plugin. - */ - static registerPlugin(pluginName, ctor) - { - Renderer.__plugins = Renderer.__plugins || {}; - Renderer.__plugins[pluginName] = ctor; - } -} diff --git a/packages/core/src/renderers/State.js b/packages/core/src/renderers/State.js deleted file mode 100644 index fa3f221..0000000 --- a/packages/core/src/renderers/State.js +++ /dev/null @@ -1,163 +0,0 @@ -/* eslint-disable max-len */ - -const BLEND = 0; -const OFFSET = 1; -const CULLING = 2; -const DEPTH_TEST = 3; -const WINDING = 4; - -/** - * This is a webGL state. It is passed The WebGL StateManager. - * Each mesh renderered may require webGL to be in a different state. - * For example you may want different blend mode or to enable polygon offsets - * - * @class - * @memberof PIXI - */ -export default class State -{ - /** - * - */ - constructor() - { - this.data = 0; - - this.blendMode = 0; - this.polygonOffset = 0; - - this.blend = true; - // this.depthTest = true; - } - - /** - * Activates blending of the computed fragment color values - * - * @member {boolean} - */ - get blend() - { - return !!(this.data & (1 << BLEND)); - } - - set blend(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << BLEND)) !== value) - { - this.data ^= (1 << BLEND); - } - } - - /** - * Activates adding an offset to depth values of polygon's fragments - * - * @member {boolean} - * @default false - */ - get offsets() - { - return !!(this.data & (1 << OFFSET)); - } - - set offsets(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << OFFSET)) !== value) - { - this.data ^= (1 << OFFSET); - } - } - - /** - * Activates culling of polygons. - * - * @member {boolean} - * @default false - */ - get culling() - { - return !!(this.data & (1 << CULLING)); - } - - set culling(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << CULLING)) !== value) - { - this.data ^= (1 << CULLING); - } - } - - /** - * Activates depth comparisons and updates to the depth buffer. - * - * @member {boolean} - * @default false - */ - get depthTest() - { - return !!(this.data & (1 << DEPTH_TEST)); - } - - set depthTest(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << DEPTH_TEST)) !== value) - { - this.data ^= (1 << DEPTH_TEST); - } - } - - /** - * Specifies whether or not front or back-facing polygons can be culled. - * @member {boolean} - * @default false - */ - get clockwiseFrontFace() - { - return !!(this.data & (1 << WINDING)); - } - - set clockwiseFrontFace(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << WINDING)) !== value) - { - this.data ^= (1 << WINDING); - } - } - - /** - * The blend mode to be applied when this state is set. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. - * Setting this mode to anything other than NO_BLEND will automatically switch blending on. - * - * @member {boolean} - * @default PIXI.BLEND_MODES.NORMAL - * @see PIXI.BLEND_MODES - */ - get blendMode() - { - return this._blendMode; - } - - set blendMode(value) // eslint-disable-line require-jsdoc - { - // 17 is NO BLEND - this.blend = (value !== 17); - this._blendMode = value; - } - - /** - * The polygon offset. Setting this property to anything other than 0 will automatically enable poygon offset fill. - * - * @member {number} - * @default 0 - */ - get polygonOffset() - { - return this._polygonOffset; - } - - set polygonOffset(value) // eslint-disable-line require-jsdoc - { - this.offsets = !!value; - this._polygonOffset = value; - } -} - diff --git a/packages/core/src/renderers/filters/Filter.js b/packages/core/src/renderers/filters/Filter.js deleted file mode 100644 index 2073b8d..0000000 --- a/packages/core/src/renderers/filters/Filter.js +++ /dev/null @@ -1,140 +0,0 @@ -import Shader from '../../shader/Shader'; -import Program from '../../shader/Program'; -import State from '../State'; -import { settings } from '@pixi/settings'; -// import extractUniformsFromSrc from './extractUniformsFromSrc'; -import defaultVertex from './defaultFilter.vert'; -import defaultFragment from './defaultFilter.frag'; - -// let math = require('../../math'); -/** - * @class - * @memberof PIXI - * @extends PIXI.Shader - */ -export default class Filter extends Shader -{ - /** - * @param {string} [vertexSrc] - The source of the vertex shader. - * @param {string} [fragmentSrc] - The source of the fragment shader. - * @param {object} [uniforms] - Custom uniforms to use to augment the built-in ones. - */ - constructor(vertexSrc, fragmentSrc, uniforms) - { - const program = Program.from(vertexSrc, fragmentSrc); - - super(program, uniforms); - - /** - * The padding of the filter. Some filters require extra space to breath such as a blur. - * Increasing this will add extra width and height to the bounds of the object that the - * filter is applied to. - * - * @member {number} - */ - this.padding = 0; - - /** - * The resolution of the filter. Setting this to be lower will lower the quality but - * increase the performance of the filter. - * - * @member {number} - */ - this.resolution = settings.RESOLUTION; - - /** - * If enabled is true the filter is applied, if false it will not. - * - * @member {boolean} - */ - this.enabled = true; - - /** - * If enabled, PixiJS will fit the filter area into boundaries for better performance. - * Switch it off if it does not work for specific shader. - * - * @member {boolean} - */ - this.autoFit = true; - - /** - * Legacy filters use position and uvs from attributes - * @member {boolean} - * @readonly - */ - this.legacy = !!this.program.attributeData.aTextureCoord; - - /** - * the webGL state the filter requires to render - * @member {PIXI.State} - */ - this.state = new State(); - } - - /** - * Applies the filter - * - * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from - * @param {PIXI.RenderTarget} input - The input render target. - * @param {PIXI.RenderTarget} output - The target to output to. - * @param {boolean} clear - Should the output be cleared before rendering to it - * @param {object} [currentState] - It's current state of filter. - * There are some useful properties in the currentState : - * target, filters, sourceFrame, destinationFrame, renderTarget, resolution - */ - apply(filterManager, input, output, clear, currentState, derp) // eslint-disable-line no-unused-vars - { - // do as you please! - - filterManager.applyFilter(this, input, output, clear, currentState, derp); - - // or just do a regular render.. - } - - /** - * Sets the blendmode of the filter - * - * @member {number} - * @default PIXI.BLEND_MODES.NORMAL - */ - get blendMode() - { - return this.state.blendMode; - } - - set blendMode(value) // eslint-disable-line require-jsdoc - { - this.state.blendMode = value; - } - - /** - * The default vertex shader source - * - * @static - * @constant - */ - static get defaultVertexSrc() - { - return defaultVertex; - } - - /** - * The default fragment shader source - * - * @static - * @constant - */ - static get defaultFragmentSrc() - { - return defaultFragment; - } -} - -/** - * Used for caching shader IDs - * - * @static - * @private - */ -Filter.SOURCE_KEY_MAP = {}; - diff --git a/packages/core/src/AbstractRenderer.js b/packages/core/src/AbstractRenderer.js new file mode 100644 index 0000000..64b2eb1 --- /dev/null +++ b/packages/core/src/AbstractRenderer.js @@ -0,0 +1,353 @@ +import { hex2string, hex2rgb } from '@pixi/utils'; +import { Matrix, Rectangle } from '@pixi/math'; +import { RENDERER_TYPE } from '@pixi/constants'; +import { settings } from '@pixi/settings'; +import { Container } from '@pixi/display'; +import RenderTexture from './renderTexture/RenderTexture'; +import EventEmitter from 'eventemitter3'; + +const tempMatrix = new Matrix(); + +/** + * 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 EventEmitter + * @memberof PIXI + */ +export default class AbstractRenderer extends EventEmitter +{ + // eslint-disable-next-line valid-jsdoc + /** + * @param {string} system - The name of the system this renderer is for. + * @param {object} [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.transparent=false] - If the render view is transparent, default false + * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false + * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. The + * resolution of the renderer retina would be 2. + * @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 {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when rendering, + * stopping pixel interpolation. + */ + constructor(system, options, arg2, arg3) + { + super(); + + // Support for constructor(system, screenWidth, screenHeight, options) + if (typeof options === 'number') + { + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + }, arg3); + } + + // 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 = RENDERER_TYPE.UNKNOWN; + + /** + * Measurements of the screen. (0, 0, screenWidth, screenHeight) + * + * Its safe to use as filterArea or hitArea for 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 1 + */ + this.resolution = options.resolution || settings.RESOLUTION; + + /** + * Whether the render view is transparent + * + * @member {boolean} + */ + this.transparent = options.transparent; + + /** + * Whether css dimensions of canvas view should be resized to screen dimensions automatically + * + * @member {boolean} + */ + this.autoResize = options.autoResize || false; + + /** + * Tracks the blend modes useful for this renderer. + * + * @member {object} + */ + this.blendModes = null; + + /** + * 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; + + /** + * If true PixiJS will Math.floor() x/y values when rendering, stopping pixel interpolation. + * Handy for crisp pixel art and speed on legacy devices. + * + * @member {boolean} + */ + this.roundPixels = options.roundPixels; + + /** + * The background color as a number. + * + * @member {number} + * @private + */ + this._backgroundColor = 0x000000; + + /** + * The background color as an [R, G, B] array. + * + * @member {number[]} + * @private + */ + this._backgroundColorRgba = [0, 0, 0, 0]; + + /** + * The background color as a string. + * + * @member {string} + * @private + */ + this._backgroundColorString = '#000000'; + + this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter + + /** + * This temporary display object used as the parent of the currently being rendered item + * + * @member {PIXI.DisplayObject} + * @private + */ + this._tempDisplayObjectParent = new Container(); + + /** + * The last root object that the renderer tried to render. + * + * @member {PIXI.DisplayObject} + * @private + */ + this._lastObjectRendered = this._tempDisplayObjectParent; + + /** + * Collection of plugins. + * @readonly + * @member {object} + */ + this.plugins = {}; + } + + /** + * Initialize the plugins. + * + * @protected + * @param {object} staticMap - The dictionary of staticly saved plugins. + */ + initPlugins(staticMap) + { + 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() + { + return this.view.width; + } + + /** + * Same as view.height, actual number of pixels in the canvas by vertical + * + * @member {number} + * @readonly + * @default 600 + */ + get height() + { + return this.view.height; + } + + /** + * Resizes the screen and canvas to the specified width and height + * Canvas dimensions are multiplied by resolution + * + * @param {number} screenWidth - the new width of the screen + * @param {number} screenHeight - the new height of the screen + */ + resize(screenWidth, screenHeight) + { + this.screen.width = screenWidth; + this.screen.height = screenHeight; + + this.view.width = screenWidth * this.resolution; + this.view.height = screenHeight * this.resolution; + + if (this.autoResize) + { + this.view.style.width = `${screenWidth}px`; + this.view.style.height = `${screenHeight}px`; + } + } + + /** + * 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 {PIXI.DisplayObject} displayObject - The displayObject the object will be generated from + * @param {number} scaleMode - Should be one of the scaleMode consts + * @param {number} resolution - The resolution / device pixel ratio of the texture being generated + * @param {PIXI.Rectangle} [region] - The region of the displayObject, that shall be rendered, + * if no region is specified, defaults to the local bounds of the displayObject. + * @return {PIXI.Texture} a texture of the graphics object + */ + generateTexture(displayObject, scaleMode, resolution, region) + { + region = region || displayObject.getLocalBounds(); + + // 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(region.width | 0, region.height | 0, scaleMode, resolution); + + tempMatrix.tx = -region.x; + tempMatrix.ty = -region.y; + + this.render(displayObject, renderTexture, false, tempMatrix, true); + + return renderTexture; + } + + /** + * Removes everything from the renderer and optionally removes the Canvas DOM element. + * + * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. + */ + destroy(removeView) + { + 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); + } + + this.plugins = null; + + this.type = RENDERER_TYPE.UNKNOWN; + + this.view = null; + + this.screen = null; + + this.resolution = 0; + + this.transparent = false; + + this.autoResize = false; + + this.blendModes = null; + + this.options = null; + + this.preserveDrawingBuffer = false; + this.clearBeforeRender = false; + + this.roundPixels = false; + + this._backgroundColor = 0; + this._backgroundColorRgba = null; + this._backgroundColorString = null; + + this._tempDisplayObjectParent = null; + this._lastObjectRendered = null; + } + + /** + * The background color to fill if not transparent + * + * @member {number} + */ + get backgroundColor() + { + return this._backgroundColor; + } + + set backgroundColor(value) // eslint-disable-line require-jsdoc + { + this._backgroundColor = value; + this._backgroundColorString = hex2string(value); + hex2rgb(value, this._backgroundColorRgba); + } +} diff --git a/packages/core/src/Renderer.js b/packages/core/src/Renderer.js new file mode 100644 index 0000000..2227e78 --- /dev/null +++ b/packages/core/src/Renderer.js @@ -0,0 +1,338 @@ +import AbstractRenderer from './AbstractRenderer'; +import { sayHello } from '@pixi/utils'; +import MaskSystem from './mask/MaskSystem'; +import StencilSystem from './mask/StencilSystem'; +import FilterSystem from './filters/FilterSystem'; +import FramebufferSystem from './framebuffer/FramebufferSystem'; +import RenderTextureSystem from './renderTexture/RenderTextureSystem'; +import TextureSystem from './textures/TextureSystem'; +import ProjectionSystem from './projection/ProjectionSystem'; +import StateSystem from './state/StateSystem'; +import GeometrySystem from './geometry/GeometrySystem'; +import ShaderSystem from './shader/ShaderSystem'; +import ContextSystem from './context/ContextSystem'; +import BatchSystem from './batch/BatchSystem'; +import TextureGCSystem from './textures/TextureGCSystem'; +import { RENDERER_TYPE } from '@pixi/constants'; +import UniformGroup from './shader/UniformGroup'; +import { Matrix } from '@pixi/math'; +import Runner from 'mini-runner'; + +/** + * 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 Render works by automatically managing webGLBatchs. + * 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 :) + * + * @class + * @memberof PIXI + * @extends PIXI.AbstractRenderer + */ +export default class Renderer extends AbstractRenderer +{ + // eslint-disable-next-line valid-jsdoc + /** + * + * @param {object} [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.transparent=false] - If the render view is transparent, default false + * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false + * @param {boolean} [options.antialias=false] - sets antialias. If not available natively then FXAA + * antialiasing is used + * @param {boolean} [options.forceFXAA=false] - forces FXAA antialiasing to be used over native. + * FXAA is faster, but may not always look as great + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. + * The resolution of the renderer retina would be 2. + * @param {boolean} [options.clearBeforeRender=true] - This sets if the renderer will clear + * the canvas or not before the new render pass. If you wish to set this to false, you *must* set + * preserveDrawingBuffer to `true`. + * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, + * enable this if you need to call toDataUrl on the webgl context. + * @param {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when + * rendering, stopping pixel interpolation. + * @param {number} [options.backgroundColor=0x000000] - The background color of the rendered area + * (shown if not transparent). + * @param {boolean} [options.legacy=false] - If true PixiJS will aim to ensure compatibility + * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. + * @param {string} [options.powerPreference] - Parameter passed to webgl context, set to "high-performance" + * for devices with dual graphics card + */ + constructor(options, arg2, arg3) + { + super('WebGL', options, arg2, arg3); + + /** + * The type of this renderer as a standardised const + * + * @member {number} + * @see PIXI.RENDERER_TYPE + */ + this.type = RENDERER_TYPE.WEBGL; + + // this will be set by the contextSystem (this.context) + this.gl = null; + this.CONTEXT_UID = 0; + this.legacy = !!options.legacy; + + // TODO legacy! + + // runners! + this.runners = { + destroy: new Runner('destroy'), + contextChange: new Runner('contextChange', 1), + reset: new Runner('reset'), + update: new Runner('update'), + postrender: new Runner('postrender'), + prerender: new Runner('prerender'), + resize: new Runner('resize', 2), + }; + + 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(GeometrySystem, 'geometry') + .addSystem(FramebufferSystem, 'framebuffer') + .addSystem(StencilSystem, 'stencil') + .addSystem(ProjectionSystem, 'projection') + .addSystem(TextureGCSystem, 'textureGC') + .addSystem(FilterSystem, 'filter') + .addSystem(RenderTextureSystem, 'renderTexture') + .addSystem(BatchSystem, 'batch'); + + this.initPlugins(Renderer.__plugins); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ + if (options.context) + { + this.context.initFromContext(options.context); + } + else + { + this.context.initFromOptions({ + alpha: this.transparent, + antialias: options.antialias, + premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', + stencil: true, + 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); + } + + /** + * Add a new system to the renderer. + * @param {class} ClassRef - Class reference + * @param {string} [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. + * @return {PIXI.Renderer} Return instance of renderer + */ + addSystem(ClassRef, name) + { + if (!name) + { + name = ClassRef.name; + } + + // TODO - read name from class.name.. + + /* + if(name.includes('System')) + { + name = name.replace('System', ''); + name = name.charAt(0).toLowerCase() + name.slice(1); + } + */ + + const system = new ClassRef(this); + + if (this[name]) + { + throw new Error(`Whoops! ${name} is already a manger`); + } + + this[name] = system; + + for (const i in this.runners) + { + this.runners[i].add(system); + } + + return this; + + /** + * 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. + */ + } + + /** + * Renders the object to its webGL view + * + * @param {PIXI.DisplayObject} displayObject - the object to be rendered + * @param {PIXI.RenderTexture} renderTexture - The render texture to render to. + * @param {boolean} [clear] - Should the canvas be cleared before the new render + * @param {PIXI.Transform} [transform] - A transform to apply to the render texture before rendering. + * @param {boolean} [skipUpdateTransform] - Should we skip the update transform pass? + */ + render(displayObject, renderTexture, clear, transform, skipUpdateTransform) + { + // can be handy to know! + this.renderingToScreen = !renderTexture; + + this.runners.prerender.run(); + this.emit('prerender'); + + // 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.parent; + + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = 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.run(); + + this.emit('postrender'); + } + + /** + * Resizes the webGL view to the specified width and height. + * + * @param {number} screenWidth - the new width of the screen + * @param {number} screenHeight - the new height of the screen + */ + resize(screenWidth, screenHeight) + { + AbstractRenderer.prototype.resize.call(this, screenWidth, screenHeight); + + this.runners.resize.run(screenWidth, screenHeight); + } + + /** + * Resets the WebGL state so you can render things however you fancy! + * + * @return {PIXI.Renderer} Returns itself. + */ + reset() + { + this.runners.reset.run(); + + return this; + } + + clear() + { + this.framebuffer.bind(); + this.framebuffer.clear(); + } + + /** + * Removes everything from the renderer (event listeners, spritebatch, etc...) + * + * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. + * See: https://github.com/pixijs/pixi.js/issues/2233 + */ + destroy(removeView) + { + this.runners.destroy.run(); + + // call base destroy + super.destroy(removeView); + + // TODO nullify all the managers.. + this.gl = null; + } + + /** + * 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 + * builds and excluding plugins. + * @name PIXI.Renderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. + */ + + /** + * Adds a plugin to the renderer. + * + * @method + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + static registerPlugin(pluginName, ctor) + { + Renderer.__plugins = Renderer.__plugins || {}; + Renderer.__plugins[pluginName] = ctor; + } +} diff --git a/packages/core/src/System.js b/packages/core/src/System.js new file mode 100644 index 0000000..05a10c8 --- /dev/null +++ b/packages/core/src/System.js @@ -0,0 +1,42 @@ +/** + * System is a base class used for extending systems used by the {@link PIXI.Renderer} + * @see PIXI.Renderer#addSystem + * @class + * @memberof PIXI + */ +export default class System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + /** + * The renderer this manager works for. + * + * @member {PIXI.Renderer} + */ + this.renderer = renderer; + + this.renderer.runners.contextChange.add(this); + } + + /** + * Generic method called when there is a WebGL context change. + * + */ + contextChange() + { + // do some codes init! + } + + /** + * Generic destroy methods to be overridden by the subclass + * + */ + destroy() + { + this.renderer.runners.contextChange.remove(this); + this.renderer = null; + } +} diff --git a/packages/core/src/batch/BatchSystem.js b/packages/core/src/batch/BatchSystem.js new file mode 100644 index 0000000..b04c249 --- /dev/null +++ b/packages/core/src/batch/BatchSystem.js @@ -0,0 +1,68 @@ +import System from '../System'; +import ObjectRenderer from './ObjectRenderer'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class BatchSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + super(renderer); + + /** + * An empty renderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.emptyRenderer = new ObjectRenderer(renderer); + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.currentRenderer = this.emptyRenderer; + } + + /** + * Changes the current renderer to the one given in parameter + * + * @param {PIXI.ObjectRenderer} objectRenderer - The object renderer to use. + */ + setObjectRenderer(objectRenderer) + { + if (this.currentRenderer === objectRenderer) + { + return; + } + + this.currentRenderer.stop(); + this.currentRenderer = objectRenderer; + + this.renderer.state.setState(objectRenderer.state); + + this.currentRenderer.start(); + } + + /** + * This should be called if you wish to do some custom rendering + * It will basically render anything that may be batched up such as sprites + * + */ + flush() + { + this.setObjectRenderer(this.emptyRenderer); + } + + reset() + { + this.setObjectRenderer(this.emptyRenderer); + } +} diff --git a/packages/core/src/batch/ObjectRenderer.js b/packages/core/src/batch/ObjectRenderer.js new file mode 100644 index 0000000..0c851a9 --- /dev/null +++ b/packages/core/src/batch/ObjectRenderer.js @@ -0,0 +1,48 @@ +import System from '../System'; + +/** + * Base for a common object renderer that can be used as a system renderer plugin. + * + * @class + * @extends PIXI.System + * @memberof PIXI + */ +export default class ObjectRenderer extends System +{ + /** + * Starts the renderer and sets the shader + * + */ + start() + { + // set the shader.. + } + + /** + * Stops the renderer + * + */ + stop() + { + this.flush(); + } + + /** + * Stub method for rendering content and emptying the current batch. + * + */ + flush() + { + // flush! + } + + /** + * Renders an object + * + * @param {PIXI.DisplayObject} object - The object to render. + */ + render(object) // eslint-disable-line no-unused-vars + { + // render the object + } +} diff --git a/packages/core/src/context/ContextSystem.js b/packages/core/src/context/ContextSystem.js new file mode 100644 index 0000000..301aab4 --- /dev/null +++ b/packages/core/src/context/ContextSystem.js @@ -0,0 +1,186 @@ +import System from '../System'; +import { settings } from '@pixi/settings'; + +let CONTEXT_UID = 0; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class ContextSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.webGLVersion = 1; + + this.handleContextLost = this.handleContextLost.bind(this); + this.handleContextRestored = this.handleContextRestored.bind(this); + + this.extensions = {}; + + renderer.view.addEventListener('webglcontextlost', this.handleContextLost, false); + renderer.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + } + + get isLost() + { + return (!this.gl || this.gl.isContextLost()); + } + + contextChange(gl) + { + this.gl = gl; + + // restore a context if it was previously lost + if (gl.isContextLost() && gl.getExtension('WEBGL_lose_context')) + { + gl.getExtension('WEBGL_lose_context').restoreContext(); + } + } + + initFromContext(gl) + { + this.gl = gl; + this.validateContext(gl); + this.renderer.gl = gl; + this.renderer.CONTEXT_UID = CONTEXT_UID++; + this.renderer.runners.contextChange.run(gl); + } + + initFromOptions(options) + { + const gl = this.createContext(this.renderer.view, options); + + this.initFromContext(gl); + } + + /** + * Helper class to create a webGL Context + * + * @class + * @memberof PIXI.glCore + * @param canvas {HTMLCanvasElement} the canvas element that we will get the context from + * @param options {Object} An options object that gets passed in to the canvas element containing the context attributes, + * see https://developer.mozilla.org/en/docs/Web/API/HTMLCanvasElement/getContext for the options available + * @return {WebGLRenderingContext} the WebGL context + */ + createContext(canvas, options) + { + let gl; + + if (settings.PREFER_WEBGL_2) + { + gl = canvas.getContext('webgl2', options); + } + + if (gl) + { + this.webGLVersion = 2; + } + else + { + this.webGLVersion = 1; + + gl = canvas.getContext('webgl', options) + || canvas.getContext('experimental-webgl', options); + + if (!gl) + { + // fail, not able to get a context + throw new Error('This browser does not support webGL. Try using the canvas renderer'); + } + } + + this.gl = gl; + + this.getExtensions(); + + return gl; + } + + getExtensions() + { + // time to set up default etensions that pixi uses.. + const gl = this.gl; + const extensions = this.extensions; + + if (this.webGLVersion === 1) + { + extensions.drawBuffers = gl.getExtension('WEBGL_draw_buffers'); + extensions.depthTexture = gl.getExtension('WEBKIT_WEBGL_depth_texture'); + extensions.floatTexture = gl.getExtension('OES_texture_float'); + extensions.loseContext = gl.getExtension('WEBGL_lose_context'); + + extensions.vertexArrayObject = gl.getExtension('OES_vertex_array_object') + || gl.getExtension('MOZ_OES_vertex_array_object') + || gl.getExtension('WEBKIT_OES_vertex_array_object'); + } + + // we don't use any specific WebGL 2 ones yet! + } + + /** + * Handles a lost webgl context + * + * @private + * @param {WebGLContextEvent} event - The context lost event. + */ + handleContextLost(event) + { + event.preventDefault(); + } + + /** + * Handles a restored webgl context + * + * @private + */ + handleContextRestored() + { + this.renderer.runners.contextChange.run(this.gl); + } + + destroy() + { + const view = this.renderer.view; + + // remove listeners + view.removeEventListener('webglcontextlost', this.handleContextLost); + view.removeEventListener('webglcontextrestored', this.handleContextRestored); + + this.gl.useProgram(null); + + if (this.extensions.loseContext) + { + this.extensions.loseContext.loseContext(); + } + } + + postrender() + { + this.gl.flush(); + } + + validateContext(gl) + { + const attributes = gl.getContextAttributes(); + + // this is going to be fairly simple for now.. but at least we have room to grow! + if (!attributes.stencil) + { + /* eslint-disable max-len */ + + /* eslint-disable no-console */ + console.warn('Provided WebGL context does not have a stencil buffer, masks may not render correctly'); + /* eslint-enable no-console */ + + /* eslint-enable max-len */ + } + } +} diff --git a/packages/core/src/filters/Filter.js b/packages/core/src/filters/Filter.js new file mode 100644 index 0000000..9d8cad8 --- /dev/null +++ b/packages/core/src/filters/Filter.js @@ -0,0 +1,140 @@ +import Shader from '../shader/Shader'; +import Program from '../shader/Program'; +import State from '../state/State'; +import { settings } from '@pixi/settings'; +// import extractUniformsFromSrc from './extractUniformsFromSrc'; +import defaultVertex from './defaultFilter.vert'; +import defaultFragment from './defaultFilter.frag'; + +// let math = require('../../math'); +/** + * @class + * @memberof PIXI + * @extends PIXI.Shader + */ +export default class Filter extends Shader +{ + /** + * @param {string} [vertexSrc] - The source of the vertex shader. + * @param {string} [fragmentSrc] - The source of the fragment shader. + * @param {object} [uniforms] - Custom uniforms to use to augment the built-in ones. + */ + constructor(vertexSrc, fragmentSrc, uniforms) + { + const program = Program.from(vertexSrc, fragmentSrc); + + super(program, uniforms); + + /** + * The padding of the filter. Some filters require extra space to breath such as a blur. + * Increasing this will add extra width and height to the bounds of the object that the + * filter is applied to. + * + * @member {number} + */ + this.padding = 0; + + /** + * The resolution of the filter. Setting this to be lower will lower the quality but + * increase the performance of the filter. + * + * @member {number} + */ + this.resolution = settings.RESOLUTION; + + /** + * If enabled is true the filter is applied, if false it will not. + * + * @member {boolean} + */ + this.enabled = true; + + /** + * If enabled, PixiJS will fit the filter area into boundaries for better performance. + * Switch it off if it does not work for specific shader. + * + * @member {boolean} + */ + this.autoFit = true; + + /** + * Legacy filters use position and uvs from attributes + * @member {boolean} + * @readonly + */ + this.legacy = !!this.program.attributeData.aTextureCoord; + + /** + * the webGL state the filter requires to render + * @member {PIXI.State} + */ + this.state = new State(); + } + + /** + * Applies the filter + * + * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + * @param {boolean} clear - Should the output be cleared before rendering to it + * @param {object} [currentState] - It's current state of filter. + * There are some useful properties in the currentState : + * target, filters, sourceFrame, destinationFrame, renderTarget, resolution + */ + apply(filterManager, input, output, clear, currentState, derp) // eslint-disable-line no-unused-vars + { + // do as you please! + + filterManager.applyFilter(this, input, output, clear, currentState, derp); + + // or just do a regular render.. + } + + /** + * Sets the blendmode of the filter + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL + */ + get blendMode() + { + return this.state.blendMode; + } + + set blendMode(value) // eslint-disable-line require-jsdoc + { + this.state.blendMode = value; + } + + /** + * The default vertex shader source + * + * @static + * @constant + */ + static get defaultVertexSrc() + { + return defaultVertex; + } + + /** + * The default fragment shader source + * + * @static + * @constant + */ + static get defaultFragmentSrc() + { + return defaultFragment; + } +} + +/** + * Used for caching shader IDs + * + * @static + * @private + */ +Filter.SOURCE_KEY_MAP = {}; + diff --git a/packages/core/src/filters/FilterSystem.js b/packages/core/src/filters/FilterSystem.js new file mode 100644 index 0000000..3174318 --- /dev/null +++ b/packages/core/src/filters/FilterSystem.js @@ -0,0 +1,431 @@ +import System from '../System'; + +import RenderTexture from '../renderTexture/RenderTexture'; +import Quad from '../utils/Quad'; +import QuadUv from '../utils/QuadUv'; +import { Rectangle } from '@pixi/math'; +import * as filterTransforms from './filterTransforms'; +import bitTwiddle from 'bit-twiddle'; +import UniformGroup from '../shader/UniformGroup'; +import { DRAW_MODES } from '@pixi/constants'; + +// +/** + * @ignore + * @class + */ +class FilterState +{ + /** + * + */ + constructor() + { + this.renderTexture = null; + this.sourceFrame = new Rectangle(); + this.destinationFrame = new Rectangle(); + this.filters = []; + this.target = null; + this.legacy = false; + this.resolution = 1; + } +} + +/** + * @class + * @memberof PIXI.systems + * @extends PIXI.System + */ +export default class FilterSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + /** + * stores a bunch of PO2 textures used for filtering + * @type {Object} + */ + this.texturePool = {}; + + /** + * a pool for storing filter states, save us creating new ones each tick + * @type {Array} + */ + this.statePool = []; + + /** + * A very simple geometry used when drawing a filter effect to the screen + * @type {Quad} + */ + this.quad = new Quad(); + + this.quadUv = new QuadUv(); + + /** + * Temporary rect for maths + * @type {PIXI.Rectangle} + */ + this.tempRect = new Rectangle(); + + this.activeState = {}; + + /** + * this uniform group is attached to filter uniforms when used + * @type {UniformGroup} + */ + this.globalUniforms = new UniformGroup({ + sourceFrame: this.tempRect, + destinationFrame: this.tempRect, + + // legacy variables + filterArea: new Float32Array(4), + filterClamp: new Float32Array(4), + }, true); + } + + /** + * Adds a new filter to the System. + * + * @param {PIXI.DisplayObject} target - The target of the filter to render. + * @param {PIXI.Filter[]} filters - The filters to apply. + */ + push(target, filters) + { + const renderer = this.renderer; + const filterStack = this.renderer.renderTexture.defaultFilterStack; + const state = this.statePool.pop() || new FilterState(); + + let resolution = filters[0].resolution; + let padding = filters[0].padding; + let autoFit = filters[0].autoFit; + let legacy = filters[0].legacy; + + for (let i = 1; i < filters.length; i++) + { + const filter = filters[i]; + + // lets use the lowest resolution.. + resolution = Math.min(resolution, filter.resolution); + // and the largest amount of padding! + padding = Math.max(padding, filter.padding); + // only auto fit if all filters are autofit + autoFit = autoFit || filter.autoFit; + + legacy = legacy || filter.legacy; + } + + filterStack.push(state); + + state.resolution = resolution; + + state.legacy = legacy; + + // round to whole number based on resolution + // TODO move that to the shader too? + state.sourceFrame = target.filterArea || target.getBounds(true); + + state.sourceFrame.pad(padding); + + if (autoFit) + { + state.sourceFrame.fit(this.renderer.renderTexture.destinationFrame); + } + + state.sourceFrame.round(resolution); + + state.renderTexture = this.getPotFilterTexture(state.sourceFrame.width, state.sourceFrame.height, resolution); + state.filters = filters; + + state.destinationFrame.width = state.renderTexture.width; + state.destinationFrame.height = state.renderTexture.height; + + state.renderTexture.filterFrame = state.sourceFrame; + renderer.renderTexture.bind(state.renderTexture, state.sourceFrame);// /, state.destinationFrame); + renderer.renderTexture.clear(); + } + + /** + * Pops off the filter and applies it. + * + */ + pop() + { + const renderer = this.renderer; + const filterStack = renderer.renderTexture.defaultFilterStack; + const state = filterStack.pop(); + const filters = state.filters; + + this.activeState = state; + + const globalUniforms = this.globalUniforms.uniforms; + + globalUniforms.sourceFrame = state.sourceFrame; + globalUniforms.destinationFrame = state.destinationFrame; + globalUniforms.resolution = state.resolution; + + // only update the rect if its legacy.. + if (state.legacy) + { + const filterArea = globalUniforms.filterArea; + const filterClamp = globalUniforms.filterClamp; + + filterArea[0] = state.destinationFrame.width; + filterArea[1] = state.destinationFrame.height; + filterArea[2] = state.sourceFrame.x; + filterArea[3] = state.sourceFrame.y; + + filterClamp[0] = 0.5 / state.resolution / state.destinationFrame.width; + filterClamp[1] = 0.5 / state.resolution / state.destinationFrame.height; + filterClamp[2] = (state.sourceFrame.width - 0.5) / state.resolution / state.destinationFrame.width; + filterClamp[3] = (state.sourceFrame.height - 0.5) / state.resolution / state.destinationFrame.height; + } + + this.globalUniforms.update(); + + const lastState = filterStack[filterStack.length - 1]; + + if (filters.length === 1) + { + filters[0].apply(this, state.renderTexture, lastState.renderTexture, false, state); + + this.returnFilterTexture(state.renderTexture); + } + else + { + let flip = state.renderTexture; + let flop = this.getPotFilterTexture( + flip.width, + flip.height, + state.resolution + ); + + flop.filterFrame = flip.filterFrame; + + let i = 0; + + for (i = 0; i < filters.length - 1; ++i) + { + filters[i].apply(this, flip, flop, true, state); + + const t = flip; + + flip = flop; + flop = t; + } + + filters[i].apply(this, flip, lastState.renderTexture, false, state); + + this.returnFilterTexture(flip); + this.returnFilterTexture(flop); + } + + this.statePool.push(state); + } + + /** + * Draws a filter. + * + * @param {PIXI.Filter} filter - The filter to draw. + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + * @param {boolean} clear - Should the output be cleared before rendering to it + */ + applyFilter(filter, input, output, clear) + { + const renderer = this.renderer; + + renderer.renderTexture.bind(output, output ? output.filterFrame : null); + + if (clear) + { + // gl.disable(gl.SCISSOR_TEST); + renderer.renderTexture.clear(); + // gl.enable(gl.SCISSOR_TEST); + } + + // set the uniforms.. + filter.uniforms.uSampler = input; + filter.uniforms.filterGlobals = this.globalUniforms; + + // TODO make it so that the order of this does not matter.. + // because it does at the moment cos of global uniforms. + // they need to get resynced + + renderer.state.setState(filter.state); + renderer.shader.bind(filter); + + if (filter.legacy) + { + this.quadUv.map(input._frame, input.filterFrame); + + renderer.geometry.bind(this.quadUv); + renderer.geometry.draw(DRAW_MODES.TRIANGLES); + } + else + { + renderer.geometry.bind(this.quad); + renderer.geometry.draw(DRAW_MODES.TRIANGLE_STRIP); + } + } + + /** + * Calculates the mapped matrix. + * + * TODO playing around here.. this is temporary - (will end up in the shader) + * this returns a matrix that will normalise map filter cords in the filter to screen space + * + * @param {PIXI.Matrix} outputMatrix - the matrix to output to. + * @return {PIXI.Matrix} The mapped matrix. + */ + calculateScreenSpaceMatrix(outputMatrix) + { + const currentState = this.activeState; + + return filterTransforms.calculateScreenSpaceMatrix( + outputMatrix, + currentState.sourceFrame, + currentState.destinationFrame + ); + } + + /** + * This will map the filter coord so that a texture can be used based on the transform of a sprite + * + * @param {PIXI.Matrix} outputMatrix - The matrix to output to. + * @param {PIXI.Sprite} sprite - The sprite to map to. + * @return {PIXI.Matrix} The mapped matrix. + */ + calculateSpriteMatrix(outputMatrix, sprite) + { + const currentState = this.activeState; + + return filterTransforms.calculateSpriteMatrix( + outputMatrix, + currentState.sourceFrame, + currentState.destinationFrame, + sprite + ); + } + + /** + * Destroys this Filter System. + * + * @param {boolean} [contextLost=false] context was lost, do not free shaders + * + */ + destroy(contextLost = false) + { + if (!contextLost) + { + this.emptyPool(); + } + else + { + this.texturePool = {}; + } + } + + /** + * Gets a Power-of-Two render texture. + * + * TODO move to a seperate class could be on renderer? + * also - could cause issue with multiple contexts? + * + * @private + * @param {WebGLRenderingContext} gl - The webgl rendering context + * @param {number} minWidth - The minimum width of the render target. + * @param {number} minHeight - The minimum height of the render target. + * @param {number} resolution - The resolution of the render target. + * @return {PIXI.RenderTarget} The new render target. + */ + getPotFilterTexture(minWidth, minHeight, resolution) + { + minWidth = bitTwiddle.nextPow2(minWidth); + minHeight = bitTwiddle.nextPow2(minHeight); + resolution = resolution || 1; + + const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + + if (!this.texturePool[key]) + { + this.texturePool[key] = []; + } + + let renderTexture = this.texturePool[key].pop(); + + if (!renderTexture) + { + // temporary bypass cache.. + // internally - this will cause a texture to be bound.. + renderTexture = RenderTexture.create({ + width: minWidth, + height: minHeight, + resolution, + }); + } + + return renderTexture; + } + + /** + * Gets extra render texture to use inside current filter + * + * @param {number} resolution resolution of the renderTexture + * @returns {PIXI.RenderTexture} + */ + getFilterTexture(resolution) + { + const rt = this.activeState.renderTexture; + + const filterTexture = this.getPotFilterTexture(rt.width, rt.height, resolution || rt.baseTexture.resolution); + + filterTexture.filterFrame = rt.filterFrame; + + return filterTexture; + } + + /** + * Frees a render texture back into the pool. + * + * @param {PIXI.RenderTarget} renderTexture - The renderTarget to free + */ + returnFilterTexture(renderTexture) + { + renderTexture.filterFrame = null; + + const base = renderTexture.baseTexture; + + const minWidth = base.width; + const minHeight = base.height; + + const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + + this.texturePool[key].push(renderTexture); + } + + /** + * Empties the texture pool. + * + */ + emptyPool() + { + for (const i in this.texturePool) + { + const textures = this.texturePool[i]; + + if (textures) + { + for (let j = 0; j < textures.length; j++) + { + textures[j].destroy(true); + } + } + } + + this.texturePool = {}; + } +} diff --git a/packages/core/src/filters/defaultFilter.frag b/packages/core/src/filters/defaultFilter.frag new file mode 100644 index 0000000..a14c1be --- /dev/null +++ b/packages/core/src/filters/defaultFilter.frag @@ -0,0 +1,22 @@ +varying vec2 vTextureCoord; +varying vec2 vFilterCoord; + +uniform sampler2D uSampler; +uniform sampler2D filterSampler; + +void main(void){ + vec4 masky = texture2D(filterSampler, vFilterCoord); + vec4 sample = texture2D(uSampler, vTextureCoord); + vec4 color; + if(mod(vFilterCoord.x, 1.0) > 0.5) + { + color = vec4(1.0, 0.0, 0.0, 1.0); + } + else + { + color = vec4(0.0, 1.0, 0.0, 1.0); + } + // gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0); + gl_FragColor = mix(sample, masky, 0.5); + gl_FragColor *= sample.a; +} diff --git a/packages/core/src/filters/defaultFilter.vert b/packages/core/src/filters/defaultFilter.vert new file mode 100644 index 0000000..e1febca --- /dev/null +++ b/packages/core/src/filters/defaultFilter.vert @@ -0,0 +1,14 @@ +attribute vec2 aVertexPosition; +attribute vec2 aTextureCoord; + +uniform mat3 projectionMatrix; +uniform mat3 filterMatrix; + +varying vec2 vTextureCoord; +varying vec2 vFilterCoord; + +void main(void){ + gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); + vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy; + vTextureCoord = aTextureCoord ; +} diff --git a/packages/core/src/filters/filterTransforms.js b/packages/core/src/filters/filterTransforms.js new file mode 100644 index 0000000..87a3cd6 --- /dev/null +++ b/packages/core/src/filters/filterTransforms.js @@ -0,0 +1,50 @@ +import { Matrix } from '@pixi/math'; + +/** + * Calculates the mapped matrix + * @param filterArea {Rectangle} The filter area + * @param sprite {Sprite} the target sprite + * @param outputMatrix {Matrix} @alvin + * @private + */ +// this returns a matrix that will normalise map filter cords in the filter to screen space +export function calculateScreenSpaceMatrix(outputMatrix, filterArea, textureSize) +{ + // TODO unwrap? + const mappedMatrix = outputMatrix.identity(); + + mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); + + mappedMatrix.scale(textureSize.width, textureSize.height); + + return mappedMatrix; +} + +export function calculateNormalizedScreenSpaceMatrix(outputMatrix, filterArea, textureSize) +{ + const mappedMatrix = outputMatrix.identity(); + + mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); + + const translateScaleX = (textureSize.width / filterArea.width); + const translateScaleY = (textureSize.height / filterArea.height); + + mappedMatrix.scale(translateScaleX, translateScaleY); + + return mappedMatrix; +} + +// this will map the filter coord so that a texture can be used based on the transform of a sprite +export function calculateSpriteMatrix(outputMatrix, filterArea, textureSize, sprite) +{ + const orig = sprite._texture.orig; + const mappedMatrix = outputMatrix.set(textureSize.width, 0, 0, textureSize.height, filterArea.x, filterArea.y); + const worldTransform = sprite.worldTransform.copyTo(Matrix.TEMP_MATRIX); + + worldTransform.invert(); + mappedMatrix.prepend(worldTransform); + mappedMatrix.scale(1.0 / orig.width, 1.0 / orig.height); + mappedMatrix.translate(sprite.anchor.x, sprite.anchor.y); + + return mappedMatrix; +} diff --git a/packages/core/src/filters/spriteMask/SpriteMaskFilter.js b/packages/core/src/filters/spriteMask/SpriteMaskFilter.js new file mode 100644 index 0000000..91697b7 --- /dev/null +++ b/packages/core/src/filters/spriteMask/SpriteMaskFilter.js @@ -0,0 +1,63 @@ +import Filter from '../Filter'; +import { Matrix } from '@pixi/math'; +import vertex from './spriteMaskFilter.vert'; +import fragment from './spriteMaskFilter.frag'; +import { default as TextureMatrix } from '../../textures/TextureMatrix'; + +/** + * The SpriteMaskFilter class + * + * @class + * @extends PIXI.Filter + * @memberof PIXI + */ +export default class SpriteMaskFilter extends Filter +{ + /** + * @param {PIXI.Sprite} sprite - the target sprite + */ + constructor(sprite) + { + const maskMatrix = new Matrix(); + + super(vertex, fragment); + + sprite.renderable = false; + + this.maskSprite = sprite; + this.maskMatrix = maskMatrix; + } + + /** + * Applies the filter + * + * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + */ + apply(filterManager, input, output) + { + const maskSprite = this.maskSprite; + const tex = this.maskSprite.texture; + + if (!tex.valid) + { + return; + } + if (!tex.transform) + { + // margin = 0.0, let it bleed a bit, shader code becomes easier + // assuming that atlas textures were made with 1-pixel padding + tex.transform = new TextureMatrix(tex, 0.0); + } + tex.transform.update(); + + this.uniforms.mask = tex; + this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite) + .prepend(tex.transform.mapCoord); + this.uniforms.alpha = maskSprite.worldAlpha; + this.uniforms.maskClamp = tex.transform.uClampFrame; + + filterManager.applyFilter(this, input, output); + } +} diff --git a/packages/core/src/filters/spriteMask/spriteMaskFilter.frag b/packages/core/src/filters/spriteMask/spriteMaskFilter.frag new file mode 100644 index 0000000..0e4aef8 --- /dev/null +++ b/packages/core/src/filters/spriteMask/spriteMaskFilter.frag @@ -0,0 +1,23 @@ +varying vec2 vMaskCoord; +varying vec2 vTextureCoord; + +uniform sampler2D uSampler; +uniform sampler2D mask; +uniform float alpha; +uniform vec4 maskClamp; + +void main(void) +{ + float clip = step(3.5, + step(maskClamp.x, vMaskCoord.x) + + step(maskClamp.y, vMaskCoord.y) + + step(vMaskCoord.x, maskClamp.z) + + step(vMaskCoord.y, maskClamp.w)); + + vec4 original = texture2D(uSampler, vTextureCoord); + vec4 masky = texture2D(mask, vMaskCoord); + + original *= (masky.r * masky.a * alpha * clip); + + gl_FragColor = original; +} diff --git a/packages/core/src/filters/spriteMask/spriteMaskFilter.vert b/packages/core/src/filters/spriteMask/spriteMaskFilter.vert new file mode 100644 index 0000000..d23a30c --- /dev/null +++ b/packages/core/src/filters/spriteMask/spriteMaskFilter.vert @@ -0,0 +1,16 @@ +attribute vec2 aVertexPosition; +attribute vec2 aTextureCoord; + +uniform mat3 projectionMatrix; +uniform mat3 otherMatrix; + +varying vec2 vMaskCoord; +varying vec2 vTextureCoord; + +void main(void) +{ + gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); + + vTextureCoord = aTextureCoord; + vMaskCoord = ( otherMatrix * vec3( aTextureCoord, 1.0) ).xy; +} diff --git a/packages/core/src/framebuffer/FrameBuffer.js b/packages/core/src/framebuffer/FrameBuffer.js new file mode 100644 index 0000000..84e9fcc --- /dev/null +++ b/packages/core/src/framebuffer/FrameBuffer.js @@ -0,0 +1,106 @@ +import Texture from '../textures/BaseTexture'; +import { FORMATS, TYPES } from '@pixi/constants'; + +/** + * Frame buffer + * @class + * @memberof PIXI + */ +export default class FrameBuffer +{ + constructor(width, height) + { + this.width = width || 100; + this.height = height || 100; + + this.stencil = false; + this.depth = false; + + this.dirtyId = 0; + this.dirtyFormat = 0; + this.dirtySize = 0; + + this.depthTexture = null; + this.colorTextures = []; + + this.glFrameBuffers = {}; + } + + get colorTexture() + { + return this.colorTextures[0]; + } + + addColorTexture(index, texture) + { + // TODO add some validation to the texture - same width / height etc? + this.colorTextures[index || 0] = texture || new Texture(null, { scaleMode: 0, + resolution: 1, + mipmap: false, + width: this.width, + height: this.height });// || new Texture(); + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + addDepthTexture(texture) + { + /* eslint-disable max-len */ + this.depthTexture = texture || new Texture(null, { scaleMode: 0, + resolution: 1, + width: this.width, + height: this.height, + mipmap: false, + format: FORMATS.DEPTH_COMPONENT, + type: TYPES.UNSIGNED_SHORT });// UNSIGNED_SHORT; + /* eslint-disable max-len */ + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + enableDepth() + { + this.depth = true; + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + enableStencil() + { + this.stencil = true; + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + resize(width, height) + { + if (width === this.width && height === this.height) return; + + this.width = width; + this.height = height; + + this.dirtyId++; + this.dirtySize++; + + for (let i = 0; i < this.colorTextures.length; i++) + { + this.colorTextures[i].setSize(width, height); + } + + if (this.depthTexture) + { + this.depthTexture.setSize(width, height); + } + } +} diff --git a/packages/core/src/framebuffer/FramebufferSystem.js b/packages/core/src/framebuffer/FramebufferSystem.js new file mode 100644 index 0000000..2cc2437 --- /dev/null +++ b/packages/core/src/framebuffer/FramebufferSystem.js @@ -0,0 +1,246 @@ +import System from '../System'; +import { Rectangle } from '@pixi/math'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class FramebufferSystem extends System +{ + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + contextChange() + { + this.gl = this.renderer.gl; + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + this.current = null; + this.viewport = new Rectangle(); + + this.drawBufferExtension = this.renderer.context.extensions.drawBuffers; + } + + bind(framebuffer, frame) + { + const gl = this.gl; + + this.current = framebuffer; + + if (framebuffer) + { + // TODO cacheing layer! + + const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID] || this.initFramebuffer(framebuffer); + + gl.bindFramebuffer(gl.FRAMEBUFFER, fbo.framebuffer); + // makesure all textures are unbound.. + + // now check for updates... + if (fbo.dirtyId !== framebuffer.dirtyId) + { + fbo.dirtyId = framebuffer.dirtyId; + + if (fbo.dirtyFormat !== framebuffer.dirtyFormat) + { + fbo.dirtyFormat = framebuffer.dirtyFormat; + this.updateFramebuffer(framebuffer); + } + else if (fbo.dirtySize !== framebuffer.dirtySize) + { + fbo.dirtySize = framebuffer.dirtySize; + this.resizeFramebuffer(framebuffer); + } + } + + for (let i = 0; i < framebuffer.colorTextures.length; i++) + { + if (framebuffer.colorTextures[i].texturePart) + { + this.renderer.texture.unbind(framebuffer.colorTextures[i].texture); + } + else + { + this.renderer.texture.unbind(framebuffer.colorTextures[i]); + } + } + + if (framebuffer.depthTexture) + { + this.renderer.texture.unbind(framebuffer.depthTexture); + } + + if (frame) + { + this.setViewport(frame.x, frame.y, frame.width, frame.height); + } + else + { + this.setViewport(0, 0, framebuffer.width, framebuffer.height); + } + } + else + { + gl.bindFramebuffer(gl.FRAMEBUFFER, null); + + if (frame) + { + this.setViewport(frame.x, frame.y, frame.width, frame.height); + } + else + { + this.setViewport(0, 0, this.renderer.width, this.renderer.height); + } + } + } + + setViewport(x, y, width, height) + { + const v = this.viewport; + + if (v.width !== width || v.height !== height || v.x !== x || v.y !== y) + { + v.x = x; + v.y = y; + v.width = width; + v.height = height; + + this.gl.viewport(x, y, width, height); + } + } + + get size() + { + if (this.current) + { + // TODO store temp + return { x: 0, y: 0, width: this.current.width, height: this.current.height }; + } + + return { x: 0, y: 0, width: this.renderer.width, height: this.renderer.height }; + } + + clear(r, g, b, a) + { + const gl = this.gl; + + // TODO clear color can be set only one right? + gl.clearColor(r, g, b, a); + gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); + } + + // private functions... + + initFramebuffer(framebuffer) + { + const gl = this.gl; + + // TODO - make this a class? + const fbo = { + framebuffer: gl.createFramebuffer(), + stencil: null, + dirtyId: 0, + dirtyFormat: 0, + dirtySize: 0, + }; + + framebuffer.glFrameBuffers[this.CONTEXT_UID] = fbo; + + return fbo; + } + + resizeFramebuffer(framebuffer) + { + const gl = this.gl; + + if (framebuffer.stencil || framebuffer.depth) + { + gl.bindRenderbuffer(gl.RENDERBUFFER, this.stencil); + gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); + } + } + + updateFramebuffer(framebuffer) + { + const gl = this.gl; + + const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID]; + + // bind the color texture + const colorTextures = framebuffer.colorTextures; + + let count = colorTextures.length; + + if (!this.drawBufferExtension) + { + count = Math.min(count, 1); + } + + const activeTextures = []; + + for (let i = 0; i < count; i++) + { + const texture = framebuffer.colorTextures[i]; + + if (texture.texturePart) + { + this.renderer.texture.bind(texture.texture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.COLOR_ATTACHMENT0 + i, + gl.TEXTURE_CUBE_MAP_NEGATIVE_X + texture.side, + texture.texture._glTextures[this.CONTEXT_UID].texture, + 0); + } + else + { + this.renderer.texture.bind(texture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.COLOR_ATTACHMENT0 + i, + gl.TEXTURE_2D, + texture._glTextures[this.CONTEXT_UID].texture, + 0); + } + + activeTextures.push(gl.COLOR_ATTACHMENT0 + i); + } + + if (this.drawBufferExtension && activeTextures.length > 1) + { + this.drawBufferExtension.drawBuffersWEBGL(activeTextures); + } + + if (framebuffer.depthTexture) + { + const depthTextureExt = this.renderer.context.extensions.depthTexture; + + if (depthTextureExt) + { + const depthTexture = framebuffer.depthTexture; + + this.renderer.texture.bind(depthTexture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.DEPTH_ATTACHMENT, + gl.TEXTURE_2D, + depthTexture._glTextures[this.CONTEXT_UID].texture, + 0); + } + } + + if (framebuffer.stencil || framebuffer.depth) + { + fbo.stencil = gl.createRenderbuffer(); + + gl.bindRenderbuffer(gl.RENDERBUFFER, fbo.stencil); + + // TODO.. this is depth AND stencil? + gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, fbo.stencil); + gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); + // fbo.enableStencil(); + } + } +} diff --git a/packages/core/src/geometry/GLBuffer.js b/packages/core/src/geometry/GLBuffer.js new file mode 100644 index 0000000..0477e8a --- /dev/null +++ b/packages/core/src/geometry/GLBuffer.js @@ -0,0 +1,9 @@ +export default class GLBuffer +{ + constructor(buffer) + { + this.buffer = buffer; + this.updateID = -1; + this.byteLength = -1; + } +} diff --git a/packages/core/src/geometry/GeometrySystem.js b/packages/core/src/geometry/GeometrySystem.js new file mode 100644 index 0000000..959f827 --- /dev/null +++ b/packages/core/src/geometry/GeometrySystem.js @@ -0,0 +1,388 @@ +import System from '../System'; +import GLBuffer from './GLBuffer'; + +const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class GeometrySystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this._activeGeometry = null; + this._activeVao = null; + + this.hasVao = true; + this.hasInstance = true; + } + + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + contextChange() + { + const gl = this.gl = this.renderer.gl; + + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + + // webgl2 + if (!gl.createVertexArray) + { + // webgl 1! + let nativeVaoExtension = this.renderer.context.extensions.vertexArrayObject; + + if (this.renderer.legacy) + { + nativeVaoExtension = null; + } + + if (nativeVaoExtension) + { + gl.createVertexArray = () => + nativeVaoExtension.createVertexArrayOES(); + + gl.bindVertexArray = (vao) => + nativeVaoExtension.bindVertexArrayOES(vao); + + gl.deleteVertexArray = (vao) => + nativeVaoExtension.deleteVertexArrayOES(vao); + } + else + { + this.hasVao = false; + gl.createVertexArray = () => + { + // empty + }; + + gl.bindVertexArray = () => + { + // empty + }; + + gl.deleteVertexArray = () => + { + // empty + }; + } + } + + if (!gl.vertexAttribDivisor) + { + const instanceExt = gl.getExtension('ANGLE_instanced_arrays'); + + if (instanceExt) + { + gl.vertexAttribDivisor = (a, b) => + instanceExt.vertexAttribDivisorANGLE(a, b); + + gl.drawElementsInstanced = (a, b, c, d, e) => + instanceExt.drawElementsInstancedANGLE(a, b, c, d, e); + + gl.drawArraysInstanced = (a, b, c, d) => + instanceExt.drawArraysInstancedANGLE(a, b, c, d); + } + else + { + this.hasInstance = false; + } + } + } + + /** + * Binds geometry so that is can be drawn. Creating a Vao if required + * @private + * @param {PIXI.Geometry} geometry instance of geometry to bind + */ + bind(geometry, shader) + { + shader = shader || this.renderer.shader.shader; + + const gl = this.gl; + + // not sure the best way to address this.. + // currently different shaders require different VAOs for the same geometry + // Still mulling over the best way to solve this one.. + // will likely need to modify the shader attribute locations at run time! + let vaos = geometry.glVertexArrayObjects[this.CONTEXT_UID]; + + if (!vaos) + { + geometry.glVertexArrayObjects[this.CONTEXT_UID] = vaos = {}; + } + + const vao = vaos[shader.program.id] || this.initGeometryVao(geometry, shader.program); + + this._activeGeometry = geometry; + + if (this._activeVao !== vao) + { + this._activeVao = vao; + + if (this.hasVao) + { + gl.bindVertexArray(vao); + } + else + { + this.activateVao(geometry, shader.program); + } + } + + // TODO - optimise later! + // don't need to loop through if nothing changed! + // maybe look to add an 'autoupdate' to geometry? + this.updateBuffers(); + } + + reset() + { + this.unbind(); + } + + updateBuffers() + { + const geometry = this._activeGeometry; + const gl = this.gl; + + for (let i = 0; i < geometry.buffers.length; i++) + { + const buffer = geometry.buffers[i]; + + const glBuffer = buffer._glBuffers[this.CONTEXT_UID]; + + if (buffer._updateID !== glBuffer.updateID) + { + glBuffer.updateID = buffer._updateID; + + // TODO can cache this on buffer! maybe added a getter / setter? + const type = buffer.index ? gl.ELEMENT_ARRAY_BUFFER : gl.ARRAY_BUFFER; + const drawType = buffer.static ? gl.STATIC_DRAW : gl.DYNAMIC_DRAW; + + gl.bindBuffer(type, glBuffer.buffer); + + if (glBuffer.byteLength >= buffer.data.byteLength) + { + // offset is always zero for now! + gl.bufferSubData(type, 0, buffer.data); + } + else + { + gl.bufferData(type, buffer.data, drawType); + } + } + } + } + + checkCompatability(geometry, program) + { + // geometry must have at least all the attributes that the shader requires. + const geometryAttributes = geometry.attributes; + const shaderAttributes = program.attributeData; + + for (const j in shaderAttributes) + { + if (!geometryAttributes[j]) + { + throw new Error(`shader and geometry incompatible, geometry missing the "${j}" attribute`); + } + } + } + + /** + * Creates a Vao with the same structure as the geometry and stores it on the geometry. + * @private + * @param {PIXI.Geometry} geometry instance of geometry to to generate Vao for + */ + initGeometryVao(geometry, program) + { + this.checkCompatability(geometry, program); + + const gl = this.gl; + const CONTEXT_UID = this.CONTEXT_UID; + const buffers = geometry.buffers; + const attributes = geometry.attributes; + + const tempStride = {}; + const tempStart = {}; + + for (const j in buffers) + { + tempStride[j] = 0; + tempStart[j] = 0; + } + + for (const j in attributes) + { + if (!attributes[j].size && program.attributeData[j]) + { + attributes[j].size = program.attributeData[j].size; + } + + tempStride[attributes[j].buffer] += attributes[j].size * byteSizeMap[attributes[j].type]; + } + + for (const j in attributes) + { + const attribute = attributes[j]; + const attribSize = attribute.size; + + if (attribute.stride === undefined) + { + if (tempStride[attribute.buffer] === attribSize * byteSizeMap[attribute.type]) + { + attribute.stride = 0; + } + else + { + attribute.stride = tempStride[attribute.buffer]; + } + } + + if (attribute.start === undefined) + { + attribute.start = tempStart[attribute.buffer]; + + tempStart[attribute.buffer] += attribSize * byteSizeMap[attribute.type]; + } + } + + // first update - and create the buffers! + // only create a gl buffer if it actually gets + for (let i = 0; i < buffers.length; i++) + { + const buffer = buffers[i]; + + if (!buffer._glBuffers[CONTEXT_UID]) + { + buffer._glBuffers[CONTEXT_UID] = new GLBuffer(gl.createBuffer()); + } + } + + // TODO - maybe make this a data object? + // lets wait to see if we need to first! + const vao = gl.createVertexArray(); + + gl.bindVertexArray(vao); + + this.activateVao(geometry, program); + + gl.bindVertexArray(null); + + // add it to the cache! + geometry.glVertexArrayObjects[this.CONTEXT_UID][program.id] = vao; + + return vao; + } + + activateVao(geometry, program) + { + const gl = this.gl; + const CONTEXT_UID = this.CONTEXT_UID; + const buffers = geometry.buffers; + const attributes = geometry.attributes; + + if (geometry.indexBuffer) + { + // first update the index buffer if we have one.. + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, geometry.indexBuffer._glBuffers[CONTEXT_UID].buffer); + } + + let lastBuffer = null; + + // add a new one! + for (const j in attributes) + { + const attribute = attributes[j]; + const buffer = buffers[attribute.buffer]; + const glBuffer = buffer._glBuffers[CONTEXT_UID]; + + if (program.attributeData[j]) + { + if (lastBuffer !== glBuffer) + { + gl.bindBuffer(gl.ARRAY_BUFFER, glBuffer.buffer); + + lastBuffer = glBuffer; + } + + const location = program.attributeData[j].location; + + // TODO introduce state again + // we can optimise this for older devices that have no VAOs + gl.enableVertexAttribArray(location); + + gl.vertexAttribPointer(location, + attribute.size, + attribute.type || gl.FLOAT, + attribute.normalized, + attribute.stride, + attribute.start); + + if (attribute.instance) + { + // TODO calculate instance count based of this... + if (this.hasInstance) + { + gl.vertexAttribDivisor(location, 1); + } + else + { + throw new Error('geometry error, GPU Instancing is not supported on this device'); + } + } + } + } + } + + draw(type, size, start, instanceCount) + { + const gl = this.gl; + const geometry = this._activeGeometry; + + // TODO.. this should not change so maybe cache the function? + + if (geometry.indexBuffer) + { + if (geometry.instanced) + { + /* eslint-disable max-len */ + gl.drawElementsInstanced(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2, instanceCount || 1); + /* eslint-enable max-len */ + } + else + { + gl.drawElements(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2); + } + } + else + if (geometry.instanced) + { + // TODO need a better way to calculate size.. + gl.drawArraysInstanced(type, start, size || geometry.getSize(), instanceCount || 1); + } + else + { + gl.drawArrays(type, start, size || geometry.getSize()); + } + + return this; + } + + unbind() + { + this.gl.bindVertexArray(null); + this._activeVao = null; + this._activeGeometry = null; + } +} diff --git a/packages/core/src/geometry/utils/setVertexAttribArrays.js b/packages/core/src/geometry/utils/setVertexAttribArrays.js new file mode 100644 index 0000000..23b6959 --- /dev/null +++ b/packages/core/src/geometry/utils/setVertexAttribArrays.js @@ -0,0 +1,54 @@ +// var GL_MAP = {}; + +/** + * @param gl {WebGLRenderingContext} The current WebGL context + * @param attribs {*} + * @param state {*} + */ +export default function setVertexAttribArrays(gl, attribs, state) +{ + let i; + + if (state) + { + const tempAttribState = state.tempAttribState; + const attribState = state.attribState; + + for (i = 0; i < tempAttribState.length; i++) + { + tempAttribState[i] = false; + } + + // set the new attribs + for (i = 0; i < attribs.length; i++) + { + tempAttribState[attribs[i].attribute.location] = true; + } + + for (i = 0; i < attribState.length; i++) + { + if (attribState[i] !== tempAttribState[i]) + { + attribState[i] = tempAttribState[i]; + + if (state.attribState[i]) + { + gl.enableVertexAttribArray(i); + } + else + { + gl.disableVertexAttribArray(i); + } + } + } + } + else + { + for (i = 0; i < attribs.length; i++) + { + const attrib = attribs[i]; + + gl.enableVertexAttribArray(attrib.attribute.location); + } + } +} diff --git a/packages/core/src/index.js b/packages/core/src/index.js index 58c456a..fd1f547 100644 --- a/packages/core/src/index.js +++ b/packages/core/src/index.js @@ -1,30 +1,30 @@ import * as resources from './textures/resources'; -import * as systems from './renderers/systems'; +import * as systems from './systems'; -export { resources }; export { systems }; +export { resources }; -export { default as Renderer } from './renderers/Renderer'; -export { default as AbstractRenderer } from './renderers/AbstractRenderer'; -export { default as FrameBuffer } from './textures/FrameBuffer'; +export { default as System } from './System'; +export { default as Renderer } from './Renderer'; +export { default as AbstractRenderer } from './AbstractRenderer'; +export { default as FrameBuffer } from './framebuffer/FrameBuffer'; export { default as CubeTexture } from './textures/CubeTexture'; export { default as BaseTexture } from './textures/BaseTexture'; export { default as Texture } from './textures/Texture'; export { default as TextureMatrix } from './textures/TextureMatrix'; -export { default as RenderTexture } from './textures/RenderTexture'; -export { default as BaseRenderTexture } from './textures/BaseRenderTexture'; +export { default as RenderTexture } from './renderTexture/RenderTexture'; +export { default as BaseRenderTexture } from './renderTexture/BaseRenderTexture'; export { default as TextureUvs } from './textures/TextureUvs'; -export { default as State } from './renderers/State'; -export { default as ObjectRenderer } from './renderers/utils/ObjectRenderer'; -export { default as Quad } from './renderers/utils/Quad'; -export { default as QuadUv } from './renderers/utils/QuadUv'; -export { default as checkMaxIfStatmentsInShader } from './renderers/utils/checkMaxIfStatmentsInShader'; +export { default as State } from './state/State'; +export { default as ObjectRenderer } from './batch/ObjectRenderer'; +export { default as Quad } from './utils/Quad'; +export { default as QuadUv } from './utils/QuadUv'; +export { default as checkMaxIfStatmentsInShader } from './shader/utils/checkMaxIfStatmentsInShader'; export { default as Shader } from './shader/Shader'; export { default as Program } from './shader/Program'; export { default as UniformGroup } from './shader/UniformGroup'; -export { default as SpriteMaskFilter } from './renderers/filters/spriteMask/SpriteMaskFilter'; -export { default as Filter } from './renderers/filters/Filter'; +export { default as SpriteMaskFilter } from './filters/spriteMask/SpriteMaskFilter'; +export { default as Filter } from './filters/Filter'; export { default as Attribute } from './geometry/Attribute'; export { default as Buffer } from './geometry/Buffer'; export { default as Geometry } from './geometry/Geometry'; - diff --git a/packages/core/src/mask/MaskSystem.js b/packages/core/src/mask/MaskSystem.js new file mode 100644 index 0000000..99174c2 --- /dev/null +++ b/packages/core/src/mask/MaskSystem.js @@ -0,0 +1,199 @@ +import System from '../System'; +import AlphaMaskFilter from '../filters/spriteMask/SpriteMaskFilter'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class MaskSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + // TODO - we don't need both! + this.scissor = false; + this.scissorData = null; + this.scissorRenderTarget = null; + + this.enableScissor = false; + + this.alphaMaskPool = []; + this.alphaMaskIndex = 0; + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.DisplayObject} target - Display Object to push the mask to + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + push(target, maskData) + { + // TODO the root check means scissor rect will not + // be used on render textures more info here: + // https://github.com/pixijs/pixi.js/pull/3545 + + if (maskData.texture) + { + this.pushSpriteMask(target, maskData); + } + else if (this.enableScissor + && !this.scissor + && this.renderer._activeRenderTarget.root + && !this.renderer.stencil.stencilMaskStack.length + && maskData.isFastRect()) + { + const matrix = maskData.worldTransform; + + let rot = Math.atan2(matrix.b, matrix.a); + + // use the nearest degree! + rot = Math.round(rot * (180 / Math.PI)); + + if (rot % 90) + { + this.pushStencilMask(maskData); + } + else + { + this.pushScissorMask(target, maskData); + } + } + else + { + this.pushStencilMask(maskData); + } + } + + /** + * Removes the last mask from the mask stack and doesn't return it. + * + * @param {PIXI.DisplayObject} target - Display Object to pop the mask from + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + pop(target, maskData) + { + if (maskData.texture) + { + this.popSpriteMask(target, maskData); + } + else if (this.enableScissor && !this.renderer.stencil.stencilMaskStack.length) + { + this.popScissorMask(target, maskData); + } + else + { + this.popStencilMask(target, maskData); + } + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.RenderTarget} target - Display Object to push the sprite mask to + * @param {PIXI.Sprite} maskData - Sprite to be used as the mask + */ + pushSpriteMask(target, maskData) + { + let alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; + + if (!alphaMaskFilter) + { + alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; + } + + alphaMaskFilter[0].resolution = this.renderer.resolution; + alphaMaskFilter[0].maskSprite = maskData; + + // TODO - may cause issues! + target.filterArea = maskData.getBounds(true); + + this.renderer.filterSystem.pushFilter(target, alphaMaskFilter); + + this.alphaMaskIndex++; + } + + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popSpriteMask() + { + this.renderer.filterSystem.popFilter(); + this.alphaMaskIndex--; + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + pushStencilMask(maskData) + { + this.renderer.batch.flush(); + this.renderer.stencil.pushStencil(maskData); + } + + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popStencilMask() + { + // this.renderer.currentRenderer.stop(); + this.renderer.stencil.popStencil(); + } + + /** + * + * @param {PIXI.DisplayObject} target - Display Object to push the mask to + * @param {PIXI.Graphics} maskData - The masking data. + */ + pushScissorMask(target, maskData) + { + maskData.renderable = true; + + const renderTarget = this.renderer._activeRenderTarget; + + const bounds = maskData.getBounds(); + + bounds.fit(renderTarget.size); + maskData.renderable = false; + + this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); + + const resolution = this.renderer.resolution; + + this.renderer.gl.scissor( + bounds.x * resolution, + (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, + bounds.width * resolution, + bounds.height * resolution + ); + + this.scissorRenderTarget = renderTarget; + this.scissorData = maskData; + this.scissor = true; + } + + /** + * + * + */ + popScissorMask() + { + this.scissorRenderTarget = null; + this.scissorData = null; + this.scissor = false; + + // must be scissor! + const gl = this.renderer.gl; + + gl.disable(gl.SCISSOR_TEST); + } +} diff --git a/packages/core/src/mask/StencilSystem.js b/packages/core/src/mask/StencilSystem.js new file mode 100644 index 0000000..62796a5 --- /dev/null +++ b/packages/core/src/mask/StencilSystem.js @@ -0,0 +1,133 @@ +import System from '../System'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class StencilSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + this.stencilMaskStack = []; + } + + /** + * Changes the mask stack that is used by this System. + * + * @param {PIXI.Graphics[]} stencilMaskStack - The mask stack + */ + setMaskStack(stencilMaskStack) + { + const gl = this.renderer.gl; + + if (stencilMaskStack.length !== this.stencilMaskStack.length) + { + if (stencilMaskStack.length === 0) + { + gl.disable(gl.STENCIL_TEST); + } + else + { + gl.enable(gl.STENCIL_TEST); + } + } + + this.stencilMaskStack = stencilMaskStack; + } + + /** + * Applies the Mask and adds it to the current stencil stack. @alvin + * + * @param {PIXI.Graphics} graphics - The mask + */ + pushStencil(graphics) + { + this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); + + // this.renderer._activeRenderTarget.attachStencilBuffer(); + + const gl = this.renderer.gl; + const prevMaskCount = this.stencilMaskStack.length; + + if (prevMaskCount === 0) + { + gl.enable(gl.STENCIL_TEST); + } + + this.stencilMaskStack.push(graphics); + + // Increment the refference stencil value where the new mask overlaps with the old ones. + gl.colorMask(false, false, false, false); + gl.stencilFunc(gl.EQUAL, prevMaskCount, this._getBitwiseMask()); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.INCR); + this.renderer.plugins.graphics.render(graphics); + + this._useCurrent(); + } + + /** + * Removes the last mask from the stencil stack. @alvin + */ + popStencil() + { + this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); + + const gl = this.renderer.gl; + const graphics = this.stencilMaskStack.pop(); + + if (this.stencilMaskStack.length === 0) + { + // the stack is empty! + gl.disable(gl.STENCIL_TEST); + gl.clear(gl.STENCIL_BUFFER_BIT); + gl.clearStencil(0); + } + else + { + // Decrement the refference stencil value where the popped mask overlaps with the other ones + gl.colorMask(false, false, false, false); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.DECR); + this.renderer.plugins.graphics.render(graphics); + + this._useCurrent(); + } + } + + /** + * Setup renderer to use the current stencil data. + */ + _useCurrent() + { + const gl = this.renderer.gl; + + gl.colorMask(true, true, true, true); + gl.stencilFunc(gl.EQUAL, this.stencilMaskStack.length, this._getBitwiseMask()); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP); + } + + /** + * Fill 1s equal to the number of acitve stencil masks. + * + * @return {number} The bitwise mask. + */ + _getBitwiseMask() + { + return (1 << this.stencilMaskStack.length) - 1; + } + + /** + * Destroys the mask stack. + * + */ + destroy() + { + super.destroy(this); + + this.stencilMaskStack = null; + } +} diff --git a/packages/core/src/projection/ProjectionSystem.js b/packages/core/src/projection/ProjectionSystem.js new file mode 100644 index 0000000..6e63b73 --- /dev/null +++ b/packages/core/src/projection/ProjectionSystem.js @@ -0,0 +1,73 @@ +import System from '../System'; +import { Matrix } from '@pixi/math'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class ProjectionSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.projectionMatrix = new Matrix(); + } + + update(destinationFrame, sourceFrame, resolution, root) + { + this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; + this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; + + this.calculateProjection(this.destinationFrame, this.sourceFrame, resolution, root); + + this.renderer.globalUniforms.uniforms.projectionMatrix = this.projectionMatrix; + this.renderer.globalUniforms.update(); + } + + /** + * Updates the projection matrix based on a projection frame (which is a rectangle) + * + * @param {Rectangle} destinationFrame - The destination frame. + * @param {Rectangle} sourceFrame - The source frame. + */ + calculateProjection(destinationFrame, sourceFrame, resolution, root) + { + const pm = this.projectionMatrix; + + // I don't think we will need this line.. + // pm.identity(); + + if (!root) + { + pm.a = (1 / destinationFrame.width * 2) * resolution; + pm.d = (1 / destinationFrame.height * 2) * resolution; + + pm.tx = -1 - (sourceFrame.x * pm.a); + pm.ty = -1 - (sourceFrame.y * pm.d); + } + else + { + pm.a = (1 / destinationFrame.width * 2) * resolution; + pm.d = (-1 / destinationFrame.height * 2) * resolution; + + pm.tx = -1 - (sourceFrame.x * pm.a); + pm.ty = 1 - (sourceFrame.y * pm.d); + } + } + + /** + * Sets the transform of the active render target to the given matrix + * + * @param {PIXI.Matrix} matrix - The transformation matrix + */ + setTransform()// matrix) + { + // this._activeRenderTarget.transform = matrix; + } +} diff --git a/packages/core/src/renderTexture/BaseRenderTexture.js b/packages/core/src/renderTexture/BaseRenderTexture.js new file mode 100644 index 0000000..88eb83b --- /dev/null +++ b/packages/core/src/renderTexture/BaseRenderTexture.js @@ -0,0 +1,137 @@ +import BaseTexture from '../textures/BaseTexture'; +import FrameBuffer from '../framebuffer/FrameBuffer'; + +/** + * A BaseRenderTexture is a special texture that allows any PixiJS display object to be rendered to it. + * + * __Hint__: All DisplayObjects (i.e. Sprites) that render to a BaseRenderTexture should be preloaded + * otherwise black rectangles will be drawn instead. + * + * A BaseRenderTexture takes a snapshot of any Display Object given to its render method. The position + * and rotation of the given Display Objects is ignored. For example: + * + * ```js + * let renderer = PIXI.autoDetectRenderer(1024, 1024, { view: canvas, ratio: 1 }); + * let baseRenderTexture = new PIXI.BaseRenderTexture(renderer, 800, 600); + * let sprite = PIXI.Sprite.fromImage("spinObj_01.png"); + * + * sprite.position.x = 800/2; + * sprite.position.y = 600/2; + * sprite.anchor.x = 0.5; + * sprite.anchor.y = 0.5; + * + * baseRenderTexture.render(sprite); + * ``` + * + * The Sprite in this case will be rendered using its local transform. To render this sprite at 0,0 + * you can clear the transform + * + * ```js + * + * sprite.setTransform() + * + * let baseRenderTexture = new PIXI.BaseRenderTexture(100, 100); + * let renderTexture = new PIXI.RenderTexture(baseRenderTexture); + * + * renderer.render(sprite, renderTexture); // Renders to center of RenderTexture + * ``` + * + * @class + * @extends PIXI.BaseTexture + * @memberof PIXI + */ +export default class BaseRenderTexture extends BaseTexture +{ + /** + * @param {object} [options] + * @param {number} [options.width=100] - The width of the base render texture + * @param {number} [options.height=100] - The height of the base render texture + * @param {PIXI.SCALE_MODES} [options.scaleMode] - See {@link PIXI.SCALE_MODES} for possible values + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the texture being generated + */ + constructor(options) + { + if (typeof options === 'number') + { + /* eslint-disable prefer-rest-params */ + // Backward compatibility of signature + const width = arguments[0]; + const height = arguments[1]; + const scaleMode = arguments[2]; + const resolution = arguments[3]; + + options = { width, height, scaleMode, resolution }; + /* eslint-enable prefer-rest-params */ + } + + super(null, options); + + const { width, height } = options || {}; + + // Set defaults + this.mipmap = false; + this.width = Math.ceil(width) || 100; + this.height = Math.ceil(height) || 100; + this.valid = true; + + /** + * A map of renderer IDs to webgl renderTargets + * + * @private + * @member {object} + */ + // this._glRenderTargets = {}; + + /** + * A reference to the canvas render target (we only need one as this can be shared across renderers) + * + * @private + * @member {object} + */ + this._canvasRenderTarget = null; + + this.clearColor = [0, 0, 0, 0]; + + this.frameBuffer = new FrameBuffer(width * this.resolution, height * this.resolution) + .addColorTexture(0, this); + + // TODO - could this be added the systems? + + /** + * The data structure for the stencil masks + * + * @member {PIXI.Graphics[]} + */ + this.stencilMaskStack = []; + + /** + * The data structure for the filters + * + * @member {PIXI.Graphics[]} + */ + this.filterStack = [{}]; + } + + /** + * Resizes the BaseRenderTexture. + * + * @param {number} width - The width to resize to. + * @param {number} height - The height to resize to. + */ + resize(width, height) + { + width = Math.ceil(width); + height = Math.ceil(height); + this.frameBuffer.resize(width * this.resolution, height * this.resolution); + } + + /** + * Destroys this texture + * + */ + destroy() + { + super.destroy(true); + this.renderer = null; + } +} diff --git a/packages/core/src/renderTexture/RenderTexture.js b/packages/core/src/renderTexture/RenderTexture.js new file mode 100644 index 0000000..f1ecf1f --- /dev/null +++ b/packages/core/src/renderTexture/RenderTexture.js @@ -0,0 +1,153 @@ +import BaseRenderTexture from './BaseRenderTexture'; +import Texture from '../textures/Texture'; + +/** + * A RenderTexture is a special texture that allows any PixiJS display object to be rendered to it. + * + * __Hint__: All DisplayObjects (i.e. Sprites) that render to a RenderTexture should be preloaded + * otherwise black rectangles will be drawn instead. + * + * A RenderTexture takes a snapshot of any Display Object given to its render method. For example: + * + * ```js + * let renderer = PIXI.autoDetectRenderer(1024, 1024, { view: canvas, ratio: 1 }); + * let renderTexture = PIXI.RenderTexture.create(800, 600); + * let sprite = PIXI.Sprite.fromImage("spinObj_01.png"); + * + * sprite.position.x = 800/2; + * sprite.position.y = 600/2; + * sprite.anchor.x = 0.5; + * sprite.anchor.y = 0.5; + * + * renderer.render(sprite, renderTexture); + * ``` + * + * The Sprite in this case will be rendered using its local transform. To render this sprite at 0,0 + * you can clear the transform + * + * ```js + * + * sprite.setTransform() + * + * let renderTexture = new PIXI.RenderTexture.create(100, 100); + * + * renderer.render(sprite, renderTexture); // Renders to center of RenderTexture + * ``` + * + * @class + * @extends PIXI.Texture + * @memberof PIXI + */ +export default class RenderTexture extends Texture +{ + /** + * @param {PIXI.BaseRenderTexture} baseRenderTexture - The renderer used for this RenderTexture + * @param {PIXI.Rectangle} [frame] - The rectangle frame of the texture to show + */ + constructor(baseRenderTexture, frame) + { + // support for legacy.. + let _legacyRenderer = null; + + if (!(baseRenderTexture instanceof BaseRenderTexture)) + { + /* eslint-disable prefer-rest-params, no-console */ + const width = arguments[1]; + const height = arguments[2]; + const scaleMode = arguments[3]; + const resolution = arguments[4]; + + // we have an old render texture.. + console.warn(`Please use RenderTexture.create(${width}, ${height}) instead of the ctor directly.`); + _legacyRenderer = arguments[0]; + /* eslint-enable prefer-rest-params, no-console */ + + frame = null; + baseRenderTexture = new BaseRenderTexture({ + width, + height, + scaleMode, + resolution, + }); + } + + /** + * The base texture object that this texture uses + * + * @member {BaseTexture} + */ + super(baseRenderTexture, frame); + + this.legacyRenderer = _legacyRenderer; + + /** + * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. + * + * @member {boolean} + */ + this.valid = true; + + /** + * FilterSystem temporary storage + * @private + * @member {PIXI.Rectangle} + */ + this.filterFrame = null; + + this._updateUvs(); + } + + /** + * Resizes the RenderTexture. + * + * @param {number} width - The width to resize to. + * @param {number} height - The height to resize to. + * @param {boolean} [resizeBaseTexture=true] - Should the baseTexture.width and height values be resized as well? + */ + resize(width, height, resizeBaseTexture = true) + { + width = Math.ceil(width); + height = Math.ceil(height); + + // TODO - could be not required.. + this.valid = (width > 0 && height > 0); + + this._frame.width = this.orig.width = width; + this._frame.height = this.orig.height = height; + + if (resizeBaseTexture) + { + this.baseTexture.resize(width, height); + } + + this._updateUvs(); + } + + /** + * A short hand way of creating a render texture. + * + * @param {object} [options] - Options + * @param {number} [options.width=100] - The width of the render texture + * @param {number} [options.height=100] - The height of the render texture + * @param {number} [options.scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the texture being generated + * @return {PIXI.RenderTexture} The new render texture + */ + static create(options) + { + // fallback, old-style: create(width, height, scaleMode, resolution) + if (typeof options === 'number') + { + /* eslint-disable prefer-rest-params */ + options = { + width: options, + height: arguments[1], + scaleMode: arguments[2], + resolution: arguments[3], + }; + /* eslint-enable prefer-rest-params */ + } + + return new RenderTexture(new BaseRenderTexture(options)); + } +} diff --git a/packages/core/src/renderTexture/RenderTextureSystem.js b/packages/core/src/renderTexture/RenderTextureSystem.js new file mode 100644 index 0000000..8d0ca61 --- /dev/null +++ b/packages/core/src/renderTexture/RenderTextureSystem.js @@ -0,0 +1,115 @@ +import System from '../System'; +import { Rectangle } from '@pixi/math'; + +const tempRect = new Rectangle(); + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class RenderTextureSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.clearColor = renderer._backgroundColorRgba; + + // TODO moe this property somewhere else! + this.defaultMaskStack = []; + this.defaultFilterStack = [{}]; + + // empty render texture? + this.renderTexture = null; + + this.destinationFrame = new Rectangle(); + } + + bind(renderTexture, sourceFrame, destinationFrame) + { + // TODO - do we want this?? + if (this.renderTexture === renderTexture) return; + this.renderTexture = renderTexture; + + const renderer = this.renderer; + + if (renderTexture) + { + const baseTexture = renderTexture.baseTexture; + + if (!destinationFrame) + { + tempRect.width = baseTexture.realWidth; + tempRect.height = baseTexture.realHeight; + + destinationFrame = tempRect; + } + + if (!sourceFrame) + { + sourceFrame = destinationFrame; + } + + this.renderer.framebuffer.bind(baseTexture.frameBuffer, destinationFrame); + + this.renderer.projection.update(destinationFrame, sourceFrame, baseTexture.resolution, false); + this.renderer.stencil.setMaskStack(baseTexture.stencilMaskStack); + } + else + { + // TODO these validation checks happen deeper down.. + // thing they can be avoided.. + if (!destinationFrame) + { + tempRect.width = renderer.width; + tempRect.height = renderer.height; + + destinationFrame = tempRect; + } + + if (!sourceFrame) + { + sourceFrame = destinationFrame; + } + + renderer.framebuffer.bind(null, destinationFrame); + + // TODO store this.. + this.renderer.projection.update(destinationFrame, sourceFrame, this.renderer.resolution, true); + this.renderer.stencil.setMaskStack(this.defaultMaskStack); + } + + this.destinationFrame.copyFrom(destinationFrame); + } + + /** + * Erases the render texture and fills the drawing area with a colour + * + * @param {number} [clearColor] - The colour + * @return {PIXI.Renderer} Returns itself. + */ + clear(clearColor) + { + if (this.renderTexture) + { + clearColor = clearColor || this.renderTexture.baseTexture.clearColor; + } + else + { + clearColor = clearColor || this.clearColor; + } + + this.renderer.framebuffer.clear(clearColor[0], clearColor[1], clearColor[2], clearColor[3]); + } + + resize()// screenWidth, screenHeight) + { + // resize the root only! + this.bind(null); + } +} diff --git a/packages/core/src/renderers/AbstractRenderer.js b/packages/core/src/renderers/AbstractRenderer.js deleted file mode 100644 index 928670f..0000000 --- a/packages/core/src/renderers/AbstractRenderer.js +++ /dev/null @@ -1,353 +0,0 @@ -import { hex2string, hex2rgb } from '@pixi/utils'; -import { Matrix, Rectangle } from '@pixi/math'; -import { RENDERER_TYPE } from '@pixi/constants'; -import { settings } from '@pixi/settings'; -import { Container } from '@pixi/display'; -import RenderTexture from '../textures/RenderTexture'; -import EventEmitter from 'eventemitter3'; - -const tempMatrix = new Matrix(); - -/** - * 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 EventEmitter - * @memberof PIXI - */ -export default class AbstractRenderer extends EventEmitter -{ - // eslint-disable-next-line valid-jsdoc - /** - * @param {string} system - The name of the system this renderer is for. - * @param {object} [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.transparent=false] - If the render view is transparent, default false - * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false - * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) - * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. The - * resolution of the renderer retina would be 2. - * @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 {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when rendering, - * stopping pixel interpolation. - */ - constructor(system, options, arg2, arg3) - { - super(); - - // Support for constructor(system, screenWidth, screenHeight, options) - if (typeof options === 'number') - { - options = Object.assign({ - width: options, - height: arg2 || settings.RENDER_OPTIONS.height, - }, arg3); - } - - // 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 = RENDERER_TYPE.UNKNOWN; - - /** - * Measurements of the screen. (0, 0, screenWidth, screenHeight) - * - * Its safe to use as filterArea or hitArea for 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 1 - */ - this.resolution = options.resolution || settings.RESOLUTION; - - /** - * Whether the render view is transparent - * - * @member {boolean} - */ - this.transparent = options.transparent; - - /** - * Whether css dimensions of canvas view should be resized to screen dimensions automatically - * - * @member {boolean} - */ - this.autoResize = options.autoResize || false; - - /** - * Tracks the blend modes useful for this renderer. - * - * @member {object} - */ - this.blendModes = null; - - /** - * 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; - - /** - * If true PixiJS will Math.floor() x/y values when rendering, stopping pixel interpolation. - * Handy for crisp pixel art and speed on legacy devices. - * - * @member {boolean} - */ - this.roundPixels = options.roundPixels; - - /** - * The background color as a number. - * - * @member {number} - * @private - */ - this._backgroundColor = 0x000000; - - /** - * The background color as an [R, G, B] array. - * - * @member {number[]} - * @private - */ - this._backgroundColorRgba = [0, 0, 0, 0]; - - /** - * The background color as a string. - * - * @member {string} - * @private - */ - this._backgroundColorString = '#000000'; - - this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter - - /** - * This temporary display object used as the parent of the currently being rendered item - * - * @member {PIXI.DisplayObject} - * @private - */ - this._tempDisplayObjectParent = new Container(); - - /** - * The last root object that the renderer tried to render. - * - * @member {PIXI.DisplayObject} - * @private - */ - this._lastObjectRendered = this._tempDisplayObjectParent; - - /** - * Collection of plugins. - * @readonly - * @member {object} - */ - this.plugins = {}; - } - - /** - * Initialize the plugins. - * - * @protected - * @param {object} staticMap - The dictionary of staticly saved plugins. - */ - initPlugins(staticMap) - { - 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() - { - return this.view.width; - } - - /** - * Same as view.height, actual number of pixels in the canvas by vertical - * - * @member {number} - * @readonly - * @default 600 - */ - get height() - { - return this.view.height; - } - - /** - * Resizes the screen and canvas to the specified width and height - * Canvas dimensions are multiplied by resolution - * - * @param {number} screenWidth - the new width of the screen - * @param {number} screenHeight - the new height of the screen - */ - resize(screenWidth, screenHeight) - { - this.screen.width = screenWidth; - this.screen.height = screenHeight; - - this.view.width = screenWidth * this.resolution; - this.view.height = screenHeight * this.resolution; - - if (this.autoResize) - { - this.view.style.width = `${screenWidth}px`; - this.view.style.height = `${screenHeight}px`; - } - } - - /** - * 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 {PIXI.DisplayObject} displayObject - The displayObject the object will be generated from - * @param {number} scaleMode - Should be one of the scaleMode consts - * @param {number} resolution - The resolution / device pixel ratio of the texture being generated - * @param {PIXI.Rectangle} [region] - The region of the displayObject, that shall be rendered, - * if no region is specified, defaults to the local bounds of the displayObject. - * @return {PIXI.Texture} a texture of the graphics object - */ - generateTexture(displayObject, scaleMode, resolution, region) - { - region = region || displayObject.getLocalBounds(); - - // 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(region.width | 0, region.height | 0, scaleMode, resolution); - - tempMatrix.tx = -region.x; - tempMatrix.ty = -region.y; - - this.render(displayObject, renderTexture, false, tempMatrix, true); - - return renderTexture; - } - - /** - * Removes everything from the renderer and optionally removes the Canvas DOM element. - * - * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. - */ - destroy(removeView) - { - 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); - } - - this.plugins = null; - - this.type = RENDERER_TYPE.UNKNOWN; - - this.view = null; - - this.screen = null; - - this.resolution = 0; - - this.transparent = false; - - this.autoResize = false; - - this.blendModes = null; - - this.options = null; - - this.preserveDrawingBuffer = false; - this.clearBeforeRender = false; - - this.roundPixels = false; - - this._backgroundColor = 0; - this._backgroundColorRgba = null; - this._backgroundColorString = null; - - this._tempDisplayObjectParent = null; - this._lastObjectRendered = null; - } - - /** - * The background color to fill if not transparent - * - * @member {number} - */ - get backgroundColor() - { - return this._backgroundColor; - } - - set backgroundColor(value) // eslint-disable-line require-jsdoc - { - this._backgroundColor = value; - this._backgroundColorString = hex2string(value); - hex2rgb(value, this._backgroundColorRgba); - } -} diff --git a/packages/core/src/renderers/Renderer.js b/packages/core/src/renderers/Renderer.js deleted file mode 100644 index a2020b1..0000000 --- a/packages/core/src/renderers/Renderer.js +++ /dev/null @@ -1,329 +0,0 @@ -import AbstractRenderer from './AbstractRenderer'; -import { sayHello } from '@pixi/utils'; -import MaskSystem from './systems/MaskSystem'; -import StencilSystem from './systems/StencilSystem'; -import FilterSystem from './systems/filter/FilterSystem'; -import FramebufferSystem from './systems/FramebufferSystem'; -import RenderTextureSystem from './systems/RenderTextureSystem'; -import TextureSystem from './systems/textures/TextureSystem'; -import ProjectionSystem from './systems/ProjectionSystem'; -import StateSystem from './systems/StateSystem'; -import GeometrySystem from './systems/geometry/GeometrySystem'; -import ShaderSystem from './systems/shader/ShaderSystem'; -import ContextSystem from './systems/ContextSystem'; -import BatchSystem from './systems/BatchSystem'; -import TextureGCSystem from './systems/textures/TextureGCSystem'; -import { RENDERER_TYPE } from '@pixi/constants'; -import UniformGroup from '../shader/UniformGroup'; -import { Matrix } from '@pixi/math'; -import Runner from 'mini-runner'; - -/** - * 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 Render works by automatically managing webGLBatchs. - * 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 :) - * - * @class - * @memberof PIXI - * @extends PIXI.AbstractRenderer - */ -export default class Renderer extends AbstractRenderer -{ - // eslint-disable-next-line valid-jsdoc - /** - * - * @param {object} [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.transparent=false] - If the render view is transparent, default false - * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false - * @param {boolean} [options.antialias=false] - sets antialias. If not available natively then FXAA - * antialiasing is used - * @param {boolean} [options.forceFXAA=false] - forces FXAA antialiasing to be used over native. - * FXAA is faster, but may not always look as great - * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. - * The resolution of the renderer retina would be 2. - * @param {boolean} [options.clearBeforeRender=true] - This sets if the renderer will clear - * the canvas or not before the new render pass. If you wish to set this to false, you *must* set - * preserveDrawingBuffer to `true`. - * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, - * enable this if you need to call toDataUrl on the webgl context. - * @param {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when - * rendering, stopping pixel interpolation. - * @param {number} [options.backgroundColor=0x000000] - The background color of the rendered area - * (shown if not transparent). - * @param {boolean} [options.legacy=false] - If true PixiJS will aim to ensure compatibility - * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. - * @param {string} [options.powerPreference] - Parameter passed to webgl context, set to "high-performance" - * for devices with dual graphics card - */ - constructor(options, arg2, arg3) - { - super('WebGL', options, arg2, arg3); - - /** - * The type of this renderer as a standardised const - * - * @member {number} - * @see PIXI.RENDERER_TYPE - */ - this.type = RENDERER_TYPE.WEBGL; - - // this will be set by the contextSystem (this.context) - this.gl = null; - this.CONTEXT_UID = 0; - this.legacy = !!options.legacy; - - // TODO legacy! - - // runners! - this.runners = { - destroy: new Runner('destroy'), - contextChange: new Runner('contextChange', 1), - reset: new Runner('reset'), - update: new Runner('update'), - postrender: new Runner('postrender'), - prerender: new Runner('prerender'), - resize: new Runner('resize', 2), - }; - - 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(GeometrySystem, 'geometry') - .addSystem(FramebufferSystem, 'framebuffer') - .addSystem(StencilSystem, 'stencil') - .addSystem(ProjectionSystem, 'projection') - .addSystem(TextureGCSystem, 'textureGC') - .addSystem(FilterSystem, 'filter') - .addSystem(RenderTextureSystem, 'renderTexture') - .addSystem(BatchSystem, 'batch'); - - this.initPlugins(Renderer.__plugins); - - /** - * The options passed in to create a new webgl context. - * - * @member {object} - * @private - */ - if (options.context) - { - this.context.initFromContext(options.context); - } - else - { - this.context.initFromOptions({ - alpha: this.transparent, - antialias: options.antialias, - premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', - stencil: true, - 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); - } - - addSystem(_class, name) - { - if (!name) - { - name = _class.name; - } - - // TODO - read name from class.name.. - - /* - if(name.includes('System')) - { - name = name.replace('System', ''); - name = name.charAt(0).toLowerCase() + name.slice(1); - } - */ - - const system = new _class(this); - - if (this[name]) - { - throw new Error(`Whoops! ${name} is already a manger`); - } - - this[name] = system; - - for (const i in this.runners) - { - this.runners[i].add(system); - } - - return this; - - /** - * 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. - */ - } - - /** - * Renders the object to its webGL view - * - * @param {PIXI.DisplayObject} displayObject - the object to be rendered - * @param {PIXI.RenderTexture} renderTexture - The render texture to render to. - * @param {boolean} [clear] - Should the canvas be cleared before the new render - * @param {PIXI.Transform} [transform] - A transform to apply to the render texture before rendering. - * @param {boolean} [skipUpdateTransform] - Should we skip the update transform pass? - */ - render(displayObject, renderTexture, clear, transform, skipUpdateTransform) - { - // can be handy to know! - this.renderingToScreen = !renderTexture; - - this.runners.prerender.run(); - this.emit('prerender'); - - // 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.parent; - - displayObject.parent = this._tempDisplayObjectParent; - displayObject.updateTransform(); - displayObject.parent = 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.run(); - - this.emit('postrender'); - } - - /** - * Resizes the webGL view to the specified width and height. - * - * @param {number} screenWidth - the new width of the screen - * @param {number} screenHeight - the new height of the screen - */ - resize(screenWidth, screenHeight) - { - AbstractRenderer.prototype.resize.call(this, screenWidth, screenHeight); - - this.runners.resize.run(screenWidth, screenHeight); - } - - /** - * Resets the WebGL state so you can render things however you fancy! - * - * @return {PIXI.Renderer} Returns itself. - */ - reset() - { - this.runners.reset.run(); - - return this; - } - - clear() - { - this.framebuffer.bind(); - this.framebuffer.clear(); - } - - /** - * Removes everything from the renderer (event listeners, spritebatch, etc...) - * - * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. - * See: https://github.com/pixijs/pixi.js/issues/2233 - */ - destroy(removeView) - { - this.runners.destroy.run(); - - // call base destroy - super.destroy(removeView); - - // TODO nullify all the managers.. - this.gl = null; - } - - /** - * 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 - * builds and excluding plugins. - * @name PIXI.Renderer#plugins - * @type {object} - * @readonly - * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. - * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. - * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. - * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. - */ - - /** - * Adds a plugin to the renderer. - * - * @method - * @param {string} pluginName - The name of the plugin. - * @param {Function} ctor - The constructor function or class for the plugin. - */ - static registerPlugin(pluginName, ctor) - { - Renderer.__plugins = Renderer.__plugins || {}; - Renderer.__plugins[pluginName] = ctor; - } -} diff --git a/packages/core/src/renderers/State.js b/packages/core/src/renderers/State.js deleted file mode 100644 index fa3f221..0000000 --- a/packages/core/src/renderers/State.js +++ /dev/null @@ -1,163 +0,0 @@ -/* eslint-disable max-len */ - -const BLEND = 0; -const OFFSET = 1; -const CULLING = 2; -const DEPTH_TEST = 3; -const WINDING = 4; - -/** - * This is a webGL state. It is passed The WebGL StateManager. - * Each mesh renderered may require webGL to be in a different state. - * For example you may want different blend mode or to enable polygon offsets - * - * @class - * @memberof PIXI - */ -export default class State -{ - /** - * - */ - constructor() - { - this.data = 0; - - this.blendMode = 0; - this.polygonOffset = 0; - - this.blend = true; - // this.depthTest = true; - } - - /** - * Activates blending of the computed fragment color values - * - * @member {boolean} - */ - get blend() - { - return !!(this.data & (1 << BLEND)); - } - - set blend(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << BLEND)) !== value) - { - this.data ^= (1 << BLEND); - } - } - - /** - * Activates adding an offset to depth values of polygon's fragments - * - * @member {boolean} - * @default false - */ - get offsets() - { - return !!(this.data & (1 << OFFSET)); - } - - set offsets(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << OFFSET)) !== value) - { - this.data ^= (1 << OFFSET); - } - } - - /** - * Activates culling of polygons. - * - * @member {boolean} - * @default false - */ - get culling() - { - return !!(this.data & (1 << CULLING)); - } - - set culling(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << CULLING)) !== value) - { - this.data ^= (1 << CULLING); - } - } - - /** - * Activates depth comparisons and updates to the depth buffer. - * - * @member {boolean} - * @default false - */ - get depthTest() - { - return !!(this.data & (1 << DEPTH_TEST)); - } - - set depthTest(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << DEPTH_TEST)) !== value) - { - this.data ^= (1 << DEPTH_TEST); - } - } - - /** - * Specifies whether or not front or back-facing polygons can be culled. - * @member {boolean} - * @default false - */ - get clockwiseFrontFace() - { - return !!(this.data & (1 << WINDING)); - } - - set clockwiseFrontFace(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << WINDING)) !== value) - { - this.data ^= (1 << WINDING); - } - } - - /** - * The blend mode to be applied when this state is set. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. - * Setting this mode to anything other than NO_BLEND will automatically switch blending on. - * - * @member {boolean} - * @default PIXI.BLEND_MODES.NORMAL - * @see PIXI.BLEND_MODES - */ - get blendMode() - { - return this._blendMode; - } - - set blendMode(value) // eslint-disable-line require-jsdoc - { - // 17 is NO BLEND - this.blend = (value !== 17); - this._blendMode = value; - } - - /** - * The polygon offset. Setting this property to anything other than 0 will automatically enable poygon offset fill. - * - * @member {number} - * @default 0 - */ - get polygonOffset() - { - return this._polygonOffset; - } - - set polygonOffset(value) // eslint-disable-line require-jsdoc - { - this.offsets = !!value; - this._polygonOffset = value; - } -} - diff --git a/packages/core/src/renderers/filters/Filter.js b/packages/core/src/renderers/filters/Filter.js deleted file mode 100644 index 2073b8d..0000000 --- a/packages/core/src/renderers/filters/Filter.js +++ /dev/null @@ -1,140 +0,0 @@ -import Shader from '../../shader/Shader'; -import Program from '../../shader/Program'; -import State from '../State'; -import { settings } from '@pixi/settings'; -// import extractUniformsFromSrc from './extractUniformsFromSrc'; -import defaultVertex from './defaultFilter.vert'; -import defaultFragment from './defaultFilter.frag'; - -// let math = require('../../math'); -/** - * @class - * @memberof PIXI - * @extends PIXI.Shader - */ -export default class Filter extends Shader -{ - /** - * @param {string} [vertexSrc] - The source of the vertex shader. - * @param {string} [fragmentSrc] - The source of the fragment shader. - * @param {object} [uniforms] - Custom uniforms to use to augment the built-in ones. - */ - constructor(vertexSrc, fragmentSrc, uniforms) - { - const program = Program.from(vertexSrc, fragmentSrc); - - super(program, uniforms); - - /** - * The padding of the filter. Some filters require extra space to breath such as a blur. - * Increasing this will add extra width and height to the bounds of the object that the - * filter is applied to. - * - * @member {number} - */ - this.padding = 0; - - /** - * The resolution of the filter. Setting this to be lower will lower the quality but - * increase the performance of the filter. - * - * @member {number} - */ - this.resolution = settings.RESOLUTION; - - /** - * If enabled is true the filter is applied, if false it will not. - * - * @member {boolean} - */ - this.enabled = true; - - /** - * If enabled, PixiJS will fit the filter area into boundaries for better performance. - * Switch it off if it does not work for specific shader. - * - * @member {boolean} - */ - this.autoFit = true; - - /** - * Legacy filters use position and uvs from attributes - * @member {boolean} - * @readonly - */ - this.legacy = !!this.program.attributeData.aTextureCoord; - - /** - * the webGL state the filter requires to render - * @member {PIXI.State} - */ - this.state = new State(); - } - - /** - * Applies the filter - * - * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from - * @param {PIXI.RenderTarget} input - The input render target. - * @param {PIXI.RenderTarget} output - The target to output to. - * @param {boolean} clear - Should the output be cleared before rendering to it - * @param {object} [currentState] - It's current state of filter. - * There are some useful properties in the currentState : - * target, filters, sourceFrame, destinationFrame, renderTarget, resolution - */ - apply(filterManager, input, output, clear, currentState, derp) // eslint-disable-line no-unused-vars - { - // do as you please! - - filterManager.applyFilter(this, input, output, clear, currentState, derp); - - // or just do a regular render.. - } - - /** - * Sets the blendmode of the filter - * - * @member {number} - * @default PIXI.BLEND_MODES.NORMAL - */ - get blendMode() - { - return this.state.blendMode; - } - - set blendMode(value) // eslint-disable-line require-jsdoc - { - this.state.blendMode = value; - } - - /** - * The default vertex shader source - * - * @static - * @constant - */ - static get defaultVertexSrc() - { - return defaultVertex; - } - - /** - * The default fragment shader source - * - * @static - * @constant - */ - static get defaultFragmentSrc() - { - return defaultFragment; - } -} - -/** - * Used for caching shader IDs - * - * @static - * @private - */ -Filter.SOURCE_KEY_MAP = {}; - diff --git a/packages/core/src/renderers/filters/defaultFilter.frag b/packages/core/src/renderers/filters/defaultFilter.frag deleted file mode 100644 index a14c1be..0000000 --- a/packages/core/src/renderers/filters/defaultFilter.frag +++ /dev/null @@ -1,22 +0,0 @@ -varying vec2 vTextureCoord; -varying vec2 vFilterCoord; - -uniform sampler2D uSampler; -uniform sampler2D filterSampler; - -void main(void){ - vec4 masky = texture2D(filterSampler, vFilterCoord); - vec4 sample = texture2D(uSampler, vTextureCoord); - vec4 color; - if(mod(vFilterCoord.x, 1.0) > 0.5) - { - color = vec4(1.0, 0.0, 0.0, 1.0); - } - else - { - color = vec4(0.0, 1.0, 0.0, 1.0); - } - // gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0); - gl_FragColor = mix(sample, masky, 0.5); - gl_FragColor *= sample.a; -} diff --git a/packages/core/src/AbstractRenderer.js b/packages/core/src/AbstractRenderer.js new file mode 100644 index 0000000..64b2eb1 --- /dev/null +++ b/packages/core/src/AbstractRenderer.js @@ -0,0 +1,353 @@ +import { hex2string, hex2rgb } from '@pixi/utils'; +import { Matrix, Rectangle } from '@pixi/math'; +import { RENDERER_TYPE } from '@pixi/constants'; +import { settings } from '@pixi/settings'; +import { Container } from '@pixi/display'; +import RenderTexture from './renderTexture/RenderTexture'; +import EventEmitter from 'eventemitter3'; + +const tempMatrix = new Matrix(); + +/** + * 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 EventEmitter + * @memberof PIXI + */ +export default class AbstractRenderer extends EventEmitter +{ + // eslint-disable-next-line valid-jsdoc + /** + * @param {string} system - The name of the system this renderer is for. + * @param {object} [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.transparent=false] - If the render view is transparent, default false + * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false + * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. The + * resolution of the renderer retina would be 2. + * @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 {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when rendering, + * stopping pixel interpolation. + */ + constructor(system, options, arg2, arg3) + { + super(); + + // Support for constructor(system, screenWidth, screenHeight, options) + if (typeof options === 'number') + { + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + }, arg3); + } + + // 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 = RENDERER_TYPE.UNKNOWN; + + /** + * Measurements of the screen. (0, 0, screenWidth, screenHeight) + * + * Its safe to use as filterArea or hitArea for 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 1 + */ + this.resolution = options.resolution || settings.RESOLUTION; + + /** + * Whether the render view is transparent + * + * @member {boolean} + */ + this.transparent = options.transparent; + + /** + * Whether css dimensions of canvas view should be resized to screen dimensions automatically + * + * @member {boolean} + */ + this.autoResize = options.autoResize || false; + + /** + * Tracks the blend modes useful for this renderer. + * + * @member {object} + */ + this.blendModes = null; + + /** + * 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; + + /** + * If true PixiJS will Math.floor() x/y values when rendering, stopping pixel interpolation. + * Handy for crisp pixel art and speed on legacy devices. + * + * @member {boolean} + */ + this.roundPixels = options.roundPixels; + + /** + * The background color as a number. + * + * @member {number} + * @private + */ + this._backgroundColor = 0x000000; + + /** + * The background color as an [R, G, B] array. + * + * @member {number[]} + * @private + */ + this._backgroundColorRgba = [0, 0, 0, 0]; + + /** + * The background color as a string. + * + * @member {string} + * @private + */ + this._backgroundColorString = '#000000'; + + this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter + + /** + * This temporary display object used as the parent of the currently being rendered item + * + * @member {PIXI.DisplayObject} + * @private + */ + this._tempDisplayObjectParent = new Container(); + + /** + * The last root object that the renderer tried to render. + * + * @member {PIXI.DisplayObject} + * @private + */ + this._lastObjectRendered = this._tempDisplayObjectParent; + + /** + * Collection of plugins. + * @readonly + * @member {object} + */ + this.plugins = {}; + } + + /** + * Initialize the plugins. + * + * @protected + * @param {object} staticMap - The dictionary of staticly saved plugins. + */ + initPlugins(staticMap) + { + 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() + { + return this.view.width; + } + + /** + * Same as view.height, actual number of pixels in the canvas by vertical + * + * @member {number} + * @readonly + * @default 600 + */ + get height() + { + return this.view.height; + } + + /** + * Resizes the screen and canvas to the specified width and height + * Canvas dimensions are multiplied by resolution + * + * @param {number} screenWidth - the new width of the screen + * @param {number} screenHeight - the new height of the screen + */ + resize(screenWidth, screenHeight) + { + this.screen.width = screenWidth; + this.screen.height = screenHeight; + + this.view.width = screenWidth * this.resolution; + this.view.height = screenHeight * this.resolution; + + if (this.autoResize) + { + this.view.style.width = `${screenWidth}px`; + this.view.style.height = `${screenHeight}px`; + } + } + + /** + * 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 {PIXI.DisplayObject} displayObject - The displayObject the object will be generated from + * @param {number} scaleMode - Should be one of the scaleMode consts + * @param {number} resolution - The resolution / device pixel ratio of the texture being generated + * @param {PIXI.Rectangle} [region] - The region of the displayObject, that shall be rendered, + * if no region is specified, defaults to the local bounds of the displayObject. + * @return {PIXI.Texture} a texture of the graphics object + */ + generateTexture(displayObject, scaleMode, resolution, region) + { + region = region || displayObject.getLocalBounds(); + + // 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(region.width | 0, region.height | 0, scaleMode, resolution); + + tempMatrix.tx = -region.x; + tempMatrix.ty = -region.y; + + this.render(displayObject, renderTexture, false, tempMatrix, true); + + return renderTexture; + } + + /** + * Removes everything from the renderer and optionally removes the Canvas DOM element. + * + * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. + */ + destroy(removeView) + { + 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); + } + + this.plugins = null; + + this.type = RENDERER_TYPE.UNKNOWN; + + this.view = null; + + this.screen = null; + + this.resolution = 0; + + this.transparent = false; + + this.autoResize = false; + + this.blendModes = null; + + this.options = null; + + this.preserveDrawingBuffer = false; + this.clearBeforeRender = false; + + this.roundPixels = false; + + this._backgroundColor = 0; + this._backgroundColorRgba = null; + this._backgroundColorString = null; + + this._tempDisplayObjectParent = null; + this._lastObjectRendered = null; + } + + /** + * The background color to fill if not transparent + * + * @member {number} + */ + get backgroundColor() + { + return this._backgroundColor; + } + + set backgroundColor(value) // eslint-disable-line require-jsdoc + { + this._backgroundColor = value; + this._backgroundColorString = hex2string(value); + hex2rgb(value, this._backgroundColorRgba); + } +} diff --git a/packages/core/src/Renderer.js b/packages/core/src/Renderer.js new file mode 100644 index 0000000..2227e78 --- /dev/null +++ b/packages/core/src/Renderer.js @@ -0,0 +1,338 @@ +import AbstractRenderer from './AbstractRenderer'; +import { sayHello } from '@pixi/utils'; +import MaskSystem from './mask/MaskSystem'; +import StencilSystem from './mask/StencilSystem'; +import FilterSystem from './filters/FilterSystem'; +import FramebufferSystem from './framebuffer/FramebufferSystem'; +import RenderTextureSystem from './renderTexture/RenderTextureSystem'; +import TextureSystem from './textures/TextureSystem'; +import ProjectionSystem from './projection/ProjectionSystem'; +import StateSystem from './state/StateSystem'; +import GeometrySystem from './geometry/GeometrySystem'; +import ShaderSystem from './shader/ShaderSystem'; +import ContextSystem from './context/ContextSystem'; +import BatchSystem from './batch/BatchSystem'; +import TextureGCSystem from './textures/TextureGCSystem'; +import { RENDERER_TYPE } from '@pixi/constants'; +import UniformGroup from './shader/UniformGroup'; +import { Matrix } from '@pixi/math'; +import Runner from 'mini-runner'; + +/** + * 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 Render works by automatically managing webGLBatchs. + * 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 :) + * + * @class + * @memberof PIXI + * @extends PIXI.AbstractRenderer + */ +export default class Renderer extends AbstractRenderer +{ + // eslint-disable-next-line valid-jsdoc + /** + * + * @param {object} [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.transparent=false] - If the render view is transparent, default false + * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false + * @param {boolean} [options.antialias=false] - sets antialias. If not available natively then FXAA + * antialiasing is used + * @param {boolean} [options.forceFXAA=false] - forces FXAA antialiasing to be used over native. + * FXAA is faster, but may not always look as great + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. + * The resolution of the renderer retina would be 2. + * @param {boolean} [options.clearBeforeRender=true] - This sets if the renderer will clear + * the canvas or not before the new render pass. If you wish to set this to false, you *must* set + * preserveDrawingBuffer to `true`. + * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, + * enable this if you need to call toDataUrl on the webgl context. + * @param {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when + * rendering, stopping pixel interpolation. + * @param {number} [options.backgroundColor=0x000000] - The background color of the rendered area + * (shown if not transparent). + * @param {boolean} [options.legacy=false] - If true PixiJS will aim to ensure compatibility + * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. + * @param {string} [options.powerPreference] - Parameter passed to webgl context, set to "high-performance" + * for devices with dual graphics card + */ + constructor(options, arg2, arg3) + { + super('WebGL', options, arg2, arg3); + + /** + * The type of this renderer as a standardised const + * + * @member {number} + * @see PIXI.RENDERER_TYPE + */ + this.type = RENDERER_TYPE.WEBGL; + + // this will be set by the contextSystem (this.context) + this.gl = null; + this.CONTEXT_UID = 0; + this.legacy = !!options.legacy; + + // TODO legacy! + + // runners! + this.runners = { + destroy: new Runner('destroy'), + contextChange: new Runner('contextChange', 1), + reset: new Runner('reset'), + update: new Runner('update'), + postrender: new Runner('postrender'), + prerender: new Runner('prerender'), + resize: new Runner('resize', 2), + }; + + 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(GeometrySystem, 'geometry') + .addSystem(FramebufferSystem, 'framebuffer') + .addSystem(StencilSystem, 'stencil') + .addSystem(ProjectionSystem, 'projection') + .addSystem(TextureGCSystem, 'textureGC') + .addSystem(FilterSystem, 'filter') + .addSystem(RenderTextureSystem, 'renderTexture') + .addSystem(BatchSystem, 'batch'); + + this.initPlugins(Renderer.__plugins); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ + if (options.context) + { + this.context.initFromContext(options.context); + } + else + { + this.context.initFromOptions({ + alpha: this.transparent, + antialias: options.antialias, + premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', + stencil: true, + 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); + } + + /** + * Add a new system to the renderer. + * @param {class} ClassRef - Class reference + * @param {string} [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. + * @return {PIXI.Renderer} Return instance of renderer + */ + addSystem(ClassRef, name) + { + if (!name) + { + name = ClassRef.name; + } + + // TODO - read name from class.name.. + + /* + if(name.includes('System')) + { + name = name.replace('System', ''); + name = name.charAt(0).toLowerCase() + name.slice(1); + } + */ + + const system = new ClassRef(this); + + if (this[name]) + { + throw new Error(`Whoops! ${name} is already a manger`); + } + + this[name] = system; + + for (const i in this.runners) + { + this.runners[i].add(system); + } + + return this; + + /** + * 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. + */ + } + + /** + * Renders the object to its webGL view + * + * @param {PIXI.DisplayObject} displayObject - the object to be rendered + * @param {PIXI.RenderTexture} renderTexture - The render texture to render to. + * @param {boolean} [clear] - Should the canvas be cleared before the new render + * @param {PIXI.Transform} [transform] - A transform to apply to the render texture before rendering. + * @param {boolean} [skipUpdateTransform] - Should we skip the update transform pass? + */ + render(displayObject, renderTexture, clear, transform, skipUpdateTransform) + { + // can be handy to know! + this.renderingToScreen = !renderTexture; + + this.runners.prerender.run(); + this.emit('prerender'); + + // 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.parent; + + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = 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.run(); + + this.emit('postrender'); + } + + /** + * Resizes the webGL view to the specified width and height. + * + * @param {number} screenWidth - the new width of the screen + * @param {number} screenHeight - the new height of the screen + */ + resize(screenWidth, screenHeight) + { + AbstractRenderer.prototype.resize.call(this, screenWidth, screenHeight); + + this.runners.resize.run(screenWidth, screenHeight); + } + + /** + * Resets the WebGL state so you can render things however you fancy! + * + * @return {PIXI.Renderer} Returns itself. + */ + reset() + { + this.runners.reset.run(); + + return this; + } + + clear() + { + this.framebuffer.bind(); + this.framebuffer.clear(); + } + + /** + * Removes everything from the renderer (event listeners, spritebatch, etc...) + * + * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. + * See: https://github.com/pixijs/pixi.js/issues/2233 + */ + destroy(removeView) + { + this.runners.destroy.run(); + + // call base destroy + super.destroy(removeView); + + // TODO nullify all the managers.. + this.gl = null; + } + + /** + * 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 + * builds and excluding plugins. + * @name PIXI.Renderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. + */ + + /** + * Adds a plugin to the renderer. + * + * @method + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + static registerPlugin(pluginName, ctor) + { + Renderer.__plugins = Renderer.__plugins || {}; + Renderer.__plugins[pluginName] = ctor; + } +} diff --git a/packages/core/src/System.js b/packages/core/src/System.js new file mode 100644 index 0000000..05a10c8 --- /dev/null +++ b/packages/core/src/System.js @@ -0,0 +1,42 @@ +/** + * System is a base class used for extending systems used by the {@link PIXI.Renderer} + * @see PIXI.Renderer#addSystem + * @class + * @memberof PIXI + */ +export default class System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + /** + * The renderer this manager works for. + * + * @member {PIXI.Renderer} + */ + this.renderer = renderer; + + this.renderer.runners.contextChange.add(this); + } + + /** + * Generic method called when there is a WebGL context change. + * + */ + contextChange() + { + // do some codes init! + } + + /** + * Generic destroy methods to be overridden by the subclass + * + */ + destroy() + { + this.renderer.runners.contextChange.remove(this); + this.renderer = null; + } +} diff --git a/packages/core/src/batch/BatchSystem.js b/packages/core/src/batch/BatchSystem.js new file mode 100644 index 0000000..b04c249 --- /dev/null +++ b/packages/core/src/batch/BatchSystem.js @@ -0,0 +1,68 @@ +import System from '../System'; +import ObjectRenderer from './ObjectRenderer'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class BatchSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + super(renderer); + + /** + * An empty renderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.emptyRenderer = new ObjectRenderer(renderer); + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.currentRenderer = this.emptyRenderer; + } + + /** + * Changes the current renderer to the one given in parameter + * + * @param {PIXI.ObjectRenderer} objectRenderer - The object renderer to use. + */ + setObjectRenderer(objectRenderer) + { + if (this.currentRenderer === objectRenderer) + { + return; + } + + this.currentRenderer.stop(); + this.currentRenderer = objectRenderer; + + this.renderer.state.setState(objectRenderer.state); + + this.currentRenderer.start(); + } + + /** + * This should be called if you wish to do some custom rendering + * It will basically render anything that may be batched up such as sprites + * + */ + flush() + { + this.setObjectRenderer(this.emptyRenderer); + } + + reset() + { + this.setObjectRenderer(this.emptyRenderer); + } +} diff --git a/packages/core/src/batch/ObjectRenderer.js b/packages/core/src/batch/ObjectRenderer.js new file mode 100644 index 0000000..0c851a9 --- /dev/null +++ b/packages/core/src/batch/ObjectRenderer.js @@ -0,0 +1,48 @@ +import System from '../System'; + +/** + * Base for a common object renderer that can be used as a system renderer plugin. + * + * @class + * @extends PIXI.System + * @memberof PIXI + */ +export default class ObjectRenderer extends System +{ + /** + * Starts the renderer and sets the shader + * + */ + start() + { + // set the shader.. + } + + /** + * Stops the renderer + * + */ + stop() + { + this.flush(); + } + + /** + * Stub method for rendering content and emptying the current batch. + * + */ + flush() + { + // flush! + } + + /** + * Renders an object + * + * @param {PIXI.DisplayObject} object - The object to render. + */ + render(object) // eslint-disable-line no-unused-vars + { + // render the object + } +} diff --git a/packages/core/src/context/ContextSystem.js b/packages/core/src/context/ContextSystem.js new file mode 100644 index 0000000..301aab4 --- /dev/null +++ b/packages/core/src/context/ContextSystem.js @@ -0,0 +1,186 @@ +import System from '../System'; +import { settings } from '@pixi/settings'; + +let CONTEXT_UID = 0; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class ContextSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.webGLVersion = 1; + + this.handleContextLost = this.handleContextLost.bind(this); + this.handleContextRestored = this.handleContextRestored.bind(this); + + this.extensions = {}; + + renderer.view.addEventListener('webglcontextlost', this.handleContextLost, false); + renderer.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + } + + get isLost() + { + return (!this.gl || this.gl.isContextLost()); + } + + contextChange(gl) + { + this.gl = gl; + + // restore a context if it was previously lost + if (gl.isContextLost() && gl.getExtension('WEBGL_lose_context')) + { + gl.getExtension('WEBGL_lose_context').restoreContext(); + } + } + + initFromContext(gl) + { + this.gl = gl; + this.validateContext(gl); + this.renderer.gl = gl; + this.renderer.CONTEXT_UID = CONTEXT_UID++; + this.renderer.runners.contextChange.run(gl); + } + + initFromOptions(options) + { + const gl = this.createContext(this.renderer.view, options); + + this.initFromContext(gl); + } + + /** + * Helper class to create a webGL Context + * + * @class + * @memberof PIXI.glCore + * @param canvas {HTMLCanvasElement} the canvas element that we will get the context from + * @param options {Object} An options object that gets passed in to the canvas element containing the context attributes, + * see https://developer.mozilla.org/en/docs/Web/API/HTMLCanvasElement/getContext for the options available + * @return {WebGLRenderingContext} the WebGL context + */ + createContext(canvas, options) + { + let gl; + + if (settings.PREFER_WEBGL_2) + { + gl = canvas.getContext('webgl2', options); + } + + if (gl) + { + this.webGLVersion = 2; + } + else + { + this.webGLVersion = 1; + + gl = canvas.getContext('webgl', options) + || canvas.getContext('experimental-webgl', options); + + if (!gl) + { + // fail, not able to get a context + throw new Error('This browser does not support webGL. Try using the canvas renderer'); + } + } + + this.gl = gl; + + this.getExtensions(); + + return gl; + } + + getExtensions() + { + // time to set up default etensions that pixi uses.. + const gl = this.gl; + const extensions = this.extensions; + + if (this.webGLVersion === 1) + { + extensions.drawBuffers = gl.getExtension('WEBGL_draw_buffers'); + extensions.depthTexture = gl.getExtension('WEBKIT_WEBGL_depth_texture'); + extensions.floatTexture = gl.getExtension('OES_texture_float'); + extensions.loseContext = gl.getExtension('WEBGL_lose_context'); + + extensions.vertexArrayObject = gl.getExtension('OES_vertex_array_object') + || gl.getExtension('MOZ_OES_vertex_array_object') + || gl.getExtension('WEBKIT_OES_vertex_array_object'); + } + + // we don't use any specific WebGL 2 ones yet! + } + + /** + * Handles a lost webgl context + * + * @private + * @param {WebGLContextEvent} event - The context lost event. + */ + handleContextLost(event) + { + event.preventDefault(); + } + + /** + * Handles a restored webgl context + * + * @private + */ + handleContextRestored() + { + this.renderer.runners.contextChange.run(this.gl); + } + + destroy() + { + const view = this.renderer.view; + + // remove listeners + view.removeEventListener('webglcontextlost', this.handleContextLost); + view.removeEventListener('webglcontextrestored', this.handleContextRestored); + + this.gl.useProgram(null); + + if (this.extensions.loseContext) + { + this.extensions.loseContext.loseContext(); + } + } + + postrender() + { + this.gl.flush(); + } + + validateContext(gl) + { + const attributes = gl.getContextAttributes(); + + // this is going to be fairly simple for now.. but at least we have room to grow! + if (!attributes.stencil) + { + /* eslint-disable max-len */ + + /* eslint-disable no-console */ + console.warn('Provided WebGL context does not have a stencil buffer, masks may not render correctly'); + /* eslint-enable no-console */ + + /* eslint-enable max-len */ + } + } +} diff --git a/packages/core/src/filters/Filter.js b/packages/core/src/filters/Filter.js new file mode 100644 index 0000000..9d8cad8 --- /dev/null +++ b/packages/core/src/filters/Filter.js @@ -0,0 +1,140 @@ +import Shader from '../shader/Shader'; +import Program from '../shader/Program'; +import State from '../state/State'; +import { settings } from '@pixi/settings'; +// import extractUniformsFromSrc from './extractUniformsFromSrc'; +import defaultVertex from './defaultFilter.vert'; +import defaultFragment from './defaultFilter.frag'; + +// let math = require('../../math'); +/** + * @class + * @memberof PIXI + * @extends PIXI.Shader + */ +export default class Filter extends Shader +{ + /** + * @param {string} [vertexSrc] - The source of the vertex shader. + * @param {string} [fragmentSrc] - The source of the fragment shader. + * @param {object} [uniforms] - Custom uniforms to use to augment the built-in ones. + */ + constructor(vertexSrc, fragmentSrc, uniforms) + { + const program = Program.from(vertexSrc, fragmentSrc); + + super(program, uniforms); + + /** + * The padding of the filter. Some filters require extra space to breath such as a blur. + * Increasing this will add extra width and height to the bounds of the object that the + * filter is applied to. + * + * @member {number} + */ + this.padding = 0; + + /** + * The resolution of the filter. Setting this to be lower will lower the quality but + * increase the performance of the filter. + * + * @member {number} + */ + this.resolution = settings.RESOLUTION; + + /** + * If enabled is true the filter is applied, if false it will not. + * + * @member {boolean} + */ + this.enabled = true; + + /** + * If enabled, PixiJS will fit the filter area into boundaries for better performance. + * Switch it off if it does not work for specific shader. + * + * @member {boolean} + */ + this.autoFit = true; + + /** + * Legacy filters use position and uvs from attributes + * @member {boolean} + * @readonly + */ + this.legacy = !!this.program.attributeData.aTextureCoord; + + /** + * the webGL state the filter requires to render + * @member {PIXI.State} + */ + this.state = new State(); + } + + /** + * Applies the filter + * + * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + * @param {boolean} clear - Should the output be cleared before rendering to it + * @param {object} [currentState] - It's current state of filter. + * There are some useful properties in the currentState : + * target, filters, sourceFrame, destinationFrame, renderTarget, resolution + */ + apply(filterManager, input, output, clear, currentState, derp) // eslint-disable-line no-unused-vars + { + // do as you please! + + filterManager.applyFilter(this, input, output, clear, currentState, derp); + + // or just do a regular render.. + } + + /** + * Sets the blendmode of the filter + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL + */ + get blendMode() + { + return this.state.blendMode; + } + + set blendMode(value) // eslint-disable-line require-jsdoc + { + this.state.blendMode = value; + } + + /** + * The default vertex shader source + * + * @static + * @constant + */ + static get defaultVertexSrc() + { + return defaultVertex; + } + + /** + * The default fragment shader source + * + * @static + * @constant + */ + static get defaultFragmentSrc() + { + return defaultFragment; + } +} + +/** + * Used for caching shader IDs + * + * @static + * @private + */ +Filter.SOURCE_KEY_MAP = {}; + diff --git a/packages/core/src/filters/FilterSystem.js b/packages/core/src/filters/FilterSystem.js new file mode 100644 index 0000000..3174318 --- /dev/null +++ b/packages/core/src/filters/FilterSystem.js @@ -0,0 +1,431 @@ +import System from '../System'; + +import RenderTexture from '../renderTexture/RenderTexture'; +import Quad from '../utils/Quad'; +import QuadUv from '../utils/QuadUv'; +import { Rectangle } from '@pixi/math'; +import * as filterTransforms from './filterTransforms'; +import bitTwiddle from 'bit-twiddle'; +import UniformGroup from '../shader/UniformGroup'; +import { DRAW_MODES } from '@pixi/constants'; + +// +/** + * @ignore + * @class + */ +class FilterState +{ + /** + * + */ + constructor() + { + this.renderTexture = null; + this.sourceFrame = new Rectangle(); + this.destinationFrame = new Rectangle(); + this.filters = []; + this.target = null; + this.legacy = false; + this.resolution = 1; + } +} + +/** + * @class + * @memberof PIXI.systems + * @extends PIXI.System + */ +export default class FilterSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + /** + * stores a bunch of PO2 textures used for filtering + * @type {Object} + */ + this.texturePool = {}; + + /** + * a pool for storing filter states, save us creating new ones each tick + * @type {Array} + */ + this.statePool = []; + + /** + * A very simple geometry used when drawing a filter effect to the screen + * @type {Quad} + */ + this.quad = new Quad(); + + this.quadUv = new QuadUv(); + + /** + * Temporary rect for maths + * @type {PIXI.Rectangle} + */ + this.tempRect = new Rectangle(); + + this.activeState = {}; + + /** + * this uniform group is attached to filter uniforms when used + * @type {UniformGroup} + */ + this.globalUniforms = new UniformGroup({ + sourceFrame: this.tempRect, + destinationFrame: this.tempRect, + + // legacy variables + filterArea: new Float32Array(4), + filterClamp: new Float32Array(4), + }, true); + } + + /** + * Adds a new filter to the System. + * + * @param {PIXI.DisplayObject} target - The target of the filter to render. + * @param {PIXI.Filter[]} filters - The filters to apply. + */ + push(target, filters) + { + const renderer = this.renderer; + const filterStack = this.renderer.renderTexture.defaultFilterStack; + const state = this.statePool.pop() || new FilterState(); + + let resolution = filters[0].resolution; + let padding = filters[0].padding; + let autoFit = filters[0].autoFit; + let legacy = filters[0].legacy; + + for (let i = 1; i < filters.length; i++) + { + const filter = filters[i]; + + // lets use the lowest resolution.. + resolution = Math.min(resolution, filter.resolution); + // and the largest amount of padding! + padding = Math.max(padding, filter.padding); + // only auto fit if all filters are autofit + autoFit = autoFit || filter.autoFit; + + legacy = legacy || filter.legacy; + } + + filterStack.push(state); + + state.resolution = resolution; + + state.legacy = legacy; + + // round to whole number based on resolution + // TODO move that to the shader too? + state.sourceFrame = target.filterArea || target.getBounds(true); + + state.sourceFrame.pad(padding); + + if (autoFit) + { + state.sourceFrame.fit(this.renderer.renderTexture.destinationFrame); + } + + state.sourceFrame.round(resolution); + + state.renderTexture = this.getPotFilterTexture(state.sourceFrame.width, state.sourceFrame.height, resolution); + state.filters = filters; + + state.destinationFrame.width = state.renderTexture.width; + state.destinationFrame.height = state.renderTexture.height; + + state.renderTexture.filterFrame = state.sourceFrame; + renderer.renderTexture.bind(state.renderTexture, state.sourceFrame);// /, state.destinationFrame); + renderer.renderTexture.clear(); + } + + /** + * Pops off the filter and applies it. + * + */ + pop() + { + const renderer = this.renderer; + const filterStack = renderer.renderTexture.defaultFilterStack; + const state = filterStack.pop(); + const filters = state.filters; + + this.activeState = state; + + const globalUniforms = this.globalUniforms.uniforms; + + globalUniforms.sourceFrame = state.sourceFrame; + globalUniforms.destinationFrame = state.destinationFrame; + globalUniforms.resolution = state.resolution; + + // only update the rect if its legacy.. + if (state.legacy) + { + const filterArea = globalUniforms.filterArea; + const filterClamp = globalUniforms.filterClamp; + + filterArea[0] = state.destinationFrame.width; + filterArea[1] = state.destinationFrame.height; + filterArea[2] = state.sourceFrame.x; + filterArea[3] = state.sourceFrame.y; + + filterClamp[0] = 0.5 / state.resolution / state.destinationFrame.width; + filterClamp[1] = 0.5 / state.resolution / state.destinationFrame.height; + filterClamp[2] = (state.sourceFrame.width - 0.5) / state.resolution / state.destinationFrame.width; + filterClamp[3] = (state.sourceFrame.height - 0.5) / state.resolution / state.destinationFrame.height; + } + + this.globalUniforms.update(); + + const lastState = filterStack[filterStack.length - 1]; + + if (filters.length === 1) + { + filters[0].apply(this, state.renderTexture, lastState.renderTexture, false, state); + + this.returnFilterTexture(state.renderTexture); + } + else + { + let flip = state.renderTexture; + let flop = this.getPotFilterTexture( + flip.width, + flip.height, + state.resolution + ); + + flop.filterFrame = flip.filterFrame; + + let i = 0; + + for (i = 0; i < filters.length - 1; ++i) + { + filters[i].apply(this, flip, flop, true, state); + + const t = flip; + + flip = flop; + flop = t; + } + + filters[i].apply(this, flip, lastState.renderTexture, false, state); + + this.returnFilterTexture(flip); + this.returnFilterTexture(flop); + } + + this.statePool.push(state); + } + + /** + * Draws a filter. + * + * @param {PIXI.Filter} filter - The filter to draw. + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + * @param {boolean} clear - Should the output be cleared before rendering to it + */ + applyFilter(filter, input, output, clear) + { + const renderer = this.renderer; + + renderer.renderTexture.bind(output, output ? output.filterFrame : null); + + if (clear) + { + // gl.disable(gl.SCISSOR_TEST); + renderer.renderTexture.clear(); + // gl.enable(gl.SCISSOR_TEST); + } + + // set the uniforms.. + filter.uniforms.uSampler = input; + filter.uniforms.filterGlobals = this.globalUniforms; + + // TODO make it so that the order of this does not matter.. + // because it does at the moment cos of global uniforms. + // they need to get resynced + + renderer.state.setState(filter.state); + renderer.shader.bind(filter); + + if (filter.legacy) + { + this.quadUv.map(input._frame, input.filterFrame); + + renderer.geometry.bind(this.quadUv); + renderer.geometry.draw(DRAW_MODES.TRIANGLES); + } + else + { + renderer.geometry.bind(this.quad); + renderer.geometry.draw(DRAW_MODES.TRIANGLE_STRIP); + } + } + + /** + * Calculates the mapped matrix. + * + * TODO playing around here.. this is temporary - (will end up in the shader) + * this returns a matrix that will normalise map filter cords in the filter to screen space + * + * @param {PIXI.Matrix} outputMatrix - the matrix to output to. + * @return {PIXI.Matrix} The mapped matrix. + */ + calculateScreenSpaceMatrix(outputMatrix) + { + const currentState = this.activeState; + + return filterTransforms.calculateScreenSpaceMatrix( + outputMatrix, + currentState.sourceFrame, + currentState.destinationFrame + ); + } + + /** + * This will map the filter coord so that a texture can be used based on the transform of a sprite + * + * @param {PIXI.Matrix} outputMatrix - The matrix to output to. + * @param {PIXI.Sprite} sprite - The sprite to map to. + * @return {PIXI.Matrix} The mapped matrix. + */ + calculateSpriteMatrix(outputMatrix, sprite) + { + const currentState = this.activeState; + + return filterTransforms.calculateSpriteMatrix( + outputMatrix, + currentState.sourceFrame, + currentState.destinationFrame, + sprite + ); + } + + /** + * Destroys this Filter System. + * + * @param {boolean} [contextLost=false] context was lost, do not free shaders + * + */ + destroy(contextLost = false) + { + if (!contextLost) + { + this.emptyPool(); + } + else + { + this.texturePool = {}; + } + } + + /** + * Gets a Power-of-Two render texture. + * + * TODO move to a seperate class could be on renderer? + * also - could cause issue with multiple contexts? + * + * @private + * @param {WebGLRenderingContext} gl - The webgl rendering context + * @param {number} minWidth - The minimum width of the render target. + * @param {number} minHeight - The minimum height of the render target. + * @param {number} resolution - The resolution of the render target. + * @return {PIXI.RenderTarget} The new render target. + */ + getPotFilterTexture(minWidth, minHeight, resolution) + { + minWidth = bitTwiddle.nextPow2(minWidth); + minHeight = bitTwiddle.nextPow2(minHeight); + resolution = resolution || 1; + + const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + + if (!this.texturePool[key]) + { + this.texturePool[key] = []; + } + + let renderTexture = this.texturePool[key].pop(); + + if (!renderTexture) + { + // temporary bypass cache.. + // internally - this will cause a texture to be bound.. + renderTexture = RenderTexture.create({ + width: minWidth, + height: minHeight, + resolution, + }); + } + + return renderTexture; + } + + /** + * Gets extra render texture to use inside current filter + * + * @param {number} resolution resolution of the renderTexture + * @returns {PIXI.RenderTexture} + */ + getFilterTexture(resolution) + { + const rt = this.activeState.renderTexture; + + const filterTexture = this.getPotFilterTexture(rt.width, rt.height, resolution || rt.baseTexture.resolution); + + filterTexture.filterFrame = rt.filterFrame; + + return filterTexture; + } + + /** + * Frees a render texture back into the pool. + * + * @param {PIXI.RenderTarget} renderTexture - The renderTarget to free + */ + returnFilterTexture(renderTexture) + { + renderTexture.filterFrame = null; + + const base = renderTexture.baseTexture; + + const minWidth = base.width; + const minHeight = base.height; + + const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + + this.texturePool[key].push(renderTexture); + } + + /** + * Empties the texture pool. + * + */ + emptyPool() + { + for (const i in this.texturePool) + { + const textures = this.texturePool[i]; + + if (textures) + { + for (let j = 0; j < textures.length; j++) + { + textures[j].destroy(true); + } + } + } + + this.texturePool = {}; + } +} diff --git a/packages/core/src/filters/defaultFilter.frag b/packages/core/src/filters/defaultFilter.frag new file mode 100644 index 0000000..a14c1be --- /dev/null +++ b/packages/core/src/filters/defaultFilter.frag @@ -0,0 +1,22 @@ +varying vec2 vTextureCoord; +varying vec2 vFilterCoord; + +uniform sampler2D uSampler; +uniform sampler2D filterSampler; + +void main(void){ + vec4 masky = texture2D(filterSampler, vFilterCoord); + vec4 sample = texture2D(uSampler, vTextureCoord); + vec4 color; + if(mod(vFilterCoord.x, 1.0) > 0.5) + { + color = vec4(1.0, 0.0, 0.0, 1.0); + } + else + { + color = vec4(0.0, 1.0, 0.0, 1.0); + } + // gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0); + gl_FragColor = mix(sample, masky, 0.5); + gl_FragColor *= sample.a; +} diff --git a/packages/core/src/filters/defaultFilter.vert b/packages/core/src/filters/defaultFilter.vert new file mode 100644 index 0000000..e1febca --- /dev/null +++ b/packages/core/src/filters/defaultFilter.vert @@ -0,0 +1,14 @@ +attribute vec2 aVertexPosition; +attribute vec2 aTextureCoord; + +uniform mat3 projectionMatrix; +uniform mat3 filterMatrix; + +varying vec2 vTextureCoord; +varying vec2 vFilterCoord; + +void main(void){ + gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); + vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy; + vTextureCoord = aTextureCoord ; +} diff --git a/packages/core/src/filters/filterTransforms.js b/packages/core/src/filters/filterTransforms.js new file mode 100644 index 0000000..87a3cd6 --- /dev/null +++ b/packages/core/src/filters/filterTransforms.js @@ -0,0 +1,50 @@ +import { Matrix } from '@pixi/math'; + +/** + * Calculates the mapped matrix + * @param filterArea {Rectangle} The filter area + * @param sprite {Sprite} the target sprite + * @param outputMatrix {Matrix} @alvin + * @private + */ +// this returns a matrix that will normalise map filter cords in the filter to screen space +export function calculateScreenSpaceMatrix(outputMatrix, filterArea, textureSize) +{ + // TODO unwrap? + const mappedMatrix = outputMatrix.identity(); + + mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); + + mappedMatrix.scale(textureSize.width, textureSize.height); + + return mappedMatrix; +} + +export function calculateNormalizedScreenSpaceMatrix(outputMatrix, filterArea, textureSize) +{ + const mappedMatrix = outputMatrix.identity(); + + mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); + + const translateScaleX = (textureSize.width / filterArea.width); + const translateScaleY = (textureSize.height / filterArea.height); + + mappedMatrix.scale(translateScaleX, translateScaleY); + + return mappedMatrix; +} + +// this will map the filter coord so that a texture can be used based on the transform of a sprite +export function calculateSpriteMatrix(outputMatrix, filterArea, textureSize, sprite) +{ + const orig = sprite._texture.orig; + const mappedMatrix = outputMatrix.set(textureSize.width, 0, 0, textureSize.height, filterArea.x, filterArea.y); + const worldTransform = sprite.worldTransform.copyTo(Matrix.TEMP_MATRIX); + + worldTransform.invert(); + mappedMatrix.prepend(worldTransform); + mappedMatrix.scale(1.0 / orig.width, 1.0 / orig.height); + mappedMatrix.translate(sprite.anchor.x, sprite.anchor.y); + + return mappedMatrix; +} diff --git a/packages/core/src/filters/spriteMask/SpriteMaskFilter.js b/packages/core/src/filters/spriteMask/SpriteMaskFilter.js new file mode 100644 index 0000000..91697b7 --- /dev/null +++ b/packages/core/src/filters/spriteMask/SpriteMaskFilter.js @@ -0,0 +1,63 @@ +import Filter from '../Filter'; +import { Matrix } from '@pixi/math'; +import vertex from './spriteMaskFilter.vert'; +import fragment from './spriteMaskFilter.frag'; +import { default as TextureMatrix } from '../../textures/TextureMatrix'; + +/** + * The SpriteMaskFilter class + * + * @class + * @extends PIXI.Filter + * @memberof PIXI + */ +export default class SpriteMaskFilter extends Filter +{ + /** + * @param {PIXI.Sprite} sprite - the target sprite + */ + constructor(sprite) + { + const maskMatrix = new Matrix(); + + super(vertex, fragment); + + sprite.renderable = false; + + this.maskSprite = sprite; + this.maskMatrix = maskMatrix; + } + + /** + * Applies the filter + * + * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + */ + apply(filterManager, input, output) + { + const maskSprite = this.maskSprite; + const tex = this.maskSprite.texture; + + if (!tex.valid) + { + return; + } + if (!tex.transform) + { + // margin = 0.0, let it bleed a bit, shader code becomes easier + // assuming that atlas textures were made with 1-pixel padding + tex.transform = new TextureMatrix(tex, 0.0); + } + tex.transform.update(); + + this.uniforms.mask = tex; + this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite) + .prepend(tex.transform.mapCoord); + this.uniforms.alpha = maskSprite.worldAlpha; + this.uniforms.maskClamp = tex.transform.uClampFrame; + + filterManager.applyFilter(this, input, output); + } +} diff --git a/packages/core/src/filters/spriteMask/spriteMaskFilter.frag b/packages/core/src/filters/spriteMask/spriteMaskFilter.frag new file mode 100644 index 0000000..0e4aef8 --- /dev/null +++ b/packages/core/src/filters/spriteMask/spriteMaskFilter.frag @@ -0,0 +1,23 @@ +varying vec2 vMaskCoord; +varying vec2 vTextureCoord; + +uniform sampler2D uSampler; +uniform sampler2D mask; +uniform float alpha; +uniform vec4 maskClamp; + +void main(void) +{ + float clip = step(3.5, + step(maskClamp.x, vMaskCoord.x) + + step(maskClamp.y, vMaskCoord.y) + + step(vMaskCoord.x, maskClamp.z) + + step(vMaskCoord.y, maskClamp.w)); + + vec4 original = texture2D(uSampler, vTextureCoord); + vec4 masky = texture2D(mask, vMaskCoord); + + original *= (masky.r * masky.a * alpha * clip); + + gl_FragColor = original; +} diff --git a/packages/core/src/filters/spriteMask/spriteMaskFilter.vert b/packages/core/src/filters/spriteMask/spriteMaskFilter.vert new file mode 100644 index 0000000..d23a30c --- /dev/null +++ b/packages/core/src/filters/spriteMask/spriteMaskFilter.vert @@ -0,0 +1,16 @@ +attribute vec2 aVertexPosition; +attribute vec2 aTextureCoord; + +uniform mat3 projectionMatrix; +uniform mat3 otherMatrix; + +varying vec2 vMaskCoord; +varying vec2 vTextureCoord; + +void main(void) +{ + gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); + + vTextureCoord = aTextureCoord; + vMaskCoord = ( otherMatrix * vec3( aTextureCoord, 1.0) ).xy; +} diff --git a/packages/core/src/framebuffer/FrameBuffer.js b/packages/core/src/framebuffer/FrameBuffer.js new file mode 100644 index 0000000..84e9fcc --- /dev/null +++ b/packages/core/src/framebuffer/FrameBuffer.js @@ -0,0 +1,106 @@ +import Texture from '../textures/BaseTexture'; +import { FORMATS, TYPES } from '@pixi/constants'; + +/** + * Frame buffer + * @class + * @memberof PIXI + */ +export default class FrameBuffer +{ + constructor(width, height) + { + this.width = width || 100; + this.height = height || 100; + + this.stencil = false; + this.depth = false; + + this.dirtyId = 0; + this.dirtyFormat = 0; + this.dirtySize = 0; + + this.depthTexture = null; + this.colorTextures = []; + + this.glFrameBuffers = {}; + } + + get colorTexture() + { + return this.colorTextures[0]; + } + + addColorTexture(index, texture) + { + // TODO add some validation to the texture - same width / height etc? + this.colorTextures[index || 0] = texture || new Texture(null, { scaleMode: 0, + resolution: 1, + mipmap: false, + width: this.width, + height: this.height });// || new Texture(); + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + addDepthTexture(texture) + { + /* eslint-disable max-len */ + this.depthTexture = texture || new Texture(null, { scaleMode: 0, + resolution: 1, + width: this.width, + height: this.height, + mipmap: false, + format: FORMATS.DEPTH_COMPONENT, + type: TYPES.UNSIGNED_SHORT });// UNSIGNED_SHORT; + /* eslint-disable max-len */ + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + enableDepth() + { + this.depth = true; + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + enableStencil() + { + this.stencil = true; + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + resize(width, height) + { + if (width === this.width && height === this.height) return; + + this.width = width; + this.height = height; + + this.dirtyId++; + this.dirtySize++; + + for (let i = 0; i < this.colorTextures.length; i++) + { + this.colorTextures[i].setSize(width, height); + } + + if (this.depthTexture) + { + this.depthTexture.setSize(width, height); + } + } +} diff --git a/packages/core/src/framebuffer/FramebufferSystem.js b/packages/core/src/framebuffer/FramebufferSystem.js new file mode 100644 index 0000000..2cc2437 --- /dev/null +++ b/packages/core/src/framebuffer/FramebufferSystem.js @@ -0,0 +1,246 @@ +import System from '../System'; +import { Rectangle } from '@pixi/math'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class FramebufferSystem extends System +{ + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + contextChange() + { + this.gl = this.renderer.gl; + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + this.current = null; + this.viewport = new Rectangle(); + + this.drawBufferExtension = this.renderer.context.extensions.drawBuffers; + } + + bind(framebuffer, frame) + { + const gl = this.gl; + + this.current = framebuffer; + + if (framebuffer) + { + // TODO cacheing layer! + + const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID] || this.initFramebuffer(framebuffer); + + gl.bindFramebuffer(gl.FRAMEBUFFER, fbo.framebuffer); + // makesure all textures are unbound.. + + // now check for updates... + if (fbo.dirtyId !== framebuffer.dirtyId) + { + fbo.dirtyId = framebuffer.dirtyId; + + if (fbo.dirtyFormat !== framebuffer.dirtyFormat) + { + fbo.dirtyFormat = framebuffer.dirtyFormat; + this.updateFramebuffer(framebuffer); + } + else if (fbo.dirtySize !== framebuffer.dirtySize) + { + fbo.dirtySize = framebuffer.dirtySize; + this.resizeFramebuffer(framebuffer); + } + } + + for (let i = 0; i < framebuffer.colorTextures.length; i++) + { + if (framebuffer.colorTextures[i].texturePart) + { + this.renderer.texture.unbind(framebuffer.colorTextures[i].texture); + } + else + { + this.renderer.texture.unbind(framebuffer.colorTextures[i]); + } + } + + if (framebuffer.depthTexture) + { + this.renderer.texture.unbind(framebuffer.depthTexture); + } + + if (frame) + { + this.setViewport(frame.x, frame.y, frame.width, frame.height); + } + else + { + this.setViewport(0, 0, framebuffer.width, framebuffer.height); + } + } + else + { + gl.bindFramebuffer(gl.FRAMEBUFFER, null); + + if (frame) + { + this.setViewport(frame.x, frame.y, frame.width, frame.height); + } + else + { + this.setViewport(0, 0, this.renderer.width, this.renderer.height); + } + } + } + + setViewport(x, y, width, height) + { + const v = this.viewport; + + if (v.width !== width || v.height !== height || v.x !== x || v.y !== y) + { + v.x = x; + v.y = y; + v.width = width; + v.height = height; + + this.gl.viewport(x, y, width, height); + } + } + + get size() + { + if (this.current) + { + // TODO store temp + return { x: 0, y: 0, width: this.current.width, height: this.current.height }; + } + + return { x: 0, y: 0, width: this.renderer.width, height: this.renderer.height }; + } + + clear(r, g, b, a) + { + const gl = this.gl; + + // TODO clear color can be set only one right? + gl.clearColor(r, g, b, a); + gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); + } + + // private functions... + + initFramebuffer(framebuffer) + { + const gl = this.gl; + + // TODO - make this a class? + const fbo = { + framebuffer: gl.createFramebuffer(), + stencil: null, + dirtyId: 0, + dirtyFormat: 0, + dirtySize: 0, + }; + + framebuffer.glFrameBuffers[this.CONTEXT_UID] = fbo; + + return fbo; + } + + resizeFramebuffer(framebuffer) + { + const gl = this.gl; + + if (framebuffer.stencil || framebuffer.depth) + { + gl.bindRenderbuffer(gl.RENDERBUFFER, this.stencil); + gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); + } + } + + updateFramebuffer(framebuffer) + { + const gl = this.gl; + + const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID]; + + // bind the color texture + const colorTextures = framebuffer.colorTextures; + + let count = colorTextures.length; + + if (!this.drawBufferExtension) + { + count = Math.min(count, 1); + } + + const activeTextures = []; + + for (let i = 0; i < count; i++) + { + const texture = framebuffer.colorTextures[i]; + + if (texture.texturePart) + { + this.renderer.texture.bind(texture.texture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.COLOR_ATTACHMENT0 + i, + gl.TEXTURE_CUBE_MAP_NEGATIVE_X + texture.side, + texture.texture._glTextures[this.CONTEXT_UID].texture, + 0); + } + else + { + this.renderer.texture.bind(texture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.COLOR_ATTACHMENT0 + i, + gl.TEXTURE_2D, + texture._glTextures[this.CONTEXT_UID].texture, + 0); + } + + activeTextures.push(gl.COLOR_ATTACHMENT0 + i); + } + + if (this.drawBufferExtension && activeTextures.length > 1) + { + this.drawBufferExtension.drawBuffersWEBGL(activeTextures); + } + + if (framebuffer.depthTexture) + { + const depthTextureExt = this.renderer.context.extensions.depthTexture; + + if (depthTextureExt) + { + const depthTexture = framebuffer.depthTexture; + + this.renderer.texture.bind(depthTexture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.DEPTH_ATTACHMENT, + gl.TEXTURE_2D, + depthTexture._glTextures[this.CONTEXT_UID].texture, + 0); + } + } + + if (framebuffer.stencil || framebuffer.depth) + { + fbo.stencil = gl.createRenderbuffer(); + + gl.bindRenderbuffer(gl.RENDERBUFFER, fbo.stencil); + + // TODO.. this is depth AND stencil? + gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, fbo.stencil); + gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); + // fbo.enableStencil(); + } + } +} diff --git a/packages/core/src/geometry/GLBuffer.js b/packages/core/src/geometry/GLBuffer.js new file mode 100644 index 0000000..0477e8a --- /dev/null +++ b/packages/core/src/geometry/GLBuffer.js @@ -0,0 +1,9 @@ +export default class GLBuffer +{ + constructor(buffer) + { + this.buffer = buffer; + this.updateID = -1; + this.byteLength = -1; + } +} diff --git a/packages/core/src/geometry/GeometrySystem.js b/packages/core/src/geometry/GeometrySystem.js new file mode 100644 index 0000000..959f827 --- /dev/null +++ b/packages/core/src/geometry/GeometrySystem.js @@ -0,0 +1,388 @@ +import System from '../System'; +import GLBuffer from './GLBuffer'; + +const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class GeometrySystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this._activeGeometry = null; + this._activeVao = null; + + this.hasVao = true; + this.hasInstance = true; + } + + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + contextChange() + { + const gl = this.gl = this.renderer.gl; + + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + + // webgl2 + if (!gl.createVertexArray) + { + // webgl 1! + let nativeVaoExtension = this.renderer.context.extensions.vertexArrayObject; + + if (this.renderer.legacy) + { + nativeVaoExtension = null; + } + + if (nativeVaoExtension) + { + gl.createVertexArray = () => + nativeVaoExtension.createVertexArrayOES(); + + gl.bindVertexArray = (vao) => + nativeVaoExtension.bindVertexArrayOES(vao); + + gl.deleteVertexArray = (vao) => + nativeVaoExtension.deleteVertexArrayOES(vao); + } + else + { + this.hasVao = false; + gl.createVertexArray = () => + { + // empty + }; + + gl.bindVertexArray = () => + { + // empty + }; + + gl.deleteVertexArray = () => + { + // empty + }; + } + } + + if (!gl.vertexAttribDivisor) + { + const instanceExt = gl.getExtension('ANGLE_instanced_arrays'); + + if (instanceExt) + { + gl.vertexAttribDivisor = (a, b) => + instanceExt.vertexAttribDivisorANGLE(a, b); + + gl.drawElementsInstanced = (a, b, c, d, e) => + instanceExt.drawElementsInstancedANGLE(a, b, c, d, e); + + gl.drawArraysInstanced = (a, b, c, d) => + instanceExt.drawArraysInstancedANGLE(a, b, c, d); + } + else + { + this.hasInstance = false; + } + } + } + + /** + * Binds geometry so that is can be drawn. Creating a Vao if required + * @private + * @param {PIXI.Geometry} geometry instance of geometry to bind + */ + bind(geometry, shader) + { + shader = shader || this.renderer.shader.shader; + + const gl = this.gl; + + // not sure the best way to address this.. + // currently different shaders require different VAOs for the same geometry + // Still mulling over the best way to solve this one.. + // will likely need to modify the shader attribute locations at run time! + let vaos = geometry.glVertexArrayObjects[this.CONTEXT_UID]; + + if (!vaos) + { + geometry.glVertexArrayObjects[this.CONTEXT_UID] = vaos = {}; + } + + const vao = vaos[shader.program.id] || this.initGeometryVao(geometry, shader.program); + + this._activeGeometry = geometry; + + if (this._activeVao !== vao) + { + this._activeVao = vao; + + if (this.hasVao) + { + gl.bindVertexArray(vao); + } + else + { + this.activateVao(geometry, shader.program); + } + } + + // TODO - optimise later! + // don't need to loop through if nothing changed! + // maybe look to add an 'autoupdate' to geometry? + this.updateBuffers(); + } + + reset() + { + this.unbind(); + } + + updateBuffers() + { + const geometry = this._activeGeometry; + const gl = this.gl; + + for (let i = 0; i < geometry.buffers.length; i++) + { + const buffer = geometry.buffers[i]; + + const glBuffer = buffer._glBuffers[this.CONTEXT_UID]; + + if (buffer._updateID !== glBuffer.updateID) + { + glBuffer.updateID = buffer._updateID; + + // TODO can cache this on buffer! maybe added a getter / setter? + const type = buffer.index ? gl.ELEMENT_ARRAY_BUFFER : gl.ARRAY_BUFFER; + const drawType = buffer.static ? gl.STATIC_DRAW : gl.DYNAMIC_DRAW; + + gl.bindBuffer(type, glBuffer.buffer); + + if (glBuffer.byteLength >= buffer.data.byteLength) + { + // offset is always zero for now! + gl.bufferSubData(type, 0, buffer.data); + } + else + { + gl.bufferData(type, buffer.data, drawType); + } + } + } + } + + checkCompatability(geometry, program) + { + // geometry must have at least all the attributes that the shader requires. + const geometryAttributes = geometry.attributes; + const shaderAttributes = program.attributeData; + + for (const j in shaderAttributes) + { + if (!geometryAttributes[j]) + { + throw new Error(`shader and geometry incompatible, geometry missing the "${j}" attribute`); + } + } + } + + /** + * Creates a Vao with the same structure as the geometry and stores it on the geometry. + * @private + * @param {PIXI.Geometry} geometry instance of geometry to to generate Vao for + */ + initGeometryVao(geometry, program) + { + this.checkCompatability(geometry, program); + + const gl = this.gl; + const CONTEXT_UID = this.CONTEXT_UID; + const buffers = geometry.buffers; + const attributes = geometry.attributes; + + const tempStride = {}; + const tempStart = {}; + + for (const j in buffers) + { + tempStride[j] = 0; + tempStart[j] = 0; + } + + for (const j in attributes) + { + if (!attributes[j].size && program.attributeData[j]) + { + attributes[j].size = program.attributeData[j].size; + } + + tempStride[attributes[j].buffer] += attributes[j].size * byteSizeMap[attributes[j].type]; + } + + for (const j in attributes) + { + const attribute = attributes[j]; + const attribSize = attribute.size; + + if (attribute.stride === undefined) + { + if (tempStride[attribute.buffer] === attribSize * byteSizeMap[attribute.type]) + { + attribute.stride = 0; + } + else + { + attribute.stride = tempStride[attribute.buffer]; + } + } + + if (attribute.start === undefined) + { + attribute.start = tempStart[attribute.buffer]; + + tempStart[attribute.buffer] += attribSize * byteSizeMap[attribute.type]; + } + } + + // first update - and create the buffers! + // only create a gl buffer if it actually gets + for (let i = 0; i < buffers.length; i++) + { + const buffer = buffers[i]; + + if (!buffer._glBuffers[CONTEXT_UID]) + { + buffer._glBuffers[CONTEXT_UID] = new GLBuffer(gl.createBuffer()); + } + } + + // TODO - maybe make this a data object? + // lets wait to see if we need to first! + const vao = gl.createVertexArray(); + + gl.bindVertexArray(vao); + + this.activateVao(geometry, program); + + gl.bindVertexArray(null); + + // add it to the cache! + geometry.glVertexArrayObjects[this.CONTEXT_UID][program.id] = vao; + + return vao; + } + + activateVao(geometry, program) + { + const gl = this.gl; + const CONTEXT_UID = this.CONTEXT_UID; + const buffers = geometry.buffers; + const attributes = geometry.attributes; + + if (geometry.indexBuffer) + { + // first update the index buffer if we have one.. + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, geometry.indexBuffer._glBuffers[CONTEXT_UID].buffer); + } + + let lastBuffer = null; + + // add a new one! + for (const j in attributes) + { + const attribute = attributes[j]; + const buffer = buffers[attribute.buffer]; + const glBuffer = buffer._glBuffers[CONTEXT_UID]; + + if (program.attributeData[j]) + { + if (lastBuffer !== glBuffer) + { + gl.bindBuffer(gl.ARRAY_BUFFER, glBuffer.buffer); + + lastBuffer = glBuffer; + } + + const location = program.attributeData[j].location; + + // TODO introduce state again + // we can optimise this for older devices that have no VAOs + gl.enableVertexAttribArray(location); + + gl.vertexAttribPointer(location, + attribute.size, + attribute.type || gl.FLOAT, + attribute.normalized, + attribute.stride, + attribute.start); + + if (attribute.instance) + { + // TODO calculate instance count based of this... + if (this.hasInstance) + { + gl.vertexAttribDivisor(location, 1); + } + else + { + throw new Error('geometry error, GPU Instancing is not supported on this device'); + } + } + } + } + } + + draw(type, size, start, instanceCount) + { + const gl = this.gl; + const geometry = this._activeGeometry; + + // TODO.. this should not change so maybe cache the function? + + if (geometry.indexBuffer) + { + if (geometry.instanced) + { + /* eslint-disable max-len */ + gl.drawElementsInstanced(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2, instanceCount || 1); + /* eslint-enable max-len */ + } + else + { + gl.drawElements(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2); + } + } + else + if (geometry.instanced) + { + // TODO need a better way to calculate size.. + gl.drawArraysInstanced(type, start, size || geometry.getSize(), instanceCount || 1); + } + else + { + gl.drawArrays(type, start, size || geometry.getSize()); + } + + return this; + } + + unbind() + { + this.gl.bindVertexArray(null); + this._activeVao = null; + this._activeGeometry = null; + } +} diff --git a/packages/core/src/geometry/utils/setVertexAttribArrays.js b/packages/core/src/geometry/utils/setVertexAttribArrays.js new file mode 100644 index 0000000..23b6959 --- /dev/null +++ b/packages/core/src/geometry/utils/setVertexAttribArrays.js @@ -0,0 +1,54 @@ +// var GL_MAP = {}; + +/** + * @param gl {WebGLRenderingContext} The current WebGL context + * @param attribs {*} + * @param state {*} + */ +export default function setVertexAttribArrays(gl, attribs, state) +{ + let i; + + if (state) + { + const tempAttribState = state.tempAttribState; + const attribState = state.attribState; + + for (i = 0; i < tempAttribState.length; i++) + { + tempAttribState[i] = false; + } + + // set the new attribs + for (i = 0; i < attribs.length; i++) + { + tempAttribState[attribs[i].attribute.location] = true; + } + + for (i = 0; i < attribState.length; i++) + { + if (attribState[i] !== tempAttribState[i]) + { + attribState[i] = tempAttribState[i]; + + if (state.attribState[i]) + { + gl.enableVertexAttribArray(i); + } + else + { + gl.disableVertexAttribArray(i); + } + } + } + } + else + { + for (i = 0; i < attribs.length; i++) + { + const attrib = attribs[i]; + + gl.enableVertexAttribArray(attrib.attribute.location); + } + } +} diff --git a/packages/core/src/index.js b/packages/core/src/index.js index 58c456a..fd1f547 100644 --- a/packages/core/src/index.js +++ b/packages/core/src/index.js @@ -1,30 +1,30 @@ import * as resources from './textures/resources'; -import * as systems from './renderers/systems'; +import * as systems from './systems'; -export { resources }; export { systems }; +export { resources }; -export { default as Renderer } from './renderers/Renderer'; -export { default as AbstractRenderer } from './renderers/AbstractRenderer'; -export { default as FrameBuffer } from './textures/FrameBuffer'; +export { default as System } from './System'; +export { default as Renderer } from './Renderer'; +export { default as AbstractRenderer } from './AbstractRenderer'; +export { default as FrameBuffer } from './framebuffer/FrameBuffer'; export { default as CubeTexture } from './textures/CubeTexture'; export { default as BaseTexture } from './textures/BaseTexture'; export { default as Texture } from './textures/Texture'; export { default as TextureMatrix } from './textures/TextureMatrix'; -export { default as RenderTexture } from './textures/RenderTexture'; -export { default as BaseRenderTexture } from './textures/BaseRenderTexture'; +export { default as RenderTexture } from './renderTexture/RenderTexture'; +export { default as BaseRenderTexture } from './renderTexture/BaseRenderTexture'; export { default as TextureUvs } from './textures/TextureUvs'; -export { default as State } from './renderers/State'; -export { default as ObjectRenderer } from './renderers/utils/ObjectRenderer'; -export { default as Quad } from './renderers/utils/Quad'; -export { default as QuadUv } from './renderers/utils/QuadUv'; -export { default as checkMaxIfStatmentsInShader } from './renderers/utils/checkMaxIfStatmentsInShader'; +export { default as State } from './state/State'; +export { default as ObjectRenderer } from './batch/ObjectRenderer'; +export { default as Quad } from './utils/Quad'; +export { default as QuadUv } from './utils/QuadUv'; +export { default as checkMaxIfStatmentsInShader } from './shader/utils/checkMaxIfStatmentsInShader'; export { default as Shader } from './shader/Shader'; export { default as Program } from './shader/Program'; export { default as UniformGroup } from './shader/UniformGroup'; -export { default as SpriteMaskFilter } from './renderers/filters/spriteMask/SpriteMaskFilter'; -export { default as Filter } from './renderers/filters/Filter'; +export { default as SpriteMaskFilter } from './filters/spriteMask/SpriteMaskFilter'; +export { default as Filter } from './filters/Filter'; export { default as Attribute } from './geometry/Attribute'; export { default as Buffer } from './geometry/Buffer'; export { default as Geometry } from './geometry/Geometry'; - diff --git a/packages/core/src/mask/MaskSystem.js b/packages/core/src/mask/MaskSystem.js new file mode 100644 index 0000000..99174c2 --- /dev/null +++ b/packages/core/src/mask/MaskSystem.js @@ -0,0 +1,199 @@ +import System from '../System'; +import AlphaMaskFilter from '../filters/spriteMask/SpriteMaskFilter'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class MaskSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + // TODO - we don't need both! + this.scissor = false; + this.scissorData = null; + this.scissorRenderTarget = null; + + this.enableScissor = false; + + this.alphaMaskPool = []; + this.alphaMaskIndex = 0; + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.DisplayObject} target - Display Object to push the mask to + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + push(target, maskData) + { + // TODO the root check means scissor rect will not + // be used on render textures more info here: + // https://github.com/pixijs/pixi.js/pull/3545 + + if (maskData.texture) + { + this.pushSpriteMask(target, maskData); + } + else if (this.enableScissor + && !this.scissor + && this.renderer._activeRenderTarget.root + && !this.renderer.stencil.stencilMaskStack.length + && maskData.isFastRect()) + { + const matrix = maskData.worldTransform; + + let rot = Math.atan2(matrix.b, matrix.a); + + // use the nearest degree! + rot = Math.round(rot * (180 / Math.PI)); + + if (rot % 90) + { + this.pushStencilMask(maskData); + } + else + { + this.pushScissorMask(target, maskData); + } + } + else + { + this.pushStencilMask(maskData); + } + } + + /** + * Removes the last mask from the mask stack and doesn't return it. + * + * @param {PIXI.DisplayObject} target - Display Object to pop the mask from + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + pop(target, maskData) + { + if (maskData.texture) + { + this.popSpriteMask(target, maskData); + } + else if (this.enableScissor && !this.renderer.stencil.stencilMaskStack.length) + { + this.popScissorMask(target, maskData); + } + else + { + this.popStencilMask(target, maskData); + } + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.RenderTarget} target - Display Object to push the sprite mask to + * @param {PIXI.Sprite} maskData - Sprite to be used as the mask + */ + pushSpriteMask(target, maskData) + { + let alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; + + if (!alphaMaskFilter) + { + alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; + } + + alphaMaskFilter[0].resolution = this.renderer.resolution; + alphaMaskFilter[0].maskSprite = maskData; + + // TODO - may cause issues! + target.filterArea = maskData.getBounds(true); + + this.renderer.filterSystem.pushFilter(target, alphaMaskFilter); + + this.alphaMaskIndex++; + } + + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popSpriteMask() + { + this.renderer.filterSystem.popFilter(); + this.alphaMaskIndex--; + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + pushStencilMask(maskData) + { + this.renderer.batch.flush(); + this.renderer.stencil.pushStencil(maskData); + } + + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popStencilMask() + { + // this.renderer.currentRenderer.stop(); + this.renderer.stencil.popStencil(); + } + + /** + * + * @param {PIXI.DisplayObject} target - Display Object to push the mask to + * @param {PIXI.Graphics} maskData - The masking data. + */ + pushScissorMask(target, maskData) + { + maskData.renderable = true; + + const renderTarget = this.renderer._activeRenderTarget; + + const bounds = maskData.getBounds(); + + bounds.fit(renderTarget.size); + maskData.renderable = false; + + this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); + + const resolution = this.renderer.resolution; + + this.renderer.gl.scissor( + bounds.x * resolution, + (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, + bounds.width * resolution, + bounds.height * resolution + ); + + this.scissorRenderTarget = renderTarget; + this.scissorData = maskData; + this.scissor = true; + } + + /** + * + * + */ + popScissorMask() + { + this.scissorRenderTarget = null; + this.scissorData = null; + this.scissor = false; + + // must be scissor! + const gl = this.renderer.gl; + + gl.disable(gl.SCISSOR_TEST); + } +} diff --git a/packages/core/src/mask/StencilSystem.js b/packages/core/src/mask/StencilSystem.js new file mode 100644 index 0000000..62796a5 --- /dev/null +++ b/packages/core/src/mask/StencilSystem.js @@ -0,0 +1,133 @@ +import System from '../System'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class StencilSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + this.stencilMaskStack = []; + } + + /** + * Changes the mask stack that is used by this System. + * + * @param {PIXI.Graphics[]} stencilMaskStack - The mask stack + */ + setMaskStack(stencilMaskStack) + { + const gl = this.renderer.gl; + + if (stencilMaskStack.length !== this.stencilMaskStack.length) + { + if (stencilMaskStack.length === 0) + { + gl.disable(gl.STENCIL_TEST); + } + else + { + gl.enable(gl.STENCIL_TEST); + } + } + + this.stencilMaskStack = stencilMaskStack; + } + + /** + * Applies the Mask and adds it to the current stencil stack. @alvin + * + * @param {PIXI.Graphics} graphics - The mask + */ + pushStencil(graphics) + { + this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); + + // this.renderer._activeRenderTarget.attachStencilBuffer(); + + const gl = this.renderer.gl; + const prevMaskCount = this.stencilMaskStack.length; + + if (prevMaskCount === 0) + { + gl.enable(gl.STENCIL_TEST); + } + + this.stencilMaskStack.push(graphics); + + // Increment the refference stencil value where the new mask overlaps with the old ones. + gl.colorMask(false, false, false, false); + gl.stencilFunc(gl.EQUAL, prevMaskCount, this._getBitwiseMask()); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.INCR); + this.renderer.plugins.graphics.render(graphics); + + this._useCurrent(); + } + + /** + * Removes the last mask from the stencil stack. @alvin + */ + popStencil() + { + this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); + + const gl = this.renderer.gl; + const graphics = this.stencilMaskStack.pop(); + + if (this.stencilMaskStack.length === 0) + { + // the stack is empty! + gl.disable(gl.STENCIL_TEST); + gl.clear(gl.STENCIL_BUFFER_BIT); + gl.clearStencil(0); + } + else + { + // Decrement the refference stencil value where the popped mask overlaps with the other ones + gl.colorMask(false, false, false, false); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.DECR); + this.renderer.plugins.graphics.render(graphics); + + this._useCurrent(); + } + } + + /** + * Setup renderer to use the current stencil data. + */ + _useCurrent() + { + const gl = this.renderer.gl; + + gl.colorMask(true, true, true, true); + gl.stencilFunc(gl.EQUAL, this.stencilMaskStack.length, this._getBitwiseMask()); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP); + } + + /** + * Fill 1s equal to the number of acitve stencil masks. + * + * @return {number} The bitwise mask. + */ + _getBitwiseMask() + { + return (1 << this.stencilMaskStack.length) - 1; + } + + /** + * Destroys the mask stack. + * + */ + destroy() + { + super.destroy(this); + + this.stencilMaskStack = null; + } +} diff --git a/packages/core/src/projection/ProjectionSystem.js b/packages/core/src/projection/ProjectionSystem.js new file mode 100644 index 0000000..6e63b73 --- /dev/null +++ b/packages/core/src/projection/ProjectionSystem.js @@ -0,0 +1,73 @@ +import System from '../System'; +import { Matrix } from '@pixi/math'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class ProjectionSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.projectionMatrix = new Matrix(); + } + + update(destinationFrame, sourceFrame, resolution, root) + { + this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; + this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; + + this.calculateProjection(this.destinationFrame, this.sourceFrame, resolution, root); + + this.renderer.globalUniforms.uniforms.projectionMatrix = this.projectionMatrix; + this.renderer.globalUniforms.update(); + } + + /** + * Updates the projection matrix based on a projection frame (which is a rectangle) + * + * @param {Rectangle} destinationFrame - The destination frame. + * @param {Rectangle} sourceFrame - The source frame. + */ + calculateProjection(destinationFrame, sourceFrame, resolution, root) + { + const pm = this.projectionMatrix; + + // I don't think we will need this line.. + // pm.identity(); + + if (!root) + { + pm.a = (1 / destinationFrame.width * 2) * resolution; + pm.d = (1 / destinationFrame.height * 2) * resolution; + + pm.tx = -1 - (sourceFrame.x * pm.a); + pm.ty = -1 - (sourceFrame.y * pm.d); + } + else + { + pm.a = (1 / destinationFrame.width * 2) * resolution; + pm.d = (-1 / destinationFrame.height * 2) * resolution; + + pm.tx = -1 - (sourceFrame.x * pm.a); + pm.ty = 1 - (sourceFrame.y * pm.d); + } + } + + /** + * Sets the transform of the active render target to the given matrix + * + * @param {PIXI.Matrix} matrix - The transformation matrix + */ + setTransform()// matrix) + { + // this._activeRenderTarget.transform = matrix; + } +} diff --git a/packages/core/src/renderTexture/BaseRenderTexture.js b/packages/core/src/renderTexture/BaseRenderTexture.js new file mode 100644 index 0000000..88eb83b --- /dev/null +++ b/packages/core/src/renderTexture/BaseRenderTexture.js @@ -0,0 +1,137 @@ +import BaseTexture from '../textures/BaseTexture'; +import FrameBuffer from '../framebuffer/FrameBuffer'; + +/** + * A BaseRenderTexture is a special texture that allows any PixiJS display object to be rendered to it. + * + * __Hint__: All DisplayObjects (i.e. Sprites) that render to a BaseRenderTexture should be preloaded + * otherwise black rectangles will be drawn instead. + * + * A BaseRenderTexture takes a snapshot of any Display Object given to its render method. The position + * and rotation of the given Display Objects is ignored. For example: + * + * ```js + * let renderer = PIXI.autoDetectRenderer(1024, 1024, { view: canvas, ratio: 1 }); + * let baseRenderTexture = new PIXI.BaseRenderTexture(renderer, 800, 600); + * let sprite = PIXI.Sprite.fromImage("spinObj_01.png"); + * + * sprite.position.x = 800/2; + * sprite.position.y = 600/2; + * sprite.anchor.x = 0.5; + * sprite.anchor.y = 0.5; + * + * baseRenderTexture.render(sprite); + * ``` + * + * The Sprite in this case will be rendered using its local transform. To render this sprite at 0,0 + * you can clear the transform + * + * ```js + * + * sprite.setTransform() + * + * let baseRenderTexture = new PIXI.BaseRenderTexture(100, 100); + * let renderTexture = new PIXI.RenderTexture(baseRenderTexture); + * + * renderer.render(sprite, renderTexture); // Renders to center of RenderTexture + * ``` + * + * @class + * @extends PIXI.BaseTexture + * @memberof PIXI + */ +export default class BaseRenderTexture extends BaseTexture +{ + /** + * @param {object} [options] + * @param {number} [options.width=100] - The width of the base render texture + * @param {number} [options.height=100] - The height of the base render texture + * @param {PIXI.SCALE_MODES} [options.scaleMode] - See {@link PIXI.SCALE_MODES} for possible values + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the texture being generated + */ + constructor(options) + { + if (typeof options === 'number') + { + /* eslint-disable prefer-rest-params */ + // Backward compatibility of signature + const width = arguments[0]; + const height = arguments[1]; + const scaleMode = arguments[2]; + const resolution = arguments[3]; + + options = { width, height, scaleMode, resolution }; + /* eslint-enable prefer-rest-params */ + } + + super(null, options); + + const { width, height } = options || {}; + + // Set defaults + this.mipmap = false; + this.width = Math.ceil(width) || 100; + this.height = Math.ceil(height) || 100; + this.valid = true; + + /** + * A map of renderer IDs to webgl renderTargets + * + * @private + * @member {object} + */ + // this._glRenderTargets = {}; + + /** + * A reference to the canvas render target (we only need one as this can be shared across renderers) + * + * @private + * @member {object} + */ + this._canvasRenderTarget = null; + + this.clearColor = [0, 0, 0, 0]; + + this.frameBuffer = new FrameBuffer(width * this.resolution, height * this.resolution) + .addColorTexture(0, this); + + // TODO - could this be added the systems? + + /** + * The data structure for the stencil masks + * + * @member {PIXI.Graphics[]} + */ + this.stencilMaskStack = []; + + /** + * The data structure for the filters + * + * @member {PIXI.Graphics[]} + */ + this.filterStack = [{}]; + } + + /** + * Resizes the BaseRenderTexture. + * + * @param {number} width - The width to resize to. + * @param {number} height - The height to resize to. + */ + resize(width, height) + { + width = Math.ceil(width); + height = Math.ceil(height); + this.frameBuffer.resize(width * this.resolution, height * this.resolution); + } + + /** + * Destroys this texture + * + */ + destroy() + { + super.destroy(true); + this.renderer = null; + } +} diff --git a/packages/core/src/renderTexture/RenderTexture.js b/packages/core/src/renderTexture/RenderTexture.js new file mode 100644 index 0000000..f1ecf1f --- /dev/null +++ b/packages/core/src/renderTexture/RenderTexture.js @@ -0,0 +1,153 @@ +import BaseRenderTexture from './BaseRenderTexture'; +import Texture from '../textures/Texture'; + +/** + * A RenderTexture is a special texture that allows any PixiJS display object to be rendered to it. + * + * __Hint__: All DisplayObjects (i.e. Sprites) that render to a RenderTexture should be preloaded + * otherwise black rectangles will be drawn instead. + * + * A RenderTexture takes a snapshot of any Display Object given to its render method. For example: + * + * ```js + * let renderer = PIXI.autoDetectRenderer(1024, 1024, { view: canvas, ratio: 1 }); + * let renderTexture = PIXI.RenderTexture.create(800, 600); + * let sprite = PIXI.Sprite.fromImage("spinObj_01.png"); + * + * sprite.position.x = 800/2; + * sprite.position.y = 600/2; + * sprite.anchor.x = 0.5; + * sprite.anchor.y = 0.5; + * + * renderer.render(sprite, renderTexture); + * ``` + * + * The Sprite in this case will be rendered using its local transform. To render this sprite at 0,0 + * you can clear the transform + * + * ```js + * + * sprite.setTransform() + * + * let renderTexture = new PIXI.RenderTexture.create(100, 100); + * + * renderer.render(sprite, renderTexture); // Renders to center of RenderTexture + * ``` + * + * @class + * @extends PIXI.Texture + * @memberof PIXI + */ +export default class RenderTexture extends Texture +{ + /** + * @param {PIXI.BaseRenderTexture} baseRenderTexture - The renderer used for this RenderTexture + * @param {PIXI.Rectangle} [frame] - The rectangle frame of the texture to show + */ + constructor(baseRenderTexture, frame) + { + // support for legacy.. + let _legacyRenderer = null; + + if (!(baseRenderTexture instanceof BaseRenderTexture)) + { + /* eslint-disable prefer-rest-params, no-console */ + const width = arguments[1]; + const height = arguments[2]; + const scaleMode = arguments[3]; + const resolution = arguments[4]; + + // we have an old render texture.. + console.warn(`Please use RenderTexture.create(${width}, ${height}) instead of the ctor directly.`); + _legacyRenderer = arguments[0]; + /* eslint-enable prefer-rest-params, no-console */ + + frame = null; + baseRenderTexture = new BaseRenderTexture({ + width, + height, + scaleMode, + resolution, + }); + } + + /** + * The base texture object that this texture uses + * + * @member {BaseTexture} + */ + super(baseRenderTexture, frame); + + this.legacyRenderer = _legacyRenderer; + + /** + * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. + * + * @member {boolean} + */ + this.valid = true; + + /** + * FilterSystem temporary storage + * @private + * @member {PIXI.Rectangle} + */ + this.filterFrame = null; + + this._updateUvs(); + } + + /** + * Resizes the RenderTexture. + * + * @param {number} width - The width to resize to. + * @param {number} height - The height to resize to. + * @param {boolean} [resizeBaseTexture=true] - Should the baseTexture.width and height values be resized as well? + */ + resize(width, height, resizeBaseTexture = true) + { + width = Math.ceil(width); + height = Math.ceil(height); + + // TODO - could be not required.. + this.valid = (width > 0 && height > 0); + + this._frame.width = this.orig.width = width; + this._frame.height = this.orig.height = height; + + if (resizeBaseTexture) + { + this.baseTexture.resize(width, height); + } + + this._updateUvs(); + } + + /** + * A short hand way of creating a render texture. + * + * @param {object} [options] - Options + * @param {number} [options.width=100] - The width of the render texture + * @param {number} [options.height=100] - The height of the render texture + * @param {number} [options.scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the texture being generated + * @return {PIXI.RenderTexture} The new render texture + */ + static create(options) + { + // fallback, old-style: create(width, height, scaleMode, resolution) + if (typeof options === 'number') + { + /* eslint-disable prefer-rest-params */ + options = { + width: options, + height: arguments[1], + scaleMode: arguments[2], + resolution: arguments[3], + }; + /* eslint-enable prefer-rest-params */ + } + + return new RenderTexture(new BaseRenderTexture(options)); + } +} diff --git a/packages/core/src/renderTexture/RenderTextureSystem.js b/packages/core/src/renderTexture/RenderTextureSystem.js new file mode 100644 index 0000000..8d0ca61 --- /dev/null +++ b/packages/core/src/renderTexture/RenderTextureSystem.js @@ -0,0 +1,115 @@ +import System from '../System'; +import { Rectangle } from '@pixi/math'; + +const tempRect = new Rectangle(); + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class RenderTextureSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.clearColor = renderer._backgroundColorRgba; + + // TODO moe this property somewhere else! + this.defaultMaskStack = []; + this.defaultFilterStack = [{}]; + + // empty render texture? + this.renderTexture = null; + + this.destinationFrame = new Rectangle(); + } + + bind(renderTexture, sourceFrame, destinationFrame) + { + // TODO - do we want this?? + if (this.renderTexture === renderTexture) return; + this.renderTexture = renderTexture; + + const renderer = this.renderer; + + if (renderTexture) + { + const baseTexture = renderTexture.baseTexture; + + if (!destinationFrame) + { + tempRect.width = baseTexture.realWidth; + tempRect.height = baseTexture.realHeight; + + destinationFrame = tempRect; + } + + if (!sourceFrame) + { + sourceFrame = destinationFrame; + } + + this.renderer.framebuffer.bind(baseTexture.frameBuffer, destinationFrame); + + this.renderer.projection.update(destinationFrame, sourceFrame, baseTexture.resolution, false); + this.renderer.stencil.setMaskStack(baseTexture.stencilMaskStack); + } + else + { + // TODO these validation checks happen deeper down.. + // thing they can be avoided.. + if (!destinationFrame) + { + tempRect.width = renderer.width; + tempRect.height = renderer.height; + + destinationFrame = tempRect; + } + + if (!sourceFrame) + { + sourceFrame = destinationFrame; + } + + renderer.framebuffer.bind(null, destinationFrame); + + // TODO store this.. + this.renderer.projection.update(destinationFrame, sourceFrame, this.renderer.resolution, true); + this.renderer.stencil.setMaskStack(this.defaultMaskStack); + } + + this.destinationFrame.copyFrom(destinationFrame); + } + + /** + * Erases the render texture and fills the drawing area with a colour + * + * @param {number} [clearColor] - The colour + * @return {PIXI.Renderer} Returns itself. + */ + clear(clearColor) + { + if (this.renderTexture) + { + clearColor = clearColor || this.renderTexture.baseTexture.clearColor; + } + else + { + clearColor = clearColor || this.clearColor; + } + + this.renderer.framebuffer.clear(clearColor[0], clearColor[1], clearColor[2], clearColor[3]); + } + + resize()// screenWidth, screenHeight) + { + // resize the root only! + this.bind(null); + } +} diff --git a/packages/core/src/renderers/AbstractRenderer.js b/packages/core/src/renderers/AbstractRenderer.js deleted file mode 100644 index 928670f..0000000 --- a/packages/core/src/renderers/AbstractRenderer.js +++ /dev/null @@ -1,353 +0,0 @@ -import { hex2string, hex2rgb } from '@pixi/utils'; -import { Matrix, Rectangle } from '@pixi/math'; -import { RENDERER_TYPE } from '@pixi/constants'; -import { settings } from '@pixi/settings'; -import { Container } from '@pixi/display'; -import RenderTexture from '../textures/RenderTexture'; -import EventEmitter from 'eventemitter3'; - -const tempMatrix = new Matrix(); - -/** - * 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 EventEmitter - * @memberof PIXI - */ -export default class AbstractRenderer extends EventEmitter -{ - // eslint-disable-next-line valid-jsdoc - /** - * @param {string} system - The name of the system this renderer is for. - * @param {object} [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.transparent=false] - If the render view is transparent, default false - * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false - * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) - * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. The - * resolution of the renderer retina would be 2. - * @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 {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when rendering, - * stopping pixel interpolation. - */ - constructor(system, options, arg2, arg3) - { - super(); - - // Support for constructor(system, screenWidth, screenHeight, options) - if (typeof options === 'number') - { - options = Object.assign({ - width: options, - height: arg2 || settings.RENDER_OPTIONS.height, - }, arg3); - } - - // 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 = RENDERER_TYPE.UNKNOWN; - - /** - * Measurements of the screen. (0, 0, screenWidth, screenHeight) - * - * Its safe to use as filterArea or hitArea for 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 1 - */ - this.resolution = options.resolution || settings.RESOLUTION; - - /** - * Whether the render view is transparent - * - * @member {boolean} - */ - this.transparent = options.transparent; - - /** - * Whether css dimensions of canvas view should be resized to screen dimensions automatically - * - * @member {boolean} - */ - this.autoResize = options.autoResize || false; - - /** - * Tracks the blend modes useful for this renderer. - * - * @member {object} - */ - this.blendModes = null; - - /** - * 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; - - /** - * If true PixiJS will Math.floor() x/y values when rendering, stopping pixel interpolation. - * Handy for crisp pixel art and speed on legacy devices. - * - * @member {boolean} - */ - this.roundPixels = options.roundPixels; - - /** - * The background color as a number. - * - * @member {number} - * @private - */ - this._backgroundColor = 0x000000; - - /** - * The background color as an [R, G, B] array. - * - * @member {number[]} - * @private - */ - this._backgroundColorRgba = [0, 0, 0, 0]; - - /** - * The background color as a string. - * - * @member {string} - * @private - */ - this._backgroundColorString = '#000000'; - - this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter - - /** - * This temporary display object used as the parent of the currently being rendered item - * - * @member {PIXI.DisplayObject} - * @private - */ - this._tempDisplayObjectParent = new Container(); - - /** - * The last root object that the renderer tried to render. - * - * @member {PIXI.DisplayObject} - * @private - */ - this._lastObjectRendered = this._tempDisplayObjectParent; - - /** - * Collection of plugins. - * @readonly - * @member {object} - */ - this.plugins = {}; - } - - /** - * Initialize the plugins. - * - * @protected - * @param {object} staticMap - The dictionary of staticly saved plugins. - */ - initPlugins(staticMap) - { - 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() - { - return this.view.width; - } - - /** - * Same as view.height, actual number of pixels in the canvas by vertical - * - * @member {number} - * @readonly - * @default 600 - */ - get height() - { - return this.view.height; - } - - /** - * Resizes the screen and canvas to the specified width and height - * Canvas dimensions are multiplied by resolution - * - * @param {number} screenWidth - the new width of the screen - * @param {number} screenHeight - the new height of the screen - */ - resize(screenWidth, screenHeight) - { - this.screen.width = screenWidth; - this.screen.height = screenHeight; - - this.view.width = screenWidth * this.resolution; - this.view.height = screenHeight * this.resolution; - - if (this.autoResize) - { - this.view.style.width = `${screenWidth}px`; - this.view.style.height = `${screenHeight}px`; - } - } - - /** - * 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 {PIXI.DisplayObject} displayObject - The displayObject the object will be generated from - * @param {number} scaleMode - Should be one of the scaleMode consts - * @param {number} resolution - The resolution / device pixel ratio of the texture being generated - * @param {PIXI.Rectangle} [region] - The region of the displayObject, that shall be rendered, - * if no region is specified, defaults to the local bounds of the displayObject. - * @return {PIXI.Texture} a texture of the graphics object - */ - generateTexture(displayObject, scaleMode, resolution, region) - { - region = region || displayObject.getLocalBounds(); - - // 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(region.width | 0, region.height | 0, scaleMode, resolution); - - tempMatrix.tx = -region.x; - tempMatrix.ty = -region.y; - - this.render(displayObject, renderTexture, false, tempMatrix, true); - - return renderTexture; - } - - /** - * Removes everything from the renderer and optionally removes the Canvas DOM element. - * - * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. - */ - destroy(removeView) - { - 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); - } - - this.plugins = null; - - this.type = RENDERER_TYPE.UNKNOWN; - - this.view = null; - - this.screen = null; - - this.resolution = 0; - - this.transparent = false; - - this.autoResize = false; - - this.blendModes = null; - - this.options = null; - - this.preserveDrawingBuffer = false; - this.clearBeforeRender = false; - - this.roundPixels = false; - - this._backgroundColor = 0; - this._backgroundColorRgba = null; - this._backgroundColorString = null; - - this._tempDisplayObjectParent = null; - this._lastObjectRendered = null; - } - - /** - * The background color to fill if not transparent - * - * @member {number} - */ - get backgroundColor() - { - return this._backgroundColor; - } - - set backgroundColor(value) // eslint-disable-line require-jsdoc - { - this._backgroundColor = value; - this._backgroundColorString = hex2string(value); - hex2rgb(value, this._backgroundColorRgba); - } -} diff --git a/packages/core/src/renderers/Renderer.js b/packages/core/src/renderers/Renderer.js deleted file mode 100644 index a2020b1..0000000 --- a/packages/core/src/renderers/Renderer.js +++ /dev/null @@ -1,329 +0,0 @@ -import AbstractRenderer from './AbstractRenderer'; -import { sayHello } from '@pixi/utils'; -import MaskSystem from './systems/MaskSystem'; -import StencilSystem from './systems/StencilSystem'; -import FilterSystem from './systems/filter/FilterSystem'; -import FramebufferSystem from './systems/FramebufferSystem'; -import RenderTextureSystem from './systems/RenderTextureSystem'; -import TextureSystem from './systems/textures/TextureSystem'; -import ProjectionSystem from './systems/ProjectionSystem'; -import StateSystem from './systems/StateSystem'; -import GeometrySystem from './systems/geometry/GeometrySystem'; -import ShaderSystem from './systems/shader/ShaderSystem'; -import ContextSystem from './systems/ContextSystem'; -import BatchSystem from './systems/BatchSystem'; -import TextureGCSystem from './systems/textures/TextureGCSystem'; -import { RENDERER_TYPE } from '@pixi/constants'; -import UniformGroup from '../shader/UniformGroup'; -import { Matrix } from '@pixi/math'; -import Runner from 'mini-runner'; - -/** - * 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 Render works by automatically managing webGLBatchs. - * 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 :) - * - * @class - * @memberof PIXI - * @extends PIXI.AbstractRenderer - */ -export default class Renderer extends AbstractRenderer -{ - // eslint-disable-next-line valid-jsdoc - /** - * - * @param {object} [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.transparent=false] - If the render view is transparent, default false - * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false - * @param {boolean} [options.antialias=false] - sets antialias. If not available natively then FXAA - * antialiasing is used - * @param {boolean} [options.forceFXAA=false] - forces FXAA antialiasing to be used over native. - * FXAA is faster, but may not always look as great - * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. - * The resolution of the renderer retina would be 2. - * @param {boolean} [options.clearBeforeRender=true] - This sets if the renderer will clear - * the canvas or not before the new render pass. If you wish to set this to false, you *must* set - * preserveDrawingBuffer to `true`. - * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, - * enable this if you need to call toDataUrl on the webgl context. - * @param {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when - * rendering, stopping pixel interpolation. - * @param {number} [options.backgroundColor=0x000000] - The background color of the rendered area - * (shown if not transparent). - * @param {boolean} [options.legacy=false] - If true PixiJS will aim to ensure compatibility - * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. - * @param {string} [options.powerPreference] - Parameter passed to webgl context, set to "high-performance" - * for devices with dual graphics card - */ - constructor(options, arg2, arg3) - { - super('WebGL', options, arg2, arg3); - - /** - * The type of this renderer as a standardised const - * - * @member {number} - * @see PIXI.RENDERER_TYPE - */ - this.type = RENDERER_TYPE.WEBGL; - - // this will be set by the contextSystem (this.context) - this.gl = null; - this.CONTEXT_UID = 0; - this.legacy = !!options.legacy; - - // TODO legacy! - - // runners! - this.runners = { - destroy: new Runner('destroy'), - contextChange: new Runner('contextChange', 1), - reset: new Runner('reset'), - update: new Runner('update'), - postrender: new Runner('postrender'), - prerender: new Runner('prerender'), - resize: new Runner('resize', 2), - }; - - 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(GeometrySystem, 'geometry') - .addSystem(FramebufferSystem, 'framebuffer') - .addSystem(StencilSystem, 'stencil') - .addSystem(ProjectionSystem, 'projection') - .addSystem(TextureGCSystem, 'textureGC') - .addSystem(FilterSystem, 'filter') - .addSystem(RenderTextureSystem, 'renderTexture') - .addSystem(BatchSystem, 'batch'); - - this.initPlugins(Renderer.__plugins); - - /** - * The options passed in to create a new webgl context. - * - * @member {object} - * @private - */ - if (options.context) - { - this.context.initFromContext(options.context); - } - else - { - this.context.initFromOptions({ - alpha: this.transparent, - antialias: options.antialias, - premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', - stencil: true, - 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); - } - - addSystem(_class, name) - { - if (!name) - { - name = _class.name; - } - - // TODO - read name from class.name.. - - /* - if(name.includes('System')) - { - name = name.replace('System', ''); - name = name.charAt(0).toLowerCase() + name.slice(1); - } - */ - - const system = new _class(this); - - if (this[name]) - { - throw new Error(`Whoops! ${name} is already a manger`); - } - - this[name] = system; - - for (const i in this.runners) - { - this.runners[i].add(system); - } - - return this; - - /** - * 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. - */ - } - - /** - * Renders the object to its webGL view - * - * @param {PIXI.DisplayObject} displayObject - the object to be rendered - * @param {PIXI.RenderTexture} renderTexture - The render texture to render to. - * @param {boolean} [clear] - Should the canvas be cleared before the new render - * @param {PIXI.Transform} [transform] - A transform to apply to the render texture before rendering. - * @param {boolean} [skipUpdateTransform] - Should we skip the update transform pass? - */ - render(displayObject, renderTexture, clear, transform, skipUpdateTransform) - { - // can be handy to know! - this.renderingToScreen = !renderTexture; - - this.runners.prerender.run(); - this.emit('prerender'); - - // 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.parent; - - displayObject.parent = this._tempDisplayObjectParent; - displayObject.updateTransform(); - displayObject.parent = 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.run(); - - this.emit('postrender'); - } - - /** - * Resizes the webGL view to the specified width and height. - * - * @param {number} screenWidth - the new width of the screen - * @param {number} screenHeight - the new height of the screen - */ - resize(screenWidth, screenHeight) - { - AbstractRenderer.prototype.resize.call(this, screenWidth, screenHeight); - - this.runners.resize.run(screenWidth, screenHeight); - } - - /** - * Resets the WebGL state so you can render things however you fancy! - * - * @return {PIXI.Renderer} Returns itself. - */ - reset() - { - this.runners.reset.run(); - - return this; - } - - clear() - { - this.framebuffer.bind(); - this.framebuffer.clear(); - } - - /** - * Removes everything from the renderer (event listeners, spritebatch, etc...) - * - * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. - * See: https://github.com/pixijs/pixi.js/issues/2233 - */ - destroy(removeView) - { - this.runners.destroy.run(); - - // call base destroy - super.destroy(removeView); - - // TODO nullify all the managers.. - this.gl = null; - } - - /** - * 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 - * builds and excluding plugins. - * @name PIXI.Renderer#plugins - * @type {object} - * @readonly - * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. - * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. - * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. - * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. - */ - - /** - * Adds a plugin to the renderer. - * - * @method - * @param {string} pluginName - The name of the plugin. - * @param {Function} ctor - The constructor function or class for the plugin. - */ - static registerPlugin(pluginName, ctor) - { - Renderer.__plugins = Renderer.__plugins || {}; - Renderer.__plugins[pluginName] = ctor; - } -} diff --git a/packages/core/src/renderers/State.js b/packages/core/src/renderers/State.js deleted file mode 100644 index fa3f221..0000000 --- a/packages/core/src/renderers/State.js +++ /dev/null @@ -1,163 +0,0 @@ -/* eslint-disable max-len */ - -const BLEND = 0; -const OFFSET = 1; -const CULLING = 2; -const DEPTH_TEST = 3; -const WINDING = 4; - -/** - * This is a webGL state. It is passed The WebGL StateManager. - * Each mesh renderered may require webGL to be in a different state. - * For example you may want different blend mode or to enable polygon offsets - * - * @class - * @memberof PIXI - */ -export default class State -{ - /** - * - */ - constructor() - { - this.data = 0; - - this.blendMode = 0; - this.polygonOffset = 0; - - this.blend = true; - // this.depthTest = true; - } - - /** - * Activates blending of the computed fragment color values - * - * @member {boolean} - */ - get blend() - { - return !!(this.data & (1 << BLEND)); - } - - set blend(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << BLEND)) !== value) - { - this.data ^= (1 << BLEND); - } - } - - /** - * Activates adding an offset to depth values of polygon's fragments - * - * @member {boolean} - * @default false - */ - get offsets() - { - return !!(this.data & (1 << OFFSET)); - } - - set offsets(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << OFFSET)) !== value) - { - this.data ^= (1 << OFFSET); - } - } - - /** - * Activates culling of polygons. - * - * @member {boolean} - * @default false - */ - get culling() - { - return !!(this.data & (1 << CULLING)); - } - - set culling(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << CULLING)) !== value) - { - this.data ^= (1 << CULLING); - } - } - - /** - * Activates depth comparisons and updates to the depth buffer. - * - * @member {boolean} - * @default false - */ - get depthTest() - { - return !!(this.data & (1 << DEPTH_TEST)); - } - - set depthTest(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << DEPTH_TEST)) !== value) - { - this.data ^= (1 << DEPTH_TEST); - } - } - - /** - * Specifies whether or not front or back-facing polygons can be culled. - * @member {boolean} - * @default false - */ - get clockwiseFrontFace() - { - return !!(this.data & (1 << WINDING)); - } - - set clockwiseFrontFace(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << WINDING)) !== value) - { - this.data ^= (1 << WINDING); - } - } - - /** - * The blend mode to be applied when this state is set. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. - * Setting this mode to anything other than NO_BLEND will automatically switch blending on. - * - * @member {boolean} - * @default PIXI.BLEND_MODES.NORMAL - * @see PIXI.BLEND_MODES - */ - get blendMode() - { - return this._blendMode; - } - - set blendMode(value) // eslint-disable-line require-jsdoc - { - // 17 is NO BLEND - this.blend = (value !== 17); - this._blendMode = value; - } - - /** - * The polygon offset. Setting this property to anything other than 0 will automatically enable poygon offset fill. - * - * @member {number} - * @default 0 - */ - get polygonOffset() - { - return this._polygonOffset; - } - - set polygonOffset(value) // eslint-disable-line require-jsdoc - { - this.offsets = !!value; - this._polygonOffset = value; - } -} - diff --git a/packages/core/src/renderers/filters/Filter.js b/packages/core/src/renderers/filters/Filter.js deleted file mode 100644 index 2073b8d..0000000 --- a/packages/core/src/renderers/filters/Filter.js +++ /dev/null @@ -1,140 +0,0 @@ -import Shader from '../../shader/Shader'; -import Program from '../../shader/Program'; -import State from '../State'; -import { settings } from '@pixi/settings'; -// import extractUniformsFromSrc from './extractUniformsFromSrc'; -import defaultVertex from './defaultFilter.vert'; -import defaultFragment from './defaultFilter.frag'; - -// let math = require('../../math'); -/** - * @class - * @memberof PIXI - * @extends PIXI.Shader - */ -export default class Filter extends Shader -{ - /** - * @param {string} [vertexSrc] - The source of the vertex shader. - * @param {string} [fragmentSrc] - The source of the fragment shader. - * @param {object} [uniforms] - Custom uniforms to use to augment the built-in ones. - */ - constructor(vertexSrc, fragmentSrc, uniforms) - { - const program = Program.from(vertexSrc, fragmentSrc); - - super(program, uniforms); - - /** - * The padding of the filter. Some filters require extra space to breath such as a blur. - * Increasing this will add extra width and height to the bounds of the object that the - * filter is applied to. - * - * @member {number} - */ - this.padding = 0; - - /** - * The resolution of the filter. Setting this to be lower will lower the quality but - * increase the performance of the filter. - * - * @member {number} - */ - this.resolution = settings.RESOLUTION; - - /** - * If enabled is true the filter is applied, if false it will not. - * - * @member {boolean} - */ - this.enabled = true; - - /** - * If enabled, PixiJS will fit the filter area into boundaries for better performance. - * Switch it off if it does not work for specific shader. - * - * @member {boolean} - */ - this.autoFit = true; - - /** - * Legacy filters use position and uvs from attributes - * @member {boolean} - * @readonly - */ - this.legacy = !!this.program.attributeData.aTextureCoord; - - /** - * the webGL state the filter requires to render - * @member {PIXI.State} - */ - this.state = new State(); - } - - /** - * Applies the filter - * - * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from - * @param {PIXI.RenderTarget} input - The input render target. - * @param {PIXI.RenderTarget} output - The target to output to. - * @param {boolean} clear - Should the output be cleared before rendering to it - * @param {object} [currentState] - It's current state of filter. - * There are some useful properties in the currentState : - * target, filters, sourceFrame, destinationFrame, renderTarget, resolution - */ - apply(filterManager, input, output, clear, currentState, derp) // eslint-disable-line no-unused-vars - { - // do as you please! - - filterManager.applyFilter(this, input, output, clear, currentState, derp); - - // or just do a regular render.. - } - - /** - * Sets the blendmode of the filter - * - * @member {number} - * @default PIXI.BLEND_MODES.NORMAL - */ - get blendMode() - { - return this.state.blendMode; - } - - set blendMode(value) // eslint-disable-line require-jsdoc - { - this.state.blendMode = value; - } - - /** - * The default vertex shader source - * - * @static - * @constant - */ - static get defaultVertexSrc() - { - return defaultVertex; - } - - /** - * The default fragment shader source - * - * @static - * @constant - */ - static get defaultFragmentSrc() - { - return defaultFragment; - } -} - -/** - * Used for caching shader IDs - * - * @static - * @private - */ -Filter.SOURCE_KEY_MAP = {}; - diff --git a/packages/core/src/renderers/filters/defaultFilter.frag b/packages/core/src/renderers/filters/defaultFilter.frag deleted file mode 100644 index a14c1be..0000000 --- a/packages/core/src/renderers/filters/defaultFilter.frag +++ /dev/null @@ -1,22 +0,0 @@ -varying vec2 vTextureCoord; -varying vec2 vFilterCoord; - -uniform sampler2D uSampler; -uniform sampler2D filterSampler; - -void main(void){ - vec4 masky = texture2D(filterSampler, vFilterCoord); - vec4 sample = texture2D(uSampler, vTextureCoord); - vec4 color; - if(mod(vFilterCoord.x, 1.0) > 0.5) - { - color = vec4(1.0, 0.0, 0.0, 1.0); - } - else - { - color = vec4(0.0, 1.0, 0.0, 1.0); - } - // gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0); - gl_FragColor = mix(sample, masky, 0.5); - gl_FragColor *= sample.a; -} diff --git a/packages/core/src/renderers/filters/defaultFilter.vert b/packages/core/src/renderers/filters/defaultFilter.vert deleted file mode 100644 index e1febca..0000000 --- a/packages/core/src/renderers/filters/defaultFilter.vert +++ /dev/null @@ -1,14 +0,0 @@ -attribute vec2 aVertexPosition; -attribute vec2 aTextureCoord; - -uniform mat3 projectionMatrix; -uniform mat3 filterMatrix; - -varying vec2 vTextureCoord; -varying vec2 vFilterCoord; - -void main(void){ - gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); - vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy; - vTextureCoord = aTextureCoord ; -} diff --git a/packages/core/src/AbstractRenderer.js b/packages/core/src/AbstractRenderer.js new file mode 100644 index 0000000..64b2eb1 --- /dev/null +++ b/packages/core/src/AbstractRenderer.js @@ -0,0 +1,353 @@ +import { hex2string, hex2rgb } from '@pixi/utils'; +import { Matrix, Rectangle } from '@pixi/math'; +import { RENDERER_TYPE } from '@pixi/constants'; +import { settings } from '@pixi/settings'; +import { Container } from '@pixi/display'; +import RenderTexture from './renderTexture/RenderTexture'; +import EventEmitter from 'eventemitter3'; + +const tempMatrix = new Matrix(); + +/** + * 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 EventEmitter + * @memberof PIXI + */ +export default class AbstractRenderer extends EventEmitter +{ + // eslint-disable-next-line valid-jsdoc + /** + * @param {string} system - The name of the system this renderer is for. + * @param {object} [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.transparent=false] - If the render view is transparent, default false + * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false + * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. The + * resolution of the renderer retina would be 2. + * @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 {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when rendering, + * stopping pixel interpolation. + */ + constructor(system, options, arg2, arg3) + { + super(); + + // Support for constructor(system, screenWidth, screenHeight, options) + if (typeof options === 'number') + { + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + }, arg3); + } + + // 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 = RENDERER_TYPE.UNKNOWN; + + /** + * Measurements of the screen. (0, 0, screenWidth, screenHeight) + * + * Its safe to use as filterArea or hitArea for 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 1 + */ + this.resolution = options.resolution || settings.RESOLUTION; + + /** + * Whether the render view is transparent + * + * @member {boolean} + */ + this.transparent = options.transparent; + + /** + * Whether css dimensions of canvas view should be resized to screen dimensions automatically + * + * @member {boolean} + */ + this.autoResize = options.autoResize || false; + + /** + * Tracks the blend modes useful for this renderer. + * + * @member {object} + */ + this.blendModes = null; + + /** + * 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; + + /** + * If true PixiJS will Math.floor() x/y values when rendering, stopping pixel interpolation. + * Handy for crisp pixel art and speed on legacy devices. + * + * @member {boolean} + */ + this.roundPixels = options.roundPixels; + + /** + * The background color as a number. + * + * @member {number} + * @private + */ + this._backgroundColor = 0x000000; + + /** + * The background color as an [R, G, B] array. + * + * @member {number[]} + * @private + */ + this._backgroundColorRgba = [0, 0, 0, 0]; + + /** + * The background color as a string. + * + * @member {string} + * @private + */ + this._backgroundColorString = '#000000'; + + this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter + + /** + * This temporary display object used as the parent of the currently being rendered item + * + * @member {PIXI.DisplayObject} + * @private + */ + this._tempDisplayObjectParent = new Container(); + + /** + * The last root object that the renderer tried to render. + * + * @member {PIXI.DisplayObject} + * @private + */ + this._lastObjectRendered = this._tempDisplayObjectParent; + + /** + * Collection of plugins. + * @readonly + * @member {object} + */ + this.plugins = {}; + } + + /** + * Initialize the plugins. + * + * @protected + * @param {object} staticMap - The dictionary of staticly saved plugins. + */ + initPlugins(staticMap) + { + 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() + { + return this.view.width; + } + + /** + * Same as view.height, actual number of pixels in the canvas by vertical + * + * @member {number} + * @readonly + * @default 600 + */ + get height() + { + return this.view.height; + } + + /** + * Resizes the screen and canvas to the specified width and height + * Canvas dimensions are multiplied by resolution + * + * @param {number} screenWidth - the new width of the screen + * @param {number} screenHeight - the new height of the screen + */ + resize(screenWidth, screenHeight) + { + this.screen.width = screenWidth; + this.screen.height = screenHeight; + + this.view.width = screenWidth * this.resolution; + this.view.height = screenHeight * this.resolution; + + if (this.autoResize) + { + this.view.style.width = `${screenWidth}px`; + this.view.style.height = `${screenHeight}px`; + } + } + + /** + * 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 {PIXI.DisplayObject} displayObject - The displayObject the object will be generated from + * @param {number} scaleMode - Should be one of the scaleMode consts + * @param {number} resolution - The resolution / device pixel ratio of the texture being generated + * @param {PIXI.Rectangle} [region] - The region of the displayObject, that shall be rendered, + * if no region is specified, defaults to the local bounds of the displayObject. + * @return {PIXI.Texture} a texture of the graphics object + */ + generateTexture(displayObject, scaleMode, resolution, region) + { + region = region || displayObject.getLocalBounds(); + + // 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(region.width | 0, region.height | 0, scaleMode, resolution); + + tempMatrix.tx = -region.x; + tempMatrix.ty = -region.y; + + this.render(displayObject, renderTexture, false, tempMatrix, true); + + return renderTexture; + } + + /** + * Removes everything from the renderer and optionally removes the Canvas DOM element. + * + * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. + */ + destroy(removeView) + { + 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); + } + + this.plugins = null; + + this.type = RENDERER_TYPE.UNKNOWN; + + this.view = null; + + this.screen = null; + + this.resolution = 0; + + this.transparent = false; + + this.autoResize = false; + + this.blendModes = null; + + this.options = null; + + this.preserveDrawingBuffer = false; + this.clearBeforeRender = false; + + this.roundPixels = false; + + this._backgroundColor = 0; + this._backgroundColorRgba = null; + this._backgroundColorString = null; + + this._tempDisplayObjectParent = null; + this._lastObjectRendered = null; + } + + /** + * The background color to fill if not transparent + * + * @member {number} + */ + get backgroundColor() + { + return this._backgroundColor; + } + + set backgroundColor(value) // eslint-disable-line require-jsdoc + { + this._backgroundColor = value; + this._backgroundColorString = hex2string(value); + hex2rgb(value, this._backgroundColorRgba); + } +} diff --git a/packages/core/src/Renderer.js b/packages/core/src/Renderer.js new file mode 100644 index 0000000..2227e78 --- /dev/null +++ b/packages/core/src/Renderer.js @@ -0,0 +1,338 @@ +import AbstractRenderer from './AbstractRenderer'; +import { sayHello } from '@pixi/utils'; +import MaskSystem from './mask/MaskSystem'; +import StencilSystem from './mask/StencilSystem'; +import FilterSystem from './filters/FilterSystem'; +import FramebufferSystem from './framebuffer/FramebufferSystem'; +import RenderTextureSystem from './renderTexture/RenderTextureSystem'; +import TextureSystem from './textures/TextureSystem'; +import ProjectionSystem from './projection/ProjectionSystem'; +import StateSystem from './state/StateSystem'; +import GeometrySystem from './geometry/GeometrySystem'; +import ShaderSystem from './shader/ShaderSystem'; +import ContextSystem from './context/ContextSystem'; +import BatchSystem from './batch/BatchSystem'; +import TextureGCSystem from './textures/TextureGCSystem'; +import { RENDERER_TYPE } from '@pixi/constants'; +import UniformGroup from './shader/UniformGroup'; +import { Matrix } from '@pixi/math'; +import Runner from 'mini-runner'; + +/** + * 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 Render works by automatically managing webGLBatchs. + * 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 :) + * + * @class + * @memberof PIXI + * @extends PIXI.AbstractRenderer + */ +export default class Renderer extends AbstractRenderer +{ + // eslint-disable-next-line valid-jsdoc + /** + * + * @param {object} [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.transparent=false] - If the render view is transparent, default false + * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false + * @param {boolean} [options.antialias=false] - sets antialias. If not available natively then FXAA + * antialiasing is used + * @param {boolean} [options.forceFXAA=false] - forces FXAA antialiasing to be used over native. + * FXAA is faster, but may not always look as great + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. + * The resolution of the renderer retina would be 2. + * @param {boolean} [options.clearBeforeRender=true] - This sets if the renderer will clear + * the canvas or not before the new render pass. If you wish to set this to false, you *must* set + * preserveDrawingBuffer to `true`. + * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, + * enable this if you need to call toDataUrl on the webgl context. + * @param {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when + * rendering, stopping pixel interpolation. + * @param {number} [options.backgroundColor=0x000000] - The background color of the rendered area + * (shown if not transparent). + * @param {boolean} [options.legacy=false] - If true PixiJS will aim to ensure compatibility + * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. + * @param {string} [options.powerPreference] - Parameter passed to webgl context, set to "high-performance" + * for devices with dual graphics card + */ + constructor(options, arg2, arg3) + { + super('WebGL', options, arg2, arg3); + + /** + * The type of this renderer as a standardised const + * + * @member {number} + * @see PIXI.RENDERER_TYPE + */ + this.type = RENDERER_TYPE.WEBGL; + + // this will be set by the contextSystem (this.context) + this.gl = null; + this.CONTEXT_UID = 0; + this.legacy = !!options.legacy; + + // TODO legacy! + + // runners! + this.runners = { + destroy: new Runner('destroy'), + contextChange: new Runner('contextChange', 1), + reset: new Runner('reset'), + update: new Runner('update'), + postrender: new Runner('postrender'), + prerender: new Runner('prerender'), + resize: new Runner('resize', 2), + }; + + 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(GeometrySystem, 'geometry') + .addSystem(FramebufferSystem, 'framebuffer') + .addSystem(StencilSystem, 'stencil') + .addSystem(ProjectionSystem, 'projection') + .addSystem(TextureGCSystem, 'textureGC') + .addSystem(FilterSystem, 'filter') + .addSystem(RenderTextureSystem, 'renderTexture') + .addSystem(BatchSystem, 'batch'); + + this.initPlugins(Renderer.__plugins); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ + if (options.context) + { + this.context.initFromContext(options.context); + } + else + { + this.context.initFromOptions({ + alpha: this.transparent, + antialias: options.antialias, + premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', + stencil: true, + 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); + } + + /** + * Add a new system to the renderer. + * @param {class} ClassRef - Class reference + * @param {string} [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. + * @return {PIXI.Renderer} Return instance of renderer + */ + addSystem(ClassRef, name) + { + if (!name) + { + name = ClassRef.name; + } + + // TODO - read name from class.name.. + + /* + if(name.includes('System')) + { + name = name.replace('System', ''); + name = name.charAt(0).toLowerCase() + name.slice(1); + } + */ + + const system = new ClassRef(this); + + if (this[name]) + { + throw new Error(`Whoops! ${name} is already a manger`); + } + + this[name] = system; + + for (const i in this.runners) + { + this.runners[i].add(system); + } + + return this; + + /** + * 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. + */ + } + + /** + * Renders the object to its webGL view + * + * @param {PIXI.DisplayObject} displayObject - the object to be rendered + * @param {PIXI.RenderTexture} renderTexture - The render texture to render to. + * @param {boolean} [clear] - Should the canvas be cleared before the new render + * @param {PIXI.Transform} [transform] - A transform to apply to the render texture before rendering. + * @param {boolean} [skipUpdateTransform] - Should we skip the update transform pass? + */ + render(displayObject, renderTexture, clear, transform, skipUpdateTransform) + { + // can be handy to know! + this.renderingToScreen = !renderTexture; + + this.runners.prerender.run(); + this.emit('prerender'); + + // 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.parent; + + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = 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.run(); + + this.emit('postrender'); + } + + /** + * Resizes the webGL view to the specified width and height. + * + * @param {number} screenWidth - the new width of the screen + * @param {number} screenHeight - the new height of the screen + */ + resize(screenWidth, screenHeight) + { + AbstractRenderer.prototype.resize.call(this, screenWidth, screenHeight); + + this.runners.resize.run(screenWidth, screenHeight); + } + + /** + * Resets the WebGL state so you can render things however you fancy! + * + * @return {PIXI.Renderer} Returns itself. + */ + reset() + { + this.runners.reset.run(); + + return this; + } + + clear() + { + this.framebuffer.bind(); + this.framebuffer.clear(); + } + + /** + * Removes everything from the renderer (event listeners, spritebatch, etc...) + * + * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. + * See: https://github.com/pixijs/pixi.js/issues/2233 + */ + destroy(removeView) + { + this.runners.destroy.run(); + + // call base destroy + super.destroy(removeView); + + // TODO nullify all the managers.. + this.gl = null; + } + + /** + * 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 + * builds and excluding plugins. + * @name PIXI.Renderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. + */ + + /** + * Adds a plugin to the renderer. + * + * @method + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + static registerPlugin(pluginName, ctor) + { + Renderer.__plugins = Renderer.__plugins || {}; + Renderer.__plugins[pluginName] = ctor; + } +} diff --git a/packages/core/src/System.js b/packages/core/src/System.js new file mode 100644 index 0000000..05a10c8 --- /dev/null +++ b/packages/core/src/System.js @@ -0,0 +1,42 @@ +/** + * System is a base class used for extending systems used by the {@link PIXI.Renderer} + * @see PIXI.Renderer#addSystem + * @class + * @memberof PIXI + */ +export default class System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + /** + * The renderer this manager works for. + * + * @member {PIXI.Renderer} + */ + this.renderer = renderer; + + this.renderer.runners.contextChange.add(this); + } + + /** + * Generic method called when there is a WebGL context change. + * + */ + contextChange() + { + // do some codes init! + } + + /** + * Generic destroy methods to be overridden by the subclass + * + */ + destroy() + { + this.renderer.runners.contextChange.remove(this); + this.renderer = null; + } +} diff --git a/packages/core/src/batch/BatchSystem.js b/packages/core/src/batch/BatchSystem.js new file mode 100644 index 0000000..b04c249 --- /dev/null +++ b/packages/core/src/batch/BatchSystem.js @@ -0,0 +1,68 @@ +import System from '../System'; +import ObjectRenderer from './ObjectRenderer'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class BatchSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + super(renderer); + + /** + * An empty renderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.emptyRenderer = new ObjectRenderer(renderer); + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.currentRenderer = this.emptyRenderer; + } + + /** + * Changes the current renderer to the one given in parameter + * + * @param {PIXI.ObjectRenderer} objectRenderer - The object renderer to use. + */ + setObjectRenderer(objectRenderer) + { + if (this.currentRenderer === objectRenderer) + { + return; + } + + this.currentRenderer.stop(); + this.currentRenderer = objectRenderer; + + this.renderer.state.setState(objectRenderer.state); + + this.currentRenderer.start(); + } + + /** + * This should be called if you wish to do some custom rendering + * It will basically render anything that may be batched up such as sprites + * + */ + flush() + { + this.setObjectRenderer(this.emptyRenderer); + } + + reset() + { + this.setObjectRenderer(this.emptyRenderer); + } +} diff --git a/packages/core/src/batch/ObjectRenderer.js b/packages/core/src/batch/ObjectRenderer.js new file mode 100644 index 0000000..0c851a9 --- /dev/null +++ b/packages/core/src/batch/ObjectRenderer.js @@ -0,0 +1,48 @@ +import System from '../System'; + +/** + * Base for a common object renderer that can be used as a system renderer plugin. + * + * @class + * @extends PIXI.System + * @memberof PIXI + */ +export default class ObjectRenderer extends System +{ + /** + * Starts the renderer and sets the shader + * + */ + start() + { + // set the shader.. + } + + /** + * Stops the renderer + * + */ + stop() + { + this.flush(); + } + + /** + * Stub method for rendering content and emptying the current batch. + * + */ + flush() + { + // flush! + } + + /** + * Renders an object + * + * @param {PIXI.DisplayObject} object - The object to render. + */ + render(object) // eslint-disable-line no-unused-vars + { + // render the object + } +} diff --git a/packages/core/src/context/ContextSystem.js b/packages/core/src/context/ContextSystem.js new file mode 100644 index 0000000..301aab4 --- /dev/null +++ b/packages/core/src/context/ContextSystem.js @@ -0,0 +1,186 @@ +import System from '../System'; +import { settings } from '@pixi/settings'; + +let CONTEXT_UID = 0; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class ContextSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.webGLVersion = 1; + + this.handleContextLost = this.handleContextLost.bind(this); + this.handleContextRestored = this.handleContextRestored.bind(this); + + this.extensions = {}; + + renderer.view.addEventListener('webglcontextlost', this.handleContextLost, false); + renderer.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + } + + get isLost() + { + return (!this.gl || this.gl.isContextLost()); + } + + contextChange(gl) + { + this.gl = gl; + + // restore a context if it was previously lost + if (gl.isContextLost() && gl.getExtension('WEBGL_lose_context')) + { + gl.getExtension('WEBGL_lose_context').restoreContext(); + } + } + + initFromContext(gl) + { + this.gl = gl; + this.validateContext(gl); + this.renderer.gl = gl; + this.renderer.CONTEXT_UID = CONTEXT_UID++; + this.renderer.runners.contextChange.run(gl); + } + + initFromOptions(options) + { + const gl = this.createContext(this.renderer.view, options); + + this.initFromContext(gl); + } + + /** + * Helper class to create a webGL Context + * + * @class + * @memberof PIXI.glCore + * @param canvas {HTMLCanvasElement} the canvas element that we will get the context from + * @param options {Object} An options object that gets passed in to the canvas element containing the context attributes, + * see https://developer.mozilla.org/en/docs/Web/API/HTMLCanvasElement/getContext for the options available + * @return {WebGLRenderingContext} the WebGL context + */ + createContext(canvas, options) + { + let gl; + + if (settings.PREFER_WEBGL_2) + { + gl = canvas.getContext('webgl2', options); + } + + if (gl) + { + this.webGLVersion = 2; + } + else + { + this.webGLVersion = 1; + + gl = canvas.getContext('webgl', options) + || canvas.getContext('experimental-webgl', options); + + if (!gl) + { + // fail, not able to get a context + throw new Error('This browser does not support webGL. Try using the canvas renderer'); + } + } + + this.gl = gl; + + this.getExtensions(); + + return gl; + } + + getExtensions() + { + // time to set up default etensions that pixi uses.. + const gl = this.gl; + const extensions = this.extensions; + + if (this.webGLVersion === 1) + { + extensions.drawBuffers = gl.getExtension('WEBGL_draw_buffers'); + extensions.depthTexture = gl.getExtension('WEBKIT_WEBGL_depth_texture'); + extensions.floatTexture = gl.getExtension('OES_texture_float'); + extensions.loseContext = gl.getExtension('WEBGL_lose_context'); + + extensions.vertexArrayObject = gl.getExtension('OES_vertex_array_object') + || gl.getExtension('MOZ_OES_vertex_array_object') + || gl.getExtension('WEBKIT_OES_vertex_array_object'); + } + + // we don't use any specific WebGL 2 ones yet! + } + + /** + * Handles a lost webgl context + * + * @private + * @param {WebGLContextEvent} event - The context lost event. + */ + handleContextLost(event) + { + event.preventDefault(); + } + + /** + * Handles a restored webgl context + * + * @private + */ + handleContextRestored() + { + this.renderer.runners.contextChange.run(this.gl); + } + + destroy() + { + const view = this.renderer.view; + + // remove listeners + view.removeEventListener('webglcontextlost', this.handleContextLost); + view.removeEventListener('webglcontextrestored', this.handleContextRestored); + + this.gl.useProgram(null); + + if (this.extensions.loseContext) + { + this.extensions.loseContext.loseContext(); + } + } + + postrender() + { + this.gl.flush(); + } + + validateContext(gl) + { + const attributes = gl.getContextAttributes(); + + // this is going to be fairly simple for now.. but at least we have room to grow! + if (!attributes.stencil) + { + /* eslint-disable max-len */ + + /* eslint-disable no-console */ + console.warn('Provided WebGL context does not have a stencil buffer, masks may not render correctly'); + /* eslint-enable no-console */ + + /* eslint-enable max-len */ + } + } +} diff --git a/packages/core/src/filters/Filter.js b/packages/core/src/filters/Filter.js new file mode 100644 index 0000000..9d8cad8 --- /dev/null +++ b/packages/core/src/filters/Filter.js @@ -0,0 +1,140 @@ +import Shader from '../shader/Shader'; +import Program from '../shader/Program'; +import State from '../state/State'; +import { settings } from '@pixi/settings'; +// import extractUniformsFromSrc from './extractUniformsFromSrc'; +import defaultVertex from './defaultFilter.vert'; +import defaultFragment from './defaultFilter.frag'; + +// let math = require('../../math'); +/** + * @class + * @memberof PIXI + * @extends PIXI.Shader + */ +export default class Filter extends Shader +{ + /** + * @param {string} [vertexSrc] - The source of the vertex shader. + * @param {string} [fragmentSrc] - The source of the fragment shader. + * @param {object} [uniforms] - Custom uniforms to use to augment the built-in ones. + */ + constructor(vertexSrc, fragmentSrc, uniforms) + { + const program = Program.from(vertexSrc, fragmentSrc); + + super(program, uniforms); + + /** + * The padding of the filter. Some filters require extra space to breath such as a blur. + * Increasing this will add extra width and height to the bounds of the object that the + * filter is applied to. + * + * @member {number} + */ + this.padding = 0; + + /** + * The resolution of the filter. Setting this to be lower will lower the quality but + * increase the performance of the filter. + * + * @member {number} + */ + this.resolution = settings.RESOLUTION; + + /** + * If enabled is true the filter is applied, if false it will not. + * + * @member {boolean} + */ + this.enabled = true; + + /** + * If enabled, PixiJS will fit the filter area into boundaries for better performance. + * Switch it off if it does not work for specific shader. + * + * @member {boolean} + */ + this.autoFit = true; + + /** + * Legacy filters use position and uvs from attributes + * @member {boolean} + * @readonly + */ + this.legacy = !!this.program.attributeData.aTextureCoord; + + /** + * the webGL state the filter requires to render + * @member {PIXI.State} + */ + this.state = new State(); + } + + /** + * Applies the filter + * + * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + * @param {boolean} clear - Should the output be cleared before rendering to it + * @param {object} [currentState] - It's current state of filter. + * There are some useful properties in the currentState : + * target, filters, sourceFrame, destinationFrame, renderTarget, resolution + */ + apply(filterManager, input, output, clear, currentState, derp) // eslint-disable-line no-unused-vars + { + // do as you please! + + filterManager.applyFilter(this, input, output, clear, currentState, derp); + + // or just do a regular render.. + } + + /** + * Sets the blendmode of the filter + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL + */ + get blendMode() + { + return this.state.blendMode; + } + + set blendMode(value) // eslint-disable-line require-jsdoc + { + this.state.blendMode = value; + } + + /** + * The default vertex shader source + * + * @static + * @constant + */ + static get defaultVertexSrc() + { + return defaultVertex; + } + + /** + * The default fragment shader source + * + * @static + * @constant + */ + static get defaultFragmentSrc() + { + return defaultFragment; + } +} + +/** + * Used for caching shader IDs + * + * @static + * @private + */ +Filter.SOURCE_KEY_MAP = {}; + diff --git a/packages/core/src/filters/FilterSystem.js b/packages/core/src/filters/FilterSystem.js new file mode 100644 index 0000000..3174318 --- /dev/null +++ b/packages/core/src/filters/FilterSystem.js @@ -0,0 +1,431 @@ +import System from '../System'; + +import RenderTexture from '../renderTexture/RenderTexture'; +import Quad from '../utils/Quad'; +import QuadUv from '../utils/QuadUv'; +import { Rectangle } from '@pixi/math'; +import * as filterTransforms from './filterTransforms'; +import bitTwiddle from 'bit-twiddle'; +import UniformGroup from '../shader/UniformGroup'; +import { DRAW_MODES } from '@pixi/constants'; + +// +/** + * @ignore + * @class + */ +class FilterState +{ + /** + * + */ + constructor() + { + this.renderTexture = null; + this.sourceFrame = new Rectangle(); + this.destinationFrame = new Rectangle(); + this.filters = []; + this.target = null; + this.legacy = false; + this.resolution = 1; + } +} + +/** + * @class + * @memberof PIXI.systems + * @extends PIXI.System + */ +export default class FilterSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + /** + * stores a bunch of PO2 textures used for filtering + * @type {Object} + */ + this.texturePool = {}; + + /** + * a pool for storing filter states, save us creating new ones each tick + * @type {Array} + */ + this.statePool = []; + + /** + * A very simple geometry used when drawing a filter effect to the screen + * @type {Quad} + */ + this.quad = new Quad(); + + this.quadUv = new QuadUv(); + + /** + * Temporary rect for maths + * @type {PIXI.Rectangle} + */ + this.tempRect = new Rectangle(); + + this.activeState = {}; + + /** + * this uniform group is attached to filter uniforms when used + * @type {UniformGroup} + */ + this.globalUniforms = new UniformGroup({ + sourceFrame: this.tempRect, + destinationFrame: this.tempRect, + + // legacy variables + filterArea: new Float32Array(4), + filterClamp: new Float32Array(4), + }, true); + } + + /** + * Adds a new filter to the System. + * + * @param {PIXI.DisplayObject} target - The target of the filter to render. + * @param {PIXI.Filter[]} filters - The filters to apply. + */ + push(target, filters) + { + const renderer = this.renderer; + const filterStack = this.renderer.renderTexture.defaultFilterStack; + const state = this.statePool.pop() || new FilterState(); + + let resolution = filters[0].resolution; + let padding = filters[0].padding; + let autoFit = filters[0].autoFit; + let legacy = filters[0].legacy; + + for (let i = 1; i < filters.length; i++) + { + const filter = filters[i]; + + // lets use the lowest resolution.. + resolution = Math.min(resolution, filter.resolution); + // and the largest amount of padding! + padding = Math.max(padding, filter.padding); + // only auto fit if all filters are autofit + autoFit = autoFit || filter.autoFit; + + legacy = legacy || filter.legacy; + } + + filterStack.push(state); + + state.resolution = resolution; + + state.legacy = legacy; + + // round to whole number based on resolution + // TODO move that to the shader too? + state.sourceFrame = target.filterArea || target.getBounds(true); + + state.sourceFrame.pad(padding); + + if (autoFit) + { + state.sourceFrame.fit(this.renderer.renderTexture.destinationFrame); + } + + state.sourceFrame.round(resolution); + + state.renderTexture = this.getPotFilterTexture(state.sourceFrame.width, state.sourceFrame.height, resolution); + state.filters = filters; + + state.destinationFrame.width = state.renderTexture.width; + state.destinationFrame.height = state.renderTexture.height; + + state.renderTexture.filterFrame = state.sourceFrame; + renderer.renderTexture.bind(state.renderTexture, state.sourceFrame);// /, state.destinationFrame); + renderer.renderTexture.clear(); + } + + /** + * Pops off the filter and applies it. + * + */ + pop() + { + const renderer = this.renderer; + const filterStack = renderer.renderTexture.defaultFilterStack; + const state = filterStack.pop(); + const filters = state.filters; + + this.activeState = state; + + const globalUniforms = this.globalUniforms.uniforms; + + globalUniforms.sourceFrame = state.sourceFrame; + globalUniforms.destinationFrame = state.destinationFrame; + globalUniforms.resolution = state.resolution; + + // only update the rect if its legacy.. + if (state.legacy) + { + const filterArea = globalUniforms.filterArea; + const filterClamp = globalUniforms.filterClamp; + + filterArea[0] = state.destinationFrame.width; + filterArea[1] = state.destinationFrame.height; + filterArea[2] = state.sourceFrame.x; + filterArea[3] = state.sourceFrame.y; + + filterClamp[0] = 0.5 / state.resolution / state.destinationFrame.width; + filterClamp[1] = 0.5 / state.resolution / state.destinationFrame.height; + filterClamp[2] = (state.sourceFrame.width - 0.5) / state.resolution / state.destinationFrame.width; + filterClamp[3] = (state.sourceFrame.height - 0.5) / state.resolution / state.destinationFrame.height; + } + + this.globalUniforms.update(); + + const lastState = filterStack[filterStack.length - 1]; + + if (filters.length === 1) + { + filters[0].apply(this, state.renderTexture, lastState.renderTexture, false, state); + + this.returnFilterTexture(state.renderTexture); + } + else + { + let flip = state.renderTexture; + let flop = this.getPotFilterTexture( + flip.width, + flip.height, + state.resolution + ); + + flop.filterFrame = flip.filterFrame; + + let i = 0; + + for (i = 0; i < filters.length - 1; ++i) + { + filters[i].apply(this, flip, flop, true, state); + + const t = flip; + + flip = flop; + flop = t; + } + + filters[i].apply(this, flip, lastState.renderTexture, false, state); + + this.returnFilterTexture(flip); + this.returnFilterTexture(flop); + } + + this.statePool.push(state); + } + + /** + * Draws a filter. + * + * @param {PIXI.Filter} filter - The filter to draw. + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + * @param {boolean} clear - Should the output be cleared before rendering to it + */ + applyFilter(filter, input, output, clear) + { + const renderer = this.renderer; + + renderer.renderTexture.bind(output, output ? output.filterFrame : null); + + if (clear) + { + // gl.disable(gl.SCISSOR_TEST); + renderer.renderTexture.clear(); + // gl.enable(gl.SCISSOR_TEST); + } + + // set the uniforms.. + filter.uniforms.uSampler = input; + filter.uniforms.filterGlobals = this.globalUniforms; + + // TODO make it so that the order of this does not matter.. + // because it does at the moment cos of global uniforms. + // they need to get resynced + + renderer.state.setState(filter.state); + renderer.shader.bind(filter); + + if (filter.legacy) + { + this.quadUv.map(input._frame, input.filterFrame); + + renderer.geometry.bind(this.quadUv); + renderer.geometry.draw(DRAW_MODES.TRIANGLES); + } + else + { + renderer.geometry.bind(this.quad); + renderer.geometry.draw(DRAW_MODES.TRIANGLE_STRIP); + } + } + + /** + * Calculates the mapped matrix. + * + * TODO playing around here.. this is temporary - (will end up in the shader) + * this returns a matrix that will normalise map filter cords in the filter to screen space + * + * @param {PIXI.Matrix} outputMatrix - the matrix to output to. + * @return {PIXI.Matrix} The mapped matrix. + */ + calculateScreenSpaceMatrix(outputMatrix) + { + const currentState = this.activeState; + + return filterTransforms.calculateScreenSpaceMatrix( + outputMatrix, + currentState.sourceFrame, + currentState.destinationFrame + ); + } + + /** + * This will map the filter coord so that a texture can be used based on the transform of a sprite + * + * @param {PIXI.Matrix} outputMatrix - The matrix to output to. + * @param {PIXI.Sprite} sprite - The sprite to map to. + * @return {PIXI.Matrix} The mapped matrix. + */ + calculateSpriteMatrix(outputMatrix, sprite) + { + const currentState = this.activeState; + + return filterTransforms.calculateSpriteMatrix( + outputMatrix, + currentState.sourceFrame, + currentState.destinationFrame, + sprite + ); + } + + /** + * Destroys this Filter System. + * + * @param {boolean} [contextLost=false] context was lost, do not free shaders + * + */ + destroy(contextLost = false) + { + if (!contextLost) + { + this.emptyPool(); + } + else + { + this.texturePool = {}; + } + } + + /** + * Gets a Power-of-Two render texture. + * + * TODO move to a seperate class could be on renderer? + * also - could cause issue with multiple contexts? + * + * @private + * @param {WebGLRenderingContext} gl - The webgl rendering context + * @param {number} minWidth - The minimum width of the render target. + * @param {number} minHeight - The minimum height of the render target. + * @param {number} resolution - The resolution of the render target. + * @return {PIXI.RenderTarget} The new render target. + */ + getPotFilterTexture(minWidth, minHeight, resolution) + { + minWidth = bitTwiddle.nextPow2(minWidth); + minHeight = bitTwiddle.nextPow2(minHeight); + resolution = resolution || 1; + + const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + + if (!this.texturePool[key]) + { + this.texturePool[key] = []; + } + + let renderTexture = this.texturePool[key].pop(); + + if (!renderTexture) + { + // temporary bypass cache.. + // internally - this will cause a texture to be bound.. + renderTexture = RenderTexture.create({ + width: minWidth, + height: minHeight, + resolution, + }); + } + + return renderTexture; + } + + /** + * Gets extra render texture to use inside current filter + * + * @param {number} resolution resolution of the renderTexture + * @returns {PIXI.RenderTexture} + */ + getFilterTexture(resolution) + { + const rt = this.activeState.renderTexture; + + const filterTexture = this.getPotFilterTexture(rt.width, rt.height, resolution || rt.baseTexture.resolution); + + filterTexture.filterFrame = rt.filterFrame; + + return filterTexture; + } + + /** + * Frees a render texture back into the pool. + * + * @param {PIXI.RenderTarget} renderTexture - The renderTarget to free + */ + returnFilterTexture(renderTexture) + { + renderTexture.filterFrame = null; + + const base = renderTexture.baseTexture; + + const minWidth = base.width; + const minHeight = base.height; + + const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + + this.texturePool[key].push(renderTexture); + } + + /** + * Empties the texture pool. + * + */ + emptyPool() + { + for (const i in this.texturePool) + { + const textures = this.texturePool[i]; + + if (textures) + { + for (let j = 0; j < textures.length; j++) + { + textures[j].destroy(true); + } + } + } + + this.texturePool = {}; + } +} diff --git a/packages/core/src/filters/defaultFilter.frag b/packages/core/src/filters/defaultFilter.frag new file mode 100644 index 0000000..a14c1be --- /dev/null +++ b/packages/core/src/filters/defaultFilter.frag @@ -0,0 +1,22 @@ +varying vec2 vTextureCoord; +varying vec2 vFilterCoord; + +uniform sampler2D uSampler; +uniform sampler2D filterSampler; + +void main(void){ + vec4 masky = texture2D(filterSampler, vFilterCoord); + vec4 sample = texture2D(uSampler, vTextureCoord); + vec4 color; + if(mod(vFilterCoord.x, 1.0) > 0.5) + { + color = vec4(1.0, 0.0, 0.0, 1.0); + } + else + { + color = vec4(0.0, 1.0, 0.0, 1.0); + } + // gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0); + gl_FragColor = mix(sample, masky, 0.5); + gl_FragColor *= sample.a; +} diff --git a/packages/core/src/filters/defaultFilter.vert b/packages/core/src/filters/defaultFilter.vert new file mode 100644 index 0000000..e1febca --- /dev/null +++ b/packages/core/src/filters/defaultFilter.vert @@ -0,0 +1,14 @@ +attribute vec2 aVertexPosition; +attribute vec2 aTextureCoord; + +uniform mat3 projectionMatrix; +uniform mat3 filterMatrix; + +varying vec2 vTextureCoord; +varying vec2 vFilterCoord; + +void main(void){ + gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); + vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy; + vTextureCoord = aTextureCoord ; +} diff --git a/packages/core/src/filters/filterTransforms.js b/packages/core/src/filters/filterTransforms.js new file mode 100644 index 0000000..87a3cd6 --- /dev/null +++ b/packages/core/src/filters/filterTransforms.js @@ -0,0 +1,50 @@ +import { Matrix } from '@pixi/math'; + +/** + * Calculates the mapped matrix + * @param filterArea {Rectangle} The filter area + * @param sprite {Sprite} the target sprite + * @param outputMatrix {Matrix} @alvin + * @private + */ +// this returns a matrix that will normalise map filter cords in the filter to screen space +export function calculateScreenSpaceMatrix(outputMatrix, filterArea, textureSize) +{ + // TODO unwrap? + const mappedMatrix = outputMatrix.identity(); + + mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); + + mappedMatrix.scale(textureSize.width, textureSize.height); + + return mappedMatrix; +} + +export function calculateNormalizedScreenSpaceMatrix(outputMatrix, filterArea, textureSize) +{ + const mappedMatrix = outputMatrix.identity(); + + mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); + + const translateScaleX = (textureSize.width / filterArea.width); + const translateScaleY = (textureSize.height / filterArea.height); + + mappedMatrix.scale(translateScaleX, translateScaleY); + + return mappedMatrix; +} + +// this will map the filter coord so that a texture can be used based on the transform of a sprite +export function calculateSpriteMatrix(outputMatrix, filterArea, textureSize, sprite) +{ + const orig = sprite._texture.orig; + const mappedMatrix = outputMatrix.set(textureSize.width, 0, 0, textureSize.height, filterArea.x, filterArea.y); + const worldTransform = sprite.worldTransform.copyTo(Matrix.TEMP_MATRIX); + + worldTransform.invert(); + mappedMatrix.prepend(worldTransform); + mappedMatrix.scale(1.0 / orig.width, 1.0 / orig.height); + mappedMatrix.translate(sprite.anchor.x, sprite.anchor.y); + + return mappedMatrix; +} diff --git a/packages/core/src/filters/spriteMask/SpriteMaskFilter.js b/packages/core/src/filters/spriteMask/SpriteMaskFilter.js new file mode 100644 index 0000000..91697b7 --- /dev/null +++ b/packages/core/src/filters/spriteMask/SpriteMaskFilter.js @@ -0,0 +1,63 @@ +import Filter from '../Filter'; +import { Matrix } from '@pixi/math'; +import vertex from './spriteMaskFilter.vert'; +import fragment from './spriteMaskFilter.frag'; +import { default as TextureMatrix } from '../../textures/TextureMatrix'; + +/** + * The SpriteMaskFilter class + * + * @class + * @extends PIXI.Filter + * @memberof PIXI + */ +export default class SpriteMaskFilter extends Filter +{ + /** + * @param {PIXI.Sprite} sprite - the target sprite + */ + constructor(sprite) + { + const maskMatrix = new Matrix(); + + super(vertex, fragment); + + sprite.renderable = false; + + this.maskSprite = sprite; + this.maskMatrix = maskMatrix; + } + + /** + * Applies the filter + * + * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + */ + apply(filterManager, input, output) + { + const maskSprite = this.maskSprite; + const tex = this.maskSprite.texture; + + if (!tex.valid) + { + return; + } + if (!tex.transform) + { + // margin = 0.0, let it bleed a bit, shader code becomes easier + // assuming that atlas textures were made with 1-pixel padding + tex.transform = new TextureMatrix(tex, 0.0); + } + tex.transform.update(); + + this.uniforms.mask = tex; + this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite) + .prepend(tex.transform.mapCoord); + this.uniforms.alpha = maskSprite.worldAlpha; + this.uniforms.maskClamp = tex.transform.uClampFrame; + + filterManager.applyFilter(this, input, output); + } +} diff --git a/packages/core/src/filters/spriteMask/spriteMaskFilter.frag b/packages/core/src/filters/spriteMask/spriteMaskFilter.frag new file mode 100644 index 0000000..0e4aef8 --- /dev/null +++ b/packages/core/src/filters/spriteMask/spriteMaskFilter.frag @@ -0,0 +1,23 @@ +varying vec2 vMaskCoord; +varying vec2 vTextureCoord; + +uniform sampler2D uSampler; +uniform sampler2D mask; +uniform float alpha; +uniform vec4 maskClamp; + +void main(void) +{ + float clip = step(3.5, + step(maskClamp.x, vMaskCoord.x) + + step(maskClamp.y, vMaskCoord.y) + + step(vMaskCoord.x, maskClamp.z) + + step(vMaskCoord.y, maskClamp.w)); + + vec4 original = texture2D(uSampler, vTextureCoord); + vec4 masky = texture2D(mask, vMaskCoord); + + original *= (masky.r * masky.a * alpha * clip); + + gl_FragColor = original; +} diff --git a/packages/core/src/filters/spriteMask/spriteMaskFilter.vert b/packages/core/src/filters/spriteMask/spriteMaskFilter.vert new file mode 100644 index 0000000..d23a30c --- /dev/null +++ b/packages/core/src/filters/spriteMask/spriteMaskFilter.vert @@ -0,0 +1,16 @@ +attribute vec2 aVertexPosition; +attribute vec2 aTextureCoord; + +uniform mat3 projectionMatrix; +uniform mat3 otherMatrix; + +varying vec2 vMaskCoord; +varying vec2 vTextureCoord; + +void main(void) +{ + gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); + + vTextureCoord = aTextureCoord; + vMaskCoord = ( otherMatrix * vec3( aTextureCoord, 1.0) ).xy; +} diff --git a/packages/core/src/framebuffer/FrameBuffer.js b/packages/core/src/framebuffer/FrameBuffer.js new file mode 100644 index 0000000..84e9fcc --- /dev/null +++ b/packages/core/src/framebuffer/FrameBuffer.js @@ -0,0 +1,106 @@ +import Texture from '../textures/BaseTexture'; +import { FORMATS, TYPES } from '@pixi/constants'; + +/** + * Frame buffer + * @class + * @memberof PIXI + */ +export default class FrameBuffer +{ + constructor(width, height) + { + this.width = width || 100; + this.height = height || 100; + + this.stencil = false; + this.depth = false; + + this.dirtyId = 0; + this.dirtyFormat = 0; + this.dirtySize = 0; + + this.depthTexture = null; + this.colorTextures = []; + + this.glFrameBuffers = {}; + } + + get colorTexture() + { + return this.colorTextures[0]; + } + + addColorTexture(index, texture) + { + // TODO add some validation to the texture - same width / height etc? + this.colorTextures[index || 0] = texture || new Texture(null, { scaleMode: 0, + resolution: 1, + mipmap: false, + width: this.width, + height: this.height });// || new Texture(); + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + addDepthTexture(texture) + { + /* eslint-disable max-len */ + this.depthTexture = texture || new Texture(null, { scaleMode: 0, + resolution: 1, + width: this.width, + height: this.height, + mipmap: false, + format: FORMATS.DEPTH_COMPONENT, + type: TYPES.UNSIGNED_SHORT });// UNSIGNED_SHORT; + /* eslint-disable max-len */ + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + enableDepth() + { + this.depth = true; + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + enableStencil() + { + this.stencil = true; + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + resize(width, height) + { + if (width === this.width && height === this.height) return; + + this.width = width; + this.height = height; + + this.dirtyId++; + this.dirtySize++; + + for (let i = 0; i < this.colorTextures.length; i++) + { + this.colorTextures[i].setSize(width, height); + } + + if (this.depthTexture) + { + this.depthTexture.setSize(width, height); + } + } +} diff --git a/packages/core/src/framebuffer/FramebufferSystem.js b/packages/core/src/framebuffer/FramebufferSystem.js new file mode 100644 index 0000000..2cc2437 --- /dev/null +++ b/packages/core/src/framebuffer/FramebufferSystem.js @@ -0,0 +1,246 @@ +import System from '../System'; +import { Rectangle } from '@pixi/math'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class FramebufferSystem extends System +{ + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + contextChange() + { + this.gl = this.renderer.gl; + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + this.current = null; + this.viewport = new Rectangle(); + + this.drawBufferExtension = this.renderer.context.extensions.drawBuffers; + } + + bind(framebuffer, frame) + { + const gl = this.gl; + + this.current = framebuffer; + + if (framebuffer) + { + // TODO cacheing layer! + + const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID] || this.initFramebuffer(framebuffer); + + gl.bindFramebuffer(gl.FRAMEBUFFER, fbo.framebuffer); + // makesure all textures are unbound.. + + // now check for updates... + if (fbo.dirtyId !== framebuffer.dirtyId) + { + fbo.dirtyId = framebuffer.dirtyId; + + if (fbo.dirtyFormat !== framebuffer.dirtyFormat) + { + fbo.dirtyFormat = framebuffer.dirtyFormat; + this.updateFramebuffer(framebuffer); + } + else if (fbo.dirtySize !== framebuffer.dirtySize) + { + fbo.dirtySize = framebuffer.dirtySize; + this.resizeFramebuffer(framebuffer); + } + } + + for (let i = 0; i < framebuffer.colorTextures.length; i++) + { + if (framebuffer.colorTextures[i].texturePart) + { + this.renderer.texture.unbind(framebuffer.colorTextures[i].texture); + } + else + { + this.renderer.texture.unbind(framebuffer.colorTextures[i]); + } + } + + if (framebuffer.depthTexture) + { + this.renderer.texture.unbind(framebuffer.depthTexture); + } + + if (frame) + { + this.setViewport(frame.x, frame.y, frame.width, frame.height); + } + else + { + this.setViewport(0, 0, framebuffer.width, framebuffer.height); + } + } + else + { + gl.bindFramebuffer(gl.FRAMEBUFFER, null); + + if (frame) + { + this.setViewport(frame.x, frame.y, frame.width, frame.height); + } + else + { + this.setViewport(0, 0, this.renderer.width, this.renderer.height); + } + } + } + + setViewport(x, y, width, height) + { + const v = this.viewport; + + if (v.width !== width || v.height !== height || v.x !== x || v.y !== y) + { + v.x = x; + v.y = y; + v.width = width; + v.height = height; + + this.gl.viewport(x, y, width, height); + } + } + + get size() + { + if (this.current) + { + // TODO store temp + return { x: 0, y: 0, width: this.current.width, height: this.current.height }; + } + + return { x: 0, y: 0, width: this.renderer.width, height: this.renderer.height }; + } + + clear(r, g, b, a) + { + const gl = this.gl; + + // TODO clear color can be set only one right? + gl.clearColor(r, g, b, a); + gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); + } + + // private functions... + + initFramebuffer(framebuffer) + { + const gl = this.gl; + + // TODO - make this a class? + const fbo = { + framebuffer: gl.createFramebuffer(), + stencil: null, + dirtyId: 0, + dirtyFormat: 0, + dirtySize: 0, + }; + + framebuffer.glFrameBuffers[this.CONTEXT_UID] = fbo; + + return fbo; + } + + resizeFramebuffer(framebuffer) + { + const gl = this.gl; + + if (framebuffer.stencil || framebuffer.depth) + { + gl.bindRenderbuffer(gl.RENDERBUFFER, this.stencil); + gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); + } + } + + updateFramebuffer(framebuffer) + { + const gl = this.gl; + + const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID]; + + // bind the color texture + const colorTextures = framebuffer.colorTextures; + + let count = colorTextures.length; + + if (!this.drawBufferExtension) + { + count = Math.min(count, 1); + } + + const activeTextures = []; + + for (let i = 0; i < count; i++) + { + const texture = framebuffer.colorTextures[i]; + + if (texture.texturePart) + { + this.renderer.texture.bind(texture.texture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.COLOR_ATTACHMENT0 + i, + gl.TEXTURE_CUBE_MAP_NEGATIVE_X + texture.side, + texture.texture._glTextures[this.CONTEXT_UID].texture, + 0); + } + else + { + this.renderer.texture.bind(texture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.COLOR_ATTACHMENT0 + i, + gl.TEXTURE_2D, + texture._glTextures[this.CONTEXT_UID].texture, + 0); + } + + activeTextures.push(gl.COLOR_ATTACHMENT0 + i); + } + + if (this.drawBufferExtension && activeTextures.length > 1) + { + this.drawBufferExtension.drawBuffersWEBGL(activeTextures); + } + + if (framebuffer.depthTexture) + { + const depthTextureExt = this.renderer.context.extensions.depthTexture; + + if (depthTextureExt) + { + const depthTexture = framebuffer.depthTexture; + + this.renderer.texture.bind(depthTexture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.DEPTH_ATTACHMENT, + gl.TEXTURE_2D, + depthTexture._glTextures[this.CONTEXT_UID].texture, + 0); + } + } + + if (framebuffer.stencil || framebuffer.depth) + { + fbo.stencil = gl.createRenderbuffer(); + + gl.bindRenderbuffer(gl.RENDERBUFFER, fbo.stencil); + + // TODO.. this is depth AND stencil? + gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, fbo.stencil); + gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); + // fbo.enableStencil(); + } + } +} diff --git a/packages/core/src/geometry/GLBuffer.js b/packages/core/src/geometry/GLBuffer.js new file mode 100644 index 0000000..0477e8a --- /dev/null +++ b/packages/core/src/geometry/GLBuffer.js @@ -0,0 +1,9 @@ +export default class GLBuffer +{ + constructor(buffer) + { + this.buffer = buffer; + this.updateID = -1; + this.byteLength = -1; + } +} diff --git a/packages/core/src/geometry/GeometrySystem.js b/packages/core/src/geometry/GeometrySystem.js new file mode 100644 index 0000000..959f827 --- /dev/null +++ b/packages/core/src/geometry/GeometrySystem.js @@ -0,0 +1,388 @@ +import System from '../System'; +import GLBuffer from './GLBuffer'; + +const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class GeometrySystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this._activeGeometry = null; + this._activeVao = null; + + this.hasVao = true; + this.hasInstance = true; + } + + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + contextChange() + { + const gl = this.gl = this.renderer.gl; + + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + + // webgl2 + if (!gl.createVertexArray) + { + // webgl 1! + let nativeVaoExtension = this.renderer.context.extensions.vertexArrayObject; + + if (this.renderer.legacy) + { + nativeVaoExtension = null; + } + + if (nativeVaoExtension) + { + gl.createVertexArray = () => + nativeVaoExtension.createVertexArrayOES(); + + gl.bindVertexArray = (vao) => + nativeVaoExtension.bindVertexArrayOES(vao); + + gl.deleteVertexArray = (vao) => + nativeVaoExtension.deleteVertexArrayOES(vao); + } + else + { + this.hasVao = false; + gl.createVertexArray = () => + { + // empty + }; + + gl.bindVertexArray = () => + { + // empty + }; + + gl.deleteVertexArray = () => + { + // empty + }; + } + } + + if (!gl.vertexAttribDivisor) + { + const instanceExt = gl.getExtension('ANGLE_instanced_arrays'); + + if (instanceExt) + { + gl.vertexAttribDivisor = (a, b) => + instanceExt.vertexAttribDivisorANGLE(a, b); + + gl.drawElementsInstanced = (a, b, c, d, e) => + instanceExt.drawElementsInstancedANGLE(a, b, c, d, e); + + gl.drawArraysInstanced = (a, b, c, d) => + instanceExt.drawArraysInstancedANGLE(a, b, c, d); + } + else + { + this.hasInstance = false; + } + } + } + + /** + * Binds geometry so that is can be drawn. Creating a Vao if required + * @private + * @param {PIXI.Geometry} geometry instance of geometry to bind + */ + bind(geometry, shader) + { + shader = shader || this.renderer.shader.shader; + + const gl = this.gl; + + // not sure the best way to address this.. + // currently different shaders require different VAOs for the same geometry + // Still mulling over the best way to solve this one.. + // will likely need to modify the shader attribute locations at run time! + let vaos = geometry.glVertexArrayObjects[this.CONTEXT_UID]; + + if (!vaos) + { + geometry.glVertexArrayObjects[this.CONTEXT_UID] = vaos = {}; + } + + const vao = vaos[shader.program.id] || this.initGeometryVao(geometry, shader.program); + + this._activeGeometry = geometry; + + if (this._activeVao !== vao) + { + this._activeVao = vao; + + if (this.hasVao) + { + gl.bindVertexArray(vao); + } + else + { + this.activateVao(geometry, shader.program); + } + } + + // TODO - optimise later! + // don't need to loop through if nothing changed! + // maybe look to add an 'autoupdate' to geometry? + this.updateBuffers(); + } + + reset() + { + this.unbind(); + } + + updateBuffers() + { + const geometry = this._activeGeometry; + const gl = this.gl; + + for (let i = 0; i < geometry.buffers.length; i++) + { + const buffer = geometry.buffers[i]; + + const glBuffer = buffer._glBuffers[this.CONTEXT_UID]; + + if (buffer._updateID !== glBuffer.updateID) + { + glBuffer.updateID = buffer._updateID; + + // TODO can cache this on buffer! maybe added a getter / setter? + const type = buffer.index ? gl.ELEMENT_ARRAY_BUFFER : gl.ARRAY_BUFFER; + const drawType = buffer.static ? gl.STATIC_DRAW : gl.DYNAMIC_DRAW; + + gl.bindBuffer(type, glBuffer.buffer); + + if (glBuffer.byteLength >= buffer.data.byteLength) + { + // offset is always zero for now! + gl.bufferSubData(type, 0, buffer.data); + } + else + { + gl.bufferData(type, buffer.data, drawType); + } + } + } + } + + checkCompatability(geometry, program) + { + // geometry must have at least all the attributes that the shader requires. + const geometryAttributes = geometry.attributes; + const shaderAttributes = program.attributeData; + + for (const j in shaderAttributes) + { + if (!geometryAttributes[j]) + { + throw new Error(`shader and geometry incompatible, geometry missing the "${j}" attribute`); + } + } + } + + /** + * Creates a Vao with the same structure as the geometry and stores it on the geometry. + * @private + * @param {PIXI.Geometry} geometry instance of geometry to to generate Vao for + */ + initGeometryVao(geometry, program) + { + this.checkCompatability(geometry, program); + + const gl = this.gl; + const CONTEXT_UID = this.CONTEXT_UID; + const buffers = geometry.buffers; + const attributes = geometry.attributes; + + const tempStride = {}; + const tempStart = {}; + + for (const j in buffers) + { + tempStride[j] = 0; + tempStart[j] = 0; + } + + for (const j in attributes) + { + if (!attributes[j].size && program.attributeData[j]) + { + attributes[j].size = program.attributeData[j].size; + } + + tempStride[attributes[j].buffer] += attributes[j].size * byteSizeMap[attributes[j].type]; + } + + for (const j in attributes) + { + const attribute = attributes[j]; + const attribSize = attribute.size; + + if (attribute.stride === undefined) + { + if (tempStride[attribute.buffer] === attribSize * byteSizeMap[attribute.type]) + { + attribute.stride = 0; + } + else + { + attribute.stride = tempStride[attribute.buffer]; + } + } + + if (attribute.start === undefined) + { + attribute.start = tempStart[attribute.buffer]; + + tempStart[attribute.buffer] += attribSize * byteSizeMap[attribute.type]; + } + } + + // first update - and create the buffers! + // only create a gl buffer if it actually gets + for (let i = 0; i < buffers.length; i++) + { + const buffer = buffers[i]; + + if (!buffer._glBuffers[CONTEXT_UID]) + { + buffer._glBuffers[CONTEXT_UID] = new GLBuffer(gl.createBuffer()); + } + } + + // TODO - maybe make this a data object? + // lets wait to see if we need to first! + const vao = gl.createVertexArray(); + + gl.bindVertexArray(vao); + + this.activateVao(geometry, program); + + gl.bindVertexArray(null); + + // add it to the cache! + geometry.glVertexArrayObjects[this.CONTEXT_UID][program.id] = vao; + + return vao; + } + + activateVao(geometry, program) + { + const gl = this.gl; + const CONTEXT_UID = this.CONTEXT_UID; + const buffers = geometry.buffers; + const attributes = geometry.attributes; + + if (geometry.indexBuffer) + { + // first update the index buffer if we have one.. + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, geometry.indexBuffer._glBuffers[CONTEXT_UID].buffer); + } + + let lastBuffer = null; + + // add a new one! + for (const j in attributes) + { + const attribute = attributes[j]; + const buffer = buffers[attribute.buffer]; + const glBuffer = buffer._glBuffers[CONTEXT_UID]; + + if (program.attributeData[j]) + { + if (lastBuffer !== glBuffer) + { + gl.bindBuffer(gl.ARRAY_BUFFER, glBuffer.buffer); + + lastBuffer = glBuffer; + } + + const location = program.attributeData[j].location; + + // TODO introduce state again + // we can optimise this for older devices that have no VAOs + gl.enableVertexAttribArray(location); + + gl.vertexAttribPointer(location, + attribute.size, + attribute.type || gl.FLOAT, + attribute.normalized, + attribute.stride, + attribute.start); + + if (attribute.instance) + { + // TODO calculate instance count based of this... + if (this.hasInstance) + { + gl.vertexAttribDivisor(location, 1); + } + else + { + throw new Error('geometry error, GPU Instancing is not supported on this device'); + } + } + } + } + } + + draw(type, size, start, instanceCount) + { + const gl = this.gl; + const geometry = this._activeGeometry; + + // TODO.. this should not change so maybe cache the function? + + if (geometry.indexBuffer) + { + if (geometry.instanced) + { + /* eslint-disable max-len */ + gl.drawElementsInstanced(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2, instanceCount || 1); + /* eslint-enable max-len */ + } + else + { + gl.drawElements(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2); + } + } + else + if (geometry.instanced) + { + // TODO need a better way to calculate size.. + gl.drawArraysInstanced(type, start, size || geometry.getSize(), instanceCount || 1); + } + else + { + gl.drawArrays(type, start, size || geometry.getSize()); + } + + return this; + } + + unbind() + { + this.gl.bindVertexArray(null); + this._activeVao = null; + this._activeGeometry = null; + } +} diff --git a/packages/core/src/geometry/utils/setVertexAttribArrays.js b/packages/core/src/geometry/utils/setVertexAttribArrays.js new file mode 100644 index 0000000..23b6959 --- /dev/null +++ b/packages/core/src/geometry/utils/setVertexAttribArrays.js @@ -0,0 +1,54 @@ +// var GL_MAP = {}; + +/** + * @param gl {WebGLRenderingContext} The current WebGL context + * @param attribs {*} + * @param state {*} + */ +export default function setVertexAttribArrays(gl, attribs, state) +{ + let i; + + if (state) + { + const tempAttribState = state.tempAttribState; + const attribState = state.attribState; + + for (i = 0; i < tempAttribState.length; i++) + { + tempAttribState[i] = false; + } + + // set the new attribs + for (i = 0; i < attribs.length; i++) + { + tempAttribState[attribs[i].attribute.location] = true; + } + + for (i = 0; i < attribState.length; i++) + { + if (attribState[i] !== tempAttribState[i]) + { + attribState[i] = tempAttribState[i]; + + if (state.attribState[i]) + { + gl.enableVertexAttribArray(i); + } + else + { + gl.disableVertexAttribArray(i); + } + } + } + } + else + { + for (i = 0; i < attribs.length; i++) + { + const attrib = attribs[i]; + + gl.enableVertexAttribArray(attrib.attribute.location); + } + } +} diff --git a/packages/core/src/index.js b/packages/core/src/index.js index 58c456a..fd1f547 100644 --- a/packages/core/src/index.js +++ b/packages/core/src/index.js @@ -1,30 +1,30 @@ import * as resources from './textures/resources'; -import * as systems from './renderers/systems'; +import * as systems from './systems'; -export { resources }; export { systems }; +export { resources }; -export { default as Renderer } from './renderers/Renderer'; -export { default as AbstractRenderer } from './renderers/AbstractRenderer'; -export { default as FrameBuffer } from './textures/FrameBuffer'; +export { default as System } from './System'; +export { default as Renderer } from './Renderer'; +export { default as AbstractRenderer } from './AbstractRenderer'; +export { default as FrameBuffer } from './framebuffer/FrameBuffer'; export { default as CubeTexture } from './textures/CubeTexture'; export { default as BaseTexture } from './textures/BaseTexture'; export { default as Texture } from './textures/Texture'; export { default as TextureMatrix } from './textures/TextureMatrix'; -export { default as RenderTexture } from './textures/RenderTexture'; -export { default as BaseRenderTexture } from './textures/BaseRenderTexture'; +export { default as RenderTexture } from './renderTexture/RenderTexture'; +export { default as BaseRenderTexture } from './renderTexture/BaseRenderTexture'; export { default as TextureUvs } from './textures/TextureUvs'; -export { default as State } from './renderers/State'; -export { default as ObjectRenderer } from './renderers/utils/ObjectRenderer'; -export { default as Quad } from './renderers/utils/Quad'; -export { default as QuadUv } from './renderers/utils/QuadUv'; -export { default as checkMaxIfStatmentsInShader } from './renderers/utils/checkMaxIfStatmentsInShader'; +export { default as State } from './state/State'; +export { default as ObjectRenderer } from './batch/ObjectRenderer'; +export { default as Quad } from './utils/Quad'; +export { default as QuadUv } from './utils/QuadUv'; +export { default as checkMaxIfStatmentsInShader } from './shader/utils/checkMaxIfStatmentsInShader'; export { default as Shader } from './shader/Shader'; export { default as Program } from './shader/Program'; export { default as UniformGroup } from './shader/UniformGroup'; -export { default as SpriteMaskFilter } from './renderers/filters/spriteMask/SpriteMaskFilter'; -export { default as Filter } from './renderers/filters/Filter'; +export { default as SpriteMaskFilter } from './filters/spriteMask/SpriteMaskFilter'; +export { default as Filter } from './filters/Filter'; export { default as Attribute } from './geometry/Attribute'; export { default as Buffer } from './geometry/Buffer'; export { default as Geometry } from './geometry/Geometry'; - diff --git a/packages/core/src/mask/MaskSystem.js b/packages/core/src/mask/MaskSystem.js new file mode 100644 index 0000000..99174c2 --- /dev/null +++ b/packages/core/src/mask/MaskSystem.js @@ -0,0 +1,199 @@ +import System from '../System'; +import AlphaMaskFilter from '../filters/spriteMask/SpriteMaskFilter'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class MaskSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + // TODO - we don't need both! + this.scissor = false; + this.scissorData = null; + this.scissorRenderTarget = null; + + this.enableScissor = false; + + this.alphaMaskPool = []; + this.alphaMaskIndex = 0; + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.DisplayObject} target - Display Object to push the mask to + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + push(target, maskData) + { + // TODO the root check means scissor rect will not + // be used on render textures more info here: + // https://github.com/pixijs/pixi.js/pull/3545 + + if (maskData.texture) + { + this.pushSpriteMask(target, maskData); + } + else if (this.enableScissor + && !this.scissor + && this.renderer._activeRenderTarget.root + && !this.renderer.stencil.stencilMaskStack.length + && maskData.isFastRect()) + { + const matrix = maskData.worldTransform; + + let rot = Math.atan2(matrix.b, matrix.a); + + // use the nearest degree! + rot = Math.round(rot * (180 / Math.PI)); + + if (rot % 90) + { + this.pushStencilMask(maskData); + } + else + { + this.pushScissorMask(target, maskData); + } + } + else + { + this.pushStencilMask(maskData); + } + } + + /** + * Removes the last mask from the mask stack and doesn't return it. + * + * @param {PIXI.DisplayObject} target - Display Object to pop the mask from + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + pop(target, maskData) + { + if (maskData.texture) + { + this.popSpriteMask(target, maskData); + } + else if (this.enableScissor && !this.renderer.stencil.stencilMaskStack.length) + { + this.popScissorMask(target, maskData); + } + else + { + this.popStencilMask(target, maskData); + } + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.RenderTarget} target - Display Object to push the sprite mask to + * @param {PIXI.Sprite} maskData - Sprite to be used as the mask + */ + pushSpriteMask(target, maskData) + { + let alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; + + if (!alphaMaskFilter) + { + alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; + } + + alphaMaskFilter[0].resolution = this.renderer.resolution; + alphaMaskFilter[0].maskSprite = maskData; + + // TODO - may cause issues! + target.filterArea = maskData.getBounds(true); + + this.renderer.filterSystem.pushFilter(target, alphaMaskFilter); + + this.alphaMaskIndex++; + } + + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popSpriteMask() + { + this.renderer.filterSystem.popFilter(); + this.alphaMaskIndex--; + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + pushStencilMask(maskData) + { + this.renderer.batch.flush(); + this.renderer.stencil.pushStencil(maskData); + } + + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popStencilMask() + { + // this.renderer.currentRenderer.stop(); + this.renderer.stencil.popStencil(); + } + + /** + * + * @param {PIXI.DisplayObject} target - Display Object to push the mask to + * @param {PIXI.Graphics} maskData - The masking data. + */ + pushScissorMask(target, maskData) + { + maskData.renderable = true; + + const renderTarget = this.renderer._activeRenderTarget; + + const bounds = maskData.getBounds(); + + bounds.fit(renderTarget.size); + maskData.renderable = false; + + this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); + + const resolution = this.renderer.resolution; + + this.renderer.gl.scissor( + bounds.x * resolution, + (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, + bounds.width * resolution, + bounds.height * resolution + ); + + this.scissorRenderTarget = renderTarget; + this.scissorData = maskData; + this.scissor = true; + } + + /** + * + * + */ + popScissorMask() + { + this.scissorRenderTarget = null; + this.scissorData = null; + this.scissor = false; + + // must be scissor! + const gl = this.renderer.gl; + + gl.disable(gl.SCISSOR_TEST); + } +} diff --git a/packages/core/src/mask/StencilSystem.js b/packages/core/src/mask/StencilSystem.js new file mode 100644 index 0000000..62796a5 --- /dev/null +++ b/packages/core/src/mask/StencilSystem.js @@ -0,0 +1,133 @@ +import System from '../System'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class StencilSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + this.stencilMaskStack = []; + } + + /** + * Changes the mask stack that is used by this System. + * + * @param {PIXI.Graphics[]} stencilMaskStack - The mask stack + */ + setMaskStack(stencilMaskStack) + { + const gl = this.renderer.gl; + + if (stencilMaskStack.length !== this.stencilMaskStack.length) + { + if (stencilMaskStack.length === 0) + { + gl.disable(gl.STENCIL_TEST); + } + else + { + gl.enable(gl.STENCIL_TEST); + } + } + + this.stencilMaskStack = stencilMaskStack; + } + + /** + * Applies the Mask and adds it to the current stencil stack. @alvin + * + * @param {PIXI.Graphics} graphics - The mask + */ + pushStencil(graphics) + { + this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); + + // this.renderer._activeRenderTarget.attachStencilBuffer(); + + const gl = this.renderer.gl; + const prevMaskCount = this.stencilMaskStack.length; + + if (prevMaskCount === 0) + { + gl.enable(gl.STENCIL_TEST); + } + + this.stencilMaskStack.push(graphics); + + // Increment the refference stencil value where the new mask overlaps with the old ones. + gl.colorMask(false, false, false, false); + gl.stencilFunc(gl.EQUAL, prevMaskCount, this._getBitwiseMask()); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.INCR); + this.renderer.plugins.graphics.render(graphics); + + this._useCurrent(); + } + + /** + * Removes the last mask from the stencil stack. @alvin + */ + popStencil() + { + this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); + + const gl = this.renderer.gl; + const graphics = this.stencilMaskStack.pop(); + + if (this.stencilMaskStack.length === 0) + { + // the stack is empty! + gl.disable(gl.STENCIL_TEST); + gl.clear(gl.STENCIL_BUFFER_BIT); + gl.clearStencil(0); + } + else + { + // Decrement the refference stencil value where the popped mask overlaps with the other ones + gl.colorMask(false, false, false, false); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.DECR); + this.renderer.plugins.graphics.render(graphics); + + this._useCurrent(); + } + } + + /** + * Setup renderer to use the current stencil data. + */ + _useCurrent() + { + const gl = this.renderer.gl; + + gl.colorMask(true, true, true, true); + gl.stencilFunc(gl.EQUAL, this.stencilMaskStack.length, this._getBitwiseMask()); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP); + } + + /** + * Fill 1s equal to the number of acitve stencil masks. + * + * @return {number} The bitwise mask. + */ + _getBitwiseMask() + { + return (1 << this.stencilMaskStack.length) - 1; + } + + /** + * Destroys the mask stack. + * + */ + destroy() + { + super.destroy(this); + + this.stencilMaskStack = null; + } +} diff --git a/packages/core/src/projection/ProjectionSystem.js b/packages/core/src/projection/ProjectionSystem.js new file mode 100644 index 0000000..6e63b73 --- /dev/null +++ b/packages/core/src/projection/ProjectionSystem.js @@ -0,0 +1,73 @@ +import System from '../System'; +import { Matrix } from '@pixi/math'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class ProjectionSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.projectionMatrix = new Matrix(); + } + + update(destinationFrame, sourceFrame, resolution, root) + { + this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; + this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; + + this.calculateProjection(this.destinationFrame, this.sourceFrame, resolution, root); + + this.renderer.globalUniforms.uniforms.projectionMatrix = this.projectionMatrix; + this.renderer.globalUniforms.update(); + } + + /** + * Updates the projection matrix based on a projection frame (which is a rectangle) + * + * @param {Rectangle} destinationFrame - The destination frame. + * @param {Rectangle} sourceFrame - The source frame. + */ + calculateProjection(destinationFrame, sourceFrame, resolution, root) + { + const pm = this.projectionMatrix; + + // I don't think we will need this line.. + // pm.identity(); + + if (!root) + { + pm.a = (1 / destinationFrame.width * 2) * resolution; + pm.d = (1 / destinationFrame.height * 2) * resolution; + + pm.tx = -1 - (sourceFrame.x * pm.a); + pm.ty = -1 - (sourceFrame.y * pm.d); + } + else + { + pm.a = (1 / destinationFrame.width * 2) * resolution; + pm.d = (-1 / destinationFrame.height * 2) * resolution; + + pm.tx = -1 - (sourceFrame.x * pm.a); + pm.ty = 1 - (sourceFrame.y * pm.d); + } + } + + /** + * Sets the transform of the active render target to the given matrix + * + * @param {PIXI.Matrix} matrix - The transformation matrix + */ + setTransform()// matrix) + { + // this._activeRenderTarget.transform = matrix; + } +} diff --git a/packages/core/src/renderTexture/BaseRenderTexture.js b/packages/core/src/renderTexture/BaseRenderTexture.js new file mode 100644 index 0000000..88eb83b --- /dev/null +++ b/packages/core/src/renderTexture/BaseRenderTexture.js @@ -0,0 +1,137 @@ +import BaseTexture from '../textures/BaseTexture'; +import FrameBuffer from '../framebuffer/FrameBuffer'; + +/** + * A BaseRenderTexture is a special texture that allows any PixiJS display object to be rendered to it. + * + * __Hint__: All DisplayObjects (i.e. Sprites) that render to a BaseRenderTexture should be preloaded + * otherwise black rectangles will be drawn instead. + * + * A BaseRenderTexture takes a snapshot of any Display Object given to its render method. The position + * and rotation of the given Display Objects is ignored. For example: + * + * ```js + * let renderer = PIXI.autoDetectRenderer(1024, 1024, { view: canvas, ratio: 1 }); + * let baseRenderTexture = new PIXI.BaseRenderTexture(renderer, 800, 600); + * let sprite = PIXI.Sprite.fromImage("spinObj_01.png"); + * + * sprite.position.x = 800/2; + * sprite.position.y = 600/2; + * sprite.anchor.x = 0.5; + * sprite.anchor.y = 0.5; + * + * baseRenderTexture.render(sprite); + * ``` + * + * The Sprite in this case will be rendered using its local transform. To render this sprite at 0,0 + * you can clear the transform + * + * ```js + * + * sprite.setTransform() + * + * let baseRenderTexture = new PIXI.BaseRenderTexture(100, 100); + * let renderTexture = new PIXI.RenderTexture(baseRenderTexture); + * + * renderer.render(sprite, renderTexture); // Renders to center of RenderTexture + * ``` + * + * @class + * @extends PIXI.BaseTexture + * @memberof PIXI + */ +export default class BaseRenderTexture extends BaseTexture +{ + /** + * @param {object} [options] + * @param {number} [options.width=100] - The width of the base render texture + * @param {number} [options.height=100] - The height of the base render texture + * @param {PIXI.SCALE_MODES} [options.scaleMode] - See {@link PIXI.SCALE_MODES} for possible values + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the texture being generated + */ + constructor(options) + { + if (typeof options === 'number') + { + /* eslint-disable prefer-rest-params */ + // Backward compatibility of signature + const width = arguments[0]; + const height = arguments[1]; + const scaleMode = arguments[2]; + const resolution = arguments[3]; + + options = { width, height, scaleMode, resolution }; + /* eslint-enable prefer-rest-params */ + } + + super(null, options); + + const { width, height } = options || {}; + + // Set defaults + this.mipmap = false; + this.width = Math.ceil(width) || 100; + this.height = Math.ceil(height) || 100; + this.valid = true; + + /** + * A map of renderer IDs to webgl renderTargets + * + * @private + * @member {object} + */ + // this._glRenderTargets = {}; + + /** + * A reference to the canvas render target (we only need one as this can be shared across renderers) + * + * @private + * @member {object} + */ + this._canvasRenderTarget = null; + + this.clearColor = [0, 0, 0, 0]; + + this.frameBuffer = new FrameBuffer(width * this.resolution, height * this.resolution) + .addColorTexture(0, this); + + // TODO - could this be added the systems? + + /** + * The data structure for the stencil masks + * + * @member {PIXI.Graphics[]} + */ + this.stencilMaskStack = []; + + /** + * The data structure for the filters + * + * @member {PIXI.Graphics[]} + */ + this.filterStack = [{}]; + } + + /** + * Resizes the BaseRenderTexture. + * + * @param {number} width - The width to resize to. + * @param {number} height - The height to resize to. + */ + resize(width, height) + { + width = Math.ceil(width); + height = Math.ceil(height); + this.frameBuffer.resize(width * this.resolution, height * this.resolution); + } + + /** + * Destroys this texture + * + */ + destroy() + { + super.destroy(true); + this.renderer = null; + } +} diff --git a/packages/core/src/renderTexture/RenderTexture.js b/packages/core/src/renderTexture/RenderTexture.js new file mode 100644 index 0000000..f1ecf1f --- /dev/null +++ b/packages/core/src/renderTexture/RenderTexture.js @@ -0,0 +1,153 @@ +import BaseRenderTexture from './BaseRenderTexture'; +import Texture from '../textures/Texture'; + +/** + * A RenderTexture is a special texture that allows any PixiJS display object to be rendered to it. + * + * __Hint__: All DisplayObjects (i.e. Sprites) that render to a RenderTexture should be preloaded + * otherwise black rectangles will be drawn instead. + * + * A RenderTexture takes a snapshot of any Display Object given to its render method. For example: + * + * ```js + * let renderer = PIXI.autoDetectRenderer(1024, 1024, { view: canvas, ratio: 1 }); + * let renderTexture = PIXI.RenderTexture.create(800, 600); + * let sprite = PIXI.Sprite.fromImage("spinObj_01.png"); + * + * sprite.position.x = 800/2; + * sprite.position.y = 600/2; + * sprite.anchor.x = 0.5; + * sprite.anchor.y = 0.5; + * + * renderer.render(sprite, renderTexture); + * ``` + * + * The Sprite in this case will be rendered using its local transform. To render this sprite at 0,0 + * you can clear the transform + * + * ```js + * + * sprite.setTransform() + * + * let renderTexture = new PIXI.RenderTexture.create(100, 100); + * + * renderer.render(sprite, renderTexture); // Renders to center of RenderTexture + * ``` + * + * @class + * @extends PIXI.Texture + * @memberof PIXI + */ +export default class RenderTexture extends Texture +{ + /** + * @param {PIXI.BaseRenderTexture} baseRenderTexture - The renderer used for this RenderTexture + * @param {PIXI.Rectangle} [frame] - The rectangle frame of the texture to show + */ + constructor(baseRenderTexture, frame) + { + // support for legacy.. + let _legacyRenderer = null; + + if (!(baseRenderTexture instanceof BaseRenderTexture)) + { + /* eslint-disable prefer-rest-params, no-console */ + const width = arguments[1]; + const height = arguments[2]; + const scaleMode = arguments[3]; + const resolution = arguments[4]; + + // we have an old render texture.. + console.warn(`Please use RenderTexture.create(${width}, ${height}) instead of the ctor directly.`); + _legacyRenderer = arguments[0]; + /* eslint-enable prefer-rest-params, no-console */ + + frame = null; + baseRenderTexture = new BaseRenderTexture({ + width, + height, + scaleMode, + resolution, + }); + } + + /** + * The base texture object that this texture uses + * + * @member {BaseTexture} + */ + super(baseRenderTexture, frame); + + this.legacyRenderer = _legacyRenderer; + + /** + * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. + * + * @member {boolean} + */ + this.valid = true; + + /** + * FilterSystem temporary storage + * @private + * @member {PIXI.Rectangle} + */ + this.filterFrame = null; + + this._updateUvs(); + } + + /** + * Resizes the RenderTexture. + * + * @param {number} width - The width to resize to. + * @param {number} height - The height to resize to. + * @param {boolean} [resizeBaseTexture=true] - Should the baseTexture.width and height values be resized as well? + */ + resize(width, height, resizeBaseTexture = true) + { + width = Math.ceil(width); + height = Math.ceil(height); + + // TODO - could be not required.. + this.valid = (width > 0 && height > 0); + + this._frame.width = this.orig.width = width; + this._frame.height = this.orig.height = height; + + if (resizeBaseTexture) + { + this.baseTexture.resize(width, height); + } + + this._updateUvs(); + } + + /** + * A short hand way of creating a render texture. + * + * @param {object} [options] - Options + * @param {number} [options.width=100] - The width of the render texture + * @param {number} [options.height=100] - The height of the render texture + * @param {number} [options.scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the texture being generated + * @return {PIXI.RenderTexture} The new render texture + */ + static create(options) + { + // fallback, old-style: create(width, height, scaleMode, resolution) + if (typeof options === 'number') + { + /* eslint-disable prefer-rest-params */ + options = { + width: options, + height: arguments[1], + scaleMode: arguments[2], + resolution: arguments[3], + }; + /* eslint-enable prefer-rest-params */ + } + + return new RenderTexture(new BaseRenderTexture(options)); + } +} diff --git a/packages/core/src/renderTexture/RenderTextureSystem.js b/packages/core/src/renderTexture/RenderTextureSystem.js new file mode 100644 index 0000000..8d0ca61 --- /dev/null +++ b/packages/core/src/renderTexture/RenderTextureSystem.js @@ -0,0 +1,115 @@ +import System from '../System'; +import { Rectangle } from '@pixi/math'; + +const tempRect = new Rectangle(); + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class RenderTextureSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.clearColor = renderer._backgroundColorRgba; + + // TODO moe this property somewhere else! + this.defaultMaskStack = []; + this.defaultFilterStack = [{}]; + + // empty render texture? + this.renderTexture = null; + + this.destinationFrame = new Rectangle(); + } + + bind(renderTexture, sourceFrame, destinationFrame) + { + // TODO - do we want this?? + if (this.renderTexture === renderTexture) return; + this.renderTexture = renderTexture; + + const renderer = this.renderer; + + if (renderTexture) + { + const baseTexture = renderTexture.baseTexture; + + if (!destinationFrame) + { + tempRect.width = baseTexture.realWidth; + tempRect.height = baseTexture.realHeight; + + destinationFrame = tempRect; + } + + if (!sourceFrame) + { + sourceFrame = destinationFrame; + } + + this.renderer.framebuffer.bind(baseTexture.frameBuffer, destinationFrame); + + this.renderer.projection.update(destinationFrame, sourceFrame, baseTexture.resolution, false); + this.renderer.stencil.setMaskStack(baseTexture.stencilMaskStack); + } + else + { + // TODO these validation checks happen deeper down.. + // thing they can be avoided.. + if (!destinationFrame) + { + tempRect.width = renderer.width; + tempRect.height = renderer.height; + + destinationFrame = tempRect; + } + + if (!sourceFrame) + { + sourceFrame = destinationFrame; + } + + renderer.framebuffer.bind(null, destinationFrame); + + // TODO store this.. + this.renderer.projection.update(destinationFrame, sourceFrame, this.renderer.resolution, true); + this.renderer.stencil.setMaskStack(this.defaultMaskStack); + } + + this.destinationFrame.copyFrom(destinationFrame); + } + + /** + * Erases the render texture and fills the drawing area with a colour + * + * @param {number} [clearColor] - The colour + * @return {PIXI.Renderer} Returns itself. + */ + clear(clearColor) + { + if (this.renderTexture) + { + clearColor = clearColor || this.renderTexture.baseTexture.clearColor; + } + else + { + clearColor = clearColor || this.clearColor; + } + + this.renderer.framebuffer.clear(clearColor[0], clearColor[1], clearColor[2], clearColor[3]); + } + + resize()// screenWidth, screenHeight) + { + // resize the root only! + this.bind(null); + } +} diff --git a/packages/core/src/renderers/AbstractRenderer.js b/packages/core/src/renderers/AbstractRenderer.js deleted file mode 100644 index 928670f..0000000 --- a/packages/core/src/renderers/AbstractRenderer.js +++ /dev/null @@ -1,353 +0,0 @@ -import { hex2string, hex2rgb } from '@pixi/utils'; -import { Matrix, Rectangle } from '@pixi/math'; -import { RENDERER_TYPE } from '@pixi/constants'; -import { settings } from '@pixi/settings'; -import { Container } from '@pixi/display'; -import RenderTexture from '../textures/RenderTexture'; -import EventEmitter from 'eventemitter3'; - -const tempMatrix = new Matrix(); - -/** - * 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 EventEmitter - * @memberof PIXI - */ -export default class AbstractRenderer extends EventEmitter -{ - // eslint-disable-next-line valid-jsdoc - /** - * @param {string} system - The name of the system this renderer is for. - * @param {object} [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.transparent=false] - If the render view is transparent, default false - * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false - * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) - * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. The - * resolution of the renderer retina would be 2. - * @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 {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when rendering, - * stopping pixel interpolation. - */ - constructor(system, options, arg2, arg3) - { - super(); - - // Support for constructor(system, screenWidth, screenHeight, options) - if (typeof options === 'number') - { - options = Object.assign({ - width: options, - height: arg2 || settings.RENDER_OPTIONS.height, - }, arg3); - } - - // 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 = RENDERER_TYPE.UNKNOWN; - - /** - * Measurements of the screen. (0, 0, screenWidth, screenHeight) - * - * Its safe to use as filterArea or hitArea for 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 1 - */ - this.resolution = options.resolution || settings.RESOLUTION; - - /** - * Whether the render view is transparent - * - * @member {boolean} - */ - this.transparent = options.transparent; - - /** - * Whether css dimensions of canvas view should be resized to screen dimensions automatically - * - * @member {boolean} - */ - this.autoResize = options.autoResize || false; - - /** - * Tracks the blend modes useful for this renderer. - * - * @member {object} - */ - this.blendModes = null; - - /** - * 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; - - /** - * If true PixiJS will Math.floor() x/y values when rendering, stopping pixel interpolation. - * Handy for crisp pixel art and speed on legacy devices. - * - * @member {boolean} - */ - this.roundPixels = options.roundPixels; - - /** - * The background color as a number. - * - * @member {number} - * @private - */ - this._backgroundColor = 0x000000; - - /** - * The background color as an [R, G, B] array. - * - * @member {number[]} - * @private - */ - this._backgroundColorRgba = [0, 0, 0, 0]; - - /** - * The background color as a string. - * - * @member {string} - * @private - */ - this._backgroundColorString = '#000000'; - - this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter - - /** - * This temporary display object used as the parent of the currently being rendered item - * - * @member {PIXI.DisplayObject} - * @private - */ - this._tempDisplayObjectParent = new Container(); - - /** - * The last root object that the renderer tried to render. - * - * @member {PIXI.DisplayObject} - * @private - */ - this._lastObjectRendered = this._tempDisplayObjectParent; - - /** - * Collection of plugins. - * @readonly - * @member {object} - */ - this.plugins = {}; - } - - /** - * Initialize the plugins. - * - * @protected - * @param {object} staticMap - The dictionary of staticly saved plugins. - */ - initPlugins(staticMap) - { - 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() - { - return this.view.width; - } - - /** - * Same as view.height, actual number of pixels in the canvas by vertical - * - * @member {number} - * @readonly - * @default 600 - */ - get height() - { - return this.view.height; - } - - /** - * Resizes the screen and canvas to the specified width and height - * Canvas dimensions are multiplied by resolution - * - * @param {number} screenWidth - the new width of the screen - * @param {number} screenHeight - the new height of the screen - */ - resize(screenWidth, screenHeight) - { - this.screen.width = screenWidth; - this.screen.height = screenHeight; - - this.view.width = screenWidth * this.resolution; - this.view.height = screenHeight * this.resolution; - - if (this.autoResize) - { - this.view.style.width = `${screenWidth}px`; - this.view.style.height = `${screenHeight}px`; - } - } - - /** - * 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 {PIXI.DisplayObject} displayObject - The displayObject the object will be generated from - * @param {number} scaleMode - Should be one of the scaleMode consts - * @param {number} resolution - The resolution / device pixel ratio of the texture being generated - * @param {PIXI.Rectangle} [region] - The region of the displayObject, that shall be rendered, - * if no region is specified, defaults to the local bounds of the displayObject. - * @return {PIXI.Texture} a texture of the graphics object - */ - generateTexture(displayObject, scaleMode, resolution, region) - { - region = region || displayObject.getLocalBounds(); - - // 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(region.width | 0, region.height | 0, scaleMode, resolution); - - tempMatrix.tx = -region.x; - tempMatrix.ty = -region.y; - - this.render(displayObject, renderTexture, false, tempMatrix, true); - - return renderTexture; - } - - /** - * Removes everything from the renderer and optionally removes the Canvas DOM element. - * - * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. - */ - destroy(removeView) - { - 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); - } - - this.plugins = null; - - this.type = RENDERER_TYPE.UNKNOWN; - - this.view = null; - - this.screen = null; - - this.resolution = 0; - - this.transparent = false; - - this.autoResize = false; - - this.blendModes = null; - - this.options = null; - - this.preserveDrawingBuffer = false; - this.clearBeforeRender = false; - - this.roundPixels = false; - - this._backgroundColor = 0; - this._backgroundColorRgba = null; - this._backgroundColorString = null; - - this._tempDisplayObjectParent = null; - this._lastObjectRendered = null; - } - - /** - * The background color to fill if not transparent - * - * @member {number} - */ - get backgroundColor() - { - return this._backgroundColor; - } - - set backgroundColor(value) // eslint-disable-line require-jsdoc - { - this._backgroundColor = value; - this._backgroundColorString = hex2string(value); - hex2rgb(value, this._backgroundColorRgba); - } -} diff --git a/packages/core/src/renderers/Renderer.js b/packages/core/src/renderers/Renderer.js deleted file mode 100644 index a2020b1..0000000 --- a/packages/core/src/renderers/Renderer.js +++ /dev/null @@ -1,329 +0,0 @@ -import AbstractRenderer from './AbstractRenderer'; -import { sayHello } from '@pixi/utils'; -import MaskSystem from './systems/MaskSystem'; -import StencilSystem from './systems/StencilSystem'; -import FilterSystem from './systems/filter/FilterSystem'; -import FramebufferSystem from './systems/FramebufferSystem'; -import RenderTextureSystem from './systems/RenderTextureSystem'; -import TextureSystem from './systems/textures/TextureSystem'; -import ProjectionSystem from './systems/ProjectionSystem'; -import StateSystem from './systems/StateSystem'; -import GeometrySystem from './systems/geometry/GeometrySystem'; -import ShaderSystem from './systems/shader/ShaderSystem'; -import ContextSystem from './systems/ContextSystem'; -import BatchSystem from './systems/BatchSystem'; -import TextureGCSystem from './systems/textures/TextureGCSystem'; -import { RENDERER_TYPE } from '@pixi/constants'; -import UniformGroup from '../shader/UniformGroup'; -import { Matrix } from '@pixi/math'; -import Runner from 'mini-runner'; - -/** - * 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 Render works by automatically managing webGLBatchs. - * 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 :) - * - * @class - * @memberof PIXI - * @extends PIXI.AbstractRenderer - */ -export default class Renderer extends AbstractRenderer -{ - // eslint-disable-next-line valid-jsdoc - /** - * - * @param {object} [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.transparent=false] - If the render view is transparent, default false - * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false - * @param {boolean} [options.antialias=false] - sets antialias. If not available natively then FXAA - * antialiasing is used - * @param {boolean} [options.forceFXAA=false] - forces FXAA antialiasing to be used over native. - * FXAA is faster, but may not always look as great - * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. - * The resolution of the renderer retina would be 2. - * @param {boolean} [options.clearBeforeRender=true] - This sets if the renderer will clear - * the canvas or not before the new render pass. If you wish to set this to false, you *must* set - * preserveDrawingBuffer to `true`. - * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, - * enable this if you need to call toDataUrl on the webgl context. - * @param {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when - * rendering, stopping pixel interpolation. - * @param {number} [options.backgroundColor=0x000000] - The background color of the rendered area - * (shown if not transparent). - * @param {boolean} [options.legacy=false] - If true PixiJS will aim to ensure compatibility - * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. - * @param {string} [options.powerPreference] - Parameter passed to webgl context, set to "high-performance" - * for devices with dual graphics card - */ - constructor(options, arg2, arg3) - { - super('WebGL', options, arg2, arg3); - - /** - * The type of this renderer as a standardised const - * - * @member {number} - * @see PIXI.RENDERER_TYPE - */ - this.type = RENDERER_TYPE.WEBGL; - - // this will be set by the contextSystem (this.context) - this.gl = null; - this.CONTEXT_UID = 0; - this.legacy = !!options.legacy; - - // TODO legacy! - - // runners! - this.runners = { - destroy: new Runner('destroy'), - contextChange: new Runner('contextChange', 1), - reset: new Runner('reset'), - update: new Runner('update'), - postrender: new Runner('postrender'), - prerender: new Runner('prerender'), - resize: new Runner('resize', 2), - }; - - 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(GeometrySystem, 'geometry') - .addSystem(FramebufferSystem, 'framebuffer') - .addSystem(StencilSystem, 'stencil') - .addSystem(ProjectionSystem, 'projection') - .addSystem(TextureGCSystem, 'textureGC') - .addSystem(FilterSystem, 'filter') - .addSystem(RenderTextureSystem, 'renderTexture') - .addSystem(BatchSystem, 'batch'); - - this.initPlugins(Renderer.__plugins); - - /** - * The options passed in to create a new webgl context. - * - * @member {object} - * @private - */ - if (options.context) - { - this.context.initFromContext(options.context); - } - else - { - this.context.initFromOptions({ - alpha: this.transparent, - antialias: options.antialias, - premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', - stencil: true, - 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); - } - - addSystem(_class, name) - { - if (!name) - { - name = _class.name; - } - - // TODO - read name from class.name.. - - /* - if(name.includes('System')) - { - name = name.replace('System', ''); - name = name.charAt(0).toLowerCase() + name.slice(1); - } - */ - - const system = new _class(this); - - if (this[name]) - { - throw new Error(`Whoops! ${name} is already a manger`); - } - - this[name] = system; - - for (const i in this.runners) - { - this.runners[i].add(system); - } - - return this; - - /** - * 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. - */ - } - - /** - * Renders the object to its webGL view - * - * @param {PIXI.DisplayObject} displayObject - the object to be rendered - * @param {PIXI.RenderTexture} renderTexture - The render texture to render to. - * @param {boolean} [clear] - Should the canvas be cleared before the new render - * @param {PIXI.Transform} [transform] - A transform to apply to the render texture before rendering. - * @param {boolean} [skipUpdateTransform] - Should we skip the update transform pass? - */ - render(displayObject, renderTexture, clear, transform, skipUpdateTransform) - { - // can be handy to know! - this.renderingToScreen = !renderTexture; - - this.runners.prerender.run(); - this.emit('prerender'); - - // 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.parent; - - displayObject.parent = this._tempDisplayObjectParent; - displayObject.updateTransform(); - displayObject.parent = 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.run(); - - this.emit('postrender'); - } - - /** - * Resizes the webGL view to the specified width and height. - * - * @param {number} screenWidth - the new width of the screen - * @param {number} screenHeight - the new height of the screen - */ - resize(screenWidth, screenHeight) - { - AbstractRenderer.prototype.resize.call(this, screenWidth, screenHeight); - - this.runners.resize.run(screenWidth, screenHeight); - } - - /** - * Resets the WebGL state so you can render things however you fancy! - * - * @return {PIXI.Renderer} Returns itself. - */ - reset() - { - this.runners.reset.run(); - - return this; - } - - clear() - { - this.framebuffer.bind(); - this.framebuffer.clear(); - } - - /** - * Removes everything from the renderer (event listeners, spritebatch, etc...) - * - * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. - * See: https://github.com/pixijs/pixi.js/issues/2233 - */ - destroy(removeView) - { - this.runners.destroy.run(); - - // call base destroy - super.destroy(removeView); - - // TODO nullify all the managers.. - this.gl = null; - } - - /** - * 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 - * builds and excluding plugins. - * @name PIXI.Renderer#plugins - * @type {object} - * @readonly - * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. - * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. - * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. - * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. - */ - - /** - * Adds a plugin to the renderer. - * - * @method - * @param {string} pluginName - The name of the plugin. - * @param {Function} ctor - The constructor function or class for the plugin. - */ - static registerPlugin(pluginName, ctor) - { - Renderer.__plugins = Renderer.__plugins || {}; - Renderer.__plugins[pluginName] = ctor; - } -} diff --git a/packages/core/src/renderers/State.js b/packages/core/src/renderers/State.js deleted file mode 100644 index fa3f221..0000000 --- a/packages/core/src/renderers/State.js +++ /dev/null @@ -1,163 +0,0 @@ -/* eslint-disable max-len */ - -const BLEND = 0; -const OFFSET = 1; -const CULLING = 2; -const DEPTH_TEST = 3; -const WINDING = 4; - -/** - * This is a webGL state. It is passed The WebGL StateManager. - * Each mesh renderered may require webGL to be in a different state. - * For example you may want different blend mode or to enable polygon offsets - * - * @class - * @memberof PIXI - */ -export default class State -{ - /** - * - */ - constructor() - { - this.data = 0; - - this.blendMode = 0; - this.polygonOffset = 0; - - this.blend = true; - // this.depthTest = true; - } - - /** - * Activates blending of the computed fragment color values - * - * @member {boolean} - */ - get blend() - { - return !!(this.data & (1 << BLEND)); - } - - set blend(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << BLEND)) !== value) - { - this.data ^= (1 << BLEND); - } - } - - /** - * Activates adding an offset to depth values of polygon's fragments - * - * @member {boolean} - * @default false - */ - get offsets() - { - return !!(this.data & (1 << OFFSET)); - } - - set offsets(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << OFFSET)) !== value) - { - this.data ^= (1 << OFFSET); - } - } - - /** - * Activates culling of polygons. - * - * @member {boolean} - * @default false - */ - get culling() - { - return !!(this.data & (1 << CULLING)); - } - - set culling(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << CULLING)) !== value) - { - this.data ^= (1 << CULLING); - } - } - - /** - * Activates depth comparisons and updates to the depth buffer. - * - * @member {boolean} - * @default false - */ - get depthTest() - { - return !!(this.data & (1 << DEPTH_TEST)); - } - - set depthTest(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << DEPTH_TEST)) !== value) - { - this.data ^= (1 << DEPTH_TEST); - } - } - - /** - * Specifies whether or not front or back-facing polygons can be culled. - * @member {boolean} - * @default false - */ - get clockwiseFrontFace() - { - return !!(this.data & (1 << WINDING)); - } - - set clockwiseFrontFace(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << WINDING)) !== value) - { - this.data ^= (1 << WINDING); - } - } - - /** - * The blend mode to be applied when this state is set. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. - * Setting this mode to anything other than NO_BLEND will automatically switch blending on. - * - * @member {boolean} - * @default PIXI.BLEND_MODES.NORMAL - * @see PIXI.BLEND_MODES - */ - get blendMode() - { - return this._blendMode; - } - - set blendMode(value) // eslint-disable-line require-jsdoc - { - // 17 is NO BLEND - this.blend = (value !== 17); - this._blendMode = value; - } - - /** - * The polygon offset. Setting this property to anything other than 0 will automatically enable poygon offset fill. - * - * @member {number} - * @default 0 - */ - get polygonOffset() - { - return this._polygonOffset; - } - - set polygonOffset(value) // eslint-disable-line require-jsdoc - { - this.offsets = !!value; - this._polygonOffset = value; - } -} - diff --git a/packages/core/src/renderers/filters/Filter.js b/packages/core/src/renderers/filters/Filter.js deleted file mode 100644 index 2073b8d..0000000 --- a/packages/core/src/renderers/filters/Filter.js +++ /dev/null @@ -1,140 +0,0 @@ -import Shader from '../../shader/Shader'; -import Program from '../../shader/Program'; -import State from '../State'; -import { settings } from '@pixi/settings'; -// import extractUniformsFromSrc from './extractUniformsFromSrc'; -import defaultVertex from './defaultFilter.vert'; -import defaultFragment from './defaultFilter.frag'; - -// let math = require('../../math'); -/** - * @class - * @memberof PIXI - * @extends PIXI.Shader - */ -export default class Filter extends Shader -{ - /** - * @param {string} [vertexSrc] - The source of the vertex shader. - * @param {string} [fragmentSrc] - The source of the fragment shader. - * @param {object} [uniforms] - Custom uniforms to use to augment the built-in ones. - */ - constructor(vertexSrc, fragmentSrc, uniforms) - { - const program = Program.from(vertexSrc, fragmentSrc); - - super(program, uniforms); - - /** - * The padding of the filter. Some filters require extra space to breath such as a blur. - * Increasing this will add extra width and height to the bounds of the object that the - * filter is applied to. - * - * @member {number} - */ - this.padding = 0; - - /** - * The resolution of the filter. Setting this to be lower will lower the quality but - * increase the performance of the filter. - * - * @member {number} - */ - this.resolution = settings.RESOLUTION; - - /** - * If enabled is true the filter is applied, if false it will not. - * - * @member {boolean} - */ - this.enabled = true; - - /** - * If enabled, PixiJS will fit the filter area into boundaries for better performance. - * Switch it off if it does not work for specific shader. - * - * @member {boolean} - */ - this.autoFit = true; - - /** - * Legacy filters use position and uvs from attributes - * @member {boolean} - * @readonly - */ - this.legacy = !!this.program.attributeData.aTextureCoord; - - /** - * the webGL state the filter requires to render - * @member {PIXI.State} - */ - this.state = new State(); - } - - /** - * Applies the filter - * - * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from - * @param {PIXI.RenderTarget} input - The input render target. - * @param {PIXI.RenderTarget} output - The target to output to. - * @param {boolean} clear - Should the output be cleared before rendering to it - * @param {object} [currentState] - It's current state of filter. - * There are some useful properties in the currentState : - * target, filters, sourceFrame, destinationFrame, renderTarget, resolution - */ - apply(filterManager, input, output, clear, currentState, derp) // eslint-disable-line no-unused-vars - { - // do as you please! - - filterManager.applyFilter(this, input, output, clear, currentState, derp); - - // or just do a regular render.. - } - - /** - * Sets the blendmode of the filter - * - * @member {number} - * @default PIXI.BLEND_MODES.NORMAL - */ - get blendMode() - { - return this.state.blendMode; - } - - set blendMode(value) // eslint-disable-line require-jsdoc - { - this.state.blendMode = value; - } - - /** - * The default vertex shader source - * - * @static - * @constant - */ - static get defaultVertexSrc() - { - return defaultVertex; - } - - /** - * The default fragment shader source - * - * @static - * @constant - */ - static get defaultFragmentSrc() - { - return defaultFragment; - } -} - -/** - * Used for caching shader IDs - * - * @static - * @private - */ -Filter.SOURCE_KEY_MAP = {}; - diff --git a/packages/core/src/renderers/filters/defaultFilter.frag b/packages/core/src/renderers/filters/defaultFilter.frag deleted file mode 100644 index a14c1be..0000000 --- a/packages/core/src/renderers/filters/defaultFilter.frag +++ /dev/null @@ -1,22 +0,0 @@ -varying vec2 vTextureCoord; -varying vec2 vFilterCoord; - -uniform sampler2D uSampler; -uniform sampler2D filterSampler; - -void main(void){ - vec4 masky = texture2D(filterSampler, vFilterCoord); - vec4 sample = texture2D(uSampler, vTextureCoord); - vec4 color; - if(mod(vFilterCoord.x, 1.0) > 0.5) - { - color = vec4(1.0, 0.0, 0.0, 1.0); - } - else - { - color = vec4(0.0, 1.0, 0.0, 1.0); - } - // gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0); - gl_FragColor = mix(sample, masky, 0.5); - gl_FragColor *= sample.a; -} diff --git a/packages/core/src/renderers/filters/defaultFilter.vert b/packages/core/src/renderers/filters/defaultFilter.vert deleted file mode 100644 index e1febca..0000000 --- a/packages/core/src/renderers/filters/defaultFilter.vert +++ /dev/null @@ -1,14 +0,0 @@ -attribute vec2 aVertexPosition; -attribute vec2 aTextureCoord; - -uniform mat3 projectionMatrix; -uniform mat3 filterMatrix; - -varying vec2 vTextureCoord; -varying vec2 vFilterCoord; - -void main(void){ - gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); - vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy; - vTextureCoord = aTextureCoord ; -} diff --git a/packages/core/src/renderers/filters/filterTransforms.js b/packages/core/src/renderers/filters/filterTransforms.js deleted file mode 100644 index 87a3cd6..0000000 --- a/packages/core/src/renderers/filters/filterTransforms.js +++ /dev/null @@ -1,50 +0,0 @@ -import { Matrix } from '@pixi/math'; - -/** - * Calculates the mapped matrix - * @param filterArea {Rectangle} The filter area - * @param sprite {Sprite} the target sprite - * @param outputMatrix {Matrix} @alvin - * @private - */ -// this returns a matrix that will normalise map filter cords in the filter to screen space -export function calculateScreenSpaceMatrix(outputMatrix, filterArea, textureSize) -{ - // TODO unwrap? - const mappedMatrix = outputMatrix.identity(); - - mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); - - mappedMatrix.scale(textureSize.width, textureSize.height); - - return mappedMatrix; -} - -export function calculateNormalizedScreenSpaceMatrix(outputMatrix, filterArea, textureSize) -{ - const mappedMatrix = outputMatrix.identity(); - - mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); - - const translateScaleX = (textureSize.width / filterArea.width); - const translateScaleY = (textureSize.height / filterArea.height); - - mappedMatrix.scale(translateScaleX, translateScaleY); - - return mappedMatrix; -} - -// this will map the filter coord so that a texture can be used based on the transform of a sprite -export function calculateSpriteMatrix(outputMatrix, filterArea, textureSize, sprite) -{ - const orig = sprite._texture.orig; - const mappedMatrix = outputMatrix.set(textureSize.width, 0, 0, textureSize.height, filterArea.x, filterArea.y); - const worldTransform = sprite.worldTransform.copyTo(Matrix.TEMP_MATRIX); - - worldTransform.invert(); - mappedMatrix.prepend(worldTransform); - mappedMatrix.scale(1.0 / orig.width, 1.0 / orig.height); - mappedMatrix.translate(sprite.anchor.x, sprite.anchor.y); - - return mappedMatrix; -} diff --git a/packages/core/src/AbstractRenderer.js b/packages/core/src/AbstractRenderer.js new file mode 100644 index 0000000..64b2eb1 --- /dev/null +++ b/packages/core/src/AbstractRenderer.js @@ -0,0 +1,353 @@ +import { hex2string, hex2rgb } from '@pixi/utils'; +import { Matrix, Rectangle } from '@pixi/math'; +import { RENDERER_TYPE } from '@pixi/constants'; +import { settings } from '@pixi/settings'; +import { Container } from '@pixi/display'; +import RenderTexture from './renderTexture/RenderTexture'; +import EventEmitter from 'eventemitter3'; + +const tempMatrix = new Matrix(); + +/** + * 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 EventEmitter + * @memberof PIXI + */ +export default class AbstractRenderer extends EventEmitter +{ + // eslint-disable-next-line valid-jsdoc + /** + * @param {string} system - The name of the system this renderer is for. + * @param {object} [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.transparent=false] - If the render view is transparent, default false + * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false + * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. The + * resolution of the renderer retina would be 2. + * @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 {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when rendering, + * stopping pixel interpolation. + */ + constructor(system, options, arg2, arg3) + { + super(); + + // Support for constructor(system, screenWidth, screenHeight, options) + if (typeof options === 'number') + { + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + }, arg3); + } + + // 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 = RENDERER_TYPE.UNKNOWN; + + /** + * Measurements of the screen. (0, 0, screenWidth, screenHeight) + * + * Its safe to use as filterArea or hitArea for 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 1 + */ + this.resolution = options.resolution || settings.RESOLUTION; + + /** + * Whether the render view is transparent + * + * @member {boolean} + */ + this.transparent = options.transparent; + + /** + * Whether css dimensions of canvas view should be resized to screen dimensions automatically + * + * @member {boolean} + */ + this.autoResize = options.autoResize || false; + + /** + * Tracks the blend modes useful for this renderer. + * + * @member {object} + */ + this.blendModes = null; + + /** + * 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; + + /** + * If true PixiJS will Math.floor() x/y values when rendering, stopping pixel interpolation. + * Handy for crisp pixel art and speed on legacy devices. + * + * @member {boolean} + */ + this.roundPixels = options.roundPixels; + + /** + * The background color as a number. + * + * @member {number} + * @private + */ + this._backgroundColor = 0x000000; + + /** + * The background color as an [R, G, B] array. + * + * @member {number[]} + * @private + */ + this._backgroundColorRgba = [0, 0, 0, 0]; + + /** + * The background color as a string. + * + * @member {string} + * @private + */ + this._backgroundColorString = '#000000'; + + this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter + + /** + * This temporary display object used as the parent of the currently being rendered item + * + * @member {PIXI.DisplayObject} + * @private + */ + this._tempDisplayObjectParent = new Container(); + + /** + * The last root object that the renderer tried to render. + * + * @member {PIXI.DisplayObject} + * @private + */ + this._lastObjectRendered = this._tempDisplayObjectParent; + + /** + * Collection of plugins. + * @readonly + * @member {object} + */ + this.plugins = {}; + } + + /** + * Initialize the plugins. + * + * @protected + * @param {object} staticMap - The dictionary of staticly saved plugins. + */ + initPlugins(staticMap) + { + 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() + { + return this.view.width; + } + + /** + * Same as view.height, actual number of pixels in the canvas by vertical + * + * @member {number} + * @readonly + * @default 600 + */ + get height() + { + return this.view.height; + } + + /** + * Resizes the screen and canvas to the specified width and height + * Canvas dimensions are multiplied by resolution + * + * @param {number} screenWidth - the new width of the screen + * @param {number} screenHeight - the new height of the screen + */ + resize(screenWidth, screenHeight) + { + this.screen.width = screenWidth; + this.screen.height = screenHeight; + + this.view.width = screenWidth * this.resolution; + this.view.height = screenHeight * this.resolution; + + if (this.autoResize) + { + this.view.style.width = `${screenWidth}px`; + this.view.style.height = `${screenHeight}px`; + } + } + + /** + * 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 {PIXI.DisplayObject} displayObject - The displayObject the object will be generated from + * @param {number} scaleMode - Should be one of the scaleMode consts + * @param {number} resolution - The resolution / device pixel ratio of the texture being generated + * @param {PIXI.Rectangle} [region] - The region of the displayObject, that shall be rendered, + * if no region is specified, defaults to the local bounds of the displayObject. + * @return {PIXI.Texture} a texture of the graphics object + */ + generateTexture(displayObject, scaleMode, resolution, region) + { + region = region || displayObject.getLocalBounds(); + + // 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(region.width | 0, region.height | 0, scaleMode, resolution); + + tempMatrix.tx = -region.x; + tempMatrix.ty = -region.y; + + this.render(displayObject, renderTexture, false, tempMatrix, true); + + return renderTexture; + } + + /** + * Removes everything from the renderer and optionally removes the Canvas DOM element. + * + * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. + */ + destroy(removeView) + { + 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); + } + + this.plugins = null; + + this.type = RENDERER_TYPE.UNKNOWN; + + this.view = null; + + this.screen = null; + + this.resolution = 0; + + this.transparent = false; + + this.autoResize = false; + + this.blendModes = null; + + this.options = null; + + this.preserveDrawingBuffer = false; + this.clearBeforeRender = false; + + this.roundPixels = false; + + this._backgroundColor = 0; + this._backgroundColorRgba = null; + this._backgroundColorString = null; + + this._tempDisplayObjectParent = null; + this._lastObjectRendered = null; + } + + /** + * The background color to fill if not transparent + * + * @member {number} + */ + get backgroundColor() + { + return this._backgroundColor; + } + + set backgroundColor(value) // eslint-disable-line require-jsdoc + { + this._backgroundColor = value; + this._backgroundColorString = hex2string(value); + hex2rgb(value, this._backgroundColorRgba); + } +} diff --git a/packages/core/src/Renderer.js b/packages/core/src/Renderer.js new file mode 100644 index 0000000..2227e78 --- /dev/null +++ b/packages/core/src/Renderer.js @@ -0,0 +1,338 @@ +import AbstractRenderer from './AbstractRenderer'; +import { sayHello } from '@pixi/utils'; +import MaskSystem from './mask/MaskSystem'; +import StencilSystem from './mask/StencilSystem'; +import FilterSystem from './filters/FilterSystem'; +import FramebufferSystem from './framebuffer/FramebufferSystem'; +import RenderTextureSystem from './renderTexture/RenderTextureSystem'; +import TextureSystem from './textures/TextureSystem'; +import ProjectionSystem from './projection/ProjectionSystem'; +import StateSystem from './state/StateSystem'; +import GeometrySystem from './geometry/GeometrySystem'; +import ShaderSystem from './shader/ShaderSystem'; +import ContextSystem from './context/ContextSystem'; +import BatchSystem from './batch/BatchSystem'; +import TextureGCSystem from './textures/TextureGCSystem'; +import { RENDERER_TYPE } from '@pixi/constants'; +import UniformGroup from './shader/UniformGroup'; +import { Matrix } from '@pixi/math'; +import Runner from 'mini-runner'; + +/** + * 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 Render works by automatically managing webGLBatchs. + * 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 :) + * + * @class + * @memberof PIXI + * @extends PIXI.AbstractRenderer + */ +export default class Renderer extends AbstractRenderer +{ + // eslint-disable-next-line valid-jsdoc + /** + * + * @param {object} [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.transparent=false] - If the render view is transparent, default false + * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false + * @param {boolean} [options.antialias=false] - sets antialias. If not available natively then FXAA + * antialiasing is used + * @param {boolean} [options.forceFXAA=false] - forces FXAA antialiasing to be used over native. + * FXAA is faster, but may not always look as great + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. + * The resolution of the renderer retina would be 2. + * @param {boolean} [options.clearBeforeRender=true] - This sets if the renderer will clear + * the canvas or not before the new render pass. If you wish to set this to false, you *must* set + * preserveDrawingBuffer to `true`. + * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, + * enable this if you need to call toDataUrl on the webgl context. + * @param {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when + * rendering, stopping pixel interpolation. + * @param {number} [options.backgroundColor=0x000000] - The background color of the rendered area + * (shown if not transparent). + * @param {boolean} [options.legacy=false] - If true PixiJS will aim to ensure compatibility + * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. + * @param {string} [options.powerPreference] - Parameter passed to webgl context, set to "high-performance" + * for devices with dual graphics card + */ + constructor(options, arg2, arg3) + { + super('WebGL', options, arg2, arg3); + + /** + * The type of this renderer as a standardised const + * + * @member {number} + * @see PIXI.RENDERER_TYPE + */ + this.type = RENDERER_TYPE.WEBGL; + + // this will be set by the contextSystem (this.context) + this.gl = null; + this.CONTEXT_UID = 0; + this.legacy = !!options.legacy; + + // TODO legacy! + + // runners! + this.runners = { + destroy: new Runner('destroy'), + contextChange: new Runner('contextChange', 1), + reset: new Runner('reset'), + update: new Runner('update'), + postrender: new Runner('postrender'), + prerender: new Runner('prerender'), + resize: new Runner('resize', 2), + }; + + 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(GeometrySystem, 'geometry') + .addSystem(FramebufferSystem, 'framebuffer') + .addSystem(StencilSystem, 'stencil') + .addSystem(ProjectionSystem, 'projection') + .addSystem(TextureGCSystem, 'textureGC') + .addSystem(FilterSystem, 'filter') + .addSystem(RenderTextureSystem, 'renderTexture') + .addSystem(BatchSystem, 'batch'); + + this.initPlugins(Renderer.__plugins); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ + if (options.context) + { + this.context.initFromContext(options.context); + } + else + { + this.context.initFromOptions({ + alpha: this.transparent, + antialias: options.antialias, + premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', + stencil: true, + 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); + } + + /** + * Add a new system to the renderer. + * @param {class} ClassRef - Class reference + * @param {string} [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. + * @return {PIXI.Renderer} Return instance of renderer + */ + addSystem(ClassRef, name) + { + if (!name) + { + name = ClassRef.name; + } + + // TODO - read name from class.name.. + + /* + if(name.includes('System')) + { + name = name.replace('System', ''); + name = name.charAt(0).toLowerCase() + name.slice(1); + } + */ + + const system = new ClassRef(this); + + if (this[name]) + { + throw new Error(`Whoops! ${name} is already a manger`); + } + + this[name] = system; + + for (const i in this.runners) + { + this.runners[i].add(system); + } + + return this; + + /** + * 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. + */ + } + + /** + * Renders the object to its webGL view + * + * @param {PIXI.DisplayObject} displayObject - the object to be rendered + * @param {PIXI.RenderTexture} renderTexture - The render texture to render to. + * @param {boolean} [clear] - Should the canvas be cleared before the new render + * @param {PIXI.Transform} [transform] - A transform to apply to the render texture before rendering. + * @param {boolean} [skipUpdateTransform] - Should we skip the update transform pass? + */ + render(displayObject, renderTexture, clear, transform, skipUpdateTransform) + { + // can be handy to know! + this.renderingToScreen = !renderTexture; + + this.runners.prerender.run(); + this.emit('prerender'); + + // 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.parent; + + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = 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.run(); + + this.emit('postrender'); + } + + /** + * Resizes the webGL view to the specified width and height. + * + * @param {number} screenWidth - the new width of the screen + * @param {number} screenHeight - the new height of the screen + */ + resize(screenWidth, screenHeight) + { + AbstractRenderer.prototype.resize.call(this, screenWidth, screenHeight); + + this.runners.resize.run(screenWidth, screenHeight); + } + + /** + * Resets the WebGL state so you can render things however you fancy! + * + * @return {PIXI.Renderer} Returns itself. + */ + reset() + { + this.runners.reset.run(); + + return this; + } + + clear() + { + this.framebuffer.bind(); + this.framebuffer.clear(); + } + + /** + * Removes everything from the renderer (event listeners, spritebatch, etc...) + * + * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. + * See: https://github.com/pixijs/pixi.js/issues/2233 + */ + destroy(removeView) + { + this.runners.destroy.run(); + + // call base destroy + super.destroy(removeView); + + // TODO nullify all the managers.. + this.gl = null; + } + + /** + * 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 + * builds and excluding plugins. + * @name PIXI.Renderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. + */ + + /** + * Adds a plugin to the renderer. + * + * @method + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + static registerPlugin(pluginName, ctor) + { + Renderer.__plugins = Renderer.__plugins || {}; + Renderer.__plugins[pluginName] = ctor; + } +} diff --git a/packages/core/src/System.js b/packages/core/src/System.js new file mode 100644 index 0000000..05a10c8 --- /dev/null +++ b/packages/core/src/System.js @@ -0,0 +1,42 @@ +/** + * System is a base class used for extending systems used by the {@link PIXI.Renderer} + * @see PIXI.Renderer#addSystem + * @class + * @memberof PIXI + */ +export default class System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + /** + * The renderer this manager works for. + * + * @member {PIXI.Renderer} + */ + this.renderer = renderer; + + this.renderer.runners.contextChange.add(this); + } + + /** + * Generic method called when there is a WebGL context change. + * + */ + contextChange() + { + // do some codes init! + } + + /** + * Generic destroy methods to be overridden by the subclass + * + */ + destroy() + { + this.renderer.runners.contextChange.remove(this); + this.renderer = null; + } +} diff --git a/packages/core/src/batch/BatchSystem.js b/packages/core/src/batch/BatchSystem.js new file mode 100644 index 0000000..b04c249 --- /dev/null +++ b/packages/core/src/batch/BatchSystem.js @@ -0,0 +1,68 @@ +import System from '../System'; +import ObjectRenderer from './ObjectRenderer'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class BatchSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + super(renderer); + + /** + * An empty renderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.emptyRenderer = new ObjectRenderer(renderer); + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.currentRenderer = this.emptyRenderer; + } + + /** + * Changes the current renderer to the one given in parameter + * + * @param {PIXI.ObjectRenderer} objectRenderer - The object renderer to use. + */ + setObjectRenderer(objectRenderer) + { + if (this.currentRenderer === objectRenderer) + { + return; + } + + this.currentRenderer.stop(); + this.currentRenderer = objectRenderer; + + this.renderer.state.setState(objectRenderer.state); + + this.currentRenderer.start(); + } + + /** + * This should be called if you wish to do some custom rendering + * It will basically render anything that may be batched up such as sprites + * + */ + flush() + { + this.setObjectRenderer(this.emptyRenderer); + } + + reset() + { + this.setObjectRenderer(this.emptyRenderer); + } +} diff --git a/packages/core/src/batch/ObjectRenderer.js b/packages/core/src/batch/ObjectRenderer.js new file mode 100644 index 0000000..0c851a9 --- /dev/null +++ b/packages/core/src/batch/ObjectRenderer.js @@ -0,0 +1,48 @@ +import System from '../System'; + +/** + * Base for a common object renderer that can be used as a system renderer plugin. + * + * @class + * @extends PIXI.System + * @memberof PIXI + */ +export default class ObjectRenderer extends System +{ + /** + * Starts the renderer and sets the shader + * + */ + start() + { + // set the shader.. + } + + /** + * Stops the renderer + * + */ + stop() + { + this.flush(); + } + + /** + * Stub method for rendering content and emptying the current batch. + * + */ + flush() + { + // flush! + } + + /** + * Renders an object + * + * @param {PIXI.DisplayObject} object - The object to render. + */ + render(object) // eslint-disable-line no-unused-vars + { + // render the object + } +} diff --git a/packages/core/src/context/ContextSystem.js b/packages/core/src/context/ContextSystem.js new file mode 100644 index 0000000..301aab4 --- /dev/null +++ b/packages/core/src/context/ContextSystem.js @@ -0,0 +1,186 @@ +import System from '../System'; +import { settings } from '@pixi/settings'; + +let CONTEXT_UID = 0; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class ContextSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.webGLVersion = 1; + + this.handleContextLost = this.handleContextLost.bind(this); + this.handleContextRestored = this.handleContextRestored.bind(this); + + this.extensions = {}; + + renderer.view.addEventListener('webglcontextlost', this.handleContextLost, false); + renderer.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + } + + get isLost() + { + return (!this.gl || this.gl.isContextLost()); + } + + contextChange(gl) + { + this.gl = gl; + + // restore a context if it was previously lost + if (gl.isContextLost() && gl.getExtension('WEBGL_lose_context')) + { + gl.getExtension('WEBGL_lose_context').restoreContext(); + } + } + + initFromContext(gl) + { + this.gl = gl; + this.validateContext(gl); + this.renderer.gl = gl; + this.renderer.CONTEXT_UID = CONTEXT_UID++; + this.renderer.runners.contextChange.run(gl); + } + + initFromOptions(options) + { + const gl = this.createContext(this.renderer.view, options); + + this.initFromContext(gl); + } + + /** + * Helper class to create a webGL Context + * + * @class + * @memberof PIXI.glCore + * @param canvas {HTMLCanvasElement} the canvas element that we will get the context from + * @param options {Object} An options object that gets passed in to the canvas element containing the context attributes, + * see https://developer.mozilla.org/en/docs/Web/API/HTMLCanvasElement/getContext for the options available + * @return {WebGLRenderingContext} the WebGL context + */ + createContext(canvas, options) + { + let gl; + + if (settings.PREFER_WEBGL_2) + { + gl = canvas.getContext('webgl2', options); + } + + if (gl) + { + this.webGLVersion = 2; + } + else + { + this.webGLVersion = 1; + + gl = canvas.getContext('webgl', options) + || canvas.getContext('experimental-webgl', options); + + if (!gl) + { + // fail, not able to get a context + throw new Error('This browser does not support webGL. Try using the canvas renderer'); + } + } + + this.gl = gl; + + this.getExtensions(); + + return gl; + } + + getExtensions() + { + // time to set up default etensions that pixi uses.. + const gl = this.gl; + const extensions = this.extensions; + + if (this.webGLVersion === 1) + { + extensions.drawBuffers = gl.getExtension('WEBGL_draw_buffers'); + extensions.depthTexture = gl.getExtension('WEBKIT_WEBGL_depth_texture'); + extensions.floatTexture = gl.getExtension('OES_texture_float'); + extensions.loseContext = gl.getExtension('WEBGL_lose_context'); + + extensions.vertexArrayObject = gl.getExtension('OES_vertex_array_object') + || gl.getExtension('MOZ_OES_vertex_array_object') + || gl.getExtension('WEBKIT_OES_vertex_array_object'); + } + + // we don't use any specific WebGL 2 ones yet! + } + + /** + * Handles a lost webgl context + * + * @private + * @param {WebGLContextEvent} event - The context lost event. + */ + handleContextLost(event) + { + event.preventDefault(); + } + + /** + * Handles a restored webgl context + * + * @private + */ + handleContextRestored() + { + this.renderer.runners.contextChange.run(this.gl); + } + + destroy() + { + const view = this.renderer.view; + + // remove listeners + view.removeEventListener('webglcontextlost', this.handleContextLost); + view.removeEventListener('webglcontextrestored', this.handleContextRestored); + + this.gl.useProgram(null); + + if (this.extensions.loseContext) + { + this.extensions.loseContext.loseContext(); + } + } + + postrender() + { + this.gl.flush(); + } + + validateContext(gl) + { + const attributes = gl.getContextAttributes(); + + // this is going to be fairly simple for now.. but at least we have room to grow! + if (!attributes.stencil) + { + /* eslint-disable max-len */ + + /* eslint-disable no-console */ + console.warn('Provided WebGL context does not have a stencil buffer, masks may not render correctly'); + /* eslint-enable no-console */ + + /* eslint-enable max-len */ + } + } +} diff --git a/packages/core/src/filters/Filter.js b/packages/core/src/filters/Filter.js new file mode 100644 index 0000000..9d8cad8 --- /dev/null +++ b/packages/core/src/filters/Filter.js @@ -0,0 +1,140 @@ +import Shader from '../shader/Shader'; +import Program from '../shader/Program'; +import State from '../state/State'; +import { settings } from '@pixi/settings'; +// import extractUniformsFromSrc from './extractUniformsFromSrc'; +import defaultVertex from './defaultFilter.vert'; +import defaultFragment from './defaultFilter.frag'; + +// let math = require('../../math'); +/** + * @class + * @memberof PIXI + * @extends PIXI.Shader + */ +export default class Filter extends Shader +{ + /** + * @param {string} [vertexSrc] - The source of the vertex shader. + * @param {string} [fragmentSrc] - The source of the fragment shader. + * @param {object} [uniforms] - Custom uniforms to use to augment the built-in ones. + */ + constructor(vertexSrc, fragmentSrc, uniforms) + { + const program = Program.from(vertexSrc, fragmentSrc); + + super(program, uniforms); + + /** + * The padding of the filter. Some filters require extra space to breath such as a blur. + * Increasing this will add extra width and height to the bounds of the object that the + * filter is applied to. + * + * @member {number} + */ + this.padding = 0; + + /** + * The resolution of the filter. Setting this to be lower will lower the quality but + * increase the performance of the filter. + * + * @member {number} + */ + this.resolution = settings.RESOLUTION; + + /** + * If enabled is true the filter is applied, if false it will not. + * + * @member {boolean} + */ + this.enabled = true; + + /** + * If enabled, PixiJS will fit the filter area into boundaries for better performance. + * Switch it off if it does not work for specific shader. + * + * @member {boolean} + */ + this.autoFit = true; + + /** + * Legacy filters use position and uvs from attributes + * @member {boolean} + * @readonly + */ + this.legacy = !!this.program.attributeData.aTextureCoord; + + /** + * the webGL state the filter requires to render + * @member {PIXI.State} + */ + this.state = new State(); + } + + /** + * Applies the filter + * + * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + * @param {boolean} clear - Should the output be cleared before rendering to it + * @param {object} [currentState] - It's current state of filter. + * There are some useful properties in the currentState : + * target, filters, sourceFrame, destinationFrame, renderTarget, resolution + */ + apply(filterManager, input, output, clear, currentState, derp) // eslint-disable-line no-unused-vars + { + // do as you please! + + filterManager.applyFilter(this, input, output, clear, currentState, derp); + + // or just do a regular render.. + } + + /** + * Sets the blendmode of the filter + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL + */ + get blendMode() + { + return this.state.blendMode; + } + + set blendMode(value) // eslint-disable-line require-jsdoc + { + this.state.blendMode = value; + } + + /** + * The default vertex shader source + * + * @static + * @constant + */ + static get defaultVertexSrc() + { + return defaultVertex; + } + + /** + * The default fragment shader source + * + * @static + * @constant + */ + static get defaultFragmentSrc() + { + return defaultFragment; + } +} + +/** + * Used for caching shader IDs + * + * @static + * @private + */ +Filter.SOURCE_KEY_MAP = {}; + diff --git a/packages/core/src/filters/FilterSystem.js b/packages/core/src/filters/FilterSystem.js new file mode 100644 index 0000000..3174318 --- /dev/null +++ b/packages/core/src/filters/FilterSystem.js @@ -0,0 +1,431 @@ +import System from '../System'; + +import RenderTexture from '../renderTexture/RenderTexture'; +import Quad from '../utils/Quad'; +import QuadUv from '../utils/QuadUv'; +import { Rectangle } from '@pixi/math'; +import * as filterTransforms from './filterTransforms'; +import bitTwiddle from 'bit-twiddle'; +import UniformGroup from '../shader/UniformGroup'; +import { DRAW_MODES } from '@pixi/constants'; + +// +/** + * @ignore + * @class + */ +class FilterState +{ + /** + * + */ + constructor() + { + this.renderTexture = null; + this.sourceFrame = new Rectangle(); + this.destinationFrame = new Rectangle(); + this.filters = []; + this.target = null; + this.legacy = false; + this.resolution = 1; + } +} + +/** + * @class + * @memberof PIXI.systems + * @extends PIXI.System + */ +export default class FilterSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + /** + * stores a bunch of PO2 textures used for filtering + * @type {Object} + */ + this.texturePool = {}; + + /** + * a pool for storing filter states, save us creating new ones each tick + * @type {Array} + */ + this.statePool = []; + + /** + * A very simple geometry used when drawing a filter effect to the screen + * @type {Quad} + */ + this.quad = new Quad(); + + this.quadUv = new QuadUv(); + + /** + * Temporary rect for maths + * @type {PIXI.Rectangle} + */ + this.tempRect = new Rectangle(); + + this.activeState = {}; + + /** + * this uniform group is attached to filter uniforms when used + * @type {UniformGroup} + */ + this.globalUniforms = new UniformGroup({ + sourceFrame: this.tempRect, + destinationFrame: this.tempRect, + + // legacy variables + filterArea: new Float32Array(4), + filterClamp: new Float32Array(4), + }, true); + } + + /** + * Adds a new filter to the System. + * + * @param {PIXI.DisplayObject} target - The target of the filter to render. + * @param {PIXI.Filter[]} filters - The filters to apply. + */ + push(target, filters) + { + const renderer = this.renderer; + const filterStack = this.renderer.renderTexture.defaultFilterStack; + const state = this.statePool.pop() || new FilterState(); + + let resolution = filters[0].resolution; + let padding = filters[0].padding; + let autoFit = filters[0].autoFit; + let legacy = filters[0].legacy; + + for (let i = 1; i < filters.length; i++) + { + const filter = filters[i]; + + // lets use the lowest resolution.. + resolution = Math.min(resolution, filter.resolution); + // and the largest amount of padding! + padding = Math.max(padding, filter.padding); + // only auto fit if all filters are autofit + autoFit = autoFit || filter.autoFit; + + legacy = legacy || filter.legacy; + } + + filterStack.push(state); + + state.resolution = resolution; + + state.legacy = legacy; + + // round to whole number based on resolution + // TODO move that to the shader too? + state.sourceFrame = target.filterArea || target.getBounds(true); + + state.sourceFrame.pad(padding); + + if (autoFit) + { + state.sourceFrame.fit(this.renderer.renderTexture.destinationFrame); + } + + state.sourceFrame.round(resolution); + + state.renderTexture = this.getPotFilterTexture(state.sourceFrame.width, state.sourceFrame.height, resolution); + state.filters = filters; + + state.destinationFrame.width = state.renderTexture.width; + state.destinationFrame.height = state.renderTexture.height; + + state.renderTexture.filterFrame = state.sourceFrame; + renderer.renderTexture.bind(state.renderTexture, state.sourceFrame);// /, state.destinationFrame); + renderer.renderTexture.clear(); + } + + /** + * Pops off the filter and applies it. + * + */ + pop() + { + const renderer = this.renderer; + const filterStack = renderer.renderTexture.defaultFilterStack; + const state = filterStack.pop(); + const filters = state.filters; + + this.activeState = state; + + const globalUniforms = this.globalUniforms.uniforms; + + globalUniforms.sourceFrame = state.sourceFrame; + globalUniforms.destinationFrame = state.destinationFrame; + globalUniforms.resolution = state.resolution; + + // only update the rect if its legacy.. + if (state.legacy) + { + const filterArea = globalUniforms.filterArea; + const filterClamp = globalUniforms.filterClamp; + + filterArea[0] = state.destinationFrame.width; + filterArea[1] = state.destinationFrame.height; + filterArea[2] = state.sourceFrame.x; + filterArea[3] = state.sourceFrame.y; + + filterClamp[0] = 0.5 / state.resolution / state.destinationFrame.width; + filterClamp[1] = 0.5 / state.resolution / state.destinationFrame.height; + filterClamp[2] = (state.sourceFrame.width - 0.5) / state.resolution / state.destinationFrame.width; + filterClamp[3] = (state.sourceFrame.height - 0.5) / state.resolution / state.destinationFrame.height; + } + + this.globalUniforms.update(); + + const lastState = filterStack[filterStack.length - 1]; + + if (filters.length === 1) + { + filters[0].apply(this, state.renderTexture, lastState.renderTexture, false, state); + + this.returnFilterTexture(state.renderTexture); + } + else + { + let flip = state.renderTexture; + let flop = this.getPotFilterTexture( + flip.width, + flip.height, + state.resolution + ); + + flop.filterFrame = flip.filterFrame; + + let i = 0; + + for (i = 0; i < filters.length - 1; ++i) + { + filters[i].apply(this, flip, flop, true, state); + + const t = flip; + + flip = flop; + flop = t; + } + + filters[i].apply(this, flip, lastState.renderTexture, false, state); + + this.returnFilterTexture(flip); + this.returnFilterTexture(flop); + } + + this.statePool.push(state); + } + + /** + * Draws a filter. + * + * @param {PIXI.Filter} filter - The filter to draw. + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + * @param {boolean} clear - Should the output be cleared before rendering to it + */ + applyFilter(filter, input, output, clear) + { + const renderer = this.renderer; + + renderer.renderTexture.bind(output, output ? output.filterFrame : null); + + if (clear) + { + // gl.disable(gl.SCISSOR_TEST); + renderer.renderTexture.clear(); + // gl.enable(gl.SCISSOR_TEST); + } + + // set the uniforms.. + filter.uniforms.uSampler = input; + filter.uniforms.filterGlobals = this.globalUniforms; + + // TODO make it so that the order of this does not matter.. + // because it does at the moment cos of global uniforms. + // they need to get resynced + + renderer.state.setState(filter.state); + renderer.shader.bind(filter); + + if (filter.legacy) + { + this.quadUv.map(input._frame, input.filterFrame); + + renderer.geometry.bind(this.quadUv); + renderer.geometry.draw(DRAW_MODES.TRIANGLES); + } + else + { + renderer.geometry.bind(this.quad); + renderer.geometry.draw(DRAW_MODES.TRIANGLE_STRIP); + } + } + + /** + * Calculates the mapped matrix. + * + * TODO playing around here.. this is temporary - (will end up in the shader) + * this returns a matrix that will normalise map filter cords in the filter to screen space + * + * @param {PIXI.Matrix} outputMatrix - the matrix to output to. + * @return {PIXI.Matrix} The mapped matrix. + */ + calculateScreenSpaceMatrix(outputMatrix) + { + const currentState = this.activeState; + + return filterTransforms.calculateScreenSpaceMatrix( + outputMatrix, + currentState.sourceFrame, + currentState.destinationFrame + ); + } + + /** + * This will map the filter coord so that a texture can be used based on the transform of a sprite + * + * @param {PIXI.Matrix} outputMatrix - The matrix to output to. + * @param {PIXI.Sprite} sprite - The sprite to map to. + * @return {PIXI.Matrix} The mapped matrix. + */ + calculateSpriteMatrix(outputMatrix, sprite) + { + const currentState = this.activeState; + + return filterTransforms.calculateSpriteMatrix( + outputMatrix, + currentState.sourceFrame, + currentState.destinationFrame, + sprite + ); + } + + /** + * Destroys this Filter System. + * + * @param {boolean} [contextLost=false] context was lost, do not free shaders + * + */ + destroy(contextLost = false) + { + if (!contextLost) + { + this.emptyPool(); + } + else + { + this.texturePool = {}; + } + } + + /** + * Gets a Power-of-Two render texture. + * + * TODO move to a seperate class could be on renderer? + * also - could cause issue with multiple contexts? + * + * @private + * @param {WebGLRenderingContext} gl - The webgl rendering context + * @param {number} minWidth - The minimum width of the render target. + * @param {number} minHeight - The minimum height of the render target. + * @param {number} resolution - The resolution of the render target. + * @return {PIXI.RenderTarget} The new render target. + */ + getPotFilterTexture(minWidth, minHeight, resolution) + { + minWidth = bitTwiddle.nextPow2(minWidth); + minHeight = bitTwiddle.nextPow2(minHeight); + resolution = resolution || 1; + + const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + + if (!this.texturePool[key]) + { + this.texturePool[key] = []; + } + + let renderTexture = this.texturePool[key].pop(); + + if (!renderTexture) + { + // temporary bypass cache.. + // internally - this will cause a texture to be bound.. + renderTexture = RenderTexture.create({ + width: minWidth, + height: minHeight, + resolution, + }); + } + + return renderTexture; + } + + /** + * Gets extra render texture to use inside current filter + * + * @param {number} resolution resolution of the renderTexture + * @returns {PIXI.RenderTexture} + */ + getFilterTexture(resolution) + { + const rt = this.activeState.renderTexture; + + const filterTexture = this.getPotFilterTexture(rt.width, rt.height, resolution || rt.baseTexture.resolution); + + filterTexture.filterFrame = rt.filterFrame; + + return filterTexture; + } + + /** + * Frees a render texture back into the pool. + * + * @param {PIXI.RenderTarget} renderTexture - The renderTarget to free + */ + returnFilterTexture(renderTexture) + { + renderTexture.filterFrame = null; + + const base = renderTexture.baseTexture; + + const minWidth = base.width; + const minHeight = base.height; + + const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + + this.texturePool[key].push(renderTexture); + } + + /** + * Empties the texture pool. + * + */ + emptyPool() + { + for (const i in this.texturePool) + { + const textures = this.texturePool[i]; + + if (textures) + { + for (let j = 0; j < textures.length; j++) + { + textures[j].destroy(true); + } + } + } + + this.texturePool = {}; + } +} diff --git a/packages/core/src/filters/defaultFilter.frag b/packages/core/src/filters/defaultFilter.frag new file mode 100644 index 0000000..a14c1be --- /dev/null +++ b/packages/core/src/filters/defaultFilter.frag @@ -0,0 +1,22 @@ +varying vec2 vTextureCoord; +varying vec2 vFilterCoord; + +uniform sampler2D uSampler; +uniform sampler2D filterSampler; + +void main(void){ + vec4 masky = texture2D(filterSampler, vFilterCoord); + vec4 sample = texture2D(uSampler, vTextureCoord); + vec4 color; + if(mod(vFilterCoord.x, 1.0) > 0.5) + { + color = vec4(1.0, 0.0, 0.0, 1.0); + } + else + { + color = vec4(0.0, 1.0, 0.0, 1.0); + } + // gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0); + gl_FragColor = mix(sample, masky, 0.5); + gl_FragColor *= sample.a; +} diff --git a/packages/core/src/filters/defaultFilter.vert b/packages/core/src/filters/defaultFilter.vert new file mode 100644 index 0000000..e1febca --- /dev/null +++ b/packages/core/src/filters/defaultFilter.vert @@ -0,0 +1,14 @@ +attribute vec2 aVertexPosition; +attribute vec2 aTextureCoord; + +uniform mat3 projectionMatrix; +uniform mat3 filterMatrix; + +varying vec2 vTextureCoord; +varying vec2 vFilterCoord; + +void main(void){ + gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); + vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy; + vTextureCoord = aTextureCoord ; +} diff --git a/packages/core/src/filters/filterTransforms.js b/packages/core/src/filters/filterTransforms.js new file mode 100644 index 0000000..87a3cd6 --- /dev/null +++ b/packages/core/src/filters/filterTransforms.js @@ -0,0 +1,50 @@ +import { Matrix } from '@pixi/math'; + +/** + * Calculates the mapped matrix + * @param filterArea {Rectangle} The filter area + * @param sprite {Sprite} the target sprite + * @param outputMatrix {Matrix} @alvin + * @private + */ +// this returns a matrix that will normalise map filter cords in the filter to screen space +export function calculateScreenSpaceMatrix(outputMatrix, filterArea, textureSize) +{ + // TODO unwrap? + const mappedMatrix = outputMatrix.identity(); + + mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); + + mappedMatrix.scale(textureSize.width, textureSize.height); + + return mappedMatrix; +} + +export function calculateNormalizedScreenSpaceMatrix(outputMatrix, filterArea, textureSize) +{ + const mappedMatrix = outputMatrix.identity(); + + mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); + + const translateScaleX = (textureSize.width / filterArea.width); + const translateScaleY = (textureSize.height / filterArea.height); + + mappedMatrix.scale(translateScaleX, translateScaleY); + + return mappedMatrix; +} + +// this will map the filter coord so that a texture can be used based on the transform of a sprite +export function calculateSpriteMatrix(outputMatrix, filterArea, textureSize, sprite) +{ + const orig = sprite._texture.orig; + const mappedMatrix = outputMatrix.set(textureSize.width, 0, 0, textureSize.height, filterArea.x, filterArea.y); + const worldTransform = sprite.worldTransform.copyTo(Matrix.TEMP_MATRIX); + + worldTransform.invert(); + mappedMatrix.prepend(worldTransform); + mappedMatrix.scale(1.0 / orig.width, 1.0 / orig.height); + mappedMatrix.translate(sprite.anchor.x, sprite.anchor.y); + + return mappedMatrix; +} diff --git a/packages/core/src/filters/spriteMask/SpriteMaskFilter.js b/packages/core/src/filters/spriteMask/SpriteMaskFilter.js new file mode 100644 index 0000000..91697b7 --- /dev/null +++ b/packages/core/src/filters/spriteMask/SpriteMaskFilter.js @@ -0,0 +1,63 @@ +import Filter from '../Filter'; +import { Matrix } from '@pixi/math'; +import vertex from './spriteMaskFilter.vert'; +import fragment from './spriteMaskFilter.frag'; +import { default as TextureMatrix } from '../../textures/TextureMatrix'; + +/** + * The SpriteMaskFilter class + * + * @class + * @extends PIXI.Filter + * @memberof PIXI + */ +export default class SpriteMaskFilter extends Filter +{ + /** + * @param {PIXI.Sprite} sprite - the target sprite + */ + constructor(sprite) + { + const maskMatrix = new Matrix(); + + super(vertex, fragment); + + sprite.renderable = false; + + this.maskSprite = sprite; + this.maskMatrix = maskMatrix; + } + + /** + * Applies the filter + * + * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + */ + apply(filterManager, input, output) + { + const maskSprite = this.maskSprite; + const tex = this.maskSprite.texture; + + if (!tex.valid) + { + return; + } + if (!tex.transform) + { + // margin = 0.0, let it bleed a bit, shader code becomes easier + // assuming that atlas textures were made with 1-pixel padding + tex.transform = new TextureMatrix(tex, 0.0); + } + tex.transform.update(); + + this.uniforms.mask = tex; + this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite) + .prepend(tex.transform.mapCoord); + this.uniforms.alpha = maskSprite.worldAlpha; + this.uniforms.maskClamp = tex.transform.uClampFrame; + + filterManager.applyFilter(this, input, output); + } +} diff --git a/packages/core/src/filters/spriteMask/spriteMaskFilter.frag b/packages/core/src/filters/spriteMask/spriteMaskFilter.frag new file mode 100644 index 0000000..0e4aef8 --- /dev/null +++ b/packages/core/src/filters/spriteMask/spriteMaskFilter.frag @@ -0,0 +1,23 @@ +varying vec2 vMaskCoord; +varying vec2 vTextureCoord; + +uniform sampler2D uSampler; +uniform sampler2D mask; +uniform float alpha; +uniform vec4 maskClamp; + +void main(void) +{ + float clip = step(3.5, + step(maskClamp.x, vMaskCoord.x) + + step(maskClamp.y, vMaskCoord.y) + + step(vMaskCoord.x, maskClamp.z) + + step(vMaskCoord.y, maskClamp.w)); + + vec4 original = texture2D(uSampler, vTextureCoord); + vec4 masky = texture2D(mask, vMaskCoord); + + original *= (masky.r * masky.a * alpha * clip); + + gl_FragColor = original; +} diff --git a/packages/core/src/filters/spriteMask/spriteMaskFilter.vert b/packages/core/src/filters/spriteMask/spriteMaskFilter.vert new file mode 100644 index 0000000..d23a30c --- /dev/null +++ b/packages/core/src/filters/spriteMask/spriteMaskFilter.vert @@ -0,0 +1,16 @@ +attribute vec2 aVertexPosition; +attribute vec2 aTextureCoord; + +uniform mat3 projectionMatrix; +uniform mat3 otherMatrix; + +varying vec2 vMaskCoord; +varying vec2 vTextureCoord; + +void main(void) +{ + gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); + + vTextureCoord = aTextureCoord; + vMaskCoord = ( otherMatrix * vec3( aTextureCoord, 1.0) ).xy; +} diff --git a/packages/core/src/framebuffer/FrameBuffer.js b/packages/core/src/framebuffer/FrameBuffer.js new file mode 100644 index 0000000..84e9fcc --- /dev/null +++ b/packages/core/src/framebuffer/FrameBuffer.js @@ -0,0 +1,106 @@ +import Texture from '../textures/BaseTexture'; +import { FORMATS, TYPES } from '@pixi/constants'; + +/** + * Frame buffer + * @class + * @memberof PIXI + */ +export default class FrameBuffer +{ + constructor(width, height) + { + this.width = width || 100; + this.height = height || 100; + + this.stencil = false; + this.depth = false; + + this.dirtyId = 0; + this.dirtyFormat = 0; + this.dirtySize = 0; + + this.depthTexture = null; + this.colorTextures = []; + + this.glFrameBuffers = {}; + } + + get colorTexture() + { + return this.colorTextures[0]; + } + + addColorTexture(index, texture) + { + // TODO add some validation to the texture - same width / height etc? + this.colorTextures[index || 0] = texture || new Texture(null, { scaleMode: 0, + resolution: 1, + mipmap: false, + width: this.width, + height: this.height });// || new Texture(); + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + addDepthTexture(texture) + { + /* eslint-disable max-len */ + this.depthTexture = texture || new Texture(null, { scaleMode: 0, + resolution: 1, + width: this.width, + height: this.height, + mipmap: false, + format: FORMATS.DEPTH_COMPONENT, + type: TYPES.UNSIGNED_SHORT });// UNSIGNED_SHORT; + /* eslint-disable max-len */ + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + enableDepth() + { + this.depth = true; + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + enableStencil() + { + this.stencil = true; + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + resize(width, height) + { + if (width === this.width && height === this.height) return; + + this.width = width; + this.height = height; + + this.dirtyId++; + this.dirtySize++; + + for (let i = 0; i < this.colorTextures.length; i++) + { + this.colorTextures[i].setSize(width, height); + } + + if (this.depthTexture) + { + this.depthTexture.setSize(width, height); + } + } +} diff --git a/packages/core/src/framebuffer/FramebufferSystem.js b/packages/core/src/framebuffer/FramebufferSystem.js new file mode 100644 index 0000000..2cc2437 --- /dev/null +++ b/packages/core/src/framebuffer/FramebufferSystem.js @@ -0,0 +1,246 @@ +import System from '../System'; +import { Rectangle } from '@pixi/math'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class FramebufferSystem extends System +{ + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + contextChange() + { + this.gl = this.renderer.gl; + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + this.current = null; + this.viewport = new Rectangle(); + + this.drawBufferExtension = this.renderer.context.extensions.drawBuffers; + } + + bind(framebuffer, frame) + { + const gl = this.gl; + + this.current = framebuffer; + + if (framebuffer) + { + // TODO cacheing layer! + + const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID] || this.initFramebuffer(framebuffer); + + gl.bindFramebuffer(gl.FRAMEBUFFER, fbo.framebuffer); + // makesure all textures are unbound.. + + // now check for updates... + if (fbo.dirtyId !== framebuffer.dirtyId) + { + fbo.dirtyId = framebuffer.dirtyId; + + if (fbo.dirtyFormat !== framebuffer.dirtyFormat) + { + fbo.dirtyFormat = framebuffer.dirtyFormat; + this.updateFramebuffer(framebuffer); + } + else if (fbo.dirtySize !== framebuffer.dirtySize) + { + fbo.dirtySize = framebuffer.dirtySize; + this.resizeFramebuffer(framebuffer); + } + } + + for (let i = 0; i < framebuffer.colorTextures.length; i++) + { + if (framebuffer.colorTextures[i].texturePart) + { + this.renderer.texture.unbind(framebuffer.colorTextures[i].texture); + } + else + { + this.renderer.texture.unbind(framebuffer.colorTextures[i]); + } + } + + if (framebuffer.depthTexture) + { + this.renderer.texture.unbind(framebuffer.depthTexture); + } + + if (frame) + { + this.setViewport(frame.x, frame.y, frame.width, frame.height); + } + else + { + this.setViewport(0, 0, framebuffer.width, framebuffer.height); + } + } + else + { + gl.bindFramebuffer(gl.FRAMEBUFFER, null); + + if (frame) + { + this.setViewport(frame.x, frame.y, frame.width, frame.height); + } + else + { + this.setViewport(0, 0, this.renderer.width, this.renderer.height); + } + } + } + + setViewport(x, y, width, height) + { + const v = this.viewport; + + if (v.width !== width || v.height !== height || v.x !== x || v.y !== y) + { + v.x = x; + v.y = y; + v.width = width; + v.height = height; + + this.gl.viewport(x, y, width, height); + } + } + + get size() + { + if (this.current) + { + // TODO store temp + return { x: 0, y: 0, width: this.current.width, height: this.current.height }; + } + + return { x: 0, y: 0, width: this.renderer.width, height: this.renderer.height }; + } + + clear(r, g, b, a) + { + const gl = this.gl; + + // TODO clear color can be set only one right? + gl.clearColor(r, g, b, a); + gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); + } + + // private functions... + + initFramebuffer(framebuffer) + { + const gl = this.gl; + + // TODO - make this a class? + const fbo = { + framebuffer: gl.createFramebuffer(), + stencil: null, + dirtyId: 0, + dirtyFormat: 0, + dirtySize: 0, + }; + + framebuffer.glFrameBuffers[this.CONTEXT_UID] = fbo; + + return fbo; + } + + resizeFramebuffer(framebuffer) + { + const gl = this.gl; + + if (framebuffer.stencil || framebuffer.depth) + { + gl.bindRenderbuffer(gl.RENDERBUFFER, this.stencil); + gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); + } + } + + updateFramebuffer(framebuffer) + { + const gl = this.gl; + + const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID]; + + // bind the color texture + const colorTextures = framebuffer.colorTextures; + + let count = colorTextures.length; + + if (!this.drawBufferExtension) + { + count = Math.min(count, 1); + } + + const activeTextures = []; + + for (let i = 0; i < count; i++) + { + const texture = framebuffer.colorTextures[i]; + + if (texture.texturePart) + { + this.renderer.texture.bind(texture.texture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.COLOR_ATTACHMENT0 + i, + gl.TEXTURE_CUBE_MAP_NEGATIVE_X + texture.side, + texture.texture._glTextures[this.CONTEXT_UID].texture, + 0); + } + else + { + this.renderer.texture.bind(texture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.COLOR_ATTACHMENT0 + i, + gl.TEXTURE_2D, + texture._glTextures[this.CONTEXT_UID].texture, + 0); + } + + activeTextures.push(gl.COLOR_ATTACHMENT0 + i); + } + + if (this.drawBufferExtension && activeTextures.length > 1) + { + this.drawBufferExtension.drawBuffersWEBGL(activeTextures); + } + + if (framebuffer.depthTexture) + { + const depthTextureExt = this.renderer.context.extensions.depthTexture; + + if (depthTextureExt) + { + const depthTexture = framebuffer.depthTexture; + + this.renderer.texture.bind(depthTexture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.DEPTH_ATTACHMENT, + gl.TEXTURE_2D, + depthTexture._glTextures[this.CONTEXT_UID].texture, + 0); + } + } + + if (framebuffer.stencil || framebuffer.depth) + { + fbo.stencil = gl.createRenderbuffer(); + + gl.bindRenderbuffer(gl.RENDERBUFFER, fbo.stencil); + + // TODO.. this is depth AND stencil? + gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, fbo.stencil); + gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); + // fbo.enableStencil(); + } + } +} diff --git a/packages/core/src/geometry/GLBuffer.js b/packages/core/src/geometry/GLBuffer.js new file mode 100644 index 0000000..0477e8a --- /dev/null +++ b/packages/core/src/geometry/GLBuffer.js @@ -0,0 +1,9 @@ +export default class GLBuffer +{ + constructor(buffer) + { + this.buffer = buffer; + this.updateID = -1; + this.byteLength = -1; + } +} diff --git a/packages/core/src/geometry/GeometrySystem.js b/packages/core/src/geometry/GeometrySystem.js new file mode 100644 index 0000000..959f827 --- /dev/null +++ b/packages/core/src/geometry/GeometrySystem.js @@ -0,0 +1,388 @@ +import System from '../System'; +import GLBuffer from './GLBuffer'; + +const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class GeometrySystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this._activeGeometry = null; + this._activeVao = null; + + this.hasVao = true; + this.hasInstance = true; + } + + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + contextChange() + { + const gl = this.gl = this.renderer.gl; + + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + + // webgl2 + if (!gl.createVertexArray) + { + // webgl 1! + let nativeVaoExtension = this.renderer.context.extensions.vertexArrayObject; + + if (this.renderer.legacy) + { + nativeVaoExtension = null; + } + + if (nativeVaoExtension) + { + gl.createVertexArray = () => + nativeVaoExtension.createVertexArrayOES(); + + gl.bindVertexArray = (vao) => + nativeVaoExtension.bindVertexArrayOES(vao); + + gl.deleteVertexArray = (vao) => + nativeVaoExtension.deleteVertexArrayOES(vao); + } + else + { + this.hasVao = false; + gl.createVertexArray = () => + { + // empty + }; + + gl.bindVertexArray = () => + { + // empty + }; + + gl.deleteVertexArray = () => + { + // empty + }; + } + } + + if (!gl.vertexAttribDivisor) + { + const instanceExt = gl.getExtension('ANGLE_instanced_arrays'); + + if (instanceExt) + { + gl.vertexAttribDivisor = (a, b) => + instanceExt.vertexAttribDivisorANGLE(a, b); + + gl.drawElementsInstanced = (a, b, c, d, e) => + instanceExt.drawElementsInstancedANGLE(a, b, c, d, e); + + gl.drawArraysInstanced = (a, b, c, d) => + instanceExt.drawArraysInstancedANGLE(a, b, c, d); + } + else + { + this.hasInstance = false; + } + } + } + + /** + * Binds geometry so that is can be drawn. Creating a Vao if required + * @private + * @param {PIXI.Geometry} geometry instance of geometry to bind + */ + bind(geometry, shader) + { + shader = shader || this.renderer.shader.shader; + + const gl = this.gl; + + // not sure the best way to address this.. + // currently different shaders require different VAOs for the same geometry + // Still mulling over the best way to solve this one.. + // will likely need to modify the shader attribute locations at run time! + let vaos = geometry.glVertexArrayObjects[this.CONTEXT_UID]; + + if (!vaos) + { + geometry.glVertexArrayObjects[this.CONTEXT_UID] = vaos = {}; + } + + const vao = vaos[shader.program.id] || this.initGeometryVao(geometry, shader.program); + + this._activeGeometry = geometry; + + if (this._activeVao !== vao) + { + this._activeVao = vao; + + if (this.hasVao) + { + gl.bindVertexArray(vao); + } + else + { + this.activateVao(geometry, shader.program); + } + } + + // TODO - optimise later! + // don't need to loop through if nothing changed! + // maybe look to add an 'autoupdate' to geometry? + this.updateBuffers(); + } + + reset() + { + this.unbind(); + } + + updateBuffers() + { + const geometry = this._activeGeometry; + const gl = this.gl; + + for (let i = 0; i < geometry.buffers.length; i++) + { + const buffer = geometry.buffers[i]; + + const glBuffer = buffer._glBuffers[this.CONTEXT_UID]; + + if (buffer._updateID !== glBuffer.updateID) + { + glBuffer.updateID = buffer._updateID; + + // TODO can cache this on buffer! maybe added a getter / setter? + const type = buffer.index ? gl.ELEMENT_ARRAY_BUFFER : gl.ARRAY_BUFFER; + const drawType = buffer.static ? gl.STATIC_DRAW : gl.DYNAMIC_DRAW; + + gl.bindBuffer(type, glBuffer.buffer); + + if (glBuffer.byteLength >= buffer.data.byteLength) + { + // offset is always zero for now! + gl.bufferSubData(type, 0, buffer.data); + } + else + { + gl.bufferData(type, buffer.data, drawType); + } + } + } + } + + checkCompatability(geometry, program) + { + // geometry must have at least all the attributes that the shader requires. + const geometryAttributes = geometry.attributes; + const shaderAttributes = program.attributeData; + + for (const j in shaderAttributes) + { + if (!geometryAttributes[j]) + { + throw new Error(`shader and geometry incompatible, geometry missing the "${j}" attribute`); + } + } + } + + /** + * Creates a Vao with the same structure as the geometry and stores it on the geometry. + * @private + * @param {PIXI.Geometry} geometry instance of geometry to to generate Vao for + */ + initGeometryVao(geometry, program) + { + this.checkCompatability(geometry, program); + + const gl = this.gl; + const CONTEXT_UID = this.CONTEXT_UID; + const buffers = geometry.buffers; + const attributes = geometry.attributes; + + const tempStride = {}; + const tempStart = {}; + + for (const j in buffers) + { + tempStride[j] = 0; + tempStart[j] = 0; + } + + for (const j in attributes) + { + if (!attributes[j].size && program.attributeData[j]) + { + attributes[j].size = program.attributeData[j].size; + } + + tempStride[attributes[j].buffer] += attributes[j].size * byteSizeMap[attributes[j].type]; + } + + for (const j in attributes) + { + const attribute = attributes[j]; + const attribSize = attribute.size; + + if (attribute.stride === undefined) + { + if (tempStride[attribute.buffer] === attribSize * byteSizeMap[attribute.type]) + { + attribute.stride = 0; + } + else + { + attribute.stride = tempStride[attribute.buffer]; + } + } + + if (attribute.start === undefined) + { + attribute.start = tempStart[attribute.buffer]; + + tempStart[attribute.buffer] += attribSize * byteSizeMap[attribute.type]; + } + } + + // first update - and create the buffers! + // only create a gl buffer if it actually gets + for (let i = 0; i < buffers.length; i++) + { + const buffer = buffers[i]; + + if (!buffer._glBuffers[CONTEXT_UID]) + { + buffer._glBuffers[CONTEXT_UID] = new GLBuffer(gl.createBuffer()); + } + } + + // TODO - maybe make this a data object? + // lets wait to see if we need to first! + const vao = gl.createVertexArray(); + + gl.bindVertexArray(vao); + + this.activateVao(geometry, program); + + gl.bindVertexArray(null); + + // add it to the cache! + geometry.glVertexArrayObjects[this.CONTEXT_UID][program.id] = vao; + + return vao; + } + + activateVao(geometry, program) + { + const gl = this.gl; + const CONTEXT_UID = this.CONTEXT_UID; + const buffers = geometry.buffers; + const attributes = geometry.attributes; + + if (geometry.indexBuffer) + { + // first update the index buffer if we have one.. + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, geometry.indexBuffer._glBuffers[CONTEXT_UID].buffer); + } + + let lastBuffer = null; + + // add a new one! + for (const j in attributes) + { + const attribute = attributes[j]; + const buffer = buffers[attribute.buffer]; + const glBuffer = buffer._glBuffers[CONTEXT_UID]; + + if (program.attributeData[j]) + { + if (lastBuffer !== glBuffer) + { + gl.bindBuffer(gl.ARRAY_BUFFER, glBuffer.buffer); + + lastBuffer = glBuffer; + } + + const location = program.attributeData[j].location; + + // TODO introduce state again + // we can optimise this for older devices that have no VAOs + gl.enableVertexAttribArray(location); + + gl.vertexAttribPointer(location, + attribute.size, + attribute.type || gl.FLOAT, + attribute.normalized, + attribute.stride, + attribute.start); + + if (attribute.instance) + { + // TODO calculate instance count based of this... + if (this.hasInstance) + { + gl.vertexAttribDivisor(location, 1); + } + else + { + throw new Error('geometry error, GPU Instancing is not supported on this device'); + } + } + } + } + } + + draw(type, size, start, instanceCount) + { + const gl = this.gl; + const geometry = this._activeGeometry; + + // TODO.. this should not change so maybe cache the function? + + if (geometry.indexBuffer) + { + if (geometry.instanced) + { + /* eslint-disable max-len */ + gl.drawElementsInstanced(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2, instanceCount || 1); + /* eslint-enable max-len */ + } + else + { + gl.drawElements(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2); + } + } + else + if (geometry.instanced) + { + // TODO need a better way to calculate size.. + gl.drawArraysInstanced(type, start, size || geometry.getSize(), instanceCount || 1); + } + else + { + gl.drawArrays(type, start, size || geometry.getSize()); + } + + return this; + } + + unbind() + { + this.gl.bindVertexArray(null); + this._activeVao = null; + this._activeGeometry = null; + } +} diff --git a/packages/core/src/geometry/utils/setVertexAttribArrays.js b/packages/core/src/geometry/utils/setVertexAttribArrays.js new file mode 100644 index 0000000..23b6959 --- /dev/null +++ b/packages/core/src/geometry/utils/setVertexAttribArrays.js @@ -0,0 +1,54 @@ +// var GL_MAP = {}; + +/** + * @param gl {WebGLRenderingContext} The current WebGL context + * @param attribs {*} + * @param state {*} + */ +export default function setVertexAttribArrays(gl, attribs, state) +{ + let i; + + if (state) + { + const tempAttribState = state.tempAttribState; + const attribState = state.attribState; + + for (i = 0; i < tempAttribState.length; i++) + { + tempAttribState[i] = false; + } + + // set the new attribs + for (i = 0; i < attribs.length; i++) + { + tempAttribState[attribs[i].attribute.location] = true; + } + + for (i = 0; i < attribState.length; i++) + { + if (attribState[i] !== tempAttribState[i]) + { + attribState[i] = tempAttribState[i]; + + if (state.attribState[i]) + { + gl.enableVertexAttribArray(i); + } + else + { + gl.disableVertexAttribArray(i); + } + } + } + } + else + { + for (i = 0; i < attribs.length; i++) + { + const attrib = attribs[i]; + + gl.enableVertexAttribArray(attrib.attribute.location); + } + } +} diff --git a/packages/core/src/index.js b/packages/core/src/index.js index 58c456a..fd1f547 100644 --- a/packages/core/src/index.js +++ b/packages/core/src/index.js @@ -1,30 +1,30 @@ import * as resources from './textures/resources'; -import * as systems from './renderers/systems'; +import * as systems from './systems'; -export { resources }; export { systems }; +export { resources }; -export { default as Renderer } from './renderers/Renderer'; -export { default as AbstractRenderer } from './renderers/AbstractRenderer'; -export { default as FrameBuffer } from './textures/FrameBuffer'; +export { default as System } from './System'; +export { default as Renderer } from './Renderer'; +export { default as AbstractRenderer } from './AbstractRenderer'; +export { default as FrameBuffer } from './framebuffer/FrameBuffer'; export { default as CubeTexture } from './textures/CubeTexture'; export { default as BaseTexture } from './textures/BaseTexture'; export { default as Texture } from './textures/Texture'; export { default as TextureMatrix } from './textures/TextureMatrix'; -export { default as RenderTexture } from './textures/RenderTexture'; -export { default as BaseRenderTexture } from './textures/BaseRenderTexture'; +export { default as RenderTexture } from './renderTexture/RenderTexture'; +export { default as BaseRenderTexture } from './renderTexture/BaseRenderTexture'; export { default as TextureUvs } from './textures/TextureUvs'; -export { default as State } from './renderers/State'; -export { default as ObjectRenderer } from './renderers/utils/ObjectRenderer'; -export { default as Quad } from './renderers/utils/Quad'; -export { default as QuadUv } from './renderers/utils/QuadUv'; -export { default as checkMaxIfStatmentsInShader } from './renderers/utils/checkMaxIfStatmentsInShader'; +export { default as State } from './state/State'; +export { default as ObjectRenderer } from './batch/ObjectRenderer'; +export { default as Quad } from './utils/Quad'; +export { default as QuadUv } from './utils/QuadUv'; +export { default as checkMaxIfStatmentsInShader } from './shader/utils/checkMaxIfStatmentsInShader'; export { default as Shader } from './shader/Shader'; export { default as Program } from './shader/Program'; export { default as UniformGroup } from './shader/UniformGroup'; -export { default as SpriteMaskFilter } from './renderers/filters/spriteMask/SpriteMaskFilter'; -export { default as Filter } from './renderers/filters/Filter'; +export { default as SpriteMaskFilter } from './filters/spriteMask/SpriteMaskFilter'; +export { default as Filter } from './filters/Filter'; export { default as Attribute } from './geometry/Attribute'; export { default as Buffer } from './geometry/Buffer'; export { default as Geometry } from './geometry/Geometry'; - diff --git a/packages/core/src/mask/MaskSystem.js b/packages/core/src/mask/MaskSystem.js new file mode 100644 index 0000000..99174c2 --- /dev/null +++ b/packages/core/src/mask/MaskSystem.js @@ -0,0 +1,199 @@ +import System from '../System'; +import AlphaMaskFilter from '../filters/spriteMask/SpriteMaskFilter'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class MaskSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + // TODO - we don't need both! + this.scissor = false; + this.scissorData = null; + this.scissorRenderTarget = null; + + this.enableScissor = false; + + this.alphaMaskPool = []; + this.alphaMaskIndex = 0; + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.DisplayObject} target - Display Object to push the mask to + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + push(target, maskData) + { + // TODO the root check means scissor rect will not + // be used on render textures more info here: + // https://github.com/pixijs/pixi.js/pull/3545 + + if (maskData.texture) + { + this.pushSpriteMask(target, maskData); + } + else if (this.enableScissor + && !this.scissor + && this.renderer._activeRenderTarget.root + && !this.renderer.stencil.stencilMaskStack.length + && maskData.isFastRect()) + { + const matrix = maskData.worldTransform; + + let rot = Math.atan2(matrix.b, matrix.a); + + // use the nearest degree! + rot = Math.round(rot * (180 / Math.PI)); + + if (rot % 90) + { + this.pushStencilMask(maskData); + } + else + { + this.pushScissorMask(target, maskData); + } + } + else + { + this.pushStencilMask(maskData); + } + } + + /** + * Removes the last mask from the mask stack and doesn't return it. + * + * @param {PIXI.DisplayObject} target - Display Object to pop the mask from + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + pop(target, maskData) + { + if (maskData.texture) + { + this.popSpriteMask(target, maskData); + } + else if (this.enableScissor && !this.renderer.stencil.stencilMaskStack.length) + { + this.popScissorMask(target, maskData); + } + else + { + this.popStencilMask(target, maskData); + } + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.RenderTarget} target - Display Object to push the sprite mask to + * @param {PIXI.Sprite} maskData - Sprite to be used as the mask + */ + pushSpriteMask(target, maskData) + { + let alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; + + if (!alphaMaskFilter) + { + alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; + } + + alphaMaskFilter[0].resolution = this.renderer.resolution; + alphaMaskFilter[0].maskSprite = maskData; + + // TODO - may cause issues! + target.filterArea = maskData.getBounds(true); + + this.renderer.filterSystem.pushFilter(target, alphaMaskFilter); + + this.alphaMaskIndex++; + } + + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popSpriteMask() + { + this.renderer.filterSystem.popFilter(); + this.alphaMaskIndex--; + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + pushStencilMask(maskData) + { + this.renderer.batch.flush(); + this.renderer.stencil.pushStencil(maskData); + } + + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popStencilMask() + { + // this.renderer.currentRenderer.stop(); + this.renderer.stencil.popStencil(); + } + + /** + * + * @param {PIXI.DisplayObject} target - Display Object to push the mask to + * @param {PIXI.Graphics} maskData - The masking data. + */ + pushScissorMask(target, maskData) + { + maskData.renderable = true; + + const renderTarget = this.renderer._activeRenderTarget; + + const bounds = maskData.getBounds(); + + bounds.fit(renderTarget.size); + maskData.renderable = false; + + this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); + + const resolution = this.renderer.resolution; + + this.renderer.gl.scissor( + bounds.x * resolution, + (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, + bounds.width * resolution, + bounds.height * resolution + ); + + this.scissorRenderTarget = renderTarget; + this.scissorData = maskData; + this.scissor = true; + } + + /** + * + * + */ + popScissorMask() + { + this.scissorRenderTarget = null; + this.scissorData = null; + this.scissor = false; + + // must be scissor! + const gl = this.renderer.gl; + + gl.disable(gl.SCISSOR_TEST); + } +} diff --git a/packages/core/src/mask/StencilSystem.js b/packages/core/src/mask/StencilSystem.js new file mode 100644 index 0000000..62796a5 --- /dev/null +++ b/packages/core/src/mask/StencilSystem.js @@ -0,0 +1,133 @@ +import System from '../System'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class StencilSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + this.stencilMaskStack = []; + } + + /** + * Changes the mask stack that is used by this System. + * + * @param {PIXI.Graphics[]} stencilMaskStack - The mask stack + */ + setMaskStack(stencilMaskStack) + { + const gl = this.renderer.gl; + + if (stencilMaskStack.length !== this.stencilMaskStack.length) + { + if (stencilMaskStack.length === 0) + { + gl.disable(gl.STENCIL_TEST); + } + else + { + gl.enable(gl.STENCIL_TEST); + } + } + + this.stencilMaskStack = stencilMaskStack; + } + + /** + * Applies the Mask and adds it to the current stencil stack. @alvin + * + * @param {PIXI.Graphics} graphics - The mask + */ + pushStencil(graphics) + { + this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); + + // this.renderer._activeRenderTarget.attachStencilBuffer(); + + const gl = this.renderer.gl; + const prevMaskCount = this.stencilMaskStack.length; + + if (prevMaskCount === 0) + { + gl.enable(gl.STENCIL_TEST); + } + + this.stencilMaskStack.push(graphics); + + // Increment the refference stencil value where the new mask overlaps with the old ones. + gl.colorMask(false, false, false, false); + gl.stencilFunc(gl.EQUAL, prevMaskCount, this._getBitwiseMask()); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.INCR); + this.renderer.plugins.graphics.render(graphics); + + this._useCurrent(); + } + + /** + * Removes the last mask from the stencil stack. @alvin + */ + popStencil() + { + this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); + + const gl = this.renderer.gl; + const graphics = this.stencilMaskStack.pop(); + + if (this.stencilMaskStack.length === 0) + { + // the stack is empty! + gl.disable(gl.STENCIL_TEST); + gl.clear(gl.STENCIL_BUFFER_BIT); + gl.clearStencil(0); + } + else + { + // Decrement the refference stencil value where the popped mask overlaps with the other ones + gl.colorMask(false, false, false, false); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.DECR); + this.renderer.plugins.graphics.render(graphics); + + this._useCurrent(); + } + } + + /** + * Setup renderer to use the current stencil data. + */ + _useCurrent() + { + const gl = this.renderer.gl; + + gl.colorMask(true, true, true, true); + gl.stencilFunc(gl.EQUAL, this.stencilMaskStack.length, this._getBitwiseMask()); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP); + } + + /** + * Fill 1s equal to the number of acitve stencil masks. + * + * @return {number} The bitwise mask. + */ + _getBitwiseMask() + { + return (1 << this.stencilMaskStack.length) - 1; + } + + /** + * Destroys the mask stack. + * + */ + destroy() + { + super.destroy(this); + + this.stencilMaskStack = null; + } +} diff --git a/packages/core/src/projection/ProjectionSystem.js b/packages/core/src/projection/ProjectionSystem.js new file mode 100644 index 0000000..6e63b73 --- /dev/null +++ b/packages/core/src/projection/ProjectionSystem.js @@ -0,0 +1,73 @@ +import System from '../System'; +import { Matrix } from '@pixi/math'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class ProjectionSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.projectionMatrix = new Matrix(); + } + + update(destinationFrame, sourceFrame, resolution, root) + { + this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; + this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; + + this.calculateProjection(this.destinationFrame, this.sourceFrame, resolution, root); + + this.renderer.globalUniforms.uniforms.projectionMatrix = this.projectionMatrix; + this.renderer.globalUniforms.update(); + } + + /** + * Updates the projection matrix based on a projection frame (which is a rectangle) + * + * @param {Rectangle} destinationFrame - The destination frame. + * @param {Rectangle} sourceFrame - The source frame. + */ + calculateProjection(destinationFrame, sourceFrame, resolution, root) + { + const pm = this.projectionMatrix; + + // I don't think we will need this line.. + // pm.identity(); + + if (!root) + { + pm.a = (1 / destinationFrame.width * 2) * resolution; + pm.d = (1 / destinationFrame.height * 2) * resolution; + + pm.tx = -1 - (sourceFrame.x * pm.a); + pm.ty = -1 - (sourceFrame.y * pm.d); + } + else + { + pm.a = (1 / destinationFrame.width * 2) * resolution; + pm.d = (-1 / destinationFrame.height * 2) * resolution; + + pm.tx = -1 - (sourceFrame.x * pm.a); + pm.ty = 1 - (sourceFrame.y * pm.d); + } + } + + /** + * Sets the transform of the active render target to the given matrix + * + * @param {PIXI.Matrix} matrix - The transformation matrix + */ + setTransform()// matrix) + { + // this._activeRenderTarget.transform = matrix; + } +} diff --git a/packages/core/src/renderTexture/BaseRenderTexture.js b/packages/core/src/renderTexture/BaseRenderTexture.js new file mode 100644 index 0000000..88eb83b --- /dev/null +++ b/packages/core/src/renderTexture/BaseRenderTexture.js @@ -0,0 +1,137 @@ +import BaseTexture from '../textures/BaseTexture'; +import FrameBuffer from '../framebuffer/FrameBuffer'; + +/** + * A BaseRenderTexture is a special texture that allows any PixiJS display object to be rendered to it. + * + * __Hint__: All DisplayObjects (i.e. Sprites) that render to a BaseRenderTexture should be preloaded + * otherwise black rectangles will be drawn instead. + * + * A BaseRenderTexture takes a snapshot of any Display Object given to its render method. The position + * and rotation of the given Display Objects is ignored. For example: + * + * ```js + * let renderer = PIXI.autoDetectRenderer(1024, 1024, { view: canvas, ratio: 1 }); + * let baseRenderTexture = new PIXI.BaseRenderTexture(renderer, 800, 600); + * let sprite = PIXI.Sprite.fromImage("spinObj_01.png"); + * + * sprite.position.x = 800/2; + * sprite.position.y = 600/2; + * sprite.anchor.x = 0.5; + * sprite.anchor.y = 0.5; + * + * baseRenderTexture.render(sprite); + * ``` + * + * The Sprite in this case will be rendered using its local transform. To render this sprite at 0,0 + * you can clear the transform + * + * ```js + * + * sprite.setTransform() + * + * let baseRenderTexture = new PIXI.BaseRenderTexture(100, 100); + * let renderTexture = new PIXI.RenderTexture(baseRenderTexture); + * + * renderer.render(sprite, renderTexture); // Renders to center of RenderTexture + * ``` + * + * @class + * @extends PIXI.BaseTexture + * @memberof PIXI + */ +export default class BaseRenderTexture extends BaseTexture +{ + /** + * @param {object} [options] + * @param {number} [options.width=100] - The width of the base render texture + * @param {number} [options.height=100] - The height of the base render texture + * @param {PIXI.SCALE_MODES} [options.scaleMode] - See {@link PIXI.SCALE_MODES} for possible values + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the texture being generated + */ + constructor(options) + { + if (typeof options === 'number') + { + /* eslint-disable prefer-rest-params */ + // Backward compatibility of signature + const width = arguments[0]; + const height = arguments[1]; + const scaleMode = arguments[2]; + const resolution = arguments[3]; + + options = { width, height, scaleMode, resolution }; + /* eslint-enable prefer-rest-params */ + } + + super(null, options); + + const { width, height } = options || {}; + + // Set defaults + this.mipmap = false; + this.width = Math.ceil(width) || 100; + this.height = Math.ceil(height) || 100; + this.valid = true; + + /** + * A map of renderer IDs to webgl renderTargets + * + * @private + * @member {object} + */ + // this._glRenderTargets = {}; + + /** + * A reference to the canvas render target (we only need one as this can be shared across renderers) + * + * @private + * @member {object} + */ + this._canvasRenderTarget = null; + + this.clearColor = [0, 0, 0, 0]; + + this.frameBuffer = new FrameBuffer(width * this.resolution, height * this.resolution) + .addColorTexture(0, this); + + // TODO - could this be added the systems? + + /** + * The data structure for the stencil masks + * + * @member {PIXI.Graphics[]} + */ + this.stencilMaskStack = []; + + /** + * The data structure for the filters + * + * @member {PIXI.Graphics[]} + */ + this.filterStack = [{}]; + } + + /** + * Resizes the BaseRenderTexture. + * + * @param {number} width - The width to resize to. + * @param {number} height - The height to resize to. + */ + resize(width, height) + { + width = Math.ceil(width); + height = Math.ceil(height); + this.frameBuffer.resize(width * this.resolution, height * this.resolution); + } + + /** + * Destroys this texture + * + */ + destroy() + { + super.destroy(true); + this.renderer = null; + } +} diff --git a/packages/core/src/renderTexture/RenderTexture.js b/packages/core/src/renderTexture/RenderTexture.js new file mode 100644 index 0000000..f1ecf1f --- /dev/null +++ b/packages/core/src/renderTexture/RenderTexture.js @@ -0,0 +1,153 @@ +import BaseRenderTexture from './BaseRenderTexture'; +import Texture from '../textures/Texture'; + +/** + * A RenderTexture is a special texture that allows any PixiJS display object to be rendered to it. + * + * __Hint__: All DisplayObjects (i.e. Sprites) that render to a RenderTexture should be preloaded + * otherwise black rectangles will be drawn instead. + * + * A RenderTexture takes a snapshot of any Display Object given to its render method. For example: + * + * ```js + * let renderer = PIXI.autoDetectRenderer(1024, 1024, { view: canvas, ratio: 1 }); + * let renderTexture = PIXI.RenderTexture.create(800, 600); + * let sprite = PIXI.Sprite.fromImage("spinObj_01.png"); + * + * sprite.position.x = 800/2; + * sprite.position.y = 600/2; + * sprite.anchor.x = 0.5; + * sprite.anchor.y = 0.5; + * + * renderer.render(sprite, renderTexture); + * ``` + * + * The Sprite in this case will be rendered using its local transform. To render this sprite at 0,0 + * you can clear the transform + * + * ```js + * + * sprite.setTransform() + * + * let renderTexture = new PIXI.RenderTexture.create(100, 100); + * + * renderer.render(sprite, renderTexture); // Renders to center of RenderTexture + * ``` + * + * @class + * @extends PIXI.Texture + * @memberof PIXI + */ +export default class RenderTexture extends Texture +{ + /** + * @param {PIXI.BaseRenderTexture} baseRenderTexture - The renderer used for this RenderTexture + * @param {PIXI.Rectangle} [frame] - The rectangle frame of the texture to show + */ + constructor(baseRenderTexture, frame) + { + // support for legacy.. + let _legacyRenderer = null; + + if (!(baseRenderTexture instanceof BaseRenderTexture)) + { + /* eslint-disable prefer-rest-params, no-console */ + const width = arguments[1]; + const height = arguments[2]; + const scaleMode = arguments[3]; + const resolution = arguments[4]; + + // we have an old render texture.. + console.warn(`Please use RenderTexture.create(${width}, ${height}) instead of the ctor directly.`); + _legacyRenderer = arguments[0]; + /* eslint-enable prefer-rest-params, no-console */ + + frame = null; + baseRenderTexture = new BaseRenderTexture({ + width, + height, + scaleMode, + resolution, + }); + } + + /** + * The base texture object that this texture uses + * + * @member {BaseTexture} + */ + super(baseRenderTexture, frame); + + this.legacyRenderer = _legacyRenderer; + + /** + * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. + * + * @member {boolean} + */ + this.valid = true; + + /** + * FilterSystem temporary storage + * @private + * @member {PIXI.Rectangle} + */ + this.filterFrame = null; + + this._updateUvs(); + } + + /** + * Resizes the RenderTexture. + * + * @param {number} width - The width to resize to. + * @param {number} height - The height to resize to. + * @param {boolean} [resizeBaseTexture=true] - Should the baseTexture.width and height values be resized as well? + */ + resize(width, height, resizeBaseTexture = true) + { + width = Math.ceil(width); + height = Math.ceil(height); + + // TODO - could be not required.. + this.valid = (width > 0 && height > 0); + + this._frame.width = this.orig.width = width; + this._frame.height = this.orig.height = height; + + if (resizeBaseTexture) + { + this.baseTexture.resize(width, height); + } + + this._updateUvs(); + } + + /** + * A short hand way of creating a render texture. + * + * @param {object} [options] - Options + * @param {number} [options.width=100] - The width of the render texture + * @param {number} [options.height=100] - The height of the render texture + * @param {number} [options.scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the texture being generated + * @return {PIXI.RenderTexture} The new render texture + */ + static create(options) + { + // fallback, old-style: create(width, height, scaleMode, resolution) + if (typeof options === 'number') + { + /* eslint-disable prefer-rest-params */ + options = { + width: options, + height: arguments[1], + scaleMode: arguments[2], + resolution: arguments[3], + }; + /* eslint-enable prefer-rest-params */ + } + + return new RenderTexture(new BaseRenderTexture(options)); + } +} diff --git a/packages/core/src/renderTexture/RenderTextureSystem.js b/packages/core/src/renderTexture/RenderTextureSystem.js new file mode 100644 index 0000000..8d0ca61 --- /dev/null +++ b/packages/core/src/renderTexture/RenderTextureSystem.js @@ -0,0 +1,115 @@ +import System from '../System'; +import { Rectangle } from '@pixi/math'; + +const tempRect = new Rectangle(); + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class RenderTextureSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.clearColor = renderer._backgroundColorRgba; + + // TODO moe this property somewhere else! + this.defaultMaskStack = []; + this.defaultFilterStack = [{}]; + + // empty render texture? + this.renderTexture = null; + + this.destinationFrame = new Rectangle(); + } + + bind(renderTexture, sourceFrame, destinationFrame) + { + // TODO - do we want this?? + if (this.renderTexture === renderTexture) return; + this.renderTexture = renderTexture; + + const renderer = this.renderer; + + if (renderTexture) + { + const baseTexture = renderTexture.baseTexture; + + if (!destinationFrame) + { + tempRect.width = baseTexture.realWidth; + tempRect.height = baseTexture.realHeight; + + destinationFrame = tempRect; + } + + if (!sourceFrame) + { + sourceFrame = destinationFrame; + } + + this.renderer.framebuffer.bind(baseTexture.frameBuffer, destinationFrame); + + this.renderer.projection.update(destinationFrame, sourceFrame, baseTexture.resolution, false); + this.renderer.stencil.setMaskStack(baseTexture.stencilMaskStack); + } + else + { + // TODO these validation checks happen deeper down.. + // thing they can be avoided.. + if (!destinationFrame) + { + tempRect.width = renderer.width; + tempRect.height = renderer.height; + + destinationFrame = tempRect; + } + + if (!sourceFrame) + { + sourceFrame = destinationFrame; + } + + renderer.framebuffer.bind(null, destinationFrame); + + // TODO store this.. + this.renderer.projection.update(destinationFrame, sourceFrame, this.renderer.resolution, true); + this.renderer.stencil.setMaskStack(this.defaultMaskStack); + } + + this.destinationFrame.copyFrom(destinationFrame); + } + + /** + * Erases the render texture and fills the drawing area with a colour + * + * @param {number} [clearColor] - The colour + * @return {PIXI.Renderer} Returns itself. + */ + clear(clearColor) + { + if (this.renderTexture) + { + clearColor = clearColor || this.renderTexture.baseTexture.clearColor; + } + else + { + clearColor = clearColor || this.clearColor; + } + + this.renderer.framebuffer.clear(clearColor[0], clearColor[1], clearColor[2], clearColor[3]); + } + + resize()// screenWidth, screenHeight) + { + // resize the root only! + this.bind(null); + } +} diff --git a/packages/core/src/renderers/AbstractRenderer.js b/packages/core/src/renderers/AbstractRenderer.js deleted file mode 100644 index 928670f..0000000 --- a/packages/core/src/renderers/AbstractRenderer.js +++ /dev/null @@ -1,353 +0,0 @@ -import { hex2string, hex2rgb } from '@pixi/utils'; -import { Matrix, Rectangle } from '@pixi/math'; -import { RENDERER_TYPE } from '@pixi/constants'; -import { settings } from '@pixi/settings'; -import { Container } from '@pixi/display'; -import RenderTexture from '../textures/RenderTexture'; -import EventEmitter from 'eventemitter3'; - -const tempMatrix = new Matrix(); - -/** - * 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 EventEmitter - * @memberof PIXI - */ -export default class AbstractRenderer extends EventEmitter -{ - // eslint-disable-next-line valid-jsdoc - /** - * @param {string} system - The name of the system this renderer is for. - * @param {object} [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.transparent=false] - If the render view is transparent, default false - * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false - * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) - * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. The - * resolution of the renderer retina would be 2. - * @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 {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when rendering, - * stopping pixel interpolation. - */ - constructor(system, options, arg2, arg3) - { - super(); - - // Support for constructor(system, screenWidth, screenHeight, options) - if (typeof options === 'number') - { - options = Object.assign({ - width: options, - height: arg2 || settings.RENDER_OPTIONS.height, - }, arg3); - } - - // 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 = RENDERER_TYPE.UNKNOWN; - - /** - * Measurements of the screen. (0, 0, screenWidth, screenHeight) - * - * Its safe to use as filterArea or hitArea for 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 1 - */ - this.resolution = options.resolution || settings.RESOLUTION; - - /** - * Whether the render view is transparent - * - * @member {boolean} - */ - this.transparent = options.transparent; - - /** - * Whether css dimensions of canvas view should be resized to screen dimensions automatically - * - * @member {boolean} - */ - this.autoResize = options.autoResize || false; - - /** - * Tracks the blend modes useful for this renderer. - * - * @member {object} - */ - this.blendModes = null; - - /** - * 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; - - /** - * If true PixiJS will Math.floor() x/y values when rendering, stopping pixel interpolation. - * Handy for crisp pixel art and speed on legacy devices. - * - * @member {boolean} - */ - this.roundPixels = options.roundPixels; - - /** - * The background color as a number. - * - * @member {number} - * @private - */ - this._backgroundColor = 0x000000; - - /** - * The background color as an [R, G, B] array. - * - * @member {number[]} - * @private - */ - this._backgroundColorRgba = [0, 0, 0, 0]; - - /** - * The background color as a string. - * - * @member {string} - * @private - */ - this._backgroundColorString = '#000000'; - - this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter - - /** - * This temporary display object used as the parent of the currently being rendered item - * - * @member {PIXI.DisplayObject} - * @private - */ - this._tempDisplayObjectParent = new Container(); - - /** - * The last root object that the renderer tried to render. - * - * @member {PIXI.DisplayObject} - * @private - */ - this._lastObjectRendered = this._tempDisplayObjectParent; - - /** - * Collection of plugins. - * @readonly - * @member {object} - */ - this.plugins = {}; - } - - /** - * Initialize the plugins. - * - * @protected - * @param {object} staticMap - The dictionary of staticly saved plugins. - */ - initPlugins(staticMap) - { - 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() - { - return this.view.width; - } - - /** - * Same as view.height, actual number of pixels in the canvas by vertical - * - * @member {number} - * @readonly - * @default 600 - */ - get height() - { - return this.view.height; - } - - /** - * Resizes the screen and canvas to the specified width and height - * Canvas dimensions are multiplied by resolution - * - * @param {number} screenWidth - the new width of the screen - * @param {number} screenHeight - the new height of the screen - */ - resize(screenWidth, screenHeight) - { - this.screen.width = screenWidth; - this.screen.height = screenHeight; - - this.view.width = screenWidth * this.resolution; - this.view.height = screenHeight * this.resolution; - - if (this.autoResize) - { - this.view.style.width = `${screenWidth}px`; - this.view.style.height = `${screenHeight}px`; - } - } - - /** - * 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 {PIXI.DisplayObject} displayObject - The displayObject the object will be generated from - * @param {number} scaleMode - Should be one of the scaleMode consts - * @param {number} resolution - The resolution / device pixel ratio of the texture being generated - * @param {PIXI.Rectangle} [region] - The region of the displayObject, that shall be rendered, - * if no region is specified, defaults to the local bounds of the displayObject. - * @return {PIXI.Texture} a texture of the graphics object - */ - generateTexture(displayObject, scaleMode, resolution, region) - { - region = region || displayObject.getLocalBounds(); - - // 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(region.width | 0, region.height | 0, scaleMode, resolution); - - tempMatrix.tx = -region.x; - tempMatrix.ty = -region.y; - - this.render(displayObject, renderTexture, false, tempMatrix, true); - - return renderTexture; - } - - /** - * Removes everything from the renderer and optionally removes the Canvas DOM element. - * - * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. - */ - destroy(removeView) - { - 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); - } - - this.plugins = null; - - this.type = RENDERER_TYPE.UNKNOWN; - - this.view = null; - - this.screen = null; - - this.resolution = 0; - - this.transparent = false; - - this.autoResize = false; - - this.blendModes = null; - - this.options = null; - - this.preserveDrawingBuffer = false; - this.clearBeforeRender = false; - - this.roundPixels = false; - - this._backgroundColor = 0; - this._backgroundColorRgba = null; - this._backgroundColorString = null; - - this._tempDisplayObjectParent = null; - this._lastObjectRendered = null; - } - - /** - * The background color to fill if not transparent - * - * @member {number} - */ - get backgroundColor() - { - return this._backgroundColor; - } - - set backgroundColor(value) // eslint-disable-line require-jsdoc - { - this._backgroundColor = value; - this._backgroundColorString = hex2string(value); - hex2rgb(value, this._backgroundColorRgba); - } -} diff --git a/packages/core/src/renderers/Renderer.js b/packages/core/src/renderers/Renderer.js deleted file mode 100644 index a2020b1..0000000 --- a/packages/core/src/renderers/Renderer.js +++ /dev/null @@ -1,329 +0,0 @@ -import AbstractRenderer from './AbstractRenderer'; -import { sayHello } from '@pixi/utils'; -import MaskSystem from './systems/MaskSystem'; -import StencilSystem from './systems/StencilSystem'; -import FilterSystem from './systems/filter/FilterSystem'; -import FramebufferSystem from './systems/FramebufferSystem'; -import RenderTextureSystem from './systems/RenderTextureSystem'; -import TextureSystem from './systems/textures/TextureSystem'; -import ProjectionSystem from './systems/ProjectionSystem'; -import StateSystem from './systems/StateSystem'; -import GeometrySystem from './systems/geometry/GeometrySystem'; -import ShaderSystem from './systems/shader/ShaderSystem'; -import ContextSystem from './systems/ContextSystem'; -import BatchSystem from './systems/BatchSystem'; -import TextureGCSystem from './systems/textures/TextureGCSystem'; -import { RENDERER_TYPE } from '@pixi/constants'; -import UniformGroup from '../shader/UniformGroup'; -import { Matrix } from '@pixi/math'; -import Runner from 'mini-runner'; - -/** - * 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 Render works by automatically managing webGLBatchs. - * 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 :) - * - * @class - * @memberof PIXI - * @extends PIXI.AbstractRenderer - */ -export default class Renderer extends AbstractRenderer -{ - // eslint-disable-next-line valid-jsdoc - /** - * - * @param {object} [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.transparent=false] - If the render view is transparent, default false - * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false - * @param {boolean} [options.antialias=false] - sets antialias. If not available natively then FXAA - * antialiasing is used - * @param {boolean} [options.forceFXAA=false] - forces FXAA antialiasing to be used over native. - * FXAA is faster, but may not always look as great - * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. - * The resolution of the renderer retina would be 2. - * @param {boolean} [options.clearBeforeRender=true] - This sets if the renderer will clear - * the canvas or not before the new render pass. If you wish to set this to false, you *must* set - * preserveDrawingBuffer to `true`. - * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, - * enable this if you need to call toDataUrl on the webgl context. - * @param {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when - * rendering, stopping pixel interpolation. - * @param {number} [options.backgroundColor=0x000000] - The background color of the rendered area - * (shown if not transparent). - * @param {boolean} [options.legacy=false] - If true PixiJS will aim to ensure compatibility - * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. - * @param {string} [options.powerPreference] - Parameter passed to webgl context, set to "high-performance" - * for devices with dual graphics card - */ - constructor(options, arg2, arg3) - { - super('WebGL', options, arg2, arg3); - - /** - * The type of this renderer as a standardised const - * - * @member {number} - * @see PIXI.RENDERER_TYPE - */ - this.type = RENDERER_TYPE.WEBGL; - - // this will be set by the contextSystem (this.context) - this.gl = null; - this.CONTEXT_UID = 0; - this.legacy = !!options.legacy; - - // TODO legacy! - - // runners! - this.runners = { - destroy: new Runner('destroy'), - contextChange: new Runner('contextChange', 1), - reset: new Runner('reset'), - update: new Runner('update'), - postrender: new Runner('postrender'), - prerender: new Runner('prerender'), - resize: new Runner('resize', 2), - }; - - 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(GeometrySystem, 'geometry') - .addSystem(FramebufferSystem, 'framebuffer') - .addSystem(StencilSystem, 'stencil') - .addSystem(ProjectionSystem, 'projection') - .addSystem(TextureGCSystem, 'textureGC') - .addSystem(FilterSystem, 'filter') - .addSystem(RenderTextureSystem, 'renderTexture') - .addSystem(BatchSystem, 'batch'); - - this.initPlugins(Renderer.__plugins); - - /** - * The options passed in to create a new webgl context. - * - * @member {object} - * @private - */ - if (options.context) - { - this.context.initFromContext(options.context); - } - else - { - this.context.initFromOptions({ - alpha: this.transparent, - antialias: options.antialias, - premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', - stencil: true, - 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); - } - - addSystem(_class, name) - { - if (!name) - { - name = _class.name; - } - - // TODO - read name from class.name.. - - /* - if(name.includes('System')) - { - name = name.replace('System', ''); - name = name.charAt(0).toLowerCase() + name.slice(1); - } - */ - - const system = new _class(this); - - if (this[name]) - { - throw new Error(`Whoops! ${name} is already a manger`); - } - - this[name] = system; - - for (const i in this.runners) - { - this.runners[i].add(system); - } - - return this; - - /** - * 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. - */ - } - - /** - * Renders the object to its webGL view - * - * @param {PIXI.DisplayObject} displayObject - the object to be rendered - * @param {PIXI.RenderTexture} renderTexture - The render texture to render to. - * @param {boolean} [clear] - Should the canvas be cleared before the new render - * @param {PIXI.Transform} [transform] - A transform to apply to the render texture before rendering. - * @param {boolean} [skipUpdateTransform] - Should we skip the update transform pass? - */ - render(displayObject, renderTexture, clear, transform, skipUpdateTransform) - { - // can be handy to know! - this.renderingToScreen = !renderTexture; - - this.runners.prerender.run(); - this.emit('prerender'); - - // 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.parent; - - displayObject.parent = this._tempDisplayObjectParent; - displayObject.updateTransform(); - displayObject.parent = 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.run(); - - this.emit('postrender'); - } - - /** - * Resizes the webGL view to the specified width and height. - * - * @param {number} screenWidth - the new width of the screen - * @param {number} screenHeight - the new height of the screen - */ - resize(screenWidth, screenHeight) - { - AbstractRenderer.prototype.resize.call(this, screenWidth, screenHeight); - - this.runners.resize.run(screenWidth, screenHeight); - } - - /** - * Resets the WebGL state so you can render things however you fancy! - * - * @return {PIXI.Renderer} Returns itself. - */ - reset() - { - this.runners.reset.run(); - - return this; - } - - clear() - { - this.framebuffer.bind(); - this.framebuffer.clear(); - } - - /** - * Removes everything from the renderer (event listeners, spritebatch, etc...) - * - * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. - * See: https://github.com/pixijs/pixi.js/issues/2233 - */ - destroy(removeView) - { - this.runners.destroy.run(); - - // call base destroy - super.destroy(removeView); - - // TODO nullify all the managers.. - this.gl = null; - } - - /** - * 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 - * builds and excluding plugins. - * @name PIXI.Renderer#plugins - * @type {object} - * @readonly - * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. - * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. - * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. - * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. - */ - - /** - * Adds a plugin to the renderer. - * - * @method - * @param {string} pluginName - The name of the plugin. - * @param {Function} ctor - The constructor function or class for the plugin. - */ - static registerPlugin(pluginName, ctor) - { - Renderer.__plugins = Renderer.__plugins || {}; - Renderer.__plugins[pluginName] = ctor; - } -} diff --git a/packages/core/src/renderers/State.js b/packages/core/src/renderers/State.js deleted file mode 100644 index fa3f221..0000000 --- a/packages/core/src/renderers/State.js +++ /dev/null @@ -1,163 +0,0 @@ -/* eslint-disable max-len */ - -const BLEND = 0; -const OFFSET = 1; -const CULLING = 2; -const DEPTH_TEST = 3; -const WINDING = 4; - -/** - * This is a webGL state. It is passed The WebGL StateManager. - * Each mesh renderered may require webGL to be in a different state. - * For example you may want different blend mode or to enable polygon offsets - * - * @class - * @memberof PIXI - */ -export default class State -{ - /** - * - */ - constructor() - { - this.data = 0; - - this.blendMode = 0; - this.polygonOffset = 0; - - this.blend = true; - // this.depthTest = true; - } - - /** - * Activates blending of the computed fragment color values - * - * @member {boolean} - */ - get blend() - { - return !!(this.data & (1 << BLEND)); - } - - set blend(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << BLEND)) !== value) - { - this.data ^= (1 << BLEND); - } - } - - /** - * Activates adding an offset to depth values of polygon's fragments - * - * @member {boolean} - * @default false - */ - get offsets() - { - return !!(this.data & (1 << OFFSET)); - } - - set offsets(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << OFFSET)) !== value) - { - this.data ^= (1 << OFFSET); - } - } - - /** - * Activates culling of polygons. - * - * @member {boolean} - * @default false - */ - get culling() - { - return !!(this.data & (1 << CULLING)); - } - - set culling(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << CULLING)) !== value) - { - this.data ^= (1 << CULLING); - } - } - - /** - * Activates depth comparisons and updates to the depth buffer. - * - * @member {boolean} - * @default false - */ - get depthTest() - { - return !!(this.data & (1 << DEPTH_TEST)); - } - - set depthTest(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << DEPTH_TEST)) !== value) - { - this.data ^= (1 << DEPTH_TEST); - } - } - - /** - * Specifies whether or not front or back-facing polygons can be culled. - * @member {boolean} - * @default false - */ - get clockwiseFrontFace() - { - return !!(this.data & (1 << WINDING)); - } - - set clockwiseFrontFace(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << WINDING)) !== value) - { - this.data ^= (1 << WINDING); - } - } - - /** - * The blend mode to be applied when this state is set. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. - * Setting this mode to anything other than NO_BLEND will automatically switch blending on. - * - * @member {boolean} - * @default PIXI.BLEND_MODES.NORMAL - * @see PIXI.BLEND_MODES - */ - get blendMode() - { - return this._blendMode; - } - - set blendMode(value) // eslint-disable-line require-jsdoc - { - // 17 is NO BLEND - this.blend = (value !== 17); - this._blendMode = value; - } - - /** - * The polygon offset. Setting this property to anything other than 0 will automatically enable poygon offset fill. - * - * @member {number} - * @default 0 - */ - get polygonOffset() - { - return this._polygonOffset; - } - - set polygonOffset(value) // eslint-disable-line require-jsdoc - { - this.offsets = !!value; - this._polygonOffset = value; - } -} - diff --git a/packages/core/src/renderers/filters/Filter.js b/packages/core/src/renderers/filters/Filter.js deleted file mode 100644 index 2073b8d..0000000 --- a/packages/core/src/renderers/filters/Filter.js +++ /dev/null @@ -1,140 +0,0 @@ -import Shader from '../../shader/Shader'; -import Program from '../../shader/Program'; -import State from '../State'; -import { settings } from '@pixi/settings'; -// import extractUniformsFromSrc from './extractUniformsFromSrc'; -import defaultVertex from './defaultFilter.vert'; -import defaultFragment from './defaultFilter.frag'; - -// let math = require('../../math'); -/** - * @class - * @memberof PIXI - * @extends PIXI.Shader - */ -export default class Filter extends Shader -{ - /** - * @param {string} [vertexSrc] - The source of the vertex shader. - * @param {string} [fragmentSrc] - The source of the fragment shader. - * @param {object} [uniforms] - Custom uniforms to use to augment the built-in ones. - */ - constructor(vertexSrc, fragmentSrc, uniforms) - { - const program = Program.from(vertexSrc, fragmentSrc); - - super(program, uniforms); - - /** - * The padding of the filter. Some filters require extra space to breath such as a blur. - * Increasing this will add extra width and height to the bounds of the object that the - * filter is applied to. - * - * @member {number} - */ - this.padding = 0; - - /** - * The resolution of the filter. Setting this to be lower will lower the quality but - * increase the performance of the filter. - * - * @member {number} - */ - this.resolution = settings.RESOLUTION; - - /** - * If enabled is true the filter is applied, if false it will not. - * - * @member {boolean} - */ - this.enabled = true; - - /** - * If enabled, PixiJS will fit the filter area into boundaries for better performance. - * Switch it off if it does not work for specific shader. - * - * @member {boolean} - */ - this.autoFit = true; - - /** - * Legacy filters use position and uvs from attributes - * @member {boolean} - * @readonly - */ - this.legacy = !!this.program.attributeData.aTextureCoord; - - /** - * the webGL state the filter requires to render - * @member {PIXI.State} - */ - this.state = new State(); - } - - /** - * Applies the filter - * - * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from - * @param {PIXI.RenderTarget} input - The input render target. - * @param {PIXI.RenderTarget} output - The target to output to. - * @param {boolean} clear - Should the output be cleared before rendering to it - * @param {object} [currentState] - It's current state of filter. - * There are some useful properties in the currentState : - * target, filters, sourceFrame, destinationFrame, renderTarget, resolution - */ - apply(filterManager, input, output, clear, currentState, derp) // eslint-disable-line no-unused-vars - { - // do as you please! - - filterManager.applyFilter(this, input, output, clear, currentState, derp); - - // or just do a regular render.. - } - - /** - * Sets the blendmode of the filter - * - * @member {number} - * @default PIXI.BLEND_MODES.NORMAL - */ - get blendMode() - { - return this.state.blendMode; - } - - set blendMode(value) // eslint-disable-line require-jsdoc - { - this.state.blendMode = value; - } - - /** - * The default vertex shader source - * - * @static - * @constant - */ - static get defaultVertexSrc() - { - return defaultVertex; - } - - /** - * The default fragment shader source - * - * @static - * @constant - */ - static get defaultFragmentSrc() - { - return defaultFragment; - } -} - -/** - * Used for caching shader IDs - * - * @static - * @private - */ -Filter.SOURCE_KEY_MAP = {}; - diff --git a/packages/core/src/renderers/filters/defaultFilter.frag b/packages/core/src/renderers/filters/defaultFilter.frag deleted file mode 100644 index a14c1be..0000000 --- a/packages/core/src/renderers/filters/defaultFilter.frag +++ /dev/null @@ -1,22 +0,0 @@ -varying vec2 vTextureCoord; -varying vec2 vFilterCoord; - -uniform sampler2D uSampler; -uniform sampler2D filterSampler; - -void main(void){ - vec4 masky = texture2D(filterSampler, vFilterCoord); - vec4 sample = texture2D(uSampler, vTextureCoord); - vec4 color; - if(mod(vFilterCoord.x, 1.0) > 0.5) - { - color = vec4(1.0, 0.0, 0.0, 1.0); - } - else - { - color = vec4(0.0, 1.0, 0.0, 1.0); - } - // gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0); - gl_FragColor = mix(sample, masky, 0.5); - gl_FragColor *= sample.a; -} diff --git a/packages/core/src/renderers/filters/defaultFilter.vert b/packages/core/src/renderers/filters/defaultFilter.vert deleted file mode 100644 index e1febca..0000000 --- a/packages/core/src/renderers/filters/defaultFilter.vert +++ /dev/null @@ -1,14 +0,0 @@ -attribute vec2 aVertexPosition; -attribute vec2 aTextureCoord; - -uniform mat3 projectionMatrix; -uniform mat3 filterMatrix; - -varying vec2 vTextureCoord; -varying vec2 vFilterCoord; - -void main(void){ - gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); - vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy; - vTextureCoord = aTextureCoord ; -} diff --git a/packages/core/src/renderers/filters/filterTransforms.js b/packages/core/src/renderers/filters/filterTransforms.js deleted file mode 100644 index 87a3cd6..0000000 --- a/packages/core/src/renderers/filters/filterTransforms.js +++ /dev/null @@ -1,50 +0,0 @@ -import { Matrix } from '@pixi/math'; - -/** - * Calculates the mapped matrix - * @param filterArea {Rectangle} The filter area - * @param sprite {Sprite} the target sprite - * @param outputMatrix {Matrix} @alvin - * @private - */ -// this returns a matrix that will normalise map filter cords in the filter to screen space -export function calculateScreenSpaceMatrix(outputMatrix, filterArea, textureSize) -{ - // TODO unwrap? - const mappedMatrix = outputMatrix.identity(); - - mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); - - mappedMatrix.scale(textureSize.width, textureSize.height); - - return mappedMatrix; -} - -export function calculateNormalizedScreenSpaceMatrix(outputMatrix, filterArea, textureSize) -{ - const mappedMatrix = outputMatrix.identity(); - - mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); - - const translateScaleX = (textureSize.width / filterArea.width); - const translateScaleY = (textureSize.height / filterArea.height); - - mappedMatrix.scale(translateScaleX, translateScaleY); - - return mappedMatrix; -} - -// this will map the filter coord so that a texture can be used based on the transform of a sprite -export function calculateSpriteMatrix(outputMatrix, filterArea, textureSize, sprite) -{ - const orig = sprite._texture.orig; - const mappedMatrix = outputMatrix.set(textureSize.width, 0, 0, textureSize.height, filterArea.x, filterArea.y); - const worldTransform = sprite.worldTransform.copyTo(Matrix.TEMP_MATRIX); - - worldTransform.invert(); - mappedMatrix.prepend(worldTransform); - mappedMatrix.scale(1.0 / orig.width, 1.0 / orig.height); - mappedMatrix.translate(sprite.anchor.x, sprite.anchor.y); - - return mappedMatrix; -} diff --git a/packages/core/src/renderers/filters/spriteMask/SpriteMaskFilter.js b/packages/core/src/renderers/filters/spriteMask/SpriteMaskFilter.js deleted file mode 100644 index d5a91a4..0000000 --- a/packages/core/src/renderers/filters/spriteMask/SpriteMaskFilter.js +++ /dev/null @@ -1,63 +0,0 @@ -import Filter from '../Filter'; -import { Matrix } from '@pixi/math'; -import vertex from './spriteMaskFilter.vert'; -import fragment from './spriteMaskFilter.frag'; -import { default as TextureMatrix } from '../../../textures/TextureMatrix'; - -/** - * The SpriteMaskFilter class - * - * @class - * @extends PIXI.Filter - * @memberof PIXI - */ -export default class SpriteMaskFilter extends Filter -{ - /** - * @param {PIXI.Sprite} sprite - the target sprite - */ - constructor(sprite) - { - const maskMatrix = new Matrix(); - - super(vertex, fragment); - - sprite.renderable = false; - - this.maskSprite = sprite; - this.maskMatrix = maskMatrix; - } - - /** - * Applies the filter - * - * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from - * @param {PIXI.RenderTarget} input - The input render target. - * @param {PIXI.RenderTarget} output - The target to output to. - */ - apply(filterManager, input, output) - { - const maskSprite = this.maskSprite; - const tex = this.maskSprite.texture; - - if (!tex.valid) - { - return; - } - if (!tex.transform) - { - // margin = 0.0, let it bleed a bit, shader code becomes easier - // assuming that atlas textures were made with 1-pixel padding - tex.transform = new TextureMatrix(tex, 0.0); - } - tex.transform.update(); - - this.uniforms.mask = tex; - this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite) - .prepend(tex.transform.mapCoord); - this.uniforms.alpha = maskSprite.worldAlpha; - this.uniforms.maskClamp = tex.transform.uClampFrame; - - filterManager.applyFilter(this, input, output); - } -} diff --git a/packages/core/src/AbstractRenderer.js b/packages/core/src/AbstractRenderer.js new file mode 100644 index 0000000..64b2eb1 --- /dev/null +++ b/packages/core/src/AbstractRenderer.js @@ -0,0 +1,353 @@ +import { hex2string, hex2rgb } from '@pixi/utils'; +import { Matrix, Rectangle } from '@pixi/math'; +import { RENDERER_TYPE } from '@pixi/constants'; +import { settings } from '@pixi/settings'; +import { Container } from '@pixi/display'; +import RenderTexture from './renderTexture/RenderTexture'; +import EventEmitter from 'eventemitter3'; + +const tempMatrix = new Matrix(); + +/** + * 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 EventEmitter + * @memberof PIXI + */ +export default class AbstractRenderer extends EventEmitter +{ + // eslint-disable-next-line valid-jsdoc + /** + * @param {string} system - The name of the system this renderer is for. + * @param {object} [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.transparent=false] - If the render view is transparent, default false + * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false + * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. The + * resolution of the renderer retina would be 2. + * @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 {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when rendering, + * stopping pixel interpolation. + */ + constructor(system, options, arg2, arg3) + { + super(); + + // Support for constructor(system, screenWidth, screenHeight, options) + if (typeof options === 'number') + { + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + }, arg3); + } + + // 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 = RENDERER_TYPE.UNKNOWN; + + /** + * Measurements of the screen. (0, 0, screenWidth, screenHeight) + * + * Its safe to use as filterArea or hitArea for 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 1 + */ + this.resolution = options.resolution || settings.RESOLUTION; + + /** + * Whether the render view is transparent + * + * @member {boolean} + */ + this.transparent = options.transparent; + + /** + * Whether css dimensions of canvas view should be resized to screen dimensions automatically + * + * @member {boolean} + */ + this.autoResize = options.autoResize || false; + + /** + * Tracks the blend modes useful for this renderer. + * + * @member {object} + */ + this.blendModes = null; + + /** + * 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; + + /** + * If true PixiJS will Math.floor() x/y values when rendering, stopping pixel interpolation. + * Handy for crisp pixel art and speed on legacy devices. + * + * @member {boolean} + */ + this.roundPixels = options.roundPixels; + + /** + * The background color as a number. + * + * @member {number} + * @private + */ + this._backgroundColor = 0x000000; + + /** + * The background color as an [R, G, B] array. + * + * @member {number[]} + * @private + */ + this._backgroundColorRgba = [0, 0, 0, 0]; + + /** + * The background color as a string. + * + * @member {string} + * @private + */ + this._backgroundColorString = '#000000'; + + this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter + + /** + * This temporary display object used as the parent of the currently being rendered item + * + * @member {PIXI.DisplayObject} + * @private + */ + this._tempDisplayObjectParent = new Container(); + + /** + * The last root object that the renderer tried to render. + * + * @member {PIXI.DisplayObject} + * @private + */ + this._lastObjectRendered = this._tempDisplayObjectParent; + + /** + * Collection of plugins. + * @readonly + * @member {object} + */ + this.plugins = {}; + } + + /** + * Initialize the plugins. + * + * @protected + * @param {object} staticMap - The dictionary of staticly saved plugins. + */ + initPlugins(staticMap) + { + 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() + { + return this.view.width; + } + + /** + * Same as view.height, actual number of pixels in the canvas by vertical + * + * @member {number} + * @readonly + * @default 600 + */ + get height() + { + return this.view.height; + } + + /** + * Resizes the screen and canvas to the specified width and height + * Canvas dimensions are multiplied by resolution + * + * @param {number} screenWidth - the new width of the screen + * @param {number} screenHeight - the new height of the screen + */ + resize(screenWidth, screenHeight) + { + this.screen.width = screenWidth; + this.screen.height = screenHeight; + + this.view.width = screenWidth * this.resolution; + this.view.height = screenHeight * this.resolution; + + if (this.autoResize) + { + this.view.style.width = `${screenWidth}px`; + this.view.style.height = `${screenHeight}px`; + } + } + + /** + * 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 {PIXI.DisplayObject} displayObject - The displayObject the object will be generated from + * @param {number} scaleMode - Should be one of the scaleMode consts + * @param {number} resolution - The resolution / device pixel ratio of the texture being generated + * @param {PIXI.Rectangle} [region] - The region of the displayObject, that shall be rendered, + * if no region is specified, defaults to the local bounds of the displayObject. + * @return {PIXI.Texture} a texture of the graphics object + */ + generateTexture(displayObject, scaleMode, resolution, region) + { + region = region || displayObject.getLocalBounds(); + + // 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(region.width | 0, region.height | 0, scaleMode, resolution); + + tempMatrix.tx = -region.x; + tempMatrix.ty = -region.y; + + this.render(displayObject, renderTexture, false, tempMatrix, true); + + return renderTexture; + } + + /** + * Removes everything from the renderer and optionally removes the Canvas DOM element. + * + * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. + */ + destroy(removeView) + { + 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); + } + + this.plugins = null; + + this.type = RENDERER_TYPE.UNKNOWN; + + this.view = null; + + this.screen = null; + + this.resolution = 0; + + this.transparent = false; + + this.autoResize = false; + + this.blendModes = null; + + this.options = null; + + this.preserveDrawingBuffer = false; + this.clearBeforeRender = false; + + this.roundPixels = false; + + this._backgroundColor = 0; + this._backgroundColorRgba = null; + this._backgroundColorString = null; + + this._tempDisplayObjectParent = null; + this._lastObjectRendered = null; + } + + /** + * The background color to fill if not transparent + * + * @member {number} + */ + get backgroundColor() + { + return this._backgroundColor; + } + + set backgroundColor(value) // eslint-disable-line require-jsdoc + { + this._backgroundColor = value; + this._backgroundColorString = hex2string(value); + hex2rgb(value, this._backgroundColorRgba); + } +} diff --git a/packages/core/src/Renderer.js b/packages/core/src/Renderer.js new file mode 100644 index 0000000..2227e78 --- /dev/null +++ b/packages/core/src/Renderer.js @@ -0,0 +1,338 @@ +import AbstractRenderer from './AbstractRenderer'; +import { sayHello } from '@pixi/utils'; +import MaskSystem from './mask/MaskSystem'; +import StencilSystem from './mask/StencilSystem'; +import FilterSystem from './filters/FilterSystem'; +import FramebufferSystem from './framebuffer/FramebufferSystem'; +import RenderTextureSystem from './renderTexture/RenderTextureSystem'; +import TextureSystem from './textures/TextureSystem'; +import ProjectionSystem from './projection/ProjectionSystem'; +import StateSystem from './state/StateSystem'; +import GeometrySystem from './geometry/GeometrySystem'; +import ShaderSystem from './shader/ShaderSystem'; +import ContextSystem from './context/ContextSystem'; +import BatchSystem from './batch/BatchSystem'; +import TextureGCSystem from './textures/TextureGCSystem'; +import { RENDERER_TYPE } from '@pixi/constants'; +import UniformGroup from './shader/UniformGroup'; +import { Matrix } from '@pixi/math'; +import Runner from 'mini-runner'; + +/** + * 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 Render works by automatically managing webGLBatchs. + * 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 :) + * + * @class + * @memberof PIXI + * @extends PIXI.AbstractRenderer + */ +export default class Renderer extends AbstractRenderer +{ + // eslint-disable-next-line valid-jsdoc + /** + * + * @param {object} [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.transparent=false] - If the render view is transparent, default false + * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false + * @param {boolean} [options.antialias=false] - sets antialias. If not available natively then FXAA + * antialiasing is used + * @param {boolean} [options.forceFXAA=false] - forces FXAA antialiasing to be used over native. + * FXAA is faster, but may not always look as great + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. + * The resolution of the renderer retina would be 2. + * @param {boolean} [options.clearBeforeRender=true] - This sets if the renderer will clear + * the canvas or not before the new render pass. If you wish to set this to false, you *must* set + * preserveDrawingBuffer to `true`. + * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, + * enable this if you need to call toDataUrl on the webgl context. + * @param {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when + * rendering, stopping pixel interpolation. + * @param {number} [options.backgroundColor=0x000000] - The background color of the rendered area + * (shown if not transparent). + * @param {boolean} [options.legacy=false] - If true PixiJS will aim to ensure compatibility + * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. + * @param {string} [options.powerPreference] - Parameter passed to webgl context, set to "high-performance" + * for devices with dual graphics card + */ + constructor(options, arg2, arg3) + { + super('WebGL', options, arg2, arg3); + + /** + * The type of this renderer as a standardised const + * + * @member {number} + * @see PIXI.RENDERER_TYPE + */ + this.type = RENDERER_TYPE.WEBGL; + + // this will be set by the contextSystem (this.context) + this.gl = null; + this.CONTEXT_UID = 0; + this.legacy = !!options.legacy; + + // TODO legacy! + + // runners! + this.runners = { + destroy: new Runner('destroy'), + contextChange: new Runner('contextChange', 1), + reset: new Runner('reset'), + update: new Runner('update'), + postrender: new Runner('postrender'), + prerender: new Runner('prerender'), + resize: new Runner('resize', 2), + }; + + 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(GeometrySystem, 'geometry') + .addSystem(FramebufferSystem, 'framebuffer') + .addSystem(StencilSystem, 'stencil') + .addSystem(ProjectionSystem, 'projection') + .addSystem(TextureGCSystem, 'textureGC') + .addSystem(FilterSystem, 'filter') + .addSystem(RenderTextureSystem, 'renderTexture') + .addSystem(BatchSystem, 'batch'); + + this.initPlugins(Renderer.__plugins); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ + if (options.context) + { + this.context.initFromContext(options.context); + } + else + { + this.context.initFromOptions({ + alpha: this.transparent, + antialias: options.antialias, + premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', + stencil: true, + 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); + } + + /** + * Add a new system to the renderer. + * @param {class} ClassRef - Class reference + * @param {string} [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. + * @return {PIXI.Renderer} Return instance of renderer + */ + addSystem(ClassRef, name) + { + if (!name) + { + name = ClassRef.name; + } + + // TODO - read name from class.name.. + + /* + if(name.includes('System')) + { + name = name.replace('System', ''); + name = name.charAt(0).toLowerCase() + name.slice(1); + } + */ + + const system = new ClassRef(this); + + if (this[name]) + { + throw new Error(`Whoops! ${name} is already a manger`); + } + + this[name] = system; + + for (const i in this.runners) + { + this.runners[i].add(system); + } + + return this; + + /** + * 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. + */ + } + + /** + * Renders the object to its webGL view + * + * @param {PIXI.DisplayObject} displayObject - the object to be rendered + * @param {PIXI.RenderTexture} renderTexture - The render texture to render to. + * @param {boolean} [clear] - Should the canvas be cleared before the new render + * @param {PIXI.Transform} [transform] - A transform to apply to the render texture before rendering. + * @param {boolean} [skipUpdateTransform] - Should we skip the update transform pass? + */ + render(displayObject, renderTexture, clear, transform, skipUpdateTransform) + { + // can be handy to know! + this.renderingToScreen = !renderTexture; + + this.runners.prerender.run(); + this.emit('prerender'); + + // 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.parent; + + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = 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.run(); + + this.emit('postrender'); + } + + /** + * Resizes the webGL view to the specified width and height. + * + * @param {number} screenWidth - the new width of the screen + * @param {number} screenHeight - the new height of the screen + */ + resize(screenWidth, screenHeight) + { + AbstractRenderer.prototype.resize.call(this, screenWidth, screenHeight); + + this.runners.resize.run(screenWidth, screenHeight); + } + + /** + * Resets the WebGL state so you can render things however you fancy! + * + * @return {PIXI.Renderer} Returns itself. + */ + reset() + { + this.runners.reset.run(); + + return this; + } + + clear() + { + this.framebuffer.bind(); + this.framebuffer.clear(); + } + + /** + * Removes everything from the renderer (event listeners, spritebatch, etc...) + * + * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. + * See: https://github.com/pixijs/pixi.js/issues/2233 + */ + destroy(removeView) + { + this.runners.destroy.run(); + + // call base destroy + super.destroy(removeView); + + // TODO nullify all the managers.. + this.gl = null; + } + + /** + * 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 + * builds and excluding plugins. + * @name PIXI.Renderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. + */ + + /** + * Adds a plugin to the renderer. + * + * @method + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + static registerPlugin(pluginName, ctor) + { + Renderer.__plugins = Renderer.__plugins || {}; + Renderer.__plugins[pluginName] = ctor; + } +} diff --git a/packages/core/src/System.js b/packages/core/src/System.js new file mode 100644 index 0000000..05a10c8 --- /dev/null +++ b/packages/core/src/System.js @@ -0,0 +1,42 @@ +/** + * System is a base class used for extending systems used by the {@link PIXI.Renderer} + * @see PIXI.Renderer#addSystem + * @class + * @memberof PIXI + */ +export default class System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + /** + * The renderer this manager works for. + * + * @member {PIXI.Renderer} + */ + this.renderer = renderer; + + this.renderer.runners.contextChange.add(this); + } + + /** + * Generic method called when there is a WebGL context change. + * + */ + contextChange() + { + // do some codes init! + } + + /** + * Generic destroy methods to be overridden by the subclass + * + */ + destroy() + { + this.renderer.runners.contextChange.remove(this); + this.renderer = null; + } +} diff --git a/packages/core/src/batch/BatchSystem.js b/packages/core/src/batch/BatchSystem.js new file mode 100644 index 0000000..b04c249 --- /dev/null +++ b/packages/core/src/batch/BatchSystem.js @@ -0,0 +1,68 @@ +import System from '../System'; +import ObjectRenderer from './ObjectRenderer'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class BatchSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + super(renderer); + + /** + * An empty renderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.emptyRenderer = new ObjectRenderer(renderer); + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.currentRenderer = this.emptyRenderer; + } + + /** + * Changes the current renderer to the one given in parameter + * + * @param {PIXI.ObjectRenderer} objectRenderer - The object renderer to use. + */ + setObjectRenderer(objectRenderer) + { + if (this.currentRenderer === objectRenderer) + { + return; + } + + this.currentRenderer.stop(); + this.currentRenderer = objectRenderer; + + this.renderer.state.setState(objectRenderer.state); + + this.currentRenderer.start(); + } + + /** + * This should be called if you wish to do some custom rendering + * It will basically render anything that may be batched up such as sprites + * + */ + flush() + { + this.setObjectRenderer(this.emptyRenderer); + } + + reset() + { + this.setObjectRenderer(this.emptyRenderer); + } +} diff --git a/packages/core/src/batch/ObjectRenderer.js b/packages/core/src/batch/ObjectRenderer.js new file mode 100644 index 0000000..0c851a9 --- /dev/null +++ b/packages/core/src/batch/ObjectRenderer.js @@ -0,0 +1,48 @@ +import System from '../System'; + +/** + * Base for a common object renderer that can be used as a system renderer plugin. + * + * @class + * @extends PIXI.System + * @memberof PIXI + */ +export default class ObjectRenderer extends System +{ + /** + * Starts the renderer and sets the shader + * + */ + start() + { + // set the shader.. + } + + /** + * Stops the renderer + * + */ + stop() + { + this.flush(); + } + + /** + * Stub method for rendering content and emptying the current batch. + * + */ + flush() + { + // flush! + } + + /** + * Renders an object + * + * @param {PIXI.DisplayObject} object - The object to render. + */ + render(object) // eslint-disable-line no-unused-vars + { + // render the object + } +} diff --git a/packages/core/src/context/ContextSystem.js b/packages/core/src/context/ContextSystem.js new file mode 100644 index 0000000..301aab4 --- /dev/null +++ b/packages/core/src/context/ContextSystem.js @@ -0,0 +1,186 @@ +import System from '../System'; +import { settings } from '@pixi/settings'; + +let CONTEXT_UID = 0; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class ContextSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.webGLVersion = 1; + + this.handleContextLost = this.handleContextLost.bind(this); + this.handleContextRestored = this.handleContextRestored.bind(this); + + this.extensions = {}; + + renderer.view.addEventListener('webglcontextlost', this.handleContextLost, false); + renderer.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + } + + get isLost() + { + return (!this.gl || this.gl.isContextLost()); + } + + contextChange(gl) + { + this.gl = gl; + + // restore a context if it was previously lost + if (gl.isContextLost() && gl.getExtension('WEBGL_lose_context')) + { + gl.getExtension('WEBGL_lose_context').restoreContext(); + } + } + + initFromContext(gl) + { + this.gl = gl; + this.validateContext(gl); + this.renderer.gl = gl; + this.renderer.CONTEXT_UID = CONTEXT_UID++; + this.renderer.runners.contextChange.run(gl); + } + + initFromOptions(options) + { + const gl = this.createContext(this.renderer.view, options); + + this.initFromContext(gl); + } + + /** + * Helper class to create a webGL Context + * + * @class + * @memberof PIXI.glCore + * @param canvas {HTMLCanvasElement} the canvas element that we will get the context from + * @param options {Object} An options object that gets passed in to the canvas element containing the context attributes, + * see https://developer.mozilla.org/en/docs/Web/API/HTMLCanvasElement/getContext for the options available + * @return {WebGLRenderingContext} the WebGL context + */ + createContext(canvas, options) + { + let gl; + + if (settings.PREFER_WEBGL_2) + { + gl = canvas.getContext('webgl2', options); + } + + if (gl) + { + this.webGLVersion = 2; + } + else + { + this.webGLVersion = 1; + + gl = canvas.getContext('webgl', options) + || canvas.getContext('experimental-webgl', options); + + if (!gl) + { + // fail, not able to get a context + throw new Error('This browser does not support webGL. Try using the canvas renderer'); + } + } + + this.gl = gl; + + this.getExtensions(); + + return gl; + } + + getExtensions() + { + // time to set up default etensions that pixi uses.. + const gl = this.gl; + const extensions = this.extensions; + + if (this.webGLVersion === 1) + { + extensions.drawBuffers = gl.getExtension('WEBGL_draw_buffers'); + extensions.depthTexture = gl.getExtension('WEBKIT_WEBGL_depth_texture'); + extensions.floatTexture = gl.getExtension('OES_texture_float'); + extensions.loseContext = gl.getExtension('WEBGL_lose_context'); + + extensions.vertexArrayObject = gl.getExtension('OES_vertex_array_object') + || gl.getExtension('MOZ_OES_vertex_array_object') + || gl.getExtension('WEBKIT_OES_vertex_array_object'); + } + + // we don't use any specific WebGL 2 ones yet! + } + + /** + * Handles a lost webgl context + * + * @private + * @param {WebGLContextEvent} event - The context lost event. + */ + handleContextLost(event) + { + event.preventDefault(); + } + + /** + * Handles a restored webgl context + * + * @private + */ + handleContextRestored() + { + this.renderer.runners.contextChange.run(this.gl); + } + + destroy() + { + const view = this.renderer.view; + + // remove listeners + view.removeEventListener('webglcontextlost', this.handleContextLost); + view.removeEventListener('webglcontextrestored', this.handleContextRestored); + + this.gl.useProgram(null); + + if (this.extensions.loseContext) + { + this.extensions.loseContext.loseContext(); + } + } + + postrender() + { + this.gl.flush(); + } + + validateContext(gl) + { + const attributes = gl.getContextAttributes(); + + // this is going to be fairly simple for now.. but at least we have room to grow! + if (!attributes.stencil) + { + /* eslint-disable max-len */ + + /* eslint-disable no-console */ + console.warn('Provided WebGL context does not have a stencil buffer, masks may not render correctly'); + /* eslint-enable no-console */ + + /* eslint-enable max-len */ + } + } +} diff --git a/packages/core/src/filters/Filter.js b/packages/core/src/filters/Filter.js new file mode 100644 index 0000000..9d8cad8 --- /dev/null +++ b/packages/core/src/filters/Filter.js @@ -0,0 +1,140 @@ +import Shader from '../shader/Shader'; +import Program from '../shader/Program'; +import State from '../state/State'; +import { settings } from '@pixi/settings'; +// import extractUniformsFromSrc from './extractUniformsFromSrc'; +import defaultVertex from './defaultFilter.vert'; +import defaultFragment from './defaultFilter.frag'; + +// let math = require('../../math'); +/** + * @class + * @memberof PIXI + * @extends PIXI.Shader + */ +export default class Filter extends Shader +{ + /** + * @param {string} [vertexSrc] - The source of the vertex shader. + * @param {string} [fragmentSrc] - The source of the fragment shader. + * @param {object} [uniforms] - Custom uniforms to use to augment the built-in ones. + */ + constructor(vertexSrc, fragmentSrc, uniforms) + { + const program = Program.from(vertexSrc, fragmentSrc); + + super(program, uniforms); + + /** + * The padding of the filter. Some filters require extra space to breath such as a blur. + * Increasing this will add extra width and height to the bounds of the object that the + * filter is applied to. + * + * @member {number} + */ + this.padding = 0; + + /** + * The resolution of the filter. Setting this to be lower will lower the quality but + * increase the performance of the filter. + * + * @member {number} + */ + this.resolution = settings.RESOLUTION; + + /** + * If enabled is true the filter is applied, if false it will not. + * + * @member {boolean} + */ + this.enabled = true; + + /** + * If enabled, PixiJS will fit the filter area into boundaries for better performance. + * Switch it off if it does not work for specific shader. + * + * @member {boolean} + */ + this.autoFit = true; + + /** + * Legacy filters use position and uvs from attributes + * @member {boolean} + * @readonly + */ + this.legacy = !!this.program.attributeData.aTextureCoord; + + /** + * the webGL state the filter requires to render + * @member {PIXI.State} + */ + this.state = new State(); + } + + /** + * Applies the filter + * + * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + * @param {boolean} clear - Should the output be cleared before rendering to it + * @param {object} [currentState] - It's current state of filter. + * There are some useful properties in the currentState : + * target, filters, sourceFrame, destinationFrame, renderTarget, resolution + */ + apply(filterManager, input, output, clear, currentState, derp) // eslint-disable-line no-unused-vars + { + // do as you please! + + filterManager.applyFilter(this, input, output, clear, currentState, derp); + + // or just do a regular render.. + } + + /** + * Sets the blendmode of the filter + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL + */ + get blendMode() + { + return this.state.blendMode; + } + + set blendMode(value) // eslint-disable-line require-jsdoc + { + this.state.blendMode = value; + } + + /** + * The default vertex shader source + * + * @static + * @constant + */ + static get defaultVertexSrc() + { + return defaultVertex; + } + + /** + * The default fragment shader source + * + * @static + * @constant + */ + static get defaultFragmentSrc() + { + return defaultFragment; + } +} + +/** + * Used for caching shader IDs + * + * @static + * @private + */ +Filter.SOURCE_KEY_MAP = {}; + diff --git a/packages/core/src/filters/FilterSystem.js b/packages/core/src/filters/FilterSystem.js new file mode 100644 index 0000000..3174318 --- /dev/null +++ b/packages/core/src/filters/FilterSystem.js @@ -0,0 +1,431 @@ +import System from '../System'; + +import RenderTexture from '../renderTexture/RenderTexture'; +import Quad from '../utils/Quad'; +import QuadUv from '../utils/QuadUv'; +import { Rectangle } from '@pixi/math'; +import * as filterTransforms from './filterTransforms'; +import bitTwiddle from 'bit-twiddle'; +import UniformGroup from '../shader/UniformGroup'; +import { DRAW_MODES } from '@pixi/constants'; + +// +/** + * @ignore + * @class + */ +class FilterState +{ + /** + * + */ + constructor() + { + this.renderTexture = null; + this.sourceFrame = new Rectangle(); + this.destinationFrame = new Rectangle(); + this.filters = []; + this.target = null; + this.legacy = false; + this.resolution = 1; + } +} + +/** + * @class + * @memberof PIXI.systems + * @extends PIXI.System + */ +export default class FilterSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + /** + * stores a bunch of PO2 textures used for filtering + * @type {Object} + */ + this.texturePool = {}; + + /** + * a pool for storing filter states, save us creating new ones each tick + * @type {Array} + */ + this.statePool = []; + + /** + * A very simple geometry used when drawing a filter effect to the screen + * @type {Quad} + */ + this.quad = new Quad(); + + this.quadUv = new QuadUv(); + + /** + * Temporary rect for maths + * @type {PIXI.Rectangle} + */ + this.tempRect = new Rectangle(); + + this.activeState = {}; + + /** + * this uniform group is attached to filter uniforms when used + * @type {UniformGroup} + */ + this.globalUniforms = new UniformGroup({ + sourceFrame: this.tempRect, + destinationFrame: this.tempRect, + + // legacy variables + filterArea: new Float32Array(4), + filterClamp: new Float32Array(4), + }, true); + } + + /** + * Adds a new filter to the System. + * + * @param {PIXI.DisplayObject} target - The target of the filter to render. + * @param {PIXI.Filter[]} filters - The filters to apply. + */ + push(target, filters) + { + const renderer = this.renderer; + const filterStack = this.renderer.renderTexture.defaultFilterStack; + const state = this.statePool.pop() || new FilterState(); + + let resolution = filters[0].resolution; + let padding = filters[0].padding; + let autoFit = filters[0].autoFit; + let legacy = filters[0].legacy; + + for (let i = 1; i < filters.length; i++) + { + const filter = filters[i]; + + // lets use the lowest resolution.. + resolution = Math.min(resolution, filter.resolution); + // and the largest amount of padding! + padding = Math.max(padding, filter.padding); + // only auto fit if all filters are autofit + autoFit = autoFit || filter.autoFit; + + legacy = legacy || filter.legacy; + } + + filterStack.push(state); + + state.resolution = resolution; + + state.legacy = legacy; + + // round to whole number based on resolution + // TODO move that to the shader too? + state.sourceFrame = target.filterArea || target.getBounds(true); + + state.sourceFrame.pad(padding); + + if (autoFit) + { + state.sourceFrame.fit(this.renderer.renderTexture.destinationFrame); + } + + state.sourceFrame.round(resolution); + + state.renderTexture = this.getPotFilterTexture(state.sourceFrame.width, state.sourceFrame.height, resolution); + state.filters = filters; + + state.destinationFrame.width = state.renderTexture.width; + state.destinationFrame.height = state.renderTexture.height; + + state.renderTexture.filterFrame = state.sourceFrame; + renderer.renderTexture.bind(state.renderTexture, state.sourceFrame);// /, state.destinationFrame); + renderer.renderTexture.clear(); + } + + /** + * Pops off the filter and applies it. + * + */ + pop() + { + const renderer = this.renderer; + const filterStack = renderer.renderTexture.defaultFilterStack; + const state = filterStack.pop(); + const filters = state.filters; + + this.activeState = state; + + const globalUniforms = this.globalUniforms.uniforms; + + globalUniforms.sourceFrame = state.sourceFrame; + globalUniforms.destinationFrame = state.destinationFrame; + globalUniforms.resolution = state.resolution; + + // only update the rect if its legacy.. + if (state.legacy) + { + const filterArea = globalUniforms.filterArea; + const filterClamp = globalUniforms.filterClamp; + + filterArea[0] = state.destinationFrame.width; + filterArea[1] = state.destinationFrame.height; + filterArea[2] = state.sourceFrame.x; + filterArea[3] = state.sourceFrame.y; + + filterClamp[0] = 0.5 / state.resolution / state.destinationFrame.width; + filterClamp[1] = 0.5 / state.resolution / state.destinationFrame.height; + filterClamp[2] = (state.sourceFrame.width - 0.5) / state.resolution / state.destinationFrame.width; + filterClamp[3] = (state.sourceFrame.height - 0.5) / state.resolution / state.destinationFrame.height; + } + + this.globalUniforms.update(); + + const lastState = filterStack[filterStack.length - 1]; + + if (filters.length === 1) + { + filters[0].apply(this, state.renderTexture, lastState.renderTexture, false, state); + + this.returnFilterTexture(state.renderTexture); + } + else + { + let flip = state.renderTexture; + let flop = this.getPotFilterTexture( + flip.width, + flip.height, + state.resolution + ); + + flop.filterFrame = flip.filterFrame; + + let i = 0; + + for (i = 0; i < filters.length - 1; ++i) + { + filters[i].apply(this, flip, flop, true, state); + + const t = flip; + + flip = flop; + flop = t; + } + + filters[i].apply(this, flip, lastState.renderTexture, false, state); + + this.returnFilterTexture(flip); + this.returnFilterTexture(flop); + } + + this.statePool.push(state); + } + + /** + * Draws a filter. + * + * @param {PIXI.Filter} filter - The filter to draw. + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + * @param {boolean} clear - Should the output be cleared before rendering to it + */ + applyFilter(filter, input, output, clear) + { + const renderer = this.renderer; + + renderer.renderTexture.bind(output, output ? output.filterFrame : null); + + if (clear) + { + // gl.disable(gl.SCISSOR_TEST); + renderer.renderTexture.clear(); + // gl.enable(gl.SCISSOR_TEST); + } + + // set the uniforms.. + filter.uniforms.uSampler = input; + filter.uniforms.filterGlobals = this.globalUniforms; + + // TODO make it so that the order of this does not matter.. + // because it does at the moment cos of global uniforms. + // they need to get resynced + + renderer.state.setState(filter.state); + renderer.shader.bind(filter); + + if (filter.legacy) + { + this.quadUv.map(input._frame, input.filterFrame); + + renderer.geometry.bind(this.quadUv); + renderer.geometry.draw(DRAW_MODES.TRIANGLES); + } + else + { + renderer.geometry.bind(this.quad); + renderer.geometry.draw(DRAW_MODES.TRIANGLE_STRIP); + } + } + + /** + * Calculates the mapped matrix. + * + * TODO playing around here.. this is temporary - (will end up in the shader) + * this returns a matrix that will normalise map filter cords in the filter to screen space + * + * @param {PIXI.Matrix} outputMatrix - the matrix to output to. + * @return {PIXI.Matrix} The mapped matrix. + */ + calculateScreenSpaceMatrix(outputMatrix) + { + const currentState = this.activeState; + + return filterTransforms.calculateScreenSpaceMatrix( + outputMatrix, + currentState.sourceFrame, + currentState.destinationFrame + ); + } + + /** + * This will map the filter coord so that a texture can be used based on the transform of a sprite + * + * @param {PIXI.Matrix} outputMatrix - The matrix to output to. + * @param {PIXI.Sprite} sprite - The sprite to map to. + * @return {PIXI.Matrix} The mapped matrix. + */ + calculateSpriteMatrix(outputMatrix, sprite) + { + const currentState = this.activeState; + + return filterTransforms.calculateSpriteMatrix( + outputMatrix, + currentState.sourceFrame, + currentState.destinationFrame, + sprite + ); + } + + /** + * Destroys this Filter System. + * + * @param {boolean} [contextLost=false] context was lost, do not free shaders + * + */ + destroy(contextLost = false) + { + if (!contextLost) + { + this.emptyPool(); + } + else + { + this.texturePool = {}; + } + } + + /** + * Gets a Power-of-Two render texture. + * + * TODO move to a seperate class could be on renderer? + * also - could cause issue with multiple contexts? + * + * @private + * @param {WebGLRenderingContext} gl - The webgl rendering context + * @param {number} minWidth - The minimum width of the render target. + * @param {number} minHeight - The minimum height of the render target. + * @param {number} resolution - The resolution of the render target. + * @return {PIXI.RenderTarget} The new render target. + */ + getPotFilterTexture(minWidth, minHeight, resolution) + { + minWidth = bitTwiddle.nextPow2(minWidth); + minHeight = bitTwiddle.nextPow2(minHeight); + resolution = resolution || 1; + + const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + + if (!this.texturePool[key]) + { + this.texturePool[key] = []; + } + + let renderTexture = this.texturePool[key].pop(); + + if (!renderTexture) + { + // temporary bypass cache.. + // internally - this will cause a texture to be bound.. + renderTexture = RenderTexture.create({ + width: minWidth, + height: minHeight, + resolution, + }); + } + + return renderTexture; + } + + /** + * Gets extra render texture to use inside current filter + * + * @param {number} resolution resolution of the renderTexture + * @returns {PIXI.RenderTexture} + */ + getFilterTexture(resolution) + { + const rt = this.activeState.renderTexture; + + const filterTexture = this.getPotFilterTexture(rt.width, rt.height, resolution || rt.baseTexture.resolution); + + filterTexture.filterFrame = rt.filterFrame; + + return filterTexture; + } + + /** + * Frees a render texture back into the pool. + * + * @param {PIXI.RenderTarget} renderTexture - The renderTarget to free + */ + returnFilterTexture(renderTexture) + { + renderTexture.filterFrame = null; + + const base = renderTexture.baseTexture; + + const minWidth = base.width; + const minHeight = base.height; + + const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + + this.texturePool[key].push(renderTexture); + } + + /** + * Empties the texture pool. + * + */ + emptyPool() + { + for (const i in this.texturePool) + { + const textures = this.texturePool[i]; + + if (textures) + { + for (let j = 0; j < textures.length; j++) + { + textures[j].destroy(true); + } + } + } + + this.texturePool = {}; + } +} diff --git a/packages/core/src/filters/defaultFilter.frag b/packages/core/src/filters/defaultFilter.frag new file mode 100644 index 0000000..a14c1be --- /dev/null +++ b/packages/core/src/filters/defaultFilter.frag @@ -0,0 +1,22 @@ +varying vec2 vTextureCoord; +varying vec2 vFilterCoord; + +uniform sampler2D uSampler; +uniform sampler2D filterSampler; + +void main(void){ + vec4 masky = texture2D(filterSampler, vFilterCoord); + vec4 sample = texture2D(uSampler, vTextureCoord); + vec4 color; + if(mod(vFilterCoord.x, 1.0) > 0.5) + { + color = vec4(1.0, 0.0, 0.0, 1.0); + } + else + { + color = vec4(0.0, 1.0, 0.0, 1.0); + } + // gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0); + gl_FragColor = mix(sample, masky, 0.5); + gl_FragColor *= sample.a; +} diff --git a/packages/core/src/filters/defaultFilter.vert b/packages/core/src/filters/defaultFilter.vert new file mode 100644 index 0000000..e1febca --- /dev/null +++ b/packages/core/src/filters/defaultFilter.vert @@ -0,0 +1,14 @@ +attribute vec2 aVertexPosition; +attribute vec2 aTextureCoord; + +uniform mat3 projectionMatrix; +uniform mat3 filterMatrix; + +varying vec2 vTextureCoord; +varying vec2 vFilterCoord; + +void main(void){ + gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); + vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy; + vTextureCoord = aTextureCoord ; +} diff --git a/packages/core/src/filters/filterTransforms.js b/packages/core/src/filters/filterTransforms.js new file mode 100644 index 0000000..87a3cd6 --- /dev/null +++ b/packages/core/src/filters/filterTransforms.js @@ -0,0 +1,50 @@ +import { Matrix } from '@pixi/math'; + +/** + * Calculates the mapped matrix + * @param filterArea {Rectangle} The filter area + * @param sprite {Sprite} the target sprite + * @param outputMatrix {Matrix} @alvin + * @private + */ +// this returns a matrix that will normalise map filter cords in the filter to screen space +export function calculateScreenSpaceMatrix(outputMatrix, filterArea, textureSize) +{ + // TODO unwrap? + const mappedMatrix = outputMatrix.identity(); + + mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); + + mappedMatrix.scale(textureSize.width, textureSize.height); + + return mappedMatrix; +} + +export function calculateNormalizedScreenSpaceMatrix(outputMatrix, filterArea, textureSize) +{ + const mappedMatrix = outputMatrix.identity(); + + mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); + + const translateScaleX = (textureSize.width / filterArea.width); + const translateScaleY = (textureSize.height / filterArea.height); + + mappedMatrix.scale(translateScaleX, translateScaleY); + + return mappedMatrix; +} + +// this will map the filter coord so that a texture can be used based on the transform of a sprite +export function calculateSpriteMatrix(outputMatrix, filterArea, textureSize, sprite) +{ + const orig = sprite._texture.orig; + const mappedMatrix = outputMatrix.set(textureSize.width, 0, 0, textureSize.height, filterArea.x, filterArea.y); + const worldTransform = sprite.worldTransform.copyTo(Matrix.TEMP_MATRIX); + + worldTransform.invert(); + mappedMatrix.prepend(worldTransform); + mappedMatrix.scale(1.0 / orig.width, 1.0 / orig.height); + mappedMatrix.translate(sprite.anchor.x, sprite.anchor.y); + + return mappedMatrix; +} diff --git a/packages/core/src/filters/spriteMask/SpriteMaskFilter.js b/packages/core/src/filters/spriteMask/SpriteMaskFilter.js new file mode 100644 index 0000000..91697b7 --- /dev/null +++ b/packages/core/src/filters/spriteMask/SpriteMaskFilter.js @@ -0,0 +1,63 @@ +import Filter from '../Filter'; +import { Matrix } from '@pixi/math'; +import vertex from './spriteMaskFilter.vert'; +import fragment from './spriteMaskFilter.frag'; +import { default as TextureMatrix } from '../../textures/TextureMatrix'; + +/** + * The SpriteMaskFilter class + * + * @class + * @extends PIXI.Filter + * @memberof PIXI + */ +export default class SpriteMaskFilter extends Filter +{ + /** + * @param {PIXI.Sprite} sprite - the target sprite + */ + constructor(sprite) + { + const maskMatrix = new Matrix(); + + super(vertex, fragment); + + sprite.renderable = false; + + this.maskSprite = sprite; + this.maskMatrix = maskMatrix; + } + + /** + * Applies the filter + * + * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + */ + apply(filterManager, input, output) + { + const maskSprite = this.maskSprite; + const tex = this.maskSprite.texture; + + if (!tex.valid) + { + return; + } + if (!tex.transform) + { + // margin = 0.0, let it bleed a bit, shader code becomes easier + // assuming that atlas textures were made with 1-pixel padding + tex.transform = new TextureMatrix(tex, 0.0); + } + tex.transform.update(); + + this.uniforms.mask = tex; + this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite) + .prepend(tex.transform.mapCoord); + this.uniforms.alpha = maskSprite.worldAlpha; + this.uniforms.maskClamp = tex.transform.uClampFrame; + + filterManager.applyFilter(this, input, output); + } +} diff --git a/packages/core/src/filters/spriteMask/spriteMaskFilter.frag b/packages/core/src/filters/spriteMask/spriteMaskFilter.frag new file mode 100644 index 0000000..0e4aef8 --- /dev/null +++ b/packages/core/src/filters/spriteMask/spriteMaskFilter.frag @@ -0,0 +1,23 @@ +varying vec2 vMaskCoord; +varying vec2 vTextureCoord; + +uniform sampler2D uSampler; +uniform sampler2D mask; +uniform float alpha; +uniform vec4 maskClamp; + +void main(void) +{ + float clip = step(3.5, + step(maskClamp.x, vMaskCoord.x) + + step(maskClamp.y, vMaskCoord.y) + + step(vMaskCoord.x, maskClamp.z) + + step(vMaskCoord.y, maskClamp.w)); + + vec4 original = texture2D(uSampler, vTextureCoord); + vec4 masky = texture2D(mask, vMaskCoord); + + original *= (masky.r * masky.a * alpha * clip); + + gl_FragColor = original; +} diff --git a/packages/core/src/filters/spriteMask/spriteMaskFilter.vert b/packages/core/src/filters/spriteMask/spriteMaskFilter.vert new file mode 100644 index 0000000..d23a30c --- /dev/null +++ b/packages/core/src/filters/spriteMask/spriteMaskFilter.vert @@ -0,0 +1,16 @@ +attribute vec2 aVertexPosition; +attribute vec2 aTextureCoord; + +uniform mat3 projectionMatrix; +uniform mat3 otherMatrix; + +varying vec2 vMaskCoord; +varying vec2 vTextureCoord; + +void main(void) +{ + gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); + + vTextureCoord = aTextureCoord; + vMaskCoord = ( otherMatrix * vec3( aTextureCoord, 1.0) ).xy; +} diff --git a/packages/core/src/framebuffer/FrameBuffer.js b/packages/core/src/framebuffer/FrameBuffer.js new file mode 100644 index 0000000..84e9fcc --- /dev/null +++ b/packages/core/src/framebuffer/FrameBuffer.js @@ -0,0 +1,106 @@ +import Texture from '../textures/BaseTexture'; +import { FORMATS, TYPES } from '@pixi/constants'; + +/** + * Frame buffer + * @class + * @memberof PIXI + */ +export default class FrameBuffer +{ + constructor(width, height) + { + this.width = width || 100; + this.height = height || 100; + + this.stencil = false; + this.depth = false; + + this.dirtyId = 0; + this.dirtyFormat = 0; + this.dirtySize = 0; + + this.depthTexture = null; + this.colorTextures = []; + + this.glFrameBuffers = {}; + } + + get colorTexture() + { + return this.colorTextures[0]; + } + + addColorTexture(index, texture) + { + // TODO add some validation to the texture - same width / height etc? + this.colorTextures[index || 0] = texture || new Texture(null, { scaleMode: 0, + resolution: 1, + mipmap: false, + width: this.width, + height: this.height });// || new Texture(); + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + addDepthTexture(texture) + { + /* eslint-disable max-len */ + this.depthTexture = texture || new Texture(null, { scaleMode: 0, + resolution: 1, + width: this.width, + height: this.height, + mipmap: false, + format: FORMATS.DEPTH_COMPONENT, + type: TYPES.UNSIGNED_SHORT });// UNSIGNED_SHORT; + /* eslint-disable max-len */ + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + enableDepth() + { + this.depth = true; + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + enableStencil() + { + this.stencil = true; + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + resize(width, height) + { + if (width === this.width && height === this.height) return; + + this.width = width; + this.height = height; + + this.dirtyId++; + this.dirtySize++; + + for (let i = 0; i < this.colorTextures.length; i++) + { + this.colorTextures[i].setSize(width, height); + } + + if (this.depthTexture) + { + this.depthTexture.setSize(width, height); + } + } +} diff --git a/packages/core/src/framebuffer/FramebufferSystem.js b/packages/core/src/framebuffer/FramebufferSystem.js new file mode 100644 index 0000000..2cc2437 --- /dev/null +++ b/packages/core/src/framebuffer/FramebufferSystem.js @@ -0,0 +1,246 @@ +import System from '../System'; +import { Rectangle } from '@pixi/math'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class FramebufferSystem extends System +{ + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + contextChange() + { + this.gl = this.renderer.gl; + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + this.current = null; + this.viewport = new Rectangle(); + + this.drawBufferExtension = this.renderer.context.extensions.drawBuffers; + } + + bind(framebuffer, frame) + { + const gl = this.gl; + + this.current = framebuffer; + + if (framebuffer) + { + // TODO cacheing layer! + + const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID] || this.initFramebuffer(framebuffer); + + gl.bindFramebuffer(gl.FRAMEBUFFER, fbo.framebuffer); + // makesure all textures are unbound.. + + // now check for updates... + if (fbo.dirtyId !== framebuffer.dirtyId) + { + fbo.dirtyId = framebuffer.dirtyId; + + if (fbo.dirtyFormat !== framebuffer.dirtyFormat) + { + fbo.dirtyFormat = framebuffer.dirtyFormat; + this.updateFramebuffer(framebuffer); + } + else if (fbo.dirtySize !== framebuffer.dirtySize) + { + fbo.dirtySize = framebuffer.dirtySize; + this.resizeFramebuffer(framebuffer); + } + } + + for (let i = 0; i < framebuffer.colorTextures.length; i++) + { + if (framebuffer.colorTextures[i].texturePart) + { + this.renderer.texture.unbind(framebuffer.colorTextures[i].texture); + } + else + { + this.renderer.texture.unbind(framebuffer.colorTextures[i]); + } + } + + if (framebuffer.depthTexture) + { + this.renderer.texture.unbind(framebuffer.depthTexture); + } + + if (frame) + { + this.setViewport(frame.x, frame.y, frame.width, frame.height); + } + else + { + this.setViewport(0, 0, framebuffer.width, framebuffer.height); + } + } + else + { + gl.bindFramebuffer(gl.FRAMEBUFFER, null); + + if (frame) + { + this.setViewport(frame.x, frame.y, frame.width, frame.height); + } + else + { + this.setViewport(0, 0, this.renderer.width, this.renderer.height); + } + } + } + + setViewport(x, y, width, height) + { + const v = this.viewport; + + if (v.width !== width || v.height !== height || v.x !== x || v.y !== y) + { + v.x = x; + v.y = y; + v.width = width; + v.height = height; + + this.gl.viewport(x, y, width, height); + } + } + + get size() + { + if (this.current) + { + // TODO store temp + return { x: 0, y: 0, width: this.current.width, height: this.current.height }; + } + + return { x: 0, y: 0, width: this.renderer.width, height: this.renderer.height }; + } + + clear(r, g, b, a) + { + const gl = this.gl; + + // TODO clear color can be set only one right? + gl.clearColor(r, g, b, a); + gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); + } + + // private functions... + + initFramebuffer(framebuffer) + { + const gl = this.gl; + + // TODO - make this a class? + const fbo = { + framebuffer: gl.createFramebuffer(), + stencil: null, + dirtyId: 0, + dirtyFormat: 0, + dirtySize: 0, + }; + + framebuffer.glFrameBuffers[this.CONTEXT_UID] = fbo; + + return fbo; + } + + resizeFramebuffer(framebuffer) + { + const gl = this.gl; + + if (framebuffer.stencil || framebuffer.depth) + { + gl.bindRenderbuffer(gl.RENDERBUFFER, this.stencil); + gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); + } + } + + updateFramebuffer(framebuffer) + { + const gl = this.gl; + + const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID]; + + // bind the color texture + const colorTextures = framebuffer.colorTextures; + + let count = colorTextures.length; + + if (!this.drawBufferExtension) + { + count = Math.min(count, 1); + } + + const activeTextures = []; + + for (let i = 0; i < count; i++) + { + const texture = framebuffer.colorTextures[i]; + + if (texture.texturePart) + { + this.renderer.texture.bind(texture.texture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.COLOR_ATTACHMENT0 + i, + gl.TEXTURE_CUBE_MAP_NEGATIVE_X + texture.side, + texture.texture._glTextures[this.CONTEXT_UID].texture, + 0); + } + else + { + this.renderer.texture.bind(texture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.COLOR_ATTACHMENT0 + i, + gl.TEXTURE_2D, + texture._glTextures[this.CONTEXT_UID].texture, + 0); + } + + activeTextures.push(gl.COLOR_ATTACHMENT0 + i); + } + + if (this.drawBufferExtension && activeTextures.length > 1) + { + this.drawBufferExtension.drawBuffersWEBGL(activeTextures); + } + + if (framebuffer.depthTexture) + { + const depthTextureExt = this.renderer.context.extensions.depthTexture; + + if (depthTextureExt) + { + const depthTexture = framebuffer.depthTexture; + + this.renderer.texture.bind(depthTexture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.DEPTH_ATTACHMENT, + gl.TEXTURE_2D, + depthTexture._glTextures[this.CONTEXT_UID].texture, + 0); + } + } + + if (framebuffer.stencil || framebuffer.depth) + { + fbo.stencil = gl.createRenderbuffer(); + + gl.bindRenderbuffer(gl.RENDERBUFFER, fbo.stencil); + + // TODO.. this is depth AND stencil? + gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, fbo.stencil); + gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); + // fbo.enableStencil(); + } + } +} diff --git a/packages/core/src/geometry/GLBuffer.js b/packages/core/src/geometry/GLBuffer.js new file mode 100644 index 0000000..0477e8a --- /dev/null +++ b/packages/core/src/geometry/GLBuffer.js @@ -0,0 +1,9 @@ +export default class GLBuffer +{ + constructor(buffer) + { + this.buffer = buffer; + this.updateID = -1; + this.byteLength = -1; + } +} diff --git a/packages/core/src/geometry/GeometrySystem.js b/packages/core/src/geometry/GeometrySystem.js new file mode 100644 index 0000000..959f827 --- /dev/null +++ b/packages/core/src/geometry/GeometrySystem.js @@ -0,0 +1,388 @@ +import System from '../System'; +import GLBuffer from './GLBuffer'; + +const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class GeometrySystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this._activeGeometry = null; + this._activeVao = null; + + this.hasVao = true; + this.hasInstance = true; + } + + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + contextChange() + { + const gl = this.gl = this.renderer.gl; + + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + + // webgl2 + if (!gl.createVertexArray) + { + // webgl 1! + let nativeVaoExtension = this.renderer.context.extensions.vertexArrayObject; + + if (this.renderer.legacy) + { + nativeVaoExtension = null; + } + + if (nativeVaoExtension) + { + gl.createVertexArray = () => + nativeVaoExtension.createVertexArrayOES(); + + gl.bindVertexArray = (vao) => + nativeVaoExtension.bindVertexArrayOES(vao); + + gl.deleteVertexArray = (vao) => + nativeVaoExtension.deleteVertexArrayOES(vao); + } + else + { + this.hasVao = false; + gl.createVertexArray = () => + { + // empty + }; + + gl.bindVertexArray = () => + { + // empty + }; + + gl.deleteVertexArray = () => + { + // empty + }; + } + } + + if (!gl.vertexAttribDivisor) + { + const instanceExt = gl.getExtension('ANGLE_instanced_arrays'); + + if (instanceExt) + { + gl.vertexAttribDivisor = (a, b) => + instanceExt.vertexAttribDivisorANGLE(a, b); + + gl.drawElementsInstanced = (a, b, c, d, e) => + instanceExt.drawElementsInstancedANGLE(a, b, c, d, e); + + gl.drawArraysInstanced = (a, b, c, d) => + instanceExt.drawArraysInstancedANGLE(a, b, c, d); + } + else + { + this.hasInstance = false; + } + } + } + + /** + * Binds geometry so that is can be drawn. Creating a Vao if required + * @private + * @param {PIXI.Geometry} geometry instance of geometry to bind + */ + bind(geometry, shader) + { + shader = shader || this.renderer.shader.shader; + + const gl = this.gl; + + // not sure the best way to address this.. + // currently different shaders require different VAOs for the same geometry + // Still mulling over the best way to solve this one.. + // will likely need to modify the shader attribute locations at run time! + let vaos = geometry.glVertexArrayObjects[this.CONTEXT_UID]; + + if (!vaos) + { + geometry.glVertexArrayObjects[this.CONTEXT_UID] = vaos = {}; + } + + const vao = vaos[shader.program.id] || this.initGeometryVao(geometry, shader.program); + + this._activeGeometry = geometry; + + if (this._activeVao !== vao) + { + this._activeVao = vao; + + if (this.hasVao) + { + gl.bindVertexArray(vao); + } + else + { + this.activateVao(geometry, shader.program); + } + } + + // TODO - optimise later! + // don't need to loop through if nothing changed! + // maybe look to add an 'autoupdate' to geometry? + this.updateBuffers(); + } + + reset() + { + this.unbind(); + } + + updateBuffers() + { + const geometry = this._activeGeometry; + const gl = this.gl; + + for (let i = 0; i < geometry.buffers.length; i++) + { + const buffer = geometry.buffers[i]; + + const glBuffer = buffer._glBuffers[this.CONTEXT_UID]; + + if (buffer._updateID !== glBuffer.updateID) + { + glBuffer.updateID = buffer._updateID; + + // TODO can cache this on buffer! maybe added a getter / setter? + const type = buffer.index ? gl.ELEMENT_ARRAY_BUFFER : gl.ARRAY_BUFFER; + const drawType = buffer.static ? gl.STATIC_DRAW : gl.DYNAMIC_DRAW; + + gl.bindBuffer(type, glBuffer.buffer); + + if (glBuffer.byteLength >= buffer.data.byteLength) + { + // offset is always zero for now! + gl.bufferSubData(type, 0, buffer.data); + } + else + { + gl.bufferData(type, buffer.data, drawType); + } + } + } + } + + checkCompatability(geometry, program) + { + // geometry must have at least all the attributes that the shader requires. + const geometryAttributes = geometry.attributes; + const shaderAttributes = program.attributeData; + + for (const j in shaderAttributes) + { + if (!geometryAttributes[j]) + { + throw new Error(`shader and geometry incompatible, geometry missing the "${j}" attribute`); + } + } + } + + /** + * Creates a Vao with the same structure as the geometry and stores it on the geometry. + * @private + * @param {PIXI.Geometry} geometry instance of geometry to to generate Vao for + */ + initGeometryVao(geometry, program) + { + this.checkCompatability(geometry, program); + + const gl = this.gl; + const CONTEXT_UID = this.CONTEXT_UID; + const buffers = geometry.buffers; + const attributes = geometry.attributes; + + const tempStride = {}; + const tempStart = {}; + + for (const j in buffers) + { + tempStride[j] = 0; + tempStart[j] = 0; + } + + for (const j in attributes) + { + if (!attributes[j].size && program.attributeData[j]) + { + attributes[j].size = program.attributeData[j].size; + } + + tempStride[attributes[j].buffer] += attributes[j].size * byteSizeMap[attributes[j].type]; + } + + for (const j in attributes) + { + const attribute = attributes[j]; + const attribSize = attribute.size; + + if (attribute.stride === undefined) + { + if (tempStride[attribute.buffer] === attribSize * byteSizeMap[attribute.type]) + { + attribute.stride = 0; + } + else + { + attribute.stride = tempStride[attribute.buffer]; + } + } + + if (attribute.start === undefined) + { + attribute.start = tempStart[attribute.buffer]; + + tempStart[attribute.buffer] += attribSize * byteSizeMap[attribute.type]; + } + } + + // first update - and create the buffers! + // only create a gl buffer if it actually gets + for (let i = 0; i < buffers.length; i++) + { + const buffer = buffers[i]; + + if (!buffer._glBuffers[CONTEXT_UID]) + { + buffer._glBuffers[CONTEXT_UID] = new GLBuffer(gl.createBuffer()); + } + } + + // TODO - maybe make this a data object? + // lets wait to see if we need to first! + const vao = gl.createVertexArray(); + + gl.bindVertexArray(vao); + + this.activateVao(geometry, program); + + gl.bindVertexArray(null); + + // add it to the cache! + geometry.glVertexArrayObjects[this.CONTEXT_UID][program.id] = vao; + + return vao; + } + + activateVao(geometry, program) + { + const gl = this.gl; + const CONTEXT_UID = this.CONTEXT_UID; + const buffers = geometry.buffers; + const attributes = geometry.attributes; + + if (geometry.indexBuffer) + { + // first update the index buffer if we have one.. + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, geometry.indexBuffer._glBuffers[CONTEXT_UID].buffer); + } + + let lastBuffer = null; + + // add a new one! + for (const j in attributes) + { + const attribute = attributes[j]; + const buffer = buffers[attribute.buffer]; + const glBuffer = buffer._glBuffers[CONTEXT_UID]; + + if (program.attributeData[j]) + { + if (lastBuffer !== glBuffer) + { + gl.bindBuffer(gl.ARRAY_BUFFER, glBuffer.buffer); + + lastBuffer = glBuffer; + } + + const location = program.attributeData[j].location; + + // TODO introduce state again + // we can optimise this for older devices that have no VAOs + gl.enableVertexAttribArray(location); + + gl.vertexAttribPointer(location, + attribute.size, + attribute.type || gl.FLOAT, + attribute.normalized, + attribute.stride, + attribute.start); + + if (attribute.instance) + { + // TODO calculate instance count based of this... + if (this.hasInstance) + { + gl.vertexAttribDivisor(location, 1); + } + else + { + throw new Error('geometry error, GPU Instancing is not supported on this device'); + } + } + } + } + } + + draw(type, size, start, instanceCount) + { + const gl = this.gl; + const geometry = this._activeGeometry; + + // TODO.. this should not change so maybe cache the function? + + if (geometry.indexBuffer) + { + if (geometry.instanced) + { + /* eslint-disable max-len */ + gl.drawElementsInstanced(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2, instanceCount || 1); + /* eslint-enable max-len */ + } + else + { + gl.drawElements(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2); + } + } + else + if (geometry.instanced) + { + // TODO need a better way to calculate size.. + gl.drawArraysInstanced(type, start, size || geometry.getSize(), instanceCount || 1); + } + else + { + gl.drawArrays(type, start, size || geometry.getSize()); + } + + return this; + } + + unbind() + { + this.gl.bindVertexArray(null); + this._activeVao = null; + this._activeGeometry = null; + } +} diff --git a/packages/core/src/geometry/utils/setVertexAttribArrays.js b/packages/core/src/geometry/utils/setVertexAttribArrays.js new file mode 100644 index 0000000..23b6959 --- /dev/null +++ b/packages/core/src/geometry/utils/setVertexAttribArrays.js @@ -0,0 +1,54 @@ +// var GL_MAP = {}; + +/** + * @param gl {WebGLRenderingContext} The current WebGL context + * @param attribs {*} + * @param state {*} + */ +export default function setVertexAttribArrays(gl, attribs, state) +{ + let i; + + if (state) + { + const tempAttribState = state.tempAttribState; + const attribState = state.attribState; + + for (i = 0; i < tempAttribState.length; i++) + { + tempAttribState[i] = false; + } + + // set the new attribs + for (i = 0; i < attribs.length; i++) + { + tempAttribState[attribs[i].attribute.location] = true; + } + + for (i = 0; i < attribState.length; i++) + { + if (attribState[i] !== tempAttribState[i]) + { + attribState[i] = tempAttribState[i]; + + if (state.attribState[i]) + { + gl.enableVertexAttribArray(i); + } + else + { + gl.disableVertexAttribArray(i); + } + } + } + } + else + { + for (i = 0; i < attribs.length; i++) + { + const attrib = attribs[i]; + + gl.enableVertexAttribArray(attrib.attribute.location); + } + } +} diff --git a/packages/core/src/index.js b/packages/core/src/index.js index 58c456a..fd1f547 100644 --- a/packages/core/src/index.js +++ b/packages/core/src/index.js @@ -1,30 +1,30 @@ import * as resources from './textures/resources'; -import * as systems from './renderers/systems'; +import * as systems from './systems'; -export { resources }; export { systems }; +export { resources }; -export { default as Renderer } from './renderers/Renderer'; -export { default as AbstractRenderer } from './renderers/AbstractRenderer'; -export { default as FrameBuffer } from './textures/FrameBuffer'; +export { default as System } from './System'; +export { default as Renderer } from './Renderer'; +export { default as AbstractRenderer } from './AbstractRenderer'; +export { default as FrameBuffer } from './framebuffer/FrameBuffer'; export { default as CubeTexture } from './textures/CubeTexture'; export { default as BaseTexture } from './textures/BaseTexture'; export { default as Texture } from './textures/Texture'; export { default as TextureMatrix } from './textures/TextureMatrix'; -export { default as RenderTexture } from './textures/RenderTexture'; -export { default as BaseRenderTexture } from './textures/BaseRenderTexture'; +export { default as RenderTexture } from './renderTexture/RenderTexture'; +export { default as BaseRenderTexture } from './renderTexture/BaseRenderTexture'; export { default as TextureUvs } from './textures/TextureUvs'; -export { default as State } from './renderers/State'; -export { default as ObjectRenderer } from './renderers/utils/ObjectRenderer'; -export { default as Quad } from './renderers/utils/Quad'; -export { default as QuadUv } from './renderers/utils/QuadUv'; -export { default as checkMaxIfStatmentsInShader } from './renderers/utils/checkMaxIfStatmentsInShader'; +export { default as State } from './state/State'; +export { default as ObjectRenderer } from './batch/ObjectRenderer'; +export { default as Quad } from './utils/Quad'; +export { default as QuadUv } from './utils/QuadUv'; +export { default as checkMaxIfStatmentsInShader } from './shader/utils/checkMaxIfStatmentsInShader'; export { default as Shader } from './shader/Shader'; export { default as Program } from './shader/Program'; export { default as UniformGroup } from './shader/UniformGroup'; -export { default as SpriteMaskFilter } from './renderers/filters/spriteMask/SpriteMaskFilter'; -export { default as Filter } from './renderers/filters/Filter'; +export { default as SpriteMaskFilter } from './filters/spriteMask/SpriteMaskFilter'; +export { default as Filter } from './filters/Filter'; export { default as Attribute } from './geometry/Attribute'; export { default as Buffer } from './geometry/Buffer'; export { default as Geometry } from './geometry/Geometry'; - diff --git a/packages/core/src/mask/MaskSystem.js b/packages/core/src/mask/MaskSystem.js new file mode 100644 index 0000000..99174c2 --- /dev/null +++ b/packages/core/src/mask/MaskSystem.js @@ -0,0 +1,199 @@ +import System from '../System'; +import AlphaMaskFilter from '../filters/spriteMask/SpriteMaskFilter'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class MaskSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + // TODO - we don't need both! + this.scissor = false; + this.scissorData = null; + this.scissorRenderTarget = null; + + this.enableScissor = false; + + this.alphaMaskPool = []; + this.alphaMaskIndex = 0; + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.DisplayObject} target - Display Object to push the mask to + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + push(target, maskData) + { + // TODO the root check means scissor rect will not + // be used on render textures more info here: + // https://github.com/pixijs/pixi.js/pull/3545 + + if (maskData.texture) + { + this.pushSpriteMask(target, maskData); + } + else if (this.enableScissor + && !this.scissor + && this.renderer._activeRenderTarget.root + && !this.renderer.stencil.stencilMaskStack.length + && maskData.isFastRect()) + { + const matrix = maskData.worldTransform; + + let rot = Math.atan2(matrix.b, matrix.a); + + // use the nearest degree! + rot = Math.round(rot * (180 / Math.PI)); + + if (rot % 90) + { + this.pushStencilMask(maskData); + } + else + { + this.pushScissorMask(target, maskData); + } + } + else + { + this.pushStencilMask(maskData); + } + } + + /** + * Removes the last mask from the mask stack and doesn't return it. + * + * @param {PIXI.DisplayObject} target - Display Object to pop the mask from + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + pop(target, maskData) + { + if (maskData.texture) + { + this.popSpriteMask(target, maskData); + } + else if (this.enableScissor && !this.renderer.stencil.stencilMaskStack.length) + { + this.popScissorMask(target, maskData); + } + else + { + this.popStencilMask(target, maskData); + } + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.RenderTarget} target - Display Object to push the sprite mask to + * @param {PIXI.Sprite} maskData - Sprite to be used as the mask + */ + pushSpriteMask(target, maskData) + { + let alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; + + if (!alphaMaskFilter) + { + alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; + } + + alphaMaskFilter[0].resolution = this.renderer.resolution; + alphaMaskFilter[0].maskSprite = maskData; + + // TODO - may cause issues! + target.filterArea = maskData.getBounds(true); + + this.renderer.filterSystem.pushFilter(target, alphaMaskFilter); + + this.alphaMaskIndex++; + } + + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popSpriteMask() + { + this.renderer.filterSystem.popFilter(); + this.alphaMaskIndex--; + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + pushStencilMask(maskData) + { + this.renderer.batch.flush(); + this.renderer.stencil.pushStencil(maskData); + } + + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popStencilMask() + { + // this.renderer.currentRenderer.stop(); + this.renderer.stencil.popStencil(); + } + + /** + * + * @param {PIXI.DisplayObject} target - Display Object to push the mask to + * @param {PIXI.Graphics} maskData - The masking data. + */ + pushScissorMask(target, maskData) + { + maskData.renderable = true; + + const renderTarget = this.renderer._activeRenderTarget; + + const bounds = maskData.getBounds(); + + bounds.fit(renderTarget.size); + maskData.renderable = false; + + this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); + + const resolution = this.renderer.resolution; + + this.renderer.gl.scissor( + bounds.x * resolution, + (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, + bounds.width * resolution, + bounds.height * resolution + ); + + this.scissorRenderTarget = renderTarget; + this.scissorData = maskData; + this.scissor = true; + } + + /** + * + * + */ + popScissorMask() + { + this.scissorRenderTarget = null; + this.scissorData = null; + this.scissor = false; + + // must be scissor! + const gl = this.renderer.gl; + + gl.disable(gl.SCISSOR_TEST); + } +} diff --git a/packages/core/src/mask/StencilSystem.js b/packages/core/src/mask/StencilSystem.js new file mode 100644 index 0000000..62796a5 --- /dev/null +++ b/packages/core/src/mask/StencilSystem.js @@ -0,0 +1,133 @@ +import System from '../System'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class StencilSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + this.stencilMaskStack = []; + } + + /** + * Changes the mask stack that is used by this System. + * + * @param {PIXI.Graphics[]} stencilMaskStack - The mask stack + */ + setMaskStack(stencilMaskStack) + { + const gl = this.renderer.gl; + + if (stencilMaskStack.length !== this.stencilMaskStack.length) + { + if (stencilMaskStack.length === 0) + { + gl.disable(gl.STENCIL_TEST); + } + else + { + gl.enable(gl.STENCIL_TEST); + } + } + + this.stencilMaskStack = stencilMaskStack; + } + + /** + * Applies the Mask and adds it to the current stencil stack. @alvin + * + * @param {PIXI.Graphics} graphics - The mask + */ + pushStencil(graphics) + { + this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); + + // this.renderer._activeRenderTarget.attachStencilBuffer(); + + const gl = this.renderer.gl; + const prevMaskCount = this.stencilMaskStack.length; + + if (prevMaskCount === 0) + { + gl.enable(gl.STENCIL_TEST); + } + + this.stencilMaskStack.push(graphics); + + // Increment the refference stencil value where the new mask overlaps with the old ones. + gl.colorMask(false, false, false, false); + gl.stencilFunc(gl.EQUAL, prevMaskCount, this._getBitwiseMask()); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.INCR); + this.renderer.plugins.graphics.render(graphics); + + this._useCurrent(); + } + + /** + * Removes the last mask from the stencil stack. @alvin + */ + popStencil() + { + this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); + + const gl = this.renderer.gl; + const graphics = this.stencilMaskStack.pop(); + + if (this.stencilMaskStack.length === 0) + { + // the stack is empty! + gl.disable(gl.STENCIL_TEST); + gl.clear(gl.STENCIL_BUFFER_BIT); + gl.clearStencil(0); + } + else + { + // Decrement the refference stencil value where the popped mask overlaps with the other ones + gl.colorMask(false, false, false, false); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.DECR); + this.renderer.plugins.graphics.render(graphics); + + this._useCurrent(); + } + } + + /** + * Setup renderer to use the current stencil data. + */ + _useCurrent() + { + const gl = this.renderer.gl; + + gl.colorMask(true, true, true, true); + gl.stencilFunc(gl.EQUAL, this.stencilMaskStack.length, this._getBitwiseMask()); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP); + } + + /** + * Fill 1s equal to the number of acitve stencil masks. + * + * @return {number} The bitwise mask. + */ + _getBitwiseMask() + { + return (1 << this.stencilMaskStack.length) - 1; + } + + /** + * Destroys the mask stack. + * + */ + destroy() + { + super.destroy(this); + + this.stencilMaskStack = null; + } +} diff --git a/packages/core/src/projection/ProjectionSystem.js b/packages/core/src/projection/ProjectionSystem.js new file mode 100644 index 0000000..6e63b73 --- /dev/null +++ b/packages/core/src/projection/ProjectionSystem.js @@ -0,0 +1,73 @@ +import System from '../System'; +import { Matrix } from '@pixi/math'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class ProjectionSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.projectionMatrix = new Matrix(); + } + + update(destinationFrame, sourceFrame, resolution, root) + { + this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; + this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; + + this.calculateProjection(this.destinationFrame, this.sourceFrame, resolution, root); + + this.renderer.globalUniforms.uniforms.projectionMatrix = this.projectionMatrix; + this.renderer.globalUniforms.update(); + } + + /** + * Updates the projection matrix based on a projection frame (which is a rectangle) + * + * @param {Rectangle} destinationFrame - The destination frame. + * @param {Rectangle} sourceFrame - The source frame. + */ + calculateProjection(destinationFrame, sourceFrame, resolution, root) + { + const pm = this.projectionMatrix; + + // I don't think we will need this line.. + // pm.identity(); + + if (!root) + { + pm.a = (1 / destinationFrame.width * 2) * resolution; + pm.d = (1 / destinationFrame.height * 2) * resolution; + + pm.tx = -1 - (sourceFrame.x * pm.a); + pm.ty = -1 - (sourceFrame.y * pm.d); + } + else + { + pm.a = (1 / destinationFrame.width * 2) * resolution; + pm.d = (-1 / destinationFrame.height * 2) * resolution; + + pm.tx = -1 - (sourceFrame.x * pm.a); + pm.ty = 1 - (sourceFrame.y * pm.d); + } + } + + /** + * Sets the transform of the active render target to the given matrix + * + * @param {PIXI.Matrix} matrix - The transformation matrix + */ + setTransform()// matrix) + { + // this._activeRenderTarget.transform = matrix; + } +} diff --git a/packages/core/src/renderTexture/BaseRenderTexture.js b/packages/core/src/renderTexture/BaseRenderTexture.js new file mode 100644 index 0000000..88eb83b --- /dev/null +++ b/packages/core/src/renderTexture/BaseRenderTexture.js @@ -0,0 +1,137 @@ +import BaseTexture from '../textures/BaseTexture'; +import FrameBuffer from '../framebuffer/FrameBuffer'; + +/** + * A BaseRenderTexture is a special texture that allows any PixiJS display object to be rendered to it. + * + * __Hint__: All DisplayObjects (i.e. Sprites) that render to a BaseRenderTexture should be preloaded + * otherwise black rectangles will be drawn instead. + * + * A BaseRenderTexture takes a snapshot of any Display Object given to its render method. The position + * and rotation of the given Display Objects is ignored. For example: + * + * ```js + * let renderer = PIXI.autoDetectRenderer(1024, 1024, { view: canvas, ratio: 1 }); + * let baseRenderTexture = new PIXI.BaseRenderTexture(renderer, 800, 600); + * let sprite = PIXI.Sprite.fromImage("spinObj_01.png"); + * + * sprite.position.x = 800/2; + * sprite.position.y = 600/2; + * sprite.anchor.x = 0.5; + * sprite.anchor.y = 0.5; + * + * baseRenderTexture.render(sprite); + * ``` + * + * The Sprite in this case will be rendered using its local transform. To render this sprite at 0,0 + * you can clear the transform + * + * ```js + * + * sprite.setTransform() + * + * let baseRenderTexture = new PIXI.BaseRenderTexture(100, 100); + * let renderTexture = new PIXI.RenderTexture(baseRenderTexture); + * + * renderer.render(sprite, renderTexture); // Renders to center of RenderTexture + * ``` + * + * @class + * @extends PIXI.BaseTexture + * @memberof PIXI + */ +export default class BaseRenderTexture extends BaseTexture +{ + /** + * @param {object} [options] + * @param {number} [options.width=100] - The width of the base render texture + * @param {number} [options.height=100] - The height of the base render texture + * @param {PIXI.SCALE_MODES} [options.scaleMode] - See {@link PIXI.SCALE_MODES} for possible values + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the texture being generated + */ + constructor(options) + { + if (typeof options === 'number') + { + /* eslint-disable prefer-rest-params */ + // Backward compatibility of signature + const width = arguments[0]; + const height = arguments[1]; + const scaleMode = arguments[2]; + const resolution = arguments[3]; + + options = { width, height, scaleMode, resolution }; + /* eslint-enable prefer-rest-params */ + } + + super(null, options); + + const { width, height } = options || {}; + + // Set defaults + this.mipmap = false; + this.width = Math.ceil(width) || 100; + this.height = Math.ceil(height) || 100; + this.valid = true; + + /** + * A map of renderer IDs to webgl renderTargets + * + * @private + * @member {object} + */ + // this._glRenderTargets = {}; + + /** + * A reference to the canvas render target (we only need one as this can be shared across renderers) + * + * @private + * @member {object} + */ + this._canvasRenderTarget = null; + + this.clearColor = [0, 0, 0, 0]; + + this.frameBuffer = new FrameBuffer(width * this.resolution, height * this.resolution) + .addColorTexture(0, this); + + // TODO - could this be added the systems? + + /** + * The data structure for the stencil masks + * + * @member {PIXI.Graphics[]} + */ + this.stencilMaskStack = []; + + /** + * The data structure for the filters + * + * @member {PIXI.Graphics[]} + */ + this.filterStack = [{}]; + } + + /** + * Resizes the BaseRenderTexture. + * + * @param {number} width - The width to resize to. + * @param {number} height - The height to resize to. + */ + resize(width, height) + { + width = Math.ceil(width); + height = Math.ceil(height); + this.frameBuffer.resize(width * this.resolution, height * this.resolution); + } + + /** + * Destroys this texture + * + */ + destroy() + { + super.destroy(true); + this.renderer = null; + } +} diff --git a/packages/core/src/renderTexture/RenderTexture.js b/packages/core/src/renderTexture/RenderTexture.js new file mode 100644 index 0000000..f1ecf1f --- /dev/null +++ b/packages/core/src/renderTexture/RenderTexture.js @@ -0,0 +1,153 @@ +import BaseRenderTexture from './BaseRenderTexture'; +import Texture from '../textures/Texture'; + +/** + * A RenderTexture is a special texture that allows any PixiJS display object to be rendered to it. + * + * __Hint__: All DisplayObjects (i.e. Sprites) that render to a RenderTexture should be preloaded + * otherwise black rectangles will be drawn instead. + * + * A RenderTexture takes a snapshot of any Display Object given to its render method. For example: + * + * ```js + * let renderer = PIXI.autoDetectRenderer(1024, 1024, { view: canvas, ratio: 1 }); + * let renderTexture = PIXI.RenderTexture.create(800, 600); + * let sprite = PIXI.Sprite.fromImage("spinObj_01.png"); + * + * sprite.position.x = 800/2; + * sprite.position.y = 600/2; + * sprite.anchor.x = 0.5; + * sprite.anchor.y = 0.5; + * + * renderer.render(sprite, renderTexture); + * ``` + * + * The Sprite in this case will be rendered using its local transform. To render this sprite at 0,0 + * you can clear the transform + * + * ```js + * + * sprite.setTransform() + * + * let renderTexture = new PIXI.RenderTexture.create(100, 100); + * + * renderer.render(sprite, renderTexture); // Renders to center of RenderTexture + * ``` + * + * @class + * @extends PIXI.Texture + * @memberof PIXI + */ +export default class RenderTexture extends Texture +{ + /** + * @param {PIXI.BaseRenderTexture} baseRenderTexture - The renderer used for this RenderTexture + * @param {PIXI.Rectangle} [frame] - The rectangle frame of the texture to show + */ + constructor(baseRenderTexture, frame) + { + // support for legacy.. + let _legacyRenderer = null; + + if (!(baseRenderTexture instanceof BaseRenderTexture)) + { + /* eslint-disable prefer-rest-params, no-console */ + const width = arguments[1]; + const height = arguments[2]; + const scaleMode = arguments[3]; + const resolution = arguments[4]; + + // we have an old render texture.. + console.warn(`Please use RenderTexture.create(${width}, ${height}) instead of the ctor directly.`); + _legacyRenderer = arguments[0]; + /* eslint-enable prefer-rest-params, no-console */ + + frame = null; + baseRenderTexture = new BaseRenderTexture({ + width, + height, + scaleMode, + resolution, + }); + } + + /** + * The base texture object that this texture uses + * + * @member {BaseTexture} + */ + super(baseRenderTexture, frame); + + this.legacyRenderer = _legacyRenderer; + + /** + * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. + * + * @member {boolean} + */ + this.valid = true; + + /** + * FilterSystem temporary storage + * @private + * @member {PIXI.Rectangle} + */ + this.filterFrame = null; + + this._updateUvs(); + } + + /** + * Resizes the RenderTexture. + * + * @param {number} width - The width to resize to. + * @param {number} height - The height to resize to. + * @param {boolean} [resizeBaseTexture=true] - Should the baseTexture.width and height values be resized as well? + */ + resize(width, height, resizeBaseTexture = true) + { + width = Math.ceil(width); + height = Math.ceil(height); + + // TODO - could be not required.. + this.valid = (width > 0 && height > 0); + + this._frame.width = this.orig.width = width; + this._frame.height = this.orig.height = height; + + if (resizeBaseTexture) + { + this.baseTexture.resize(width, height); + } + + this._updateUvs(); + } + + /** + * A short hand way of creating a render texture. + * + * @param {object} [options] - Options + * @param {number} [options.width=100] - The width of the render texture + * @param {number} [options.height=100] - The height of the render texture + * @param {number} [options.scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the texture being generated + * @return {PIXI.RenderTexture} The new render texture + */ + static create(options) + { + // fallback, old-style: create(width, height, scaleMode, resolution) + if (typeof options === 'number') + { + /* eslint-disable prefer-rest-params */ + options = { + width: options, + height: arguments[1], + scaleMode: arguments[2], + resolution: arguments[3], + }; + /* eslint-enable prefer-rest-params */ + } + + return new RenderTexture(new BaseRenderTexture(options)); + } +} diff --git a/packages/core/src/renderTexture/RenderTextureSystem.js b/packages/core/src/renderTexture/RenderTextureSystem.js new file mode 100644 index 0000000..8d0ca61 --- /dev/null +++ b/packages/core/src/renderTexture/RenderTextureSystem.js @@ -0,0 +1,115 @@ +import System from '../System'; +import { Rectangle } from '@pixi/math'; + +const tempRect = new Rectangle(); + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class RenderTextureSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.clearColor = renderer._backgroundColorRgba; + + // TODO moe this property somewhere else! + this.defaultMaskStack = []; + this.defaultFilterStack = [{}]; + + // empty render texture? + this.renderTexture = null; + + this.destinationFrame = new Rectangle(); + } + + bind(renderTexture, sourceFrame, destinationFrame) + { + // TODO - do we want this?? + if (this.renderTexture === renderTexture) return; + this.renderTexture = renderTexture; + + const renderer = this.renderer; + + if (renderTexture) + { + const baseTexture = renderTexture.baseTexture; + + if (!destinationFrame) + { + tempRect.width = baseTexture.realWidth; + tempRect.height = baseTexture.realHeight; + + destinationFrame = tempRect; + } + + if (!sourceFrame) + { + sourceFrame = destinationFrame; + } + + this.renderer.framebuffer.bind(baseTexture.frameBuffer, destinationFrame); + + this.renderer.projection.update(destinationFrame, sourceFrame, baseTexture.resolution, false); + this.renderer.stencil.setMaskStack(baseTexture.stencilMaskStack); + } + else + { + // TODO these validation checks happen deeper down.. + // thing they can be avoided.. + if (!destinationFrame) + { + tempRect.width = renderer.width; + tempRect.height = renderer.height; + + destinationFrame = tempRect; + } + + if (!sourceFrame) + { + sourceFrame = destinationFrame; + } + + renderer.framebuffer.bind(null, destinationFrame); + + // TODO store this.. + this.renderer.projection.update(destinationFrame, sourceFrame, this.renderer.resolution, true); + this.renderer.stencil.setMaskStack(this.defaultMaskStack); + } + + this.destinationFrame.copyFrom(destinationFrame); + } + + /** + * Erases the render texture and fills the drawing area with a colour + * + * @param {number} [clearColor] - The colour + * @return {PIXI.Renderer} Returns itself. + */ + clear(clearColor) + { + if (this.renderTexture) + { + clearColor = clearColor || this.renderTexture.baseTexture.clearColor; + } + else + { + clearColor = clearColor || this.clearColor; + } + + this.renderer.framebuffer.clear(clearColor[0], clearColor[1], clearColor[2], clearColor[3]); + } + + resize()// screenWidth, screenHeight) + { + // resize the root only! + this.bind(null); + } +} diff --git a/packages/core/src/renderers/AbstractRenderer.js b/packages/core/src/renderers/AbstractRenderer.js deleted file mode 100644 index 928670f..0000000 --- a/packages/core/src/renderers/AbstractRenderer.js +++ /dev/null @@ -1,353 +0,0 @@ -import { hex2string, hex2rgb } from '@pixi/utils'; -import { Matrix, Rectangle } from '@pixi/math'; -import { RENDERER_TYPE } from '@pixi/constants'; -import { settings } from '@pixi/settings'; -import { Container } from '@pixi/display'; -import RenderTexture from '../textures/RenderTexture'; -import EventEmitter from 'eventemitter3'; - -const tempMatrix = new Matrix(); - -/** - * 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 EventEmitter - * @memberof PIXI - */ -export default class AbstractRenderer extends EventEmitter -{ - // eslint-disable-next-line valid-jsdoc - /** - * @param {string} system - The name of the system this renderer is for. - * @param {object} [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.transparent=false] - If the render view is transparent, default false - * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false - * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) - * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. The - * resolution of the renderer retina would be 2. - * @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 {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when rendering, - * stopping pixel interpolation. - */ - constructor(system, options, arg2, arg3) - { - super(); - - // Support for constructor(system, screenWidth, screenHeight, options) - if (typeof options === 'number') - { - options = Object.assign({ - width: options, - height: arg2 || settings.RENDER_OPTIONS.height, - }, arg3); - } - - // 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 = RENDERER_TYPE.UNKNOWN; - - /** - * Measurements of the screen. (0, 0, screenWidth, screenHeight) - * - * Its safe to use as filterArea or hitArea for 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 1 - */ - this.resolution = options.resolution || settings.RESOLUTION; - - /** - * Whether the render view is transparent - * - * @member {boolean} - */ - this.transparent = options.transparent; - - /** - * Whether css dimensions of canvas view should be resized to screen dimensions automatically - * - * @member {boolean} - */ - this.autoResize = options.autoResize || false; - - /** - * Tracks the blend modes useful for this renderer. - * - * @member {object} - */ - this.blendModes = null; - - /** - * 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; - - /** - * If true PixiJS will Math.floor() x/y values when rendering, stopping pixel interpolation. - * Handy for crisp pixel art and speed on legacy devices. - * - * @member {boolean} - */ - this.roundPixels = options.roundPixels; - - /** - * The background color as a number. - * - * @member {number} - * @private - */ - this._backgroundColor = 0x000000; - - /** - * The background color as an [R, G, B] array. - * - * @member {number[]} - * @private - */ - this._backgroundColorRgba = [0, 0, 0, 0]; - - /** - * The background color as a string. - * - * @member {string} - * @private - */ - this._backgroundColorString = '#000000'; - - this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter - - /** - * This temporary display object used as the parent of the currently being rendered item - * - * @member {PIXI.DisplayObject} - * @private - */ - this._tempDisplayObjectParent = new Container(); - - /** - * The last root object that the renderer tried to render. - * - * @member {PIXI.DisplayObject} - * @private - */ - this._lastObjectRendered = this._tempDisplayObjectParent; - - /** - * Collection of plugins. - * @readonly - * @member {object} - */ - this.plugins = {}; - } - - /** - * Initialize the plugins. - * - * @protected - * @param {object} staticMap - The dictionary of staticly saved plugins. - */ - initPlugins(staticMap) - { - 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() - { - return this.view.width; - } - - /** - * Same as view.height, actual number of pixels in the canvas by vertical - * - * @member {number} - * @readonly - * @default 600 - */ - get height() - { - return this.view.height; - } - - /** - * Resizes the screen and canvas to the specified width and height - * Canvas dimensions are multiplied by resolution - * - * @param {number} screenWidth - the new width of the screen - * @param {number} screenHeight - the new height of the screen - */ - resize(screenWidth, screenHeight) - { - this.screen.width = screenWidth; - this.screen.height = screenHeight; - - this.view.width = screenWidth * this.resolution; - this.view.height = screenHeight * this.resolution; - - if (this.autoResize) - { - this.view.style.width = `${screenWidth}px`; - this.view.style.height = `${screenHeight}px`; - } - } - - /** - * 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 {PIXI.DisplayObject} displayObject - The displayObject the object will be generated from - * @param {number} scaleMode - Should be one of the scaleMode consts - * @param {number} resolution - The resolution / device pixel ratio of the texture being generated - * @param {PIXI.Rectangle} [region] - The region of the displayObject, that shall be rendered, - * if no region is specified, defaults to the local bounds of the displayObject. - * @return {PIXI.Texture} a texture of the graphics object - */ - generateTexture(displayObject, scaleMode, resolution, region) - { - region = region || displayObject.getLocalBounds(); - - // 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(region.width | 0, region.height | 0, scaleMode, resolution); - - tempMatrix.tx = -region.x; - tempMatrix.ty = -region.y; - - this.render(displayObject, renderTexture, false, tempMatrix, true); - - return renderTexture; - } - - /** - * Removes everything from the renderer and optionally removes the Canvas DOM element. - * - * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. - */ - destroy(removeView) - { - 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); - } - - this.plugins = null; - - this.type = RENDERER_TYPE.UNKNOWN; - - this.view = null; - - this.screen = null; - - this.resolution = 0; - - this.transparent = false; - - this.autoResize = false; - - this.blendModes = null; - - this.options = null; - - this.preserveDrawingBuffer = false; - this.clearBeforeRender = false; - - this.roundPixels = false; - - this._backgroundColor = 0; - this._backgroundColorRgba = null; - this._backgroundColorString = null; - - this._tempDisplayObjectParent = null; - this._lastObjectRendered = null; - } - - /** - * The background color to fill if not transparent - * - * @member {number} - */ - get backgroundColor() - { - return this._backgroundColor; - } - - set backgroundColor(value) // eslint-disable-line require-jsdoc - { - this._backgroundColor = value; - this._backgroundColorString = hex2string(value); - hex2rgb(value, this._backgroundColorRgba); - } -} diff --git a/packages/core/src/renderers/Renderer.js b/packages/core/src/renderers/Renderer.js deleted file mode 100644 index a2020b1..0000000 --- a/packages/core/src/renderers/Renderer.js +++ /dev/null @@ -1,329 +0,0 @@ -import AbstractRenderer from './AbstractRenderer'; -import { sayHello } from '@pixi/utils'; -import MaskSystem from './systems/MaskSystem'; -import StencilSystem from './systems/StencilSystem'; -import FilterSystem from './systems/filter/FilterSystem'; -import FramebufferSystem from './systems/FramebufferSystem'; -import RenderTextureSystem from './systems/RenderTextureSystem'; -import TextureSystem from './systems/textures/TextureSystem'; -import ProjectionSystem from './systems/ProjectionSystem'; -import StateSystem from './systems/StateSystem'; -import GeometrySystem from './systems/geometry/GeometrySystem'; -import ShaderSystem from './systems/shader/ShaderSystem'; -import ContextSystem from './systems/ContextSystem'; -import BatchSystem from './systems/BatchSystem'; -import TextureGCSystem from './systems/textures/TextureGCSystem'; -import { RENDERER_TYPE } from '@pixi/constants'; -import UniformGroup from '../shader/UniformGroup'; -import { Matrix } from '@pixi/math'; -import Runner from 'mini-runner'; - -/** - * 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 Render works by automatically managing webGLBatchs. - * 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 :) - * - * @class - * @memberof PIXI - * @extends PIXI.AbstractRenderer - */ -export default class Renderer extends AbstractRenderer -{ - // eslint-disable-next-line valid-jsdoc - /** - * - * @param {object} [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.transparent=false] - If the render view is transparent, default false - * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false - * @param {boolean} [options.antialias=false] - sets antialias. If not available natively then FXAA - * antialiasing is used - * @param {boolean} [options.forceFXAA=false] - forces FXAA antialiasing to be used over native. - * FXAA is faster, but may not always look as great - * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. - * The resolution of the renderer retina would be 2. - * @param {boolean} [options.clearBeforeRender=true] - This sets if the renderer will clear - * the canvas or not before the new render pass. If you wish to set this to false, you *must* set - * preserveDrawingBuffer to `true`. - * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, - * enable this if you need to call toDataUrl on the webgl context. - * @param {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when - * rendering, stopping pixel interpolation. - * @param {number} [options.backgroundColor=0x000000] - The background color of the rendered area - * (shown if not transparent). - * @param {boolean} [options.legacy=false] - If true PixiJS will aim to ensure compatibility - * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. - * @param {string} [options.powerPreference] - Parameter passed to webgl context, set to "high-performance" - * for devices with dual graphics card - */ - constructor(options, arg2, arg3) - { - super('WebGL', options, arg2, arg3); - - /** - * The type of this renderer as a standardised const - * - * @member {number} - * @see PIXI.RENDERER_TYPE - */ - this.type = RENDERER_TYPE.WEBGL; - - // this will be set by the contextSystem (this.context) - this.gl = null; - this.CONTEXT_UID = 0; - this.legacy = !!options.legacy; - - // TODO legacy! - - // runners! - this.runners = { - destroy: new Runner('destroy'), - contextChange: new Runner('contextChange', 1), - reset: new Runner('reset'), - update: new Runner('update'), - postrender: new Runner('postrender'), - prerender: new Runner('prerender'), - resize: new Runner('resize', 2), - }; - - 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(GeometrySystem, 'geometry') - .addSystem(FramebufferSystem, 'framebuffer') - .addSystem(StencilSystem, 'stencil') - .addSystem(ProjectionSystem, 'projection') - .addSystem(TextureGCSystem, 'textureGC') - .addSystem(FilterSystem, 'filter') - .addSystem(RenderTextureSystem, 'renderTexture') - .addSystem(BatchSystem, 'batch'); - - this.initPlugins(Renderer.__plugins); - - /** - * The options passed in to create a new webgl context. - * - * @member {object} - * @private - */ - if (options.context) - { - this.context.initFromContext(options.context); - } - else - { - this.context.initFromOptions({ - alpha: this.transparent, - antialias: options.antialias, - premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', - stencil: true, - 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); - } - - addSystem(_class, name) - { - if (!name) - { - name = _class.name; - } - - // TODO - read name from class.name.. - - /* - if(name.includes('System')) - { - name = name.replace('System', ''); - name = name.charAt(0).toLowerCase() + name.slice(1); - } - */ - - const system = new _class(this); - - if (this[name]) - { - throw new Error(`Whoops! ${name} is already a manger`); - } - - this[name] = system; - - for (const i in this.runners) - { - this.runners[i].add(system); - } - - return this; - - /** - * 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. - */ - } - - /** - * Renders the object to its webGL view - * - * @param {PIXI.DisplayObject} displayObject - the object to be rendered - * @param {PIXI.RenderTexture} renderTexture - The render texture to render to. - * @param {boolean} [clear] - Should the canvas be cleared before the new render - * @param {PIXI.Transform} [transform] - A transform to apply to the render texture before rendering. - * @param {boolean} [skipUpdateTransform] - Should we skip the update transform pass? - */ - render(displayObject, renderTexture, clear, transform, skipUpdateTransform) - { - // can be handy to know! - this.renderingToScreen = !renderTexture; - - this.runners.prerender.run(); - this.emit('prerender'); - - // 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.parent; - - displayObject.parent = this._tempDisplayObjectParent; - displayObject.updateTransform(); - displayObject.parent = 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.run(); - - this.emit('postrender'); - } - - /** - * Resizes the webGL view to the specified width and height. - * - * @param {number} screenWidth - the new width of the screen - * @param {number} screenHeight - the new height of the screen - */ - resize(screenWidth, screenHeight) - { - AbstractRenderer.prototype.resize.call(this, screenWidth, screenHeight); - - this.runners.resize.run(screenWidth, screenHeight); - } - - /** - * Resets the WebGL state so you can render things however you fancy! - * - * @return {PIXI.Renderer} Returns itself. - */ - reset() - { - this.runners.reset.run(); - - return this; - } - - clear() - { - this.framebuffer.bind(); - this.framebuffer.clear(); - } - - /** - * Removes everything from the renderer (event listeners, spritebatch, etc...) - * - * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. - * See: https://github.com/pixijs/pixi.js/issues/2233 - */ - destroy(removeView) - { - this.runners.destroy.run(); - - // call base destroy - super.destroy(removeView); - - // TODO nullify all the managers.. - this.gl = null; - } - - /** - * 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 - * builds and excluding plugins. - * @name PIXI.Renderer#plugins - * @type {object} - * @readonly - * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. - * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. - * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. - * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. - */ - - /** - * Adds a plugin to the renderer. - * - * @method - * @param {string} pluginName - The name of the plugin. - * @param {Function} ctor - The constructor function or class for the plugin. - */ - static registerPlugin(pluginName, ctor) - { - Renderer.__plugins = Renderer.__plugins || {}; - Renderer.__plugins[pluginName] = ctor; - } -} diff --git a/packages/core/src/renderers/State.js b/packages/core/src/renderers/State.js deleted file mode 100644 index fa3f221..0000000 --- a/packages/core/src/renderers/State.js +++ /dev/null @@ -1,163 +0,0 @@ -/* eslint-disable max-len */ - -const BLEND = 0; -const OFFSET = 1; -const CULLING = 2; -const DEPTH_TEST = 3; -const WINDING = 4; - -/** - * This is a webGL state. It is passed The WebGL StateManager. - * Each mesh renderered may require webGL to be in a different state. - * For example you may want different blend mode or to enable polygon offsets - * - * @class - * @memberof PIXI - */ -export default class State -{ - /** - * - */ - constructor() - { - this.data = 0; - - this.blendMode = 0; - this.polygonOffset = 0; - - this.blend = true; - // this.depthTest = true; - } - - /** - * Activates blending of the computed fragment color values - * - * @member {boolean} - */ - get blend() - { - return !!(this.data & (1 << BLEND)); - } - - set blend(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << BLEND)) !== value) - { - this.data ^= (1 << BLEND); - } - } - - /** - * Activates adding an offset to depth values of polygon's fragments - * - * @member {boolean} - * @default false - */ - get offsets() - { - return !!(this.data & (1 << OFFSET)); - } - - set offsets(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << OFFSET)) !== value) - { - this.data ^= (1 << OFFSET); - } - } - - /** - * Activates culling of polygons. - * - * @member {boolean} - * @default false - */ - get culling() - { - return !!(this.data & (1 << CULLING)); - } - - set culling(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << CULLING)) !== value) - { - this.data ^= (1 << CULLING); - } - } - - /** - * Activates depth comparisons and updates to the depth buffer. - * - * @member {boolean} - * @default false - */ - get depthTest() - { - return !!(this.data & (1 << DEPTH_TEST)); - } - - set depthTest(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << DEPTH_TEST)) !== value) - { - this.data ^= (1 << DEPTH_TEST); - } - } - - /** - * Specifies whether or not front or back-facing polygons can be culled. - * @member {boolean} - * @default false - */ - get clockwiseFrontFace() - { - return !!(this.data & (1 << WINDING)); - } - - set clockwiseFrontFace(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << WINDING)) !== value) - { - this.data ^= (1 << WINDING); - } - } - - /** - * The blend mode to be applied when this state is set. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. - * Setting this mode to anything other than NO_BLEND will automatically switch blending on. - * - * @member {boolean} - * @default PIXI.BLEND_MODES.NORMAL - * @see PIXI.BLEND_MODES - */ - get blendMode() - { - return this._blendMode; - } - - set blendMode(value) // eslint-disable-line require-jsdoc - { - // 17 is NO BLEND - this.blend = (value !== 17); - this._blendMode = value; - } - - /** - * The polygon offset. Setting this property to anything other than 0 will automatically enable poygon offset fill. - * - * @member {number} - * @default 0 - */ - get polygonOffset() - { - return this._polygonOffset; - } - - set polygonOffset(value) // eslint-disable-line require-jsdoc - { - this.offsets = !!value; - this._polygonOffset = value; - } -} - diff --git a/packages/core/src/renderers/filters/Filter.js b/packages/core/src/renderers/filters/Filter.js deleted file mode 100644 index 2073b8d..0000000 --- a/packages/core/src/renderers/filters/Filter.js +++ /dev/null @@ -1,140 +0,0 @@ -import Shader from '../../shader/Shader'; -import Program from '../../shader/Program'; -import State from '../State'; -import { settings } from '@pixi/settings'; -// import extractUniformsFromSrc from './extractUniformsFromSrc'; -import defaultVertex from './defaultFilter.vert'; -import defaultFragment from './defaultFilter.frag'; - -// let math = require('../../math'); -/** - * @class - * @memberof PIXI - * @extends PIXI.Shader - */ -export default class Filter extends Shader -{ - /** - * @param {string} [vertexSrc] - The source of the vertex shader. - * @param {string} [fragmentSrc] - The source of the fragment shader. - * @param {object} [uniforms] - Custom uniforms to use to augment the built-in ones. - */ - constructor(vertexSrc, fragmentSrc, uniforms) - { - const program = Program.from(vertexSrc, fragmentSrc); - - super(program, uniforms); - - /** - * The padding of the filter. Some filters require extra space to breath such as a blur. - * Increasing this will add extra width and height to the bounds of the object that the - * filter is applied to. - * - * @member {number} - */ - this.padding = 0; - - /** - * The resolution of the filter. Setting this to be lower will lower the quality but - * increase the performance of the filter. - * - * @member {number} - */ - this.resolution = settings.RESOLUTION; - - /** - * If enabled is true the filter is applied, if false it will not. - * - * @member {boolean} - */ - this.enabled = true; - - /** - * If enabled, PixiJS will fit the filter area into boundaries for better performance. - * Switch it off if it does not work for specific shader. - * - * @member {boolean} - */ - this.autoFit = true; - - /** - * Legacy filters use position and uvs from attributes - * @member {boolean} - * @readonly - */ - this.legacy = !!this.program.attributeData.aTextureCoord; - - /** - * the webGL state the filter requires to render - * @member {PIXI.State} - */ - this.state = new State(); - } - - /** - * Applies the filter - * - * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from - * @param {PIXI.RenderTarget} input - The input render target. - * @param {PIXI.RenderTarget} output - The target to output to. - * @param {boolean} clear - Should the output be cleared before rendering to it - * @param {object} [currentState] - It's current state of filter. - * There are some useful properties in the currentState : - * target, filters, sourceFrame, destinationFrame, renderTarget, resolution - */ - apply(filterManager, input, output, clear, currentState, derp) // eslint-disable-line no-unused-vars - { - // do as you please! - - filterManager.applyFilter(this, input, output, clear, currentState, derp); - - // or just do a regular render.. - } - - /** - * Sets the blendmode of the filter - * - * @member {number} - * @default PIXI.BLEND_MODES.NORMAL - */ - get blendMode() - { - return this.state.blendMode; - } - - set blendMode(value) // eslint-disable-line require-jsdoc - { - this.state.blendMode = value; - } - - /** - * The default vertex shader source - * - * @static - * @constant - */ - static get defaultVertexSrc() - { - return defaultVertex; - } - - /** - * The default fragment shader source - * - * @static - * @constant - */ - static get defaultFragmentSrc() - { - return defaultFragment; - } -} - -/** - * Used for caching shader IDs - * - * @static - * @private - */ -Filter.SOURCE_KEY_MAP = {}; - diff --git a/packages/core/src/renderers/filters/defaultFilter.frag b/packages/core/src/renderers/filters/defaultFilter.frag deleted file mode 100644 index a14c1be..0000000 --- a/packages/core/src/renderers/filters/defaultFilter.frag +++ /dev/null @@ -1,22 +0,0 @@ -varying vec2 vTextureCoord; -varying vec2 vFilterCoord; - -uniform sampler2D uSampler; -uniform sampler2D filterSampler; - -void main(void){ - vec4 masky = texture2D(filterSampler, vFilterCoord); - vec4 sample = texture2D(uSampler, vTextureCoord); - vec4 color; - if(mod(vFilterCoord.x, 1.0) > 0.5) - { - color = vec4(1.0, 0.0, 0.0, 1.0); - } - else - { - color = vec4(0.0, 1.0, 0.0, 1.0); - } - // gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0); - gl_FragColor = mix(sample, masky, 0.5); - gl_FragColor *= sample.a; -} diff --git a/packages/core/src/renderers/filters/defaultFilter.vert b/packages/core/src/renderers/filters/defaultFilter.vert deleted file mode 100644 index e1febca..0000000 --- a/packages/core/src/renderers/filters/defaultFilter.vert +++ /dev/null @@ -1,14 +0,0 @@ -attribute vec2 aVertexPosition; -attribute vec2 aTextureCoord; - -uniform mat3 projectionMatrix; -uniform mat3 filterMatrix; - -varying vec2 vTextureCoord; -varying vec2 vFilterCoord; - -void main(void){ - gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); - vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy; - vTextureCoord = aTextureCoord ; -} diff --git a/packages/core/src/renderers/filters/filterTransforms.js b/packages/core/src/renderers/filters/filterTransforms.js deleted file mode 100644 index 87a3cd6..0000000 --- a/packages/core/src/renderers/filters/filterTransforms.js +++ /dev/null @@ -1,50 +0,0 @@ -import { Matrix } from '@pixi/math'; - -/** - * Calculates the mapped matrix - * @param filterArea {Rectangle} The filter area - * @param sprite {Sprite} the target sprite - * @param outputMatrix {Matrix} @alvin - * @private - */ -// this returns a matrix that will normalise map filter cords in the filter to screen space -export function calculateScreenSpaceMatrix(outputMatrix, filterArea, textureSize) -{ - // TODO unwrap? - const mappedMatrix = outputMatrix.identity(); - - mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); - - mappedMatrix.scale(textureSize.width, textureSize.height); - - return mappedMatrix; -} - -export function calculateNormalizedScreenSpaceMatrix(outputMatrix, filterArea, textureSize) -{ - const mappedMatrix = outputMatrix.identity(); - - mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); - - const translateScaleX = (textureSize.width / filterArea.width); - const translateScaleY = (textureSize.height / filterArea.height); - - mappedMatrix.scale(translateScaleX, translateScaleY); - - return mappedMatrix; -} - -// this will map the filter coord so that a texture can be used based on the transform of a sprite -export function calculateSpriteMatrix(outputMatrix, filterArea, textureSize, sprite) -{ - const orig = sprite._texture.orig; - const mappedMatrix = outputMatrix.set(textureSize.width, 0, 0, textureSize.height, filterArea.x, filterArea.y); - const worldTransform = sprite.worldTransform.copyTo(Matrix.TEMP_MATRIX); - - worldTransform.invert(); - mappedMatrix.prepend(worldTransform); - mappedMatrix.scale(1.0 / orig.width, 1.0 / orig.height); - mappedMatrix.translate(sprite.anchor.x, sprite.anchor.y); - - return mappedMatrix; -} diff --git a/packages/core/src/renderers/filters/spriteMask/SpriteMaskFilter.js b/packages/core/src/renderers/filters/spriteMask/SpriteMaskFilter.js deleted file mode 100644 index d5a91a4..0000000 --- a/packages/core/src/renderers/filters/spriteMask/SpriteMaskFilter.js +++ /dev/null @@ -1,63 +0,0 @@ -import Filter from '../Filter'; -import { Matrix } from '@pixi/math'; -import vertex from './spriteMaskFilter.vert'; -import fragment from './spriteMaskFilter.frag'; -import { default as TextureMatrix } from '../../../textures/TextureMatrix'; - -/** - * The SpriteMaskFilter class - * - * @class - * @extends PIXI.Filter - * @memberof PIXI - */ -export default class SpriteMaskFilter extends Filter -{ - /** - * @param {PIXI.Sprite} sprite - the target sprite - */ - constructor(sprite) - { - const maskMatrix = new Matrix(); - - super(vertex, fragment); - - sprite.renderable = false; - - this.maskSprite = sprite; - this.maskMatrix = maskMatrix; - } - - /** - * Applies the filter - * - * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from - * @param {PIXI.RenderTarget} input - The input render target. - * @param {PIXI.RenderTarget} output - The target to output to. - */ - apply(filterManager, input, output) - { - const maskSprite = this.maskSprite; - const tex = this.maskSprite.texture; - - if (!tex.valid) - { - return; - } - if (!tex.transform) - { - // margin = 0.0, let it bleed a bit, shader code becomes easier - // assuming that atlas textures were made with 1-pixel padding - tex.transform = new TextureMatrix(tex, 0.0); - } - tex.transform.update(); - - this.uniforms.mask = tex; - this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite) - .prepend(tex.transform.mapCoord); - this.uniforms.alpha = maskSprite.worldAlpha; - this.uniforms.maskClamp = tex.transform.uClampFrame; - - filterManager.applyFilter(this, input, output); - } -} diff --git a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.frag b/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.frag deleted file mode 100644 index 0e4aef8..0000000 --- a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.frag +++ /dev/null @@ -1,23 +0,0 @@ -varying vec2 vMaskCoord; -varying vec2 vTextureCoord; - -uniform sampler2D uSampler; -uniform sampler2D mask; -uniform float alpha; -uniform vec4 maskClamp; - -void main(void) -{ - float clip = step(3.5, - step(maskClamp.x, vMaskCoord.x) + - step(maskClamp.y, vMaskCoord.y) + - step(vMaskCoord.x, maskClamp.z) + - step(vMaskCoord.y, maskClamp.w)); - - vec4 original = texture2D(uSampler, vTextureCoord); - vec4 masky = texture2D(mask, vMaskCoord); - - original *= (masky.r * masky.a * alpha * clip); - - gl_FragColor = original; -} diff --git a/packages/core/src/AbstractRenderer.js b/packages/core/src/AbstractRenderer.js new file mode 100644 index 0000000..64b2eb1 --- /dev/null +++ b/packages/core/src/AbstractRenderer.js @@ -0,0 +1,353 @@ +import { hex2string, hex2rgb } from '@pixi/utils'; +import { Matrix, Rectangle } from '@pixi/math'; +import { RENDERER_TYPE } from '@pixi/constants'; +import { settings } from '@pixi/settings'; +import { Container } from '@pixi/display'; +import RenderTexture from './renderTexture/RenderTexture'; +import EventEmitter from 'eventemitter3'; + +const tempMatrix = new Matrix(); + +/** + * 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 EventEmitter + * @memberof PIXI + */ +export default class AbstractRenderer extends EventEmitter +{ + // eslint-disable-next-line valid-jsdoc + /** + * @param {string} system - The name of the system this renderer is for. + * @param {object} [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.transparent=false] - If the render view is transparent, default false + * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false + * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. The + * resolution of the renderer retina would be 2. + * @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 {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when rendering, + * stopping pixel interpolation. + */ + constructor(system, options, arg2, arg3) + { + super(); + + // Support for constructor(system, screenWidth, screenHeight, options) + if (typeof options === 'number') + { + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + }, arg3); + } + + // 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 = RENDERER_TYPE.UNKNOWN; + + /** + * Measurements of the screen. (0, 0, screenWidth, screenHeight) + * + * Its safe to use as filterArea or hitArea for 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 1 + */ + this.resolution = options.resolution || settings.RESOLUTION; + + /** + * Whether the render view is transparent + * + * @member {boolean} + */ + this.transparent = options.transparent; + + /** + * Whether css dimensions of canvas view should be resized to screen dimensions automatically + * + * @member {boolean} + */ + this.autoResize = options.autoResize || false; + + /** + * Tracks the blend modes useful for this renderer. + * + * @member {object} + */ + this.blendModes = null; + + /** + * 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; + + /** + * If true PixiJS will Math.floor() x/y values when rendering, stopping pixel interpolation. + * Handy for crisp pixel art and speed on legacy devices. + * + * @member {boolean} + */ + this.roundPixels = options.roundPixels; + + /** + * The background color as a number. + * + * @member {number} + * @private + */ + this._backgroundColor = 0x000000; + + /** + * The background color as an [R, G, B] array. + * + * @member {number[]} + * @private + */ + this._backgroundColorRgba = [0, 0, 0, 0]; + + /** + * The background color as a string. + * + * @member {string} + * @private + */ + this._backgroundColorString = '#000000'; + + this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter + + /** + * This temporary display object used as the parent of the currently being rendered item + * + * @member {PIXI.DisplayObject} + * @private + */ + this._tempDisplayObjectParent = new Container(); + + /** + * The last root object that the renderer tried to render. + * + * @member {PIXI.DisplayObject} + * @private + */ + this._lastObjectRendered = this._tempDisplayObjectParent; + + /** + * Collection of plugins. + * @readonly + * @member {object} + */ + this.plugins = {}; + } + + /** + * Initialize the plugins. + * + * @protected + * @param {object} staticMap - The dictionary of staticly saved plugins. + */ + initPlugins(staticMap) + { + 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() + { + return this.view.width; + } + + /** + * Same as view.height, actual number of pixels in the canvas by vertical + * + * @member {number} + * @readonly + * @default 600 + */ + get height() + { + return this.view.height; + } + + /** + * Resizes the screen and canvas to the specified width and height + * Canvas dimensions are multiplied by resolution + * + * @param {number} screenWidth - the new width of the screen + * @param {number} screenHeight - the new height of the screen + */ + resize(screenWidth, screenHeight) + { + this.screen.width = screenWidth; + this.screen.height = screenHeight; + + this.view.width = screenWidth * this.resolution; + this.view.height = screenHeight * this.resolution; + + if (this.autoResize) + { + this.view.style.width = `${screenWidth}px`; + this.view.style.height = `${screenHeight}px`; + } + } + + /** + * 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 {PIXI.DisplayObject} displayObject - The displayObject the object will be generated from + * @param {number} scaleMode - Should be one of the scaleMode consts + * @param {number} resolution - The resolution / device pixel ratio of the texture being generated + * @param {PIXI.Rectangle} [region] - The region of the displayObject, that shall be rendered, + * if no region is specified, defaults to the local bounds of the displayObject. + * @return {PIXI.Texture} a texture of the graphics object + */ + generateTexture(displayObject, scaleMode, resolution, region) + { + region = region || displayObject.getLocalBounds(); + + // 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(region.width | 0, region.height | 0, scaleMode, resolution); + + tempMatrix.tx = -region.x; + tempMatrix.ty = -region.y; + + this.render(displayObject, renderTexture, false, tempMatrix, true); + + return renderTexture; + } + + /** + * Removes everything from the renderer and optionally removes the Canvas DOM element. + * + * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. + */ + destroy(removeView) + { + 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); + } + + this.plugins = null; + + this.type = RENDERER_TYPE.UNKNOWN; + + this.view = null; + + this.screen = null; + + this.resolution = 0; + + this.transparent = false; + + this.autoResize = false; + + this.blendModes = null; + + this.options = null; + + this.preserveDrawingBuffer = false; + this.clearBeforeRender = false; + + this.roundPixels = false; + + this._backgroundColor = 0; + this._backgroundColorRgba = null; + this._backgroundColorString = null; + + this._tempDisplayObjectParent = null; + this._lastObjectRendered = null; + } + + /** + * The background color to fill if not transparent + * + * @member {number} + */ + get backgroundColor() + { + return this._backgroundColor; + } + + set backgroundColor(value) // eslint-disable-line require-jsdoc + { + this._backgroundColor = value; + this._backgroundColorString = hex2string(value); + hex2rgb(value, this._backgroundColorRgba); + } +} diff --git a/packages/core/src/Renderer.js b/packages/core/src/Renderer.js new file mode 100644 index 0000000..2227e78 --- /dev/null +++ b/packages/core/src/Renderer.js @@ -0,0 +1,338 @@ +import AbstractRenderer from './AbstractRenderer'; +import { sayHello } from '@pixi/utils'; +import MaskSystem from './mask/MaskSystem'; +import StencilSystem from './mask/StencilSystem'; +import FilterSystem from './filters/FilterSystem'; +import FramebufferSystem from './framebuffer/FramebufferSystem'; +import RenderTextureSystem from './renderTexture/RenderTextureSystem'; +import TextureSystem from './textures/TextureSystem'; +import ProjectionSystem from './projection/ProjectionSystem'; +import StateSystem from './state/StateSystem'; +import GeometrySystem from './geometry/GeometrySystem'; +import ShaderSystem from './shader/ShaderSystem'; +import ContextSystem from './context/ContextSystem'; +import BatchSystem from './batch/BatchSystem'; +import TextureGCSystem from './textures/TextureGCSystem'; +import { RENDERER_TYPE } from '@pixi/constants'; +import UniformGroup from './shader/UniformGroup'; +import { Matrix } from '@pixi/math'; +import Runner from 'mini-runner'; + +/** + * 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 Render works by automatically managing webGLBatchs. + * 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 :) + * + * @class + * @memberof PIXI + * @extends PIXI.AbstractRenderer + */ +export default class Renderer extends AbstractRenderer +{ + // eslint-disable-next-line valid-jsdoc + /** + * + * @param {object} [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.transparent=false] - If the render view is transparent, default false + * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false + * @param {boolean} [options.antialias=false] - sets antialias. If not available natively then FXAA + * antialiasing is used + * @param {boolean} [options.forceFXAA=false] - forces FXAA antialiasing to be used over native. + * FXAA is faster, but may not always look as great + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. + * The resolution of the renderer retina would be 2. + * @param {boolean} [options.clearBeforeRender=true] - This sets if the renderer will clear + * the canvas or not before the new render pass. If you wish to set this to false, you *must* set + * preserveDrawingBuffer to `true`. + * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, + * enable this if you need to call toDataUrl on the webgl context. + * @param {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when + * rendering, stopping pixel interpolation. + * @param {number} [options.backgroundColor=0x000000] - The background color of the rendered area + * (shown if not transparent). + * @param {boolean} [options.legacy=false] - If true PixiJS will aim to ensure compatibility + * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. + * @param {string} [options.powerPreference] - Parameter passed to webgl context, set to "high-performance" + * for devices with dual graphics card + */ + constructor(options, arg2, arg3) + { + super('WebGL', options, arg2, arg3); + + /** + * The type of this renderer as a standardised const + * + * @member {number} + * @see PIXI.RENDERER_TYPE + */ + this.type = RENDERER_TYPE.WEBGL; + + // this will be set by the contextSystem (this.context) + this.gl = null; + this.CONTEXT_UID = 0; + this.legacy = !!options.legacy; + + // TODO legacy! + + // runners! + this.runners = { + destroy: new Runner('destroy'), + contextChange: new Runner('contextChange', 1), + reset: new Runner('reset'), + update: new Runner('update'), + postrender: new Runner('postrender'), + prerender: new Runner('prerender'), + resize: new Runner('resize', 2), + }; + + 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(GeometrySystem, 'geometry') + .addSystem(FramebufferSystem, 'framebuffer') + .addSystem(StencilSystem, 'stencil') + .addSystem(ProjectionSystem, 'projection') + .addSystem(TextureGCSystem, 'textureGC') + .addSystem(FilterSystem, 'filter') + .addSystem(RenderTextureSystem, 'renderTexture') + .addSystem(BatchSystem, 'batch'); + + this.initPlugins(Renderer.__plugins); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ + if (options.context) + { + this.context.initFromContext(options.context); + } + else + { + this.context.initFromOptions({ + alpha: this.transparent, + antialias: options.antialias, + premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', + stencil: true, + 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); + } + + /** + * Add a new system to the renderer. + * @param {class} ClassRef - Class reference + * @param {string} [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. + * @return {PIXI.Renderer} Return instance of renderer + */ + addSystem(ClassRef, name) + { + if (!name) + { + name = ClassRef.name; + } + + // TODO - read name from class.name.. + + /* + if(name.includes('System')) + { + name = name.replace('System', ''); + name = name.charAt(0).toLowerCase() + name.slice(1); + } + */ + + const system = new ClassRef(this); + + if (this[name]) + { + throw new Error(`Whoops! ${name} is already a manger`); + } + + this[name] = system; + + for (const i in this.runners) + { + this.runners[i].add(system); + } + + return this; + + /** + * 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. + */ + } + + /** + * Renders the object to its webGL view + * + * @param {PIXI.DisplayObject} displayObject - the object to be rendered + * @param {PIXI.RenderTexture} renderTexture - The render texture to render to. + * @param {boolean} [clear] - Should the canvas be cleared before the new render + * @param {PIXI.Transform} [transform] - A transform to apply to the render texture before rendering. + * @param {boolean} [skipUpdateTransform] - Should we skip the update transform pass? + */ + render(displayObject, renderTexture, clear, transform, skipUpdateTransform) + { + // can be handy to know! + this.renderingToScreen = !renderTexture; + + this.runners.prerender.run(); + this.emit('prerender'); + + // 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.parent; + + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = 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.run(); + + this.emit('postrender'); + } + + /** + * Resizes the webGL view to the specified width and height. + * + * @param {number} screenWidth - the new width of the screen + * @param {number} screenHeight - the new height of the screen + */ + resize(screenWidth, screenHeight) + { + AbstractRenderer.prototype.resize.call(this, screenWidth, screenHeight); + + this.runners.resize.run(screenWidth, screenHeight); + } + + /** + * Resets the WebGL state so you can render things however you fancy! + * + * @return {PIXI.Renderer} Returns itself. + */ + reset() + { + this.runners.reset.run(); + + return this; + } + + clear() + { + this.framebuffer.bind(); + this.framebuffer.clear(); + } + + /** + * Removes everything from the renderer (event listeners, spritebatch, etc...) + * + * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. + * See: https://github.com/pixijs/pixi.js/issues/2233 + */ + destroy(removeView) + { + this.runners.destroy.run(); + + // call base destroy + super.destroy(removeView); + + // TODO nullify all the managers.. + this.gl = null; + } + + /** + * 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 + * builds and excluding plugins. + * @name PIXI.Renderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. + */ + + /** + * Adds a plugin to the renderer. + * + * @method + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + static registerPlugin(pluginName, ctor) + { + Renderer.__plugins = Renderer.__plugins || {}; + Renderer.__plugins[pluginName] = ctor; + } +} diff --git a/packages/core/src/System.js b/packages/core/src/System.js new file mode 100644 index 0000000..05a10c8 --- /dev/null +++ b/packages/core/src/System.js @@ -0,0 +1,42 @@ +/** + * System is a base class used for extending systems used by the {@link PIXI.Renderer} + * @see PIXI.Renderer#addSystem + * @class + * @memberof PIXI + */ +export default class System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + /** + * The renderer this manager works for. + * + * @member {PIXI.Renderer} + */ + this.renderer = renderer; + + this.renderer.runners.contextChange.add(this); + } + + /** + * Generic method called when there is a WebGL context change. + * + */ + contextChange() + { + // do some codes init! + } + + /** + * Generic destroy methods to be overridden by the subclass + * + */ + destroy() + { + this.renderer.runners.contextChange.remove(this); + this.renderer = null; + } +} diff --git a/packages/core/src/batch/BatchSystem.js b/packages/core/src/batch/BatchSystem.js new file mode 100644 index 0000000..b04c249 --- /dev/null +++ b/packages/core/src/batch/BatchSystem.js @@ -0,0 +1,68 @@ +import System from '../System'; +import ObjectRenderer from './ObjectRenderer'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class BatchSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + super(renderer); + + /** + * An empty renderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.emptyRenderer = new ObjectRenderer(renderer); + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.currentRenderer = this.emptyRenderer; + } + + /** + * Changes the current renderer to the one given in parameter + * + * @param {PIXI.ObjectRenderer} objectRenderer - The object renderer to use. + */ + setObjectRenderer(objectRenderer) + { + if (this.currentRenderer === objectRenderer) + { + return; + } + + this.currentRenderer.stop(); + this.currentRenderer = objectRenderer; + + this.renderer.state.setState(objectRenderer.state); + + this.currentRenderer.start(); + } + + /** + * This should be called if you wish to do some custom rendering + * It will basically render anything that may be batched up such as sprites + * + */ + flush() + { + this.setObjectRenderer(this.emptyRenderer); + } + + reset() + { + this.setObjectRenderer(this.emptyRenderer); + } +} diff --git a/packages/core/src/batch/ObjectRenderer.js b/packages/core/src/batch/ObjectRenderer.js new file mode 100644 index 0000000..0c851a9 --- /dev/null +++ b/packages/core/src/batch/ObjectRenderer.js @@ -0,0 +1,48 @@ +import System from '../System'; + +/** + * Base for a common object renderer that can be used as a system renderer plugin. + * + * @class + * @extends PIXI.System + * @memberof PIXI + */ +export default class ObjectRenderer extends System +{ + /** + * Starts the renderer and sets the shader + * + */ + start() + { + // set the shader.. + } + + /** + * Stops the renderer + * + */ + stop() + { + this.flush(); + } + + /** + * Stub method for rendering content and emptying the current batch. + * + */ + flush() + { + // flush! + } + + /** + * Renders an object + * + * @param {PIXI.DisplayObject} object - The object to render. + */ + render(object) // eslint-disable-line no-unused-vars + { + // render the object + } +} diff --git a/packages/core/src/context/ContextSystem.js b/packages/core/src/context/ContextSystem.js new file mode 100644 index 0000000..301aab4 --- /dev/null +++ b/packages/core/src/context/ContextSystem.js @@ -0,0 +1,186 @@ +import System from '../System'; +import { settings } from '@pixi/settings'; + +let CONTEXT_UID = 0; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class ContextSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.webGLVersion = 1; + + this.handleContextLost = this.handleContextLost.bind(this); + this.handleContextRestored = this.handleContextRestored.bind(this); + + this.extensions = {}; + + renderer.view.addEventListener('webglcontextlost', this.handleContextLost, false); + renderer.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + } + + get isLost() + { + return (!this.gl || this.gl.isContextLost()); + } + + contextChange(gl) + { + this.gl = gl; + + // restore a context if it was previously lost + if (gl.isContextLost() && gl.getExtension('WEBGL_lose_context')) + { + gl.getExtension('WEBGL_lose_context').restoreContext(); + } + } + + initFromContext(gl) + { + this.gl = gl; + this.validateContext(gl); + this.renderer.gl = gl; + this.renderer.CONTEXT_UID = CONTEXT_UID++; + this.renderer.runners.contextChange.run(gl); + } + + initFromOptions(options) + { + const gl = this.createContext(this.renderer.view, options); + + this.initFromContext(gl); + } + + /** + * Helper class to create a webGL Context + * + * @class + * @memberof PIXI.glCore + * @param canvas {HTMLCanvasElement} the canvas element that we will get the context from + * @param options {Object} An options object that gets passed in to the canvas element containing the context attributes, + * see https://developer.mozilla.org/en/docs/Web/API/HTMLCanvasElement/getContext for the options available + * @return {WebGLRenderingContext} the WebGL context + */ + createContext(canvas, options) + { + let gl; + + if (settings.PREFER_WEBGL_2) + { + gl = canvas.getContext('webgl2', options); + } + + if (gl) + { + this.webGLVersion = 2; + } + else + { + this.webGLVersion = 1; + + gl = canvas.getContext('webgl', options) + || canvas.getContext('experimental-webgl', options); + + if (!gl) + { + // fail, not able to get a context + throw new Error('This browser does not support webGL. Try using the canvas renderer'); + } + } + + this.gl = gl; + + this.getExtensions(); + + return gl; + } + + getExtensions() + { + // time to set up default etensions that pixi uses.. + const gl = this.gl; + const extensions = this.extensions; + + if (this.webGLVersion === 1) + { + extensions.drawBuffers = gl.getExtension('WEBGL_draw_buffers'); + extensions.depthTexture = gl.getExtension('WEBKIT_WEBGL_depth_texture'); + extensions.floatTexture = gl.getExtension('OES_texture_float'); + extensions.loseContext = gl.getExtension('WEBGL_lose_context'); + + extensions.vertexArrayObject = gl.getExtension('OES_vertex_array_object') + || gl.getExtension('MOZ_OES_vertex_array_object') + || gl.getExtension('WEBKIT_OES_vertex_array_object'); + } + + // we don't use any specific WebGL 2 ones yet! + } + + /** + * Handles a lost webgl context + * + * @private + * @param {WebGLContextEvent} event - The context lost event. + */ + handleContextLost(event) + { + event.preventDefault(); + } + + /** + * Handles a restored webgl context + * + * @private + */ + handleContextRestored() + { + this.renderer.runners.contextChange.run(this.gl); + } + + destroy() + { + const view = this.renderer.view; + + // remove listeners + view.removeEventListener('webglcontextlost', this.handleContextLost); + view.removeEventListener('webglcontextrestored', this.handleContextRestored); + + this.gl.useProgram(null); + + if (this.extensions.loseContext) + { + this.extensions.loseContext.loseContext(); + } + } + + postrender() + { + this.gl.flush(); + } + + validateContext(gl) + { + const attributes = gl.getContextAttributes(); + + // this is going to be fairly simple for now.. but at least we have room to grow! + if (!attributes.stencil) + { + /* eslint-disable max-len */ + + /* eslint-disable no-console */ + console.warn('Provided WebGL context does not have a stencil buffer, masks may not render correctly'); + /* eslint-enable no-console */ + + /* eslint-enable max-len */ + } + } +} diff --git a/packages/core/src/filters/Filter.js b/packages/core/src/filters/Filter.js new file mode 100644 index 0000000..9d8cad8 --- /dev/null +++ b/packages/core/src/filters/Filter.js @@ -0,0 +1,140 @@ +import Shader from '../shader/Shader'; +import Program from '../shader/Program'; +import State from '../state/State'; +import { settings } from '@pixi/settings'; +// import extractUniformsFromSrc from './extractUniformsFromSrc'; +import defaultVertex from './defaultFilter.vert'; +import defaultFragment from './defaultFilter.frag'; + +// let math = require('../../math'); +/** + * @class + * @memberof PIXI + * @extends PIXI.Shader + */ +export default class Filter extends Shader +{ + /** + * @param {string} [vertexSrc] - The source of the vertex shader. + * @param {string} [fragmentSrc] - The source of the fragment shader. + * @param {object} [uniforms] - Custom uniforms to use to augment the built-in ones. + */ + constructor(vertexSrc, fragmentSrc, uniforms) + { + const program = Program.from(vertexSrc, fragmentSrc); + + super(program, uniforms); + + /** + * The padding of the filter. Some filters require extra space to breath such as a blur. + * Increasing this will add extra width and height to the bounds of the object that the + * filter is applied to. + * + * @member {number} + */ + this.padding = 0; + + /** + * The resolution of the filter. Setting this to be lower will lower the quality but + * increase the performance of the filter. + * + * @member {number} + */ + this.resolution = settings.RESOLUTION; + + /** + * If enabled is true the filter is applied, if false it will not. + * + * @member {boolean} + */ + this.enabled = true; + + /** + * If enabled, PixiJS will fit the filter area into boundaries for better performance. + * Switch it off if it does not work for specific shader. + * + * @member {boolean} + */ + this.autoFit = true; + + /** + * Legacy filters use position and uvs from attributes + * @member {boolean} + * @readonly + */ + this.legacy = !!this.program.attributeData.aTextureCoord; + + /** + * the webGL state the filter requires to render + * @member {PIXI.State} + */ + this.state = new State(); + } + + /** + * Applies the filter + * + * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + * @param {boolean} clear - Should the output be cleared before rendering to it + * @param {object} [currentState] - It's current state of filter. + * There are some useful properties in the currentState : + * target, filters, sourceFrame, destinationFrame, renderTarget, resolution + */ + apply(filterManager, input, output, clear, currentState, derp) // eslint-disable-line no-unused-vars + { + // do as you please! + + filterManager.applyFilter(this, input, output, clear, currentState, derp); + + // or just do a regular render.. + } + + /** + * Sets the blendmode of the filter + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL + */ + get blendMode() + { + return this.state.blendMode; + } + + set blendMode(value) // eslint-disable-line require-jsdoc + { + this.state.blendMode = value; + } + + /** + * The default vertex shader source + * + * @static + * @constant + */ + static get defaultVertexSrc() + { + return defaultVertex; + } + + /** + * The default fragment shader source + * + * @static + * @constant + */ + static get defaultFragmentSrc() + { + return defaultFragment; + } +} + +/** + * Used for caching shader IDs + * + * @static + * @private + */ +Filter.SOURCE_KEY_MAP = {}; + diff --git a/packages/core/src/filters/FilterSystem.js b/packages/core/src/filters/FilterSystem.js new file mode 100644 index 0000000..3174318 --- /dev/null +++ b/packages/core/src/filters/FilterSystem.js @@ -0,0 +1,431 @@ +import System from '../System'; + +import RenderTexture from '../renderTexture/RenderTexture'; +import Quad from '../utils/Quad'; +import QuadUv from '../utils/QuadUv'; +import { Rectangle } from '@pixi/math'; +import * as filterTransforms from './filterTransforms'; +import bitTwiddle from 'bit-twiddle'; +import UniformGroup from '../shader/UniformGroup'; +import { DRAW_MODES } from '@pixi/constants'; + +// +/** + * @ignore + * @class + */ +class FilterState +{ + /** + * + */ + constructor() + { + this.renderTexture = null; + this.sourceFrame = new Rectangle(); + this.destinationFrame = new Rectangle(); + this.filters = []; + this.target = null; + this.legacy = false; + this.resolution = 1; + } +} + +/** + * @class + * @memberof PIXI.systems + * @extends PIXI.System + */ +export default class FilterSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + /** + * stores a bunch of PO2 textures used for filtering + * @type {Object} + */ + this.texturePool = {}; + + /** + * a pool for storing filter states, save us creating new ones each tick + * @type {Array} + */ + this.statePool = []; + + /** + * A very simple geometry used when drawing a filter effect to the screen + * @type {Quad} + */ + this.quad = new Quad(); + + this.quadUv = new QuadUv(); + + /** + * Temporary rect for maths + * @type {PIXI.Rectangle} + */ + this.tempRect = new Rectangle(); + + this.activeState = {}; + + /** + * this uniform group is attached to filter uniforms when used + * @type {UniformGroup} + */ + this.globalUniforms = new UniformGroup({ + sourceFrame: this.tempRect, + destinationFrame: this.tempRect, + + // legacy variables + filterArea: new Float32Array(4), + filterClamp: new Float32Array(4), + }, true); + } + + /** + * Adds a new filter to the System. + * + * @param {PIXI.DisplayObject} target - The target of the filter to render. + * @param {PIXI.Filter[]} filters - The filters to apply. + */ + push(target, filters) + { + const renderer = this.renderer; + const filterStack = this.renderer.renderTexture.defaultFilterStack; + const state = this.statePool.pop() || new FilterState(); + + let resolution = filters[0].resolution; + let padding = filters[0].padding; + let autoFit = filters[0].autoFit; + let legacy = filters[0].legacy; + + for (let i = 1; i < filters.length; i++) + { + const filter = filters[i]; + + // lets use the lowest resolution.. + resolution = Math.min(resolution, filter.resolution); + // and the largest amount of padding! + padding = Math.max(padding, filter.padding); + // only auto fit if all filters are autofit + autoFit = autoFit || filter.autoFit; + + legacy = legacy || filter.legacy; + } + + filterStack.push(state); + + state.resolution = resolution; + + state.legacy = legacy; + + // round to whole number based on resolution + // TODO move that to the shader too? + state.sourceFrame = target.filterArea || target.getBounds(true); + + state.sourceFrame.pad(padding); + + if (autoFit) + { + state.sourceFrame.fit(this.renderer.renderTexture.destinationFrame); + } + + state.sourceFrame.round(resolution); + + state.renderTexture = this.getPotFilterTexture(state.sourceFrame.width, state.sourceFrame.height, resolution); + state.filters = filters; + + state.destinationFrame.width = state.renderTexture.width; + state.destinationFrame.height = state.renderTexture.height; + + state.renderTexture.filterFrame = state.sourceFrame; + renderer.renderTexture.bind(state.renderTexture, state.sourceFrame);// /, state.destinationFrame); + renderer.renderTexture.clear(); + } + + /** + * Pops off the filter and applies it. + * + */ + pop() + { + const renderer = this.renderer; + const filterStack = renderer.renderTexture.defaultFilterStack; + const state = filterStack.pop(); + const filters = state.filters; + + this.activeState = state; + + const globalUniforms = this.globalUniforms.uniforms; + + globalUniforms.sourceFrame = state.sourceFrame; + globalUniforms.destinationFrame = state.destinationFrame; + globalUniforms.resolution = state.resolution; + + // only update the rect if its legacy.. + if (state.legacy) + { + const filterArea = globalUniforms.filterArea; + const filterClamp = globalUniforms.filterClamp; + + filterArea[0] = state.destinationFrame.width; + filterArea[1] = state.destinationFrame.height; + filterArea[2] = state.sourceFrame.x; + filterArea[3] = state.sourceFrame.y; + + filterClamp[0] = 0.5 / state.resolution / state.destinationFrame.width; + filterClamp[1] = 0.5 / state.resolution / state.destinationFrame.height; + filterClamp[2] = (state.sourceFrame.width - 0.5) / state.resolution / state.destinationFrame.width; + filterClamp[3] = (state.sourceFrame.height - 0.5) / state.resolution / state.destinationFrame.height; + } + + this.globalUniforms.update(); + + const lastState = filterStack[filterStack.length - 1]; + + if (filters.length === 1) + { + filters[0].apply(this, state.renderTexture, lastState.renderTexture, false, state); + + this.returnFilterTexture(state.renderTexture); + } + else + { + let flip = state.renderTexture; + let flop = this.getPotFilterTexture( + flip.width, + flip.height, + state.resolution + ); + + flop.filterFrame = flip.filterFrame; + + let i = 0; + + for (i = 0; i < filters.length - 1; ++i) + { + filters[i].apply(this, flip, flop, true, state); + + const t = flip; + + flip = flop; + flop = t; + } + + filters[i].apply(this, flip, lastState.renderTexture, false, state); + + this.returnFilterTexture(flip); + this.returnFilterTexture(flop); + } + + this.statePool.push(state); + } + + /** + * Draws a filter. + * + * @param {PIXI.Filter} filter - The filter to draw. + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + * @param {boolean} clear - Should the output be cleared before rendering to it + */ + applyFilter(filter, input, output, clear) + { + const renderer = this.renderer; + + renderer.renderTexture.bind(output, output ? output.filterFrame : null); + + if (clear) + { + // gl.disable(gl.SCISSOR_TEST); + renderer.renderTexture.clear(); + // gl.enable(gl.SCISSOR_TEST); + } + + // set the uniforms.. + filter.uniforms.uSampler = input; + filter.uniforms.filterGlobals = this.globalUniforms; + + // TODO make it so that the order of this does not matter.. + // because it does at the moment cos of global uniforms. + // they need to get resynced + + renderer.state.setState(filter.state); + renderer.shader.bind(filter); + + if (filter.legacy) + { + this.quadUv.map(input._frame, input.filterFrame); + + renderer.geometry.bind(this.quadUv); + renderer.geometry.draw(DRAW_MODES.TRIANGLES); + } + else + { + renderer.geometry.bind(this.quad); + renderer.geometry.draw(DRAW_MODES.TRIANGLE_STRIP); + } + } + + /** + * Calculates the mapped matrix. + * + * TODO playing around here.. this is temporary - (will end up in the shader) + * this returns a matrix that will normalise map filter cords in the filter to screen space + * + * @param {PIXI.Matrix} outputMatrix - the matrix to output to. + * @return {PIXI.Matrix} The mapped matrix. + */ + calculateScreenSpaceMatrix(outputMatrix) + { + const currentState = this.activeState; + + return filterTransforms.calculateScreenSpaceMatrix( + outputMatrix, + currentState.sourceFrame, + currentState.destinationFrame + ); + } + + /** + * This will map the filter coord so that a texture can be used based on the transform of a sprite + * + * @param {PIXI.Matrix} outputMatrix - The matrix to output to. + * @param {PIXI.Sprite} sprite - The sprite to map to. + * @return {PIXI.Matrix} The mapped matrix. + */ + calculateSpriteMatrix(outputMatrix, sprite) + { + const currentState = this.activeState; + + return filterTransforms.calculateSpriteMatrix( + outputMatrix, + currentState.sourceFrame, + currentState.destinationFrame, + sprite + ); + } + + /** + * Destroys this Filter System. + * + * @param {boolean} [contextLost=false] context was lost, do not free shaders + * + */ + destroy(contextLost = false) + { + if (!contextLost) + { + this.emptyPool(); + } + else + { + this.texturePool = {}; + } + } + + /** + * Gets a Power-of-Two render texture. + * + * TODO move to a seperate class could be on renderer? + * also - could cause issue with multiple contexts? + * + * @private + * @param {WebGLRenderingContext} gl - The webgl rendering context + * @param {number} minWidth - The minimum width of the render target. + * @param {number} minHeight - The minimum height of the render target. + * @param {number} resolution - The resolution of the render target. + * @return {PIXI.RenderTarget} The new render target. + */ + getPotFilterTexture(minWidth, minHeight, resolution) + { + minWidth = bitTwiddle.nextPow2(minWidth); + minHeight = bitTwiddle.nextPow2(minHeight); + resolution = resolution || 1; + + const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + + if (!this.texturePool[key]) + { + this.texturePool[key] = []; + } + + let renderTexture = this.texturePool[key].pop(); + + if (!renderTexture) + { + // temporary bypass cache.. + // internally - this will cause a texture to be bound.. + renderTexture = RenderTexture.create({ + width: minWidth, + height: minHeight, + resolution, + }); + } + + return renderTexture; + } + + /** + * Gets extra render texture to use inside current filter + * + * @param {number} resolution resolution of the renderTexture + * @returns {PIXI.RenderTexture} + */ + getFilterTexture(resolution) + { + const rt = this.activeState.renderTexture; + + const filterTexture = this.getPotFilterTexture(rt.width, rt.height, resolution || rt.baseTexture.resolution); + + filterTexture.filterFrame = rt.filterFrame; + + return filterTexture; + } + + /** + * Frees a render texture back into the pool. + * + * @param {PIXI.RenderTarget} renderTexture - The renderTarget to free + */ + returnFilterTexture(renderTexture) + { + renderTexture.filterFrame = null; + + const base = renderTexture.baseTexture; + + const minWidth = base.width; + const minHeight = base.height; + + const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + + this.texturePool[key].push(renderTexture); + } + + /** + * Empties the texture pool. + * + */ + emptyPool() + { + for (const i in this.texturePool) + { + const textures = this.texturePool[i]; + + if (textures) + { + for (let j = 0; j < textures.length; j++) + { + textures[j].destroy(true); + } + } + } + + this.texturePool = {}; + } +} diff --git a/packages/core/src/filters/defaultFilter.frag b/packages/core/src/filters/defaultFilter.frag new file mode 100644 index 0000000..a14c1be --- /dev/null +++ b/packages/core/src/filters/defaultFilter.frag @@ -0,0 +1,22 @@ +varying vec2 vTextureCoord; +varying vec2 vFilterCoord; + +uniform sampler2D uSampler; +uniform sampler2D filterSampler; + +void main(void){ + vec4 masky = texture2D(filterSampler, vFilterCoord); + vec4 sample = texture2D(uSampler, vTextureCoord); + vec4 color; + if(mod(vFilterCoord.x, 1.0) > 0.5) + { + color = vec4(1.0, 0.0, 0.0, 1.0); + } + else + { + color = vec4(0.0, 1.0, 0.0, 1.0); + } + // gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0); + gl_FragColor = mix(sample, masky, 0.5); + gl_FragColor *= sample.a; +} diff --git a/packages/core/src/filters/defaultFilter.vert b/packages/core/src/filters/defaultFilter.vert new file mode 100644 index 0000000..e1febca --- /dev/null +++ b/packages/core/src/filters/defaultFilter.vert @@ -0,0 +1,14 @@ +attribute vec2 aVertexPosition; +attribute vec2 aTextureCoord; + +uniform mat3 projectionMatrix; +uniform mat3 filterMatrix; + +varying vec2 vTextureCoord; +varying vec2 vFilterCoord; + +void main(void){ + gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); + vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy; + vTextureCoord = aTextureCoord ; +} diff --git a/packages/core/src/filters/filterTransforms.js b/packages/core/src/filters/filterTransforms.js new file mode 100644 index 0000000..87a3cd6 --- /dev/null +++ b/packages/core/src/filters/filterTransforms.js @@ -0,0 +1,50 @@ +import { Matrix } from '@pixi/math'; + +/** + * Calculates the mapped matrix + * @param filterArea {Rectangle} The filter area + * @param sprite {Sprite} the target sprite + * @param outputMatrix {Matrix} @alvin + * @private + */ +// this returns a matrix that will normalise map filter cords in the filter to screen space +export function calculateScreenSpaceMatrix(outputMatrix, filterArea, textureSize) +{ + // TODO unwrap? + const mappedMatrix = outputMatrix.identity(); + + mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); + + mappedMatrix.scale(textureSize.width, textureSize.height); + + return mappedMatrix; +} + +export function calculateNormalizedScreenSpaceMatrix(outputMatrix, filterArea, textureSize) +{ + const mappedMatrix = outputMatrix.identity(); + + mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); + + const translateScaleX = (textureSize.width / filterArea.width); + const translateScaleY = (textureSize.height / filterArea.height); + + mappedMatrix.scale(translateScaleX, translateScaleY); + + return mappedMatrix; +} + +// this will map the filter coord so that a texture can be used based on the transform of a sprite +export function calculateSpriteMatrix(outputMatrix, filterArea, textureSize, sprite) +{ + const orig = sprite._texture.orig; + const mappedMatrix = outputMatrix.set(textureSize.width, 0, 0, textureSize.height, filterArea.x, filterArea.y); + const worldTransform = sprite.worldTransform.copyTo(Matrix.TEMP_MATRIX); + + worldTransform.invert(); + mappedMatrix.prepend(worldTransform); + mappedMatrix.scale(1.0 / orig.width, 1.0 / orig.height); + mappedMatrix.translate(sprite.anchor.x, sprite.anchor.y); + + return mappedMatrix; +} diff --git a/packages/core/src/filters/spriteMask/SpriteMaskFilter.js b/packages/core/src/filters/spriteMask/SpriteMaskFilter.js new file mode 100644 index 0000000..91697b7 --- /dev/null +++ b/packages/core/src/filters/spriteMask/SpriteMaskFilter.js @@ -0,0 +1,63 @@ +import Filter from '../Filter'; +import { Matrix } from '@pixi/math'; +import vertex from './spriteMaskFilter.vert'; +import fragment from './spriteMaskFilter.frag'; +import { default as TextureMatrix } from '../../textures/TextureMatrix'; + +/** + * The SpriteMaskFilter class + * + * @class + * @extends PIXI.Filter + * @memberof PIXI + */ +export default class SpriteMaskFilter extends Filter +{ + /** + * @param {PIXI.Sprite} sprite - the target sprite + */ + constructor(sprite) + { + const maskMatrix = new Matrix(); + + super(vertex, fragment); + + sprite.renderable = false; + + this.maskSprite = sprite; + this.maskMatrix = maskMatrix; + } + + /** + * Applies the filter + * + * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + */ + apply(filterManager, input, output) + { + const maskSprite = this.maskSprite; + const tex = this.maskSprite.texture; + + if (!tex.valid) + { + return; + } + if (!tex.transform) + { + // margin = 0.0, let it bleed a bit, shader code becomes easier + // assuming that atlas textures were made with 1-pixel padding + tex.transform = new TextureMatrix(tex, 0.0); + } + tex.transform.update(); + + this.uniforms.mask = tex; + this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite) + .prepend(tex.transform.mapCoord); + this.uniforms.alpha = maskSprite.worldAlpha; + this.uniforms.maskClamp = tex.transform.uClampFrame; + + filterManager.applyFilter(this, input, output); + } +} diff --git a/packages/core/src/filters/spriteMask/spriteMaskFilter.frag b/packages/core/src/filters/spriteMask/spriteMaskFilter.frag new file mode 100644 index 0000000..0e4aef8 --- /dev/null +++ b/packages/core/src/filters/spriteMask/spriteMaskFilter.frag @@ -0,0 +1,23 @@ +varying vec2 vMaskCoord; +varying vec2 vTextureCoord; + +uniform sampler2D uSampler; +uniform sampler2D mask; +uniform float alpha; +uniform vec4 maskClamp; + +void main(void) +{ + float clip = step(3.5, + step(maskClamp.x, vMaskCoord.x) + + step(maskClamp.y, vMaskCoord.y) + + step(vMaskCoord.x, maskClamp.z) + + step(vMaskCoord.y, maskClamp.w)); + + vec4 original = texture2D(uSampler, vTextureCoord); + vec4 masky = texture2D(mask, vMaskCoord); + + original *= (masky.r * masky.a * alpha * clip); + + gl_FragColor = original; +} diff --git a/packages/core/src/filters/spriteMask/spriteMaskFilter.vert b/packages/core/src/filters/spriteMask/spriteMaskFilter.vert new file mode 100644 index 0000000..d23a30c --- /dev/null +++ b/packages/core/src/filters/spriteMask/spriteMaskFilter.vert @@ -0,0 +1,16 @@ +attribute vec2 aVertexPosition; +attribute vec2 aTextureCoord; + +uniform mat3 projectionMatrix; +uniform mat3 otherMatrix; + +varying vec2 vMaskCoord; +varying vec2 vTextureCoord; + +void main(void) +{ + gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); + + vTextureCoord = aTextureCoord; + vMaskCoord = ( otherMatrix * vec3( aTextureCoord, 1.0) ).xy; +} diff --git a/packages/core/src/framebuffer/FrameBuffer.js b/packages/core/src/framebuffer/FrameBuffer.js new file mode 100644 index 0000000..84e9fcc --- /dev/null +++ b/packages/core/src/framebuffer/FrameBuffer.js @@ -0,0 +1,106 @@ +import Texture from '../textures/BaseTexture'; +import { FORMATS, TYPES } from '@pixi/constants'; + +/** + * Frame buffer + * @class + * @memberof PIXI + */ +export default class FrameBuffer +{ + constructor(width, height) + { + this.width = width || 100; + this.height = height || 100; + + this.stencil = false; + this.depth = false; + + this.dirtyId = 0; + this.dirtyFormat = 0; + this.dirtySize = 0; + + this.depthTexture = null; + this.colorTextures = []; + + this.glFrameBuffers = {}; + } + + get colorTexture() + { + return this.colorTextures[0]; + } + + addColorTexture(index, texture) + { + // TODO add some validation to the texture - same width / height etc? + this.colorTextures[index || 0] = texture || new Texture(null, { scaleMode: 0, + resolution: 1, + mipmap: false, + width: this.width, + height: this.height });// || new Texture(); + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + addDepthTexture(texture) + { + /* eslint-disable max-len */ + this.depthTexture = texture || new Texture(null, { scaleMode: 0, + resolution: 1, + width: this.width, + height: this.height, + mipmap: false, + format: FORMATS.DEPTH_COMPONENT, + type: TYPES.UNSIGNED_SHORT });// UNSIGNED_SHORT; + /* eslint-disable max-len */ + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + enableDepth() + { + this.depth = true; + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + enableStencil() + { + this.stencil = true; + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + resize(width, height) + { + if (width === this.width && height === this.height) return; + + this.width = width; + this.height = height; + + this.dirtyId++; + this.dirtySize++; + + for (let i = 0; i < this.colorTextures.length; i++) + { + this.colorTextures[i].setSize(width, height); + } + + if (this.depthTexture) + { + this.depthTexture.setSize(width, height); + } + } +} diff --git a/packages/core/src/framebuffer/FramebufferSystem.js b/packages/core/src/framebuffer/FramebufferSystem.js new file mode 100644 index 0000000..2cc2437 --- /dev/null +++ b/packages/core/src/framebuffer/FramebufferSystem.js @@ -0,0 +1,246 @@ +import System from '../System'; +import { Rectangle } from '@pixi/math'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class FramebufferSystem extends System +{ + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + contextChange() + { + this.gl = this.renderer.gl; + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + this.current = null; + this.viewport = new Rectangle(); + + this.drawBufferExtension = this.renderer.context.extensions.drawBuffers; + } + + bind(framebuffer, frame) + { + const gl = this.gl; + + this.current = framebuffer; + + if (framebuffer) + { + // TODO cacheing layer! + + const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID] || this.initFramebuffer(framebuffer); + + gl.bindFramebuffer(gl.FRAMEBUFFER, fbo.framebuffer); + // makesure all textures are unbound.. + + // now check for updates... + if (fbo.dirtyId !== framebuffer.dirtyId) + { + fbo.dirtyId = framebuffer.dirtyId; + + if (fbo.dirtyFormat !== framebuffer.dirtyFormat) + { + fbo.dirtyFormat = framebuffer.dirtyFormat; + this.updateFramebuffer(framebuffer); + } + else if (fbo.dirtySize !== framebuffer.dirtySize) + { + fbo.dirtySize = framebuffer.dirtySize; + this.resizeFramebuffer(framebuffer); + } + } + + for (let i = 0; i < framebuffer.colorTextures.length; i++) + { + if (framebuffer.colorTextures[i].texturePart) + { + this.renderer.texture.unbind(framebuffer.colorTextures[i].texture); + } + else + { + this.renderer.texture.unbind(framebuffer.colorTextures[i]); + } + } + + if (framebuffer.depthTexture) + { + this.renderer.texture.unbind(framebuffer.depthTexture); + } + + if (frame) + { + this.setViewport(frame.x, frame.y, frame.width, frame.height); + } + else + { + this.setViewport(0, 0, framebuffer.width, framebuffer.height); + } + } + else + { + gl.bindFramebuffer(gl.FRAMEBUFFER, null); + + if (frame) + { + this.setViewport(frame.x, frame.y, frame.width, frame.height); + } + else + { + this.setViewport(0, 0, this.renderer.width, this.renderer.height); + } + } + } + + setViewport(x, y, width, height) + { + const v = this.viewport; + + if (v.width !== width || v.height !== height || v.x !== x || v.y !== y) + { + v.x = x; + v.y = y; + v.width = width; + v.height = height; + + this.gl.viewport(x, y, width, height); + } + } + + get size() + { + if (this.current) + { + // TODO store temp + return { x: 0, y: 0, width: this.current.width, height: this.current.height }; + } + + return { x: 0, y: 0, width: this.renderer.width, height: this.renderer.height }; + } + + clear(r, g, b, a) + { + const gl = this.gl; + + // TODO clear color can be set only one right? + gl.clearColor(r, g, b, a); + gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); + } + + // private functions... + + initFramebuffer(framebuffer) + { + const gl = this.gl; + + // TODO - make this a class? + const fbo = { + framebuffer: gl.createFramebuffer(), + stencil: null, + dirtyId: 0, + dirtyFormat: 0, + dirtySize: 0, + }; + + framebuffer.glFrameBuffers[this.CONTEXT_UID] = fbo; + + return fbo; + } + + resizeFramebuffer(framebuffer) + { + const gl = this.gl; + + if (framebuffer.stencil || framebuffer.depth) + { + gl.bindRenderbuffer(gl.RENDERBUFFER, this.stencil); + gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); + } + } + + updateFramebuffer(framebuffer) + { + const gl = this.gl; + + const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID]; + + // bind the color texture + const colorTextures = framebuffer.colorTextures; + + let count = colorTextures.length; + + if (!this.drawBufferExtension) + { + count = Math.min(count, 1); + } + + const activeTextures = []; + + for (let i = 0; i < count; i++) + { + const texture = framebuffer.colorTextures[i]; + + if (texture.texturePart) + { + this.renderer.texture.bind(texture.texture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.COLOR_ATTACHMENT0 + i, + gl.TEXTURE_CUBE_MAP_NEGATIVE_X + texture.side, + texture.texture._glTextures[this.CONTEXT_UID].texture, + 0); + } + else + { + this.renderer.texture.bind(texture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.COLOR_ATTACHMENT0 + i, + gl.TEXTURE_2D, + texture._glTextures[this.CONTEXT_UID].texture, + 0); + } + + activeTextures.push(gl.COLOR_ATTACHMENT0 + i); + } + + if (this.drawBufferExtension && activeTextures.length > 1) + { + this.drawBufferExtension.drawBuffersWEBGL(activeTextures); + } + + if (framebuffer.depthTexture) + { + const depthTextureExt = this.renderer.context.extensions.depthTexture; + + if (depthTextureExt) + { + const depthTexture = framebuffer.depthTexture; + + this.renderer.texture.bind(depthTexture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.DEPTH_ATTACHMENT, + gl.TEXTURE_2D, + depthTexture._glTextures[this.CONTEXT_UID].texture, + 0); + } + } + + if (framebuffer.stencil || framebuffer.depth) + { + fbo.stencil = gl.createRenderbuffer(); + + gl.bindRenderbuffer(gl.RENDERBUFFER, fbo.stencil); + + // TODO.. this is depth AND stencil? + gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, fbo.stencil); + gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); + // fbo.enableStencil(); + } + } +} diff --git a/packages/core/src/geometry/GLBuffer.js b/packages/core/src/geometry/GLBuffer.js new file mode 100644 index 0000000..0477e8a --- /dev/null +++ b/packages/core/src/geometry/GLBuffer.js @@ -0,0 +1,9 @@ +export default class GLBuffer +{ + constructor(buffer) + { + this.buffer = buffer; + this.updateID = -1; + this.byteLength = -1; + } +} diff --git a/packages/core/src/geometry/GeometrySystem.js b/packages/core/src/geometry/GeometrySystem.js new file mode 100644 index 0000000..959f827 --- /dev/null +++ b/packages/core/src/geometry/GeometrySystem.js @@ -0,0 +1,388 @@ +import System from '../System'; +import GLBuffer from './GLBuffer'; + +const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class GeometrySystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this._activeGeometry = null; + this._activeVao = null; + + this.hasVao = true; + this.hasInstance = true; + } + + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + contextChange() + { + const gl = this.gl = this.renderer.gl; + + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + + // webgl2 + if (!gl.createVertexArray) + { + // webgl 1! + let nativeVaoExtension = this.renderer.context.extensions.vertexArrayObject; + + if (this.renderer.legacy) + { + nativeVaoExtension = null; + } + + if (nativeVaoExtension) + { + gl.createVertexArray = () => + nativeVaoExtension.createVertexArrayOES(); + + gl.bindVertexArray = (vao) => + nativeVaoExtension.bindVertexArrayOES(vao); + + gl.deleteVertexArray = (vao) => + nativeVaoExtension.deleteVertexArrayOES(vao); + } + else + { + this.hasVao = false; + gl.createVertexArray = () => + { + // empty + }; + + gl.bindVertexArray = () => + { + // empty + }; + + gl.deleteVertexArray = () => + { + // empty + }; + } + } + + if (!gl.vertexAttribDivisor) + { + const instanceExt = gl.getExtension('ANGLE_instanced_arrays'); + + if (instanceExt) + { + gl.vertexAttribDivisor = (a, b) => + instanceExt.vertexAttribDivisorANGLE(a, b); + + gl.drawElementsInstanced = (a, b, c, d, e) => + instanceExt.drawElementsInstancedANGLE(a, b, c, d, e); + + gl.drawArraysInstanced = (a, b, c, d) => + instanceExt.drawArraysInstancedANGLE(a, b, c, d); + } + else + { + this.hasInstance = false; + } + } + } + + /** + * Binds geometry so that is can be drawn. Creating a Vao if required + * @private + * @param {PIXI.Geometry} geometry instance of geometry to bind + */ + bind(geometry, shader) + { + shader = shader || this.renderer.shader.shader; + + const gl = this.gl; + + // not sure the best way to address this.. + // currently different shaders require different VAOs for the same geometry + // Still mulling over the best way to solve this one.. + // will likely need to modify the shader attribute locations at run time! + let vaos = geometry.glVertexArrayObjects[this.CONTEXT_UID]; + + if (!vaos) + { + geometry.glVertexArrayObjects[this.CONTEXT_UID] = vaos = {}; + } + + const vao = vaos[shader.program.id] || this.initGeometryVao(geometry, shader.program); + + this._activeGeometry = geometry; + + if (this._activeVao !== vao) + { + this._activeVao = vao; + + if (this.hasVao) + { + gl.bindVertexArray(vao); + } + else + { + this.activateVao(geometry, shader.program); + } + } + + // TODO - optimise later! + // don't need to loop through if nothing changed! + // maybe look to add an 'autoupdate' to geometry? + this.updateBuffers(); + } + + reset() + { + this.unbind(); + } + + updateBuffers() + { + const geometry = this._activeGeometry; + const gl = this.gl; + + for (let i = 0; i < geometry.buffers.length; i++) + { + const buffer = geometry.buffers[i]; + + const glBuffer = buffer._glBuffers[this.CONTEXT_UID]; + + if (buffer._updateID !== glBuffer.updateID) + { + glBuffer.updateID = buffer._updateID; + + // TODO can cache this on buffer! maybe added a getter / setter? + const type = buffer.index ? gl.ELEMENT_ARRAY_BUFFER : gl.ARRAY_BUFFER; + const drawType = buffer.static ? gl.STATIC_DRAW : gl.DYNAMIC_DRAW; + + gl.bindBuffer(type, glBuffer.buffer); + + if (glBuffer.byteLength >= buffer.data.byteLength) + { + // offset is always zero for now! + gl.bufferSubData(type, 0, buffer.data); + } + else + { + gl.bufferData(type, buffer.data, drawType); + } + } + } + } + + checkCompatability(geometry, program) + { + // geometry must have at least all the attributes that the shader requires. + const geometryAttributes = geometry.attributes; + const shaderAttributes = program.attributeData; + + for (const j in shaderAttributes) + { + if (!geometryAttributes[j]) + { + throw new Error(`shader and geometry incompatible, geometry missing the "${j}" attribute`); + } + } + } + + /** + * Creates a Vao with the same structure as the geometry and stores it on the geometry. + * @private + * @param {PIXI.Geometry} geometry instance of geometry to to generate Vao for + */ + initGeometryVao(geometry, program) + { + this.checkCompatability(geometry, program); + + const gl = this.gl; + const CONTEXT_UID = this.CONTEXT_UID; + const buffers = geometry.buffers; + const attributes = geometry.attributes; + + const tempStride = {}; + const tempStart = {}; + + for (const j in buffers) + { + tempStride[j] = 0; + tempStart[j] = 0; + } + + for (const j in attributes) + { + if (!attributes[j].size && program.attributeData[j]) + { + attributes[j].size = program.attributeData[j].size; + } + + tempStride[attributes[j].buffer] += attributes[j].size * byteSizeMap[attributes[j].type]; + } + + for (const j in attributes) + { + const attribute = attributes[j]; + const attribSize = attribute.size; + + if (attribute.stride === undefined) + { + if (tempStride[attribute.buffer] === attribSize * byteSizeMap[attribute.type]) + { + attribute.stride = 0; + } + else + { + attribute.stride = tempStride[attribute.buffer]; + } + } + + if (attribute.start === undefined) + { + attribute.start = tempStart[attribute.buffer]; + + tempStart[attribute.buffer] += attribSize * byteSizeMap[attribute.type]; + } + } + + // first update - and create the buffers! + // only create a gl buffer if it actually gets + for (let i = 0; i < buffers.length; i++) + { + const buffer = buffers[i]; + + if (!buffer._glBuffers[CONTEXT_UID]) + { + buffer._glBuffers[CONTEXT_UID] = new GLBuffer(gl.createBuffer()); + } + } + + // TODO - maybe make this a data object? + // lets wait to see if we need to first! + const vao = gl.createVertexArray(); + + gl.bindVertexArray(vao); + + this.activateVao(geometry, program); + + gl.bindVertexArray(null); + + // add it to the cache! + geometry.glVertexArrayObjects[this.CONTEXT_UID][program.id] = vao; + + return vao; + } + + activateVao(geometry, program) + { + const gl = this.gl; + const CONTEXT_UID = this.CONTEXT_UID; + const buffers = geometry.buffers; + const attributes = geometry.attributes; + + if (geometry.indexBuffer) + { + // first update the index buffer if we have one.. + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, geometry.indexBuffer._glBuffers[CONTEXT_UID].buffer); + } + + let lastBuffer = null; + + // add a new one! + for (const j in attributes) + { + const attribute = attributes[j]; + const buffer = buffers[attribute.buffer]; + const glBuffer = buffer._glBuffers[CONTEXT_UID]; + + if (program.attributeData[j]) + { + if (lastBuffer !== glBuffer) + { + gl.bindBuffer(gl.ARRAY_BUFFER, glBuffer.buffer); + + lastBuffer = glBuffer; + } + + const location = program.attributeData[j].location; + + // TODO introduce state again + // we can optimise this for older devices that have no VAOs + gl.enableVertexAttribArray(location); + + gl.vertexAttribPointer(location, + attribute.size, + attribute.type || gl.FLOAT, + attribute.normalized, + attribute.stride, + attribute.start); + + if (attribute.instance) + { + // TODO calculate instance count based of this... + if (this.hasInstance) + { + gl.vertexAttribDivisor(location, 1); + } + else + { + throw new Error('geometry error, GPU Instancing is not supported on this device'); + } + } + } + } + } + + draw(type, size, start, instanceCount) + { + const gl = this.gl; + const geometry = this._activeGeometry; + + // TODO.. this should not change so maybe cache the function? + + if (geometry.indexBuffer) + { + if (geometry.instanced) + { + /* eslint-disable max-len */ + gl.drawElementsInstanced(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2, instanceCount || 1); + /* eslint-enable max-len */ + } + else + { + gl.drawElements(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2); + } + } + else + if (geometry.instanced) + { + // TODO need a better way to calculate size.. + gl.drawArraysInstanced(type, start, size || geometry.getSize(), instanceCount || 1); + } + else + { + gl.drawArrays(type, start, size || geometry.getSize()); + } + + return this; + } + + unbind() + { + this.gl.bindVertexArray(null); + this._activeVao = null; + this._activeGeometry = null; + } +} diff --git a/packages/core/src/geometry/utils/setVertexAttribArrays.js b/packages/core/src/geometry/utils/setVertexAttribArrays.js new file mode 100644 index 0000000..23b6959 --- /dev/null +++ b/packages/core/src/geometry/utils/setVertexAttribArrays.js @@ -0,0 +1,54 @@ +// var GL_MAP = {}; + +/** + * @param gl {WebGLRenderingContext} The current WebGL context + * @param attribs {*} + * @param state {*} + */ +export default function setVertexAttribArrays(gl, attribs, state) +{ + let i; + + if (state) + { + const tempAttribState = state.tempAttribState; + const attribState = state.attribState; + + for (i = 0; i < tempAttribState.length; i++) + { + tempAttribState[i] = false; + } + + // set the new attribs + for (i = 0; i < attribs.length; i++) + { + tempAttribState[attribs[i].attribute.location] = true; + } + + for (i = 0; i < attribState.length; i++) + { + if (attribState[i] !== tempAttribState[i]) + { + attribState[i] = tempAttribState[i]; + + if (state.attribState[i]) + { + gl.enableVertexAttribArray(i); + } + else + { + gl.disableVertexAttribArray(i); + } + } + } + } + else + { + for (i = 0; i < attribs.length; i++) + { + const attrib = attribs[i]; + + gl.enableVertexAttribArray(attrib.attribute.location); + } + } +} diff --git a/packages/core/src/index.js b/packages/core/src/index.js index 58c456a..fd1f547 100644 --- a/packages/core/src/index.js +++ b/packages/core/src/index.js @@ -1,30 +1,30 @@ import * as resources from './textures/resources'; -import * as systems from './renderers/systems'; +import * as systems from './systems'; -export { resources }; export { systems }; +export { resources }; -export { default as Renderer } from './renderers/Renderer'; -export { default as AbstractRenderer } from './renderers/AbstractRenderer'; -export { default as FrameBuffer } from './textures/FrameBuffer'; +export { default as System } from './System'; +export { default as Renderer } from './Renderer'; +export { default as AbstractRenderer } from './AbstractRenderer'; +export { default as FrameBuffer } from './framebuffer/FrameBuffer'; export { default as CubeTexture } from './textures/CubeTexture'; export { default as BaseTexture } from './textures/BaseTexture'; export { default as Texture } from './textures/Texture'; export { default as TextureMatrix } from './textures/TextureMatrix'; -export { default as RenderTexture } from './textures/RenderTexture'; -export { default as BaseRenderTexture } from './textures/BaseRenderTexture'; +export { default as RenderTexture } from './renderTexture/RenderTexture'; +export { default as BaseRenderTexture } from './renderTexture/BaseRenderTexture'; export { default as TextureUvs } from './textures/TextureUvs'; -export { default as State } from './renderers/State'; -export { default as ObjectRenderer } from './renderers/utils/ObjectRenderer'; -export { default as Quad } from './renderers/utils/Quad'; -export { default as QuadUv } from './renderers/utils/QuadUv'; -export { default as checkMaxIfStatmentsInShader } from './renderers/utils/checkMaxIfStatmentsInShader'; +export { default as State } from './state/State'; +export { default as ObjectRenderer } from './batch/ObjectRenderer'; +export { default as Quad } from './utils/Quad'; +export { default as QuadUv } from './utils/QuadUv'; +export { default as checkMaxIfStatmentsInShader } from './shader/utils/checkMaxIfStatmentsInShader'; export { default as Shader } from './shader/Shader'; export { default as Program } from './shader/Program'; export { default as UniformGroup } from './shader/UniformGroup'; -export { default as SpriteMaskFilter } from './renderers/filters/spriteMask/SpriteMaskFilter'; -export { default as Filter } from './renderers/filters/Filter'; +export { default as SpriteMaskFilter } from './filters/spriteMask/SpriteMaskFilter'; +export { default as Filter } from './filters/Filter'; export { default as Attribute } from './geometry/Attribute'; export { default as Buffer } from './geometry/Buffer'; export { default as Geometry } from './geometry/Geometry'; - diff --git a/packages/core/src/mask/MaskSystem.js b/packages/core/src/mask/MaskSystem.js new file mode 100644 index 0000000..99174c2 --- /dev/null +++ b/packages/core/src/mask/MaskSystem.js @@ -0,0 +1,199 @@ +import System from '../System'; +import AlphaMaskFilter from '../filters/spriteMask/SpriteMaskFilter'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class MaskSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + // TODO - we don't need both! + this.scissor = false; + this.scissorData = null; + this.scissorRenderTarget = null; + + this.enableScissor = false; + + this.alphaMaskPool = []; + this.alphaMaskIndex = 0; + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.DisplayObject} target - Display Object to push the mask to + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + push(target, maskData) + { + // TODO the root check means scissor rect will not + // be used on render textures more info here: + // https://github.com/pixijs/pixi.js/pull/3545 + + if (maskData.texture) + { + this.pushSpriteMask(target, maskData); + } + else if (this.enableScissor + && !this.scissor + && this.renderer._activeRenderTarget.root + && !this.renderer.stencil.stencilMaskStack.length + && maskData.isFastRect()) + { + const matrix = maskData.worldTransform; + + let rot = Math.atan2(matrix.b, matrix.a); + + // use the nearest degree! + rot = Math.round(rot * (180 / Math.PI)); + + if (rot % 90) + { + this.pushStencilMask(maskData); + } + else + { + this.pushScissorMask(target, maskData); + } + } + else + { + this.pushStencilMask(maskData); + } + } + + /** + * Removes the last mask from the mask stack and doesn't return it. + * + * @param {PIXI.DisplayObject} target - Display Object to pop the mask from + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + pop(target, maskData) + { + if (maskData.texture) + { + this.popSpriteMask(target, maskData); + } + else if (this.enableScissor && !this.renderer.stencil.stencilMaskStack.length) + { + this.popScissorMask(target, maskData); + } + else + { + this.popStencilMask(target, maskData); + } + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.RenderTarget} target - Display Object to push the sprite mask to + * @param {PIXI.Sprite} maskData - Sprite to be used as the mask + */ + pushSpriteMask(target, maskData) + { + let alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; + + if (!alphaMaskFilter) + { + alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; + } + + alphaMaskFilter[0].resolution = this.renderer.resolution; + alphaMaskFilter[0].maskSprite = maskData; + + // TODO - may cause issues! + target.filterArea = maskData.getBounds(true); + + this.renderer.filterSystem.pushFilter(target, alphaMaskFilter); + + this.alphaMaskIndex++; + } + + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popSpriteMask() + { + this.renderer.filterSystem.popFilter(); + this.alphaMaskIndex--; + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + pushStencilMask(maskData) + { + this.renderer.batch.flush(); + this.renderer.stencil.pushStencil(maskData); + } + + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popStencilMask() + { + // this.renderer.currentRenderer.stop(); + this.renderer.stencil.popStencil(); + } + + /** + * + * @param {PIXI.DisplayObject} target - Display Object to push the mask to + * @param {PIXI.Graphics} maskData - The masking data. + */ + pushScissorMask(target, maskData) + { + maskData.renderable = true; + + const renderTarget = this.renderer._activeRenderTarget; + + const bounds = maskData.getBounds(); + + bounds.fit(renderTarget.size); + maskData.renderable = false; + + this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); + + const resolution = this.renderer.resolution; + + this.renderer.gl.scissor( + bounds.x * resolution, + (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, + bounds.width * resolution, + bounds.height * resolution + ); + + this.scissorRenderTarget = renderTarget; + this.scissorData = maskData; + this.scissor = true; + } + + /** + * + * + */ + popScissorMask() + { + this.scissorRenderTarget = null; + this.scissorData = null; + this.scissor = false; + + // must be scissor! + const gl = this.renderer.gl; + + gl.disable(gl.SCISSOR_TEST); + } +} diff --git a/packages/core/src/mask/StencilSystem.js b/packages/core/src/mask/StencilSystem.js new file mode 100644 index 0000000..62796a5 --- /dev/null +++ b/packages/core/src/mask/StencilSystem.js @@ -0,0 +1,133 @@ +import System from '../System'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class StencilSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + this.stencilMaskStack = []; + } + + /** + * Changes the mask stack that is used by this System. + * + * @param {PIXI.Graphics[]} stencilMaskStack - The mask stack + */ + setMaskStack(stencilMaskStack) + { + const gl = this.renderer.gl; + + if (stencilMaskStack.length !== this.stencilMaskStack.length) + { + if (stencilMaskStack.length === 0) + { + gl.disable(gl.STENCIL_TEST); + } + else + { + gl.enable(gl.STENCIL_TEST); + } + } + + this.stencilMaskStack = stencilMaskStack; + } + + /** + * Applies the Mask and adds it to the current stencil stack. @alvin + * + * @param {PIXI.Graphics} graphics - The mask + */ + pushStencil(graphics) + { + this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); + + // this.renderer._activeRenderTarget.attachStencilBuffer(); + + const gl = this.renderer.gl; + const prevMaskCount = this.stencilMaskStack.length; + + if (prevMaskCount === 0) + { + gl.enable(gl.STENCIL_TEST); + } + + this.stencilMaskStack.push(graphics); + + // Increment the refference stencil value where the new mask overlaps with the old ones. + gl.colorMask(false, false, false, false); + gl.stencilFunc(gl.EQUAL, prevMaskCount, this._getBitwiseMask()); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.INCR); + this.renderer.plugins.graphics.render(graphics); + + this._useCurrent(); + } + + /** + * Removes the last mask from the stencil stack. @alvin + */ + popStencil() + { + this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); + + const gl = this.renderer.gl; + const graphics = this.stencilMaskStack.pop(); + + if (this.stencilMaskStack.length === 0) + { + // the stack is empty! + gl.disable(gl.STENCIL_TEST); + gl.clear(gl.STENCIL_BUFFER_BIT); + gl.clearStencil(0); + } + else + { + // Decrement the refference stencil value where the popped mask overlaps with the other ones + gl.colorMask(false, false, false, false); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.DECR); + this.renderer.plugins.graphics.render(graphics); + + this._useCurrent(); + } + } + + /** + * Setup renderer to use the current stencil data. + */ + _useCurrent() + { + const gl = this.renderer.gl; + + gl.colorMask(true, true, true, true); + gl.stencilFunc(gl.EQUAL, this.stencilMaskStack.length, this._getBitwiseMask()); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP); + } + + /** + * Fill 1s equal to the number of acitve stencil masks. + * + * @return {number} The bitwise mask. + */ + _getBitwiseMask() + { + return (1 << this.stencilMaskStack.length) - 1; + } + + /** + * Destroys the mask stack. + * + */ + destroy() + { + super.destroy(this); + + this.stencilMaskStack = null; + } +} diff --git a/packages/core/src/projection/ProjectionSystem.js b/packages/core/src/projection/ProjectionSystem.js new file mode 100644 index 0000000..6e63b73 --- /dev/null +++ b/packages/core/src/projection/ProjectionSystem.js @@ -0,0 +1,73 @@ +import System from '../System'; +import { Matrix } from '@pixi/math'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class ProjectionSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.projectionMatrix = new Matrix(); + } + + update(destinationFrame, sourceFrame, resolution, root) + { + this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; + this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; + + this.calculateProjection(this.destinationFrame, this.sourceFrame, resolution, root); + + this.renderer.globalUniforms.uniforms.projectionMatrix = this.projectionMatrix; + this.renderer.globalUniforms.update(); + } + + /** + * Updates the projection matrix based on a projection frame (which is a rectangle) + * + * @param {Rectangle} destinationFrame - The destination frame. + * @param {Rectangle} sourceFrame - The source frame. + */ + calculateProjection(destinationFrame, sourceFrame, resolution, root) + { + const pm = this.projectionMatrix; + + // I don't think we will need this line.. + // pm.identity(); + + if (!root) + { + pm.a = (1 / destinationFrame.width * 2) * resolution; + pm.d = (1 / destinationFrame.height * 2) * resolution; + + pm.tx = -1 - (sourceFrame.x * pm.a); + pm.ty = -1 - (sourceFrame.y * pm.d); + } + else + { + pm.a = (1 / destinationFrame.width * 2) * resolution; + pm.d = (-1 / destinationFrame.height * 2) * resolution; + + pm.tx = -1 - (sourceFrame.x * pm.a); + pm.ty = 1 - (sourceFrame.y * pm.d); + } + } + + /** + * Sets the transform of the active render target to the given matrix + * + * @param {PIXI.Matrix} matrix - The transformation matrix + */ + setTransform()// matrix) + { + // this._activeRenderTarget.transform = matrix; + } +} diff --git a/packages/core/src/renderTexture/BaseRenderTexture.js b/packages/core/src/renderTexture/BaseRenderTexture.js new file mode 100644 index 0000000..88eb83b --- /dev/null +++ b/packages/core/src/renderTexture/BaseRenderTexture.js @@ -0,0 +1,137 @@ +import BaseTexture from '../textures/BaseTexture'; +import FrameBuffer from '../framebuffer/FrameBuffer'; + +/** + * A BaseRenderTexture is a special texture that allows any PixiJS display object to be rendered to it. + * + * __Hint__: All DisplayObjects (i.e. Sprites) that render to a BaseRenderTexture should be preloaded + * otherwise black rectangles will be drawn instead. + * + * A BaseRenderTexture takes a snapshot of any Display Object given to its render method. The position + * and rotation of the given Display Objects is ignored. For example: + * + * ```js + * let renderer = PIXI.autoDetectRenderer(1024, 1024, { view: canvas, ratio: 1 }); + * let baseRenderTexture = new PIXI.BaseRenderTexture(renderer, 800, 600); + * let sprite = PIXI.Sprite.fromImage("spinObj_01.png"); + * + * sprite.position.x = 800/2; + * sprite.position.y = 600/2; + * sprite.anchor.x = 0.5; + * sprite.anchor.y = 0.5; + * + * baseRenderTexture.render(sprite); + * ``` + * + * The Sprite in this case will be rendered using its local transform. To render this sprite at 0,0 + * you can clear the transform + * + * ```js + * + * sprite.setTransform() + * + * let baseRenderTexture = new PIXI.BaseRenderTexture(100, 100); + * let renderTexture = new PIXI.RenderTexture(baseRenderTexture); + * + * renderer.render(sprite, renderTexture); // Renders to center of RenderTexture + * ``` + * + * @class + * @extends PIXI.BaseTexture + * @memberof PIXI + */ +export default class BaseRenderTexture extends BaseTexture +{ + /** + * @param {object} [options] + * @param {number} [options.width=100] - The width of the base render texture + * @param {number} [options.height=100] - The height of the base render texture + * @param {PIXI.SCALE_MODES} [options.scaleMode] - See {@link PIXI.SCALE_MODES} for possible values + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the texture being generated + */ + constructor(options) + { + if (typeof options === 'number') + { + /* eslint-disable prefer-rest-params */ + // Backward compatibility of signature + const width = arguments[0]; + const height = arguments[1]; + const scaleMode = arguments[2]; + const resolution = arguments[3]; + + options = { width, height, scaleMode, resolution }; + /* eslint-enable prefer-rest-params */ + } + + super(null, options); + + const { width, height } = options || {}; + + // Set defaults + this.mipmap = false; + this.width = Math.ceil(width) || 100; + this.height = Math.ceil(height) || 100; + this.valid = true; + + /** + * A map of renderer IDs to webgl renderTargets + * + * @private + * @member {object} + */ + // this._glRenderTargets = {}; + + /** + * A reference to the canvas render target (we only need one as this can be shared across renderers) + * + * @private + * @member {object} + */ + this._canvasRenderTarget = null; + + this.clearColor = [0, 0, 0, 0]; + + this.frameBuffer = new FrameBuffer(width * this.resolution, height * this.resolution) + .addColorTexture(0, this); + + // TODO - could this be added the systems? + + /** + * The data structure for the stencil masks + * + * @member {PIXI.Graphics[]} + */ + this.stencilMaskStack = []; + + /** + * The data structure for the filters + * + * @member {PIXI.Graphics[]} + */ + this.filterStack = [{}]; + } + + /** + * Resizes the BaseRenderTexture. + * + * @param {number} width - The width to resize to. + * @param {number} height - The height to resize to. + */ + resize(width, height) + { + width = Math.ceil(width); + height = Math.ceil(height); + this.frameBuffer.resize(width * this.resolution, height * this.resolution); + } + + /** + * Destroys this texture + * + */ + destroy() + { + super.destroy(true); + this.renderer = null; + } +} diff --git a/packages/core/src/renderTexture/RenderTexture.js b/packages/core/src/renderTexture/RenderTexture.js new file mode 100644 index 0000000..f1ecf1f --- /dev/null +++ b/packages/core/src/renderTexture/RenderTexture.js @@ -0,0 +1,153 @@ +import BaseRenderTexture from './BaseRenderTexture'; +import Texture from '../textures/Texture'; + +/** + * A RenderTexture is a special texture that allows any PixiJS display object to be rendered to it. + * + * __Hint__: All DisplayObjects (i.e. Sprites) that render to a RenderTexture should be preloaded + * otherwise black rectangles will be drawn instead. + * + * A RenderTexture takes a snapshot of any Display Object given to its render method. For example: + * + * ```js + * let renderer = PIXI.autoDetectRenderer(1024, 1024, { view: canvas, ratio: 1 }); + * let renderTexture = PIXI.RenderTexture.create(800, 600); + * let sprite = PIXI.Sprite.fromImage("spinObj_01.png"); + * + * sprite.position.x = 800/2; + * sprite.position.y = 600/2; + * sprite.anchor.x = 0.5; + * sprite.anchor.y = 0.5; + * + * renderer.render(sprite, renderTexture); + * ``` + * + * The Sprite in this case will be rendered using its local transform. To render this sprite at 0,0 + * you can clear the transform + * + * ```js + * + * sprite.setTransform() + * + * let renderTexture = new PIXI.RenderTexture.create(100, 100); + * + * renderer.render(sprite, renderTexture); // Renders to center of RenderTexture + * ``` + * + * @class + * @extends PIXI.Texture + * @memberof PIXI + */ +export default class RenderTexture extends Texture +{ + /** + * @param {PIXI.BaseRenderTexture} baseRenderTexture - The renderer used for this RenderTexture + * @param {PIXI.Rectangle} [frame] - The rectangle frame of the texture to show + */ + constructor(baseRenderTexture, frame) + { + // support for legacy.. + let _legacyRenderer = null; + + if (!(baseRenderTexture instanceof BaseRenderTexture)) + { + /* eslint-disable prefer-rest-params, no-console */ + const width = arguments[1]; + const height = arguments[2]; + const scaleMode = arguments[3]; + const resolution = arguments[4]; + + // we have an old render texture.. + console.warn(`Please use RenderTexture.create(${width}, ${height}) instead of the ctor directly.`); + _legacyRenderer = arguments[0]; + /* eslint-enable prefer-rest-params, no-console */ + + frame = null; + baseRenderTexture = new BaseRenderTexture({ + width, + height, + scaleMode, + resolution, + }); + } + + /** + * The base texture object that this texture uses + * + * @member {BaseTexture} + */ + super(baseRenderTexture, frame); + + this.legacyRenderer = _legacyRenderer; + + /** + * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. + * + * @member {boolean} + */ + this.valid = true; + + /** + * FilterSystem temporary storage + * @private + * @member {PIXI.Rectangle} + */ + this.filterFrame = null; + + this._updateUvs(); + } + + /** + * Resizes the RenderTexture. + * + * @param {number} width - The width to resize to. + * @param {number} height - The height to resize to. + * @param {boolean} [resizeBaseTexture=true] - Should the baseTexture.width and height values be resized as well? + */ + resize(width, height, resizeBaseTexture = true) + { + width = Math.ceil(width); + height = Math.ceil(height); + + // TODO - could be not required.. + this.valid = (width > 0 && height > 0); + + this._frame.width = this.orig.width = width; + this._frame.height = this.orig.height = height; + + if (resizeBaseTexture) + { + this.baseTexture.resize(width, height); + } + + this._updateUvs(); + } + + /** + * A short hand way of creating a render texture. + * + * @param {object} [options] - Options + * @param {number} [options.width=100] - The width of the render texture + * @param {number} [options.height=100] - The height of the render texture + * @param {number} [options.scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the texture being generated + * @return {PIXI.RenderTexture} The new render texture + */ + static create(options) + { + // fallback, old-style: create(width, height, scaleMode, resolution) + if (typeof options === 'number') + { + /* eslint-disable prefer-rest-params */ + options = { + width: options, + height: arguments[1], + scaleMode: arguments[2], + resolution: arguments[3], + }; + /* eslint-enable prefer-rest-params */ + } + + return new RenderTexture(new BaseRenderTexture(options)); + } +} diff --git a/packages/core/src/renderTexture/RenderTextureSystem.js b/packages/core/src/renderTexture/RenderTextureSystem.js new file mode 100644 index 0000000..8d0ca61 --- /dev/null +++ b/packages/core/src/renderTexture/RenderTextureSystem.js @@ -0,0 +1,115 @@ +import System from '../System'; +import { Rectangle } from '@pixi/math'; + +const tempRect = new Rectangle(); + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class RenderTextureSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.clearColor = renderer._backgroundColorRgba; + + // TODO moe this property somewhere else! + this.defaultMaskStack = []; + this.defaultFilterStack = [{}]; + + // empty render texture? + this.renderTexture = null; + + this.destinationFrame = new Rectangle(); + } + + bind(renderTexture, sourceFrame, destinationFrame) + { + // TODO - do we want this?? + if (this.renderTexture === renderTexture) return; + this.renderTexture = renderTexture; + + const renderer = this.renderer; + + if (renderTexture) + { + const baseTexture = renderTexture.baseTexture; + + if (!destinationFrame) + { + tempRect.width = baseTexture.realWidth; + tempRect.height = baseTexture.realHeight; + + destinationFrame = tempRect; + } + + if (!sourceFrame) + { + sourceFrame = destinationFrame; + } + + this.renderer.framebuffer.bind(baseTexture.frameBuffer, destinationFrame); + + this.renderer.projection.update(destinationFrame, sourceFrame, baseTexture.resolution, false); + this.renderer.stencil.setMaskStack(baseTexture.stencilMaskStack); + } + else + { + // TODO these validation checks happen deeper down.. + // thing they can be avoided.. + if (!destinationFrame) + { + tempRect.width = renderer.width; + tempRect.height = renderer.height; + + destinationFrame = tempRect; + } + + if (!sourceFrame) + { + sourceFrame = destinationFrame; + } + + renderer.framebuffer.bind(null, destinationFrame); + + // TODO store this.. + this.renderer.projection.update(destinationFrame, sourceFrame, this.renderer.resolution, true); + this.renderer.stencil.setMaskStack(this.defaultMaskStack); + } + + this.destinationFrame.copyFrom(destinationFrame); + } + + /** + * Erases the render texture and fills the drawing area with a colour + * + * @param {number} [clearColor] - The colour + * @return {PIXI.Renderer} Returns itself. + */ + clear(clearColor) + { + if (this.renderTexture) + { + clearColor = clearColor || this.renderTexture.baseTexture.clearColor; + } + else + { + clearColor = clearColor || this.clearColor; + } + + this.renderer.framebuffer.clear(clearColor[0], clearColor[1], clearColor[2], clearColor[3]); + } + + resize()// screenWidth, screenHeight) + { + // resize the root only! + this.bind(null); + } +} diff --git a/packages/core/src/renderers/AbstractRenderer.js b/packages/core/src/renderers/AbstractRenderer.js deleted file mode 100644 index 928670f..0000000 --- a/packages/core/src/renderers/AbstractRenderer.js +++ /dev/null @@ -1,353 +0,0 @@ -import { hex2string, hex2rgb } from '@pixi/utils'; -import { Matrix, Rectangle } from '@pixi/math'; -import { RENDERER_TYPE } from '@pixi/constants'; -import { settings } from '@pixi/settings'; -import { Container } from '@pixi/display'; -import RenderTexture from '../textures/RenderTexture'; -import EventEmitter from 'eventemitter3'; - -const tempMatrix = new Matrix(); - -/** - * 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 EventEmitter - * @memberof PIXI - */ -export default class AbstractRenderer extends EventEmitter -{ - // eslint-disable-next-line valid-jsdoc - /** - * @param {string} system - The name of the system this renderer is for. - * @param {object} [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.transparent=false] - If the render view is transparent, default false - * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false - * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) - * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. The - * resolution of the renderer retina would be 2. - * @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 {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when rendering, - * stopping pixel interpolation. - */ - constructor(system, options, arg2, arg3) - { - super(); - - // Support for constructor(system, screenWidth, screenHeight, options) - if (typeof options === 'number') - { - options = Object.assign({ - width: options, - height: arg2 || settings.RENDER_OPTIONS.height, - }, arg3); - } - - // 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 = RENDERER_TYPE.UNKNOWN; - - /** - * Measurements of the screen. (0, 0, screenWidth, screenHeight) - * - * Its safe to use as filterArea or hitArea for 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 1 - */ - this.resolution = options.resolution || settings.RESOLUTION; - - /** - * Whether the render view is transparent - * - * @member {boolean} - */ - this.transparent = options.transparent; - - /** - * Whether css dimensions of canvas view should be resized to screen dimensions automatically - * - * @member {boolean} - */ - this.autoResize = options.autoResize || false; - - /** - * Tracks the blend modes useful for this renderer. - * - * @member {object} - */ - this.blendModes = null; - - /** - * 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; - - /** - * If true PixiJS will Math.floor() x/y values when rendering, stopping pixel interpolation. - * Handy for crisp pixel art and speed on legacy devices. - * - * @member {boolean} - */ - this.roundPixels = options.roundPixels; - - /** - * The background color as a number. - * - * @member {number} - * @private - */ - this._backgroundColor = 0x000000; - - /** - * The background color as an [R, G, B] array. - * - * @member {number[]} - * @private - */ - this._backgroundColorRgba = [0, 0, 0, 0]; - - /** - * The background color as a string. - * - * @member {string} - * @private - */ - this._backgroundColorString = '#000000'; - - this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter - - /** - * This temporary display object used as the parent of the currently being rendered item - * - * @member {PIXI.DisplayObject} - * @private - */ - this._tempDisplayObjectParent = new Container(); - - /** - * The last root object that the renderer tried to render. - * - * @member {PIXI.DisplayObject} - * @private - */ - this._lastObjectRendered = this._tempDisplayObjectParent; - - /** - * Collection of plugins. - * @readonly - * @member {object} - */ - this.plugins = {}; - } - - /** - * Initialize the plugins. - * - * @protected - * @param {object} staticMap - The dictionary of staticly saved plugins. - */ - initPlugins(staticMap) - { - 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() - { - return this.view.width; - } - - /** - * Same as view.height, actual number of pixels in the canvas by vertical - * - * @member {number} - * @readonly - * @default 600 - */ - get height() - { - return this.view.height; - } - - /** - * Resizes the screen and canvas to the specified width and height - * Canvas dimensions are multiplied by resolution - * - * @param {number} screenWidth - the new width of the screen - * @param {number} screenHeight - the new height of the screen - */ - resize(screenWidth, screenHeight) - { - this.screen.width = screenWidth; - this.screen.height = screenHeight; - - this.view.width = screenWidth * this.resolution; - this.view.height = screenHeight * this.resolution; - - if (this.autoResize) - { - this.view.style.width = `${screenWidth}px`; - this.view.style.height = `${screenHeight}px`; - } - } - - /** - * 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 {PIXI.DisplayObject} displayObject - The displayObject the object will be generated from - * @param {number} scaleMode - Should be one of the scaleMode consts - * @param {number} resolution - The resolution / device pixel ratio of the texture being generated - * @param {PIXI.Rectangle} [region] - The region of the displayObject, that shall be rendered, - * if no region is specified, defaults to the local bounds of the displayObject. - * @return {PIXI.Texture} a texture of the graphics object - */ - generateTexture(displayObject, scaleMode, resolution, region) - { - region = region || displayObject.getLocalBounds(); - - // 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(region.width | 0, region.height | 0, scaleMode, resolution); - - tempMatrix.tx = -region.x; - tempMatrix.ty = -region.y; - - this.render(displayObject, renderTexture, false, tempMatrix, true); - - return renderTexture; - } - - /** - * Removes everything from the renderer and optionally removes the Canvas DOM element. - * - * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. - */ - destroy(removeView) - { - 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); - } - - this.plugins = null; - - this.type = RENDERER_TYPE.UNKNOWN; - - this.view = null; - - this.screen = null; - - this.resolution = 0; - - this.transparent = false; - - this.autoResize = false; - - this.blendModes = null; - - this.options = null; - - this.preserveDrawingBuffer = false; - this.clearBeforeRender = false; - - this.roundPixels = false; - - this._backgroundColor = 0; - this._backgroundColorRgba = null; - this._backgroundColorString = null; - - this._tempDisplayObjectParent = null; - this._lastObjectRendered = null; - } - - /** - * The background color to fill if not transparent - * - * @member {number} - */ - get backgroundColor() - { - return this._backgroundColor; - } - - set backgroundColor(value) // eslint-disable-line require-jsdoc - { - this._backgroundColor = value; - this._backgroundColorString = hex2string(value); - hex2rgb(value, this._backgroundColorRgba); - } -} diff --git a/packages/core/src/renderers/Renderer.js b/packages/core/src/renderers/Renderer.js deleted file mode 100644 index a2020b1..0000000 --- a/packages/core/src/renderers/Renderer.js +++ /dev/null @@ -1,329 +0,0 @@ -import AbstractRenderer from './AbstractRenderer'; -import { sayHello } from '@pixi/utils'; -import MaskSystem from './systems/MaskSystem'; -import StencilSystem from './systems/StencilSystem'; -import FilterSystem from './systems/filter/FilterSystem'; -import FramebufferSystem from './systems/FramebufferSystem'; -import RenderTextureSystem from './systems/RenderTextureSystem'; -import TextureSystem from './systems/textures/TextureSystem'; -import ProjectionSystem from './systems/ProjectionSystem'; -import StateSystem from './systems/StateSystem'; -import GeometrySystem from './systems/geometry/GeometrySystem'; -import ShaderSystem from './systems/shader/ShaderSystem'; -import ContextSystem from './systems/ContextSystem'; -import BatchSystem from './systems/BatchSystem'; -import TextureGCSystem from './systems/textures/TextureGCSystem'; -import { RENDERER_TYPE } from '@pixi/constants'; -import UniformGroup from '../shader/UniformGroup'; -import { Matrix } from '@pixi/math'; -import Runner from 'mini-runner'; - -/** - * 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 Render works by automatically managing webGLBatchs. - * 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 :) - * - * @class - * @memberof PIXI - * @extends PIXI.AbstractRenderer - */ -export default class Renderer extends AbstractRenderer -{ - // eslint-disable-next-line valid-jsdoc - /** - * - * @param {object} [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.transparent=false] - If the render view is transparent, default false - * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false - * @param {boolean} [options.antialias=false] - sets antialias. If not available natively then FXAA - * antialiasing is used - * @param {boolean} [options.forceFXAA=false] - forces FXAA antialiasing to be used over native. - * FXAA is faster, but may not always look as great - * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. - * The resolution of the renderer retina would be 2. - * @param {boolean} [options.clearBeforeRender=true] - This sets if the renderer will clear - * the canvas or not before the new render pass. If you wish to set this to false, you *must* set - * preserveDrawingBuffer to `true`. - * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, - * enable this if you need to call toDataUrl on the webgl context. - * @param {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when - * rendering, stopping pixel interpolation. - * @param {number} [options.backgroundColor=0x000000] - The background color of the rendered area - * (shown if not transparent). - * @param {boolean} [options.legacy=false] - If true PixiJS will aim to ensure compatibility - * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. - * @param {string} [options.powerPreference] - Parameter passed to webgl context, set to "high-performance" - * for devices with dual graphics card - */ - constructor(options, arg2, arg3) - { - super('WebGL', options, arg2, arg3); - - /** - * The type of this renderer as a standardised const - * - * @member {number} - * @see PIXI.RENDERER_TYPE - */ - this.type = RENDERER_TYPE.WEBGL; - - // this will be set by the contextSystem (this.context) - this.gl = null; - this.CONTEXT_UID = 0; - this.legacy = !!options.legacy; - - // TODO legacy! - - // runners! - this.runners = { - destroy: new Runner('destroy'), - contextChange: new Runner('contextChange', 1), - reset: new Runner('reset'), - update: new Runner('update'), - postrender: new Runner('postrender'), - prerender: new Runner('prerender'), - resize: new Runner('resize', 2), - }; - - 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(GeometrySystem, 'geometry') - .addSystem(FramebufferSystem, 'framebuffer') - .addSystem(StencilSystem, 'stencil') - .addSystem(ProjectionSystem, 'projection') - .addSystem(TextureGCSystem, 'textureGC') - .addSystem(FilterSystem, 'filter') - .addSystem(RenderTextureSystem, 'renderTexture') - .addSystem(BatchSystem, 'batch'); - - this.initPlugins(Renderer.__plugins); - - /** - * The options passed in to create a new webgl context. - * - * @member {object} - * @private - */ - if (options.context) - { - this.context.initFromContext(options.context); - } - else - { - this.context.initFromOptions({ - alpha: this.transparent, - antialias: options.antialias, - premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', - stencil: true, - 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); - } - - addSystem(_class, name) - { - if (!name) - { - name = _class.name; - } - - // TODO - read name from class.name.. - - /* - if(name.includes('System')) - { - name = name.replace('System', ''); - name = name.charAt(0).toLowerCase() + name.slice(1); - } - */ - - const system = new _class(this); - - if (this[name]) - { - throw new Error(`Whoops! ${name} is already a manger`); - } - - this[name] = system; - - for (const i in this.runners) - { - this.runners[i].add(system); - } - - return this; - - /** - * 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. - */ - } - - /** - * Renders the object to its webGL view - * - * @param {PIXI.DisplayObject} displayObject - the object to be rendered - * @param {PIXI.RenderTexture} renderTexture - The render texture to render to. - * @param {boolean} [clear] - Should the canvas be cleared before the new render - * @param {PIXI.Transform} [transform] - A transform to apply to the render texture before rendering. - * @param {boolean} [skipUpdateTransform] - Should we skip the update transform pass? - */ - render(displayObject, renderTexture, clear, transform, skipUpdateTransform) - { - // can be handy to know! - this.renderingToScreen = !renderTexture; - - this.runners.prerender.run(); - this.emit('prerender'); - - // 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.parent; - - displayObject.parent = this._tempDisplayObjectParent; - displayObject.updateTransform(); - displayObject.parent = 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.run(); - - this.emit('postrender'); - } - - /** - * Resizes the webGL view to the specified width and height. - * - * @param {number} screenWidth - the new width of the screen - * @param {number} screenHeight - the new height of the screen - */ - resize(screenWidth, screenHeight) - { - AbstractRenderer.prototype.resize.call(this, screenWidth, screenHeight); - - this.runners.resize.run(screenWidth, screenHeight); - } - - /** - * Resets the WebGL state so you can render things however you fancy! - * - * @return {PIXI.Renderer} Returns itself. - */ - reset() - { - this.runners.reset.run(); - - return this; - } - - clear() - { - this.framebuffer.bind(); - this.framebuffer.clear(); - } - - /** - * Removes everything from the renderer (event listeners, spritebatch, etc...) - * - * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. - * See: https://github.com/pixijs/pixi.js/issues/2233 - */ - destroy(removeView) - { - this.runners.destroy.run(); - - // call base destroy - super.destroy(removeView); - - // TODO nullify all the managers.. - this.gl = null; - } - - /** - * 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 - * builds and excluding plugins. - * @name PIXI.Renderer#plugins - * @type {object} - * @readonly - * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. - * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. - * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. - * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. - */ - - /** - * Adds a plugin to the renderer. - * - * @method - * @param {string} pluginName - The name of the plugin. - * @param {Function} ctor - The constructor function or class for the plugin. - */ - static registerPlugin(pluginName, ctor) - { - Renderer.__plugins = Renderer.__plugins || {}; - Renderer.__plugins[pluginName] = ctor; - } -} diff --git a/packages/core/src/renderers/State.js b/packages/core/src/renderers/State.js deleted file mode 100644 index fa3f221..0000000 --- a/packages/core/src/renderers/State.js +++ /dev/null @@ -1,163 +0,0 @@ -/* eslint-disable max-len */ - -const BLEND = 0; -const OFFSET = 1; -const CULLING = 2; -const DEPTH_TEST = 3; -const WINDING = 4; - -/** - * This is a webGL state. It is passed The WebGL StateManager. - * Each mesh renderered may require webGL to be in a different state. - * For example you may want different blend mode or to enable polygon offsets - * - * @class - * @memberof PIXI - */ -export default class State -{ - /** - * - */ - constructor() - { - this.data = 0; - - this.blendMode = 0; - this.polygonOffset = 0; - - this.blend = true; - // this.depthTest = true; - } - - /** - * Activates blending of the computed fragment color values - * - * @member {boolean} - */ - get blend() - { - return !!(this.data & (1 << BLEND)); - } - - set blend(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << BLEND)) !== value) - { - this.data ^= (1 << BLEND); - } - } - - /** - * Activates adding an offset to depth values of polygon's fragments - * - * @member {boolean} - * @default false - */ - get offsets() - { - return !!(this.data & (1 << OFFSET)); - } - - set offsets(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << OFFSET)) !== value) - { - this.data ^= (1 << OFFSET); - } - } - - /** - * Activates culling of polygons. - * - * @member {boolean} - * @default false - */ - get culling() - { - return !!(this.data & (1 << CULLING)); - } - - set culling(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << CULLING)) !== value) - { - this.data ^= (1 << CULLING); - } - } - - /** - * Activates depth comparisons and updates to the depth buffer. - * - * @member {boolean} - * @default false - */ - get depthTest() - { - return !!(this.data & (1 << DEPTH_TEST)); - } - - set depthTest(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << DEPTH_TEST)) !== value) - { - this.data ^= (1 << DEPTH_TEST); - } - } - - /** - * Specifies whether or not front or back-facing polygons can be culled. - * @member {boolean} - * @default false - */ - get clockwiseFrontFace() - { - return !!(this.data & (1 << WINDING)); - } - - set clockwiseFrontFace(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << WINDING)) !== value) - { - this.data ^= (1 << WINDING); - } - } - - /** - * The blend mode to be applied when this state is set. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. - * Setting this mode to anything other than NO_BLEND will automatically switch blending on. - * - * @member {boolean} - * @default PIXI.BLEND_MODES.NORMAL - * @see PIXI.BLEND_MODES - */ - get blendMode() - { - return this._blendMode; - } - - set blendMode(value) // eslint-disable-line require-jsdoc - { - // 17 is NO BLEND - this.blend = (value !== 17); - this._blendMode = value; - } - - /** - * The polygon offset. Setting this property to anything other than 0 will automatically enable poygon offset fill. - * - * @member {number} - * @default 0 - */ - get polygonOffset() - { - return this._polygonOffset; - } - - set polygonOffset(value) // eslint-disable-line require-jsdoc - { - this.offsets = !!value; - this._polygonOffset = value; - } -} - diff --git a/packages/core/src/renderers/filters/Filter.js b/packages/core/src/renderers/filters/Filter.js deleted file mode 100644 index 2073b8d..0000000 --- a/packages/core/src/renderers/filters/Filter.js +++ /dev/null @@ -1,140 +0,0 @@ -import Shader from '../../shader/Shader'; -import Program from '../../shader/Program'; -import State from '../State'; -import { settings } from '@pixi/settings'; -// import extractUniformsFromSrc from './extractUniformsFromSrc'; -import defaultVertex from './defaultFilter.vert'; -import defaultFragment from './defaultFilter.frag'; - -// let math = require('../../math'); -/** - * @class - * @memberof PIXI - * @extends PIXI.Shader - */ -export default class Filter extends Shader -{ - /** - * @param {string} [vertexSrc] - The source of the vertex shader. - * @param {string} [fragmentSrc] - The source of the fragment shader. - * @param {object} [uniforms] - Custom uniforms to use to augment the built-in ones. - */ - constructor(vertexSrc, fragmentSrc, uniforms) - { - const program = Program.from(vertexSrc, fragmentSrc); - - super(program, uniforms); - - /** - * The padding of the filter. Some filters require extra space to breath such as a blur. - * Increasing this will add extra width and height to the bounds of the object that the - * filter is applied to. - * - * @member {number} - */ - this.padding = 0; - - /** - * The resolution of the filter. Setting this to be lower will lower the quality but - * increase the performance of the filter. - * - * @member {number} - */ - this.resolution = settings.RESOLUTION; - - /** - * If enabled is true the filter is applied, if false it will not. - * - * @member {boolean} - */ - this.enabled = true; - - /** - * If enabled, PixiJS will fit the filter area into boundaries for better performance. - * Switch it off if it does not work for specific shader. - * - * @member {boolean} - */ - this.autoFit = true; - - /** - * Legacy filters use position and uvs from attributes - * @member {boolean} - * @readonly - */ - this.legacy = !!this.program.attributeData.aTextureCoord; - - /** - * the webGL state the filter requires to render - * @member {PIXI.State} - */ - this.state = new State(); - } - - /** - * Applies the filter - * - * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from - * @param {PIXI.RenderTarget} input - The input render target. - * @param {PIXI.RenderTarget} output - The target to output to. - * @param {boolean} clear - Should the output be cleared before rendering to it - * @param {object} [currentState] - It's current state of filter. - * There are some useful properties in the currentState : - * target, filters, sourceFrame, destinationFrame, renderTarget, resolution - */ - apply(filterManager, input, output, clear, currentState, derp) // eslint-disable-line no-unused-vars - { - // do as you please! - - filterManager.applyFilter(this, input, output, clear, currentState, derp); - - // or just do a regular render.. - } - - /** - * Sets the blendmode of the filter - * - * @member {number} - * @default PIXI.BLEND_MODES.NORMAL - */ - get blendMode() - { - return this.state.blendMode; - } - - set blendMode(value) // eslint-disable-line require-jsdoc - { - this.state.blendMode = value; - } - - /** - * The default vertex shader source - * - * @static - * @constant - */ - static get defaultVertexSrc() - { - return defaultVertex; - } - - /** - * The default fragment shader source - * - * @static - * @constant - */ - static get defaultFragmentSrc() - { - return defaultFragment; - } -} - -/** - * Used for caching shader IDs - * - * @static - * @private - */ -Filter.SOURCE_KEY_MAP = {}; - diff --git a/packages/core/src/renderers/filters/defaultFilter.frag b/packages/core/src/renderers/filters/defaultFilter.frag deleted file mode 100644 index a14c1be..0000000 --- a/packages/core/src/renderers/filters/defaultFilter.frag +++ /dev/null @@ -1,22 +0,0 @@ -varying vec2 vTextureCoord; -varying vec2 vFilterCoord; - -uniform sampler2D uSampler; -uniform sampler2D filterSampler; - -void main(void){ - vec4 masky = texture2D(filterSampler, vFilterCoord); - vec4 sample = texture2D(uSampler, vTextureCoord); - vec4 color; - if(mod(vFilterCoord.x, 1.0) > 0.5) - { - color = vec4(1.0, 0.0, 0.0, 1.0); - } - else - { - color = vec4(0.0, 1.0, 0.0, 1.0); - } - // gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0); - gl_FragColor = mix(sample, masky, 0.5); - gl_FragColor *= sample.a; -} diff --git a/packages/core/src/renderers/filters/defaultFilter.vert b/packages/core/src/renderers/filters/defaultFilter.vert deleted file mode 100644 index e1febca..0000000 --- a/packages/core/src/renderers/filters/defaultFilter.vert +++ /dev/null @@ -1,14 +0,0 @@ -attribute vec2 aVertexPosition; -attribute vec2 aTextureCoord; - -uniform mat3 projectionMatrix; -uniform mat3 filterMatrix; - -varying vec2 vTextureCoord; -varying vec2 vFilterCoord; - -void main(void){ - gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); - vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy; - vTextureCoord = aTextureCoord ; -} diff --git a/packages/core/src/renderers/filters/filterTransforms.js b/packages/core/src/renderers/filters/filterTransforms.js deleted file mode 100644 index 87a3cd6..0000000 --- a/packages/core/src/renderers/filters/filterTransforms.js +++ /dev/null @@ -1,50 +0,0 @@ -import { Matrix } from '@pixi/math'; - -/** - * Calculates the mapped matrix - * @param filterArea {Rectangle} The filter area - * @param sprite {Sprite} the target sprite - * @param outputMatrix {Matrix} @alvin - * @private - */ -// this returns a matrix that will normalise map filter cords in the filter to screen space -export function calculateScreenSpaceMatrix(outputMatrix, filterArea, textureSize) -{ - // TODO unwrap? - const mappedMatrix = outputMatrix.identity(); - - mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); - - mappedMatrix.scale(textureSize.width, textureSize.height); - - return mappedMatrix; -} - -export function calculateNormalizedScreenSpaceMatrix(outputMatrix, filterArea, textureSize) -{ - const mappedMatrix = outputMatrix.identity(); - - mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); - - const translateScaleX = (textureSize.width / filterArea.width); - const translateScaleY = (textureSize.height / filterArea.height); - - mappedMatrix.scale(translateScaleX, translateScaleY); - - return mappedMatrix; -} - -// this will map the filter coord so that a texture can be used based on the transform of a sprite -export function calculateSpriteMatrix(outputMatrix, filterArea, textureSize, sprite) -{ - const orig = sprite._texture.orig; - const mappedMatrix = outputMatrix.set(textureSize.width, 0, 0, textureSize.height, filterArea.x, filterArea.y); - const worldTransform = sprite.worldTransform.copyTo(Matrix.TEMP_MATRIX); - - worldTransform.invert(); - mappedMatrix.prepend(worldTransform); - mappedMatrix.scale(1.0 / orig.width, 1.0 / orig.height); - mappedMatrix.translate(sprite.anchor.x, sprite.anchor.y); - - return mappedMatrix; -} diff --git a/packages/core/src/renderers/filters/spriteMask/SpriteMaskFilter.js b/packages/core/src/renderers/filters/spriteMask/SpriteMaskFilter.js deleted file mode 100644 index d5a91a4..0000000 --- a/packages/core/src/renderers/filters/spriteMask/SpriteMaskFilter.js +++ /dev/null @@ -1,63 +0,0 @@ -import Filter from '../Filter'; -import { Matrix } from '@pixi/math'; -import vertex from './spriteMaskFilter.vert'; -import fragment from './spriteMaskFilter.frag'; -import { default as TextureMatrix } from '../../../textures/TextureMatrix'; - -/** - * The SpriteMaskFilter class - * - * @class - * @extends PIXI.Filter - * @memberof PIXI - */ -export default class SpriteMaskFilter extends Filter -{ - /** - * @param {PIXI.Sprite} sprite - the target sprite - */ - constructor(sprite) - { - const maskMatrix = new Matrix(); - - super(vertex, fragment); - - sprite.renderable = false; - - this.maskSprite = sprite; - this.maskMatrix = maskMatrix; - } - - /** - * Applies the filter - * - * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from - * @param {PIXI.RenderTarget} input - The input render target. - * @param {PIXI.RenderTarget} output - The target to output to. - */ - apply(filterManager, input, output) - { - const maskSprite = this.maskSprite; - const tex = this.maskSprite.texture; - - if (!tex.valid) - { - return; - } - if (!tex.transform) - { - // margin = 0.0, let it bleed a bit, shader code becomes easier - // assuming that atlas textures were made with 1-pixel padding - tex.transform = new TextureMatrix(tex, 0.0); - } - tex.transform.update(); - - this.uniforms.mask = tex; - this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite) - .prepend(tex.transform.mapCoord); - this.uniforms.alpha = maskSprite.worldAlpha; - this.uniforms.maskClamp = tex.transform.uClampFrame; - - filterManager.applyFilter(this, input, output); - } -} diff --git a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.frag b/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.frag deleted file mode 100644 index 0e4aef8..0000000 --- a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.frag +++ /dev/null @@ -1,23 +0,0 @@ -varying vec2 vMaskCoord; -varying vec2 vTextureCoord; - -uniform sampler2D uSampler; -uniform sampler2D mask; -uniform float alpha; -uniform vec4 maskClamp; - -void main(void) -{ - float clip = step(3.5, - step(maskClamp.x, vMaskCoord.x) + - step(maskClamp.y, vMaskCoord.y) + - step(vMaskCoord.x, maskClamp.z) + - step(vMaskCoord.y, maskClamp.w)); - - vec4 original = texture2D(uSampler, vTextureCoord); - vec4 masky = texture2D(mask, vMaskCoord); - - original *= (masky.r * masky.a * alpha * clip); - - gl_FragColor = original; -} diff --git a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.vert b/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.vert deleted file mode 100644 index d23a30c..0000000 --- a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.vert +++ /dev/null @@ -1,16 +0,0 @@ -attribute vec2 aVertexPosition; -attribute vec2 aTextureCoord; - -uniform mat3 projectionMatrix; -uniform mat3 otherMatrix; - -varying vec2 vMaskCoord; -varying vec2 vTextureCoord; - -void main(void) -{ - gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); - - vTextureCoord = aTextureCoord; - vMaskCoord = ( otherMatrix * vec3( aTextureCoord, 1.0) ).xy; -} diff --git a/packages/core/src/AbstractRenderer.js b/packages/core/src/AbstractRenderer.js new file mode 100644 index 0000000..64b2eb1 --- /dev/null +++ b/packages/core/src/AbstractRenderer.js @@ -0,0 +1,353 @@ +import { hex2string, hex2rgb } from '@pixi/utils'; +import { Matrix, Rectangle } from '@pixi/math'; +import { RENDERER_TYPE } from '@pixi/constants'; +import { settings } from '@pixi/settings'; +import { Container } from '@pixi/display'; +import RenderTexture from './renderTexture/RenderTexture'; +import EventEmitter from 'eventemitter3'; + +const tempMatrix = new Matrix(); + +/** + * 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 EventEmitter + * @memberof PIXI + */ +export default class AbstractRenderer extends EventEmitter +{ + // eslint-disable-next-line valid-jsdoc + /** + * @param {string} system - The name of the system this renderer is for. + * @param {object} [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.transparent=false] - If the render view is transparent, default false + * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false + * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. The + * resolution of the renderer retina would be 2. + * @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 {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when rendering, + * stopping pixel interpolation. + */ + constructor(system, options, arg2, arg3) + { + super(); + + // Support for constructor(system, screenWidth, screenHeight, options) + if (typeof options === 'number') + { + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + }, arg3); + } + + // 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 = RENDERER_TYPE.UNKNOWN; + + /** + * Measurements of the screen. (0, 0, screenWidth, screenHeight) + * + * Its safe to use as filterArea or hitArea for 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 1 + */ + this.resolution = options.resolution || settings.RESOLUTION; + + /** + * Whether the render view is transparent + * + * @member {boolean} + */ + this.transparent = options.transparent; + + /** + * Whether css dimensions of canvas view should be resized to screen dimensions automatically + * + * @member {boolean} + */ + this.autoResize = options.autoResize || false; + + /** + * Tracks the blend modes useful for this renderer. + * + * @member {object} + */ + this.blendModes = null; + + /** + * 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; + + /** + * If true PixiJS will Math.floor() x/y values when rendering, stopping pixel interpolation. + * Handy for crisp pixel art and speed on legacy devices. + * + * @member {boolean} + */ + this.roundPixels = options.roundPixels; + + /** + * The background color as a number. + * + * @member {number} + * @private + */ + this._backgroundColor = 0x000000; + + /** + * The background color as an [R, G, B] array. + * + * @member {number[]} + * @private + */ + this._backgroundColorRgba = [0, 0, 0, 0]; + + /** + * The background color as a string. + * + * @member {string} + * @private + */ + this._backgroundColorString = '#000000'; + + this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter + + /** + * This temporary display object used as the parent of the currently being rendered item + * + * @member {PIXI.DisplayObject} + * @private + */ + this._tempDisplayObjectParent = new Container(); + + /** + * The last root object that the renderer tried to render. + * + * @member {PIXI.DisplayObject} + * @private + */ + this._lastObjectRendered = this._tempDisplayObjectParent; + + /** + * Collection of plugins. + * @readonly + * @member {object} + */ + this.plugins = {}; + } + + /** + * Initialize the plugins. + * + * @protected + * @param {object} staticMap - The dictionary of staticly saved plugins. + */ + initPlugins(staticMap) + { + 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() + { + return this.view.width; + } + + /** + * Same as view.height, actual number of pixels in the canvas by vertical + * + * @member {number} + * @readonly + * @default 600 + */ + get height() + { + return this.view.height; + } + + /** + * Resizes the screen and canvas to the specified width and height + * Canvas dimensions are multiplied by resolution + * + * @param {number} screenWidth - the new width of the screen + * @param {number} screenHeight - the new height of the screen + */ + resize(screenWidth, screenHeight) + { + this.screen.width = screenWidth; + this.screen.height = screenHeight; + + this.view.width = screenWidth * this.resolution; + this.view.height = screenHeight * this.resolution; + + if (this.autoResize) + { + this.view.style.width = `${screenWidth}px`; + this.view.style.height = `${screenHeight}px`; + } + } + + /** + * 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 {PIXI.DisplayObject} displayObject - The displayObject the object will be generated from + * @param {number} scaleMode - Should be one of the scaleMode consts + * @param {number} resolution - The resolution / device pixel ratio of the texture being generated + * @param {PIXI.Rectangle} [region] - The region of the displayObject, that shall be rendered, + * if no region is specified, defaults to the local bounds of the displayObject. + * @return {PIXI.Texture} a texture of the graphics object + */ + generateTexture(displayObject, scaleMode, resolution, region) + { + region = region || displayObject.getLocalBounds(); + + // 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(region.width | 0, region.height | 0, scaleMode, resolution); + + tempMatrix.tx = -region.x; + tempMatrix.ty = -region.y; + + this.render(displayObject, renderTexture, false, tempMatrix, true); + + return renderTexture; + } + + /** + * Removes everything from the renderer and optionally removes the Canvas DOM element. + * + * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. + */ + destroy(removeView) + { + 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); + } + + this.plugins = null; + + this.type = RENDERER_TYPE.UNKNOWN; + + this.view = null; + + this.screen = null; + + this.resolution = 0; + + this.transparent = false; + + this.autoResize = false; + + this.blendModes = null; + + this.options = null; + + this.preserveDrawingBuffer = false; + this.clearBeforeRender = false; + + this.roundPixels = false; + + this._backgroundColor = 0; + this._backgroundColorRgba = null; + this._backgroundColorString = null; + + this._tempDisplayObjectParent = null; + this._lastObjectRendered = null; + } + + /** + * The background color to fill if not transparent + * + * @member {number} + */ + get backgroundColor() + { + return this._backgroundColor; + } + + set backgroundColor(value) // eslint-disable-line require-jsdoc + { + this._backgroundColor = value; + this._backgroundColorString = hex2string(value); + hex2rgb(value, this._backgroundColorRgba); + } +} diff --git a/packages/core/src/Renderer.js b/packages/core/src/Renderer.js new file mode 100644 index 0000000..2227e78 --- /dev/null +++ b/packages/core/src/Renderer.js @@ -0,0 +1,338 @@ +import AbstractRenderer from './AbstractRenderer'; +import { sayHello } from '@pixi/utils'; +import MaskSystem from './mask/MaskSystem'; +import StencilSystem from './mask/StencilSystem'; +import FilterSystem from './filters/FilterSystem'; +import FramebufferSystem from './framebuffer/FramebufferSystem'; +import RenderTextureSystem from './renderTexture/RenderTextureSystem'; +import TextureSystem from './textures/TextureSystem'; +import ProjectionSystem from './projection/ProjectionSystem'; +import StateSystem from './state/StateSystem'; +import GeometrySystem from './geometry/GeometrySystem'; +import ShaderSystem from './shader/ShaderSystem'; +import ContextSystem from './context/ContextSystem'; +import BatchSystem from './batch/BatchSystem'; +import TextureGCSystem from './textures/TextureGCSystem'; +import { RENDERER_TYPE } from '@pixi/constants'; +import UniformGroup from './shader/UniformGroup'; +import { Matrix } from '@pixi/math'; +import Runner from 'mini-runner'; + +/** + * 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 Render works by automatically managing webGLBatchs. + * 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 :) + * + * @class + * @memberof PIXI + * @extends PIXI.AbstractRenderer + */ +export default class Renderer extends AbstractRenderer +{ + // eslint-disable-next-line valid-jsdoc + /** + * + * @param {object} [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.transparent=false] - If the render view is transparent, default false + * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false + * @param {boolean} [options.antialias=false] - sets antialias. If not available natively then FXAA + * antialiasing is used + * @param {boolean} [options.forceFXAA=false] - forces FXAA antialiasing to be used over native. + * FXAA is faster, but may not always look as great + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. + * The resolution of the renderer retina would be 2. + * @param {boolean} [options.clearBeforeRender=true] - This sets if the renderer will clear + * the canvas or not before the new render pass. If you wish to set this to false, you *must* set + * preserveDrawingBuffer to `true`. + * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, + * enable this if you need to call toDataUrl on the webgl context. + * @param {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when + * rendering, stopping pixel interpolation. + * @param {number} [options.backgroundColor=0x000000] - The background color of the rendered area + * (shown if not transparent). + * @param {boolean} [options.legacy=false] - If true PixiJS will aim to ensure compatibility + * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. + * @param {string} [options.powerPreference] - Parameter passed to webgl context, set to "high-performance" + * for devices with dual graphics card + */ + constructor(options, arg2, arg3) + { + super('WebGL', options, arg2, arg3); + + /** + * The type of this renderer as a standardised const + * + * @member {number} + * @see PIXI.RENDERER_TYPE + */ + this.type = RENDERER_TYPE.WEBGL; + + // this will be set by the contextSystem (this.context) + this.gl = null; + this.CONTEXT_UID = 0; + this.legacy = !!options.legacy; + + // TODO legacy! + + // runners! + this.runners = { + destroy: new Runner('destroy'), + contextChange: new Runner('contextChange', 1), + reset: new Runner('reset'), + update: new Runner('update'), + postrender: new Runner('postrender'), + prerender: new Runner('prerender'), + resize: new Runner('resize', 2), + }; + + 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(GeometrySystem, 'geometry') + .addSystem(FramebufferSystem, 'framebuffer') + .addSystem(StencilSystem, 'stencil') + .addSystem(ProjectionSystem, 'projection') + .addSystem(TextureGCSystem, 'textureGC') + .addSystem(FilterSystem, 'filter') + .addSystem(RenderTextureSystem, 'renderTexture') + .addSystem(BatchSystem, 'batch'); + + this.initPlugins(Renderer.__plugins); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ + if (options.context) + { + this.context.initFromContext(options.context); + } + else + { + this.context.initFromOptions({ + alpha: this.transparent, + antialias: options.antialias, + premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', + stencil: true, + 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); + } + + /** + * Add a new system to the renderer. + * @param {class} ClassRef - Class reference + * @param {string} [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. + * @return {PIXI.Renderer} Return instance of renderer + */ + addSystem(ClassRef, name) + { + if (!name) + { + name = ClassRef.name; + } + + // TODO - read name from class.name.. + + /* + if(name.includes('System')) + { + name = name.replace('System', ''); + name = name.charAt(0).toLowerCase() + name.slice(1); + } + */ + + const system = new ClassRef(this); + + if (this[name]) + { + throw new Error(`Whoops! ${name} is already a manger`); + } + + this[name] = system; + + for (const i in this.runners) + { + this.runners[i].add(system); + } + + return this; + + /** + * 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. + */ + } + + /** + * Renders the object to its webGL view + * + * @param {PIXI.DisplayObject} displayObject - the object to be rendered + * @param {PIXI.RenderTexture} renderTexture - The render texture to render to. + * @param {boolean} [clear] - Should the canvas be cleared before the new render + * @param {PIXI.Transform} [transform] - A transform to apply to the render texture before rendering. + * @param {boolean} [skipUpdateTransform] - Should we skip the update transform pass? + */ + render(displayObject, renderTexture, clear, transform, skipUpdateTransform) + { + // can be handy to know! + this.renderingToScreen = !renderTexture; + + this.runners.prerender.run(); + this.emit('prerender'); + + // 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.parent; + + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = 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.run(); + + this.emit('postrender'); + } + + /** + * Resizes the webGL view to the specified width and height. + * + * @param {number} screenWidth - the new width of the screen + * @param {number} screenHeight - the new height of the screen + */ + resize(screenWidth, screenHeight) + { + AbstractRenderer.prototype.resize.call(this, screenWidth, screenHeight); + + this.runners.resize.run(screenWidth, screenHeight); + } + + /** + * Resets the WebGL state so you can render things however you fancy! + * + * @return {PIXI.Renderer} Returns itself. + */ + reset() + { + this.runners.reset.run(); + + return this; + } + + clear() + { + this.framebuffer.bind(); + this.framebuffer.clear(); + } + + /** + * Removes everything from the renderer (event listeners, spritebatch, etc...) + * + * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. + * See: https://github.com/pixijs/pixi.js/issues/2233 + */ + destroy(removeView) + { + this.runners.destroy.run(); + + // call base destroy + super.destroy(removeView); + + // TODO nullify all the managers.. + this.gl = null; + } + + /** + * 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 + * builds and excluding plugins. + * @name PIXI.Renderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. + */ + + /** + * Adds a plugin to the renderer. + * + * @method + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + static registerPlugin(pluginName, ctor) + { + Renderer.__plugins = Renderer.__plugins || {}; + Renderer.__plugins[pluginName] = ctor; + } +} diff --git a/packages/core/src/System.js b/packages/core/src/System.js new file mode 100644 index 0000000..05a10c8 --- /dev/null +++ b/packages/core/src/System.js @@ -0,0 +1,42 @@ +/** + * System is a base class used for extending systems used by the {@link PIXI.Renderer} + * @see PIXI.Renderer#addSystem + * @class + * @memberof PIXI + */ +export default class System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + /** + * The renderer this manager works for. + * + * @member {PIXI.Renderer} + */ + this.renderer = renderer; + + this.renderer.runners.contextChange.add(this); + } + + /** + * Generic method called when there is a WebGL context change. + * + */ + contextChange() + { + // do some codes init! + } + + /** + * Generic destroy methods to be overridden by the subclass + * + */ + destroy() + { + this.renderer.runners.contextChange.remove(this); + this.renderer = null; + } +} diff --git a/packages/core/src/batch/BatchSystem.js b/packages/core/src/batch/BatchSystem.js new file mode 100644 index 0000000..b04c249 --- /dev/null +++ b/packages/core/src/batch/BatchSystem.js @@ -0,0 +1,68 @@ +import System from '../System'; +import ObjectRenderer from './ObjectRenderer'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class BatchSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + super(renderer); + + /** + * An empty renderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.emptyRenderer = new ObjectRenderer(renderer); + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.currentRenderer = this.emptyRenderer; + } + + /** + * Changes the current renderer to the one given in parameter + * + * @param {PIXI.ObjectRenderer} objectRenderer - The object renderer to use. + */ + setObjectRenderer(objectRenderer) + { + if (this.currentRenderer === objectRenderer) + { + return; + } + + this.currentRenderer.stop(); + this.currentRenderer = objectRenderer; + + this.renderer.state.setState(objectRenderer.state); + + this.currentRenderer.start(); + } + + /** + * This should be called if you wish to do some custom rendering + * It will basically render anything that may be batched up such as sprites + * + */ + flush() + { + this.setObjectRenderer(this.emptyRenderer); + } + + reset() + { + this.setObjectRenderer(this.emptyRenderer); + } +} diff --git a/packages/core/src/batch/ObjectRenderer.js b/packages/core/src/batch/ObjectRenderer.js new file mode 100644 index 0000000..0c851a9 --- /dev/null +++ b/packages/core/src/batch/ObjectRenderer.js @@ -0,0 +1,48 @@ +import System from '../System'; + +/** + * Base for a common object renderer that can be used as a system renderer plugin. + * + * @class + * @extends PIXI.System + * @memberof PIXI + */ +export default class ObjectRenderer extends System +{ + /** + * Starts the renderer and sets the shader + * + */ + start() + { + // set the shader.. + } + + /** + * Stops the renderer + * + */ + stop() + { + this.flush(); + } + + /** + * Stub method for rendering content and emptying the current batch. + * + */ + flush() + { + // flush! + } + + /** + * Renders an object + * + * @param {PIXI.DisplayObject} object - The object to render. + */ + render(object) // eslint-disable-line no-unused-vars + { + // render the object + } +} diff --git a/packages/core/src/context/ContextSystem.js b/packages/core/src/context/ContextSystem.js new file mode 100644 index 0000000..301aab4 --- /dev/null +++ b/packages/core/src/context/ContextSystem.js @@ -0,0 +1,186 @@ +import System from '../System'; +import { settings } from '@pixi/settings'; + +let CONTEXT_UID = 0; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class ContextSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.webGLVersion = 1; + + this.handleContextLost = this.handleContextLost.bind(this); + this.handleContextRestored = this.handleContextRestored.bind(this); + + this.extensions = {}; + + renderer.view.addEventListener('webglcontextlost', this.handleContextLost, false); + renderer.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + } + + get isLost() + { + return (!this.gl || this.gl.isContextLost()); + } + + contextChange(gl) + { + this.gl = gl; + + // restore a context if it was previously lost + if (gl.isContextLost() && gl.getExtension('WEBGL_lose_context')) + { + gl.getExtension('WEBGL_lose_context').restoreContext(); + } + } + + initFromContext(gl) + { + this.gl = gl; + this.validateContext(gl); + this.renderer.gl = gl; + this.renderer.CONTEXT_UID = CONTEXT_UID++; + this.renderer.runners.contextChange.run(gl); + } + + initFromOptions(options) + { + const gl = this.createContext(this.renderer.view, options); + + this.initFromContext(gl); + } + + /** + * Helper class to create a webGL Context + * + * @class + * @memberof PIXI.glCore + * @param canvas {HTMLCanvasElement} the canvas element that we will get the context from + * @param options {Object} An options object that gets passed in to the canvas element containing the context attributes, + * see https://developer.mozilla.org/en/docs/Web/API/HTMLCanvasElement/getContext for the options available + * @return {WebGLRenderingContext} the WebGL context + */ + createContext(canvas, options) + { + let gl; + + if (settings.PREFER_WEBGL_2) + { + gl = canvas.getContext('webgl2', options); + } + + if (gl) + { + this.webGLVersion = 2; + } + else + { + this.webGLVersion = 1; + + gl = canvas.getContext('webgl', options) + || canvas.getContext('experimental-webgl', options); + + if (!gl) + { + // fail, not able to get a context + throw new Error('This browser does not support webGL. Try using the canvas renderer'); + } + } + + this.gl = gl; + + this.getExtensions(); + + return gl; + } + + getExtensions() + { + // time to set up default etensions that pixi uses.. + const gl = this.gl; + const extensions = this.extensions; + + if (this.webGLVersion === 1) + { + extensions.drawBuffers = gl.getExtension('WEBGL_draw_buffers'); + extensions.depthTexture = gl.getExtension('WEBKIT_WEBGL_depth_texture'); + extensions.floatTexture = gl.getExtension('OES_texture_float'); + extensions.loseContext = gl.getExtension('WEBGL_lose_context'); + + extensions.vertexArrayObject = gl.getExtension('OES_vertex_array_object') + || gl.getExtension('MOZ_OES_vertex_array_object') + || gl.getExtension('WEBKIT_OES_vertex_array_object'); + } + + // we don't use any specific WebGL 2 ones yet! + } + + /** + * Handles a lost webgl context + * + * @private + * @param {WebGLContextEvent} event - The context lost event. + */ + handleContextLost(event) + { + event.preventDefault(); + } + + /** + * Handles a restored webgl context + * + * @private + */ + handleContextRestored() + { + this.renderer.runners.contextChange.run(this.gl); + } + + destroy() + { + const view = this.renderer.view; + + // remove listeners + view.removeEventListener('webglcontextlost', this.handleContextLost); + view.removeEventListener('webglcontextrestored', this.handleContextRestored); + + this.gl.useProgram(null); + + if (this.extensions.loseContext) + { + this.extensions.loseContext.loseContext(); + } + } + + postrender() + { + this.gl.flush(); + } + + validateContext(gl) + { + const attributes = gl.getContextAttributes(); + + // this is going to be fairly simple for now.. but at least we have room to grow! + if (!attributes.stencil) + { + /* eslint-disable max-len */ + + /* eslint-disable no-console */ + console.warn('Provided WebGL context does not have a stencil buffer, masks may not render correctly'); + /* eslint-enable no-console */ + + /* eslint-enable max-len */ + } + } +} diff --git a/packages/core/src/filters/Filter.js b/packages/core/src/filters/Filter.js new file mode 100644 index 0000000..9d8cad8 --- /dev/null +++ b/packages/core/src/filters/Filter.js @@ -0,0 +1,140 @@ +import Shader from '../shader/Shader'; +import Program from '../shader/Program'; +import State from '../state/State'; +import { settings } from '@pixi/settings'; +// import extractUniformsFromSrc from './extractUniformsFromSrc'; +import defaultVertex from './defaultFilter.vert'; +import defaultFragment from './defaultFilter.frag'; + +// let math = require('../../math'); +/** + * @class + * @memberof PIXI + * @extends PIXI.Shader + */ +export default class Filter extends Shader +{ + /** + * @param {string} [vertexSrc] - The source of the vertex shader. + * @param {string} [fragmentSrc] - The source of the fragment shader. + * @param {object} [uniforms] - Custom uniforms to use to augment the built-in ones. + */ + constructor(vertexSrc, fragmentSrc, uniforms) + { + const program = Program.from(vertexSrc, fragmentSrc); + + super(program, uniforms); + + /** + * The padding of the filter. Some filters require extra space to breath such as a blur. + * Increasing this will add extra width and height to the bounds of the object that the + * filter is applied to. + * + * @member {number} + */ + this.padding = 0; + + /** + * The resolution of the filter. Setting this to be lower will lower the quality but + * increase the performance of the filter. + * + * @member {number} + */ + this.resolution = settings.RESOLUTION; + + /** + * If enabled is true the filter is applied, if false it will not. + * + * @member {boolean} + */ + this.enabled = true; + + /** + * If enabled, PixiJS will fit the filter area into boundaries for better performance. + * Switch it off if it does not work for specific shader. + * + * @member {boolean} + */ + this.autoFit = true; + + /** + * Legacy filters use position and uvs from attributes + * @member {boolean} + * @readonly + */ + this.legacy = !!this.program.attributeData.aTextureCoord; + + /** + * the webGL state the filter requires to render + * @member {PIXI.State} + */ + this.state = new State(); + } + + /** + * Applies the filter + * + * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + * @param {boolean} clear - Should the output be cleared before rendering to it + * @param {object} [currentState] - It's current state of filter. + * There are some useful properties in the currentState : + * target, filters, sourceFrame, destinationFrame, renderTarget, resolution + */ + apply(filterManager, input, output, clear, currentState, derp) // eslint-disable-line no-unused-vars + { + // do as you please! + + filterManager.applyFilter(this, input, output, clear, currentState, derp); + + // or just do a regular render.. + } + + /** + * Sets the blendmode of the filter + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL + */ + get blendMode() + { + return this.state.blendMode; + } + + set blendMode(value) // eslint-disable-line require-jsdoc + { + this.state.blendMode = value; + } + + /** + * The default vertex shader source + * + * @static + * @constant + */ + static get defaultVertexSrc() + { + return defaultVertex; + } + + /** + * The default fragment shader source + * + * @static + * @constant + */ + static get defaultFragmentSrc() + { + return defaultFragment; + } +} + +/** + * Used for caching shader IDs + * + * @static + * @private + */ +Filter.SOURCE_KEY_MAP = {}; + diff --git a/packages/core/src/filters/FilterSystem.js b/packages/core/src/filters/FilterSystem.js new file mode 100644 index 0000000..3174318 --- /dev/null +++ b/packages/core/src/filters/FilterSystem.js @@ -0,0 +1,431 @@ +import System from '../System'; + +import RenderTexture from '../renderTexture/RenderTexture'; +import Quad from '../utils/Quad'; +import QuadUv from '../utils/QuadUv'; +import { Rectangle } from '@pixi/math'; +import * as filterTransforms from './filterTransforms'; +import bitTwiddle from 'bit-twiddle'; +import UniformGroup from '../shader/UniformGroup'; +import { DRAW_MODES } from '@pixi/constants'; + +// +/** + * @ignore + * @class + */ +class FilterState +{ + /** + * + */ + constructor() + { + this.renderTexture = null; + this.sourceFrame = new Rectangle(); + this.destinationFrame = new Rectangle(); + this.filters = []; + this.target = null; + this.legacy = false; + this.resolution = 1; + } +} + +/** + * @class + * @memberof PIXI.systems + * @extends PIXI.System + */ +export default class FilterSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + /** + * stores a bunch of PO2 textures used for filtering + * @type {Object} + */ + this.texturePool = {}; + + /** + * a pool for storing filter states, save us creating new ones each tick + * @type {Array} + */ + this.statePool = []; + + /** + * A very simple geometry used when drawing a filter effect to the screen + * @type {Quad} + */ + this.quad = new Quad(); + + this.quadUv = new QuadUv(); + + /** + * Temporary rect for maths + * @type {PIXI.Rectangle} + */ + this.tempRect = new Rectangle(); + + this.activeState = {}; + + /** + * this uniform group is attached to filter uniforms when used + * @type {UniformGroup} + */ + this.globalUniforms = new UniformGroup({ + sourceFrame: this.tempRect, + destinationFrame: this.tempRect, + + // legacy variables + filterArea: new Float32Array(4), + filterClamp: new Float32Array(4), + }, true); + } + + /** + * Adds a new filter to the System. + * + * @param {PIXI.DisplayObject} target - The target of the filter to render. + * @param {PIXI.Filter[]} filters - The filters to apply. + */ + push(target, filters) + { + const renderer = this.renderer; + const filterStack = this.renderer.renderTexture.defaultFilterStack; + const state = this.statePool.pop() || new FilterState(); + + let resolution = filters[0].resolution; + let padding = filters[0].padding; + let autoFit = filters[0].autoFit; + let legacy = filters[0].legacy; + + for (let i = 1; i < filters.length; i++) + { + const filter = filters[i]; + + // lets use the lowest resolution.. + resolution = Math.min(resolution, filter.resolution); + // and the largest amount of padding! + padding = Math.max(padding, filter.padding); + // only auto fit if all filters are autofit + autoFit = autoFit || filter.autoFit; + + legacy = legacy || filter.legacy; + } + + filterStack.push(state); + + state.resolution = resolution; + + state.legacy = legacy; + + // round to whole number based on resolution + // TODO move that to the shader too? + state.sourceFrame = target.filterArea || target.getBounds(true); + + state.sourceFrame.pad(padding); + + if (autoFit) + { + state.sourceFrame.fit(this.renderer.renderTexture.destinationFrame); + } + + state.sourceFrame.round(resolution); + + state.renderTexture = this.getPotFilterTexture(state.sourceFrame.width, state.sourceFrame.height, resolution); + state.filters = filters; + + state.destinationFrame.width = state.renderTexture.width; + state.destinationFrame.height = state.renderTexture.height; + + state.renderTexture.filterFrame = state.sourceFrame; + renderer.renderTexture.bind(state.renderTexture, state.sourceFrame);// /, state.destinationFrame); + renderer.renderTexture.clear(); + } + + /** + * Pops off the filter and applies it. + * + */ + pop() + { + const renderer = this.renderer; + const filterStack = renderer.renderTexture.defaultFilterStack; + const state = filterStack.pop(); + const filters = state.filters; + + this.activeState = state; + + const globalUniforms = this.globalUniforms.uniforms; + + globalUniforms.sourceFrame = state.sourceFrame; + globalUniforms.destinationFrame = state.destinationFrame; + globalUniforms.resolution = state.resolution; + + // only update the rect if its legacy.. + if (state.legacy) + { + const filterArea = globalUniforms.filterArea; + const filterClamp = globalUniforms.filterClamp; + + filterArea[0] = state.destinationFrame.width; + filterArea[1] = state.destinationFrame.height; + filterArea[2] = state.sourceFrame.x; + filterArea[3] = state.sourceFrame.y; + + filterClamp[0] = 0.5 / state.resolution / state.destinationFrame.width; + filterClamp[1] = 0.5 / state.resolution / state.destinationFrame.height; + filterClamp[2] = (state.sourceFrame.width - 0.5) / state.resolution / state.destinationFrame.width; + filterClamp[3] = (state.sourceFrame.height - 0.5) / state.resolution / state.destinationFrame.height; + } + + this.globalUniforms.update(); + + const lastState = filterStack[filterStack.length - 1]; + + if (filters.length === 1) + { + filters[0].apply(this, state.renderTexture, lastState.renderTexture, false, state); + + this.returnFilterTexture(state.renderTexture); + } + else + { + let flip = state.renderTexture; + let flop = this.getPotFilterTexture( + flip.width, + flip.height, + state.resolution + ); + + flop.filterFrame = flip.filterFrame; + + let i = 0; + + for (i = 0; i < filters.length - 1; ++i) + { + filters[i].apply(this, flip, flop, true, state); + + const t = flip; + + flip = flop; + flop = t; + } + + filters[i].apply(this, flip, lastState.renderTexture, false, state); + + this.returnFilterTexture(flip); + this.returnFilterTexture(flop); + } + + this.statePool.push(state); + } + + /** + * Draws a filter. + * + * @param {PIXI.Filter} filter - The filter to draw. + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + * @param {boolean} clear - Should the output be cleared before rendering to it + */ + applyFilter(filter, input, output, clear) + { + const renderer = this.renderer; + + renderer.renderTexture.bind(output, output ? output.filterFrame : null); + + if (clear) + { + // gl.disable(gl.SCISSOR_TEST); + renderer.renderTexture.clear(); + // gl.enable(gl.SCISSOR_TEST); + } + + // set the uniforms.. + filter.uniforms.uSampler = input; + filter.uniforms.filterGlobals = this.globalUniforms; + + // TODO make it so that the order of this does not matter.. + // because it does at the moment cos of global uniforms. + // they need to get resynced + + renderer.state.setState(filter.state); + renderer.shader.bind(filter); + + if (filter.legacy) + { + this.quadUv.map(input._frame, input.filterFrame); + + renderer.geometry.bind(this.quadUv); + renderer.geometry.draw(DRAW_MODES.TRIANGLES); + } + else + { + renderer.geometry.bind(this.quad); + renderer.geometry.draw(DRAW_MODES.TRIANGLE_STRIP); + } + } + + /** + * Calculates the mapped matrix. + * + * TODO playing around here.. this is temporary - (will end up in the shader) + * this returns a matrix that will normalise map filter cords in the filter to screen space + * + * @param {PIXI.Matrix} outputMatrix - the matrix to output to. + * @return {PIXI.Matrix} The mapped matrix. + */ + calculateScreenSpaceMatrix(outputMatrix) + { + const currentState = this.activeState; + + return filterTransforms.calculateScreenSpaceMatrix( + outputMatrix, + currentState.sourceFrame, + currentState.destinationFrame + ); + } + + /** + * This will map the filter coord so that a texture can be used based on the transform of a sprite + * + * @param {PIXI.Matrix} outputMatrix - The matrix to output to. + * @param {PIXI.Sprite} sprite - The sprite to map to. + * @return {PIXI.Matrix} The mapped matrix. + */ + calculateSpriteMatrix(outputMatrix, sprite) + { + const currentState = this.activeState; + + return filterTransforms.calculateSpriteMatrix( + outputMatrix, + currentState.sourceFrame, + currentState.destinationFrame, + sprite + ); + } + + /** + * Destroys this Filter System. + * + * @param {boolean} [contextLost=false] context was lost, do not free shaders + * + */ + destroy(contextLost = false) + { + if (!contextLost) + { + this.emptyPool(); + } + else + { + this.texturePool = {}; + } + } + + /** + * Gets a Power-of-Two render texture. + * + * TODO move to a seperate class could be on renderer? + * also - could cause issue with multiple contexts? + * + * @private + * @param {WebGLRenderingContext} gl - The webgl rendering context + * @param {number} minWidth - The minimum width of the render target. + * @param {number} minHeight - The minimum height of the render target. + * @param {number} resolution - The resolution of the render target. + * @return {PIXI.RenderTarget} The new render target. + */ + getPotFilterTexture(minWidth, minHeight, resolution) + { + minWidth = bitTwiddle.nextPow2(minWidth); + minHeight = bitTwiddle.nextPow2(minHeight); + resolution = resolution || 1; + + const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + + if (!this.texturePool[key]) + { + this.texturePool[key] = []; + } + + let renderTexture = this.texturePool[key].pop(); + + if (!renderTexture) + { + // temporary bypass cache.. + // internally - this will cause a texture to be bound.. + renderTexture = RenderTexture.create({ + width: minWidth, + height: minHeight, + resolution, + }); + } + + return renderTexture; + } + + /** + * Gets extra render texture to use inside current filter + * + * @param {number} resolution resolution of the renderTexture + * @returns {PIXI.RenderTexture} + */ + getFilterTexture(resolution) + { + const rt = this.activeState.renderTexture; + + const filterTexture = this.getPotFilterTexture(rt.width, rt.height, resolution || rt.baseTexture.resolution); + + filterTexture.filterFrame = rt.filterFrame; + + return filterTexture; + } + + /** + * Frees a render texture back into the pool. + * + * @param {PIXI.RenderTarget} renderTexture - The renderTarget to free + */ + returnFilterTexture(renderTexture) + { + renderTexture.filterFrame = null; + + const base = renderTexture.baseTexture; + + const minWidth = base.width; + const minHeight = base.height; + + const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + + this.texturePool[key].push(renderTexture); + } + + /** + * Empties the texture pool. + * + */ + emptyPool() + { + for (const i in this.texturePool) + { + const textures = this.texturePool[i]; + + if (textures) + { + for (let j = 0; j < textures.length; j++) + { + textures[j].destroy(true); + } + } + } + + this.texturePool = {}; + } +} diff --git a/packages/core/src/filters/defaultFilter.frag b/packages/core/src/filters/defaultFilter.frag new file mode 100644 index 0000000..a14c1be --- /dev/null +++ b/packages/core/src/filters/defaultFilter.frag @@ -0,0 +1,22 @@ +varying vec2 vTextureCoord; +varying vec2 vFilterCoord; + +uniform sampler2D uSampler; +uniform sampler2D filterSampler; + +void main(void){ + vec4 masky = texture2D(filterSampler, vFilterCoord); + vec4 sample = texture2D(uSampler, vTextureCoord); + vec4 color; + if(mod(vFilterCoord.x, 1.0) > 0.5) + { + color = vec4(1.0, 0.0, 0.0, 1.0); + } + else + { + color = vec4(0.0, 1.0, 0.0, 1.0); + } + // gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0); + gl_FragColor = mix(sample, masky, 0.5); + gl_FragColor *= sample.a; +} diff --git a/packages/core/src/filters/defaultFilter.vert b/packages/core/src/filters/defaultFilter.vert new file mode 100644 index 0000000..e1febca --- /dev/null +++ b/packages/core/src/filters/defaultFilter.vert @@ -0,0 +1,14 @@ +attribute vec2 aVertexPosition; +attribute vec2 aTextureCoord; + +uniform mat3 projectionMatrix; +uniform mat3 filterMatrix; + +varying vec2 vTextureCoord; +varying vec2 vFilterCoord; + +void main(void){ + gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); + vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy; + vTextureCoord = aTextureCoord ; +} diff --git a/packages/core/src/filters/filterTransforms.js b/packages/core/src/filters/filterTransforms.js new file mode 100644 index 0000000..87a3cd6 --- /dev/null +++ b/packages/core/src/filters/filterTransforms.js @@ -0,0 +1,50 @@ +import { Matrix } from '@pixi/math'; + +/** + * Calculates the mapped matrix + * @param filterArea {Rectangle} The filter area + * @param sprite {Sprite} the target sprite + * @param outputMatrix {Matrix} @alvin + * @private + */ +// this returns a matrix that will normalise map filter cords in the filter to screen space +export function calculateScreenSpaceMatrix(outputMatrix, filterArea, textureSize) +{ + // TODO unwrap? + const mappedMatrix = outputMatrix.identity(); + + mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); + + mappedMatrix.scale(textureSize.width, textureSize.height); + + return mappedMatrix; +} + +export function calculateNormalizedScreenSpaceMatrix(outputMatrix, filterArea, textureSize) +{ + const mappedMatrix = outputMatrix.identity(); + + mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); + + const translateScaleX = (textureSize.width / filterArea.width); + const translateScaleY = (textureSize.height / filterArea.height); + + mappedMatrix.scale(translateScaleX, translateScaleY); + + return mappedMatrix; +} + +// this will map the filter coord so that a texture can be used based on the transform of a sprite +export function calculateSpriteMatrix(outputMatrix, filterArea, textureSize, sprite) +{ + const orig = sprite._texture.orig; + const mappedMatrix = outputMatrix.set(textureSize.width, 0, 0, textureSize.height, filterArea.x, filterArea.y); + const worldTransform = sprite.worldTransform.copyTo(Matrix.TEMP_MATRIX); + + worldTransform.invert(); + mappedMatrix.prepend(worldTransform); + mappedMatrix.scale(1.0 / orig.width, 1.0 / orig.height); + mappedMatrix.translate(sprite.anchor.x, sprite.anchor.y); + + return mappedMatrix; +} diff --git a/packages/core/src/filters/spriteMask/SpriteMaskFilter.js b/packages/core/src/filters/spriteMask/SpriteMaskFilter.js new file mode 100644 index 0000000..91697b7 --- /dev/null +++ b/packages/core/src/filters/spriteMask/SpriteMaskFilter.js @@ -0,0 +1,63 @@ +import Filter from '../Filter'; +import { Matrix } from '@pixi/math'; +import vertex from './spriteMaskFilter.vert'; +import fragment from './spriteMaskFilter.frag'; +import { default as TextureMatrix } from '../../textures/TextureMatrix'; + +/** + * The SpriteMaskFilter class + * + * @class + * @extends PIXI.Filter + * @memberof PIXI + */ +export default class SpriteMaskFilter extends Filter +{ + /** + * @param {PIXI.Sprite} sprite - the target sprite + */ + constructor(sprite) + { + const maskMatrix = new Matrix(); + + super(vertex, fragment); + + sprite.renderable = false; + + this.maskSprite = sprite; + this.maskMatrix = maskMatrix; + } + + /** + * Applies the filter + * + * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + */ + apply(filterManager, input, output) + { + const maskSprite = this.maskSprite; + const tex = this.maskSprite.texture; + + if (!tex.valid) + { + return; + } + if (!tex.transform) + { + // margin = 0.0, let it bleed a bit, shader code becomes easier + // assuming that atlas textures were made with 1-pixel padding + tex.transform = new TextureMatrix(tex, 0.0); + } + tex.transform.update(); + + this.uniforms.mask = tex; + this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite) + .prepend(tex.transform.mapCoord); + this.uniforms.alpha = maskSprite.worldAlpha; + this.uniforms.maskClamp = tex.transform.uClampFrame; + + filterManager.applyFilter(this, input, output); + } +} diff --git a/packages/core/src/filters/spriteMask/spriteMaskFilter.frag b/packages/core/src/filters/spriteMask/spriteMaskFilter.frag new file mode 100644 index 0000000..0e4aef8 --- /dev/null +++ b/packages/core/src/filters/spriteMask/spriteMaskFilter.frag @@ -0,0 +1,23 @@ +varying vec2 vMaskCoord; +varying vec2 vTextureCoord; + +uniform sampler2D uSampler; +uniform sampler2D mask; +uniform float alpha; +uniform vec4 maskClamp; + +void main(void) +{ + float clip = step(3.5, + step(maskClamp.x, vMaskCoord.x) + + step(maskClamp.y, vMaskCoord.y) + + step(vMaskCoord.x, maskClamp.z) + + step(vMaskCoord.y, maskClamp.w)); + + vec4 original = texture2D(uSampler, vTextureCoord); + vec4 masky = texture2D(mask, vMaskCoord); + + original *= (masky.r * masky.a * alpha * clip); + + gl_FragColor = original; +} diff --git a/packages/core/src/filters/spriteMask/spriteMaskFilter.vert b/packages/core/src/filters/spriteMask/spriteMaskFilter.vert new file mode 100644 index 0000000..d23a30c --- /dev/null +++ b/packages/core/src/filters/spriteMask/spriteMaskFilter.vert @@ -0,0 +1,16 @@ +attribute vec2 aVertexPosition; +attribute vec2 aTextureCoord; + +uniform mat3 projectionMatrix; +uniform mat3 otherMatrix; + +varying vec2 vMaskCoord; +varying vec2 vTextureCoord; + +void main(void) +{ + gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); + + vTextureCoord = aTextureCoord; + vMaskCoord = ( otherMatrix * vec3( aTextureCoord, 1.0) ).xy; +} diff --git a/packages/core/src/framebuffer/FrameBuffer.js b/packages/core/src/framebuffer/FrameBuffer.js new file mode 100644 index 0000000..84e9fcc --- /dev/null +++ b/packages/core/src/framebuffer/FrameBuffer.js @@ -0,0 +1,106 @@ +import Texture from '../textures/BaseTexture'; +import { FORMATS, TYPES } from '@pixi/constants'; + +/** + * Frame buffer + * @class + * @memberof PIXI + */ +export default class FrameBuffer +{ + constructor(width, height) + { + this.width = width || 100; + this.height = height || 100; + + this.stencil = false; + this.depth = false; + + this.dirtyId = 0; + this.dirtyFormat = 0; + this.dirtySize = 0; + + this.depthTexture = null; + this.colorTextures = []; + + this.glFrameBuffers = {}; + } + + get colorTexture() + { + return this.colorTextures[0]; + } + + addColorTexture(index, texture) + { + // TODO add some validation to the texture - same width / height etc? + this.colorTextures[index || 0] = texture || new Texture(null, { scaleMode: 0, + resolution: 1, + mipmap: false, + width: this.width, + height: this.height });// || new Texture(); + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + addDepthTexture(texture) + { + /* eslint-disable max-len */ + this.depthTexture = texture || new Texture(null, { scaleMode: 0, + resolution: 1, + width: this.width, + height: this.height, + mipmap: false, + format: FORMATS.DEPTH_COMPONENT, + type: TYPES.UNSIGNED_SHORT });// UNSIGNED_SHORT; + /* eslint-disable max-len */ + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + enableDepth() + { + this.depth = true; + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + enableStencil() + { + this.stencil = true; + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + resize(width, height) + { + if (width === this.width && height === this.height) return; + + this.width = width; + this.height = height; + + this.dirtyId++; + this.dirtySize++; + + for (let i = 0; i < this.colorTextures.length; i++) + { + this.colorTextures[i].setSize(width, height); + } + + if (this.depthTexture) + { + this.depthTexture.setSize(width, height); + } + } +} diff --git a/packages/core/src/framebuffer/FramebufferSystem.js b/packages/core/src/framebuffer/FramebufferSystem.js new file mode 100644 index 0000000..2cc2437 --- /dev/null +++ b/packages/core/src/framebuffer/FramebufferSystem.js @@ -0,0 +1,246 @@ +import System from '../System'; +import { Rectangle } from '@pixi/math'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class FramebufferSystem extends System +{ + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + contextChange() + { + this.gl = this.renderer.gl; + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + this.current = null; + this.viewport = new Rectangle(); + + this.drawBufferExtension = this.renderer.context.extensions.drawBuffers; + } + + bind(framebuffer, frame) + { + const gl = this.gl; + + this.current = framebuffer; + + if (framebuffer) + { + // TODO cacheing layer! + + const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID] || this.initFramebuffer(framebuffer); + + gl.bindFramebuffer(gl.FRAMEBUFFER, fbo.framebuffer); + // makesure all textures are unbound.. + + // now check for updates... + if (fbo.dirtyId !== framebuffer.dirtyId) + { + fbo.dirtyId = framebuffer.dirtyId; + + if (fbo.dirtyFormat !== framebuffer.dirtyFormat) + { + fbo.dirtyFormat = framebuffer.dirtyFormat; + this.updateFramebuffer(framebuffer); + } + else if (fbo.dirtySize !== framebuffer.dirtySize) + { + fbo.dirtySize = framebuffer.dirtySize; + this.resizeFramebuffer(framebuffer); + } + } + + for (let i = 0; i < framebuffer.colorTextures.length; i++) + { + if (framebuffer.colorTextures[i].texturePart) + { + this.renderer.texture.unbind(framebuffer.colorTextures[i].texture); + } + else + { + this.renderer.texture.unbind(framebuffer.colorTextures[i]); + } + } + + if (framebuffer.depthTexture) + { + this.renderer.texture.unbind(framebuffer.depthTexture); + } + + if (frame) + { + this.setViewport(frame.x, frame.y, frame.width, frame.height); + } + else + { + this.setViewport(0, 0, framebuffer.width, framebuffer.height); + } + } + else + { + gl.bindFramebuffer(gl.FRAMEBUFFER, null); + + if (frame) + { + this.setViewport(frame.x, frame.y, frame.width, frame.height); + } + else + { + this.setViewport(0, 0, this.renderer.width, this.renderer.height); + } + } + } + + setViewport(x, y, width, height) + { + const v = this.viewport; + + if (v.width !== width || v.height !== height || v.x !== x || v.y !== y) + { + v.x = x; + v.y = y; + v.width = width; + v.height = height; + + this.gl.viewport(x, y, width, height); + } + } + + get size() + { + if (this.current) + { + // TODO store temp + return { x: 0, y: 0, width: this.current.width, height: this.current.height }; + } + + return { x: 0, y: 0, width: this.renderer.width, height: this.renderer.height }; + } + + clear(r, g, b, a) + { + const gl = this.gl; + + // TODO clear color can be set only one right? + gl.clearColor(r, g, b, a); + gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); + } + + // private functions... + + initFramebuffer(framebuffer) + { + const gl = this.gl; + + // TODO - make this a class? + const fbo = { + framebuffer: gl.createFramebuffer(), + stencil: null, + dirtyId: 0, + dirtyFormat: 0, + dirtySize: 0, + }; + + framebuffer.glFrameBuffers[this.CONTEXT_UID] = fbo; + + return fbo; + } + + resizeFramebuffer(framebuffer) + { + const gl = this.gl; + + if (framebuffer.stencil || framebuffer.depth) + { + gl.bindRenderbuffer(gl.RENDERBUFFER, this.stencil); + gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); + } + } + + updateFramebuffer(framebuffer) + { + const gl = this.gl; + + const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID]; + + // bind the color texture + const colorTextures = framebuffer.colorTextures; + + let count = colorTextures.length; + + if (!this.drawBufferExtension) + { + count = Math.min(count, 1); + } + + const activeTextures = []; + + for (let i = 0; i < count; i++) + { + const texture = framebuffer.colorTextures[i]; + + if (texture.texturePart) + { + this.renderer.texture.bind(texture.texture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.COLOR_ATTACHMENT0 + i, + gl.TEXTURE_CUBE_MAP_NEGATIVE_X + texture.side, + texture.texture._glTextures[this.CONTEXT_UID].texture, + 0); + } + else + { + this.renderer.texture.bind(texture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.COLOR_ATTACHMENT0 + i, + gl.TEXTURE_2D, + texture._glTextures[this.CONTEXT_UID].texture, + 0); + } + + activeTextures.push(gl.COLOR_ATTACHMENT0 + i); + } + + if (this.drawBufferExtension && activeTextures.length > 1) + { + this.drawBufferExtension.drawBuffersWEBGL(activeTextures); + } + + if (framebuffer.depthTexture) + { + const depthTextureExt = this.renderer.context.extensions.depthTexture; + + if (depthTextureExt) + { + const depthTexture = framebuffer.depthTexture; + + this.renderer.texture.bind(depthTexture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.DEPTH_ATTACHMENT, + gl.TEXTURE_2D, + depthTexture._glTextures[this.CONTEXT_UID].texture, + 0); + } + } + + if (framebuffer.stencil || framebuffer.depth) + { + fbo.stencil = gl.createRenderbuffer(); + + gl.bindRenderbuffer(gl.RENDERBUFFER, fbo.stencil); + + // TODO.. this is depth AND stencil? + gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, fbo.stencil); + gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); + // fbo.enableStencil(); + } + } +} diff --git a/packages/core/src/geometry/GLBuffer.js b/packages/core/src/geometry/GLBuffer.js new file mode 100644 index 0000000..0477e8a --- /dev/null +++ b/packages/core/src/geometry/GLBuffer.js @@ -0,0 +1,9 @@ +export default class GLBuffer +{ + constructor(buffer) + { + this.buffer = buffer; + this.updateID = -1; + this.byteLength = -1; + } +} diff --git a/packages/core/src/geometry/GeometrySystem.js b/packages/core/src/geometry/GeometrySystem.js new file mode 100644 index 0000000..959f827 --- /dev/null +++ b/packages/core/src/geometry/GeometrySystem.js @@ -0,0 +1,388 @@ +import System from '../System'; +import GLBuffer from './GLBuffer'; + +const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class GeometrySystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this._activeGeometry = null; + this._activeVao = null; + + this.hasVao = true; + this.hasInstance = true; + } + + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + contextChange() + { + const gl = this.gl = this.renderer.gl; + + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + + // webgl2 + if (!gl.createVertexArray) + { + // webgl 1! + let nativeVaoExtension = this.renderer.context.extensions.vertexArrayObject; + + if (this.renderer.legacy) + { + nativeVaoExtension = null; + } + + if (nativeVaoExtension) + { + gl.createVertexArray = () => + nativeVaoExtension.createVertexArrayOES(); + + gl.bindVertexArray = (vao) => + nativeVaoExtension.bindVertexArrayOES(vao); + + gl.deleteVertexArray = (vao) => + nativeVaoExtension.deleteVertexArrayOES(vao); + } + else + { + this.hasVao = false; + gl.createVertexArray = () => + { + // empty + }; + + gl.bindVertexArray = () => + { + // empty + }; + + gl.deleteVertexArray = () => + { + // empty + }; + } + } + + if (!gl.vertexAttribDivisor) + { + const instanceExt = gl.getExtension('ANGLE_instanced_arrays'); + + if (instanceExt) + { + gl.vertexAttribDivisor = (a, b) => + instanceExt.vertexAttribDivisorANGLE(a, b); + + gl.drawElementsInstanced = (a, b, c, d, e) => + instanceExt.drawElementsInstancedANGLE(a, b, c, d, e); + + gl.drawArraysInstanced = (a, b, c, d) => + instanceExt.drawArraysInstancedANGLE(a, b, c, d); + } + else + { + this.hasInstance = false; + } + } + } + + /** + * Binds geometry so that is can be drawn. Creating a Vao if required + * @private + * @param {PIXI.Geometry} geometry instance of geometry to bind + */ + bind(geometry, shader) + { + shader = shader || this.renderer.shader.shader; + + const gl = this.gl; + + // not sure the best way to address this.. + // currently different shaders require different VAOs for the same geometry + // Still mulling over the best way to solve this one.. + // will likely need to modify the shader attribute locations at run time! + let vaos = geometry.glVertexArrayObjects[this.CONTEXT_UID]; + + if (!vaos) + { + geometry.glVertexArrayObjects[this.CONTEXT_UID] = vaos = {}; + } + + const vao = vaos[shader.program.id] || this.initGeometryVao(geometry, shader.program); + + this._activeGeometry = geometry; + + if (this._activeVao !== vao) + { + this._activeVao = vao; + + if (this.hasVao) + { + gl.bindVertexArray(vao); + } + else + { + this.activateVao(geometry, shader.program); + } + } + + // TODO - optimise later! + // don't need to loop through if nothing changed! + // maybe look to add an 'autoupdate' to geometry? + this.updateBuffers(); + } + + reset() + { + this.unbind(); + } + + updateBuffers() + { + const geometry = this._activeGeometry; + const gl = this.gl; + + for (let i = 0; i < geometry.buffers.length; i++) + { + const buffer = geometry.buffers[i]; + + const glBuffer = buffer._glBuffers[this.CONTEXT_UID]; + + if (buffer._updateID !== glBuffer.updateID) + { + glBuffer.updateID = buffer._updateID; + + // TODO can cache this on buffer! maybe added a getter / setter? + const type = buffer.index ? gl.ELEMENT_ARRAY_BUFFER : gl.ARRAY_BUFFER; + const drawType = buffer.static ? gl.STATIC_DRAW : gl.DYNAMIC_DRAW; + + gl.bindBuffer(type, glBuffer.buffer); + + if (glBuffer.byteLength >= buffer.data.byteLength) + { + // offset is always zero for now! + gl.bufferSubData(type, 0, buffer.data); + } + else + { + gl.bufferData(type, buffer.data, drawType); + } + } + } + } + + checkCompatability(geometry, program) + { + // geometry must have at least all the attributes that the shader requires. + const geometryAttributes = geometry.attributes; + const shaderAttributes = program.attributeData; + + for (const j in shaderAttributes) + { + if (!geometryAttributes[j]) + { + throw new Error(`shader and geometry incompatible, geometry missing the "${j}" attribute`); + } + } + } + + /** + * Creates a Vao with the same structure as the geometry and stores it on the geometry. + * @private + * @param {PIXI.Geometry} geometry instance of geometry to to generate Vao for + */ + initGeometryVao(geometry, program) + { + this.checkCompatability(geometry, program); + + const gl = this.gl; + const CONTEXT_UID = this.CONTEXT_UID; + const buffers = geometry.buffers; + const attributes = geometry.attributes; + + const tempStride = {}; + const tempStart = {}; + + for (const j in buffers) + { + tempStride[j] = 0; + tempStart[j] = 0; + } + + for (const j in attributes) + { + if (!attributes[j].size && program.attributeData[j]) + { + attributes[j].size = program.attributeData[j].size; + } + + tempStride[attributes[j].buffer] += attributes[j].size * byteSizeMap[attributes[j].type]; + } + + for (const j in attributes) + { + const attribute = attributes[j]; + const attribSize = attribute.size; + + if (attribute.stride === undefined) + { + if (tempStride[attribute.buffer] === attribSize * byteSizeMap[attribute.type]) + { + attribute.stride = 0; + } + else + { + attribute.stride = tempStride[attribute.buffer]; + } + } + + if (attribute.start === undefined) + { + attribute.start = tempStart[attribute.buffer]; + + tempStart[attribute.buffer] += attribSize * byteSizeMap[attribute.type]; + } + } + + // first update - and create the buffers! + // only create a gl buffer if it actually gets + for (let i = 0; i < buffers.length; i++) + { + const buffer = buffers[i]; + + if (!buffer._glBuffers[CONTEXT_UID]) + { + buffer._glBuffers[CONTEXT_UID] = new GLBuffer(gl.createBuffer()); + } + } + + // TODO - maybe make this a data object? + // lets wait to see if we need to first! + const vao = gl.createVertexArray(); + + gl.bindVertexArray(vao); + + this.activateVao(geometry, program); + + gl.bindVertexArray(null); + + // add it to the cache! + geometry.glVertexArrayObjects[this.CONTEXT_UID][program.id] = vao; + + return vao; + } + + activateVao(geometry, program) + { + const gl = this.gl; + const CONTEXT_UID = this.CONTEXT_UID; + const buffers = geometry.buffers; + const attributes = geometry.attributes; + + if (geometry.indexBuffer) + { + // first update the index buffer if we have one.. + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, geometry.indexBuffer._glBuffers[CONTEXT_UID].buffer); + } + + let lastBuffer = null; + + // add a new one! + for (const j in attributes) + { + const attribute = attributes[j]; + const buffer = buffers[attribute.buffer]; + const glBuffer = buffer._glBuffers[CONTEXT_UID]; + + if (program.attributeData[j]) + { + if (lastBuffer !== glBuffer) + { + gl.bindBuffer(gl.ARRAY_BUFFER, glBuffer.buffer); + + lastBuffer = glBuffer; + } + + const location = program.attributeData[j].location; + + // TODO introduce state again + // we can optimise this for older devices that have no VAOs + gl.enableVertexAttribArray(location); + + gl.vertexAttribPointer(location, + attribute.size, + attribute.type || gl.FLOAT, + attribute.normalized, + attribute.stride, + attribute.start); + + if (attribute.instance) + { + // TODO calculate instance count based of this... + if (this.hasInstance) + { + gl.vertexAttribDivisor(location, 1); + } + else + { + throw new Error('geometry error, GPU Instancing is not supported on this device'); + } + } + } + } + } + + draw(type, size, start, instanceCount) + { + const gl = this.gl; + const geometry = this._activeGeometry; + + // TODO.. this should not change so maybe cache the function? + + if (geometry.indexBuffer) + { + if (geometry.instanced) + { + /* eslint-disable max-len */ + gl.drawElementsInstanced(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2, instanceCount || 1); + /* eslint-enable max-len */ + } + else + { + gl.drawElements(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2); + } + } + else + if (geometry.instanced) + { + // TODO need a better way to calculate size.. + gl.drawArraysInstanced(type, start, size || geometry.getSize(), instanceCount || 1); + } + else + { + gl.drawArrays(type, start, size || geometry.getSize()); + } + + return this; + } + + unbind() + { + this.gl.bindVertexArray(null); + this._activeVao = null; + this._activeGeometry = null; + } +} diff --git a/packages/core/src/geometry/utils/setVertexAttribArrays.js b/packages/core/src/geometry/utils/setVertexAttribArrays.js new file mode 100644 index 0000000..23b6959 --- /dev/null +++ b/packages/core/src/geometry/utils/setVertexAttribArrays.js @@ -0,0 +1,54 @@ +// var GL_MAP = {}; + +/** + * @param gl {WebGLRenderingContext} The current WebGL context + * @param attribs {*} + * @param state {*} + */ +export default function setVertexAttribArrays(gl, attribs, state) +{ + let i; + + if (state) + { + const tempAttribState = state.tempAttribState; + const attribState = state.attribState; + + for (i = 0; i < tempAttribState.length; i++) + { + tempAttribState[i] = false; + } + + // set the new attribs + for (i = 0; i < attribs.length; i++) + { + tempAttribState[attribs[i].attribute.location] = true; + } + + for (i = 0; i < attribState.length; i++) + { + if (attribState[i] !== tempAttribState[i]) + { + attribState[i] = tempAttribState[i]; + + if (state.attribState[i]) + { + gl.enableVertexAttribArray(i); + } + else + { + gl.disableVertexAttribArray(i); + } + } + } + } + else + { + for (i = 0; i < attribs.length; i++) + { + const attrib = attribs[i]; + + gl.enableVertexAttribArray(attrib.attribute.location); + } + } +} diff --git a/packages/core/src/index.js b/packages/core/src/index.js index 58c456a..fd1f547 100644 --- a/packages/core/src/index.js +++ b/packages/core/src/index.js @@ -1,30 +1,30 @@ import * as resources from './textures/resources'; -import * as systems from './renderers/systems'; +import * as systems from './systems'; -export { resources }; export { systems }; +export { resources }; -export { default as Renderer } from './renderers/Renderer'; -export { default as AbstractRenderer } from './renderers/AbstractRenderer'; -export { default as FrameBuffer } from './textures/FrameBuffer'; +export { default as System } from './System'; +export { default as Renderer } from './Renderer'; +export { default as AbstractRenderer } from './AbstractRenderer'; +export { default as FrameBuffer } from './framebuffer/FrameBuffer'; export { default as CubeTexture } from './textures/CubeTexture'; export { default as BaseTexture } from './textures/BaseTexture'; export { default as Texture } from './textures/Texture'; export { default as TextureMatrix } from './textures/TextureMatrix'; -export { default as RenderTexture } from './textures/RenderTexture'; -export { default as BaseRenderTexture } from './textures/BaseRenderTexture'; +export { default as RenderTexture } from './renderTexture/RenderTexture'; +export { default as BaseRenderTexture } from './renderTexture/BaseRenderTexture'; export { default as TextureUvs } from './textures/TextureUvs'; -export { default as State } from './renderers/State'; -export { default as ObjectRenderer } from './renderers/utils/ObjectRenderer'; -export { default as Quad } from './renderers/utils/Quad'; -export { default as QuadUv } from './renderers/utils/QuadUv'; -export { default as checkMaxIfStatmentsInShader } from './renderers/utils/checkMaxIfStatmentsInShader'; +export { default as State } from './state/State'; +export { default as ObjectRenderer } from './batch/ObjectRenderer'; +export { default as Quad } from './utils/Quad'; +export { default as QuadUv } from './utils/QuadUv'; +export { default as checkMaxIfStatmentsInShader } from './shader/utils/checkMaxIfStatmentsInShader'; export { default as Shader } from './shader/Shader'; export { default as Program } from './shader/Program'; export { default as UniformGroup } from './shader/UniformGroup'; -export { default as SpriteMaskFilter } from './renderers/filters/spriteMask/SpriteMaskFilter'; -export { default as Filter } from './renderers/filters/Filter'; +export { default as SpriteMaskFilter } from './filters/spriteMask/SpriteMaskFilter'; +export { default as Filter } from './filters/Filter'; export { default as Attribute } from './geometry/Attribute'; export { default as Buffer } from './geometry/Buffer'; export { default as Geometry } from './geometry/Geometry'; - diff --git a/packages/core/src/mask/MaskSystem.js b/packages/core/src/mask/MaskSystem.js new file mode 100644 index 0000000..99174c2 --- /dev/null +++ b/packages/core/src/mask/MaskSystem.js @@ -0,0 +1,199 @@ +import System from '../System'; +import AlphaMaskFilter from '../filters/spriteMask/SpriteMaskFilter'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class MaskSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + // TODO - we don't need both! + this.scissor = false; + this.scissorData = null; + this.scissorRenderTarget = null; + + this.enableScissor = false; + + this.alphaMaskPool = []; + this.alphaMaskIndex = 0; + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.DisplayObject} target - Display Object to push the mask to + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + push(target, maskData) + { + // TODO the root check means scissor rect will not + // be used on render textures more info here: + // https://github.com/pixijs/pixi.js/pull/3545 + + if (maskData.texture) + { + this.pushSpriteMask(target, maskData); + } + else if (this.enableScissor + && !this.scissor + && this.renderer._activeRenderTarget.root + && !this.renderer.stencil.stencilMaskStack.length + && maskData.isFastRect()) + { + const matrix = maskData.worldTransform; + + let rot = Math.atan2(matrix.b, matrix.a); + + // use the nearest degree! + rot = Math.round(rot * (180 / Math.PI)); + + if (rot % 90) + { + this.pushStencilMask(maskData); + } + else + { + this.pushScissorMask(target, maskData); + } + } + else + { + this.pushStencilMask(maskData); + } + } + + /** + * Removes the last mask from the mask stack and doesn't return it. + * + * @param {PIXI.DisplayObject} target - Display Object to pop the mask from + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + pop(target, maskData) + { + if (maskData.texture) + { + this.popSpriteMask(target, maskData); + } + else if (this.enableScissor && !this.renderer.stencil.stencilMaskStack.length) + { + this.popScissorMask(target, maskData); + } + else + { + this.popStencilMask(target, maskData); + } + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.RenderTarget} target - Display Object to push the sprite mask to + * @param {PIXI.Sprite} maskData - Sprite to be used as the mask + */ + pushSpriteMask(target, maskData) + { + let alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; + + if (!alphaMaskFilter) + { + alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; + } + + alphaMaskFilter[0].resolution = this.renderer.resolution; + alphaMaskFilter[0].maskSprite = maskData; + + // TODO - may cause issues! + target.filterArea = maskData.getBounds(true); + + this.renderer.filterSystem.pushFilter(target, alphaMaskFilter); + + this.alphaMaskIndex++; + } + + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popSpriteMask() + { + this.renderer.filterSystem.popFilter(); + this.alphaMaskIndex--; + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + pushStencilMask(maskData) + { + this.renderer.batch.flush(); + this.renderer.stencil.pushStencil(maskData); + } + + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popStencilMask() + { + // this.renderer.currentRenderer.stop(); + this.renderer.stencil.popStencil(); + } + + /** + * + * @param {PIXI.DisplayObject} target - Display Object to push the mask to + * @param {PIXI.Graphics} maskData - The masking data. + */ + pushScissorMask(target, maskData) + { + maskData.renderable = true; + + const renderTarget = this.renderer._activeRenderTarget; + + const bounds = maskData.getBounds(); + + bounds.fit(renderTarget.size); + maskData.renderable = false; + + this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); + + const resolution = this.renderer.resolution; + + this.renderer.gl.scissor( + bounds.x * resolution, + (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, + bounds.width * resolution, + bounds.height * resolution + ); + + this.scissorRenderTarget = renderTarget; + this.scissorData = maskData; + this.scissor = true; + } + + /** + * + * + */ + popScissorMask() + { + this.scissorRenderTarget = null; + this.scissorData = null; + this.scissor = false; + + // must be scissor! + const gl = this.renderer.gl; + + gl.disable(gl.SCISSOR_TEST); + } +} diff --git a/packages/core/src/mask/StencilSystem.js b/packages/core/src/mask/StencilSystem.js new file mode 100644 index 0000000..62796a5 --- /dev/null +++ b/packages/core/src/mask/StencilSystem.js @@ -0,0 +1,133 @@ +import System from '../System'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class StencilSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + this.stencilMaskStack = []; + } + + /** + * Changes the mask stack that is used by this System. + * + * @param {PIXI.Graphics[]} stencilMaskStack - The mask stack + */ + setMaskStack(stencilMaskStack) + { + const gl = this.renderer.gl; + + if (stencilMaskStack.length !== this.stencilMaskStack.length) + { + if (stencilMaskStack.length === 0) + { + gl.disable(gl.STENCIL_TEST); + } + else + { + gl.enable(gl.STENCIL_TEST); + } + } + + this.stencilMaskStack = stencilMaskStack; + } + + /** + * Applies the Mask and adds it to the current stencil stack. @alvin + * + * @param {PIXI.Graphics} graphics - The mask + */ + pushStencil(graphics) + { + this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); + + // this.renderer._activeRenderTarget.attachStencilBuffer(); + + const gl = this.renderer.gl; + const prevMaskCount = this.stencilMaskStack.length; + + if (prevMaskCount === 0) + { + gl.enable(gl.STENCIL_TEST); + } + + this.stencilMaskStack.push(graphics); + + // Increment the refference stencil value where the new mask overlaps with the old ones. + gl.colorMask(false, false, false, false); + gl.stencilFunc(gl.EQUAL, prevMaskCount, this._getBitwiseMask()); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.INCR); + this.renderer.plugins.graphics.render(graphics); + + this._useCurrent(); + } + + /** + * Removes the last mask from the stencil stack. @alvin + */ + popStencil() + { + this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); + + const gl = this.renderer.gl; + const graphics = this.stencilMaskStack.pop(); + + if (this.stencilMaskStack.length === 0) + { + // the stack is empty! + gl.disable(gl.STENCIL_TEST); + gl.clear(gl.STENCIL_BUFFER_BIT); + gl.clearStencil(0); + } + else + { + // Decrement the refference stencil value where the popped mask overlaps with the other ones + gl.colorMask(false, false, false, false); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.DECR); + this.renderer.plugins.graphics.render(graphics); + + this._useCurrent(); + } + } + + /** + * Setup renderer to use the current stencil data. + */ + _useCurrent() + { + const gl = this.renderer.gl; + + gl.colorMask(true, true, true, true); + gl.stencilFunc(gl.EQUAL, this.stencilMaskStack.length, this._getBitwiseMask()); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP); + } + + /** + * Fill 1s equal to the number of acitve stencil masks. + * + * @return {number} The bitwise mask. + */ + _getBitwiseMask() + { + return (1 << this.stencilMaskStack.length) - 1; + } + + /** + * Destroys the mask stack. + * + */ + destroy() + { + super.destroy(this); + + this.stencilMaskStack = null; + } +} diff --git a/packages/core/src/projection/ProjectionSystem.js b/packages/core/src/projection/ProjectionSystem.js new file mode 100644 index 0000000..6e63b73 --- /dev/null +++ b/packages/core/src/projection/ProjectionSystem.js @@ -0,0 +1,73 @@ +import System from '../System'; +import { Matrix } from '@pixi/math'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class ProjectionSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.projectionMatrix = new Matrix(); + } + + update(destinationFrame, sourceFrame, resolution, root) + { + this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; + this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; + + this.calculateProjection(this.destinationFrame, this.sourceFrame, resolution, root); + + this.renderer.globalUniforms.uniforms.projectionMatrix = this.projectionMatrix; + this.renderer.globalUniforms.update(); + } + + /** + * Updates the projection matrix based on a projection frame (which is a rectangle) + * + * @param {Rectangle} destinationFrame - The destination frame. + * @param {Rectangle} sourceFrame - The source frame. + */ + calculateProjection(destinationFrame, sourceFrame, resolution, root) + { + const pm = this.projectionMatrix; + + // I don't think we will need this line.. + // pm.identity(); + + if (!root) + { + pm.a = (1 / destinationFrame.width * 2) * resolution; + pm.d = (1 / destinationFrame.height * 2) * resolution; + + pm.tx = -1 - (sourceFrame.x * pm.a); + pm.ty = -1 - (sourceFrame.y * pm.d); + } + else + { + pm.a = (1 / destinationFrame.width * 2) * resolution; + pm.d = (-1 / destinationFrame.height * 2) * resolution; + + pm.tx = -1 - (sourceFrame.x * pm.a); + pm.ty = 1 - (sourceFrame.y * pm.d); + } + } + + /** + * Sets the transform of the active render target to the given matrix + * + * @param {PIXI.Matrix} matrix - The transformation matrix + */ + setTransform()// matrix) + { + // this._activeRenderTarget.transform = matrix; + } +} diff --git a/packages/core/src/renderTexture/BaseRenderTexture.js b/packages/core/src/renderTexture/BaseRenderTexture.js new file mode 100644 index 0000000..88eb83b --- /dev/null +++ b/packages/core/src/renderTexture/BaseRenderTexture.js @@ -0,0 +1,137 @@ +import BaseTexture from '../textures/BaseTexture'; +import FrameBuffer from '../framebuffer/FrameBuffer'; + +/** + * A BaseRenderTexture is a special texture that allows any PixiJS display object to be rendered to it. + * + * __Hint__: All DisplayObjects (i.e. Sprites) that render to a BaseRenderTexture should be preloaded + * otherwise black rectangles will be drawn instead. + * + * A BaseRenderTexture takes a snapshot of any Display Object given to its render method. The position + * and rotation of the given Display Objects is ignored. For example: + * + * ```js + * let renderer = PIXI.autoDetectRenderer(1024, 1024, { view: canvas, ratio: 1 }); + * let baseRenderTexture = new PIXI.BaseRenderTexture(renderer, 800, 600); + * let sprite = PIXI.Sprite.fromImage("spinObj_01.png"); + * + * sprite.position.x = 800/2; + * sprite.position.y = 600/2; + * sprite.anchor.x = 0.5; + * sprite.anchor.y = 0.5; + * + * baseRenderTexture.render(sprite); + * ``` + * + * The Sprite in this case will be rendered using its local transform. To render this sprite at 0,0 + * you can clear the transform + * + * ```js + * + * sprite.setTransform() + * + * let baseRenderTexture = new PIXI.BaseRenderTexture(100, 100); + * let renderTexture = new PIXI.RenderTexture(baseRenderTexture); + * + * renderer.render(sprite, renderTexture); // Renders to center of RenderTexture + * ``` + * + * @class + * @extends PIXI.BaseTexture + * @memberof PIXI + */ +export default class BaseRenderTexture extends BaseTexture +{ + /** + * @param {object} [options] + * @param {number} [options.width=100] - The width of the base render texture + * @param {number} [options.height=100] - The height of the base render texture + * @param {PIXI.SCALE_MODES} [options.scaleMode] - See {@link PIXI.SCALE_MODES} for possible values + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the texture being generated + */ + constructor(options) + { + if (typeof options === 'number') + { + /* eslint-disable prefer-rest-params */ + // Backward compatibility of signature + const width = arguments[0]; + const height = arguments[1]; + const scaleMode = arguments[2]; + const resolution = arguments[3]; + + options = { width, height, scaleMode, resolution }; + /* eslint-enable prefer-rest-params */ + } + + super(null, options); + + const { width, height } = options || {}; + + // Set defaults + this.mipmap = false; + this.width = Math.ceil(width) || 100; + this.height = Math.ceil(height) || 100; + this.valid = true; + + /** + * A map of renderer IDs to webgl renderTargets + * + * @private + * @member {object} + */ + // this._glRenderTargets = {}; + + /** + * A reference to the canvas render target (we only need one as this can be shared across renderers) + * + * @private + * @member {object} + */ + this._canvasRenderTarget = null; + + this.clearColor = [0, 0, 0, 0]; + + this.frameBuffer = new FrameBuffer(width * this.resolution, height * this.resolution) + .addColorTexture(0, this); + + // TODO - could this be added the systems? + + /** + * The data structure for the stencil masks + * + * @member {PIXI.Graphics[]} + */ + this.stencilMaskStack = []; + + /** + * The data structure for the filters + * + * @member {PIXI.Graphics[]} + */ + this.filterStack = [{}]; + } + + /** + * Resizes the BaseRenderTexture. + * + * @param {number} width - The width to resize to. + * @param {number} height - The height to resize to. + */ + resize(width, height) + { + width = Math.ceil(width); + height = Math.ceil(height); + this.frameBuffer.resize(width * this.resolution, height * this.resolution); + } + + /** + * Destroys this texture + * + */ + destroy() + { + super.destroy(true); + this.renderer = null; + } +} diff --git a/packages/core/src/renderTexture/RenderTexture.js b/packages/core/src/renderTexture/RenderTexture.js new file mode 100644 index 0000000..f1ecf1f --- /dev/null +++ b/packages/core/src/renderTexture/RenderTexture.js @@ -0,0 +1,153 @@ +import BaseRenderTexture from './BaseRenderTexture'; +import Texture from '../textures/Texture'; + +/** + * A RenderTexture is a special texture that allows any PixiJS display object to be rendered to it. + * + * __Hint__: All DisplayObjects (i.e. Sprites) that render to a RenderTexture should be preloaded + * otherwise black rectangles will be drawn instead. + * + * A RenderTexture takes a snapshot of any Display Object given to its render method. For example: + * + * ```js + * let renderer = PIXI.autoDetectRenderer(1024, 1024, { view: canvas, ratio: 1 }); + * let renderTexture = PIXI.RenderTexture.create(800, 600); + * let sprite = PIXI.Sprite.fromImage("spinObj_01.png"); + * + * sprite.position.x = 800/2; + * sprite.position.y = 600/2; + * sprite.anchor.x = 0.5; + * sprite.anchor.y = 0.5; + * + * renderer.render(sprite, renderTexture); + * ``` + * + * The Sprite in this case will be rendered using its local transform. To render this sprite at 0,0 + * you can clear the transform + * + * ```js + * + * sprite.setTransform() + * + * let renderTexture = new PIXI.RenderTexture.create(100, 100); + * + * renderer.render(sprite, renderTexture); // Renders to center of RenderTexture + * ``` + * + * @class + * @extends PIXI.Texture + * @memberof PIXI + */ +export default class RenderTexture extends Texture +{ + /** + * @param {PIXI.BaseRenderTexture} baseRenderTexture - The renderer used for this RenderTexture + * @param {PIXI.Rectangle} [frame] - The rectangle frame of the texture to show + */ + constructor(baseRenderTexture, frame) + { + // support for legacy.. + let _legacyRenderer = null; + + if (!(baseRenderTexture instanceof BaseRenderTexture)) + { + /* eslint-disable prefer-rest-params, no-console */ + const width = arguments[1]; + const height = arguments[2]; + const scaleMode = arguments[3]; + const resolution = arguments[4]; + + // we have an old render texture.. + console.warn(`Please use RenderTexture.create(${width}, ${height}) instead of the ctor directly.`); + _legacyRenderer = arguments[0]; + /* eslint-enable prefer-rest-params, no-console */ + + frame = null; + baseRenderTexture = new BaseRenderTexture({ + width, + height, + scaleMode, + resolution, + }); + } + + /** + * The base texture object that this texture uses + * + * @member {BaseTexture} + */ + super(baseRenderTexture, frame); + + this.legacyRenderer = _legacyRenderer; + + /** + * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. + * + * @member {boolean} + */ + this.valid = true; + + /** + * FilterSystem temporary storage + * @private + * @member {PIXI.Rectangle} + */ + this.filterFrame = null; + + this._updateUvs(); + } + + /** + * Resizes the RenderTexture. + * + * @param {number} width - The width to resize to. + * @param {number} height - The height to resize to. + * @param {boolean} [resizeBaseTexture=true] - Should the baseTexture.width and height values be resized as well? + */ + resize(width, height, resizeBaseTexture = true) + { + width = Math.ceil(width); + height = Math.ceil(height); + + // TODO - could be not required.. + this.valid = (width > 0 && height > 0); + + this._frame.width = this.orig.width = width; + this._frame.height = this.orig.height = height; + + if (resizeBaseTexture) + { + this.baseTexture.resize(width, height); + } + + this._updateUvs(); + } + + /** + * A short hand way of creating a render texture. + * + * @param {object} [options] - Options + * @param {number} [options.width=100] - The width of the render texture + * @param {number} [options.height=100] - The height of the render texture + * @param {number} [options.scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the texture being generated + * @return {PIXI.RenderTexture} The new render texture + */ + static create(options) + { + // fallback, old-style: create(width, height, scaleMode, resolution) + if (typeof options === 'number') + { + /* eslint-disable prefer-rest-params */ + options = { + width: options, + height: arguments[1], + scaleMode: arguments[2], + resolution: arguments[3], + }; + /* eslint-enable prefer-rest-params */ + } + + return new RenderTexture(new BaseRenderTexture(options)); + } +} diff --git a/packages/core/src/renderTexture/RenderTextureSystem.js b/packages/core/src/renderTexture/RenderTextureSystem.js new file mode 100644 index 0000000..8d0ca61 --- /dev/null +++ b/packages/core/src/renderTexture/RenderTextureSystem.js @@ -0,0 +1,115 @@ +import System from '../System'; +import { Rectangle } from '@pixi/math'; + +const tempRect = new Rectangle(); + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class RenderTextureSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.clearColor = renderer._backgroundColorRgba; + + // TODO moe this property somewhere else! + this.defaultMaskStack = []; + this.defaultFilterStack = [{}]; + + // empty render texture? + this.renderTexture = null; + + this.destinationFrame = new Rectangle(); + } + + bind(renderTexture, sourceFrame, destinationFrame) + { + // TODO - do we want this?? + if (this.renderTexture === renderTexture) return; + this.renderTexture = renderTexture; + + const renderer = this.renderer; + + if (renderTexture) + { + const baseTexture = renderTexture.baseTexture; + + if (!destinationFrame) + { + tempRect.width = baseTexture.realWidth; + tempRect.height = baseTexture.realHeight; + + destinationFrame = tempRect; + } + + if (!sourceFrame) + { + sourceFrame = destinationFrame; + } + + this.renderer.framebuffer.bind(baseTexture.frameBuffer, destinationFrame); + + this.renderer.projection.update(destinationFrame, sourceFrame, baseTexture.resolution, false); + this.renderer.stencil.setMaskStack(baseTexture.stencilMaskStack); + } + else + { + // TODO these validation checks happen deeper down.. + // thing they can be avoided.. + if (!destinationFrame) + { + tempRect.width = renderer.width; + tempRect.height = renderer.height; + + destinationFrame = tempRect; + } + + if (!sourceFrame) + { + sourceFrame = destinationFrame; + } + + renderer.framebuffer.bind(null, destinationFrame); + + // TODO store this.. + this.renderer.projection.update(destinationFrame, sourceFrame, this.renderer.resolution, true); + this.renderer.stencil.setMaskStack(this.defaultMaskStack); + } + + this.destinationFrame.copyFrom(destinationFrame); + } + + /** + * Erases the render texture and fills the drawing area with a colour + * + * @param {number} [clearColor] - The colour + * @return {PIXI.Renderer} Returns itself. + */ + clear(clearColor) + { + if (this.renderTexture) + { + clearColor = clearColor || this.renderTexture.baseTexture.clearColor; + } + else + { + clearColor = clearColor || this.clearColor; + } + + this.renderer.framebuffer.clear(clearColor[0], clearColor[1], clearColor[2], clearColor[3]); + } + + resize()// screenWidth, screenHeight) + { + // resize the root only! + this.bind(null); + } +} diff --git a/packages/core/src/renderers/AbstractRenderer.js b/packages/core/src/renderers/AbstractRenderer.js deleted file mode 100644 index 928670f..0000000 --- a/packages/core/src/renderers/AbstractRenderer.js +++ /dev/null @@ -1,353 +0,0 @@ -import { hex2string, hex2rgb } from '@pixi/utils'; -import { Matrix, Rectangle } from '@pixi/math'; -import { RENDERER_TYPE } from '@pixi/constants'; -import { settings } from '@pixi/settings'; -import { Container } from '@pixi/display'; -import RenderTexture from '../textures/RenderTexture'; -import EventEmitter from 'eventemitter3'; - -const tempMatrix = new Matrix(); - -/** - * 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 EventEmitter - * @memberof PIXI - */ -export default class AbstractRenderer extends EventEmitter -{ - // eslint-disable-next-line valid-jsdoc - /** - * @param {string} system - The name of the system this renderer is for. - * @param {object} [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.transparent=false] - If the render view is transparent, default false - * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false - * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) - * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. The - * resolution of the renderer retina would be 2. - * @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 {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when rendering, - * stopping pixel interpolation. - */ - constructor(system, options, arg2, arg3) - { - super(); - - // Support for constructor(system, screenWidth, screenHeight, options) - if (typeof options === 'number') - { - options = Object.assign({ - width: options, - height: arg2 || settings.RENDER_OPTIONS.height, - }, arg3); - } - - // 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 = RENDERER_TYPE.UNKNOWN; - - /** - * Measurements of the screen. (0, 0, screenWidth, screenHeight) - * - * Its safe to use as filterArea or hitArea for 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 1 - */ - this.resolution = options.resolution || settings.RESOLUTION; - - /** - * Whether the render view is transparent - * - * @member {boolean} - */ - this.transparent = options.transparent; - - /** - * Whether css dimensions of canvas view should be resized to screen dimensions automatically - * - * @member {boolean} - */ - this.autoResize = options.autoResize || false; - - /** - * Tracks the blend modes useful for this renderer. - * - * @member {object} - */ - this.blendModes = null; - - /** - * 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; - - /** - * If true PixiJS will Math.floor() x/y values when rendering, stopping pixel interpolation. - * Handy for crisp pixel art and speed on legacy devices. - * - * @member {boolean} - */ - this.roundPixels = options.roundPixels; - - /** - * The background color as a number. - * - * @member {number} - * @private - */ - this._backgroundColor = 0x000000; - - /** - * The background color as an [R, G, B] array. - * - * @member {number[]} - * @private - */ - this._backgroundColorRgba = [0, 0, 0, 0]; - - /** - * The background color as a string. - * - * @member {string} - * @private - */ - this._backgroundColorString = '#000000'; - - this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter - - /** - * This temporary display object used as the parent of the currently being rendered item - * - * @member {PIXI.DisplayObject} - * @private - */ - this._tempDisplayObjectParent = new Container(); - - /** - * The last root object that the renderer tried to render. - * - * @member {PIXI.DisplayObject} - * @private - */ - this._lastObjectRendered = this._tempDisplayObjectParent; - - /** - * Collection of plugins. - * @readonly - * @member {object} - */ - this.plugins = {}; - } - - /** - * Initialize the plugins. - * - * @protected - * @param {object} staticMap - The dictionary of staticly saved plugins. - */ - initPlugins(staticMap) - { - 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() - { - return this.view.width; - } - - /** - * Same as view.height, actual number of pixels in the canvas by vertical - * - * @member {number} - * @readonly - * @default 600 - */ - get height() - { - return this.view.height; - } - - /** - * Resizes the screen and canvas to the specified width and height - * Canvas dimensions are multiplied by resolution - * - * @param {number} screenWidth - the new width of the screen - * @param {number} screenHeight - the new height of the screen - */ - resize(screenWidth, screenHeight) - { - this.screen.width = screenWidth; - this.screen.height = screenHeight; - - this.view.width = screenWidth * this.resolution; - this.view.height = screenHeight * this.resolution; - - if (this.autoResize) - { - this.view.style.width = `${screenWidth}px`; - this.view.style.height = `${screenHeight}px`; - } - } - - /** - * 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 {PIXI.DisplayObject} displayObject - The displayObject the object will be generated from - * @param {number} scaleMode - Should be one of the scaleMode consts - * @param {number} resolution - The resolution / device pixel ratio of the texture being generated - * @param {PIXI.Rectangle} [region] - The region of the displayObject, that shall be rendered, - * if no region is specified, defaults to the local bounds of the displayObject. - * @return {PIXI.Texture} a texture of the graphics object - */ - generateTexture(displayObject, scaleMode, resolution, region) - { - region = region || displayObject.getLocalBounds(); - - // 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(region.width | 0, region.height | 0, scaleMode, resolution); - - tempMatrix.tx = -region.x; - tempMatrix.ty = -region.y; - - this.render(displayObject, renderTexture, false, tempMatrix, true); - - return renderTexture; - } - - /** - * Removes everything from the renderer and optionally removes the Canvas DOM element. - * - * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. - */ - destroy(removeView) - { - 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); - } - - this.plugins = null; - - this.type = RENDERER_TYPE.UNKNOWN; - - this.view = null; - - this.screen = null; - - this.resolution = 0; - - this.transparent = false; - - this.autoResize = false; - - this.blendModes = null; - - this.options = null; - - this.preserveDrawingBuffer = false; - this.clearBeforeRender = false; - - this.roundPixels = false; - - this._backgroundColor = 0; - this._backgroundColorRgba = null; - this._backgroundColorString = null; - - this._tempDisplayObjectParent = null; - this._lastObjectRendered = null; - } - - /** - * The background color to fill if not transparent - * - * @member {number} - */ - get backgroundColor() - { - return this._backgroundColor; - } - - set backgroundColor(value) // eslint-disable-line require-jsdoc - { - this._backgroundColor = value; - this._backgroundColorString = hex2string(value); - hex2rgb(value, this._backgroundColorRgba); - } -} diff --git a/packages/core/src/renderers/Renderer.js b/packages/core/src/renderers/Renderer.js deleted file mode 100644 index a2020b1..0000000 --- a/packages/core/src/renderers/Renderer.js +++ /dev/null @@ -1,329 +0,0 @@ -import AbstractRenderer from './AbstractRenderer'; -import { sayHello } from '@pixi/utils'; -import MaskSystem from './systems/MaskSystem'; -import StencilSystem from './systems/StencilSystem'; -import FilterSystem from './systems/filter/FilterSystem'; -import FramebufferSystem from './systems/FramebufferSystem'; -import RenderTextureSystem from './systems/RenderTextureSystem'; -import TextureSystem from './systems/textures/TextureSystem'; -import ProjectionSystem from './systems/ProjectionSystem'; -import StateSystem from './systems/StateSystem'; -import GeometrySystem from './systems/geometry/GeometrySystem'; -import ShaderSystem from './systems/shader/ShaderSystem'; -import ContextSystem from './systems/ContextSystem'; -import BatchSystem from './systems/BatchSystem'; -import TextureGCSystem from './systems/textures/TextureGCSystem'; -import { RENDERER_TYPE } from '@pixi/constants'; -import UniformGroup from '../shader/UniformGroup'; -import { Matrix } from '@pixi/math'; -import Runner from 'mini-runner'; - -/** - * 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 Render works by automatically managing webGLBatchs. - * 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 :) - * - * @class - * @memberof PIXI - * @extends PIXI.AbstractRenderer - */ -export default class Renderer extends AbstractRenderer -{ - // eslint-disable-next-line valid-jsdoc - /** - * - * @param {object} [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.transparent=false] - If the render view is transparent, default false - * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false - * @param {boolean} [options.antialias=false] - sets antialias. If not available natively then FXAA - * antialiasing is used - * @param {boolean} [options.forceFXAA=false] - forces FXAA antialiasing to be used over native. - * FXAA is faster, but may not always look as great - * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. - * The resolution of the renderer retina would be 2. - * @param {boolean} [options.clearBeforeRender=true] - This sets if the renderer will clear - * the canvas or not before the new render pass. If you wish to set this to false, you *must* set - * preserveDrawingBuffer to `true`. - * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, - * enable this if you need to call toDataUrl on the webgl context. - * @param {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when - * rendering, stopping pixel interpolation. - * @param {number} [options.backgroundColor=0x000000] - The background color of the rendered area - * (shown if not transparent). - * @param {boolean} [options.legacy=false] - If true PixiJS will aim to ensure compatibility - * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. - * @param {string} [options.powerPreference] - Parameter passed to webgl context, set to "high-performance" - * for devices with dual graphics card - */ - constructor(options, arg2, arg3) - { - super('WebGL', options, arg2, arg3); - - /** - * The type of this renderer as a standardised const - * - * @member {number} - * @see PIXI.RENDERER_TYPE - */ - this.type = RENDERER_TYPE.WEBGL; - - // this will be set by the contextSystem (this.context) - this.gl = null; - this.CONTEXT_UID = 0; - this.legacy = !!options.legacy; - - // TODO legacy! - - // runners! - this.runners = { - destroy: new Runner('destroy'), - contextChange: new Runner('contextChange', 1), - reset: new Runner('reset'), - update: new Runner('update'), - postrender: new Runner('postrender'), - prerender: new Runner('prerender'), - resize: new Runner('resize', 2), - }; - - 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(GeometrySystem, 'geometry') - .addSystem(FramebufferSystem, 'framebuffer') - .addSystem(StencilSystem, 'stencil') - .addSystem(ProjectionSystem, 'projection') - .addSystem(TextureGCSystem, 'textureGC') - .addSystem(FilterSystem, 'filter') - .addSystem(RenderTextureSystem, 'renderTexture') - .addSystem(BatchSystem, 'batch'); - - this.initPlugins(Renderer.__plugins); - - /** - * The options passed in to create a new webgl context. - * - * @member {object} - * @private - */ - if (options.context) - { - this.context.initFromContext(options.context); - } - else - { - this.context.initFromOptions({ - alpha: this.transparent, - antialias: options.antialias, - premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', - stencil: true, - 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); - } - - addSystem(_class, name) - { - if (!name) - { - name = _class.name; - } - - // TODO - read name from class.name.. - - /* - if(name.includes('System')) - { - name = name.replace('System', ''); - name = name.charAt(0).toLowerCase() + name.slice(1); - } - */ - - const system = new _class(this); - - if (this[name]) - { - throw new Error(`Whoops! ${name} is already a manger`); - } - - this[name] = system; - - for (const i in this.runners) - { - this.runners[i].add(system); - } - - return this; - - /** - * 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. - */ - } - - /** - * Renders the object to its webGL view - * - * @param {PIXI.DisplayObject} displayObject - the object to be rendered - * @param {PIXI.RenderTexture} renderTexture - The render texture to render to. - * @param {boolean} [clear] - Should the canvas be cleared before the new render - * @param {PIXI.Transform} [transform] - A transform to apply to the render texture before rendering. - * @param {boolean} [skipUpdateTransform] - Should we skip the update transform pass? - */ - render(displayObject, renderTexture, clear, transform, skipUpdateTransform) - { - // can be handy to know! - this.renderingToScreen = !renderTexture; - - this.runners.prerender.run(); - this.emit('prerender'); - - // 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.parent; - - displayObject.parent = this._tempDisplayObjectParent; - displayObject.updateTransform(); - displayObject.parent = 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.run(); - - this.emit('postrender'); - } - - /** - * Resizes the webGL view to the specified width and height. - * - * @param {number} screenWidth - the new width of the screen - * @param {number} screenHeight - the new height of the screen - */ - resize(screenWidth, screenHeight) - { - AbstractRenderer.prototype.resize.call(this, screenWidth, screenHeight); - - this.runners.resize.run(screenWidth, screenHeight); - } - - /** - * Resets the WebGL state so you can render things however you fancy! - * - * @return {PIXI.Renderer} Returns itself. - */ - reset() - { - this.runners.reset.run(); - - return this; - } - - clear() - { - this.framebuffer.bind(); - this.framebuffer.clear(); - } - - /** - * Removes everything from the renderer (event listeners, spritebatch, etc...) - * - * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. - * See: https://github.com/pixijs/pixi.js/issues/2233 - */ - destroy(removeView) - { - this.runners.destroy.run(); - - // call base destroy - super.destroy(removeView); - - // TODO nullify all the managers.. - this.gl = null; - } - - /** - * 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 - * builds and excluding plugins. - * @name PIXI.Renderer#plugins - * @type {object} - * @readonly - * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. - * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. - * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. - * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. - */ - - /** - * Adds a plugin to the renderer. - * - * @method - * @param {string} pluginName - The name of the plugin. - * @param {Function} ctor - The constructor function or class for the plugin. - */ - static registerPlugin(pluginName, ctor) - { - Renderer.__plugins = Renderer.__plugins || {}; - Renderer.__plugins[pluginName] = ctor; - } -} diff --git a/packages/core/src/renderers/State.js b/packages/core/src/renderers/State.js deleted file mode 100644 index fa3f221..0000000 --- a/packages/core/src/renderers/State.js +++ /dev/null @@ -1,163 +0,0 @@ -/* eslint-disable max-len */ - -const BLEND = 0; -const OFFSET = 1; -const CULLING = 2; -const DEPTH_TEST = 3; -const WINDING = 4; - -/** - * This is a webGL state. It is passed The WebGL StateManager. - * Each mesh renderered may require webGL to be in a different state. - * For example you may want different blend mode or to enable polygon offsets - * - * @class - * @memberof PIXI - */ -export default class State -{ - /** - * - */ - constructor() - { - this.data = 0; - - this.blendMode = 0; - this.polygonOffset = 0; - - this.blend = true; - // this.depthTest = true; - } - - /** - * Activates blending of the computed fragment color values - * - * @member {boolean} - */ - get blend() - { - return !!(this.data & (1 << BLEND)); - } - - set blend(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << BLEND)) !== value) - { - this.data ^= (1 << BLEND); - } - } - - /** - * Activates adding an offset to depth values of polygon's fragments - * - * @member {boolean} - * @default false - */ - get offsets() - { - return !!(this.data & (1 << OFFSET)); - } - - set offsets(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << OFFSET)) !== value) - { - this.data ^= (1 << OFFSET); - } - } - - /** - * Activates culling of polygons. - * - * @member {boolean} - * @default false - */ - get culling() - { - return !!(this.data & (1 << CULLING)); - } - - set culling(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << CULLING)) !== value) - { - this.data ^= (1 << CULLING); - } - } - - /** - * Activates depth comparisons and updates to the depth buffer. - * - * @member {boolean} - * @default false - */ - get depthTest() - { - return !!(this.data & (1 << DEPTH_TEST)); - } - - set depthTest(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << DEPTH_TEST)) !== value) - { - this.data ^= (1 << DEPTH_TEST); - } - } - - /** - * Specifies whether or not front or back-facing polygons can be culled. - * @member {boolean} - * @default false - */ - get clockwiseFrontFace() - { - return !!(this.data & (1 << WINDING)); - } - - set clockwiseFrontFace(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << WINDING)) !== value) - { - this.data ^= (1 << WINDING); - } - } - - /** - * The blend mode to be applied when this state is set. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. - * Setting this mode to anything other than NO_BLEND will automatically switch blending on. - * - * @member {boolean} - * @default PIXI.BLEND_MODES.NORMAL - * @see PIXI.BLEND_MODES - */ - get blendMode() - { - return this._blendMode; - } - - set blendMode(value) // eslint-disable-line require-jsdoc - { - // 17 is NO BLEND - this.blend = (value !== 17); - this._blendMode = value; - } - - /** - * The polygon offset. Setting this property to anything other than 0 will automatically enable poygon offset fill. - * - * @member {number} - * @default 0 - */ - get polygonOffset() - { - return this._polygonOffset; - } - - set polygonOffset(value) // eslint-disable-line require-jsdoc - { - this.offsets = !!value; - this._polygonOffset = value; - } -} - diff --git a/packages/core/src/renderers/filters/Filter.js b/packages/core/src/renderers/filters/Filter.js deleted file mode 100644 index 2073b8d..0000000 --- a/packages/core/src/renderers/filters/Filter.js +++ /dev/null @@ -1,140 +0,0 @@ -import Shader from '../../shader/Shader'; -import Program from '../../shader/Program'; -import State from '../State'; -import { settings } from '@pixi/settings'; -// import extractUniformsFromSrc from './extractUniformsFromSrc'; -import defaultVertex from './defaultFilter.vert'; -import defaultFragment from './defaultFilter.frag'; - -// let math = require('../../math'); -/** - * @class - * @memberof PIXI - * @extends PIXI.Shader - */ -export default class Filter extends Shader -{ - /** - * @param {string} [vertexSrc] - The source of the vertex shader. - * @param {string} [fragmentSrc] - The source of the fragment shader. - * @param {object} [uniforms] - Custom uniforms to use to augment the built-in ones. - */ - constructor(vertexSrc, fragmentSrc, uniforms) - { - const program = Program.from(vertexSrc, fragmentSrc); - - super(program, uniforms); - - /** - * The padding of the filter. Some filters require extra space to breath such as a blur. - * Increasing this will add extra width and height to the bounds of the object that the - * filter is applied to. - * - * @member {number} - */ - this.padding = 0; - - /** - * The resolution of the filter. Setting this to be lower will lower the quality but - * increase the performance of the filter. - * - * @member {number} - */ - this.resolution = settings.RESOLUTION; - - /** - * If enabled is true the filter is applied, if false it will not. - * - * @member {boolean} - */ - this.enabled = true; - - /** - * If enabled, PixiJS will fit the filter area into boundaries for better performance. - * Switch it off if it does not work for specific shader. - * - * @member {boolean} - */ - this.autoFit = true; - - /** - * Legacy filters use position and uvs from attributes - * @member {boolean} - * @readonly - */ - this.legacy = !!this.program.attributeData.aTextureCoord; - - /** - * the webGL state the filter requires to render - * @member {PIXI.State} - */ - this.state = new State(); - } - - /** - * Applies the filter - * - * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from - * @param {PIXI.RenderTarget} input - The input render target. - * @param {PIXI.RenderTarget} output - The target to output to. - * @param {boolean} clear - Should the output be cleared before rendering to it - * @param {object} [currentState] - It's current state of filter. - * There are some useful properties in the currentState : - * target, filters, sourceFrame, destinationFrame, renderTarget, resolution - */ - apply(filterManager, input, output, clear, currentState, derp) // eslint-disable-line no-unused-vars - { - // do as you please! - - filterManager.applyFilter(this, input, output, clear, currentState, derp); - - // or just do a regular render.. - } - - /** - * Sets the blendmode of the filter - * - * @member {number} - * @default PIXI.BLEND_MODES.NORMAL - */ - get blendMode() - { - return this.state.blendMode; - } - - set blendMode(value) // eslint-disable-line require-jsdoc - { - this.state.blendMode = value; - } - - /** - * The default vertex shader source - * - * @static - * @constant - */ - static get defaultVertexSrc() - { - return defaultVertex; - } - - /** - * The default fragment shader source - * - * @static - * @constant - */ - static get defaultFragmentSrc() - { - return defaultFragment; - } -} - -/** - * Used for caching shader IDs - * - * @static - * @private - */ -Filter.SOURCE_KEY_MAP = {}; - diff --git a/packages/core/src/renderers/filters/defaultFilter.frag b/packages/core/src/renderers/filters/defaultFilter.frag deleted file mode 100644 index a14c1be..0000000 --- a/packages/core/src/renderers/filters/defaultFilter.frag +++ /dev/null @@ -1,22 +0,0 @@ -varying vec2 vTextureCoord; -varying vec2 vFilterCoord; - -uniform sampler2D uSampler; -uniform sampler2D filterSampler; - -void main(void){ - vec4 masky = texture2D(filterSampler, vFilterCoord); - vec4 sample = texture2D(uSampler, vTextureCoord); - vec4 color; - if(mod(vFilterCoord.x, 1.0) > 0.5) - { - color = vec4(1.0, 0.0, 0.0, 1.0); - } - else - { - color = vec4(0.0, 1.0, 0.0, 1.0); - } - // gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0); - gl_FragColor = mix(sample, masky, 0.5); - gl_FragColor *= sample.a; -} diff --git a/packages/core/src/renderers/filters/defaultFilter.vert b/packages/core/src/renderers/filters/defaultFilter.vert deleted file mode 100644 index e1febca..0000000 --- a/packages/core/src/renderers/filters/defaultFilter.vert +++ /dev/null @@ -1,14 +0,0 @@ -attribute vec2 aVertexPosition; -attribute vec2 aTextureCoord; - -uniform mat3 projectionMatrix; -uniform mat3 filterMatrix; - -varying vec2 vTextureCoord; -varying vec2 vFilterCoord; - -void main(void){ - gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); - vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy; - vTextureCoord = aTextureCoord ; -} diff --git a/packages/core/src/renderers/filters/filterTransforms.js b/packages/core/src/renderers/filters/filterTransforms.js deleted file mode 100644 index 87a3cd6..0000000 --- a/packages/core/src/renderers/filters/filterTransforms.js +++ /dev/null @@ -1,50 +0,0 @@ -import { Matrix } from '@pixi/math'; - -/** - * Calculates the mapped matrix - * @param filterArea {Rectangle} The filter area - * @param sprite {Sprite} the target sprite - * @param outputMatrix {Matrix} @alvin - * @private - */ -// this returns a matrix that will normalise map filter cords in the filter to screen space -export function calculateScreenSpaceMatrix(outputMatrix, filterArea, textureSize) -{ - // TODO unwrap? - const mappedMatrix = outputMatrix.identity(); - - mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); - - mappedMatrix.scale(textureSize.width, textureSize.height); - - return mappedMatrix; -} - -export function calculateNormalizedScreenSpaceMatrix(outputMatrix, filterArea, textureSize) -{ - const mappedMatrix = outputMatrix.identity(); - - mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); - - const translateScaleX = (textureSize.width / filterArea.width); - const translateScaleY = (textureSize.height / filterArea.height); - - mappedMatrix.scale(translateScaleX, translateScaleY); - - return mappedMatrix; -} - -// this will map the filter coord so that a texture can be used based on the transform of a sprite -export function calculateSpriteMatrix(outputMatrix, filterArea, textureSize, sprite) -{ - const orig = sprite._texture.orig; - const mappedMatrix = outputMatrix.set(textureSize.width, 0, 0, textureSize.height, filterArea.x, filterArea.y); - const worldTransform = sprite.worldTransform.copyTo(Matrix.TEMP_MATRIX); - - worldTransform.invert(); - mappedMatrix.prepend(worldTransform); - mappedMatrix.scale(1.0 / orig.width, 1.0 / orig.height); - mappedMatrix.translate(sprite.anchor.x, sprite.anchor.y); - - return mappedMatrix; -} diff --git a/packages/core/src/renderers/filters/spriteMask/SpriteMaskFilter.js b/packages/core/src/renderers/filters/spriteMask/SpriteMaskFilter.js deleted file mode 100644 index d5a91a4..0000000 --- a/packages/core/src/renderers/filters/spriteMask/SpriteMaskFilter.js +++ /dev/null @@ -1,63 +0,0 @@ -import Filter from '../Filter'; -import { Matrix } from '@pixi/math'; -import vertex from './spriteMaskFilter.vert'; -import fragment from './spriteMaskFilter.frag'; -import { default as TextureMatrix } from '../../../textures/TextureMatrix'; - -/** - * The SpriteMaskFilter class - * - * @class - * @extends PIXI.Filter - * @memberof PIXI - */ -export default class SpriteMaskFilter extends Filter -{ - /** - * @param {PIXI.Sprite} sprite - the target sprite - */ - constructor(sprite) - { - const maskMatrix = new Matrix(); - - super(vertex, fragment); - - sprite.renderable = false; - - this.maskSprite = sprite; - this.maskMatrix = maskMatrix; - } - - /** - * Applies the filter - * - * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from - * @param {PIXI.RenderTarget} input - The input render target. - * @param {PIXI.RenderTarget} output - The target to output to. - */ - apply(filterManager, input, output) - { - const maskSprite = this.maskSprite; - const tex = this.maskSprite.texture; - - if (!tex.valid) - { - return; - } - if (!tex.transform) - { - // margin = 0.0, let it bleed a bit, shader code becomes easier - // assuming that atlas textures were made with 1-pixel padding - tex.transform = new TextureMatrix(tex, 0.0); - } - tex.transform.update(); - - this.uniforms.mask = tex; - this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite) - .prepend(tex.transform.mapCoord); - this.uniforms.alpha = maskSprite.worldAlpha; - this.uniforms.maskClamp = tex.transform.uClampFrame; - - filterManager.applyFilter(this, input, output); - } -} diff --git a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.frag b/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.frag deleted file mode 100644 index 0e4aef8..0000000 --- a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.frag +++ /dev/null @@ -1,23 +0,0 @@ -varying vec2 vMaskCoord; -varying vec2 vTextureCoord; - -uniform sampler2D uSampler; -uniform sampler2D mask; -uniform float alpha; -uniform vec4 maskClamp; - -void main(void) -{ - float clip = step(3.5, - step(maskClamp.x, vMaskCoord.x) + - step(maskClamp.y, vMaskCoord.y) + - step(vMaskCoord.x, maskClamp.z) + - step(vMaskCoord.y, maskClamp.w)); - - vec4 original = texture2D(uSampler, vTextureCoord); - vec4 masky = texture2D(mask, vMaskCoord); - - original *= (masky.r * masky.a * alpha * clip); - - gl_FragColor = original; -} diff --git a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.vert b/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.vert deleted file mode 100644 index d23a30c..0000000 --- a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.vert +++ /dev/null @@ -1,16 +0,0 @@ -attribute vec2 aVertexPosition; -attribute vec2 aTextureCoord; - -uniform mat3 projectionMatrix; -uniform mat3 otherMatrix; - -varying vec2 vMaskCoord; -varying vec2 vTextureCoord; - -void main(void) -{ - gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); - - vTextureCoord = aTextureCoord; - vMaskCoord = ( otherMatrix * vec3( aTextureCoord, 1.0) ).xy; -} diff --git a/packages/core/src/renderers/systems/BatchSystem.js b/packages/core/src/renderers/systems/BatchSystem.js deleted file mode 100644 index 70e9518..0000000 --- a/packages/core/src/renderers/systems/BatchSystem.js +++ /dev/null @@ -1,68 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import ObjectRenderer from '../utils/ObjectRenderer'; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ - -export default class BatchSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this manager works for. - */ - constructor(renderer) - { - super(renderer); - - /** - * An empty renderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.emptyRenderer = new ObjectRenderer(renderer); - - /** - * The currently active ObjectRenderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.currentRenderer = this.emptyRenderer; - } - - /** - * Changes the current renderer to the one given in parameter - * - * @param {PIXI.ObjectRenderer} objectRenderer - The object renderer to use. - */ - setObjectRenderer(objectRenderer) - { - if (this.currentRenderer === objectRenderer) - { - return; - } - - this.currentRenderer.stop(); - this.currentRenderer = objectRenderer; - - this.renderer.state.setState(objectRenderer.state); - - this.currentRenderer.start(); - } - - /** - * This should be called if you wish to do some custom rendering - * It will basically render anything that may be batched up such as sprites - * - */ - flush() - { - this.setObjectRenderer(this.emptyRenderer); - } - - reset() - { - this.setObjectRenderer(this.emptyRenderer); - } -} diff --git a/packages/core/src/AbstractRenderer.js b/packages/core/src/AbstractRenderer.js new file mode 100644 index 0000000..64b2eb1 --- /dev/null +++ b/packages/core/src/AbstractRenderer.js @@ -0,0 +1,353 @@ +import { hex2string, hex2rgb } from '@pixi/utils'; +import { Matrix, Rectangle } from '@pixi/math'; +import { RENDERER_TYPE } from '@pixi/constants'; +import { settings } from '@pixi/settings'; +import { Container } from '@pixi/display'; +import RenderTexture from './renderTexture/RenderTexture'; +import EventEmitter from 'eventemitter3'; + +const tempMatrix = new Matrix(); + +/** + * 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 EventEmitter + * @memberof PIXI + */ +export default class AbstractRenderer extends EventEmitter +{ + // eslint-disable-next-line valid-jsdoc + /** + * @param {string} system - The name of the system this renderer is for. + * @param {object} [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.transparent=false] - If the render view is transparent, default false + * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false + * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. The + * resolution of the renderer retina would be 2. + * @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 {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when rendering, + * stopping pixel interpolation. + */ + constructor(system, options, arg2, arg3) + { + super(); + + // Support for constructor(system, screenWidth, screenHeight, options) + if (typeof options === 'number') + { + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + }, arg3); + } + + // 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 = RENDERER_TYPE.UNKNOWN; + + /** + * Measurements of the screen. (0, 0, screenWidth, screenHeight) + * + * Its safe to use as filterArea or hitArea for 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 1 + */ + this.resolution = options.resolution || settings.RESOLUTION; + + /** + * Whether the render view is transparent + * + * @member {boolean} + */ + this.transparent = options.transparent; + + /** + * Whether css dimensions of canvas view should be resized to screen dimensions automatically + * + * @member {boolean} + */ + this.autoResize = options.autoResize || false; + + /** + * Tracks the blend modes useful for this renderer. + * + * @member {object} + */ + this.blendModes = null; + + /** + * 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; + + /** + * If true PixiJS will Math.floor() x/y values when rendering, stopping pixel interpolation. + * Handy for crisp pixel art and speed on legacy devices. + * + * @member {boolean} + */ + this.roundPixels = options.roundPixels; + + /** + * The background color as a number. + * + * @member {number} + * @private + */ + this._backgroundColor = 0x000000; + + /** + * The background color as an [R, G, B] array. + * + * @member {number[]} + * @private + */ + this._backgroundColorRgba = [0, 0, 0, 0]; + + /** + * The background color as a string. + * + * @member {string} + * @private + */ + this._backgroundColorString = '#000000'; + + this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter + + /** + * This temporary display object used as the parent of the currently being rendered item + * + * @member {PIXI.DisplayObject} + * @private + */ + this._tempDisplayObjectParent = new Container(); + + /** + * The last root object that the renderer tried to render. + * + * @member {PIXI.DisplayObject} + * @private + */ + this._lastObjectRendered = this._tempDisplayObjectParent; + + /** + * Collection of plugins. + * @readonly + * @member {object} + */ + this.plugins = {}; + } + + /** + * Initialize the plugins. + * + * @protected + * @param {object} staticMap - The dictionary of staticly saved plugins. + */ + initPlugins(staticMap) + { + 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() + { + return this.view.width; + } + + /** + * Same as view.height, actual number of pixels in the canvas by vertical + * + * @member {number} + * @readonly + * @default 600 + */ + get height() + { + return this.view.height; + } + + /** + * Resizes the screen and canvas to the specified width and height + * Canvas dimensions are multiplied by resolution + * + * @param {number} screenWidth - the new width of the screen + * @param {number} screenHeight - the new height of the screen + */ + resize(screenWidth, screenHeight) + { + this.screen.width = screenWidth; + this.screen.height = screenHeight; + + this.view.width = screenWidth * this.resolution; + this.view.height = screenHeight * this.resolution; + + if (this.autoResize) + { + this.view.style.width = `${screenWidth}px`; + this.view.style.height = `${screenHeight}px`; + } + } + + /** + * 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 {PIXI.DisplayObject} displayObject - The displayObject the object will be generated from + * @param {number} scaleMode - Should be one of the scaleMode consts + * @param {number} resolution - The resolution / device pixel ratio of the texture being generated + * @param {PIXI.Rectangle} [region] - The region of the displayObject, that shall be rendered, + * if no region is specified, defaults to the local bounds of the displayObject. + * @return {PIXI.Texture} a texture of the graphics object + */ + generateTexture(displayObject, scaleMode, resolution, region) + { + region = region || displayObject.getLocalBounds(); + + // 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(region.width | 0, region.height | 0, scaleMode, resolution); + + tempMatrix.tx = -region.x; + tempMatrix.ty = -region.y; + + this.render(displayObject, renderTexture, false, tempMatrix, true); + + return renderTexture; + } + + /** + * Removes everything from the renderer and optionally removes the Canvas DOM element. + * + * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. + */ + destroy(removeView) + { + 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); + } + + this.plugins = null; + + this.type = RENDERER_TYPE.UNKNOWN; + + this.view = null; + + this.screen = null; + + this.resolution = 0; + + this.transparent = false; + + this.autoResize = false; + + this.blendModes = null; + + this.options = null; + + this.preserveDrawingBuffer = false; + this.clearBeforeRender = false; + + this.roundPixels = false; + + this._backgroundColor = 0; + this._backgroundColorRgba = null; + this._backgroundColorString = null; + + this._tempDisplayObjectParent = null; + this._lastObjectRendered = null; + } + + /** + * The background color to fill if not transparent + * + * @member {number} + */ + get backgroundColor() + { + return this._backgroundColor; + } + + set backgroundColor(value) // eslint-disable-line require-jsdoc + { + this._backgroundColor = value; + this._backgroundColorString = hex2string(value); + hex2rgb(value, this._backgroundColorRgba); + } +} diff --git a/packages/core/src/Renderer.js b/packages/core/src/Renderer.js new file mode 100644 index 0000000..2227e78 --- /dev/null +++ b/packages/core/src/Renderer.js @@ -0,0 +1,338 @@ +import AbstractRenderer from './AbstractRenderer'; +import { sayHello } from '@pixi/utils'; +import MaskSystem from './mask/MaskSystem'; +import StencilSystem from './mask/StencilSystem'; +import FilterSystem from './filters/FilterSystem'; +import FramebufferSystem from './framebuffer/FramebufferSystem'; +import RenderTextureSystem from './renderTexture/RenderTextureSystem'; +import TextureSystem from './textures/TextureSystem'; +import ProjectionSystem from './projection/ProjectionSystem'; +import StateSystem from './state/StateSystem'; +import GeometrySystem from './geometry/GeometrySystem'; +import ShaderSystem from './shader/ShaderSystem'; +import ContextSystem from './context/ContextSystem'; +import BatchSystem from './batch/BatchSystem'; +import TextureGCSystem from './textures/TextureGCSystem'; +import { RENDERER_TYPE } from '@pixi/constants'; +import UniformGroup from './shader/UniformGroup'; +import { Matrix } from '@pixi/math'; +import Runner from 'mini-runner'; + +/** + * 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 Render works by automatically managing webGLBatchs. + * 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 :) + * + * @class + * @memberof PIXI + * @extends PIXI.AbstractRenderer + */ +export default class Renderer extends AbstractRenderer +{ + // eslint-disable-next-line valid-jsdoc + /** + * + * @param {object} [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.transparent=false] - If the render view is transparent, default false + * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false + * @param {boolean} [options.antialias=false] - sets antialias. If not available natively then FXAA + * antialiasing is used + * @param {boolean} [options.forceFXAA=false] - forces FXAA antialiasing to be used over native. + * FXAA is faster, but may not always look as great + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. + * The resolution of the renderer retina would be 2. + * @param {boolean} [options.clearBeforeRender=true] - This sets if the renderer will clear + * the canvas or not before the new render pass. If you wish to set this to false, you *must* set + * preserveDrawingBuffer to `true`. + * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, + * enable this if you need to call toDataUrl on the webgl context. + * @param {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when + * rendering, stopping pixel interpolation. + * @param {number} [options.backgroundColor=0x000000] - The background color of the rendered area + * (shown if not transparent). + * @param {boolean} [options.legacy=false] - If true PixiJS will aim to ensure compatibility + * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. + * @param {string} [options.powerPreference] - Parameter passed to webgl context, set to "high-performance" + * for devices with dual graphics card + */ + constructor(options, arg2, arg3) + { + super('WebGL', options, arg2, arg3); + + /** + * The type of this renderer as a standardised const + * + * @member {number} + * @see PIXI.RENDERER_TYPE + */ + this.type = RENDERER_TYPE.WEBGL; + + // this will be set by the contextSystem (this.context) + this.gl = null; + this.CONTEXT_UID = 0; + this.legacy = !!options.legacy; + + // TODO legacy! + + // runners! + this.runners = { + destroy: new Runner('destroy'), + contextChange: new Runner('contextChange', 1), + reset: new Runner('reset'), + update: new Runner('update'), + postrender: new Runner('postrender'), + prerender: new Runner('prerender'), + resize: new Runner('resize', 2), + }; + + 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(GeometrySystem, 'geometry') + .addSystem(FramebufferSystem, 'framebuffer') + .addSystem(StencilSystem, 'stencil') + .addSystem(ProjectionSystem, 'projection') + .addSystem(TextureGCSystem, 'textureGC') + .addSystem(FilterSystem, 'filter') + .addSystem(RenderTextureSystem, 'renderTexture') + .addSystem(BatchSystem, 'batch'); + + this.initPlugins(Renderer.__plugins); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ + if (options.context) + { + this.context.initFromContext(options.context); + } + else + { + this.context.initFromOptions({ + alpha: this.transparent, + antialias: options.antialias, + premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', + stencil: true, + 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); + } + + /** + * Add a new system to the renderer. + * @param {class} ClassRef - Class reference + * @param {string} [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. + * @return {PIXI.Renderer} Return instance of renderer + */ + addSystem(ClassRef, name) + { + if (!name) + { + name = ClassRef.name; + } + + // TODO - read name from class.name.. + + /* + if(name.includes('System')) + { + name = name.replace('System', ''); + name = name.charAt(0).toLowerCase() + name.slice(1); + } + */ + + const system = new ClassRef(this); + + if (this[name]) + { + throw new Error(`Whoops! ${name} is already a manger`); + } + + this[name] = system; + + for (const i in this.runners) + { + this.runners[i].add(system); + } + + return this; + + /** + * 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. + */ + } + + /** + * Renders the object to its webGL view + * + * @param {PIXI.DisplayObject} displayObject - the object to be rendered + * @param {PIXI.RenderTexture} renderTexture - The render texture to render to. + * @param {boolean} [clear] - Should the canvas be cleared before the new render + * @param {PIXI.Transform} [transform] - A transform to apply to the render texture before rendering. + * @param {boolean} [skipUpdateTransform] - Should we skip the update transform pass? + */ + render(displayObject, renderTexture, clear, transform, skipUpdateTransform) + { + // can be handy to know! + this.renderingToScreen = !renderTexture; + + this.runners.prerender.run(); + this.emit('prerender'); + + // 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.parent; + + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = 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.run(); + + this.emit('postrender'); + } + + /** + * Resizes the webGL view to the specified width and height. + * + * @param {number} screenWidth - the new width of the screen + * @param {number} screenHeight - the new height of the screen + */ + resize(screenWidth, screenHeight) + { + AbstractRenderer.prototype.resize.call(this, screenWidth, screenHeight); + + this.runners.resize.run(screenWidth, screenHeight); + } + + /** + * Resets the WebGL state so you can render things however you fancy! + * + * @return {PIXI.Renderer} Returns itself. + */ + reset() + { + this.runners.reset.run(); + + return this; + } + + clear() + { + this.framebuffer.bind(); + this.framebuffer.clear(); + } + + /** + * Removes everything from the renderer (event listeners, spritebatch, etc...) + * + * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. + * See: https://github.com/pixijs/pixi.js/issues/2233 + */ + destroy(removeView) + { + this.runners.destroy.run(); + + // call base destroy + super.destroy(removeView); + + // TODO nullify all the managers.. + this.gl = null; + } + + /** + * 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 + * builds and excluding plugins. + * @name PIXI.Renderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. + */ + + /** + * Adds a plugin to the renderer. + * + * @method + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + static registerPlugin(pluginName, ctor) + { + Renderer.__plugins = Renderer.__plugins || {}; + Renderer.__plugins[pluginName] = ctor; + } +} diff --git a/packages/core/src/System.js b/packages/core/src/System.js new file mode 100644 index 0000000..05a10c8 --- /dev/null +++ b/packages/core/src/System.js @@ -0,0 +1,42 @@ +/** + * System is a base class used for extending systems used by the {@link PIXI.Renderer} + * @see PIXI.Renderer#addSystem + * @class + * @memberof PIXI + */ +export default class System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + /** + * The renderer this manager works for. + * + * @member {PIXI.Renderer} + */ + this.renderer = renderer; + + this.renderer.runners.contextChange.add(this); + } + + /** + * Generic method called when there is a WebGL context change. + * + */ + contextChange() + { + // do some codes init! + } + + /** + * Generic destroy methods to be overridden by the subclass + * + */ + destroy() + { + this.renderer.runners.contextChange.remove(this); + this.renderer = null; + } +} diff --git a/packages/core/src/batch/BatchSystem.js b/packages/core/src/batch/BatchSystem.js new file mode 100644 index 0000000..b04c249 --- /dev/null +++ b/packages/core/src/batch/BatchSystem.js @@ -0,0 +1,68 @@ +import System from '../System'; +import ObjectRenderer from './ObjectRenderer'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class BatchSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + super(renderer); + + /** + * An empty renderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.emptyRenderer = new ObjectRenderer(renderer); + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.currentRenderer = this.emptyRenderer; + } + + /** + * Changes the current renderer to the one given in parameter + * + * @param {PIXI.ObjectRenderer} objectRenderer - The object renderer to use. + */ + setObjectRenderer(objectRenderer) + { + if (this.currentRenderer === objectRenderer) + { + return; + } + + this.currentRenderer.stop(); + this.currentRenderer = objectRenderer; + + this.renderer.state.setState(objectRenderer.state); + + this.currentRenderer.start(); + } + + /** + * This should be called if you wish to do some custom rendering + * It will basically render anything that may be batched up such as sprites + * + */ + flush() + { + this.setObjectRenderer(this.emptyRenderer); + } + + reset() + { + this.setObjectRenderer(this.emptyRenderer); + } +} diff --git a/packages/core/src/batch/ObjectRenderer.js b/packages/core/src/batch/ObjectRenderer.js new file mode 100644 index 0000000..0c851a9 --- /dev/null +++ b/packages/core/src/batch/ObjectRenderer.js @@ -0,0 +1,48 @@ +import System from '../System'; + +/** + * Base for a common object renderer that can be used as a system renderer plugin. + * + * @class + * @extends PIXI.System + * @memberof PIXI + */ +export default class ObjectRenderer extends System +{ + /** + * Starts the renderer and sets the shader + * + */ + start() + { + // set the shader.. + } + + /** + * Stops the renderer + * + */ + stop() + { + this.flush(); + } + + /** + * Stub method for rendering content and emptying the current batch. + * + */ + flush() + { + // flush! + } + + /** + * Renders an object + * + * @param {PIXI.DisplayObject} object - The object to render. + */ + render(object) // eslint-disable-line no-unused-vars + { + // render the object + } +} diff --git a/packages/core/src/context/ContextSystem.js b/packages/core/src/context/ContextSystem.js new file mode 100644 index 0000000..301aab4 --- /dev/null +++ b/packages/core/src/context/ContextSystem.js @@ -0,0 +1,186 @@ +import System from '../System'; +import { settings } from '@pixi/settings'; + +let CONTEXT_UID = 0; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class ContextSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.webGLVersion = 1; + + this.handleContextLost = this.handleContextLost.bind(this); + this.handleContextRestored = this.handleContextRestored.bind(this); + + this.extensions = {}; + + renderer.view.addEventListener('webglcontextlost', this.handleContextLost, false); + renderer.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + } + + get isLost() + { + return (!this.gl || this.gl.isContextLost()); + } + + contextChange(gl) + { + this.gl = gl; + + // restore a context if it was previously lost + if (gl.isContextLost() && gl.getExtension('WEBGL_lose_context')) + { + gl.getExtension('WEBGL_lose_context').restoreContext(); + } + } + + initFromContext(gl) + { + this.gl = gl; + this.validateContext(gl); + this.renderer.gl = gl; + this.renderer.CONTEXT_UID = CONTEXT_UID++; + this.renderer.runners.contextChange.run(gl); + } + + initFromOptions(options) + { + const gl = this.createContext(this.renderer.view, options); + + this.initFromContext(gl); + } + + /** + * Helper class to create a webGL Context + * + * @class + * @memberof PIXI.glCore + * @param canvas {HTMLCanvasElement} the canvas element that we will get the context from + * @param options {Object} An options object that gets passed in to the canvas element containing the context attributes, + * see https://developer.mozilla.org/en/docs/Web/API/HTMLCanvasElement/getContext for the options available + * @return {WebGLRenderingContext} the WebGL context + */ + createContext(canvas, options) + { + let gl; + + if (settings.PREFER_WEBGL_2) + { + gl = canvas.getContext('webgl2', options); + } + + if (gl) + { + this.webGLVersion = 2; + } + else + { + this.webGLVersion = 1; + + gl = canvas.getContext('webgl', options) + || canvas.getContext('experimental-webgl', options); + + if (!gl) + { + // fail, not able to get a context + throw new Error('This browser does not support webGL. Try using the canvas renderer'); + } + } + + this.gl = gl; + + this.getExtensions(); + + return gl; + } + + getExtensions() + { + // time to set up default etensions that pixi uses.. + const gl = this.gl; + const extensions = this.extensions; + + if (this.webGLVersion === 1) + { + extensions.drawBuffers = gl.getExtension('WEBGL_draw_buffers'); + extensions.depthTexture = gl.getExtension('WEBKIT_WEBGL_depth_texture'); + extensions.floatTexture = gl.getExtension('OES_texture_float'); + extensions.loseContext = gl.getExtension('WEBGL_lose_context'); + + extensions.vertexArrayObject = gl.getExtension('OES_vertex_array_object') + || gl.getExtension('MOZ_OES_vertex_array_object') + || gl.getExtension('WEBKIT_OES_vertex_array_object'); + } + + // we don't use any specific WebGL 2 ones yet! + } + + /** + * Handles a lost webgl context + * + * @private + * @param {WebGLContextEvent} event - The context lost event. + */ + handleContextLost(event) + { + event.preventDefault(); + } + + /** + * Handles a restored webgl context + * + * @private + */ + handleContextRestored() + { + this.renderer.runners.contextChange.run(this.gl); + } + + destroy() + { + const view = this.renderer.view; + + // remove listeners + view.removeEventListener('webglcontextlost', this.handleContextLost); + view.removeEventListener('webglcontextrestored', this.handleContextRestored); + + this.gl.useProgram(null); + + if (this.extensions.loseContext) + { + this.extensions.loseContext.loseContext(); + } + } + + postrender() + { + this.gl.flush(); + } + + validateContext(gl) + { + const attributes = gl.getContextAttributes(); + + // this is going to be fairly simple for now.. but at least we have room to grow! + if (!attributes.stencil) + { + /* eslint-disable max-len */ + + /* eslint-disable no-console */ + console.warn('Provided WebGL context does not have a stencil buffer, masks may not render correctly'); + /* eslint-enable no-console */ + + /* eslint-enable max-len */ + } + } +} diff --git a/packages/core/src/filters/Filter.js b/packages/core/src/filters/Filter.js new file mode 100644 index 0000000..9d8cad8 --- /dev/null +++ b/packages/core/src/filters/Filter.js @@ -0,0 +1,140 @@ +import Shader from '../shader/Shader'; +import Program from '../shader/Program'; +import State from '../state/State'; +import { settings } from '@pixi/settings'; +// import extractUniformsFromSrc from './extractUniformsFromSrc'; +import defaultVertex from './defaultFilter.vert'; +import defaultFragment from './defaultFilter.frag'; + +// let math = require('../../math'); +/** + * @class + * @memberof PIXI + * @extends PIXI.Shader + */ +export default class Filter extends Shader +{ + /** + * @param {string} [vertexSrc] - The source of the vertex shader. + * @param {string} [fragmentSrc] - The source of the fragment shader. + * @param {object} [uniforms] - Custom uniforms to use to augment the built-in ones. + */ + constructor(vertexSrc, fragmentSrc, uniforms) + { + const program = Program.from(vertexSrc, fragmentSrc); + + super(program, uniforms); + + /** + * The padding of the filter. Some filters require extra space to breath such as a blur. + * Increasing this will add extra width and height to the bounds of the object that the + * filter is applied to. + * + * @member {number} + */ + this.padding = 0; + + /** + * The resolution of the filter. Setting this to be lower will lower the quality but + * increase the performance of the filter. + * + * @member {number} + */ + this.resolution = settings.RESOLUTION; + + /** + * If enabled is true the filter is applied, if false it will not. + * + * @member {boolean} + */ + this.enabled = true; + + /** + * If enabled, PixiJS will fit the filter area into boundaries for better performance. + * Switch it off if it does not work for specific shader. + * + * @member {boolean} + */ + this.autoFit = true; + + /** + * Legacy filters use position and uvs from attributes + * @member {boolean} + * @readonly + */ + this.legacy = !!this.program.attributeData.aTextureCoord; + + /** + * the webGL state the filter requires to render + * @member {PIXI.State} + */ + this.state = new State(); + } + + /** + * Applies the filter + * + * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + * @param {boolean} clear - Should the output be cleared before rendering to it + * @param {object} [currentState] - It's current state of filter. + * There are some useful properties in the currentState : + * target, filters, sourceFrame, destinationFrame, renderTarget, resolution + */ + apply(filterManager, input, output, clear, currentState, derp) // eslint-disable-line no-unused-vars + { + // do as you please! + + filterManager.applyFilter(this, input, output, clear, currentState, derp); + + // or just do a regular render.. + } + + /** + * Sets the blendmode of the filter + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL + */ + get blendMode() + { + return this.state.blendMode; + } + + set blendMode(value) // eslint-disable-line require-jsdoc + { + this.state.blendMode = value; + } + + /** + * The default vertex shader source + * + * @static + * @constant + */ + static get defaultVertexSrc() + { + return defaultVertex; + } + + /** + * The default fragment shader source + * + * @static + * @constant + */ + static get defaultFragmentSrc() + { + return defaultFragment; + } +} + +/** + * Used for caching shader IDs + * + * @static + * @private + */ +Filter.SOURCE_KEY_MAP = {}; + diff --git a/packages/core/src/filters/FilterSystem.js b/packages/core/src/filters/FilterSystem.js new file mode 100644 index 0000000..3174318 --- /dev/null +++ b/packages/core/src/filters/FilterSystem.js @@ -0,0 +1,431 @@ +import System from '../System'; + +import RenderTexture from '../renderTexture/RenderTexture'; +import Quad from '../utils/Quad'; +import QuadUv from '../utils/QuadUv'; +import { Rectangle } from '@pixi/math'; +import * as filterTransforms from './filterTransforms'; +import bitTwiddle from 'bit-twiddle'; +import UniformGroup from '../shader/UniformGroup'; +import { DRAW_MODES } from '@pixi/constants'; + +// +/** + * @ignore + * @class + */ +class FilterState +{ + /** + * + */ + constructor() + { + this.renderTexture = null; + this.sourceFrame = new Rectangle(); + this.destinationFrame = new Rectangle(); + this.filters = []; + this.target = null; + this.legacy = false; + this.resolution = 1; + } +} + +/** + * @class + * @memberof PIXI.systems + * @extends PIXI.System + */ +export default class FilterSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + /** + * stores a bunch of PO2 textures used for filtering + * @type {Object} + */ + this.texturePool = {}; + + /** + * a pool for storing filter states, save us creating new ones each tick + * @type {Array} + */ + this.statePool = []; + + /** + * A very simple geometry used when drawing a filter effect to the screen + * @type {Quad} + */ + this.quad = new Quad(); + + this.quadUv = new QuadUv(); + + /** + * Temporary rect for maths + * @type {PIXI.Rectangle} + */ + this.tempRect = new Rectangle(); + + this.activeState = {}; + + /** + * this uniform group is attached to filter uniforms when used + * @type {UniformGroup} + */ + this.globalUniforms = new UniformGroup({ + sourceFrame: this.tempRect, + destinationFrame: this.tempRect, + + // legacy variables + filterArea: new Float32Array(4), + filterClamp: new Float32Array(4), + }, true); + } + + /** + * Adds a new filter to the System. + * + * @param {PIXI.DisplayObject} target - The target of the filter to render. + * @param {PIXI.Filter[]} filters - The filters to apply. + */ + push(target, filters) + { + const renderer = this.renderer; + const filterStack = this.renderer.renderTexture.defaultFilterStack; + const state = this.statePool.pop() || new FilterState(); + + let resolution = filters[0].resolution; + let padding = filters[0].padding; + let autoFit = filters[0].autoFit; + let legacy = filters[0].legacy; + + for (let i = 1; i < filters.length; i++) + { + const filter = filters[i]; + + // lets use the lowest resolution.. + resolution = Math.min(resolution, filter.resolution); + // and the largest amount of padding! + padding = Math.max(padding, filter.padding); + // only auto fit if all filters are autofit + autoFit = autoFit || filter.autoFit; + + legacy = legacy || filter.legacy; + } + + filterStack.push(state); + + state.resolution = resolution; + + state.legacy = legacy; + + // round to whole number based on resolution + // TODO move that to the shader too? + state.sourceFrame = target.filterArea || target.getBounds(true); + + state.sourceFrame.pad(padding); + + if (autoFit) + { + state.sourceFrame.fit(this.renderer.renderTexture.destinationFrame); + } + + state.sourceFrame.round(resolution); + + state.renderTexture = this.getPotFilterTexture(state.sourceFrame.width, state.sourceFrame.height, resolution); + state.filters = filters; + + state.destinationFrame.width = state.renderTexture.width; + state.destinationFrame.height = state.renderTexture.height; + + state.renderTexture.filterFrame = state.sourceFrame; + renderer.renderTexture.bind(state.renderTexture, state.sourceFrame);// /, state.destinationFrame); + renderer.renderTexture.clear(); + } + + /** + * Pops off the filter and applies it. + * + */ + pop() + { + const renderer = this.renderer; + const filterStack = renderer.renderTexture.defaultFilterStack; + const state = filterStack.pop(); + const filters = state.filters; + + this.activeState = state; + + const globalUniforms = this.globalUniforms.uniforms; + + globalUniforms.sourceFrame = state.sourceFrame; + globalUniforms.destinationFrame = state.destinationFrame; + globalUniforms.resolution = state.resolution; + + // only update the rect if its legacy.. + if (state.legacy) + { + const filterArea = globalUniforms.filterArea; + const filterClamp = globalUniforms.filterClamp; + + filterArea[0] = state.destinationFrame.width; + filterArea[1] = state.destinationFrame.height; + filterArea[2] = state.sourceFrame.x; + filterArea[3] = state.sourceFrame.y; + + filterClamp[0] = 0.5 / state.resolution / state.destinationFrame.width; + filterClamp[1] = 0.5 / state.resolution / state.destinationFrame.height; + filterClamp[2] = (state.sourceFrame.width - 0.5) / state.resolution / state.destinationFrame.width; + filterClamp[3] = (state.sourceFrame.height - 0.5) / state.resolution / state.destinationFrame.height; + } + + this.globalUniforms.update(); + + const lastState = filterStack[filterStack.length - 1]; + + if (filters.length === 1) + { + filters[0].apply(this, state.renderTexture, lastState.renderTexture, false, state); + + this.returnFilterTexture(state.renderTexture); + } + else + { + let flip = state.renderTexture; + let flop = this.getPotFilterTexture( + flip.width, + flip.height, + state.resolution + ); + + flop.filterFrame = flip.filterFrame; + + let i = 0; + + for (i = 0; i < filters.length - 1; ++i) + { + filters[i].apply(this, flip, flop, true, state); + + const t = flip; + + flip = flop; + flop = t; + } + + filters[i].apply(this, flip, lastState.renderTexture, false, state); + + this.returnFilterTexture(flip); + this.returnFilterTexture(flop); + } + + this.statePool.push(state); + } + + /** + * Draws a filter. + * + * @param {PIXI.Filter} filter - The filter to draw. + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + * @param {boolean} clear - Should the output be cleared before rendering to it + */ + applyFilter(filter, input, output, clear) + { + const renderer = this.renderer; + + renderer.renderTexture.bind(output, output ? output.filterFrame : null); + + if (clear) + { + // gl.disable(gl.SCISSOR_TEST); + renderer.renderTexture.clear(); + // gl.enable(gl.SCISSOR_TEST); + } + + // set the uniforms.. + filter.uniforms.uSampler = input; + filter.uniforms.filterGlobals = this.globalUniforms; + + // TODO make it so that the order of this does not matter.. + // because it does at the moment cos of global uniforms. + // they need to get resynced + + renderer.state.setState(filter.state); + renderer.shader.bind(filter); + + if (filter.legacy) + { + this.quadUv.map(input._frame, input.filterFrame); + + renderer.geometry.bind(this.quadUv); + renderer.geometry.draw(DRAW_MODES.TRIANGLES); + } + else + { + renderer.geometry.bind(this.quad); + renderer.geometry.draw(DRAW_MODES.TRIANGLE_STRIP); + } + } + + /** + * Calculates the mapped matrix. + * + * TODO playing around here.. this is temporary - (will end up in the shader) + * this returns a matrix that will normalise map filter cords in the filter to screen space + * + * @param {PIXI.Matrix} outputMatrix - the matrix to output to. + * @return {PIXI.Matrix} The mapped matrix. + */ + calculateScreenSpaceMatrix(outputMatrix) + { + const currentState = this.activeState; + + return filterTransforms.calculateScreenSpaceMatrix( + outputMatrix, + currentState.sourceFrame, + currentState.destinationFrame + ); + } + + /** + * This will map the filter coord so that a texture can be used based on the transform of a sprite + * + * @param {PIXI.Matrix} outputMatrix - The matrix to output to. + * @param {PIXI.Sprite} sprite - The sprite to map to. + * @return {PIXI.Matrix} The mapped matrix. + */ + calculateSpriteMatrix(outputMatrix, sprite) + { + const currentState = this.activeState; + + return filterTransforms.calculateSpriteMatrix( + outputMatrix, + currentState.sourceFrame, + currentState.destinationFrame, + sprite + ); + } + + /** + * Destroys this Filter System. + * + * @param {boolean} [contextLost=false] context was lost, do not free shaders + * + */ + destroy(contextLost = false) + { + if (!contextLost) + { + this.emptyPool(); + } + else + { + this.texturePool = {}; + } + } + + /** + * Gets a Power-of-Two render texture. + * + * TODO move to a seperate class could be on renderer? + * also - could cause issue with multiple contexts? + * + * @private + * @param {WebGLRenderingContext} gl - The webgl rendering context + * @param {number} minWidth - The minimum width of the render target. + * @param {number} minHeight - The minimum height of the render target. + * @param {number} resolution - The resolution of the render target. + * @return {PIXI.RenderTarget} The new render target. + */ + getPotFilterTexture(minWidth, minHeight, resolution) + { + minWidth = bitTwiddle.nextPow2(minWidth); + minHeight = bitTwiddle.nextPow2(minHeight); + resolution = resolution || 1; + + const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + + if (!this.texturePool[key]) + { + this.texturePool[key] = []; + } + + let renderTexture = this.texturePool[key].pop(); + + if (!renderTexture) + { + // temporary bypass cache.. + // internally - this will cause a texture to be bound.. + renderTexture = RenderTexture.create({ + width: minWidth, + height: minHeight, + resolution, + }); + } + + return renderTexture; + } + + /** + * Gets extra render texture to use inside current filter + * + * @param {number} resolution resolution of the renderTexture + * @returns {PIXI.RenderTexture} + */ + getFilterTexture(resolution) + { + const rt = this.activeState.renderTexture; + + const filterTexture = this.getPotFilterTexture(rt.width, rt.height, resolution || rt.baseTexture.resolution); + + filterTexture.filterFrame = rt.filterFrame; + + return filterTexture; + } + + /** + * Frees a render texture back into the pool. + * + * @param {PIXI.RenderTarget} renderTexture - The renderTarget to free + */ + returnFilterTexture(renderTexture) + { + renderTexture.filterFrame = null; + + const base = renderTexture.baseTexture; + + const minWidth = base.width; + const minHeight = base.height; + + const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + + this.texturePool[key].push(renderTexture); + } + + /** + * Empties the texture pool. + * + */ + emptyPool() + { + for (const i in this.texturePool) + { + const textures = this.texturePool[i]; + + if (textures) + { + for (let j = 0; j < textures.length; j++) + { + textures[j].destroy(true); + } + } + } + + this.texturePool = {}; + } +} diff --git a/packages/core/src/filters/defaultFilter.frag b/packages/core/src/filters/defaultFilter.frag new file mode 100644 index 0000000..a14c1be --- /dev/null +++ b/packages/core/src/filters/defaultFilter.frag @@ -0,0 +1,22 @@ +varying vec2 vTextureCoord; +varying vec2 vFilterCoord; + +uniform sampler2D uSampler; +uniform sampler2D filterSampler; + +void main(void){ + vec4 masky = texture2D(filterSampler, vFilterCoord); + vec4 sample = texture2D(uSampler, vTextureCoord); + vec4 color; + if(mod(vFilterCoord.x, 1.0) > 0.5) + { + color = vec4(1.0, 0.0, 0.0, 1.0); + } + else + { + color = vec4(0.0, 1.0, 0.0, 1.0); + } + // gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0); + gl_FragColor = mix(sample, masky, 0.5); + gl_FragColor *= sample.a; +} diff --git a/packages/core/src/filters/defaultFilter.vert b/packages/core/src/filters/defaultFilter.vert new file mode 100644 index 0000000..e1febca --- /dev/null +++ b/packages/core/src/filters/defaultFilter.vert @@ -0,0 +1,14 @@ +attribute vec2 aVertexPosition; +attribute vec2 aTextureCoord; + +uniform mat3 projectionMatrix; +uniform mat3 filterMatrix; + +varying vec2 vTextureCoord; +varying vec2 vFilterCoord; + +void main(void){ + gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); + vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy; + vTextureCoord = aTextureCoord ; +} diff --git a/packages/core/src/filters/filterTransforms.js b/packages/core/src/filters/filterTransforms.js new file mode 100644 index 0000000..87a3cd6 --- /dev/null +++ b/packages/core/src/filters/filterTransforms.js @@ -0,0 +1,50 @@ +import { Matrix } from '@pixi/math'; + +/** + * Calculates the mapped matrix + * @param filterArea {Rectangle} The filter area + * @param sprite {Sprite} the target sprite + * @param outputMatrix {Matrix} @alvin + * @private + */ +// this returns a matrix that will normalise map filter cords in the filter to screen space +export function calculateScreenSpaceMatrix(outputMatrix, filterArea, textureSize) +{ + // TODO unwrap? + const mappedMatrix = outputMatrix.identity(); + + mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); + + mappedMatrix.scale(textureSize.width, textureSize.height); + + return mappedMatrix; +} + +export function calculateNormalizedScreenSpaceMatrix(outputMatrix, filterArea, textureSize) +{ + const mappedMatrix = outputMatrix.identity(); + + mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); + + const translateScaleX = (textureSize.width / filterArea.width); + const translateScaleY = (textureSize.height / filterArea.height); + + mappedMatrix.scale(translateScaleX, translateScaleY); + + return mappedMatrix; +} + +// this will map the filter coord so that a texture can be used based on the transform of a sprite +export function calculateSpriteMatrix(outputMatrix, filterArea, textureSize, sprite) +{ + const orig = sprite._texture.orig; + const mappedMatrix = outputMatrix.set(textureSize.width, 0, 0, textureSize.height, filterArea.x, filterArea.y); + const worldTransform = sprite.worldTransform.copyTo(Matrix.TEMP_MATRIX); + + worldTransform.invert(); + mappedMatrix.prepend(worldTransform); + mappedMatrix.scale(1.0 / orig.width, 1.0 / orig.height); + mappedMatrix.translate(sprite.anchor.x, sprite.anchor.y); + + return mappedMatrix; +} diff --git a/packages/core/src/filters/spriteMask/SpriteMaskFilter.js b/packages/core/src/filters/spriteMask/SpriteMaskFilter.js new file mode 100644 index 0000000..91697b7 --- /dev/null +++ b/packages/core/src/filters/spriteMask/SpriteMaskFilter.js @@ -0,0 +1,63 @@ +import Filter from '../Filter'; +import { Matrix } from '@pixi/math'; +import vertex from './spriteMaskFilter.vert'; +import fragment from './spriteMaskFilter.frag'; +import { default as TextureMatrix } from '../../textures/TextureMatrix'; + +/** + * The SpriteMaskFilter class + * + * @class + * @extends PIXI.Filter + * @memberof PIXI + */ +export default class SpriteMaskFilter extends Filter +{ + /** + * @param {PIXI.Sprite} sprite - the target sprite + */ + constructor(sprite) + { + const maskMatrix = new Matrix(); + + super(vertex, fragment); + + sprite.renderable = false; + + this.maskSprite = sprite; + this.maskMatrix = maskMatrix; + } + + /** + * Applies the filter + * + * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + */ + apply(filterManager, input, output) + { + const maskSprite = this.maskSprite; + const tex = this.maskSprite.texture; + + if (!tex.valid) + { + return; + } + if (!tex.transform) + { + // margin = 0.0, let it bleed a bit, shader code becomes easier + // assuming that atlas textures were made with 1-pixel padding + tex.transform = new TextureMatrix(tex, 0.0); + } + tex.transform.update(); + + this.uniforms.mask = tex; + this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite) + .prepend(tex.transform.mapCoord); + this.uniforms.alpha = maskSprite.worldAlpha; + this.uniforms.maskClamp = tex.transform.uClampFrame; + + filterManager.applyFilter(this, input, output); + } +} diff --git a/packages/core/src/filters/spriteMask/spriteMaskFilter.frag b/packages/core/src/filters/spriteMask/spriteMaskFilter.frag new file mode 100644 index 0000000..0e4aef8 --- /dev/null +++ b/packages/core/src/filters/spriteMask/spriteMaskFilter.frag @@ -0,0 +1,23 @@ +varying vec2 vMaskCoord; +varying vec2 vTextureCoord; + +uniform sampler2D uSampler; +uniform sampler2D mask; +uniform float alpha; +uniform vec4 maskClamp; + +void main(void) +{ + float clip = step(3.5, + step(maskClamp.x, vMaskCoord.x) + + step(maskClamp.y, vMaskCoord.y) + + step(vMaskCoord.x, maskClamp.z) + + step(vMaskCoord.y, maskClamp.w)); + + vec4 original = texture2D(uSampler, vTextureCoord); + vec4 masky = texture2D(mask, vMaskCoord); + + original *= (masky.r * masky.a * alpha * clip); + + gl_FragColor = original; +} diff --git a/packages/core/src/filters/spriteMask/spriteMaskFilter.vert b/packages/core/src/filters/spriteMask/spriteMaskFilter.vert new file mode 100644 index 0000000..d23a30c --- /dev/null +++ b/packages/core/src/filters/spriteMask/spriteMaskFilter.vert @@ -0,0 +1,16 @@ +attribute vec2 aVertexPosition; +attribute vec2 aTextureCoord; + +uniform mat3 projectionMatrix; +uniform mat3 otherMatrix; + +varying vec2 vMaskCoord; +varying vec2 vTextureCoord; + +void main(void) +{ + gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); + + vTextureCoord = aTextureCoord; + vMaskCoord = ( otherMatrix * vec3( aTextureCoord, 1.0) ).xy; +} diff --git a/packages/core/src/framebuffer/FrameBuffer.js b/packages/core/src/framebuffer/FrameBuffer.js new file mode 100644 index 0000000..84e9fcc --- /dev/null +++ b/packages/core/src/framebuffer/FrameBuffer.js @@ -0,0 +1,106 @@ +import Texture from '../textures/BaseTexture'; +import { FORMATS, TYPES } from '@pixi/constants'; + +/** + * Frame buffer + * @class + * @memberof PIXI + */ +export default class FrameBuffer +{ + constructor(width, height) + { + this.width = width || 100; + this.height = height || 100; + + this.stencil = false; + this.depth = false; + + this.dirtyId = 0; + this.dirtyFormat = 0; + this.dirtySize = 0; + + this.depthTexture = null; + this.colorTextures = []; + + this.glFrameBuffers = {}; + } + + get colorTexture() + { + return this.colorTextures[0]; + } + + addColorTexture(index, texture) + { + // TODO add some validation to the texture - same width / height etc? + this.colorTextures[index || 0] = texture || new Texture(null, { scaleMode: 0, + resolution: 1, + mipmap: false, + width: this.width, + height: this.height });// || new Texture(); + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + addDepthTexture(texture) + { + /* eslint-disable max-len */ + this.depthTexture = texture || new Texture(null, { scaleMode: 0, + resolution: 1, + width: this.width, + height: this.height, + mipmap: false, + format: FORMATS.DEPTH_COMPONENT, + type: TYPES.UNSIGNED_SHORT });// UNSIGNED_SHORT; + /* eslint-disable max-len */ + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + enableDepth() + { + this.depth = true; + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + enableStencil() + { + this.stencil = true; + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + resize(width, height) + { + if (width === this.width && height === this.height) return; + + this.width = width; + this.height = height; + + this.dirtyId++; + this.dirtySize++; + + for (let i = 0; i < this.colorTextures.length; i++) + { + this.colorTextures[i].setSize(width, height); + } + + if (this.depthTexture) + { + this.depthTexture.setSize(width, height); + } + } +} diff --git a/packages/core/src/framebuffer/FramebufferSystem.js b/packages/core/src/framebuffer/FramebufferSystem.js new file mode 100644 index 0000000..2cc2437 --- /dev/null +++ b/packages/core/src/framebuffer/FramebufferSystem.js @@ -0,0 +1,246 @@ +import System from '../System'; +import { Rectangle } from '@pixi/math'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class FramebufferSystem extends System +{ + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + contextChange() + { + this.gl = this.renderer.gl; + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + this.current = null; + this.viewport = new Rectangle(); + + this.drawBufferExtension = this.renderer.context.extensions.drawBuffers; + } + + bind(framebuffer, frame) + { + const gl = this.gl; + + this.current = framebuffer; + + if (framebuffer) + { + // TODO cacheing layer! + + const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID] || this.initFramebuffer(framebuffer); + + gl.bindFramebuffer(gl.FRAMEBUFFER, fbo.framebuffer); + // makesure all textures are unbound.. + + // now check for updates... + if (fbo.dirtyId !== framebuffer.dirtyId) + { + fbo.dirtyId = framebuffer.dirtyId; + + if (fbo.dirtyFormat !== framebuffer.dirtyFormat) + { + fbo.dirtyFormat = framebuffer.dirtyFormat; + this.updateFramebuffer(framebuffer); + } + else if (fbo.dirtySize !== framebuffer.dirtySize) + { + fbo.dirtySize = framebuffer.dirtySize; + this.resizeFramebuffer(framebuffer); + } + } + + for (let i = 0; i < framebuffer.colorTextures.length; i++) + { + if (framebuffer.colorTextures[i].texturePart) + { + this.renderer.texture.unbind(framebuffer.colorTextures[i].texture); + } + else + { + this.renderer.texture.unbind(framebuffer.colorTextures[i]); + } + } + + if (framebuffer.depthTexture) + { + this.renderer.texture.unbind(framebuffer.depthTexture); + } + + if (frame) + { + this.setViewport(frame.x, frame.y, frame.width, frame.height); + } + else + { + this.setViewport(0, 0, framebuffer.width, framebuffer.height); + } + } + else + { + gl.bindFramebuffer(gl.FRAMEBUFFER, null); + + if (frame) + { + this.setViewport(frame.x, frame.y, frame.width, frame.height); + } + else + { + this.setViewport(0, 0, this.renderer.width, this.renderer.height); + } + } + } + + setViewport(x, y, width, height) + { + const v = this.viewport; + + if (v.width !== width || v.height !== height || v.x !== x || v.y !== y) + { + v.x = x; + v.y = y; + v.width = width; + v.height = height; + + this.gl.viewport(x, y, width, height); + } + } + + get size() + { + if (this.current) + { + // TODO store temp + return { x: 0, y: 0, width: this.current.width, height: this.current.height }; + } + + return { x: 0, y: 0, width: this.renderer.width, height: this.renderer.height }; + } + + clear(r, g, b, a) + { + const gl = this.gl; + + // TODO clear color can be set only one right? + gl.clearColor(r, g, b, a); + gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); + } + + // private functions... + + initFramebuffer(framebuffer) + { + const gl = this.gl; + + // TODO - make this a class? + const fbo = { + framebuffer: gl.createFramebuffer(), + stencil: null, + dirtyId: 0, + dirtyFormat: 0, + dirtySize: 0, + }; + + framebuffer.glFrameBuffers[this.CONTEXT_UID] = fbo; + + return fbo; + } + + resizeFramebuffer(framebuffer) + { + const gl = this.gl; + + if (framebuffer.stencil || framebuffer.depth) + { + gl.bindRenderbuffer(gl.RENDERBUFFER, this.stencil); + gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); + } + } + + updateFramebuffer(framebuffer) + { + const gl = this.gl; + + const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID]; + + // bind the color texture + const colorTextures = framebuffer.colorTextures; + + let count = colorTextures.length; + + if (!this.drawBufferExtension) + { + count = Math.min(count, 1); + } + + const activeTextures = []; + + for (let i = 0; i < count; i++) + { + const texture = framebuffer.colorTextures[i]; + + if (texture.texturePart) + { + this.renderer.texture.bind(texture.texture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.COLOR_ATTACHMENT0 + i, + gl.TEXTURE_CUBE_MAP_NEGATIVE_X + texture.side, + texture.texture._glTextures[this.CONTEXT_UID].texture, + 0); + } + else + { + this.renderer.texture.bind(texture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.COLOR_ATTACHMENT0 + i, + gl.TEXTURE_2D, + texture._glTextures[this.CONTEXT_UID].texture, + 0); + } + + activeTextures.push(gl.COLOR_ATTACHMENT0 + i); + } + + if (this.drawBufferExtension && activeTextures.length > 1) + { + this.drawBufferExtension.drawBuffersWEBGL(activeTextures); + } + + if (framebuffer.depthTexture) + { + const depthTextureExt = this.renderer.context.extensions.depthTexture; + + if (depthTextureExt) + { + const depthTexture = framebuffer.depthTexture; + + this.renderer.texture.bind(depthTexture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.DEPTH_ATTACHMENT, + gl.TEXTURE_2D, + depthTexture._glTextures[this.CONTEXT_UID].texture, + 0); + } + } + + if (framebuffer.stencil || framebuffer.depth) + { + fbo.stencil = gl.createRenderbuffer(); + + gl.bindRenderbuffer(gl.RENDERBUFFER, fbo.stencil); + + // TODO.. this is depth AND stencil? + gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, fbo.stencil); + gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); + // fbo.enableStencil(); + } + } +} diff --git a/packages/core/src/geometry/GLBuffer.js b/packages/core/src/geometry/GLBuffer.js new file mode 100644 index 0000000..0477e8a --- /dev/null +++ b/packages/core/src/geometry/GLBuffer.js @@ -0,0 +1,9 @@ +export default class GLBuffer +{ + constructor(buffer) + { + this.buffer = buffer; + this.updateID = -1; + this.byteLength = -1; + } +} diff --git a/packages/core/src/geometry/GeometrySystem.js b/packages/core/src/geometry/GeometrySystem.js new file mode 100644 index 0000000..959f827 --- /dev/null +++ b/packages/core/src/geometry/GeometrySystem.js @@ -0,0 +1,388 @@ +import System from '../System'; +import GLBuffer from './GLBuffer'; + +const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class GeometrySystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this._activeGeometry = null; + this._activeVao = null; + + this.hasVao = true; + this.hasInstance = true; + } + + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + contextChange() + { + const gl = this.gl = this.renderer.gl; + + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + + // webgl2 + if (!gl.createVertexArray) + { + // webgl 1! + let nativeVaoExtension = this.renderer.context.extensions.vertexArrayObject; + + if (this.renderer.legacy) + { + nativeVaoExtension = null; + } + + if (nativeVaoExtension) + { + gl.createVertexArray = () => + nativeVaoExtension.createVertexArrayOES(); + + gl.bindVertexArray = (vao) => + nativeVaoExtension.bindVertexArrayOES(vao); + + gl.deleteVertexArray = (vao) => + nativeVaoExtension.deleteVertexArrayOES(vao); + } + else + { + this.hasVao = false; + gl.createVertexArray = () => + { + // empty + }; + + gl.bindVertexArray = () => + { + // empty + }; + + gl.deleteVertexArray = () => + { + // empty + }; + } + } + + if (!gl.vertexAttribDivisor) + { + const instanceExt = gl.getExtension('ANGLE_instanced_arrays'); + + if (instanceExt) + { + gl.vertexAttribDivisor = (a, b) => + instanceExt.vertexAttribDivisorANGLE(a, b); + + gl.drawElementsInstanced = (a, b, c, d, e) => + instanceExt.drawElementsInstancedANGLE(a, b, c, d, e); + + gl.drawArraysInstanced = (a, b, c, d) => + instanceExt.drawArraysInstancedANGLE(a, b, c, d); + } + else + { + this.hasInstance = false; + } + } + } + + /** + * Binds geometry so that is can be drawn. Creating a Vao if required + * @private + * @param {PIXI.Geometry} geometry instance of geometry to bind + */ + bind(geometry, shader) + { + shader = shader || this.renderer.shader.shader; + + const gl = this.gl; + + // not sure the best way to address this.. + // currently different shaders require different VAOs for the same geometry + // Still mulling over the best way to solve this one.. + // will likely need to modify the shader attribute locations at run time! + let vaos = geometry.glVertexArrayObjects[this.CONTEXT_UID]; + + if (!vaos) + { + geometry.glVertexArrayObjects[this.CONTEXT_UID] = vaos = {}; + } + + const vao = vaos[shader.program.id] || this.initGeometryVao(geometry, shader.program); + + this._activeGeometry = geometry; + + if (this._activeVao !== vao) + { + this._activeVao = vao; + + if (this.hasVao) + { + gl.bindVertexArray(vao); + } + else + { + this.activateVao(geometry, shader.program); + } + } + + // TODO - optimise later! + // don't need to loop through if nothing changed! + // maybe look to add an 'autoupdate' to geometry? + this.updateBuffers(); + } + + reset() + { + this.unbind(); + } + + updateBuffers() + { + const geometry = this._activeGeometry; + const gl = this.gl; + + for (let i = 0; i < geometry.buffers.length; i++) + { + const buffer = geometry.buffers[i]; + + const glBuffer = buffer._glBuffers[this.CONTEXT_UID]; + + if (buffer._updateID !== glBuffer.updateID) + { + glBuffer.updateID = buffer._updateID; + + // TODO can cache this on buffer! maybe added a getter / setter? + const type = buffer.index ? gl.ELEMENT_ARRAY_BUFFER : gl.ARRAY_BUFFER; + const drawType = buffer.static ? gl.STATIC_DRAW : gl.DYNAMIC_DRAW; + + gl.bindBuffer(type, glBuffer.buffer); + + if (glBuffer.byteLength >= buffer.data.byteLength) + { + // offset is always zero for now! + gl.bufferSubData(type, 0, buffer.data); + } + else + { + gl.bufferData(type, buffer.data, drawType); + } + } + } + } + + checkCompatability(geometry, program) + { + // geometry must have at least all the attributes that the shader requires. + const geometryAttributes = geometry.attributes; + const shaderAttributes = program.attributeData; + + for (const j in shaderAttributes) + { + if (!geometryAttributes[j]) + { + throw new Error(`shader and geometry incompatible, geometry missing the "${j}" attribute`); + } + } + } + + /** + * Creates a Vao with the same structure as the geometry and stores it on the geometry. + * @private + * @param {PIXI.Geometry} geometry instance of geometry to to generate Vao for + */ + initGeometryVao(geometry, program) + { + this.checkCompatability(geometry, program); + + const gl = this.gl; + const CONTEXT_UID = this.CONTEXT_UID; + const buffers = geometry.buffers; + const attributes = geometry.attributes; + + const tempStride = {}; + const tempStart = {}; + + for (const j in buffers) + { + tempStride[j] = 0; + tempStart[j] = 0; + } + + for (const j in attributes) + { + if (!attributes[j].size && program.attributeData[j]) + { + attributes[j].size = program.attributeData[j].size; + } + + tempStride[attributes[j].buffer] += attributes[j].size * byteSizeMap[attributes[j].type]; + } + + for (const j in attributes) + { + const attribute = attributes[j]; + const attribSize = attribute.size; + + if (attribute.stride === undefined) + { + if (tempStride[attribute.buffer] === attribSize * byteSizeMap[attribute.type]) + { + attribute.stride = 0; + } + else + { + attribute.stride = tempStride[attribute.buffer]; + } + } + + if (attribute.start === undefined) + { + attribute.start = tempStart[attribute.buffer]; + + tempStart[attribute.buffer] += attribSize * byteSizeMap[attribute.type]; + } + } + + // first update - and create the buffers! + // only create a gl buffer if it actually gets + for (let i = 0; i < buffers.length; i++) + { + const buffer = buffers[i]; + + if (!buffer._glBuffers[CONTEXT_UID]) + { + buffer._glBuffers[CONTEXT_UID] = new GLBuffer(gl.createBuffer()); + } + } + + // TODO - maybe make this a data object? + // lets wait to see if we need to first! + const vao = gl.createVertexArray(); + + gl.bindVertexArray(vao); + + this.activateVao(geometry, program); + + gl.bindVertexArray(null); + + // add it to the cache! + geometry.glVertexArrayObjects[this.CONTEXT_UID][program.id] = vao; + + return vao; + } + + activateVao(geometry, program) + { + const gl = this.gl; + const CONTEXT_UID = this.CONTEXT_UID; + const buffers = geometry.buffers; + const attributes = geometry.attributes; + + if (geometry.indexBuffer) + { + // first update the index buffer if we have one.. + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, geometry.indexBuffer._glBuffers[CONTEXT_UID].buffer); + } + + let lastBuffer = null; + + // add a new one! + for (const j in attributes) + { + const attribute = attributes[j]; + const buffer = buffers[attribute.buffer]; + const glBuffer = buffer._glBuffers[CONTEXT_UID]; + + if (program.attributeData[j]) + { + if (lastBuffer !== glBuffer) + { + gl.bindBuffer(gl.ARRAY_BUFFER, glBuffer.buffer); + + lastBuffer = glBuffer; + } + + const location = program.attributeData[j].location; + + // TODO introduce state again + // we can optimise this for older devices that have no VAOs + gl.enableVertexAttribArray(location); + + gl.vertexAttribPointer(location, + attribute.size, + attribute.type || gl.FLOAT, + attribute.normalized, + attribute.stride, + attribute.start); + + if (attribute.instance) + { + // TODO calculate instance count based of this... + if (this.hasInstance) + { + gl.vertexAttribDivisor(location, 1); + } + else + { + throw new Error('geometry error, GPU Instancing is not supported on this device'); + } + } + } + } + } + + draw(type, size, start, instanceCount) + { + const gl = this.gl; + const geometry = this._activeGeometry; + + // TODO.. this should not change so maybe cache the function? + + if (geometry.indexBuffer) + { + if (geometry.instanced) + { + /* eslint-disable max-len */ + gl.drawElementsInstanced(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2, instanceCount || 1); + /* eslint-enable max-len */ + } + else + { + gl.drawElements(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2); + } + } + else + if (geometry.instanced) + { + // TODO need a better way to calculate size.. + gl.drawArraysInstanced(type, start, size || geometry.getSize(), instanceCount || 1); + } + else + { + gl.drawArrays(type, start, size || geometry.getSize()); + } + + return this; + } + + unbind() + { + this.gl.bindVertexArray(null); + this._activeVao = null; + this._activeGeometry = null; + } +} diff --git a/packages/core/src/geometry/utils/setVertexAttribArrays.js b/packages/core/src/geometry/utils/setVertexAttribArrays.js new file mode 100644 index 0000000..23b6959 --- /dev/null +++ b/packages/core/src/geometry/utils/setVertexAttribArrays.js @@ -0,0 +1,54 @@ +// var GL_MAP = {}; + +/** + * @param gl {WebGLRenderingContext} The current WebGL context + * @param attribs {*} + * @param state {*} + */ +export default function setVertexAttribArrays(gl, attribs, state) +{ + let i; + + if (state) + { + const tempAttribState = state.tempAttribState; + const attribState = state.attribState; + + for (i = 0; i < tempAttribState.length; i++) + { + tempAttribState[i] = false; + } + + // set the new attribs + for (i = 0; i < attribs.length; i++) + { + tempAttribState[attribs[i].attribute.location] = true; + } + + for (i = 0; i < attribState.length; i++) + { + if (attribState[i] !== tempAttribState[i]) + { + attribState[i] = tempAttribState[i]; + + if (state.attribState[i]) + { + gl.enableVertexAttribArray(i); + } + else + { + gl.disableVertexAttribArray(i); + } + } + } + } + else + { + for (i = 0; i < attribs.length; i++) + { + const attrib = attribs[i]; + + gl.enableVertexAttribArray(attrib.attribute.location); + } + } +} diff --git a/packages/core/src/index.js b/packages/core/src/index.js index 58c456a..fd1f547 100644 --- a/packages/core/src/index.js +++ b/packages/core/src/index.js @@ -1,30 +1,30 @@ import * as resources from './textures/resources'; -import * as systems from './renderers/systems'; +import * as systems from './systems'; -export { resources }; export { systems }; +export { resources }; -export { default as Renderer } from './renderers/Renderer'; -export { default as AbstractRenderer } from './renderers/AbstractRenderer'; -export { default as FrameBuffer } from './textures/FrameBuffer'; +export { default as System } from './System'; +export { default as Renderer } from './Renderer'; +export { default as AbstractRenderer } from './AbstractRenderer'; +export { default as FrameBuffer } from './framebuffer/FrameBuffer'; export { default as CubeTexture } from './textures/CubeTexture'; export { default as BaseTexture } from './textures/BaseTexture'; export { default as Texture } from './textures/Texture'; export { default as TextureMatrix } from './textures/TextureMatrix'; -export { default as RenderTexture } from './textures/RenderTexture'; -export { default as BaseRenderTexture } from './textures/BaseRenderTexture'; +export { default as RenderTexture } from './renderTexture/RenderTexture'; +export { default as BaseRenderTexture } from './renderTexture/BaseRenderTexture'; export { default as TextureUvs } from './textures/TextureUvs'; -export { default as State } from './renderers/State'; -export { default as ObjectRenderer } from './renderers/utils/ObjectRenderer'; -export { default as Quad } from './renderers/utils/Quad'; -export { default as QuadUv } from './renderers/utils/QuadUv'; -export { default as checkMaxIfStatmentsInShader } from './renderers/utils/checkMaxIfStatmentsInShader'; +export { default as State } from './state/State'; +export { default as ObjectRenderer } from './batch/ObjectRenderer'; +export { default as Quad } from './utils/Quad'; +export { default as QuadUv } from './utils/QuadUv'; +export { default as checkMaxIfStatmentsInShader } from './shader/utils/checkMaxIfStatmentsInShader'; export { default as Shader } from './shader/Shader'; export { default as Program } from './shader/Program'; export { default as UniformGroup } from './shader/UniformGroup'; -export { default as SpriteMaskFilter } from './renderers/filters/spriteMask/SpriteMaskFilter'; -export { default as Filter } from './renderers/filters/Filter'; +export { default as SpriteMaskFilter } from './filters/spriteMask/SpriteMaskFilter'; +export { default as Filter } from './filters/Filter'; export { default as Attribute } from './geometry/Attribute'; export { default as Buffer } from './geometry/Buffer'; export { default as Geometry } from './geometry/Geometry'; - diff --git a/packages/core/src/mask/MaskSystem.js b/packages/core/src/mask/MaskSystem.js new file mode 100644 index 0000000..99174c2 --- /dev/null +++ b/packages/core/src/mask/MaskSystem.js @@ -0,0 +1,199 @@ +import System from '../System'; +import AlphaMaskFilter from '../filters/spriteMask/SpriteMaskFilter'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class MaskSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + // TODO - we don't need both! + this.scissor = false; + this.scissorData = null; + this.scissorRenderTarget = null; + + this.enableScissor = false; + + this.alphaMaskPool = []; + this.alphaMaskIndex = 0; + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.DisplayObject} target - Display Object to push the mask to + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + push(target, maskData) + { + // TODO the root check means scissor rect will not + // be used on render textures more info here: + // https://github.com/pixijs/pixi.js/pull/3545 + + if (maskData.texture) + { + this.pushSpriteMask(target, maskData); + } + else if (this.enableScissor + && !this.scissor + && this.renderer._activeRenderTarget.root + && !this.renderer.stencil.stencilMaskStack.length + && maskData.isFastRect()) + { + const matrix = maskData.worldTransform; + + let rot = Math.atan2(matrix.b, matrix.a); + + // use the nearest degree! + rot = Math.round(rot * (180 / Math.PI)); + + if (rot % 90) + { + this.pushStencilMask(maskData); + } + else + { + this.pushScissorMask(target, maskData); + } + } + else + { + this.pushStencilMask(maskData); + } + } + + /** + * Removes the last mask from the mask stack and doesn't return it. + * + * @param {PIXI.DisplayObject} target - Display Object to pop the mask from + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + pop(target, maskData) + { + if (maskData.texture) + { + this.popSpriteMask(target, maskData); + } + else if (this.enableScissor && !this.renderer.stencil.stencilMaskStack.length) + { + this.popScissorMask(target, maskData); + } + else + { + this.popStencilMask(target, maskData); + } + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.RenderTarget} target - Display Object to push the sprite mask to + * @param {PIXI.Sprite} maskData - Sprite to be used as the mask + */ + pushSpriteMask(target, maskData) + { + let alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; + + if (!alphaMaskFilter) + { + alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; + } + + alphaMaskFilter[0].resolution = this.renderer.resolution; + alphaMaskFilter[0].maskSprite = maskData; + + // TODO - may cause issues! + target.filterArea = maskData.getBounds(true); + + this.renderer.filterSystem.pushFilter(target, alphaMaskFilter); + + this.alphaMaskIndex++; + } + + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popSpriteMask() + { + this.renderer.filterSystem.popFilter(); + this.alphaMaskIndex--; + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + pushStencilMask(maskData) + { + this.renderer.batch.flush(); + this.renderer.stencil.pushStencil(maskData); + } + + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popStencilMask() + { + // this.renderer.currentRenderer.stop(); + this.renderer.stencil.popStencil(); + } + + /** + * + * @param {PIXI.DisplayObject} target - Display Object to push the mask to + * @param {PIXI.Graphics} maskData - The masking data. + */ + pushScissorMask(target, maskData) + { + maskData.renderable = true; + + const renderTarget = this.renderer._activeRenderTarget; + + const bounds = maskData.getBounds(); + + bounds.fit(renderTarget.size); + maskData.renderable = false; + + this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); + + const resolution = this.renderer.resolution; + + this.renderer.gl.scissor( + bounds.x * resolution, + (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, + bounds.width * resolution, + bounds.height * resolution + ); + + this.scissorRenderTarget = renderTarget; + this.scissorData = maskData; + this.scissor = true; + } + + /** + * + * + */ + popScissorMask() + { + this.scissorRenderTarget = null; + this.scissorData = null; + this.scissor = false; + + // must be scissor! + const gl = this.renderer.gl; + + gl.disable(gl.SCISSOR_TEST); + } +} diff --git a/packages/core/src/mask/StencilSystem.js b/packages/core/src/mask/StencilSystem.js new file mode 100644 index 0000000..62796a5 --- /dev/null +++ b/packages/core/src/mask/StencilSystem.js @@ -0,0 +1,133 @@ +import System from '../System'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class StencilSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + this.stencilMaskStack = []; + } + + /** + * Changes the mask stack that is used by this System. + * + * @param {PIXI.Graphics[]} stencilMaskStack - The mask stack + */ + setMaskStack(stencilMaskStack) + { + const gl = this.renderer.gl; + + if (stencilMaskStack.length !== this.stencilMaskStack.length) + { + if (stencilMaskStack.length === 0) + { + gl.disable(gl.STENCIL_TEST); + } + else + { + gl.enable(gl.STENCIL_TEST); + } + } + + this.stencilMaskStack = stencilMaskStack; + } + + /** + * Applies the Mask and adds it to the current stencil stack. @alvin + * + * @param {PIXI.Graphics} graphics - The mask + */ + pushStencil(graphics) + { + this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); + + // this.renderer._activeRenderTarget.attachStencilBuffer(); + + const gl = this.renderer.gl; + const prevMaskCount = this.stencilMaskStack.length; + + if (prevMaskCount === 0) + { + gl.enable(gl.STENCIL_TEST); + } + + this.stencilMaskStack.push(graphics); + + // Increment the refference stencil value where the new mask overlaps with the old ones. + gl.colorMask(false, false, false, false); + gl.stencilFunc(gl.EQUAL, prevMaskCount, this._getBitwiseMask()); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.INCR); + this.renderer.plugins.graphics.render(graphics); + + this._useCurrent(); + } + + /** + * Removes the last mask from the stencil stack. @alvin + */ + popStencil() + { + this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); + + const gl = this.renderer.gl; + const graphics = this.stencilMaskStack.pop(); + + if (this.stencilMaskStack.length === 0) + { + // the stack is empty! + gl.disable(gl.STENCIL_TEST); + gl.clear(gl.STENCIL_BUFFER_BIT); + gl.clearStencil(0); + } + else + { + // Decrement the refference stencil value where the popped mask overlaps with the other ones + gl.colorMask(false, false, false, false); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.DECR); + this.renderer.plugins.graphics.render(graphics); + + this._useCurrent(); + } + } + + /** + * Setup renderer to use the current stencil data. + */ + _useCurrent() + { + const gl = this.renderer.gl; + + gl.colorMask(true, true, true, true); + gl.stencilFunc(gl.EQUAL, this.stencilMaskStack.length, this._getBitwiseMask()); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP); + } + + /** + * Fill 1s equal to the number of acitve stencil masks. + * + * @return {number} The bitwise mask. + */ + _getBitwiseMask() + { + return (1 << this.stencilMaskStack.length) - 1; + } + + /** + * Destroys the mask stack. + * + */ + destroy() + { + super.destroy(this); + + this.stencilMaskStack = null; + } +} diff --git a/packages/core/src/projection/ProjectionSystem.js b/packages/core/src/projection/ProjectionSystem.js new file mode 100644 index 0000000..6e63b73 --- /dev/null +++ b/packages/core/src/projection/ProjectionSystem.js @@ -0,0 +1,73 @@ +import System from '../System'; +import { Matrix } from '@pixi/math'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class ProjectionSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.projectionMatrix = new Matrix(); + } + + update(destinationFrame, sourceFrame, resolution, root) + { + this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; + this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; + + this.calculateProjection(this.destinationFrame, this.sourceFrame, resolution, root); + + this.renderer.globalUniforms.uniforms.projectionMatrix = this.projectionMatrix; + this.renderer.globalUniforms.update(); + } + + /** + * Updates the projection matrix based on a projection frame (which is a rectangle) + * + * @param {Rectangle} destinationFrame - The destination frame. + * @param {Rectangle} sourceFrame - The source frame. + */ + calculateProjection(destinationFrame, sourceFrame, resolution, root) + { + const pm = this.projectionMatrix; + + // I don't think we will need this line.. + // pm.identity(); + + if (!root) + { + pm.a = (1 / destinationFrame.width * 2) * resolution; + pm.d = (1 / destinationFrame.height * 2) * resolution; + + pm.tx = -1 - (sourceFrame.x * pm.a); + pm.ty = -1 - (sourceFrame.y * pm.d); + } + else + { + pm.a = (1 / destinationFrame.width * 2) * resolution; + pm.d = (-1 / destinationFrame.height * 2) * resolution; + + pm.tx = -1 - (sourceFrame.x * pm.a); + pm.ty = 1 - (sourceFrame.y * pm.d); + } + } + + /** + * Sets the transform of the active render target to the given matrix + * + * @param {PIXI.Matrix} matrix - The transformation matrix + */ + setTransform()// matrix) + { + // this._activeRenderTarget.transform = matrix; + } +} diff --git a/packages/core/src/renderTexture/BaseRenderTexture.js b/packages/core/src/renderTexture/BaseRenderTexture.js new file mode 100644 index 0000000..88eb83b --- /dev/null +++ b/packages/core/src/renderTexture/BaseRenderTexture.js @@ -0,0 +1,137 @@ +import BaseTexture from '../textures/BaseTexture'; +import FrameBuffer from '../framebuffer/FrameBuffer'; + +/** + * A BaseRenderTexture is a special texture that allows any PixiJS display object to be rendered to it. + * + * __Hint__: All DisplayObjects (i.e. Sprites) that render to a BaseRenderTexture should be preloaded + * otherwise black rectangles will be drawn instead. + * + * A BaseRenderTexture takes a snapshot of any Display Object given to its render method. The position + * and rotation of the given Display Objects is ignored. For example: + * + * ```js + * let renderer = PIXI.autoDetectRenderer(1024, 1024, { view: canvas, ratio: 1 }); + * let baseRenderTexture = new PIXI.BaseRenderTexture(renderer, 800, 600); + * let sprite = PIXI.Sprite.fromImage("spinObj_01.png"); + * + * sprite.position.x = 800/2; + * sprite.position.y = 600/2; + * sprite.anchor.x = 0.5; + * sprite.anchor.y = 0.5; + * + * baseRenderTexture.render(sprite); + * ``` + * + * The Sprite in this case will be rendered using its local transform. To render this sprite at 0,0 + * you can clear the transform + * + * ```js + * + * sprite.setTransform() + * + * let baseRenderTexture = new PIXI.BaseRenderTexture(100, 100); + * let renderTexture = new PIXI.RenderTexture(baseRenderTexture); + * + * renderer.render(sprite, renderTexture); // Renders to center of RenderTexture + * ``` + * + * @class + * @extends PIXI.BaseTexture + * @memberof PIXI + */ +export default class BaseRenderTexture extends BaseTexture +{ + /** + * @param {object} [options] + * @param {number} [options.width=100] - The width of the base render texture + * @param {number} [options.height=100] - The height of the base render texture + * @param {PIXI.SCALE_MODES} [options.scaleMode] - See {@link PIXI.SCALE_MODES} for possible values + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the texture being generated + */ + constructor(options) + { + if (typeof options === 'number') + { + /* eslint-disable prefer-rest-params */ + // Backward compatibility of signature + const width = arguments[0]; + const height = arguments[1]; + const scaleMode = arguments[2]; + const resolution = arguments[3]; + + options = { width, height, scaleMode, resolution }; + /* eslint-enable prefer-rest-params */ + } + + super(null, options); + + const { width, height } = options || {}; + + // Set defaults + this.mipmap = false; + this.width = Math.ceil(width) || 100; + this.height = Math.ceil(height) || 100; + this.valid = true; + + /** + * A map of renderer IDs to webgl renderTargets + * + * @private + * @member {object} + */ + // this._glRenderTargets = {}; + + /** + * A reference to the canvas render target (we only need one as this can be shared across renderers) + * + * @private + * @member {object} + */ + this._canvasRenderTarget = null; + + this.clearColor = [0, 0, 0, 0]; + + this.frameBuffer = new FrameBuffer(width * this.resolution, height * this.resolution) + .addColorTexture(0, this); + + // TODO - could this be added the systems? + + /** + * The data structure for the stencil masks + * + * @member {PIXI.Graphics[]} + */ + this.stencilMaskStack = []; + + /** + * The data structure for the filters + * + * @member {PIXI.Graphics[]} + */ + this.filterStack = [{}]; + } + + /** + * Resizes the BaseRenderTexture. + * + * @param {number} width - The width to resize to. + * @param {number} height - The height to resize to. + */ + resize(width, height) + { + width = Math.ceil(width); + height = Math.ceil(height); + this.frameBuffer.resize(width * this.resolution, height * this.resolution); + } + + /** + * Destroys this texture + * + */ + destroy() + { + super.destroy(true); + this.renderer = null; + } +} diff --git a/packages/core/src/renderTexture/RenderTexture.js b/packages/core/src/renderTexture/RenderTexture.js new file mode 100644 index 0000000..f1ecf1f --- /dev/null +++ b/packages/core/src/renderTexture/RenderTexture.js @@ -0,0 +1,153 @@ +import BaseRenderTexture from './BaseRenderTexture'; +import Texture from '../textures/Texture'; + +/** + * A RenderTexture is a special texture that allows any PixiJS display object to be rendered to it. + * + * __Hint__: All DisplayObjects (i.e. Sprites) that render to a RenderTexture should be preloaded + * otherwise black rectangles will be drawn instead. + * + * A RenderTexture takes a snapshot of any Display Object given to its render method. For example: + * + * ```js + * let renderer = PIXI.autoDetectRenderer(1024, 1024, { view: canvas, ratio: 1 }); + * let renderTexture = PIXI.RenderTexture.create(800, 600); + * let sprite = PIXI.Sprite.fromImage("spinObj_01.png"); + * + * sprite.position.x = 800/2; + * sprite.position.y = 600/2; + * sprite.anchor.x = 0.5; + * sprite.anchor.y = 0.5; + * + * renderer.render(sprite, renderTexture); + * ``` + * + * The Sprite in this case will be rendered using its local transform. To render this sprite at 0,0 + * you can clear the transform + * + * ```js + * + * sprite.setTransform() + * + * let renderTexture = new PIXI.RenderTexture.create(100, 100); + * + * renderer.render(sprite, renderTexture); // Renders to center of RenderTexture + * ``` + * + * @class + * @extends PIXI.Texture + * @memberof PIXI + */ +export default class RenderTexture extends Texture +{ + /** + * @param {PIXI.BaseRenderTexture} baseRenderTexture - The renderer used for this RenderTexture + * @param {PIXI.Rectangle} [frame] - The rectangle frame of the texture to show + */ + constructor(baseRenderTexture, frame) + { + // support for legacy.. + let _legacyRenderer = null; + + if (!(baseRenderTexture instanceof BaseRenderTexture)) + { + /* eslint-disable prefer-rest-params, no-console */ + const width = arguments[1]; + const height = arguments[2]; + const scaleMode = arguments[3]; + const resolution = arguments[4]; + + // we have an old render texture.. + console.warn(`Please use RenderTexture.create(${width}, ${height}) instead of the ctor directly.`); + _legacyRenderer = arguments[0]; + /* eslint-enable prefer-rest-params, no-console */ + + frame = null; + baseRenderTexture = new BaseRenderTexture({ + width, + height, + scaleMode, + resolution, + }); + } + + /** + * The base texture object that this texture uses + * + * @member {BaseTexture} + */ + super(baseRenderTexture, frame); + + this.legacyRenderer = _legacyRenderer; + + /** + * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. + * + * @member {boolean} + */ + this.valid = true; + + /** + * FilterSystem temporary storage + * @private + * @member {PIXI.Rectangle} + */ + this.filterFrame = null; + + this._updateUvs(); + } + + /** + * Resizes the RenderTexture. + * + * @param {number} width - The width to resize to. + * @param {number} height - The height to resize to. + * @param {boolean} [resizeBaseTexture=true] - Should the baseTexture.width and height values be resized as well? + */ + resize(width, height, resizeBaseTexture = true) + { + width = Math.ceil(width); + height = Math.ceil(height); + + // TODO - could be not required.. + this.valid = (width > 0 && height > 0); + + this._frame.width = this.orig.width = width; + this._frame.height = this.orig.height = height; + + if (resizeBaseTexture) + { + this.baseTexture.resize(width, height); + } + + this._updateUvs(); + } + + /** + * A short hand way of creating a render texture. + * + * @param {object} [options] - Options + * @param {number} [options.width=100] - The width of the render texture + * @param {number} [options.height=100] - The height of the render texture + * @param {number} [options.scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the texture being generated + * @return {PIXI.RenderTexture} The new render texture + */ + static create(options) + { + // fallback, old-style: create(width, height, scaleMode, resolution) + if (typeof options === 'number') + { + /* eslint-disable prefer-rest-params */ + options = { + width: options, + height: arguments[1], + scaleMode: arguments[2], + resolution: arguments[3], + }; + /* eslint-enable prefer-rest-params */ + } + + return new RenderTexture(new BaseRenderTexture(options)); + } +} diff --git a/packages/core/src/renderTexture/RenderTextureSystem.js b/packages/core/src/renderTexture/RenderTextureSystem.js new file mode 100644 index 0000000..8d0ca61 --- /dev/null +++ b/packages/core/src/renderTexture/RenderTextureSystem.js @@ -0,0 +1,115 @@ +import System from '../System'; +import { Rectangle } from '@pixi/math'; + +const tempRect = new Rectangle(); + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class RenderTextureSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.clearColor = renderer._backgroundColorRgba; + + // TODO moe this property somewhere else! + this.defaultMaskStack = []; + this.defaultFilterStack = [{}]; + + // empty render texture? + this.renderTexture = null; + + this.destinationFrame = new Rectangle(); + } + + bind(renderTexture, sourceFrame, destinationFrame) + { + // TODO - do we want this?? + if (this.renderTexture === renderTexture) return; + this.renderTexture = renderTexture; + + const renderer = this.renderer; + + if (renderTexture) + { + const baseTexture = renderTexture.baseTexture; + + if (!destinationFrame) + { + tempRect.width = baseTexture.realWidth; + tempRect.height = baseTexture.realHeight; + + destinationFrame = tempRect; + } + + if (!sourceFrame) + { + sourceFrame = destinationFrame; + } + + this.renderer.framebuffer.bind(baseTexture.frameBuffer, destinationFrame); + + this.renderer.projection.update(destinationFrame, sourceFrame, baseTexture.resolution, false); + this.renderer.stencil.setMaskStack(baseTexture.stencilMaskStack); + } + else + { + // TODO these validation checks happen deeper down.. + // thing they can be avoided.. + if (!destinationFrame) + { + tempRect.width = renderer.width; + tempRect.height = renderer.height; + + destinationFrame = tempRect; + } + + if (!sourceFrame) + { + sourceFrame = destinationFrame; + } + + renderer.framebuffer.bind(null, destinationFrame); + + // TODO store this.. + this.renderer.projection.update(destinationFrame, sourceFrame, this.renderer.resolution, true); + this.renderer.stencil.setMaskStack(this.defaultMaskStack); + } + + this.destinationFrame.copyFrom(destinationFrame); + } + + /** + * Erases the render texture and fills the drawing area with a colour + * + * @param {number} [clearColor] - The colour + * @return {PIXI.Renderer} Returns itself. + */ + clear(clearColor) + { + if (this.renderTexture) + { + clearColor = clearColor || this.renderTexture.baseTexture.clearColor; + } + else + { + clearColor = clearColor || this.clearColor; + } + + this.renderer.framebuffer.clear(clearColor[0], clearColor[1], clearColor[2], clearColor[3]); + } + + resize()// screenWidth, screenHeight) + { + // resize the root only! + this.bind(null); + } +} diff --git a/packages/core/src/renderers/AbstractRenderer.js b/packages/core/src/renderers/AbstractRenderer.js deleted file mode 100644 index 928670f..0000000 --- a/packages/core/src/renderers/AbstractRenderer.js +++ /dev/null @@ -1,353 +0,0 @@ -import { hex2string, hex2rgb } from '@pixi/utils'; -import { Matrix, Rectangle } from '@pixi/math'; -import { RENDERER_TYPE } from '@pixi/constants'; -import { settings } from '@pixi/settings'; -import { Container } from '@pixi/display'; -import RenderTexture from '../textures/RenderTexture'; -import EventEmitter from 'eventemitter3'; - -const tempMatrix = new Matrix(); - -/** - * 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 EventEmitter - * @memberof PIXI - */ -export default class AbstractRenderer extends EventEmitter -{ - // eslint-disable-next-line valid-jsdoc - /** - * @param {string} system - The name of the system this renderer is for. - * @param {object} [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.transparent=false] - If the render view is transparent, default false - * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false - * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) - * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. The - * resolution of the renderer retina would be 2. - * @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 {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when rendering, - * stopping pixel interpolation. - */ - constructor(system, options, arg2, arg3) - { - super(); - - // Support for constructor(system, screenWidth, screenHeight, options) - if (typeof options === 'number') - { - options = Object.assign({ - width: options, - height: arg2 || settings.RENDER_OPTIONS.height, - }, arg3); - } - - // 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 = RENDERER_TYPE.UNKNOWN; - - /** - * Measurements of the screen. (0, 0, screenWidth, screenHeight) - * - * Its safe to use as filterArea or hitArea for 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 1 - */ - this.resolution = options.resolution || settings.RESOLUTION; - - /** - * Whether the render view is transparent - * - * @member {boolean} - */ - this.transparent = options.transparent; - - /** - * Whether css dimensions of canvas view should be resized to screen dimensions automatically - * - * @member {boolean} - */ - this.autoResize = options.autoResize || false; - - /** - * Tracks the blend modes useful for this renderer. - * - * @member {object} - */ - this.blendModes = null; - - /** - * 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; - - /** - * If true PixiJS will Math.floor() x/y values when rendering, stopping pixel interpolation. - * Handy for crisp pixel art and speed on legacy devices. - * - * @member {boolean} - */ - this.roundPixels = options.roundPixels; - - /** - * The background color as a number. - * - * @member {number} - * @private - */ - this._backgroundColor = 0x000000; - - /** - * The background color as an [R, G, B] array. - * - * @member {number[]} - * @private - */ - this._backgroundColorRgba = [0, 0, 0, 0]; - - /** - * The background color as a string. - * - * @member {string} - * @private - */ - this._backgroundColorString = '#000000'; - - this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter - - /** - * This temporary display object used as the parent of the currently being rendered item - * - * @member {PIXI.DisplayObject} - * @private - */ - this._tempDisplayObjectParent = new Container(); - - /** - * The last root object that the renderer tried to render. - * - * @member {PIXI.DisplayObject} - * @private - */ - this._lastObjectRendered = this._tempDisplayObjectParent; - - /** - * Collection of plugins. - * @readonly - * @member {object} - */ - this.plugins = {}; - } - - /** - * Initialize the plugins. - * - * @protected - * @param {object} staticMap - The dictionary of staticly saved plugins. - */ - initPlugins(staticMap) - { - 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() - { - return this.view.width; - } - - /** - * Same as view.height, actual number of pixels in the canvas by vertical - * - * @member {number} - * @readonly - * @default 600 - */ - get height() - { - return this.view.height; - } - - /** - * Resizes the screen and canvas to the specified width and height - * Canvas dimensions are multiplied by resolution - * - * @param {number} screenWidth - the new width of the screen - * @param {number} screenHeight - the new height of the screen - */ - resize(screenWidth, screenHeight) - { - this.screen.width = screenWidth; - this.screen.height = screenHeight; - - this.view.width = screenWidth * this.resolution; - this.view.height = screenHeight * this.resolution; - - if (this.autoResize) - { - this.view.style.width = `${screenWidth}px`; - this.view.style.height = `${screenHeight}px`; - } - } - - /** - * 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 {PIXI.DisplayObject} displayObject - The displayObject the object will be generated from - * @param {number} scaleMode - Should be one of the scaleMode consts - * @param {number} resolution - The resolution / device pixel ratio of the texture being generated - * @param {PIXI.Rectangle} [region] - The region of the displayObject, that shall be rendered, - * if no region is specified, defaults to the local bounds of the displayObject. - * @return {PIXI.Texture} a texture of the graphics object - */ - generateTexture(displayObject, scaleMode, resolution, region) - { - region = region || displayObject.getLocalBounds(); - - // 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(region.width | 0, region.height | 0, scaleMode, resolution); - - tempMatrix.tx = -region.x; - tempMatrix.ty = -region.y; - - this.render(displayObject, renderTexture, false, tempMatrix, true); - - return renderTexture; - } - - /** - * Removes everything from the renderer and optionally removes the Canvas DOM element. - * - * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. - */ - destroy(removeView) - { - 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); - } - - this.plugins = null; - - this.type = RENDERER_TYPE.UNKNOWN; - - this.view = null; - - this.screen = null; - - this.resolution = 0; - - this.transparent = false; - - this.autoResize = false; - - this.blendModes = null; - - this.options = null; - - this.preserveDrawingBuffer = false; - this.clearBeforeRender = false; - - this.roundPixels = false; - - this._backgroundColor = 0; - this._backgroundColorRgba = null; - this._backgroundColorString = null; - - this._tempDisplayObjectParent = null; - this._lastObjectRendered = null; - } - - /** - * The background color to fill if not transparent - * - * @member {number} - */ - get backgroundColor() - { - return this._backgroundColor; - } - - set backgroundColor(value) // eslint-disable-line require-jsdoc - { - this._backgroundColor = value; - this._backgroundColorString = hex2string(value); - hex2rgb(value, this._backgroundColorRgba); - } -} diff --git a/packages/core/src/renderers/Renderer.js b/packages/core/src/renderers/Renderer.js deleted file mode 100644 index a2020b1..0000000 --- a/packages/core/src/renderers/Renderer.js +++ /dev/null @@ -1,329 +0,0 @@ -import AbstractRenderer from './AbstractRenderer'; -import { sayHello } from '@pixi/utils'; -import MaskSystem from './systems/MaskSystem'; -import StencilSystem from './systems/StencilSystem'; -import FilterSystem from './systems/filter/FilterSystem'; -import FramebufferSystem from './systems/FramebufferSystem'; -import RenderTextureSystem from './systems/RenderTextureSystem'; -import TextureSystem from './systems/textures/TextureSystem'; -import ProjectionSystem from './systems/ProjectionSystem'; -import StateSystem from './systems/StateSystem'; -import GeometrySystem from './systems/geometry/GeometrySystem'; -import ShaderSystem from './systems/shader/ShaderSystem'; -import ContextSystem from './systems/ContextSystem'; -import BatchSystem from './systems/BatchSystem'; -import TextureGCSystem from './systems/textures/TextureGCSystem'; -import { RENDERER_TYPE } from '@pixi/constants'; -import UniformGroup from '../shader/UniformGroup'; -import { Matrix } from '@pixi/math'; -import Runner from 'mini-runner'; - -/** - * 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 Render works by automatically managing webGLBatchs. - * 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 :) - * - * @class - * @memberof PIXI - * @extends PIXI.AbstractRenderer - */ -export default class Renderer extends AbstractRenderer -{ - // eslint-disable-next-line valid-jsdoc - /** - * - * @param {object} [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.transparent=false] - If the render view is transparent, default false - * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false - * @param {boolean} [options.antialias=false] - sets antialias. If not available natively then FXAA - * antialiasing is used - * @param {boolean} [options.forceFXAA=false] - forces FXAA antialiasing to be used over native. - * FXAA is faster, but may not always look as great - * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. - * The resolution of the renderer retina would be 2. - * @param {boolean} [options.clearBeforeRender=true] - This sets if the renderer will clear - * the canvas or not before the new render pass. If you wish to set this to false, you *must* set - * preserveDrawingBuffer to `true`. - * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, - * enable this if you need to call toDataUrl on the webgl context. - * @param {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when - * rendering, stopping pixel interpolation. - * @param {number} [options.backgroundColor=0x000000] - The background color of the rendered area - * (shown if not transparent). - * @param {boolean} [options.legacy=false] - If true PixiJS will aim to ensure compatibility - * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. - * @param {string} [options.powerPreference] - Parameter passed to webgl context, set to "high-performance" - * for devices with dual graphics card - */ - constructor(options, arg2, arg3) - { - super('WebGL', options, arg2, arg3); - - /** - * The type of this renderer as a standardised const - * - * @member {number} - * @see PIXI.RENDERER_TYPE - */ - this.type = RENDERER_TYPE.WEBGL; - - // this will be set by the contextSystem (this.context) - this.gl = null; - this.CONTEXT_UID = 0; - this.legacy = !!options.legacy; - - // TODO legacy! - - // runners! - this.runners = { - destroy: new Runner('destroy'), - contextChange: new Runner('contextChange', 1), - reset: new Runner('reset'), - update: new Runner('update'), - postrender: new Runner('postrender'), - prerender: new Runner('prerender'), - resize: new Runner('resize', 2), - }; - - 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(GeometrySystem, 'geometry') - .addSystem(FramebufferSystem, 'framebuffer') - .addSystem(StencilSystem, 'stencil') - .addSystem(ProjectionSystem, 'projection') - .addSystem(TextureGCSystem, 'textureGC') - .addSystem(FilterSystem, 'filter') - .addSystem(RenderTextureSystem, 'renderTexture') - .addSystem(BatchSystem, 'batch'); - - this.initPlugins(Renderer.__plugins); - - /** - * The options passed in to create a new webgl context. - * - * @member {object} - * @private - */ - if (options.context) - { - this.context.initFromContext(options.context); - } - else - { - this.context.initFromOptions({ - alpha: this.transparent, - antialias: options.antialias, - premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', - stencil: true, - 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); - } - - addSystem(_class, name) - { - if (!name) - { - name = _class.name; - } - - // TODO - read name from class.name.. - - /* - if(name.includes('System')) - { - name = name.replace('System', ''); - name = name.charAt(0).toLowerCase() + name.slice(1); - } - */ - - const system = new _class(this); - - if (this[name]) - { - throw new Error(`Whoops! ${name} is already a manger`); - } - - this[name] = system; - - for (const i in this.runners) - { - this.runners[i].add(system); - } - - return this; - - /** - * 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. - */ - } - - /** - * Renders the object to its webGL view - * - * @param {PIXI.DisplayObject} displayObject - the object to be rendered - * @param {PIXI.RenderTexture} renderTexture - The render texture to render to. - * @param {boolean} [clear] - Should the canvas be cleared before the new render - * @param {PIXI.Transform} [transform] - A transform to apply to the render texture before rendering. - * @param {boolean} [skipUpdateTransform] - Should we skip the update transform pass? - */ - render(displayObject, renderTexture, clear, transform, skipUpdateTransform) - { - // can be handy to know! - this.renderingToScreen = !renderTexture; - - this.runners.prerender.run(); - this.emit('prerender'); - - // 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.parent; - - displayObject.parent = this._tempDisplayObjectParent; - displayObject.updateTransform(); - displayObject.parent = 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.run(); - - this.emit('postrender'); - } - - /** - * Resizes the webGL view to the specified width and height. - * - * @param {number} screenWidth - the new width of the screen - * @param {number} screenHeight - the new height of the screen - */ - resize(screenWidth, screenHeight) - { - AbstractRenderer.prototype.resize.call(this, screenWidth, screenHeight); - - this.runners.resize.run(screenWidth, screenHeight); - } - - /** - * Resets the WebGL state so you can render things however you fancy! - * - * @return {PIXI.Renderer} Returns itself. - */ - reset() - { - this.runners.reset.run(); - - return this; - } - - clear() - { - this.framebuffer.bind(); - this.framebuffer.clear(); - } - - /** - * Removes everything from the renderer (event listeners, spritebatch, etc...) - * - * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. - * See: https://github.com/pixijs/pixi.js/issues/2233 - */ - destroy(removeView) - { - this.runners.destroy.run(); - - // call base destroy - super.destroy(removeView); - - // TODO nullify all the managers.. - this.gl = null; - } - - /** - * 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 - * builds and excluding plugins. - * @name PIXI.Renderer#plugins - * @type {object} - * @readonly - * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. - * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. - * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. - * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. - */ - - /** - * Adds a plugin to the renderer. - * - * @method - * @param {string} pluginName - The name of the plugin. - * @param {Function} ctor - The constructor function or class for the plugin. - */ - static registerPlugin(pluginName, ctor) - { - Renderer.__plugins = Renderer.__plugins || {}; - Renderer.__plugins[pluginName] = ctor; - } -} diff --git a/packages/core/src/renderers/State.js b/packages/core/src/renderers/State.js deleted file mode 100644 index fa3f221..0000000 --- a/packages/core/src/renderers/State.js +++ /dev/null @@ -1,163 +0,0 @@ -/* eslint-disable max-len */ - -const BLEND = 0; -const OFFSET = 1; -const CULLING = 2; -const DEPTH_TEST = 3; -const WINDING = 4; - -/** - * This is a webGL state. It is passed The WebGL StateManager. - * Each mesh renderered may require webGL to be in a different state. - * For example you may want different blend mode or to enable polygon offsets - * - * @class - * @memberof PIXI - */ -export default class State -{ - /** - * - */ - constructor() - { - this.data = 0; - - this.blendMode = 0; - this.polygonOffset = 0; - - this.blend = true; - // this.depthTest = true; - } - - /** - * Activates blending of the computed fragment color values - * - * @member {boolean} - */ - get blend() - { - return !!(this.data & (1 << BLEND)); - } - - set blend(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << BLEND)) !== value) - { - this.data ^= (1 << BLEND); - } - } - - /** - * Activates adding an offset to depth values of polygon's fragments - * - * @member {boolean} - * @default false - */ - get offsets() - { - return !!(this.data & (1 << OFFSET)); - } - - set offsets(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << OFFSET)) !== value) - { - this.data ^= (1 << OFFSET); - } - } - - /** - * Activates culling of polygons. - * - * @member {boolean} - * @default false - */ - get culling() - { - return !!(this.data & (1 << CULLING)); - } - - set culling(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << CULLING)) !== value) - { - this.data ^= (1 << CULLING); - } - } - - /** - * Activates depth comparisons and updates to the depth buffer. - * - * @member {boolean} - * @default false - */ - get depthTest() - { - return !!(this.data & (1 << DEPTH_TEST)); - } - - set depthTest(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << DEPTH_TEST)) !== value) - { - this.data ^= (1 << DEPTH_TEST); - } - } - - /** - * Specifies whether or not front or back-facing polygons can be culled. - * @member {boolean} - * @default false - */ - get clockwiseFrontFace() - { - return !!(this.data & (1 << WINDING)); - } - - set clockwiseFrontFace(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << WINDING)) !== value) - { - this.data ^= (1 << WINDING); - } - } - - /** - * The blend mode to be applied when this state is set. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. - * Setting this mode to anything other than NO_BLEND will automatically switch blending on. - * - * @member {boolean} - * @default PIXI.BLEND_MODES.NORMAL - * @see PIXI.BLEND_MODES - */ - get blendMode() - { - return this._blendMode; - } - - set blendMode(value) // eslint-disable-line require-jsdoc - { - // 17 is NO BLEND - this.blend = (value !== 17); - this._blendMode = value; - } - - /** - * The polygon offset. Setting this property to anything other than 0 will automatically enable poygon offset fill. - * - * @member {number} - * @default 0 - */ - get polygonOffset() - { - return this._polygonOffset; - } - - set polygonOffset(value) // eslint-disable-line require-jsdoc - { - this.offsets = !!value; - this._polygonOffset = value; - } -} - diff --git a/packages/core/src/renderers/filters/Filter.js b/packages/core/src/renderers/filters/Filter.js deleted file mode 100644 index 2073b8d..0000000 --- a/packages/core/src/renderers/filters/Filter.js +++ /dev/null @@ -1,140 +0,0 @@ -import Shader from '../../shader/Shader'; -import Program from '../../shader/Program'; -import State from '../State'; -import { settings } from '@pixi/settings'; -// import extractUniformsFromSrc from './extractUniformsFromSrc'; -import defaultVertex from './defaultFilter.vert'; -import defaultFragment from './defaultFilter.frag'; - -// let math = require('../../math'); -/** - * @class - * @memberof PIXI - * @extends PIXI.Shader - */ -export default class Filter extends Shader -{ - /** - * @param {string} [vertexSrc] - The source of the vertex shader. - * @param {string} [fragmentSrc] - The source of the fragment shader. - * @param {object} [uniforms] - Custom uniforms to use to augment the built-in ones. - */ - constructor(vertexSrc, fragmentSrc, uniforms) - { - const program = Program.from(vertexSrc, fragmentSrc); - - super(program, uniforms); - - /** - * The padding of the filter. Some filters require extra space to breath such as a blur. - * Increasing this will add extra width and height to the bounds of the object that the - * filter is applied to. - * - * @member {number} - */ - this.padding = 0; - - /** - * The resolution of the filter. Setting this to be lower will lower the quality but - * increase the performance of the filter. - * - * @member {number} - */ - this.resolution = settings.RESOLUTION; - - /** - * If enabled is true the filter is applied, if false it will not. - * - * @member {boolean} - */ - this.enabled = true; - - /** - * If enabled, PixiJS will fit the filter area into boundaries for better performance. - * Switch it off if it does not work for specific shader. - * - * @member {boolean} - */ - this.autoFit = true; - - /** - * Legacy filters use position and uvs from attributes - * @member {boolean} - * @readonly - */ - this.legacy = !!this.program.attributeData.aTextureCoord; - - /** - * the webGL state the filter requires to render - * @member {PIXI.State} - */ - this.state = new State(); - } - - /** - * Applies the filter - * - * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from - * @param {PIXI.RenderTarget} input - The input render target. - * @param {PIXI.RenderTarget} output - The target to output to. - * @param {boolean} clear - Should the output be cleared before rendering to it - * @param {object} [currentState] - It's current state of filter. - * There are some useful properties in the currentState : - * target, filters, sourceFrame, destinationFrame, renderTarget, resolution - */ - apply(filterManager, input, output, clear, currentState, derp) // eslint-disable-line no-unused-vars - { - // do as you please! - - filterManager.applyFilter(this, input, output, clear, currentState, derp); - - // or just do a regular render.. - } - - /** - * Sets the blendmode of the filter - * - * @member {number} - * @default PIXI.BLEND_MODES.NORMAL - */ - get blendMode() - { - return this.state.blendMode; - } - - set blendMode(value) // eslint-disable-line require-jsdoc - { - this.state.blendMode = value; - } - - /** - * The default vertex shader source - * - * @static - * @constant - */ - static get defaultVertexSrc() - { - return defaultVertex; - } - - /** - * The default fragment shader source - * - * @static - * @constant - */ - static get defaultFragmentSrc() - { - return defaultFragment; - } -} - -/** - * Used for caching shader IDs - * - * @static - * @private - */ -Filter.SOURCE_KEY_MAP = {}; - diff --git a/packages/core/src/renderers/filters/defaultFilter.frag b/packages/core/src/renderers/filters/defaultFilter.frag deleted file mode 100644 index a14c1be..0000000 --- a/packages/core/src/renderers/filters/defaultFilter.frag +++ /dev/null @@ -1,22 +0,0 @@ -varying vec2 vTextureCoord; -varying vec2 vFilterCoord; - -uniform sampler2D uSampler; -uniform sampler2D filterSampler; - -void main(void){ - vec4 masky = texture2D(filterSampler, vFilterCoord); - vec4 sample = texture2D(uSampler, vTextureCoord); - vec4 color; - if(mod(vFilterCoord.x, 1.0) > 0.5) - { - color = vec4(1.0, 0.0, 0.0, 1.0); - } - else - { - color = vec4(0.0, 1.0, 0.0, 1.0); - } - // gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0); - gl_FragColor = mix(sample, masky, 0.5); - gl_FragColor *= sample.a; -} diff --git a/packages/core/src/renderers/filters/defaultFilter.vert b/packages/core/src/renderers/filters/defaultFilter.vert deleted file mode 100644 index e1febca..0000000 --- a/packages/core/src/renderers/filters/defaultFilter.vert +++ /dev/null @@ -1,14 +0,0 @@ -attribute vec2 aVertexPosition; -attribute vec2 aTextureCoord; - -uniform mat3 projectionMatrix; -uniform mat3 filterMatrix; - -varying vec2 vTextureCoord; -varying vec2 vFilterCoord; - -void main(void){ - gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); - vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy; - vTextureCoord = aTextureCoord ; -} diff --git a/packages/core/src/renderers/filters/filterTransforms.js b/packages/core/src/renderers/filters/filterTransforms.js deleted file mode 100644 index 87a3cd6..0000000 --- a/packages/core/src/renderers/filters/filterTransforms.js +++ /dev/null @@ -1,50 +0,0 @@ -import { Matrix } from '@pixi/math'; - -/** - * Calculates the mapped matrix - * @param filterArea {Rectangle} The filter area - * @param sprite {Sprite} the target sprite - * @param outputMatrix {Matrix} @alvin - * @private - */ -// this returns a matrix that will normalise map filter cords in the filter to screen space -export function calculateScreenSpaceMatrix(outputMatrix, filterArea, textureSize) -{ - // TODO unwrap? - const mappedMatrix = outputMatrix.identity(); - - mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); - - mappedMatrix.scale(textureSize.width, textureSize.height); - - return mappedMatrix; -} - -export function calculateNormalizedScreenSpaceMatrix(outputMatrix, filterArea, textureSize) -{ - const mappedMatrix = outputMatrix.identity(); - - mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); - - const translateScaleX = (textureSize.width / filterArea.width); - const translateScaleY = (textureSize.height / filterArea.height); - - mappedMatrix.scale(translateScaleX, translateScaleY); - - return mappedMatrix; -} - -// this will map the filter coord so that a texture can be used based on the transform of a sprite -export function calculateSpriteMatrix(outputMatrix, filterArea, textureSize, sprite) -{ - const orig = sprite._texture.orig; - const mappedMatrix = outputMatrix.set(textureSize.width, 0, 0, textureSize.height, filterArea.x, filterArea.y); - const worldTransform = sprite.worldTransform.copyTo(Matrix.TEMP_MATRIX); - - worldTransform.invert(); - mappedMatrix.prepend(worldTransform); - mappedMatrix.scale(1.0 / orig.width, 1.0 / orig.height); - mappedMatrix.translate(sprite.anchor.x, sprite.anchor.y); - - return mappedMatrix; -} diff --git a/packages/core/src/renderers/filters/spriteMask/SpriteMaskFilter.js b/packages/core/src/renderers/filters/spriteMask/SpriteMaskFilter.js deleted file mode 100644 index d5a91a4..0000000 --- a/packages/core/src/renderers/filters/spriteMask/SpriteMaskFilter.js +++ /dev/null @@ -1,63 +0,0 @@ -import Filter from '../Filter'; -import { Matrix } from '@pixi/math'; -import vertex from './spriteMaskFilter.vert'; -import fragment from './spriteMaskFilter.frag'; -import { default as TextureMatrix } from '../../../textures/TextureMatrix'; - -/** - * The SpriteMaskFilter class - * - * @class - * @extends PIXI.Filter - * @memberof PIXI - */ -export default class SpriteMaskFilter extends Filter -{ - /** - * @param {PIXI.Sprite} sprite - the target sprite - */ - constructor(sprite) - { - const maskMatrix = new Matrix(); - - super(vertex, fragment); - - sprite.renderable = false; - - this.maskSprite = sprite; - this.maskMatrix = maskMatrix; - } - - /** - * Applies the filter - * - * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from - * @param {PIXI.RenderTarget} input - The input render target. - * @param {PIXI.RenderTarget} output - The target to output to. - */ - apply(filterManager, input, output) - { - const maskSprite = this.maskSprite; - const tex = this.maskSprite.texture; - - if (!tex.valid) - { - return; - } - if (!tex.transform) - { - // margin = 0.0, let it bleed a bit, shader code becomes easier - // assuming that atlas textures were made with 1-pixel padding - tex.transform = new TextureMatrix(tex, 0.0); - } - tex.transform.update(); - - this.uniforms.mask = tex; - this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite) - .prepend(tex.transform.mapCoord); - this.uniforms.alpha = maskSprite.worldAlpha; - this.uniforms.maskClamp = tex.transform.uClampFrame; - - filterManager.applyFilter(this, input, output); - } -} diff --git a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.frag b/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.frag deleted file mode 100644 index 0e4aef8..0000000 --- a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.frag +++ /dev/null @@ -1,23 +0,0 @@ -varying vec2 vMaskCoord; -varying vec2 vTextureCoord; - -uniform sampler2D uSampler; -uniform sampler2D mask; -uniform float alpha; -uniform vec4 maskClamp; - -void main(void) -{ - float clip = step(3.5, - step(maskClamp.x, vMaskCoord.x) + - step(maskClamp.y, vMaskCoord.y) + - step(vMaskCoord.x, maskClamp.z) + - step(vMaskCoord.y, maskClamp.w)); - - vec4 original = texture2D(uSampler, vTextureCoord); - vec4 masky = texture2D(mask, vMaskCoord); - - original *= (masky.r * masky.a * alpha * clip); - - gl_FragColor = original; -} diff --git a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.vert b/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.vert deleted file mode 100644 index d23a30c..0000000 --- a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.vert +++ /dev/null @@ -1,16 +0,0 @@ -attribute vec2 aVertexPosition; -attribute vec2 aTextureCoord; - -uniform mat3 projectionMatrix; -uniform mat3 otherMatrix; - -varying vec2 vMaskCoord; -varying vec2 vTextureCoord; - -void main(void) -{ - gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); - - vTextureCoord = aTextureCoord; - vMaskCoord = ( otherMatrix * vec3( aTextureCoord, 1.0) ).xy; -} diff --git a/packages/core/src/renderers/systems/BatchSystem.js b/packages/core/src/renderers/systems/BatchSystem.js deleted file mode 100644 index 70e9518..0000000 --- a/packages/core/src/renderers/systems/BatchSystem.js +++ /dev/null @@ -1,68 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import ObjectRenderer from '../utils/ObjectRenderer'; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ - -export default class BatchSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this manager works for. - */ - constructor(renderer) - { - super(renderer); - - /** - * An empty renderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.emptyRenderer = new ObjectRenderer(renderer); - - /** - * The currently active ObjectRenderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.currentRenderer = this.emptyRenderer; - } - - /** - * Changes the current renderer to the one given in parameter - * - * @param {PIXI.ObjectRenderer} objectRenderer - The object renderer to use. - */ - setObjectRenderer(objectRenderer) - { - if (this.currentRenderer === objectRenderer) - { - return; - } - - this.currentRenderer.stop(); - this.currentRenderer = objectRenderer; - - this.renderer.state.setState(objectRenderer.state); - - this.currentRenderer.start(); - } - - /** - * This should be called if you wish to do some custom rendering - * It will basically render anything that may be batched up such as sprites - * - */ - flush() - { - this.setObjectRenderer(this.emptyRenderer); - } - - reset() - { - this.setObjectRenderer(this.emptyRenderer); - } -} diff --git a/packages/core/src/renderers/systems/ContextSystem.js b/packages/core/src/renderers/systems/ContextSystem.js deleted file mode 100644 index 43bf827..0000000 --- a/packages/core/src/renderers/systems/ContextSystem.js +++ /dev/null @@ -1,186 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { settings } from '@pixi/settings'; - -let CONTEXT_UID = 0; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class ContextSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this.webGLVersion = 1; - - this.handleContextLost = this.handleContextLost.bind(this); - this.handleContextRestored = this.handleContextRestored.bind(this); - - this.extensions = {}; - - renderer.view.addEventListener('webglcontextlost', this.handleContextLost, false); - renderer.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); - } - - get isLost() - { - return (!this.gl || this.gl.isContextLost()); - } - - contextChange(gl) - { - this.gl = gl; - - // restore a context if it was previously lost - if (gl.isContextLost() && gl.getExtension('WEBGL_lose_context')) - { - gl.getExtension('WEBGL_lose_context').restoreContext(); - } - } - - initFromContext(gl) - { - this.gl = gl; - this.validateContext(gl); - this.renderer.gl = gl; - this.renderer.CONTEXT_UID = CONTEXT_UID++; - this.renderer.runners.contextChange.run(gl); - } - - initFromOptions(options) - { - const gl = this.createContext(this.renderer.view, options); - - this.initFromContext(gl); - } - - /** - * Helper class to create a webGL Context - * - * @class - * @memberof PIXI.glCore - * @param canvas {HTMLCanvasElement} the canvas element that we will get the context from - * @param options {Object} An options object that gets passed in to the canvas element containing the context attributes, - * see https://developer.mozilla.org/en/docs/Web/API/HTMLCanvasElement/getContext for the options available - * @return {WebGLRenderingContext} the WebGL context - */ - createContext(canvas, options) - { - let gl; - - if (settings.PREFER_WEBGL_2) - { - gl = canvas.getContext('webgl2', options); - } - - if (gl) - { - this.webGLVersion = 2; - } - else - { - this.webGLVersion = 1; - - gl = canvas.getContext('webgl', options) - || canvas.getContext('experimental-webgl', options); - - if (!gl) - { - // fail, not able to get a context - throw new Error('This browser does not support webGL. Try using the canvas renderer'); - } - } - - this.gl = gl; - - this.getExtensions(); - - return gl; - } - - getExtensions() - { - // time to set up default etensions that pixi uses.. - const gl = this.gl; - const extensions = this.extensions; - - if (this.webGLVersion === 1) - { - extensions.drawBuffers = gl.getExtension('WEBGL_draw_buffers'); - extensions.depthTexture = gl.getExtension('WEBKIT_WEBGL_depth_texture'); - extensions.floatTexture = gl.getExtension('OES_texture_float'); - extensions.loseContext = gl.getExtension('WEBGL_lose_context'); - - extensions.vertexArrayObject = gl.getExtension('OES_vertex_array_object') - || gl.getExtension('MOZ_OES_vertex_array_object') - || gl.getExtension('WEBKIT_OES_vertex_array_object'); - } - - // we don't use any specific WebGL 2 ones yet! - } - - /** - * Handles a lost webgl context - * - * @private - * @param {WebGLContextEvent} event - The context lost event. - */ - handleContextLost(event) - { - event.preventDefault(); - } - - /** - * Handles a restored webgl context - * - * @private - */ - handleContextRestored() - { - this.renderer.runners.contextChange.run(this.gl); - } - - destroy() - { - const view = this.renderer.view; - - // remove listeners - view.removeEventListener('webglcontextlost', this.handleContextLost); - view.removeEventListener('webglcontextrestored', this.handleContextRestored); - - this.gl.useProgram(null); - - if (this.extensions.loseContext) - { - this.extensions.loseContext.loseContext(); - } - } - - postrender() - { - this.gl.flush(); - } - - validateContext(gl) - { - const attributes = gl.getContextAttributes(); - - // this is going to be fairly simple for now.. but at least we have room to grow! - if (!attributes.stencil) - { - /* eslint-disable max-len */ - - /* eslint-disable no-console */ - console.warn('Provided WebGL context does not have a stencil buffer, masks may not render correctly'); - /* eslint-enable no-console */ - - /* eslint-enable max-len */ - } - } -} diff --git a/packages/core/src/AbstractRenderer.js b/packages/core/src/AbstractRenderer.js new file mode 100644 index 0000000..64b2eb1 --- /dev/null +++ b/packages/core/src/AbstractRenderer.js @@ -0,0 +1,353 @@ +import { hex2string, hex2rgb } from '@pixi/utils'; +import { Matrix, Rectangle } from '@pixi/math'; +import { RENDERER_TYPE } from '@pixi/constants'; +import { settings } from '@pixi/settings'; +import { Container } from '@pixi/display'; +import RenderTexture from './renderTexture/RenderTexture'; +import EventEmitter from 'eventemitter3'; + +const tempMatrix = new Matrix(); + +/** + * 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 EventEmitter + * @memberof PIXI + */ +export default class AbstractRenderer extends EventEmitter +{ + // eslint-disable-next-line valid-jsdoc + /** + * @param {string} system - The name of the system this renderer is for. + * @param {object} [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.transparent=false] - If the render view is transparent, default false + * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false + * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. The + * resolution of the renderer retina would be 2. + * @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 {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when rendering, + * stopping pixel interpolation. + */ + constructor(system, options, arg2, arg3) + { + super(); + + // Support for constructor(system, screenWidth, screenHeight, options) + if (typeof options === 'number') + { + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + }, arg3); + } + + // 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 = RENDERER_TYPE.UNKNOWN; + + /** + * Measurements of the screen. (0, 0, screenWidth, screenHeight) + * + * Its safe to use as filterArea or hitArea for 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 1 + */ + this.resolution = options.resolution || settings.RESOLUTION; + + /** + * Whether the render view is transparent + * + * @member {boolean} + */ + this.transparent = options.transparent; + + /** + * Whether css dimensions of canvas view should be resized to screen dimensions automatically + * + * @member {boolean} + */ + this.autoResize = options.autoResize || false; + + /** + * Tracks the blend modes useful for this renderer. + * + * @member {object} + */ + this.blendModes = null; + + /** + * 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; + + /** + * If true PixiJS will Math.floor() x/y values when rendering, stopping pixel interpolation. + * Handy for crisp pixel art and speed on legacy devices. + * + * @member {boolean} + */ + this.roundPixels = options.roundPixels; + + /** + * The background color as a number. + * + * @member {number} + * @private + */ + this._backgroundColor = 0x000000; + + /** + * The background color as an [R, G, B] array. + * + * @member {number[]} + * @private + */ + this._backgroundColorRgba = [0, 0, 0, 0]; + + /** + * The background color as a string. + * + * @member {string} + * @private + */ + this._backgroundColorString = '#000000'; + + this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter + + /** + * This temporary display object used as the parent of the currently being rendered item + * + * @member {PIXI.DisplayObject} + * @private + */ + this._tempDisplayObjectParent = new Container(); + + /** + * The last root object that the renderer tried to render. + * + * @member {PIXI.DisplayObject} + * @private + */ + this._lastObjectRendered = this._tempDisplayObjectParent; + + /** + * Collection of plugins. + * @readonly + * @member {object} + */ + this.plugins = {}; + } + + /** + * Initialize the plugins. + * + * @protected + * @param {object} staticMap - The dictionary of staticly saved plugins. + */ + initPlugins(staticMap) + { + 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() + { + return this.view.width; + } + + /** + * Same as view.height, actual number of pixels in the canvas by vertical + * + * @member {number} + * @readonly + * @default 600 + */ + get height() + { + return this.view.height; + } + + /** + * Resizes the screen and canvas to the specified width and height + * Canvas dimensions are multiplied by resolution + * + * @param {number} screenWidth - the new width of the screen + * @param {number} screenHeight - the new height of the screen + */ + resize(screenWidth, screenHeight) + { + this.screen.width = screenWidth; + this.screen.height = screenHeight; + + this.view.width = screenWidth * this.resolution; + this.view.height = screenHeight * this.resolution; + + if (this.autoResize) + { + this.view.style.width = `${screenWidth}px`; + this.view.style.height = `${screenHeight}px`; + } + } + + /** + * 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 {PIXI.DisplayObject} displayObject - The displayObject the object will be generated from + * @param {number} scaleMode - Should be one of the scaleMode consts + * @param {number} resolution - The resolution / device pixel ratio of the texture being generated + * @param {PIXI.Rectangle} [region] - The region of the displayObject, that shall be rendered, + * if no region is specified, defaults to the local bounds of the displayObject. + * @return {PIXI.Texture} a texture of the graphics object + */ + generateTexture(displayObject, scaleMode, resolution, region) + { + region = region || displayObject.getLocalBounds(); + + // 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(region.width | 0, region.height | 0, scaleMode, resolution); + + tempMatrix.tx = -region.x; + tempMatrix.ty = -region.y; + + this.render(displayObject, renderTexture, false, tempMatrix, true); + + return renderTexture; + } + + /** + * Removes everything from the renderer and optionally removes the Canvas DOM element. + * + * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. + */ + destroy(removeView) + { + 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); + } + + this.plugins = null; + + this.type = RENDERER_TYPE.UNKNOWN; + + this.view = null; + + this.screen = null; + + this.resolution = 0; + + this.transparent = false; + + this.autoResize = false; + + this.blendModes = null; + + this.options = null; + + this.preserveDrawingBuffer = false; + this.clearBeforeRender = false; + + this.roundPixels = false; + + this._backgroundColor = 0; + this._backgroundColorRgba = null; + this._backgroundColorString = null; + + this._tempDisplayObjectParent = null; + this._lastObjectRendered = null; + } + + /** + * The background color to fill if not transparent + * + * @member {number} + */ + get backgroundColor() + { + return this._backgroundColor; + } + + set backgroundColor(value) // eslint-disable-line require-jsdoc + { + this._backgroundColor = value; + this._backgroundColorString = hex2string(value); + hex2rgb(value, this._backgroundColorRgba); + } +} diff --git a/packages/core/src/Renderer.js b/packages/core/src/Renderer.js new file mode 100644 index 0000000..2227e78 --- /dev/null +++ b/packages/core/src/Renderer.js @@ -0,0 +1,338 @@ +import AbstractRenderer from './AbstractRenderer'; +import { sayHello } from '@pixi/utils'; +import MaskSystem from './mask/MaskSystem'; +import StencilSystem from './mask/StencilSystem'; +import FilterSystem from './filters/FilterSystem'; +import FramebufferSystem from './framebuffer/FramebufferSystem'; +import RenderTextureSystem from './renderTexture/RenderTextureSystem'; +import TextureSystem from './textures/TextureSystem'; +import ProjectionSystem from './projection/ProjectionSystem'; +import StateSystem from './state/StateSystem'; +import GeometrySystem from './geometry/GeometrySystem'; +import ShaderSystem from './shader/ShaderSystem'; +import ContextSystem from './context/ContextSystem'; +import BatchSystem from './batch/BatchSystem'; +import TextureGCSystem from './textures/TextureGCSystem'; +import { RENDERER_TYPE } from '@pixi/constants'; +import UniformGroup from './shader/UniformGroup'; +import { Matrix } from '@pixi/math'; +import Runner from 'mini-runner'; + +/** + * 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 Render works by automatically managing webGLBatchs. + * 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 :) + * + * @class + * @memberof PIXI + * @extends PIXI.AbstractRenderer + */ +export default class Renderer extends AbstractRenderer +{ + // eslint-disable-next-line valid-jsdoc + /** + * + * @param {object} [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.transparent=false] - If the render view is transparent, default false + * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false + * @param {boolean} [options.antialias=false] - sets antialias. If not available natively then FXAA + * antialiasing is used + * @param {boolean} [options.forceFXAA=false] - forces FXAA antialiasing to be used over native. + * FXAA is faster, but may not always look as great + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. + * The resolution of the renderer retina would be 2. + * @param {boolean} [options.clearBeforeRender=true] - This sets if the renderer will clear + * the canvas or not before the new render pass. If you wish to set this to false, you *must* set + * preserveDrawingBuffer to `true`. + * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, + * enable this if you need to call toDataUrl on the webgl context. + * @param {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when + * rendering, stopping pixel interpolation. + * @param {number} [options.backgroundColor=0x000000] - The background color of the rendered area + * (shown if not transparent). + * @param {boolean} [options.legacy=false] - If true PixiJS will aim to ensure compatibility + * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. + * @param {string} [options.powerPreference] - Parameter passed to webgl context, set to "high-performance" + * for devices with dual graphics card + */ + constructor(options, arg2, arg3) + { + super('WebGL', options, arg2, arg3); + + /** + * The type of this renderer as a standardised const + * + * @member {number} + * @see PIXI.RENDERER_TYPE + */ + this.type = RENDERER_TYPE.WEBGL; + + // this will be set by the contextSystem (this.context) + this.gl = null; + this.CONTEXT_UID = 0; + this.legacy = !!options.legacy; + + // TODO legacy! + + // runners! + this.runners = { + destroy: new Runner('destroy'), + contextChange: new Runner('contextChange', 1), + reset: new Runner('reset'), + update: new Runner('update'), + postrender: new Runner('postrender'), + prerender: new Runner('prerender'), + resize: new Runner('resize', 2), + }; + + 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(GeometrySystem, 'geometry') + .addSystem(FramebufferSystem, 'framebuffer') + .addSystem(StencilSystem, 'stencil') + .addSystem(ProjectionSystem, 'projection') + .addSystem(TextureGCSystem, 'textureGC') + .addSystem(FilterSystem, 'filter') + .addSystem(RenderTextureSystem, 'renderTexture') + .addSystem(BatchSystem, 'batch'); + + this.initPlugins(Renderer.__plugins); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ + if (options.context) + { + this.context.initFromContext(options.context); + } + else + { + this.context.initFromOptions({ + alpha: this.transparent, + antialias: options.antialias, + premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', + stencil: true, + 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); + } + + /** + * Add a new system to the renderer. + * @param {class} ClassRef - Class reference + * @param {string} [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. + * @return {PIXI.Renderer} Return instance of renderer + */ + addSystem(ClassRef, name) + { + if (!name) + { + name = ClassRef.name; + } + + // TODO - read name from class.name.. + + /* + if(name.includes('System')) + { + name = name.replace('System', ''); + name = name.charAt(0).toLowerCase() + name.slice(1); + } + */ + + const system = new ClassRef(this); + + if (this[name]) + { + throw new Error(`Whoops! ${name} is already a manger`); + } + + this[name] = system; + + for (const i in this.runners) + { + this.runners[i].add(system); + } + + return this; + + /** + * 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. + */ + } + + /** + * Renders the object to its webGL view + * + * @param {PIXI.DisplayObject} displayObject - the object to be rendered + * @param {PIXI.RenderTexture} renderTexture - The render texture to render to. + * @param {boolean} [clear] - Should the canvas be cleared before the new render + * @param {PIXI.Transform} [transform] - A transform to apply to the render texture before rendering. + * @param {boolean} [skipUpdateTransform] - Should we skip the update transform pass? + */ + render(displayObject, renderTexture, clear, transform, skipUpdateTransform) + { + // can be handy to know! + this.renderingToScreen = !renderTexture; + + this.runners.prerender.run(); + this.emit('prerender'); + + // 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.parent; + + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = 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.run(); + + this.emit('postrender'); + } + + /** + * Resizes the webGL view to the specified width and height. + * + * @param {number} screenWidth - the new width of the screen + * @param {number} screenHeight - the new height of the screen + */ + resize(screenWidth, screenHeight) + { + AbstractRenderer.prototype.resize.call(this, screenWidth, screenHeight); + + this.runners.resize.run(screenWidth, screenHeight); + } + + /** + * Resets the WebGL state so you can render things however you fancy! + * + * @return {PIXI.Renderer} Returns itself. + */ + reset() + { + this.runners.reset.run(); + + return this; + } + + clear() + { + this.framebuffer.bind(); + this.framebuffer.clear(); + } + + /** + * Removes everything from the renderer (event listeners, spritebatch, etc...) + * + * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. + * See: https://github.com/pixijs/pixi.js/issues/2233 + */ + destroy(removeView) + { + this.runners.destroy.run(); + + // call base destroy + super.destroy(removeView); + + // TODO nullify all the managers.. + this.gl = null; + } + + /** + * 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 + * builds and excluding plugins. + * @name PIXI.Renderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. + */ + + /** + * Adds a plugin to the renderer. + * + * @method + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + static registerPlugin(pluginName, ctor) + { + Renderer.__plugins = Renderer.__plugins || {}; + Renderer.__plugins[pluginName] = ctor; + } +} diff --git a/packages/core/src/System.js b/packages/core/src/System.js new file mode 100644 index 0000000..05a10c8 --- /dev/null +++ b/packages/core/src/System.js @@ -0,0 +1,42 @@ +/** + * System is a base class used for extending systems used by the {@link PIXI.Renderer} + * @see PIXI.Renderer#addSystem + * @class + * @memberof PIXI + */ +export default class System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + /** + * The renderer this manager works for. + * + * @member {PIXI.Renderer} + */ + this.renderer = renderer; + + this.renderer.runners.contextChange.add(this); + } + + /** + * Generic method called when there is a WebGL context change. + * + */ + contextChange() + { + // do some codes init! + } + + /** + * Generic destroy methods to be overridden by the subclass + * + */ + destroy() + { + this.renderer.runners.contextChange.remove(this); + this.renderer = null; + } +} diff --git a/packages/core/src/batch/BatchSystem.js b/packages/core/src/batch/BatchSystem.js new file mode 100644 index 0000000..b04c249 --- /dev/null +++ b/packages/core/src/batch/BatchSystem.js @@ -0,0 +1,68 @@ +import System from '../System'; +import ObjectRenderer from './ObjectRenderer'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class BatchSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + super(renderer); + + /** + * An empty renderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.emptyRenderer = new ObjectRenderer(renderer); + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.currentRenderer = this.emptyRenderer; + } + + /** + * Changes the current renderer to the one given in parameter + * + * @param {PIXI.ObjectRenderer} objectRenderer - The object renderer to use. + */ + setObjectRenderer(objectRenderer) + { + if (this.currentRenderer === objectRenderer) + { + return; + } + + this.currentRenderer.stop(); + this.currentRenderer = objectRenderer; + + this.renderer.state.setState(objectRenderer.state); + + this.currentRenderer.start(); + } + + /** + * This should be called if you wish to do some custom rendering + * It will basically render anything that may be batched up such as sprites + * + */ + flush() + { + this.setObjectRenderer(this.emptyRenderer); + } + + reset() + { + this.setObjectRenderer(this.emptyRenderer); + } +} diff --git a/packages/core/src/batch/ObjectRenderer.js b/packages/core/src/batch/ObjectRenderer.js new file mode 100644 index 0000000..0c851a9 --- /dev/null +++ b/packages/core/src/batch/ObjectRenderer.js @@ -0,0 +1,48 @@ +import System from '../System'; + +/** + * Base for a common object renderer that can be used as a system renderer plugin. + * + * @class + * @extends PIXI.System + * @memberof PIXI + */ +export default class ObjectRenderer extends System +{ + /** + * Starts the renderer and sets the shader + * + */ + start() + { + // set the shader.. + } + + /** + * Stops the renderer + * + */ + stop() + { + this.flush(); + } + + /** + * Stub method for rendering content and emptying the current batch. + * + */ + flush() + { + // flush! + } + + /** + * Renders an object + * + * @param {PIXI.DisplayObject} object - The object to render. + */ + render(object) // eslint-disable-line no-unused-vars + { + // render the object + } +} diff --git a/packages/core/src/context/ContextSystem.js b/packages/core/src/context/ContextSystem.js new file mode 100644 index 0000000..301aab4 --- /dev/null +++ b/packages/core/src/context/ContextSystem.js @@ -0,0 +1,186 @@ +import System from '../System'; +import { settings } from '@pixi/settings'; + +let CONTEXT_UID = 0; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class ContextSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.webGLVersion = 1; + + this.handleContextLost = this.handleContextLost.bind(this); + this.handleContextRestored = this.handleContextRestored.bind(this); + + this.extensions = {}; + + renderer.view.addEventListener('webglcontextlost', this.handleContextLost, false); + renderer.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + } + + get isLost() + { + return (!this.gl || this.gl.isContextLost()); + } + + contextChange(gl) + { + this.gl = gl; + + // restore a context if it was previously lost + if (gl.isContextLost() && gl.getExtension('WEBGL_lose_context')) + { + gl.getExtension('WEBGL_lose_context').restoreContext(); + } + } + + initFromContext(gl) + { + this.gl = gl; + this.validateContext(gl); + this.renderer.gl = gl; + this.renderer.CONTEXT_UID = CONTEXT_UID++; + this.renderer.runners.contextChange.run(gl); + } + + initFromOptions(options) + { + const gl = this.createContext(this.renderer.view, options); + + this.initFromContext(gl); + } + + /** + * Helper class to create a webGL Context + * + * @class + * @memberof PIXI.glCore + * @param canvas {HTMLCanvasElement} the canvas element that we will get the context from + * @param options {Object} An options object that gets passed in to the canvas element containing the context attributes, + * see https://developer.mozilla.org/en/docs/Web/API/HTMLCanvasElement/getContext for the options available + * @return {WebGLRenderingContext} the WebGL context + */ + createContext(canvas, options) + { + let gl; + + if (settings.PREFER_WEBGL_2) + { + gl = canvas.getContext('webgl2', options); + } + + if (gl) + { + this.webGLVersion = 2; + } + else + { + this.webGLVersion = 1; + + gl = canvas.getContext('webgl', options) + || canvas.getContext('experimental-webgl', options); + + if (!gl) + { + // fail, not able to get a context + throw new Error('This browser does not support webGL. Try using the canvas renderer'); + } + } + + this.gl = gl; + + this.getExtensions(); + + return gl; + } + + getExtensions() + { + // time to set up default etensions that pixi uses.. + const gl = this.gl; + const extensions = this.extensions; + + if (this.webGLVersion === 1) + { + extensions.drawBuffers = gl.getExtension('WEBGL_draw_buffers'); + extensions.depthTexture = gl.getExtension('WEBKIT_WEBGL_depth_texture'); + extensions.floatTexture = gl.getExtension('OES_texture_float'); + extensions.loseContext = gl.getExtension('WEBGL_lose_context'); + + extensions.vertexArrayObject = gl.getExtension('OES_vertex_array_object') + || gl.getExtension('MOZ_OES_vertex_array_object') + || gl.getExtension('WEBKIT_OES_vertex_array_object'); + } + + // we don't use any specific WebGL 2 ones yet! + } + + /** + * Handles a lost webgl context + * + * @private + * @param {WebGLContextEvent} event - The context lost event. + */ + handleContextLost(event) + { + event.preventDefault(); + } + + /** + * Handles a restored webgl context + * + * @private + */ + handleContextRestored() + { + this.renderer.runners.contextChange.run(this.gl); + } + + destroy() + { + const view = this.renderer.view; + + // remove listeners + view.removeEventListener('webglcontextlost', this.handleContextLost); + view.removeEventListener('webglcontextrestored', this.handleContextRestored); + + this.gl.useProgram(null); + + if (this.extensions.loseContext) + { + this.extensions.loseContext.loseContext(); + } + } + + postrender() + { + this.gl.flush(); + } + + validateContext(gl) + { + const attributes = gl.getContextAttributes(); + + // this is going to be fairly simple for now.. but at least we have room to grow! + if (!attributes.stencil) + { + /* eslint-disable max-len */ + + /* eslint-disable no-console */ + console.warn('Provided WebGL context does not have a stencil buffer, masks may not render correctly'); + /* eslint-enable no-console */ + + /* eslint-enable max-len */ + } + } +} diff --git a/packages/core/src/filters/Filter.js b/packages/core/src/filters/Filter.js new file mode 100644 index 0000000..9d8cad8 --- /dev/null +++ b/packages/core/src/filters/Filter.js @@ -0,0 +1,140 @@ +import Shader from '../shader/Shader'; +import Program from '../shader/Program'; +import State from '../state/State'; +import { settings } from '@pixi/settings'; +// import extractUniformsFromSrc from './extractUniformsFromSrc'; +import defaultVertex from './defaultFilter.vert'; +import defaultFragment from './defaultFilter.frag'; + +// let math = require('../../math'); +/** + * @class + * @memberof PIXI + * @extends PIXI.Shader + */ +export default class Filter extends Shader +{ + /** + * @param {string} [vertexSrc] - The source of the vertex shader. + * @param {string} [fragmentSrc] - The source of the fragment shader. + * @param {object} [uniforms] - Custom uniforms to use to augment the built-in ones. + */ + constructor(vertexSrc, fragmentSrc, uniforms) + { + const program = Program.from(vertexSrc, fragmentSrc); + + super(program, uniforms); + + /** + * The padding of the filter. Some filters require extra space to breath such as a blur. + * Increasing this will add extra width and height to the bounds of the object that the + * filter is applied to. + * + * @member {number} + */ + this.padding = 0; + + /** + * The resolution of the filter. Setting this to be lower will lower the quality but + * increase the performance of the filter. + * + * @member {number} + */ + this.resolution = settings.RESOLUTION; + + /** + * If enabled is true the filter is applied, if false it will not. + * + * @member {boolean} + */ + this.enabled = true; + + /** + * If enabled, PixiJS will fit the filter area into boundaries for better performance. + * Switch it off if it does not work for specific shader. + * + * @member {boolean} + */ + this.autoFit = true; + + /** + * Legacy filters use position and uvs from attributes + * @member {boolean} + * @readonly + */ + this.legacy = !!this.program.attributeData.aTextureCoord; + + /** + * the webGL state the filter requires to render + * @member {PIXI.State} + */ + this.state = new State(); + } + + /** + * Applies the filter + * + * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + * @param {boolean} clear - Should the output be cleared before rendering to it + * @param {object} [currentState] - It's current state of filter. + * There are some useful properties in the currentState : + * target, filters, sourceFrame, destinationFrame, renderTarget, resolution + */ + apply(filterManager, input, output, clear, currentState, derp) // eslint-disable-line no-unused-vars + { + // do as you please! + + filterManager.applyFilter(this, input, output, clear, currentState, derp); + + // or just do a regular render.. + } + + /** + * Sets the blendmode of the filter + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL + */ + get blendMode() + { + return this.state.blendMode; + } + + set blendMode(value) // eslint-disable-line require-jsdoc + { + this.state.blendMode = value; + } + + /** + * The default vertex shader source + * + * @static + * @constant + */ + static get defaultVertexSrc() + { + return defaultVertex; + } + + /** + * The default fragment shader source + * + * @static + * @constant + */ + static get defaultFragmentSrc() + { + return defaultFragment; + } +} + +/** + * Used for caching shader IDs + * + * @static + * @private + */ +Filter.SOURCE_KEY_MAP = {}; + diff --git a/packages/core/src/filters/FilterSystem.js b/packages/core/src/filters/FilterSystem.js new file mode 100644 index 0000000..3174318 --- /dev/null +++ b/packages/core/src/filters/FilterSystem.js @@ -0,0 +1,431 @@ +import System from '../System'; + +import RenderTexture from '../renderTexture/RenderTexture'; +import Quad from '../utils/Quad'; +import QuadUv from '../utils/QuadUv'; +import { Rectangle } from '@pixi/math'; +import * as filterTransforms from './filterTransforms'; +import bitTwiddle from 'bit-twiddle'; +import UniformGroup from '../shader/UniformGroup'; +import { DRAW_MODES } from '@pixi/constants'; + +// +/** + * @ignore + * @class + */ +class FilterState +{ + /** + * + */ + constructor() + { + this.renderTexture = null; + this.sourceFrame = new Rectangle(); + this.destinationFrame = new Rectangle(); + this.filters = []; + this.target = null; + this.legacy = false; + this.resolution = 1; + } +} + +/** + * @class + * @memberof PIXI.systems + * @extends PIXI.System + */ +export default class FilterSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + /** + * stores a bunch of PO2 textures used for filtering + * @type {Object} + */ + this.texturePool = {}; + + /** + * a pool for storing filter states, save us creating new ones each tick + * @type {Array} + */ + this.statePool = []; + + /** + * A very simple geometry used when drawing a filter effect to the screen + * @type {Quad} + */ + this.quad = new Quad(); + + this.quadUv = new QuadUv(); + + /** + * Temporary rect for maths + * @type {PIXI.Rectangle} + */ + this.tempRect = new Rectangle(); + + this.activeState = {}; + + /** + * this uniform group is attached to filter uniforms when used + * @type {UniformGroup} + */ + this.globalUniforms = new UniformGroup({ + sourceFrame: this.tempRect, + destinationFrame: this.tempRect, + + // legacy variables + filterArea: new Float32Array(4), + filterClamp: new Float32Array(4), + }, true); + } + + /** + * Adds a new filter to the System. + * + * @param {PIXI.DisplayObject} target - The target of the filter to render. + * @param {PIXI.Filter[]} filters - The filters to apply. + */ + push(target, filters) + { + const renderer = this.renderer; + const filterStack = this.renderer.renderTexture.defaultFilterStack; + const state = this.statePool.pop() || new FilterState(); + + let resolution = filters[0].resolution; + let padding = filters[0].padding; + let autoFit = filters[0].autoFit; + let legacy = filters[0].legacy; + + for (let i = 1; i < filters.length; i++) + { + const filter = filters[i]; + + // lets use the lowest resolution.. + resolution = Math.min(resolution, filter.resolution); + // and the largest amount of padding! + padding = Math.max(padding, filter.padding); + // only auto fit if all filters are autofit + autoFit = autoFit || filter.autoFit; + + legacy = legacy || filter.legacy; + } + + filterStack.push(state); + + state.resolution = resolution; + + state.legacy = legacy; + + // round to whole number based on resolution + // TODO move that to the shader too? + state.sourceFrame = target.filterArea || target.getBounds(true); + + state.sourceFrame.pad(padding); + + if (autoFit) + { + state.sourceFrame.fit(this.renderer.renderTexture.destinationFrame); + } + + state.sourceFrame.round(resolution); + + state.renderTexture = this.getPotFilterTexture(state.sourceFrame.width, state.sourceFrame.height, resolution); + state.filters = filters; + + state.destinationFrame.width = state.renderTexture.width; + state.destinationFrame.height = state.renderTexture.height; + + state.renderTexture.filterFrame = state.sourceFrame; + renderer.renderTexture.bind(state.renderTexture, state.sourceFrame);// /, state.destinationFrame); + renderer.renderTexture.clear(); + } + + /** + * Pops off the filter and applies it. + * + */ + pop() + { + const renderer = this.renderer; + const filterStack = renderer.renderTexture.defaultFilterStack; + const state = filterStack.pop(); + const filters = state.filters; + + this.activeState = state; + + const globalUniforms = this.globalUniforms.uniforms; + + globalUniforms.sourceFrame = state.sourceFrame; + globalUniforms.destinationFrame = state.destinationFrame; + globalUniforms.resolution = state.resolution; + + // only update the rect if its legacy.. + if (state.legacy) + { + const filterArea = globalUniforms.filterArea; + const filterClamp = globalUniforms.filterClamp; + + filterArea[0] = state.destinationFrame.width; + filterArea[1] = state.destinationFrame.height; + filterArea[2] = state.sourceFrame.x; + filterArea[3] = state.sourceFrame.y; + + filterClamp[0] = 0.5 / state.resolution / state.destinationFrame.width; + filterClamp[1] = 0.5 / state.resolution / state.destinationFrame.height; + filterClamp[2] = (state.sourceFrame.width - 0.5) / state.resolution / state.destinationFrame.width; + filterClamp[3] = (state.sourceFrame.height - 0.5) / state.resolution / state.destinationFrame.height; + } + + this.globalUniforms.update(); + + const lastState = filterStack[filterStack.length - 1]; + + if (filters.length === 1) + { + filters[0].apply(this, state.renderTexture, lastState.renderTexture, false, state); + + this.returnFilterTexture(state.renderTexture); + } + else + { + let flip = state.renderTexture; + let flop = this.getPotFilterTexture( + flip.width, + flip.height, + state.resolution + ); + + flop.filterFrame = flip.filterFrame; + + let i = 0; + + for (i = 0; i < filters.length - 1; ++i) + { + filters[i].apply(this, flip, flop, true, state); + + const t = flip; + + flip = flop; + flop = t; + } + + filters[i].apply(this, flip, lastState.renderTexture, false, state); + + this.returnFilterTexture(flip); + this.returnFilterTexture(flop); + } + + this.statePool.push(state); + } + + /** + * Draws a filter. + * + * @param {PIXI.Filter} filter - The filter to draw. + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + * @param {boolean} clear - Should the output be cleared before rendering to it + */ + applyFilter(filter, input, output, clear) + { + const renderer = this.renderer; + + renderer.renderTexture.bind(output, output ? output.filterFrame : null); + + if (clear) + { + // gl.disable(gl.SCISSOR_TEST); + renderer.renderTexture.clear(); + // gl.enable(gl.SCISSOR_TEST); + } + + // set the uniforms.. + filter.uniforms.uSampler = input; + filter.uniforms.filterGlobals = this.globalUniforms; + + // TODO make it so that the order of this does not matter.. + // because it does at the moment cos of global uniforms. + // they need to get resynced + + renderer.state.setState(filter.state); + renderer.shader.bind(filter); + + if (filter.legacy) + { + this.quadUv.map(input._frame, input.filterFrame); + + renderer.geometry.bind(this.quadUv); + renderer.geometry.draw(DRAW_MODES.TRIANGLES); + } + else + { + renderer.geometry.bind(this.quad); + renderer.geometry.draw(DRAW_MODES.TRIANGLE_STRIP); + } + } + + /** + * Calculates the mapped matrix. + * + * TODO playing around here.. this is temporary - (will end up in the shader) + * this returns a matrix that will normalise map filter cords in the filter to screen space + * + * @param {PIXI.Matrix} outputMatrix - the matrix to output to. + * @return {PIXI.Matrix} The mapped matrix. + */ + calculateScreenSpaceMatrix(outputMatrix) + { + const currentState = this.activeState; + + return filterTransforms.calculateScreenSpaceMatrix( + outputMatrix, + currentState.sourceFrame, + currentState.destinationFrame + ); + } + + /** + * This will map the filter coord so that a texture can be used based on the transform of a sprite + * + * @param {PIXI.Matrix} outputMatrix - The matrix to output to. + * @param {PIXI.Sprite} sprite - The sprite to map to. + * @return {PIXI.Matrix} The mapped matrix. + */ + calculateSpriteMatrix(outputMatrix, sprite) + { + const currentState = this.activeState; + + return filterTransforms.calculateSpriteMatrix( + outputMatrix, + currentState.sourceFrame, + currentState.destinationFrame, + sprite + ); + } + + /** + * Destroys this Filter System. + * + * @param {boolean} [contextLost=false] context was lost, do not free shaders + * + */ + destroy(contextLost = false) + { + if (!contextLost) + { + this.emptyPool(); + } + else + { + this.texturePool = {}; + } + } + + /** + * Gets a Power-of-Two render texture. + * + * TODO move to a seperate class could be on renderer? + * also - could cause issue with multiple contexts? + * + * @private + * @param {WebGLRenderingContext} gl - The webgl rendering context + * @param {number} minWidth - The minimum width of the render target. + * @param {number} minHeight - The minimum height of the render target. + * @param {number} resolution - The resolution of the render target. + * @return {PIXI.RenderTarget} The new render target. + */ + getPotFilterTexture(minWidth, minHeight, resolution) + { + minWidth = bitTwiddle.nextPow2(minWidth); + minHeight = bitTwiddle.nextPow2(minHeight); + resolution = resolution || 1; + + const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + + if (!this.texturePool[key]) + { + this.texturePool[key] = []; + } + + let renderTexture = this.texturePool[key].pop(); + + if (!renderTexture) + { + // temporary bypass cache.. + // internally - this will cause a texture to be bound.. + renderTexture = RenderTexture.create({ + width: minWidth, + height: minHeight, + resolution, + }); + } + + return renderTexture; + } + + /** + * Gets extra render texture to use inside current filter + * + * @param {number} resolution resolution of the renderTexture + * @returns {PIXI.RenderTexture} + */ + getFilterTexture(resolution) + { + const rt = this.activeState.renderTexture; + + const filterTexture = this.getPotFilterTexture(rt.width, rt.height, resolution || rt.baseTexture.resolution); + + filterTexture.filterFrame = rt.filterFrame; + + return filterTexture; + } + + /** + * Frees a render texture back into the pool. + * + * @param {PIXI.RenderTarget} renderTexture - The renderTarget to free + */ + returnFilterTexture(renderTexture) + { + renderTexture.filterFrame = null; + + const base = renderTexture.baseTexture; + + const minWidth = base.width; + const minHeight = base.height; + + const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + + this.texturePool[key].push(renderTexture); + } + + /** + * Empties the texture pool. + * + */ + emptyPool() + { + for (const i in this.texturePool) + { + const textures = this.texturePool[i]; + + if (textures) + { + for (let j = 0; j < textures.length; j++) + { + textures[j].destroy(true); + } + } + } + + this.texturePool = {}; + } +} diff --git a/packages/core/src/filters/defaultFilter.frag b/packages/core/src/filters/defaultFilter.frag new file mode 100644 index 0000000..a14c1be --- /dev/null +++ b/packages/core/src/filters/defaultFilter.frag @@ -0,0 +1,22 @@ +varying vec2 vTextureCoord; +varying vec2 vFilterCoord; + +uniform sampler2D uSampler; +uniform sampler2D filterSampler; + +void main(void){ + vec4 masky = texture2D(filterSampler, vFilterCoord); + vec4 sample = texture2D(uSampler, vTextureCoord); + vec4 color; + if(mod(vFilterCoord.x, 1.0) > 0.5) + { + color = vec4(1.0, 0.0, 0.0, 1.0); + } + else + { + color = vec4(0.0, 1.0, 0.0, 1.0); + } + // gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0); + gl_FragColor = mix(sample, masky, 0.5); + gl_FragColor *= sample.a; +} diff --git a/packages/core/src/filters/defaultFilter.vert b/packages/core/src/filters/defaultFilter.vert new file mode 100644 index 0000000..e1febca --- /dev/null +++ b/packages/core/src/filters/defaultFilter.vert @@ -0,0 +1,14 @@ +attribute vec2 aVertexPosition; +attribute vec2 aTextureCoord; + +uniform mat3 projectionMatrix; +uniform mat3 filterMatrix; + +varying vec2 vTextureCoord; +varying vec2 vFilterCoord; + +void main(void){ + gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); + vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy; + vTextureCoord = aTextureCoord ; +} diff --git a/packages/core/src/filters/filterTransforms.js b/packages/core/src/filters/filterTransforms.js new file mode 100644 index 0000000..87a3cd6 --- /dev/null +++ b/packages/core/src/filters/filterTransforms.js @@ -0,0 +1,50 @@ +import { Matrix } from '@pixi/math'; + +/** + * Calculates the mapped matrix + * @param filterArea {Rectangle} The filter area + * @param sprite {Sprite} the target sprite + * @param outputMatrix {Matrix} @alvin + * @private + */ +// this returns a matrix that will normalise map filter cords in the filter to screen space +export function calculateScreenSpaceMatrix(outputMatrix, filterArea, textureSize) +{ + // TODO unwrap? + const mappedMatrix = outputMatrix.identity(); + + mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); + + mappedMatrix.scale(textureSize.width, textureSize.height); + + return mappedMatrix; +} + +export function calculateNormalizedScreenSpaceMatrix(outputMatrix, filterArea, textureSize) +{ + const mappedMatrix = outputMatrix.identity(); + + mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); + + const translateScaleX = (textureSize.width / filterArea.width); + const translateScaleY = (textureSize.height / filterArea.height); + + mappedMatrix.scale(translateScaleX, translateScaleY); + + return mappedMatrix; +} + +// this will map the filter coord so that a texture can be used based on the transform of a sprite +export function calculateSpriteMatrix(outputMatrix, filterArea, textureSize, sprite) +{ + const orig = sprite._texture.orig; + const mappedMatrix = outputMatrix.set(textureSize.width, 0, 0, textureSize.height, filterArea.x, filterArea.y); + const worldTransform = sprite.worldTransform.copyTo(Matrix.TEMP_MATRIX); + + worldTransform.invert(); + mappedMatrix.prepend(worldTransform); + mappedMatrix.scale(1.0 / orig.width, 1.0 / orig.height); + mappedMatrix.translate(sprite.anchor.x, sprite.anchor.y); + + return mappedMatrix; +} diff --git a/packages/core/src/filters/spriteMask/SpriteMaskFilter.js b/packages/core/src/filters/spriteMask/SpriteMaskFilter.js new file mode 100644 index 0000000..91697b7 --- /dev/null +++ b/packages/core/src/filters/spriteMask/SpriteMaskFilter.js @@ -0,0 +1,63 @@ +import Filter from '../Filter'; +import { Matrix } from '@pixi/math'; +import vertex from './spriteMaskFilter.vert'; +import fragment from './spriteMaskFilter.frag'; +import { default as TextureMatrix } from '../../textures/TextureMatrix'; + +/** + * The SpriteMaskFilter class + * + * @class + * @extends PIXI.Filter + * @memberof PIXI + */ +export default class SpriteMaskFilter extends Filter +{ + /** + * @param {PIXI.Sprite} sprite - the target sprite + */ + constructor(sprite) + { + const maskMatrix = new Matrix(); + + super(vertex, fragment); + + sprite.renderable = false; + + this.maskSprite = sprite; + this.maskMatrix = maskMatrix; + } + + /** + * Applies the filter + * + * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + */ + apply(filterManager, input, output) + { + const maskSprite = this.maskSprite; + const tex = this.maskSprite.texture; + + if (!tex.valid) + { + return; + } + if (!tex.transform) + { + // margin = 0.0, let it bleed a bit, shader code becomes easier + // assuming that atlas textures were made with 1-pixel padding + tex.transform = new TextureMatrix(tex, 0.0); + } + tex.transform.update(); + + this.uniforms.mask = tex; + this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite) + .prepend(tex.transform.mapCoord); + this.uniforms.alpha = maskSprite.worldAlpha; + this.uniforms.maskClamp = tex.transform.uClampFrame; + + filterManager.applyFilter(this, input, output); + } +} diff --git a/packages/core/src/filters/spriteMask/spriteMaskFilter.frag b/packages/core/src/filters/spriteMask/spriteMaskFilter.frag new file mode 100644 index 0000000..0e4aef8 --- /dev/null +++ b/packages/core/src/filters/spriteMask/spriteMaskFilter.frag @@ -0,0 +1,23 @@ +varying vec2 vMaskCoord; +varying vec2 vTextureCoord; + +uniform sampler2D uSampler; +uniform sampler2D mask; +uniform float alpha; +uniform vec4 maskClamp; + +void main(void) +{ + float clip = step(3.5, + step(maskClamp.x, vMaskCoord.x) + + step(maskClamp.y, vMaskCoord.y) + + step(vMaskCoord.x, maskClamp.z) + + step(vMaskCoord.y, maskClamp.w)); + + vec4 original = texture2D(uSampler, vTextureCoord); + vec4 masky = texture2D(mask, vMaskCoord); + + original *= (masky.r * masky.a * alpha * clip); + + gl_FragColor = original; +} diff --git a/packages/core/src/filters/spriteMask/spriteMaskFilter.vert b/packages/core/src/filters/spriteMask/spriteMaskFilter.vert new file mode 100644 index 0000000..d23a30c --- /dev/null +++ b/packages/core/src/filters/spriteMask/spriteMaskFilter.vert @@ -0,0 +1,16 @@ +attribute vec2 aVertexPosition; +attribute vec2 aTextureCoord; + +uniform mat3 projectionMatrix; +uniform mat3 otherMatrix; + +varying vec2 vMaskCoord; +varying vec2 vTextureCoord; + +void main(void) +{ + gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); + + vTextureCoord = aTextureCoord; + vMaskCoord = ( otherMatrix * vec3( aTextureCoord, 1.0) ).xy; +} diff --git a/packages/core/src/framebuffer/FrameBuffer.js b/packages/core/src/framebuffer/FrameBuffer.js new file mode 100644 index 0000000..84e9fcc --- /dev/null +++ b/packages/core/src/framebuffer/FrameBuffer.js @@ -0,0 +1,106 @@ +import Texture from '../textures/BaseTexture'; +import { FORMATS, TYPES } from '@pixi/constants'; + +/** + * Frame buffer + * @class + * @memberof PIXI + */ +export default class FrameBuffer +{ + constructor(width, height) + { + this.width = width || 100; + this.height = height || 100; + + this.stencil = false; + this.depth = false; + + this.dirtyId = 0; + this.dirtyFormat = 0; + this.dirtySize = 0; + + this.depthTexture = null; + this.colorTextures = []; + + this.glFrameBuffers = {}; + } + + get colorTexture() + { + return this.colorTextures[0]; + } + + addColorTexture(index, texture) + { + // TODO add some validation to the texture - same width / height etc? + this.colorTextures[index || 0] = texture || new Texture(null, { scaleMode: 0, + resolution: 1, + mipmap: false, + width: this.width, + height: this.height });// || new Texture(); + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + addDepthTexture(texture) + { + /* eslint-disable max-len */ + this.depthTexture = texture || new Texture(null, { scaleMode: 0, + resolution: 1, + width: this.width, + height: this.height, + mipmap: false, + format: FORMATS.DEPTH_COMPONENT, + type: TYPES.UNSIGNED_SHORT });// UNSIGNED_SHORT; + /* eslint-disable max-len */ + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + enableDepth() + { + this.depth = true; + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + enableStencil() + { + this.stencil = true; + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + resize(width, height) + { + if (width === this.width && height === this.height) return; + + this.width = width; + this.height = height; + + this.dirtyId++; + this.dirtySize++; + + for (let i = 0; i < this.colorTextures.length; i++) + { + this.colorTextures[i].setSize(width, height); + } + + if (this.depthTexture) + { + this.depthTexture.setSize(width, height); + } + } +} diff --git a/packages/core/src/framebuffer/FramebufferSystem.js b/packages/core/src/framebuffer/FramebufferSystem.js new file mode 100644 index 0000000..2cc2437 --- /dev/null +++ b/packages/core/src/framebuffer/FramebufferSystem.js @@ -0,0 +1,246 @@ +import System from '../System'; +import { Rectangle } from '@pixi/math'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class FramebufferSystem extends System +{ + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + contextChange() + { + this.gl = this.renderer.gl; + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + this.current = null; + this.viewport = new Rectangle(); + + this.drawBufferExtension = this.renderer.context.extensions.drawBuffers; + } + + bind(framebuffer, frame) + { + const gl = this.gl; + + this.current = framebuffer; + + if (framebuffer) + { + // TODO cacheing layer! + + const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID] || this.initFramebuffer(framebuffer); + + gl.bindFramebuffer(gl.FRAMEBUFFER, fbo.framebuffer); + // makesure all textures are unbound.. + + // now check for updates... + if (fbo.dirtyId !== framebuffer.dirtyId) + { + fbo.dirtyId = framebuffer.dirtyId; + + if (fbo.dirtyFormat !== framebuffer.dirtyFormat) + { + fbo.dirtyFormat = framebuffer.dirtyFormat; + this.updateFramebuffer(framebuffer); + } + else if (fbo.dirtySize !== framebuffer.dirtySize) + { + fbo.dirtySize = framebuffer.dirtySize; + this.resizeFramebuffer(framebuffer); + } + } + + for (let i = 0; i < framebuffer.colorTextures.length; i++) + { + if (framebuffer.colorTextures[i].texturePart) + { + this.renderer.texture.unbind(framebuffer.colorTextures[i].texture); + } + else + { + this.renderer.texture.unbind(framebuffer.colorTextures[i]); + } + } + + if (framebuffer.depthTexture) + { + this.renderer.texture.unbind(framebuffer.depthTexture); + } + + if (frame) + { + this.setViewport(frame.x, frame.y, frame.width, frame.height); + } + else + { + this.setViewport(0, 0, framebuffer.width, framebuffer.height); + } + } + else + { + gl.bindFramebuffer(gl.FRAMEBUFFER, null); + + if (frame) + { + this.setViewport(frame.x, frame.y, frame.width, frame.height); + } + else + { + this.setViewport(0, 0, this.renderer.width, this.renderer.height); + } + } + } + + setViewport(x, y, width, height) + { + const v = this.viewport; + + if (v.width !== width || v.height !== height || v.x !== x || v.y !== y) + { + v.x = x; + v.y = y; + v.width = width; + v.height = height; + + this.gl.viewport(x, y, width, height); + } + } + + get size() + { + if (this.current) + { + // TODO store temp + return { x: 0, y: 0, width: this.current.width, height: this.current.height }; + } + + return { x: 0, y: 0, width: this.renderer.width, height: this.renderer.height }; + } + + clear(r, g, b, a) + { + const gl = this.gl; + + // TODO clear color can be set only one right? + gl.clearColor(r, g, b, a); + gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); + } + + // private functions... + + initFramebuffer(framebuffer) + { + const gl = this.gl; + + // TODO - make this a class? + const fbo = { + framebuffer: gl.createFramebuffer(), + stencil: null, + dirtyId: 0, + dirtyFormat: 0, + dirtySize: 0, + }; + + framebuffer.glFrameBuffers[this.CONTEXT_UID] = fbo; + + return fbo; + } + + resizeFramebuffer(framebuffer) + { + const gl = this.gl; + + if (framebuffer.stencil || framebuffer.depth) + { + gl.bindRenderbuffer(gl.RENDERBUFFER, this.stencil); + gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); + } + } + + updateFramebuffer(framebuffer) + { + const gl = this.gl; + + const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID]; + + // bind the color texture + const colorTextures = framebuffer.colorTextures; + + let count = colorTextures.length; + + if (!this.drawBufferExtension) + { + count = Math.min(count, 1); + } + + const activeTextures = []; + + for (let i = 0; i < count; i++) + { + const texture = framebuffer.colorTextures[i]; + + if (texture.texturePart) + { + this.renderer.texture.bind(texture.texture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.COLOR_ATTACHMENT0 + i, + gl.TEXTURE_CUBE_MAP_NEGATIVE_X + texture.side, + texture.texture._glTextures[this.CONTEXT_UID].texture, + 0); + } + else + { + this.renderer.texture.bind(texture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.COLOR_ATTACHMENT0 + i, + gl.TEXTURE_2D, + texture._glTextures[this.CONTEXT_UID].texture, + 0); + } + + activeTextures.push(gl.COLOR_ATTACHMENT0 + i); + } + + if (this.drawBufferExtension && activeTextures.length > 1) + { + this.drawBufferExtension.drawBuffersWEBGL(activeTextures); + } + + if (framebuffer.depthTexture) + { + const depthTextureExt = this.renderer.context.extensions.depthTexture; + + if (depthTextureExt) + { + const depthTexture = framebuffer.depthTexture; + + this.renderer.texture.bind(depthTexture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.DEPTH_ATTACHMENT, + gl.TEXTURE_2D, + depthTexture._glTextures[this.CONTEXT_UID].texture, + 0); + } + } + + if (framebuffer.stencil || framebuffer.depth) + { + fbo.stencil = gl.createRenderbuffer(); + + gl.bindRenderbuffer(gl.RENDERBUFFER, fbo.stencil); + + // TODO.. this is depth AND stencil? + gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, fbo.stencil); + gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); + // fbo.enableStencil(); + } + } +} diff --git a/packages/core/src/geometry/GLBuffer.js b/packages/core/src/geometry/GLBuffer.js new file mode 100644 index 0000000..0477e8a --- /dev/null +++ b/packages/core/src/geometry/GLBuffer.js @@ -0,0 +1,9 @@ +export default class GLBuffer +{ + constructor(buffer) + { + this.buffer = buffer; + this.updateID = -1; + this.byteLength = -1; + } +} diff --git a/packages/core/src/geometry/GeometrySystem.js b/packages/core/src/geometry/GeometrySystem.js new file mode 100644 index 0000000..959f827 --- /dev/null +++ b/packages/core/src/geometry/GeometrySystem.js @@ -0,0 +1,388 @@ +import System from '../System'; +import GLBuffer from './GLBuffer'; + +const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class GeometrySystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this._activeGeometry = null; + this._activeVao = null; + + this.hasVao = true; + this.hasInstance = true; + } + + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + contextChange() + { + const gl = this.gl = this.renderer.gl; + + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + + // webgl2 + if (!gl.createVertexArray) + { + // webgl 1! + let nativeVaoExtension = this.renderer.context.extensions.vertexArrayObject; + + if (this.renderer.legacy) + { + nativeVaoExtension = null; + } + + if (nativeVaoExtension) + { + gl.createVertexArray = () => + nativeVaoExtension.createVertexArrayOES(); + + gl.bindVertexArray = (vao) => + nativeVaoExtension.bindVertexArrayOES(vao); + + gl.deleteVertexArray = (vao) => + nativeVaoExtension.deleteVertexArrayOES(vao); + } + else + { + this.hasVao = false; + gl.createVertexArray = () => + { + // empty + }; + + gl.bindVertexArray = () => + { + // empty + }; + + gl.deleteVertexArray = () => + { + // empty + }; + } + } + + if (!gl.vertexAttribDivisor) + { + const instanceExt = gl.getExtension('ANGLE_instanced_arrays'); + + if (instanceExt) + { + gl.vertexAttribDivisor = (a, b) => + instanceExt.vertexAttribDivisorANGLE(a, b); + + gl.drawElementsInstanced = (a, b, c, d, e) => + instanceExt.drawElementsInstancedANGLE(a, b, c, d, e); + + gl.drawArraysInstanced = (a, b, c, d) => + instanceExt.drawArraysInstancedANGLE(a, b, c, d); + } + else + { + this.hasInstance = false; + } + } + } + + /** + * Binds geometry so that is can be drawn. Creating a Vao if required + * @private + * @param {PIXI.Geometry} geometry instance of geometry to bind + */ + bind(geometry, shader) + { + shader = shader || this.renderer.shader.shader; + + const gl = this.gl; + + // not sure the best way to address this.. + // currently different shaders require different VAOs for the same geometry + // Still mulling over the best way to solve this one.. + // will likely need to modify the shader attribute locations at run time! + let vaos = geometry.glVertexArrayObjects[this.CONTEXT_UID]; + + if (!vaos) + { + geometry.glVertexArrayObjects[this.CONTEXT_UID] = vaos = {}; + } + + const vao = vaos[shader.program.id] || this.initGeometryVao(geometry, shader.program); + + this._activeGeometry = geometry; + + if (this._activeVao !== vao) + { + this._activeVao = vao; + + if (this.hasVao) + { + gl.bindVertexArray(vao); + } + else + { + this.activateVao(geometry, shader.program); + } + } + + // TODO - optimise later! + // don't need to loop through if nothing changed! + // maybe look to add an 'autoupdate' to geometry? + this.updateBuffers(); + } + + reset() + { + this.unbind(); + } + + updateBuffers() + { + const geometry = this._activeGeometry; + const gl = this.gl; + + for (let i = 0; i < geometry.buffers.length; i++) + { + const buffer = geometry.buffers[i]; + + const glBuffer = buffer._glBuffers[this.CONTEXT_UID]; + + if (buffer._updateID !== glBuffer.updateID) + { + glBuffer.updateID = buffer._updateID; + + // TODO can cache this on buffer! maybe added a getter / setter? + const type = buffer.index ? gl.ELEMENT_ARRAY_BUFFER : gl.ARRAY_BUFFER; + const drawType = buffer.static ? gl.STATIC_DRAW : gl.DYNAMIC_DRAW; + + gl.bindBuffer(type, glBuffer.buffer); + + if (glBuffer.byteLength >= buffer.data.byteLength) + { + // offset is always zero for now! + gl.bufferSubData(type, 0, buffer.data); + } + else + { + gl.bufferData(type, buffer.data, drawType); + } + } + } + } + + checkCompatability(geometry, program) + { + // geometry must have at least all the attributes that the shader requires. + const geometryAttributes = geometry.attributes; + const shaderAttributes = program.attributeData; + + for (const j in shaderAttributes) + { + if (!geometryAttributes[j]) + { + throw new Error(`shader and geometry incompatible, geometry missing the "${j}" attribute`); + } + } + } + + /** + * Creates a Vao with the same structure as the geometry and stores it on the geometry. + * @private + * @param {PIXI.Geometry} geometry instance of geometry to to generate Vao for + */ + initGeometryVao(geometry, program) + { + this.checkCompatability(geometry, program); + + const gl = this.gl; + const CONTEXT_UID = this.CONTEXT_UID; + const buffers = geometry.buffers; + const attributes = geometry.attributes; + + const tempStride = {}; + const tempStart = {}; + + for (const j in buffers) + { + tempStride[j] = 0; + tempStart[j] = 0; + } + + for (const j in attributes) + { + if (!attributes[j].size && program.attributeData[j]) + { + attributes[j].size = program.attributeData[j].size; + } + + tempStride[attributes[j].buffer] += attributes[j].size * byteSizeMap[attributes[j].type]; + } + + for (const j in attributes) + { + const attribute = attributes[j]; + const attribSize = attribute.size; + + if (attribute.stride === undefined) + { + if (tempStride[attribute.buffer] === attribSize * byteSizeMap[attribute.type]) + { + attribute.stride = 0; + } + else + { + attribute.stride = tempStride[attribute.buffer]; + } + } + + if (attribute.start === undefined) + { + attribute.start = tempStart[attribute.buffer]; + + tempStart[attribute.buffer] += attribSize * byteSizeMap[attribute.type]; + } + } + + // first update - and create the buffers! + // only create a gl buffer if it actually gets + for (let i = 0; i < buffers.length; i++) + { + const buffer = buffers[i]; + + if (!buffer._glBuffers[CONTEXT_UID]) + { + buffer._glBuffers[CONTEXT_UID] = new GLBuffer(gl.createBuffer()); + } + } + + // TODO - maybe make this a data object? + // lets wait to see if we need to first! + const vao = gl.createVertexArray(); + + gl.bindVertexArray(vao); + + this.activateVao(geometry, program); + + gl.bindVertexArray(null); + + // add it to the cache! + geometry.glVertexArrayObjects[this.CONTEXT_UID][program.id] = vao; + + return vao; + } + + activateVao(geometry, program) + { + const gl = this.gl; + const CONTEXT_UID = this.CONTEXT_UID; + const buffers = geometry.buffers; + const attributes = geometry.attributes; + + if (geometry.indexBuffer) + { + // first update the index buffer if we have one.. + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, geometry.indexBuffer._glBuffers[CONTEXT_UID].buffer); + } + + let lastBuffer = null; + + // add a new one! + for (const j in attributes) + { + const attribute = attributes[j]; + const buffer = buffers[attribute.buffer]; + const glBuffer = buffer._glBuffers[CONTEXT_UID]; + + if (program.attributeData[j]) + { + if (lastBuffer !== glBuffer) + { + gl.bindBuffer(gl.ARRAY_BUFFER, glBuffer.buffer); + + lastBuffer = glBuffer; + } + + const location = program.attributeData[j].location; + + // TODO introduce state again + // we can optimise this for older devices that have no VAOs + gl.enableVertexAttribArray(location); + + gl.vertexAttribPointer(location, + attribute.size, + attribute.type || gl.FLOAT, + attribute.normalized, + attribute.stride, + attribute.start); + + if (attribute.instance) + { + // TODO calculate instance count based of this... + if (this.hasInstance) + { + gl.vertexAttribDivisor(location, 1); + } + else + { + throw new Error('geometry error, GPU Instancing is not supported on this device'); + } + } + } + } + } + + draw(type, size, start, instanceCount) + { + const gl = this.gl; + const geometry = this._activeGeometry; + + // TODO.. this should not change so maybe cache the function? + + if (geometry.indexBuffer) + { + if (geometry.instanced) + { + /* eslint-disable max-len */ + gl.drawElementsInstanced(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2, instanceCount || 1); + /* eslint-enable max-len */ + } + else + { + gl.drawElements(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2); + } + } + else + if (geometry.instanced) + { + // TODO need a better way to calculate size.. + gl.drawArraysInstanced(type, start, size || geometry.getSize(), instanceCount || 1); + } + else + { + gl.drawArrays(type, start, size || geometry.getSize()); + } + + return this; + } + + unbind() + { + this.gl.bindVertexArray(null); + this._activeVao = null; + this._activeGeometry = null; + } +} diff --git a/packages/core/src/geometry/utils/setVertexAttribArrays.js b/packages/core/src/geometry/utils/setVertexAttribArrays.js new file mode 100644 index 0000000..23b6959 --- /dev/null +++ b/packages/core/src/geometry/utils/setVertexAttribArrays.js @@ -0,0 +1,54 @@ +// var GL_MAP = {}; + +/** + * @param gl {WebGLRenderingContext} The current WebGL context + * @param attribs {*} + * @param state {*} + */ +export default function setVertexAttribArrays(gl, attribs, state) +{ + let i; + + if (state) + { + const tempAttribState = state.tempAttribState; + const attribState = state.attribState; + + for (i = 0; i < tempAttribState.length; i++) + { + tempAttribState[i] = false; + } + + // set the new attribs + for (i = 0; i < attribs.length; i++) + { + tempAttribState[attribs[i].attribute.location] = true; + } + + for (i = 0; i < attribState.length; i++) + { + if (attribState[i] !== tempAttribState[i]) + { + attribState[i] = tempAttribState[i]; + + if (state.attribState[i]) + { + gl.enableVertexAttribArray(i); + } + else + { + gl.disableVertexAttribArray(i); + } + } + } + } + else + { + for (i = 0; i < attribs.length; i++) + { + const attrib = attribs[i]; + + gl.enableVertexAttribArray(attrib.attribute.location); + } + } +} diff --git a/packages/core/src/index.js b/packages/core/src/index.js index 58c456a..fd1f547 100644 --- a/packages/core/src/index.js +++ b/packages/core/src/index.js @@ -1,30 +1,30 @@ import * as resources from './textures/resources'; -import * as systems from './renderers/systems'; +import * as systems from './systems'; -export { resources }; export { systems }; +export { resources }; -export { default as Renderer } from './renderers/Renderer'; -export { default as AbstractRenderer } from './renderers/AbstractRenderer'; -export { default as FrameBuffer } from './textures/FrameBuffer'; +export { default as System } from './System'; +export { default as Renderer } from './Renderer'; +export { default as AbstractRenderer } from './AbstractRenderer'; +export { default as FrameBuffer } from './framebuffer/FrameBuffer'; export { default as CubeTexture } from './textures/CubeTexture'; export { default as BaseTexture } from './textures/BaseTexture'; export { default as Texture } from './textures/Texture'; export { default as TextureMatrix } from './textures/TextureMatrix'; -export { default as RenderTexture } from './textures/RenderTexture'; -export { default as BaseRenderTexture } from './textures/BaseRenderTexture'; +export { default as RenderTexture } from './renderTexture/RenderTexture'; +export { default as BaseRenderTexture } from './renderTexture/BaseRenderTexture'; export { default as TextureUvs } from './textures/TextureUvs'; -export { default as State } from './renderers/State'; -export { default as ObjectRenderer } from './renderers/utils/ObjectRenderer'; -export { default as Quad } from './renderers/utils/Quad'; -export { default as QuadUv } from './renderers/utils/QuadUv'; -export { default as checkMaxIfStatmentsInShader } from './renderers/utils/checkMaxIfStatmentsInShader'; +export { default as State } from './state/State'; +export { default as ObjectRenderer } from './batch/ObjectRenderer'; +export { default as Quad } from './utils/Quad'; +export { default as QuadUv } from './utils/QuadUv'; +export { default as checkMaxIfStatmentsInShader } from './shader/utils/checkMaxIfStatmentsInShader'; export { default as Shader } from './shader/Shader'; export { default as Program } from './shader/Program'; export { default as UniformGroup } from './shader/UniformGroup'; -export { default as SpriteMaskFilter } from './renderers/filters/spriteMask/SpriteMaskFilter'; -export { default as Filter } from './renderers/filters/Filter'; +export { default as SpriteMaskFilter } from './filters/spriteMask/SpriteMaskFilter'; +export { default as Filter } from './filters/Filter'; export { default as Attribute } from './geometry/Attribute'; export { default as Buffer } from './geometry/Buffer'; export { default as Geometry } from './geometry/Geometry'; - diff --git a/packages/core/src/mask/MaskSystem.js b/packages/core/src/mask/MaskSystem.js new file mode 100644 index 0000000..99174c2 --- /dev/null +++ b/packages/core/src/mask/MaskSystem.js @@ -0,0 +1,199 @@ +import System from '../System'; +import AlphaMaskFilter from '../filters/spriteMask/SpriteMaskFilter'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class MaskSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + // TODO - we don't need both! + this.scissor = false; + this.scissorData = null; + this.scissorRenderTarget = null; + + this.enableScissor = false; + + this.alphaMaskPool = []; + this.alphaMaskIndex = 0; + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.DisplayObject} target - Display Object to push the mask to + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + push(target, maskData) + { + // TODO the root check means scissor rect will not + // be used on render textures more info here: + // https://github.com/pixijs/pixi.js/pull/3545 + + if (maskData.texture) + { + this.pushSpriteMask(target, maskData); + } + else if (this.enableScissor + && !this.scissor + && this.renderer._activeRenderTarget.root + && !this.renderer.stencil.stencilMaskStack.length + && maskData.isFastRect()) + { + const matrix = maskData.worldTransform; + + let rot = Math.atan2(matrix.b, matrix.a); + + // use the nearest degree! + rot = Math.round(rot * (180 / Math.PI)); + + if (rot % 90) + { + this.pushStencilMask(maskData); + } + else + { + this.pushScissorMask(target, maskData); + } + } + else + { + this.pushStencilMask(maskData); + } + } + + /** + * Removes the last mask from the mask stack and doesn't return it. + * + * @param {PIXI.DisplayObject} target - Display Object to pop the mask from + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + pop(target, maskData) + { + if (maskData.texture) + { + this.popSpriteMask(target, maskData); + } + else if (this.enableScissor && !this.renderer.stencil.stencilMaskStack.length) + { + this.popScissorMask(target, maskData); + } + else + { + this.popStencilMask(target, maskData); + } + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.RenderTarget} target - Display Object to push the sprite mask to + * @param {PIXI.Sprite} maskData - Sprite to be used as the mask + */ + pushSpriteMask(target, maskData) + { + let alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; + + if (!alphaMaskFilter) + { + alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; + } + + alphaMaskFilter[0].resolution = this.renderer.resolution; + alphaMaskFilter[0].maskSprite = maskData; + + // TODO - may cause issues! + target.filterArea = maskData.getBounds(true); + + this.renderer.filterSystem.pushFilter(target, alphaMaskFilter); + + this.alphaMaskIndex++; + } + + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popSpriteMask() + { + this.renderer.filterSystem.popFilter(); + this.alphaMaskIndex--; + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + pushStencilMask(maskData) + { + this.renderer.batch.flush(); + this.renderer.stencil.pushStencil(maskData); + } + + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popStencilMask() + { + // this.renderer.currentRenderer.stop(); + this.renderer.stencil.popStencil(); + } + + /** + * + * @param {PIXI.DisplayObject} target - Display Object to push the mask to + * @param {PIXI.Graphics} maskData - The masking data. + */ + pushScissorMask(target, maskData) + { + maskData.renderable = true; + + const renderTarget = this.renderer._activeRenderTarget; + + const bounds = maskData.getBounds(); + + bounds.fit(renderTarget.size); + maskData.renderable = false; + + this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); + + const resolution = this.renderer.resolution; + + this.renderer.gl.scissor( + bounds.x * resolution, + (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, + bounds.width * resolution, + bounds.height * resolution + ); + + this.scissorRenderTarget = renderTarget; + this.scissorData = maskData; + this.scissor = true; + } + + /** + * + * + */ + popScissorMask() + { + this.scissorRenderTarget = null; + this.scissorData = null; + this.scissor = false; + + // must be scissor! + const gl = this.renderer.gl; + + gl.disable(gl.SCISSOR_TEST); + } +} diff --git a/packages/core/src/mask/StencilSystem.js b/packages/core/src/mask/StencilSystem.js new file mode 100644 index 0000000..62796a5 --- /dev/null +++ b/packages/core/src/mask/StencilSystem.js @@ -0,0 +1,133 @@ +import System from '../System'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class StencilSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + this.stencilMaskStack = []; + } + + /** + * Changes the mask stack that is used by this System. + * + * @param {PIXI.Graphics[]} stencilMaskStack - The mask stack + */ + setMaskStack(stencilMaskStack) + { + const gl = this.renderer.gl; + + if (stencilMaskStack.length !== this.stencilMaskStack.length) + { + if (stencilMaskStack.length === 0) + { + gl.disable(gl.STENCIL_TEST); + } + else + { + gl.enable(gl.STENCIL_TEST); + } + } + + this.stencilMaskStack = stencilMaskStack; + } + + /** + * Applies the Mask and adds it to the current stencil stack. @alvin + * + * @param {PIXI.Graphics} graphics - The mask + */ + pushStencil(graphics) + { + this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); + + // this.renderer._activeRenderTarget.attachStencilBuffer(); + + const gl = this.renderer.gl; + const prevMaskCount = this.stencilMaskStack.length; + + if (prevMaskCount === 0) + { + gl.enable(gl.STENCIL_TEST); + } + + this.stencilMaskStack.push(graphics); + + // Increment the refference stencil value where the new mask overlaps with the old ones. + gl.colorMask(false, false, false, false); + gl.stencilFunc(gl.EQUAL, prevMaskCount, this._getBitwiseMask()); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.INCR); + this.renderer.plugins.graphics.render(graphics); + + this._useCurrent(); + } + + /** + * Removes the last mask from the stencil stack. @alvin + */ + popStencil() + { + this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); + + const gl = this.renderer.gl; + const graphics = this.stencilMaskStack.pop(); + + if (this.stencilMaskStack.length === 0) + { + // the stack is empty! + gl.disable(gl.STENCIL_TEST); + gl.clear(gl.STENCIL_BUFFER_BIT); + gl.clearStencil(0); + } + else + { + // Decrement the refference stencil value where the popped mask overlaps with the other ones + gl.colorMask(false, false, false, false); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.DECR); + this.renderer.plugins.graphics.render(graphics); + + this._useCurrent(); + } + } + + /** + * Setup renderer to use the current stencil data. + */ + _useCurrent() + { + const gl = this.renderer.gl; + + gl.colorMask(true, true, true, true); + gl.stencilFunc(gl.EQUAL, this.stencilMaskStack.length, this._getBitwiseMask()); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP); + } + + /** + * Fill 1s equal to the number of acitve stencil masks. + * + * @return {number} The bitwise mask. + */ + _getBitwiseMask() + { + return (1 << this.stencilMaskStack.length) - 1; + } + + /** + * Destroys the mask stack. + * + */ + destroy() + { + super.destroy(this); + + this.stencilMaskStack = null; + } +} diff --git a/packages/core/src/projection/ProjectionSystem.js b/packages/core/src/projection/ProjectionSystem.js new file mode 100644 index 0000000..6e63b73 --- /dev/null +++ b/packages/core/src/projection/ProjectionSystem.js @@ -0,0 +1,73 @@ +import System from '../System'; +import { Matrix } from '@pixi/math'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class ProjectionSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.projectionMatrix = new Matrix(); + } + + update(destinationFrame, sourceFrame, resolution, root) + { + this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; + this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; + + this.calculateProjection(this.destinationFrame, this.sourceFrame, resolution, root); + + this.renderer.globalUniforms.uniforms.projectionMatrix = this.projectionMatrix; + this.renderer.globalUniforms.update(); + } + + /** + * Updates the projection matrix based on a projection frame (which is a rectangle) + * + * @param {Rectangle} destinationFrame - The destination frame. + * @param {Rectangle} sourceFrame - The source frame. + */ + calculateProjection(destinationFrame, sourceFrame, resolution, root) + { + const pm = this.projectionMatrix; + + // I don't think we will need this line.. + // pm.identity(); + + if (!root) + { + pm.a = (1 / destinationFrame.width * 2) * resolution; + pm.d = (1 / destinationFrame.height * 2) * resolution; + + pm.tx = -1 - (sourceFrame.x * pm.a); + pm.ty = -1 - (sourceFrame.y * pm.d); + } + else + { + pm.a = (1 / destinationFrame.width * 2) * resolution; + pm.d = (-1 / destinationFrame.height * 2) * resolution; + + pm.tx = -1 - (sourceFrame.x * pm.a); + pm.ty = 1 - (sourceFrame.y * pm.d); + } + } + + /** + * Sets the transform of the active render target to the given matrix + * + * @param {PIXI.Matrix} matrix - The transformation matrix + */ + setTransform()// matrix) + { + // this._activeRenderTarget.transform = matrix; + } +} diff --git a/packages/core/src/renderTexture/BaseRenderTexture.js b/packages/core/src/renderTexture/BaseRenderTexture.js new file mode 100644 index 0000000..88eb83b --- /dev/null +++ b/packages/core/src/renderTexture/BaseRenderTexture.js @@ -0,0 +1,137 @@ +import BaseTexture from '../textures/BaseTexture'; +import FrameBuffer from '../framebuffer/FrameBuffer'; + +/** + * A BaseRenderTexture is a special texture that allows any PixiJS display object to be rendered to it. + * + * __Hint__: All DisplayObjects (i.e. Sprites) that render to a BaseRenderTexture should be preloaded + * otherwise black rectangles will be drawn instead. + * + * A BaseRenderTexture takes a snapshot of any Display Object given to its render method. The position + * and rotation of the given Display Objects is ignored. For example: + * + * ```js + * let renderer = PIXI.autoDetectRenderer(1024, 1024, { view: canvas, ratio: 1 }); + * let baseRenderTexture = new PIXI.BaseRenderTexture(renderer, 800, 600); + * let sprite = PIXI.Sprite.fromImage("spinObj_01.png"); + * + * sprite.position.x = 800/2; + * sprite.position.y = 600/2; + * sprite.anchor.x = 0.5; + * sprite.anchor.y = 0.5; + * + * baseRenderTexture.render(sprite); + * ``` + * + * The Sprite in this case will be rendered using its local transform. To render this sprite at 0,0 + * you can clear the transform + * + * ```js + * + * sprite.setTransform() + * + * let baseRenderTexture = new PIXI.BaseRenderTexture(100, 100); + * let renderTexture = new PIXI.RenderTexture(baseRenderTexture); + * + * renderer.render(sprite, renderTexture); // Renders to center of RenderTexture + * ``` + * + * @class + * @extends PIXI.BaseTexture + * @memberof PIXI + */ +export default class BaseRenderTexture extends BaseTexture +{ + /** + * @param {object} [options] + * @param {number} [options.width=100] - The width of the base render texture + * @param {number} [options.height=100] - The height of the base render texture + * @param {PIXI.SCALE_MODES} [options.scaleMode] - See {@link PIXI.SCALE_MODES} for possible values + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the texture being generated + */ + constructor(options) + { + if (typeof options === 'number') + { + /* eslint-disable prefer-rest-params */ + // Backward compatibility of signature + const width = arguments[0]; + const height = arguments[1]; + const scaleMode = arguments[2]; + const resolution = arguments[3]; + + options = { width, height, scaleMode, resolution }; + /* eslint-enable prefer-rest-params */ + } + + super(null, options); + + const { width, height } = options || {}; + + // Set defaults + this.mipmap = false; + this.width = Math.ceil(width) || 100; + this.height = Math.ceil(height) || 100; + this.valid = true; + + /** + * A map of renderer IDs to webgl renderTargets + * + * @private + * @member {object} + */ + // this._glRenderTargets = {}; + + /** + * A reference to the canvas render target (we only need one as this can be shared across renderers) + * + * @private + * @member {object} + */ + this._canvasRenderTarget = null; + + this.clearColor = [0, 0, 0, 0]; + + this.frameBuffer = new FrameBuffer(width * this.resolution, height * this.resolution) + .addColorTexture(0, this); + + // TODO - could this be added the systems? + + /** + * The data structure for the stencil masks + * + * @member {PIXI.Graphics[]} + */ + this.stencilMaskStack = []; + + /** + * The data structure for the filters + * + * @member {PIXI.Graphics[]} + */ + this.filterStack = [{}]; + } + + /** + * Resizes the BaseRenderTexture. + * + * @param {number} width - The width to resize to. + * @param {number} height - The height to resize to. + */ + resize(width, height) + { + width = Math.ceil(width); + height = Math.ceil(height); + this.frameBuffer.resize(width * this.resolution, height * this.resolution); + } + + /** + * Destroys this texture + * + */ + destroy() + { + super.destroy(true); + this.renderer = null; + } +} diff --git a/packages/core/src/renderTexture/RenderTexture.js b/packages/core/src/renderTexture/RenderTexture.js new file mode 100644 index 0000000..f1ecf1f --- /dev/null +++ b/packages/core/src/renderTexture/RenderTexture.js @@ -0,0 +1,153 @@ +import BaseRenderTexture from './BaseRenderTexture'; +import Texture from '../textures/Texture'; + +/** + * A RenderTexture is a special texture that allows any PixiJS display object to be rendered to it. + * + * __Hint__: All DisplayObjects (i.e. Sprites) that render to a RenderTexture should be preloaded + * otherwise black rectangles will be drawn instead. + * + * A RenderTexture takes a snapshot of any Display Object given to its render method. For example: + * + * ```js + * let renderer = PIXI.autoDetectRenderer(1024, 1024, { view: canvas, ratio: 1 }); + * let renderTexture = PIXI.RenderTexture.create(800, 600); + * let sprite = PIXI.Sprite.fromImage("spinObj_01.png"); + * + * sprite.position.x = 800/2; + * sprite.position.y = 600/2; + * sprite.anchor.x = 0.5; + * sprite.anchor.y = 0.5; + * + * renderer.render(sprite, renderTexture); + * ``` + * + * The Sprite in this case will be rendered using its local transform. To render this sprite at 0,0 + * you can clear the transform + * + * ```js + * + * sprite.setTransform() + * + * let renderTexture = new PIXI.RenderTexture.create(100, 100); + * + * renderer.render(sprite, renderTexture); // Renders to center of RenderTexture + * ``` + * + * @class + * @extends PIXI.Texture + * @memberof PIXI + */ +export default class RenderTexture extends Texture +{ + /** + * @param {PIXI.BaseRenderTexture} baseRenderTexture - The renderer used for this RenderTexture + * @param {PIXI.Rectangle} [frame] - The rectangle frame of the texture to show + */ + constructor(baseRenderTexture, frame) + { + // support for legacy.. + let _legacyRenderer = null; + + if (!(baseRenderTexture instanceof BaseRenderTexture)) + { + /* eslint-disable prefer-rest-params, no-console */ + const width = arguments[1]; + const height = arguments[2]; + const scaleMode = arguments[3]; + const resolution = arguments[4]; + + // we have an old render texture.. + console.warn(`Please use RenderTexture.create(${width}, ${height}) instead of the ctor directly.`); + _legacyRenderer = arguments[0]; + /* eslint-enable prefer-rest-params, no-console */ + + frame = null; + baseRenderTexture = new BaseRenderTexture({ + width, + height, + scaleMode, + resolution, + }); + } + + /** + * The base texture object that this texture uses + * + * @member {BaseTexture} + */ + super(baseRenderTexture, frame); + + this.legacyRenderer = _legacyRenderer; + + /** + * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. + * + * @member {boolean} + */ + this.valid = true; + + /** + * FilterSystem temporary storage + * @private + * @member {PIXI.Rectangle} + */ + this.filterFrame = null; + + this._updateUvs(); + } + + /** + * Resizes the RenderTexture. + * + * @param {number} width - The width to resize to. + * @param {number} height - The height to resize to. + * @param {boolean} [resizeBaseTexture=true] - Should the baseTexture.width and height values be resized as well? + */ + resize(width, height, resizeBaseTexture = true) + { + width = Math.ceil(width); + height = Math.ceil(height); + + // TODO - could be not required.. + this.valid = (width > 0 && height > 0); + + this._frame.width = this.orig.width = width; + this._frame.height = this.orig.height = height; + + if (resizeBaseTexture) + { + this.baseTexture.resize(width, height); + } + + this._updateUvs(); + } + + /** + * A short hand way of creating a render texture. + * + * @param {object} [options] - Options + * @param {number} [options.width=100] - The width of the render texture + * @param {number} [options.height=100] - The height of the render texture + * @param {number} [options.scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the texture being generated + * @return {PIXI.RenderTexture} The new render texture + */ + static create(options) + { + // fallback, old-style: create(width, height, scaleMode, resolution) + if (typeof options === 'number') + { + /* eslint-disable prefer-rest-params */ + options = { + width: options, + height: arguments[1], + scaleMode: arguments[2], + resolution: arguments[3], + }; + /* eslint-enable prefer-rest-params */ + } + + return new RenderTexture(new BaseRenderTexture(options)); + } +} diff --git a/packages/core/src/renderTexture/RenderTextureSystem.js b/packages/core/src/renderTexture/RenderTextureSystem.js new file mode 100644 index 0000000..8d0ca61 --- /dev/null +++ b/packages/core/src/renderTexture/RenderTextureSystem.js @@ -0,0 +1,115 @@ +import System from '../System'; +import { Rectangle } from '@pixi/math'; + +const tempRect = new Rectangle(); + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class RenderTextureSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.clearColor = renderer._backgroundColorRgba; + + // TODO moe this property somewhere else! + this.defaultMaskStack = []; + this.defaultFilterStack = [{}]; + + // empty render texture? + this.renderTexture = null; + + this.destinationFrame = new Rectangle(); + } + + bind(renderTexture, sourceFrame, destinationFrame) + { + // TODO - do we want this?? + if (this.renderTexture === renderTexture) return; + this.renderTexture = renderTexture; + + const renderer = this.renderer; + + if (renderTexture) + { + const baseTexture = renderTexture.baseTexture; + + if (!destinationFrame) + { + tempRect.width = baseTexture.realWidth; + tempRect.height = baseTexture.realHeight; + + destinationFrame = tempRect; + } + + if (!sourceFrame) + { + sourceFrame = destinationFrame; + } + + this.renderer.framebuffer.bind(baseTexture.frameBuffer, destinationFrame); + + this.renderer.projection.update(destinationFrame, sourceFrame, baseTexture.resolution, false); + this.renderer.stencil.setMaskStack(baseTexture.stencilMaskStack); + } + else + { + // TODO these validation checks happen deeper down.. + // thing they can be avoided.. + if (!destinationFrame) + { + tempRect.width = renderer.width; + tempRect.height = renderer.height; + + destinationFrame = tempRect; + } + + if (!sourceFrame) + { + sourceFrame = destinationFrame; + } + + renderer.framebuffer.bind(null, destinationFrame); + + // TODO store this.. + this.renderer.projection.update(destinationFrame, sourceFrame, this.renderer.resolution, true); + this.renderer.stencil.setMaskStack(this.defaultMaskStack); + } + + this.destinationFrame.copyFrom(destinationFrame); + } + + /** + * Erases the render texture and fills the drawing area with a colour + * + * @param {number} [clearColor] - The colour + * @return {PIXI.Renderer} Returns itself. + */ + clear(clearColor) + { + if (this.renderTexture) + { + clearColor = clearColor || this.renderTexture.baseTexture.clearColor; + } + else + { + clearColor = clearColor || this.clearColor; + } + + this.renderer.framebuffer.clear(clearColor[0], clearColor[1], clearColor[2], clearColor[3]); + } + + resize()// screenWidth, screenHeight) + { + // resize the root only! + this.bind(null); + } +} diff --git a/packages/core/src/renderers/AbstractRenderer.js b/packages/core/src/renderers/AbstractRenderer.js deleted file mode 100644 index 928670f..0000000 --- a/packages/core/src/renderers/AbstractRenderer.js +++ /dev/null @@ -1,353 +0,0 @@ -import { hex2string, hex2rgb } from '@pixi/utils'; -import { Matrix, Rectangle } from '@pixi/math'; -import { RENDERER_TYPE } from '@pixi/constants'; -import { settings } from '@pixi/settings'; -import { Container } from '@pixi/display'; -import RenderTexture from '../textures/RenderTexture'; -import EventEmitter from 'eventemitter3'; - -const tempMatrix = new Matrix(); - -/** - * 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 EventEmitter - * @memberof PIXI - */ -export default class AbstractRenderer extends EventEmitter -{ - // eslint-disable-next-line valid-jsdoc - /** - * @param {string} system - The name of the system this renderer is for. - * @param {object} [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.transparent=false] - If the render view is transparent, default false - * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false - * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) - * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. The - * resolution of the renderer retina would be 2. - * @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 {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when rendering, - * stopping pixel interpolation. - */ - constructor(system, options, arg2, arg3) - { - super(); - - // Support for constructor(system, screenWidth, screenHeight, options) - if (typeof options === 'number') - { - options = Object.assign({ - width: options, - height: arg2 || settings.RENDER_OPTIONS.height, - }, arg3); - } - - // 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 = RENDERER_TYPE.UNKNOWN; - - /** - * Measurements of the screen. (0, 0, screenWidth, screenHeight) - * - * Its safe to use as filterArea or hitArea for 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 1 - */ - this.resolution = options.resolution || settings.RESOLUTION; - - /** - * Whether the render view is transparent - * - * @member {boolean} - */ - this.transparent = options.transparent; - - /** - * Whether css dimensions of canvas view should be resized to screen dimensions automatically - * - * @member {boolean} - */ - this.autoResize = options.autoResize || false; - - /** - * Tracks the blend modes useful for this renderer. - * - * @member {object} - */ - this.blendModes = null; - - /** - * 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; - - /** - * If true PixiJS will Math.floor() x/y values when rendering, stopping pixel interpolation. - * Handy for crisp pixel art and speed on legacy devices. - * - * @member {boolean} - */ - this.roundPixels = options.roundPixels; - - /** - * The background color as a number. - * - * @member {number} - * @private - */ - this._backgroundColor = 0x000000; - - /** - * The background color as an [R, G, B] array. - * - * @member {number[]} - * @private - */ - this._backgroundColorRgba = [0, 0, 0, 0]; - - /** - * The background color as a string. - * - * @member {string} - * @private - */ - this._backgroundColorString = '#000000'; - - this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter - - /** - * This temporary display object used as the parent of the currently being rendered item - * - * @member {PIXI.DisplayObject} - * @private - */ - this._tempDisplayObjectParent = new Container(); - - /** - * The last root object that the renderer tried to render. - * - * @member {PIXI.DisplayObject} - * @private - */ - this._lastObjectRendered = this._tempDisplayObjectParent; - - /** - * Collection of plugins. - * @readonly - * @member {object} - */ - this.plugins = {}; - } - - /** - * Initialize the plugins. - * - * @protected - * @param {object} staticMap - The dictionary of staticly saved plugins. - */ - initPlugins(staticMap) - { - 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() - { - return this.view.width; - } - - /** - * Same as view.height, actual number of pixels in the canvas by vertical - * - * @member {number} - * @readonly - * @default 600 - */ - get height() - { - return this.view.height; - } - - /** - * Resizes the screen and canvas to the specified width and height - * Canvas dimensions are multiplied by resolution - * - * @param {number} screenWidth - the new width of the screen - * @param {number} screenHeight - the new height of the screen - */ - resize(screenWidth, screenHeight) - { - this.screen.width = screenWidth; - this.screen.height = screenHeight; - - this.view.width = screenWidth * this.resolution; - this.view.height = screenHeight * this.resolution; - - if (this.autoResize) - { - this.view.style.width = `${screenWidth}px`; - this.view.style.height = `${screenHeight}px`; - } - } - - /** - * 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 {PIXI.DisplayObject} displayObject - The displayObject the object will be generated from - * @param {number} scaleMode - Should be one of the scaleMode consts - * @param {number} resolution - The resolution / device pixel ratio of the texture being generated - * @param {PIXI.Rectangle} [region] - The region of the displayObject, that shall be rendered, - * if no region is specified, defaults to the local bounds of the displayObject. - * @return {PIXI.Texture} a texture of the graphics object - */ - generateTexture(displayObject, scaleMode, resolution, region) - { - region = region || displayObject.getLocalBounds(); - - // 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(region.width | 0, region.height | 0, scaleMode, resolution); - - tempMatrix.tx = -region.x; - tempMatrix.ty = -region.y; - - this.render(displayObject, renderTexture, false, tempMatrix, true); - - return renderTexture; - } - - /** - * Removes everything from the renderer and optionally removes the Canvas DOM element. - * - * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. - */ - destroy(removeView) - { - 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); - } - - this.plugins = null; - - this.type = RENDERER_TYPE.UNKNOWN; - - this.view = null; - - this.screen = null; - - this.resolution = 0; - - this.transparent = false; - - this.autoResize = false; - - this.blendModes = null; - - this.options = null; - - this.preserveDrawingBuffer = false; - this.clearBeforeRender = false; - - this.roundPixels = false; - - this._backgroundColor = 0; - this._backgroundColorRgba = null; - this._backgroundColorString = null; - - this._tempDisplayObjectParent = null; - this._lastObjectRendered = null; - } - - /** - * The background color to fill if not transparent - * - * @member {number} - */ - get backgroundColor() - { - return this._backgroundColor; - } - - set backgroundColor(value) // eslint-disable-line require-jsdoc - { - this._backgroundColor = value; - this._backgroundColorString = hex2string(value); - hex2rgb(value, this._backgroundColorRgba); - } -} diff --git a/packages/core/src/renderers/Renderer.js b/packages/core/src/renderers/Renderer.js deleted file mode 100644 index a2020b1..0000000 --- a/packages/core/src/renderers/Renderer.js +++ /dev/null @@ -1,329 +0,0 @@ -import AbstractRenderer from './AbstractRenderer'; -import { sayHello } from '@pixi/utils'; -import MaskSystem from './systems/MaskSystem'; -import StencilSystem from './systems/StencilSystem'; -import FilterSystem from './systems/filter/FilterSystem'; -import FramebufferSystem from './systems/FramebufferSystem'; -import RenderTextureSystem from './systems/RenderTextureSystem'; -import TextureSystem from './systems/textures/TextureSystem'; -import ProjectionSystem from './systems/ProjectionSystem'; -import StateSystem from './systems/StateSystem'; -import GeometrySystem from './systems/geometry/GeometrySystem'; -import ShaderSystem from './systems/shader/ShaderSystem'; -import ContextSystem from './systems/ContextSystem'; -import BatchSystem from './systems/BatchSystem'; -import TextureGCSystem from './systems/textures/TextureGCSystem'; -import { RENDERER_TYPE } from '@pixi/constants'; -import UniformGroup from '../shader/UniformGroup'; -import { Matrix } from '@pixi/math'; -import Runner from 'mini-runner'; - -/** - * 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 Render works by automatically managing webGLBatchs. - * 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 :) - * - * @class - * @memberof PIXI - * @extends PIXI.AbstractRenderer - */ -export default class Renderer extends AbstractRenderer -{ - // eslint-disable-next-line valid-jsdoc - /** - * - * @param {object} [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.transparent=false] - If the render view is transparent, default false - * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false - * @param {boolean} [options.antialias=false] - sets antialias. If not available natively then FXAA - * antialiasing is used - * @param {boolean} [options.forceFXAA=false] - forces FXAA antialiasing to be used over native. - * FXAA is faster, but may not always look as great - * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. - * The resolution of the renderer retina would be 2. - * @param {boolean} [options.clearBeforeRender=true] - This sets if the renderer will clear - * the canvas or not before the new render pass. If you wish to set this to false, you *must* set - * preserveDrawingBuffer to `true`. - * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, - * enable this if you need to call toDataUrl on the webgl context. - * @param {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when - * rendering, stopping pixel interpolation. - * @param {number} [options.backgroundColor=0x000000] - The background color of the rendered area - * (shown if not transparent). - * @param {boolean} [options.legacy=false] - If true PixiJS will aim to ensure compatibility - * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. - * @param {string} [options.powerPreference] - Parameter passed to webgl context, set to "high-performance" - * for devices with dual graphics card - */ - constructor(options, arg2, arg3) - { - super('WebGL', options, arg2, arg3); - - /** - * The type of this renderer as a standardised const - * - * @member {number} - * @see PIXI.RENDERER_TYPE - */ - this.type = RENDERER_TYPE.WEBGL; - - // this will be set by the contextSystem (this.context) - this.gl = null; - this.CONTEXT_UID = 0; - this.legacy = !!options.legacy; - - // TODO legacy! - - // runners! - this.runners = { - destroy: new Runner('destroy'), - contextChange: new Runner('contextChange', 1), - reset: new Runner('reset'), - update: new Runner('update'), - postrender: new Runner('postrender'), - prerender: new Runner('prerender'), - resize: new Runner('resize', 2), - }; - - 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(GeometrySystem, 'geometry') - .addSystem(FramebufferSystem, 'framebuffer') - .addSystem(StencilSystem, 'stencil') - .addSystem(ProjectionSystem, 'projection') - .addSystem(TextureGCSystem, 'textureGC') - .addSystem(FilterSystem, 'filter') - .addSystem(RenderTextureSystem, 'renderTexture') - .addSystem(BatchSystem, 'batch'); - - this.initPlugins(Renderer.__plugins); - - /** - * The options passed in to create a new webgl context. - * - * @member {object} - * @private - */ - if (options.context) - { - this.context.initFromContext(options.context); - } - else - { - this.context.initFromOptions({ - alpha: this.transparent, - antialias: options.antialias, - premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', - stencil: true, - 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); - } - - addSystem(_class, name) - { - if (!name) - { - name = _class.name; - } - - // TODO - read name from class.name.. - - /* - if(name.includes('System')) - { - name = name.replace('System', ''); - name = name.charAt(0).toLowerCase() + name.slice(1); - } - */ - - const system = new _class(this); - - if (this[name]) - { - throw new Error(`Whoops! ${name} is already a manger`); - } - - this[name] = system; - - for (const i in this.runners) - { - this.runners[i].add(system); - } - - return this; - - /** - * 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. - */ - } - - /** - * Renders the object to its webGL view - * - * @param {PIXI.DisplayObject} displayObject - the object to be rendered - * @param {PIXI.RenderTexture} renderTexture - The render texture to render to. - * @param {boolean} [clear] - Should the canvas be cleared before the new render - * @param {PIXI.Transform} [transform] - A transform to apply to the render texture before rendering. - * @param {boolean} [skipUpdateTransform] - Should we skip the update transform pass? - */ - render(displayObject, renderTexture, clear, transform, skipUpdateTransform) - { - // can be handy to know! - this.renderingToScreen = !renderTexture; - - this.runners.prerender.run(); - this.emit('prerender'); - - // 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.parent; - - displayObject.parent = this._tempDisplayObjectParent; - displayObject.updateTransform(); - displayObject.parent = 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.run(); - - this.emit('postrender'); - } - - /** - * Resizes the webGL view to the specified width and height. - * - * @param {number} screenWidth - the new width of the screen - * @param {number} screenHeight - the new height of the screen - */ - resize(screenWidth, screenHeight) - { - AbstractRenderer.prototype.resize.call(this, screenWidth, screenHeight); - - this.runners.resize.run(screenWidth, screenHeight); - } - - /** - * Resets the WebGL state so you can render things however you fancy! - * - * @return {PIXI.Renderer} Returns itself. - */ - reset() - { - this.runners.reset.run(); - - return this; - } - - clear() - { - this.framebuffer.bind(); - this.framebuffer.clear(); - } - - /** - * Removes everything from the renderer (event listeners, spritebatch, etc...) - * - * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. - * See: https://github.com/pixijs/pixi.js/issues/2233 - */ - destroy(removeView) - { - this.runners.destroy.run(); - - // call base destroy - super.destroy(removeView); - - // TODO nullify all the managers.. - this.gl = null; - } - - /** - * 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 - * builds and excluding plugins. - * @name PIXI.Renderer#plugins - * @type {object} - * @readonly - * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. - * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. - * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. - * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. - */ - - /** - * Adds a plugin to the renderer. - * - * @method - * @param {string} pluginName - The name of the plugin. - * @param {Function} ctor - The constructor function or class for the plugin. - */ - static registerPlugin(pluginName, ctor) - { - Renderer.__plugins = Renderer.__plugins || {}; - Renderer.__plugins[pluginName] = ctor; - } -} diff --git a/packages/core/src/renderers/State.js b/packages/core/src/renderers/State.js deleted file mode 100644 index fa3f221..0000000 --- a/packages/core/src/renderers/State.js +++ /dev/null @@ -1,163 +0,0 @@ -/* eslint-disable max-len */ - -const BLEND = 0; -const OFFSET = 1; -const CULLING = 2; -const DEPTH_TEST = 3; -const WINDING = 4; - -/** - * This is a webGL state. It is passed The WebGL StateManager. - * Each mesh renderered may require webGL to be in a different state. - * For example you may want different blend mode or to enable polygon offsets - * - * @class - * @memberof PIXI - */ -export default class State -{ - /** - * - */ - constructor() - { - this.data = 0; - - this.blendMode = 0; - this.polygonOffset = 0; - - this.blend = true; - // this.depthTest = true; - } - - /** - * Activates blending of the computed fragment color values - * - * @member {boolean} - */ - get blend() - { - return !!(this.data & (1 << BLEND)); - } - - set blend(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << BLEND)) !== value) - { - this.data ^= (1 << BLEND); - } - } - - /** - * Activates adding an offset to depth values of polygon's fragments - * - * @member {boolean} - * @default false - */ - get offsets() - { - return !!(this.data & (1 << OFFSET)); - } - - set offsets(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << OFFSET)) !== value) - { - this.data ^= (1 << OFFSET); - } - } - - /** - * Activates culling of polygons. - * - * @member {boolean} - * @default false - */ - get culling() - { - return !!(this.data & (1 << CULLING)); - } - - set culling(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << CULLING)) !== value) - { - this.data ^= (1 << CULLING); - } - } - - /** - * Activates depth comparisons and updates to the depth buffer. - * - * @member {boolean} - * @default false - */ - get depthTest() - { - return !!(this.data & (1 << DEPTH_TEST)); - } - - set depthTest(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << DEPTH_TEST)) !== value) - { - this.data ^= (1 << DEPTH_TEST); - } - } - - /** - * Specifies whether or not front or back-facing polygons can be culled. - * @member {boolean} - * @default false - */ - get clockwiseFrontFace() - { - return !!(this.data & (1 << WINDING)); - } - - set clockwiseFrontFace(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << WINDING)) !== value) - { - this.data ^= (1 << WINDING); - } - } - - /** - * The blend mode to be applied when this state is set. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. - * Setting this mode to anything other than NO_BLEND will automatically switch blending on. - * - * @member {boolean} - * @default PIXI.BLEND_MODES.NORMAL - * @see PIXI.BLEND_MODES - */ - get blendMode() - { - return this._blendMode; - } - - set blendMode(value) // eslint-disable-line require-jsdoc - { - // 17 is NO BLEND - this.blend = (value !== 17); - this._blendMode = value; - } - - /** - * The polygon offset. Setting this property to anything other than 0 will automatically enable poygon offset fill. - * - * @member {number} - * @default 0 - */ - get polygonOffset() - { - return this._polygonOffset; - } - - set polygonOffset(value) // eslint-disable-line require-jsdoc - { - this.offsets = !!value; - this._polygonOffset = value; - } -} - diff --git a/packages/core/src/renderers/filters/Filter.js b/packages/core/src/renderers/filters/Filter.js deleted file mode 100644 index 2073b8d..0000000 --- a/packages/core/src/renderers/filters/Filter.js +++ /dev/null @@ -1,140 +0,0 @@ -import Shader from '../../shader/Shader'; -import Program from '../../shader/Program'; -import State from '../State'; -import { settings } from '@pixi/settings'; -// import extractUniformsFromSrc from './extractUniformsFromSrc'; -import defaultVertex from './defaultFilter.vert'; -import defaultFragment from './defaultFilter.frag'; - -// let math = require('../../math'); -/** - * @class - * @memberof PIXI - * @extends PIXI.Shader - */ -export default class Filter extends Shader -{ - /** - * @param {string} [vertexSrc] - The source of the vertex shader. - * @param {string} [fragmentSrc] - The source of the fragment shader. - * @param {object} [uniforms] - Custom uniforms to use to augment the built-in ones. - */ - constructor(vertexSrc, fragmentSrc, uniforms) - { - const program = Program.from(vertexSrc, fragmentSrc); - - super(program, uniforms); - - /** - * The padding of the filter. Some filters require extra space to breath such as a blur. - * Increasing this will add extra width and height to the bounds of the object that the - * filter is applied to. - * - * @member {number} - */ - this.padding = 0; - - /** - * The resolution of the filter. Setting this to be lower will lower the quality but - * increase the performance of the filter. - * - * @member {number} - */ - this.resolution = settings.RESOLUTION; - - /** - * If enabled is true the filter is applied, if false it will not. - * - * @member {boolean} - */ - this.enabled = true; - - /** - * If enabled, PixiJS will fit the filter area into boundaries for better performance. - * Switch it off if it does not work for specific shader. - * - * @member {boolean} - */ - this.autoFit = true; - - /** - * Legacy filters use position and uvs from attributes - * @member {boolean} - * @readonly - */ - this.legacy = !!this.program.attributeData.aTextureCoord; - - /** - * the webGL state the filter requires to render - * @member {PIXI.State} - */ - this.state = new State(); - } - - /** - * Applies the filter - * - * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from - * @param {PIXI.RenderTarget} input - The input render target. - * @param {PIXI.RenderTarget} output - The target to output to. - * @param {boolean} clear - Should the output be cleared before rendering to it - * @param {object} [currentState] - It's current state of filter. - * There are some useful properties in the currentState : - * target, filters, sourceFrame, destinationFrame, renderTarget, resolution - */ - apply(filterManager, input, output, clear, currentState, derp) // eslint-disable-line no-unused-vars - { - // do as you please! - - filterManager.applyFilter(this, input, output, clear, currentState, derp); - - // or just do a regular render.. - } - - /** - * Sets the blendmode of the filter - * - * @member {number} - * @default PIXI.BLEND_MODES.NORMAL - */ - get blendMode() - { - return this.state.blendMode; - } - - set blendMode(value) // eslint-disable-line require-jsdoc - { - this.state.blendMode = value; - } - - /** - * The default vertex shader source - * - * @static - * @constant - */ - static get defaultVertexSrc() - { - return defaultVertex; - } - - /** - * The default fragment shader source - * - * @static - * @constant - */ - static get defaultFragmentSrc() - { - return defaultFragment; - } -} - -/** - * Used for caching shader IDs - * - * @static - * @private - */ -Filter.SOURCE_KEY_MAP = {}; - diff --git a/packages/core/src/renderers/filters/defaultFilter.frag b/packages/core/src/renderers/filters/defaultFilter.frag deleted file mode 100644 index a14c1be..0000000 --- a/packages/core/src/renderers/filters/defaultFilter.frag +++ /dev/null @@ -1,22 +0,0 @@ -varying vec2 vTextureCoord; -varying vec2 vFilterCoord; - -uniform sampler2D uSampler; -uniform sampler2D filterSampler; - -void main(void){ - vec4 masky = texture2D(filterSampler, vFilterCoord); - vec4 sample = texture2D(uSampler, vTextureCoord); - vec4 color; - if(mod(vFilterCoord.x, 1.0) > 0.5) - { - color = vec4(1.0, 0.0, 0.0, 1.0); - } - else - { - color = vec4(0.0, 1.0, 0.0, 1.0); - } - // gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0); - gl_FragColor = mix(sample, masky, 0.5); - gl_FragColor *= sample.a; -} diff --git a/packages/core/src/renderers/filters/defaultFilter.vert b/packages/core/src/renderers/filters/defaultFilter.vert deleted file mode 100644 index e1febca..0000000 --- a/packages/core/src/renderers/filters/defaultFilter.vert +++ /dev/null @@ -1,14 +0,0 @@ -attribute vec2 aVertexPosition; -attribute vec2 aTextureCoord; - -uniform mat3 projectionMatrix; -uniform mat3 filterMatrix; - -varying vec2 vTextureCoord; -varying vec2 vFilterCoord; - -void main(void){ - gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); - vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy; - vTextureCoord = aTextureCoord ; -} diff --git a/packages/core/src/renderers/filters/filterTransforms.js b/packages/core/src/renderers/filters/filterTransforms.js deleted file mode 100644 index 87a3cd6..0000000 --- a/packages/core/src/renderers/filters/filterTransforms.js +++ /dev/null @@ -1,50 +0,0 @@ -import { Matrix } from '@pixi/math'; - -/** - * Calculates the mapped matrix - * @param filterArea {Rectangle} The filter area - * @param sprite {Sprite} the target sprite - * @param outputMatrix {Matrix} @alvin - * @private - */ -// this returns a matrix that will normalise map filter cords in the filter to screen space -export function calculateScreenSpaceMatrix(outputMatrix, filterArea, textureSize) -{ - // TODO unwrap? - const mappedMatrix = outputMatrix.identity(); - - mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); - - mappedMatrix.scale(textureSize.width, textureSize.height); - - return mappedMatrix; -} - -export function calculateNormalizedScreenSpaceMatrix(outputMatrix, filterArea, textureSize) -{ - const mappedMatrix = outputMatrix.identity(); - - mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); - - const translateScaleX = (textureSize.width / filterArea.width); - const translateScaleY = (textureSize.height / filterArea.height); - - mappedMatrix.scale(translateScaleX, translateScaleY); - - return mappedMatrix; -} - -// this will map the filter coord so that a texture can be used based on the transform of a sprite -export function calculateSpriteMatrix(outputMatrix, filterArea, textureSize, sprite) -{ - const orig = sprite._texture.orig; - const mappedMatrix = outputMatrix.set(textureSize.width, 0, 0, textureSize.height, filterArea.x, filterArea.y); - const worldTransform = sprite.worldTransform.copyTo(Matrix.TEMP_MATRIX); - - worldTransform.invert(); - mappedMatrix.prepend(worldTransform); - mappedMatrix.scale(1.0 / orig.width, 1.0 / orig.height); - mappedMatrix.translate(sprite.anchor.x, sprite.anchor.y); - - return mappedMatrix; -} diff --git a/packages/core/src/renderers/filters/spriteMask/SpriteMaskFilter.js b/packages/core/src/renderers/filters/spriteMask/SpriteMaskFilter.js deleted file mode 100644 index d5a91a4..0000000 --- a/packages/core/src/renderers/filters/spriteMask/SpriteMaskFilter.js +++ /dev/null @@ -1,63 +0,0 @@ -import Filter from '../Filter'; -import { Matrix } from '@pixi/math'; -import vertex from './spriteMaskFilter.vert'; -import fragment from './spriteMaskFilter.frag'; -import { default as TextureMatrix } from '../../../textures/TextureMatrix'; - -/** - * The SpriteMaskFilter class - * - * @class - * @extends PIXI.Filter - * @memberof PIXI - */ -export default class SpriteMaskFilter extends Filter -{ - /** - * @param {PIXI.Sprite} sprite - the target sprite - */ - constructor(sprite) - { - const maskMatrix = new Matrix(); - - super(vertex, fragment); - - sprite.renderable = false; - - this.maskSprite = sprite; - this.maskMatrix = maskMatrix; - } - - /** - * Applies the filter - * - * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from - * @param {PIXI.RenderTarget} input - The input render target. - * @param {PIXI.RenderTarget} output - The target to output to. - */ - apply(filterManager, input, output) - { - const maskSprite = this.maskSprite; - const tex = this.maskSprite.texture; - - if (!tex.valid) - { - return; - } - if (!tex.transform) - { - // margin = 0.0, let it bleed a bit, shader code becomes easier - // assuming that atlas textures were made with 1-pixel padding - tex.transform = new TextureMatrix(tex, 0.0); - } - tex.transform.update(); - - this.uniforms.mask = tex; - this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite) - .prepend(tex.transform.mapCoord); - this.uniforms.alpha = maskSprite.worldAlpha; - this.uniforms.maskClamp = tex.transform.uClampFrame; - - filterManager.applyFilter(this, input, output); - } -} diff --git a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.frag b/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.frag deleted file mode 100644 index 0e4aef8..0000000 --- a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.frag +++ /dev/null @@ -1,23 +0,0 @@ -varying vec2 vMaskCoord; -varying vec2 vTextureCoord; - -uniform sampler2D uSampler; -uniform sampler2D mask; -uniform float alpha; -uniform vec4 maskClamp; - -void main(void) -{ - float clip = step(3.5, - step(maskClamp.x, vMaskCoord.x) + - step(maskClamp.y, vMaskCoord.y) + - step(vMaskCoord.x, maskClamp.z) + - step(vMaskCoord.y, maskClamp.w)); - - vec4 original = texture2D(uSampler, vTextureCoord); - vec4 masky = texture2D(mask, vMaskCoord); - - original *= (masky.r * masky.a * alpha * clip); - - gl_FragColor = original; -} diff --git a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.vert b/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.vert deleted file mode 100644 index d23a30c..0000000 --- a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.vert +++ /dev/null @@ -1,16 +0,0 @@ -attribute vec2 aVertexPosition; -attribute vec2 aTextureCoord; - -uniform mat3 projectionMatrix; -uniform mat3 otherMatrix; - -varying vec2 vMaskCoord; -varying vec2 vTextureCoord; - -void main(void) -{ - gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); - - vTextureCoord = aTextureCoord; - vMaskCoord = ( otherMatrix * vec3( aTextureCoord, 1.0) ).xy; -} diff --git a/packages/core/src/renderers/systems/BatchSystem.js b/packages/core/src/renderers/systems/BatchSystem.js deleted file mode 100644 index 70e9518..0000000 --- a/packages/core/src/renderers/systems/BatchSystem.js +++ /dev/null @@ -1,68 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import ObjectRenderer from '../utils/ObjectRenderer'; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ - -export default class BatchSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this manager works for. - */ - constructor(renderer) - { - super(renderer); - - /** - * An empty renderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.emptyRenderer = new ObjectRenderer(renderer); - - /** - * The currently active ObjectRenderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.currentRenderer = this.emptyRenderer; - } - - /** - * Changes the current renderer to the one given in parameter - * - * @param {PIXI.ObjectRenderer} objectRenderer - The object renderer to use. - */ - setObjectRenderer(objectRenderer) - { - if (this.currentRenderer === objectRenderer) - { - return; - } - - this.currentRenderer.stop(); - this.currentRenderer = objectRenderer; - - this.renderer.state.setState(objectRenderer.state); - - this.currentRenderer.start(); - } - - /** - * This should be called if you wish to do some custom rendering - * It will basically render anything that may be batched up such as sprites - * - */ - flush() - { - this.setObjectRenderer(this.emptyRenderer); - } - - reset() - { - this.setObjectRenderer(this.emptyRenderer); - } -} diff --git a/packages/core/src/renderers/systems/ContextSystem.js b/packages/core/src/renderers/systems/ContextSystem.js deleted file mode 100644 index 43bf827..0000000 --- a/packages/core/src/renderers/systems/ContextSystem.js +++ /dev/null @@ -1,186 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { settings } from '@pixi/settings'; - -let CONTEXT_UID = 0; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class ContextSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this.webGLVersion = 1; - - this.handleContextLost = this.handleContextLost.bind(this); - this.handleContextRestored = this.handleContextRestored.bind(this); - - this.extensions = {}; - - renderer.view.addEventListener('webglcontextlost', this.handleContextLost, false); - renderer.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); - } - - get isLost() - { - return (!this.gl || this.gl.isContextLost()); - } - - contextChange(gl) - { - this.gl = gl; - - // restore a context if it was previously lost - if (gl.isContextLost() && gl.getExtension('WEBGL_lose_context')) - { - gl.getExtension('WEBGL_lose_context').restoreContext(); - } - } - - initFromContext(gl) - { - this.gl = gl; - this.validateContext(gl); - this.renderer.gl = gl; - this.renderer.CONTEXT_UID = CONTEXT_UID++; - this.renderer.runners.contextChange.run(gl); - } - - initFromOptions(options) - { - const gl = this.createContext(this.renderer.view, options); - - this.initFromContext(gl); - } - - /** - * Helper class to create a webGL Context - * - * @class - * @memberof PIXI.glCore - * @param canvas {HTMLCanvasElement} the canvas element that we will get the context from - * @param options {Object} An options object that gets passed in to the canvas element containing the context attributes, - * see https://developer.mozilla.org/en/docs/Web/API/HTMLCanvasElement/getContext for the options available - * @return {WebGLRenderingContext} the WebGL context - */ - createContext(canvas, options) - { - let gl; - - if (settings.PREFER_WEBGL_2) - { - gl = canvas.getContext('webgl2', options); - } - - if (gl) - { - this.webGLVersion = 2; - } - else - { - this.webGLVersion = 1; - - gl = canvas.getContext('webgl', options) - || canvas.getContext('experimental-webgl', options); - - if (!gl) - { - // fail, not able to get a context - throw new Error('This browser does not support webGL. Try using the canvas renderer'); - } - } - - this.gl = gl; - - this.getExtensions(); - - return gl; - } - - getExtensions() - { - // time to set up default etensions that pixi uses.. - const gl = this.gl; - const extensions = this.extensions; - - if (this.webGLVersion === 1) - { - extensions.drawBuffers = gl.getExtension('WEBGL_draw_buffers'); - extensions.depthTexture = gl.getExtension('WEBKIT_WEBGL_depth_texture'); - extensions.floatTexture = gl.getExtension('OES_texture_float'); - extensions.loseContext = gl.getExtension('WEBGL_lose_context'); - - extensions.vertexArrayObject = gl.getExtension('OES_vertex_array_object') - || gl.getExtension('MOZ_OES_vertex_array_object') - || gl.getExtension('WEBKIT_OES_vertex_array_object'); - } - - // we don't use any specific WebGL 2 ones yet! - } - - /** - * Handles a lost webgl context - * - * @private - * @param {WebGLContextEvent} event - The context lost event. - */ - handleContextLost(event) - { - event.preventDefault(); - } - - /** - * Handles a restored webgl context - * - * @private - */ - handleContextRestored() - { - this.renderer.runners.contextChange.run(this.gl); - } - - destroy() - { - const view = this.renderer.view; - - // remove listeners - view.removeEventListener('webglcontextlost', this.handleContextLost); - view.removeEventListener('webglcontextrestored', this.handleContextRestored); - - this.gl.useProgram(null); - - if (this.extensions.loseContext) - { - this.extensions.loseContext.loseContext(); - } - } - - postrender() - { - this.gl.flush(); - } - - validateContext(gl) - { - const attributes = gl.getContextAttributes(); - - // this is going to be fairly simple for now.. but at least we have room to grow! - if (!attributes.stencil) - { - /* eslint-disable max-len */ - - /* eslint-disable no-console */ - console.warn('Provided WebGL context does not have a stencil buffer, masks may not render correctly'); - /* eslint-enable no-console */ - - /* eslint-enable max-len */ - } - } -} diff --git a/packages/core/src/renderers/systems/FramebufferSystem.js b/packages/core/src/renderers/systems/FramebufferSystem.js deleted file mode 100644 index df7902b..0000000 --- a/packages/core/src/renderers/systems/FramebufferSystem.js +++ /dev/null @@ -1,246 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { Rectangle } from '@pixi/math'; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class FramebufferSystem extends WebGLSystem -{ - /** - * Sets up the renderer context and necessary buffers. - * - * @private - */ - contextChange() - { - this.gl = this.renderer.gl; - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - this.current = null; - this.viewport = new Rectangle(); - - this.drawBufferExtension = this.renderer.context.extensions.drawBuffers; - } - - bind(framebuffer, frame) - { - const gl = this.gl; - - this.current = framebuffer; - - if (framebuffer) - { - // TODO cacheing layer! - - const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID] || this.initFramebuffer(framebuffer); - - gl.bindFramebuffer(gl.FRAMEBUFFER, fbo.framebuffer); - // makesure all textures are unbound.. - - // now check for updates... - if (fbo.dirtyId !== framebuffer.dirtyId) - { - fbo.dirtyId = framebuffer.dirtyId; - - if (fbo.dirtyFormat !== framebuffer.dirtyFormat) - { - fbo.dirtyFormat = framebuffer.dirtyFormat; - this.updateFramebuffer(framebuffer); - } - else if (fbo.dirtySize !== framebuffer.dirtySize) - { - fbo.dirtySize = framebuffer.dirtySize; - this.resizeFramebuffer(framebuffer); - } - } - - for (let i = 0; i < framebuffer.colorTextures.length; i++) - { - if (framebuffer.colorTextures[i].texturePart) - { - this.renderer.texture.unbind(framebuffer.colorTextures[i].texture); - } - else - { - this.renderer.texture.unbind(framebuffer.colorTextures[i]); - } - } - - if (framebuffer.depthTexture) - { - this.renderer.texture.unbind(framebuffer.depthTexture); - } - - if (frame) - { - this.setViewport(frame.x, frame.y, frame.width, frame.height); - } - else - { - this.setViewport(0, 0, framebuffer.width, framebuffer.height); - } - } - else - { - gl.bindFramebuffer(gl.FRAMEBUFFER, null); - - if (frame) - { - this.setViewport(frame.x, frame.y, frame.width, frame.height); - } - else - { - this.setViewport(0, 0, this.renderer.width, this.renderer.height); - } - } - } - - setViewport(x, y, width, height) - { - const v = this.viewport; - - if (v.width !== width || v.height !== height || v.x !== x || v.y !== y) - { - v.x = x; - v.y = y; - v.width = width; - v.height = height; - - this.gl.viewport(x, y, width, height); - } - } - - get size() - { - if (this.current) - { - // TODO store temp - return { x: 0, y: 0, width: this.current.width, height: this.current.height }; - } - - return { x: 0, y: 0, width: this.renderer.width, height: this.renderer.height }; - } - - clear(r, g, b, a) - { - const gl = this.gl; - - // TODO clear color can be set only one right? - gl.clearColor(r, g, b, a); - gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); - } - - // private functions... - - initFramebuffer(framebuffer) - { - const gl = this.gl; - - // TODO - make this a class? - const fbo = { - framebuffer: gl.createFramebuffer(), - stencil: null, - dirtyId: 0, - dirtyFormat: 0, - dirtySize: 0, - }; - - framebuffer.glFrameBuffers[this.CONTEXT_UID] = fbo; - - return fbo; - } - - resizeFramebuffer(framebuffer) - { - const gl = this.gl; - - if (framebuffer.stencil || framebuffer.depth) - { - gl.bindRenderbuffer(gl.RENDERBUFFER, this.stencil); - gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); - } - } - - updateFramebuffer(framebuffer) - { - const gl = this.gl; - - const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID]; - - // bind the color texture - const colorTextures = framebuffer.colorTextures; - - let count = colorTextures.length; - - if (!this.drawBufferExtension) - { - count = Math.min(count, 1); - } - - const activeTextures = []; - - for (let i = 0; i < count; i++) - { - const texture = framebuffer.colorTextures[i]; - - if (texture.texturePart) - { - this.renderer.texture.bind(texture.texture, 0); - - gl.framebufferTexture2D(gl.FRAMEBUFFER, - gl.COLOR_ATTACHMENT0 + i, - gl.TEXTURE_CUBE_MAP_NEGATIVE_X + texture.side, - texture.texture._glTextures[this.CONTEXT_UID].texture, - 0); - } - else - { - this.renderer.texture.bind(texture, 0); - - gl.framebufferTexture2D(gl.FRAMEBUFFER, - gl.COLOR_ATTACHMENT0 + i, - gl.TEXTURE_2D, - texture._glTextures[this.CONTEXT_UID].texture, - 0); - } - - activeTextures.push(gl.COLOR_ATTACHMENT0 + i); - } - - if (this.drawBufferExtension && activeTextures.length > 1) - { - this.drawBufferExtension.drawBuffersWEBGL(activeTextures); - } - - if (framebuffer.depthTexture) - { - const depthTextureExt = this.renderer.context.extensions.depthTexture; - - if (depthTextureExt) - { - const depthTexture = framebuffer.depthTexture; - - this.renderer.texture.bind(depthTexture, 0); - - gl.framebufferTexture2D(gl.FRAMEBUFFER, - gl.DEPTH_ATTACHMENT, - gl.TEXTURE_2D, - depthTexture._glTextures[this.CONTEXT_UID].texture, - 0); - } - } - - if (framebuffer.stencil || framebuffer.depth) - { - fbo.stencil = gl.createRenderbuffer(); - - gl.bindRenderbuffer(gl.RENDERBUFFER, fbo.stencil); - - // TODO.. this is depth AND stencil? - gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, fbo.stencil); - gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); - // fbo.enableStencil(); - } - } -} diff --git a/packages/core/src/AbstractRenderer.js b/packages/core/src/AbstractRenderer.js new file mode 100644 index 0000000..64b2eb1 --- /dev/null +++ b/packages/core/src/AbstractRenderer.js @@ -0,0 +1,353 @@ +import { hex2string, hex2rgb } from '@pixi/utils'; +import { Matrix, Rectangle } from '@pixi/math'; +import { RENDERER_TYPE } from '@pixi/constants'; +import { settings } from '@pixi/settings'; +import { Container } from '@pixi/display'; +import RenderTexture from './renderTexture/RenderTexture'; +import EventEmitter from 'eventemitter3'; + +const tempMatrix = new Matrix(); + +/** + * 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 EventEmitter + * @memberof PIXI + */ +export default class AbstractRenderer extends EventEmitter +{ + // eslint-disable-next-line valid-jsdoc + /** + * @param {string} system - The name of the system this renderer is for. + * @param {object} [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.transparent=false] - If the render view is transparent, default false + * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false + * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. The + * resolution of the renderer retina would be 2. + * @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 {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when rendering, + * stopping pixel interpolation. + */ + constructor(system, options, arg2, arg3) + { + super(); + + // Support for constructor(system, screenWidth, screenHeight, options) + if (typeof options === 'number') + { + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + }, arg3); + } + + // 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 = RENDERER_TYPE.UNKNOWN; + + /** + * Measurements of the screen. (0, 0, screenWidth, screenHeight) + * + * Its safe to use as filterArea or hitArea for 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 1 + */ + this.resolution = options.resolution || settings.RESOLUTION; + + /** + * Whether the render view is transparent + * + * @member {boolean} + */ + this.transparent = options.transparent; + + /** + * Whether css dimensions of canvas view should be resized to screen dimensions automatically + * + * @member {boolean} + */ + this.autoResize = options.autoResize || false; + + /** + * Tracks the blend modes useful for this renderer. + * + * @member {object} + */ + this.blendModes = null; + + /** + * 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; + + /** + * If true PixiJS will Math.floor() x/y values when rendering, stopping pixel interpolation. + * Handy for crisp pixel art and speed on legacy devices. + * + * @member {boolean} + */ + this.roundPixels = options.roundPixels; + + /** + * The background color as a number. + * + * @member {number} + * @private + */ + this._backgroundColor = 0x000000; + + /** + * The background color as an [R, G, B] array. + * + * @member {number[]} + * @private + */ + this._backgroundColorRgba = [0, 0, 0, 0]; + + /** + * The background color as a string. + * + * @member {string} + * @private + */ + this._backgroundColorString = '#000000'; + + this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter + + /** + * This temporary display object used as the parent of the currently being rendered item + * + * @member {PIXI.DisplayObject} + * @private + */ + this._tempDisplayObjectParent = new Container(); + + /** + * The last root object that the renderer tried to render. + * + * @member {PIXI.DisplayObject} + * @private + */ + this._lastObjectRendered = this._tempDisplayObjectParent; + + /** + * Collection of plugins. + * @readonly + * @member {object} + */ + this.plugins = {}; + } + + /** + * Initialize the plugins. + * + * @protected + * @param {object} staticMap - The dictionary of staticly saved plugins. + */ + initPlugins(staticMap) + { + 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() + { + return this.view.width; + } + + /** + * Same as view.height, actual number of pixels in the canvas by vertical + * + * @member {number} + * @readonly + * @default 600 + */ + get height() + { + return this.view.height; + } + + /** + * Resizes the screen and canvas to the specified width and height + * Canvas dimensions are multiplied by resolution + * + * @param {number} screenWidth - the new width of the screen + * @param {number} screenHeight - the new height of the screen + */ + resize(screenWidth, screenHeight) + { + this.screen.width = screenWidth; + this.screen.height = screenHeight; + + this.view.width = screenWidth * this.resolution; + this.view.height = screenHeight * this.resolution; + + if (this.autoResize) + { + this.view.style.width = `${screenWidth}px`; + this.view.style.height = `${screenHeight}px`; + } + } + + /** + * 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 {PIXI.DisplayObject} displayObject - The displayObject the object will be generated from + * @param {number} scaleMode - Should be one of the scaleMode consts + * @param {number} resolution - The resolution / device pixel ratio of the texture being generated + * @param {PIXI.Rectangle} [region] - The region of the displayObject, that shall be rendered, + * if no region is specified, defaults to the local bounds of the displayObject. + * @return {PIXI.Texture} a texture of the graphics object + */ + generateTexture(displayObject, scaleMode, resolution, region) + { + region = region || displayObject.getLocalBounds(); + + // 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(region.width | 0, region.height | 0, scaleMode, resolution); + + tempMatrix.tx = -region.x; + tempMatrix.ty = -region.y; + + this.render(displayObject, renderTexture, false, tempMatrix, true); + + return renderTexture; + } + + /** + * Removes everything from the renderer and optionally removes the Canvas DOM element. + * + * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. + */ + destroy(removeView) + { + 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); + } + + this.plugins = null; + + this.type = RENDERER_TYPE.UNKNOWN; + + this.view = null; + + this.screen = null; + + this.resolution = 0; + + this.transparent = false; + + this.autoResize = false; + + this.blendModes = null; + + this.options = null; + + this.preserveDrawingBuffer = false; + this.clearBeforeRender = false; + + this.roundPixels = false; + + this._backgroundColor = 0; + this._backgroundColorRgba = null; + this._backgroundColorString = null; + + this._tempDisplayObjectParent = null; + this._lastObjectRendered = null; + } + + /** + * The background color to fill if not transparent + * + * @member {number} + */ + get backgroundColor() + { + return this._backgroundColor; + } + + set backgroundColor(value) // eslint-disable-line require-jsdoc + { + this._backgroundColor = value; + this._backgroundColorString = hex2string(value); + hex2rgb(value, this._backgroundColorRgba); + } +} diff --git a/packages/core/src/Renderer.js b/packages/core/src/Renderer.js new file mode 100644 index 0000000..2227e78 --- /dev/null +++ b/packages/core/src/Renderer.js @@ -0,0 +1,338 @@ +import AbstractRenderer from './AbstractRenderer'; +import { sayHello } from '@pixi/utils'; +import MaskSystem from './mask/MaskSystem'; +import StencilSystem from './mask/StencilSystem'; +import FilterSystem from './filters/FilterSystem'; +import FramebufferSystem from './framebuffer/FramebufferSystem'; +import RenderTextureSystem from './renderTexture/RenderTextureSystem'; +import TextureSystem from './textures/TextureSystem'; +import ProjectionSystem from './projection/ProjectionSystem'; +import StateSystem from './state/StateSystem'; +import GeometrySystem from './geometry/GeometrySystem'; +import ShaderSystem from './shader/ShaderSystem'; +import ContextSystem from './context/ContextSystem'; +import BatchSystem from './batch/BatchSystem'; +import TextureGCSystem from './textures/TextureGCSystem'; +import { RENDERER_TYPE } from '@pixi/constants'; +import UniformGroup from './shader/UniformGroup'; +import { Matrix } from '@pixi/math'; +import Runner from 'mini-runner'; + +/** + * 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 Render works by automatically managing webGLBatchs. + * 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 :) + * + * @class + * @memberof PIXI + * @extends PIXI.AbstractRenderer + */ +export default class Renderer extends AbstractRenderer +{ + // eslint-disable-next-line valid-jsdoc + /** + * + * @param {object} [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.transparent=false] - If the render view is transparent, default false + * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false + * @param {boolean} [options.antialias=false] - sets antialias. If not available natively then FXAA + * antialiasing is used + * @param {boolean} [options.forceFXAA=false] - forces FXAA antialiasing to be used over native. + * FXAA is faster, but may not always look as great + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. + * The resolution of the renderer retina would be 2. + * @param {boolean} [options.clearBeforeRender=true] - This sets if the renderer will clear + * the canvas or not before the new render pass. If you wish to set this to false, you *must* set + * preserveDrawingBuffer to `true`. + * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, + * enable this if you need to call toDataUrl on the webgl context. + * @param {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when + * rendering, stopping pixel interpolation. + * @param {number} [options.backgroundColor=0x000000] - The background color of the rendered area + * (shown if not transparent). + * @param {boolean} [options.legacy=false] - If true PixiJS will aim to ensure compatibility + * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. + * @param {string} [options.powerPreference] - Parameter passed to webgl context, set to "high-performance" + * for devices with dual graphics card + */ + constructor(options, arg2, arg3) + { + super('WebGL', options, arg2, arg3); + + /** + * The type of this renderer as a standardised const + * + * @member {number} + * @see PIXI.RENDERER_TYPE + */ + this.type = RENDERER_TYPE.WEBGL; + + // this will be set by the contextSystem (this.context) + this.gl = null; + this.CONTEXT_UID = 0; + this.legacy = !!options.legacy; + + // TODO legacy! + + // runners! + this.runners = { + destroy: new Runner('destroy'), + contextChange: new Runner('contextChange', 1), + reset: new Runner('reset'), + update: new Runner('update'), + postrender: new Runner('postrender'), + prerender: new Runner('prerender'), + resize: new Runner('resize', 2), + }; + + 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(GeometrySystem, 'geometry') + .addSystem(FramebufferSystem, 'framebuffer') + .addSystem(StencilSystem, 'stencil') + .addSystem(ProjectionSystem, 'projection') + .addSystem(TextureGCSystem, 'textureGC') + .addSystem(FilterSystem, 'filter') + .addSystem(RenderTextureSystem, 'renderTexture') + .addSystem(BatchSystem, 'batch'); + + this.initPlugins(Renderer.__plugins); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ + if (options.context) + { + this.context.initFromContext(options.context); + } + else + { + this.context.initFromOptions({ + alpha: this.transparent, + antialias: options.antialias, + premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', + stencil: true, + 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); + } + + /** + * Add a new system to the renderer. + * @param {class} ClassRef - Class reference + * @param {string} [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. + * @return {PIXI.Renderer} Return instance of renderer + */ + addSystem(ClassRef, name) + { + if (!name) + { + name = ClassRef.name; + } + + // TODO - read name from class.name.. + + /* + if(name.includes('System')) + { + name = name.replace('System', ''); + name = name.charAt(0).toLowerCase() + name.slice(1); + } + */ + + const system = new ClassRef(this); + + if (this[name]) + { + throw new Error(`Whoops! ${name} is already a manger`); + } + + this[name] = system; + + for (const i in this.runners) + { + this.runners[i].add(system); + } + + return this; + + /** + * 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. + */ + } + + /** + * Renders the object to its webGL view + * + * @param {PIXI.DisplayObject} displayObject - the object to be rendered + * @param {PIXI.RenderTexture} renderTexture - The render texture to render to. + * @param {boolean} [clear] - Should the canvas be cleared before the new render + * @param {PIXI.Transform} [transform] - A transform to apply to the render texture before rendering. + * @param {boolean} [skipUpdateTransform] - Should we skip the update transform pass? + */ + render(displayObject, renderTexture, clear, transform, skipUpdateTransform) + { + // can be handy to know! + this.renderingToScreen = !renderTexture; + + this.runners.prerender.run(); + this.emit('prerender'); + + // 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.parent; + + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = 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.run(); + + this.emit('postrender'); + } + + /** + * Resizes the webGL view to the specified width and height. + * + * @param {number} screenWidth - the new width of the screen + * @param {number} screenHeight - the new height of the screen + */ + resize(screenWidth, screenHeight) + { + AbstractRenderer.prototype.resize.call(this, screenWidth, screenHeight); + + this.runners.resize.run(screenWidth, screenHeight); + } + + /** + * Resets the WebGL state so you can render things however you fancy! + * + * @return {PIXI.Renderer} Returns itself. + */ + reset() + { + this.runners.reset.run(); + + return this; + } + + clear() + { + this.framebuffer.bind(); + this.framebuffer.clear(); + } + + /** + * Removes everything from the renderer (event listeners, spritebatch, etc...) + * + * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. + * See: https://github.com/pixijs/pixi.js/issues/2233 + */ + destroy(removeView) + { + this.runners.destroy.run(); + + // call base destroy + super.destroy(removeView); + + // TODO nullify all the managers.. + this.gl = null; + } + + /** + * 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 + * builds and excluding plugins. + * @name PIXI.Renderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. + */ + + /** + * Adds a plugin to the renderer. + * + * @method + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + static registerPlugin(pluginName, ctor) + { + Renderer.__plugins = Renderer.__plugins || {}; + Renderer.__plugins[pluginName] = ctor; + } +} diff --git a/packages/core/src/System.js b/packages/core/src/System.js new file mode 100644 index 0000000..05a10c8 --- /dev/null +++ b/packages/core/src/System.js @@ -0,0 +1,42 @@ +/** + * System is a base class used for extending systems used by the {@link PIXI.Renderer} + * @see PIXI.Renderer#addSystem + * @class + * @memberof PIXI + */ +export default class System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + /** + * The renderer this manager works for. + * + * @member {PIXI.Renderer} + */ + this.renderer = renderer; + + this.renderer.runners.contextChange.add(this); + } + + /** + * Generic method called when there is a WebGL context change. + * + */ + contextChange() + { + // do some codes init! + } + + /** + * Generic destroy methods to be overridden by the subclass + * + */ + destroy() + { + this.renderer.runners.contextChange.remove(this); + this.renderer = null; + } +} diff --git a/packages/core/src/batch/BatchSystem.js b/packages/core/src/batch/BatchSystem.js new file mode 100644 index 0000000..b04c249 --- /dev/null +++ b/packages/core/src/batch/BatchSystem.js @@ -0,0 +1,68 @@ +import System from '../System'; +import ObjectRenderer from './ObjectRenderer'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class BatchSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + super(renderer); + + /** + * An empty renderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.emptyRenderer = new ObjectRenderer(renderer); + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.currentRenderer = this.emptyRenderer; + } + + /** + * Changes the current renderer to the one given in parameter + * + * @param {PIXI.ObjectRenderer} objectRenderer - The object renderer to use. + */ + setObjectRenderer(objectRenderer) + { + if (this.currentRenderer === objectRenderer) + { + return; + } + + this.currentRenderer.stop(); + this.currentRenderer = objectRenderer; + + this.renderer.state.setState(objectRenderer.state); + + this.currentRenderer.start(); + } + + /** + * This should be called if you wish to do some custom rendering + * It will basically render anything that may be batched up such as sprites + * + */ + flush() + { + this.setObjectRenderer(this.emptyRenderer); + } + + reset() + { + this.setObjectRenderer(this.emptyRenderer); + } +} diff --git a/packages/core/src/batch/ObjectRenderer.js b/packages/core/src/batch/ObjectRenderer.js new file mode 100644 index 0000000..0c851a9 --- /dev/null +++ b/packages/core/src/batch/ObjectRenderer.js @@ -0,0 +1,48 @@ +import System from '../System'; + +/** + * Base for a common object renderer that can be used as a system renderer plugin. + * + * @class + * @extends PIXI.System + * @memberof PIXI + */ +export default class ObjectRenderer extends System +{ + /** + * Starts the renderer and sets the shader + * + */ + start() + { + // set the shader.. + } + + /** + * Stops the renderer + * + */ + stop() + { + this.flush(); + } + + /** + * Stub method for rendering content and emptying the current batch. + * + */ + flush() + { + // flush! + } + + /** + * Renders an object + * + * @param {PIXI.DisplayObject} object - The object to render. + */ + render(object) // eslint-disable-line no-unused-vars + { + // render the object + } +} diff --git a/packages/core/src/context/ContextSystem.js b/packages/core/src/context/ContextSystem.js new file mode 100644 index 0000000..301aab4 --- /dev/null +++ b/packages/core/src/context/ContextSystem.js @@ -0,0 +1,186 @@ +import System from '../System'; +import { settings } from '@pixi/settings'; + +let CONTEXT_UID = 0; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class ContextSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.webGLVersion = 1; + + this.handleContextLost = this.handleContextLost.bind(this); + this.handleContextRestored = this.handleContextRestored.bind(this); + + this.extensions = {}; + + renderer.view.addEventListener('webglcontextlost', this.handleContextLost, false); + renderer.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + } + + get isLost() + { + return (!this.gl || this.gl.isContextLost()); + } + + contextChange(gl) + { + this.gl = gl; + + // restore a context if it was previously lost + if (gl.isContextLost() && gl.getExtension('WEBGL_lose_context')) + { + gl.getExtension('WEBGL_lose_context').restoreContext(); + } + } + + initFromContext(gl) + { + this.gl = gl; + this.validateContext(gl); + this.renderer.gl = gl; + this.renderer.CONTEXT_UID = CONTEXT_UID++; + this.renderer.runners.contextChange.run(gl); + } + + initFromOptions(options) + { + const gl = this.createContext(this.renderer.view, options); + + this.initFromContext(gl); + } + + /** + * Helper class to create a webGL Context + * + * @class + * @memberof PIXI.glCore + * @param canvas {HTMLCanvasElement} the canvas element that we will get the context from + * @param options {Object} An options object that gets passed in to the canvas element containing the context attributes, + * see https://developer.mozilla.org/en/docs/Web/API/HTMLCanvasElement/getContext for the options available + * @return {WebGLRenderingContext} the WebGL context + */ + createContext(canvas, options) + { + let gl; + + if (settings.PREFER_WEBGL_2) + { + gl = canvas.getContext('webgl2', options); + } + + if (gl) + { + this.webGLVersion = 2; + } + else + { + this.webGLVersion = 1; + + gl = canvas.getContext('webgl', options) + || canvas.getContext('experimental-webgl', options); + + if (!gl) + { + // fail, not able to get a context + throw new Error('This browser does not support webGL. Try using the canvas renderer'); + } + } + + this.gl = gl; + + this.getExtensions(); + + return gl; + } + + getExtensions() + { + // time to set up default etensions that pixi uses.. + const gl = this.gl; + const extensions = this.extensions; + + if (this.webGLVersion === 1) + { + extensions.drawBuffers = gl.getExtension('WEBGL_draw_buffers'); + extensions.depthTexture = gl.getExtension('WEBKIT_WEBGL_depth_texture'); + extensions.floatTexture = gl.getExtension('OES_texture_float'); + extensions.loseContext = gl.getExtension('WEBGL_lose_context'); + + extensions.vertexArrayObject = gl.getExtension('OES_vertex_array_object') + || gl.getExtension('MOZ_OES_vertex_array_object') + || gl.getExtension('WEBKIT_OES_vertex_array_object'); + } + + // we don't use any specific WebGL 2 ones yet! + } + + /** + * Handles a lost webgl context + * + * @private + * @param {WebGLContextEvent} event - The context lost event. + */ + handleContextLost(event) + { + event.preventDefault(); + } + + /** + * Handles a restored webgl context + * + * @private + */ + handleContextRestored() + { + this.renderer.runners.contextChange.run(this.gl); + } + + destroy() + { + const view = this.renderer.view; + + // remove listeners + view.removeEventListener('webglcontextlost', this.handleContextLost); + view.removeEventListener('webglcontextrestored', this.handleContextRestored); + + this.gl.useProgram(null); + + if (this.extensions.loseContext) + { + this.extensions.loseContext.loseContext(); + } + } + + postrender() + { + this.gl.flush(); + } + + validateContext(gl) + { + const attributes = gl.getContextAttributes(); + + // this is going to be fairly simple for now.. but at least we have room to grow! + if (!attributes.stencil) + { + /* eslint-disable max-len */ + + /* eslint-disable no-console */ + console.warn('Provided WebGL context does not have a stencil buffer, masks may not render correctly'); + /* eslint-enable no-console */ + + /* eslint-enable max-len */ + } + } +} diff --git a/packages/core/src/filters/Filter.js b/packages/core/src/filters/Filter.js new file mode 100644 index 0000000..9d8cad8 --- /dev/null +++ b/packages/core/src/filters/Filter.js @@ -0,0 +1,140 @@ +import Shader from '../shader/Shader'; +import Program from '../shader/Program'; +import State from '../state/State'; +import { settings } from '@pixi/settings'; +// import extractUniformsFromSrc from './extractUniformsFromSrc'; +import defaultVertex from './defaultFilter.vert'; +import defaultFragment from './defaultFilter.frag'; + +// let math = require('../../math'); +/** + * @class + * @memberof PIXI + * @extends PIXI.Shader + */ +export default class Filter extends Shader +{ + /** + * @param {string} [vertexSrc] - The source of the vertex shader. + * @param {string} [fragmentSrc] - The source of the fragment shader. + * @param {object} [uniforms] - Custom uniforms to use to augment the built-in ones. + */ + constructor(vertexSrc, fragmentSrc, uniforms) + { + const program = Program.from(vertexSrc, fragmentSrc); + + super(program, uniforms); + + /** + * The padding of the filter. Some filters require extra space to breath such as a blur. + * Increasing this will add extra width and height to the bounds of the object that the + * filter is applied to. + * + * @member {number} + */ + this.padding = 0; + + /** + * The resolution of the filter. Setting this to be lower will lower the quality but + * increase the performance of the filter. + * + * @member {number} + */ + this.resolution = settings.RESOLUTION; + + /** + * If enabled is true the filter is applied, if false it will not. + * + * @member {boolean} + */ + this.enabled = true; + + /** + * If enabled, PixiJS will fit the filter area into boundaries for better performance. + * Switch it off if it does not work for specific shader. + * + * @member {boolean} + */ + this.autoFit = true; + + /** + * Legacy filters use position and uvs from attributes + * @member {boolean} + * @readonly + */ + this.legacy = !!this.program.attributeData.aTextureCoord; + + /** + * the webGL state the filter requires to render + * @member {PIXI.State} + */ + this.state = new State(); + } + + /** + * Applies the filter + * + * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + * @param {boolean} clear - Should the output be cleared before rendering to it + * @param {object} [currentState] - It's current state of filter. + * There are some useful properties in the currentState : + * target, filters, sourceFrame, destinationFrame, renderTarget, resolution + */ + apply(filterManager, input, output, clear, currentState, derp) // eslint-disable-line no-unused-vars + { + // do as you please! + + filterManager.applyFilter(this, input, output, clear, currentState, derp); + + // or just do a regular render.. + } + + /** + * Sets the blendmode of the filter + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL + */ + get blendMode() + { + return this.state.blendMode; + } + + set blendMode(value) // eslint-disable-line require-jsdoc + { + this.state.blendMode = value; + } + + /** + * The default vertex shader source + * + * @static + * @constant + */ + static get defaultVertexSrc() + { + return defaultVertex; + } + + /** + * The default fragment shader source + * + * @static + * @constant + */ + static get defaultFragmentSrc() + { + return defaultFragment; + } +} + +/** + * Used for caching shader IDs + * + * @static + * @private + */ +Filter.SOURCE_KEY_MAP = {}; + diff --git a/packages/core/src/filters/FilterSystem.js b/packages/core/src/filters/FilterSystem.js new file mode 100644 index 0000000..3174318 --- /dev/null +++ b/packages/core/src/filters/FilterSystem.js @@ -0,0 +1,431 @@ +import System from '../System'; + +import RenderTexture from '../renderTexture/RenderTexture'; +import Quad from '../utils/Quad'; +import QuadUv from '../utils/QuadUv'; +import { Rectangle } from '@pixi/math'; +import * as filterTransforms from './filterTransforms'; +import bitTwiddle from 'bit-twiddle'; +import UniformGroup from '../shader/UniformGroup'; +import { DRAW_MODES } from '@pixi/constants'; + +// +/** + * @ignore + * @class + */ +class FilterState +{ + /** + * + */ + constructor() + { + this.renderTexture = null; + this.sourceFrame = new Rectangle(); + this.destinationFrame = new Rectangle(); + this.filters = []; + this.target = null; + this.legacy = false; + this.resolution = 1; + } +} + +/** + * @class + * @memberof PIXI.systems + * @extends PIXI.System + */ +export default class FilterSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + /** + * stores a bunch of PO2 textures used for filtering + * @type {Object} + */ + this.texturePool = {}; + + /** + * a pool for storing filter states, save us creating new ones each tick + * @type {Array} + */ + this.statePool = []; + + /** + * A very simple geometry used when drawing a filter effect to the screen + * @type {Quad} + */ + this.quad = new Quad(); + + this.quadUv = new QuadUv(); + + /** + * Temporary rect for maths + * @type {PIXI.Rectangle} + */ + this.tempRect = new Rectangle(); + + this.activeState = {}; + + /** + * this uniform group is attached to filter uniforms when used + * @type {UniformGroup} + */ + this.globalUniforms = new UniformGroup({ + sourceFrame: this.tempRect, + destinationFrame: this.tempRect, + + // legacy variables + filterArea: new Float32Array(4), + filterClamp: new Float32Array(4), + }, true); + } + + /** + * Adds a new filter to the System. + * + * @param {PIXI.DisplayObject} target - The target of the filter to render. + * @param {PIXI.Filter[]} filters - The filters to apply. + */ + push(target, filters) + { + const renderer = this.renderer; + const filterStack = this.renderer.renderTexture.defaultFilterStack; + const state = this.statePool.pop() || new FilterState(); + + let resolution = filters[0].resolution; + let padding = filters[0].padding; + let autoFit = filters[0].autoFit; + let legacy = filters[0].legacy; + + for (let i = 1; i < filters.length; i++) + { + const filter = filters[i]; + + // lets use the lowest resolution.. + resolution = Math.min(resolution, filter.resolution); + // and the largest amount of padding! + padding = Math.max(padding, filter.padding); + // only auto fit if all filters are autofit + autoFit = autoFit || filter.autoFit; + + legacy = legacy || filter.legacy; + } + + filterStack.push(state); + + state.resolution = resolution; + + state.legacy = legacy; + + // round to whole number based on resolution + // TODO move that to the shader too? + state.sourceFrame = target.filterArea || target.getBounds(true); + + state.sourceFrame.pad(padding); + + if (autoFit) + { + state.sourceFrame.fit(this.renderer.renderTexture.destinationFrame); + } + + state.sourceFrame.round(resolution); + + state.renderTexture = this.getPotFilterTexture(state.sourceFrame.width, state.sourceFrame.height, resolution); + state.filters = filters; + + state.destinationFrame.width = state.renderTexture.width; + state.destinationFrame.height = state.renderTexture.height; + + state.renderTexture.filterFrame = state.sourceFrame; + renderer.renderTexture.bind(state.renderTexture, state.sourceFrame);// /, state.destinationFrame); + renderer.renderTexture.clear(); + } + + /** + * Pops off the filter and applies it. + * + */ + pop() + { + const renderer = this.renderer; + const filterStack = renderer.renderTexture.defaultFilterStack; + const state = filterStack.pop(); + const filters = state.filters; + + this.activeState = state; + + const globalUniforms = this.globalUniforms.uniforms; + + globalUniforms.sourceFrame = state.sourceFrame; + globalUniforms.destinationFrame = state.destinationFrame; + globalUniforms.resolution = state.resolution; + + // only update the rect if its legacy.. + if (state.legacy) + { + const filterArea = globalUniforms.filterArea; + const filterClamp = globalUniforms.filterClamp; + + filterArea[0] = state.destinationFrame.width; + filterArea[1] = state.destinationFrame.height; + filterArea[2] = state.sourceFrame.x; + filterArea[3] = state.sourceFrame.y; + + filterClamp[0] = 0.5 / state.resolution / state.destinationFrame.width; + filterClamp[1] = 0.5 / state.resolution / state.destinationFrame.height; + filterClamp[2] = (state.sourceFrame.width - 0.5) / state.resolution / state.destinationFrame.width; + filterClamp[3] = (state.sourceFrame.height - 0.5) / state.resolution / state.destinationFrame.height; + } + + this.globalUniforms.update(); + + const lastState = filterStack[filterStack.length - 1]; + + if (filters.length === 1) + { + filters[0].apply(this, state.renderTexture, lastState.renderTexture, false, state); + + this.returnFilterTexture(state.renderTexture); + } + else + { + let flip = state.renderTexture; + let flop = this.getPotFilterTexture( + flip.width, + flip.height, + state.resolution + ); + + flop.filterFrame = flip.filterFrame; + + let i = 0; + + for (i = 0; i < filters.length - 1; ++i) + { + filters[i].apply(this, flip, flop, true, state); + + const t = flip; + + flip = flop; + flop = t; + } + + filters[i].apply(this, flip, lastState.renderTexture, false, state); + + this.returnFilterTexture(flip); + this.returnFilterTexture(flop); + } + + this.statePool.push(state); + } + + /** + * Draws a filter. + * + * @param {PIXI.Filter} filter - The filter to draw. + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + * @param {boolean} clear - Should the output be cleared before rendering to it + */ + applyFilter(filter, input, output, clear) + { + const renderer = this.renderer; + + renderer.renderTexture.bind(output, output ? output.filterFrame : null); + + if (clear) + { + // gl.disable(gl.SCISSOR_TEST); + renderer.renderTexture.clear(); + // gl.enable(gl.SCISSOR_TEST); + } + + // set the uniforms.. + filter.uniforms.uSampler = input; + filter.uniforms.filterGlobals = this.globalUniforms; + + // TODO make it so that the order of this does not matter.. + // because it does at the moment cos of global uniforms. + // they need to get resynced + + renderer.state.setState(filter.state); + renderer.shader.bind(filter); + + if (filter.legacy) + { + this.quadUv.map(input._frame, input.filterFrame); + + renderer.geometry.bind(this.quadUv); + renderer.geometry.draw(DRAW_MODES.TRIANGLES); + } + else + { + renderer.geometry.bind(this.quad); + renderer.geometry.draw(DRAW_MODES.TRIANGLE_STRIP); + } + } + + /** + * Calculates the mapped matrix. + * + * TODO playing around here.. this is temporary - (will end up in the shader) + * this returns a matrix that will normalise map filter cords in the filter to screen space + * + * @param {PIXI.Matrix} outputMatrix - the matrix to output to. + * @return {PIXI.Matrix} The mapped matrix. + */ + calculateScreenSpaceMatrix(outputMatrix) + { + const currentState = this.activeState; + + return filterTransforms.calculateScreenSpaceMatrix( + outputMatrix, + currentState.sourceFrame, + currentState.destinationFrame + ); + } + + /** + * This will map the filter coord so that a texture can be used based on the transform of a sprite + * + * @param {PIXI.Matrix} outputMatrix - The matrix to output to. + * @param {PIXI.Sprite} sprite - The sprite to map to. + * @return {PIXI.Matrix} The mapped matrix. + */ + calculateSpriteMatrix(outputMatrix, sprite) + { + const currentState = this.activeState; + + return filterTransforms.calculateSpriteMatrix( + outputMatrix, + currentState.sourceFrame, + currentState.destinationFrame, + sprite + ); + } + + /** + * Destroys this Filter System. + * + * @param {boolean} [contextLost=false] context was lost, do not free shaders + * + */ + destroy(contextLost = false) + { + if (!contextLost) + { + this.emptyPool(); + } + else + { + this.texturePool = {}; + } + } + + /** + * Gets a Power-of-Two render texture. + * + * TODO move to a seperate class could be on renderer? + * also - could cause issue with multiple contexts? + * + * @private + * @param {WebGLRenderingContext} gl - The webgl rendering context + * @param {number} minWidth - The minimum width of the render target. + * @param {number} minHeight - The minimum height of the render target. + * @param {number} resolution - The resolution of the render target. + * @return {PIXI.RenderTarget} The new render target. + */ + getPotFilterTexture(minWidth, minHeight, resolution) + { + minWidth = bitTwiddle.nextPow2(minWidth); + minHeight = bitTwiddle.nextPow2(minHeight); + resolution = resolution || 1; + + const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + + if (!this.texturePool[key]) + { + this.texturePool[key] = []; + } + + let renderTexture = this.texturePool[key].pop(); + + if (!renderTexture) + { + // temporary bypass cache.. + // internally - this will cause a texture to be bound.. + renderTexture = RenderTexture.create({ + width: minWidth, + height: minHeight, + resolution, + }); + } + + return renderTexture; + } + + /** + * Gets extra render texture to use inside current filter + * + * @param {number} resolution resolution of the renderTexture + * @returns {PIXI.RenderTexture} + */ + getFilterTexture(resolution) + { + const rt = this.activeState.renderTexture; + + const filterTexture = this.getPotFilterTexture(rt.width, rt.height, resolution || rt.baseTexture.resolution); + + filterTexture.filterFrame = rt.filterFrame; + + return filterTexture; + } + + /** + * Frees a render texture back into the pool. + * + * @param {PIXI.RenderTarget} renderTexture - The renderTarget to free + */ + returnFilterTexture(renderTexture) + { + renderTexture.filterFrame = null; + + const base = renderTexture.baseTexture; + + const minWidth = base.width; + const minHeight = base.height; + + const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + + this.texturePool[key].push(renderTexture); + } + + /** + * Empties the texture pool. + * + */ + emptyPool() + { + for (const i in this.texturePool) + { + const textures = this.texturePool[i]; + + if (textures) + { + for (let j = 0; j < textures.length; j++) + { + textures[j].destroy(true); + } + } + } + + this.texturePool = {}; + } +} diff --git a/packages/core/src/filters/defaultFilter.frag b/packages/core/src/filters/defaultFilter.frag new file mode 100644 index 0000000..a14c1be --- /dev/null +++ b/packages/core/src/filters/defaultFilter.frag @@ -0,0 +1,22 @@ +varying vec2 vTextureCoord; +varying vec2 vFilterCoord; + +uniform sampler2D uSampler; +uniform sampler2D filterSampler; + +void main(void){ + vec4 masky = texture2D(filterSampler, vFilterCoord); + vec4 sample = texture2D(uSampler, vTextureCoord); + vec4 color; + if(mod(vFilterCoord.x, 1.0) > 0.5) + { + color = vec4(1.0, 0.0, 0.0, 1.0); + } + else + { + color = vec4(0.0, 1.0, 0.0, 1.0); + } + // gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0); + gl_FragColor = mix(sample, masky, 0.5); + gl_FragColor *= sample.a; +} diff --git a/packages/core/src/filters/defaultFilter.vert b/packages/core/src/filters/defaultFilter.vert new file mode 100644 index 0000000..e1febca --- /dev/null +++ b/packages/core/src/filters/defaultFilter.vert @@ -0,0 +1,14 @@ +attribute vec2 aVertexPosition; +attribute vec2 aTextureCoord; + +uniform mat3 projectionMatrix; +uniform mat3 filterMatrix; + +varying vec2 vTextureCoord; +varying vec2 vFilterCoord; + +void main(void){ + gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); + vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy; + vTextureCoord = aTextureCoord ; +} diff --git a/packages/core/src/filters/filterTransforms.js b/packages/core/src/filters/filterTransforms.js new file mode 100644 index 0000000..87a3cd6 --- /dev/null +++ b/packages/core/src/filters/filterTransforms.js @@ -0,0 +1,50 @@ +import { Matrix } from '@pixi/math'; + +/** + * Calculates the mapped matrix + * @param filterArea {Rectangle} The filter area + * @param sprite {Sprite} the target sprite + * @param outputMatrix {Matrix} @alvin + * @private + */ +// this returns a matrix that will normalise map filter cords in the filter to screen space +export function calculateScreenSpaceMatrix(outputMatrix, filterArea, textureSize) +{ + // TODO unwrap? + const mappedMatrix = outputMatrix.identity(); + + mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); + + mappedMatrix.scale(textureSize.width, textureSize.height); + + return mappedMatrix; +} + +export function calculateNormalizedScreenSpaceMatrix(outputMatrix, filterArea, textureSize) +{ + const mappedMatrix = outputMatrix.identity(); + + mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); + + const translateScaleX = (textureSize.width / filterArea.width); + const translateScaleY = (textureSize.height / filterArea.height); + + mappedMatrix.scale(translateScaleX, translateScaleY); + + return mappedMatrix; +} + +// this will map the filter coord so that a texture can be used based on the transform of a sprite +export function calculateSpriteMatrix(outputMatrix, filterArea, textureSize, sprite) +{ + const orig = sprite._texture.orig; + const mappedMatrix = outputMatrix.set(textureSize.width, 0, 0, textureSize.height, filterArea.x, filterArea.y); + const worldTransform = sprite.worldTransform.copyTo(Matrix.TEMP_MATRIX); + + worldTransform.invert(); + mappedMatrix.prepend(worldTransform); + mappedMatrix.scale(1.0 / orig.width, 1.0 / orig.height); + mappedMatrix.translate(sprite.anchor.x, sprite.anchor.y); + + return mappedMatrix; +} diff --git a/packages/core/src/filters/spriteMask/SpriteMaskFilter.js b/packages/core/src/filters/spriteMask/SpriteMaskFilter.js new file mode 100644 index 0000000..91697b7 --- /dev/null +++ b/packages/core/src/filters/spriteMask/SpriteMaskFilter.js @@ -0,0 +1,63 @@ +import Filter from '../Filter'; +import { Matrix } from '@pixi/math'; +import vertex from './spriteMaskFilter.vert'; +import fragment from './spriteMaskFilter.frag'; +import { default as TextureMatrix } from '../../textures/TextureMatrix'; + +/** + * The SpriteMaskFilter class + * + * @class + * @extends PIXI.Filter + * @memberof PIXI + */ +export default class SpriteMaskFilter extends Filter +{ + /** + * @param {PIXI.Sprite} sprite - the target sprite + */ + constructor(sprite) + { + const maskMatrix = new Matrix(); + + super(vertex, fragment); + + sprite.renderable = false; + + this.maskSprite = sprite; + this.maskMatrix = maskMatrix; + } + + /** + * Applies the filter + * + * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + */ + apply(filterManager, input, output) + { + const maskSprite = this.maskSprite; + const tex = this.maskSprite.texture; + + if (!tex.valid) + { + return; + } + if (!tex.transform) + { + // margin = 0.0, let it bleed a bit, shader code becomes easier + // assuming that atlas textures were made with 1-pixel padding + tex.transform = new TextureMatrix(tex, 0.0); + } + tex.transform.update(); + + this.uniforms.mask = tex; + this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite) + .prepend(tex.transform.mapCoord); + this.uniforms.alpha = maskSprite.worldAlpha; + this.uniforms.maskClamp = tex.transform.uClampFrame; + + filterManager.applyFilter(this, input, output); + } +} diff --git a/packages/core/src/filters/spriteMask/spriteMaskFilter.frag b/packages/core/src/filters/spriteMask/spriteMaskFilter.frag new file mode 100644 index 0000000..0e4aef8 --- /dev/null +++ b/packages/core/src/filters/spriteMask/spriteMaskFilter.frag @@ -0,0 +1,23 @@ +varying vec2 vMaskCoord; +varying vec2 vTextureCoord; + +uniform sampler2D uSampler; +uniform sampler2D mask; +uniform float alpha; +uniform vec4 maskClamp; + +void main(void) +{ + float clip = step(3.5, + step(maskClamp.x, vMaskCoord.x) + + step(maskClamp.y, vMaskCoord.y) + + step(vMaskCoord.x, maskClamp.z) + + step(vMaskCoord.y, maskClamp.w)); + + vec4 original = texture2D(uSampler, vTextureCoord); + vec4 masky = texture2D(mask, vMaskCoord); + + original *= (masky.r * masky.a * alpha * clip); + + gl_FragColor = original; +} diff --git a/packages/core/src/filters/spriteMask/spriteMaskFilter.vert b/packages/core/src/filters/spriteMask/spriteMaskFilter.vert new file mode 100644 index 0000000..d23a30c --- /dev/null +++ b/packages/core/src/filters/spriteMask/spriteMaskFilter.vert @@ -0,0 +1,16 @@ +attribute vec2 aVertexPosition; +attribute vec2 aTextureCoord; + +uniform mat3 projectionMatrix; +uniform mat3 otherMatrix; + +varying vec2 vMaskCoord; +varying vec2 vTextureCoord; + +void main(void) +{ + gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); + + vTextureCoord = aTextureCoord; + vMaskCoord = ( otherMatrix * vec3( aTextureCoord, 1.0) ).xy; +} diff --git a/packages/core/src/framebuffer/FrameBuffer.js b/packages/core/src/framebuffer/FrameBuffer.js new file mode 100644 index 0000000..84e9fcc --- /dev/null +++ b/packages/core/src/framebuffer/FrameBuffer.js @@ -0,0 +1,106 @@ +import Texture from '../textures/BaseTexture'; +import { FORMATS, TYPES } from '@pixi/constants'; + +/** + * Frame buffer + * @class + * @memberof PIXI + */ +export default class FrameBuffer +{ + constructor(width, height) + { + this.width = width || 100; + this.height = height || 100; + + this.stencil = false; + this.depth = false; + + this.dirtyId = 0; + this.dirtyFormat = 0; + this.dirtySize = 0; + + this.depthTexture = null; + this.colorTextures = []; + + this.glFrameBuffers = {}; + } + + get colorTexture() + { + return this.colorTextures[0]; + } + + addColorTexture(index, texture) + { + // TODO add some validation to the texture - same width / height etc? + this.colorTextures[index || 0] = texture || new Texture(null, { scaleMode: 0, + resolution: 1, + mipmap: false, + width: this.width, + height: this.height });// || new Texture(); + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + addDepthTexture(texture) + { + /* eslint-disable max-len */ + this.depthTexture = texture || new Texture(null, { scaleMode: 0, + resolution: 1, + width: this.width, + height: this.height, + mipmap: false, + format: FORMATS.DEPTH_COMPONENT, + type: TYPES.UNSIGNED_SHORT });// UNSIGNED_SHORT; + /* eslint-disable max-len */ + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + enableDepth() + { + this.depth = true; + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + enableStencil() + { + this.stencil = true; + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + resize(width, height) + { + if (width === this.width && height === this.height) return; + + this.width = width; + this.height = height; + + this.dirtyId++; + this.dirtySize++; + + for (let i = 0; i < this.colorTextures.length; i++) + { + this.colorTextures[i].setSize(width, height); + } + + if (this.depthTexture) + { + this.depthTexture.setSize(width, height); + } + } +} diff --git a/packages/core/src/framebuffer/FramebufferSystem.js b/packages/core/src/framebuffer/FramebufferSystem.js new file mode 100644 index 0000000..2cc2437 --- /dev/null +++ b/packages/core/src/framebuffer/FramebufferSystem.js @@ -0,0 +1,246 @@ +import System from '../System'; +import { Rectangle } from '@pixi/math'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class FramebufferSystem extends System +{ + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + contextChange() + { + this.gl = this.renderer.gl; + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + this.current = null; + this.viewport = new Rectangle(); + + this.drawBufferExtension = this.renderer.context.extensions.drawBuffers; + } + + bind(framebuffer, frame) + { + const gl = this.gl; + + this.current = framebuffer; + + if (framebuffer) + { + // TODO cacheing layer! + + const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID] || this.initFramebuffer(framebuffer); + + gl.bindFramebuffer(gl.FRAMEBUFFER, fbo.framebuffer); + // makesure all textures are unbound.. + + // now check for updates... + if (fbo.dirtyId !== framebuffer.dirtyId) + { + fbo.dirtyId = framebuffer.dirtyId; + + if (fbo.dirtyFormat !== framebuffer.dirtyFormat) + { + fbo.dirtyFormat = framebuffer.dirtyFormat; + this.updateFramebuffer(framebuffer); + } + else if (fbo.dirtySize !== framebuffer.dirtySize) + { + fbo.dirtySize = framebuffer.dirtySize; + this.resizeFramebuffer(framebuffer); + } + } + + for (let i = 0; i < framebuffer.colorTextures.length; i++) + { + if (framebuffer.colorTextures[i].texturePart) + { + this.renderer.texture.unbind(framebuffer.colorTextures[i].texture); + } + else + { + this.renderer.texture.unbind(framebuffer.colorTextures[i]); + } + } + + if (framebuffer.depthTexture) + { + this.renderer.texture.unbind(framebuffer.depthTexture); + } + + if (frame) + { + this.setViewport(frame.x, frame.y, frame.width, frame.height); + } + else + { + this.setViewport(0, 0, framebuffer.width, framebuffer.height); + } + } + else + { + gl.bindFramebuffer(gl.FRAMEBUFFER, null); + + if (frame) + { + this.setViewport(frame.x, frame.y, frame.width, frame.height); + } + else + { + this.setViewport(0, 0, this.renderer.width, this.renderer.height); + } + } + } + + setViewport(x, y, width, height) + { + const v = this.viewport; + + if (v.width !== width || v.height !== height || v.x !== x || v.y !== y) + { + v.x = x; + v.y = y; + v.width = width; + v.height = height; + + this.gl.viewport(x, y, width, height); + } + } + + get size() + { + if (this.current) + { + // TODO store temp + return { x: 0, y: 0, width: this.current.width, height: this.current.height }; + } + + return { x: 0, y: 0, width: this.renderer.width, height: this.renderer.height }; + } + + clear(r, g, b, a) + { + const gl = this.gl; + + // TODO clear color can be set only one right? + gl.clearColor(r, g, b, a); + gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); + } + + // private functions... + + initFramebuffer(framebuffer) + { + const gl = this.gl; + + // TODO - make this a class? + const fbo = { + framebuffer: gl.createFramebuffer(), + stencil: null, + dirtyId: 0, + dirtyFormat: 0, + dirtySize: 0, + }; + + framebuffer.glFrameBuffers[this.CONTEXT_UID] = fbo; + + return fbo; + } + + resizeFramebuffer(framebuffer) + { + const gl = this.gl; + + if (framebuffer.stencil || framebuffer.depth) + { + gl.bindRenderbuffer(gl.RENDERBUFFER, this.stencil); + gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); + } + } + + updateFramebuffer(framebuffer) + { + const gl = this.gl; + + const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID]; + + // bind the color texture + const colorTextures = framebuffer.colorTextures; + + let count = colorTextures.length; + + if (!this.drawBufferExtension) + { + count = Math.min(count, 1); + } + + const activeTextures = []; + + for (let i = 0; i < count; i++) + { + const texture = framebuffer.colorTextures[i]; + + if (texture.texturePart) + { + this.renderer.texture.bind(texture.texture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.COLOR_ATTACHMENT0 + i, + gl.TEXTURE_CUBE_MAP_NEGATIVE_X + texture.side, + texture.texture._glTextures[this.CONTEXT_UID].texture, + 0); + } + else + { + this.renderer.texture.bind(texture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.COLOR_ATTACHMENT0 + i, + gl.TEXTURE_2D, + texture._glTextures[this.CONTEXT_UID].texture, + 0); + } + + activeTextures.push(gl.COLOR_ATTACHMENT0 + i); + } + + if (this.drawBufferExtension && activeTextures.length > 1) + { + this.drawBufferExtension.drawBuffersWEBGL(activeTextures); + } + + if (framebuffer.depthTexture) + { + const depthTextureExt = this.renderer.context.extensions.depthTexture; + + if (depthTextureExt) + { + const depthTexture = framebuffer.depthTexture; + + this.renderer.texture.bind(depthTexture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.DEPTH_ATTACHMENT, + gl.TEXTURE_2D, + depthTexture._glTextures[this.CONTEXT_UID].texture, + 0); + } + } + + if (framebuffer.stencil || framebuffer.depth) + { + fbo.stencil = gl.createRenderbuffer(); + + gl.bindRenderbuffer(gl.RENDERBUFFER, fbo.stencil); + + // TODO.. this is depth AND stencil? + gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, fbo.stencil); + gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); + // fbo.enableStencil(); + } + } +} diff --git a/packages/core/src/geometry/GLBuffer.js b/packages/core/src/geometry/GLBuffer.js new file mode 100644 index 0000000..0477e8a --- /dev/null +++ b/packages/core/src/geometry/GLBuffer.js @@ -0,0 +1,9 @@ +export default class GLBuffer +{ + constructor(buffer) + { + this.buffer = buffer; + this.updateID = -1; + this.byteLength = -1; + } +} diff --git a/packages/core/src/geometry/GeometrySystem.js b/packages/core/src/geometry/GeometrySystem.js new file mode 100644 index 0000000..959f827 --- /dev/null +++ b/packages/core/src/geometry/GeometrySystem.js @@ -0,0 +1,388 @@ +import System from '../System'; +import GLBuffer from './GLBuffer'; + +const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class GeometrySystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this._activeGeometry = null; + this._activeVao = null; + + this.hasVao = true; + this.hasInstance = true; + } + + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + contextChange() + { + const gl = this.gl = this.renderer.gl; + + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + + // webgl2 + if (!gl.createVertexArray) + { + // webgl 1! + let nativeVaoExtension = this.renderer.context.extensions.vertexArrayObject; + + if (this.renderer.legacy) + { + nativeVaoExtension = null; + } + + if (nativeVaoExtension) + { + gl.createVertexArray = () => + nativeVaoExtension.createVertexArrayOES(); + + gl.bindVertexArray = (vao) => + nativeVaoExtension.bindVertexArrayOES(vao); + + gl.deleteVertexArray = (vao) => + nativeVaoExtension.deleteVertexArrayOES(vao); + } + else + { + this.hasVao = false; + gl.createVertexArray = () => + { + // empty + }; + + gl.bindVertexArray = () => + { + // empty + }; + + gl.deleteVertexArray = () => + { + // empty + }; + } + } + + if (!gl.vertexAttribDivisor) + { + const instanceExt = gl.getExtension('ANGLE_instanced_arrays'); + + if (instanceExt) + { + gl.vertexAttribDivisor = (a, b) => + instanceExt.vertexAttribDivisorANGLE(a, b); + + gl.drawElementsInstanced = (a, b, c, d, e) => + instanceExt.drawElementsInstancedANGLE(a, b, c, d, e); + + gl.drawArraysInstanced = (a, b, c, d) => + instanceExt.drawArraysInstancedANGLE(a, b, c, d); + } + else + { + this.hasInstance = false; + } + } + } + + /** + * Binds geometry so that is can be drawn. Creating a Vao if required + * @private + * @param {PIXI.Geometry} geometry instance of geometry to bind + */ + bind(geometry, shader) + { + shader = shader || this.renderer.shader.shader; + + const gl = this.gl; + + // not sure the best way to address this.. + // currently different shaders require different VAOs for the same geometry + // Still mulling over the best way to solve this one.. + // will likely need to modify the shader attribute locations at run time! + let vaos = geometry.glVertexArrayObjects[this.CONTEXT_UID]; + + if (!vaos) + { + geometry.glVertexArrayObjects[this.CONTEXT_UID] = vaos = {}; + } + + const vao = vaos[shader.program.id] || this.initGeometryVao(geometry, shader.program); + + this._activeGeometry = geometry; + + if (this._activeVao !== vao) + { + this._activeVao = vao; + + if (this.hasVao) + { + gl.bindVertexArray(vao); + } + else + { + this.activateVao(geometry, shader.program); + } + } + + // TODO - optimise later! + // don't need to loop through if nothing changed! + // maybe look to add an 'autoupdate' to geometry? + this.updateBuffers(); + } + + reset() + { + this.unbind(); + } + + updateBuffers() + { + const geometry = this._activeGeometry; + const gl = this.gl; + + for (let i = 0; i < geometry.buffers.length; i++) + { + const buffer = geometry.buffers[i]; + + const glBuffer = buffer._glBuffers[this.CONTEXT_UID]; + + if (buffer._updateID !== glBuffer.updateID) + { + glBuffer.updateID = buffer._updateID; + + // TODO can cache this on buffer! maybe added a getter / setter? + const type = buffer.index ? gl.ELEMENT_ARRAY_BUFFER : gl.ARRAY_BUFFER; + const drawType = buffer.static ? gl.STATIC_DRAW : gl.DYNAMIC_DRAW; + + gl.bindBuffer(type, glBuffer.buffer); + + if (glBuffer.byteLength >= buffer.data.byteLength) + { + // offset is always zero for now! + gl.bufferSubData(type, 0, buffer.data); + } + else + { + gl.bufferData(type, buffer.data, drawType); + } + } + } + } + + checkCompatability(geometry, program) + { + // geometry must have at least all the attributes that the shader requires. + const geometryAttributes = geometry.attributes; + const shaderAttributes = program.attributeData; + + for (const j in shaderAttributes) + { + if (!geometryAttributes[j]) + { + throw new Error(`shader and geometry incompatible, geometry missing the "${j}" attribute`); + } + } + } + + /** + * Creates a Vao with the same structure as the geometry and stores it on the geometry. + * @private + * @param {PIXI.Geometry} geometry instance of geometry to to generate Vao for + */ + initGeometryVao(geometry, program) + { + this.checkCompatability(geometry, program); + + const gl = this.gl; + const CONTEXT_UID = this.CONTEXT_UID; + const buffers = geometry.buffers; + const attributes = geometry.attributes; + + const tempStride = {}; + const tempStart = {}; + + for (const j in buffers) + { + tempStride[j] = 0; + tempStart[j] = 0; + } + + for (const j in attributes) + { + if (!attributes[j].size && program.attributeData[j]) + { + attributes[j].size = program.attributeData[j].size; + } + + tempStride[attributes[j].buffer] += attributes[j].size * byteSizeMap[attributes[j].type]; + } + + for (const j in attributes) + { + const attribute = attributes[j]; + const attribSize = attribute.size; + + if (attribute.stride === undefined) + { + if (tempStride[attribute.buffer] === attribSize * byteSizeMap[attribute.type]) + { + attribute.stride = 0; + } + else + { + attribute.stride = tempStride[attribute.buffer]; + } + } + + if (attribute.start === undefined) + { + attribute.start = tempStart[attribute.buffer]; + + tempStart[attribute.buffer] += attribSize * byteSizeMap[attribute.type]; + } + } + + // first update - and create the buffers! + // only create a gl buffer if it actually gets + for (let i = 0; i < buffers.length; i++) + { + const buffer = buffers[i]; + + if (!buffer._glBuffers[CONTEXT_UID]) + { + buffer._glBuffers[CONTEXT_UID] = new GLBuffer(gl.createBuffer()); + } + } + + // TODO - maybe make this a data object? + // lets wait to see if we need to first! + const vao = gl.createVertexArray(); + + gl.bindVertexArray(vao); + + this.activateVao(geometry, program); + + gl.bindVertexArray(null); + + // add it to the cache! + geometry.glVertexArrayObjects[this.CONTEXT_UID][program.id] = vao; + + return vao; + } + + activateVao(geometry, program) + { + const gl = this.gl; + const CONTEXT_UID = this.CONTEXT_UID; + const buffers = geometry.buffers; + const attributes = geometry.attributes; + + if (geometry.indexBuffer) + { + // first update the index buffer if we have one.. + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, geometry.indexBuffer._glBuffers[CONTEXT_UID].buffer); + } + + let lastBuffer = null; + + // add a new one! + for (const j in attributes) + { + const attribute = attributes[j]; + const buffer = buffers[attribute.buffer]; + const glBuffer = buffer._glBuffers[CONTEXT_UID]; + + if (program.attributeData[j]) + { + if (lastBuffer !== glBuffer) + { + gl.bindBuffer(gl.ARRAY_BUFFER, glBuffer.buffer); + + lastBuffer = glBuffer; + } + + const location = program.attributeData[j].location; + + // TODO introduce state again + // we can optimise this for older devices that have no VAOs + gl.enableVertexAttribArray(location); + + gl.vertexAttribPointer(location, + attribute.size, + attribute.type || gl.FLOAT, + attribute.normalized, + attribute.stride, + attribute.start); + + if (attribute.instance) + { + // TODO calculate instance count based of this... + if (this.hasInstance) + { + gl.vertexAttribDivisor(location, 1); + } + else + { + throw new Error('geometry error, GPU Instancing is not supported on this device'); + } + } + } + } + } + + draw(type, size, start, instanceCount) + { + const gl = this.gl; + const geometry = this._activeGeometry; + + // TODO.. this should not change so maybe cache the function? + + if (geometry.indexBuffer) + { + if (geometry.instanced) + { + /* eslint-disable max-len */ + gl.drawElementsInstanced(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2, instanceCount || 1); + /* eslint-enable max-len */ + } + else + { + gl.drawElements(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2); + } + } + else + if (geometry.instanced) + { + // TODO need a better way to calculate size.. + gl.drawArraysInstanced(type, start, size || geometry.getSize(), instanceCount || 1); + } + else + { + gl.drawArrays(type, start, size || geometry.getSize()); + } + + return this; + } + + unbind() + { + this.gl.bindVertexArray(null); + this._activeVao = null; + this._activeGeometry = null; + } +} diff --git a/packages/core/src/geometry/utils/setVertexAttribArrays.js b/packages/core/src/geometry/utils/setVertexAttribArrays.js new file mode 100644 index 0000000..23b6959 --- /dev/null +++ b/packages/core/src/geometry/utils/setVertexAttribArrays.js @@ -0,0 +1,54 @@ +// var GL_MAP = {}; + +/** + * @param gl {WebGLRenderingContext} The current WebGL context + * @param attribs {*} + * @param state {*} + */ +export default function setVertexAttribArrays(gl, attribs, state) +{ + let i; + + if (state) + { + const tempAttribState = state.tempAttribState; + const attribState = state.attribState; + + for (i = 0; i < tempAttribState.length; i++) + { + tempAttribState[i] = false; + } + + // set the new attribs + for (i = 0; i < attribs.length; i++) + { + tempAttribState[attribs[i].attribute.location] = true; + } + + for (i = 0; i < attribState.length; i++) + { + if (attribState[i] !== tempAttribState[i]) + { + attribState[i] = tempAttribState[i]; + + if (state.attribState[i]) + { + gl.enableVertexAttribArray(i); + } + else + { + gl.disableVertexAttribArray(i); + } + } + } + } + else + { + for (i = 0; i < attribs.length; i++) + { + const attrib = attribs[i]; + + gl.enableVertexAttribArray(attrib.attribute.location); + } + } +} diff --git a/packages/core/src/index.js b/packages/core/src/index.js index 58c456a..fd1f547 100644 --- a/packages/core/src/index.js +++ b/packages/core/src/index.js @@ -1,30 +1,30 @@ import * as resources from './textures/resources'; -import * as systems from './renderers/systems'; +import * as systems from './systems'; -export { resources }; export { systems }; +export { resources }; -export { default as Renderer } from './renderers/Renderer'; -export { default as AbstractRenderer } from './renderers/AbstractRenderer'; -export { default as FrameBuffer } from './textures/FrameBuffer'; +export { default as System } from './System'; +export { default as Renderer } from './Renderer'; +export { default as AbstractRenderer } from './AbstractRenderer'; +export { default as FrameBuffer } from './framebuffer/FrameBuffer'; export { default as CubeTexture } from './textures/CubeTexture'; export { default as BaseTexture } from './textures/BaseTexture'; export { default as Texture } from './textures/Texture'; export { default as TextureMatrix } from './textures/TextureMatrix'; -export { default as RenderTexture } from './textures/RenderTexture'; -export { default as BaseRenderTexture } from './textures/BaseRenderTexture'; +export { default as RenderTexture } from './renderTexture/RenderTexture'; +export { default as BaseRenderTexture } from './renderTexture/BaseRenderTexture'; export { default as TextureUvs } from './textures/TextureUvs'; -export { default as State } from './renderers/State'; -export { default as ObjectRenderer } from './renderers/utils/ObjectRenderer'; -export { default as Quad } from './renderers/utils/Quad'; -export { default as QuadUv } from './renderers/utils/QuadUv'; -export { default as checkMaxIfStatmentsInShader } from './renderers/utils/checkMaxIfStatmentsInShader'; +export { default as State } from './state/State'; +export { default as ObjectRenderer } from './batch/ObjectRenderer'; +export { default as Quad } from './utils/Quad'; +export { default as QuadUv } from './utils/QuadUv'; +export { default as checkMaxIfStatmentsInShader } from './shader/utils/checkMaxIfStatmentsInShader'; export { default as Shader } from './shader/Shader'; export { default as Program } from './shader/Program'; export { default as UniformGroup } from './shader/UniformGroup'; -export { default as SpriteMaskFilter } from './renderers/filters/spriteMask/SpriteMaskFilter'; -export { default as Filter } from './renderers/filters/Filter'; +export { default as SpriteMaskFilter } from './filters/spriteMask/SpriteMaskFilter'; +export { default as Filter } from './filters/Filter'; export { default as Attribute } from './geometry/Attribute'; export { default as Buffer } from './geometry/Buffer'; export { default as Geometry } from './geometry/Geometry'; - diff --git a/packages/core/src/mask/MaskSystem.js b/packages/core/src/mask/MaskSystem.js new file mode 100644 index 0000000..99174c2 --- /dev/null +++ b/packages/core/src/mask/MaskSystem.js @@ -0,0 +1,199 @@ +import System from '../System'; +import AlphaMaskFilter from '../filters/spriteMask/SpriteMaskFilter'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class MaskSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + // TODO - we don't need both! + this.scissor = false; + this.scissorData = null; + this.scissorRenderTarget = null; + + this.enableScissor = false; + + this.alphaMaskPool = []; + this.alphaMaskIndex = 0; + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.DisplayObject} target - Display Object to push the mask to + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + push(target, maskData) + { + // TODO the root check means scissor rect will not + // be used on render textures more info here: + // https://github.com/pixijs/pixi.js/pull/3545 + + if (maskData.texture) + { + this.pushSpriteMask(target, maskData); + } + else if (this.enableScissor + && !this.scissor + && this.renderer._activeRenderTarget.root + && !this.renderer.stencil.stencilMaskStack.length + && maskData.isFastRect()) + { + const matrix = maskData.worldTransform; + + let rot = Math.atan2(matrix.b, matrix.a); + + // use the nearest degree! + rot = Math.round(rot * (180 / Math.PI)); + + if (rot % 90) + { + this.pushStencilMask(maskData); + } + else + { + this.pushScissorMask(target, maskData); + } + } + else + { + this.pushStencilMask(maskData); + } + } + + /** + * Removes the last mask from the mask stack and doesn't return it. + * + * @param {PIXI.DisplayObject} target - Display Object to pop the mask from + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + pop(target, maskData) + { + if (maskData.texture) + { + this.popSpriteMask(target, maskData); + } + else if (this.enableScissor && !this.renderer.stencil.stencilMaskStack.length) + { + this.popScissorMask(target, maskData); + } + else + { + this.popStencilMask(target, maskData); + } + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.RenderTarget} target - Display Object to push the sprite mask to + * @param {PIXI.Sprite} maskData - Sprite to be used as the mask + */ + pushSpriteMask(target, maskData) + { + let alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; + + if (!alphaMaskFilter) + { + alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; + } + + alphaMaskFilter[0].resolution = this.renderer.resolution; + alphaMaskFilter[0].maskSprite = maskData; + + // TODO - may cause issues! + target.filterArea = maskData.getBounds(true); + + this.renderer.filterSystem.pushFilter(target, alphaMaskFilter); + + this.alphaMaskIndex++; + } + + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popSpriteMask() + { + this.renderer.filterSystem.popFilter(); + this.alphaMaskIndex--; + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + pushStencilMask(maskData) + { + this.renderer.batch.flush(); + this.renderer.stencil.pushStencil(maskData); + } + + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popStencilMask() + { + // this.renderer.currentRenderer.stop(); + this.renderer.stencil.popStencil(); + } + + /** + * + * @param {PIXI.DisplayObject} target - Display Object to push the mask to + * @param {PIXI.Graphics} maskData - The masking data. + */ + pushScissorMask(target, maskData) + { + maskData.renderable = true; + + const renderTarget = this.renderer._activeRenderTarget; + + const bounds = maskData.getBounds(); + + bounds.fit(renderTarget.size); + maskData.renderable = false; + + this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); + + const resolution = this.renderer.resolution; + + this.renderer.gl.scissor( + bounds.x * resolution, + (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, + bounds.width * resolution, + bounds.height * resolution + ); + + this.scissorRenderTarget = renderTarget; + this.scissorData = maskData; + this.scissor = true; + } + + /** + * + * + */ + popScissorMask() + { + this.scissorRenderTarget = null; + this.scissorData = null; + this.scissor = false; + + // must be scissor! + const gl = this.renderer.gl; + + gl.disable(gl.SCISSOR_TEST); + } +} diff --git a/packages/core/src/mask/StencilSystem.js b/packages/core/src/mask/StencilSystem.js new file mode 100644 index 0000000..62796a5 --- /dev/null +++ b/packages/core/src/mask/StencilSystem.js @@ -0,0 +1,133 @@ +import System from '../System'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class StencilSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + this.stencilMaskStack = []; + } + + /** + * Changes the mask stack that is used by this System. + * + * @param {PIXI.Graphics[]} stencilMaskStack - The mask stack + */ + setMaskStack(stencilMaskStack) + { + const gl = this.renderer.gl; + + if (stencilMaskStack.length !== this.stencilMaskStack.length) + { + if (stencilMaskStack.length === 0) + { + gl.disable(gl.STENCIL_TEST); + } + else + { + gl.enable(gl.STENCIL_TEST); + } + } + + this.stencilMaskStack = stencilMaskStack; + } + + /** + * Applies the Mask and adds it to the current stencil stack. @alvin + * + * @param {PIXI.Graphics} graphics - The mask + */ + pushStencil(graphics) + { + this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); + + // this.renderer._activeRenderTarget.attachStencilBuffer(); + + const gl = this.renderer.gl; + const prevMaskCount = this.stencilMaskStack.length; + + if (prevMaskCount === 0) + { + gl.enable(gl.STENCIL_TEST); + } + + this.stencilMaskStack.push(graphics); + + // Increment the refference stencil value where the new mask overlaps with the old ones. + gl.colorMask(false, false, false, false); + gl.stencilFunc(gl.EQUAL, prevMaskCount, this._getBitwiseMask()); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.INCR); + this.renderer.plugins.graphics.render(graphics); + + this._useCurrent(); + } + + /** + * Removes the last mask from the stencil stack. @alvin + */ + popStencil() + { + this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); + + const gl = this.renderer.gl; + const graphics = this.stencilMaskStack.pop(); + + if (this.stencilMaskStack.length === 0) + { + // the stack is empty! + gl.disable(gl.STENCIL_TEST); + gl.clear(gl.STENCIL_BUFFER_BIT); + gl.clearStencil(0); + } + else + { + // Decrement the refference stencil value where the popped mask overlaps with the other ones + gl.colorMask(false, false, false, false); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.DECR); + this.renderer.plugins.graphics.render(graphics); + + this._useCurrent(); + } + } + + /** + * Setup renderer to use the current stencil data. + */ + _useCurrent() + { + const gl = this.renderer.gl; + + gl.colorMask(true, true, true, true); + gl.stencilFunc(gl.EQUAL, this.stencilMaskStack.length, this._getBitwiseMask()); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP); + } + + /** + * Fill 1s equal to the number of acitve stencil masks. + * + * @return {number} The bitwise mask. + */ + _getBitwiseMask() + { + return (1 << this.stencilMaskStack.length) - 1; + } + + /** + * Destroys the mask stack. + * + */ + destroy() + { + super.destroy(this); + + this.stencilMaskStack = null; + } +} diff --git a/packages/core/src/projection/ProjectionSystem.js b/packages/core/src/projection/ProjectionSystem.js new file mode 100644 index 0000000..6e63b73 --- /dev/null +++ b/packages/core/src/projection/ProjectionSystem.js @@ -0,0 +1,73 @@ +import System from '../System'; +import { Matrix } from '@pixi/math'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class ProjectionSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.projectionMatrix = new Matrix(); + } + + update(destinationFrame, sourceFrame, resolution, root) + { + this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; + this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; + + this.calculateProjection(this.destinationFrame, this.sourceFrame, resolution, root); + + this.renderer.globalUniforms.uniforms.projectionMatrix = this.projectionMatrix; + this.renderer.globalUniforms.update(); + } + + /** + * Updates the projection matrix based on a projection frame (which is a rectangle) + * + * @param {Rectangle} destinationFrame - The destination frame. + * @param {Rectangle} sourceFrame - The source frame. + */ + calculateProjection(destinationFrame, sourceFrame, resolution, root) + { + const pm = this.projectionMatrix; + + // I don't think we will need this line.. + // pm.identity(); + + if (!root) + { + pm.a = (1 / destinationFrame.width * 2) * resolution; + pm.d = (1 / destinationFrame.height * 2) * resolution; + + pm.tx = -1 - (sourceFrame.x * pm.a); + pm.ty = -1 - (sourceFrame.y * pm.d); + } + else + { + pm.a = (1 / destinationFrame.width * 2) * resolution; + pm.d = (-1 / destinationFrame.height * 2) * resolution; + + pm.tx = -1 - (sourceFrame.x * pm.a); + pm.ty = 1 - (sourceFrame.y * pm.d); + } + } + + /** + * Sets the transform of the active render target to the given matrix + * + * @param {PIXI.Matrix} matrix - The transformation matrix + */ + setTransform()// matrix) + { + // this._activeRenderTarget.transform = matrix; + } +} diff --git a/packages/core/src/renderTexture/BaseRenderTexture.js b/packages/core/src/renderTexture/BaseRenderTexture.js new file mode 100644 index 0000000..88eb83b --- /dev/null +++ b/packages/core/src/renderTexture/BaseRenderTexture.js @@ -0,0 +1,137 @@ +import BaseTexture from '../textures/BaseTexture'; +import FrameBuffer from '../framebuffer/FrameBuffer'; + +/** + * A BaseRenderTexture is a special texture that allows any PixiJS display object to be rendered to it. + * + * __Hint__: All DisplayObjects (i.e. Sprites) that render to a BaseRenderTexture should be preloaded + * otherwise black rectangles will be drawn instead. + * + * A BaseRenderTexture takes a snapshot of any Display Object given to its render method. The position + * and rotation of the given Display Objects is ignored. For example: + * + * ```js + * let renderer = PIXI.autoDetectRenderer(1024, 1024, { view: canvas, ratio: 1 }); + * let baseRenderTexture = new PIXI.BaseRenderTexture(renderer, 800, 600); + * let sprite = PIXI.Sprite.fromImage("spinObj_01.png"); + * + * sprite.position.x = 800/2; + * sprite.position.y = 600/2; + * sprite.anchor.x = 0.5; + * sprite.anchor.y = 0.5; + * + * baseRenderTexture.render(sprite); + * ``` + * + * The Sprite in this case will be rendered using its local transform. To render this sprite at 0,0 + * you can clear the transform + * + * ```js + * + * sprite.setTransform() + * + * let baseRenderTexture = new PIXI.BaseRenderTexture(100, 100); + * let renderTexture = new PIXI.RenderTexture(baseRenderTexture); + * + * renderer.render(sprite, renderTexture); // Renders to center of RenderTexture + * ``` + * + * @class + * @extends PIXI.BaseTexture + * @memberof PIXI + */ +export default class BaseRenderTexture extends BaseTexture +{ + /** + * @param {object} [options] + * @param {number} [options.width=100] - The width of the base render texture + * @param {number} [options.height=100] - The height of the base render texture + * @param {PIXI.SCALE_MODES} [options.scaleMode] - See {@link PIXI.SCALE_MODES} for possible values + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the texture being generated + */ + constructor(options) + { + if (typeof options === 'number') + { + /* eslint-disable prefer-rest-params */ + // Backward compatibility of signature + const width = arguments[0]; + const height = arguments[1]; + const scaleMode = arguments[2]; + const resolution = arguments[3]; + + options = { width, height, scaleMode, resolution }; + /* eslint-enable prefer-rest-params */ + } + + super(null, options); + + const { width, height } = options || {}; + + // Set defaults + this.mipmap = false; + this.width = Math.ceil(width) || 100; + this.height = Math.ceil(height) || 100; + this.valid = true; + + /** + * A map of renderer IDs to webgl renderTargets + * + * @private + * @member {object} + */ + // this._glRenderTargets = {}; + + /** + * A reference to the canvas render target (we only need one as this can be shared across renderers) + * + * @private + * @member {object} + */ + this._canvasRenderTarget = null; + + this.clearColor = [0, 0, 0, 0]; + + this.frameBuffer = new FrameBuffer(width * this.resolution, height * this.resolution) + .addColorTexture(0, this); + + // TODO - could this be added the systems? + + /** + * The data structure for the stencil masks + * + * @member {PIXI.Graphics[]} + */ + this.stencilMaskStack = []; + + /** + * The data structure for the filters + * + * @member {PIXI.Graphics[]} + */ + this.filterStack = [{}]; + } + + /** + * Resizes the BaseRenderTexture. + * + * @param {number} width - The width to resize to. + * @param {number} height - The height to resize to. + */ + resize(width, height) + { + width = Math.ceil(width); + height = Math.ceil(height); + this.frameBuffer.resize(width * this.resolution, height * this.resolution); + } + + /** + * Destroys this texture + * + */ + destroy() + { + super.destroy(true); + this.renderer = null; + } +} diff --git a/packages/core/src/renderTexture/RenderTexture.js b/packages/core/src/renderTexture/RenderTexture.js new file mode 100644 index 0000000..f1ecf1f --- /dev/null +++ b/packages/core/src/renderTexture/RenderTexture.js @@ -0,0 +1,153 @@ +import BaseRenderTexture from './BaseRenderTexture'; +import Texture from '../textures/Texture'; + +/** + * A RenderTexture is a special texture that allows any PixiJS display object to be rendered to it. + * + * __Hint__: All DisplayObjects (i.e. Sprites) that render to a RenderTexture should be preloaded + * otherwise black rectangles will be drawn instead. + * + * A RenderTexture takes a snapshot of any Display Object given to its render method. For example: + * + * ```js + * let renderer = PIXI.autoDetectRenderer(1024, 1024, { view: canvas, ratio: 1 }); + * let renderTexture = PIXI.RenderTexture.create(800, 600); + * let sprite = PIXI.Sprite.fromImage("spinObj_01.png"); + * + * sprite.position.x = 800/2; + * sprite.position.y = 600/2; + * sprite.anchor.x = 0.5; + * sprite.anchor.y = 0.5; + * + * renderer.render(sprite, renderTexture); + * ``` + * + * The Sprite in this case will be rendered using its local transform. To render this sprite at 0,0 + * you can clear the transform + * + * ```js + * + * sprite.setTransform() + * + * let renderTexture = new PIXI.RenderTexture.create(100, 100); + * + * renderer.render(sprite, renderTexture); // Renders to center of RenderTexture + * ``` + * + * @class + * @extends PIXI.Texture + * @memberof PIXI + */ +export default class RenderTexture extends Texture +{ + /** + * @param {PIXI.BaseRenderTexture} baseRenderTexture - The renderer used for this RenderTexture + * @param {PIXI.Rectangle} [frame] - The rectangle frame of the texture to show + */ + constructor(baseRenderTexture, frame) + { + // support for legacy.. + let _legacyRenderer = null; + + if (!(baseRenderTexture instanceof BaseRenderTexture)) + { + /* eslint-disable prefer-rest-params, no-console */ + const width = arguments[1]; + const height = arguments[2]; + const scaleMode = arguments[3]; + const resolution = arguments[4]; + + // we have an old render texture.. + console.warn(`Please use RenderTexture.create(${width}, ${height}) instead of the ctor directly.`); + _legacyRenderer = arguments[0]; + /* eslint-enable prefer-rest-params, no-console */ + + frame = null; + baseRenderTexture = new BaseRenderTexture({ + width, + height, + scaleMode, + resolution, + }); + } + + /** + * The base texture object that this texture uses + * + * @member {BaseTexture} + */ + super(baseRenderTexture, frame); + + this.legacyRenderer = _legacyRenderer; + + /** + * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. + * + * @member {boolean} + */ + this.valid = true; + + /** + * FilterSystem temporary storage + * @private + * @member {PIXI.Rectangle} + */ + this.filterFrame = null; + + this._updateUvs(); + } + + /** + * Resizes the RenderTexture. + * + * @param {number} width - The width to resize to. + * @param {number} height - The height to resize to. + * @param {boolean} [resizeBaseTexture=true] - Should the baseTexture.width and height values be resized as well? + */ + resize(width, height, resizeBaseTexture = true) + { + width = Math.ceil(width); + height = Math.ceil(height); + + // TODO - could be not required.. + this.valid = (width > 0 && height > 0); + + this._frame.width = this.orig.width = width; + this._frame.height = this.orig.height = height; + + if (resizeBaseTexture) + { + this.baseTexture.resize(width, height); + } + + this._updateUvs(); + } + + /** + * A short hand way of creating a render texture. + * + * @param {object} [options] - Options + * @param {number} [options.width=100] - The width of the render texture + * @param {number} [options.height=100] - The height of the render texture + * @param {number} [options.scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the texture being generated + * @return {PIXI.RenderTexture} The new render texture + */ + static create(options) + { + // fallback, old-style: create(width, height, scaleMode, resolution) + if (typeof options === 'number') + { + /* eslint-disable prefer-rest-params */ + options = { + width: options, + height: arguments[1], + scaleMode: arguments[2], + resolution: arguments[3], + }; + /* eslint-enable prefer-rest-params */ + } + + return new RenderTexture(new BaseRenderTexture(options)); + } +} diff --git a/packages/core/src/renderTexture/RenderTextureSystem.js b/packages/core/src/renderTexture/RenderTextureSystem.js new file mode 100644 index 0000000..8d0ca61 --- /dev/null +++ b/packages/core/src/renderTexture/RenderTextureSystem.js @@ -0,0 +1,115 @@ +import System from '../System'; +import { Rectangle } from '@pixi/math'; + +const tempRect = new Rectangle(); + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class RenderTextureSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.clearColor = renderer._backgroundColorRgba; + + // TODO moe this property somewhere else! + this.defaultMaskStack = []; + this.defaultFilterStack = [{}]; + + // empty render texture? + this.renderTexture = null; + + this.destinationFrame = new Rectangle(); + } + + bind(renderTexture, sourceFrame, destinationFrame) + { + // TODO - do we want this?? + if (this.renderTexture === renderTexture) return; + this.renderTexture = renderTexture; + + const renderer = this.renderer; + + if (renderTexture) + { + const baseTexture = renderTexture.baseTexture; + + if (!destinationFrame) + { + tempRect.width = baseTexture.realWidth; + tempRect.height = baseTexture.realHeight; + + destinationFrame = tempRect; + } + + if (!sourceFrame) + { + sourceFrame = destinationFrame; + } + + this.renderer.framebuffer.bind(baseTexture.frameBuffer, destinationFrame); + + this.renderer.projection.update(destinationFrame, sourceFrame, baseTexture.resolution, false); + this.renderer.stencil.setMaskStack(baseTexture.stencilMaskStack); + } + else + { + // TODO these validation checks happen deeper down.. + // thing they can be avoided.. + if (!destinationFrame) + { + tempRect.width = renderer.width; + tempRect.height = renderer.height; + + destinationFrame = tempRect; + } + + if (!sourceFrame) + { + sourceFrame = destinationFrame; + } + + renderer.framebuffer.bind(null, destinationFrame); + + // TODO store this.. + this.renderer.projection.update(destinationFrame, sourceFrame, this.renderer.resolution, true); + this.renderer.stencil.setMaskStack(this.defaultMaskStack); + } + + this.destinationFrame.copyFrom(destinationFrame); + } + + /** + * Erases the render texture and fills the drawing area with a colour + * + * @param {number} [clearColor] - The colour + * @return {PIXI.Renderer} Returns itself. + */ + clear(clearColor) + { + if (this.renderTexture) + { + clearColor = clearColor || this.renderTexture.baseTexture.clearColor; + } + else + { + clearColor = clearColor || this.clearColor; + } + + this.renderer.framebuffer.clear(clearColor[0], clearColor[1], clearColor[2], clearColor[3]); + } + + resize()// screenWidth, screenHeight) + { + // resize the root only! + this.bind(null); + } +} diff --git a/packages/core/src/renderers/AbstractRenderer.js b/packages/core/src/renderers/AbstractRenderer.js deleted file mode 100644 index 928670f..0000000 --- a/packages/core/src/renderers/AbstractRenderer.js +++ /dev/null @@ -1,353 +0,0 @@ -import { hex2string, hex2rgb } from '@pixi/utils'; -import { Matrix, Rectangle } from '@pixi/math'; -import { RENDERER_TYPE } from '@pixi/constants'; -import { settings } from '@pixi/settings'; -import { Container } from '@pixi/display'; -import RenderTexture from '../textures/RenderTexture'; -import EventEmitter from 'eventemitter3'; - -const tempMatrix = new Matrix(); - -/** - * 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 EventEmitter - * @memberof PIXI - */ -export default class AbstractRenderer extends EventEmitter -{ - // eslint-disable-next-line valid-jsdoc - /** - * @param {string} system - The name of the system this renderer is for. - * @param {object} [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.transparent=false] - If the render view is transparent, default false - * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false - * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) - * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. The - * resolution of the renderer retina would be 2. - * @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 {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when rendering, - * stopping pixel interpolation. - */ - constructor(system, options, arg2, arg3) - { - super(); - - // Support for constructor(system, screenWidth, screenHeight, options) - if (typeof options === 'number') - { - options = Object.assign({ - width: options, - height: arg2 || settings.RENDER_OPTIONS.height, - }, arg3); - } - - // 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 = RENDERER_TYPE.UNKNOWN; - - /** - * Measurements of the screen. (0, 0, screenWidth, screenHeight) - * - * Its safe to use as filterArea or hitArea for 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 1 - */ - this.resolution = options.resolution || settings.RESOLUTION; - - /** - * Whether the render view is transparent - * - * @member {boolean} - */ - this.transparent = options.transparent; - - /** - * Whether css dimensions of canvas view should be resized to screen dimensions automatically - * - * @member {boolean} - */ - this.autoResize = options.autoResize || false; - - /** - * Tracks the blend modes useful for this renderer. - * - * @member {object} - */ - this.blendModes = null; - - /** - * 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; - - /** - * If true PixiJS will Math.floor() x/y values when rendering, stopping pixel interpolation. - * Handy for crisp pixel art and speed on legacy devices. - * - * @member {boolean} - */ - this.roundPixels = options.roundPixels; - - /** - * The background color as a number. - * - * @member {number} - * @private - */ - this._backgroundColor = 0x000000; - - /** - * The background color as an [R, G, B] array. - * - * @member {number[]} - * @private - */ - this._backgroundColorRgba = [0, 0, 0, 0]; - - /** - * The background color as a string. - * - * @member {string} - * @private - */ - this._backgroundColorString = '#000000'; - - this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter - - /** - * This temporary display object used as the parent of the currently being rendered item - * - * @member {PIXI.DisplayObject} - * @private - */ - this._tempDisplayObjectParent = new Container(); - - /** - * The last root object that the renderer tried to render. - * - * @member {PIXI.DisplayObject} - * @private - */ - this._lastObjectRendered = this._tempDisplayObjectParent; - - /** - * Collection of plugins. - * @readonly - * @member {object} - */ - this.plugins = {}; - } - - /** - * Initialize the plugins. - * - * @protected - * @param {object} staticMap - The dictionary of staticly saved plugins. - */ - initPlugins(staticMap) - { - 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() - { - return this.view.width; - } - - /** - * Same as view.height, actual number of pixels in the canvas by vertical - * - * @member {number} - * @readonly - * @default 600 - */ - get height() - { - return this.view.height; - } - - /** - * Resizes the screen and canvas to the specified width and height - * Canvas dimensions are multiplied by resolution - * - * @param {number} screenWidth - the new width of the screen - * @param {number} screenHeight - the new height of the screen - */ - resize(screenWidth, screenHeight) - { - this.screen.width = screenWidth; - this.screen.height = screenHeight; - - this.view.width = screenWidth * this.resolution; - this.view.height = screenHeight * this.resolution; - - if (this.autoResize) - { - this.view.style.width = `${screenWidth}px`; - this.view.style.height = `${screenHeight}px`; - } - } - - /** - * 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 {PIXI.DisplayObject} displayObject - The displayObject the object will be generated from - * @param {number} scaleMode - Should be one of the scaleMode consts - * @param {number} resolution - The resolution / device pixel ratio of the texture being generated - * @param {PIXI.Rectangle} [region] - The region of the displayObject, that shall be rendered, - * if no region is specified, defaults to the local bounds of the displayObject. - * @return {PIXI.Texture} a texture of the graphics object - */ - generateTexture(displayObject, scaleMode, resolution, region) - { - region = region || displayObject.getLocalBounds(); - - // 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(region.width | 0, region.height | 0, scaleMode, resolution); - - tempMatrix.tx = -region.x; - tempMatrix.ty = -region.y; - - this.render(displayObject, renderTexture, false, tempMatrix, true); - - return renderTexture; - } - - /** - * Removes everything from the renderer and optionally removes the Canvas DOM element. - * - * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. - */ - destroy(removeView) - { - 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); - } - - this.plugins = null; - - this.type = RENDERER_TYPE.UNKNOWN; - - this.view = null; - - this.screen = null; - - this.resolution = 0; - - this.transparent = false; - - this.autoResize = false; - - this.blendModes = null; - - this.options = null; - - this.preserveDrawingBuffer = false; - this.clearBeforeRender = false; - - this.roundPixels = false; - - this._backgroundColor = 0; - this._backgroundColorRgba = null; - this._backgroundColorString = null; - - this._tempDisplayObjectParent = null; - this._lastObjectRendered = null; - } - - /** - * The background color to fill if not transparent - * - * @member {number} - */ - get backgroundColor() - { - return this._backgroundColor; - } - - set backgroundColor(value) // eslint-disable-line require-jsdoc - { - this._backgroundColor = value; - this._backgroundColorString = hex2string(value); - hex2rgb(value, this._backgroundColorRgba); - } -} diff --git a/packages/core/src/renderers/Renderer.js b/packages/core/src/renderers/Renderer.js deleted file mode 100644 index a2020b1..0000000 --- a/packages/core/src/renderers/Renderer.js +++ /dev/null @@ -1,329 +0,0 @@ -import AbstractRenderer from './AbstractRenderer'; -import { sayHello } from '@pixi/utils'; -import MaskSystem from './systems/MaskSystem'; -import StencilSystem from './systems/StencilSystem'; -import FilterSystem from './systems/filter/FilterSystem'; -import FramebufferSystem from './systems/FramebufferSystem'; -import RenderTextureSystem from './systems/RenderTextureSystem'; -import TextureSystem from './systems/textures/TextureSystem'; -import ProjectionSystem from './systems/ProjectionSystem'; -import StateSystem from './systems/StateSystem'; -import GeometrySystem from './systems/geometry/GeometrySystem'; -import ShaderSystem from './systems/shader/ShaderSystem'; -import ContextSystem from './systems/ContextSystem'; -import BatchSystem from './systems/BatchSystem'; -import TextureGCSystem from './systems/textures/TextureGCSystem'; -import { RENDERER_TYPE } from '@pixi/constants'; -import UniformGroup from '../shader/UniformGroup'; -import { Matrix } from '@pixi/math'; -import Runner from 'mini-runner'; - -/** - * 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 Render works by automatically managing webGLBatchs. - * 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 :) - * - * @class - * @memberof PIXI - * @extends PIXI.AbstractRenderer - */ -export default class Renderer extends AbstractRenderer -{ - // eslint-disable-next-line valid-jsdoc - /** - * - * @param {object} [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.transparent=false] - If the render view is transparent, default false - * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false - * @param {boolean} [options.antialias=false] - sets antialias. If not available natively then FXAA - * antialiasing is used - * @param {boolean} [options.forceFXAA=false] - forces FXAA antialiasing to be used over native. - * FXAA is faster, but may not always look as great - * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. - * The resolution of the renderer retina would be 2. - * @param {boolean} [options.clearBeforeRender=true] - This sets if the renderer will clear - * the canvas or not before the new render pass. If you wish to set this to false, you *must* set - * preserveDrawingBuffer to `true`. - * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, - * enable this if you need to call toDataUrl on the webgl context. - * @param {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when - * rendering, stopping pixel interpolation. - * @param {number} [options.backgroundColor=0x000000] - The background color of the rendered area - * (shown if not transparent). - * @param {boolean} [options.legacy=false] - If true PixiJS will aim to ensure compatibility - * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. - * @param {string} [options.powerPreference] - Parameter passed to webgl context, set to "high-performance" - * for devices with dual graphics card - */ - constructor(options, arg2, arg3) - { - super('WebGL', options, arg2, arg3); - - /** - * The type of this renderer as a standardised const - * - * @member {number} - * @see PIXI.RENDERER_TYPE - */ - this.type = RENDERER_TYPE.WEBGL; - - // this will be set by the contextSystem (this.context) - this.gl = null; - this.CONTEXT_UID = 0; - this.legacy = !!options.legacy; - - // TODO legacy! - - // runners! - this.runners = { - destroy: new Runner('destroy'), - contextChange: new Runner('contextChange', 1), - reset: new Runner('reset'), - update: new Runner('update'), - postrender: new Runner('postrender'), - prerender: new Runner('prerender'), - resize: new Runner('resize', 2), - }; - - 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(GeometrySystem, 'geometry') - .addSystem(FramebufferSystem, 'framebuffer') - .addSystem(StencilSystem, 'stencil') - .addSystem(ProjectionSystem, 'projection') - .addSystem(TextureGCSystem, 'textureGC') - .addSystem(FilterSystem, 'filter') - .addSystem(RenderTextureSystem, 'renderTexture') - .addSystem(BatchSystem, 'batch'); - - this.initPlugins(Renderer.__plugins); - - /** - * The options passed in to create a new webgl context. - * - * @member {object} - * @private - */ - if (options.context) - { - this.context.initFromContext(options.context); - } - else - { - this.context.initFromOptions({ - alpha: this.transparent, - antialias: options.antialias, - premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', - stencil: true, - 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); - } - - addSystem(_class, name) - { - if (!name) - { - name = _class.name; - } - - // TODO - read name from class.name.. - - /* - if(name.includes('System')) - { - name = name.replace('System', ''); - name = name.charAt(0).toLowerCase() + name.slice(1); - } - */ - - const system = new _class(this); - - if (this[name]) - { - throw new Error(`Whoops! ${name} is already a manger`); - } - - this[name] = system; - - for (const i in this.runners) - { - this.runners[i].add(system); - } - - return this; - - /** - * 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. - */ - } - - /** - * Renders the object to its webGL view - * - * @param {PIXI.DisplayObject} displayObject - the object to be rendered - * @param {PIXI.RenderTexture} renderTexture - The render texture to render to. - * @param {boolean} [clear] - Should the canvas be cleared before the new render - * @param {PIXI.Transform} [transform] - A transform to apply to the render texture before rendering. - * @param {boolean} [skipUpdateTransform] - Should we skip the update transform pass? - */ - render(displayObject, renderTexture, clear, transform, skipUpdateTransform) - { - // can be handy to know! - this.renderingToScreen = !renderTexture; - - this.runners.prerender.run(); - this.emit('prerender'); - - // 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.parent; - - displayObject.parent = this._tempDisplayObjectParent; - displayObject.updateTransform(); - displayObject.parent = 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.run(); - - this.emit('postrender'); - } - - /** - * Resizes the webGL view to the specified width and height. - * - * @param {number} screenWidth - the new width of the screen - * @param {number} screenHeight - the new height of the screen - */ - resize(screenWidth, screenHeight) - { - AbstractRenderer.prototype.resize.call(this, screenWidth, screenHeight); - - this.runners.resize.run(screenWidth, screenHeight); - } - - /** - * Resets the WebGL state so you can render things however you fancy! - * - * @return {PIXI.Renderer} Returns itself. - */ - reset() - { - this.runners.reset.run(); - - return this; - } - - clear() - { - this.framebuffer.bind(); - this.framebuffer.clear(); - } - - /** - * Removes everything from the renderer (event listeners, spritebatch, etc...) - * - * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. - * See: https://github.com/pixijs/pixi.js/issues/2233 - */ - destroy(removeView) - { - this.runners.destroy.run(); - - // call base destroy - super.destroy(removeView); - - // TODO nullify all the managers.. - this.gl = null; - } - - /** - * 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 - * builds and excluding plugins. - * @name PIXI.Renderer#plugins - * @type {object} - * @readonly - * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. - * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. - * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. - * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. - */ - - /** - * Adds a plugin to the renderer. - * - * @method - * @param {string} pluginName - The name of the plugin. - * @param {Function} ctor - The constructor function or class for the plugin. - */ - static registerPlugin(pluginName, ctor) - { - Renderer.__plugins = Renderer.__plugins || {}; - Renderer.__plugins[pluginName] = ctor; - } -} diff --git a/packages/core/src/renderers/State.js b/packages/core/src/renderers/State.js deleted file mode 100644 index fa3f221..0000000 --- a/packages/core/src/renderers/State.js +++ /dev/null @@ -1,163 +0,0 @@ -/* eslint-disable max-len */ - -const BLEND = 0; -const OFFSET = 1; -const CULLING = 2; -const DEPTH_TEST = 3; -const WINDING = 4; - -/** - * This is a webGL state. It is passed The WebGL StateManager. - * Each mesh renderered may require webGL to be in a different state. - * For example you may want different blend mode or to enable polygon offsets - * - * @class - * @memberof PIXI - */ -export default class State -{ - /** - * - */ - constructor() - { - this.data = 0; - - this.blendMode = 0; - this.polygonOffset = 0; - - this.blend = true; - // this.depthTest = true; - } - - /** - * Activates blending of the computed fragment color values - * - * @member {boolean} - */ - get blend() - { - return !!(this.data & (1 << BLEND)); - } - - set blend(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << BLEND)) !== value) - { - this.data ^= (1 << BLEND); - } - } - - /** - * Activates adding an offset to depth values of polygon's fragments - * - * @member {boolean} - * @default false - */ - get offsets() - { - return !!(this.data & (1 << OFFSET)); - } - - set offsets(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << OFFSET)) !== value) - { - this.data ^= (1 << OFFSET); - } - } - - /** - * Activates culling of polygons. - * - * @member {boolean} - * @default false - */ - get culling() - { - return !!(this.data & (1 << CULLING)); - } - - set culling(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << CULLING)) !== value) - { - this.data ^= (1 << CULLING); - } - } - - /** - * Activates depth comparisons and updates to the depth buffer. - * - * @member {boolean} - * @default false - */ - get depthTest() - { - return !!(this.data & (1 << DEPTH_TEST)); - } - - set depthTest(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << DEPTH_TEST)) !== value) - { - this.data ^= (1 << DEPTH_TEST); - } - } - - /** - * Specifies whether or not front or back-facing polygons can be culled. - * @member {boolean} - * @default false - */ - get clockwiseFrontFace() - { - return !!(this.data & (1 << WINDING)); - } - - set clockwiseFrontFace(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << WINDING)) !== value) - { - this.data ^= (1 << WINDING); - } - } - - /** - * The blend mode to be applied when this state is set. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. - * Setting this mode to anything other than NO_BLEND will automatically switch blending on. - * - * @member {boolean} - * @default PIXI.BLEND_MODES.NORMAL - * @see PIXI.BLEND_MODES - */ - get blendMode() - { - return this._blendMode; - } - - set blendMode(value) // eslint-disable-line require-jsdoc - { - // 17 is NO BLEND - this.blend = (value !== 17); - this._blendMode = value; - } - - /** - * The polygon offset. Setting this property to anything other than 0 will automatically enable poygon offset fill. - * - * @member {number} - * @default 0 - */ - get polygonOffset() - { - return this._polygonOffset; - } - - set polygonOffset(value) // eslint-disable-line require-jsdoc - { - this.offsets = !!value; - this._polygonOffset = value; - } -} - diff --git a/packages/core/src/renderers/filters/Filter.js b/packages/core/src/renderers/filters/Filter.js deleted file mode 100644 index 2073b8d..0000000 --- a/packages/core/src/renderers/filters/Filter.js +++ /dev/null @@ -1,140 +0,0 @@ -import Shader from '../../shader/Shader'; -import Program from '../../shader/Program'; -import State from '../State'; -import { settings } from '@pixi/settings'; -// import extractUniformsFromSrc from './extractUniformsFromSrc'; -import defaultVertex from './defaultFilter.vert'; -import defaultFragment from './defaultFilter.frag'; - -// let math = require('../../math'); -/** - * @class - * @memberof PIXI - * @extends PIXI.Shader - */ -export default class Filter extends Shader -{ - /** - * @param {string} [vertexSrc] - The source of the vertex shader. - * @param {string} [fragmentSrc] - The source of the fragment shader. - * @param {object} [uniforms] - Custom uniforms to use to augment the built-in ones. - */ - constructor(vertexSrc, fragmentSrc, uniforms) - { - const program = Program.from(vertexSrc, fragmentSrc); - - super(program, uniforms); - - /** - * The padding of the filter. Some filters require extra space to breath such as a blur. - * Increasing this will add extra width and height to the bounds of the object that the - * filter is applied to. - * - * @member {number} - */ - this.padding = 0; - - /** - * The resolution of the filter. Setting this to be lower will lower the quality but - * increase the performance of the filter. - * - * @member {number} - */ - this.resolution = settings.RESOLUTION; - - /** - * If enabled is true the filter is applied, if false it will not. - * - * @member {boolean} - */ - this.enabled = true; - - /** - * If enabled, PixiJS will fit the filter area into boundaries for better performance. - * Switch it off if it does not work for specific shader. - * - * @member {boolean} - */ - this.autoFit = true; - - /** - * Legacy filters use position and uvs from attributes - * @member {boolean} - * @readonly - */ - this.legacy = !!this.program.attributeData.aTextureCoord; - - /** - * the webGL state the filter requires to render - * @member {PIXI.State} - */ - this.state = new State(); - } - - /** - * Applies the filter - * - * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from - * @param {PIXI.RenderTarget} input - The input render target. - * @param {PIXI.RenderTarget} output - The target to output to. - * @param {boolean} clear - Should the output be cleared before rendering to it - * @param {object} [currentState] - It's current state of filter. - * There are some useful properties in the currentState : - * target, filters, sourceFrame, destinationFrame, renderTarget, resolution - */ - apply(filterManager, input, output, clear, currentState, derp) // eslint-disable-line no-unused-vars - { - // do as you please! - - filterManager.applyFilter(this, input, output, clear, currentState, derp); - - // or just do a regular render.. - } - - /** - * Sets the blendmode of the filter - * - * @member {number} - * @default PIXI.BLEND_MODES.NORMAL - */ - get blendMode() - { - return this.state.blendMode; - } - - set blendMode(value) // eslint-disable-line require-jsdoc - { - this.state.blendMode = value; - } - - /** - * The default vertex shader source - * - * @static - * @constant - */ - static get defaultVertexSrc() - { - return defaultVertex; - } - - /** - * The default fragment shader source - * - * @static - * @constant - */ - static get defaultFragmentSrc() - { - return defaultFragment; - } -} - -/** - * Used for caching shader IDs - * - * @static - * @private - */ -Filter.SOURCE_KEY_MAP = {}; - diff --git a/packages/core/src/renderers/filters/defaultFilter.frag b/packages/core/src/renderers/filters/defaultFilter.frag deleted file mode 100644 index a14c1be..0000000 --- a/packages/core/src/renderers/filters/defaultFilter.frag +++ /dev/null @@ -1,22 +0,0 @@ -varying vec2 vTextureCoord; -varying vec2 vFilterCoord; - -uniform sampler2D uSampler; -uniform sampler2D filterSampler; - -void main(void){ - vec4 masky = texture2D(filterSampler, vFilterCoord); - vec4 sample = texture2D(uSampler, vTextureCoord); - vec4 color; - if(mod(vFilterCoord.x, 1.0) > 0.5) - { - color = vec4(1.0, 0.0, 0.0, 1.0); - } - else - { - color = vec4(0.0, 1.0, 0.0, 1.0); - } - // gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0); - gl_FragColor = mix(sample, masky, 0.5); - gl_FragColor *= sample.a; -} diff --git a/packages/core/src/renderers/filters/defaultFilter.vert b/packages/core/src/renderers/filters/defaultFilter.vert deleted file mode 100644 index e1febca..0000000 --- a/packages/core/src/renderers/filters/defaultFilter.vert +++ /dev/null @@ -1,14 +0,0 @@ -attribute vec2 aVertexPosition; -attribute vec2 aTextureCoord; - -uniform mat3 projectionMatrix; -uniform mat3 filterMatrix; - -varying vec2 vTextureCoord; -varying vec2 vFilterCoord; - -void main(void){ - gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); - vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy; - vTextureCoord = aTextureCoord ; -} diff --git a/packages/core/src/renderers/filters/filterTransforms.js b/packages/core/src/renderers/filters/filterTransforms.js deleted file mode 100644 index 87a3cd6..0000000 --- a/packages/core/src/renderers/filters/filterTransforms.js +++ /dev/null @@ -1,50 +0,0 @@ -import { Matrix } from '@pixi/math'; - -/** - * Calculates the mapped matrix - * @param filterArea {Rectangle} The filter area - * @param sprite {Sprite} the target sprite - * @param outputMatrix {Matrix} @alvin - * @private - */ -// this returns a matrix that will normalise map filter cords in the filter to screen space -export function calculateScreenSpaceMatrix(outputMatrix, filterArea, textureSize) -{ - // TODO unwrap? - const mappedMatrix = outputMatrix.identity(); - - mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); - - mappedMatrix.scale(textureSize.width, textureSize.height); - - return mappedMatrix; -} - -export function calculateNormalizedScreenSpaceMatrix(outputMatrix, filterArea, textureSize) -{ - const mappedMatrix = outputMatrix.identity(); - - mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); - - const translateScaleX = (textureSize.width / filterArea.width); - const translateScaleY = (textureSize.height / filterArea.height); - - mappedMatrix.scale(translateScaleX, translateScaleY); - - return mappedMatrix; -} - -// this will map the filter coord so that a texture can be used based on the transform of a sprite -export function calculateSpriteMatrix(outputMatrix, filterArea, textureSize, sprite) -{ - const orig = sprite._texture.orig; - const mappedMatrix = outputMatrix.set(textureSize.width, 0, 0, textureSize.height, filterArea.x, filterArea.y); - const worldTransform = sprite.worldTransform.copyTo(Matrix.TEMP_MATRIX); - - worldTransform.invert(); - mappedMatrix.prepend(worldTransform); - mappedMatrix.scale(1.0 / orig.width, 1.0 / orig.height); - mappedMatrix.translate(sprite.anchor.x, sprite.anchor.y); - - return mappedMatrix; -} diff --git a/packages/core/src/renderers/filters/spriteMask/SpriteMaskFilter.js b/packages/core/src/renderers/filters/spriteMask/SpriteMaskFilter.js deleted file mode 100644 index d5a91a4..0000000 --- a/packages/core/src/renderers/filters/spriteMask/SpriteMaskFilter.js +++ /dev/null @@ -1,63 +0,0 @@ -import Filter from '../Filter'; -import { Matrix } from '@pixi/math'; -import vertex from './spriteMaskFilter.vert'; -import fragment from './spriteMaskFilter.frag'; -import { default as TextureMatrix } from '../../../textures/TextureMatrix'; - -/** - * The SpriteMaskFilter class - * - * @class - * @extends PIXI.Filter - * @memberof PIXI - */ -export default class SpriteMaskFilter extends Filter -{ - /** - * @param {PIXI.Sprite} sprite - the target sprite - */ - constructor(sprite) - { - const maskMatrix = new Matrix(); - - super(vertex, fragment); - - sprite.renderable = false; - - this.maskSprite = sprite; - this.maskMatrix = maskMatrix; - } - - /** - * Applies the filter - * - * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from - * @param {PIXI.RenderTarget} input - The input render target. - * @param {PIXI.RenderTarget} output - The target to output to. - */ - apply(filterManager, input, output) - { - const maskSprite = this.maskSprite; - const tex = this.maskSprite.texture; - - if (!tex.valid) - { - return; - } - if (!tex.transform) - { - // margin = 0.0, let it bleed a bit, shader code becomes easier - // assuming that atlas textures were made with 1-pixel padding - tex.transform = new TextureMatrix(tex, 0.0); - } - tex.transform.update(); - - this.uniforms.mask = tex; - this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite) - .prepend(tex.transform.mapCoord); - this.uniforms.alpha = maskSprite.worldAlpha; - this.uniforms.maskClamp = tex.transform.uClampFrame; - - filterManager.applyFilter(this, input, output); - } -} diff --git a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.frag b/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.frag deleted file mode 100644 index 0e4aef8..0000000 --- a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.frag +++ /dev/null @@ -1,23 +0,0 @@ -varying vec2 vMaskCoord; -varying vec2 vTextureCoord; - -uniform sampler2D uSampler; -uniform sampler2D mask; -uniform float alpha; -uniform vec4 maskClamp; - -void main(void) -{ - float clip = step(3.5, - step(maskClamp.x, vMaskCoord.x) + - step(maskClamp.y, vMaskCoord.y) + - step(vMaskCoord.x, maskClamp.z) + - step(vMaskCoord.y, maskClamp.w)); - - vec4 original = texture2D(uSampler, vTextureCoord); - vec4 masky = texture2D(mask, vMaskCoord); - - original *= (masky.r * masky.a * alpha * clip); - - gl_FragColor = original; -} diff --git a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.vert b/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.vert deleted file mode 100644 index d23a30c..0000000 --- a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.vert +++ /dev/null @@ -1,16 +0,0 @@ -attribute vec2 aVertexPosition; -attribute vec2 aTextureCoord; - -uniform mat3 projectionMatrix; -uniform mat3 otherMatrix; - -varying vec2 vMaskCoord; -varying vec2 vTextureCoord; - -void main(void) -{ - gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); - - vTextureCoord = aTextureCoord; - vMaskCoord = ( otherMatrix * vec3( aTextureCoord, 1.0) ).xy; -} diff --git a/packages/core/src/renderers/systems/BatchSystem.js b/packages/core/src/renderers/systems/BatchSystem.js deleted file mode 100644 index 70e9518..0000000 --- a/packages/core/src/renderers/systems/BatchSystem.js +++ /dev/null @@ -1,68 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import ObjectRenderer from '../utils/ObjectRenderer'; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ - -export default class BatchSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this manager works for. - */ - constructor(renderer) - { - super(renderer); - - /** - * An empty renderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.emptyRenderer = new ObjectRenderer(renderer); - - /** - * The currently active ObjectRenderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.currentRenderer = this.emptyRenderer; - } - - /** - * Changes the current renderer to the one given in parameter - * - * @param {PIXI.ObjectRenderer} objectRenderer - The object renderer to use. - */ - setObjectRenderer(objectRenderer) - { - if (this.currentRenderer === objectRenderer) - { - return; - } - - this.currentRenderer.stop(); - this.currentRenderer = objectRenderer; - - this.renderer.state.setState(objectRenderer.state); - - this.currentRenderer.start(); - } - - /** - * This should be called if you wish to do some custom rendering - * It will basically render anything that may be batched up such as sprites - * - */ - flush() - { - this.setObjectRenderer(this.emptyRenderer); - } - - reset() - { - this.setObjectRenderer(this.emptyRenderer); - } -} diff --git a/packages/core/src/renderers/systems/ContextSystem.js b/packages/core/src/renderers/systems/ContextSystem.js deleted file mode 100644 index 43bf827..0000000 --- a/packages/core/src/renderers/systems/ContextSystem.js +++ /dev/null @@ -1,186 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { settings } from '@pixi/settings'; - -let CONTEXT_UID = 0; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class ContextSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this.webGLVersion = 1; - - this.handleContextLost = this.handleContextLost.bind(this); - this.handleContextRestored = this.handleContextRestored.bind(this); - - this.extensions = {}; - - renderer.view.addEventListener('webglcontextlost', this.handleContextLost, false); - renderer.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); - } - - get isLost() - { - return (!this.gl || this.gl.isContextLost()); - } - - contextChange(gl) - { - this.gl = gl; - - // restore a context if it was previously lost - if (gl.isContextLost() && gl.getExtension('WEBGL_lose_context')) - { - gl.getExtension('WEBGL_lose_context').restoreContext(); - } - } - - initFromContext(gl) - { - this.gl = gl; - this.validateContext(gl); - this.renderer.gl = gl; - this.renderer.CONTEXT_UID = CONTEXT_UID++; - this.renderer.runners.contextChange.run(gl); - } - - initFromOptions(options) - { - const gl = this.createContext(this.renderer.view, options); - - this.initFromContext(gl); - } - - /** - * Helper class to create a webGL Context - * - * @class - * @memberof PIXI.glCore - * @param canvas {HTMLCanvasElement} the canvas element that we will get the context from - * @param options {Object} An options object that gets passed in to the canvas element containing the context attributes, - * see https://developer.mozilla.org/en/docs/Web/API/HTMLCanvasElement/getContext for the options available - * @return {WebGLRenderingContext} the WebGL context - */ - createContext(canvas, options) - { - let gl; - - if (settings.PREFER_WEBGL_2) - { - gl = canvas.getContext('webgl2', options); - } - - if (gl) - { - this.webGLVersion = 2; - } - else - { - this.webGLVersion = 1; - - gl = canvas.getContext('webgl', options) - || canvas.getContext('experimental-webgl', options); - - if (!gl) - { - // fail, not able to get a context - throw new Error('This browser does not support webGL. Try using the canvas renderer'); - } - } - - this.gl = gl; - - this.getExtensions(); - - return gl; - } - - getExtensions() - { - // time to set up default etensions that pixi uses.. - const gl = this.gl; - const extensions = this.extensions; - - if (this.webGLVersion === 1) - { - extensions.drawBuffers = gl.getExtension('WEBGL_draw_buffers'); - extensions.depthTexture = gl.getExtension('WEBKIT_WEBGL_depth_texture'); - extensions.floatTexture = gl.getExtension('OES_texture_float'); - extensions.loseContext = gl.getExtension('WEBGL_lose_context'); - - extensions.vertexArrayObject = gl.getExtension('OES_vertex_array_object') - || gl.getExtension('MOZ_OES_vertex_array_object') - || gl.getExtension('WEBKIT_OES_vertex_array_object'); - } - - // we don't use any specific WebGL 2 ones yet! - } - - /** - * Handles a lost webgl context - * - * @private - * @param {WebGLContextEvent} event - The context lost event. - */ - handleContextLost(event) - { - event.preventDefault(); - } - - /** - * Handles a restored webgl context - * - * @private - */ - handleContextRestored() - { - this.renderer.runners.contextChange.run(this.gl); - } - - destroy() - { - const view = this.renderer.view; - - // remove listeners - view.removeEventListener('webglcontextlost', this.handleContextLost); - view.removeEventListener('webglcontextrestored', this.handleContextRestored); - - this.gl.useProgram(null); - - if (this.extensions.loseContext) - { - this.extensions.loseContext.loseContext(); - } - } - - postrender() - { - this.gl.flush(); - } - - validateContext(gl) - { - const attributes = gl.getContextAttributes(); - - // this is going to be fairly simple for now.. but at least we have room to grow! - if (!attributes.stencil) - { - /* eslint-disable max-len */ - - /* eslint-disable no-console */ - console.warn('Provided WebGL context does not have a stencil buffer, masks may not render correctly'); - /* eslint-enable no-console */ - - /* eslint-enable max-len */ - } - } -} diff --git a/packages/core/src/renderers/systems/FramebufferSystem.js b/packages/core/src/renderers/systems/FramebufferSystem.js deleted file mode 100644 index df7902b..0000000 --- a/packages/core/src/renderers/systems/FramebufferSystem.js +++ /dev/null @@ -1,246 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { Rectangle } from '@pixi/math'; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class FramebufferSystem extends WebGLSystem -{ - /** - * Sets up the renderer context and necessary buffers. - * - * @private - */ - contextChange() - { - this.gl = this.renderer.gl; - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - this.current = null; - this.viewport = new Rectangle(); - - this.drawBufferExtension = this.renderer.context.extensions.drawBuffers; - } - - bind(framebuffer, frame) - { - const gl = this.gl; - - this.current = framebuffer; - - if (framebuffer) - { - // TODO cacheing layer! - - const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID] || this.initFramebuffer(framebuffer); - - gl.bindFramebuffer(gl.FRAMEBUFFER, fbo.framebuffer); - // makesure all textures are unbound.. - - // now check for updates... - if (fbo.dirtyId !== framebuffer.dirtyId) - { - fbo.dirtyId = framebuffer.dirtyId; - - if (fbo.dirtyFormat !== framebuffer.dirtyFormat) - { - fbo.dirtyFormat = framebuffer.dirtyFormat; - this.updateFramebuffer(framebuffer); - } - else if (fbo.dirtySize !== framebuffer.dirtySize) - { - fbo.dirtySize = framebuffer.dirtySize; - this.resizeFramebuffer(framebuffer); - } - } - - for (let i = 0; i < framebuffer.colorTextures.length; i++) - { - if (framebuffer.colorTextures[i].texturePart) - { - this.renderer.texture.unbind(framebuffer.colorTextures[i].texture); - } - else - { - this.renderer.texture.unbind(framebuffer.colorTextures[i]); - } - } - - if (framebuffer.depthTexture) - { - this.renderer.texture.unbind(framebuffer.depthTexture); - } - - if (frame) - { - this.setViewport(frame.x, frame.y, frame.width, frame.height); - } - else - { - this.setViewport(0, 0, framebuffer.width, framebuffer.height); - } - } - else - { - gl.bindFramebuffer(gl.FRAMEBUFFER, null); - - if (frame) - { - this.setViewport(frame.x, frame.y, frame.width, frame.height); - } - else - { - this.setViewport(0, 0, this.renderer.width, this.renderer.height); - } - } - } - - setViewport(x, y, width, height) - { - const v = this.viewport; - - if (v.width !== width || v.height !== height || v.x !== x || v.y !== y) - { - v.x = x; - v.y = y; - v.width = width; - v.height = height; - - this.gl.viewport(x, y, width, height); - } - } - - get size() - { - if (this.current) - { - // TODO store temp - return { x: 0, y: 0, width: this.current.width, height: this.current.height }; - } - - return { x: 0, y: 0, width: this.renderer.width, height: this.renderer.height }; - } - - clear(r, g, b, a) - { - const gl = this.gl; - - // TODO clear color can be set only one right? - gl.clearColor(r, g, b, a); - gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); - } - - // private functions... - - initFramebuffer(framebuffer) - { - const gl = this.gl; - - // TODO - make this a class? - const fbo = { - framebuffer: gl.createFramebuffer(), - stencil: null, - dirtyId: 0, - dirtyFormat: 0, - dirtySize: 0, - }; - - framebuffer.glFrameBuffers[this.CONTEXT_UID] = fbo; - - return fbo; - } - - resizeFramebuffer(framebuffer) - { - const gl = this.gl; - - if (framebuffer.stencil || framebuffer.depth) - { - gl.bindRenderbuffer(gl.RENDERBUFFER, this.stencil); - gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); - } - } - - updateFramebuffer(framebuffer) - { - const gl = this.gl; - - const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID]; - - // bind the color texture - const colorTextures = framebuffer.colorTextures; - - let count = colorTextures.length; - - if (!this.drawBufferExtension) - { - count = Math.min(count, 1); - } - - const activeTextures = []; - - for (let i = 0; i < count; i++) - { - const texture = framebuffer.colorTextures[i]; - - if (texture.texturePart) - { - this.renderer.texture.bind(texture.texture, 0); - - gl.framebufferTexture2D(gl.FRAMEBUFFER, - gl.COLOR_ATTACHMENT0 + i, - gl.TEXTURE_CUBE_MAP_NEGATIVE_X + texture.side, - texture.texture._glTextures[this.CONTEXT_UID].texture, - 0); - } - else - { - this.renderer.texture.bind(texture, 0); - - gl.framebufferTexture2D(gl.FRAMEBUFFER, - gl.COLOR_ATTACHMENT0 + i, - gl.TEXTURE_2D, - texture._glTextures[this.CONTEXT_UID].texture, - 0); - } - - activeTextures.push(gl.COLOR_ATTACHMENT0 + i); - } - - if (this.drawBufferExtension && activeTextures.length > 1) - { - this.drawBufferExtension.drawBuffersWEBGL(activeTextures); - } - - if (framebuffer.depthTexture) - { - const depthTextureExt = this.renderer.context.extensions.depthTexture; - - if (depthTextureExt) - { - const depthTexture = framebuffer.depthTexture; - - this.renderer.texture.bind(depthTexture, 0); - - gl.framebufferTexture2D(gl.FRAMEBUFFER, - gl.DEPTH_ATTACHMENT, - gl.TEXTURE_2D, - depthTexture._glTextures[this.CONTEXT_UID].texture, - 0); - } - } - - if (framebuffer.stencil || framebuffer.depth) - { - fbo.stencil = gl.createRenderbuffer(); - - gl.bindRenderbuffer(gl.RENDERBUFFER, fbo.stencil); - - // TODO.. this is depth AND stencil? - gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, fbo.stencil); - gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); - // fbo.enableStencil(); - } - } -} diff --git a/packages/core/src/renderers/systems/MaskSystem.js b/packages/core/src/renderers/systems/MaskSystem.js deleted file mode 100644 index d9c4b1a..0000000 --- a/packages/core/src/renderers/systems/MaskSystem.js +++ /dev/null @@ -1,199 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import AlphaMaskFilter from '../filters/spriteMask/SpriteMaskFilter'; - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class MaskSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - // TODO - we don't need both! - this.scissor = false; - this.scissorData = null; - this.scissorRenderTarget = null; - - this.enableScissor = false; - - this.alphaMaskPool = []; - this.alphaMaskIndex = 0; - } - - /** - * Applies the Mask and adds it to the current filter stack. - * - * @param {PIXI.DisplayObject} target - Display Object to push the mask to - * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. - */ - push(target, maskData) - { - // TODO the root check means scissor rect will not - // be used on render textures more info here: - // https://github.com/pixijs/pixi.js/pull/3545 - - if (maskData.texture) - { - this.pushSpriteMask(target, maskData); - } - else if (this.enableScissor - && !this.scissor - && this.renderer._activeRenderTarget.root - && !this.renderer.stencil.stencilMaskStack.length - && maskData.isFastRect()) - { - const matrix = maskData.worldTransform; - - let rot = Math.atan2(matrix.b, matrix.a); - - // use the nearest degree! - rot = Math.round(rot * (180 / Math.PI)); - - if (rot % 90) - { - this.pushStencilMask(maskData); - } - else - { - this.pushScissorMask(target, maskData); - } - } - else - { - this.pushStencilMask(maskData); - } - } - - /** - * Removes the last mask from the mask stack and doesn't return it. - * - * @param {PIXI.DisplayObject} target - Display Object to pop the mask from - * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. - */ - pop(target, maskData) - { - if (maskData.texture) - { - this.popSpriteMask(target, maskData); - } - else if (this.enableScissor && !this.renderer.stencil.stencilMaskStack.length) - { - this.popScissorMask(target, maskData); - } - else - { - this.popStencilMask(target, maskData); - } - } - - /** - * Applies the Mask and adds it to the current filter stack. - * - * @param {PIXI.RenderTarget} target - Display Object to push the sprite mask to - * @param {PIXI.Sprite} maskData - Sprite to be used as the mask - */ - pushSpriteMask(target, maskData) - { - let alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; - - if (!alphaMaskFilter) - { - alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; - } - - alphaMaskFilter[0].resolution = this.renderer.resolution; - alphaMaskFilter[0].maskSprite = maskData; - - // TODO - may cause issues! - target.filterArea = maskData.getBounds(true); - - this.renderer.filterSystem.pushFilter(target, alphaMaskFilter); - - this.alphaMaskIndex++; - } - - /** - * Removes the last filter from the filter stack and doesn't return it. - * - */ - popSpriteMask() - { - this.renderer.filterSystem.popFilter(); - this.alphaMaskIndex--; - } - - /** - * Applies the Mask and adds it to the current filter stack. - * - * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. - */ - pushStencilMask(maskData) - { - this.renderer.batch.flush(); - this.renderer.stencil.pushStencil(maskData); - } - - /** - * Removes the last filter from the filter stack and doesn't return it. - * - */ - popStencilMask() - { - // this.renderer.currentRenderer.stop(); - this.renderer.stencil.popStencil(); - } - - /** - * - * @param {PIXI.DisplayObject} target - Display Object to push the mask to - * @param {PIXI.Graphics} maskData - The masking data. - */ - pushScissorMask(target, maskData) - { - maskData.renderable = true; - - const renderTarget = this.renderer._activeRenderTarget; - - const bounds = maskData.getBounds(); - - bounds.fit(renderTarget.size); - maskData.renderable = false; - - this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); - - const resolution = this.renderer.resolution; - - this.renderer.gl.scissor( - bounds.x * resolution, - (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, - bounds.width * resolution, - bounds.height * resolution - ); - - this.scissorRenderTarget = renderTarget; - this.scissorData = maskData; - this.scissor = true; - } - - /** - * - * - */ - popScissorMask() - { - this.scissorRenderTarget = null; - this.scissorData = null; - this.scissor = false; - - // must be scissor! - const gl = this.renderer.gl; - - gl.disable(gl.SCISSOR_TEST); - } -} diff --git a/packages/core/src/AbstractRenderer.js b/packages/core/src/AbstractRenderer.js new file mode 100644 index 0000000..64b2eb1 --- /dev/null +++ b/packages/core/src/AbstractRenderer.js @@ -0,0 +1,353 @@ +import { hex2string, hex2rgb } from '@pixi/utils'; +import { Matrix, Rectangle } from '@pixi/math'; +import { RENDERER_TYPE } from '@pixi/constants'; +import { settings } from '@pixi/settings'; +import { Container } from '@pixi/display'; +import RenderTexture from './renderTexture/RenderTexture'; +import EventEmitter from 'eventemitter3'; + +const tempMatrix = new Matrix(); + +/** + * 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 EventEmitter + * @memberof PIXI + */ +export default class AbstractRenderer extends EventEmitter +{ + // eslint-disable-next-line valid-jsdoc + /** + * @param {string} system - The name of the system this renderer is for. + * @param {object} [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.transparent=false] - If the render view is transparent, default false + * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false + * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. The + * resolution of the renderer retina would be 2. + * @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 {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when rendering, + * stopping pixel interpolation. + */ + constructor(system, options, arg2, arg3) + { + super(); + + // Support for constructor(system, screenWidth, screenHeight, options) + if (typeof options === 'number') + { + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + }, arg3); + } + + // 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 = RENDERER_TYPE.UNKNOWN; + + /** + * Measurements of the screen. (0, 0, screenWidth, screenHeight) + * + * Its safe to use as filterArea or hitArea for 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 1 + */ + this.resolution = options.resolution || settings.RESOLUTION; + + /** + * Whether the render view is transparent + * + * @member {boolean} + */ + this.transparent = options.transparent; + + /** + * Whether css dimensions of canvas view should be resized to screen dimensions automatically + * + * @member {boolean} + */ + this.autoResize = options.autoResize || false; + + /** + * Tracks the blend modes useful for this renderer. + * + * @member {object} + */ + this.blendModes = null; + + /** + * 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; + + /** + * If true PixiJS will Math.floor() x/y values when rendering, stopping pixel interpolation. + * Handy for crisp pixel art and speed on legacy devices. + * + * @member {boolean} + */ + this.roundPixels = options.roundPixels; + + /** + * The background color as a number. + * + * @member {number} + * @private + */ + this._backgroundColor = 0x000000; + + /** + * The background color as an [R, G, B] array. + * + * @member {number[]} + * @private + */ + this._backgroundColorRgba = [0, 0, 0, 0]; + + /** + * The background color as a string. + * + * @member {string} + * @private + */ + this._backgroundColorString = '#000000'; + + this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter + + /** + * This temporary display object used as the parent of the currently being rendered item + * + * @member {PIXI.DisplayObject} + * @private + */ + this._tempDisplayObjectParent = new Container(); + + /** + * The last root object that the renderer tried to render. + * + * @member {PIXI.DisplayObject} + * @private + */ + this._lastObjectRendered = this._tempDisplayObjectParent; + + /** + * Collection of plugins. + * @readonly + * @member {object} + */ + this.plugins = {}; + } + + /** + * Initialize the plugins. + * + * @protected + * @param {object} staticMap - The dictionary of staticly saved plugins. + */ + initPlugins(staticMap) + { + 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() + { + return this.view.width; + } + + /** + * Same as view.height, actual number of pixels in the canvas by vertical + * + * @member {number} + * @readonly + * @default 600 + */ + get height() + { + return this.view.height; + } + + /** + * Resizes the screen and canvas to the specified width and height + * Canvas dimensions are multiplied by resolution + * + * @param {number} screenWidth - the new width of the screen + * @param {number} screenHeight - the new height of the screen + */ + resize(screenWidth, screenHeight) + { + this.screen.width = screenWidth; + this.screen.height = screenHeight; + + this.view.width = screenWidth * this.resolution; + this.view.height = screenHeight * this.resolution; + + if (this.autoResize) + { + this.view.style.width = `${screenWidth}px`; + this.view.style.height = `${screenHeight}px`; + } + } + + /** + * 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 {PIXI.DisplayObject} displayObject - The displayObject the object will be generated from + * @param {number} scaleMode - Should be one of the scaleMode consts + * @param {number} resolution - The resolution / device pixel ratio of the texture being generated + * @param {PIXI.Rectangle} [region] - The region of the displayObject, that shall be rendered, + * if no region is specified, defaults to the local bounds of the displayObject. + * @return {PIXI.Texture} a texture of the graphics object + */ + generateTexture(displayObject, scaleMode, resolution, region) + { + region = region || displayObject.getLocalBounds(); + + // 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(region.width | 0, region.height | 0, scaleMode, resolution); + + tempMatrix.tx = -region.x; + tempMatrix.ty = -region.y; + + this.render(displayObject, renderTexture, false, tempMatrix, true); + + return renderTexture; + } + + /** + * Removes everything from the renderer and optionally removes the Canvas DOM element. + * + * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. + */ + destroy(removeView) + { + 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); + } + + this.plugins = null; + + this.type = RENDERER_TYPE.UNKNOWN; + + this.view = null; + + this.screen = null; + + this.resolution = 0; + + this.transparent = false; + + this.autoResize = false; + + this.blendModes = null; + + this.options = null; + + this.preserveDrawingBuffer = false; + this.clearBeforeRender = false; + + this.roundPixels = false; + + this._backgroundColor = 0; + this._backgroundColorRgba = null; + this._backgroundColorString = null; + + this._tempDisplayObjectParent = null; + this._lastObjectRendered = null; + } + + /** + * The background color to fill if not transparent + * + * @member {number} + */ + get backgroundColor() + { + return this._backgroundColor; + } + + set backgroundColor(value) // eslint-disable-line require-jsdoc + { + this._backgroundColor = value; + this._backgroundColorString = hex2string(value); + hex2rgb(value, this._backgroundColorRgba); + } +} diff --git a/packages/core/src/Renderer.js b/packages/core/src/Renderer.js new file mode 100644 index 0000000..2227e78 --- /dev/null +++ b/packages/core/src/Renderer.js @@ -0,0 +1,338 @@ +import AbstractRenderer from './AbstractRenderer'; +import { sayHello } from '@pixi/utils'; +import MaskSystem from './mask/MaskSystem'; +import StencilSystem from './mask/StencilSystem'; +import FilterSystem from './filters/FilterSystem'; +import FramebufferSystem from './framebuffer/FramebufferSystem'; +import RenderTextureSystem from './renderTexture/RenderTextureSystem'; +import TextureSystem from './textures/TextureSystem'; +import ProjectionSystem from './projection/ProjectionSystem'; +import StateSystem from './state/StateSystem'; +import GeometrySystem from './geometry/GeometrySystem'; +import ShaderSystem from './shader/ShaderSystem'; +import ContextSystem from './context/ContextSystem'; +import BatchSystem from './batch/BatchSystem'; +import TextureGCSystem from './textures/TextureGCSystem'; +import { RENDERER_TYPE } from '@pixi/constants'; +import UniformGroup from './shader/UniformGroup'; +import { Matrix } from '@pixi/math'; +import Runner from 'mini-runner'; + +/** + * 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 Render works by automatically managing webGLBatchs. + * 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 :) + * + * @class + * @memberof PIXI + * @extends PIXI.AbstractRenderer + */ +export default class Renderer extends AbstractRenderer +{ + // eslint-disable-next-line valid-jsdoc + /** + * + * @param {object} [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.transparent=false] - If the render view is transparent, default false + * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false + * @param {boolean} [options.antialias=false] - sets antialias. If not available natively then FXAA + * antialiasing is used + * @param {boolean} [options.forceFXAA=false] - forces FXAA antialiasing to be used over native. + * FXAA is faster, but may not always look as great + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. + * The resolution of the renderer retina would be 2. + * @param {boolean} [options.clearBeforeRender=true] - This sets if the renderer will clear + * the canvas or not before the new render pass. If you wish to set this to false, you *must* set + * preserveDrawingBuffer to `true`. + * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, + * enable this if you need to call toDataUrl on the webgl context. + * @param {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when + * rendering, stopping pixel interpolation. + * @param {number} [options.backgroundColor=0x000000] - The background color of the rendered area + * (shown if not transparent). + * @param {boolean} [options.legacy=false] - If true PixiJS will aim to ensure compatibility + * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. + * @param {string} [options.powerPreference] - Parameter passed to webgl context, set to "high-performance" + * for devices with dual graphics card + */ + constructor(options, arg2, arg3) + { + super('WebGL', options, arg2, arg3); + + /** + * The type of this renderer as a standardised const + * + * @member {number} + * @see PIXI.RENDERER_TYPE + */ + this.type = RENDERER_TYPE.WEBGL; + + // this will be set by the contextSystem (this.context) + this.gl = null; + this.CONTEXT_UID = 0; + this.legacy = !!options.legacy; + + // TODO legacy! + + // runners! + this.runners = { + destroy: new Runner('destroy'), + contextChange: new Runner('contextChange', 1), + reset: new Runner('reset'), + update: new Runner('update'), + postrender: new Runner('postrender'), + prerender: new Runner('prerender'), + resize: new Runner('resize', 2), + }; + + 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(GeometrySystem, 'geometry') + .addSystem(FramebufferSystem, 'framebuffer') + .addSystem(StencilSystem, 'stencil') + .addSystem(ProjectionSystem, 'projection') + .addSystem(TextureGCSystem, 'textureGC') + .addSystem(FilterSystem, 'filter') + .addSystem(RenderTextureSystem, 'renderTexture') + .addSystem(BatchSystem, 'batch'); + + this.initPlugins(Renderer.__plugins); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ + if (options.context) + { + this.context.initFromContext(options.context); + } + else + { + this.context.initFromOptions({ + alpha: this.transparent, + antialias: options.antialias, + premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', + stencil: true, + 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); + } + + /** + * Add a new system to the renderer. + * @param {class} ClassRef - Class reference + * @param {string} [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. + * @return {PIXI.Renderer} Return instance of renderer + */ + addSystem(ClassRef, name) + { + if (!name) + { + name = ClassRef.name; + } + + // TODO - read name from class.name.. + + /* + if(name.includes('System')) + { + name = name.replace('System', ''); + name = name.charAt(0).toLowerCase() + name.slice(1); + } + */ + + const system = new ClassRef(this); + + if (this[name]) + { + throw new Error(`Whoops! ${name} is already a manger`); + } + + this[name] = system; + + for (const i in this.runners) + { + this.runners[i].add(system); + } + + return this; + + /** + * 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. + */ + } + + /** + * Renders the object to its webGL view + * + * @param {PIXI.DisplayObject} displayObject - the object to be rendered + * @param {PIXI.RenderTexture} renderTexture - The render texture to render to. + * @param {boolean} [clear] - Should the canvas be cleared before the new render + * @param {PIXI.Transform} [transform] - A transform to apply to the render texture before rendering. + * @param {boolean} [skipUpdateTransform] - Should we skip the update transform pass? + */ + render(displayObject, renderTexture, clear, transform, skipUpdateTransform) + { + // can be handy to know! + this.renderingToScreen = !renderTexture; + + this.runners.prerender.run(); + this.emit('prerender'); + + // 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.parent; + + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = 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.run(); + + this.emit('postrender'); + } + + /** + * Resizes the webGL view to the specified width and height. + * + * @param {number} screenWidth - the new width of the screen + * @param {number} screenHeight - the new height of the screen + */ + resize(screenWidth, screenHeight) + { + AbstractRenderer.prototype.resize.call(this, screenWidth, screenHeight); + + this.runners.resize.run(screenWidth, screenHeight); + } + + /** + * Resets the WebGL state so you can render things however you fancy! + * + * @return {PIXI.Renderer} Returns itself. + */ + reset() + { + this.runners.reset.run(); + + return this; + } + + clear() + { + this.framebuffer.bind(); + this.framebuffer.clear(); + } + + /** + * Removes everything from the renderer (event listeners, spritebatch, etc...) + * + * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. + * See: https://github.com/pixijs/pixi.js/issues/2233 + */ + destroy(removeView) + { + this.runners.destroy.run(); + + // call base destroy + super.destroy(removeView); + + // TODO nullify all the managers.. + this.gl = null; + } + + /** + * 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 + * builds and excluding plugins. + * @name PIXI.Renderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. + */ + + /** + * Adds a plugin to the renderer. + * + * @method + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + static registerPlugin(pluginName, ctor) + { + Renderer.__plugins = Renderer.__plugins || {}; + Renderer.__plugins[pluginName] = ctor; + } +} diff --git a/packages/core/src/System.js b/packages/core/src/System.js new file mode 100644 index 0000000..05a10c8 --- /dev/null +++ b/packages/core/src/System.js @@ -0,0 +1,42 @@ +/** + * System is a base class used for extending systems used by the {@link PIXI.Renderer} + * @see PIXI.Renderer#addSystem + * @class + * @memberof PIXI + */ +export default class System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + /** + * The renderer this manager works for. + * + * @member {PIXI.Renderer} + */ + this.renderer = renderer; + + this.renderer.runners.contextChange.add(this); + } + + /** + * Generic method called when there is a WebGL context change. + * + */ + contextChange() + { + // do some codes init! + } + + /** + * Generic destroy methods to be overridden by the subclass + * + */ + destroy() + { + this.renderer.runners.contextChange.remove(this); + this.renderer = null; + } +} diff --git a/packages/core/src/batch/BatchSystem.js b/packages/core/src/batch/BatchSystem.js new file mode 100644 index 0000000..b04c249 --- /dev/null +++ b/packages/core/src/batch/BatchSystem.js @@ -0,0 +1,68 @@ +import System from '../System'; +import ObjectRenderer from './ObjectRenderer'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class BatchSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + super(renderer); + + /** + * An empty renderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.emptyRenderer = new ObjectRenderer(renderer); + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.currentRenderer = this.emptyRenderer; + } + + /** + * Changes the current renderer to the one given in parameter + * + * @param {PIXI.ObjectRenderer} objectRenderer - The object renderer to use. + */ + setObjectRenderer(objectRenderer) + { + if (this.currentRenderer === objectRenderer) + { + return; + } + + this.currentRenderer.stop(); + this.currentRenderer = objectRenderer; + + this.renderer.state.setState(objectRenderer.state); + + this.currentRenderer.start(); + } + + /** + * This should be called if you wish to do some custom rendering + * It will basically render anything that may be batched up such as sprites + * + */ + flush() + { + this.setObjectRenderer(this.emptyRenderer); + } + + reset() + { + this.setObjectRenderer(this.emptyRenderer); + } +} diff --git a/packages/core/src/batch/ObjectRenderer.js b/packages/core/src/batch/ObjectRenderer.js new file mode 100644 index 0000000..0c851a9 --- /dev/null +++ b/packages/core/src/batch/ObjectRenderer.js @@ -0,0 +1,48 @@ +import System from '../System'; + +/** + * Base for a common object renderer that can be used as a system renderer plugin. + * + * @class + * @extends PIXI.System + * @memberof PIXI + */ +export default class ObjectRenderer extends System +{ + /** + * Starts the renderer and sets the shader + * + */ + start() + { + // set the shader.. + } + + /** + * Stops the renderer + * + */ + stop() + { + this.flush(); + } + + /** + * Stub method for rendering content and emptying the current batch. + * + */ + flush() + { + // flush! + } + + /** + * Renders an object + * + * @param {PIXI.DisplayObject} object - The object to render. + */ + render(object) // eslint-disable-line no-unused-vars + { + // render the object + } +} diff --git a/packages/core/src/context/ContextSystem.js b/packages/core/src/context/ContextSystem.js new file mode 100644 index 0000000..301aab4 --- /dev/null +++ b/packages/core/src/context/ContextSystem.js @@ -0,0 +1,186 @@ +import System from '../System'; +import { settings } from '@pixi/settings'; + +let CONTEXT_UID = 0; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class ContextSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.webGLVersion = 1; + + this.handleContextLost = this.handleContextLost.bind(this); + this.handleContextRestored = this.handleContextRestored.bind(this); + + this.extensions = {}; + + renderer.view.addEventListener('webglcontextlost', this.handleContextLost, false); + renderer.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + } + + get isLost() + { + return (!this.gl || this.gl.isContextLost()); + } + + contextChange(gl) + { + this.gl = gl; + + // restore a context if it was previously lost + if (gl.isContextLost() && gl.getExtension('WEBGL_lose_context')) + { + gl.getExtension('WEBGL_lose_context').restoreContext(); + } + } + + initFromContext(gl) + { + this.gl = gl; + this.validateContext(gl); + this.renderer.gl = gl; + this.renderer.CONTEXT_UID = CONTEXT_UID++; + this.renderer.runners.contextChange.run(gl); + } + + initFromOptions(options) + { + const gl = this.createContext(this.renderer.view, options); + + this.initFromContext(gl); + } + + /** + * Helper class to create a webGL Context + * + * @class + * @memberof PIXI.glCore + * @param canvas {HTMLCanvasElement} the canvas element that we will get the context from + * @param options {Object} An options object that gets passed in to the canvas element containing the context attributes, + * see https://developer.mozilla.org/en/docs/Web/API/HTMLCanvasElement/getContext for the options available + * @return {WebGLRenderingContext} the WebGL context + */ + createContext(canvas, options) + { + let gl; + + if (settings.PREFER_WEBGL_2) + { + gl = canvas.getContext('webgl2', options); + } + + if (gl) + { + this.webGLVersion = 2; + } + else + { + this.webGLVersion = 1; + + gl = canvas.getContext('webgl', options) + || canvas.getContext('experimental-webgl', options); + + if (!gl) + { + // fail, not able to get a context + throw new Error('This browser does not support webGL. Try using the canvas renderer'); + } + } + + this.gl = gl; + + this.getExtensions(); + + return gl; + } + + getExtensions() + { + // time to set up default etensions that pixi uses.. + const gl = this.gl; + const extensions = this.extensions; + + if (this.webGLVersion === 1) + { + extensions.drawBuffers = gl.getExtension('WEBGL_draw_buffers'); + extensions.depthTexture = gl.getExtension('WEBKIT_WEBGL_depth_texture'); + extensions.floatTexture = gl.getExtension('OES_texture_float'); + extensions.loseContext = gl.getExtension('WEBGL_lose_context'); + + extensions.vertexArrayObject = gl.getExtension('OES_vertex_array_object') + || gl.getExtension('MOZ_OES_vertex_array_object') + || gl.getExtension('WEBKIT_OES_vertex_array_object'); + } + + // we don't use any specific WebGL 2 ones yet! + } + + /** + * Handles a lost webgl context + * + * @private + * @param {WebGLContextEvent} event - The context lost event. + */ + handleContextLost(event) + { + event.preventDefault(); + } + + /** + * Handles a restored webgl context + * + * @private + */ + handleContextRestored() + { + this.renderer.runners.contextChange.run(this.gl); + } + + destroy() + { + const view = this.renderer.view; + + // remove listeners + view.removeEventListener('webglcontextlost', this.handleContextLost); + view.removeEventListener('webglcontextrestored', this.handleContextRestored); + + this.gl.useProgram(null); + + if (this.extensions.loseContext) + { + this.extensions.loseContext.loseContext(); + } + } + + postrender() + { + this.gl.flush(); + } + + validateContext(gl) + { + const attributes = gl.getContextAttributes(); + + // this is going to be fairly simple for now.. but at least we have room to grow! + if (!attributes.stencil) + { + /* eslint-disable max-len */ + + /* eslint-disable no-console */ + console.warn('Provided WebGL context does not have a stencil buffer, masks may not render correctly'); + /* eslint-enable no-console */ + + /* eslint-enable max-len */ + } + } +} diff --git a/packages/core/src/filters/Filter.js b/packages/core/src/filters/Filter.js new file mode 100644 index 0000000..9d8cad8 --- /dev/null +++ b/packages/core/src/filters/Filter.js @@ -0,0 +1,140 @@ +import Shader from '../shader/Shader'; +import Program from '../shader/Program'; +import State from '../state/State'; +import { settings } from '@pixi/settings'; +// import extractUniformsFromSrc from './extractUniformsFromSrc'; +import defaultVertex from './defaultFilter.vert'; +import defaultFragment from './defaultFilter.frag'; + +// let math = require('../../math'); +/** + * @class + * @memberof PIXI + * @extends PIXI.Shader + */ +export default class Filter extends Shader +{ + /** + * @param {string} [vertexSrc] - The source of the vertex shader. + * @param {string} [fragmentSrc] - The source of the fragment shader. + * @param {object} [uniforms] - Custom uniforms to use to augment the built-in ones. + */ + constructor(vertexSrc, fragmentSrc, uniforms) + { + const program = Program.from(vertexSrc, fragmentSrc); + + super(program, uniforms); + + /** + * The padding of the filter. Some filters require extra space to breath such as a blur. + * Increasing this will add extra width and height to the bounds of the object that the + * filter is applied to. + * + * @member {number} + */ + this.padding = 0; + + /** + * The resolution of the filter. Setting this to be lower will lower the quality but + * increase the performance of the filter. + * + * @member {number} + */ + this.resolution = settings.RESOLUTION; + + /** + * If enabled is true the filter is applied, if false it will not. + * + * @member {boolean} + */ + this.enabled = true; + + /** + * If enabled, PixiJS will fit the filter area into boundaries for better performance. + * Switch it off if it does not work for specific shader. + * + * @member {boolean} + */ + this.autoFit = true; + + /** + * Legacy filters use position and uvs from attributes + * @member {boolean} + * @readonly + */ + this.legacy = !!this.program.attributeData.aTextureCoord; + + /** + * the webGL state the filter requires to render + * @member {PIXI.State} + */ + this.state = new State(); + } + + /** + * Applies the filter + * + * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + * @param {boolean} clear - Should the output be cleared before rendering to it + * @param {object} [currentState] - It's current state of filter. + * There are some useful properties in the currentState : + * target, filters, sourceFrame, destinationFrame, renderTarget, resolution + */ + apply(filterManager, input, output, clear, currentState, derp) // eslint-disable-line no-unused-vars + { + // do as you please! + + filterManager.applyFilter(this, input, output, clear, currentState, derp); + + // or just do a regular render.. + } + + /** + * Sets the blendmode of the filter + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL + */ + get blendMode() + { + return this.state.blendMode; + } + + set blendMode(value) // eslint-disable-line require-jsdoc + { + this.state.blendMode = value; + } + + /** + * The default vertex shader source + * + * @static + * @constant + */ + static get defaultVertexSrc() + { + return defaultVertex; + } + + /** + * The default fragment shader source + * + * @static + * @constant + */ + static get defaultFragmentSrc() + { + return defaultFragment; + } +} + +/** + * Used for caching shader IDs + * + * @static + * @private + */ +Filter.SOURCE_KEY_MAP = {}; + diff --git a/packages/core/src/filters/FilterSystem.js b/packages/core/src/filters/FilterSystem.js new file mode 100644 index 0000000..3174318 --- /dev/null +++ b/packages/core/src/filters/FilterSystem.js @@ -0,0 +1,431 @@ +import System from '../System'; + +import RenderTexture from '../renderTexture/RenderTexture'; +import Quad from '../utils/Quad'; +import QuadUv from '../utils/QuadUv'; +import { Rectangle } from '@pixi/math'; +import * as filterTransforms from './filterTransforms'; +import bitTwiddle from 'bit-twiddle'; +import UniformGroup from '../shader/UniformGroup'; +import { DRAW_MODES } from '@pixi/constants'; + +// +/** + * @ignore + * @class + */ +class FilterState +{ + /** + * + */ + constructor() + { + this.renderTexture = null; + this.sourceFrame = new Rectangle(); + this.destinationFrame = new Rectangle(); + this.filters = []; + this.target = null; + this.legacy = false; + this.resolution = 1; + } +} + +/** + * @class + * @memberof PIXI.systems + * @extends PIXI.System + */ +export default class FilterSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + /** + * stores a bunch of PO2 textures used for filtering + * @type {Object} + */ + this.texturePool = {}; + + /** + * a pool for storing filter states, save us creating new ones each tick + * @type {Array} + */ + this.statePool = []; + + /** + * A very simple geometry used when drawing a filter effect to the screen + * @type {Quad} + */ + this.quad = new Quad(); + + this.quadUv = new QuadUv(); + + /** + * Temporary rect for maths + * @type {PIXI.Rectangle} + */ + this.tempRect = new Rectangle(); + + this.activeState = {}; + + /** + * this uniform group is attached to filter uniforms when used + * @type {UniformGroup} + */ + this.globalUniforms = new UniformGroup({ + sourceFrame: this.tempRect, + destinationFrame: this.tempRect, + + // legacy variables + filterArea: new Float32Array(4), + filterClamp: new Float32Array(4), + }, true); + } + + /** + * Adds a new filter to the System. + * + * @param {PIXI.DisplayObject} target - The target of the filter to render. + * @param {PIXI.Filter[]} filters - The filters to apply. + */ + push(target, filters) + { + const renderer = this.renderer; + const filterStack = this.renderer.renderTexture.defaultFilterStack; + const state = this.statePool.pop() || new FilterState(); + + let resolution = filters[0].resolution; + let padding = filters[0].padding; + let autoFit = filters[0].autoFit; + let legacy = filters[0].legacy; + + for (let i = 1; i < filters.length; i++) + { + const filter = filters[i]; + + // lets use the lowest resolution.. + resolution = Math.min(resolution, filter.resolution); + // and the largest amount of padding! + padding = Math.max(padding, filter.padding); + // only auto fit if all filters are autofit + autoFit = autoFit || filter.autoFit; + + legacy = legacy || filter.legacy; + } + + filterStack.push(state); + + state.resolution = resolution; + + state.legacy = legacy; + + // round to whole number based on resolution + // TODO move that to the shader too? + state.sourceFrame = target.filterArea || target.getBounds(true); + + state.sourceFrame.pad(padding); + + if (autoFit) + { + state.sourceFrame.fit(this.renderer.renderTexture.destinationFrame); + } + + state.sourceFrame.round(resolution); + + state.renderTexture = this.getPotFilterTexture(state.sourceFrame.width, state.sourceFrame.height, resolution); + state.filters = filters; + + state.destinationFrame.width = state.renderTexture.width; + state.destinationFrame.height = state.renderTexture.height; + + state.renderTexture.filterFrame = state.sourceFrame; + renderer.renderTexture.bind(state.renderTexture, state.sourceFrame);// /, state.destinationFrame); + renderer.renderTexture.clear(); + } + + /** + * Pops off the filter and applies it. + * + */ + pop() + { + const renderer = this.renderer; + const filterStack = renderer.renderTexture.defaultFilterStack; + const state = filterStack.pop(); + const filters = state.filters; + + this.activeState = state; + + const globalUniforms = this.globalUniforms.uniforms; + + globalUniforms.sourceFrame = state.sourceFrame; + globalUniforms.destinationFrame = state.destinationFrame; + globalUniforms.resolution = state.resolution; + + // only update the rect if its legacy.. + if (state.legacy) + { + const filterArea = globalUniforms.filterArea; + const filterClamp = globalUniforms.filterClamp; + + filterArea[0] = state.destinationFrame.width; + filterArea[1] = state.destinationFrame.height; + filterArea[2] = state.sourceFrame.x; + filterArea[3] = state.sourceFrame.y; + + filterClamp[0] = 0.5 / state.resolution / state.destinationFrame.width; + filterClamp[1] = 0.5 / state.resolution / state.destinationFrame.height; + filterClamp[2] = (state.sourceFrame.width - 0.5) / state.resolution / state.destinationFrame.width; + filterClamp[3] = (state.sourceFrame.height - 0.5) / state.resolution / state.destinationFrame.height; + } + + this.globalUniforms.update(); + + const lastState = filterStack[filterStack.length - 1]; + + if (filters.length === 1) + { + filters[0].apply(this, state.renderTexture, lastState.renderTexture, false, state); + + this.returnFilterTexture(state.renderTexture); + } + else + { + let flip = state.renderTexture; + let flop = this.getPotFilterTexture( + flip.width, + flip.height, + state.resolution + ); + + flop.filterFrame = flip.filterFrame; + + let i = 0; + + for (i = 0; i < filters.length - 1; ++i) + { + filters[i].apply(this, flip, flop, true, state); + + const t = flip; + + flip = flop; + flop = t; + } + + filters[i].apply(this, flip, lastState.renderTexture, false, state); + + this.returnFilterTexture(flip); + this.returnFilterTexture(flop); + } + + this.statePool.push(state); + } + + /** + * Draws a filter. + * + * @param {PIXI.Filter} filter - The filter to draw. + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + * @param {boolean} clear - Should the output be cleared before rendering to it + */ + applyFilter(filter, input, output, clear) + { + const renderer = this.renderer; + + renderer.renderTexture.bind(output, output ? output.filterFrame : null); + + if (clear) + { + // gl.disable(gl.SCISSOR_TEST); + renderer.renderTexture.clear(); + // gl.enable(gl.SCISSOR_TEST); + } + + // set the uniforms.. + filter.uniforms.uSampler = input; + filter.uniforms.filterGlobals = this.globalUniforms; + + // TODO make it so that the order of this does not matter.. + // because it does at the moment cos of global uniforms. + // they need to get resynced + + renderer.state.setState(filter.state); + renderer.shader.bind(filter); + + if (filter.legacy) + { + this.quadUv.map(input._frame, input.filterFrame); + + renderer.geometry.bind(this.quadUv); + renderer.geometry.draw(DRAW_MODES.TRIANGLES); + } + else + { + renderer.geometry.bind(this.quad); + renderer.geometry.draw(DRAW_MODES.TRIANGLE_STRIP); + } + } + + /** + * Calculates the mapped matrix. + * + * TODO playing around here.. this is temporary - (will end up in the shader) + * this returns a matrix that will normalise map filter cords in the filter to screen space + * + * @param {PIXI.Matrix} outputMatrix - the matrix to output to. + * @return {PIXI.Matrix} The mapped matrix. + */ + calculateScreenSpaceMatrix(outputMatrix) + { + const currentState = this.activeState; + + return filterTransforms.calculateScreenSpaceMatrix( + outputMatrix, + currentState.sourceFrame, + currentState.destinationFrame + ); + } + + /** + * This will map the filter coord so that a texture can be used based on the transform of a sprite + * + * @param {PIXI.Matrix} outputMatrix - The matrix to output to. + * @param {PIXI.Sprite} sprite - The sprite to map to. + * @return {PIXI.Matrix} The mapped matrix. + */ + calculateSpriteMatrix(outputMatrix, sprite) + { + const currentState = this.activeState; + + return filterTransforms.calculateSpriteMatrix( + outputMatrix, + currentState.sourceFrame, + currentState.destinationFrame, + sprite + ); + } + + /** + * Destroys this Filter System. + * + * @param {boolean} [contextLost=false] context was lost, do not free shaders + * + */ + destroy(contextLost = false) + { + if (!contextLost) + { + this.emptyPool(); + } + else + { + this.texturePool = {}; + } + } + + /** + * Gets a Power-of-Two render texture. + * + * TODO move to a seperate class could be on renderer? + * also - could cause issue with multiple contexts? + * + * @private + * @param {WebGLRenderingContext} gl - The webgl rendering context + * @param {number} minWidth - The minimum width of the render target. + * @param {number} minHeight - The minimum height of the render target. + * @param {number} resolution - The resolution of the render target. + * @return {PIXI.RenderTarget} The new render target. + */ + getPotFilterTexture(minWidth, minHeight, resolution) + { + minWidth = bitTwiddle.nextPow2(minWidth); + minHeight = bitTwiddle.nextPow2(minHeight); + resolution = resolution || 1; + + const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + + if (!this.texturePool[key]) + { + this.texturePool[key] = []; + } + + let renderTexture = this.texturePool[key].pop(); + + if (!renderTexture) + { + // temporary bypass cache.. + // internally - this will cause a texture to be bound.. + renderTexture = RenderTexture.create({ + width: minWidth, + height: minHeight, + resolution, + }); + } + + return renderTexture; + } + + /** + * Gets extra render texture to use inside current filter + * + * @param {number} resolution resolution of the renderTexture + * @returns {PIXI.RenderTexture} + */ + getFilterTexture(resolution) + { + const rt = this.activeState.renderTexture; + + const filterTexture = this.getPotFilterTexture(rt.width, rt.height, resolution || rt.baseTexture.resolution); + + filterTexture.filterFrame = rt.filterFrame; + + return filterTexture; + } + + /** + * Frees a render texture back into the pool. + * + * @param {PIXI.RenderTarget} renderTexture - The renderTarget to free + */ + returnFilterTexture(renderTexture) + { + renderTexture.filterFrame = null; + + const base = renderTexture.baseTexture; + + const minWidth = base.width; + const minHeight = base.height; + + const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + + this.texturePool[key].push(renderTexture); + } + + /** + * Empties the texture pool. + * + */ + emptyPool() + { + for (const i in this.texturePool) + { + const textures = this.texturePool[i]; + + if (textures) + { + for (let j = 0; j < textures.length; j++) + { + textures[j].destroy(true); + } + } + } + + this.texturePool = {}; + } +} diff --git a/packages/core/src/filters/defaultFilter.frag b/packages/core/src/filters/defaultFilter.frag new file mode 100644 index 0000000..a14c1be --- /dev/null +++ b/packages/core/src/filters/defaultFilter.frag @@ -0,0 +1,22 @@ +varying vec2 vTextureCoord; +varying vec2 vFilterCoord; + +uniform sampler2D uSampler; +uniform sampler2D filterSampler; + +void main(void){ + vec4 masky = texture2D(filterSampler, vFilterCoord); + vec4 sample = texture2D(uSampler, vTextureCoord); + vec4 color; + if(mod(vFilterCoord.x, 1.0) > 0.5) + { + color = vec4(1.0, 0.0, 0.0, 1.0); + } + else + { + color = vec4(0.0, 1.0, 0.0, 1.0); + } + // gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0); + gl_FragColor = mix(sample, masky, 0.5); + gl_FragColor *= sample.a; +} diff --git a/packages/core/src/filters/defaultFilter.vert b/packages/core/src/filters/defaultFilter.vert new file mode 100644 index 0000000..e1febca --- /dev/null +++ b/packages/core/src/filters/defaultFilter.vert @@ -0,0 +1,14 @@ +attribute vec2 aVertexPosition; +attribute vec2 aTextureCoord; + +uniform mat3 projectionMatrix; +uniform mat3 filterMatrix; + +varying vec2 vTextureCoord; +varying vec2 vFilterCoord; + +void main(void){ + gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); + vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy; + vTextureCoord = aTextureCoord ; +} diff --git a/packages/core/src/filters/filterTransforms.js b/packages/core/src/filters/filterTransforms.js new file mode 100644 index 0000000..87a3cd6 --- /dev/null +++ b/packages/core/src/filters/filterTransforms.js @@ -0,0 +1,50 @@ +import { Matrix } from '@pixi/math'; + +/** + * Calculates the mapped matrix + * @param filterArea {Rectangle} The filter area + * @param sprite {Sprite} the target sprite + * @param outputMatrix {Matrix} @alvin + * @private + */ +// this returns a matrix that will normalise map filter cords in the filter to screen space +export function calculateScreenSpaceMatrix(outputMatrix, filterArea, textureSize) +{ + // TODO unwrap? + const mappedMatrix = outputMatrix.identity(); + + mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); + + mappedMatrix.scale(textureSize.width, textureSize.height); + + return mappedMatrix; +} + +export function calculateNormalizedScreenSpaceMatrix(outputMatrix, filterArea, textureSize) +{ + const mappedMatrix = outputMatrix.identity(); + + mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); + + const translateScaleX = (textureSize.width / filterArea.width); + const translateScaleY = (textureSize.height / filterArea.height); + + mappedMatrix.scale(translateScaleX, translateScaleY); + + return mappedMatrix; +} + +// this will map the filter coord so that a texture can be used based on the transform of a sprite +export function calculateSpriteMatrix(outputMatrix, filterArea, textureSize, sprite) +{ + const orig = sprite._texture.orig; + const mappedMatrix = outputMatrix.set(textureSize.width, 0, 0, textureSize.height, filterArea.x, filterArea.y); + const worldTransform = sprite.worldTransform.copyTo(Matrix.TEMP_MATRIX); + + worldTransform.invert(); + mappedMatrix.prepend(worldTransform); + mappedMatrix.scale(1.0 / orig.width, 1.0 / orig.height); + mappedMatrix.translate(sprite.anchor.x, sprite.anchor.y); + + return mappedMatrix; +} diff --git a/packages/core/src/filters/spriteMask/SpriteMaskFilter.js b/packages/core/src/filters/spriteMask/SpriteMaskFilter.js new file mode 100644 index 0000000..91697b7 --- /dev/null +++ b/packages/core/src/filters/spriteMask/SpriteMaskFilter.js @@ -0,0 +1,63 @@ +import Filter from '../Filter'; +import { Matrix } from '@pixi/math'; +import vertex from './spriteMaskFilter.vert'; +import fragment from './spriteMaskFilter.frag'; +import { default as TextureMatrix } from '../../textures/TextureMatrix'; + +/** + * The SpriteMaskFilter class + * + * @class + * @extends PIXI.Filter + * @memberof PIXI + */ +export default class SpriteMaskFilter extends Filter +{ + /** + * @param {PIXI.Sprite} sprite - the target sprite + */ + constructor(sprite) + { + const maskMatrix = new Matrix(); + + super(vertex, fragment); + + sprite.renderable = false; + + this.maskSprite = sprite; + this.maskMatrix = maskMatrix; + } + + /** + * Applies the filter + * + * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + */ + apply(filterManager, input, output) + { + const maskSprite = this.maskSprite; + const tex = this.maskSprite.texture; + + if (!tex.valid) + { + return; + } + if (!tex.transform) + { + // margin = 0.0, let it bleed a bit, shader code becomes easier + // assuming that atlas textures were made with 1-pixel padding + tex.transform = new TextureMatrix(tex, 0.0); + } + tex.transform.update(); + + this.uniforms.mask = tex; + this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite) + .prepend(tex.transform.mapCoord); + this.uniforms.alpha = maskSprite.worldAlpha; + this.uniforms.maskClamp = tex.transform.uClampFrame; + + filterManager.applyFilter(this, input, output); + } +} diff --git a/packages/core/src/filters/spriteMask/spriteMaskFilter.frag b/packages/core/src/filters/spriteMask/spriteMaskFilter.frag new file mode 100644 index 0000000..0e4aef8 --- /dev/null +++ b/packages/core/src/filters/spriteMask/spriteMaskFilter.frag @@ -0,0 +1,23 @@ +varying vec2 vMaskCoord; +varying vec2 vTextureCoord; + +uniform sampler2D uSampler; +uniform sampler2D mask; +uniform float alpha; +uniform vec4 maskClamp; + +void main(void) +{ + float clip = step(3.5, + step(maskClamp.x, vMaskCoord.x) + + step(maskClamp.y, vMaskCoord.y) + + step(vMaskCoord.x, maskClamp.z) + + step(vMaskCoord.y, maskClamp.w)); + + vec4 original = texture2D(uSampler, vTextureCoord); + vec4 masky = texture2D(mask, vMaskCoord); + + original *= (masky.r * masky.a * alpha * clip); + + gl_FragColor = original; +} diff --git a/packages/core/src/filters/spriteMask/spriteMaskFilter.vert b/packages/core/src/filters/spriteMask/spriteMaskFilter.vert new file mode 100644 index 0000000..d23a30c --- /dev/null +++ b/packages/core/src/filters/spriteMask/spriteMaskFilter.vert @@ -0,0 +1,16 @@ +attribute vec2 aVertexPosition; +attribute vec2 aTextureCoord; + +uniform mat3 projectionMatrix; +uniform mat3 otherMatrix; + +varying vec2 vMaskCoord; +varying vec2 vTextureCoord; + +void main(void) +{ + gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); + + vTextureCoord = aTextureCoord; + vMaskCoord = ( otherMatrix * vec3( aTextureCoord, 1.0) ).xy; +} diff --git a/packages/core/src/framebuffer/FrameBuffer.js b/packages/core/src/framebuffer/FrameBuffer.js new file mode 100644 index 0000000..84e9fcc --- /dev/null +++ b/packages/core/src/framebuffer/FrameBuffer.js @@ -0,0 +1,106 @@ +import Texture from '../textures/BaseTexture'; +import { FORMATS, TYPES } from '@pixi/constants'; + +/** + * Frame buffer + * @class + * @memberof PIXI + */ +export default class FrameBuffer +{ + constructor(width, height) + { + this.width = width || 100; + this.height = height || 100; + + this.stencil = false; + this.depth = false; + + this.dirtyId = 0; + this.dirtyFormat = 0; + this.dirtySize = 0; + + this.depthTexture = null; + this.colorTextures = []; + + this.glFrameBuffers = {}; + } + + get colorTexture() + { + return this.colorTextures[0]; + } + + addColorTexture(index, texture) + { + // TODO add some validation to the texture - same width / height etc? + this.colorTextures[index || 0] = texture || new Texture(null, { scaleMode: 0, + resolution: 1, + mipmap: false, + width: this.width, + height: this.height });// || new Texture(); + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + addDepthTexture(texture) + { + /* eslint-disable max-len */ + this.depthTexture = texture || new Texture(null, { scaleMode: 0, + resolution: 1, + width: this.width, + height: this.height, + mipmap: false, + format: FORMATS.DEPTH_COMPONENT, + type: TYPES.UNSIGNED_SHORT });// UNSIGNED_SHORT; + /* eslint-disable max-len */ + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + enableDepth() + { + this.depth = true; + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + enableStencil() + { + this.stencil = true; + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + resize(width, height) + { + if (width === this.width && height === this.height) return; + + this.width = width; + this.height = height; + + this.dirtyId++; + this.dirtySize++; + + for (let i = 0; i < this.colorTextures.length; i++) + { + this.colorTextures[i].setSize(width, height); + } + + if (this.depthTexture) + { + this.depthTexture.setSize(width, height); + } + } +} diff --git a/packages/core/src/framebuffer/FramebufferSystem.js b/packages/core/src/framebuffer/FramebufferSystem.js new file mode 100644 index 0000000..2cc2437 --- /dev/null +++ b/packages/core/src/framebuffer/FramebufferSystem.js @@ -0,0 +1,246 @@ +import System from '../System'; +import { Rectangle } from '@pixi/math'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class FramebufferSystem extends System +{ + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + contextChange() + { + this.gl = this.renderer.gl; + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + this.current = null; + this.viewport = new Rectangle(); + + this.drawBufferExtension = this.renderer.context.extensions.drawBuffers; + } + + bind(framebuffer, frame) + { + const gl = this.gl; + + this.current = framebuffer; + + if (framebuffer) + { + // TODO cacheing layer! + + const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID] || this.initFramebuffer(framebuffer); + + gl.bindFramebuffer(gl.FRAMEBUFFER, fbo.framebuffer); + // makesure all textures are unbound.. + + // now check for updates... + if (fbo.dirtyId !== framebuffer.dirtyId) + { + fbo.dirtyId = framebuffer.dirtyId; + + if (fbo.dirtyFormat !== framebuffer.dirtyFormat) + { + fbo.dirtyFormat = framebuffer.dirtyFormat; + this.updateFramebuffer(framebuffer); + } + else if (fbo.dirtySize !== framebuffer.dirtySize) + { + fbo.dirtySize = framebuffer.dirtySize; + this.resizeFramebuffer(framebuffer); + } + } + + for (let i = 0; i < framebuffer.colorTextures.length; i++) + { + if (framebuffer.colorTextures[i].texturePart) + { + this.renderer.texture.unbind(framebuffer.colorTextures[i].texture); + } + else + { + this.renderer.texture.unbind(framebuffer.colorTextures[i]); + } + } + + if (framebuffer.depthTexture) + { + this.renderer.texture.unbind(framebuffer.depthTexture); + } + + if (frame) + { + this.setViewport(frame.x, frame.y, frame.width, frame.height); + } + else + { + this.setViewport(0, 0, framebuffer.width, framebuffer.height); + } + } + else + { + gl.bindFramebuffer(gl.FRAMEBUFFER, null); + + if (frame) + { + this.setViewport(frame.x, frame.y, frame.width, frame.height); + } + else + { + this.setViewport(0, 0, this.renderer.width, this.renderer.height); + } + } + } + + setViewport(x, y, width, height) + { + const v = this.viewport; + + if (v.width !== width || v.height !== height || v.x !== x || v.y !== y) + { + v.x = x; + v.y = y; + v.width = width; + v.height = height; + + this.gl.viewport(x, y, width, height); + } + } + + get size() + { + if (this.current) + { + // TODO store temp + return { x: 0, y: 0, width: this.current.width, height: this.current.height }; + } + + return { x: 0, y: 0, width: this.renderer.width, height: this.renderer.height }; + } + + clear(r, g, b, a) + { + const gl = this.gl; + + // TODO clear color can be set only one right? + gl.clearColor(r, g, b, a); + gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); + } + + // private functions... + + initFramebuffer(framebuffer) + { + const gl = this.gl; + + // TODO - make this a class? + const fbo = { + framebuffer: gl.createFramebuffer(), + stencil: null, + dirtyId: 0, + dirtyFormat: 0, + dirtySize: 0, + }; + + framebuffer.glFrameBuffers[this.CONTEXT_UID] = fbo; + + return fbo; + } + + resizeFramebuffer(framebuffer) + { + const gl = this.gl; + + if (framebuffer.stencil || framebuffer.depth) + { + gl.bindRenderbuffer(gl.RENDERBUFFER, this.stencil); + gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); + } + } + + updateFramebuffer(framebuffer) + { + const gl = this.gl; + + const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID]; + + // bind the color texture + const colorTextures = framebuffer.colorTextures; + + let count = colorTextures.length; + + if (!this.drawBufferExtension) + { + count = Math.min(count, 1); + } + + const activeTextures = []; + + for (let i = 0; i < count; i++) + { + const texture = framebuffer.colorTextures[i]; + + if (texture.texturePart) + { + this.renderer.texture.bind(texture.texture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.COLOR_ATTACHMENT0 + i, + gl.TEXTURE_CUBE_MAP_NEGATIVE_X + texture.side, + texture.texture._glTextures[this.CONTEXT_UID].texture, + 0); + } + else + { + this.renderer.texture.bind(texture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.COLOR_ATTACHMENT0 + i, + gl.TEXTURE_2D, + texture._glTextures[this.CONTEXT_UID].texture, + 0); + } + + activeTextures.push(gl.COLOR_ATTACHMENT0 + i); + } + + if (this.drawBufferExtension && activeTextures.length > 1) + { + this.drawBufferExtension.drawBuffersWEBGL(activeTextures); + } + + if (framebuffer.depthTexture) + { + const depthTextureExt = this.renderer.context.extensions.depthTexture; + + if (depthTextureExt) + { + const depthTexture = framebuffer.depthTexture; + + this.renderer.texture.bind(depthTexture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.DEPTH_ATTACHMENT, + gl.TEXTURE_2D, + depthTexture._glTextures[this.CONTEXT_UID].texture, + 0); + } + } + + if (framebuffer.stencil || framebuffer.depth) + { + fbo.stencil = gl.createRenderbuffer(); + + gl.bindRenderbuffer(gl.RENDERBUFFER, fbo.stencil); + + // TODO.. this is depth AND stencil? + gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, fbo.stencil); + gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); + // fbo.enableStencil(); + } + } +} diff --git a/packages/core/src/geometry/GLBuffer.js b/packages/core/src/geometry/GLBuffer.js new file mode 100644 index 0000000..0477e8a --- /dev/null +++ b/packages/core/src/geometry/GLBuffer.js @@ -0,0 +1,9 @@ +export default class GLBuffer +{ + constructor(buffer) + { + this.buffer = buffer; + this.updateID = -1; + this.byteLength = -1; + } +} diff --git a/packages/core/src/geometry/GeometrySystem.js b/packages/core/src/geometry/GeometrySystem.js new file mode 100644 index 0000000..959f827 --- /dev/null +++ b/packages/core/src/geometry/GeometrySystem.js @@ -0,0 +1,388 @@ +import System from '../System'; +import GLBuffer from './GLBuffer'; + +const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class GeometrySystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this._activeGeometry = null; + this._activeVao = null; + + this.hasVao = true; + this.hasInstance = true; + } + + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + contextChange() + { + const gl = this.gl = this.renderer.gl; + + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + + // webgl2 + if (!gl.createVertexArray) + { + // webgl 1! + let nativeVaoExtension = this.renderer.context.extensions.vertexArrayObject; + + if (this.renderer.legacy) + { + nativeVaoExtension = null; + } + + if (nativeVaoExtension) + { + gl.createVertexArray = () => + nativeVaoExtension.createVertexArrayOES(); + + gl.bindVertexArray = (vao) => + nativeVaoExtension.bindVertexArrayOES(vao); + + gl.deleteVertexArray = (vao) => + nativeVaoExtension.deleteVertexArrayOES(vao); + } + else + { + this.hasVao = false; + gl.createVertexArray = () => + { + // empty + }; + + gl.bindVertexArray = () => + { + // empty + }; + + gl.deleteVertexArray = () => + { + // empty + }; + } + } + + if (!gl.vertexAttribDivisor) + { + const instanceExt = gl.getExtension('ANGLE_instanced_arrays'); + + if (instanceExt) + { + gl.vertexAttribDivisor = (a, b) => + instanceExt.vertexAttribDivisorANGLE(a, b); + + gl.drawElementsInstanced = (a, b, c, d, e) => + instanceExt.drawElementsInstancedANGLE(a, b, c, d, e); + + gl.drawArraysInstanced = (a, b, c, d) => + instanceExt.drawArraysInstancedANGLE(a, b, c, d); + } + else + { + this.hasInstance = false; + } + } + } + + /** + * Binds geometry so that is can be drawn. Creating a Vao if required + * @private + * @param {PIXI.Geometry} geometry instance of geometry to bind + */ + bind(geometry, shader) + { + shader = shader || this.renderer.shader.shader; + + const gl = this.gl; + + // not sure the best way to address this.. + // currently different shaders require different VAOs for the same geometry + // Still mulling over the best way to solve this one.. + // will likely need to modify the shader attribute locations at run time! + let vaos = geometry.glVertexArrayObjects[this.CONTEXT_UID]; + + if (!vaos) + { + geometry.glVertexArrayObjects[this.CONTEXT_UID] = vaos = {}; + } + + const vao = vaos[shader.program.id] || this.initGeometryVao(geometry, shader.program); + + this._activeGeometry = geometry; + + if (this._activeVao !== vao) + { + this._activeVao = vao; + + if (this.hasVao) + { + gl.bindVertexArray(vao); + } + else + { + this.activateVao(geometry, shader.program); + } + } + + // TODO - optimise later! + // don't need to loop through if nothing changed! + // maybe look to add an 'autoupdate' to geometry? + this.updateBuffers(); + } + + reset() + { + this.unbind(); + } + + updateBuffers() + { + const geometry = this._activeGeometry; + const gl = this.gl; + + for (let i = 0; i < geometry.buffers.length; i++) + { + const buffer = geometry.buffers[i]; + + const glBuffer = buffer._glBuffers[this.CONTEXT_UID]; + + if (buffer._updateID !== glBuffer.updateID) + { + glBuffer.updateID = buffer._updateID; + + // TODO can cache this on buffer! maybe added a getter / setter? + const type = buffer.index ? gl.ELEMENT_ARRAY_BUFFER : gl.ARRAY_BUFFER; + const drawType = buffer.static ? gl.STATIC_DRAW : gl.DYNAMIC_DRAW; + + gl.bindBuffer(type, glBuffer.buffer); + + if (glBuffer.byteLength >= buffer.data.byteLength) + { + // offset is always zero for now! + gl.bufferSubData(type, 0, buffer.data); + } + else + { + gl.bufferData(type, buffer.data, drawType); + } + } + } + } + + checkCompatability(geometry, program) + { + // geometry must have at least all the attributes that the shader requires. + const geometryAttributes = geometry.attributes; + const shaderAttributes = program.attributeData; + + for (const j in shaderAttributes) + { + if (!geometryAttributes[j]) + { + throw new Error(`shader and geometry incompatible, geometry missing the "${j}" attribute`); + } + } + } + + /** + * Creates a Vao with the same structure as the geometry and stores it on the geometry. + * @private + * @param {PIXI.Geometry} geometry instance of geometry to to generate Vao for + */ + initGeometryVao(geometry, program) + { + this.checkCompatability(geometry, program); + + const gl = this.gl; + const CONTEXT_UID = this.CONTEXT_UID; + const buffers = geometry.buffers; + const attributes = geometry.attributes; + + const tempStride = {}; + const tempStart = {}; + + for (const j in buffers) + { + tempStride[j] = 0; + tempStart[j] = 0; + } + + for (const j in attributes) + { + if (!attributes[j].size && program.attributeData[j]) + { + attributes[j].size = program.attributeData[j].size; + } + + tempStride[attributes[j].buffer] += attributes[j].size * byteSizeMap[attributes[j].type]; + } + + for (const j in attributes) + { + const attribute = attributes[j]; + const attribSize = attribute.size; + + if (attribute.stride === undefined) + { + if (tempStride[attribute.buffer] === attribSize * byteSizeMap[attribute.type]) + { + attribute.stride = 0; + } + else + { + attribute.stride = tempStride[attribute.buffer]; + } + } + + if (attribute.start === undefined) + { + attribute.start = tempStart[attribute.buffer]; + + tempStart[attribute.buffer] += attribSize * byteSizeMap[attribute.type]; + } + } + + // first update - and create the buffers! + // only create a gl buffer if it actually gets + for (let i = 0; i < buffers.length; i++) + { + const buffer = buffers[i]; + + if (!buffer._glBuffers[CONTEXT_UID]) + { + buffer._glBuffers[CONTEXT_UID] = new GLBuffer(gl.createBuffer()); + } + } + + // TODO - maybe make this a data object? + // lets wait to see if we need to first! + const vao = gl.createVertexArray(); + + gl.bindVertexArray(vao); + + this.activateVao(geometry, program); + + gl.bindVertexArray(null); + + // add it to the cache! + geometry.glVertexArrayObjects[this.CONTEXT_UID][program.id] = vao; + + return vao; + } + + activateVao(geometry, program) + { + const gl = this.gl; + const CONTEXT_UID = this.CONTEXT_UID; + const buffers = geometry.buffers; + const attributes = geometry.attributes; + + if (geometry.indexBuffer) + { + // first update the index buffer if we have one.. + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, geometry.indexBuffer._glBuffers[CONTEXT_UID].buffer); + } + + let lastBuffer = null; + + // add a new one! + for (const j in attributes) + { + const attribute = attributes[j]; + const buffer = buffers[attribute.buffer]; + const glBuffer = buffer._glBuffers[CONTEXT_UID]; + + if (program.attributeData[j]) + { + if (lastBuffer !== glBuffer) + { + gl.bindBuffer(gl.ARRAY_BUFFER, glBuffer.buffer); + + lastBuffer = glBuffer; + } + + const location = program.attributeData[j].location; + + // TODO introduce state again + // we can optimise this for older devices that have no VAOs + gl.enableVertexAttribArray(location); + + gl.vertexAttribPointer(location, + attribute.size, + attribute.type || gl.FLOAT, + attribute.normalized, + attribute.stride, + attribute.start); + + if (attribute.instance) + { + // TODO calculate instance count based of this... + if (this.hasInstance) + { + gl.vertexAttribDivisor(location, 1); + } + else + { + throw new Error('geometry error, GPU Instancing is not supported on this device'); + } + } + } + } + } + + draw(type, size, start, instanceCount) + { + const gl = this.gl; + const geometry = this._activeGeometry; + + // TODO.. this should not change so maybe cache the function? + + if (geometry.indexBuffer) + { + if (geometry.instanced) + { + /* eslint-disable max-len */ + gl.drawElementsInstanced(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2, instanceCount || 1); + /* eslint-enable max-len */ + } + else + { + gl.drawElements(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2); + } + } + else + if (geometry.instanced) + { + // TODO need a better way to calculate size.. + gl.drawArraysInstanced(type, start, size || geometry.getSize(), instanceCount || 1); + } + else + { + gl.drawArrays(type, start, size || geometry.getSize()); + } + + return this; + } + + unbind() + { + this.gl.bindVertexArray(null); + this._activeVao = null; + this._activeGeometry = null; + } +} diff --git a/packages/core/src/geometry/utils/setVertexAttribArrays.js b/packages/core/src/geometry/utils/setVertexAttribArrays.js new file mode 100644 index 0000000..23b6959 --- /dev/null +++ b/packages/core/src/geometry/utils/setVertexAttribArrays.js @@ -0,0 +1,54 @@ +// var GL_MAP = {}; + +/** + * @param gl {WebGLRenderingContext} The current WebGL context + * @param attribs {*} + * @param state {*} + */ +export default function setVertexAttribArrays(gl, attribs, state) +{ + let i; + + if (state) + { + const tempAttribState = state.tempAttribState; + const attribState = state.attribState; + + for (i = 0; i < tempAttribState.length; i++) + { + tempAttribState[i] = false; + } + + // set the new attribs + for (i = 0; i < attribs.length; i++) + { + tempAttribState[attribs[i].attribute.location] = true; + } + + for (i = 0; i < attribState.length; i++) + { + if (attribState[i] !== tempAttribState[i]) + { + attribState[i] = tempAttribState[i]; + + if (state.attribState[i]) + { + gl.enableVertexAttribArray(i); + } + else + { + gl.disableVertexAttribArray(i); + } + } + } + } + else + { + for (i = 0; i < attribs.length; i++) + { + const attrib = attribs[i]; + + gl.enableVertexAttribArray(attrib.attribute.location); + } + } +} diff --git a/packages/core/src/index.js b/packages/core/src/index.js index 58c456a..fd1f547 100644 --- a/packages/core/src/index.js +++ b/packages/core/src/index.js @@ -1,30 +1,30 @@ import * as resources from './textures/resources'; -import * as systems from './renderers/systems'; +import * as systems from './systems'; -export { resources }; export { systems }; +export { resources }; -export { default as Renderer } from './renderers/Renderer'; -export { default as AbstractRenderer } from './renderers/AbstractRenderer'; -export { default as FrameBuffer } from './textures/FrameBuffer'; +export { default as System } from './System'; +export { default as Renderer } from './Renderer'; +export { default as AbstractRenderer } from './AbstractRenderer'; +export { default as FrameBuffer } from './framebuffer/FrameBuffer'; export { default as CubeTexture } from './textures/CubeTexture'; export { default as BaseTexture } from './textures/BaseTexture'; export { default as Texture } from './textures/Texture'; export { default as TextureMatrix } from './textures/TextureMatrix'; -export { default as RenderTexture } from './textures/RenderTexture'; -export { default as BaseRenderTexture } from './textures/BaseRenderTexture'; +export { default as RenderTexture } from './renderTexture/RenderTexture'; +export { default as BaseRenderTexture } from './renderTexture/BaseRenderTexture'; export { default as TextureUvs } from './textures/TextureUvs'; -export { default as State } from './renderers/State'; -export { default as ObjectRenderer } from './renderers/utils/ObjectRenderer'; -export { default as Quad } from './renderers/utils/Quad'; -export { default as QuadUv } from './renderers/utils/QuadUv'; -export { default as checkMaxIfStatmentsInShader } from './renderers/utils/checkMaxIfStatmentsInShader'; +export { default as State } from './state/State'; +export { default as ObjectRenderer } from './batch/ObjectRenderer'; +export { default as Quad } from './utils/Quad'; +export { default as QuadUv } from './utils/QuadUv'; +export { default as checkMaxIfStatmentsInShader } from './shader/utils/checkMaxIfStatmentsInShader'; export { default as Shader } from './shader/Shader'; export { default as Program } from './shader/Program'; export { default as UniformGroup } from './shader/UniformGroup'; -export { default as SpriteMaskFilter } from './renderers/filters/spriteMask/SpriteMaskFilter'; -export { default as Filter } from './renderers/filters/Filter'; +export { default as SpriteMaskFilter } from './filters/spriteMask/SpriteMaskFilter'; +export { default as Filter } from './filters/Filter'; export { default as Attribute } from './geometry/Attribute'; export { default as Buffer } from './geometry/Buffer'; export { default as Geometry } from './geometry/Geometry'; - diff --git a/packages/core/src/mask/MaskSystem.js b/packages/core/src/mask/MaskSystem.js new file mode 100644 index 0000000..99174c2 --- /dev/null +++ b/packages/core/src/mask/MaskSystem.js @@ -0,0 +1,199 @@ +import System from '../System'; +import AlphaMaskFilter from '../filters/spriteMask/SpriteMaskFilter'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class MaskSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + // TODO - we don't need both! + this.scissor = false; + this.scissorData = null; + this.scissorRenderTarget = null; + + this.enableScissor = false; + + this.alphaMaskPool = []; + this.alphaMaskIndex = 0; + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.DisplayObject} target - Display Object to push the mask to + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + push(target, maskData) + { + // TODO the root check means scissor rect will not + // be used on render textures more info here: + // https://github.com/pixijs/pixi.js/pull/3545 + + if (maskData.texture) + { + this.pushSpriteMask(target, maskData); + } + else if (this.enableScissor + && !this.scissor + && this.renderer._activeRenderTarget.root + && !this.renderer.stencil.stencilMaskStack.length + && maskData.isFastRect()) + { + const matrix = maskData.worldTransform; + + let rot = Math.atan2(matrix.b, matrix.a); + + // use the nearest degree! + rot = Math.round(rot * (180 / Math.PI)); + + if (rot % 90) + { + this.pushStencilMask(maskData); + } + else + { + this.pushScissorMask(target, maskData); + } + } + else + { + this.pushStencilMask(maskData); + } + } + + /** + * Removes the last mask from the mask stack and doesn't return it. + * + * @param {PIXI.DisplayObject} target - Display Object to pop the mask from + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + pop(target, maskData) + { + if (maskData.texture) + { + this.popSpriteMask(target, maskData); + } + else if (this.enableScissor && !this.renderer.stencil.stencilMaskStack.length) + { + this.popScissorMask(target, maskData); + } + else + { + this.popStencilMask(target, maskData); + } + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.RenderTarget} target - Display Object to push the sprite mask to + * @param {PIXI.Sprite} maskData - Sprite to be used as the mask + */ + pushSpriteMask(target, maskData) + { + let alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; + + if (!alphaMaskFilter) + { + alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; + } + + alphaMaskFilter[0].resolution = this.renderer.resolution; + alphaMaskFilter[0].maskSprite = maskData; + + // TODO - may cause issues! + target.filterArea = maskData.getBounds(true); + + this.renderer.filterSystem.pushFilter(target, alphaMaskFilter); + + this.alphaMaskIndex++; + } + + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popSpriteMask() + { + this.renderer.filterSystem.popFilter(); + this.alphaMaskIndex--; + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + pushStencilMask(maskData) + { + this.renderer.batch.flush(); + this.renderer.stencil.pushStencil(maskData); + } + + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popStencilMask() + { + // this.renderer.currentRenderer.stop(); + this.renderer.stencil.popStencil(); + } + + /** + * + * @param {PIXI.DisplayObject} target - Display Object to push the mask to + * @param {PIXI.Graphics} maskData - The masking data. + */ + pushScissorMask(target, maskData) + { + maskData.renderable = true; + + const renderTarget = this.renderer._activeRenderTarget; + + const bounds = maskData.getBounds(); + + bounds.fit(renderTarget.size); + maskData.renderable = false; + + this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); + + const resolution = this.renderer.resolution; + + this.renderer.gl.scissor( + bounds.x * resolution, + (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, + bounds.width * resolution, + bounds.height * resolution + ); + + this.scissorRenderTarget = renderTarget; + this.scissorData = maskData; + this.scissor = true; + } + + /** + * + * + */ + popScissorMask() + { + this.scissorRenderTarget = null; + this.scissorData = null; + this.scissor = false; + + // must be scissor! + const gl = this.renderer.gl; + + gl.disable(gl.SCISSOR_TEST); + } +} diff --git a/packages/core/src/mask/StencilSystem.js b/packages/core/src/mask/StencilSystem.js new file mode 100644 index 0000000..62796a5 --- /dev/null +++ b/packages/core/src/mask/StencilSystem.js @@ -0,0 +1,133 @@ +import System from '../System'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class StencilSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + this.stencilMaskStack = []; + } + + /** + * Changes the mask stack that is used by this System. + * + * @param {PIXI.Graphics[]} stencilMaskStack - The mask stack + */ + setMaskStack(stencilMaskStack) + { + const gl = this.renderer.gl; + + if (stencilMaskStack.length !== this.stencilMaskStack.length) + { + if (stencilMaskStack.length === 0) + { + gl.disable(gl.STENCIL_TEST); + } + else + { + gl.enable(gl.STENCIL_TEST); + } + } + + this.stencilMaskStack = stencilMaskStack; + } + + /** + * Applies the Mask and adds it to the current stencil stack. @alvin + * + * @param {PIXI.Graphics} graphics - The mask + */ + pushStencil(graphics) + { + this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); + + // this.renderer._activeRenderTarget.attachStencilBuffer(); + + const gl = this.renderer.gl; + const prevMaskCount = this.stencilMaskStack.length; + + if (prevMaskCount === 0) + { + gl.enable(gl.STENCIL_TEST); + } + + this.stencilMaskStack.push(graphics); + + // Increment the refference stencil value where the new mask overlaps with the old ones. + gl.colorMask(false, false, false, false); + gl.stencilFunc(gl.EQUAL, prevMaskCount, this._getBitwiseMask()); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.INCR); + this.renderer.plugins.graphics.render(graphics); + + this._useCurrent(); + } + + /** + * Removes the last mask from the stencil stack. @alvin + */ + popStencil() + { + this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); + + const gl = this.renderer.gl; + const graphics = this.stencilMaskStack.pop(); + + if (this.stencilMaskStack.length === 0) + { + // the stack is empty! + gl.disable(gl.STENCIL_TEST); + gl.clear(gl.STENCIL_BUFFER_BIT); + gl.clearStencil(0); + } + else + { + // Decrement the refference stencil value where the popped mask overlaps with the other ones + gl.colorMask(false, false, false, false); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.DECR); + this.renderer.plugins.graphics.render(graphics); + + this._useCurrent(); + } + } + + /** + * Setup renderer to use the current stencil data. + */ + _useCurrent() + { + const gl = this.renderer.gl; + + gl.colorMask(true, true, true, true); + gl.stencilFunc(gl.EQUAL, this.stencilMaskStack.length, this._getBitwiseMask()); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP); + } + + /** + * Fill 1s equal to the number of acitve stencil masks. + * + * @return {number} The bitwise mask. + */ + _getBitwiseMask() + { + return (1 << this.stencilMaskStack.length) - 1; + } + + /** + * Destroys the mask stack. + * + */ + destroy() + { + super.destroy(this); + + this.stencilMaskStack = null; + } +} diff --git a/packages/core/src/projection/ProjectionSystem.js b/packages/core/src/projection/ProjectionSystem.js new file mode 100644 index 0000000..6e63b73 --- /dev/null +++ b/packages/core/src/projection/ProjectionSystem.js @@ -0,0 +1,73 @@ +import System from '../System'; +import { Matrix } from '@pixi/math'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class ProjectionSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.projectionMatrix = new Matrix(); + } + + update(destinationFrame, sourceFrame, resolution, root) + { + this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; + this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; + + this.calculateProjection(this.destinationFrame, this.sourceFrame, resolution, root); + + this.renderer.globalUniforms.uniforms.projectionMatrix = this.projectionMatrix; + this.renderer.globalUniforms.update(); + } + + /** + * Updates the projection matrix based on a projection frame (which is a rectangle) + * + * @param {Rectangle} destinationFrame - The destination frame. + * @param {Rectangle} sourceFrame - The source frame. + */ + calculateProjection(destinationFrame, sourceFrame, resolution, root) + { + const pm = this.projectionMatrix; + + // I don't think we will need this line.. + // pm.identity(); + + if (!root) + { + pm.a = (1 / destinationFrame.width * 2) * resolution; + pm.d = (1 / destinationFrame.height * 2) * resolution; + + pm.tx = -1 - (sourceFrame.x * pm.a); + pm.ty = -1 - (sourceFrame.y * pm.d); + } + else + { + pm.a = (1 / destinationFrame.width * 2) * resolution; + pm.d = (-1 / destinationFrame.height * 2) * resolution; + + pm.tx = -1 - (sourceFrame.x * pm.a); + pm.ty = 1 - (sourceFrame.y * pm.d); + } + } + + /** + * Sets the transform of the active render target to the given matrix + * + * @param {PIXI.Matrix} matrix - The transformation matrix + */ + setTransform()// matrix) + { + // this._activeRenderTarget.transform = matrix; + } +} diff --git a/packages/core/src/renderTexture/BaseRenderTexture.js b/packages/core/src/renderTexture/BaseRenderTexture.js new file mode 100644 index 0000000..88eb83b --- /dev/null +++ b/packages/core/src/renderTexture/BaseRenderTexture.js @@ -0,0 +1,137 @@ +import BaseTexture from '../textures/BaseTexture'; +import FrameBuffer from '../framebuffer/FrameBuffer'; + +/** + * A BaseRenderTexture is a special texture that allows any PixiJS display object to be rendered to it. + * + * __Hint__: All DisplayObjects (i.e. Sprites) that render to a BaseRenderTexture should be preloaded + * otherwise black rectangles will be drawn instead. + * + * A BaseRenderTexture takes a snapshot of any Display Object given to its render method. The position + * and rotation of the given Display Objects is ignored. For example: + * + * ```js + * let renderer = PIXI.autoDetectRenderer(1024, 1024, { view: canvas, ratio: 1 }); + * let baseRenderTexture = new PIXI.BaseRenderTexture(renderer, 800, 600); + * let sprite = PIXI.Sprite.fromImage("spinObj_01.png"); + * + * sprite.position.x = 800/2; + * sprite.position.y = 600/2; + * sprite.anchor.x = 0.5; + * sprite.anchor.y = 0.5; + * + * baseRenderTexture.render(sprite); + * ``` + * + * The Sprite in this case will be rendered using its local transform. To render this sprite at 0,0 + * you can clear the transform + * + * ```js + * + * sprite.setTransform() + * + * let baseRenderTexture = new PIXI.BaseRenderTexture(100, 100); + * let renderTexture = new PIXI.RenderTexture(baseRenderTexture); + * + * renderer.render(sprite, renderTexture); // Renders to center of RenderTexture + * ``` + * + * @class + * @extends PIXI.BaseTexture + * @memberof PIXI + */ +export default class BaseRenderTexture extends BaseTexture +{ + /** + * @param {object} [options] + * @param {number} [options.width=100] - The width of the base render texture + * @param {number} [options.height=100] - The height of the base render texture + * @param {PIXI.SCALE_MODES} [options.scaleMode] - See {@link PIXI.SCALE_MODES} for possible values + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the texture being generated + */ + constructor(options) + { + if (typeof options === 'number') + { + /* eslint-disable prefer-rest-params */ + // Backward compatibility of signature + const width = arguments[0]; + const height = arguments[1]; + const scaleMode = arguments[2]; + const resolution = arguments[3]; + + options = { width, height, scaleMode, resolution }; + /* eslint-enable prefer-rest-params */ + } + + super(null, options); + + const { width, height } = options || {}; + + // Set defaults + this.mipmap = false; + this.width = Math.ceil(width) || 100; + this.height = Math.ceil(height) || 100; + this.valid = true; + + /** + * A map of renderer IDs to webgl renderTargets + * + * @private + * @member {object} + */ + // this._glRenderTargets = {}; + + /** + * A reference to the canvas render target (we only need one as this can be shared across renderers) + * + * @private + * @member {object} + */ + this._canvasRenderTarget = null; + + this.clearColor = [0, 0, 0, 0]; + + this.frameBuffer = new FrameBuffer(width * this.resolution, height * this.resolution) + .addColorTexture(0, this); + + // TODO - could this be added the systems? + + /** + * The data structure for the stencil masks + * + * @member {PIXI.Graphics[]} + */ + this.stencilMaskStack = []; + + /** + * The data structure for the filters + * + * @member {PIXI.Graphics[]} + */ + this.filterStack = [{}]; + } + + /** + * Resizes the BaseRenderTexture. + * + * @param {number} width - The width to resize to. + * @param {number} height - The height to resize to. + */ + resize(width, height) + { + width = Math.ceil(width); + height = Math.ceil(height); + this.frameBuffer.resize(width * this.resolution, height * this.resolution); + } + + /** + * Destroys this texture + * + */ + destroy() + { + super.destroy(true); + this.renderer = null; + } +} diff --git a/packages/core/src/renderTexture/RenderTexture.js b/packages/core/src/renderTexture/RenderTexture.js new file mode 100644 index 0000000..f1ecf1f --- /dev/null +++ b/packages/core/src/renderTexture/RenderTexture.js @@ -0,0 +1,153 @@ +import BaseRenderTexture from './BaseRenderTexture'; +import Texture from '../textures/Texture'; + +/** + * A RenderTexture is a special texture that allows any PixiJS display object to be rendered to it. + * + * __Hint__: All DisplayObjects (i.e. Sprites) that render to a RenderTexture should be preloaded + * otherwise black rectangles will be drawn instead. + * + * A RenderTexture takes a snapshot of any Display Object given to its render method. For example: + * + * ```js + * let renderer = PIXI.autoDetectRenderer(1024, 1024, { view: canvas, ratio: 1 }); + * let renderTexture = PIXI.RenderTexture.create(800, 600); + * let sprite = PIXI.Sprite.fromImage("spinObj_01.png"); + * + * sprite.position.x = 800/2; + * sprite.position.y = 600/2; + * sprite.anchor.x = 0.5; + * sprite.anchor.y = 0.5; + * + * renderer.render(sprite, renderTexture); + * ``` + * + * The Sprite in this case will be rendered using its local transform. To render this sprite at 0,0 + * you can clear the transform + * + * ```js + * + * sprite.setTransform() + * + * let renderTexture = new PIXI.RenderTexture.create(100, 100); + * + * renderer.render(sprite, renderTexture); // Renders to center of RenderTexture + * ``` + * + * @class + * @extends PIXI.Texture + * @memberof PIXI + */ +export default class RenderTexture extends Texture +{ + /** + * @param {PIXI.BaseRenderTexture} baseRenderTexture - The renderer used for this RenderTexture + * @param {PIXI.Rectangle} [frame] - The rectangle frame of the texture to show + */ + constructor(baseRenderTexture, frame) + { + // support for legacy.. + let _legacyRenderer = null; + + if (!(baseRenderTexture instanceof BaseRenderTexture)) + { + /* eslint-disable prefer-rest-params, no-console */ + const width = arguments[1]; + const height = arguments[2]; + const scaleMode = arguments[3]; + const resolution = arguments[4]; + + // we have an old render texture.. + console.warn(`Please use RenderTexture.create(${width}, ${height}) instead of the ctor directly.`); + _legacyRenderer = arguments[0]; + /* eslint-enable prefer-rest-params, no-console */ + + frame = null; + baseRenderTexture = new BaseRenderTexture({ + width, + height, + scaleMode, + resolution, + }); + } + + /** + * The base texture object that this texture uses + * + * @member {BaseTexture} + */ + super(baseRenderTexture, frame); + + this.legacyRenderer = _legacyRenderer; + + /** + * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. + * + * @member {boolean} + */ + this.valid = true; + + /** + * FilterSystem temporary storage + * @private + * @member {PIXI.Rectangle} + */ + this.filterFrame = null; + + this._updateUvs(); + } + + /** + * Resizes the RenderTexture. + * + * @param {number} width - The width to resize to. + * @param {number} height - The height to resize to. + * @param {boolean} [resizeBaseTexture=true] - Should the baseTexture.width and height values be resized as well? + */ + resize(width, height, resizeBaseTexture = true) + { + width = Math.ceil(width); + height = Math.ceil(height); + + // TODO - could be not required.. + this.valid = (width > 0 && height > 0); + + this._frame.width = this.orig.width = width; + this._frame.height = this.orig.height = height; + + if (resizeBaseTexture) + { + this.baseTexture.resize(width, height); + } + + this._updateUvs(); + } + + /** + * A short hand way of creating a render texture. + * + * @param {object} [options] - Options + * @param {number} [options.width=100] - The width of the render texture + * @param {number} [options.height=100] - The height of the render texture + * @param {number} [options.scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the texture being generated + * @return {PIXI.RenderTexture} The new render texture + */ + static create(options) + { + // fallback, old-style: create(width, height, scaleMode, resolution) + if (typeof options === 'number') + { + /* eslint-disable prefer-rest-params */ + options = { + width: options, + height: arguments[1], + scaleMode: arguments[2], + resolution: arguments[3], + }; + /* eslint-enable prefer-rest-params */ + } + + return new RenderTexture(new BaseRenderTexture(options)); + } +} diff --git a/packages/core/src/renderTexture/RenderTextureSystem.js b/packages/core/src/renderTexture/RenderTextureSystem.js new file mode 100644 index 0000000..8d0ca61 --- /dev/null +++ b/packages/core/src/renderTexture/RenderTextureSystem.js @@ -0,0 +1,115 @@ +import System from '../System'; +import { Rectangle } from '@pixi/math'; + +const tempRect = new Rectangle(); + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class RenderTextureSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.clearColor = renderer._backgroundColorRgba; + + // TODO moe this property somewhere else! + this.defaultMaskStack = []; + this.defaultFilterStack = [{}]; + + // empty render texture? + this.renderTexture = null; + + this.destinationFrame = new Rectangle(); + } + + bind(renderTexture, sourceFrame, destinationFrame) + { + // TODO - do we want this?? + if (this.renderTexture === renderTexture) return; + this.renderTexture = renderTexture; + + const renderer = this.renderer; + + if (renderTexture) + { + const baseTexture = renderTexture.baseTexture; + + if (!destinationFrame) + { + tempRect.width = baseTexture.realWidth; + tempRect.height = baseTexture.realHeight; + + destinationFrame = tempRect; + } + + if (!sourceFrame) + { + sourceFrame = destinationFrame; + } + + this.renderer.framebuffer.bind(baseTexture.frameBuffer, destinationFrame); + + this.renderer.projection.update(destinationFrame, sourceFrame, baseTexture.resolution, false); + this.renderer.stencil.setMaskStack(baseTexture.stencilMaskStack); + } + else + { + // TODO these validation checks happen deeper down.. + // thing they can be avoided.. + if (!destinationFrame) + { + tempRect.width = renderer.width; + tempRect.height = renderer.height; + + destinationFrame = tempRect; + } + + if (!sourceFrame) + { + sourceFrame = destinationFrame; + } + + renderer.framebuffer.bind(null, destinationFrame); + + // TODO store this.. + this.renderer.projection.update(destinationFrame, sourceFrame, this.renderer.resolution, true); + this.renderer.stencil.setMaskStack(this.defaultMaskStack); + } + + this.destinationFrame.copyFrom(destinationFrame); + } + + /** + * Erases the render texture and fills the drawing area with a colour + * + * @param {number} [clearColor] - The colour + * @return {PIXI.Renderer} Returns itself. + */ + clear(clearColor) + { + if (this.renderTexture) + { + clearColor = clearColor || this.renderTexture.baseTexture.clearColor; + } + else + { + clearColor = clearColor || this.clearColor; + } + + this.renderer.framebuffer.clear(clearColor[0], clearColor[1], clearColor[2], clearColor[3]); + } + + resize()// screenWidth, screenHeight) + { + // resize the root only! + this.bind(null); + } +} diff --git a/packages/core/src/renderers/AbstractRenderer.js b/packages/core/src/renderers/AbstractRenderer.js deleted file mode 100644 index 928670f..0000000 --- a/packages/core/src/renderers/AbstractRenderer.js +++ /dev/null @@ -1,353 +0,0 @@ -import { hex2string, hex2rgb } from '@pixi/utils'; -import { Matrix, Rectangle } from '@pixi/math'; -import { RENDERER_TYPE } from '@pixi/constants'; -import { settings } from '@pixi/settings'; -import { Container } from '@pixi/display'; -import RenderTexture from '../textures/RenderTexture'; -import EventEmitter from 'eventemitter3'; - -const tempMatrix = new Matrix(); - -/** - * 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 EventEmitter - * @memberof PIXI - */ -export default class AbstractRenderer extends EventEmitter -{ - // eslint-disable-next-line valid-jsdoc - /** - * @param {string} system - The name of the system this renderer is for. - * @param {object} [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.transparent=false] - If the render view is transparent, default false - * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false - * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) - * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. The - * resolution of the renderer retina would be 2. - * @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 {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when rendering, - * stopping pixel interpolation. - */ - constructor(system, options, arg2, arg3) - { - super(); - - // Support for constructor(system, screenWidth, screenHeight, options) - if (typeof options === 'number') - { - options = Object.assign({ - width: options, - height: arg2 || settings.RENDER_OPTIONS.height, - }, arg3); - } - - // 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 = RENDERER_TYPE.UNKNOWN; - - /** - * Measurements of the screen. (0, 0, screenWidth, screenHeight) - * - * Its safe to use as filterArea or hitArea for 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 1 - */ - this.resolution = options.resolution || settings.RESOLUTION; - - /** - * Whether the render view is transparent - * - * @member {boolean} - */ - this.transparent = options.transparent; - - /** - * Whether css dimensions of canvas view should be resized to screen dimensions automatically - * - * @member {boolean} - */ - this.autoResize = options.autoResize || false; - - /** - * Tracks the blend modes useful for this renderer. - * - * @member {object} - */ - this.blendModes = null; - - /** - * 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; - - /** - * If true PixiJS will Math.floor() x/y values when rendering, stopping pixel interpolation. - * Handy for crisp pixel art and speed on legacy devices. - * - * @member {boolean} - */ - this.roundPixels = options.roundPixels; - - /** - * The background color as a number. - * - * @member {number} - * @private - */ - this._backgroundColor = 0x000000; - - /** - * The background color as an [R, G, B] array. - * - * @member {number[]} - * @private - */ - this._backgroundColorRgba = [0, 0, 0, 0]; - - /** - * The background color as a string. - * - * @member {string} - * @private - */ - this._backgroundColorString = '#000000'; - - this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter - - /** - * This temporary display object used as the parent of the currently being rendered item - * - * @member {PIXI.DisplayObject} - * @private - */ - this._tempDisplayObjectParent = new Container(); - - /** - * The last root object that the renderer tried to render. - * - * @member {PIXI.DisplayObject} - * @private - */ - this._lastObjectRendered = this._tempDisplayObjectParent; - - /** - * Collection of plugins. - * @readonly - * @member {object} - */ - this.plugins = {}; - } - - /** - * Initialize the plugins. - * - * @protected - * @param {object} staticMap - The dictionary of staticly saved plugins. - */ - initPlugins(staticMap) - { - 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() - { - return this.view.width; - } - - /** - * Same as view.height, actual number of pixels in the canvas by vertical - * - * @member {number} - * @readonly - * @default 600 - */ - get height() - { - return this.view.height; - } - - /** - * Resizes the screen and canvas to the specified width and height - * Canvas dimensions are multiplied by resolution - * - * @param {number} screenWidth - the new width of the screen - * @param {number} screenHeight - the new height of the screen - */ - resize(screenWidth, screenHeight) - { - this.screen.width = screenWidth; - this.screen.height = screenHeight; - - this.view.width = screenWidth * this.resolution; - this.view.height = screenHeight * this.resolution; - - if (this.autoResize) - { - this.view.style.width = `${screenWidth}px`; - this.view.style.height = `${screenHeight}px`; - } - } - - /** - * 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 {PIXI.DisplayObject} displayObject - The displayObject the object will be generated from - * @param {number} scaleMode - Should be one of the scaleMode consts - * @param {number} resolution - The resolution / device pixel ratio of the texture being generated - * @param {PIXI.Rectangle} [region] - The region of the displayObject, that shall be rendered, - * if no region is specified, defaults to the local bounds of the displayObject. - * @return {PIXI.Texture} a texture of the graphics object - */ - generateTexture(displayObject, scaleMode, resolution, region) - { - region = region || displayObject.getLocalBounds(); - - // 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(region.width | 0, region.height | 0, scaleMode, resolution); - - tempMatrix.tx = -region.x; - tempMatrix.ty = -region.y; - - this.render(displayObject, renderTexture, false, tempMatrix, true); - - return renderTexture; - } - - /** - * Removes everything from the renderer and optionally removes the Canvas DOM element. - * - * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. - */ - destroy(removeView) - { - 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); - } - - this.plugins = null; - - this.type = RENDERER_TYPE.UNKNOWN; - - this.view = null; - - this.screen = null; - - this.resolution = 0; - - this.transparent = false; - - this.autoResize = false; - - this.blendModes = null; - - this.options = null; - - this.preserveDrawingBuffer = false; - this.clearBeforeRender = false; - - this.roundPixels = false; - - this._backgroundColor = 0; - this._backgroundColorRgba = null; - this._backgroundColorString = null; - - this._tempDisplayObjectParent = null; - this._lastObjectRendered = null; - } - - /** - * The background color to fill if not transparent - * - * @member {number} - */ - get backgroundColor() - { - return this._backgroundColor; - } - - set backgroundColor(value) // eslint-disable-line require-jsdoc - { - this._backgroundColor = value; - this._backgroundColorString = hex2string(value); - hex2rgb(value, this._backgroundColorRgba); - } -} diff --git a/packages/core/src/renderers/Renderer.js b/packages/core/src/renderers/Renderer.js deleted file mode 100644 index a2020b1..0000000 --- a/packages/core/src/renderers/Renderer.js +++ /dev/null @@ -1,329 +0,0 @@ -import AbstractRenderer from './AbstractRenderer'; -import { sayHello } from '@pixi/utils'; -import MaskSystem from './systems/MaskSystem'; -import StencilSystem from './systems/StencilSystem'; -import FilterSystem from './systems/filter/FilterSystem'; -import FramebufferSystem from './systems/FramebufferSystem'; -import RenderTextureSystem from './systems/RenderTextureSystem'; -import TextureSystem from './systems/textures/TextureSystem'; -import ProjectionSystem from './systems/ProjectionSystem'; -import StateSystem from './systems/StateSystem'; -import GeometrySystem from './systems/geometry/GeometrySystem'; -import ShaderSystem from './systems/shader/ShaderSystem'; -import ContextSystem from './systems/ContextSystem'; -import BatchSystem from './systems/BatchSystem'; -import TextureGCSystem from './systems/textures/TextureGCSystem'; -import { RENDERER_TYPE } from '@pixi/constants'; -import UniformGroup from '../shader/UniformGroup'; -import { Matrix } from '@pixi/math'; -import Runner from 'mini-runner'; - -/** - * 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 Render works by automatically managing webGLBatchs. - * 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 :) - * - * @class - * @memberof PIXI - * @extends PIXI.AbstractRenderer - */ -export default class Renderer extends AbstractRenderer -{ - // eslint-disable-next-line valid-jsdoc - /** - * - * @param {object} [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.transparent=false] - If the render view is transparent, default false - * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false - * @param {boolean} [options.antialias=false] - sets antialias. If not available natively then FXAA - * antialiasing is used - * @param {boolean} [options.forceFXAA=false] - forces FXAA antialiasing to be used over native. - * FXAA is faster, but may not always look as great - * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. - * The resolution of the renderer retina would be 2. - * @param {boolean} [options.clearBeforeRender=true] - This sets if the renderer will clear - * the canvas or not before the new render pass. If you wish to set this to false, you *must* set - * preserveDrawingBuffer to `true`. - * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, - * enable this if you need to call toDataUrl on the webgl context. - * @param {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when - * rendering, stopping pixel interpolation. - * @param {number} [options.backgroundColor=0x000000] - The background color of the rendered area - * (shown if not transparent). - * @param {boolean} [options.legacy=false] - If true PixiJS will aim to ensure compatibility - * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. - * @param {string} [options.powerPreference] - Parameter passed to webgl context, set to "high-performance" - * for devices with dual graphics card - */ - constructor(options, arg2, arg3) - { - super('WebGL', options, arg2, arg3); - - /** - * The type of this renderer as a standardised const - * - * @member {number} - * @see PIXI.RENDERER_TYPE - */ - this.type = RENDERER_TYPE.WEBGL; - - // this will be set by the contextSystem (this.context) - this.gl = null; - this.CONTEXT_UID = 0; - this.legacy = !!options.legacy; - - // TODO legacy! - - // runners! - this.runners = { - destroy: new Runner('destroy'), - contextChange: new Runner('contextChange', 1), - reset: new Runner('reset'), - update: new Runner('update'), - postrender: new Runner('postrender'), - prerender: new Runner('prerender'), - resize: new Runner('resize', 2), - }; - - 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(GeometrySystem, 'geometry') - .addSystem(FramebufferSystem, 'framebuffer') - .addSystem(StencilSystem, 'stencil') - .addSystem(ProjectionSystem, 'projection') - .addSystem(TextureGCSystem, 'textureGC') - .addSystem(FilterSystem, 'filter') - .addSystem(RenderTextureSystem, 'renderTexture') - .addSystem(BatchSystem, 'batch'); - - this.initPlugins(Renderer.__plugins); - - /** - * The options passed in to create a new webgl context. - * - * @member {object} - * @private - */ - if (options.context) - { - this.context.initFromContext(options.context); - } - else - { - this.context.initFromOptions({ - alpha: this.transparent, - antialias: options.antialias, - premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', - stencil: true, - 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); - } - - addSystem(_class, name) - { - if (!name) - { - name = _class.name; - } - - // TODO - read name from class.name.. - - /* - if(name.includes('System')) - { - name = name.replace('System', ''); - name = name.charAt(0).toLowerCase() + name.slice(1); - } - */ - - const system = new _class(this); - - if (this[name]) - { - throw new Error(`Whoops! ${name} is already a manger`); - } - - this[name] = system; - - for (const i in this.runners) - { - this.runners[i].add(system); - } - - return this; - - /** - * 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. - */ - } - - /** - * Renders the object to its webGL view - * - * @param {PIXI.DisplayObject} displayObject - the object to be rendered - * @param {PIXI.RenderTexture} renderTexture - The render texture to render to. - * @param {boolean} [clear] - Should the canvas be cleared before the new render - * @param {PIXI.Transform} [transform] - A transform to apply to the render texture before rendering. - * @param {boolean} [skipUpdateTransform] - Should we skip the update transform pass? - */ - render(displayObject, renderTexture, clear, transform, skipUpdateTransform) - { - // can be handy to know! - this.renderingToScreen = !renderTexture; - - this.runners.prerender.run(); - this.emit('prerender'); - - // 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.parent; - - displayObject.parent = this._tempDisplayObjectParent; - displayObject.updateTransform(); - displayObject.parent = 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.run(); - - this.emit('postrender'); - } - - /** - * Resizes the webGL view to the specified width and height. - * - * @param {number} screenWidth - the new width of the screen - * @param {number} screenHeight - the new height of the screen - */ - resize(screenWidth, screenHeight) - { - AbstractRenderer.prototype.resize.call(this, screenWidth, screenHeight); - - this.runners.resize.run(screenWidth, screenHeight); - } - - /** - * Resets the WebGL state so you can render things however you fancy! - * - * @return {PIXI.Renderer} Returns itself. - */ - reset() - { - this.runners.reset.run(); - - return this; - } - - clear() - { - this.framebuffer.bind(); - this.framebuffer.clear(); - } - - /** - * Removes everything from the renderer (event listeners, spritebatch, etc...) - * - * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. - * See: https://github.com/pixijs/pixi.js/issues/2233 - */ - destroy(removeView) - { - this.runners.destroy.run(); - - // call base destroy - super.destroy(removeView); - - // TODO nullify all the managers.. - this.gl = null; - } - - /** - * 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 - * builds and excluding plugins. - * @name PIXI.Renderer#plugins - * @type {object} - * @readonly - * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. - * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. - * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. - * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. - */ - - /** - * Adds a plugin to the renderer. - * - * @method - * @param {string} pluginName - The name of the plugin. - * @param {Function} ctor - The constructor function or class for the plugin. - */ - static registerPlugin(pluginName, ctor) - { - Renderer.__plugins = Renderer.__plugins || {}; - Renderer.__plugins[pluginName] = ctor; - } -} diff --git a/packages/core/src/renderers/State.js b/packages/core/src/renderers/State.js deleted file mode 100644 index fa3f221..0000000 --- a/packages/core/src/renderers/State.js +++ /dev/null @@ -1,163 +0,0 @@ -/* eslint-disable max-len */ - -const BLEND = 0; -const OFFSET = 1; -const CULLING = 2; -const DEPTH_TEST = 3; -const WINDING = 4; - -/** - * This is a webGL state. It is passed The WebGL StateManager. - * Each mesh renderered may require webGL to be in a different state. - * For example you may want different blend mode or to enable polygon offsets - * - * @class - * @memberof PIXI - */ -export default class State -{ - /** - * - */ - constructor() - { - this.data = 0; - - this.blendMode = 0; - this.polygonOffset = 0; - - this.blend = true; - // this.depthTest = true; - } - - /** - * Activates blending of the computed fragment color values - * - * @member {boolean} - */ - get blend() - { - return !!(this.data & (1 << BLEND)); - } - - set blend(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << BLEND)) !== value) - { - this.data ^= (1 << BLEND); - } - } - - /** - * Activates adding an offset to depth values of polygon's fragments - * - * @member {boolean} - * @default false - */ - get offsets() - { - return !!(this.data & (1 << OFFSET)); - } - - set offsets(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << OFFSET)) !== value) - { - this.data ^= (1 << OFFSET); - } - } - - /** - * Activates culling of polygons. - * - * @member {boolean} - * @default false - */ - get culling() - { - return !!(this.data & (1 << CULLING)); - } - - set culling(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << CULLING)) !== value) - { - this.data ^= (1 << CULLING); - } - } - - /** - * Activates depth comparisons and updates to the depth buffer. - * - * @member {boolean} - * @default false - */ - get depthTest() - { - return !!(this.data & (1 << DEPTH_TEST)); - } - - set depthTest(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << DEPTH_TEST)) !== value) - { - this.data ^= (1 << DEPTH_TEST); - } - } - - /** - * Specifies whether or not front or back-facing polygons can be culled. - * @member {boolean} - * @default false - */ - get clockwiseFrontFace() - { - return !!(this.data & (1 << WINDING)); - } - - set clockwiseFrontFace(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << WINDING)) !== value) - { - this.data ^= (1 << WINDING); - } - } - - /** - * The blend mode to be applied when this state is set. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. - * Setting this mode to anything other than NO_BLEND will automatically switch blending on. - * - * @member {boolean} - * @default PIXI.BLEND_MODES.NORMAL - * @see PIXI.BLEND_MODES - */ - get blendMode() - { - return this._blendMode; - } - - set blendMode(value) // eslint-disable-line require-jsdoc - { - // 17 is NO BLEND - this.blend = (value !== 17); - this._blendMode = value; - } - - /** - * The polygon offset. Setting this property to anything other than 0 will automatically enable poygon offset fill. - * - * @member {number} - * @default 0 - */ - get polygonOffset() - { - return this._polygonOffset; - } - - set polygonOffset(value) // eslint-disable-line require-jsdoc - { - this.offsets = !!value; - this._polygonOffset = value; - } -} - diff --git a/packages/core/src/renderers/filters/Filter.js b/packages/core/src/renderers/filters/Filter.js deleted file mode 100644 index 2073b8d..0000000 --- a/packages/core/src/renderers/filters/Filter.js +++ /dev/null @@ -1,140 +0,0 @@ -import Shader from '../../shader/Shader'; -import Program from '../../shader/Program'; -import State from '../State'; -import { settings } from '@pixi/settings'; -// import extractUniformsFromSrc from './extractUniformsFromSrc'; -import defaultVertex from './defaultFilter.vert'; -import defaultFragment from './defaultFilter.frag'; - -// let math = require('../../math'); -/** - * @class - * @memberof PIXI - * @extends PIXI.Shader - */ -export default class Filter extends Shader -{ - /** - * @param {string} [vertexSrc] - The source of the vertex shader. - * @param {string} [fragmentSrc] - The source of the fragment shader. - * @param {object} [uniforms] - Custom uniforms to use to augment the built-in ones. - */ - constructor(vertexSrc, fragmentSrc, uniforms) - { - const program = Program.from(vertexSrc, fragmentSrc); - - super(program, uniforms); - - /** - * The padding of the filter. Some filters require extra space to breath such as a blur. - * Increasing this will add extra width and height to the bounds of the object that the - * filter is applied to. - * - * @member {number} - */ - this.padding = 0; - - /** - * The resolution of the filter. Setting this to be lower will lower the quality but - * increase the performance of the filter. - * - * @member {number} - */ - this.resolution = settings.RESOLUTION; - - /** - * If enabled is true the filter is applied, if false it will not. - * - * @member {boolean} - */ - this.enabled = true; - - /** - * If enabled, PixiJS will fit the filter area into boundaries for better performance. - * Switch it off if it does not work for specific shader. - * - * @member {boolean} - */ - this.autoFit = true; - - /** - * Legacy filters use position and uvs from attributes - * @member {boolean} - * @readonly - */ - this.legacy = !!this.program.attributeData.aTextureCoord; - - /** - * the webGL state the filter requires to render - * @member {PIXI.State} - */ - this.state = new State(); - } - - /** - * Applies the filter - * - * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from - * @param {PIXI.RenderTarget} input - The input render target. - * @param {PIXI.RenderTarget} output - The target to output to. - * @param {boolean} clear - Should the output be cleared before rendering to it - * @param {object} [currentState] - It's current state of filter. - * There are some useful properties in the currentState : - * target, filters, sourceFrame, destinationFrame, renderTarget, resolution - */ - apply(filterManager, input, output, clear, currentState, derp) // eslint-disable-line no-unused-vars - { - // do as you please! - - filterManager.applyFilter(this, input, output, clear, currentState, derp); - - // or just do a regular render.. - } - - /** - * Sets the blendmode of the filter - * - * @member {number} - * @default PIXI.BLEND_MODES.NORMAL - */ - get blendMode() - { - return this.state.blendMode; - } - - set blendMode(value) // eslint-disable-line require-jsdoc - { - this.state.blendMode = value; - } - - /** - * The default vertex shader source - * - * @static - * @constant - */ - static get defaultVertexSrc() - { - return defaultVertex; - } - - /** - * The default fragment shader source - * - * @static - * @constant - */ - static get defaultFragmentSrc() - { - return defaultFragment; - } -} - -/** - * Used for caching shader IDs - * - * @static - * @private - */ -Filter.SOURCE_KEY_MAP = {}; - diff --git a/packages/core/src/renderers/filters/defaultFilter.frag b/packages/core/src/renderers/filters/defaultFilter.frag deleted file mode 100644 index a14c1be..0000000 --- a/packages/core/src/renderers/filters/defaultFilter.frag +++ /dev/null @@ -1,22 +0,0 @@ -varying vec2 vTextureCoord; -varying vec2 vFilterCoord; - -uniform sampler2D uSampler; -uniform sampler2D filterSampler; - -void main(void){ - vec4 masky = texture2D(filterSampler, vFilterCoord); - vec4 sample = texture2D(uSampler, vTextureCoord); - vec4 color; - if(mod(vFilterCoord.x, 1.0) > 0.5) - { - color = vec4(1.0, 0.0, 0.0, 1.0); - } - else - { - color = vec4(0.0, 1.0, 0.0, 1.0); - } - // gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0); - gl_FragColor = mix(sample, masky, 0.5); - gl_FragColor *= sample.a; -} diff --git a/packages/core/src/renderers/filters/defaultFilter.vert b/packages/core/src/renderers/filters/defaultFilter.vert deleted file mode 100644 index e1febca..0000000 --- a/packages/core/src/renderers/filters/defaultFilter.vert +++ /dev/null @@ -1,14 +0,0 @@ -attribute vec2 aVertexPosition; -attribute vec2 aTextureCoord; - -uniform mat3 projectionMatrix; -uniform mat3 filterMatrix; - -varying vec2 vTextureCoord; -varying vec2 vFilterCoord; - -void main(void){ - gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); - vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy; - vTextureCoord = aTextureCoord ; -} diff --git a/packages/core/src/renderers/filters/filterTransforms.js b/packages/core/src/renderers/filters/filterTransforms.js deleted file mode 100644 index 87a3cd6..0000000 --- a/packages/core/src/renderers/filters/filterTransforms.js +++ /dev/null @@ -1,50 +0,0 @@ -import { Matrix } from '@pixi/math'; - -/** - * Calculates the mapped matrix - * @param filterArea {Rectangle} The filter area - * @param sprite {Sprite} the target sprite - * @param outputMatrix {Matrix} @alvin - * @private - */ -// this returns a matrix that will normalise map filter cords in the filter to screen space -export function calculateScreenSpaceMatrix(outputMatrix, filterArea, textureSize) -{ - // TODO unwrap? - const mappedMatrix = outputMatrix.identity(); - - mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); - - mappedMatrix.scale(textureSize.width, textureSize.height); - - return mappedMatrix; -} - -export function calculateNormalizedScreenSpaceMatrix(outputMatrix, filterArea, textureSize) -{ - const mappedMatrix = outputMatrix.identity(); - - mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); - - const translateScaleX = (textureSize.width / filterArea.width); - const translateScaleY = (textureSize.height / filterArea.height); - - mappedMatrix.scale(translateScaleX, translateScaleY); - - return mappedMatrix; -} - -// this will map the filter coord so that a texture can be used based on the transform of a sprite -export function calculateSpriteMatrix(outputMatrix, filterArea, textureSize, sprite) -{ - const orig = sprite._texture.orig; - const mappedMatrix = outputMatrix.set(textureSize.width, 0, 0, textureSize.height, filterArea.x, filterArea.y); - const worldTransform = sprite.worldTransform.copyTo(Matrix.TEMP_MATRIX); - - worldTransform.invert(); - mappedMatrix.prepend(worldTransform); - mappedMatrix.scale(1.0 / orig.width, 1.0 / orig.height); - mappedMatrix.translate(sprite.anchor.x, sprite.anchor.y); - - return mappedMatrix; -} diff --git a/packages/core/src/renderers/filters/spriteMask/SpriteMaskFilter.js b/packages/core/src/renderers/filters/spriteMask/SpriteMaskFilter.js deleted file mode 100644 index d5a91a4..0000000 --- a/packages/core/src/renderers/filters/spriteMask/SpriteMaskFilter.js +++ /dev/null @@ -1,63 +0,0 @@ -import Filter from '../Filter'; -import { Matrix } from '@pixi/math'; -import vertex from './spriteMaskFilter.vert'; -import fragment from './spriteMaskFilter.frag'; -import { default as TextureMatrix } from '../../../textures/TextureMatrix'; - -/** - * The SpriteMaskFilter class - * - * @class - * @extends PIXI.Filter - * @memberof PIXI - */ -export default class SpriteMaskFilter extends Filter -{ - /** - * @param {PIXI.Sprite} sprite - the target sprite - */ - constructor(sprite) - { - const maskMatrix = new Matrix(); - - super(vertex, fragment); - - sprite.renderable = false; - - this.maskSprite = sprite; - this.maskMatrix = maskMatrix; - } - - /** - * Applies the filter - * - * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from - * @param {PIXI.RenderTarget} input - The input render target. - * @param {PIXI.RenderTarget} output - The target to output to. - */ - apply(filterManager, input, output) - { - const maskSprite = this.maskSprite; - const tex = this.maskSprite.texture; - - if (!tex.valid) - { - return; - } - if (!tex.transform) - { - // margin = 0.0, let it bleed a bit, shader code becomes easier - // assuming that atlas textures were made with 1-pixel padding - tex.transform = new TextureMatrix(tex, 0.0); - } - tex.transform.update(); - - this.uniforms.mask = tex; - this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite) - .prepend(tex.transform.mapCoord); - this.uniforms.alpha = maskSprite.worldAlpha; - this.uniforms.maskClamp = tex.transform.uClampFrame; - - filterManager.applyFilter(this, input, output); - } -} diff --git a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.frag b/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.frag deleted file mode 100644 index 0e4aef8..0000000 --- a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.frag +++ /dev/null @@ -1,23 +0,0 @@ -varying vec2 vMaskCoord; -varying vec2 vTextureCoord; - -uniform sampler2D uSampler; -uniform sampler2D mask; -uniform float alpha; -uniform vec4 maskClamp; - -void main(void) -{ - float clip = step(3.5, - step(maskClamp.x, vMaskCoord.x) + - step(maskClamp.y, vMaskCoord.y) + - step(vMaskCoord.x, maskClamp.z) + - step(vMaskCoord.y, maskClamp.w)); - - vec4 original = texture2D(uSampler, vTextureCoord); - vec4 masky = texture2D(mask, vMaskCoord); - - original *= (masky.r * masky.a * alpha * clip); - - gl_FragColor = original; -} diff --git a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.vert b/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.vert deleted file mode 100644 index d23a30c..0000000 --- a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.vert +++ /dev/null @@ -1,16 +0,0 @@ -attribute vec2 aVertexPosition; -attribute vec2 aTextureCoord; - -uniform mat3 projectionMatrix; -uniform mat3 otherMatrix; - -varying vec2 vMaskCoord; -varying vec2 vTextureCoord; - -void main(void) -{ - gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); - - vTextureCoord = aTextureCoord; - vMaskCoord = ( otherMatrix * vec3( aTextureCoord, 1.0) ).xy; -} diff --git a/packages/core/src/renderers/systems/BatchSystem.js b/packages/core/src/renderers/systems/BatchSystem.js deleted file mode 100644 index 70e9518..0000000 --- a/packages/core/src/renderers/systems/BatchSystem.js +++ /dev/null @@ -1,68 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import ObjectRenderer from '../utils/ObjectRenderer'; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ - -export default class BatchSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this manager works for. - */ - constructor(renderer) - { - super(renderer); - - /** - * An empty renderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.emptyRenderer = new ObjectRenderer(renderer); - - /** - * The currently active ObjectRenderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.currentRenderer = this.emptyRenderer; - } - - /** - * Changes the current renderer to the one given in parameter - * - * @param {PIXI.ObjectRenderer} objectRenderer - The object renderer to use. - */ - setObjectRenderer(objectRenderer) - { - if (this.currentRenderer === objectRenderer) - { - return; - } - - this.currentRenderer.stop(); - this.currentRenderer = objectRenderer; - - this.renderer.state.setState(objectRenderer.state); - - this.currentRenderer.start(); - } - - /** - * This should be called if you wish to do some custom rendering - * It will basically render anything that may be batched up such as sprites - * - */ - flush() - { - this.setObjectRenderer(this.emptyRenderer); - } - - reset() - { - this.setObjectRenderer(this.emptyRenderer); - } -} diff --git a/packages/core/src/renderers/systems/ContextSystem.js b/packages/core/src/renderers/systems/ContextSystem.js deleted file mode 100644 index 43bf827..0000000 --- a/packages/core/src/renderers/systems/ContextSystem.js +++ /dev/null @@ -1,186 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { settings } from '@pixi/settings'; - -let CONTEXT_UID = 0; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class ContextSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this.webGLVersion = 1; - - this.handleContextLost = this.handleContextLost.bind(this); - this.handleContextRestored = this.handleContextRestored.bind(this); - - this.extensions = {}; - - renderer.view.addEventListener('webglcontextlost', this.handleContextLost, false); - renderer.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); - } - - get isLost() - { - return (!this.gl || this.gl.isContextLost()); - } - - contextChange(gl) - { - this.gl = gl; - - // restore a context if it was previously lost - if (gl.isContextLost() && gl.getExtension('WEBGL_lose_context')) - { - gl.getExtension('WEBGL_lose_context').restoreContext(); - } - } - - initFromContext(gl) - { - this.gl = gl; - this.validateContext(gl); - this.renderer.gl = gl; - this.renderer.CONTEXT_UID = CONTEXT_UID++; - this.renderer.runners.contextChange.run(gl); - } - - initFromOptions(options) - { - const gl = this.createContext(this.renderer.view, options); - - this.initFromContext(gl); - } - - /** - * Helper class to create a webGL Context - * - * @class - * @memberof PIXI.glCore - * @param canvas {HTMLCanvasElement} the canvas element that we will get the context from - * @param options {Object} An options object that gets passed in to the canvas element containing the context attributes, - * see https://developer.mozilla.org/en/docs/Web/API/HTMLCanvasElement/getContext for the options available - * @return {WebGLRenderingContext} the WebGL context - */ - createContext(canvas, options) - { - let gl; - - if (settings.PREFER_WEBGL_2) - { - gl = canvas.getContext('webgl2', options); - } - - if (gl) - { - this.webGLVersion = 2; - } - else - { - this.webGLVersion = 1; - - gl = canvas.getContext('webgl', options) - || canvas.getContext('experimental-webgl', options); - - if (!gl) - { - // fail, not able to get a context - throw new Error('This browser does not support webGL. Try using the canvas renderer'); - } - } - - this.gl = gl; - - this.getExtensions(); - - return gl; - } - - getExtensions() - { - // time to set up default etensions that pixi uses.. - const gl = this.gl; - const extensions = this.extensions; - - if (this.webGLVersion === 1) - { - extensions.drawBuffers = gl.getExtension('WEBGL_draw_buffers'); - extensions.depthTexture = gl.getExtension('WEBKIT_WEBGL_depth_texture'); - extensions.floatTexture = gl.getExtension('OES_texture_float'); - extensions.loseContext = gl.getExtension('WEBGL_lose_context'); - - extensions.vertexArrayObject = gl.getExtension('OES_vertex_array_object') - || gl.getExtension('MOZ_OES_vertex_array_object') - || gl.getExtension('WEBKIT_OES_vertex_array_object'); - } - - // we don't use any specific WebGL 2 ones yet! - } - - /** - * Handles a lost webgl context - * - * @private - * @param {WebGLContextEvent} event - The context lost event. - */ - handleContextLost(event) - { - event.preventDefault(); - } - - /** - * Handles a restored webgl context - * - * @private - */ - handleContextRestored() - { - this.renderer.runners.contextChange.run(this.gl); - } - - destroy() - { - const view = this.renderer.view; - - // remove listeners - view.removeEventListener('webglcontextlost', this.handleContextLost); - view.removeEventListener('webglcontextrestored', this.handleContextRestored); - - this.gl.useProgram(null); - - if (this.extensions.loseContext) - { - this.extensions.loseContext.loseContext(); - } - } - - postrender() - { - this.gl.flush(); - } - - validateContext(gl) - { - const attributes = gl.getContextAttributes(); - - // this is going to be fairly simple for now.. but at least we have room to grow! - if (!attributes.stencil) - { - /* eslint-disable max-len */ - - /* eslint-disable no-console */ - console.warn('Provided WebGL context does not have a stencil buffer, masks may not render correctly'); - /* eslint-enable no-console */ - - /* eslint-enable max-len */ - } - } -} diff --git a/packages/core/src/renderers/systems/FramebufferSystem.js b/packages/core/src/renderers/systems/FramebufferSystem.js deleted file mode 100644 index df7902b..0000000 --- a/packages/core/src/renderers/systems/FramebufferSystem.js +++ /dev/null @@ -1,246 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { Rectangle } from '@pixi/math'; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class FramebufferSystem extends WebGLSystem -{ - /** - * Sets up the renderer context and necessary buffers. - * - * @private - */ - contextChange() - { - this.gl = this.renderer.gl; - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - this.current = null; - this.viewport = new Rectangle(); - - this.drawBufferExtension = this.renderer.context.extensions.drawBuffers; - } - - bind(framebuffer, frame) - { - const gl = this.gl; - - this.current = framebuffer; - - if (framebuffer) - { - // TODO cacheing layer! - - const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID] || this.initFramebuffer(framebuffer); - - gl.bindFramebuffer(gl.FRAMEBUFFER, fbo.framebuffer); - // makesure all textures are unbound.. - - // now check for updates... - if (fbo.dirtyId !== framebuffer.dirtyId) - { - fbo.dirtyId = framebuffer.dirtyId; - - if (fbo.dirtyFormat !== framebuffer.dirtyFormat) - { - fbo.dirtyFormat = framebuffer.dirtyFormat; - this.updateFramebuffer(framebuffer); - } - else if (fbo.dirtySize !== framebuffer.dirtySize) - { - fbo.dirtySize = framebuffer.dirtySize; - this.resizeFramebuffer(framebuffer); - } - } - - for (let i = 0; i < framebuffer.colorTextures.length; i++) - { - if (framebuffer.colorTextures[i].texturePart) - { - this.renderer.texture.unbind(framebuffer.colorTextures[i].texture); - } - else - { - this.renderer.texture.unbind(framebuffer.colorTextures[i]); - } - } - - if (framebuffer.depthTexture) - { - this.renderer.texture.unbind(framebuffer.depthTexture); - } - - if (frame) - { - this.setViewport(frame.x, frame.y, frame.width, frame.height); - } - else - { - this.setViewport(0, 0, framebuffer.width, framebuffer.height); - } - } - else - { - gl.bindFramebuffer(gl.FRAMEBUFFER, null); - - if (frame) - { - this.setViewport(frame.x, frame.y, frame.width, frame.height); - } - else - { - this.setViewport(0, 0, this.renderer.width, this.renderer.height); - } - } - } - - setViewport(x, y, width, height) - { - const v = this.viewport; - - if (v.width !== width || v.height !== height || v.x !== x || v.y !== y) - { - v.x = x; - v.y = y; - v.width = width; - v.height = height; - - this.gl.viewport(x, y, width, height); - } - } - - get size() - { - if (this.current) - { - // TODO store temp - return { x: 0, y: 0, width: this.current.width, height: this.current.height }; - } - - return { x: 0, y: 0, width: this.renderer.width, height: this.renderer.height }; - } - - clear(r, g, b, a) - { - const gl = this.gl; - - // TODO clear color can be set only one right? - gl.clearColor(r, g, b, a); - gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); - } - - // private functions... - - initFramebuffer(framebuffer) - { - const gl = this.gl; - - // TODO - make this a class? - const fbo = { - framebuffer: gl.createFramebuffer(), - stencil: null, - dirtyId: 0, - dirtyFormat: 0, - dirtySize: 0, - }; - - framebuffer.glFrameBuffers[this.CONTEXT_UID] = fbo; - - return fbo; - } - - resizeFramebuffer(framebuffer) - { - const gl = this.gl; - - if (framebuffer.stencil || framebuffer.depth) - { - gl.bindRenderbuffer(gl.RENDERBUFFER, this.stencil); - gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); - } - } - - updateFramebuffer(framebuffer) - { - const gl = this.gl; - - const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID]; - - // bind the color texture - const colorTextures = framebuffer.colorTextures; - - let count = colorTextures.length; - - if (!this.drawBufferExtension) - { - count = Math.min(count, 1); - } - - const activeTextures = []; - - for (let i = 0; i < count; i++) - { - const texture = framebuffer.colorTextures[i]; - - if (texture.texturePart) - { - this.renderer.texture.bind(texture.texture, 0); - - gl.framebufferTexture2D(gl.FRAMEBUFFER, - gl.COLOR_ATTACHMENT0 + i, - gl.TEXTURE_CUBE_MAP_NEGATIVE_X + texture.side, - texture.texture._glTextures[this.CONTEXT_UID].texture, - 0); - } - else - { - this.renderer.texture.bind(texture, 0); - - gl.framebufferTexture2D(gl.FRAMEBUFFER, - gl.COLOR_ATTACHMENT0 + i, - gl.TEXTURE_2D, - texture._glTextures[this.CONTEXT_UID].texture, - 0); - } - - activeTextures.push(gl.COLOR_ATTACHMENT0 + i); - } - - if (this.drawBufferExtension && activeTextures.length > 1) - { - this.drawBufferExtension.drawBuffersWEBGL(activeTextures); - } - - if (framebuffer.depthTexture) - { - const depthTextureExt = this.renderer.context.extensions.depthTexture; - - if (depthTextureExt) - { - const depthTexture = framebuffer.depthTexture; - - this.renderer.texture.bind(depthTexture, 0); - - gl.framebufferTexture2D(gl.FRAMEBUFFER, - gl.DEPTH_ATTACHMENT, - gl.TEXTURE_2D, - depthTexture._glTextures[this.CONTEXT_UID].texture, - 0); - } - } - - if (framebuffer.stencil || framebuffer.depth) - { - fbo.stencil = gl.createRenderbuffer(); - - gl.bindRenderbuffer(gl.RENDERBUFFER, fbo.stencil); - - // TODO.. this is depth AND stencil? - gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, fbo.stencil); - gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); - // fbo.enableStencil(); - } - } -} diff --git a/packages/core/src/renderers/systems/MaskSystem.js b/packages/core/src/renderers/systems/MaskSystem.js deleted file mode 100644 index d9c4b1a..0000000 --- a/packages/core/src/renderers/systems/MaskSystem.js +++ /dev/null @@ -1,199 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import AlphaMaskFilter from '../filters/spriteMask/SpriteMaskFilter'; - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class MaskSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - // TODO - we don't need both! - this.scissor = false; - this.scissorData = null; - this.scissorRenderTarget = null; - - this.enableScissor = false; - - this.alphaMaskPool = []; - this.alphaMaskIndex = 0; - } - - /** - * Applies the Mask and adds it to the current filter stack. - * - * @param {PIXI.DisplayObject} target - Display Object to push the mask to - * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. - */ - push(target, maskData) - { - // TODO the root check means scissor rect will not - // be used on render textures more info here: - // https://github.com/pixijs/pixi.js/pull/3545 - - if (maskData.texture) - { - this.pushSpriteMask(target, maskData); - } - else if (this.enableScissor - && !this.scissor - && this.renderer._activeRenderTarget.root - && !this.renderer.stencil.stencilMaskStack.length - && maskData.isFastRect()) - { - const matrix = maskData.worldTransform; - - let rot = Math.atan2(matrix.b, matrix.a); - - // use the nearest degree! - rot = Math.round(rot * (180 / Math.PI)); - - if (rot % 90) - { - this.pushStencilMask(maskData); - } - else - { - this.pushScissorMask(target, maskData); - } - } - else - { - this.pushStencilMask(maskData); - } - } - - /** - * Removes the last mask from the mask stack and doesn't return it. - * - * @param {PIXI.DisplayObject} target - Display Object to pop the mask from - * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. - */ - pop(target, maskData) - { - if (maskData.texture) - { - this.popSpriteMask(target, maskData); - } - else if (this.enableScissor && !this.renderer.stencil.stencilMaskStack.length) - { - this.popScissorMask(target, maskData); - } - else - { - this.popStencilMask(target, maskData); - } - } - - /** - * Applies the Mask and adds it to the current filter stack. - * - * @param {PIXI.RenderTarget} target - Display Object to push the sprite mask to - * @param {PIXI.Sprite} maskData - Sprite to be used as the mask - */ - pushSpriteMask(target, maskData) - { - let alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; - - if (!alphaMaskFilter) - { - alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; - } - - alphaMaskFilter[0].resolution = this.renderer.resolution; - alphaMaskFilter[0].maskSprite = maskData; - - // TODO - may cause issues! - target.filterArea = maskData.getBounds(true); - - this.renderer.filterSystem.pushFilter(target, alphaMaskFilter); - - this.alphaMaskIndex++; - } - - /** - * Removes the last filter from the filter stack and doesn't return it. - * - */ - popSpriteMask() - { - this.renderer.filterSystem.popFilter(); - this.alphaMaskIndex--; - } - - /** - * Applies the Mask and adds it to the current filter stack. - * - * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. - */ - pushStencilMask(maskData) - { - this.renderer.batch.flush(); - this.renderer.stencil.pushStencil(maskData); - } - - /** - * Removes the last filter from the filter stack and doesn't return it. - * - */ - popStencilMask() - { - // this.renderer.currentRenderer.stop(); - this.renderer.stencil.popStencil(); - } - - /** - * - * @param {PIXI.DisplayObject} target - Display Object to push the mask to - * @param {PIXI.Graphics} maskData - The masking data. - */ - pushScissorMask(target, maskData) - { - maskData.renderable = true; - - const renderTarget = this.renderer._activeRenderTarget; - - const bounds = maskData.getBounds(); - - bounds.fit(renderTarget.size); - maskData.renderable = false; - - this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); - - const resolution = this.renderer.resolution; - - this.renderer.gl.scissor( - bounds.x * resolution, - (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, - bounds.width * resolution, - bounds.height * resolution - ); - - this.scissorRenderTarget = renderTarget; - this.scissorData = maskData; - this.scissor = true; - } - - /** - * - * - */ - popScissorMask() - { - this.scissorRenderTarget = null; - this.scissorData = null; - this.scissor = false; - - // must be scissor! - const gl = this.renderer.gl; - - gl.disable(gl.SCISSOR_TEST); - } -} diff --git a/packages/core/src/renderers/systems/ProjectionSystem.js b/packages/core/src/renderers/systems/ProjectionSystem.js deleted file mode 100644 index ec5efdb..0000000 --- a/packages/core/src/renderers/systems/ProjectionSystem.js +++ /dev/null @@ -1,73 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { Matrix } from '@pixi/math'; - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ - -export default class ProjectionSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this.projectionMatrix = new Matrix(); - } - - update(destinationFrame, sourceFrame, resolution, root) - { - this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; - this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; - - this.calculateProjection(this.destinationFrame, this.sourceFrame, resolution, root); - - this.renderer.globalUniforms.uniforms.projectionMatrix = this.projectionMatrix; - this.renderer.globalUniforms.update(); - } - - /** - * Updates the projection matrix based on a projection frame (which is a rectangle) - * - * @param {Rectangle} destinationFrame - The destination frame. - * @param {Rectangle} sourceFrame - The source frame. - */ - calculateProjection(destinationFrame, sourceFrame, resolution, root) - { - const pm = this.projectionMatrix; - - // I don't think we will need this line.. - // pm.identity(); - - if (!root) - { - pm.a = (1 / destinationFrame.width * 2) * resolution; - pm.d = (1 / destinationFrame.height * 2) * resolution; - - pm.tx = -1 - (sourceFrame.x * pm.a); - pm.ty = -1 - (sourceFrame.y * pm.d); - } - else - { - pm.a = (1 / destinationFrame.width * 2) * resolution; - pm.d = (-1 / destinationFrame.height * 2) * resolution; - - pm.tx = -1 - (sourceFrame.x * pm.a); - pm.ty = 1 - (sourceFrame.y * pm.d); - } - } - - /** - * Sets the transform of the active render target to the given matrix - * - * @param {PIXI.Matrix} matrix - The transformation matrix - */ - setTransform()// matrix) - { - // this._activeRenderTarget.transform = matrix; - } -} diff --git a/packages/core/src/AbstractRenderer.js b/packages/core/src/AbstractRenderer.js new file mode 100644 index 0000000..64b2eb1 --- /dev/null +++ b/packages/core/src/AbstractRenderer.js @@ -0,0 +1,353 @@ +import { hex2string, hex2rgb } from '@pixi/utils'; +import { Matrix, Rectangle } from '@pixi/math'; +import { RENDERER_TYPE } from '@pixi/constants'; +import { settings } from '@pixi/settings'; +import { Container } from '@pixi/display'; +import RenderTexture from './renderTexture/RenderTexture'; +import EventEmitter from 'eventemitter3'; + +const tempMatrix = new Matrix(); + +/** + * 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 EventEmitter + * @memberof PIXI + */ +export default class AbstractRenderer extends EventEmitter +{ + // eslint-disable-next-line valid-jsdoc + /** + * @param {string} system - The name of the system this renderer is for. + * @param {object} [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.transparent=false] - If the render view is transparent, default false + * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false + * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. The + * resolution of the renderer retina would be 2. + * @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 {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when rendering, + * stopping pixel interpolation. + */ + constructor(system, options, arg2, arg3) + { + super(); + + // Support for constructor(system, screenWidth, screenHeight, options) + if (typeof options === 'number') + { + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + }, arg3); + } + + // 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 = RENDERER_TYPE.UNKNOWN; + + /** + * Measurements of the screen. (0, 0, screenWidth, screenHeight) + * + * Its safe to use as filterArea or hitArea for 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 1 + */ + this.resolution = options.resolution || settings.RESOLUTION; + + /** + * Whether the render view is transparent + * + * @member {boolean} + */ + this.transparent = options.transparent; + + /** + * Whether css dimensions of canvas view should be resized to screen dimensions automatically + * + * @member {boolean} + */ + this.autoResize = options.autoResize || false; + + /** + * Tracks the blend modes useful for this renderer. + * + * @member {object} + */ + this.blendModes = null; + + /** + * 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; + + /** + * If true PixiJS will Math.floor() x/y values when rendering, stopping pixel interpolation. + * Handy for crisp pixel art and speed on legacy devices. + * + * @member {boolean} + */ + this.roundPixels = options.roundPixels; + + /** + * The background color as a number. + * + * @member {number} + * @private + */ + this._backgroundColor = 0x000000; + + /** + * The background color as an [R, G, B] array. + * + * @member {number[]} + * @private + */ + this._backgroundColorRgba = [0, 0, 0, 0]; + + /** + * The background color as a string. + * + * @member {string} + * @private + */ + this._backgroundColorString = '#000000'; + + this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter + + /** + * This temporary display object used as the parent of the currently being rendered item + * + * @member {PIXI.DisplayObject} + * @private + */ + this._tempDisplayObjectParent = new Container(); + + /** + * The last root object that the renderer tried to render. + * + * @member {PIXI.DisplayObject} + * @private + */ + this._lastObjectRendered = this._tempDisplayObjectParent; + + /** + * Collection of plugins. + * @readonly + * @member {object} + */ + this.plugins = {}; + } + + /** + * Initialize the plugins. + * + * @protected + * @param {object} staticMap - The dictionary of staticly saved plugins. + */ + initPlugins(staticMap) + { + 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() + { + return this.view.width; + } + + /** + * Same as view.height, actual number of pixels in the canvas by vertical + * + * @member {number} + * @readonly + * @default 600 + */ + get height() + { + return this.view.height; + } + + /** + * Resizes the screen and canvas to the specified width and height + * Canvas dimensions are multiplied by resolution + * + * @param {number} screenWidth - the new width of the screen + * @param {number} screenHeight - the new height of the screen + */ + resize(screenWidth, screenHeight) + { + this.screen.width = screenWidth; + this.screen.height = screenHeight; + + this.view.width = screenWidth * this.resolution; + this.view.height = screenHeight * this.resolution; + + if (this.autoResize) + { + this.view.style.width = `${screenWidth}px`; + this.view.style.height = `${screenHeight}px`; + } + } + + /** + * 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 {PIXI.DisplayObject} displayObject - The displayObject the object will be generated from + * @param {number} scaleMode - Should be one of the scaleMode consts + * @param {number} resolution - The resolution / device pixel ratio of the texture being generated + * @param {PIXI.Rectangle} [region] - The region of the displayObject, that shall be rendered, + * if no region is specified, defaults to the local bounds of the displayObject. + * @return {PIXI.Texture} a texture of the graphics object + */ + generateTexture(displayObject, scaleMode, resolution, region) + { + region = region || displayObject.getLocalBounds(); + + // 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(region.width | 0, region.height | 0, scaleMode, resolution); + + tempMatrix.tx = -region.x; + tempMatrix.ty = -region.y; + + this.render(displayObject, renderTexture, false, tempMatrix, true); + + return renderTexture; + } + + /** + * Removes everything from the renderer and optionally removes the Canvas DOM element. + * + * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. + */ + destroy(removeView) + { + 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); + } + + this.plugins = null; + + this.type = RENDERER_TYPE.UNKNOWN; + + this.view = null; + + this.screen = null; + + this.resolution = 0; + + this.transparent = false; + + this.autoResize = false; + + this.blendModes = null; + + this.options = null; + + this.preserveDrawingBuffer = false; + this.clearBeforeRender = false; + + this.roundPixels = false; + + this._backgroundColor = 0; + this._backgroundColorRgba = null; + this._backgroundColorString = null; + + this._tempDisplayObjectParent = null; + this._lastObjectRendered = null; + } + + /** + * The background color to fill if not transparent + * + * @member {number} + */ + get backgroundColor() + { + return this._backgroundColor; + } + + set backgroundColor(value) // eslint-disable-line require-jsdoc + { + this._backgroundColor = value; + this._backgroundColorString = hex2string(value); + hex2rgb(value, this._backgroundColorRgba); + } +} diff --git a/packages/core/src/Renderer.js b/packages/core/src/Renderer.js new file mode 100644 index 0000000..2227e78 --- /dev/null +++ b/packages/core/src/Renderer.js @@ -0,0 +1,338 @@ +import AbstractRenderer from './AbstractRenderer'; +import { sayHello } from '@pixi/utils'; +import MaskSystem from './mask/MaskSystem'; +import StencilSystem from './mask/StencilSystem'; +import FilterSystem from './filters/FilterSystem'; +import FramebufferSystem from './framebuffer/FramebufferSystem'; +import RenderTextureSystem from './renderTexture/RenderTextureSystem'; +import TextureSystem from './textures/TextureSystem'; +import ProjectionSystem from './projection/ProjectionSystem'; +import StateSystem from './state/StateSystem'; +import GeometrySystem from './geometry/GeometrySystem'; +import ShaderSystem from './shader/ShaderSystem'; +import ContextSystem from './context/ContextSystem'; +import BatchSystem from './batch/BatchSystem'; +import TextureGCSystem from './textures/TextureGCSystem'; +import { RENDERER_TYPE } from '@pixi/constants'; +import UniformGroup from './shader/UniformGroup'; +import { Matrix } from '@pixi/math'; +import Runner from 'mini-runner'; + +/** + * 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 Render works by automatically managing webGLBatchs. + * 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 :) + * + * @class + * @memberof PIXI + * @extends PIXI.AbstractRenderer + */ +export default class Renderer extends AbstractRenderer +{ + // eslint-disable-next-line valid-jsdoc + /** + * + * @param {object} [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.transparent=false] - If the render view is transparent, default false + * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false + * @param {boolean} [options.antialias=false] - sets antialias. If not available natively then FXAA + * antialiasing is used + * @param {boolean} [options.forceFXAA=false] - forces FXAA antialiasing to be used over native. + * FXAA is faster, but may not always look as great + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. + * The resolution of the renderer retina would be 2. + * @param {boolean} [options.clearBeforeRender=true] - This sets if the renderer will clear + * the canvas or not before the new render pass. If you wish to set this to false, you *must* set + * preserveDrawingBuffer to `true`. + * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, + * enable this if you need to call toDataUrl on the webgl context. + * @param {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when + * rendering, stopping pixel interpolation. + * @param {number} [options.backgroundColor=0x000000] - The background color of the rendered area + * (shown if not transparent). + * @param {boolean} [options.legacy=false] - If true PixiJS will aim to ensure compatibility + * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. + * @param {string} [options.powerPreference] - Parameter passed to webgl context, set to "high-performance" + * for devices with dual graphics card + */ + constructor(options, arg2, arg3) + { + super('WebGL', options, arg2, arg3); + + /** + * The type of this renderer as a standardised const + * + * @member {number} + * @see PIXI.RENDERER_TYPE + */ + this.type = RENDERER_TYPE.WEBGL; + + // this will be set by the contextSystem (this.context) + this.gl = null; + this.CONTEXT_UID = 0; + this.legacy = !!options.legacy; + + // TODO legacy! + + // runners! + this.runners = { + destroy: new Runner('destroy'), + contextChange: new Runner('contextChange', 1), + reset: new Runner('reset'), + update: new Runner('update'), + postrender: new Runner('postrender'), + prerender: new Runner('prerender'), + resize: new Runner('resize', 2), + }; + + 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(GeometrySystem, 'geometry') + .addSystem(FramebufferSystem, 'framebuffer') + .addSystem(StencilSystem, 'stencil') + .addSystem(ProjectionSystem, 'projection') + .addSystem(TextureGCSystem, 'textureGC') + .addSystem(FilterSystem, 'filter') + .addSystem(RenderTextureSystem, 'renderTexture') + .addSystem(BatchSystem, 'batch'); + + this.initPlugins(Renderer.__plugins); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ + if (options.context) + { + this.context.initFromContext(options.context); + } + else + { + this.context.initFromOptions({ + alpha: this.transparent, + antialias: options.antialias, + premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', + stencil: true, + 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); + } + + /** + * Add a new system to the renderer. + * @param {class} ClassRef - Class reference + * @param {string} [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. + * @return {PIXI.Renderer} Return instance of renderer + */ + addSystem(ClassRef, name) + { + if (!name) + { + name = ClassRef.name; + } + + // TODO - read name from class.name.. + + /* + if(name.includes('System')) + { + name = name.replace('System', ''); + name = name.charAt(0).toLowerCase() + name.slice(1); + } + */ + + const system = new ClassRef(this); + + if (this[name]) + { + throw new Error(`Whoops! ${name} is already a manger`); + } + + this[name] = system; + + for (const i in this.runners) + { + this.runners[i].add(system); + } + + return this; + + /** + * 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. + */ + } + + /** + * Renders the object to its webGL view + * + * @param {PIXI.DisplayObject} displayObject - the object to be rendered + * @param {PIXI.RenderTexture} renderTexture - The render texture to render to. + * @param {boolean} [clear] - Should the canvas be cleared before the new render + * @param {PIXI.Transform} [transform] - A transform to apply to the render texture before rendering. + * @param {boolean} [skipUpdateTransform] - Should we skip the update transform pass? + */ + render(displayObject, renderTexture, clear, transform, skipUpdateTransform) + { + // can be handy to know! + this.renderingToScreen = !renderTexture; + + this.runners.prerender.run(); + this.emit('prerender'); + + // 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.parent; + + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = 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.run(); + + this.emit('postrender'); + } + + /** + * Resizes the webGL view to the specified width and height. + * + * @param {number} screenWidth - the new width of the screen + * @param {number} screenHeight - the new height of the screen + */ + resize(screenWidth, screenHeight) + { + AbstractRenderer.prototype.resize.call(this, screenWidth, screenHeight); + + this.runners.resize.run(screenWidth, screenHeight); + } + + /** + * Resets the WebGL state so you can render things however you fancy! + * + * @return {PIXI.Renderer} Returns itself. + */ + reset() + { + this.runners.reset.run(); + + return this; + } + + clear() + { + this.framebuffer.bind(); + this.framebuffer.clear(); + } + + /** + * Removes everything from the renderer (event listeners, spritebatch, etc...) + * + * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. + * See: https://github.com/pixijs/pixi.js/issues/2233 + */ + destroy(removeView) + { + this.runners.destroy.run(); + + // call base destroy + super.destroy(removeView); + + // TODO nullify all the managers.. + this.gl = null; + } + + /** + * 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 + * builds and excluding plugins. + * @name PIXI.Renderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. + */ + + /** + * Adds a plugin to the renderer. + * + * @method + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + static registerPlugin(pluginName, ctor) + { + Renderer.__plugins = Renderer.__plugins || {}; + Renderer.__plugins[pluginName] = ctor; + } +} diff --git a/packages/core/src/System.js b/packages/core/src/System.js new file mode 100644 index 0000000..05a10c8 --- /dev/null +++ b/packages/core/src/System.js @@ -0,0 +1,42 @@ +/** + * System is a base class used for extending systems used by the {@link PIXI.Renderer} + * @see PIXI.Renderer#addSystem + * @class + * @memberof PIXI + */ +export default class System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + /** + * The renderer this manager works for. + * + * @member {PIXI.Renderer} + */ + this.renderer = renderer; + + this.renderer.runners.contextChange.add(this); + } + + /** + * Generic method called when there is a WebGL context change. + * + */ + contextChange() + { + // do some codes init! + } + + /** + * Generic destroy methods to be overridden by the subclass + * + */ + destroy() + { + this.renderer.runners.contextChange.remove(this); + this.renderer = null; + } +} diff --git a/packages/core/src/batch/BatchSystem.js b/packages/core/src/batch/BatchSystem.js new file mode 100644 index 0000000..b04c249 --- /dev/null +++ b/packages/core/src/batch/BatchSystem.js @@ -0,0 +1,68 @@ +import System from '../System'; +import ObjectRenderer from './ObjectRenderer'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class BatchSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + super(renderer); + + /** + * An empty renderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.emptyRenderer = new ObjectRenderer(renderer); + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.currentRenderer = this.emptyRenderer; + } + + /** + * Changes the current renderer to the one given in parameter + * + * @param {PIXI.ObjectRenderer} objectRenderer - The object renderer to use. + */ + setObjectRenderer(objectRenderer) + { + if (this.currentRenderer === objectRenderer) + { + return; + } + + this.currentRenderer.stop(); + this.currentRenderer = objectRenderer; + + this.renderer.state.setState(objectRenderer.state); + + this.currentRenderer.start(); + } + + /** + * This should be called if you wish to do some custom rendering + * It will basically render anything that may be batched up such as sprites + * + */ + flush() + { + this.setObjectRenderer(this.emptyRenderer); + } + + reset() + { + this.setObjectRenderer(this.emptyRenderer); + } +} diff --git a/packages/core/src/batch/ObjectRenderer.js b/packages/core/src/batch/ObjectRenderer.js new file mode 100644 index 0000000..0c851a9 --- /dev/null +++ b/packages/core/src/batch/ObjectRenderer.js @@ -0,0 +1,48 @@ +import System from '../System'; + +/** + * Base for a common object renderer that can be used as a system renderer plugin. + * + * @class + * @extends PIXI.System + * @memberof PIXI + */ +export default class ObjectRenderer extends System +{ + /** + * Starts the renderer and sets the shader + * + */ + start() + { + // set the shader.. + } + + /** + * Stops the renderer + * + */ + stop() + { + this.flush(); + } + + /** + * Stub method for rendering content and emptying the current batch. + * + */ + flush() + { + // flush! + } + + /** + * Renders an object + * + * @param {PIXI.DisplayObject} object - The object to render. + */ + render(object) // eslint-disable-line no-unused-vars + { + // render the object + } +} diff --git a/packages/core/src/context/ContextSystem.js b/packages/core/src/context/ContextSystem.js new file mode 100644 index 0000000..301aab4 --- /dev/null +++ b/packages/core/src/context/ContextSystem.js @@ -0,0 +1,186 @@ +import System from '../System'; +import { settings } from '@pixi/settings'; + +let CONTEXT_UID = 0; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class ContextSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.webGLVersion = 1; + + this.handleContextLost = this.handleContextLost.bind(this); + this.handleContextRestored = this.handleContextRestored.bind(this); + + this.extensions = {}; + + renderer.view.addEventListener('webglcontextlost', this.handleContextLost, false); + renderer.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + } + + get isLost() + { + return (!this.gl || this.gl.isContextLost()); + } + + contextChange(gl) + { + this.gl = gl; + + // restore a context if it was previously lost + if (gl.isContextLost() && gl.getExtension('WEBGL_lose_context')) + { + gl.getExtension('WEBGL_lose_context').restoreContext(); + } + } + + initFromContext(gl) + { + this.gl = gl; + this.validateContext(gl); + this.renderer.gl = gl; + this.renderer.CONTEXT_UID = CONTEXT_UID++; + this.renderer.runners.contextChange.run(gl); + } + + initFromOptions(options) + { + const gl = this.createContext(this.renderer.view, options); + + this.initFromContext(gl); + } + + /** + * Helper class to create a webGL Context + * + * @class + * @memberof PIXI.glCore + * @param canvas {HTMLCanvasElement} the canvas element that we will get the context from + * @param options {Object} An options object that gets passed in to the canvas element containing the context attributes, + * see https://developer.mozilla.org/en/docs/Web/API/HTMLCanvasElement/getContext for the options available + * @return {WebGLRenderingContext} the WebGL context + */ + createContext(canvas, options) + { + let gl; + + if (settings.PREFER_WEBGL_2) + { + gl = canvas.getContext('webgl2', options); + } + + if (gl) + { + this.webGLVersion = 2; + } + else + { + this.webGLVersion = 1; + + gl = canvas.getContext('webgl', options) + || canvas.getContext('experimental-webgl', options); + + if (!gl) + { + // fail, not able to get a context + throw new Error('This browser does not support webGL. Try using the canvas renderer'); + } + } + + this.gl = gl; + + this.getExtensions(); + + return gl; + } + + getExtensions() + { + // time to set up default etensions that pixi uses.. + const gl = this.gl; + const extensions = this.extensions; + + if (this.webGLVersion === 1) + { + extensions.drawBuffers = gl.getExtension('WEBGL_draw_buffers'); + extensions.depthTexture = gl.getExtension('WEBKIT_WEBGL_depth_texture'); + extensions.floatTexture = gl.getExtension('OES_texture_float'); + extensions.loseContext = gl.getExtension('WEBGL_lose_context'); + + extensions.vertexArrayObject = gl.getExtension('OES_vertex_array_object') + || gl.getExtension('MOZ_OES_vertex_array_object') + || gl.getExtension('WEBKIT_OES_vertex_array_object'); + } + + // we don't use any specific WebGL 2 ones yet! + } + + /** + * Handles a lost webgl context + * + * @private + * @param {WebGLContextEvent} event - The context lost event. + */ + handleContextLost(event) + { + event.preventDefault(); + } + + /** + * Handles a restored webgl context + * + * @private + */ + handleContextRestored() + { + this.renderer.runners.contextChange.run(this.gl); + } + + destroy() + { + const view = this.renderer.view; + + // remove listeners + view.removeEventListener('webglcontextlost', this.handleContextLost); + view.removeEventListener('webglcontextrestored', this.handleContextRestored); + + this.gl.useProgram(null); + + if (this.extensions.loseContext) + { + this.extensions.loseContext.loseContext(); + } + } + + postrender() + { + this.gl.flush(); + } + + validateContext(gl) + { + const attributes = gl.getContextAttributes(); + + // this is going to be fairly simple for now.. but at least we have room to grow! + if (!attributes.stencil) + { + /* eslint-disable max-len */ + + /* eslint-disable no-console */ + console.warn('Provided WebGL context does not have a stencil buffer, masks may not render correctly'); + /* eslint-enable no-console */ + + /* eslint-enable max-len */ + } + } +} diff --git a/packages/core/src/filters/Filter.js b/packages/core/src/filters/Filter.js new file mode 100644 index 0000000..9d8cad8 --- /dev/null +++ b/packages/core/src/filters/Filter.js @@ -0,0 +1,140 @@ +import Shader from '../shader/Shader'; +import Program from '../shader/Program'; +import State from '../state/State'; +import { settings } from '@pixi/settings'; +// import extractUniformsFromSrc from './extractUniformsFromSrc'; +import defaultVertex from './defaultFilter.vert'; +import defaultFragment from './defaultFilter.frag'; + +// let math = require('../../math'); +/** + * @class + * @memberof PIXI + * @extends PIXI.Shader + */ +export default class Filter extends Shader +{ + /** + * @param {string} [vertexSrc] - The source of the vertex shader. + * @param {string} [fragmentSrc] - The source of the fragment shader. + * @param {object} [uniforms] - Custom uniforms to use to augment the built-in ones. + */ + constructor(vertexSrc, fragmentSrc, uniforms) + { + const program = Program.from(vertexSrc, fragmentSrc); + + super(program, uniforms); + + /** + * The padding of the filter. Some filters require extra space to breath such as a blur. + * Increasing this will add extra width and height to the bounds of the object that the + * filter is applied to. + * + * @member {number} + */ + this.padding = 0; + + /** + * The resolution of the filter. Setting this to be lower will lower the quality but + * increase the performance of the filter. + * + * @member {number} + */ + this.resolution = settings.RESOLUTION; + + /** + * If enabled is true the filter is applied, if false it will not. + * + * @member {boolean} + */ + this.enabled = true; + + /** + * If enabled, PixiJS will fit the filter area into boundaries for better performance. + * Switch it off if it does not work for specific shader. + * + * @member {boolean} + */ + this.autoFit = true; + + /** + * Legacy filters use position and uvs from attributes + * @member {boolean} + * @readonly + */ + this.legacy = !!this.program.attributeData.aTextureCoord; + + /** + * the webGL state the filter requires to render + * @member {PIXI.State} + */ + this.state = new State(); + } + + /** + * Applies the filter + * + * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + * @param {boolean} clear - Should the output be cleared before rendering to it + * @param {object} [currentState] - It's current state of filter. + * There are some useful properties in the currentState : + * target, filters, sourceFrame, destinationFrame, renderTarget, resolution + */ + apply(filterManager, input, output, clear, currentState, derp) // eslint-disable-line no-unused-vars + { + // do as you please! + + filterManager.applyFilter(this, input, output, clear, currentState, derp); + + // or just do a regular render.. + } + + /** + * Sets the blendmode of the filter + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL + */ + get blendMode() + { + return this.state.blendMode; + } + + set blendMode(value) // eslint-disable-line require-jsdoc + { + this.state.blendMode = value; + } + + /** + * The default vertex shader source + * + * @static + * @constant + */ + static get defaultVertexSrc() + { + return defaultVertex; + } + + /** + * The default fragment shader source + * + * @static + * @constant + */ + static get defaultFragmentSrc() + { + return defaultFragment; + } +} + +/** + * Used for caching shader IDs + * + * @static + * @private + */ +Filter.SOURCE_KEY_MAP = {}; + diff --git a/packages/core/src/filters/FilterSystem.js b/packages/core/src/filters/FilterSystem.js new file mode 100644 index 0000000..3174318 --- /dev/null +++ b/packages/core/src/filters/FilterSystem.js @@ -0,0 +1,431 @@ +import System from '../System'; + +import RenderTexture from '../renderTexture/RenderTexture'; +import Quad from '../utils/Quad'; +import QuadUv from '../utils/QuadUv'; +import { Rectangle } from '@pixi/math'; +import * as filterTransforms from './filterTransforms'; +import bitTwiddle from 'bit-twiddle'; +import UniformGroup from '../shader/UniformGroup'; +import { DRAW_MODES } from '@pixi/constants'; + +// +/** + * @ignore + * @class + */ +class FilterState +{ + /** + * + */ + constructor() + { + this.renderTexture = null; + this.sourceFrame = new Rectangle(); + this.destinationFrame = new Rectangle(); + this.filters = []; + this.target = null; + this.legacy = false; + this.resolution = 1; + } +} + +/** + * @class + * @memberof PIXI.systems + * @extends PIXI.System + */ +export default class FilterSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + /** + * stores a bunch of PO2 textures used for filtering + * @type {Object} + */ + this.texturePool = {}; + + /** + * a pool for storing filter states, save us creating new ones each tick + * @type {Array} + */ + this.statePool = []; + + /** + * A very simple geometry used when drawing a filter effect to the screen + * @type {Quad} + */ + this.quad = new Quad(); + + this.quadUv = new QuadUv(); + + /** + * Temporary rect for maths + * @type {PIXI.Rectangle} + */ + this.tempRect = new Rectangle(); + + this.activeState = {}; + + /** + * this uniform group is attached to filter uniforms when used + * @type {UniformGroup} + */ + this.globalUniforms = new UniformGroup({ + sourceFrame: this.tempRect, + destinationFrame: this.tempRect, + + // legacy variables + filterArea: new Float32Array(4), + filterClamp: new Float32Array(4), + }, true); + } + + /** + * Adds a new filter to the System. + * + * @param {PIXI.DisplayObject} target - The target of the filter to render. + * @param {PIXI.Filter[]} filters - The filters to apply. + */ + push(target, filters) + { + const renderer = this.renderer; + const filterStack = this.renderer.renderTexture.defaultFilterStack; + const state = this.statePool.pop() || new FilterState(); + + let resolution = filters[0].resolution; + let padding = filters[0].padding; + let autoFit = filters[0].autoFit; + let legacy = filters[0].legacy; + + for (let i = 1; i < filters.length; i++) + { + const filter = filters[i]; + + // lets use the lowest resolution.. + resolution = Math.min(resolution, filter.resolution); + // and the largest amount of padding! + padding = Math.max(padding, filter.padding); + // only auto fit if all filters are autofit + autoFit = autoFit || filter.autoFit; + + legacy = legacy || filter.legacy; + } + + filterStack.push(state); + + state.resolution = resolution; + + state.legacy = legacy; + + // round to whole number based on resolution + // TODO move that to the shader too? + state.sourceFrame = target.filterArea || target.getBounds(true); + + state.sourceFrame.pad(padding); + + if (autoFit) + { + state.sourceFrame.fit(this.renderer.renderTexture.destinationFrame); + } + + state.sourceFrame.round(resolution); + + state.renderTexture = this.getPotFilterTexture(state.sourceFrame.width, state.sourceFrame.height, resolution); + state.filters = filters; + + state.destinationFrame.width = state.renderTexture.width; + state.destinationFrame.height = state.renderTexture.height; + + state.renderTexture.filterFrame = state.sourceFrame; + renderer.renderTexture.bind(state.renderTexture, state.sourceFrame);// /, state.destinationFrame); + renderer.renderTexture.clear(); + } + + /** + * Pops off the filter and applies it. + * + */ + pop() + { + const renderer = this.renderer; + const filterStack = renderer.renderTexture.defaultFilterStack; + const state = filterStack.pop(); + const filters = state.filters; + + this.activeState = state; + + const globalUniforms = this.globalUniforms.uniforms; + + globalUniforms.sourceFrame = state.sourceFrame; + globalUniforms.destinationFrame = state.destinationFrame; + globalUniforms.resolution = state.resolution; + + // only update the rect if its legacy.. + if (state.legacy) + { + const filterArea = globalUniforms.filterArea; + const filterClamp = globalUniforms.filterClamp; + + filterArea[0] = state.destinationFrame.width; + filterArea[1] = state.destinationFrame.height; + filterArea[2] = state.sourceFrame.x; + filterArea[3] = state.sourceFrame.y; + + filterClamp[0] = 0.5 / state.resolution / state.destinationFrame.width; + filterClamp[1] = 0.5 / state.resolution / state.destinationFrame.height; + filterClamp[2] = (state.sourceFrame.width - 0.5) / state.resolution / state.destinationFrame.width; + filterClamp[3] = (state.sourceFrame.height - 0.5) / state.resolution / state.destinationFrame.height; + } + + this.globalUniforms.update(); + + const lastState = filterStack[filterStack.length - 1]; + + if (filters.length === 1) + { + filters[0].apply(this, state.renderTexture, lastState.renderTexture, false, state); + + this.returnFilterTexture(state.renderTexture); + } + else + { + let flip = state.renderTexture; + let flop = this.getPotFilterTexture( + flip.width, + flip.height, + state.resolution + ); + + flop.filterFrame = flip.filterFrame; + + let i = 0; + + for (i = 0; i < filters.length - 1; ++i) + { + filters[i].apply(this, flip, flop, true, state); + + const t = flip; + + flip = flop; + flop = t; + } + + filters[i].apply(this, flip, lastState.renderTexture, false, state); + + this.returnFilterTexture(flip); + this.returnFilterTexture(flop); + } + + this.statePool.push(state); + } + + /** + * Draws a filter. + * + * @param {PIXI.Filter} filter - The filter to draw. + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + * @param {boolean} clear - Should the output be cleared before rendering to it + */ + applyFilter(filter, input, output, clear) + { + const renderer = this.renderer; + + renderer.renderTexture.bind(output, output ? output.filterFrame : null); + + if (clear) + { + // gl.disable(gl.SCISSOR_TEST); + renderer.renderTexture.clear(); + // gl.enable(gl.SCISSOR_TEST); + } + + // set the uniforms.. + filter.uniforms.uSampler = input; + filter.uniforms.filterGlobals = this.globalUniforms; + + // TODO make it so that the order of this does not matter.. + // because it does at the moment cos of global uniforms. + // they need to get resynced + + renderer.state.setState(filter.state); + renderer.shader.bind(filter); + + if (filter.legacy) + { + this.quadUv.map(input._frame, input.filterFrame); + + renderer.geometry.bind(this.quadUv); + renderer.geometry.draw(DRAW_MODES.TRIANGLES); + } + else + { + renderer.geometry.bind(this.quad); + renderer.geometry.draw(DRAW_MODES.TRIANGLE_STRIP); + } + } + + /** + * Calculates the mapped matrix. + * + * TODO playing around here.. this is temporary - (will end up in the shader) + * this returns a matrix that will normalise map filter cords in the filter to screen space + * + * @param {PIXI.Matrix} outputMatrix - the matrix to output to. + * @return {PIXI.Matrix} The mapped matrix. + */ + calculateScreenSpaceMatrix(outputMatrix) + { + const currentState = this.activeState; + + return filterTransforms.calculateScreenSpaceMatrix( + outputMatrix, + currentState.sourceFrame, + currentState.destinationFrame + ); + } + + /** + * This will map the filter coord so that a texture can be used based on the transform of a sprite + * + * @param {PIXI.Matrix} outputMatrix - The matrix to output to. + * @param {PIXI.Sprite} sprite - The sprite to map to. + * @return {PIXI.Matrix} The mapped matrix. + */ + calculateSpriteMatrix(outputMatrix, sprite) + { + const currentState = this.activeState; + + return filterTransforms.calculateSpriteMatrix( + outputMatrix, + currentState.sourceFrame, + currentState.destinationFrame, + sprite + ); + } + + /** + * Destroys this Filter System. + * + * @param {boolean} [contextLost=false] context was lost, do not free shaders + * + */ + destroy(contextLost = false) + { + if (!contextLost) + { + this.emptyPool(); + } + else + { + this.texturePool = {}; + } + } + + /** + * Gets a Power-of-Two render texture. + * + * TODO move to a seperate class could be on renderer? + * also - could cause issue with multiple contexts? + * + * @private + * @param {WebGLRenderingContext} gl - The webgl rendering context + * @param {number} minWidth - The minimum width of the render target. + * @param {number} minHeight - The minimum height of the render target. + * @param {number} resolution - The resolution of the render target. + * @return {PIXI.RenderTarget} The new render target. + */ + getPotFilterTexture(minWidth, minHeight, resolution) + { + minWidth = bitTwiddle.nextPow2(minWidth); + minHeight = bitTwiddle.nextPow2(minHeight); + resolution = resolution || 1; + + const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + + if (!this.texturePool[key]) + { + this.texturePool[key] = []; + } + + let renderTexture = this.texturePool[key].pop(); + + if (!renderTexture) + { + // temporary bypass cache.. + // internally - this will cause a texture to be bound.. + renderTexture = RenderTexture.create({ + width: minWidth, + height: minHeight, + resolution, + }); + } + + return renderTexture; + } + + /** + * Gets extra render texture to use inside current filter + * + * @param {number} resolution resolution of the renderTexture + * @returns {PIXI.RenderTexture} + */ + getFilterTexture(resolution) + { + const rt = this.activeState.renderTexture; + + const filterTexture = this.getPotFilterTexture(rt.width, rt.height, resolution || rt.baseTexture.resolution); + + filterTexture.filterFrame = rt.filterFrame; + + return filterTexture; + } + + /** + * Frees a render texture back into the pool. + * + * @param {PIXI.RenderTarget} renderTexture - The renderTarget to free + */ + returnFilterTexture(renderTexture) + { + renderTexture.filterFrame = null; + + const base = renderTexture.baseTexture; + + const minWidth = base.width; + const minHeight = base.height; + + const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + + this.texturePool[key].push(renderTexture); + } + + /** + * Empties the texture pool. + * + */ + emptyPool() + { + for (const i in this.texturePool) + { + const textures = this.texturePool[i]; + + if (textures) + { + for (let j = 0; j < textures.length; j++) + { + textures[j].destroy(true); + } + } + } + + this.texturePool = {}; + } +} diff --git a/packages/core/src/filters/defaultFilter.frag b/packages/core/src/filters/defaultFilter.frag new file mode 100644 index 0000000..a14c1be --- /dev/null +++ b/packages/core/src/filters/defaultFilter.frag @@ -0,0 +1,22 @@ +varying vec2 vTextureCoord; +varying vec2 vFilterCoord; + +uniform sampler2D uSampler; +uniform sampler2D filterSampler; + +void main(void){ + vec4 masky = texture2D(filterSampler, vFilterCoord); + vec4 sample = texture2D(uSampler, vTextureCoord); + vec4 color; + if(mod(vFilterCoord.x, 1.0) > 0.5) + { + color = vec4(1.0, 0.0, 0.0, 1.0); + } + else + { + color = vec4(0.0, 1.0, 0.0, 1.0); + } + // gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0); + gl_FragColor = mix(sample, masky, 0.5); + gl_FragColor *= sample.a; +} diff --git a/packages/core/src/filters/defaultFilter.vert b/packages/core/src/filters/defaultFilter.vert new file mode 100644 index 0000000..e1febca --- /dev/null +++ b/packages/core/src/filters/defaultFilter.vert @@ -0,0 +1,14 @@ +attribute vec2 aVertexPosition; +attribute vec2 aTextureCoord; + +uniform mat3 projectionMatrix; +uniform mat3 filterMatrix; + +varying vec2 vTextureCoord; +varying vec2 vFilterCoord; + +void main(void){ + gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); + vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy; + vTextureCoord = aTextureCoord ; +} diff --git a/packages/core/src/filters/filterTransforms.js b/packages/core/src/filters/filterTransforms.js new file mode 100644 index 0000000..87a3cd6 --- /dev/null +++ b/packages/core/src/filters/filterTransforms.js @@ -0,0 +1,50 @@ +import { Matrix } from '@pixi/math'; + +/** + * Calculates the mapped matrix + * @param filterArea {Rectangle} The filter area + * @param sprite {Sprite} the target sprite + * @param outputMatrix {Matrix} @alvin + * @private + */ +// this returns a matrix that will normalise map filter cords in the filter to screen space +export function calculateScreenSpaceMatrix(outputMatrix, filterArea, textureSize) +{ + // TODO unwrap? + const mappedMatrix = outputMatrix.identity(); + + mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); + + mappedMatrix.scale(textureSize.width, textureSize.height); + + return mappedMatrix; +} + +export function calculateNormalizedScreenSpaceMatrix(outputMatrix, filterArea, textureSize) +{ + const mappedMatrix = outputMatrix.identity(); + + mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); + + const translateScaleX = (textureSize.width / filterArea.width); + const translateScaleY = (textureSize.height / filterArea.height); + + mappedMatrix.scale(translateScaleX, translateScaleY); + + return mappedMatrix; +} + +// this will map the filter coord so that a texture can be used based on the transform of a sprite +export function calculateSpriteMatrix(outputMatrix, filterArea, textureSize, sprite) +{ + const orig = sprite._texture.orig; + const mappedMatrix = outputMatrix.set(textureSize.width, 0, 0, textureSize.height, filterArea.x, filterArea.y); + const worldTransform = sprite.worldTransform.copyTo(Matrix.TEMP_MATRIX); + + worldTransform.invert(); + mappedMatrix.prepend(worldTransform); + mappedMatrix.scale(1.0 / orig.width, 1.0 / orig.height); + mappedMatrix.translate(sprite.anchor.x, sprite.anchor.y); + + return mappedMatrix; +} diff --git a/packages/core/src/filters/spriteMask/SpriteMaskFilter.js b/packages/core/src/filters/spriteMask/SpriteMaskFilter.js new file mode 100644 index 0000000..91697b7 --- /dev/null +++ b/packages/core/src/filters/spriteMask/SpriteMaskFilter.js @@ -0,0 +1,63 @@ +import Filter from '../Filter'; +import { Matrix } from '@pixi/math'; +import vertex from './spriteMaskFilter.vert'; +import fragment from './spriteMaskFilter.frag'; +import { default as TextureMatrix } from '../../textures/TextureMatrix'; + +/** + * The SpriteMaskFilter class + * + * @class + * @extends PIXI.Filter + * @memberof PIXI + */ +export default class SpriteMaskFilter extends Filter +{ + /** + * @param {PIXI.Sprite} sprite - the target sprite + */ + constructor(sprite) + { + const maskMatrix = new Matrix(); + + super(vertex, fragment); + + sprite.renderable = false; + + this.maskSprite = sprite; + this.maskMatrix = maskMatrix; + } + + /** + * Applies the filter + * + * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + */ + apply(filterManager, input, output) + { + const maskSprite = this.maskSprite; + const tex = this.maskSprite.texture; + + if (!tex.valid) + { + return; + } + if (!tex.transform) + { + // margin = 0.0, let it bleed a bit, shader code becomes easier + // assuming that atlas textures were made with 1-pixel padding + tex.transform = new TextureMatrix(tex, 0.0); + } + tex.transform.update(); + + this.uniforms.mask = tex; + this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite) + .prepend(tex.transform.mapCoord); + this.uniforms.alpha = maskSprite.worldAlpha; + this.uniforms.maskClamp = tex.transform.uClampFrame; + + filterManager.applyFilter(this, input, output); + } +} diff --git a/packages/core/src/filters/spriteMask/spriteMaskFilter.frag b/packages/core/src/filters/spriteMask/spriteMaskFilter.frag new file mode 100644 index 0000000..0e4aef8 --- /dev/null +++ b/packages/core/src/filters/spriteMask/spriteMaskFilter.frag @@ -0,0 +1,23 @@ +varying vec2 vMaskCoord; +varying vec2 vTextureCoord; + +uniform sampler2D uSampler; +uniform sampler2D mask; +uniform float alpha; +uniform vec4 maskClamp; + +void main(void) +{ + float clip = step(3.5, + step(maskClamp.x, vMaskCoord.x) + + step(maskClamp.y, vMaskCoord.y) + + step(vMaskCoord.x, maskClamp.z) + + step(vMaskCoord.y, maskClamp.w)); + + vec4 original = texture2D(uSampler, vTextureCoord); + vec4 masky = texture2D(mask, vMaskCoord); + + original *= (masky.r * masky.a * alpha * clip); + + gl_FragColor = original; +} diff --git a/packages/core/src/filters/spriteMask/spriteMaskFilter.vert b/packages/core/src/filters/spriteMask/spriteMaskFilter.vert new file mode 100644 index 0000000..d23a30c --- /dev/null +++ b/packages/core/src/filters/spriteMask/spriteMaskFilter.vert @@ -0,0 +1,16 @@ +attribute vec2 aVertexPosition; +attribute vec2 aTextureCoord; + +uniform mat3 projectionMatrix; +uniform mat3 otherMatrix; + +varying vec2 vMaskCoord; +varying vec2 vTextureCoord; + +void main(void) +{ + gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); + + vTextureCoord = aTextureCoord; + vMaskCoord = ( otherMatrix * vec3( aTextureCoord, 1.0) ).xy; +} diff --git a/packages/core/src/framebuffer/FrameBuffer.js b/packages/core/src/framebuffer/FrameBuffer.js new file mode 100644 index 0000000..84e9fcc --- /dev/null +++ b/packages/core/src/framebuffer/FrameBuffer.js @@ -0,0 +1,106 @@ +import Texture from '../textures/BaseTexture'; +import { FORMATS, TYPES } from '@pixi/constants'; + +/** + * Frame buffer + * @class + * @memberof PIXI + */ +export default class FrameBuffer +{ + constructor(width, height) + { + this.width = width || 100; + this.height = height || 100; + + this.stencil = false; + this.depth = false; + + this.dirtyId = 0; + this.dirtyFormat = 0; + this.dirtySize = 0; + + this.depthTexture = null; + this.colorTextures = []; + + this.glFrameBuffers = {}; + } + + get colorTexture() + { + return this.colorTextures[0]; + } + + addColorTexture(index, texture) + { + // TODO add some validation to the texture - same width / height etc? + this.colorTextures[index || 0] = texture || new Texture(null, { scaleMode: 0, + resolution: 1, + mipmap: false, + width: this.width, + height: this.height });// || new Texture(); + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + addDepthTexture(texture) + { + /* eslint-disable max-len */ + this.depthTexture = texture || new Texture(null, { scaleMode: 0, + resolution: 1, + width: this.width, + height: this.height, + mipmap: false, + format: FORMATS.DEPTH_COMPONENT, + type: TYPES.UNSIGNED_SHORT });// UNSIGNED_SHORT; + /* eslint-disable max-len */ + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + enableDepth() + { + this.depth = true; + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + enableStencil() + { + this.stencil = true; + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + resize(width, height) + { + if (width === this.width && height === this.height) return; + + this.width = width; + this.height = height; + + this.dirtyId++; + this.dirtySize++; + + for (let i = 0; i < this.colorTextures.length; i++) + { + this.colorTextures[i].setSize(width, height); + } + + if (this.depthTexture) + { + this.depthTexture.setSize(width, height); + } + } +} diff --git a/packages/core/src/framebuffer/FramebufferSystem.js b/packages/core/src/framebuffer/FramebufferSystem.js new file mode 100644 index 0000000..2cc2437 --- /dev/null +++ b/packages/core/src/framebuffer/FramebufferSystem.js @@ -0,0 +1,246 @@ +import System from '../System'; +import { Rectangle } from '@pixi/math'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class FramebufferSystem extends System +{ + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + contextChange() + { + this.gl = this.renderer.gl; + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + this.current = null; + this.viewport = new Rectangle(); + + this.drawBufferExtension = this.renderer.context.extensions.drawBuffers; + } + + bind(framebuffer, frame) + { + const gl = this.gl; + + this.current = framebuffer; + + if (framebuffer) + { + // TODO cacheing layer! + + const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID] || this.initFramebuffer(framebuffer); + + gl.bindFramebuffer(gl.FRAMEBUFFER, fbo.framebuffer); + // makesure all textures are unbound.. + + // now check for updates... + if (fbo.dirtyId !== framebuffer.dirtyId) + { + fbo.dirtyId = framebuffer.dirtyId; + + if (fbo.dirtyFormat !== framebuffer.dirtyFormat) + { + fbo.dirtyFormat = framebuffer.dirtyFormat; + this.updateFramebuffer(framebuffer); + } + else if (fbo.dirtySize !== framebuffer.dirtySize) + { + fbo.dirtySize = framebuffer.dirtySize; + this.resizeFramebuffer(framebuffer); + } + } + + for (let i = 0; i < framebuffer.colorTextures.length; i++) + { + if (framebuffer.colorTextures[i].texturePart) + { + this.renderer.texture.unbind(framebuffer.colorTextures[i].texture); + } + else + { + this.renderer.texture.unbind(framebuffer.colorTextures[i]); + } + } + + if (framebuffer.depthTexture) + { + this.renderer.texture.unbind(framebuffer.depthTexture); + } + + if (frame) + { + this.setViewport(frame.x, frame.y, frame.width, frame.height); + } + else + { + this.setViewport(0, 0, framebuffer.width, framebuffer.height); + } + } + else + { + gl.bindFramebuffer(gl.FRAMEBUFFER, null); + + if (frame) + { + this.setViewport(frame.x, frame.y, frame.width, frame.height); + } + else + { + this.setViewport(0, 0, this.renderer.width, this.renderer.height); + } + } + } + + setViewport(x, y, width, height) + { + const v = this.viewport; + + if (v.width !== width || v.height !== height || v.x !== x || v.y !== y) + { + v.x = x; + v.y = y; + v.width = width; + v.height = height; + + this.gl.viewport(x, y, width, height); + } + } + + get size() + { + if (this.current) + { + // TODO store temp + return { x: 0, y: 0, width: this.current.width, height: this.current.height }; + } + + return { x: 0, y: 0, width: this.renderer.width, height: this.renderer.height }; + } + + clear(r, g, b, a) + { + const gl = this.gl; + + // TODO clear color can be set only one right? + gl.clearColor(r, g, b, a); + gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); + } + + // private functions... + + initFramebuffer(framebuffer) + { + const gl = this.gl; + + // TODO - make this a class? + const fbo = { + framebuffer: gl.createFramebuffer(), + stencil: null, + dirtyId: 0, + dirtyFormat: 0, + dirtySize: 0, + }; + + framebuffer.glFrameBuffers[this.CONTEXT_UID] = fbo; + + return fbo; + } + + resizeFramebuffer(framebuffer) + { + const gl = this.gl; + + if (framebuffer.stencil || framebuffer.depth) + { + gl.bindRenderbuffer(gl.RENDERBUFFER, this.stencil); + gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); + } + } + + updateFramebuffer(framebuffer) + { + const gl = this.gl; + + const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID]; + + // bind the color texture + const colorTextures = framebuffer.colorTextures; + + let count = colorTextures.length; + + if (!this.drawBufferExtension) + { + count = Math.min(count, 1); + } + + const activeTextures = []; + + for (let i = 0; i < count; i++) + { + const texture = framebuffer.colorTextures[i]; + + if (texture.texturePart) + { + this.renderer.texture.bind(texture.texture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.COLOR_ATTACHMENT0 + i, + gl.TEXTURE_CUBE_MAP_NEGATIVE_X + texture.side, + texture.texture._glTextures[this.CONTEXT_UID].texture, + 0); + } + else + { + this.renderer.texture.bind(texture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.COLOR_ATTACHMENT0 + i, + gl.TEXTURE_2D, + texture._glTextures[this.CONTEXT_UID].texture, + 0); + } + + activeTextures.push(gl.COLOR_ATTACHMENT0 + i); + } + + if (this.drawBufferExtension && activeTextures.length > 1) + { + this.drawBufferExtension.drawBuffersWEBGL(activeTextures); + } + + if (framebuffer.depthTexture) + { + const depthTextureExt = this.renderer.context.extensions.depthTexture; + + if (depthTextureExt) + { + const depthTexture = framebuffer.depthTexture; + + this.renderer.texture.bind(depthTexture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.DEPTH_ATTACHMENT, + gl.TEXTURE_2D, + depthTexture._glTextures[this.CONTEXT_UID].texture, + 0); + } + } + + if (framebuffer.stencil || framebuffer.depth) + { + fbo.stencil = gl.createRenderbuffer(); + + gl.bindRenderbuffer(gl.RENDERBUFFER, fbo.stencil); + + // TODO.. this is depth AND stencil? + gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, fbo.stencil); + gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); + // fbo.enableStencil(); + } + } +} diff --git a/packages/core/src/geometry/GLBuffer.js b/packages/core/src/geometry/GLBuffer.js new file mode 100644 index 0000000..0477e8a --- /dev/null +++ b/packages/core/src/geometry/GLBuffer.js @@ -0,0 +1,9 @@ +export default class GLBuffer +{ + constructor(buffer) + { + this.buffer = buffer; + this.updateID = -1; + this.byteLength = -1; + } +} diff --git a/packages/core/src/geometry/GeometrySystem.js b/packages/core/src/geometry/GeometrySystem.js new file mode 100644 index 0000000..959f827 --- /dev/null +++ b/packages/core/src/geometry/GeometrySystem.js @@ -0,0 +1,388 @@ +import System from '../System'; +import GLBuffer from './GLBuffer'; + +const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class GeometrySystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this._activeGeometry = null; + this._activeVao = null; + + this.hasVao = true; + this.hasInstance = true; + } + + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + contextChange() + { + const gl = this.gl = this.renderer.gl; + + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + + // webgl2 + if (!gl.createVertexArray) + { + // webgl 1! + let nativeVaoExtension = this.renderer.context.extensions.vertexArrayObject; + + if (this.renderer.legacy) + { + nativeVaoExtension = null; + } + + if (nativeVaoExtension) + { + gl.createVertexArray = () => + nativeVaoExtension.createVertexArrayOES(); + + gl.bindVertexArray = (vao) => + nativeVaoExtension.bindVertexArrayOES(vao); + + gl.deleteVertexArray = (vao) => + nativeVaoExtension.deleteVertexArrayOES(vao); + } + else + { + this.hasVao = false; + gl.createVertexArray = () => + { + // empty + }; + + gl.bindVertexArray = () => + { + // empty + }; + + gl.deleteVertexArray = () => + { + // empty + }; + } + } + + if (!gl.vertexAttribDivisor) + { + const instanceExt = gl.getExtension('ANGLE_instanced_arrays'); + + if (instanceExt) + { + gl.vertexAttribDivisor = (a, b) => + instanceExt.vertexAttribDivisorANGLE(a, b); + + gl.drawElementsInstanced = (a, b, c, d, e) => + instanceExt.drawElementsInstancedANGLE(a, b, c, d, e); + + gl.drawArraysInstanced = (a, b, c, d) => + instanceExt.drawArraysInstancedANGLE(a, b, c, d); + } + else + { + this.hasInstance = false; + } + } + } + + /** + * Binds geometry so that is can be drawn. Creating a Vao if required + * @private + * @param {PIXI.Geometry} geometry instance of geometry to bind + */ + bind(geometry, shader) + { + shader = shader || this.renderer.shader.shader; + + const gl = this.gl; + + // not sure the best way to address this.. + // currently different shaders require different VAOs for the same geometry + // Still mulling over the best way to solve this one.. + // will likely need to modify the shader attribute locations at run time! + let vaos = geometry.glVertexArrayObjects[this.CONTEXT_UID]; + + if (!vaos) + { + geometry.glVertexArrayObjects[this.CONTEXT_UID] = vaos = {}; + } + + const vao = vaos[shader.program.id] || this.initGeometryVao(geometry, shader.program); + + this._activeGeometry = geometry; + + if (this._activeVao !== vao) + { + this._activeVao = vao; + + if (this.hasVao) + { + gl.bindVertexArray(vao); + } + else + { + this.activateVao(geometry, shader.program); + } + } + + // TODO - optimise later! + // don't need to loop through if nothing changed! + // maybe look to add an 'autoupdate' to geometry? + this.updateBuffers(); + } + + reset() + { + this.unbind(); + } + + updateBuffers() + { + const geometry = this._activeGeometry; + const gl = this.gl; + + for (let i = 0; i < geometry.buffers.length; i++) + { + const buffer = geometry.buffers[i]; + + const glBuffer = buffer._glBuffers[this.CONTEXT_UID]; + + if (buffer._updateID !== glBuffer.updateID) + { + glBuffer.updateID = buffer._updateID; + + // TODO can cache this on buffer! maybe added a getter / setter? + const type = buffer.index ? gl.ELEMENT_ARRAY_BUFFER : gl.ARRAY_BUFFER; + const drawType = buffer.static ? gl.STATIC_DRAW : gl.DYNAMIC_DRAW; + + gl.bindBuffer(type, glBuffer.buffer); + + if (glBuffer.byteLength >= buffer.data.byteLength) + { + // offset is always zero for now! + gl.bufferSubData(type, 0, buffer.data); + } + else + { + gl.bufferData(type, buffer.data, drawType); + } + } + } + } + + checkCompatability(geometry, program) + { + // geometry must have at least all the attributes that the shader requires. + const geometryAttributes = geometry.attributes; + const shaderAttributes = program.attributeData; + + for (const j in shaderAttributes) + { + if (!geometryAttributes[j]) + { + throw new Error(`shader and geometry incompatible, geometry missing the "${j}" attribute`); + } + } + } + + /** + * Creates a Vao with the same structure as the geometry and stores it on the geometry. + * @private + * @param {PIXI.Geometry} geometry instance of geometry to to generate Vao for + */ + initGeometryVao(geometry, program) + { + this.checkCompatability(geometry, program); + + const gl = this.gl; + const CONTEXT_UID = this.CONTEXT_UID; + const buffers = geometry.buffers; + const attributes = geometry.attributes; + + const tempStride = {}; + const tempStart = {}; + + for (const j in buffers) + { + tempStride[j] = 0; + tempStart[j] = 0; + } + + for (const j in attributes) + { + if (!attributes[j].size && program.attributeData[j]) + { + attributes[j].size = program.attributeData[j].size; + } + + tempStride[attributes[j].buffer] += attributes[j].size * byteSizeMap[attributes[j].type]; + } + + for (const j in attributes) + { + const attribute = attributes[j]; + const attribSize = attribute.size; + + if (attribute.stride === undefined) + { + if (tempStride[attribute.buffer] === attribSize * byteSizeMap[attribute.type]) + { + attribute.stride = 0; + } + else + { + attribute.stride = tempStride[attribute.buffer]; + } + } + + if (attribute.start === undefined) + { + attribute.start = tempStart[attribute.buffer]; + + tempStart[attribute.buffer] += attribSize * byteSizeMap[attribute.type]; + } + } + + // first update - and create the buffers! + // only create a gl buffer if it actually gets + for (let i = 0; i < buffers.length; i++) + { + const buffer = buffers[i]; + + if (!buffer._glBuffers[CONTEXT_UID]) + { + buffer._glBuffers[CONTEXT_UID] = new GLBuffer(gl.createBuffer()); + } + } + + // TODO - maybe make this a data object? + // lets wait to see if we need to first! + const vao = gl.createVertexArray(); + + gl.bindVertexArray(vao); + + this.activateVao(geometry, program); + + gl.bindVertexArray(null); + + // add it to the cache! + geometry.glVertexArrayObjects[this.CONTEXT_UID][program.id] = vao; + + return vao; + } + + activateVao(geometry, program) + { + const gl = this.gl; + const CONTEXT_UID = this.CONTEXT_UID; + const buffers = geometry.buffers; + const attributes = geometry.attributes; + + if (geometry.indexBuffer) + { + // first update the index buffer if we have one.. + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, geometry.indexBuffer._glBuffers[CONTEXT_UID].buffer); + } + + let lastBuffer = null; + + // add a new one! + for (const j in attributes) + { + const attribute = attributes[j]; + const buffer = buffers[attribute.buffer]; + const glBuffer = buffer._glBuffers[CONTEXT_UID]; + + if (program.attributeData[j]) + { + if (lastBuffer !== glBuffer) + { + gl.bindBuffer(gl.ARRAY_BUFFER, glBuffer.buffer); + + lastBuffer = glBuffer; + } + + const location = program.attributeData[j].location; + + // TODO introduce state again + // we can optimise this for older devices that have no VAOs + gl.enableVertexAttribArray(location); + + gl.vertexAttribPointer(location, + attribute.size, + attribute.type || gl.FLOAT, + attribute.normalized, + attribute.stride, + attribute.start); + + if (attribute.instance) + { + // TODO calculate instance count based of this... + if (this.hasInstance) + { + gl.vertexAttribDivisor(location, 1); + } + else + { + throw new Error('geometry error, GPU Instancing is not supported on this device'); + } + } + } + } + } + + draw(type, size, start, instanceCount) + { + const gl = this.gl; + const geometry = this._activeGeometry; + + // TODO.. this should not change so maybe cache the function? + + if (geometry.indexBuffer) + { + if (geometry.instanced) + { + /* eslint-disable max-len */ + gl.drawElementsInstanced(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2, instanceCount || 1); + /* eslint-enable max-len */ + } + else + { + gl.drawElements(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2); + } + } + else + if (geometry.instanced) + { + // TODO need a better way to calculate size.. + gl.drawArraysInstanced(type, start, size || geometry.getSize(), instanceCount || 1); + } + else + { + gl.drawArrays(type, start, size || geometry.getSize()); + } + + return this; + } + + unbind() + { + this.gl.bindVertexArray(null); + this._activeVao = null; + this._activeGeometry = null; + } +} diff --git a/packages/core/src/geometry/utils/setVertexAttribArrays.js b/packages/core/src/geometry/utils/setVertexAttribArrays.js new file mode 100644 index 0000000..23b6959 --- /dev/null +++ b/packages/core/src/geometry/utils/setVertexAttribArrays.js @@ -0,0 +1,54 @@ +// var GL_MAP = {}; + +/** + * @param gl {WebGLRenderingContext} The current WebGL context + * @param attribs {*} + * @param state {*} + */ +export default function setVertexAttribArrays(gl, attribs, state) +{ + let i; + + if (state) + { + const tempAttribState = state.tempAttribState; + const attribState = state.attribState; + + for (i = 0; i < tempAttribState.length; i++) + { + tempAttribState[i] = false; + } + + // set the new attribs + for (i = 0; i < attribs.length; i++) + { + tempAttribState[attribs[i].attribute.location] = true; + } + + for (i = 0; i < attribState.length; i++) + { + if (attribState[i] !== tempAttribState[i]) + { + attribState[i] = tempAttribState[i]; + + if (state.attribState[i]) + { + gl.enableVertexAttribArray(i); + } + else + { + gl.disableVertexAttribArray(i); + } + } + } + } + else + { + for (i = 0; i < attribs.length; i++) + { + const attrib = attribs[i]; + + gl.enableVertexAttribArray(attrib.attribute.location); + } + } +} diff --git a/packages/core/src/index.js b/packages/core/src/index.js index 58c456a..fd1f547 100644 --- a/packages/core/src/index.js +++ b/packages/core/src/index.js @@ -1,30 +1,30 @@ import * as resources from './textures/resources'; -import * as systems from './renderers/systems'; +import * as systems from './systems'; -export { resources }; export { systems }; +export { resources }; -export { default as Renderer } from './renderers/Renderer'; -export { default as AbstractRenderer } from './renderers/AbstractRenderer'; -export { default as FrameBuffer } from './textures/FrameBuffer'; +export { default as System } from './System'; +export { default as Renderer } from './Renderer'; +export { default as AbstractRenderer } from './AbstractRenderer'; +export { default as FrameBuffer } from './framebuffer/FrameBuffer'; export { default as CubeTexture } from './textures/CubeTexture'; export { default as BaseTexture } from './textures/BaseTexture'; export { default as Texture } from './textures/Texture'; export { default as TextureMatrix } from './textures/TextureMatrix'; -export { default as RenderTexture } from './textures/RenderTexture'; -export { default as BaseRenderTexture } from './textures/BaseRenderTexture'; +export { default as RenderTexture } from './renderTexture/RenderTexture'; +export { default as BaseRenderTexture } from './renderTexture/BaseRenderTexture'; export { default as TextureUvs } from './textures/TextureUvs'; -export { default as State } from './renderers/State'; -export { default as ObjectRenderer } from './renderers/utils/ObjectRenderer'; -export { default as Quad } from './renderers/utils/Quad'; -export { default as QuadUv } from './renderers/utils/QuadUv'; -export { default as checkMaxIfStatmentsInShader } from './renderers/utils/checkMaxIfStatmentsInShader'; +export { default as State } from './state/State'; +export { default as ObjectRenderer } from './batch/ObjectRenderer'; +export { default as Quad } from './utils/Quad'; +export { default as QuadUv } from './utils/QuadUv'; +export { default as checkMaxIfStatmentsInShader } from './shader/utils/checkMaxIfStatmentsInShader'; export { default as Shader } from './shader/Shader'; export { default as Program } from './shader/Program'; export { default as UniformGroup } from './shader/UniformGroup'; -export { default as SpriteMaskFilter } from './renderers/filters/spriteMask/SpriteMaskFilter'; -export { default as Filter } from './renderers/filters/Filter'; +export { default as SpriteMaskFilter } from './filters/spriteMask/SpriteMaskFilter'; +export { default as Filter } from './filters/Filter'; export { default as Attribute } from './geometry/Attribute'; export { default as Buffer } from './geometry/Buffer'; export { default as Geometry } from './geometry/Geometry'; - diff --git a/packages/core/src/mask/MaskSystem.js b/packages/core/src/mask/MaskSystem.js new file mode 100644 index 0000000..99174c2 --- /dev/null +++ b/packages/core/src/mask/MaskSystem.js @@ -0,0 +1,199 @@ +import System from '../System'; +import AlphaMaskFilter from '../filters/spriteMask/SpriteMaskFilter'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class MaskSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + // TODO - we don't need both! + this.scissor = false; + this.scissorData = null; + this.scissorRenderTarget = null; + + this.enableScissor = false; + + this.alphaMaskPool = []; + this.alphaMaskIndex = 0; + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.DisplayObject} target - Display Object to push the mask to + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + push(target, maskData) + { + // TODO the root check means scissor rect will not + // be used on render textures more info here: + // https://github.com/pixijs/pixi.js/pull/3545 + + if (maskData.texture) + { + this.pushSpriteMask(target, maskData); + } + else if (this.enableScissor + && !this.scissor + && this.renderer._activeRenderTarget.root + && !this.renderer.stencil.stencilMaskStack.length + && maskData.isFastRect()) + { + const matrix = maskData.worldTransform; + + let rot = Math.atan2(matrix.b, matrix.a); + + // use the nearest degree! + rot = Math.round(rot * (180 / Math.PI)); + + if (rot % 90) + { + this.pushStencilMask(maskData); + } + else + { + this.pushScissorMask(target, maskData); + } + } + else + { + this.pushStencilMask(maskData); + } + } + + /** + * Removes the last mask from the mask stack and doesn't return it. + * + * @param {PIXI.DisplayObject} target - Display Object to pop the mask from + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + pop(target, maskData) + { + if (maskData.texture) + { + this.popSpriteMask(target, maskData); + } + else if (this.enableScissor && !this.renderer.stencil.stencilMaskStack.length) + { + this.popScissorMask(target, maskData); + } + else + { + this.popStencilMask(target, maskData); + } + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.RenderTarget} target - Display Object to push the sprite mask to + * @param {PIXI.Sprite} maskData - Sprite to be used as the mask + */ + pushSpriteMask(target, maskData) + { + let alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; + + if (!alphaMaskFilter) + { + alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; + } + + alphaMaskFilter[0].resolution = this.renderer.resolution; + alphaMaskFilter[0].maskSprite = maskData; + + // TODO - may cause issues! + target.filterArea = maskData.getBounds(true); + + this.renderer.filterSystem.pushFilter(target, alphaMaskFilter); + + this.alphaMaskIndex++; + } + + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popSpriteMask() + { + this.renderer.filterSystem.popFilter(); + this.alphaMaskIndex--; + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + pushStencilMask(maskData) + { + this.renderer.batch.flush(); + this.renderer.stencil.pushStencil(maskData); + } + + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popStencilMask() + { + // this.renderer.currentRenderer.stop(); + this.renderer.stencil.popStencil(); + } + + /** + * + * @param {PIXI.DisplayObject} target - Display Object to push the mask to + * @param {PIXI.Graphics} maskData - The masking data. + */ + pushScissorMask(target, maskData) + { + maskData.renderable = true; + + const renderTarget = this.renderer._activeRenderTarget; + + const bounds = maskData.getBounds(); + + bounds.fit(renderTarget.size); + maskData.renderable = false; + + this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); + + const resolution = this.renderer.resolution; + + this.renderer.gl.scissor( + bounds.x * resolution, + (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, + bounds.width * resolution, + bounds.height * resolution + ); + + this.scissorRenderTarget = renderTarget; + this.scissorData = maskData; + this.scissor = true; + } + + /** + * + * + */ + popScissorMask() + { + this.scissorRenderTarget = null; + this.scissorData = null; + this.scissor = false; + + // must be scissor! + const gl = this.renderer.gl; + + gl.disable(gl.SCISSOR_TEST); + } +} diff --git a/packages/core/src/mask/StencilSystem.js b/packages/core/src/mask/StencilSystem.js new file mode 100644 index 0000000..62796a5 --- /dev/null +++ b/packages/core/src/mask/StencilSystem.js @@ -0,0 +1,133 @@ +import System from '../System'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class StencilSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + this.stencilMaskStack = []; + } + + /** + * Changes the mask stack that is used by this System. + * + * @param {PIXI.Graphics[]} stencilMaskStack - The mask stack + */ + setMaskStack(stencilMaskStack) + { + const gl = this.renderer.gl; + + if (stencilMaskStack.length !== this.stencilMaskStack.length) + { + if (stencilMaskStack.length === 0) + { + gl.disable(gl.STENCIL_TEST); + } + else + { + gl.enable(gl.STENCIL_TEST); + } + } + + this.stencilMaskStack = stencilMaskStack; + } + + /** + * Applies the Mask and adds it to the current stencil stack. @alvin + * + * @param {PIXI.Graphics} graphics - The mask + */ + pushStencil(graphics) + { + this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); + + // this.renderer._activeRenderTarget.attachStencilBuffer(); + + const gl = this.renderer.gl; + const prevMaskCount = this.stencilMaskStack.length; + + if (prevMaskCount === 0) + { + gl.enable(gl.STENCIL_TEST); + } + + this.stencilMaskStack.push(graphics); + + // Increment the refference stencil value where the new mask overlaps with the old ones. + gl.colorMask(false, false, false, false); + gl.stencilFunc(gl.EQUAL, prevMaskCount, this._getBitwiseMask()); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.INCR); + this.renderer.plugins.graphics.render(graphics); + + this._useCurrent(); + } + + /** + * Removes the last mask from the stencil stack. @alvin + */ + popStencil() + { + this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); + + const gl = this.renderer.gl; + const graphics = this.stencilMaskStack.pop(); + + if (this.stencilMaskStack.length === 0) + { + // the stack is empty! + gl.disable(gl.STENCIL_TEST); + gl.clear(gl.STENCIL_BUFFER_BIT); + gl.clearStencil(0); + } + else + { + // Decrement the refference stencil value where the popped mask overlaps with the other ones + gl.colorMask(false, false, false, false); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.DECR); + this.renderer.plugins.graphics.render(graphics); + + this._useCurrent(); + } + } + + /** + * Setup renderer to use the current stencil data. + */ + _useCurrent() + { + const gl = this.renderer.gl; + + gl.colorMask(true, true, true, true); + gl.stencilFunc(gl.EQUAL, this.stencilMaskStack.length, this._getBitwiseMask()); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP); + } + + /** + * Fill 1s equal to the number of acitve stencil masks. + * + * @return {number} The bitwise mask. + */ + _getBitwiseMask() + { + return (1 << this.stencilMaskStack.length) - 1; + } + + /** + * Destroys the mask stack. + * + */ + destroy() + { + super.destroy(this); + + this.stencilMaskStack = null; + } +} diff --git a/packages/core/src/projection/ProjectionSystem.js b/packages/core/src/projection/ProjectionSystem.js new file mode 100644 index 0000000..6e63b73 --- /dev/null +++ b/packages/core/src/projection/ProjectionSystem.js @@ -0,0 +1,73 @@ +import System from '../System'; +import { Matrix } from '@pixi/math'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class ProjectionSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.projectionMatrix = new Matrix(); + } + + update(destinationFrame, sourceFrame, resolution, root) + { + this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; + this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; + + this.calculateProjection(this.destinationFrame, this.sourceFrame, resolution, root); + + this.renderer.globalUniforms.uniforms.projectionMatrix = this.projectionMatrix; + this.renderer.globalUniforms.update(); + } + + /** + * Updates the projection matrix based on a projection frame (which is a rectangle) + * + * @param {Rectangle} destinationFrame - The destination frame. + * @param {Rectangle} sourceFrame - The source frame. + */ + calculateProjection(destinationFrame, sourceFrame, resolution, root) + { + const pm = this.projectionMatrix; + + // I don't think we will need this line.. + // pm.identity(); + + if (!root) + { + pm.a = (1 / destinationFrame.width * 2) * resolution; + pm.d = (1 / destinationFrame.height * 2) * resolution; + + pm.tx = -1 - (sourceFrame.x * pm.a); + pm.ty = -1 - (sourceFrame.y * pm.d); + } + else + { + pm.a = (1 / destinationFrame.width * 2) * resolution; + pm.d = (-1 / destinationFrame.height * 2) * resolution; + + pm.tx = -1 - (sourceFrame.x * pm.a); + pm.ty = 1 - (sourceFrame.y * pm.d); + } + } + + /** + * Sets the transform of the active render target to the given matrix + * + * @param {PIXI.Matrix} matrix - The transformation matrix + */ + setTransform()// matrix) + { + // this._activeRenderTarget.transform = matrix; + } +} diff --git a/packages/core/src/renderTexture/BaseRenderTexture.js b/packages/core/src/renderTexture/BaseRenderTexture.js new file mode 100644 index 0000000..88eb83b --- /dev/null +++ b/packages/core/src/renderTexture/BaseRenderTexture.js @@ -0,0 +1,137 @@ +import BaseTexture from '../textures/BaseTexture'; +import FrameBuffer from '../framebuffer/FrameBuffer'; + +/** + * A BaseRenderTexture is a special texture that allows any PixiJS display object to be rendered to it. + * + * __Hint__: All DisplayObjects (i.e. Sprites) that render to a BaseRenderTexture should be preloaded + * otherwise black rectangles will be drawn instead. + * + * A BaseRenderTexture takes a snapshot of any Display Object given to its render method. The position + * and rotation of the given Display Objects is ignored. For example: + * + * ```js + * let renderer = PIXI.autoDetectRenderer(1024, 1024, { view: canvas, ratio: 1 }); + * let baseRenderTexture = new PIXI.BaseRenderTexture(renderer, 800, 600); + * let sprite = PIXI.Sprite.fromImage("spinObj_01.png"); + * + * sprite.position.x = 800/2; + * sprite.position.y = 600/2; + * sprite.anchor.x = 0.5; + * sprite.anchor.y = 0.5; + * + * baseRenderTexture.render(sprite); + * ``` + * + * The Sprite in this case will be rendered using its local transform. To render this sprite at 0,0 + * you can clear the transform + * + * ```js + * + * sprite.setTransform() + * + * let baseRenderTexture = new PIXI.BaseRenderTexture(100, 100); + * let renderTexture = new PIXI.RenderTexture(baseRenderTexture); + * + * renderer.render(sprite, renderTexture); // Renders to center of RenderTexture + * ``` + * + * @class + * @extends PIXI.BaseTexture + * @memberof PIXI + */ +export default class BaseRenderTexture extends BaseTexture +{ + /** + * @param {object} [options] + * @param {number} [options.width=100] - The width of the base render texture + * @param {number} [options.height=100] - The height of the base render texture + * @param {PIXI.SCALE_MODES} [options.scaleMode] - See {@link PIXI.SCALE_MODES} for possible values + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the texture being generated + */ + constructor(options) + { + if (typeof options === 'number') + { + /* eslint-disable prefer-rest-params */ + // Backward compatibility of signature + const width = arguments[0]; + const height = arguments[1]; + const scaleMode = arguments[2]; + const resolution = arguments[3]; + + options = { width, height, scaleMode, resolution }; + /* eslint-enable prefer-rest-params */ + } + + super(null, options); + + const { width, height } = options || {}; + + // Set defaults + this.mipmap = false; + this.width = Math.ceil(width) || 100; + this.height = Math.ceil(height) || 100; + this.valid = true; + + /** + * A map of renderer IDs to webgl renderTargets + * + * @private + * @member {object} + */ + // this._glRenderTargets = {}; + + /** + * A reference to the canvas render target (we only need one as this can be shared across renderers) + * + * @private + * @member {object} + */ + this._canvasRenderTarget = null; + + this.clearColor = [0, 0, 0, 0]; + + this.frameBuffer = new FrameBuffer(width * this.resolution, height * this.resolution) + .addColorTexture(0, this); + + // TODO - could this be added the systems? + + /** + * The data structure for the stencil masks + * + * @member {PIXI.Graphics[]} + */ + this.stencilMaskStack = []; + + /** + * The data structure for the filters + * + * @member {PIXI.Graphics[]} + */ + this.filterStack = [{}]; + } + + /** + * Resizes the BaseRenderTexture. + * + * @param {number} width - The width to resize to. + * @param {number} height - The height to resize to. + */ + resize(width, height) + { + width = Math.ceil(width); + height = Math.ceil(height); + this.frameBuffer.resize(width * this.resolution, height * this.resolution); + } + + /** + * Destroys this texture + * + */ + destroy() + { + super.destroy(true); + this.renderer = null; + } +} diff --git a/packages/core/src/renderTexture/RenderTexture.js b/packages/core/src/renderTexture/RenderTexture.js new file mode 100644 index 0000000..f1ecf1f --- /dev/null +++ b/packages/core/src/renderTexture/RenderTexture.js @@ -0,0 +1,153 @@ +import BaseRenderTexture from './BaseRenderTexture'; +import Texture from '../textures/Texture'; + +/** + * A RenderTexture is a special texture that allows any PixiJS display object to be rendered to it. + * + * __Hint__: All DisplayObjects (i.e. Sprites) that render to a RenderTexture should be preloaded + * otherwise black rectangles will be drawn instead. + * + * A RenderTexture takes a snapshot of any Display Object given to its render method. For example: + * + * ```js + * let renderer = PIXI.autoDetectRenderer(1024, 1024, { view: canvas, ratio: 1 }); + * let renderTexture = PIXI.RenderTexture.create(800, 600); + * let sprite = PIXI.Sprite.fromImage("spinObj_01.png"); + * + * sprite.position.x = 800/2; + * sprite.position.y = 600/2; + * sprite.anchor.x = 0.5; + * sprite.anchor.y = 0.5; + * + * renderer.render(sprite, renderTexture); + * ``` + * + * The Sprite in this case will be rendered using its local transform. To render this sprite at 0,0 + * you can clear the transform + * + * ```js + * + * sprite.setTransform() + * + * let renderTexture = new PIXI.RenderTexture.create(100, 100); + * + * renderer.render(sprite, renderTexture); // Renders to center of RenderTexture + * ``` + * + * @class + * @extends PIXI.Texture + * @memberof PIXI + */ +export default class RenderTexture extends Texture +{ + /** + * @param {PIXI.BaseRenderTexture} baseRenderTexture - The renderer used for this RenderTexture + * @param {PIXI.Rectangle} [frame] - The rectangle frame of the texture to show + */ + constructor(baseRenderTexture, frame) + { + // support for legacy.. + let _legacyRenderer = null; + + if (!(baseRenderTexture instanceof BaseRenderTexture)) + { + /* eslint-disable prefer-rest-params, no-console */ + const width = arguments[1]; + const height = arguments[2]; + const scaleMode = arguments[3]; + const resolution = arguments[4]; + + // we have an old render texture.. + console.warn(`Please use RenderTexture.create(${width}, ${height}) instead of the ctor directly.`); + _legacyRenderer = arguments[0]; + /* eslint-enable prefer-rest-params, no-console */ + + frame = null; + baseRenderTexture = new BaseRenderTexture({ + width, + height, + scaleMode, + resolution, + }); + } + + /** + * The base texture object that this texture uses + * + * @member {BaseTexture} + */ + super(baseRenderTexture, frame); + + this.legacyRenderer = _legacyRenderer; + + /** + * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. + * + * @member {boolean} + */ + this.valid = true; + + /** + * FilterSystem temporary storage + * @private + * @member {PIXI.Rectangle} + */ + this.filterFrame = null; + + this._updateUvs(); + } + + /** + * Resizes the RenderTexture. + * + * @param {number} width - The width to resize to. + * @param {number} height - The height to resize to. + * @param {boolean} [resizeBaseTexture=true] - Should the baseTexture.width and height values be resized as well? + */ + resize(width, height, resizeBaseTexture = true) + { + width = Math.ceil(width); + height = Math.ceil(height); + + // TODO - could be not required.. + this.valid = (width > 0 && height > 0); + + this._frame.width = this.orig.width = width; + this._frame.height = this.orig.height = height; + + if (resizeBaseTexture) + { + this.baseTexture.resize(width, height); + } + + this._updateUvs(); + } + + /** + * A short hand way of creating a render texture. + * + * @param {object} [options] - Options + * @param {number} [options.width=100] - The width of the render texture + * @param {number} [options.height=100] - The height of the render texture + * @param {number} [options.scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the texture being generated + * @return {PIXI.RenderTexture} The new render texture + */ + static create(options) + { + // fallback, old-style: create(width, height, scaleMode, resolution) + if (typeof options === 'number') + { + /* eslint-disable prefer-rest-params */ + options = { + width: options, + height: arguments[1], + scaleMode: arguments[2], + resolution: arguments[3], + }; + /* eslint-enable prefer-rest-params */ + } + + return new RenderTexture(new BaseRenderTexture(options)); + } +} diff --git a/packages/core/src/renderTexture/RenderTextureSystem.js b/packages/core/src/renderTexture/RenderTextureSystem.js new file mode 100644 index 0000000..8d0ca61 --- /dev/null +++ b/packages/core/src/renderTexture/RenderTextureSystem.js @@ -0,0 +1,115 @@ +import System from '../System'; +import { Rectangle } from '@pixi/math'; + +const tempRect = new Rectangle(); + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class RenderTextureSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.clearColor = renderer._backgroundColorRgba; + + // TODO moe this property somewhere else! + this.defaultMaskStack = []; + this.defaultFilterStack = [{}]; + + // empty render texture? + this.renderTexture = null; + + this.destinationFrame = new Rectangle(); + } + + bind(renderTexture, sourceFrame, destinationFrame) + { + // TODO - do we want this?? + if (this.renderTexture === renderTexture) return; + this.renderTexture = renderTexture; + + const renderer = this.renderer; + + if (renderTexture) + { + const baseTexture = renderTexture.baseTexture; + + if (!destinationFrame) + { + tempRect.width = baseTexture.realWidth; + tempRect.height = baseTexture.realHeight; + + destinationFrame = tempRect; + } + + if (!sourceFrame) + { + sourceFrame = destinationFrame; + } + + this.renderer.framebuffer.bind(baseTexture.frameBuffer, destinationFrame); + + this.renderer.projection.update(destinationFrame, sourceFrame, baseTexture.resolution, false); + this.renderer.stencil.setMaskStack(baseTexture.stencilMaskStack); + } + else + { + // TODO these validation checks happen deeper down.. + // thing they can be avoided.. + if (!destinationFrame) + { + tempRect.width = renderer.width; + tempRect.height = renderer.height; + + destinationFrame = tempRect; + } + + if (!sourceFrame) + { + sourceFrame = destinationFrame; + } + + renderer.framebuffer.bind(null, destinationFrame); + + // TODO store this.. + this.renderer.projection.update(destinationFrame, sourceFrame, this.renderer.resolution, true); + this.renderer.stencil.setMaskStack(this.defaultMaskStack); + } + + this.destinationFrame.copyFrom(destinationFrame); + } + + /** + * Erases the render texture and fills the drawing area with a colour + * + * @param {number} [clearColor] - The colour + * @return {PIXI.Renderer} Returns itself. + */ + clear(clearColor) + { + if (this.renderTexture) + { + clearColor = clearColor || this.renderTexture.baseTexture.clearColor; + } + else + { + clearColor = clearColor || this.clearColor; + } + + this.renderer.framebuffer.clear(clearColor[0], clearColor[1], clearColor[2], clearColor[3]); + } + + resize()// screenWidth, screenHeight) + { + // resize the root only! + this.bind(null); + } +} diff --git a/packages/core/src/renderers/AbstractRenderer.js b/packages/core/src/renderers/AbstractRenderer.js deleted file mode 100644 index 928670f..0000000 --- a/packages/core/src/renderers/AbstractRenderer.js +++ /dev/null @@ -1,353 +0,0 @@ -import { hex2string, hex2rgb } from '@pixi/utils'; -import { Matrix, Rectangle } from '@pixi/math'; -import { RENDERER_TYPE } from '@pixi/constants'; -import { settings } from '@pixi/settings'; -import { Container } from '@pixi/display'; -import RenderTexture from '../textures/RenderTexture'; -import EventEmitter from 'eventemitter3'; - -const tempMatrix = new Matrix(); - -/** - * 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 EventEmitter - * @memberof PIXI - */ -export default class AbstractRenderer extends EventEmitter -{ - // eslint-disable-next-line valid-jsdoc - /** - * @param {string} system - The name of the system this renderer is for. - * @param {object} [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.transparent=false] - If the render view is transparent, default false - * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false - * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) - * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. The - * resolution of the renderer retina would be 2. - * @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 {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when rendering, - * stopping pixel interpolation. - */ - constructor(system, options, arg2, arg3) - { - super(); - - // Support for constructor(system, screenWidth, screenHeight, options) - if (typeof options === 'number') - { - options = Object.assign({ - width: options, - height: arg2 || settings.RENDER_OPTIONS.height, - }, arg3); - } - - // 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 = RENDERER_TYPE.UNKNOWN; - - /** - * Measurements of the screen. (0, 0, screenWidth, screenHeight) - * - * Its safe to use as filterArea or hitArea for 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 1 - */ - this.resolution = options.resolution || settings.RESOLUTION; - - /** - * Whether the render view is transparent - * - * @member {boolean} - */ - this.transparent = options.transparent; - - /** - * Whether css dimensions of canvas view should be resized to screen dimensions automatically - * - * @member {boolean} - */ - this.autoResize = options.autoResize || false; - - /** - * Tracks the blend modes useful for this renderer. - * - * @member {object} - */ - this.blendModes = null; - - /** - * 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; - - /** - * If true PixiJS will Math.floor() x/y values when rendering, stopping pixel interpolation. - * Handy for crisp pixel art and speed on legacy devices. - * - * @member {boolean} - */ - this.roundPixels = options.roundPixels; - - /** - * The background color as a number. - * - * @member {number} - * @private - */ - this._backgroundColor = 0x000000; - - /** - * The background color as an [R, G, B] array. - * - * @member {number[]} - * @private - */ - this._backgroundColorRgba = [0, 0, 0, 0]; - - /** - * The background color as a string. - * - * @member {string} - * @private - */ - this._backgroundColorString = '#000000'; - - this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter - - /** - * This temporary display object used as the parent of the currently being rendered item - * - * @member {PIXI.DisplayObject} - * @private - */ - this._tempDisplayObjectParent = new Container(); - - /** - * The last root object that the renderer tried to render. - * - * @member {PIXI.DisplayObject} - * @private - */ - this._lastObjectRendered = this._tempDisplayObjectParent; - - /** - * Collection of plugins. - * @readonly - * @member {object} - */ - this.plugins = {}; - } - - /** - * Initialize the plugins. - * - * @protected - * @param {object} staticMap - The dictionary of staticly saved plugins. - */ - initPlugins(staticMap) - { - 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() - { - return this.view.width; - } - - /** - * Same as view.height, actual number of pixels in the canvas by vertical - * - * @member {number} - * @readonly - * @default 600 - */ - get height() - { - return this.view.height; - } - - /** - * Resizes the screen and canvas to the specified width and height - * Canvas dimensions are multiplied by resolution - * - * @param {number} screenWidth - the new width of the screen - * @param {number} screenHeight - the new height of the screen - */ - resize(screenWidth, screenHeight) - { - this.screen.width = screenWidth; - this.screen.height = screenHeight; - - this.view.width = screenWidth * this.resolution; - this.view.height = screenHeight * this.resolution; - - if (this.autoResize) - { - this.view.style.width = `${screenWidth}px`; - this.view.style.height = `${screenHeight}px`; - } - } - - /** - * 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 {PIXI.DisplayObject} displayObject - The displayObject the object will be generated from - * @param {number} scaleMode - Should be one of the scaleMode consts - * @param {number} resolution - The resolution / device pixel ratio of the texture being generated - * @param {PIXI.Rectangle} [region] - The region of the displayObject, that shall be rendered, - * if no region is specified, defaults to the local bounds of the displayObject. - * @return {PIXI.Texture} a texture of the graphics object - */ - generateTexture(displayObject, scaleMode, resolution, region) - { - region = region || displayObject.getLocalBounds(); - - // 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(region.width | 0, region.height | 0, scaleMode, resolution); - - tempMatrix.tx = -region.x; - tempMatrix.ty = -region.y; - - this.render(displayObject, renderTexture, false, tempMatrix, true); - - return renderTexture; - } - - /** - * Removes everything from the renderer and optionally removes the Canvas DOM element. - * - * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. - */ - destroy(removeView) - { - 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); - } - - this.plugins = null; - - this.type = RENDERER_TYPE.UNKNOWN; - - this.view = null; - - this.screen = null; - - this.resolution = 0; - - this.transparent = false; - - this.autoResize = false; - - this.blendModes = null; - - this.options = null; - - this.preserveDrawingBuffer = false; - this.clearBeforeRender = false; - - this.roundPixels = false; - - this._backgroundColor = 0; - this._backgroundColorRgba = null; - this._backgroundColorString = null; - - this._tempDisplayObjectParent = null; - this._lastObjectRendered = null; - } - - /** - * The background color to fill if not transparent - * - * @member {number} - */ - get backgroundColor() - { - return this._backgroundColor; - } - - set backgroundColor(value) // eslint-disable-line require-jsdoc - { - this._backgroundColor = value; - this._backgroundColorString = hex2string(value); - hex2rgb(value, this._backgroundColorRgba); - } -} diff --git a/packages/core/src/renderers/Renderer.js b/packages/core/src/renderers/Renderer.js deleted file mode 100644 index a2020b1..0000000 --- a/packages/core/src/renderers/Renderer.js +++ /dev/null @@ -1,329 +0,0 @@ -import AbstractRenderer from './AbstractRenderer'; -import { sayHello } from '@pixi/utils'; -import MaskSystem from './systems/MaskSystem'; -import StencilSystem from './systems/StencilSystem'; -import FilterSystem from './systems/filter/FilterSystem'; -import FramebufferSystem from './systems/FramebufferSystem'; -import RenderTextureSystem from './systems/RenderTextureSystem'; -import TextureSystem from './systems/textures/TextureSystem'; -import ProjectionSystem from './systems/ProjectionSystem'; -import StateSystem from './systems/StateSystem'; -import GeometrySystem from './systems/geometry/GeometrySystem'; -import ShaderSystem from './systems/shader/ShaderSystem'; -import ContextSystem from './systems/ContextSystem'; -import BatchSystem from './systems/BatchSystem'; -import TextureGCSystem from './systems/textures/TextureGCSystem'; -import { RENDERER_TYPE } from '@pixi/constants'; -import UniformGroup from '../shader/UniformGroup'; -import { Matrix } from '@pixi/math'; -import Runner from 'mini-runner'; - -/** - * 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 Render works by automatically managing webGLBatchs. - * 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 :) - * - * @class - * @memberof PIXI - * @extends PIXI.AbstractRenderer - */ -export default class Renderer extends AbstractRenderer -{ - // eslint-disable-next-line valid-jsdoc - /** - * - * @param {object} [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.transparent=false] - If the render view is transparent, default false - * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false - * @param {boolean} [options.antialias=false] - sets antialias. If not available natively then FXAA - * antialiasing is used - * @param {boolean} [options.forceFXAA=false] - forces FXAA antialiasing to be used over native. - * FXAA is faster, but may not always look as great - * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. - * The resolution of the renderer retina would be 2. - * @param {boolean} [options.clearBeforeRender=true] - This sets if the renderer will clear - * the canvas or not before the new render pass. If you wish to set this to false, you *must* set - * preserveDrawingBuffer to `true`. - * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, - * enable this if you need to call toDataUrl on the webgl context. - * @param {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when - * rendering, stopping pixel interpolation. - * @param {number} [options.backgroundColor=0x000000] - The background color of the rendered area - * (shown if not transparent). - * @param {boolean} [options.legacy=false] - If true PixiJS will aim to ensure compatibility - * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. - * @param {string} [options.powerPreference] - Parameter passed to webgl context, set to "high-performance" - * for devices with dual graphics card - */ - constructor(options, arg2, arg3) - { - super('WebGL', options, arg2, arg3); - - /** - * The type of this renderer as a standardised const - * - * @member {number} - * @see PIXI.RENDERER_TYPE - */ - this.type = RENDERER_TYPE.WEBGL; - - // this will be set by the contextSystem (this.context) - this.gl = null; - this.CONTEXT_UID = 0; - this.legacy = !!options.legacy; - - // TODO legacy! - - // runners! - this.runners = { - destroy: new Runner('destroy'), - contextChange: new Runner('contextChange', 1), - reset: new Runner('reset'), - update: new Runner('update'), - postrender: new Runner('postrender'), - prerender: new Runner('prerender'), - resize: new Runner('resize', 2), - }; - - 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(GeometrySystem, 'geometry') - .addSystem(FramebufferSystem, 'framebuffer') - .addSystem(StencilSystem, 'stencil') - .addSystem(ProjectionSystem, 'projection') - .addSystem(TextureGCSystem, 'textureGC') - .addSystem(FilterSystem, 'filter') - .addSystem(RenderTextureSystem, 'renderTexture') - .addSystem(BatchSystem, 'batch'); - - this.initPlugins(Renderer.__plugins); - - /** - * The options passed in to create a new webgl context. - * - * @member {object} - * @private - */ - if (options.context) - { - this.context.initFromContext(options.context); - } - else - { - this.context.initFromOptions({ - alpha: this.transparent, - antialias: options.antialias, - premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', - stencil: true, - 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); - } - - addSystem(_class, name) - { - if (!name) - { - name = _class.name; - } - - // TODO - read name from class.name.. - - /* - if(name.includes('System')) - { - name = name.replace('System', ''); - name = name.charAt(0).toLowerCase() + name.slice(1); - } - */ - - const system = new _class(this); - - if (this[name]) - { - throw new Error(`Whoops! ${name} is already a manger`); - } - - this[name] = system; - - for (const i in this.runners) - { - this.runners[i].add(system); - } - - return this; - - /** - * 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. - */ - } - - /** - * Renders the object to its webGL view - * - * @param {PIXI.DisplayObject} displayObject - the object to be rendered - * @param {PIXI.RenderTexture} renderTexture - The render texture to render to. - * @param {boolean} [clear] - Should the canvas be cleared before the new render - * @param {PIXI.Transform} [transform] - A transform to apply to the render texture before rendering. - * @param {boolean} [skipUpdateTransform] - Should we skip the update transform pass? - */ - render(displayObject, renderTexture, clear, transform, skipUpdateTransform) - { - // can be handy to know! - this.renderingToScreen = !renderTexture; - - this.runners.prerender.run(); - this.emit('prerender'); - - // 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.parent; - - displayObject.parent = this._tempDisplayObjectParent; - displayObject.updateTransform(); - displayObject.parent = 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.run(); - - this.emit('postrender'); - } - - /** - * Resizes the webGL view to the specified width and height. - * - * @param {number} screenWidth - the new width of the screen - * @param {number} screenHeight - the new height of the screen - */ - resize(screenWidth, screenHeight) - { - AbstractRenderer.prototype.resize.call(this, screenWidth, screenHeight); - - this.runners.resize.run(screenWidth, screenHeight); - } - - /** - * Resets the WebGL state so you can render things however you fancy! - * - * @return {PIXI.Renderer} Returns itself. - */ - reset() - { - this.runners.reset.run(); - - return this; - } - - clear() - { - this.framebuffer.bind(); - this.framebuffer.clear(); - } - - /** - * Removes everything from the renderer (event listeners, spritebatch, etc...) - * - * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. - * See: https://github.com/pixijs/pixi.js/issues/2233 - */ - destroy(removeView) - { - this.runners.destroy.run(); - - // call base destroy - super.destroy(removeView); - - // TODO nullify all the managers.. - this.gl = null; - } - - /** - * 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 - * builds and excluding plugins. - * @name PIXI.Renderer#plugins - * @type {object} - * @readonly - * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. - * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. - * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. - * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. - */ - - /** - * Adds a plugin to the renderer. - * - * @method - * @param {string} pluginName - The name of the plugin. - * @param {Function} ctor - The constructor function or class for the plugin. - */ - static registerPlugin(pluginName, ctor) - { - Renderer.__plugins = Renderer.__plugins || {}; - Renderer.__plugins[pluginName] = ctor; - } -} diff --git a/packages/core/src/renderers/State.js b/packages/core/src/renderers/State.js deleted file mode 100644 index fa3f221..0000000 --- a/packages/core/src/renderers/State.js +++ /dev/null @@ -1,163 +0,0 @@ -/* eslint-disable max-len */ - -const BLEND = 0; -const OFFSET = 1; -const CULLING = 2; -const DEPTH_TEST = 3; -const WINDING = 4; - -/** - * This is a webGL state. It is passed The WebGL StateManager. - * Each mesh renderered may require webGL to be in a different state. - * For example you may want different blend mode or to enable polygon offsets - * - * @class - * @memberof PIXI - */ -export default class State -{ - /** - * - */ - constructor() - { - this.data = 0; - - this.blendMode = 0; - this.polygonOffset = 0; - - this.blend = true; - // this.depthTest = true; - } - - /** - * Activates blending of the computed fragment color values - * - * @member {boolean} - */ - get blend() - { - return !!(this.data & (1 << BLEND)); - } - - set blend(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << BLEND)) !== value) - { - this.data ^= (1 << BLEND); - } - } - - /** - * Activates adding an offset to depth values of polygon's fragments - * - * @member {boolean} - * @default false - */ - get offsets() - { - return !!(this.data & (1 << OFFSET)); - } - - set offsets(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << OFFSET)) !== value) - { - this.data ^= (1 << OFFSET); - } - } - - /** - * Activates culling of polygons. - * - * @member {boolean} - * @default false - */ - get culling() - { - return !!(this.data & (1 << CULLING)); - } - - set culling(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << CULLING)) !== value) - { - this.data ^= (1 << CULLING); - } - } - - /** - * Activates depth comparisons and updates to the depth buffer. - * - * @member {boolean} - * @default false - */ - get depthTest() - { - return !!(this.data & (1 << DEPTH_TEST)); - } - - set depthTest(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << DEPTH_TEST)) !== value) - { - this.data ^= (1 << DEPTH_TEST); - } - } - - /** - * Specifies whether or not front or back-facing polygons can be culled. - * @member {boolean} - * @default false - */ - get clockwiseFrontFace() - { - return !!(this.data & (1 << WINDING)); - } - - set clockwiseFrontFace(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << WINDING)) !== value) - { - this.data ^= (1 << WINDING); - } - } - - /** - * The blend mode to be applied when this state is set. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. - * Setting this mode to anything other than NO_BLEND will automatically switch blending on. - * - * @member {boolean} - * @default PIXI.BLEND_MODES.NORMAL - * @see PIXI.BLEND_MODES - */ - get blendMode() - { - return this._blendMode; - } - - set blendMode(value) // eslint-disable-line require-jsdoc - { - // 17 is NO BLEND - this.blend = (value !== 17); - this._blendMode = value; - } - - /** - * The polygon offset. Setting this property to anything other than 0 will automatically enable poygon offset fill. - * - * @member {number} - * @default 0 - */ - get polygonOffset() - { - return this._polygonOffset; - } - - set polygonOffset(value) // eslint-disable-line require-jsdoc - { - this.offsets = !!value; - this._polygonOffset = value; - } -} - diff --git a/packages/core/src/renderers/filters/Filter.js b/packages/core/src/renderers/filters/Filter.js deleted file mode 100644 index 2073b8d..0000000 --- a/packages/core/src/renderers/filters/Filter.js +++ /dev/null @@ -1,140 +0,0 @@ -import Shader from '../../shader/Shader'; -import Program from '../../shader/Program'; -import State from '../State'; -import { settings } from '@pixi/settings'; -// import extractUniformsFromSrc from './extractUniformsFromSrc'; -import defaultVertex from './defaultFilter.vert'; -import defaultFragment from './defaultFilter.frag'; - -// let math = require('../../math'); -/** - * @class - * @memberof PIXI - * @extends PIXI.Shader - */ -export default class Filter extends Shader -{ - /** - * @param {string} [vertexSrc] - The source of the vertex shader. - * @param {string} [fragmentSrc] - The source of the fragment shader. - * @param {object} [uniforms] - Custom uniforms to use to augment the built-in ones. - */ - constructor(vertexSrc, fragmentSrc, uniforms) - { - const program = Program.from(vertexSrc, fragmentSrc); - - super(program, uniforms); - - /** - * The padding of the filter. Some filters require extra space to breath such as a blur. - * Increasing this will add extra width and height to the bounds of the object that the - * filter is applied to. - * - * @member {number} - */ - this.padding = 0; - - /** - * The resolution of the filter. Setting this to be lower will lower the quality but - * increase the performance of the filter. - * - * @member {number} - */ - this.resolution = settings.RESOLUTION; - - /** - * If enabled is true the filter is applied, if false it will not. - * - * @member {boolean} - */ - this.enabled = true; - - /** - * If enabled, PixiJS will fit the filter area into boundaries for better performance. - * Switch it off if it does not work for specific shader. - * - * @member {boolean} - */ - this.autoFit = true; - - /** - * Legacy filters use position and uvs from attributes - * @member {boolean} - * @readonly - */ - this.legacy = !!this.program.attributeData.aTextureCoord; - - /** - * the webGL state the filter requires to render - * @member {PIXI.State} - */ - this.state = new State(); - } - - /** - * Applies the filter - * - * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from - * @param {PIXI.RenderTarget} input - The input render target. - * @param {PIXI.RenderTarget} output - The target to output to. - * @param {boolean} clear - Should the output be cleared before rendering to it - * @param {object} [currentState] - It's current state of filter. - * There are some useful properties in the currentState : - * target, filters, sourceFrame, destinationFrame, renderTarget, resolution - */ - apply(filterManager, input, output, clear, currentState, derp) // eslint-disable-line no-unused-vars - { - // do as you please! - - filterManager.applyFilter(this, input, output, clear, currentState, derp); - - // or just do a regular render.. - } - - /** - * Sets the blendmode of the filter - * - * @member {number} - * @default PIXI.BLEND_MODES.NORMAL - */ - get blendMode() - { - return this.state.blendMode; - } - - set blendMode(value) // eslint-disable-line require-jsdoc - { - this.state.blendMode = value; - } - - /** - * The default vertex shader source - * - * @static - * @constant - */ - static get defaultVertexSrc() - { - return defaultVertex; - } - - /** - * The default fragment shader source - * - * @static - * @constant - */ - static get defaultFragmentSrc() - { - return defaultFragment; - } -} - -/** - * Used for caching shader IDs - * - * @static - * @private - */ -Filter.SOURCE_KEY_MAP = {}; - diff --git a/packages/core/src/renderers/filters/defaultFilter.frag b/packages/core/src/renderers/filters/defaultFilter.frag deleted file mode 100644 index a14c1be..0000000 --- a/packages/core/src/renderers/filters/defaultFilter.frag +++ /dev/null @@ -1,22 +0,0 @@ -varying vec2 vTextureCoord; -varying vec2 vFilterCoord; - -uniform sampler2D uSampler; -uniform sampler2D filterSampler; - -void main(void){ - vec4 masky = texture2D(filterSampler, vFilterCoord); - vec4 sample = texture2D(uSampler, vTextureCoord); - vec4 color; - if(mod(vFilterCoord.x, 1.0) > 0.5) - { - color = vec4(1.0, 0.0, 0.0, 1.0); - } - else - { - color = vec4(0.0, 1.0, 0.0, 1.0); - } - // gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0); - gl_FragColor = mix(sample, masky, 0.5); - gl_FragColor *= sample.a; -} diff --git a/packages/core/src/renderers/filters/defaultFilter.vert b/packages/core/src/renderers/filters/defaultFilter.vert deleted file mode 100644 index e1febca..0000000 --- a/packages/core/src/renderers/filters/defaultFilter.vert +++ /dev/null @@ -1,14 +0,0 @@ -attribute vec2 aVertexPosition; -attribute vec2 aTextureCoord; - -uniform mat3 projectionMatrix; -uniform mat3 filterMatrix; - -varying vec2 vTextureCoord; -varying vec2 vFilterCoord; - -void main(void){ - gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); - vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy; - vTextureCoord = aTextureCoord ; -} diff --git a/packages/core/src/renderers/filters/filterTransforms.js b/packages/core/src/renderers/filters/filterTransforms.js deleted file mode 100644 index 87a3cd6..0000000 --- a/packages/core/src/renderers/filters/filterTransforms.js +++ /dev/null @@ -1,50 +0,0 @@ -import { Matrix } from '@pixi/math'; - -/** - * Calculates the mapped matrix - * @param filterArea {Rectangle} The filter area - * @param sprite {Sprite} the target sprite - * @param outputMatrix {Matrix} @alvin - * @private - */ -// this returns a matrix that will normalise map filter cords in the filter to screen space -export function calculateScreenSpaceMatrix(outputMatrix, filterArea, textureSize) -{ - // TODO unwrap? - const mappedMatrix = outputMatrix.identity(); - - mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); - - mappedMatrix.scale(textureSize.width, textureSize.height); - - return mappedMatrix; -} - -export function calculateNormalizedScreenSpaceMatrix(outputMatrix, filterArea, textureSize) -{ - const mappedMatrix = outputMatrix.identity(); - - mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); - - const translateScaleX = (textureSize.width / filterArea.width); - const translateScaleY = (textureSize.height / filterArea.height); - - mappedMatrix.scale(translateScaleX, translateScaleY); - - return mappedMatrix; -} - -// this will map the filter coord so that a texture can be used based on the transform of a sprite -export function calculateSpriteMatrix(outputMatrix, filterArea, textureSize, sprite) -{ - const orig = sprite._texture.orig; - const mappedMatrix = outputMatrix.set(textureSize.width, 0, 0, textureSize.height, filterArea.x, filterArea.y); - const worldTransform = sprite.worldTransform.copyTo(Matrix.TEMP_MATRIX); - - worldTransform.invert(); - mappedMatrix.prepend(worldTransform); - mappedMatrix.scale(1.0 / orig.width, 1.0 / orig.height); - mappedMatrix.translate(sprite.anchor.x, sprite.anchor.y); - - return mappedMatrix; -} diff --git a/packages/core/src/renderers/filters/spriteMask/SpriteMaskFilter.js b/packages/core/src/renderers/filters/spriteMask/SpriteMaskFilter.js deleted file mode 100644 index d5a91a4..0000000 --- a/packages/core/src/renderers/filters/spriteMask/SpriteMaskFilter.js +++ /dev/null @@ -1,63 +0,0 @@ -import Filter from '../Filter'; -import { Matrix } from '@pixi/math'; -import vertex from './spriteMaskFilter.vert'; -import fragment from './spriteMaskFilter.frag'; -import { default as TextureMatrix } from '../../../textures/TextureMatrix'; - -/** - * The SpriteMaskFilter class - * - * @class - * @extends PIXI.Filter - * @memberof PIXI - */ -export default class SpriteMaskFilter extends Filter -{ - /** - * @param {PIXI.Sprite} sprite - the target sprite - */ - constructor(sprite) - { - const maskMatrix = new Matrix(); - - super(vertex, fragment); - - sprite.renderable = false; - - this.maskSprite = sprite; - this.maskMatrix = maskMatrix; - } - - /** - * Applies the filter - * - * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from - * @param {PIXI.RenderTarget} input - The input render target. - * @param {PIXI.RenderTarget} output - The target to output to. - */ - apply(filterManager, input, output) - { - const maskSprite = this.maskSprite; - const tex = this.maskSprite.texture; - - if (!tex.valid) - { - return; - } - if (!tex.transform) - { - // margin = 0.0, let it bleed a bit, shader code becomes easier - // assuming that atlas textures were made with 1-pixel padding - tex.transform = new TextureMatrix(tex, 0.0); - } - tex.transform.update(); - - this.uniforms.mask = tex; - this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite) - .prepend(tex.transform.mapCoord); - this.uniforms.alpha = maskSprite.worldAlpha; - this.uniforms.maskClamp = tex.transform.uClampFrame; - - filterManager.applyFilter(this, input, output); - } -} diff --git a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.frag b/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.frag deleted file mode 100644 index 0e4aef8..0000000 --- a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.frag +++ /dev/null @@ -1,23 +0,0 @@ -varying vec2 vMaskCoord; -varying vec2 vTextureCoord; - -uniform sampler2D uSampler; -uniform sampler2D mask; -uniform float alpha; -uniform vec4 maskClamp; - -void main(void) -{ - float clip = step(3.5, - step(maskClamp.x, vMaskCoord.x) + - step(maskClamp.y, vMaskCoord.y) + - step(vMaskCoord.x, maskClamp.z) + - step(vMaskCoord.y, maskClamp.w)); - - vec4 original = texture2D(uSampler, vTextureCoord); - vec4 masky = texture2D(mask, vMaskCoord); - - original *= (masky.r * masky.a * alpha * clip); - - gl_FragColor = original; -} diff --git a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.vert b/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.vert deleted file mode 100644 index d23a30c..0000000 --- a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.vert +++ /dev/null @@ -1,16 +0,0 @@ -attribute vec2 aVertexPosition; -attribute vec2 aTextureCoord; - -uniform mat3 projectionMatrix; -uniform mat3 otherMatrix; - -varying vec2 vMaskCoord; -varying vec2 vTextureCoord; - -void main(void) -{ - gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); - - vTextureCoord = aTextureCoord; - vMaskCoord = ( otherMatrix * vec3( aTextureCoord, 1.0) ).xy; -} diff --git a/packages/core/src/renderers/systems/BatchSystem.js b/packages/core/src/renderers/systems/BatchSystem.js deleted file mode 100644 index 70e9518..0000000 --- a/packages/core/src/renderers/systems/BatchSystem.js +++ /dev/null @@ -1,68 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import ObjectRenderer from '../utils/ObjectRenderer'; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ - -export default class BatchSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this manager works for. - */ - constructor(renderer) - { - super(renderer); - - /** - * An empty renderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.emptyRenderer = new ObjectRenderer(renderer); - - /** - * The currently active ObjectRenderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.currentRenderer = this.emptyRenderer; - } - - /** - * Changes the current renderer to the one given in parameter - * - * @param {PIXI.ObjectRenderer} objectRenderer - The object renderer to use. - */ - setObjectRenderer(objectRenderer) - { - if (this.currentRenderer === objectRenderer) - { - return; - } - - this.currentRenderer.stop(); - this.currentRenderer = objectRenderer; - - this.renderer.state.setState(objectRenderer.state); - - this.currentRenderer.start(); - } - - /** - * This should be called if you wish to do some custom rendering - * It will basically render anything that may be batched up such as sprites - * - */ - flush() - { - this.setObjectRenderer(this.emptyRenderer); - } - - reset() - { - this.setObjectRenderer(this.emptyRenderer); - } -} diff --git a/packages/core/src/renderers/systems/ContextSystem.js b/packages/core/src/renderers/systems/ContextSystem.js deleted file mode 100644 index 43bf827..0000000 --- a/packages/core/src/renderers/systems/ContextSystem.js +++ /dev/null @@ -1,186 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { settings } from '@pixi/settings'; - -let CONTEXT_UID = 0; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class ContextSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this.webGLVersion = 1; - - this.handleContextLost = this.handleContextLost.bind(this); - this.handleContextRestored = this.handleContextRestored.bind(this); - - this.extensions = {}; - - renderer.view.addEventListener('webglcontextlost', this.handleContextLost, false); - renderer.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); - } - - get isLost() - { - return (!this.gl || this.gl.isContextLost()); - } - - contextChange(gl) - { - this.gl = gl; - - // restore a context if it was previously lost - if (gl.isContextLost() && gl.getExtension('WEBGL_lose_context')) - { - gl.getExtension('WEBGL_lose_context').restoreContext(); - } - } - - initFromContext(gl) - { - this.gl = gl; - this.validateContext(gl); - this.renderer.gl = gl; - this.renderer.CONTEXT_UID = CONTEXT_UID++; - this.renderer.runners.contextChange.run(gl); - } - - initFromOptions(options) - { - const gl = this.createContext(this.renderer.view, options); - - this.initFromContext(gl); - } - - /** - * Helper class to create a webGL Context - * - * @class - * @memberof PIXI.glCore - * @param canvas {HTMLCanvasElement} the canvas element that we will get the context from - * @param options {Object} An options object that gets passed in to the canvas element containing the context attributes, - * see https://developer.mozilla.org/en/docs/Web/API/HTMLCanvasElement/getContext for the options available - * @return {WebGLRenderingContext} the WebGL context - */ - createContext(canvas, options) - { - let gl; - - if (settings.PREFER_WEBGL_2) - { - gl = canvas.getContext('webgl2', options); - } - - if (gl) - { - this.webGLVersion = 2; - } - else - { - this.webGLVersion = 1; - - gl = canvas.getContext('webgl', options) - || canvas.getContext('experimental-webgl', options); - - if (!gl) - { - // fail, not able to get a context - throw new Error('This browser does not support webGL. Try using the canvas renderer'); - } - } - - this.gl = gl; - - this.getExtensions(); - - return gl; - } - - getExtensions() - { - // time to set up default etensions that pixi uses.. - const gl = this.gl; - const extensions = this.extensions; - - if (this.webGLVersion === 1) - { - extensions.drawBuffers = gl.getExtension('WEBGL_draw_buffers'); - extensions.depthTexture = gl.getExtension('WEBKIT_WEBGL_depth_texture'); - extensions.floatTexture = gl.getExtension('OES_texture_float'); - extensions.loseContext = gl.getExtension('WEBGL_lose_context'); - - extensions.vertexArrayObject = gl.getExtension('OES_vertex_array_object') - || gl.getExtension('MOZ_OES_vertex_array_object') - || gl.getExtension('WEBKIT_OES_vertex_array_object'); - } - - // we don't use any specific WebGL 2 ones yet! - } - - /** - * Handles a lost webgl context - * - * @private - * @param {WebGLContextEvent} event - The context lost event. - */ - handleContextLost(event) - { - event.preventDefault(); - } - - /** - * Handles a restored webgl context - * - * @private - */ - handleContextRestored() - { - this.renderer.runners.contextChange.run(this.gl); - } - - destroy() - { - const view = this.renderer.view; - - // remove listeners - view.removeEventListener('webglcontextlost', this.handleContextLost); - view.removeEventListener('webglcontextrestored', this.handleContextRestored); - - this.gl.useProgram(null); - - if (this.extensions.loseContext) - { - this.extensions.loseContext.loseContext(); - } - } - - postrender() - { - this.gl.flush(); - } - - validateContext(gl) - { - const attributes = gl.getContextAttributes(); - - // this is going to be fairly simple for now.. but at least we have room to grow! - if (!attributes.stencil) - { - /* eslint-disable max-len */ - - /* eslint-disable no-console */ - console.warn('Provided WebGL context does not have a stencil buffer, masks may not render correctly'); - /* eslint-enable no-console */ - - /* eslint-enable max-len */ - } - } -} diff --git a/packages/core/src/renderers/systems/FramebufferSystem.js b/packages/core/src/renderers/systems/FramebufferSystem.js deleted file mode 100644 index df7902b..0000000 --- a/packages/core/src/renderers/systems/FramebufferSystem.js +++ /dev/null @@ -1,246 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { Rectangle } from '@pixi/math'; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class FramebufferSystem extends WebGLSystem -{ - /** - * Sets up the renderer context and necessary buffers. - * - * @private - */ - contextChange() - { - this.gl = this.renderer.gl; - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - this.current = null; - this.viewport = new Rectangle(); - - this.drawBufferExtension = this.renderer.context.extensions.drawBuffers; - } - - bind(framebuffer, frame) - { - const gl = this.gl; - - this.current = framebuffer; - - if (framebuffer) - { - // TODO cacheing layer! - - const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID] || this.initFramebuffer(framebuffer); - - gl.bindFramebuffer(gl.FRAMEBUFFER, fbo.framebuffer); - // makesure all textures are unbound.. - - // now check for updates... - if (fbo.dirtyId !== framebuffer.dirtyId) - { - fbo.dirtyId = framebuffer.dirtyId; - - if (fbo.dirtyFormat !== framebuffer.dirtyFormat) - { - fbo.dirtyFormat = framebuffer.dirtyFormat; - this.updateFramebuffer(framebuffer); - } - else if (fbo.dirtySize !== framebuffer.dirtySize) - { - fbo.dirtySize = framebuffer.dirtySize; - this.resizeFramebuffer(framebuffer); - } - } - - for (let i = 0; i < framebuffer.colorTextures.length; i++) - { - if (framebuffer.colorTextures[i].texturePart) - { - this.renderer.texture.unbind(framebuffer.colorTextures[i].texture); - } - else - { - this.renderer.texture.unbind(framebuffer.colorTextures[i]); - } - } - - if (framebuffer.depthTexture) - { - this.renderer.texture.unbind(framebuffer.depthTexture); - } - - if (frame) - { - this.setViewport(frame.x, frame.y, frame.width, frame.height); - } - else - { - this.setViewport(0, 0, framebuffer.width, framebuffer.height); - } - } - else - { - gl.bindFramebuffer(gl.FRAMEBUFFER, null); - - if (frame) - { - this.setViewport(frame.x, frame.y, frame.width, frame.height); - } - else - { - this.setViewport(0, 0, this.renderer.width, this.renderer.height); - } - } - } - - setViewport(x, y, width, height) - { - const v = this.viewport; - - if (v.width !== width || v.height !== height || v.x !== x || v.y !== y) - { - v.x = x; - v.y = y; - v.width = width; - v.height = height; - - this.gl.viewport(x, y, width, height); - } - } - - get size() - { - if (this.current) - { - // TODO store temp - return { x: 0, y: 0, width: this.current.width, height: this.current.height }; - } - - return { x: 0, y: 0, width: this.renderer.width, height: this.renderer.height }; - } - - clear(r, g, b, a) - { - const gl = this.gl; - - // TODO clear color can be set only one right? - gl.clearColor(r, g, b, a); - gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); - } - - // private functions... - - initFramebuffer(framebuffer) - { - const gl = this.gl; - - // TODO - make this a class? - const fbo = { - framebuffer: gl.createFramebuffer(), - stencil: null, - dirtyId: 0, - dirtyFormat: 0, - dirtySize: 0, - }; - - framebuffer.glFrameBuffers[this.CONTEXT_UID] = fbo; - - return fbo; - } - - resizeFramebuffer(framebuffer) - { - const gl = this.gl; - - if (framebuffer.stencil || framebuffer.depth) - { - gl.bindRenderbuffer(gl.RENDERBUFFER, this.stencil); - gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); - } - } - - updateFramebuffer(framebuffer) - { - const gl = this.gl; - - const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID]; - - // bind the color texture - const colorTextures = framebuffer.colorTextures; - - let count = colorTextures.length; - - if (!this.drawBufferExtension) - { - count = Math.min(count, 1); - } - - const activeTextures = []; - - for (let i = 0; i < count; i++) - { - const texture = framebuffer.colorTextures[i]; - - if (texture.texturePart) - { - this.renderer.texture.bind(texture.texture, 0); - - gl.framebufferTexture2D(gl.FRAMEBUFFER, - gl.COLOR_ATTACHMENT0 + i, - gl.TEXTURE_CUBE_MAP_NEGATIVE_X + texture.side, - texture.texture._glTextures[this.CONTEXT_UID].texture, - 0); - } - else - { - this.renderer.texture.bind(texture, 0); - - gl.framebufferTexture2D(gl.FRAMEBUFFER, - gl.COLOR_ATTACHMENT0 + i, - gl.TEXTURE_2D, - texture._glTextures[this.CONTEXT_UID].texture, - 0); - } - - activeTextures.push(gl.COLOR_ATTACHMENT0 + i); - } - - if (this.drawBufferExtension && activeTextures.length > 1) - { - this.drawBufferExtension.drawBuffersWEBGL(activeTextures); - } - - if (framebuffer.depthTexture) - { - const depthTextureExt = this.renderer.context.extensions.depthTexture; - - if (depthTextureExt) - { - const depthTexture = framebuffer.depthTexture; - - this.renderer.texture.bind(depthTexture, 0); - - gl.framebufferTexture2D(gl.FRAMEBUFFER, - gl.DEPTH_ATTACHMENT, - gl.TEXTURE_2D, - depthTexture._glTextures[this.CONTEXT_UID].texture, - 0); - } - } - - if (framebuffer.stencil || framebuffer.depth) - { - fbo.stencil = gl.createRenderbuffer(); - - gl.bindRenderbuffer(gl.RENDERBUFFER, fbo.stencil); - - // TODO.. this is depth AND stencil? - gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, fbo.stencil); - gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); - // fbo.enableStencil(); - } - } -} diff --git a/packages/core/src/renderers/systems/MaskSystem.js b/packages/core/src/renderers/systems/MaskSystem.js deleted file mode 100644 index d9c4b1a..0000000 --- a/packages/core/src/renderers/systems/MaskSystem.js +++ /dev/null @@ -1,199 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import AlphaMaskFilter from '../filters/spriteMask/SpriteMaskFilter'; - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class MaskSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - // TODO - we don't need both! - this.scissor = false; - this.scissorData = null; - this.scissorRenderTarget = null; - - this.enableScissor = false; - - this.alphaMaskPool = []; - this.alphaMaskIndex = 0; - } - - /** - * Applies the Mask and adds it to the current filter stack. - * - * @param {PIXI.DisplayObject} target - Display Object to push the mask to - * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. - */ - push(target, maskData) - { - // TODO the root check means scissor rect will not - // be used on render textures more info here: - // https://github.com/pixijs/pixi.js/pull/3545 - - if (maskData.texture) - { - this.pushSpriteMask(target, maskData); - } - else if (this.enableScissor - && !this.scissor - && this.renderer._activeRenderTarget.root - && !this.renderer.stencil.stencilMaskStack.length - && maskData.isFastRect()) - { - const matrix = maskData.worldTransform; - - let rot = Math.atan2(matrix.b, matrix.a); - - // use the nearest degree! - rot = Math.round(rot * (180 / Math.PI)); - - if (rot % 90) - { - this.pushStencilMask(maskData); - } - else - { - this.pushScissorMask(target, maskData); - } - } - else - { - this.pushStencilMask(maskData); - } - } - - /** - * Removes the last mask from the mask stack and doesn't return it. - * - * @param {PIXI.DisplayObject} target - Display Object to pop the mask from - * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. - */ - pop(target, maskData) - { - if (maskData.texture) - { - this.popSpriteMask(target, maskData); - } - else if (this.enableScissor && !this.renderer.stencil.stencilMaskStack.length) - { - this.popScissorMask(target, maskData); - } - else - { - this.popStencilMask(target, maskData); - } - } - - /** - * Applies the Mask and adds it to the current filter stack. - * - * @param {PIXI.RenderTarget} target - Display Object to push the sprite mask to - * @param {PIXI.Sprite} maskData - Sprite to be used as the mask - */ - pushSpriteMask(target, maskData) - { - let alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; - - if (!alphaMaskFilter) - { - alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; - } - - alphaMaskFilter[0].resolution = this.renderer.resolution; - alphaMaskFilter[0].maskSprite = maskData; - - // TODO - may cause issues! - target.filterArea = maskData.getBounds(true); - - this.renderer.filterSystem.pushFilter(target, alphaMaskFilter); - - this.alphaMaskIndex++; - } - - /** - * Removes the last filter from the filter stack and doesn't return it. - * - */ - popSpriteMask() - { - this.renderer.filterSystem.popFilter(); - this.alphaMaskIndex--; - } - - /** - * Applies the Mask and adds it to the current filter stack. - * - * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. - */ - pushStencilMask(maskData) - { - this.renderer.batch.flush(); - this.renderer.stencil.pushStencil(maskData); - } - - /** - * Removes the last filter from the filter stack and doesn't return it. - * - */ - popStencilMask() - { - // this.renderer.currentRenderer.stop(); - this.renderer.stencil.popStencil(); - } - - /** - * - * @param {PIXI.DisplayObject} target - Display Object to push the mask to - * @param {PIXI.Graphics} maskData - The masking data. - */ - pushScissorMask(target, maskData) - { - maskData.renderable = true; - - const renderTarget = this.renderer._activeRenderTarget; - - const bounds = maskData.getBounds(); - - bounds.fit(renderTarget.size); - maskData.renderable = false; - - this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); - - const resolution = this.renderer.resolution; - - this.renderer.gl.scissor( - bounds.x * resolution, - (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, - bounds.width * resolution, - bounds.height * resolution - ); - - this.scissorRenderTarget = renderTarget; - this.scissorData = maskData; - this.scissor = true; - } - - /** - * - * - */ - popScissorMask() - { - this.scissorRenderTarget = null; - this.scissorData = null; - this.scissor = false; - - // must be scissor! - const gl = this.renderer.gl; - - gl.disable(gl.SCISSOR_TEST); - } -} diff --git a/packages/core/src/renderers/systems/ProjectionSystem.js b/packages/core/src/renderers/systems/ProjectionSystem.js deleted file mode 100644 index ec5efdb..0000000 --- a/packages/core/src/renderers/systems/ProjectionSystem.js +++ /dev/null @@ -1,73 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { Matrix } from '@pixi/math'; - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ - -export default class ProjectionSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this.projectionMatrix = new Matrix(); - } - - update(destinationFrame, sourceFrame, resolution, root) - { - this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; - this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; - - this.calculateProjection(this.destinationFrame, this.sourceFrame, resolution, root); - - this.renderer.globalUniforms.uniforms.projectionMatrix = this.projectionMatrix; - this.renderer.globalUniforms.update(); - } - - /** - * Updates the projection matrix based on a projection frame (which is a rectangle) - * - * @param {Rectangle} destinationFrame - The destination frame. - * @param {Rectangle} sourceFrame - The source frame. - */ - calculateProjection(destinationFrame, sourceFrame, resolution, root) - { - const pm = this.projectionMatrix; - - // I don't think we will need this line.. - // pm.identity(); - - if (!root) - { - pm.a = (1 / destinationFrame.width * 2) * resolution; - pm.d = (1 / destinationFrame.height * 2) * resolution; - - pm.tx = -1 - (sourceFrame.x * pm.a); - pm.ty = -1 - (sourceFrame.y * pm.d); - } - else - { - pm.a = (1 / destinationFrame.width * 2) * resolution; - pm.d = (-1 / destinationFrame.height * 2) * resolution; - - pm.tx = -1 - (sourceFrame.x * pm.a); - pm.ty = 1 - (sourceFrame.y * pm.d); - } - } - - /** - * Sets the transform of the active render target to the given matrix - * - * @param {PIXI.Matrix} matrix - The transformation matrix - */ - setTransform()// matrix) - { - // this._activeRenderTarget.transform = matrix; - } -} diff --git a/packages/core/src/renderers/systems/RenderTextureSystem.js b/packages/core/src/renderers/systems/RenderTextureSystem.js deleted file mode 100644 index a133ef1..0000000 --- a/packages/core/src/renderers/systems/RenderTextureSystem.js +++ /dev/null @@ -1,115 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { Rectangle } from '@pixi/math'; - -const tempRect = new Rectangle(); - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ - -export default class RenderTextureSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this.clearColor = renderer._backgroundColorRgba; - - // TODO moe this property somewhere else! - this.defaultMaskStack = []; - this.defaultFilterStack = [{}]; - - // empty render texture? - this.renderTexture = null; - - this.destinationFrame = new Rectangle(); - } - - bind(renderTexture, sourceFrame, destinationFrame) - { - // TODO - do we want this?? - if (this.renderTexture === renderTexture) return; - this.renderTexture = renderTexture; - - const renderer = this.renderer; - - if (renderTexture) - { - const baseTexture = renderTexture.baseTexture; - - if (!destinationFrame) - { - tempRect.width = baseTexture.realWidth; - tempRect.height = baseTexture.realHeight; - - destinationFrame = tempRect; - } - - if (!sourceFrame) - { - sourceFrame = destinationFrame; - } - - this.renderer.framebuffer.bind(baseTexture.frameBuffer, destinationFrame); - - this.renderer.projection.update(destinationFrame, sourceFrame, baseTexture.resolution, false); - this.renderer.stencil.setMaskStack(baseTexture.stencilMaskStack); - } - else - { - // TODO these validation checks happen deeper down.. - // thing they can be avoided.. - if (!destinationFrame) - { - tempRect.width = renderer.width; - tempRect.height = renderer.height; - - destinationFrame = tempRect; - } - - if (!sourceFrame) - { - sourceFrame = destinationFrame; - } - - renderer.framebuffer.bind(null, destinationFrame); - - // TODO store this.. - this.renderer.projection.update(destinationFrame, sourceFrame, this.renderer.resolution, true); - this.renderer.stencil.setMaskStack(this.defaultMaskStack); - } - - this.destinationFrame.copyFrom(destinationFrame); - } - - /** - * Erases the render texture and fills the drawing area with a colour - * - * @param {number} [clearColor] - The colour - * @return {PIXI.Renderer} Returns itself. - */ - clear(clearColor) - { - if (this.renderTexture) - { - clearColor = clearColor || this.renderTexture.baseTexture.clearColor; - } - else - { - clearColor = clearColor || this.clearColor; - } - - this.renderer.framebuffer.clear(clearColor[0], clearColor[1], clearColor[2], clearColor[3]); - } - - resize()// screenWidth, screenHeight) - { - // resize the root only! - this.bind(null); - } -} diff --git a/packages/core/src/AbstractRenderer.js b/packages/core/src/AbstractRenderer.js new file mode 100644 index 0000000..64b2eb1 --- /dev/null +++ b/packages/core/src/AbstractRenderer.js @@ -0,0 +1,353 @@ +import { hex2string, hex2rgb } from '@pixi/utils'; +import { Matrix, Rectangle } from '@pixi/math'; +import { RENDERER_TYPE } from '@pixi/constants'; +import { settings } from '@pixi/settings'; +import { Container } from '@pixi/display'; +import RenderTexture from './renderTexture/RenderTexture'; +import EventEmitter from 'eventemitter3'; + +const tempMatrix = new Matrix(); + +/** + * 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 EventEmitter + * @memberof PIXI + */ +export default class AbstractRenderer extends EventEmitter +{ + // eslint-disable-next-line valid-jsdoc + /** + * @param {string} system - The name of the system this renderer is for. + * @param {object} [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.transparent=false] - If the render view is transparent, default false + * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false + * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. The + * resolution of the renderer retina would be 2. + * @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 {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when rendering, + * stopping pixel interpolation. + */ + constructor(system, options, arg2, arg3) + { + super(); + + // Support for constructor(system, screenWidth, screenHeight, options) + if (typeof options === 'number') + { + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + }, arg3); + } + + // 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 = RENDERER_TYPE.UNKNOWN; + + /** + * Measurements of the screen. (0, 0, screenWidth, screenHeight) + * + * Its safe to use as filterArea or hitArea for 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 1 + */ + this.resolution = options.resolution || settings.RESOLUTION; + + /** + * Whether the render view is transparent + * + * @member {boolean} + */ + this.transparent = options.transparent; + + /** + * Whether css dimensions of canvas view should be resized to screen dimensions automatically + * + * @member {boolean} + */ + this.autoResize = options.autoResize || false; + + /** + * Tracks the blend modes useful for this renderer. + * + * @member {object} + */ + this.blendModes = null; + + /** + * 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; + + /** + * If true PixiJS will Math.floor() x/y values when rendering, stopping pixel interpolation. + * Handy for crisp pixel art and speed on legacy devices. + * + * @member {boolean} + */ + this.roundPixels = options.roundPixels; + + /** + * The background color as a number. + * + * @member {number} + * @private + */ + this._backgroundColor = 0x000000; + + /** + * The background color as an [R, G, B] array. + * + * @member {number[]} + * @private + */ + this._backgroundColorRgba = [0, 0, 0, 0]; + + /** + * The background color as a string. + * + * @member {string} + * @private + */ + this._backgroundColorString = '#000000'; + + this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter + + /** + * This temporary display object used as the parent of the currently being rendered item + * + * @member {PIXI.DisplayObject} + * @private + */ + this._tempDisplayObjectParent = new Container(); + + /** + * The last root object that the renderer tried to render. + * + * @member {PIXI.DisplayObject} + * @private + */ + this._lastObjectRendered = this._tempDisplayObjectParent; + + /** + * Collection of plugins. + * @readonly + * @member {object} + */ + this.plugins = {}; + } + + /** + * Initialize the plugins. + * + * @protected + * @param {object} staticMap - The dictionary of staticly saved plugins. + */ + initPlugins(staticMap) + { + 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() + { + return this.view.width; + } + + /** + * Same as view.height, actual number of pixels in the canvas by vertical + * + * @member {number} + * @readonly + * @default 600 + */ + get height() + { + return this.view.height; + } + + /** + * Resizes the screen and canvas to the specified width and height + * Canvas dimensions are multiplied by resolution + * + * @param {number} screenWidth - the new width of the screen + * @param {number} screenHeight - the new height of the screen + */ + resize(screenWidth, screenHeight) + { + this.screen.width = screenWidth; + this.screen.height = screenHeight; + + this.view.width = screenWidth * this.resolution; + this.view.height = screenHeight * this.resolution; + + if (this.autoResize) + { + this.view.style.width = `${screenWidth}px`; + this.view.style.height = `${screenHeight}px`; + } + } + + /** + * 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 {PIXI.DisplayObject} displayObject - The displayObject the object will be generated from + * @param {number} scaleMode - Should be one of the scaleMode consts + * @param {number} resolution - The resolution / device pixel ratio of the texture being generated + * @param {PIXI.Rectangle} [region] - The region of the displayObject, that shall be rendered, + * if no region is specified, defaults to the local bounds of the displayObject. + * @return {PIXI.Texture} a texture of the graphics object + */ + generateTexture(displayObject, scaleMode, resolution, region) + { + region = region || displayObject.getLocalBounds(); + + // 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(region.width | 0, region.height | 0, scaleMode, resolution); + + tempMatrix.tx = -region.x; + tempMatrix.ty = -region.y; + + this.render(displayObject, renderTexture, false, tempMatrix, true); + + return renderTexture; + } + + /** + * Removes everything from the renderer and optionally removes the Canvas DOM element. + * + * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. + */ + destroy(removeView) + { + 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); + } + + this.plugins = null; + + this.type = RENDERER_TYPE.UNKNOWN; + + this.view = null; + + this.screen = null; + + this.resolution = 0; + + this.transparent = false; + + this.autoResize = false; + + this.blendModes = null; + + this.options = null; + + this.preserveDrawingBuffer = false; + this.clearBeforeRender = false; + + this.roundPixels = false; + + this._backgroundColor = 0; + this._backgroundColorRgba = null; + this._backgroundColorString = null; + + this._tempDisplayObjectParent = null; + this._lastObjectRendered = null; + } + + /** + * The background color to fill if not transparent + * + * @member {number} + */ + get backgroundColor() + { + return this._backgroundColor; + } + + set backgroundColor(value) // eslint-disable-line require-jsdoc + { + this._backgroundColor = value; + this._backgroundColorString = hex2string(value); + hex2rgb(value, this._backgroundColorRgba); + } +} diff --git a/packages/core/src/Renderer.js b/packages/core/src/Renderer.js new file mode 100644 index 0000000..2227e78 --- /dev/null +++ b/packages/core/src/Renderer.js @@ -0,0 +1,338 @@ +import AbstractRenderer from './AbstractRenderer'; +import { sayHello } from '@pixi/utils'; +import MaskSystem from './mask/MaskSystem'; +import StencilSystem from './mask/StencilSystem'; +import FilterSystem from './filters/FilterSystem'; +import FramebufferSystem from './framebuffer/FramebufferSystem'; +import RenderTextureSystem from './renderTexture/RenderTextureSystem'; +import TextureSystem from './textures/TextureSystem'; +import ProjectionSystem from './projection/ProjectionSystem'; +import StateSystem from './state/StateSystem'; +import GeometrySystem from './geometry/GeometrySystem'; +import ShaderSystem from './shader/ShaderSystem'; +import ContextSystem from './context/ContextSystem'; +import BatchSystem from './batch/BatchSystem'; +import TextureGCSystem from './textures/TextureGCSystem'; +import { RENDERER_TYPE } from '@pixi/constants'; +import UniformGroup from './shader/UniformGroup'; +import { Matrix } from '@pixi/math'; +import Runner from 'mini-runner'; + +/** + * 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 Render works by automatically managing webGLBatchs. + * 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 :) + * + * @class + * @memberof PIXI + * @extends PIXI.AbstractRenderer + */ +export default class Renderer extends AbstractRenderer +{ + // eslint-disable-next-line valid-jsdoc + /** + * + * @param {object} [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.transparent=false] - If the render view is transparent, default false + * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false + * @param {boolean} [options.antialias=false] - sets antialias. If not available natively then FXAA + * antialiasing is used + * @param {boolean} [options.forceFXAA=false] - forces FXAA antialiasing to be used over native. + * FXAA is faster, but may not always look as great + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. + * The resolution of the renderer retina would be 2. + * @param {boolean} [options.clearBeforeRender=true] - This sets if the renderer will clear + * the canvas or not before the new render pass. If you wish to set this to false, you *must* set + * preserveDrawingBuffer to `true`. + * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, + * enable this if you need to call toDataUrl on the webgl context. + * @param {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when + * rendering, stopping pixel interpolation. + * @param {number} [options.backgroundColor=0x000000] - The background color of the rendered area + * (shown if not transparent). + * @param {boolean} [options.legacy=false] - If true PixiJS will aim to ensure compatibility + * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. + * @param {string} [options.powerPreference] - Parameter passed to webgl context, set to "high-performance" + * for devices with dual graphics card + */ + constructor(options, arg2, arg3) + { + super('WebGL', options, arg2, arg3); + + /** + * The type of this renderer as a standardised const + * + * @member {number} + * @see PIXI.RENDERER_TYPE + */ + this.type = RENDERER_TYPE.WEBGL; + + // this will be set by the contextSystem (this.context) + this.gl = null; + this.CONTEXT_UID = 0; + this.legacy = !!options.legacy; + + // TODO legacy! + + // runners! + this.runners = { + destroy: new Runner('destroy'), + contextChange: new Runner('contextChange', 1), + reset: new Runner('reset'), + update: new Runner('update'), + postrender: new Runner('postrender'), + prerender: new Runner('prerender'), + resize: new Runner('resize', 2), + }; + + 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(GeometrySystem, 'geometry') + .addSystem(FramebufferSystem, 'framebuffer') + .addSystem(StencilSystem, 'stencil') + .addSystem(ProjectionSystem, 'projection') + .addSystem(TextureGCSystem, 'textureGC') + .addSystem(FilterSystem, 'filter') + .addSystem(RenderTextureSystem, 'renderTexture') + .addSystem(BatchSystem, 'batch'); + + this.initPlugins(Renderer.__plugins); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ + if (options.context) + { + this.context.initFromContext(options.context); + } + else + { + this.context.initFromOptions({ + alpha: this.transparent, + antialias: options.antialias, + premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', + stencil: true, + 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); + } + + /** + * Add a new system to the renderer. + * @param {class} ClassRef - Class reference + * @param {string} [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. + * @return {PIXI.Renderer} Return instance of renderer + */ + addSystem(ClassRef, name) + { + if (!name) + { + name = ClassRef.name; + } + + // TODO - read name from class.name.. + + /* + if(name.includes('System')) + { + name = name.replace('System', ''); + name = name.charAt(0).toLowerCase() + name.slice(1); + } + */ + + const system = new ClassRef(this); + + if (this[name]) + { + throw new Error(`Whoops! ${name} is already a manger`); + } + + this[name] = system; + + for (const i in this.runners) + { + this.runners[i].add(system); + } + + return this; + + /** + * 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. + */ + } + + /** + * Renders the object to its webGL view + * + * @param {PIXI.DisplayObject} displayObject - the object to be rendered + * @param {PIXI.RenderTexture} renderTexture - The render texture to render to. + * @param {boolean} [clear] - Should the canvas be cleared before the new render + * @param {PIXI.Transform} [transform] - A transform to apply to the render texture before rendering. + * @param {boolean} [skipUpdateTransform] - Should we skip the update transform pass? + */ + render(displayObject, renderTexture, clear, transform, skipUpdateTransform) + { + // can be handy to know! + this.renderingToScreen = !renderTexture; + + this.runners.prerender.run(); + this.emit('prerender'); + + // 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.parent; + + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = 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.run(); + + this.emit('postrender'); + } + + /** + * Resizes the webGL view to the specified width and height. + * + * @param {number} screenWidth - the new width of the screen + * @param {number} screenHeight - the new height of the screen + */ + resize(screenWidth, screenHeight) + { + AbstractRenderer.prototype.resize.call(this, screenWidth, screenHeight); + + this.runners.resize.run(screenWidth, screenHeight); + } + + /** + * Resets the WebGL state so you can render things however you fancy! + * + * @return {PIXI.Renderer} Returns itself. + */ + reset() + { + this.runners.reset.run(); + + return this; + } + + clear() + { + this.framebuffer.bind(); + this.framebuffer.clear(); + } + + /** + * Removes everything from the renderer (event listeners, spritebatch, etc...) + * + * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. + * See: https://github.com/pixijs/pixi.js/issues/2233 + */ + destroy(removeView) + { + this.runners.destroy.run(); + + // call base destroy + super.destroy(removeView); + + // TODO nullify all the managers.. + this.gl = null; + } + + /** + * 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 + * builds and excluding plugins. + * @name PIXI.Renderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. + */ + + /** + * Adds a plugin to the renderer. + * + * @method + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + static registerPlugin(pluginName, ctor) + { + Renderer.__plugins = Renderer.__plugins || {}; + Renderer.__plugins[pluginName] = ctor; + } +} diff --git a/packages/core/src/System.js b/packages/core/src/System.js new file mode 100644 index 0000000..05a10c8 --- /dev/null +++ b/packages/core/src/System.js @@ -0,0 +1,42 @@ +/** + * System is a base class used for extending systems used by the {@link PIXI.Renderer} + * @see PIXI.Renderer#addSystem + * @class + * @memberof PIXI + */ +export default class System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + /** + * The renderer this manager works for. + * + * @member {PIXI.Renderer} + */ + this.renderer = renderer; + + this.renderer.runners.contextChange.add(this); + } + + /** + * Generic method called when there is a WebGL context change. + * + */ + contextChange() + { + // do some codes init! + } + + /** + * Generic destroy methods to be overridden by the subclass + * + */ + destroy() + { + this.renderer.runners.contextChange.remove(this); + this.renderer = null; + } +} diff --git a/packages/core/src/batch/BatchSystem.js b/packages/core/src/batch/BatchSystem.js new file mode 100644 index 0000000..b04c249 --- /dev/null +++ b/packages/core/src/batch/BatchSystem.js @@ -0,0 +1,68 @@ +import System from '../System'; +import ObjectRenderer from './ObjectRenderer'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class BatchSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + super(renderer); + + /** + * An empty renderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.emptyRenderer = new ObjectRenderer(renderer); + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.currentRenderer = this.emptyRenderer; + } + + /** + * Changes the current renderer to the one given in parameter + * + * @param {PIXI.ObjectRenderer} objectRenderer - The object renderer to use. + */ + setObjectRenderer(objectRenderer) + { + if (this.currentRenderer === objectRenderer) + { + return; + } + + this.currentRenderer.stop(); + this.currentRenderer = objectRenderer; + + this.renderer.state.setState(objectRenderer.state); + + this.currentRenderer.start(); + } + + /** + * This should be called if you wish to do some custom rendering + * It will basically render anything that may be batched up such as sprites + * + */ + flush() + { + this.setObjectRenderer(this.emptyRenderer); + } + + reset() + { + this.setObjectRenderer(this.emptyRenderer); + } +} diff --git a/packages/core/src/batch/ObjectRenderer.js b/packages/core/src/batch/ObjectRenderer.js new file mode 100644 index 0000000..0c851a9 --- /dev/null +++ b/packages/core/src/batch/ObjectRenderer.js @@ -0,0 +1,48 @@ +import System from '../System'; + +/** + * Base for a common object renderer that can be used as a system renderer plugin. + * + * @class + * @extends PIXI.System + * @memberof PIXI + */ +export default class ObjectRenderer extends System +{ + /** + * Starts the renderer and sets the shader + * + */ + start() + { + // set the shader.. + } + + /** + * Stops the renderer + * + */ + stop() + { + this.flush(); + } + + /** + * Stub method for rendering content and emptying the current batch. + * + */ + flush() + { + // flush! + } + + /** + * Renders an object + * + * @param {PIXI.DisplayObject} object - The object to render. + */ + render(object) // eslint-disable-line no-unused-vars + { + // render the object + } +} diff --git a/packages/core/src/context/ContextSystem.js b/packages/core/src/context/ContextSystem.js new file mode 100644 index 0000000..301aab4 --- /dev/null +++ b/packages/core/src/context/ContextSystem.js @@ -0,0 +1,186 @@ +import System from '../System'; +import { settings } from '@pixi/settings'; + +let CONTEXT_UID = 0; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class ContextSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.webGLVersion = 1; + + this.handleContextLost = this.handleContextLost.bind(this); + this.handleContextRestored = this.handleContextRestored.bind(this); + + this.extensions = {}; + + renderer.view.addEventListener('webglcontextlost', this.handleContextLost, false); + renderer.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + } + + get isLost() + { + return (!this.gl || this.gl.isContextLost()); + } + + contextChange(gl) + { + this.gl = gl; + + // restore a context if it was previously lost + if (gl.isContextLost() && gl.getExtension('WEBGL_lose_context')) + { + gl.getExtension('WEBGL_lose_context').restoreContext(); + } + } + + initFromContext(gl) + { + this.gl = gl; + this.validateContext(gl); + this.renderer.gl = gl; + this.renderer.CONTEXT_UID = CONTEXT_UID++; + this.renderer.runners.contextChange.run(gl); + } + + initFromOptions(options) + { + const gl = this.createContext(this.renderer.view, options); + + this.initFromContext(gl); + } + + /** + * Helper class to create a webGL Context + * + * @class + * @memberof PIXI.glCore + * @param canvas {HTMLCanvasElement} the canvas element that we will get the context from + * @param options {Object} An options object that gets passed in to the canvas element containing the context attributes, + * see https://developer.mozilla.org/en/docs/Web/API/HTMLCanvasElement/getContext for the options available + * @return {WebGLRenderingContext} the WebGL context + */ + createContext(canvas, options) + { + let gl; + + if (settings.PREFER_WEBGL_2) + { + gl = canvas.getContext('webgl2', options); + } + + if (gl) + { + this.webGLVersion = 2; + } + else + { + this.webGLVersion = 1; + + gl = canvas.getContext('webgl', options) + || canvas.getContext('experimental-webgl', options); + + if (!gl) + { + // fail, not able to get a context + throw new Error('This browser does not support webGL. Try using the canvas renderer'); + } + } + + this.gl = gl; + + this.getExtensions(); + + return gl; + } + + getExtensions() + { + // time to set up default etensions that pixi uses.. + const gl = this.gl; + const extensions = this.extensions; + + if (this.webGLVersion === 1) + { + extensions.drawBuffers = gl.getExtension('WEBGL_draw_buffers'); + extensions.depthTexture = gl.getExtension('WEBKIT_WEBGL_depth_texture'); + extensions.floatTexture = gl.getExtension('OES_texture_float'); + extensions.loseContext = gl.getExtension('WEBGL_lose_context'); + + extensions.vertexArrayObject = gl.getExtension('OES_vertex_array_object') + || gl.getExtension('MOZ_OES_vertex_array_object') + || gl.getExtension('WEBKIT_OES_vertex_array_object'); + } + + // we don't use any specific WebGL 2 ones yet! + } + + /** + * Handles a lost webgl context + * + * @private + * @param {WebGLContextEvent} event - The context lost event. + */ + handleContextLost(event) + { + event.preventDefault(); + } + + /** + * Handles a restored webgl context + * + * @private + */ + handleContextRestored() + { + this.renderer.runners.contextChange.run(this.gl); + } + + destroy() + { + const view = this.renderer.view; + + // remove listeners + view.removeEventListener('webglcontextlost', this.handleContextLost); + view.removeEventListener('webglcontextrestored', this.handleContextRestored); + + this.gl.useProgram(null); + + if (this.extensions.loseContext) + { + this.extensions.loseContext.loseContext(); + } + } + + postrender() + { + this.gl.flush(); + } + + validateContext(gl) + { + const attributes = gl.getContextAttributes(); + + // this is going to be fairly simple for now.. but at least we have room to grow! + if (!attributes.stencil) + { + /* eslint-disable max-len */ + + /* eslint-disable no-console */ + console.warn('Provided WebGL context does not have a stencil buffer, masks may not render correctly'); + /* eslint-enable no-console */ + + /* eslint-enable max-len */ + } + } +} diff --git a/packages/core/src/filters/Filter.js b/packages/core/src/filters/Filter.js new file mode 100644 index 0000000..9d8cad8 --- /dev/null +++ b/packages/core/src/filters/Filter.js @@ -0,0 +1,140 @@ +import Shader from '../shader/Shader'; +import Program from '../shader/Program'; +import State from '../state/State'; +import { settings } from '@pixi/settings'; +// import extractUniformsFromSrc from './extractUniformsFromSrc'; +import defaultVertex from './defaultFilter.vert'; +import defaultFragment from './defaultFilter.frag'; + +// let math = require('../../math'); +/** + * @class + * @memberof PIXI + * @extends PIXI.Shader + */ +export default class Filter extends Shader +{ + /** + * @param {string} [vertexSrc] - The source of the vertex shader. + * @param {string} [fragmentSrc] - The source of the fragment shader. + * @param {object} [uniforms] - Custom uniforms to use to augment the built-in ones. + */ + constructor(vertexSrc, fragmentSrc, uniforms) + { + const program = Program.from(vertexSrc, fragmentSrc); + + super(program, uniforms); + + /** + * The padding of the filter. Some filters require extra space to breath such as a blur. + * Increasing this will add extra width and height to the bounds of the object that the + * filter is applied to. + * + * @member {number} + */ + this.padding = 0; + + /** + * The resolution of the filter. Setting this to be lower will lower the quality but + * increase the performance of the filter. + * + * @member {number} + */ + this.resolution = settings.RESOLUTION; + + /** + * If enabled is true the filter is applied, if false it will not. + * + * @member {boolean} + */ + this.enabled = true; + + /** + * If enabled, PixiJS will fit the filter area into boundaries for better performance. + * Switch it off if it does not work for specific shader. + * + * @member {boolean} + */ + this.autoFit = true; + + /** + * Legacy filters use position and uvs from attributes + * @member {boolean} + * @readonly + */ + this.legacy = !!this.program.attributeData.aTextureCoord; + + /** + * the webGL state the filter requires to render + * @member {PIXI.State} + */ + this.state = new State(); + } + + /** + * Applies the filter + * + * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + * @param {boolean} clear - Should the output be cleared before rendering to it + * @param {object} [currentState] - It's current state of filter. + * There are some useful properties in the currentState : + * target, filters, sourceFrame, destinationFrame, renderTarget, resolution + */ + apply(filterManager, input, output, clear, currentState, derp) // eslint-disable-line no-unused-vars + { + // do as you please! + + filterManager.applyFilter(this, input, output, clear, currentState, derp); + + // or just do a regular render.. + } + + /** + * Sets the blendmode of the filter + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL + */ + get blendMode() + { + return this.state.blendMode; + } + + set blendMode(value) // eslint-disable-line require-jsdoc + { + this.state.blendMode = value; + } + + /** + * The default vertex shader source + * + * @static + * @constant + */ + static get defaultVertexSrc() + { + return defaultVertex; + } + + /** + * The default fragment shader source + * + * @static + * @constant + */ + static get defaultFragmentSrc() + { + return defaultFragment; + } +} + +/** + * Used for caching shader IDs + * + * @static + * @private + */ +Filter.SOURCE_KEY_MAP = {}; + diff --git a/packages/core/src/filters/FilterSystem.js b/packages/core/src/filters/FilterSystem.js new file mode 100644 index 0000000..3174318 --- /dev/null +++ b/packages/core/src/filters/FilterSystem.js @@ -0,0 +1,431 @@ +import System from '../System'; + +import RenderTexture from '../renderTexture/RenderTexture'; +import Quad from '../utils/Quad'; +import QuadUv from '../utils/QuadUv'; +import { Rectangle } from '@pixi/math'; +import * as filterTransforms from './filterTransforms'; +import bitTwiddle from 'bit-twiddle'; +import UniformGroup from '../shader/UniformGroup'; +import { DRAW_MODES } from '@pixi/constants'; + +// +/** + * @ignore + * @class + */ +class FilterState +{ + /** + * + */ + constructor() + { + this.renderTexture = null; + this.sourceFrame = new Rectangle(); + this.destinationFrame = new Rectangle(); + this.filters = []; + this.target = null; + this.legacy = false; + this.resolution = 1; + } +} + +/** + * @class + * @memberof PIXI.systems + * @extends PIXI.System + */ +export default class FilterSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + /** + * stores a bunch of PO2 textures used for filtering + * @type {Object} + */ + this.texturePool = {}; + + /** + * a pool for storing filter states, save us creating new ones each tick + * @type {Array} + */ + this.statePool = []; + + /** + * A very simple geometry used when drawing a filter effect to the screen + * @type {Quad} + */ + this.quad = new Quad(); + + this.quadUv = new QuadUv(); + + /** + * Temporary rect for maths + * @type {PIXI.Rectangle} + */ + this.tempRect = new Rectangle(); + + this.activeState = {}; + + /** + * this uniform group is attached to filter uniforms when used + * @type {UniformGroup} + */ + this.globalUniforms = new UniformGroup({ + sourceFrame: this.tempRect, + destinationFrame: this.tempRect, + + // legacy variables + filterArea: new Float32Array(4), + filterClamp: new Float32Array(4), + }, true); + } + + /** + * Adds a new filter to the System. + * + * @param {PIXI.DisplayObject} target - The target of the filter to render. + * @param {PIXI.Filter[]} filters - The filters to apply. + */ + push(target, filters) + { + const renderer = this.renderer; + const filterStack = this.renderer.renderTexture.defaultFilterStack; + const state = this.statePool.pop() || new FilterState(); + + let resolution = filters[0].resolution; + let padding = filters[0].padding; + let autoFit = filters[0].autoFit; + let legacy = filters[0].legacy; + + for (let i = 1; i < filters.length; i++) + { + const filter = filters[i]; + + // lets use the lowest resolution.. + resolution = Math.min(resolution, filter.resolution); + // and the largest amount of padding! + padding = Math.max(padding, filter.padding); + // only auto fit if all filters are autofit + autoFit = autoFit || filter.autoFit; + + legacy = legacy || filter.legacy; + } + + filterStack.push(state); + + state.resolution = resolution; + + state.legacy = legacy; + + // round to whole number based on resolution + // TODO move that to the shader too? + state.sourceFrame = target.filterArea || target.getBounds(true); + + state.sourceFrame.pad(padding); + + if (autoFit) + { + state.sourceFrame.fit(this.renderer.renderTexture.destinationFrame); + } + + state.sourceFrame.round(resolution); + + state.renderTexture = this.getPotFilterTexture(state.sourceFrame.width, state.sourceFrame.height, resolution); + state.filters = filters; + + state.destinationFrame.width = state.renderTexture.width; + state.destinationFrame.height = state.renderTexture.height; + + state.renderTexture.filterFrame = state.sourceFrame; + renderer.renderTexture.bind(state.renderTexture, state.sourceFrame);// /, state.destinationFrame); + renderer.renderTexture.clear(); + } + + /** + * Pops off the filter and applies it. + * + */ + pop() + { + const renderer = this.renderer; + const filterStack = renderer.renderTexture.defaultFilterStack; + const state = filterStack.pop(); + const filters = state.filters; + + this.activeState = state; + + const globalUniforms = this.globalUniforms.uniforms; + + globalUniforms.sourceFrame = state.sourceFrame; + globalUniforms.destinationFrame = state.destinationFrame; + globalUniforms.resolution = state.resolution; + + // only update the rect if its legacy.. + if (state.legacy) + { + const filterArea = globalUniforms.filterArea; + const filterClamp = globalUniforms.filterClamp; + + filterArea[0] = state.destinationFrame.width; + filterArea[1] = state.destinationFrame.height; + filterArea[2] = state.sourceFrame.x; + filterArea[3] = state.sourceFrame.y; + + filterClamp[0] = 0.5 / state.resolution / state.destinationFrame.width; + filterClamp[1] = 0.5 / state.resolution / state.destinationFrame.height; + filterClamp[2] = (state.sourceFrame.width - 0.5) / state.resolution / state.destinationFrame.width; + filterClamp[3] = (state.sourceFrame.height - 0.5) / state.resolution / state.destinationFrame.height; + } + + this.globalUniforms.update(); + + const lastState = filterStack[filterStack.length - 1]; + + if (filters.length === 1) + { + filters[0].apply(this, state.renderTexture, lastState.renderTexture, false, state); + + this.returnFilterTexture(state.renderTexture); + } + else + { + let flip = state.renderTexture; + let flop = this.getPotFilterTexture( + flip.width, + flip.height, + state.resolution + ); + + flop.filterFrame = flip.filterFrame; + + let i = 0; + + for (i = 0; i < filters.length - 1; ++i) + { + filters[i].apply(this, flip, flop, true, state); + + const t = flip; + + flip = flop; + flop = t; + } + + filters[i].apply(this, flip, lastState.renderTexture, false, state); + + this.returnFilterTexture(flip); + this.returnFilterTexture(flop); + } + + this.statePool.push(state); + } + + /** + * Draws a filter. + * + * @param {PIXI.Filter} filter - The filter to draw. + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + * @param {boolean} clear - Should the output be cleared before rendering to it + */ + applyFilter(filter, input, output, clear) + { + const renderer = this.renderer; + + renderer.renderTexture.bind(output, output ? output.filterFrame : null); + + if (clear) + { + // gl.disable(gl.SCISSOR_TEST); + renderer.renderTexture.clear(); + // gl.enable(gl.SCISSOR_TEST); + } + + // set the uniforms.. + filter.uniforms.uSampler = input; + filter.uniforms.filterGlobals = this.globalUniforms; + + // TODO make it so that the order of this does not matter.. + // because it does at the moment cos of global uniforms. + // they need to get resynced + + renderer.state.setState(filter.state); + renderer.shader.bind(filter); + + if (filter.legacy) + { + this.quadUv.map(input._frame, input.filterFrame); + + renderer.geometry.bind(this.quadUv); + renderer.geometry.draw(DRAW_MODES.TRIANGLES); + } + else + { + renderer.geometry.bind(this.quad); + renderer.geometry.draw(DRAW_MODES.TRIANGLE_STRIP); + } + } + + /** + * Calculates the mapped matrix. + * + * TODO playing around here.. this is temporary - (will end up in the shader) + * this returns a matrix that will normalise map filter cords in the filter to screen space + * + * @param {PIXI.Matrix} outputMatrix - the matrix to output to. + * @return {PIXI.Matrix} The mapped matrix. + */ + calculateScreenSpaceMatrix(outputMatrix) + { + const currentState = this.activeState; + + return filterTransforms.calculateScreenSpaceMatrix( + outputMatrix, + currentState.sourceFrame, + currentState.destinationFrame + ); + } + + /** + * This will map the filter coord so that a texture can be used based on the transform of a sprite + * + * @param {PIXI.Matrix} outputMatrix - The matrix to output to. + * @param {PIXI.Sprite} sprite - The sprite to map to. + * @return {PIXI.Matrix} The mapped matrix. + */ + calculateSpriteMatrix(outputMatrix, sprite) + { + const currentState = this.activeState; + + return filterTransforms.calculateSpriteMatrix( + outputMatrix, + currentState.sourceFrame, + currentState.destinationFrame, + sprite + ); + } + + /** + * Destroys this Filter System. + * + * @param {boolean} [contextLost=false] context was lost, do not free shaders + * + */ + destroy(contextLost = false) + { + if (!contextLost) + { + this.emptyPool(); + } + else + { + this.texturePool = {}; + } + } + + /** + * Gets a Power-of-Two render texture. + * + * TODO move to a seperate class could be on renderer? + * also - could cause issue with multiple contexts? + * + * @private + * @param {WebGLRenderingContext} gl - The webgl rendering context + * @param {number} minWidth - The minimum width of the render target. + * @param {number} minHeight - The minimum height of the render target. + * @param {number} resolution - The resolution of the render target. + * @return {PIXI.RenderTarget} The new render target. + */ + getPotFilterTexture(minWidth, minHeight, resolution) + { + minWidth = bitTwiddle.nextPow2(minWidth); + minHeight = bitTwiddle.nextPow2(minHeight); + resolution = resolution || 1; + + const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + + if (!this.texturePool[key]) + { + this.texturePool[key] = []; + } + + let renderTexture = this.texturePool[key].pop(); + + if (!renderTexture) + { + // temporary bypass cache.. + // internally - this will cause a texture to be bound.. + renderTexture = RenderTexture.create({ + width: minWidth, + height: minHeight, + resolution, + }); + } + + return renderTexture; + } + + /** + * Gets extra render texture to use inside current filter + * + * @param {number} resolution resolution of the renderTexture + * @returns {PIXI.RenderTexture} + */ + getFilterTexture(resolution) + { + const rt = this.activeState.renderTexture; + + const filterTexture = this.getPotFilterTexture(rt.width, rt.height, resolution || rt.baseTexture.resolution); + + filterTexture.filterFrame = rt.filterFrame; + + return filterTexture; + } + + /** + * Frees a render texture back into the pool. + * + * @param {PIXI.RenderTarget} renderTexture - The renderTarget to free + */ + returnFilterTexture(renderTexture) + { + renderTexture.filterFrame = null; + + const base = renderTexture.baseTexture; + + const minWidth = base.width; + const minHeight = base.height; + + const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + + this.texturePool[key].push(renderTexture); + } + + /** + * Empties the texture pool. + * + */ + emptyPool() + { + for (const i in this.texturePool) + { + const textures = this.texturePool[i]; + + if (textures) + { + for (let j = 0; j < textures.length; j++) + { + textures[j].destroy(true); + } + } + } + + this.texturePool = {}; + } +} diff --git a/packages/core/src/filters/defaultFilter.frag b/packages/core/src/filters/defaultFilter.frag new file mode 100644 index 0000000..a14c1be --- /dev/null +++ b/packages/core/src/filters/defaultFilter.frag @@ -0,0 +1,22 @@ +varying vec2 vTextureCoord; +varying vec2 vFilterCoord; + +uniform sampler2D uSampler; +uniform sampler2D filterSampler; + +void main(void){ + vec4 masky = texture2D(filterSampler, vFilterCoord); + vec4 sample = texture2D(uSampler, vTextureCoord); + vec4 color; + if(mod(vFilterCoord.x, 1.0) > 0.5) + { + color = vec4(1.0, 0.0, 0.0, 1.0); + } + else + { + color = vec4(0.0, 1.0, 0.0, 1.0); + } + // gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0); + gl_FragColor = mix(sample, masky, 0.5); + gl_FragColor *= sample.a; +} diff --git a/packages/core/src/filters/defaultFilter.vert b/packages/core/src/filters/defaultFilter.vert new file mode 100644 index 0000000..e1febca --- /dev/null +++ b/packages/core/src/filters/defaultFilter.vert @@ -0,0 +1,14 @@ +attribute vec2 aVertexPosition; +attribute vec2 aTextureCoord; + +uniform mat3 projectionMatrix; +uniform mat3 filterMatrix; + +varying vec2 vTextureCoord; +varying vec2 vFilterCoord; + +void main(void){ + gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); + vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy; + vTextureCoord = aTextureCoord ; +} diff --git a/packages/core/src/filters/filterTransforms.js b/packages/core/src/filters/filterTransforms.js new file mode 100644 index 0000000..87a3cd6 --- /dev/null +++ b/packages/core/src/filters/filterTransforms.js @@ -0,0 +1,50 @@ +import { Matrix } from '@pixi/math'; + +/** + * Calculates the mapped matrix + * @param filterArea {Rectangle} The filter area + * @param sprite {Sprite} the target sprite + * @param outputMatrix {Matrix} @alvin + * @private + */ +// this returns a matrix that will normalise map filter cords in the filter to screen space +export function calculateScreenSpaceMatrix(outputMatrix, filterArea, textureSize) +{ + // TODO unwrap? + const mappedMatrix = outputMatrix.identity(); + + mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); + + mappedMatrix.scale(textureSize.width, textureSize.height); + + return mappedMatrix; +} + +export function calculateNormalizedScreenSpaceMatrix(outputMatrix, filterArea, textureSize) +{ + const mappedMatrix = outputMatrix.identity(); + + mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); + + const translateScaleX = (textureSize.width / filterArea.width); + const translateScaleY = (textureSize.height / filterArea.height); + + mappedMatrix.scale(translateScaleX, translateScaleY); + + return mappedMatrix; +} + +// this will map the filter coord so that a texture can be used based on the transform of a sprite +export function calculateSpriteMatrix(outputMatrix, filterArea, textureSize, sprite) +{ + const orig = sprite._texture.orig; + const mappedMatrix = outputMatrix.set(textureSize.width, 0, 0, textureSize.height, filterArea.x, filterArea.y); + const worldTransform = sprite.worldTransform.copyTo(Matrix.TEMP_MATRIX); + + worldTransform.invert(); + mappedMatrix.prepend(worldTransform); + mappedMatrix.scale(1.0 / orig.width, 1.0 / orig.height); + mappedMatrix.translate(sprite.anchor.x, sprite.anchor.y); + + return mappedMatrix; +} diff --git a/packages/core/src/filters/spriteMask/SpriteMaskFilter.js b/packages/core/src/filters/spriteMask/SpriteMaskFilter.js new file mode 100644 index 0000000..91697b7 --- /dev/null +++ b/packages/core/src/filters/spriteMask/SpriteMaskFilter.js @@ -0,0 +1,63 @@ +import Filter from '../Filter'; +import { Matrix } from '@pixi/math'; +import vertex from './spriteMaskFilter.vert'; +import fragment from './spriteMaskFilter.frag'; +import { default as TextureMatrix } from '../../textures/TextureMatrix'; + +/** + * The SpriteMaskFilter class + * + * @class + * @extends PIXI.Filter + * @memberof PIXI + */ +export default class SpriteMaskFilter extends Filter +{ + /** + * @param {PIXI.Sprite} sprite - the target sprite + */ + constructor(sprite) + { + const maskMatrix = new Matrix(); + + super(vertex, fragment); + + sprite.renderable = false; + + this.maskSprite = sprite; + this.maskMatrix = maskMatrix; + } + + /** + * Applies the filter + * + * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + */ + apply(filterManager, input, output) + { + const maskSprite = this.maskSprite; + const tex = this.maskSprite.texture; + + if (!tex.valid) + { + return; + } + if (!tex.transform) + { + // margin = 0.0, let it bleed a bit, shader code becomes easier + // assuming that atlas textures were made with 1-pixel padding + tex.transform = new TextureMatrix(tex, 0.0); + } + tex.transform.update(); + + this.uniforms.mask = tex; + this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite) + .prepend(tex.transform.mapCoord); + this.uniforms.alpha = maskSprite.worldAlpha; + this.uniforms.maskClamp = tex.transform.uClampFrame; + + filterManager.applyFilter(this, input, output); + } +} diff --git a/packages/core/src/filters/spriteMask/spriteMaskFilter.frag b/packages/core/src/filters/spriteMask/spriteMaskFilter.frag new file mode 100644 index 0000000..0e4aef8 --- /dev/null +++ b/packages/core/src/filters/spriteMask/spriteMaskFilter.frag @@ -0,0 +1,23 @@ +varying vec2 vMaskCoord; +varying vec2 vTextureCoord; + +uniform sampler2D uSampler; +uniform sampler2D mask; +uniform float alpha; +uniform vec4 maskClamp; + +void main(void) +{ + float clip = step(3.5, + step(maskClamp.x, vMaskCoord.x) + + step(maskClamp.y, vMaskCoord.y) + + step(vMaskCoord.x, maskClamp.z) + + step(vMaskCoord.y, maskClamp.w)); + + vec4 original = texture2D(uSampler, vTextureCoord); + vec4 masky = texture2D(mask, vMaskCoord); + + original *= (masky.r * masky.a * alpha * clip); + + gl_FragColor = original; +} diff --git a/packages/core/src/filters/spriteMask/spriteMaskFilter.vert b/packages/core/src/filters/spriteMask/spriteMaskFilter.vert new file mode 100644 index 0000000..d23a30c --- /dev/null +++ b/packages/core/src/filters/spriteMask/spriteMaskFilter.vert @@ -0,0 +1,16 @@ +attribute vec2 aVertexPosition; +attribute vec2 aTextureCoord; + +uniform mat3 projectionMatrix; +uniform mat3 otherMatrix; + +varying vec2 vMaskCoord; +varying vec2 vTextureCoord; + +void main(void) +{ + gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); + + vTextureCoord = aTextureCoord; + vMaskCoord = ( otherMatrix * vec3( aTextureCoord, 1.0) ).xy; +} diff --git a/packages/core/src/framebuffer/FrameBuffer.js b/packages/core/src/framebuffer/FrameBuffer.js new file mode 100644 index 0000000..84e9fcc --- /dev/null +++ b/packages/core/src/framebuffer/FrameBuffer.js @@ -0,0 +1,106 @@ +import Texture from '../textures/BaseTexture'; +import { FORMATS, TYPES } from '@pixi/constants'; + +/** + * Frame buffer + * @class + * @memberof PIXI + */ +export default class FrameBuffer +{ + constructor(width, height) + { + this.width = width || 100; + this.height = height || 100; + + this.stencil = false; + this.depth = false; + + this.dirtyId = 0; + this.dirtyFormat = 0; + this.dirtySize = 0; + + this.depthTexture = null; + this.colorTextures = []; + + this.glFrameBuffers = {}; + } + + get colorTexture() + { + return this.colorTextures[0]; + } + + addColorTexture(index, texture) + { + // TODO add some validation to the texture - same width / height etc? + this.colorTextures[index || 0] = texture || new Texture(null, { scaleMode: 0, + resolution: 1, + mipmap: false, + width: this.width, + height: this.height });// || new Texture(); + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + addDepthTexture(texture) + { + /* eslint-disable max-len */ + this.depthTexture = texture || new Texture(null, { scaleMode: 0, + resolution: 1, + width: this.width, + height: this.height, + mipmap: false, + format: FORMATS.DEPTH_COMPONENT, + type: TYPES.UNSIGNED_SHORT });// UNSIGNED_SHORT; + /* eslint-disable max-len */ + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + enableDepth() + { + this.depth = true; + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + enableStencil() + { + this.stencil = true; + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + resize(width, height) + { + if (width === this.width && height === this.height) return; + + this.width = width; + this.height = height; + + this.dirtyId++; + this.dirtySize++; + + for (let i = 0; i < this.colorTextures.length; i++) + { + this.colorTextures[i].setSize(width, height); + } + + if (this.depthTexture) + { + this.depthTexture.setSize(width, height); + } + } +} diff --git a/packages/core/src/framebuffer/FramebufferSystem.js b/packages/core/src/framebuffer/FramebufferSystem.js new file mode 100644 index 0000000..2cc2437 --- /dev/null +++ b/packages/core/src/framebuffer/FramebufferSystem.js @@ -0,0 +1,246 @@ +import System from '../System'; +import { Rectangle } from '@pixi/math'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class FramebufferSystem extends System +{ + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + contextChange() + { + this.gl = this.renderer.gl; + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + this.current = null; + this.viewport = new Rectangle(); + + this.drawBufferExtension = this.renderer.context.extensions.drawBuffers; + } + + bind(framebuffer, frame) + { + const gl = this.gl; + + this.current = framebuffer; + + if (framebuffer) + { + // TODO cacheing layer! + + const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID] || this.initFramebuffer(framebuffer); + + gl.bindFramebuffer(gl.FRAMEBUFFER, fbo.framebuffer); + // makesure all textures are unbound.. + + // now check for updates... + if (fbo.dirtyId !== framebuffer.dirtyId) + { + fbo.dirtyId = framebuffer.dirtyId; + + if (fbo.dirtyFormat !== framebuffer.dirtyFormat) + { + fbo.dirtyFormat = framebuffer.dirtyFormat; + this.updateFramebuffer(framebuffer); + } + else if (fbo.dirtySize !== framebuffer.dirtySize) + { + fbo.dirtySize = framebuffer.dirtySize; + this.resizeFramebuffer(framebuffer); + } + } + + for (let i = 0; i < framebuffer.colorTextures.length; i++) + { + if (framebuffer.colorTextures[i].texturePart) + { + this.renderer.texture.unbind(framebuffer.colorTextures[i].texture); + } + else + { + this.renderer.texture.unbind(framebuffer.colorTextures[i]); + } + } + + if (framebuffer.depthTexture) + { + this.renderer.texture.unbind(framebuffer.depthTexture); + } + + if (frame) + { + this.setViewport(frame.x, frame.y, frame.width, frame.height); + } + else + { + this.setViewport(0, 0, framebuffer.width, framebuffer.height); + } + } + else + { + gl.bindFramebuffer(gl.FRAMEBUFFER, null); + + if (frame) + { + this.setViewport(frame.x, frame.y, frame.width, frame.height); + } + else + { + this.setViewport(0, 0, this.renderer.width, this.renderer.height); + } + } + } + + setViewport(x, y, width, height) + { + const v = this.viewport; + + if (v.width !== width || v.height !== height || v.x !== x || v.y !== y) + { + v.x = x; + v.y = y; + v.width = width; + v.height = height; + + this.gl.viewport(x, y, width, height); + } + } + + get size() + { + if (this.current) + { + // TODO store temp + return { x: 0, y: 0, width: this.current.width, height: this.current.height }; + } + + return { x: 0, y: 0, width: this.renderer.width, height: this.renderer.height }; + } + + clear(r, g, b, a) + { + const gl = this.gl; + + // TODO clear color can be set only one right? + gl.clearColor(r, g, b, a); + gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); + } + + // private functions... + + initFramebuffer(framebuffer) + { + const gl = this.gl; + + // TODO - make this a class? + const fbo = { + framebuffer: gl.createFramebuffer(), + stencil: null, + dirtyId: 0, + dirtyFormat: 0, + dirtySize: 0, + }; + + framebuffer.glFrameBuffers[this.CONTEXT_UID] = fbo; + + return fbo; + } + + resizeFramebuffer(framebuffer) + { + const gl = this.gl; + + if (framebuffer.stencil || framebuffer.depth) + { + gl.bindRenderbuffer(gl.RENDERBUFFER, this.stencil); + gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); + } + } + + updateFramebuffer(framebuffer) + { + const gl = this.gl; + + const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID]; + + // bind the color texture + const colorTextures = framebuffer.colorTextures; + + let count = colorTextures.length; + + if (!this.drawBufferExtension) + { + count = Math.min(count, 1); + } + + const activeTextures = []; + + for (let i = 0; i < count; i++) + { + const texture = framebuffer.colorTextures[i]; + + if (texture.texturePart) + { + this.renderer.texture.bind(texture.texture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.COLOR_ATTACHMENT0 + i, + gl.TEXTURE_CUBE_MAP_NEGATIVE_X + texture.side, + texture.texture._glTextures[this.CONTEXT_UID].texture, + 0); + } + else + { + this.renderer.texture.bind(texture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.COLOR_ATTACHMENT0 + i, + gl.TEXTURE_2D, + texture._glTextures[this.CONTEXT_UID].texture, + 0); + } + + activeTextures.push(gl.COLOR_ATTACHMENT0 + i); + } + + if (this.drawBufferExtension && activeTextures.length > 1) + { + this.drawBufferExtension.drawBuffersWEBGL(activeTextures); + } + + if (framebuffer.depthTexture) + { + const depthTextureExt = this.renderer.context.extensions.depthTexture; + + if (depthTextureExt) + { + const depthTexture = framebuffer.depthTexture; + + this.renderer.texture.bind(depthTexture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.DEPTH_ATTACHMENT, + gl.TEXTURE_2D, + depthTexture._glTextures[this.CONTEXT_UID].texture, + 0); + } + } + + if (framebuffer.stencil || framebuffer.depth) + { + fbo.stencil = gl.createRenderbuffer(); + + gl.bindRenderbuffer(gl.RENDERBUFFER, fbo.stencil); + + // TODO.. this is depth AND stencil? + gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, fbo.stencil); + gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); + // fbo.enableStencil(); + } + } +} diff --git a/packages/core/src/geometry/GLBuffer.js b/packages/core/src/geometry/GLBuffer.js new file mode 100644 index 0000000..0477e8a --- /dev/null +++ b/packages/core/src/geometry/GLBuffer.js @@ -0,0 +1,9 @@ +export default class GLBuffer +{ + constructor(buffer) + { + this.buffer = buffer; + this.updateID = -1; + this.byteLength = -1; + } +} diff --git a/packages/core/src/geometry/GeometrySystem.js b/packages/core/src/geometry/GeometrySystem.js new file mode 100644 index 0000000..959f827 --- /dev/null +++ b/packages/core/src/geometry/GeometrySystem.js @@ -0,0 +1,388 @@ +import System from '../System'; +import GLBuffer from './GLBuffer'; + +const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class GeometrySystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this._activeGeometry = null; + this._activeVao = null; + + this.hasVao = true; + this.hasInstance = true; + } + + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + contextChange() + { + const gl = this.gl = this.renderer.gl; + + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + + // webgl2 + if (!gl.createVertexArray) + { + // webgl 1! + let nativeVaoExtension = this.renderer.context.extensions.vertexArrayObject; + + if (this.renderer.legacy) + { + nativeVaoExtension = null; + } + + if (nativeVaoExtension) + { + gl.createVertexArray = () => + nativeVaoExtension.createVertexArrayOES(); + + gl.bindVertexArray = (vao) => + nativeVaoExtension.bindVertexArrayOES(vao); + + gl.deleteVertexArray = (vao) => + nativeVaoExtension.deleteVertexArrayOES(vao); + } + else + { + this.hasVao = false; + gl.createVertexArray = () => + { + // empty + }; + + gl.bindVertexArray = () => + { + // empty + }; + + gl.deleteVertexArray = () => + { + // empty + }; + } + } + + if (!gl.vertexAttribDivisor) + { + const instanceExt = gl.getExtension('ANGLE_instanced_arrays'); + + if (instanceExt) + { + gl.vertexAttribDivisor = (a, b) => + instanceExt.vertexAttribDivisorANGLE(a, b); + + gl.drawElementsInstanced = (a, b, c, d, e) => + instanceExt.drawElementsInstancedANGLE(a, b, c, d, e); + + gl.drawArraysInstanced = (a, b, c, d) => + instanceExt.drawArraysInstancedANGLE(a, b, c, d); + } + else + { + this.hasInstance = false; + } + } + } + + /** + * Binds geometry so that is can be drawn. Creating a Vao if required + * @private + * @param {PIXI.Geometry} geometry instance of geometry to bind + */ + bind(geometry, shader) + { + shader = shader || this.renderer.shader.shader; + + const gl = this.gl; + + // not sure the best way to address this.. + // currently different shaders require different VAOs for the same geometry + // Still mulling over the best way to solve this one.. + // will likely need to modify the shader attribute locations at run time! + let vaos = geometry.glVertexArrayObjects[this.CONTEXT_UID]; + + if (!vaos) + { + geometry.glVertexArrayObjects[this.CONTEXT_UID] = vaos = {}; + } + + const vao = vaos[shader.program.id] || this.initGeometryVao(geometry, shader.program); + + this._activeGeometry = geometry; + + if (this._activeVao !== vao) + { + this._activeVao = vao; + + if (this.hasVao) + { + gl.bindVertexArray(vao); + } + else + { + this.activateVao(geometry, shader.program); + } + } + + // TODO - optimise later! + // don't need to loop through if nothing changed! + // maybe look to add an 'autoupdate' to geometry? + this.updateBuffers(); + } + + reset() + { + this.unbind(); + } + + updateBuffers() + { + const geometry = this._activeGeometry; + const gl = this.gl; + + for (let i = 0; i < geometry.buffers.length; i++) + { + const buffer = geometry.buffers[i]; + + const glBuffer = buffer._glBuffers[this.CONTEXT_UID]; + + if (buffer._updateID !== glBuffer.updateID) + { + glBuffer.updateID = buffer._updateID; + + // TODO can cache this on buffer! maybe added a getter / setter? + const type = buffer.index ? gl.ELEMENT_ARRAY_BUFFER : gl.ARRAY_BUFFER; + const drawType = buffer.static ? gl.STATIC_DRAW : gl.DYNAMIC_DRAW; + + gl.bindBuffer(type, glBuffer.buffer); + + if (glBuffer.byteLength >= buffer.data.byteLength) + { + // offset is always zero for now! + gl.bufferSubData(type, 0, buffer.data); + } + else + { + gl.bufferData(type, buffer.data, drawType); + } + } + } + } + + checkCompatability(geometry, program) + { + // geometry must have at least all the attributes that the shader requires. + const geometryAttributes = geometry.attributes; + const shaderAttributes = program.attributeData; + + for (const j in shaderAttributes) + { + if (!geometryAttributes[j]) + { + throw new Error(`shader and geometry incompatible, geometry missing the "${j}" attribute`); + } + } + } + + /** + * Creates a Vao with the same structure as the geometry and stores it on the geometry. + * @private + * @param {PIXI.Geometry} geometry instance of geometry to to generate Vao for + */ + initGeometryVao(geometry, program) + { + this.checkCompatability(geometry, program); + + const gl = this.gl; + const CONTEXT_UID = this.CONTEXT_UID; + const buffers = geometry.buffers; + const attributes = geometry.attributes; + + const tempStride = {}; + const tempStart = {}; + + for (const j in buffers) + { + tempStride[j] = 0; + tempStart[j] = 0; + } + + for (const j in attributes) + { + if (!attributes[j].size && program.attributeData[j]) + { + attributes[j].size = program.attributeData[j].size; + } + + tempStride[attributes[j].buffer] += attributes[j].size * byteSizeMap[attributes[j].type]; + } + + for (const j in attributes) + { + const attribute = attributes[j]; + const attribSize = attribute.size; + + if (attribute.stride === undefined) + { + if (tempStride[attribute.buffer] === attribSize * byteSizeMap[attribute.type]) + { + attribute.stride = 0; + } + else + { + attribute.stride = tempStride[attribute.buffer]; + } + } + + if (attribute.start === undefined) + { + attribute.start = tempStart[attribute.buffer]; + + tempStart[attribute.buffer] += attribSize * byteSizeMap[attribute.type]; + } + } + + // first update - and create the buffers! + // only create a gl buffer if it actually gets + for (let i = 0; i < buffers.length; i++) + { + const buffer = buffers[i]; + + if (!buffer._glBuffers[CONTEXT_UID]) + { + buffer._glBuffers[CONTEXT_UID] = new GLBuffer(gl.createBuffer()); + } + } + + // TODO - maybe make this a data object? + // lets wait to see if we need to first! + const vao = gl.createVertexArray(); + + gl.bindVertexArray(vao); + + this.activateVao(geometry, program); + + gl.bindVertexArray(null); + + // add it to the cache! + geometry.glVertexArrayObjects[this.CONTEXT_UID][program.id] = vao; + + return vao; + } + + activateVao(geometry, program) + { + const gl = this.gl; + const CONTEXT_UID = this.CONTEXT_UID; + const buffers = geometry.buffers; + const attributes = geometry.attributes; + + if (geometry.indexBuffer) + { + // first update the index buffer if we have one.. + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, geometry.indexBuffer._glBuffers[CONTEXT_UID].buffer); + } + + let lastBuffer = null; + + // add a new one! + for (const j in attributes) + { + const attribute = attributes[j]; + const buffer = buffers[attribute.buffer]; + const glBuffer = buffer._glBuffers[CONTEXT_UID]; + + if (program.attributeData[j]) + { + if (lastBuffer !== glBuffer) + { + gl.bindBuffer(gl.ARRAY_BUFFER, glBuffer.buffer); + + lastBuffer = glBuffer; + } + + const location = program.attributeData[j].location; + + // TODO introduce state again + // we can optimise this for older devices that have no VAOs + gl.enableVertexAttribArray(location); + + gl.vertexAttribPointer(location, + attribute.size, + attribute.type || gl.FLOAT, + attribute.normalized, + attribute.stride, + attribute.start); + + if (attribute.instance) + { + // TODO calculate instance count based of this... + if (this.hasInstance) + { + gl.vertexAttribDivisor(location, 1); + } + else + { + throw new Error('geometry error, GPU Instancing is not supported on this device'); + } + } + } + } + } + + draw(type, size, start, instanceCount) + { + const gl = this.gl; + const geometry = this._activeGeometry; + + // TODO.. this should not change so maybe cache the function? + + if (geometry.indexBuffer) + { + if (geometry.instanced) + { + /* eslint-disable max-len */ + gl.drawElementsInstanced(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2, instanceCount || 1); + /* eslint-enable max-len */ + } + else + { + gl.drawElements(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2); + } + } + else + if (geometry.instanced) + { + // TODO need a better way to calculate size.. + gl.drawArraysInstanced(type, start, size || geometry.getSize(), instanceCount || 1); + } + else + { + gl.drawArrays(type, start, size || geometry.getSize()); + } + + return this; + } + + unbind() + { + this.gl.bindVertexArray(null); + this._activeVao = null; + this._activeGeometry = null; + } +} diff --git a/packages/core/src/geometry/utils/setVertexAttribArrays.js b/packages/core/src/geometry/utils/setVertexAttribArrays.js new file mode 100644 index 0000000..23b6959 --- /dev/null +++ b/packages/core/src/geometry/utils/setVertexAttribArrays.js @@ -0,0 +1,54 @@ +// var GL_MAP = {}; + +/** + * @param gl {WebGLRenderingContext} The current WebGL context + * @param attribs {*} + * @param state {*} + */ +export default function setVertexAttribArrays(gl, attribs, state) +{ + let i; + + if (state) + { + const tempAttribState = state.tempAttribState; + const attribState = state.attribState; + + for (i = 0; i < tempAttribState.length; i++) + { + tempAttribState[i] = false; + } + + // set the new attribs + for (i = 0; i < attribs.length; i++) + { + tempAttribState[attribs[i].attribute.location] = true; + } + + for (i = 0; i < attribState.length; i++) + { + if (attribState[i] !== tempAttribState[i]) + { + attribState[i] = tempAttribState[i]; + + if (state.attribState[i]) + { + gl.enableVertexAttribArray(i); + } + else + { + gl.disableVertexAttribArray(i); + } + } + } + } + else + { + for (i = 0; i < attribs.length; i++) + { + const attrib = attribs[i]; + + gl.enableVertexAttribArray(attrib.attribute.location); + } + } +} diff --git a/packages/core/src/index.js b/packages/core/src/index.js index 58c456a..fd1f547 100644 --- a/packages/core/src/index.js +++ b/packages/core/src/index.js @@ -1,30 +1,30 @@ import * as resources from './textures/resources'; -import * as systems from './renderers/systems'; +import * as systems from './systems'; -export { resources }; export { systems }; +export { resources }; -export { default as Renderer } from './renderers/Renderer'; -export { default as AbstractRenderer } from './renderers/AbstractRenderer'; -export { default as FrameBuffer } from './textures/FrameBuffer'; +export { default as System } from './System'; +export { default as Renderer } from './Renderer'; +export { default as AbstractRenderer } from './AbstractRenderer'; +export { default as FrameBuffer } from './framebuffer/FrameBuffer'; export { default as CubeTexture } from './textures/CubeTexture'; export { default as BaseTexture } from './textures/BaseTexture'; export { default as Texture } from './textures/Texture'; export { default as TextureMatrix } from './textures/TextureMatrix'; -export { default as RenderTexture } from './textures/RenderTexture'; -export { default as BaseRenderTexture } from './textures/BaseRenderTexture'; +export { default as RenderTexture } from './renderTexture/RenderTexture'; +export { default as BaseRenderTexture } from './renderTexture/BaseRenderTexture'; export { default as TextureUvs } from './textures/TextureUvs'; -export { default as State } from './renderers/State'; -export { default as ObjectRenderer } from './renderers/utils/ObjectRenderer'; -export { default as Quad } from './renderers/utils/Quad'; -export { default as QuadUv } from './renderers/utils/QuadUv'; -export { default as checkMaxIfStatmentsInShader } from './renderers/utils/checkMaxIfStatmentsInShader'; +export { default as State } from './state/State'; +export { default as ObjectRenderer } from './batch/ObjectRenderer'; +export { default as Quad } from './utils/Quad'; +export { default as QuadUv } from './utils/QuadUv'; +export { default as checkMaxIfStatmentsInShader } from './shader/utils/checkMaxIfStatmentsInShader'; export { default as Shader } from './shader/Shader'; export { default as Program } from './shader/Program'; export { default as UniformGroup } from './shader/UniformGroup'; -export { default as SpriteMaskFilter } from './renderers/filters/spriteMask/SpriteMaskFilter'; -export { default as Filter } from './renderers/filters/Filter'; +export { default as SpriteMaskFilter } from './filters/spriteMask/SpriteMaskFilter'; +export { default as Filter } from './filters/Filter'; export { default as Attribute } from './geometry/Attribute'; export { default as Buffer } from './geometry/Buffer'; export { default as Geometry } from './geometry/Geometry'; - diff --git a/packages/core/src/mask/MaskSystem.js b/packages/core/src/mask/MaskSystem.js new file mode 100644 index 0000000..99174c2 --- /dev/null +++ b/packages/core/src/mask/MaskSystem.js @@ -0,0 +1,199 @@ +import System from '../System'; +import AlphaMaskFilter from '../filters/spriteMask/SpriteMaskFilter'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class MaskSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + // TODO - we don't need both! + this.scissor = false; + this.scissorData = null; + this.scissorRenderTarget = null; + + this.enableScissor = false; + + this.alphaMaskPool = []; + this.alphaMaskIndex = 0; + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.DisplayObject} target - Display Object to push the mask to + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + push(target, maskData) + { + // TODO the root check means scissor rect will not + // be used on render textures more info here: + // https://github.com/pixijs/pixi.js/pull/3545 + + if (maskData.texture) + { + this.pushSpriteMask(target, maskData); + } + else if (this.enableScissor + && !this.scissor + && this.renderer._activeRenderTarget.root + && !this.renderer.stencil.stencilMaskStack.length + && maskData.isFastRect()) + { + const matrix = maskData.worldTransform; + + let rot = Math.atan2(matrix.b, matrix.a); + + // use the nearest degree! + rot = Math.round(rot * (180 / Math.PI)); + + if (rot % 90) + { + this.pushStencilMask(maskData); + } + else + { + this.pushScissorMask(target, maskData); + } + } + else + { + this.pushStencilMask(maskData); + } + } + + /** + * Removes the last mask from the mask stack and doesn't return it. + * + * @param {PIXI.DisplayObject} target - Display Object to pop the mask from + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + pop(target, maskData) + { + if (maskData.texture) + { + this.popSpriteMask(target, maskData); + } + else if (this.enableScissor && !this.renderer.stencil.stencilMaskStack.length) + { + this.popScissorMask(target, maskData); + } + else + { + this.popStencilMask(target, maskData); + } + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.RenderTarget} target - Display Object to push the sprite mask to + * @param {PIXI.Sprite} maskData - Sprite to be used as the mask + */ + pushSpriteMask(target, maskData) + { + let alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; + + if (!alphaMaskFilter) + { + alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; + } + + alphaMaskFilter[0].resolution = this.renderer.resolution; + alphaMaskFilter[0].maskSprite = maskData; + + // TODO - may cause issues! + target.filterArea = maskData.getBounds(true); + + this.renderer.filterSystem.pushFilter(target, alphaMaskFilter); + + this.alphaMaskIndex++; + } + + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popSpriteMask() + { + this.renderer.filterSystem.popFilter(); + this.alphaMaskIndex--; + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + pushStencilMask(maskData) + { + this.renderer.batch.flush(); + this.renderer.stencil.pushStencil(maskData); + } + + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popStencilMask() + { + // this.renderer.currentRenderer.stop(); + this.renderer.stencil.popStencil(); + } + + /** + * + * @param {PIXI.DisplayObject} target - Display Object to push the mask to + * @param {PIXI.Graphics} maskData - The masking data. + */ + pushScissorMask(target, maskData) + { + maskData.renderable = true; + + const renderTarget = this.renderer._activeRenderTarget; + + const bounds = maskData.getBounds(); + + bounds.fit(renderTarget.size); + maskData.renderable = false; + + this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); + + const resolution = this.renderer.resolution; + + this.renderer.gl.scissor( + bounds.x * resolution, + (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, + bounds.width * resolution, + bounds.height * resolution + ); + + this.scissorRenderTarget = renderTarget; + this.scissorData = maskData; + this.scissor = true; + } + + /** + * + * + */ + popScissorMask() + { + this.scissorRenderTarget = null; + this.scissorData = null; + this.scissor = false; + + // must be scissor! + const gl = this.renderer.gl; + + gl.disable(gl.SCISSOR_TEST); + } +} diff --git a/packages/core/src/mask/StencilSystem.js b/packages/core/src/mask/StencilSystem.js new file mode 100644 index 0000000..62796a5 --- /dev/null +++ b/packages/core/src/mask/StencilSystem.js @@ -0,0 +1,133 @@ +import System from '../System'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class StencilSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + this.stencilMaskStack = []; + } + + /** + * Changes the mask stack that is used by this System. + * + * @param {PIXI.Graphics[]} stencilMaskStack - The mask stack + */ + setMaskStack(stencilMaskStack) + { + const gl = this.renderer.gl; + + if (stencilMaskStack.length !== this.stencilMaskStack.length) + { + if (stencilMaskStack.length === 0) + { + gl.disable(gl.STENCIL_TEST); + } + else + { + gl.enable(gl.STENCIL_TEST); + } + } + + this.stencilMaskStack = stencilMaskStack; + } + + /** + * Applies the Mask and adds it to the current stencil stack. @alvin + * + * @param {PIXI.Graphics} graphics - The mask + */ + pushStencil(graphics) + { + this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); + + // this.renderer._activeRenderTarget.attachStencilBuffer(); + + const gl = this.renderer.gl; + const prevMaskCount = this.stencilMaskStack.length; + + if (prevMaskCount === 0) + { + gl.enable(gl.STENCIL_TEST); + } + + this.stencilMaskStack.push(graphics); + + // Increment the refference stencil value where the new mask overlaps with the old ones. + gl.colorMask(false, false, false, false); + gl.stencilFunc(gl.EQUAL, prevMaskCount, this._getBitwiseMask()); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.INCR); + this.renderer.plugins.graphics.render(graphics); + + this._useCurrent(); + } + + /** + * Removes the last mask from the stencil stack. @alvin + */ + popStencil() + { + this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); + + const gl = this.renderer.gl; + const graphics = this.stencilMaskStack.pop(); + + if (this.stencilMaskStack.length === 0) + { + // the stack is empty! + gl.disable(gl.STENCIL_TEST); + gl.clear(gl.STENCIL_BUFFER_BIT); + gl.clearStencil(0); + } + else + { + // Decrement the refference stencil value where the popped mask overlaps with the other ones + gl.colorMask(false, false, false, false); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.DECR); + this.renderer.plugins.graphics.render(graphics); + + this._useCurrent(); + } + } + + /** + * Setup renderer to use the current stencil data. + */ + _useCurrent() + { + const gl = this.renderer.gl; + + gl.colorMask(true, true, true, true); + gl.stencilFunc(gl.EQUAL, this.stencilMaskStack.length, this._getBitwiseMask()); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP); + } + + /** + * Fill 1s equal to the number of acitve stencil masks. + * + * @return {number} The bitwise mask. + */ + _getBitwiseMask() + { + return (1 << this.stencilMaskStack.length) - 1; + } + + /** + * Destroys the mask stack. + * + */ + destroy() + { + super.destroy(this); + + this.stencilMaskStack = null; + } +} diff --git a/packages/core/src/projection/ProjectionSystem.js b/packages/core/src/projection/ProjectionSystem.js new file mode 100644 index 0000000..6e63b73 --- /dev/null +++ b/packages/core/src/projection/ProjectionSystem.js @@ -0,0 +1,73 @@ +import System from '../System'; +import { Matrix } from '@pixi/math'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class ProjectionSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.projectionMatrix = new Matrix(); + } + + update(destinationFrame, sourceFrame, resolution, root) + { + this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; + this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; + + this.calculateProjection(this.destinationFrame, this.sourceFrame, resolution, root); + + this.renderer.globalUniforms.uniforms.projectionMatrix = this.projectionMatrix; + this.renderer.globalUniforms.update(); + } + + /** + * Updates the projection matrix based on a projection frame (which is a rectangle) + * + * @param {Rectangle} destinationFrame - The destination frame. + * @param {Rectangle} sourceFrame - The source frame. + */ + calculateProjection(destinationFrame, sourceFrame, resolution, root) + { + const pm = this.projectionMatrix; + + // I don't think we will need this line.. + // pm.identity(); + + if (!root) + { + pm.a = (1 / destinationFrame.width * 2) * resolution; + pm.d = (1 / destinationFrame.height * 2) * resolution; + + pm.tx = -1 - (sourceFrame.x * pm.a); + pm.ty = -1 - (sourceFrame.y * pm.d); + } + else + { + pm.a = (1 / destinationFrame.width * 2) * resolution; + pm.d = (-1 / destinationFrame.height * 2) * resolution; + + pm.tx = -1 - (sourceFrame.x * pm.a); + pm.ty = 1 - (sourceFrame.y * pm.d); + } + } + + /** + * Sets the transform of the active render target to the given matrix + * + * @param {PIXI.Matrix} matrix - The transformation matrix + */ + setTransform()// matrix) + { + // this._activeRenderTarget.transform = matrix; + } +} diff --git a/packages/core/src/renderTexture/BaseRenderTexture.js b/packages/core/src/renderTexture/BaseRenderTexture.js new file mode 100644 index 0000000..88eb83b --- /dev/null +++ b/packages/core/src/renderTexture/BaseRenderTexture.js @@ -0,0 +1,137 @@ +import BaseTexture from '../textures/BaseTexture'; +import FrameBuffer from '../framebuffer/FrameBuffer'; + +/** + * A BaseRenderTexture is a special texture that allows any PixiJS display object to be rendered to it. + * + * __Hint__: All DisplayObjects (i.e. Sprites) that render to a BaseRenderTexture should be preloaded + * otherwise black rectangles will be drawn instead. + * + * A BaseRenderTexture takes a snapshot of any Display Object given to its render method. The position + * and rotation of the given Display Objects is ignored. For example: + * + * ```js + * let renderer = PIXI.autoDetectRenderer(1024, 1024, { view: canvas, ratio: 1 }); + * let baseRenderTexture = new PIXI.BaseRenderTexture(renderer, 800, 600); + * let sprite = PIXI.Sprite.fromImage("spinObj_01.png"); + * + * sprite.position.x = 800/2; + * sprite.position.y = 600/2; + * sprite.anchor.x = 0.5; + * sprite.anchor.y = 0.5; + * + * baseRenderTexture.render(sprite); + * ``` + * + * The Sprite in this case will be rendered using its local transform. To render this sprite at 0,0 + * you can clear the transform + * + * ```js + * + * sprite.setTransform() + * + * let baseRenderTexture = new PIXI.BaseRenderTexture(100, 100); + * let renderTexture = new PIXI.RenderTexture(baseRenderTexture); + * + * renderer.render(sprite, renderTexture); // Renders to center of RenderTexture + * ``` + * + * @class + * @extends PIXI.BaseTexture + * @memberof PIXI + */ +export default class BaseRenderTexture extends BaseTexture +{ + /** + * @param {object} [options] + * @param {number} [options.width=100] - The width of the base render texture + * @param {number} [options.height=100] - The height of the base render texture + * @param {PIXI.SCALE_MODES} [options.scaleMode] - See {@link PIXI.SCALE_MODES} for possible values + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the texture being generated + */ + constructor(options) + { + if (typeof options === 'number') + { + /* eslint-disable prefer-rest-params */ + // Backward compatibility of signature + const width = arguments[0]; + const height = arguments[1]; + const scaleMode = arguments[2]; + const resolution = arguments[3]; + + options = { width, height, scaleMode, resolution }; + /* eslint-enable prefer-rest-params */ + } + + super(null, options); + + const { width, height } = options || {}; + + // Set defaults + this.mipmap = false; + this.width = Math.ceil(width) || 100; + this.height = Math.ceil(height) || 100; + this.valid = true; + + /** + * A map of renderer IDs to webgl renderTargets + * + * @private + * @member {object} + */ + // this._glRenderTargets = {}; + + /** + * A reference to the canvas render target (we only need one as this can be shared across renderers) + * + * @private + * @member {object} + */ + this._canvasRenderTarget = null; + + this.clearColor = [0, 0, 0, 0]; + + this.frameBuffer = new FrameBuffer(width * this.resolution, height * this.resolution) + .addColorTexture(0, this); + + // TODO - could this be added the systems? + + /** + * The data structure for the stencil masks + * + * @member {PIXI.Graphics[]} + */ + this.stencilMaskStack = []; + + /** + * The data structure for the filters + * + * @member {PIXI.Graphics[]} + */ + this.filterStack = [{}]; + } + + /** + * Resizes the BaseRenderTexture. + * + * @param {number} width - The width to resize to. + * @param {number} height - The height to resize to. + */ + resize(width, height) + { + width = Math.ceil(width); + height = Math.ceil(height); + this.frameBuffer.resize(width * this.resolution, height * this.resolution); + } + + /** + * Destroys this texture + * + */ + destroy() + { + super.destroy(true); + this.renderer = null; + } +} diff --git a/packages/core/src/renderTexture/RenderTexture.js b/packages/core/src/renderTexture/RenderTexture.js new file mode 100644 index 0000000..f1ecf1f --- /dev/null +++ b/packages/core/src/renderTexture/RenderTexture.js @@ -0,0 +1,153 @@ +import BaseRenderTexture from './BaseRenderTexture'; +import Texture from '../textures/Texture'; + +/** + * A RenderTexture is a special texture that allows any PixiJS display object to be rendered to it. + * + * __Hint__: All DisplayObjects (i.e. Sprites) that render to a RenderTexture should be preloaded + * otherwise black rectangles will be drawn instead. + * + * A RenderTexture takes a snapshot of any Display Object given to its render method. For example: + * + * ```js + * let renderer = PIXI.autoDetectRenderer(1024, 1024, { view: canvas, ratio: 1 }); + * let renderTexture = PIXI.RenderTexture.create(800, 600); + * let sprite = PIXI.Sprite.fromImage("spinObj_01.png"); + * + * sprite.position.x = 800/2; + * sprite.position.y = 600/2; + * sprite.anchor.x = 0.5; + * sprite.anchor.y = 0.5; + * + * renderer.render(sprite, renderTexture); + * ``` + * + * The Sprite in this case will be rendered using its local transform. To render this sprite at 0,0 + * you can clear the transform + * + * ```js + * + * sprite.setTransform() + * + * let renderTexture = new PIXI.RenderTexture.create(100, 100); + * + * renderer.render(sprite, renderTexture); // Renders to center of RenderTexture + * ``` + * + * @class + * @extends PIXI.Texture + * @memberof PIXI + */ +export default class RenderTexture extends Texture +{ + /** + * @param {PIXI.BaseRenderTexture} baseRenderTexture - The renderer used for this RenderTexture + * @param {PIXI.Rectangle} [frame] - The rectangle frame of the texture to show + */ + constructor(baseRenderTexture, frame) + { + // support for legacy.. + let _legacyRenderer = null; + + if (!(baseRenderTexture instanceof BaseRenderTexture)) + { + /* eslint-disable prefer-rest-params, no-console */ + const width = arguments[1]; + const height = arguments[2]; + const scaleMode = arguments[3]; + const resolution = arguments[4]; + + // we have an old render texture.. + console.warn(`Please use RenderTexture.create(${width}, ${height}) instead of the ctor directly.`); + _legacyRenderer = arguments[0]; + /* eslint-enable prefer-rest-params, no-console */ + + frame = null; + baseRenderTexture = new BaseRenderTexture({ + width, + height, + scaleMode, + resolution, + }); + } + + /** + * The base texture object that this texture uses + * + * @member {BaseTexture} + */ + super(baseRenderTexture, frame); + + this.legacyRenderer = _legacyRenderer; + + /** + * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. + * + * @member {boolean} + */ + this.valid = true; + + /** + * FilterSystem temporary storage + * @private + * @member {PIXI.Rectangle} + */ + this.filterFrame = null; + + this._updateUvs(); + } + + /** + * Resizes the RenderTexture. + * + * @param {number} width - The width to resize to. + * @param {number} height - The height to resize to. + * @param {boolean} [resizeBaseTexture=true] - Should the baseTexture.width and height values be resized as well? + */ + resize(width, height, resizeBaseTexture = true) + { + width = Math.ceil(width); + height = Math.ceil(height); + + // TODO - could be not required.. + this.valid = (width > 0 && height > 0); + + this._frame.width = this.orig.width = width; + this._frame.height = this.orig.height = height; + + if (resizeBaseTexture) + { + this.baseTexture.resize(width, height); + } + + this._updateUvs(); + } + + /** + * A short hand way of creating a render texture. + * + * @param {object} [options] - Options + * @param {number} [options.width=100] - The width of the render texture + * @param {number} [options.height=100] - The height of the render texture + * @param {number} [options.scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the texture being generated + * @return {PIXI.RenderTexture} The new render texture + */ + static create(options) + { + // fallback, old-style: create(width, height, scaleMode, resolution) + if (typeof options === 'number') + { + /* eslint-disable prefer-rest-params */ + options = { + width: options, + height: arguments[1], + scaleMode: arguments[2], + resolution: arguments[3], + }; + /* eslint-enable prefer-rest-params */ + } + + return new RenderTexture(new BaseRenderTexture(options)); + } +} diff --git a/packages/core/src/renderTexture/RenderTextureSystem.js b/packages/core/src/renderTexture/RenderTextureSystem.js new file mode 100644 index 0000000..8d0ca61 --- /dev/null +++ b/packages/core/src/renderTexture/RenderTextureSystem.js @@ -0,0 +1,115 @@ +import System from '../System'; +import { Rectangle } from '@pixi/math'; + +const tempRect = new Rectangle(); + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class RenderTextureSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.clearColor = renderer._backgroundColorRgba; + + // TODO moe this property somewhere else! + this.defaultMaskStack = []; + this.defaultFilterStack = [{}]; + + // empty render texture? + this.renderTexture = null; + + this.destinationFrame = new Rectangle(); + } + + bind(renderTexture, sourceFrame, destinationFrame) + { + // TODO - do we want this?? + if (this.renderTexture === renderTexture) return; + this.renderTexture = renderTexture; + + const renderer = this.renderer; + + if (renderTexture) + { + const baseTexture = renderTexture.baseTexture; + + if (!destinationFrame) + { + tempRect.width = baseTexture.realWidth; + tempRect.height = baseTexture.realHeight; + + destinationFrame = tempRect; + } + + if (!sourceFrame) + { + sourceFrame = destinationFrame; + } + + this.renderer.framebuffer.bind(baseTexture.frameBuffer, destinationFrame); + + this.renderer.projection.update(destinationFrame, sourceFrame, baseTexture.resolution, false); + this.renderer.stencil.setMaskStack(baseTexture.stencilMaskStack); + } + else + { + // TODO these validation checks happen deeper down.. + // thing they can be avoided.. + if (!destinationFrame) + { + tempRect.width = renderer.width; + tempRect.height = renderer.height; + + destinationFrame = tempRect; + } + + if (!sourceFrame) + { + sourceFrame = destinationFrame; + } + + renderer.framebuffer.bind(null, destinationFrame); + + // TODO store this.. + this.renderer.projection.update(destinationFrame, sourceFrame, this.renderer.resolution, true); + this.renderer.stencil.setMaskStack(this.defaultMaskStack); + } + + this.destinationFrame.copyFrom(destinationFrame); + } + + /** + * Erases the render texture and fills the drawing area with a colour + * + * @param {number} [clearColor] - The colour + * @return {PIXI.Renderer} Returns itself. + */ + clear(clearColor) + { + if (this.renderTexture) + { + clearColor = clearColor || this.renderTexture.baseTexture.clearColor; + } + else + { + clearColor = clearColor || this.clearColor; + } + + this.renderer.framebuffer.clear(clearColor[0], clearColor[1], clearColor[2], clearColor[3]); + } + + resize()// screenWidth, screenHeight) + { + // resize the root only! + this.bind(null); + } +} diff --git a/packages/core/src/renderers/AbstractRenderer.js b/packages/core/src/renderers/AbstractRenderer.js deleted file mode 100644 index 928670f..0000000 --- a/packages/core/src/renderers/AbstractRenderer.js +++ /dev/null @@ -1,353 +0,0 @@ -import { hex2string, hex2rgb } from '@pixi/utils'; -import { Matrix, Rectangle } from '@pixi/math'; -import { RENDERER_TYPE } from '@pixi/constants'; -import { settings } from '@pixi/settings'; -import { Container } from '@pixi/display'; -import RenderTexture from '../textures/RenderTexture'; -import EventEmitter from 'eventemitter3'; - -const tempMatrix = new Matrix(); - -/** - * 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 EventEmitter - * @memberof PIXI - */ -export default class AbstractRenderer extends EventEmitter -{ - // eslint-disable-next-line valid-jsdoc - /** - * @param {string} system - The name of the system this renderer is for. - * @param {object} [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.transparent=false] - If the render view is transparent, default false - * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false - * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) - * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. The - * resolution of the renderer retina would be 2. - * @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 {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when rendering, - * stopping pixel interpolation. - */ - constructor(system, options, arg2, arg3) - { - super(); - - // Support for constructor(system, screenWidth, screenHeight, options) - if (typeof options === 'number') - { - options = Object.assign({ - width: options, - height: arg2 || settings.RENDER_OPTIONS.height, - }, arg3); - } - - // 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 = RENDERER_TYPE.UNKNOWN; - - /** - * Measurements of the screen. (0, 0, screenWidth, screenHeight) - * - * Its safe to use as filterArea or hitArea for 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 1 - */ - this.resolution = options.resolution || settings.RESOLUTION; - - /** - * Whether the render view is transparent - * - * @member {boolean} - */ - this.transparent = options.transparent; - - /** - * Whether css dimensions of canvas view should be resized to screen dimensions automatically - * - * @member {boolean} - */ - this.autoResize = options.autoResize || false; - - /** - * Tracks the blend modes useful for this renderer. - * - * @member {object} - */ - this.blendModes = null; - - /** - * 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; - - /** - * If true PixiJS will Math.floor() x/y values when rendering, stopping pixel interpolation. - * Handy for crisp pixel art and speed on legacy devices. - * - * @member {boolean} - */ - this.roundPixels = options.roundPixels; - - /** - * The background color as a number. - * - * @member {number} - * @private - */ - this._backgroundColor = 0x000000; - - /** - * The background color as an [R, G, B] array. - * - * @member {number[]} - * @private - */ - this._backgroundColorRgba = [0, 0, 0, 0]; - - /** - * The background color as a string. - * - * @member {string} - * @private - */ - this._backgroundColorString = '#000000'; - - this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter - - /** - * This temporary display object used as the parent of the currently being rendered item - * - * @member {PIXI.DisplayObject} - * @private - */ - this._tempDisplayObjectParent = new Container(); - - /** - * The last root object that the renderer tried to render. - * - * @member {PIXI.DisplayObject} - * @private - */ - this._lastObjectRendered = this._tempDisplayObjectParent; - - /** - * Collection of plugins. - * @readonly - * @member {object} - */ - this.plugins = {}; - } - - /** - * Initialize the plugins. - * - * @protected - * @param {object} staticMap - The dictionary of staticly saved plugins. - */ - initPlugins(staticMap) - { - 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() - { - return this.view.width; - } - - /** - * Same as view.height, actual number of pixels in the canvas by vertical - * - * @member {number} - * @readonly - * @default 600 - */ - get height() - { - return this.view.height; - } - - /** - * Resizes the screen and canvas to the specified width and height - * Canvas dimensions are multiplied by resolution - * - * @param {number} screenWidth - the new width of the screen - * @param {number} screenHeight - the new height of the screen - */ - resize(screenWidth, screenHeight) - { - this.screen.width = screenWidth; - this.screen.height = screenHeight; - - this.view.width = screenWidth * this.resolution; - this.view.height = screenHeight * this.resolution; - - if (this.autoResize) - { - this.view.style.width = `${screenWidth}px`; - this.view.style.height = `${screenHeight}px`; - } - } - - /** - * 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 {PIXI.DisplayObject} displayObject - The displayObject the object will be generated from - * @param {number} scaleMode - Should be one of the scaleMode consts - * @param {number} resolution - The resolution / device pixel ratio of the texture being generated - * @param {PIXI.Rectangle} [region] - The region of the displayObject, that shall be rendered, - * if no region is specified, defaults to the local bounds of the displayObject. - * @return {PIXI.Texture} a texture of the graphics object - */ - generateTexture(displayObject, scaleMode, resolution, region) - { - region = region || displayObject.getLocalBounds(); - - // 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(region.width | 0, region.height | 0, scaleMode, resolution); - - tempMatrix.tx = -region.x; - tempMatrix.ty = -region.y; - - this.render(displayObject, renderTexture, false, tempMatrix, true); - - return renderTexture; - } - - /** - * Removes everything from the renderer and optionally removes the Canvas DOM element. - * - * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. - */ - destroy(removeView) - { - 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); - } - - this.plugins = null; - - this.type = RENDERER_TYPE.UNKNOWN; - - this.view = null; - - this.screen = null; - - this.resolution = 0; - - this.transparent = false; - - this.autoResize = false; - - this.blendModes = null; - - this.options = null; - - this.preserveDrawingBuffer = false; - this.clearBeforeRender = false; - - this.roundPixels = false; - - this._backgroundColor = 0; - this._backgroundColorRgba = null; - this._backgroundColorString = null; - - this._tempDisplayObjectParent = null; - this._lastObjectRendered = null; - } - - /** - * The background color to fill if not transparent - * - * @member {number} - */ - get backgroundColor() - { - return this._backgroundColor; - } - - set backgroundColor(value) // eslint-disable-line require-jsdoc - { - this._backgroundColor = value; - this._backgroundColorString = hex2string(value); - hex2rgb(value, this._backgroundColorRgba); - } -} diff --git a/packages/core/src/renderers/Renderer.js b/packages/core/src/renderers/Renderer.js deleted file mode 100644 index a2020b1..0000000 --- a/packages/core/src/renderers/Renderer.js +++ /dev/null @@ -1,329 +0,0 @@ -import AbstractRenderer from './AbstractRenderer'; -import { sayHello } from '@pixi/utils'; -import MaskSystem from './systems/MaskSystem'; -import StencilSystem from './systems/StencilSystem'; -import FilterSystem from './systems/filter/FilterSystem'; -import FramebufferSystem from './systems/FramebufferSystem'; -import RenderTextureSystem from './systems/RenderTextureSystem'; -import TextureSystem from './systems/textures/TextureSystem'; -import ProjectionSystem from './systems/ProjectionSystem'; -import StateSystem from './systems/StateSystem'; -import GeometrySystem from './systems/geometry/GeometrySystem'; -import ShaderSystem from './systems/shader/ShaderSystem'; -import ContextSystem from './systems/ContextSystem'; -import BatchSystem from './systems/BatchSystem'; -import TextureGCSystem from './systems/textures/TextureGCSystem'; -import { RENDERER_TYPE } from '@pixi/constants'; -import UniformGroup from '../shader/UniformGroup'; -import { Matrix } from '@pixi/math'; -import Runner from 'mini-runner'; - -/** - * 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 Render works by automatically managing webGLBatchs. - * 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 :) - * - * @class - * @memberof PIXI - * @extends PIXI.AbstractRenderer - */ -export default class Renderer extends AbstractRenderer -{ - // eslint-disable-next-line valid-jsdoc - /** - * - * @param {object} [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.transparent=false] - If the render view is transparent, default false - * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false - * @param {boolean} [options.antialias=false] - sets antialias. If not available natively then FXAA - * antialiasing is used - * @param {boolean} [options.forceFXAA=false] - forces FXAA antialiasing to be used over native. - * FXAA is faster, but may not always look as great - * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. - * The resolution of the renderer retina would be 2. - * @param {boolean} [options.clearBeforeRender=true] - This sets if the renderer will clear - * the canvas or not before the new render pass. If you wish to set this to false, you *must* set - * preserveDrawingBuffer to `true`. - * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, - * enable this if you need to call toDataUrl on the webgl context. - * @param {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when - * rendering, stopping pixel interpolation. - * @param {number} [options.backgroundColor=0x000000] - The background color of the rendered area - * (shown if not transparent). - * @param {boolean} [options.legacy=false] - If true PixiJS will aim to ensure compatibility - * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. - * @param {string} [options.powerPreference] - Parameter passed to webgl context, set to "high-performance" - * for devices with dual graphics card - */ - constructor(options, arg2, arg3) - { - super('WebGL', options, arg2, arg3); - - /** - * The type of this renderer as a standardised const - * - * @member {number} - * @see PIXI.RENDERER_TYPE - */ - this.type = RENDERER_TYPE.WEBGL; - - // this will be set by the contextSystem (this.context) - this.gl = null; - this.CONTEXT_UID = 0; - this.legacy = !!options.legacy; - - // TODO legacy! - - // runners! - this.runners = { - destroy: new Runner('destroy'), - contextChange: new Runner('contextChange', 1), - reset: new Runner('reset'), - update: new Runner('update'), - postrender: new Runner('postrender'), - prerender: new Runner('prerender'), - resize: new Runner('resize', 2), - }; - - 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(GeometrySystem, 'geometry') - .addSystem(FramebufferSystem, 'framebuffer') - .addSystem(StencilSystem, 'stencil') - .addSystem(ProjectionSystem, 'projection') - .addSystem(TextureGCSystem, 'textureGC') - .addSystem(FilterSystem, 'filter') - .addSystem(RenderTextureSystem, 'renderTexture') - .addSystem(BatchSystem, 'batch'); - - this.initPlugins(Renderer.__plugins); - - /** - * The options passed in to create a new webgl context. - * - * @member {object} - * @private - */ - if (options.context) - { - this.context.initFromContext(options.context); - } - else - { - this.context.initFromOptions({ - alpha: this.transparent, - antialias: options.antialias, - premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', - stencil: true, - 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); - } - - addSystem(_class, name) - { - if (!name) - { - name = _class.name; - } - - // TODO - read name from class.name.. - - /* - if(name.includes('System')) - { - name = name.replace('System', ''); - name = name.charAt(0).toLowerCase() + name.slice(1); - } - */ - - const system = new _class(this); - - if (this[name]) - { - throw new Error(`Whoops! ${name} is already a manger`); - } - - this[name] = system; - - for (const i in this.runners) - { - this.runners[i].add(system); - } - - return this; - - /** - * 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. - */ - } - - /** - * Renders the object to its webGL view - * - * @param {PIXI.DisplayObject} displayObject - the object to be rendered - * @param {PIXI.RenderTexture} renderTexture - The render texture to render to. - * @param {boolean} [clear] - Should the canvas be cleared before the new render - * @param {PIXI.Transform} [transform] - A transform to apply to the render texture before rendering. - * @param {boolean} [skipUpdateTransform] - Should we skip the update transform pass? - */ - render(displayObject, renderTexture, clear, transform, skipUpdateTransform) - { - // can be handy to know! - this.renderingToScreen = !renderTexture; - - this.runners.prerender.run(); - this.emit('prerender'); - - // 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.parent; - - displayObject.parent = this._tempDisplayObjectParent; - displayObject.updateTransform(); - displayObject.parent = 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.run(); - - this.emit('postrender'); - } - - /** - * Resizes the webGL view to the specified width and height. - * - * @param {number} screenWidth - the new width of the screen - * @param {number} screenHeight - the new height of the screen - */ - resize(screenWidth, screenHeight) - { - AbstractRenderer.prototype.resize.call(this, screenWidth, screenHeight); - - this.runners.resize.run(screenWidth, screenHeight); - } - - /** - * Resets the WebGL state so you can render things however you fancy! - * - * @return {PIXI.Renderer} Returns itself. - */ - reset() - { - this.runners.reset.run(); - - return this; - } - - clear() - { - this.framebuffer.bind(); - this.framebuffer.clear(); - } - - /** - * Removes everything from the renderer (event listeners, spritebatch, etc...) - * - * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. - * See: https://github.com/pixijs/pixi.js/issues/2233 - */ - destroy(removeView) - { - this.runners.destroy.run(); - - // call base destroy - super.destroy(removeView); - - // TODO nullify all the managers.. - this.gl = null; - } - - /** - * 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 - * builds and excluding plugins. - * @name PIXI.Renderer#plugins - * @type {object} - * @readonly - * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. - * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. - * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. - * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. - */ - - /** - * Adds a plugin to the renderer. - * - * @method - * @param {string} pluginName - The name of the plugin. - * @param {Function} ctor - The constructor function or class for the plugin. - */ - static registerPlugin(pluginName, ctor) - { - Renderer.__plugins = Renderer.__plugins || {}; - Renderer.__plugins[pluginName] = ctor; - } -} diff --git a/packages/core/src/renderers/State.js b/packages/core/src/renderers/State.js deleted file mode 100644 index fa3f221..0000000 --- a/packages/core/src/renderers/State.js +++ /dev/null @@ -1,163 +0,0 @@ -/* eslint-disable max-len */ - -const BLEND = 0; -const OFFSET = 1; -const CULLING = 2; -const DEPTH_TEST = 3; -const WINDING = 4; - -/** - * This is a webGL state. It is passed The WebGL StateManager. - * Each mesh renderered may require webGL to be in a different state. - * For example you may want different blend mode or to enable polygon offsets - * - * @class - * @memberof PIXI - */ -export default class State -{ - /** - * - */ - constructor() - { - this.data = 0; - - this.blendMode = 0; - this.polygonOffset = 0; - - this.blend = true; - // this.depthTest = true; - } - - /** - * Activates blending of the computed fragment color values - * - * @member {boolean} - */ - get blend() - { - return !!(this.data & (1 << BLEND)); - } - - set blend(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << BLEND)) !== value) - { - this.data ^= (1 << BLEND); - } - } - - /** - * Activates adding an offset to depth values of polygon's fragments - * - * @member {boolean} - * @default false - */ - get offsets() - { - return !!(this.data & (1 << OFFSET)); - } - - set offsets(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << OFFSET)) !== value) - { - this.data ^= (1 << OFFSET); - } - } - - /** - * Activates culling of polygons. - * - * @member {boolean} - * @default false - */ - get culling() - { - return !!(this.data & (1 << CULLING)); - } - - set culling(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << CULLING)) !== value) - { - this.data ^= (1 << CULLING); - } - } - - /** - * Activates depth comparisons and updates to the depth buffer. - * - * @member {boolean} - * @default false - */ - get depthTest() - { - return !!(this.data & (1 << DEPTH_TEST)); - } - - set depthTest(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << DEPTH_TEST)) !== value) - { - this.data ^= (1 << DEPTH_TEST); - } - } - - /** - * Specifies whether or not front or back-facing polygons can be culled. - * @member {boolean} - * @default false - */ - get clockwiseFrontFace() - { - return !!(this.data & (1 << WINDING)); - } - - set clockwiseFrontFace(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << WINDING)) !== value) - { - this.data ^= (1 << WINDING); - } - } - - /** - * The blend mode to be applied when this state is set. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. - * Setting this mode to anything other than NO_BLEND will automatically switch blending on. - * - * @member {boolean} - * @default PIXI.BLEND_MODES.NORMAL - * @see PIXI.BLEND_MODES - */ - get blendMode() - { - return this._blendMode; - } - - set blendMode(value) // eslint-disable-line require-jsdoc - { - // 17 is NO BLEND - this.blend = (value !== 17); - this._blendMode = value; - } - - /** - * The polygon offset. Setting this property to anything other than 0 will automatically enable poygon offset fill. - * - * @member {number} - * @default 0 - */ - get polygonOffset() - { - return this._polygonOffset; - } - - set polygonOffset(value) // eslint-disable-line require-jsdoc - { - this.offsets = !!value; - this._polygonOffset = value; - } -} - diff --git a/packages/core/src/renderers/filters/Filter.js b/packages/core/src/renderers/filters/Filter.js deleted file mode 100644 index 2073b8d..0000000 --- a/packages/core/src/renderers/filters/Filter.js +++ /dev/null @@ -1,140 +0,0 @@ -import Shader from '../../shader/Shader'; -import Program from '../../shader/Program'; -import State from '../State'; -import { settings } from '@pixi/settings'; -// import extractUniformsFromSrc from './extractUniformsFromSrc'; -import defaultVertex from './defaultFilter.vert'; -import defaultFragment from './defaultFilter.frag'; - -// let math = require('../../math'); -/** - * @class - * @memberof PIXI - * @extends PIXI.Shader - */ -export default class Filter extends Shader -{ - /** - * @param {string} [vertexSrc] - The source of the vertex shader. - * @param {string} [fragmentSrc] - The source of the fragment shader. - * @param {object} [uniforms] - Custom uniforms to use to augment the built-in ones. - */ - constructor(vertexSrc, fragmentSrc, uniforms) - { - const program = Program.from(vertexSrc, fragmentSrc); - - super(program, uniforms); - - /** - * The padding of the filter. Some filters require extra space to breath such as a blur. - * Increasing this will add extra width and height to the bounds of the object that the - * filter is applied to. - * - * @member {number} - */ - this.padding = 0; - - /** - * The resolution of the filter. Setting this to be lower will lower the quality but - * increase the performance of the filter. - * - * @member {number} - */ - this.resolution = settings.RESOLUTION; - - /** - * If enabled is true the filter is applied, if false it will not. - * - * @member {boolean} - */ - this.enabled = true; - - /** - * If enabled, PixiJS will fit the filter area into boundaries for better performance. - * Switch it off if it does not work for specific shader. - * - * @member {boolean} - */ - this.autoFit = true; - - /** - * Legacy filters use position and uvs from attributes - * @member {boolean} - * @readonly - */ - this.legacy = !!this.program.attributeData.aTextureCoord; - - /** - * the webGL state the filter requires to render - * @member {PIXI.State} - */ - this.state = new State(); - } - - /** - * Applies the filter - * - * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from - * @param {PIXI.RenderTarget} input - The input render target. - * @param {PIXI.RenderTarget} output - The target to output to. - * @param {boolean} clear - Should the output be cleared before rendering to it - * @param {object} [currentState] - It's current state of filter. - * There are some useful properties in the currentState : - * target, filters, sourceFrame, destinationFrame, renderTarget, resolution - */ - apply(filterManager, input, output, clear, currentState, derp) // eslint-disable-line no-unused-vars - { - // do as you please! - - filterManager.applyFilter(this, input, output, clear, currentState, derp); - - // or just do a regular render.. - } - - /** - * Sets the blendmode of the filter - * - * @member {number} - * @default PIXI.BLEND_MODES.NORMAL - */ - get blendMode() - { - return this.state.blendMode; - } - - set blendMode(value) // eslint-disable-line require-jsdoc - { - this.state.blendMode = value; - } - - /** - * The default vertex shader source - * - * @static - * @constant - */ - static get defaultVertexSrc() - { - return defaultVertex; - } - - /** - * The default fragment shader source - * - * @static - * @constant - */ - static get defaultFragmentSrc() - { - return defaultFragment; - } -} - -/** - * Used for caching shader IDs - * - * @static - * @private - */ -Filter.SOURCE_KEY_MAP = {}; - diff --git a/packages/core/src/renderers/filters/defaultFilter.frag b/packages/core/src/renderers/filters/defaultFilter.frag deleted file mode 100644 index a14c1be..0000000 --- a/packages/core/src/renderers/filters/defaultFilter.frag +++ /dev/null @@ -1,22 +0,0 @@ -varying vec2 vTextureCoord; -varying vec2 vFilterCoord; - -uniform sampler2D uSampler; -uniform sampler2D filterSampler; - -void main(void){ - vec4 masky = texture2D(filterSampler, vFilterCoord); - vec4 sample = texture2D(uSampler, vTextureCoord); - vec4 color; - if(mod(vFilterCoord.x, 1.0) > 0.5) - { - color = vec4(1.0, 0.0, 0.0, 1.0); - } - else - { - color = vec4(0.0, 1.0, 0.0, 1.0); - } - // gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0); - gl_FragColor = mix(sample, masky, 0.5); - gl_FragColor *= sample.a; -} diff --git a/packages/core/src/renderers/filters/defaultFilter.vert b/packages/core/src/renderers/filters/defaultFilter.vert deleted file mode 100644 index e1febca..0000000 --- a/packages/core/src/renderers/filters/defaultFilter.vert +++ /dev/null @@ -1,14 +0,0 @@ -attribute vec2 aVertexPosition; -attribute vec2 aTextureCoord; - -uniform mat3 projectionMatrix; -uniform mat3 filterMatrix; - -varying vec2 vTextureCoord; -varying vec2 vFilterCoord; - -void main(void){ - gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); - vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy; - vTextureCoord = aTextureCoord ; -} diff --git a/packages/core/src/renderers/filters/filterTransforms.js b/packages/core/src/renderers/filters/filterTransforms.js deleted file mode 100644 index 87a3cd6..0000000 --- a/packages/core/src/renderers/filters/filterTransforms.js +++ /dev/null @@ -1,50 +0,0 @@ -import { Matrix } from '@pixi/math'; - -/** - * Calculates the mapped matrix - * @param filterArea {Rectangle} The filter area - * @param sprite {Sprite} the target sprite - * @param outputMatrix {Matrix} @alvin - * @private - */ -// this returns a matrix that will normalise map filter cords in the filter to screen space -export function calculateScreenSpaceMatrix(outputMatrix, filterArea, textureSize) -{ - // TODO unwrap? - const mappedMatrix = outputMatrix.identity(); - - mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); - - mappedMatrix.scale(textureSize.width, textureSize.height); - - return mappedMatrix; -} - -export function calculateNormalizedScreenSpaceMatrix(outputMatrix, filterArea, textureSize) -{ - const mappedMatrix = outputMatrix.identity(); - - mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); - - const translateScaleX = (textureSize.width / filterArea.width); - const translateScaleY = (textureSize.height / filterArea.height); - - mappedMatrix.scale(translateScaleX, translateScaleY); - - return mappedMatrix; -} - -// this will map the filter coord so that a texture can be used based on the transform of a sprite -export function calculateSpriteMatrix(outputMatrix, filterArea, textureSize, sprite) -{ - const orig = sprite._texture.orig; - const mappedMatrix = outputMatrix.set(textureSize.width, 0, 0, textureSize.height, filterArea.x, filterArea.y); - const worldTransform = sprite.worldTransform.copyTo(Matrix.TEMP_MATRIX); - - worldTransform.invert(); - mappedMatrix.prepend(worldTransform); - mappedMatrix.scale(1.0 / orig.width, 1.0 / orig.height); - mappedMatrix.translate(sprite.anchor.x, sprite.anchor.y); - - return mappedMatrix; -} diff --git a/packages/core/src/renderers/filters/spriteMask/SpriteMaskFilter.js b/packages/core/src/renderers/filters/spriteMask/SpriteMaskFilter.js deleted file mode 100644 index d5a91a4..0000000 --- a/packages/core/src/renderers/filters/spriteMask/SpriteMaskFilter.js +++ /dev/null @@ -1,63 +0,0 @@ -import Filter from '../Filter'; -import { Matrix } from '@pixi/math'; -import vertex from './spriteMaskFilter.vert'; -import fragment from './spriteMaskFilter.frag'; -import { default as TextureMatrix } from '../../../textures/TextureMatrix'; - -/** - * The SpriteMaskFilter class - * - * @class - * @extends PIXI.Filter - * @memberof PIXI - */ -export default class SpriteMaskFilter extends Filter -{ - /** - * @param {PIXI.Sprite} sprite - the target sprite - */ - constructor(sprite) - { - const maskMatrix = new Matrix(); - - super(vertex, fragment); - - sprite.renderable = false; - - this.maskSprite = sprite; - this.maskMatrix = maskMatrix; - } - - /** - * Applies the filter - * - * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from - * @param {PIXI.RenderTarget} input - The input render target. - * @param {PIXI.RenderTarget} output - The target to output to. - */ - apply(filterManager, input, output) - { - const maskSprite = this.maskSprite; - const tex = this.maskSprite.texture; - - if (!tex.valid) - { - return; - } - if (!tex.transform) - { - // margin = 0.0, let it bleed a bit, shader code becomes easier - // assuming that atlas textures were made with 1-pixel padding - tex.transform = new TextureMatrix(tex, 0.0); - } - tex.transform.update(); - - this.uniforms.mask = tex; - this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite) - .prepend(tex.transform.mapCoord); - this.uniforms.alpha = maskSprite.worldAlpha; - this.uniforms.maskClamp = tex.transform.uClampFrame; - - filterManager.applyFilter(this, input, output); - } -} diff --git a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.frag b/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.frag deleted file mode 100644 index 0e4aef8..0000000 --- a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.frag +++ /dev/null @@ -1,23 +0,0 @@ -varying vec2 vMaskCoord; -varying vec2 vTextureCoord; - -uniform sampler2D uSampler; -uniform sampler2D mask; -uniform float alpha; -uniform vec4 maskClamp; - -void main(void) -{ - float clip = step(3.5, - step(maskClamp.x, vMaskCoord.x) + - step(maskClamp.y, vMaskCoord.y) + - step(vMaskCoord.x, maskClamp.z) + - step(vMaskCoord.y, maskClamp.w)); - - vec4 original = texture2D(uSampler, vTextureCoord); - vec4 masky = texture2D(mask, vMaskCoord); - - original *= (masky.r * masky.a * alpha * clip); - - gl_FragColor = original; -} diff --git a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.vert b/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.vert deleted file mode 100644 index d23a30c..0000000 --- a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.vert +++ /dev/null @@ -1,16 +0,0 @@ -attribute vec2 aVertexPosition; -attribute vec2 aTextureCoord; - -uniform mat3 projectionMatrix; -uniform mat3 otherMatrix; - -varying vec2 vMaskCoord; -varying vec2 vTextureCoord; - -void main(void) -{ - gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); - - vTextureCoord = aTextureCoord; - vMaskCoord = ( otherMatrix * vec3( aTextureCoord, 1.0) ).xy; -} diff --git a/packages/core/src/renderers/systems/BatchSystem.js b/packages/core/src/renderers/systems/BatchSystem.js deleted file mode 100644 index 70e9518..0000000 --- a/packages/core/src/renderers/systems/BatchSystem.js +++ /dev/null @@ -1,68 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import ObjectRenderer from '../utils/ObjectRenderer'; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ - -export default class BatchSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this manager works for. - */ - constructor(renderer) - { - super(renderer); - - /** - * An empty renderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.emptyRenderer = new ObjectRenderer(renderer); - - /** - * The currently active ObjectRenderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.currentRenderer = this.emptyRenderer; - } - - /** - * Changes the current renderer to the one given in parameter - * - * @param {PIXI.ObjectRenderer} objectRenderer - The object renderer to use. - */ - setObjectRenderer(objectRenderer) - { - if (this.currentRenderer === objectRenderer) - { - return; - } - - this.currentRenderer.stop(); - this.currentRenderer = objectRenderer; - - this.renderer.state.setState(objectRenderer.state); - - this.currentRenderer.start(); - } - - /** - * This should be called if you wish to do some custom rendering - * It will basically render anything that may be batched up such as sprites - * - */ - flush() - { - this.setObjectRenderer(this.emptyRenderer); - } - - reset() - { - this.setObjectRenderer(this.emptyRenderer); - } -} diff --git a/packages/core/src/renderers/systems/ContextSystem.js b/packages/core/src/renderers/systems/ContextSystem.js deleted file mode 100644 index 43bf827..0000000 --- a/packages/core/src/renderers/systems/ContextSystem.js +++ /dev/null @@ -1,186 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { settings } from '@pixi/settings'; - -let CONTEXT_UID = 0; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class ContextSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this.webGLVersion = 1; - - this.handleContextLost = this.handleContextLost.bind(this); - this.handleContextRestored = this.handleContextRestored.bind(this); - - this.extensions = {}; - - renderer.view.addEventListener('webglcontextlost', this.handleContextLost, false); - renderer.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); - } - - get isLost() - { - return (!this.gl || this.gl.isContextLost()); - } - - contextChange(gl) - { - this.gl = gl; - - // restore a context if it was previously lost - if (gl.isContextLost() && gl.getExtension('WEBGL_lose_context')) - { - gl.getExtension('WEBGL_lose_context').restoreContext(); - } - } - - initFromContext(gl) - { - this.gl = gl; - this.validateContext(gl); - this.renderer.gl = gl; - this.renderer.CONTEXT_UID = CONTEXT_UID++; - this.renderer.runners.contextChange.run(gl); - } - - initFromOptions(options) - { - const gl = this.createContext(this.renderer.view, options); - - this.initFromContext(gl); - } - - /** - * Helper class to create a webGL Context - * - * @class - * @memberof PIXI.glCore - * @param canvas {HTMLCanvasElement} the canvas element that we will get the context from - * @param options {Object} An options object that gets passed in to the canvas element containing the context attributes, - * see https://developer.mozilla.org/en/docs/Web/API/HTMLCanvasElement/getContext for the options available - * @return {WebGLRenderingContext} the WebGL context - */ - createContext(canvas, options) - { - let gl; - - if (settings.PREFER_WEBGL_2) - { - gl = canvas.getContext('webgl2', options); - } - - if (gl) - { - this.webGLVersion = 2; - } - else - { - this.webGLVersion = 1; - - gl = canvas.getContext('webgl', options) - || canvas.getContext('experimental-webgl', options); - - if (!gl) - { - // fail, not able to get a context - throw new Error('This browser does not support webGL. Try using the canvas renderer'); - } - } - - this.gl = gl; - - this.getExtensions(); - - return gl; - } - - getExtensions() - { - // time to set up default etensions that pixi uses.. - const gl = this.gl; - const extensions = this.extensions; - - if (this.webGLVersion === 1) - { - extensions.drawBuffers = gl.getExtension('WEBGL_draw_buffers'); - extensions.depthTexture = gl.getExtension('WEBKIT_WEBGL_depth_texture'); - extensions.floatTexture = gl.getExtension('OES_texture_float'); - extensions.loseContext = gl.getExtension('WEBGL_lose_context'); - - extensions.vertexArrayObject = gl.getExtension('OES_vertex_array_object') - || gl.getExtension('MOZ_OES_vertex_array_object') - || gl.getExtension('WEBKIT_OES_vertex_array_object'); - } - - // we don't use any specific WebGL 2 ones yet! - } - - /** - * Handles a lost webgl context - * - * @private - * @param {WebGLContextEvent} event - The context lost event. - */ - handleContextLost(event) - { - event.preventDefault(); - } - - /** - * Handles a restored webgl context - * - * @private - */ - handleContextRestored() - { - this.renderer.runners.contextChange.run(this.gl); - } - - destroy() - { - const view = this.renderer.view; - - // remove listeners - view.removeEventListener('webglcontextlost', this.handleContextLost); - view.removeEventListener('webglcontextrestored', this.handleContextRestored); - - this.gl.useProgram(null); - - if (this.extensions.loseContext) - { - this.extensions.loseContext.loseContext(); - } - } - - postrender() - { - this.gl.flush(); - } - - validateContext(gl) - { - const attributes = gl.getContextAttributes(); - - // this is going to be fairly simple for now.. but at least we have room to grow! - if (!attributes.stencil) - { - /* eslint-disable max-len */ - - /* eslint-disable no-console */ - console.warn('Provided WebGL context does not have a stencil buffer, masks may not render correctly'); - /* eslint-enable no-console */ - - /* eslint-enable max-len */ - } - } -} diff --git a/packages/core/src/renderers/systems/FramebufferSystem.js b/packages/core/src/renderers/systems/FramebufferSystem.js deleted file mode 100644 index df7902b..0000000 --- a/packages/core/src/renderers/systems/FramebufferSystem.js +++ /dev/null @@ -1,246 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { Rectangle } from '@pixi/math'; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class FramebufferSystem extends WebGLSystem -{ - /** - * Sets up the renderer context and necessary buffers. - * - * @private - */ - contextChange() - { - this.gl = this.renderer.gl; - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - this.current = null; - this.viewport = new Rectangle(); - - this.drawBufferExtension = this.renderer.context.extensions.drawBuffers; - } - - bind(framebuffer, frame) - { - const gl = this.gl; - - this.current = framebuffer; - - if (framebuffer) - { - // TODO cacheing layer! - - const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID] || this.initFramebuffer(framebuffer); - - gl.bindFramebuffer(gl.FRAMEBUFFER, fbo.framebuffer); - // makesure all textures are unbound.. - - // now check for updates... - if (fbo.dirtyId !== framebuffer.dirtyId) - { - fbo.dirtyId = framebuffer.dirtyId; - - if (fbo.dirtyFormat !== framebuffer.dirtyFormat) - { - fbo.dirtyFormat = framebuffer.dirtyFormat; - this.updateFramebuffer(framebuffer); - } - else if (fbo.dirtySize !== framebuffer.dirtySize) - { - fbo.dirtySize = framebuffer.dirtySize; - this.resizeFramebuffer(framebuffer); - } - } - - for (let i = 0; i < framebuffer.colorTextures.length; i++) - { - if (framebuffer.colorTextures[i].texturePart) - { - this.renderer.texture.unbind(framebuffer.colorTextures[i].texture); - } - else - { - this.renderer.texture.unbind(framebuffer.colorTextures[i]); - } - } - - if (framebuffer.depthTexture) - { - this.renderer.texture.unbind(framebuffer.depthTexture); - } - - if (frame) - { - this.setViewport(frame.x, frame.y, frame.width, frame.height); - } - else - { - this.setViewport(0, 0, framebuffer.width, framebuffer.height); - } - } - else - { - gl.bindFramebuffer(gl.FRAMEBUFFER, null); - - if (frame) - { - this.setViewport(frame.x, frame.y, frame.width, frame.height); - } - else - { - this.setViewport(0, 0, this.renderer.width, this.renderer.height); - } - } - } - - setViewport(x, y, width, height) - { - const v = this.viewport; - - if (v.width !== width || v.height !== height || v.x !== x || v.y !== y) - { - v.x = x; - v.y = y; - v.width = width; - v.height = height; - - this.gl.viewport(x, y, width, height); - } - } - - get size() - { - if (this.current) - { - // TODO store temp - return { x: 0, y: 0, width: this.current.width, height: this.current.height }; - } - - return { x: 0, y: 0, width: this.renderer.width, height: this.renderer.height }; - } - - clear(r, g, b, a) - { - const gl = this.gl; - - // TODO clear color can be set only one right? - gl.clearColor(r, g, b, a); - gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); - } - - // private functions... - - initFramebuffer(framebuffer) - { - const gl = this.gl; - - // TODO - make this a class? - const fbo = { - framebuffer: gl.createFramebuffer(), - stencil: null, - dirtyId: 0, - dirtyFormat: 0, - dirtySize: 0, - }; - - framebuffer.glFrameBuffers[this.CONTEXT_UID] = fbo; - - return fbo; - } - - resizeFramebuffer(framebuffer) - { - const gl = this.gl; - - if (framebuffer.stencil || framebuffer.depth) - { - gl.bindRenderbuffer(gl.RENDERBUFFER, this.stencil); - gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); - } - } - - updateFramebuffer(framebuffer) - { - const gl = this.gl; - - const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID]; - - // bind the color texture - const colorTextures = framebuffer.colorTextures; - - let count = colorTextures.length; - - if (!this.drawBufferExtension) - { - count = Math.min(count, 1); - } - - const activeTextures = []; - - for (let i = 0; i < count; i++) - { - const texture = framebuffer.colorTextures[i]; - - if (texture.texturePart) - { - this.renderer.texture.bind(texture.texture, 0); - - gl.framebufferTexture2D(gl.FRAMEBUFFER, - gl.COLOR_ATTACHMENT0 + i, - gl.TEXTURE_CUBE_MAP_NEGATIVE_X + texture.side, - texture.texture._glTextures[this.CONTEXT_UID].texture, - 0); - } - else - { - this.renderer.texture.bind(texture, 0); - - gl.framebufferTexture2D(gl.FRAMEBUFFER, - gl.COLOR_ATTACHMENT0 + i, - gl.TEXTURE_2D, - texture._glTextures[this.CONTEXT_UID].texture, - 0); - } - - activeTextures.push(gl.COLOR_ATTACHMENT0 + i); - } - - if (this.drawBufferExtension && activeTextures.length > 1) - { - this.drawBufferExtension.drawBuffersWEBGL(activeTextures); - } - - if (framebuffer.depthTexture) - { - const depthTextureExt = this.renderer.context.extensions.depthTexture; - - if (depthTextureExt) - { - const depthTexture = framebuffer.depthTexture; - - this.renderer.texture.bind(depthTexture, 0); - - gl.framebufferTexture2D(gl.FRAMEBUFFER, - gl.DEPTH_ATTACHMENT, - gl.TEXTURE_2D, - depthTexture._glTextures[this.CONTEXT_UID].texture, - 0); - } - } - - if (framebuffer.stencil || framebuffer.depth) - { - fbo.stencil = gl.createRenderbuffer(); - - gl.bindRenderbuffer(gl.RENDERBUFFER, fbo.stencil); - - // TODO.. this is depth AND stencil? - gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, fbo.stencil); - gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); - // fbo.enableStencil(); - } - } -} diff --git a/packages/core/src/renderers/systems/MaskSystem.js b/packages/core/src/renderers/systems/MaskSystem.js deleted file mode 100644 index d9c4b1a..0000000 --- a/packages/core/src/renderers/systems/MaskSystem.js +++ /dev/null @@ -1,199 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import AlphaMaskFilter from '../filters/spriteMask/SpriteMaskFilter'; - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class MaskSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - // TODO - we don't need both! - this.scissor = false; - this.scissorData = null; - this.scissorRenderTarget = null; - - this.enableScissor = false; - - this.alphaMaskPool = []; - this.alphaMaskIndex = 0; - } - - /** - * Applies the Mask and adds it to the current filter stack. - * - * @param {PIXI.DisplayObject} target - Display Object to push the mask to - * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. - */ - push(target, maskData) - { - // TODO the root check means scissor rect will not - // be used on render textures more info here: - // https://github.com/pixijs/pixi.js/pull/3545 - - if (maskData.texture) - { - this.pushSpriteMask(target, maskData); - } - else if (this.enableScissor - && !this.scissor - && this.renderer._activeRenderTarget.root - && !this.renderer.stencil.stencilMaskStack.length - && maskData.isFastRect()) - { - const matrix = maskData.worldTransform; - - let rot = Math.atan2(matrix.b, matrix.a); - - // use the nearest degree! - rot = Math.round(rot * (180 / Math.PI)); - - if (rot % 90) - { - this.pushStencilMask(maskData); - } - else - { - this.pushScissorMask(target, maskData); - } - } - else - { - this.pushStencilMask(maskData); - } - } - - /** - * Removes the last mask from the mask stack and doesn't return it. - * - * @param {PIXI.DisplayObject} target - Display Object to pop the mask from - * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. - */ - pop(target, maskData) - { - if (maskData.texture) - { - this.popSpriteMask(target, maskData); - } - else if (this.enableScissor && !this.renderer.stencil.stencilMaskStack.length) - { - this.popScissorMask(target, maskData); - } - else - { - this.popStencilMask(target, maskData); - } - } - - /** - * Applies the Mask and adds it to the current filter stack. - * - * @param {PIXI.RenderTarget} target - Display Object to push the sprite mask to - * @param {PIXI.Sprite} maskData - Sprite to be used as the mask - */ - pushSpriteMask(target, maskData) - { - let alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; - - if (!alphaMaskFilter) - { - alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; - } - - alphaMaskFilter[0].resolution = this.renderer.resolution; - alphaMaskFilter[0].maskSprite = maskData; - - // TODO - may cause issues! - target.filterArea = maskData.getBounds(true); - - this.renderer.filterSystem.pushFilter(target, alphaMaskFilter); - - this.alphaMaskIndex++; - } - - /** - * Removes the last filter from the filter stack and doesn't return it. - * - */ - popSpriteMask() - { - this.renderer.filterSystem.popFilter(); - this.alphaMaskIndex--; - } - - /** - * Applies the Mask and adds it to the current filter stack. - * - * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. - */ - pushStencilMask(maskData) - { - this.renderer.batch.flush(); - this.renderer.stencil.pushStencil(maskData); - } - - /** - * Removes the last filter from the filter stack and doesn't return it. - * - */ - popStencilMask() - { - // this.renderer.currentRenderer.stop(); - this.renderer.stencil.popStencil(); - } - - /** - * - * @param {PIXI.DisplayObject} target - Display Object to push the mask to - * @param {PIXI.Graphics} maskData - The masking data. - */ - pushScissorMask(target, maskData) - { - maskData.renderable = true; - - const renderTarget = this.renderer._activeRenderTarget; - - const bounds = maskData.getBounds(); - - bounds.fit(renderTarget.size); - maskData.renderable = false; - - this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); - - const resolution = this.renderer.resolution; - - this.renderer.gl.scissor( - bounds.x * resolution, - (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, - bounds.width * resolution, - bounds.height * resolution - ); - - this.scissorRenderTarget = renderTarget; - this.scissorData = maskData; - this.scissor = true; - } - - /** - * - * - */ - popScissorMask() - { - this.scissorRenderTarget = null; - this.scissorData = null; - this.scissor = false; - - // must be scissor! - const gl = this.renderer.gl; - - gl.disable(gl.SCISSOR_TEST); - } -} diff --git a/packages/core/src/renderers/systems/ProjectionSystem.js b/packages/core/src/renderers/systems/ProjectionSystem.js deleted file mode 100644 index ec5efdb..0000000 --- a/packages/core/src/renderers/systems/ProjectionSystem.js +++ /dev/null @@ -1,73 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { Matrix } from '@pixi/math'; - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ - -export default class ProjectionSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this.projectionMatrix = new Matrix(); - } - - update(destinationFrame, sourceFrame, resolution, root) - { - this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; - this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; - - this.calculateProjection(this.destinationFrame, this.sourceFrame, resolution, root); - - this.renderer.globalUniforms.uniforms.projectionMatrix = this.projectionMatrix; - this.renderer.globalUniforms.update(); - } - - /** - * Updates the projection matrix based on a projection frame (which is a rectangle) - * - * @param {Rectangle} destinationFrame - The destination frame. - * @param {Rectangle} sourceFrame - The source frame. - */ - calculateProjection(destinationFrame, sourceFrame, resolution, root) - { - const pm = this.projectionMatrix; - - // I don't think we will need this line.. - // pm.identity(); - - if (!root) - { - pm.a = (1 / destinationFrame.width * 2) * resolution; - pm.d = (1 / destinationFrame.height * 2) * resolution; - - pm.tx = -1 - (sourceFrame.x * pm.a); - pm.ty = -1 - (sourceFrame.y * pm.d); - } - else - { - pm.a = (1 / destinationFrame.width * 2) * resolution; - pm.d = (-1 / destinationFrame.height * 2) * resolution; - - pm.tx = -1 - (sourceFrame.x * pm.a); - pm.ty = 1 - (sourceFrame.y * pm.d); - } - } - - /** - * Sets the transform of the active render target to the given matrix - * - * @param {PIXI.Matrix} matrix - The transformation matrix - */ - setTransform()// matrix) - { - // this._activeRenderTarget.transform = matrix; - } -} diff --git a/packages/core/src/renderers/systems/RenderTextureSystem.js b/packages/core/src/renderers/systems/RenderTextureSystem.js deleted file mode 100644 index a133ef1..0000000 --- a/packages/core/src/renderers/systems/RenderTextureSystem.js +++ /dev/null @@ -1,115 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { Rectangle } from '@pixi/math'; - -const tempRect = new Rectangle(); - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ - -export default class RenderTextureSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this.clearColor = renderer._backgroundColorRgba; - - // TODO moe this property somewhere else! - this.defaultMaskStack = []; - this.defaultFilterStack = [{}]; - - // empty render texture? - this.renderTexture = null; - - this.destinationFrame = new Rectangle(); - } - - bind(renderTexture, sourceFrame, destinationFrame) - { - // TODO - do we want this?? - if (this.renderTexture === renderTexture) return; - this.renderTexture = renderTexture; - - const renderer = this.renderer; - - if (renderTexture) - { - const baseTexture = renderTexture.baseTexture; - - if (!destinationFrame) - { - tempRect.width = baseTexture.realWidth; - tempRect.height = baseTexture.realHeight; - - destinationFrame = tempRect; - } - - if (!sourceFrame) - { - sourceFrame = destinationFrame; - } - - this.renderer.framebuffer.bind(baseTexture.frameBuffer, destinationFrame); - - this.renderer.projection.update(destinationFrame, sourceFrame, baseTexture.resolution, false); - this.renderer.stencil.setMaskStack(baseTexture.stencilMaskStack); - } - else - { - // TODO these validation checks happen deeper down.. - // thing they can be avoided.. - if (!destinationFrame) - { - tempRect.width = renderer.width; - tempRect.height = renderer.height; - - destinationFrame = tempRect; - } - - if (!sourceFrame) - { - sourceFrame = destinationFrame; - } - - renderer.framebuffer.bind(null, destinationFrame); - - // TODO store this.. - this.renderer.projection.update(destinationFrame, sourceFrame, this.renderer.resolution, true); - this.renderer.stencil.setMaskStack(this.defaultMaskStack); - } - - this.destinationFrame.copyFrom(destinationFrame); - } - - /** - * Erases the render texture and fills the drawing area with a colour - * - * @param {number} [clearColor] - The colour - * @return {PIXI.Renderer} Returns itself. - */ - clear(clearColor) - { - if (this.renderTexture) - { - clearColor = clearColor || this.renderTexture.baseTexture.clearColor; - } - else - { - clearColor = clearColor || this.clearColor; - } - - this.renderer.framebuffer.clear(clearColor[0], clearColor[1], clearColor[2], clearColor[3]); - } - - resize()// screenWidth, screenHeight) - { - // resize the root only! - this.bind(null); - } -} diff --git a/packages/core/src/renderers/systems/StateSystem.js b/packages/core/src/renderers/systems/StateSystem.js deleted file mode 100755 index d7f4caf..0000000 --- a/packages/core/src/renderers/systems/StateSystem.js +++ /dev/null @@ -1,288 +0,0 @@ -import mapWebGLBlendModesToPixi from '../utils/mapWebGLBlendModesToPixi'; -import WebGLSystem from './WebGLSystem'; -import WebGLState from '../State'; - -const BLEND = 0; -const OFFSET = 1; -const CULLING = 2; -const DEPTH_TEST = 3; -const WINDING = 4; - -/** - * A WebGL state machines - * - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class StateSystem extends WebGLSystem -{ - /** - * @param {WebGLRenderingContext} gl - The current WebGL rendering context - */ - constructor(renderer) - { - super(renderer); - - this.gl = null; - - this.maxAttribs = null; - - // check we have vao.. - this.nativeVaoExtension = null; - - this.attribState = null; - - this.stateId = 0; - this.polygonOffset = 0; - this.blendMode = 17; - - this.map = []; - - // map functions for when we set state.. - this.map[BLEND] = this.setBlend; - this.map[OFFSET] = this.setOffset; - this.map[CULLING] = this.setCullFace; - this.map[DEPTH_TEST] = this.setDepthTest; - this.map[WINDING] = this.setFrontFace; - - this.checks = []; - - this.defaultState = new WebGLState(); - this.defaultState.blend = true; - this.defaultState.depth = true; - } - - contextChange(gl) - { - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; - - this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); - - // check we have vao.. - this.nativeVaoExtension = ( - gl.getExtension('OES_vertex_array_object') - || gl.getExtension('MOZ_OES_vertex_array_object') - || gl.getExtension('WEBKIT_OES_vertex_array_object') - ); - - this.attribState = { - tempAttribState: new Array(this.maxAttribs), - attribState: new Array(this.maxAttribs), - }; - - this.blendModes = mapWebGLBlendModesToPixi(gl); - - this.setState(this.defaultState); - - this.reset(); - } - - /** - * Sets the current state - * - * @param {*} state - The state to set. - */ - setState(state) - { - state = state || this.defaultState; - - // TODO maybe to an object check? ( this.state === state )? - if (this.stateId !== state.data) - { - let diff = this.stateId ^ state.data; - let i = 0; - - // order from least to most common - while (diff) - { - if (diff & 1) - { - // state change! - this.map[i].call(this, !!(state.data & (1 << i))); - } - - diff = diff >> 1; - i++; - } - - this.stateId = state.data; - } - - // based on the above settings we check for specific modes.. - // for example if blend is active we check and set the blend modes - // or of polygon offset is active we check the poly depth. - for (let i = 0; i < this.checks.length; i++) - { - this.checks[i](this, state); - } - } - - /** - * Enables or disabled blending. - * - * @param {boolean} value - Turn on or off webgl blending. - */ - setBlend(value) - { - this.updateCheck(StateSystem.checkBlendMode, value); - - this.gl[value ? 'enable' : 'disable'](this.gl.BLEND); - } - - /** - * Enables or disable polygon offset fill - * - * @param {boolean} value - Turn on or off webgl polygon offset testing. - */ - setOffset(value) - { - this.gl[value ? 'enable' : 'disable'](this.gl.POLYGON_OFFSET_FILL); - } - - /** - * Sets whether to enable or disable depth test. - * - * @param {boolean} value - Turn on or off webgl depth testing. - */ - setDepthTest(value) - { - this.gl[value ? 'enable' : 'disable'](this.gl.DEPTH_TEST); - } - - /** - * Sets whether to enable or disable cull face. - * - * @param {boolean} value - Turn on or off webgl cull face. - */ - setCullFace(value) - { - this.gl[value ? 'enable' : 'disable'](this.gl.CULL_FACE); - } - - /** - * Sets the gl front face. - * - * @param {boolean} value - true is clockwise and false is counter-clockwise - */ - setFrontFace(value) - { - this.gl.frontFace(this.gl[value ? 'CW' : 'CCW']); - } - - /** - * Sets the blend mode. - * - * @param {number} value - The blend mode to set to. - */ - setBlendMode(value) - { - if (value === this.blendMode) - { - return; - } - - this.blendMode = value; - this.gl.blendFunc(this.blendModes[value][0], this.blendModes[value][1]); - } - - /** - * Sets the polygon offset. - * - * @param {number} value - the polygon offset - * @param {number} scale - the polygon offset scale - */ - setPolygonOffset(value, scale) - { - this.gl.polygonOffset(value, scale); - } - - /** - * Disables all the vaos in use - * - */ - resetAttributes() - { - for (let i = 0; i < this.attribState.tempAttribState.length; i++) - { - this.attribState.tempAttribState[i] = 0; - } - - for (let i = 0; i < this.attribState.attribState.length; i++) - { - this.attribState.attribState[i] = 0; - } - - // im going to assume one is always active for performance reasons. - for (let i = 1; i < this.maxAttribs; i++) - { - this.gl.disableVertexAttribArray(i); - } - } - - // used - /** - * Resets all the logic and disables the vaos - */ - reset() - { - // unbind any VAO if they exist.. - if (this.nativeVaoExtension) - { - this.nativeVaoExtension.bindVertexArrayOES(null); - } - - // reset all attributes.. - this.resetAttributes(); - - this.gl.pixelStorei(this.gl.UNPACK_FLIP_Y_WEBGL, false); - - this.setBlendMode(0); - - // TODO? - // this.setState(this.defaultState); - } - - /** - * checks to see which updates should be checked based on which settings have been activated - * for example if blend is enabled then we shold check the blend modes each time the state is changed - * or if poygon fill is activated then we need to check if the polygone offset changes. - * The idea is that we only check what we have too. - * - * @param {Function} func the checking function to add or remove - * @param {boolean} value should the check function be added or removed. - */ - updateCheck(func, value) - { - const index = this.checks.indexOf(func); - - if (value && index === -1) - { - this.checks.push(func); - } - else if (!value && index !== -1) - { - this.checks.splice(index, 1); - } - } - - /** - * A private little wrapper function that we call to check the blend mode. - * - * @static - * @private - * @param {PIXI.StateSystem} System the System to perform the state check on - * @param {PIXI.State} state the state that the blendMode will pulled from - */ - static checkBlendMode(system, state) - { - system.setBlendMode(state.blendMode); - } - - // TODO - add polygon offset? -} diff --git a/packages/core/src/AbstractRenderer.js b/packages/core/src/AbstractRenderer.js new file mode 100644 index 0000000..64b2eb1 --- /dev/null +++ b/packages/core/src/AbstractRenderer.js @@ -0,0 +1,353 @@ +import { hex2string, hex2rgb } from '@pixi/utils'; +import { Matrix, Rectangle } from '@pixi/math'; +import { RENDERER_TYPE } from '@pixi/constants'; +import { settings } from '@pixi/settings'; +import { Container } from '@pixi/display'; +import RenderTexture from './renderTexture/RenderTexture'; +import EventEmitter from 'eventemitter3'; + +const tempMatrix = new Matrix(); + +/** + * 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 EventEmitter + * @memberof PIXI + */ +export default class AbstractRenderer extends EventEmitter +{ + // eslint-disable-next-line valid-jsdoc + /** + * @param {string} system - The name of the system this renderer is for. + * @param {object} [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.transparent=false] - If the render view is transparent, default false + * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false + * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. The + * resolution of the renderer retina would be 2. + * @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 {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when rendering, + * stopping pixel interpolation. + */ + constructor(system, options, arg2, arg3) + { + super(); + + // Support for constructor(system, screenWidth, screenHeight, options) + if (typeof options === 'number') + { + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + }, arg3); + } + + // 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 = RENDERER_TYPE.UNKNOWN; + + /** + * Measurements of the screen. (0, 0, screenWidth, screenHeight) + * + * Its safe to use as filterArea or hitArea for 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 1 + */ + this.resolution = options.resolution || settings.RESOLUTION; + + /** + * Whether the render view is transparent + * + * @member {boolean} + */ + this.transparent = options.transparent; + + /** + * Whether css dimensions of canvas view should be resized to screen dimensions automatically + * + * @member {boolean} + */ + this.autoResize = options.autoResize || false; + + /** + * Tracks the blend modes useful for this renderer. + * + * @member {object} + */ + this.blendModes = null; + + /** + * 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; + + /** + * If true PixiJS will Math.floor() x/y values when rendering, stopping pixel interpolation. + * Handy for crisp pixel art and speed on legacy devices. + * + * @member {boolean} + */ + this.roundPixels = options.roundPixels; + + /** + * The background color as a number. + * + * @member {number} + * @private + */ + this._backgroundColor = 0x000000; + + /** + * The background color as an [R, G, B] array. + * + * @member {number[]} + * @private + */ + this._backgroundColorRgba = [0, 0, 0, 0]; + + /** + * The background color as a string. + * + * @member {string} + * @private + */ + this._backgroundColorString = '#000000'; + + this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter + + /** + * This temporary display object used as the parent of the currently being rendered item + * + * @member {PIXI.DisplayObject} + * @private + */ + this._tempDisplayObjectParent = new Container(); + + /** + * The last root object that the renderer tried to render. + * + * @member {PIXI.DisplayObject} + * @private + */ + this._lastObjectRendered = this._tempDisplayObjectParent; + + /** + * Collection of plugins. + * @readonly + * @member {object} + */ + this.plugins = {}; + } + + /** + * Initialize the plugins. + * + * @protected + * @param {object} staticMap - The dictionary of staticly saved plugins. + */ + initPlugins(staticMap) + { + 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() + { + return this.view.width; + } + + /** + * Same as view.height, actual number of pixels in the canvas by vertical + * + * @member {number} + * @readonly + * @default 600 + */ + get height() + { + return this.view.height; + } + + /** + * Resizes the screen and canvas to the specified width and height + * Canvas dimensions are multiplied by resolution + * + * @param {number} screenWidth - the new width of the screen + * @param {number} screenHeight - the new height of the screen + */ + resize(screenWidth, screenHeight) + { + this.screen.width = screenWidth; + this.screen.height = screenHeight; + + this.view.width = screenWidth * this.resolution; + this.view.height = screenHeight * this.resolution; + + if (this.autoResize) + { + this.view.style.width = `${screenWidth}px`; + this.view.style.height = `${screenHeight}px`; + } + } + + /** + * 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 {PIXI.DisplayObject} displayObject - The displayObject the object will be generated from + * @param {number} scaleMode - Should be one of the scaleMode consts + * @param {number} resolution - The resolution / device pixel ratio of the texture being generated + * @param {PIXI.Rectangle} [region] - The region of the displayObject, that shall be rendered, + * if no region is specified, defaults to the local bounds of the displayObject. + * @return {PIXI.Texture} a texture of the graphics object + */ + generateTexture(displayObject, scaleMode, resolution, region) + { + region = region || displayObject.getLocalBounds(); + + // 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(region.width | 0, region.height | 0, scaleMode, resolution); + + tempMatrix.tx = -region.x; + tempMatrix.ty = -region.y; + + this.render(displayObject, renderTexture, false, tempMatrix, true); + + return renderTexture; + } + + /** + * Removes everything from the renderer and optionally removes the Canvas DOM element. + * + * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. + */ + destroy(removeView) + { + 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); + } + + this.plugins = null; + + this.type = RENDERER_TYPE.UNKNOWN; + + this.view = null; + + this.screen = null; + + this.resolution = 0; + + this.transparent = false; + + this.autoResize = false; + + this.blendModes = null; + + this.options = null; + + this.preserveDrawingBuffer = false; + this.clearBeforeRender = false; + + this.roundPixels = false; + + this._backgroundColor = 0; + this._backgroundColorRgba = null; + this._backgroundColorString = null; + + this._tempDisplayObjectParent = null; + this._lastObjectRendered = null; + } + + /** + * The background color to fill if not transparent + * + * @member {number} + */ + get backgroundColor() + { + return this._backgroundColor; + } + + set backgroundColor(value) // eslint-disable-line require-jsdoc + { + this._backgroundColor = value; + this._backgroundColorString = hex2string(value); + hex2rgb(value, this._backgroundColorRgba); + } +} diff --git a/packages/core/src/Renderer.js b/packages/core/src/Renderer.js new file mode 100644 index 0000000..2227e78 --- /dev/null +++ b/packages/core/src/Renderer.js @@ -0,0 +1,338 @@ +import AbstractRenderer from './AbstractRenderer'; +import { sayHello } from '@pixi/utils'; +import MaskSystem from './mask/MaskSystem'; +import StencilSystem from './mask/StencilSystem'; +import FilterSystem from './filters/FilterSystem'; +import FramebufferSystem from './framebuffer/FramebufferSystem'; +import RenderTextureSystem from './renderTexture/RenderTextureSystem'; +import TextureSystem from './textures/TextureSystem'; +import ProjectionSystem from './projection/ProjectionSystem'; +import StateSystem from './state/StateSystem'; +import GeometrySystem from './geometry/GeometrySystem'; +import ShaderSystem from './shader/ShaderSystem'; +import ContextSystem from './context/ContextSystem'; +import BatchSystem from './batch/BatchSystem'; +import TextureGCSystem from './textures/TextureGCSystem'; +import { RENDERER_TYPE } from '@pixi/constants'; +import UniformGroup from './shader/UniformGroup'; +import { Matrix } from '@pixi/math'; +import Runner from 'mini-runner'; + +/** + * 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 Render works by automatically managing webGLBatchs. + * 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 :) + * + * @class + * @memberof PIXI + * @extends PIXI.AbstractRenderer + */ +export default class Renderer extends AbstractRenderer +{ + // eslint-disable-next-line valid-jsdoc + /** + * + * @param {object} [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.transparent=false] - If the render view is transparent, default false + * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false + * @param {boolean} [options.antialias=false] - sets antialias. If not available natively then FXAA + * antialiasing is used + * @param {boolean} [options.forceFXAA=false] - forces FXAA antialiasing to be used over native. + * FXAA is faster, but may not always look as great + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. + * The resolution of the renderer retina would be 2. + * @param {boolean} [options.clearBeforeRender=true] - This sets if the renderer will clear + * the canvas or not before the new render pass. If you wish to set this to false, you *must* set + * preserveDrawingBuffer to `true`. + * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, + * enable this if you need to call toDataUrl on the webgl context. + * @param {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when + * rendering, stopping pixel interpolation. + * @param {number} [options.backgroundColor=0x000000] - The background color of the rendered area + * (shown if not transparent). + * @param {boolean} [options.legacy=false] - If true PixiJS will aim to ensure compatibility + * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. + * @param {string} [options.powerPreference] - Parameter passed to webgl context, set to "high-performance" + * for devices with dual graphics card + */ + constructor(options, arg2, arg3) + { + super('WebGL', options, arg2, arg3); + + /** + * The type of this renderer as a standardised const + * + * @member {number} + * @see PIXI.RENDERER_TYPE + */ + this.type = RENDERER_TYPE.WEBGL; + + // this will be set by the contextSystem (this.context) + this.gl = null; + this.CONTEXT_UID = 0; + this.legacy = !!options.legacy; + + // TODO legacy! + + // runners! + this.runners = { + destroy: new Runner('destroy'), + contextChange: new Runner('contextChange', 1), + reset: new Runner('reset'), + update: new Runner('update'), + postrender: new Runner('postrender'), + prerender: new Runner('prerender'), + resize: new Runner('resize', 2), + }; + + 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(GeometrySystem, 'geometry') + .addSystem(FramebufferSystem, 'framebuffer') + .addSystem(StencilSystem, 'stencil') + .addSystem(ProjectionSystem, 'projection') + .addSystem(TextureGCSystem, 'textureGC') + .addSystem(FilterSystem, 'filter') + .addSystem(RenderTextureSystem, 'renderTexture') + .addSystem(BatchSystem, 'batch'); + + this.initPlugins(Renderer.__plugins); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ + if (options.context) + { + this.context.initFromContext(options.context); + } + else + { + this.context.initFromOptions({ + alpha: this.transparent, + antialias: options.antialias, + premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', + stencil: true, + 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); + } + + /** + * Add a new system to the renderer. + * @param {class} ClassRef - Class reference + * @param {string} [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. + * @return {PIXI.Renderer} Return instance of renderer + */ + addSystem(ClassRef, name) + { + if (!name) + { + name = ClassRef.name; + } + + // TODO - read name from class.name.. + + /* + if(name.includes('System')) + { + name = name.replace('System', ''); + name = name.charAt(0).toLowerCase() + name.slice(1); + } + */ + + const system = new ClassRef(this); + + if (this[name]) + { + throw new Error(`Whoops! ${name} is already a manger`); + } + + this[name] = system; + + for (const i in this.runners) + { + this.runners[i].add(system); + } + + return this; + + /** + * 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. + */ + } + + /** + * Renders the object to its webGL view + * + * @param {PIXI.DisplayObject} displayObject - the object to be rendered + * @param {PIXI.RenderTexture} renderTexture - The render texture to render to. + * @param {boolean} [clear] - Should the canvas be cleared before the new render + * @param {PIXI.Transform} [transform] - A transform to apply to the render texture before rendering. + * @param {boolean} [skipUpdateTransform] - Should we skip the update transform pass? + */ + render(displayObject, renderTexture, clear, transform, skipUpdateTransform) + { + // can be handy to know! + this.renderingToScreen = !renderTexture; + + this.runners.prerender.run(); + this.emit('prerender'); + + // 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.parent; + + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = 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.run(); + + this.emit('postrender'); + } + + /** + * Resizes the webGL view to the specified width and height. + * + * @param {number} screenWidth - the new width of the screen + * @param {number} screenHeight - the new height of the screen + */ + resize(screenWidth, screenHeight) + { + AbstractRenderer.prototype.resize.call(this, screenWidth, screenHeight); + + this.runners.resize.run(screenWidth, screenHeight); + } + + /** + * Resets the WebGL state so you can render things however you fancy! + * + * @return {PIXI.Renderer} Returns itself. + */ + reset() + { + this.runners.reset.run(); + + return this; + } + + clear() + { + this.framebuffer.bind(); + this.framebuffer.clear(); + } + + /** + * Removes everything from the renderer (event listeners, spritebatch, etc...) + * + * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. + * See: https://github.com/pixijs/pixi.js/issues/2233 + */ + destroy(removeView) + { + this.runners.destroy.run(); + + // call base destroy + super.destroy(removeView); + + // TODO nullify all the managers.. + this.gl = null; + } + + /** + * 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 + * builds and excluding plugins. + * @name PIXI.Renderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. + */ + + /** + * Adds a plugin to the renderer. + * + * @method + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + static registerPlugin(pluginName, ctor) + { + Renderer.__plugins = Renderer.__plugins || {}; + Renderer.__plugins[pluginName] = ctor; + } +} diff --git a/packages/core/src/System.js b/packages/core/src/System.js new file mode 100644 index 0000000..05a10c8 --- /dev/null +++ b/packages/core/src/System.js @@ -0,0 +1,42 @@ +/** + * System is a base class used for extending systems used by the {@link PIXI.Renderer} + * @see PIXI.Renderer#addSystem + * @class + * @memberof PIXI + */ +export default class System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + /** + * The renderer this manager works for. + * + * @member {PIXI.Renderer} + */ + this.renderer = renderer; + + this.renderer.runners.contextChange.add(this); + } + + /** + * Generic method called when there is a WebGL context change. + * + */ + contextChange() + { + // do some codes init! + } + + /** + * Generic destroy methods to be overridden by the subclass + * + */ + destroy() + { + this.renderer.runners.contextChange.remove(this); + this.renderer = null; + } +} diff --git a/packages/core/src/batch/BatchSystem.js b/packages/core/src/batch/BatchSystem.js new file mode 100644 index 0000000..b04c249 --- /dev/null +++ b/packages/core/src/batch/BatchSystem.js @@ -0,0 +1,68 @@ +import System from '../System'; +import ObjectRenderer from './ObjectRenderer'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class BatchSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + super(renderer); + + /** + * An empty renderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.emptyRenderer = new ObjectRenderer(renderer); + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.currentRenderer = this.emptyRenderer; + } + + /** + * Changes the current renderer to the one given in parameter + * + * @param {PIXI.ObjectRenderer} objectRenderer - The object renderer to use. + */ + setObjectRenderer(objectRenderer) + { + if (this.currentRenderer === objectRenderer) + { + return; + } + + this.currentRenderer.stop(); + this.currentRenderer = objectRenderer; + + this.renderer.state.setState(objectRenderer.state); + + this.currentRenderer.start(); + } + + /** + * This should be called if you wish to do some custom rendering + * It will basically render anything that may be batched up such as sprites + * + */ + flush() + { + this.setObjectRenderer(this.emptyRenderer); + } + + reset() + { + this.setObjectRenderer(this.emptyRenderer); + } +} diff --git a/packages/core/src/batch/ObjectRenderer.js b/packages/core/src/batch/ObjectRenderer.js new file mode 100644 index 0000000..0c851a9 --- /dev/null +++ b/packages/core/src/batch/ObjectRenderer.js @@ -0,0 +1,48 @@ +import System from '../System'; + +/** + * Base for a common object renderer that can be used as a system renderer plugin. + * + * @class + * @extends PIXI.System + * @memberof PIXI + */ +export default class ObjectRenderer extends System +{ + /** + * Starts the renderer and sets the shader + * + */ + start() + { + // set the shader.. + } + + /** + * Stops the renderer + * + */ + stop() + { + this.flush(); + } + + /** + * Stub method for rendering content and emptying the current batch. + * + */ + flush() + { + // flush! + } + + /** + * Renders an object + * + * @param {PIXI.DisplayObject} object - The object to render. + */ + render(object) // eslint-disable-line no-unused-vars + { + // render the object + } +} diff --git a/packages/core/src/context/ContextSystem.js b/packages/core/src/context/ContextSystem.js new file mode 100644 index 0000000..301aab4 --- /dev/null +++ b/packages/core/src/context/ContextSystem.js @@ -0,0 +1,186 @@ +import System from '../System'; +import { settings } from '@pixi/settings'; + +let CONTEXT_UID = 0; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class ContextSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.webGLVersion = 1; + + this.handleContextLost = this.handleContextLost.bind(this); + this.handleContextRestored = this.handleContextRestored.bind(this); + + this.extensions = {}; + + renderer.view.addEventListener('webglcontextlost', this.handleContextLost, false); + renderer.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + } + + get isLost() + { + return (!this.gl || this.gl.isContextLost()); + } + + contextChange(gl) + { + this.gl = gl; + + // restore a context if it was previously lost + if (gl.isContextLost() && gl.getExtension('WEBGL_lose_context')) + { + gl.getExtension('WEBGL_lose_context').restoreContext(); + } + } + + initFromContext(gl) + { + this.gl = gl; + this.validateContext(gl); + this.renderer.gl = gl; + this.renderer.CONTEXT_UID = CONTEXT_UID++; + this.renderer.runners.contextChange.run(gl); + } + + initFromOptions(options) + { + const gl = this.createContext(this.renderer.view, options); + + this.initFromContext(gl); + } + + /** + * Helper class to create a webGL Context + * + * @class + * @memberof PIXI.glCore + * @param canvas {HTMLCanvasElement} the canvas element that we will get the context from + * @param options {Object} An options object that gets passed in to the canvas element containing the context attributes, + * see https://developer.mozilla.org/en/docs/Web/API/HTMLCanvasElement/getContext for the options available + * @return {WebGLRenderingContext} the WebGL context + */ + createContext(canvas, options) + { + let gl; + + if (settings.PREFER_WEBGL_2) + { + gl = canvas.getContext('webgl2', options); + } + + if (gl) + { + this.webGLVersion = 2; + } + else + { + this.webGLVersion = 1; + + gl = canvas.getContext('webgl', options) + || canvas.getContext('experimental-webgl', options); + + if (!gl) + { + // fail, not able to get a context + throw new Error('This browser does not support webGL. Try using the canvas renderer'); + } + } + + this.gl = gl; + + this.getExtensions(); + + return gl; + } + + getExtensions() + { + // time to set up default etensions that pixi uses.. + const gl = this.gl; + const extensions = this.extensions; + + if (this.webGLVersion === 1) + { + extensions.drawBuffers = gl.getExtension('WEBGL_draw_buffers'); + extensions.depthTexture = gl.getExtension('WEBKIT_WEBGL_depth_texture'); + extensions.floatTexture = gl.getExtension('OES_texture_float'); + extensions.loseContext = gl.getExtension('WEBGL_lose_context'); + + extensions.vertexArrayObject = gl.getExtension('OES_vertex_array_object') + || gl.getExtension('MOZ_OES_vertex_array_object') + || gl.getExtension('WEBKIT_OES_vertex_array_object'); + } + + // we don't use any specific WebGL 2 ones yet! + } + + /** + * Handles a lost webgl context + * + * @private + * @param {WebGLContextEvent} event - The context lost event. + */ + handleContextLost(event) + { + event.preventDefault(); + } + + /** + * Handles a restored webgl context + * + * @private + */ + handleContextRestored() + { + this.renderer.runners.contextChange.run(this.gl); + } + + destroy() + { + const view = this.renderer.view; + + // remove listeners + view.removeEventListener('webglcontextlost', this.handleContextLost); + view.removeEventListener('webglcontextrestored', this.handleContextRestored); + + this.gl.useProgram(null); + + if (this.extensions.loseContext) + { + this.extensions.loseContext.loseContext(); + } + } + + postrender() + { + this.gl.flush(); + } + + validateContext(gl) + { + const attributes = gl.getContextAttributes(); + + // this is going to be fairly simple for now.. but at least we have room to grow! + if (!attributes.stencil) + { + /* eslint-disable max-len */ + + /* eslint-disable no-console */ + console.warn('Provided WebGL context does not have a stencil buffer, masks may not render correctly'); + /* eslint-enable no-console */ + + /* eslint-enable max-len */ + } + } +} diff --git a/packages/core/src/filters/Filter.js b/packages/core/src/filters/Filter.js new file mode 100644 index 0000000..9d8cad8 --- /dev/null +++ b/packages/core/src/filters/Filter.js @@ -0,0 +1,140 @@ +import Shader from '../shader/Shader'; +import Program from '../shader/Program'; +import State from '../state/State'; +import { settings } from '@pixi/settings'; +// import extractUniformsFromSrc from './extractUniformsFromSrc'; +import defaultVertex from './defaultFilter.vert'; +import defaultFragment from './defaultFilter.frag'; + +// let math = require('../../math'); +/** + * @class + * @memberof PIXI + * @extends PIXI.Shader + */ +export default class Filter extends Shader +{ + /** + * @param {string} [vertexSrc] - The source of the vertex shader. + * @param {string} [fragmentSrc] - The source of the fragment shader. + * @param {object} [uniforms] - Custom uniforms to use to augment the built-in ones. + */ + constructor(vertexSrc, fragmentSrc, uniforms) + { + const program = Program.from(vertexSrc, fragmentSrc); + + super(program, uniforms); + + /** + * The padding of the filter. Some filters require extra space to breath such as a blur. + * Increasing this will add extra width and height to the bounds of the object that the + * filter is applied to. + * + * @member {number} + */ + this.padding = 0; + + /** + * The resolution of the filter. Setting this to be lower will lower the quality but + * increase the performance of the filter. + * + * @member {number} + */ + this.resolution = settings.RESOLUTION; + + /** + * If enabled is true the filter is applied, if false it will not. + * + * @member {boolean} + */ + this.enabled = true; + + /** + * If enabled, PixiJS will fit the filter area into boundaries for better performance. + * Switch it off if it does not work for specific shader. + * + * @member {boolean} + */ + this.autoFit = true; + + /** + * Legacy filters use position and uvs from attributes + * @member {boolean} + * @readonly + */ + this.legacy = !!this.program.attributeData.aTextureCoord; + + /** + * the webGL state the filter requires to render + * @member {PIXI.State} + */ + this.state = new State(); + } + + /** + * Applies the filter + * + * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + * @param {boolean} clear - Should the output be cleared before rendering to it + * @param {object} [currentState] - It's current state of filter. + * There are some useful properties in the currentState : + * target, filters, sourceFrame, destinationFrame, renderTarget, resolution + */ + apply(filterManager, input, output, clear, currentState, derp) // eslint-disable-line no-unused-vars + { + // do as you please! + + filterManager.applyFilter(this, input, output, clear, currentState, derp); + + // or just do a regular render.. + } + + /** + * Sets the blendmode of the filter + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL + */ + get blendMode() + { + return this.state.blendMode; + } + + set blendMode(value) // eslint-disable-line require-jsdoc + { + this.state.blendMode = value; + } + + /** + * The default vertex shader source + * + * @static + * @constant + */ + static get defaultVertexSrc() + { + return defaultVertex; + } + + /** + * The default fragment shader source + * + * @static + * @constant + */ + static get defaultFragmentSrc() + { + return defaultFragment; + } +} + +/** + * Used for caching shader IDs + * + * @static + * @private + */ +Filter.SOURCE_KEY_MAP = {}; + diff --git a/packages/core/src/filters/FilterSystem.js b/packages/core/src/filters/FilterSystem.js new file mode 100644 index 0000000..3174318 --- /dev/null +++ b/packages/core/src/filters/FilterSystem.js @@ -0,0 +1,431 @@ +import System from '../System'; + +import RenderTexture from '../renderTexture/RenderTexture'; +import Quad from '../utils/Quad'; +import QuadUv from '../utils/QuadUv'; +import { Rectangle } from '@pixi/math'; +import * as filterTransforms from './filterTransforms'; +import bitTwiddle from 'bit-twiddle'; +import UniformGroup from '../shader/UniformGroup'; +import { DRAW_MODES } from '@pixi/constants'; + +// +/** + * @ignore + * @class + */ +class FilterState +{ + /** + * + */ + constructor() + { + this.renderTexture = null; + this.sourceFrame = new Rectangle(); + this.destinationFrame = new Rectangle(); + this.filters = []; + this.target = null; + this.legacy = false; + this.resolution = 1; + } +} + +/** + * @class + * @memberof PIXI.systems + * @extends PIXI.System + */ +export default class FilterSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + /** + * stores a bunch of PO2 textures used for filtering + * @type {Object} + */ + this.texturePool = {}; + + /** + * a pool for storing filter states, save us creating new ones each tick + * @type {Array} + */ + this.statePool = []; + + /** + * A very simple geometry used when drawing a filter effect to the screen + * @type {Quad} + */ + this.quad = new Quad(); + + this.quadUv = new QuadUv(); + + /** + * Temporary rect for maths + * @type {PIXI.Rectangle} + */ + this.tempRect = new Rectangle(); + + this.activeState = {}; + + /** + * this uniform group is attached to filter uniforms when used + * @type {UniformGroup} + */ + this.globalUniforms = new UniformGroup({ + sourceFrame: this.tempRect, + destinationFrame: this.tempRect, + + // legacy variables + filterArea: new Float32Array(4), + filterClamp: new Float32Array(4), + }, true); + } + + /** + * Adds a new filter to the System. + * + * @param {PIXI.DisplayObject} target - The target of the filter to render. + * @param {PIXI.Filter[]} filters - The filters to apply. + */ + push(target, filters) + { + const renderer = this.renderer; + const filterStack = this.renderer.renderTexture.defaultFilterStack; + const state = this.statePool.pop() || new FilterState(); + + let resolution = filters[0].resolution; + let padding = filters[0].padding; + let autoFit = filters[0].autoFit; + let legacy = filters[0].legacy; + + for (let i = 1; i < filters.length; i++) + { + const filter = filters[i]; + + // lets use the lowest resolution.. + resolution = Math.min(resolution, filter.resolution); + // and the largest amount of padding! + padding = Math.max(padding, filter.padding); + // only auto fit if all filters are autofit + autoFit = autoFit || filter.autoFit; + + legacy = legacy || filter.legacy; + } + + filterStack.push(state); + + state.resolution = resolution; + + state.legacy = legacy; + + // round to whole number based on resolution + // TODO move that to the shader too? + state.sourceFrame = target.filterArea || target.getBounds(true); + + state.sourceFrame.pad(padding); + + if (autoFit) + { + state.sourceFrame.fit(this.renderer.renderTexture.destinationFrame); + } + + state.sourceFrame.round(resolution); + + state.renderTexture = this.getPotFilterTexture(state.sourceFrame.width, state.sourceFrame.height, resolution); + state.filters = filters; + + state.destinationFrame.width = state.renderTexture.width; + state.destinationFrame.height = state.renderTexture.height; + + state.renderTexture.filterFrame = state.sourceFrame; + renderer.renderTexture.bind(state.renderTexture, state.sourceFrame);// /, state.destinationFrame); + renderer.renderTexture.clear(); + } + + /** + * Pops off the filter and applies it. + * + */ + pop() + { + const renderer = this.renderer; + const filterStack = renderer.renderTexture.defaultFilterStack; + const state = filterStack.pop(); + const filters = state.filters; + + this.activeState = state; + + const globalUniforms = this.globalUniforms.uniforms; + + globalUniforms.sourceFrame = state.sourceFrame; + globalUniforms.destinationFrame = state.destinationFrame; + globalUniforms.resolution = state.resolution; + + // only update the rect if its legacy.. + if (state.legacy) + { + const filterArea = globalUniforms.filterArea; + const filterClamp = globalUniforms.filterClamp; + + filterArea[0] = state.destinationFrame.width; + filterArea[1] = state.destinationFrame.height; + filterArea[2] = state.sourceFrame.x; + filterArea[3] = state.sourceFrame.y; + + filterClamp[0] = 0.5 / state.resolution / state.destinationFrame.width; + filterClamp[1] = 0.5 / state.resolution / state.destinationFrame.height; + filterClamp[2] = (state.sourceFrame.width - 0.5) / state.resolution / state.destinationFrame.width; + filterClamp[3] = (state.sourceFrame.height - 0.5) / state.resolution / state.destinationFrame.height; + } + + this.globalUniforms.update(); + + const lastState = filterStack[filterStack.length - 1]; + + if (filters.length === 1) + { + filters[0].apply(this, state.renderTexture, lastState.renderTexture, false, state); + + this.returnFilterTexture(state.renderTexture); + } + else + { + let flip = state.renderTexture; + let flop = this.getPotFilterTexture( + flip.width, + flip.height, + state.resolution + ); + + flop.filterFrame = flip.filterFrame; + + let i = 0; + + for (i = 0; i < filters.length - 1; ++i) + { + filters[i].apply(this, flip, flop, true, state); + + const t = flip; + + flip = flop; + flop = t; + } + + filters[i].apply(this, flip, lastState.renderTexture, false, state); + + this.returnFilterTexture(flip); + this.returnFilterTexture(flop); + } + + this.statePool.push(state); + } + + /** + * Draws a filter. + * + * @param {PIXI.Filter} filter - The filter to draw. + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + * @param {boolean} clear - Should the output be cleared before rendering to it + */ + applyFilter(filter, input, output, clear) + { + const renderer = this.renderer; + + renderer.renderTexture.bind(output, output ? output.filterFrame : null); + + if (clear) + { + // gl.disable(gl.SCISSOR_TEST); + renderer.renderTexture.clear(); + // gl.enable(gl.SCISSOR_TEST); + } + + // set the uniforms.. + filter.uniforms.uSampler = input; + filter.uniforms.filterGlobals = this.globalUniforms; + + // TODO make it so that the order of this does not matter.. + // because it does at the moment cos of global uniforms. + // they need to get resynced + + renderer.state.setState(filter.state); + renderer.shader.bind(filter); + + if (filter.legacy) + { + this.quadUv.map(input._frame, input.filterFrame); + + renderer.geometry.bind(this.quadUv); + renderer.geometry.draw(DRAW_MODES.TRIANGLES); + } + else + { + renderer.geometry.bind(this.quad); + renderer.geometry.draw(DRAW_MODES.TRIANGLE_STRIP); + } + } + + /** + * Calculates the mapped matrix. + * + * TODO playing around here.. this is temporary - (will end up in the shader) + * this returns a matrix that will normalise map filter cords in the filter to screen space + * + * @param {PIXI.Matrix} outputMatrix - the matrix to output to. + * @return {PIXI.Matrix} The mapped matrix. + */ + calculateScreenSpaceMatrix(outputMatrix) + { + const currentState = this.activeState; + + return filterTransforms.calculateScreenSpaceMatrix( + outputMatrix, + currentState.sourceFrame, + currentState.destinationFrame + ); + } + + /** + * This will map the filter coord so that a texture can be used based on the transform of a sprite + * + * @param {PIXI.Matrix} outputMatrix - The matrix to output to. + * @param {PIXI.Sprite} sprite - The sprite to map to. + * @return {PIXI.Matrix} The mapped matrix. + */ + calculateSpriteMatrix(outputMatrix, sprite) + { + const currentState = this.activeState; + + return filterTransforms.calculateSpriteMatrix( + outputMatrix, + currentState.sourceFrame, + currentState.destinationFrame, + sprite + ); + } + + /** + * Destroys this Filter System. + * + * @param {boolean} [contextLost=false] context was lost, do not free shaders + * + */ + destroy(contextLost = false) + { + if (!contextLost) + { + this.emptyPool(); + } + else + { + this.texturePool = {}; + } + } + + /** + * Gets a Power-of-Two render texture. + * + * TODO move to a seperate class could be on renderer? + * also - could cause issue with multiple contexts? + * + * @private + * @param {WebGLRenderingContext} gl - The webgl rendering context + * @param {number} minWidth - The minimum width of the render target. + * @param {number} minHeight - The minimum height of the render target. + * @param {number} resolution - The resolution of the render target. + * @return {PIXI.RenderTarget} The new render target. + */ + getPotFilterTexture(minWidth, minHeight, resolution) + { + minWidth = bitTwiddle.nextPow2(minWidth); + minHeight = bitTwiddle.nextPow2(minHeight); + resolution = resolution || 1; + + const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + + if (!this.texturePool[key]) + { + this.texturePool[key] = []; + } + + let renderTexture = this.texturePool[key].pop(); + + if (!renderTexture) + { + // temporary bypass cache.. + // internally - this will cause a texture to be bound.. + renderTexture = RenderTexture.create({ + width: minWidth, + height: minHeight, + resolution, + }); + } + + return renderTexture; + } + + /** + * Gets extra render texture to use inside current filter + * + * @param {number} resolution resolution of the renderTexture + * @returns {PIXI.RenderTexture} + */ + getFilterTexture(resolution) + { + const rt = this.activeState.renderTexture; + + const filterTexture = this.getPotFilterTexture(rt.width, rt.height, resolution || rt.baseTexture.resolution); + + filterTexture.filterFrame = rt.filterFrame; + + return filterTexture; + } + + /** + * Frees a render texture back into the pool. + * + * @param {PIXI.RenderTarget} renderTexture - The renderTarget to free + */ + returnFilterTexture(renderTexture) + { + renderTexture.filterFrame = null; + + const base = renderTexture.baseTexture; + + const minWidth = base.width; + const minHeight = base.height; + + const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + + this.texturePool[key].push(renderTexture); + } + + /** + * Empties the texture pool. + * + */ + emptyPool() + { + for (const i in this.texturePool) + { + const textures = this.texturePool[i]; + + if (textures) + { + for (let j = 0; j < textures.length; j++) + { + textures[j].destroy(true); + } + } + } + + this.texturePool = {}; + } +} diff --git a/packages/core/src/filters/defaultFilter.frag b/packages/core/src/filters/defaultFilter.frag new file mode 100644 index 0000000..a14c1be --- /dev/null +++ b/packages/core/src/filters/defaultFilter.frag @@ -0,0 +1,22 @@ +varying vec2 vTextureCoord; +varying vec2 vFilterCoord; + +uniform sampler2D uSampler; +uniform sampler2D filterSampler; + +void main(void){ + vec4 masky = texture2D(filterSampler, vFilterCoord); + vec4 sample = texture2D(uSampler, vTextureCoord); + vec4 color; + if(mod(vFilterCoord.x, 1.0) > 0.5) + { + color = vec4(1.0, 0.0, 0.0, 1.0); + } + else + { + color = vec4(0.0, 1.0, 0.0, 1.0); + } + // gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0); + gl_FragColor = mix(sample, masky, 0.5); + gl_FragColor *= sample.a; +} diff --git a/packages/core/src/filters/defaultFilter.vert b/packages/core/src/filters/defaultFilter.vert new file mode 100644 index 0000000..e1febca --- /dev/null +++ b/packages/core/src/filters/defaultFilter.vert @@ -0,0 +1,14 @@ +attribute vec2 aVertexPosition; +attribute vec2 aTextureCoord; + +uniform mat3 projectionMatrix; +uniform mat3 filterMatrix; + +varying vec2 vTextureCoord; +varying vec2 vFilterCoord; + +void main(void){ + gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); + vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy; + vTextureCoord = aTextureCoord ; +} diff --git a/packages/core/src/filters/filterTransforms.js b/packages/core/src/filters/filterTransforms.js new file mode 100644 index 0000000..87a3cd6 --- /dev/null +++ b/packages/core/src/filters/filterTransforms.js @@ -0,0 +1,50 @@ +import { Matrix } from '@pixi/math'; + +/** + * Calculates the mapped matrix + * @param filterArea {Rectangle} The filter area + * @param sprite {Sprite} the target sprite + * @param outputMatrix {Matrix} @alvin + * @private + */ +// this returns a matrix that will normalise map filter cords in the filter to screen space +export function calculateScreenSpaceMatrix(outputMatrix, filterArea, textureSize) +{ + // TODO unwrap? + const mappedMatrix = outputMatrix.identity(); + + mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); + + mappedMatrix.scale(textureSize.width, textureSize.height); + + return mappedMatrix; +} + +export function calculateNormalizedScreenSpaceMatrix(outputMatrix, filterArea, textureSize) +{ + const mappedMatrix = outputMatrix.identity(); + + mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); + + const translateScaleX = (textureSize.width / filterArea.width); + const translateScaleY = (textureSize.height / filterArea.height); + + mappedMatrix.scale(translateScaleX, translateScaleY); + + return mappedMatrix; +} + +// this will map the filter coord so that a texture can be used based on the transform of a sprite +export function calculateSpriteMatrix(outputMatrix, filterArea, textureSize, sprite) +{ + const orig = sprite._texture.orig; + const mappedMatrix = outputMatrix.set(textureSize.width, 0, 0, textureSize.height, filterArea.x, filterArea.y); + const worldTransform = sprite.worldTransform.copyTo(Matrix.TEMP_MATRIX); + + worldTransform.invert(); + mappedMatrix.prepend(worldTransform); + mappedMatrix.scale(1.0 / orig.width, 1.0 / orig.height); + mappedMatrix.translate(sprite.anchor.x, sprite.anchor.y); + + return mappedMatrix; +} diff --git a/packages/core/src/filters/spriteMask/SpriteMaskFilter.js b/packages/core/src/filters/spriteMask/SpriteMaskFilter.js new file mode 100644 index 0000000..91697b7 --- /dev/null +++ b/packages/core/src/filters/spriteMask/SpriteMaskFilter.js @@ -0,0 +1,63 @@ +import Filter from '../Filter'; +import { Matrix } from '@pixi/math'; +import vertex from './spriteMaskFilter.vert'; +import fragment from './spriteMaskFilter.frag'; +import { default as TextureMatrix } from '../../textures/TextureMatrix'; + +/** + * The SpriteMaskFilter class + * + * @class + * @extends PIXI.Filter + * @memberof PIXI + */ +export default class SpriteMaskFilter extends Filter +{ + /** + * @param {PIXI.Sprite} sprite - the target sprite + */ + constructor(sprite) + { + const maskMatrix = new Matrix(); + + super(vertex, fragment); + + sprite.renderable = false; + + this.maskSprite = sprite; + this.maskMatrix = maskMatrix; + } + + /** + * Applies the filter + * + * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + */ + apply(filterManager, input, output) + { + const maskSprite = this.maskSprite; + const tex = this.maskSprite.texture; + + if (!tex.valid) + { + return; + } + if (!tex.transform) + { + // margin = 0.0, let it bleed a bit, shader code becomes easier + // assuming that atlas textures were made with 1-pixel padding + tex.transform = new TextureMatrix(tex, 0.0); + } + tex.transform.update(); + + this.uniforms.mask = tex; + this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite) + .prepend(tex.transform.mapCoord); + this.uniforms.alpha = maskSprite.worldAlpha; + this.uniforms.maskClamp = tex.transform.uClampFrame; + + filterManager.applyFilter(this, input, output); + } +} diff --git a/packages/core/src/filters/spriteMask/spriteMaskFilter.frag b/packages/core/src/filters/spriteMask/spriteMaskFilter.frag new file mode 100644 index 0000000..0e4aef8 --- /dev/null +++ b/packages/core/src/filters/spriteMask/spriteMaskFilter.frag @@ -0,0 +1,23 @@ +varying vec2 vMaskCoord; +varying vec2 vTextureCoord; + +uniform sampler2D uSampler; +uniform sampler2D mask; +uniform float alpha; +uniform vec4 maskClamp; + +void main(void) +{ + float clip = step(3.5, + step(maskClamp.x, vMaskCoord.x) + + step(maskClamp.y, vMaskCoord.y) + + step(vMaskCoord.x, maskClamp.z) + + step(vMaskCoord.y, maskClamp.w)); + + vec4 original = texture2D(uSampler, vTextureCoord); + vec4 masky = texture2D(mask, vMaskCoord); + + original *= (masky.r * masky.a * alpha * clip); + + gl_FragColor = original; +} diff --git a/packages/core/src/filters/spriteMask/spriteMaskFilter.vert b/packages/core/src/filters/spriteMask/spriteMaskFilter.vert new file mode 100644 index 0000000..d23a30c --- /dev/null +++ b/packages/core/src/filters/spriteMask/spriteMaskFilter.vert @@ -0,0 +1,16 @@ +attribute vec2 aVertexPosition; +attribute vec2 aTextureCoord; + +uniform mat3 projectionMatrix; +uniform mat3 otherMatrix; + +varying vec2 vMaskCoord; +varying vec2 vTextureCoord; + +void main(void) +{ + gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); + + vTextureCoord = aTextureCoord; + vMaskCoord = ( otherMatrix * vec3( aTextureCoord, 1.0) ).xy; +} diff --git a/packages/core/src/framebuffer/FrameBuffer.js b/packages/core/src/framebuffer/FrameBuffer.js new file mode 100644 index 0000000..84e9fcc --- /dev/null +++ b/packages/core/src/framebuffer/FrameBuffer.js @@ -0,0 +1,106 @@ +import Texture from '../textures/BaseTexture'; +import { FORMATS, TYPES } from '@pixi/constants'; + +/** + * Frame buffer + * @class + * @memberof PIXI + */ +export default class FrameBuffer +{ + constructor(width, height) + { + this.width = width || 100; + this.height = height || 100; + + this.stencil = false; + this.depth = false; + + this.dirtyId = 0; + this.dirtyFormat = 0; + this.dirtySize = 0; + + this.depthTexture = null; + this.colorTextures = []; + + this.glFrameBuffers = {}; + } + + get colorTexture() + { + return this.colorTextures[0]; + } + + addColorTexture(index, texture) + { + // TODO add some validation to the texture - same width / height etc? + this.colorTextures[index || 0] = texture || new Texture(null, { scaleMode: 0, + resolution: 1, + mipmap: false, + width: this.width, + height: this.height });// || new Texture(); + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + addDepthTexture(texture) + { + /* eslint-disable max-len */ + this.depthTexture = texture || new Texture(null, { scaleMode: 0, + resolution: 1, + width: this.width, + height: this.height, + mipmap: false, + format: FORMATS.DEPTH_COMPONENT, + type: TYPES.UNSIGNED_SHORT });// UNSIGNED_SHORT; + /* eslint-disable max-len */ + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + enableDepth() + { + this.depth = true; + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + enableStencil() + { + this.stencil = true; + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + resize(width, height) + { + if (width === this.width && height === this.height) return; + + this.width = width; + this.height = height; + + this.dirtyId++; + this.dirtySize++; + + for (let i = 0; i < this.colorTextures.length; i++) + { + this.colorTextures[i].setSize(width, height); + } + + if (this.depthTexture) + { + this.depthTexture.setSize(width, height); + } + } +} diff --git a/packages/core/src/framebuffer/FramebufferSystem.js b/packages/core/src/framebuffer/FramebufferSystem.js new file mode 100644 index 0000000..2cc2437 --- /dev/null +++ b/packages/core/src/framebuffer/FramebufferSystem.js @@ -0,0 +1,246 @@ +import System from '../System'; +import { Rectangle } from '@pixi/math'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class FramebufferSystem extends System +{ + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + contextChange() + { + this.gl = this.renderer.gl; + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + this.current = null; + this.viewport = new Rectangle(); + + this.drawBufferExtension = this.renderer.context.extensions.drawBuffers; + } + + bind(framebuffer, frame) + { + const gl = this.gl; + + this.current = framebuffer; + + if (framebuffer) + { + // TODO cacheing layer! + + const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID] || this.initFramebuffer(framebuffer); + + gl.bindFramebuffer(gl.FRAMEBUFFER, fbo.framebuffer); + // makesure all textures are unbound.. + + // now check for updates... + if (fbo.dirtyId !== framebuffer.dirtyId) + { + fbo.dirtyId = framebuffer.dirtyId; + + if (fbo.dirtyFormat !== framebuffer.dirtyFormat) + { + fbo.dirtyFormat = framebuffer.dirtyFormat; + this.updateFramebuffer(framebuffer); + } + else if (fbo.dirtySize !== framebuffer.dirtySize) + { + fbo.dirtySize = framebuffer.dirtySize; + this.resizeFramebuffer(framebuffer); + } + } + + for (let i = 0; i < framebuffer.colorTextures.length; i++) + { + if (framebuffer.colorTextures[i].texturePart) + { + this.renderer.texture.unbind(framebuffer.colorTextures[i].texture); + } + else + { + this.renderer.texture.unbind(framebuffer.colorTextures[i]); + } + } + + if (framebuffer.depthTexture) + { + this.renderer.texture.unbind(framebuffer.depthTexture); + } + + if (frame) + { + this.setViewport(frame.x, frame.y, frame.width, frame.height); + } + else + { + this.setViewport(0, 0, framebuffer.width, framebuffer.height); + } + } + else + { + gl.bindFramebuffer(gl.FRAMEBUFFER, null); + + if (frame) + { + this.setViewport(frame.x, frame.y, frame.width, frame.height); + } + else + { + this.setViewport(0, 0, this.renderer.width, this.renderer.height); + } + } + } + + setViewport(x, y, width, height) + { + const v = this.viewport; + + if (v.width !== width || v.height !== height || v.x !== x || v.y !== y) + { + v.x = x; + v.y = y; + v.width = width; + v.height = height; + + this.gl.viewport(x, y, width, height); + } + } + + get size() + { + if (this.current) + { + // TODO store temp + return { x: 0, y: 0, width: this.current.width, height: this.current.height }; + } + + return { x: 0, y: 0, width: this.renderer.width, height: this.renderer.height }; + } + + clear(r, g, b, a) + { + const gl = this.gl; + + // TODO clear color can be set only one right? + gl.clearColor(r, g, b, a); + gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); + } + + // private functions... + + initFramebuffer(framebuffer) + { + const gl = this.gl; + + // TODO - make this a class? + const fbo = { + framebuffer: gl.createFramebuffer(), + stencil: null, + dirtyId: 0, + dirtyFormat: 0, + dirtySize: 0, + }; + + framebuffer.glFrameBuffers[this.CONTEXT_UID] = fbo; + + return fbo; + } + + resizeFramebuffer(framebuffer) + { + const gl = this.gl; + + if (framebuffer.stencil || framebuffer.depth) + { + gl.bindRenderbuffer(gl.RENDERBUFFER, this.stencil); + gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); + } + } + + updateFramebuffer(framebuffer) + { + const gl = this.gl; + + const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID]; + + // bind the color texture + const colorTextures = framebuffer.colorTextures; + + let count = colorTextures.length; + + if (!this.drawBufferExtension) + { + count = Math.min(count, 1); + } + + const activeTextures = []; + + for (let i = 0; i < count; i++) + { + const texture = framebuffer.colorTextures[i]; + + if (texture.texturePart) + { + this.renderer.texture.bind(texture.texture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.COLOR_ATTACHMENT0 + i, + gl.TEXTURE_CUBE_MAP_NEGATIVE_X + texture.side, + texture.texture._glTextures[this.CONTEXT_UID].texture, + 0); + } + else + { + this.renderer.texture.bind(texture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.COLOR_ATTACHMENT0 + i, + gl.TEXTURE_2D, + texture._glTextures[this.CONTEXT_UID].texture, + 0); + } + + activeTextures.push(gl.COLOR_ATTACHMENT0 + i); + } + + if (this.drawBufferExtension && activeTextures.length > 1) + { + this.drawBufferExtension.drawBuffersWEBGL(activeTextures); + } + + if (framebuffer.depthTexture) + { + const depthTextureExt = this.renderer.context.extensions.depthTexture; + + if (depthTextureExt) + { + const depthTexture = framebuffer.depthTexture; + + this.renderer.texture.bind(depthTexture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.DEPTH_ATTACHMENT, + gl.TEXTURE_2D, + depthTexture._glTextures[this.CONTEXT_UID].texture, + 0); + } + } + + if (framebuffer.stencil || framebuffer.depth) + { + fbo.stencil = gl.createRenderbuffer(); + + gl.bindRenderbuffer(gl.RENDERBUFFER, fbo.stencil); + + // TODO.. this is depth AND stencil? + gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, fbo.stencil); + gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); + // fbo.enableStencil(); + } + } +} diff --git a/packages/core/src/geometry/GLBuffer.js b/packages/core/src/geometry/GLBuffer.js new file mode 100644 index 0000000..0477e8a --- /dev/null +++ b/packages/core/src/geometry/GLBuffer.js @@ -0,0 +1,9 @@ +export default class GLBuffer +{ + constructor(buffer) + { + this.buffer = buffer; + this.updateID = -1; + this.byteLength = -1; + } +} diff --git a/packages/core/src/geometry/GeometrySystem.js b/packages/core/src/geometry/GeometrySystem.js new file mode 100644 index 0000000..959f827 --- /dev/null +++ b/packages/core/src/geometry/GeometrySystem.js @@ -0,0 +1,388 @@ +import System from '../System'; +import GLBuffer from './GLBuffer'; + +const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class GeometrySystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this._activeGeometry = null; + this._activeVao = null; + + this.hasVao = true; + this.hasInstance = true; + } + + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + contextChange() + { + const gl = this.gl = this.renderer.gl; + + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + + // webgl2 + if (!gl.createVertexArray) + { + // webgl 1! + let nativeVaoExtension = this.renderer.context.extensions.vertexArrayObject; + + if (this.renderer.legacy) + { + nativeVaoExtension = null; + } + + if (nativeVaoExtension) + { + gl.createVertexArray = () => + nativeVaoExtension.createVertexArrayOES(); + + gl.bindVertexArray = (vao) => + nativeVaoExtension.bindVertexArrayOES(vao); + + gl.deleteVertexArray = (vao) => + nativeVaoExtension.deleteVertexArrayOES(vao); + } + else + { + this.hasVao = false; + gl.createVertexArray = () => + { + // empty + }; + + gl.bindVertexArray = () => + { + // empty + }; + + gl.deleteVertexArray = () => + { + // empty + }; + } + } + + if (!gl.vertexAttribDivisor) + { + const instanceExt = gl.getExtension('ANGLE_instanced_arrays'); + + if (instanceExt) + { + gl.vertexAttribDivisor = (a, b) => + instanceExt.vertexAttribDivisorANGLE(a, b); + + gl.drawElementsInstanced = (a, b, c, d, e) => + instanceExt.drawElementsInstancedANGLE(a, b, c, d, e); + + gl.drawArraysInstanced = (a, b, c, d) => + instanceExt.drawArraysInstancedANGLE(a, b, c, d); + } + else + { + this.hasInstance = false; + } + } + } + + /** + * Binds geometry so that is can be drawn. Creating a Vao if required + * @private + * @param {PIXI.Geometry} geometry instance of geometry to bind + */ + bind(geometry, shader) + { + shader = shader || this.renderer.shader.shader; + + const gl = this.gl; + + // not sure the best way to address this.. + // currently different shaders require different VAOs for the same geometry + // Still mulling over the best way to solve this one.. + // will likely need to modify the shader attribute locations at run time! + let vaos = geometry.glVertexArrayObjects[this.CONTEXT_UID]; + + if (!vaos) + { + geometry.glVertexArrayObjects[this.CONTEXT_UID] = vaos = {}; + } + + const vao = vaos[shader.program.id] || this.initGeometryVao(geometry, shader.program); + + this._activeGeometry = geometry; + + if (this._activeVao !== vao) + { + this._activeVao = vao; + + if (this.hasVao) + { + gl.bindVertexArray(vao); + } + else + { + this.activateVao(geometry, shader.program); + } + } + + // TODO - optimise later! + // don't need to loop through if nothing changed! + // maybe look to add an 'autoupdate' to geometry? + this.updateBuffers(); + } + + reset() + { + this.unbind(); + } + + updateBuffers() + { + const geometry = this._activeGeometry; + const gl = this.gl; + + for (let i = 0; i < geometry.buffers.length; i++) + { + const buffer = geometry.buffers[i]; + + const glBuffer = buffer._glBuffers[this.CONTEXT_UID]; + + if (buffer._updateID !== glBuffer.updateID) + { + glBuffer.updateID = buffer._updateID; + + // TODO can cache this on buffer! maybe added a getter / setter? + const type = buffer.index ? gl.ELEMENT_ARRAY_BUFFER : gl.ARRAY_BUFFER; + const drawType = buffer.static ? gl.STATIC_DRAW : gl.DYNAMIC_DRAW; + + gl.bindBuffer(type, glBuffer.buffer); + + if (glBuffer.byteLength >= buffer.data.byteLength) + { + // offset is always zero for now! + gl.bufferSubData(type, 0, buffer.data); + } + else + { + gl.bufferData(type, buffer.data, drawType); + } + } + } + } + + checkCompatability(geometry, program) + { + // geometry must have at least all the attributes that the shader requires. + const geometryAttributes = geometry.attributes; + const shaderAttributes = program.attributeData; + + for (const j in shaderAttributes) + { + if (!geometryAttributes[j]) + { + throw new Error(`shader and geometry incompatible, geometry missing the "${j}" attribute`); + } + } + } + + /** + * Creates a Vao with the same structure as the geometry and stores it on the geometry. + * @private + * @param {PIXI.Geometry} geometry instance of geometry to to generate Vao for + */ + initGeometryVao(geometry, program) + { + this.checkCompatability(geometry, program); + + const gl = this.gl; + const CONTEXT_UID = this.CONTEXT_UID; + const buffers = geometry.buffers; + const attributes = geometry.attributes; + + const tempStride = {}; + const tempStart = {}; + + for (const j in buffers) + { + tempStride[j] = 0; + tempStart[j] = 0; + } + + for (const j in attributes) + { + if (!attributes[j].size && program.attributeData[j]) + { + attributes[j].size = program.attributeData[j].size; + } + + tempStride[attributes[j].buffer] += attributes[j].size * byteSizeMap[attributes[j].type]; + } + + for (const j in attributes) + { + const attribute = attributes[j]; + const attribSize = attribute.size; + + if (attribute.stride === undefined) + { + if (tempStride[attribute.buffer] === attribSize * byteSizeMap[attribute.type]) + { + attribute.stride = 0; + } + else + { + attribute.stride = tempStride[attribute.buffer]; + } + } + + if (attribute.start === undefined) + { + attribute.start = tempStart[attribute.buffer]; + + tempStart[attribute.buffer] += attribSize * byteSizeMap[attribute.type]; + } + } + + // first update - and create the buffers! + // only create a gl buffer if it actually gets + for (let i = 0; i < buffers.length; i++) + { + const buffer = buffers[i]; + + if (!buffer._glBuffers[CONTEXT_UID]) + { + buffer._glBuffers[CONTEXT_UID] = new GLBuffer(gl.createBuffer()); + } + } + + // TODO - maybe make this a data object? + // lets wait to see if we need to first! + const vao = gl.createVertexArray(); + + gl.bindVertexArray(vao); + + this.activateVao(geometry, program); + + gl.bindVertexArray(null); + + // add it to the cache! + geometry.glVertexArrayObjects[this.CONTEXT_UID][program.id] = vao; + + return vao; + } + + activateVao(geometry, program) + { + const gl = this.gl; + const CONTEXT_UID = this.CONTEXT_UID; + const buffers = geometry.buffers; + const attributes = geometry.attributes; + + if (geometry.indexBuffer) + { + // first update the index buffer if we have one.. + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, geometry.indexBuffer._glBuffers[CONTEXT_UID].buffer); + } + + let lastBuffer = null; + + // add a new one! + for (const j in attributes) + { + const attribute = attributes[j]; + const buffer = buffers[attribute.buffer]; + const glBuffer = buffer._glBuffers[CONTEXT_UID]; + + if (program.attributeData[j]) + { + if (lastBuffer !== glBuffer) + { + gl.bindBuffer(gl.ARRAY_BUFFER, glBuffer.buffer); + + lastBuffer = glBuffer; + } + + const location = program.attributeData[j].location; + + // TODO introduce state again + // we can optimise this for older devices that have no VAOs + gl.enableVertexAttribArray(location); + + gl.vertexAttribPointer(location, + attribute.size, + attribute.type || gl.FLOAT, + attribute.normalized, + attribute.stride, + attribute.start); + + if (attribute.instance) + { + // TODO calculate instance count based of this... + if (this.hasInstance) + { + gl.vertexAttribDivisor(location, 1); + } + else + { + throw new Error('geometry error, GPU Instancing is not supported on this device'); + } + } + } + } + } + + draw(type, size, start, instanceCount) + { + const gl = this.gl; + const geometry = this._activeGeometry; + + // TODO.. this should not change so maybe cache the function? + + if (geometry.indexBuffer) + { + if (geometry.instanced) + { + /* eslint-disable max-len */ + gl.drawElementsInstanced(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2, instanceCount || 1); + /* eslint-enable max-len */ + } + else + { + gl.drawElements(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2); + } + } + else + if (geometry.instanced) + { + // TODO need a better way to calculate size.. + gl.drawArraysInstanced(type, start, size || geometry.getSize(), instanceCount || 1); + } + else + { + gl.drawArrays(type, start, size || geometry.getSize()); + } + + return this; + } + + unbind() + { + this.gl.bindVertexArray(null); + this._activeVao = null; + this._activeGeometry = null; + } +} diff --git a/packages/core/src/geometry/utils/setVertexAttribArrays.js b/packages/core/src/geometry/utils/setVertexAttribArrays.js new file mode 100644 index 0000000..23b6959 --- /dev/null +++ b/packages/core/src/geometry/utils/setVertexAttribArrays.js @@ -0,0 +1,54 @@ +// var GL_MAP = {}; + +/** + * @param gl {WebGLRenderingContext} The current WebGL context + * @param attribs {*} + * @param state {*} + */ +export default function setVertexAttribArrays(gl, attribs, state) +{ + let i; + + if (state) + { + const tempAttribState = state.tempAttribState; + const attribState = state.attribState; + + for (i = 0; i < tempAttribState.length; i++) + { + tempAttribState[i] = false; + } + + // set the new attribs + for (i = 0; i < attribs.length; i++) + { + tempAttribState[attribs[i].attribute.location] = true; + } + + for (i = 0; i < attribState.length; i++) + { + if (attribState[i] !== tempAttribState[i]) + { + attribState[i] = tempAttribState[i]; + + if (state.attribState[i]) + { + gl.enableVertexAttribArray(i); + } + else + { + gl.disableVertexAttribArray(i); + } + } + } + } + else + { + for (i = 0; i < attribs.length; i++) + { + const attrib = attribs[i]; + + gl.enableVertexAttribArray(attrib.attribute.location); + } + } +} diff --git a/packages/core/src/index.js b/packages/core/src/index.js index 58c456a..fd1f547 100644 --- a/packages/core/src/index.js +++ b/packages/core/src/index.js @@ -1,30 +1,30 @@ import * as resources from './textures/resources'; -import * as systems from './renderers/systems'; +import * as systems from './systems'; -export { resources }; export { systems }; +export { resources }; -export { default as Renderer } from './renderers/Renderer'; -export { default as AbstractRenderer } from './renderers/AbstractRenderer'; -export { default as FrameBuffer } from './textures/FrameBuffer'; +export { default as System } from './System'; +export { default as Renderer } from './Renderer'; +export { default as AbstractRenderer } from './AbstractRenderer'; +export { default as FrameBuffer } from './framebuffer/FrameBuffer'; export { default as CubeTexture } from './textures/CubeTexture'; export { default as BaseTexture } from './textures/BaseTexture'; export { default as Texture } from './textures/Texture'; export { default as TextureMatrix } from './textures/TextureMatrix'; -export { default as RenderTexture } from './textures/RenderTexture'; -export { default as BaseRenderTexture } from './textures/BaseRenderTexture'; +export { default as RenderTexture } from './renderTexture/RenderTexture'; +export { default as BaseRenderTexture } from './renderTexture/BaseRenderTexture'; export { default as TextureUvs } from './textures/TextureUvs'; -export { default as State } from './renderers/State'; -export { default as ObjectRenderer } from './renderers/utils/ObjectRenderer'; -export { default as Quad } from './renderers/utils/Quad'; -export { default as QuadUv } from './renderers/utils/QuadUv'; -export { default as checkMaxIfStatmentsInShader } from './renderers/utils/checkMaxIfStatmentsInShader'; +export { default as State } from './state/State'; +export { default as ObjectRenderer } from './batch/ObjectRenderer'; +export { default as Quad } from './utils/Quad'; +export { default as QuadUv } from './utils/QuadUv'; +export { default as checkMaxIfStatmentsInShader } from './shader/utils/checkMaxIfStatmentsInShader'; export { default as Shader } from './shader/Shader'; export { default as Program } from './shader/Program'; export { default as UniformGroup } from './shader/UniformGroup'; -export { default as SpriteMaskFilter } from './renderers/filters/spriteMask/SpriteMaskFilter'; -export { default as Filter } from './renderers/filters/Filter'; +export { default as SpriteMaskFilter } from './filters/spriteMask/SpriteMaskFilter'; +export { default as Filter } from './filters/Filter'; export { default as Attribute } from './geometry/Attribute'; export { default as Buffer } from './geometry/Buffer'; export { default as Geometry } from './geometry/Geometry'; - diff --git a/packages/core/src/mask/MaskSystem.js b/packages/core/src/mask/MaskSystem.js new file mode 100644 index 0000000..99174c2 --- /dev/null +++ b/packages/core/src/mask/MaskSystem.js @@ -0,0 +1,199 @@ +import System from '../System'; +import AlphaMaskFilter from '../filters/spriteMask/SpriteMaskFilter'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class MaskSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + // TODO - we don't need both! + this.scissor = false; + this.scissorData = null; + this.scissorRenderTarget = null; + + this.enableScissor = false; + + this.alphaMaskPool = []; + this.alphaMaskIndex = 0; + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.DisplayObject} target - Display Object to push the mask to + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + push(target, maskData) + { + // TODO the root check means scissor rect will not + // be used on render textures more info here: + // https://github.com/pixijs/pixi.js/pull/3545 + + if (maskData.texture) + { + this.pushSpriteMask(target, maskData); + } + else if (this.enableScissor + && !this.scissor + && this.renderer._activeRenderTarget.root + && !this.renderer.stencil.stencilMaskStack.length + && maskData.isFastRect()) + { + const matrix = maskData.worldTransform; + + let rot = Math.atan2(matrix.b, matrix.a); + + // use the nearest degree! + rot = Math.round(rot * (180 / Math.PI)); + + if (rot % 90) + { + this.pushStencilMask(maskData); + } + else + { + this.pushScissorMask(target, maskData); + } + } + else + { + this.pushStencilMask(maskData); + } + } + + /** + * Removes the last mask from the mask stack and doesn't return it. + * + * @param {PIXI.DisplayObject} target - Display Object to pop the mask from + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + pop(target, maskData) + { + if (maskData.texture) + { + this.popSpriteMask(target, maskData); + } + else if (this.enableScissor && !this.renderer.stencil.stencilMaskStack.length) + { + this.popScissorMask(target, maskData); + } + else + { + this.popStencilMask(target, maskData); + } + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.RenderTarget} target - Display Object to push the sprite mask to + * @param {PIXI.Sprite} maskData - Sprite to be used as the mask + */ + pushSpriteMask(target, maskData) + { + let alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; + + if (!alphaMaskFilter) + { + alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; + } + + alphaMaskFilter[0].resolution = this.renderer.resolution; + alphaMaskFilter[0].maskSprite = maskData; + + // TODO - may cause issues! + target.filterArea = maskData.getBounds(true); + + this.renderer.filterSystem.pushFilter(target, alphaMaskFilter); + + this.alphaMaskIndex++; + } + + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popSpriteMask() + { + this.renderer.filterSystem.popFilter(); + this.alphaMaskIndex--; + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + pushStencilMask(maskData) + { + this.renderer.batch.flush(); + this.renderer.stencil.pushStencil(maskData); + } + + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popStencilMask() + { + // this.renderer.currentRenderer.stop(); + this.renderer.stencil.popStencil(); + } + + /** + * + * @param {PIXI.DisplayObject} target - Display Object to push the mask to + * @param {PIXI.Graphics} maskData - The masking data. + */ + pushScissorMask(target, maskData) + { + maskData.renderable = true; + + const renderTarget = this.renderer._activeRenderTarget; + + const bounds = maskData.getBounds(); + + bounds.fit(renderTarget.size); + maskData.renderable = false; + + this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); + + const resolution = this.renderer.resolution; + + this.renderer.gl.scissor( + bounds.x * resolution, + (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, + bounds.width * resolution, + bounds.height * resolution + ); + + this.scissorRenderTarget = renderTarget; + this.scissorData = maskData; + this.scissor = true; + } + + /** + * + * + */ + popScissorMask() + { + this.scissorRenderTarget = null; + this.scissorData = null; + this.scissor = false; + + // must be scissor! + const gl = this.renderer.gl; + + gl.disable(gl.SCISSOR_TEST); + } +} diff --git a/packages/core/src/mask/StencilSystem.js b/packages/core/src/mask/StencilSystem.js new file mode 100644 index 0000000..62796a5 --- /dev/null +++ b/packages/core/src/mask/StencilSystem.js @@ -0,0 +1,133 @@ +import System from '../System'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class StencilSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + this.stencilMaskStack = []; + } + + /** + * Changes the mask stack that is used by this System. + * + * @param {PIXI.Graphics[]} stencilMaskStack - The mask stack + */ + setMaskStack(stencilMaskStack) + { + const gl = this.renderer.gl; + + if (stencilMaskStack.length !== this.stencilMaskStack.length) + { + if (stencilMaskStack.length === 0) + { + gl.disable(gl.STENCIL_TEST); + } + else + { + gl.enable(gl.STENCIL_TEST); + } + } + + this.stencilMaskStack = stencilMaskStack; + } + + /** + * Applies the Mask and adds it to the current stencil stack. @alvin + * + * @param {PIXI.Graphics} graphics - The mask + */ + pushStencil(graphics) + { + this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); + + // this.renderer._activeRenderTarget.attachStencilBuffer(); + + const gl = this.renderer.gl; + const prevMaskCount = this.stencilMaskStack.length; + + if (prevMaskCount === 0) + { + gl.enable(gl.STENCIL_TEST); + } + + this.stencilMaskStack.push(graphics); + + // Increment the refference stencil value where the new mask overlaps with the old ones. + gl.colorMask(false, false, false, false); + gl.stencilFunc(gl.EQUAL, prevMaskCount, this._getBitwiseMask()); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.INCR); + this.renderer.plugins.graphics.render(graphics); + + this._useCurrent(); + } + + /** + * Removes the last mask from the stencil stack. @alvin + */ + popStencil() + { + this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); + + const gl = this.renderer.gl; + const graphics = this.stencilMaskStack.pop(); + + if (this.stencilMaskStack.length === 0) + { + // the stack is empty! + gl.disable(gl.STENCIL_TEST); + gl.clear(gl.STENCIL_BUFFER_BIT); + gl.clearStencil(0); + } + else + { + // Decrement the refference stencil value where the popped mask overlaps with the other ones + gl.colorMask(false, false, false, false); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.DECR); + this.renderer.plugins.graphics.render(graphics); + + this._useCurrent(); + } + } + + /** + * Setup renderer to use the current stencil data. + */ + _useCurrent() + { + const gl = this.renderer.gl; + + gl.colorMask(true, true, true, true); + gl.stencilFunc(gl.EQUAL, this.stencilMaskStack.length, this._getBitwiseMask()); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP); + } + + /** + * Fill 1s equal to the number of acitve stencil masks. + * + * @return {number} The bitwise mask. + */ + _getBitwiseMask() + { + return (1 << this.stencilMaskStack.length) - 1; + } + + /** + * Destroys the mask stack. + * + */ + destroy() + { + super.destroy(this); + + this.stencilMaskStack = null; + } +} diff --git a/packages/core/src/projection/ProjectionSystem.js b/packages/core/src/projection/ProjectionSystem.js new file mode 100644 index 0000000..6e63b73 --- /dev/null +++ b/packages/core/src/projection/ProjectionSystem.js @@ -0,0 +1,73 @@ +import System from '../System'; +import { Matrix } from '@pixi/math'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class ProjectionSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.projectionMatrix = new Matrix(); + } + + update(destinationFrame, sourceFrame, resolution, root) + { + this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; + this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; + + this.calculateProjection(this.destinationFrame, this.sourceFrame, resolution, root); + + this.renderer.globalUniforms.uniforms.projectionMatrix = this.projectionMatrix; + this.renderer.globalUniforms.update(); + } + + /** + * Updates the projection matrix based on a projection frame (which is a rectangle) + * + * @param {Rectangle} destinationFrame - The destination frame. + * @param {Rectangle} sourceFrame - The source frame. + */ + calculateProjection(destinationFrame, sourceFrame, resolution, root) + { + const pm = this.projectionMatrix; + + // I don't think we will need this line.. + // pm.identity(); + + if (!root) + { + pm.a = (1 / destinationFrame.width * 2) * resolution; + pm.d = (1 / destinationFrame.height * 2) * resolution; + + pm.tx = -1 - (sourceFrame.x * pm.a); + pm.ty = -1 - (sourceFrame.y * pm.d); + } + else + { + pm.a = (1 / destinationFrame.width * 2) * resolution; + pm.d = (-1 / destinationFrame.height * 2) * resolution; + + pm.tx = -1 - (sourceFrame.x * pm.a); + pm.ty = 1 - (sourceFrame.y * pm.d); + } + } + + /** + * Sets the transform of the active render target to the given matrix + * + * @param {PIXI.Matrix} matrix - The transformation matrix + */ + setTransform()// matrix) + { + // this._activeRenderTarget.transform = matrix; + } +} diff --git a/packages/core/src/renderTexture/BaseRenderTexture.js b/packages/core/src/renderTexture/BaseRenderTexture.js new file mode 100644 index 0000000..88eb83b --- /dev/null +++ b/packages/core/src/renderTexture/BaseRenderTexture.js @@ -0,0 +1,137 @@ +import BaseTexture from '../textures/BaseTexture'; +import FrameBuffer from '../framebuffer/FrameBuffer'; + +/** + * A BaseRenderTexture is a special texture that allows any PixiJS display object to be rendered to it. + * + * __Hint__: All DisplayObjects (i.e. Sprites) that render to a BaseRenderTexture should be preloaded + * otherwise black rectangles will be drawn instead. + * + * A BaseRenderTexture takes a snapshot of any Display Object given to its render method. The position + * and rotation of the given Display Objects is ignored. For example: + * + * ```js + * let renderer = PIXI.autoDetectRenderer(1024, 1024, { view: canvas, ratio: 1 }); + * let baseRenderTexture = new PIXI.BaseRenderTexture(renderer, 800, 600); + * let sprite = PIXI.Sprite.fromImage("spinObj_01.png"); + * + * sprite.position.x = 800/2; + * sprite.position.y = 600/2; + * sprite.anchor.x = 0.5; + * sprite.anchor.y = 0.5; + * + * baseRenderTexture.render(sprite); + * ``` + * + * The Sprite in this case will be rendered using its local transform. To render this sprite at 0,0 + * you can clear the transform + * + * ```js + * + * sprite.setTransform() + * + * let baseRenderTexture = new PIXI.BaseRenderTexture(100, 100); + * let renderTexture = new PIXI.RenderTexture(baseRenderTexture); + * + * renderer.render(sprite, renderTexture); // Renders to center of RenderTexture + * ``` + * + * @class + * @extends PIXI.BaseTexture + * @memberof PIXI + */ +export default class BaseRenderTexture extends BaseTexture +{ + /** + * @param {object} [options] + * @param {number} [options.width=100] - The width of the base render texture + * @param {number} [options.height=100] - The height of the base render texture + * @param {PIXI.SCALE_MODES} [options.scaleMode] - See {@link PIXI.SCALE_MODES} for possible values + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the texture being generated + */ + constructor(options) + { + if (typeof options === 'number') + { + /* eslint-disable prefer-rest-params */ + // Backward compatibility of signature + const width = arguments[0]; + const height = arguments[1]; + const scaleMode = arguments[2]; + const resolution = arguments[3]; + + options = { width, height, scaleMode, resolution }; + /* eslint-enable prefer-rest-params */ + } + + super(null, options); + + const { width, height } = options || {}; + + // Set defaults + this.mipmap = false; + this.width = Math.ceil(width) || 100; + this.height = Math.ceil(height) || 100; + this.valid = true; + + /** + * A map of renderer IDs to webgl renderTargets + * + * @private + * @member {object} + */ + // this._glRenderTargets = {}; + + /** + * A reference to the canvas render target (we only need one as this can be shared across renderers) + * + * @private + * @member {object} + */ + this._canvasRenderTarget = null; + + this.clearColor = [0, 0, 0, 0]; + + this.frameBuffer = new FrameBuffer(width * this.resolution, height * this.resolution) + .addColorTexture(0, this); + + // TODO - could this be added the systems? + + /** + * The data structure for the stencil masks + * + * @member {PIXI.Graphics[]} + */ + this.stencilMaskStack = []; + + /** + * The data structure for the filters + * + * @member {PIXI.Graphics[]} + */ + this.filterStack = [{}]; + } + + /** + * Resizes the BaseRenderTexture. + * + * @param {number} width - The width to resize to. + * @param {number} height - The height to resize to. + */ + resize(width, height) + { + width = Math.ceil(width); + height = Math.ceil(height); + this.frameBuffer.resize(width * this.resolution, height * this.resolution); + } + + /** + * Destroys this texture + * + */ + destroy() + { + super.destroy(true); + this.renderer = null; + } +} diff --git a/packages/core/src/renderTexture/RenderTexture.js b/packages/core/src/renderTexture/RenderTexture.js new file mode 100644 index 0000000..f1ecf1f --- /dev/null +++ b/packages/core/src/renderTexture/RenderTexture.js @@ -0,0 +1,153 @@ +import BaseRenderTexture from './BaseRenderTexture'; +import Texture from '../textures/Texture'; + +/** + * A RenderTexture is a special texture that allows any PixiJS display object to be rendered to it. + * + * __Hint__: All DisplayObjects (i.e. Sprites) that render to a RenderTexture should be preloaded + * otherwise black rectangles will be drawn instead. + * + * A RenderTexture takes a snapshot of any Display Object given to its render method. For example: + * + * ```js + * let renderer = PIXI.autoDetectRenderer(1024, 1024, { view: canvas, ratio: 1 }); + * let renderTexture = PIXI.RenderTexture.create(800, 600); + * let sprite = PIXI.Sprite.fromImage("spinObj_01.png"); + * + * sprite.position.x = 800/2; + * sprite.position.y = 600/2; + * sprite.anchor.x = 0.5; + * sprite.anchor.y = 0.5; + * + * renderer.render(sprite, renderTexture); + * ``` + * + * The Sprite in this case will be rendered using its local transform. To render this sprite at 0,0 + * you can clear the transform + * + * ```js + * + * sprite.setTransform() + * + * let renderTexture = new PIXI.RenderTexture.create(100, 100); + * + * renderer.render(sprite, renderTexture); // Renders to center of RenderTexture + * ``` + * + * @class + * @extends PIXI.Texture + * @memberof PIXI + */ +export default class RenderTexture extends Texture +{ + /** + * @param {PIXI.BaseRenderTexture} baseRenderTexture - The renderer used for this RenderTexture + * @param {PIXI.Rectangle} [frame] - The rectangle frame of the texture to show + */ + constructor(baseRenderTexture, frame) + { + // support for legacy.. + let _legacyRenderer = null; + + if (!(baseRenderTexture instanceof BaseRenderTexture)) + { + /* eslint-disable prefer-rest-params, no-console */ + const width = arguments[1]; + const height = arguments[2]; + const scaleMode = arguments[3]; + const resolution = arguments[4]; + + // we have an old render texture.. + console.warn(`Please use RenderTexture.create(${width}, ${height}) instead of the ctor directly.`); + _legacyRenderer = arguments[0]; + /* eslint-enable prefer-rest-params, no-console */ + + frame = null; + baseRenderTexture = new BaseRenderTexture({ + width, + height, + scaleMode, + resolution, + }); + } + + /** + * The base texture object that this texture uses + * + * @member {BaseTexture} + */ + super(baseRenderTexture, frame); + + this.legacyRenderer = _legacyRenderer; + + /** + * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. + * + * @member {boolean} + */ + this.valid = true; + + /** + * FilterSystem temporary storage + * @private + * @member {PIXI.Rectangle} + */ + this.filterFrame = null; + + this._updateUvs(); + } + + /** + * Resizes the RenderTexture. + * + * @param {number} width - The width to resize to. + * @param {number} height - The height to resize to. + * @param {boolean} [resizeBaseTexture=true] - Should the baseTexture.width and height values be resized as well? + */ + resize(width, height, resizeBaseTexture = true) + { + width = Math.ceil(width); + height = Math.ceil(height); + + // TODO - could be not required.. + this.valid = (width > 0 && height > 0); + + this._frame.width = this.orig.width = width; + this._frame.height = this.orig.height = height; + + if (resizeBaseTexture) + { + this.baseTexture.resize(width, height); + } + + this._updateUvs(); + } + + /** + * A short hand way of creating a render texture. + * + * @param {object} [options] - Options + * @param {number} [options.width=100] - The width of the render texture + * @param {number} [options.height=100] - The height of the render texture + * @param {number} [options.scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the texture being generated + * @return {PIXI.RenderTexture} The new render texture + */ + static create(options) + { + // fallback, old-style: create(width, height, scaleMode, resolution) + if (typeof options === 'number') + { + /* eslint-disable prefer-rest-params */ + options = { + width: options, + height: arguments[1], + scaleMode: arguments[2], + resolution: arguments[3], + }; + /* eslint-enable prefer-rest-params */ + } + + return new RenderTexture(new BaseRenderTexture(options)); + } +} diff --git a/packages/core/src/renderTexture/RenderTextureSystem.js b/packages/core/src/renderTexture/RenderTextureSystem.js new file mode 100644 index 0000000..8d0ca61 --- /dev/null +++ b/packages/core/src/renderTexture/RenderTextureSystem.js @@ -0,0 +1,115 @@ +import System from '../System'; +import { Rectangle } from '@pixi/math'; + +const tempRect = new Rectangle(); + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class RenderTextureSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.clearColor = renderer._backgroundColorRgba; + + // TODO moe this property somewhere else! + this.defaultMaskStack = []; + this.defaultFilterStack = [{}]; + + // empty render texture? + this.renderTexture = null; + + this.destinationFrame = new Rectangle(); + } + + bind(renderTexture, sourceFrame, destinationFrame) + { + // TODO - do we want this?? + if (this.renderTexture === renderTexture) return; + this.renderTexture = renderTexture; + + const renderer = this.renderer; + + if (renderTexture) + { + const baseTexture = renderTexture.baseTexture; + + if (!destinationFrame) + { + tempRect.width = baseTexture.realWidth; + tempRect.height = baseTexture.realHeight; + + destinationFrame = tempRect; + } + + if (!sourceFrame) + { + sourceFrame = destinationFrame; + } + + this.renderer.framebuffer.bind(baseTexture.frameBuffer, destinationFrame); + + this.renderer.projection.update(destinationFrame, sourceFrame, baseTexture.resolution, false); + this.renderer.stencil.setMaskStack(baseTexture.stencilMaskStack); + } + else + { + // TODO these validation checks happen deeper down.. + // thing they can be avoided.. + if (!destinationFrame) + { + tempRect.width = renderer.width; + tempRect.height = renderer.height; + + destinationFrame = tempRect; + } + + if (!sourceFrame) + { + sourceFrame = destinationFrame; + } + + renderer.framebuffer.bind(null, destinationFrame); + + // TODO store this.. + this.renderer.projection.update(destinationFrame, sourceFrame, this.renderer.resolution, true); + this.renderer.stencil.setMaskStack(this.defaultMaskStack); + } + + this.destinationFrame.copyFrom(destinationFrame); + } + + /** + * Erases the render texture and fills the drawing area with a colour + * + * @param {number} [clearColor] - The colour + * @return {PIXI.Renderer} Returns itself. + */ + clear(clearColor) + { + if (this.renderTexture) + { + clearColor = clearColor || this.renderTexture.baseTexture.clearColor; + } + else + { + clearColor = clearColor || this.clearColor; + } + + this.renderer.framebuffer.clear(clearColor[0], clearColor[1], clearColor[2], clearColor[3]); + } + + resize()// screenWidth, screenHeight) + { + // resize the root only! + this.bind(null); + } +} diff --git a/packages/core/src/renderers/AbstractRenderer.js b/packages/core/src/renderers/AbstractRenderer.js deleted file mode 100644 index 928670f..0000000 --- a/packages/core/src/renderers/AbstractRenderer.js +++ /dev/null @@ -1,353 +0,0 @@ -import { hex2string, hex2rgb } from '@pixi/utils'; -import { Matrix, Rectangle } from '@pixi/math'; -import { RENDERER_TYPE } from '@pixi/constants'; -import { settings } from '@pixi/settings'; -import { Container } from '@pixi/display'; -import RenderTexture from '../textures/RenderTexture'; -import EventEmitter from 'eventemitter3'; - -const tempMatrix = new Matrix(); - -/** - * 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 EventEmitter - * @memberof PIXI - */ -export default class AbstractRenderer extends EventEmitter -{ - // eslint-disable-next-line valid-jsdoc - /** - * @param {string} system - The name of the system this renderer is for. - * @param {object} [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.transparent=false] - If the render view is transparent, default false - * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false - * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) - * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. The - * resolution of the renderer retina would be 2. - * @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 {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when rendering, - * stopping pixel interpolation. - */ - constructor(system, options, arg2, arg3) - { - super(); - - // Support for constructor(system, screenWidth, screenHeight, options) - if (typeof options === 'number') - { - options = Object.assign({ - width: options, - height: arg2 || settings.RENDER_OPTIONS.height, - }, arg3); - } - - // 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 = RENDERER_TYPE.UNKNOWN; - - /** - * Measurements of the screen. (0, 0, screenWidth, screenHeight) - * - * Its safe to use as filterArea or hitArea for 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 1 - */ - this.resolution = options.resolution || settings.RESOLUTION; - - /** - * Whether the render view is transparent - * - * @member {boolean} - */ - this.transparent = options.transparent; - - /** - * Whether css dimensions of canvas view should be resized to screen dimensions automatically - * - * @member {boolean} - */ - this.autoResize = options.autoResize || false; - - /** - * Tracks the blend modes useful for this renderer. - * - * @member {object} - */ - this.blendModes = null; - - /** - * 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; - - /** - * If true PixiJS will Math.floor() x/y values when rendering, stopping pixel interpolation. - * Handy for crisp pixel art and speed on legacy devices. - * - * @member {boolean} - */ - this.roundPixels = options.roundPixels; - - /** - * The background color as a number. - * - * @member {number} - * @private - */ - this._backgroundColor = 0x000000; - - /** - * The background color as an [R, G, B] array. - * - * @member {number[]} - * @private - */ - this._backgroundColorRgba = [0, 0, 0, 0]; - - /** - * The background color as a string. - * - * @member {string} - * @private - */ - this._backgroundColorString = '#000000'; - - this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter - - /** - * This temporary display object used as the parent of the currently being rendered item - * - * @member {PIXI.DisplayObject} - * @private - */ - this._tempDisplayObjectParent = new Container(); - - /** - * The last root object that the renderer tried to render. - * - * @member {PIXI.DisplayObject} - * @private - */ - this._lastObjectRendered = this._tempDisplayObjectParent; - - /** - * Collection of plugins. - * @readonly - * @member {object} - */ - this.plugins = {}; - } - - /** - * Initialize the plugins. - * - * @protected - * @param {object} staticMap - The dictionary of staticly saved plugins. - */ - initPlugins(staticMap) - { - 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() - { - return this.view.width; - } - - /** - * Same as view.height, actual number of pixels in the canvas by vertical - * - * @member {number} - * @readonly - * @default 600 - */ - get height() - { - return this.view.height; - } - - /** - * Resizes the screen and canvas to the specified width and height - * Canvas dimensions are multiplied by resolution - * - * @param {number} screenWidth - the new width of the screen - * @param {number} screenHeight - the new height of the screen - */ - resize(screenWidth, screenHeight) - { - this.screen.width = screenWidth; - this.screen.height = screenHeight; - - this.view.width = screenWidth * this.resolution; - this.view.height = screenHeight * this.resolution; - - if (this.autoResize) - { - this.view.style.width = `${screenWidth}px`; - this.view.style.height = `${screenHeight}px`; - } - } - - /** - * 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 {PIXI.DisplayObject} displayObject - The displayObject the object will be generated from - * @param {number} scaleMode - Should be one of the scaleMode consts - * @param {number} resolution - The resolution / device pixel ratio of the texture being generated - * @param {PIXI.Rectangle} [region] - The region of the displayObject, that shall be rendered, - * if no region is specified, defaults to the local bounds of the displayObject. - * @return {PIXI.Texture} a texture of the graphics object - */ - generateTexture(displayObject, scaleMode, resolution, region) - { - region = region || displayObject.getLocalBounds(); - - // 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(region.width | 0, region.height | 0, scaleMode, resolution); - - tempMatrix.tx = -region.x; - tempMatrix.ty = -region.y; - - this.render(displayObject, renderTexture, false, tempMatrix, true); - - return renderTexture; - } - - /** - * Removes everything from the renderer and optionally removes the Canvas DOM element. - * - * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. - */ - destroy(removeView) - { - 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); - } - - this.plugins = null; - - this.type = RENDERER_TYPE.UNKNOWN; - - this.view = null; - - this.screen = null; - - this.resolution = 0; - - this.transparent = false; - - this.autoResize = false; - - this.blendModes = null; - - this.options = null; - - this.preserveDrawingBuffer = false; - this.clearBeforeRender = false; - - this.roundPixels = false; - - this._backgroundColor = 0; - this._backgroundColorRgba = null; - this._backgroundColorString = null; - - this._tempDisplayObjectParent = null; - this._lastObjectRendered = null; - } - - /** - * The background color to fill if not transparent - * - * @member {number} - */ - get backgroundColor() - { - return this._backgroundColor; - } - - set backgroundColor(value) // eslint-disable-line require-jsdoc - { - this._backgroundColor = value; - this._backgroundColorString = hex2string(value); - hex2rgb(value, this._backgroundColorRgba); - } -} diff --git a/packages/core/src/renderers/Renderer.js b/packages/core/src/renderers/Renderer.js deleted file mode 100644 index a2020b1..0000000 --- a/packages/core/src/renderers/Renderer.js +++ /dev/null @@ -1,329 +0,0 @@ -import AbstractRenderer from './AbstractRenderer'; -import { sayHello } from '@pixi/utils'; -import MaskSystem from './systems/MaskSystem'; -import StencilSystem from './systems/StencilSystem'; -import FilterSystem from './systems/filter/FilterSystem'; -import FramebufferSystem from './systems/FramebufferSystem'; -import RenderTextureSystem from './systems/RenderTextureSystem'; -import TextureSystem from './systems/textures/TextureSystem'; -import ProjectionSystem from './systems/ProjectionSystem'; -import StateSystem from './systems/StateSystem'; -import GeometrySystem from './systems/geometry/GeometrySystem'; -import ShaderSystem from './systems/shader/ShaderSystem'; -import ContextSystem from './systems/ContextSystem'; -import BatchSystem from './systems/BatchSystem'; -import TextureGCSystem from './systems/textures/TextureGCSystem'; -import { RENDERER_TYPE } from '@pixi/constants'; -import UniformGroup from '../shader/UniformGroup'; -import { Matrix } from '@pixi/math'; -import Runner from 'mini-runner'; - -/** - * 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 Render works by automatically managing webGLBatchs. - * 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 :) - * - * @class - * @memberof PIXI - * @extends PIXI.AbstractRenderer - */ -export default class Renderer extends AbstractRenderer -{ - // eslint-disable-next-line valid-jsdoc - /** - * - * @param {object} [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.transparent=false] - If the render view is transparent, default false - * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false - * @param {boolean} [options.antialias=false] - sets antialias. If not available natively then FXAA - * antialiasing is used - * @param {boolean} [options.forceFXAA=false] - forces FXAA antialiasing to be used over native. - * FXAA is faster, but may not always look as great - * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. - * The resolution of the renderer retina would be 2. - * @param {boolean} [options.clearBeforeRender=true] - This sets if the renderer will clear - * the canvas or not before the new render pass. If you wish to set this to false, you *must* set - * preserveDrawingBuffer to `true`. - * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, - * enable this if you need to call toDataUrl on the webgl context. - * @param {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when - * rendering, stopping pixel interpolation. - * @param {number} [options.backgroundColor=0x000000] - The background color of the rendered area - * (shown if not transparent). - * @param {boolean} [options.legacy=false] - If true PixiJS will aim to ensure compatibility - * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. - * @param {string} [options.powerPreference] - Parameter passed to webgl context, set to "high-performance" - * for devices with dual graphics card - */ - constructor(options, arg2, arg3) - { - super('WebGL', options, arg2, arg3); - - /** - * The type of this renderer as a standardised const - * - * @member {number} - * @see PIXI.RENDERER_TYPE - */ - this.type = RENDERER_TYPE.WEBGL; - - // this will be set by the contextSystem (this.context) - this.gl = null; - this.CONTEXT_UID = 0; - this.legacy = !!options.legacy; - - // TODO legacy! - - // runners! - this.runners = { - destroy: new Runner('destroy'), - contextChange: new Runner('contextChange', 1), - reset: new Runner('reset'), - update: new Runner('update'), - postrender: new Runner('postrender'), - prerender: new Runner('prerender'), - resize: new Runner('resize', 2), - }; - - 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(GeometrySystem, 'geometry') - .addSystem(FramebufferSystem, 'framebuffer') - .addSystem(StencilSystem, 'stencil') - .addSystem(ProjectionSystem, 'projection') - .addSystem(TextureGCSystem, 'textureGC') - .addSystem(FilterSystem, 'filter') - .addSystem(RenderTextureSystem, 'renderTexture') - .addSystem(BatchSystem, 'batch'); - - this.initPlugins(Renderer.__plugins); - - /** - * The options passed in to create a new webgl context. - * - * @member {object} - * @private - */ - if (options.context) - { - this.context.initFromContext(options.context); - } - else - { - this.context.initFromOptions({ - alpha: this.transparent, - antialias: options.antialias, - premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', - stencil: true, - 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); - } - - addSystem(_class, name) - { - if (!name) - { - name = _class.name; - } - - // TODO - read name from class.name.. - - /* - if(name.includes('System')) - { - name = name.replace('System', ''); - name = name.charAt(0).toLowerCase() + name.slice(1); - } - */ - - const system = new _class(this); - - if (this[name]) - { - throw new Error(`Whoops! ${name} is already a manger`); - } - - this[name] = system; - - for (const i in this.runners) - { - this.runners[i].add(system); - } - - return this; - - /** - * 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. - */ - } - - /** - * Renders the object to its webGL view - * - * @param {PIXI.DisplayObject} displayObject - the object to be rendered - * @param {PIXI.RenderTexture} renderTexture - The render texture to render to. - * @param {boolean} [clear] - Should the canvas be cleared before the new render - * @param {PIXI.Transform} [transform] - A transform to apply to the render texture before rendering. - * @param {boolean} [skipUpdateTransform] - Should we skip the update transform pass? - */ - render(displayObject, renderTexture, clear, transform, skipUpdateTransform) - { - // can be handy to know! - this.renderingToScreen = !renderTexture; - - this.runners.prerender.run(); - this.emit('prerender'); - - // 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.parent; - - displayObject.parent = this._tempDisplayObjectParent; - displayObject.updateTransform(); - displayObject.parent = 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.run(); - - this.emit('postrender'); - } - - /** - * Resizes the webGL view to the specified width and height. - * - * @param {number} screenWidth - the new width of the screen - * @param {number} screenHeight - the new height of the screen - */ - resize(screenWidth, screenHeight) - { - AbstractRenderer.prototype.resize.call(this, screenWidth, screenHeight); - - this.runners.resize.run(screenWidth, screenHeight); - } - - /** - * Resets the WebGL state so you can render things however you fancy! - * - * @return {PIXI.Renderer} Returns itself. - */ - reset() - { - this.runners.reset.run(); - - return this; - } - - clear() - { - this.framebuffer.bind(); - this.framebuffer.clear(); - } - - /** - * Removes everything from the renderer (event listeners, spritebatch, etc...) - * - * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. - * See: https://github.com/pixijs/pixi.js/issues/2233 - */ - destroy(removeView) - { - this.runners.destroy.run(); - - // call base destroy - super.destroy(removeView); - - // TODO nullify all the managers.. - this.gl = null; - } - - /** - * 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 - * builds and excluding plugins. - * @name PIXI.Renderer#plugins - * @type {object} - * @readonly - * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. - * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. - * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. - * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. - */ - - /** - * Adds a plugin to the renderer. - * - * @method - * @param {string} pluginName - The name of the plugin. - * @param {Function} ctor - The constructor function or class for the plugin. - */ - static registerPlugin(pluginName, ctor) - { - Renderer.__plugins = Renderer.__plugins || {}; - Renderer.__plugins[pluginName] = ctor; - } -} diff --git a/packages/core/src/renderers/State.js b/packages/core/src/renderers/State.js deleted file mode 100644 index fa3f221..0000000 --- a/packages/core/src/renderers/State.js +++ /dev/null @@ -1,163 +0,0 @@ -/* eslint-disable max-len */ - -const BLEND = 0; -const OFFSET = 1; -const CULLING = 2; -const DEPTH_TEST = 3; -const WINDING = 4; - -/** - * This is a webGL state. It is passed The WebGL StateManager. - * Each mesh renderered may require webGL to be in a different state. - * For example you may want different blend mode or to enable polygon offsets - * - * @class - * @memberof PIXI - */ -export default class State -{ - /** - * - */ - constructor() - { - this.data = 0; - - this.blendMode = 0; - this.polygonOffset = 0; - - this.blend = true; - // this.depthTest = true; - } - - /** - * Activates blending of the computed fragment color values - * - * @member {boolean} - */ - get blend() - { - return !!(this.data & (1 << BLEND)); - } - - set blend(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << BLEND)) !== value) - { - this.data ^= (1 << BLEND); - } - } - - /** - * Activates adding an offset to depth values of polygon's fragments - * - * @member {boolean} - * @default false - */ - get offsets() - { - return !!(this.data & (1 << OFFSET)); - } - - set offsets(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << OFFSET)) !== value) - { - this.data ^= (1 << OFFSET); - } - } - - /** - * Activates culling of polygons. - * - * @member {boolean} - * @default false - */ - get culling() - { - return !!(this.data & (1 << CULLING)); - } - - set culling(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << CULLING)) !== value) - { - this.data ^= (1 << CULLING); - } - } - - /** - * Activates depth comparisons and updates to the depth buffer. - * - * @member {boolean} - * @default false - */ - get depthTest() - { - return !!(this.data & (1 << DEPTH_TEST)); - } - - set depthTest(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << DEPTH_TEST)) !== value) - { - this.data ^= (1 << DEPTH_TEST); - } - } - - /** - * Specifies whether or not front or back-facing polygons can be culled. - * @member {boolean} - * @default false - */ - get clockwiseFrontFace() - { - return !!(this.data & (1 << WINDING)); - } - - set clockwiseFrontFace(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << WINDING)) !== value) - { - this.data ^= (1 << WINDING); - } - } - - /** - * The blend mode to be applied when this state is set. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. - * Setting this mode to anything other than NO_BLEND will automatically switch blending on. - * - * @member {boolean} - * @default PIXI.BLEND_MODES.NORMAL - * @see PIXI.BLEND_MODES - */ - get blendMode() - { - return this._blendMode; - } - - set blendMode(value) // eslint-disable-line require-jsdoc - { - // 17 is NO BLEND - this.blend = (value !== 17); - this._blendMode = value; - } - - /** - * The polygon offset. Setting this property to anything other than 0 will automatically enable poygon offset fill. - * - * @member {number} - * @default 0 - */ - get polygonOffset() - { - return this._polygonOffset; - } - - set polygonOffset(value) // eslint-disable-line require-jsdoc - { - this.offsets = !!value; - this._polygonOffset = value; - } -} - diff --git a/packages/core/src/renderers/filters/Filter.js b/packages/core/src/renderers/filters/Filter.js deleted file mode 100644 index 2073b8d..0000000 --- a/packages/core/src/renderers/filters/Filter.js +++ /dev/null @@ -1,140 +0,0 @@ -import Shader from '../../shader/Shader'; -import Program from '../../shader/Program'; -import State from '../State'; -import { settings } from '@pixi/settings'; -// import extractUniformsFromSrc from './extractUniformsFromSrc'; -import defaultVertex from './defaultFilter.vert'; -import defaultFragment from './defaultFilter.frag'; - -// let math = require('../../math'); -/** - * @class - * @memberof PIXI - * @extends PIXI.Shader - */ -export default class Filter extends Shader -{ - /** - * @param {string} [vertexSrc] - The source of the vertex shader. - * @param {string} [fragmentSrc] - The source of the fragment shader. - * @param {object} [uniforms] - Custom uniforms to use to augment the built-in ones. - */ - constructor(vertexSrc, fragmentSrc, uniforms) - { - const program = Program.from(vertexSrc, fragmentSrc); - - super(program, uniforms); - - /** - * The padding of the filter. Some filters require extra space to breath such as a blur. - * Increasing this will add extra width and height to the bounds of the object that the - * filter is applied to. - * - * @member {number} - */ - this.padding = 0; - - /** - * The resolution of the filter. Setting this to be lower will lower the quality but - * increase the performance of the filter. - * - * @member {number} - */ - this.resolution = settings.RESOLUTION; - - /** - * If enabled is true the filter is applied, if false it will not. - * - * @member {boolean} - */ - this.enabled = true; - - /** - * If enabled, PixiJS will fit the filter area into boundaries for better performance. - * Switch it off if it does not work for specific shader. - * - * @member {boolean} - */ - this.autoFit = true; - - /** - * Legacy filters use position and uvs from attributes - * @member {boolean} - * @readonly - */ - this.legacy = !!this.program.attributeData.aTextureCoord; - - /** - * the webGL state the filter requires to render - * @member {PIXI.State} - */ - this.state = new State(); - } - - /** - * Applies the filter - * - * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from - * @param {PIXI.RenderTarget} input - The input render target. - * @param {PIXI.RenderTarget} output - The target to output to. - * @param {boolean} clear - Should the output be cleared before rendering to it - * @param {object} [currentState] - It's current state of filter. - * There are some useful properties in the currentState : - * target, filters, sourceFrame, destinationFrame, renderTarget, resolution - */ - apply(filterManager, input, output, clear, currentState, derp) // eslint-disable-line no-unused-vars - { - // do as you please! - - filterManager.applyFilter(this, input, output, clear, currentState, derp); - - // or just do a regular render.. - } - - /** - * Sets the blendmode of the filter - * - * @member {number} - * @default PIXI.BLEND_MODES.NORMAL - */ - get blendMode() - { - return this.state.blendMode; - } - - set blendMode(value) // eslint-disable-line require-jsdoc - { - this.state.blendMode = value; - } - - /** - * The default vertex shader source - * - * @static - * @constant - */ - static get defaultVertexSrc() - { - return defaultVertex; - } - - /** - * The default fragment shader source - * - * @static - * @constant - */ - static get defaultFragmentSrc() - { - return defaultFragment; - } -} - -/** - * Used for caching shader IDs - * - * @static - * @private - */ -Filter.SOURCE_KEY_MAP = {}; - diff --git a/packages/core/src/renderers/filters/defaultFilter.frag b/packages/core/src/renderers/filters/defaultFilter.frag deleted file mode 100644 index a14c1be..0000000 --- a/packages/core/src/renderers/filters/defaultFilter.frag +++ /dev/null @@ -1,22 +0,0 @@ -varying vec2 vTextureCoord; -varying vec2 vFilterCoord; - -uniform sampler2D uSampler; -uniform sampler2D filterSampler; - -void main(void){ - vec4 masky = texture2D(filterSampler, vFilterCoord); - vec4 sample = texture2D(uSampler, vTextureCoord); - vec4 color; - if(mod(vFilterCoord.x, 1.0) > 0.5) - { - color = vec4(1.0, 0.0, 0.0, 1.0); - } - else - { - color = vec4(0.0, 1.0, 0.0, 1.0); - } - // gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0); - gl_FragColor = mix(sample, masky, 0.5); - gl_FragColor *= sample.a; -} diff --git a/packages/core/src/renderers/filters/defaultFilter.vert b/packages/core/src/renderers/filters/defaultFilter.vert deleted file mode 100644 index e1febca..0000000 --- a/packages/core/src/renderers/filters/defaultFilter.vert +++ /dev/null @@ -1,14 +0,0 @@ -attribute vec2 aVertexPosition; -attribute vec2 aTextureCoord; - -uniform mat3 projectionMatrix; -uniform mat3 filterMatrix; - -varying vec2 vTextureCoord; -varying vec2 vFilterCoord; - -void main(void){ - gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); - vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy; - vTextureCoord = aTextureCoord ; -} diff --git a/packages/core/src/renderers/filters/filterTransforms.js b/packages/core/src/renderers/filters/filterTransforms.js deleted file mode 100644 index 87a3cd6..0000000 --- a/packages/core/src/renderers/filters/filterTransforms.js +++ /dev/null @@ -1,50 +0,0 @@ -import { Matrix } from '@pixi/math'; - -/** - * Calculates the mapped matrix - * @param filterArea {Rectangle} The filter area - * @param sprite {Sprite} the target sprite - * @param outputMatrix {Matrix} @alvin - * @private - */ -// this returns a matrix that will normalise map filter cords in the filter to screen space -export function calculateScreenSpaceMatrix(outputMatrix, filterArea, textureSize) -{ - // TODO unwrap? - const mappedMatrix = outputMatrix.identity(); - - mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); - - mappedMatrix.scale(textureSize.width, textureSize.height); - - return mappedMatrix; -} - -export function calculateNormalizedScreenSpaceMatrix(outputMatrix, filterArea, textureSize) -{ - const mappedMatrix = outputMatrix.identity(); - - mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); - - const translateScaleX = (textureSize.width / filterArea.width); - const translateScaleY = (textureSize.height / filterArea.height); - - mappedMatrix.scale(translateScaleX, translateScaleY); - - return mappedMatrix; -} - -// this will map the filter coord so that a texture can be used based on the transform of a sprite -export function calculateSpriteMatrix(outputMatrix, filterArea, textureSize, sprite) -{ - const orig = sprite._texture.orig; - const mappedMatrix = outputMatrix.set(textureSize.width, 0, 0, textureSize.height, filterArea.x, filterArea.y); - const worldTransform = sprite.worldTransform.copyTo(Matrix.TEMP_MATRIX); - - worldTransform.invert(); - mappedMatrix.prepend(worldTransform); - mappedMatrix.scale(1.0 / orig.width, 1.0 / orig.height); - mappedMatrix.translate(sprite.anchor.x, sprite.anchor.y); - - return mappedMatrix; -} diff --git a/packages/core/src/renderers/filters/spriteMask/SpriteMaskFilter.js b/packages/core/src/renderers/filters/spriteMask/SpriteMaskFilter.js deleted file mode 100644 index d5a91a4..0000000 --- a/packages/core/src/renderers/filters/spriteMask/SpriteMaskFilter.js +++ /dev/null @@ -1,63 +0,0 @@ -import Filter from '../Filter'; -import { Matrix } from '@pixi/math'; -import vertex from './spriteMaskFilter.vert'; -import fragment from './spriteMaskFilter.frag'; -import { default as TextureMatrix } from '../../../textures/TextureMatrix'; - -/** - * The SpriteMaskFilter class - * - * @class - * @extends PIXI.Filter - * @memberof PIXI - */ -export default class SpriteMaskFilter extends Filter -{ - /** - * @param {PIXI.Sprite} sprite - the target sprite - */ - constructor(sprite) - { - const maskMatrix = new Matrix(); - - super(vertex, fragment); - - sprite.renderable = false; - - this.maskSprite = sprite; - this.maskMatrix = maskMatrix; - } - - /** - * Applies the filter - * - * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from - * @param {PIXI.RenderTarget} input - The input render target. - * @param {PIXI.RenderTarget} output - The target to output to. - */ - apply(filterManager, input, output) - { - const maskSprite = this.maskSprite; - const tex = this.maskSprite.texture; - - if (!tex.valid) - { - return; - } - if (!tex.transform) - { - // margin = 0.0, let it bleed a bit, shader code becomes easier - // assuming that atlas textures were made with 1-pixel padding - tex.transform = new TextureMatrix(tex, 0.0); - } - tex.transform.update(); - - this.uniforms.mask = tex; - this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite) - .prepend(tex.transform.mapCoord); - this.uniforms.alpha = maskSprite.worldAlpha; - this.uniforms.maskClamp = tex.transform.uClampFrame; - - filterManager.applyFilter(this, input, output); - } -} diff --git a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.frag b/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.frag deleted file mode 100644 index 0e4aef8..0000000 --- a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.frag +++ /dev/null @@ -1,23 +0,0 @@ -varying vec2 vMaskCoord; -varying vec2 vTextureCoord; - -uniform sampler2D uSampler; -uniform sampler2D mask; -uniform float alpha; -uniform vec4 maskClamp; - -void main(void) -{ - float clip = step(3.5, - step(maskClamp.x, vMaskCoord.x) + - step(maskClamp.y, vMaskCoord.y) + - step(vMaskCoord.x, maskClamp.z) + - step(vMaskCoord.y, maskClamp.w)); - - vec4 original = texture2D(uSampler, vTextureCoord); - vec4 masky = texture2D(mask, vMaskCoord); - - original *= (masky.r * masky.a * alpha * clip); - - gl_FragColor = original; -} diff --git a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.vert b/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.vert deleted file mode 100644 index d23a30c..0000000 --- a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.vert +++ /dev/null @@ -1,16 +0,0 @@ -attribute vec2 aVertexPosition; -attribute vec2 aTextureCoord; - -uniform mat3 projectionMatrix; -uniform mat3 otherMatrix; - -varying vec2 vMaskCoord; -varying vec2 vTextureCoord; - -void main(void) -{ - gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); - - vTextureCoord = aTextureCoord; - vMaskCoord = ( otherMatrix * vec3( aTextureCoord, 1.0) ).xy; -} diff --git a/packages/core/src/renderers/systems/BatchSystem.js b/packages/core/src/renderers/systems/BatchSystem.js deleted file mode 100644 index 70e9518..0000000 --- a/packages/core/src/renderers/systems/BatchSystem.js +++ /dev/null @@ -1,68 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import ObjectRenderer from '../utils/ObjectRenderer'; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ - -export default class BatchSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this manager works for. - */ - constructor(renderer) - { - super(renderer); - - /** - * An empty renderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.emptyRenderer = new ObjectRenderer(renderer); - - /** - * The currently active ObjectRenderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.currentRenderer = this.emptyRenderer; - } - - /** - * Changes the current renderer to the one given in parameter - * - * @param {PIXI.ObjectRenderer} objectRenderer - The object renderer to use. - */ - setObjectRenderer(objectRenderer) - { - if (this.currentRenderer === objectRenderer) - { - return; - } - - this.currentRenderer.stop(); - this.currentRenderer = objectRenderer; - - this.renderer.state.setState(objectRenderer.state); - - this.currentRenderer.start(); - } - - /** - * This should be called if you wish to do some custom rendering - * It will basically render anything that may be batched up such as sprites - * - */ - flush() - { - this.setObjectRenderer(this.emptyRenderer); - } - - reset() - { - this.setObjectRenderer(this.emptyRenderer); - } -} diff --git a/packages/core/src/renderers/systems/ContextSystem.js b/packages/core/src/renderers/systems/ContextSystem.js deleted file mode 100644 index 43bf827..0000000 --- a/packages/core/src/renderers/systems/ContextSystem.js +++ /dev/null @@ -1,186 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { settings } from '@pixi/settings'; - -let CONTEXT_UID = 0; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class ContextSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this.webGLVersion = 1; - - this.handleContextLost = this.handleContextLost.bind(this); - this.handleContextRestored = this.handleContextRestored.bind(this); - - this.extensions = {}; - - renderer.view.addEventListener('webglcontextlost', this.handleContextLost, false); - renderer.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); - } - - get isLost() - { - return (!this.gl || this.gl.isContextLost()); - } - - contextChange(gl) - { - this.gl = gl; - - // restore a context if it was previously lost - if (gl.isContextLost() && gl.getExtension('WEBGL_lose_context')) - { - gl.getExtension('WEBGL_lose_context').restoreContext(); - } - } - - initFromContext(gl) - { - this.gl = gl; - this.validateContext(gl); - this.renderer.gl = gl; - this.renderer.CONTEXT_UID = CONTEXT_UID++; - this.renderer.runners.contextChange.run(gl); - } - - initFromOptions(options) - { - const gl = this.createContext(this.renderer.view, options); - - this.initFromContext(gl); - } - - /** - * Helper class to create a webGL Context - * - * @class - * @memberof PIXI.glCore - * @param canvas {HTMLCanvasElement} the canvas element that we will get the context from - * @param options {Object} An options object that gets passed in to the canvas element containing the context attributes, - * see https://developer.mozilla.org/en/docs/Web/API/HTMLCanvasElement/getContext for the options available - * @return {WebGLRenderingContext} the WebGL context - */ - createContext(canvas, options) - { - let gl; - - if (settings.PREFER_WEBGL_2) - { - gl = canvas.getContext('webgl2', options); - } - - if (gl) - { - this.webGLVersion = 2; - } - else - { - this.webGLVersion = 1; - - gl = canvas.getContext('webgl', options) - || canvas.getContext('experimental-webgl', options); - - if (!gl) - { - // fail, not able to get a context - throw new Error('This browser does not support webGL. Try using the canvas renderer'); - } - } - - this.gl = gl; - - this.getExtensions(); - - return gl; - } - - getExtensions() - { - // time to set up default etensions that pixi uses.. - const gl = this.gl; - const extensions = this.extensions; - - if (this.webGLVersion === 1) - { - extensions.drawBuffers = gl.getExtension('WEBGL_draw_buffers'); - extensions.depthTexture = gl.getExtension('WEBKIT_WEBGL_depth_texture'); - extensions.floatTexture = gl.getExtension('OES_texture_float'); - extensions.loseContext = gl.getExtension('WEBGL_lose_context'); - - extensions.vertexArrayObject = gl.getExtension('OES_vertex_array_object') - || gl.getExtension('MOZ_OES_vertex_array_object') - || gl.getExtension('WEBKIT_OES_vertex_array_object'); - } - - // we don't use any specific WebGL 2 ones yet! - } - - /** - * Handles a lost webgl context - * - * @private - * @param {WebGLContextEvent} event - The context lost event. - */ - handleContextLost(event) - { - event.preventDefault(); - } - - /** - * Handles a restored webgl context - * - * @private - */ - handleContextRestored() - { - this.renderer.runners.contextChange.run(this.gl); - } - - destroy() - { - const view = this.renderer.view; - - // remove listeners - view.removeEventListener('webglcontextlost', this.handleContextLost); - view.removeEventListener('webglcontextrestored', this.handleContextRestored); - - this.gl.useProgram(null); - - if (this.extensions.loseContext) - { - this.extensions.loseContext.loseContext(); - } - } - - postrender() - { - this.gl.flush(); - } - - validateContext(gl) - { - const attributes = gl.getContextAttributes(); - - // this is going to be fairly simple for now.. but at least we have room to grow! - if (!attributes.stencil) - { - /* eslint-disable max-len */ - - /* eslint-disable no-console */ - console.warn('Provided WebGL context does not have a stencil buffer, masks may not render correctly'); - /* eslint-enable no-console */ - - /* eslint-enable max-len */ - } - } -} diff --git a/packages/core/src/renderers/systems/FramebufferSystem.js b/packages/core/src/renderers/systems/FramebufferSystem.js deleted file mode 100644 index df7902b..0000000 --- a/packages/core/src/renderers/systems/FramebufferSystem.js +++ /dev/null @@ -1,246 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { Rectangle } from '@pixi/math'; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class FramebufferSystem extends WebGLSystem -{ - /** - * Sets up the renderer context and necessary buffers. - * - * @private - */ - contextChange() - { - this.gl = this.renderer.gl; - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - this.current = null; - this.viewport = new Rectangle(); - - this.drawBufferExtension = this.renderer.context.extensions.drawBuffers; - } - - bind(framebuffer, frame) - { - const gl = this.gl; - - this.current = framebuffer; - - if (framebuffer) - { - // TODO cacheing layer! - - const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID] || this.initFramebuffer(framebuffer); - - gl.bindFramebuffer(gl.FRAMEBUFFER, fbo.framebuffer); - // makesure all textures are unbound.. - - // now check for updates... - if (fbo.dirtyId !== framebuffer.dirtyId) - { - fbo.dirtyId = framebuffer.dirtyId; - - if (fbo.dirtyFormat !== framebuffer.dirtyFormat) - { - fbo.dirtyFormat = framebuffer.dirtyFormat; - this.updateFramebuffer(framebuffer); - } - else if (fbo.dirtySize !== framebuffer.dirtySize) - { - fbo.dirtySize = framebuffer.dirtySize; - this.resizeFramebuffer(framebuffer); - } - } - - for (let i = 0; i < framebuffer.colorTextures.length; i++) - { - if (framebuffer.colorTextures[i].texturePart) - { - this.renderer.texture.unbind(framebuffer.colorTextures[i].texture); - } - else - { - this.renderer.texture.unbind(framebuffer.colorTextures[i]); - } - } - - if (framebuffer.depthTexture) - { - this.renderer.texture.unbind(framebuffer.depthTexture); - } - - if (frame) - { - this.setViewport(frame.x, frame.y, frame.width, frame.height); - } - else - { - this.setViewport(0, 0, framebuffer.width, framebuffer.height); - } - } - else - { - gl.bindFramebuffer(gl.FRAMEBUFFER, null); - - if (frame) - { - this.setViewport(frame.x, frame.y, frame.width, frame.height); - } - else - { - this.setViewport(0, 0, this.renderer.width, this.renderer.height); - } - } - } - - setViewport(x, y, width, height) - { - const v = this.viewport; - - if (v.width !== width || v.height !== height || v.x !== x || v.y !== y) - { - v.x = x; - v.y = y; - v.width = width; - v.height = height; - - this.gl.viewport(x, y, width, height); - } - } - - get size() - { - if (this.current) - { - // TODO store temp - return { x: 0, y: 0, width: this.current.width, height: this.current.height }; - } - - return { x: 0, y: 0, width: this.renderer.width, height: this.renderer.height }; - } - - clear(r, g, b, a) - { - const gl = this.gl; - - // TODO clear color can be set only one right? - gl.clearColor(r, g, b, a); - gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); - } - - // private functions... - - initFramebuffer(framebuffer) - { - const gl = this.gl; - - // TODO - make this a class? - const fbo = { - framebuffer: gl.createFramebuffer(), - stencil: null, - dirtyId: 0, - dirtyFormat: 0, - dirtySize: 0, - }; - - framebuffer.glFrameBuffers[this.CONTEXT_UID] = fbo; - - return fbo; - } - - resizeFramebuffer(framebuffer) - { - const gl = this.gl; - - if (framebuffer.stencil || framebuffer.depth) - { - gl.bindRenderbuffer(gl.RENDERBUFFER, this.stencil); - gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); - } - } - - updateFramebuffer(framebuffer) - { - const gl = this.gl; - - const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID]; - - // bind the color texture - const colorTextures = framebuffer.colorTextures; - - let count = colorTextures.length; - - if (!this.drawBufferExtension) - { - count = Math.min(count, 1); - } - - const activeTextures = []; - - for (let i = 0; i < count; i++) - { - const texture = framebuffer.colorTextures[i]; - - if (texture.texturePart) - { - this.renderer.texture.bind(texture.texture, 0); - - gl.framebufferTexture2D(gl.FRAMEBUFFER, - gl.COLOR_ATTACHMENT0 + i, - gl.TEXTURE_CUBE_MAP_NEGATIVE_X + texture.side, - texture.texture._glTextures[this.CONTEXT_UID].texture, - 0); - } - else - { - this.renderer.texture.bind(texture, 0); - - gl.framebufferTexture2D(gl.FRAMEBUFFER, - gl.COLOR_ATTACHMENT0 + i, - gl.TEXTURE_2D, - texture._glTextures[this.CONTEXT_UID].texture, - 0); - } - - activeTextures.push(gl.COLOR_ATTACHMENT0 + i); - } - - if (this.drawBufferExtension && activeTextures.length > 1) - { - this.drawBufferExtension.drawBuffersWEBGL(activeTextures); - } - - if (framebuffer.depthTexture) - { - const depthTextureExt = this.renderer.context.extensions.depthTexture; - - if (depthTextureExt) - { - const depthTexture = framebuffer.depthTexture; - - this.renderer.texture.bind(depthTexture, 0); - - gl.framebufferTexture2D(gl.FRAMEBUFFER, - gl.DEPTH_ATTACHMENT, - gl.TEXTURE_2D, - depthTexture._glTextures[this.CONTEXT_UID].texture, - 0); - } - } - - if (framebuffer.stencil || framebuffer.depth) - { - fbo.stencil = gl.createRenderbuffer(); - - gl.bindRenderbuffer(gl.RENDERBUFFER, fbo.stencil); - - // TODO.. this is depth AND stencil? - gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, fbo.stencil); - gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); - // fbo.enableStencil(); - } - } -} diff --git a/packages/core/src/renderers/systems/MaskSystem.js b/packages/core/src/renderers/systems/MaskSystem.js deleted file mode 100644 index d9c4b1a..0000000 --- a/packages/core/src/renderers/systems/MaskSystem.js +++ /dev/null @@ -1,199 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import AlphaMaskFilter from '../filters/spriteMask/SpriteMaskFilter'; - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class MaskSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - // TODO - we don't need both! - this.scissor = false; - this.scissorData = null; - this.scissorRenderTarget = null; - - this.enableScissor = false; - - this.alphaMaskPool = []; - this.alphaMaskIndex = 0; - } - - /** - * Applies the Mask and adds it to the current filter stack. - * - * @param {PIXI.DisplayObject} target - Display Object to push the mask to - * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. - */ - push(target, maskData) - { - // TODO the root check means scissor rect will not - // be used on render textures more info here: - // https://github.com/pixijs/pixi.js/pull/3545 - - if (maskData.texture) - { - this.pushSpriteMask(target, maskData); - } - else if (this.enableScissor - && !this.scissor - && this.renderer._activeRenderTarget.root - && !this.renderer.stencil.stencilMaskStack.length - && maskData.isFastRect()) - { - const matrix = maskData.worldTransform; - - let rot = Math.atan2(matrix.b, matrix.a); - - // use the nearest degree! - rot = Math.round(rot * (180 / Math.PI)); - - if (rot % 90) - { - this.pushStencilMask(maskData); - } - else - { - this.pushScissorMask(target, maskData); - } - } - else - { - this.pushStencilMask(maskData); - } - } - - /** - * Removes the last mask from the mask stack and doesn't return it. - * - * @param {PIXI.DisplayObject} target - Display Object to pop the mask from - * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. - */ - pop(target, maskData) - { - if (maskData.texture) - { - this.popSpriteMask(target, maskData); - } - else if (this.enableScissor && !this.renderer.stencil.stencilMaskStack.length) - { - this.popScissorMask(target, maskData); - } - else - { - this.popStencilMask(target, maskData); - } - } - - /** - * Applies the Mask and adds it to the current filter stack. - * - * @param {PIXI.RenderTarget} target - Display Object to push the sprite mask to - * @param {PIXI.Sprite} maskData - Sprite to be used as the mask - */ - pushSpriteMask(target, maskData) - { - let alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; - - if (!alphaMaskFilter) - { - alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; - } - - alphaMaskFilter[0].resolution = this.renderer.resolution; - alphaMaskFilter[0].maskSprite = maskData; - - // TODO - may cause issues! - target.filterArea = maskData.getBounds(true); - - this.renderer.filterSystem.pushFilter(target, alphaMaskFilter); - - this.alphaMaskIndex++; - } - - /** - * Removes the last filter from the filter stack and doesn't return it. - * - */ - popSpriteMask() - { - this.renderer.filterSystem.popFilter(); - this.alphaMaskIndex--; - } - - /** - * Applies the Mask and adds it to the current filter stack. - * - * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. - */ - pushStencilMask(maskData) - { - this.renderer.batch.flush(); - this.renderer.stencil.pushStencil(maskData); - } - - /** - * Removes the last filter from the filter stack and doesn't return it. - * - */ - popStencilMask() - { - // this.renderer.currentRenderer.stop(); - this.renderer.stencil.popStencil(); - } - - /** - * - * @param {PIXI.DisplayObject} target - Display Object to push the mask to - * @param {PIXI.Graphics} maskData - The masking data. - */ - pushScissorMask(target, maskData) - { - maskData.renderable = true; - - const renderTarget = this.renderer._activeRenderTarget; - - const bounds = maskData.getBounds(); - - bounds.fit(renderTarget.size); - maskData.renderable = false; - - this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); - - const resolution = this.renderer.resolution; - - this.renderer.gl.scissor( - bounds.x * resolution, - (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, - bounds.width * resolution, - bounds.height * resolution - ); - - this.scissorRenderTarget = renderTarget; - this.scissorData = maskData; - this.scissor = true; - } - - /** - * - * - */ - popScissorMask() - { - this.scissorRenderTarget = null; - this.scissorData = null; - this.scissor = false; - - // must be scissor! - const gl = this.renderer.gl; - - gl.disable(gl.SCISSOR_TEST); - } -} diff --git a/packages/core/src/renderers/systems/ProjectionSystem.js b/packages/core/src/renderers/systems/ProjectionSystem.js deleted file mode 100644 index ec5efdb..0000000 --- a/packages/core/src/renderers/systems/ProjectionSystem.js +++ /dev/null @@ -1,73 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { Matrix } from '@pixi/math'; - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ - -export default class ProjectionSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this.projectionMatrix = new Matrix(); - } - - update(destinationFrame, sourceFrame, resolution, root) - { - this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; - this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; - - this.calculateProjection(this.destinationFrame, this.sourceFrame, resolution, root); - - this.renderer.globalUniforms.uniforms.projectionMatrix = this.projectionMatrix; - this.renderer.globalUniforms.update(); - } - - /** - * Updates the projection matrix based on a projection frame (which is a rectangle) - * - * @param {Rectangle} destinationFrame - The destination frame. - * @param {Rectangle} sourceFrame - The source frame. - */ - calculateProjection(destinationFrame, sourceFrame, resolution, root) - { - const pm = this.projectionMatrix; - - // I don't think we will need this line.. - // pm.identity(); - - if (!root) - { - pm.a = (1 / destinationFrame.width * 2) * resolution; - pm.d = (1 / destinationFrame.height * 2) * resolution; - - pm.tx = -1 - (sourceFrame.x * pm.a); - pm.ty = -1 - (sourceFrame.y * pm.d); - } - else - { - pm.a = (1 / destinationFrame.width * 2) * resolution; - pm.d = (-1 / destinationFrame.height * 2) * resolution; - - pm.tx = -1 - (sourceFrame.x * pm.a); - pm.ty = 1 - (sourceFrame.y * pm.d); - } - } - - /** - * Sets the transform of the active render target to the given matrix - * - * @param {PIXI.Matrix} matrix - The transformation matrix - */ - setTransform()// matrix) - { - // this._activeRenderTarget.transform = matrix; - } -} diff --git a/packages/core/src/renderers/systems/RenderTextureSystem.js b/packages/core/src/renderers/systems/RenderTextureSystem.js deleted file mode 100644 index a133ef1..0000000 --- a/packages/core/src/renderers/systems/RenderTextureSystem.js +++ /dev/null @@ -1,115 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { Rectangle } from '@pixi/math'; - -const tempRect = new Rectangle(); - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ - -export default class RenderTextureSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this.clearColor = renderer._backgroundColorRgba; - - // TODO moe this property somewhere else! - this.defaultMaskStack = []; - this.defaultFilterStack = [{}]; - - // empty render texture? - this.renderTexture = null; - - this.destinationFrame = new Rectangle(); - } - - bind(renderTexture, sourceFrame, destinationFrame) - { - // TODO - do we want this?? - if (this.renderTexture === renderTexture) return; - this.renderTexture = renderTexture; - - const renderer = this.renderer; - - if (renderTexture) - { - const baseTexture = renderTexture.baseTexture; - - if (!destinationFrame) - { - tempRect.width = baseTexture.realWidth; - tempRect.height = baseTexture.realHeight; - - destinationFrame = tempRect; - } - - if (!sourceFrame) - { - sourceFrame = destinationFrame; - } - - this.renderer.framebuffer.bind(baseTexture.frameBuffer, destinationFrame); - - this.renderer.projection.update(destinationFrame, sourceFrame, baseTexture.resolution, false); - this.renderer.stencil.setMaskStack(baseTexture.stencilMaskStack); - } - else - { - // TODO these validation checks happen deeper down.. - // thing they can be avoided.. - if (!destinationFrame) - { - tempRect.width = renderer.width; - tempRect.height = renderer.height; - - destinationFrame = tempRect; - } - - if (!sourceFrame) - { - sourceFrame = destinationFrame; - } - - renderer.framebuffer.bind(null, destinationFrame); - - // TODO store this.. - this.renderer.projection.update(destinationFrame, sourceFrame, this.renderer.resolution, true); - this.renderer.stencil.setMaskStack(this.defaultMaskStack); - } - - this.destinationFrame.copyFrom(destinationFrame); - } - - /** - * Erases the render texture and fills the drawing area with a colour - * - * @param {number} [clearColor] - The colour - * @return {PIXI.Renderer} Returns itself. - */ - clear(clearColor) - { - if (this.renderTexture) - { - clearColor = clearColor || this.renderTexture.baseTexture.clearColor; - } - else - { - clearColor = clearColor || this.clearColor; - } - - this.renderer.framebuffer.clear(clearColor[0], clearColor[1], clearColor[2], clearColor[3]); - } - - resize()// screenWidth, screenHeight) - { - // resize the root only! - this.bind(null); - } -} diff --git a/packages/core/src/renderers/systems/StateSystem.js b/packages/core/src/renderers/systems/StateSystem.js deleted file mode 100755 index d7f4caf..0000000 --- a/packages/core/src/renderers/systems/StateSystem.js +++ /dev/null @@ -1,288 +0,0 @@ -import mapWebGLBlendModesToPixi from '../utils/mapWebGLBlendModesToPixi'; -import WebGLSystem from './WebGLSystem'; -import WebGLState from '../State'; - -const BLEND = 0; -const OFFSET = 1; -const CULLING = 2; -const DEPTH_TEST = 3; -const WINDING = 4; - -/** - * A WebGL state machines - * - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class StateSystem extends WebGLSystem -{ - /** - * @param {WebGLRenderingContext} gl - The current WebGL rendering context - */ - constructor(renderer) - { - super(renderer); - - this.gl = null; - - this.maxAttribs = null; - - // check we have vao.. - this.nativeVaoExtension = null; - - this.attribState = null; - - this.stateId = 0; - this.polygonOffset = 0; - this.blendMode = 17; - - this.map = []; - - // map functions for when we set state.. - this.map[BLEND] = this.setBlend; - this.map[OFFSET] = this.setOffset; - this.map[CULLING] = this.setCullFace; - this.map[DEPTH_TEST] = this.setDepthTest; - this.map[WINDING] = this.setFrontFace; - - this.checks = []; - - this.defaultState = new WebGLState(); - this.defaultState.blend = true; - this.defaultState.depth = true; - } - - contextChange(gl) - { - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; - - this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); - - // check we have vao.. - this.nativeVaoExtension = ( - gl.getExtension('OES_vertex_array_object') - || gl.getExtension('MOZ_OES_vertex_array_object') - || gl.getExtension('WEBKIT_OES_vertex_array_object') - ); - - this.attribState = { - tempAttribState: new Array(this.maxAttribs), - attribState: new Array(this.maxAttribs), - }; - - this.blendModes = mapWebGLBlendModesToPixi(gl); - - this.setState(this.defaultState); - - this.reset(); - } - - /** - * Sets the current state - * - * @param {*} state - The state to set. - */ - setState(state) - { - state = state || this.defaultState; - - // TODO maybe to an object check? ( this.state === state )? - if (this.stateId !== state.data) - { - let diff = this.stateId ^ state.data; - let i = 0; - - // order from least to most common - while (diff) - { - if (diff & 1) - { - // state change! - this.map[i].call(this, !!(state.data & (1 << i))); - } - - diff = diff >> 1; - i++; - } - - this.stateId = state.data; - } - - // based on the above settings we check for specific modes.. - // for example if blend is active we check and set the blend modes - // or of polygon offset is active we check the poly depth. - for (let i = 0; i < this.checks.length; i++) - { - this.checks[i](this, state); - } - } - - /** - * Enables or disabled blending. - * - * @param {boolean} value - Turn on or off webgl blending. - */ - setBlend(value) - { - this.updateCheck(StateSystem.checkBlendMode, value); - - this.gl[value ? 'enable' : 'disable'](this.gl.BLEND); - } - - /** - * Enables or disable polygon offset fill - * - * @param {boolean} value - Turn on or off webgl polygon offset testing. - */ - setOffset(value) - { - this.gl[value ? 'enable' : 'disable'](this.gl.POLYGON_OFFSET_FILL); - } - - /** - * Sets whether to enable or disable depth test. - * - * @param {boolean} value - Turn on or off webgl depth testing. - */ - setDepthTest(value) - { - this.gl[value ? 'enable' : 'disable'](this.gl.DEPTH_TEST); - } - - /** - * Sets whether to enable or disable cull face. - * - * @param {boolean} value - Turn on or off webgl cull face. - */ - setCullFace(value) - { - this.gl[value ? 'enable' : 'disable'](this.gl.CULL_FACE); - } - - /** - * Sets the gl front face. - * - * @param {boolean} value - true is clockwise and false is counter-clockwise - */ - setFrontFace(value) - { - this.gl.frontFace(this.gl[value ? 'CW' : 'CCW']); - } - - /** - * Sets the blend mode. - * - * @param {number} value - The blend mode to set to. - */ - setBlendMode(value) - { - if (value === this.blendMode) - { - return; - } - - this.blendMode = value; - this.gl.blendFunc(this.blendModes[value][0], this.blendModes[value][1]); - } - - /** - * Sets the polygon offset. - * - * @param {number} value - the polygon offset - * @param {number} scale - the polygon offset scale - */ - setPolygonOffset(value, scale) - { - this.gl.polygonOffset(value, scale); - } - - /** - * Disables all the vaos in use - * - */ - resetAttributes() - { - for (let i = 0; i < this.attribState.tempAttribState.length; i++) - { - this.attribState.tempAttribState[i] = 0; - } - - for (let i = 0; i < this.attribState.attribState.length; i++) - { - this.attribState.attribState[i] = 0; - } - - // im going to assume one is always active for performance reasons. - for (let i = 1; i < this.maxAttribs; i++) - { - this.gl.disableVertexAttribArray(i); - } - } - - // used - /** - * Resets all the logic and disables the vaos - */ - reset() - { - // unbind any VAO if they exist.. - if (this.nativeVaoExtension) - { - this.nativeVaoExtension.bindVertexArrayOES(null); - } - - // reset all attributes.. - this.resetAttributes(); - - this.gl.pixelStorei(this.gl.UNPACK_FLIP_Y_WEBGL, false); - - this.setBlendMode(0); - - // TODO? - // this.setState(this.defaultState); - } - - /** - * checks to see which updates should be checked based on which settings have been activated - * for example if blend is enabled then we shold check the blend modes each time the state is changed - * or if poygon fill is activated then we need to check if the polygone offset changes. - * The idea is that we only check what we have too. - * - * @param {Function} func the checking function to add or remove - * @param {boolean} value should the check function be added or removed. - */ - updateCheck(func, value) - { - const index = this.checks.indexOf(func); - - if (value && index === -1) - { - this.checks.push(func); - } - else if (!value && index !== -1) - { - this.checks.splice(index, 1); - } - } - - /** - * A private little wrapper function that we call to check the blend mode. - * - * @static - * @private - * @param {PIXI.StateSystem} System the System to perform the state check on - * @param {PIXI.State} state the state that the blendMode will pulled from - */ - static checkBlendMode(system, state) - { - system.setBlendMode(state.blendMode); - } - - // TODO - add polygon offset? -} diff --git a/packages/core/src/renderers/systems/StencilSystem.js b/packages/core/src/renderers/systems/StencilSystem.js deleted file mode 100644 index 5d538cb..0000000 --- a/packages/core/src/renderers/systems/StencilSystem.js +++ /dev/null @@ -1,133 +0,0 @@ -import WebGLSystem from './WebGLSystem'; - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class StencilSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - this.stencilMaskStack = []; - } - - /** - * Changes the mask stack that is used by this System. - * - * @param {PIXI.Graphics[]} stencilMaskStack - The mask stack - */ - setMaskStack(stencilMaskStack) - { - const gl = this.renderer.gl; - - if (stencilMaskStack.length !== this.stencilMaskStack.length) - { - if (stencilMaskStack.length === 0) - { - gl.disable(gl.STENCIL_TEST); - } - else - { - gl.enable(gl.STENCIL_TEST); - } - } - - this.stencilMaskStack = stencilMaskStack; - } - - /** - * Applies the Mask and adds it to the current stencil stack. @alvin - * - * @param {PIXI.Graphics} graphics - The mask - */ - pushStencil(graphics) - { - this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); - - // this.renderer._activeRenderTarget.attachStencilBuffer(); - - const gl = this.renderer.gl; - const prevMaskCount = this.stencilMaskStack.length; - - if (prevMaskCount === 0) - { - gl.enable(gl.STENCIL_TEST); - } - - this.stencilMaskStack.push(graphics); - - // Increment the refference stencil value where the new mask overlaps with the old ones. - gl.colorMask(false, false, false, false); - gl.stencilFunc(gl.EQUAL, prevMaskCount, this._getBitwiseMask()); - gl.stencilOp(gl.KEEP, gl.KEEP, gl.INCR); - this.renderer.plugins.graphics.render(graphics); - - this._useCurrent(); - } - - /** - * Removes the last mask from the stencil stack. @alvin - */ - popStencil() - { - this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); - - const gl = this.renderer.gl; - const graphics = this.stencilMaskStack.pop(); - - if (this.stencilMaskStack.length === 0) - { - // the stack is empty! - gl.disable(gl.STENCIL_TEST); - gl.clear(gl.STENCIL_BUFFER_BIT); - gl.clearStencil(0); - } - else - { - // Decrement the refference stencil value where the popped mask overlaps with the other ones - gl.colorMask(false, false, false, false); - gl.stencilOp(gl.KEEP, gl.KEEP, gl.DECR); - this.renderer.plugins.graphics.render(graphics); - - this._useCurrent(); - } - } - - /** - * Setup renderer to use the current stencil data. - */ - _useCurrent() - { - const gl = this.renderer.gl; - - gl.colorMask(true, true, true, true); - gl.stencilFunc(gl.EQUAL, this.stencilMaskStack.length, this._getBitwiseMask()); - gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP); - } - - /** - * Fill 1s equal to the number of acitve stencil masks. - * - * @return {number} The bitwise mask. - */ - _getBitwiseMask() - { - return (1 << this.stencilMaskStack.length) - 1; - } - - /** - * Destroys the mask stack. - * - */ - destroy() - { - super.destroy(this); - - this.stencilMaskStack = null; - } -} diff --git a/packages/core/src/AbstractRenderer.js b/packages/core/src/AbstractRenderer.js new file mode 100644 index 0000000..64b2eb1 --- /dev/null +++ b/packages/core/src/AbstractRenderer.js @@ -0,0 +1,353 @@ +import { hex2string, hex2rgb } from '@pixi/utils'; +import { Matrix, Rectangle } from '@pixi/math'; +import { RENDERER_TYPE } from '@pixi/constants'; +import { settings } from '@pixi/settings'; +import { Container } from '@pixi/display'; +import RenderTexture from './renderTexture/RenderTexture'; +import EventEmitter from 'eventemitter3'; + +const tempMatrix = new Matrix(); + +/** + * 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 EventEmitter + * @memberof PIXI + */ +export default class AbstractRenderer extends EventEmitter +{ + // eslint-disable-next-line valid-jsdoc + /** + * @param {string} system - The name of the system this renderer is for. + * @param {object} [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.transparent=false] - If the render view is transparent, default false + * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false + * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. The + * resolution of the renderer retina would be 2. + * @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 {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when rendering, + * stopping pixel interpolation. + */ + constructor(system, options, arg2, arg3) + { + super(); + + // Support for constructor(system, screenWidth, screenHeight, options) + if (typeof options === 'number') + { + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + }, arg3); + } + + // 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 = RENDERER_TYPE.UNKNOWN; + + /** + * Measurements of the screen. (0, 0, screenWidth, screenHeight) + * + * Its safe to use as filterArea or hitArea for 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 1 + */ + this.resolution = options.resolution || settings.RESOLUTION; + + /** + * Whether the render view is transparent + * + * @member {boolean} + */ + this.transparent = options.transparent; + + /** + * Whether css dimensions of canvas view should be resized to screen dimensions automatically + * + * @member {boolean} + */ + this.autoResize = options.autoResize || false; + + /** + * Tracks the blend modes useful for this renderer. + * + * @member {object} + */ + this.blendModes = null; + + /** + * 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; + + /** + * If true PixiJS will Math.floor() x/y values when rendering, stopping pixel interpolation. + * Handy for crisp pixel art and speed on legacy devices. + * + * @member {boolean} + */ + this.roundPixels = options.roundPixels; + + /** + * The background color as a number. + * + * @member {number} + * @private + */ + this._backgroundColor = 0x000000; + + /** + * The background color as an [R, G, B] array. + * + * @member {number[]} + * @private + */ + this._backgroundColorRgba = [0, 0, 0, 0]; + + /** + * The background color as a string. + * + * @member {string} + * @private + */ + this._backgroundColorString = '#000000'; + + this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter + + /** + * This temporary display object used as the parent of the currently being rendered item + * + * @member {PIXI.DisplayObject} + * @private + */ + this._tempDisplayObjectParent = new Container(); + + /** + * The last root object that the renderer tried to render. + * + * @member {PIXI.DisplayObject} + * @private + */ + this._lastObjectRendered = this._tempDisplayObjectParent; + + /** + * Collection of plugins. + * @readonly + * @member {object} + */ + this.plugins = {}; + } + + /** + * Initialize the plugins. + * + * @protected + * @param {object} staticMap - The dictionary of staticly saved plugins. + */ + initPlugins(staticMap) + { + 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() + { + return this.view.width; + } + + /** + * Same as view.height, actual number of pixels in the canvas by vertical + * + * @member {number} + * @readonly + * @default 600 + */ + get height() + { + return this.view.height; + } + + /** + * Resizes the screen and canvas to the specified width and height + * Canvas dimensions are multiplied by resolution + * + * @param {number} screenWidth - the new width of the screen + * @param {number} screenHeight - the new height of the screen + */ + resize(screenWidth, screenHeight) + { + this.screen.width = screenWidth; + this.screen.height = screenHeight; + + this.view.width = screenWidth * this.resolution; + this.view.height = screenHeight * this.resolution; + + if (this.autoResize) + { + this.view.style.width = `${screenWidth}px`; + this.view.style.height = `${screenHeight}px`; + } + } + + /** + * 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 {PIXI.DisplayObject} displayObject - The displayObject the object will be generated from + * @param {number} scaleMode - Should be one of the scaleMode consts + * @param {number} resolution - The resolution / device pixel ratio of the texture being generated + * @param {PIXI.Rectangle} [region] - The region of the displayObject, that shall be rendered, + * if no region is specified, defaults to the local bounds of the displayObject. + * @return {PIXI.Texture} a texture of the graphics object + */ + generateTexture(displayObject, scaleMode, resolution, region) + { + region = region || displayObject.getLocalBounds(); + + // 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(region.width | 0, region.height | 0, scaleMode, resolution); + + tempMatrix.tx = -region.x; + tempMatrix.ty = -region.y; + + this.render(displayObject, renderTexture, false, tempMatrix, true); + + return renderTexture; + } + + /** + * Removes everything from the renderer and optionally removes the Canvas DOM element. + * + * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. + */ + destroy(removeView) + { + 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); + } + + this.plugins = null; + + this.type = RENDERER_TYPE.UNKNOWN; + + this.view = null; + + this.screen = null; + + this.resolution = 0; + + this.transparent = false; + + this.autoResize = false; + + this.blendModes = null; + + this.options = null; + + this.preserveDrawingBuffer = false; + this.clearBeforeRender = false; + + this.roundPixels = false; + + this._backgroundColor = 0; + this._backgroundColorRgba = null; + this._backgroundColorString = null; + + this._tempDisplayObjectParent = null; + this._lastObjectRendered = null; + } + + /** + * The background color to fill if not transparent + * + * @member {number} + */ + get backgroundColor() + { + return this._backgroundColor; + } + + set backgroundColor(value) // eslint-disable-line require-jsdoc + { + this._backgroundColor = value; + this._backgroundColorString = hex2string(value); + hex2rgb(value, this._backgroundColorRgba); + } +} diff --git a/packages/core/src/Renderer.js b/packages/core/src/Renderer.js new file mode 100644 index 0000000..2227e78 --- /dev/null +++ b/packages/core/src/Renderer.js @@ -0,0 +1,338 @@ +import AbstractRenderer from './AbstractRenderer'; +import { sayHello } from '@pixi/utils'; +import MaskSystem from './mask/MaskSystem'; +import StencilSystem from './mask/StencilSystem'; +import FilterSystem from './filters/FilterSystem'; +import FramebufferSystem from './framebuffer/FramebufferSystem'; +import RenderTextureSystem from './renderTexture/RenderTextureSystem'; +import TextureSystem from './textures/TextureSystem'; +import ProjectionSystem from './projection/ProjectionSystem'; +import StateSystem from './state/StateSystem'; +import GeometrySystem from './geometry/GeometrySystem'; +import ShaderSystem from './shader/ShaderSystem'; +import ContextSystem from './context/ContextSystem'; +import BatchSystem from './batch/BatchSystem'; +import TextureGCSystem from './textures/TextureGCSystem'; +import { RENDERER_TYPE } from '@pixi/constants'; +import UniformGroup from './shader/UniformGroup'; +import { Matrix } from '@pixi/math'; +import Runner from 'mini-runner'; + +/** + * 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 Render works by automatically managing webGLBatchs. + * 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 :) + * + * @class + * @memberof PIXI + * @extends PIXI.AbstractRenderer + */ +export default class Renderer extends AbstractRenderer +{ + // eslint-disable-next-line valid-jsdoc + /** + * + * @param {object} [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.transparent=false] - If the render view is transparent, default false + * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false + * @param {boolean} [options.antialias=false] - sets antialias. If not available natively then FXAA + * antialiasing is used + * @param {boolean} [options.forceFXAA=false] - forces FXAA antialiasing to be used over native. + * FXAA is faster, but may not always look as great + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. + * The resolution of the renderer retina would be 2. + * @param {boolean} [options.clearBeforeRender=true] - This sets if the renderer will clear + * the canvas or not before the new render pass. If you wish to set this to false, you *must* set + * preserveDrawingBuffer to `true`. + * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, + * enable this if you need to call toDataUrl on the webgl context. + * @param {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when + * rendering, stopping pixel interpolation. + * @param {number} [options.backgroundColor=0x000000] - The background color of the rendered area + * (shown if not transparent). + * @param {boolean} [options.legacy=false] - If true PixiJS will aim to ensure compatibility + * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. + * @param {string} [options.powerPreference] - Parameter passed to webgl context, set to "high-performance" + * for devices with dual graphics card + */ + constructor(options, arg2, arg3) + { + super('WebGL', options, arg2, arg3); + + /** + * The type of this renderer as a standardised const + * + * @member {number} + * @see PIXI.RENDERER_TYPE + */ + this.type = RENDERER_TYPE.WEBGL; + + // this will be set by the contextSystem (this.context) + this.gl = null; + this.CONTEXT_UID = 0; + this.legacy = !!options.legacy; + + // TODO legacy! + + // runners! + this.runners = { + destroy: new Runner('destroy'), + contextChange: new Runner('contextChange', 1), + reset: new Runner('reset'), + update: new Runner('update'), + postrender: new Runner('postrender'), + prerender: new Runner('prerender'), + resize: new Runner('resize', 2), + }; + + 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(GeometrySystem, 'geometry') + .addSystem(FramebufferSystem, 'framebuffer') + .addSystem(StencilSystem, 'stencil') + .addSystem(ProjectionSystem, 'projection') + .addSystem(TextureGCSystem, 'textureGC') + .addSystem(FilterSystem, 'filter') + .addSystem(RenderTextureSystem, 'renderTexture') + .addSystem(BatchSystem, 'batch'); + + this.initPlugins(Renderer.__plugins); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ + if (options.context) + { + this.context.initFromContext(options.context); + } + else + { + this.context.initFromOptions({ + alpha: this.transparent, + antialias: options.antialias, + premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', + stencil: true, + 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); + } + + /** + * Add a new system to the renderer. + * @param {class} ClassRef - Class reference + * @param {string} [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. + * @return {PIXI.Renderer} Return instance of renderer + */ + addSystem(ClassRef, name) + { + if (!name) + { + name = ClassRef.name; + } + + // TODO - read name from class.name.. + + /* + if(name.includes('System')) + { + name = name.replace('System', ''); + name = name.charAt(0).toLowerCase() + name.slice(1); + } + */ + + const system = new ClassRef(this); + + if (this[name]) + { + throw new Error(`Whoops! ${name} is already a manger`); + } + + this[name] = system; + + for (const i in this.runners) + { + this.runners[i].add(system); + } + + return this; + + /** + * 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. + */ + } + + /** + * Renders the object to its webGL view + * + * @param {PIXI.DisplayObject} displayObject - the object to be rendered + * @param {PIXI.RenderTexture} renderTexture - The render texture to render to. + * @param {boolean} [clear] - Should the canvas be cleared before the new render + * @param {PIXI.Transform} [transform] - A transform to apply to the render texture before rendering. + * @param {boolean} [skipUpdateTransform] - Should we skip the update transform pass? + */ + render(displayObject, renderTexture, clear, transform, skipUpdateTransform) + { + // can be handy to know! + this.renderingToScreen = !renderTexture; + + this.runners.prerender.run(); + this.emit('prerender'); + + // 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.parent; + + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = 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.run(); + + this.emit('postrender'); + } + + /** + * Resizes the webGL view to the specified width and height. + * + * @param {number} screenWidth - the new width of the screen + * @param {number} screenHeight - the new height of the screen + */ + resize(screenWidth, screenHeight) + { + AbstractRenderer.prototype.resize.call(this, screenWidth, screenHeight); + + this.runners.resize.run(screenWidth, screenHeight); + } + + /** + * Resets the WebGL state so you can render things however you fancy! + * + * @return {PIXI.Renderer} Returns itself. + */ + reset() + { + this.runners.reset.run(); + + return this; + } + + clear() + { + this.framebuffer.bind(); + this.framebuffer.clear(); + } + + /** + * Removes everything from the renderer (event listeners, spritebatch, etc...) + * + * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. + * See: https://github.com/pixijs/pixi.js/issues/2233 + */ + destroy(removeView) + { + this.runners.destroy.run(); + + // call base destroy + super.destroy(removeView); + + // TODO nullify all the managers.. + this.gl = null; + } + + /** + * 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 + * builds and excluding plugins. + * @name PIXI.Renderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. + */ + + /** + * Adds a plugin to the renderer. + * + * @method + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + static registerPlugin(pluginName, ctor) + { + Renderer.__plugins = Renderer.__plugins || {}; + Renderer.__plugins[pluginName] = ctor; + } +} diff --git a/packages/core/src/System.js b/packages/core/src/System.js new file mode 100644 index 0000000..05a10c8 --- /dev/null +++ b/packages/core/src/System.js @@ -0,0 +1,42 @@ +/** + * System is a base class used for extending systems used by the {@link PIXI.Renderer} + * @see PIXI.Renderer#addSystem + * @class + * @memberof PIXI + */ +export default class System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + /** + * The renderer this manager works for. + * + * @member {PIXI.Renderer} + */ + this.renderer = renderer; + + this.renderer.runners.contextChange.add(this); + } + + /** + * Generic method called when there is a WebGL context change. + * + */ + contextChange() + { + // do some codes init! + } + + /** + * Generic destroy methods to be overridden by the subclass + * + */ + destroy() + { + this.renderer.runners.contextChange.remove(this); + this.renderer = null; + } +} diff --git a/packages/core/src/batch/BatchSystem.js b/packages/core/src/batch/BatchSystem.js new file mode 100644 index 0000000..b04c249 --- /dev/null +++ b/packages/core/src/batch/BatchSystem.js @@ -0,0 +1,68 @@ +import System from '../System'; +import ObjectRenderer from './ObjectRenderer'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class BatchSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + super(renderer); + + /** + * An empty renderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.emptyRenderer = new ObjectRenderer(renderer); + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.currentRenderer = this.emptyRenderer; + } + + /** + * Changes the current renderer to the one given in parameter + * + * @param {PIXI.ObjectRenderer} objectRenderer - The object renderer to use. + */ + setObjectRenderer(objectRenderer) + { + if (this.currentRenderer === objectRenderer) + { + return; + } + + this.currentRenderer.stop(); + this.currentRenderer = objectRenderer; + + this.renderer.state.setState(objectRenderer.state); + + this.currentRenderer.start(); + } + + /** + * This should be called if you wish to do some custom rendering + * It will basically render anything that may be batched up such as sprites + * + */ + flush() + { + this.setObjectRenderer(this.emptyRenderer); + } + + reset() + { + this.setObjectRenderer(this.emptyRenderer); + } +} diff --git a/packages/core/src/batch/ObjectRenderer.js b/packages/core/src/batch/ObjectRenderer.js new file mode 100644 index 0000000..0c851a9 --- /dev/null +++ b/packages/core/src/batch/ObjectRenderer.js @@ -0,0 +1,48 @@ +import System from '../System'; + +/** + * Base for a common object renderer that can be used as a system renderer plugin. + * + * @class + * @extends PIXI.System + * @memberof PIXI + */ +export default class ObjectRenderer extends System +{ + /** + * Starts the renderer and sets the shader + * + */ + start() + { + // set the shader.. + } + + /** + * Stops the renderer + * + */ + stop() + { + this.flush(); + } + + /** + * Stub method for rendering content and emptying the current batch. + * + */ + flush() + { + // flush! + } + + /** + * Renders an object + * + * @param {PIXI.DisplayObject} object - The object to render. + */ + render(object) // eslint-disable-line no-unused-vars + { + // render the object + } +} diff --git a/packages/core/src/context/ContextSystem.js b/packages/core/src/context/ContextSystem.js new file mode 100644 index 0000000..301aab4 --- /dev/null +++ b/packages/core/src/context/ContextSystem.js @@ -0,0 +1,186 @@ +import System from '../System'; +import { settings } from '@pixi/settings'; + +let CONTEXT_UID = 0; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class ContextSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.webGLVersion = 1; + + this.handleContextLost = this.handleContextLost.bind(this); + this.handleContextRestored = this.handleContextRestored.bind(this); + + this.extensions = {}; + + renderer.view.addEventListener('webglcontextlost', this.handleContextLost, false); + renderer.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + } + + get isLost() + { + return (!this.gl || this.gl.isContextLost()); + } + + contextChange(gl) + { + this.gl = gl; + + // restore a context if it was previously lost + if (gl.isContextLost() && gl.getExtension('WEBGL_lose_context')) + { + gl.getExtension('WEBGL_lose_context').restoreContext(); + } + } + + initFromContext(gl) + { + this.gl = gl; + this.validateContext(gl); + this.renderer.gl = gl; + this.renderer.CONTEXT_UID = CONTEXT_UID++; + this.renderer.runners.contextChange.run(gl); + } + + initFromOptions(options) + { + const gl = this.createContext(this.renderer.view, options); + + this.initFromContext(gl); + } + + /** + * Helper class to create a webGL Context + * + * @class + * @memberof PIXI.glCore + * @param canvas {HTMLCanvasElement} the canvas element that we will get the context from + * @param options {Object} An options object that gets passed in to the canvas element containing the context attributes, + * see https://developer.mozilla.org/en/docs/Web/API/HTMLCanvasElement/getContext for the options available + * @return {WebGLRenderingContext} the WebGL context + */ + createContext(canvas, options) + { + let gl; + + if (settings.PREFER_WEBGL_2) + { + gl = canvas.getContext('webgl2', options); + } + + if (gl) + { + this.webGLVersion = 2; + } + else + { + this.webGLVersion = 1; + + gl = canvas.getContext('webgl', options) + || canvas.getContext('experimental-webgl', options); + + if (!gl) + { + // fail, not able to get a context + throw new Error('This browser does not support webGL. Try using the canvas renderer'); + } + } + + this.gl = gl; + + this.getExtensions(); + + return gl; + } + + getExtensions() + { + // time to set up default etensions that pixi uses.. + const gl = this.gl; + const extensions = this.extensions; + + if (this.webGLVersion === 1) + { + extensions.drawBuffers = gl.getExtension('WEBGL_draw_buffers'); + extensions.depthTexture = gl.getExtension('WEBKIT_WEBGL_depth_texture'); + extensions.floatTexture = gl.getExtension('OES_texture_float'); + extensions.loseContext = gl.getExtension('WEBGL_lose_context'); + + extensions.vertexArrayObject = gl.getExtension('OES_vertex_array_object') + || gl.getExtension('MOZ_OES_vertex_array_object') + || gl.getExtension('WEBKIT_OES_vertex_array_object'); + } + + // we don't use any specific WebGL 2 ones yet! + } + + /** + * Handles a lost webgl context + * + * @private + * @param {WebGLContextEvent} event - The context lost event. + */ + handleContextLost(event) + { + event.preventDefault(); + } + + /** + * Handles a restored webgl context + * + * @private + */ + handleContextRestored() + { + this.renderer.runners.contextChange.run(this.gl); + } + + destroy() + { + const view = this.renderer.view; + + // remove listeners + view.removeEventListener('webglcontextlost', this.handleContextLost); + view.removeEventListener('webglcontextrestored', this.handleContextRestored); + + this.gl.useProgram(null); + + if (this.extensions.loseContext) + { + this.extensions.loseContext.loseContext(); + } + } + + postrender() + { + this.gl.flush(); + } + + validateContext(gl) + { + const attributes = gl.getContextAttributes(); + + // this is going to be fairly simple for now.. but at least we have room to grow! + if (!attributes.stencil) + { + /* eslint-disable max-len */ + + /* eslint-disable no-console */ + console.warn('Provided WebGL context does not have a stencil buffer, masks may not render correctly'); + /* eslint-enable no-console */ + + /* eslint-enable max-len */ + } + } +} diff --git a/packages/core/src/filters/Filter.js b/packages/core/src/filters/Filter.js new file mode 100644 index 0000000..9d8cad8 --- /dev/null +++ b/packages/core/src/filters/Filter.js @@ -0,0 +1,140 @@ +import Shader from '../shader/Shader'; +import Program from '../shader/Program'; +import State from '../state/State'; +import { settings } from '@pixi/settings'; +// import extractUniformsFromSrc from './extractUniformsFromSrc'; +import defaultVertex from './defaultFilter.vert'; +import defaultFragment from './defaultFilter.frag'; + +// let math = require('../../math'); +/** + * @class + * @memberof PIXI + * @extends PIXI.Shader + */ +export default class Filter extends Shader +{ + /** + * @param {string} [vertexSrc] - The source of the vertex shader. + * @param {string} [fragmentSrc] - The source of the fragment shader. + * @param {object} [uniforms] - Custom uniforms to use to augment the built-in ones. + */ + constructor(vertexSrc, fragmentSrc, uniforms) + { + const program = Program.from(vertexSrc, fragmentSrc); + + super(program, uniforms); + + /** + * The padding of the filter. Some filters require extra space to breath such as a blur. + * Increasing this will add extra width and height to the bounds of the object that the + * filter is applied to. + * + * @member {number} + */ + this.padding = 0; + + /** + * The resolution of the filter. Setting this to be lower will lower the quality but + * increase the performance of the filter. + * + * @member {number} + */ + this.resolution = settings.RESOLUTION; + + /** + * If enabled is true the filter is applied, if false it will not. + * + * @member {boolean} + */ + this.enabled = true; + + /** + * If enabled, PixiJS will fit the filter area into boundaries for better performance. + * Switch it off if it does not work for specific shader. + * + * @member {boolean} + */ + this.autoFit = true; + + /** + * Legacy filters use position and uvs from attributes + * @member {boolean} + * @readonly + */ + this.legacy = !!this.program.attributeData.aTextureCoord; + + /** + * the webGL state the filter requires to render + * @member {PIXI.State} + */ + this.state = new State(); + } + + /** + * Applies the filter + * + * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + * @param {boolean} clear - Should the output be cleared before rendering to it + * @param {object} [currentState] - It's current state of filter. + * There are some useful properties in the currentState : + * target, filters, sourceFrame, destinationFrame, renderTarget, resolution + */ + apply(filterManager, input, output, clear, currentState, derp) // eslint-disable-line no-unused-vars + { + // do as you please! + + filterManager.applyFilter(this, input, output, clear, currentState, derp); + + // or just do a regular render.. + } + + /** + * Sets the blendmode of the filter + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL + */ + get blendMode() + { + return this.state.blendMode; + } + + set blendMode(value) // eslint-disable-line require-jsdoc + { + this.state.blendMode = value; + } + + /** + * The default vertex shader source + * + * @static + * @constant + */ + static get defaultVertexSrc() + { + return defaultVertex; + } + + /** + * The default fragment shader source + * + * @static + * @constant + */ + static get defaultFragmentSrc() + { + return defaultFragment; + } +} + +/** + * Used for caching shader IDs + * + * @static + * @private + */ +Filter.SOURCE_KEY_MAP = {}; + diff --git a/packages/core/src/filters/FilterSystem.js b/packages/core/src/filters/FilterSystem.js new file mode 100644 index 0000000..3174318 --- /dev/null +++ b/packages/core/src/filters/FilterSystem.js @@ -0,0 +1,431 @@ +import System from '../System'; + +import RenderTexture from '../renderTexture/RenderTexture'; +import Quad from '../utils/Quad'; +import QuadUv from '../utils/QuadUv'; +import { Rectangle } from '@pixi/math'; +import * as filterTransforms from './filterTransforms'; +import bitTwiddle from 'bit-twiddle'; +import UniformGroup from '../shader/UniformGroup'; +import { DRAW_MODES } from '@pixi/constants'; + +// +/** + * @ignore + * @class + */ +class FilterState +{ + /** + * + */ + constructor() + { + this.renderTexture = null; + this.sourceFrame = new Rectangle(); + this.destinationFrame = new Rectangle(); + this.filters = []; + this.target = null; + this.legacy = false; + this.resolution = 1; + } +} + +/** + * @class + * @memberof PIXI.systems + * @extends PIXI.System + */ +export default class FilterSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + /** + * stores a bunch of PO2 textures used for filtering + * @type {Object} + */ + this.texturePool = {}; + + /** + * a pool for storing filter states, save us creating new ones each tick + * @type {Array} + */ + this.statePool = []; + + /** + * A very simple geometry used when drawing a filter effect to the screen + * @type {Quad} + */ + this.quad = new Quad(); + + this.quadUv = new QuadUv(); + + /** + * Temporary rect for maths + * @type {PIXI.Rectangle} + */ + this.tempRect = new Rectangle(); + + this.activeState = {}; + + /** + * this uniform group is attached to filter uniforms when used + * @type {UniformGroup} + */ + this.globalUniforms = new UniformGroup({ + sourceFrame: this.tempRect, + destinationFrame: this.tempRect, + + // legacy variables + filterArea: new Float32Array(4), + filterClamp: new Float32Array(4), + }, true); + } + + /** + * Adds a new filter to the System. + * + * @param {PIXI.DisplayObject} target - The target of the filter to render. + * @param {PIXI.Filter[]} filters - The filters to apply. + */ + push(target, filters) + { + const renderer = this.renderer; + const filterStack = this.renderer.renderTexture.defaultFilterStack; + const state = this.statePool.pop() || new FilterState(); + + let resolution = filters[0].resolution; + let padding = filters[0].padding; + let autoFit = filters[0].autoFit; + let legacy = filters[0].legacy; + + for (let i = 1; i < filters.length; i++) + { + const filter = filters[i]; + + // lets use the lowest resolution.. + resolution = Math.min(resolution, filter.resolution); + // and the largest amount of padding! + padding = Math.max(padding, filter.padding); + // only auto fit if all filters are autofit + autoFit = autoFit || filter.autoFit; + + legacy = legacy || filter.legacy; + } + + filterStack.push(state); + + state.resolution = resolution; + + state.legacy = legacy; + + // round to whole number based on resolution + // TODO move that to the shader too? + state.sourceFrame = target.filterArea || target.getBounds(true); + + state.sourceFrame.pad(padding); + + if (autoFit) + { + state.sourceFrame.fit(this.renderer.renderTexture.destinationFrame); + } + + state.sourceFrame.round(resolution); + + state.renderTexture = this.getPotFilterTexture(state.sourceFrame.width, state.sourceFrame.height, resolution); + state.filters = filters; + + state.destinationFrame.width = state.renderTexture.width; + state.destinationFrame.height = state.renderTexture.height; + + state.renderTexture.filterFrame = state.sourceFrame; + renderer.renderTexture.bind(state.renderTexture, state.sourceFrame);// /, state.destinationFrame); + renderer.renderTexture.clear(); + } + + /** + * Pops off the filter and applies it. + * + */ + pop() + { + const renderer = this.renderer; + const filterStack = renderer.renderTexture.defaultFilterStack; + const state = filterStack.pop(); + const filters = state.filters; + + this.activeState = state; + + const globalUniforms = this.globalUniforms.uniforms; + + globalUniforms.sourceFrame = state.sourceFrame; + globalUniforms.destinationFrame = state.destinationFrame; + globalUniforms.resolution = state.resolution; + + // only update the rect if its legacy.. + if (state.legacy) + { + const filterArea = globalUniforms.filterArea; + const filterClamp = globalUniforms.filterClamp; + + filterArea[0] = state.destinationFrame.width; + filterArea[1] = state.destinationFrame.height; + filterArea[2] = state.sourceFrame.x; + filterArea[3] = state.sourceFrame.y; + + filterClamp[0] = 0.5 / state.resolution / state.destinationFrame.width; + filterClamp[1] = 0.5 / state.resolution / state.destinationFrame.height; + filterClamp[2] = (state.sourceFrame.width - 0.5) / state.resolution / state.destinationFrame.width; + filterClamp[3] = (state.sourceFrame.height - 0.5) / state.resolution / state.destinationFrame.height; + } + + this.globalUniforms.update(); + + const lastState = filterStack[filterStack.length - 1]; + + if (filters.length === 1) + { + filters[0].apply(this, state.renderTexture, lastState.renderTexture, false, state); + + this.returnFilterTexture(state.renderTexture); + } + else + { + let flip = state.renderTexture; + let flop = this.getPotFilterTexture( + flip.width, + flip.height, + state.resolution + ); + + flop.filterFrame = flip.filterFrame; + + let i = 0; + + for (i = 0; i < filters.length - 1; ++i) + { + filters[i].apply(this, flip, flop, true, state); + + const t = flip; + + flip = flop; + flop = t; + } + + filters[i].apply(this, flip, lastState.renderTexture, false, state); + + this.returnFilterTexture(flip); + this.returnFilterTexture(flop); + } + + this.statePool.push(state); + } + + /** + * Draws a filter. + * + * @param {PIXI.Filter} filter - The filter to draw. + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + * @param {boolean} clear - Should the output be cleared before rendering to it + */ + applyFilter(filter, input, output, clear) + { + const renderer = this.renderer; + + renderer.renderTexture.bind(output, output ? output.filterFrame : null); + + if (clear) + { + // gl.disable(gl.SCISSOR_TEST); + renderer.renderTexture.clear(); + // gl.enable(gl.SCISSOR_TEST); + } + + // set the uniforms.. + filter.uniforms.uSampler = input; + filter.uniforms.filterGlobals = this.globalUniforms; + + // TODO make it so that the order of this does not matter.. + // because it does at the moment cos of global uniforms. + // they need to get resynced + + renderer.state.setState(filter.state); + renderer.shader.bind(filter); + + if (filter.legacy) + { + this.quadUv.map(input._frame, input.filterFrame); + + renderer.geometry.bind(this.quadUv); + renderer.geometry.draw(DRAW_MODES.TRIANGLES); + } + else + { + renderer.geometry.bind(this.quad); + renderer.geometry.draw(DRAW_MODES.TRIANGLE_STRIP); + } + } + + /** + * Calculates the mapped matrix. + * + * TODO playing around here.. this is temporary - (will end up in the shader) + * this returns a matrix that will normalise map filter cords in the filter to screen space + * + * @param {PIXI.Matrix} outputMatrix - the matrix to output to. + * @return {PIXI.Matrix} The mapped matrix. + */ + calculateScreenSpaceMatrix(outputMatrix) + { + const currentState = this.activeState; + + return filterTransforms.calculateScreenSpaceMatrix( + outputMatrix, + currentState.sourceFrame, + currentState.destinationFrame + ); + } + + /** + * This will map the filter coord so that a texture can be used based on the transform of a sprite + * + * @param {PIXI.Matrix} outputMatrix - The matrix to output to. + * @param {PIXI.Sprite} sprite - The sprite to map to. + * @return {PIXI.Matrix} The mapped matrix. + */ + calculateSpriteMatrix(outputMatrix, sprite) + { + const currentState = this.activeState; + + return filterTransforms.calculateSpriteMatrix( + outputMatrix, + currentState.sourceFrame, + currentState.destinationFrame, + sprite + ); + } + + /** + * Destroys this Filter System. + * + * @param {boolean} [contextLost=false] context was lost, do not free shaders + * + */ + destroy(contextLost = false) + { + if (!contextLost) + { + this.emptyPool(); + } + else + { + this.texturePool = {}; + } + } + + /** + * Gets a Power-of-Two render texture. + * + * TODO move to a seperate class could be on renderer? + * also - could cause issue with multiple contexts? + * + * @private + * @param {WebGLRenderingContext} gl - The webgl rendering context + * @param {number} minWidth - The minimum width of the render target. + * @param {number} minHeight - The minimum height of the render target. + * @param {number} resolution - The resolution of the render target. + * @return {PIXI.RenderTarget} The new render target. + */ + getPotFilterTexture(minWidth, minHeight, resolution) + { + minWidth = bitTwiddle.nextPow2(minWidth); + minHeight = bitTwiddle.nextPow2(minHeight); + resolution = resolution || 1; + + const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + + if (!this.texturePool[key]) + { + this.texturePool[key] = []; + } + + let renderTexture = this.texturePool[key].pop(); + + if (!renderTexture) + { + // temporary bypass cache.. + // internally - this will cause a texture to be bound.. + renderTexture = RenderTexture.create({ + width: minWidth, + height: minHeight, + resolution, + }); + } + + return renderTexture; + } + + /** + * Gets extra render texture to use inside current filter + * + * @param {number} resolution resolution of the renderTexture + * @returns {PIXI.RenderTexture} + */ + getFilterTexture(resolution) + { + const rt = this.activeState.renderTexture; + + const filterTexture = this.getPotFilterTexture(rt.width, rt.height, resolution || rt.baseTexture.resolution); + + filterTexture.filterFrame = rt.filterFrame; + + return filterTexture; + } + + /** + * Frees a render texture back into the pool. + * + * @param {PIXI.RenderTarget} renderTexture - The renderTarget to free + */ + returnFilterTexture(renderTexture) + { + renderTexture.filterFrame = null; + + const base = renderTexture.baseTexture; + + const minWidth = base.width; + const minHeight = base.height; + + const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + + this.texturePool[key].push(renderTexture); + } + + /** + * Empties the texture pool. + * + */ + emptyPool() + { + for (const i in this.texturePool) + { + const textures = this.texturePool[i]; + + if (textures) + { + for (let j = 0; j < textures.length; j++) + { + textures[j].destroy(true); + } + } + } + + this.texturePool = {}; + } +} diff --git a/packages/core/src/filters/defaultFilter.frag b/packages/core/src/filters/defaultFilter.frag new file mode 100644 index 0000000..a14c1be --- /dev/null +++ b/packages/core/src/filters/defaultFilter.frag @@ -0,0 +1,22 @@ +varying vec2 vTextureCoord; +varying vec2 vFilterCoord; + +uniform sampler2D uSampler; +uniform sampler2D filterSampler; + +void main(void){ + vec4 masky = texture2D(filterSampler, vFilterCoord); + vec4 sample = texture2D(uSampler, vTextureCoord); + vec4 color; + if(mod(vFilterCoord.x, 1.0) > 0.5) + { + color = vec4(1.0, 0.0, 0.0, 1.0); + } + else + { + color = vec4(0.0, 1.0, 0.0, 1.0); + } + // gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0); + gl_FragColor = mix(sample, masky, 0.5); + gl_FragColor *= sample.a; +} diff --git a/packages/core/src/filters/defaultFilter.vert b/packages/core/src/filters/defaultFilter.vert new file mode 100644 index 0000000..e1febca --- /dev/null +++ b/packages/core/src/filters/defaultFilter.vert @@ -0,0 +1,14 @@ +attribute vec2 aVertexPosition; +attribute vec2 aTextureCoord; + +uniform mat3 projectionMatrix; +uniform mat3 filterMatrix; + +varying vec2 vTextureCoord; +varying vec2 vFilterCoord; + +void main(void){ + gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); + vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy; + vTextureCoord = aTextureCoord ; +} diff --git a/packages/core/src/filters/filterTransforms.js b/packages/core/src/filters/filterTransforms.js new file mode 100644 index 0000000..87a3cd6 --- /dev/null +++ b/packages/core/src/filters/filterTransforms.js @@ -0,0 +1,50 @@ +import { Matrix } from '@pixi/math'; + +/** + * Calculates the mapped matrix + * @param filterArea {Rectangle} The filter area + * @param sprite {Sprite} the target sprite + * @param outputMatrix {Matrix} @alvin + * @private + */ +// this returns a matrix that will normalise map filter cords in the filter to screen space +export function calculateScreenSpaceMatrix(outputMatrix, filterArea, textureSize) +{ + // TODO unwrap? + const mappedMatrix = outputMatrix.identity(); + + mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); + + mappedMatrix.scale(textureSize.width, textureSize.height); + + return mappedMatrix; +} + +export function calculateNormalizedScreenSpaceMatrix(outputMatrix, filterArea, textureSize) +{ + const mappedMatrix = outputMatrix.identity(); + + mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); + + const translateScaleX = (textureSize.width / filterArea.width); + const translateScaleY = (textureSize.height / filterArea.height); + + mappedMatrix.scale(translateScaleX, translateScaleY); + + return mappedMatrix; +} + +// this will map the filter coord so that a texture can be used based on the transform of a sprite +export function calculateSpriteMatrix(outputMatrix, filterArea, textureSize, sprite) +{ + const orig = sprite._texture.orig; + const mappedMatrix = outputMatrix.set(textureSize.width, 0, 0, textureSize.height, filterArea.x, filterArea.y); + const worldTransform = sprite.worldTransform.copyTo(Matrix.TEMP_MATRIX); + + worldTransform.invert(); + mappedMatrix.prepend(worldTransform); + mappedMatrix.scale(1.0 / orig.width, 1.0 / orig.height); + mappedMatrix.translate(sprite.anchor.x, sprite.anchor.y); + + return mappedMatrix; +} diff --git a/packages/core/src/filters/spriteMask/SpriteMaskFilter.js b/packages/core/src/filters/spriteMask/SpriteMaskFilter.js new file mode 100644 index 0000000..91697b7 --- /dev/null +++ b/packages/core/src/filters/spriteMask/SpriteMaskFilter.js @@ -0,0 +1,63 @@ +import Filter from '../Filter'; +import { Matrix } from '@pixi/math'; +import vertex from './spriteMaskFilter.vert'; +import fragment from './spriteMaskFilter.frag'; +import { default as TextureMatrix } from '../../textures/TextureMatrix'; + +/** + * The SpriteMaskFilter class + * + * @class + * @extends PIXI.Filter + * @memberof PIXI + */ +export default class SpriteMaskFilter extends Filter +{ + /** + * @param {PIXI.Sprite} sprite - the target sprite + */ + constructor(sprite) + { + const maskMatrix = new Matrix(); + + super(vertex, fragment); + + sprite.renderable = false; + + this.maskSprite = sprite; + this.maskMatrix = maskMatrix; + } + + /** + * Applies the filter + * + * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + */ + apply(filterManager, input, output) + { + const maskSprite = this.maskSprite; + const tex = this.maskSprite.texture; + + if (!tex.valid) + { + return; + } + if (!tex.transform) + { + // margin = 0.0, let it bleed a bit, shader code becomes easier + // assuming that atlas textures were made with 1-pixel padding + tex.transform = new TextureMatrix(tex, 0.0); + } + tex.transform.update(); + + this.uniforms.mask = tex; + this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite) + .prepend(tex.transform.mapCoord); + this.uniforms.alpha = maskSprite.worldAlpha; + this.uniforms.maskClamp = tex.transform.uClampFrame; + + filterManager.applyFilter(this, input, output); + } +} diff --git a/packages/core/src/filters/spriteMask/spriteMaskFilter.frag b/packages/core/src/filters/spriteMask/spriteMaskFilter.frag new file mode 100644 index 0000000..0e4aef8 --- /dev/null +++ b/packages/core/src/filters/spriteMask/spriteMaskFilter.frag @@ -0,0 +1,23 @@ +varying vec2 vMaskCoord; +varying vec2 vTextureCoord; + +uniform sampler2D uSampler; +uniform sampler2D mask; +uniform float alpha; +uniform vec4 maskClamp; + +void main(void) +{ + float clip = step(3.5, + step(maskClamp.x, vMaskCoord.x) + + step(maskClamp.y, vMaskCoord.y) + + step(vMaskCoord.x, maskClamp.z) + + step(vMaskCoord.y, maskClamp.w)); + + vec4 original = texture2D(uSampler, vTextureCoord); + vec4 masky = texture2D(mask, vMaskCoord); + + original *= (masky.r * masky.a * alpha * clip); + + gl_FragColor = original; +} diff --git a/packages/core/src/filters/spriteMask/spriteMaskFilter.vert b/packages/core/src/filters/spriteMask/spriteMaskFilter.vert new file mode 100644 index 0000000..d23a30c --- /dev/null +++ b/packages/core/src/filters/spriteMask/spriteMaskFilter.vert @@ -0,0 +1,16 @@ +attribute vec2 aVertexPosition; +attribute vec2 aTextureCoord; + +uniform mat3 projectionMatrix; +uniform mat3 otherMatrix; + +varying vec2 vMaskCoord; +varying vec2 vTextureCoord; + +void main(void) +{ + gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); + + vTextureCoord = aTextureCoord; + vMaskCoord = ( otherMatrix * vec3( aTextureCoord, 1.0) ).xy; +} diff --git a/packages/core/src/framebuffer/FrameBuffer.js b/packages/core/src/framebuffer/FrameBuffer.js new file mode 100644 index 0000000..84e9fcc --- /dev/null +++ b/packages/core/src/framebuffer/FrameBuffer.js @@ -0,0 +1,106 @@ +import Texture from '../textures/BaseTexture'; +import { FORMATS, TYPES } from '@pixi/constants'; + +/** + * Frame buffer + * @class + * @memberof PIXI + */ +export default class FrameBuffer +{ + constructor(width, height) + { + this.width = width || 100; + this.height = height || 100; + + this.stencil = false; + this.depth = false; + + this.dirtyId = 0; + this.dirtyFormat = 0; + this.dirtySize = 0; + + this.depthTexture = null; + this.colorTextures = []; + + this.glFrameBuffers = {}; + } + + get colorTexture() + { + return this.colorTextures[0]; + } + + addColorTexture(index, texture) + { + // TODO add some validation to the texture - same width / height etc? + this.colorTextures[index || 0] = texture || new Texture(null, { scaleMode: 0, + resolution: 1, + mipmap: false, + width: this.width, + height: this.height });// || new Texture(); + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + addDepthTexture(texture) + { + /* eslint-disable max-len */ + this.depthTexture = texture || new Texture(null, { scaleMode: 0, + resolution: 1, + width: this.width, + height: this.height, + mipmap: false, + format: FORMATS.DEPTH_COMPONENT, + type: TYPES.UNSIGNED_SHORT });// UNSIGNED_SHORT; + /* eslint-disable max-len */ + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + enableDepth() + { + this.depth = true; + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + enableStencil() + { + this.stencil = true; + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + resize(width, height) + { + if (width === this.width && height === this.height) return; + + this.width = width; + this.height = height; + + this.dirtyId++; + this.dirtySize++; + + for (let i = 0; i < this.colorTextures.length; i++) + { + this.colorTextures[i].setSize(width, height); + } + + if (this.depthTexture) + { + this.depthTexture.setSize(width, height); + } + } +} diff --git a/packages/core/src/framebuffer/FramebufferSystem.js b/packages/core/src/framebuffer/FramebufferSystem.js new file mode 100644 index 0000000..2cc2437 --- /dev/null +++ b/packages/core/src/framebuffer/FramebufferSystem.js @@ -0,0 +1,246 @@ +import System from '../System'; +import { Rectangle } from '@pixi/math'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class FramebufferSystem extends System +{ + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + contextChange() + { + this.gl = this.renderer.gl; + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + this.current = null; + this.viewport = new Rectangle(); + + this.drawBufferExtension = this.renderer.context.extensions.drawBuffers; + } + + bind(framebuffer, frame) + { + const gl = this.gl; + + this.current = framebuffer; + + if (framebuffer) + { + // TODO cacheing layer! + + const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID] || this.initFramebuffer(framebuffer); + + gl.bindFramebuffer(gl.FRAMEBUFFER, fbo.framebuffer); + // makesure all textures are unbound.. + + // now check for updates... + if (fbo.dirtyId !== framebuffer.dirtyId) + { + fbo.dirtyId = framebuffer.dirtyId; + + if (fbo.dirtyFormat !== framebuffer.dirtyFormat) + { + fbo.dirtyFormat = framebuffer.dirtyFormat; + this.updateFramebuffer(framebuffer); + } + else if (fbo.dirtySize !== framebuffer.dirtySize) + { + fbo.dirtySize = framebuffer.dirtySize; + this.resizeFramebuffer(framebuffer); + } + } + + for (let i = 0; i < framebuffer.colorTextures.length; i++) + { + if (framebuffer.colorTextures[i].texturePart) + { + this.renderer.texture.unbind(framebuffer.colorTextures[i].texture); + } + else + { + this.renderer.texture.unbind(framebuffer.colorTextures[i]); + } + } + + if (framebuffer.depthTexture) + { + this.renderer.texture.unbind(framebuffer.depthTexture); + } + + if (frame) + { + this.setViewport(frame.x, frame.y, frame.width, frame.height); + } + else + { + this.setViewport(0, 0, framebuffer.width, framebuffer.height); + } + } + else + { + gl.bindFramebuffer(gl.FRAMEBUFFER, null); + + if (frame) + { + this.setViewport(frame.x, frame.y, frame.width, frame.height); + } + else + { + this.setViewport(0, 0, this.renderer.width, this.renderer.height); + } + } + } + + setViewport(x, y, width, height) + { + const v = this.viewport; + + if (v.width !== width || v.height !== height || v.x !== x || v.y !== y) + { + v.x = x; + v.y = y; + v.width = width; + v.height = height; + + this.gl.viewport(x, y, width, height); + } + } + + get size() + { + if (this.current) + { + // TODO store temp + return { x: 0, y: 0, width: this.current.width, height: this.current.height }; + } + + return { x: 0, y: 0, width: this.renderer.width, height: this.renderer.height }; + } + + clear(r, g, b, a) + { + const gl = this.gl; + + // TODO clear color can be set only one right? + gl.clearColor(r, g, b, a); + gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); + } + + // private functions... + + initFramebuffer(framebuffer) + { + const gl = this.gl; + + // TODO - make this a class? + const fbo = { + framebuffer: gl.createFramebuffer(), + stencil: null, + dirtyId: 0, + dirtyFormat: 0, + dirtySize: 0, + }; + + framebuffer.glFrameBuffers[this.CONTEXT_UID] = fbo; + + return fbo; + } + + resizeFramebuffer(framebuffer) + { + const gl = this.gl; + + if (framebuffer.stencil || framebuffer.depth) + { + gl.bindRenderbuffer(gl.RENDERBUFFER, this.stencil); + gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); + } + } + + updateFramebuffer(framebuffer) + { + const gl = this.gl; + + const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID]; + + // bind the color texture + const colorTextures = framebuffer.colorTextures; + + let count = colorTextures.length; + + if (!this.drawBufferExtension) + { + count = Math.min(count, 1); + } + + const activeTextures = []; + + for (let i = 0; i < count; i++) + { + const texture = framebuffer.colorTextures[i]; + + if (texture.texturePart) + { + this.renderer.texture.bind(texture.texture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.COLOR_ATTACHMENT0 + i, + gl.TEXTURE_CUBE_MAP_NEGATIVE_X + texture.side, + texture.texture._glTextures[this.CONTEXT_UID].texture, + 0); + } + else + { + this.renderer.texture.bind(texture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.COLOR_ATTACHMENT0 + i, + gl.TEXTURE_2D, + texture._glTextures[this.CONTEXT_UID].texture, + 0); + } + + activeTextures.push(gl.COLOR_ATTACHMENT0 + i); + } + + if (this.drawBufferExtension && activeTextures.length > 1) + { + this.drawBufferExtension.drawBuffersWEBGL(activeTextures); + } + + if (framebuffer.depthTexture) + { + const depthTextureExt = this.renderer.context.extensions.depthTexture; + + if (depthTextureExt) + { + const depthTexture = framebuffer.depthTexture; + + this.renderer.texture.bind(depthTexture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.DEPTH_ATTACHMENT, + gl.TEXTURE_2D, + depthTexture._glTextures[this.CONTEXT_UID].texture, + 0); + } + } + + if (framebuffer.stencil || framebuffer.depth) + { + fbo.stencil = gl.createRenderbuffer(); + + gl.bindRenderbuffer(gl.RENDERBUFFER, fbo.stencil); + + // TODO.. this is depth AND stencil? + gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, fbo.stencil); + gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); + // fbo.enableStencil(); + } + } +} diff --git a/packages/core/src/geometry/GLBuffer.js b/packages/core/src/geometry/GLBuffer.js new file mode 100644 index 0000000..0477e8a --- /dev/null +++ b/packages/core/src/geometry/GLBuffer.js @@ -0,0 +1,9 @@ +export default class GLBuffer +{ + constructor(buffer) + { + this.buffer = buffer; + this.updateID = -1; + this.byteLength = -1; + } +} diff --git a/packages/core/src/geometry/GeometrySystem.js b/packages/core/src/geometry/GeometrySystem.js new file mode 100644 index 0000000..959f827 --- /dev/null +++ b/packages/core/src/geometry/GeometrySystem.js @@ -0,0 +1,388 @@ +import System from '../System'; +import GLBuffer from './GLBuffer'; + +const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class GeometrySystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this._activeGeometry = null; + this._activeVao = null; + + this.hasVao = true; + this.hasInstance = true; + } + + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + contextChange() + { + const gl = this.gl = this.renderer.gl; + + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + + // webgl2 + if (!gl.createVertexArray) + { + // webgl 1! + let nativeVaoExtension = this.renderer.context.extensions.vertexArrayObject; + + if (this.renderer.legacy) + { + nativeVaoExtension = null; + } + + if (nativeVaoExtension) + { + gl.createVertexArray = () => + nativeVaoExtension.createVertexArrayOES(); + + gl.bindVertexArray = (vao) => + nativeVaoExtension.bindVertexArrayOES(vao); + + gl.deleteVertexArray = (vao) => + nativeVaoExtension.deleteVertexArrayOES(vao); + } + else + { + this.hasVao = false; + gl.createVertexArray = () => + { + // empty + }; + + gl.bindVertexArray = () => + { + // empty + }; + + gl.deleteVertexArray = () => + { + // empty + }; + } + } + + if (!gl.vertexAttribDivisor) + { + const instanceExt = gl.getExtension('ANGLE_instanced_arrays'); + + if (instanceExt) + { + gl.vertexAttribDivisor = (a, b) => + instanceExt.vertexAttribDivisorANGLE(a, b); + + gl.drawElementsInstanced = (a, b, c, d, e) => + instanceExt.drawElementsInstancedANGLE(a, b, c, d, e); + + gl.drawArraysInstanced = (a, b, c, d) => + instanceExt.drawArraysInstancedANGLE(a, b, c, d); + } + else + { + this.hasInstance = false; + } + } + } + + /** + * Binds geometry so that is can be drawn. Creating a Vao if required + * @private + * @param {PIXI.Geometry} geometry instance of geometry to bind + */ + bind(geometry, shader) + { + shader = shader || this.renderer.shader.shader; + + const gl = this.gl; + + // not sure the best way to address this.. + // currently different shaders require different VAOs for the same geometry + // Still mulling over the best way to solve this one.. + // will likely need to modify the shader attribute locations at run time! + let vaos = geometry.glVertexArrayObjects[this.CONTEXT_UID]; + + if (!vaos) + { + geometry.glVertexArrayObjects[this.CONTEXT_UID] = vaos = {}; + } + + const vao = vaos[shader.program.id] || this.initGeometryVao(geometry, shader.program); + + this._activeGeometry = geometry; + + if (this._activeVao !== vao) + { + this._activeVao = vao; + + if (this.hasVao) + { + gl.bindVertexArray(vao); + } + else + { + this.activateVao(geometry, shader.program); + } + } + + // TODO - optimise later! + // don't need to loop through if nothing changed! + // maybe look to add an 'autoupdate' to geometry? + this.updateBuffers(); + } + + reset() + { + this.unbind(); + } + + updateBuffers() + { + const geometry = this._activeGeometry; + const gl = this.gl; + + for (let i = 0; i < geometry.buffers.length; i++) + { + const buffer = geometry.buffers[i]; + + const glBuffer = buffer._glBuffers[this.CONTEXT_UID]; + + if (buffer._updateID !== glBuffer.updateID) + { + glBuffer.updateID = buffer._updateID; + + // TODO can cache this on buffer! maybe added a getter / setter? + const type = buffer.index ? gl.ELEMENT_ARRAY_BUFFER : gl.ARRAY_BUFFER; + const drawType = buffer.static ? gl.STATIC_DRAW : gl.DYNAMIC_DRAW; + + gl.bindBuffer(type, glBuffer.buffer); + + if (glBuffer.byteLength >= buffer.data.byteLength) + { + // offset is always zero for now! + gl.bufferSubData(type, 0, buffer.data); + } + else + { + gl.bufferData(type, buffer.data, drawType); + } + } + } + } + + checkCompatability(geometry, program) + { + // geometry must have at least all the attributes that the shader requires. + const geometryAttributes = geometry.attributes; + const shaderAttributes = program.attributeData; + + for (const j in shaderAttributes) + { + if (!geometryAttributes[j]) + { + throw new Error(`shader and geometry incompatible, geometry missing the "${j}" attribute`); + } + } + } + + /** + * Creates a Vao with the same structure as the geometry and stores it on the geometry. + * @private + * @param {PIXI.Geometry} geometry instance of geometry to to generate Vao for + */ + initGeometryVao(geometry, program) + { + this.checkCompatability(geometry, program); + + const gl = this.gl; + const CONTEXT_UID = this.CONTEXT_UID; + const buffers = geometry.buffers; + const attributes = geometry.attributes; + + const tempStride = {}; + const tempStart = {}; + + for (const j in buffers) + { + tempStride[j] = 0; + tempStart[j] = 0; + } + + for (const j in attributes) + { + if (!attributes[j].size && program.attributeData[j]) + { + attributes[j].size = program.attributeData[j].size; + } + + tempStride[attributes[j].buffer] += attributes[j].size * byteSizeMap[attributes[j].type]; + } + + for (const j in attributes) + { + const attribute = attributes[j]; + const attribSize = attribute.size; + + if (attribute.stride === undefined) + { + if (tempStride[attribute.buffer] === attribSize * byteSizeMap[attribute.type]) + { + attribute.stride = 0; + } + else + { + attribute.stride = tempStride[attribute.buffer]; + } + } + + if (attribute.start === undefined) + { + attribute.start = tempStart[attribute.buffer]; + + tempStart[attribute.buffer] += attribSize * byteSizeMap[attribute.type]; + } + } + + // first update - and create the buffers! + // only create a gl buffer if it actually gets + for (let i = 0; i < buffers.length; i++) + { + const buffer = buffers[i]; + + if (!buffer._glBuffers[CONTEXT_UID]) + { + buffer._glBuffers[CONTEXT_UID] = new GLBuffer(gl.createBuffer()); + } + } + + // TODO - maybe make this a data object? + // lets wait to see if we need to first! + const vao = gl.createVertexArray(); + + gl.bindVertexArray(vao); + + this.activateVao(geometry, program); + + gl.bindVertexArray(null); + + // add it to the cache! + geometry.glVertexArrayObjects[this.CONTEXT_UID][program.id] = vao; + + return vao; + } + + activateVao(geometry, program) + { + const gl = this.gl; + const CONTEXT_UID = this.CONTEXT_UID; + const buffers = geometry.buffers; + const attributes = geometry.attributes; + + if (geometry.indexBuffer) + { + // first update the index buffer if we have one.. + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, geometry.indexBuffer._glBuffers[CONTEXT_UID].buffer); + } + + let lastBuffer = null; + + // add a new one! + for (const j in attributes) + { + const attribute = attributes[j]; + const buffer = buffers[attribute.buffer]; + const glBuffer = buffer._glBuffers[CONTEXT_UID]; + + if (program.attributeData[j]) + { + if (lastBuffer !== glBuffer) + { + gl.bindBuffer(gl.ARRAY_BUFFER, glBuffer.buffer); + + lastBuffer = glBuffer; + } + + const location = program.attributeData[j].location; + + // TODO introduce state again + // we can optimise this for older devices that have no VAOs + gl.enableVertexAttribArray(location); + + gl.vertexAttribPointer(location, + attribute.size, + attribute.type || gl.FLOAT, + attribute.normalized, + attribute.stride, + attribute.start); + + if (attribute.instance) + { + // TODO calculate instance count based of this... + if (this.hasInstance) + { + gl.vertexAttribDivisor(location, 1); + } + else + { + throw new Error('geometry error, GPU Instancing is not supported on this device'); + } + } + } + } + } + + draw(type, size, start, instanceCount) + { + const gl = this.gl; + const geometry = this._activeGeometry; + + // TODO.. this should not change so maybe cache the function? + + if (geometry.indexBuffer) + { + if (geometry.instanced) + { + /* eslint-disable max-len */ + gl.drawElementsInstanced(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2, instanceCount || 1); + /* eslint-enable max-len */ + } + else + { + gl.drawElements(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2); + } + } + else + if (geometry.instanced) + { + // TODO need a better way to calculate size.. + gl.drawArraysInstanced(type, start, size || geometry.getSize(), instanceCount || 1); + } + else + { + gl.drawArrays(type, start, size || geometry.getSize()); + } + + return this; + } + + unbind() + { + this.gl.bindVertexArray(null); + this._activeVao = null; + this._activeGeometry = null; + } +} diff --git a/packages/core/src/geometry/utils/setVertexAttribArrays.js b/packages/core/src/geometry/utils/setVertexAttribArrays.js new file mode 100644 index 0000000..23b6959 --- /dev/null +++ b/packages/core/src/geometry/utils/setVertexAttribArrays.js @@ -0,0 +1,54 @@ +// var GL_MAP = {}; + +/** + * @param gl {WebGLRenderingContext} The current WebGL context + * @param attribs {*} + * @param state {*} + */ +export default function setVertexAttribArrays(gl, attribs, state) +{ + let i; + + if (state) + { + const tempAttribState = state.tempAttribState; + const attribState = state.attribState; + + for (i = 0; i < tempAttribState.length; i++) + { + tempAttribState[i] = false; + } + + // set the new attribs + for (i = 0; i < attribs.length; i++) + { + tempAttribState[attribs[i].attribute.location] = true; + } + + for (i = 0; i < attribState.length; i++) + { + if (attribState[i] !== tempAttribState[i]) + { + attribState[i] = tempAttribState[i]; + + if (state.attribState[i]) + { + gl.enableVertexAttribArray(i); + } + else + { + gl.disableVertexAttribArray(i); + } + } + } + } + else + { + for (i = 0; i < attribs.length; i++) + { + const attrib = attribs[i]; + + gl.enableVertexAttribArray(attrib.attribute.location); + } + } +} diff --git a/packages/core/src/index.js b/packages/core/src/index.js index 58c456a..fd1f547 100644 --- a/packages/core/src/index.js +++ b/packages/core/src/index.js @@ -1,30 +1,30 @@ import * as resources from './textures/resources'; -import * as systems from './renderers/systems'; +import * as systems from './systems'; -export { resources }; export { systems }; +export { resources }; -export { default as Renderer } from './renderers/Renderer'; -export { default as AbstractRenderer } from './renderers/AbstractRenderer'; -export { default as FrameBuffer } from './textures/FrameBuffer'; +export { default as System } from './System'; +export { default as Renderer } from './Renderer'; +export { default as AbstractRenderer } from './AbstractRenderer'; +export { default as FrameBuffer } from './framebuffer/FrameBuffer'; export { default as CubeTexture } from './textures/CubeTexture'; export { default as BaseTexture } from './textures/BaseTexture'; export { default as Texture } from './textures/Texture'; export { default as TextureMatrix } from './textures/TextureMatrix'; -export { default as RenderTexture } from './textures/RenderTexture'; -export { default as BaseRenderTexture } from './textures/BaseRenderTexture'; +export { default as RenderTexture } from './renderTexture/RenderTexture'; +export { default as BaseRenderTexture } from './renderTexture/BaseRenderTexture'; export { default as TextureUvs } from './textures/TextureUvs'; -export { default as State } from './renderers/State'; -export { default as ObjectRenderer } from './renderers/utils/ObjectRenderer'; -export { default as Quad } from './renderers/utils/Quad'; -export { default as QuadUv } from './renderers/utils/QuadUv'; -export { default as checkMaxIfStatmentsInShader } from './renderers/utils/checkMaxIfStatmentsInShader'; +export { default as State } from './state/State'; +export { default as ObjectRenderer } from './batch/ObjectRenderer'; +export { default as Quad } from './utils/Quad'; +export { default as QuadUv } from './utils/QuadUv'; +export { default as checkMaxIfStatmentsInShader } from './shader/utils/checkMaxIfStatmentsInShader'; export { default as Shader } from './shader/Shader'; export { default as Program } from './shader/Program'; export { default as UniformGroup } from './shader/UniformGroup'; -export { default as SpriteMaskFilter } from './renderers/filters/spriteMask/SpriteMaskFilter'; -export { default as Filter } from './renderers/filters/Filter'; +export { default as SpriteMaskFilter } from './filters/spriteMask/SpriteMaskFilter'; +export { default as Filter } from './filters/Filter'; export { default as Attribute } from './geometry/Attribute'; export { default as Buffer } from './geometry/Buffer'; export { default as Geometry } from './geometry/Geometry'; - diff --git a/packages/core/src/mask/MaskSystem.js b/packages/core/src/mask/MaskSystem.js new file mode 100644 index 0000000..99174c2 --- /dev/null +++ b/packages/core/src/mask/MaskSystem.js @@ -0,0 +1,199 @@ +import System from '../System'; +import AlphaMaskFilter from '../filters/spriteMask/SpriteMaskFilter'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class MaskSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + // TODO - we don't need both! + this.scissor = false; + this.scissorData = null; + this.scissorRenderTarget = null; + + this.enableScissor = false; + + this.alphaMaskPool = []; + this.alphaMaskIndex = 0; + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.DisplayObject} target - Display Object to push the mask to + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + push(target, maskData) + { + // TODO the root check means scissor rect will not + // be used on render textures more info here: + // https://github.com/pixijs/pixi.js/pull/3545 + + if (maskData.texture) + { + this.pushSpriteMask(target, maskData); + } + else if (this.enableScissor + && !this.scissor + && this.renderer._activeRenderTarget.root + && !this.renderer.stencil.stencilMaskStack.length + && maskData.isFastRect()) + { + const matrix = maskData.worldTransform; + + let rot = Math.atan2(matrix.b, matrix.a); + + // use the nearest degree! + rot = Math.round(rot * (180 / Math.PI)); + + if (rot % 90) + { + this.pushStencilMask(maskData); + } + else + { + this.pushScissorMask(target, maskData); + } + } + else + { + this.pushStencilMask(maskData); + } + } + + /** + * Removes the last mask from the mask stack and doesn't return it. + * + * @param {PIXI.DisplayObject} target - Display Object to pop the mask from + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + pop(target, maskData) + { + if (maskData.texture) + { + this.popSpriteMask(target, maskData); + } + else if (this.enableScissor && !this.renderer.stencil.stencilMaskStack.length) + { + this.popScissorMask(target, maskData); + } + else + { + this.popStencilMask(target, maskData); + } + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.RenderTarget} target - Display Object to push the sprite mask to + * @param {PIXI.Sprite} maskData - Sprite to be used as the mask + */ + pushSpriteMask(target, maskData) + { + let alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; + + if (!alphaMaskFilter) + { + alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; + } + + alphaMaskFilter[0].resolution = this.renderer.resolution; + alphaMaskFilter[0].maskSprite = maskData; + + // TODO - may cause issues! + target.filterArea = maskData.getBounds(true); + + this.renderer.filterSystem.pushFilter(target, alphaMaskFilter); + + this.alphaMaskIndex++; + } + + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popSpriteMask() + { + this.renderer.filterSystem.popFilter(); + this.alphaMaskIndex--; + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + pushStencilMask(maskData) + { + this.renderer.batch.flush(); + this.renderer.stencil.pushStencil(maskData); + } + + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popStencilMask() + { + // this.renderer.currentRenderer.stop(); + this.renderer.stencil.popStencil(); + } + + /** + * + * @param {PIXI.DisplayObject} target - Display Object to push the mask to + * @param {PIXI.Graphics} maskData - The masking data. + */ + pushScissorMask(target, maskData) + { + maskData.renderable = true; + + const renderTarget = this.renderer._activeRenderTarget; + + const bounds = maskData.getBounds(); + + bounds.fit(renderTarget.size); + maskData.renderable = false; + + this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); + + const resolution = this.renderer.resolution; + + this.renderer.gl.scissor( + bounds.x * resolution, + (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, + bounds.width * resolution, + bounds.height * resolution + ); + + this.scissorRenderTarget = renderTarget; + this.scissorData = maskData; + this.scissor = true; + } + + /** + * + * + */ + popScissorMask() + { + this.scissorRenderTarget = null; + this.scissorData = null; + this.scissor = false; + + // must be scissor! + const gl = this.renderer.gl; + + gl.disable(gl.SCISSOR_TEST); + } +} diff --git a/packages/core/src/mask/StencilSystem.js b/packages/core/src/mask/StencilSystem.js new file mode 100644 index 0000000..62796a5 --- /dev/null +++ b/packages/core/src/mask/StencilSystem.js @@ -0,0 +1,133 @@ +import System from '../System'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class StencilSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + this.stencilMaskStack = []; + } + + /** + * Changes the mask stack that is used by this System. + * + * @param {PIXI.Graphics[]} stencilMaskStack - The mask stack + */ + setMaskStack(stencilMaskStack) + { + const gl = this.renderer.gl; + + if (stencilMaskStack.length !== this.stencilMaskStack.length) + { + if (stencilMaskStack.length === 0) + { + gl.disable(gl.STENCIL_TEST); + } + else + { + gl.enable(gl.STENCIL_TEST); + } + } + + this.stencilMaskStack = stencilMaskStack; + } + + /** + * Applies the Mask and adds it to the current stencil stack. @alvin + * + * @param {PIXI.Graphics} graphics - The mask + */ + pushStencil(graphics) + { + this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); + + // this.renderer._activeRenderTarget.attachStencilBuffer(); + + const gl = this.renderer.gl; + const prevMaskCount = this.stencilMaskStack.length; + + if (prevMaskCount === 0) + { + gl.enable(gl.STENCIL_TEST); + } + + this.stencilMaskStack.push(graphics); + + // Increment the refference stencil value where the new mask overlaps with the old ones. + gl.colorMask(false, false, false, false); + gl.stencilFunc(gl.EQUAL, prevMaskCount, this._getBitwiseMask()); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.INCR); + this.renderer.plugins.graphics.render(graphics); + + this._useCurrent(); + } + + /** + * Removes the last mask from the stencil stack. @alvin + */ + popStencil() + { + this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); + + const gl = this.renderer.gl; + const graphics = this.stencilMaskStack.pop(); + + if (this.stencilMaskStack.length === 0) + { + // the stack is empty! + gl.disable(gl.STENCIL_TEST); + gl.clear(gl.STENCIL_BUFFER_BIT); + gl.clearStencil(0); + } + else + { + // Decrement the refference stencil value where the popped mask overlaps with the other ones + gl.colorMask(false, false, false, false); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.DECR); + this.renderer.plugins.graphics.render(graphics); + + this._useCurrent(); + } + } + + /** + * Setup renderer to use the current stencil data. + */ + _useCurrent() + { + const gl = this.renderer.gl; + + gl.colorMask(true, true, true, true); + gl.stencilFunc(gl.EQUAL, this.stencilMaskStack.length, this._getBitwiseMask()); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP); + } + + /** + * Fill 1s equal to the number of acitve stencil masks. + * + * @return {number} The bitwise mask. + */ + _getBitwiseMask() + { + return (1 << this.stencilMaskStack.length) - 1; + } + + /** + * Destroys the mask stack. + * + */ + destroy() + { + super.destroy(this); + + this.stencilMaskStack = null; + } +} diff --git a/packages/core/src/projection/ProjectionSystem.js b/packages/core/src/projection/ProjectionSystem.js new file mode 100644 index 0000000..6e63b73 --- /dev/null +++ b/packages/core/src/projection/ProjectionSystem.js @@ -0,0 +1,73 @@ +import System from '../System'; +import { Matrix } from '@pixi/math'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class ProjectionSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.projectionMatrix = new Matrix(); + } + + update(destinationFrame, sourceFrame, resolution, root) + { + this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; + this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; + + this.calculateProjection(this.destinationFrame, this.sourceFrame, resolution, root); + + this.renderer.globalUniforms.uniforms.projectionMatrix = this.projectionMatrix; + this.renderer.globalUniforms.update(); + } + + /** + * Updates the projection matrix based on a projection frame (which is a rectangle) + * + * @param {Rectangle} destinationFrame - The destination frame. + * @param {Rectangle} sourceFrame - The source frame. + */ + calculateProjection(destinationFrame, sourceFrame, resolution, root) + { + const pm = this.projectionMatrix; + + // I don't think we will need this line.. + // pm.identity(); + + if (!root) + { + pm.a = (1 / destinationFrame.width * 2) * resolution; + pm.d = (1 / destinationFrame.height * 2) * resolution; + + pm.tx = -1 - (sourceFrame.x * pm.a); + pm.ty = -1 - (sourceFrame.y * pm.d); + } + else + { + pm.a = (1 / destinationFrame.width * 2) * resolution; + pm.d = (-1 / destinationFrame.height * 2) * resolution; + + pm.tx = -1 - (sourceFrame.x * pm.a); + pm.ty = 1 - (sourceFrame.y * pm.d); + } + } + + /** + * Sets the transform of the active render target to the given matrix + * + * @param {PIXI.Matrix} matrix - The transformation matrix + */ + setTransform()// matrix) + { + // this._activeRenderTarget.transform = matrix; + } +} diff --git a/packages/core/src/renderTexture/BaseRenderTexture.js b/packages/core/src/renderTexture/BaseRenderTexture.js new file mode 100644 index 0000000..88eb83b --- /dev/null +++ b/packages/core/src/renderTexture/BaseRenderTexture.js @@ -0,0 +1,137 @@ +import BaseTexture from '../textures/BaseTexture'; +import FrameBuffer from '../framebuffer/FrameBuffer'; + +/** + * A BaseRenderTexture is a special texture that allows any PixiJS display object to be rendered to it. + * + * __Hint__: All DisplayObjects (i.e. Sprites) that render to a BaseRenderTexture should be preloaded + * otherwise black rectangles will be drawn instead. + * + * A BaseRenderTexture takes a snapshot of any Display Object given to its render method. The position + * and rotation of the given Display Objects is ignored. For example: + * + * ```js + * let renderer = PIXI.autoDetectRenderer(1024, 1024, { view: canvas, ratio: 1 }); + * let baseRenderTexture = new PIXI.BaseRenderTexture(renderer, 800, 600); + * let sprite = PIXI.Sprite.fromImage("spinObj_01.png"); + * + * sprite.position.x = 800/2; + * sprite.position.y = 600/2; + * sprite.anchor.x = 0.5; + * sprite.anchor.y = 0.5; + * + * baseRenderTexture.render(sprite); + * ``` + * + * The Sprite in this case will be rendered using its local transform. To render this sprite at 0,0 + * you can clear the transform + * + * ```js + * + * sprite.setTransform() + * + * let baseRenderTexture = new PIXI.BaseRenderTexture(100, 100); + * let renderTexture = new PIXI.RenderTexture(baseRenderTexture); + * + * renderer.render(sprite, renderTexture); // Renders to center of RenderTexture + * ``` + * + * @class + * @extends PIXI.BaseTexture + * @memberof PIXI + */ +export default class BaseRenderTexture extends BaseTexture +{ + /** + * @param {object} [options] + * @param {number} [options.width=100] - The width of the base render texture + * @param {number} [options.height=100] - The height of the base render texture + * @param {PIXI.SCALE_MODES} [options.scaleMode] - See {@link PIXI.SCALE_MODES} for possible values + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the texture being generated + */ + constructor(options) + { + if (typeof options === 'number') + { + /* eslint-disable prefer-rest-params */ + // Backward compatibility of signature + const width = arguments[0]; + const height = arguments[1]; + const scaleMode = arguments[2]; + const resolution = arguments[3]; + + options = { width, height, scaleMode, resolution }; + /* eslint-enable prefer-rest-params */ + } + + super(null, options); + + const { width, height } = options || {}; + + // Set defaults + this.mipmap = false; + this.width = Math.ceil(width) || 100; + this.height = Math.ceil(height) || 100; + this.valid = true; + + /** + * A map of renderer IDs to webgl renderTargets + * + * @private + * @member {object} + */ + // this._glRenderTargets = {}; + + /** + * A reference to the canvas render target (we only need one as this can be shared across renderers) + * + * @private + * @member {object} + */ + this._canvasRenderTarget = null; + + this.clearColor = [0, 0, 0, 0]; + + this.frameBuffer = new FrameBuffer(width * this.resolution, height * this.resolution) + .addColorTexture(0, this); + + // TODO - could this be added the systems? + + /** + * The data structure for the stencil masks + * + * @member {PIXI.Graphics[]} + */ + this.stencilMaskStack = []; + + /** + * The data structure for the filters + * + * @member {PIXI.Graphics[]} + */ + this.filterStack = [{}]; + } + + /** + * Resizes the BaseRenderTexture. + * + * @param {number} width - The width to resize to. + * @param {number} height - The height to resize to. + */ + resize(width, height) + { + width = Math.ceil(width); + height = Math.ceil(height); + this.frameBuffer.resize(width * this.resolution, height * this.resolution); + } + + /** + * Destroys this texture + * + */ + destroy() + { + super.destroy(true); + this.renderer = null; + } +} diff --git a/packages/core/src/renderTexture/RenderTexture.js b/packages/core/src/renderTexture/RenderTexture.js new file mode 100644 index 0000000..f1ecf1f --- /dev/null +++ b/packages/core/src/renderTexture/RenderTexture.js @@ -0,0 +1,153 @@ +import BaseRenderTexture from './BaseRenderTexture'; +import Texture from '../textures/Texture'; + +/** + * A RenderTexture is a special texture that allows any PixiJS display object to be rendered to it. + * + * __Hint__: All DisplayObjects (i.e. Sprites) that render to a RenderTexture should be preloaded + * otherwise black rectangles will be drawn instead. + * + * A RenderTexture takes a snapshot of any Display Object given to its render method. For example: + * + * ```js + * let renderer = PIXI.autoDetectRenderer(1024, 1024, { view: canvas, ratio: 1 }); + * let renderTexture = PIXI.RenderTexture.create(800, 600); + * let sprite = PIXI.Sprite.fromImage("spinObj_01.png"); + * + * sprite.position.x = 800/2; + * sprite.position.y = 600/2; + * sprite.anchor.x = 0.5; + * sprite.anchor.y = 0.5; + * + * renderer.render(sprite, renderTexture); + * ``` + * + * The Sprite in this case will be rendered using its local transform. To render this sprite at 0,0 + * you can clear the transform + * + * ```js + * + * sprite.setTransform() + * + * let renderTexture = new PIXI.RenderTexture.create(100, 100); + * + * renderer.render(sprite, renderTexture); // Renders to center of RenderTexture + * ``` + * + * @class + * @extends PIXI.Texture + * @memberof PIXI + */ +export default class RenderTexture extends Texture +{ + /** + * @param {PIXI.BaseRenderTexture} baseRenderTexture - The renderer used for this RenderTexture + * @param {PIXI.Rectangle} [frame] - The rectangle frame of the texture to show + */ + constructor(baseRenderTexture, frame) + { + // support for legacy.. + let _legacyRenderer = null; + + if (!(baseRenderTexture instanceof BaseRenderTexture)) + { + /* eslint-disable prefer-rest-params, no-console */ + const width = arguments[1]; + const height = arguments[2]; + const scaleMode = arguments[3]; + const resolution = arguments[4]; + + // we have an old render texture.. + console.warn(`Please use RenderTexture.create(${width}, ${height}) instead of the ctor directly.`); + _legacyRenderer = arguments[0]; + /* eslint-enable prefer-rest-params, no-console */ + + frame = null; + baseRenderTexture = new BaseRenderTexture({ + width, + height, + scaleMode, + resolution, + }); + } + + /** + * The base texture object that this texture uses + * + * @member {BaseTexture} + */ + super(baseRenderTexture, frame); + + this.legacyRenderer = _legacyRenderer; + + /** + * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. + * + * @member {boolean} + */ + this.valid = true; + + /** + * FilterSystem temporary storage + * @private + * @member {PIXI.Rectangle} + */ + this.filterFrame = null; + + this._updateUvs(); + } + + /** + * Resizes the RenderTexture. + * + * @param {number} width - The width to resize to. + * @param {number} height - The height to resize to. + * @param {boolean} [resizeBaseTexture=true] - Should the baseTexture.width and height values be resized as well? + */ + resize(width, height, resizeBaseTexture = true) + { + width = Math.ceil(width); + height = Math.ceil(height); + + // TODO - could be not required.. + this.valid = (width > 0 && height > 0); + + this._frame.width = this.orig.width = width; + this._frame.height = this.orig.height = height; + + if (resizeBaseTexture) + { + this.baseTexture.resize(width, height); + } + + this._updateUvs(); + } + + /** + * A short hand way of creating a render texture. + * + * @param {object} [options] - Options + * @param {number} [options.width=100] - The width of the render texture + * @param {number} [options.height=100] - The height of the render texture + * @param {number} [options.scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the texture being generated + * @return {PIXI.RenderTexture} The new render texture + */ + static create(options) + { + // fallback, old-style: create(width, height, scaleMode, resolution) + if (typeof options === 'number') + { + /* eslint-disable prefer-rest-params */ + options = { + width: options, + height: arguments[1], + scaleMode: arguments[2], + resolution: arguments[3], + }; + /* eslint-enable prefer-rest-params */ + } + + return new RenderTexture(new BaseRenderTexture(options)); + } +} diff --git a/packages/core/src/renderTexture/RenderTextureSystem.js b/packages/core/src/renderTexture/RenderTextureSystem.js new file mode 100644 index 0000000..8d0ca61 --- /dev/null +++ b/packages/core/src/renderTexture/RenderTextureSystem.js @@ -0,0 +1,115 @@ +import System from '../System'; +import { Rectangle } from '@pixi/math'; + +const tempRect = new Rectangle(); + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class RenderTextureSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.clearColor = renderer._backgroundColorRgba; + + // TODO moe this property somewhere else! + this.defaultMaskStack = []; + this.defaultFilterStack = [{}]; + + // empty render texture? + this.renderTexture = null; + + this.destinationFrame = new Rectangle(); + } + + bind(renderTexture, sourceFrame, destinationFrame) + { + // TODO - do we want this?? + if (this.renderTexture === renderTexture) return; + this.renderTexture = renderTexture; + + const renderer = this.renderer; + + if (renderTexture) + { + const baseTexture = renderTexture.baseTexture; + + if (!destinationFrame) + { + tempRect.width = baseTexture.realWidth; + tempRect.height = baseTexture.realHeight; + + destinationFrame = tempRect; + } + + if (!sourceFrame) + { + sourceFrame = destinationFrame; + } + + this.renderer.framebuffer.bind(baseTexture.frameBuffer, destinationFrame); + + this.renderer.projection.update(destinationFrame, sourceFrame, baseTexture.resolution, false); + this.renderer.stencil.setMaskStack(baseTexture.stencilMaskStack); + } + else + { + // TODO these validation checks happen deeper down.. + // thing they can be avoided.. + if (!destinationFrame) + { + tempRect.width = renderer.width; + tempRect.height = renderer.height; + + destinationFrame = tempRect; + } + + if (!sourceFrame) + { + sourceFrame = destinationFrame; + } + + renderer.framebuffer.bind(null, destinationFrame); + + // TODO store this.. + this.renderer.projection.update(destinationFrame, sourceFrame, this.renderer.resolution, true); + this.renderer.stencil.setMaskStack(this.defaultMaskStack); + } + + this.destinationFrame.copyFrom(destinationFrame); + } + + /** + * Erases the render texture and fills the drawing area with a colour + * + * @param {number} [clearColor] - The colour + * @return {PIXI.Renderer} Returns itself. + */ + clear(clearColor) + { + if (this.renderTexture) + { + clearColor = clearColor || this.renderTexture.baseTexture.clearColor; + } + else + { + clearColor = clearColor || this.clearColor; + } + + this.renderer.framebuffer.clear(clearColor[0], clearColor[1], clearColor[2], clearColor[3]); + } + + resize()// screenWidth, screenHeight) + { + // resize the root only! + this.bind(null); + } +} diff --git a/packages/core/src/renderers/AbstractRenderer.js b/packages/core/src/renderers/AbstractRenderer.js deleted file mode 100644 index 928670f..0000000 --- a/packages/core/src/renderers/AbstractRenderer.js +++ /dev/null @@ -1,353 +0,0 @@ -import { hex2string, hex2rgb } from '@pixi/utils'; -import { Matrix, Rectangle } from '@pixi/math'; -import { RENDERER_TYPE } from '@pixi/constants'; -import { settings } from '@pixi/settings'; -import { Container } from '@pixi/display'; -import RenderTexture from '../textures/RenderTexture'; -import EventEmitter from 'eventemitter3'; - -const tempMatrix = new Matrix(); - -/** - * 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 EventEmitter - * @memberof PIXI - */ -export default class AbstractRenderer extends EventEmitter -{ - // eslint-disable-next-line valid-jsdoc - /** - * @param {string} system - The name of the system this renderer is for. - * @param {object} [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.transparent=false] - If the render view is transparent, default false - * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false - * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) - * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. The - * resolution of the renderer retina would be 2. - * @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 {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when rendering, - * stopping pixel interpolation. - */ - constructor(system, options, arg2, arg3) - { - super(); - - // Support for constructor(system, screenWidth, screenHeight, options) - if (typeof options === 'number') - { - options = Object.assign({ - width: options, - height: arg2 || settings.RENDER_OPTIONS.height, - }, arg3); - } - - // 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 = RENDERER_TYPE.UNKNOWN; - - /** - * Measurements of the screen. (0, 0, screenWidth, screenHeight) - * - * Its safe to use as filterArea or hitArea for 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 1 - */ - this.resolution = options.resolution || settings.RESOLUTION; - - /** - * Whether the render view is transparent - * - * @member {boolean} - */ - this.transparent = options.transparent; - - /** - * Whether css dimensions of canvas view should be resized to screen dimensions automatically - * - * @member {boolean} - */ - this.autoResize = options.autoResize || false; - - /** - * Tracks the blend modes useful for this renderer. - * - * @member {object} - */ - this.blendModes = null; - - /** - * 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; - - /** - * If true PixiJS will Math.floor() x/y values when rendering, stopping pixel interpolation. - * Handy for crisp pixel art and speed on legacy devices. - * - * @member {boolean} - */ - this.roundPixels = options.roundPixels; - - /** - * The background color as a number. - * - * @member {number} - * @private - */ - this._backgroundColor = 0x000000; - - /** - * The background color as an [R, G, B] array. - * - * @member {number[]} - * @private - */ - this._backgroundColorRgba = [0, 0, 0, 0]; - - /** - * The background color as a string. - * - * @member {string} - * @private - */ - this._backgroundColorString = '#000000'; - - this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter - - /** - * This temporary display object used as the parent of the currently being rendered item - * - * @member {PIXI.DisplayObject} - * @private - */ - this._tempDisplayObjectParent = new Container(); - - /** - * The last root object that the renderer tried to render. - * - * @member {PIXI.DisplayObject} - * @private - */ - this._lastObjectRendered = this._tempDisplayObjectParent; - - /** - * Collection of plugins. - * @readonly - * @member {object} - */ - this.plugins = {}; - } - - /** - * Initialize the plugins. - * - * @protected - * @param {object} staticMap - The dictionary of staticly saved plugins. - */ - initPlugins(staticMap) - { - 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() - { - return this.view.width; - } - - /** - * Same as view.height, actual number of pixels in the canvas by vertical - * - * @member {number} - * @readonly - * @default 600 - */ - get height() - { - return this.view.height; - } - - /** - * Resizes the screen and canvas to the specified width and height - * Canvas dimensions are multiplied by resolution - * - * @param {number} screenWidth - the new width of the screen - * @param {number} screenHeight - the new height of the screen - */ - resize(screenWidth, screenHeight) - { - this.screen.width = screenWidth; - this.screen.height = screenHeight; - - this.view.width = screenWidth * this.resolution; - this.view.height = screenHeight * this.resolution; - - if (this.autoResize) - { - this.view.style.width = `${screenWidth}px`; - this.view.style.height = `${screenHeight}px`; - } - } - - /** - * 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 {PIXI.DisplayObject} displayObject - The displayObject the object will be generated from - * @param {number} scaleMode - Should be one of the scaleMode consts - * @param {number} resolution - The resolution / device pixel ratio of the texture being generated - * @param {PIXI.Rectangle} [region] - The region of the displayObject, that shall be rendered, - * if no region is specified, defaults to the local bounds of the displayObject. - * @return {PIXI.Texture} a texture of the graphics object - */ - generateTexture(displayObject, scaleMode, resolution, region) - { - region = region || displayObject.getLocalBounds(); - - // 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(region.width | 0, region.height | 0, scaleMode, resolution); - - tempMatrix.tx = -region.x; - tempMatrix.ty = -region.y; - - this.render(displayObject, renderTexture, false, tempMatrix, true); - - return renderTexture; - } - - /** - * Removes everything from the renderer and optionally removes the Canvas DOM element. - * - * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. - */ - destroy(removeView) - { - 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); - } - - this.plugins = null; - - this.type = RENDERER_TYPE.UNKNOWN; - - this.view = null; - - this.screen = null; - - this.resolution = 0; - - this.transparent = false; - - this.autoResize = false; - - this.blendModes = null; - - this.options = null; - - this.preserveDrawingBuffer = false; - this.clearBeforeRender = false; - - this.roundPixels = false; - - this._backgroundColor = 0; - this._backgroundColorRgba = null; - this._backgroundColorString = null; - - this._tempDisplayObjectParent = null; - this._lastObjectRendered = null; - } - - /** - * The background color to fill if not transparent - * - * @member {number} - */ - get backgroundColor() - { - return this._backgroundColor; - } - - set backgroundColor(value) // eslint-disable-line require-jsdoc - { - this._backgroundColor = value; - this._backgroundColorString = hex2string(value); - hex2rgb(value, this._backgroundColorRgba); - } -} diff --git a/packages/core/src/renderers/Renderer.js b/packages/core/src/renderers/Renderer.js deleted file mode 100644 index a2020b1..0000000 --- a/packages/core/src/renderers/Renderer.js +++ /dev/null @@ -1,329 +0,0 @@ -import AbstractRenderer from './AbstractRenderer'; -import { sayHello } from '@pixi/utils'; -import MaskSystem from './systems/MaskSystem'; -import StencilSystem from './systems/StencilSystem'; -import FilterSystem from './systems/filter/FilterSystem'; -import FramebufferSystem from './systems/FramebufferSystem'; -import RenderTextureSystem from './systems/RenderTextureSystem'; -import TextureSystem from './systems/textures/TextureSystem'; -import ProjectionSystem from './systems/ProjectionSystem'; -import StateSystem from './systems/StateSystem'; -import GeometrySystem from './systems/geometry/GeometrySystem'; -import ShaderSystem from './systems/shader/ShaderSystem'; -import ContextSystem from './systems/ContextSystem'; -import BatchSystem from './systems/BatchSystem'; -import TextureGCSystem from './systems/textures/TextureGCSystem'; -import { RENDERER_TYPE } from '@pixi/constants'; -import UniformGroup from '../shader/UniformGroup'; -import { Matrix } from '@pixi/math'; -import Runner from 'mini-runner'; - -/** - * 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 Render works by automatically managing webGLBatchs. - * 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 :) - * - * @class - * @memberof PIXI - * @extends PIXI.AbstractRenderer - */ -export default class Renderer extends AbstractRenderer -{ - // eslint-disable-next-line valid-jsdoc - /** - * - * @param {object} [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.transparent=false] - If the render view is transparent, default false - * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false - * @param {boolean} [options.antialias=false] - sets antialias. If not available natively then FXAA - * antialiasing is used - * @param {boolean} [options.forceFXAA=false] - forces FXAA antialiasing to be used over native. - * FXAA is faster, but may not always look as great - * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. - * The resolution of the renderer retina would be 2. - * @param {boolean} [options.clearBeforeRender=true] - This sets if the renderer will clear - * the canvas or not before the new render pass. If you wish to set this to false, you *must* set - * preserveDrawingBuffer to `true`. - * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, - * enable this if you need to call toDataUrl on the webgl context. - * @param {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when - * rendering, stopping pixel interpolation. - * @param {number} [options.backgroundColor=0x000000] - The background color of the rendered area - * (shown if not transparent). - * @param {boolean} [options.legacy=false] - If true PixiJS will aim to ensure compatibility - * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. - * @param {string} [options.powerPreference] - Parameter passed to webgl context, set to "high-performance" - * for devices with dual graphics card - */ - constructor(options, arg2, arg3) - { - super('WebGL', options, arg2, arg3); - - /** - * The type of this renderer as a standardised const - * - * @member {number} - * @see PIXI.RENDERER_TYPE - */ - this.type = RENDERER_TYPE.WEBGL; - - // this will be set by the contextSystem (this.context) - this.gl = null; - this.CONTEXT_UID = 0; - this.legacy = !!options.legacy; - - // TODO legacy! - - // runners! - this.runners = { - destroy: new Runner('destroy'), - contextChange: new Runner('contextChange', 1), - reset: new Runner('reset'), - update: new Runner('update'), - postrender: new Runner('postrender'), - prerender: new Runner('prerender'), - resize: new Runner('resize', 2), - }; - - 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(GeometrySystem, 'geometry') - .addSystem(FramebufferSystem, 'framebuffer') - .addSystem(StencilSystem, 'stencil') - .addSystem(ProjectionSystem, 'projection') - .addSystem(TextureGCSystem, 'textureGC') - .addSystem(FilterSystem, 'filter') - .addSystem(RenderTextureSystem, 'renderTexture') - .addSystem(BatchSystem, 'batch'); - - this.initPlugins(Renderer.__plugins); - - /** - * The options passed in to create a new webgl context. - * - * @member {object} - * @private - */ - if (options.context) - { - this.context.initFromContext(options.context); - } - else - { - this.context.initFromOptions({ - alpha: this.transparent, - antialias: options.antialias, - premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', - stencil: true, - 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); - } - - addSystem(_class, name) - { - if (!name) - { - name = _class.name; - } - - // TODO - read name from class.name.. - - /* - if(name.includes('System')) - { - name = name.replace('System', ''); - name = name.charAt(0).toLowerCase() + name.slice(1); - } - */ - - const system = new _class(this); - - if (this[name]) - { - throw new Error(`Whoops! ${name} is already a manger`); - } - - this[name] = system; - - for (const i in this.runners) - { - this.runners[i].add(system); - } - - return this; - - /** - * 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. - */ - } - - /** - * Renders the object to its webGL view - * - * @param {PIXI.DisplayObject} displayObject - the object to be rendered - * @param {PIXI.RenderTexture} renderTexture - The render texture to render to. - * @param {boolean} [clear] - Should the canvas be cleared before the new render - * @param {PIXI.Transform} [transform] - A transform to apply to the render texture before rendering. - * @param {boolean} [skipUpdateTransform] - Should we skip the update transform pass? - */ - render(displayObject, renderTexture, clear, transform, skipUpdateTransform) - { - // can be handy to know! - this.renderingToScreen = !renderTexture; - - this.runners.prerender.run(); - this.emit('prerender'); - - // 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.parent; - - displayObject.parent = this._tempDisplayObjectParent; - displayObject.updateTransform(); - displayObject.parent = 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.run(); - - this.emit('postrender'); - } - - /** - * Resizes the webGL view to the specified width and height. - * - * @param {number} screenWidth - the new width of the screen - * @param {number} screenHeight - the new height of the screen - */ - resize(screenWidth, screenHeight) - { - AbstractRenderer.prototype.resize.call(this, screenWidth, screenHeight); - - this.runners.resize.run(screenWidth, screenHeight); - } - - /** - * Resets the WebGL state so you can render things however you fancy! - * - * @return {PIXI.Renderer} Returns itself. - */ - reset() - { - this.runners.reset.run(); - - return this; - } - - clear() - { - this.framebuffer.bind(); - this.framebuffer.clear(); - } - - /** - * Removes everything from the renderer (event listeners, spritebatch, etc...) - * - * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. - * See: https://github.com/pixijs/pixi.js/issues/2233 - */ - destroy(removeView) - { - this.runners.destroy.run(); - - // call base destroy - super.destroy(removeView); - - // TODO nullify all the managers.. - this.gl = null; - } - - /** - * 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 - * builds and excluding plugins. - * @name PIXI.Renderer#plugins - * @type {object} - * @readonly - * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. - * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. - * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. - * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. - */ - - /** - * Adds a plugin to the renderer. - * - * @method - * @param {string} pluginName - The name of the plugin. - * @param {Function} ctor - The constructor function or class for the plugin. - */ - static registerPlugin(pluginName, ctor) - { - Renderer.__plugins = Renderer.__plugins || {}; - Renderer.__plugins[pluginName] = ctor; - } -} diff --git a/packages/core/src/renderers/State.js b/packages/core/src/renderers/State.js deleted file mode 100644 index fa3f221..0000000 --- a/packages/core/src/renderers/State.js +++ /dev/null @@ -1,163 +0,0 @@ -/* eslint-disable max-len */ - -const BLEND = 0; -const OFFSET = 1; -const CULLING = 2; -const DEPTH_TEST = 3; -const WINDING = 4; - -/** - * This is a webGL state. It is passed The WebGL StateManager. - * Each mesh renderered may require webGL to be in a different state. - * For example you may want different blend mode or to enable polygon offsets - * - * @class - * @memberof PIXI - */ -export default class State -{ - /** - * - */ - constructor() - { - this.data = 0; - - this.blendMode = 0; - this.polygonOffset = 0; - - this.blend = true; - // this.depthTest = true; - } - - /** - * Activates blending of the computed fragment color values - * - * @member {boolean} - */ - get blend() - { - return !!(this.data & (1 << BLEND)); - } - - set blend(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << BLEND)) !== value) - { - this.data ^= (1 << BLEND); - } - } - - /** - * Activates adding an offset to depth values of polygon's fragments - * - * @member {boolean} - * @default false - */ - get offsets() - { - return !!(this.data & (1 << OFFSET)); - } - - set offsets(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << OFFSET)) !== value) - { - this.data ^= (1 << OFFSET); - } - } - - /** - * Activates culling of polygons. - * - * @member {boolean} - * @default false - */ - get culling() - { - return !!(this.data & (1 << CULLING)); - } - - set culling(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << CULLING)) !== value) - { - this.data ^= (1 << CULLING); - } - } - - /** - * Activates depth comparisons and updates to the depth buffer. - * - * @member {boolean} - * @default false - */ - get depthTest() - { - return !!(this.data & (1 << DEPTH_TEST)); - } - - set depthTest(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << DEPTH_TEST)) !== value) - { - this.data ^= (1 << DEPTH_TEST); - } - } - - /** - * Specifies whether or not front or back-facing polygons can be culled. - * @member {boolean} - * @default false - */ - get clockwiseFrontFace() - { - return !!(this.data & (1 << WINDING)); - } - - set clockwiseFrontFace(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << WINDING)) !== value) - { - this.data ^= (1 << WINDING); - } - } - - /** - * The blend mode to be applied when this state is set. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. - * Setting this mode to anything other than NO_BLEND will automatically switch blending on. - * - * @member {boolean} - * @default PIXI.BLEND_MODES.NORMAL - * @see PIXI.BLEND_MODES - */ - get blendMode() - { - return this._blendMode; - } - - set blendMode(value) // eslint-disable-line require-jsdoc - { - // 17 is NO BLEND - this.blend = (value !== 17); - this._blendMode = value; - } - - /** - * The polygon offset. Setting this property to anything other than 0 will automatically enable poygon offset fill. - * - * @member {number} - * @default 0 - */ - get polygonOffset() - { - return this._polygonOffset; - } - - set polygonOffset(value) // eslint-disable-line require-jsdoc - { - this.offsets = !!value; - this._polygonOffset = value; - } -} - diff --git a/packages/core/src/renderers/filters/Filter.js b/packages/core/src/renderers/filters/Filter.js deleted file mode 100644 index 2073b8d..0000000 --- a/packages/core/src/renderers/filters/Filter.js +++ /dev/null @@ -1,140 +0,0 @@ -import Shader from '../../shader/Shader'; -import Program from '../../shader/Program'; -import State from '../State'; -import { settings } from '@pixi/settings'; -// import extractUniformsFromSrc from './extractUniformsFromSrc'; -import defaultVertex from './defaultFilter.vert'; -import defaultFragment from './defaultFilter.frag'; - -// let math = require('../../math'); -/** - * @class - * @memberof PIXI - * @extends PIXI.Shader - */ -export default class Filter extends Shader -{ - /** - * @param {string} [vertexSrc] - The source of the vertex shader. - * @param {string} [fragmentSrc] - The source of the fragment shader. - * @param {object} [uniforms] - Custom uniforms to use to augment the built-in ones. - */ - constructor(vertexSrc, fragmentSrc, uniforms) - { - const program = Program.from(vertexSrc, fragmentSrc); - - super(program, uniforms); - - /** - * The padding of the filter. Some filters require extra space to breath such as a blur. - * Increasing this will add extra width and height to the bounds of the object that the - * filter is applied to. - * - * @member {number} - */ - this.padding = 0; - - /** - * The resolution of the filter. Setting this to be lower will lower the quality but - * increase the performance of the filter. - * - * @member {number} - */ - this.resolution = settings.RESOLUTION; - - /** - * If enabled is true the filter is applied, if false it will not. - * - * @member {boolean} - */ - this.enabled = true; - - /** - * If enabled, PixiJS will fit the filter area into boundaries for better performance. - * Switch it off if it does not work for specific shader. - * - * @member {boolean} - */ - this.autoFit = true; - - /** - * Legacy filters use position and uvs from attributes - * @member {boolean} - * @readonly - */ - this.legacy = !!this.program.attributeData.aTextureCoord; - - /** - * the webGL state the filter requires to render - * @member {PIXI.State} - */ - this.state = new State(); - } - - /** - * Applies the filter - * - * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from - * @param {PIXI.RenderTarget} input - The input render target. - * @param {PIXI.RenderTarget} output - The target to output to. - * @param {boolean} clear - Should the output be cleared before rendering to it - * @param {object} [currentState] - It's current state of filter. - * There are some useful properties in the currentState : - * target, filters, sourceFrame, destinationFrame, renderTarget, resolution - */ - apply(filterManager, input, output, clear, currentState, derp) // eslint-disable-line no-unused-vars - { - // do as you please! - - filterManager.applyFilter(this, input, output, clear, currentState, derp); - - // or just do a regular render.. - } - - /** - * Sets the blendmode of the filter - * - * @member {number} - * @default PIXI.BLEND_MODES.NORMAL - */ - get blendMode() - { - return this.state.blendMode; - } - - set blendMode(value) // eslint-disable-line require-jsdoc - { - this.state.blendMode = value; - } - - /** - * The default vertex shader source - * - * @static - * @constant - */ - static get defaultVertexSrc() - { - return defaultVertex; - } - - /** - * The default fragment shader source - * - * @static - * @constant - */ - static get defaultFragmentSrc() - { - return defaultFragment; - } -} - -/** - * Used for caching shader IDs - * - * @static - * @private - */ -Filter.SOURCE_KEY_MAP = {}; - diff --git a/packages/core/src/renderers/filters/defaultFilter.frag b/packages/core/src/renderers/filters/defaultFilter.frag deleted file mode 100644 index a14c1be..0000000 --- a/packages/core/src/renderers/filters/defaultFilter.frag +++ /dev/null @@ -1,22 +0,0 @@ -varying vec2 vTextureCoord; -varying vec2 vFilterCoord; - -uniform sampler2D uSampler; -uniform sampler2D filterSampler; - -void main(void){ - vec4 masky = texture2D(filterSampler, vFilterCoord); - vec4 sample = texture2D(uSampler, vTextureCoord); - vec4 color; - if(mod(vFilterCoord.x, 1.0) > 0.5) - { - color = vec4(1.0, 0.0, 0.0, 1.0); - } - else - { - color = vec4(0.0, 1.0, 0.0, 1.0); - } - // gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0); - gl_FragColor = mix(sample, masky, 0.5); - gl_FragColor *= sample.a; -} diff --git a/packages/core/src/renderers/filters/defaultFilter.vert b/packages/core/src/renderers/filters/defaultFilter.vert deleted file mode 100644 index e1febca..0000000 --- a/packages/core/src/renderers/filters/defaultFilter.vert +++ /dev/null @@ -1,14 +0,0 @@ -attribute vec2 aVertexPosition; -attribute vec2 aTextureCoord; - -uniform mat3 projectionMatrix; -uniform mat3 filterMatrix; - -varying vec2 vTextureCoord; -varying vec2 vFilterCoord; - -void main(void){ - gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); - vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy; - vTextureCoord = aTextureCoord ; -} diff --git a/packages/core/src/renderers/filters/filterTransforms.js b/packages/core/src/renderers/filters/filterTransforms.js deleted file mode 100644 index 87a3cd6..0000000 --- a/packages/core/src/renderers/filters/filterTransforms.js +++ /dev/null @@ -1,50 +0,0 @@ -import { Matrix } from '@pixi/math'; - -/** - * Calculates the mapped matrix - * @param filterArea {Rectangle} The filter area - * @param sprite {Sprite} the target sprite - * @param outputMatrix {Matrix} @alvin - * @private - */ -// this returns a matrix that will normalise map filter cords in the filter to screen space -export function calculateScreenSpaceMatrix(outputMatrix, filterArea, textureSize) -{ - // TODO unwrap? - const mappedMatrix = outputMatrix.identity(); - - mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); - - mappedMatrix.scale(textureSize.width, textureSize.height); - - return mappedMatrix; -} - -export function calculateNormalizedScreenSpaceMatrix(outputMatrix, filterArea, textureSize) -{ - const mappedMatrix = outputMatrix.identity(); - - mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); - - const translateScaleX = (textureSize.width / filterArea.width); - const translateScaleY = (textureSize.height / filterArea.height); - - mappedMatrix.scale(translateScaleX, translateScaleY); - - return mappedMatrix; -} - -// this will map the filter coord so that a texture can be used based on the transform of a sprite -export function calculateSpriteMatrix(outputMatrix, filterArea, textureSize, sprite) -{ - const orig = sprite._texture.orig; - const mappedMatrix = outputMatrix.set(textureSize.width, 0, 0, textureSize.height, filterArea.x, filterArea.y); - const worldTransform = sprite.worldTransform.copyTo(Matrix.TEMP_MATRIX); - - worldTransform.invert(); - mappedMatrix.prepend(worldTransform); - mappedMatrix.scale(1.0 / orig.width, 1.0 / orig.height); - mappedMatrix.translate(sprite.anchor.x, sprite.anchor.y); - - return mappedMatrix; -} diff --git a/packages/core/src/renderers/filters/spriteMask/SpriteMaskFilter.js b/packages/core/src/renderers/filters/spriteMask/SpriteMaskFilter.js deleted file mode 100644 index d5a91a4..0000000 --- a/packages/core/src/renderers/filters/spriteMask/SpriteMaskFilter.js +++ /dev/null @@ -1,63 +0,0 @@ -import Filter from '../Filter'; -import { Matrix } from '@pixi/math'; -import vertex from './spriteMaskFilter.vert'; -import fragment from './spriteMaskFilter.frag'; -import { default as TextureMatrix } from '../../../textures/TextureMatrix'; - -/** - * The SpriteMaskFilter class - * - * @class - * @extends PIXI.Filter - * @memberof PIXI - */ -export default class SpriteMaskFilter extends Filter -{ - /** - * @param {PIXI.Sprite} sprite - the target sprite - */ - constructor(sprite) - { - const maskMatrix = new Matrix(); - - super(vertex, fragment); - - sprite.renderable = false; - - this.maskSprite = sprite; - this.maskMatrix = maskMatrix; - } - - /** - * Applies the filter - * - * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from - * @param {PIXI.RenderTarget} input - The input render target. - * @param {PIXI.RenderTarget} output - The target to output to. - */ - apply(filterManager, input, output) - { - const maskSprite = this.maskSprite; - const tex = this.maskSprite.texture; - - if (!tex.valid) - { - return; - } - if (!tex.transform) - { - // margin = 0.0, let it bleed a bit, shader code becomes easier - // assuming that atlas textures were made with 1-pixel padding - tex.transform = new TextureMatrix(tex, 0.0); - } - tex.transform.update(); - - this.uniforms.mask = tex; - this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite) - .prepend(tex.transform.mapCoord); - this.uniforms.alpha = maskSprite.worldAlpha; - this.uniforms.maskClamp = tex.transform.uClampFrame; - - filterManager.applyFilter(this, input, output); - } -} diff --git a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.frag b/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.frag deleted file mode 100644 index 0e4aef8..0000000 --- a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.frag +++ /dev/null @@ -1,23 +0,0 @@ -varying vec2 vMaskCoord; -varying vec2 vTextureCoord; - -uniform sampler2D uSampler; -uniform sampler2D mask; -uniform float alpha; -uniform vec4 maskClamp; - -void main(void) -{ - float clip = step(3.5, - step(maskClamp.x, vMaskCoord.x) + - step(maskClamp.y, vMaskCoord.y) + - step(vMaskCoord.x, maskClamp.z) + - step(vMaskCoord.y, maskClamp.w)); - - vec4 original = texture2D(uSampler, vTextureCoord); - vec4 masky = texture2D(mask, vMaskCoord); - - original *= (masky.r * masky.a * alpha * clip); - - gl_FragColor = original; -} diff --git a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.vert b/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.vert deleted file mode 100644 index d23a30c..0000000 --- a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.vert +++ /dev/null @@ -1,16 +0,0 @@ -attribute vec2 aVertexPosition; -attribute vec2 aTextureCoord; - -uniform mat3 projectionMatrix; -uniform mat3 otherMatrix; - -varying vec2 vMaskCoord; -varying vec2 vTextureCoord; - -void main(void) -{ - gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); - - vTextureCoord = aTextureCoord; - vMaskCoord = ( otherMatrix * vec3( aTextureCoord, 1.0) ).xy; -} diff --git a/packages/core/src/renderers/systems/BatchSystem.js b/packages/core/src/renderers/systems/BatchSystem.js deleted file mode 100644 index 70e9518..0000000 --- a/packages/core/src/renderers/systems/BatchSystem.js +++ /dev/null @@ -1,68 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import ObjectRenderer from '../utils/ObjectRenderer'; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ - -export default class BatchSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this manager works for. - */ - constructor(renderer) - { - super(renderer); - - /** - * An empty renderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.emptyRenderer = new ObjectRenderer(renderer); - - /** - * The currently active ObjectRenderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.currentRenderer = this.emptyRenderer; - } - - /** - * Changes the current renderer to the one given in parameter - * - * @param {PIXI.ObjectRenderer} objectRenderer - The object renderer to use. - */ - setObjectRenderer(objectRenderer) - { - if (this.currentRenderer === objectRenderer) - { - return; - } - - this.currentRenderer.stop(); - this.currentRenderer = objectRenderer; - - this.renderer.state.setState(objectRenderer.state); - - this.currentRenderer.start(); - } - - /** - * This should be called if you wish to do some custom rendering - * It will basically render anything that may be batched up such as sprites - * - */ - flush() - { - this.setObjectRenderer(this.emptyRenderer); - } - - reset() - { - this.setObjectRenderer(this.emptyRenderer); - } -} diff --git a/packages/core/src/renderers/systems/ContextSystem.js b/packages/core/src/renderers/systems/ContextSystem.js deleted file mode 100644 index 43bf827..0000000 --- a/packages/core/src/renderers/systems/ContextSystem.js +++ /dev/null @@ -1,186 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { settings } from '@pixi/settings'; - -let CONTEXT_UID = 0; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class ContextSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this.webGLVersion = 1; - - this.handleContextLost = this.handleContextLost.bind(this); - this.handleContextRestored = this.handleContextRestored.bind(this); - - this.extensions = {}; - - renderer.view.addEventListener('webglcontextlost', this.handleContextLost, false); - renderer.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); - } - - get isLost() - { - return (!this.gl || this.gl.isContextLost()); - } - - contextChange(gl) - { - this.gl = gl; - - // restore a context if it was previously lost - if (gl.isContextLost() && gl.getExtension('WEBGL_lose_context')) - { - gl.getExtension('WEBGL_lose_context').restoreContext(); - } - } - - initFromContext(gl) - { - this.gl = gl; - this.validateContext(gl); - this.renderer.gl = gl; - this.renderer.CONTEXT_UID = CONTEXT_UID++; - this.renderer.runners.contextChange.run(gl); - } - - initFromOptions(options) - { - const gl = this.createContext(this.renderer.view, options); - - this.initFromContext(gl); - } - - /** - * Helper class to create a webGL Context - * - * @class - * @memberof PIXI.glCore - * @param canvas {HTMLCanvasElement} the canvas element that we will get the context from - * @param options {Object} An options object that gets passed in to the canvas element containing the context attributes, - * see https://developer.mozilla.org/en/docs/Web/API/HTMLCanvasElement/getContext for the options available - * @return {WebGLRenderingContext} the WebGL context - */ - createContext(canvas, options) - { - let gl; - - if (settings.PREFER_WEBGL_2) - { - gl = canvas.getContext('webgl2', options); - } - - if (gl) - { - this.webGLVersion = 2; - } - else - { - this.webGLVersion = 1; - - gl = canvas.getContext('webgl', options) - || canvas.getContext('experimental-webgl', options); - - if (!gl) - { - // fail, not able to get a context - throw new Error('This browser does not support webGL. Try using the canvas renderer'); - } - } - - this.gl = gl; - - this.getExtensions(); - - return gl; - } - - getExtensions() - { - // time to set up default etensions that pixi uses.. - const gl = this.gl; - const extensions = this.extensions; - - if (this.webGLVersion === 1) - { - extensions.drawBuffers = gl.getExtension('WEBGL_draw_buffers'); - extensions.depthTexture = gl.getExtension('WEBKIT_WEBGL_depth_texture'); - extensions.floatTexture = gl.getExtension('OES_texture_float'); - extensions.loseContext = gl.getExtension('WEBGL_lose_context'); - - extensions.vertexArrayObject = gl.getExtension('OES_vertex_array_object') - || gl.getExtension('MOZ_OES_vertex_array_object') - || gl.getExtension('WEBKIT_OES_vertex_array_object'); - } - - // we don't use any specific WebGL 2 ones yet! - } - - /** - * Handles a lost webgl context - * - * @private - * @param {WebGLContextEvent} event - The context lost event. - */ - handleContextLost(event) - { - event.preventDefault(); - } - - /** - * Handles a restored webgl context - * - * @private - */ - handleContextRestored() - { - this.renderer.runners.contextChange.run(this.gl); - } - - destroy() - { - const view = this.renderer.view; - - // remove listeners - view.removeEventListener('webglcontextlost', this.handleContextLost); - view.removeEventListener('webglcontextrestored', this.handleContextRestored); - - this.gl.useProgram(null); - - if (this.extensions.loseContext) - { - this.extensions.loseContext.loseContext(); - } - } - - postrender() - { - this.gl.flush(); - } - - validateContext(gl) - { - const attributes = gl.getContextAttributes(); - - // this is going to be fairly simple for now.. but at least we have room to grow! - if (!attributes.stencil) - { - /* eslint-disable max-len */ - - /* eslint-disable no-console */ - console.warn('Provided WebGL context does not have a stencil buffer, masks may not render correctly'); - /* eslint-enable no-console */ - - /* eslint-enable max-len */ - } - } -} diff --git a/packages/core/src/renderers/systems/FramebufferSystem.js b/packages/core/src/renderers/systems/FramebufferSystem.js deleted file mode 100644 index df7902b..0000000 --- a/packages/core/src/renderers/systems/FramebufferSystem.js +++ /dev/null @@ -1,246 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { Rectangle } from '@pixi/math'; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class FramebufferSystem extends WebGLSystem -{ - /** - * Sets up the renderer context and necessary buffers. - * - * @private - */ - contextChange() - { - this.gl = this.renderer.gl; - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - this.current = null; - this.viewport = new Rectangle(); - - this.drawBufferExtension = this.renderer.context.extensions.drawBuffers; - } - - bind(framebuffer, frame) - { - const gl = this.gl; - - this.current = framebuffer; - - if (framebuffer) - { - // TODO cacheing layer! - - const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID] || this.initFramebuffer(framebuffer); - - gl.bindFramebuffer(gl.FRAMEBUFFER, fbo.framebuffer); - // makesure all textures are unbound.. - - // now check for updates... - if (fbo.dirtyId !== framebuffer.dirtyId) - { - fbo.dirtyId = framebuffer.dirtyId; - - if (fbo.dirtyFormat !== framebuffer.dirtyFormat) - { - fbo.dirtyFormat = framebuffer.dirtyFormat; - this.updateFramebuffer(framebuffer); - } - else if (fbo.dirtySize !== framebuffer.dirtySize) - { - fbo.dirtySize = framebuffer.dirtySize; - this.resizeFramebuffer(framebuffer); - } - } - - for (let i = 0; i < framebuffer.colorTextures.length; i++) - { - if (framebuffer.colorTextures[i].texturePart) - { - this.renderer.texture.unbind(framebuffer.colorTextures[i].texture); - } - else - { - this.renderer.texture.unbind(framebuffer.colorTextures[i]); - } - } - - if (framebuffer.depthTexture) - { - this.renderer.texture.unbind(framebuffer.depthTexture); - } - - if (frame) - { - this.setViewport(frame.x, frame.y, frame.width, frame.height); - } - else - { - this.setViewport(0, 0, framebuffer.width, framebuffer.height); - } - } - else - { - gl.bindFramebuffer(gl.FRAMEBUFFER, null); - - if (frame) - { - this.setViewport(frame.x, frame.y, frame.width, frame.height); - } - else - { - this.setViewport(0, 0, this.renderer.width, this.renderer.height); - } - } - } - - setViewport(x, y, width, height) - { - const v = this.viewport; - - if (v.width !== width || v.height !== height || v.x !== x || v.y !== y) - { - v.x = x; - v.y = y; - v.width = width; - v.height = height; - - this.gl.viewport(x, y, width, height); - } - } - - get size() - { - if (this.current) - { - // TODO store temp - return { x: 0, y: 0, width: this.current.width, height: this.current.height }; - } - - return { x: 0, y: 0, width: this.renderer.width, height: this.renderer.height }; - } - - clear(r, g, b, a) - { - const gl = this.gl; - - // TODO clear color can be set only one right? - gl.clearColor(r, g, b, a); - gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); - } - - // private functions... - - initFramebuffer(framebuffer) - { - const gl = this.gl; - - // TODO - make this a class? - const fbo = { - framebuffer: gl.createFramebuffer(), - stencil: null, - dirtyId: 0, - dirtyFormat: 0, - dirtySize: 0, - }; - - framebuffer.glFrameBuffers[this.CONTEXT_UID] = fbo; - - return fbo; - } - - resizeFramebuffer(framebuffer) - { - const gl = this.gl; - - if (framebuffer.stencil || framebuffer.depth) - { - gl.bindRenderbuffer(gl.RENDERBUFFER, this.stencil); - gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); - } - } - - updateFramebuffer(framebuffer) - { - const gl = this.gl; - - const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID]; - - // bind the color texture - const colorTextures = framebuffer.colorTextures; - - let count = colorTextures.length; - - if (!this.drawBufferExtension) - { - count = Math.min(count, 1); - } - - const activeTextures = []; - - for (let i = 0; i < count; i++) - { - const texture = framebuffer.colorTextures[i]; - - if (texture.texturePart) - { - this.renderer.texture.bind(texture.texture, 0); - - gl.framebufferTexture2D(gl.FRAMEBUFFER, - gl.COLOR_ATTACHMENT0 + i, - gl.TEXTURE_CUBE_MAP_NEGATIVE_X + texture.side, - texture.texture._glTextures[this.CONTEXT_UID].texture, - 0); - } - else - { - this.renderer.texture.bind(texture, 0); - - gl.framebufferTexture2D(gl.FRAMEBUFFER, - gl.COLOR_ATTACHMENT0 + i, - gl.TEXTURE_2D, - texture._glTextures[this.CONTEXT_UID].texture, - 0); - } - - activeTextures.push(gl.COLOR_ATTACHMENT0 + i); - } - - if (this.drawBufferExtension && activeTextures.length > 1) - { - this.drawBufferExtension.drawBuffersWEBGL(activeTextures); - } - - if (framebuffer.depthTexture) - { - const depthTextureExt = this.renderer.context.extensions.depthTexture; - - if (depthTextureExt) - { - const depthTexture = framebuffer.depthTexture; - - this.renderer.texture.bind(depthTexture, 0); - - gl.framebufferTexture2D(gl.FRAMEBUFFER, - gl.DEPTH_ATTACHMENT, - gl.TEXTURE_2D, - depthTexture._glTextures[this.CONTEXT_UID].texture, - 0); - } - } - - if (framebuffer.stencil || framebuffer.depth) - { - fbo.stencil = gl.createRenderbuffer(); - - gl.bindRenderbuffer(gl.RENDERBUFFER, fbo.stencil); - - // TODO.. this is depth AND stencil? - gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, fbo.stencil); - gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); - // fbo.enableStencil(); - } - } -} diff --git a/packages/core/src/renderers/systems/MaskSystem.js b/packages/core/src/renderers/systems/MaskSystem.js deleted file mode 100644 index d9c4b1a..0000000 --- a/packages/core/src/renderers/systems/MaskSystem.js +++ /dev/null @@ -1,199 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import AlphaMaskFilter from '../filters/spriteMask/SpriteMaskFilter'; - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class MaskSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - // TODO - we don't need both! - this.scissor = false; - this.scissorData = null; - this.scissorRenderTarget = null; - - this.enableScissor = false; - - this.alphaMaskPool = []; - this.alphaMaskIndex = 0; - } - - /** - * Applies the Mask and adds it to the current filter stack. - * - * @param {PIXI.DisplayObject} target - Display Object to push the mask to - * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. - */ - push(target, maskData) - { - // TODO the root check means scissor rect will not - // be used on render textures more info here: - // https://github.com/pixijs/pixi.js/pull/3545 - - if (maskData.texture) - { - this.pushSpriteMask(target, maskData); - } - else if (this.enableScissor - && !this.scissor - && this.renderer._activeRenderTarget.root - && !this.renderer.stencil.stencilMaskStack.length - && maskData.isFastRect()) - { - const matrix = maskData.worldTransform; - - let rot = Math.atan2(matrix.b, matrix.a); - - // use the nearest degree! - rot = Math.round(rot * (180 / Math.PI)); - - if (rot % 90) - { - this.pushStencilMask(maskData); - } - else - { - this.pushScissorMask(target, maskData); - } - } - else - { - this.pushStencilMask(maskData); - } - } - - /** - * Removes the last mask from the mask stack and doesn't return it. - * - * @param {PIXI.DisplayObject} target - Display Object to pop the mask from - * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. - */ - pop(target, maskData) - { - if (maskData.texture) - { - this.popSpriteMask(target, maskData); - } - else if (this.enableScissor && !this.renderer.stencil.stencilMaskStack.length) - { - this.popScissorMask(target, maskData); - } - else - { - this.popStencilMask(target, maskData); - } - } - - /** - * Applies the Mask and adds it to the current filter stack. - * - * @param {PIXI.RenderTarget} target - Display Object to push the sprite mask to - * @param {PIXI.Sprite} maskData - Sprite to be used as the mask - */ - pushSpriteMask(target, maskData) - { - let alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; - - if (!alphaMaskFilter) - { - alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; - } - - alphaMaskFilter[0].resolution = this.renderer.resolution; - alphaMaskFilter[0].maskSprite = maskData; - - // TODO - may cause issues! - target.filterArea = maskData.getBounds(true); - - this.renderer.filterSystem.pushFilter(target, alphaMaskFilter); - - this.alphaMaskIndex++; - } - - /** - * Removes the last filter from the filter stack and doesn't return it. - * - */ - popSpriteMask() - { - this.renderer.filterSystem.popFilter(); - this.alphaMaskIndex--; - } - - /** - * Applies the Mask and adds it to the current filter stack. - * - * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. - */ - pushStencilMask(maskData) - { - this.renderer.batch.flush(); - this.renderer.stencil.pushStencil(maskData); - } - - /** - * Removes the last filter from the filter stack and doesn't return it. - * - */ - popStencilMask() - { - // this.renderer.currentRenderer.stop(); - this.renderer.stencil.popStencil(); - } - - /** - * - * @param {PIXI.DisplayObject} target - Display Object to push the mask to - * @param {PIXI.Graphics} maskData - The masking data. - */ - pushScissorMask(target, maskData) - { - maskData.renderable = true; - - const renderTarget = this.renderer._activeRenderTarget; - - const bounds = maskData.getBounds(); - - bounds.fit(renderTarget.size); - maskData.renderable = false; - - this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); - - const resolution = this.renderer.resolution; - - this.renderer.gl.scissor( - bounds.x * resolution, - (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, - bounds.width * resolution, - bounds.height * resolution - ); - - this.scissorRenderTarget = renderTarget; - this.scissorData = maskData; - this.scissor = true; - } - - /** - * - * - */ - popScissorMask() - { - this.scissorRenderTarget = null; - this.scissorData = null; - this.scissor = false; - - // must be scissor! - const gl = this.renderer.gl; - - gl.disable(gl.SCISSOR_TEST); - } -} diff --git a/packages/core/src/renderers/systems/ProjectionSystem.js b/packages/core/src/renderers/systems/ProjectionSystem.js deleted file mode 100644 index ec5efdb..0000000 --- a/packages/core/src/renderers/systems/ProjectionSystem.js +++ /dev/null @@ -1,73 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { Matrix } from '@pixi/math'; - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ - -export default class ProjectionSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this.projectionMatrix = new Matrix(); - } - - update(destinationFrame, sourceFrame, resolution, root) - { - this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; - this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; - - this.calculateProjection(this.destinationFrame, this.sourceFrame, resolution, root); - - this.renderer.globalUniforms.uniforms.projectionMatrix = this.projectionMatrix; - this.renderer.globalUniforms.update(); - } - - /** - * Updates the projection matrix based on a projection frame (which is a rectangle) - * - * @param {Rectangle} destinationFrame - The destination frame. - * @param {Rectangle} sourceFrame - The source frame. - */ - calculateProjection(destinationFrame, sourceFrame, resolution, root) - { - const pm = this.projectionMatrix; - - // I don't think we will need this line.. - // pm.identity(); - - if (!root) - { - pm.a = (1 / destinationFrame.width * 2) * resolution; - pm.d = (1 / destinationFrame.height * 2) * resolution; - - pm.tx = -1 - (sourceFrame.x * pm.a); - pm.ty = -1 - (sourceFrame.y * pm.d); - } - else - { - pm.a = (1 / destinationFrame.width * 2) * resolution; - pm.d = (-1 / destinationFrame.height * 2) * resolution; - - pm.tx = -1 - (sourceFrame.x * pm.a); - pm.ty = 1 - (sourceFrame.y * pm.d); - } - } - - /** - * Sets the transform of the active render target to the given matrix - * - * @param {PIXI.Matrix} matrix - The transformation matrix - */ - setTransform()// matrix) - { - // this._activeRenderTarget.transform = matrix; - } -} diff --git a/packages/core/src/renderers/systems/RenderTextureSystem.js b/packages/core/src/renderers/systems/RenderTextureSystem.js deleted file mode 100644 index a133ef1..0000000 --- a/packages/core/src/renderers/systems/RenderTextureSystem.js +++ /dev/null @@ -1,115 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { Rectangle } from '@pixi/math'; - -const tempRect = new Rectangle(); - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ - -export default class RenderTextureSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this.clearColor = renderer._backgroundColorRgba; - - // TODO moe this property somewhere else! - this.defaultMaskStack = []; - this.defaultFilterStack = [{}]; - - // empty render texture? - this.renderTexture = null; - - this.destinationFrame = new Rectangle(); - } - - bind(renderTexture, sourceFrame, destinationFrame) - { - // TODO - do we want this?? - if (this.renderTexture === renderTexture) return; - this.renderTexture = renderTexture; - - const renderer = this.renderer; - - if (renderTexture) - { - const baseTexture = renderTexture.baseTexture; - - if (!destinationFrame) - { - tempRect.width = baseTexture.realWidth; - tempRect.height = baseTexture.realHeight; - - destinationFrame = tempRect; - } - - if (!sourceFrame) - { - sourceFrame = destinationFrame; - } - - this.renderer.framebuffer.bind(baseTexture.frameBuffer, destinationFrame); - - this.renderer.projection.update(destinationFrame, sourceFrame, baseTexture.resolution, false); - this.renderer.stencil.setMaskStack(baseTexture.stencilMaskStack); - } - else - { - // TODO these validation checks happen deeper down.. - // thing they can be avoided.. - if (!destinationFrame) - { - tempRect.width = renderer.width; - tempRect.height = renderer.height; - - destinationFrame = tempRect; - } - - if (!sourceFrame) - { - sourceFrame = destinationFrame; - } - - renderer.framebuffer.bind(null, destinationFrame); - - // TODO store this.. - this.renderer.projection.update(destinationFrame, sourceFrame, this.renderer.resolution, true); - this.renderer.stencil.setMaskStack(this.defaultMaskStack); - } - - this.destinationFrame.copyFrom(destinationFrame); - } - - /** - * Erases the render texture and fills the drawing area with a colour - * - * @param {number} [clearColor] - The colour - * @return {PIXI.Renderer} Returns itself. - */ - clear(clearColor) - { - if (this.renderTexture) - { - clearColor = clearColor || this.renderTexture.baseTexture.clearColor; - } - else - { - clearColor = clearColor || this.clearColor; - } - - this.renderer.framebuffer.clear(clearColor[0], clearColor[1], clearColor[2], clearColor[3]); - } - - resize()// screenWidth, screenHeight) - { - // resize the root only! - this.bind(null); - } -} diff --git a/packages/core/src/renderers/systems/StateSystem.js b/packages/core/src/renderers/systems/StateSystem.js deleted file mode 100755 index d7f4caf..0000000 --- a/packages/core/src/renderers/systems/StateSystem.js +++ /dev/null @@ -1,288 +0,0 @@ -import mapWebGLBlendModesToPixi from '../utils/mapWebGLBlendModesToPixi'; -import WebGLSystem from './WebGLSystem'; -import WebGLState from '../State'; - -const BLEND = 0; -const OFFSET = 1; -const CULLING = 2; -const DEPTH_TEST = 3; -const WINDING = 4; - -/** - * A WebGL state machines - * - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class StateSystem extends WebGLSystem -{ - /** - * @param {WebGLRenderingContext} gl - The current WebGL rendering context - */ - constructor(renderer) - { - super(renderer); - - this.gl = null; - - this.maxAttribs = null; - - // check we have vao.. - this.nativeVaoExtension = null; - - this.attribState = null; - - this.stateId = 0; - this.polygonOffset = 0; - this.blendMode = 17; - - this.map = []; - - // map functions for when we set state.. - this.map[BLEND] = this.setBlend; - this.map[OFFSET] = this.setOffset; - this.map[CULLING] = this.setCullFace; - this.map[DEPTH_TEST] = this.setDepthTest; - this.map[WINDING] = this.setFrontFace; - - this.checks = []; - - this.defaultState = new WebGLState(); - this.defaultState.blend = true; - this.defaultState.depth = true; - } - - contextChange(gl) - { - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; - - this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); - - // check we have vao.. - this.nativeVaoExtension = ( - gl.getExtension('OES_vertex_array_object') - || gl.getExtension('MOZ_OES_vertex_array_object') - || gl.getExtension('WEBKIT_OES_vertex_array_object') - ); - - this.attribState = { - tempAttribState: new Array(this.maxAttribs), - attribState: new Array(this.maxAttribs), - }; - - this.blendModes = mapWebGLBlendModesToPixi(gl); - - this.setState(this.defaultState); - - this.reset(); - } - - /** - * Sets the current state - * - * @param {*} state - The state to set. - */ - setState(state) - { - state = state || this.defaultState; - - // TODO maybe to an object check? ( this.state === state )? - if (this.stateId !== state.data) - { - let diff = this.stateId ^ state.data; - let i = 0; - - // order from least to most common - while (diff) - { - if (diff & 1) - { - // state change! - this.map[i].call(this, !!(state.data & (1 << i))); - } - - diff = diff >> 1; - i++; - } - - this.stateId = state.data; - } - - // based on the above settings we check for specific modes.. - // for example if blend is active we check and set the blend modes - // or of polygon offset is active we check the poly depth. - for (let i = 0; i < this.checks.length; i++) - { - this.checks[i](this, state); - } - } - - /** - * Enables or disabled blending. - * - * @param {boolean} value - Turn on or off webgl blending. - */ - setBlend(value) - { - this.updateCheck(StateSystem.checkBlendMode, value); - - this.gl[value ? 'enable' : 'disable'](this.gl.BLEND); - } - - /** - * Enables or disable polygon offset fill - * - * @param {boolean} value - Turn on or off webgl polygon offset testing. - */ - setOffset(value) - { - this.gl[value ? 'enable' : 'disable'](this.gl.POLYGON_OFFSET_FILL); - } - - /** - * Sets whether to enable or disable depth test. - * - * @param {boolean} value - Turn on or off webgl depth testing. - */ - setDepthTest(value) - { - this.gl[value ? 'enable' : 'disable'](this.gl.DEPTH_TEST); - } - - /** - * Sets whether to enable or disable cull face. - * - * @param {boolean} value - Turn on or off webgl cull face. - */ - setCullFace(value) - { - this.gl[value ? 'enable' : 'disable'](this.gl.CULL_FACE); - } - - /** - * Sets the gl front face. - * - * @param {boolean} value - true is clockwise and false is counter-clockwise - */ - setFrontFace(value) - { - this.gl.frontFace(this.gl[value ? 'CW' : 'CCW']); - } - - /** - * Sets the blend mode. - * - * @param {number} value - The blend mode to set to. - */ - setBlendMode(value) - { - if (value === this.blendMode) - { - return; - } - - this.blendMode = value; - this.gl.blendFunc(this.blendModes[value][0], this.blendModes[value][1]); - } - - /** - * Sets the polygon offset. - * - * @param {number} value - the polygon offset - * @param {number} scale - the polygon offset scale - */ - setPolygonOffset(value, scale) - { - this.gl.polygonOffset(value, scale); - } - - /** - * Disables all the vaos in use - * - */ - resetAttributes() - { - for (let i = 0; i < this.attribState.tempAttribState.length; i++) - { - this.attribState.tempAttribState[i] = 0; - } - - for (let i = 0; i < this.attribState.attribState.length; i++) - { - this.attribState.attribState[i] = 0; - } - - // im going to assume one is always active for performance reasons. - for (let i = 1; i < this.maxAttribs; i++) - { - this.gl.disableVertexAttribArray(i); - } - } - - // used - /** - * Resets all the logic and disables the vaos - */ - reset() - { - // unbind any VAO if they exist.. - if (this.nativeVaoExtension) - { - this.nativeVaoExtension.bindVertexArrayOES(null); - } - - // reset all attributes.. - this.resetAttributes(); - - this.gl.pixelStorei(this.gl.UNPACK_FLIP_Y_WEBGL, false); - - this.setBlendMode(0); - - // TODO? - // this.setState(this.defaultState); - } - - /** - * checks to see which updates should be checked based on which settings have been activated - * for example if blend is enabled then we shold check the blend modes each time the state is changed - * or if poygon fill is activated then we need to check if the polygone offset changes. - * The idea is that we only check what we have too. - * - * @param {Function} func the checking function to add or remove - * @param {boolean} value should the check function be added or removed. - */ - updateCheck(func, value) - { - const index = this.checks.indexOf(func); - - if (value && index === -1) - { - this.checks.push(func); - } - else if (!value && index !== -1) - { - this.checks.splice(index, 1); - } - } - - /** - * A private little wrapper function that we call to check the blend mode. - * - * @static - * @private - * @param {PIXI.StateSystem} System the System to perform the state check on - * @param {PIXI.State} state the state that the blendMode will pulled from - */ - static checkBlendMode(system, state) - { - system.setBlendMode(state.blendMode); - } - - // TODO - add polygon offset? -} diff --git a/packages/core/src/renderers/systems/StencilSystem.js b/packages/core/src/renderers/systems/StencilSystem.js deleted file mode 100644 index 5d538cb..0000000 --- a/packages/core/src/renderers/systems/StencilSystem.js +++ /dev/null @@ -1,133 +0,0 @@ -import WebGLSystem from './WebGLSystem'; - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class StencilSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - this.stencilMaskStack = []; - } - - /** - * Changes the mask stack that is used by this System. - * - * @param {PIXI.Graphics[]} stencilMaskStack - The mask stack - */ - setMaskStack(stencilMaskStack) - { - const gl = this.renderer.gl; - - if (stencilMaskStack.length !== this.stencilMaskStack.length) - { - if (stencilMaskStack.length === 0) - { - gl.disable(gl.STENCIL_TEST); - } - else - { - gl.enable(gl.STENCIL_TEST); - } - } - - this.stencilMaskStack = stencilMaskStack; - } - - /** - * Applies the Mask and adds it to the current stencil stack. @alvin - * - * @param {PIXI.Graphics} graphics - The mask - */ - pushStencil(graphics) - { - this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); - - // this.renderer._activeRenderTarget.attachStencilBuffer(); - - const gl = this.renderer.gl; - const prevMaskCount = this.stencilMaskStack.length; - - if (prevMaskCount === 0) - { - gl.enable(gl.STENCIL_TEST); - } - - this.stencilMaskStack.push(graphics); - - // Increment the refference stencil value where the new mask overlaps with the old ones. - gl.colorMask(false, false, false, false); - gl.stencilFunc(gl.EQUAL, prevMaskCount, this._getBitwiseMask()); - gl.stencilOp(gl.KEEP, gl.KEEP, gl.INCR); - this.renderer.plugins.graphics.render(graphics); - - this._useCurrent(); - } - - /** - * Removes the last mask from the stencil stack. @alvin - */ - popStencil() - { - this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); - - const gl = this.renderer.gl; - const graphics = this.stencilMaskStack.pop(); - - if (this.stencilMaskStack.length === 0) - { - // the stack is empty! - gl.disable(gl.STENCIL_TEST); - gl.clear(gl.STENCIL_BUFFER_BIT); - gl.clearStencil(0); - } - else - { - // Decrement the refference stencil value where the popped mask overlaps with the other ones - gl.colorMask(false, false, false, false); - gl.stencilOp(gl.KEEP, gl.KEEP, gl.DECR); - this.renderer.plugins.graphics.render(graphics); - - this._useCurrent(); - } - } - - /** - * Setup renderer to use the current stencil data. - */ - _useCurrent() - { - const gl = this.renderer.gl; - - gl.colorMask(true, true, true, true); - gl.stencilFunc(gl.EQUAL, this.stencilMaskStack.length, this._getBitwiseMask()); - gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP); - } - - /** - * Fill 1s equal to the number of acitve stencil masks. - * - * @return {number} The bitwise mask. - */ - _getBitwiseMask() - { - return (1 << this.stencilMaskStack.length) - 1; - } - - /** - * Destroys the mask stack. - * - */ - destroy() - { - super.destroy(this); - - this.stencilMaskStack = null; - } -} diff --git a/packages/core/src/renderers/systems/WebGLSystem.js b/packages/core/src/renderers/systems/WebGLSystem.js deleted file mode 100644 index d3d5b31..0000000 --- a/packages/core/src/renderers/systems/WebGLSystem.js +++ /dev/null @@ -1,41 +0,0 @@ -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this manager works for. - */ - constructor(renderer) - { - /** - * The renderer this manager works for. - * - * @member {PIXI.Renderer} - */ - this.renderer = renderer; - - this.renderer.runners.contextChange.add(this); - } - - /** - * Generic method called when there is a WebGL context change. - * - */ - contextChange() - { - // do some codes init! - } - - /** - * Generic destroy methods to be overridden by the subclass - * - */ - destroy() - { - this.renderer.runners.contextChange.remove(this); - this.renderer = null; - } -} diff --git a/packages/core/src/AbstractRenderer.js b/packages/core/src/AbstractRenderer.js new file mode 100644 index 0000000..64b2eb1 --- /dev/null +++ b/packages/core/src/AbstractRenderer.js @@ -0,0 +1,353 @@ +import { hex2string, hex2rgb } from '@pixi/utils'; +import { Matrix, Rectangle } from '@pixi/math'; +import { RENDERER_TYPE } from '@pixi/constants'; +import { settings } from '@pixi/settings'; +import { Container } from '@pixi/display'; +import RenderTexture from './renderTexture/RenderTexture'; +import EventEmitter from 'eventemitter3'; + +const tempMatrix = new Matrix(); + +/** + * 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 EventEmitter + * @memberof PIXI + */ +export default class AbstractRenderer extends EventEmitter +{ + // eslint-disable-next-line valid-jsdoc + /** + * @param {string} system - The name of the system this renderer is for. + * @param {object} [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.transparent=false] - If the render view is transparent, default false + * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false + * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. The + * resolution of the renderer retina would be 2. + * @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 {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when rendering, + * stopping pixel interpolation. + */ + constructor(system, options, arg2, arg3) + { + super(); + + // Support for constructor(system, screenWidth, screenHeight, options) + if (typeof options === 'number') + { + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + }, arg3); + } + + // 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 = RENDERER_TYPE.UNKNOWN; + + /** + * Measurements of the screen. (0, 0, screenWidth, screenHeight) + * + * Its safe to use as filterArea or hitArea for 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 1 + */ + this.resolution = options.resolution || settings.RESOLUTION; + + /** + * Whether the render view is transparent + * + * @member {boolean} + */ + this.transparent = options.transparent; + + /** + * Whether css dimensions of canvas view should be resized to screen dimensions automatically + * + * @member {boolean} + */ + this.autoResize = options.autoResize || false; + + /** + * Tracks the blend modes useful for this renderer. + * + * @member {object} + */ + this.blendModes = null; + + /** + * 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; + + /** + * If true PixiJS will Math.floor() x/y values when rendering, stopping pixel interpolation. + * Handy for crisp pixel art and speed on legacy devices. + * + * @member {boolean} + */ + this.roundPixels = options.roundPixels; + + /** + * The background color as a number. + * + * @member {number} + * @private + */ + this._backgroundColor = 0x000000; + + /** + * The background color as an [R, G, B] array. + * + * @member {number[]} + * @private + */ + this._backgroundColorRgba = [0, 0, 0, 0]; + + /** + * The background color as a string. + * + * @member {string} + * @private + */ + this._backgroundColorString = '#000000'; + + this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter + + /** + * This temporary display object used as the parent of the currently being rendered item + * + * @member {PIXI.DisplayObject} + * @private + */ + this._tempDisplayObjectParent = new Container(); + + /** + * The last root object that the renderer tried to render. + * + * @member {PIXI.DisplayObject} + * @private + */ + this._lastObjectRendered = this._tempDisplayObjectParent; + + /** + * Collection of plugins. + * @readonly + * @member {object} + */ + this.plugins = {}; + } + + /** + * Initialize the plugins. + * + * @protected + * @param {object} staticMap - The dictionary of staticly saved plugins. + */ + initPlugins(staticMap) + { + 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() + { + return this.view.width; + } + + /** + * Same as view.height, actual number of pixels in the canvas by vertical + * + * @member {number} + * @readonly + * @default 600 + */ + get height() + { + return this.view.height; + } + + /** + * Resizes the screen and canvas to the specified width and height + * Canvas dimensions are multiplied by resolution + * + * @param {number} screenWidth - the new width of the screen + * @param {number} screenHeight - the new height of the screen + */ + resize(screenWidth, screenHeight) + { + this.screen.width = screenWidth; + this.screen.height = screenHeight; + + this.view.width = screenWidth * this.resolution; + this.view.height = screenHeight * this.resolution; + + if (this.autoResize) + { + this.view.style.width = `${screenWidth}px`; + this.view.style.height = `${screenHeight}px`; + } + } + + /** + * 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 {PIXI.DisplayObject} displayObject - The displayObject the object will be generated from + * @param {number} scaleMode - Should be one of the scaleMode consts + * @param {number} resolution - The resolution / device pixel ratio of the texture being generated + * @param {PIXI.Rectangle} [region] - The region of the displayObject, that shall be rendered, + * if no region is specified, defaults to the local bounds of the displayObject. + * @return {PIXI.Texture} a texture of the graphics object + */ + generateTexture(displayObject, scaleMode, resolution, region) + { + region = region || displayObject.getLocalBounds(); + + // 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(region.width | 0, region.height | 0, scaleMode, resolution); + + tempMatrix.tx = -region.x; + tempMatrix.ty = -region.y; + + this.render(displayObject, renderTexture, false, tempMatrix, true); + + return renderTexture; + } + + /** + * Removes everything from the renderer and optionally removes the Canvas DOM element. + * + * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. + */ + destroy(removeView) + { + 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); + } + + this.plugins = null; + + this.type = RENDERER_TYPE.UNKNOWN; + + this.view = null; + + this.screen = null; + + this.resolution = 0; + + this.transparent = false; + + this.autoResize = false; + + this.blendModes = null; + + this.options = null; + + this.preserveDrawingBuffer = false; + this.clearBeforeRender = false; + + this.roundPixels = false; + + this._backgroundColor = 0; + this._backgroundColorRgba = null; + this._backgroundColorString = null; + + this._tempDisplayObjectParent = null; + this._lastObjectRendered = null; + } + + /** + * The background color to fill if not transparent + * + * @member {number} + */ + get backgroundColor() + { + return this._backgroundColor; + } + + set backgroundColor(value) // eslint-disable-line require-jsdoc + { + this._backgroundColor = value; + this._backgroundColorString = hex2string(value); + hex2rgb(value, this._backgroundColorRgba); + } +} diff --git a/packages/core/src/Renderer.js b/packages/core/src/Renderer.js new file mode 100644 index 0000000..2227e78 --- /dev/null +++ b/packages/core/src/Renderer.js @@ -0,0 +1,338 @@ +import AbstractRenderer from './AbstractRenderer'; +import { sayHello } from '@pixi/utils'; +import MaskSystem from './mask/MaskSystem'; +import StencilSystem from './mask/StencilSystem'; +import FilterSystem from './filters/FilterSystem'; +import FramebufferSystem from './framebuffer/FramebufferSystem'; +import RenderTextureSystem from './renderTexture/RenderTextureSystem'; +import TextureSystem from './textures/TextureSystem'; +import ProjectionSystem from './projection/ProjectionSystem'; +import StateSystem from './state/StateSystem'; +import GeometrySystem from './geometry/GeometrySystem'; +import ShaderSystem from './shader/ShaderSystem'; +import ContextSystem from './context/ContextSystem'; +import BatchSystem from './batch/BatchSystem'; +import TextureGCSystem from './textures/TextureGCSystem'; +import { RENDERER_TYPE } from '@pixi/constants'; +import UniformGroup from './shader/UniformGroup'; +import { Matrix } from '@pixi/math'; +import Runner from 'mini-runner'; + +/** + * 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 Render works by automatically managing webGLBatchs. + * 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 :) + * + * @class + * @memberof PIXI + * @extends PIXI.AbstractRenderer + */ +export default class Renderer extends AbstractRenderer +{ + // eslint-disable-next-line valid-jsdoc + /** + * + * @param {object} [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.transparent=false] - If the render view is transparent, default false + * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false + * @param {boolean} [options.antialias=false] - sets antialias. If not available natively then FXAA + * antialiasing is used + * @param {boolean} [options.forceFXAA=false] - forces FXAA antialiasing to be used over native. + * FXAA is faster, but may not always look as great + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. + * The resolution of the renderer retina would be 2. + * @param {boolean} [options.clearBeforeRender=true] - This sets if the renderer will clear + * the canvas or not before the new render pass. If you wish to set this to false, you *must* set + * preserveDrawingBuffer to `true`. + * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, + * enable this if you need to call toDataUrl on the webgl context. + * @param {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when + * rendering, stopping pixel interpolation. + * @param {number} [options.backgroundColor=0x000000] - The background color of the rendered area + * (shown if not transparent). + * @param {boolean} [options.legacy=false] - If true PixiJS will aim to ensure compatibility + * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. + * @param {string} [options.powerPreference] - Parameter passed to webgl context, set to "high-performance" + * for devices with dual graphics card + */ + constructor(options, arg2, arg3) + { + super('WebGL', options, arg2, arg3); + + /** + * The type of this renderer as a standardised const + * + * @member {number} + * @see PIXI.RENDERER_TYPE + */ + this.type = RENDERER_TYPE.WEBGL; + + // this will be set by the contextSystem (this.context) + this.gl = null; + this.CONTEXT_UID = 0; + this.legacy = !!options.legacy; + + // TODO legacy! + + // runners! + this.runners = { + destroy: new Runner('destroy'), + contextChange: new Runner('contextChange', 1), + reset: new Runner('reset'), + update: new Runner('update'), + postrender: new Runner('postrender'), + prerender: new Runner('prerender'), + resize: new Runner('resize', 2), + }; + + 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(GeometrySystem, 'geometry') + .addSystem(FramebufferSystem, 'framebuffer') + .addSystem(StencilSystem, 'stencil') + .addSystem(ProjectionSystem, 'projection') + .addSystem(TextureGCSystem, 'textureGC') + .addSystem(FilterSystem, 'filter') + .addSystem(RenderTextureSystem, 'renderTexture') + .addSystem(BatchSystem, 'batch'); + + this.initPlugins(Renderer.__plugins); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ + if (options.context) + { + this.context.initFromContext(options.context); + } + else + { + this.context.initFromOptions({ + alpha: this.transparent, + antialias: options.antialias, + premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', + stencil: true, + 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); + } + + /** + * Add a new system to the renderer. + * @param {class} ClassRef - Class reference + * @param {string} [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. + * @return {PIXI.Renderer} Return instance of renderer + */ + addSystem(ClassRef, name) + { + if (!name) + { + name = ClassRef.name; + } + + // TODO - read name from class.name.. + + /* + if(name.includes('System')) + { + name = name.replace('System', ''); + name = name.charAt(0).toLowerCase() + name.slice(1); + } + */ + + const system = new ClassRef(this); + + if (this[name]) + { + throw new Error(`Whoops! ${name} is already a manger`); + } + + this[name] = system; + + for (const i in this.runners) + { + this.runners[i].add(system); + } + + return this; + + /** + * 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. + */ + } + + /** + * Renders the object to its webGL view + * + * @param {PIXI.DisplayObject} displayObject - the object to be rendered + * @param {PIXI.RenderTexture} renderTexture - The render texture to render to. + * @param {boolean} [clear] - Should the canvas be cleared before the new render + * @param {PIXI.Transform} [transform] - A transform to apply to the render texture before rendering. + * @param {boolean} [skipUpdateTransform] - Should we skip the update transform pass? + */ + render(displayObject, renderTexture, clear, transform, skipUpdateTransform) + { + // can be handy to know! + this.renderingToScreen = !renderTexture; + + this.runners.prerender.run(); + this.emit('prerender'); + + // 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.parent; + + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = 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.run(); + + this.emit('postrender'); + } + + /** + * Resizes the webGL view to the specified width and height. + * + * @param {number} screenWidth - the new width of the screen + * @param {number} screenHeight - the new height of the screen + */ + resize(screenWidth, screenHeight) + { + AbstractRenderer.prototype.resize.call(this, screenWidth, screenHeight); + + this.runners.resize.run(screenWidth, screenHeight); + } + + /** + * Resets the WebGL state so you can render things however you fancy! + * + * @return {PIXI.Renderer} Returns itself. + */ + reset() + { + this.runners.reset.run(); + + return this; + } + + clear() + { + this.framebuffer.bind(); + this.framebuffer.clear(); + } + + /** + * Removes everything from the renderer (event listeners, spritebatch, etc...) + * + * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. + * See: https://github.com/pixijs/pixi.js/issues/2233 + */ + destroy(removeView) + { + this.runners.destroy.run(); + + // call base destroy + super.destroy(removeView); + + // TODO nullify all the managers.. + this.gl = null; + } + + /** + * 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 + * builds and excluding plugins. + * @name PIXI.Renderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. + */ + + /** + * Adds a plugin to the renderer. + * + * @method + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + static registerPlugin(pluginName, ctor) + { + Renderer.__plugins = Renderer.__plugins || {}; + Renderer.__plugins[pluginName] = ctor; + } +} diff --git a/packages/core/src/System.js b/packages/core/src/System.js new file mode 100644 index 0000000..05a10c8 --- /dev/null +++ b/packages/core/src/System.js @@ -0,0 +1,42 @@ +/** + * System is a base class used for extending systems used by the {@link PIXI.Renderer} + * @see PIXI.Renderer#addSystem + * @class + * @memberof PIXI + */ +export default class System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + /** + * The renderer this manager works for. + * + * @member {PIXI.Renderer} + */ + this.renderer = renderer; + + this.renderer.runners.contextChange.add(this); + } + + /** + * Generic method called when there is a WebGL context change. + * + */ + contextChange() + { + // do some codes init! + } + + /** + * Generic destroy methods to be overridden by the subclass + * + */ + destroy() + { + this.renderer.runners.contextChange.remove(this); + this.renderer = null; + } +} diff --git a/packages/core/src/batch/BatchSystem.js b/packages/core/src/batch/BatchSystem.js new file mode 100644 index 0000000..b04c249 --- /dev/null +++ b/packages/core/src/batch/BatchSystem.js @@ -0,0 +1,68 @@ +import System from '../System'; +import ObjectRenderer from './ObjectRenderer'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class BatchSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + super(renderer); + + /** + * An empty renderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.emptyRenderer = new ObjectRenderer(renderer); + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.currentRenderer = this.emptyRenderer; + } + + /** + * Changes the current renderer to the one given in parameter + * + * @param {PIXI.ObjectRenderer} objectRenderer - The object renderer to use. + */ + setObjectRenderer(objectRenderer) + { + if (this.currentRenderer === objectRenderer) + { + return; + } + + this.currentRenderer.stop(); + this.currentRenderer = objectRenderer; + + this.renderer.state.setState(objectRenderer.state); + + this.currentRenderer.start(); + } + + /** + * This should be called if you wish to do some custom rendering + * It will basically render anything that may be batched up such as sprites + * + */ + flush() + { + this.setObjectRenderer(this.emptyRenderer); + } + + reset() + { + this.setObjectRenderer(this.emptyRenderer); + } +} diff --git a/packages/core/src/batch/ObjectRenderer.js b/packages/core/src/batch/ObjectRenderer.js new file mode 100644 index 0000000..0c851a9 --- /dev/null +++ b/packages/core/src/batch/ObjectRenderer.js @@ -0,0 +1,48 @@ +import System from '../System'; + +/** + * Base for a common object renderer that can be used as a system renderer plugin. + * + * @class + * @extends PIXI.System + * @memberof PIXI + */ +export default class ObjectRenderer extends System +{ + /** + * Starts the renderer and sets the shader + * + */ + start() + { + // set the shader.. + } + + /** + * Stops the renderer + * + */ + stop() + { + this.flush(); + } + + /** + * Stub method for rendering content and emptying the current batch. + * + */ + flush() + { + // flush! + } + + /** + * Renders an object + * + * @param {PIXI.DisplayObject} object - The object to render. + */ + render(object) // eslint-disable-line no-unused-vars + { + // render the object + } +} diff --git a/packages/core/src/context/ContextSystem.js b/packages/core/src/context/ContextSystem.js new file mode 100644 index 0000000..301aab4 --- /dev/null +++ b/packages/core/src/context/ContextSystem.js @@ -0,0 +1,186 @@ +import System from '../System'; +import { settings } from '@pixi/settings'; + +let CONTEXT_UID = 0; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class ContextSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.webGLVersion = 1; + + this.handleContextLost = this.handleContextLost.bind(this); + this.handleContextRestored = this.handleContextRestored.bind(this); + + this.extensions = {}; + + renderer.view.addEventListener('webglcontextlost', this.handleContextLost, false); + renderer.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + } + + get isLost() + { + return (!this.gl || this.gl.isContextLost()); + } + + contextChange(gl) + { + this.gl = gl; + + // restore a context if it was previously lost + if (gl.isContextLost() && gl.getExtension('WEBGL_lose_context')) + { + gl.getExtension('WEBGL_lose_context').restoreContext(); + } + } + + initFromContext(gl) + { + this.gl = gl; + this.validateContext(gl); + this.renderer.gl = gl; + this.renderer.CONTEXT_UID = CONTEXT_UID++; + this.renderer.runners.contextChange.run(gl); + } + + initFromOptions(options) + { + const gl = this.createContext(this.renderer.view, options); + + this.initFromContext(gl); + } + + /** + * Helper class to create a webGL Context + * + * @class + * @memberof PIXI.glCore + * @param canvas {HTMLCanvasElement} the canvas element that we will get the context from + * @param options {Object} An options object that gets passed in to the canvas element containing the context attributes, + * see https://developer.mozilla.org/en/docs/Web/API/HTMLCanvasElement/getContext for the options available + * @return {WebGLRenderingContext} the WebGL context + */ + createContext(canvas, options) + { + let gl; + + if (settings.PREFER_WEBGL_2) + { + gl = canvas.getContext('webgl2', options); + } + + if (gl) + { + this.webGLVersion = 2; + } + else + { + this.webGLVersion = 1; + + gl = canvas.getContext('webgl', options) + || canvas.getContext('experimental-webgl', options); + + if (!gl) + { + // fail, not able to get a context + throw new Error('This browser does not support webGL. Try using the canvas renderer'); + } + } + + this.gl = gl; + + this.getExtensions(); + + return gl; + } + + getExtensions() + { + // time to set up default etensions that pixi uses.. + const gl = this.gl; + const extensions = this.extensions; + + if (this.webGLVersion === 1) + { + extensions.drawBuffers = gl.getExtension('WEBGL_draw_buffers'); + extensions.depthTexture = gl.getExtension('WEBKIT_WEBGL_depth_texture'); + extensions.floatTexture = gl.getExtension('OES_texture_float'); + extensions.loseContext = gl.getExtension('WEBGL_lose_context'); + + extensions.vertexArrayObject = gl.getExtension('OES_vertex_array_object') + || gl.getExtension('MOZ_OES_vertex_array_object') + || gl.getExtension('WEBKIT_OES_vertex_array_object'); + } + + // we don't use any specific WebGL 2 ones yet! + } + + /** + * Handles a lost webgl context + * + * @private + * @param {WebGLContextEvent} event - The context lost event. + */ + handleContextLost(event) + { + event.preventDefault(); + } + + /** + * Handles a restored webgl context + * + * @private + */ + handleContextRestored() + { + this.renderer.runners.contextChange.run(this.gl); + } + + destroy() + { + const view = this.renderer.view; + + // remove listeners + view.removeEventListener('webglcontextlost', this.handleContextLost); + view.removeEventListener('webglcontextrestored', this.handleContextRestored); + + this.gl.useProgram(null); + + if (this.extensions.loseContext) + { + this.extensions.loseContext.loseContext(); + } + } + + postrender() + { + this.gl.flush(); + } + + validateContext(gl) + { + const attributes = gl.getContextAttributes(); + + // this is going to be fairly simple for now.. but at least we have room to grow! + if (!attributes.stencil) + { + /* eslint-disable max-len */ + + /* eslint-disable no-console */ + console.warn('Provided WebGL context does not have a stencil buffer, masks may not render correctly'); + /* eslint-enable no-console */ + + /* eslint-enable max-len */ + } + } +} diff --git a/packages/core/src/filters/Filter.js b/packages/core/src/filters/Filter.js new file mode 100644 index 0000000..9d8cad8 --- /dev/null +++ b/packages/core/src/filters/Filter.js @@ -0,0 +1,140 @@ +import Shader from '../shader/Shader'; +import Program from '../shader/Program'; +import State from '../state/State'; +import { settings } from '@pixi/settings'; +// import extractUniformsFromSrc from './extractUniformsFromSrc'; +import defaultVertex from './defaultFilter.vert'; +import defaultFragment from './defaultFilter.frag'; + +// let math = require('../../math'); +/** + * @class + * @memberof PIXI + * @extends PIXI.Shader + */ +export default class Filter extends Shader +{ + /** + * @param {string} [vertexSrc] - The source of the vertex shader. + * @param {string} [fragmentSrc] - The source of the fragment shader. + * @param {object} [uniforms] - Custom uniforms to use to augment the built-in ones. + */ + constructor(vertexSrc, fragmentSrc, uniforms) + { + const program = Program.from(vertexSrc, fragmentSrc); + + super(program, uniforms); + + /** + * The padding of the filter. Some filters require extra space to breath such as a blur. + * Increasing this will add extra width and height to the bounds of the object that the + * filter is applied to. + * + * @member {number} + */ + this.padding = 0; + + /** + * The resolution of the filter. Setting this to be lower will lower the quality but + * increase the performance of the filter. + * + * @member {number} + */ + this.resolution = settings.RESOLUTION; + + /** + * If enabled is true the filter is applied, if false it will not. + * + * @member {boolean} + */ + this.enabled = true; + + /** + * If enabled, PixiJS will fit the filter area into boundaries for better performance. + * Switch it off if it does not work for specific shader. + * + * @member {boolean} + */ + this.autoFit = true; + + /** + * Legacy filters use position and uvs from attributes + * @member {boolean} + * @readonly + */ + this.legacy = !!this.program.attributeData.aTextureCoord; + + /** + * the webGL state the filter requires to render + * @member {PIXI.State} + */ + this.state = new State(); + } + + /** + * Applies the filter + * + * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + * @param {boolean} clear - Should the output be cleared before rendering to it + * @param {object} [currentState] - It's current state of filter. + * There are some useful properties in the currentState : + * target, filters, sourceFrame, destinationFrame, renderTarget, resolution + */ + apply(filterManager, input, output, clear, currentState, derp) // eslint-disable-line no-unused-vars + { + // do as you please! + + filterManager.applyFilter(this, input, output, clear, currentState, derp); + + // or just do a regular render.. + } + + /** + * Sets the blendmode of the filter + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL + */ + get blendMode() + { + return this.state.blendMode; + } + + set blendMode(value) // eslint-disable-line require-jsdoc + { + this.state.blendMode = value; + } + + /** + * The default vertex shader source + * + * @static + * @constant + */ + static get defaultVertexSrc() + { + return defaultVertex; + } + + /** + * The default fragment shader source + * + * @static + * @constant + */ + static get defaultFragmentSrc() + { + return defaultFragment; + } +} + +/** + * Used for caching shader IDs + * + * @static + * @private + */ +Filter.SOURCE_KEY_MAP = {}; + diff --git a/packages/core/src/filters/FilterSystem.js b/packages/core/src/filters/FilterSystem.js new file mode 100644 index 0000000..3174318 --- /dev/null +++ b/packages/core/src/filters/FilterSystem.js @@ -0,0 +1,431 @@ +import System from '../System'; + +import RenderTexture from '../renderTexture/RenderTexture'; +import Quad from '../utils/Quad'; +import QuadUv from '../utils/QuadUv'; +import { Rectangle } from '@pixi/math'; +import * as filterTransforms from './filterTransforms'; +import bitTwiddle from 'bit-twiddle'; +import UniformGroup from '../shader/UniformGroup'; +import { DRAW_MODES } from '@pixi/constants'; + +// +/** + * @ignore + * @class + */ +class FilterState +{ + /** + * + */ + constructor() + { + this.renderTexture = null; + this.sourceFrame = new Rectangle(); + this.destinationFrame = new Rectangle(); + this.filters = []; + this.target = null; + this.legacy = false; + this.resolution = 1; + } +} + +/** + * @class + * @memberof PIXI.systems + * @extends PIXI.System + */ +export default class FilterSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + /** + * stores a bunch of PO2 textures used for filtering + * @type {Object} + */ + this.texturePool = {}; + + /** + * a pool for storing filter states, save us creating new ones each tick + * @type {Array} + */ + this.statePool = []; + + /** + * A very simple geometry used when drawing a filter effect to the screen + * @type {Quad} + */ + this.quad = new Quad(); + + this.quadUv = new QuadUv(); + + /** + * Temporary rect for maths + * @type {PIXI.Rectangle} + */ + this.tempRect = new Rectangle(); + + this.activeState = {}; + + /** + * this uniform group is attached to filter uniforms when used + * @type {UniformGroup} + */ + this.globalUniforms = new UniformGroup({ + sourceFrame: this.tempRect, + destinationFrame: this.tempRect, + + // legacy variables + filterArea: new Float32Array(4), + filterClamp: new Float32Array(4), + }, true); + } + + /** + * Adds a new filter to the System. + * + * @param {PIXI.DisplayObject} target - The target of the filter to render. + * @param {PIXI.Filter[]} filters - The filters to apply. + */ + push(target, filters) + { + const renderer = this.renderer; + const filterStack = this.renderer.renderTexture.defaultFilterStack; + const state = this.statePool.pop() || new FilterState(); + + let resolution = filters[0].resolution; + let padding = filters[0].padding; + let autoFit = filters[0].autoFit; + let legacy = filters[0].legacy; + + for (let i = 1; i < filters.length; i++) + { + const filter = filters[i]; + + // lets use the lowest resolution.. + resolution = Math.min(resolution, filter.resolution); + // and the largest amount of padding! + padding = Math.max(padding, filter.padding); + // only auto fit if all filters are autofit + autoFit = autoFit || filter.autoFit; + + legacy = legacy || filter.legacy; + } + + filterStack.push(state); + + state.resolution = resolution; + + state.legacy = legacy; + + // round to whole number based on resolution + // TODO move that to the shader too? + state.sourceFrame = target.filterArea || target.getBounds(true); + + state.sourceFrame.pad(padding); + + if (autoFit) + { + state.sourceFrame.fit(this.renderer.renderTexture.destinationFrame); + } + + state.sourceFrame.round(resolution); + + state.renderTexture = this.getPotFilterTexture(state.sourceFrame.width, state.sourceFrame.height, resolution); + state.filters = filters; + + state.destinationFrame.width = state.renderTexture.width; + state.destinationFrame.height = state.renderTexture.height; + + state.renderTexture.filterFrame = state.sourceFrame; + renderer.renderTexture.bind(state.renderTexture, state.sourceFrame);// /, state.destinationFrame); + renderer.renderTexture.clear(); + } + + /** + * Pops off the filter and applies it. + * + */ + pop() + { + const renderer = this.renderer; + const filterStack = renderer.renderTexture.defaultFilterStack; + const state = filterStack.pop(); + const filters = state.filters; + + this.activeState = state; + + const globalUniforms = this.globalUniforms.uniforms; + + globalUniforms.sourceFrame = state.sourceFrame; + globalUniforms.destinationFrame = state.destinationFrame; + globalUniforms.resolution = state.resolution; + + // only update the rect if its legacy.. + if (state.legacy) + { + const filterArea = globalUniforms.filterArea; + const filterClamp = globalUniforms.filterClamp; + + filterArea[0] = state.destinationFrame.width; + filterArea[1] = state.destinationFrame.height; + filterArea[2] = state.sourceFrame.x; + filterArea[3] = state.sourceFrame.y; + + filterClamp[0] = 0.5 / state.resolution / state.destinationFrame.width; + filterClamp[1] = 0.5 / state.resolution / state.destinationFrame.height; + filterClamp[2] = (state.sourceFrame.width - 0.5) / state.resolution / state.destinationFrame.width; + filterClamp[3] = (state.sourceFrame.height - 0.5) / state.resolution / state.destinationFrame.height; + } + + this.globalUniforms.update(); + + const lastState = filterStack[filterStack.length - 1]; + + if (filters.length === 1) + { + filters[0].apply(this, state.renderTexture, lastState.renderTexture, false, state); + + this.returnFilterTexture(state.renderTexture); + } + else + { + let flip = state.renderTexture; + let flop = this.getPotFilterTexture( + flip.width, + flip.height, + state.resolution + ); + + flop.filterFrame = flip.filterFrame; + + let i = 0; + + for (i = 0; i < filters.length - 1; ++i) + { + filters[i].apply(this, flip, flop, true, state); + + const t = flip; + + flip = flop; + flop = t; + } + + filters[i].apply(this, flip, lastState.renderTexture, false, state); + + this.returnFilterTexture(flip); + this.returnFilterTexture(flop); + } + + this.statePool.push(state); + } + + /** + * Draws a filter. + * + * @param {PIXI.Filter} filter - The filter to draw. + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + * @param {boolean} clear - Should the output be cleared before rendering to it + */ + applyFilter(filter, input, output, clear) + { + const renderer = this.renderer; + + renderer.renderTexture.bind(output, output ? output.filterFrame : null); + + if (clear) + { + // gl.disable(gl.SCISSOR_TEST); + renderer.renderTexture.clear(); + // gl.enable(gl.SCISSOR_TEST); + } + + // set the uniforms.. + filter.uniforms.uSampler = input; + filter.uniforms.filterGlobals = this.globalUniforms; + + // TODO make it so that the order of this does not matter.. + // because it does at the moment cos of global uniforms. + // they need to get resynced + + renderer.state.setState(filter.state); + renderer.shader.bind(filter); + + if (filter.legacy) + { + this.quadUv.map(input._frame, input.filterFrame); + + renderer.geometry.bind(this.quadUv); + renderer.geometry.draw(DRAW_MODES.TRIANGLES); + } + else + { + renderer.geometry.bind(this.quad); + renderer.geometry.draw(DRAW_MODES.TRIANGLE_STRIP); + } + } + + /** + * Calculates the mapped matrix. + * + * TODO playing around here.. this is temporary - (will end up in the shader) + * this returns a matrix that will normalise map filter cords in the filter to screen space + * + * @param {PIXI.Matrix} outputMatrix - the matrix to output to. + * @return {PIXI.Matrix} The mapped matrix. + */ + calculateScreenSpaceMatrix(outputMatrix) + { + const currentState = this.activeState; + + return filterTransforms.calculateScreenSpaceMatrix( + outputMatrix, + currentState.sourceFrame, + currentState.destinationFrame + ); + } + + /** + * This will map the filter coord so that a texture can be used based on the transform of a sprite + * + * @param {PIXI.Matrix} outputMatrix - The matrix to output to. + * @param {PIXI.Sprite} sprite - The sprite to map to. + * @return {PIXI.Matrix} The mapped matrix. + */ + calculateSpriteMatrix(outputMatrix, sprite) + { + const currentState = this.activeState; + + return filterTransforms.calculateSpriteMatrix( + outputMatrix, + currentState.sourceFrame, + currentState.destinationFrame, + sprite + ); + } + + /** + * Destroys this Filter System. + * + * @param {boolean} [contextLost=false] context was lost, do not free shaders + * + */ + destroy(contextLost = false) + { + if (!contextLost) + { + this.emptyPool(); + } + else + { + this.texturePool = {}; + } + } + + /** + * Gets a Power-of-Two render texture. + * + * TODO move to a seperate class could be on renderer? + * also - could cause issue with multiple contexts? + * + * @private + * @param {WebGLRenderingContext} gl - The webgl rendering context + * @param {number} minWidth - The minimum width of the render target. + * @param {number} minHeight - The minimum height of the render target. + * @param {number} resolution - The resolution of the render target. + * @return {PIXI.RenderTarget} The new render target. + */ + getPotFilterTexture(minWidth, minHeight, resolution) + { + minWidth = bitTwiddle.nextPow2(minWidth); + minHeight = bitTwiddle.nextPow2(minHeight); + resolution = resolution || 1; + + const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + + if (!this.texturePool[key]) + { + this.texturePool[key] = []; + } + + let renderTexture = this.texturePool[key].pop(); + + if (!renderTexture) + { + // temporary bypass cache.. + // internally - this will cause a texture to be bound.. + renderTexture = RenderTexture.create({ + width: minWidth, + height: minHeight, + resolution, + }); + } + + return renderTexture; + } + + /** + * Gets extra render texture to use inside current filter + * + * @param {number} resolution resolution of the renderTexture + * @returns {PIXI.RenderTexture} + */ + getFilterTexture(resolution) + { + const rt = this.activeState.renderTexture; + + const filterTexture = this.getPotFilterTexture(rt.width, rt.height, resolution || rt.baseTexture.resolution); + + filterTexture.filterFrame = rt.filterFrame; + + return filterTexture; + } + + /** + * Frees a render texture back into the pool. + * + * @param {PIXI.RenderTarget} renderTexture - The renderTarget to free + */ + returnFilterTexture(renderTexture) + { + renderTexture.filterFrame = null; + + const base = renderTexture.baseTexture; + + const minWidth = base.width; + const minHeight = base.height; + + const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + + this.texturePool[key].push(renderTexture); + } + + /** + * Empties the texture pool. + * + */ + emptyPool() + { + for (const i in this.texturePool) + { + const textures = this.texturePool[i]; + + if (textures) + { + for (let j = 0; j < textures.length; j++) + { + textures[j].destroy(true); + } + } + } + + this.texturePool = {}; + } +} diff --git a/packages/core/src/filters/defaultFilter.frag b/packages/core/src/filters/defaultFilter.frag new file mode 100644 index 0000000..a14c1be --- /dev/null +++ b/packages/core/src/filters/defaultFilter.frag @@ -0,0 +1,22 @@ +varying vec2 vTextureCoord; +varying vec2 vFilterCoord; + +uniform sampler2D uSampler; +uniform sampler2D filterSampler; + +void main(void){ + vec4 masky = texture2D(filterSampler, vFilterCoord); + vec4 sample = texture2D(uSampler, vTextureCoord); + vec4 color; + if(mod(vFilterCoord.x, 1.0) > 0.5) + { + color = vec4(1.0, 0.0, 0.0, 1.0); + } + else + { + color = vec4(0.0, 1.0, 0.0, 1.0); + } + // gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0); + gl_FragColor = mix(sample, masky, 0.5); + gl_FragColor *= sample.a; +} diff --git a/packages/core/src/filters/defaultFilter.vert b/packages/core/src/filters/defaultFilter.vert new file mode 100644 index 0000000..e1febca --- /dev/null +++ b/packages/core/src/filters/defaultFilter.vert @@ -0,0 +1,14 @@ +attribute vec2 aVertexPosition; +attribute vec2 aTextureCoord; + +uniform mat3 projectionMatrix; +uniform mat3 filterMatrix; + +varying vec2 vTextureCoord; +varying vec2 vFilterCoord; + +void main(void){ + gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); + vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy; + vTextureCoord = aTextureCoord ; +} diff --git a/packages/core/src/filters/filterTransforms.js b/packages/core/src/filters/filterTransforms.js new file mode 100644 index 0000000..87a3cd6 --- /dev/null +++ b/packages/core/src/filters/filterTransforms.js @@ -0,0 +1,50 @@ +import { Matrix } from '@pixi/math'; + +/** + * Calculates the mapped matrix + * @param filterArea {Rectangle} The filter area + * @param sprite {Sprite} the target sprite + * @param outputMatrix {Matrix} @alvin + * @private + */ +// this returns a matrix that will normalise map filter cords in the filter to screen space +export function calculateScreenSpaceMatrix(outputMatrix, filterArea, textureSize) +{ + // TODO unwrap? + const mappedMatrix = outputMatrix.identity(); + + mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); + + mappedMatrix.scale(textureSize.width, textureSize.height); + + return mappedMatrix; +} + +export function calculateNormalizedScreenSpaceMatrix(outputMatrix, filterArea, textureSize) +{ + const mappedMatrix = outputMatrix.identity(); + + mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); + + const translateScaleX = (textureSize.width / filterArea.width); + const translateScaleY = (textureSize.height / filterArea.height); + + mappedMatrix.scale(translateScaleX, translateScaleY); + + return mappedMatrix; +} + +// this will map the filter coord so that a texture can be used based on the transform of a sprite +export function calculateSpriteMatrix(outputMatrix, filterArea, textureSize, sprite) +{ + const orig = sprite._texture.orig; + const mappedMatrix = outputMatrix.set(textureSize.width, 0, 0, textureSize.height, filterArea.x, filterArea.y); + const worldTransform = sprite.worldTransform.copyTo(Matrix.TEMP_MATRIX); + + worldTransform.invert(); + mappedMatrix.prepend(worldTransform); + mappedMatrix.scale(1.0 / orig.width, 1.0 / orig.height); + mappedMatrix.translate(sprite.anchor.x, sprite.anchor.y); + + return mappedMatrix; +} diff --git a/packages/core/src/filters/spriteMask/SpriteMaskFilter.js b/packages/core/src/filters/spriteMask/SpriteMaskFilter.js new file mode 100644 index 0000000..91697b7 --- /dev/null +++ b/packages/core/src/filters/spriteMask/SpriteMaskFilter.js @@ -0,0 +1,63 @@ +import Filter from '../Filter'; +import { Matrix } from '@pixi/math'; +import vertex from './spriteMaskFilter.vert'; +import fragment from './spriteMaskFilter.frag'; +import { default as TextureMatrix } from '../../textures/TextureMatrix'; + +/** + * The SpriteMaskFilter class + * + * @class + * @extends PIXI.Filter + * @memberof PIXI + */ +export default class SpriteMaskFilter extends Filter +{ + /** + * @param {PIXI.Sprite} sprite - the target sprite + */ + constructor(sprite) + { + const maskMatrix = new Matrix(); + + super(vertex, fragment); + + sprite.renderable = false; + + this.maskSprite = sprite; + this.maskMatrix = maskMatrix; + } + + /** + * Applies the filter + * + * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + */ + apply(filterManager, input, output) + { + const maskSprite = this.maskSprite; + const tex = this.maskSprite.texture; + + if (!tex.valid) + { + return; + } + if (!tex.transform) + { + // margin = 0.0, let it bleed a bit, shader code becomes easier + // assuming that atlas textures were made with 1-pixel padding + tex.transform = new TextureMatrix(tex, 0.0); + } + tex.transform.update(); + + this.uniforms.mask = tex; + this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite) + .prepend(tex.transform.mapCoord); + this.uniforms.alpha = maskSprite.worldAlpha; + this.uniforms.maskClamp = tex.transform.uClampFrame; + + filterManager.applyFilter(this, input, output); + } +} diff --git a/packages/core/src/filters/spriteMask/spriteMaskFilter.frag b/packages/core/src/filters/spriteMask/spriteMaskFilter.frag new file mode 100644 index 0000000..0e4aef8 --- /dev/null +++ b/packages/core/src/filters/spriteMask/spriteMaskFilter.frag @@ -0,0 +1,23 @@ +varying vec2 vMaskCoord; +varying vec2 vTextureCoord; + +uniform sampler2D uSampler; +uniform sampler2D mask; +uniform float alpha; +uniform vec4 maskClamp; + +void main(void) +{ + float clip = step(3.5, + step(maskClamp.x, vMaskCoord.x) + + step(maskClamp.y, vMaskCoord.y) + + step(vMaskCoord.x, maskClamp.z) + + step(vMaskCoord.y, maskClamp.w)); + + vec4 original = texture2D(uSampler, vTextureCoord); + vec4 masky = texture2D(mask, vMaskCoord); + + original *= (masky.r * masky.a * alpha * clip); + + gl_FragColor = original; +} diff --git a/packages/core/src/filters/spriteMask/spriteMaskFilter.vert b/packages/core/src/filters/spriteMask/spriteMaskFilter.vert new file mode 100644 index 0000000..d23a30c --- /dev/null +++ b/packages/core/src/filters/spriteMask/spriteMaskFilter.vert @@ -0,0 +1,16 @@ +attribute vec2 aVertexPosition; +attribute vec2 aTextureCoord; + +uniform mat3 projectionMatrix; +uniform mat3 otherMatrix; + +varying vec2 vMaskCoord; +varying vec2 vTextureCoord; + +void main(void) +{ + gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); + + vTextureCoord = aTextureCoord; + vMaskCoord = ( otherMatrix * vec3( aTextureCoord, 1.0) ).xy; +} diff --git a/packages/core/src/framebuffer/FrameBuffer.js b/packages/core/src/framebuffer/FrameBuffer.js new file mode 100644 index 0000000..84e9fcc --- /dev/null +++ b/packages/core/src/framebuffer/FrameBuffer.js @@ -0,0 +1,106 @@ +import Texture from '../textures/BaseTexture'; +import { FORMATS, TYPES } from '@pixi/constants'; + +/** + * Frame buffer + * @class + * @memberof PIXI + */ +export default class FrameBuffer +{ + constructor(width, height) + { + this.width = width || 100; + this.height = height || 100; + + this.stencil = false; + this.depth = false; + + this.dirtyId = 0; + this.dirtyFormat = 0; + this.dirtySize = 0; + + this.depthTexture = null; + this.colorTextures = []; + + this.glFrameBuffers = {}; + } + + get colorTexture() + { + return this.colorTextures[0]; + } + + addColorTexture(index, texture) + { + // TODO add some validation to the texture - same width / height etc? + this.colorTextures[index || 0] = texture || new Texture(null, { scaleMode: 0, + resolution: 1, + mipmap: false, + width: this.width, + height: this.height });// || new Texture(); + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + addDepthTexture(texture) + { + /* eslint-disable max-len */ + this.depthTexture = texture || new Texture(null, { scaleMode: 0, + resolution: 1, + width: this.width, + height: this.height, + mipmap: false, + format: FORMATS.DEPTH_COMPONENT, + type: TYPES.UNSIGNED_SHORT });// UNSIGNED_SHORT; + /* eslint-disable max-len */ + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + enableDepth() + { + this.depth = true; + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + enableStencil() + { + this.stencil = true; + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + resize(width, height) + { + if (width === this.width && height === this.height) return; + + this.width = width; + this.height = height; + + this.dirtyId++; + this.dirtySize++; + + for (let i = 0; i < this.colorTextures.length; i++) + { + this.colorTextures[i].setSize(width, height); + } + + if (this.depthTexture) + { + this.depthTexture.setSize(width, height); + } + } +} diff --git a/packages/core/src/framebuffer/FramebufferSystem.js b/packages/core/src/framebuffer/FramebufferSystem.js new file mode 100644 index 0000000..2cc2437 --- /dev/null +++ b/packages/core/src/framebuffer/FramebufferSystem.js @@ -0,0 +1,246 @@ +import System from '../System'; +import { Rectangle } from '@pixi/math'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class FramebufferSystem extends System +{ + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + contextChange() + { + this.gl = this.renderer.gl; + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + this.current = null; + this.viewport = new Rectangle(); + + this.drawBufferExtension = this.renderer.context.extensions.drawBuffers; + } + + bind(framebuffer, frame) + { + const gl = this.gl; + + this.current = framebuffer; + + if (framebuffer) + { + // TODO cacheing layer! + + const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID] || this.initFramebuffer(framebuffer); + + gl.bindFramebuffer(gl.FRAMEBUFFER, fbo.framebuffer); + // makesure all textures are unbound.. + + // now check for updates... + if (fbo.dirtyId !== framebuffer.dirtyId) + { + fbo.dirtyId = framebuffer.dirtyId; + + if (fbo.dirtyFormat !== framebuffer.dirtyFormat) + { + fbo.dirtyFormat = framebuffer.dirtyFormat; + this.updateFramebuffer(framebuffer); + } + else if (fbo.dirtySize !== framebuffer.dirtySize) + { + fbo.dirtySize = framebuffer.dirtySize; + this.resizeFramebuffer(framebuffer); + } + } + + for (let i = 0; i < framebuffer.colorTextures.length; i++) + { + if (framebuffer.colorTextures[i].texturePart) + { + this.renderer.texture.unbind(framebuffer.colorTextures[i].texture); + } + else + { + this.renderer.texture.unbind(framebuffer.colorTextures[i]); + } + } + + if (framebuffer.depthTexture) + { + this.renderer.texture.unbind(framebuffer.depthTexture); + } + + if (frame) + { + this.setViewport(frame.x, frame.y, frame.width, frame.height); + } + else + { + this.setViewport(0, 0, framebuffer.width, framebuffer.height); + } + } + else + { + gl.bindFramebuffer(gl.FRAMEBUFFER, null); + + if (frame) + { + this.setViewport(frame.x, frame.y, frame.width, frame.height); + } + else + { + this.setViewport(0, 0, this.renderer.width, this.renderer.height); + } + } + } + + setViewport(x, y, width, height) + { + const v = this.viewport; + + if (v.width !== width || v.height !== height || v.x !== x || v.y !== y) + { + v.x = x; + v.y = y; + v.width = width; + v.height = height; + + this.gl.viewport(x, y, width, height); + } + } + + get size() + { + if (this.current) + { + // TODO store temp + return { x: 0, y: 0, width: this.current.width, height: this.current.height }; + } + + return { x: 0, y: 0, width: this.renderer.width, height: this.renderer.height }; + } + + clear(r, g, b, a) + { + const gl = this.gl; + + // TODO clear color can be set only one right? + gl.clearColor(r, g, b, a); + gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); + } + + // private functions... + + initFramebuffer(framebuffer) + { + const gl = this.gl; + + // TODO - make this a class? + const fbo = { + framebuffer: gl.createFramebuffer(), + stencil: null, + dirtyId: 0, + dirtyFormat: 0, + dirtySize: 0, + }; + + framebuffer.glFrameBuffers[this.CONTEXT_UID] = fbo; + + return fbo; + } + + resizeFramebuffer(framebuffer) + { + const gl = this.gl; + + if (framebuffer.stencil || framebuffer.depth) + { + gl.bindRenderbuffer(gl.RENDERBUFFER, this.stencil); + gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); + } + } + + updateFramebuffer(framebuffer) + { + const gl = this.gl; + + const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID]; + + // bind the color texture + const colorTextures = framebuffer.colorTextures; + + let count = colorTextures.length; + + if (!this.drawBufferExtension) + { + count = Math.min(count, 1); + } + + const activeTextures = []; + + for (let i = 0; i < count; i++) + { + const texture = framebuffer.colorTextures[i]; + + if (texture.texturePart) + { + this.renderer.texture.bind(texture.texture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.COLOR_ATTACHMENT0 + i, + gl.TEXTURE_CUBE_MAP_NEGATIVE_X + texture.side, + texture.texture._glTextures[this.CONTEXT_UID].texture, + 0); + } + else + { + this.renderer.texture.bind(texture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.COLOR_ATTACHMENT0 + i, + gl.TEXTURE_2D, + texture._glTextures[this.CONTEXT_UID].texture, + 0); + } + + activeTextures.push(gl.COLOR_ATTACHMENT0 + i); + } + + if (this.drawBufferExtension && activeTextures.length > 1) + { + this.drawBufferExtension.drawBuffersWEBGL(activeTextures); + } + + if (framebuffer.depthTexture) + { + const depthTextureExt = this.renderer.context.extensions.depthTexture; + + if (depthTextureExt) + { + const depthTexture = framebuffer.depthTexture; + + this.renderer.texture.bind(depthTexture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.DEPTH_ATTACHMENT, + gl.TEXTURE_2D, + depthTexture._glTextures[this.CONTEXT_UID].texture, + 0); + } + } + + if (framebuffer.stencil || framebuffer.depth) + { + fbo.stencil = gl.createRenderbuffer(); + + gl.bindRenderbuffer(gl.RENDERBUFFER, fbo.stencil); + + // TODO.. this is depth AND stencil? + gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, fbo.stencil); + gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); + // fbo.enableStencil(); + } + } +} diff --git a/packages/core/src/geometry/GLBuffer.js b/packages/core/src/geometry/GLBuffer.js new file mode 100644 index 0000000..0477e8a --- /dev/null +++ b/packages/core/src/geometry/GLBuffer.js @@ -0,0 +1,9 @@ +export default class GLBuffer +{ + constructor(buffer) + { + this.buffer = buffer; + this.updateID = -1; + this.byteLength = -1; + } +} diff --git a/packages/core/src/geometry/GeometrySystem.js b/packages/core/src/geometry/GeometrySystem.js new file mode 100644 index 0000000..959f827 --- /dev/null +++ b/packages/core/src/geometry/GeometrySystem.js @@ -0,0 +1,388 @@ +import System from '../System'; +import GLBuffer from './GLBuffer'; + +const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class GeometrySystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this._activeGeometry = null; + this._activeVao = null; + + this.hasVao = true; + this.hasInstance = true; + } + + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + contextChange() + { + const gl = this.gl = this.renderer.gl; + + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + + // webgl2 + if (!gl.createVertexArray) + { + // webgl 1! + let nativeVaoExtension = this.renderer.context.extensions.vertexArrayObject; + + if (this.renderer.legacy) + { + nativeVaoExtension = null; + } + + if (nativeVaoExtension) + { + gl.createVertexArray = () => + nativeVaoExtension.createVertexArrayOES(); + + gl.bindVertexArray = (vao) => + nativeVaoExtension.bindVertexArrayOES(vao); + + gl.deleteVertexArray = (vao) => + nativeVaoExtension.deleteVertexArrayOES(vao); + } + else + { + this.hasVao = false; + gl.createVertexArray = () => + { + // empty + }; + + gl.bindVertexArray = () => + { + // empty + }; + + gl.deleteVertexArray = () => + { + // empty + }; + } + } + + if (!gl.vertexAttribDivisor) + { + const instanceExt = gl.getExtension('ANGLE_instanced_arrays'); + + if (instanceExt) + { + gl.vertexAttribDivisor = (a, b) => + instanceExt.vertexAttribDivisorANGLE(a, b); + + gl.drawElementsInstanced = (a, b, c, d, e) => + instanceExt.drawElementsInstancedANGLE(a, b, c, d, e); + + gl.drawArraysInstanced = (a, b, c, d) => + instanceExt.drawArraysInstancedANGLE(a, b, c, d); + } + else + { + this.hasInstance = false; + } + } + } + + /** + * Binds geometry so that is can be drawn. Creating a Vao if required + * @private + * @param {PIXI.Geometry} geometry instance of geometry to bind + */ + bind(geometry, shader) + { + shader = shader || this.renderer.shader.shader; + + const gl = this.gl; + + // not sure the best way to address this.. + // currently different shaders require different VAOs for the same geometry + // Still mulling over the best way to solve this one.. + // will likely need to modify the shader attribute locations at run time! + let vaos = geometry.glVertexArrayObjects[this.CONTEXT_UID]; + + if (!vaos) + { + geometry.glVertexArrayObjects[this.CONTEXT_UID] = vaos = {}; + } + + const vao = vaos[shader.program.id] || this.initGeometryVao(geometry, shader.program); + + this._activeGeometry = geometry; + + if (this._activeVao !== vao) + { + this._activeVao = vao; + + if (this.hasVao) + { + gl.bindVertexArray(vao); + } + else + { + this.activateVao(geometry, shader.program); + } + } + + // TODO - optimise later! + // don't need to loop through if nothing changed! + // maybe look to add an 'autoupdate' to geometry? + this.updateBuffers(); + } + + reset() + { + this.unbind(); + } + + updateBuffers() + { + const geometry = this._activeGeometry; + const gl = this.gl; + + for (let i = 0; i < geometry.buffers.length; i++) + { + const buffer = geometry.buffers[i]; + + const glBuffer = buffer._glBuffers[this.CONTEXT_UID]; + + if (buffer._updateID !== glBuffer.updateID) + { + glBuffer.updateID = buffer._updateID; + + // TODO can cache this on buffer! maybe added a getter / setter? + const type = buffer.index ? gl.ELEMENT_ARRAY_BUFFER : gl.ARRAY_BUFFER; + const drawType = buffer.static ? gl.STATIC_DRAW : gl.DYNAMIC_DRAW; + + gl.bindBuffer(type, glBuffer.buffer); + + if (glBuffer.byteLength >= buffer.data.byteLength) + { + // offset is always zero for now! + gl.bufferSubData(type, 0, buffer.data); + } + else + { + gl.bufferData(type, buffer.data, drawType); + } + } + } + } + + checkCompatability(geometry, program) + { + // geometry must have at least all the attributes that the shader requires. + const geometryAttributes = geometry.attributes; + const shaderAttributes = program.attributeData; + + for (const j in shaderAttributes) + { + if (!geometryAttributes[j]) + { + throw new Error(`shader and geometry incompatible, geometry missing the "${j}" attribute`); + } + } + } + + /** + * Creates a Vao with the same structure as the geometry and stores it on the geometry. + * @private + * @param {PIXI.Geometry} geometry instance of geometry to to generate Vao for + */ + initGeometryVao(geometry, program) + { + this.checkCompatability(geometry, program); + + const gl = this.gl; + const CONTEXT_UID = this.CONTEXT_UID; + const buffers = geometry.buffers; + const attributes = geometry.attributes; + + const tempStride = {}; + const tempStart = {}; + + for (const j in buffers) + { + tempStride[j] = 0; + tempStart[j] = 0; + } + + for (const j in attributes) + { + if (!attributes[j].size && program.attributeData[j]) + { + attributes[j].size = program.attributeData[j].size; + } + + tempStride[attributes[j].buffer] += attributes[j].size * byteSizeMap[attributes[j].type]; + } + + for (const j in attributes) + { + const attribute = attributes[j]; + const attribSize = attribute.size; + + if (attribute.stride === undefined) + { + if (tempStride[attribute.buffer] === attribSize * byteSizeMap[attribute.type]) + { + attribute.stride = 0; + } + else + { + attribute.stride = tempStride[attribute.buffer]; + } + } + + if (attribute.start === undefined) + { + attribute.start = tempStart[attribute.buffer]; + + tempStart[attribute.buffer] += attribSize * byteSizeMap[attribute.type]; + } + } + + // first update - and create the buffers! + // only create a gl buffer if it actually gets + for (let i = 0; i < buffers.length; i++) + { + const buffer = buffers[i]; + + if (!buffer._glBuffers[CONTEXT_UID]) + { + buffer._glBuffers[CONTEXT_UID] = new GLBuffer(gl.createBuffer()); + } + } + + // TODO - maybe make this a data object? + // lets wait to see if we need to first! + const vao = gl.createVertexArray(); + + gl.bindVertexArray(vao); + + this.activateVao(geometry, program); + + gl.bindVertexArray(null); + + // add it to the cache! + geometry.glVertexArrayObjects[this.CONTEXT_UID][program.id] = vao; + + return vao; + } + + activateVao(geometry, program) + { + const gl = this.gl; + const CONTEXT_UID = this.CONTEXT_UID; + const buffers = geometry.buffers; + const attributes = geometry.attributes; + + if (geometry.indexBuffer) + { + // first update the index buffer if we have one.. + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, geometry.indexBuffer._glBuffers[CONTEXT_UID].buffer); + } + + let lastBuffer = null; + + // add a new one! + for (const j in attributes) + { + const attribute = attributes[j]; + const buffer = buffers[attribute.buffer]; + const glBuffer = buffer._glBuffers[CONTEXT_UID]; + + if (program.attributeData[j]) + { + if (lastBuffer !== glBuffer) + { + gl.bindBuffer(gl.ARRAY_BUFFER, glBuffer.buffer); + + lastBuffer = glBuffer; + } + + const location = program.attributeData[j].location; + + // TODO introduce state again + // we can optimise this for older devices that have no VAOs + gl.enableVertexAttribArray(location); + + gl.vertexAttribPointer(location, + attribute.size, + attribute.type || gl.FLOAT, + attribute.normalized, + attribute.stride, + attribute.start); + + if (attribute.instance) + { + // TODO calculate instance count based of this... + if (this.hasInstance) + { + gl.vertexAttribDivisor(location, 1); + } + else + { + throw new Error('geometry error, GPU Instancing is not supported on this device'); + } + } + } + } + } + + draw(type, size, start, instanceCount) + { + const gl = this.gl; + const geometry = this._activeGeometry; + + // TODO.. this should not change so maybe cache the function? + + if (geometry.indexBuffer) + { + if (geometry.instanced) + { + /* eslint-disable max-len */ + gl.drawElementsInstanced(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2, instanceCount || 1); + /* eslint-enable max-len */ + } + else + { + gl.drawElements(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2); + } + } + else + if (geometry.instanced) + { + // TODO need a better way to calculate size.. + gl.drawArraysInstanced(type, start, size || geometry.getSize(), instanceCount || 1); + } + else + { + gl.drawArrays(type, start, size || geometry.getSize()); + } + + return this; + } + + unbind() + { + this.gl.bindVertexArray(null); + this._activeVao = null; + this._activeGeometry = null; + } +} diff --git a/packages/core/src/geometry/utils/setVertexAttribArrays.js b/packages/core/src/geometry/utils/setVertexAttribArrays.js new file mode 100644 index 0000000..23b6959 --- /dev/null +++ b/packages/core/src/geometry/utils/setVertexAttribArrays.js @@ -0,0 +1,54 @@ +// var GL_MAP = {}; + +/** + * @param gl {WebGLRenderingContext} The current WebGL context + * @param attribs {*} + * @param state {*} + */ +export default function setVertexAttribArrays(gl, attribs, state) +{ + let i; + + if (state) + { + const tempAttribState = state.tempAttribState; + const attribState = state.attribState; + + for (i = 0; i < tempAttribState.length; i++) + { + tempAttribState[i] = false; + } + + // set the new attribs + for (i = 0; i < attribs.length; i++) + { + tempAttribState[attribs[i].attribute.location] = true; + } + + for (i = 0; i < attribState.length; i++) + { + if (attribState[i] !== tempAttribState[i]) + { + attribState[i] = tempAttribState[i]; + + if (state.attribState[i]) + { + gl.enableVertexAttribArray(i); + } + else + { + gl.disableVertexAttribArray(i); + } + } + } + } + else + { + for (i = 0; i < attribs.length; i++) + { + const attrib = attribs[i]; + + gl.enableVertexAttribArray(attrib.attribute.location); + } + } +} diff --git a/packages/core/src/index.js b/packages/core/src/index.js index 58c456a..fd1f547 100644 --- a/packages/core/src/index.js +++ b/packages/core/src/index.js @@ -1,30 +1,30 @@ import * as resources from './textures/resources'; -import * as systems from './renderers/systems'; +import * as systems from './systems'; -export { resources }; export { systems }; +export { resources }; -export { default as Renderer } from './renderers/Renderer'; -export { default as AbstractRenderer } from './renderers/AbstractRenderer'; -export { default as FrameBuffer } from './textures/FrameBuffer'; +export { default as System } from './System'; +export { default as Renderer } from './Renderer'; +export { default as AbstractRenderer } from './AbstractRenderer'; +export { default as FrameBuffer } from './framebuffer/FrameBuffer'; export { default as CubeTexture } from './textures/CubeTexture'; export { default as BaseTexture } from './textures/BaseTexture'; export { default as Texture } from './textures/Texture'; export { default as TextureMatrix } from './textures/TextureMatrix'; -export { default as RenderTexture } from './textures/RenderTexture'; -export { default as BaseRenderTexture } from './textures/BaseRenderTexture'; +export { default as RenderTexture } from './renderTexture/RenderTexture'; +export { default as BaseRenderTexture } from './renderTexture/BaseRenderTexture'; export { default as TextureUvs } from './textures/TextureUvs'; -export { default as State } from './renderers/State'; -export { default as ObjectRenderer } from './renderers/utils/ObjectRenderer'; -export { default as Quad } from './renderers/utils/Quad'; -export { default as QuadUv } from './renderers/utils/QuadUv'; -export { default as checkMaxIfStatmentsInShader } from './renderers/utils/checkMaxIfStatmentsInShader'; +export { default as State } from './state/State'; +export { default as ObjectRenderer } from './batch/ObjectRenderer'; +export { default as Quad } from './utils/Quad'; +export { default as QuadUv } from './utils/QuadUv'; +export { default as checkMaxIfStatmentsInShader } from './shader/utils/checkMaxIfStatmentsInShader'; export { default as Shader } from './shader/Shader'; export { default as Program } from './shader/Program'; export { default as UniformGroup } from './shader/UniformGroup'; -export { default as SpriteMaskFilter } from './renderers/filters/spriteMask/SpriteMaskFilter'; -export { default as Filter } from './renderers/filters/Filter'; +export { default as SpriteMaskFilter } from './filters/spriteMask/SpriteMaskFilter'; +export { default as Filter } from './filters/Filter'; export { default as Attribute } from './geometry/Attribute'; export { default as Buffer } from './geometry/Buffer'; export { default as Geometry } from './geometry/Geometry'; - diff --git a/packages/core/src/mask/MaskSystem.js b/packages/core/src/mask/MaskSystem.js new file mode 100644 index 0000000..99174c2 --- /dev/null +++ b/packages/core/src/mask/MaskSystem.js @@ -0,0 +1,199 @@ +import System from '../System'; +import AlphaMaskFilter from '../filters/spriteMask/SpriteMaskFilter'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class MaskSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + // TODO - we don't need both! + this.scissor = false; + this.scissorData = null; + this.scissorRenderTarget = null; + + this.enableScissor = false; + + this.alphaMaskPool = []; + this.alphaMaskIndex = 0; + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.DisplayObject} target - Display Object to push the mask to + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + push(target, maskData) + { + // TODO the root check means scissor rect will not + // be used on render textures more info here: + // https://github.com/pixijs/pixi.js/pull/3545 + + if (maskData.texture) + { + this.pushSpriteMask(target, maskData); + } + else if (this.enableScissor + && !this.scissor + && this.renderer._activeRenderTarget.root + && !this.renderer.stencil.stencilMaskStack.length + && maskData.isFastRect()) + { + const matrix = maskData.worldTransform; + + let rot = Math.atan2(matrix.b, matrix.a); + + // use the nearest degree! + rot = Math.round(rot * (180 / Math.PI)); + + if (rot % 90) + { + this.pushStencilMask(maskData); + } + else + { + this.pushScissorMask(target, maskData); + } + } + else + { + this.pushStencilMask(maskData); + } + } + + /** + * Removes the last mask from the mask stack and doesn't return it. + * + * @param {PIXI.DisplayObject} target - Display Object to pop the mask from + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + pop(target, maskData) + { + if (maskData.texture) + { + this.popSpriteMask(target, maskData); + } + else if (this.enableScissor && !this.renderer.stencil.stencilMaskStack.length) + { + this.popScissorMask(target, maskData); + } + else + { + this.popStencilMask(target, maskData); + } + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.RenderTarget} target - Display Object to push the sprite mask to + * @param {PIXI.Sprite} maskData - Sprite to be used as the mask + */ + pushSpriteMask(target, maskData) + { + let alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; + + if (!alphaMaskFilter) + { + alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; + } + + alphaMaskFilter[0].resolution = this.renderer.resolution; + alphaMaskFilter[0].maskSprite = maskData; + + // TODO - may cause issues! + target.filterArea = maskData.getBounds(true); + + this.renderer.filterSystem.pushFilter(target, alphaMaskFilter); + + this.alphaMaskIndex++; + } + + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popSpriteMask() + { + this.renderer.filterSystem.popFilter(); + this.alphaMaskIndex--; + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + pushStencilMask(maskData) + { + this.renderer.batch.flush(); + this.renderer.stencil.pushStencil(maskData); + } + + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popStencilMask() + { + // this.renderer.currentRenderer.stop(); + this.renderer.stencil.popStencil(); + } + + /** + * + * @param {PIXI.DisplayObject} target - Display Object to push the mask to + * @param {PIXI.Graphics} maskData - The masking data. + */ + pushScissorMask(target, maskData) + { + maskData.renderable = true; + + const renderTarget = this.renderer._activeRenderTarget; + + const bounds = maskData.getBounds(); + + bounds.fit(renderTarget.size); + maskData.renderable = false; + + this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); + + const resolution = this.renderer.resolution; + + this.renderer.gl.scissor( + bounds.x * resolution, + (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, + bounds.width * resolution, + bounds.height * resolution + ); + + this.scissorRenderTarget = renderTarget; + this.scissorData = maskData; + this.scissor = true; + } + + /** + * + * + */ + popScissorMask() + { + this.scissorRenderTarget = null; + this.scissorData = null; + this.scissor = false; + + // must be scissor! + const gl = this.renderer.gl; + + gl.disable(gl.SCISSOR_TEST); + } +} diff --git a/packages/core/src/mask/StencilSystem.js b/packages/core/src/mask/StencilSystem.js new file mode 100644 index 0000000..62796a5 --- /dev/null +++ b/packages/core/src/mask/StencilSystem.js @@ -0,0 +1,133 @@ +import System from '../System'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class StencilSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + this.stencilMaskStack = []; + } + + /** + * Changes the mask stack that is used by this System. + * + * @param {PIXI.Graphics[]} stencilMaskStack - The mask stack + */ + setMaskStack(stencilMaskStack) + { + const gl = this.renderer.gl; + + if (stencilMaskStack.length !== this.stencilMaskStack.length) + { + if (stencilMaskStack.length === 0) + { + gl.disable(gl.STENCIL_TEST); + } + else + { + gl.enable(gl.STENCIL_TEST); + } + } + + this.stencilMaskStack = stencilMaskStack; + } + + /** + * Applies the Mask and adds it to the current stencil stack. @alvin + * + * @param {PIXI.Graphics} graphics - The mask + */ + pushStencil(graphics) + { + this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); + + // this.renderer._activeRenderTarget.attachStencilBuffer(); + + const gl = this.renderer.gl; + const prevMaskCount = this.stencilMaskStack.length; + + if (prevMaskCount === 0) + { + gl.enable(gl.STENCIL_TEST); + } + + this.stencilMaskStack.push(graphics); + + // Increment the refference stencil value where the new mask overlaps with the old ones. + gl.colorMask(false, false, false, false); + gl.stencilFunc(gl.EQUAL, prevMaskCount, this._getBitwiseMask()); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.INCR); + this.renderer.plugins.graphics.render(graphics); + + this._useCurrent(); + } + + /** + * Removes the last mask from the stencil stack. @alvin + */ + popStencil() + { + this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); + + const gl = this.renderer.gl; + const graphics = this.stencilMaskStack.pop(); + + if (this.stencilMaskStack.length === 0) + { + // the stack is empty! + gl.disable(gl.STENCIL_TEST); + gl.clear(gl.STENCIL_BUFFER_BIT); + gl.clearStencil(0); + } + else + { + // Decrement the refference stencil value where the popped mask overlaps with the other ones + gl.colorMask(false, false, false, false); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.DECR); + this.renderer.plugins.graphics.render(graphics); + + this._useCurrent(); + } + } + + /** + * Setup renderer to use the current stencil data. + */ + _useCurrent() + { + const gl = this.renderer.gl; + + gl.colorMask(true, true, true, true); + gl.stencilFunc(gl.EQUAL, this.stencilMaskStack.length, this._getBitwiseMask()); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP); + } + + /** + * Fill 1s equal to the number of acitve stencil masks. + * + * @return {number} The bitwise mask. + */ + _getBitwiseMask() + { + return (1 << this.stencilMaskStack.length) - 1; + } + + /** + * Destroys the mask stack. + * + */ + destroy() + { + super.destroy(this); + + this.stencilMaskStack = null; + } +} diff --git a/packages/core/src/projection/ProjectionSystem.js b/packages/core/src/projection/ProjectionSystem.js new file mode 100644 index 0000000..6e63b73 --- /dev/null +++ b/packages/core/src/projection/ProjectionSystem.js @@ -0,0 +1,73 @@ +import System from '../System'; +import { Matrix } from '@pixi/math'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class ProjectionSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.projectionMatrix = new Matrix(); + } + + update(destinationFrame, sourceFrame, resolution, root) + { + this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; + this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; + + this.calculateProjection(this.destinationFrame, this.sourceFrame, resolution, root); + + this.renderer.globalUniforms.uniforms.projectionMatrix = this.projectionMatrix; + this.renderer.globalUniforms.update(); + } + + /** + * Updates the projection matrix based on a projection frame (which is a rectangle) + * + * @param {Rectangle} destinationFrame - The destination frame. + * @param {Rectangle} sourceFrame - The source frame. + */ + calculateProjection(destinationFrame, sourceFrame, resolution, root) + { + const pm = this.projectionMatrix; + + // I don't think we will need this line.. + // pm.identity(); + + if (!root) + { + pm.a = (1 / destinationFrame.width * 2) * resolution; + pm.d = (1 / destinationFrame.height * 2) * resolution; + + pm.tx = -1 - (sourceFrame.x * pm.a); + pm.ty = -1 - (sourceFrame.y * pm.d); + } + else + { + pm.a = (1 / destinationFrame.width * 2) * resolution; + pm.d = (-1 / destinationFrame.height * 2) * resolution; + + pm.tx = -1 - (sourceFrame.x * pm.a); + pm.ty = 1 - (sourceFrame.y * pm.d); + } + } + + /** + * Sets the transform of the active render target to the given matrix + * + * @param {PIXI.Matrix} matrix - The transformation matrix + */ + setTransform()// matrix) + { + // this._activeRenderTarget.transform = matrix; + } +} diff --git a/packages/core/src/renderTexture/BaseRenderTexture.js b/packages/core/src/renderTexture/BaseRenderTexture.js new file mode 100644 index 0000000..88eb83b --- /dev/null +++ b/packages/core/src/renderTexture/BaseRenderTexture.js @@ -0,0 +1,137 @@ +import BaseTexture from '../textures/BaseTexture'; +import FrameBuffer from '../framebuffer/FrameBuffer'; + +/** + * A BaseRenderTexture is a special texture that allows any PixiJS display object to be rendered to it. + * + * __Hint__: All DisplayObjects (i.e. Sprites) that render to a BaseRenderTexture should be preloaded + * otherwise black rectangles will be drawn instead. + * + * A BaseRenderTexture takes a snapshot of any Display Object given to its render method. The position + * and rotation of the given Display Objects is ignored. For example: + * + * ```js + * let renderer = PIXI.autoDetectRenderer(1024, 1024, { view: canvas, ratio: 1 }); + * let baseRenderTexture = new PIXI.BaseRenderTexture(renderer, 800, 600); + * let sprite = PIXI.Sprite.fromImage("spinObj_01.png"); + * + * sprite.position.x = 800/2; + * sprite.position.y = 600/2; + * sprite.anchor.x = 0.5; + * sprite.anchor.y = 0.5; + * + * baseRenderTexture.render(sprite); + * ``` + * + * The Sprite in this case will be rendered using its local transform. To render this sprite at 0,0 + * you can clear the transform + * + * ```js + * + * sprite.setTransform() + * + * let baseRenderTexture = new PIXI.BaseRenderTexture(100, 100); + * let renderTexture = new PIXI.RenderTexture(baseRenderTexture); + * + * renderer.render(sprite, renderTexture); // Renders to center of RenderTexture + * ``` + * + * @class + * @extends PIXI.BaseTexture + * @memberof PIXI + */ +export default class BaseRenderTexture extends BaseTexture +{ + /** + * @param {object} [options] + * @param {number} [options.width=100] - The width of the base render texture + * @param {number} [options.height=100] - The height of the base render texture + * @param {PIXI.SCALE_MODES} [options.scaleMode] - See {@link PIXI.SCALE_MODES} for possible values + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the texture being generated + */ + constructor(options) + { + if (typeof options === 'number') + { + /* eslint-disable prefer-rest-params */ + // Backward compatibility of signature + const width = arguments[0]; + const height = arguments[1]; + const scaleMode = arguments[2]; + const resolution = arguments[3]; + + options = { width, height, scaleMode, resolution }; + /* eslint-enable prefer-rest-params */ + } + + super(null, options); + + const { width, height } = options || {}; + + // Set defaults + this.mipmap = false; + this.width = Math.ceil(width) || 100; + this.height = Math.ceil(height) || 100; + this.valid = true; + + /** + * A map of renderer IDs to webgl renderTargets + * + * @private + * @member {object} + */ + // this._glRenderTargets = {}; + + /** + * A reference to the canvas render target (we only need one as this can be shared across renderers) + * + * @private + * @member {object} + */ + this._canvasRenderTarget = null; + + this.clearColor = [0, 0, 0, 0]; + + this.frameBuffer = new FrameBuffer(width * this.resolution, height * this.resolution) + .addColorTexture(0, this); + + // TODO - could this be added the systems? + + /** + * The data structure for the stencil masks + * + * @member {PIXI.Graphics[]} + */ + this.stencilMaskStack = []; + + /** + * The data structure for the filters + * + * @member {PIXI.Graphics[]} + */ + this.filterStack = [{}]; + } + + /** + * Resizes the BaseRenderTexture. + * + * @param {number} width - The width to resize to. + * @param {number} height - The height to resize to. + */ + resize(width, height) + { + width = Math.ceil(width); + height = Math.ceil(height); + this.frameBuffer.resize(width * this.resolution, height * this.resolution); + } + + /** + * Destroys this texture + * + */ + destroy() + { + super.destroy(true); + this.renderer = null; + } +} diff --git a/packages/core/src/renderTexture/RenderTexture.js b/packages/core/src/renderTexture/RenderTexture.js new file mode 100644 index 0000000..f1ecf1f --- /dev/null +++ b/packages/core/src/renderTexture/RenderTexture.js @@ -0,0 +1,153 @@ +import BaseRenderTexture from './BaseRenderTexture'; +import Texture from '../textures/Texture'; + +/** + * A RenderTexture is a special texture that allows any PixiJS display object to be rendered to it. + * + * __Hint__: All DisplayObjects (i.e. Sprites) that render to a RenderTexture should be preloaded + * otherwise black rectangles will be drawn instead. + * + * A RenderTexture takes a snapshot of any Display Object given to its render method. For example: + * + * ```js + * let renderer = PIXI.autoDetectRenderer(1024, 1024, { view: canvas, ratio: 1 }); + * let renderTexture = PIXI.RenderTexture.create(800, 600); + * let sprite = PIXI.Sprite.fromImage("spinObj_01.png"); + * + * sprite.position.x = 800/2; + * sprite.position.y = 600/2; + * sprite.anchor.x = 0.5; + * sprite.anchor.y = 0.5; + * + * renderer.render(sprite, renderTexture); + * ``` + * + * The Sprite in this case will be rendered using its local transform. To render this sprite at 0,0 + * you can clear the transform + * + * ```js + * + * sprite.setTransform() + * + * let renderTexture = new PIXI.RenderTexture.create(100, 100); + * + * renderer.render(sprite, renderTexture); // Renders to center of RenderTexture + * ``` + * + * @class + * @extends PIXI.Texture + * @memberof PIXI + */ +export default class RenderTexture extends Texture +{ + /** + * @param {PIXI.BaseRenderTexture} baseRenderTexture - The renderer used for this RenderTexture + * @param {PIXI.Rectangle} [frame] - The rectangle frame of the texture to show + */ + constructor(baseRenderTexture, frame) + { + // support for legacy.. + let _legacyRenderer = null; + + if (!(baseRenderTexture instanceof BaseRenderTexture)) + { + /* eslint-disable prefer-rest-params, no-console */ + const width = arguments[1]; + const height = arguments[2]; + const scaleMode = arguments[3]; + const resolution = arguments[4]; + + // we have an old render texture.. + console.warn(`Please use RenderTexture.create(${width}, ${height}) instead of the ctor directly.`); + _legacyRenderer = arguments[0]; + /* eslint-enable prefer-rest-params, no-console */ + + frame = null; + baseRenderTexture = new BaseRenderTexture({ + width, + height, + scaleMode, + resolution, + }); + } + + /** + * The base texture object that this texture uses + * + * @member {BaseTexture} + */ + super(baseRenderTexture, frame); + + this.legacyRenderer = _legacyRenderer; + + /** + * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. + * + * @member {boolean} + */ + this.valid = true; + + /** + * FilterSystem temporary storage + * @private + * @member {PIXI.Rectangle} + */ + this.filterFrame = null; + + this._updateUvs(); + } + + /** + * Resizes the RenderTexture. + * + * @param {number} width - The width to resize to. + * @param {number} height - The height to resize to. + * @param {boolean} [resizeBaseTexture=true] - Should the baseTexture.width and height values be resized as well? + */ + resize(width, height, resizeBaseTexture = true) + { + width = Math.ceil(width); + height = Math.ceil(height); + + // TODO - could be not required.. + this.valid = (width > 0 && height > 0); + + this._frame.width = this.orig.width = width; + this._frame.height = this.orig.height = height; + + if (resizeBaseTexture) + { + this.baseTexture.resize(width, height); + } + + this._updateUvs(); + } + + /** + * A short hand way of creating a render texture. + * + * @param {object} [options] - Options + * @param {number} [options.width=100] - The width of the render texture + * @param {number} [options.height=100] - The height of the render texture + * @param {number} [options.scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the texture being generated + * @return {PIXI.RenderTexture} The new render texture + */ + static create(options) + { + // fallback, old-style: create(width, height, scaleMode, resolution) + if (typeof options === 'number') + { + /* eslint-disable prefer-rest-params */ + options = { + width: options, + height: arguments[1], + scaleMode: arguments[2], + resolution: arguments[3], + }; + /* eslint-enable prefer-rest-params */ + } + + return new RenderTexture(new BaseRenderTexture(options)); + } +} diff --git a/packages/core/src/renderTexture/RenderTextureSystem.js b/packages/core/src/renderTexture/RenderTextureSystem.js new file mode 100644 index 0000000..8d0ca61 --- /dev/null +++ b/packages/core/src/renderTexture/RenderTextureSystem.js @@ -0,0 +1,115 @@ +import System from '../System'; +import { Rectangle } from '@pixi/math'; + +const tempRect = new Rectangle(); + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class RenderTextureSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.clearColor = renderer._backgroundColorRgba; + + // TODO moe this property somewhere else! + this.defaultMaskStack = []; + this.defaultFilterStack = [{}]; + + // empty render texture? + this.renderTexture = null; + + this.destinationFrame = new Rectangle(); + } + + bind(renderTexture, sourceFrame, destinationFrame) + { + // TODO - do we want this?? + if (this.renderTexture === renderTexture) return; + this.renderTexture = renderTexture; + + const renderer = this.renderer; + + if (renderTexture) + { + const baseTexture = renderTexture.baseTexture; + + if (!destinationFrame) + { + tempRect.width = baseTexture.realWidth; + tempRect.height = baseTexture.realHeight; + + destinationFrame = tempRect; + } + + if (!sourceFrame) + { + sourceFrame = destinationFrame; + } + + this.renderer.framebuffer.bind(baseTexture.frameBuffer, destinationFrame); + + this.renderer.projection.update(destinationFrame, sourceFrame, baseTexture.resolution, false); + this.renderer.stencil.setMaskStack(baseTexture.stencilMaskStack); + } + else + { + // TODO these validation checks happen deeper down.. + // thing they can be avoided.. + if (!destinationFrame) + { + tempRect.width = renderer.width; + tempRect.height = renderer.height; + + destinationFrame = tempRect; + } + + if (!sourceFrame) + { + sourceFrame = destinationFrame; + } + + renderer.framebuffer.bind(null, destinationFrame); + + // TODO store this.. + this.renderer.projection.update(destinationFrame, sourceFrame, this.renderer.resolution, true); + this.renderer.stencil.setMaskStack(this.defaultMaskStack); + } + + this.destinationFrame.copyFrom(destinationFrame); + } + + /** + * Erases the render texture and fills the drawing area with a colour + * + * @param {number} [clearColor] - The colour + * @return {PIXI.Renderer} Returns itself. + */ + clear(clearColor) + { + if (this.renderTexture) + { + clearColor = clearColor || this.renderTexture.baseTexture.clearColor; + } + else + { + clearColor = clearColor || this.clearColor; + } + + this.renderer.framebuffer.clear(clearColor[0], clearColor[1], clearColor[2], clearColor[3]); + } + + resize()// screenWidth, screenHeight) + { + // resize the root only! + this.bind(null); + } +} diff --git a/packages/core/src/renderers/AbstractRenderer.js b/packages/core/src/renderers/AbstractRenderer.js deleted file mode 100644 index 928670f..0000000 --- a/packages/core/src/renderers/AbstractRenderer.js +++ /dev/null @@ -1,353 +0,0 @@ -import { hex2string, hex2rgb } from '@pixi/utils'; -import { Matrix, Rectangle } from '@pixi/math'; -import { RENDERER_TYPE } from '@pixi/constants'; -import { settings } from '@pixi/settings'; -import { Container } from '@pixi/display'; -import RenderTexture from '../textures/RenderTexture'; -import EventEmitter from 'eventemitter3'; - -const tempMatrix = new Matrix(); - -/** - * 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 EventEmitter - * @memberof PIXI - */ -export default class AbstractRenderer extends EventEmitter -{ - // eslint-disable-next-line valid-jsdoc - /** - * @param {string} system - The name of the system this renderer is for. - * @param {object} [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.transparent=false] - If the render view is transparent, default false - * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false - * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) - * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. The - * resolution of the renderer retina would be 2. - * @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 {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when rendering, - * stopping pixel interpolation. - */ - constructor(system, options, arg2, arg3) - { - super(); - - // Support for constructor(system, screenWidth, screenHeight, options) - if (typeof options === 'number') - { - options = Object.assign({ - width: options, - height: arg2 || settings.RENDER_OPTIONS.height, - }, arg3); - } - - // 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 = RENDERER_TYPE.UNKNOWN; - - /** - * Measurements of the screen. (0, 0, screenWidth, screenHeight) - * - * Its safe to use as filterArea or hitArea for 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 1 - */ - this.resolution = options.resolution || settings.RESOLUTION; - - /** - * Whether the render view is transparent - * - * @member {boolean} - */ - this.transparent = options.transparent; - - /** - * Whether css dimensions of canvas view should be resized to screen dimensions automatically - * - * @member {boolean} - */ - this.autoResize = options.autoResize || false; - - /** - * Tracks the blend modes useful for this renderer. - * - * @member {object} - */ - this.blendModes = null; - - /** - * 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; - - /** - * If true PixiJS will Math.floor() x/y values when rendering, stopping pixel interpolation. - * Handy for crisp pixel art and speed on legacy devices. - * - * @member {boolean} - */ - this.roundPixels = options.roundPixels; - - /** - * The background color as a number. - * - * @member {number} - * @private - */ - this._backgroundColor = 0x000000; - - /** - * The background color as an [R, G, B] array. - * - * @member {number[]} - * @private - */ - this._backgroundColorRgba = [0, 0, 0, 0]; - - /** - * The background color as a string. - * - * @member {string} - * @private - */ - this._backgroundColorString = '#000000'; - - this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter - - /** - * This temporary display object used as the parent of the currently being rendered item - * - * @member {PIXI.DisplayObject} - * @private - */ - this._tempDisplayObjectParent = new Container(); - - /** - * The last root object that the renderer tried to render. - * - * @member {PIXI.DisplayObject} - * @private - */ - this._lastObjectRendered = this._tempDisplayObjectParent; - - /** - * Collection of plugins. - * @readonly - * @member {object} - */ - this.plugins = {}; - } - - /** - * Initialize the plugins. - * - * @protected - * @param {object} staticMap - The dictionary of staticly saved plugins. - */ - initPlugins(staticMap) - { - 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() - { - return this.view.width; - } - - /** - * Same as view.height, actual number of pixels in the canvas by vertical - * - * @member {number} - * @readonly - * @default 600 - */ - get height() - { - return this.view.height; - } - - /** - * Resizes the screen and canvas to the specified width and height - * Canvas dimensions are multiplied by resolution - * - * @param {number} screenWidth - the new width of the screen - * @param {number} screenHeight - the new height of the screen - */ - resize(screenWidth, screenHeight) - { - this.screen.width = screenWidth; - this.screen.height = screenHeight; - - this.view.width = screenWidth * this.resolution; - this.view.height = screenHeight * this.resolution; - - if (this.autoResize) - { - this.view.style.width = `${screenWidth}px`; - this.view.style.height = `${screenHeight}px`; - } - } - - /** - * 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 {PIXI.DisplayObject} displayObject - The displayObject the object will be generated from - * @param {number} scaleMode - Should be one of the scaleMode consts - * @param {number} resolution - The resolution / device pixel ratio of the texture being generated - * @param {PIXI.Rectangle} [region] - The region of the displayObject, that shall be rendered, - * if no region is specified, defaults to the local bounds of the displayObject. - * @return {PIXI.Texture} a texture of the graphics object - */ - generateTexture(displayObject, scaleMode, resolution, region) - { - region = region || displayObject.getLocalBounds(); - - // 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(region.width | 0, region.height | 0, scaleMode, resolution); - - tempMatrix.tx = -region.x; - tempMatrix.ty = -region.y; - - this.render(displayObject, renderTexture, false, tempMatrix, true); - - return renderTexture; - } - - /** - * Removes everything from the renderer and optionally removes the Canvas DOM element. - * - * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. - */ - destroy(removeView) - { - 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); - } - - this.plugins = null; - - this.type = RENDERER_TYPE.UNKNOWN; - - this.view = null; - - this.screen = null; - - this.resolution = 0; - - this.transparent = false; - - this.autoResize = false; - - this.blendModes = null; - - this.options = null; - - this.preserveDrawingBuffer = false; - this.clearBeforeRender = false; - - this.roundPixels = false; - - this._backgroundColor = 0; - this._backgroundColorRgba = null; - this._backgroundColorString = null; - - this._tempDisplayObjectParent = null; - this._lastObjectRendered = null; - } - - /** - * The background color to fill if not transparent - * - * @member {number} - */ - get backgroundColor() - { - return this._backgroundColor; - } - - set backgroundColor(value) // eslint-disable-line require-jsdoc - { - this._backgroundColor = value; - this._backgroundColorString = hex2string(value); - hex2rgb(value, this._backgroundColorRgba); - } -} diff --git a/packages/core/src/renderers/Renderer.js b/packages/core/src/renderers/Renderer.js deleted file mode 100644 index a2020b1..0000000 --- a/packages/core/src/renderers/Renderer.js +++ /dev/null @@ -1,329 +0,0 @@ -import AbstractRenderer from './AbstractRenderer'; -import { sayHello } from '@pixi/utils'; -import MaskSystem from './systems/MaskSystem'; -import StencilSystem from './systems/StencilSystem'; -import FilterSystem from './systems/filter/FilterSystem'; -import FramebufferSystem from './systems/FramebufferSystem'; -import RenderTextureSystem from './systems/RenderTextureSystem'; -import TextureSystem from './systems/textures/TextureSystem'; -import ProjectionSystem from './systems/ProjectionSystem'; -import StateSystem from './systems/StateSystem'; -import GeometrySystem from './systems/geometry/GeometrySystem'; -import ShaderSystem from './systems/shader/ShaderSystem'; -import ContextSystem from './systems/ContextSystem'; -import BatchSystem from './systems/BatchSystem'; -import TextureGCSystem from './systems/textures/TextureGCSystem'; -import { RENDERER_TYPE } from '@pixi/constants'; -import UniformGroup from '../shader/UniformGroup'; -import { Matrix } from '@pixi/math'; -import Runner from 'mini-runner'; - -/** - * 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 Render works by automatically managing webGLBatchs. - * 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 :) - * - * @class - * @memberof PIXI - * @extends PIXI.AbstractRenderer - */ -export default class Renderer extends AbstractRenderer -{ - // eslint-disable-next-line valid-jsdoc - /** - * - * @param {object} [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.transparent=false] - If the render view is transparent, default false - * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false - * @param {boolean} [options.antialias=false] - sets antialias. If not available natively then FXAA - * antialiasing is used - * @param {boolean} [options.forceFXAA=false] - forces FXAA antialiasing to be used over native. - * FXAA is faster, but may not always look as great - * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. - * The resolution of the renderer retina would be 2. - * @param {boolean} [options.clearBeforeRender=true] - This sets if the renderer will clear - * the canvas or not before the new render pass. If you wish to set this to false, you *must* set - * preserveDrawingBuffer to `true`. - * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, - * enable this if you need to call toDataUrl on the webgl context. - * @param {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when - * rendering, stopping pixel interpolation. - * @param {number} [options.backgroundColor=0x000000] - The background color of the rendered area - * (shown if not transparent). - * @param {boolean} [options.legacy=false] - If true PixiJS will aim to ensure compatibility - * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. - * @param {string} [options.powerPreference] - Parameter passed to webgl context, set to "high-performance" - * for devices with dual graphics card - */ - constructor(options, arg2, arg3) - { - super('WebGL', options, arg2, arg3); - - /** - * The type of this renderer as a standardised const - * - * @member {number} - * @see PIXI.RENDERER_TYPE - */ - this.type = RENDERER_TYPE.WEBGL; - - // this will be set by the contextSystem (this.context) - this.gl = null; - this.CONTEXT_UID = 0; - this.legacy = !!options.legacy; - - // TODO legacy! - - // runners! - this.runners = { - destroy: new Runner('destroy'), - contextChange: new Runner('contextChange', 1), - reset: new Runner('reset'), - update: new Runner('update'), - postrender: new Runner('postrender'), - prerender: new Runner('prerender'), - resize: new Runner('resize', 2), - }; - - 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(GeometrySystem, 'geometry') - .addSystem(FramebufferSystem, 'framebuffer') - .addSystem(StencilSystem, 'stencil') - .addSystem(ProjectionSystem, 'projection') - .addSystem(TextureGCSystem, 'textureGC') - .addSystem(FilterSystem, 'filter') - .addSystem(RenderTextureSystem, 'renderTexture') - .addSystem(BatchSystem, 'batch'); - - this.initPlugins(Renderer.__plugins); - - /** - * The options passed in to create a new webgl context. - * - * @member {object} - * @private - */ - if (options.context) - { - this.context.initFromContext(options.context); - } - else - { - this.context.initFromOptions({ - alpha: this.transparent, - antialias: options.antialias, - premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', - stencil: true, - 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); - } - - addSystem(_class, name) - { - if (!name) - { - name = _class.name; - } - - // TODO - read name from class.name.. - - /* - if(name.includes('System')) - { - name = name.replace('System', ''); - name = name.charAt(0).toLowerCase() + name.slice(1); - } - */ - - const system = new _class(this); - - if (this[name]) - { - throw new Error(`Whoops! ${name} is already a manger`); - } - - this[name] = system; - - for (const i in this.runners) - { - this.runners[i].add(system); - } - - return this; - - /** - * 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. - */ - } - - /** - * Renders the object to its webGL view - * - * @param {PIXI.DisplayObject} displayObject - the object to be rendered - * @param {PIXI.RenderTexture} renderTexture - The render texture to render to. - * @param {boolean} [clear] - Should the canvas be cleared before the new render - * @param {PIXI.Transform} [transform] - A transform to apply to the render texture before rendering. - * @param {boolean} [skipUpdateTransform] - Should we skip the update transform pass? - */ - render(displayObject, renderTexture, clear, transform, skipUpdateTransform) - { - // can be handy to know! - this.renderingToScreen = !renderTexture; - - this.runners.prerender.run(); - this.emit('prerender'); - - // 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.parent; - - displayObject.parent = this._tempDisplayObjectParent; - displayObject.updateTransform(); - displayObject.parent = 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.run(); - - this.emit('postrender'); - } - - /** - * Resizes the webGL view to the specified width and height. - * - * @param {number} screenWidth - the new width of the screen - * @param {number} screenHeight - the new height of the screen - */ - resize(screenWidth, screenHeight) - { - AbstractRenderer.prototype.resize.call(this, screenWidth, screenHeight); - - this.runners.resize.run(screenWidth, screenHeight); - } - - /** - * Resets the WebGL state so you can render things however you fancy! - * - * @return {PIXI.Renderer} Returns itself. - */ - reset() - { - this.runners.reset.run(); - - return this; - } - - clear() - { - this.framebuffer.bind(); - this.framebuffer.clear(); - } - - /** - * Removes everything from the renderer (event listeners, spritebatch, etc...) - * - * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. - * See: https://github.com/pixijs/pixi.js/issues/2233 - */ - destroy(removeView) - { - this.runners.destroy.run(); - - // call base destroy - super.destroy(removeView); - - // TODO nullify all the managers.. - this.gl = null; - } - - /** - * 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 - * builds and excluding plugins. - * @name PIXI.Renderer#plugins - * @type {object} - * @readonly - * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. - * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. - * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. - * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. - */ - - /** - * Adds a plugin to the renderer. - * - * @method - * @param {string} pluginName - The name of the plugin. - * @param {Function} ctor - The constructor function or class for the plugin. - */ - static registerPlugin(pluginName, ctor) - { - Renderer.__plugins = Renderer.__plugins || {}; - Renderer.__plugins[pluginName] = ctor; - } -} diff --git a/packages/core/src/renderers/State.js b/packages/core/src/renderers/State.js deleted file mode 100644 index fa3f221..0000000 --- a/packages/core/src/renderers/State.js +++ /dev/null @@ -1,163 +0,0 @@ -/* eslint-disable max-len */ - -const BLEND = 0; -const OFFSET = 1; -const CULLING = 2; -const DEPTH_TEST = 3; -const WINDING = 4; - -/** - * This is a webGL state. It is passed The WebGL StateManager. - * Each mesh renderered may require webGL to be in a different state. - * For example you may want different blend mode or to enable polygon offsets - * - * @class - * @memberof PIXI - */ -export default class State -{ - /** - * - */ - constructor() - { - this.data = 0; - - this.blendMode = 0; - this.polygonOffset = 0; - - this.blend = true; - // this.depthTest = true; - } - - /** - * Activates blending of the computed fragment color values - * - * @member {boolean} - */ - get blend() - { - return !!(this.data & (1 << BLEND)); - } - - set blend(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << BLEND)) !== value) - { - this.data ^= (1 << BLEND); - } - } - - /** - * Activates adding an offset to depth values of polygon's fragments - * - * @member {boolean} - * @default false - */ - get offsets() - { - return !!(this.data & (1 << OFFSET)); - } - - set offsets(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << OFFSET)) !== value) - { - this.data ^= (1 << OFFSET); - } - } - - /** - * Activates culling of polygons. - * - * @member {boolean} - * @default false - */ - get culling() - { - return !!(this.data & (1 << CULLING)); - } - - set culling(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << CULLING)) !== value) - { - this.data ^= (1 << CULLING); - } - } - - /** - * Activates depth comparisons and updates to the depth buffer. - * - * @member {boolean} - * @default false - */ - get depthTest() - { - return !!(this.data & (1 << DEPTH_TEST)); - } - - set depthTest(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << DEPTH_TEST)) !== value) - { - this.data ^= (1 << DEPTH_TEST); - } - } - - /** - * Specifies whether or not front or back-facing polygons can be culled. - * @member {boolean} - * @default false - */ - get clockwiseFrontFace() - { - return !!(this.data & (1 << WINDING)); - } - - set clockwiseFrontFace(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << WINDING)) !== value) - { - this.data ^= (1 << WINDING); - } - } - - /** - * The blend mode to be applied when this state is set. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. - * Setting this mode to anything other than NO_BLEND will automatically switch blending on. - * - * @member {boolean} - * @default PIXI.BLEND_MODES.NORMAL - * @see PIXI.BLEND_MODES - */ - get blendMode() - { - return this._blendMode; - } - - set blendMode(value) // eslint-disable-line require-jsdoc - { - // 17 is NO BLEND - this.blend = (value !== 17); - this._blendMode = value; - } - - /** - * The polygon offset. Setting this property to anything other than 0 will automatically enable poygon offset fill. - * - * @member {number} - * @default 0 - */ - get polygonOffset() - { - return this._polygonOffset; - } - - set polygonOffset(value) // eslint-disable-line require-jsdoc - { - this.offsets = !!value; - this._polygonOffset = value; - } -} - diff --git a/packages/core/src/renderers/filters/Filter.js b/packages/core/src/renderers/filters/Filter.js deleted file mode 100644 index 2073b8d..0000000 --- a/packages/core/src/renderers/filters/Filter.js +++ /dev/null @@ -1,140 +0,0 @@ -import Shader from '../../shader/Shader'; -import Program from '../../shader/Program'; -import State from '../State'; -import { settings } from '@pixi/settings'; -// import extractUniformsFromSrc from './extractUniformsFromSrc'; -import defaultVertex from './defaultFilter.vert'; -import defaultFragment from './defaultFilter.frag'; - -// let math = require('../../math'); -/** - * @class - * @memberof PIXI - * @extends PIXI.Shader - */ -export default class Filter extends Shader -{ - /** - * @param {string} [vertexSrc] - The source of the vertex shader. - * @param {string} [fragmentSrc] - The source of the fragment shader. - * @param {object} [uniforms] - Custom uniforms to use to augment the built-in ones. - */ - constructor(vertexSrc, fragmentSrc, uniforms) - { - const program = Program.from(vertexSrc, fragmentSrc); - - super(program, uniforms); - - /** - * The padding of the filter. Some filters require extra space to breath such as a blur. - * Increasing this will add extra width and height to the bounds of the object that the - * filter is applied to. - * - * @member {number} - */ - this.padding = 0; - - /** - * The resolution of the filter. Setting this to be lower will lower the quality but - * increase the performance of the filter. - * - * @member {number} - */ - this.resolution = settings.RESOLUTION; - - /** - * If enabled is true the filter is applied, if false it will not. - * - * @member {boolean} - */ - this.enabled = true; - - /** - * If enabled, PixiJS will fit the filter area into boundaries for better performance. - * Switch it off if it does not work for specific shader. - * - * @member {boolean} - */ - this.autoFit = true; - - /** - * Legacy filters use position and uvs from attributes - * @member {boolean} - * @readonly - */ - this.legacy = !!this.program.attributeData.aTextureCoord; - - /** - * the webGL state the filter requires to render - * @member {PIXI.State} - */ - this.state = new State(); - } - - /** - * Applies the filter - * - * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from - * @param {PIXI.RenderTarget} input - The input render target. - * @param {PIXI.RenderTarget} output - The target to output to. - * @param {boolean} clear - Should the output be cleared before rendering to it - * @param {object} [currentState] - It's current state of filter. - * There are some useful properties in the currentState : - * target, filters, sourceFrame, destinationFrame, renderTarget, resolution - */ - apply(filterManager, input, output, clear, currentState, derp) // eslint-disable-line no-unused-vars - { - // do as you please! - - filterManager.applyFilter(this, input, output, clear, currentState, derp); - - // or just do a regular render.. - } - - /** - * Sets the blendmode of the filter - * - * @member {number} - * @default PIXI.BLEND_MODES.NORMAL - */ - get blendMode() - { - return this.state.blendMode; - } - - set blendMode(value) // eslint-disable-line require-jsdoc - { - this.state.blendMode = value; - } - - /** - * The default vertex shader source - * - * @static - * @constant - */ - static get defaultVertexSrc() - { - return defaultVertex; - } - - /** - * The default fragment shader source - * - * @static - * @constant - */ - static get defaultFragmentSrc() - { - return defaultFragment; - } -} - -/** - * Used for caching shader IDs - * - * @static - * @private - */ -Filter.SOURCE_KEY_MAP = {}; - diff --git a/packages/core/src/renderers/filters/defaultFilter.frag b/packages/core/src/renderers/filters/defaultFilter.frag deleted file mode 100644 index a14c1be..0000000 --- a/packages/core/src/renderers/filters/defaultFilter.frag +++ /dev/null @@ -1,22 +0,0 @@ -varying vec2 vTextureCoord; -varying vec2 vFilterCoord; - -uniform sampler2D uSampler; -uniform sampler2D filterSampler; - -void main(void){ - vec4 masky = texture2D(filterSampler, vFilterCoord); - vec4 sample = texture2D(uSampler, vTextureCoord); - vec4 color; - if(mod(vFilterCoord.x, 1.0) > 0.5) - { - color = vec4(1.0, 0.0, 0.0, 1.0); - } - else - { - color = vec4(0.0, 1.0, 0.0, 1.0); - } - // gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0); - gl_FragColor = mix(sample, masky, 0.5); - gl_FragColor *= sample.a; -} diff --git a/packages/core/src/renderers/filters/defaultFilter.vert b/packages/core/src/renderers/filters/defaultFilter.vert deleted file mode 100644 index e1febca..0000000 --- a/packages/core/src/renderers/filters/defaultFilter.vert +++ /dev/null @@ -1,14 +0,0 @@ -attribute vec2 aVertexPosition; -attribute vec2 aTextureCoord; - -uniform mat3 projectionMatrix; -uniform mat3 filterMatrix; - -varying vec2 vTextureCoord; -varying vec2 vFilterCoord; - -void main(void){ - gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); - vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy; - vTextureCoord = aTextureCoord ; -} diff --git a/packages/core/src/renderers/filters/filterTransforms.js b/packages/core/src/renderers/filters/filterTransforms.js deleted file mode 100644 index 87a3cd6..0000000 --- a/packages/core/src/renderers/filters/filterTransforms.js +++ /dev/null @@ -1,50 +0,0 @@ -import { Matrix } from '@pixi/math'; - -/** - * Calculates the mapped matrix - * @param filterArea {Rectangle} The filter area - * @param sprite {Sprite} the target sprite - * @param outputMatrix {Matrix} @alvin - * @private - */ -// this returns a matrix that will normalise map filter cords in the filter to screen space -export function calculateScreenSpaceMatrix(outputMatrix, filterArea, textureSize) -{ - // TODO unwrap? - const mappedMatrix = outputMatrix.identity(); - - mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); - - mappedMatrix.scale(textureSize.width, textureSize.height); - - return mappedMatrix; -} - -export function calculateNormalizedScreenSpaceMatrix(outputMatrix, filterArea, textureSize) -{ - const mappedMatrix = outputMatrix.identity(); - - mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); - - const translateScaleX = (textureSize.width / filterArea.width); - const translateScaleY = (textureSize.height / filterArea.height); - - mappedMatrix.scale(translateScaleX, translateScaleY); - - return mappedMatrix; -} - -// this will map the filter coord so that a texture can be used based on the transform of a sprite -export function calculateSpriteMatrix(outputMatrix, filterArea, textureSize, sprite) -{ - const orig = sprite._texture.orig; - const mappedMatrix = outputMatrix.set(textureSize.width, 0, 0, textureSize.height, filterArea.x, filterArea.y); - const worldTransform = sprite.worldTransform.copyTo(Matrix.TEMP_MATRIX); - - worldTransform.invert(); - mappedMatrix.prepend(worldTransform); - mappedMatrix.scale(1.0 / orig.width, 1.0 / orig.height); - mappedMatrix.translate(sprite.anchor.x, sprite.anchor.y); - - return mappedMatrix; -} diff --git a/packages/core/src/renderers/filters/spriteMask/SpriteMaskFilter.js b/packages/core/src/renderers/filters/spriteMask/SpriteMaskFilter.js deleted file mode 100644 index d5a91a4..0000000 --- a/packages/core/src/renderers/filters/spriteMask/SpriteMaskFilter.js +++ /dev/null @@ -1,63 +0,0 @@ -import Filter from '../Filter'; -import { Matrix } from '@pixi/math'; -import vertex from './spriteMaskFilter.vert'; -import fragment from './spriteMaskFilter.frag'; -import { default as TextureMatrix } from '../../../textures/TextureMatrix'; - -/** - * The SpriteMaskFilter class - * - * @class - * @extends PIXI.Filter - * @memberof PIXI - */ -export default class SpriteMaskFilter extends Filter -{ - /** - * @param {PIXI.Sprite} sprite - the target sprite - */ - constructor(sprite) - { - const maskMatrix = new Matrix(); - - super(vertex, fragment); - - sprite.renderable = false; - - this.maskSprite = sprite; - this.maskMatrix = maskMatrix; - } - - /** - * Applies the filter - * - * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from - * @param {PIXI.RenderTarget} input - The input render target. - * @param {PIXI.RenderTarget} output - The target to output to. - */ - apply(filterManager, input, output) - { - const maskSprite = this.maskSprite; - const tex = this.maskSprite.texture; - - if (!tex.valid) - { - return; - } - if (!tex.transform) - { - // margin = 0.0, let it bleed a bit, shader code becomes easier - // assuming that atlas textures were made with 1-pixel padding - tex.transform = new TextureMatrix(tex, 0.0); - } - tex.transform.update(); - - this.uniforms.mask = tex; - this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite) - .prepend(tex.transform.mapCoord); - this.uniforms.alpha = maskSprite.worldAlpha; - this.uniforms.maskClamp = tex.transform.uClampFrame; - - filterManager.applyFilter(this, input, output); - } -} diff --git a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.frag b/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.frag deleted file mode 100644 index 0e4aef8..0000000 --- a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.frag +++ /dev/null @@ -1,23 +0,0 @@ -varying vec2 vMaskCoord; -varying vec2 vTextureCoord; - -uniform sampler2D uSampler; -uniform sampler2D mask; -uniform float alpha; -uniform vec4 maskClamp; - -void main(void) -{ - float clip = step(3.5, - step(maskClamp.x, vMaskCoord.x) + - step(maskClamp.y, vMaskCoord.y) + - step(vMaskCoord.x, maskClamp.z) + - step(vMaskCoord.y, maskClamp.w)); - - vec4 original = texture2D(uSampler, vTextureCoord); - vec4 masky = texture2D(mask, vMaskCoord); - - original *= (masky.r * masky.a * alpha * clip); - - gl_FragColor = original; -} diff --git a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.vert b/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.vert deleted file mode 100644 index d23a30c..0000000 --- a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.vert +++ /dev/null @@ -1,16 +0,0 @@ -attribute vec2 aVertexPosition; -attribute vec2 aTextureCoord; - -uniform mat3 projectionMatrix; -uniform mat3 otherMatrix; - -varying vec2 vMaskCoord; -varying vec2 vTextureCoord; - -void main(void) -{ - gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); - - vTextureCoord = aTextureCoord; - vMaskCoord = ( otherMatrix * vec3( aTextureCoord, 1.0) ).xy; -} diff --git a/packages/core/src/renderers/systems/BatchSystem.js b/packages/core/src/renderers/systems/BatchSystem.js deleted file mode 100644 index 70e9518..0000000 --- a/packages/core/src/renderers/systems/BatchSystem.js +++ /dev/null @@ -1,68 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import ObjectRenderer from '../utils/ObjectRenderer'; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ - -export default class BatchSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this manager works for. - */ - constructor(renderer) - { - super(renderer); - - /** - * An empty renderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.emptyRenderer = new ObjectRenderer(renderer); - - /** - * The currently active ObjectRenderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.currentRenderer = this.emptyRenderer; - } - - /** - * Changes the current renderer to the one given in parameter - * - * @param {PIXI.ObjectRenderer} objectRenderer - The object renderer to use. - */ - setObjectRenderer(objectRenderer) - { - if (this.currentRenderer === objectRenderer) - { - return; - } - - this.currentRenderer.stop(); - this.currentRenderer = objectRenderer; - - this.renderer.state.setState(objectRenderer.state); - - this.currentRenderer.start(); - } - - /** - * This should be called if you wish to do some custom rendering - * It will basically render anything that may be batched up such as sprites - * - */ - flush() - { - this.setObjectRenderer(this.emptyRenderer); - } - - reset() - { - this.setObjectRenderer(this.emptyRenderer); - } -} diff --git a/packages/core/src/renderers/systems/ContextSystem.js b/packages/core/src/renderers/systems/ContextSystem.js deleted file mode 100644 index 43bf827..0000000 --- a/packages/core/src/renderers/systems/ContextSystem.js +++ /dev/null @@ -1,186 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { settings } from '@pixi/settings'; - -let CONTEXT_UID = 0; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class ContextSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this.webGLVersion = 1; - - this.handleContextLost = this.handleContextLost.bind(this); - this.handleContextRestored = this.handleContextRestored.bind(this); - - this.extensions = {}; - - renderer.view.addEventListener('webglcontextlost', this.handleContextLost, false); - renderer.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); - } - - get isLost() - { - return (!this.gl || this.gl.isContextLost()); - } - - contextChange(gl) - { - this.gl = gl; - - // restore a context if it was previously lost - if (gl.isContextLost() && gl.getExtension('WEBGL_lose_context')) - { - gl.getExtension('WEBGL_lose_context').restoreContext(); - } - } - - initFromContext(gl) - { - this.gl = gl; - this.validateContext(gl); - this.renderer.gl = gl; - this.renderer.CONTEXT_UID = CONTEXT_UID++; - this.renderer.runners.contextChange.run(gl); - } - - initFromOptions(options) - { - const gl = this.createContext(this.renderer.view, options); - - this.initFromContext(gl); - } - - /** - * Helper class to create a webGL Context - * - * @class - * @memberof PIXI.glCore - * @param canvas {HTMLCanvasElement} the canvas element that we will get the context from - * @param options {Object} An options object that gets passed in to the canvas element containing the context attributes, - * see https://developer.mozilla.org/en/docs/Web/API/HTMLCanvasElement/getContext for the options available - * @return {WebGLRenderingContext} the WebGL context - */ - createContext(canvas, options) - { - let gl; - - if (settings.PREFER_WEBGL_2) - { - gl = canvas.getContext('webgl2', options); - } - - if (gl) - { - this.webGLVersion = 2; - } - else - { - this.webGLVersion = 1; - - gl = canvas.getContext('webgl', options) - || canvas.getContext('experimental-webgl', options); - - if (!gl) - { - // fail, not able to get a context - throw new Error('This browser does not support webGL. Try using the canvas renderer'); - } - } - - this.gl = gl; - - this.getExtensions(); - - return gl; - } - - getExtensions() - { - // time to set up default etensions that pixi uses.. - const gl = this.gl; - const extensions = this.extensions; - - if (this.webGLVersion === 1) - { - extensions.drawBuffers = gl.getExtension('WEBGL_draw_buffers'); - extensions.depthTexture = gl.getExtension('WEBKIT_WEBGL_depth_texture'); - extensions.floatTexture = gl.getExtension('OES_texture_float'); - extensions.loseContext = gl.getExtension('WEBGL_lose_context'); - - extensions.vertexArrayObject = gl.getExtension('OES_vertex_array_object') - || gl.getExtension('MOZ_OES_vertex_array_object') - || gl.getExtension('WEBKIT_OES_vertex_array_object'); - } - - // we don't use any specific WebGL 2 ones yet! - } - - /** - * Handles a lost webgl context - * - * @private - * @param {WebGLContextEvent} event - The context lost event. - */ - handleContextLost(event) - { - event.preventDefault(); - } - - /** - * Handles a restored webgl context - * - * @private - */ - handleContextRestored() - { - this.renderer.runners.contextChange.run(this.gl); - } - - destroy() - { - const view = this.renderer.view; - - // remove listeners - view.removeEventListener('webglcontextlost', this.handleContextLost); - view.removeEventListener('webglcontextrestored', this.handleContextRestored); - - this.gl.useProgram(null); - - if (this.extensions.loseContext) - { - this.extensions.loseContext.loseContext(); - } - } - - postrender() - { - this.gl.flush(); - } - - validateContext(gl) - { - const attributes = gl.getContextAttributes(); - - // this is going to be fairly simple for now.. but at least we have room to grow! - if (!attributes.stencil) - { - /* eslint-disable max-len */ - - /* eslint-disable no-console */ - console.warn('Provided WebGL context does not have a stencil buffer, masks may not render correctly'); - /* eslint-enable no-console */ - - /* eslint-enable max-len */ - } - } -} diff --git a/packages/core/src/renderers/systems/FramebufferSystem.js b/packages/core/src/renderers/systems/FramebufferSystem.js deleted file mode 100644 index df7902b..0000000 --- a/packages/core/src/renderers/systems/FramebufferSystem.js +++ /dev/null @@ -1,246 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { Rectangle } from '@pixi/math'; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class FramebufferSystem extends WebGLSystem -{ - /** - * Sets up the renderer context and necessary buffers. - * - * @private - */ - contextChange() - { - this.gl = this.renderer.gl; - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - this.current = null; - this.viewport = new Rectangle(); - - this.drawBufferExtension = this.renderer.context.extensions.drawBuffers; - } - - bind(framebuffer, frame) - { - const gl = this.gl; - - this.current = framebuffer; - - if (framebuffer) - { - // TODO cacheing layer! - - const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID] || this.initFramebuffer(framebuffer); - - gl.bindFramebuffer(gl.FRAMEBUFFER, fbo.framebuffer); - // makesure all textures are unbound.. - - // now check for updates... - if (fbo.dirtyId !== framebuffer.dirtyId) - { - fbo.dirtyId = framebuffer.dirtyId; - - if (fbo.dirtyFormat !== framebuffer.dirtyFormat) - { - fbo.dirtyFormat = framebuffer.dirtyFormat; - this.updateFramebuffer(framebuffer); - } - else if (fbo.dirtySize !== framebuffer.dirtySize) - { - fbo.dirtySize = framebuffer.dirtySize; - this.resizeFramebuffer(framebuffer); - } - } - - for (let i = 0; i < framebuffer.colorTextures.length; i++) - { - if (framebuffer.colorTextures[i].texturePart) - { - this.renderer.texture.unbind(framebuffer.colorTextures[i].texture); - } - else - { - this.renderer.texture.unbind(framebuffer.colorTextures[i]); - } - } - - if (framebuffer.depthTexture) - { - this.renderer.texture.unbind(framebuffer.depthTexture); - } - - if (frame) - { - this.setViewport(frame.x, frame.y, frame.width, frame.height); - } - else - { - this.setViewport(0, 0, framebuffer.width, framebuffer.height); - } - } - else - { - gl.bindFramebuffer(gl.FRAMEBUFFER, null); - - if (frame) - { - this.setViewport(frame.x, frame.y, frame.width, frame.height); - } - else - { - this.setViewport(0, 0, this.renderer.width, this.renderer.height); - } - } - } - - setViewport(x, y, width, height) - { - const v = this.viewport; - - if (v.width !== width || v.height !== height || v.x !== x || v.y !== y) - { - v.x = x; - v.y = y; - v.width = width; - v.height = height; - - this.gl.viewport(x, y, width, height); - } - } - - get size() - { - if (this.current) - { - // TODO store temp - return { x: 0, y: 0, width: this.current.width, height: this.current.height }; - } - - return { x: 0, y: 0, width: this.renderer.width, height: this.renderer.height }; - } - - clear(r, g, b, a) - { - const gl = this.gl; - - // TODO clear color can be set only one right? - gl.clearColor(r, g, b, a); - gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); - } - - // private functions... - - initFramebuffer(framebuffer) - { - const gl = this.gl; - - // TODO - make this a class? - const fbo = { - framebuffer: gl.createFramebuffer(), - stencil: null, - dirtyId: 0, - dirtyFormat: 0, - dirtySize: 0, - }; - - framebuffer.glFrameBuffers[this.CONTEXT_UID] = fbo; - - return fbo; - } - - resizeFramebuffer(framebuffer) - { - const gl = this.gl; - - if (framebuffer.stencil || framebuffer.depth) - { - gl.bindRenderbuffer(gl.RENDERBUFFER, this.stencil); - gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); - } - } - - updateFramebuffer(framebuffer) - { - const gl = this.gl; - - const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID]; - - // bind the color texture - const colorTextures = framebuffer.colorTextures; - - let count = colorTextures.length; - - if (!this.drawBufferExtension) - { - count = Math.min(count, 1); - } - - const activeTextures = []; - - for (let i = 0; i < count; i++) - { - const texture = framebuffer.colorTextures[i]; - - if (texture.texturePart) - { - this.renderer.texture.bind(texture.texture, 0); - - gl.framebufferTexture2D(gl.FRAMEBUFFER, - gl.COLOR_ATTACHMENT0 + i, - gl.TEXTURE_CUBE_MAP_NEGATIVE_X + texture.side, - texture.texture._glTextures[this.CONTEXT_UID].texture, - 0); - } - else - { - this.renderer.texture.bind(texture, 0); - - gl.framebufferTexture2D(gl.FRAMEBUFFER, - gl.COLOR_ATTACHMENT0 + i, - gl.TEXTURE_2D, - texture._glTextures[this.CONTEXT_UID].texture, - 0); - } - - activeTextures.push(gl.COLOR_ATTACHMENT0 + i); - } - - if (this.drawBufferExtension && activeTextures.length > 1) - { - this.drawBufferExtension.drawBuffersWEBGL(activeTextures); - } - - if (framebuffer.depthTexture) - { - const depthTextureExt = this.renderer.context.extensions.depthTexture; - - if (depthTextureExt) - { - const depthTexture = framebuffer.depthTexture; - - this.renderer.texture.bind(depthTexture, 0); - - gl.framebufferTexture2D(gl.FRAMEBUFFER, - gl.DEPTH_ATTACHMENT, - gl.TEXTURE_2D, - depthTexture._glTextures[this.CONTEXT_UID].texture, - 0); - } - } - - if (framebuffer.stencil || framebuffer.depth) - { - fbo.stencil = gl.createRenderbuffer(); - - gl.bindRenderbuffer(gl.RENDERBUFFER, fbo.stencil); - - // TODO.. this is depth AND stencil? - gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, fbo.stencil); - gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); - // fbo.enableStencil(); - } - } -} diff --git a/packages/core/src/renderers/systems/MaskSystem.js b/packages/core/src/renderers/systems/MaskSystem.js deleted file mode 100644 index d9c4b1a..0000000 --- a/packages/core/src/renderers/systems/MaskSystem.js +++ /dev/null @@ -1,199 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import AlphaMaskFilter from '../filters/spriteMask/SpriteMaskFilter'; - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class MaskSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - // TODO - we don't need both! - this.scissor = false; - this.scissorData = null; - this.scissorRenderTarget = null; - - this.enableScissor = false; - - this.alphaMaskPool = []; - this.alphaMaskIndex = 0; - } - - /** - * Applies the Mask and adds it to the current filter stack. - * - * @param {PIXI.DisplayObject} target - Display Object to push the mask to - * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. - */ - push(target, maskData) - { - // TODO the root check means scissor rect will not - // be used on render textures more info here: - // https://github.com/pixijs/pixi.js/pull/3545 - - if (maskData.texture) - { - this.pushSpriteMask(target, maskData); - } - else if (this.enableScissor - && !this.scissor - && this.renderer._activeRenderTarget.root - && !this.renderer.stencil.stencilMaskStack.length - && maskData.isFastRect()) - { - const matrix = maskData.worldTransform; - - let rot = Math.atan2(matrix.b, matrix.a); - - // use the nearest degree! - rot = Math.round(rot * (180 / Math.PI)); - - if (rot % 90) - { - this.pushStencilMask(maskData); - } - else - { - this.pushScissorMask(target, maskData); - } - } - else - { - this.pushStencilMask(maskData); - } - } - - /** - * Removes the last mask from the mask stack and doesn't return it. - * - * @param {PIXI.DisplayObject} target - Display Object to pop the mask from - * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. - */ - pop(target, maskData) - { - if (maskData.texture) - { - this.popSpriteMask(target, maskData); - } - else if (this.enableScissor && !this.renderer.stencil.stencilMaskStack.length) - { - this.popScissorMask(target, maskData); - } - else - { - this.popStencilMask(target, maskData); - } - } - - /** - * Applies the Mask and adds it to the current filter stack. - * - * @param {PIXI.RenderTarget} target - Display Object to push the sprite mask to - * @param {PIXI.Sprite} maskData - Sprite to be used as the mask - */ - pushSpriteMask(target, maskData) - { - let alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; - - if (!alphaMaskFilter) - { - alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; - } - - alphaMaskFilter[0].resolution = this.renderer.resolution; - alphaMaskFilter[0].maskSprite = maskData; - - // TODO - may cause issues! - target.filterArea = maskData.getBounds(true); - - this.renderer.filterSystem.pushFilter(target, alphaMaskFilter); - - this.alphaMaskIndex++; - } - - /** - * Removes the last filter from the filter stack and doesn't return it. - * - */ - popSpriteMask() - { - this.renderer.filterSystem.popFilter(); - this.alphaMaskIndex--; - } - - /** - * Applies the Mask and adds it to the current filter stack. - * - * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. - */ - pushStencilMask(maskData) - { - this.renderer.batch.flush(); - this.renderer.stencil.pushStencil(maskData); - } - - /** - * Removes the last filter from the filter stack and doesn't return it. - * - */ - popStencilMask() - { - // this.renderer.currentRenderer.stop(); - this.renderer.stencil.popStencil(); - } - - /** - * - * @param {PIXI.DisplayObject} target - Display Object to push the mask to - * @param {PIXI.Graphics} maskData - The masking data. - */ - pushScissorMask(target, maskData) - { - maskData.renderable = true; - - const renderTarget = this.renderer._activeRenderTarget; - - const bounds = maskData.getBounds(); - - bounds.fit(renderTarget.size); - maskData.renderable = false; - - this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); - - const resolution = this.renderer.resolution; - - this.renderer.gl.scissor( - bounds.x * resolution, - (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, - bounds.width * resolution, - bounds.height * resolution - ); - - this.scissorRenderTarget = renderTarget; - this.scissorData = maskData; - this.scissor = true; - } - - /** - * - * - */ - popScissorMask() - { - this.scissorRenderTarget = null; - this.scissorData = null; - this.scissor = false; - - // must be scissor! - const gl = this.renderer.gl; - - gl.disable(gl.SCISSOR_TEST); - } -} diff --git a/packages/core/src/renderers/systems/ProjectionSystem.js b/packages/core/src/renderers/systems/ProjectionSystem.js deleted file mode 100644 index ec5efdb..0000000 --- a/packages/core/src/renderers/systems/ProjectionSystem.js +++ /dev/null @@ -1,73 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { Matrix } from '@pixi/math'; - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ - -export default class ProjectionSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this.projectionMatrix = new Matrix(); - } - - update(destinationFrame, sourceFrame, resolution, root) - { - this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; - this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; - - this.calculateProjection(this.destinationFrame, this.sourceFrame, resolution, root); - - this.renderer.globalUniforms.uniforms.projectionMatrix = this.projectionMatrix; - this.renderer.globalUniforms.update(); - } - - /** - * Updates the projection matrix based on a projection frame (which is a rectangle) - * - * @param {Rectangle} destinationFrame - The destination frame. - * @param {Rectangle} sourceFrame - The source frame. - */ - calculateProjection(destinationFrame, sourceFrame, resolution, root) - { - const pm = this.projectionMatrix; - - // I don't think we will need this line.. - // pm.identity(); - - if (!root) - { - pm.a = (1 / destinationFrame.width * 2) * resolution; - pm.d = (1 / destinationFrame.height * 2) * resolution; - - pm.tx = -1 - (sourceFrame.x * pm.a); - pm.ty = -1 - (sourceFrame.y * pm.d); - } - else - { - pm.a = (1 / destinationFrame.width * 2) * resolution; - pm.d = (-1 / destinationFrame.height * 2) * resolution; - - pm.tx = -1 - (sourceFrame.x * pm.a); - pm.ty = 1 - (sourceFrame.y * pm.d); - } - } - - /** - * Sets the transform of the active render target to the given matrix - * - * @param {PIXI.Matrix} matrix - The transformation matrix - */ - setTransform()// matrix) - { - // this._activeRenderTarget.transform = matrix; - } -} diff --git a/packages/core/src/renderers/systems/RenderTextureSystem.js b/packages/core/src/renderers/systems/RenderTextureSystem.js deleted file mode 100644 index a133ef1..0000000 --- a/packages/core/src/renderers/systems/RenderTextureSystem.js +++ /dev/null @@ -1,115 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { Rectangle } from '@pixi/math'; - -const tempRect = new Rectangle(); - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ - -export default class RenderTextureSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this.clearColor = renderer._backgroundColorRgba; - - // TODO moe this property somewhere else! - this.defaultMaskStack = []; - this.defaultFilterStack = [{}]; - - // empty render texture? - this.renderTexture = null; - - this.destinationFrame = new Rectangle(); - } - - bind(renderTexture, sourceFrame, destinationFrame) - { - // TODO - do we want this?? - if (this.renderTexture === renderTexture) return; - this.renderTexture = renderTexture; - - const renderer = this.renderer; - - if (renderTexture) - { - const baseTexture = renderTexture.baseTexture; - - if (!destinationFrame) - { - tempRect.width = baseTexture.realWidth; - tempRect.height = baseTexture.realHeight; - - destinationFrame = tempRect; - } - - if (!sourceFrame) - { - sourceFrame = destinationFrame; - } - - this.renderer.framebuffer.bind(baseTexture.frameBuffer, destinationFrame); - - this.renderer.projection.update(destinationFrame, sourceFrame, baseTexture.resolution, false); - this.renderer.stencil.setMaskStack(baseTexture.stencilMaskStack); - } - else - { - // TODO these validation checks happen deeper down.. - // thing they can be avoided.. - if (!destinationFrame) - { - tempRect.width = renderer.width; - tempRect.height = renderer.height; - - destinationFrame = tempRect; - } - - if (!sourceFrame) - { - sourceFrame = destinationFrame; - } - - renderer.framebuffer.bind(null, destinationFrame); - - // TODO store this.. - this.renderer.projection.update(destinationFrame, sourceFrame, this.renderer.resolution, true); - this.renderer.stencil.setMaskStack(this.defaultMaskStack); - } - - this.destinationFrame.copyFrom(destinationFrame); - } - - /** - * Erases the render texture and fills the drawing area with a colour - * - * @param {number} [clearColor] - The colour - * @return {PIXI.Renderer} Returns itself. - */ - clear(clearColor) - { - if (this.renderTexture) - { - clearColor = clearColor || this.renderTexture.baseTexture.clearColor; - } - else - { - clearColor = clearColor || this.clearColor; - } - - this.renderer.framebuffer.clear(clearColor[0], clearColor[1], clearColor[2], clearColor[3]); - } - - resize()// screenWidth, screenHeight) - { - // resize the root only! - this.bind(null); - } -} diff --git a/packages/core/src/renderers/systems/StateSystem.js b/packages/core/src/renderers/systems/StateSystem.js deleted file mode 100755 index d7f4caf..0000000 --- a/packages/core/src/renderers/systems/StateSystem.js +++ /dev/null @@ -1,288 +0,0 @@ -import mapWebGLBlendModesToPixi from '../utils/mapWebGLBlendModesToPixi'; -import WebGLSystem from './WebGLSystem'; -import WebGLState from '../State'; - -const BLEND = 0; -const OFFSET = 1; -const CULLING = 2; -const DEPTH_TEST = 3; -const WINDING = 4; - -/** - * A WebGL state machines - * - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class StateSystem extends WebGLSystem -{ - /** - * @param {WebGLRenderingContext} gl - The current WebGL rendering context - */ - constructor(renderer) - { - super(renderer); - - this.gl = null; - - this.maxAttribs = null; - - // check we have vao.. - this.nativeVaoExtension = null; - - this.attribState = null; - - this.stateId = 0; - this.polygonOffset = 0; - this.blendMode = 17; - - this.map = []; - - // map functions for when we set state.. - this.map[BLEND] = this.setBlend; - this.map[OFFSET] = this.setOffset; - this.map[CULLING] = this.setCullFace; - this.map[DEPTH_TEST] = this.setDepthTest; - this.map[WINDING] = this.setFrontFace; - - this.checks = []; - - this.defaultState = new WebGLState(); - this.defaultState.blend = true; - this.defaultState.depth = true; - } - - contextChange(gl) - { - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; - - this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); - - // check we have vao.. - this.nativeVaoExtension = ( - gl.getExtension('OES_vertex_array_object') - || gl.getExtension('MOZ_OES_vertex_array_object') - || gl.getExtension('WEBKIT_OES_vertex_array_object') - ); - - this.attribState = { - tempAttribState: new Array(this.maxAttribs), - attribState: new Array(this.maxAttribs), - }; - - this.blendModes = mapWebGLBlendModesToPixi(gl); - - this.setState(this.defaultState); - - this.reset(); - } - - /** - * Sets the current state - * - * @param {*} state - The state to set. - */ - setState(state) - { - state = state || this.defaultState; - - // TODO maybe to an object check? ( this.state === state )? - if (this.stateId !== state.data) - { - let diff = this.stateId ^ state.data; - let i = 0; - - // order from least to most common - while (diff) - { - if (diff & 1) - { - // state change! - this.map[i].call(this, !!(state.data & (1 << i))); - } - - diff = diff >> 1; - i++; - } - - this.stateId = state.data; - } - - // based on the above settings we check for specific modes.. - // for example if blend is active we check and set the blend modes - // or of polygon offset is active we check the poly depth. - for (let i = 0; i < this.checks.length; i++) - { - this.checks[i](this, state); - } - } - - /** - * Enables or disabled blending. - * - * @param {boolean} value - Turn on or off webgl blending. - */ - setBlend(value) - { - this.updateCheck(StateSystem.checkBlendMode, value); - - this.gl[value ? 'enable' : 'disable'](this.gl.BLEND); - } - - /** - * Enables or disable polygon offset fill - * - * @param {boolean} value - Turn on or off webgl polygon offset testing. - */ - setOffset(value) - { - this.gl[value ? 'enable' : 'disable'](this.gl.POLYGON_OFFSET_FILL); - } - - /** - * Sets whether to enable or disable depth test. - * - * @param {boolean} value - Turn on or off webgl depth testing. - */ - setDepthTest(value) - { - this.gl[value ? 'enable' : 'disable'](this.gl.DEPTH_TEST); - } - - /** - * Sets whether to enable or disable cull face. - * - * @param {boolean} value - Turn on or off webgl cull face. - */ - setCullFace(value) - { - this.gl[value ? 'enable' : 'disable'](this.gl.CULL_FACE); - } - - /** - * Sets the gl front face. - * - * @param {boolean} value - true is clockwise and false is counter-clockwise - */ - setFrontFace(value) - { - this.gl.frontFace(this.gl[value ? 'CW' : 'CCW']); - } - - /** - * Sets the blend mode. - * - * @param {number} value - The blend mode to set to. - */ - setBlendMode(value) - { - if (value === this.blendMode) - { - return; - } - - this.blendMode = value; - this.gl.blendFunc(this.blendModes[value][0], this.blendModes[value][1]); - } - - /** - * Sets the polygon offset. - * - * @param {number} value - the polygon offset - * @param {number} scale - the polygon offset scale - */ - setPolygonOffset(value, scale) - { - this.gl.polygonOffset(value, scale); - } - - /** - * Disables all the vaos in use - * - */ - resetAttributes() - { - for (let i = 0; i < this.attribState.tempAttribState.length; i++) - { - this.attribState.tempAttribState[i] = 0; - } - - for (let i = 0; i < this.attribState.attribState.length; i++) - { - this.attribState.attribState[i] = 0; - } - - // im going to assume one is always active for performance reasons. - for (let i = 1; i < this.maxAttribs; i++) - { - this.gl.disableVertexAttribArray(i); - } - } - - // used - /** - * Resets all the logic and disables the vaos - */ - reset() - { - // unbind any VAO if they exist.. - if (this.nativeVaoExtension) - { - this.nativeVaoExtension.bindVertexArrayOES(null); - } - - // reset all attributes.. - this.resetAttributes(); - - this.gl.pixelStorei(this.gl.UNPACK_FLIP_Y_WEBGL, false); - - this.setBlendMode(0); - - // TODO? - // this.setState(this.defaultState); - } - - /** - * checks to see which updates should be checked based on which settings have been activated - * for example if blend is enabled then we shold check the blend modes each time the state is changed - * or if poygon fill is activated then we need to check if the polygone offset changes. - * The idea is that we only check what we have too. - * - * @param {Function} func the checking function to add or remove - * @param {boolean} value should the check function be added or removed. - */ - updateCheck(func, value) - { - const index = this.checks.indexOf(func); - - if (value && index === -1) - { - this.checks.push(func); - } - else if (!value && index !== -1) - { - this.checks.splice(index, 1); - } - } - - /** - * A private little wrapper function that we call to check the blend mode. - * - * @static - * @private - * @param {PIXI.StateSystem} System the System to perform the state check on - * @param {PIXI.State} state the state that the blendMode will pulled from - */ - static checkBlendMode(system, state) - { - system.setBlendMode(state.blendMode); - } - - // TODO - add polygon offset? -} diff --git a/packages/core/src/renderers/systems/StencilSystem.js b/packages/core/src/renderers/systems/StencilSystem.js deleted file mode 100644 index 5d538cb..0000000 --- a/packages/core/src/renderers/systems/StencilSystem.js +++ /dev/null @@ -1,133 +0,0 @@ -import WebGLSystem from './WebGLSystem'; - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class StencilSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - this.stencilMaskStack = []; - } - - /** - * Changes the mask stack that is used by this System. - * - * @param {PIXI.Graphics[]} stencilMaskStack - The mask stack - */ - setMaskStack(stencilMaskStack) - { - const gl = this.renderer.gl; - - if (stencilMaskStack.length !== this.stencilMaskStack.length) - { - if (stencilMaskStack.length === 0) - { - gl.disable(gl.STENCIL_TEST); - } - else - { - gl.enable(gl.STENCIL_TEST); - } - } - - this.stencilMaskStack = stencilMaskStack; - } - - /** - * Applies the Mask and adds it to the current stencil stack. @alvin - * - * @param {PIXI.Graphics} graphics - The mask - */ - pushStencil(graphics) - { - this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); - - // this.renderer._activeRenderTarget.attachStencilBuffer(); - - const gl = this.renderer.gl; - const prevMaskCount = this.stencilMaskStack.length; - - if (prevMaskCount === 0) - { - gl.enable(gl.STENCIL_TEST); - } - - this.stencilMaskStack.push(graphics); - - // Increment the refference stencil value where the new mask overlaps with the old ones. - gl.colorMask(false, false, false, false); - gl.stencilFunc(gl.EQUAL, prevMaskCount, this._getBitwiseMask()); - gl.stencilOp(gl.KEEP, gl.KEEP, gl.INCR); - this.renderer.plugins.graphics.render(graphics); - - this._useCurrent(); - } - - /** - * Removes the last mask from the stencil stack. @alvin - */ - popStencil() - { - this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); - - const gl = this.renderer.gl; - const graphics = this.stencilMaskStack.pop(); - - if (this.stencilMaskStack.length === 0) - { - // the stack is empty! - gl.disable(gl.STENCIL_TEST); - gl.clear(gl.STENCIL_BUFFER_BIT); - gl.clearStencil(0); - } - else - { - // Decrement the refference stencil value where the popped mask overlaps with the other ones - gl.colorMask(false, false, false, false); - gl.stencilOp(gl.KEEP, gl.KEEP, gl.DECR); - this.renderer.plugins.graphics.render(graphics); - - this._useCurrent(); - } - } - - /** - * Setup renderer to use the current stencil data. - */ - _useCurrent() - { - const gl = this.renderer.gl; - - gl.colorMask(true, true, true, true); - gl.stencilFunc(gl.EQUAL, this.stencilMaskStack.length, this._getBitwiseMask()); - gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP); - } - - /** - * Fill 1s equal to the number of acitve stencil masks. - * - * @return {number} The bitwise mask. - */ - _getBitwiseMask() - { - return (1 << this.stencilMaskStack.length) - 1; - } - - /** - * Destroys the mask stack. - * - */ - destroy() - { - super.destroy(this); - - this.stencilMaskStack = null; - } -} diff --git a/packages/core/src/renderers/systems/WebGLSystem.js b/packages/core/src/renderers/systems/WebGLSystem.js deleted file mode 100644 index d3d5b31..0000000 --- a/packages/core/src/renderers/systems/WebGLSystem.js +++ /dev/null @@ -1,41 +0,0 @@ -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this manager works for. - */ - constructor(renderer) - { - /** - * The renderer this manager works for. - * - * @member {PIXI.Renderer} - */ - this.renderer = renderer; - - this.renderer.runners.contextChange.add(this); - } - - /** - * Generic method called when there is a WebGL context change. - * - */ - contextChange() - { - // do some codes init! - } - - /** - * Generic destroy methods to be overridden by the subclass - * - */ - destroy() - { - this.renderer.runners.contextChange.remove(this); - this.renderer = null; - } -} diff --git a/packages/core/src/renderers/systems/filter/FilterSystem.js b/packages/core/src/renderers/systems/filter/FilterSystem.js deleted file mode 100644 index e615037..0000000 --- a/packages/core/src/renderers/systems/filter/FilterSystem.js +++ /dev/null @@ -1,431 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; - -import RenderTexture from '../../../textures/RenderTexture'; -import Quad from '../../utils/Quad'; -import QuadUv from '../../utils/QuadUv'; -import { Rectangle } from '@pixi/math'; -import * as filterTransforms from '../../filters/filterTransforms'; -import bitTwiddle from 'bit-twiddle'; -import UniformGroup from '../../../shader/UniformGroup'; -import { DRAW_MODES } from '../../../../../constants'; - -// -/** - * @ignore - * @class - */ -class FilterState -{ - /** - * - */ - constructor() - { - this.renderTexture = null; - this.sourceFrame = new Rectangle(); - this.destinationFrame = new Rectangle(); - this.filters = []; - this.target = null; - this.legacy = false; - this.resolution = 1; - } -} - -/** - * @class - * @memberof PIXI.systems - * @extends PIXI.systems.WebGLSystem - */ -export default class FilterSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - /** - * stores a bunch of PO2 textures used for filtering - * @type {Object} - */ - this.texturePool = {}; - - /** - * a pool for storing filter states, save us creating new ones each tick - * @type {Array} - */ - this.statePool = []; - - /** - * A very simple geometry used when drawing a filter effect to the screen - * @type {Quad} - */ - this.quad = new Quad(); - - this.quadUv = new QuadUv(); - - /** - * Temporary rect for maths - * @type {PIXI.Rectangle} - */ - this.tempRect = new Rectangle(); - - this.activeState = {}; - - /** - * this uniform group is attached to filter uniforms when used - * @type {UniformGroup} - */ - this.globalUniforms = new UniformGroup({ - sourceFrame: this.tempRect, - destinationFrame: this.tempRect, - - // legacy variables - filterArea: new Float32Array(4), - filterClamp: new Float32Array(4), - }, true); - } - - /** - * Adds a new filter to the System. - * - * @param {PIXI.DisplayObject} target - The target of the filter to render. - * @param {PIXI.Filter[]} filters - The filters to apply. - */ - push(target, filters) - { - const renderer = this.renderer; - const filterStack = this.renderer.renderTexture.defaultFilterStack; - const state = this.statePool.pop() || new FilterState(); - - let resolution = filters[0].resolution; - let padding = filters[0].padding; - let autoFit = filters[0].autoFit; - let legacy = filters[0].legacy; - - for (let i = 1; i < filters.length; i++) - { - const filter = filters[i]; - - // lets use the lowest resolution.. - resolution = Math.min(resolution, filter.resolution); - // and the largest amount of padding! - padding = Math.max(padding, filter.padding); - // only auto fit if all filters are autofit - autoFit = autoFit || filter.autoFit; - - legacy = legacy || filter.legacy; - } - - filterStack.push(state); - - state.resolution = resolution; - - state.legacy = legacy; - - // round to whole number based on resolution - // TODO move that to the shader too? - state.sourceFrame = target.filterArea || target.getBounds(true); - - state.sourceFrame.pad(padding); - - if (autoFit) - { - state.sourceFrame.fit(this.renderer.renderTexture.destinationFrame); - } - - state.sourceFrame.round(resolution); - - state.renderTexture = this.getPotFilterTexture(state.sourceFrame.width, state.sourceFrame.height, resolution); - state.filters = filters; - - state.destinationFrame.width = state.renderTexture.width; - state.destinationFrame.height = state.renderTexture.height; - - state.renderTexture.filterFrame = state.sourceFrame; - renderer.renderTexture.bind(state.renderTexture, state.sourceFrame);// /, state.destinationFrame); - renderer.renderTexture.clear(); - } - - /** - * Pops off the filter and applies it. - * - */ - pop() - { - const renderer = this.renderer; - const filterStack = renderer.renderTexture.defaultFilterStack; - const state = filterStack.pop(); - const filters = state.filters; - - this.activeState = state; - - const globalUniforms = this.globalUniforms.uniforms; - - globalUniforms.sourceFrame = state.sourceFrame; - globalUniforms.destinationFrame = state.destinationFrame; - globalUniforms.resolution = state.resolution; - - // only update the rect if its legacy.. - if (state.legacy) - { - const filterArea = globalUniforms.filterArea; - const filterClamp = globalUniforms.filterClamp; - - filterArea[0] = state.destinationFrame.width; - filterArea[1] = state.destinationFrame.height; - filterArea[2] = state.sourceFrame.x; - filterArea[3] = state.sourceFrame.y; - - filterClamp[0] = 0.5 / state.resolution / state.destinationFrame.width; - filterClamp[1] = 0.5 / state.resolution / state.destinationFrame.height; - filterClamp[2] = (state.sourceFrame.width - 0.5) / state.resolution / state.destinationFrame.width; - filterClamp[3] = (state.sourceFrame.height - 0.5) / state.resolution / state.destinationFrame.height; - } - - this.globalUniforms.update(); - - const lastState = filterStack[filterStack.length - 1]; - - if (filters.length === 1) - { - filters[0].apply(this, state.renderTexture, lastState.renderTexture, false, state); - - this.returnFilterTexture(state.renderTexture); - } - else - { - let flip = state.renderTexture; - let flop = this.getPotFilterTexture( - flip.width, - flip.height, - state.resolution - ); - - flop.filterFrame = flip.filterFrame; - - let i = 0; - - for (i = 0; i < filters.length - 1; ++i) - { - filters[i].apply(this, flip, flop, true, state); - - const t = flip; - - flip = flop; - flop = t; - } - - filters[i].apply(this, flip, lastState.renderTexture, false, state); - - this.returnFilterTexture(flip); - this.returnFilterTexture(flop); - } - - this.statePool.push(state); - } - - /** - * Draws a filter. - * - * @param {PIXI.Filter} filter - The filter to draw. - * @param {PIXI.RenderTarget} input - The input render target. - * @param {PIXI.RenderTarget} output - The target to output to. - * @param {boolean} clear - Should the output be cleared before rendering to it - */ - applyFilter(filter, input, output, clear) - { - const renderer = this.renderer; - - renderer.renderTexture.bind(output, output ? output.filterFrame : null); - - if (clear) - { - // gl.disable(gl.SCISSOR_TEST); - renderer.renderTexture.clear(); - // gl.enable(gl.SCISSOR_TEST); - } - - // set the uniforms.. - filter.uniforms.uSampler = input; - filter.uniforms.filterGlobals = this.globalUniforms; - - // TODO make it so that the order of this does not matter.. - // because it does at the moment cos of global uniforms. - // they need to get resynced - - renderer.state.setState(filter.state); - renderer.shader.bind(filter); - - if (filter.legacy) - { - this.quadUv.map(input._frame, input.filterFrame); - - renderer.geometry.bind(this.quadUv); - renderer.geometry.draw(DRAW_MODES.TRIANGLES); - } - else - { - renderer.geometry.bind(this.quad); - renderer.geometry.draw(DRAW_MODES.TRIANGLE_STRIP); - } - } - - /** - * Calculates the mapped matrix. - * - * TODO playing around here.. this is temporary - (will end up in the shader) - * this returns a matrix that will normalise map filter cords in the filter to screen space - * - * @param {PIXI.Matrix} outputMatrix - the matrix to output to. - * @return {PIXI.Matrix} The mapped matrix. - */ - calculateScreenSpaceMatrix(outputMatrix) - { - const currentState = this.activeState; - - return filterTransforms.calculateScreenSpaceMatrix( - outputMatrix, - currentState.sourceFrame, - currentState.destinationFrame - ); - } - - /** - * This will map the filter coord so that a texture can be used based on the transform of a sprite - * - * @param {PIXI.Matrix} outputMatrix - The matrix to output to. - * @param {PIXI.Sprite} sprite - The sprite to map to. - * @return {PIXI.Matrix} The mapped matrix. - */ - calculateSpriteMatrix(outputMatrix, sprite) - { - const currentState = this.activeState; - - return filterTransforms.calculateSpriteMatrix( - outputMatrix, - currentState.sourceFrame, - currentState.destinationFrame, - sprite - ); - } - - /** - * Destroys this Filter System. - * - * @param {boolean} [contextLost=false] context was lost, do not free shaders - * - */ - destroy(contextLost = false) - { - if (!contextLost) - { - this.emptyPool(); - } - else - { - this.texturePool = {}; - } - } - - /** - * Gets a Power-of-Two render texture. - * - * TODO move to a seperate class could be on renderer? - * also - could cause issue with multiple contexts? - * - * @private - * @param {WebGLRenderingContext} gl - The webgl rendering context - * @param {number} minWidth - The minimum width of the render target. - * @param {number} minHeight - The minimum height of the render target. - * @param {number} resolution - The resolution of the render target. - * @return {PIXI.RenderTarget} The new render target. - */ - getPotFilterTexture(minWidth, minHeight, resolution) - { - minWidth = bitTwiddle.nextPow2(minWidth); - minHeight = bitTwiddle.nextPow2(minHeight); - resolution = resolution || 1; - - const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); - - if (!this.texturePool[key]) - { - this.texturePool[key] = []; - } - - let renderTexture = this.texturePool[key].pop(); - - if (!renderTexture) - { - // temporary bypass cache.. - // internally - this will cause a texture to be bound.. - renderTexture = RenderTexture.create({ - width: minWidth, - height: minHeight, - resolution, - }); - } - - return renderTexture; - } - - /** - * Gets extra render texture to use inside current filter - * - * @param {number} resolution resolution of the renderTexture - * @returns {PIXI.RenderTexture} - */ - getFilterTexture(resolution) - { - const rt = this.activeState.renderTexture; - - const filterTexture = this.getPotFilterTexture(rt.width, rt.height, resolution || rt.baseTexture.resolution); - - filterTexture.filterFrame = rt.filterFrame; - - return filterTexture; - } - - /** - * Frees a render texture back into the pool. - * - * @param {PIXI.RenderTarget} renderTexture - The renderTarget to free - */ - returnFilterTexture(renderTexture) - { - renderTexture.filterFrame = null; - - const base = renderTexture.baseTexture; - - const minWidth = base.width; - const minHeight = base.height; - - const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); - - this.texturePool[key].push(renderTexture); - } - - /** - * Empties the texture pool. - * - */ - emptyPool() - { - for (const i in this.texturePool) - { - const textures = this.texturePool[i]; - - if (textures) - { - for (let j = 0; j < textures.length; j++) - { - textures[j].destroy(true); - } - } - } - - this.texturePool = {}; - } -} diff --git a/packages/core/src/AbstractRenderer.js b/packages/core/src/AbstractRenderer.js new file mode 100644 index 0000000..64b2eb1 --- /dev/null +++ b/packages/core/src/AbstractRenderer.js @@ -0,0 +1,353 @@ +import { hex2string, hex2rgb } from '@pixi/utils'; +import { Matrix, Rectangle } from '@pixi/math'; +import { RENDERER_TYPE } from '@pixi/constants'; +import { settings } from '@pixi/settings'; +import { Container } from '@pixi/display'; +import RenderTexture from './renderTexture/RenderTexture'; +import EventEmitter from 'eventemitter3'; + +const tempMatrix = new Matrix(); + +/** + * 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 EventEmitter + * @memberof PIXI + */ +export default class AbstractRenderer extends EventEmitter +{ + // eslint-disable-next-line valid-jsdoc + /** + * @param {string} system - The name of the system this renderer is for. + * @param {object} [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.transparent=false] - If the render view is transparent, default false + * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false + * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. The + * resolution of the renderer retina would be 2. + * @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 {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when rendering, + * stopping pixel interpolation. + */ + constructor(system, options, arg2, arg3) + { + super(); + + // Support for constructor(system, screenWidth, screenHeight, options) + if (typeof options === 'number') + { + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + }, arg3); + } + + // 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 = RENDERER_TYPE.UNKNOWN; + + /** + * Measurements of the screen. (0, 0, screenWidth, screenHeight) + * + * Its safe to use as filterArea or hitArea for 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 1 + */ + this.resolution = options.resolution || settings.RESOLUTION; + + /** + * Whether the render view is transparent + * + * @member {boolean} + */ + this.transparent = options.transparent; + + /** + * Whether css dimensions of canvas view should be resized to screen dimensions automatically + * + * @member {boolean} + */ + this.autoResize = options.autoResize || false; + + /** + * Tracks the blend modes useful for this renderer. + * + * @member {object} + */ + this.blendModes = null; + + /** + * 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; + + /** + * If true PixiJS will Math.floor() x/y values when rendering, stopping pixel interpolation. + * Handy for crisp pixel art and speed on legacy devices. + * + * @member {boolean} + */ + this.roundPixels = options.roundPixels; + + /** + * The background color as a number. + * + * @member {number} + * @private + */ + this._backgroundColor = 0x000000; + + /** + * The background color as an [R, G, B] array. + * + * @member {number[]} + * @private + */ + this._backgroundColorRgba = [0, 0, 0, 0]; + + /** + * The background color as a string. + * + * @member {string} + * @private + */ + this._backgroundColorString = '#000000'; + + this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter + + /** + * This temporary display object used as the parent of the currently being rendered item + * + * @member {PIXI.DisplayObject} + * @private + */ + this._tempDisplayObjectParent = new Container(); + + /** + * The last root object that the renderer tried to render. + * + * @member {PIXI.DisplayObject} + * @private + */ + this._lastObjectRendered = this._tempDisplayObjectParent; + + /** + * Collection of plugins. + * @readonly + * @member {object} + */ + this.plugins = {}; + } + + /** + * Initialize the plugins. + * + * @protected + * @param {object} staticMap - The dictionary of staticly saved plugins. + */ + initPlugins(staticMap) + { + 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() + { + return this.view.width; + } + + /** + * Same as view.height, actual number of pixels in the canvas by vertical + * + * @member {number} + * @readonly + * @default 600 + */ + get height() + { + return this.view.height; + } + + /** + * Resizes the screen and canvas to the specified width and height + * Canvas dimensions are multiplied by resolution + * + * @param {number} screenWidth - the new width of the screen + * @param {number} screenHeight - the new height of the screen + */ + resize(screenWidth, screenHeight) + { + this.screen.width = screenWidth; + this.screen.height = screenHeight; + + this.view.width = screenWidth * this.resolution; + this.view.height = screenHeight * this.resolution; + + if (this.autoResize) + { + this.view.style.width = `${screenWidth}px`; + this.view.style.height = `${screenHeight}px`; + } + } + + /** + * 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 {PIXI.DisplayObject} displayObject - The displayObject the object will be generated from + * @param {number} scaleMode - Should be one of the scaleMode consts + * @param {number} resolution - The resolution / device pixel ratio of the texture being generated + * @param {PIXI.Rectangle} [region] - The region of the displayObject, that shall be rendered, + * if no region is specified, defaults to the local bounds of the displayObject. + * @return {PIXI.Texture} a texture of the graphics object + */ + generateTexture(displayObject, scaleMode, resolution, region) + { + region = region || displayObject.getLocalBounds(); + + // 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(region.width | 0, region.height | 0, scaleMode, resolution); + + tempMatrix.tx = -region.x; + tempMatrix.ty = -region.y; + + this.render(displayObject, renderTexture, false, tempMatrix, true); + + return renderTexture; + } + + /** + * Removes everything from the renderer and optionally removes the Canvas DOM element. + * + * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. + */ + destroy(removeView) + { + 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); + } + + this.plugins = null; + + this.type = RENDERER_TYPE.UNKNOWN; + + this.view = null; + + this.screen = null; + + this.resolution = 0; + + this.transparent = false; + + this.autoResize = false; + + this.blendModes = null; + + this.options = null; + + this.preserveDrawingBuffer = false; + this.clearBeforeRender = false; + + this.roundPixels = false; + + this._backgroundColor = 0; + this._backgroundColorRgba = null; + this._backgroundColorString = null; + + this._tempDisplayObjectParent = null; + this._lastObjectRendered = null; + } + + /** + * The background color to fill if not transparent + * + * @member {number} + */ + get backgroundColor() + { + return this._backgroundColor; + } + + set backgroundColor(value) // eslint-disable-line require-jsdoc + { + this._backgroundColor = value; + this._backgroundColorString = hex2string(value); + hex2rgb(value, this._backgroundColorRgba); + } +} diff --git a/packages/core/src/Renderer.js b/packages/core/src/Renderer.js new file mode 100644 index 0000000..2227e78 --- /dev/null +++ b/packages/core/src/Renderer.js @@ -0,0 +1,338 @@ +import AbstractRenderer from './AbstractRenderer'; +import { sayHello } from '@pixi/utils'; +import MaskSystem from './mask/MaskSystem'; +import StencilSystem from './mask/StencilSystem'; +import FilterSystem from './filters/FilterSystem'; +import FramebufferSystem from './framebuffer/FramebufferSystem'; +import RenderTextureSystem from './renderTexture/RenderTextureSystem'; +import TextureSystem from './textures/TextureSystem'; +import ProjectionSystem from './projection/ProjectionSystem'; +import StateSystem from './state/StateSystem'; +import GeometrySystem from './geometry/GeometrySystem'; +import ShaderSystem from './shader/ShaderSystem'; +import ContextSystem from './context/ContextSystem'; +import BatchSystem from './batch/BatchSystem'; +import TextureGCSystem from './textures/TextureGCSystem'; +import { RENDERER_TYPE } from '@pixi/constants'; +import UniformGroup from './shader/UniformGroup'; +import { Matrix } from '@pixi/math'; +import Runner from 'mini-runner'; + +/** + * 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 Render works by automatically managing webGLBatchs. + * 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 :) + * + * @class + * @memberof PIXI + * @extends PIXI.AbstractRenderer + */ +export default class Renderer extends AbstractRenderer +{ + // eslint-disable-next-line valid-jsdoc + /** + * + * @param {object} [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.transparent=false] - If the render view is transparent, default false + * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false + * @param {boolean} [options.antialias=false] - sets antialias. If not available natively then FXAA + * antialiasing is used + * @param {boolean} [options.forceFXAA=false] - forces FXAA antialiasing to be used over native. + * FXAA is faster, but may not always look as great + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. + * The resolution of the renderer retina would be 2. + * @param {boolean} [options.clearBeforeRender=true] - This sets if the renderer will clear + * the canvas or not before the new render pass. If you wish to set this to false, you *must* set + * preserveDrawingBuffer to `true`. + * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, + * enable this if you need to call toDataUrl on the webgl context. + * @param {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when + * rendering, stopping pixel interpolation. + * @param {number} [options.backgroundColor=0x000000] - The background color of the rendered area + * (shown if not transparent). + * @param {boolean} [options.legacy=false] - If true PixiJS will aim to ensure compatibility + * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. + * @param {string} [options.powerPreference] - Parameter passed to webgl context, set to "high-performance" + * for devices with dual graphics card + */ + constructor(options, arg2, arg3) + { + super('WebGL', options, arg2, arg3); + + /** + * The type of this renderer as a standardised const + * + * @member {number} + * @see PIXI.RENDERER_TYPE + */ + this.type = RENDERER_TYPE.WEBGL; + + // this will be set by the contextSystem (this.context) + this.gl = null; + this.CONTEXT_UID = 0; + this.legacy = !!options.legacy; + + // TODO legacy! + + // runners! + this.runners = { + destroy: new Runner('destroy'), + contextChange: new Runner('contextChange', 1), + reset: new Runner('reset'), + update: new Runner('update'), + postrender: new Runner('postrender'), + prerender: new Runner('prerender'), + resize: new Runner('resize', 2), + }; + + 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(GeometrySystem, 'geometry') + .addSystem(FramebufferSystem, 'framebuffer') + .addSystem(StencilSystem, 'stencil') + .addSystem(ProjectionSystem, 'projection') + .addSystem(TextureGCSystem, 'textureGC') + .addSystem(FilterSystem, 'filter') + .addSystem(RenderTextureSystem, 'renderTexture') + .addSystem(BatchSystem, 'batch'); + + this.initPlugins(Renderer.__plugins); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ + if (options.context) + { + this.context.initFromContext(options.context); + } + else + { + this.context.initFromOptions({ + alpha: this.transparent, + antialias: options.antialias, + premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', + stencil: true, + 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); + } + + /** + * Add a new system to the renderer. + * @param {class} ClassRef - Class reference + * @param {string} [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. + * @return {PIXI.Renderer} Return instance of renderer + */ + addSystem(ClassRef, name) + { + if (!name) + { + name = ClassRef.name; + } + + // TODO - read name from class.name.. + + /* + if(name.includes('System')) + { + name = name.replace('System', ''); + name = name.charAt(0).toLowerCase() + name.slice(1); + } + */ + + const system = new ClassRef(this); + + if (this[name]) + { + throw new Error(`Whoops! ${name} is already a manger`); + } + + this[name] = system; + + for (const i in this.runners) + { + this.runners[i].add(system); + } + + return this; + + /** + * 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. + */ + } + + /** + * Renders the object to its webGL view + * + * @param {PIXI.DisplayObject} displayObject - the object to be rendered + * @param {PIXI.RenderTexture} renderTexture - The render texture to render to. + * @param {boolean} [clear] - Should the canvas be cleared before the new render + * @param {PIXI.Transform} [transform] - A transform to apply to the render texture before rendering. + * @param {boolean} [skipUpdateTransform] - Should we skip the update transform pass? + */ + render(displayObject, renderTexture, clear, transform, skipUpdateTransform) + { + // can be handy to know! + this.renderingToScreen = !renderTexture; + + this.runners.prerender.run(); + this.emit('prerender'); + + // 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.parent; + + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = 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.run(); + + this.emit('postrender'); + } + + /** + * Resizes the webGL view to the specified width and height. + * + * @param {number} screenWidth - the new width of the screen + * @param {number} screenHeight - the new height of the screen + */ + resize(screenWidth, screenHeight) + { + AbstractRenderer.prototype.resize.call(this, screenWidth, screenHeight); + + this.runners.resize.run(screenWidth, screenHeight); + } + + /** + * Resets the WebGL state so you can render things however you fancy! + * + * @return {PIXI.Renderer} Returns itself. + */ + reset() + { + this.runners.reset.run(); + + return this; + } + + clear() + { + this.framebuffer.bind(); + this.framebuffer.clear(); + } + + /** + * Removes everything from the renderer (event listeners, spritebatch, etc...) + * + * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. + * See: https://github.com/pixijs/pixi.js/issues/2233 + */ + destroy(removeView) + { + this.runners.destroy.run(); + + // call base destroy + super.destroy(removeView); + + // TODO nullify all the managers.. + this.gl = null; + } + + /** + * 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 + * builds and excluding plugins. + * @name PIXI.Renderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. + */ + + /** + * Adds a plugin to the renderer. + * + * @method + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + static registerPlugin(pluginName, ctor) + { + Renderer.__plugins = Renderer.__plugins || {}; + Renderer.__plugins[pluginName] = ctor; + } +} diff --git a/packages/core/src/System.js b/packages/core/src/System.js new file mode 100644 index 0000000..05a10c8 --- /dev/null +++ b/packages/core/src/System.js @@ -0,0 +1,42 @@ +/** + * System is a base class used for extending systems used by the {@link PIXI.Renderer} + * @see PIXI.Renderer#addSystem + * @class + * @memberof PIXI + */ +export default class System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + /** + * The renderer this manager works for. + * + * @member {PIXI.Renderer} + */ + this.renderer = renderer; + + this.renderer.runners.contextChange.add(this); + } + + /** + * Generic method called when there is a WebGL context change. + * + */ + contextChange() + { + // do some codes init! + } + + /** + * Generic destroy methods to be overridden by the subclass + * + */ + destroy() + { + this.renderer.runners.contextChange.remove(this); + this.renderer = null; + } +} diff --git a/packages/core/src/batch/BatchSystem.js b/packages/core/src/batch/BatchSystem.js new file mode 100644 index 0000000..b04c249 --- /dev/null +++ b/packages/core/src/batch/BatchSystem.js @@ -0,0 +1,68 @@ +import System from '../System'; +import ObjectRenderer from './ObjectRenderer'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class BatchSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + super(renderer); + + /** + * An empty renderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.emptyRenderer = new ObjectRenderer(renderer); + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.currentRenderer = this.emptyRenderer; + } + + /** + * Changes the current renderer to the one given in parameter + * + * @param {PIXI.ObjectRenderer} objectRenderer - The object renderer to use. + */ + setObjectRenderer(objectRenderer) + { + if (this.currentRenderer === objectRenderer) + { + return; + } + + this.currentRenderer.stop(); + this.currentRenderer = objectRenderer; + + this.renderer.state.setState(objectRenderer.state); + + this.currentRenderer.start(); + } + + /** + * This should be called if you wish to do some custom rendering + * It will basically render anything that may be batched up such as sprites + * + */ + flush() + { + this.setObjectRenderer(this.emptyRenderer); + } + + reset() + { + this.setObjectRenderer(this.emptyRenderer); + } +} diff --git a/packages/core/src/batch/ObjectRenderer.js b/packages/core/src/batch/ObjectRenderer.js new file mode 100644 index 0000000..0c851a9 --- /dev/null +++ b/packages/core/src/batch/ObjectRenderer.js @@ -0,0 +1,48 @@ +import System from '../System'; + +/** + * Base for a common object renderer that can be used as a system renderer plugin. + * + * @class + * @extends PIXI.System + * @memberof PIXI + */ +export default class ObjectRenderer extends System +{ + /** + * Starts the renderer and sets the shader + * + */ + start() + { + // set the shader.. + } + + /** + * Stops the renderer + * + */ + stop() + { + this.flush(); + } + + /** + * Stub method for rendering content and emptying the current batch. + * + */ + flush() + { + // flush! + } + + /** + * Renders an object + * + * @param {PIXI.DisplayObject} object - The object to render. + */ + render(object) // eslint-disable-line no-unused-vars + { + // render the object + } +} diff --git a/packages/core/src/context/ContextSystem.js b/packages/core/src/context/ContextSystem.js new file mode 100644 index 0000000..301aab4 --- /dev/null +++ b/packages/core/src/context/ContextSystem.js @@ -0,0 +1,186 @@ +import System from '../System'; +import { settings } from '@pixi/settings'; + +let CONTEXT_UID = 0; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class ContextSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.webGLVersion = 1; + + this.handleContextLost = this.handleContextLost.bind(this); + this.handleContextRestored = this.handleContextRestored.bind(this); + + this.extensions = {}; + + renderer.view.addEventListener('webglcontextlost', this.handleContextLost, false); + renderer.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + } + + get isLost() + { + return (!this.gl || this.gl.isContextLost()); + } + + contextChange(gl) + { + this.gl = gl; + + // restore a context if it was previously lost + if (gl.isContextLost() && gl.getExtension('WEBGL_lose_context')) + { + gl.getExtension('WEBGL_lose_context').restoreContext(); + } + } + + initFromContext(gl) + { + this.gl = gl; + this.validateContext(gl); + this.renderer.gl = gl; + this.renderer.CONTEXT_UID = CONTEXT_UID++; + this.renderer.runners.contextChange.run(gl); + } + + initFromOptions(options) + { + const gl = this.createContext(this.renderer.view, options); + + this.initFromContext(gl); + } + + /** + * Helper class to create a webGL Context + * + * @class + * @memberof PIXI.glCore + * @param canvas {HTMLCanvasElement} the canvas element that we will get the context from + * @param options {Object} An options object that gets passed in to the canvas element containing the context attributes, + * see https://developer.mozilla.org/en/docs/Web/API/HTMLCanvasElement/getContext for the options available + * @return {WebGLRenderingContext} the WebGL context + */ + createContext(canvas, options) + { + let gl; + + if (settings.PREFER_WEBGL_2) + { + gl = canvas.getContext('webgl2', options); + } + + if (gl) + { + this.webGLVersion = 2; + } + else + { + this.webGLVersion = 1; + + gl = canvas.getContext('webgl', options) + || canvas.getContext('experimental-webgl', options); + + if (!gl) + { + // fail, not able to get a context + throw new Error('This browser does not support webGL. Try using the canvas renderer'); + } + } + + this.gl = gl; + + this.getExtensions(); + + return gl; + } + + getExtensions() + { + // time to set up default etensions that pixi uses.. + const gl = this.gl; + const extensions = this.extensions; + + if (this.webGLVersion === 1) + { + extensions.drawBuffers = gl.getExtension('WEBGL_draw_buffers'); + extensions.depthTexture = gl.getExtension('WEBKIT_WEBGL_depth_texture'); + extensions.floatTexture = gl.getExtension('OES_texture_float'); + extensions.loseContext = gl.getExtension('WEBGL_lose_context'); + + extensions.vertexArrayObject = gl.getExtension('OES_vertex_array_object') + || gl.getExtension('MOZ_OES_vertex_array_object') + || gl.getExtension('WEBKIT_OES_vertex_array_object'); + } + + // we don't use any specific WebGL 2 ones yet! + } + + /** + * Handles a lost webgl context + * + * @private + * @param {WebGLContextEvent} event - The context lost event. + */ + handleContextLost(event) + { + event.preventDefault(); + } + + /** + * Handles a restored webgl context + * + * @private + */ + handleContextRestored() + { + this.renderer.runners.contextChange.run(this.gl); + } + + destroy() + { + const view = this.renderer.view; + + // remove listeners + view.removeEventListener('webglcontextlost', this.handleContextLost); + view.removeEventListener('webglcontextrestored', this.handleContextRestored); + + this.gl.useProgram(null); + + if (this.extensions.loseContext) + { + this.extensions.loseContext.loseContext(); + } + } + + postrender() + { + this.gl.flush(); + } + + validateContext(gl) + { + const attributes = gl.getContextAttributes(); + + // this is going to be fairly simple for now.. but at least we have room to grow! + if (!attributes.stencil) + { + /* eslint-disable max-len */ + + /* eslint-disable no-console */ + console.warn('Provided WebGL context does not have a stencil buffer, masks may not render correctly'); + /* eslint-enable no-console */ + + /* eslint-enable max-len */ + } + } +} diff --git a/packages/core/src/filters/Filter.js b/packages/core/src/filters/Filter.js new file mode 100644 index 0000000..9d8cad8 --- /dev/null +++ b/packages/core/src/filters/Filter.js @@ -0,0 +1,140 @@ +import Shader from '../shader/Shader'; +import Program from '../shader/Program'; +import State from '../state/State'; +import { settings } from '@pixi/settings'; +// import extractUniformsFromSrc from './extractUniformsFromSrc'; +import defaultVertex from './defaultFilter.vert'; +import defaultFragment from './defaultFilter.frag'; + +// let math = require('../../math'); +/** + * @class + * @memberof PIXI + * @extends PIXI.Shader + */ +export default class Filter extends Shader +{ + /** + * @param {string} [vertexSrc] - The source of the vertex shader. + * @param {string} [fragmentSrc] - The source of the fragment shader. + * @param {object} [uniforms] - Custom uniforms to use to augment the built-in ones. + */ + constructor(vertexSrc, fragmentSrc, uniforms) + { + const program = Program.from(vertexSrc, fragmentSrc); + + super(program, uniforms); + + /** + * The padding of the filter. Some filters require extra space to breath such as a blur. + * Increasing this will add extra width and height to the bounds of the object that the + * filter is applied to. + * + * @member {number} + */ + this.padding = 0; + + /** + * The resolution of the filter. Setting this to be lower will lower the quality but + * increase the performance of the filter. + * + * @member {number} + */ + this.resolution = settings.RESOLUTION; + + /** + * If enabled is true the filter is applied, if false it will not. + * + * @member {boolean} + */ + this.enabled = true; + + /** + * If enabled, PixiJS will fit the filter area into boundaries for better performance. + * Switch it off if it does not work for specific shader. + * + * @member {boolean} + */ + this.autoFit = true; + + /** + * Legacy filters use position and uvs from attributes + * @member {boolean} + * @readonly + */ + this.legacy = !!this.program.attributeData.aTextureCoord; + + /** + * the webGL state the filter requires to render + * @member {PIXI.State} + */ + this.state = new State(); + } + + /** + * Applies the filter + * + * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + * @param {boolean} clear - Should the output be cleared before rendering to it + * @param {object} [currentState] - It's current state of filter. + * There are some useful properties in the currentState : + * target, filters, sourceFrame, destinationFrame, renderTarget, resolution + */ + apply(filterManager, input, output, clear, currentState, derp) // eslint-disable-line no-unused-vars + { + // do as you please! + + filterManager.applyFilter(this, input, output, clear, currentState, derp); + + // or just do a regular render.. + } + + /** + * Sets the blendmode of the filter + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL + */ + get blendMode() + { + return this.state.blendMode; + } + + set blendMode(value) // eslint-disable-line require-jsdoc + { + this.state.blendMode = value; + } + + /** + * The default vertex shader source + * + * @static + * @constant + */ + static get defaultVertexSrc() + { + return defaultVertex; + } + + /** + * The default fragment shader source + * + * @static + * @constant + */ + static get defaultFragmentSrc() + { + return defaultFragment; + } +} + +/** + * Used for caching shader IDs + * + * @static + * @private + */ +Filter.SOURCE_KEY_MAP = {}; + diff --git a/packages/core/src/filters/FilterSystem.js b/packages/core/src/filters/FilterSystem.js new file mode 100644 index 0000000..3174318 --- /dev/null +++ b/packages/core/src/filters/FilterSystem.js @@ -0,0 +1,431 @@ +import System from '../System'; + +import RenderTexture from '../renderTexture/RenderTexture'; +import Quad from '../utils/Quad'; +import QuadUv from '../utils/QuadUv'; +import { Rectangle } from '@pixi/math'; +import * as filterTransforms from './filterTransforms'; +import bitTwiddle from 'bit-twiddle'; +import UniformGroup from '../shader/UniformGroup'; +import { DRAW_MODES } from '@pixi/constants'; + +// +/** + * @ignore + * @class + */ +class FilterState +{ + /** + * + */ + constructor() + { + this.renderTexture = null; + this.sourceFrame = new Rectangle(); + this.destinationFrame = new Rectangle(); + this.filters = []; + this.target = null; + this.legacy = false; + this.resolution = 1; + } +} + +/** + * @class + * @memberof PIXI.systems + * @extends PIXI.System + */ +export default class FilterSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + /** + * stores a bunch of PO2 textures used for filtering + * @type {Object} + */ + this.texturePool = {}; + + /** + * a pool for storing filter states, save us creating new ones each tick + * @type {Array} + */ + this.statePool = []; + + /** + * A very simple geometry used when drawing a filter effect to the screen + * @type {Quad} + */ + this.quad = new Quad(); + + this.quadUv = new QuadUv(); + + /** + * Temporary rect for maths + * @type {PIXI.Rectangle} + */ + this.tempRect = new Rectangle(); + + this.activeState = {}; + + /** + * this uniform group is attached to filter uniforms when used + * @type {UniformGroup} + */ + this.globalUniforms = new UniformGroup({ + sourceFrame: this.tempRect, + destinationFrame: this.tempRect, + + // legacy variables + filterArea: new Float32Array(4), + filterClamp: new Float32Array(4), + }, true); + } + + /** + * Adds a new filter to the System. + * + * @param {PIXI.DisplayObject} target - The target of the filter to render. + * @param {PIXI.Filter[]} filters - The filters to apply. + */ + push(target, filters) + { + const renderer = this.renderer; + const filterStack = this.renderer.renderTexture.defaultFilterStack; + const state = this.statePool.pop() || new FilterState(); + + let resolution = filters[0].resolution; + let padding = filters[0].padding; + let autoFit = filters[0].autoFit; + let legacy = filters[0].legacy; + + for (let i = 1; i < filters.length; i++) + { + const filter = filters[i]; + + // lets use the lowest resolution.. + resolution = Math.min(resolution, filter.resolution); + // and the largest amount of padding! + padding = Math.max(padding, filter.padding); + // only auto fit if all filters are autofit + autoFit = autoFit || filter.autoFit; + + legacy = legacy || filter.legacy; + } + + filterStack.push(state); + + state.resolution = resolution; + + state.legacy = legacy; + + // round to whole number based on resolution + // TODO move that to the shader too? + state.sourceFrame = target.filterArea || target.getBounds(true); + + state.sourceFrame.pad(padding); + + if (autoFit) + { + state.sourceFrame.fit(this.renderer.renderTexture.destinationFrame); + } + + state.sourceFrame.round(resolution); + + state.renderTexture = this.getPotFilterTexture(state.sourceFrame.width, state.sourceFrame.height, resolution); + state.filters = filters; + + state.destinationFrame.width = state.renderTexture.width; + state.destinationFrame.height = state.renderTexture.height; + + state.renderTexture.filterFrame = state.sourceFrame; + renderer.renderTexture.bind(state.renderTexture, state.sourceFrame);// /, state.destinationFrame); + renderer.renderTexture.clear(); + } + + /** + * Pops off the filter and applies it. + * + */ + pop() + { + const renderer = this.renderer; + const filterStack = renderer.renderTexture.defaultFilterStack; + const state = filterStack.pop(); + const filters = state.filters; + + this.activeState = state; + + const globalUniforms = this.globalUniforms.uniforms; + + globalUniforms.sourceFrame = state.sourceFrame; + globalUniforms.destinationFrame = state.destinationFrame; + globalUniforms.resolution = state.resolution; + + // only update the rect if its legacy.. + if (state.legacy) + { + const filterArea = globalUniforms.filterArea; + const filterClamp = globalUniforms.filterClamp; + + filterArea[0] = state.destinationFrame.width; + filterArea[1] = state.destinationFrame.height; + filterArea[2] = state.sourceFrame.x; + filterArea[3] = state.sourceFrame.y; + + filterClamp[0] = 0.5 / state.resolution / state.destinationFrame.width; + filterClamp[1] = 0.5 / state.resolution / state.destinationFrame.height; + filterClamp[2] = (state.sourceFrame.width - 0.5) / state.resolution / state.destinationFrame.width; + filterClamp[3] = (state.sourceFrame.height - 0.5) / state.resolution / state.destinationFrame.height; + } + + this.globalUniforms.update(); + + const lastState = filterStack[filterStack.length - 1]; + + if (filters.length === 1) + { + filters[0].apply(this, state.renderTexture, lastState.renderTexture, false, state); + + this.returnFilterTexture(state.renderTexture); + } + else + { + let flip = state.renderTexture; + let flop = this.getPotFilterTexture( + flip.width, + flip.height, + state.resolution + ); + + flop.filterFrame = flip.filterFrame; + + let i = 0; + + for (i = 0; i < filters.length - 1; ++i) + { + filters[i].apply(this, flip, flop, true, state); + + const t = flip; + + flip = flop; + flop = t; + } + + filters[i].apply(this, flip, lastState.renderTexture, false, state); + + this.returnFilterTexture(flip); + this.returnFilterTexture(flop); + } + + this.statePool.push(state); + } + + /** + * Draws a filter. + * + * @param {PIXI.Filter} filter - The filter to draw. + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + * @param {boolean} clear - Should the output be cleared before rendering to it + */ + applyFilter(filter, input, output, clear) + { + const renderer = this.renderer; + + renderer.renderTexture.bind(output, output ? output.filterFrame : null); + + if (clear) + { + // gl.disable(gl.SCISSOR_TEST); + renderer.renderTexture.clear(); + // gl.enable(gl.SCISSOR_TEST); + } + + // set the uniforms.. + filter.uniforms.uSampler = input; + filter.uniforms.filterGlobals = this.globalUniforms; + + // TODO make it so that the order of this does not matter.. + // because it does at the moment cos of global uniforms. + // they need to get resynced + + renderer.state.setState(filter.state); + renderer.shader.bind(filter); + + if (filter.legacy) + { + this.quadUv.map(input._frame, input.filterFrame); + + renderer.geometry.bind(this.quadUv); + renderer.geometry.draw(DRAW_MODES.TRIANGLES); + } + else + { + renderer.geometry.bind(this.quad); + renderer.geometry.draw(DRAW_MODES.TRIANGLE_STRIP); + } + } + + /** + * Calculates the mapped matrix. + * + * TODO playing around here.. this is temporary - (will end up in the shader) + * this returns a matrix that will normalise map filter cords in the filter to screen space + * + * @param {PIXI.Matrix} outputMatrix - the matrix to output to. + * @return {PIXI.Matrix} The mapped matrix. + */ + calculateScreenSpaceMatrix(outputMatrix) + { + const currentState = this.activeState; + + return filterTransforms.calculateScreenSpaceMatrix( + outputMatrix, + currentState.sourceFrame, + currentState.destinationFrame + ); + } + + /** + * This will map the filter coord so that a texture can be used based on the transform of a sprite + * + * @param {PIXI.Matrix} outputMatrix - The matrix to output to. + * @param {PIXI.Sprite} sprite - The sprite to map to. + * @return {PIXI.Matrix} The mapped matrix. + */ + calculateSpriteMatrix(outputMatrix, sprite) + { + const currentState = this.activeState; + + return filterTransforms.calculateSpriteMatrix( + outputMatrix, + currentState.sourceFrame, + currentState.destinationFrame, + sprite + ); + } + + /** + * Destroys this Filter System. + * + * @param {boolean} [contextLost=false] context was lost, do not free shaders + * + */ + destroy(contextLost = false) + { + if (!contextLost) + { + this.emptyPool(); + } + else + { + this.texturePool = {}; + } + } + + /** + * Gets a Power-of-Two render texture. + * + * TODO move to a seperate class could be on renderer? + * also - could cause issue with multiple contexts? + * + * @private + * @param {WebGLRenderingContext} gl - The webgl rendering context + * @param {number} minWidth - The minimum width of the render target. + * @param {number} minHeight - The minimum height of the render target. + * @param {number} resolution - The resolution of the render target. + * @return {PIXI.RenderTarget} The new render target. + */ + getPotFilterTexture(minWidth, minHeight, resolution) + { + minWidth = bitTwiddle.nextPow2(minWidth); + minHeight = bitTwiddle.nextPow2(minHeight); + resolution = resolution || 1; + + const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + + if (!this.texturePool[key]) + { + this.texturePool[key] = []; + } + + let renderTexture = this.texturePool[key].pop(); + + if (!renderTexture) + { + // temporary bypass cache.. + // internally - this will cause a texture to be bound.. + renderTexture = RenderTexture.create({ + width: minWidth, + height: minHeight, + resolution, + }); + } + + return renderTexture; + } + + /** + * Gets extra render texture to use inside current filter + * + * @param {number} resolution resolution of the renderTexture + * @returns {PIXI.RenderTexture} + */ + getFilterTexture(resolution) + { + const rt = this.activeState.renderTexture; + + const filterTexture = this.getPotFilterTexture(rt.width, rt.height, resolution || rt.baseTexture.resolution); + + filterTexture.filterFrame = rt.filterFrame; + + return filterTexture; + } + + /** + * Frees a render texture back into the pool. + * + * @param {PIXI.RenderTarget} renderTexture - The renderTarget to free + */ + returnFilterTexture(renderTexture) + { + renderTexture.filterFrame = null; + + const base = renderTexture.baseTexture; + + const minWidth = base.width; + const minHeight = base.height; + + const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + + this.texturePool[key].push(renderTexture); + } + + /** + * Empties the texture pool. + * + */ + emptyPool() + { + for (const i in this.texturePool) + { + const textures = this.texturePool[i]; + + if (textures) + { + for (let j = 0; j < textures.length; j++) + { + textures[j].destroy(true); + } + } + } + + this.texturePool = {}; + } +} diff --git a/packages/core/src/filters/defaultFilter.frag b/packages/core/src/filters/defaultFilter.frag new file mode 100644 index 0000000..a14c1be --- /dev/null +++ b/packages/core/src/filters/defaultFilter.frag @@ -0,0 +1,22 @@ +varying vec2 vTextureCoord; +varying vec2 vFilterCoord; + +uniform sampler2D uSampler; +uniform sampler2D filterSampler; + +void main(void){ + vec4 masky = texture2D(filterSampler, vFilterCoord); + vec4 sample = texture2D(uSampler, vTextureCoord); + vec4 color; + if(mod(vFilterCoord.x, 1.0) > 0.5) + { + color = vec4(1.0, 0.0, 0.0, 1.0); + } + else + { + color = vec4(0.0, 1.0, 0.0, 1.0); + } + // gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0); + gl_FragColor = mix(sample, masky, 0.5); + gl_FragColor *= sample.a; +} diff --git a/packages/core/src/filters/defaultFilter.vert b/packages/core/src/filters/defaultFilter.vert new file mode 100644 index 0000000..e1febca --- /dev/null +++ b/packages/core/src/filters/defaultFilter.vert @@ -0,0 +1,14 @@ +attribute vec2 aVertexPosition; +attribute vec2 aTextureCoord; + +uniform mat3 projectionMatrix; +uniform mat3 filterMatrix; + +varying vec2 vTextureCoord; +varying vec2 vFilterCoord; + +void main(void){ + gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); + vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy; + vTextureCoord = aTextureCoord ; +} diff --git a/packages/core/src/filters/filterTransforms.js b/packages/core/src/filters/filterTransforms.js new file mode 100644 index 0000000..87a3cd6 --- /dev/null +++ b/packages/core/src/filters/filterTransforms.js @@ -0,0 +1,50 @@ +import { Matrix } from '@pixi/math'; + +/** + * Calculates the mapped matrix + * @param filterArea {Rectangle} The filter area + * @param sprite {Sprite} the target sprite + * @param outputMatrix {Matrix} @alvin + * @private + */ +// this returns a matrix that will normalise map filter cords in the filter to screen space +export function calculateScreenSpaceMatrix(outputMatrix, filterArea, textureSize) +{ + // TODO unwrap? + const mappedMatrix = outputMatrix.identity(); + + mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); + + mappedMatrix.scale(textureSize.width, textureSize.height); + + return mappedMatrix; +} + +export function calculateNormalizedScreenSpaceMatrix(outputMatrix, filterArea, textureSize) +{ + const mappedMatrix = outputMatrix.identity(); + + mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); + + const translateScaleX = (textureSize.width / filterArea.width); + const translateScaleY = (textureSize.height / filterArea.height); + + mappedMatrix.scale(translateScaleX, translateScaleY); + + return mappedMatrix; +} + +// this will map the filter coord so that a texture can be used based on the transform of a sprite +export function calculateSpriteMatrix(outputMatrix, filterArea, textureSize, sprite) +{ + const orig = sprite._texture.orig; + const mappedMatrix = outputMatrix.set(textureSize.width, 0, 0, textureSize.height, filterArea.x, filterArea.y); + const worldTransform = sprite.worldTransform.copyTo(Matrix.TEMP_MATRIX); + + worldTransform.invert(); + mappedMatrix.prepend(worldTransform); + mappedMatrix.scale(1.0 / orig.width, 1.0 / orig.height); + mappedMatrix.translate(sprite.anchor.x, sprite.anchor.y); + + return mappedMatrix; +} diff --git a/packages/core/src/filters/spriteMask/SpriteMaskFilter.js b/packages/core/src/filters/spriteMask/SpriteMaskFilter.js new file mode 100644 index 0000000..91697b7 --- /dev/null +++ b/packages/core/src/filters/spriteMask/SpriteMaskFilter.js @@ -0,0 +1,63 @@ +import Filter from '../Filter'; +import { Matrix } from '@pixi/math'; +import vertex from './spriteMaskFilter.vert'; +import fragment from './spriteMaskFilter.frag'; +import { default as TextureMatrix } from '../../textures/TextureMatrix'; + +/** + * The SpriteMaskFilter class + * + * @class + * @extends PIXI.Filter + * @memberof PIXI + */ +export default class SpriteMaskFilter extends Filter +{ + /** + * @param {PIXI.Sprite} sprite - the target sprite + */ + constructor(sprite) + { + const maskMatrix = new Matrix(); + + super(vertex, fragment); + + sprite.renderable = false; + + this.maskSprite = sprite; + this.maskMatrix = maskMatrix; + } + + /** + * Applies the filter + * + * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + */ + apply(filterManager, input, output) + { + const maskSprite = this.maskSprite; + const tex = this.maskSprite.texture; + + if (!tex.valid) + { + return; + } + if (!tex.transform) + { + // margin = 0.0, let it bleed a bit, shader code becomes easier + // assuming that atlas textures were made with 1-pixel padding + tex.transform = new TextureMatrix(tex, 0.0); + } + tex.transform.update(); + + this.uniforms.mask = tex; + this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite) + .prepend(tex.transform.mapCoord); + this.uniforms.alpha = maskSprite.worldAlpha; + this.uniforms.maskClamp = tex.transform.uClampFrame; + + filterManager.applyFilter(this, input, output); + } +} diff --git a/packages/core/src/filters/spriteMask/spriteMaskFilter.frag b/packages/core/src/filters/spriteMask/spriteMaskFilter.frag new file mode 100644 index 0000000..0e4aef8 --- /dev/null +++ b/packages/core/src/filters/spriteMask/spriteMaskFilter.frag @@ -0,0 +1,23 @@ +varying vec2 vMaskCoord; +varying vec2 vTextureCoord; + +uniform sampler2D uSampler; +uniform sampler2D mask; +uniform float alpha; +uniform vec4 maskClamp; + +void main(void) +{ + float clip = step(3.5, + step(maskClamp.x, vMaskCoord.x) + + step(maskClamp.y, vMaskCoord.y) + + step(vMaskCoord.x, maskClamp.z) + + step(vMaskCoord.y, maskClamp.w)); + + vec4 original = texture2D(uSampler, vTextureCoord); + vec4 masky = texture2D(mask, vMaskCoord); + + original *= (masky.r * masky.a * alpha * clip); + + gl_FragColor = original; +} diff --git a/packages/core/src/filters/spriteMask/spriteMaskFilter.vert b/packages/core/src/filters/spriteMask/spriteMaskFilter.vert new file mode 100644 index 0000000..d23a30c --- /dev/null +++ b/packages/core/src/filters/spriteMask/spriteMaskFilter.vert @@ -0,0 +1,16 @@ +attribute vec2 aVertexPosition; +attribute vec2 aTextureCoord; + +uniform mat3 projectionMatrix; +uniform mat3 otherMatrix; + +varying vec2 vMaskCoord; +varying vec2 vTextureCoord; + +void main(void) +{ + gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); + + vTextureCoord = aTextureCoord; + vMaskCoord = ( otherMatrix * vec3( aTextureCoord, 1.0) ).xy; +} diff --git a/packages/core/src/framebuffer/FrameBuffer.js b/packages/core/src/framebuffer/FrameBuffer.js new file mode 100644 index 0000000..84e9fcc --- /dev/null +++ b/packages/core/src/framebuffer/FrameBuffer.js @@ -0,0 +1,106 @@ +import Texture from '../textures/BaseTexture'; +import { FORMATS, TYPES } from '@pixi/constants'; + +/** + * Frame buffer + * @class + * @memberof PIXI + */ +export default class FrameBuffer +{ + constructor(width, height) + { + this.width = width || 100; + this.height = height || 100; + + this.stencil = false; + this.depth = false; + + this.dirtyId = 0; + this.dirtyFormat = 0; + this.dirtySize = 0; + + this.depthTexture = null; + this.colorTextures = []; + + this.glFrameBuffers = {}; + } + + get colorTexture() + { + return this.colorTextures[0]; + } + + addColorTexture(index, texture) + { + // TODO add some validation to the texture - same width / height etc? + this.colorTextures[index || 0] = texture || new Texture(null, { scaleMode: 0, + resolution: 1, + mipmap: false, + width: this.width, + height: this.height });// || new Texture(); + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + addDepthTexture(texture) + { + /* eslint-disable max-len */ + this.depthTexture = texture || new Texture(null, { scaleMode: 0, + resolution: 1, + width: this.width, + height: this.height, + mipmap: false, + format: FORMATS.DEPTH_COMPONENT, + type: TYPES.UNSIGNED_SHORT });// UNSIGNED_SHORT; + /* eslint-disable max-len */ + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + enableDepth() + { + this.depth = true; + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + enableStencil() + { + this.stencil = true; + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + resize(width, height) + { + if (width === this.width && height === this.height) return; + + this.width = width; + this.height = height; + + this.dirtyId++; + this.dirtySize++; + + for (let i = 0; i < this.colorTextures.length; i++) + { + this.colorTextures[i].setSize(width, height); + } + + if (this.depthTexture) + { + this.depthTexture.setSize(width, height); + } + } +} diff --git a/packages/core/src/framebuffer/FramebufferSystem.js b/packages/core/src/framebuffer/FramebufferSystem.js new file mode 100644 index 0000000..2cc2437 --- /dev/null +++ b/packages/core/src/framebuffer/FramebufferSystem.js @@ -0,0 +1,246 @@ +import System from '../System'; +import { Rectangle } from '@pixi/math'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class FramebufferSystem extends System +{ + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + contextChange() + { + this.gl = this.renderer.gl; + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + this.current = null; + this.viewport = new Rectangle(); + + this.drawBufferExtension = this.renderer.context.extensions.drawBuffers; + } + + bind(framebuffer, frame) + { + const gl = this.gl; + + this.current = framebuffer; + + if (framebuffer) + { + // TODO cacheing layer! + + const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID] || this.initFramebuffer(framebuffer); + + gl.bindFramebuffer(gl.FRAMEBUFFER, fbo.framebuffer); + // makesure all textures are unbound.. + + // now check for updates... + if (fbo.dirtyId !== framebuffer.dirtyId) + { + fbo.dirtyId = framebuffer.dirtyId; + + if (fbo.dirtyFormat !== framebuffer.dirtyFormat) + { + fbo.dirtyFormat = framebuffer.dirtyFormat; + this.updateFramebuffer(framebuffer); + } + else if (fbo.dirtySize !== framebuffer.dirtySize) + { + fbo.dirtySize = framebuffer.dirtySize; + this.resizeFramebuffer(framebuffer); + } + } + + for (let i = 0; i < framebuffer.colorTextures.length; i++) + { + if (framebuffer.colorTextures[i].texturePart) + { + this.renderer.texture.unbind(framebuffer.colorTextures[i].texture); + } + else + { + this.renderer.texture.unbind(framebuffer.colorTextures[i]); + } + } + + if (framebuffer.depthTexture) + { + this.renderer.texture.unbind(framebuffer.depthTexture); + } + + if (frame) + { + this.setViewport(frame.x, frame.y, frame.width, frame.height); + } + else + { + this.setViewport(0, 0, framebuffer.width, framebuffer.height); + } + } + else + { + gl.bindFramebuffer(gl.FRAMEBUFFER, null); + + if (frame) + { + this.setViewport(frame.x, frame.y, frame.width, frame.height); + } + else + { + this.setViewport(0, 0, this.renderer.width, this.renderer.height); + } + } + } + + setViewport(x, y, width, height) + { + const v = this.viewport; + + if (v.width !== width || v.height !== height || v.x !== x || v.y !== y) + { + v.x = x; + v.y = y; + v.width = width; + v.height = height; + + this.gl.viewport(x, y, width, height); + } + } + + get size() + { + if (this.current) + { + // TODO store temp + return { x: 0, y: 0, width: this.current.width, height: this.current.height }; + } + + return { x: 0, y: 0, width: this.renderer.width, height: this.renderer.height }; + } + + clear(r, g, b, a) + { + const gl = this.gl; + + // TODO clear color can be set only one right? + gl.clearColor(r, g, b, a); + gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); + } + + // private functions... + + initFramebuffer(framebuffer) + { + const gl = this.gl; + + // TODO - make this a class? + const fbo = { + framebuffer: gl.createFramebuffer(), + stencil: null, + dirtyId: 0, + dirtyFormat: 0, + dirtySize: 0, + }; + + framebuffer.glFrameBuffers[this.CONTEXT_UID] = fbo; + + return fbo; + } + + resizeFramebuffer(framebuffer) + { + const gl = this.gl; + + if (framebuffer.stencil || framebuffer.depth) + { + gl.bindRenderbuffer(gl.RENDERBUFFER, this.stencil); + gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); + } + } + + updateFramebuffer(framebuffer) + { + const gl = this.gl; + + const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID]; + + // bind the color texture + const colorTextures = framebuffer.colorTextures; + + let count = colorTextures.length; + + if (!this.drawBufferExtension) + { + count = Math.min(count, 1); + } + + const activeTextures = []; + + for (let i = 0; i < count; i++) + { + const texture = framebuffer.colorTextures[i]; + + if (texture.texturePart) + { + this.renderer.texture.bind(texture.texture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.COLOR_ATTACHMENT0 + i, + gl.TEXTURE_CUBE_MAP_NEGATIVE_X + texture.side, + texture.texture._glTextures[this.CONTEXT_UID].texture, + 0); + } + else + { + this.renderer.texture.bind(texture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.COLOR_ATTACHMENT0 + i, + gl.TEXTURE_2D, + texture._glTextures[this.CONTEXT_UID].texture, + 0); + } + + activeTextures.push(gl.COLOR_ATTACHMENT0 + i); + } + + if (this.drawBufferExtension && activeTextures.length > 1) + { + this.drawBufferExtension.drawBuffersWEBGL(activeTextures); + } + + if (framebuffer.depthTexture) + { + const depthTextureExt = this.renderer.context.extensions.depthTexture; + + if (depthTextureExt) + { + const depthTexture = framebuffer.depthTexture; + + this.renderer.texture.bind(depthTexture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.DEPTH_ATTACHMENT, + gl.TEXTURE_2D, + depthTexture._glTextures[this.CONTEXT_UID].texture, + 0); + } + } + + if (framebuffer.stencil || framebuffer.depth) + { + fbo.stencil = gl.createRenderbuffer(); + + gl.bindRenderbuffer(gl.RENDERBUFFER, fbo.stencil); + + // TODO.. this is depth AND stencil? + gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, fbo.stencil); + gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); + // fbo.enableStencil(); + } + } +} diff --git a/packages/core/src/geometry/GLBuffer.js b/packages/core/src/geometry/GLBuffer.js new file mode 100644 index 0000000..0477e8a --- /dev/null +++ b/packages/core/src/geometry/GLBuffer.js @@ -0,0 +1,9 @@ +export default class GLBuffer +{ + constructor(buffer) + { + this.buffer = buffer; + this.updateID = -1; + this.byteLength = -1; + } +} diff --git a/packages/core/src/geometry/GeometrySystem.js b/packages/core/src/geometry/GeometrySystem.js new file mode 100644 index 0000000..959f827 --- /dev/null +++ b/packages/core/src/geometry/GeometrySystem.js @@ -0,0 +1,388 @@ +import System from '../System'; +import GLBuffer from './GLBuffer'; + +const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class GeometrySystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this._activeGeometry = null; + this._activeVao = null; + + this.hasVao = true; + this.hasInstance = true; + } + + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + contextChange() + { + const gl = this.gl = this.renderer.gl; + + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + + // webgl2 + if (!gl.createVertexArray) + { + // webgl 1! + let nativeVaoExtension = this.renderer.context.extensions.vertexArrayObject; + + if (this.renderer.legacy) + { + nativeVaoExtension = null; + } + + if (nativeVaoExtension) + { + gl.createVertexArray = () => + nativeVaoExtension.createVertexArrayOES(); + + gl.bindVertexArray = (vao) => + nativeVaoExtension.bindVertexArrayOES(vao); + + gl.deleteVertexArray = (vao) => + nativeVaoExtension.deleteVertexArrayOES(vao); + } + else + { + this.hasVao = false; + gl.createVertexArray = () => + { + // empty + }; + + gl.bindVertexArray = () => + { + // empty + }; + + gl.deleteVertexArray = () => + { + // empty + }; + } + } + + if (!gl.vertexAttribDivisor) + { + const instanceExt = gl.getExtension('ANGLE_instanced_arrays'); + + if (instanceExt) + { + gl.vertexAttribDivisor = (a, b) => + instanceExt.vertexAttribDivisorANGLE(a, b); + + gl.drawElementsInstanced = (a, b, c, d, e) => + instanceExt.drawElementsInstancedANGLE(a, b, c, d, e); + + gl.drawArraysInstanced = (a, b, c, d) => + instanceExt.drawArraysInstancedANGLE(a, b, c, d); + } + else + { + this.hasInstance = false; + } + } + } + + /** + * Binds geometry so that is can be drawn. Creating a Vao if required + * @private + * @param {PIXI.Geometry} geometry instance of geometry to bind + */ + bind(geometry, shader) + { + shader = shader || this.renderer.shader.shader; + + const gl = this.gl; + + // not sure the best way to address this.. + // currently different shaders require different VAOs for the same geometry + // Still mulling over the best way to solve this one.. + // will likely need to modify the shader attribute locations at run time! + let vaos = geometry.glVertexArrayObjects[this.CONTEXT_UID]; + + if (!vaos) + { + geometry.glVertexArrayObjects[this.CONTEXT_UID] = vaos = {}; + } + + const vao = vaos[shader.program.id] || this.initGeometryVao(geometry, shader.program); + + this._activeGeometry = geometry; + + if (this._activeVao !== vao) + { + this._activeVao = vao; + + if (this.hasVao) + { + gl.bindVertexArray(vao); + } + else + { + this.activateVao(geometry, shader.program); + } + } + + // TODO - optimise later! + // don't need to loop through if nothing changed! + // maybe look to add an 'autoupdate' to geometry? + this.updateBuffers(); + } + + reset() + { + this.unbind(); + } + + updateBuffers() + { + const geometry = this._activeGeometry; + const gl = this.gl; + + for (let i = 0; i < geometry.buffers.length; i++) + { + const buffer = geometry.buffers[i]; + + const glBuffer = buffer._glBuffers[this.CONTEXT_UID]; + + if (buffer._updateID !== glBuffer.updateID) + { + glBuffer.updateID = buffer._updateID; + + // TODO can cache this on buffer! maybe added a getter / setter? + const type = buffer.index ? gl.ELEMENT_ARRAY_BUFFER : gl.ARRAY_BUFFER; + const drawType = buffer.static ? gl.STATIC_DRAW : gl.DYNAMIC_DRAW; + + gl.bindBuffer(type, glBuffer.buffer); + + if (glBuffer.byteLength >= buffer.data.byteLength) + { + // offset is always zero for now! + gl.bufferSubData(type, 0, buffer.data); + } + else + { + gl.bufferData(type, buffer.data, drawType); + } + } + } + } + + checkCompatability(geometry, program) + { + // geometry must have at least all the attributes that the shader requires. + const geometryAttributes = geometry.attributes; + const shaderAttributes = program.attributeData; + + for (const j in shaderAttributes) + { + if (!geometryAttributes[j]) + { + throw new Error(`shader and geometry incompatible, geometry missing the "${j}" attribute`); + } + } + } + + /** + * Creates a Vao with the same structure as the geometry and stores it on the geometry. + * @private + * @param {PIXI.Geometry} geometry instance of geometry to to generate Vao for + */ + initGeometryVao(geometry, program) + { + this.checkCompatability(geometry, program); + + const gl = this.gl; + const CONTEXT_UID = this.CONTEXT_UID; + const buffers = geometry.buffers; + const attributes = geometry.attributes; + + const tempStride = {}; + const tempStart = {}; + + for (const j in buffers) + { + tempStride[j] = 0; + tempStart[j] = 0; + } + + for (const j in attributes) + { + if (!attributes[j].size && program.attributeData[j]) + { + attributes[j].size = program.attributeData[j].size; + } + + tempStride[attributes[j].buffer] += attributes[j].size * byteSizeMap[attributes[j].type]; + } + + for (const j in attributes) + { + const attribute = attributes[j]; + const attribSize = attribute.size; + + if (attribute.stride === undefined) + { + if (tempStride[attribute.buffer] === attribSize * byteSizeMap[attribute.type]) + { + attribute.stride = 0; + } + else + { + attribute.stride = tempStride[attribute.buffer]; + } + } + + if (attribute.start === undefined) + { + attribute.start = tempStart[attribute.buffer]; + + tempStart[attribute.buffer] += attribSize * byteSizeMap[attribute.type]; + } + } + + // first update - and create the buffers! + // only create a gl buffer if it actually gets + for (let i = 0; i < buffers.length; i++) + { + const buffer = buffers[i]; + + if (!buffer._glBuffers[CONTEXT_UID]) + { + buffer._glBuffers[CONTEXT_UID] = new GLBuffer(gl.createBuffer()); + } + } + + // TODO - maybe make this a data object? + // lets wait to see if we need to first! + const vao = gl.createVertexArray(); + + gl.bindVertexArray(vao); + + this.activateVao(geometry, program); + + gl.bindVertexArray(null); + + // add it to the cache! + geometry.glVertexArrayObjects[this.CONTEXT_UID][program.id] = vao; + + return vao; + } + + activateVao(geometry, program) + { + const gl = this.gl; + const CONTEXT_UID = this.CONTEXT_UID; + const buffers = geometry.buffers; + const attributes = geometry.attributes; + + if (geometry.indexBuffer) + { + // first update the index buffer if we have one.. + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, geometry.indexBuffer._glBuffers[CONTEXT_UID].buffer); + } + + let lastBuffer = null; + + // add a new one! + for (const j in attributes) + { + const attribute = attributes[j]; + const buffer = buffers[attribute.buffer]; + const glBuffer = buffer._glBuffers[CONTEXT_UID]; + + if (program.attributeData[j]) + { + if (lastBuffer !== glBuffer) + { + gl.bindBuffer(gl.ARRAY_BUFFER, glBuffer.buffer); + + lastBuffer = glBuffer; + } + + const location = program.attributeData[j].location; + + // TODO introduce state again + // we can optimise this for older devices that have no VAOs + gl.enableVertexAttribArray(location); + + gl.vertexAttribPointer(location, + attribute.size, + attribute.type || gl.FLOAT, + attribute.normalized, + attribute.stride, + attribute.start); + + if (attribute.instance) + { + // TODO calculate instance count based of this... + if (this.hasInstance) + { + gl.vertexAttribDivisor(location, 1); + } + else + { + throw new Error('geometry error, GPU Instancing is not supported on this device'); + } + } + } + } + } + + draw(type, size, start, instanceCount) + { + const gl = this.gl; + const geometry = this._activeGeometry; + + // TODO.. this should not change so maybe cache the function? + + if (geometry.indexBuffer) + { + if (geometry.instanced) + { + /* eslint-disable max-len */ + gl.drawElementsInstanced(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2, instanceCount || 1); + /* eslint-enable max-len */ + } + else + { + gl.drawElements(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2); + } + } + else + if (geometry.instanced) + { + // TODO need a better way to calculate size.. + gl.drawArraysInstanced(type, start, size || geometry.getSize(), instanceCount || 1); + } + else + { + gl.drawArrays(type, start, size || geometry.getSize()); + } + + return this; + } + + unbind() + { + this.gl.bindVertexArray(null); + this._activeVao = null; + this._activeGeometry = null; + } +} diff --git a/packages/core/src/geometry/utils/setVertexAttribArrays.js b/packages/core/src/geometry/utils/setVertexAttribArrays.js new file mode 100644 index 0000000..23b6959 --- /dev/null +++ b/packages/core/src/geometry/utils/setVertexAttribArrays.js @@ -0,0 +1,54 @@ +// var GL_MAP = {}; + +/** + * @param gl {WebGLRenderingContext} The current WebGL context + * @param attribs {*} + * @param state {*} + */ +export default function setVertexAttribArrays(gl, attribs, state) +{ + let i; + + if (state) + { + const tempAttribState = state.tempAttribState; + const attribState = state.attribState; + + for (i = 0; i < tempAttribState.length; i++) + { + tempAttribState[i] = false; + } + + // set the new attribs + for (i = 0; i < attribs.length; i++) + { + tempAttribState[attribs[i].attribute.location] = true; + } + + for (i = 0; i < attribState.length; i++) + { + if (attribState[i] !== tempAttribState[i]) + { + attribState[i] = tempAttribState[i]; + + if (state.attribState[i]) + { + gl.enableVertexAttribArray(i); + } + else + { + gl.disableVertexAttribArray(i); + } + } + } + } + else + { + for (i = 0; i < attribs.length; i++) + { + const attrib = attribs[i]; + + gl.enableVertexAttribArray(attrib.attribute.location); + } + } +} diff --git a/packages/core/src/index.js b/packages/core/src/index.js index 58c456a..fd1f547 100644 --- a/packages/core/src/index.js +++ b/packages/core/src/index.js @@ -1,30 +1,30 @@ import * as resources from './textures/resources'; -import * as systems from './renderers/systems'; +import * as systems from './systems'; -export { resources }; export { systems }; +export { resources }; -export { default as Renderer } from './renderers/Renderer'; -export { default as AbstractRenderer } from './renderers/AbstractRenderer'; -export { default as FrameBuffer } from './textures/FrameBuffer'; +export { default as System } from './System'; +export { default as Renderer } from './Renderer'; +export { default as AbstractRenderer } from './AbstractRenderer'; +export { default as FrameBuffer } from './framebuffer/FrameBuffer'; export { default as CubeTexture } from './textures/CubeTexture'; export { default as BaseTexture } from './textures/BaseTexture'; export { default as Texture } from './textures/Texture'; export { default as TextureMatrix } from './textures/TextureMatrix'; -export { default as RenderTexture } from './textures/RenderTexture'; -export { default as BaseRenderTexture } from './textures/BaseRenderTexture'; +export { default as RenderTexture } from './renderTexture/RenderTexture'; +export { default as BaseRenderTexture } from './renderTexture/BaseRenderTexture'; export { default as TextureUvs } from './textures/TextureUvs'; -export { default as State } from './renderers/State'; -export { default as ObjectRenderer } from './renderers/utils/ObjectRenderer'; -export { default as Quad } from './renderers/utils/Quad'; -export { default as QuadUv } from './renderers/utils/QuadUv'; -export { default as checkMaxIfStatmentsInShader } from './renderers/utils/checkMaxIfStatmentsInShader'; +export { default as State } from './state/State'; +export { default as ObjectRenderer } from './batch/ObjectRenderer'; +export { default as Quad } from './utils/Quad'; +export { default as QuadUv } from './utils/QuadUv'; +export { default as checkMaxIfStatmentsInShader } from './shader/utils/checkMaxIfStatmentsInShader'; export { default as Shader } from './shader/Shader'; export { default as Program } from './shader/Program'; export { default as UniformGroup } from './shader/UniformGroup'; -export { default as SpriteMaskFilter } from './renderers/filters/spriteMask/SpriteMaskFilter'; -export { default as Filter } from './renderers/filters/Filter'; +export { default as SpriteMaskFilter } from './filters/spriteMask/SpriteMaskFilter'; +export { default as Filter } from './filters/Filter'; export { default as Attribute } from './geometry/Attribute'; export { default as Buffer } from './geometry/Buffer'; export { default as Geometry } from './geometry/Geometry'; - diff --git a/packages/core/src/mask/MaskSystem.js b/packages/core/src/mask/MaskSystem.js new file mode 100644 index 0000000..99174c2 --- /dev/null +++ b/packages/core/src/mask/MaskSystem.js @@ -0,0 +1,199 @@ +import System from '../System'; +import AlphaMaskFilter from '../filters/spriteMask/SpriteMaskFilter'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class MaskSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + // TODO - we don't need both! + this.scissor = false; + this.scissorData = null; + this.scissorRenderTarget = null; + + this.enableScissor = false; + + this.alphaMaskPool = []; + this.alphaMaskIndex = 0; + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.DisplayObject} target - Display Object to push the mask to + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + push(target, maskData) + { + // TODO the root check means scissor rect will not + // be used on render textures more info here: + // https://github.com/pixijs/pixi.js/pull/3545 + + if (maskData.texture) + { + this.pushSpriteMask(target, maskData); + } + else if (this.enableScissor + && !this.scissor + && this.renderer._activeRenderTarget.root + && !this.renderer.stencil.stencilMaskStack.length + && maskData.isFastRect()) + { + const matrix = maskData.worldTransform; + + let rot = Math.atan2(matrix.b, matrix.a); + + // use the nearest degree! + rot = Math.round(rot * (180 / Math.PI)); + + if (rot % 90) + { + this.pushStencilMask(maskData); + } + else + { + this.pushScissorMask(target, maskData); + } + } + else + { + this.pushStencilMask(maskData); + } + } + + /** + * Removes the last mask from the mask stack and doesn't return it. + * + * @param {PIXI.DisplayObject} target - Display Object to pop the mask from + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + pop(target, maskData) + { + if (maskData.texture) + { + this.popSpriteMask(target, maskData); + } + else if (this.enableScissor && !this.renderer.stencil.stencilMaskStack.length) + { + this.popScissorMask(target, maskData); + } + else + { + this.popStencilMask(target, maskData); + } + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.RenderTarget} target - Display Object to push the sprite mask to + * @param {PIXI.Sprite} maskData - Sprite to be used as the mask + */ + pushSpriteMask(target, maskData) + { + let alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; + + if (!alphaMaskFilter) + { + alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; + } + + alphaMaskFilter[0].resolution = this.renderer.resolution; + alphaMaskFilter[0].maskSprite = maskData; + + // TODO - may cause issues! + target.filterArea = maskData.getBounds(true); + + this.renderer.filterSystem.pushFilter(target, alphaMaskFilter); + + this.alphaMaskIndex++; + } + + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popSpriteMask() + { + this.renderer.filterSystem.popFilter(); + this.alphaMaskIndex--; + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + pushStencilMask(maskData) + { + this.renderer.batch.flush(); + this.renderer.stencil.pushStencil(maskData); + } + + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popStencilMask() + { + // this.renderer.currentRenderer.stop(); + this.renderer.stencil.popStencil(); + } + + /** + * + * @param {PIXI.DisplayObject} target - Display Object to push the mask to + * @param {PIXI.Graphics} maskData - The masking data. + */ + pushScissorMask(target, maskData) + { + maskData.renderable = true; + + const renderTarget = this.renderer._activeRenderTarget; + + const bounds = maskData.getBounds(); + + bounds.fit(renderTarget.size); + maskData.renderable = false; + + this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); + + const resolution = this.renderer.resolution; + + this.renderer.gl.scissor( + bounds.x * resolution, + (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, + bounds.width * resolution, + bounds.height * resolution + ); + + this.scissorRenderTarget = renderTarget; + this.scissorData = maskData; + this.scissor = true; + } + + /** + * + * + */ + popScissorMask() + { + this.scissorRenderTarget = null; + this.scissorData = null; + this.scissor = false; + + // must be scissor! + const gl = this.renderer.gl; + + gl.disable(gl.SCISSOR_TEST); + } +} diff --git a/packages/core/src/mask/StencilSystem.js b/packages/core/src/mask/StencilSystem.js new file mode 100644 index 0000000..62796a5 --- /dev/null +++ b/packages/core/src/mask/StencilSystem.js @@ -0,0 +1,133 @@ +import System from '../System'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class StencilSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + this.stencilMaskStack = []; + } + + /** + * Changes the mask stack that is used by this System. + * + * @param {PIXI.Graphics[]} stencilMaskStack - The mask stack + */ + setMaskStack(stencilMaskStack) + { + const gl = this.renderer.gl; + + if (stencilMaskStack.length !== this.stencilMaskStack.length) + { + if (stencilMaskStack.length === 0) + { + gl.disable(gl.STENCIL_TEST); + } + else + { + gl.enable(gl.STENCIL_TEST); + } + } + + this.stencilMaskStack = stencilMaskStack; + } + + /** + * Applies the Mask and adds it to the current stencil stack. @alvin + * + * @param {PIXI.Graphics} graphics - The mask + */ + pushStencil(graphics) + { + this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); + + // this.renderer._activeRenderTarget.attachStencilBuffer(); + + const gl = this.renderer.gl; + const prevMaskCount = this.stencilMaskStack.length; + + if (prevMaskCount === 0) + { + gl.enable(gl.STENCIL_TEST); + } + + this.stencilMaskStack.push(graphics); + + // Increment the refference stencil value where the new mask overlaps with the old ones. + gl.colorMask(false, false, false, false); + gl.stencilFunc(gl.EQUAL, prevMaskCount, this._getBitwiseMask()); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.INCR); + this.renderer.plugins.graphics.render(graphics); + + this._useCurrent(); + } + + /** + * Removes the last mask from the stencil stack. @alvin + */ + popStencil() + { + this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); + + const gl = this.renderer.gl; + const graphics = this.stencilMaskStack.pop(); + + if (this.stencilMaskStack.length === 0) + { + // the stack is empty! + gl.disable(gl.STENCIL_TEST); + gl.clear(gl.STENCIL_BUFFER_BIT); + gl.clearStencil(0); + } + else + { + // Decrement the refference stencil value where the popped mask overlaps with the other ones + gl.colorMask(false, false, false, false); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.DECR); + this.renderer.plugins.graphics.render(graphics); + + this._useCurrent(); + } + } + + /** + * Setup renderer to use the current stencil data. + */ + _useCurrent() + { + const gl = this.renderer.gl; + + gl.colorMask(true, true, true, true); + gl.stencilFunc(gl.EQUAL, this.stencilMaskStack.length, this._getBitwiseMask()); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP); + } + + /** + * Fill 1s equal to the number of acitve stencil masks. + * + * @return {number} The bitwise mask. + */ + _getBitwiseMask() + { + return (1 << this.stencilMaskStack.length) - 1; + } + + /** + * Destroys the mask stack. + * + */ + destroy() + { + super.destroy(this); + + this.stencilMaskStack = null; + } +} diff --git a/packages/core/src/projection/ProjectionSystem.js b/packages/core/src/projection/ProjectionSystem.js new file mode 100644 index 0000000..6e63b73 --- /dev/null +++ b/packages/core/src/projection/ProjectionSystem.js @@ -0,0 +1,73 @@ +import System from '../System'; +import { Matrix } from '@pixi/math'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class ProjectionSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.projectionMatrix = new Matrix(); + } + + update(destinationFrame, sourceFrame, resolution, root) + { + this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; + this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; + + this.calculateProjection(this.destinationFrame, this.sourceFrame, resolution, root); + + this.renderer.globalUniforms.uniforms.projectionMatrix = this.projectionMatrix; + this.renderer.globalUniforms.update(); + } + + /** + * Updates the projection matrix based on a projection frame (which is a rectangle) + * + * @param {Rectangle} destinationFrame - The destination frame. + * @param {Rectangle} sourceFrame - The source frame. + */ + calculateProjection(destinationFrame, sourceFrame, resolution, root) + { + const pm = this.projectionMatrix; + + // I don't think we will need this line.. + // pm.identity(); + + if (!root) + { + pm.a = (1 / destinationFrame.width * 2) * resolution; + pm.d = (1 / destinationFrame.height * 2) * resolution; + + pm.tx = -1 - (sourceFrame.x * pm.a); + pm.ty = -1 - (sourceFrame.y * pm.d); + } + else + { + pm.a = (1 / destinationFrame.width * 2) * resolution; + pm.d = (-1 / destinationFrame.height * 2) * resolution; + + pm.tx = -1 - (sourceFrame.x * pm.a); + pm.ty = 1 - (sourceFrame.y * pm.d); + } + } + + /** + * Sets the transform of the active render target to the given matrix + * + * @param {PIXI.Matrix} matrix - The transformation matrix + */ + setTransform()// matrix) + { + // this._activeRenderTarget.transform = matrix; + } +} diff --git a/packages/core/src/renderTexture/BaseRenderTexture.js b/packages/core/src/renderTexture/BaseRenderTexture.js new file mode 100644 index 0000000..88eb83b --- /dev/null +++ b/packages/core/src/renderTexture/BaseRenderTexture.js @@ -0,0 +1,137 @@ +import BaseTexture from '../textures/BaseTexture'; +import FrameBuffer from '../framebuffer/FrameBuffer'; + +/** + * A BaseRenderTexture is a special texture that allows any PixiJS display object to be rendered to it. + * + * __Hint__: All DisplayObjects (i.e. Sprites) that render to a BaseRenderTexture should be preloaded + * otherwise black rectangles will be drawn instead. + * + * A BaseRenderTexture takes a snapshot of any Display Object given to its render method. The position + * and rotation of the given Display Objects is ignored. For example: + * + * ```js + * let renderer = PIXI.autoDetectRenderer(1024, 1024, { view: canvas, ratio: 1 }); + * let baseRenderTexture = new PIXI.BaseRenderTexture(renderer, 800, 600); + * let sprite = PIXI.Sprite.fromImage("spinObj_01.png"); + * + * sprite.position.x = 800/2; + * sprite.position.y = 600/2; + * sprite.anchor.x = 0.5; + * sprite.anchor.y = 0.5; + * + * baseRenderTexture.render(sprite); + * ``` + * + * The Sprite in this case will be rendered using its local transform. To render this sprite at 0,0 + * you can clear the transform + * + * ```js + * + * sprite.setTransform() + * + * let baseRenderTexture = new PIXI.BaseRenderTexture(100, 100); + * let renderTexture = new PIXI.RenderTexture(baseRenderTexture); + * + * renderer.render(sprite, renderTexture); // Renders to center of RenderTexture + * ``` + * + * @class + * @extends PIXI.BaseTexture + * @memberof PIXI + */ +export default class BaseRenderTexture extends BaseTexture +{ + /** + * @param {object} [options] + * @param {number} [options.width=100] - The width of the base render texture + * @param {number} [options.height=100] - The height of the base render texture + * @param {PIXI.SCALE_MODES} [options.scaleMode] - See {@link PIXI.SCALE_MODES} for possible values + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the texture being generated + */ + constructor(options) + { + if (typeof options === 'number') + { + /* eslint-disable prefer-rest-params */ + // Backward compatibility of signature + const width = arguments[0]; + const height = arguments[1]; + const scaleMode = arguments[2]; + const resolution = arguments[3]; + + options = { width, height, scaleMode, resolution }; + /* eslint-enable prefer-rest-params */ + } + + super(null, options); + + const { width, height } = options || {}; + + // Set defaults + this.mipmap = false; + this.width = Math.ceil(width) || 100; + this.height = Math.ceil(height) || 100; + this.valid = true; + + /** + * A map of renderer IDs to webgl renderTargets + * + * @private + * @member {object} + */ + // this._glRenderTargets = {}; + + /** + * A reference to the canvas render target (we only need one as this can be shared across renderers) + * + * @private + * @member {object} + */ + this._canvasRenderTarget = null; + + this.clearColor = [0, 0, 0, 0]; + + this.frameBuffer = new FrameBuffer(width * this.resolution, height * this.resolution) + .addColorTexture(0, this); + + // TODO - could this be added the systems? + + /** + * The data structure for the stencil masks + * + * @member {PIXI.Graphics[]} + */ + this.stencilMaskStack = []; + + /** + * The data structure for the filters + * + * @member {PIXI.Graphics[]} + */ + this.filterStack = [{}]; + } + + /** + * Resizes the BaseRenderTexture. + * + * @param {number} width - The width to resize to. + * @param {number} height - The height to resize to. + */ + resize(width, height) + { + width = Math.ceil(width); + height = Math.ceil(height); + this.frameBuffer.resize(width * this.resolution, height * this.resolution); + } + + /** + * Destroys this texture + * + */ + destroy() + { + super.destroy(true); + this.renderer = null; + } +} diff --git a/packages/core/src/renderTexture/RenderTexture.js b/packages/core/src/renderTexture/RenderTexture.js new file mode 100644 index 0000000..f1ecf1f --- /dev/null +++ b/packages/core/src/renderTexture/RenderTexture.js @@ -0,0 +1,153 @@ +import BaseRenderTexture from './BaseRenderTexture'; +import Texture from '../textures/Texture'; + +/** + * A RenderTexture is a special texture that allows any PixiJS display object to be rendered to it. + * + * __Hint__: All DisplayObjects (i.e. Sprites) that render to a RenderTexture should be preloaded + * otherwise black rectangles will be drawn instead. + * + * A RenderTexture takes a snapshot of any Display Object given to its render method. For example: + * + * ```js + * let renderer = PIXI.autoDetectRenderer(1024, 1024, { view: canvas, ratio: 1 }); + * let renderTexture = PIXI.RenderTexture.create(800, 600); + * let sprite = PIXI.Sprite.fromImage("spinObj_01.png"); + * + * sprite.position.x = 800/2; + * sprite.position.y = 600/2; + * sprite.anchor.x = 0.5; + * sprite.anchor.y = 0.5; + * + * renderer.render(sprite, renderTexture); + * ``` + * + * The Sprite in this case will be rendered using its local transform. To render this sprite at 0,0 + * you can clear the transform + * + * ```js + * + * sprite.setTransform() + * + * let renderTexture = new PIXI.RenderTexture.create(100, 100); + * + * renderer.render(sprite, renderTexture); // Renders to center of RenderTexture + * ``` + * + * @class + * @extends PIXI.Texture + * @memberof PIXI + */ +export default class RenderTexture extends Texture +{ + /** + * @param {PIXI.BaseRenderTexture} baseRenderTexture - The renderer used for this RenderTexture + * @param {PIXI.Rectangle} [frame] - The rectangle frame of the texture to show + */ + constructor(baseRenderTexture, frame) + { + // support for legacy.. + let _legacyRenderer = null; + + if (!(baseRenderTexture instanceof BaseRenderTexture)) + { + /* eslint-disable prefer-rest-params, no-console */ + const width = arguments[1]; + const height = arguments[2]; + const scaleMode = arguments[3]; + const resolution = arguments[4]; + + // we have an old render texture.. + console.warn(`Please use RenderTexture.create(${width}, ${height}) instead of the ctor directly.`); + _legacyRenderer = arguments[0]; + /* eslint-enable prefer-rest-params, no-console */ + + frame = null; + baseRenderTexture = new BaseRenderTexture({ + width, + height, + scaleMode, + resolution, + }); + } + + /** + * The base texture object that this texture uses + * + * @member {BaseTexture} + */ + super(baseRenderTexture, frame); + + this.legacyRenderer = _legacyRenderer; + + /** + * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. + * + * @member {boolean} + */ + this.valid = true; + + /** + * FilterSystem temporary storage + * @private + * @member {PIXI.Rectangle} + */ + this.filterFrame = null; + + this._updateUvs(); + } + + /** + * Resizes the RenderTexture. + * + * @param {number} width - The width to resize to. + * @param {number} height - The height to resize to. + * @param {boolean} [resizeBaseTexture=true] - Should the baseTexture.width and height values be resized as well? + */ + resize(width, height, resizeBaseTexture = true) + { + width = Math.ceil(width); + height = Math.ceil(height); + + // TODO - could be not required.. + this.valid = (width > 0 && height > 0); + + this._frame.width = this.orig.width = width; + this._frame.height = this.orig.height = height; + + if (resizeBaseTexture) + { + this.baseTexture.resize(width, height); + } + + this._updateUvs(); + } + + /** + * A short hand way of creating a render texture. + * + * @param {object} [options] - Options + * @param {number} [options.width=100] - The width of the render texture + * @param {number} [options.height=100] - The height of the render texture + * @param {number} [options.scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the texture being generated + * @return {PIXI.RenderTexture} The new render texture + */ + static create(options) + { + // fallback, old-style: create(width, height, scaleMode, resolution) + if (typeof options === 'number') + { + /* eslint-disable prefer-rest-params */ + options = { + width: options, + height: arguments[1], + scaleMode: arguments[2], + resolution: arguments[3], + }; + /* eslint-enable prefer-rest-params */ + } + + return new RenderTexture(new BaseRenderTexture(options)); + } +} diff --git a/packages/core/src/renderTexture/RenderTextureSystem.js b/packages/core/src/renderTexture/RenderTextureSystem.js new file mode 100644 index 0000000..8d0ca61 --- /dev/null +++ b/packages/core/src/renderTexture/RenderTextureSystem.js @@ -0,0 +1,115 @@ +import System from '../System'; +import { Rectangle } from '@pixi/math'; + +const tempRect = new Rectangle(); + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class RenderTextureSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.clearColor = renderer._backgroundColorRgba; + + // TODO moe this property somewhere else! + this.defaultMaskStack = []; + this.defaultFilterStack = [{}]; + + // empty render texture? + this.renderTexture = null; + + this.destinationFrame = new Rectangle(); + } + + bind(renderTexture, sourceFrame, destinationFrame) + { + // TODO - do we want this?? + if (this.renderTexture === renderTexture) return; + this.renderTexture = renderTexture; + + const renderer = this.renderer; + + if (renderTexture) + { + const baseTexture = renderTexture.baseTexture; + + if (!destinationFrame) + { + tempRect.width = baseTexture.realWidth; + tempRect.height = baseTexture.realHeight; + + destinationFrame = tempRect; + } + + if (!sourceFrame) + { + sourceFrame = destinationFrame; + } + + this.renderer.framebuffer.bind(baseTexture.frameBuffer, destinationFrame); + + this.renderer.projection.update(destinationFrame, sourceFrame, baseTexture.resolution, false); + this.renderer.stencil.setMaskStack(baseTexture.stencilMaskStack); + } + else + { + // TODO these validation checks happen deeper down.. + // thing they can be avoided.. + if (!destinationFrame) + { + tempRect.width = renderer.width; + tempRect.height = renderer.height; + + destinationFrame = tempRect; + } + + if (!sourceFrame) + { + sourceFrame = destinationFrame; + } + + renderer.framebuffer.bind(null, destinationFrame); + + // TODO store this.. + this.renderer.projection.update(destinationFrame, sourceFrame, this.renderer.resolution, true); + this.renderer.stencil.setMaskStack(this.defaultMaskStack); + } + + this.destinationFrame.copyFrom(destinationFrame); + } + + /** + * Erases the render texture and fills the drawing area with a colour + * + * @param {number} [clearColor] - The colour + * @return {PIXI.Renderer} Returns itself. + */ + clear(clearColor) + { + if (this.renderTexture) + { + clearColor = clearColor || this.renderTexture.baseTexture.clearColor; + } + else + { + clearColor = clearColor || this.clearColor; + } + + this.renderer.framebuffer.clear(clearColor[0], clearColor[1], clearColor[2], clearColor[3]); + } + + resize()// screenWidth, screenHeight) + { + // resize the root only! + this.bind(null); + } +} diff --git a/packages/core/src/renderers/AbstractRenderer.js b/packages/core/src/renderers/AbstractRenderer.js deleted file mode 100644 index 928670f..0000000 --- a/packages/core/src/renderers/AbstractRenderer.js +++ /dev/null @@ -1,353 +0,0 @@ -import { hex2string, hex2rgb } from '@pixi/utils'; -import { Matrix, Rectangle } from '@pixi/math'; -import { RENDERER_TYPE } from '@pixi/constants'; -import { settings } from '@pixi/settings'; -import { Container } from '@pixi/display'; -import RenderTexture from '../textures/RenderTexture'; -import EventEmitter from 'eventemitter3'; - -const tempMatrix = new Matrix(); - -/** - * 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 EventEmitter - * @memberof PIXI - */ -export default class AbstractRenderer extends EventEmitter -{ - // eslint-disable-next-line valid-jsdoc - /** - * @param {string} system - The name of the system this renderer is for. - * @param {object} [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.transparent=false] - If the render view is transparent, default false - * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false - * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) - * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. The - * resolution of the renderer retina would be 2. - * @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 {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when rendering, - * stopping pixel interpolation. - */ - constructor(system, options, arg2, arg3) - { - super(); - - // Support for constructor(system, screenWidth, screenHeight, options) - if (typeof options === 'number') - { - options = Object.assign({ - width: options, - height: arg2 || settings.RENDER_OPTIONS.height, - }, arg3); - } - - // 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 = RENDERER_TYPE.UNKNOWN; - - /** - * Measurements of the screen. (0, 0, screenWidth, screenHeight) - * - * Its safe to use as filterArea or hitArea for 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 1 - */ - this.resolution = options.resolution || settings.RESOLUTION; - - /** - * Whether the render view is transparent - * - * @member {boolean} - */ - this.transparent = options.transparent; - - /** - * Whether css dimensions of canvas view should be resized to screen dimensions automatically - * - * @member {boolean} - */ - this.autoResize = options.autoResize || false; - - /** - * Tracks the blend modes useful for this renderer. - * - * @member {object} - */ - this.blendModes = null; - - /** - * 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; - - /** - * If true PixiJS will Math.floor() x/y values when rendering, stopping pixel interpolation. - * Handy for crisp pixel art and speed on legacy devices. - * - * @member {boolean} - */ - this.roundPixels = options.roundPixels; - - /** - * The background color as a number. - * - * @member {number} - * @private - */ - this._backgroundColor = 0x000000; - - /** - * The background color as an [R, G, B] array. - * - * @member {number[]} - * @private - */ - this._backgroundColorRgba = [0, 0, 0, 0]; - - /** - * The background color as a string. - * - * @member {string} - * @private - */ - this._backgroundColorString = '#000000'; - - this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter - - /** - * This temporary display object used as the parent of the currently being rendered item - * - * @member {PIXI.DisplayObject} - * @private - */ - this._tempDisplayObjectParent = new Container(); - - /** - * The last root object that the renderer tried to render. - * - * @member {PIXI.DisplayObject} - * @private - */ - this._lastObjectRendered = this._tempDisplayObjectParent; - - /** - * Collection of plugins. - * @readonly - * @member {object} - */ - this.plugins = {}; - } - - /** - * Initialize the plugins. - * - * @protected - * @param {object} staticMap - The dictionary of staticly saved plugins. - */ - initPlugins(staticMap) - { - 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() - { - return this.view.width; - } - - /** - * Same as view.height, actual number of pixels in the canvas by vertical - * - * @member {number} - * @readonly - * @default 600 - */ - get height() - { - return this.view.height; - } - - /** - * Resizes the screen and canvas to the specified width and height - * Canvas dimensions are multiplied by resolution - * - * @param {number} screenWidth - the new width of the screen - * @param {number} screenHeight - the new height of the screen - */ - resize(screenWidth, screenHeight) - { - this.screen.width = screenWidth; - this.screen.height = screenHeight; - - this.view.width = screenWidth * this.resolution; - this.view.height = screenHeight * this.resolution; - - if (this.autoResize) - { - this.view.style.width = `${screenWidth}px`; - this.view.style.height = `${screenHeight}px`; - } - } - - /** - * 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 {PIXI.DisplayObject} displayObject - The displayObject the object will be generated from - * @param {number} scaleMode - Should be one of the scaleMode consts - * @param {number} resolution - The resolution / device pixel ratio of the texture being generated - * @param {PIXI.Rectangle} [region] - The region of the displayObject, that shall be rendered, - * if no region is specified, defaults to the local bounds of the displayObject. - * @return {PIXI.Texture} a texture of the graphics object - */ - generateTexture(displayObject, scaleMode, resolution, region) - { - region = region || displayObject.getLocalBounds(); - - // 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(region.width | 0, region.height | 0, scaleMode, resolution); - - tempMatrix.tx = -region.x; - tempMatrix.ty = -region.y; - - this.render(displayObject, renderTexture, false, tempMatrix, true); - - return renderTexture; - } - - /** - * Removes everything from the renderer and optionally removes the Canvas DOM element. - * - * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. - */ - destroy(removeView) - { - 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); - } - - this.plugins = null; - - this.type = RENDERER_TYPE.UNKNOWN; - - this.view = null; - - this.screen = null; - - this.resolution = 0; - - this.transparent = false; - - this.autoResize = false; - - this.blendModes = null; - - this.options = null; - - this.preserveDrawingBuffer = false; - this.clearBeforeRender = false; - - this.roundPixels = false; - - this._backgroundColor = 0; - this._backgroundColorRgba = null; - this._backgroundColorString = null; - - this._tempDisplayObjectParent = null; - this._lastObjectRendered = null; - } - - /** - * The background color to fill if not transparent - * - * @member {number} - */ - get backgroundColor() - { - return this._backgroundColor; - } - - set backgroundColor(value) // eslint-disable-line require-jsdoc - { - this._backgroundColor = value; - this._backgroundColorString = hex2string(value); - hex2rgb(value, this._backgroundColorRgba); - } -} diff --git a/packages/core/src/renderers/Renderer.js b/packages/core/src/renderers/Renderer.js deleted file mode 100644 index a2020b1..0000000 --- a/packages/core/src/renderers/Renderer.js +++ /dev/null @@ -1,329 +0,0 @@ -import AbstractRenderer from './AbstractRenderer'; -import { sayHello } from '@pixi/utils'; -import MaskSystem from './systems/MaskSystem'; -import StencilSystem from './systems/StencilSystem'; -import FilterSystem from './systems/filter/FilterSystem'; -import FramebufferSystem from './systems/FramebufferSystem'; -import RenderTextureSystem from './systems/RenderTextureSystem'; -import TextureSystem from './systems/textures/TextureSystem'; -import ProjectionSystem from './systems/ProjectionSystem'; -import StateSystem from './systems/StateSystem'; -import GeometrySystem from './systems/geometry/GeometrySystem'; -import ShaderSystem from './systems/shader/ShaderSystem'; -import ContextSystem from './systems/ContextSystem'; -import BatchSystem from './systems/BatchSystem'; -import TextureGCSystem from './systems/textures/TextureGCSystem'; -import { RENDERER_TYPE } from '@pixi/constants'; -import UniformGroup from '../shader/UniformGroup'; -import { Matrix } from '@pixi/math'; -import Runner from 'mini-runner'; - -/** - * 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 Render works by automatically managing webGLBatchs. - * 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 :) - * - * @class - * @memberof PIXI - * @extends PIXI.AbstractRenderer - */ -export default class Renderer extends AbstractRenderer -{ - // eslint-disable-next-line valid-jsdoc - /** - * - * @param {object} [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.transparent=false] - If the render view is transparent, default false - * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false - * @param {boolean} [options.antialias=false] - sets antialias. If not available natively then FXAA - * antialiasing is used - * @param {boolean} [options.forceFXAA=false] - forces FXAA antialiasing to be used over native. - * FXAA is faster, but may not always look as great - * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. - * The resolution of the renderer retina would be 2. - * @param {boolean} [options.clearBeforeRender=true] - This sets if the renderer will clear - * the canvas or not before the new render pass. If you wish to set this to false, you *must* set - * preserveDrawingBuffer to `true`. - * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, - * enable this if you need to call toDataUrl on the webgl context. - * @param {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when - * rendering, stopping pixel interpolation. - * @param {number} [options.backgroundColor=0x000000] - The background color of the rendered area - * (shown if not transparent). - * @param {boolean} [options.legacy=false] - If true PixiJS will aim to ensure compatibility - * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. - * @param {string} [options.powerPreference] - Parameter passed to webgl context, set to "high-performance" - * for devices with dual graphics card - */ - constructor(options, arg2, arg3) - { - super('WebGL', options, arg2, arg3); - - /** - * The type of this renderer as a standardised const - * - * @member {number} - * @see PIXI.RENDERER_TYPE - */ - this.type = RENDERER_TYPE.WEBGL; - - // this will be set by the contextSystem (this.context) - this.gl = null; - this.CONTEXT_UID = 0; - this.legacy = !!options.legacy; - - // TODO legacy! - - // runners! - this.runners = { - destroy: new Runner('destroy'), - contextChange: new Runner('contextChange', 1), - reset: new Runner('reset'), - update: new Runner('update'), - postrender: new Runner('postrender'), - prerender: new Runner('prerender'), - resize: new Runner('resize', 2), - }; - - 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(GeometrySystem, 'geometry') - .addSystem(FramebufferSystem, 'framebuffer') - .addSystem(StencilSystem, 'stencil') - .addSystem(ProjectionSystem, 'projection') - .addSystem(TextureGCSystem, 'textureGC') - .addSystem(FilterSystem, 'filter') - .addSystem(RenderTextureSystem, 'renderTexture') - .addSystem(BatchSystem, 'batch'); - - this.initPlugins(Renderer.__plugins); - - /** - * The options passed in to create a new webgl context. - * - * @member {object} - * @private - */ - if (options.context) - { - this.context.initFromContext(options.context); - } - else - { - this.context.initFromOptions({ - alpha: this.transparent, - antialias: options.antialias, - premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', - stencil: true, - 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); - } - - addSystem(_class, name) - { - if (!name) - { - name = _class.name; - } - - // TODO - read name from class.name.. - - /* - if(name.includes('System')) - { - name = name.replace('System', ''); - name = name.charAt(0).toLowerCase() + name.slice(1); - } - */ - - const system = new _class(this); - - if (this[name]) - { - throw new Error(`Whoops! ${name} is already a manger`); - } - - this[name] = system; - - for (const i in this.runners) - { - this.runners[i].add(system); - } - - return this; - - /** - * 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. - */ - } - - /** - * Renders the object to its webGL view - * - * @param {PIXI.DisplayObject} displayObject - the object to be rendered - * @param {PIXI.RenderTexture} renderTexture - The render texture to render to. - * @param {boolean} [clear] - Should the canvas be cleared before the new render - * @param {PIXI.Transform} [transform] - A transform to apply to the render texture before rendering. - * @param {boolean} [skipUpdateTransform] - Should we skip the update transform pass? - */ - render(displayObject, renderTexture, clear, transform, skipUpdateTransform) - { - // can be handy to know! - this.renderingToScreen = !renderTexture; - - this.runners.prerender.run(); - this.emit('prerender'); - - // 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.parent; - - displayObject.parent = this._tempDisplayObjectParent; - displayObject.updateTransform(); - displayObject.parent = 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.run(); - - this.emit('postrender'); - } - - /** - * Resizes the webGL view to the specified width and height. - * - * @param {number} screenWidth - the new width of the screen - * @param {number} screenHeight - the new height of the screen - */ - resize(screenWidth, screenHeight) - { - AbstractRenderer.prototype.resize.call(this, screenWidth, screenHeight); - - this.runners.resize.run(screenWidth, screenHeight); - } - - /** - * Resets the WebGL state so you can render things however you fancy! - * - * @return {PIXI.Renderer} Returns itself. - */ - reset() - { - this.runners.reset.run(); - - return this; - } - - clear() - { - this.framebuffer.bind(); - this.framebuffer.clear(); - } - - /** - * Removes everything from the renderer (event listeners, spritebatch, etc...) - * - * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. - * See: https://github.com/pixijs/pixi.js/issues/2233 - */ - destroy(removeView) - { - this.runners.destroy.run(); - - // call base destroy - super.destroy(removeView); - - // TODO nullify all the managers.. - this.gl = null; - } - - /** - * 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 - * builds and excluding plugins. - * @name PIXI.Renderer#plugins - * @type {object} - * @readonly - * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. - * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. - * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. - * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. - */ - - /** - * Adds a plugin to the renderer. - * - * @method - * @param {string} pluginName - The name of the plugin. - * @param {Function} ctor - The constructor function or class for the plugin. - */ - static registerPlugin(pluginName, ctor) - { - Renderer.__plugins = Renderer.__plugins || {}; - Renderer.__plugins[pluginName] = ctor; - } -} diff --git a/packages/core/src/renderers/State.js b/packages/core/src/renderers/State.js deleted file mode 100644 index fa3f221..0000000 --- a/packages/core/src/renderers/State.js +++ /dev/null @@ -1,163 +0,0 @@ -/* eslint-disable max-len */ - -const BLEND = 0; -const OFFSET = 1; -const CULLING = 2; -const DEPTH_TEST = 3; -const WINDING = 4; - -/** - * This is a webGL state. It is passed The WebGL StateManager. - * Each mesh renderered may require webGL to be in a different state. - * For example you may want different blend mode or to enable polygon offsets - * - * @class - * @memberof PIXI - */ -export default class State -{ - /** - * - */ - constructor() - { - this.data = 0; - - this.blendMode = 0; - this.polygonOffset = 0; - - this.blend = true; - // this.depthTest = true; - } - - /** - * Activates blending of the computed fragment color values - * - * @member {boolean} - */ - get blend() - { - return !!(this.data & (1 << BLEND)); - } - - set blend(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << BLEND)) !== value) - { - this.data ^= (1 << BLEND); - } - } - - /** - * Activates adding an offset to depth values of polygon's fragments - * - * @member {boolean} - * @default false - */ - get offsets() - { - return !!(this.data & (1 << OFFSET)); - } - - set offsets(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << OFFSET)) !== value) - { - this.data ^= (1 << OFFSET); - } - } - - /** - * Activates culling of polygons. - * - * @member {boolean} - * @default false - */ - get culling() - { - return !!(this.data & (1 << CULLING)); - } - - set culling(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << CULLING)) !== value) - { - this.data ^= (1 << CULLING); - } - } - - /** - * Activates depth comparisons and updates to the depth buffer. - * - * @member {boolean} - * @default false - */ - get depthTest() - { - return !!(this.data & (1 << DEPTH_TEST)); - } - - set depthTest(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << DEPTH_TEST)) !== value) - { - this.data ^= (1 << DEPTH_TEST); - } - } - - /** - * Specifies whether or not front or back-facing polygons can be culled. - * @member {boolean} - * @default false - */ - get clockwiseFrontFace() - { - return !!(this.data & (1 << WINDING)); - } - - set clockwiseFrontFace(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << WINDING)) !== value) - { - this.data ^= (1 << WINDING); - } - } - - /** - * The blend mode to be applied when this state is set. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. - * Setting this mode to anything other than NO_BLEND will automatically switch blending on. - * - * @member {boolean} - * @default PIXI.BLEND_MODES.NORMAL - * @see PIXI.BLEND_MODES - */ - get blendMode() - { - return this._blendMode; - } - - set blendMode(value) // eslint-disable-line require-jsdoc - { - // 17 is NO BLEND - this.blend = (value !== 17); - this._blendMode = value; - } - - /** - * The polygon offset. Setting this property to anything other than 0 will automatically enable poygon offset fill. - * - * @member {number} - * @default 0 - */ - get polygonOffset() - { - return this._polygonOffset; - } - - set polygonOffset(value) // eslint-disable-line require-jsdoc - { - this.offsets = !!value; - this._polygonOffset = value; - } -} - diff --git a/packages/core/src/renderers/filters/Filter.js b/packages/core/src/renderers/filters/Filter.js deleted file mode 100644 index 2073b8d..0000000 --- a/packages/core/src/renderers/filters/Filter.js +++ /dev/null @@ -1,140 +0,0 @@ -import Shader from '../../shader/Shader'; -import Program from '../../shader/Program'; -import State from '../State'; -import { settings } from '@pixi/settings'; -// import extractUniformsFromSrc from './extractUniformsFromSrc'; -import defaultVertex from './defaultFilter.vert'; -import defaultFragment from './defaultFilter.frag'; - -// let math = require('../../math'); -/** - * @class - * @memberof PIXI - * @extends PIXI.Shader - */ -export default class Filter extends Shader -{ - /** - * @param {string} [vertexSrc] - The source of the vertex shader. - * @param {string} [fragmentSrc] - The source of the fragment shader. - * @param {object} [uniforms] - Custom uniforms to use to augment the built-in ones. - */ - constructor(vertexSrc, fragmentSrc, uniforms) - { - const program = Program.from(vertexSrc, fragmentSrc); - - super(program, uniforms); - - /** - * The padding of the filter. Some filters require extra space to breath such as a blur. - * Increasing this will add extra width and height to the bounds of the object that the - * filter is applied to. - * - * @member {number} - */ - this.padding = 0; - - /** - * The resolution of the filter. Setting this to be lower will lower the quality but - * increase the performance of the filter. - * - * @member {number} - */ - this.resolution = settings.RESOLUTION; - - /** - * If enabled is true the filter is applied, if false it will not. - * - * @member {boolean} - */ - this.enabled = true; - - /** - * If enabled, PixiJS will fit the filter area into boundaries for better performance. - * Switch it off if it does not work for specific shader. - * - * @member {boolean} - */ - this.autoFit = true; - - /** - * Legacy filters use position and uvs from attributes - * @member {boolean} - * @readonly - */ - this.legacy = !!this.program.attributeData.aTextureCoord; - - /** - * the webGL state the filter requires to render - * @member {PIXI.State} - */ - this.state = new State(); - } - - /** - * Applies the filter - * - * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from - * @param {PIXI.RenderTarget} input - The input render target. - * @param {PIXI.RenderTarget} output - The target to output to. - * @param {boolean} clear - Should the output be cleared before rendering to it - * @param {object} [currentState] - It's current state of filter. - * There are some useful properties in the currentState : - * target, filters, sourceFrame, destinationFrame, renderTarget, resolution - */ - apply(filterManager, input, output, clear, currentState, derp) // eslint-disable-line no-unused-vars - { - // do as you please! - - filterManager.applyFilter(this, input, output, clear, currentState, derp); - - // or just do a regular render.. - } - - /** - * Sets the blendmode of the filter - * - * @member {number} - * @default PIXI.BLEND_MODES.NORMAL - */ - get blendMode() - { - return this.state.blendMode; - } - - set blendMode(value) // eslint-disable-line require-jsdoc - { - this.state.blendMode = value; - } - - /** - * The default vertex shader source - * - * @static - * @constant - */ - static get defaultVertexSrc() - { - return defaultVertex; - } - - /** - * The default fragment shader source - * - * @static - * @constant - */ - static get defaultFragmentSrc() - { - return defaultFragment; - } -} - -/** - * Used for caching shader IDs - * - * @static - * @private - */ -Filter.SOURCE_KEY_MAP = {}; - diff --git a/packages/core/src/renderers/filters/defaultFilter.frag b/packages/core/src/renderers/filters/defaultFilter.frag deleted file mode 100644 index a14c1be..0000000 --- a/packages/core/src/renderers/filters/defaultFilter.frag +++ /dev/null @@ -1,22 +0,0 @@ -varying vec2 vTextureCoord; -varying vec2 vFilterCoord; - -uniform sampler2D uSampler; -uniform sampler2D filterSampler; - -void main(void){ - vec4 masky = texture2D(filterSampler, vFilterCoord); - vec4 sample = texture2D(uSampler, vTextureCoord); - vec4 color; - if(mod(vFilterCoord.x, 1.0) > 0.5) - { - color = vec4(1.0, 0.0, 0.0, 1.0); - } - else - { - color = vec4(0.0, 1.0, 0.0, 1.0); - } - // gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0); - gl_FragColor = mix(sample, masky, 0.5); - gl_FragColor *= sample.a; -} diff --git a/packages/core/src/renderers/filters/defaultFilter.vert b/packages/core/src/renderers/filters/defaultFilter.vert deleted file mode 100644 index e1febca..0000000 --- a/packages/core/src/renderers/filters/defaultFilter.vert +++ /dev/null @@ -1,14 +0,0 @@ -attribute vec2 aVertexPosition; -attribute vec2 aTextureCoord; - -uniform mat3 projectionMatrix; -uniform mat3 filterMatrix; - -varying vec2 vTextureCoord; -varying vec2 vFilterCoord; - -void main(void){ - gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); - vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy; - vTextureCoord = aTextureCoord ; -} diff --git a/packages/core/src/renderers/filters/filterTransforms.js b/packages/core/src/renderers/filters/filterTransforms.js deleted file mode 100644 index 87a3cd6..0000000 --- a/packages/core/src/renderers/filters/filterTransforms.js +++ /dev/null @@ -1,50 +0,0 @@ -import { Matrix } from '@pixi/math'; - -/** - * Calculates the mapped matrix - * @param filterArea {Rectangle} The filter area - * @param sprite {Sprite} the target sprite - * @param outputMatrix {Matrix} @alvin - * @private - */ -// this returns a matrix that will normalise map filter cords in the filter to screen space -export function calculateScreenSpaceMatrix(outputMatrix, filterArea, textureSize) -{ - // TODO unwrap? - const mappedMatrix = outputMatrix.identity(); - - mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); - - mappedMatrix.scale(textureSize.width, textureSize.height); - - return mappedMatrix; -} - -export function calculateNormalizedScreenSpaceMatrix(outputMatrix, filterArea, textureSize) -{ - const mappedMatrix = outputMatrix.identity(); - - mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); - - const translateScaleX = (textureSize.width / filterArea.width); - const translateScaleY = (textureSize.height / filterArea.height); - - mappedMatrix.scale(translateScaleX, translateScaleY); - - return mappedMatrix; -} - -// this will map the filter coord so that a texture can be used based on the transform of a sprite -export function calculateSpriteMatrix(outputMatrix, filterArea, textureSize, sprite) -{ - const orig = sprite._texture.orig; - const mappedMatrix = outputMatrix.set(textureSize.width, 0, 0, textureSize.height, filterArea.x, filterArea.y); - const worldTransform = sprite.worldTransform.copyTo(Matrix.TEMP_MATRIX); - - worldTransform.invert(); - mappedMatrix.prepend(worldTransform); - mappedMatrix.scale(1.0 / orig.width, 1.0 / orig.height); - mappedMatrix.translate(sprite.anchor.x, sprite.anchor.y); - - return mappedMatrix; -} diff --git a/packages/core/src/renderers/filters/spriteMask/SpriteMaskFilter.js b/packages/core/src/renderers/filters/spriteMask/SpriteMaskFilter.js deleted file mode 100644 index d5a91a4..0000000 --- a/packages/core/src/renderers/filters/spriteMask/SpriteMaskFilter.js +++ /dev/null @@ -1,63 +0,0 @@ -import Filter from '../Filter'; -import { Matrix } from '@pixi/math'; -import vertex from './spriteMaskFilter.vert'; -import fragment from './spriteMaskFilter.frag'; -import { default as TextureMatrix } from '../../../textures/TextureMatrix'; - -/** - * The SpriteMaskFilter class - * - * @class - * @extends PIXI.Filter - * @memberof PIXI - */ -export default class SpriteMaskFilter extends Filter -{ - /** - * @param {PIXI.Sprite} sprite - the target sprite - */ - constructor(sprite) - { - const maskMatrix = new Matrix(); - - super(vertex, fragment); - - sprite.renderable = false; - - this.maskSprite = sprite; - this.maskMatrix = maskMatrix; - } - - /** - * Applies the filter - * - * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from - * @param {PIXI.RenderTarget} input - The input render target. - * @param {PIXI.RenderTarget} output - The target to output to. - */ - apply(filterManager, input, output) - { - const maskSprite = this.maskSprite; - const tex = this.maskSprite.texture; - - if (!tex.valid) - { - return; - } - if (!tex.transform) - { - // margin = 0.0, let it bleed a bit, shader code becomes easier - // assuming that atlas textures were made with 1-pixel padding - tex.transform = new TextureMatrix(tex, 0.0); - } - tex.transform.update(); - - this.uniforms.mask = tex; - this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite) - .prepend(tex.transform.mapCoord); - this.uniforms.alpha = maskSprite.worldAlpha; - this.uniforms.maskClamp = tex.transform.uClampFrame; - - filterManager.applyFilter(this, input, output); - } -} diff --git a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.frag b/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.frag deleted file mode 100644 index 0e4aef8..0000000 --- a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.frag +++ /dev/null @@ -1,23 +0,0 @@ -varying vec2 vMaskCoord; -varying vec2 vTextureCoord; - -uniform sampler2D uSampler; -uniform sampler2D mask; -uniform float alpha; -uniform vec4 maskClamp; - -void main(void) -{ - float clip = step(3.5, - step(maskClamp.x, vMaskCoord.x) + - step(maskClamp.y, vMaskCoord.y) + - step(vMaskCoord.x, maskClamp.z) + - step(vMaskCoord.y, maskClamp.w)); - - vec4 original = texture2D(uSampler, vTextureCoord); - vec4 masky = texture2D(mask, vMaskCoord); - - original *= (masky.r * masky.a * alpha * clip); - - gl_FragColor = original; -} diff --git a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.vert b/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.vert deleted file mode 100644 index d23a30c..0000000 --- a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.vert +++ /dev/null @@ -1,16 +0,0 @@ -attribute vec2 aVertexPosition; -attribute vec2 aTextureCoord; - -uniform mat3 projectionMatrix; -uniform mat3 otherMatrix; - -varying vec2 vMaskCoord; -varying vec2 vTextureCoord; - -void main(void) -{ - gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); - - vTextureCoord = aTextureCoord; - vMaskCoord = ( otherMatrix * vec3( aTextureCoord, 1.0) ).xy; -} diff --git a/packages/core/src/renderers/systems/BatchSystem.js b/packages/core/src/renderers/systems/BatchSystem.js deleted file mode 100644 index 70e9518..0000000 --- a/packages/core/src/renderers/systems/BatchSystem.js +++ /dev/null @@ -1,68 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import ObjectRenderer from '../utils/ObjectRenderer'; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ - -export default class BatchSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this manager works for. - */ - constructor(renderer) - { - super(renderer); - - /** - * An empty renderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.emptyRenderer = new ObjectRenderer(renderer); - - /** - * The currently active ObjectRenderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.currentRenderer = this.emptyRenderer; - } - - /** - * Changes the current renderer to the one given in parameter - * - * @param {PIXI.ObjectRenderer} objectRenderer - The object renderer to use. - */ - setObjectRenderer(objectRenderer) - { - if (this.currentRenderer === objectRenderer) - { - return; - } - - this.currentRenderer.stop(); - this.currentRenderer = objectRenderer; - - this.renderer.state.setState(objectRenderer.state); - - this.currentRenderer.start(); - } - - /** - * This should be called if you wish to do some custom rendering - * It will basically render anything that may be batched up such as sprites - * - */ - flush() - { - this.setObjectRenderer(this.emptyRenderer); - } - - reset() - { - this.setObjectRenderer(this.emptyRenderer); - } -} diff --git a/packages/core/src/renderers/systems/ContextSystem.js b/packages/core/src/renderers/systems/ContextSystem.js deleted file mode 100644 index 43bf827..0000000 --- a/packages/core/src/renderers/systems/ContextSystem.js +++ /dev/null @@ -1,186 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { settings } from '@pixi/settings'; - -let CONTEXT_UID = 0; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class ContextSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this.webGLVersion = 1; - - this.handleContextLost = this.handleContextLost.bind(this); - this.handleContextRestored = this.handleContextRestored.bind(this); - - this.extensions = {}; - - renderer.view.addEventListener('webglcontextlost', this.handleContextLost, false); - renderer.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); - } - - get isLost() - { - return (!this.gl || this.gl.isContextLost()); - } - - contextChange(gl) - { - this.gl = gl; - - // restore a context if it was previously lost - if (gl.isContextLost() && gl.getExtension('WEBGL_lose_context')) - { - gl.getExtension('WEBGL_lose_context').restoreContext(); - } - } - - initFromContext(gl) - { - this.gl = gl; - this.validateContext(gl); - this.renderer.gl = gl; - this.renderer.CONTEXT_UID = CONTEXT_UID++; - this.renderer.runners.contextChange.run(gl); - } - - initFromOptions(options) - { - const gl = this.createContext(this.renderer.view, options); - - this.initFromContext(gl); - } - - /** - * Helper class to create a webGL Context - * - * @class - * @memberof PIXI.glCore - * @param canvas {HTMLCanvasElement} the canvas element that we will get the context from - * @param options {Object} An options object that gets passed in to the canvas element containing the context attributes, - * see https://developer.mozilla.org/en/docs/Web/API/HTMLCanvasElement/getContext for the options available - * @return {WebGLRenderingContext} the WebGL context - */ - createContext(canvas, options) - { - let gl; - - if (settings.PREFER_WEBGL_2) - { - gl = canvas.getContext('webgl2', options); - } - - if (gl) - { - this.webGLVersion = 2; - } - else - { - this.webGLVersion = 1; - - gl = canvas.getContext('webgl', options) - || canvas.getContext('experimental-webgl', options); - - if (!gl) - { - // fail, not able to get a context - throw new Error('This browser does not support webGL. Try using the canvas renderer'); - } - } - - this.gl = gl; - - this.getExtensions(); - - return gl; - } - - getExtensions() - { - // time to set up default etensions that pixi uses.. - const gl = this.gl; - const extensions = this.extensions; - - if (this.webGLVersion === 1) - { - extensions.drawBuffers = gl.getExtension('WEBGL_draw_buffers'); - extensions.depthTexture = gl.getExtension('WEBKIT_WEBGL_depth_texture'); - extensions.floatTexture = gl.getExtension('OES_texture_float'); - extensions.loseContext = gl.getExtension('WEBGL_lose_context'); - - extensions.vertexArrayObject = gl.getExtension('OES_vertex_array_object') - || gl.getExtension('MOZ_OES_vertex_array_object') - || gl.getExtension('WEBKIT_OES_vertex_array_object'); - } - - // we don't use any specific WebGL 2 ones yet! - } - - /** - * Handles a lost webgl context - * - * @private - * @param {WebGLContextEvent} event - The context lost event. - */ - handleContextLost(event) - { - event.preventDefault(); - } - - /** - * Handles a restored webgl context - * - * @private - */ - handleContextRestored() - { - this.renderer.runners.contextChange.run(this.gl); - } - - destroy() - { - const view = this.renderer.view; - - // remove listeners - view.removeEventListener('webglcontextlost', this.handleContextLost); - view.removeEventListener('webglcontextrestored', this.handleContextRestored); - - this.gl.useProgram(null); - - if (this.extensions.loseContext) - { - this.extensions.loseContext.loseContext(); - } - } - - postrender() - { - this.gl.flush(); - } - - validateContext(gl) - { - const attributes = gl.getContextAttributes(); - - // this is going to be fairly simple for now.. but at least we have room to grow! - if (!attributes.stencil) - { - /* eslint-disable max-len */ - - /* eslint-disable no-console */ - console.warn('Provided WebGL context does not have a stencil buffer, masks may not render correctly'); - /* eslint-enable no-console */ - - /* eslint-enable max-len */ - } - } -} diff --git a/packages/core/src/renderers/systems/FramebufferSystem.js b/packages/core/src/renderers/systems/FramebufferSystem.js deleted file mode 100644 index df7902b..0000000 --- a/packages/core/src/renderers/systems/FramebufferSystem.js +++ /dev/null @@ -1,246 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { Rectangle } from '@pixi/math'; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class FramebufferSystem extends WebGLSystem -{ - /** - * Sets up the renderer context and necessary buffers. - * - * @private - */ - contextChange() - { - this.gl = this.renderer.gl; - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - this.current = null; - this.viewport = new Rectangle(); - - this.drawBufferExtension = this.renderer.context.extensions.drawBuffers; - } - - bind(framebuffer, frame) - { - const gl = this.gl; - - this.current = framebuffer; - - if (framebuffer) - { - // TODO cacheing layer! - - const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID] || this.initFramebuffer(framebuffer); - - gl.bindFramebuffer(gl.FRAMEBUFFER, fbo.framebuffer); - // makesure all textures are unbound.. - - // now check for updates... - if (fbo.dirtyId !== framebuffer.dirtyId) - { - fbo.dirtyId = framebuffer.dirtyId; - - if (fbo.dirtyFormat !== framebuffer.dirtyFormat) - { - fbo.dirtyFormat = framebuffer.dirtyFormat; - this.updateFramebuffer(framebuffer); - } - else if (fbo.dirtySize !== framebuffer.dirtySize) - { - fbo.dirtySize = framebuffer.dirtySize; - this.resizeFramebuffer(framebuffer); - } - } - - for (let i = 0; i < framebuffer.colorTextures.length; i++) - { - if (framebuffer.colorTextures[i].texturePart) - { - this.renderer.texture.unbind(framebuffer.colorTextures[i].texture); - } - else - { - this.renderer.texture.unbind(framebuffer.colorTextures[i]); - } - } - - if (framebuffer.depthTexture) - { - this.renderer.texture.unbind(framebuffer.depthTexture); - } - - if (frame) - { - this.setViewport(frame.x, frame.y, frame.width, frame.height); - } - else - { - this.setViewport(0, 0, framebuffer.width, framebuffer.height); - } - } - else - { - gl.bindFramebuffer(gl.FRAMEBUFFER, null); - - if (frame) - { - this.setViewport(frame.x, frame.y, frame.width, frame.height); - } - else - { - this.setViewport(0, 0, this.renderer.width, this.renderer.height); - } - } - } - - setViewport(x, y, width, height) - { - const v = this.viewport; - - if (v.width !== width || v.height !== height || v.x !== x || v.y !== y) - { - v.x = x; - v.y = y; - v.width = width; - v.height = height; - - this.gl.viewport(x, y, width, height); - } - } - - get size() - { - if (this.current) - { - // TODO store temp - return { x: 0, y: 0, width: this.current.width, height: this.current.height }; - } - - return { x: 0, y: 0, width: this.renderer.width, height: this.renderer.height }; - } - - clear(r, g, b, a) - { - const gl = this.gl; - - // TODO clear color can be set only one right? - gl.clearColor(r, g, b, a); - gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); - } - - // private functions... - - initFramebuffer(framebuffer) - { - const gl = this.gl; - - // TODO - make this a class? - const fbo = { - framebuffer: gl.createFramebuffer(), - stencil: null, - dirtyId: 0, - dirtyFormat: 0, - dirtySize: 0, - }; - - framebuffer.glFrameBuffers[this.CONTEXT_UID] = fbo; - - return fbo; - } - - resizeFramebuffer(framebuffer) - { - const gl = this.gl; - - if (framebuffer.stencil || framebuffer.depth) - { - gl.bindRenderbuffer(gl.RENDERBUFFER, this.stencil); - gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); - } - } - - updateFramebuffer(framebuffer) - { - const gl = this.gl; - - const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID]; - - // bind the color texture - const colorTextures = framebuffer.colorTextures; - - let count = colorTextures.length; - - if (!this.drawBufferExtension) - { - count = Math.min(count, 1); - } - - const activeTextures = []; - - for (let i = 0; i < count; i++) - { - const texture = framebuffer.colorTextures[i]; - - if (texture.texturePart) - { - this.renderer.texture.bind(texture.texture, 0); - - gl.framebufferTexture2D(gl.FRAMEBUFFER, - gl.COLOR_ATTACHMENT0 + i, - gl.TEXTURE_CUBE_MAP_NEGATIVE_X + texture.side, - texture.texture._glTextures[this.CONTEXT_UID].texture, - 0); - } - else - { - this.renderer.texture.bind(texture, 0); - - gl.framebufferTexture2D(gl.FRAMEBUFFER, - gl.COLOR_ATTACHMENT0 + i, - gl.TEXTURE_2D, - texture._glTextures[this.CONTEXT_UID].texture, - 0); - } - - activeTextures.push(gl.COLOR_ATTACHMENT0 + i); - } - - if (this.drawBufferExtension && activeTextures.length > 1) - { - this.drawBufferExtension.drawBuffersWEBGL(activeTextures); - } - - if (framebuffer.depthTexture) - { - const depthTextureExt = this.renderer.context.extensions.depthTexture; - - if (depthTextureExt) - { - const depthTexture = framebuffer.depthTexture; - - this.renderer.texture.bind(depthTexture, 0); - - gl.framebufferTexture2D(gl.FRAMEBUFFER, - gl.DEPTH_ATTACHMENT, - gl.TEXTURE_2D, - depthTexture._glTextures[this.CONTEXT_UID].texture, - 0); - } - } - - if (framebuffer.stencil || framebuffer.depth) - { - fbo.stencil = gl.createRenderbuffer(); - - gl.bindRenderbuffer(gl.RENDERBUFFER, fbo.stencil); - - // TODO.. this is depth AND stencil? - gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, fbo.stencil); - gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); - // fbo.enableStencil(); - } - } -} diff --git a/packages/core/src/renderers/systems/MaskSystem.js b/packages/core/src/renderers/systems/MaskSystem.js deleted file mode 100644 index d9c4b1a..0000000 --- a/packages/core/src/renderers/systems/MaskSystem.js +++ /dev/null @@ -1,199 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import AlphaMaskFilter from '../filters/spriteMask/SpriteMaskFilter'; - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class MaskSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - // TODO - we don't need both! - this.scissor = false; - this.scissorData = null; - this.scissorRenderTarget = null; - - this.enableScissor = false; - - this.alphaMaskPool = []; - this.alphaMaskIndex = 0; - } - - /** - * Applies the Mask and adds it to the current filter stack. - * - * @param {PIXI.DisplayObject} target - Display Object to push the mask to - * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. - */ - push(target, maskData) - { - // TODO the root check means scissor rect will not - // be used on render textures more info here: - // https://github.com/pixijs/pixi.js/pull/3545 - - if (maskData.texture) - { - this.pushSpriteMask(target, maskData); - } - else if (this.enableScissor - && !this.scissor - && this.renderer._activeRenderTarget.root - && !this.renderer.stencil.stencilMaskStack.length - && maskData.isFastRect()) - { - const matrix = maskData.worldTransform; - - let rot = Math.atan2(matrix.b, matrix.a); - - // use the nearest degree! - rot = Math.round(rot * (180 / Math.PI)); - - if (rot % 90) - { - this.pushStencilMask(maskData); - } - else - { - this.pushScissorMask(target, maskData); - } - } - else - { - this.pushStencilMask(maskData); - } - } - - /** - * Removes the last mask from the mask stack and doesn't return it. - * - * @param {PIXI.DisplayObject} target - Display Object to pop the mask from - * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. - */ - pop(target, maskData) - { - if (maskData.texture) - { - this.popSpriteMask(target, maskData); - } - else if (this.enableScissor && !this.renderer.stencil.stencilMaskStack.length) - { - this.popScissorMask(target, maskData); - } - else - { - this.popStencilMask(target, maskData); - } - } - - /** - * Applies the Mask and adds it to the current filter stack. - * - * @param {PIXI.RenderTarget} target - Display Object to push the sprite mask to - * @param {PIXI.Sprite} maskData - Sprite to be used as the mask - */ - pushSpriteMask(target, maskData) - { - let alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; - - if (!alphaMaskFilter) - { - alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; - } - - alphaMaskFilter[0].resolution = this.renderer.resolution; - alphaMaskFilter[0].maskSprite = maskData; - - // TODO - may cause issues! - target.filterArea = maskData.getBounds(true); - - this.renderer.filterSystem.pushFilter(target, alphaMaskFilter); - - this.alphaMaskIndex++; - } - - /** - * Removes the last filter from the filter stack and doesn't return it. - * - */ - popSpriteMask() - { - this.renderer.filterSystem.popFilter(); - this.alphaMaskIndex--; - } - - /** - * Applies the Mask and adds it to the current filter stack. - * - * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. - */ - pushStencilMask(maskData) - { - this.renderer.batch.flush(); - this.renderer.stencil.pushStencil(maskData); - } - - /** - * Removes the last filter from the filter stack and doesn't return it. - * - */ - popStencilMask() - { - // this.renderer.currentRenderer.stop(); - this.renderer.stencil.popStencil(); - } - - /** - * - * @param {PIXI.DisplayObject} target - Display Object to push the mask to - * @param {PIXI.Graphics} maskData - The masking data. - */ - pushScissorMask(target, maskData) - { - maskData.renderable = true; - - const renderTarget = this.renderer._activeRenderTarget; - - const bounds = maskData.getBounds(); - - bounds.fit(renderTarget.size); - maskData.renderable = false; - - this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); - - const resolution = this.renderer.resolution; - - this.renderer.gl.scissor( - bounds.x * resolution, - (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, - bounds.width * resolution, - bounds.height * resolution - ); - - this.scissorRenderTarget = renderTarget; - this.scissorData = maskData; - this.scissor = true; - } - - /** - * - * - */ - popScissorMask() - { - this.scissorRenderTarget = null; - this.scissorData = null; - this.scissor = false; - - // must be scissor! - const gl = this.renderer.gl; - - gl.disable(gl.SCISSOR_TEST); - } -} diff --git a/packages/core/src/renderers/systems/ProjectionSystem.js b/packages/core/src/renderers/systems/ProjectionSystem.js deleted file mode 100644 index ec5efdb..0000000 --- a/packages/core/src/renderers/systems/ProjectionSystem.js +++ /dev/null @@ -1,73 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { Matrix } from '@pixi/math'; - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ - -export default class ProjectionSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this.projectionMatrix = new Matrix(); - } - - update(destinationFrame, sourceFrame, resolution, root) - { - this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; - this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; - - this.calculateProjection(this.destinationFrame, this.sourceFrame, resolution, root); - - this.renderer.globalUniforms.uniforms.projectionMatrix = this.projectionMatrix; - this.renderer.globalUniforms.update(); - } - - /** - * Updates the projection matrix based on a projection frame (which is a rectangle) - * - * @param {Rectangle} destinationFrame - The destination frame. - * @param {Rectangle} sourceFrame - The source frame. - */ - calculateProjection(destinationFrame, sourceFrame, resolution, root) - { - const pm = this.projectionMatrix; - - // I don't think we will need this line.. - // pm.identity(); - - if (!root) - { - pm.a = (1 / destinationFrame.width * 2) * resolution; - pm.d = (1 / destinationFrame.height * 2) * resolution; - - pm.tx = -1 - (sourceFrame.x * pm.a); - pm.ty = -1 - (sourceFrame.y * pm.d); - } - else - { - pm.a = (1 / destinationFrame.width * 2) * resolution; - pm.d = (-1 / destinationFrame.height * 2) * resolution; - - pm.tx = -1 - (sourceFrame.x * pm.a); - pm.ty = 1 - (sourceFrame.y * pm.d); - } - } - - /** - * Sets the transform of the active render target to the given matrix - * - * @param {PIXI.Matrix} matrix - The transformation matrix - */ - setTransform()// matrix) - { - // this._activeRenderTarget.transform = matrix; - } -} diff --git a/packages/core/src/renderers/systems/RenderTextureSystem.js b/packages/core/src/renderers/systems/RenderTextureSystem.js deleted file mode 100644 index a133ef1..0000000 --- a/packages/core/src/renderers/systems/RenderTextureSystem.js +++ /dev/null @@ -1,115 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { Rectangle } from '@pixi/math'; - -const tempRect = new Rectangle(); - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ - -export default class RenderTextureSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this.clearColor = renderer._backgroundColorRgba; - - // TODO moe this property somewhere else! - this.defaultMaskStack = []; - this.defaultFilterStack = [{}]; - - // empty render texture? - this.renderTexture = null; - - this.destinationFrame = new Rectangle(); - } - - bind(renderTexture, sourceFrame, destinationFrame) - { - // TODO - do we want this?? - if (this.renderTexture === renderTexture) return; - this.renderTexture = renderTexture; - - const renderer = this.renderer; - - if (renderTexture) - { - const baseTexture = renderTexture.baseTexture; - - if (!destinationFrame) - { - tempRect.width = baseTexture.realWidth; - tempRect.height = baseTexture.realHeight; - - destinationFrame = tempRect; - } - - if (!sourceFrame) - { - sourceFrame = destinationFrame; - } - - this.renderer.framebuffer.bind(baseTexture.frameBuffer, destinationFrame); - - this.renderer.projection.update(destinationFrame, sourceFrame, baseTexture.resolution, false); - this.renderer.stencil.setMaskStack(baseTexture.stencilMaskStack); - } - else - { - // TODO these validation checks happen deeper down.. - // thing they can be avoided.. - if (!destinationFrame) - { - tempRect.width = renderer.width; - tempRect.height = renderer.height; - - destinationFrame = tempRect; - } - - if (!sourceFrame) - { - sourceFrame = destinationFrame; - } - - renderer.framebuffer.bind(null, destinationFrame); - - // TODO store this.. - this.renderer.projection.update(destinationFrame, sourceFrame, this.renderer.resolution, true); - this.renderer.stencil.setMaskStack(this.defaultMaskStack); - } - - this.destinationFrame.copyFrom(destinationFrame); - } - - /** - * Erases the render texture and fills the drawing area with a colour - * - * @param {number} [clearColor] - The colour - * @return {PIXI.Renderer} Returns itself. - */ - clear(clearColor) - { - if (this.renderTexture) - { - clearColor = clearColor || this.renderTexture.baseTexture.clearColor; - } - else - { - clearColor = clearColor || this.clearColor; - } - - this.renderer.framebuffer.clear(clearColor[0], clearColor[1], clearColor[2], clearColor[3]); - } - - resize()// screenWidth, screenHeight) - { - // resize the root only! - this.bind(null); - } -} diff --git a/packages/core/src/renderers/systems/StateSystem.js b/packages/core/src/renderers/systems/StateSystem.js deleted file mode 100755 index d7f4caf..0000000 --- a/packages/core/src/renderers/systems/StateSystem.js +++ /dev/null @@ -1,288 +0,0 @@ -import mapWebGLBlendModesToPixi from '../utils/mapWebGLBlendModesToPixi'; -import WebGLSystem from './WebGLSystem'; -import WebGLState from '../State'; - -const BLEND = 0; -const OFFSET = 1; -const CULLING = 2; -const DEPTH_TEST = 3; -const WINDING = 4; - -/** - * A WebGL state machines - * - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class StateSystem extends WebGLSystem -{ - /** - * @param {WebGLRenderingContext} gl - The current WebGL rendering context - */ - constructor(renderer) - { - super(renderer); - - this.gl = null; - - this.maxAttribs = null; - - // check we have vao.. - this.nativeVaoExtension = null; - - this.attribState = null; - - this.stateId = 0; - this.polygonOffset = 0; - this.blendMode = 17; - - this.map = []; - - // map functions for when we set state.. - this.map[BLEND] = this.setBlend; - this.map[OFFSET] = this.setOffset; - this.map[CULLING] = this.setCullFace; - this.map[DEPTH_TEST] = this.setDepthTest; - this.map[WINDING] = this.setFrontFace; - - this.checks = []; - - this.defaultState = new WebGLState(); - this.defaultState.blend = true; - this.defaultState.depth = true; - } - - contextChange(gl) - { - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; - - this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); - - // check we have vao.. - this.nativeVaoExtension = ( - gl.getExtension('OES_vertex_array_object') - || gl.getExtension('MOZ_OES_vertex_array_object') - || gl.getExtension('WEBKIT_OES_vertex_array_object') - ); - - this.attribState = { - tempAttribState: new Array(this.maxAttribs), - attribState: new Array(this.maxAttribs), - }; - - this.blendModes = mapWebGLBlendModesToPixi(gl); - - this.setState(this.defaultState); - - this.reset(); - } - - /** - * Sets the current state - * - * @param {*} state - The state to set. - */ - setState(state) - { - state = state || this.defaultState; - - // TODO maybe to an object check? ( this.state === state )? - if (this.stateId !== state.data) - { - let diff = this.stateId ^ state.data; - let i = 0; - - // order from least to most common - while (diff) - { - if (diff & 1) - { - // state change! - this.map[i].call(this, !!(state.data & (1 << i))); - } - - diff = diff >> 1; - i++; - } - - this.stateId = state.data; - } - - // based on the above settings we check for specific modes.. - // for example if blend is active we check and set the blend modes - // or of polygon offset is active we check the poly depth. - for (let i = 0; i < this.checks.length; i++) - { - this.checks[i](this, state); - } - } - - /** - * Enables or disabled blending. - * - * @param {boolean} value - Turn on or off webgl blending. - */ - setBlend(value) - { - this.updateCheck(StateSystem.checkBlendMode, value); - - this.gl[value ? 'enable' : 'disable'](this.gl.BLEND); - } - - /** - * Enables or disable polygon offset fill - * - * @param {boolean} value - Turn on or off webgl polygon offset testing. - */ - setOffset(value) - { - this.gl[value ? 'enable' : 'disable'](this.gl.POLYGON_OFFSET_FILL); - } - - /** - * Sets whether to enable or disable depth test. - * - * @param {boolean} value - Turn on or off webgl depth testing. - */ - setDepthTest(value) - { - this.gl[value ? 'enable' : 'disable'](this.gl.DEPTH_TEST); - } - - /** - * Sets whether to enable or disable cull face. - * - * @param {boolean} value - Turn on or off webgl cull face. - */ - setCullFace(value) - { - this.gl[value ? 'enable' : 'disable'](this.gl.CULL_FACE); - } - - /** - * Sets the gl front face. - * - * @param {boolean} value - true is clockwise and false is counter-clockwise - */ - setFrontFace(value) - { - this.gl.frontFace(this.gl[value ? 'CW' : 'CCW']); - } - - /** - * Sets the blend mode. - * - * @param {number} value - The blend mode to set to. - */ - setBlendMode(value) - { - if (value === this.blendMode) - { - return; - } - - this.blendMode = value; - this.gl.blendFunc(this.blendModes[value][0], this.blendModes[value][1]); - } - - /** - * Sets the polygon offset. - * - * @param {number} value - the polygon offset - * @param {number} scale - the polygon offset scale - */ - setPolygonOffset(value, scale) - { - this.gl.polygonOffset(value, scale); - } - - /** - * Disables all the vaos in use - * - */ - resetAttributes() - { - for (let i = 0; i < this.attribState.tempAttribState.length; i++) - { - this.attribState.tempAttribState[i] = 0; - } - - for (let i = 0; i < this.attribState.attribState.length; i++) - { - this.attribState.attribState[i] = 0; - } - - // im going to assume one is always active for performance reasons. - for (let i = 1; i < this.maxAttribs; i++) - { - this.gl.disableVertexAttribArray(i); - } - } - - // used - /** - * Resets all the logic and disables the vaos - */ - reset() - { - // unbind any VAO if they exist.. - if (this.nativeVaoExtension) - { - this.nativeVaoExtension.bindVertexArrayOES(null); - } - - // reset all attributes.. - this.resetAttributes(); - - this.gl.pixelStorei(this.gl.UNPACK_FLIP_Y_WEBGL, false); - - this.setBlendMode(0); - - // TODO? - // this.setState(this.defaultState); - } - - /** - * checks to see which updates should be checked based on which settings have been activated - * for example if blend is enabled then we shold check the blend modes each time the state is changed - * or if poygon fill is activated then we need to check if the polygone offset changes. - * The idea is that we only check what we have too. - * - * @param {Function} func the checking function to add or remove - * @param {boolean} value should the check function be added or removed. - */ - updateCheck(func, value) - { - const index = this.checks.indexOf(func); - - if (value && index === -1) - { - this.checks.push(func); - } - else if (!value && index !== -1) - { - this.checks.splice(index, 1); - } - } - - /** - * A private little wrapper function that we call to check the blend mode. - * - * @static - * @private - * @param {PIXI.StateSystem} System the System to perform the state check on - * @param {PIXI.State} state the state that the blendMode will pulled from - */ - static checkBlendMode(system, state) - { - system.setBlendMode(state.blendMode); - } - - // TODO - add polygon offset? -} diff --git a/packages/core/src/renderers/systems/StencilSystem.js b/packages/core/src/renderers/systems/StencilSystem.js deleted file mode 100644 index 5d538cb..0000000 --- a/packages/core/src/renderers/systems/StencilSystem.js +++ /dev/null @@ -1,133 +0,0 @@ -import WebGLSystem from './WebGLSystem'; - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class StencilSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - this.stencilMaskStack = []; - } - - /** - * Changes the mask stack that is used by this System. - * - * @param {PIXI.Graphics[]} stencilMaskStack - The mask stack - */ - setMaskStack(stencilMaskStack) - { - const gl = this.renderer.gl; - - if (stencilMaskStack.length !== this.stencilMaskStack.length) - { - if (stencilMaskStack.length === 0) - { - gl.disable(gl.STENCIL_TEST); - } - else - { - gl.enable(gl.STENCIL_TEST); - } - } - - this.stencilMaskStack = stencilMaskStack; - } - - /** - * Applies the Mask and adds it to the current stencil stack. @alvin - * - * @param {PIXI.Graphics} graphics - The mask - */ - pushStencil(graphics) - { - this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); - - // this.renderer._activeRenderTarget.attachStencilBuffer(); - - const gl = this.renderer.gl; - const prevMaskCount = this.stencilMaskStack.length; - - if (prevMaskCount === 0) - { - gl.enable(gl.STENCIL_TEST); - } - - this.stencilMaskStack.push(graphics); - - // Increment the refference stencil value where the new mask overlaps with the old ones. - gl.colorMask(false, false, false, false); - gl.stencilFunc(gl.EQUAL, prevMaskCount, this._getBitwiseMask()); - gl.stencilOp(gl.KEEP, gl.KEEP, gl.INCR); - this.renderer.plugins.graphics.render(graphics); - - this._useCurrent(); - } - - /** - * Removes the last mask from the stencil stack. @alvin - */ - popStencil() - { - this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); - - const gl = this.renderer.gl; - const graphics = this.stencilMaskStack.pop(); - - if (this.stencilMaskStack.length === 0) - { - // the stack is empty! - gl.disable(gl.STENCIL_TEST); - gl.clear(gl.STENCIL_BUFFER_BIT); - gl.clearStencil(0); - } - else - { - // Decrement the refference stencil value where the popped mask overlaps with the other ones - gl.colorMask(false, false, false, false); - gl.stencilOp(gl.KEEP, gl.KEEP, gl.DECR); - this.renderer.plugins.graphics.render(graphics); - - this._useCurrent(); - } - } - - /** - * Setup renderer to use the current stencil data. - */ - _useCurrent() - { - const gl = this.renderer.gl; - - gl.colorMask(true, true, true, true); - gl.stencilFunc(gl.EQUAL, this.stencilMaskStack.length, this._getBitwiseMask()); - gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP); - } - - /** - * Fill 1s equal to the number of acitve stencil masks. - * - * @return {number} The bitwise mask. - */ - _getBitwiseMask() - { - return (1 << this.stencilMaskStack.length) - 1; - } - - /** - * Destroys the mask stack. - * - */ - destroy() - { - super.destroy(this); - - this.stencilMaskStack = null; - } -} diff --git a/packages/core/src/renderers/systems/WebGLSystem.js b/packages/core/src/renderers/systems/WebGLSystem.js deleted file mode 100644 index d3d5b31..0000000 --- a/packages/core/src/renderers/systems/WebGLSystem.js +++ /dev/null @@ -1,41 +0,0 @@ -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this manager works for. - */ - constructor(renderer) - { - /** - * The renderer this manager works for. - * - * @member {PIXI.Renderer} - */ - this.renderer = renderer; - - this.renderer.runners.contextChange.add(this); - } - - /** - * Generic method called when there is a WebGL context change. - * - */ - contextChange() - { - // do some codes init! - } - - /** - * Generic destroy methods to be overridden by the subclass - * - */ - destroy() - { - this.renderer.runners.contextChange.remove(this); - this.renderer = null; - } -} diff --git a/packages/core/src/renderers/systems/filter/FilterSystem.js b/packages/core/src/renderers/systems/filter/FilterSystem.js deleted file mode 100644 index e615037..0000000 --- a/packages/core/src/renderers/systems/filter/FilterSystem.js +++ /dev/null @@ -1,431 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; - -import RenderTexture from '../../../textures/RenderTexture'; -import Quad from '../../utils/Quad'; -import QuadUv from '../../utils/QuadUv'; -import { Rectangle } from '@pixi/math'; -import * as filterTransforms from '../../filters/filterTransforms'; -import bitTwiddle from 'bit-twiddle'; -import UniformGroup from '../../../shader/UniformGroup'; -import { DRAW_MODES } from '../../../../../constants'; - -// -/** - * @ignore - * @class - */ -class FilterState -{ - /** - * - */ - constructor() - { - this.renderTexture = null; - this.sourceFrame = new Rectangle(); - this.destinationFrame = new Rectangle(); - this.filters = []; - this.target = null; - this.legacy = false; - this.resolution = 1; - } -} - -/** - * @class - * @memberof PIXI.systems - * @extends PIXI.systems.WebGLSystem - */ -export default class FilterSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - /** - * stores a bunch of PO2 textures used for filtering - * @type {Object} - */ - this.texturePool = {}; - - /** - * a pool for storing filter states, save us creating new ones each tick - * @type {Array} - */ - this.statePool = []; - - /** - * A very simple geometry used when drawing a filter effect to the screen - * @type {Quad} - */ - this.quad = new Quad(); - - this.quadUv = new QuadUv(); - - /** - * Temporary rect for maths - * @type {PIXI.Rectangle} - */ - this.tempRect = new Rectangle(); - - this.activeState = {}; - - /** - * this uniform group is attached to filter uniforms when used - * @type {UniformGroup} - */ - this.globalUniforms = new UniformGroup({ - sourceFrame: this.tempRect, - destinationFrame: this.tempRect, - - // legacy variables - filterArea: new Float32Array(4), - filterClamp: new Float32Array(4), - }, true); - } - - /** - * Adds a new filter to the System. - * - * @param {PIXI.DisplayObject} target - The target of the filter to render. - * @param {PIXI.Filter[]} filters - The filters to apply. - */ - push(target, filters) - { - const renderer = this.renderer; - const filterStack = this.renderer.renderTexture.defaultFilterStack; - const state = this.statePool.pop() || new FilterState(); - - let resolution = filters[0].resolution; - let padding = filters[0].padding; - let autoFit = filters[0].autoFit; - let legacy = filters[0].legacy; - - for (let i = 1; i < filters.length; i++) - { - const filter = filters[i]; - - // lets use the lowest resolution.. - resolution = Math.min(resolution, filter.resolution); - // and the largest amount of padding! - padding = Math.max(padding, filter.padding); - // only auto fit if all filters are autofit - autoFit = autoFit || filter.autoFit; - - legacy = legacy || filter.legacy; - } - - filterStack.push(state); - - state.resolution = resolution; - - state.legacy = legacy; - - // round to whole number based on resolution - // TODO move that to the shader too? - state.sourceFrame = target.filterArea || target.getBounds(true); - - state.sourceFrame.pad(padding); - - if (autoFit) - { - state.sourceFrame.fit(this.renderer.renderTexture.destinationFrame); - } - - state.sourceFrame.round(resolution); - - state.renderTexture = this.getPotFilterTexture(state.sourceFrame.width, state.sourceFrame.height, resolution); - state.filters = filters; - - state.destinationFrame.width = state.renderTexture.width; - state.destinationFrame.height = state.renderTexture.height; - - state.renderTexture.filterFrame = state.sourceFrame; - renderer.renderTexture.bind(state.renderTexture, state.sourceFrame);// /, state.destinationFrame); - renderer.renderTexture.clear(); - } - - /** - * Pops off the filter and applies it. - * - */ - pop() - { - const renderer = this.renderer; - const filterStack = renderer.renderTexture.defaultFilterStack; - const state = filterStack.pop(); - const filters = state.filters; - - this.activeState = state; - - const globalUniforms = this.globalUniforms.uniforms; - - globalUniforms.sourceFrame = state.sourceFrame; - globalUniforms.destinationFrame = state.destinationFrame; - globalUniforms.resolution = state.resolution; - - // only update the rect if its legacy.. - if (state.legacy) - { - const filterArea = globalUniforms.filterArea; - const filterClamp = globalUniforms.filterClamp; - - filterArea[0] = state.destinationFrame.width; - filterArea[1] = state.destinationFrame.height; - filterArea[2] = state.sourceFrame.x; - filterArea[3] = state.sourceFrame.y; - - filterClamp[0] = 0.5 / state.resolution / state.destinationFrame.width; - filterClamp[1] = 0.5 / state.resolution / state.destinationFrame.height; - filterClamp[2] = (state.sourceFrame.width - 0.5) / state.resolution / state.destinationFrame.width; - filterClamp[3] = (state.sourceFrame.height - 0.5) / state.resolution / state.destinationFrame.height; - } - - this.globalUniforms.update(); - - const lastState = filterStack[filterStack.length - 1]; - - if (filters.length === 1) - { - filters[0].apply(this, state.renderTexture, lastState.renderTexture, false, state); - - this.returnFilterTexture(state.renderTexture); - } - else - { - let flip = state.renderTexture; - let flop = this.getPotFilterTexture( - flip.width, - flip.height, - state.resolution - ); - - flop.filterFrame = flip.filterFrame; - - let i = 0; - - for (i = 0; i < filters.length - 1; ++i) - { - filters[i].apply(this, flip, flop, true, state); - - const t = flip; - - flip = flop; - flop = t; - } - - filters[i].apply(this, flip, lastState.renderTexture, false, state); - - this.returnFilterTexture(flip); - this.returnFilterTexture(flop); - } - - this.statePool.push(state); - } - - /** - * Draws a filter. - * - * @param {PIXI.Filter} filter - The filter to draw. - * @param {PIXI.RenderTarget} input - The input render target. - * @param {PIXI.RenderTarget} output - The target to output to. - * @param {boolean} clear - Should the output be cleared before rendering to it - */ - applyFilter(filter, input, output, clear) - { - const renderer = this.renderer; - - renderer.renderTexture.bind(output, output ? output.filterFrame : null); - - if (clear) - { - // gl.disable(gl.SCISSOR_TEST); - renderer.renderTexture.clear(); - // gl.enable(gl.SCISSOR_TEST); - } - - // set the uniforms.. - filter.uniforms.uSampler = input; - filter.uniforms.filterGlobals = this.globalUniforms; - - // TODO make it so that the order of this does not matter.. - // because it does at the moment cos of global uniforms. - // they need to get resynced - - renderer.state.setState(filter.state); - renderer.shader.bind(filter); - - if (filter.legacy) - { - this.quadUv.map(input._frame, input.filterFrame); - - renderer.geometry.bind(this.quadUv); - renderer.geometry.draw(DRAW_MODES.TRIANGLES); - } - else - { - renderer.geometry.bind(this.quad); - renderer.geometry.draw(DRAW_MODES.TRIANGLE_STRIP); - } - } - - /** - * Calculates the mapped matrix. - * - * TODO playing around here.. this is temporary - (will end up in the shader) - * this returns a matrix that will normalise map filter cords in the filter to screen space - * - * @param {PIXI.Matrix} outputMatrix - the matrix to output to. - * @return {PIXI.Matrix} The mapped matrix. - */ - calculateScreenSpaceMatrix(outputMatrix) - { - const currentState = this.activeState; - - return filterTransforms.calculateScreenSpaceMatrix( - outputMatrix, - currentState.sourceFrame, - currentState.destinationFrame - ); - } - - /** - * This will map the filter coord so that a texture can be used based on the transform of a sprite - * - * @param {PIXI.Matrix} outputMatrix - The matrix to output to. - * @param {PIXI.Sprite} sprite - The sprite to map to. - * @return {PIXI.Matrix} The mapped matrix. - */ - calculateSpriteMatrix(outputMatrix, sprite) - { - const currentState = this.activeState; - - return filterTransforms.calculateSpriteMatrix( - outputMatrix, - currentState.sourceFrame, - currentState.destinationFrame, - sprite - ); - } - - /** - * Destroys this Filter System. - * - * @param {boolean} [contextLost=false] context was lost, do not free shaders - * - */ - destroy(contextLost = false) - { - if (!contextLost) - { - this.emptyPool(); - } - else - { - this.texturePool = {}; - } - } - - /** - * Gets a Power-of-Two render texture. - * - * TODO move to a seperate class could be on renderer? - * also - could cause issue with multiple contexts? - * - * @private - * @param {WebGLRenderingContext} gl - The webgl rendering context - * @param {number} minWidth - The minimum width of the render target. - * @param {number} minHeight - The minimum height of the render target. - * @param {number} resolution - The resolution of the render target. - * @return {PIXI.RenderTarget} The new render target. - */ - getPotFilterTexture(minWidth, minHeight, resolution) - { - minWidth = bitTwiddle.nextPow2(minWidth); - minHeight = bitTwiddle.nextPow2(minHeight); - resolution = resolution || 1; - - const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); - - if (!this.texturePool[key]) - { - this.texturePool[key] = []; - } - - let renderTexture = this.texturePool[key].pop(); - - if (!renderTexture) - { - // temporary bypass cache.. - // internally - this will cause a texture to be bound.. - renderTexture = RenderTexture.create({ - width: minWidth, - height: minHeight, - resolution, - }); - } - - return renderTexture; - } - - /** - * Gets extra render texture to use inside current filter - * - * @param {number} resolution resolution of the renderTexture - * @returns {PIXI.RenderTexture} - */ - getFilterTexture(resolution) - { - const rt = this.activeState.renderTexture; - - const filterTexture = this.getPotFilterTexture(rt.width, rt.height, resolution || rt.baseTexture.resolution); - - filterTexture.filterFrame = rt.filterFrame; - - return filterTexture; - } - - /** - * Frees a render texture back into the pool. - * - * @param {PIXI.RenderTarget} renderTexture - The renderTarget to free - */ - returnFilterTexture(renderTexture) - { - renderTexture.filterFrame = null; - - const base = renderTexture.baseTexture; - - const minWidth = base.width; - const minHeight = base.height; - - const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); - - this.texturePool[key].push(renderTexture); - } - - /** - * Empties the texture pool. - * - */ - emptyPool() - { - for (const i in this.texturePool) - { - const textures = this.texturePool[i]; - - if (textures) - { - for (let j = 0; j < textures.length; j++) - { - textures[j].destroy(true); - } - } - } - - this.texturePool = {}; - } -} diff --git a/packages/core/src/renderers/systems/geometry/GLBuffer.js b/packages/core/src/renderers/systems/geometry/GLBuffer.js deleted file mode 100644 index 0477e8a..0000000 --- a/packages/core/src/renderers/systems/geometry/GLBuffer.js +++ /dev/null @@ -1,9 +0,0 @@ -export default class GLBuffer -{ - constructor(buffer) - { - this.buffer = buffer; - this.updateID = -1; - this.byteLength = -1; - } -} diff --git a/packages/core/src/AbstractRenderer.js b/packages/core/src/AbstractRenderer.js new file mode 100644 index 0000000..64b2eb1 --- /dev/null +++ b/packages/core/src/AbstractRenderer.js @@ -0,0 +1,353 @@ +import { hex2string, hex2rgb } from '@pixi/utils'; +import { Matrix, Rectangle } from '@pixi/math'; +import { RENDERER_TYPE } from '@pixi/constants'; +import { settings } from '@pixi/settings'; +import { Container } from '@pixi/display'; +import RenderTexture from './renderTexture/RenderTexture'; +import EventEmitter from 'eventemitter3'; + +const tempMatrix = new Matrix(); + +/** + * 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 EventEmitter + * @memberof PIXI + */ +export default class AbstractRenderer extends EventEmitter +{ + // eslint-disable-next-line valid-jsdoc + /** + * @param {string} system - The name of the system this renderer is for. + * @param {object} [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.transparent=false] - If the render view is transparent, default false + * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false + * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. The + * resolution of the renderer retina would be 2. + * @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 {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when rendering, + * stopping pixel interpolation. + */ + constructor(system, options, arg2, arg3) + { + super(); + + // Support for constructor(system, screenWidth, screenHeight, options) + if (typeof options === 'number') + { + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + }, arg3); + } + + // 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 = RENDERER_TYPE.UNKNOWN; + + /** + * Measurements of the screen. (0, 0, screenWidth, screenHeight) + * + * Its safe to use as filterArea or hitArea for 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 1 + */ + this.resolution = options.resolution || settings.RESOLUTION; + + /** + * Whether the render view is transparent + * + * @member {boolean} + */ + this.transparent = options.transparent; + + /** + * Whether css dimensions of canvas view should be resized to screen dimensions automatically + * + * @member {boolean} + */ + this.autoResize = options.autoResize || false; + + /** + * Tracks the blend modes useful for this renderer. + * + * @member {object} + */ + this.blendModes = null; + + /** + * 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; + + /** + * If true PixiJS will Math.floor() x/y values when rendering, stopping pixel interpolation. + * Handy for crisp pixel art and speed on legacy devices. + * + * @member {boolean} + */ + this.roundPixels = options.roundPixels; + + /** + * The background color as a number. + * + * @member {number} + * @private + */ + this._backgroundColor = 0x000000; + + /** + * The background color as an [R, G, B] array. + * + * @member {number[]} + * @private + */ + this._backgroundColorRgba = [0, 0, 0, 0]; + + /** + * The background color as a string. + * + * @member {string} + * @private + */ + this._backgroundColorString = '#000000'; + + this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter + + /** + * This temporary display object used as the parent of the currently being rendered item + * + * @member {PIXI.DisplayObject} + * @private + */ + this._tempDisplayObjectParent = new Container(); + + /** + * The last root object that the renderer tried to render. + * + * @member {PIXI.DisplayObject} + * @private + */ + this._lastObjectRendered = this._tempDisplayObjectParent; + + /** + * Collection of plugins. + * @readonly + * @member {object} + */ + this.plugins = {}; + } + + /** + * Initialize the plugins. + * + * @protected + * @param {object} staticMap - The dictionary of staticly saved plugins. + */ + initPlugins(staticMap) + { + 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() + { + return this.view.width; + } + + /** + * Same as view.height, actual number of pixels in the canvas by vertical + * + * @member {number} + * @readonly + * @default 600 + */ + get height() + { + return this.view.height; + } + + /** + * Resizes the screen and canvas to the specified width and height + * Canvas dimensions are multiplied by resolution + * + * @param {number} screenWidth - the new width of the screen + * @param {number} screenHeight - the new height of the screen + */ + resize(screenWidth, screenHeight) + { + this.screen.width = screenWidth; + this.screen.height = screenHeight; + + this.view.width = screenWidth * this.resolution; + this.view.height = screenHeight * this.resolution; + + if (this.autoResize) + { + this.view.style.width = `${screenWidth}px`; + this.view.style.height = `${screenHeight}px`; + } + } + + /** + * 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 {PIXI.DisplayObject} displayObject - The displayObject the object will be generated from + * @param {number} scaleMode - Should be one of the scaleMode consts + * @param {number} resolution - The resolution / device pixel ratio of the texture being generated + * @param {PIXI.Rectangle} [region] - The region of the displayObject, that shall be rendered, + * if no region is specified, defaults to the local bounds of the displayObject. + * @return {PIXI.Texture} a texture of the graphics object + */ + generateTexture(displayObject, scaleMode, resolution, region) + { + region = region || displayObject.getLocalBounds(); + + // 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(region.width | 0, region.height | 0, scaleMode, resolution); + + tempMatrix.tx = -region.x; + tempMatrix.ty = -region.y; + + this.render(displayObject, renderTexture, false, tempMatrix, true); + + return renderTexture; + } + + /** + * Removes everything from the renderer and optionally removes the Canvas DOM element. + * + * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. + */ + destroy(removeView) + { + 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); + } + + this.plugins = null; + + this.type = RENDERER_TYPE.UNKNOWN; + + this.view = null; + + this.screen = null; + + this.resolution = 0; + + this.transparent = false; + + this.autoResize = false; + + this.blendModes = null; + + this.options = null; + + this.preserveDrawingBuffer = false; + this.clearBeforeRender = false; + + this.roundPixels = false; + + this._backgroundColor = 0; + this._backgroundColorRgba = null; + this._backgroundColorString = null; + + this._tempDisplayObjectParent = null; + this._lastObjectRendered = null; + } + + /** + * The background color to fill if not transparent + * + * @member {number} + */ + get backgroundColor() + { + return this._backgroundColor; + } + + set backgroundColor(value) // eslint-disable-line require-jsdoc + { + this._backgroundColor = value; + this._backgroundColorString = hex2string(value); + hex2rgb(value, this._backgroundColorRgba); + } +} diff --git a/packages/core/src/Renderer.js b/packages/core/src/Renderer.js new file mode 100644 index 0000000..2227e78 --- /dev/null +++ b/packages/core/src/Renderer.js @@ -0,0 +1,338 @@ +import AbstractRenderer from './AbstractRenderer'; +import { sayHello } from '@pixi/utils'; +import MaskSystem from './mask/MaskSystem'; +import StencilSystem from './mask/StencilSystem'; +import FilterSystem from './filters/FilterSystem'; +import FramebufferSystem from './framebuffer/FramebufferSystem'; +import RenderTextureSystem from './renderTexture/RenderTextureSystem'; +import TextureSystem from './textures/TextureSystem'; +import ProjectionSystem from './projection/ProjectionSystem'; +import StateSystem from './state/StateSystem'; +import GeometrySystem from './geometry/GeometrySystem'; +import ShaderSystem from './shader/ShaderSystem'; +import ContextSystem from './context/ContextSystem'; +import BatchSystem from './batch/BatchSystem'; +import TextureGCSystem from './textures/TextureGCSystem'; +import { RENDERER_TYPE } from '@pixi/constants'; +import UniformGroup from './shader/UniformGroup'; +import { Matrix } from '@pixi/math'; +import Runner from 'mini-runner'; + +/** + * 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 Render works by automatically managing webGLBatchs. + * 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 :) + * + * @class + * @memberof PIXI + * @extends PIXI.AbstractRenderer + */ +export default class Renderer extends AbstractRenderer +{ + // eslint-disable-next-line valid-jsdoc + /** + * + * @param {object} [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.transparent=false] - If the render view is transparent, default false + * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false + * @param {boolean} [options.antialias=false] - sets antialias. If not available natively then FXAA + * antialiasing is used + * @param {boolean} [options.forceFXAA=false] - forces FXAA antialiasing to be used over native. + * FXAA is faster, but may not always look as great + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. + * The resolution of the renderer retina would be 2. + * @param {boolean} [options.clearBeforeRender=true] - This sets if the renderer will clear + * the canvas or not before the new render pass. If you wish to set this to false, you *must* set + * preserveDrawingBuffer to `true`. + * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, + * enable this if you need to call toDataUrl on the webgl context. + * @param {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when + * rendering, stopping pixel interpolation. + * @param {number} [options.backgroundColor=0x000000] - The background color of the rendered area + * (shown if not transparent). + * @param {boolean} [options.legacy=false] - If true PixiJS will aim to ensure compatibility + * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. + * @param {string} [options.powerPreference] - Parameter passed to webgl context, set to "high-performance" + * for devices with dual graphics card + */ + constructor(options, arg2, arg3) + { + super('WebGL', options, arg2, arg3); + + /** + * The type of this renderer as a standardised const + * + * @member {number} + * @see PIXI.RENDERER_TYPE + */ + this.type = RENDERER_TYPE.WEBGL; + + // this will be set by the contextSystem (this.context) + this.gl = null; + this.CONTEXT_UID = 0; + this.legacy = !!options.legacy; + + // TODO legacy! + + // runners! + this.runners = { + destroy: new Runner('destroy'), + contextChange: new Runner('contextChange', 1), + reset: new Runner('reset'), + update: new Runner('update'), + postrender: new Runner('postrender'), + prerender: new Runner('prerender'), + resize: new Runner('resize', 2), + }; + + 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(GeometrySystem, 'geometry') + .addSystem(FramebufferSystem, 'framebuffer') + .addSystem(StencilSystem, 'stencil') + .addSystem(ProjectionSystem, 'projection') + .addSystem(TextureGCSystem, 'textureGC') + .addSystem(FilterSystem, 'filter') + .addSystem(RenderTextureSystem, 'renderTexture') + .addSystem(BatchSystem, 'batch'); + + this.initPlugins(Renderer.__plugins); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ + if (options.context) + { + this.context.initFromContext(options.context); + } + else + { + this.context.initFromOptions({ + alpha: this.transparent, + antialias: options.antialias, + premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', + stencil: true, + 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); + } + + /** + * Add a new system to the renderer. + * @param {class} ClassRef - Class reference + * @param {string} [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. + * @return {PIXI.Renderer} Return instance of renderer + */ + addSystem(ClassRef, name) + { + if (!name) + { + name = ClassRef.name; + } + + // TODO - read name from class.name.. + + /* + if(name.includes('System')) + { + name = name.replace('System', ''); + name = name.charAt(0).toLowerCase() + name.slice(1); + } + */ + + const system = new ClassRef(this); + + if (this[name]) + { + throw new Error(`Whoops! ${name} is already a manger`); + } + + this[name] = system; + + for (const i in this.runners) + { + this.runners[i].add(system); + } + + return this; + + /** + * 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. + */ + } + + /** + * Renders the object to its webGL view + * + * @param {PIXI.DisplayObject} displayObject - the object to be rendered + * @param {PIXI.RenderTexture} renderTexture - The render texture to render to. + * @param {boolean} [clear] - Should the canvas be cleared before the new render + * @param {PIXI.Transform} [transform] - A transform to apply to the render texture before rendering. + * @param {boolean} [skipUpdateTransform] - Should we skip the update transform pass? + */ + render(displayObject, renderTexture, clear, transform, skipUpdateTransform) + { + // can be handy to know! + this.renderingToScreen = !renderTexture; + + this.runners.prerender.run(); + this.emit('prerender'); + + // 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.parent; + + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = 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.run(); + + this.emit('postrender'); + } + + /** + * Resizes the webGL view to the specified width and height. + * + * @param {number} screenWidth - the new width of the screen + * @param {number} screenHeight - the new height of the screen + */ + resize(screenWidth, screenHeight) + { + AbstractRenderer.prototype.resize.call(this, screenWidth, screenHeight); + + this.runners.resize.run(screenWidth, screenHeight); + } + + /** + * Resets the WebGL state so you can render things however you fancy! + * + * @return {PIXI.Renderer} Returns itself. + */ + reset() + { + this.runners.reset.run(); + + return this; + } + + clear() + { + this.framebuffer.bind(); + this.framebuffer.clear(); + } + + /** + * Removes everything from the renderer (event listeners, spritebatch, etc...) + * + * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. + * See: https://github.com/pixijs/pixi.js/issues/2233 + */ + destroy(removeView) + { + this.runners.destroy.run(); + + // call base destroy + super.destroy(removeView); + + // TODO nullify all the managers.. + this.gl = null; + } + + /** + * 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 + * builds and excluding plugins. + * @name PIXI.Renderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. + */ + + /** + * Adds a plugin to the renderer. + * + * @method + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + static registerPlugin(pluginName, ctor) + { + Renderer.__plugins = Renderer.__plugins || {}; + Renderer.__plugins[pluginName] = ctor; + } +} diff --git a/packages/core/src/System.js b/packages/core/src/System.js new file mode 100644 index 0000000..05a10c8 --- /dev/null +++ b/packages/core/src/System.js @@ -0,0 +1,42 @@ +/** + * System is a base class used for extending systems used by the {@link PIXI.Renderer} + * @see PIXI.Renderer#addSystem + * @class + * @memberof PIXI + */ +export default class System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + /** + * The renderer this manager works for. + * + * @member {PIXI.Renderer} + */ + this.renderer = renderer; + + this.renderer.runners.contextChange.add(this); + } + + /** + * Generic method called when there is a WebGL context change. + * + */ + contextChange() + { + // do some codes init! + } + + /** + * Generic destroy methods to be overridden by the subclass + * + */ + destroy() + { + this.renderer.runners.contextChange.remove(this); + this.renderer = null; + } +} diff --git a/packages/core/src/batch/BatchSystem.js b/packages/core/src/batch/BatchSystem.js new file mode 100644 index 0000000..b04c249 --- /dev/null +++ b/packages/core/src/batch/BatchSystem.js @@ -0,0 +1,68 @@ +import System from '../System'; +import ObjectRenderer from './ObjectRenderer'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class BatchSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + super(renderer); + + /** + * An empty renderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.emptyRenderer = new ObjectRenderer(renderer); + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.currentRenderer = this.emptyRenderer; + } + + /** + * Changes the current renderer to the one given in parameter + * + * @param {PIXI.ObjectRenderer} objectRenderer - The object renderer to use. + */ + setObjectRenderer(objectRenderer) + { + if (this.currentRenderer === objectRenderer) + { + return; + } + + this.currentRenderer.stop(); + this.currentRenderer = objectRenderer; + + this.renderer.state.setState(objectRenderer.state); + + this.currentRenderer.start(); + } + + /** + * This should be called if you wish to do some custom rendering + * It will basically render anything that may be batched up such as sprites + * + */ + flush() + { + this.setObjectRenderer(this.emptyRenderer); + } + + reset() + { + this.setObjectRenderer(this.emptyRenderer); + } +} diff --git a/packages/core/src/batch/ObjectRenderer.js b/packages/core/src/batch/ObjectRenderer.js new file mode 100644 index 0000000..0c851a9 --- /dev/null +++ b/packages/core/src/batch/ObjectRenderer.js @@ -0,0 +1,48 @@ +import System from '../System'; + +/** + * Base for a common object renderer that can be used as a system renderer plugin. + * + * @class + * @extends PIXI.System + * @memberof PIXI + */ +export default class ObjectRenderer extends System +{ + /** + * Starts the renderer and sets the shader + * + */ + start() + { + // set the shader.. + } + + /** + * Stops the renderer + * + */ + stop() + { + this.flush(); + } + + /** + * Stub method for rendering content and emptying the current batch. + * + */ + flush() + { + // flush! + } + + /** + * Renders an object + * + * @param {PIXI.DisplayObject} object - The object to render. + */ + render(object) // eslint-disable-line no-unused-vars + { + // render the object + } +} diff --git a/packages/core/src/context/ContextSystem.js b/packages/core/src/context/ContextSystem.js new file mode 100644 index 0000000..301aab4 --- /dev/null +++ b/packages/core/src/context/ContextSystem.js @@ -0,0 +1,186 @@ +import System from '../System'; +import { settings } from '@pixi/settings'; + +let CONTEXT_UID = 0; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class ContextSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.webGLVersion = 1; + + this.handleContextLost = this.handleContextLost.bind(this); + this.handleContextRestored = this.handleContextRestored.bind(this); + + this.extensions = {}; + + renderer.view.addEventListener('webglcontextlost', this.handleContextLost, false); + renderer.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + } + + get isLost() + { + return (!this.gl || this.gl.isContextLost()); + } + + contextChange(gl) + { + this.gl = gl; + + // restore a context if it was previously lost + if (gl.isContextLost() && gl.getExtension('WEBGL_lose_context')) + { + gl.getExtension('WEBGL_lose_context').restoreContext(); + } + } + + initFromContext(gl) + { + this.gl = gl; + this.validateContext(gl); + this.renderer.gl = gl; + this.renderer.CONTEXT_UID = CONTEXT_UID++; + this.renderer.runners.contextChange.run(gl); + } + + initFromOptions(options) + { + const gl = this.createContext(this.renderer.view, options); + + this.initFromContext(gl); + } + + /** + * Helper class to create a webGL Context + * + * @class + * @memberof PIXI.glCore + * @param canvas {HTMLCanvasElement} the canvas element that we will get the context from + * @param options {Object} An options object that gets passed in to the canvas element containing the context attributes, + * see https://developer.mozilla.org/en/docs/Web/API/HTMLCanvasElement/getContext for the options available + * @return {WebGLRenderingContext} the WebGL context + */ + createContext(canvas, options) + { + let gl; + + if (settings.PREFER_WEBGL_2) + { + gl = canvas.getContext('webgl2', options); + } + + if (gl) + { + this.webGLVersion = 2; + } + else + { + this.webGLVersion = 1; + + gl = canvas.getContext('webgl', options) + || canvas.getContext('experimental-webgl', options); + + if (!gl) + { + // fail, not able to get a context + throw new Error('This browser does not support webGL. Try using the canvas renderer'); + } + } + + this.gl = gl; + + this.getExtensions(); + + return gl; + } + + getExtensions() + { + // time to set up default etensions that pixi uses.. + const gl = this.gl; + const extensions = this.extensions; + + if (this.webGLVersion === 1) + { + extensions.drawBuffers = gl.getExtension('WEBGL_draw_buffers'); + extensions.depthTexture = gl.getExtension('WEBKIT_WEBGL_depth_texture'); + extensions.floatTexture = gl.getExtension('OES_texture_float'); + extensions.loseContext = gl.getExtension('WEBGL_lose_context'); + + extensions.vertexArrayObject = gl.getExtension('OES_vertex_array_object') + || gl.getExtension('MOZ_OES_vertex_array_object') + || gl.getExtension('WEBKIT_OES_vertex_array_object'); + } + + // we don't use any specific WebGL 2 ones yet! + } + + /** + * Handles a lost webgl context + * + * @private + * @param {WebGLContextEvent} event - The context lost event. + */ + handleContextLost(event) + { + event.preventDefault(); + } + + /** + * Handles a restored webgl context + * + * @private + */ + handleContextRestored() + { + this.renderer.runners.contextChange.run(this.gl); + } + + destroy() + { + const view = this.renderer.view; + + // remove listeners + view.removeEventListener('webglcontextlost', this.handleContextLost); + view.removeEventListener('webglcontextrestored', this.handleContextRestored); + + this.gl.useProgram(null); + + if (this.extensions.loseContext) + { + this.extensions.loseContext.loseContext(); + } + } + + postrender() + { + this.gl.flush(); + } + + validateContext(gl) + { + const attributes = gl.getContextAttributes(); + + // this is going to be fairly simple for now.. but at least we have room to grow! + if (!attributes.stencil) + { + /* eslint-disable max-len */ + + /* eslint-disable no-console */ + console.warn('Provided WebGL context does not have a stencil buffer, masks may not render correctly'); + /* eslint-enable no-console */ + + /* eslint-enable max-len */ + } + } +} diff --git a/packages/core/src/filters/Filter.js b/packages/core/src/filters/Filter.js new file mode 100644 index 0000000..9d8cad8 --- /dev/null +++ b/packages/core/src/filters/Filter.js @@ -0,0 +1,140 @@ +import Shader from '../shader/Shader'; +import Program from '../shader/Program'; +import State from '../state/State'; +import { settings } from '@pixi/settings'; +// import extractUniformsFromSrc from './extractUniformsFromSrc'; +import defaultVertex from './defaultFilter.vert'; +import defaultFragment from './defaultFilter.frag'; + +// let math = require('../../math'); +/** + * @class + * @memberof PIXI + * @extends PIXI.Shader + */ +export default class Filter extends Shader +{ + /** + * @param {string} [vertexSrc] - The source of the vertex shader. + * @param {string} [fragmentSrc] - The source of the fragment shader. + * @param {object} [uniforms] - Custom uniforms to use to augment the built-in ones. + */ + constructor(vertexSrc, fragmentSrc, uniforms) + { + const program = Program.from(vertexSrc, fragmentSrc); + + super(program, uniforms); + + /** + * The padding of the filter. Some filters require extra space to breath such as a blur. + * Increasing this will add extra width and height to the bounds of the object that the + * filter is applied to. + * + * @member {number} + */ + this.padding = 0; + + /** + * The resolution of the filter. Setting this to be lower will lower the quality but + * increase the performance of the filter. + * + * @member {number} + */ + this.resolution = settings.RESOLUTION; + + /** + * If enabled is true the filter is applied, if false it will not. + * + * @member {boolean} + */ + this.enabled = true; + + /** + * If enabled, PixiJS will fit the filter area into boundaries for better performance. + * Switch it off if it does not work for specific shader. + * + * @member {boolean} + */ + this.autoFit = true; + + /** + * Legacy filters use position and uvs from attributes + * @member {boolean} + * @readonly + */ + this.legacy = !!this.program.attributeData.aTextureCoord; + + /** + * the webGL state the filter requires to render + * @member {PIXI.State} + */ + this.state = new State(); + } + + /** + * Applies the filter + * + * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + * @param {boolean} clear - Should the output be cleared before rendering to it + * @param {object} [currentState] - It's current state of filter. + * There are some useful properties in the currentState : + * target, filters, sourceFrame, destinationFrame, renderTarget, resolution + */ + apply(filterManager, input, output, clear, currentState, derp) // eslint-disable-line no-unused-vars + { + // do as you please! + + filterManager.applyFilter(this, input, output, clear, currentState, derp); + + // or just do a regular render.. + } + + /** + * Sets the blendmode of the filter + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL + */ + get blendMode() + { + return this.state.blendMode; + } + + set blendMode(value) // eslint-disable-line require-jsdoc + { + this.state.blendMode = value; + } + + /** + * The default vertex shader source + * + * @static + * @constant + */ + static get defaultVertexSrc() + { + return defaultVertex; + } + + /** + * The default fragment shader source + * + * @static + * @constant + */ + static get defaultFragmentSrc() + { + return defaultFragment; + } +} + +/** + * Used for caching shader IDs + * + * @static + * @private + */ +Filter.SOURCE_KEY_MAP = {}; + diff --git a/packages/core/src/filters/FilterSystem.js b/packages/core/src/filters/FilterSystem.js new file mode 100644 index 0000000..3174318 --- /dev/null +++ b/packages/core/src/filters/FilterSystem.js @@ -0,0 +1,431 @@ +import System from '../System'; + +import RenderTexture from '../renderTexture/RenderTexture'; +import Quad from '../utils/Quad'; +import QuadUv from '../utils/QuadUv'; +import { Rectangle } from '@pixi/math'; +import * as filterTransforms from './filterTransforms'; +import bitTwiddle from 'bit-twiddle'; +import UniformGroup from '../shader/UniformGroup'; +import { DRAW_MODES } from '@pixi/constants'; + +// +/** + * @ignore + * @class + */ +class FilterState +{ + /** + * + */ + constructor() + { + this.renderTexture = null; + this.sourceFrame = new Rectangle(); + this.destinationFrame = new Rectangle(); + this.filters = []; + this.target = null; + this.legacy = false; + this.resolution = 1; + } +} + +/** + * @class + * @memberof PIXI.systems + * @extends PIXI.System + */ +export default class FilterSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + /** + * stores a bunch of PO2 textures used for filtering + * @type {Object} + */ + this.texturePool = {}; + + /** + * a pool for storing filter states, save us creating new ones each tick + * @type {Array} + */ + this.statePool = []; + + /** + * A very simple geometry used when drawing a filter effect to the screen + * @type {Quad} + */ + this.quad = new Quad(); + + this.quadUv = new QuadUv(); + + /** + * Temporary rect for maths + * @type {PIXI.Rectangle} + */ + this.tempRect = new Rectangle(); + + this.activeState = {}; + + /** + * this uniform group is attached to filter uniforms when used + * @type {UniformGroup} + */ + this.globalUniforms = new UniformGroup({ + sourceFrame: this.tempRect, + destinationFrame: this.tempRect, + + // legacy variables + filterArea: new Float32Array(4), + filterClamp: new Float32Array(4), + }, true); + } + + /** + * Adds a new filter to the System. + * + * @param {PIXI.DisplayObject} target - The target of the filter to render. + * @param {PIXI.Filter[]} filters - The filters to apply. + */ + push(target, filters) + { + const renderer = this.renderer; + const filterStack = this.renderer.renderTexture.defaultFilterStack; + const state = this.statePool.pop() || new FilterState(); + + let resolution = filters[0].resolution; + let padding = filters[0].padding; + let autoFit = filters[0].autoFit; + let legacy = filters[0].legacy; + + for (let i = 1; i < filters.length; i++) + { + const filter = filters[i]; + + // lets use the lowest resolution.. + resolution = Math.min(resolution, filter.resolution); + // and the largest amount of padding! + padding = Math.max(padding, filter.padding); + // only auto fit if all filters are autofit + autoFit = autoFit || filter.autoFit; + + legacy = legacy || filter.legacy; + } + + filterStack.push(state); + + state.resolution = resolution; + + state.legacy = legacy; + + // round to whole number based on resolution + // TODO move that to the shader too? + state.sourceFrame = target.filterArea || target.getBounds(true); + + state.sourceFrame.pad(padding); + + if (autoFit) + { + state.sourceFrame.fit(this.renderer.renderTexture.destinationFrame); + } + + state.sourceFrame.round(resolution); + + state.renderTexture = this.getPotFilterTexture(state.sourceFrame.width, state.sourceFrame.height, resolution); + state.filters = filters; + + state.destinationFrame.width = state.renderTexture.width; + state.destinationFrame.height = state.renderTexture.height; + + state.renderTexture.filterFrame = state.sourceFrame; + renderer.renderTexture.bind(state.renderTexture, state.sourceFrame);// /, state.destinationFrame); + renderer.renderTexture.clear(); + } + + /** + * Pops off the filter and applies it. + * + */ + pop() + { + const renderer = this.renderer; + const filterStack = renderer.renderTexture.defaultFilterStack; + const state = filterStack.pop(); + const filters = state.filters; + + this.activeState = state; + + const globalUniforms = this.globalUniforms.uniforms; + + globalUniforms.sourceFrame = state.sourceFrame; + globalUniforms.destinationFrame = state.destinationFrame; + globalUniforms.resolution = state.resolution; + + // only update the rect if its legacy.. + if (state.legacy) + { + const filterArea = globalUniforms.filterArea; + const filterClamp = globalUniforms.filterClamp; + + filterArea[0] = state.destinationFrame.width; + filterArea[1] = state.destinationFrame.height; + filterArea[2] = state.sourceFrame.x; + filterArea[3] = state.sourceFrame.y; + + filterClamp[0] = 0.5 / state.resolution / state.destinationFrame.width; + filterClamp[1] = 0.5 / state.resolution / state.destinationFrame.height; + filterClamp[2] = (state.sourceFrame.width - 0.5) / state.resolution / state.destinationFrame.width; + filterClamp[3] = (state.sourceFrame.height - 0.5) / state.resolution / state.destinationFrame.height; + } + + this.globalUniforms.update(); + + const lastState = filterStack[filterStack.length - 1]; + + if (filters.length === 1) + { + filters[0].apply(this, state.renderTexture, lastState.renderTexture, false, state); + + this.returnFilterTexture(state.renderTexture); + } + else + { + let flip = state.renderTexture; + let flop = this.getPotFilterTexture( + flip.width, + flip.height, + state.resolution + ); + + flop.filterFrame = flip.filterFrame; + + let i = 0; + + for (i = 0; i < filters.length - 1; ++i) + { + filters[i].apply(this, flip, flop, true, state); + + const t = flip; + + flip = flop; + flop = t; + } + + filters[i].apply(this, flip, lastState.renderTexture, false, state); + + this.returnFilterTexture(flip); + this.returnFilterTexture(flop); + } + + this.statePool.push(state); + } + + /** + * Draws a filter. + * + * @param {PIXI.Filter} filter - The filter to draw. + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + * @param {boolean} clear - Should the output be cleared before rendering to it + */ + applyFilter(filter, input, output, clear) + { + const renderer = this.renderer; + + renderer.renderTexture.bind(output, output ? output.filterFrame : null); + + if (clear) + { + // gl.disable(gl.SCISSOR_TEST); + renderer.renderTexture.clear(); + // gl.enable(gl.SCISSOR_TEST); + } + + // set the uniforms.. + filter.uniforms.uSampler = input; + filter.uniforms.filterGlobals = this.globalUniforms; + + // TODO make it so that the order of this does not matter.. + // because it does at the moment cos of global uniforms. + // they need to get resynced + + renderer.state.setState(filter.state); + renderer.shader.bind(filter); + + if (filter.legacy) + { + this.quadUv.map(input._frame, input.filterFrame); + + renderer.geometry.bind(this.quadUv); + renderer.geometry.draw(DRAW_MODES.TRIANGLES); + } + else + { + renderer.geometry.bind(this.quad); + renderer.geometry.draw(DRAW_MODES.TRIANGLE_STRIP); + } + } + + /** + * Calculates the mapped matrix. + * + * TODO playing around here.. this is temporary - (will end up in the shader) + * this returns a matrix that will normalise map filter cords in the filter to screen space + * + * @param {PIXI.Matrix} outputMatrix - the matrix to output to. + * @return {PIXI.Matrix} The mapped matrix. + */ + calculateScreenSpaceMatrix(outputMatrix) + { + const currentState = this.activeState; + + return filterTransforms.calculateScreenSpaceMatrix( + outputMatrix, + currentState.sourceFrame, + currentState.destinationFrame + ); + } + + /** + * This will map the filter coord so that a texture can be used based on the transform of a sprite + * + * @param {PIXI.Matrix} outputMatrix - The matrix to output to. + * @param {PIXI.Sprite} sprite - The sprite to map to. + * @return {PIXI.Matrix} The mapped matrix. + */ + calculateSpriteMatrix(outputMatrix, sprite) + { + const currentState = this.activeState; + + return filterTransforms.calculateSpriteMatrix( + outputMatrix, + currentState.sourceFrame, + currentState.destinationFrame, + sprite + ); + } + + /** + * Destroys this Filter System. + * + * @param {boolean} [contextLost=false] context was lost, do not free shaders + * + */ + destroy(contextLost = false) + { + if (!contextLost) + { + this.emptyPool(); + } + else + { + this.texturePool = {}; + } + } + + /** + * Gets a Power-of-Two render texture. + * + * TODO move to a seperate class could be on renderer? + * also - could cause issue with multiple contexts? + * + * @private + * @param {WebGLRenderingContext} gl - The webgl rendering context + * @param {number} minWidth - The minimum width of the render target. + * @param {number} minHeight - The minimum height of the render target. + * @param {number} resolution - The resolution of the render target. + * @return {PIXI.RenderTarget} The new render target. + */ + getPotFilterTexture(minWidth, minHeight, resolution) + { + minWidth = bitTwiddle.nextPow2(minWidth); + minHeight = bitTwiddle.nextPow2(minHeight); + resolution = resolution || 1; + + const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + + if (!this.texturePool[key]) + { + this.texturePool[key] = []; + } + + let renderTexture = this.texturePool[key].pop(); + + if (!renderTexture) + { + // temporary bypass cache.. + // internally - this will cause a texture to be bound.. + renderTexture = RenderTexture.create({ + width: minWidth, + height: minHeight, + resolution, + }); + } + + return renderTexture; + } + + /** + * Gets extra render texture to use inside current filter + * + * @param {number} resolution resolution of the renderTexture + * @returns {PIXI.RenderTexture} + */ + getFilterTexture(resolution) + { + const rt = this.activeState.renderTexture; + + const filterTexture = this.getPotFilterTexture(rt.width, rt.height, resolution || rt.baseTexture.resolution); + + filterTexture.filterFrame = rt.filterFrame; + + return filterTexture; + } + + /** + * Frees a render texture back into the pool. + * + * @param {PIXI.RenderTarget} renderTexture - The renderTarget to free + */ + returnFilterTexture(renderTexture) + { + renderTexture.filterFrame = null; + + const base = renderTexture.baseTexture; + + const minWidth = base.width; + const minHeight = base.height; + + const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + + this.texturePool[key].push(renderTexture); + } + + /** + * Empties the texture pool. + * + */ + emptyPool() + { + for (const i in this.texturePool) + { + const textures = this.texturePool[i]; + + if (textures) + { + for (let j = 0; j < textures.length; j++) + { + textures[j].destroy(true); + } + } + } + + this.texturePool = {}; + } +} diff --git a/packages/core/src/filters/defaultFilter.frag b/packages/core/src/filters/defaultFilter.frag new file mode 100644 index 0000000..a14c1be --- /dev/null +++ b/packages/core/src/filters/defaultFilter.frag @@ -0,0 +1,22 @@ +varying vec2 vTextureCoord; +varying vec2 vFilterCoord; + +uniform sampler2D uSampler; +uniform sampler2D filterSampler; + +void main(void){ + vec4 masky = texture2D(filterSampler, vFilterCoord); + vec4 sample = texture2D(uSampler, vTextureCoord); + vec4 color; + if(mod(vFilterCoord.x, 1.0) > 0.5) + { + color = vec4(1.0, 0.0, 0.0, 1.0); + } + else + { + color = vec4(0.0, 1.0, 0.0, 1.0); + } + // gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0); + gl_FragColor = mix(sample, masky, 0.5); + gl_FragColor *= sample.a; +} diff --git a/packages/core/src/filters/defaultFilter.vert b/packages/core/src/filters/defaultFilter.vert new file mode 100644 index 0000000..e1febca --- /dev/null +++ b/packages/core/src/filters/defaultFilter.vert @@ -0,0 +1,14 @@ +attribute vec2 aVertexPosition; +attribute vec2 aTextureCoord; + +uniform mat3 projectionMatrix; +uniform mat3 filterMatrix; + +varying vec2 vTextureCoord; +varying vec2 vFilterCoord; + +void main(void){ + gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); + vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy; + vTextureCoord = aTextureCoord ; +} diff --git a/packages/core/src/filters/filterTransforms.js b/packages/core/src/filters/filterTransforms.js new file mode 100644 index 0000000..87a3cd6 --- /dev/null +++ b/packages/core/src/filters/filterTransforms.js @@ -0,0 +1,50 @@ +import { Matrix } from '@pixi/math'; + +/** + * Calculates the mapped matrix + * @param filterArea {Rectangle} The filter area + * @param sprite {Sprite} the target sprite + * @param outputMatrix {Matrix} @alvin + * @private + */ +// this returns a matrix that will normalise map filter cords in the filter to screen space +export function calculateScreenSpaceMatrix(outputMatrix, filterArea, textureSize) +{ + // TODO unwrap? + const mappedMatrix = outputMatrix.identity(); + + mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); + + mappedMatrix.scale(textureSize.width, textureSize.height); + + return mappedMatrix; +} + +export function calculateNormalizedScreenSpaceMatrix(outputMatrix, filterArea, textureSize) +{ + const mappedMatrix = outputMatrix.identity(); + + mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); + + const translateScaleX = (textureSize.width / filterArea.width); + const translateScaleY = (textureSize.height / filterArea.height); + + mappedMatrix.scale(translateScaleX, translateScaleY); + + return mappedMatrix; +} + +// this will map the filter coord so that a texture can be used based on the transform of a sprite +export function calculateSpriteMatrix(outputMatrix, filterArea, textureSize, sprite) +{ + const orig = sprite._texture.orig; + const mappedMatrix = outputMatrix.set(textureSize.width, 0, 0, textureSize.height, filterArea.x, filterArea.y); + const worldTransform = sprite.worldTransform.copyTo(Matrix.TEMP_MATRIX); + + worldTransform.invert(); + mappedMatrix.prepend(worldTransform); + mappedMatrix.scale(1.0 / orig.width, 1.0 / orig.height); + mappedMatrix.translate(sprite.anchor.x, sprite.anchor.y); + + return mappedMatrix; +} diff --git a/packages/core/src/filters/spriteMask/SpriteMaskFilter.js b/packages/core/src/filters/spriteMask/SpriteMaskFilter.js new file mode 100644 index 0000000..91697b7 --- /dev/null +++ b/packages/core/src/filters/spriteMask/SpriteMaskFilter.js @@ -0,0 +1,63 @@ +import Filter from '../Filter'; +import { Matrix } from '@pixi/math'; +import vertex from './spriteMaskFilter.vert'; +import fragment from './spriteMaskFilter.frag'; +import { default as TextureMatrix } from '../../textures/TextureMatrix'; + +/** + * The SpriteMaskFilter class + * + * @class + * @extends PIXI.Filter + * @memberof PIXI + */ +export default class SpriteMaskFilter extends Filter +{ + /** + * @param {PIXI.Sprite} sprite - the target sprite + */ + constructor(sprite) + { + const maskMatrix = new Matrix(); + + super(vertex, fragment); + + sprite.renderable = false; + + this.maskSprite = sprite; + this.maskMatrix = maskMatrix; + } + + /** + * Applies the filter + * + * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + */ + apply(filterManager, input, output) + { + const maskSprite = this.maskSprite; + const tex = this.maskSprite.texture; + + if (!tex.valid) + { + return; + } + if (!tex.transform) + { + // margin = 0.0, let it bleed a bit, shader code becomes easier + // assuming that atlas textures were made with 1-pixel padding + tex.transform = new TextureMatrix(tex, 0.0); + } + tex.transform.update(); + + this.uniforms.mask = tex; + this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite) + .prepend(tex.transform.mapCoord); + this.uniforms.alpha = maskSprite.worldAlpha; + this.uniforms.maskClamp = tex.transform.uClampFrame; + + filterManager.applyFilter(this, input, output); + } +} diff --git a/packages/core/src/filters/spriteMask/spriteMaskFilter.frag b/packages/core/src/filters/spriteMask/spriteMaskFilter.frag new file mode 100644 index 0000000..0e4aef8 --- /dev/null +++ b/packages/core/src/filters/spriteMask/spriteMaskFilter.frag @@ -0,0 +1,23 @@ +varying vec2 vMaskCoord; +varying vec2 vTextureCoord; + +uniform sampler2D uSampler; +uniform sampler2D mask; +uniform float alpha; +uniform vec4 maskClamp; + +void main(void) +{ + float clip = step(3.5, + step(maskClamp.x, vMaskCoord.x) + + step(maskClamp.y, vMaskCoord.y) + + step(vMaskCoord.x, maskClamp.z) + + step(vMaskCoord.y, maskClamp.w)); + + vec4 original = texture2D(uSampler, vTextureCoord); + vec4 masky = texture2D(mask, vMaskCoord); + + original *= (masky.r * masky.a * alpha * clip); + + gl_FragColor = original; +} diff --git a/packages/core/src/filters/spriteMask/spriteMaskFilter.vert b/packages/core/src/filters/spriteMask/spriteMaskFilter.vert new file mode 100644 index 0000000..d23a30c --- /dev/null +++ b/packages/core/src/filters/spriteMask/spriteMaskFilter.vert @@ -0,0 +1,16 @@ +attribute vec2 aVertexPosition; +attribute vec2 aTextureCoord; + +uniform mat3 projectionMatrix; +uniform mat3 otherMatrix; + +varying vec2 vMaskCoord; +varying vec2 vTextureCoord; + +void main(void) +{ + gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); + + vTextureCoord = aTextureCoord; + vMaskCoord = ( otherMatrix * vec3( aTextureCoord, 1.0) ).xy; +} diff --git a/packages/core/src/framebuffer/FrameBuffer.js b/packages/core/src/framebuffer/FrameBuffer.js new file mode 100644 index 0000000..84e9fcc --- /dev/null +++ b/packages/core/src/framebuffer/FrameBuffer.js @@ -0,0 +1,106 @@ +import Texture from '../textures/BaseTexture'; +import { FORMATS, TYPES } from '@pixi/constants'; + +/** + * Frame buffer + * @class + * @memberof PIXI + */ +export default class FrameBuffer +{ + constructor(width, height) + { + this.width = width || 100; + this.height = height || 100; + + this.stencil = false; + this.depth = false; + + this.dirtyId = 0; + this.dirtyFormat = 0; + this.dirtySize = 0; + + this.depthTexture = null; + this.colorTextures = []; + + this.glFrameBuffers = {}; + } + + get colorTexture() + { + return this.colorTextures[0]; + } + + addColorTexture(index, texture) + { + // TODO add some validation to the texture - same width / height etc? + this.colorTextures[index || 0] = texture || new Texture(null, { scaleMode: 0, + resolution: 1, + mipmap: false, + width: this.width, + height: this.height });// || new Texture(); + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + addDepthTexture(texture) + { + /* eslint-disable max-len */ + this.depthTexture = texture || new Texture(null, { scaleMode: 0, + resolution: 1, + width: this.width, + height: this.height, + mipmap: false, + format: FORMATS.DEPTH_COMPONENT, + type: TYPES.UNSIGNED_SHORT });// UNSIGNED_SHORT; + /* eslint-disable max-len */ + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + enableDepth() + { + this.depth = true; + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + enableStencil() + { + this.stencil = true; + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + resize(width, height) + { + if (width === this.width && height === this.height) return; + + this.width = width; + this.height = height; + + this.dirtyId++; + this.dirtySize++; + + for (let i = 0; i < this.colorTextures.length; i++) + { + this.colorTextures[i].setSize(width, height); + } + + if (this.depthTexture) + { + this.depthTexture.setSize(width, height); + } + } +} diff --git a/packages/core/src/framebuffer/FramebufferSystem.js b/packages/core/src/framebuffer/FramebufferSystem.js new file mode 100644 index 0000000..2cc2437 --- /dev/null +++ b/packages/core/src/framebuffer/FramebufferSystem.js @@ -0,0 +1,246 @@ +import System from '../System'; +import { Rectangle } from '@pixi/math'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class FramebufferSystem extends System +{ + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + contextChange() + { + this.gl = this.renderer.gl; + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + this.current = null; + this.viewport = new Rectangle(); + + this.drawBufferExtension = this.renderer.context.extensions.drawBuffers; + } + + bind(framebuffer, frame) + { + const gl = this.gl; + + this.current = framebuffer; + + if (framebuffer) + { + // TODO cacheing layer! + + const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID] || this.initFramebuffer(framebuffer); + + gl.bindFramebuffer(gl.FRAMEBUFFER, fbo.framebuffer); + // makesure all textures are unbound.. + + // now check for updates... + if (fbo.dirtyId !== framebuffer.dirtyId) + { + fbo.dirtyId = framebuffer.dirtyId; + + if (fbo.dirtyFormat !== framebuffer.dirtyFormat) + { + fbo.dirtyFormat = framebuffer.dirtyFormat; + this.updateFramebuffer(framebuffer); + } + else if (fbo.dirtySize !== framebuffer.dirtySize) + { + fbo.dirtySize = framebuffer.dirtySize; + this.resizeFramebuffer(framebuffer); + } + } + + for (let i = 0; i < framebuffer.colorTextures.length; i++) + { + if (framebuffer.colorTextures[i].texturePart) + { + this.renderer.texture.unbind(framebuffer.colorTextures[i].texture); + } + else + { + this.renderer.texture.unbind(framebuffer.colorTextures[i]); + } + } + + if (framebuffer.depthTexture) + { + this.renderer.texture.unbind(framebuffer.depthTexture); + } + + if (frame) + { + this.setViewport(frame.x, frame.y, frame.width, frame.height); + } + else + { + this.setViewport(0, 0, framebuffer.width, framebuffer.height); + } + } + else + { + gl.bindFramebuffer(gl.FRAMEBUFFER, null); + + if (frame) + { + this.setViewport(frame.x, frame.y, frame.width, frame.height); + } + else + { + this.setViewport(0, 0, this.renderer.width, this.renderer.height); + } + } + } + + setViewport(x, y, width, height) + { + const v = this.viewport; + + if (v.width !== width || v.height !== height || v.x !== x || v.y !== y) + { + v.x = x; + v.y = y; + v.width = width; + v.height = height; + + this.gl.viewport(x, y, width, height); + } + } + + get size() + { + if (this.current) + { + // TODO store temp + return { x: 0, y: 0, width: this.current.width, height: this.current.height }; + } + + return { x: 0, y: 0, width: this.renderer.width, height: this.renderer.height }; + } + + clear(r, g, b, a) + { + const gl = this.gl; + + // TODO clear color can be set only one right? + gl.clearColor(r, g, b, a); + gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); + } + + // private functions... + + initFramebuffer(framebuffer) + { + const gl = this.gl; + + // TODO - make this a class? + const fbo = { + framebuffer: gl.createFramebuffer(), + stencil: null, + dirtyId: 0, + dirtyFormat: 0, + dirtySize: 0, + }; + + framebuffer.glFrameBuffers[this.CONTEXT_UID] = fbo; + + return fbo; + } + + resizeFramebuffer(framebuffer) + { + const gl = this.gl; + + if (framebuffer.stencil || framebuffer.depth) + { + gl.bindRenderbuffer(gl.RENDERBUFFER, this.stencil); + gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); + } + } + + updateFramebuffer(framebuffer) + { + const gl = this.gl; + + const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID]; + + // bind the color texture + const colorTextures = framebuffer.colorTextures; + + let count = colorTextures.length; + + if (!this.drawBufferExtension) + { + count = Math.min(count, 1); + } + + const activeTextures = []; + + for (let i = 0; i < count; i++) + { + const texture = framebuffer.colorTextures[i]; + + if (texture.texturePart) + { + this.renderer.texture.bind(texture.texture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.COLOR_ATTACHMENT0 + i, + gl.TEXTURE_CUBE_MAP_NEGATIVE_X + texture.side, + texture.texture._glTextures[this.CONTEXT_UID].texture, + 0); + } + else + { + this.renderer.texture.bind(texture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.COLOR_ATTACHMENT0 + i, + gl.TEXTURE_2D, + texture._glTextures[this.CONTEXT_UID].texture, + 0); + } + + activeTextures.push(gl.COLOR_ATTACHMENT0 + i); + } + + if (this.drawBufferExtension && activeTextures.length > 1) + { + this.drawBufferExtension.drawBuffersWEBGL(activeTextures); + } + + if (framebuffer.depthTexture) + { + const depthTextureExt = this.renderer.context.extensions.depthTexture; + + if (depthTextureExt) + { + const depthTexture = framebuffer.depthTexture; + + this.renderer.texture.bind(depthTexture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.DEPTH_ATTACHMENT, + gl.TEXTURE_2D, + depthTexture._glTextures[this.CONTEXT_UID].texture, + 0); + } + } + + if (framebuffer.stencil || framebuffer.depth) + { + fbo.stencil = gl.createRenderbuffer(); + + gl.bindRenderbuffer(gl.RENDERBUFFER, fbo.stencil); + + // TODO.. this is depth AND stencil? + gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, fbo.stencil); + gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); + // fbo.enableStencil(); + } + } +} diff --git a/packages/core/src/geometry/GLBuffer.js b/packages/core/src/geometry/GLBuffer.js new file mode 100644 index 0000000..0477e8a --- /dev/null +++ b/packages/core/src/geometry/GLBuffer.js @@ -0,0 +1,9 @@ +export default class GLBuffer +{ + constructor(buffer) + { + this.buffer = buffer; + this.updateID = -1; + this.byteLength = -1; + } +} diff --git a/packages/core/src/geometry/GeometrySystem.js b/packages/core/src/geometry/GeometrySystem.js new file mode 100644 index 0000000..959f827 --- /dev/null +++ b/packages/core/src/geometry/GeometrySystem.js @@ -0,0 +1,388 @@ +import System from '../System'; +import GLBuffer from './GLBuffer'; + +const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class GeometrySystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this._activeGeometry = null; + this._activeVao = null; + + this.hasVao = true; + this.hasInstance = true; + } + + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + contextChange() + { + const gl = this.gl = this.renderer.gl; + + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + + // webgl2 + if (!gl.createVertexArray) + { + // webgl 1! + let nativeVaoExtension = this.renderer.context.extensions.vertexArrayObject; + + if (this.renderer.legacy) + { + nativeVaoExtension = null; + } + + if (nativeVaoExtension) + { + gl.createVertexArray = () => + nativeVaoExtension.createVertexArrayOES(); + + gl.bindVertexArray = (vao) => + nativeVaoExtension.bindVertexArrayOES(vao); + + gl.deleteVertexArray = (vao) => + nativeVaoExtension.deleteVertexArrayOES(vao); + } + else + { + this.hasVao = false; + gl.createVertexArray = () => + { + // empty + }; + + gl.bindVertexArray = () => + { + // empty + }; + + gl.deleteVertexArray = () => + { + // empty + }; + } + } + + if (!gl.vertexAttribDivisor) + { + const instanceExt = gl.getExtension('ANGLE_instanced_arrays'); + + if (instanceExt) + { + gl.vertexAttribDivisor = (a, b) => + instanceExt.vertexAttribDivisorANGLE(a, b); + + gl.drawElementsInstanced = (a, b, c, d, e) => + instanceExt.drawElementsInstancedANGLE(a, b, c, d, e); + + gl.drawArraysInstanced = (a, b, c, d) => + instanceExt.drawArraysInstancedANGLE(a, b, c, d); + } + else + { + this.hasInstance = false; + } + } + } + + /** + * Binds geometry so that is can be drawn. Creating a Vao if required + * @private + * @param {PIXI.Geometry} geometry instance of geometry to bind + */ + bind(geometry, shader) + { + shader = shader || this.renderer.shader.shader; + + const gl = this.gl; + + // not sure the best way to address this.. + // currently different shaders require different VAOs for the same geometry + // Still mulling over the best way to solve this one.. + // will likely need to modify the shader attribute locations at run time! + let vaos = geometry.glVertexArrayObjects[this.CONTEXT_UID]; + + if (!vaos) + { + geometry.glVertexArrayObjects[this.CONTEXT_UID] = vaos = {}; + } + + const vao = vaos[shader.program.id] || this.initGeometryVao(geometry, shader.program); + + this._activeGeometry = geometry; + + if (this._activeVao !== vao) + { + this._activeVao = vao; + + if (this.hasVao) + { + gl.bindVertexArray(vao); + } + else + { + this.activateVao(geometry, shader.program); + } + } + + // TODO - optimise later! + // don't need to loop through if nothing changed! + // maybe look to add an 'autoupdate' to geometry? + this.updateBuffers(); + } + + reset() + { + this.unbind(); + } + + updateBuffers() + { + const geometry = this._activeGeometry; + const gl = this.gl; + + for (let i = 0; i < geometry.buffers.length; i++) + { + const buffer = geometry.buffers[i]; + + const glBuffer = buffer._glBuffers[this.CONTEXT_UID]; + + if (buffer._updateID !== glBuffer.updateID) + { + glBuffer.updateID = buffer._updateID; + + // TODO can cache this on buffer! maybe added a getter / setter? + const type = buffer.index ? gl.ELEMENT_ARRAY_BUFFER : gl.ARRAY_BUFFER; + const drawType = buffer.static ? gl.STATIC_DRAW : gl.DYNAMIC_DRAW; + + gl.bindBuffer(type, glBuffer.buffer); + + if (glBuffer.byteLength >= buffer.data.byteLength) + { + // offset is always zero for now! + gl.bufferSubData(type, 0, buffer.data); + } + else + { + gl.bufferData(type, buffer.data, drawType); + } + } + } + } + + checkCompatability(geometry, program) + { + // geometry must have at least all the attributes that the shader requires. + const geometryAttributes = geometry.attributes; + const shaderAttributes = program.attributeData; + + for (const j in shaderAttributes) + { + if (!geometryAttributes[j]) + { + throw new Error(`shader and geometry incompatible, geometry missing the "${j}" attribute`); + } + } + } + + /** + * Creates a Vao with the same structure as the geometry and stores it on the geometry. + * @private + * @param {PIXI.Geometry} geometry instance of geometry to to generate Vao for + */ + initGeometryVao(geometry, program) + { + this.checkCompatability(geometry, program); + + const gl = this.gl; + const CONTEXT_UID = this.CONTEXT_UID; + const buffers = geometry.buffers; + const attributes = geometry.attributes; + + const tempStride = {}; + const tempStart = {}; + + for (const j in buffers) + { + tempStride[j] = 0; + tempStart[j] = 0; + } + + for (const j in attributes) + { + if (!attributes[j].size && program.attributeData[j]) + { + attributes[j].size = program.attributeData[j].size; + } + + tempStride[attributes[j].buffer] += attributes[j].size * byteSizeMap[attributes[j].type]; + } + + for (const j in attributes) + { + const attribute = attributes[j]; + const attribSize = attribute.size; + + if (attribute.stride === undefined) + { + if (tempStride[attribute.buffer] === attribSize * byteSizeMap[attribute.type]) + { + attribute.stride = 0; + } + else + { + attribute.stride = tempStride[attribute.buffer]; + } + } + + if (attribute.start === undefined) + { + attribute.start = tempStart[attribute.buffer]; + + tempStart[attribute.buffer] += attribSize * byteSizeMap[attribute.type]; + } + } + + // first update - and create the buffers! + // only create a gl buffer if it actually gets + for (let i = 0; i < buffers.length; i++) + { + const buffer = buffers[i]; + + if (!buffer._glBuffers[CONTEXT_UID]) + { + buffer._glBuffers[CONTEXT_UID] = new GLBuffer(gl.createBuffer()); + } + } + + // TODO - maybe make this a data object? + // lets wait to see if we need to first! + const vao = gl.createVertexArray(); + + gl.bindVertexArray(vao); + + this.activateVao(geometry, program); + + gl.bindVertexArray(null); + + // add it to the cache! + geometry.glVertexArrayObjects[this.CONTEXT_UID][program.id] = vao; + + return vao; + } + + activateVao(geometry, program) + { + const gl = this.gl; + const CONTEXT_UID = this.CONTEXT_UID; + const buffers = geometry.buffers; + const attributes = geometry.attributes; + + if (geometry.indexBuffer) + { + // first update the index buffer if we have one.. + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, geometry.indexBuffer._glBuffers[CONTEXT_UID].buffer); + } + + let lastBuffer = null; + + // add a new one! + for (const j in attributes) + { + const attribute = attributes[j]; + const buffer = buffers[attribute.buffer]; + const glBuffer = buffer._glBuffers[CONTEXT_UID]; + + if (program.attributeData[j]) + { + if (lastBuffer !== glBuffer) + { + gl.bindBuffer(gl.ARRAY_BUFFER, glBuffer.buffer); + + lastBuffer = glBuffer; + } + + const location = program.attributeData[j].location; + + // TODO introduce state again + // we can optimise this for older devices that have no VAOs + gl.enableVertexAttribArray(location); + + gl.vertexAttribPointer(location, + attribute.size, + attribute.type || gl.FLOAT, + attribute.normalized, + attribute.stride, + attribute.start); + + if (attribute.instance) + { + // TODO calculate instance count based of this... + if (this.hasInstance) + { + gl.vertexAttribDivisor(location, 1); + } + else + { + throw new Error('geometry error, GPU Instancing is not supported on this device'); + } + } + } + } + } + + draw(type, size, start, instanceCount) + { + const gl = this.gl; + const geometry = this._activeGeometry; + + // TODO.. this should not change so maybe cache the function? + + if (geometry.indexBuffer) + { + if (geometry.instanced) + { + /* eslint-disable max-len */ + gl.drawElementsInstanced(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2, instanceCount || 1); + /* eslint-enable max-len */ + } + else + { + gl.drawElements(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2); + } + } + else + if (geometry.instanced) + { + // TODO need a better way to calculate size.. + gl.drawArraysInstanced(type, start, size || geometry.getSize(), instanceCount || 1); + } + else + { + gl.drawArrays(type, start, size || geometry.getSize()); + } + + return this; + } + + unbind() + { + this.gl.bindVertexArray(null); + this._activeVao = null; + this._activeGeometry = null; + } +} diff --git a/packages/core/src/geometry/utils/setVertexAttribArrays.js b/packages/core/src/geometry/utils/setVertexAttribArrays.js new file mode 100644 index 0000000..23b6959 --- /dev/null +++ b/packages/core/src/geometry/utils/setVertexAttribArrays.js @@ -0,0 +1,54 @@ +// var GL_MAP = {}; + +/** + * @param gl {WebGLRenderingContext} The current WebGL context + * @param attribs {*} + * @param state {*} + */ +export default function setVertexAttribArrays(gl, attribs, state) +{ + let i; + + if (state) + { + const tempAttribState = state.tempAttribState; + const attribState = state.attribState; + + for (i = 0; i < tempAttribState.length; i++) + { + tempAttribState[i] = false; + } + + // set the new attribs + for (i = 0; i < attribs.length; i++) + { + tempAttribState[attribs[i].attribute.location] = true; + } + + for (i = 0; i < attribState.length; i++) + { + if (attribState[i] !== tempAttribState[i]) + { + attribState[i] = tempAttribState[i]; + + if (state.attribState[i]) + { + gl.enableVertexAttribArray(i); + } + else + { + gl.disableVertexAttribArray(i); + } + } + } + } + else + { + for (i = 0; i < attribs.length; i++) + { + const attrib = attribs[i]; + + gl.enableVertexAttribArray(attrib.attribute.location); + } + } +} diff --git a/packages/core/src/index.js b/packages/core/src/index.js index 58c456a..fd1f547 100644 --- a/packages/core/src/index.js +++ b/packages/core/src/index.js @@ -1,30 +1,30 @@ import * as resources from './textures/resources'; -import * as systems from './renderers/systems'; +import * as systems from './systems'; -export { resources }; export { systems }; +export { resources }; -export { default as Renderer } from './renderers/Renderer'; -export { default as AbstractRenderer } from './renderers/AbstractRenderer'; -export { default as FrameBuffer } from './textures/FrameBuffer'; +export { default as System } from './System'; +export { default as Renderer } from './Renderer'; +export { default as AbstractRenderer } from './AbstractRenderer'; +export { default as FrameBuffer } from './framebuffer/FrameBuffer'; export { default as CubeTexture } from './textures/CubeTexture'; export { default as BaseTexture } from './textures/BaseTexture'; export { default as Texture } from './textures/Texture'; export { default as TextureMatrix } from './textures/TextureMatrix'; -export { default as RenderTexture } from './textures/RenderTexture'; -export { default as BaseRenderTexture } from './textures/BaseRenderTexture'; +export { default as RenderTexture } from './renderTexture/RenderTexture'; +export { default as BaseRenderTexture } from './renderTexture/BaseRenderTexture'; export { default as TextureUvs } from './textures/TextureUvs'; -export { default as State } from './renderers/State'; -export { default as ObjectRenderer } from './renderers/utils/ObjectRenderer'; -export { default as Quad } from './renderers/utils/Quad'; -export { default as QuadUv } from './renderers/utils/QuadUv'; -export { default as checkMaxIfStatmentsInShader } from './renderers/utils/checkMaxIfStatmentsInShader'; +export { default as State } from './state/State'; +export { default as ObjectRenderer } from './batch/ObjectRenderer'; +export { default as Quad } from './utils/Quad'; +export { default as QuadUv } from './utils/QuadUv'; +export { default as checkMaxIfStatmentsInShader } from './shader/utils/checkMaxIfStatmentsInShader'; export { default as Shader } from './shader/Shader'; export { default as Program } from './shader/Program'; export { default as UniformGroup } from './shader/UniformGroup'; -export { default as SpriteMaskFilter } from './renderers/filters/spriteMask/SpriteMaskFilter'; -export { default as Filter } from './renderers/filters/Filter'; +export { default as SpriteMaskFilter } from './filters/spriteMask/SpriteMaskFilter'; +export { default as Filter } from './filters/Filter'; export { default as Attribute } from './geometry/Attribute'; export { default as Buffer } from './geometry/Buffer'; export { default as Geometry } from './geometry/Geometry'; - diff --git a/packages/core/src/mask/MaskSystem.js b/packages/core/src/mask/MaskSystem.js new file mode 100644 index 0000000..99174c2 --- /dev/null +++ b/packages/core/src/mask/MaskSystem.js @@ -0,0 +1,199 @@ +import System from '../System'; +import AlphaMaskFilter from '../filters/spriteMask/SpriteMaskFilter'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class MaskSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + // TODO - we don't need both! + this.scissor = false; + this.scissorData = null; + this.scissorRenderTarget = null; + + this.enableScissor = false; + + this.alphaMaskPool = []; + this.alphaMaskIndex = 0; + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.DisplayObject} target - Display Object to push the mask to + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + push(target, maskData) + { + // TODO the root check means scissor rect will not + // be used on render textures more info here: + // https://github.com/pixijs/pixi.js/pull/3545 + + if (maskData.texture) + { + this.pushSpriteMask(target, maskData); + } + else if (this.enableScissor + && !this.scissor + && this.renderer._activeRenderTarget.root + && !this.renderer.stencil.stencilMaskStack.length + && maskData.isFastRect()) + { + const matrix = maskData.worldTransform; + + let rot = Math.atan2(matrix.b, matrix.a); + + // use the nearest degree! + rot = Math.round(rot * (180 / Math.PI)); + + if (rot % 90) + { + this.pushStencilMask(maskData); + } + else + { + this.pushScissorMask(target, maskData); + } + } + else + { + this.pushStencilMask(maskData); + } + } + + /** + * Removes the last mask from the mask stack and doesn't return it. + * + * @param {PIXI.DisplayObject} target - Display Object to pop the mask from + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + pop(target, maskData) + { + if (maskData.texture) + { + this.popSpriteMask(target, maskData); + } + else if (this.enableScissor && !this.renderer.stencil.stencilMaskStack.length) + { + this.popScissorMask(target, maskData); + } + else + { + this.popStencilMask(target, maskData); + } + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.RenderTarget} target - Display Object to push the sprite mask to + * @param {PIXI.Sprite} maskData - Sprite to be used as the mask + */ + pushSpriteMask(target, maskData) + { + let alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; + + if (!alphaMaskFilter) + { + alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; + } + + alphaMaskFilter[0].resolution = this.renderer.resolution; + alphaMaskFilter[0].maskSprite = maskData; + + // TODO - may cause issues! + target.filterArea = maskData.getBounds(true); + + this.renderer.filterSystem.pushFilter(target, alphaMaskFilter); + + this.alphaMaskIndex++; + } + + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popSpriteMask() + { + this.renderer.filterSystem.popFilter(); + this.alphaMaskIndex--; + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + pushStencilMask(maskData) + { + this.renderer.batch.flush(); + this.renderer.stencil.pushStencil(maskData); + } + + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popStencilMask() + { + // this.renderer.currentRenderer.stop(); + this.renderer.stencil.popStencil(); + } + + /** + * + * @param {PIXI.DisplayObject} target - Display Object to push the mask to + * @param {PIXI.Graphics} maskData - The masking data. + */ + pushScissorMask(target, maskData) + { + maskData.renderable = true; + + const renderTarget = this.renderer._activeRenderTarget; + + const bounds = maskData.getBounds(); + + bounds.fit(renderTarget.size); + maskData.renderable = false; + + this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); + + const resolution = this.renderer.resolution; + + this.renderer.gl.scissor( + bounds.x * resolution, + (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, + bounds.width * resolution, + bounds.height * resolution + ); + + this.scissorRenderTarget = renderTarget; + this.scissorData = maskData; + this.scissor = true; + } + + /** + * + * + */ + popScissorMask() + { + this.scissorRenderTarget = null; + this.scissorData = null; + this.scissor = false; + + // must be scissor! + const gl = this.renderer.gl; + + gl.disable(gl.SCISSOR_TEST); + } +} diff --git a/packages/core/src/mask/StencilSystem.js b/packages/core/src/mask/StencilSystem.js new file mode 100644 index 0000000..62796a5 --- /dev/null +++ b/packages/core/src/mask/StencilSystem.js @@ -0,0 +1,133 @@ +import System from '../System'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class StencilSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + this.stencilMaskStack = []; + } + + /** + * Changes the mask stack that is used by this System. + * + * @param {PIXI.Graphics[]} stencilMaskStack - The mask stack + */ + setMaskStack(stencilMaskStack) + { + const gl = this.renderer.gl; + + if (stencilMaskStack.length !== this.stencilMaskStack.length) + { + if (stencilMaskStack.length === 0) + { + gl.disable(gl.STENCIL_TEST); + } + else + { + gl.enable(gl.STENCIL_TEST); + } + } + + this.stencilMaskStack = stencilMaskStack; + } + + /** + * Applies the Mask and adds it to the current stencil stack. @alvin + * + * @param {PIXI.Graphics} graphics - The mask + */ + pushStencil(graphics) + { + this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); + + // this.renderer._activeRenderTarget.attachStencilBuffer(); + + const gl = this.renderer.gl; + const prevMaskCount = this.stencilMaskStack.length; + + if (prevMaskCount === 0) + { + gl.enable(gl.STENCIL_TEST); + } + + this.stencilMaskStack.push(graphics); + + // Increment the refference stencil value where the new mask overlaps with the old ones. + gl.colorMask(false, false, false, false); + gl.stencilFunc(gl.EQUAL, prevMaskCount, this._getBitwiseMask()); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.INCR); + this.renderer.plugins.graphics.render(graphics); + + this._useCurrent(); + } + + /** + * Removes the last mask from the stencil stack. @alvin + */ + popStencil() + { + this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); + + const gl = this.renderer.gl; + const graphics = this.stencilMaskStack.pop(); + + if (this.stencilMaskStack.length === 0) + { + // the stack is empty! + gl.disable(gl.STENCIL_TEST); + gl.clear(gl.STENCIL_BUFFER_BIT); + gl.clearStencil(0); + } + else + { + // Decrement the refference stencil value where the popped mask overlaps with the other ones + gl.colorMask(false, false, false, false); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.DECR); + this.renderer.plugins.graphics.render(graphics); + + this._useCurrent(); + } + } + + /** + * Setup renderer to use the current stencil data. + */ + _useCurrent() + { + const gl = this.renderer.gl; + + gl.colorMask(true, true, true, true); + gl.stencilFunc(gl.EQUAL, this.stencilMaskStack.length, this._getBitwiseMask()); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP); + } + + /** + * Fill 1s equal to the number of acitve stencil masks. + * + * @return {number} The bitwise mask. + */ + _getBitwiseMask() + { + return (1 << this.stencilMaskStack.length) - 1; + } + + /** + * Destroys the mask stack. + * + */ + destroy() + { + super.destroy(this); + + this.stencilMaskStack = null; + } +} diff --git a/packages/core/src/projection/ProjectionSystem.js b/packages/core/src/projection/ProjectionSystem.js new file mode 100644 index 0000000..6e63b73 --- /dev/null +++ b/packages/core/src/projection/ProjectionSystem.js @@ -0,0 +1,73 @@ +import System from '../System'; +import { Matrix } from '@pixi/math'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class ProjectionSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.projectionMatrix = new Matrix(); + } + + update(destinationFrame, sourceFrame, resolution, root) + { + this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; + this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; + + this.calculateProjection(this.destinationFrame, this.sourceFrame, resolution, root); + + this.renderer.globalUniforms.uniforms.projectionMatrix = this.projectionMatrix; + this.renderer.globalUniforms.update(); + } + + /** + * Updates the projection matrix based on a projection frame (which is a rectangle) + * + * @param {Rectangle} destinationFrame - The destination frame. + * @param {Rectangle} sourceFrame - The source frame. + */ + calculateProjection(destinationFrame, sourceFrame, resolution, root) + { + const pm = this.projectionMatrix; + + // I don't think we will need this line.. + // pm.identity(); + + if (!root) + { + pm.a = (1 / destinationFrame.width * 2) * resolution; + pm.d = (1 / destinationFrame.height * 2) * resolution; + + pm.tx = -1 - (sourceFrame.x * pm.a); + pm.ty = -1 - (sourceFrame.y * pm.d); + } + else + { + pm.a = (1 / destinationFrame.width * 2) * resolution; + pm.d = (-1 / destinationFrame.height * 2) * resolution; + + pm.tx = -1 - (sourceFrame.x * pm.a); + pm.ty = 1 - (sourceFrame.y * pm.d); + } + } + + /** + * Sets the transform of the active render target to the given matrix + * + * @param {PIXI.Matrix} matrix - The transformation matrix + */ + setTransform()// matrix) + { + // this._activeRenderTarget.transform = matrix; + } +} diff --git a/packages/core/src/renderTexture/BaseRenderTexture.js b/packages/core/src/renderTexture/BaseRenderTexture.js new file mode 100644 index 0000000..88eb83b --- /dev/null +++ b/packages/core/src/renderTexture/BaseRenderTexture.js @@ -0,0 +1,137 @@ +import BaseTexture from '../textures/BaseTexture'; +import FrameBuffer from '../framebuffer/FrameBuffer'; + +/** + * A BaseRenderTexture is a special texture that allows any PixiJS display object to be rendered to it. + * + * __Hint__: All DisplayObjects (i.e. Sprites) that render to a BaseRenderTexture should be preloaded + * otherwise black rectangles will be drawn instead. + * + * A BaseRenderTexture takes a snapshot of any Display Object given to its render method. The position + * and rotation of the given Display Objects is ignored. For example: + * + * ```js + * let renderer = PIXI.autoDetectRenderer(1024, 1024, { view: canvas, ratio: 1 }); + * let baseRenderTexture = new PIXI.BaseRenderTexture(renderer, 800, 600); + * let sprite = PIXI.Sprite.fromImage("spinObj_01.png"); + * + * sprite.position.x = 800/2; + * sprite.position.y = 600/2; + * sprite.anchor.x = 0.5; + * sprite.anchor.y = 0.5; + * + * baseRenderTexture.render(sprite); + * ``` + * + * The Sprite in this case will be rendered using its local transform. To render this sprite at 0,0 + * you can clear the transform + * + * ```js + * + * sprite.setTransform() + * + * let baseRenderTexture = new PIXI.BaseRenderTexture(100, 100); + * let renderTexture = new PIXI.RenderTexture(baseRenderTexture); + * + * renderer.render(sprite, renderTexture); // Renders to center of RenderTexture + * ``` + * + * @class + * @extends PIXI.BaseTexture + * @memberof PIXI + */ +export default class BaseRenderTexture extends BaseTexture +{ + /** + * @param {object} [options] + * @param {number} [options.width=100] - The width of the base render texture + * @param {number} [options.height=100] - The height of the base render texture + * @param {PIXI.SCALE_MODES} [options.scaleMode] - See {@link PIXI.SCALE_MODES} for possible values + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the texture being generated + */ + constructor(options) + { + if (typeof options === 'number') + { + /* eslint-disable prefer-rest-params */ + // Backward compatibility of signature + const width = arguments[0]; + const height = arguments[1]; + const scaleMode = arguments[2]; + const resolution = arguments[3]; + + options = { width, height, scaleMode, resolution }; + /* eslint-enable prefer-rest-params */ + } + + super(null, options); + + const { width, height } = options || {}; + + // Set defaults + this.mipmap = false; + this.width = Math.ceil(width) || 100; + this.height = Math.ceil(height) || 100; + this.valid = true; + + /** + * A map of renderer IDs to webgl renderTargets + * + * @private + * @member {object} + */ + // this._glRenderTargets = {}; + + /** + * A reference to the canvas render target (we only need one as this can be shared across renderers) + * + * @private + * @member {object} + */ + this._canvasRenderTarget = null; + + this.clearColor = [0, 0, 0, 0]; + + this.frameBuffer = new FrameBuffer(width * this.resolution, height * this.resolution) + .addColorTexture(0, this); + + // TODO - could this be added the systems? + + /** + * The data structure for the stencil masks + * + * @member {PIXI.Graphics[]} + */ + this.stencilMaskStack = []; + + /** + * The data structure for the filters + * + * @member {PIXI.Graphics[]} + */ + this.filterStack = [{}]; + } + + /** + * Resizes the BaseRenderTexture. + * + * @param {number} width - The width to resize to. + * @param {number} height - The height to resize to. + */ + resize(width, height) + { + width = Math.ceil(width); + height = Math.ceil(height); + this.frameBuffer.resize(width * this.resolution, height * this.resolution); + } + + /** + * Destroys this texture + * + */ + destroy() + { + super.destroy(true); + this.renderer = null; + } +} diff --git a/packages/core/src/renderTexture/RenderTexture.js b/packages/core/src/renderTexture/RenderTexture.js new file mode 100644 index 0000000..f1ecf1f --- /dev/null +++ b/packages/core/src/renderTexture/RenderTexture.js @@ -0,0 +1,153 @@ +import BaseRenderTexture from './BaseRenderTexture'; +import Texture from '../textures/Texture'; + +/** + * A RenderTexture is a special texture that allows any PixiJS display object to be rendered to it. + * + * __Hint__: All DisplayObjects (i.e. Sprites) that render to a RenderTexture should be preloaded + * otherwise black rectangles will be drawn instead. + * + * A RenderTexture takes a snapshot of any Display Object given to its render method. For example: + * + * ```js + * let renderer = PIXI.autoDetectRenderer(1024, 1024, { view: canvas, ratio: 1 }); + * let renderTexture = PIXI.RenderTexture.create(800, 600); + * let sprite = PIXI.Sprite.fromImage("spinObj_01.png"); + * + * sprite.position.x = 800/2; + * sprite.position.y = 600/2; + * sprite.anchor.x = 0.5; + * sprite.anchor.y = 0.5; + * + * renderer.render(sprite, renderTexture); + * ``` + * + * The Sprite in this case will be rendered using its local transform. To render this sprite at 0,0 + * you can clear the transform + * + * ```js + * + * sprite.setTransform() + * + * let renderTexture = new PIXI.RenderTexture.create(100, 100); + * + * renderer.render(sprite, renderTexture); // Renders to center of RenderTexture + * ``` + * + * @class + * @extends PIXI.Texture + * @memberof PIXI + */ +export default class RenderTexture extends Texture +{ + /** + * @param {PIXI.BaseRenderTexture} baseRenderTexture - The renderer used for this RenderTexture + * @param {PIXI.Rectangle} [frame] - The rectangle frame of the texture to show + */ + constructor(baseRenderTexture, frame) + { + // support for legacy.. + let _legacyRenderer = null; + + if (!(baseRenderTexture instanceof BaseRenderTexture)) + { + /* eslint-disable prefer-rest-params, no-console */ + const width = arguments[1]; + const height = arguments[2]; + const scaleMode = arguments[3]; + const resolution = arguments[4]; + + // we have an old render texture.. + console.warn(`Please use RenderTexture.create(${width}, ${height}) instead of the ctor directly.`); + _legacyRenderer = arguments[0]; + /* eslint-enable prefer-rest-params, no-console */ + + frame = null; + baseRenderTexture = new BaseRenderTexture({ + width, + height, + scaleMode, + resolution, + }); + } + + /** + * The base texture object that this texture uses + * + * @member {BaseTexture} + */ + super(baseRenderTexture, frame); + + this.legacyRenderer = _legacyRenderer; + + /** + * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. + * + * @member {boolean} + */ + this.valid = true; + + /** + * FilterSystem temporary storage + * @private + * @member {PIXI.Rectangle} + */ + this.filterFrame = null; + + this._updateUvs(); + } + + /** + * Resizes the RenderTexture. + * + * @param {number} width - The width to resize to. + * @param {number} height - The height to resize to. + * @param {boolean} [resizeBaseTexture=true] - Should the baseTexture.width and height values be resized as well? + */ + resize(width, height, resizeBaseTexture = true) + { + width = Math.ceil(width); + height = Math.ceil(height); + + // TODO - could be not required.. + this.valid = (width > 0 && height > 0); + + this._frame.width = this.orig.width = width; + this._frame.height = this.orig.height = height; + + if (resizeBaseTexture) + { + this.baseTexture.resize(width, height); + } + + this._updateUvs(); + } + + /** + * A short hand way of creating a render texture. + * + * @param {object} [options] - Options + * @param {number} [options.width=100] - The width of the render texture + * @param {number} [options.height=100] - The height of the render texture + * @param {number} [options.scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the texture being generated + * @return {PIXI.RenderTexture} The new render texture + */ + static create(options) + { + // fallback, old-style: create(width, height, scaleMode, resolution) + if (typeof options === 'number') + { + /* eslint-disable prefer-rest-params */ + options = { + width: options, + height: arguments[1], + scaleMode: arguments[2], + resolution: arguments[3], + }; + /* eslint-enable prefer-rest-params */ + } + + return new RenderTexture(new BaseRenderTexture(options)); + } +} diff --git a/packages/core/src/renderTexture/RenderTextureSystem.js b/packages/core/src/renderTexture/RenderTextureSystem.js new file mode 100644 index 0000000..8d0ca61 --- /dev/null +++ b/packages/core/src/renderTexture/RenderTextureSystem.js @@ -0,0 +1,115 @@ +import System from '../System'; +import { Rectangle } from '@pixi/math'; + +const tempRect = new Rectangle(); + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class RenderTextureSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.clearColor = renderer._backgroundColorRgba; + + // TODO moe this property somewhere else! + this.defaultMaskStack = []; + this.defaultFilterStack = [{}]; + + // empty render texture? + this.renderTexture = null; + + this.destinationFrame = new Rectangle(); + } + + bind(renderTexture, sourceFrame, destinationFrame) + { + // TODO - do we want this?? + if (this.renderTexture === renderTexture) return; + this.renderTexture = renderTexture; + + const renderer = this.renderer; + + if (renderTexture) + { + const baseTexture = renderTexture.baseTexture; + + if (!destinationFrame) + { + tempRect.width = baseTexture.realWidth; + tempRect.height = baseTexture.realHeight; + + destinationFrame = tempRect; + } + + if (!sourceFrame) + { + sourceFrame = destinationFrame; + } + + this.renderer.framebuffer.bind(baseTexture.frameBuffer, destinationFrame); + + this.renderer.projection.update(destinationFrame, sourceFrame, baseTexture.resolution, false); + this.renderer.stencil.setMaskStack(baseTexture.stencilMaskStack); + } + else + { + // TODO these validation checks happen deeper down.. + // thing they can be avoided.. + if (!destinationFrame) + { + tempRect.width = renderer.width; + tempRect.height = renderer.height; + + destinationFrame = tempRect; + } + + if (!sourceFrame) + { + sourceFrame = destinationFrame; + } + + renderer.framebuffer.bind(null, destinationFrame); + + // TODO store this.. + this.renderer.projection.update(destinationFrame, sourceFrame, this.renderer.resolution, true); + this.renderer.stencil.setMaskStack(this.defaultMaskStack); + } + + this.destinationFrame.copyFrom(destinationFrame); + } + + /** + * Erases the render texture and fills the drawing area with a colour + * + * @param {number} [clearColor] - The colour + * @return {PIXI.Renderer} Returns itself. + */ + clear(clearColor) + { + if (this.renderTexture) + { + clearColor = clearColor || this.renderTexture.baseTexture.clearColor; + } + else + { + clearColor = clearColor || this.clearColor; + } + + this.renderer.framebuffer.clear(clearColor[0], clearColor[1], clearColor[2], clearColor[3]); + } + + resize()// screenWidth, screenHeight) + { + // resize the root only! + this.bind(null); + } +} diff --git a/packages/core/src/renderers/AbstractRenderer.js b/packages/core/src/renderers/AbstractRenderer.js deleted file mode 100644 index 928670f..0000000 --- a/packages/core/src/renderers/AbstractRenderer.js +++ /dev/null @@ -1,353 +0,0 @@ -import { hex2string, hex2rgb } from '@pixi/utils'; -import { Matrix, Rectangle } from '@pixi/math'; -import { RENDERER_TYPE } from '@pixi/constants'; -import { settings } from '@pixi/settings'; -import { Container } from '@pixi/display'; -import RenderTexture from '../textures/RenderTexture'; -import EventEmitter from 'eventemitter3'; - -const tempMatrix = new Matrix(); - -/** - * 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 EventEmitter - * @memberof PIXI - */ -export default class AbstractRenderer extends EventEmitter -{ - // eslint-disable-next-line valid-jsdoc - /** - * @param {string} system - The name of the system this renderer is for. - * @param {object} [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.transparent=false] - If the render view is transparent, default false - * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false - * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) - * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. The - * resolution of the renderer retina would be 2. - * @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 {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when rendering, - * stopping pixel interpolation. - */ - constructor(system, options, arg2, arg3) - { - super(); - - // Support for constructor(system, screenWidth, screenHeight, options) - if (typeof options === 'number') - { - options = Object.assign({ - width: options, - height: arg2 || settings.RENDER_OPTIONS.height, - }, arg3); - } - - // 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 = RENDERER_TYPE.UNKNOWN; - - /** - * Measurements of the screen. (0, 0, screenWidth, screenHeight) - * - * Its safe to use as filterArea or hitArea for 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 1 - */ - this.resolution = options.resolution || settings.RESOLUTION; - - /** - * Whether the render view is transparent - * - * @member {boolean} - */ - this.transparent = options.transparent; - - /** - * Whether css dimensions of canvas view should be resized to screen dimensions automatically - * - * @member {boolean} - */ - this.autoResize = options.autoResize || false; - - /** - * Tracks the blend modes useful for this renderer. - * - * @member {object} - */ - this.blendModes = null; - - /** - * 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; - - /** - * If true PixiJS will Math.floor() x/y values when rendering, stopping pixel interpolation. - * Handy for crisp pixel art and speed on legacy devices. - * - * @member {boolean} - */ - this.roundPixels = options.roundPixels; - - /** - * The background color as a number. - * - * @member {number} - * @private - */ - this._backgroundColor = 0x000000; - - /** - * The background color as an [R, G, B] array. - * - * @member {number[]} - * @private - */ - this._backgroundColorRgba = [0, 0, 0, 0]; - - /** - * The background color as a string. - * - * @member {string} - * @private - */ - this._backgroundColorString = '#000000'; - - this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter - - /** - * This temporary display object used as the parent of the currently being rendered item - * - * @member {PIXI.DisplayObject} - * @private - */ - this._tempDisplayObjectParent = new Container(); - - /** - * The last root object that the renderer tried to render. - * - * @member {PIXI.DisplayObject} - * @private - */ - this._lastObjectRendered = this._tempDisplayObjectParent; - - /** - * Collection of plugins. - * @readonly - * @member {object} - */ - this.plugins = {}; - } - - /** - * Initialize the plugins. - * - * @protected - * @param {object} staticMap - The dictionary of staticly saved plugins. - */ - initPlugins(staticMap) - { - 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() - { - return this.view.width; - } - - /** - * Same as view.height, actual number of pixels in the canvas by vertical - * - * @member {number} - * @readonly - * @default 600 - */ - get height() - { - return this.view.height; - } - - /** - * Resizes the screen and canvas to the specified width and height - * Canvas dimensions are multiplied by resolution - * - * @param {number} screenWidth - the new width of the screen - * @param {number} screenHeight - the new height of the screen - */ - resize(screenWidth, screenHeight) - { - this.screen.width = screenWidth; - this.screen.height = screenHeight; - - this.view.width = screenWidth * this.resolution; - this.view.height = screenHeight * this.resolution; - - if (this.autoResize) - { - this.view.style.width = `${screenWidth}px`; - this.view.style.height = `${screenHeight}px`; - } - } - - /** - * 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 {PIXI.DisplayObject} displayObject - The displayObject the object will be generated from - * @param {number} scaleMode - Should be one of the scaleMode consts - * @param {number} resolution - The resolution / device pixel ratio of the texture being generated - * @param {PIXI.Rectangle} [region] - The region of the displayObject, that shall be rendered, - * if no region is specified, defaults to the local bounds of the displayObject. - * @return {PIXI.Texture} a texture of the graphics object - */ - generateTexture(displayObject, scaleMode, resolution, region) - { - region = region || displayObject.getLocalBounds(); - - // 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(region.width | 0, region.height | 0, scaleMode, resolution); - - tempMatrix.tx = -region.x; - tempMatrix.ty = -region.y; - - this.render(displayObject, renderTexture, false, tempMatrix, true); - - return renderTexture; - } - - /** - * Removes everything from the renderer and optionally removes the Canvas DOM element. - * - * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. - */ - destroy(removeView) - { - 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); - } - - this.plugins = null; - - this.type = RENDERER_TYPE.UNKNOWN; - - this.view = null; - - this.screen = null; - - this.resolution = 0; - - this.transparent = false; - - this.autoResize = false; - - this.blendModes = null; - - this.options = null; - - this.preserveDrawingBuffer = false; - this.clearBeforeRender = false; - - this.roundPixels = false; - - this._backgroundColor = 0; - this._backgroundColorRgba = null; - this._backgroundColorString = null; - - this._tempDisplayObjectParent = null; - this._lastObjectRendered = null; - } - - /** - * The background color to fill if not transparent - * - * @member {number} - */ - get backgroundColor() - { - return this._backgroundColor; - } - - set backgroundColor(value) // eslint-disable-line require-jsdoc - { - this._backgroundColor = value; - this._backgroundColorString = hex2string(value); - hex2rgb(value, this._backgroundColorRgba); - } -} diff --git a/packages/core/src/renderers/Renderer.js b/packages/core/src/renderers/Renderer.js deleted file mode 100644 index a2020b1..0000000 --- a/packages/core/src/renderers/Renderer.js +++ /dev/null @@ -1,329 +0,0 @@ -import AbstractRenderer from './AbstractRenderer'; -import { sayHello } from '@pixi/utils'; -import MaskSystem from './systems/MaskSystem'; -import StencilSystem from './systems/StencilSystem'; -import FilterSystem from './systems/filter/FilterSystem'; -import FramebufferSystem from './systems/FramebufferSystem'; -import RenderTextureSystem from './systems/RenderTextureSystem'; -import TextureSystem from './systems/textures/TextureSystem'; -import ProjectionSystem from './systems/ProjectionSystem'; -import StateSystem from './systems/StateSystem'; -import GeometrySystem from './systems/geometry/GeometrySystem'; -import ShaderSystem from './systems/shader/ShaderSystem'; -import ContextSystem from './systems/ContextSystem'; -import BatchSystem from './systems/BatchSystem'; -import TextureGCSystem from './systems/textures/TextureGCSystem'; -import { RENDERER_TYPE } from '@pixi/constants'; -import UniformGroup from '../shader/UniformGroup'; -import { Matrix } from '@pixi/math'; -import Runner from 'mini-runner'; - -/** - * 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 Render works by automatically managing webGLBatchs. - * 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 :) - * - * @class - * @memberof PIXI - * @extends PIXI.AbstractRenderer - */ -export default class Renderer extends AbstractRenderer -{ - // eslint-disable-next-line valid-jsdoc - /** - * - * @param {object} [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.transparent=false] - If the render view is transparent, default false - * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false - * @param {boolean} [options.antialias=false] - sets antialias. If not available natively then FXAA - * antialiasing is used - * @param {boolean} [options.forceFXAA=false] - forces FXAA antialiasing to be used over native. - * FXAA is faster, but may not always look as great - * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. - * The resolution of the renderer retina would be 2. - * @param {boolean} [options.clearBeforeRender=true] - This sets if the renderer will clear - * the canvas or not before the new render pass. If you wish to set this to false, you *must* set - * preserveDrawingBuffer to `true`. - * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, - * enable this if you need to call toDataUrl on the webgl context. - * @param {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when - * rendering, stopping pixel interpolation. - * @param {number} [options.backgroundColor=0x000000] - The background color of the rendered area - * (shown if not transparent). - * @param {boolean} [options.legacy=false] - If true PixiJS will aim to ensure compatibility - * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. - * @param {string} [options.powerPreference] - Parameter passed to webgl context, set to "high-performance" - * for devices with dual graphics card - */ - constructor(options, arg2, arg3) - { - super('WebGL', options, arg2, arg3); - - /** - * The type of this renderer as a standardised const - * - * @member {number} - * @see PIXI.RENDERER_TYPE - */ - this.type = RENDERER_TYPE.WEBGL; - - // this will be set by the contextSystem (this.context) - this.gl = null; - this.CONTEXT_UID = 0; - this.legacy = !!options.legacy; - - // TODO legacy! - - // runners! - this.runners = { - destroy: new Runner('destroy'), - contextChange: new Runner('contextChange', 1), - reset: new Runner('reset'), - update: new Runner('update'), - postrender: new Runner('postrender'), - prerender: new Runner('prerender'), - resize: new Runner('resize', 2), - }; - - 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(GeometrySystem, 'geometry') - .addSystem(FramebufferSystem, 'framebuffer') - .addSystem(StencilSystem, 'stencil') - .addSystem(ProjectionSystem, 'projection') - .addSystem(TextureGCSystem, 'textureGC') - .addSystem(FilterSystem, 'filter') - .addSystem(RenderTextureSystem, 'renderTexture') - .addSystem(BatchSystem, 'batch'); - - this.initPlugins(Renderer.__plugins); - - /** - * The options passed in to create a new webgl context. - * - * @member {object} - * @private - */ - if (options.context) - { - this.context.initFromContext(options.context); - } - else - { - this.context.initFromOptions({ - alpha: this.transparent, - antialias: options.antialias, - premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', - stencil: true, - 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); - } - - addSystem(_class, name) - { - if (!name) - { - name = _class.name; - } - - // TODO - read name from class.name.. - - /* - if(name.includes('System')) - { - name = name.replace('System', ''); - name = name.charAt(0).toLowerCase() + name.slice(1); - } - */ - - const system = new _class(this); - - if (this[name]) - { - throw new Error(`Whoops! ${name} is already a manger`); - } - - this[name] = system; - - for (const i in this.runners) - { - this.runners[i].add(system); - } - - return this; - - /** - * 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. - */ - } - - /** - * Renders the object to its webGL view - * - * @param {PIXI.DisplayObject} displayObject - the object to be rendered - * @param {PIXI.RenderTexture} renderTexture - The render texture to render to. - * @param {boolean} [clear] - Should the canvas be cleared before the new render - * @param {PIXI.Transform} [transform] - A transform to apply to the render texture before rendering. - * @param {boolean} [skipUpdateTransform] - Should we skip the update transform pass? - */ - render(displayObject, renderTexture, clear, transform, skipUpdateTransform) - { - // can be handy to know! - this.renderingToScreen = !renderTexture; - - this.runners.prerender.run(); - this.emit('prerender'); - - // 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.parent; - - displayObject.parent = this._tempDisplayObjectParent; - displayObject.updateTransform(); - displayObject.parent = 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.run(); - - this.emit('postrender'); - } - - /** - * Resizes the webGL view to the specified width and height. - * - * @param {number} screenWidth - the new width of the screen - * @param {number} screenHeight - the new height of the screen - */ - resize(screenWidth, screenHeight) - { - AbstractRenderer.prototype.resize.call(this, screenWidth, screenHeight); - - this.runners.resize.run(screenWidth, screenHeight); - } - - /** - * Resets the WebGL state so you can render things however you fancy! - * - * @return {PIXI.Renderer} Returns itself. - */ - reset() - { - this.runners.reset.run(); - - return this; - } - - clear() - { - this.framebuffer.bind(); - this.framebuffer.clear(); - } - - /** - * Removes everything from the renderer (event listeners, spritebatch, etc...) - * - * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. - * See: https://github.com/pixijs/pixi.js/issues/2233 - */ - destroy(removeView) - { - this.runners.destroy.run(); - - // call base destroy - super.destroy(removeView); - - // TODO nullify all the managers.. - this.gl = null; - } - - /** - * 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 - * builds and excluding plugins. - * @name PIXI.Renderer#plugins - * @type {object} - * @readonly - * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. - * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. - * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. - * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. - */ - - /** - * Adds a plugin to the renderer. - * - * @method - * @param {string} pluginName - The name of the plugin. - * @param {Function} ctor - The constructor function or class for the plugin. - */ - static registerPlugin(pluginName, ctor) - { - Renderer.__plugins = Renderer.__plugins || {}; - Renderer.__plugins[pluginName] = ctor; - } -} diff --git a/packages/core/src/renderers/State.js b/packages/core/src/renderers/State.js deleted file mode 100644 index fa3f221..0000000 --- a/packages/core/src/renderers/State.js +++ /dev/null @@ -1,163 +0,0 @@ -/* eslint-disable max-len */ - -const BLEND = 0; -const OFFSET = 1; -const CULLING = 2; -const DEPTH_TEST = 3; -const WINDING = 4; - -/** - * This is a webGL state. It is passed The WebGL StateManager. - * Each mesh renderered may require webGL to be in a different state. - * For example you may want different blend mode or to enable polygon offsets - * - * @class - * @memberof PIXI - */ -export default class State -{ - /** - * - */ - constructor() - { - this.data = 0; - - this.blendMode = 0; - this.polygonOffset = 0; - - this.blend = true; - // this.depthTest = true; - } - - /** - * Activates blending of the computed fragment color values - * - * @member {boolean} - */ - get blend() - { - return !!(this.data & (1 << BLEND)); - } - - set blend(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << BLEND)) !== value) - { - this.data ^= (1 << BLEND); - } - } - - /** - * Activates adding an offset to depth values of polygon's fragments - * - * @member {boolean} - * @default false - */ - get offsets() - { - return !!(this.data & (1 << OFFSET)); - } - - set offsets(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << OFFSET)) !== value) - { - this.data ^= (1 << OFFSET); - } - } - - /** - * Activates culling of polygons. - * - * @member {boolean} - * @default false - */ - get culling() - { - return !!(this.data & (1 << CULLING)); - } - - set culling(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << CULLING)) !== value) - { - this.data ^= (1 << CULLING); - } - } - - /** - * Activates depth comparisons and updates to the depth buffer. - * - * @member {boolean} - * @default false - */ - get depthTest() - { - return !!(this.data & (1 << DEPTH_TEST)); - } - - set depthTest(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << DEPTH_TEST)) !== value) - { - this.data ^= (1 << DEPTH_TEST); - } - } - - /** - * Specifies whether or not front or back-facing polygons can be culled. - * @member {boolean} - * @default false - */ - get clockwiseFrontFace() - { - return !!(this.data & (1 << WINDING)); - } - - set clockwiseFrontFace(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << WINDING)) !== value) - { - this.data ^= (1 << WINDING); - } - } - - /** - * The blend mode to be applied when this state is set. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. - * Setting this mode to anything other than NO_BLEND will automatically switch blending on. - * - * @member {boolean} - * @default PIXI.BLEND_MODES.NORMAL - * @see PIXI.BLEND_MODES - */ - get blendMode() - { - return this._blendMode; - } - - set blendMode(value) // eslint-disable-line require-jsdoc - { - // 17 is NO BLEND - this.blend = (value !== 17); - this._blendMode = value; - } - - /** - * The polygon offset. Setting this property to anything other than 0 will automatically enable poygon offset fill. - * - * @member {number} - * @default 0 - */ - get polygonOffset() - { - return this._polygonOffset; - } - - set polygonOffset(value) // eslint-disable-line require-jsdoc - { - this.offsets = !!value; - this._polygonOffset = value; - } -} - diff --git a/packages/core/src/renderers/filters/Filter.js b/packages/core/src/renderers/filters/Filter.js deleted file mode 100644 index 2073b8d..0000000 --- a/packages/core/src/renderers/filters/Filter.js +++ /dev/null @@ -1,140 +0,0 @@ -import Shader from '../../shader/Shader'; -import Program from '../../shader/Program'; -import State from '../State'; -import { settings } from '@pixi/settings'; -// import extractUniformsFromSrc from './extractUniformsFromSrc'; -import defaultVertex from './defaultFilter.vert'; -import defaultFragment from './defaultFilter.frag'; - -// let math = require('../../math'); -/** - * @class - * @memberof PIXI - * @extends PIXI.Shader - */ -export default class Filter extends Shader -{ - /** - * @param {string} [vertexSrc] - The source of the vertex shader. - * @param {string} [fragmentSrc] - The source of the fragment shader. - * @param {object} [uniforms] - Custom uniforms to use to augment the built-in ones. - */ - constructor(vertexSrc, fragmentSrc, uniforms) - { - const program = Program.from(vertexSrc, fragmentSrc); - - super(program, uniforms); - - /** - * The padding of the filter. Some filters require extra space to breath such as a blur. - * Increasing this will add extra width and height to the bounds of the object that the - * filter is applied to. - * - * @member {number} - */ - this.padding = 0; - - /** - * The resolution of the filter. Setting this to be lower will lower the quality but - * increase the performance of the filter. - * - * @member {number} - */ - this.resolution = settings.RESOLUTION; - - /** - * If enabled is true the filter is applied, if false it will not. - * - * @member {boolean} - */ - this.enabled = true; - - /** - * If enabled, PixiJS will fit the filter area into boundaries for better performance. - * Switch it off if it does not work for specific shader. - * - * @member {boolean} - */ - this.autoFit = true; - - /** - * Legacy filters use position and uvs from attributes - * @member {boolean} - * @readonly - */ - this.legacy = !!this.program.attributeData.aTextureCoord; - - /** - * the webGL state the filter requires to render - * @member {PIXI.State} - */ - this.state = new State(); - } - - /** - * Applies the filter - * - * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from - * @param {PIXI.RenderTarget} input - The input render target. - * @param {PIXI.RenderTarget} output - The target to output to. - * @param {boolean} clear - Should the output be cleared before rendering to it - * @param {object} [currentState] - It's current state of filter. - * There are some useful properties in the currentState : - * target, filters, sourceFrame, destinationFrame, renderTarget, resolution - */ - apply(filterManager, input, output, clear, currentState, derp) // eslint-disable-line no-unused-vars - { - // do as you please! - - filterManager.applyFilter(this, input, output, clear, currentState, derp); - - // or just do a regular render.. - } - - /** - * Sets the blendmode of the filter - * - * @member {number} - * @default PIXI.BLEND_MODES.NORMAL - */ - get blendMode() - { - return this.state.blendMode; - } - - set blendMode(value) // eslint-disable-line require-jsdoc - { - this.state.blendMode = value; - } - - /** - * The default vertex shader source - * - * @static - * @constant - */ - static get defaultVertexSrc() - { - return defaultVertex; - } - - /** - * The default fragment shader source - * - * @static - * @constant - */ - static get defaultFragmentSrc() - { - return defaultFragment; - } -} - -/** - * Used for caching shader IDs - * - * @static - * @private - */ -Filter.SOURCE_KEY_MAP = {}; - diff --git a/packages/core/src/renderers/filters/defaultFilter.frag b/packages/core/src/renderers/filters/defaultFilter.frag deleted file mode 100644 index a14c1be..0000000 --- a/packages/core/src/renderers/filters/defaultFilter.frag +++ /dev/null @@ -1,22 +0,0 @@ -varying vec2 vTextureCoord; -varying vec2 vFilterCoord; - -uniform sampler2D uSampler; -uniform sampler2D filterSampler; - -void main(void){ - vec4 masky = texture2D(filterSampler, vFilterCoord); - vec4 sample = texture2D(uSampler, vTextureCoord); - vec4 color; - if(mod(vFilterCoord.x, 1.0) > 0.5) - { - color = vec4(1.0, 0.0, 0.0, 1.0); - } - else - { - color = vec4(0.0, 1.0, 0.0, 1.0); - } - // gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0); - gl_FragColor = mix(sample, masky, 0.5); - gl_FragColor *= sample.a; -} diff --git a/packages/core/src/renderers/filters/defaultFilter.vert b/packages/core/src/renderers/filters/defaultFilter.vert deleted file mode 100644 index e1febca..0000000 --- a/packages/core/src/renderers/filters/defaultFilter.vert +++ /dev/null @@ -1,14 +0,0 @@ -attribute vec2 aVertexPosition; -attribute vec2 aTextureCoord; - -uniform mat3 projectionMatrix; -uniform mat3 filterMatrix; - -varying vec2 vTextureCoord; -varying vec2 vFilterCoord; - -void main(void){ - gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); - vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy; - vTextureCoord = aTextureCoord ; -} diff --git a/packages/core/src/renderers/filters/filterTransforms.js b/packages/core/src/renderers/filters/filterTransforms.js deleted file mode 100644 index 87a3cd6..0000000 --- a/packages/core/src/renderers/filters/filterTransforms.js +++ /dev/null @@ -1,50 +0,0 @@ -import { Matrix } from '@pixi/math'; - -/** - * Calculates the mapped matrix - * @param filterArea {Rectangle} The filter area - * @param sprite {Sprite} the target sprite - * @param outputMatrix {Matrix} @alvin - * @private - */ -// this returns a matrix that will normalise map filter cords in the filter to screen space -export function calculateScreenSpaceMatrix(outputMatrix, filterArea, textureSize) -{ - // TODO unwrap? - const mappedMatrix = outputMatrix.identity(); - - mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); - - mappedMatrix.scale(textureSize.width, textureSize.height); - - return mappedMatrix; -} - -export function calculateNormalizedScreenSpaceMatrix(outputMatrix, filterArea, textureSize) -{ - const mappedMatrix = outputMatrix.identity(); - - mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); - - const translateScaleX = (textureSize.width / filterArea.width); - const translateScaleY = (textureSize.height / filterArea.height); - - mappedMatrix.scale(translateScaleX, translateScaleY); - - return mappedMatrix; -} - -// this will map the filter coord so that a texture can be used based on the transform of a sprite -export function calculateSpriteMatrix(outputMatrix, filterArea, textureSize, sprite) -{ - const orig = sprite._texture.orig; - const mappedMatrix = outputMatrix.set(textureSize.width, 0, 0, textureSize.height, filterArea.x, filterArea.y); - const worldTransform = sprite.worldTransform.copyTo(Matrix.TEMP_MATRIX); - - worldTransform.invert(); - mappedMatrix.prepend(worldTransform); - mappedMatrix.scale(1.0 / orig.width, 1.0 / orig.height); - mappedMatrix.translate(sprite.anchor.x, sprite.anchor.y); - - return mappedMatrix; -} diff --git a/packages/core/src/renderers/filters/spriteMask/SpriteMaskFilter.js b/packages/core/src/renderers/filters/spriteMask/SpriteMaskFilter.js deleted file mode 100644 index d5a91a4..0000000 --- a/packages/core/src/renderers/filters/spriteMask/SpriteMaskFilter.js +++ /dev/null @@ -1,63 +0,0 @@ -import Filter from '../Filter'; -import { Matrix } from '@pixi/math'; -import vertex from './spriteMaskFilter.vert'; -import fragment from './spriteMaskFilter.frag'; -import { default as TextureMatrix } from '../../../textures/TextureMatrix'; - -/** - * The SpriteMaskFilter class - * - * @class - * @extends PIXI.Filter - * @memberof PIXI - */ -export default class SpriteMaskFilter extends Filter -{ - /** - * @param {PIXI.Sprite} sprite - the target sprite - */ - constructor(sprite) - { - const maskMatrix = new Matrix(); - - super(vertex, fragment); - - sprite.renderable = false; - - this.maskSprite = sprite; - this.maskMatrix = maskMatrix; - } - - /** - * Applies the filter - * - * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from - * @param {PIXI.RenderTarget} input - The input render target. - * @param {PIXI.RenderTarget} output - The target to output to. - */ - apply(filterManager, input, output) - { - const maskSprite = this.maskSprite; - const tex = this.maskSprite.texture; - - if (!tex.valid) - { - return; - } - if (!tex.transform) - { - // margin = 0.0, let it bleed a bit, shader code becomes easier - // assuming that atlas textures were made with 1-pixel padding - tex.transform = new TextureMatrix(tex, 0.0); - } - tex.transform.update(); - - this.uniforms.mask = tex; - this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite) - .prepend(tex.transform.mapCoord); - this.uniforms.alpha = maskSprite.worldAlpha; - this.uniforms.maskClamp = tex.transform.uClampFrame; - - filterManager.applyFilter(this, input, output); - } -} diff --git a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.frag b/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.frag deleted file mode 100644 index 0e4aef8..0000000 --- a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.frag +++ /dev/null @@ -1,23 +0,0 @@ -varying vec2 vMaskCoord; -varying vec2 vTextureCoord; - -uniform sampler2D uSampler; -uniform sampler2D mask; -uniform float alpha; -uniform vec4 maskClamp; - -void main(void) -{ - float clip = step(3.5, - step(maskClamp.x, vMaskCoord.x) + - step(maskClamp.y, vMaskCoord.y) + - step(vMaskCoord.x, maskClamp.z) + - step(vMaskCoord.y, maskClamp.w)); - - vec4 original = texture2D(uSampler, vTextureCoord); - vec4 masky = texture2D(mask, vMaskCoord); - - original *= (masky.r * masky.a * alpha * clip); - - gl_FragColor = original; -} diff --git a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.vert b/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.vert deleted file mode 100644 index d23a30c..0000000 --- a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.vert +++ /dev/null @@ -1,16 +0,0 @@ -attribute vec2 aVertexPosition; -attribute vec2 aTextureCoord; - -uniform mat3 projectionMatrix; -uniform mat3 otherMatrix; - -varying vec2 vMaskCoord; -varying vec2 vTextureCoord; - -void main(void) -{ - gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); - - vTextureCoord = aTextureCoord; - vMaskCoord = ( otherMatrix * vec3( aTextureCoord, 1.0) ).xy; -} diff --git a/packages/core/src/renderers/systems/BatchSystem.js b/packages/core/src/renderers/systems/BatchSystem.js deleted file mode 100644 index 70e9518..0000000 --- a/packages/core/src/renderers/systems/BatchSystem.js +++ /dev/null @@ -1,68 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import ObjectRenderer from '../utils/ObjectRenderer'; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ - -export default class BatchSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this manager works for. - */ - constructor(renderer) - { - super(renderer); - - /** - * An empty renderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.emptyRenderer = new ObjectRenderer(renderer); - - /** - * The currently active ObjectRenderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.currentRenderer = this.emptyRenderer; - } - - /** - * Changes the current renderer to the one given in parameter - * - * @param {PIXI.ObjectRenderer} objectRenderer - The object renderer to use. - */ - setObjectRenderer(objectRenderer) - { - if (this.currentRenderer === objectRenderer) - { - return; - } - - this.currentRenderer.stop(); - this.currentRenderer = objectRenderer; - - this.renderer.state.setState(objectRenderer.state); - - this.currentRenderer.start(); - } - - /** - * This should be called if you wish to do some custom rendering - * It will basically render anything that may be batched up such as sprites - * - */ - flush() - { - this.setObjectRenderer(this.emptyRenderer); - } - - reset() - { - this.setObjectRenderer(this.emptyRenderer); - } -} diff --git a/packages/core/src/renderers/systems/ContextSystem.js b/packages/core/src/renderers/systems/ContextSystem.js deleted file mode 100644 index 43bf827..0000000 --- a/packages/core/src/renderers/systems/ContextSystem.js +++ /dev/null @@ -1,186 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { settings } from '@pixi/settings'; - -let CONTEXT_UID = 0; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class ContextSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this.webGLVersion = 1; - - this.handleContextLost = this.handleContextLost.bind(this); - this.handleContextRestored = this.handleContextRestored.bind(this); - - this.extensions = {}; - - renderer.view.addEventListener('webglcontextlost', this.handleContextLost, false); - renderer.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); - } - - get isLost() - { - return (!this.gl || this.gl.isContextLost()); - } - - contextChange(gl) - { - this.gl = gl; - - // restore a context if it was previously lost - if (gl.isContextLost() && gl.getExtension('WEBGL_lose_context')) - { - gl.getExtension('WEBGL_lose_context').restoreContext(); - } - } - - initFromContext(gl) - { - this.gl = gl; - this.validateContext(gl); - this.renderer.gl = gl; - this.renderer.CONTEXT_UID = CONTEXT_UID++; - this.renderer.runners.contextChange.run(gl); - } - - initFromOptions(options) - { - const gl = this.createContext(this.renderer.view, options); - - this.initFromContext(gl); - } - - /** - * Helper class to create a webGL Context - * - * @class - * @memberof PIXI.glCore - * @param canvas {HTMLCanvasElement} the canvas element that we will get the context from - * @param options {Object} An options object that gets passed in to the canvas element containing the context attributes, - * see https://developer.mozilla.org/en/docs/Web/API/HTMLCanvasElement/getContext for the options available - * @return {WebGLRenderingContext} the WebGL context - */ - createContext(canvas, options) - { - let gl; - - if (settings.PREFER_WEBGL_2) - { - gl = canvas.getContext('webgl2', options); - } - - if (gl) - { - this.webGLVersion = 2; - } - else - { - this.webGLVersion = 1; - - gl = canvas.getContext('webgl', options) - || canvas.getContext('experimental-webgl', options); - - if (!gl) - { - // fail, not able to get a context - throw new Error('This browser does not support webGL. Try using the canvas renderer'); - } - } - - this.gl = gl; - - this.getExtensions(); - - return gl; - } - - getExtensions() - { - // time to set up default etensions that pixi uses.. - const gl = this.gl; - const extensions = this.extensions; - - if (this.webGLVersion === 1) - { - extensions.drawBuffers = gl.getExtension('WEBGL_draw_buffers'); - extensions.depthTexture = gl.getExtension('WEBKIT_WEBGL_depth_texture'); - extensions.floatTexture = gl.getExtension('OES_texture_float'); - extensions.loseContext = gl.getExtension('WEBGL_lose_context'); - - extensions.vertexArrayObject = gl.getExtension('OES_vertex_array_object') - || gl.getExtension('MOZ_OES_vertex_array_object') - || gl.getExtension('WEBKIT_OES_vertex_array_object'); - } - - // we don't use any specific WebGL 2 ones yet! - } - - /** - * Handles a lost webgl context - * - * @private - * @param {WebGLContextEvent} event - The context lost event. - */ - handleContextLost(event) - { - event.preventDefault(); - } - - /** - * Handles a restored webgl context - * - * @private - */ - handleContextRestored() - { - this.renderer.runners.contextChange.run(this.gl); - } - - destroy() - { - const view = this.renderer.view; - - // remove listeners - view.removeEventListener('webglcontextlost', this.handleContextLost); - view.removeEventListener('webglcontextrestored', this.handleContextRestored); - - this.gl.useProgram(null); - - if (this.extensions.loseContext) - { - this.extensions.loseContext.loseContext(); - } - } - - postrender() - { - this.gl.flush(); - } - - validateContext(gl) - { - const attributes = gl.getContextAttributes(); - - // this is going to be fairly simple for now.. but at least we have room to grow! - if (!attributes.stencil) - { - /* eslint-disable max-len */ - - /* eslint-disable no-console */ - console.warn('Provided WebGL context does not have a stencil buffer, masks may not render correctly'); - /* eslint-enable no-console */ - - /* eslint-enable max-len */ - } - } -} diff --git a/packages/core/src/renderers/systems/FramebufferSystem.js b/packages/core/src/renderers/systems/FramebufferSystem.js deleted file mode 100644 index df7902b..0000000 --- a/packages/core/src/renderers/systems/FramebufferSystem.js +++ /dev/null @@ -1,246 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { Rectangle } from '@pixi/math'; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class FramebufferSystem extends WebGLSystem -{ - /** - * Sets up the renderer context and necessary buffers. - * - * @private - */ - contextChange() - { - this.gl = this.renderer.gl; - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - this.current = null; - this.viewport = new Rectangle(); - - this.drawBufferExtension = this.renderer.context.extensions.drawBuffers; - } - - bind(framebuffer, frame) - { - const gl = this.gl; - - this.current = framebuffer; - - if (framebuffer) - { - // TODO cacheing layer! - - const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID] || this.initFramebuffer(framebuffer); - - gl.bindFramebuffer(gl.FRAMEBUFFER, fbo.framebuffer); - // makesure all textures are unbound.. - - // now check for updates... - if (fbo.dirtyId !== framebuffer.dirtyId) - { - fbo.dirtyId = framebuffer.dirtyId; - - if (fbo.dirtyFormat !== framebuffer.dirtyFormat) - { - fbo.dirtyFormat = framebuffer.dirtyFormat; - this.updateFramebuffer(framebuffer); - } - else if (fbo.dirtySize !== framebuffer.dirtySize) - { - fbo.dirtySize = framebuffer.dirtySize; - this.resizeFramebuffer(framebuffer); - } - } - - for (let i = 0; i < framebuffer.colorTextures.length; i++) - { - if (framebuffer.colorTextures[i].texturePart) - { - this.renderer.texture.unbind(framebuffer.colorTextures[i].texture); - } - else - { - this.renderer.texture.unbind(framebuffer.colorTextures[i]); - } - } - - if (framebuffer.depthTexture) - { - this.renderer.texture.unbind(framebuffer.depthTexture); - } - - if (frame) - { - this.setViewport(frame.x, frame.y, frame.width, frame.height); - } - else - { - this.setViewport(0, 0, framebuffer.width, framebuffer.height); - } - } - else - { - gl.bindFramebuffer(gl.FRAMEBUFFER, null); - - if (frame) - { - this.setViewport(frame.x, frame.y, frame.width, frame.height); - } - else - { - this.setViewport(0, 0, this.renderer.width, this.renderer.height); - } - } - } - - setViewport(x, y, width, height) - { - const v = this.viewport; - - if (v.width !== width || v.height !== height || v.x !== x || v.y !== y) - { - v.x = x; - v.y = y; - v.width = width; - v.height = height; - - this.gl.viewport(x, y, width, height); - } - } - - get size() - { - if (this.current) - { - // TODO store temp - return { x: 0, y: 0, width: this.current.width, height: this.current.height }; - } - - return { x: 0, y: 0, width: this.renderer.width, height: this.renderer.height }; - } - - clear(r, g, b, a) - { - const gl = this.gl; - - // TODO clear color can be set only one right? - gl.clearColor(r, g, b, a); - gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); - } - - // private functions... - - initFramebuffer(framebuffer) - { - const gl = this.gl; - - // TODO - make this a class? - const fbo = { - framebuffer: gl.createFramebuffer(), - stencil: null, - dirtyId: 0, - dirtyFormat: 0, - dirtySize: 0, - }; - - framebuffer.glFrameBuffers[this.CONTEXT_UID] = fbo; - - return fbo; - } - - resizeFramebuffer(framebuffer) - { - const gl = this.gl; - - if (framebuffer.stencil || framebuffer.depth) - { - gl.bindRenderbuffer(gl.RENDERBUFFER, this.stencil); - gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); - } - } - - updateFramebuffer(framebuffer) - { - const gl = this.gl; - - const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID]; - - // bind the color texture - const colorTextures = framebuffer.colorTextures; - - let count = colorTextures.length; - - if (!this.drawBufferExtension) - { - count = Math.min(count, 1); - } - - const activeTextures = []; - - for (let i = 0; i < count; i++) - { - const texture = framebuffer.colorTextures[i]; - - if (texture.texturePart) - { - this.renderer.texture.bind(texture.texture, 0); - - gl.framebufferTexture2D(gl.FRAMEBUFFER, - gl.COLOR_ATTACHMENT0 + i, - gl.TEXTURE_CUBE_MAP_NEGATIVE_X + texture.side, - texture.texture._glTextures[this.CONTEXT_UID].texture, - 0); - } - else - { - this.renderer.texture.bind(texture, 0); - - gl.framebufferTexture2D(gl.FRAMEBUFFER, - gl.COLOR_ATTACHMENT0 + i, - gl.TEXTURE_2D, - texture._glTextures[this.CONTEXT_UID].texture, - 0); - } - - activeTextures.push(gl.COLOR_ATTACHMENT0 + i); - } - - if (this.drawBufferExtension && activeTextures.length > 1) - { - this.drawBufferExtension.drawBuffersWEBGL(activeTextures); - } - - if (framebuffer.depthTexture) - { - const depthTextureExt = this.renderer.context.extensions.depthTexture; - - if (depthTextureExt) - { - const depthTexture = framebuffer.depthTexture; - - this.renderer.texture.bind(depthTexture, 0); - - gl.framebufferTexture2D(gl.FRAMEBUFFER, - gl.DEPTH_ATTACHMENT, - gl.TEXTURE_2D, - depthTexture._glTextures[this.CONTEXT_UID].texture, - 0); - } - } - - if (framebuffer.stencil || framebuffer.depth) - { - fbo.stencil = gl.createRenderbuffer(); - - gl.bindRenderbuffer(gl.RENDERBUFFER, fbo.stencil); - - // TODO.. this is depth AND stencil? - gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, fbo.stencil); - gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); - // fbo.enableStencil(); - } - } -} diff --git a/packages/core/src/renderers/systems/MaskSystem.js b/packages/core/src/renderers/systems/MaskSystem.js deleted file mode 100644 index d9c4b1a..0000000 --- a/packages/core/src/renderers/systems/MaskSystem.js +++ /dev/null @@ -1,199 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import AlphaMaskFilter from '../filters/spriteMask/SpriteMaskFilter'; - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class MaskSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - // TODO - we don't need both! - this.scissor = false; - this.scissorData = null; - this.scissorRenderTarget = null; - - this.enableScissor = false; - - this.alphaMaskPool = []; - this.alphaMaskIndex = 0; - } - - /** - * Applies the Mask and adds it to the current filter stack. - * - * @param {PIXI.DisplayObject} target - Display Object to push the mask to - * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. - */ - push(target, maskData) - { - // TODO the root check means scissor rect will not - // be used on render textures more info here: - // https://github.com/pixijs/pixi.js/pull/3545 - - if (maskData.texture) - { - this.pushSpriteMask(target, maskData); - } - else if (this.enableScissor - && !this.scissor - && this.renderer._activeRenderTarget.root - && !this.renderer.stencil.stencilMaskStack.length - && maskData.isFastRect()) - { - const matrix = maskData.worldTransform; - - let rot = Math.atan2(matrix.b, matrix.a); - - // use the nearest degree! - rot = Math.round(rot * (180 / Math.PI)); - - if (rot % 90) - { - this.pushStencilMask(maskData); - } - else - { - this.pushScissorMask(target, maskData); - } - } - else - { - this.pushStencilMask(maskData); - } - } - - /** - * Removes the last mask from the mask stack and doesn't return it. - * - * @param {PIXI.DisplayObject} target - Display Object to pop the mask from - * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. - */ - pop(target, maskData) - { - if (maskData.texture) - { - this.popSpriteMask(target, maskData); - } - else if (this.enableScissor && !this.renderer.stencil.stencilMaskStack.length) - { - this.popScissorMask(target, maskData); - } - else - { - this.popStencilMask(target, maskData); - } - } - - /** - * Applies the Mask and adds it to the current filter stack. - * - * @param {PIXI.RenderTarget} target - Display Object to push the sprite mask to - * @param {PIXI.Sprite} maskData - Sprite to be used as the mask - */ - pushSpriteMask(target, maskData) - { - let alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; - - if (!alphaMaskFilter) - { - alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; - } - - alphaMaskFilter[0].resolution = this.renderer.resolution; - alphaMaskFilter[0].maskSprite = maskData; - - // TODO - may cause issues! - target.filterArea = maskData.getBounds(true); - - this.renderer.filterSystem.pushFilter(target, alphaMaskFilter); - - this.alphaMaskIndex++; - } - - /** - * Removes the last filter from the filter stack and doesn't return it. - * - */ - popSpriteMask() - { - this.renderer.filterSystem.popFilter(); - this.alphaMaskIndex--; - } - - /** - * Applies the Mask and adds it to the current filter stack. - * - * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. - */ - pushStencilMask(maskData) - { - this.renderer.batch.flush(); - this.renderer.stencil.pushStencil(maskData); - } - - /** - * Removes the last filter from the filter stack and doesn't return it. - * - */ - popStencilMask() - { - // this.renderer.currentRenderer.stop(); - this.renderer.stencil.popStencil(); - } - - /** - * - * @param {PIXI.DisplayObject} target - Display Object to push the mask to - * @param {PIXI.Graphics} maskData - The masking data. - */ - pushScissorMask(target, maskData) - { - maskData.renderable = true; - - const renderTarget = this.renderer._activeRenderTarget; - - const bounds = maskData.getBounds(); - - bounds.fit(renderTarget.size); - maskData.renderable = false; - - this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); - - const resolution = this.renderer.resolution; - - this.renderer.gl.scissor( - bounds.x * resolution, - (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, - bounds.width * resolution, - bounds.height * resolution - ); - - this.scissorRenderTarget = renderTarget; - this.scissorData = maskData; - this.scissor = true; - } - - /** - * - * - */ - popScissorMask() - { - this.scissorRenderTarget = null; - this.scissorData = null; - this.scissor = false; - - // must be scissor! - const gl = this.renderer.gl; - - gl.disable(gl.SCISSOR_TEST); - } -} diff --git a/packages/core/src/renderers/systems/ProjectionSystem.js b/packages/core/src/renderers/systems/ProjectionSystem.js deleted file mode 100644 index ec5efdb..0000000 --- a/packages/core/src/renderers/systems/ProjectionSystem.js +++ /dev/null @@ -1,73 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { Matrix } from '@pixi/math'; - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ - -export default class ProjectionSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this.projectionMatrix = new Matrix(); - } - - update(destinationFrame, sourceFrame, resolution, root) - { - this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; - this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; - - this.calculateProjection(this.destinationFrame, this.sourceFrame, resolution, root); - - this.renderer.globalUniforms.uniforms.projectionMatrix = this.projectionMatrix; - this.renderer.globalUniforms.update(); - } - - /** - * Updates the projection matrix based on a projection frame (which is a rectangle) - * - * @param {Rectangle} destinationFrame - The destination frame. - * @param {Rectangle} sourceFrame - The source frame. - */ - calculateProjection(destinationFrame, sourceFrame, resolution, root) - { - const pm = this.projectionMatrix; - - // I don't think we will need this line.. - // pm.identity(); - - if (!root) - { - pm.a = (1 / destinationFrame.width * 2) * resolution; - pm.d = (1 / destinationFrame.height * 2) * resolution; - - pm.tx = -1 - (sourceFrame.x * pm.a); - pm.ty = -1 - (sourceFrame.y * pm.d); - } - else - { - pm.a = (1 / destinationFrame.width * 2) * resolution; - pm.d = (-1 / destinationFrame.height * 2) * resolution; - - pm.tx = -1 - (sourceFrame.x * pm.a); - pm.ty = 1 - (sourceFrame.y * pm.d); - } - } - - /** - * Sets the transform of the active render target to the given matrix - * - * @param {PIXI.Matrix} matrix - The transformation matrix - */ - setTransform()// matrix) - { - // this._activeRenderTarget.transform = matrix; - } -} diff --git a/packages/core/src/renderers/systems/RenderTextureSystem.js b/packages/core/src/renderers/systems/RenderTextureSystem.js deleted file mode 100644 index a133ef1..0000000 --- a/packages/core/src/renderers/systems/RenderTextureSystem.js +++ /dev/null @@ -1,115 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { Rectangle } from '@pixi/math'; - -const tempRect = new Rectangle(); - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ - -export default class RenderTextureSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this.clearColor = renderer._backgroundColorRgba; - - // TODO moe this property somewhere else! - this.defaultMaskStack = []; - this.defaultFilterStack = [{}]; - - // empty render texture? - this.renderTexture = null; - - this.destinationFrame = new Rectangle(); - } - - bind(renderTexture, sourceFrame, destinationFrame) - { - // TODO - do we want this?? - if (this.renderTexture === renderTexture) return; - this.renderTexture = renderTexture; - - const renderer = this.renderer; - - if (renderTexture) - { - const baseTexture = renderTexture.baseTexture; - - if (!destinationFrame) - { - tempRect.width = baseTexture.realWidth; - tempRect.height = baseTexture.realHeight; - - destinationFrame = tempRect; - } - - if (!sourceFrame) - { - sourceFrame = destinationFrame; - } - - this.renderer.framebuffer.bind(baseTexture.frameBuffer, destinationFrame); - - this.renderer.projection.update(destinationFrame, sourceFrame, baseTexture.resolution, false); - this.renderer.stencil.setMaskStack(baseTexture.stencilMaskStack); - } - else - { - // TODO these validation checks happen deeper down.. - // thing they can be avoided.. - if (!destinationFrame) - { - tempRect.width = renderer.width; - tempRect.height = renderer.height; - - destinationFrame = tempRect; - } - - if (!sourceFrame) - { - sourceFrame = destinationFrame; - } - - renderer.framebuffer.bind(null, destinationFrame); - - // TODO store this.. - this.renderer.projection.update(destinationFrame, sourceFrame, this.renderer.resolution, true); - this.renderer.stencil.setMaskStack(this.defaultMaskStack); - } - - this.destinationFrame.copyFrom(destinationFrame); - } - - /** - * Erases the render texture and fills the drawing area with a colour - * - * @param {number} [clearColor] - The colour - * @return {PIXI.Renderer} Returns itself. - */ - clear(clearColor) - { - if (this.renderTexture) - { - clearColor = clearColor || this.renderTexture.baseTexture.clearColor; - } - else - { - clearColor = clearColor || this.clearColor; - } - - this.renderer.framebuffer.clear(clearColor[0], clearColor[1], clearColor[2], clearColor[3]); - } - - resize()// screenWidth, screenHeight) - { - // resize the root only! - this.bind(null); - } -} diff --git a/packages/core/src/renderers/systems/StateSystem.js b/packages/core/src/renderers/systems/StateSystem.js deleted file mode 100755 index d7f4caf..0000000 --- a/packages/core/src/renderers/systems/StateSystem.js +++ /dev/null @@ -1,288 +0,0 @@ -import mapWebGLBlendModesToPixi from '../utils/mapWebGLBlendModesToPixi'; -import WebGLSystem from './WebGLSystem'; -import WebGLState from '../State'; - -const BLEND = 0; -const OFFSET = 1; -const CULLING = 2; -const DEPTH_TEST = 3; -const WINDING = 4; - -/** - * A WebGL state machines - * - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class StateSystem extends WebGLSystem -{ - /** - * @param {WebGLRenderingContext} gl - The current WebGL rendering context - */ - constructor(renderer) - { - super(renderer); - - this.gl = null; - - this.maxAttribs = null; - - // check we have vao.. - this.nativeVaoExtension = null; - - this.attribState = null; - - this.stateId = 0; - this.polygonOffset = 0; - this.blendMode = 17; - - this.map = []; - - // map functions for when we set state.. - this.map[BLEND] = this.setBlend; - this.map[OFFSET] = this.setOffset; - this.map[CULLING] = this.setCullFace; - this.map[DEPTH_TEST] = this.setDepthTest; - this.map[WINDING] = this.setFrontFace; - - this.checks = []; - - this.defaultState = new WebGLState(); - this.defaultState.blend = true; - this.defaultState.depth = true; - } - - contextChange(gl) - { - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; - - this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); - - // check we have vao.. - this.nativeVaoExtension = ( - gl.getExtension('OES_vertex_array_object') - || gl.getExtension('MOZ_OES_vertex_array_object') - || gl.getExtension('WEBKIT_OES_vertex_array_object') - ); - - this.attribState = { - tempAttribState: new Array(this.maxAttribs), - attribState: new Array(this.maxAttribs), - }; - - this.blendModes = mapWebGLBlendModesToPixi(gl); - - this.setState(this.defaultState); - - this.reset(); - } - - /** - * Sets the current state - * - * @param {*} state - The state to set. - */ - setState(state) - { - state = state || this.defaultState; - - // TODO maybe to an object check? ( this.state === state )? - if (this.stateId !== state.data) - { - let diff = this.stateId ^ state.data; - let i = 0; - - // order from least to most common - while (diff) - { - if (diff & 1) - { - // state change! - this.map[i].call(this, !!(state.data & (1 << i))); - } - - diff = diff >> 1; - i++; - } - - this.stateId = state.data; - } - - // based on the above settings we check for specific modes.. - // for example if blend is active we check and set the blend modes - // or of polygon offset is active we check the poly depth. - for (let i = 0; i < this.checks.length; i++) - { - this.checks[i](this, state); - } - } - - /** - * Enables or disabled blending. - * - * @param {boolean} value - Turn on or off webgl blending. - */ - setBlend(value) - { - this.updateCheck(StateSystem.checkBlendMode, value); - - this.gl[value ? 'enable' : 'disable'](this.gl.BLEND); - } - - /** - * Enables or disable polygon offset fill - * - * @param {boolean} value - Turn on or off webgl polygon offset testing. - */ - setOffset(value) - { - this.gl[value ? 'enable' : 'disable'](this.gl.POLYGON_OFFSET_FILL); - } - - /** - * Sets whether to enable or disable depth test. - * - * @param {boolean} value - Turn on or off webgl depth testing. - */ - setDepthTest(value) - { - this.gl[value ? 'enable' : 'disable'](this.gl.DEPTH_TEST); - } - - /** - * Sets whether to enable or disable cull face. - * - * @param {boolean} value - Turn on or off webgl cull face. - */ - setCullFace(value) - { - this.gl[value ? 'enable' : 'disable'](this.gl.CULL_FACE); - } - - /** - * Sets the gl front face. - * - * @param {boolean} value - true is clockwise and false is counter-clockwise - */ - setFrontFace(value) - { - this.gl.frontFace(this.gl[value ? 'CW' : 'CCW']); - } - - /** - * Sets the blend mode. - * - * @param {number} value - The blend mode to set to. - */ - setBlendMode(value) - { - if (value === this.blendMode) - { - return; - } - - this.blendMode = value; - this.gl.blendFunc(this.blendModes[value][0], this.blendModes[value][1]); - } - - /** - * Sets the polygon offset. - * - * @param {number} value - the polygon offset - * @param {number} scale - the polygon offset scale - */ - setPolygonOffset(value, scale) - { - this.gl.polygonOffset(value, scale); - } - - /** - * Disables all the vaos in use - * - */ - resetAttributes() - { - for (let i = 0; i < this.attribState.tempAttribState.length; i++) - { - this.attribState.tempAttribState[i] = 0; - } - - for (let i = 0; i < this.attribState.attribState.length; i++) - { - this.attribState.attribState[i] = 0; - } - - // im going to assume one is always active for performance reasons. - for (let i = 1; i < this.maxAttribs; i++) - { - this.gl.disableVertexAttribArray(i); - } - } - - // used - /** - * Resets all the logic and disables the vaos - */ - reset() - { - // unbind any VAO if they exist.. - if (this.nativeVaoExtension) - { - this.nativeVaoExtension.bindVertexArrayOES(null); - } - - // reset all attributes.. - this.resetAttributes(); - - this.gl.pixelStorei(this.gl.UNPACK_FLIP_Y_WEBGL, false); - - this.setBlendMode(0); - - // TODO? - // this.setState(this.defaultState); - } - - /** - * checks to see which updates should be checked based on which settings have been activated - * for example if blend is enabled then we shold check the blend modes each time the state is changed - * or if poygon fill is activated then we need to check if the polygone offset changes. - * The idea is that we only check what we have too. - * - * @param {Function} func the checking function to add or remove - * @param {boolean} value should the check function be added or removed. - */ - updateCheck(func, value) - { - const index = this.checks.indexOf(func); - - if (value && index === -1) - { - this.checks.push(func); - } - else if (!value && index !== -1) - { - this.checks.splice(index, 1); - } - } - - /** - * A private little wrapper function that we call to check the blend mode. - * - * @static - * @private - * @param {PIXI.StateSystem} System the System to perform the state check on - * @param {PIXI.State} state the state that the blendMode will pulled from - */ - static checkBlendMode(system, state) - { - system.setBlendMode(state.blendMode); - } - - // TODO - add polygon offset? -} diff --git a/packages/core/src/renderers/systems/StencilSystem.js b/packages/core/src/renderers/systems/StencilSystem.js deleted file mode 100644 index 5d538cb..0000000 --- a/packages/core/src/renderers/systems/StencilSystem.js +++ /dev/null @@ -1,133 +0,0 @@ -import WebGLSystem from './WebGLSystem'; - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class StencilSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - this.stencilMaskStack = []; - } - - /** - * Changes the mask stack that is used by this System. - * - * @param {PIXI.Graphics[]} stencilMaskStack - The mask stack - */ - setMaskStack(stencilMaskStack) - { - const gl = this.renderer.gl; - - if (stencilMaskStack.length !== this.stencilMaskStack.length) - { - if (stencilMaskStack.length === 0) - { - gl.disable(gl.STENCIL_TEST); - } - else - { - gl.enable(gl.STENCIL_TEST); - } - } - - this.stencilMaskStack = stencilMaskStack; - } - - /** - * Applies the Mask and adds it to the current stencil stack. @alvin - * - * @param {PIXI.Graphics} graphics - The mask - */ - pushStencil(graphics) - { - this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); - - // this.renderer._activeRenderTarget.attachStencilBuffer(); - - const gl = this.renderer.gl; - const prevMaskCount = this.stencilMaskStack.length; - - if (prevMaskCount === 0) - { - gl.enable(gl.STENCIL_TEST); - } - - this.stencilMaskStack.push(graphics); - - // Increment the refference stencil value where the new mask overlaps with the old ones. - gl.colorMask(false, false, false, false); - gl.stencilFunc(gl.EQUAL, prevMaskCount, this._getBitwiseMask()); - gl.stencilOp(gl.KEEP, gl.KEEP, gl.INCR); - this.renderer.plugins.graphics.render(graphics); - - this._useCurrent(); - } - - /** - * Removes the last mask from the stencil stack. @alvin - */ - popStencil() - { - this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); - - const gl = this.renderer.gl; - const graphics = this.stencilMaskStack.pop(); - - if (this.stencilMaskStack.length === 0) - { - // the stack is empty! - gl.disable(gl.STENCIL_TEST); - gl.clear(gl.STENCIL_BUFFER_BIT); - gl.clearStencil(0); - } - else - { - // Decrement the refference stencil value where the popped mask overlaps with the other ones - gl.colorMask(false, false, false, false); - gl.stencilOp(gl.KEEP, gl.KEEP, gl.DECR); - this.renderer.plugins.graphics.render(graphics); - - this._useCurrent(); - } - } - - /** - * Setup renderer to use the current stencil data. - */ - _useCurrent() - { - const gl = this.renderer.gl; - - gl.colorMask(true, true, true, true); - gl.stencilFunc(gl.EQUAL, this.stencilMaskStack.length, this._getBitwiseMask()); - gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP); - } - - /** - * Fill 1s equal to the number of acitve stencil masks. - * - * @return {number} The bitwise mask. - */ - _getBitwiseMask() - { - return (1 << this.stencilMaskStack.length) - 1; - } - - /** - * Destroys the mask stack. - * - */ - destroy() - { - super.destroy(this); - - this.stencilMaskStack = null; - } -} diff --git a/packages/core/src/renderers/systems/WebGLSystem.js b/packages/core/src/renderers/systems/WebGLSystem.js deleted file mode 100644 index d3d5b31..0000000 --- a/packages/core/src/renderers/systems/WebGLSystem.js +++ /dev/null @@ -1,41 +0,0 @@ -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this manager works for. - */ - constructor(renderer) - { - /** - * The renderer this manager works for. - * - * @member {PIXI.Renderer} - */ - this.renderer = renderer; - - this.renderer.runners.contextChange.add(this); - } - - /** - * Generic method called when there is a WebGL context change. - * - */ - contextChange() - { - // do some codes init! - } - - /** - * Generic destroy methods to be overridden by the subclass - * - */ - destroy() - { - this.renderer.runners.contextChange.remove(this); - this.renderer = null; - } -} diff --git a/packages/core/src/renderers/systems/filter/FilterSystem.js b/packages/core/src/renderers/systems/filter/FilterSystem.js deleted file mode 100644 index e615037..0000000 --- a/packages/core/src/renderers/systems/filter/FilterSystem.js +++ /dev/null @@ -1,431 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; - -import RenderTexture from '../../../textures/RenderTexture'; -import Quad from '../../utils/Quad'; -import QuadUv from '../../utils/QuadUv'; -import { Rectangle } from '@pixi/math'; -import * as filterTransforms from '../../filters/filterTransforms'; -import bitTwiddle from 'bit-twiddle'; -import UniformGroup from '../../../shader/UniformGroup'; -import { DRAW_MODES } from '../../../../../constants'; - -// -/** - * @ignore - * @class - */ -class FilterState -{ - /** - * - */ - constructor() - { - this.renderTexture = null; - this.sourceFrame = new Rectangle(); - this.destinationFrame = new Rectangle(); - this.filters = []; - this.target = null; - this.legacy = false; - this.resolution = 1; - } -} - -/** - * @class - * @memberof PIXI.systems - * @extends PIXI.systems.WebGLSystem - */ -export default class FilterSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - /** - * stores a bunch of PO2 textures used for filtering - * @type {Object} - */ - this.texturePool = {}; - - /** - * a pool for storing filter states, save us creating new ones each tick - * @type {Array} - */ - this.statePool = []; - - /** - * A very simple geometry used when drawing a filter effect to the screen - * @type {Quad} - */ - this.quad = new Quad(); - - this.quadUv = new QuadUv(); - - /** - * Temporary rect for maths - * @type {PIXI.Rectangle} - */ - this.tempRect = new Rectangle(); - - this.activeState = {}; - - /** - * this uniform group is attached to filter uniforms when used - * @type {UniformGroup} - */ - this.globalUniforms = new UniformGroup({ - sourceFrame: this.tempRect, - destinationFrame: this.tempRect, - - // legacy variables - filterArea: new Float32Array(4), - filterClamp: new Float32Array(4), - }, true); - } - - /** - * Adds a new filter to the System. - * - * @param {PIXI.DisplayObject} target - The target of the filter to render. - * @param {PIXI.Filter[]} filters - The filters to apply. - */ - push(target, filters) - { - const renderer = this.renderer; - const filterStack = this.renderer.renderTexture.defaultFilterStack; - const state = this.statePool.pop() || new FilterState(); - - let resolution = filters[0].resolution; - let padding = filters[0].padding; - let autoFit = filters[0].autoFit; - let legacy = filters[0].legacy; - - for (let i = 1; i < filters.length; i++) - { - const filter = filters[i]; - - // lets use the lowest resolution.. - resolution = Math.min(resolution, filter.resolution); - // and the largest amount of padding! - padding = Math.max(padding, filter.padding); - // only auto fit if all filters are autofit - autoFit = autoFit || filter.autoFit; - - legacy = legacy || filter.legacy; - } - - filterStack.push(state); - - state.resolution = resolution; - - state.legacy = legacy; - - // round to whole number based on resolution - // TODO move that to the shader too? - state.sourceFrame = target.filterArea || target.getBounds(true); - - state.sourceFrame.pad(padding); - - if (autoFit) - { - state.sourceFrame.fit(this.renderer.renderTexture.destinationFrame); - } - - state.sourceFrame.round(resolution); - - state.renderTexture = this.getPotFilterTexture(state.sourceFrame.width, state.sourceFrame.height, resolution); - state.filters = filters; - - state.destinationFrame.width = state.renderTexture.width; - state.destinationFrame.height = state.renderTexture.height; - - state.renderTexture.filterFrame = state.sourceFrame; - renderer.renderTexture.bind(state.renderTexture, state.sourceFrame);// /, state.destinationFrame); - renderer.renderTexture.clear(); - } - - /** - * Pops off the filter and applies it. - * - */ - pop() - { - const renderer = this.renderer; - const filterStack = renderer.renderTexture.defaultFilterStack; - const state = filterStack.pop(); - const filters = state.filters; - - this.activeState = state; - - const globalUniforms = this.globalUniforms.uniforms; - - globalUniforms.sourceFrame = state.sourceFrame; - globalUniforms.destinationFrame = state.destinationFrame; - globalUniforms.resolution = state.resolution; - - // only update the rect if its legacy.. - if (state.legacy) - { - const filterArea = globalUniforms.filterArea; - const filterClamp = globalUniforms.filterClamp; - - filterArea[0] = state.destinationFrame.width; - filterArea[1] = state.destinationFrame.height; - filterArea[2] = state.sourceFrame.x; - filterArea[3] = state.sourceFrame.y; - - filterClamp[0] = 0.5 / state.resolution / state.destinationFrame.width; - filterClamp[1] = 0.5 / state.resolution / state.destinationFrame.height; - filterClamp[2] = (state.sourceFrame.width - 0.5) / state.resolution / state.destinationFrame.width; - filterClamp[3] = (state.sourceFrame.height - 0.5) / state.resolution / state.destinationFrame.height; - } - - this.globalUniforms.update(); - - const lastState = filterStack[filterStack.length - 1]; - - if (filters.length === 1) - { - filters[0].apply(this, state.renderTexture, lastState.renderTexture, false, state); - - this.returnFilterTexture(state.renderTexture); - } - else - { - let flip = state.renderTexture; - let flop = this.getPotFilterTexture( - flip.width, - flip.height, - state.resolution - ); - - flop.filterFrame = flip.filterFrame; - - let i = 0; - - for (i = 0; i < filters.length - 1; ++i) - { - filters[i].apply(this, flip, flop, true, state); - - const t = flip; - - flip = flop; - flop = t; - } - - filters[i].apply(this, flip, lastState.renderTexture, false, state); - - this.returnFilterTexture(flip); - this.returnFilterTexture(flop); - } - - this.statePool.push(state); - } - - /** - * Draws a filter. - * - * @param {PIXI.Filter} filter - The filter to draw. - * @param {PIXI.RenderTarget} input - The input render target. - * @param {PIXI.RenderTarget} output - The target to output to. - * @param {boolean} clear - Should the output be cleared before rendering to it - */ - applyFilter(filter, input, output, clear) - { - const renderer = this.renderer; - - renderer.renderTexture.bind(output, output ? output.filterFrame : null); - - if (clear) - { - // gl.disable(gl.SCISSOR_TEST); - renderer.renderTexture.clear(); - // gl.enable(gl.SCISSOR_TEST); - } - - // set the uniforms.. - filter.uniforms.uSampler = input; - filter.uniforms.filterGlobals = this.globalUniforms; - - // TODO make it so that the order of this does not matter.. - // because it does at the moment cos of global uniforms. - // they need to get resynced - - renderer.state.setState(filter.state); - renderer.shader.bind(filter); - - if (filter.legacy) - { - this.quadUv.map(input._frame, input.filterFrame); - - renderer.geometry.bind(this.quadUv); - renderer.geometry.draw(DRAW_MODES.TRIANGLES); - } - else - { - renderer.geometry.bind(this.quad); - renderer.geometry.draw(DRAW_MODES.TRIANGLE_STRIP); - } - } - - /** - * Calculates the mapped matrix. - * - * TODO playing around here.. this is temporary - (will end up in the shader) - * this returns a matrix that will normalise map filter cords in the filter to screen space - * - * @param {PIXI.Matrix} outputMatrix - the matrix to output to. - * @return {PIXI.Matrix} The mapped matrix. - */ - calculateScreenSpaceMatrix(outputMatrix) - { - const currentState = this.activeState; - - return filterTransforms.calculateScreenSpaceMatrix( - outputMatrix, - currentState.sourceFrame, - currentState.destinationFrame - ); - } - - /** - * This will map the filter coord so that a texture can be used based on the transform of a sprite - * - * @param {PIXI.Matrix} outputMatrix - The matrix to output to. - * @param {PIXI.Sprite} sprite - The sprite to map to. - * @return {PIXI.Matrix} The mapped matrix. - */ - calculateSpriteMatrix(outputMatrix, sprite) - { - const currentState = this.activeState; - - return filterTransforms.calculateSpriteMatrix( - outputMatrix, - currentState.sourceFrame, - currentState.destinationFrame, - sprite - ); - } - - /** - * Destroys this Filter System. - * - * @param {boolean} [contextLost=false] context was lost, do not free shaders - * - */ - destroy(contextLost = false) - { - if (!contextLost) - { - this.emptyPool(); - } - else - { - this.texturePool = {}; - } - } - - /** - * Gets a Power-of-Two render texture. - * - * TODO move to a seperate class could be on renderer? - * also - could cause issue with multiple contexts? - * - * @private - * @param {WebGLRenderingContext} gl - The webgl rendering context - * @param {number} minWidth - The minimum width of the render target. - * @param {number} minHeight - The minimum height of the render target. - * @param {number} resolution - The resolution of the render target. - * @return {PIXI.RenderTarget} The new render target. - */ - getPotFilterTexture(minWidth, minHeight, resolution) - { - minWidth = bitTwiddle.nextPow2(minWidth); - minHeight = bitTwiddle.nextPow2(minHeight); - resolution = resolution || 1; - - const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); - - if (!this.texturePool[key]) - { - this.texturePool[key] = []; - } - - let renderTexture = this.texturePool[key].pop(); - - if (!renderTexture) - { - // temporary bypass cache.. - // internally - this will cause a texture to be bound.. - renderTexture = RenderTexture.create({ - width: minWidth, - height: minHeight, - resolution, - }); - } - - return renderTexture; - } - - /** - * Gets extra render texture to use inside current filter - * - * @param {number} resolution resolution of the renderTexture - * @returns {PIXI.RenderTexture} - */ - getFilterTexture(resolution) - { - const rt = this.activeState.renderTexture; - - const filterTexture = this.getPotFilterTexture(rt.width, rt.height, resolution || rt.baseTexture.resolution); - - filterTexture.filterFrame = rt.filterFrame; - - return filterTexture; - } - - /** - * Frees a render texture back into the pool. - * - * @param {PIXI.RenderTarget} renderTexture - The renderTarget to free - */ - returnFilterTexture(renderTexture) - { - renderTexture.filterFrame = null; - - const base = renderTexture.baseTexture; - - const minWidth = base.width; - const minHeight = base.height; - - const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); - - this.texturePool[key].push(renderTexture); - } - - /** - * Empties the texture pool. - * - */ - emptyPool() - { - for (const i in this.texturePool) - { - const textures = this.texturePool[i]; - - if (textures) - { - for (let j = 0; j < textures.length; j++) - { - textures[j].destroy(true); - } - } - } - - this.texturePool = {}; - } -} diff --git a/packages/core/src/renderers/systems/geometry/GLBuffer.js b/packages/core/src/renderers/systems/geometry/GLBuffer.js deleted file mode 100644 index 0477e8a..0000000 --- a/packages/core/src/renderers/systems/geometry/GLBuffer.js +++ /dev/null @@ -1,9 +0,0 @@ -export default class GLBuffer -{ - constructor(buffer) - { - this.buffer = buffer; - this.updateID = -1; - this.byteLength = -1; - } -} diff --git a/packages/core/src/renderers/systems/geometry/GeometrySystem.js b/packages/core/src/renderers/systems/geometry/GeometrySystem.js deleted file mode 100644 index 26c8e9e..0000000 --- a/packages/core/src/renderers/systems/geometry/GeometrySystem.js +++ /dev/null @@ -1,388 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; -import GLBuffer from './GLBuffer'; - -const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class GeometrySystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this._activeGeometry = null; - this._activeVao = null; - - this.hasVao = true; - this.hasInstance = true; - } - - /** - * Sets up the renderer context and necessary buffers. - * - * @private - */ - contextChange() - { - const gl = this.gl = this.renderer.gl; - - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - - // webgl2 - if (!gl.createVertexArray) - { - // webgl 1! - let nativeVaoExtension = this.renderer.context.extensions.vertexArrayObject; - - if (this.renderer.legacy) - { - nativeVaoExtension = null; - } - - if (nativeVaoExtension) - { - gl.createVertexArray = () => - nativeVaoExtension.createVertexArrayOES(); - - gl.bindVertexArray = (vao) => - nativeVaoExtension.bindVertexArrayOES(vao); - - gl.deleteVertexArray = (vao) => - nativeVaoExtension.deleteVertexArrayOES(vao); - } - else - { - this.hasVao = false; - gl.createVertexArray = () => - { - // empty - }; - - gl.bindVertexArray = () => - { - // empty - }; - - gl.deleteVertexArray = () => - { - // empty - }; - } - } - - if (!gl.vertexAttribDivisor) - { - const instanceExt = gl.getExtension('ANGLE_instanced_arrays'); - - if (instanceExt) - { - gl.vertexAttribDivisor = (a, b) => - instanceExt.vertexAttribDivisorANGLE(a, b); - - gl.drawElementsInstanced = (a, b, c, d, e) => - instanceExt.drawElementsInstancedANGLE(a, b, c, d, e); - - gl.drawArraysInstanced = (a, b, c, d) => - instanceExt.drawArraysInstancedANGLE(a, b, c, d); - } - else - { - this.hasInstance = false; - } - } - } - - /** - * Binds geometry so that is can be drawn. Creating a Vao if required - * @private - * @param {PIXI.Geometry} geometry instance of geometry to bind - */ - bind(geometry, shader) - { - shader = shader || this.renderer.shader.shader; - - const gl = this.gl; - - // not sure the best way to address this.. - // currently different shaders require different VAOs for the same geometry - // Still mulling over the best way to solve this one.. - // will likely need to modify the shader attribute locations at run time! - let vaos = geometry.glVertexArrayObjects[this.CONTEXT_UID]; - - if (!vaos) - { - geometry.glVertexArrayObjects[this.CONTEXT_UID] = vaos = {}; - } - - const vao = vaos[shader.program.id] || this.initGeometryVao(geometry, shader.program); - - this._activeGeometry = geometry; - - if (this._activeVao !== vao) - { - this._activeVao = vao; - - if (this.hasVao) - { - gl.bindVertexArray(vao); - } - else - { - this.activateVao(geometry, shader.program); - } - } - - // TODO - optimise later! - // don't need to loop through if nothing changed! - // maybe look to add an 'autoupdate' to geometry? - this.updateBuffers(); - } - - reset() - { - this.unbind(); - } - - updateBuffers() - { - const geometry = this._activeGeometry; - const gl = this.gl; - - for (let i = 0; i < geometry.buffers.length; i++) - { - const buffer = geometry.buffers[i]; - - const glBuffer = buffer._glBuffers[this.CONTEXT_UID]; - - if (buffer._updateID !== glBuffer.updateID) - { - glBuffer.updateID = buffer._updateID; - - // TODO can cache this on buffer! maybe added a getter / setter? - const type = buffer.index ? gl.ELEMENT_ARRAY_BUFFER : gl.ARRAY_BUFFER; - const drawType = buffer.static ? gl.STATIC_DRAW : gl.DYNAMIC_DRAW; - - gl.bindBuffer(type, glBuffer.buffer); - - if (glBuffer.byteLength >= buffer.data.byteLength) - { - // offset is always zero for now! - gl.bufferSubData(type, 0, buffer.data); - } - else - { - gl.bufferData(type, buffer.data, drawType); - } - } - } - } - - checkCompatability(geometry, program) - { - // geometry must have at least all the attributes that the shader requires. - const geometryAttributes = geometry.attributes; - const shaderAttributes = program.attributeData; - - for (const j in shaderAttributes) - { - if (!geometryAttributes[j]) - { - throw new Error(`shader and geometry incompatible, geometry missing the "${j}" attribute`); - } - } - } - - /** - * Creates a Vao with the same structure as the geometry and stores it on the geometry. - * @private - * @param {PIXI.Geometry} geometry instance of geometry to to generate Vao for - */ - initGeometryVao(geometry, program) - { - this.checkCompatability(geometry, program); - - const gl = this.gl; - const CONTEXT_UID = this.CONTEXT_UID; - const buffers = geometry.buffers; - const attributes = geometry.attributes; - - const tempStride = {}; - const tempStart = {}; - - for (const j in buffers) - { - tempStride[j] = 0; - tempStart[j] = 0; - } - - for (const j in attributes) - { - if (!attributes[j].size && program.attributeData[j]) - { - attributes[j].size = program.attributeData[j].size; - } - - tempStride[attributes[j].buffer] += attributes[j].size * byteSizeMap[attributes[j].type]; - } - - for (const j in attributes) - { - const attribute = attributes[j]; - const attribSize = attribute.size; - - if (attribute.stride === undefined) - { - if (tempStride[attribute.buffer] === attribSize * byteSizeMap[attribute.type]) - { - attribute.stride = 0; - } - else - { - attribute.stride = tempStride[attribute.buffer]; - } - } - - if (attribute.start === undefined) - { - attribute.start = tempStart[attribute.buffer]; - - tempStart[attribute.buffer] += attribSize * byteSizeMap[attribute.type]; - } - } - - // first update - and create the buffers! - // only create a gl buffer if it actually gets - for (let i = 0; i < buffers.length; i++) - { - const buffer = buffers[i]; - - if (!buffer._glBuffers[CONTEXT_UID]) - { - buffer._glBuffers[CONTEXT_UID] = new GLBuffer(gl.createBuffer()); - } - } - - // TODO - maybe make this a data object? - // lets wait to see if we need to first! - const vao = gl.createVertexArray(); - - gl.bindVertexArray(vao); - - this.activateVao(geometry, program); - - gl.bindVertexArray(null); - - // add it to the cache! - geometry.glVertexArrayObjects[this.CONTEXT_UID][program.id] = vao; - - return vao; - } - - activateVao(geometry, program) - { - const gl = this.gl; - const CONTEXT_UID = this.CONTEXT_UID; - const buffers = geometry.buffers; - const attributes = geometry.attributes; - - if (geometry.indexBuffer) - { - // first update the index buffer if we have one.. - gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, geometry.indexBuffer._glBuffers[CONTEXT_UID].buffer); - } - - let lastBuffer = null; - - // add a new one! - for (const j in attributes) - { - const attribute = attributes[j]; - const buffer = buffers[attribute.buffer]; - const glBuffer = buffer._glBuffers[CONTEXT_UID]; - - if (program.attributeData[j]) - { - if (lastBuffer !== glBuffer) - { - gl.bindBuffer(gl.ARRAY_BUFFER, glBuffer.buffer); - - lastBuffer = glBuffer; - } - - const location = program.attributeData[j].location; - - // TODO introduce state again - // we can optimise this for older devices that have no VAOs - gl.enableVertexAttribArray(location); - - gl.vertexAttribPointer(location, - attribute.size, - attribute.type || gl.FLOAT, - attribute.normalized, - attribute.stride, - attribute.start); - - if (attribute.instance) - { - // TODO calculate instance count based of this... - if (this.hasInstance) - { - gl.vertexAttribDivisor(location, 1); - } - else - { - throw new Error('geometry error, GPU Instancing is not supported on this device'); - } - } - } - } - } - - draw(type, size, start, instanceCount) - { - const gl = this.gl; - const geometry = this._activeGeometry; - - // TODO.. this should not change so maybe cache the function? - - if (geometry.indexBuffer) - { - if (geometry.instanced) - { - /* eslint-disable max-len */ - gl.drawElementsInstanced(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2, instanceCount || 1); - /* eslint-enable max-len */ - } - else - { - gl.drawElements(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2); - } - } - else - if (geometry.instanced) - { - // TODO need a better way to calculate size.. - gl.drawArraysInstanced(type, start, size || geometry.getSize(), instanceCount || 1); - } - else - { - gl.drawArrays(type, start, size || geometry.getSize()); - } - - return this; - } - - unbind() - { - this.gl.bindVertexArray(null); - this._activeVao = null; - this._activeGeometry = null; - } -} diff --git a/packages/core/src/AbstractRenderer.js b/packages/core/src/AbstractRenderer.js new file mode 100644 index 0000000..64b2eb1 --- /dev/null +++ b/packages/core/src/AbstractRenderer.js @@ -0,0 +1,353 @@ +import { hex2string, hex2rgb } from '@pixi/utils'; +import { Matrix, Rectangle } from '@pixi/math'; +import { RENDERER_TYPE } from '@pixi/constants'; +import { settings } from '@pixi/settings'; +import { Container } from '@pixi/display'; +import RenderTexture from './renderTexture/RenderTexture'; +import EventEmitter from 'eventemitter3'; + +const tempMatrix = new Matrix(); + +/** + * 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 EventEmitter + * @memberof PIXI + */ +export default class AbstractRenderer extends EventEmitter +{ + // eslint-disable-next-line valid-jsdoc + /** + * @param {string} system - The name of the system this renderer is for. + * @param {object} [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.transparent=false] - If the render view is transparent, default false + * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false + * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. The + * resolution of the renderer retina would be 2. + * @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 {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when rendering, + * stopping pixel interpolation. + */ + constructor(system, options, arg2, arg3) + { + super(); + + // Support for constructor(system, screenWidth, screenHeight, options) + if (typeof options === 'number') + { + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + }, arg3); + } + + // 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 = RENDERER_TYPE.UNKNOWN; + + /** + * Measurements of the screen. (0, 0, screenWidth, screenHeight) + * + * Its safe to use as filterArea or hitArea for 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 1 + */ + this.resolution = options.resolution || settings.RESOLUTION; + + /** + * Whether the render view is transparent + * + * @member {boolean} + */ + this.transparent = options.transparent; + + /** + * Whether css dimensions of canvas view should be resized to screen dimensions automatically + * + * @member {boolean} + */ + this.autoResize = options.autoResize || false; + + /** + * Tracks the blend modes useful for this renderer. + * + * @member {object} + */ + this.blendModes = null; + + /** + * 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; + + /** + * If true PixiJS will Math.floor() x/y values when rendering, stopping pixel interpolation. + * Handy for crisp pixel art and speed on legacy devices. + * + * @member {boolean} + */ + this.roundPixels = options.roundPixels; + + /** + * The background color as a number. + * + * @member {number} + * @private + */ + this._backgroundColor = 0x000000; + + /** + * The background color as an [R, G, B] array. + * + * @member {number[]} + * @private + */ + this._backgroundColorRgba = [0, 0, 0, 0]; + + /** + * The background color as a string. + * + * @member {string} + * @private + */ + this._backgroundColorString = '#000000'; + + this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter + + /** + * This temporary display object used as the parent of the currently being rendered item + * + * @member {PIXI.DisplayObject} + * @private + */ + this._tempDisplayObjectParent = new Container(); + + /** + * The last root object that the renderer tried to render. + * + * @member {PIXI.DisplayObject} + * @private + */ + this._lastObjectRendered = this._tempDisplayObjectParent; + + /** + * Collection of plugins. + * @readonly + * @member {object} + */ + this.plugins = {}; + } + + /** + * Initialize the plugins. + * + * @protected + * @param {object} staticMap - The dictionary of staticly saved plugins. + */ + initPlugins(staticMap) + { + 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() + { + return this.view.width; + } + + /** + * Same as view.height, actual number of pixels in the canvas by vertical + * + * @member {number} + * @readonly + * @default 600 + */ + get height() + { + return this.view.height; + } + + /** + * Resizes the screen and canvas to the specified width and height + * Canvas dimensions are multiplied by resolution + * + * @param {number} screenWidth - the new width of the screen + * @param {number} screenHeight - the new height of the screen + */ + resize(screenWidth, screenHeight) + { + this.screen.width = screenWidth; + this.screen.height = screenHeight; + + this.view.width = screenWidth * this.resolution; + this.view.height = screenHeight * this.resolution; + + if (this.autoResize) + { + this.view.style.width = `${screenWidth}px`; + this.view.style.height = `${screenHeight}px`; + } + } + + /** + * 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 {PIXI.DisplayObject} displayObject - The displayObject the object will be generated from + * @param {number} scaleMode - Should be one of the scaleMode consts + * @param {number} resolution - The resolution / device pixel ratio of the texture being generated + * @param {PIXI.Rectangle} [region] - The region of the displayObject, that shall be rendered, + * if no region is specified, defaults to the local bounds of the displayObject. + * @return {PIXI.Texture} a texture of the graphics object + */ + generateTexture(displayObject, scaleMode, resolution, region) + { + region = region || displayObject.getLocalBounds(); + + // 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(region.width | 0, region.height | 0, scaleMode, resolution); + + tempMatrix.tx = -region.x; + tempMatrix.ty = -region.y; + + this.render(displayObject, renderTexture, false, tempMatrix, true); + + return renderTexture; + } + + /** + * Removes everything from the renderer and optionally removes the Canvas DOM element. + * + * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. + */ + destroy(removeView) + { + 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); + } + + this.plugins = null; + + this.type = RENDERER_TYPE.UNKNOWN; + + this.view = null; + + this.screen = null; + + this.resolution = 0; + + this.transparent = false; + + this.autoResize = false; + + this.blendModes = null; + + this.options = null; + + this.preserveDrawingBuffer = false; + this.clearBeforeRender = false; + + this.roundPixels = false; + + this._backgroundColor = 0; + this._backgroundColorRgba = null; + this._backgroundColorString = null; + + this._tempDisplayObjectParent = null; + this._lastObjectRendered = null; + } + + /** + * The background color to fill if not transparent + * + * @member {number} + */ + get backgroundColor() + { + return this._backgroundColor; + } + + set backgroundColor(value) // eslint-disable-line require-jsdoc + { + this._backgroundColor = value; + this._backgroundColorString = hex2string(value); + hex2rgb(value, this._backgroundColorRgba); + } +} diff --git a/packages/core/src/Renderer.js b/packages/core/src/Renderer.js new file mode 100644 index 0000000..2227e78 --- /dev/null +++ b/packages/core/src/Renderer.js @@ -0,0 +1,338 @@ +import AbstractRenderer from './AbstractRenderer'; +import { sayHello } from '@pixi/utils'; +import MaskSystem from './mask/MaskSystem'; +import StencilSystem from './mask/StencilSystem'; +import FilterSystem from './filters/FilterSystem'; +import FramebufferSystem from './framebuffer/FramebufferSystem'; +import RenderTextureSystem from './renderTexture/RenderTextureSystem'; +import TextureSystem from './textures/TextureSystem'; +import ProjectionSystem from './projection/ProjectionSystem'; +import StateSystem from './state/StateSystem'; +import GeometrySystem from './geometry/GeometrySystem'; +import ShaderSystem from './shader/ShaderSystem'; +import ContextSystem from './context/ContextSystem'; +import BatchSystem from './batch/BatchSystem'; +import TextureGCSystem from './textures/TextureGCSystem'; +import { RENDERER_TYPE } from '@pixi/constants'; +import UniformGroup from './shader/UniformGroup'; +import { Matrix } from '@pixi/math'; +import Runner from 'mini-runner'; + +/** + * 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 Render works by automatically managing webGLBatchs. + * 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 :) + * + * @class + * @memberof PIXI + * @extends PIXI.AbstractRenderer + */ +export default class Renderer extends AbstractRenderer +{ + // eslint-disable-next-line valid-jsdoc + /** + * + * @param {object} [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.transparent=false] - If the render view is transparent, default false + * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false + * @param {boolean} [options.antialias=false] - sets antialias. If not available natively then FXAA + * antialiasing is used + * @param {boolean} [options.forceFXAA=false] - forces FXAA antialiasing to be used over native. + * FXAA is faster, but may not always look as great + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. + * The resolution of the renderer retina would be 2. + * @param {boolean} [options.clearBeforeRender=true] - This sets if the renderer will clear + * the canvas or not before the new render pass. If you wish to set this to false, you *must* set + * preserveDrawingBuffer to `true`. + * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, + * enable this if you need to call toDataUrl on the webgl context. + * @param {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when + * rendering, stopping pixel interpolation. + * @param {number} [options.backgroundColor=0x000000] - The background color of the rendered area + * (shown if not transparent). + * @param {boolean} [options.legacy=false] - If true PixiJS will aim to ensure compatibility + * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. + * @param {string} [options.powerPreference] - Parameter passed to webgl context, set to "high-performance" + * for devices with dual graphics card + */ + constructor(options, arg2, arg3) + { + super('WebGL', options, arg2, arg3); + + /** + * The type of this renderer as a standardised const + * + * @member {number} + * @see PIXI.RENDERER_TYPE + */ + this.type = RENDERER_TYPE.WEBGL; + + // this will be set by the contextSystem (this.context) + this.gl = null; + this.CONTEXT_UID = 0; + this.legacy = !!options.legacy; + + // TODO legacy! + + // runners! + this.runners = { + destroy: new Runner('destroy'), + contextChange: new Runner('contextChange', 1), + reset: new Runner('reset'), + update: new Runner('update'), + postrender: new Runner('postrender'), + prerender: new Runner('prerender'), + resize: new Runner('resize', 2), + }; + + 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(GeometrySystem, 'geometry') + .addSystem(FramebufferSystem, 'framebuffer') + .addSystem(StencilSystem, 'stencil') + .addSystem(ProjectionSystem, 'projection') + .addSystem(TextureGCSystem, 'textureGC') + .addSystem(FilterSystem, 'filter') + .addSystem(RenderTextureSystem, 'renderTexture') + .addSystem(BatchSystem, 'batch'); + + this.initPlugins(Renderer.__plugins); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ + if (options.context) + { + this.context.initFromContext(options.context); + } + else + { + this.context.initFromOptions({ + alpha: this.transparent, + antialias: options.antialias, + premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', + stencil: true, + 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); + } + + /** + * Add a new system to the renderer. + * @param {class} ClassRef - Class reference + * @param {string} [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. + * @return {PIXI.Renderer} Return instance of renderer + */ + addSystem(ClassRef, name) + { + if (!name) + { + name = ClassRef.name; + } + + // TODO - read name from class.name.. + + /* + if(name.includes('System')) + { + name = name.replace('System', ''); + name = name.charAt(0).toLowerCase() + name.slice(1); + } + */ + + const system = new ClassRef(this); + + if (this[name]) + { + throw new Error(`Whoops! ${name} is already a manger`); + } + + this[name] = system; + + for (const i in this.runners) + { + this.runners[i].add(system); + } + + return this; + + /** + * 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. + */ + } + + /** + * Renders the object to its webGL view + * + * @param {PIXI.DisplayObject} displayObject - the object to be rendered + * @param {PIXI.RenderTexture} renderTexture - The render texture to render to. + * @param {boolean} [clear] - Should the canvas be cleared before the new render + * @param {PIXI.Transform} [transform] - A transform to apply to the render texture before rendering. + * @param {boolean} [skipUpdateTransform] - Should we skip the update transform pass? + */ + render(displayObject, renderTexture, clear, transform, skipUpdateTransform) + { + // can be handy to know! + this.renderingToScreen = !renderTexture; + + this.runners.prerender.run(); + this.emit('prerender'); + + // 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.parent; + + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = 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.run(); + + this.emit('postrender'); + } + + /** + * Resizes the webGL view to the specified width and height. + * + * @param {number} screenWidth - the new width of the screen + * @param {number} screenHeight - the new height of the screen + */ + resize(screenWidth, screenHeight) + { + AbstractRenderer.prototype.resize.call(this, screenWidth, screenHeight); + + this.runners.resize.run(screenWidth, screenHeight); + } + + /** + * Resets the WebGL state so you can render things however you fancy! + * + * @return {PIXI.Renderer} Returns itself. + */ + reset() + { + this.runners.reset.run(); + + return this; + } + + clear() + { + this.framebuffer.bind(); + this.framebuffer.clear(); + } + + /** + * Removes everything from the renderer (event listeners, spritebatch, etc...) + * + * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. + * See: https://github.com/pixijs/pixi.js/issues/2233 + */ + destroy(removeView) + { + this.runners.destroy.run(); + + // call base destroy + super.destroy(removeView); + + // TODO nullify all the managers.. + this.gl = null; + } + + /** + * 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 + * builds and excluding plugins. + * @name PIXI.Renderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. + */ + + /** + * Adds a plugin to the renderer. + * + * @method + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + static registerPlugin(pluginName, ctor) + { + Renderer.__plugins = Renderer.__plugins || {}; + Renderer.__plugins[pluginName] = ctor; + } +} diff --git a/packages/core/src/System.js b/packages/core/src/System.js new file mode 100644 index 0000000..05a10c8 --- /dev/null +++ b/packages/core/src/System.js @@ -0,0 +1,42 @@ +/** + * System is a base class used for extending systems used by the {@link PIXI.Renderer} + * @see PIXI.Renderer#addSystem + * @class + * @memberof PIXI + */ +export default class System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + /** + * The renderer this manager works for. + * + * @member {PIXI.Renderer} + */ + this.renderer = renderer; + + this.renderer.runners.contextChange.add(this); + } + + /** + * Generic method called when there is a WebGL context change. + * + */ + contextChange() + { + // do some codes init! + } + + /** + * Generic destroy methods to be overridden by the subclass + * + */ + destroy() + { + this.renderer.runners.contextChange.remove(this); + this.renderer = null; + } +} diff --git a/packages/core/src/batch/BatchSystem.js b/packages/core/src/batch/BatchSystem.js new file mode 100644 index 0000000..b04c249 --- /dev/null +++ b/packages/core/src/batch/BatchSystem.js @@ -0,0 +1,68 @@ +import System from '../System'; +import ObjectRenderer from './ObjectRenderer'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class BatchSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + super(renderer); + + /** + * An empty renderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.emptyRenderer = new ObjectRenderer(renderer); + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.currentRenderer = this.emptyRenderer; + } + + /** + * Changes the current renderer to the one given in parameter + * + * @param {PIXI.ObjectRenderer} objectRenderer - The object renderer to use. + */ + setObjectRenderer(objectRenderer) + { + if (this.currentRenderer === objectRenderer) + { + return; + } + + this.currentRenderer.stop(); + this.currentRenderer = objectRenderer; + + this.renderer.state.setState(objectRenderer.state); + + this.currentRenderer.start(); + } + + /** + * This should be called if you wish to do some custom rendering + * It will basically render anything that may be batched up such as sprites + * + */ + flush() + { + this.setObjectRenderer(this.emptyRenderer); + } + + reset() + { + this.setObjectRenderer(this.emptyRenderer); + } +} diff --git a/packages/core/src/batch/ObjectRenderer.js b/packages/core/src/batch/ObjectRenderer.js new file mode 100644 index 0000000..0c851a9 --- /dev/null +++ b/packages/core/src/batch/ObjectRenderer.js @@ -0,0 +1,48 @@ +import System from '../System'; + +/** + * Base for a common object renderer that can be used as a system renderer plugin. + * + * @class + * @extends PIXI.System + * @memberof PIXI + */ +export default class ObjectRenderer extends System +{ + /** + * Starts the renderer and sets the shader + * + */ + start() + { + // set the shader.. + } + + /** + * Stops the renderer + * + */ + stop() + { + this.flush(); + } + + /** + * Stub method for rendering content and emptying the current batch. + * + */ + flush() + { + // flush! + } + + /** + * Renders an object + * + * @param {PIXI.DisplayObject} object - The object to render. + */ + render(object) // eslint-disable-line no-unused-vars + { + // render the object + } +} diff --git a/packages/core/src/context/ContextSystem.js b/packages/core/src/context/ContextSystem.js new file mode 100644 index 0000000..301aab4 --- /dev/null +++ b/packages/core/src/context/ContextSystem.js @@ -0,0 +1,186 @@ +import System from '../System'; +import { settings } from '@pixi/settings'; + +let CONTEXT_UID = 0; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class ContextSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.webGLVersion = 1; + + this.handleContextLost = this.handleContextLost.bind(this); + this.handleContextRestored = this.handleContextRestored.bind(this); + + this.extensions = {}; + + renderer.view.addEventListener('webglcontextlost', this.handleContextLost, false); + renderer.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + } + + get isLost() + { + return (!this.gl || this.gl.isContextLost()); + } + + contextChange(gl) + { + this.gl = gl; + + // restore a context if it was previously lost + if (gl.isContextLost() && gl.getExtension('WEBGL_lose_context')) + { + gl.getExtension('WEBGL_lose_context').restoreContext(); + } + } + + initFromContext(gl) + { + this.gl = gl; + this.validateContext(gl); + this.renderer.gl = gl; + this.renderer.CONTEXT_UID = CONTEXT_UID++; + this.renderer.runners.contextChange.run(gl); + } + + initFromOptions(options) + { + const gl = this.createContext(this.renderer.view, options); + + this.initFromContext(gl); + } + + /** + * Helper class to create a webGL Context + * + * @class + * @memberof PIXI.glCore + * @param canvas {HTMLCanvasElement} the canvas element that we will get the context from + * @param options {Object} An options object that gets passed in to the canvas element containing the context attributes, + * see https://developer.mozilla.org/en/docs/Web/API/HTMLCanvasElement/getContext for the options available + * @return {WebGLRenderingContext} the WebGL context + */ + createContext(canvas, options) + { + let gl; + + if (settings.PREFER_WEBGL_2) + { + gl = canvas.getContext('webgl2', options); + } + + if (gl) + { + this.webGLVersion = 2; + } + else + { + this.webGLVersion = 1; + + gl = canvas.getContext('webgl', options) + || canvas.getContext('experimental-webgl', options); + + if (!gl) + { + // fail, not able to get a context + throw new Error('This browser does not support webGL. Try using the canvas renderer'); + } + } + + this.gl = gl; + + this.getExtensions(); + + return gl; + } + + getExtensions() + { + // time to set up default etensions that pixi uses.. + const gl = this.gl; + const extensions = this.extensions; + + if (this.webGLVersion === 1) + { + extensions.drawBuffers = gl.getExtension('WEBGL_draw_buffers'); + extensions.depthTexture = gl.getExtension('WEBKIT_WEBGL_depth_texture'); + extensions.floatTexture = gl.getExtension('OES_texture_float'); + extensions.loseContext = gl.getExtension('WEBGL_lose_context'); + + extensions.vertexArrayObject = gl.getExtension('OES_vertex_array_object') + || gl.getExtension('MOZ_OES_vertex_array_object') + || gl.getExtension('WEBKIT_OES_vertex_array_object'); + } + + // we don't use any specific WebGL 2 ones yet! + } + + /** + * Handles a lost webgl context + * + * @private + * @param {WebGLContextEvent} event - The context lost event. + */ + handleContextLost(event) + { + event.preventDefault(); + } + + /** + * Handles a restored webgl context + * + * @private + */ + handleContextRestored() + { + this.renderer.runners.contextChange.run(this.gl); + } + + destroy() + { + const view = this.renderer.view; + + // remove listeners + view.removeEventListener('webglcontextlost', this.handleContextLost); + view.removeEventListener('webglcontextrestored', this.handleContextRestored); + + this.gl.useProgram(null); + + if (this.extensions.loseContext) + { + this.extensions.loseContext.loseContext(); + } + } + + postrender() + { + this.gl.flush(); + } + + validateContext(gl) + { + const attributes = gl.getContextAttributes(); + + // this is going to be fairly simple for now.. but at least we have room to grow! + if (!attributes.stencil) + { + /* eslint-disable max-len */ + + /* eslint-disable no-console */ + console.warn('Provided WebGL context does not have a stencil buffer, masks may not render correctly'); + /* eslint-enable no-console */ + + /* eslint-enable max-len */ + } + } +} diff --git a/packages/core/src/filters/Filter.js b/packages/core/src/filters/Filter.js new file mode 100644 index 0000000..9d8cad8 --- /dev/null +++ b/packages/core/src/filters/Filter.js @@ -0,0 +1,140 @@ +import Shader from '../shader/Shader'; +import Program from '../shader/Program'; +import State from '../state/State'; +import { settings } from '@pixi/settings'; +// import extractUniformsFromSrc from './extractUniformsFromSrc'; +import defaultVertex from './defaultFilter.vert'; +import defaultFragment from './defaultFilter.frag'; + +// let math = require('../../math'); +/** + * @class + * @memberof PIXI + * @extends PIXI.Shader + */ +export default class Filter extends Shader +{ + /** + * @param {string} [vertexSrc] - The source of the vertex shader. + * @param {string} [fragmentSrc] - The source of the fragment shader. + * @param {object} [uniforms] - Custom uniforms to use to augment the built-in ones. + */ + constructor(vertexSrc, fragmentSrc, uniforms) + { + const program = Program.from(vertexSrc, fragmentSrc); + + super(program, uniforms); + + /** + * The padding of the filter. Some filters require extra space to breath such as a blur. + * Increasing this will add extra width and height to the bounds of the object that the + * filter is applied to. + * + * @member {number} + */ + this.padding = 0; + + /** + * The resolution of the filter. Setting this to be lower will lower the quality but + * increase the performance of the filter. + * + * @member {number} + */ + this.resolution = settings.RESOLUTION; + + /** + * If enabled is true the filter is applied, if false it will not. + * + * @member {boolean} + */ + this.enabled = true; + + /** + * If enabled, PixiJS will fit the filter area into boundaries for better performance. + * Switch it off if it does not work for specific shader. + * + * @member {boolean} + */ + this.autoFit = true; + + /** + * Legacy filters use position and uvs from attributes + * @member {boolean} + * @readonly + */ + this.legacy = !!this.program.attributeData.aTextureCoord; + + /** + * the webGL state the filter requires to render + * @member {PIXI.State} + */ + this.state = new State(); + } + + /** + * Applies the filter + * + * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + * @param {boolean} clear - Should the output be cleared before rendering to it + * @param {object} [currentState] - It's current state of filter. + * There are some useful properties in the currentState : + * target, filters, sourceFrame, destinationFrame, renderTarget, resolution + */ + apply(filterManager, input, output, clear, currentState, derp) // eslint-disable-line no-unused-vars + { + // do as you please! + + filterManager.applyFilter(this, input, output, clear, currentState, derp); + + // or just do a regular render.. + } + + /** + * Sets the blendmode of the filter + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL + */ + get blendMode() + { + return this.state.blendMode; + } + + set blendMode(value) // eslint-disable-line require-jsdoc + { + this.state.blendMode = value; + } + + /** + * The default vertex shader source + * + * @static + * @constant + */ + static get defaultVertexSrc() + { + return defaultVertex; + } + + /** + * The default fragment shader source + * + * @static + * @constant + */ + static get defaultFragmentSrc() + { + return defaultFragment; + } +} + +/** + * Used for caching shader IDs + * + * @static + * @private + */ +Filter.SOURCE_KEY_MAP = {}; + diff --git a/packages/core/src/filters/FilterSystem.js b/packages/core/src/filters/FilterSystem.js new file mode 100644 index 0000000..3174318 --- /dev/null +++ b/packages/core/src/filters/FilterSystem.js @@ -0,0 +1,431 @@ +import System from '../System'; + +import RenderTexture from '../renderTexture/RenderTexture'; +import Quad from '../utils/Quad'; +import QuadUv from '../utils/QuadUv'; +import { Rectangle } from '@pixi/math'; +import * as filterTransforms from './filterTransforms'; +import bitTwiddle from 'bit-twiddle'; +import UniformGroup from '../shader/UniformGroup'; +import { DRAW_MODES } from '@pixi/constants'; + +// +/** + * @ignore + * @class + */ +class FilterState +{ + /** + * + */ + constructor() + { + this.renderTexture = null; + this.sourceFrame = new Rectangle(); + this.destinationFrame = new Rectangle(); + this.filters = []; + this.target = null; + this.legacy = false; + this.resolution = 1; + } +} + +/** + * @class + * @memberof PIXI.systems + * @extends PIXI.System + */ +export default class FilterSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + /** + * stores a bunch of PO2 textures used for filtering + * @type {Object} + */ + this.texturePool = {}; + + /** + * a pool for storing filter states, save us creating new ones each tick + * @type {Array} + */ + this.statePool = []; + + /** + * A very simple geometry used when drawing a filter effect to the screen + * @type {Quad} + */ + this.quad = new Quad(); + + this.quadUv = new QuadUv(); + + /** + * Temporary rect for maths + * @type {PIXI.Rectangle} + */ + this.tempRect = new Rectangle(); + + this.activeState = {}; + + /** + * this uniform group is attached to filter uniforms when used + * @type {UniformGroup} + */ + this.globalUniforms = new UniformGroup({ + sourceFrame: this.tempRect, + destinationFrame: this.tempRect, + + // legacy variables + filterArea: new Float32Array(4), + filterClamp: new Float32Array(4), + }, true); + } + + /** + * Adds a new filter to the System. + * + * @param {PIXI.DisplayObject} target - The target of the filter to render. + * @param {PIXI.Filter[]} filters - The filters to apply. + */ + push(target, filters) + { + const renderer = this.renderer; + const filterStack = this.renderer.renderTexture.defaultFilterStack; + const state = this.statePool.pop() || new FilterState(); + + let resolution = filters[0].resolution; + let padding = filters[0].padding; + let autoFit = filters[0].autoFit; + let legacy = filters[0].legacy; + + for (let i = 1; i < filters.length; i++) + { + const filter = filters[i]; + + // lets use the lowest resolution.. + resolution = Math.min(resolution, filter.resolution); + // and the largest amount of padding! + padding = Math.max(padding, filter.padding); + // only auto fit if all filters are autofit + autoFit = autoFit || filter.autoFit; + + legacy = legacy || filter.legacy; + } + + filterStack.push(state); + + state.resolution = resolution; + + state.legacy = legacy; + + // round to whole number based on resolution + // TODO move that to the shader too? + state.sourceFrame = target.filterArea || target.getBounds(true); + + state.sourceFrame.pad(padding); + + if (autoFit) + { + state.sourceFrame.fit(this.renderer.renderTexture.destinationFrame); + } + + state.sourceFrame.round(resolution); + + state.renderTexture = this.getPotFilterTexture(state.sourceFrame.width, state.sourceFrame.height, resolution); + state.filters = filters; + + state.destinationFrame.width = state.renderTexture.width; + state.destinationFrame.height = state.renderTexture.height; + + state.renderTexture.filterFrame = state.sourceFrame; + renderer.renderTexture.bind(state.renderTexture, state.sourceFrame);// /, state.destinationFrame); + renderer.renderTexture.clear(); + } + + /** + * Pops off the filter and applies it. + * + */ + pop() + { + const renderer = this.renderer; + const filterStack = renderer.renderTexture.defaultFilterStack; + const state = filterStack.pop(); + const filters = state.filters; + + this.activeState = state; + + const globalUniforms = this.globalUniforms.uniforms; + + globalUniforms.sourceFrame = state.sourceFrame; + globalUniforms.destinationFrame = state.destinationFrame; + globalUniforms.resolution = state.resolution; + + // only update the rect if its legacy.. + if (state.legacy) + { + const filterArea = globalUniforms.filterArea; + const filterClamp = globalUniforms.filterClamp; + + filterArea[0] = state.destinationFrame.width; + filterArea[1] = state.destinationFrame.height; + filterArea[2] = state.sourceFrame.x; + filterArea[3] = state.sourceFrame.y; + + filterClamp[0] = 0.5 / state.resolution / state.destinationFrame.width; + filterClamp[1] = 0.5 / state.resolution / state.destinationFrame.height; + filterClamp[2] = (state.sourceFrame.width - 0.5) / state.resolution / state.destinationFrame.width; + filterClamp[3] = (state.sourceFrame.height - 0.5) / state.resolution / state.destinationFrame.height; + } + + this.globalUniforms.update(); + + const lastState = filterStack[filterStack.length - 1]; + + if (filters.length === 1) + { + filters[0].apply(this, state.renderTexture, lastState.renderTexture, false, state); + + this.returnFilterTexture(state.renderTexture); + } + else + { + let flip = state.renderTexture; + let flop = this.getPotFilterTexture( + flip.width, + flip.height, + state.resolution + ); + + flop.filterFrame = flip.filterFrame; + + let i = 0; + + for (i = 0; i < filters.length - 1; ++i) + { + filters[i].apply(this, flip, flop, true, state); + + const t = flip; + + flip = flop; + flop = t; + } + + filters[i].apply(this, flip, lastState.renderTexture, false, state); + + this.returnFilterTexture(flip); + this.returnFilterTexture(flop); + } + + this.statePool.push(state); + } + + /** + * Draws a filter. + * + * @param {PIXI.Filter} filter - The filter to draw. + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + * @param {boolean} clear - Should the output be cleared before rendering to it + */ + applyFilter(filter, input, output, clear) + { + const renderer = this.renderer; + + renderer.renderTexture.bind(output, output ? output.filterFrame : null); + + if (clear) + { + // gl.disable(gl.SCISSOR_TEST); + renderer.renderTexture.clear(); + // gl.enable(gl.SCISSOR_TEST); + } + + // set the uniforms.. + filter.uniforms.uSampler = input; + filter.uniforms.filterGlobals = this.globalUniforms; + + // TODO make it so that the order of this does not matter.. + // because it does at the moment cos of global uniforms. + // they need to get resynced + + renderer.state.setState(filter.state); + renderer.shader.bind(filter); + + if (filter.legacy) + { + this.quadUv.map(input._frame, input.filterFrame); + + renderer.geometry.bind(this.quadUv); + renderer.geometry.draw(DRAW_MODES.TRIANGLES); + } + else + { + renderer.geometry.bind(this.quad); + renderer.geometry.draw(DRAW_MODES.TRIANGLE_STRIP); + } + } + + /** + * Calculates the mapped matrix. + * + * TODO playing around here.. this is temporary - (will end up in the shader) + * this returns a matrix that will normalise map filter cords in the filter to screen space + * + * @param {PIXI.Matrix} outputMatrix - the matrix to output to. + * @return {PIXI.Matrix} The mapped matrix. + */ + calculateScreenSpaceMatrix(outputMatrix) + { + const currentState = this.activeState; + + return filterTransforms.calculateScreenSpaceMatrix( + outputMatrix, + currentState.sourceFrame, + currentState.destinationFrame + ); + } + + /** + * This will map the filter coord so that a texture can be used based on the transform of a sprite + * + * @param {PIXI.Matrix} outputMatrix - The matrix to output to. + * @param {PIXI.Sprite} sprite - The sprite to map to. + * @return {PIXI.Matrix} The mapped matrix. + */ + calculateSpriteMatrix(outputMatrix, sprite) + { + const currentState = this.activeState; + + return filterTransforms.calculateSpriteMatrix( + outputMatrix, + currentState.sourceFrame, + currentState.destinationFrame, + sprite + ); + } + + /** + * Destroys this Filter System. + * + * @param {boolean} [contextLost=false] context was lost, do not free shaders + * + */ + destroy(contextLost = false) + { + if (!contextLost) + { + this.emptyPool(); + } + else + { + this.texturePool = {}; + } + } + + /** + * Gets a Power-of-Two render texture. + * + * TODO move to a seperate class could be on renderer? + * also - could cause issue with multiple contexts? + * + * @private + * @param {WebGLRenderingContext} gl - The webgl rendering context + * @param {number} minWidth - The minimum width of the render target. + * @param {number} minHeight - The minimum height of the render target. + * @param {number} resolution - The resolution of the render target. + * @return {PIXI.RenderTarget} The new render target. + */ + getPotFilterTexture(minWidth, minHeight, resolution) + { + minWidth = bitTwiddle.nextPow2(minWidth); + minHeight = bitTwiddle.nextPow2(minHeight); + resolution = resolution || 1; + + const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + + if (!this.texturePool[key]) + { + this.texturePool[key] = []; + } + + let renderTexture = this.texturePool[key].pop(); + + if (!renderTexture) + { + // temporary bypass cache.. + // internally - this will cause a texture to be bound.. + renderTexture = RenderTexture.create({ + width: minWidth, + height: minHeight, + resolution, + }); + } + + return renderTexture; + } + + /** + * Gets extra render texture to use inside current filter + * + * @param {number} resolution resolution of the renderTexture + * @returns {PIXI.RenderTexture} + */ + getFilterTexture(resolution) + { + const rt = this.activeState.renderTexture; + + const filterTexture = this.getPotFilterTexture(rt.width, rt.height, resolution || rt.baseTexture.resolution); + + filterTexture.filterFrame = rt.filterFrame; + + return filterTexture; + } + + /** + * Frees a render texture back into the pool. + * + * @param {PIXI.RenderTarget} renderTexture - The renderTarget to free + */ + returnFilterTexture(renderTexture) + { + renderTexture.filterFrame = null; + + const base = renderTexture.baseTexture; + + const minWidth = base.width; + const minHeight = base.height; + + const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + + this.texturePool[key].push(renderTexture); + } + + /** + * Empties the texture pool. + * + */ + emptyPool() + { + for (const i in this.texturePool) + { + const textures = this.texturePool[i]; + + if (textures) + { + for (let j = 0; j < textures.length; j++) + { + textures[j].destroy(true); + } + } + } + + this.texturePool = {}; + } +} diff --git a/packages/core/src/filters/defaultFilter.frag b/packages/core/src/filters/defaultFilter.frag new file mode 100644 index 0000000..a14c1be --- /dev/null +++ b/packages/core/src/filters/defaultFilter.frag @@ -0,0 +1,22 @@ +varying vec2 vTextureCoord; +varying vec2 vFilterCoord; + +uniform sampler2D uSampler; +uniform sampler2D filterSampler; + +void main(void){ + vec4 masky = texture2D(filterSampler, vFilterCoord); + vec4 sample = texture2D(uSampler, vTextureCoord); + vec4 color; + if(mod(vFilterCoord.x, 1.0) > 0.5) + { + color = vec4(1.0, 0.0, 0.0, 1.0); + } + else + { + color = vec4(0.0, 1.0, 0.0, 1.0); + } + // gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0); + gl_FragColor = mix(sample, masky, 0.5); + gl_FragColor *= sample.a; +} diff --git a/packages/core/src/filters/defaultFilter.vert b/packages/core/src/filters/defaultFilter.vert new file mode 100644 index 0000000..e1febca --- /dev/null +++ b/packages/core/src/filters/defaultFilter.vert @@ -0,0 +1,14 @@ +attribute vec2 aVertexPosition; +attribute vec2 aTextureCoord; + +uniform mat3 projectionMatrix; +uniform mat3 filterMatrix; + +varying vec2 vTextureCoord; +varying vec2 vFilterCoord; + +void main(void){ + gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); + vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy; + vTextureCoord = aTextureCoord ; +} diff --git a/packages/core/src/filters/filterTransforms.js b/packages/core/src/filters/filterTransforms.js new file mode 100644 index 0000000..87a3cd6 --- /dev/null +++ b/packages/core/src/filters/filterTransforms.js @@ -0,0 +1,50 @@ +import { Matrix } from '@pixi/math'; + +/** + * Calculates the mapped matrix + * @param filterArea {Rectangle} The filter area + * @param sprite {Sprite} the target sprite + * @param outputMatrix {Matrix} @alvin + * @private + */ +// this returns a matrix that will normalise map filter cords in the filter to screen space +export function calculateScreenSpaceMatrix(outputMatrix, filterArea, textureSize) +{ + // TODO unwrap? + const mappedMatrix = outputMatrix.identity(); + + mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); + + mappedMatrix.scale(textureSize.width, textureSize.height); + + return mappedMatrix; +} + +export function calculateNormalizedScreenSpaceMatrix(outputMatrix, filterArea, textureSize) +{ + const mappedMatrix = outputMatrix.identity(); + + mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); + + const translateScaleX = (textureSize.width / filterArea.width); + const translateScaleY = (textureSize.height / filterArea.height); + + mappedMatrix.scale(translateScaleX, translateScaleY); + + return mappedMatrix; +} + +// this will map the filter coord so that a texture can be used based on the transform of a sprite +export function calculateSpriteMatrix(outputMatrix, filterArea, textureSize, sprite) +{ + const orig = sprite._texture.orig; + const mappedMatrix = outputMatrix.set(textureSize.width, 0, 0, textureSize.height, filterArea.x, filterArea.y); + const worldTransform = sprite.worldTransform.copyTo(Matrix.TEMP_MATRIX); + + worldTransform.invert(); + mappedMatrix.prepend(worldTransform); + mappedMatrix.scale(1.0 / orig.width, 1.0 / orig.height); + mappedMatrix.translate(sprite.anchor.x, sprite.anchor.y); + + return mappedMatrix; +} diff --git a/packages/core/src/filters/spriteMask/SpriteMaskFilter.js b/packages/core/src/filters/spriteMask/SpriteMaskFilter.js new file mode 100644 index 0000000..91697b7 --- /dev/null +++ b/packages/core/src/filters/spriteMask/SpriteMaskFilter.js @@ -0,0 +1,63 @@ +import Filter from '../Filter'; +import { Matrix } from '@pixi/math'; +import vertex from './spriteMaskFilter.vert'; +import fragment from './spriteMaskFilter.frag'; +import { default as TextureMatrix } from '../../textures/TextureMatrix'; + +/** + * The SpriteMaskFilter class + * + * @class + * @extends PIXI.Filter + * @memberof PIXI + */ +export default class SpriteMaskFilter extends Filter +{ + /** + * @param {PIXI.Sprite} sprite - the target sprite + */ + constructor(sprite) + { + const maskMatrix = new Matrix(); + + super(vertex, fragment); + + sprite.renderable = false; + + this.maskSprite = sprite; + this.maskMatrix = maskMatrix; + } + + /** + * Applies the filter + * + * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + */ + apply(filterManager, input, output) + { + const maskSprite = this.maskSprite; + const tex = this.maskSprite.texture; + + if (!tex.valid) + { + return; + } + if (!tex.transform) + { + // margin = 0.0, let it bleed a bit, shader code becomes easier + // assuming that atlas textures were made with 1-pixel padding + tex.transform = new TextureMatrix(tex, 0.0); + } + tex.transform.update(); + + this.uniforms.mask = tex; + this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite) + .prepend(tex.transform.mapCoord); + this.uniforms.alpha = maskSprite.worldAlpha; + this.uniforms.maskClamp = tex.transform.uClampFrame; + + filterManager.applyFilter(this, input, output); + } +} diff --git a/packages/core/src/filters/spriteMask/spriteMaskFilter.frag b/packages/core/src/filters/spriteMask/spriteMaskFilter.frag new file mode 100644 index 0000000..0e4aef8 --- /dev/null +++ b/packages/core/src/filters/spriteMask/spriteMaskFilter.frag @@ -0,0 +1,23 @@ +varying vec2 vMaskCoord; +varying vec2 vTextureCoord; + +uniform sampler2D uSampler; +uniform sampler2D mask; +uniform float alpha; +uniform vec4 maskClamp; + +void main(void) +{ + float clip = step(3.5, + step(maskClamp.x, vMaskCoord.x) + + step(maskClamp.y, vMaskCoord.y) + + step(vMaskCoord.x, maskClamp.z) + + step(vMaskCoord.y, maskClamp.w)); + + vec4 original = texture2D(uSampler, vTextureCoord); + vec4 masky = texture2D(mask, vMaskCoord); + + original *= (masky.r * masky.a * alpha * clip); + + gl_FragColor = original; +} diff --git a/packages/core/src/filters/spriteMask/spriteMaskFilter.vert b/packages/core/src/filters/spriteMask/spriteMaskFilter.vert new file mode 100644 index 0000000..d23a30c --- /dev/null +++ b/packages/core/src/filters/spriteMask/spriteMaskFilter.vert @@ -0,0 +1,16 @@ +attribute vec2 aVertexPosition; +attribute vec2 aTextureCoord; + +uniform mat3 projectionMatrix; +uniform mat3 otherMatrix; + +varying vec2 vMaskCoord; +varying vec2 vTextureCoord; + +void main(void) +{ + gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); + + vTextureCoord = aTextureCoord; + vMaskCoord = ( otherMatrix * vec3( aTextureCoord, 1.0) ).xy; +} diff --git a/packages/core/src/framebuffer/FrameBuffer.js b/packages/core/src/framebuffer/FrameBuffer.js new file mode 100644 index 0000000..84e9fcc --- /dev/null +++ b/packages/core/src/framebuffer/FrameBuffer.js @@ -0,0 +1,106 @@ +import Texture from '../textures/BaseTexture'; +import { FORMATS, TYPES } from '@pixi/constants'; + +/** + * Frame buffer + * @class + * @memberof PIXI + */ +export default class FrameBuffer +{ + constructor(width, height) + { + this.width = width || 100; + this.height = height || 100; + + this.stencil = false; + this.depth = false; + + this.dirtyId = 0; + this.dirtyFormat = 0; + this.dirtySize = 0; + + this.depthTexture = null; + this.colorTextures = []; + + this.glFrameBuffers = {}; + } + + get colorTexture() + { + return this.colorTextures[0]; + } + + addColorTexture(index, texture) + { + // TODO add some validation to the texture - same width / height etc? + this.colorTextures[index || 0] = texture || new Texture(null, { scaleMode: 0, + resolution: 1, + mipmap: false, + width: this.width, + height: this.height });// || new Texture(); + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + addDepthTexture(texture) + { + /* eslint-disable max-len */ + this.depthTexture = texture || new Texture(null, { scaleMode: 0, + resolution: 1, + width: this.width, + height: this.height, + mipmap: false, + format: FORMATS.DEPTH_COMPONENT, + type: TYPES.UNSIGNED_SHORT });// UNSIGNED_SHORT; + /* eslint-disable max-len */ + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + enableDepth() + { + this.depth = true; + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + enableStencil() + { + this.stencil = true; + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + resize(width, height) + { + if (width === this.width && height === this.height) return; + + this.width = width; + this.height = height; + + this.dirtyId++; + this.dirtySize++; + + for (let i = 0; i < this.colorTextures.length; i++) + { + this.colorTextures[i].setSize(width, height); + } + + if (this.depthTexture) + { + this.depthTexture.setSize(width, height); + } + } +} diff --git a/packages/core/src/framebuffer/FramebufferSystem.js b/packages/core/src/framebuffer/FramebufferSystem.js new file mode 100644 index 0000000..2cc2437 --- /dev/null +++ b/packages/core/src/framebuffer/FramebufferSystem.js @@ -0,0 +1,246 @@ +import System from '../System'; +import { Rectangle } from '@pixi/math'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class FramebufferSystem extends System +{ + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + contextChange() + { + this.gl = this.renderer.gl; + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + this.current = null; + this.viewport = new Rectangle(); + + this.drawBufferExtension = this.renderer.context.extensions.drawBuffers; + } + + bind(framebuffer, frame) + { + const gl = this.gl; + + this.current = framebuffer; + + if (framebuffer) + { + // TODO cacheing layer! + + const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID] || this.initFramebuffer(framebuffer); + + gl.bindFramebuffer(gl.FRAMEBUFFER, fbo.framebuffer); + // makesure all textures are unbound.. + + // now check for updates... + if (fbo.dirtyId !== framebuffer.dirtyId) + { + fbo.dirtyId = framebuffer.dirtyId; + + if (fbo.dirtyFormat !== framebuffer.dirtyFormat) + { + fbo.dirtyFormat = framebuffer.dirtyFormat; + this.updateFramebuffer(framebuffer); + } + else if (fbo.dirtySize !== framebuffer.dirtySize) + { + fbo.dirtySize = framebuffer.dirtySize; + this.resizeFramebuffer(framebuffer); + } + } + + for (let i = 0; i < framebuffer.colorTextures.length; i++) + { + if (framebuffer.colorTextures[i].texturePart) + { + this.renderer.texture.unbind(framebuffer.colorTextures[i].texture); + } + else + { + this.renderer.texture.unbind(framebuffer.colorTextures[i]); + } + } + + if (framebuffer.depthTexture) + { + this.renderer.texture.unbind(framebuffer.depthTexture); + } + + if (frame) + { + this.setViewport(frame.x, frame.y, frame.width, frame.height); + } + else + { + this.setViewport(0, 0, framebuffer.width, framebuffer.height); + } + } + else + { + gl.bindFramebuffer(gl.FRAMEBUFFER, null); + + if (frame) + { + this.setViewport(frame.x, frame.y, frame.width, frame.height); + } + else + { + this.setViewport(0, 0, this.renderer.width, this.renderer.height); + } + } + } + + setViewport(x, y, width, height) + { + const v = this.viewport; + + if (v.width !== width || v.height !== height || v.x !== x || v.y !== y) + { + v.x = x; + v.y = y; + v.width = width; + v.height = height; + + this.gl.viewport(x, y, width, height); + } + } + + get size() + { + if (this.current) + { + // TODO store temp + return { x: 0, y: 0, width: this.current.width, height: this.current.height }; + } + + return { x: 0, y: 0, width: this.renderer.width, height: this.renderer.height }; + } + + clear(r, g, b, a) + { + const gl = this.gl; + + // TODO clear color can be set only one right? + gl.clearColor(r, g, b, a); + gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); + } + + // private functions... + + initFramebuffer(framebuffer) + { + const gl = this.gl; + + // TODO - make this a class? + const fbo = { + framebuffer: gl.createFramebuffer(), + stencil: null, + dirtyId: 0, + dirtyFormat: 0, + dirtySize: 0, + }; + + framebuffer.glFrameBuffers[this.CONTEXT_UID] = fbo; + + return fbo; + } + + resizeFramebuffer(framebuffer) + { + const gl = this.gl; + + if (framebuffer.stencil || framebuffer.depth) + { + gl.bindRenderbuffer(gl.RENDERBUFFER, this.stencil); + gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); + } + } + + updateFramebuffer(framebuffer) + { + const gl = this.gl; + + const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID]; + + // bind the color texture + const colorTextures = framebuffer.colorTextures; + + let count = colorTextures.length; + + if (!this.drawBufferExtension) + { + count = Math.min(count, 1); + } + + const activeTextures = []; + + for (let i = 0; i < count; i++) + { + const texture = framebuffer.colorTextures[i]; + + if (texture.texturePart) + { + this.renderer.texture.bind(texture.texture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.COLOR_ATTACHMENT0 + i, + gl.TEXTURE_CUBE_MAP_NEGATIVE_X + texture.side, + texture.texture._glTextures[this.CONTEXT_UID].texture, + 0); + } + else + { + this.renderer.texture.bind(texture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.COLOR_ATTACHMENT0 + i, + gl.TEXTURE_2D, + texture._glTextures[this.CONTEXT_UID].texture, + 0); + } + + activeTextures.push(gl.COLOR_ATTACHMENT0 + i); + } + + if (this.drawBufferExtension && activeTextures.length > 1) + { + this.drawBufferExtension.drawBuffersWEBGL(activeTextures); + } + + if (framebuffer.depthTexture) + { + const depthTextureExt = this.renderer.context.extensions.depthTexture; + + if (depthTextureExt) + { + const depthTexture = framebuffer.depthTexture; + + this.renderer.texture.bind(depthTexture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.DEPTH_ATTACHMENT, + gl.TEXTURE_2D, + depthTexture._glTextures[this.CONTEXT_UID].texture, + 0); + } + } + + if (framebuffer.stencil || framebuffer.depth) + { + fbo.stencil = gl.createRenderbuffer(); + + gl.bindRenderbuffer(gl.RENDERBUFFER, fbo.stencil); + + // TODO.. this is depth AND stencil? + gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, fbo.stencil); + gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); + // fbo.enableStencil(); + } + } +} diff --git a/packages/core/src/geometry/GLBuffer.js b/packages/core/src/geometry/GLBuffer.js new file mode 100644 index 0000000..0477e8a --- /dev/null +++ b/packages/core/src/geometry/GLBuffer.js @@ -0,0 +1,9 @@ +export default class GLBuffer +{ + constructor(buffer) + { + this.buffer = buffer; + this.updateID = -1; + this.byteLength = -1; + } +} diff --git a/packages/core/src/geometry/GeometrySystem.js b/packages/core/src/geometry/GeometrySystem.js new file mode 100644 index 0000000..959f827 --- /dev/null +++ b/packages/core/src/geometry/GeometrySystem.js @@ -0,0 +1,388 @@ +import System from '../System'; +import GLBuffer from './GLBuffer'; + +const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class GeometrySystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this._activeGeometry = null; + this._activeVao = null; + + this.hasVao = true; + this.hasInstance = true; + } + + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + contextChange() + { + const gl = this.gl = this.renderer.gl; + + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + + // webgl2 + if (!gl.createVertexArray) + { + // webgl 1! + let nativeVaoExtension = this.renderer.context.extensions.vertexArrayObject; + + if (this.renderer.legacy) + { + nativeVaoExtension = null; + } + + if (nativeVaoExtension) + { + gl.createVertexArray = () => + nativeVaoExtension.createVertexArrayOES(); + + gl.bindVertexArray = (vao) => + nativeVaoExtension.bindVertexArrayOES(vao); + + gl.deleteVertexArray = (vao) => + nativeVaoExtension.deleteVertexArrayOES(vao); + } + else + { + this.hasVao = false; + gl.createVertexArray = () => + { + // empty + }; + + gl.bindVertexArray = () => + { + // empty + }; + + gl.deleteVertexArray = () => + { + // empty + }; + } + } + + if (!gl.vertexAttribDivisor) + { + const instanceExt = gl.getExtension('ANGLE_instanced_arrays'); + + if (instanceExt) + { + gl.vertexAttribDivisor = (a, b) => + instanceExt.vertexAttribDivisorANGLE(a, b); + + gl.drawElementsInstanced = (a, b, c, d, e) => + instanceExt.drawElementsInstancedANGLE(a, b, c, d, e); + + gl.drawArraysInstanced = (a, b, c, d) => + instanceExt.drawArraysInstancedANGLE(a, b, c, d); + } + else + { + this.hasInstance = false; + } + } + } + + /** + * Binds geometry so that is can be drawn. Creating a Vao if required + * @private + * @param {PIXI.Geometry} geometry instance of geometry to bind + */ + bind(geometry, shader) + { + shader = shader || this.renderer.shader.shader; + + const gl = this.gl; + + // not sure the best way to address this.. + // currently different shaders require different VAOs for the same geometry + // Still mulling over the best way to solve this one.. + // will likely need to modify the shader attribute locations at run time! + let vaos = geometry.glVertexArrayObjects[this.CONTEXT_UID]; + + if (!vaos) + { + geometry.glVertexArrayObjects[this.CONTEXT_UID] = vaos = {}; + } + + const vao = vaos[shader.program.id] || this.initGeometryVao(geometry, shader.program); + + this._activeGeometry = geometry; + + if (this._activeVao !== vao) + { + this._activeVao = vao; + + if (this.hasVao) + { + gl.bindVertexArray(vao); + } + else + { + this.activateVao(geometry, shader.program); + } + } + + // TODO - optimise later! + // don't need to loop through if nothing changed! + // maybe look to add an 'autoupdate' to geometry? + this.updateBuffers(); + } + + reset() + { + this.unbind(); + } + + updateBuffers() + { + const geometry = this._activeGeometry; + const gl = this.gl; + + for (let i = 0; i < geometry.buffers.length; i++) + { + const buffer = geometry.buffers[i]; + + const glBuffer = buffer._glBuffers[this.CONTEXT_UID]; + + if (buffer._updateID !== glBuffer.updateID) + { + glBuffer.updateID = buffer._updateID; + + // TODO can cache this on buffer! maybe added a getter / setter? + const type = buffer.index ? gl.ELEMENT_ARRAY_BUFFER : gl.ARRAY_BUFFER; + const drawType = buffer.static ? gl.STATIC_DRAW : gl.DYNAMIC_DRAW; + + gl.bindBuffer(type, glBuffer.buffer); + + if (glBuffer.byteLength >= buffer.data.byteLength) + { + // offset is always zero for now! + gl.bufferSubData(type, 0, buffer.data); + } + else + { + gl.bufferData(type, buffer.data, drawType); + } + } + } + } + + checkCompatability(geometry, program) + { + // geometry must have at least all the attributes that the shader requires. + const geometryAttributes = geometry.attributes; + const shaderAttributes = program.attributeData; + + for (const j in shaderAttributes) + { + if (!geometryAttributes[j]) + { + throw new Error(`shader and geometry incompatible, geometry missing the "${j}" attribute`); + } + } + } + + /** + * Creates a Vao with the same structure as the geometry and stores it on the geometry. + * @private + * @param {PIXI.Geometry} geometry instance of geometry to to generate Vao for + */ + initGeometryVao(geometry, program) + { + this.checkCompatability(geometry, program); + + const gl = this.gl; + const CONTEXT_UID = this.CONTEXT_UID; + const buffers = geometry.buffers; + const attributes = geometry.attributes; + + const tempStride = {}; + const tempStart = {}; + + for (const j in buffers) + { + tempStride[j] = 0; + tempStart[j] = 0; + } + + for (const j in attributes) + { + if (!attributes[j].size && program.attributeData[j]) + { + attributes[j].size = program.attributeData[j].size; + } + + tempStride[attributes[j].buffer] += attributes[j].size * byteSizeMap[attributes[j].type]; + } + + for (const j in attributes) + { + const attribute = attributes[j]; + const attribSize = attribute.size; + + if (attribute.stride === undefined) + { + if (tempStride[attribute.buffer] === attribSize * byteSizeMap[attribute.type]) + { + attribute.stride = 0; + } + else + { + attribute.stride = tempStride[attribute.buffer]; + } + } + + if (attribute.start === undefined) + { + attribute.start = tempStart[attribute.buffer]; + + tempStart[attribute.buffer] += attribSize * byteSizeMap[attribute.type]; + } + } + + // first update - and create the buffers! + // only create a gl buffer if it actually gets + for (let i = 0; i < buffers.length; i++) + { + const buffer = buffers[i]; + + if (!buffer._glBuffers[CONTEXT_UID]) + { + buffer._glBuffers[CONTEXT_UID] = new GLBuffer(gl.createBuffer()); + } + } + + // TODO - maybe make this a data object? + // lets wait to see if we need to first! + const vao = gl.createVertexArray(); + + gl.bindVertexArray(vao); + + this.activateVao(geometry, program); + + gl.bindVertexArray(null); + + // add it to the cache! + geometry.glVertexArrayObjects[this.CONTEXT_UID][program.id] = vao; + + return vao; + } + + activateVao(geometry, program) + { + const gl = this.gl; + const CONTEXT_UID = this.CONTEXT_UID; + const buffers = geometry.buffers; + const attributes = geometry.attributes; + + if (geometry.indexBuffer) + { + // first update the index buffer if we have one.. + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, geometry.indexBuffer._glBuffers[CONTEXT_UID].buffer); + } + + let lastBuffer = null; + + // add a new one! + for (const j in attributes) + { + const attribute = attributes[j]; + const buffer = buffers[attribute.buffer]; + const glBuffer = buffer._glBuffers[CONTEXT_UID]; + + if (program.attributeData[j]) + { + if (lastBuffer !== glBuffer) + { + gl.bindBuffer(gl.ARRAY_BUFFER, glBuffer.buffer); + + lastBuffer = glBuffer; + } + + const location = program.attributeData[j].location; + + // TODO introduce state again + // we can optimise this for older devices that have no VAOs + gl.enableVertexAttribArray(location); + + gl.vertexAttribPointer(location, + attribute.size, + attribute.type || gl.FLOAT, + attribute.normalized, + attribute.stride, + attribute.start); + + if (attribute.instance) + { + // TODO calculate instance count based of this... + if (this.hasInstance) + { + gl.vertexAttribDivisor(location, 1); + } + else + { + throw new Error('geometry error, GPU Instancing is not supported on this device'); + } + } + } + } + } + + draw(type, size, start, instanceCount) + { + const gl = this.gl; + const geometry = this._activeGeometry; + + // TODO.. this should not change so maybe cache the function? + + if (geometry.indexBuffer) + { + if (geometry.instanced) + { + /* eslint-disable max-len */ + gl.drawElementsInstanced(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2, instanceCount || 1); + /* eslint-enable max-len */ + } + else + { + gl.drawElements(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2); + } + } + else + if (geometry.instanced) + { + // TODO need a better way to calculate size.. + gl.drawArraysInstanced(type, start, size || geometry.getSize(), instanceCount || 1); + } + else + { + gl.drawArrays(type, start, size || geometry.getSize()); + } + + return this; + } + + unbind() + { + this.gl.bindVertexArray(null); + this._activeVao = null; + this._activeGeometry = null; + } +} diff --git a/packages/core/src/geometry/utils/setVertexAttribArrays.js b/packages/core/src/geometry/utils/setVertexAttribArrays.js new file mode 100644 index 0000000..23b6959 --- /dev/null +++ b/packages/core/src/geometry/utils/setVertexAttribArrays.js @@ -0,0 +1,54 @@ +// var GL_MAP = {}; + +/** + * @param gl {WebGLRenderingContext} The current WebGL context + * @param attribs {*} + * @param state {*} + */ +export default function setVertexAttribArrays(gl, attribs, state) +{ + let i; + + if (state) + { + const tempAttribState = state.tempAttribState; + const attribState = state.attribState; + + for (i = 0; i < tempAttribState.length; i++) + { + tempAttribState[i] = false; + } + + // set the new attribs + for (i = 0; i < attribs.length; i++) + { + tempAttribState[attribs[i].attribute.location] = true; + } + + for (i = 0; i < attribState.length; i++) + { + if (attribState[i] !== tempAttribState[i]) + { + attribState[i] = tempAttribState[i]; + + if (state.attribState[i]) + { + gl.enableVertexAttribArray(i); + } + else + { + gl.disableVertexAttribArray(i); + } + } + } + } + else + { + for (i = 0; i < attribs.length; i++) + { + const attrib = attribs[i]; + + gl.enableVertexAttribArray(attrib.attribute.location); + } + } +} diff --git a/packages/core/src/index.js b/packages/core/src/index.js index 58c456a..fd1f547 100644 --- a/packages/core/src/index.js +++ b/packages/core/src/index.js @@ -1,30 +1,30 @@ import * as resources from './textures/resources'; -import * as systems from './renderers/systems'; +import * as systems from './systems'; -export { resources }; export { systems }; +export { resources }; -export { default as Renderer } from './renderers/Renderer'; -export { default as AbstractRenderer } from './renderers/AbstractRenderer'; -export { default as FrameBuffer } from './textures/FrameBuffer'; +export { default as System } from './System'; +export { default as Renderer } from './Renderer'; +export { default as AbstractRenderer } from './AbstractRenderer'; +export { default as FrameBuffer } from './framebuffer/FrameBuffer'; export { default as CubeTexture } from './textures/CubeTexture'; export { default as BaseTexture } from './textures/BaseTexture'; export { default as Texture } from './textures/Texture'; export { default as TextureMatrix } from './textures/TextureMatrix'; -export { default as RenderTexture } from './textures/RenderTexture'; -export { default as BaseRenderTexture } from './textures/BaseRenderTexture'; +export { default as RenderTexture } from './renderTexture/RenderTexture'; +export { default as BaseRenderTexture } from './renderTexture/BaseRenderTexture'; export { default as TextureUvs } from './textures/TextureUvs'; -export { default as State } from './renderers/State'; -export { default as ObjectRenderer } from './renderers/utils/ObjectRenderer'; -export { default as Quad } from './renderers/utils/Quad'; -export { default as QuadUv } from './renderers/utils/QuadUv'; -export { default as checkMaxIfStatmentsInShader } from './renderers/utils/checkMaxIfStatmentsInShader'; +export { default as State } from './state/State'; +export { default as ObjectRenderer } from './batch/ObjectRenderer'; +export { default as Quad } from './utils/Quad'; +export { default as QuadUv } from './utils/QuadUv'; +export { default as checkMaxIfStatmentsInShader } from './shader/utils/checkMaxIfStatmentsInShader'; export { default as Shader } from './shader/Shader'; export { default as Program } from './shader/Program'; export { default as UniformGroup } from './shader/UniformGroup'; -export { default as SpriteMaskFilter } from './renderers/filters/spriteMask/SpriteMaskFilter'; -export { default as Filter } from './renderers/filters/Filter'; +export { default as SpriteMaskFilter } from './filters/spriteMask/SpriteMaskFilter'; +export { default as Filter } from './filters/Filter'; export { default as Attribute } from './geometry/Attribute'; export { default as Buffer } from './geometry/Buffer'; export { default as Geometry } from './geometry/Geometry'; - diff --git a/packages/core/src/mask/MaskSystem.js b/packages/core/src/mask/MaskSystem.js new file mode 100644 index 0000000..99174c2 --- /dev/null +++ b/packages/core/src/mask/MaskSystem.js @@ -0,0 +1,199 @@ +import System from '../System'; +import AlphaMaskFilter from '../filters/spriteMask/SpriteMaskFilter'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class MaskSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + // TODO - we don't need both! + this.scissor = false; + this.scissorData = null; + this.scissorRenderTarget = null; + + this.enableScissor = false; + + this.alphaMaskPool = []; + this.alphaMaskIndex = 0; + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.DisplayObject} target - Display Object to push the mask to + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + push(target, maskData) + { + // TODO the root check means scissor rect will not + // be used on render textures more info here: + // https://github.com/pixijs/pixi.js/pull/3545 + + if (maskData.texture) + { + this.pushSpriteMask(target, maskData); + } + else if (this.enableScissor + && !this.scissor + && this.renderer._activeRenderTarget.root + && !this.renderer.stencil.stencilMaskStack.length + && maskData.isFastRect()) + { + const matrix = maskData.worldTransform; + + let rot = Math.atan2(matrix.b, matrix.a); + + // use the nearest degree! + rot = Math.round(rot * (180 / Math.PI)); + + if (rot % 90) + { + this.pushStencilMask(maskData); + } + else + { + this.pushScissorMask(target, maskData); + } + } + else + { + this.pushStencilMask(maskData); + } + } + + /** + * Removes the last mask from the mask stack and doesn't return it. + * + * @param {PIXI.DisplayObject} target - Display Object to pop the mask from + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + pop(target, maskData) + { + if (maskData.texture) + { + this.popSpriteMask(target, maskData); + } + else if (this.enableScissor && !this.renderer.stencil.stencilMaskStack.length) + { + this.popScissorMask(target, maskData); + } + else + { + this.popStencilMask(target, maskData); + } + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.RenderTarget} target - Display Object to push the sprite mask to + * @param {PIXI.Sprite} maskData - Sprite to be used as the mask + */ + pushSpriteMask(target, maskData) + { + let alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; + + if (!alphaMaskFilter) + { + alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; + } + + alphaMaskFilter[0].resolution = this.renderer.resolution; + alphaMaskFilter[0].maskSprite = maskData; + + // TODO - may cause issues! + target.filterArea = maskData.getBounds(true); + + this.renderer.filterSystem.pushFilter(target, alphaMaskFilter); + + this.alphaMaskIndex++; + } + + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popSpriteMask() + { + this.renderer.filterSystem.popFilter(); + this.alphaMaskIndex--; + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + pushStencilMask(maskData) + { + this.renderer.batch.flush(); + this.renderer.stencil.pushStencil(maskData); + } + + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popStencilMask() + { + // this.renderer.currentRenderer.stop(); + this.renderer.stencil.popStencil(); + } + + /** + * + * @param {PIXI.DisplayObject} target - Display Object to push the mask to + * @param {PIXI.Graphics} maskData - The masking data. + */ + pushScissorMask(target, maskData) + { + maskData.renderable = true; + + const renderTarget = this.renderer._activeRenderTarget; + + const bounds = maskData.getBounds(); + + bounds.fit(renderTarget.size); + maskData.renderable = false; + + this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); + + const resolution = this.renderer.resolution; + + this.renderer.gl.scissor( + bounds.x * resolution, + (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, + bounds.width * resolution, + bounds.height * resolution + ); + + this.scissorRenderTarget = renderTarget; + this.scissorData = maskData; + this.scissor = true; + } + + /** + * + * + */ + popScissorMask() + { + this.scissorRenderTarget = null; + this.scissorData = null; + this.scissor = false; + + // must be scissor! + const gl = this.renderer.gl; + + gl.disable(gl.SCISSOR_TEST); + } +} diff --git a/packages/core/src/mask/StencilSystem.js b/packages/core/src/mask/StencilSystem.js new file mode 100644 index 0000000..62796a5 --- /dev/null +++ b/packages/core/src/mask/StencilSystem.js @@ -0,0 +1,133 @@ +import System from '../System'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class StencilSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + this.stencilMaskStack = []; + } + + /** + * Changes the mask stack that is used by this System. + * + * @param {PIXI.Graphics[]} stencilMaskStack - The mask stack + */ + setMaskStack(stencilMaskStack) + { + const gl = this.renderer.gl; + + if (stencilMaskStack.length !== this.stencilMaskStack.length) + { + if (stencilMaskStack.length === 0) + { + gl.disable(gl.STENCIL_TEST); + } + else + { + gl.enable(gl.STENCIL_TEST); + } + } + + this.stencilMaskStack = stencilMaskStack; + } + + /** + * Applies the Mask and adds it to the current stencil stack. @alvin + * + * @param {PIXI.Graphics} graphics - The mask + */ + pushStencil(graphics) + { + this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); + + // this.renderer._activeRenderTarget.attachStencilBuffer(); + + const gl = this.renderer.gl; + const prevMaskCount = this.stencilMaskStack.length; + + if (prevMaskCount === 0) + { + gl.enable(gl.STENCIL_TEST); + } + + this.stencilMaskStack.push(graphics); + + // Increment the refference stencil value where the new mask overlaps with the old ones. + gl.colorMask(false, false, false, false); + gl.stencilFunc(gl.EQUAL, prevMaskCount, this._getBitwiseMask()); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.INCR); + this.renderer.plugins.graphics.render(graphics); + + this._useCurrent(); + } + + /** + * Removes the last mask from the stencil stack. @alvin + */ + popStencil() + { + this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); + + const gl = this.renderer.gl; + const graphics = this.stencilMaskStack.pop(); + + if (this.stencilMaskStack.length === 0) + { + // the stack is empty! + gl.disable(gl.STENCIL_TEST); + gl.clear(gl.STENCIL_BUFFER_BIT); + gl.clearStencil(0); + } + else + { + // Decrement the refference stencil value where the popped mask overlaps with the other ones + gl.colorMask(false, false, false, false); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.DECR); + this.renderer.plugins.graphics.render(graphics); + + this._useCurrent(); + } + } + + /** + * Setup renderer to use the current stencil data. + */ + _useCurrent() + { + const gl = this.renderer.gl; + + gl.colorMask(true, true, true, true); + gl.stencilFunc(gl.EQUAL, this.stencilMaskStack.length, this._getBitwiseMask()); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP); + } + + /** + * Fill 1s equal to the number of acitve stencil masks. + * + * @return {number} The bitwise mask. + */ + _getBitwiseMask() + { + return (1 << this.stencilMaskStack.length) - 1; + } + + /** + * Destroys the mask stack. + * + */ + destroy() + { + super.destroy(this); + + this.stencilMaskStack = null; + } +} diff --git a/packages/core/src/projection/ProjectionSystem.js b/packages/core/src/projection/ProjectionSystem.js new file mode 100644 index 0000000..6e63b73 --- /dev/null +++ b/packages/core/src/projection/ProjectionSystem.js @@ -0,0 +1,73 @@ +import System from '../System'; +import { Matrix } from '@pixi/math'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class ProjectionSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.projectionMatrix = new Matrix(); + } + + update(destinationFrame, sourceFrame, resolution, root) + { + this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; + this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; + + this.calculateProjection(this.destinationFrame, this.sourceFrame, resolution, root); + + this.renderer.globalUniforms.uniforms.projectionMatrix = this.projectionMatrix; + this.renderer.globalUniforms.update(); + } + + /** + * Updates the projection matrix based on a projection frame (which is a rectangle) + * + * @param {Rectangle} destinationFrame - The destination frame. + * @param {Rectangle} sourceFrame - The source frame. + */ + calculateProjection(destinationFrame, sourceFrame, resolution, root) + { + const pm = this.projectionMatrix; + + // I don't think we will need this line.. + // pm.identity(); + + if (!root) + { + pm.a = (1 / destinationFrame.width * 2) * resolution; + pm.d = (1 / destinationFrame.height * 2) * resolution; + + pm.tx = -1 - (sourceFrame.x * pm.a); + pm.ty = -1 - (sourceFrame.y * pm.d); + } + else + { + pm.a = (1 / destinationFrame.width * 2) * resolution; + pm.d = (-1 / destinationFrame.height * 2) * resolution; + + pm.tx = -1 - (sourceFrame.x * pm.a); + pm.ty = 1 - (sourceFrame.y * pm.d); + } + } + + /** + * Sets the transform of the active render target to the given matrix + * + * @param {PIXI.Matrix} matrix - The transformation matrix + */ + setTransform()// matrix) + { + // this._activeRenderTarget.transform = matrix; + } +} diff --git a/packages/core/src/renderTexture/BaseRenderTexture.js b/packages/core/src/renderTexture/BaseRenderTexture.js new file mode 100644 index 0000000..88eb83b --- /dev/null +++ b/packages/core/src/renderTexture/BaseRenderTexture.js @@ -0,0 +1,137 @@ +import BaseTexture from '../textures/BaseTexture'; +import FrameBuffer from '../framebuffer/FrameBuffer'; + +/** + * A BaseRenderTexture is a special texture that allows any PixiJS display object to be rendered to it. + * + * __Hint__: All DisplayObjects (i.e. Sprites) that render to a BaseRenderTexture should be preloaded + * otherwise black rectangles will be drawn instead. + * + * A BaseRenderTexture takes a snapshot of any Display Object given to its render method. The position + * and rotation of the given Display Objects is ignored. For example: + * + * ```js + * let renderer = PIXI.autoDetectRenderer(1024, 1024, { view: canvas, ratio: 1 }); + * let baseRenderTexture = new PIXI.BaseRenderTexture(renderer, 800, 600); + * let sprite = PIXI.Sprite.fromImage("spinObj_01.png"); + * + * sprite.position.x = 800/2; + * sprite.position.y = 600/2; + * sprite.anchor.x = 0.5; + * sprite.anchor.y = 0.5; + * + * baseRenderTexture.render(sprite); + * ``` + * + * The Sprite in this case will be rendered using its local transform. To render this sprite at 0,0 + * you can clear the transform + * + * ```js + * + * sprite.setTransform() + * + * let baseRenderTexture = new PIXI.BaseRenderTexture(100, 100); + * let renderTexture = new PIXI.RenderTexture(baseRenderTexture); + * + * renderer.render(sprite, renderTexture); // Renders to center of RenderTexture + * ``` + * + * @class + * @extends PIXI.BaseTexture + * @memberof PIXI + */ +export default class BaseRenderTexture extends BaseTexture +{ + /** + * @param {object} [options] + * @param {number} [options.width=100] - The width of the base render texture + * @param {number} [options.height=100] - The height of the base render texture + * @param {PIXI.SCALE_MODES} [options.scaleMode] - See {@link PIXI.SCALE_MODES} for possible values + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the texture being generated + */ + constructor(options) + { + if (typeof options === 'number') + { + /* eslint-disable prefer-rest-params */ + // Backward compatibility of signature + const width = arguments[0]; + const height = arguments[1]; + const scaleMode = arguments[2]; + const resolution = arguments[3]; + + options = { width, height, scaleMode, resolution }; + /* eslint-enable prefer-rest-params */ + } + + super(null, options); + + const { width, height } = options || {}; + + // Set defaults + this.mipmap = false; + this.width = Math.ceil(width) || 100; + this.height = Math.ceil(height) || 100; + this.valid = true; + + /** + * A map of renderer IDs to webgl renderTargets + * + * @private + * @member {object} + */ + // this._glRenderTargets = {}; + + /** + * A reference to the canvas render target (we only need one as this can be shared across renderers) + * + * @private + * @member {object} + */ + this._canvasRenderTarget = null; + + this.clearColor = [0, 0, 0, 0]; + + this.frameBuffer = new FrameBuffer(width * this.resolution, height * this.resolution) + .addColorTexture(0, this); + + // TODO - could this be added the systems? + + /** + * The data structure for the stencil masks + * + * @member {PIXI.Graphics[]} + */ + this.stencilMaskStack = []; + + /** + * The data structure for the filters + * + * @member {PIXI.Graphics[]} + */ + this.filterStack = [{}]; + } + + /** + * Resizes the BaseRenderTexture. + * + * @param {number} width - The width to resize to. + * @param {number} height - The height to resize to. + */ + resize(width, height) + { + width = Math.ceil(width); + height = Math.ceil(height); + this.frameBuffer.resize(width * this.resolution, height * this.resolution); + } + + /** + * Destroys this texture + * + */ + destroy() + { + super.destroy(true); + this.renderer = null; + } +} diff --git a/packages/core/src/renderTexture/RenderTexture.js b/packages/core/src/renderTexture/RenderTexture.js new file mode 100644 index 0000000..f1ecf1f --- /dev/null +++ b/packages/core/src/renderTexture/RenderTexture.js @@ -0,0 +1,153 @@ +import BaseRenderTexture from './BaseRenderTexture'; +import Texture from '../textures/Texture'; + +/** + * A RenderTexture is a special texture that allows any PixiJS display object to be rendered to it. + * + * __Hint__: All DisplayObjects (i.e. Sprites) that render to a RenderTexture should be preloaded + * otherwise black rectangles will be drawn instead. + * + * A RenderTexture takes a snapshot of any Display Object given to its render method. For example: + * + * ```js + * let renderer = PIXI.autoDetectRenderer(1024, 1024, { view: canvas, ratio: 1 }); + * let renderTexture = PIXI.RenderTexture.create(800, 600); + * let sprite = PIXI.Sprite.fromImage("spinObj_01.png"); + * + * sprite.position.x = 800/2; + * sprite.position.y = 600/2; + * sprite.anchor.x = 0.5; + * sprite.anchor.y = 0.5; + * + * renderer.render(sprite, renderTexture); + * ``` + * + * The Sprite in this case will be rendered using its local transform. To render this sprite at 0,0 + * you can clear the transform + * + * ```js + * + * sprite.setTransform() + * + * let renderTexture = new PIXI.RenderTexture.create(100, 100); + * + * renderer.render(sprite, renderTexture); // Renders to center of RenderTexture + * ``` + * + * @class + * @extends PIXI.Texture + * @memberof PIXI + */ +export default class RenderTexture extends Texture +{ + /** + * @param {PIXI.BaseRenderTexture} baseRenderTexture - The renderer used for this RenderTexture + * @param {PIXI.Rectangle} [frame] - The rectangle frame of the texture to show + */ + constructor(baseRenderTexture, frame) + { + // support for legacy.. + let _legacyRenderer = null; + + if (!(baseRenderTexture instanceof BaseRenderTexture)) + { + /* eslint-disable prefer-rest-params, no-console */ + const width = arguments[1]; + const height = arguments[2]; + const scaleMode = arguments[3]; + const resolution = arguments[4]; + + // we have an old render texture.. + console.warn(`Please use RenderTexture.create(${width}, ${height}) instead of the ctor directly.`); + _legacyRenderer = arguments[0]; + /* eslint-enable prefer-rest-params, no-console */ + + frame = null; + baseRenderTexture = new BaseRenderTexture({ + width, + height, + scaleMode, + resolution, + }); + } + + /** + * The base texture object that this texture uses + * + * @member {BaseTexture} + */ + super(baseRenderTexture, frame); + + this.legacyRenderer = _legacyRenderer; + + /** + * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. + * + * @member {boolean} + */ + this.valid = true; + + /** + * FilterSystem temporary storage + * @private + * @member {PIXI.Rectangle} + */ + this.filterFrame = null; + + this._updateUvs(); + } + + /** + * Resizes the RenderTexture. + * + * @param {number} width - The width to resize to. + * @param {number} height - The height to resize to. + * @param {boolean} [resizeBaseTexture=true] - Should the baseTexture.width and height values be resized as well? + */ + resize(width, height, resizeBaseTexture = true) + { + width = Math.ceil(width); + height = Math.ceil(height); + + // TODO - could be not required.. + this.valid = (width > 0 && height > 0); + + this._frame.width = this.orig.width = width; + this._frame.height = this.orig.height = height; + + if (resizeBaseTexture) + { + this.baseTexture.resize(width, height); + } + + this._updateUvs(); + } + + /** + * A short hand way of creating a render texture. + * + * @param {object} [options] - Options + * @param {number} [options.width=100] - The width of the render texture + * @param {number} [options.height=100] - The height of the render texture + * @param {number} [options.scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the texture being generated + * @return {PIXI.RenderTexture} The new render texture + */ + static create(options) + { + // fallback, old-style: create(width, height, scaleMode, resolution) + if (typeof options === 'number') + { + /* eslint-disable prefer-rest-params */ + options = { + width: options, + height: arguments[1], + scaleMode: arguments[2], + resolution: arguments[3], + }; + /* eslint-enable prefer-rest-params */ + } + + return new RenderTexture(new BaseRenderTexture(options)); + } +} diff --git a/packages/core/src/renderTexture/RenderTextureSystem.js b/packages/core/src/renderTexture/RenderTextureSystem.js new file mode 100644 index 0000000..8d0ca61 --- /dev/null +++ b/packages/core/src/renderTexture/RenderTextureSystem.js @@ -0,0 +1,115 @@ +import System from '../System'; +import { Rectangle } from '@pixi/math'; + +const tempRect = new Rectangle(); + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class RenderTextureSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.clearColor = renderer._backgroundColorRgba; + + // TODO moe this property somewhere else! + this.defaultMaskStack = []; + this.defaultFilterStack = [{}]; + + // empty render texture? + this.renderTexture = null; + + this.destinationFrame = new Rectangle(); + } + + bind(renderTexture, sourceFrame, destinationFrame) + { + // TODO - do we want this?? + if (this.renderTexture === renderTexture) return; + this.renderTexture = renderTexture; + + const renderer = this.renderer; + + if (renderTexture) + { + const baseTexture = renderTexture.baseTexture; + + if (!destinationFrame) + { + tempRect.width = baseTexture.realWidth; + tempRect.height = baseTexture.realHeight; + + destinationFrame = tempRect; + } + + if (!sourceFrame) + { + sourceFrame = destinationFrame; + } + + this.renderer.framebuffer.bind(baseTexture.frameBuffer, destinationFrame); + + this.renderer.projection.update(destinationFrame, sourceFrame, baseTexture.resolution, false); + this.renderer.stencil.setMaskStack(baseTexture.stencilMaskStack); + } + else + { + // TODO these validation checks happen deeper down.. + // thing they can be avoided.. + if (!destinationFrame) + { + tempRect.width = renderer.width; + tempRect.height = renderer.height; + + destinationFrame = tempRect; + } + + if (!sourceFrame) + { + sourceFrame = destinationFrame; + } + + renderer.framebuffer.bind(null, destinationFrame); + + // TODO store this.. + this.renderer.projection.update(destinationFrame, sourceFrame, this.renderer.resolution, true); + this.renderer.stencil.setMaskStack(this.defaultMaskStack); + } + + this.destinationFrame.copyFrom(destinationFrame); + } + + /** + * Erases the render texture and fills the drawing area with a colour + * + * @param {number} [clearColor] - The colour + * @return {PIXI.Renderer} Returns itself. + */ + clear(clearColor) + { + if (this.renderTexture) + { + clearColor = clearColor || this.renderTexture.baseTexture.clearColor; + } + else + { + clearColor = clearColor || this.clearColor; + } + + this.renderer.framebuffer.clear(clearColor[0], clearColor[1], clearColor[2], clearColor[3]); + } + + resize()// screenWidth, screenHeight) + { + // resize the root only! + this.bind(null); + } +} diff --git a/packages/core/src/renderers/AbstractRenderer.js b/packages/core/src/renderers/AbstractRenderer.js deleted file mode 100644 index 928670f..0000000 --- a/packages/core/src/renderers/AbstractRenderer.js +++ /dev/null @@ -1,353 +0,0 @@ -import { hex2string, hex2rgb } from '@pixi/utils'; -import { Matrix, Rectangle } from '@pixi/math'; -import { RENDERER_TYPE } from '@pixi/constants'; -import { settings } from '@pixi/settings'; -import { Container } from '@pixi/display'; -import RenderTexture from '../textures/RenderTexture'; -import EventEmitter from 'eventemitter3'; - -const tempMatrix = new Matrix(); - -/** - * 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 EventEmitter - * @memberof PIXI - */ -export default class AbstractRenderer extends EventEmitter -{ - // eslint-disable-next-line valid-jsdoc - /** - * @param {string} system - The name of the system this renderer is for. - * @param {object} [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.transparent=false] - If the render view is transparent, default false - * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false - * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) - * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. The - * resolution of the renderer retina would be 2. - * @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 {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when rendering, - * stopping pixel interpolation. - */ - constructor(system, options, arg2, arg3) - { - super(); - - // Support for constructor(system, screenWidth, screenHeight, options) - if (typeof options === 'number') - { - options = Object.assign({ - width: options, - height: arg2 || settings.RENDER_OPTIONS.height, - }, arg3); - } - - // 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 = RENDERER_TYPE.UNKNOWN; - - /** - * Measurements of the screen. (0, 0, screenWidth, screenHeight) - * - * Its safe to use as filterArea or hitArea for 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 1 - */ - this.resolution = options.resolution || settings.RESOLUTION; - - /** - * Whether the render view is transparent - * - * @member {boolean} - */ - this.transparent = options.transparent; - - /** - * Whether css dimensions of canvas view should be resized to screen dimensions automatically - * - * @member {boolean} - */ - this.autoResize = options.autoResize || false; - - /** - * Tracks the blend modes useful for this renderer. - * - * @member {object} - */ - this.blendModes = null; - - /** - * 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; - - /** - * If true PixiJS will Math.floor() x/y values when rendering, stopping pixel interpolation. - * Handy for crisp pixel art and speed on legacy devices. - * - * @member {boolean} - */ - this.roundPixels = options.roundPixels; - - /** - * The background color as a number. - * - * @member {number} - * @private - */ - this._backgroundColor = 0x000000; - - /** - * The background color as an [R, G, B] array. - * - * @member {number[]} - * @private - */ - this._backgroundColorRgba = [0, 0, 0, 0]; - - /** - * The background color as a string. - * - * @member {string} - * @private - */ - this._backgroundColorString = '#000000'; - - this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter - - /** - * This temporary display object used as the parent of the currently being rendered item - * - * @member {PIXI.DisplayObject} - * @private - */ - this._tempDisplayObjectParent = new Container(); - - /** - * The last root object that the renderer tried to render. - * - * @member {PIXI.DisplayObject} - * @private - */ - this._lastObjectRendered = this._tempDisplayObjectParent; - - /** - * Collection of plugins. - * @readonly - * @member {object} - */ - this.plugins = {}; - } - - /** - * Initialize the plugins. - * - * @protected - * @param {object} staticMap - The dictionary of staticly saved plugins. - */ - initPlugins(staticMap) - { - 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() - { - return this.view.width; - } - - /** - * Same as view.height, actual number of pixels in the canvas by vertical - * - * @member {number} - * @readonly - * @default 600 - */ - get height() - { - return this.view.height; - } - - /** - * Resizes the screen and canvas to the specified width and height - * Canvas dimensions are multiplied by resolution - * - * @param {number} screenWidth - the new width of the screen - * @param {number} screenHeight - the new height of the screen - */ - resize(screenWidth, screenHeight) - { - this.screen.width = screenWidth; - this.screen.height = screenHeight; - - this.view.width = screenWidth * this.resolution; - this.view.height = screenHeight * this.resolution; - - if (this.autoResize) - { - this.view.style.width = `${screenWidth}px`; - this.view.style.height = `${screenHeight}px`; - } - } - - /** - * 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 {PIXI.DisplayObject} displayObject - The displayObject the object will be generated from - * @param {number} scaleMode - Should be one of the scaleMode consts - * @param {number} resolution - The resolution / device pixel ratio of the texture being generated - * @param {PIXI.Rectangle} [region] - The region of the displayObject, that shall be rendered, - * if no region is specified, defaults to the local bounds of the displayObject. - * @return {PIXI.Texture} a texture of the graphics object - */ - generateTexture(displayObject, scaleMode, resolution, region) - { - region = region || displayObject.getLocalBounds(); - - // 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(region.width | 0, region.height | 0, scaleMode, resolution); - - tempMatrix.tx = -region.x; - tempMatrix.ty = -region.y; - - this.render(displayObject, renderTexture, false, tempMatrix, true); - - return renderTexture; - } - - /** - * Removes everything from the renderer and optionally removes the Canvas DOM element. - * - * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. - */ - destroy(removeView) - { - 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); - } - - this.plugins = null; - - this.type = RENDERER_TYPE.UNKNOWN; - - this.view = null; - - this.screen = null; - - this.resolution = 0; - - this.transparent = false; - - this.autoResize = false; - - this.blendModes = null; - - this.options = null; - - this.preserveDrawingBuffer = false; - this.clearBeforeRender = false; - - this.roundPixels = false; - - this._backgroundColor = 0; - this._backgroundColorRgba = null; - this._backgroundColorString = null; - - this._tempDisplayObjectParent = null; - this._lastObjectRendered = null; - } - - /** - * The background color to fill if not transparent - * - * @member {number} - */ - get backgroundColor() - { - return this._backgroundColor; - } - - set backgroundColor(value) // eslint-disable-line require-jsdoc - { - this._backgroundColor = value; - this._backgroundColorString = hex2string(value); - hex2rgb(value, this._backgroundColorRgba); - } -} diff --git a/packages/core/src/renderers/Renderer.js b/packages/core/src/renderers/Renderer.js deleted file mode 100644 index a2020b1..0000000 --- a/packages/core/src/renderers/Renderer.js +++ /dev/null @@ -1,329 +0,0 @@ -import AbstractRenderer from './AbstractRenderer'; -import { sayHello } from '@pixi/utils'; -import MaskSystem from './systems/MaskSystem'; -import StencilSystem from './systems/StencilSystem'; -import FilterSystem from './systems/filter/FilterSystem'; -import FramebufferSystem from './systems/FramebufferSystem'; -import RenderTextureSystem from './systems/RenderTextureSystem'; -import TextureSystem from './systems/textures/TextureSystem'; -import ProjectionSystem from './systems/ProjectionSystem'; -import StateSystem from './systems/StateSystem'; -import GeometrySystem from './systems/geometry/GeometrySystem'; -import ShaderSystem from './systems/shader/ShaderSystem'; -import ContextSystem from './systems/ContextSystem'; -import BatchSystem from './systems/BatchSystem'; -import TextureGCSystem from './systems/textures/TextureGCSystem'; -import { RENDERER_TYPE } from '@pixi/constants'; -import UniformGroup from '../shader/UniformGroup'; -import { Matrix } from '@pixi/math'; -import Runner from 'mini-runner'; - -/** - * 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 Render works by automatically managing webGLBatchs. - * 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 :) - * - * @class - * @memberof PIXI - * @extends PIXI.AbstractRenderer - */ -export default class Renderer extends AbstractRenderer -{ - // eslint-disable-next-line valid-jsdoc - /** - * - * @param {object} [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.transparent=false] - If the render view is transparent, default false - * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false - * @param {boolean} [options.antialias=false] - sets antialias. If not available natively then FXAA - * antialiasing is used - * @param {boolean} [options.forceFXAA=false] - forces FXAA antialiasing to be used over native. - * FXAA is faster, but may not always look as great - * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. - * The resolution of the renderer retina would be 2. - * @param {boolean} [options.clearBeforeRender=true] - This sets if the renderer will clear - * the canvas or not before the new render pass. If you wish to set this to false, you *must* set - * preserveDrawingBuffer to `true`. - * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, - * enable this if you need to call toDataUrl on the webgl context. - * @param {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when - * rendering, stopping pixel interpolation. - * @param {number} [options.backgroundColor=0x000000] - The background color of the rendered area - * (shown if not transparent). - * @param {boolean} [options.legacy=false] - If true PixiJS will aim to ensure compatibility - * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. - * @param {string} [options.powerPreference] - Parameter passed to webgl context, set to "high-performance" - * for devices with dual graphics card - */ - constructor(options, arg2, arg3) - { - super('WebGL', options, arg2, arg3); - - /** - * The type of this renderer as a standardised const - * - * @member {number} - * @see PIXI.RENDERER_TYPE - */ - this.type = RENDERER_TYPE.WEBGL; - - // this will be set by the contextSystem (this.context) - this.gl = null; - this.CONTEXT_UID = 0; - this.legacy = !!options.legacy; - - // TODO legacy! - - // runners! - this.runners = { - destroy: new Runner('destroy'), - contextChange: new Runner('contextChange', 1), - reset: new Runner('reset'), - update: new Runner('update'), - postrender: new Runner('postrender'), - prerender: new Runner('prerender'), - resize: new Runner('resize', 2), - }; - - 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(GeometrySystem, 'geometry') - .addSystem(FramebufferSystem, 'framebuffer') - .addSystem(StencilSystem, 'stencil') - .addSystem(ProjectionSystem, 'projection') - .addSystem(TextureGCSystem, 'textureGC') - .addSystem(FilterSystem, 'filter') - .addSystem(RenderTextureSystem, 'renderTexture') - .addSystem(BatchSystem, 'batch'); - - this.initPlugins(Renderer.__plugins); - - /** - * The options passed in to create a new webgl context. - * - * @member {object} - * @private - */ - if (options.context) - { - this.context.initFromContext(options.context); - } - else - { - this.context.initFromOptions({ - alpha: this.transparent, - antialias: options.antialias, - premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', - stencil: true, - 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); - } - - addSystem(_class, name) - { - if (!name) - { - name = _class.name; - } - - // TODO - read name from class.name.. - - /* - if(name.includes('System')) - { - name = name.replace('System', ''); - name = name.charAt(0).toLowerCase() + name.slice(1); - } - */ - - const system = new _class(this); - - if (this[name]) - { - throw new Error(`Whoops! ${name} is already a manger`); - } - - this[name] = system; - - for (const i in this.runners) - { - this.runners[i].add(system); - } - - return this; - - /** - * 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. - */ - } - - /** - * Renders the object to its webGL view - * - * @param {PIXI.DisplayObject} displayObject - the object to be rendered - * @param {PIXI.RenderTexture} renderTexture - The render texture to render to. - * @param {boolean} [clear] - Should the canvas be cleared before the new render - * @param {PIXI.Transform} [transform] - A transform to apply to the render texture before rendering. - * @param {boolean} [skipUpdateTransform] - Should we skip the update transform pass? - */ - render(displayObject, renderTexture, clear, transform, skipUpdateTransform) - { - // can be handy to know! - this.renderingToScreen = !renderTexture; - - this.runners.prerender.run(); - this.emit('prerender'); - - // 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.parent; - - displayObject.parent = this._tempDisplayObjectParent; - displayObject.updateTransform(); - displayObject.parent = 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.run(); - - this.emit('postrender'); - } - - /** - * Resizes the webGL view to the specified width and height. - * - * @param {number} screenWidth - the new width of the screen - * @param {number} screenHeight - the new height of the screen - */ - resize(screenWidth, screenHeight) - { - AbstractRenderer.prototype.resize.call(this, screenWidth, screenHeight); - - this.runners.resize.run(screenWidth, screenHeight); - } - - /** - * Resets the WebGL state so you can render things however you fancy! - * - * @return {PIXI.Renderer} Returns itself. - */ - reset() - { - this.runners.reset.run(); - - return this; - } - - clear() - { - this.framebuffer.bind(); - this.framebuffer.clear(); - } - - /** - * Removes everything from the renderer (event listeners, spritebatch, etc...) - * - * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. - * See: https://github.com/pixijs/pixi.js/issues/2233 - */ - destroy(removeView) - { - this.runners.destroy.run(); - - // call base destroy - super.destroy(removeView); - - // TODO nullify all the managers.. - this.gl = null; - } - - /** - * 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 - * builds and excluding plugins. - * @name PIXI.Renderer#plugins - * @type {object} - * @readonly - * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. - * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. - * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. - * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. - */ - - /** - * Adds a plugin to the renderer. - * - * @method - * @param {string} pluginName - The name of the plugin. - * @param {Function} ctor - The constructor function or class for the plugin. - */ - static registerPlugin(pluginName, ctor) - { - Renderer.__plugins = Renderer.__plugins || {}; - Renderer.__plugins[pluginName] = ctor; - } -} diff --git a/packages/core/src/renderers/State.js b/packages/core/src/renderers/State.js deleted file mode 100644 index fa3f221..0000000 --- a/packages/core/src/renderers/State.js +++ /dev/null @@ -1,163 +0,0 @@ -/* eslint-disable max-len */ - -const BLEND = 0; -const OFFSET = 1; -const CULLING = 2; -const DEPTH_TEST = 3; -const WINDING = 4; - -/** - * This is a webGL state. It is passed The WebGL StateManager. - * Each mesh renderered may require webGL to be in a different state. - * For example you may want different blend mode or to enable polygon offsets - * - * @class - * @memberof PIXI - */ -export default class State -{ - /** - * - */ - constructor() - { - this.data = 0; - - this.blendMode = 0; - this.polygonOffset = 0; - - this.blend = true; - // this.depthTest = true; - } - - /** - * Activates blending of the computed fragment color values - * - * @member {boolean} - */ - get blend() - { - return !!(this.data & (1 << BLEND)); - } - - set blend(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << BLEND)) !== value) - { - this.data ^= (1 << BLEND); - } - } - - /** - * Activates adding an offset to depth values of polygon's fragments - * - * @member {boolean} - * @default false - */ - get offsets() - { - return !!(this.data & (1 << OFFSET)); - } - - set offsets(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << OFFSET)) !== value) - { - this.data ^= (1 << OFFSET); - } - } - - /** - * Activates culling of polygons. - * - * @member {boolean} - * @default false - */ - get culling() - { - return !!(this.data & (1 << CULLING)); - } - - set culling(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << CULLING)) !== value) - { - this.data ^= (1 << CULLING); - } - } - - /** - * Activates depth comparisons and updates to the depth buffer. - * - * @member {boolean} - * @default false - */ - get depthTest() - { - return !!(this.data & (1 << DEPTH_TEST)); - } - - set depthTest(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << DEPTH_TEST)) !== value) - { - this.data ^= (1 << DEPTH_TEST); - } - } - - /** - * Specifies whether or not front or back-facing polygons can be culled. - * @member {boolean} - * @default false - */ - get clockwiseFrontFace() - { - return !!(this.data & (1 << WINDING)); - } - - set clockwiseFrontFace(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << WINDING)) !== value) - { - this.data ^= (1 << WINDING); - } - } - - /** - * The blend mode to be applied when this state is set. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. - * Setting this mode to anything other than NO_BLEND will automatically switch blending on. - * - * @member {boolean} - * @default PIXI.BLEND_MODES.NORMAL - * @see PIXI.BLEND_MODES - */ - get blendMode() - { - return this._blendMode; - } - - set blendMode(value) // eslint-disable-line require-jsdoc - { - // 17 is NO BLEND - this.blend = (value !== 17); - this._blendMode = value; - } - - /** - * The polygon offset. Setting this property to anything other than 0 will automatically enable poygon offset fill. - * - * @member {number} - * @default 0 - */ - get polygonOffset() - { - return this._polygonOffset; - } - - set polygonOffset(value) // eslint-disable-line require-jsdoc - { - this.offsets = !!value; - this._polygonOffset = value; - } -} - diff --git a/packages/core/src/renderers/filters/Filter.js b/packages/core/src/renderers/filters/Filter.js deleted file mode 100644 index 2073b8d..0000000 --- a/packages/core/src/renderers/filters/Filter.js +++ /dev/null @@ -1,140 +0,0 @@ -import Shader from '../../shader/Shader'; -import Program from '../../shader/Program'; -import State from '../State'; -import { settings } from '@pixi/settings'; -// import extractUniformsFromSrc from './extractUniformsFromSrc'; -import defaultVertex from './defaultFilter.vert'; -import defaultFragment from './defaultFilter.frag'; - -// let math = require('../../math'); -/** - * @class - * @memberof PIXI - * @extends PIXI.Shader - */ -export default class Filter extends Shader -{ - /** - * @param {string} [vertexSrc] - The source of the vertex shader. - * @param {string} [fragmentSrc] - The source of the fragment shader. - * @param {object} [uniforms] - Custom uniforms to use to augment the built-in ones. - */ - constructor(vertexSrc, fragmentSrc, uniforms) - { - const program = Program.from(vertexSrc, fragmentSrc); - - super(program, uniforms); - - /** - * The padding of the filter. Some filters require extra space to breath such as a blur. - * Increasing this will add extra width and height to the bounds of the object that the - * filter is applied to. - * - * @member {number} - */ - this.padding = 0; - - /** - * The resolution of the filter. Setting this to be lower will lower the quality but - * increase the performance of the filter. - * - * @member {number} - */ - this.resolution = settings.RESOLUTION; - - /** - * If enabled is true the filter is applied, if false it will not. - * - * @member {boolean} - */ - this.enabled = true; - - /** - * If enabled, PixiJS will fit the filter area into boundaries for better performance. - * Switch it off if it does not work for specific shader. - * - * @member {boolean} - */ - this.autoFit = true; - - /** - * Legacy filters use position and uvs from attributes - * @member {boolean} - * @readonly - */ - this.legacy = !!this.program.attributeData.aTextureCoord; - - /** - * the webGL state the filter requires to render - * @member {PIXI.State} - */ - this.state = new State(); - } - - /** - * Applies the filter - * - * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from - * @param {PIXI.RenderTarget} input - The input render target. - * @param {PIXI.RenderTarget} output - The target to output to. - * @param {boolean} clear - Should the output be cleared before rendering to it - * @param {object} [currentState] - It's current state of filter. - * There are some useful properties in the currentState : - * target, filters, sourceFrame, destinationFrame, renderTarget, resolution - */ - apply(filterManager, input, output, clear, currentState, derp) // eslint-disable-line no-unused-vars - { - // do as you please! - - filterManager.applyFilter(this, input, output, clear, currentState, derp); - - // or just do a regular render.. - } - - /** - * Sets the blendmode of the filter - * - * @member {number} - * @default PIXI.BLEND_MODES.NORMAL - */ - get blendMode() - { - return this.state.blendMode; - } - - set blendMode(value) // eslint-disable-line require-jsdoc - { - this.state.blendMode = value; - } - - /** - * The default vertex shader source - * - * @static - * @constant - */ - static get defaultVertexSrc() - { - return defaultVertex; - } - - /** - * The default fragment shader source - * - * @static - * @constant - */ - static get defaultFragmentSrc() - { - return defaultFragment; - } -} - -/** - * Used for caching shader IDs - * - * @static - * @private - */ -Filter.SOURCE_KEY_MAP = {}; - diff --git a/packages/core/src/renderers/filters/defaultFilter.frag b/packages/core/src/renderers/filters/defaultFilter.frag deleted file mode 100644 index a14c1be..0000000 --- a/packages/core/src/renderers/filters/defaultFilter.frag +++ /dev/null @@ -1,22 +0,0 @@ -varying vec2 vTextureCoord; -varying vec2 vFilterCoord; - -uniform sampler2D uSampler; -uniform sampler2D filterSampler; - -void main(void){ - vec4 masky = texture2D(filterSampler, vFilterCoord); - vec4 sample = texture2D(uSampler, vTextureCoord); - vec4 color; - if(mod(vFilterCoord.x, 1.0) > 0.5) - { - color = vec4(1.0, 0.0, 0.0, 1.0); - } - else - { - color = vec4(0.0, 1.0, 0.0, 1.0); - } - // gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0); - gl_FragColor = mix(sample, masky, 0.5); - gl_FragColor *= sample.a; -} diff --git a/packages/core/src/renderers/filters/defaultFilter.vert b/packages/core/src/renderers/filters/defaultFilter.vert deleted file mode 100644 index e1febca..0000000 --- a/packages/core/src/renderers/filters/defaultFilter.vert +++ /dev/null @@ -1,14 +0,0 @@ -attribute vec2 aVertexPosition; -attribute vec2 aTextureCoord; - -uniform mat3 projectionMatrix; -uniform mat3 filterMatrix; - -varying vec2 vTextureCoord; -varying vec2 vFilterCoord; - -void main(void){ - gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); - vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy; - vTextureCoord = aTextureCoord ; -} diff --git a/packages/core/src/renderers/filters/filterTransforms.js b/packages/core/src/renderers/filters/filterTransforms.js deleted file mode 100644 index 87a3cd6..0000000 --- a/packages/core/src/renderers/filters/filterTransforms.js +++ /dev/null @@ -1,50 +0,0 @@ -import { Matrix } from '@pixi/math'; - -/** - * Calculates the mapped matrix - * @param filterArea {Rectangle} The filter area - * @param sprite {Sprite} the target sprite - * @param outputMatrix {Matrix} @alvin - * @private - */ -// this returns a matrix that will normalise map filter cords in the filter to screen space -export function calculateScreenSpaceMatrix(outputMatrix, filterArea, textureSize) -{ - // TODO unwrap? - const mappedMatrix = outputMatrix.identity(); - - mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); - - mappedMatrix.scale(textureSize.width, textureSize.height); - - return mappedMatrix; -} - -export function calculateNormalizedScreenSpaceMatrix(outputMatrix, filterArea, textureSize) -{ - const mappedMatrix = outputMatrix.identity(); - - mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); - - const translateScaleX = (textureSize.width / filterArea.width); - const translateScaleY = (textureSize.height / filterArea.height); - - mappedMatrix.scale(translateScaleX, translateScaleY); - - return mappedMatrix; -} - -// this will map the filter coord so that a texture can be used based on the transform of a sprite -export function calculateSpriteMatrix(outputMatrix, filterArea, textureSize, sprite) -{ - const orig = sprite._texture.orig; - const mappedMatrix = outputMatrix.set(textureSize.width, 0, 0, textureSize.height, filterArea.x, filterArea.y); - const worldTransform = sprite.worldTransform.copyTo(Matrix.TEMP_MATRIX); - - worldTransform.invert(); - mappedMatrix.prepend(worldTransform); - mappedMatrix.scale(1.0 / orig.width, 1.0 / orig.height); - mappedMatrix.translate(sprite.anchor.x, sprite.anchor.y); - - return mappedMatrix; -} diff --git a/packages/core/src/renderers/filters/spriteMask/SpriteMaskFilter.js b/packages/core/src/renderers/filters/spriteMask/SpriteMaskFilter.js deleted file mode 100644 index d5a91a4..0000000 --- a/packages/core/src/renderers/filters/spriteMask/SpriteMaskFilter.js +++ /dev/null @@ -1,63 +0,0 @@ -import Filter from '../Filter'; -import { Matrix } from '@pixi/math'; -import vertex from './spriteMaskFilter.vert'; -import fragment from './spriteMaskFilter.frag'; -import { default as TextureMatrix } from '../../../textures/TextureMatrix'; - -/** - * The SpriteMaskFilter class - * - * @class - * @extends PIXI.Filter - * @memberof PIXI - */ -export default class SpriteMaskFilter extends Filter -{ - /** - * @param {PIXI.Sprite} sprite - the target sprite - */ - constructor(sprite) - { - const maskMatrix = new Matrix(); - - super(vertex, fragment); - - sprite.renderable = false; - - this.maskSprite = sprite; - this.maskMatrix = maskMatrix; - } - - /** - * Applies the filter - * - * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from - * @param {PIXI.RenderTarget} input - The input render target. - * @param {PIXI.RenderTarget} output - The target to output to. - */ - apply(filterManager, input, output) - { - const maskSprite = this.maskSprite; - const tex = this.maskSprite.texture; - - if (!tex.valid) - { - return; - } - if (!tex.transform) - { - // margin = 0.0, let it bleed a bit, shader code becomes easier - // assuming that atlas textures were made with 1-pixel padding - tex.transform = new TextureMatrix(tex, 0.0); - } - tex.transform.update(); - - this.uniforms.mask = tex; - this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite) - .prepend(tex.transform.mapCoord); - this.uniforms.alpha = maskSprite.worldAlpha; - this.uniforms.maskClamp = tex.transform.uClampFrame; - - filterManager.applyFilter(this, input, output); - } -} diff --git a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.frag b/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.frag deleted file mode 100644 index 0e4aef8..0000000 --- a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.frag +++ /dev/null @@ -1,23 +0,0 @@ -varying vec2 vMaskCoord; -varying vec2 vTextureCoord; - -uniform sampler2D uSampler; -uniform sampler2D mask; -uniform float alpha; -uniform vec4 maskClamp; - -void main(void) -{ - float clip = step(3.5, - step(maskClamp.x, vMaskCoord.x) + - step(maskClamp.y, vMaskCoord.y) + - step(vMaskCoord.x, maskClamp.z) + - step(vMaskCoord.y, maskClamp.w)); - - vec4 original = texture2D(uSampler, vTextureCoord); - vec4 masky = texture2D(mask, vMaskCoord); - - original *= (masky.r * masky.a * alpha * clip); - - gl_FragColor = original; -} diff --git a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.vert b/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.vert deleted file mode 100644 index d23a30c..0000000 --- a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.vert +++ /dev/null @@ -1,16 +0,0 @@ -attribute vec2 aVertexPosition; -attribute vec2 aTextureCoord; - -uniform mat3 projectionMatrix; -uniform mat3 otherMatrix; - -varying vec2 vMaskCoord; -varying vec2 vTextureCoord; - -void main(void) -{ - gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); - - vTextureCoord = aTextureCoord; - vMaskCoord = ( otherMatrix * vec3( aTextureCoord, 1.0) ).xy; -} diff --git a/packages/core/src/renderers/systems/BatchSystem.js b/packages/core/src/renderers/systems/BatchSystem.js deleted file mode 100644 index 70e9518..0000000 --- a/packages/core/src/renderers/systems/BatchSystem.js +++ /dev/null @@ -1,68 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import ObjectRenderer from '../utils/ObjectRenderer'; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ - -export default class BatchSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this manager works for. - */ - constructor(renderer) - { - super(renderer); - - /** - * An empty renderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.emptyRenderer = new ObjectRenderer(renderer); - - /** - * The currently active ObjectRenderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.currentRenderer = this.emptyRenderer; - } - - /** - * Changes the current renderer to the one given in parameter - * - * @param {PIXI.ObjectRenderer} objectRenderer - The object renderer to use. - */ - setObjectRenderer(objectRenderer) - { - if (this.currentRenderer === objectRenderer) - { - return; - } - - this.currentRenderer.stop(); - this.currentRenderer = objectRenderer; - - this.renderer.state.setState(objectRenderer.state); - - this.currentRenderer.start(); - } - - /** - * This should be called if you wish to do some custom rendering - * It will basically render anything that may be batched up such as sprites - * - */ - flush() - { - this.setObjectRenderer(this.emptyRenderer); - } - - reset() - { - this.setObjectRenderer(this.emptyRenderer); - } -} diff --git a/packages/core/src/renderers/systems/ContextSystem.js b/packages/core/src/renderers/systems/ContextSystem.js deleted file mode 100644 index 43bf827..0000000 --- a/packages/core/src/renderers/systems/ContextSystem.js +++ /dev/null @@ -1,186 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { settings } from '@pixi/settings'; - -let CONTEXT_UID = 0; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class ContextSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this.webGLVersion = 1; - - this.handleContextLost = this.handleContextLost.bind(this); - this.handleContextRestored = this.handleContextRestored.bind(this); - - this.extensions = {}; - - renderer.view.addEventListener('webglcontextlost', this.handleContextLost, false); - renderer.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); - } - - get isLost() - { - return (!this.gl || this.gl.isContextLost()); - } - - contextChange(gl) - { - this.gl = gl; - - // restore a context if it was previously lost - if (gl.isContextLost() && gl.getExtension('WEBGL_lose_context')) - { - gl.getExtension('WEBGL_lose_context').restoreContext(); - } - } - - initFromContext(gl) - { - this.gl = gl; - this.validateContext(gl); - this.renderer.gl = gl; - this.renderer.CONTEXT_UID = CONTEXT_UID++; - this.renderer.runners.contextChange.run(gl); - } - - initFromOptions(options) - { - const gl = this.createContext(this.renderer.view, options); - - this.initFromContext(gl); - } - - /** - * Helper class to create a webGL Context - * - * @class - * @memberof PIXI.glCore - * @param canvas {HTMLCanvasElement} the canvas element that we will get the context from - * @param options {Object} An options object that gets passed in to the canvas element containing the context attributes, - * see https://developer.mozilla.org/en/docs/Web/API/HTMLCanvasElement/getContext for the options available - * @return {WebGLRenderingContext} the WebGL context - */ - createContext(canvas, options) - { - let gl; - - if (settings.PREFER_WEBGL_2) - { - gl = canvas.getContext('webgl2', options); - } - - if (gl) - { - this.webGLVersion = 2; - } - else - { - this.webGLVersion = 1; - - gl = canvas.getContext('webgl', options) - || canvas.getContext('experimental-webgl', options); - - if (!gl) - { - // fail, not able to get a context - throw new Error('This browser does not support webGL. Try using the canvas renderer'); - } - } - - this.gl = gl; - - this.getExtensions(); - - return gl; - } - - getExtensions() - { - // time to set up default etensions that pixi uses.. - const gl = this.gl; - const extensions = this.extensions; - - if (this.webGLVersion === 1) - { - extensions.drawBuffers = gl.getExtension('WEBGL_draw_buffers'); - extensions.depthTexture = gl.getExtension('WEBKIT_WEBGL_depth_texture'); - extensions.floatTexture = gl.getExtension('OES_texture_float'); - extensions.loseContext = gl.getExtension('WEBGL_lose_context'); - - extensions.vertexArrayObject = gl.getExtension('OES_vertex_array_object') - || gl.getExtension('MOZ_OES_vertex_array_object') - || gl.getExtension('WEBKIT_OES_vertex_array_object'); - } - - // we don't use any specific WebGL 2 ones yet! - } - - /** - * Handles a lost webgl context - * - * @private - * @param {WebGLContextEvent} event - The context lost event. - */ - handleContextLost(event) - { - event.preventDefault(); - } - - /** - * Handles a restored webgl context - * - * @private - */ - handleContextRestored() - { - this.renderer.runners.contextChange.run(this.gl); - } - - destroy() - { - const view = this.renderer.view; - - // remove listeners - view.removeEventListener('webglcontextlost', this.handleContextLost); - view.removeEventListener('webglcontextrestored', this.handleContextRestored); - - this.gl.useProgram(null); - - if (this.extensions.loseContext) - { - this.extensions.loseContext.loseContext(); - } - } - - postrender() - { - this.gl.flush(); - } - - validateContext(gl) - { - const attributes = gl.getContextAttributes(); - - // this is going to be fairly simple for now.. but at least we have room to grow! - if (!attributes.stencil) - { - /* eslint-disable max-len */ - - /* eslint-disable no-console */ - console.warn('Provided WebGL context does not have a stencil buffer, masks may not render correctly'); - /* eslint-enable no-console */ - - /* eslint-enable max-len */ - } - } -} diff --git a/packages/core/src/renderers/systems/FramebufferSystem.js b/packages/core/src/renderers/systems/FramebufferSystem.js deleted file mode 100644 index df7902b..0000000 --- a/packages/core/src/renderers/systems/FramebufferSystem.js +++ /dev/null @@ -1,246 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { Rectangle } from '@pixi/math'; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class FramebufferSystem extends WebGLSystem -{ - /** - * Sets up the renderer context and necessary buffers. - * - * @private - */ - contextChange() - { - this.gl = this.renderer.gl; - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - this.current = null; - this.viewport = new Rectangle(); - - this.drawBufferExtension = this.renderer.context.extensions.drawBuffers; - } - - bind(framebuffer, frame) - { - const gl = this.gl; - - this.current = framebuffer; - - if (framebuffer) - { - // TODO cacheing layer! - - const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID] || this.initFramebuffer(framebuffer); - - gl.bindFramebuffer(gl.FRAMEBUFFER, fbo.framebuffer); - // makesure all textures are unbound.. - - // now check for updates... - if (fbo.dirtyId !== framebuffer.dirtyId) - { - fbo.dirtyId = framebuffer.dirtyId; - - if (fbo.dirtyFormat !== framebuffer.dirtyFormat) - { - fbo.dirtyFormat = framebuffer.dirtyFormat; - this.updateFramebuffer(framebuffer); - } - else if (fbo.dirtySize !== framebuffer.dirtySize) - { - fbo.dirtySize = framebuffer.dirtySize; - this.resizeFramebuffer(framebuffer); - } - } - - for (let i = 0; i < framebuffer.colorTextures.length; i++) - { - if (framebuffer.colorTextures[i].texturePart) - { - this.renderer.texture.unbind(framebuffer.colorTextures[i].texture); - } - else - { - this.renderer.texture.unbind(framebuffer.colorTextures[i]); - } - } - - if (framebuffer.depthTexture) - { - this.renderer.texture.unbind(framebuffer.depthTexture); - } - - if (frame) - { - this.setViewport(frame.x, frame.y, frame.width, frame.height); - } - else - { - this.setViewport(0, 0, framebuffer.width, framebuffer.height); - } - } - else - { - gl.bindFramebuffer(gl.FRAMEBUFFER, null); - - if (frame) - { - this.setViewport(frame.x, frame.y, frame.width, frame.height); - } - else - { - this.setViewport(0, 0, this.renderer.width, this.renderer.height); - } - } - } - - setViewport(x, y, width, height) - { - const v = this.viewport; - - if (v.width !== width || v.height !== height || v.x !== x || v.y !== y) - { - v.x = x; - v.y = y; - v.width = width; - v.height = height; - - this.gl.viewport(x, y, width, height); - } - } - - get size() - { - if (this.current) - { - // TODO store temp - return { x: 0, y: 0, width: this.current.width, height: this.current.height }; - } - - return { x: 0, y: 0, width: this.renderer.width, height: this.renderer.height }; - } - - clear(r, g, b, a) - { - const gl = this.gl; - - // TODO clear color can be set only one right? - gl.clearColor(r, g, b, a); - gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); - } - - // private functions... - - initFramebuffer(framebuffer) - { - const gl = this.gl; - - // TODO - make this a class? - const fbo = { - framebuffer: gl.createFramebuffer(), - stencil: null, - dirtyId: 0, - dirtyFormat: 0, - dirtySize: 0, - }; - - framebuffer.glFrameBuffers[this.CONTEXT_UID] = fbo; - - return fbo; - } - - resizeFramebuffer(framebuffer) - { - const gl = this.gl; - - if (framebuffer.stencil || framebuffer.depth) - { - gl.bindRenderbuffer(gl.RENDERBUFFER, this.stencil); - gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); - } - } - - updateFramebuffer(framebuffer) - { - const gl = this.gl; - - const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID]; - - // bind the color texture - const colorTextures = framebuffer.colorTextures; - - let count = colorTextures.length; - - if (!this.drawBufferExtension) - { - count = Math.min(count, 1); - } - - const activeTextures = []; - - for (let i = 0; i < count; i++) - { - const texture = framebuffer.colorTextures[i]; - - if (texture.texturePart) - { - this.renderer.texture.bind(texture.texture, 0); - - gl.framebufferTexture2D(gl.FRAMEBUFFER, - gl.COLOR_ATTACHMENT0 + i, - gl.TEXTURE_CUBE_MAP_NEGATIVE_X + texture.side, - texture.texture._glTextures[this.CONTEXT_UID].texture, - 0); - } - else - { - this.renderer.texture.bind(texture, 0); - - gl.framebufferTexture2D(gl.FRAMEBUFFER, - gl.COLOR_ATTACHMENT0 + i, - gl.TEXTURE_2D, - texture._glTextures[this.CONTEXT_UID].texture, - 0); - } - - activeTextures.push(gl.COLOR_ATTACHMENT0 + i); - } - - if (this.drawBufferExtension && activeTextures.length > 1) - { - this.drawBufferExtension.drawBuffersWEBGL(activeTextures); - } - - if (framebuffer.depthTexture) - { - const depthTextureExt = this.renderer.context.extensions.depthTexture; - - if (depthTextureExt) - { - const depthTexture = framebuffer.depthTexture; - - this.renderer.texture.bind(depthTexture, 0); - - gl.framebufferTexture2D(gl.FRAMEBUFFER, - gl.DEPTH_ATTACHMENT, - gl.TEXTURE_2D, - depthTexture._glTextures[this.CONTEXT_UID].texture, - 0); - } - } - - if (framebuffer.stencil || framebuffer.depth) - { - fbo.stencil = gl.createRenderbuffer(); - - gl.bindRenderbuffer(gl.RENDERBUFFER, fbo.stencil); - - // TODO.. this is depth AND stencil? - gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, fbo.stencil); - gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); - // fbo.enableStencil(); - } - } -} diff --git a/packages/core/src/renderers/systems/MaskSystem.js b/packages/core/src/renderers/systems/MaskSystem.js deleted file mode 100644 index d9c4b1a..0000000 --- a/packages/core/src/renderers/systems/MaskSystem.js +++ /dev/null @@ -1,199 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import AlphaMaskFilter from '../filters/spriteMask/SpriteMaskFilter'; - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class MaskSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - // TODO - we don't need both! - this.scissor = false; - this.scissorData = null; - this.scissorRenderTarget = null; - - this.enableScissor = false; - - this.alphaMaskPool = []; - this.alphaMaskIndex = 0; - } - - /** - * Applies the Mask and adds it to the current filter stack. - * - * @param {PIXI.DisplayObject} target - Display Object to push the mask to - * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. - */ - push(target, maskData) - { - // TODO the root check means scissor rect will not - // be used on render textures more info here: - // https://github.com/pixijs/pixi.js/pull/3545 - - if (maskData.texture) - { - this.pushSpriteMask(target, maskData); - } - else if (this.enableScissor - && !this.scissor - && this.renderer._activeRenderTarget.root - && !this.renderer.stencil.stencilMaskStack.length - && maskData.isFastRect()) - { - const matrix = maskData.worldTransform; - - let rot = Math.atan2(matrix.b, matrix.a); - - // use the nearest degree! - rot = Math.round(rot * (180 / Math.PI)); - - if (rot % 90) - { - this.pushStencilMask(maskData); - } - else - { - this.pushScissorMask(target, maskData); - } - } - else - { - this.pushStencilMask(maskData); - } - } - - /** - * Removes the last mask from the mask stack and doesn't return it. - * - * @param {PIXI.DisplayObject} target - Display Object to pop the mask from - * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. - */ - pop(target, maskData) - { - if (maskData.texture) - { - this.popSpriteMask(target, maskData); - } - else if (this.enableScissor && !this.renderer.stencil.stencilMaskStack.length) - { - this.popScissorMask(target, maskData); - } - else - { - this.popStencilMask(target, maskData); - } - } - - /** - * Applies the Mask and adds it to the current filter stack. - * - * @param {PIXI.RenderTarget} target - Display Object to push the sprite mask to - * @param {PIXI.Sprite} maskData - Sprite to be used as the mask - */ - pushSpriteMask(target, maskData) - { - let alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; - - if (!alphaMaskFilter) - { - alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; - } - - alphaMaskFilter[0].resolution = this.renderer.resolution; - alphaMaskFilter[0].maskSprite = maskData; - - // TODO - may cause issues! - target.filterArea = maskData.getBounds(true); - - this.renderer.filterSystem.pushFilter(target, alphaMaskFilter); - - this.alphaMaskIndex++; - } - - /** - * Removes the last filter from the filter stack and doesn't return it. - * - */ - popSpriteMask() - { - this.renderer.filterSystem.popFilter(); - this.alphaMaskIndex--; - } - - /** - * Applies the Mask and adds it to the current filter stack. - * - * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. - */ - pushStencilMask(maskData) - { - this.renderer.batch.flush(); - this.renderer.stencil.pushStencil(maskData); - } - - /** - * Removes the last filter from the filter stack and doesn't return it. - * - */ - popStencilMask() - { - // this.renderer.currentRenderer.stop(); - this.renderer.stencil.popStencil(); - } - - /** - * - * @param {PIXI.DisplayObject} target - Display Object to push the mask to - * @param {PIXI.Graphics} maskData - The masking data. - */ - pushScissorMask(target, maskData) - { - maskData.renderable = true; - - const renderTarget = this.renderer._activeRenderTarget; - - const bounds = maskData.getBounds(); - - bounds.fit(renderTarget.size); - maskData.renderable = false; - - this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); - - const resolution = this.renderer.resolution; - - this.renderer.gl.scissor( - bounds.x * resolution, - (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, - bounds.width * resolution, - bounds.height * resolution - ); - - this.scissorRenderTarget = renderTarget; - this.scissorData = maskData; - this.scissor = true; - } - - /** - * - * - */ - popScissorMask() - { - this.scissorRenderTarget = null; - this.scissorData = null; - this.scissor = false; - - // must be scissor! - const gl = this.renderer.gl; - - gl.disable(gl.SCISSOR_TEST); - } -} diff --git a/packages/core/src/renderers/systems/ProjectionSystem.js b/packages/core/src/renderers/systems/ProjectionSystem.js deleted file mode 100644 index ec5efdb..0000000 --- a/packages/core/src/renderers/systems/ProjectionSystem.js +++ /dev/null @@ -1,73 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { Matrix } from '@pixi/math'; - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ - -export default class ProjectionSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this.projectionMatrix = new Matrix(); - } - - update(destinationFrame, sourceFrame, resolution, root) - { - this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; - this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; - - this.calculateProjection(this.destinationFrame, this.sourceFrame, resolution, root); - - this.renderer.globalUniforms.uniforms.projectionMatrix = this.projectionMatrix; - this.renderer.globalUniforms.update(); - } - - /** - * Updates the projection matrix based on a projection frame (which is a rectangle) - * - * @param {Rectangle} destinationFrame - The destination frame. - * @param {Rectangle} sourceFrame - The source frame. - */ - calculateProjection(destinationFrame, sourceFrame, resolution, root) - { - const pm = this.projectionMatrix; - - // I don't think we will need this line.. - // pm.identity(); - - if (!root) - { - pm.a = (1 / destinationFrame.width * 2) * resolution; - pm.d = (1 / destinationFrame.height * 2) * resolution; - - pm.tx = -1 - (sourceFrame.x * pm.a); - pm.ty = -1 - (sourceFrame.y * pm.d); - } - else - { - pm.a = (1 / destinationFrame.width * 2) * resolution; - pm.d = (-1 / destinationFrame.height * 2) * resolution; - - pm.tx = -1 - (sourceFrame.x * pm.a); - pm.ty = 1 - (sourceFrame.y * pm.d); - } - } - - /** - * Sets the transform of the active render target to the given matrix - * - * @param {PIXI.Matrix} matrix - The transformation matrix - */ - setTransform()// matrix) - { - // this._activeRenderTarget.transform = matrix; - } -} diff --git a/packages/core/src/renderers/systems/RenderTextureSystem.js b/packages/core/src/renderers/systems/RenderTextureSystem.js deleted file mode 100644 index a133ef1..0000000 --- a/packages/core/src/renderers/systems/RenderTextureSystem.js +++ /dev/null @@ -1,115 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { Rectangle } from '@pixi/math'; - -const tempRect = new Rectangle(); - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ - -export default class RenderTextureSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this.clearColor = renderer._backgroundColorRgba; - - // TODO moe this property somewhere else! - this.defaultMaskStack = []; - this.defaultFilterStack = [{}]; - - // empty render texture? - this.renderTexture = null; - - this.destinationFrame = new Rectangle(); - } - - bind(renderTexture, sourceFrame, destinationFrame) - { - // TODO - do we want this?? - if (this.renderTexture === renderTexture) return; - this.renderTexture = renderTexture; - - const renderer = this.renderer; - - if (renderTexture) - { - const baseTexture = renderTexture.baseTexture; - - if (!destinationFrame) - { - tempRect.width = baseTexture.realWidth; - tempRect.height = baseTexture.realHeight; - - destinationFrame = tempRect; - } - - if (!sourceFrame) - { - sourceFrame = destinationFrame; - } - - this.renderer.framebuffer.bind(baseTexture.frameBuffer, destinationFrame); - - this.renderer.projection.update(destinationFrame, sourceFrame, baseTexture.resolution, false); - this.renderer.stencil.setMaskStack(baseTexture.stencilMaskStack); - } - else - { - // TODO these validation checks happen deeper down.. - // thing they can be avoided.. - if (!destinationFrame) - { - tempRect.width = renderer.width; - tempRect.height = renderer.height; - - destinationFrame = tempRect; - } - - if (!sourceFrame) - { - sourceFrame = destinationFrame; - } - - renderer.framebuffer.bind(null, destinationFrame); - - // TODO store this.. - this.renderer.projection.update(destinationFrame, sourceFrame, this.renderer.resolution, true); - this.renderer.stencil.setMaskStack(this.defaultMaskStack); - } - - this.destinationFrame.copyFrom(destinationFrame); - } - - /** - * Erases the render texture and fills the drawing area with a colour - * - * @param {number} [clearColor] - The colour - * @return {PIXI.Renderer} Returns itself. - */ - clear(clearColor) - { - if (this.renderTexture) - { - clearColor = clearColor || this.renderTexture.baseTexture.clearColor; - } - else - { - clearColor = clearColor || this.clearColor; - } - - this.renderer.framebuffer.clear(clearColor[0], clearColor[1], clearColor[2], clearColor[3]); - } - - resize()// screenWidth, screenHeight) - { - // resize the root only! - this.bind(null); - } -} diff --git a/packages/core/src/renderers/systems/StateSystem.js b/packages/core/src/renderers/systems/StateSystem.js deleted file mode 100755 index d7f4caf..0000000 --- a/packages/core/src/renderers/systems/StateSystem.js +++ /dev/null @@ -1,288 +0,0 @@ -import mapWebGLBlendModesToPixi from '../utils/mapWebGLBlendModesToPixi'; -import WebGLSystem from './WebGLSystem'; -import WebGLState from '../State'; - -const BLEND = 0; -const OFFSET = 1; -const CULLING = 2; -const DEPTH_TEST = 3; -const WINDING = 4; - -/** - * A WebGL state machines - * - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class StateSystem extends WebGLSystem -{ - /** - * @param {WebGLRenderingContext} gl - The current WebGL rendering context - */ - constructor(renderer) - { - super(renderer); - - this.gl = null; - - this.maxAttribs = null; - - // check we have vao.. - this.nativeVaoExtension = null; - - this.attribState = null; - - this.stateId = 0; - this.polygonOffset = 0; - this.blendMode = 17; - - this.map = []; - - // map functions for when we set state.. - this.map[BLEND] = this.setBlend; - this.map[OFFSET] = this.setOffset; - this.map[CULLING] = this.setCullFace; - this.map[DEPTH_TEST] = this.setDepthTest; - this.map[WINDING] = this.setFrontFace; - - this.checks = []; - - this.defaultState = new WebGLState(); - this.defaultState.blend = true; - this.defaultState.depth = true; - } - - contextChange(gl) - { - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; - - this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); - - // check we have vao.. - this.nativeVaoExtension = ( - gl.getExtension('OES_vertex_array_object') - || gl.getExtension('MOZ_OES_vertex_array_object') - || gl.getExtension('WEBKIT_OES_vertex_array_object') - ); - - this.attribState = { - tempAttribState: new Array(this.maxAttribs), - attribState: new Array(this.maxAttribs), - }; - - this.blendModes = mapWebGLBlendModesToPixi(gl); - - this.setState(this.defaultState); - - this.reset(); - } - - /** - * Sets the current state - * - * @param {*} state - The state to set. - */ - setState(state) - { - state = state || this.defaultState; - - // TODO maybe to an object check? ( this.state === state )? - if (this.stateId !== state.data) - { - let diff = this.stateId ^ state.data; - let i = 0; - - // order from least to most common - while (diff) - { - if (diff & 1) - { - // state change! - this.map[i].call(this, !!(state.data & (1 << i))); - } - - diff = diff >> 1; - i++; - } - - this.stateId = state.data; - } - - // based on the above settings we check for specific modes.. - // for example if blend is active we check and set the blend modes - // or of polygon offset is active we check the poly depth. - for (let i = 0; i < this.checks.length; i++) - { - this.checks[i](this, state); - } - } - - /** - * Enables or disabled blending. - * - * @param {boolean} value - Turn on or off webgl blending. - */ - setBlend(value) - { - this.updateCheck(StateSystem.checkBlendMode, value); - - this.gl[value ? 'enable' : 'disable'](this.gl.BLEND); - } - - /** - * Enables or disable polygon offset fill - * - * @param {boolean} value - Turn on or off webgl polygon offset testing. - */ - setOffset(value) - { - this.gl[value ? 'enable' : 'disable'](this.gl.POLYGON_OFFSET_FILL); - } - - /** - * Sets whether to enable or disable depth test. - * - * @param {boolean} value - Turn on or off webgl depth testing. - */ - setDepthTest(value) - { - this.gl[value ? 'enable' : 'disable'](this.gl.DEPTH_TEST); - } - - /** - * Sets whether to enable or disable cull face. - * - * @param {boolean} value - Turn on or off webgl cull face. - */ - setCullFace(value) - { - this.gl[value ? 'enable' : 'disable'](this.gl.CULL_FACE); - } - - /** - * Sets the gl front face. - * - * @param {boolean} value - true is clockwise and false is counter-clockwise - */ - setFrontFace(value) - { - this.gl.frontFace(this.gl[value ? 'CW' : 'CCW']); - } - - /** - * Sets the blend mode. - * - * @param {number} value - The blend mode to set to. - */ - setBlendMode(value) - { - if (value === this.blendMode) - { - return; - } - - this.blendMode = value; - this.gl.blendFunc(this.blendModes[value][0], this.blendModes[value][1]); - } - - /** - * Sets the polygon offset. - * - * @param {number} value - the polygon offset - * @param {number} scale - the polygon offset scale - */ - setPolygonOffset(value, scale) - { - this.gl.polygonOffset(value, scale); - } - - /** - * Disables all the vaos in use - * - */ - resetAttributes() - { - for (let i = 0; i < this.attribState.tempAttribState.length; i++) - { - this.attribState.tempAttribState[i] = 0; - } - - for (let i = 0; i < this.attribState.attribState.length; i++) - { - this.attribState.attribState[i] = 0; - } - - // im going to assume one is always active for performance reasons. - for (let i = 1; i < this.maxAttribs; i++) - { - this.gl.disableVertexAttribArray(i); - } - } - - // used - /** - * Resets all the logic and disables the vaos - */ - reset() - { - // unbind any VAO if they exist.. - if (this.nativeVaoExtension) - { - this.nativeVaoExtension.bindVertexArrayOES(null); - } - - // reset all attributes.. - this.resetAttributes(); - - this.gl.pixelStorei(this.gl.UNPACK_FLIP_Y_WEBGL, false); - - this.setBlendMode(0); - - // TODO? - // this.setState(this.defaultState); - } - - /** - * checks to see which updates should be checked based on which settings have been activated - * for example if blend is enabled then we shold check the blend modes each time the state is changed - * or if poygon fill is activated then we need to check if the polygone offset changes. - * The idea is that we only check what we have too. - * - * @param {Function} func the checking function to add or remove - * @param {boolean} value should the check function be added or removed. - */ - updateCheck(func, value) - { - const index = this.checks.indexOf(func); - - if (value && index === -1) - { - this.checks.push(func); - } - else if (!value && index !== -1) - { - this.checks.splice(index, 1); - } - } - - /** - * A private little wrapper function that we call to check the blend mode. - * - * @static - * @private - * @param {PIXI.StateSystem} System the System to perform the state check on - * @param {PIXI.State} state the state that the blendMode will pulled from - */ - static checkBlendMode(system, state) - { - system.setBlendMode(state.blendMode); - } - - // TODO - add polygon offset? -} diff --git a/packages/core/src/renderers/systems/StencilSystem.js b/packages/core/src/renderers/systems/StencilSystem.js deleted file mode 100644 index 5d538cb..0000000 --- a/packages/core/src/renderers/systems/StencilSystem.js +++ /dev/null @@ -1,133 +0,0 @@ -import WebGLSystem from './WebGLSystem'; - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class StencilSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - this.stencilMaskStack = []; - } - - /** - * Changes the mask stack that is used by this System. - * - * @param {PIXI.Graphics[]} stencilMaskStack - The mask stack - */ - setMaskStack(stencilMaskStack) - { - const gl = this.renderer.gl; - - if (stencilMaskStack.length !== this.stencilMaskStack.length) - { - if (stencilMaskStack.length === 0) - { - gl.disable(gl.STENCIL_TEST); - } - else - { - gl.enable(gl.STENCIL_TEST); - } - } - - this.stencilMaskStack = stencilMaskStack; - } - - /** - * Applies the Mask and adds it to the current stencil stack. @alvin - * - * @param {PIXI.Graphics} graphics - The mask - */ - pushStencil(graphics) - { - this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); - - // this.renderer._activeRenderTarget.attachStencilBuffer(); - - const gl = this.renderer.gl; - const prevMaskCount = this.stencilMaskStack.length; - - if (prevMaskCount === 0) - { - gl.enable(gl.STENCIL_TEST); - } - - this.stencilMaskStack.push(graphics); - - // Increment the refference stencil value where the new mask overlaps with the old ones. - gl.colorMask(false, false, false, false); - gl.stencilFunc(gl.EQUAL, prevMaskCount, this._getBitwiseMask()); - gl.stencilOp(gl.KEEP, gl.KEEP, gl.INCR); - this.renderer.plugins.graphics.render(graphics); - - this._useCurrent(); - } - - /** - * Removes the last mask from the stencil stack. @alvin - */ - popStencil() - { - this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); - - const gl = this.renderer.gl; - const graphics = this.stencilMaskStack.pop(); - - if (this.stencilMaskStack.length === 0) - { - // the stack is empty! - gl.disable(gl.STENCIL_TEST); - gl.clear(gl.STENCIL_BUFFER_BIT); - gl.clearStencil(0); - } - else - { - // Decrement the refference stencil value where the popped mask overlaps with the other ones - gl.colorMask(false, false, false, false); - gl.stencilOp(gl.KEEP, gl.KEEP, gl.DECR); - this.renderer.plugins.graphics.render(graphics); - - this._useCurrent(); - } - } - - /** - * Setup renderer to use the current stencil data. - */ - _useCurrent() - { - const gl = this.renderer.gl; - - gl.colorMask(true, true, true, true); - gl.stencilFunc(gl.EQUAL, this.stencilMaskStack.length, this._getBitwiseMask()); - gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP); - } - - /** - * Fill 1s equal to the number of acitve stencil masks. - * - * @return {number} The bitwise mask. - */ - _getBitwiseMask() - { - return (1 << this.stencilMaskStack.length) - 1; - } - - /** - * Destroys the mask stack. - * - */ - destroy() - { - super.destroy(this); - - this.stencilMaskStack = null; - } -} diff --git a/packages/core/src/renderers/systems/WebGLSystem.js b/packages/core/src/renderers/systems/WebGLSystem.js deleted file mode 100644 index d3d5b31..0000000 --- a/packages/core/src/renderers/systems/WebGLSystem.js +++ /dev/null @@ -1,41 +0,0 @@ -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this manager works for. - */ - constructor(renderer) - { - /** - * The renderer this manager works for. - * - * @member {PIXI.Renderer} - */ - this.renderer = renderer; - - this.renderer.runners.contextChange.add(this); - } - - /** - * Generic method called when there is a WebGL context change. - * - */ - contextChange() - { - // do some codes init! - } - - /** - * Generic destroy methods to be overridden by the subclass - * - */ - destroy() - { - this.renderer.runners.contextChange.remove(this); - this.renderer = null; - } -} diff --git a/packages/core/src/renderers/systems/filter/FilterSystem.js b/packages/core/src/renderers/systems/filter/FilterSystem.js deleted file mode 100644 index e615037..0000000 --- a/packages/core/src/renderers/systems/filter/FilterSystem.js +++ /dev/null @@ -1,431 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; - -import RenderTexture from '../../../textures/RenderTexture'; -import Quad from '../../utils/Quad'; -import QuadUv from '../../utils/QuadUv'; -import { Rectangle } from '@pixi/math'; -import * as filterTransforms from '../../filters/filterTransforms'; -import bitTwiddle from 'bit-twiddle'; -import UniformGroup from '../../../shader/UniformGroup'; -import { DRAW_MODES } from '../../../../../constants'; - -// -/** - * @ignore - * @class - */ -class FilterState -{ - /** - * - */ - constructor() - { - this.renderTexture = null; - this.sourceFrame = new Rectangle(); - this.destinationFrame = new Rectangle(); - this.filters = []; - this.target = null; - this.legacy = false; - this.resolution = 1; - } -} - -/** - * @class - * @memberof PIXI.systems - * @extends PIXI.systems.WebGLSystem - */ -export default class FilterSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - /** - * stores a bunch of PO2 textures used for filtering - * @type {Object} - */ - this.texturePool = {}; - - /** - * a pool for storing filter states, save us creating new ones each tick - * @type {Array} - */ - this.statePool = []; - - /** - * A very simple geometry used when drawing a filter effect to the screen - * @type {Quad} - */ - this.quad = new Quad(); - - this.quadUv = new QuadUv(); - - /** - * Temporary rect for maths - * @type {PIXI.Rectangle} - */ - this.tempRect = new Rectangle(); - - this.activeState = {}; - - /** - * this uniform group is attached to filter uniforms when used - * @type {UniformGroup} - */ - this.globalUniforms = new UniformGroup({ - sourceFrame: this.tempRect, - destinationFrame: this.tempRect, - - // legacy variables - filterArea: new Float32Array(4), - filterClamp: new Float32Array(4), - }, true); - } - - /** - * Adds a new filter to the System. - * - * @param {PIXI.DisplayObject} target - The target of the filter to render. - * @param {PIXI.Filter[]} filters - The filters to apply. - */ - push(target, filters) - { - const renderer = this.renderer; - const filterStack = this.renderer.renderTexture.defaultFilterStack; - const state = this.statePool.pop() || new FilterState(); - - let resolution = filters[0].resolution; - let padding = filters[0].padding; - let autoFit = filters[0].autoFit; - let legacy = filters[0].legacy; - - for (let i = 1; i < filters.length; i++) - { - const filter = filters[i]; - - // lets use the lowest resolution.. - resolution = Math.min(resolution, filter.resolution); - // and the largest amount of padding! - padding = Math.max(padding, filter.padding); - // only auto fit if all filters are autofit - autoFit = autoFit || filter.autoFit; - - legacy = legacy || filter.legacy; - } - - filterStack.push(state); - - state.resolution = resolution; - - state.legacy = legacy; - - // round to whole number based on resolution - // TODO move that to the shader too? - state.sourceFrame = target.filterArea || target.getBounds(true); - - state.sourceFrame.pad(padding); - - if (autoFit) - { - state.sourceFrame.fit(this.renderer.renderTexture.destinationFrame); - } - - state.sourceFrame.round(resolution); - - state.renderTexture = this.getPotFilterTexture(state.sourceFrame.width, state.sourceFrame.height, resolution); - state.filters = filters; - - state.destinationFrame.width = state.renderTexture.width; - state.destinationFrame.height = state.renderTexture.height; - - state.renderTexture.filterFrame = state.sourceFrame; - renderer.renderTexture.bind(state.renderTexture, state.sourceFrame);// /, state.destinationFrame); - renderer.renderTexture.clear(); - } - - /** - * Pops off the filter and applies it. - * - */ - pop() - { - const renderer = this.renderer; - const filterStack = renderer.renderTexture.defaultFilterStack; - const state = filterStack.pop(); - const filters = state.filters; - - this.activeState = state; - - const globalUniforms = this.globalUniforms.uniforms; - - globalUniforms.sourceFrame = state.sourceFrame; - globalUniforms.destinationFrame = state.destinationFrame; - globalUniforms.resolution = state.resolution; - - // only update the rect if its legacy.. - if (state.legacy) - { - const filterArea = globalUniforms.filterArea; - const filterClamp = globalUniforms.filterClamp; - - filterArea[0] = state.destinationFrame.width; - filterArea[1] = state.destinationFrame.height; - filterArea[2] = state.sourceFrame.x; - filterArea[3] = state.sourceFrame.y; - - filterClamp[0] = 0.5 / state.resolution / state.destinationFrame.width; - filterClamp[1] = 0.5 / state.resolution / state.destinationFrame.height; - filterClamp[2] = (state.sourceFrame.width - 0.5) / state.resolution / state.destinationFrame.width; - filterClamp[3] = (state.sourceFrame.height - 0.5) / state.resolution / state.destinationFrame.height; - } - - this.globalUniforms.update(); - - const lastState = filterStack[filterStack.length - 1]; - - if (filters.length === 1) - { - filters[0].apply(this, state.renderTexture, lastState.renderTexture, false, state); - - this.returnFilterTexture(state.renderTexture); - } - else - { - let flip = state.renderTexture; - let flop = this.getPotFilterTexture( - flip.width, - flip.height, - state.resolution - ); - - flop.filterFrame = flip.filterFrame; - - let i = 0; - - for (i = 0; i < filters.length - 1; ++i) - { - filters[i].apply(this, flip, flop, true, state); - - const t = flip; - - flip = flop; - flop = t; - } - - filters[i].apply(this, flip, lastState.renderTexture, false, state); - - this.returnFilterTexture(flip); - this.returnFilterTexture(flop); - } - - this.statePool.push(state); - } - - /** - * Draws a filter. - * - * @param {PIXI.Filter} filter - The filter to draw. - * @param {PIXI.RenderTarget} input - The input render target. - * @param {PIXI.RenderTarget} output - The target to output to. - * @param {boolean} clear - Should the output be cleared before rendering to it - */ - applyFilter(filter, input, output, clear) - { - const renderer = this.renderer; - - renderer.renderTexture.bind(output, output ? output.filterFrame : null); - - if (clear) - { - // gl.disable(gl.SCISSOR_TEST); - renderer.renderTexture.clear(); - // gl.enable(gl.SCISSOR_TEST); - } - - // set the uniforms.. - filter.uniforms.uSampler = input; - filter.uniforms.filterGlobals = this.globalUniforms; - - // TODO make it so that the order of this does not matter.. - // because it does at the moment cos of global uniforms. - // they need to get resynced - - renderer.state.setState(filter.state); - renderer.shader.bind(filter); - - if (filter.legacy) - { - this.quadUv.map(input._frame, input.filterFrame); - - renderer.geometry.bind(this.quadUv); - renderer.geometry.draw(DRAW_MODES.TRIANGLES); - } - else - { - renderer.geometry.bind(this.quad); - renderer.geometry.draw(DRAW_MODES.TRIANGLE_STRIP); - } - } - - /** - * Calculates the mapped matrix. - * - * TODO playing around here.. this is temporary - (will end up in the shader) - * this returns a matrix that will normalise map filter cords in the filter to screen space - * - * @param {PIXI.Matrix} outputMatrix - the matrix to output to. - * @return {PIXI.Matrix} The mapped matrix. - */ - calculateScreenSpaceMatrix(outputMatrix) - { - const currentState = this.activeState; - - return filterTransforms.calculateScreenSpaceMatrix( - outputMatrix, - currentState.sourceFrame, - currentState.destinationFrame - ); - } - - /** - * This will map the filter coord so that a texture can be used based on the transform of a sprite - * - * @param {PIXI.Matrix} outputMatrix - The matrix to output to. - * @param {PIXI.Sprite} sprite - The sprite to map to. - * @return {PIXI.Matrix} The mapped matrix. - */ - calculateSpriteMatrix(outputMatrix, sprite) - { - const currentState = this.activeState; - - return filterTransforms.calculateSpriteMatrix( - outputMatrix, - currentState.sourceFrame, - currentState.destinationFrame, - sprite - ); - } - - /** - * Destroys this Filter System. - * - * @param {boolean} [contextLost=false] context was lost, do not free shaders - * - */ - destroy(contextLost = false) - { - if (!contextLost) - { - this.emptyPool(); - } - else - { - this.texturePool = {}; - } - } - - /** - * Gets a Power-of-Two render texture. - * - * TODO move to a seperate class could be on renderer? - * also - could cause issue with multiple contexts? - * - * @private - * @param {WebGLRenderingContext} gl - The webgl rendering context - * @param {number} minWidth - The minimum width of the render target. - * @param {number} minHeight - The minimum height of the render target. - * @param {number} resolution - The resolution of the render target. - * @return {PIXI.RenderTarget} The new render target. - */ - getPotFilterTexture(minWidth, minHeight, resolution) - { - minWidth = bitTwiddle.nextPow2(minWidth); - minHeight = bitTwiddle.nextPow2(minHeight); - resolution = resolution || 1; - - const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); - - if (!this.texturePool[key]) - { - this.texturePool[key] = []; - } - - let renderTexture = this.texturePool[key].pop(); - - if (!renderTexture) - { - // temporary bypass cache.. - // internally - this will cause a texture to be bound.. - renderTexture = RenderTexture.create({ - width: minWidth, - height: minHeight, - resolution, - }); - } - - return renderTexture; - } - - /** - * Gets extra render texture to use inside current filter - * - * @param {number} resolution resolution of the renderTexture - * @returns {PIXI.RenderTexture} - */ - getFilterTexture(resolution) - { - const rt = this.activeState.renderTexture; - - const filterTexture = this.getPotFilterTexture(rt.width, rt.height, resolution || rt.baseTexture.resolution); - - filterTexture.filterFrame = rt.filterFrame; - - return filterTexture; - } - - /** - * Frees a render texture back into the pool. - * - * @param {PIXI.RenderTarget} renderTexture - The renderTarget to free - */ - returnFilterTexture(renderTexture) - { - renderTexture.filterFrame = null; - - const base = renderTexture.baseTexture; - - const minWidth = base.width; - const minHeight = base.height; - - const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); - - this.texturePool[key].push(renderTexture); - } - - /** - * Empties the texture pool. - * - */ - emptyPool() - { - for (const i in this.texturePool) - { - const textures = this.texturePool[i]; - - if (textures) - { - for (let j = 0; j < textures.length; j++) - { - textures[j].destroy(true); - } - } - } - - this.texturePool = {}; - } -} diff --git a/packages/core/src/renderers/systems/geometry/GLBuffer.js b/packages/core/src/renderers/systems/geometry/GLBuffer.js deleted file mode 100644 index 0477e8a..0000000 --- a/packages/core/src/renderers/systems/geometry/GLBuffer.js +++ /dev/null @@ -1,9 +0,0 @@ -export default class GLBuffer -{ - constructor(buffer) - { - this.buffer = buffer; - this.updateID = -1; - this.byteLength = -1; - } -} diff --git a/packages/core/src/renderers/systems/geometry/GeometrySystem.js b/packages/core/src/renderers/systems/geometry/GeometrySystem.js deleted file mode 100644 index 26c8e9e..0000000 --- a/packages/core/src/renderers/systems/geometry/GeometrySystem.js +++ /dev/null @@ -1,388 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; -import GLBuffer from './GLBuffer'; - -const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class GeometrySystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this._activeGeometry = null; - this._activeVao = null; - - this.hasVao = true; - this.hasInstance = true; - } - - /** - * Sets up the renderer context and necessary buffers. - * - * @private - */ - contextChange() - { - const gl = this.gl = this.renderer.gl; - - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - - // webgl2 - if (!gl.createVertexArray) - { - // webgl 1! - let nativeVaoExtension = this.renderer.context.extensions.vertexArrayObject; - - if (this.renderer.legacy) - { - nativeVaoExtension = null; - } - - if (nativeVaoExtension) - { - gl.createVertexArray = () => - nativeVaoExtension.createVertexArrayOES(); - - gl.bindVertexArray = (vao) => - nativeVaoExtension.bindVertexArrayOES(vao); - - gl.deleteVertexArray = (vao) => - nativeVaoExtension.deleteVertexArrayOES(vao); - } - else - { - this.hasVao = false; - gl.createVertexArray = () => - { - // empty - }; - - gl.bindVertexArray = () => - { - // empty - }; - - gl.deleteVertexArray = () => - { - // empty - }; - } - } - - if (!gl.vertexAttribDivisor) - { - const instanceExt = gl.getExtension('ANGLE_instanced_arrays'); - - if (instanceExt) - { - gl.vertexAttribDivisor = (a, b) => - instanceExt.vertexAttribDivisorANGLE(a, b); - - gl.drawElementsInstanced = (a, b, c, d, e) => - instanceExt.drawElementsInstancedANGLE(a, b, c, d, e); - - gl.drawArraysInstanced = (a, b, c, d) => - instanceExt.drawArraysInstancedANGLE(a, b, c, d); - } - else - { - this.hasInstance = false; - } - } - } - - /** - * Binds geometry so that is can be drawn. Creating a Vao if required - * @private - * @param {PIXI.Geometry} geometry instance of geometry to bind - */ - bind(geometry, shader) - { - shader = shader || this.renderer.shader.shader; - - const gl = this.gl; - - // not sure the best way to address this.. - // currently different shaders require different VAOs for the same geometry - // Still mulling over the best way to solve this one.. - // will likely need to modify the shader attribute locations at run time! - let vaos = geometry.glVertexArrayObjects[this.CONTEXT_UID]; - - if (!vaos) - { - geometry.glVertexArrayObjects[this.CONTEXT_UID] = vaos = {}; - } - - const vao = vaos[shader.program.id] || this.initGeometryVao(geometry, shader.program); - - this._activeGeometry = geometry; - - if (this._activeVao !== vao) - { - this._activeVao = vao; - - if (this.hasVao) - { - gl.bindVertexArray(vao); - } - else - { - this.activateVao(geometry, shader.program); - } - } - - // TODO - optimise later! - // don't need to loop through if nothing changed! - // maybe look to add an 'autoupdate' to geometry? - this.updateBuffers(); - } - - reset() - { - this.unbind(); - } - - updateBuffers() - { - const geometry = this._activeGeometry; - const gl = this.gl; - - for (let i = 0; i < geometry.buffers.length; i++) - { - const buffer = geometry.buffers[i]; - - const glBuffer = buffer._glBuffers[this.CONTEXT_UID]; - - if (buffer._updateID !== glBuffer.updateID) - { - glBuffer.updateID = buffer._updateID; - - // TODO can cache this on buffer! maybe added a getter / setter? - const type = buffer.index ? gl.ELEMENT_ARRAY_BUFFER : gl.ARRAY_BUFFER; - const drawType = buffer.static ? gl.STATIC_DRAW : gl.DYNAMIC_DRAW; - - gl.bindBuffer(type, glBuffer.buffer); - - if (glBuffer.byteLength >= buffer.data.byteLength) - { - // offset is always zero for now! - gl.bufferSubData(type, 0, buffer.data); - } - else - { - gl.bufferData(type, buffer.data, drawType); - } - } - } - } - - checkCompatability(geometry, program) - { - // geometry must have at least all the attributes that the shader requires. - const geometryAttributes = geometry.attributes; - const shaderAttributes = program.attributeData; - - for (const j in shaderAttributes) - { - if (!geometryAttributes[j]) - { - throw new Error(`shader and geometry incompatible, geometry missing the "${j}" attribute`); - } - } - } - - /** - * Creates a Vao with the same structure as the geometry and stores it on the geometry. - * @private - * @param {PIXI.Geometry} geometry instance of geometry to to generate Vao for - */ - initGeometryVao(geometry, program) - { - this.checkCompatability(geometry, program); - - const gl = this.gl; - const CONTEXT_UID = this.CONTEXT_UID; - const buffers = geometry.buffers; - const attributes = geometry.attributes; - - const tempStride = {}; - const tempStart = {}; - - for (const j in buffers) - { - tempStride[j] = 0; - tempStart[j] = 0; - } - - for (const j in attributes) - { - if (!attributes[j].size && program.attributeData[j]) - { - attributes[j].size = program.attributeData[j].size; - } - - tempStride[attributes[j].buffer] += attributes[j].size * byteSizeMap[attributes[j].type]; - } - - for (const j in attributes) - { - const attribute = attributes[j]; - const attribSize = attribute.size; - - if (attribute.stride === undefined) - { - if (tempStride[attribute.buffer] === attribSize * byteSizeMap[attribute.type]) - { - attribute.stride = 0; - } - else - { - attribute.stride = tempStride[attribute.buffer]; - } - } - - if (attribute.start === undefined) - { - attribute.start = tempStart[attribute.buffer]; - - tempStart[attribute.buffer] += attribSize * byteSizeMap[attribute.type]; - } - } - - // first update - and create the buffers! - // only create a gl buffer if it actually gets - for (let i = 0; i < buffers.length; i++) - { - const buffer = buffers[i]; - - if (!buffer._glBuffers[CONTEXT_UID]) - { - buffer._glBuffers[CONTEXT_UID] = new GLBuffer(gl.createBuffer()); - } - } - - // TODO - maybe make this a data object? - // lets wait to see if we need to first! - const vao = gl.createVertexArray(); - - gl.bindVertexArray(vao); - - this.activateVao(geometry, program); - - gl.bindVertexArray(null); - - // add it to the cache! - geometry.glVertexArrayObjects[this.CONTEXT_UID][program.id] = vao; - - return vao; - } - - activateVao(geometry, program) - { - const gl = this.gl; - const CONTEXT_UID = this.CONTEXT_UID; - const buffers = geometry.buffers; - const attributes = geometry.attributes; - - if (geometry.indexBuffer) - { - // first update the index buffer if we have one.. - gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, geometry.indexBuffer._glBuffers[CONTEXT_UID].buffer); - } - - let lastBuffer = null; - - // add a new one! - for (const j in attributes) - { - const attribute = attributes[j]; - const buffer = buffers[attribute.buffer]; - const glBuffer = buffer._glBuffers[CONTEXT_UID]; - - if (program.attributeData[j]) - { - if (lastBuffer !== glBuffer) - { - gl.bindBuffer(gl.ARRAY_BUFFER, glBuffer.buffer); - - lastBuffer = glBuffer; - } - - const location = program.attributeData[j].location; - - // TODO introduce state again - // we can optimise this for older devices that have no VAOs - gl.enableVertexAttribArray(location); - - gl.vertexAttribPointer(location, - attribute.size, - attribute.type || gl.FLOAT, - attribute.normalized, - attribute.stride, - attribute.start); - - if (attribute.instance) - { - // TODO calculate instance count based of this... - if (this.hasInstance) - { - gl.vertexAttribDivisor(location, 1); - } - else - { - throw new Error('geometry error, GPU Instancing is not supported on this device'); - } - } - } - } - } - - draw(type, size, start, instanceCount) - { - const gl = this.gl; - const geometry = this._activeGeometry; - - // TODO.. this should not change so maybe cache the function? - - if (geometry.indexBuffer) - { - if (geometry.instanced) - { - /* eslint-disable max-len */ - gl.drawElementsInstanced(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2, instanceCount || 1); - /* eslint-enable max-len */ - } - else - { - gl.drawElements(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2); - } - } - else - if (geometry.instanced) - { - // TODO need a better way to calculate size.. - gl.drawArraysInstanced(type, start, size || geometry.getSize(), instanceCount || 1); - } - else - { - gl.drawArrays(type, start, size || geometry.getSize()); - } - - return this; - } - - unbind() - { - this.gl.bindVertexArray(null); - this._activeVao = null; - this._activeGeometry = null; - } -} diff --git a/packages/core/src/renderers/systems/geometry/setVertexAttribArrays.js b/packages/core/src/renderers/systems/geometry/setVertexAttribArrays.js deleted file mode 100644 index 23b6959..0000000 --- a/packages/core/src/renderers/systems/geometry/setVertexAttribArrays.js +++ /dev/null @@ -1,54 +0,0 @@ -// var GL_MAP = {}; - -/** - * @param gl {WebGLRenderingContext} The current WebGL context - * @param attribs {*} - * @param state {*} - */ -export default function setVertexAttribArrays(gl, attribs, state) -{ - let i; - - if (state) - { - const tempAttribState = state.tempAttribState; - const attribState = state.attribState; - - for (i = 0; i < tempAttribState.length; i++) - { - tempAttribState[i] = false; - } - - // set the new attribs - for (i = 0; i < attribs.length; i++) - { - tempAttribState[attribs[i].attribute.location] = true; - } - - for (i = 0; i < attribState.length; i++) - { - if (attribState[i] !== tempAttribState[i]) - { - attribState[i] = tempAttribState[i]; - - if (state.attribState[i]) - { - gl.enableVertexAttribArray(i); - } - else - { - gl.disableVertexAttribArray(i); - } - } - } - } - else - { - for (i = 0; i < attribs.length; i++) - { - const attrib = attribs[i]; - - gl.enableVertexAttribArray(attrib.attribute.location); - } - } -} diff --git a/packages/core/src/AbstractRenderer.js b/packages/core/src/AbstractRenderer.js new file mode 100644 index 0000000..64b2eb1 --- /dev/null +++ b/packages/core/src/AbstractRenderer.js @@ -0,0 +1,353 @@ +import { hex2string, hex2rgb } from '@pixi/utils'; +import { Matrix, Rectangle } from '@pixi/math'; +import { RENDERER_TYPE } from '@pixi/constants'; +import { settings } from '@pixi/settings'; +import { Container } from '@pixi/display'; +import RenderTexture from './renderTexture/RenderTexture'; +import EventEmitter from 'eventemitter3'; + +const tempMatrix = new Matrix(); + +/** + * 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 EventEmitter + * @memberof PIXI + */ +export default class AbstractRenderer extends EventEmitter +{ + // eslint-disable-next-line valid-jsdoc + /** + * @param {string} system - The name of the system this renderer is for. + * @param {object} [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.transparent=false] - If the render view is transparent, default false + * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false + * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. The + * resolution of the renderer retina would be 2. + * @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 {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when rendering, + * stopping pixel interpolation. + */ + constructor(system, options, arg2, arg3) + { + super(); + + // Support for constructor(system, screenWidth, screenHeight, options) + if (typeof options === 'number') + { + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + }, arg3); + } + + // 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 = RENDERER_TYPE.UNKNOWN; + + /** + * Measurements of the screen. (0, 0, screenWidth, screenHeight) + * + * Its safe to use as filterArea or hitArea for 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 1 + */ + this.resolution = options.resolution || settings.RESOLUTION; + + /** + * Whether the render view is transparent + * + * @member {boolean} + */ + this.transparent = options.transparent; + + /** + * Whether css dimensions of canvas view should be resized to screen dimensions automatically + * + * @member {boolean} + */ + this.autoResize = options.autoResize || false; + + /** + * Tracks the blend modes useful for this renderer. + * + * @member {object} + */ + this.blendModes = null; + + /** + * 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; + + /** + * If true PixiJS will Math.floor() x/y values when rendering, stopping pixel interpolation. + * Handy for crisp pixel art and speed on legacy devices. + * + * @member {boolean} + */ + this.roundPixels = options.roundPixels; + + /** + * The background color as a number. + * + * @member {number} + * @private + */ + this._backgroundColor = 0x000000; + + /** + * The background color as an [R, G, B] array. + * + * @member {number[]} + * @private + */ + this._backgroundColorRgba = [0, 0, 0, 0]; + + /** + * The background color as a string. + * + * @member {string} + * @private + */ + this._backgroundColorString = '#000000'; + + this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter + + /** + * This temporary display object used as the parent of the currently being rendered item + * + * @member {PIXI.DisplayObject} + * @private + */ + this._tempDisplayObjectParent = new Container(); + + /** + * The last root object that the renderer tried to render. + * + * @member {PIXI.DisplayObject} + * @private + */ + this._lastObjectRendered = this._tempDisplayObjectParent; + + /** + * Collection of plugins. + * @readonly + * @member {object} + */ + this.plugins = {}; + } + + /** + * Initialize the plugins. + * + * @protected + * @param {object} staticMap - The dictionary of staticly saved plugins. + */ + initPlugins(staticMap) + { + 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() + { + return this.view.width; + } + + /** + * Same as view.height, actual number of pixels in the canvas by vertical + * + * @member {number} + * @readonly + * @default 600 + */ + get height() + { + return this.view.height; + } + + /** + * Resizes the screen and canvas to the specified width and height + * Canvas dimensions are multiplied by resolution + * + * @param {number} screenWidth - the new width of the screen + * @param {number} screenHeight - the new height of the screen + */ + resize(screenWidth, screenHeight) + { + this.screen.width = screenWidth; + this.screen.height = screenHeight; + + this.view.width = screenWidth * this.resolution; + this.view.height = screenHeight * this.resolution; + + if (this.autoResize) + { + this.view.style.width = `${screenWidth}px`; + this.view.style.height = `${screenHeight}px`; + } + } + + /** + * 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 {PIXI.DisplayObject} displayObject - The displayObject the object will be generated from + * @param {number} scaleMode - Should be one of the scaleMode consts + * @param {number} resolution - The resolution / device pixel ratio of the texture being generated + * @param {PIXI.Rectangle} [region] - The region of the displayObject, that shall be rendered, + * if no region is specified, defaults to the local bounds of the displayObject. + * @return {PIXI.Texture} a texture of the graphics object + */ + generateTexture(displayObject, scaleMode, resolution, region) + { + region = region || displayObject.getLocalBounds(); + + // 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(region.width | 0, region.height | 0, scaleMode, resolution); + + tempMatrix.tx = -region.x; + tempMatrix.ty = -region.y; + + this.render(displayObject, renderTexture, false, tempMatrix, true); + + return renderTexture; + } + + /** + * Removes everything from the renderer and optionally removes the Canvas DOM element. + * + * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. + */ + destroy(removeView) + { + 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); + } + + this.plugins = null; + + this.type = RENDERER_TYPE.UNKNOWN; + + this.view = null; + + this.screen = null; + + this.resolution = 0; + + this.transparent = false; + + this.autoResize = false; + + this.blendModes = null; + + this.options = null; + + this.preserveDrawingBuffer = false; + this.clearBeforeRender = false; + + this.roundPixels = false; + + this._backgroundColor = 0; + this._backgroundColorRgba = null; + this._backgroundColorString = null; + + this._tempDisplayObjectParent = null; + this._lastObjectRendered = null; + } + + /** + * The background color to fill if not transparent + * + * @member {number} + */ + get backgroundColor() + { + return this._backgroundColor; + } + + set backgroundColor(value) // eslint-disable-line require-jsdoc + { + this._backgroundColor = value; + this._backgroundColorString = hex2string(value); + hex2rgb(value, this._backgroundColorRgba); + } +} diff --git a/packages/core/src/Renderer.js b/packages/core/src/Renderer.js new file mode 100644 index 0000000..2227e78 --- /dev/null +++ b/packages/core/src/Renderer.js @@ -0,0 +1,338 @@ +import AbstractRenderer from './AbstractRenderer'; +import { sayHello } from '@pixi/utils'; +import MaskSystem from './mask/MaskSystem'; +import StencilSystem from './mask/StencilSystem'; +import FilterSystem from './filters/FilterSystem'; +import FramebufferSystem from './framebuffer/FramebufferSystem'; +import RenderTextureSystem from './renderTexture/RenderTextureSystem'; +import TextureSystem from './textures/TextureSystem'; +import ProjectionSystem from './projection/ProjectionSystem'; +import StateSystem from './state/StateSystem'; +import GeometrySystem from './geometry/GeometrySystem'; +import ShaderSystem from './shader/ShaderSystem'; +import ContextSystem from './context/ContextSystem'; +import BatchSystem from './batch/BatchSystem'; +import TextureGCSystem from './textures/TextureGCSystem'; +import { RENDERER_TYPE } from '@pixi/constants'; +import UniformGroup from './shader/UniformGroup'; +import { Matrix } from '@pixi/math'; +import Runner from 'mini-runner'; + +/** + * 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 Render works by automatically managing webGLBatchs. + * 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 :) + * + * @class + * @memberof PIXI + * @extends PIXI.AbstractRenderer + */ +export default class Renderer extends AbstractRenderer +{ + // eslint-disable-next-line valid-jsdoc + /** + * + * @param {object} [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.transparent=false] - If the render view is transparent, default false + * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false + * @param {boolean} [options.antialias=false] - sets antialias. If not available natively then FXAA + * antialiasing is used + * @param {boolean} [options.forceFXAA=false] - forces FXAA antialiasing to be used over native. + * FXAA is faster, but may not always look as great + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. + * The resolution of the renderer retina would be 2. + * @param {boolean} [options.clearBeforeRender=true] - This sets if the renderer will clear + * the canvas or not before the new render pass. If you wish to set this to false, you *must* set + * preserveDrawingBuffer to `true`. + * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, + * enable this if you need to call toDataUrl on the webgl context. + * @param {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when + * rendering, stopping pixel interpolation. + * @param {number} [options.backgroundColor=0x000000] - The background color of the rendered area + * (shown if not transparent). + * @param {boolean} [options.legacy=false] - If true PixiJS will aim to ensure compatibility + * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. + * @param {string} [options.powerPreference] - Parameter passed to webgl context, set to "high-performance" + * for devices with dual graphics card + */ + constructor(options, arg2, arg3) + { + super('WebGL', options, arg2, arg3); + + /** + * The type of this renderer as a standardised const + * + * @member {number} + * @see PIXI.RENDERER_TYPE + */ + this.type = RENDERER_TYPE.WEBGL; + + // this will be set by the contextSystem (this.context) + this.gl = null; + this.CONTEXT_UID = 0; + this.legacy = !!options.legacy; + + // TODO legacy! + + // runners! + this.runners = { + destroy: new Runner('destroy'), + contextChange: new Runner('contextChange', 1), + reset: new Runner('reset'), + update: new Runner('update'), + postrender: new Runner('postrender'), + prerender: new Runner('prerender'), + resize: new Runner('resize', 2), + }; + + 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(GeometrySystem, 'geometry') + .addSystem(FramebufferSystem, 'framebuffer') + .addSystem(StencilSystem, 'stencil') + .addSystem(ProjectionSystem, 'projection') + .addSystem(TextureGCSystem, 'textureGC') + .addSystem(FilterSystem, 'filter') + .addSystem(RenderTextureSystem, 'renderTexture') + .addSystem(BatchSystem, 'batch'); + + this.initPlugins(Renderer.__plugins); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ + if (options.context) + { + this.context.initFromContext(options.context); + } + else + { + this.context.initFromOptions({ + alpha: this.transparent, + antialias: options.antialias, + premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', + stencil: true, + 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); + } + + /** + * Add a new system to the renderer. + * @param {class} ClassRef - Class reference + * @param {string} [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. + * @return {PIXI.Renderer} Return instance of renderer + */ + addSystem(ClassRef, name) + { + if (!name) + { + name = ClassRef.name; + } + + // TODO - read name from class.name.. + + /* + if(name.includes('System')) + { + name = name.replace('System', ''); + name = name.charAt(0).toLowerCase() + name.slice(1); + } + */ + + const system = new ClassRef(this); + + if (this[name]) + { + throw new Error(`Whoops! ${name} is already a manger`); + } + + this[name] = system; + + for (const i in this.runners) + { + this.runners[i].add(system); + } + + return this; + + /** + * 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. + */ + } + + /** + * Renders the object to its webGL view + * + * @param {PIXI.DisplayObject} displayObject - the object to be rendered + * @param {PIXI.RenderTexture} renderTexture - The render texture to render to. + * @param {boolean} [clear] - Should the canvas be cleared before the new render + * @param {PIXI.Transform} [transform] - A transform to apply to the render texture before rendering. + * @param {boolean} [skipUpdateTransform] - Should we skip the update transform pass? + */ + render(displayObject, renderTexture, clear, transform, skipUpdateTransform) + { + // can be handy to know! + this.renderingToScreen = !renderTexture; + + this.runners.prerender.run(); + this.emit('prerender'); + + // 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.parent; + + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = 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.run(); + + this.emit('postrender'); + } + + /** + * Resizes the webGL view to the specified width and height. + * + * @param {number} screenWidth - the new width of the screen + * @param {number} screenHeight - the new height of the screen + */ + resize(screenWidth, screenHeight) + { + AbstractRenderer.prototype.resize.call(this, screenWidth, screenHeight); + + this.runners.resize.run(screenWidth, screenHeight); + } + + /** + * Resets the WebGL state so you can render things however you fancy! + * + * @return {PIXI.Renderer} Returns itself. + */ + reset() + { + this.runners.reset.run(); + + return this; + } + + clear() + { + this.framebuffer.bind(); + this.framebuffer.clear(); + } + + /** + * Removes everything from the renderer (event listeners, spritebatch, etc...) + * + * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. + * See: https://github.com/pixijs/pixi.js/issues/2233 + */ + destroy(removeView) + { + this.runners.destroy.run(); + + // call base destroy + super.destroy(removeView); + + // TODO nullify all the managers.. + this.gl = null; + } + + /** + * 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 + * builds and excluding plugins. + * @name PIXI.Renderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. + */ + + /** + * Adds a plugin to the renderer. + * + * @method + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + static registerPlugin(pluginName, ctor) + { + Renderer.__plugins = Renderer.__plugins || {}; + Renderer.__plugins[pluginName] = ctor; + } +} diff --git a/packages/core/src/System.js b/packages/core/src/System.js new file mode 100644 index 0000000..05a10c8 --- /dev/null +++ b/packages/core/src/System.js @@ -0,0 +1,42 @@ +/** + * System is a base class used for extending systems used by the {@link PIXI.Renderer} + * @see PIXI.Renderer#addSystem + * @class + * @memberof PIXI + */ +export default class System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + /** + * The renderer this manager works for. + * + * @member {PIXI.Renderer} + */ + this.renderer = renderer; + + this.renderer.runners.contextChange.add(this); + } + + /** + * Generic method called when there is a WebGL context change. + * + */ + contextChange() + { + // do some codes init! + } + + /** + * Generic destroy methods to be overridden by the subclass + * + */ + destroy() + { + this.renderer.runners.contextChange.remove(this); + this.renderer = null; + } +} diff --git a/packages/core/src/batch/BatchSystem.js b/packages/core/src/batch/BatchSystem.js new file mode 100644 index 0000000..b04c249 --- /dev/null +++ b/packages/core/src/batch/BatchSystem.js @@ -0,0 +1,68 @@ +import System from '../System'; +import ObjectRenderer from './ObjectRenderer'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class BatchSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + super(renderer); + + /** + * An empty renderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.emptyRenderer = new ObjectRenderer(renderer); + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.currentRenderer = this.emptyRenderer; + } + + /** + * Changes the current renderer to the one given in parameter + * + * @param {PIXI.ObjectRenderer} objectRenderer - The object renderer to use. + */ + setObjectRenderer(objectRenderer) + { + if (this.currentRenderer === objectRenderer) + { + return; + } + + this.currentRenderer.stop(); + this.currentRenderer = objectRenderer; + + this.renderer.state.setState(objectRenderer.state); + + this.currentRenderer.start(); + } + + /** + * This should be called if you wish to do some custom rendering + * It will basically render anything that may be batched up such as sprites + * + */ + flush() + { + this.setObjectRenderer(this.emptyRenderer); + } + + reset() + { + this.setObjectRenderer(this.emptyRenderer); + } +} diff --git a/packages/core/src/batch/ObjectRenderer.js b/packages/core/src/batch/ObjectRenderer.js new file mode 100644 index 0000000..0c851a9 --- /dev/null +++ b/packages/core/src/batch/ObjectRenderer.js @@ -0,0 +1,48 @@ +import System from '../System'; + +/** + * Base for a common object renderer that can be used as a system renderer plugin. + * + * @class + * @extends PIXI.System + * @memberof PIXI + */ +export default class ObjectRenderer extends System +{ + /** + * Starts the renderer and sets the shader + * + */ + start() + { + // set the shader.. + } + + /** + * Stops the renderer + * + */ + stop() + { + this.flush(); + } + + /** + * Stub method for rendering content and emptying the current batch. + * + */ + flush() + { + // flush! + } + + /** + * Renders an object + * + * @param {PIXI.DisplayObject} object - The object to render. + */ + render(object) // eslint-disable-line no-unused-vars + { + // render the object + } +} diff --git a/packages/core/src/context/ContextSystem.js b/packages/core/src/context/ContextSystem.js new file mode 100644 index 0000000..301aab4 --- /dev/null +++ b/packages/core/src/context/ContextSystem.js @@ -0,0 +1,186 @@ +import System from '../System'; +import { settings } from '@pixi/settings'; + +let CONTEXT_UID = 0; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class ContextSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.webGLVersion = 1; + + this.handleContextLost = this.handleContextLost.bind(this); + this.handleContextRestored = this.handleContextRestored.bind(this); + + this.extensions = {}; + + renderer.view.addEventListener('webglcontextlost', this.handleContextLost, false); + renderer.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + } + + get isLost() + { + return (!this.gl || this.gl.isContextLost()); + } + + contextChange(gl) + { + this.gl = gl; + + // restore a context if it was previously lost + if (gl.isContextLost() && gl.getExtension('WEBGL_lose_context')) + { + gl.getExtension('WEBGL_lose_context').restoreContext(); + } + } + + initFromContext(gl) + { + this.gl = gl; + this.validateContext(gl); + this.renderer.gl = gl; + this.renderer.CONTEXT_UID = CONTEXT_UID++; + this.renderer.runners.contextChange.run(gl); + } + + initFromOptions(options) + { + const gl = this.createContext(this.renderer.view, options); + + this.initFromContext(gl); + } + + /** + * Helper class to create a webGL Context + * + * @class + * @memberof PIXI.glCore + * @param canvas {HTMLCanvasElement} the canvas element that we will get the context from + * @param options {Object} An options object that gets passed in to the canvas element containing the context attributes, + * see https://developer.mozilla.org/en/docs/Web/API/HTMLCanvasElement/getContext for the options available + * @return {WebGLRenderingContext} the WebGL context + */ + createContext(canvas, options) + { + let gl; + + if (settings.PREFER_WEBGL_2) + { + gl = canvas.getContext('webgl2', options); + } + + if (gl) + { + this.webGLVersion = 2; + } + else + { + this.webGLVersion = 1; + + gl = canvas.getContext('webgl', options) + || canvas.getContext('experimental-webgl', options); + + if (!gl) + { + // fail, not able to get a context + throw new Error('This browser does not support webGL. Try using the canvas renderer'); + } + } + + this.gl = gl; + + this.getExtensions(); + + return gl; + } + + getExtensions() + { + // time to set up default etensions that pixi uses.. + const gl = this.gl; + const extensions = this.extensions; + + if (this.webGLVersion === 1) + { + extensions.drawBuffers = gl.getExtension('WEBGL_draw_buffers'); + extensions.depthTexture = gl.getExtension('WEBKIT_WEBGL_depth_texture'); + extensions.floatTexture = gl.getExtension('OES_texture_float'); + extensions.loseContext = gl.getExtension('WEBGL_lose_context'); + + extensions.vertexArrayObject = gl.getExtension('OES_vertex_array_object') + || gl.getExtension('MOZ_OES_vertex_array_object') + || gl.getExtension('WEBKIT_OES_vertex_array_object'); + } + + // we don't use any specific WebGL 2 ones yet! + } + + /** + * Handles a lost webgl context + * + * @private + * @param {WebGLContextEvent} event - The context lost event. + */ + handleContextLost(event) + { + event.preventDefault(); + } + + /** + * Handles a restored webgl context + * + * @private + */ + handleContextRestored() + { + this.renderer.runners.contextChange.run(this.gl); + } + + destroy() + { + const view = this.renderer.view; + + // remove listeners + view.removeEventListener('webglcontextlost', this.handleContextLost); + view.removeEventListener('webglcontextrestored', this.handleContextRestored); + + this.gl.useProgram(null); + + if (this.extensions.loseContext) + { + this.extensions.loseContext.loseContext(); + } + } + + postrender() + { + this.gl.flush(); + } + + validateContext(gl) + { + const attributes = gl.getContextAttributes(); + + // this is going to be fairly simple for now.. but at least we have room to grow! + if (!attributes.stencil) + { + /* eslint-disable max-len */ + + /* eslint-disable no-console */ + console.warn('Provided WebGL context does not have a stencil buffer, masks may not render correctly'); + /* eslint-enable no-console */ + + /* eslint-enable max-len */ + } + } +} diff --git a/packages/core/src/filters/Filter.js b/packages/core/src/filters/Filter.js new file mode 100644 index 0000000..9d8cad8 --- /dev/null +++ b/packages/core/src/filters/Filter.js @@ -0,0 +1,140 @@ +import Shader from '../shader/Shader'; +import Program from '../shader/Program'; +import State from '../state/State'; +import { settings } from '@pixi/settings'; +// import extractUniformsFromSrc from './extractUniformsFromSrc'; +import defaultVertex from './defaultFilter.vert'; +import defaultFragment from './defaultFilter.frag'; + +// let math = require('../../math'); +/** + * @class + * @memberof PIXI + * @extends PIXI.Shader + */ +export default class Filter extends Shader +{ + /** + * @param {string} [vertexSrc] - The source of the vertex shader. + * @param {string} [fragmentSrc] - The source of the fragment shader. + * @param {object} [uniforms] - Custom uniforms to use to augment the built-in ones. + */ + constructor(vertexSrc, fragmentSrc, uniforms) + { + const program = Program.from(vertexSrc, fragmentSrc); + + super(program, uniforms); + + /** + * The padding of the filter. Some filters require extra space to breath such as a blur. + * Increasing this will add extra width and height to the bounds of the object that the + * filter is applied to. + * + * @member {number} + */ + this.padding = 0; + + /** + * The resolution of the filter. Setting this to be lower will lower the quality but + * increase the performance of the filter. + * + * @member {number} + */ + this.resolution = settings.RESOLUTION; + + /** + * If enabled is true the filter is applied, if false it will not. + * + * @member {boolean} + */ + this.enabled = true; + + /** + * If enabled, PixiJS will fit the filter area into boundaries for better performance. + * Switch it off if it does not work for specific shader. + * + * @member {boolean} + */ + this.autoFit = true; + + /** + * Legacy filters use position and uvs from attributes + * @member {boolean} + * @readonly + */ + this.legacy = !!this.program.attributeData.aTextureCoord; + + /** + * the webGL state the filter requires to render + * @member {PIXI.State} + */ + this.state = new State(); + } + + /** + * Applies the filter + * + * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + * @param {boolean} clear - Should the output be cleared before rendering to it + * @param {object} [currentState] - It's current state of filter. + * There are some useful properties in the currentState : + * target, filters, sourceFrame, destinationFrame, renderTarget, resolution + */ + apply(filterManager, input, output, clear, currentState, derp) // eslint-disable-line no-unused-vars + { + // do as you please! + + filterManager.applyFilter(this, input, output, clear, currentState, derp); + + // or just do a regular render.. + } + + /** + * Sets the blendmode of the filter + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL + */ + get blendMode() + { + return this.state.blendMode; + } + + set blendMode(value) // eslint-disable-line require-jsdoc + { + this.state.blendMode = value; + } + + /** + * The default vertex shader source + * + * @static + * @constant + */ + static get defaultVertexSrc() + { + return defaultVertex; + } + + /** + * The default fragment shader source + * + * @static + * @constant + */ + static get defaultFragmentSrc() + { + return defaultFragment; + } +} + +/** + * Used for caching shader IDs + * + * @static + * @private + */ +Filter.SOURCE_KEY_MAP = {}; + diff --git a/packages/core/src/filters/FilterSystem.js b/packages/core/src/filters/FilterSystem.js new file mode 100644 index 0000000..3174318 --- /dev/null +++ b/packages/core/src/filters/FilterSystem.js @@ -0,0 +1,431 @@ +import System from '../System'; + +import RenderTexture from '../renderTexture/RenderTexture'; +import Quad from '../utils/Quad'; +import QuadUv from '../utils/QuadUv'; +import { Rectangle } from '@pixi/math'; +import * as filterTransforms from './filterTransforms'; +import bitTwiddle from 'bit-twiddle'; +import UniformGroup from '../shader/UniformGroup'; +import { DRAW_MODES } from '@pixi/constants'; + +// +/** + * @ignore + * @class + */ +class FilterState +{ + /** + * + */ + constructor() + { + this.renderTexture = null; + this.sourceFrame = new Rectangle(); + this.destinationFrame = new Rectangle(); + this.filters = []; + this.target = null; + this.legacy = false; + this.resolution = 1; + } +} + +/** + * @class + * @memberof PIXI.systems + * @extends PIXI.System + */ +export default class FilterSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + /** + * stores a bunch of PO2 textures used for filtering + * @type {Object} + */ + this.texturePool = {}; + + /** + * a pool for storing filter states, save us creating new ones each tick + * @type {Array} + */ + this.statePool = []; + + /** + * A very simple geometry used when drawing a filter effect to the screen + * @type {Quad} + */ + this.quad = new Quad(); + + this.quadUv = new QuadUv(); + + /** + * Temporary rect for maths + * @type {PIXI.Rectangle} + */ + this.tempRect = new Rectangle(); + + this.activeState = {}; + + /** + * this uniform group is attached to filter uniforms when used + * @type {UniformGroup} + */ + this.globalUniforms = new UniformGroup({ + sourceFrame: this.tempRect, + destinationFrame: this.tempRect, + + // legacy variables + filterArea: new Float32Array(4), + filterClamp: new Float32Array(4), + }, true); + } + + /** + * Adds a new filter to the System. + * + * @param {PIXI.DisplayObject} target - The target of the filter to render. + * @param {PIXI.Filter[]} filters - The filters to apply. + */ + push(target, filters) + { + const renderer = this.renderer; + const filterStack = this.renderer.renderTexture.defaultFilterStack; + const state = this.statePool.pop() || new FilterState(); + + let resolution = filters[0].resolution; + let padding = filters[0].padding; + let autoFit = filters[0].autoFit; + let legacy = filters[0].legacy; + + for (let i = 1; i < filters.length; i++) + { + const filter = filters[i]; + + // lets use the lowest resolution.. + resolution = Math.min(resolution, filter.resolution); + // and the largest amount of padding! + padding = Math.max(padding, filter.padding); + // only auto fit if all filters are autofit + autoFit = autoFit || filter.autoFit; + + legacy = legacy || filter.legacy; + } + + filterStack.push(state); + + state.resolution = resolution; + + state.legacy = legacy; + + // round to whole number based on resolution + // TODO move that to the shader too? + state.sourceFrame = target.filterArea || target.getBounds(true); + + state.sourceFrame.pad(padding); + + if (autoFit) + { + state.sourceFrame.fit(this.renderer.renderTexture.destinationFrame); + } + + state.sourceFrame.round(resolution); + + state.renderTexture = this.getPotFilterTexture(state.sourceFrame.width, state.sourceFrame.height, resolution); + state.filters = filters; + + state.destinationFrame.width = state.renderTexture.width; + state.destinationFrame.height = state.renderTexture.height; + + state.renderTexture.filterFrame = state.sourceFrame; + renderer.renderTexture.bind(state.renderTexture, state.sourceFrame);// /, state.destinationFrame); + renderer.renderTexture.clear(); + } + + /** + * Pops off the filter and applies it. + * + */ + pop() + { + const renderer = this.renderer; + const filterStack = renderer.renderTexture.defaultFilterStack; + const state = filterStack.pop(); + const filters = state.filters; + + this.activeState = state; + + const globalUniforms = this.globalUniforms.uniforms; + + globalUniforms.sourceFrame = state.sourceFrame; + globalUniforms.destinationFrame = state.destinationFrame; + globalUniforms.resolution = state.resolution; + + // only update the rect if its legacy.. + if (state.legacy) + { + const filterArea = globalUniforms.filterArea; + const filterClamp = globalUniforms.filterClamp; + + filterArea[0] = state.destinationFrame.width; + filterArea[1] = state.destinationFrame.height; + filterArea[2] = state.sourceFrame.x; + filterArea[3] = state.sourceFrame.y; + + filterClamp[0] = 0.5 / state.resolution / state.destinationFrame.width; + filterClamp[1] = 0.5 / state.resolution / state.destinationFrame.height; + filterClamp[2] = (state.sourceFrame.width - 0.5) / state.resolution / state.destinationFrame.width; + filterClamp[3] = (state.sourceFrame.height - 0.5) / state.resolution / state.destinationFrame.height; + } + + this.globalUniforms.update(); + + const lastState = filterStack[filterStack.length - 1]; + + if (filters.length === 1) + { + filters[0].apply(this, state.renderTexture, lastState.renderTexture, false, state); + + this.returnFilterTexture(state.renderTexture); + } + else + { + let flip = state.renderTexture; + let flop = this.getPotFilterTexture( + flip.width, + flip.height, + state.resolution + ); + + flop.filterFrame = flip.filterFrame; + + let i = 0; + + for (i = 0; i < filters.length - 1; ++i) + { + filters[i].apply(this, flip, flop, true, state); + + const t = flip; + + flip = flop; + flop = t; + } + + filters[i].apply(this, flip, lastState.renderTexture, false, state); + + this.returnFilterTexture(flip); + this.returnFilterTexture(flop); + } + + this.statePool.push(state); + } + + /** + * Draws a filter. + * + * @param {PIXI.Filter} filter - The filter to draw. + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + * @param {boolean} clear - Should the output be cleared before rendering to it + */ + applyFilter(filter, input, output, clear) + { + const renderer = this.renderer; + + renderer.renderTexture.bind(output, output ? output.filterFrame : null); + + if (clear) + { + // gl.disable(gl.SCISSOR_TEST); + renderer.renderTexture.clear(); + // gl.enable(gl.SCISSOR_TEST); + } + + // set the uniforms.. + filter.uniforms.uSampler = input; + filter.uniforms.filterGlobals = this.globalUniforms; + + // TODO make it so that the order of this does not matter.. + // because it does at the moment cos of global uniforms. + // they need to get resynced + + renderer.state.setState(filter.state); + renderer.shader.bind(filter); + + if (filter.legacy) + { + this.quadUv.map(input._frame, input.filterFrame); + + renderer.geometry.bind(this.quadUv); + renderer.geometry.draw(DRAW_MODES.TRIANGLES); + } + else + { + renderer.geometry.bind(this.quad); + renderer.geometry.draw(DRAW_MODES.TRIANGLE_STRIP); + } + } + + /** + * Calculates the mapped matrix. + * + * TODO playing around here.. this is temporary - (will end up in the shader) + * this returns a matrix that will normalise map filter cords in the filter to screen space + * + * @param {PIXI.Matrix} outputMatrix - the matrix to output to. + * @return {PIXI.Matrix} The mapped matrix. + */ + calculateScreenSpaceMatrix(outputMatrix) + { + const currentState = this.activeState; + + return filterTransforms.calculateScreenSpaceMatrix( + outputMatrix, + currentState.sourceFrame, + currentState.destinationFrame + ); + } + + /** + * This will map the filter coord so that a texture can be used based on the transform of a sprite + * + * @param {PIXI.Matrix} outputMatrix - The matrix to output to. + * @param {PIXI.Sprite} sprite - The sprite to map to. + * @return {PIXI.Matrix} The mapped matrix. + */ + calculateSpriteMatrix(outputMatrix, sprite) + { + const currentState = this.activeState; + + return filterTransforms.calculateSpriteMatrix( + outputMatrix, + currentState.sourceFrame, + currentState.destinationFrame, + sprite + ); + } + + /** + * Destroys this Filter System. + * + * @param {boolean} [contextLost=false] context was lost, do not free shaders + * + */ + destroy(contextLost = false) + { + if (!contextLost) + { + this.emptyPool(); + } + else + { + this.texturePool = {}; + } + } + + /** + * Gets a Power-of-Two render texture. + * + * TODO move to a seperate class could be on renderer? + * also - could cause issue with multiple contexts? + * + * @private + * @param {WebGLRenderingContext} gl - The webgl rendering context + * @param {number} minWidth - The minimum width of the render target. + * @param {number} minHeight - The minimum height of the render target. + * @param {number} resolution - The resolution of the render target. + * @return {PIXI.RenderTarget} The new render target. + */ + getPotFilterTexture(minWidth, minHeight, resolution) + { + minWidth = bitTwiddle.nextPow2(minWidth); + minHeight = bitTwiddle.nextPow2(minHeight); + resolution = resolution || 1; + + const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + + if (!this.texturePool[key]) + { + this.texturePool[key] = []; + } + + let renderTexture = this.texturePool[key].pop(); + + if (!renderTexture) + { + // temporary bypass cache.. + // internally - this will cause a texture to be bound.. + renderTexture = RenderTexture.create({ + width: minWidth, + height: minHeight, + resolution, + }); + } + + return renderTexture; + } + + /** + * Gets extra render texture to use inside current filter + * + * @param {number} resolution resolution of the renderTexture + * @returns {PIXI.RenderTexture} + */ + getFilterTexture(resolution) + { + const rt = this.activeState.renderTexture; + + const filterTexture = this.getPotFilterTexture(rt.width, rt.height, resolution || rt.baseTexture.resolution); + + filterTexture.filterFrame = rt.filterFrame; + + return filterTexture; + } + + /** + * Frees a render texture back into the pool. + * + * @param {PIXI.RenderTarget} renderTexture - The renderTarget to free + */ + returnFilterTexture(renderTexture) + { + renderTexture.filterFrame = null; + + const base = renderTexture.baseTexture; + + const minWidth = base.width; + const minHeight = base.height; + + const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + + this.texturePool[key].push(renderTexture); + } + + /** + * Empties the texture pool. + * + */ + emptyPool() + { + for (const i in this.texturePool) + { + const textures = this.texturePool[i]; + + if (textures) + { + for (let j = 0; j < textures.length; j++) + { + textures[j].destroy(true); + } + } + } + + this.texturePool = {}; + } +} diff --git a/packages/core/src/filters/defaultFilter.frag b/packages/core/src/filters/defaultFilter.frag new file mode 100644 index 0000000..a14c1be --- /dev/null +++ b/packages/core/src/filters/defaultFilter.frag @@ -0,0 +1,22 @@ +varying vec2 vTextureCoord; +varying vec2 vFilterCoord; + +uniform sampler2D uSampler; +uniform sampler2D filterSampler; + +void main(void){ + vec4 masky = texture2D(filterSampler, vFilterCoord); + vec4 sample = texture2D(uSampler, vTextureCoord); + vec4 color; + if(mod(vFilterCoord.x, 1.0) > 0.5) + { + color = vec4(1.0, 0.0, 0.0, 1.0); + } + else + { + color = vec4(0.0, 1.0, 0.0, 1.0); + } + // gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0); + gl_FragColor = mix(sample, masky, 0.5); + gl_FragColor *= sample.a; +} diff --git a/packages/core/src/filters/defaultFilter.vert b/packages/core/src/filters/defaultFilter.vert new file mode 100644 index 0000000..e1febca --- /dev/null +++ b/packages/core/src/filters/defaultFilter.vert @@ -0,0 +1,14 @@ +attribute vec2 aVertexPosition; +attribute vec2 aTextureCoord; + +uniform mat3 projectionMatrix; +uniform mat3 filterMatrix; + +varying vec2 vTextureCoord; +varying vec2 vFilterCoord; + +void main(void){ + gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); + vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy; + vTextureCoord = aTextureCoord ; +} diff --git a/packages/core/src/filters/filterTransforms.js b/packages/core/src/filters/filterTransforms.js new file mode 100644 index 0000000..87a3cd6 --- /dev/null +++ b/packages/core/src/filters/filterTransforms.js @@ -0,0 +1,50 @@ +import { Matrix } from '@pixi/math'; + +/** + * Calculates the mapped matrix + * @param filterArea {Rectangle} The filter area + * @param sprite {Sprite} the target sprite + * @param outputMatrix {Matrix} @alvin + * @private + */ +// this returns a matrix that will normalise map filter cords in the filter to screen space +export function calculateScreenSpaceMatrix(outputMatrix, filterArea, textureSize) +{ + // TODO unwrap? + const mappedMatrix = outputMatrix.identity(); + + mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); + + mappedMatrix.scale(textureSize.width, textureSize.height); + + return mappedMatrix; +} + +export function calculateNormalizedScreenSpaceMatrix(outputMatrix, filterArea, textureSize) +{ + const mappedMatrix = outputMatrix.identity(); + + mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); + + const translateScaleX = (textureSize.width / filterArea.width); + const translateScaleY = (textureSize.height / filterArea.height); + + mappedMatrix.scale(translateScaleX, translateScaleY); + + return mappedMatrix; +} + +// this will map the filter coord so that a texture can be used based on the transform of a sprite +export function calculateSpriteMatrix(outputMatrix, filterArea, textureSize, sprite) +{ + const orig = sprite._texture.orig; + const mappedMatrix = outputMatrix.set(textureSize.width, 0, 0, textureSize.height, filterArea.x, filterArea.y); + const worldTransform = sprite.worldTransform.copyTo(Matrix.TEMP_MATRIX); + + worldTransform.invert(); + mappedMatrix.prepend(worldTransform); + mappedMatrix.scale(1.0 / orig.width, 1.0 / orig.height); + mappedMatrix.translate(sprite.anchor.x, sprite.anchor.y); + + return mappedMatrix; +} diff --git a/packages/core/src/filters/spriteMask/SpriteMaskFilter.js b/packages/core/src/filters/spriteMask/SpriteMaskFilter.js new file mode 100644 index 0000000..91697b7 --- /dev/null +++ b/packages/core/src/filters/spriteMask/SpriteMaskFilter.js @@ -0,0 +1,63 @@ +import Filter from '../Filter'; +import { Matrix } from '@pixi/math'; +import vertex from './spriteMaskFilter.vert'; +import fragment from './spriteMaskFilter.frag'; +import { default as TextureMatrix } from '../../textures/TextureMatrix'; + +/** + * The SpriteMaskFilter class + * + * @class + * @extends PIXI.Filter + * @memberof PIXI + */ +export default class SpriteMaskFilter extends Filter +{ + /** + * @param {PIXI.Sprite} sprite - the target sprite + */ + constructor(sprite) + { + const maskMatrix = new Matrix(); + + super(vertex, fragment); + + sprite.renderable = false; + + this.maskSprite = sprite; + this.maskMatrix = maskMatrix; + } + + /** + * Applies the filter + * + * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + */ + apply(filterManager, input, output) + { + const maskSprite = this.maskSprite; + const tex = this.maskSprite.texture; + + if (!tex.valid) + { + return; + } + if (!tex.transform) + { + // margin = 0.0, let it bleed a bit, shader code becomes easier + // assuming that atlas textures were made with 1-pixel padding + tex.transform = new TextureMatrix(tex, 0.0); + } + tex.transform.update(); + + this.uniforms.mask = tex; + this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite) + .prepend(tex.transform.mapCoord); + this.uniforms.alpha = maskSprite.worldAlpha; + this.uniforms.maskClamp = tex.transform.uClampFrame; + + filterManager.applyFilter(this, input, output); + } +} diff --git a/packages/core/src/filters/spriteMask/spriteMaskFilter.frag b/packages/core/src/filters/spriteMask/spriteMaskFilter.frag new file mode 100644 index 0000000..0e4aef8 --- /dev/null +++ b/packages/core/src/filters/spriteMask/spriteMaskFilter.frag @@ -0,0 +1,23 @@ +varying vec2 vMaskCoord; +varying vec2 vTextureCoord; + +uniform sampler2D uSampler; +uniform sampler2D mask; +uniform float alpha; +uniform vec4 maskClamp; + +void main(void) +{ + float clip = step(3.5, + step(maskClamp.x, vMaskCoord.x) + + step(maskClamp.y, vMaskCoord.y) + + step(vMaskCoord.x, maskClamp.z) + + step(vMaskCoord.y, maskClamp.w)); + + vec4 original = texture2D(uSampler, vTextureCoord); + vec4 masky = texture2D(mask, vMaskCoord); + + original *= (masky.r * masky.a * alpha * clip); + + gl_FragColor = original; +} diff --git a/packages/core/src/filters/spriteMask/spriteMaskFilter.vert b/packages/core/src/filters/spriteMask/spriteMaskFilter.vert new file mode 100644 index 0000000..d23a30c --- /dev/null +++ b/packages/core/src/filters/spriteMask/spriteMaskFilter.vert @@ -0,0 +1,16 @@ +attribute vec2 aVertexPosition; +attribute vec2 aTextureCoord; + +uniform mat3 projectionMatrix; +uniform mat3 otherMatrix; + +varying vec2 vMaskCoord; +varying vec2 vTextureCoord; + +void main(void) +{ + gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); + + vTextureCoord = aTextureCoord; + vMaskCoord = ( otherMatrix * vec3( aTextureCoord, 1.0) ).xy; +} diff --git a/packages/core/src/framebuffer/FrameBuffer.js b/packages/core/src/framebuffer/FrameBuffer.js new file mode 100644 index 0000000..84e9fcc --- /dev/null +++ b/packages/core/src/framebuffer/FrameBuffer.js @@ -0,0 +1,106 @@ +import Texture from '../textures/BaseTexture'; +import { FORMATS, TYPES } from '@pixi/constants'; + +/** + * Frame buffer + * @class + * @memberof PIXI + */ +export default class FrameBuffer +{ + constructor(width, height) + { + this.width = width || 100; + this.height = height || 100; + + this.stencil = false; + this.depth = false; + + this.dirtyId = 0; + this.dirtyFormat = 0; + this.dirtySize = 0; + + this.depthTexture = null; + this.colorTextures = []; + + this.glFrameBuffers = {}; + } + + get colorTexture() + { + return this.colorTextures[0]; + } + + addColorTexture(index, texture) + { + // TODO add some validation to the texture - same width / height etc? + this.colorTextures[index || 0] = texture || new Texture(null, { scaleMode: 0, + resolution: 1, + mipmap: false, + width: this.width, + height: this.height });// || new Texture(); + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + addDepthTexture(texture) + { + /* eslint-disable max-len */ + this.depthTexture = texture || new Texture(null, { scaleMode: 0, + resolution: 1, + width: this.width, + height: this.height, + mipmap: false, + format: FORMATS.DEPTH_COMPONENT, + type: TYPES.UNSIGNED_SHORT });// UNSIGNED_SHORT; + /* eslint-disable max-len */ + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + enableDepth() + { + this.depth = true; + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + enableStencil() + { + this.stencil = true; + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + resize(width, height) + { + if (width === this.width && height === this.height) return; + + this.width = width; + this.height = height; + + this.dirtyId++; + this.dirtySize++; + + for (let i = 0; i < this.colorTextures.length; i++) + { + this.colorTextures[i].setSize(width, height); + } + + if (this.depthTexture) + { + this.depthTexture.setSize(width, height); + } + } +} diff --git a/packages/core/src/framebuffer/FramebufferSystem.js b/packages/core/src/framebuffer/FramebufferSystem.js new file mode 100644 index 0000000..2cc2437 --- /dev/null +++ b/packages/core/src/framebuffer/FramebufferSystem.js @@ -0,0 +1,246 @@ +import System from '../System'; +import { Rectangle } from '@pixi/math'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class FramebufferSystem extends System +{ + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + contextChange() + { + this.gl = this.renderer.gl; + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + this.current = null; + this.viewport = new Rectangle(); + + this.drawBufferExtension = this.renderer.context.extensions.drawBuffers; + } + + bind(framebuffer, frame) + { + const gl = this.gl; + + this.current = framebuffer; + + if (framebuffer) + { + // TODO cacheing layer! + + const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID] || this.initFramebuffer(framebuffer); + + gl.bindFramebuffer(gl.FRAMEBUFFER, fbo.framebuffer); + // makesure all textures are unbound.. + + // now check for updates... + if (fbo.dirtyId !== framebuffer.dirtyId) + { + fbo.dirtyId = framebuffer.dirtyId; + + if (fbo.dirtyFormat !== framebuffer.dirtyFormat) + { + fbo.dirtyFormat = framebuffer.dirtyFormat; + this.updateFramebuffer(framebuffer); + } + else if (fbo.dirtySize !== framebuffer.dirtySize) + { + fbo.dirtySize = framebuffer.dirtySize; + this.resizeFramebuffer(framebuffer); + } + } + + for (let i = 0; i < framebuffer.colorTextures.length; i++) + { + if (framebuffer.colorTextures[i].texturePart) + { + this.renderer.texture.unbind(framebuffer.colorTextures[i].texture); + } + else + { + this.renderer.texture.unbind(framebuffer.colorTextures[i]); + } + } + + if (framebuffer.depthTexture) + { + this.renderer.texture.unbind(framebuffer.depthTexture); + } + + if (frame) + { + this.setViewport(frame.x, frame.y, frame.width, frame.height); + } + else + { + this.setViewport(0, 0, framebuffer.width, framebuffer.height); + } + } + else + { + gl.bindFramebuffer(gl.FRAMEBUFFER, null); + + if (frame) + { + this.setViewport(frame.x, frame.y, frame.width, frame.height); + } + else + { + this.setViewport(0, 0, this.renderer.width, this.renderer.height); + } + } + } + + setViewport(x, y, width, height) + { + const v = this.viewport; + + if (v.width !== width || v.height !== height || v.x !== x || v.y !== y) + { + v.x = x; + v.y = y; + v.width = width; + v.height = height; + + this.gl.viewport(x, y, width, height); + } + } + + get size() + { + if (this.current) + { + // TODO store temp + return { x: 0, y: 0, width: this.current.width, height: this.current.height }; + } + + return { x: 0, y: 0, width: this.renderer.width, height: this.renderer.height }; + } + + clear(r, g, b, a) + { + const gl = this.gl; + + // TODO clear color can be set only one right? + gl.clearColor(r, g, b, a); + gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); + } + + // private functions... + + initFramebuffer(framebuffer) + { + const gl = this.gl; + + // TODO - make this a class? + const fbo = { + framebuffer: gl.createFramebuffer(), + stencil: null, + dirtyId: 0, + dirtyFormat: 0, + dirtySize: 0, + }; + + framebuffer.glFrameBuffers[this.CONTEXT_UID] = fbo; + + return fbo; + } + + resizeFramebuffer(framebuffer) + { + const gl = this.gl; + + if (framebuffer.stencil || framebuffer.depth) + { + gl.bindRenderbuffer(gl.RENDERBUFFER, this.stencil); + gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); + } + } + + updateFramebuffer(framebuffer) + { + const gl = this.gl; + + const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID]; + + // bind the color texture + const colorTextures = framebuffer.colorTextures; + + let count = colorTextures.length; + + if (!this.drawBufferExtension) + { + count = Math.min(count, 1); + } + + const activeTextures = []; + + for (let i = 0; i < count; i++) + { + const texture = framebuffer.colorTextures[i]; + + if (texture.texturePart) + { + this.renderer.texture.bind(texture.texture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.COLOR_ATTACHMENT0 + i, + gl.TEXTURE_CUBE_MAP_NEGATIVE_X + texture.side, + texture.texture._glTextures[this.CONTEXT_UID].texture, + 0); + } + else + { + this.renderer.texture.bind(texture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.COLOR_ATTACHMENT0 + i, + gl.TEXTURE_2D, + texture._glTextures[this.CONTEXT_UID].texture, + 0); + } + + activeTextures.push(gl.COLOR_ATTACHMENT0 + i); + } + + if (this.drawBufferExtension && activeTextures.length > 1) + { + this.drawBufferExtension.drawBuffersWEBGL(activeTextures); + } + + if (framebuffer.depthTexture) + { + const depthTextureExt = this.renderer.context.extensions.depthTexture; + + if (depthTextureExt) + { + const depthTexture = framebuffer.depthTexture; + + this.renderer.texture.bind(depthTexture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.DEPTH_ATTACHMENT, + gl.TEXTURE_2D, + depthTexture._glTextures[this.CONTEXT_UID].texture, + 0); + } + } + + if (framebuffer.stencil || framebuffer.depth) + { + fbo.stencil = gl.createRenderbuffer(); + + gl.bindRenderbuffer(gl.RENDERBUFFER, fbo.stencil); + + // TODO.. this is depth AND stencil? + gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, fbo.stencil); + gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); + // fbo.enableStencil(); + } + } +} diff --git a/packages/core/src/geometry/GLBuffer.js b/packages/core/src/geometry/GLBuffer.js new file mode 100644 index 0000000..0477e8a --- /dev/null +++ b/packages/core/src/geometry/GLBuffer.js @@ -0,0 +1,9 @@ +export default class GLBuffer +{ + constructor(buffer) + { + this.buffer = buffer; + this.updateID = -1; + this.byteLength = -1; + } +} diff --git a/packages/core/src/geometry/GeometrySystem.js b/packages/core/src/geometry/GeometrySystem.js new file mode 100644 index 0000000..959f827 --- /dev/null +++ b/packages/core/src/geometry/GeometrySystem.js @@ -0,0 +1,388 @@ +import System from '../System'; +import GLBuffer from './GLBuffer'; + +const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class GeometrySystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this._activeGeometry = null; + this._activeVao = null; + + this.hasVao = true; + this.hasInstance = true; + } + + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + contextChange() + { + const gl = this.gl = this.renderer.gl; + + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + + // webgl2 + if (!gl.createVertexArray) + { + // webgl 1! + let nativeVaoExtension = this.renderer.context.extensions.vertexArrayObject; + + if (this.renderer.legacy) + { + nativeVaoExtension = null; + } + + if (nativeVaoExtension) + { + gl.createVertexArray = () => + nativeVaoExtension.createVertexArrayOES(); + + gl.bindVertexArray = (vao) => + nativeVaoExtension.bindVertexArrayOES(vao); + + gl.deleteVertexArray = (vao) => + nativeVaoExtension.deleteVertexArrayOES(vao); + } + else + { + this.hasVao = false; + gl.createVertexArray = () => + { + // empty + }; + + gl.bindVertexArray = () => + { + // empty + }; + + gl.deleteVertexArray = () => + { + // empty + }; + } + } + + if (!gl.vertexAttribDivisor) + { + const instanceExt = gl.getExtension('ANGLE_instanced_arrays'); + + if (instanceExt) + { + gl.vertexAttribDivisor = (a, b) => + instanceExt.vertexAttribDivisorANGLE(a, b); + + gl.drawElementsInstanced = (a, b, c, d, e) => + instanceExt.drawElementsInstancedANGLE(a, b, c, d, e); + + gl.drawArraysInstanced = (a, b, c, d) => + instanceExt.drawArraysInstancedANGLE(a, b, c, d); + } + else + { + this.hasInstance = false; + } + } + } + + /** + * Binds geometry so that is can be drawn. Creating a Vao if required + * @private + * @param {PIXI.Geometry} geometry instance of geometry to bind + */ + bind(geometry, shader) + { + shader = shader || this.renderer.shader.shader; + + const gl = this.gl; + + // not sure the best way to address this.. + // currently different shaders require different VAOs for the same geometry + // Still mulling over the best way to solve this one.. + // will likely need to modify the shader attribute locations at run time! + let vaos = geometry.glVertexArrayObjects[this.CONTEXT_UID]; + + if (!vaos) + { + geometry.glVertexArrayObjects[this.CONTEXT_UID] = vaos = {}; + } + + const vao = vaos[shader.program.id] || this.initGeometryVao(geometry, shader.program); + + this._activeGeometry = geometry; + + if (this._activeVao !== vao) + { + this._activeVao = vao; + + if (this.hasVao) + { + gl.bindVertexArray(vao); + } + else + { + this.activateVao(geometry, shader.program); + } + } + + // TODO - optimise later! + // don't need to loop through if nothing changed! + // maybe look to add an 'autoupdate' to geometry? + this.updateBuffers(); + } + + reset() + { + this.unbind(); + } + + updateBuffers() + { + const geometry = this._activeGeometry; + const gl = this.gl; + + for (let i = 0; i < geometry.buffers.length; i++) + { + const buffer = geometry.buffers[i]; + + const glBuffer = buffer._glBuffers[this.CONTEXT_UID]; + + if (buffer._updateID !== glBuffer.updateID) + { + glBuffer.updateID = buffer._updateID; + + // TODO can cache this on buffer! maybe added a getter / setter? + const type = buffer.index ? gl.ELEMENT_ARRAY_BUFFER : gl.ARRAY_BUFFER; + const drawType = buffer.static ? gl.STATIC_DRAW : gl.DYNAMIC_DRAW; + + gl.bindBuffer(type, glBuffer.buffer); + + if (glBuffer.byteLength >= buffer.data.byteLength) + { + // offset is always zero for now! + gl.bufferSubData(type, 0, buffer.data); + } + else + { + gl.bufferData(type, buffer.data, drawType); + } + } + } + } + + checkCompatability(geometry, program) + { + // geometry must have at least all the attributes that the shader requires. + const geometryAttributes = geometry.attributes; + const shaderAttributes = program.attributeData; + + for (const j in shaderAttributes) + { + if (!geometryAttributes[j]) + { + throw new Error(`shader and geometry incompatible, geometry missing the "${j}" attribute`); + } + } + } + + /** + * Creates a Vao with the same structure as the geometry and stores it on the geometry. + * @private + * @param {PIXI.Geometry} geometry instance of geometry to to generate Vao for + */ + initGeometryVao(geometry, program) + { + this.checkCompatability(geometry, program); + + const gl = this.gl; + const CONTEXT_UID = this.CONTEXT_UID; + const buffers = geometry.buffers; + const attributes = geometry.attributes; + + const tempStride = {}; + const tempStart = {}; + + for (const j in buffers) + { + tempStride[j] = 0; + tempStart[j] = 0; + } + + for (const j in attributes) + { + if (!attributes[j].size && program.attributeData[j]) + { + attributes[j].size = program.attributeData[j].size; + } + + tempStride[attributes[j].buffer] += attributes[j].size * byteSizeMap[attributes[j].type]; + } + + for (const j in attributes) + { + const attribute = attributes[j]; + const attribSize = attribute.size; + + if (attribute.stride === undefined) + { + if (tempStride[attribute.buffer] === attribSize * byteSizeMap[attribute.type]) + { + attribute.stride = 0; + } + else + { + attribute.stride = tempStride[attribute.buffer]; + } + } + + if (attribute.start === undefined) + { + attribute.start = tempStart[attribute.buffer]; + + tempStart[attribute.buffer] += attribSize * byteSizeMap[attribute.type]; + } + } + + // first update - and create the buffers! + // only create a gl buffer if it actually gets + for (let i = 0; i < buffers.length; i++) + { + const buffer = buffers[i]; + + if (!buffer._glBuffers[CONTEXT_UID]) + { + buffer._glBuffers[CONTEXT_UID] = new GLBuffer(gl.createBuffer()); + } + } + + // TODO - maybe make this a data object? + // lets wait to see if we need to first! + const vao = gl.createVertexArray(); + + gl.bindVertexArray(vao); + + this.activateVao(geometry, program); + + gl.bindVertexArray(null); + + // add it to the cache! + geometry.glVertexArrayObjects[this.CONTEXT_UID][program.id] = vao; + + return vao; + } + + activateVao(geometry, program) + { + const gl = this.gl; + const CONTEXT_UID = this.CONTEXT_UID; + const buffers = geometry.buffers; + const attributes = geometry.attributes; + + if (geometry.indexBuffer) + { + // first update the index buffer if we have one.. + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, geometry.indexBuffer._glBuffers[CONTEXT_UID].buffer); + } + + let lastBuffer = null; + + // add a new one! + for (const j in attributes) + { + const attribute = attributes[j]; + const buffer = buffers[attribute.buffer]; + const glBuffer = buffer._glBuffers[CONTEXT_UID]; + + if (program.attributeData[j]) + { + if (lastBuffer !== glBuffer) + { + gl.bindBuffer(gl.ARRAY_BUFFER, glBuffer.buffer); + + lastBuffer = glBuffer; + } + + const location = program.attributeData[j].location; + + // TODO introduce state again + // we can optimise this for older devices that have no VAOs + gl.enableVertexAttribArray(location); + + gl.vertexAttribPointer(location, + attribute.size, + attribute.type || gl.FLOAT, + attribute.normalized, + attribute.stride, + attribute.start); + + if (attribute.instance) + { + // TODO calculate instance count based of this... + if (this.hasInstance) + { + gl.vertexAttribDivisor(location, 1); + } + else + { + throw new Error('geometry error, GPU Instancing is not supported on this device'); + } + } + } + } + } + + draw(type, size, start, instanceCount) + { + const gl = this.gl; + const geometry = this._activeGeometry; + + // TODO.. this should not change so maybe cache the function? + + if (geometry.indexBuffer) + { + if (geometry.instanced) + { + /* eslint-disable max-len */ + gl.drawElementsInstanced(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2, instanceCount || 1); + /* eslint-enable max-len */ + } + else + { + gl.drawElements(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2); + } + } + else + if (geometry.instanced) + { + // TODO need a better way to calculate size.. + gl.drawArraysInstanced(type, start, size || geometry.getSize(), instanceCount || 1); + } + else + { + gl.drawArrays(type, start, size || geometry.getSize()); + } + + return this; + } + + unbind() + { + this.gl.bindVertexArray(null); + this._activeVao = null; + this._activeGeometry = null; + } +} diff --git a/packages/core/src/geometry/utils/setVertexAttribArrays.js b/packages/core/src/geometry/utils/setVertexAttribArrays.js new file mode 100644 index 0000000..23b6959 --- /dev/null +++ b/packages/core/src/geometry/utils/setVertexAttribArrays.js @@ -0,0 +1,54 @@ +// var GL_MAP = {}; + +/** + * @param gl {WebGLRenderingContext} The current WebGL context + * @param attribs {*} + * @param state {*} + */ +export default function setVertexAttribArrays(gl, attribs, state) +{ + let i; + + if (state) + { + const tempAttribState = state.tempAttribState; + const attribState = state.attribState; + + for (i = 0; i < tempAttribState.length; i++) + { + tempAttribState[i] = false; + } + + // set the new attribs + for (i = 0; i < attribs.length; i++) + { + tempAttribState[attribs[i].attribute.location] = true; + } + + for (i = 0; i < attribState.length; i++) + { + if (attribState[i] !== tempAttribState[i]) + { + attribState[i] = tempAttribState[i]; + + if (state.attribState[i]) + { + gl.enableVertexAttribArray(i); + } + else + { + gl.disableVertexAttribArray(i); + } + } + } + } + else + { + for (i = 0; i < attribs.length; i++) + { + const attrib = attribs[i]; + + gl.enableVertexAttribArray(attrib.attribute.location); + } + } +} diff --git a/packages/core/src/index.js b/packages/core/src/index.js index 58c456a..fd1f547 100644 --- a/packages/core/src/index.js +++ b/packages/core/src/index.js @@ -1,30 +1,30 @@ import * as resources from './textures/resources'; -import * as systems from './renderers/systems'; +import * as systems from './systems'; -export { resources }; export { systems }; +export { resources }; -export { default as Renderer } from './renderers/Renderer'; -export { default as AbstractRenderer } from './renderers/AbstractRenderer'; -export { default as FrameBuffer } from './textures/FrameBuffer'; +export { default as System } from './System'; +export { default as Renderer } from './Renderer'; +export { default as AbstractRenderer } from './AbstractRenderer'; +export { default as FrameBuffer } from './framebuffer/FrameBuffer'; export { default as CubeTexture } from './textures/CubeTexture'; export { default as BaseTexture } from './textures/BaseTexture'; export { default as Texture } from './textures/Texture'; export { default as TextureMatrix } from './textures/TextureMatrix'; -export { default as RenderTexture } from './textures/RenderTexture'; -export { default as BaseRenderTexture } from './textures/BaseRenderTexture'; +export { default as RenderTexture } from './renderTexture/RenderTexture'; +export { default as BaseRenderTexture } from './renderTexture/BaseRenderTexture'; export { default as TextureUvs } from './textures/TextureUvs'; -export { default as State } from './renderers/State'; -export { default as ObjectRenderer } from './renderers/utils/ObjectRenderer'; -export { default as Quad } from './renderers/utils/Quad'; -export { default as QuadUv } from './renderers/utils/QuadUv'; -export { default as checkMaxIfStatmentsInShader } from './renderers/utils/checkMaxIfStatmentsInShader'; +export { default as State } from './state/State'; +export { default as ObjectRenderer } from './batch/ObjectRenderer'; +export { default as Quad } from './utils/Quad'; +export { default as QuadUv } from './utils/QuadUv'; +export { default as checkMaxIfStatmentsInShader } from './shader/utils/checkMaxIfStatmentsInShader'; export { default as Shader } from './shader/Shader'; export { default as Program } from './shader/Program'; export { default as UniformGroup } from './shader/UniformGroup'; -export { default as SpriteMaskFilter } from './renderers/filters/spriteMask/SpriteMaskFilter'; -export { default as Filter } from './renderers/filters/Filter'; +export { default as SpriteMaskFilter } from './filters/spriteMask/SpriteMaskFilter'; +export { default as Filter } from './filters/Filter'; export { default as Attribute } from './geometry/Attribute'; export { default as Buffer } from './geometry/Buffer'; export { default as Geometry } from './geometry/Geometry'; - diff --git a/packages/core/src/mask/MaskSystem.js b/packages/core/src/mask/MaskSystem.js new file mode 100644 index 0000000..99174c2 --- /dev/null +++ b/packages/core/src/mask/MaskSystem.js @@ -0,0 +1,199 @@ +import System from '../System'; +import AlphaMaskFilter from '../filters/spriteMask/SpriteMaskFilter'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class MaskSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + // TODO - we don't need both! + this.scissor = false; + this.scissorData = null; + this.scissorRenderTarget = null; + + this.enableScissor = false; + + this.alphaMaskPool = []; + this.alphaMaskIndex = 0; + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.DisplayObject} target - Display Object to push the mask to + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + push(target, maskData) + { + // TODO the root check means scissor rect will not + // be used on render textures more info here: + // https://github.com/pixijs/pixi.js/pull/3545 + + if (maskData.texture) + { + this.pushSpriteMask(target, maskData); + } + else if (this.enableScissor + && !this.scissor + && this.renderer._activeRenderTarget.root + && !this.renderer.stencil.stencilMaskStack.length + && maskData.isFastRect()) + { + const matrix = maskData.worldTransform; + + let rot = Math.atan2(matrix.b, matrix.a); + + // use the nearest degree! + rot = Math.round(rot * (180 / Math.PI)); + + if (rot % 90) + { + this.pushStencilMask(maskData); + } + else + { + this.pushScissorMask(target, maskData); + } + } + else + { + this.pushStencilMask(maskData); + } + } + + /** + * Removes the last mask from the mask stack and doesn't return it. + * + * @param {PIXI.DisplayObject} target - Display Object to pop the mask from + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + pop(target, maskData) + { + if (maskData.texture) + { + this.popSpriteMask(target, maskData); + } + else if (this.enableScissor && !this.renderer.stencil.stencilMaskStack.length) + { + this.popScissorMask(target, maskData); + } + else + { + this.popStencilMask(target, maskData); + } + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.RenderTarget} target - Display Object to push the sprite mask to + * @param {PIXI.Sprite} maskData - Sprite to be used as the mask + */ + pushSpriteMask(target, maskData) + { + let alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; + + if (!alphaMaskFilter) + { + alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; + } + + alphaMaskFilter[0].resolution = this.renderer.resolution; + alphaMaskFilter[0].maskSprite = maskData; + + // TODO - may cause issues! + target.filterArea = maskData.getBounds(true); + + this.renderer.filterSystem.pushFilter(target, alphaMaskFilter); + + this.alphaMaskIndex++; + } + + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popSpriteMask() + { + this.renderer.filterSystem.popFilter(); + this.alphaMaskIndex--; + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + pushStencilMask(maskData) + { + this.renderer.batch.flush(); + this.renderer.stencil.pushStencil(maskData); + } + + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popStencilMask() + { + // this.renderer.currentRenderer.stop(); + this.renderer.stencil.popStencil(); + } + + /** + * + * @param {PIXI.DisplayObject} target - Display Object to push the mask to + * @param {PIXI.Graphics} maskData - The masking data. + */ + pushScissorMask(target, maskData) + { + maskData.renderable = true; + + const renderTarget = this.renderer._activeRenderTarget; + + const bounds = maskData.getBounds(); + + bounds.fit(renderTarget.size); + maskData.renderable = false; + + this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); + + const resolution = this.renderer.resolution; + + this.renderer.gl.scissor( + bounds.x * resolution, + (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, + bounds.width * resolution, + bounds.height * resolution + ); + + this.scissorRenderTarget = renderTarget; + this.scissorData = maskData; + this.scissor = true; + } + + /** + * + * + */ + popScissorMask() + { + this.scissorRenderTarget = null; + this.scissorData = null; + this.scissor = false; + + // must be scissor! + const gl = this.renderer.gl; + + gl.disable(gl.SCISSOR_TEST); + } +} diff --git a/packages/core/src/mask/StencilSystem.js b/packages/core/src/mask/StencilSystem.js new file mode 100644 index 0000000..62796a5 --- /dev/null +++ b/packages/core/src/mask/StencilSystem.js @@ -0,0 +1,133 @@ +import System from '../System'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class StencilSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + this.stencilMaskStack = []; + } + + /** + * Changes the mask stack that is used by this System. + * + * @param {PIXI.Graphics[]} stencilMaskStack - The mask stack + */ + setMaskStack(stencilMaskStack) + { + const gl = this.renderer.gl; + + if (stencilMaskStack.length !== this.stencilMaskStack.length) + { + if (stencilMaskStack.length === 0) + { + gl.disable(gl.STENCIL_TEST); + } + else + { + gl.enable(gl.STENCIL_TEST); + } + } + + this.stencilMaskStack = stencilMaskStack; + } + + /** + * Applies the Mask and adds it to the current stencil stack. @alvin + * + * @param {PIXI.Graphics} graphics - The mask + */ + pushStencil(graphics) + { + this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); + + // this.renderer._activeRenderTarget.attachStencilBuffer(); + + const gl = this.renderer.gl; + const prevMaskCount = this.stencilMaskStack.length; + + if (prevMaskCount === 0) + { + gl.enable(gl.STENCIL_TEST); + } + + this.stencilMaskStack.push(graphics); + + // Increment the refference stencil value where the new mask overlaps with the old ones. + gl.colorMask(false, false, false, false); + gl.stencilFunc(gl.EQUAL, prevMaskCount, this._getBitwiseMask()); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.INCR); + this.renderer.plugins.graphics.render(graphics); + + this._useCurrent(); + } + + /** + * Removes the last mask from the stencil stack. @alvin + */ + popStencil() + { + this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); + + const gl = this.renderer.gl; + const graphics = this.stencilMaskStack.pop(); + + if (this.stencilMaskStack.length === 0) + { + // the stack is empty! + gl.disable(gl.STENCIL_TEST); + gl.clear(gl.STENCIL_BUFFER_BIT); + gl.clearStencil(0); + } + else + { + // Decrement the refference stencil value where the popped mask overlaps with the other ones + gl.colorMask(false, false, false, false); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.DECR); + this.renderer.plugins.graphics.render(graphics); + + this._useCurrent(); + } + } + + /** + * Setup renderer to use the current stencil data. + */ + _useCurrent() + { + const gl = this.renderer.gl; + + gl.colorMask(true, true, true, true); + gl.stencilFunc(gl.EQUAL, this.stencilMaskStack.length, this._getBitwiseMask()); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP); + } + + /** + * Fill 1s equal to the number of acitve stencil masks. + * + * @return {number} The bitwise mask. + */ + _getBitwiseMask() + { + return (1 << this.stencilMaskStack.length) - 1; + } + + /** + * Destroys the mask stack. + * + */ + destroy() + { + super.destroy(this); + + this.stencilMaskStack = null; + } +} diff --git a/packages/core/src/projection/ProjectionSystem.js b/packages/core/src/projection/ProjectionSystem.js new file mode 100644 index 0000000..6e63b73 --- /dev/null +++ b/packages/core/src/projection/ProjectionSystem.js @@ -0,0 +1,73 @@ +import System from '../System'; +import { Matrix } from '@pixi/math'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class ProjectionSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.projectionMatrix = new Matrix(); + } + + update(destinationFrame, sourceFrame, resolution, root) + { + this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; + this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; + + this.calculateProjection(this.destinationFrame, this.sourceFrame, resolution, root); + + this.renderer.globalUniforms.uniforms.projectionMatrix = this.projectionMatrix; + this.renderer.globalUniforms.update(); + } + + /** + * Updates the projection matrix based on a projection frame (which is a rectangle) + * + * @param {Rectangle} destinationFrame - The destination frame. + * @param {Rectangle} sourceFrame - The source frame. + */ + calculateProjection(destinationFrame, sourceFrame, resolution, root) + { + const pm = this.projectionMatrix; + + // I don't think we will need this line.. + // pm.identity(); + + if (!root) + { + pm.a = (1 / destinationFrame.width * 2) * resolution; + pm.d = (1 / destinationFrame.height * 2) * resolution; + + pm.tx = -1 - (sourceFrame.x * pm.a); + pm.ty = -1 - (sourceFrame.y * pm.d); + } + else + { + pm.a = (1 / destinationFrame.width * 2) * resolution; + pm.d = (-1 / destinationFrame.height * 2) * resolution; + + pm.tx = -1 - (sourceFrame.x * pm.a); + pm.ty = 1 - (sourceFrame.y * pm.d); + } + } + + /** + * Sets the transform of the active render target to the given matrix + * + * @param {PIXI.Matrix} matrix - The transformation matrix + */ + setTransform()// matrix) + { + // this._activeRenderTarget.transform = matrix; + } +} diff --git a/packages/core/src/renderTexture/BaseRenderTexture.js b/packages/core/src/renderTexture/BaseRenderTexture.js new file mode 100644 index 0000000..88eb83b --- /dev/null +++ b/packages/core/src/renderTexture/BaseRenderTexture.js @@ -0,0 +1,137 @@ +import BaseTexture from '../textures/BaseTexture'; +import FrameBuffer from '../framebuffer/FrameBuffer'; + +/** + * A BaseRenderTexture is a special texture that allows any PixiJS display object to be rendered to it. + * + * __Hint__: All DisplayObjects (i.e. Sprites) that render to a BaseRenderTexture should be preloaded + * otherwise black rectangles will be drawn instead. + * + * A BaseRenderTexture takes a snapshot of any Display Object given to its render method. The position + * and rotation of the given Display Objects is ignored. For example: + * + * ```js + * let renderer = PIXI.autoDetectRenderer(1024, 1024, { view: canvas, ratio: 1 }); + * let baseRenderTexture = new PIXI.BaseRenderTexture(renderer, 800, 600); + * let sprite = PIXI.Sprite.fromImage("spinObj_01.png"); + * + * sprite.position.x = 800/2; + * sprite.position.y = 600/2; + * sprite.anchor.x = 0.5; + * sprite.anchor.y = 0.5; + * + * baseRenderTexture.render(sprite); + * ``` + * + * The Sprite in this case will be rendered using its local transform. To render this sprite at 0,0 + * you can clear the transform + * + * ```js + * + * sprite.setTransform() + * + * let baseRenderTexture = new PIXI.BaseRenderTexture(100, 100); + * let renderTexture = new PIXI.RenderTexture(baseRenderTexture); + * + * renderer.render(sprite, renderTexture); // Renders to center of RenderTexture + * ``` + * + * @class + * @extends PIXI.BaseTexture + * @memberof PIXI + */ +export default class BaseRenderTexture extends BaseTexture +{ + /** + * @param {object} [options] + * @param {number} [options.width=100] - The width of the base render texture + * @param {number} [options.height=100] - The height of the base render texture + * @param {PIXI.SCALE_MODES} [options.scaleMode] - See {@link PIXI.SCALE_MODES} for possible values + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the texture being generated + */ + constructor(options) + { + if (typeof options === 'number') + { + /* eslint-disable prefer-rest-params */ + // Backward compatibility of signature + const width = arguments[0]; + const height = arguments[1]; + const scaleMode = arguments[2]; + const resolution = arguments[3]; + + options = { width, height, scaleMode, resolution }; + /* eslint-enable prefer-rest-params */ + } + + super(null, options); + + const { width, height } = options || {}; + + // Set defaults + this.mipmap = false; + this.width = Math.ceil(width) || 100; + this.height = Math.ceil(height) || 100; + this.valid = true; + + /** + * A map of renderer IDs to webgl renderTargets + * + * @private + * @member {object} + */ + // this._glRenderTargets = {}; + + /** + * A reference to the canvas render target (we only need one as this can be shared across renderers) + * + * @private + * @member {object} + */ + this._canvasRenderTarget = null; + + this.clearColor = [0, 0, 0, 0]; + + this.frameBuffer = new FrameBuffer(width * this.resolution, height * this.resolution) + .addColorTexture(0, this); + + // TODO - could this be added the systems? + + /** + * The data structure for the stencil masks + * + * @member {PIXI.Graphics[]} + */ + this.stencilMaskStack = []; + + /** + * The data structure for the filters + * + * @member {PIXI.Graphics[]} + */ + this.filterStack = [{}]; + } + + /** + * Resizes the BaseRenderTexture. + * + * @param {number} width - The width to resize to. + * @param {number} height - The height to resize to. + */ + resize(width, height) + { + width = Math.ceil(width); + height = Math.ceil(height); + this.frameBuffer.resize(width * this.resolution, height * this.resolution); + } + + /** + * Destroys this texture + * + */ + destroy() + { + super.destroy(true); + this.renderer = null; + } +} diff --git a/packages/core/src/renderTexture/RenderTexture.js b/packages/core/src/renderTexture/RenderTexture.js new file mode 100644 index 0000000..f1ecf1f --- /dev/null +++ b/packages/core/src/renderTexture/RenderTexture.js @@ -0,0 +1,153 @@ +import BaseRenderTexture from './BaseRenderTexture'; +import Texture from '../textures/Texture'; + +/** + * A RenderTexture is a special texture that allows any PixiJS display object to be rendered to it. + * + * __Hint__: All DisplayObjects (i.e. Sprites) that render to a RenderTexture should be preloaded + * otherwise black rectangles will be drawn instead. + * + * A RenderTexture takes a snapshot of any Display Object given to its render method. For example: + * + * ```js + * let renderer = PIXI.autoDetectRenderer(1024, 1024, { view: canvas, ratio: 1 }); + * let renderTexture = PIXI.RenderTexture.create(800, 600); + * let sprite = PIXI.Sprite.fromImage("spinObj_01.png"); + * + * sprite.position.x = 800/2; + * sprite.position.y = 600/2; + * sprite.anchor.x = 0.5; + * sprite.anchor.y = 0.5; + * + * renderer.render(sprite, renderTexture); + * ``` + * + * The Sprite in this case will be rendered using its local transform. To render this sprite at 0,0 + * you can clear the transform + * + * ```js + * + * sprite.setTransform() + * + * let renderTexture = new PIXI.RenderTexture.create(100, 100); + * + * renderer.render(sprite, renderTexture); // Renders to center of RenderTexture + * ``` + * + * @class + * @extends PIXI.Texture + * @memberof PIXI + */ +export default class RenderTexture extends Texture +{ + /** + * @param {PIXI.BaseRenderTexture} baseRenderTexture - The renderer used for this RenderTexture + * @param {PIXI.Rectangle} [frame] - The rectangle frame of the texture to show + */ + constructor(baseRenderTexture, frame) + { + // support for legacy.. + let _legacyRenderer = null; + + if (!(baseRenderTexture instanceof BaseRenderTexture)) + { + /* eslint-disable prefer-rest-params, no-console */ + const width = arguments[1]; + const height = arguments[2]; + const scaleMode = arguments[3]; + const resolution = arguments[4]; + + // we have an old render texture.. + console.warn(`Please use RenderTexture.create(${width}, ${height}) instead of the ctor directly.`); + _legacyRenderer = arguments[0]; + /* eslint-enable prefer-rest-params, no-console */ + + frame = null; + baseRenderTexture = new BaseRenderTexture({ + width, + height, + scaleMode, + resolution, + }); + } + + /** + * The base texture object that this texture uses + * + * @member {BaseTexture} + */ + super(baseRenderTexture, frame); + + this.legacyRenderer = _legacyRenderer; + + /** + * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. + * + * @member {boolean} + */ + this.valid = true; + + /** + * FilterSystem temporary storage + * @private + * @member {PIXI.Rectangle} + */ + this.filterFrame = null; + + this._updateUvs(); + } + + /** + * Resizes the RenderTexture. + * + * @param {number} width - The width to resize to. + * @param {number} height - The height to resize to. + * @param {boolean} [resizeBaseTexture=true] - Should the baseTexture.width and height values be resized as well? + */ + resize(width, height, resizeBaseTexture = true) + { + width = Math.ceil(width); + height = Math.ceil(height); + + // TODO - could be not required.. + this.valid = (width > 0 && height > 0); + + this._frame.width = this.orig.width = width; + this._frame.height = this.orig.height = height; + + if (resizeBaseTexture) + { + this.baseTexture.resize(width, height); + } + + this._updateUvs(); + } + + /** + * A short hand way of creating a render texture. + * + * @param {object} [options] - Options + * @param {number} [options.width=100] - The width of the render texture + * @param {number} [options.height=100] - The height of the render texture + * @param {number} [options.scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the texture being generated + * @return {PIXI.RenderTexture} The new render texture + */ + static create(options) + { + // fallback, old-style: create(width, height, scaleMode, resolution) + if (typeof options === 'number') + { + /* eslint-disable prefer-rest-params */ + options = { + width: options, + height: arguments[1], + scaleMode: arguments[2], + resolution: arguments[3], + }; + /* eslint-enable prefer-rest-params */ + } + + return new RenderTexture(new BaseRenderTexture(options)); + } +} diff --git a/packages/core/src/renderTexture/RenderTextureSystem.js b/packages/core/src/renderTexture/RenderTextureSystem.js new file mode 100644 index 0000000..8d0ca61 --- /dev/null +++ b/packages/core/src/renderTexture/RenderTextureSystem.js @@ -0,0 +1,115 @@ +import System from '../System'; +import { Rectangle } from '@pixi/math'; + +const tempRect = new Rectangle(); + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class RenderTextureSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.clearColor = renderer._backgroundColorRgba; + + // TODO moe this property somewhere else! + this.defaultMaskStack = []; + this.defaultFilterStack = [{}]; + + // empty render texture? + this.renderTexture = null; + + this.destinationFrame = new Rectangle(); + } + + bind(renderTexture, sourceFrame, destinationFrame) + { + // TODO - do we want this?? + if (this.renderTexture === renderTexture) return; + this.renderTexture = renderTexture; + + const renderer = this.renderer; + + if (renderTexture) + { + const baseTexture = renderTexture.baseTexture; + + if (!destinationFrame) + { + tempRect.width = baseTexture.realWidth; + tempRect.height = baseTexture.realHeight; + + destinationFrame = tempRect; + } + + if (!sourceFrame) + { + sourceFrame = destinationFrame; + } + + this.renderer.framebuffer.bind(baseTexture.frameBuffer, destinationFrame); + + this.renderer.projection.update(destinationFrame, sourceFrame, baseTexture.resolution, false); + this.renderer.stencil.setMaskStack(baseTexture.stencilMaskStack); + } + else + { + // TODO these validation checks happen deeper down.. + // thing they can be avoided.. + if (!destinationFrame) + { + tempRect.width = renderer.width; + tempRect.height = renderer.height; + + destinationFrame = tempRect; + } + + if (!sourceFrame) + { + sourceFrame = destinationFrame; + } + + renderer.framebuffer.bind(null, destinationFrame); + + // TODO store this.. + this.renderer.projection.update(destinationFrame, sourceFrame, this.renderer.resolution, true); + this.renderer.stencil.setMaskStack(this.defaultMaskStack); + } + + this.destinationFrame.copyFrom(destinationFrame); + } + + /** + * Erases the render texture and fills the drawing area with a colour + * + * @param {number} [clearColor] - The colour + * @return {PIXI.Renderer} Returns itself. + */ + clear(clearColor) + { + if (this.renderTexture) + { + clearColor = clearColor || this.renderTexture.baseTexture.clearColor; + } + else + { + clearColor = clearColor || this.clearColor; + } + + this.renderer.framebuffer.clear(clearColor[0], clearColor[1], clearColor[2], clearColor[3]); + } + + resize()// screenWidth, screenHeight) + { + // resize the root only! + this.bind(null); + } +} diff --git a/packages/core/src/renderers/AbstractRenderer.js b/packages/core/src/renderers/AbstractRenderer.js deleted file mode 100644 index 928670f..0000000 --- a/packages/core/src/renderers/AbstractRenderer.js +++ /dev/null @@ -1,353 +0,0 @@ -import { hex2string, hex2rgb } from '@pixi/utils'; -import { Matrix, Rectangle } from '@pixi/math'; -import { RENDERER_TYPE } from '@pixi/constants'; -import { settings } from '@pixi/settings'; -import { Container } from '@pixi/display'; -import RenderTexture from '../textures/RenderTexture'; -import EventEmitter from 'eventemitter3'; - -const tempMatrix = new Matrix(); - -/** - * 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 EventEmitter - * @memberof PIXI - */ -export default class AbstractRenderer extends EventEmitter -{ - // eslint-disable-next-line valid-jsdoc - /** - * @param {string} system - The name of the system this renderer is for. - * @param {object} [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.transparent=false] - If the render view is transparent, default false - * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false - * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) - * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. The - * resolution of the renderer retina would be 2. - * @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 {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when rendering, - * stopping pixel interpolation. - */ - constructor(system, options, arg2, arg3) - { - super(); - - // Support for constructor(system, screenWidth, screenHeight, options) - if (typeof options === 'number') - { - options = Object.assign({ - width: options, - height: arg2 || settings.RENDER_OPTIONS.height, - }, arg3); - } - - // 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 = RENDERER_TYPE.UNKNOWN; - - /** - * Measurements of the screen. (0, 0, screenWidth, screenHeight) - * - * Its safe to use as filterArea or hitArea for 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 1 - */ - this.resolution = options.resolution || settings.RESOLUTION; - - /** - * Whether the render view is transparent - * - * @member {boolean} - */ - this.transparent = options.transparent; - - /** - * Whether css dimensions of canvas view should be resized to screen dimensions automatically - * - * @member {boolean} - */ - this.autoResize = options.autoResize || false; - - /** - * Tracks the blend modes useful for this renderer. - * - * @member {object} - */ - this.blendModes = null; - - /** - * 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; - - /** - * If true PixiJS will Math.floor() x/y values when rendering, stopping pixel interpolation. - * Handy for crisp pixel art and speed on legacy devices. - * - * @member {boolean} - */ - this.roundPixels = options.roundPixels; - - /** - * The background color as a number. - * - * @member {number} - * @private - */ - this._backgroundColor = 0x000000; - - /** - * The background color as an [R, G, B] array. - * - * @member {number[]} - * @private - */ - this._backgroundColorRgba = [0, 0, 0, 0]; - - /** - * The background color as a string. - * - * @member {string} - * @private - */ - this._backgroundColorString = '#000000'; - - this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter - - /** - * This temporary display object used as the parent of the currently being rendered item - * - * @member {PIXI.DisplayObject} - * @private - */ - this._tempDisplayObjectParent = new Container(); - - /** - * The last root object that the renderer tried to render. - * - * @member {PIXI.DisplayObject} - * @private - */ - this._lastObjectRendered = this._tempDisplayObjectParent; - - /** - * Collection of plugins. - * @readonly - * @member {object} - */ - this.plugins = {}; - } - - /** - * Initialize the plugins. - * - * @protected - * @param {object} staticMap - The dictionary of staticly saved plugins. - */ - initPlugins(staticMap) - { - 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() - { - return this.view.width; - } - - /** - * Same as view.height, actual number of pixels in the canvas by vertical - * - * @member {number} - * @readonly - * @default 600 - */ - get height() - { - return this.view.height; - } - - /** - * Resizes the screen and canvas to the specified width and height - * Canvas dimensions are multiplied by resolution - * - * @param {number} screenWidth - the new width of the screen - * @param {number} screenHeight - the new height of the screen - */ - resize(screenWidth, screenHeight) - { - this.screen.width = screenWidth; - this.screen.height = screenHeight; - - this.view.width = screenWidth * this.resolution; - this.view.height = screenHeight * this.resolution; - - if (this.autoResize) - { - this.view.style.width = `${screenWidth}px`; - this.view.style.height = `${screenHeight}px`; - } - } - - /** - * 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 {PIXI.DisplayObject} displayObject - The displayObject the object will be generated from - * @param {number} scaleMode - Should be one of the scaleMode consts - * @param {number} resolution - The resolution / device pixel ratio of the texture being generated - * @param {PIXI.Rectangle} [region] - The region of the displayObject, that shall be rendered, - * if no region is specified, defaults to the local bounds of the displayObject. - * @return {PIXI.Texture} a texture of the graphics object - */ - generateTexture(displayObject, scaleMode, resolution, region) - { - region = region || displayObject.getLocalBounds(); - - // 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(region.width | 0, region.height | 0, scaleMode, resolution); - - tempMatrix.tx = -region.x; - tempMatrix.ty = -region.y; - - this.render(displayObject, renderTexture, false, tempMatrix, true); - - return renderTexture; - } - - /** - * Removes everything from the renderer and optionally removes the Canvas DOM element. - * - * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. - */ - destroy(removeView) - { - 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); - } - - this.plugins = null; - - this.type = RENDERER_TYPE.UNKNOWN; - - this.view = null; - - this.screen = null; - - this.resolution = 0; - - this.transparent = false; - - this.autoResize = false; - - this.blendModes = null; - - this.options = null; - - this.preserveDrawingBuffer = false; - this.clearBeforeRender = false; - - this.roundPixels = false; - - this._backgroundColor = 0; - this._backgroundColorRgba = null; - this._backgroundColorString = null; - - this._tempDisplayObjectParent = null; - this._lastObjectRendered = null; - } - - /** - * The background color to fill if not transparent - * - * @member {number} - */ - get backgroundColor() - { - return this._backgroundColor; - } - - set backgroundColor(value) // eslint-disable-line require-jsdoc - { - this._backgroundColor = value; - this._backgroundColorString = hex2string(value); - hex2rgb(value, this._backgroundColorRgba); - } -} diff --git a/packages/core/src/renderers/Renderer.js b/packages/core/src/renderers/Renderer.js deleted file mode 100644 index a2020b1..0000000 --- a/packages/core/src/renderers/Renderer.js +++ /dev/null @@ -1,329 +0,0 @@ -import AbstractRenderer from './AbstractRenderer'; -import { sayHello } from '@pixi/utils'; -import MaskSystem from './systems/MaskSystem'; -import StencilSystem from './systems/StencilSystem'; -import FilterSystem from './systems/filter/FilterSystem'; -import FramebufferSystem from './systems/FramebufferSystem'; -import RenderTextureSystem from './systems/RenderTextureSystem'; -import TextureSystem from './systems/textures/TextureSystem'; -import ProjectionSystem from './systems/ProjectionSystem'; -import StateSystem from './systems/StateSystem'; -import GeometrySystem from './systems/geometry/GeometrySystem'; -import ShaderSystem from './systems/shader/ShaderSystem'; -import ContextSystem from './systems/ContextSystem'; -import BatchSystem from './systems/BatchSystem'; -import TextureGCSystem from './systems/textures/TextureGCSystem'; -import { RENDERER_TYPE } from '@pixi/constants'; -import UniformGroup from '../shader/UniformGroup'; -import { Matrix } from '@pixi/math'; -import Runner from 'mini-runner'; - -/** - * 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 Render works by automatically managing webGLBatchs. - * 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 :) - * - * @class - * @memberof PIXI - * @extends PIXI.AbstractRenderer - */ -export default class Renderer extends AbstractRenderer -{ - // eslint-disable-next-line valid-jsdoc - /** - * - * @param {object} [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.transparent=false] - If the render view is transparent, default false - * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false - * @param {boolean} [options.antialias=false] - sets antialias. If not available natively then FXAA - * antialiasing is used - * @param {boolean} [options.forceFXAA=false] - forces FXAA antialiasing to be used over native. - * FXAA is faster, but may not always look as great - * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. - * The resolution of the renderer retina would be 2. - * @param {boolean} [options.clearBeforeRender=true] - This sets if the renderer will clear - * the canvas or not before the new render pass. If you wish to set this to false, you *must* set - * preserveDrawingBuffer to `true`. - * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, - * enable this if you need to call toDataUrl on the webgl context. - * @param {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when - * rendering, stopping pixel interpolation. - * @param {number} [options.backgroundColor=0x000000] - The background color of the rendered area - * (shown if not transparent). - * @param {boolean} [options.legacy=false] - If true PixiJS will aim to ensure compatibility - * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. - * @param {string} [options.powerPreference] - Parameter passed to webgl context, set to "high-performance" - * for devices with dual graphics card - */ - constructor(options, arg2, arg3) - { - super('WebGL', options, arg2, arg3); - - /** - * The type of this renderer as a standardised const - * - * @member {number} - * @see PIXI.RENDERER_TYPE - */ - this.type = RENDERER_TYPE.WEBGL; - - // this will be set by the contextSystem (this.context) - this.gl = null; - this.CONTEXT_UID = 0; - this.legacy = !!options.legacy; - - // TODO legacy! - - // runners! - this.runners = { - destroy: new Runner('destroy'), - contextChange: new Runner('contextChange', 1), - reset: new Runner('reset'), - update: new Runner('update'), - postrender: new Runner('postrender'), - prerender: new Runner('prerender'), - resize: new Runner('resize', 2), - }; - - 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(GeometrySystem, 'geometry') - .addSystem(FramebufferSystem, 'framebuffer') - .addSystem(StencilSystem, 'stencil') - .addSystem(ProjectionSystem, 'projection') - .addSystem(TextureGCSystem, 'textureGC') - .addSystem(FilterSystem, 'filter') - .addSystem(RenderTextureSystem, 'renderTexture') - .addSystem(BatchSystem, 'batch'); - - this.initPlugins(Renderer.__plugins); - - /** - * The options passed in to create a new webgl context. - * - * @member {object} - * @private - */ - if (options.context) - { - this.context.initFromContext(options.context); - } - else - { - this.context.initFromOptions({ - alpha: this.transparent, - antialias: options.antialias, - premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', - stencil: true, - 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); - } - - addSystem(_class, name) - { - if (!name) - { - name = _class.name; - } - - // TODO - read name from class.name.. - - /* - if(name.includes('System')) - { - name = name.replace('System', ''); - name = name.charAt(0).toLowerCase() + name.slice(1); - } - */ - - const system = new _class(this); - - if (this[name]) - { - throw new Error(`Whoops! ${name} is already a manger`); - } - - this[name] = system; - - for (const i in this.runners) - { - this.runners[i].add(system); - } - - return this; - - /** - * 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. - */ - } - - /** - * Renders the object to its webGL view - * - * @param {PIXI.DisplayObject} displayObject - the object to be rendered - * @param {PIXI.RenderTexture} renderTexture - The render texture to render to. - * @param {boolean} [clear] - Should the canvas be cleared before the new render - * @param {PIXI.Transform} [transform] - A transform to apply to the render texture before rendering. - * @param {boolean} [skipUpdateTransform] - Should we skip the update transform pass? - */ - render(displayObject, renderTexture, clear, transform, skipUpdateTransform) - { - // can be handy to know! - this.renderingToScreen = !renderTexture; - - this.runners.prerender.run(); - this.emit('prerender'); - - // 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.parent; - - displayObject.parent = this._tempDisplayObjectParent; - displayObject.updateTransform(); - displayObject.parent = 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.run(); - - this.emit('postrender'); - } - - /** - * Resizes the webGL view to the specified width and height. - * - * @param {number} screenWidth - the new width of the screen - * @param {number} screenHeight - the new height of the screen - */ - resize(screenWidth, screenHeight) - { - AbstractRenderer.prototype.resize.call(this, screenWidth, screenHeight); - - this.runners.resize.run(screenWidth, screenHeight); - } - - /** - * Resets the WebGL state so you can render things however you fancy! - * - * @return {PIXI.Renderer} Returns itself. - */ - reset() - { - this.runners.reset.run(); - - return this; - } - - clear() - { - this.framebuffer.bind(); - this.framebuffer.clear(); - } - - /** - * Removes everything from the renderer (event listeners, spritebatch, etc...) - * - * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. - * See: https://github.com/pixijs/pixi.js/issues/2233 - */ - destroy(removeView) - { - this.runners.destroy.run(); - - // call base destroy - super.destroy(removeView); - - // TODO nullify all the managers.. - this.gl = null; - } - - /** - * 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 - * builds and excluding plugins. - * @name PIXI.Renderer#plugins - * @type {object} - * @readonly - * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. - * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. - * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. - * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. - */ - - /** - * Adds a plugin to the renderer. - * - * @method - * @param {string} pluginName - The name of the plugin. - * @param {Function} ctor - The constructor function or class for the plugin. - */ - static registerPlugin(pluginName, ctor) - { - Renderer.__plugins = Renderer.__plugins || {}; - Renderer.__plugins[pluginName] = ctor; - } -} diff --git a/packages/core/src/renderers/State.js b/packages/core/src/renderers/State.js deleted file mode 100644 index fa3f221..0000000 --- a/packages/core/src/renderers/State.js +++ /dev/null @@ -1,163 +0,0 @@ -/* eslint-disable max-len */ - -const BLEND = 0; -const OFFSET = 1; -const CULLING = 2; -const DEPTH_TEST = 3; -const WINDING = 4; - -/** - * This is a webGL state. It is passed The WebGL StateManager. - * Each mesh renderered may require webGL to be in a different state. - * For example you may want different blend mode or to enable polygon offsets - * - * @class - * @memberof PIXI - */ -export default class State -{ - /** - * - */ - constructor() - { - this.data = 0; - - this.blendMode = 0; - this.polygonOffset = 0; - - this.blend = true; - // this.depthTest = true; - } - - /** - * Activates blending of the computed fragment color values - * - * @member {boolean} - */ - get blend() - { - return !!(this.data & (1 << BLEND)); - } - - set blend(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << BLEND)) !== value) - { - this.data ^= (1 << BLEND); - } - } - - /** - * Activates adding an offset to depth values of polygon's fragments - * - * @member {boolean} - * @default false - */ - get offsets() - { - return !!(this.data & (1 << OFFSET)); - } - - set offsets(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << OFFSET)) !== value) - { - this.data ^= (1 << OFFSET); - } - } - - /** - * Activates culling of polygons. - * - * @member {boolean} - * @default false - */ - get culling() - { - return !!(this.data & (1 << CULLING)); - } - - set culling(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << CULLING)) !== value) - { - this.data ^= (1 << CULLING); - } - } - - /** - * Activates depth comparisons and updates to the depth buffer. - * - * @member {boolean} - * @default false - */ - get depthTest() - { - return !!(this.data & (1 << DEPTH_TEST)); - } - - set depthTest(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << DEPTH_TEST)) !== value) - { - this.data ^= (1 << DEPTH_TEST); - } - } - - /** - * Specifies whether or not front or back-facing polygons can be culled. - * @member {boolean} - * @default false - */ - get clockwiseFrontFace() - { - return !!(this.data & (1 << WINDING)); - } - - set clockwiseFrontFace(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << WINDING)) !== value) - { - this.data ^= (1 << WINDING); - } - } - - /** - * The blend mode to be applied when this state is set. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. - * Setting this mode to anything other than NO_BLEND will automatically switch blending on. - * - * @member {boolean} - * @default PIXI.BLEND_MODES.NORMAL - * @see PIXI.BLEND_MODES - */ - get blendMode() - { - return this._blendMode; - } - - set blendMode(value) // eslint-disable-line require-jsdoc - { - // 17 is NO BLEND - this.blend = (value !== 17); - this._blendMode = value; - } - - /** - * The polygon offset. Setting this property to anything other than 0 will automatically enable poygon offset fill. - * - * @member {number} - * @default 0 - */ - get polygonOffset() - { - return this._polygonOffset; - } - - set polygonOffset(value) // eslint-disable-line require-jsdoc - { - this.offsets = !!value; - this._polygonOffset = value; - } -} - diff --git a/packages/core/src/renderers/filters/Filter.js b/packages/core/src/renderers/filters/Filter.js deleted file mode 100644 index 2073b8d..0000000 --- a/packages/core/src/renderers/filters/Filter.js +++ /dev/null @@ -1,140 +0,0 @@ -import Shader from '../../shader/Shader'; -import Program from '../../shader/Program'; -import State from '../State'; -import { settings } from '@pixi/settings'; -// import extractUniformsFromSrc from './extractUniformsFromSrc'; -import defaultVertex from './defaultFilter.vert'; -import defaultFragment from './defaultFilter.frag'; - -// let math = require('../../math'); -/** - * @class - * @memberof PIXI - * @extends PIXI.Shader - */ -export default class Filter extends Shader -{ - /** - * @param {string} [vertexSrc] - The source of the vertex shader. - * @param {string} [fragmentSrc] - The source of the fragment shader. - * @param {object} [uniforms] - Custom uniforms to use to augment the built-in ones. - */ - constructor(vertexSrc, fragmentSrc, uniforms) - { - const program = Program.from(vertexSrc, fragmentSrc); - - super(program, uniforms); - - /** - * The padding of the filter. Some filters require extra space to breath such as a blur. - * Increasing this will add extra width and height to the bounds of the object that the - * filter is applied to. - * - * @member {number} - */ - this.padding = 0; - - /** - * The resolution of the filter. Setting this to be lower will lower the quality but - * increase the performance of the filter. - * - * @member {number} - */ - this.resolution = settings.RESOLUTION; - - /** - * If enabled is true the filter is applied, if false it will not. - * - * @member {boolean} - */ - this.enabled = true; - - /** - * If enabled, PixiJS will fit the filter area into boundaries for better performance. - * Switch it off if it does not work for specific shader. - * - * @member {boolean} - */ - this.autoFit = true; - - /** - * Legacy filters use position and uvs from attributes - * @member {boolean} - * @readonly - */ - this.legacy = !!this.program.attributeData.aTextureCoord; - - /** - * the webGL state the filter requires to render - * @member {PIXI.State} - */ - this.state = new State(); - } - - /** - * Applies the filter - * - * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from - * @param {PIXI.RenderTarget} input - The input render target. - * @param {PIXI.RenderTarget} output - The target to output to. - * @param {boolean} clear - Should the output be cleared before rendering to it - * @param {object} [currentState] - It's current state of filter. - * There are some useful properties in the currentState : - * target, filters, sourceFrame, destinationFrame, renderTarget, resolution - */ - apply(filterManager, input, output, clear, currentState, derp) // eslint-disable-line no-unused-vars - { - // do as you please! - - filterManager.applyFilter(this, input, output, clear, currentState, derp); - - // or just do a regular render.. - } - - /** - * Sets the blendmode of the filter - * - * @member {number} - * @default PIXI.BLEND_MODES.NORMAL - */ - get blendMode() - { - return this.state.blendMode; - } - - set blendMode(value) // eslint-disable-line require-jsdoc - { - this.state.blendMode = value; - } - - /** - * The default vertex shader source - * - * @static - * @constant - */ - static get defaultVertexSrc() - { - return defaultVertex; - } - - /** - * The default fragment shader source - * - * @static - * @constant - */ - static get defaultFragmentSrc() - { - return defaultFragment; - } -} - -/** - * Used for caching shader IDs - * - * @static - * @private - */ -Filter.SOURCE_KEY_MAP = {}; - diff --git a/packages/core/src/renderers/filters/defaultFilter.frag b/packages/core/src/renderers/filters/defaultFilter.frag deleted file mode 100644 index a14c1be..0000000 --- a/packages/core/src/renderers/filters/defaultFilter.frag +++ /dev/null @@ -1,22 +0,0 @@ -varying vec2 vTextureCoord; -varying vec2 vFilterCoord; - -uniform sampler2D uSampler; -uniform sampler2D filterSampler; - -void main(void){ - vec4 masky = texture2D(filterSampler, vFilterCoord); - vec4 sample = texture2D(uSampler, vTextureCoord); - vec4 color; - if(mod(vFilterCoord.x, 1.0) > 0.5) - { - color = vec4(1.0, 0.0, 0.0, 1.0); - } - else - { - color = vec4(0.0, 1.0, 0.0, 1.0); - } - // gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0); - gl_FragColor = mix(sample, masky, 0.5); - gl_FragColor *= sample.a; -} diff --git a/packages/core/src/renderers/filters/defaultFilter.vert b/packages/core/src/renderers/filters/defaultFilter.vert deleted file mode 100644 index e1febca..0000000 --- a/packages/core/src/renderers/filters/defaultFilter.vert +++ /dev/null @@ -1,14 +0,0 @@ -attribute vec2 aVertexPosition; -attribute vec2 aTextureCoord; - -uniform mat3 projectionMatrix; -uniform mat3 filterMatrix; - -varying vec2 vTextureCoord; -varying vec2 vFilterCoord; - -void main(void){ - gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); - vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy; - vTextureCoord = aTextureCoord ; -} diff --git a/packages/core/src/renderers/filters/filterTransforms.js b/packages/core/src/renderers/filters/filterTransforms.js deleted file mode 100644 index 87a3cd6..0000000 --- a/packages/core/src/renderers/filters/filterTransforms.js +++ /dev/null @@ -1,50 +0,0 @@ -import { Matrix } from '@pixi/math'; - -/** - * Calculates the mapped matrix - * @param filterArea {Rectangle} The filter area - * @param sprite {Sprite} the target sprite - * @param outputMatrix {Matrix} @alvin - * @private - */ -// this returns a matrix that will normalise map filter cords in the filter to screen space -export function calculateScreenSpaceMatrix(outputMatrix, filterArea, textureSize) -{ - // TODO unwrap? - const mappedMatrix = outputMatrix.identity(); - - mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); - - mappedMatrix.scale(textureSize.width, textureSize.height); - - return mappedMatrix; -} - -export function calculateNormalizedScreenSpaceMatrix(outputMatrix, filterArea, textureSize) -{ - const mappedMatrix = outputMatrix.identity(); - - mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); - - const translateScaleX = (textureSize.width / filterArea.width); - const translateScaleY = (textureSize.height / filterArea.height); - - mappedMatrix.scale(translateScaleX, translateScaleY); - - return mappedMatrix; -} - -// this will map the filter coord so that a texture can be used based on the transform of a sprite -export function calculateSpriteMatrix(outputMatrix, filterArea, textureSize, sprite) -{ - const orig = sprite._texture.orig; - const mappedMatrix = outputMatrix.set(textureSize.width, 0, 0, textureSize.height, filterArea.x, filterArea.y); - const worldTransform = sprite.worldTransform.copyTo(Matrix.TEMP_MATRIX); - - worldTransform.invert(); - mappedMatrix.prepend(worldTransform); - mappedMatrix.scale(1.0 / orig.width, 1.0 / orig.height); - mappedMatrix.translate(sprite.anchor.x, sprite.anchor.y); - - return mappedMatrix; -} diff --git a/packages/core/src/renderers/filters/spriteMask/SpriteMaskFilter.js b/packages/core/src/renderers/filters/spriteMask/SpriteMaskFilter.js deleted file mode 100644 index d5a91a4..0000000 --- a/packages/core/src/renderers/filters/spriteMask/SpriteMaskFilter.js +++ /dev/null @@ -1,63 +0,0 @@ -import Filter from '../Filter'; -import { Matrix } from '@pixi/math'; -import vertex from './spriteMaskFilter.vert'; -import fragment from './spriteMaskFilter.frag'; -import { default as TextureMatrix } from '../../../textures/TextureMatrix'; - -/** - * The SpriteMaskFilter class - * - * @class - * @extends PIXI.Filter - * @memberof PIXI - */ -export default class SpriteMaskFilter extends Filter -{ - /** - * @param {PIXI.Sprite} sprite - the target sprite - */ - constructor(sprite) - { - const maskMatrix = new Matrix(); - - super(vertex, fragment); - - sprite.renderable = false; - - this.maskSprite = sprite; - this.maskMatrix = maskMatrix; - } - - /** - * Applies the filter - * - * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from - * @param {PIXI.RenderTarget} input - The input render target. - * @param {PIXI.RenderTarget} output - The target to output to. - */ - apply(filterManager, input, output) - { - const maskSprite = this.maskSprite; - const tex = this.maskSprite.texture; - - if (!tex.valid) - { - return; - } - if (!tex.transform) - { - // margin = 0.0, let it bleed a bit, shader code becomes easier - // assuming that atlas textures were made with 1-pixel padding - tex.transform = new TextureMatrix(tex, 0.0); - } - tex.transform.update(); - - this.uniforms.mask = tex; - this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite) - .prepend(tex.transform.mapCoord); - this.uniforms.alpha = maskSprite.worldAlpha; - this.uniforms.maskClamp = tex.transform.uClampFrame; - - filterManager.applyFilter(this, input, output); - } -} diff --git a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.frag b/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.frag deleted file mode 100644 index 0e4aef8..0000000 --- a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.frag +++ /dev/null @@ -1,23 +0,0 @@ -varying vec2 vMaskCoord; -varying vec2 vTextureCoord; - -uniform sampler2D uSampler; -uniform sampler2D mask; -uniform float alpha; -uniform vec4 maskClamp; - -void main(void) -{ - float clip = step(3.5, - step(maskClamp.x, vMaskCoord.x) + - step(maskClamp.y, vMaskCoord.y) + - step(vMaskCoord.x, maskClamp.z) + - step(vMaskCoord.y, maskClamp.w)); - - vec4 original = texture2D(uSampler, vTextureCoord); - vec4 masky = texture2D(mask, vMaskCoord); - - original *= (masky.r * masky.a * alpha * clip); - - gl_FragColor = original; -} diff --git a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.vert b/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.vert deleted file mode 100644 index d23a30c..0000000 --- a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.vert +++ /dev/null @@ -1,16 +0,0 @@ -attribute vec2 aVertexPosition; -attribute vec2 aTextureCoord; - -uniform mat3 projectionMatrix; -uniform mat3 otherMatrix; - -varying vec2 vMaskCoord; -varying vec2 vTextureCoord; - -void main(void) -{ - gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); - - vTextureCoord = aTextureCoord; - vMaskCoord = ( otherMatrix * vec3( aTextureCoord, 1.0) ).xy; -} diff --git a/packages/core/src/renderers/systems/BatchSystem.js b/packages/core/src/renderers/systems/BatchSystem.js deleted file mode 100644 index 70e9518..0000000 --- a/packages/core/src/renderers/systems/BatchSystem.js +++ /dev/null @@ -1,68 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import ObjectRenderer from '../utils/ObjectRenderer'; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ - -export default class BatchSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this manager works for. - */ - constructor(renderer) - { - super(renderer); - - /** - * An empty renderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.emptyRenderer = new ObjectRenderer(renderer); - - /** - * The currently active ObjectRenderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.currentRenderer = this.emptyRenderer; - } - - /** - * Changes the current renderer to the one given in parameter - * - * @param {PIXI.ObjectRenderer} objectRenderer - The object renderer to use. - */ - setObjectRenderer(objectRenderer) - { - if (this.currentRenderer === objectRenderer) - { - return; - } - - this.currentRenderer.stop(); - this.currentRenderer = objectRenderer; - - this.renderer.state.setState(objectRenderer.state); - - this.currentRenderer.start(); - } - - /** - * This should be called if you wish to do some custom rendering - * It will basically render anything that may be batched up such as sprites - * - */ - flush() - { - this.setObjectRenderer(this.emptyRenderer); - } - - reset() - { - this.setObjectRenderer(this.emptyRenderer); - } -} diff --git a/packages/core/src/renderers/systems/ContextSystem.js b/packages/core/src/renderers/systems/ContextSystem.js deleted file mode 100644 index 43bf827..0000000 --- a/packages/core/src/renderers/systems/ContextSystem.js +++ /dev/null @@ -1,186 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { settings } from '@pixi/settings'; - -let CONTEXT_UID = 0; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class ContextSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this.webGLVersion = 1; - - this.handleContextLost = this.handleContextLost.bind(this); - this.handleContextRestored = this.handleContextRestored.bind(this); - - this.extensions = {}; - - renderer.view.addEventListener('webglcontextlost', this.handleContextLost, false); - renderer.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); - } - - get isLost() - { - return (!this.gl || this.gl.isContextLost()); - } - - contextChange(gl) - { - this.gl = gl; - - // restore a context if it was previously lost - if (gl.isContextLost() && gl.getExtension('WEBGL_lose_context')) - { - gl.getExtension('WEBGL_lose_context').restoreContext(); - } - } - - initFromContext(gl) - { - this.gl = gl; - this.validateContext(gl); - this.renderer.gl = gl; - this.renderer.CONTEXT_UID = CONTEXT_UID++; - this.renderer.runners.contextChange.run(gl); - } - - initFromOptions(options) - { - const gl = this.createContext(this.renderer.view, options); - - this.initFromContext(gl); - } - - /** - * Helper class to create a webGL Context - * - * @class - * @memberof PIXI.glCore - * @param canvas {HTMLCanvasElement} the canvas element that we will get the context from - * @param options {Object} An options object that gets passed in to the canvas element containing the context attributes, - * see https://developer.mozilla.org/en/docs/Web/API/HTMLCanvasElement/getContext for the options available - * @return {WebGLRenderingContext} the WebGL context - */ - createContext(canvas, options) - { - let gl; - - if (settings.PREFER_WEBGL_2) - { - gl = canvas.getContext('webgl2', options); - } - - if (gl) - { - this.webGLVersion = 2; - } - else - { - this.webGLVersion = 1; - - gl = canvas.getContext('webgl', options) - || canvas.getContext('experimental-webgl', options); - - if (!gl) - { - // fail, not able to get a context - throw new Error('This browser does not support webGL. Try using the canvas renderer'); - } - } - - this.gl = gl; - - this.getExtensions(); - - return gl; - } - - getExtensions() - { - // time to set up default etensions that pixi uses.. - const gl = this.gl; - const extensions = this.extensions; - - if (this.webGLVersion === 1) - { - extensions.drawBuffers = gl.getExtension('WEBGL_draw_buffers'); - extensions.depthTexture = gl.getExtension('WEBKIT_WEBGL_depth_texture'); - extensions.floatTexture = gl.getExtension('OES_texture_float'); - extensions.loseContext = gl.getExtension('WEBGL_lose_context'); - - extensions.vertexArrayObject = gl.getExtension('OES_vertex_array_object') - || gl.getExtension('MOZ_OES_vertex_array_object') - || gl.getExtension('WEBKIT_OES_vertex_array_object'); - } - - // we don't use any specific WebGL 2 ones yet! - } - - /** - * Handles a lost webgl context - * - * @private - * @param {WebGLContextEvent} event - The context lost event. - */ - handleContextLost(event) - { - event.preventDefault(); - } - - /** - * Handles a restored webgl context - * - * @private - */ - handleContextRestored() - { - this.renderer.runners.contextChange.run(this.gl); - } - - destroy() - { - const view = this.renderer.view; - - // remove listeners - view.removeEventListener('webglcontextlost', this.handleContextLost); - view.removeEventListener('webglcontextrestored', this.handleContextRestored); - - this.gl.useProgram(null); - - if (this.extensions.loseContext) - { - this.extensions.loseContext.loseContext(); - } - } - - postrender() - { - this.gl.flush(); - } - - validateContext(gl) - { - const attributes = gl.getContextAttributes(); - - // this is going to be fairly simple for now.. but at least we have room to grow! - if (!attributes.stencil) - { - /* eslint-disable max-len */ - - /* eslint-disable no-console */ - console.warn('Provided WebGL context does not have a stencil buffer, masks may not render correctly'); - /* eslint-enable no-console */ - - /* eslint-enable max-len */ - } - } -} diff --git a/packages/core/src/renderers/systems/FramebufferSystem.js b/packages/core/src/renderers/systems/FramebufferSystem.js deleted file mode 100644 index df7902b..0000000 --- a/packages/core/src/renderers/systems/FramebufferSystem.js +++ /dev/null @@ -1,246 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { Rectangle } from '@pixi/math'; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class FramebufferSystem extends WebGLSystem -{ - /** - * Sets up the renderer context and necessary buffers. - * - * @private - */ - contextChange() - { - this.gl = this.renderer.gl; - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - this.current = null; - this.viewport = new Rectangle(); - - this.drawBufferExtension = this.renderer.context.extensions.drawBuffers; - } - - bind(framebuffer, frame) - { - const gl = this.gl; - - this.current = framebuffer; - - if (framebuffer) - { - // TODO cacheing layer! - - const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID] || this.initFramebuffer(framebuffer); - - gl.bindFramebuffer(gl.FRAMEBUFFER, fbo.framebuffer); - // makesure all textures are unbound.. - - // now check for updates... - if (fbo.dirtyId !== framebuffer.dirtyId) - { - fbo.dirtyId = framebuffer.dirtyId; - - if (fbo.dirtyFormat !== framebuffer.dirtyFormat) - { - fbo.dirtyFormat = framebuffer.dirtyFormat; - this.updateFramebuffer(framebuffer); - } - else if (fbo.dirtySize !== framebuffer.dirtySize) - { - fbo.dirtySize = framebuffer.dirtySize; - this.resizeFramebuffer(framebuffer); - } - } - - for (let i = 0; i < framebuffer.colorTextures.length; i++) - { - if (framebuffer.colorTextures[i].texturePart) - { - this.renderer.texture.unbind(framebuffer.colorTextures[i].texture); - } - else - { - this.renderer.texture.unbind(framebuffer.colorTextures[i]); - } - } - - if (framebuffer.depthTexture) - { - this.renderer.texture.unbind(framebuffer.depthTexture); - } - - if (frame) - { - this.setViewport(frame.x, frame.y, frame.width, frame.height); - } - else - { - this.setViewport(0, 0, framebuffer.width, framebuffer.height); - } - } - else - { - gl.bindFramebuffer(gl.FRAMEBUFFER, null); - - if (frame) - { - this.setViewport(frame.x, frame.y, frame.width, frame.height); - } - else - { - this.setViewport(0, 0, this.renderer.width, this.renderer.height); - } - } - } - - setViewport(x, y, width, height) - { - const v = this.viewport; - - if (v.width !== width || v.height !== height || v.x !== x || v.y !== y) - { - v.x = x; - v.y = y; - v.width = width; - v.height = height; - - this.gl.viewport(x, y, width, height); - } - } - - get size() - { - if (this.current) - { - // TODO store temp - return { x: 0, y: 0, width: this.current.width, height: this.current.height }; - } - - return { x: 0, y: 0, width: this.renderer.width, height: this.renderer.height }; - } - - clear(r, g, b, a) - { - const gl = this.gl; - - // TODO clear color can be set only one right? - gl.clearColor(r, g, b, a); - gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); - } - - // private functions... - - initFramebuffer(framebuffer) - { - const gl = this.gl; - - // TODO - make this a class? - const fbo = { - framebuffer: gl.createFramebuffer(), - stencil: null, - dirtyId: 0, - dirtyFormat: 0, - dirtySize: 0, - }; - - framebuffer.glFrameBuffers[this.CONTEXT_UID] = fbo; - - return fbo; - } - - resizeFramebuffer(framebuffer) - { - const gl = this.gl; - - if (framebuffer.stencil || framebuffer.depth) - { - gl.bindRenderbuffer(gl.RENDERBUFFER, this.stencil); - gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); - } - } - - updateFramebuffer(framebuffer) - { - const gl = this.gl; - - const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID]; - - // bind the color texture - const colorTextures = framebuffer.colorTextures; - - let count = colorTextures.length; - - if (!this.drawBufferExtension) - { - count = Math.min(count, 1); - } - - const activeTextures = []; - - for (let i = 0; i < count; i++) - { - const texture = framebuffer.colorTextures[i]; - - if (texture.texturePart) - { - this.renderer.texture.bind(texture.texture, 0); - - gl.framebufferTexture2D(gl.FRAMEBUFFER, - gl.COLOR_ATTACHMENT0 + i, - gl.TEXTURE_CUBE_MAP_NEGATIVE_X + texture.side, - texture.texture._glTextures[this.CONTEXT_UID].texture, - 0); - } - else - { - this.renderer.texture.bind(texture, 0); - - gl.framebufferTexture2D(gl.FRAMEBUFFER, - gl.COLOR_ATTACHMENT0 + i, - gl.TEXTURE_2D, - texture._glTextures[this.CONTEXT_UID].texture, - 0); - } - - activeTextures.push(gl.COLOR_ATTACHMENT0 + i); - } - - if (this.drawBufferExtension && activeTextures.length > 1) - { - this.drawBufferExtension.drawBuffersWEBGL(activeTextures); - } - - if (framebuffer.depthTexture) - { - const depthTextureExt = this.renderer.context.extensions.depthTexture; - - if (depthTextureExt) - { - const depthTexture = framebuffer.depthTexture; - - this.renderer.texture.bind(depthTexture, 0); - - gl.framebufferTexture2D(gl.FRAMEBUFFER, - gl.DEPTH_ATTACHMENT, - gl.TEXTURE_2D, - depthTexture._glTextures[this.CONTEXT_UID].texture, - 0); - } - } - - if (framebuffer.stencil || framebuffer.depth) - { - fbo.stencil = gl.createRenderbuffer(); - - gl.bindRenderbuffer(gl.RENDERBUFFER, fbo.stencil); - - // TODO.. this is depth AND stencil? - gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, fbo.stencil); - gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); - // fbo.enableStencil(); - } - } -} diff --git a/packages/core/src/renderers/systems/MaskSystem.js b/packages/core/src/renderers/systems/MaskSystem.js deleted file mode 100644 index d9c4b1a..0000000 --- a/packages/core/src/renderers/systems/MaskSystem.js +++ /dev/null @@ -1,199 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import AlphaMaskFilter from '../filters/spriteMask/SpriteMaskFilter'; - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class MaskSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - // TODO - we don't need both! - this.scissor = false; - this.scissorData = null; - this.scissorRenderTarget = null; - - this.enableScissor = false; - - this.alphaMaskPool = []; - this.alphaMaskIndex = 0; - } - - /** - * Applies the Mask and adds it to the current filter stack. - * - * @param {PIXI.DisplayObject} target - Display Object to push the mask to - * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. - */ - push(target, maskData) - { - // TODO the root check means scissor rect will not - // be used on render textures more info here: - // https://github.com/pixijs/pixi.js/pull/3545 - - if (maskData.texture) - { - this.pushSpriteMask(target, maskData); - } - else if (this.enableScissor - && !this.scissor - && this.renderer._activeRenderTarget.root - && !this.renderer.stencil.stencilMaskStack.length - && maskData.isFastRect()) - { - const matrix = maskData.worldTransform; - - let rot = Math.atan2(matrix.b, matrix.a); - - // use the nearest degree! - rot = Math.round(rot * (180 / Math.PI)); - - if (rot % 90) - { - this.pushStencilMask(maskData); - } - else - { - this.pushScissorMask(target, maskData); - } - } - else - { - this.pushStencilMask(maskData); - } - } - - /** - * Removes the last mask from the mask stack and doesn't return it. - * - * @param {PIXI.DisplayObject} target - Display Object to pop the mask from - * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. - */ - pop(target, maskData) - { - if (maskData.texture) - { - this.popSpriteMask(target, maskData); - } - else if (this.enableScissor && !this.renderer.stencil.stencilMaskStack.length) - { - this.popScissorMask(target, maskData); - } - else - { - this.popStencilMask(target, maskData); - } - } - - /** - * Applies the Mask and adds it to the current filter stack. - * - * @param {PIXI.RenderTarget} target - Display Object to push the sprite mask to - * @param {PIXI.Sprite} maskData - Sprite to be used as the mask - */ - pushSpriteMask(target, maskData) - { - let alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; - - if (!alphaMaskFilter) - { - alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; - } - - alphaMaskFilter[0].resolution = this.renderer.resolution; - alphaMaskFilter[0].maskSprite = maskData; - - // TODO - may cause issues! - target.filterArea = maskData.getBounds(true); - - this.renderer.filterSystem.pushFilter(target, alphaMaskFilter); - - this.alphaMaskIndex++; - } - - /** - * Removes the last filter from the filter stack and doesn't return it. - * - */ - popSpriteMask() - { - this.renderer.filterSystem.popFilter(); - this.alphaMaskIndex--; - } - - /** - * Applies the Mask and adds it to the current filter stack. - * - * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. - */ - pushStencilMask(maskData) - { - this.renderer.batch.flush(); - this.renderer.stencil.pushStencil(maskData); - } - - /** - * Removes the last filter from the filter stack and doesn't return it. - * - */ - popStencilMask() - { - // this.renderer.currentRenderer.stop(); - this.renderer.stencil.popStencil(); - } - - /** - * - * @param {PIXI.DisplayObject} target - Display Object to push the mask to - * @param {PIXI.Graphics} maskData - The masking data. - */ - pushScissorMask(target, maskData) - { - maskData.renderable = true; - - const renderTarget = this.renderer._activeRenderTarget; - - const bounds = maskData.getBounds(); - - bounds.fit(renderTarget.size); - maskData.renderable = false; - - this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); - - const resolution = this.renderer.resolution; - - this.renderer.gl.scissor( - bounds.x * resolution, - (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, - bounds.width * resolution, - bounds.height * resolution - ); - - this.scissorRenderTarget = renderTarget; - this.scissorData = maskData; - this.scissor = true; - } - - /** - * - * - */ - popScissorMask() - { - this.scissorRenderTarget = null; - this.scissorData = null; - this.scissor = false; - - // must be scissor! - const gl = this.renderer.gl; - - gl.disable(gl.SCISSOR_TEST); - } -} diff --git a/packages/core/src/renderers/systems/ProjectionSystem.js b/packages/core/src/renderers/systems/ProjectionSystem.js deleted file mode 100644 index ec5efdb..0000000 --- a/packages/core/src/renderers/systems/ProjectionSystem.js +++ /dev/null @@ -1,73 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { Matrix } from '@pixi/math'; - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ - -export default class ProjectionSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this.projectionMatrix = new Matrix(); - } - - update(destinationFrame, sourceFrame, resolution, root) - { - this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; - this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; - - this.calculateProjection(this.destinationFrame, this.sourceFrame, resolution, root); - - this.renderer.globalUniforms.uniforms.projectionMatrix = this.projectionMatrix; - this.renderer.globalUniforms.update(); - } - - /** - * Updates the projection matrix based on a projection frame (which is a rectangle) - * - * @param {Rectangle} destinationFrame - The destination frame. - * @param {Rectangle} sourceFrame - The source frame. - */ - calculateProjection(destinationFrame, sourceFrame, resolution, root) - { - const pm = this.projectionMatrix; - - // I don't think we will need this line.. - // pm.identity(); - - if (!root) - { - pm.a = (1 / destinationFrame.width * 2) * resolution; - pm.d = (1 / destinationFrame.height * 2) * resolution; - - pm.tx = -1 - (sourceFrame.x * pm.a); - pm.ty = -1 - (sourceFrame.y * pm.d); - } - else - { - pm.a = (1 / destinationFrame.width * 2) * resolution; - pm.d = (-1 / destinationFrame.height * 2) * resolution; - - pm.tx = -1 - (sourceFrame.x * pm.a); - pm.ty = 1 - (sourceFrame.y * pm.d); - } - } - - /** - * Sets the transform of the active render target to the given matrix - * - * @param {PIXI.Matrix} matrix - The transformation matrix - */ - setTransform()// matrix) - { - // this._activeRenderTarget.transform = matrix; - } -} diff --git a/packages/core/src/renderers/systems/RenderTextureSystem.js b/packages/core/src/renderers/systems/RenderTextureSystem.js deleted file mode 100644 index a133ef1..0000000 --- a/packages/core/src/renderers/systems/RenderTextureSystem.js +++ /dev/null @@ -1,115 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { Rectangle } from '@pixi/math'; - -const tempRect = new Rectangle(); - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ - -export default class RenderTextureSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this.clearColor = renderer._backgroundColorRgba; - - // TODO moe this property somewhere else! - this.defaultMaskStack = []; - this.defaultFilterStack = [{}]; - - // empty render texture? - this.renderTexture = null; - - this.destinationFrame = new Rectangle(); - } - - bind(renderTexture, sourceFrame, destinationFrame) - { - // TODO - do we want this?? - if (this.renderTexture === renderTexture) return; - this.renderTexture = renderTexture; - - const renderer = this.renderer; - - if (renderTexture) - { - const baseTexture = renderTexture.baseTexture; - - if (!destinationFrame) - { - tempRect.width = baseTexture.realWidth; - tempRect.height = baseTexture.realHeight; - - destinationFrame = tempRect; - } - - if (!sourceFrame) - { - sourceFrame = destinationFrame; - } - - this.renderer.framebuffer.bind(baseTexture.frameBuffer, destinationFrame); - - this.renderer.projection.update(destinationFrame, sourceFrame, baseTexture.resolution, false); - this.renderer.stencil.setMaskStack(baseTexture.stencilMaskStack); - } - else - { - // TODO these validation checks happen deeper down.. - // thing they can be avoided.. - if (!destinationFrame) - { - tempRect.width = renderer.width; - tempRect.height = renderer.height; - - destinationFrame = tempRect; - } - - if (!sourceFrame) - { - sourceFrame = destinationFrame; - } - - renderer.framebuffer.bind(null, destinationFrame); - - // TODO store this.. - this.renderer.projection.update(destinationFrame, sourceFrame, this.renderer.resolution, true); - this.renderer.stencil.setMaskStack(this.defaultMaskStack); - } - - this.destinationFrame.copyFrom(destinationFrame); - } - - /** - * Erases the render texture and fills the drawing area with a colour - * - * @param {number} [clearColor] - The colour - * @return {PIXI.Renderer} Returns itself. - */ - clear(clearColor) - { - if (this.renderTexture) - { - clearColor = clearColor || this.renderTexture.baseTexture.clearColor; - } - else - { - clearColor = clearColor || this.clearColor; - } - - this.renderer.framebuffer.clear(clearColor[0], clearColor[1], clearColor[2], clearColor[3]); - } - - resize()// screenWidth, screenHeight) - { - // resize the root only! - this.bind(null); - } -} diff --git a/packages/core/src/renderers/systems/StateSystem.js b/packages/core/src/renderers/systems/StateSystem.js deleted file mode 100755 index d7f4caf..0000000 --- a/packages/core/src/renderers/systems/StateSystem.js +++ /dev/null @@ -1,288 +0,0 @@ -import mapWebGLBlendModesToPixi from '../utils/mapWebGLBlendModesToPixi'; -import WebGLSystem from './WebGLSystem'; -import WebGLState from '../State'; - -const BLEND = 0; -const OFFSET = 1; -const CULLING = 2; -const DEPTH_TEST = 3; -const WINDING = 4; - -/** - * A WebGL state machines - * - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class StateSystem extends WebGLSystem -{ - /** - * @param {WebGLRenderingContext} gl - The current WebGL rendering context - */ - constructor(renderer) - { - super(renderer); - - this.gl = null; - - this.maxAttribs = null; - - // check we have vao.. - this.nativeVaoExtension = null; - - this.attribState = null; - - this.stateId = 0; - this.polygonOffset = 0; - this.blendMode = 17; - - this.map = []; - - // map functions for when we set state.. - this.map[BLEND] = this.setBlend; - this.map[OFFSET] = this.setOffset; - this.map[CULLING] = this.setCullFace; - this.map[DEPTH_TEST] = this.setDepthTest; - this.map[WINDING] = this.setFrontFace; - - this.checks = []; - - this.defaultState = new WebGLState(); - this.defaultState.blend = true; - this.defaultState.depth = true; - } - - contextChange(gl) - { - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; - - this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); - - // check we have vao.. - this.nativeVaoExtension = ( - gl.getExtension('OES_vertex_array_object') - || gl.getExtension('MOZ_OES_vertex_array_object') - || gl.getExtension('WEBKIT_OES_vertex_array_object') - ); - - this.attribState = { - tempAttribState: new Array(this.maxAttribs), - attribState: new Array(this.maxAttribs), - }; - - this.blendModes = mapWebGLBlendModesToPixi(gl); - - this.setState(this.defaultState); - - this.reset(); - } - - /** - * Sets the current state - * - * @param {*} state - The state to set. - */ - setState(state) - { - state = state || this.defaultState; - - // TODO maybe to an object check? ( this.state === state )? - if (this.stateId !== state.data) - { - let diff = this.stateId ^ state.data; - let i = 0; - - // order from least to most common - while (diff) - { - if (diff & 1) - { - // state change! - this.map[i].call(this, !!(state.data & (1 << i))); - } - - diff = diff >> 1; - i++; - } - - this.stateId = state.data; - } - - // based on the above settings we check for specific modes.. - // for example if blend is active we check and set the blend modes - // or of polygon offset is active we check the poly depth. - for (let i = 0; i < this.checks.length; i++) - { - this.checks[i](this, state); - } - } - - /** - * Enables or disabled blending. - * - * @param {boolean} value - Turn on or off webgl blending. - */ - setBlend(value) - { - this.updateCheck(StateSystem.checkBlendMode, value); - - this.gl[value ? 'enable' : 'disable'](this.gl.BLEND); - } - - /** - * Enables or disable polygon offset fill - * - * @param {boolean} value - Turn on or off webgl polygon offset testing. - */ - setOffset(value) - { - this.gl[value ? 'enable' : 'disable'](this.gl.POLYGON_OFFSET_FILL); - } - - /** - * Sets whether to enable or disable depth test. - * - * @param {boolean} value - Turn on or off webgl depth testing. - */ - setDepthTest(value) - { - this.gl[value ? 'enable' : 'disable'](this.gl.DEPTH_TEST); - } - - /** - * Sets whether to enable or disable cull face. - * - * @param {boolean} value - Turn on or off webgl cull face. - */ - setCullFace(value) - { - this.gl[value ? 'enable' : 'disable'](this.gl.CULL_FACE); - } - - /** - * Sets the gl front face. - * - * @param {boolean} value - true is clockwise and false is counter-clockwise - */ - setFrontFace(value) - { - this.gl.frontFace(this.gl[value ? 'CW' : 'CCW']); - } - - /** - * Sets the blend mode. - * - * @param {number} value - The blend mode to set to. - */ - setBlendMode(value) - { - if (value === this.blendMode) - { - return; - } - - this.blendMode = value; - this.gl.blendFunc(this.blendModes[value][0], this.blendModes[value][1]); - } - - /** - * Sets the polygon offset. - * - * @param {number} value - the polygon offset - * @param {number} scale - the polygon offset scale - */ - setPolygonOffset(value, scale) - { - this.gl.polygonOffset(value, scale); - } - - /** - * Disables all the vaos in use - * - */ - resetAttributes() - { - for (let i = 0; i < this.attribState.tempAttribState.length; i++) - { - this.attribState.tempAttribState[i] = 0; - } - - for (let i = 0; i < this.attribState.attribState.length; i++) - { - this.attribState.attribState[i] = 0; - } - - // im going to assume one is always active for performance reasons. - for (let i = 1; i < this.maxAttribs; i++) - { - this.gl.disableVertexAttribArray(i); - } - } - - // used - /** - * Resets all the logic and disables the vaos - */ - reset() - { - // unbind any VAO if they exist.. - if (this.nativeVaoExtension) - { - this.nativeVaoExtension.bindVertexArrayOES(null); - } - - // reset all attributes.. - this.resetAttributes(); - - this.gl.pixelStorei(this.gl.UNPACK_FLIP_Y_WEBGL, false); - - this.setBlendMode(0); - - // TODO? - // this.setState(this.defaultState); - } - - /** - * checks to see which updates should be checked based on which settings have been activated - * for example if blend is enabled then we shold check the blend modes each time the state is changed - * or if poygon fill is activated then we need to check if the polygone offset changes. - * The idea is that we only check what we have too. - * - * @param {Function} func the checking function to add or remove - * @param {boolean} value should the check function be added or removed. - */ - updateCheck(func, value) - { - const index = this.checks.indexOf(func); - - if (value && index === -1) - { - this.checks.push(func); - } - else if (!value && index !== -1) - { - this.checks.splice(index, 1); - } - } - - /** - * A private little wrapper function that we call to check the blend mode. - * - * @static - * @private - * @param {PIXI.StateSystem} System the System to perform the state check on - * @param {PIXI.State} state the state that the blendMode will pulled from - */ - static checkBlendMode(system, state) - { - system.setBlendMode(state.blendMode); - } - - // TODO - add polygon offset? -} diff --git a/packages/core/src/renderers/systems/StencilSystem.js b/packages/core/src/renderers/systems/StencilSystem.js deleted file mode 100644 index 5d538cb..0000000 --- a/packages/core/src/renderers/systems/StencilSystem.js +++ /dev/null @@ -1,133 +0,0 @@ -import WebGLSystem from './WebGLSystem'; - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class StencilSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - this.stencilMaskStack = []; - } - - /** - * Changes the mask stack that is used by this System. - * - * @param {PIXI.Graphics[]} stencilMaskStack - The mask stack - */ - setMaskStack(stencilMaskStack) - { - const gl = this.renderer.gl; - - if (stencilMaskStack.length !== this.stencilMaskStack.length) - { - if (stencilMaskStack.length === 0) - { - gl.disable(gl.STENCIL_TEST); - } - else - { - gl.enable(gl.STENCIL_TEST); - } - } - - this.stencilMaskStack = stencilMaskStack; - } - - /** - * Applies the Mask and adds it to the current stencil stack. @alvin - * - * @param {PIXI.Graphics} graphics - The mask - */ - pushStencil(graphics) - { - this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); - - // this.renderer._activeRenderTarget.attachStencilBuffer(); - - const gl = this.renderer.gl; - const prevMaskCount = this.stencilMaskStack.length; - - if (prevMaskCount === 0) - { - gl.enable(gl.STENCIL_TEST); - } - - this.stencilMaskStack.push(graphics); - - // Increment the refference stencil value where the new mask overlaps with the old ones. - gl.colorMask(false, false, false, false); - gl.stencilFunc(gl.EQUAL, prevMaskCount, this._getBitwiseMask()); - gl.stencilOp(gl.KEEP, gl.KEEP, gl.INCR); - this.renderer.plugins.graphics.render(graphics); - - this._useCurrent(); - } - - /** - * Removes the last mask from the stencil stack. @alvin - */ - popStencil() - { - this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); - - const gl = this.renderer.gl; - const graphics = this.stencilMaskStack.pop(); - - if (this.stencilMaskStack.length === 0) - { - // the stack is empty! - gl.disable(gl.STENCIL_TEST); - gl.clear(gl.STENCIL_BUFFER_BIT); - gl.clearStencil(0); - } - else - { - // Decrement the refference stencil value where the popped mask overlaps with the other ones - gl.colorMask(false, false, false, false); - gl.stencilOp(gl.KEEP, gl.KEEP, gl.DECR); - this.renderer.plugins.graphics.render(graphics); - - this._useCurrent(); - } - } - - /** - * Setup renderer to use the current stencil data. - */ - _useCurrent() - { - const gl = this.renderer.gl; - - gl.colorMask(true, true, true, true); - gl.stencilFunc(gl.EQUAL, this.stencilMaskStack.length, this._getBitwiseMask()); - gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP); - } - - /** - * Fill 1s equal to the number of acitve stencil masks. - * - * @return {number} The bitwise mask. - */ - _getBitwiseMask() - { - return (1 << this.stencilMaskStack.length) - 1; - } - - /** - * Destroys the mask stack. - * - */ - destroy() - { - super.destroy(this); - - this.stencilMaskStack = null; - } -} diff --git a/packages/core/src/renderers/systems/WebGLSystem.js b/packages/core/src/renderers/systems/WebGLSystem.js deleted file mode 100644 index d3d5b31..0000000 --- a/packages/core/src/renderers/systems/WebGLSystem.js +++ /dev/null @@ -1,41 +0,0 @@ -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this manager works for. - */ - constructor(renderer) - { - /** - * The renderer this manager works for. - * - * @member {PIXI.Renderer} - */ - this.renderer = renderer; - - this.renderer.runners.contextChange.add(this); - } - - /** - * Generic method called when there is a WebGL context change. - * - */ - contextChange() - { - // do some codes init! - } - - /** - * Generic destroy methods to be overridden by the subclass - * - */ - destroy() - { - this.renderer.runners.contextChange.remove(this); - this.renderer = null; - } -} diff --git a/packages/core/src/renderers/systems/filter/FilterSystem.js b/packages/core/src/renderers/systems/filter/FilterSystem.js deleted file mode 100644 index e615037..0000000 --- a/packages/core/src/renderers/systems/filter/FilterSystem.js +++ /dev/null @@ -1,431 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; - -import RenderTexture from '../../../textures/RenderTexture'; -import Quad from '../../utils/Quad'; -import QuadUv from '../../utils/QuadUv'; -import { Rectangle } from '@pixi/math'; -import * as filterTransforms from '../../filters/filterTransforms'; -import bitTwiddle from 'bit-twiddle'; -import UniformGroup from '../../../shader/UniformGroup'; -import { DRAW_MODES } from '../../../../../constants'; - -// -/** - * @ignore - * @class - */ -class FilterState -{ - /** - * - */ - constructor() - { - this.renderTexture = null; - this.sourceFrame = new Rectangle(); - this.destinationFrame = new Rectangle(); - this.filters = []; - this.target = null; - this.legacy = false; - this.resolution = 1; - } -} - -/** - * @class - * @memberof PIXI.systems - * @extends PIXI.systems.WebGLSystem - */ -export default class FilterSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - /** - * stores a bunch of PO2 textures used for filtering - * @type {Object} - */ - this.texturePool = {}; - - /** - * a pool for storing filter states, save us creating new ones each tick - * @type {Array} - */ - this.statePool = []; - - /** - * A very simple geometry used when drawing a filter effect to the screen - * @type {Quad} - */ - this.quad = new Quad(); - - this.quadUv = new QuadUv(); - - /** - * Temporary rect for maths - * @type {PIXI.Rectangle} - */ - this.tempRect = new Rectangle(); - - this.activeState = {}; - - /** - * this uniform group is attached to filter uniforms when used - * @type {UniformGroup} - */ - this.globalUniforms = new UniformGroup({ - sourceFrame: this.tempRect, - destinationFrame: this.tempRect, - - // legacy variables - filterArea: new Float32Array(4), - filterClamp: new Float32Array(4), - }, true); - } - - /** - * Adds a new filter to the System. - * - * @param {PIXI.DisplayObject} target - The target of the filter to render. - * @param {PIXI.Filter[]} filters - The filters to apply. - */ - push(target, filters) - { - const renderer = this.renderer; - const filterStack = this.renderer.renderTexture.defaultFilterStack; - const state = this.statePool.pop() || new FilterState(); - - let resolution = filters[0].resolution; - let padding = filters[0].padding; - let autoFit = filters[0].autoFit; - let legacy = filters[0].legacy; - - for (let i = 1; i < filters.length; i++) - { - const filter = filters[i]; - - // lets use the lowest resolution.. - resolution = Math.min(resolution, filter.resolution); - // and the largest amount of padding! - padding = Math.max(padding, filter.padding); - // only auto fit if all filters are autofit - autoFit = autoFit || filter.autoFit; - - legacy = legacy || filter.legacy; - } - - filterStack.push(state); - - state.resolution = resolution; - - state.legacy = legacy; - - // round to whole number based on resolution - // TODO move that to the shader too? - state.sourceFrame = target.filterArea || target.getBounds(true); - - state.sourceFrame.pad(padding); - - if (autoFit) - { - state.sourceFrame.fit(this.renderer.renderTexture.destinationFrame); - } - - state.sourceFrame.round(resolution); - - state.renderTexture = this.getPotFilterTexture(state.sourceFrame.width, state.sourceFrame.height, resolution); - state.filters = filters; - - state.destinationFrame.width = state.renderTexture.width; - state.destinationFrame.height = state.renderTexture.height; - - state.renderTexture.filterFrame = state.sourceFrame; - renderer.renderTexture.bind(state.renderTexture, state.sourceFrame);// /, state.destinationFrame); - renderer.renderTexture.clear(); - } - - /** - * Pops off the filter and applies it. - * - */ - pop() - { - const renderer = this.renderer; - const filterStack = renderer.renderTexture.defaultFilterStack; - const state = filterStack.pop(); - const filters = state.filters; - - this.activeState = state; - - const globalUniforms = this.globalUniforms.uniforms; - - globalUniforms.sourceFrame = state.sourceFrame; - globalUniforms.destinationFrame = state.destinationFrame; - globalUniforms.resolution = state.resolution; - - // only update the rect if its legacy.. - if (state.legacy) - { - const filterArea = globalUniforms.filterArea; - const filterClamp = globalUniforms.filterClamp; - - filterArea[0] = state.destinationFrame.width; - filterArea[1] = state.destinationFrame.height; - filterArea[2] = state.sourceFrame.x; - filterArea[3] = state.sourceFrame.y; - - filterClamp[0] = 0.5 / state.resolution / state.destinationFrame.width; - filterClamp[1] = 0.5 / state.resolution / state.destinationFrame.height; - filterClamp[2] = (state.sourceFrame.width - 0.5) / state.resolution / state.destinationFrame.width; - filterClamp[3] = (state.sourceFrame.height - 0.5) / state.resolution / state.destinationFrame.height; - } - - this.globalUniforms.update(); - - const lastState = filterStack[filterStack.length - 1]; - - if (filters.length === 1) - { - filters[0].apply(this, state.renderTexture, lastState.renderTexture, false, state); - - this.returnFilterTexture(state.renderTexture); - } - else - { - let flip = state.renderTexture; - let flop = this.getPotFilterTexture( - flip.width, - flip.height, - state.resolution - ); - - flop.filterFrame = flip.filterFrame; - - let i = 0; - - for (i = 0; i < filters.length - 1; ++i) - { - filters[i].apply(this, flip, flop, true, state); - - const t = flip; - - flip = flop; - flop = t; - } - - filters[i].apply(this, flip, lastState.renderTexture, false, state); - - this.returnFilterTexture(flip); - this.returnFilterTexture(flop); - } - - this.statePool.push(state); - } - - /** - * Draws a filter. - * - * @param {PIXI.Filter} filter - The filter to draw. - * @param {PIXI.RenderTarget} input - The input render target. - * @param {PIXI.RenderTarget} output - The target to output to. - * @param {boolean} clear - Should the output be cleared before rendering to it - */ - applyFilter(filter, input, output, clear) - { - const renderer = this.renderer; - - renderer.renderTexture.bind(output, output ? output.filterFrame : null); - - if (clear) - { - // gl.disable(gl.SCISSOR_TEST); - renderer.renderTexture.clear(); - // gl.enable(gl.SCISSOR_TEST); - } - - // set the uniforms.. - filter.uniforms.uSampler = input; - filter.uniforms.filterGlobals = this.globalUniforms; - - // TODO make it so that the order of this does not matter.. - // because it does at the moment cos of global uniforms. - // they need to get resynced - - renderer.state.setState(filter.state); - renderer.shader.bind(filter); - - if (filter.legacy) - { - this.quadUv.map(input._frame, input.filterFrame); - - renderer.geometry.bind(this.quadUv); - renderer.geometry.draw(DRAW_MODES.TRIANGLES); - } - else - { - renderer.geometry.bind(this.quad); - renderer.geometry.draw(DRAW_MODES.TRIANGLE_STRIP); - } - } - - /** - * Calculates the mapped matrix. - * - * TODO playing around here.. this is temporary - (will end up in the shader) - * this returns a matrix that will normalise map filter cords in the filter to screen space - * - * @param {PIXI.Matrix} outputMatrix - the matrix to output to. - * @return {PIXI.Matrix} The mapped matrix. - */ - calculateScreenSpaceMatrix(outputMatrix) - { - const currentState = this.activeState; - - return filterTransforms.calculateScreenSpaceMatrix( - outputMatrix, - currentState.sourceFrame, - currentState.destinationFrame - ); - } - - /** - * This will map the filter coord so that a texture can be used based on the transform of a sprite - * - * @param {PIXI.Matrix} outputMatrix - The matrix to output to. - * @param {PIXI.Sprite} sprite - The sprite to map to. - * @return {PIXI.Matrix} The mapped matrix. - */ - calculateSpriteMatrix(outputMatrix, sprite) - { - const currentState = this.activeState; - - return filterTransforms.calculateSpriteMatrix( - outputMatrix, - currentState.sourceFrame, - currentState.destinationFrame, - sprite - ); - } - - /** - * Destroys this Filter System. - * - * @param {boolean} [contextLost=false] context was lost, do not free shaders - * - */ - destroy(contextLost = false) - { - if (!contextLost) - { - this.emptyPool(); - } - else - { - this.texturePool = {}; - } - } - - /** - * Gets a Power-of-Two render texture. - * - * TODO move to a seperate class could be on renderer? - * also - could cause issue with multiple contexts? - * - * @private - * @param {WebGLRenderingContext} gl - The webgl rendering context - * @param {number} minWidth - The minimum width of the render target. - * @param {number} minHeight - The minimum height of the render target. - * @param {number} resolution - The resolution of the render target. - * @return {PIXI.RenderTarget} The new render target. - */ - getPotFilterTexture(minWidth, minHeight, resolution) - { - minWidth = bitTwiddle.nextPow2(minWidth); - minHeight = bitTwiddle.nextPow2(minHeight); - resolution = resolution || 1; - - const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); - - if (!this.texturePool[key]) - { - this.texturePool[key] = []; - } - - let renderTexture = this.texturePool[key].pop(); - - if (!renderTexture) - { - // temporary bypass cache.. - // internally - this will cause a texture to be bound.. - renderTexture = RenderTexture.create({ - width: minWidth, - height: minHeight, - resolution, - }); - } - - return renderTexture; - } - - /** - * Gets extra render texture to use inside current filter - * - * @param {number} resolution resolution of the renderTexture - * @returns {PIXI.RenderTexture} - */ - getFilterTexture(resolution) - { - const rt = this.activeState.renderTexture; - - const filterTexture = this.getPotFilterTexture(rt.width, rt.height, resolution || rt.baseTexture.resolution); - - filterTexture.filterFrame = rt.filterFrame; - - return filterTexture; - } - - /** - * Frees a render texture back into the pool. - * - * @param {PIXI.RenderTarget} renderTexture - The renderTarget to free - */ - returnFilterTexture(renderTexture) - { - renderTexture.filterFrame = null; - - const base = renderTexture.baseTexture; - - const minWidth = base.width; - const minHeight = base.height; - - const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); - - this.texturePool[key].push(renderTexture); - } - - /** - * Empties the texture pool. - * - */ - emptyPool() - { - for (const i in this.texturePool) - { - const textures = this.texturePool[i]; - - if (textures) - { - for (let j = 0; j < textures.length; j++) - { - textures[j].destroy(true); - } - } - } - - this.texturePool = {}; - } -} diff --git a/packages/core/src/renderers/systems/geometry/GLBuffer.js b/packages/core/src/renderers/systems/geometry/GLBuffer.js deleted file mode 100644 index 0477e8a..0000000 --- a/packages/core/src/renderers/systems/geometry/GLBuffer.js +++ /dev/null @@ -1,9 +0,0 @@ -export default class GLBuffer -{ - constructor(buffer) - { - this.buffer = buffer; - this.updateID = -1; - this.byteLength = -1; - } -} diff --git a/packages/core/src/renderers/systems/geometry/GeometrySystem.js b/packages/core/src/renderers/systems/geometry/GeometrySystem.js deleted file mode 100644 index 26c8e9e..0000000 --- a/packages/core/src/renderers/systems/geometry/GeometrySystem.js +++ /dev/null @@ -1,388 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; -import GLBuffer from './GLBuffer'; - -const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class GeometrySystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this._activeGeometry = null; - this._activeVao = null; - - this.hasVao = true; - this.hasInstance = true; - } - - /** - * Sets up the renderer context and necessary buffers. - * - * @private - */ - contextChange() - { - const gl = this.gl = this.renderer.gl; - - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - - // webgl2 - if (!gl.createVertexArray) - { - // webgl 1! - let nativeVaoExtension = this.renderer.context.extensions.vertexArrayObject; - - if (this.renderer.legacy) - { - nativeVaoExtension = null; - } - - if (nativeVaoExtension) - { - gl.createVertexArray = () => - nativeVaoExtension.createVertexArrayOES(); - - gl.bindVertexArray = (vao) => - nativeVaoExtension.bindVertexArrayOES(vao); - - gl.deleteVertexArray = (vao) => - nativeVaoExtension.deleteVertexArrayOES(vao); - } - else - { - this.hasVao = false; - gl.createVertexArray = () => - { - // empty - }; - - gl.bindVertexArray = () => - { - // empty - }; - - gl.deleteVertexArray = () => - { - // empty - }; - } - } - - if (!gl.vertexAttribDivisor) - { - const instanceExt = gl.getExtension('ANGLE_instanced_arrays'); - - if (instanceExt) - { - gl.vertexAttribDivisor = (a, b) => - instanceExt.vertexAttribDivisorANGLE(a, b); - - gl.drawElementsInstanced = (a, b, c, d, e) => - instanceExt.drawElementsInstancedANGLE(a, b, c, d, e); - - gl.drawArraysInstanced = (a, b, c, d) => - instanceExt.drawArraysInstancedANGLE(a, b, c, d); - } - else - { - this.hasInstance = false; - } - } - } - - /** - * Binds geometry so that is can be drawn. Creating a Vao if required - * @private - * @param {PIXI.Geometry} geometry instance of geometry to bind - */ - bind(geometry, shader) - { - shader = shader || this.renderer.shader.shader; - - const gl = this.gl; - - // not sure the best way to address this.. - // currently different shaders require different VAOs for the same geometry - // Still mulling over the best way to solve this one.. - // will likely need to modify the shader attribute locations at run time! - let vaos = geometry.glVertexArrayObjects[this.CONTEXT_UID]; - - if (!vaos) - { - geometry.glVertexArrayObjects[this.CONTEXT_UID] = vaos = {}; - } - - const vao = vaos[shader.program.id] || this.initGeometryVao(geometry, shader.program); - - this._activeGeometry = geometry; - - if (this._activeVao !== vao) - { - this._activeVao = vao; - - if (this.hasVao) - { - gl.bindVertexArray(vao); - } - else - { - this.activateVao(geometry, shader.program); - } - } - - // TODO - optimise later! - // don't need to loop through if nothing changed! - // maybe look to add an 'autoupdate' to geometry? - this.updateBuffers(); - } - - reset() - { - this.unbind(); - } - - updateBuffers() - { - const geometry = this._activeGeometry; - const gl = this.gl; - - for (let i = 0; i < geometry.buffers.length; i++) - { - const buffer = geometry.buffers[i]; - - const glBuffer = buffer._glBuffers[this.CONTEXT_UID]; - - if (buffer._updateID !== glBuffer.updateID) - { - glBuffer.updateID = buffer._updateID; - - // TODO can cache this on buffer! maybe added a getter / setter? - const type = buffer.index ? gl.ELEMENT_ARRAY_BUFFER : gl.ARRAY_BUFFER; - const drawType = buffer.static ? gl.STATIC_DRAW : gl.DYNAMIC_DRAW; - - gl.bindBuffer(type, glBuffer.buffer); - - if (glBuffer.byteLength >= buffer.data.byteLength) - { - // offset is always zero for now! - gl.bufferSubData(type, 0, buffer.data); - } - else - { - gl.bufferData(type, buffer.data, drawType); - } - } - } - } - - checkCompatability(geometry, program) - { - // geometry must have at least all the attributes that the shader requires. - const geometryAttributes = geometry.attributes; - const shaderAttributes = program.attributeData; - - for (const j in shaderAttributes) - { - if (!geometryAttributes[j]) - { - throw new Error(`shader and geometry incompatible, geometry missing the "${j}" attribute`); - } - } - } - - /** - * Creates a Vao with the same structure as the geometry and stores it on the geometry. - * @private - * @param {PIXI.Geometry} geometry instance of geometry to to generate Vao for - */ - initGeometryVao(geometry, program) - { - this.checkCompatability(geometry, program); - - const gl = this.gl; - const CONTEXT_UID = this.CONTEXT_UID; - const buffers = geometry.buffers; - const attributes = geometry.attributes; - - const tempStride = {}; - const tempStart = {}; - - for (const j in buffers) - { - tempStride[j] = 0; - tempStart[j] = 0; - } - - for (const j in attributes) - { - if (!attributes[j].size && program.attributeData[j]) - { - attributes[j].size = program.attributeData[j].size; - } - - tempStride[attributes[j].buffer] += attributes[j].size * byteSizeMap[attributes[j].type]; - } - - for (const j in attributes) - { - const attribute = attributes[j]; - const attribSize = attribute.size; - - if (attribute.stride === undefined) - { - if (tempStride[attribute.buffer] === attribSize * byteSizeMap[attribute.type]) - { - attribute.stride = 0; - } - else - { - attribute.stride = tempStride[attribute.buffer]; - } - } - - if (attribute.start === undefined) - { - attribute.start = tempStart[attribute.buffer]; - - tempStart[attribute.buffer] += attribSize * byteSizeMap[attribute.type]; - } - } - - // first update - and create the buffers! - // only create a gl buffer if it actually gets - for (let i = 0; i < buffers.length; i++) - { - const buffer = buffers[i]; - - if (!buffer._glBuffers[CONTEXT_UID]) - { - buffer._glBuffers[CONTEXT_UID] = new GLBuffer(gl.createBuffer()); - } - } - - // TODO - maybe make this a data object? - // lets wait to see if we need to first! - const vao = gl.createVertexArray(); - - gl.bindVertexArray(vao); - - this.activateVao(geometry, program); - - gl.bindVertexArray(null); - - // add it to the cache! - geometry.glVertexArrayObjects[this.CONTEXT_UID][program.id] = vao; - - return vao; - } - - activateVao(geometry, program) - { - const gl = this.gl; - const CONTEXT_UID = this.CONTEXT_UID; - const buffers = geometry.buffers; - const attributes = geometry.attributes; - - if (geometry.indexBuffer) - { - // first update the index buffer if we have one.. - gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, geometry.indexBuffer._glBuffers[CONTEXT_UID].buffer); - } - - let lastBuffer = null; - - // add a new one! - for (const j in attributes) - { - const attribute = attributes[j]; - const buffer = buffers[attribute.buffer]; - const glBuffer = buffer._glBuffers[CONTEXT_UID]; - - if (program.attributeData[j]) - { - if (lastBuffer !== glBuffer) - { - gl.bindBuffer(gl.ARRAY_BUFFER, glBuffer.buffer); - - lastBuffer = glBuffer; - } - - const location = program.attributeData[j].location; - - // TODO introduce state again - // we can optimise this for older devices that have no VAOs - gl.enableVertexAttribArray(location); - - gl.vertexAttribPointer(location, - attribute.size, - attribute.type || gl.FLOAT, - attribute.normalized, - attribute.stride, - attribute.start); - - if (attribute.instance) - { - // TODO calculate instance count based of this... - if (this.hasInstance) - { - gl.vertexAttribDivisor(location, 1); - } - else - { - throw new Error('geometry error, GPU Instancing is not supported on this device'); - } - } - } - } - } - - draw(type, size, start, instanceCount) - { - const gl = this.gl; - const geometry = this._activeGeometry; - - // TODO.. this should not change so maybe cache the function? - - if (geometry.indexBuffer) - { - if (geometry.instanced) - { - /* eslint-disable max-len */ - gl.drawElementsInstanced(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2, instanceCount || 1); - /* eslint-enable max-len */ - } - else - { - gl.drawElements(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2); - } - } - else - if (geometry.instanced) - { - // TODO need a better way to calculate size.. - gl.drawArraysInstanced(type, start, size || geometry.getSize(), instanceCount || 1); - } - else - { - gl.drawArrays(type, start, size || geometry.getSize()); - } - - return this; - } - - unbind() - { - this.gl.bindVertexArray(null); - this._activeVao = null; - this._activeGeometry = null; - } -} diff --git a/packages/core/src/renderers/systems/geometry/setVertexAttribArrays.js b/packages/core/src/renderers/systems/geometry/setVertexAttribArrays.js deleted file mode 100644 index 23b6959..0000000 --- a/packages/core/src/renderers/systems/geometry/setVertexAttribArrays.js +++ /dev/null @@ -1,54 +0,0 @@ -// var GL_MAP = {}; - -/** - * @param gl {WebGLRenderingContext} The current WebGL context - * @param attribs {*} - * @param state {*} - */ -export default function setVertexAttribArrays(gl, attribs, state) -{ - let i; - - if (state) - { - const tempAttribState = state.tempAttribState; - const attribState = state.attribState; - - for (i = 0; i < tempAttribState.length; i++) - { - tempAttribState[i] = false; - } - - // set the new attribs - for (i = 0; i < attribs.length; i++) - { - tempAttribState[attribs[i].attribute.location] = true; - } - - for (i = 0; i < attribState.length; i++) - { - if (attribState[i] !== tempAttribState[i]) - { - attribState[i] = tempAttribState[i]; - - if (state.attribState[i]) - { - gl.enableVertexAttribArray(i); - } - else - { - gl.disableVertexAttribArray(i); - } - } - } - } - else - { - for (i = 0; i < attribs.length; i++) - { - const attrib = attribs[i]; - - gl.enableVertexAttribArray(attrib.attribute.location); - } - } -} diff --git a/packages/core/src/renderers/systems/index.js b/packages/core/src/renderers/systems/index.js deleted file mode 100644 index c843f03..0000000 --- a/packages/core/src/renderers/systems/index.js +++ /dev/null @@ -1,6 +0,0 @@ -/** - * Systems are individual components to the Renderer pipeline. - * @namespace PIXI.systems - */ -export { default as WebGLSystem } from './WebGLSystem'; -export { default as FilterSystem } from './filter/FilterSystem'; diff --git a/packages/core/src/AbstractRenderer.js b/packages/core/src/AbstractRenderer.js new file mode 100644 index 0000000..64b2eb1 --- /dev/null +++ b/packages/core/src/AbstractRenderer.js @@ -0,0 +1,353 @@ +import { hex2string, hex2rgb } from '@pixi/utils'; +import { Matrix, Rectangle } from '@pixi/math'; +import { RENDERER_TYPE } from '@pixi/constants'; +import { settings } from '@pixi/settings'; +import { Container } from '@pixi/display'; +import RenderTexture from './renderTexture/RenderTexture'; +import EventEmitter from 'eventemitter3'; + +const tempMatrix = new Matrix(); + +/** + * 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 EventEmitter + * @memberof PIXI + */ +export default class AbstractRenderer extends EventEmitter +{ + // eslint-disable-next-line valid-jsdoc + /** + * @param {string} system - The name of the system this renderer is for. + * @param {object} [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.transparent=false] - If the render view is transparent, default false + * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false + * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. The + * resolution of the renderer retina would be 2. + * @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 {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when rendering, + * stopping pixel interpolation. + */ + constructor(system, options, arg2, arg3) + { + super(); + + // Support for constructor(system, screenWidth, screenHeight, options) + if (typeof options === 'number') + { + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + }, arg3); + } + + // 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 = RENDERER_TYPE.UNKNOWN; + + /** + * Measurements of the screen. (0, 0, screenWidth, screenHeight) + * + * Its safe to use as filterArea or hitArea for 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 1 + */ + this.resolution = options.resolution || settings.RESOLUTION; + + /** + * Whether the render view is transparent + * + * @member {boolean} + */ + this.transparent = options.transparent; + + /** + * Whether css dimensions of canvas view should be resized to screen dimensions automatically + * + * @member {boolean} + */ + this.autoResize = options.autoResize || false; + + /** + * Tracks the blend modes useful for this renderer. + * + * @member {object} + */ + this.blendModes = null; + + /** + * 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; + + /** + * If true PixiJS will Math.floor() x/y values when rendering, stopping pixel interpolation. + * Handy for crisp pixel art and speed on legacy devices. + * + * @member {boolean} + */ + this.roundPixels = options.roundPixels; + + /** + * The background color as a number. + * + * @member {number} + * @private + */ + this._backgroundColor = 0x000000; + + /** + * The background color as an [R, G, B] array. + * + * @member {number[]} + * @private + */ + this._backgroundColorRgba = [0, 0, 0, 0]; + + /** + * The background color as a string. + * + * @member {string} + * @private + */ + this._backgroundColorString = '#000000'; + + this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter + + /** + * This temporary display object used as the parent of the currently being rendered item + * + * @member {PIXI.DisplayObject} + * @private + */ + this._tempDisplayObjectParent = new Container(); + + /** + * The last root object that the renderer tried to render. + * + * @member {PIXI.DisplayObject} + * @private + */ + this._lastObjectRendered = this._tempDisplayObjectParent; + + /** + * Collection of plugins. + * @readonly + * @member {object} + */ + this.plugins = {}; + } + + /** + * Initialize the plugins. + * + * @protected + * @param {object} staticMap - The dictionary of staticly saved plugins. + */ + initPlugins(staticMap) + { + 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() + { + return this.view.width; + } + + /** + * Same as view.height, actual number of pixels in the canvas by vertical + * + * @member {number} + * @readonly + * @default 600 + */ + get height() + { + return this.view.height; + } + + /** + * Resizes the screen and canvas to the specified width and height + * Canvas dimensions are multiplied by resolution + * + * @param {number} screenWidth - the new width of the screen + * @param {number} screenHeight - the new height of the screen + */ + resize(screenWidth, screenHeight) + { + this.screen.width = screenWidth; + this.screen.height = screenHeight; + + this.view.width = screenWidth * this.resolution; + this.view.height = screenHeight * this.resolution; + + if (this.autoResize) + { + this.view.style.width = `${screenWidth}px`; + this.view.style.height = `${screenHeight}px`; + } + } + + /** + * 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 {PIXI.DisplayObject} displayObject - The displayObject the object will be generated from + * @param {number} scaleMode - Should be one of the scaleMode consts + * @param {number} resolution - The resolution / device pixel ratio of the texture being generated + * @param {PIXI.Rectangle} [region] - The region of the displayObject, that shall be rendered, + * if no region is specified, defaults to the local bounds of the displayObject. + * @return {PIXI.Texture} a texture of the graphics object + */ + generateTexture(displayObject, scaleMode, resolution, region) + { + region = region || displayObject.getLocalBounds(); + + // 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(region.width | 0, region.height | 0, scaleMode, resolution); + + tempMatrix.tx = -region.x; + tempMatrix.ty = -region.y; + + this.render(displayObject, renderTexture, false, tempMatrix, true); + + return renderTexture; + } + + /** + * Removes everything from the renderer and optionally removes the Canvas DOM element. + * + * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. + */ + destroy(removeView) + { + 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); + } + + this.plugins = null; + + this.type = RENDERER_TYPE.UNKNOWN; + + this.view = null; + + this.screen = null; + + this.resolution = 0; + + this.transparent = false; + + this.autoResize = false; + + this.blendModes = null; + + this.options = null; + + this.preserveDrawingBuffer = false; + this.clearBeforeRender = false; + + this.roundPixels = false; + + this._backgroundColor = 0; + this._backgroundColorRgba = null; + this._backgroundColorString = null; + + this._tempDisplayObjectParent = null; + this._lastObjectRendered = null; + } + + /** + * The background color to fill if not transparent + * + * @member {number} + */ + get backgroundColor() + { + return this._backgroundColor; + } + + set backgroundColor(value) // eslint-disable-line require-jsdoc + { + this._backgroundColor = value; + this._backgroundColorString = hex2string(value); + hex2rgb(value, this._backgroundColorRgba); + } +} diff --git a/packages/core/src/Renderer.js b/packages/core/src/Renderer.js new file mode 100644 index 0000000..2227e78 --- /dev/null +++ b/packages/core/src/Renderer.js @@ -0,0 +1,338 @@ +import AbstractRenderer from './AbstractRenderer'; +import { sayHello } from '@pixi/utils'; +import MaskSystem from './mask/MaskSystem'; +import StencilSystem from './mask/StencilSystem'; +import FilterSystem from './filters/FilterSystem'; +import FramebufferSystem from './framebuffer/FramebufferSystem'; +import RenderTextureSystem from './renderTexture/RenderTextureSystem'; +import TextureSystem from './textures/TextureSystem'; +import ProjectionSystem from './projection/ProjectionSystem'; +import StateSystem from './state/StateSystem'; +import GeometrySystem from './geometry/GeometrySystem'; +import ShaderSystem from './shader/ShaderSystem'; +import ContextSystem from './context/ContextSystem'; +import BatchSystem from './batch/BatchSystem'; +import TextureGCSystem from './textures/TextureGCSystem'; +import { RENDERER_TYPE } from '@pixi/constants'; +import UniformGroup from './shader/UniformGroup'; +import { Matrix } from '@pixi/math'; +import Runner from 'mini-runner'; + +/** + * 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 Render works by automatically managing webGLBatchs. + * 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 :) + * + * @class + * @memberof PIXI + * @extends PIXI.AbstractRenderer + */ +export default class Renderer extends AbstractRenderer +{ + // eslint-disable-next-line valid-jsdoc + /** + * + * @param {object} [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.transparent=false] - If the render view is transparent, default false + * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false + * @param {boolean} [options.antialias=false] - sets antialias. If not available natively then FXAA + * antialiasing is used + * @param {boolean} [options.forceFXAA=false] - forces FXAA antialiasing to be used over native. + * FXAA is faster, but may not always look as great + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. + * The resolution of the renderer retina would be 2. + * @param {boolean} [options.clearBeforeRender=true] - This sets if the renderer will clear + * the canvas or not before the new render pass. If you wish to set this to false, you *must* set + * preserveDrawingBuffer to `true`. + * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, + * enable this if you need to call toDataUrl on the webgl context. + * @param {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when + * rendering, stopping pixel interpolation. + * @param {number} [options.backgroundColor=0x000000] - The background color of the rendered area + * (shown if not transparent). + * @param {boolean} [options.legacy=false] - If true PixiJS will aim to ensure compatibility + * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. + * @param {string} [options.powerPreference] - Parameter passed to webgl context, set to "high-performance" + * for devices with dual graphics card + */ + constructor(options, arg2, arg3) + { + super('WebGL', options, arg2, arg3); + + /** + * The type of this renderer as a standardised const + * + * @member {number} + * @see PIXI.RENDERER_TYPE + */ + this.type = RENDERER_TYPE.WEBGL; + + // this will be set by the contextSystem (this.context) + this.gl = null; + this.CONTEXT_UID = 0; + this.legacy = !!options.legacy; + + // TODO legacy! + + // runners! + this.runners = { + destroy: new Runner('destroy'), + contextChange: new Runner('contextChange', 1), + reset: new Runner('reset'), + update: new Runner('update'), + postrender: new Runner('postrender'), + prerender: new Runner('prerender'), + resize: new Runner('resize', 2), + }; + + 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(GeometrySystem, 'geometry') + .addSystem(FramebufferSystem, 'framebuffer') + .addSystem(StencilSystem, 'stencil') + .addSystem(ProjectionSystem, 'projection') + .addSystem(TextureGCSystem, 'textureGC') + .addSystem(FilterSystem, 'filter') + .addSystem(RenderTextureSystem, 'renderTexture') + .addSystem(BatchSystem, 'batch'); + + this.initPlugins(Renderer.__plugins); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ + if (options.context) + { + this.context.initFromContext(options.context); + } + else + { + this.context.initFromOptions({ + alpha: this.transparent, + antialias: options.antialias, + premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', + stencil: true, + 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); + } + + /** + * Add a new system to the renderer. + * @param {class} ClassRef - Class reference + * @param {string} [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. + * @return {PIXI.Renderer} Return instance of renderer + */ + addSystem(ClassRef, name) + { + if (!name) + { + name = ClassRef.name; + } + + // TODO - read name from class.name.. + + /* + if(name.includes('System')) + { + name = name.replace('System', ''); + name = name.charAt(0).toLowerCase() + name.slice(1); + } + */ + + const system = new ClassRef(this); + + if (this[name]) + { + throw new Error(`Whoops! ${name} is already a manger`); + } + + this[name] = system; + + for (const i in this.runners) + { + this.runners[i].add(system); + } + + return this; + + /** + * 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. + */ + } + + /** + * Renders the object to its webGL view + * + * @param {PIXI.DisplayObject} displayObject - the object to be rendered + * @param {PIXI.RenderTexture} renderTexture - The render texture to render to. + * @param {boolean} [clear] - Should the canvas be cleared before the new render + * @param {PIXI.Transform} [transform] - A transform to apply to the render texture before rendering. + * @param {boolean} [skipUpdateTransform] - Should we skip the update transform pass? + */ + render(displayObject, renderTexture, clear, transform, skipUpdateTransform) + { + // can be handy to know! + this.renderingToScreen = !renderTexture; + + this.runners.prerender.run(); + this.emit('prerender'); + + // 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.parent; + + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = 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.run(); + + this.emit('postrender'); + } + + /** + * Resizes the webGL view to the specified width and height. + * + * @param {number} screenWidth - the new width of the screen + * @param {number} screenHeight - the new height of the screen + */ + resize(screenWidth, screenHeight) + { + AbstractRenderer.prototype.resize.call(this, screenWidth, screenHeight); + + this.runners.resize.run(screenWidth, screenHeight); + } + + /** + * Resets the WebGL state so you can render things however you fancy! + * + * @return {PIXI.Renderer} Returns itself. + */ + reset() + { + this.runners.reset.run(); + + return this; + } + + clear() + { + this.framebuffer.bind(); + this.framebuffer.clear(); + } + + /** + * Removes everything from the renderer (event listeners, spritebatch, etc...) + * + * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. + * See: https://github.com/pixijs/pixi.js/issues/2233 + */ + destroy(removeView) + { + this.runners.destroy.run(); + + // call base destroy + super.destroy(removeView); + + // TODO nullify all the managers.. + this.gl = null; + } + + /** + * 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 + * builds and excluding plugins. + * @name PIXI.Renderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. + */ + + /** + * Adds a plugin to the renderer. + * + * @method + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + static registerPlugin(pluginName, ctor) + { + Renderer.__plugins = Renderer.__plugins || {}; + Renderer.__plugins[pluginName] = ctor; + } +} diff --git a/packages/core/src/System.js b/packages/core/src/System.js new file mode 100644 index 0000000..05a10c8 --- /dev/null +++ b/packages/core/src/System.js @@ -0,0 +1,42 @@ +/** + * System is a base class used for extending systems used by the {@link PIXI.Renderer} + * @see PIXI.Renderer#addSystem + * @class + * @memberof PIXI + */ +export default class System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + /** + * The renderer this manager works for. + * + * @member {PIXI.Renderer} + */ + this.renderer = renderer; + + this.renderer.runners.contextChange.add(this); + } + + /** + * Generic method called when there is a WebGL context change. + * + */ + contextChange() + { + // do some codes init! + } + + /** + * Generic destroy methods to be overridden by the subclass + * + */ + destroy() + { + this.renderer.runners.contextChange.remove(this); + this.renderer = null; + } +} diff --git a/packages/core/src/batch/BatchSystem.js b/packages/core/src/batch/BatchSystem.js new file mode 100644 index 0000000..b04c249 --- /dev/null +++ b/packages/core/src/batch/BatchSystem.js @@ -0,0 +1,68 @@ +import System from '../System'; +import ObjectRenderer from './ObjectRenderer'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class BatchSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + super(renderer); + + /** + * An empty renderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.emptyRenderer = new ObjectRenderer(renderer); + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.currentRenderer = this.emptyRenderer; + } + + /** + * Changes the current renderer to the one given in parameter + * + * @param {PIXI.ObjectRenderer} objectRenderer - The object renderer to use. + */ + setObjectRenderer(objectRenderer) + { + if (this.currentRenderer === objectRenderer) + { + return; + } + + this.currentRenderer.stop(); + this.currentRenderer = objectRenderer; + + this.renderer.state.setState(objectRenderer.state); + + this.currentRenderer.start(); + } + + /** + * This should be called if you wish to do some custom rendering + * It will basically render anything that may be batched up such as sprites + * + */ + flush() + { + this.setObjectRenderer(this.emptyRenderer); + } + + reset() + { + this.setObjectRenderer(this.emptyRenderer); + } +} diff --git a/packages/core/src/batch/ObjectRenderer.js b/packages/core/src/batch/ObjectRenderer.js new file mode 100644 index 0000000..0c851a9 --- /dev/null +++ b/packages/core/src/batch/ObjectRenderer.js @@ -0,0 +1,48 @@ +import System from '../System'; + +/** + * Base for a common object renderer that can be used as a system renderer plugin. + * + * @class + * @extends PIXI.System + * @memberof PIXI + */ +export default class ObjectRenderer extends System +{ + /** + * Starts the renderer and sets the shader + * + */ + start() + { + // set the shader.. + } + + /** + * Stops the renderer + * + */ + stop() + { + this.flush(); + } + + /** + * Stub method for rendering content and emptying the current batch. + * + */ + flush() + { + // flush! + } + + /** + * Renders an object + * + * @param {PIXI.DisplayObject} object - The object to render. + */ + render(object) // eslint-disable-line no-unused-vars + { + // render the object + } +} diff --git a/packages/core/src/context/ContextSystem.js b/packages/core/src/context/ContextSystem.js new file mode 100644 index 0000000..301aab4 --- /dev/null +++ b/packages/core/src/context/ContextSystem.js @@ -0,0 +1,186 @@ +import System from '../System'; +import { settings } from '@pixi/settings'; + +let CONTEXT_UID = 0; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class ContextSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.webGLVersion = 1; + + this.handleContextLost = this.handleContextLost.bind(this); + this.handleContextRestored = this.handleContextRestored.bind(this); + + this.extensions = {}; + + renderer.view.addEventListener('webglcontextlost', this.handleContextLost, false); + renderer.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + } + + get isLost() + { + return (!this.gl || this.gl.isContextLost()); + } + + contextChange(gl) + { + this.gl = gl; + + // restore a context if it was previously lost + if (gl.isContextLost() && gl.getExtension('WEBGL_lose_context')) + { + gl.getExtension('WEBGL_lose_context').restoreContext(); + } + } + + initFromContext(gl) + { + this.gl = gl; + this.validateContext(gl); + this.renderer.gl = gl; + this.renderer.CONTEXT_UID = CONTEXT_UID++; + this.renderer.runners.contextChange.run(gl); + } + + initFromOptions(options) + { + const gl = this.createContext(this.renderer.view, options); + + this.initFromContext(gl); + } + + /** + * Helper class to create a webGL Context + * + * @class + * @memberof PIXI.glCore + * @param canvas {HTMLCanvasElement} the canvas element that we will get the context from + * @param options {Object} An options object that gets passed in to the canvas element containing the context attributes, + * see https://developer.mozilla.org/en/docs/Web/API/HTMLCanvasElement/getContext for the options available + * @return {WebGLRenderingContext} the WebGL context + */ + createContext(canvas, options) + { + let gl; + + if (settings.PREFER_WEBGL_2) + { + gl = canvas.getContext('webgl2', options); + } + + if (gl) + { + this.webGLVersion = 2; + } + else + { + this.webGLVersion = 1; + + gl = canvas.getContext('webgl', options) + || canvas.getContext('experimental-webgl', options); + + if (!gl) + { + // fail, not able to get a context + throw new Error('This browser does not support webGL. Try using the canvas renderer'); + } + } + + this.gl = gl; + + this.getExtensions(); + + return gl; + } + + getExtensions() + { + // time to set up default etensions that pixi uses.. + const gl = this.gl; + const extensions = this.extensions; + + if (this.webGLVersion === 1) + { + extensions.drawBuffers = gl.getExtension('WEBGL_draw_buffers'); + extensions.depthTexture = gl.getExtension('WEBKIT_WEBGL_depth_texture'); + extensions.floatTexture = gl.getExtension('OES_texture_float'); + extensions.loseContext = gl.getExtension('WEBGL_lose_context'); + + extensions.vertexArrayObject = gl.getExtension('OES_vertex_array_object') + || gl.getExtension('MOZ_OES_vertex_array_object') + || gl.getExtension('WEBKIT_OES_vertex_array_object'); + } + + // we don't use any specific WebGL 2 ones yet! + } + + /** + * Handles a lost webgl context + * + * @private + * @param {WebGLContextEvent} event - The context lost event. + */ + handleContextLost(event) + { + event.preventDefault(); + } + + /** + * Handles a restored webgl context + * + * @private + */ + handleContextRestored() + { + this.renderer.runners.contextChange.run(this.gl); + } + + destroy() + { + const view = this.renderer.view; + + // remove listeners + view.removeEventListener('webglcontextlost', this.handleContextLost); + view.removeEventListener('webglcontextrestored', this.handleContextRestored); + + this.gl.useProgram(null); + + if (this.extensions.loseContext) + { + this.extensions.loseContext.loseContext(); + } + } + + postrender() + { + this.gl.flush(); + } + + validateContext(gl) + { + const attributes = gl.getContextAttributes(); + + // this is going to be fairly simple for now.. but at least we have room to grow! + if (!attributes.stencil) + { + /* eslint-disable max-len */ + + /* eslint-disable no-console */ + console.warn('Provided WebGL context does not have a stencil buffer, masks may not render correctly'); + /* eslint-enable no-console */ + + /* eslint-enable max-len */ + } + } +} diff --git a/packages/core/src/filters/Filter.js b/packages/core/src/filters/Filter.js new file mode 100644 index 0000000..9d8cad8 --- /dev/null +++ b/packages/core/src/filters/Filter.js @@ -0,0 +1,140 @@ +import Shader from '../shader/Shader'; +import Program from '../shader/Program'; +import State from '../state/State'; +import { settings } from '@pixi/settings'; +// import extractUniformsFromSrc from './extractUniformsFromSrc'; +import defaultVertex from './defaultFilter.vert'; +import defaultFragment from './defaultFilter.frag'; + +// let math = require('../../math'); +/** + * @class + * @memberof PIXI + * @extends PIXI.Shader + */ +export default class Filter extends Shader +{ + /** + * @param {string} [vertexSrc] - The source of the vertex shader. + * @param {string} [fragmentSrc] - The source of the fragment shader. + * @param {object} [uniforms] - Custom uniforms to use to augment the built-in ones. + */ + constructor(vertexSrc, fragmentSrc, uniforms) + { + const program = Program.from(vertexSrc, fragmentSrc); + + super(program, uniforms); + + /** + * The padding of the filter. Some filters require extra space to breath such as a blur. + * Increasing this will add extra width and height to the bounds of the object that the + * filter is applied to. + * + * @member {number} + */ + this.padding = 0; + + /** + * The resolution of the filter. Setting this to be lower will lower the quality but + * increase the performance of the filter. + * + * @member {number} + */ + this.resolution = settings.RESOLUTION; + + /** + * If enabled is true the filter is applied, if false it will not. + * + * @member {boolean} + */ + this.enabled = true; + + /** + * If enabled, PixiJS will fit the filter area into boundaries for better performance. + * Switch it off if it does not work for specific shader. + * + * @member {boolean} + */ + this.autoFit = true; + + /** + * Legacy filters use position and uvs from attributes + * @member {boolean} + * @readonly + */ + this.legacy = !!this.program.attributeData.aTextureCoord; + + /** + * the webGL state the filter requires to render + * @member {PIXI.State} + */ + this.state = new State(); + } + + /** + * Applies the filter + * + * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + * @param {boolean} clear - Should the output be cleared before rendering to it + * @param {object} [currentState] - It's current state of filter. + * There are some useful properties in the currentState : + * target, filters, sourceFrame, destinationFrame, renderTarget, resolution + */ + apply(filterManager, input, output, clear, currentState, derp) // eslint-disable-line no-unused-vars + { + // do as you please! + + filterManager.applyFilter(this, input, output, clear, currentState, derp); + + // or just do a regular render.. + } + + /** + * Sets the blendmode of the filter + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL + */ + get blendMode() + { + return this.state.blendMode; + } + + set blendMode(value) // eslint-disable-line require-jsdoc + { + this.state.blendMode = value; + } + + /** + * The default vertex shader source + * + * @static + * @constant + */ + static get defaultVertexSrc() + { + return defaultVertex; + } + + /** + * The default fragment shader source + * + * @static + * @constant + */ + static get defaultFragmentSrc() + { + return defaultFragment; + } +} + +/** + * Used for caching shader IDs + * + * @static + * @private + */ +Filter.SOURCE_KEY_MAP = {}; + diff --git a/packages/core/src/filters/FilterSystem.js b/packages/core/src/filters/FilterSystem.js new file mode 100644 index 0000000..3174318 --- /dev/null +++ b/packages/core/src/filters/FilterSystem.js @@ -0,0 +1,431 @@ +import System from '../System'; + +import RenderTexture from '../renderTexture/RenderTexture'; +import Quad from '../utils/Quad'; +import QuadUv from '../utils/QuadUv'; +import { Rectangle } from '@pixi/math'; +import * as filterTransforms from './filterTransforms'; +import bitTwiddle from 'bit-twiddle'; +import UniformGroup from '../shader/UniformGroup'; +import { DRAW_MODES } from '@pixi/constants'; + +// +/** + * @ignore + * @class + */ +class FilterState +{ + /** + * + */ + constructor() + { + this.renderTexture = null; + this.sourceFrame = new Rectangle(); + this.destinationFrame = new Rectangle(); + this.filters = []; + this.target = null; + this.legacy = false; + this.resolution = 1; + } +} + +/** + * @class + * @memberof PIXI.systems + * @extends PIXI.System + */ +export default class FilterSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + /** + * stores a bunch of PO2 textures used for filtering + * @type {Object} + */ + this.texturePool = {}; + + /** + * a pool for storing filter states, save us creating new ones each tick + * @type {Array} + */ + this.statePool = []; + + /** + * A very simple geometry used when drawing a filter effect to the screen + * @type {Quad} + */ + this.quad = new Quad(); + + this.quadUv = new QuadUv(); + + /** + * Temporary rect for maths + * @type {PIXI.Rectangle} + */ + this.tempRect = new Rectangle(); + + this.activeState = {}; + + /** + * this uniform group is attached to filter uniforms when used + * @type {UniformGroup} + */ + this.globalUniforms = new UniformGroup({ + sourceFrame: this.tempRect, + destinationFrame: this.tempRect, + + // legacy variables + filterArea: new Float32Array(4), + filterClamp: new Float32Array(4), + }, true); + } + + /** + * Adds a new filter to the System. + * + * @param {PIXI.DisplayObject} target - The target of the filter to render. + * @param {PIXI.Filter[]} filters - The filters to apply. + */ + push(target, filters) + { + const renderer = this.renderer; + const filterStack = this.renderer.renderTexture.defaultFilterStack; + const state = this.statePool.pop() || new FilterState(); + + let resolution = filters[0].resolution; + let padding = filters[0].padding; + let autoFit = filters[0].autoFit; + let legacy = filters[0].legacy; + + for (let i = 1; i < filters.length; i++) + { + const filter = filters[i]; + + // lets use the lowest resolution.. + resolution = Math.min(resolution, filter.resolution); + // and the largest amount of padding! + padding = Math.max(padding, filter.padding); + // only auto fit if all filters are autofit + autoFit = autoFit || filter.autoFit; + + legacy = legacy || filter.legacy; + } + + filterStack.push(state); + + state.resolution = resolution; + + state.legacy = legacy; + + // round to whole number based on resolution + // TODO move that to the shader too? + state.sourceFrame = target.filterArea || target.getBounds(true); + + state.sourceFrame.pad(padding); + + if (autoFit) + { + state.sourceFrame.fit(this.renderer.renderTexture.destinationFrame); + } + + state.sourceFrame.round(resolution); + + state.renderTexture = this.getPotFilterTexture(state.sourceFrame.width, state.sourceFrame.height, resolution); + state.filters = filters; + + state.destinationFrame.width = state.renderTexture.width; + state.destinationFrame.height = state.renderTexture.height; + + state.renderTexture.filterFrame = state.sourceFrame; + renderer.renderTexture.bind(state.renderTexture, state.sourceFrame);// /, state.destinationFrame); + renderer.renderTexture.clear(); + } + + /** + * Pops off the filter and applies it. + * + */ + pop() + { + const renderer = this.renderer; + const filterStack = renderer.renderTexture.defaultFilterStack; + const state = filterStack.pop(); + const filters = state.filters; + + this.activeState = state; + + const globalUniforms = this.globalUniforms.uniforms; + + globalUniforms.sourceFrame = state.sourceFrame; + globalUniforms.destinationFrame = state.destinationFrame; + globalUniforms.resolution = state.resolution; + + // only update the rect if its legacy.. + if (state.legacy) + { + const filterArea = globalUniforms.filterArea; + const filterClamp = globalUniforms.filterClamp; + + filterArea[0] = state.destinationFrame.width; + filterArea[1] = state.destinationFrame.height; + filterArea[2] = state.sourceFrame.x; + filterArea[3] = state.sourceFrame.y; + + filterClamp[0] = 0.5 / state.resolution / state.destinationFrame.width; + filterClamp[1] = 0.5 / state.resolution / state.destinationFrame.height; + filterClamp[2] = (state.sourceFrame.width - 0.5) / state.resolution / state.destinationFrame.width; + filterClamp[3] = (state.sourceFrame.height - 0.5) / state.resolution / state.destinationFrame.height; + } + + this.globalUniforms.update(); + + const lastState = filterStack[filterStack.length - 1]; + + if (filters.length === 1) + { + filters[0].apply(this, state.renderTexture, lastState.renderTexture, false, state); + + this.returnFilterTexture(state.renderTexture); + } + else + { + let flip = state.renderTexture; + let flop = this.getPotFilterTexture( + flip.width, + flip.height, + state.resolution + ); + + flop.filterFrame = flip.filterFrame; + + let i = 0; + + for (i = 0; i < filters.length - 1; ++i) + { + filters[i].apply(this, flip, flop, true, state); + + const t = flip; + + flip = flop; + flop = t; + } + + filters[i].apply(this, flip, lastState.renderTexture, false, state); + + this.returnFilterTexture(flip); + this.returnFilterTexture(flop); + } + + this.statePool.push(state); + } + + /** + * Draws a filter. + * + * @param {PIXI.Filter} filter - The filter to draw. + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + * @param {boolean} clear - Should the output be cleared before rendering to it + */ + applyFilter(filter, input, output, clear) + { + const renderer = this.renderer; + + renderer.renderTexture.bind(output, output ? output.filterFrame : null); + + if (clear) + { + // gl.disable(gl.SCISSOR_TEST); + renderer.renderTexture.clear(); + // gl.enable(gl.SCISSOR_TEST); + } + + // set the uniforms.. + filter.uniforms.uSampler = input; + filter.uniforms.filterGlobals = this.globalUniforms; + + // TODO make it so that the order of this does not matter.. + // because it does at the moment cos of global uniforms. + // they need to get resynced + + renderer.state.setState(filter.state); + renderer.shader.bind(filter); + + if (filter.legacy) + { + this.quadUv.map(input._frame, input.filterFrame); + + renderer.geometry.bind(this.quadUv); + renderer.geometry.draw(DRAW_MODES.TRIANGLES); + } + else + { + renderer.geometry.bind(this.quad); + renderer.geometry.draw(DRAW_MODES.TRIANGLE_STRIP); + } + } + + /** + * Calculates the mapped matrix. + * + * TODO playing around here.. this is temporary - (will end up in the shader) + * this returns a matrix that will normalise map filter cords in the filter to screen space + * + * @param {PIXI.Matrix} outputMatrix - the matrix to output to. + * @return {PIXI.Matrix} The mapped matrix. + */ + calculateScreenSpaceMatrix(outputMatrix) + { + const currentState = this.activeState; + + return filterTransforms.calculateScreenSpaceMatrix( + outputMatrix, + currentState.sourceFrame, + currentState.destinationFrame + ); + } + + /** + * This will map the filter coord so that a texture can be used based on the transform of a sprite + * + * @param {PIXI.Matrix} outputMatrix - The matrix to output to. + * @param {PIXI.Sprite} sprite - The sprite to map to. + * @return {PIXI.Matrix} The mapped matrix. + */ + calculateSpriteMatrix(outputMatrix, sprite) + { + const currentState = this.activeState; + + return filterTransforms.calculateSpriteMatrix( + outputMatrix, + currentState.sourceFrame, + currentState.destinationFrame, + sprite + ); + } + + /** + * Destroys this Filter System. + * + * @param {boolean} [contextLost=false] context was lost, do not free shaders + * + */ + destroy(contextLost = false) + { + if (!contextLost) + { + this.emptyPool(); + } + else + { + this.texturePool = {}; + } + } + + /** + * Gets a Power-of-Two render texture. + * + * TODO move to a seperate class could be on renderer? + * also - could cause issue with multiple contexts? + * + * @private + * @param {WebGLRenderingContext} gl - The webgl rendering context + * @param {number} minWidth - The minimum width of the render target. + * @param {number} minHeight - The minimum height of the render target. + * @param {number} resolution - The resolution of the render target. + * @return {PIXI.RenderTarget} The new render target. + */ + getPotFilterTexture(minWidth, minHeight, resolution) + { + minWidth = bitTwiddle.nextPow2(minWidth); + minHeight = bitTwiddle.nextPow2(minHeight); + resolution = resolution || 1; + + const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + + if (!this.texturePool[key]) + { + this.texturePool[key] = []; + } + + let renderTexture = this.texturePool[key].pop(); + + if (!renderTexture) + { + // temporary bypass cache.. + // internally - this will cause a texture to be bound.. + renderTexture = RenderTexture.create({ + width: minWidth, + height: minHeight, + resolution, + }); + } + + return renderTexture; + } + + /** + * Gets extra render texture to use inside current filter + * + * @param {number} resolution resolution of the renderTexture + * @returns {PIXI.RenderTexture} + */ + getFilterTexture(resolution) + { + const rt = this.activeState.renderTexture; + + const filterTexture = this.getPotFilterTexture(rt.width, rt.height, resolution || rt.baseTexture.resolution); + + filterTexture.filterFrame = rt.filterFrame; + + return filterTexture; + } + + /** + * Frees a render texture back into the pool. + * + * @param {PIXI.RenderTarget} renderTexture - The renderTarget to free + */ + returnFilterTexture(renderTexture) + { + renderTexture.filterFrame = null; + + const base = renderTexture.baseTexture; + + const minWidth = base.width; + const minHeight = base.height; + + const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + + this.texturePool[key].push(renderTexture); + } + + /** + * Empties the texture pool. + * + */ + emptyPool() + { + for (const i in this.texturePool) + { + const textures = this.texturePool[i]; + + if (textures) + { + for (let j = 0; j < textures.length; j++) + { + textures[j].destroy(true); + } + } + } + + this.texturePool = {}; + } +} diff --git a/packages/core/src/filters/defaultFilter.frag b/packages/core/src/filters/defaultFilter.frag new file mode 100644 index 0000000..a14c1be --- /dev/null +++ b/packages/core/src/filters/defaultFilter.frag @@ -0,0 +1,22 @@ +varying vec2 vTextureCoord; +varying vec2 vFilterCoord; + +uniform sampler2D uSampler; +uniform sampler2D filterSampler; + +void main(void){ + vec4 masky = texture2D(filterSampler, vFilterCoord); + vec4 sample = texture2D(uSampler, vTextureCoord); + vec4 color; + if(mod(vFilterCoord.x, 1.0) > 0.5) + { + color = vec4(1.0, 0.0, 0.0, 1.0); + } + else + { + color = vec4(0.0, 1.0, 0.0, 1.0); + } + // gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0); + gl_FragColor = mix(sample, masky, 0.5); + gl_FragColor *= sample.a; +} diff --git a/packages/core/src/filters/defaultFilter.vert b/packages/core/src/filters/defaultFilter.vert new file mode 100644 index 0000000..e1febca --- /dev/null +++ b/packages/core/src/filters/defaultFilter.vert @@ -0,0 +1,14 @@ +attribute vec2 aVertexPosition; +attribute vec2 aTextureCoord; + +uniform mat3 projectionMatrix; +uniform mat3 filterMatrix; + +varying vec2 vTextureCoord; +varying vec2 vFilterCoord; + +void main(void){ + gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); + vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy; + vTextureCoord = aTextureCoord ; +} diff --git a/packages/core/src/filters/filterTransforms.js b/packages/core/src/filters/filterTransforms.js new file mode 100644 index 0000000..87a3cd6 --- /dev/null +++ b/packages/core/src/filters/filterTransforms.js @@ -0,0 +1,50 @@ +import { Matrix } from '@pixi/math'; + +/** + * Calculates the mapped matrix + * @param filterArea {Rectangle} The filter area + * @param sprite {Sprite} the target sprite + * @param outputMatrix {Matrix} @alvin + * @private + */ +// this returns a matrix that will normalise map filter cords in the filter to screen space +export function calculateScreenSpaceMatrix(outputMatrix, filterArea, textureSize) +{ + // TODO unwrap? + const mappedMatrix = outputMatrix.identity(); + + mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); + + mappedMatrix.scale(textureSize.width, textureSize.height); + + return mappedMatrix; +} + +export function calculateNormalizedScreenSpaceMatrix(outputMatrix, filterArea, textureSize) +{ + const mappedMatrix = outputMatrix.identity(); + + mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); + + const translateScaleX = (textureSize.width / filterArea.width); + const translateScaleY = (textureSize.height / filterArea.height); + + mappedMatrix.scale(translateScaleX, translateScaleY); + + return mappedMatrix; +} + +// this will map the filter coord so that a texture can be used based on the transform of a sprite +export function calculateSpriteMatrix(outputMatrix, filterArea, textureSize, sprite) +{ + const orig = sprite._texture.orig; + const mappedMatrix = outputMatrix.set(textureSize.width, 0, 0, textureSize.height, filterArea.x, filterArea.y); + const worldTransform = sprite.worldTransform.copyTo(Matrix.TEMP_MATRIX); + + worldTransform.invert(); + mappedMatrix.prepend(worldTransform); + mappedMatrix.scale(1.0 / orig.width, 1.0 / orig.height); + mappedMatrix.translate(sprite.anchor.x, sprite.anchor.y); + + return mappedMatrix; +} diff --git a/packages/core/src/filters/spriteMask/SpriteMaskFilter.js b/packages/core/src/filters/spriteMask/SpriteMaskFilter.js new file mode 100644 index 0000000..91697b7 --- /dev/null +++ b/packages/core/src/filters/spriteMask/SpriteMaskFilter.js @@ -0,0 +1,63 @@ +import Filter from '../Filter'; +import { Matrix } from '@pixi/math'; +import vertex from './spriteMaskFilter.vert'; +import fragment from './spriteMaskFilter.frag'; +import { default as TextureMatrix } from '../../textures/TextureMatrix'; + +/** + * The SpriteMaskFilter class + * + * @class + * @extends PIXI.Filter + * @memberof PIXI + */ +export default class SpriteMaskFilter extends Filter +{ + /** + * @param {PIXI.Sprite} sprite - the target sprite + */ + constructor(sprite) + { + const maskMatrix = new Matrix(); + + super(vertex, fragment); + + sprite.renderable = false; + + this.maskSprite = sprite; + this.maskMatrix = maskMatrix; + } + + /** + * Applies the filter + * + * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + */ + apply(filterManager, input, output) + { + const maskSprite = this.maskSprite; + const tex = this.maskSprite.texture; + + if (!tex.valid) + { + return; + } + if (!tex.transform) + { + // margin = 0.0, let it bleed a bit, shader code becomes easier + // assuming that atlas textures were made with 1-pixel padding + tex.transform = new TextureMatrix(tex, 0.0); + } + tex.transform.update(); + + this.uniforms.mask = tex; + this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite) + .prepend(tex.transform.mapCoord); + this.uniforms.alpha = maskSprite.worldAlpha; + this.uniforms.maskClamp = tex.transform.uClampFrame; + + filterManager.applyFilter(this, input, output); + } +} diff --git a/packages/core/src/filters/spriteMask/spriteMaskFilter.frag b/packages/core/src/filters/spriteMask/spriteMaskFilter.frag new file mode 100644 index 0000000..0e4aef8 --- /dev/null +++ b/packages/core/src/filters/spriteMask/spriteMaskFilter.frag @@ -0,0 +1,23 @@ +varying vec2 vMaskCoord; +varying vec2 vTextureCoord; + +uniform sampler2D uSampler; +uniform sampler2D mask; +uniform float alpha; +uniform vec4 maskClamp; + +void main(void) +{ + float clip = step(3.5, + step(maskClamp.x, vMaskCoord.x) + + step(maskClamp.y, vMaskCoord.y) + + step(vMaskCoord.x, maskClamp.z) + + step(vMaskCoord.y, maskClamp.w)); + + vec4 original = texture2D(uSampler, vTextureCoord); + vec4 masky = texture2D(mask, vMaskCoord); + + original *= (masky.r * masky.a * alpha * clip); + + gl_FragColor = original; +} diff --git a/packages/core/src/filters/spriteMask/spriteMaskFilter.vert b/packages/core/src/filters/spriteMask/spriteMaskFilter.vert new file mode 100644 index 0000000..d23a30c --- /dev/null +++ b/packages/core/src/filters/spriteMask/spriteMaskFilter.vert @@ -0,0 +1,16 @@ +attribute vec2 aVertexPosition; +attribute vec2 aTextureCoord; + +uniform mat3 projectionMatrix; +uniform mat3 otherMatrix; + +varying vec2 vMaskCoord; +varying vec2 vTextureCoord; + +void main(void) +{ + gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); + + vTextureCoord = aTextureCoord; + vMaskCoord = ( otherMatrix * vec3( aTextureCoord, 1.0) ).xy; +} diff --git a/packages/core/src/framebuffer/FrameBuffer.js b/packages/core/src/framebuffer/FrameBuffer.js new file mode 100644 index 0000000..84e9fcc --- /dev/null +++ b/packages/core/src/framebuffer/FrameBuffer.js @@ -0,0 +1,106 @@ +import Texture from '../textures/BaseTexture'; +import { FORMATS, TYPES } from '@pixi/constants'; + +/** + * Frame buffer + * @class + * @memberof PIXI + */ +export default class FrameBuffer +{ + constructor(width, height) + { + this.width = width || 100; + this.height = height || 100; + + this.stencil = false; + this.depth = false; + + this.dirtyId = 0; + this.dirtyFormat = 0; + this.dirtySize = 0; + + this.depthTexture = null; + this.colorTextures = []; + + this.glFrameBuffers = {}; + } + + get colorTexture() + { + return this.colorTextures[0]; + } + + addColorTexture(index, texture) + { + // TODO add some validation to the texture - same width / height etc? + this.colorTextures[index || 0] = texture || new Texture(null, { scaleMode: 0, + resolution: 1, + mipmap: false, + width: this.width, + height: this.height });// || new Texture(); + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + addDepthTexture(texture) + { + /* eslint-disable max-len */ + this.depthTexture = texture || new Texture(null, { scaleMode: 0, + resolution: 1, + width: this.width, + height: this.height, + mipmap: false, + format: FORMATS.DEPTH_COMPONENT, + type: TYPES.UNSIGNED_SHORT });// UNSIGNED_SHORT; + /* eslint-disable max-len */ + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + enableDepth() + { + this.depth = true; + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + enableStencil() + { + this.stencil = true; + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + resize(width, height) + { + if (width === this.width && height === this.height) return; + + this.width = width; + this.height = height; + + this.dirtyId++; + this.dirtySize++; + + for (let i = 0; i < this.colorTextures.length; i++) + { + this.colorTextures[i].setSize(width, height); + } + + if (this.depthTexture) + { + this.depthTexture.setSize(width, height); + } + } +} diff --git a/packages/core/src/framebuffer/FramebufferSystem.js b/packages/core/src/framebuffer/FramebufferSystem.js new file mode 100644 index 0000000..2cc2437 --- /dev/null +++ b/packages/core/src/framebuffer/FramebufferSystem.js @@ -0,0 +1,246 @@ +import System from '../System'; +import { Rectangle } from '@pixi/math'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class FramebufferSystem extends System +{ + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + contextChange() + { + this.gl = this.renderer.gl; + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + this.current = null; + this.viewport = new Rectangle(); + + this.drawBufferExtension = this.renderer.context.extensions.drawBuffers; + } + + bind(framebuffer, frame) + { + const gl = this.gl; + + this.current = framebuffer; + + if (framebuffer) + { + // TODO cacheing layer! + + const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID] || this.initFramebuffer(framebuffer); + + gl.bindFramebuffer(gl.FRAMEBUFFER, fbo.framebuffer); + // makesure all textures are unbound.. + + // now check for updates... + if (fbo.dirtyId !== framebuffer.dirtyId) + { + fbo.dirtyId = framebuffer.dirtyId; + + if (fbo.dirtyFormat !== framebuffer.dirtyFormat) + { + fbo.dirtyFormat = framebuffer.dirtyFormat; + this.updateFramebuffer(framebuffer); + } + else if (fbo.dirtySize !== framebuffer.dirtySize) + { + fbo.dirtySize = framebuffer.dirtySize; + this.resizeFramebuffer(framebuffer); + } + } + + for (let i = 0; i < framebuffer.colorTextures.length; i++) + { + if (framebuffer.colorTextures[i].texturePart) + { + this.renderer.texture.unbind(framebuffer.colorTextures[i].texture); + } + else + { + this.renderer.texture.unbind(framebuffer.colorTextures[i]); + } + } + + if (framebuffer.depthTexture) + { + this.renderer.texture.unbind(framebuffer.depthTexture); + } + + if (frame) + { + this.setViewport(frame.x, frame.y, frame.width, frame.height); + } + else + { + this.setViewport(0, 0, framebuffer.width, framebuffer.height); + } + } + else + { + gl.bindFramebuffer(gl.FRAMEBUFFER, null); + + if (frame) + { + this.setViewport(frame.x, frame.y, frame.width, frame.height); + } + else + { + this.setViewport(0, 0, this.renderer.width, this.renderer.height); + } + } + } + + setViewport(x, y, width, height) + { + const v = this.viewport; + + if (v.width !== width || v.height !== height || v.x !== x || v.y !== y) + { + v.x = x; + v.y = y; + v.width = width; + v.height = height; + + this.gl.viewport(x, y, width, height); + } + } + + get size() + { + if (this.current) + { + // TODO store temp + return { x: 0, y: 0, width: this.current.width, height: this.current.height }; + } + + return { x: 0, y: 0, width: this.renderer.width, height: this.renderer.height }; + } + + clear(r, g, b, a) + { + const gl = this.gl; + + // TODO clear color can be set only one right? + gl.clearColor(r, g, b, a); + gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); + } + + // private functions... + + initFramebuffer(framebuffer) + { + const gl = this.gl; + + // TODO - make this a class? + const fbo = { + framebuffer: gl.createFramebuffer(), + stencil: null, + dirtyId: 0, + dirtyFormat: 0, + dirtySize: 0, + }; + + framebuffer.glFrameBuffers[this.CONTEXT_UID] = fbo; + + return fbo; + } + + resizeFramebuffer(framebuffer) + { + const gl = this.gl; + + if (framebuffer.stencil || framebuffer.depth) + { + gl.bindRenderbuffer(gl.RENDERBUFFER, this.stencil); + gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); + } + } + + updateFramebuffer(framebuffer) + { + const gl = this.gl; + + const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID]; + + // bind the color texture + const colorTextures = framebuffer.colorTextures; + + let count = colorTextures.length; + + if (!this.drawBufferExtension) + { + count = Math.min(count, 1); + } + + const activeTextures = []; + + for (let i = 0; i < count; i++) + { + const texture = framebuffer.colorTextures[i]; + + if (texture.texturePart) + { + this.renderer.texture.bind(texture.texture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.COLOR_ATTACHMENT0 + i, + gl.TEXTURE_CUBE_MAP_NEGATIVE_X + texture.side, + texture.texture._glTextures[this.CONTEXT_UID].texture, + 0); + } + else + { + this.renderer.texture.bind(texture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.COLOR_ATTACHMENT0 + i, + gl.TEXTURE_2D, + texture._glTextures[this.CONTEXT_UID].texture, + 0); + } + + activeTextures.push(gl.COLOR_ATTACHMENT0 + i); + } + + if (this.drawBufferExtension && activeTextures.length > 1) + { + this.drawBufferExtension.drawBuffersWEBGL(activeTextures); + } + + if (framebuffer.depthTexture) + { + const depthTextureExt = this.renderer.context.extensions.depthTexture; + + if (depthTextureExt) + { + const depthTexture = framebuffer.depthTexture; + + this.renderer.texture.bind(depthTexture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.DEPTH_ATTACHMENT, + gl.TEXTURE_2D, + depthTexture._glTextures[this.CONTEXT_UID].texture, + 0); + } + } + + if (framebuffer.stencil || framebuffer.depth) + { + fbo.stencil = gl.createRenderbuffer(); + + gl.bindRenderbuffer(gl.RENDERBUFFER, fbo.stencil); + + // TODO.. this is depth AND stencil? + gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, fbo.stencil); + gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); + // fbo.enableStencil(); + } + } +} diff --git a/packages/core/src/geometry/GLBuffer.js b/packages/core/src/geometry/GLBuffer.js new file mode 100644 index 0000000..0477e8a --- /dev/null +++ b/packages/core/src/geometry/GLBuffer.js @@ -0,0 +1,9 @@ +export default class GLBuffer +{ + constructor(buffer) + { + this.buffer = buffer; + this.updateID = -1; + this.byteLength = -1; + } +} diff --git a/packages/core/src/geometry/GeometrySystem.js b/packages/core/src/geometry/GeometrySystem.js new file mode 100644 index 0000000..959f827 --- /dev/null +++ b/packages/core/src/geometry/GeometrySystem.js @@ -0,0 +1,388 @@ +import System from '../System'; +import GLBuffer from './GLBuffer'; + +const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class GeometrySystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this._activeGeometry = null; + this._activeVao = null; + + this.hasVao = true; + this.hasInstance = true; + } + + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + contextChange() + { + const gl = this.gl = this.renderer.gl; + + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + + // webgl2 + if (!gl.createVertexArray) + { + // webgl 1! + let nativeVaoExtension = this.renderer.context.extensions.vertexArrayObject; + + if (this.renderer.legacy) + { + nativeVaoExtension = null; + } + + if (nativeVaoExtension) + { + gl.createVertexArray = () => + nativeVaoExtension.createVertexArrayOES(); + + gl.bindVertexArray = (vao) => + nativeVaoExtension.bindVertexArrayOES(vao); + + gl.deleteVertexArray = (vao) => + nativeVaoExtension.deleteVertexArrayOES(vao); + } + else + { + this.hasVao = false; + gl.createVertexArray = () => + { + // empty + }; + + gl.bindVertexArray = () => + { + // empty + }; + + gl.deleteVertexArray = () => + { + // empty + }; + } + } + + if (!gl.vertexAttribDivisor) + { + const instanceExt = gl.getExtension('ANGLE_instanced_arrays'); + + if (instanceExt) + { + gl.vertexAttribDivisor = (a, b) => + instanceExt.vertexAttribDivisorANGLE(a, b); + + gl.drawElementsInstanced = (a, b, c, d, e) => + instanceExt.drawElementsInstancedANGLE(a, b, c, d, e); + + gl.drawArraysInstanced = (a, b, c, d) => + instanceExt.drawArraysInstancedANGLE(a, b, c, d); + } + else + { + this.hasInstance = false; + } + } + } + + /** + * Binds geometry so that is can be drawn. Creating a Vao if required + * @private + * @param {PIXI.Geometry} geometry instance of geometry to bind + */ + bind(geometry, shader) + { + shader = shader || this.renderer.shader.shader; + + const gl = this.gl; + + // not sure the best way to address this.. + // currently different shaders require different VAOs for the same geometry + // Still mulling over the best way to solve this one.. + // will likely need to modify the shader attribute locations at run time! + let vaos = geometry.glVertexArrayObjects[this.CONTEXT_UID]; + + if (!vaos) + { + geometry.glVertexArrayObjects[this.CONTEXT_UID] = vaos = {}; + } + + const vao = vaos[shader.program.id] || this.initGeometryVao(geometry, shader.program); + + this._activeGeometry = geometry; + + if (this._activeVao !== vao) + { + this._activeVao = vao; + + if (this.hasVao) + { + gl.bindVertexArray(vao); + } + else + { + this.activateVao(geometry, shader.program); + } + } + + // TODO - optimise later! + // don't need to loop through if nothing changed! + // maybe look to add an 'autoupdate' to geometry? + this.updateBuffers(); + } + + reset() + { + this.unbind(); + } + + updateBuffers() + { + const geometry = this._activeGeometry; + const gl = this.gl; + + for (let i = 0; i < geometry.buffers.length; i++) + { + const buffer = geometry.buffers[i]; + + const glBuffer = buffer._glBuffers[this.CONTEXT_UID]; + + if (buffer._updateID !== glBuffer.updateID) + { + glBuffer.updateID = buffer._updateID; + + // TODO can cache this on buffer! maybe added a getter / setter? + const type = buffer.index ? gl.ELEMENT_ARRAY_BUFFER : gl.ARRAY_BUFFER; + const drawType = buffer.static ? gl.STATIC_DRAW : gl.DYNAMIC_DRAW; + + gl.bindBuffer(type, glBuffer.buffer); + + if (glBuffer.byteLength >= buffer.data.byteLength) + { + // offset is always zero for now! + gl.bufferSubData(type, 0, buffer.data); + } + else + { + gl.bufferData(type, buffer.data, drawType); + } + } + } + } + + checkCompatability(geometry, program) + { + // geometry must have at least all the attributes that the shader requires. + const geometryAttributes = geometry.attributes; + const shaderAttributes = program.attributeData; + + for (const j in shaderAttributes) + { + if (!geometryAttributes[j]) + { + throw new Error(`shader and geometry incompatible, geometry missing the "${j}" attribute`); + } + } + } + + /** + * Creates a Vao with the same structure as the geometry and stores it on the geometry. + * @private + * @param {PIXI.Geometry} geometry instance of geometry to to generate Vao for + */ + initGeometryVao(geometry, program) + { + this.checkCompatability(geometry, program); + + const gl = this.gl; + const CONTEXT_UID = this.CONTEXT_UID; + const buffers = geometry.buffers; + const attributes = geometry.attributes; + + const tempStride = {}; + const tempStart = {}; + + for (const j in buffers) + { + tempStride[j] = 0; + tempStart[j] = 0; + } + + for (const j in attributes) + { + if (!attributes[j].size && program.attributeData[j]) + { + attributes[j].size = program.attributeData[j].size; + } + + tempStride[attributes[j].buffer] += attributes[j].size * byteSizeMap[attributes[j].type]; + } + + for (const j in attributes) + { + const attribute = attributes[j]; + const attribSize = attribute.size; + + if (attribute.stride === undefined) + { + if (tempStride[attribute.buffer] === attribSize * byteSizeMap[attribute.type]) + { + attribute.stride = 0; + } + else + { + attribute.stride = tempStride[attribute.buffer]; + } + } + + if (attribute.start === undefined) + { + attribute.start = tempStart[attribute.buffer]; + + tempStart[attribute.buffer] += attribSize * byteSizeMap[attribute.type]; + } + } + + // first update - and create the buffers! + // only create a gl buffer if it actually gets + for (let i = 0; i < buffers.length; i++) + { + const buffer = buffers[i]; + + if (!buffer._glBuffers[CONTEXT_UID]) + { + buffer._glBuffers[CONTEXT_UID] = new GLBuffer(gl.createBuffer()); + } + } + + // TODO - maybe make this a data object? + // lets wait to see if we need to first! + const vao = gl.createVertexArray(); + + gl.bindVertexArray(vao); + + this.activateVao(geometry, program); + + gl.bindVertexArray(null); + + // add it to the cache! + geometry.glVertexArrayObjects[this.CONTEXT_UID][program.id] = vao; + + return vao; + } + + activateVao(geometry, program) + { + const gl = this.gl; + const CONTEXT_UID = this.CONTEXT_UID; + const buffers = geometry.buffers; + const attributes = geometry.attributes; + + if (geometry.indexBuffer) + { + // first update the index buffer if we have one.. + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, geometry.indexBuffer._glBuffers[CONTEXT_UID].buffer); + } + + let lastBuffer = null; + + // add a new one! + for (const j in attributes) + { + const attribute = attributes[j]; + const buffer = buffers[attribute.buffer]; + const glBuffer = buffer._glBuffers[CONTEXT_UID]; + + if (program.attributeData[j]) + { + if (lastBuffer !== glBuffer) + { + gl.bindBuffer(gl.ARRAY_BUFFER, glBuffer.buffer); + + lastBuffer = glBuffer; + } + + const location = program.attributeData[j].location; + + // TODO introduce state again + // we can optimise this for older devices that have no VAOs + gl.enableVertexAttribArray(location); + + gl.vertexAttribPointer(location, + attribute.size, + attribute.type || gl.FLOAT, + attribute.normalized, + attribute.stride, + attribute.start); + + if (attribute.instance) + { + // TODO calculate instance count based of this... + if (this.hasInstance) + { + gl.vertexAttribDivisor(location, 1); + } + else + { + throw new Error('geometry error, GPU Instancing is not supported on this device'); + } + } + } + } + } + + draw(type, size, start, instanceCount) + { + const gl = this.gl; + const geometry = this._activeGeometry; + + // TODO.. this should not change so maybe cache the function? + + if (geometry.indexBuffer) + { + if (geometry.instanced) + { + /* eslint-disable max-len */ + gl.drawElementsInstanced(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2, instanceCount || 1); + /* eslint-enable max-len */ + } + else + { + gl.drawElements(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2); + } + } + else + if (geometry.instanced) + { + // TODO need a better way to calculate size.. + gl.drawArraysInstanced(type, start, size || geometry.getSize(), instanceCount || 1); + } + else + { + gl.drawArrays(type, start, size || geometry.getSize()); + } + + return this; + } + + unbind() + { + this.gl.bindVertexArray(null); + this._activeVao = null; + this._activeGeometry = null; + } +} diff --git a/packages/core/src/geometry/utils/setVertexAttribArrays.js b/packages/core/src/geometry/utils/setVertexAttribArrays.js new file mode 100644 index 0000000..23b6959 --- /dev/null +++ b/packages/core/src/geometry/utils/setVertexAttribArrays.js @@ -0,0 +1,54 @@ +// var GL_MAP = {}; + +/** + * @param gl {WebGLRenderingContext} The current WebGL context + * @param attribs {*} + * @param state {*} + */ +export default function setVertexAttribArrays(gl, attribs, state) +{ + let i; + + if (state) + { + const tempAttribState = state.tempAttribState; + const attribState = state.attribState; + + for (i = 0; i < tempAttribState.length; i++) + { + tempAttribState[i] = false; + } + + // set the new attribs + for (i = 0; i < attribs.length; i++) + { + tempAttribState[attribs[i].attribute.location] = true; + } + + for (i = 0; i < attribState.length; i++) + { + if (attribState[i] !== tempAttribState[i]) + { + attribState[i] = tempAttribState[i]; + + if (state.attribState[i]) + { + gl.enableVertexAttribArray(i); + } + else + { + gl.disableVertexAttribArray(i); + } + } + } + } + else + { + for (i = 0; i < attribs.length; i++) + { + const attrib = attribs[i]; + + gl.enableVertexAttribArray(attrib.attribute.location); + } + } +} diff --git a/packages/core/src/index.js b/packages/core/src/index.js index 58c456a..fd1f547 100644 --- a/packages/core/src/index.js +++ b/packages/core/src/index.js @@ -1,30 +1,30 @@ import * as resources from './textures/resources'; -import * as systems from './renderers/systems'; +import * as systems from './systems'; -export { resources }; export { systems }; +export { resources }; -export { default as Renderer } from './renderers/Renderer'; -export { default as AbstractRenderer } from './renderers/AbstractRenderer'; -export { default as FrameBuffer } from './textures/FrameBuffer'; +export { default as System } from './System'; +export { default as Renderer } from './Renderer'; +export { default as AbstractRenderer } from './AbstractRenderer'; +export { default as FrameBuffer } from './framebuffer/FrameBuffer'; export { default as CubeTexture } from './textures/CubeTexture'; export { default as BaseTexture } from './textures/BaseTexture'; export { default as Texture } from './textures/Texture'; export { default as TextureMatrix } from './textures/TextureMatrix'; -export { default as RenderTexture } from './textures/RenderTexture'; -export { default as BaseRenderTexture } from './textures/BaseRenderTexture'; +export { default as RenderTexture } from './renderTexture/RenderTexture'; +export { default as BaseRenderTexture } from './renderTexture/BaseRenderTexture'; export { default as TextureUvs } from './textures/TextureUvs'; -export { default as State } from './renderers/State'; -export { default as ObjectRenderer } from './renderers/utils/ObjectRenderer'; -export { default as Quad } from './renderers/utils/Quad'; -export { default as QuadUv } from './renderers/utils/QuadUv'; -export { default as checkMaxIfStatmentsInShader } from './renderers/utils/checkMaxIfStatmentsInShader'; +export { default as State } from './state/State'; +export { default as ObjectRenderer } from './batch/ObjectRenderer'; +export { default as Quad } from './utils/Quad'; +export { default as QuadUv } from './utils/QuadUv'; +export { default as checkMaxIfStatmentsInShader } from './shader/utils/checkMaxIfStatmentsInShader'; export { default as Shader } from './shader/Shader'; export { default as Program } from './shader/Program'; export { default as UniformGroup } from './shader/UniformGroup'; -export { default as SpriteMaskFilter } from './renderers/filters/spriteMask/SpriteMaskFilter'; -export { default as Filter } from './renderers/filters/Filter'; +export { default as SpriteMaskFilter } from './filters/spriteMask/SpriteMaskFilter'; +export { default as Filter } from './filters/Filter'; export { default as Attribute } from './geometry/Attribute'; export { default as Buffer } from './geometry/Buffer'; export { default as Geometry } from './geometry/Geometry'; - diff --git a/packages/core/src/mask/MaskSystem.js b/packages/core/src/mask/MaskSystem.js new file mode 100644 index 0000000..99174c2 --- /dev/null +++ b/packages/core/src/mask/MaskSystem.js @@ -0,0 +1,199 @@ +import System from '../System'; +import AlphaMaskFilter from '../filters/spriteMask/SpriteMaskFilter'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class MaskSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + // TODO - we don't need both! + this.scissor = false; + this.scissorData = null; + this.scissorRenderTarget = null; + + this.enableScissor = false; + + this.alphaMaskPool = []; + this.alphaMaskIndex = 0; + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.DisplayObject} target - Display Object to push the mask to + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + push(target, maskData) + { + // TODO the root check means scissor rect will not + // be used on render textures more info here: + // https://github.com/pixijs/pixi.js/pull/3545 + + if (maskData.texture) + { + this.pushSpriteMask(target, maskData); + } + else if (this.enableScissor + && !this.scissor + && this.renderer._activeRenderTarget.root + && !this.renderer.stencil.stencilMaskStack.length + && maskData.isFastRect()) + { + const matrix = maskData.worldTransform; + + let rot = Math.atan2(matrix.b, matrix.a); + + // use the nearest degree! + rot = Math.round(rot * (180 / Math.PI)); + + if (rot % 90) + { + this.pushStencilMask(maskData); + } + else + { + this.pushScissorMask(target, maskData); + } + } + else + { + this.pushStencilMask(maskData); + } + } + + /** + * Removes the last mask from the mask stack and doesn't return it. + * + * @param {PIXI.DisplayObject} target - Display Object to pop the mask from + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + pop(target, maskData) + { + if (maskData.texture) + { + this.popSpriteMask(target, maskData); + } + else if (this.enableScissor && !this.renderer.stencil.stencilMaskStack.length) + { + this.popScissorMask(target, maskData); + } + else + { + this.popStencilMask(target, maskData); + } + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.RenderTarget} target - Display Object to push the sprite mask to + * @param {PIXI.Sprite} maskData - Sprite to be used as the mask + */ + pushSpriteMask(target, maskData) + { + let alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; + + if (!alphaMaskFilter) + { + alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; + } + + alphaMaskFilter[0].resolution = this.renderer.resolution; + alphaMaskFilter[0].maskSprite = maskData; + + // TODO - may cause issues! + target.filterArea = maskData.getBounds(true); + + this.renderer.filterSystem.pushFilter(target, alphaMaskFilter); + + this.alphaMaskIndex++; + } + + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popSpriteMask() + { + this.renderer.filterSystem.popFilter(); + this.alphaMaskIndex--; + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + pushStencilMask(maskData) + { + this.renderer.batch.flush(); + this.renderer.stencil.pushStencil(maskData); + } + + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popStencilMask() + { + // this.renderer.currentRenderer.stop(); + this.renderer.stencil.popStencil(); + } + + /** + * + * @param {PIXI.DisplayObject} target - Display Object to push the mask to + * @param {PIXI.Graphics} maskData - The masking data. + */ + pushScissorMask(target, maskData) + { + maskData.renderable = true; + + const renderTarget = this.renderer._activeRenderTarget; + + const bounds = maskData.getBounds(); + + bounds.fit(renderTarget.size); + maskData.renderable = false; + + this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); + + const resolution = this.renderer.resolution; + + this.renderer.gl.scissor( + bounds.x * resolution, + (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, + bounds.width * resolution, + bounds.height * resolution + ); + + this.scissorRenderTarget = renderTarget; + this.scissorData = maskData; + this.scissor = true; + } + + /** + * + * + */ + popScissorMask() + { + this.scissorRenderTarget = null; + this.scissorData = null; + this.scissor = false; + + // must be scissor! + const gl = this.renderer.gl; + + gl.disable(gl.SCISSOR_TEST); + } +} diff --git a/packages/core/src/mask/StencilSystem.js b/packages/core/src/mask/StencilSystem.js new file mode 100644 index 0000000..62796a5 --- /dev/null +++ b/packages/core/src/mask/StencilSystem.js @@ -0,0 +1,133 @@ +import System from '../System'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class StencilSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + this.stencilMaskStack = []; + } + + /** + * Changes the mask stack that is used by this System. + * + * @param {PIXI.Graphics[]} stencilMaskStack - The mask stack + */ + setMaskStack(stencilMaskStack) + { + const gl = this.renderer.gl; + + if (stencilMaskStack.length !== this.stencilMaskStack.length) + { + if (stencilMaskStack.length === 0) + { + gl.disable(gl.STENCIL_TEST); + } + else + { + gl.enable(gl.STENCIL_TEST); + } + } + + this.stencilMaskStack = stencilMaskStack; + } + + /** + * Applies the Mask and adds it to the current stencil stack. @alvin + * + * @param {PIXI.Graphics} graphics - The mask + */ + pushStencil(graphics) + { + this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); + + // this.renderer._activeRenderTarget.attachStencilBuffer(); + + const gl = this.renderer.gl; + const prevMaskCount = this.stencilMaskStack.length; + + if (prevMaskCount === 0) + { + gl.enable(gl.STENCIL_TEST); + } + + this.stencilMaskStack.push(graphics); + + // Increment the refference stencil value where the new mask overlaps with the old ones. + gl.colorMask(false, false, false, false); + gl.stencilFunc(gl.EQUAL, prevMaskCount, this._getBitwiseMask()); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.INCR); + this.renderer.plugins.graphics.render(graphics); + + this._useCurrent(); + } + + /** + * Removes the last mask from the stencil stack. @alvin + */ + popStencil() + { + this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); + + const gl = this.renderer.gl; + const graphics = this.stencilMaskStack.pop(); + + if (this.stencilMaskStack.length === 0) + { + // the stack is empty! + gl.disable(gl.STENCIL_TEST); + gl.clear(gl.STENCIL_BUFFER_BIT); + gl.clearStencil(0); + } + else + { + // Decrement the refference stencil value where the popped mask overlaps with the other ones + gl.colorMask(false, false, false, false); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.DECR); + this.renderer.plugins.graphics.render(graphics); + + this._useCurrent(); + } + } + + /** + * Setup renderer to use the current stencil data. + */ + _useCurrent() + { + const gl = this.renderer.gl; + + gl.colorMask(true, true, true, true); + gl.stencilFunc(gl.EQUAL, this.stencilMaskStack.length, this._getBitwiseMask()); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP); + } + + /** + * Fill 1s equal to the number of acitve stencil masks. + * + * @return {number} The bitwise mask. + */ + _getBitwiseMask() + { + return (1 << this.stencilMaskStack.length) - 1; + } + + /** + * Destroys the mask stack. + * + */ + destroy() + { + super.destroy(this); + + this.stencilMaskStack = null; + } +} diff --git a/packages/core/src/projection/ProjectionSystem.js b/packages/core/src/projection/ProjectionSystem.js new file mode 100644 index 0000000..6e63b73 --- /dev/null +++ b/packages/core/src/projection/ProjectionSystem.js @@ -0,0 +1,73 @@ +import System from '../System'; +import { Matrix } from '@pixi/math'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class ProjectionSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.projectionMatrix = new Matrix(); + } + + update(destinationFrame, sourceFrame, resolution, root) + { + this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; + this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; + + this.calculateProjection(this.destinationFrame, this.sourceFrame, resolution, root); + + this.renderer.globalUniforms.uniforms.projectionMatrix = this.projectionMatrix; + this.renderer.globalUniforms.update(); + } + + /** + * Updates the projection matrix based on a projection frame (which is a rectangle) + * + * @param {Rectangle} destinationFrame - The destination frame. + * @param {Rectangle} sourceFrame - The source frame. + */ + calculateProjection(destinationFrame, sourceFrame, resolution, root) + { + const pm = this.projectionMatrix; + + // I don't think we will need this line.. + // pm.identity(); + + if (!root) + { + pm.a = (1 / destinationFrame.width * 2) * resolution; + pm.d = (1 / destinationFrame.height * 2) * resolution; + + pm.tx = -1 - (sourceFrame.x * pm.a); + pm.ty = -1 - (sourceFrame.y * pm.d); + } + else + { + pm.a = (1 / destinationFrame.width * 2) * resolution; + pm.d = (-1 / destinationFrame.height * 2) * resolution; + + pm.tx = -1 - (sourceFrame.x * pm.a); + pm.ty = 1 - (sourceFrame.y * pm.d); + } + } + + /** + * Sets the transform of the active render target to the given matrix + * + * @param {PIXI.Matrix} matrix - The transformation matrix + */ + setTransform()// matrix) + { + // this._activeRenderTarget.transform = matrix; + } +} diff --git a/packages/core/src/renderTexture/BaseRenderTexture.js b/packages/core/src/renderTexture/BaseRenderTexture.js new file mode 100644 index 0000000..88eb83b --- /dev/null +++ b/packages/core/src/renderTexture/BaseRenderTexture.js @@ -0,0 +1,137 @@ +import BaseTexture from '../textures/BaseTexture'; +import FrameBuffer from '../framebuffer/FrameBuffer'; + +/** + * A BaseRenderTexture is a special texture that allows any PixiJS display object to be rendered to it. + * + * __Hint__: All DisplayObjects (i.e. Sprites) that render to a BaseRenderTexture should be preloaded + * otherwise black rectangles will be drawn instead. + * + * A BaseRenderTexture takes a snapshot of any Display Object given to its render method. The position + * and rotation of the given Display Objects is ignored. For example: + * + * ```js + * let renderer = PIXI.autoDetectRenderer(1024, 1024, { view: canvas, ratio: 1 }); + * let baseRenderTexture = new PIXI.BaseRenderTexture(renderer, 800, 600); + * let sprite = PIXI.Sprite.fromImage("spinObj_01.png"); + * + * sprite.position.x = 800/2; + * sprite.position.y = 600/2; + * sprite.anchor.x = 0.5; + * sprite.anchor.y = 0.5; + * + * baseRenderTexture.render(sprite); + * ``` + * + * The Sprite in this case will be rendered using its local transform. To render this sprite at 0,0 + * you can clear the transform + * + * ```js + * + * sprite.setTransform() + * + * let baseRenderTexture = new PIXI.BaseRenderTexture(100, 100); + * let renderTexture = new PIXI.RenderTexture(baseRenderTexture); + * + * renderer.render(sprite, renderTexture); // Renders to center of RenderTexture + * ``` + * + * @class + * @extends PIXI.BaseTexture + * @memberof PIXI + */ +export default class BaseRenderTexture extends BaseTexture +{ + /** + * @param {object} [options] + * @param {number} [options.width=100] - The width of the base render texture + * @param {number} [options.height=100] - The height of the base render texture + * @param {PIXI.SCALE_MODES} [options.scaleMode] - See {@link PIXI.SCALE_MODES} for possible values + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the texture being generated + */ + constructor(options) + { + if (typeof options === 'number') + { + /* eslint-disable prefer-rest-params */ + // Backward compatibility of signature + const width = arguments[0]; + const height = arguments[1]; + const scaleMode = arguments[2]; + const resolution = arguments[3]; + + options = { width, height, scaleMode, resolution }; + /* eslint-enable prefer-rest-params */ + } + + super(null, options); + + const { width, height } = options || {}; + + // Set defaults + this.mipmap = false; + this.width = Math.ceil(width) || 100; + this.height = Math.ceil(height) || 100; + this.valid = true; + + /** + * A map of renderer IDs to webgl renderTargets + * + * @private + * @member {object} + */ + // this._glRenderTargets = {}; + + /** + * A reference to the canvas render target (we only need one as this can be shared across renderers) + * + * @private + * @member {object} + */ + this._canvasRenderTarget = null; + + this.clearColor = [0, 0, 0, 0]; + + this.frameBuffer = new FrameBuffer(width * this.resolution, height * this.resolution) + .addColorTexture(0, this); + + // TODO - could this be added the systems? + + /** + * The data structure for the stencil masks + * + * @member {PIXI.Graphics[]} + */ + this.stencilMaskStack = []; + + /** + * The data structure for the filters + * + * @member {PIXI.Graphics[]} + */ + this.filterStack = [{}]; + } + + /** + * Resizes the BaseRenderTexture. + * + * @param {number} width - The width to resize to. + * @param {number} height - The height to resize to. + */ + resize(width, height) + { + width = Math.ceil(width); + height = Math.ceil(height); + this.frameBuffer.resize(width * this.resolution, height * this.resolution); + } + + /** + * Destroys this texture + * + */ + destroy() + { + super.destroy(true); + this.renderer = null; + } +} diff --git a/packages/core/src/renderTexture/RenderTexture.js b/packages/core/src/renderTexture/RenderTexture.js new file mode 100644 index 0000000..f1ecf1f --- /dev/null +++ b/packages/core/src/renderTexture/RenderTexture.js @@ -0,0 +1,153 @@ +import BaseRenderTexture from './BaseRenderTexture'; +import Texture from '../textures/Texture'; + +/** + * A RenderTexture is a special texture that allows any PixiJS display object to be rendered to it. + * + * __Hint__: All DisplayObjects (i.e. Sprites) that render to a RenderTexture should be preloaded + * otherwise black rectangles will be drawn instead. + * + * A RenderTexture takes a snapshot of any Display Object given to its render method. For example: + * + * ```js + * let renderer = PIXI.autoDetectRenderer(1024, 1024, { view: canvas, ratio: 1 }); + * let renderTexture = PIXI.RenderTexture.create(800, 600); + * let sprite = PIXI.Sprite.fromImage("spinObj_01.png"); + * + * sprite.position.x = 800/2; + * sprite.position.y = 600/2; + * sprite.anchor.x = 0.5; + * sprite.anchor.y = 0.5; + * + * renderer.render(sprite, renderTexture); + * ``` + * + * The Sprite in this case will be rendered using its local transform. To render this sprite at 0,0 + * you can clear the transform + * + * ```js + * + * sprite.setTransform() + * + * let renderTexture = new PIXI.RenderTexture.create(100, 100); + * + * renderer.render(sprite, renderTexture); // Renders to center of RenderTexture + * ``` + * + * @class + * @extends PIXI.Texture + * @memberof PIXI + */ +export default class RenderTexture extends Texture +{ + /** + * @param {PIXI.BaseRenderTexture} baseRenderTexture - The renderer used for this RenderTexture + * @param {PIXI.Rectangle} [frame] - The rectangle frame of the texture to show + */ + constructor(baseRenderTexture, frame) + { + // support for legacy.. + let _legacyRenderer = null; + + if (!(baseRenderTexture instanceof BaseRenderTexture)) + { + /* eslint-disable prefer-rest-params, no-console */ + const width = arguments[1]; + const height = arguments[2]; + const scaleMode = arguments[3]; + const resolution = arguments[4]; + + // we have an old render texture.. + console.warn(`Please use RenderTexture.create(${width}, ${height}) instead of the ctor directly.`); + _legacyRenderer = arguments[0]; + /* eslint-enable prefer-rest-params, no-console */ + + frame = null; + baseRenderTexture = new BaseRenderTexture({ + width, + height, + scaleMode, + resolution, + }); + } + + /** + * The base texture object that this texture uses + * + * @member {BaseTexture} + */ + super(baseRenderTexture, frame); + + this.legacyRenderer = _legacyRenderer; + + /** + * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. + * + * @member {boolean} + */ + this.valid = true; + + /** + * FilterSystem temporary storage + * @private + * @member {PIXI.Rectangle} + */ + this.filterFrame = null; + + this._updateUvs(); + } + + /** + * Resizes the RenderTexture. + * + * @param {number} width - The width to resize to. + * @param {number} height - The height to resize to. + * @param {boolean} [resizeBaseTexture=true] - Should the baseTexture.width and height values be resized as well? + */ + resize(width, height, resizeBaseTexture = true) + { + width = Math.ceil(width); + height = Math.ceil(height); + + // TODO - could be not required.. + this.valid = (width > 0 && height > 0); + + this._frame.width = this.orig.width = width; + this._frame.height = this.orig.height = height; + + if (resizeBaseTexture) + { + this.baseTexture.resize(width, height); + } + + this._updateUvs(); + } + + /** + * A short hand way of creating a render texture. + * + * @param {object} [options] - Options + * @param {number} [options.width=100] - The width of the render texture + * @param {number} [options.height=100] - The height of the render texture + * @param {number} [options.scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the texture being generated + * @return {PIXI.RenderTexture} The new render texture + */ + static create(options) + { + // fallback, old-style: create(width, height, scaleMode, resolution) + if (typeof options === 'number') + { + /* eslint-disable prefer-rest-params */ + options = { + width: options, + height: arguments[1], + scaleMode: arguments[2], + resolution: arguments[3], + }; + /* eslint-enable prefer-rest-params */ + } + + return new RenderTexture(new BaseRenderTexture(options)); + } +} diff --git a/packages/core/src/renderTexture/RenderTextureSystem.js b/packages/core/src/renderTexture/RenderTextureSystem.js new file mode 100644 index 0000000..8d0ca61 --- /dev/null +++ b/packages/core/src/renderTexture/RenderTextureSystem.js @@ -0,0 +1,115 @@ +import System from '../System'; +import { Rectangle } from '@pixi/math'; + +const tempRect = new Rectangle(); + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class RenderTextureSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.clearColor = renderer._backgroundColorRgba; + + // TODO moe this property somewhere else! + this.defaultMaskStack = []; + this.defaultFilterStack = [{}]; + + // empty render texture? + this.renderTexture = null; + + this.destinationFrame = new Rectangle(); + } + + bind(renderTexture, sourceFrame, destinationFrame) + { + // TODO - do we want this?? + if (this.renderTexture === renderTexture) return; + this.renderTexture = renderTexture; + + const renderer = this.renderer; + + if (renderTexture) + { + const baseTexture = renderTexture.baseTexture; + + if (!destinationFrame) + { + tempRect.width = baseTexture.realWidth; + tempRect.height = baseTexture.realHeight; + + destinationFrame = tempRect; + } + + if (!sourceFrame) + { + sourceFrame = destinationFrame; + } + + this.renderer.framebuffer.bind(baseTexture.frameBuffer, destinationFrame); + + this.renderer.projection.update(destinationFrame, sourceFrame, baseTexture.resolution, false); + this.renderer.stencil.setMaskStack(baseTexture.stencilMaskStack); + } + else + { + // TODO these validation checks happen deeper down.. + // thing they can be avoided.. + if (!destinationFrame) + { + tempRect.width = renderer.width; + tempRect.height = renderer.height; + + destinationFrame = tempRect; + } + + if (!sourceFrame) + { + sourceFrame = destinationFrame; + } + + renderer.framebuffer.bind(null, destinationFrame); + + // TODO store this.. + this.renderer.projection.update(destinationFrame, sourceFrame, this.renderer.resolution, true); + this.renderer.stencil.setMaskStack(this.defaultMaskStack); + } + + this.destinationFrame.copyFrom(destinationFrame); + } + + /** + * Erases the render texture and fills the drawing area with a colour + * + * @param {number} [clearColor] - The colour + * @return {PIXI.Renderer} Returns itself. + */ + clear(clearColor) + { + if (this.renderTexture) + { + clearColor = clearColor || this.renderTexture.baseTexture.clearColor; + } + else + { + clearColor = clearColor || this.clearColor; + } + + this.renderer.framebuffer.clear(clearColor[0], clearColor[1], clearColor[2], clearColor[3]); + } + + resize()// screenWidth, screenHeight) + { + // resize the root only! + this.bind(null); + } +} diff --git a/packages/core/src/renderers/AbstractRenderer.js b/packages/core/src/renderers/AbstractRenderer.js deleted file mode 100644 index 928670f..0000000 --- a/packages/core/src/renderers/AbstractRenderer.js +++ /dev/null @@ -1,353 +0,0 @@ -import { hex2string, hex2rgb } from '@pixi/utils'; -import { Matrix, Rectangle } from '@pixi/math'; -import { RENDERER_TYPE } from '@pixi/constants'; -import { settings } from '@pixi/settings'; -import { Container } from '@pixi/display'; -import RenderTexture from '../textures/RenderTexture'; -import EventEmitter from 'eventemitter3'; - -const tempMatrix = new Matrix(); - -/** - * 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 EventEmitter - * @memberof PIXI - */ -export default class AbstractRenderer extends EventEmitter -{ - // eslint-disable-next-line valid-jsdoc - /** - * @param {string} system - The name of the system this renderer is for. - * @param {object} [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.transparent=false] - If the render view is transparent, default false - * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false - * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) - * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. The - * resolution of the renderer retina would be 2. - * @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 {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when rendering, - * stopping pixel interpolation. - */ - constructor(system, options, arg2, arg3) - { - super(); - - // Support for constructor(system, screenWidth, screenHeight, options) - if (typeof options === 'number') - { - options = Object.assign({ - width: options, - height: arg2 || settings.RENDER_OPTIONS.height, - }, arg3); - } - - // 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 = RENDERER_TYPE.UNKNOWN; - - /** - * Measurements of the screen. (0, 0, screenWidth, screenHeight) - * - * Its safe to use as filterArea or hitArea for 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 1 - */ - this.resolution = options.resolution || settings.RESOLUTION; - - /** - * Whether the render view is transparent - * - * @member {boolean} - */ - this.transparent = options.transparent; - - /** - * Whether css dimensions of canvas view should be resized to screen dimensions automatically - * - * @member {boolean} - */ - this.autoResize = options.autoResize || false; - - /** - * Tracks the blend modes useful for this renderer. - * - * @member {object} - */ - this.blendModes = null; - - /** - * 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; - - /** - * If true PixiJS will Math.floor() x/y values when rendering, stopping pixel interpolation. - * Handy for crisp pixel art and speed on legacy devices. - * - * @member {boolean} - */ - this.roundPixels = options.roundPixels; - - /** - * The background color as a number. - * - * @member {number} - * @private - */ - this._backgroundColor = 0x000000; - - /** - * The background color as an [R, G, B] array. - * - * @member {number[]} - * @private - */ - this._backgroundColorRgba = [0, 0, 0, 0]; - - /** - * The background color as a string. - * - * @member {string} - * @private - */ - this._backgroundColorString = '#000000'; - - this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter - - /** - * This temporary display object used as the parent of the currently being rendered item - * - * @member {PIXI.DisplayObject} - * @private - */ - this._tempDisplayObjectParent = new Container(); - - /** - * The last root object that the renderer tried to render. - * - * @member {PIXI.DisplayObject} - * @private - */ - this._lastObjectRendered = this._tempDisplayObjectParent; - - /** - * Collection of plugins. - * @readonly - * @member {object} - */ - this.plugins = {}; - } - - /** - * Initialize the plugins. - * - * @protected - * @param {object} staticMap - The dictionary of staticly saved plugins. - */ - initPlugins(staticMap) - { - 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() - { - return this.view.width; - } - - /** - * Same as view.height, actual number of pixels in the canvas by vertical - * - * @member {number} - * @readonly - * @default 600 - */ - get height() - { - return this.view.height; - } - - /** - * Resizes the screen and canvas to the specified width and height - * Canvas dimensions are multiplied by resolution - * - * @param {number} screenWidth - the new width of the screen - * @param {number} screenHeight - the new height of the screen - */ - resize(screenWidth, screenHeight) - { - this.screen.width = screenWidth; - this.screen.height = screenHeight; - - this.view.width = screenWidth * this.resolution; - this.view.height = screenHeight * this.resolution; - - if (this.autoResize) - { - this.view.style.width = `${screenWidth}px`; - this.view.style.height = `${screenHeight}px`; - } - } - - /** - * 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 {PIXI.DisplayObject} displayObject - The displayObject the object will be generated from - * @param {number} scaleMode - Should be one of the scaleMode consts - * @param {number} resolution - The resolution / device pixel ratio of the texture being generated - * @param {PIXI.Rectangle} [region] - The region of the displayObject, that shall be rendered, - * if no region is specified, defaults to the local bounds of the displayObject. - * @return {PIXI.Texture} a texture of the graphics object - */ - generateTexture(displayObject, scaleMode, resolution, region) - { - region = region || displayObject.getLocalBounds(); - - // 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(region.width | 0, region.height | 0, scaleMode, resolution); - - tempMatrix.tx = -region.x; - tempMatrix.ty = -region.y; - - this.render(displayObject, renderTexture, false, tempMatrix, true); - - return renderTexture; - } - - /** - * Removes everything from the renderer and optionally removes the Canvas DOM element. - * - * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. - */ - destroy(removeView) - { - 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); - } - - this.plugins = null; - - this.type = RENDERER_TYPE.UNKNOWN; - - this.view = null; - - this.screen = null; - - this.resolution = 0; - - this.transparent = false; - - this.autoResize = false; - - this.blendModes = null; - - this.options = null; - - this.preserveDrawingBuffer = false; - this.clearBeforeRender = false; - - this.roundPixels = false; - - this._backgroundColor = 0; - this._backgroundColorRgba = null; - this._backgroundColorString = null; - - this._tempDisplayObjectParent = null; - this._lastObjectRendered = null; - } - - /** - * The background color to fill if not transparent - * - * @member {number} - */ - get backgroundColor() - { - return this._backgroundColor; - } - - set backgroundColor(value) // eslint-disable-line require-jsdoc - { - this._backgroundColor = value; - this._backgroundColorString = hex2string(value); - hex2rgb(value, this._backgroundColorRgba); - } -} diff --git a/packages/core/src/renderers/Renderer.js b/packages/core/src/renderers/Renderer.js deleted file mode 100644 index a2020b1..0000000 --- a/packages/core/src/renderers/Renderer.js +++ /dev/null @@ -1,329 +0,0 @@ -import AbstractRenderer from './AbstractRenderer'; -import { sayHello } from '@pixi/utils'; -import MaskSystem from './systems/MaskSystem'; -import StencilSystem from './systems/StencilSystem'; -import FilterSystem from './systems/filter/FilterSystem'; -import FramebufferSystem from './systems/FramebufferSystem'; -import RenderTextureSystem from './systems/RenderTextureSystem'; -import TextureSystem from './systems/textures/TextureSystem'; -import ProjectionSystem from './systems/ProjectionSystem'; -import StateSystem from './systems/StateSystem'; -import GeometrySystem from './systems/geometry/GeometrySystem'; -import ShaderSystem from './systems/shader/ShaderSystem'; -import ContextSystem from './systems/ContextSystem'; -import BatchSystem from './systems/BatchSystem'; -import TextureGCSystem from './systems/textures/TextureGCSystem'; -import { RENDERER_TYPE } from '@pixi/constants'; -import UniformGroup from '../shader/UniformGroup'; -import { Matrix } from '@pixi/math'; -import Runner from 'mini-runner'; - -/** - * 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 Render works by automatically managing webGLBatchs. - * 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 :) - * - * @class - * @memberof PIXI - * @extends PIXI.AbstractRenderer - */ -export default class Renderer extends AbstractRenderer -{ - // eslint-disable-next-line valid-jsdoc - /** - * - * @param {object} [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.transparent=false] - If the render view is transparent, default false - * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false - * @param {boolean} [options.antialias=false] - sets antialias. If not available natively then FXAA - * antialiasing is used - * @param {boolean} [options.forceFXAA=false] - forces FXAA antialiasing to be used over native. - * FXAA is faster, but may not always look as great - * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. - * The resolution of the renderer retina would be 2. - * @param {boolean} [options.clearBeforeRender=true] - This sets if the renderer will clear - * the canvas or not before the new render pass. If you wish to set this to false, you *must* set - * preserveDrawingBuffer to `true`. - * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, - * enable this if you need to call toDataUrl on the webgl context. - * @param {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when - * rendering, stopping pixel interpolation. - * @param {number} [options.backgroundColor=0x000000] - The background color of the rendered area - * (shown if not transparent). - * @param {boolean} [options.legacy=false] - If true PixiJS will aim to ensure compatibility - * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. - * @param {string} [options.powerPreference] - Parameter passed to webgl context, set to "high-performance" - * for devices with dual graphics card - */ - constructor(options, arg2, arg3) - { - super('WebGL', options, arg2, arg3); - - /** - * The type of this renderer as a standardised const - * - * @member {number} - * @see PIXI.RENDERER_TYPE - */ - this.type = RENDERER_TYPE.WEBGL; - - // this will be set by the contextSystem (this.context) - this.gl = null; - this.CONTEXT_UID = 0; - this.legacy = !!options.legacy; - - // TODO legacy! - - // runners! - this.runners = { - destroy: new Runner('destroy'), - contextChange: new Runner('contextChange', 1), - reset: new Runner('reset'), - update: new Runner('update'), - postrender: new Runner('postrender'), - prerender: new Runner('prerender'), - resize: new Runner('resize', 2), - }; - - 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(GeometrySystem, 'geometry') - .addSystem(FramebufferSystem, 'framebuffer') - .addSystem(StencilSystem, 'stencil') - .addSystem(ProjectionSystem, 'projection') - .addSystem(TextureGCSystem, 'textureGC') - .addSystem(FilterSystem, 'filter') - .addSystem(RenderTextureSystem, 'renderTexture') - .addSystem(BatchSystem, 'batch'); - - this.initPlugins(Renderer.__plugins); - - /** - * The options passed in to create a new webgl context. - * - * @member {object} - * @private - */ - if (options.context) - { - this.context.initFromContext(options.context); - } - else - { - this.context.initFromOptions({ - alpha: this.transparent, - antialias: options.antialias, - premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', - stencil: true, - 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); - } - - addSystem(_class, name) - { - if (!name) - { - name = _class.name; - } - - // TODO - read name from class.name.. - - /* - if(name.includes('System')) - { - name = name.replace('System', ''); - name = name.charAt(0).toLowerCase() + name.slice(1); - } - */ - - const system = new _class(this); - - if (this[name]) - { - throw new Error(`Whoops! ${name} is already a manger`); - } - - this[name] = system; - - for (const i in this.runners) - { - this.runners[i].add(system); - } - - return this; - - /** - * 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. - */ - } - - /** - * Renders the object to its webGL view - * - * @param {PIXI.DisplayObject} displayObject - the object to be rendered - * @param {PIXI.RenderTexture} renderTexture - The render texture to render to. - * @param {boolean} [clear] - Should the canvas be cleared before the new render - * @param {PIXI.Transform} [transform] - A transform to apply to the render texture before rendering. - * @param {boolean} [skipUpdateTransform] - Should we skip the update transform pass? - */ - render(displayObject, renderTexture, clear, transform, skipUpdateTransform) - { - // can be handy to know! - this.renderingToScreen = !renderTexture; - - this.runners.prerender.run(); - this.emit('prerender'); - - // 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.parent; - - displayObject.parent = this._tempDisplayObjectParent; - displayObject.updateTransform(); - displayObject.parent = 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.run(); - - this.emit('postrender'); - } - - /** - * Resizes the webGL view to the specified width and height. - * - * @param {number} screenWidth - the new width of the screen - * @param {number} screenHeight - the new height of the screen - */ - resize(screenWidth, screenHeight) - { - AbstractRenderer.prototype.resize.call(this, screenWidth, screenHeight); - - this.runners.resize.run(screenWidth, screenHeight); - } - - /** - * Resets the WebGL state so you can render things however you fancy! - * - * @return {PIXI.Renderer} Returns itself. - */ - reset() - { - this.runners.reset.run(); - - return this; - } - - clear() - { - this.framebuffer.bind(); - this.framebuffer.clear(); - } - - /** - * Removes everything from the renderer (event listeners, spritebatch, etc...) - * - * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. - * See: https://github.com/pixijs/pixi.js/issues/2233 - */ - destroy(removeView) - { - this.runners.destroy.run(); - - // call base destroy - super.destroy(removeView); - - // TODO nullify all the managers.. - this.gl = null; - } - - /** - * 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 - * builds and excluding plugins. - * @name PIXI.Renderer#plugins - * @type {object} - * @readonly - * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. - * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. - * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. - * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. - */ - - /** - * Adds a plugin to the renderer. - * - * @method - * @param {string} pluginName - The name of the plugin. - * @param {Function} ctor - The constructor function or class for the plugin. - */ - static registerPlugin(pluginName, ctor) - { - Renderer.__plugins = Renderer.__plugins || {}; - Renderer.__plugins[pluginName] = ctor; - } -} diff --git a/packages/core/src/renderers/State.js b/packages/core/src/renderers/State.js deleted file mode 100644 index fa3f221..0000000 --- a/packages/core/src/renderers/State.js +++ /dev/null @@ -1,163 +0,0 @@ -/* eslint-disable max-len */ - -const BLEND = 0; -const OFFSET = 1; -const CULLING = 2; -const DEPTH_TEST = 3; -const WINDING = 4; - -/** - * This is a webGL state. It is passed The WebGL StateManager. - * Each mesh renderered may require webGL to be in a different state. - * For example you may want different blend mode or to enable polygon offsets - * - * @class - * @memberof PIXI - */ -export default class State -{ - /** - * - */ - constructor() - { - this.data = 0; - - this.blendMode = 0; - this.polygonOffset = 0; - - this.blend = true; - // this.depthTest = true; - } - - /** - * Activates blending of the computed fragment color values - * - * @member {boolean} - */ - get blend() - { - return !!(this.data & (1 << BLEND)); - } - - set blend(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << BLEND)) !== value) - { - this.data ^= (1 << BLEND); - } - } - - /** - * Activates adding an offset to depth values of polygon's fragments - * - * @member {boolean} - * @default false - */ - get offsets() - { - return !!(this.data & (1 << OFFSET)); - } - - set offsets(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << OFFSET)) !== value) - { - this.data ^= (1 << OFFSET); - } - } - - /** - * Activates culling of polygons. - * - * @member {boolean} - * @default false - */ - get culling() - { - return !!(this.data & (1 << CULLING)); - } - - set culling(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << CULLING)) !== value) - { - this.data ^= (1 << CULLING); - } - } - - /** - * Activates depth comparisons and updates to the depth buffer. - * - * @member {boolean} - * @default false - */ - get depthTest() - { - return !!(this.data & (1 << DEPTH_TEST)); - } - - set depthTest(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << DEPTH_TEST)) !== value) - { - this.data ^= (1 << DEPTH_TEST); - } - } - - /** - * Specifies whether or not front or back-facing polygons can be culled. - * @member {boolean} - * @default false - */ - get clockwiseFrontFace() - { - return !!(this.data & (1 << WINDING)); - } - - set clockwiseFrontFace(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << WINDING)) !== value) - { - this.data ^= (1 << WINDING); - } - } - - /** - * The blend mode to be applied when this state is set. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. - * Setting this mode to anything other than NO_BLEND will automatically switch blending on. - * - * @member {boolean} - * @default PIXI.BLEND_MODES.NORMAL - * @see PIXI.BLEND_MODES - */ - get blendMode() - { - return this._blendMode; - } - - set blendMode(value) // eslint-disable-line require-jsdoc - { - // 17 is NO BLEND - this.blend = (value !== 17); - this._blendMode = value; - } - - /** - * The polygon offset. Setting this property to anything other than 0 will automatically enable poygon offset fill. - * - * @member {number} - * @default 0 - */ - get polygonOffset() - { - return this._polygonOffset; - } - - set polygonOffset(value) // eslint-disable-line require-jsdoc - { - this.offsets = !!value; - this._polygonOffset = value; - } -} - diff --git a/packages/core/src/renderers/filters/Filter.js b/packages/core/src/renderers/filters/Filter.js deleted file mode 100644 index 2073b8d..0000000 --- a/packages/core/src/renderers/filters/Filter.js +++ /dev/null @@ -1,140 +0,0 @@ -import Shader from '../../shader/Shader'; -import Program from '../../shader/Program'; -import State from '../State'; -import { settings } from '@pixi/settings'; -// import extractUniformsFromSrc from './extractUniformsFromSrc'; -import defaultVertex from './defaultFilter.vert'; -import defaultFragment from './defaultFilter.frag'; - -// let math = require('../../math'); -/** - * @class - * @memberof PIXI - * @extends PIXI.Shader - */ -export default class Filter extends Shader -{ - /** - * @param {string} [vertexSrc] - The source of the vertex shader. - * @param {string} [fragmentSrc] - The source of the fragment shader. - * @param {object} [uniforms] - Custom uniforms to use to augment the built-in ones. - */ - constructor(vertexSrc, fragmentSrc, uniforms) - { - const program = Program.from(vertexSrc, fragmentSrc); - - super(program, uniforms); - - /** - * The padding of the filter. Some filters require extra space to breath such as a blur. - * Increasing this will add extra width and height to the bounds of the object that the - * filter is applied to. - * - * @member {number} - */ - this.padding = 0; - - /** - * The resolution of the filter. Setting this to be lower will lower the quality but - * increase the performance of the filter. - * - * @member {number} - */ - this.resolution = settings.RESOLUTION; - - /** - * If enabled is true the filter is applied, if false it will not. - * - * @member {boolean} - */ - this.enabled = true; - - /** - * If enabled, PixiJS will fit the filter area into boundaries for better performance. - * Switch it off if it does not work for specific shader. - * - * @member {boolean} - */ - this.autoFit = true; - - /** - * Legacy filters use position and uvs from attributes - * @member {boolean} - * @readonly - */ - this.legacy = !!this.program.attributeData.aTextureCoord; - - /** - * the webGL state the filter requires to render - * @member {PIXI.State} - */ - this.state = new State(); - } - - /** - * Applies the filter - * - * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from - * @param {PIXI.RenderTarget} input - The input render target. - * @param {PIXI.RenderTarget} output - The target to output to. - * @param {boolean} clear - Should the output be cleared before rendering to it - * @param {object} [currentState] - It's current state of filter. - * There are some useful properties in the currentState : - * target, filters, sourceFrame, destinationFrame, renderTarget, resolution - */ - apply(filterManager, input, output, clear, currentState, derp) // eslint-disable-line no-unused-vars - { - // do as you please! - - filterManager.applyFilter(this, input, output, clear, currentState, derp); - - // or just do a regular render.. - } - - /** - * Sets the blendmode of the filter - * - * @member {number} - * @default PIXI.BLEND_MODES.NORMAL - */ - get blendMode() - { - return this.state.blendMode; - } - - set blendMode(value) // eslint-disable-line require-jsdoc - { - this.state.blendMode = value; - } - - /** - * The default vertex shader source - * - * @static - * @constant - */ - static get defaultVertexSrc() - { - return defaultVertex; - } - - /** - * The default fragment shader source - * - * @static - * @constant - */ - static get defaultFragmentSrc() - { - return defaultFragment; - } -} - -/** - * Used for caching shader IDs - * - * @static - * @private - */ -Filter.SOURCE_KEY_MAP = {}; - diff --git a/packages/core/src/renderers/filters/defaultFilter.frag b/packages/core/src/renderers/filters/defaultFilter.frag deleted file mode 100644 index a14c1be..0000000 --- a/packages/core/src/renderers/filters/defaultFilter.frag +++ /dev/null @@ -1,22 +0,0 @@ -varying vec2 vTextureCoord; -varying vec2 vFilterCoord; - -uniform sampler2D uSampler; -uniform sampler2D filterSampler; - -void main(void){ - vec4 masky = texture2D(filterSampler, vFilterCoord); - vec4 sample = texture2D(uSampler, vTextureCoord); - vec4 color; - if(mod(vFilterCoord.x, 1.0) > 0.5) - { - color = vec4(1.0, 0.0, 0.0, 1.0); - } - else - { - color = vec4(0.0, 1.0, 0.0, 1.0); - } - // gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0); - gl_FragColor = mix(sample, masky, 0.5); - gl_FragColor *= sample.a; -} diff --git a/packages/core/src/renderers/filters/defaultFilter.vert b/packages/core/src/renderers/filters/defaultFilter.vert deleted file mode 100644 index e1febca..0000000 --- a/packages/core/src/renderers/filters/defaultFilter.vert +++ /dev/null @@ -1,14 +0,0 @@ -attribute vec2 aVertexPosition; -attribute vec2 aTextureCoord; - -uniform mat3 projectionMatrix; -uniform mat3 filterMatrix; - -varying vec2 vTextureCoord; -varying vec2 vFilterCoord; - -void main(void){ - gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); - vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy; - vTextureCoord = aTextureCoord ; -} diff --git a/packages/core/src/renderers/filters/filterTransforms.js b/packages/core/src/renderers/filters/filterTransforms.js deleted file mode 100644 index 87a3cd6..0000000 --- a/packages/core/src/renderers/filters/filterTransforms.js +++ /dev/null @@ -1,50 +0,0 @@ -import { Matrix } from '@pixi/math'; - -/** - * Calculates the mapped matrix - * @param filterArea {Rectangle} The filter area - * @param sprite {Sprite} the target sprite - * @param outputMatrix {Matrix} @alvin - * @private - */ -// this returns a matrix that will normalise map filter cords in the filter to screen space -export function calculateScreenSpaceMatrix(outputMatrix, filterArea, textureSize) -{ - // TODO unwrap? - const mappedMatrix = outputMatrix.identity(); - - mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); - - mappedMatrix.scale(textureSize.width, textureSize.height); - - return mappedMatrix; -} - -export function calculateNormalizedScreenSpaceMatrix(outputMatrix, filterArea, textureSize) -{ - const mappedMatrix = outputMatrix.identity(); - - mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); - - const translateScaleX = (textureSize.width / filterArea.width); - const translateScaleY = (textureSize.height / filterArea.height); - - mappedMatrix.scale(translateScaleX, translateScaleY); - - return mappedMatrix; -} - -// this will map the filter coord so that a texture can be used based on the transform of a sprite -export function calculateSpriteMatrix(outputMatrix, filterArea, textureSize, sprite) -{ - const orig = sprite._texture.orig; - const mappedMatrix = outputMatrix.set(textureSize.width, 0, 0, textureSize.height, filterArea.x, filterArea.y); - const worldTransform = sprite.worldTransform.copyTo(Matrix.TEMP_MATRIX); - - worldTransform.invert(); - mappedMatrix.prepend(worldTransform); - mappedMatrix.scale(1.0 / orig.width, 1.0 / orig.height); - mappedMatrix.translate(sprite.anchor.x, sprite.anchor.y); - - return mappedMatrix; -} diff --git a/packages/core/src/renderers/filters/spriteMask/SpriteMaskFilter.js b/packages/core/src/renderers/filters/spriteMask/SpriteMaskFilter.js deleted file mode 100644 index d5a91a4..0000000 --- a/packages/core/src/renderers/filters/spriteMask/SpriteMaskFilter.js +++ /dev/null @@ -1,63 +0,0 @@ -import Filter from '../Filter'; -import { Matrix } from '@pixi/math'; -import vertex from './spriteMaskFilter.vert'; -import fragment from './spriteMaskFilter.frag'; -import { default as TextureMatrix } from '../../../textures/TextureMatrix'; - -/** - * The SpriteMaskFilter class - * - * @class - * @extends PIXI.Filter - * @memberof PIXI - */ -export default class SpriteMaskFilter extends Filter -{ - /** - * @param {PIXI.Sprite} sprite - the target sprite - */ - constructor(sprite) - { - const maskMatrix = new Matrix(); - - super(vertex, fragment); - - sprite.renderable = false; - - this.maskSprite = sprite; - this.maskMatrix = maskMatrix; - } - - /** - * Applies the filter - * - * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from - * @param {PIXI.RenderTarget} input - The input render target. - * @param {PIXI.RenderTarget} output - The target to output to. - */ - apply(filterManager, input, output) - { - const maskSprite = this.maskSprite; - const tex = this.maskSprite.texture; - - if (!tex.valid) - { - return; - } - if (!tex.transform) - { - // margin = 0.0, let it bleed a bit, shader code becomes easier - // assuming that atlas textures were made with 1-pixel padding - tex.transform = new TextureMatrix(tex, 0.0); - } - tex.transform.update(); - - this.uniforms.mask = tex; - this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite) - .prepend(tex.transform.mapCoord); - this.uniforms.alpha = maskSprite.worldAlpha; - this.uniforms.maskClamp = tex.transform.uClampFrame; - - filterManager.applyFilter(this, input, output); - } -} diff --git a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.frag b/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.frag deleted file mode 100644 index 0e4aef8..0000000 --- a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.frag +++ /dev/null @@ -1,23 +0,0 @@ -varying vec2 vMaskCoord; -varying vec2 vTextureCoord; - -uniform sampler2D uSampler; -uniform sampler2D mask; -uniform float alpha; -uniform vec4 maskClamp; - -void main(void) -{ - float clip = step(3.5, - step(maskClamp.x, vMaskCoord.x) + - step(maskClamp.y, vMaskCoord.y) + - step(vMaskCoord.x, maskClamp.z) + - step(vMaskCoord.y, maskClamp.w)); - - vec4 original = texture2D(uSampler, vTextureCoord); - vec4 masky = texture2D(mask, vMaskCoord); - - original *= (masky.r * masky.a * alpha * clip); - - gl_FragColor = original; -} diff --git a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.vert b/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.vert deleted file mode 100644 index d23a30c..0000000 --- a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.vert +++ /dev/null @@ -1,16 +0,0 @@ -attribute vec2 aVertexPosition; -attribute vec2 aTextureCoord; - -uniform mat3 projectionMatrix; -uniform mat3 otherMatrix; - -varying vec2 vMaskCoord; -varying vec2 vTextureCoord; - -void main(void) -{ - gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); - - vTextureCoord = aTextureCoord; - vMaskCoord = ( otherMatrix * vec3( aTextureCoord, 1.0) ).xy; -} diff --git a/packages/core/src/renderers/systems/BatchSystem.js b/packages/core/src/renderers/systems/BatchSystem.js deleted file mode 100644 index 70e9518..0000000 --- a/packages/core/src/renderers/systems/BatchSystem.js +++ /dev/null @@ -1,68 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import ObjectRenderer from '../utils/ObjectRenderer'; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ - -export default class BatchSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this manager works for. - */ - constructor(renderer) - { - super(renderer); - - /** - * An empty renderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.emptyRenderer = new ObjectRenderer(renderer); - - /** - * The currently active ObjectRenderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.currentRenderer = this.emptyRenderer; - } - - /** - * Changes the current renderer to the one given in parameter - * - * @param {PIXI.ObjectRenderer} objectRenderer - The object renderer to use. - */ - setObjectRenderer(objectRenderer) - { - if (this.currentRenderer === objectRenderer) - { - return; - } - - this.currentRenderer.stop(); - this.currentRenderer = objectRenderer; - - this.renderer.state.setState(objectRenderer.state); - - this.currentRenderer.start(); - } - - /** - * This should be called if you wish to do some custom rendering - * It will basically render anything that may be batched up such as sprites - * - */ - flush() - { - this.setObjectRenderer(this.emptyRenderer); - } - - reset() - { - this.setObjectRenderer(this.emptyRenderer); - } -} diff --git a/packages/core/src/renderers/systems/ContextSystem.js b/packages/core/src/renderers/systems/ContextSystem.js deleted file mode 100644 index 43bf827..0000000 --- a/packages/core/src/renderers/systems/ContextSystem.js +++ /dev/null @@ -1,186 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { settings } from '@pixi/settings'; - -let CONTEXT_UID = 0; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class ContextSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this.webGLVersion = 1; - - this.handleContextLost = this.handleContextLost.bind(this); - this.handleContextRestored = this.handleContextRestored.bind(this); - - this.extensions = {}; - - renderer.view.addEventListener('webglcontextlost', this.handleContextLost, false); - renderer.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); - } - - get isLost() - { - return (!this.gl || this.gl.isContextLost()); - } - - contextChange(gl) - { - this.gl = gl; - - // restore a context if it was previously lost - if (gl.isContextLost() && gl.getExtension('WEBGL_lose_context')) - { - gl.getExtension('WEBGL_lose_context').restoreContext(); - } - } - - initFromContext(gl) - { - this.gl = gl; - this.validateContext(gl); - this.renderer.gl = gl; - this.renderer.CONTEXT_UID = CONTEXT_UID++; - this.renderer.runners.contextChange.run(gl); - } - - initFromOptions(options) - { - const gl = this.createContext(this.renderer.view, options); - - this.initFromContext(gl); - } - - /** - * Helper class to create a webGL Context - * - * @class - * @memberof PIXI.glCore - * @param canvas {HTMLCanvasElement} the canvas element that we will get the context from - * @param options {Object} An options object that gets passed in to the canvas element containing the context attributes, - * see https://developer.mozilla.org/en/docs/Web/API/HTMLCanvasElement/getContext for the options available - * @return {WebGLRenderingContext} the WebGL context - */ - createContext(canvas, options) - { - let gl; - - if (settings.PREFER_WEBGL_2) - { - gl = canvas.getContext('webgl2', options); - } - - if (gl) - { - this.webGLVersion = 2; - } - else - { - this.webGLVersion = 1; - - gl = canvas.getContext('webgl', options) - || canvas.getContext('experimental-webgl', options); - - if (!gl) - { - // fail, not able to get a context - throw new Error('This browser does not support webGL. Try using the canvas renderer'); - } - } - - this.gl = gl; - - this.getExtensions(); - - return gl; - } - - getExtensions() - { - // time to set up default etensions that pixi uses.. - const gl = this.gl; - const extensions = this.extensions; - - if (this.webGLVersion === 1) - { - extensions.drawBuffers = gl.getExtension('WEBGL_draw_buffers'); - extensions.depthTexture = gl.getExtension('WEBKIT_WEBGL_depth_texture'); - extensions.floatTexture = gl.getExtension('OES_texture_float'); - extensions.loseContext = gl.getExtension('WEBGL_lose_context'); - - extensions.vertexArrayObject = gl.getExtension('OES_vertex_array_object') - || gl.getExtension('MOZ_OES_vertex_array_object') - || gl.getExtension('WEBKIT_OES_vertex_array_object'); - } - - // we don't use any specific WebGL 2 ones yet! - } - - /** - * Handles a lost webgl context - * - * @private - * @param {WebGLContextEvent} event - The context lost event. - */ - handleContextLost(event) - { - event.preventDefault(); - } - - /** - * Handles a restored webgl context - * - * @private - */ - handleContextRestored() - { - this.renderer.runners.contextChange.run(this.gl); - } - - destroy() - { - const view = this.renderer.view; - - // remove listeners - view.removeEventListener('webglcontextlost', this.handleContextLost); - view.removeEventListener('webglcontextrestored', this.handleContextRestored); - - this.gl.useProgram(null); - - if (this.extensions.loseContext) - { - this.extensions.loseContext.loseContext(); - } - } - - postrender() - { - this.gl.flush(); - } - - validateContext(gl) - { - const attributes = gl.getContextAttributes(); - - // this is going to be fairly simple for now.. but at least we have room to grow! - if (!attributes.stencil) - { - /* eslint-disable max-len */ - - /* eslint-disable no-console */ - console.warn('Provided WebGL context does not have a stencil buffer, masks may not render correctly'); - /* eslint-enable no-console */ - - /* eslint-enable max-len */ - } - } -} diff --git a/packages/core/src/renderers/systems/FramebufferSystem.js b/packages/core/src/renderers/systems/FramebufferSystem.js deleted file mode 100644 index df7902b..0000000 --- a/packages/core/src/renderers/systems/FramebufferSystem.js +++ /dev/null @@ -1,246 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { Rectangle } from '@pixi/math'; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class FramebufferSystem extends WebGLSystem -{ - /** - * Sets up the renderer context and necessary buffers. - * - * @private - */ - contextChange() - { - this.gl = this.renderer.gl; - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - this.current = null; - this.viewport = new Rectangle(); - - this.drawBufferExtension = this.renderer.context.extensions.drawBuffers; - } - - bind(framebuffer, frame) - { - const gl = this.gl; - - this.current = framebuffer; - - if (framebuffer) - { - // TODO cacheing layer! - - const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID] || this.initFramebuffer(framebuffer); - - gl.bindFramebuffer(gl.FRAMEBUFFER, fbo.framebuffer); - // makesure all textures are unbound.. - - // now check for updates... - if (fbo.dirtyId !== framebuffer.dirtyId) - { - fbo.dirtyId = framebuffer.dirtyId; - - if (fbo.dirtyFormat !== framebuffer.dirtyFormat) - { - fbo.dirtyFormat = framebuffer.dirtyFormat; - this.updateFramebuffer(framebuffer); - } - else if (fbo.dirtySize !== framebuffer.dirtySize) - { - fbo.dirtySize = framebuffer.dirtySize; - this.resizeFramebuffer(framebuffer); - } - } - - for (let i = 0; i < framebuffer.colorTextures.length; i++) - { - if (framebuffer.colorTextures[i].texturePart) - { - this.renderer.texture.unbind(framebuffer.colorTextures[i].texture); - } - else - { - this.renderer.texture.unbind(framebuffer.colorTextures[i]); - } - } - - if (framebuffer.depthTexture) - { - this.renderer.texture.unbind(framebuffer.depthTexture); - } - - if (frame) - { - this.setViewport(frame.x, frame.y, frame.width, frame.height); - } - else - { - this.setViewport(0, 0, framebuffer.width, framebuffer.height); - } - } - else - { - gl.bindFramebuffer(gl.FRAMEBUFFER, null); - - if (frame) - { - this.setViewport(frame.x, frame.y, frame.width, frame.height); - } - else - { - this.setViewport(0, 0, this.renderer.width, this.renderer.height); - } - } - } - - setViewport(x, y, width, height) - { - const v = this.viewport; - - if (v.width !== width || v.height !== height || v.x !== x || v.y !== y) - { - v.x = x; - v.y = y; - v.width = width; - v.height = height; - - this.gl.viewport(x, y, width, height); - } - } - - get size() - { - if (this.current) - { - // TODO store temp - return { x: 0, y: 0, width: this.current.width, height: this.current.height }; - } - - return { x: 0, y: 0, width: this.renderer.width, height: this.renderer.height }; - } - - clear(r, g, b, a) - { - const gl = this.gl; - - // TODO clear color can be set only one right? - gl.clearColor(r, g, b, a); - gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); - } - - // private functions... - - initFramebuffer(framebuffer) - { - const gl = this.gl; - - // TODO - make this a class? - const fbo = { - framebuffer: gl.createFramebuffer(), - stencil: null, - dirtyId: 0, - dirtyFormat: 0, - dirtySize: 0, - }; - - framebuffer.glFrameBuffers[this.CONTEXT_UID] = fbo; - - return fbo; - } - - resizeFramebuffer(framebuffer) - { - const gl = this.gl; - - if (framebuffer.stencil || framebuffer.depth) - { - gl.bindRenderbuffer(gl.RENDERBUFFER, this.stencil); - gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); - } - } - - updateFramebuffer(framebuffer) - { - const gl = this.gl; - - const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID]; - - // bind the color texture - const colorTextures = framebuffer.colorTextures; - - let count = colorTextures.length; - - if (!this.drawBufferExtension) - { - count = Math.min(count, 1); - } - - const activeTextures = []; - - for (let i = 0; i < count; i++) - { - const texture = framebuffer.colorTextures[i]; - - if (texture.texturePart) - { - this.renderer.texture.bind(texture.texture, 0); - - gl.framebufferTexture2D(gl.FRAMEBUFFER, - gl.COLOR_ATTACHMENT0 + i, - gl.TEXTURE_CUBE_MAP_NEGATIVE_X + texture.side, - texture.texture._glTextures[this.CONTEXT_UID].texture, - 0); - } - else - { - this.renderer.texture.bind(texture, 0); - - gl.framebufferTexture2D(gl.FRAMEBUFFER, - gl.COLOR_ATTACHMENT0 + i, - gl.TEXTURE_2D, - texture._glTextures[this.CONTEXT_UID].texture, - 0); - } - - activeTextures.push(gl.COLOR_ATTACHMENT0 + i); - } - - if (this.drawBufferExtension && activeTextures.length > 1) - { - this.drawBufferExtension.drawBuffersWEBGL(activeTextures); - } - - if (framebuffer.depthTexture) - { - const depthTextureExt = this.renderer.context.extensions.depthTexture; - - if (depthTextureExt) - { - const depthTexture = framebuffer.depthTexture; - - this.renderer.texture.bind(depthTexture, 0); - - gl.framebufferTexture2D(gl.FRAMEBUFFER, - gl.DEPTH_ATTACHMENT, - gl.TEXTURE_2D, - depthTexture._glTextures[this.CONTEXT_UID].texture, - 0); - } - } - - if (framebuffer.stencil || framebuffer.depth) - { - fbo.stencil = gl.createRenderbuffer(); - - gl.bindRenderbuffer(gl.RENDERBUFFER, fbo.stencil); - - // TODO.. this is depth AND stencil? - gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, fbo.stencil); - gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); - // fbo.enableStencil(); - } - } -} diff --git a/packages/core/src/renderers/systems/MaskSystem.js b/packages/core/src/renderers/systems/MaskSystem.js deleted file mode 100644 index d9c4b1a..0000000 --- a/packages/core/src/renderers/systems/MaskSystem.js +++ /dev/null @@ -1,199 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import AlphaMaskFilter from '../filters/spriteMask/SpriteMaskFilter'; - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class MaskSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - // TODO - we don't need both! - this.scissor = false; - this.scissorData = null; - this.scissorRenderTarget = null; - - this.enableScissor = false; - - this.alphaMaskPool = []; - this.alphaMaskIndex = 0; - } - - /** - * Applies the Mask and adds it to the current filter stack. - * - * @param {PIXI.DisplayObject} target - Display Object to push the mask to - * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. - */ - push(target, maskData) - { - // TODO the root check means scissor rect will not - // be used on render textures more info here: - // https://github.com/pixijs/pixi.js/pull/3545 - - if (maskData.texture) - { - this.pushSpriteMask(target, maskData); - } - else if (this.enableScissor - && !this.scissor - && this.renderer._activeRenderTarget.root - && !this.renderer.stencil.stencilMaskStack.length - && maskData.isFastRect()) - { - const matrix = maskData.worldTransform; - - let rot = Math.atan2(matrix.b, matrix.a); - - // use the nearest degree! - rot = Math.round(rot * (180 / Math.PI)); - - if (rot % 90) - { - this.pushStencilMask(maskData); - } - else - { - this.pushScissorMask(target, maskData); - } - } - else - { - this.pushStencilMask(maskData); - } - } - - /** - * Removes the last mask from the mask stack and doesn't return it. - * - * @param {PIXI.DisplayObject} target - Display Object to pop the mask from - * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. - */ - pop(target, maskData) - { - if (maskData.texture) - { - this.popSpriteMask(target, maskData); - } - else if (this.enableScissor && !this.renderer.stencil.stencilMaskStack.length) - { - this.popScissorMask(target, maskData); - } - else - { - this.popStencilMask(target, maskData); - } - } - - /** - * Applies the Mask and adds it to the current filter stack. - * - * @param {PIXI.RenderTarget} target - Display Object to push the sprite mask to - * @param {PIXI.Sprite} maskData - Sprite to be used as the mask - */ - pushSpriteMask(target, maskData) - { - let alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; - - if (!alphaMaskFilter) - { - alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; - } - - alphaMaskFilter[0].resolution = this.renderer.resolution; - alphaMaskFilter[0].maskSprite = maskData; - - // TODO - may cause issues! - target.filterArea = maskData.getBounds(true); - - this.renderer.filterSystem.pushFilter(target, alphaMaskFilter); - - this.alphaMaskIndex++; - } - - /** - * Removes the last filter from the filter stack and doesn't return it. - * - */ - popSpriteMask() - { - this.renderer.filterSystem.popFilter(); - this.alphaMaskIndex--; - } - - /** - * Applies the Mask and adds it to the current filter stack. - * - * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. - */ - pushStencilMask(maskData) - { - this.renderer.batch.flush(); - this.renderer.stencil.pushStencil(maskData); - } - - /** - * Removes the last filter from the filter stack and doesn't return it. - * - */ - popStencilMask() - { - // this.renderer.currentRenderer.stop(); - this.renderer.stencil.popStencil(); - } - - /** - * - * @param {PIXI.DisplayObject} target - Display Object to push the mask to - * @param {PIXI.Graphics} maskData - The masking data. - */ - pushScissorMask(target, maskData) - { - maskData.renderable = true; - - const renderTarget = this.renderer._activeRenderTarget; - - const bounds = maskData.getBounds(); - - bounds.fit(renderTarget.size); - maskData.renderable = false; - - this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); - - const resolution = this.renderer.resolution; - - this.renderer.gl.scissor( - bounds.x * resolution, - (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, - bounds.width * resolution, - bounds.height * resolution - ); - - this.scissorRenderTarget = renderTarget; - this.scissorData = maskData; - this.scissor = true; - } - - /** - * - * - */ - popScissorMask() - { - this.scissorRenderTarget = null; - this.scissorData = null; - this.scissor = false; - - // must be scissor! - const gl = this.renderer.gl; - - gl.disable(gl.SCISSOR_TEST); - } -} diff --git a/packages/core/src/renderers/systems/ProjectionSystem.js b/packages/core/src/renderers/systems/ProjectionSystem.js deleted file mode 100644 index ec5efdb..0000000 --- a/packages/core/src/renderers/systems/ProjectionSystem.js +++ /dev/null @@ -1,73 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { Matrix } from '@pixi/math'; - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ - -export default class ProjectionSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this.projectionMatrix = new Matrix(); - } - - update(destinationFrame, sourceFrame, resolution, root) - { - this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; - this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; - - this.calculateProjection(this.destinationFrame, this.sourceFrame, resolution, root); - - this.renderer.globalUniforms.uniforms.projectionMatrix = this.projectionMatrix; - this.renderer.globalUniforms.update(); - } - - /** - * Updates the projection matrix based on a projection frame (which is a rectangle) - * - * @param {Rectangle} destinationFrame - The destination frame. - * @param {Rectangle} sourceFrame - The source frame. - */ - calculateProjection(destinationFrame, sourceFrame, resolution, root) - { - const pm = this.projectionMatrix; - - // I don't think we will need this line.. - // pm.identity(); - - if (!root) - { - pm.a = (1 / destinationFrame.width * 2) * resolution; - pm.d = (1 / destinationFrame.height * 2) * resolution; - - pm.tx = -1 - (sourceFrame.x * pm.a); - pm.ty = -1 - (sourceFrame.y * pm.d); - } - else - { - pm.a = (1 / destinationFrame.width * 2) * resolution; - pm.d = (-1 / destinationFrame.height * 2) * resolution; - - pm.tx = -1 - (sourceFrame.x * pm.a); - pm.ty = 1 - (sourceFrame.y * pm.d); - } - } - - /** - * Sets the transform of the active render target to the given matrix - * - * @param {PIXI.Matrix} matrix - The transformation matrix - */ - setTransform()// matrix) - { - // this._activeRenderTarget.transform = matrix; - } -} diff --git a/packages/core/src/renderers/systems/RenderTextureSystem.js b/packages/core/src/renderers/systems/RenderTextureSystem.js deleted file mode 100644 index a133ef1..0000000 --- a/packages/core/src/renderers/systems/RenderTextureSystem.js +++ /dev/null @@ -1,115 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { Rectangle } from '@pixi/math'; - -const tempRect = new Rectangle(); - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ - -export default class RenderTextureSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this.clearColor = renderer._backgroundColorRgba; - - // TODO moe this property somewhere else! - this.defaultMaskStack = []; - this.defaultFilterStack = [{}]; - - // empty render texture? - this.renderTexture = null; - - this.destinationFrame = new Rectangle(); - } - - bind(renderTexture, sourceFrame, destinationFrame) - { - // TODO - do we want this?? - if (this.renderTexture === renderTexture) return; - this.renderTexture = renderTexture; - - const renderer = this.renderer; - - if (renderTexture) - { - const baseTexture = renderTexture.baseTexture; - - if (!destinationFrame) - { - tempRect.width = baseTexture.realWidth; - tempRect.height = baseTexture.realHeight; - - destinationFrame = tempRect; - } - - if (!sourceFrame) - { - sourceFrame = destinationFrame; - } - - this.renderer.framebuffer.bind(baseTexture.frameBuffer, destinationFrame); - - this.renderer.projection.update(destinationFrame, sourceFrame, baseTexture.resolution, false); - this.renderer.stencil.setMaskStack(baseTexture.stencilMaskStack); - } - else - { - // TODO these validation checks happen deeper down.. - // thing they can be avoided.. - if (!destinationFrame) - { - tempRect.width = renderer.width; - tempRect.height = renderer.height; - - destinationFrame = tempRect; - } - - if (!sourceFrame) - { - sourceFrame = destinationFrame; - } - - renderer.framebuffer.bind(null, destinationFrame); - - // TODO store this.. - this.renderer.projection.update(destinationFrame, sourceFrame, this.renderer.resolution, true); - this.renderer.stencil.setMaskStack(this.defaultMaskStack); - } - - this.destinationFrame.copyFrom(destinationFrame); - } - - /** - * Erases the render texture and fills the drawing area with a colour - * - * @param {number} [clearColor] - The colour - * @return {PIXI.Renderer} Returns itself. - */ - clear(clearColor) - { - if (this.renderTexture) - { - clearColor = clearColor || this.renderTexture.baseTexture.clearColor; - } - else - { - clearColor = clearColor || this.clearColor; - } - - this.renderer.framebuffer.clear(clearColor[0], clearColor[1], clearColor[2], clearColor[3]); - } - - resize()// screenWidth, screenHeight) - { - // resize the root only! - this.bind(null); - } -} diff --git a/packages/core/src/renderers/systems/StateSystem.js b/packages/core/src/renderers/systems/StateSystem.js deleted file mode 100755 index d7f4caf..0000000 --- a/packages/core/src/renderers/systems/StateSystem.js +++ /dev/null @@ -1,288 +0,0 @@ -import mapWebGLBlendModesToPixi from '../utils/mapWebGLBlendModesToPixi'; -import WebGLSystem from './WebGLSystem'; -import WebGLState from '../State'; - -const BLEND = 0; -const OFFSET = 1; -const CULLING = 2; -const DEPTH_TEST = 3; -const WINDING = 4; - -/** - * A WebGL state machines - * - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class StateSystem extends WebGLSystem -{ - /** - * @param {WebGLRenderingContext} gl - The current WebGL rendering context - */ - constructor(renderer) - { - super(renderer); - - this.gl = null; - - this.maxAttribs = null; - - // check we have vao.. - this.nativeVaoExtension = null; - - this.attribState = null; - - this.stateId = 0; - this.polygonOffset = 0; - this.blendMode = 17; - - this.map = []; - - // map functions for when we set state.. - this.map[BLEND] = this.setBlend; - this.map[OFFSET] = this.setOffset; - this.map[CULLING] = this.setCullFace; - this.map[DEPTH_TEST] = this.setDepthTest; - this.map[WINDING] = this.setFrontFace; - - this.checks = []; - - this.defaultState = new WebGLState(); - this.defaultState.blend = true; - this.defaultState.depth = true; - } - - contextChange(gl) - { - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; - - this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); - - // check we have vao.. - this.nativeVaoExtension = ( - gl.getExtension('OES_vertex_array_object') - || gl.getExtension('MOZ_OES_vertex_array_object') - || gl.getExtension('WEBKIT_OES_vertex_array_object') - ); - - this.attribState = { - tempAttribState: new Array(this.maxAttribs), - attribState: new Array(this.maxAttribs), - }; - - this.blendModes = mapWebGLBlendModesToPixi(gl); - - this.setState(this.defaultState); - - this.reset(); - } - - /** - * Sets the current state - * - * @param {*} state - The state to set. - */ - setState(state) - { - state = state || this.defaultState; - - // TODO maybe to an object check? ( this.state === state )? - if (this.stateId !== state.data) - { - let diff = this.stateId ^ state.data; - let i = 0; - - // order from least to most common - while (diff) - { - if (diff & 1) - { - // state change! - this.map[i].call(this, !!(state.data & (1 << i))); - } - - diff = diff >> 1; - i++; - } - - this.stateId = state.data; - } - - // based on the above settings we check for specific modes.. - // for example if blend is active we check and set the blend modes - // or of polygon offset is active we check the poly depth. - for (let i = 0; i < this.checks.length; i++) - { - this.checks[i](this, state); - } - } - - /** - * Enables or disabled blending. - * - * @param {boolean} value - Turn on or off webgl blending. - */ - setBlend(value) - { - this.updateCheck(StateSystem.checkBlendMode, value); - - this.gl[value ? 'enable' : 'disable'](this.gl.BLEND); - } - - /** - * Enables or disable polygon offset fill - * - * @param {boolean} value - Turn on or off webgl polygon offset testing. - */ - setOffset(value) - { - this.gl[value ? 'enable' : 'disable'](this.gl.POLYGON_OFFSET_FILL); - } - - /** - * Sets whether to enable or disable depth test. - * - * @param {boolean} value - Turn on or off webgl depth testing. - */ - setDepthTest(value) - { - this.gl[value ? 'enable' : 'disable'](this.gl.DEPTH_TEST); - } - - /** - * Sets whether to enable or disable cull face. - * - * @param {boolean} value - Turn on or off webgl cull face. - */ - setCullFace(value) - { - this.gl[value ? 'enable' : 'disable'](this.gl.CULL_FACE); - } - - /** - * Sets the gl front face. - * - * @param {boolean} value - true is clockwise and false is counter-clockwise - */ - setFrontFace(value) - { - this.gl.frontFace(this.gl[value ? 'CW' : 'CCW']); - } - - /** - * Sets the blend mode. - * - * @param {number} value - The blend mode to set to. - */ - setBlendMode(value) - { - if (value === this.blendMode) - { - return; - } - - this.blendMode = value; - this.gl.blendFunc(this.blendModes[value][0], this.blendModes[value][1]); - } - - /** - * Sets the polygon offset. - * - * @param {number} value - the polygon offset - * @param {number} scale - the polygon offset scale - */ - setPolygonOffset(value, scale) - { - this.gl.polygonOffset(value, scale); - } - - /** - * Disables all the vaos in use - * - */ - resetAttributes() - { - for (let i = 0; i < this.attribState.tempAttribState.length; i++) - { - this.attribState.tempAttribState[i] = 0; - } - - for (let i = 0; i < this.attribState.attribState.length; i++) - { - this.attribState.attribState[i] = 0; - } - - // im going to assume one is always active for performance reasons. - for (let i = 1; i < this.maxAttribs; i++) - { - this.gl.disableVertexAttribArray(i); - } - } - - // used - /** - * Resets all the logic and disables the vaos - */ - reset() - { - // unbind any VAO if they exist.. - if (this.nativeVaoExtension) - { - this.nativeVaoExtension.bindVertexArrayOES(null); - } - - // reset all attributes.. - this.resetAttributes(); - - this.gl.pixelStorei(this.gl.UNPACK_FLIP_Y_WEBGL, false); - - this.setBlendMode(0); - - // TODO? - // this.setState(this.defaultState); - } - - /** - * checks to see which updates should be checked based on which settings have been activated - * for example if blend is enabled then we shold check the blend modes each time the state is changed - * or if poygon fill is activated then we need to check if the polygone offset changes. - * The idea is that we only check what we have too. - * - * @param {Function} func the checking function to add or remove - * @param {boolean} value should the check function be added or removed. - */ - updateCheck(func, value) - { - const index = this.checks.indexOf(func); - - if (value && index === -1) - { - this.checks.push(func); - } - else if (!value && index !== -1) - { - this.checks.splice(index, 1); - } - } - - /** - * A private little wrapper function that we call to check the blend mode. - * - * @static - * @private - * @param {PIXI.StateSystem} System the System to perform the state check on - * @param {PIXI.State} state the state that the blendMode will pulled from - */ - static checkBlendMode(system, state) - { - system.setBlendMode(state.blendMode); - } - - // TODO - add polygon offset? -} diff --git a/packages/core/src/renderers/systems/StencilSystem.js b/packages/core/src/renderers/systems/StencilSystem.js deleted file mode 100644 index 5d538cb..0000000 --- a/packages/core/src/renderers/systems/StencilSystem.js +++ /dev/null @@ -1,133 +0,0 @@ -import WebGLSystem from './WebGLSystem'; - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class StencilSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - this.stencilMaskStack = []; - } - - /** - * Changes the mask stack that is used by this System. - * - * @param {PIXI.Graphics[]} stencilMaskStack - The mask stack - */ - setMaskStack(stencilMaskStack) - { - const gl = this.renderer.gl; - - if (stencilMaskStack.length !== this.stencilMaskStack.length) - { - if (stencilMaskStack.length === 0) - { - gl.disable(gl.STENCIL_TEST); - } - else - { - gl.enable(gl.STENCIL_TEST); - } - } - - this.stencilMaskStack = stencilMaskStack; - } - - /** - * Applies the Mask and adds it to the current stencil stack. @alvin - * - * @param {PIXI.Graphics} graphics - The mask - */ - pushStencil(graphics) - { - this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); - - // this.renderer._activeRenderTarget.attachStencilBuffer(); - - const gl = this.renderer.gl; - const prevMaskCount = this.stencilMaskStack.length; - - if (prevMaskCount === 0) - { - gl.enable(gl.STENCIL_TEST); - } - - this.stencilMaskStack.push(graphics); - - // Increment the refference stencil value where the new mask overlaps with the old ones. - gl.colorMask(false, false, false, false); - gl.stencilFunc(gl.EQUAL, prevMaskCount, this._getBitwiseMask()); - gl.stencilOp(gl.KEEP, gl.KEEP, gl.INCR); - this.renderer.plugins.graphics.render(graphics); - - this._useCurrent(); - } - - /** - * Removes the last mask from the stencil stack. @alvin - */ - popStencil() - { - this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); - - const gl = this.renderer.gl; - const graphics = this.stencilMaskStack.pop(); - - if (this.stencilMaskStack.length === 0) - { - // the stack is empty! - gl.disable(gl.STENCIL_TEST); - gl.clear(gl.STENCIL_BUFFER_BIT); - gl.clearStencil(0); - } - else - { - // Decrement the refference stencil value where the popped mask overlaps with the other ones - gl.colorMask(false, false, false, false); - gl.stencilOp(gl.KEEP, gl.KEEP, gl.DECR); - this.renderer.plugins.graphics.render(graphics); - - this._useCurrent(); - } - } - - /** - * Setup renderer to use the current stencil data. - */ - _useCurrent() - { - const gl = this.renderer.gl; - - gl.colorMask(true, true, true, true); - gl.stencilFunc(gl.EQUAL, this.stencilMaskStack.length, this._getBitwiseMask()); - gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP); - } - - /** - * Fill 1s equal to the number of acitve stencil masks. - * - * @return {number} The bitwise mask. - */ - _getBitwiseMask() - { - return (1 << this.stencilMaskStack.length) - 1; - } - - /** - * Destroys the mask stack. - * - */ - destroy() - { - super.destroy(this); - - this.stencilMaskStack = null; - } -} diff --git a/packages/core/src/renderers/systems/WebGLSystem.js b/packages/core/src/renderers/systems/WebGLSystem.js deleted file mode 100644 index d3d5b31..0000000 --- a/packages/core/src/renderers/systems/WebGLSystem.js +++ /dev/null @@ -1,41 +0,0 @@ -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this manager works for. - */ - constructor(renderer) - { - /** - * The renderer this manager works for. - * - * @member {PIXI.Renderer} - */ - this.renderer = renderer; - - this.renderer.runners.contextChange.add(this); - } - - /** - * Generic method called when there is a WebGL context change. - * - */ - contextChange() - { - // do some codes init! - } - - /** - * Generic destroy methods to be overridden by the subclass - * - */ - destroy() - { - this.renderer.runners.contextChange.remove(this); - this.renderer = null; - } -} diff --git a/packages/core/src/renderers/systems/filter/FilterSystem.js b/packages/core/src/renderers/systems/filter/FilterSystem.js deleted file mode 100644 index e615037..0000000 --- a/packages/core/src/renderers/systems/filter/FilterSystem.js +++ /dev/null @@ -1,431 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; - -import RenderTexture from '../../../textures/RenderTexture'; -import Quad from '../../utils/Quad'; -import QuadUv from '../../utils/QuadUv'; -import { Rectangle } from '@pixi/math'; -import * as filterTransforms from '../../filters/filterTransforms'; -import bitTwiddle from 'bit-twiddle'; -import UniformGroup from '../../../shader/UniformGroup'; -import { DRAW_MODES } from '../../../../../constants'; - -// -/** - * @ignore - * @class - */ -class FilterState -{ - /** - * - */ - constructor() - { - this.renderTexture = null; - this.sourceFrame = new Rectangle(); - this.destinationFrame = new Rectangle(); - this.filters = []; - this.target = null; - this.legacy = false; - this.resolution = 1; - } -} - -/** - * @class - * @memberof PIXI.systems - * @extends PIXI.systems.WebGLSystem - */ -export default class FilterSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - /** - * stores a bunch of PO2 textures used for filtering - * @type {Object} - */ - this.texturePool = {}; - - /** - * a pool for storing filter states, save us creating new ones each tick - * @type {Array} - */ - this.statePool = []; - - /** - * A very simple geometry used when drawing a filter effect to the screen - * @type {Quad} - */ - this.quad = new Quad(); - - this.quadUv = new QuadUv(); - - /** - * Temporary rect for maths - * @type {PIXI.Rectangle} - */ - this.tempRect = new Rectangle(); - - this.activeState = {}; - - /** - * this uniform group is attached to filter uniforms when used - * @type {UniformGroup} - */ - this.globalUniforms = new UniformGroup({ - sourceFrame: this.tempRect, - destinationFrame: this.tempRect, - - // legacy variables - filterArea: new Float32Array(4), - filterClamp: new Float32Array(4), - }, true); - } - - /** - * Adds a new filter to the System. - * - * @param {PIXI.DisplayObject} target - The target of the filter to render. - * @param {PIXI.Filter[]} filters - The filters to apply. - */ - push(target, filters) - { - const renderer = this.renderer; - const filterStack = this.renderer.renderTexture.defaultFilterStack; - const state = this.statePool.pop() || new FilterState(); - - let resolution = filters[0].resolution; - let padding = filters[0].padding; - let autoFit = filters[0].autoFit; - let legacy = filters[0].legacy; - - for (let i = 1; i < filters.length; i++) - { - const filter = filters[i]; - - // lets use the lowest resolution.. - resolution = Math.min(resolution, filter.resolution); - // and the largest amount of padding! - padding = Math.max(padding, filter.padding); - // only auto fit if all filters are autofit - autoFit = autoFit || filter.autoFit; - - legacy = legacy || filter.legacy; - } - - filterStack.push(state); - - state.resolution = resolution; - - state.legacy = legacy; - - // round to whole number based on resolution - // TODO move that to the shader too? - state.sourceFrame = target.filterArea || target.getBounds(true); - - state.sourceFrame.pad(padding); - - if (autoFit) - { - state.sourceFrame.fit(this.renderer.renderTexture.destinationFrame); - } - - state.sourceFrame.round(resolution); - - state.renderTexture = this.getPotFilterTexture(state.sourceFrame.width, state.sourceFrame.height, resolution); - state.filters = filters; - - state.destinationFrame.width = state.renderTexture.width; - state.destinationFrame.height = state.renderTexture.height; - - state.renderTexture.filterFrame = state.sourceFrame; - renderer.renderTexture.bind(state.renderTexture, state.sourceFrame);// /, state.destinationFrame); - renderer.renderTexture.clear(); - } - - /** - * Pops off the filter and applies it. - * - */ - pop() - { - const renderer = this.renderer; - const filterStack = renderer.renderTexture.defaultFilterStack; - const state = filterStack.pop(); - const filters = state.filters; - - this.activeState = state; - - const globalUniforms = this.globalUniforms.uniforms; - - globalUniforms.sourceFrame = state.sourceFrame; - globalUniforms.destinationFrame = state.destinationFrame; - globalUniforms.resolution = state.resolution; - - // only update the rect if its legacy.. - if (state.legacy) - { - const filterArea = globalUniforms.filterArea; - const filterClamp = globalUniforms.filterClamp; - - filterArea[0] = state.destinationFrame.width; - filterArea[1] = state.destinationFrame.height; - filterArea[2] = state.sourceFrame.x; - filterArea[3] = state.sourceFrame.y; - - filterClamp[0] = 0.5 / state.resolution / state.destinationFrame.width; - filterClamp[1] = 0.5 / state.resolution / state.destinationFrame.height; - filterClamp[2] = (state.sourceFrame.width - 0.5) / state.resolution / state.destinationFrame.width; - filterClamp[3] = (state.sourceFrame.height - 0.5) / state.resolution / state.destinationFrame.height; - } - - this.globalUniforms.update(); - - const lastState = filterStack[filterStack.length - 1]; - - if (filters.length === 1) - { - filters[0].apply(this, state.renderTexture, lastState.renderTexture, false, state); - - this.returnFilterTexture(state.renderTexture); - } - else - { - let flip = state.renderTexture; - let flop = this.getPotFilterTexture( - flip.width, - flip.height, - state.resolution - ); - - flop.filterFrame = flip.filterFrame; - - let i = 0; - - for (i = 0; i < filters.length - 1; ++i) - { - filters[i].apply(this, flip, flop, true, state); - - const t = flip; - - flip = flop; - flop = t; - } - - filters[i].apply(this, flip, lastState.renderTexture, false, state); - - this.returnFilterTexture(flip); - this.returnFilterTexture(flop); - } - - this.statePool.push(state); - } - - /** - * Draws a filter. - * - * @param {PIXI.Filter} filter - The filter to draw. - * @param {PIXI.RenderTarget} input - The input render target. - * @param {PIXI.RenderTarget} output - The target to output to. - * @param {boolean} clear - Should the output be cleared before rendering to it - */ - applyFilter(filter, input, output, clear) - { - const renderer = this.renderer; - - renderer.renderTexture.bind(output, output ? output.filterFrame : null); - - if (clear) - { - // gl.disable(gl.SCISSOR_TEST); - renderer.renderTexture.clear(); - // gl.enable(gl.SCISSOR_TEST); - } - - // set the uniforms.. - filter.uniforms.uSampler = input; - filter.uniforms.filterGlobals = this.globalUniforms; - - // TODO make it so that the order of this does not matter.. - // because it does at the moment cos of global uniforms. - // they need to get resynced - - renderer.state.setState(filter.state); - renderer.shader.bind(filter); - - if (filter.legacy) - { - this.quadUv.map(input._frame, input.filterFrame); - - renderer.geometry.bind(this.quadUv); - renderer.geometry.draw(DRAW_MODES.TRIANGLES); - } - else - { - renderer.geometry.bind(this.quad); - renderer.geometry.draw(DRAW_MODES.TRIANGLE_STRIP); - } - } - - /** - * Calculates the mapped matrix. - * - * TODO playing around here.. this is temporary - (will end up in the shader) - * this returns a matrix that will normalise map filter cords in the filter to screen space - * - * @param {PIXI.Matrix} outputMatrix - the matrix to output to. - * @return {PIXI.Matrix} The mapped matrix. - */ - calculateScreenSpaceMatrix(outputMatrix) - { - const currentState = this.activeState; - - return filterTransforms.calculateScreenSpaceMatrix( - outputMatrix, - currentState.sourceFrame, - currentState.destinationFrame - ); - } - - /** - * This will map the filter coord so that a texture can be used based on the transform of a sprite - * - * @param {PIXI.Matrix} outputMatrix - The matrix to output to. - * @param {PIXI.Sprite} sprite - The sprite to map to. - * @return {PIXI.Matrix} The mapped matrix. - */ - calculateSpriteMatrix(outputMatrix, sprite) - { - const currentState = this.activeState; - - return filterTransforms.calculateSpriteMatrix( - outputMatrix, - currentState.sourceFrame, - currentState.destinationFrame, - sprite - ); - } - - /** - * Destroys this Filter System. - * - * @param {boolean} [contextLost=false] context was lost, do not free shaders - * - */ - destroy(contextLost = false) - { - if (!contextLost) - { - this.emptyPool(); - } - else - { - this.texturePool = {}; - } - } - - /** - * Gets a Power-of-Two render texture. - * - * TODO move to a seperate class could be on renderer? - * also - could cause issue with multiple contexts? - * - * @private - * @param {WebGLRenderingContext} gl - The webgl rendering context - * @param {number} minWidth - The minimum width of the render target. - * @param {number} minHeight - The minimum height of the render target. - * @param {number} resolution - The resolution of the render target. - * @return {PIXI.RenderTarget} The new render target. - */ - getPotFilterTexture(minWidth, minHeight, resolution) - { - minWidth = bitTwiddle.nextPow2(minWidth); - minHeight = bitTwiddle.nextPow2(minHeight); - resolution = resolution || 1; - - const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); - - if (!this.texturePool[key]) - { - this.texturePool[key] = []; - } - - let renderTexture = this.texturePool[key].pop(); - - if (!renderTexture) - { - // temporary bypass cache.. - // internally - this will cause a texture to be bound.. - renderTexture = RenderTexture.create({ - width: minWidth, - height: minHeight, - resolution, - }); - } - - return renderTexture; - } - - /** - * Gets extra render texture to use inside current filter - * - * @param {number} resolution resolution of the renderTexture - * @returns {PIXI.RenderTexture} - */ - getFilterTexture(resolution) - { - const rt = this.activeState.renderTexture; - - const filterTexture = this.getPotFilterTexture(rt.width, rt.height, resolution || rt.baseTexture.resolution); - - filterTexture.filterFrame = rt.filterFrame; - - return filterTexture; - } - - /** - * Frees a render texture back into the pool. - * - * @param {PIXI.RenderTarget} renderTexture - The renderTarget to free - */ - returnFilterTexture(renderTexture) - { - renderTexture.filterFrame = null; - - const base = renderTexture.baseTexture; - - const minWidth = base.width; - const minHeight = base.height; - - const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); - - this.texturePool[key].push(renderTexture); - } - - /** - * Empties the texture pool. - * - */ - emptyPool() - { - for (const i in this.texturePool) - { - const textures = this.texturePool[i]; - - if (textures) - { - for (let j = 0; j < textures.length; j++) - { - textures[j].destroy(true); - } - } - } - - this.texturePool = {}; - } -} diff --git a/packages/core/src/renderers/systems/geometry/GLBuffer.js b/packages/core/src/renderers/systems/geometry/GLBuffer.js deleted file mode 100644 index 0477e8a..0000000 --- a/packages/core/src/renderers/systems/geometry/GLBuffer.js +++ /dev/null @@ -1,9 +0,0 @@ -export default class GLBuffer -{ - constructor(buffer) - { - this.buffer = buffer; - this.updateID = -1; - this.byteLength = -1; - } -} diff --git a/packages/core/src/renderers/systems/geometry/GeometrySystem.js b/packages/core/src/renderers/systems/geometry/GeometrySystem.js deleted file mode 100644 index 26c8e9e..0000000 --- a/packages/core/src/renderers/systems/geometry/GeometrySystem.js +++ /dev/null @@ -1,388 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; -import GLBuffer from './GLBuffer'; - -const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class GeometrySystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this._activeGeometry = null; - this._activeVao = null; - - this.hasVao = true; - this.hasInstance = true; - } - - /** - * Sets up the renderer context and necessary buffers. - * - * @private - */ - contextChange() - { - const gl = this.gl = this.renderer.gl; - - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - - // webgl2 - if (!gl.createVertexArray) - { - // webgl 1! - let nativeVaoExtension = this.renderer.context.extensions.vertexArrayObject; - - if (this.renderer.legacy) - { - nativeVaoExtension = null; - } - - if (nativeVaoExtension) - { - gl.createVertexArray = () => - nativeVaoExtension.createVertexArrayOES(); - - gl.bindVertexArray = (vao) => - nativeVaoExtension.bindVertexArrayOES(vao); - - gl.deleteVertexArray = (vao) => - nativeVaoExtension.deleteVertexArrayOES(vao); - } - else - { - this.hasVao = false; - gl.createVertexArray = () => - { - // empty - }; - - gl.bindVertexArray = () => - { - // empty - }; - - gl.deleteVertexArray = () => - { - // empty - }; - } - } - - if (!gl.vertexAttribDivisor) - { - const instanceExt = gl.getExtension('ANGLE_instanced_arrays'); - - if (instanceExt) - { - gl.vertexAttribDivisor = (a, b) => - instanceExt.vertexAttribDivisorANGLE(a, b); - - gl.drawElementsInstanced = (a, b, c, d, e) => - instanceExt.drawElementsInstancedANGLE(a, b, c, d, e); - - gl.drawArraysInstanced = (a, b, c, d) => - instanceExt.drawArraysInstancedANGLE(a, b, c, d); - } - else - { - this.hasInstance = false; - } - } - } - - /** - * Binds geometry so that is can be drawn. Creating a Vao if required - * @private - * @param {PIXI.Geometry} geometry instance of geometry to bind - */ - bind(geometry, shader) - { - shader = shader || this.renderer.shader.shader; - - const gl = this.gl; - - // not sure the best way to address this.. - // currently different shaders require different VAOs for the same geometry - // Still mulling over the best way to solve this one.. - // will likely need to modify the shader attribute locations at run time! - let vaos = geometry.glVertexArrayObjects[this.CONTEXT_UID]; - - if (!vaos) - { - geometry.glVertexArrayObjects[this.CONTEXT_UID] = vaos = {}; - } - - const vao = vaos[shader.program.id] || this.initGeometryVao(geometry, shader.program); - - this._activeGeometry = geometry; - - if (this._activeVao !== vao) - { - this._activeVao = vao; - - if (this.hasVao) - { - gl.bindVertexArray(vao); - } - else - { - this.activateVao(geometry, shader.program); - } - } - - // TODO - optimise later! - // don't need to loop through if nothing changed! - // maybe look to add an 'autoupdate' to geometry? - this.updateBuffers(); - } - - reset() - { - this.unbind(); - } - - updateBuffers() - { - const geometry = this._activeGeometry; - const gl = this.gl; - - for (let i = 0; i < geometry.buffers.length; i++) - { - const buffer = geometry.buffers[i]; - - const glBuffer = buffer._glBuffers[this.CONTEXT_UID]; - - if (buffer._updateID !== glBuffer.updateID) - { - glBuffer.updateID = buffer._updateID; - - // TODO can cache this on buffer! maybe added a getter / setter? - const type = buffer.index ? gl.ELEMENT_ARRAY_BUFFER : gl.ARRAY_BUFFER; - const drawType = buffer.static ? gl.STATIC_DRAW : gl.DYNAMIC_DRAW; - - gl.bindBuffer(type, glBuffer.buffer); - - if (glBuffer.byteLength >= buffer.data.byteLength) - { - // offset is always zero for now! - gl.bufferSubData(type, 0, buffer.data); - } - else - { - gl.bufferData(type, buffer.data, drawType); - } - } - } - } - - checkCompatability(geometry, program) - { - // geometry must have at least all the attributes that the shader requires. - const geometryAttributes = geometry.attributes; - const shaderAttributes = program.attributeData; - - for (const j in shaderAttributes) - { - if (!geometryAttributes[j]) - { - throw new Error(`shader and geometry incompatible, geometry missing the "${j}" attribute`); - } - } - } - - /** - * Creates a Vao with the same structure as the geometry and stores it on the geometry. - * @private - * @param {PIXI.Geometry} geometry instance of geometry to to generate Vao for - */ - initGeometryVao(geometry, program) - { - this.checkCompatability(geometry, program); - - const gl = this.gl; - const CONTEXT_UID = this.CONTEXT_UID; - const buffers = geometry.buffers; - const attributes = geometry.attributes; - - const tempStride = {}; - const tempStart = {}; - - for (const j in buffers) - { - tempStride[j] = 0; - tempStart[j] = 0; - } - - for (const j in attributes) - { - if (!attributes[j].size && program.attributeData[j]) - { - attributes[j].size = program.attributeData[j].size; - } - - tempStride[attributes[j].buffer] += attributes[j].size * byteSizeMap[attributes[j].type]; - } - - for (const j in attributes) - { - const attribute = attributes[j]; - const attribSize = attribute.size; - - if (attribute.stride === undefined) - { - if (tempStride[attribute.buffer] === attribSize * byteSizeMap[attribute.type]) - { - attribute.stride = 0; - } - else - { - attribute.stride = tempStride[attribute.buffer]; - } - } - - if (attribute.start === undefined) - { - attribute.start = tempStart[attribute.buffer]; - - tempStart[attribute.buffer] += attribSize * byteSizeMap[attribute.type]; - } - } - - // first update - and create the buffers! - // only create a gl buffer if it actually gets - for (let i = 0; i < buffers.length; i++) - { - const buffer = buffers[i]; - - if (!buffer._glBuffers[CONTEXT_UID]) - { - buffer._glBuffers[CONTEXT_UID] = new GLBuffer(gl.createBuffer()); - } - } - - // TODO - maybe make this a data object? - // lets wait to see if we need to first! - const vao = gl.createVertexArray(); - - gl.bindVertexArray(vao); - - this.activateVao(geometry, program); - - gl.bindVertexArray(null); - - // add it to the cache! - geometry.glVertexArrayObjects[this.CONTEXT_UID][program.id] = vao; - - return vao; - } - - activateVao(geometry, program) - { - const gl = this.gl; - const CONTEXT_UID = this.CONTEXT_UID; - const buffers = geometry.buffers; - const attributes = geometry.attributes; - - if (geometry.indexBuffer) - { - // first update the index buffer if we have one.. - gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, geometry.indexBuffer._glBuffers[CONTEXT_UID].buffer); - } - - let lastBuffer = null; - - // add a new one! - for (const j in attributes) - { - const attribute = attributes[j]; - const buffer = buffers[attribute.buffer]; - const glBuffer = buffer._glBuffers[CONTEXT_UID]; - - if (program.attributeData[j]) - { - if (lastBuffer !== glBuffer) - { - gl.bindBuffer(gl.ARRAY_BUFFER, glBuffer.buffer); - - lastBuffer = glBuffer; - } - - const location = program.attributeData[j].location; - - // TODO introduce state again - // we can optimise this for older devices that have no VAOs - gl.enableVertexAttribArray(location); - - gl.vertexAttribPointer(location, - attribute.size, - attribute.type || gl.FLOAT, - attribute.normalized, - attribute.stride, - attribute.start); - - if (attribute.instance) - { - // TODO calculate instance count based of this... - if (this.hasInstance) - { - gl.vertexAttribDivisor(location, 1); - } - else - { - throw new Error('geometry error, GPU Instancing is not supported on this device'); - } - } - } - } - } - - draw(type, size, start, instanceCount) - { - const gl = this.gl; - const geometry = this._activeGeometry; - - // TODO.. this should not change so maybe cache the function? - - if (geometry.indexBuffer) - { - if (geometry.instanced) - { - /* eslint-disable max-len */ - gl.drawElementsInstanced(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2, instanceCount || 1); - /* eslint-enable max-len */ - } - else - { - gl.drawElements(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2); - } - } - else - if (geometry.instanced) - { - // TODO need a better way to calculate size.. - gl.drawArraysInstanced(type, start, size || geometry.getSize(), instanceCount || 1); - } - else - { - gl.drawArrays(type, start, size || geometry.getSize()); - } - - return this; - } - - unbind() - { - this.gl.bindVertexArray(null); - this._activeVao = null; - this._activeGeometry = null; - } -} diff --git a/packages/core/src/renderers/systems/geometry/setVertexAttribArrays.js b/packages/core/src/renderers/systems/geometry/setVertexAttribArrays.js deleted file mode 100644 index 23b6959..0000000 --- a/packages/core/src/renderers/systems/geometry/setVertexAttribArrays.js +++ /dev/null @@ -1,54 +0,0 @@ -// var GL_MAP = {}; - -/** - * @param gl {WebGLRenderingContext} The current WebGL context - * @param attribs {*} - * @param state {*} - */ -export default function setVertexAttribArrays(gl, attribs, state) -{ - let i; - - if (state) - { - const tempAttribState = state.tempAttribState; - const attribState = state.attribState; - - for (i = 0; i < tempAttribState.length; i++) - { - tempAttribState[i] = false; - } - - // set the new attribs - for (i = 0; i < attribs.length; i++) - { - tempAttribState[attribs[i].attribute.location] = true; - } - - for (i = 0; i < attribState.length; i++) - { - if (attribState[i] !== tempAttribState[i]) - { - attribState[i] = tempAttribState[i]; - - if (state.attribState[i]) - { - gl.enableVertexAttribArray(i); - } - else - { - gl.disableVertexAttribArray(i); - } - } - } - } - else - { - for (i = 0; i < attribs.length; i++) - { - const attrib = attribs[i]; - - gl.enableVertexAttribArray(attrib.attribute.location); - } - } -} diff --git a/packages/core/src/renderers/systems/index.js b/packages/core/src/renderers/systems/index.js deleted file mode 100644 index c843f03..0000000 --- a/packages/core/src/renderers/systems/index.js +++ /dev/null @@ -1,6 +0,0 @@ -/** - * Systems are individual components to the Renderer pipeline. - * @namespace PIXI.systems - */ -export { default as WebGLSystem } from './WebGLSystem'; -export { default as FilterSystem } from './filter/FilterSystem'; diff --git a/packages/core/src/renderers/systems/shader/GLProgram.js b/packages/core/src/renderers/systems/shader/GLProgram.js deleted file mode 100644 index 92980ca..0000000 --- a/packages/core/src/renderers/systems/shader/GLProgram.js +++ /dev/null @@ -1,43 +0,0 @@ -/** - * Helper class to create a webGL Program - * - * @class - * @private - */ -export default class GLProgram -{ - constructor(program, uniformData) - { - /** - * The shader program - * - * @member {WebGLProgram} - */ - this.program = program; - - /** - * holds the uniform data which contains uniform locations - * and current uniform values used for caching and preventing undeed GPU commands - * @type {Object} - */ - this.uniformData = uniformData; - - /** - * uniformGroups holds the various upload functions for the shader. Each uniform group - * and program have a unique upload function generated. - * @type {Object} - */ - this.uniformGroups = {}; - } - - /** - * Destroys this program - * TODO - */ - destroy() - { - this.uniformData = null; - this.uniformGroups = null; - this.program = null; - } -} diff --git a/packages/core/src/AbstractRenderer.js b/packages/core/src/AbstractRenderer.js new file mode 100644 index 0000000..64b2eb1 --- /dev/null +++ b/packages/core/src/AbstractRenderer.js @@ -0,0 +1,353 @@ +import { hex2string, hex2rgb } from '@pixi/utils'; +import { Matrix, Rectangle } from '@pixi/math'; +import { RENDERER_TYPE } from '@pixi/constants'; +import { settings } from '@pixi/settings'; +import { Container } from '@pixi/display'; +import RenderTexture from './renderTexture/RenderTexture'; +import EventEmitter from 'eventemitter3'; + +const tempMatrix = new Matrix(); + +/** + * 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 EventEmitter + * @memberof PIXI + */ +export default class AbstractRenderer extends EventEmitter +{ + // eslint-disable-next-line valid-jsdoc + /** + * @param {string} system - The name of the system this renderer is for. + * @param {object} [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.transparent=false] - If the render view is transparent, default false + * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false + * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. The + * resolution of the renderer retina would be 2. + * @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 {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when rendering, + * stopping pixel interpolation. + */ + constructor(system, options, arg2, arg3) + { + super(); + + // Support for constructor(system, screenWidth, screenHeight, options) + if (typeof options === 'number') + { + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + }, arg3); + } + + // 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 = RENDERER_TYPE.UNKNOWN; + + /** + * Measurements of the screen. (0, 0, screenWidth, screenHeight) + * + * Its safe to use as filterArea or hitArea for 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 1 + */ + this.resolution = options.resolution || settings.RESOLUTION; + + /** + * Whether the render view is transparent + * + * @member {boolean} + */ + this.transparent = options.transparent; + + /** + * Whether css dimensions of canvas view should be resized to screen dimensions automatically + * + * @member {boolean} + */ + this.autoResize = options.autoResize || false; + + /** + * Tracks the blend modes useful for this renderer. + * + * @member {object} + */ + this.blendModes = null; + + /** + * 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; + + /** + * If true PixiJS will Math.floor() x/y values when rendering, stopping pixel interpolation. + * Handy for crisp pixel art and speed on legacy devices. + * + * @member {boolean} + */ + this.roundPixels = options.roundPixels; + + /** + * The background color as a number. + * + * @member {number} + * @private + */ + this._backgroundColor = 0x000000; + + /** + * The background color as an [R, G, B] array. + * + * @member {number[]} + * @private + */ + this._backgroundColorRgba = [0, 0, 0, 0]; + + /** + * The background color as a string. + * + * @member {string} + * @private + */ + this._backgroundColorString = '#000000'; + + this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter + + /** + * This temporary display object used as the parent of the currently being rendered item + * + * @member {PIXI.DisplayObject} + * @private + */ + this._tempDisplayObjectParent = new Container(); + + /** + * The last root object that the renderer tried to render. + * + * @member {PIXI.DisplayObject} + * @private + */ + this._lastObjectRendered = this._tempDisplayObjectParent; + + /** + * Collection of plugins. + * @readonly + * @member {object} + */ + this.plugins = {}; + } + + /** + * Initialize the plugins. + * + * @protected + * @param {object} staticMap - The dictionary of staticly saved plugins. + */ + initPlugins(staticMap) + { + 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() + { + return this.view.width; + } + + /** + * Same as view.height, actual number of pixels in the canvas by vertical + * + * @member {number} + * @readonly + * @default 600 + */ + get height() + { + return this.view.height; + } + + /** + * Resizes the screen and canvas to the specified width and height + * Canvas dimensions are multiplied by resolution + * + * @param {number} screenWidth - the new width of the screen + * @param {number} screenHeight - the new height of the screen + */ + resize(screenWidth, screenHeight) + { + this.screen.width = screenWidth; + this.screen.height = screenHeight; + + this.view.width = screenWidth * this.resolution; + this.view.height = screenHeight * this.resolution; + + if (this.autoResize) + { + this.view.style.width = `${screenWidth}px`; + this.view.style.height = `${screenHeight}px`; + } + } + + /** + * 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 {PIXI.DisplayObject} displayObject - The displayObject the object will be generated from + * @param {number} scaleMode - Should be one of the scaleMode consts + * @param {number} resolution - The resolution / device pixel ratio of the texture being generated + * @param {PIXI.Rectangle} [region] - The region of the displayObject, that shall be rendered, + * if no region is specified, defaults to the local bounds of the displayObject. + * @return {PIXI.Texture} a texture of the graphics object + */ + generateTexture(displayObject, scaleMode, resolution, region) + { + region = region || displayObject.getLocalBounds(); + + // 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(region.width | 0, region.height | 0, scaleMode, resolution); + + tempMatrix.tx = -region.x; + tempMatrix.ty = -region.y; + + this.render(displayObject, renderTexture, false, tempMatrix, true); + + return renderTexture; + } + + /** + * Removes everything from the renderer and optionally removes the Canvas DOM element. + * + * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. + */ + destroy(removeView) + { + 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); + } + + this.plugins = null; + + this.type = RENDERER_TYPE.UNKNOWN; + + this.view = null; + + this.screen = null; + + this.resolution = 0; + + this.transparent = false; + + this.autoResize = false; + + this.blendModes = null; + + this.options = null; + + this.preserveDrawingBuffer = false; + this.clearBeforeRender = false; + + this.roundPixels = false; + + this._backgroundColor = 0; + this._backgroundColorRgba = null; + this._backgroundColorString = null; + + this._tempDisplayObjectParent = null; + this._lastObjectRendered = null; + } + + /** + * The background color to fill if not transparent + * + * @member {number} + */ + get backgroundColor() + { + return this._backgroundColor; + } + + set backgroundColor(value) // eslint-disable-line require-jsdoc + { + this._backgroundColor = value; + this._backgroundColorString = hex2string(value); + hex2rgb(value, this._backgroundColorRgba); + } +} diff --git a/packages/core/src/Renderer.js b/packages/core/src/Renderer.js new file mode 100644 index 0000000..2227e78 --- /dev/null +++ b/packages/core/src/Renderer.js @@ -0,0 +1,338 @@ +import AbstractRenderer from './AbstractRenderer'; +import { sayHello } from '@pixi/utils'; +import MaskSystem from './mask/MaskSystem'; +import StencilSystem from './mask/StencilSystem'; +import FilterSystem from './filters/FilterSystem'; +import FramebufferSystem from './framebuffer/FramebufferSystem'; +import RenderTextureSystem from './renderTexture/RenderTextureSystem'; +import TextureSystem from './textures/TextureSystem'; +import ProjectionSystem from './projection/ProjectionSystem'; +import StateSystem from './state/StateSystem'; +import GeometrySystem from './geometry/GeometrySystem'; +import ShaderSystem from './shader/ShaderSystem'; +import ContextSystem from './context/ContextSystem'; +import BatchSystem from './batch/BatchSystem'; +import TextureGCSystem from './textures/TextureGCSystem'; +import { RENDERER_TYPE } from '@pixi/constants'; +import UniformGroup from './shader/UniformGroup'; +import { Matrix } from '@pixi/math'; +import Runner from 'mini-runner'; + +/** + * 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 Render works by automatically managing webGLBatchs. + * 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 :) + * + * @class + * @memberof PIXI + * @extends PIXI.AbstractRenderer + */ +export default class Renderer extends AbstractRenderer +{ + // eslint-disable-next-line valid-jsdoc + /** + * + * @param {object} [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.transparent=false] - If the render view is transparent, default false + * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false + * @param {boolean} [options.antialias=false] - sets antialias. If not available natively then FXAA + * antialiasing is used + * @param {boolean} [options.forceFXAA=false] - forces FXAA antialiasing to be used over native. + * FXAA is faster, but may not always look as great + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. + * The resolution of the renderer retina would be 2. + * @param {boolean} [options.clearBeforeRender=true] - This sets if the renderer will clear + * the canvas or not before the new render pass. If you wish to set this to false, you *must* set + * preserveDrawingBuffer to `true`. + * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, + * enable this if you need to call toDataUrl on the webgl context. + * @param {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when + * rendering, stopping pixel interpolation. + * @param {number} [options.backgroundColor=0x000000] - The background color of the rendered area + * (shown if not transparent). + * @param {boolean} [options.legacy=false] - If true PixiJS will aim to ensure compatibility + * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. + * @param {string} [options.powerPreference] - Parameter passed to webgl context, set to "high-performance" + * for devices with dual graphics card + */ + constructor(options, arg2, arg3) + { + super('WebGL', options, arg2, arg3); + + /** + * The type of this renderer as a standardised const + * + * @member {number} + * @see PIXI.RENDERER_TYPE + */ + this.type = RENDERER_TYPE.WEBGL; + + // this will be set by the contextSystem (this.context) + this.gl = null; + this.CONTEXT_UID = 0; + this.legacy = !!options.legacy; + + // TODO legacy! + + // runners! + this.runners = { + destroy: new Runner('destroy'), + contextChange: new Runner('contextChange', 1), + reset: new Runner('reset'), + update: new Runner('update'), + postrender: new Runner('postrender'), + prerender: new Runner('prerender'), + resize: new Runner('resize', 2), + }; + + 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(GeometrySystem, 'geometry') + .addSystem(FramebufferSystem, 'framebuffer') + .addSystem(StencilSystem, 'stencil') + .addSystem(ProjectionSystem, 'projection') + .addSystem(TextureGCSystem, 'textureGC') + .addSystem(FilterSystem, 'filter') + .addSystem(RenderTextureSystem, 'renderTexture') + .addSystem(BatchSystem, 'batch'); + + this.initPlugins(Renderer.__plugins); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ + if (options.context) + { + this.context.initFromContext(options.context); + } + else + { + this.context.initFromOptions({ + alpha: this.transparent, + antialias: options.antialias, + premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', + stencil: true, + 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); + } + + /** + * Add a new system to the renderer. + * @param {class} ClassRef - Class reference + * @param {string} [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. + * @return {PIXI.Renderer} Return instance of renderer + */ + addSystem(ClassRef, name) + { + if (!name) + { + name = ClassRef.name; + } + + // TODO - read name from class.name.. + + /* + if(name.includes('System')) + { + name = name.replace('System', ''); + name = name.charAt(0).toLowerCase() + name.slice(1); + } + */ + + const system = new ClassRef(this); + + if (this[name]) + { + throw new Error(`Whoops! ${name} is already a manger`); + } + + this[name] = system; + + for (const i in this.runners) + { + this.runners[i].add(system); + } + + return this; + + /** + * 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. + */ + } + + /** + * Renders the object to its webGL view + * + * @param {PIXI.DisplayObject} displayObject - the object to be rendered + * @param {PIXI.RenderTexture} renderTexture - The render texture to render to. + * @param {boolean} [clear] - Should the canvas be cleared before the new render + * @param {PIXI.Transform} [transform] - A transform to apply to the render texture before rendering. + * @param {boolean} [skipUpdateTransform] - Should we skip the update transform pass? + */ + render(displayObject, renderTexture, clear, transform, skipUpdateTransform) + { + // can be handy to know! + this.renderingToScreen = !renderTexture; + + this.runners.prerender.run(); + this.emit('prerender'); + + // 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.parent; + + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = 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.run(); + + this.emit('postrender'); + } + + /** + * Resizes the webGL view to the specified width and height. + * + * @param {number} screenWidth - the new width of the screen + * @param {number} screenHeight - the new height of the screen + */ + resize(screenWidth, screenHeight) + { + AbstractRenderer.prototype.resize.call(this, screenWidth, screenHeight); + + this.runners.resize.run(screenWidth, screenHeight); + } + + /** + * Resets the WebGL state so you can render things however you fancy! + * + * @return {PIXI.Renderer} Returns itself. + */ + reset() + { + this.runners.reset.run(); + + return this; + } + + clear() + { + this.framebuffer.bind(); + this.framebuffer.clear(); + } + + /** + * Removes everything from the renderer (event listeners, spritebatch, etc...) + * + * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. + * See: https://github.com/pixijs/pixi.js/issues/2233 + */ + destroy(removeView) + { + this.runners.destroy.run(); + + // call base destroy + super.destroy(removeView); + + // TODO nullify all the managers.. + this.gl = null; + } + + /** + * 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 + * builds and excluding plugins. + * @name PIXI.Renderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. + */ + + /** + * Adds a plugin to the renderer. + * + * @method + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + static registerPlugin(pluginName, ctor) + { + Renderer.__plugins = Renderer.__plugins || {}; + Renderer.__plugins[pluginName] = ctor; + } +} diff --git a/packages/core/src/System.js b/packages/core/src/System.js new file mode 100644 index 0000000..05a10c8 --- /dev/null +++ b/packages/core/src/System.js @@ -0,0 +1,42 @@ +/** + * System is a base class used for extending systems used by the {@link PIXI.Renderer} + * @see PIXI.Renderer#addSystem + * @class + * @memberof PIXI + */ +export default class System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + /** + * The renderer this manager works for. + * + * @member {PIXI.Renderer} + */ + this.renderer = renderer; + + this.renderer.runners.contextChange.add(this); + } + + /** + * Generic method called when there is a WebGL context change. + * + */ + contextChange() + { + // do some codes init! + } + + /** + * Generic destroy methods to be overridden by the subclass + * + */ + destroy() + { + this.renderer.runners.contextChange.remove(this); + this.renderer = null; + } +} diff --git a/packages/core/src/batch/BatchSystem.js b/packages/core/src/batch/BatchSystem.js new file mode 100644 index 0000000..b04c249 --- /dev/null +++ b/packages/core/src/batch/BatchSystem.js @@ -0,0 +1,68 @@ +import System from '../System'; +import ObjectRenderer from './ObjectRenderer'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class BatchSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + super(renderer); + + /** + * An empty renderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.emptyRenderer = new ObjectRenderer(renderer); + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.currentRenderer = this.emptyRenderer; + } + + /** + * Changes the current renderer to the one given in parameter + * + * @param {PIXI.ObjectRenderer} objectRenderer - The object renderer to use. + */ + setObjectRenderer(objectRenderer) + { + if (this.currentRenderer === objectRenderer) + { + return; + } + + this.currentRenderer.stop(); + this.currentRenderer = objectRenderer; + + this.renderer.state.setState(objectRenderer.state); + + this.currentRenderer.start(); + } + + /** + * This should be called if you wish to do some custom rendering + * It will basically render anything that may be batched up such as sprites + * + */ + flush() + { + this.setObjectRenderer(this.emptyRenderer); + } + + reset() + { + this.setObjectRenderer(this.emptyRenderer); + } +} diff --git a/packages/core/src/batch/ObjectRenderer.js b/packages/core/src/batch/ObjectRenderer.js new file mode 100644 index 0000000..0c851a9 --- /dev/null +++ b/packages/core/src/batch/ObjectRenderer.js @@ -0,0 +1,48 @@ +import System from '../System'; + +/** + * Base for a common object renderer that can be used as a system renderer plugin. + * + * @class + * @extends PIXI.System + * @memberof PIXI + */ +export default class ObjectRenderer extends System +{ + /** + * Starts the renderer and sets the shader + * + */ + start() + { + // set the shader.. + } + + /** + * Stops the renderer + * + */ + stop() + { + this.flush(); + } + + /** + * Stub method for rendering content and emptying the current batch. + * + */ + flush() + { + // flush! + } + + /** + * Renders an object + * + * @param {PIXI.DisplayObject} object - The object to render. + */ + render(object) // eslint-disable-line no-unused-vars + { + // render the object + } +} diff --git a/packages/core/src/context/ContextSystem.js b/packages/core/src/context/ContextSystem.js new file mode 100644 index 0000000..301aab4 --- /dev/null +++ b/packages/core/src/context/ContextSystem.js @@ -0,0 +1,186 @@ +import System from '../System'; +import { settings } from '@pixi/settings'; + +let CONTEXT_UID = 0; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class ContextSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.webGLVersion = 1; + + this.handleContextLost = this.handleContextLost.bind(this); + this.handleContextRestored = this.handleContextRestored.bind(this); + + this.extensions = {}; + + renderer.view.addEventListener('webglcontextlost', this.handleContextLost, false); + renderer.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + } + + get isLost() + { + return (!this.gl || this.gl.isContextLost()); + } + + contextChange(gl) + { + this.gl = gl; + + // restore a context if it was previously lost + if (gl.isContextLost() && gl.getExtension('WEBGL_lose_context')) + { + gl.getExtension('WEBGL_lose_context').restoreContext(); + } + } + + initFromContext(gl) + { + this.gl = gl; + this.validateContext(gl); + this.renderer.gl = gl; + this.renderer.CONTEXT_UID = CONTEXT_UID++; + this.renderer.runners.contextChange.run(gl); + } + + initFromOptions(options) + { + const gl = this.createContext(this.renderer.view, options); + + this.initFromContext(gl); + } + + /** + * Helper class to create a webGL Context + * + * @class + * @memberof PIXI.glCore + * @param canvas {HTMLCanvasElement} the canvas element that we will get the context from + * @param options {Object} An options object that gets passed in to the canvas element containing the context attributes, + * see https://developer.mozilla.org/en/docs/Web/API/HTMLCanvasElement/getContext for the options available + * @return {WebGLRenderingContext} the WebGL context + */ + createContext(canvas, options) + { + let gl; + + if (settings.PREFER_WEBGL_2) + { + gl = canvas.getContext('webgl2', options); + } + + if (gl) + { + this.webGLVersion = 2; + } + else + { + this.webGLVersion = 1; + + gl = canvas.getContext('webgl', options) + || canvas.getContext('experimental-webgl', options); + + if (!gl) + { + // fail, not able to get a context + throw new Error('This browser does not support webGL. Try using the canvas renderer'); + } + } + + this.gl = gl; + + this.getExtensions(); + + return gl; + } + + getExtensions() + { + // time to set up default etensions that pixi uses.. + const gl = this.gl; + const extensions = this.extensions; + + if (this.webGLVersion === 1) + { + extensions.drawBuffers = gl.getExtension('WEBGL_draw_buffers'); + extensions.depthTexture = gl.getExtension('WEBKIT_WEBGL_depth_texture'); + extensions.floatTexture = gl.getExtension('OES_texture_float'); + extensions.loseContext = gl.getExtension('WEBGL_lose_context'); + + extensions.vertexArrayObject = gl.getExtension('OES_vertex_array_object') + || gl.getExtension('MOZ_OES_vertex_array_object') + || gl.getExtension('WEBKIT_OES_vertex_array_object'); + } + + // we don't use any specific WebGL 2 ones yet! + } + + /** + * Handles a lost webgl context + * + * @private + * @param {WebGLContextEvent} event - The context lost event. + */ + handleContextLost(event) + { + event.preventDefault(); + } + + /** + * Handles a restored webgl context + * + * @private + */ + handleContextRestored() + { + this.renderer.runners.contextChange.run(this.gl); + } + + destroy() + { + const view = this.renderer.view; + + // remove listeners + view.removeEventListener('webglcontextlost', this.handleContextLost); + view.removeEventListener('webglcontextrestored', this.handleContextRestored); + + this.gl.useProgram(null); + + if (this.extensions.loseContext) + { + this.extensions.loseContext.loseContext(); + } + } + + postrender() + { + this.gl.flush(); + } + + validateContext(gl) + { + const attributes = gl.getContextAttributes(); + + // this is going to be fairly simple for now.. but at least we have room to grow! + if (!attributes.stencil) + { + /* eslint-disable max-len */ + + /* eslint-disable no-console */ + console.warn('Provided WebGL context does not have a stencil buffer, masks may not render correctly'); + /* eslint-enable no-console */ + + /* eslint-enable max-len */ + } + } +} diff --git a/packages/core/src/filters/Filter.js b/packages/core/src/filters/Filter.js new file mode 100644 index 0000000..9d8cad8 --- /dev/null +++ b/packages/core/src/filters/Filter.js @@ -0,0 +1,140 @@ +import Shader from '../shader/Shader'; +import Program from '../shader/Program'; +import State from '../state/State'; +import { settings } from '@pixi/settings'; +// import extractUniformsFromSrc from './extractUniformsFromSrc'; +import defaultVertex from './defaultFilter.vert'; +import defaultFragment from './defaultFilter.frag'; + +// let math = require('../../math'); +/** + * @class + * @memberof PIXI + * @extends PIXI.Shader + */ +export default class Filter extends Shader +{ + /** + * @param {string} [vertexSrc] - The source of the vertex shader. + * @param {string} [fragmentSrc] - The source of the fragment shader. + * @param {object} [uniforms] - Custom uniforms to use to augment the built-in ones. + */ + constructor(vertexSrc, fragmentSrc, uniforms) + { + const program = Program.from(vertexSrc, fragmentSrc); + + super(program, uniforms); + + /** + * The padding of the filter. Some filters require extra space to breath such as a blur. + * Increasing this will add extra width and height to the bounds of the object that the + * filter is applied to. + * + * @member {number} + */ + this.padding = 0; + + /** + * The resolution of the filter. Setting this to be lower will lower the quality but + * increase the performance of the filter. + * + * @member {number} + */ + this.resolution = settings.RESOLUTION; + + /** + * If enabled is true the filter is applied, if false it will not. + * + * @member {boolean} + */ + this.enabled = true; + + /** + * If enabled, PixiJS will fit the filter area into boundaries for better performance. + * Switch it off if it does not work for specific shader. + * + * @member {boolean} + */ + this.autoFit = true; + + /** + * Legacy filters use position and uvs from attributes + * @member {boolean} + * @readonly + */ + this.legacy = !!this.program.attributeData.aTextureCoord; + + /** + * the webGL state the filter requires to render + * @member {PIXI.State} + */ + this.state = new State(); + } + + /** + * Applies the filter + * + * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + * @param {boolean} clear - Should the output be cleared before rendering to it + * @param {object} [currentState] - It's current state of filter. + * There are some useful properties in the currentState : + * target, filters, sourceFrame, destinationFrame, renderTarget, resolution + */ + apply(filterManager, input, output, clear, currentState, derp) // eslint-disable-line no-unused-vars + { + // do as you please! + + filterManager.applyFilter(this, input, output, clear, currentState, derp); + + // or just do a regular render.. + } + + /** + * Sets the blendmode of the filter + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL + */ + get blendMode() + { + return this.state.blendMode; + } + + set blendMode(value) // eslint-disable-line require-jsdoc + { + this.state.blendMode = value; + } + + /** + * The default vertex shader source + * + * @static + * @constant + */ + static get defaultVertexSrc() + { + return defaultVertex; + } + + /** + * The default fragment shader source + * + * @static + * @constant + */ + static get defaultFragmentSrc() + { + return defaultFragment; + } +} + +/** + * Used for caching shader IDs + * + * @static + * @private + */ +Filter.SOURCE_KEY_MAP = {}; + diff --git a/packages/core/src/filters/FilterSystem.js b/packages/core/src/filters/FilterSystem.js new file mode 100644 index 0000000..3174318 --- /dev/null +++ b/packages/core/src/filters/FilterSystem.js @@ -0,0 +1,431 @@ +import System from '../System'; + +import RenderTexture from '../renderTexture/RenderTexture'; +import Quad from '../utils/Quad'; +import QuadUv from '../utils/QuadUv'; +import { Rectangle } from '@pixi/math'; +import * as filterTransforms from './filterTransforms'; +import bitTwiddle from 'bit-twiddle'; +import UniformGroup from '../shader/UniformGroup'; +import { DRAW_MODES } from '@pixi/constants'; + +// +/** + * @ignore + * @class + */ +class FilterState +{ + /** + * + */ + constructor() + { + this.renderTexture = null; + this.sourceFrame = new Rectangle(); + this.destinationFrame = new Rectangle(); + this.filters = []; + this.target = null; + this.legacy = false; + this.resolution = 1; + } +} + +/** + * @class + * @memberof PIXI.systems + * @extends PIXI.System + */ +export default class FilterSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + /** + * stores a bunch of PO2 textures used for filtering + * @type {Object} + */ + this.texturePool = {}; + + /** + * a pool for storing filter states, save us creating new ones each tick + * @type {Array} + */ + this.statePool = []; + + /** + * A very simple geometry used when drawing a filter effect to the screen + * @type {Quad} + */ + this.quad = new Quad(); + + this.quadUv = new QuadUv(); + + /** + * Temporary rect for maths + * @type {PIXI.Rectangle} + */ + this.tempRect = new Rectangle(); + + this.activeState = {}; + + /** + * this uniform group is attached to filter uniforms when used + * @type {UniformGroup} + */ + this.globalUniforms = new UniformGroup({ + sourceFrame: this.tempRect, + destinationFrame: this.tempRect, + + // legacy variables + filterArea: new Float32Array(4), + filterClamp: new Float32Array(4), + }, true); + } + + /** + * Adds a new filter to the System. + * + * @param {PIXI.DisplayObject} target - The target of the filter to render. + * @param {PIXI.Filter[]} filters - The filters to apply. + */ + push(target, filters) + { + const renderer = this.renderer; + const filterStack = this.renderer.renderTexture.defaultFilterStack; + const state = this.statePool.pop() || new FilterState(); + + let resolution = filters[0].resolution; + let padding = filters[0].padding; + let autoFit = filters[0].autoFit; + let legacy = filters[0].legacy; + + for (let i = 1; i < filters.length; i++) + { + const filter = filters[i]; + + // lets use the lowest resolution.. + resolution = Math.min(resolution, filter.resolution); + // and the largest amount of padding! + padding = Math.max(padding, filter.padding); + // only auto fit if all filters are autofit + autoFit = autoFit || filter.autoFit; + + legacy = legacy || filter.legacy; + } + + filterStack.push(state); + + state.resolution = resolution; + + state.legacy = legacy; + + // round to whole number based on resolution + // TODO move that to the shader too? + state.sourceFrame = target.filterArea || target.getBounds(true); + + state.sourceFrame.pad(padding); + + if (autoFit) + { + state.sourceFrame.fit(this.renderer.renderTexture.destinationFrame); + } + + state.sourceFrame.round(resolution); + + state.renderTexture = this.getPotFilterTexture(state.sourceFrame.width, state.sourceFrame.height, resolution); + state.filters = filters; + + state.destinationFrame.width = state.renderTexture.width; + state.destinationFrame.height = state.renderTexture.height; + + state.renderTexture.filterFrame = state.sourceFrame; + renderer.renderTexture.bind(state.renderTexture, state.sourceFrame);// /, state.destinationFrame); + renderer.renderTexture.clear(); + } + + /** + * Pops off the filter and applies it. + * + */ + pop() + { + const renderer = this.renderer; + const filterStack = renderer.renderTexture.defaultFilterStack; + const state = filterStack.pop(); + const filters = state.filters; + + this.activeState = state; + + const globalUniforms = this.globalUniforms.uniforms; + + globalUniforms.sourceFrame = state.sourceFrame; + globalUniforms.destinationFrame = state.destinationFrame; + globalUniforms.resolution = state.resolution; + + // only update the rect if its legacy.. + if (state.legacy) + { + const filterArea = globalUniforms.filterArea; + const filterClamp = globalUniforms.filterClamp; + + filterArea[0] = state.destinationFrame.width; + filterArea[1] = state.destinationFrame.height; + filterArea[2] = state.sourceFrame.x; + filterArea[3] = state.sourceFrame.y; + + filterClamp[0] = 0.5 / state.resolution / state.destinationFrame.width; + filterClamp[1] = 0.5 / state.resolution / state.destinationFrame.height; + filterClamp[2] = (state.sourceFrame.width - 0.5) / state.resolution / state.destinationFrame.width; + filterClamp[3] = (state.sourceFrame.height - 0.5) / state.resolution / state.destinationFrame.height; + } + + this.globalUniforms.update(); + + const lastState = filterStack[filterStack.length - 1]; + + if (filters.length === 1) + { + filters[0].apply(this, state.renderTexture, lastState.renderTexture, false, state); + + this.returnFilterTexture(state.renderTexture); + } + else + { + let flip = state.renderTexture; + let flop = this.getPotFilterTexture( + flip.width, + flip.height, + state.resolution + ); + + flop.filterFrame = flip.filterFrame; + + let i = 0; + + for (i = 0; i < filters.length - 1; ++i) + { + filters[i].apply(this, flip, flop, true, state); + + const t = flip; + + flip = flop; + flop = t; + } + + filters[i].apply(this, flip, lastState.renderTexture, false, state); + + this.returnFilterTexture(flip); + this.returnFilterTexture(flop); + } + + this.statePool.push(state); + } + + /** + * Draws a filter. + * + * @param {PIXI.Filter} filter - The filter to draw. + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + * @param {boolean} clear - Should the output be cleared before rendering to it + */ + applyFilter(filter, input, output, clear) + { + const renderer = this.renderer; + + renderer.renderTexture.bind(output, output ? output.filterFrame : null); + + if (clear) + { + // gl.disable(gl.SCISSOR_TEST); + renderer.renderTexture.clear(); + // gl.enable(gl.SCISSOR_TEST); + } + + // set the uniforms.. + filter.uniforms.uSampler = input; + filter.uniforms.filterGlobals = this.globalUniforms; + + // TODO make it so that the order of this does not matter.. + // because it does at the moment cos of global uniforms. + // they need to get resynced + + renderer.state.setState(filter.state); + renderer.shader.bind(filter); + + if (filter.legacy) + { + this.quadUv.map(input._frame, input.filterFrame); + + renderer.geometry.bind(this.quadUv); + renderer.geometry.draw(DRAW_MODES.TRIANGLES); + } + else + { + renderer.geometry.bind(this.quad); + renderer.geometry.draw(DRAW_MODES.TRIANGLE_STRIP); + } + } + + /** + * Calculates the mapped matrix. + * + * TODO playing around here.. this is temporary - (will end up in the shader) + * this returns a matrix that will normalise map filter cords in the filter to screen space + * + * @param {PIXI.Matrix} outputMatrix - the matrix to output to. + * @return {PIXI.Matrix} The mapped matrix. + */ + calculateScreenSpaceMatrix(outputMatrix) + { + const currentState = this.activeState; + + return filterTransforms.calculateScreenSpaceMatrix( + outputMatrix, + currentState.sourceFrame, + currentState.destinationFrame + ); + } + + /** + * This will map the filter coord so that a texture can be used based on the transform of a sprite + * + * @param {PIXI.Matrix} outputMatrix - The matrix to output to. + * @param {PIXI.Sprite} sprite - The sprite to map to. + * @return {PIXI.Matrix} The mapped matrix. + */ + calculateSpriteMatrix(outputMatrix, sprite) + { + const currentState = this.activeState; + + return filterTransforms.calculateSpriteMatrix( + outputMatrix, + currentState.sourceFrame, + currentState.destinationFrame, + sprite + ); + } + + /** + * Destroys this Filter System. + * + * @param {boolean} [contextLost=false] context was lost, do not free shaders + * + */ + destroy(contextLost = false) + { + if (!contextLost) + { + this.emptyPool(); + } + else + { + this.texturePool = {}; + } + } + + /** + * Gets a Power-of-Two render texture. + * + * TODO move to a seperate class could be on renderer? + * also - could cause issue with multiple contexts? + * + * @private + * @param {WebGLRenderingContext} gl - The webgl rendering context + * @param {number} minWidth - The minimum width of the render target. + * @param {number} minHeight - The minimum height of the render target. + * @param {number} resolution - The resolution of the render target. + * @return {PIXI.RenderTarget} The new render target. + */ + getPotFilterTexture(minWidth, minHeight, resolution) + { + minWidth = bitTwiddle.nextPow2(minWidth); + minHeight = bitTwiddle.nextPow2(minHeight); + resolution = resolution || 1; + + const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + + if (!this.texturePool[key]) + { + this.texturePool[key] = []; + } + + let renderTexture = this.texturePool[key].pop(); + + if (!renderTexture) + { + // temporary bypass cache.. + // internally - this will cause a texture to be bound.. + renderTexture = RenderTexture.create({ + width: minWidth, + height: minHeight, + resolution, + }); + } + + return renderTexture; + } + + /** + * Gets extra render texture to use inside current filter + * + * @param {number} resolution resolution of the renderTexture + * @returns {PIXI.RenderTexture} + */ + getFilterTexture(resolution) + { + const rt = this.activeState.renderTexture; + + const filterTexture = this.getPotFilterTexture(rt.width, rt.height, resolution || rt.baseTexture.resolution); + + filterTexture.filterFrame = rt.filterFrame; + + return filterTexture; + } + + /** + * Frees a render texture back into the pool. + * + * @param {PIXI.RenderTarget} renderTexture - The renderTarget to free + */ + returnFilterTexture(renderTexture) + { + renderTexture.filterFrame = null; + + const base = renderTexture.baseTexture; + + const minWidth = base.width; + const minHeight = base.height; + + const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + + this.texturePool[key].push(renderTexture); + } + + /** + * Empties the texture pool. + * + */ + emptyPool() + { + for (const i in this.texturePool) + { + const textures = this.texturePool[i]; + + if (textures) + { + for (let j = 0; j < textures.length; j++) + { + textures[j].destroy(true); + } + } + } + + this.texturePool = {}; + } +} diff --git a/packages/core/src/filters/defaultFilter.frag b/packages/core/src/filters/defaultFilter.frag new file mode 100644 index 0000000..a14c1be --- /dev/null +++ b/packages/core/src/filters/defaultFilter.frag @@ -0,0 +1,22 @@ +varying vec2 vTextureCoord; +varying vec2 vFilterCoord; + +uniform sampler2D uSampler; +uniform sampler2D filterSampler; + +void main(void){ + vec4 masky = texture2D(filterSampler, vFilterCoord); + vec4 sample = texture2D(uSampler, vTextureCoord); + vec4 color; + if(mod(vFilterCoord.x, 1.0) > 0.5) + { + color = vec4(1.0, 0.0, 0.0, 1.0); + } + else + { + color = vec4(0.0, 1.0, 0.0, 1.0); + } + // gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0); + gl_FragColor = mix(sample, masky, 0.5); + gl_FragColor *= sample.a; +} diff --git a/packages/core/src/filters/defaultFilter.vert b/packages/core/src/filters/defaultFilter.vert new file mode 100644 index 0000000..e1febca --- /dev/null +++ b/packages/core/src/filters/defaultFilter.vert @@ -0,0 +1,14 @@ +attribute vec2 aVertexPosition; +attribute vec2 aTextureCoord; + +uniform mat3 projectionMatrix; +uniform mat3 filterMatrix; + +varying vec2 vTextureCoord; +varying vec2 vFilterCoord; + +void main(void){ + gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); + vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy; + vTextureCoord = aTextureCoord ; +} diff --git a/packages/core/src/filters/filterTransforms.js b/packages/core/src/filters/filterTransforms.js new file mode 100644 index 0000000..87a3cd6 --- /dev/null +++ b/packages/core/src/filters/filterTransforms.js @@ -0,0 +1,50 @@ +import { Matrix } from '@pixi/math'; + +/** + * Calculates the mapped matrix + * @param filterArea {Rectangle} The filter area + * @param sprite {Sprite} the target sprite + * @param outputMatrix {Matrix} @alvin + * @private + */ +// this returns a matrix that will normalise map filter cords in the filter to screen space +export function calculateScreenSpaceMatrix(outputMatrix, filterArea, textureSize) +{ + // TODO unwrap? + const mappedMatrix = outputMatrix.identity(); + + mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); + + mappedMatrix.scale(textureSize.width, textureSize.height); + + return mappedMatrix; +} + +export function calculateNormalizedScreenSpaceMatrix(outputMatrix, filterArea, textureSize) +{ + const mappedMatrix = outputMatrix.identity(); + + mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); + + const translateScaleX = (textureSize.width / filterArea.width); + const translateScaleY = (textureSize.height / filterArea.height); + + mappedMatrix.scale(translateScaleX, translateScaleY); + + return mappedMatrix; +} + +// this will map the filter coord so that a texture can be used based on the transform of a sprite +export function calculateSpriteMatrix(outputMatrix, filterArea, textureSize, sprite) +{ + const orig = sprite._texture.orig; + const mappedMatrix = outputMatrix.set(textureSize.width, 0, 0, textureSize.height, filterArea.x, filterArea.y); + const worldTransform = sprite.worldTransform.copyTo(Matrix.TEMP_MATRIX); + + worldTransform.invert(); + mappedMatrix.prepend(worldTransform); + mappedMatrix.scale(1.0 / orig.width, 1.0 / orig.height); + mappedMatrix.translate(sprite.anchor.x, sprite.anchor.y); + + return mappedMatrix; +} diff --git a/packages/core/src/filters/spriteMask/SpriteMaskFilter.js b/packages/core/src/filters/spriteMask/SpriteMaskFilter.js new file mode 100644 index 0000000..91697b7 --- /dev/null +++ b/packages/core/src/filters/spriteMask/SpriteMaskFilter.js @@ -0,0 +1,63 @@ +import Filter from '../Filter'; +import { Matrix } from '@pixi/math'; +import vertex from './spriteMaskFilter.vert'; +import fragment from './spriteMaskFilter.frag'; +import { default as TextureMatrix } from '../../textures/TextureMatrix'; + +/** + * The SpriteMaskFilter class + * + * @class + * @extends PIXI.Filter + * @memberof PIXI + */ +export default class SpriteMaskFilter extends Filter +{ + /** + * @param {PIXI.Sprite} sprite - the target sprite + */ + constructor(sprite) + { + const maskMatrix = new Matrix(); + + super(vertex, fragment); + + sprite.renderable = false; + + this.maskSprite = sprite; + this.maskMatrix = maskMatrix; + } + + /** + * Applies the filter + * + * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + */ + apply(filterManager, input, output) + { + const maskSprite = this.maskSprite; + const tex = this.maskSprite.texture; + + if (!tex.valid) + { + return; + } + if (!tex.transform) + { + // margin = 0.0, let it bleed a bit, shader code becomes easier + // assuming that atlas textures were made with 1-pixel padding + tex.transform = new TextureMatrix(tex, 0.0); + } + tex.transform.update(); + + this.uniforms.mask = tex; + this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite) + .prepend(tex.transform.mapCoord); + this.uniforms.alpha = maskSprite.worldAlpha; + this.uniforms.maskClamp = tex.transform.uClampFrame; + + filterManager.applyFilter(this, input, output); + } +} diff --git a/packages/core/src/filters/spriteMask/spriteMaskFilter.frag b/packages/core/src/filters/spriteMask/spriteMaskFilter.frag new file mode 100644 index 0000000..0e4aef8 --- /dev/null +++ b/packages/core/src/filters/spriteMask/spriteMaskFilter.frag @@ -0,0 +1,23 @@ +varying vec2 vMaskCoord; +varying vec2 vTextureCoord; + +uniform sampler2D uSampler; +uniform sampler2D mask; +uniform float alpha; +uniform vec4 maskClamp; + +void main(void) +{ + float clip = step(3.5, + step(maskClamp.x, vMaskCoord.x) + + step(maskClamp.y, vMaskCoord.y) + + step(vMaskCoord.x, maskClamp.z) + + step(vMaskCoord.y, maskClamp.w)); + + vec4 original = texture2D(uSampler, vTextureCoord); + vec4 masky = texture2D(mask, vMaskCoord); + + original *= (masky.r * masky.a * alpha * clip); + + gl_FragColor = original; +} diff --git a/packages/core/src/filters/spriteMask/spriteMaskFilter.vert b/packages/core/src/filters/spriteMask/spriteMaskFilter.vert new file mode 100644 index 0000000..d23a30c --- /dev/null +++ b/packages/core/src/filters/spriteMask/spriteMaskFilter.vert @@ -0,0 +1,16 @@ +attribute vec2 aVertexPosition; +attribute vec2 aTextureCoord; + +uniform mat3 projectionMatrix; +uniform mat3 otherMatrix; + +varying vec2 vMaskCoord; +varying vec2 vTextureCoord; + +void main(void) +{ + gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); + + vTextureCoord = aTextureCoord; + vMaskCoord = ( otherMatrix * vec3( aTextureCoord, 1.0) ).xy; +} diff --git a/packages/core/src/framebuffer/FrameBuffer.js b/packages/core/src/framebuffer/FrameBuffer.js new file mode 100644 index 0000000..84e9fcc --- /dev/null +++ b/packages/core/src/framebuffer/FrameBuffer.js @@ -0,0 +1,106 @@ +import Texture from '../textures/BaseTexture'; +import { FORMATS, TYPES } from '@pixi/constants'; + +/** + * Frame buffer + * @class + * @memberof PIXI + */ +export default class FrameBuffer +{ + constructor(width, height) + { + this.width = width || 100; + this.height = height || 100; + + this.stencil = false; + this.depth = false; + + this.dirtyId = 0; + this.dirtyFormat = 0; + this.dirtySize = 0; + + this.depthTexture = null; + this.colorTextures = []; + + this.glFrameBuffers = {}; + } + + get colorTexture() + { + return this.colorTextures[0]; + } + + addColorTexture(index, texture) + { + // TODO add some validation to the texture - same width / height etc? + this.colorTextures[index || 0] = texture || new Texture(null, { scaleMode: 0, + resolution: 1, + mipmap: false, + width: this.width, + height: this.height });// || new Texture(); + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + addDepthTexture(texture) + { + /* eslint-disable max-len */ + this.depthTexture = texture || new Texture(null, { scaleMode: 0, + resolution: 1, + width: this.width, + height: this.height, + mipmap: false, + format: FORMATS.DEPTH_COMPONENT, + type: TYPES.UNSIGNED_SHORT });// UNSIGNED_SHORT; + /* eslint-disable max-len */ + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + enableDepth() + { + this.depth = true; + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + enableStencil() + { + this.stencil = true; + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + resize(width, height) + { + if (width === this.width && height === this.height) return; + + this.width = width; + this.height = height; + + this.dirtyId++; + this.dirtySize++; + + for (let i = 0; i < this.colorTextures.length; i++) + { + this.colorTextures[i].setSize(width, height); + } + + if (this.depthTexture) + { + this.depthTexture.setSize(width, height); + } + } +} diff --git a/packages/core/src/framebuffer/FramebufferSystem.js b/packages/core/src/framebuffer/FramebufferSystem.js new file mode 100644 index 0000000..2cc2437 --- /dev/null +++ b/packages/core/src/framebuffer/FramebufferSystem.js @@ -0,0 +1,246 @@ +import System from '../System'; +import { Rectangle } from '@pixi/math'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class FramebufferSystem extends System +{ + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + contextChange() + { + this.gl = this.renderer.gl; + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + this.current = null; + this.viewport = new Rectangle(); + + this.drawBufferExtension = this.renderer.context.extensions.drawBuffers; + } + + bind(framebuffer, frame) + { + const gl = this.gl; + + this.current = framebuffer; + + if (framebuffer) + { + // TODO cacheing layer! + + const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID] || this.initFramebuffer(framebuffer); + + gl.bindFramebuffer(gl.FRAMEBUFFER, fbo.framebuffer); + // makesure all textures are unbound.. + + // now check for updates... + if (fbo.dirtyId !== framebuffer.dirtyId) + { + fbo.dirtyId = framebuffer.dirtyId; + + if (fbo.dirtyFormat !== framebuffer.dirtyFormat) + { + fbo.dirtyFormat = framebuffer.dirtyFormat; + this.updateFramebuffer(framebuffer); + } + else if (fbo.dirtySize !== framebuffer.dirtySize) + { + fbo.dirtySize = framebuffer.dirtySize; + this.resizeFramebuffer(framebuffer); + } + } + + for (let i = 0; i < framebuffer.colorTextures.length; i++) + { + if (framebuffer.colorTextures[i].texturePart) + { + this.renderer.texture.unbind(framebuffer.colorTextures[i].texture); + } + else + { + this.renderer.texture.unbind(framebuffer.colorTextures[i]); + } + } + + if (framebuffer.depthTexture) + { + this.renderer.texture.unbind(framebuffer.depthTexture); + } + + if (frame) + { + this.setViewport(frame.x, frame.y, frame.width, frame.height); + } + else + { + this.setViewport(0, 0, framebuffer.width, framebuffer.height); + } + } + else + { + gl.bindFramebuffer(gl.FRAMEBUFFER, null); + + if (frame) + { + this.setViewport(frame.x, frame.y, frame.width, frame.height); + } + else + { + this.setViewport(0, 0, this.renderer.width, this.renderer.height); + } + } + } + + setViewport(x, y, width, height) + { + const v = this.viewport; + + if (v.width !== width || v.height !== height || v.x !== x || v.y !== y) + { + v.x = x; + v.y = y; + v.width = width; + v.height = height; + + this.gl.viewport(x, y, width, height); + } + } + + get size() + { + if (this.current) + { + // TODO store temp + return { x: 0, y: 0, width: this.current.width, height: this.current.height }; + } + + return { x: 0, y: 0, width: this.renderer.width, height: this.renderer.height }; + } + + clear(r, g, b, a) + { + const gl = this.gl; + + // TODO clear color can be set only one right? + gl.clearColor(r, g, b, a); + gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); + } + + // private functions... + + initFramebuffer(framebuffer) + { + const gl = this.gl; + + // TODO - make this a class? + const fbo = { + framebuffer: gl.createFramebuffer(), + stencil: null, + dirtyId: 0, + dirtyFormat: 0, + dirtySize: 0, + }; + + framebuffer.glFrameBuffers[this.CONTEXT_UID] = fbo; + + return fbo; + } + + resizeFramebuffer(framebuffer) + { + const gl = this.gl; + + if (framebuffer.stencil || framebuffer.depth) + { + gl.bindRenderbuffer(gl.RENDERBUFFER, this.stencil); + gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); + } + } + + updateFramebuffer(framebuffer) + { + const gl = this.gl; + + const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID]; + + // bind the color texture + const colorTextures = framebuffer.colorTextures; + + let count = colorTextures.length; + + if (!this.drawBufferExtension) + { + count = Math.min(count, 1); + } + + const activeTextures = []; + + for (let i = 0; i < count; i++) + { + const texture = framebuffer.colorTextures[i]; + + if (texture.texturePart) + { + this.renderer.texture.bind(texture.texture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.COLOR_ATTACHMENT0 + i, + gl.TEXTURE_CUBE_MAP_NEGATIVE_X + texture.side, + texture.texture._glTextures[this.CONTEXT_UID].texture, + 0); + } + else + { + this.renderer.texture.bind(texture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.COLOR_ATTACHMENT0 + i, + gl.TEXTURE_2D, + texture._glTextures[this.CONTEXT_UID].texture, + 0); + } + + activeTextures.push(gl.COLOR_ATTACHMENT0 + i); + } + + if (this.drawBufferExtension && activeTextures.length > 1) + { + this.drawBufferExtension.drawBuffersWEBGL(activeTextures); + } + + if (framebuffer.depthTexture) + { + const depthTextureExt = this.renderer.context.extensions.depthTexture; + + if (depthTextureExt) + { + const depthTexture = framebuffer.depthTexture; + + this.renderer.texture.bind(depthTexture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.DEPTH_ATTACHMENT, + gl.TEXTURE_2D, + depthTexture._glTextures[this.CONTEXT_UID].texture, + 0); + } + } + + if (framebuffer.stencil || framebuffer.depth) + { + fbo.stencil = gl.createRenderbuffer(); + + gl.bindRenderbuffer(gl.RENDERBUFFER, fbo.stencil); + + // TODO.. this is depth AND stencil? + gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, fbo.stencil); + gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); + // fbo.enableStencil(); + } + } +} diff --git a/packages/core/src/geometry/GLBuffer.js b/packages/core/src/geometry/GLBuffer.js new file mode 100644 index 0000000..0477e8a --- /dev/null +++ b/packages/core/src/geometry/GLBuffer.js @@ -0,0 +1,9 @@ +export default class GLBuffer +{ + constructor(buffer) + { + this.buffer = buffer; + this.updateID = -1; + this.byteLength = -1; + } +} diff --git a/packages/core/src/geometry/GeometrySystem.js b/packages/core/src/geometry/GeometrySystem.js new file mode 100644 index 0000000..959f827 --- /dev/null +++ b/packages/core/src/geometry/GeometrySystem.js @@ -0,0 +1,388 @@ +import System from '../System'; +import GLBuffer from './GLBuffer'; + +const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class GeometrySystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this._activeGeometry = null; + this._activeVao = null; + + this.hasVao = true; + this.hasInstance = true; + } + + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + contextChange() + { + const gl = this.gl = this.renderer.gl; + + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + + // webgl2 + if (!gl.createVertexArray) + { + // webgl 1! + let nativeVaoExtension = this.renderer.context.extensions.vertexArrayObject; + + if (this.renderer.legacy) + { + nativeVaoExtension = null; + } + + if (nativeVaoExtension) + { + gl.createVertexArray = () => + nativeVaoExtension.createVertexArrayOES(); + + gl.bindVertexArray = (vao) => + nativeVaoExtension.bindVertexArrayOES(vao); + + gl.deleteVertexArray = (vao) => + nativeVaoExtension.deleteVertexArrayOES(vao); + } + else + { + this.hasVao = false; + gl.createVertexArray = () => + { + // empty + }; + + gl.bindVertexArray = () => + { + // empty + }; + + gl.deleteVertexArray = () => + { + // empty + }; + } + } + + if (!gl.vertexAttribDivisor) + { + const instanceExt = gl.getExtension('ANGLE_instanced_arrays'); + + if (instanceExt) + { + gl.vertexAttribDivisor = (a, b) => + instanceExt.vertexAttribDivisorANGLE(a, b); + + gl.drawElementsInstanced = (a, b, c, d, e) => + instanceExt.drawElementsInstancedANGLE(a, b, c, d, e); + + gl.drawArraysInstanced = (a, b, c, d) => + instanceExt.drawArraysInstancedANGLE(a, b, c, d); + } + else + { + this.hasInstance = false; + } + } + } + + /** + * Binds geometry so that is can be drawn. Creating a Vao if required + * @private + * @param {PIXI.Geometry} geometry instance of geometry to bind + */ + bind(geometry, shader) + { + shader = shader || this.renderer.shader.shader; + + const gl = this.gl; + + // not sure the best way to address this.. + // currently different shaders require different VAOs for the same geometry + // Still mulling over the best way to solve this one.. + // will likely need to modify the shader attribute locations at run time! + let vaos = geometry.glVertexArrayObjects[this.CONTEXT_UID]; + + if (!vaos) + { + geometry.glVertexArrayObjects[this.CONTEXT_UID] = vaos = {}; + } + + const vao = vaos[shader.program.id] || this.initGeometryVao(geometry, shader.program); + + this._activeGeometry = geometry; + + if (this._activeVao !== vao) + { + this._activeVao = vao; + + if (this.hasVao) + { + gl.bindVertexArray(vao); + } + else + { + this.activateVao(geometry, shader.program); + } + } + + // TODO - optimise later! + // don't need to loop through if nothing changed! + // maybe look to add an 'autoupdate' to geometry? + this.updateBuffers(); + } + + reset() + { + this.unbind(); + } + + updateBuffers() + { + const geometry = this._activeGeometry; + const gl = this.gl; + + for (let i = 0; i < geometry.buffers.length; i++) + { + const buffer = geometry.buffers[i]; + + const glBuffer = buffer._glBuffers[this.CONTEXT_UID]; + + if (buffer._updateID !== glBuffer.updateID) + { + glBuffer.updateID = buffer._updateID; + + // TODO can cache this on buffer! maybe added a getter / setter? + const type = buffer.index ? gl.ELEMENT_ARRAY_BUFFER : gl.ARRAY_BUFFER; + const drawType = buffer.static ? gl.STATIC_DRAW : gl.DYNAMIC_DRAW; + + gl.bindBuffer(type, glBuffer.buffer); + + if (glBuffer.byteLength >= buffer.data.byteLength) + { + // offset is always zero for now! + gl.bufferSubData(type, 0, buffer.data); + } + else + { + gl.bufferData(type, buffer.data, drawType); + } + } + } + } + + checkCompatability(geometry, program) + { + // geometry must have at least all the attributes that the shader requires. + const geometryAttributes = geometry.attributes; + const shaderAttributes = program.attributeData; + + for (const j in shaderAttributes) + { + if (!geometryAttributes[j]) + { + throw new Error(`shader and geometry incompatible, geometry missing the "${j}" attribute`); + } + } + } + + /** + * Creates a Vao with the same structure as the geometry and stores it on the geometry. + * @private + * @param {PIXI.Geometry} geometry instance of geometry to to generate Vao for + */ + initGeometryVao(geometry, program) + { + this.checkCompatability(geometry, program); + + const gl = this.gl; + const CONTEXT_UID = this.CONTEXT_UID; + const buffers = geometry.buffers; + const attributes = geometry.attributes; + + const tempStride = {}; + const tempStart = {}; + + for (const j in buffers) + { + tempStride[j] = 0; + tempStart[j] = 0; + } + + for (const j in attributes) + { + if (!attributes[j].size && program.attributeData[j]) + { + attributes[j].size = program.attributeData[j].size; + } + + tempStride[attributes[j].buffer] += attributes[j].size * byteSizeMap[attributes[j].type]; + } + + for (const j in attributes) + { + const attribute = attributes[j]; + const attribSize = attribute.size; + + if (attribute.stride === undefined) + { + if (tempStride[attribute.buffer] === attribSize * byteSizeMap[attribute.type]) + { + attribute.stride = 0; + } + else + { + attribute.stride = tempStride[attribute.buffer]; + } + } + + if (attribute.start === undefined) + { + attribute.start = tempStart[attribute.buffer]; + + tempStart[attribute.buffer] += attribSize * byteSizeMap[attribute.type]; + } + } + + // first update - and create the buffers! + // only create a gl buffer if it actually gets + for (let i = 0; i < buffers.length; i++) + { + const buffer = buffers[i]; + + if (!buffer._glBuffers[CONTEXT_UID]) + { + buffer._glBuffers[CONTEXT_UID] = new GLBuffer(gl.createBuffer()); + } + } + + // TODO - maybe make this a data object? + // lets wait to see if we need to first! + const vao = gl.createVertexArray(); + + gl.bindVertexArray(vao); + + this.activateVao(geometry, program); + + gl.bindVertexArray(null); + + // add it to the cache! + geometry.glVertexArrayObjects[this.CONTEXT_UID][program.id] = vao; + + return vao; + } + + activateVao(geometry, program) + { + const gl = this.gl; + const CONTEXT_UID = this.CONTEXT_UID; + const buffers = geometry.buffers; + const attributes = geometry.attributes; + + if (geometry.indexBuffer) + { + // first update the index buffer if we have one.. + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, geometry.indexBuffer._glBuffers[CONTEXT_UID].buffer); + } + + let lastBuffer = null; + + // add a new one! + for (const j in attributes) + { + const attribute = attributes[j]; + const buffer = buffers[attribute.buffer]; + const glBuffer = buffer._glBuffers[CONTEXT_UID]; + + if (program.attributeData[j]) + { + if (lastBuffer !== glBuffer) + { + gl.bindBuffer(gl.ARRAY_BUFFER, glBuffer.buffer); + + lastBuffer = glBuffer; + } + + const location = program.attributeData[j].location; + + // TODO introduce state again + // we can optimise this for older devices that have no VAOs + gl.enableVertexAttribArray(location); + + gl.vertexAttribPointer(location, + attribute.size, + attribute.type || gl.FLOAT, + attribute.normalized, + attribute.stride, + attribute.start); + + if (attribute.instance) + { + // TODO calculate instance count based of this... + if (this.hasInstance) + { + gl.vertexAttribDivisor(location, 1); + } + else + { + throw new Error('geometry error, GPU Instancing is not supported on this device'); + } + } + } + } + } + + draw(type, size, start, instanceCount) + { + const gl = this.gl; + const geometry = this._activeGeometry; + + // TODO.. this should not change so maybe cache the function? + + if (geometry.indexBuffer) + { + if (geometry.instanced) + { + /* eslint-disable max-len */ + gl.drawElementsInstanced(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2, instanceCount || 1); + /* eslint-enable max-len */ + } + else + { + gl.drawElements(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2); + } + } + else + if (geometry.instanced) + { + // TODO need a better way to calculate size.. + gl.drawArraysInstanced(type, start, size || geometry.getSize(), instanceCount || 1); + } + else + { + gl.drawArrays(type, start, size || geometry.getSize()); + } + + return this; + } + + unbind() + { + this.gl.bindVertexArray(null); + this._activeVao = null; + this._activeGeometry = null; + } +} diff --git a/packages/core/src/geometry/utils/setVertexAttribArrays.js b/packages/core/src/geometry/utils/setVertexAttribArrays.js new file mode 100644 index 0000000..23b6959 --- /dev/null +++ b/packages/core/src/geometry/utils/setVertexAttribArrays.js @@ -0,0 +1,54 @@ +// var GL_MAP = {}; + +/** + * @param gl {WebGLRenderingContext} The current WebGL context + * @param attribs {*} + * @param state {*} + */ +export default function setVertexAttribArrays(gl, attribs, state) +{ + let i; + + if (state) + { + const tempAttribState = state.tempAttribState; + const attribState = state.attribState; + + for (i = 0; i < tempAttribState.length; i++) + { + tempAttribState[i] = false; + } + + // set the new attribs + for (i = 0; i < attribs.length; i++) + { + tempAttribState[attribs[i].attribute.location] = true; + } + + for (i = 0; i < attribState.length; i++) + { + if (attribState[i] !== tempAttribState[i]) + { + attribState[i] = tempAttribState[i]; + + if (state.attribState[i]) + { + gl.enableVertexAttribArray(i); + } + else + { + gl.disableVertexAttribArray(i); + } + } + } + } + else + { + for (i = 0; i < attribs.length; i++) + { + const attrib = attribs[i]; + + gl.enableVertexAttribArray(attrib.attribute.location); + } + } +} diff --git a/packages/core/src/index.js b/packages/core/src/index.js index 58c456a..fd1f547 100644 --- a/packages/core/src/index.js +++ b/packages/core/src/index.js @@ -1,30 +1,30 @@ import * as resources from './textures/resources'; -import * as systems from './renderers/systems'; +import * as systems from './systems'; -export { resources }; export { systems }; +export { resources }; -export { default as Renderer } from './renderers/Renderer'; -export { default as AbstractRenderer } from './renderers/AbstractRenderer'; -export { default as FrameBuffer } from './textures/FrameBuffer'; +export { default as System } from './System'; +export { default as Renderer } from './Renderer'; +export { default as AbstractRenderer } from './AbstractRenderer'; +export { default as FrameBuffer } from './framebuffer/FrameBuffer'; export { default as CubeTexture } from './textures/CubeTexture'; export { default as BaseTexture } from './textures/BaseTexture'; export { default as Texture } from './textures/Texture'; export { default as TextureMatrix } from './textures/TextureMatrix'; -export { default as RenderTexture } from './textures/RenderTexture'; -export { default as BaseRenderTexture } from './textures/BaseRenderTexture'; +export { default as RenderTexture } from './renderTexture/RenderTexture'; +export { default as BaseRenderTexture } from './renderTexture/BaseRenderTexture'; export { default as TextureUvs } from './textures/TextureUvs'; -export { default as State } from './renderers/State'; -export { default as ObjectRenderer } from './renderers/utils/ObjectRenderer'; -export { default as Quad } from './renderers/utils/Quad'; -export { default as QuadUv } from './renderers/utils/QuadUv'; -export { default as checkMaxIfStatmentsInShader } from './renderers/utils/checkMaxIfStatmentsInShader'; +export { default as State } from './state/State'; +export { default as ObjectRenderer } from './batch/ObjectRenderer'; +export { default as Quad } from './utils/Quad'; +export { default as QuadUv } from './utils/QuadUv'; +export { default as checkMaxIfStatmentsInShader } from './shader/utils/checkMaxIfStatmentsInShader'; export { default as Shader } from './shader/Shader'; export { default as Program } from './shader/Program'; export { default as UniformGroup } from './shader/UniformGroup'; -export { default as SpriteMaskFilter } from './renderers/filters/spriteMask/SpriteMaskFilter'; -export { default as Filter } from './renderers/filters/Filter'; +export { default as SpriteMaskFilter } from './filters/spriteMask/SpriteMaskFilter'; +export { default as Filter } from './filters/Filter'; export { default as Attribute } from './geometry/Attribute'; export { default as Buffer } from './geometry/Buffer'; export { default as Geometry } from './geometry/Geometry'; - diff --git a/packages/core/src/mask/MaskSystem.js b/packages/core/src/mask/MaskSystem.js new file mode 100644 index 0000000..99174c2 --- /dev/null +++ b/packages/core/src/mask/MaskSystem.js @@ -0,0 +1,199 @@ +import System from '../System'; +import AlphaMaskFilter from '../filters/spriteMask/SpriteMaskFilter'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class MaskSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + // TODO - we don't need both! + this.scissor = false; + this.scissorData = null; + this.scissorRenderTarget = null; + + this.enableScissor = false; + + this.alphaMaskPool = []; + this.alphaMaskIndex = 0; + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.DisplayObject} target - Display Object to push the mask to + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + push(target, maskData) + { + // TODO the root check means scissor rect will not + // be used on render textures more info here: + // https://github.com/pixijs/pixi.js/pull/3545 + + if (maskData.texture) + { + this.pushSpriteMask(target, maskData); + } + else if (this.enableScissor + && !this.scissor + && this.renderer._activeRenderTarget.root + && !this.renderer.stencil.stencilMaskStack.length + && maskData.isFastRect()) + { + const matrix = maskData.worldTransform; + + let rot = Math.atan2(matrix.b, matrix.a); + + // use the nearest degree! + rot = Math.round(rot * (180 / Math.PI)); + + if (rot % 90) + { + this.pushStencilMask(maskData); + } + else + { + this.pushScissorMask(target, maskData); + } + } + else + { + this.pushStencilMask(maskData); + } + } + + /** + * Removes the last mask from the mask stack and doesn't return it. + * + * @param {PIXI.DisplayObject} target - Display Object to pop the mask from + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + pop(target, maskData) + { + if (maskData.texture) + { + this.popSpriteMask(target, maskData); + } + else if (this.enableScissor && !this.renderer.stencil.stencilMaskStack.length) + { + this.popScissorMask(target, maskData); + } + else + { + this.popStencilMask(target, maskData); + } + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.RenderTarget} target - Display Object to push the sprite mask to + * @param {PIXI.Sprite} maskData - Sprite to be used as the mask + */ + pushSpriteMask(target, maskData) + { + let alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; + + if (!alphaMaskFilter) + { + alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; + } + + alphaMaskFilter[0].resolution = this.renderer.resolution; + alphaMaskFilter[0].maskSprite = maskData; + + // TODO - may cause issues! + target.filterArea = maskData.getBounds(true); + + this.renderer.filterSystem.pushFilter(target, alphaMaskFilter); + + this.alphaMaskIndex++; + } + + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popSpriteMask() + { + this.renderer.filterSystem.popFilter(); + this.alphaMaskIndex--; + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + pushStencilMask(maskData) + { + this.renderer.batch.flush(); + this.renderer.stencil.pushStencil(maskData); + } + + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popStencilMask() + { + // this.renderer.currentRenderer.stop(); + this.renderer.stencil.popStencil(); + } + + /** + * + * @param {PIXI.DisplayObject} target - Display Object to push the mask to + * @param {PIXI.Graphics} maskData - The masking data. + */ + pushScissorMask(target, maskData) + { + maskData.renderable = true; + + const renderTarget = this.renderer._activeRenderTarget; + + const bounds = maskData.getBounds(); + + bounds.fit(renderTarget.size); + maskData.renderable = false; + + this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); + + const resolution = this.renderer.resolution; + + this.renderer.gl.scissor( + bounds.x * resolution, + (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, + bounds.width * resolution, + bounds.height * resolution + ); + + this.scissorRenderTarget = renderTarget; + this.scissorData = maskData; + this.scissor = true; + } + + /** + * + * + */ + popScissorMask() + { + this.scissorRenderTarget = null; + this.scissorData = null; + this.scissor = false; + + // must be scissor! + const gl = this.renderer.gl; + + gl.disable(gl.SCISSOR_TEST); + } +} diff --git a/packages/core/src/mask/StencilSystem.js b/packages/core/src/mask/StencilSystem.js new file mode 100644 index 0000000..62796a5 --- /dev/null +++ b/packages/core/src/mask/StencilSystem.js @@ -0,0 +1,133 @@ +import System from '../System'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class StencilSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + this.stencilMaskStack = []; + } + + /** + * Changes the mask stack that is used by this System. + * + * @param {PIXI.Graphics[]} stencilMaskStack - The mask stack + */ + setMaskStack(stencilMaskStack) + { + const gl = this.renderer.gl; + + if (stencilMaskStack.length !== this.stencilMaskStack.length) + { + if (stencilMaskStack.length === 0) + { + gl.disable(gl.STENCIL_TEST); + } + else + { + gl.enable(gl.STENCIL_TEST); + } + } + + this.stencilMaskStack = stencilMaskStack; + } + + /** + * Applies the Mask and adds it to the current stencil stack. @alvin + * + * @param {PIXI.Graphics} graphics - The mask + */ + pushStencil(graphics) + { + this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); + + // this.renderer._activeRenderTarget.attachStencilBuffer(); + + const gl = this.renderer.gl; + const prevMaskCount = this.stencilMaskStack.length; + + if (prevMaskCount === 0) + { + gl.enable(gl.STENCIL_TEST); + } + + this.stencilMaskStack.push(graphics); + + // Increment the refference stencil value where the new mask overlaps with the old ones. + gl.colorMask(false, false, false, false); + gl.stencilFunc(gl.EQUAL, prevMaskCount, this._getBitwiseMask()); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.INCR); + this.renderer.plugins.graphics.render(graphics); + + this._useCurrent(); + } + + /** + * Removes the last mask from the stencil stack. @alvin + */ + popStencil() + { + this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); + + const gl = this.renderer.gl; + const graphics = this.stencilMaskStack.pop(); + + if (this.stencilMaskStack.length === 0) + { + // the stack is empty! + gl.disable(gl.STENCIL_TEST); + gl.clear(gl.STENCIL_BUFFER_BIT); + gl.clearStencil(0); + } + else + { + // Decrement the refference stencil value where the popped mask overlaps with the other ones + gl.colorMask(false, false, false, false); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.DECR); + this.renderer.plugins.graphics.render(graphics); + + this._useCurrent(); + } + } + + /** + * Setup renderer to use the current stencil data. + */ + _useCurrent() + { + const gl = this.renderer.gl; + + gl.colorMask(true, true, true, true); + gl.stencilFunc(gl.EQUAL, this.stencilMaskStack.length, this._getBitwiseMask()); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP); + } + + /** + * Fill 1s equal to the number of acitve stencil masks. + * + * @return {number} The bitwise mask. + */ + _getBitwiseMask() + { + return (1 << this.stencilMaskStack.length) - 1; + } + + /** + * Destroys the mask stack. + * + */ + destroy() + { + super.destroy(this); + + this.stencilMaskStack = null; + } +} diff --git a/packages/core/src/projection/ProjectionSystem.js b/packages/core/src/projection/ProjectionSystem.js new file mode 100644 index 0000000..6e63b73 --- /dev/null +++ b/packages/core/src/projection/ProjectionSystem.js @@ -0,0 +1,73 @@ +import System from '../System'; +import { Matrix } from '@pixi/math'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class ProjectionSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.projectionMatrix = new Matrix(); + } + + update(destinationFrame, sourceFrame, resolution, root) + { + this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; + this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; + + this.calculateProjection(this.destinationFrame, this.sourceFrame, resolution, root); + + this.renderer.globalUniforms.uniforms.projectionMatrix = this.projectionMatrix; + this.renderer.globalUniforms.update(); + } + + /** + * Updates the projection matrix based on a projection frame (which is a rectangle) + * + * @param {Rectangle} destinationFrame - The destination frame. + * @param {Rectangle} sourceFrame - The source frame. + */ + calculateProjection(destinationFrame, sourceFrame, resolution, root) + { + const pm = this.projectionMatrix; + + // I don't think we will need this line.. + // pm.identity(); + + if (!root) + { + pm.a = (1 / destinationFrame.width * 2) * resolution; + pm.d = (1 / destinationFrame.height * 2) * resolution; + + pm.tx = -1 - (sourceFrame.x * pm.a); + pm.ty = -1 - (sourceFrame.y * pm.d); + } + else + { + pm.a = (1 / destinationFrame.width * 2) * resolution; + pm.d = (-1 / destinationFrame.height * 2) * resolution; + + pm.tx = -1 - (sourceFrame.x * pm.a); + pm.ty = 1 - (sourceFrame.y * pm.d); + } + } + + /** + * Sets the transform of the active render target to the given matrix + * + * @param {PIXI.Matrix} matrix - The transformation matrix + */ + setTransform()// matrix) + { + // this._activeRenderTarget.transform = matrix; + } +} diff --git a/packages/core/src/renderTexture/BaseRenderTexture.js b/packages/core/src/renderTexture/BaseRenderTexture.js new file mode 100644 index 0000000..88eb83b --- /dev/null +++ b/packages/core/src/renderTexture/BaseRenderTexture.js @@ -0,0 +1,137 @@ +import BaseTexture from '../textures/BaseTexture'; +import FrameBuffer from '../framebuffer/FrameBuffer'; + +/** + * A BaseRenderTexture is a special texture that allows any PixiJS display object to be rendered to it. + * + * __Hint__: All DisplayObjects (i.e. Sprites) that render to a BaseRenderTexture should be preloaded + * otherwise black rectangles will be drawn instead. + * + * A BaseRenderTexture takes a snapshot of any Display Object given to its render method. The position + * and rotation of the given Display Objects is ignored. For example: + * + * ```js + * let renderer = PIXI.autoDetectRenderer(1024, 1024, { view: canvas, ratio: 1 }); + * let baseRenderTexture = new PIXI.BaseRenderTexture(renderer, 800, 600); + * let sprite = PIXI.Sprite.fromImage("spinObj_01.png"); + * + * sprite.position.x = 800/2; + * sprite.position.y = 600/2; + * sprite.anchor.x = 0.5; + * sprite.anchor.y = 0.5; + * + * baseRenderTexture.render(sprite); + * ``` + * + * The Sprite in this case will be rendered using its local transform. To render this sprite at 0,0 + * you can clear the transform + * + * ```js + * + * sprite.setTransform() + * + * let baseRenderTexture = new PIXI.BaseRenderTexture(100, 100); + * let renderTexture = new PIXI.RenderTexture(baseRenderTexture); + * + * renderer.render(sprite, renderTexture); // Renders to center of RenderTexture + * ``` + * + * @class + * @extends PIXI.BaseTexture + * @memberof PIXI + */ +export default class BaseRenderTexture extends BaseTexture +{ + /** + * @param {object} [options] + * @param {number} [options.width=100] - The width of the base render texture + * @param {number} [options.height=100] - The height of the base render texture + * @param {PIXI.SCALE_MODES} [options.scaleMode] - See {@link PIXI.SCALE_MODES} for possible values + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the texture being generated + */ + constructor(options) + { + if (typeof options === 'number') + { + /* eslint-disable prefer-rest-params */ + // Backward compatibility of signature + const width = arguments[0]; + const height = arguments[1]; + const scaleMode = arguments[2]; + const resolution = arguments[3]; + + options = { width, height, scaleMode, resolution }; + /* eslint-enable prefer-rest-params */ + } + + super(null, options); + + const { width, height } = options || {}; + + // Set defaults + this.mipmap = false; + this.width = Math.ceil(width) || 100; + this.height = Math.ceil(height) || 100; + this.valid = true; + + /** + * A map of renderer IDs to webgl renderTargets + * + * @private + * @member {object} + */ + // this._glRenderTargets = {}; + + /** + * A reference to the canvas render target (we only need one as this can be shared across renderers) + * + * @private + * @member {object} + */ + this._canvasRenderTarget = null; + + this.clearColor = [0, 0, 0, 0]; + + this.frameBuffer = new FrameBuffer(width * this.resolution, height * this.resolution) + .addColorTexture(0, this); + + // TODO - could this be added the systems? + + /** + * The data structure for the stencil masks + * + * @member {PIXI.Graphics[]} + */ + this.stencilMaskStack = []; + + /** + * The data structure for the filters + * + * @member {PIXI.Graphics[]} + */ + this.filterStack = [{}]; + } + + /** + * Resizes the BaseRenderTexture. + * + * @param {number} width - The width to resize to. + * @param {number} height - The height to resize to. + */ + resize(width, height) + { + width = Math.ceil(width); + height = Math.ceil(height); + this.frameBuffer.resize(width * this.resolution, height * this.resolution); + } + + /** + * Destroys this texture + * + */ + destroy() + { + super.destroy(true); + this.renderer = null; + } +} diff --git a/packages/core/src/renderTexture/RenderTexture.js b/packages/core/src/renderTexture/RenderTexture.js new file mode 100644 index 0000000..f1ecf1f --- /dev/null +++ b/packages/core/src/renderTexture/RenderTexture.js @@ -0,0 +1,153 @@ +import BaseRenderTexture from './BaseRenderTexture'; +import Texture from '../textures/Texture'; + +/** + * A RenderTexture is a special texture that allows any PixiJS display object to be rendered to it. + * + * __Hint__: All DisplayObjects (i.e. Sprites) that render to a RenderTexture should be preloaded + * otherwise black rectangles will be drawn instead. + * + * A RenderTexture takes a snapshot of any Display Object given to its render method. For example: + * + * ```js + * let renderer = PIXI.autoDetectRenderer(1024, 1024, { view: canvas, ratio: 1 }); + * let renderTexture = PIXI.RenderTexture.create(800, 600); + * let sprite = PIXI.Sprite.fromImage("spinObj_01.png"); + * + * sprite.position.x = 800/2; + * sprite.position.y = 600/2; + * sprite.anchor.x = 0.5; + * sprite.anchor.y = 0.5; + * + * renderer.render(sprite, renderTexture); + * ``` + * + * The Sprite in this case will be rendered using its local transform. To render this sprite at 0,0 + * you can clear the transform + * + * ```js + * + * sprite.setTransform() + * + * let renderTexture = new PIXI.RenderTexture.create(100, 100); + * + * renderer.render(sprite, renderTexture); // Renders to center of RenderTexture + * ``` + * + * @class + * @extends PIXI.Texture + * @memberof PIXI + */ +export default class RenderTexture extends Texture +{ + /** + * @param {PIXI.BaseRenderTexture} baseRenderTexture - The renderer used for this RenderTexture + * @param {PIXI.Rectangle} [frame] - The rectangle frame of the texture to show + */ + constructor(baseRenderTexture, frame) + { + // support for legacy.. + let _legacyRenderer = null; + + if (!(baseRenderTexture instanceof BaseRenderTexture)) + { + /* eslint-disable prefer-rest-params, no-console */ + const width = arguments[1]; + const height = arguments[2]; + const scaleMode = arguments[3]; + const resolution = arguments[4]; + + // we have an old render texture.. + console.warn(`Please use RenderTexture.create(${width}, ${height}) instead of the ctor directly.`); + _legacyRenderer = arguments[0]; + /* eslint-enable prefer-rest-params, no-console */ + + frame = null; + baseRenderTexture = new BaseRenderTexture({ + width, + height, + scaleMode, + resolution, + }); + } + + /** + * The base texture object that this texture uses + * + * @member {BaseTexture} + */ + super(baseRenderTexture, frame); + + this.legacyRenderer = _legacyRenderer; + + /** + * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. + * + * @member {boolean} + */ + this.valid = true; + + /** + * FilterSystem temporary storage + * @private + * @member {PIXI.Rectangle} + */ + this.filterFrame = null; + + this._updateUvs(); + } + + /** + * Resizes the RenderTexture. + * + * @param {number} width - The width to resize to. + * @param {number} height - The height to resize to. + * @param {boolean} [resizeBaseTexture=true] - Should the baseTexture.width and height values be resized as well? + */ + resize(width, height, resizeBaseTexture = true) + { + width = Math.ceil(width); + height = Math.ceil(height); + + // TODO - could be not required.. + this.valid = (width > 0 && height > 0); + + this._frame.width = this.orig.width = width; + this._frame.height = this.orig.height = height; + + if (resizeBaseTexture) + { + this.baseTexture.resize(width, height); + } + + this._updateUvs(); + } + + /** + * A short hand way of creating a render texture. + * + * @param {object} [options] - Options + * @param {number} [options.width=100] - The width of the render texture + * @param {number} [options.height=100] - The height of the render texture + * @param {number} [options.scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the texture being generated + * @return {PIXI.RenderTexture} The new render texture + */ + static create(options) + { + // fallback, old-style: create(width, height, scaleMode, resolution) + if (typeof options === 'number') + { + /* eslint-disable prefer-rest-params */ + options = { + width: options, + height: arguments[1], + scaleMode: arguments[2], + resolution: arguments[3], + }; + /* eslint-enable prefer-rest-params */ + } + + return new RenderTexture(new BaseRenderTexture(options)); + } +} diff --git a/packages/core/src/renderTexture/RenderTextureSystem.js b/packages/core/src/renderTexture/RenderTextureSystem.js new file mode 100644 index 0000000..8d0ca61 --- /dev/null +++ b/packages/core/src/renderTexture/RenderTextureSystem.js @@ -0,0 +1,115 @@ +import System from '../System'; +import { Rectangle } from '@pixi/math'; + +const tempRect = new Rectangle(); + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class RenderTextureSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.clearColor = renderer._backgroundColorRgba; + + // TODO moe this property somewhere else! + this.defaultMaskStack = []; + this.defaultFilterStack = [{}]; + + // empty render texture? + this.renderTexture = null; + + this.destinationFrame = new Rectangle(); + } + + bind(renderTexture, sourceFrame, destinationFrame) + { + // TODO - do we want this?? + if (this.renderTexture === renderTexture) return; + this.renderTexture = renderTexture; + + const renderer = this.renderer; + + if (renderTexture) + { + const baseTexture = renderTexture.baseTexture; + + if (!destinationFrame) + { + tempRect.width = baseTexture.realWidth; + tempRect.height = baseTexture.realHeight; + + destinationFrame = tempRect; + } + + if (!sourceFrame) + { + sourceFrame = destinationFrame; + } + + this.renderer.framebuffer.bind(baseTexture.frameBuffer, destinationFrame); + + this.renderer.projection.update(destinationFrame, sourceFrame, baseTexture.resolution, false); + this.renderer.stencil.setMaskStack(baseTexture.stencilMaskStack); + } + else + { + // TODO these validation checks happen deeper down.. + // thing they can be avoided.. + if (!destinationFrame) + { + tempRect.width = renderer.width; + tempRect.height = renderer.height; + + destinationFrame = tempRect; + } + + if (!sourceFrame) + { + sourceFrame = destinationFrame; + } + + renderer.framebuffer.bind(null, destinationFrame); + + // TODO store this.. + this.renderer.projection.update(destinationFrame, sourceFrame, this.renderer.resolution, true); + this.renderer.stencil.setMaskStack(this.defaultMaskStack); + } + + this.destinationFrame.copyFrom(destinationFrame); + } + + /** + * Erases the render texture and fills the drawing area with a colour + * + * @param {number} [clearColor] - The colour + * @return {PIXI.Renderer} Returns itself. + */ + clear(clearColor) + { + if (this.renderTexture) + { + clearColor = clearColor || this.renderTexture.baseTexture.clearColor; + } + else + { + clearColor = clearColor || this.clearColor; + } + + this.renderer.framebuffer.clear(clearColor[0], clearColor[1], clearColor[2], clearColor[3]); + } + + resize()// screenWidth, screenHeight) + { + // resize the root only! + this.bind(null); + } +} diff --git a/packages/core/src/renderers/AbstractRenderer.js b/packages/core/src/renderers/AbstractRenderer.js deleted file mode 100644 index 928670f..0000000 --- a/packages/core/src/renderers/AbstractRenderer.js +++ /dev/null @@ -1,353 +0,0 @@ -import { hex2string, hex2rgb } from '@pixi/utils'; -import { Matrix, Rectangle } from '@pixi/math'; -import { RENDERER_TYPE } from '@pixi/constants'; -import { settings } from '@pixi/settings'; -import { Container } from '@pixi/display'; -import RenderTexture from '../textures/RenderTexture'; -import EventEmitter from 'eventemitter3'; - -const tempMatrix = new Matrix(); - -/** - * 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 EventEmitter - * @memberof PIXI - */ -export default class AbstractRenderer extends EventEmitter -{ - // eslint-disable-next-line valid-jsdoc - /** - * @param {string} system - The name of the system this renderer is for. - * @param {object} [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.transparent=false] - If the render view is transparent, default false - * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false - * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) - * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. The - * resolution of the renderer retina would be 2. - * @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 {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when rendering, - * stopping pixel interpolation. - */ - constructor(system, options, arg2, arg3) - { - super(); - - // Support for constructor(system, screenWidth, screenHeight, options) - if (typeof options === 'number') - { - options = Object.assign({ - width: options, - height: arg2 || settings.RENDER_OPTIONS.height, - }, arg3); - } - - // 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 = RENDERER_TYPE.UNKNOWN; - - /** - * Measurements of the screen. (0, 0, screenWidth, screenHeight) - * - * Its safe to use as filterArea or hitArea for 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 1 - */ - this.resolution = options.resolution || settings.RESOLUTION; - - /** - * Whether the render view is transparent - * - * @member {boolean} - */ - this.transparent = options.transparent; - - /** - * Whether css dimensions of canvas view should be resized to screen dimensions automatically - * - * @member {boolean} - */ - this.autoResize = options.autoResize || false; - - /** - * Tracks the blend modes useful for this renderer. - * - * @member {object} - */ - this.blendModes = null; - - /** - * 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; - - /** - * If true PixiJS will Math.floor() x/y values when rendering, stopping pixel interpolation. - * Handy for crisp pixel art and speed on legacy devices. - * - * @member {boolean} - */ - this.roundPixels = options.roundPixels; - - /** - * The background color as a number. - * - * @member {number} - * @private - */ - this._backgroundColor = 0x000000; - - /** - * The background color as an [R, G, B] array. - * - * @member {number[]} - * @private - */ - this._backgroundColorRgba = [0, 0, 0, 0]; - - /** - * The background color as a string. - * - * @member {string} - * @private - */ - this._backgroundColorString = '#000000'; - - this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter - - /** - * This temporary display object used as the parent of the currently being rendered item - * - * @member {PIXI.DisplayObject} - * @private - */ - this._tempDisplayObjectParent = new Container(); - - /** - * The last root object that the renderer tried to render. - * - * @member {PIXI.DisplayObject} - * @private - */ - this._lastObjectRendered = this._tempDisplayObjectParent; - - /** - * Collection of plugins. - * @readonly - * @member {object} - */ - this.plugins = {}; - } - - /** - * Initialize the plugins. - * - * @protected - * @param {object} staticMap - The dictionary of staticly saved plugins. - */ - initPlugins(staticMap) - { - 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() - { - return this.view.width; - } - - /** - * Same as view.height, actual number of pixels in the canvas by vertical - * - * @member {number} - * @readonly - * @default 600 - */ - get height() - { - return this.view.height; - } - - /** - * Resizes the screen and canvas to the specified width and height - * Canvas dimensions are multiplied by resolution - * - * @param {number} screenWidth - the new width of the screen - * @param {number} screenHeight - the new height of the screen - */ - resize(screenWidth, screenHeight) - { - this.screen.width = screenWidth; - this.screen.height = screenHeight; - - this.view.width = screenWidth * this.resolution; - this.view.height = screenHeight * this.resolution; - - if (this.autoResize) - { - this.view.style.width = `${screenWidth}px`; - this.view.style.height = `${screenHeight}px`; - } - } - - /** - * 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 {PIXI.DisplayObject} displayObject - The displayObject the object will be generated from - * @param {number} scaleMode - Should be one of the scaleMode consts - * @param {number} resolution - The resolution / device pixel ratio of the texture being generated - * @param {PIXI.Rectangle} [region] - The region of the displayObject, that shall be rendered, - * if no region is specified, defaults to the local bounds of the displayObject. - * @return {PIXI.Texture} a texture of the graphics object - */ - generateTexture(displayObject, scaleMode, resolution, region) - { - region = region || displayObject.getLocalBounds(); - - // 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(region.width | 0, region.height | 0, scaleMode, resolution); - - tempMatrix.tx = -region.x; - tempMatrix.ty = -region.y; - - this.render(displayObject, renderTexture, false, tempMatrix, true); - - return renderTexture; - } - - /** - * Removes everything from the renderer and optionally removes the Canvas DOM element. - * - * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. - */ - destroy(removeView) - { - 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); - } - - this.plugins = null; - - this.type = RENDERER_TYPE.UNKNOWN; - - this.view = null; - - this.screen = null; - - this.resolution = 0; - - this.transparent = false; - - this.autoResize = false; - - this.blendModes = null; - - this.options = null; - - this.preserveDrawingBuffer = false; - this.clearBeforeRender = false; - - this.roundPixels = false; - - this._backgroundColor = 0; - this._backgroundColorRgba = null; - this._backgroundColorString = null; - - this._tempDisplayObjectParent = null; - this._lastObjectRendered = null; - } - - /** - * The background color to fill if not transparent - * - * @member {number} - */ - get backgroundColor() - { - return this._backgroundColor; - } - - set backgroundColor(value) // eslint-disable-line require-jsdoc - { - this._backgroundColor = value; - this._backgroundColorString = hex2string(value); - hex2rgb(value, this._backgroundColorRgba); - } -} diff --git a/packages/core/src/renderers/Renderer.js b/packages/core/src/renderers/Renderer.js deleted file mode 100644 index a2020b1..0000000 --- a/packages/core/src/renderers/Renderer.js +++ /dev/null @@ -1,329 +0,0 @@ -import AbstractRenderer from './AbstractRenderer'; -import { sayHello } from '@pixi/utils'; -import MaskSystem from './systems/MaskSystem'; -import StencilSystem from './systems/StencilSystem'; -import FilterSystem from './systems/filter/FilterSystem'; -import FramebufferSystem from './systems/FramebufferSystem'; -import RenderTextureSystem from './systems/RenderTextureSystem'; -import TextureSystem from './systems/textures/TextureSystem'; -import ProjectionSystem from './systems/ProjectionSystem'; -import StateSystem from './systems/StateSystem'; -import GeometrySystem from './systems/geometry/GeometrySystem'; -import ShaderSystem from './systems/shader/ShaderSystem'; -import ContextSystem from './systems/ContextSystem'; -import BatchSystem from './systems/BatchSystem'; -import TextureGCSystem from './systems/textures/TextureGCSystem'; -import { RENDERER_TYPE } from '@pixi/constants'; -import UniformGroup from '../shader/UniformGroup'; -import { Matrix } from '@pixi/math'; -import Runner from 'mini-runner'; - -/** - * 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 Render works by automatically managing webGLBatchs. - * 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 :) - * - * @class - * @memberof PIXI - * @extends PIXI.AbstractRenderer - */ -export default class Renderer extends AbstractRenderer -{ - // eslint-disable-next-line valid-jsdoc - /** - * - * @param {object} [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.transparent=false] - If the render view is transparent, default false - * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false - * @param {boolean} [options.antialias=false] - sets antialias. If not available natively then FXAA - * antialiasing is used - * @param {boolean} [options.forceFXAA=false] - forces FXAA antialiasing to be used over native. - * FXAA is faster, but may not always look as great - * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. - * The resolution of the renderer retina would be 2. - * @param {boolean} [options.clearBeforeRender=true] - This sets if the renderer will clear - * the canvas or not before the new render pass. If you wish to set this to false, you *must* set - * preserveDrawingBuffer to `true`. - * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, - * enable this if you need to call toDataUrl on the webgl context. - * @param {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when - * rendering, stopping pixel interpolation. - * @param {number} [options.backgroundColor=0x000000] - The background color of the rendered area - * (shown if not transparent). - * @param {boolean} [options.legacy=false] - If true PixiJS will aim to ensure compatibility - * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. - * @param {string} [options.powerPreference] - Parameter passed to webgl context, set to "high-performance" - * for devices with dual graphics card - */ - constructor(options, arg2, arg3) - { - super('WebGL', options, arg2, arg3); - - /** - * The type of this renderer as a standardised const - * - * @member {number} - * @see PIXI.RENDERER_TYPE - */ - this.type = RENDERER_TYPE.WEBGL; - - // this will be set by the contextSystem (this.context) - this.gl = null; - this.CONTEXT_UID = 0; - this.legacy = !!options.legacy; - - // TODO legacy! - - // runners! - this.runners = { - destroy: new Runner('destroy'), - contextChange: new Runner('contextChange', 1), - reset: new Runner('reset'), - update: new Runner('update'), - postrender: new Runner('postrender'), - prerender: new Runner('prerender'), - resize: new Runner('resize', 2), - }; - - 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(GeometrySystem, 'geometry') - .addSystem(FramebufferSystem, 'framebuffer') - .addSystem(StencilSystem, 'stencil') - .addSystem(ProjectionSystem, 'projection') - .addSystem(TextureGCSystem, 'textureGC') - .addSystem(FilterSystem, 'filter') - .addSystem(RenderTextureSystem, 'renderTexture') - .addSystem(BatchSystem, 'batch'); - - this.initPlugins(Renderer.__plugins); - - /** - * The options passed in to create a new webgl context. - * - * @member {object} - * @private - */ - if (options.context) - { - this.context.initFromContext(options.context); - } - else - { - this.context.initFromOptions({ - alpha: this.transparent, - antialias: options.antialias, - premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', - stencil: true, - 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); - } - - addSystem(_class, name) - { - if (!name) - { - name = _class.name; - } - - // TODO - read name from class.name.. - - /* - if(name.includes('System')) - { - name = name.replace('System', ''); - name = name.charAt(0).toLowerCase() + name.slice(1); - } - */ - - const system = new _class(this); - - if (this[name]) - { - throw new Error(`Whoops! ${name} is already a manger`); - } - - this[name] = system; - - for (const i in this.runners) - { - this.runners[i].add(system); - } - - return this; - - /** - * 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. - */ - } - - /** - * Renders the object to its webGL view - * - * @param {PIXI.DisplayObject} displayObject - the object to be rendered - * @param {PIXI.RenderTexture} renderTexture - The render texture to render to. - * @param {boolean} [clear] - Should the canvas be cleared before the new render - * @param {PIXI.Transform} [transform] - A transform to apply to the render texture before rendering. - * @param {boolean} [skipUpdateTransform] - Should we skip the update transform pass? - */ - render(displayObject, renderTexture, clear, transform, skipUpdateTransform) - { - // can be handy to know! - this.renderingToScreen = !renderTexture; - - this.runners.prerender.run(); - this.emit('prerender'); - - // 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.parent; - - displayObject.parent = this._tempDisplayObjectParent; - displayObject.updateTransform(); - displayObject.parent = 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.run(); - - this.emit('postrender'); - } - - /** - * Resizes the webGL view to the specified width and height. - * - * @param {number} screenWidth - the new width of the screen - * @param {number} screenHeight - the new height of the screen - */ - resize(screenWidth, screenHeight) - { - AbstractRenderer.prototype.resize.call(this, screenWidth, screenHeight); - - this.runners.resize.run(screenWidth, screenHeight); - } - - /** - * Resets the WebGL state so you can render things however you fancy! - * - * @return {PIXI.Renderer} Returns itself. - */ - reset() - { - this.runners.reset.run(); - - return this; - } - - clear() - { - this.framebuffer.bind(); - this.framebuffer.clear(); - } - - /** - * Removes everything from the renderer (event listeners, spritebatch, etc...) - * - * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. - * See: https://github.com/pixijs/pixi.js/issues/2233 - */ - destroy(removeView) - { - this.runners.destroy.run(); - - // call base destroy - super.destroy(removeView); - - // TODO nullify all the managers.. - this.gl = null; - } - - /** - * 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 - * builds and excluding plugins. - * @name PIXI.Renderer#plugins - * @type {object} - * @readonly - * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. - * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. - * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. - * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. - */ - - /** - * Adds a plugin to the renderer. - * - * @method - * @param {string} pluginName - The name of the plugin. - * @param {Function} ctor - The constructor function or class for the plugin. - */ - static registerPlugin(pluginName, ctor) - { - Renderer.__plugins = Renderer.__plugins || {}; - Renderer.__plugins[pluginName] = ctor; - } -} diff --git a/packages/core/src/renderers/State.js b/packages/core/src/renderers/State.js deleted file mode 100644 index fa3f221..0000000 --- a/packages/core/src/renderers/State.js +++ /dev/null @@ -1,163 +0,0 @@ -/* eslint-disable max-len */ - -const BLEND = 0; -const OFFSET = 1; -const CULLING = 2; -const DEPTH_TEST = 3; -const WINDING = 4; - -/** - * This is a webGL state. It is passed The WebGL StateManager. - * Each mesh renderered may require webGL to be in a different state. - * For example you may want different blend mode or to enable polygon offsets - * - * @class - * @memberof PIXI - */ -export default class State -{ - /** - * - */ - constructor() - { - this.data = 0; - - this.blendMode = 0; - this.polygonOffset = 0; - - this.blend = true; - // this.depthTest = true; - } - - /** - * Activates blending of the computed fragment color values - * - * @member {boolean} - */ - get blend() - { - return !!(this.data & (1 << BLEND)); - } - - set blend(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << BLEND)) !== value) - { - this.data ^= (1 << BLEND); - } - } - - /** - * Activates adding an offset to depth values of polygon's fragments - * - * @member {boolean} - * @default false - */ - get offsets() - { - return !!(this.data & (1 << OFFSET)); - } - - set offsets(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << OFFSET)) !== value) - { - this.data ^= (1 << OFFSET); - } - } - - /** - * Activates culling of polygons. - * - * @member {boolean} - * @default false - */ - get culling() - { - return !!(this.data & (1 << CULLING)); - } - - set culling(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << CULLING)) !== value) - { - this.data ^= (1 << CULLING); - } - } - - /** - * Activates depth comparisons and updates to the depth buffer. - * - * @member {boolean} - * @default false - */ - get depthTest() - { - return !!(this.data & (1 << DEPTH_TEST)); - } - - set depthTest(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << DEPTH_TEST)) !== value) - { - this.data ^= (1 << DEPTH_TEST); - } - } - - /** - * Specifies whether or not front or back-facing polygons can be culled. - * @member {boolean} - * @default false - */ - get clockwiseFrontFace() - { - return !!(this.data & (1 << WINDING)); - } - - set clockwiseFrontFace(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << WINDING)) !== value) - { - this.data ^= (1 << WINDING); - } - } - - /** - * The blend mode to be applied when this state is set. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. - * Setting this mode to anything other than NO_BLEND will automatically switch blending on. - * - * @member {boolean} - * @default PIXI.BLEND_MODES.NORMAL - * @see PIXI.BLEND_MODES - */ - get blendMode() - { - return this._blendMode; - } - - set blendMode(value) // eslint-disable-line require-jsdoc - { - // 17 is NO BLEND - this.blend = (value !== 17); - this._blendMode = value; - } - - /** - * The polygon offset. Setting this property to anything other than 0 will automatically enable poygon offset fill. - * - * @member {number} - * @default 0 - */ - get polygonOffset() - { - return this._polygonOffset; - } - - set polygonOffset(value) // eslint-disable-line require-jsdoc - { - this.offsets = !!value; - this._polygonOffset = value; - } -} - diff --git a/packages/core/src/renderers/filters/Filter.js b/packages/core/src/renderers/filters/Filter.js deleted file mode 100644 index 2073b8d..0000000 --- a/packages/core/src/renderers/filters/Filter.js +++ /dev/null @@ -1,140 +0,0 @@ -import Shader from '../../shader/Shader'; -import Program from '../../shader/Program'; -import State from '../State'; -import { settings } from '@pixi/settings'; -// import extractUniformsFromSrc from './extractUniformsFromSrc'; -import defaultVertex from './defaultFilter.vert'; -import defaultFragment from './defaultFilter.frag'; - -// let math = require('../../math'); -/** - * @class - * @memberof PIXI - * @extends PIXI.Shader - */ -export default class Filter extends Shader -{ - /** - * @param {string} [vertexSrc] - The source of the vertex shader. - * @param {string} [fragmentSrc] - The source of the fragment shader. - * @param {object} [uniforms] - Custom uniforms to use to augment the built-in ones. - */ - constructor(vertexSrc, fragmentSrc, uniforms) - { - const program = Program.from(vertexSrc, fragmentSrc); - - super(program, uniforms); - - /** - * The padding of the filter. Some filters require extra space to breath such as a blur. - * Increasing this will add extra width and height to the bounds of the object that the - * filter is applied to. - * - * @member {number} - */ - this.padding = 0; - - /** - * The resolution of the filter. Setting this to be lower will lower the quality but - * increase the performance of the filter. - * - * @member {number} - */ - this.resolution = settings.RESOLUTION; - - /** - * If enabled is true the filter is applied, if false it will not. - * - * @member {boolean} - */ - this.enabled = true; - - /** - * If enabled, PixiJS will fit the filter area into boundaries for better performance. - * Switch it off if it does not work for specific shader. - * - * @member {boolean} - */ - this.autoFit = true; - - /** - * Legacy filters use position and uvs from attributes - * @member {boolean} - * @readonly - */ - this.legacy = !!this.program.attributeData.aTextureCoord; - - /** - * the webGL state the filter requires to render - * @member {PIXI.State} - */ - this.state = new State(); - } - - /** - * Applies the filter - * - * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from - * @param {PIXI.RenderTarget} input - The input render target. - * @param {PIXI.RenderTarget} output - The target to output to. - * @param {boolean} clear - Should the output be cleared before rendering to it - * @param {object} [currentState] - It's current state of filter. - * There are some useful properties in the currentState : - * target, filters, sourceFrame, destinationFrame, renderTarget, resolution - */ - apply(filterManager, input, output, clear, currentState, derp) // eslint-disable-line no-unused-vars - { - // do as you please! - - filterManager.applyFilter(this, input, output, clear, currentState, derp); - - // or just do a regular render.. - } - - /** - * Sets the blendmode of the filter - * - * @member {number} - * @default PIXI.BLEND_MODES.NORMAL - */ - get blendMode() - { - return this.state.blendMode; - } - - set blendMode(value) // eslint-disable-line require-jsdoc - { - this.state.blendMode = value; - } - - /** - * The default vertex shader source - * - * @static - * @constant - */ - static get defaultVertexSrc() - { - return defaultVertex; - } - - /** - * The default fragment shader source - * - * @static - * @constant - */ - static get defaultFragmentSrc() - { - return defaultFragment; - } -} - -/** - * Used for caching shader IDs - * - * @static - * @private - */ -Filter.SOURCE_KEY_MAP = {}; - diff --git a/packages/core/src/renderers/filters/defaultFilter.frag b/packages/core/src/renderers/filters/defaultFilter.frag deleted file mode 100644 index a14c1be..0000000 --- a/packages/core/src/renderers/filters/defaultFilter.frag +++ /dev/null @@ -1,22 +0,0 @@ -varying vec2 vTextureCoord; -varying vec2 vFilterCoord; - -uniform sampler2D uSampler; -uniform sampler2D filterSampler; - -void main(void){ - vec4 masky = texture2D(filterSampler, vFilterCoord); - vec4 sample = texture2D(uSampler, vTextureCoord); - vec4 color; - if(mod(vFilterCoord.x, 1.0) > 0.5) - { - color = vec4(1.0, 0.0, 0.0, 1.0); - } - else - { - color = vec4(0.0, 1.0, 0.0, 1.0); - } - // gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0); - gl_FragColor = mix(sample, masky, 0.5); - gl_FragColor *= sample.a; -} diff --git a/packages/core/src/renderers/filters/defaultFilter.vert b/packages/core/src/renderers/filters/defaultFilter.vert deleted file mode 100644 index e1febca..0000000 --- a/packages/core/src/renderers/filters/defaultFilter.vert +++ /dev/null @@ -1,14 +0,0 @@ -attribute vec2 aVertexPosition; -attribute vec2 aTextureCoord; - -uniform mat3 projectionMatrix; -uniform mat3 filterMatrix; - -varying vec2 vTextureCoord; -varying vec2 vFilterCoord; - -void main(void){ - gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); - vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy; - vTextureCoord = aTextureCoord ; -} diff --git a/packages/core/src/renderers/filters/filterTransforms.js b/packages/core/src/renderers/filters/filterTransforms.js deleted file mode 100644 index 87a3cd6..0000000 --- a/packages/core/src/renderers/filters/filterTransforms.js +++ /dev/null @@ -1,50 +0,0 @@ -import { Matrix } from '@pixi/math'; - -/** - * Calculates the mapped matrix - * @param filterArea {Rectangle} The filter area - * @param sprite {Sprite} the target sprite - * @param outputMatrix {Matrix} @alvin - * @private - */ -// this returns a matrix that will normalise map filter cords in the filter to screen space -export function calculateScreenSpaceMatrix(outputMatrix, filterArea, textureSize) -{ - // TODO unwrap? - const mappedMatrix = outputMatrix.identity(); - - mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); - - mappedMatrix.scale(textureSize.width, textureSize.height); - - return mappedMatrix; -} - -export function calculateNormalizedScreenSpaceMatrix(outputMatrix, filterArea, textureSize) -{ - const mappedMatrix = outputMatrix.identity(); - - mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); - - const translateScaleX = (textureSize.width / filterArea.width); - const translateScaleY = (textureSize.height / filterArea.height); - - mappedMatrix.scale(translateScaleX, translateScaleY); - - return mappedMatrix; -} - -// this will map the filter coord so that a texture can be used based on the transform of a sprite -export function calculateSpriteMatrix(outputMatrix, filterArea, textureSize, sprite) -{ - const orig = sprite._texture.orig; - const mappedMatrix = outputMatrix.set(textureSize.width, 0, 0, textureSize.height, filterArea.x, filterArea.y); - const worldTransform = sprite.worldTransform.copyTo(Matrix.TEMP_MATRIX); - - worldTransform.invert(); - mappedMatrix.prepend(worldTransform); - mappedMatrix.scale(1.0 / orig.width, 1.0 / orig.height); - mappedMatrix.translate(sprite.anchor.x, sprite.anchor.y); - - return mappedMatrix; -} diff --git a/packages/core/src/renderers/filters/spriteMask/SpriteMaskFilter.js b/packages/core/src/renderers/filters/spriteMask/SpriteMaskFilter.js deleted file mode 100644 index d5a91a4..0000000 --- a/packages/core/src/renderers/filters/spriteMask/SpriteMaskFilter.js +++ /dev/null @@ -1,63 +0,0 @@ -import Filter from '../Filter'; -import { Matrix } from '@pixi/math'; -import vertex from './spriteMaskFilter.vert'; -import fragment from './spriteMaskFilter.frag'; -import { default as TextureMatrix } from '../../../textures/TextureMatrix'; - -/** - * The SpriteMaskFilter class - * - * @class - * @extends PIXI.Filter - * @memberof PIXI - */ -export default class SpriteMaskFilter extends Filter -{ - /** - * @param {PIXI.Sprite} sprite - the target sprite - */ - constructor(sprite) - { - const maskMatrix = new Matrix(); - - super(vertex, fragment); - - sprite.renderable = false; - - this.maskSprite = sprite; - this.maskMatrix = maskMatrix; - } - - /** - * Applies the filter - * - * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from - * @param {PIXI.RenderTarget} input - The input render target. - * @param {PIXI.RenderTarget} output - The target to output to. - */ - apply(filterManager, input, output) - { - const maskSprite = this.maskSprite; - const tex = this.maskSprite.texture; - - if (!tex.valid) - { - return; - } - if (!tex.transform) - { - // margin = 0.0, let it bleed a bit, shader code becomes easier - // assuming that atlas textures were made with 1-pixel padding - tex.transform = new TextureMatrix(tex, 0.0); - } - tex.transform.update(); - - this.uniforms.mask = tex; - this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite) - .prepend(tex.transform.mapCoord); - this.uniforms.alpha = maskSprite.worldAlpha; - this.uniforms.maskClamp = tex.transform.uClampFrame; - - filterManager.applyFilter(this, input, output); - } -} diff --git a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.frag b/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.frag deleted file mode 100644 index 0e4aef8..0000000 --- a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.frag +++ /dev/null @@ -1,23 +0,0 @@ -varying vec2 vMaskCoord; -varying vec2 vTextureCoord; - -uniform sampler2D uSampler; -uniform sampler2D mask; -uniform float alpha; -uniform vec4 maskClamp; - -void main(void) -{ - float clip = step(3.5, - step(maskClamp.x, vMaskCoord.x) + - step(maskClamp.y, vMaskCoord.y) + - step(vMaskCoord.x, maskClamp.z) + - step(vMaskCoord.y, maskClamp.w)); - - vec4 original = texture2D(uSampler, vTextureCoord); - vec4 masky = texture2D(mask, vMaskCoord); - - original *= (masky.r * masky.a * alpha * clip); - - gl_FragColor = original; -} diff --git a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.vert b/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.vert deleted file mode 100644 index d23a30c..0000000 --- a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.vert +++ /dev/null @@ -1,16 +0,0 @@ -attribute vec2 aVertexPosition; -attribute vec2 aTextureCoord; - -uniform mat3 projectionMatrix; -uniform mat3 otherMatrix; - -varying vec2 vMaskCoord; -varying vec2 vTextureCoord; - -void main(void) -{ - gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); - - vTextureCoord = aTextureCoord; - vMaskCoord = ( otherMatrix * vec3( aTextureCoord, 1.0) ).xy; -} diff --git a/packages/core/src/renderers/systems/BatchSystem.js b/packages/core/src/renderers/systems/BatchSystem.js deleted file mode 100644 index 70e9518..0000000 --- a/packages/core/src/renderers/systems/BatchSystem.js +++ /dev/null @@ -1,68 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import ObjectRenderer from '../utils/ObjectRenderer'; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ - -export default class BatchSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this manager works for. - */ - constructor(renderer) - { - super(renderer); - - /** - * An empty renderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.emptyRenderer = new ObjectRenderer(renderer); - - /** - * The currently active ObjectRenderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.currentRenderer = this.emptyRenderer; - } - - /** - * Changes the current renderer to the one given in parameter - * - * @param {PIXI.ObjectRenderer} objectRenderer - The object renderer to use. - */ - setObjectRenderer(objectRenderer) - { - if (this.currentRenderer === objectRenderer) - { - return; - } - - this.currentRenderer.stop(); - this.currentRenderer = objectRenderer; - - this.renderer.state.setState(objectRenderer.state); - - this.currentRenderer.start(); - } - - /** - * This should be called if you wish to do some custom rendering - * It will basically render anything that may be batched up such as sprites - * - */ - flush() - { - this.setObjectRenderer(this.emptyRenderer); - } - - reset() - { - this.setObjectRenderer(this.emptyRenderer); - } -} diff --git a/packages/core/src/renderers/systems/ContextSystem.js b/packages/core/src/renderers/systems/ContextSystem.js deleted file mode 100644 index 43bf827..0000000 --- a/packages/core/src/renderers/systems/ContextSystem.js +++ /dev/null @@ -1,186 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { settings } from '@pixi/settings'; - -let CONTEXT_UID = 0; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class ContextSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this.webGLVersion = 1; - - this.handleContextLost = this.handleContextLost.bind(this); - this.handleContextRestored = this.handleContextRestored.bind(this); - - this.extensions = {}; - - renderer.view.addEventListener('webglcontextlost', this.handleContextLost, false); - renderer.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); - } - - get isLost() - { - return (!this.gl || this.gl.isContextLost()); - } - - contextChange(gl) - { - this.gl = gl; - - // restore a context if it was previously lost - if (gl.isContextLost() && gl.getExtension('WEBGL_lose_context')) - { - gl.getExtension('WEBGL_lose_context').restoreContext(); - } - } - - initFromContext(gl) - { - this.gl = gl; - this.validateContext(gl); - this.renderer.gl = gl; - this.renderer.CONTEXT_UID = CONTEXT_UID++; - this.renderer.runners.contextChange.run(gl); - } - - initFromOptions(options) - { - const gl = this.createContext(this.renderer.view, options); - - this.initFromContext(gl); - } - - /** - * Helper class to create a webGL Context - * - * @class - * @memberof PIXI.glCore - * @param canvas {HTMLCanvasElement} the canvas element that we will get the context from - * @param options {Object} An options object that gets passed in to the canvas element containing the context attributes, - * see https://developer.mozilla.org/en/docs/Web/API/HTMLCanvasElement/getContext for the options available - * @return {WebGLRenderingContext} the WebGL context - */ - createContext(canvas, options) - { - let gl; - - if (settings.PREFER_WEBGL_2) - { - gl = canvas.getContext('webgl2', options); - } - - if (gl) - { - this.webGLVersion = 2; - } - else - { - this.webGLVersion = 1; - - gl = canvas.getContext('webgl', options) - || canvas.getContext('experimental-webgl', options); - - if (!gl) - { - // fail, not able to get a context - throw new Error('This browser does not support webGL. Try using the canvas renderer'); - } - } - - this.gl = gl; - - this.getExtensions(); - - return gl; - } - - getExtensions() - { - // time to set up default etensions that pixi uses.. - const gl = this.gl; - const extensions = this.extensions; - - if (this.webGLVersion === 1) - { - extensions.drawBuffers = gl.getExtension('WEBGL_draw_buffers'); - extensions.depthTexture = gl.getExtension('WEBKIT_WEBGL_depth_texture'); - extensions.floatTexture = gl.getExtension('OES_texture_float'); - extensions.loseContext = gl.getExtension('WEBGL_lose_context'); - - extensions.vertexArrayObject = gl.getExtension('OES_vertex_array_object') - || gl.getExtension('MOZ_OES_vertex_array_object') - || gl.getExtension('WEBKIT_OES_vertex_array_object'); - } - - // we don't use any specific WebGL 2 ones yet! - } - - /** - * Handles a lost webgl context - * - * @private - * @param {WebGLContextEvent} event - The context lost event. - */ - handleContextLost(event) - { - event.preventDefault(); - } - - /** - * Handles a restored webgl context - * - * @private - */ - handleContextRestored() - { - this.renderer.runners.contextChange.run(this.gl); - } - - destroy() - { - const view = this.renderer.view; - - // remove listeners - view.removeEventListener('webglcontextlost', this.handleContextLost); - view.removeEventListener('webglcontextrestored', this.handleContextRestored); - - this.gl.useProgram(null); - - if (this.extensions.loseContext) - { - this.extensions.loseContext.loseContext(); - } - } - - postrender() - { - this.gl.flush(); - } - - validateContext(gl) - { - const attributes = gl.getContextAttributes(); - - // this is going to be fairly simple for now.. but at least we have room to grow! - if (!attributes.stencil) - { - /* eslint-disable max-len */ - - /* eslint-disable no-console */ - console.warn('Provided WebGL context does not have a stencil buffer, masks may not render correctly'); - /* eslint-enable no-console */ - - /* eslint-enable max-len */ - } - } -} diff --git a/packages/core/src/renderers/systems/FramebufferSystem.js b/packages/core/src/renderers/systems/FramebufferSystem.js deleted file mode 100644 index df7902b..0000000 --- a/packages/core/src/renderers/systems/FramebufferSystem.js +++ /dev/null @@ -1,246 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { Rectangle } from '@pixi/math'; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class FramebufferSystem extends WebGLSystem -{ - /** - * Sets up the renderer context and necessary buffers. - * - * @private - */ - contextChange() - { - this.gl = this.renderer.gl; - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - this.current = null; - this.viewport = new Rectangle(); - - this.drawBufferExtension = this.renderer.context.extensions.drawBuffers; - } - - bind(framebuffer, frame) - { - const gl = this.gl; - - this.current = framebuffer; - - if (framebuffer) - { - // TODO cacheing layer! - - const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID] || this.initFramebuffer(framebuffer); - - gl.bindFramebuffer(gl.FRAMEBUFFER, fbo.framebuffer); - // makesure all textures are unbound.. - - // now check for updates... - if (fbo.dirtyId !== framebuffer.dirtyId) - { - fbo.dirtyId = framebuffer.dirtyId; - - if (fbo.dirtyFormat !== framebuffer.dirtyFormat) - { - fbo.dirtyFormat = framebuffer.dirtyFormat; - this.updateFramebuffer(framebuffer); - } - else if (fbo.dirtySize !== framebuffer.dirtySize) - { - fbo.dirtySize = framebuffer.dirtySize; - this.resizeFramebuffer(framebuffer); - } - } - - for (let i = 0; i < framebuffer.colorTextures.length; i++) - { - if (framebuffer.colorTextures[i].texturePart) - { - this.renderer.texture.unbind(framebuffer.colorTextures[i].texture); - } - else - { - this.renderer.texture.unbind(framebuffer.colorTextures[i]); - } - } - - if (framebuffer.depthTexture) - { - this.renderer.texture.unbind(framebuffer.depthTexture); - } - - if (frame) - { - this.setViewport(frame.x, frame.y, frame.width, frame.height); - } - else - { - this.setViewport(0, 0, framebuffer.width, framebuffer.height); - } - } - else - { - gl.bindFramebuffer(gl.FRAMEBUFFER, null); - - if (frame) - { - this.setViewport(frame.x, frame.y, frame.width, frame.height); - } - else - { - this.setViewport(0, 0, this.renderer.width, this.renderer.height); - } - } - } - - setViewport(x, y, width, height) - { - const v = this.viewport; - - if (v.width !== width || v.height !== height || v.x !== x || v.y !== y) - { - v.x = x; - v.y = y; - v.width = width; - v.height = height; - - this.gl.viewport(x, y, width, height); - } - } - - get size() - { - if (this.current) - { - // TODO store temp - return { x: 0, y: 0, width: this.current.width, height: this.current.height }; - } - - return { x: 0, y: 0, width: this.renderer.width, height: this.renderer.height }; - } - - clear(r, g, b, a) - { - const gl = this.gl; - - // TODO clear color can be set only one right? - gl.clearColor(r, g, b, a); - gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); - } - - // private functions... - - initFramebuffer(framebuffer) - { - const gl = this.gl; - - // TODO - make this a class? - const fbo = { - framebuffer: gl.createFramebuffer(), - stencil: null, - dirtyId: 0, - dirtyFormat: 0, - dirtySize: 0, - }; - - framebuffer.glFrameBuffers[this.CONTEXT_UID] = fbo; - - return fbo; - } - - resizeFramebuffer(framebuffer) - { - const gl = this.gl; - - if (framebuffer.stencil || framebuffer.depth) - { - gl.bindRenderbuffer(gl.RENDERBUFFER, this.stencil); - gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); - } - } - - updateFramebuffer(framebuffer) - { - const gl = this.gl; - - const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID]; - - // bind the color texture - const colorTextures = framebuffer.colorTextures; - - let count = colorTextures.length; - - if (!this.drawBufferExtension) - { - count = Math.min(count, 1); - } - - const activeTextures = []; - - for (let i = 0; i < count; i++) - { - const texture = framebuffer.colorTextures[i]; - - if (texture.texturePart) - { - this.renderer.texture.bind(texture.texture, 0); - - gl.framebufferTexture2D(gl.FRAMEBUFFER, - gl.COLOR_ATTACHMENT0 + i, - gl.TEXTURE_CUBE_MAP_NEGATIVE_X + texture.side, - texture.texture._glTextures[this.CONTEXT_UID].texture, - 0); - } - else - { - this.renderer.texture.bind(texture, 0); - - gl.framebufferTexture2D(gl.FRAMEBUFFER, - gl.COLOR_ATTACHMENT0 + i, - gl.TEXTURE_2D, - texture._glTextures[this.CONTEXT_UID].texture, - 0); - } - - activeTextures.push(gl.COLOR_ATTACHMENT0 + i); - } - - if (this.drawBufferExtension && activeTextures.length > 1) - { - this.drawBufferExtension.drawBuffersWEBGL(activeTextures); - } - - if (framebuffer.depthTexture) - { - const depthTextureExt = this.renderer.context.extensions.depthTexture; - - if (depthTextureExt) - { - const depthTexture = framebuffer.depthTexture; - - this.renderer.texture.bind(depthTexture, 0); - - gl.framebufferTexture2D(gl.FRAMEBUFFER, - gl.DEPTH_ATTACHMENT, - gl.TEXTURE_2D, - depthTexture._glTextures[this.CONTEXT_UID].texture, - 0); - } - } - - if (framebuffer.stencil || framebuffer.depth) - { - fbo.stencil = gl.createRenderbuffer(); - - gl.bindRenderbuffer(gl.RENDERBUFFER, fbo.stencil); - - // TODO.. this is depth AND stencil? - gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, fbo.stencil); - gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); - // fbo.enableStencil(); - } - } -} diff --git a/packages/core/src/renderers/systems/MaskSystem.js b/packages/core/src/renderers/systems/MaskSystem.js deleted file mode 100644 index d9c4b1a..0000000 --- a/packages/core/src/renderers/systems/MaskSystem.js +++ /dev/null @@ -1,199 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import AlphaMaskFilter from '../filters/spriteMask/SpriteMaskFilter'; - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class MaskSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - // TODO - we don't need both! - this.scissor = false; - this.scissorData = null; - this.scissorRenderTarget = null; - - this.enableScissor = false; - - this.alphaMaskPool = []; - this.alphaMaskIndex = 0; - } - - /** - * Applies the Mask and adds it to the current filter stack. - * - * @param {PIXI.DisplayObject} target - Display Object to push the mask to - * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. - */ - push(target, maskData) - { - // TODO the root check means scissor rect will not - // be used on render textures more info here: - // https://github.com/pixijs/pixi.js/pull/3545 - - if (maskData.texture) - { - this.pushSpriteMask(target, maskData); - } - else if (this.enableScissor - && !this.scissor - && this.renderer._activeRenderTarget.root - && !this.renderer.stencil.stencilMaskStack.length - && maskData.isFastRect()) - { - const matrix = maskData.worldTransform; - - let rot = Math.atan2(matrix.b, matrix.a); - - // use the nearest degree! - rot = Math.round(rot * (180 / Math.PI)); - - if (rot % 90) - { - this.pushStencilMask(maskData); - } - else - { - this.pushScissorMask(target, maskData); - } - } - else - { - this.pushStencilMask(maskData); - } - } - - /** - * Removes the last mask from the mask stack and doesn't return it. - * - * @param {PIXI.DisplayObject} target - Display Object to pop the mask from - * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. - */ - pop(target, maskData) - { - if (maskData.texture) - { - this.popSpriteMask(target, maskData); - } - else if (this.enableScissor && !this.renderer.stencil.stencilMaskStack.length) - { - this.popScissorMask(target, maskData); - } - else - { - this.popStencilMask(target, maskData); - } - } - - /** - * Applies the Mask and adds it to the current filter stack. - * - * @param {PIXI.RenderTarget} target - Display Object to push the sprite mask to - * @param {PIXI.Sprite} maskData - Sprite to be used as the mask - */ - pushSpriteMask(target, maskData) - { - let alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; - - if (!alphaMaskFilter) - { - alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; - } - - alphaMaskFilter[0].resolution = this.renderer.resolution; - alphaMaskFilter[0].maskSprite = maskData; - - // TODO - may cause issues! - target.filterArea = maskData.getBounds(true); - - this.renderer.filterSystem.pushFilter(target, alphaMaskFilter); - - this.alphaMaskIndex++; - } - - /** - * Removes the last filter from the filter stack and doesn't return it. - * - */ - popSpriteMask() - { - this.renderer.filterSystem.popFilter(); - this.alphaMaskIndex--; - } - - /** - * Applies the Mask and adds it to the current filter stack. - * - * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. - */ - pushStencilMask(maskData) - { - this.renderer.batch.flush(); - this.renderer.stencil.pushStencil(maskData); - } - - /** - * Removes the last filter from the filter stack and doesn't return it. - * - */ - popStencilMask() - { - // this.renderer.currentRenderer.stop(); - this.renderer.stencil.popStencil(); - } - - /** - * - * @param {PIXI.DisplayObject} target - Display Object to push the mask to - * @param {PIXI.Graphics} maskData - The masking data. - */ - pushScissorMask(target, maskData) - { - maskData.renderable = true; - - const renderTarget = this.renderer._activeRenderTarget; - - const bounds = maskData.getBounds(); - - bounds.fit(renderTarget.size); - maskData.renderable = false; - - this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); - - const resolution = this.renderer.resolution; - - this.renderer.gl.scissor( - bounds.x * resolution, - (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, - bounds.width * resolution, - bounds.height * resolution - ); - - this.scissorRenderTarget = renderTarget; - this.scissorData = maskData; - this.scissor = true; - } - - /** - * - * - */ - popScissorMask() - { - this.scissorRenderTarget = null; - this.scissorData = null; - this.scissor = false; - - // must be scissor! - const gl = this.renderer.gl; - - gl.disable(gl.SCISSOR_TEST); - } -} diff --git a/packages/core/src/renderers/systems/ProjectionSystem.js b/packages/core/src/renderers/systems/ProjectionSystem.js deleted file mode 100644 index ec5efdb..0000000 --- a/packages/core/src/renderers/systems/ProjectionSystem.js +++ /dev/null @@ -1,73 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { Matrix } from '@pixi/math'; - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ - -export default class ProjectionSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this.projectionMatrix = new Matrix(); - } - - update(destinationFrame, sourceFrame, resolution, root) - { - this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; - this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; - - this.calculateProjection(this.destinationFrame, this.sourceFrame, resolution, root); - - this.renderer.globalUniforms.uniforms.projectionMatrix = this.projectionMatrix; - this.renderer.globalUniforms.update(); - } - - /** - * Updates the projection matrix based on a projection frame (which is a rectangle) - * - * @param {Rectangle} destinationFrame - The destination frame. - * @param {Rectangle} sourceFrame - The source frame. - */ - calculateProjection(destinationFrame, sourceFrame, resolution, root) - { - const pm = this.projectionMatrix; - - // I don't think we will need this line.. - // pm.identity(); - - if (!root) - { - pm.a = (1 / destinationFrame.width * 2) * resolution; - pm.d = (1 / destinationFrame.height * 2) * resolution; - - pm.tx = -1 - (sourceFrame.x * pm.a); - pm.ty = -1 - (sourceFrame.y * pm.d); - } - else - { - pm.a = (1 / destinationFrame.width * 2) * resolution; - pm.d = (-1 / destinationFrame.height * 2) * resolution; - - pm.tx = -1 - (sourceFrame.x * pm.a); - pm.ty = 1 - (sourceFrame.y * pm.d); - } - } - - /** - * Sets the transform of the active render target to the given matrix - * - * @param {PIXI.Matrix} matrix - The transformation matrix - */ - setTransform()// matrix) - { - // this._activeRenderTarget.transform = matrix; - } -} diff --git a/packages/core/src/renderers/systems/RenderTextureSystem.js b/packages/core/src/renderers/systems/RenderTextureSystem.js deleted file mode 100644 index a133ef1..0000000 --- a/packages/core/src/renderers/systems/RenderTextureSystem.js +++ /dev/null @@ -1,115 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { Rectangle } from '@pixi/math'; - -const tempRect = new Rectangle(); - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ - -export default class RenderTextureSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this.clearColor = renderer._backgroundColorRgba; - - // TODO moe this property somewhere else! - this.defaultMaskStack = []; - this.defaultFilterStack = [{}]; - - // empty render texture? - this.renderTexture = null; - - this.destinationFrame = new Rectangle(); - } - - bind(renderTexture, sourceFrame, destinationFrame) - { - // TODO - do we want this?? - if (this.renderTexture === renderTexture) return; - this.renderTexture = renderTexture; - - const renderer = this.renderer; - - if (renderTexture) - { - const baseTexture = renderTexture.baseTexture; - - if (!destinationFrame) - { - tempRect.width = baseTexture.realWidth; - tempRect.height = baseTexture.realHeight; - - destinationFrame = tempRect; - } - - if (!sourceFrame) - { - sourceFrame = destinationFrame; - } - - this.renderer.framebuffer.bind(baseTexture.frameBuffer, destinationFrame); - - this.renderer.projection.update(destinationFrame, sourceFrame, baseTexture.resolution, false); - this.renderer.stencil.setMaskStack(baseTexture.stencilMaskStack); - } - else - { - // TODO these validation checks happen deeper down.. - // thing they can be avoided.. - if (!destinationFrame) - { - tempRect.width = renderer.width; - tempRect.height = renderer.height; - - destinationFrame = tempRect; - } - - if (!sourceFrame) - { - sourceFrame = destinationFrame; - } - - renderer.framebuffer.bind(null, destinationFrame); - - // TODO store this.. - this.renderer.projection.update(destinationFrame, sourceFrame, this.renderer.resolution, true); - this.renderer.stencil.setMaskStack(this.defaultMaskStack); - } - - this.destinationFrame.copyFrom(destinationFrame); - } - - /** - * Erases the render texture and fills the drawing area with a colour - * - * @param {number} [clearColor] - The colour - * @return {PIXI.Renderer} Returns itself. - */ - clear(clearColor) - { - if (this.renderTexture) - { - clearColor = clearColor || this.renderTexture.baseTexture.clearColor; - } - else - { - clearColor = clearColor || this.clearColor; - } - - this.renderer.framebuffer.clear(clearColor[0], clearColor[1], clearColor[2], clearColor[3]); - } - - resize()// screenWidth, screenHeight) - { - // resize the root only! - this.bind(null); - } -} diff --git a/packages/core/src/renderers/systems/StateSystem.js b/packages/core/src/renderers/systems/StateSystem.js deleted file mode 100755 index d7f4caf..0000000 --- a/packages/core/src/renderers/systems/StateSystem.js +++ /dev/null @@ -1,288 +0,0 @@ -import mapWebGLBlendModesToPixi from '../utils/mapWebGLBlendModesToPixi'; -import WebGLSystem from './WebGLSystem'; -import WebGLState from '../State'; - -const BLEND = 0; -const OFFSET = 1; -const CULLING = 2; -const DEPTH_TEST = 3; -const WINDING = 4; - -/** - * A WebGL state machines - * - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class StateSystem extends WebGLSystem -{ - /** - * @param {WebGLRenderingContext} gl - The current WebGL rendering context - */ - constructor(renderer) - { - super(renderer); - - this.gl = null; - - this.maxAttribs = null; - - // check we have vao.. - this.nativeVaoExtension = null; - - this.attribState = null; - - this.stateId = 0; - this.polygonOffset = 0; - this.blendMode = 17; - - this.map = []; - - // map functions for when we set state.. - this.map[BLEND] = this.setBlend; - this.map[OFFSET] = this.setOffset; - this.map[CULLING] = this.setCullFace; - this.map[DEPTH_TEST] = this.setDepthTest; - this.map[WINDING] = this.setFrontFace; - - this.checks = []; - - this.defaultState = new WebGLState(); - this.defaultState.blend = true; - this.defaultState.depth = true; - } - - contextChange(gl) - { - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; - - this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); - - // check we have vao.. - this.nativeVaoExtension = ( - gl.getExtension('OES_vertex_array_object') - || gl.getExtension('MOZ_OES_vertex_array_object') - || gl.getExtension('WEBKIT_OES_vertex_array_object') - ); - - this.attribState = { - tempAttribState: new Array(this.maxAttribs), - attribState: new Array(this.maxAttribs), - }; - - this.blendModes = mapWebGLBlendModesToPixi(gl); - - this.setState(this.defaultState); - - this.reset(); - } - - /** - * Sets the current state - * - * @param {*} state - The state to set. - */ - setState(state) - { - state = state || this.defaultState; - - // TODO maybe to an object check? ( this.state === state )? - if (this.stateId !== state.data) - { - let diff = this.stateId ^ state.data; - let i = 0; - - // order from least to most common - while (diff) - { - if (diff & 1) - { - // state change! - this.map[i].call(this, !!(state.data & (1 << i))); - } - - diff = diff >> 1; - i++; - } - - this.stateId = state.data; - } - - // based on the above settings we check for specific modes.. - // for example if blend is active we check and set the blend modes - // or of polygon offset is active we check the poly depth. - for (let i = 0; i < this.checks.length; i++) - { - this.checks[i](this, state); - } - } - - /** - * Enables or disabled blending. - * - * @param {boolean} value - Turn on or off webgl blending. - */ - setBlend(value) - { - this.updateCheck(StateSystem.checkBlendMode, value); - - this.gl[value ? 'enable' : 'disable'](this.gl.BLEND); - } - - /** - * Enables or disable polygon offset fill - * - * @param {boolean} value - Turn on or off webgl polygon offset testing. - */ - setOffset(value) - { - this.gl[value ? 'enable' : 'disable'](this.gl.POLYGON_OFFSET_FILL); - } - - /** - * Sets whether to enable or disable depth test. - * - * @param {boolean} value - Turn on or off webgl depth testing. - */ - setDepthTest(value) - { - this.gl[value ? 'enable' : 'disable'](this.gl.DEPTH_TEST); - } - - /** - * Sets whether to enable or disable cull face. - * - * @param {boolean} value - Turn on or off webgl cull face. - */ - setCullFace(value) - { - this.gl[value ? 'enable' : 'disable'](this.gl.CULL_FACE); - } - - /** - * Sets the gl front face. - * - * @param {boolean} value - true is clockwise and false is counter-clockwise - */ - setFrontFace(value) - { - this.gl.frontFace(this.gl[value ? 'CW' : 'CCW']); - } - - /** - * Sets the blend mode. - * - * @param {number} value - The blend mode to set to. - */ - setBlendMode(value) - { - if (value === this.blendMode) - { - return; - } - - this.blendMode = value; - this.gl.blendFunc(this.blendModes[value][0], this.blendModes[value][1]); - } - - /** - * Sets the polygon offset. - * - * @param {number} value - the polygon offset - * @param {number} scale - the polygon offset scale - */ - setPolygonOffset(value, scale) - { - this.gl.polygonOffset(value, scale); - } - - /** - * Disables all the vaos in use - * - */ - resetAttributes() - { - for (let i = 0; i < this.attribState.tempAttribState.length; i++) - { - this.attribState.tempAttribState[i] = 0; - } - - for (let i = 0; i < this.attribState.attribState.length; i++) - { - this.attribState.attribState[i] = 0; - } - - // im going to assume one is always active for performance reasons. - for (let i = 1; i < this.maxAttribs; i++) - { - this.gl.disableVertexAttribArray(i); - } - } - - // used - /** - * Resets all the logic and disables the vaos - */ - reset() - { - // unbind any VAO if they exist.. - if (this.nativeVaoExtension) - { - this.nativeVaoExtension.bindVertexArrayOES(null); - } - - // reset all attributes.. - this.resetAttributes(); - - this.gl.pixelStorei(this.gl.UNPACK_FLIP_Y_WEBGL, false); - - this.setBlendMode(0); - - // TODO? - // this.setState(this.defaultState); - } - - /** - * checks to see which updates should be checked based on which settings have been activated - * for example if blend is enabled then we shold check the blend modes each time the state is changed - * or if poygon fill is activated then we need to check if the polygone offset changes. - * The idea is that we only check what we have too. - * - * @param {Function} func the checking function to add or remove - * @param {boolean} value should the check function be added or removed. - */ - updateCheck(func, value) - { - const index = this.checks.indexOf(func); - - if (value && index === -1) - { - this.checks.push(func); - } - else if (!value && index !== -1) - { - this.checks.splice(index, 1); - } - } - - /** - * A private little wrapper function that we call to check the blend mode. - * - * @static - * @private - * @param {PIXI.StateSystem} System the System to perform the state check on - * @param {PIXI.State} state the state that the blendMode will pulled from - */ - static checkBlendMode(system, state) - { - system.setBlendMode(state.blendMode); - } - - // TODO - add polygon offset? -} diff --git a/packages/core/src/renderers/systems/StencilSystem.js b/packages/core/src/renderers/systems/StencilSystem.js deleted file mode 100644 index 5d538cb..0000000 --- a/packages/core/src/renderers/systems/StencilSystem.js +++ /dev/null @@ -1,133 +0,0 @@ -import WebGLSystem from './WebGLSystem'; - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class StencilSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - this.stencilMaskStack = []; - } - - /** - * Changes the mask stack that is used by this System. - * - * @param {PIXI.Graphics[]} stencilMaskStack - The mask stack - */ - setMaskStack(stencilMaskStack) - { - const gl = this.renderer.gl; - - if (stencilMaskStack.length !== this.stencilMaskStack.length) - { - if (stencilMaskStack.length === 0) - { - gl.disable(gl.STENCIL_TEST); - } - else - { - gl.enable(gl.STENCIL_TEST); - } - } - - this.stencilMaskStack = stencilMaskStack; - } - - /** - * Applies the Mask and adds it to the current stencil stack. @alvin - * - * @param {PIXI.Graphics} graphics - The mask - */ - pushStencil(graphics) - { - this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); - - // this.renderer._activeRenderTarget.attachStencilBuffer(); - - const gl = this.renderer.gl; - const prevMaskCount = this.stencilMaskStack.length; - - if (prevMaskCount === 0) - { - gl.enable(gl.STENCIL_TEST); - } - - this.stencilMaskStack.push(graphics); - - // Increment the refference stencil value where the new mask overlaps with the old ones. - gl.colorMask(false, false, false, false); - gl.stencilFunc(gl.EQUAL, prevMaskCount, this._getBitwiseMask()); - gl.stencilOp(gl.KEEP, gl.KEEP, gl.INCR); - this.renderer.plugins.graphics.render(graphics); - - this._useCurrent(); - } - - /** - * Removes the last mask from the stencil stack. @alvin - */ - popStencil() - { - this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); - - const gl = this.renderer.gl; - const graphics = this.stencilMaskStack.pop(); - - if (this.stencilMaskStack.length === 0) - { - // the stack is empty! - gl.disable(gl.STENCIL_TEST); - gl.clear(gl.STENCIL_BUFFER_BIT); - gl.clearStencil(0); - } - else - { - // Decrement the refference stencil value where the popped mask overlaps with the other ones - gl.colorMask(false, false, false, false); - gl.stencilOp(gl.KEEP, gl.KEEP, gl.DECR); - this.renderer.plugins.graphics.render(graphics); - - this._useCurrent(); - } - } - - /** - * Setup renderer to use the current stencil data. - */ - _useCurrent() - { - const gl = this.renderer.gl; - - gl.colorMask(true, true, true, true); - gl.stencilFunc(gl.EQUAL, this.stencilMaskStack.length, this._getBitwiseMask()); - gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP); - } - - /** - * Fill 1s equal to the number of acitve stencil masks. - * - * @return {number} The bitwise mask. - */ - _getBitwiseMask() - { - return (1 << this.stencilMaskStack.length) - 1; - } - - /** - * Destroys the mask stack. - * - */ - destroy() - { - super.destroy(this); - - this.stencilMaskStack = null; - } -} diff --git a/packages/core/src/renderers/systems/WebGLSystem.js b/packages/core/src/renderers/systems/WebGLSystem.js deleted file mode 100644 index d3d5b31..0000000 --- a/packages/core/src/renderers/systems/WebGLSystem.js +++ /dev/null @@ -1,41 +0,0 @@ -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this manager works for. - */ - constructor(renderer) - { - /** - * The renderer this manager works for. - * - * @member {PIXI.Renderer} - */ - this.renderer = renderer; - - this.renderer.runners.contextChange.add(this); - } - - /** - * Generic method called when there is a WebGL context change. - * - */ - contextChange() - { - // do some codes init! - } - - /** - * Generic destroy methods to be overridden by the subclass - * - */ - destroy() - { - this.renderer.runners.contextChange.remove(this); - this.renderer = null; - } -} diff --git a/packages/core/src/renderers/systems/filter/FilterSystem.js b/packages/core/src/renderers/systems/filter/FilterSystem.js deleted file mode 100644 index e615037..0000000 --- a/packages/core/src/renderers/systems/filter/FilterSystem.js +++ /dev/null @@ -1,431 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; - -import RenderTexture from '../../../textures/RenderTexture'; -import Quad from '../../utils/Quad'; -import QuadUv from '../../utils/QuadUv'; -import { Rectangle } from '@pixi/math'; -import * as filterTransforms from '../../filters/filterTransforms'; -import bitTwiddle from 'bit-twiddle'; -import UniformGroup from '../../../shader/UniformGroup'; -import { DRAW_MODES } from '../../../../../constants'; - -// -/** - * @ignore - * @class - */ -class FilterState -{ - /** - * - */ - constructor() - { - this.renderTexture = null; - this.sourceFrame = new Rectangle(); - this.destinationFrame = new Rectangle(); - this.filters = []; - this.target = null; - this.legacy = false; - this.resolution = 1; - } -} - -/** - * @class - * @memberof PIXI.systems - * @extends PIXI.systems.WebGLSystem - */ -export default class FilterSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - /** - * stores a bunch of PO2 textures used for filtering - * @type {Object} - */ - this.texturePool = {}; - - /** - * a pool for storing filter states, save us creating new ones each tick - * @type {Array} - */ - this.statePool = []; - - /** - * A very simple geometry used when drawing a filter effect to the screen - * @type {Quad} - */ - this.quad = new Quad(); - - this.quadUv = new QuadUv(); - - /** - * Temporary rect for maths - * @type {PIXI.Rectangle} - */ - this.tempRect = new Rectangle(); - - this.activeState = {}; - - /** - * this uniform group is attached to filter uniforms when used - * @type {UniformGroup} - */ - this.globalUniforms = new UniformGroup({ - sourceFrame: this.tempRect, - destinationFrame: this.tempRect, - - // legacy variables - filterArea: new Float32Array(4), - filterClamp: new Float32Array(4), - }, true); - } - - /** - * Adds a new filter to the System. - * - * @param {PIXI.DisplayObject} target - The target of the filter to render. - * @param {PIXI.Filter[]} filters - The filters to apply. - */ - push(target, filters) - { - const renderer = this.renderer; - const filterStack = this.renderer.renderTexture.defaultFilterStack; - const state = this.statePool.pop() || new FilterState(); - - let resolution = filters[0].resolution; - let padding = filters[0].padding; - let autoFit = filters[0].autoFit; - let legacy = filters[0].legacy; - - for (let i = 1; i < filters.length; i++) - { - const filter = filters[i]; - - // lets use the lowest resolution.. - resolution = Math.min(resolution, filter.resolution); - // and the largest amount of padding! - padding = Math.max(padding, filter.padding); - // only auto fit if all filters are autofit - autoFit = autoFit || filter.autoFit; - - legacy = legacy || filter.legacy; - } - - filterStack.push(state); - - state.resolution = resolution; - - state.legacy = legacy; - - // round to whole number based on resolution - // TODO move that to the shader too? - state.sourceFrame = target.filterArea || target.getBounds(true); - - state.sourceFrame.pad(padding); - - if (autoFit) - { - state.sourceFrame.fit(this.renderer.renderTexture.destinationFrame); - } - - state.sourceFrame.round(resolution); - - state.renderTexture = this.getPotFilterTexture(state.sourceFrame.width, state.sourceFrame.height, resolution); - state.filters = filters; - - state.destinationFrame.width = state.renderTexture.width; - state.destinationFrame.height = state.renderTexture.height; - - state.renderTexture.filterFrame = state.sourceFrame; - renderer.renderTexture.bind(state.renderTexture, state.sourceFrame);// /, state.destinationFrame); - renderer.renderTexture.clear(); - } - - /** - * Pops off the filter and applies it. - * - */ - pop() - { - const renderer = this.renderer; - const filterStack = renderer.renderTexture.defaultFilterStack; - const state = filterStack.pop(); - const filters = state.filters; - - this.activeState = state; - - const globalUniforms = this.globalUniforms.uniforms; - - globalUniforms.sourceFrame = state.sourceFrame; - globalUniforms.destinationFrame = state.destinationFrame; - globalUniforms.resolution = state.resolution; - - // only update the rect if its legacy.. - if (state.legacy) - { - const filterArea = globalUniforms.filterArea; - const filterClamp = globalUniforms.filterClamp; - - filterArea[0] = state.destinationFrame.width; - filterArea[1] = state.destinationFrame.height; - filterArea[2] = state.sourceFrame.x; - filterArea[3] = state.sourceFrame.y; - - filterClamp[0] = 0.5 / state.resolution / state.destinationFrame.width; - filterClamp[1] = 0.5 / state.resolution / state.destinationFrame.height; - filterClamp[2] = (state.sourceFrame.width - 0.5) / state.resolution / state.destinationFrame.width; - filterClamp[3] = (state.sourceFrame.height - 0.5) / state.resolution / state.destinationFrame.height; - } - - this.globalUniforms.update(); - - const lastState = filterStack[filterStack.length - 1]; - - if (filters.length === 1) - { - filters[0].apply(this, state.renderTexture, lastState.renderTexture, false, state); - - this.returnFilterTexture(state.renderTexture); - } - else - { - let flip = state.renderTexture; - let flop = this.getPotFilterTexture( - flip.width, - flip.height, - state.resolution - ); - - flop.filterFrame = flip.filterFrame; - - let i = 0; - - for (i = 0; i < filters.length - 1; ++i) - { - filters[i].apply(this, flip, flop, true, state); - - const t = flip; - - flip = flop; - flop = t; - } - - filters[i].apply(this, flip, lastState.renderTexture, false, state); - - this.returnFilterTexture(flip); - this.returnFilterTexture(flop); - } - - this.statePool.push(state); - } - - /** - * Draws a filter. - * - * @param {PIXI.Filter} filter - The filter to draw. - * @param {PIXI.RenderTarget} input - The input render target. - * @param {PIXI.RenderTarget} output - The target to output to. - * @param {boolean} clear - Should the output be cleared before rendering to it - */ - applyFilter(filter, input, output, clear) - { - const renderer = this.renderer; - - renderer.renderTexture.bind(output, output ? output.filterFrame : null); - - if (clear) - { - // gl.disable(gl.SCISSOR_TEST); - renderer.renderTexture.clear(); - // gl.enable(gl.SCISSOR_TEST); - } - - // set the uniforms.. - filter.uniforms.uSampler = input; - filter.uniforms.filterGlobals = this.globalUniforms; - - // TODO make it so that the order of this does not matter.. - // because it does at the moment cos of global uniforms. - // they need to get resynced - - renderer.state.setState(filter.state); - renderer.shader.bind(filter); - - if (filter.legacy) - { - this.quadUv.map(input._frame, input.filterFrame); - - renderer.geometry.bind(this.quadUv); - renderer.geometry.draw(DRAW_MODES.TRIANGLES); - } - else - { - renderer.geometry.bind(this.quad); - renderer.geometry.draw(DRAW_MODES.TRIANGLE_STRIP); - } - } - - /** - * Calculates the mapped matrix. - * - * TODO playing around here.. this is temporary - (will end up in the shader) - * this returns a matrix that will normalise map filter cords in the filter to screen space - * - * @param {PIXI.Matrix} outputMatrix - the matrix to output to. - * @return {PIXI.Matrix} The mapped matrix. - */ - calculateScreenSpaceMatrix(outputMatrix) - { - const currentState = this.activeState; - - return filterTransforms.calculateScreenSpaceMatrix( - outputMatrix, - currentState.sourceFrame, - currentState.destinationFrame - ); - } - - /** - * This will map the filter coord so that a texture can be used based on the transform of a sprite - * - * @param {PIXI.Matrix} outputMatrix - The matrix to output to. - * @param {PIXI.Sprite} sprite - The sprite to map to. - * @return {PIXI.Matrix} The mapped matrix. - */ - calculateSpriteMatrix(outputMatrix, sprite) - { - const currentState = this.activeState; - - return filterTransforms.calculateSpriteMatrix( - outputMatrix, - currentState.sourceFrame, - currentState.destinationFrame, - sprite - ); - } - - /** - * Destroys this Filter System. - * - * @param {boolean} [contextLost=false] context was lost, do not free shaders - * - */ - destroy(contextLost = false) - { - if (!contextLost) - { - this.emptyPool(); - } - else - { - this.texturePool = {}; - } - } - - /** - * Gets a Power-of-Two render texture. - * - * TODO move to a seperate class could be on renderer? - * also - could cause issue with multiple contexts? - * - * @private - * @param {WebGLRenderingContext} gl - The webgl rendering context - * @param {number} minWidth - The minimum width of the render target. - * @param {number} minHeight - The minimum height of the render target. - * @param {number} resolution - The resolution of the render target. - * @return {PIXI.RenderTarget} The new render target. - */ - getPotFilterTexture(minWidth, minHeight, resolution) - { - minWidth = bitTwiddle.nextPow2(minWidth); - minHeight = bitTwiddle.nextPow2(minHeight); - resolution = resolution || 1; - - const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); - - if (!this.texturePool[key]) - { - this.texturePool[key] = []; - } - - let renderTexture = this.texturePool[key].pop(); - - if (!renderTexture) - { - // temporary bypass cache.. - // internally - this will cause a texture to be bound.. - renderTexture = RenderTexture.create({ - width: minWidth, - height: minHeight, - resolution, - }); - } - - return renderTexture; - } - - /** - * Gets extra render texture to use inside current filter - * - * @param {number} resolution resolution of the renderTexture - * @returns {PIXI.RenderTexture} - */ - getFilterTexture(resolution) - { - const rt = this.activeState.renderTexture; - - const filterTexture = this.getPotFilterTexture(rt.width, rt.height, resolution || rt.baseTexture.resolution); - - filterTexture.filterFrame = rt.filterFrame; - - return filterTexture; - } - - /** - * Frees a render texture back into the pool. - * - * @param {PIXI.RenderTarget} renderTexture - The renderTarget to free - */ - returnFilterTexture(renderTexture) - { - renderTexture.filterFrame = null; - - const base = renderTexture.baseTexture; - - const minWidth = base.width; - const minHeight = base.height; - - const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); - - this.texturePool[key].push(renderTexture); - } - - /** - * Empties the texture pool. - * - */ - emptyPool() - { - for (const i in this.texturePool) - { - const textures = this.texturePool[i]; - - if (textures) - { - for (let j = 0; j < textures.length; j++) - { - textures[j].destroy(true); - } - } - } - - this.texturePool = {}; - } -} diff --git a/packages/core/src/renderers/systems/geometry/GLBuffer.js b/packages/core/src/renderers/systems/geometry/GLBuffer.js deleted file mode 100644 index 0477e8a..0000000 --- a/packages/core/src/renderers/systems/geometry/GLBuffer.js +++ /dev/null @@ -1,9 +0,0 @@ -export default class GLBuffer -{ - constructor(buffer) - { - this.buffer = buffer; - this.updateID = -1; - this.byteLength = -1; - } -} diff --git a/packages/core/src/renderers/systems/geometry/GeometrySystem.js b/packages/core/src/renderers/systems/geometry/GeometrySystem.js deleted file mode 100644 index 26c8e9e..0000000 --- a/packages/core/src/renderers/systems/geometry/GeometrySystem.js +++ /dev/null @@ -1,388 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; -import GLBuffer from './GLBuffer'; - -const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class GeometrySystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this._activeGeometry = null; - this._activeVao = null; - - this.hasVao = true; - this.hasInstance = true; - } - - /** - * Sets up the renderer context and necessary buffers. - * - * @private - */ - contextChange() - { - const gl = this.gl = this.renderer.gl; - - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - - // webgl2 - if (!gl.createVertexArray) - { - // webgl 1! - let nativeVaoExtension = this.renderer.context.extensions.vertexArrayObject; - - if (this.renderer.legacy) - { - nativeVaoExtension = null; - } - - if (nativeVaoExtension) - { - gl.createVertexArray = () => - nativeVaoExtension.createVertexArrayOES(); - - gl.bindVertexArray = (vao) => - nativeVaoExtension.bindVertexArrayOES(vao); - - gl.deleteVertexArray = (vao) => - nativeVaoExtension.deleteVertexArrayOES(vao); - } - else - { - this.hasVao = false; - gl.createVertexArray = () => - { - // empty - }; - - gl.bindVertexArray = () => - { - // empty - }; - - gl.deleteVertexArray = () => - { - // empty - }; - } - } - - if (!gl.vertexAttribDivisor) - { - const instanceExt = gl.getExtension('ANGLE_instanced_arrays'); - - if (instanceExt) - { - gl.vertexAttribDivisor = (a, b) => - instanceExt.vertexAttribDivisorANGLE(a, b); - - gl.drawElementsInstanced = (a, b, c, d, e) => - instanceExt.drawElementsInstancedANGLE(a, b, c, d, e); - - gl.drawArraysInstanced = (a, b, c, d) => - instanceExt.drawArraysInstancedANGLE(a, b, c, d); - } - else - { - this.hasInstance = false; - } - } - } - - /** - * Binds geometry so that is can be drawn. Creating a Vao if required - * @private - * @param {PIXI.Geometry} geometry instance of geometry to bind - */ - bind(geometry, shader) - { - shader = shader || this.renderer.shader.shader; - - const gl = this.gl; - - // not sure the best way to address this.. - // currently different shaders require different VAOs for the same geometry - // Still mulling over the best way to solve this one.. - // will likely need to modify the shader attribute locations at run time! - let vaos = geometry.glVertexArrayObjects[this.CONTEXT_UID]; - - if (!vaos) - { - geometry.glVertexArrayObjects[this.CONTEXT_UID] = vaos = {}; - } - - const vao = vaos[shader.program.id] || this.initGeometryVao(geometry, shader.program); - - this._activeGeometry = geometry; - - if (this._activeVao !== vao) - { - this._activeVao = vao; - - if (this.hasVao) - { - gl.bindVertexArray(vao); - } - else - { - this.activateVao(geometry, shader.program); - } - } - - // TODO - optimise later! - // don't need to loop through if nothing changed! - // maybe look to add an 'autoupdate' to geometry? - this.updateBuffers(); - } - - reset() - { - this.unbind(); - } - - updateBuffers() - { - const geometry = this._activeGeometry; - const gl = this.gl; - - for (let i = 0; i < geometry.buffers.length; i++) - { - const buffer = geometry.buffers[i]; - - const glBuffer = buffer._glBuffers[this.CONTEXT_UID]; - - if (buffer._updateID !== glBuffer.updateID) - { - glBuffer.updateID = buffer._updateID; - - // TODO can cache this on buffer! maybe added a getter / setter? - const type = buffer.index ? gl.ELEMENT_ARRAY_BUFFER : gl.ARRAY_BUFFER; - const drawType = buffer.static ? gl.STATIC_DRAW : gl.DYNAMIC_DRAW; - - gl.bindBuffer(type, glBuffer.buffer); - - if (glBuffer.byteLength >= buffer.data.byteLength) - { - // offset is always zero for now! - gl.bufferSubData(type, 0, buffer.data); - } - else - { - gl.bufferData(type, buffer.data, drawType); - } - } - } - } - - checkCompatability(geometry, program) - { - // geometry must have at least all the attributes that the shader requires. - const geometryAttributes = geometry.attributes; - const shaderAttributes = program.attributeData; - - for (const j in shaderAttributes) - { - if (!geometryAttributes[j]) - { - throw new Error(`shader and geometry incompatible, geometry missing the "${j}" attribute`); - } - } - } - - /** - * Creates a Vao with the same structure as the geometry and stores it on the geometry. - * @private - * @param {PIXI.Geometry} geometry instance of geometry to to generate Vao for - */ - initGeometryVao(geometry, program) - { - this.checkCompatability(geometry, program); - - const gl = this.gl; - const CONTEXT_UID = this.CONTEXT_UID; - const buffers = geometry.buffers; - const attributes = geometry.attributes; - - const tempStride = {}; - const tempStart = {}; - - for (const j in buffers) - { - tempStride[j] = 0; - tempStart[j] = 0; - } - - for (const j in attributes) - { - if (!attributes[j].size && program.attributeData[j]) - { - attributes[j].size = program.attributeData[j].size; - } - - tempStride[attributes[j].buffer] += attributes[j].size * byteSizeMap[attributes[j].type]; - } - - for (const j in attributes) - { - const attribute = attributes[j]; - const attribSize = attribute.size; - - if (attribute.stride === undefined) - { - if (tempStride[attribute.buffer] === attribSize * byteSizeMap[attribute.type]) - { - attribute.stride = 0; - } - else - { - attribute.stride = tempStride[attribute.buffer]; - } - } - - if (attribute.start === undefined) - { - attribute.start = tempStart[attribute.buffer]; - - tempStart[attribute.buffer] += attribSize * byteSizeMap[attribute.type]; - } - } - - // first update - and create the buffers! - // only create a gl buffer if it actually gets - for (let i = 0; i < buffers.length; i++) - { - const buffer = buffers[i]; - - if (!buffer._glBuffers[CONTEXT_UID]) - { - buffer._glBuffers[CONTEXT_UID] = new GLBuffer(gl.createBuffer()); - } - } - - // TODO - maybe make this a data object? - // lets wait to see if we need to first! - const vao = gl.createVertexArray(); - - gl.bindVertexArray(vao); - - this.activateVao(geometry, program); - - gl.bindVertexArray(null); - - // add it to the cache! - geometry.glVertexArrayObjects[this.CONTEXT_UID][program.id] = vao; - - return vao; - } - - activateVao(geometry, program) - { - const gl = this.gl; - const CONTEXT_UID = this.CONTEXT_UID; - const buffers = geometry.buffers; - const attributes = geometry.attributes; - - if (geometry.indexBuffer) - { - // first update the index buffer if we have one.. - gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, geometry.indexBuffer._glBuffers[CONTEXT_UID].buffer); - } - - let lastBuffer = null; - - // add a new one! - for (const j in attributes) - { - const attribute = attributes[j]; - const buffer = buffers[attribute.buffer]; - const glBuffer = buffer._glBuffers[CONTEXT_UID]; - - if (program.attributeData[j]) - { - if (lastBuffer !== glBuffer) - { - gl.bindBuffer(gl.ARRAY_BUFFER, glBuffer.buffer); - - lastBuffer = glBuffer; - } - - const location = program.attributeData[j].location; - - // TODO introduce state again - // we can optimise this for older devices that have no VAOs - gl.enableVertexAttribArray(location); - - gl.vertexAttribPointer(location, - attribute.size, - attribute.type || gl.FLOAT, - attribute.normalized, - attribute.stride, - attribute.start); - - if (attribute.instance) - { - // TODO calculate instance count based of this... - if (this.hasInstance) - { - gl.vertexAttribDivisor(location, 1); - } - else - { - throw new Error('geometry error, GPU Instancing is not supported on this device'); - } - } - } - } - } - - draw(type, size, start, instanceCount) - { - const gl = this.gl; - const geometry = this._activeGeometry; - - // TODO.. this should not change so maybe cache the function? - - if (geometry.indexBuffer) - { - if (geometry.instanced) - { - /* eslint-disable max-len */ - gl.drawElementsInstanced(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2, instanceCount || 1); - /* eslint-enable max-len */ - } - else - { - gl.drawElements(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2); - } - } - else - if (geometry.instanced) - { - // TODO need a better way to calculate size.. - gl.drawArraysInstanced(type, start, size || geometry.getSize(), instanceCount || 1); - } - else - { - gl.drawArrays(type, start, size || geometry.getSize()); - } - - return this; - } - - unbind() - { - this.gl.bindVertexArray(null); - this._activeVao = null; - this._activeGeometry = null; - } -} diff --git a/packages/core/src/renderers/systems/geometry/setVertexAttribArrays.js b/packages/core/src/renderers/systems/geometry/setVertexAttribArrays.js deleted file mode 100644 index 23b6959..0000000 --- a/packages/core/src/renderers/systems/geometry/setVertexAttribArrays.js +++ /dev/null @@ -1,54 +0,0 @@ -// var GL_MAP = {}; - -/** - * @param gl {WebGLRenderingContext} The current WebGL context - * @param attribs {*} - * @param state {*} - */ -export default function setVertexAttribArrays(gl, attribs, state) -{ - let i; - - if (state) - { - const tempAttribState = state.tempAttribState; - const attribState = state.attribState; - - for (i = 0; i < tempAttribState.length; i++) - { - tempAttribState[i] = false; - } - - // set the new attribs - for (i = 0; i < attribs.length; i++) - { - tempAttribState[attribs[i].attribute.location] = true; - } - - for (i = 0; i < attribState.length; i++) - { - if (attribState[i] !== tempAttribState[i]) - { - attribState[i] = tempAttribState[i]; - - if (state.attribState[i]) - { - gl.enableVertexAttribArray(i); - } - else - { - gl.disableVertexAttribArray(i); - } - } - } - } - else - { - for (i = 0; i < attribs.length; i++) - { - const attrib = attribs[i]; - - gl.enableVertexAttribArray(attrib.attribute.location); - } - } -} diff --git a/packages/core/src/renderers/systems/index.js b/packages/core/src/renderers/systems/index.js deleted file mode 100644 index c843f03..0000000 --- a/packages/core/src/renderers/systems/index.js +++ /dev/null @@ -1,6 +0,0 @@ -/** - * Systems are individual components to the Renderer pipeline. - * @namespace PIXI.systems - */ -export { default as WebGLSystem } from './WebGLSystem'; -export { default as FilterSystem } from './filter/FilterSystem'; diff --git a/packages/core/src/renderers/systems/shader/GLProgram.js b/packages/core/src/renderers/systems/shader/GLProgram.js deleted file mode 100644 index 92980ca..0000000 --- a/packages/core/src/renderers/systems/shader/GLProgram.js +++ /dev/null @@ -1,43 +0,0 @@ -/** - * Helper class to create a webGL Program - * - * @class - * @private - */ -export default class GLProgram -{ - constructor(program, uniformData) - { - /** - * The shader program - * - * @member {WebGLProgram} - */ - this.program = program; - - /** - * holds the uniform data which contains uniform locations - * and current uniform values used for caching and preventing undeed GPU commands - * @type {Object} - */ - this.uniformData = uniformData; - - /** - * uniformGroups holds the various upload functions for the shader. Each uniform group - * and program have a unique upload function generated. - * @type {Object} - */ - this.uniformGroups = {}; - } - - /** - * Destroys this program - * TODO - */ - destroy() - { - this.uniformData = null; - this.uniformGroups = null; - this.program = null; - } -} diff --git a/packages/core/src/renderers/systems/shader/ShaderSystem.js b/packages/core/src/renderers/systems/shader/ShaderSystem.js deleted file mode 100644 index 6cf57b6..0000000 --- a/packages/core/src/renderers/systems/shader/ShaderSystem.js +++ /dev/null @@ -1,170 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; -import GLProgram from './GLProgram'; -import generateUniformsSync from '../../../shader/generateUniformsSync'; -import defaultValue from '../../../shader/defaultValue'; -import compileProgram from './shader/compileProgram'; - -let UID = 0; - -/** - * Helper class to create a webGL Texture - * - * @class - * @memberof PIXI - */ -export default class ShaderSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - A reference to the current renderer - */ - constructor(renderer) - { - super(renderer); - - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = null; - - this.shader = null; - this.program = null; - - this.id = UID++; - } - - contextChange(gl) - { - this.gl = gl; - } - - /** - * Changes the current shader to the one given in parameter - * - * @param {PIXI.Shader} shader - the new shader - * @param {boolean} dontSync - false if the shader should automatically sync its uniforms. - * @returns {PIXI.glCore.glProgram} the glProgram that belongs to the shader. - */ - bind(shader, dontSync) - { - shader.uniforms.globals = this.renderer.globalUniforms; - - const program = shader.program; - const glProgram = program.glPrograms[this.renderer.CONTEXT_UID] || this.generateShader(shader); - - this.shader = shader; - - // TODO - some current pixi plugins bypass this.. so it not safe to use yet.. - if (this.program !== program) - { - this.program = program; - this.gl.useProgram(glProgram.program); - } - - if (!dontSync) - { - this.syncUniformGroup(shader.uniformGroup); - } - - return glProgram; - } - - /** - * Uploads the uniforms values to the currently bound shader. - * - * @param {object} uniforms - the uniforms valiues that be applied to the current shader - */ - setUniforms(uniforms) - { - const shader = this.shader.program; - const glProgram = shader.glPrograms[this.renderer.CONTEXT_UID]; - - shader.syncUniforms(glProgram.uniformData, uniforms, this.renderer); - } - - syncUniformGroup(group) - { - const glProgram = this.getglProgram(); - - if (!group.static || group.dirtyId !== glProgram.uniformGroups[group.id]) - { - glProgram.uniformGroups[group.id] = group.dirtyId; - const syncFunc = group.syncUniforms[this.shader.program.id] || this.createSyncGroups(group); - - syncFunc(glProgram.uniformData, group.uniforms, this.renderer); - } - } - - createSyncGroups(group) - { - group.syncUniforms[this.shader.program.id] = generateUniformsSync(group, this.shader.program.uniformData); - - return group.syncUniforms[this.shader.program.id]; - } - - /** - * Returns the underlying GLShade rof the currently bound shader. - * This can be handy for when you to have a little more control over the setting of your uniforms. - * - * @return {PIXI.glCore.Shader} the glProgram for the currently bound Shader for this context - */ - getglProgram() - { - if (this.shader) - { - return this.shader.program.glPrograms[this.renderer.CONTEXT_UID]; - } - - return null; - } - - /** - * Generates a glProgram verion of the Shader provided. - * - * @private - * @param {PIXI.Shader} shader the shader that the glProgram will be based on. - * @return {PIXI.glCore.glProgram} A shiney new glProgram - */ - generateShader(shader) - { - const gl = this.gl; - - const program = shader.program; - - const attribMap = {}; - - for (const i in program.attributeData) - { - attribMap[i] = program.attributeData[i].location; - } - - const shaderProgram = compileProgram(gl, program.vertexSrc, program.fragmentSrc, attribMap); - const uniformData = {}; - - for (const i in program.uniformData) - { - const data = program.uniformData[i]; - - uniformData[i] = { - location: gl.getUniformLocation(shaderProgram, i), - value: defaultValue(data.type, data.size), - }; - } - - const glProgram = new GLProgram(shaderProgram, uniformData); - - program.glPrograms[this.renderer.CONTEXT_UID] = glProgram; - - return glProgram; - } - - /** - * Destroys this System and removes all its textures - */ - destroy() - { - // TODO implement destroy method for ShaderSystem - this.destroyed = true; - } -} diff --git a/packages/core/src/AbstractRenderer.js b/packages/core/src/AbstractRenderer.js new file mode 100644 index 0000000..64b2eb1 --- /dev/null +++ b/packages/core/src/AbstractRenderer.js @@ -0,0 +1,353 @@ +import { hex2string, hex2rgb } from '@pixi/utils'; +import { Matrix, Rectangle } from '@pixi/math'; +import { RENDERER_TYPE } from '@pixi/constants'; +import { settings } from '@pixi/settings'; +import { Container } from '@pixi/display'; +import RenderTexture from './renderTexture/RenderTexture'; +import EventEmitter from 'eventemitter3'; + +const tempMatrix = new Matrix(); + +/** + * 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 EventEmitter + * @memberof PIXI + */ +export default class AbstractRenderer extends EventEmitter +{ + // eslint-disable-next-line valid-jsdoc + /** + * @param {string} system - The name of the system this renderer is for. + * @param {object} [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.transparent=false] - If the render view is transparent, default false + * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false + * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. The + * resolution of the renderer retina would be 2. + * @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 {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when rendering, + * stopping pixel interpolation. + */ + constructor(system, options, arg2, arg3) + { + super(); + + // Support for constructor(system, screenWidth, screenHeight, options) + if (typeof options === 'number') + { + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + }, arg3); + } + + // 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 = RENDERER_TYPE.UNKNOWN; + + /** + * Measurements of the screen. (0, 0, screenWidth, screenHeight) + * + * Its safe to use as filterArea or hitArea for 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 1 + */ + this.resolution = options.resolution || settings.RESOLUTION; + + /** + * Whether the render view is transparent + * + * @member {boolean} + */ + this.transparent = options.transparent; + + /** + * Whether css dimensions of canvas view should be resized to screen dimensions automatically + * + * @member {boolean} + */ + this.autoResize = options.autoResize || false; + + /** + * Tracks the blend modes useful for this renderer. + * + * @member {object} + */ + this.blendModes = null; + + /** + * 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; + + /** + * If true PixiJS will Math.floor() x/y values when rendering, stopping pixel interpolation. + * Handy for crisp pixel art and speed on legacy devices. + * + * @member {boolean} + */ + this.roundPixels = options.roundPixels; + + /** + * The background color as a number. + * + * @member {number} + * @private + */ + this._backgroundColor = 0x000000; + + /** + * The background color as an [R, G, B] array. + * + * @member {number[]} + * @private + */ + this._backgroundColorRgba = [0, 0, 0, 0]; + + /** + * The background color as a string. + * + * @member {string} + * @private + */ + this._backgroundColorString = '#000000'; + + this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter + + /** + * This temporary display object used as the parent of the currently being rendered item + * + * @member {PIXI.DisplayObject} + * @private + */ + this._tempDisplayObjectParent = new Container(); + + /** + * The last root object that the renderer tried to render. + * + * @member {PIXI.DisplayObject} + * @private + */ + this._lastObjectRendered = this._tempDisplayObjectParent; + + /** + * Collection of plugins. + * @readonly + * @member {object} + */ + this.plugins = {}; + } + + /** + * Initialize the plugins. + * + * @protected + * @param {object} staticMap - The dictionary of staticly saved plugins. + */ + initPlugins(staticMap) + { + 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() + { + return this.view.width; + } + + /** + * Same as view.height, actual number of pixels in the canvas by vertical + * + * @member {number} + * @readonly + * @default 600 + */ + get height() + { + return this.view.height; + } + + /** + * Resizes the screen and canvas to the specified width and height + * Canvas dimensions are multiplied by resolution + * + * @param {number} screenWidth - the new width of the screen + * @param {number} screenHeight - the new height of the screen + */ + resize(screenWidth, screenHeight) + { + this.screen.width = screenWidth; + this.screen.height = screenHeight; + + this.view.width = screenWidth * this.resolution; + this.view.height = screenHeight * this.resolution; + + if (this.autoResize) + { + this.view.style.width = `${screenWidth}px`; + this.view.style.height = `${screenHeight}px`; + } + } + + /** + * 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 {PIXI.DisplayObject} displayObject - The displayObject the object will be generated from + * @param {number} scaleMode - Should be one of the scaleMode consts + * @param {number} resolution - The resolution / device pixel ratio of the texture being generated + * @param {PIXI.Rectangle} [region] - The region of the displayObject, that shall be rendered, + * if no region is specified, defaults to the local bounds of the displayObject. + * @return {PIXI.Texture} a texture of the graphics object + */ + generateTexture(displayObject, scaleMode, resolution, region) + { + region = region || displayObject.getLocalBounds(); + + // 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(region.width | 0, region.height | 0, scaleMode, resolution); + + tempMatrix.tx = -region.x; + tempMatrix.ty = -region.y; + + this.render(displayObject, renderTexture, false, tempMatrix, true); + + return renderTexture; + } + + /** + * Removes everything from the renderer and optionally removes the Canvas DOM element. + * + * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. + */ + destroy(removeView) + { + 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); + } + + this.plugins = null; + + this.type = RENDERER_TYPE.UNKNOWN; + + this.view = null; + + this.screen = null; + + this.resolution = 0; + + this.transparent = false; + + this.autoResize = false; + + this.blendModes = null; + + this.options = null; + + this.preserveDrawingBuffer = false; + this.clearBeforeRender = false; + + this.roundPixels = false; + + this._backgroundColor = 0; + this._backgroundColorRgba = null; + this._backgroundColorString = null; + + this._tempDisplayObjectParent = null; + this._lastObjectRendered = null; + } + + /** + * The background color to fill if not transparent + * + * @member {number} + */ + get backgroundColor() + { + return this._backgroundColor; + } + + set backgroundColor(value) // eslint-disable-line require-jsdoc + { + this._backgroundColor = value; + this._backgroundColorString = hex2string(value); + hex2rgb(value, this._backgroundColorRgba); + } +} diff --git a/packages/core/src/Renderer.js b/packages/core/src/Renderer.js new file mode 100644 index 0000000..2227e78 --- /dev/null +++ b/packages/core/src/Renderer.js @@ -0,0 +1,338 @@ +import AbstractRenderer from './AbstractRenderer'; +import { sayHello } from '@pixi/utils'; +import MaskSystem from './mask/MaskSystem'; +import StencilSystem from './mask/StencilSystem'; +import FilterSystem from './filters/FilterSystem'; +import FramebufferSystem from './framebuffer/FramebufferSystem'; +import RenderTextureSystem from './renderTexture/RenderTextureSystem'; +import TextureSystem from './textures/TextureSystem'; +import ProjectionSystem from './projection/ProjectionSystem'; +import StateSystem from './state/StateSystem'; +import GeometrySystem from './geometry/GeometrySystem'; +import ShaderSystem from './shader/ShaderSystem'; +import ContextSystem from './context/ContextSystem'; +import BatchSystem from './batch/BatchSystem'; +import TextureGCSystem from './textures/TextureGCSystem'; +import { RENDERER_TYPE } from '@pixi/constants'; +import UniformGroup from './shader/UniformGroup'; +import { Matrix } from '@pixi/math'; +import Runner from 'mini-runner'; + +/** + * 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 Render works by automatically managing webGLBatchs. + * 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 :) + * + * @class + * @memberof PIXI + * @extends PIXI.AbstractRenderer + */ +export default class Renderer extends AbstractRenderer +{ + // eslint-disable-next-line valid-jsdoc + /** + * + * @param {object} [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.transparent=false] - If the render view is transparent, default false + * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false + * @param {boolean} [options.antialias=false] - sets antialias. If not available natively then FXAA + * antialiasing is used + * @param {boolean} [options.forceFXAA=false] - forces FXAA antialiasing to be used over native. + * FXAA is faster, but may not always look as great + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. + * The resolution of the renderer retina would be 2. + * @param {boolean} [options.clearBeforeRender=true] - This sets if the renderer will clear + * the canvas or not before the new render pass. If you wish to set this to false, you *must* set + * preserveDrawingBuffer to `true`. + * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, + * enable this if you need to call toDataUrl on the webgl context. + * @param {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when + * rendering, stopping pixel interpolation. + * @param {number} [options.backgroundColor=0x000000] - The background color of the rendered area + * (shown if not transparent). + * @param {boolean} [options.legacy=false] - If true PixiJS will aim to ensure compatibility + * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. + * @param {string} [options.powerPreference] - Parameter passed to webgl context, set to "high-performance" + * for devices with dual graphics card + */ + constructor(options, arg2, arg3) + { + super('WebGL', options, arg2, arg3); + + /** + * The type of this renderer as a standardised const + * + * @member {number} + * @see PIXI.RENDERER_TYPE + */ + this.type = RENDERER_TYPE.WEBGL; + + // this will be set by the contextSystem (this.context) + this.gl = null; + this.CONTEXT_UID = 0; + this.legacy = !!options.legacy; + + // TODO legacy! + + // runners! + this.runners = { + destroy: new Runner('destroy'), + contextChange: new Runner('contextChange', 1), + reset: new Runner('reset'), + update: new Runner('update'), + postrender: new Runner('postrender'), + prerender: new Runner('prerender'), + resize: new Runner('resize', 2), + }; + + 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(GeometrySystem, 'geometry') + .addSystem(FramebufferSystem, 'framebuffer') + .addSystem(StencilSystem, 'stencil') + .addSystem(ProjectionSystem, 'projection') + .addSystem(TextureGCSystem, 'textureGC') + .addSystem(FilterSystem, 'filter') + .addSystem(RenderTextureSystem, 'renderTexture') + .addSystem(BatchSystem, 'batch'); + + this.initPlugins(Renderer.__plugins); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ + if (options.context) + { + this.context.initFromContext(options.context); + } + else + { + this.context.initFromOptions({ + alpha: this.transparent, + antialias: options.antialias, + premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', + stencil: true, + 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); + } + + /** + * Add a new system to the renderer. + * @param {class} ClassRef - Class reference + * @param {string} [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. + * @return {PIXI.Renderer} Return instance of renderer + */ + addSystem(ClassRef, name) + { + if (!name) + { + name = ClassRef.name; + } + + // TODO - read name from class.name.. + + /* + if(name.includes('System')) + { + name = name.replace('System', ''); + name = name.charAt(0).toLowerCase() + name.slice(1); + } + */ + + const system = new ClassRef(this); + + if (this[name]) + { + throw new Error(`Whoops! ${name} is already a manger`); + } + + this[name] = system; + + for (const i in this.runners) + { + this.runners[i].add(system); + } + + return this; + + /** + * 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. + */ + } + + /** + * Renders the object to its webGL view + * + * @param {PIXI.DisplayObject} displayObject - the object to be rendered + * @param {PIXI.RenderTexture} renderTexture - The render texture to render to. + * @param {boolean} [clear] - Should the canvas be cleared before the new render + * @param {PIXI.Transform} [transform] - A transform to apply to the render texture before rendering. + * @param {boolean} [skipUpdateTransform] - Should we skip the update transform pass? + */ + render(displayObject, renderTexture, clear, transform, skipUpdateTransform) + { + // can be handy to know! + this.renderingToScreen = !renderTexture; + + this.runners.prerender.run(); + this.emit('prerender'); + + // 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.parent; + + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = 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.run(); + + this.emit('postrender'); + } + + /** + * Resizes the webGL view to the specified width and height. + * + * @param {number} screenWidth - the new width of the screen + * @param {number} screenHeight - the new height of the screen + */ + resize(screenWidth, screenHeight) + { + AbstractRenderer.prototype.resize.call(this, screenWidth, screenHeight); + + this.runners.resize.run(screenWidth, screenHeight); + } + + /** + * Resets the WebGL state so you can render things however you fancy! + * + * @return {PIXI.Renderer} Returns itself. + */ + reset() + { + this.runners.reset.run(); + + return this; + } + + clear() + { + this.framebuffer.bind(); + this.framebuffer.clear(); + } + + /** + * Removes everything from the renderer (event listeners, spritebatch, etc...) + * + * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. + * See: https://github.com/pixijs/pixi.js/issues/2233 + */ + destroy(removeView) + { + this.runners.destroy.run(); + + // call base destroy + super.destroy(removeView); + + // TODO nullify all the managers.. + this.gl = null; + } + + /** + * 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 + * builds and excluding plugins. + * @name PIXI.Renderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. + */ + + /** + * Adds a plugin to the renderer. + * + * @method + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + static registerPlugin(pluginName, ctor) + { + Renderer.__plugins = Renderer.__plugins || {}; + Renderer.__plugins[pluginName] = ctor; + } +} diff --git a/packages/core/src/System.js b/packages/core/src/System.js new file mode 100644 index 0000000..05a10c8 --- /dev/null +++ b/packages/core/src/System.js @@ -0,0 +1,42 @@ +/** + * System is a base class used for extending systems used by the {@link PIXI.Renderer} + * @see PIXI.Renderer#addSystem + * @class + * @memberof PIXI + */ +export default class System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + /** + * The renderer this manager works for. + * + * @member {PIXI.Renderer} + */ + this.renderer = renderer; + + this.renderer.runners.contextChange.add(this); + } + + /** + * Generic method called when there is a WebGL context change. + * + */ + contextChange() + { + // do some codes init! + } + + /** + * Generic destroy methods to be overridden by the subclass + * + */ + destroy() + { + this.renderer.runners.contextChange.remove(this); + this.renderer = null; + } +} diff --git a/packages/core/src/batch/BatchSystem.js b/packages/core/src/batch/BatchSystem.js new file mode 100644 index 0000000..b04c249 --- /dev/null +++ b/packages/core/src/batch/BatchSystem.js @@ -0,0 +1,68 @@ +import System from '../System'; +import ObjectRenderer from './ObjectRenderer'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class BatchSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + super(renderer); + + /** + * An empty renderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.emptyRenderer = new ObjectRenderer(renderer); + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.currentRenderer = this.emptyRenderer; + } + + /** + * Changes the current renderer to the one given in parameter + * + * @param {PIXI.ObjectRenderer} objectRenderer - The object renderer to use. + */ + setObjectRenderer(objectRenderer) + { + if (this.currentRenderer === objectRenderer) + { + return; + } + + this.currentRenderer.stop(); + this.currentRenderer = objectRenderer; + + this.renderer.state.setState(objectRenderer.state); + + this.currentRenderer.start(); + } + + /** + * This should be called if you wish to do some custom rendering + * It will basically render anything that may be batched up such as sprites + * + */ + flush() + { + this.setObjectRenderer(this.emptyRenderer); + } + + reset() + { + this.setObjectRenderer(this.emptyRenderer); + } +} diff --git a/packages/core/src/batch/ObjectRenderer.js b/packages/core/src/batch/ObjectRenderer.js new file mode 100644 index 0000000..0c851a9 --- /dev/null +++ b/packages/core/src/batch/ObjectRenderer.js @@ -0,0 +1,48 @@ +import System from '../System'; + +/** + * Base for a common object renderer that can be used as a system renderer plugin. + * + * @class + * @extends PIXI.System + * @memberof PIXI + */ +export default class ObjectRenderer extends System +{ + /** + * Starts the renderer and sets the shader + * + */ + start() + { + // set the shader.. + } + + /** + * Stops the renderer + * + */ + stop() + { + this.flush(); + } + + /** + * Stub method for rendering content and emptying the current batch. + * + */ + flush() + { + // flush! + } + + /** + * Renders an object + * + * @param {PIXI.DisplayObject} object - The object to render. + */ + render(object) // eslint-disable-line no-unused-vars + { + // render the object + } +} diff --git a/packages/core/src/context/ContextSystem.js b/packages/core/src/context/ContextSystem.js new file mode 100644 index 0000000..301aab4 --- /dev/null +++ b/packages/core/src/context/ContextSystem.js @@ -0,0 +1,186 @@ +import System from '../System'; +import { settings } from '@pixi/settings'; + +let CONTEXT_UID = 0; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class ContextSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.webGLVersion = 1; + + this.handleContextLost = this.handleContextLost.bind(this); + this.handleContextRestored = this.handleContextRestored.bind(this); + + this.extensions = {}; + + renderer.view.addEventListener('webglcontextlost', this.handleContextLost, false); + renderer.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + } + + get isLost() + { + return (!this.gl || this.gl.isContextLost()); + } + + contextChange(gl) + { + this.gl = gl; + + // restore a context if it was previously lost + if (gl.isContextLost() && gl.getExtension('WEBGL_lose_context')) + { + gl.getExtension('WEBGL_lose_context').restoreContext(); + } + } + + initFromContext(gl) + { + this.gl = gl; + this.validateContext(gl); + this.renderer.gl = gl; + this.renderer.CONTEXT_UID = CONTEXT_UID++; + this.renderer.runners.contextChange.run(gl); + } + + initFromOptions(options) + { + const gl = this.createContext(this.renderer.view, options); + + this.initFromContext(gl); + } + + /** + * Helper class to create a webGL Context + * + * @class + * @memberof PIXI.glCore + * @param canvas {HTMLCanvasElement} the canvas element that we will get the context from + * @param options {Object} An options object that gets passed in to the canvas element containing the context attributes, + * see https://developer.mozilla.org/en/docs/Web/API/HTMLCanvasElement/getContext for the options available + * @return {WebGLRenderingContext} the WebGL context + */ + createContext(canvas, options) + { + let gl; + + if (settings.PREFER_WEBGL_2) + { + gl = canvas.getContext('webgl2', options); + } + + if (gl) + { + this.webGLVersion = 2; + } + else + { + this.webGLVersion = 1; + + gl = canvas.getContext('webgl', options) + || canvas.getContext('experimental-webgl', options); + + if (!gl) + { + // fail, not able to get a context + throw new Error('This browser does not support webGL. Try using the canvas renderer'); + } + } + + this.gl = gl; + + this.getExtensions(); + + return gl; + } + + getExtensions() + { + // time to set up default etensions that pixi uses.. + const gl = this.gl; + const extensions = this.extensions; + + if (this.webGLVersion === 1) + { + extensions.drawBuffers = gl.getExtension('WEBGL_draw_buffers'); + extensions.depthTexture = gl.getExtension('WEBKIT_WEBGL_depth_texture'); + extensions.floatTexture = gl.getExtension('OES_texture_float'); + extensions.loseContext = gl.getExtension('WEBGL_lose_context'); + + extensions.vertexArrayObject = gl.getExtension('OES_vertex_array_object') + || gl.getExtension('MOZ_OES_vertex_array_object') + || gl.getExtension('WEBKIT_OES_vertex_array_object'); + } + + // we don't use any specific WebGL 2 ones yet! + } + + /** + * Handles a lost webgl context + * + * @private + * @param {WebGLContextEvent} event - The context lost event. + */ + handleContextLost(event) + { + event.preventDefault(); + } + + /** + * Handles a restored webgl context + * + * @private + */ + handleContextRestored() + { + this.renderer.runners.contextChange.run(this.gl); + } + + destroy() + { + const view = this.renderer.view; + + // remove listeners + view.removeEventListener('webglcontextlost', this.handleContextLost); + view.removeEventListener('webglcontextrestored', this.handleContextRestored); + + this.gl.useProgram(null); + + if (this.extensions.loseContext) + { + this.extensions.loseContext.loseContext(); + } + } + + postrender() + { + this.gl.flush(); + } + + validateContext(gl) + { + const attributes = gl.getContextAttributes(); + + // this is going to be fairly simple for now.. but at least we have room to grow! + if (!attributes.stencil) + { + /* eslint-disable max-len */ + + /* eslint-disable no-console */ + console.warn('Provided WebGL context does not have a stencil buffer, masks may not render correctly'); + /* eslint-enable no-console */ + + /* eslint-enable max-len */ + } + } +} diff --git a/packages/core/src/filters/Filter.js b/packages/core/src/filters/Filter.js new file mode 100644 index 0000000..9d8cad8 --- /dev/null +++ b/packages/core/src/filters/Filter.js @@ -0,0 +1,140 @@ +import Shader from '../shader/Shader'; +import Program from '../shader/Program'; +import State from '../state/State'; +import { settings } from '@pixi/settings'; +// import extractUniformsFromSrc from './extractUniformsFromSrc'; +import defaultVertex from './defaultFilter.vert'; +import defaultFragment from './defaultFilter.frag'; + +// let math = require('../../math'); +/** + * @class + * @memberof PIXI + * @extends PIXI.Shader + */ +export default class Filter extends Shader +{ + /** + * @param {string} [vertexSrc] - The source of the vertex shader. + * @param {string} [fragmentSrc] - The source of the fragment shader. + * @param {object} [uniforms] - Custom uniforms to use to augment the built-in ones. + */ + constructor(vertexSrc, fragmentSrc, uniforms) + { + const program = Program.from(vertexSrc, fragmentSrc); + + super(program, uniforms); + + /** + * The padding of the filter. Some filters require extra space to breath such as a blur. + * Increasing this will add extra width and height to the bounds of the object that the + * filter is applied to. + * + * @member {number} + */ + this.padding = 0; + + /** + * The resolution of the filter. Setting this to be lower will lower the quality but + * increase the performance of the filter. + * + * @member {number} + */ + this.resolution = settings.RESOLUTION; + + /** + * If enabled is true the filter is applied, if false it will not. + * + * @member {boolean} + */ + this.enabled = true; + + /** + * If enabled, PixiJS will fit the filter area into boundaries for better performance. + * Switch it off if it does not work for specific shader. + * + * @member {boolean} + */ + this.autoFit = true; + + /** + * Legacy filters use position and uvs from attributes + * @member {boolean} + * @readonly + */ + this.legacy = !!this.program.attributeData.aTextureCoord; + + /** + * the webGL state the filter requires to render + * @member {PIXI.State} + */ + this.state = new State(); + } + + /** + * Applies the filter + * + * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + * @param {boolean} clear - Should the output be cleared before rendering to it + * @param {object} [currentState] - It's current state of filter. + * There are some useful properties in the currentState : + * target, filters, sourceFrame, destinationFrame, renderTarget, resolution + */ + apply(filterManager, input, output, clear, currentState, derp) // eslint-disable-line no-unused-vars + { + // do as you please! + + filterManager.applyFilter(this, input, output, clear, currentState, derp); + + // or just do a regular render.. + } + + /** + * Sets the blendmode of the filter + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL + */ + get blendMode() + { + return this.state.blendMode; + } + + set blendMode(value) // eslint-disable-line require-jsdoc + { + this.state.blendMode = value; + } + + /** + * The default vertex shader source + * + * @static + * @constant + */ + static get defaultVertexSrc() + { + return defaultVertex; + } + + /** + * The default fragment shader source + * + * @static + * @constant + */ + static get defaultFragmentSrc() + { + return defaultFragment; + } +} + +/** + * Used for caching shader IDs + * + * @static + * @private + */ +Filter.SOURCE_KEY_MAP = {}; + diff --git a/packages/core/src/filters/FilterSystem.js b/packages/core/src/filters/FilterSystem.js new file mode 100644 index 0000000..3174318 --- /dev/null +++ b/packages/core/src/filters/FilterSystem.js @@ -0,0 +1,431 @@ +import System from '../System'; + +import RenderTexture from '../renderTexture/RenderTexture'; +import Quad from '../utils/Quad'; +import QuadUv from '../utils/QuadUv'; +import { Rectangle } from '@pixi/math'; +import * as filterTransforms from './filterTransforms'; +import bitTwiddle from 'bit-twiddle'; +import UniformGroup from '../shader/UniformGroup'; +import { DRAW_MODES } from '@pixi/constants'; + +// +/** + * @ignore + * @class + */ +class FilterState +{ + /** + * + */ + constructor() + { + this.renderTexture = null; + this.sourceFrame = new Rectangle(); + this.destinationFrame = new Rectangle(); + this.filters = []; + this.target = null; + this.legacy = false; + this.resolution = 1; + } +} + +/** + * @class + * @memberof PIXI.systems + * @extends PIXI.System + */ +export default class FilterSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + /** + * stores a bunch of PO2 textures used for filtering + * @type {Object} + */ + this.texturePool = {}; + + /** + * a pool for storing filter states, save us creating new ones each tick + * @type {Array} + */ + this.statePool = []; + + /** + * A very simple geometry used when drawing a filter effect to the screen + * @type {Quad} + */ + this.quad = new Quad(); + + this.quadUv = new QuadUv(); + + /** + * Temporary rect for maths + * @type {PIXI.Rectangle} + */ + this.tempRect = new Rectangle(); + + this.activeState = {}; + + /** + * this uniform group is attached to filter uniforms when used + * @type {UniformGroup} + */ + this.globalUniforms = new UniformGroup({ + sourceFrame: this.tempRect, + destinationFrame: this.tempRect, + + // legacy variables + filterArea: new Float32Array(4), + filterClamp: new Float32Array(4), + }, true); + } + + /** + * Adds a new filter to the System. + * + * @param {PIXI.DisplayObject} target - The target of the filter to render. + * @param {PIXI.Filter[]} filters - The filters to apply. + */ + push(target, filters) + { + const renderer = this.renderer; + const filterStack = this.renderer.renderTexture.defaultFilterStack; + const state = this.statePool.pop() || new FilterState(); + + let resolution = filters[0].resolution; + let padding = filters[0].padding; + let autoFit = filters[0].autoFit; + let legacy = filters[0].legacy; + + for (let i = 1; i < filters.length; i++) + { + const filter = filters[i]; + + // lets use the lowest resolution.. + resolution = Math.min(resolution, filter.resolution); + // and the largest amount of padding! + padding = Math.max(padding, filter.padding); + // only auto fit if all filters are autofit + autoFit = autoFit || filter.autoFit; + + legacy = legacy || filter.legacy; + } + + filterStack.push(state); + + state.resolution = resolution; + + state.legacy = legacy; + + // round to whole number based on resolution + // TODO move that to the shader too? + state.sourceFrame = target.filterArea || target.getBounds(true); + + state.sourceFrame.pad(padding); + + if (autoFit) + { + state.sourceFrame.fit(this.renderer.renderTexture.destinationFrame); + } + + state.sourceFrame.round(resolution); + + state.renderTexture = this.getPotFilterTexture(state.sourceFrame.width, state.sourceFrame.height, resolution); + state.filters = filters; + + state.destinationFrame.width = state.renderTexture.width; + state.destinationFrame.height = state.renderTexture.height; + + state.renderTexture.filterFrame = state.sourceFrame; + renderer.renderTexture.bind(state.renderTexture, state.sourceFrame);// /, state.destinationFrame); + renderer.renderTexture.clear(); + } + + /** + * Pops off the filter and applies it. + * + */ + pop() + { + const renderer = this.renderer; + const filterStack = renderer.renderTexture.defaultFilterStack; + const state = filterStack.pop(); + const filters = state.filters; + + this.activeState = state; + + const globalUniforms = this.globalUniforms.uniforms; + + globalUniforms.sourceFrame = state.sourceFrame; + globalUniforms.destinationFrame = state.destinationFrame; + globalUniforms.resolution = state.resolution; + + // only update the rect if its legacy.. + if (state.legacy) + { + const filterArea = globalUniforms.filterArea; + const filterClamp = globalUniforms.filterClamp; + + filterArea[0] = state.destinationFrame.width; + filterArea[1] = state.destinationFrame.height; + filterArea[2] = state.sourceFrame.x; + filterArea[3] = state.sourceFrame.y; + + filterClamp[0] = 0.5 / state.resolution / state.destinationFrame.width; + filterClamp[1] = 0.5 / state.resolution / state.destinationFrame.height; + filterClamp[2] = (state.sourceFrame.width - 0.5) / state.resolution / state.destinationFrame.width; + filterClamp[3] = (state.sourceFrame.height - 0.5) / state.resolution / state.destinationFrame.height; + } + + this.globalUniforms.update(); + + const lastState = filterStack[filterStack.length - 1]; + + if (filters.length === 1) + { + filters[0].apply(this, state.renderTexture, lastState.renderTexture, false, state); + + this.returnFilterTexture(state.renderTexture); + } + else + { + let flip = state.renderTexture; + let flop = this.getPotFilterTexture( + flip.width, + flip.height, + state.resolution + ); + + flop.filterFrame = flip.filterFrame; + + let i = 0; + + for (i = 0; i < filters.length - 1; ++i) + { + filters[i].apply(this, flip, flop, true, state); + + const t = flip; + + flip = flop; + flop = t; + } + + filters[i].apply(this, flip, lastState.renderTexture, false, state); + + this.returnFilterTexture(flip); + this.returnFilterTexture(flop); + } + + this.statePool.push(state); + } + + /** + * Draws a filter. + * + * @param {PIXI.Filter} filter - The filter to draw. + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + * @param {boolean} clear - Should the output be cleared before rendering to it + */ + applyFilter(filter, input, output, clear) + { + const renderer = this.renderer; + + renderer.renderTexture.bind(output, output ? output.filterFrame : null); + + if (clear) + { + // gl.disable(gl.SCISSOR_TEST); + renderer.renderTexture.clear(); + // gl.enable(gl.SCISSOR_TEST); + } + + // set the uniforms.. + filter.uniforms.uSampler = input; + filter.uniforms.filterGlobals = this.globalUniforms; + + // TODO make it so that the order of this does not matter.. + // because it does at the moment cos of global uniforms. + // they need to get resynced + + renderer.state.setState(filter.state); + renderer.shader.bind(filter); + + if (filter.legacy) + { + this.quadUv.map(input._frame, input.filterFrame); + + renderer.geometry.bind(this.quadUv); + renderer.geometry.draw(DRAW_MODES.TRIANGLES); + } + else + { + renderer.geometry.bind(this.quad); + renderer.geometry.draw(DRAW_MODES.TRIANGLE_STRIP); + } + } + + /** + * Calculates the mapped matrix. + * + * TODO playing around here.. this is temporary - (will end up in the shader) + * this returns a matrix that will normalise map filter cords in the filter to screen space + * + * @param {PIXI.Matrix} outputMatrix - the matrix to output to. + * @return {PIXI.Matrix} The mapped matrix. + */ + calculateScreenSpaceMatrix(outputMatrix) + { + const currentState = this.activeState; + + return filterTransforms.calculateScreenSpaceMatrix( + outputMatrix, + currentState.sourceFrame, + currentState.destinationFrame + ); + } + + /** + * This will map the filter coord so that a texture can be used based on the transform of a sprite + * + * @param {PIXI.Matrix} outputMatrix - The matrix to output to. + * @param {PIXI.Sprite} sprite - The sprite to map to. + * @return {PIXI.Matrix} The mapped matrix. + */ + calculateSpriteMatrix(outputMatrix, sprite) + { + const currentState = this.activeState; + + return filterTransforms.calculateSpriteMatrix( + outputMatrix, + currentState.sourceFrame, + currentState.destinationFrame, + sprite + ); + } + + /** + * Destroys this Filter System. + * + * @param {boolean} [contextLost=false] context was lost, do not free shaders + * + */ + destroy(contextLost = false) + { + if (!contextLost) + { + this.emptyPool(); + } + else + { + this.texturePool = {}; + } + } + + /** + * Gets a Power-of-Two render texture. + * + * TODO move to a seperate class could be on renderer? + * also - could cause issue with multiple contexts? + * + * @private + * @param {WebGLRenderingContext} gl - The webgl rendering context + * @param {number} minWidth - The minimum width of the render target. + * @param {number} minHeight - The minimum height of the render target. + * @param {number} resolution - The resolution of the render target. + * @return {PIXI.RenderTarget} The new render target. + */ + getPotFilterTexture(minWidth, minHeight, resolution) + { + minWidth = bitTwiddle.nextPow2(minWidth); + minHeight = bitTwiddle.nextPow2(minHeight); + resolution = resolution || 1; + + const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + + if (!this.texturePool[key]) + { + this.texturePool[key] = []; + } + + let renderTexture = this.texturePool[key].pop(); + + if (!renderTexture) + { + // temporary bypass cache.. + // internally - this will cause a texture to be bound.. + renderTexture = RenderTexture.create({ + width: minWidth, + height: minHeight, + resolution, + }); + } + + return renderTexture; + } + + /** + * Gets extra render texture to use inside current filter + * + * @param {number} resolution resolution of the renderTexture + * @returns {PIXI.RenderTexture} + */ + getFilterTexture(resolution) + { + const rt = this.activeState.renderTexture; + + const filterTexture = this.getPotFilterTexture(rt.width, rt.height, resolution || rt.baseTexture.resolution); + + filterTexture.filterFrame = rt.filterFrame; + + return filterTexture; + } + + /** + * Frees a render texture back into the pool. + * + * @param {PIXI.RenderTarget} renderTexture - The renderTarget to free + */ + returnFilterTexture(renderTexture) + { + renderTexture.filterFrame = null; + + const base = renderTexture.baseTexture; + + const minWidth = base.width; + const minHeight = base.height; + + const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + + this.texturePool[key].push(renderTexture); + } + + /** + * Empties the texture pool. + * + */ + emptyPool() + { + for (const i in this.texturePool) + { + const textures = this.texturePool[i]; + + if (textures) + { + for (let j = 0; j < textures.length; j++) + { + textures[j].destroy(true); + } + } + } + + this.texturePool = {}; + } +} diff --git a/packages/core/src/filters/defaultFilter.frag b/packages/core/src/filters/defaultFilter.frag new file mode 100644 index 0000000..a14c1be --- /dev/null +++ b/packages/core/src/filters/defaultFilter.frag @@ -0,0 +1,22 @@ +varying vec2 vTextureCoord; +varying vec2 vFilterCoord; + +uniform sampler2D uSampler; +uniform sampler2D filterSampler; + +void main(void){ + vec4 masky = texture2D(filterSampler, vFilterCoord); + vec4 sample = texture2D(uSampler, vTextureCoord); + vec4 color; + if(mod(vFilterCoord.x, 1.0) > 0.5) + { + color = vec4(1.0, 0.0, 0.0, 1.0); + } + else + { + color = vec4(0.0, 1.0, 0.0, 1.0); + } + // gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0); + gl_FragColor = mix(sample, masky, 0.5); + gl_FragColor *= sample.a; +} diff --git a/packages/core/src/filters/defaultFilter.vert b/packages/core/src/filters/defaultFilter.vert new file mode 100644 index 0000000..e1febca --- /dev/null +++ b/packages/core/src/filters/defaultFilter.vert @@ -0,0 +1,14 @@ +attribute vec2 aVertexPosition; +attribute vec2 aTextureCoord; + +uniform mat3 projectionMatrix; +uniform mat3 filterMatrix; + +varying vec2 vTextureCoord; +varying vec2 vFilterCoord; + +void main(void){ + gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); + vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy; + vTextureCoord = aTextureCoord ; +} diff --git a/packages/core/src/filters/filterTransforms.js b/packages/core/src/filters/filterTransforms.js new file mode 100644 index 0000000..87a3cd6 --- /dev/null +++ b/packages/core/src/filters/filterTransforms.js @@ -0,0 +1,50 @@ +import { Matrix } from '@pixi/math'; + +/** + * Calculates the mapped matrix + * @param filterArea {Rectangle} The filter area + * @param sprite {Sprite} the target sprite + * @param outputMatrix {Matrix} @alvin + * @private + */ +// this returns a matrix that will normalise map filter cords in the filter to screen space +export function calculateScreenSpaceMatrix(outputMatrix, filterArea, textureSize) +{ + // TODO unwrap? + const mappedMatrix = outputMatrix.identity(); + + mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); + + mappedMatrix.scale(textureSize.width, textureSize.height); + + return mappedMatrix; +} + +export function calculateNormalizedScreenSpaceMatrix(outputMatrix, filterArea, textureSize) +{ + const mappedMatrix = outputMatrix.identity(); + + mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); + + const translateScaleX = (textureSize.width / filterArea.width); + const translateScaleY = (textureSize.height / filterArea.height); + + mappedMatrix.scale(translateScaleX, translateScaleY); + + return mappedMatrix; +} + +// this will map the filter coord so that a texture can be used based on the transform of a sprite +export function calculateSpriteMatrix(outputMatrix, filterArea, textureSize, sprite) +{ + const orig = sprite._texture.orig; + const mappedMatrix = outputMatrix.set(textureSize.width, 0, 0, textureSize.height, filterArea.x, filterArea.y); + const worldTransform = sprite.worldTransform.copyTo(Matrix.TEMP_MATRIX); + + worldTransform.invert(); + mappedMatrix.prepend(worldTransform); + mappedMatrix.scale(1.0 / orig.width, 1.0 / orig.height); + mappedMatrix.translate(sprite.anchor.x, sprite.anchor.y); + + return mappedMatrix; +} diff --git a/packages/core/src/filters/spriteMask/SpriteMaskFilter.js b/packages/core/src/filters/spriteMask/SpriteMaskFilter.js new file mode 100644 index 0000000..91697b7 --- /dev/null +++ b/packages/core/src/filters/spriteMask/SpriteMaskFilter.js @@ -0,0 +1,63 @@ +import Filter from '../Filter'; +import { Matrix } from '@pixi/math'; +import vertex from './spriteMaskFilter.vert'; +import fragment from './spriteMaskFilter.frag'; +import { default as TextureMatrix } from '../../textures/TextureMatrix'; + +/** + * The SpriteMaskFilter class + * + * @class + * @extends PIXI.Filter + * @memberof PIXI + */ +export default class SpriteMaskFilter extends Filter +{ + /** + * @param {PIXI.Sprite} sprite - the target sprite + */ + constructor(sprite) + { + const maskMatrix = new Matrix(); + + super(vertex, fragment); + + sprite.renderable = false; + + this.maskSprite = sprite; + this.maskMatrix = maskMatrix; + } + + /** + * Applies the filter + * + * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + */ + apply(filterManager, input, output) + { + const maskSprite = this.maskSprite; + const tex = this.maskSprite.texture; + + if (!tex.valid) + { + return; + } + if (!tex.transform) + { + // margin = 0.0, let it bleed a bit, shader code becomes easier + // assuming that atlas textures were made with 1-pixel padding + tex.transform = new TextureMatrix(tex, 0.0); + } + tex.transform.update(); + + this.uniforms.mask = tex; + this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite) + .prepend(tex.transform.mapCoord); + this.uniforms.alpha = maskSprite.worldAlpha; + this.uniforms.maskClamp = tex.transform.uClampFrame; + + filterManager.applyFilter(this, input, output); + } +} diff --git a/packages/core/src/filters/spriteMask/spriteMaskFilter.frag b/packages/core/src/filters/spriteMask/spriteMaskFilter.frag new file mode 100644 index 0000000..0e4aef8 --- /dev/null +++ b/packages/core/src/filters/spriteMask/spriteMaskFilter.frag @@ -0,0 +1,23 @@ +varying vec2 vMaskCoord; +varying vec2 vTextureCoord; + +uniform sampler2D uSampler; +uniform sampler2D mask; +uniform float alpha; +uniform vec4 maskClamp; + +void main(void) +{ + float clip = step(3.5, + step(maskClamp.x, vMaskCoord.x) + + step(maskClamp.y, vMaskCoord.y) + + step(vMaskCoord.x, maskClamp.z) + + step(vMaskCoord.y, maskClamp.w)); + + vec4 original = texture2D(uSampler, vTextureCoord); + vec4 masky = texture2D(mask, vMaskCoord); + + original *= (masky.r * masky.a * alpha * clip); + + gl_FragColor = original; +} diff --git a/packages/core/src/filters/spriteMask/spriteMaskFilter.vert b/packages/core/src/filters/spriteMask/spriteMaskFilter.vert new file mode 100644 index 0000000..d23a30c --- /dev/null +++ b/packages/core/src/filters/spriteMask/spriteMaskFilter.vert @@ -0,0 +1,16 @@ +attribute vec2 aVertexPosition; +attribute vec2 aTextureCoord; + +uniform mat3 projectionMatrix; +uniform mat3 otherMatrix; + +varying vec2 vMaskCoord; +varying vec2 vTextureCoord; + +void main(void) +{ + gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); + + vTextureCoord = aTextureCoord; + vMaskCoord = ( otherMatrix * vec3( aTextureCoord, 1.0) ).xy; +} diff --git a/packages/core/src/framebuffer/FrameBuffer.js b/packages/core/src/framebuffer/FrameBuffer.js new file mode 100644 index 0000000..84e9fcc --- /dev/null +++ b/packages/core/src/framebuffer/FrameBuffer.js @@ -0,0 +1,106 @@ +import Texture from '../textures/BaseTexture'; +import { FORMATS, TYPES } from '@pixi/constants'; + +/** + * Frame buffer + * @class + * @memberof PIXI + */ +export default class FrameBuffer +{ + constructor(width, height) + { + this.width = width || 100; + this.height = height || 100; + + this.stencil = false; + this.depth = false; + + this.dirtyId = 0; + this.dirtyFormat = 0; + this.dirtySize = 0; + + this.depthTexture = null; + this.colorTextures = []; + + this.glFrameBuffers = {}; + } + + get colorTexture() + { + return this.colorTextures[0]; + } + + addColorTexture(index, texture) + { + // TODO add some validation to the texture - same width / height etc? + this.colorTextures[index || 0] = texture || new Texture(null, { scaleMode: 0, + resolution: 1, + mipmap: false, + width: this.width, + height: this.height });// || new Texture(); + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + addDepthTexture(texture) + { + /* eslint-disable max-len */ + this.depthTexture = texture || new Texture(null, { scaleMode: 0, + resolution: 1, + width: this.width, + height: this.height, + mipmap: false, + format: FORMATS.DEPTH_COMPONENT, + type: TYPES.UNSIGNED_SHORT });// UNSIGNED_SHORT; + /* eslint-disable max-len */ + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + enableDepth() + { + this.depth = true; + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + enableStencil() + { + this.stencil = true; + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + resize(width, height) + { + if (width === this.width && height === this.height) return; + + this.width = width; + this.height = height; + + this.dirtyId++; + this.dirtySize++; + + for (let i = 0; i < this.colorTextures.length; i++) + { + this.colorTextures[i].setSize(width, height); + } + + if (this.depthTexture) + { + this.depthTexture.setSize(width, height); + } + } +} diff --git a/packages/core/src/framebuffer/FramebufferSystem.js b/packages/core/src/framebuffer/FramebufferSystem.js new file mode 100644 index 0000000..2cc2437 --- /dev/null +++ b/packages/core/src/framebuffer/FramebufferSystem.js @@ -0,0 +1,246 @@ +import System from '../System'; +import { Rectangle } from '@pixi/math'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class FramebufferSystem extends System +{ + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + contextChange() + { + this.gl = this.renderer.gl; + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + this.current = null; + this.viewport = new Rectangle(); + + this.drawBufferExtension = this.renderer.context.extensions.drawBuffers; + } + + bind(framebuffer, frame) + { + const gl = this.gl; + + this.current = framebuffer; + + if (framebuffer) + { + // TODO cacheing layer! + + const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID] || this.initFramebuffer(framebuffer); + + gl.bindFramebuffer(gl.FRAMEBUFFER, fbo.framebuffer); + // makesure all textures are unbound.. + + // now check for updates... + if (fbo.dirtyId !== framebuffer.dirtyId) + { + fbo.dirtyId = framebuffer.dirtyId; + + if (fbo.dirtyFormat !== framebuffer.dirtyFormat) + { + fbo.dirtyFormat = framebuffer.dirtyFormat; + this.updateFramebuffer(framebuffer); + } + else if (fbo.dirtySize !== framebuffer.dirtySize) + { + fbo.dirtySize = framebuffer.dirtySize; + this.resizeFramebuffer(framebuffer); + } + } + + for (let i = 0; i < framebuffer.colorTextures.length; i++) + { + if (framebuffer.colorTextures[i].texturePart) + { + this.renderer.texture.unbind(framebuffer.colorTextures[i].texture); + } + else + { + this.renderer.texture.unbind(framebuffer.colorTextures[i]); + } + } + + if (framebuffer.depthTexture) + { + this.renderer.texture.unbind(framebuffer.depthTexture); + } + + if (frame) + { + this.setViewport(frame.x, frame.y, frame.width, frame.height); + } + else + { + this.setViewport(0, 0, framebuffer.width, framebuffer.height); + } + } + else + { + gl.bindFramebuffer(gl.FRAMEBUFFER, null); + + if (frame) + { + this.setViewport(frame.x, frame.y, frame.width, frame.height); + } + else + { + this.setViewport(0, 0, this.renderer.width, this.renderer.height); + } + } + } + + setViewport(x, y, width, height) + { + const v = this.viewport; + + if (v.width !== width || v.height !== height || v.x !== x || v.y !== y) + { + v.x = x; + v.y = y; + v.width = width; + v.height = height; + + this.gl.viewport(x, y, width, height); + } + } + + get size() + { + if (this.current) + { + // TODO store temp + return { x: 0, y: 0, width: this.current.width, height: this.current.height }; + } + + return { x: 0, y: 0, width: this.renderer.width, height: this.renderer.height }; + } + + clear(r, g, b, a) + { + const gl = this.gl; + + // TODO clear color can be set only one right? + gl.clearColor(r, g, b, a); + gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); + } + + // private functions... + + initFramebuffer(framebuffer) + { + const gl = this.gl; + + // TODO - make this a class? + const fbo = { + framebuffer: gl.createFramebuffer(), + stencil: null, + dirtyId: 0, + dirtyFormat: 0, + dirtySize: 0, + }; + + framebuffer.glFrameBuffers[this.CONTEXT_UID] = fbo; + + return fbo; + } + + resizeFramebuffer(framebuffer) + { + const gl = this.gl; + + if (framebuffer.stencil || framebuffer.depth) + { + gl.bindRenderbuffer(gl.RENDERBUFFER, this.stencil); + gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); + } + } + + updateFramebuffer(framebuffer) + { + const gl = this.gl; + + const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID]; + + // bind the color texture + const colorTextures = framebuffer.colorTextures; + + let count = colorTextures.length; + + if (!this.drawBufferExtension) + { + count = Math.min(count, 1); + } + + const activeTextures = []; + + for (let i = 0; i < count; i++) + { + const texture = framebuffer.colorTextures[i]; + + if (texture.texturePart) + { + this.renderer.texture.bind(texture.texture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.COLOR_ATTACHMENT0 + i, + gl.TEXTURE_CUBE_MAP_NEGATIVE_X + texture.side, + texture.texture._glTextures[this.CONTEXT_UID].texture, + 0); + } + else + { + this.renderer.texture.bind(texture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.COLOR_ATTACHMENT0 + i, + gl.TEXTURE_2D, + texture._glTextures[this.CONTEXT_UID].texture, + 0); + } + + activeTextures.push(gl.COLOR_ATTACHMENT0 + i); + } + + if (this.drawBufferExtension && activeTextures.length > 1) + { + this.drawBufferExtension.drawBuffersWEBGL(activeTextures); + } + + if (framebuffer.depthTexture) + { + const depthTextureExt = this.renderer.context.extensions.depthTexture; + + if (depthTextureExt) + { + const depthTexture = framebuffer.depthTexture; + + this.renderer.texture.bind(depthTexture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.DEPTH_ATTACHMENT, + gl.TEXTURE_2D, + depthTexture._glTextures[this.CONTEXT_UID].texture, + 0); + } + } + + if (framebuffer.stencil || framebuffer.depth) + { + fbo.stencil = gl.createRenderbuffer(); + + gl.bindRenderbuffer(gl.RENDERBUFFER, fbo.stencil); + + // TODO.. this is depth AND stencil? + gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, fbo.stencil); + gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); + // fbo.enableStencil(); + } + } +} diff --git a/packages/core/src/geometry/GLBuffer.js b/packages/core/src/geometry/GLBuffer.js new file mode 100644 index 0000000..0477e8a --- /dev/null +++ b/packages/core/src/geometry/GLBuffer.js @@ -0,0 +1,9 @@ +export default class GLBuffer +{ + constructor(buffer) + { + this.buffer = buffer; + this.updateID = -1; + this.byteLength = -1; + } +} diff --git a/packages/core/src/geometry/GeometrySystem.js b/packages/core/src/geometry/GeometrySystem.js new file mode 100644 index 0000000..959f827 --- /dev/null +++ b/packages/core/src/geometry/GeometrySystem.js @@ -0,0 +1,388 @@ +import System from '../System'; +import GLBuffer from './GLBuffer'; + +const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class GeometrySystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this._activeGeometry = null; + this._activeVao = null; + + this.hasVao = true; + this.hasInstance = true; + } + + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + contextChange() + { + const gl = this.gl = this.renderer.gl; + + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + + // webgl2 + if (!gl.createVertexArray) + { + // webgl 1! + let nativeVaoExtension = this.renderer.context.extensions.vertexArrayObject; + + if (this.renderer.legacy) + { + nativeVaoExtension = null; + } + + if (nativeVaoExtension) + { + gl.createVertexArray = () => + nativeVaoExtension.createVertexArrayOES(); + + gl.bindVertexArray = (vao) => + nativeVaoExtension.bindVertexArrayOES(vao); + + gl.deleteVertexArray = (vao) => + nativeVaoExtension.deleteVertexArrayOES(vao); + } + else + { + this.hasVao = false; + gl.createVertexArray = () => + { + // empty + }; + + gl.bindVertexArray = () => + { + // empty + }; + + gl.deleteVertexArray = () => + { + // empty + }; + } + } + + if (!gl.vertexAttribDivisor) + { + const instanceExt = gl.getExtension('ANGLE_instanced_arrays'); + + if (instanceExt) + { + gl.vertexAttribDivisor = (a, b) => + instanceExt.vertexAttribDivisorANGLE(a, b); + + gl.drawElementsInstanced = (a, b, c, d, e) => + instanceExt.drawElementsInstancedANGLE(a, b, c, d, e); + + gl.drawArraysInstanced = (a, b, c, d) => + instanceExt.drawArraysInstancedANGLE(a, b, c, d); + } + else + { + this.hasInstance = false; + } + } + } + + /** + * Binds geometry so that is can be drawn. Creating a Vao if required + * @private + * @param {PIXI.Geometry} geometry instance of geometry to bind + */ + bind(geometry, shader) + { + shader = shader || this.renderer.shader.shader; + + const gl = this.gl; + + // not sure the best way to address this.. + // currently different shaders require different VAOs for the same geometry + // Still mulling over the best way to solve this one.. + // will likely need to modify the shader attribute locations at run time! + let vaos = geometry.glVertexArrayObjects[this.CONTEXT_UID]; + + if (!vaos) + { + geometry.glVertexArrayObjects[this.CONTEXT_UID] = vaos = {}; + } + + const vao = vaos[shader.program.id] || this.initGeometryVao(geometry, shader.program); + + this._activeGeometry = geometry; + + if (this._activeVao !== vao) + { + this._activeVao = vao; + + if (this.hasVao) + { + gl.bindVertexArray(vao); + } + else + { + this.activateVao(geometry, shader.program); + } + } + + // TODO - optimise later! + // don't need to loop through if nothing changed! + // maybe look to add an 'autoupdate' to geometry? + this.updateBuffers(); + } + + reset() + { + this.unbind(); + } + + updateBuffers() + { + const geometry = this._activeGeometry; + const gl = this.gl; + + for (let i = 0; i < geometry.buffers.length; i++) + { + const buffer = geometry.buffers[i]; + + const glBuffer = buffer._glBuffers[this.CONTEXT_UID]; + + if (buffer._updateID !== glBuffer.updateID) + { + glBuffer.updateID = buffer._updateID; + + // TODO can cache this on buffer! maybe added a getter / setter? + const type = buffer.index ? gl.ELEMENT_ARRAY_BUFFER : gl.ARRAY_BUFFER; + const drawType = buffer.static ? gl.STATIC_DRAW : gl.DYNAMIC_DRAW; + + gl.bindBuffer(type, glBuffer.buffer); + + if (glBuffer.byteLength >= buffer.data.byteLength) + { + // offset is always zero for now! + gl.bufferSubData(type, 0, buffer.data); + } + else + { + gl.bufferData(type, buffer.data, drawType); + } + } + } + } + + checkCompatability(geometry, program) + { + // geometry must have at least all the attributes that the shader requires. + const geometryAttributes = geometry.attributes; + const shaderAttributes = program.attributeData; + + for (const j in shaderAttributes) + { + if (!geometryAttributes[j]) + { + throw new Error(`shader and geometry incompatible, geometry missing the "${j}" attribute`); + } + } + } + + /** + * Creates a Vao with the same structure as the geometry and stores it on the geometry. + * @private + * @param {PIXI.Geometry} geometry instance of geometry to to generate Vao for + */ + initGeometryVao(geometry, program) + { + this.checkCompatability(geometry, program); + + const gl = this.gl; + const CONTEXT_UID = this.CONTEXT_UID; + const buffers = geometry.buffers; + const attributes = geometry.attributes; + + const tempStride = {}; + const tempStart = {}; + + for (const j in buffers) + { + tempStride[j] = 0; + tempStart[j] = 0; + } + + for (const j in attributes) + { + if (!attributes[j].size && program.attributeData[j]) + { + attributes[j].size = program.attributeData[j].size; + } + + tempStride[attributes[j].buffer] += attributes[j].size * byteSizeMap[attributes[j].type]; + } + + for (const j in attributes) + { + const attribute = attributes[j]; + const attribSize = attribute.size; + + if (attribute.stride === undefined) + { + if (tempStride[attribute.buffer] === attribSize * byteSizeMap[attribute.type]) + { + attribute.stride = 0; + } + else + { + attribute.stride = tempStride[attribute.buffer]; + } + } + + if (attribute.start === undefined) + { + attribute.start = tempStart[attribute.buffer]; + + tempStart[attribute.buffer] += attribSize * byteSizeMap[attribute.type]; + } + } + + // first update - and create the buffers! + // only create a gl buffer if it actually gets + for (let i = 0; i < buffers.length; i++) + { + const buffer = buffers[i]; + + if (!buffer._glBuffers[CONTEXT_UID]) + { + buffer._glBuffers[CONTEXT_UID] = new GLBuffer(gl.createBuffer()); + } + } + + // TODO - maybe make this a data object? + // lets wait to see if we need to first! + const vao = gl.createVertexArray(); + + gl.bindVertexArray(vao); + + this.activateVao(geometry, program); + + gl.bindVertexArray(null); + + // add it to the cache! + geometry.glVertexArrayObjects[this.CONTEXT_UID][program.id] = vao; + + return vao; + } + + activateVao(geometry, program) + { + const gl = this.gl; + const CONTEXT_UID = this.CONTEXT_UID; + const buffers = geometry.buffers; + const attributes = geometry.attributes; + + if (geometry.indexBuffer) + { + // first update the index buffer if we have one.. + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, geometry.indexBuffer._glBuffers[CONTEXT_UID].buffer); + } + + let lastBuffer = null; + + // add a new one! + for (const j in attributes) + { + const attribute = attributes[j]; + const buffer = buffers[attribute.buffer]; + const glBuffer = buffer._glBuffers[CONTEXT_UID]; + + if (program.attributeData[j]) + { + if (lastBuffer !== glBuffer) + { + gl.bindBuffer(gl.ARRAY_BUFFER, glBuffer.buffer); + + lastBuffer = glBuffer; + } + + const location = program.attributeData[j].location; + + // TODO introduce state again + // we can optimise this for older devices that have no VAOs + gl.enableVertexAttribArray(location); + + gl.vertexAttribPointer(location, + attribute.size, + attribute.type || gl.FLOAT, + attribute.normalized, + attribute.stride, + attribute.start); + + if (attribute.instance) + { + // TODO calculate instance count based of this... + if (this.hasInstance) + { + gl.vertexAttribDivisor(location, 1); + } + else + { + throw new Error('geometry error, GPU Instancing is not supported on this device'); + } + } + } + } + } + + draw(type, size, start, instanceCount) + { + const gl = this.gl; + const geometry = this._activeGeometry; + + // TODO.. this should not change so maybe cache the function? + + if (geometry.indexBuffer) + { + if (geometry.instanced) + { + /* eslint-disable max-len */ + gl.drawElementsInstanced(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2, instanceCount || 1); + /* eslint-enable max-len */ + } + else + { + gl.drawElements(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2); + } + } + else + if (geometry.instanced) + { + // TODO need a better way to calculate size.. + gl.drawArraysInstanced(type, start, size || geometry.getSize(), instanceCount || 1); + } + else + { + gl.drawArrays(type, start, size || geometry.getSize()); + } + + return this; + } + + unbind() + { + this.gl.bindVertexArray(null); + this._activeVao = null; + this._activeGeometry = null; + } +} diff --git a/packages/core/src/geometry/utils/setVertexAttribArrays.js b/packages/core/src/geometry/utils/setVertexAttribArrays.js new file mode 100644 index 0000000..23b6959 --- /dev/null +++ b/packages/core/src/geometry/utils/setVertexAttribArrays.js @@ -0,0 +1,54 @@ +// var GL_MAP = {}; + +/** + * @param gl {WebGLRenderingContext} The current WebGL context + * @param attribs {*} + * @param state {*} + */ +export default function setVertexAttribArrays(gl, attribs, state) +{ + let i; + + if (state) + { + const tempAttribState = state.tempAttribState; + const attribState = state.attribState; + + for (i = 0; i < tempAttribState.length; i++) + { + tempAttribState[i] = false; + } + + // set the new attribs + for (i = 0; i < attribs.length; i++) + { + tempAttribState[attribs[i].attribute.location] = true; + } + + for (i = 0; i < attribState.length; i++) + { + if (attribState[i] !== tempAttribState[i]) + { + attribState[i] = tempAttribState[i]; + + if (state.attribState[i]) + { + gl.enableVertexAttribArray(i); + } + else + { + gl.disableVertexAttribArray(i); + } + } + } + } + else + { + for (i = 0; i < attribs.length; i++) + { + const attrib = attribs[i]; + + gl.enableVertexAttribArray(attrib.attribute.location); + } + } +} diff --git a/packages/core/src/index.js b/packages/core/src/index.js index 58c456a..fd1f547 100644 --- a/packages/core/src/index.js +++ b/packages/core/src/index.js @@ -1,30 +1,30 @@ import * as resources from './textures/resources'; -import * as systems from './renderers/systems'; +import * as systems from './systems'; -export { resources }; export { systems }; +export { resources }; -export { default as Renderer } from './renderers/Renderer'; -export { default as AbstractRenderer } from './renderers/AbstractRenderer'; -export { default as FrameBuffer } from './textures/FrameBuffer'; +export { default as System } from './System'; +export { default as Renderer } from './Renderer'; +export { default as AbstractRenderer } from './AbstractRenderer'; +export { default as FrameBuffer } from './framebuffer/FrameBuffer'; export { default as CubeTexture } from './textures/CubeTexture'; export { default as BaseTexture } from './textures/BaseTexture'; export { default as Texture } from './textures/Texture'; export { default as TextureMatrix } from './textures/TextureMatrix'; -export { default as RenderTexture } from './textures/RenderTexture'; -export { default as BaseRenderTexture } from './textures/BaseRenderTexture'; +export { default as RenderTexture } from './renderTexture/RenderTexture'; +export { default as BaseRenderTexture } from './renderTexture/BaseRenderTexture'; export { default as TextureUvs } from './textures/TextureUvs'; -export { default as State } from './renderers/State'; -export { default as ObjectRenderer } from './renderers/utils/ObjectRenderer'; -export { default as Quad } from './renderers/utils/Quad'; -export { default as QuadUv } from './renderers/utils/QuadUv'; -export { default as checkMaxIfStatmentsInShader } from './renderers/utils/checkMaxIfStatmentsInShader'; +export { default as State } from './state/State'; +export { default as ObjectRenderer } from './batch/ObjectRenderer'; +export { default as Quad } from './utils/Quad'; +export { default as QuadUv } from './utils/QuadUv'; +export { default as checkMaxIfStatmentsInShader } from './shader/utils/checkMaxIfStatmentsInShader'; export { default as Shader } from './shader/Shader'; export { default as Program } from './shader/Program'; export { default as UniformGroup } from './shader/UniformGroup'; -export { default as SpriteMaskFilter } from './renderers/filters/spriteMask/SpriteMaskFilter'; -export { default as Filter } from './renderers/filters/Filter'; +export { default as SpriteMaskFilter } from './filters/spriteMask/SpriteMaskFilter'; +export { default as Filter } from './filters/Filter'; export { default as Attribute } from './geometry/Attribute'; export { default as Buffer } from './geometry/Buffer'; export { default as Geometry } from './geometry/Geometry'; - diff --git a/packages/core/src/mask/MaskSystem.js b/packages/core/src/mask/MaskSystem.js new file mode 100644 index 0000000..99174c2 --- /dev/null +++ b/packages/core/src/mask/MaskSystem.js @@ -0,0 +1,199 @@ +import System from '../System'; +import AlphaMaskFilter from '../filters/spriteMask/SpriteMaskFilter'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class MaskSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + // TODO - we don't need both! + this.scissor = false; + this.scissorData = null; + this.scissorRenderTarget = null; + + this.enableScissor = false; + + this.alphaMaskPool = []; + this.alphaMaskIndex = 0; + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.DisplayObject} target - Display Object to push the mask to + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + push(target, maskData) + { + // TODO the root check means scissor rect will not + // be used on render textures more info here: + // https://github.com/pixijs/pixi.js/pull/3545 + + if (maskData.texture) + { + this.pushSpriteMask(target, maskData); + } + else if (this.enableScissor + && !this.scissor + && this.renderer._activeRenderTarget.root + && !this.renderer.stencil.stencilMaskStack.length + && maskData.isFastRect()) + { + const matrix = maskData.worldTransform; + + let rot = Math.atan2(matrix.b, matrix.a); + + // use the nearest degree! + rot = Math.round(rot * (180 / Math.PI)); + + if (rot % 90) + { + this.pushStencilMask(maskData); + } + else + { + this.pushScissorMask(target, maskData); + } + } + else + { + this.pushStencilMask(maskData); + } + } + + /** + * Removes the last mask from the mask stack and doesn't return it. + * + * @param {PIXI.DisplayObject} target - Display Object to pop the mask from + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + pop(target, maskData) + { + if (maskData.texture) + { + this.popSpriteMask(target, maskData); + } + else if (this.enableScissor && !this.renderer.stencil.stencilMaskStack.length) + { + this.popScissorMask(target, maskData); + } + else + { + this.popStencilMask(target, maskData); + } + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.RenderTarget} target - Display Object to push the sprite mask to + * @param {PIXI.Sprite} maskData - Sprite to be used as the mask + */ + pushSpriteMask(target, maskData) + { + let alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; + + if (!alphaMaskFilter) + { + alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; + } + + alphaMaskFilter[0].resolution = this.renderer.resolution; + alphaMaskFilter[0].maskSprite = maskData; + + // TODO - may cause issues! + target.filterArea = maskData.getBounds(true); + + this.renderer.filterSystem.pushFilter(target, alphaMaskFilter); + + this.alphaMaskIndex++; + } + + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popSpriteMask() + { + this.renderer.filterSystem.popFilter(); + this.alphaMaskIndex--; + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + pushStencilMask(maskData) + { + this.renderer.batch.flush(); + this.renderer.stencil.pushStencil(maskData); + } + + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popStencilMask() + { + // this.renderer.currentRenderer.stop(); + this.renderer.stencil.popStencil(); + } + + /** + * + * @param {PIXI.DisplayObject} target - Display Object to push the mask to + * @param {PIXI.Graphics} maskData - The masking data. + */ + pushScissorMask(target, maskData) + { + maskData.renderable = true; + + const renderTarget = this.renderer._activeRenderTarget; + + const bounds = maskData.getBounds(); + + bounds.fit(renderTarget.size); + maskData.renderable = false; + + this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); + + const resolution = this.renderer.resolution; + + this.renderer.gl.scissor( + bounds.x * resolution, + (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, + bounds.width * resolution, + bounds.height * resolution + ); + + this.scissorRenderTarget = renderTarget; + this.scissorData = maskData; + this.scissor = true; + } + + /** + * + * + */ + popScissorMask() + { + this.scissorRenderTarget = null; + this.scissorData = null; + this.scissor = false; + + // must be scissor! + const gl = this.renderer.gl; + + gl.disable(gl.SCISSOR_TEST); + } +} diff --git a/packages/core/src/mask/StencilSystem.js b/packages/core/src/mask/StencilSystem.js new file mode 100644 index 0000000..62796a5 --- /dev/null +++ b/packages/core/src/mask/StencilSystem.js @@ -0,0 +1,133 @@ +import System from '../System'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class StencilSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + this.stencilMaskStack = []; + } + + /** + * Changes the mask stack that is used by this System. + * + * @param {PIXI.Graphics[]} stencilMaskStack - The mask stack + */ + setMaskStack(stencilMaskStack) + { + const gl = this.renderer.gl; + + if (stencilMaskStack.length !== this.stencilMaskStack.length) + { + if (stencilMaskStack.length === 0) + { + gl.disable(gl.STENCIL_TEST); + } + else + { + gl.enable(gl.STENCIL_TEST); + } + } + + this.stencilMaskStack = stencilMaskStack; + } + + /** + * Applies the Mask and adds it to the current stencil stack. @alvin + * + * @param {PIXI.Graphics} graphics - The mask + */ + pushStencil(graphics) + { + this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); + + // this.renderer._activeRenderTarget.attachStencilBuffer(); + + const gl = this.renderer.gl; + const prevMaskCount = this.stencilMaskStack.length; + + if (prevMaskCount === 0) + { + gl.enable(gl.STENCIL_TEST); + } + + this.stencilMaskStack.push(graphics); + + // Increment the refference stencil value where the new mask overlaps with the old ones. + gl.colorMask(false, false, false, false); + gl.stencilFunc(gl.EQUAL, prevMaskCount, this._getBitwiseMask()); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.INCR); + this.renderer.plugins.graphics.render(graphics); + + this._useCurrent(); + } + + /** + * Removes the last mask from the stencil stack. @alvin + */ + popStencil() + { + this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); + + const gl = this.renderer.gl; + const graphics = this.stencilMaskStack.pop(); + + if (this.stencilMaskStack.length === 0) + { + // the stack is empty! + gl.disable(gl.STENCIL_TEST); + gl.clear(gl.STENCIL_BUFFER_BIT); + gl.clearStencil(0); + } + else + { + // Decrement the refference stencil value where the popped mask overlaps with the other ones + gl.colorMask(false, false, false, false); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.DECR); + this.renderer.plugins.graphics.render(graphics); + + this._useCurrent(); + } + } + + /** + * Setup renderer to use the current stencil data. + */ + _useCurrent() + { + const gl = this.renderer.gl; + + gl.colorMask(true, true, true, true); + gl.stencilFunc(gl.EQUAL, this.stencilMaskStack.length, this._getBitwiseMask()); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP); + } + + /** + * Fill 1s equal to the number of acitve stencil masks. + * + * @return {number} The bitwise mask. + */ + _getBitwiseMask() + { + return (1 << this.stencilMaskStack.length) - 1; + } + + /** + * Destroys the mask stack. + * + */ + destroy() + { + super.destroy(this); + + this.stencilMaskStack = null; + } +} diff --git a/packages/core/src/projection/ProjectionSystem.js b/packages/core/src/projection/ProjectionSystem.js new file mode 100644 index 0000000..6e63b73 --- /dev/null +++ b/packages/core/src/projection/ProjectionSystem.js @@ -0,0 +1,73 @@ +import System from '../System'; +import { Matrix } from '@pixi/math'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class ProjectionSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.projectionMatrix = new Matrix(); + } + + update(destinationFrame, sourceFrame, resolution, root) + { + this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; + this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; + + this.calculateProjection(this.destinationFrame, this.sourceFrame, resolution, root); + + this.renderer.globalUniforms.uniforms.projectionMatrix = this.projectionMatrix; + this.renderer.globalUniforms.update(); + } + + /** + * Updates the projection matrix based on a projection frame (which is a rectangle) + * + * @param {Rectangle} destinationFrame - The destination frame. + * @param {Rectangle} sourceFrame - The source frame. + */ + calculateProjection(destinationFrame, sourceFrame, resolution, root) + { + const pm = this.projectionMatrix; + + // I don't think we will need this line.. + // pm.identity(); + + if (!root) + { + pm.a = (1 / destinationFrame.width * 2) * resolution; + pm.d = (1 / destinationFrame.height * 2) * resolution; + + pm.tx = -1 - (sourceFrame.x * pm.a); + pm.ty = -1 - (sourceFrame.y * pm.d); + } + else + { + pm.a = (1 / destinationFrame.width * 2) * resolution; + pm.d = (-1 / destinationFrame.height * 2) * resolution; + + pm.tx = -1 - (sourceFrame.x * pm.a); + pm.ty = 1 - (sourceFrame.y * pm.d); + } + } + + /** + * Sets the transform of the active render target to the given matrix + * + * @param {PIXI.Matrix} matrix - The transformation matrix + */ + setTransform()// matrix) + { + // this._activeRenderTarget.transform = matrix; + } +} diff --git a/packages/core/src/renderTexture/BaseRenderTexture.js b/packages/core/src/renderTexture/BaseRenderTexture.js new file mode 100644 index 0000000..88eb83b --- /dev/null +++ b/packages/core/src/renderTexture/BaseRenderTexture.js @@ -0,0 +1,137 @@ +import BaseTexture from '../textures/BaseTexture'; +import FrameBuffer from '../framebuffer/FrameBuffer'; + +/** + * A BaseRenderTexture is a special texture that allows any PixiJS display object to be rendered to it. + * + * __Hint__: All DisplayObjects (i.e. Sprites) that render to a BaseRenderTexture should be preloaded + * otherwise black rectangles will be drawn instead. + * + * A BaseRenderTexture takes a snapshot of any Display Object given to its render method. The position + * and rotation of the given Display Objects is ignored. For example: + * + * ```js + * let renderer = PIXI.autoDetectRenderer(1024, 1024, { view: canvas, ratio: 1 }); + * let baseRenderTexture = new PIXI.BaseRenderTexture(renderer, 800, 600); + * let sprite = PIXI.Sprite.fromImage("spinObj_01.png"); + * + * sprite.position.x = 800/2; + * sprite.position.y = 600/2; + * sprite.anchor.x = 0.5; + * sprite.anchor.y = 0.5; + * + * baseRenderTexture.render(sprite); + * ``` + * + * The Sprite in this case will be rendered using its local transform. To render this sprite at 0,0 + * you can clear the transform + * + * ```js + * + * sprite.setTransform() + * + * let baseRenderTexture = new PIXI.BaseRenderTexture(100, 100); + * let renderTexture = new PIXI.RenderTexture(baseRenderTexture); + * + * renderer.render(sprite, renderTexture); // Renders to center of RenderTexture + * ``` + * + * @class + * @extends PIXI.BaseTexture + * @memberof PIXI + */ +export default class BaseRenderTexture extends BaseTexture +{ + /** + * @param {object} [options] + * @param {number} [options.width=100] - The width of the base render texture + * @param {number} [options.height=100] - The height of the base render texture + * @param {PIXI.SCALE_MODES} [options.scaleMode] - See {@link PIXI.SCALE_MODES} for possible values + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the texture being generated + */ + constructor(options) + { + if (typeof options === 'number') + { + /* eslint-disable prefer-rest-params */ + // Backward compatibility of signature + const width = arguments[0]; + const height = arguments[1]; + const scaleMode = arguments[2]; + const resolution = arguments[3]; + + options = { width, height, scaleMode, resolution }; + /* eslint-enable prefer-rest-params */ + } + + super(null, options); + + const { width, height } = options || {}; + + // Set defaults + this.mipmap = false; + this.width = Math.ceil(width) || 100; + this.height = Math.ceil(height) || 100; + this.valid = true; + + /** + * A map of renderer IDs to webgl renderTargets + * + * @private + * @member {object} + */ + // this._glRenderTargets = {}; + + /** + * A reference to the canvas render target (we only need one as this can be shared across renderers) + * + * @private + * @member {object} + */ + this._canvasRenderTarget = null; + + this.clearColor = [0, 0, 0, 0]; + + this.frameBuffer = new FrameBuffer(width * this.resolution, height * this.resolution) + .addColorTexture(0, this); + + // TODO - could this be added the systems? + + /** + * The data structure for the stencil masks + * + * @member {PIXI.Graphics[]} + */ + this.stencilMaskStack = []; + + /** + * The data structure for the filters + * + * @member {PIXI.Graphics[]} + */ + this.filterStack = [{}]; + } + + /** + * Resizes the BaseRenderTexture. + * + * @param {number} width - The width to resize to. + * @param {number} height - The height to resize to. + */ + resize(width, height) + { + width = Math.ceil(width); + height = Math.ceil(height); + this.frameBuffer.resize(width * this.resolution, height * this.resolution); + } + + /** + * Destroys this texture + * + */ + destroy() + { + super.destroy(true); + this.renderer = null; + } +} diff --git a/packages/core/src/renderTexture/RenderTexture.js b/packages/core/src/renderTexture/RenderTexture.js new file mode 100644 index 0000000..f1ecf1f --- /dev/null +++ b/packages/core/src/renderTexture/RenderTexture.js @@ -0,0 +1,153 @@ +import BaseRenderTexture from './BaseRenderTexture'; +import Texture from '../textures/Texture'; + +/** + * A RenderTexture is a special texture that allows any PixiJS display object to be rendered to it. + * + * __Hint__: All DisplayObjects (i.e. Sprites) that render to a RenderTexture should be preloaded + * otherwise black rectangles will be drawn instead. + * + * A RenderTexture takes a snapshot of any Display Object given to its render method. For example: + * + * ```js + * let renderer = PIXI.autoDetectRenderer(1024, 1024, { view: canvas, ratio: 1 }); + * let renderTexture = PIXI.RenderTexture.create(800, 600); + * let sprite = PIXI.Sprite.fromImage("spinObj_01.png"); + * + * sprite.position.x = 800/2; + * sprite.position.y = 600/2; + * sprite.anchor.x = 0.5; + * sprite.anchor.y = 0.5; + * + * renderer.render(sprite, renderTexture); + * ``` + * + * The Sprite in this case will be rendered using its local transform. To render this sprite at 0,0 + * you can clear the transform + * + * ```js + * + * sprite.setTransform() + * + * let renderTexture = new PIXI.RenderTexture.create(100, 100); + * + * renderer.render(sprite, renderTexture); // Renders to center of RenderTexture + * ``` + * + * @class + * @extends PIXI.Texture + * @memberof PIXI + */ +export default class RenderTexture extends Texture +{ + /** + * @param {PIXI.BaseRenderTexture} baseRenderTexture - The renderer used for this RenderTexture + * @param {PIXI.Rectangle} [frame] - The rectangle frame of the texture to show + */ + constructor(baseRenderTexture, frame) + { + // support for legacy.. + let _legacyRenderer = null; + + if (!(baseRenderTexture instanceof BaseRenderTexture)) + { + /* eslint-disable prefer-rest-params, no-console */ + const width = arguments[1]; + const height = arguments[2]; + const scaleMode = arguments[3]; + const resolution = arguments[4]; + + // we have an old render texture.. + console.warn(`Please use RenderTexture.create(${width}, ${height}) instead of the ctor directly.`); + _legacyRenderer = arguments[0]; + /* eslint-enable prefer-rest-params, no-console */ + + frame = null; + baseRenderTexture = new BaseRenderTexture({ + width, + height, + scaleMode, + resolution, + }); + } + + /** + * The base texture object that this texture uses + * + * @member {BaseTexture} + */ + super(baseRenderTexture, frame); + + this.legacyRenderer = _legacyRenderer; + + /** + * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. + * + * @member {boolean} + */ + this.valid = true; + + /** + * FilterSystem temporary storage + * @private + * @member {PIXI.Rectangle} + */ + this.filterFrame = null; + + this._updateUvs(); + } + + /** + * Resizes the RenderTexture. + * + * @param {number} width - The width to resize to. + * @param {number} height - The height to resize to. + * @param {boolean} [resizeBaseTexture=true] - Should the baseTexture.width and height values be resized as well? + */ + resize(width, height, resizeBaseTexture = true) + { + width = Math.ceil(width); + height = Math.ceil(height); + + // TODO - could be not required.. + this.valid = (width > 0 && height > 0); + + this._frame.width = this.orig.width = width; + this._frame.height = this.orig.height = height; + + if (resizeBaseTexture) + { + this.baseTexture.resize(width, height); + } + + this._updateUvs(); + } + + /** + * A short hand way of creating a render texture. + * + * @param {object} [options] - Options + * @param {number} [options.width=100] - The width of the render texture + * @param {number} [options.height=100] - The height of the render texture + * @param {number} [options.scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the texture being generated + * @return {PIXI.RenderTexture} The new render texture + */ + static create(options) + { + // fallback, old-style: create(width, height, scaleMode, resolution) + if (typeof options === 'number') + { + /* eslint-disable prefer-rest-params */ + options = { + width: options, + height: arguments[1], + scaleMode: arguments[2], + resolution: arguments[3], + }; + /* eslint-enable prefer-rest-params */ + } + + return new RenderTexture(new BaseRenderTexture(options)); + } +} diff --git a/packages/core/src/renderTexture/RenderTextureSystem.js b/packages/core/src/renderTexture/RenderTextureSystem.js new file mode 100644 index 0000000..8d0ca61 --- /dev/null +++ b/packages/core/src/renderTexture/RenderTextureSystem.js @@ -0,0 +1,115 @@ +import System from '../System'; +import { Rectangle } from '@pixi/math'; + +const tempRect = new Rectangle(); + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class RenderTextureSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.clearColor = renderer._backgroundColorRgba; + + // TODO moe this property somewhere else! + this.defaultMaskStack = []; + this.defaultFilterStack = [{}]; + + // empty render texture? + this.renderTexture = null; + + this.destinationFrame = new Rectangle(); + } + + bind(renderTexture, sourceFrame, destinationFrame) + { + // TODO - do we want this?? + if (this.renderTexture === renderTexture) return; + this.renderTexture = renderTexture; + + const renderer = this.renderer; + + if (renderTexture) + { + const baseTexture = renderTexture.baseTexture; + + if (!destinationFrame) + { + tempRect.width = baseTexture.realWidth; + tempRect.height = baseTexture.realHeight; + + destinationFrame = tempRect; + } + + if (!sourceFrame) + { + sourceFrame = destinationFrame; + } + + this.renderer.framebuffer.bind(baseTexture.frameBuffer, destinationFrame); + + this.renderer.projection.update(destinationFrame, sourceFrame, baseTexture.resolution, false); + this.renderer.stencil.setMaskStack(baseTexture.stencilMaskStack); + } + else + { + // TODO these validation checks happen deeper down.. + // thing they can be avoided.. + if (!destinationFrame) + { + tempRect.width = renderer.width; + tempRect.height = renderer.height; + + destinationFrame = tempRect; + } + + if (!sourceFrame) + { + sourceFrame = destinationFrame; + } + + renderer.framebuffer.bind(null, destinationFrame); + + // TODO store this.. + this.renderer.projection.update(destinationFrame, sourceFrame, this.renderer.resolution, true); + this.renderer.stencil.setMaskStack(this.defaultMaskStack); + } + + this.destinationFrame.copyFrom(destinationFrame); + } + + /** + * Erases the render texture and fills the drawing area with a colour + * + * @param {number} [clearColor] - The colour + * @return {PIXI.Renderer} Returns itself. + */ + clear(clearColor) + { + if (this.renderTexture) + { + clearColor = clearColor || this.renderTexture.baseTexture.clearColor; + } + else + { + clearColor = clearColor || this.clearColor; + } + + this.renderer.framebuffer.clear(clearColor[0], clearColor[1], clearColor[2], clearColor[3]); + } + + resize()// screenWidth, screenHeight) + { + // resize the root only! + this.bind(null); + } +} diff --git a/packages/core/src/renderers/AbstractRenderer.js b/packages/core/src/renderers/AbstractRenderer.js deleted file mode 100644 index 928670f..0000000 --- a/packages/core/src/renderers/AbstractRenderer.js +++ /dev/null @@ -1,353 +0,0 @@ -import { hex2string, hex2rgb } from '@pixi/utils'; -import { Matrix, Rectangle } from '@pixi/math'; -import { RENDERER_TYPE } from '@pixi/constants'; -import { settings } from '@pixi/settings'; -import { Container } from '@pixi/display'; -import RenderTexture from '../textures/RenderTexture'; -import EventEmitter from 'eventemitter3'; - -const tempMatrix = new Matrix(); - -/** - * 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 EventEmitter - * @memberof PIXI - */ -export default class AbstractRenderer extends EventEmitter -{ - // eslint-disable-next-line valid-jsdoc - /** - * @param {string} system - The name of the system this renderer is for. - * @param {object} [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.transparent=false] - If the render view is transparent, default false - * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false - * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) - * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. The - * resolution of the renderer retina would be 2. - * @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 {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when rendering, - * stopping pixel interpolation. - */ - constructor(system, options, arg2, arg3) - { - super(); - - // Support for constructor(system, screenWidth, screenHeight, options) - if (typeof options === 'number') - { - options = Object.assign({ - width: options, - height: arg2 || settings.RENDER_OPTIONS.height, - }, arg3); - } - - // 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 = RENDERER_TYPE.UNKNOWN; - - /** - * Measurements of the screen. (0, 0, screenWidth, screenHeight) - * - * Its safe to use as filterArea or hitArea for 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 1 - */ - this.resolution = options.resolution || settings.RESOLUTION; - - /** - * Whether the render view is transparent - * - * @member {boolean} - */ - this.transparent = options.transparent; - - /** - * Whether css dimensions of canvas view should be resized to screen dimensions automatically - * - * @member {boolean} - */ - this.autoResize = options.autoResize || false; - - /** - * Tracks the blend modes useful for this renderer. - * - * @member {object} - */ - this.blendModes = null; - - /** - * 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; - - /** - * If true PixiJS will Math.floor() x/y values when rendering, stopping pixel interpolation. - * Handy for crisp pixel art and speed on legacy devices. - * - * @member {boolean} - */ - this.roundPixels = options.roundPixels; - - /** - * The background color as a number. - * - * @member {number} - * @private - */ - this._backgroundColor = 0x000000; - - /** - * The background color as an [R, G, B] array. - * - * @member {number[]} - * @private - */ - this._backgroundColorRgba = [0, 0, 0, 0]; - - /** - * The background color as a string. - * - * @member {string} - * @private - */ - this._backgroundColorString = '#000000'; - - this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter - - /** - * This temporary display object used as the parent of the currently being rendered item - * - * @member {PIXI.DisplayObject} - * @private - */ - this._tempDisplayObjectParent = new Container(); - - /** - * The last root object that the renderer tried to render. - * - * @member {PIXI.DisplayObject} - * @private - */ - this._lastObjectRendered = this._tempDisplayObjectParent; - - /** - * Collection of plugins. - * @readonly - * @member {object} - */ - this.plugins = {}; - } - - /** - * Initialize the plugins. - * - * @protected - * @param {object} staticMap - The dictionary of staticly saved plugins. - */ - initPlugins(staticMap) - { - 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() - { - return this.view.width; - } - - /** - * Same as view.height, actual number of pixels in the canvas by vertical - * - * @member {number} - * @readonly - * @default 600 - */ - get height() - { - return this.view.height; - } - - /** - * Resizes the screen and canvas to the specified width and height - * Canvas dimensions are multiplied by resolution - * - * @param {number} screenWidth - the new width of the screen - * @param {number} screenHeight - the new height of the screen - */ - resize(screenWidth, screenHeight) - { - this.screen.width = screenWidth; - this.screen.height = screenHeight; - - this.view.width = screenWidth * this.resolution; - this.view.height = screenHeight * this.resolution; - - if (this.autoResize) - { - this.view.style.width = `${screenWidth}px`; - this.view.style.height = `${screenHeight}px`; - } - } - - /** - * 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 {PIXI.DisplayObject} displayObject - The displayObject the object will be generated from - * @param {number} scaleMode - Should be one of the scaleMode consts - * @param {number} resolution - The resolution / device pixel ratio of the texture being generated - * @param {PIXI.Rectangle} [region] - The region of the displayObject, that shall be rendered, - * if no region is specified, defaults to the local bounds of the displayObject. - * @return {PIXI.Texture} a texture of the graphics object - */ - generateTexture(displayObject, scaleMode, resolution, region) - { - region = region || displayObject.getLocalBounds(); - - // 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(region.width | 0, region.height | 0, scaleMode, resolution); - - tempMatrix.tx = -region.x; - tempMatrix.ty = -region.y; - - this.render(displayObject, renderTexture, false, tempMatrix, true); - - return renderTexture; - } - - /** - * Removes everything from the renderer and optionally removes the Canvas DOM element. - * - * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. - */ - destroy(removeView) - { - 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); - } - - this.plugins = null; - - this.type = RENDERER_TYPE.UNKNOWN; - - this.view = null; - - this.screen = null; - - this.resolution = 0; - - this.transparent = false; - - this.autoResize = false; - - this.blendModes = null; - - this.options = null; - - this.preserveDrawingBuffer = false; - this.clearBeforeRender = false; - - this.roundPixels = false; - - this._backgroundColor = 0; - this._backgroundColorRgba = null; - this._backgroundColorString = null; - - this._tempDisplayObjectParent = null; - this._lastObjectRendered = null; - } - - /** - * The background color to fill if not transparent - * - * @member {number} - */ - get backgroundColor() - { - return this._backgroundColor; - } - - set backgroundColor(value) // eslint-disable-line require-jsdoc - { - this._backgroundColor = value; - this._backgroundColorString = hex2string(value); - hex2rgb(value, this._backgroundColorRgba); - } -} diff --git a/packages/core/src/renderers/Renderer.js b/packages/core/src/renderers/Renderer.js deleted file mode 100644 index a2020b1..0000000 --- a/packages/core/src/renderers/Renderer.js +++ /dev/null @@ -1,329 +0,0 @@ -import AbstractRenderer from './AbstractRenderer'; -import { sayHello } from '@pixi/utils'; -import MaskSystem from './systems/MaskSystem'; -import StencilSystem from './systems/StencilSystem'; -import FilterSystem from './systems/filter/FilterSystem'; -import FramebufferSystem from './systems/FramebufferSystem'; -import RenderTextureSystem from './systems/RenderTextureSystem'; -import TextureSystem from './systems/textures/TextureSystem'; -import ProjectionSystem from './systems/ProjectionSystem'; -import StateSystem from './systems/StateSystem'; -import GeometrySystem from './systems/geometry/GeometrySystem'; -import ShaderSystem from './systems/shader/ShaderSystem'; -import ContextSystem from './systems/ContextSystem'; -import BatchSystem from './systems/BatchSystem'; -import TextureGCSystem from './systems/textures/TextureGCSystem'; -import { RENDERER_TYPE } from '@pixi/constants'; -import UniformGroup from '../shader/UniformGroup'; -import { Matrix } from '@pixi/math'; -import Runner from 'mini-runner'; - -/** - * 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 Render works by automatically managing webGLBatchs. - * 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 :) - * - * @class - * @memberof PIXI - * @extends PIXI.AbstractRenderer - */ -export default class Renderer extends AbstractRenderer -{ - // eslint-disable-next-line valid-jsdoc - /** - * - * @param {object} [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.transparent=false] - If the render view is transparent, default false - * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false - * @param {boolean} [options.antialias=false] - sets antialias. If not available natively then FXAA - * antialiasing is used - * @param {boolean} [options.forceFXAA=false] - forces FXAA antialiasing to be used over native. - * FXAA is faster, but may not always look as great - * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. - * The resolution of the renderer retina would be 2. - * @param {boolean} [options.clearBeforeRender=true] - This sets if the renderer will clear - * the canvas or not before the new render pass. If you wish to set this to false, you *must* set - * preserveDrawingBuffer to `true`. - * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, - * enable this if you need to call toDataUrl on the webgl context. - * @param {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when - * rendering, stopping pixel interpolation. - * @param {number} [options.backgroundColor=0x000000] - The background color of the rendered area - * (shown if not transparent). - * @param {boolean} [options.legacy=false] - If true PixiJS will aim to ensure compatibility - * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. - * @param {string} [options.powerPreference] - Parameter passed to webgl context, set to "high-performance" - * for devices with dual graphics card - */ - constructor(options, arg2, arg3) - { - super('WebGL', options, arg2, arg3); - - /** - * The type of this renderer as a standardised const - * - * @member {number} - * @see PIXI.RENDERER_TYPE - */ - this.type = RENDERER_TYPE.WEBGL; - - // this will be set by the contextSystem (this.context) - this.gl = null; - this.CONTEXT_UID = 0; - this.legacy = !!options.legacy; - - // TODO legacy! - - // runners! - this.runners = { - destroy: new Runner('destroy'), - contextChange: new Runner('contextChange', 1), - reset: new Runner('reset'), - update: new Runner('update'), - postrender: new Runner('postrender'), - prerender: new Runner('prerender'), - resize: new Runner('resize', 2), - }; - - 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(GeometrySystem, 'geometry') - .addSystem(FramebufferSystem, 'framebuffer') - .addSystem(StencilSystem, 'stencil') - .addSystem(ProjectionSystem, 'projection') - .addSystem(TextureGCSystem, 'textureGC') - .addSystem(FilterSystem, 'filter') - .addSystem(RenderTextureSystem, 'renderTexture') - .addSystem(BatchSystem, 'batch'); - - this.initPlugins(Renderer.__plugins); - - /** - * The options passed in to create a new webgl context. - * - * @member {object} - * @private - */ - if (options.context) - { - this.context.initFromContext(options.context); - } - else - { - this.context.initFromOptions({ - alpha: this.transparent, - antialias: options.antialias, - premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', - stencil: true, - 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); - } - - addSystem(_class, name) - { - if (!name) - { - name = _class.name; - } - - // TODO - read name from class.name.. - - /* - if(name.includes('System')) - { - name = name.replace('System', ''); - name = name.charAt(0).toLowerCase() + name.slice(1); - } - */ - - const system = new _class(this); - - if (this[name]) - { - throw new Error(`Whoops! ${name} is already a manger`); - } - - this[name] = system; - - for (const i in this.runners) - { - this.runners[i].add(system); - } - - return this; - - /** - * 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. - */ - } - - /** - * Renders the object to its webGL view - * - * @param {PIXI.DisplayObject} displayObject - the object to be rendered - * @param {PIXI.RenderTexture} renderTexture - The render texture to render to. - * @param {boolean} [clear] - Should the canvas be cleared before the new render - * @param {PIXI.Transform} [transform] - A transform to apply to the render texture before rendering. - * @param {boolean} [skipUpdateTransform] - Should we skip the update transform pass? - */ - render(displayObject, renderTexture, clear, transform, skipUpdateTransform) - { - // can be handy to know! - this.renderingToScreen = !renderTexture; - - this.runners.prerender.run(); - this.emit('prerender'); - - // 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.parent; - - displayObject.parent = this._tempDisplayObjectParent; - displayObject.updateTransform(); - displayObject.parent = 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.run(); - - this.emit('postrender'); - } - - /** - * Resizes the webGL view to the specified width and height. - * - * @param {number} screenWidth - the new width of the screen - * @param {number} screenHeight - the new height of the screen - */ - resize(screenWidth, screenHeight) - { - AbstractRenderer.prototype.resize.call(this, screenWidth, screenHeight); - - this.runners.resize.run(screenWidth, screenHeight); - } - - /** - * Resets the WebGL state so you can render things however you fancy! - * - * @return {PIXI.Renderer} Returns itself. - */ - reset() - { - this.runners.reset.run(); - - return this; - } - - clear() - { - this.framebuffer.bind(); - this.framebuffer.clear(); - } - - /** - * Removes everything from the renderer (event listeners, spritebatch, etc...) - * - * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. - * See: https://github.com/pixijs/pixi.js/issues/2233 - */ - destroy(removeView) - { - this.runners.destroy.run(); - - // call base destroy - super.destroy(removeView); - - // TODO nullify all the managers.. - this.gl = null; - } - - /** - * 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 - * builds and excluding plugins. - * @name PIXI.Renderer#plugins - * @type {object} - * @readonly - * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. - * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. - * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. - * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. - */ - - /** - * Adds a plugin to the renderer. - * - * @method - * @param {string} pluginName - The name of the plugin. - * @param {Function} ctor - The constructor function or class for the plugin. - */ - static registerPlugin(pluginName, ctor) - { - Renderer.__plugins = Renderer.__plugins || {}; - Renderer.__plugins[pluginName] = ctor; - } -} diff --git a/packages/core/src/renderers/State.js b/packages/core/src/renderers/State.js deleted file mode 100644 index fa3f221..0000000 --- a/packages/core/src/renderers/State.js +++ /dev/null @@ -1,163 +0,0 @@ -/* eslint-disable max-len */ - -const BLEND = 0; -const OFFSET = 1; -const CULLING = 2; -const DEPTH_TEST = 3; -const WINDING = 4; - -/** - * This is a webGL state. It is passed The WebGL StateManager. - * Each mesh renderered may require webGL to be in a different state. - * For example you may want different blend mode or to enable polygon offsets - * - * @class - * @memberof PIXI - */ -export default class State -{ - /** - * - */ - constructor() - { - this.data = 0; - - this.blendMode = 0; - this.polygonOffset = 0; - - this.blend = true; - // this.depthTest = true; - } - - /** - * Activates blending of the computed fragment color values - * - * @member {boolean} - */ - get blend() - { - return !!(this.data & (1 << BLEND)); - } - - set blend(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << BLEND)) !== value) - { - this.data ^= (1 << BLEND); - } - } - - /** - * Activates adding an offset to depth values of polygon's fragments - * - * @member {boolean} - * @default false - */ - get offsets() - { - return !!(this.data & (1 << OFFSET)); - } - - set offsets(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << OFFSET)) !== value) - { - this.data ^= (1 << OFFSET); - } - } - - /** - * Activates culling of polygons. - * - * @member {boolean} - * @default false - */ - get culling() - { - return !!(this.data & (1 << CULLING)); - } - - set culling(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << CULLING)) !== value) - { - this.data ^= (1 << CULLING); - } - } - - /** - * Activates depth comparisons and updates to the depth buffer. - * - * @member {boolean} - * @default false - */ - get depthTest() - { - return !!(this.data & (1 << DEPTH_TEST)); - } - - set depthTest(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << DEPTH_TEST)) !== value) - { - this.data ^= (1 << DEPTH_TEST); - } - } - - /** - * Specifies whether or not front or back-facing polygons can be culled. - * @member {boolean} - * @default false - */ - get clockwiseFrontFace() - { - return !!(this.data & (1 << WINDING)); - } - - set clockwiseFrontFace(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << WINDING)) !== value) - { - this.data ^= (1 << WINDING); - } - } - - /** - * The blend mode to be applied when this state is set. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. - * Setting this mode to anything other than NO_BLEND will automatically switch blending on. - * - * @member {boolean} - * @default PIXI.BLEND_MODES.NORMAL - * @see PIXI.BLEND_MODES - */ - get blendMode() - { - return this._blendMode; - } - - set blendMode(value) // eslint-disable-line require-jsdoc - { - // 17 is NO BLEND - this.blend = (value !== 17); - this._blendMode = value; - } - - /** - * The polygon offset. Setting this property to anything other than 0 will automatically enable poygon offset fill. - * - * @member {number} - * @default 0 - */ - get polygonOffset() - { - return this._polygonOffset; - } - - set polygonOffset(value) // eslint-disable-line require-jsdoc - { - this.offsets = !!value; - this._polygonOffset = value; - } -} - diff --git a/packages/core/src/renderers/filters/Filter.js b/packages/core/src/renderers/filters/Filter.js deleted file mode 100644 index 2073b8d..0000000 --- a/packages/core/src/renderers/filters/Filter.js +++ /dev/null @@ -1,140 +0,0 @@ -import Shader from '../../shader/Shader'; -import Program from '../../shader/Program'; -import State from '../State'; -import { settings } from '@pixi/settings'; -// import extractUniformsFromSrc from './extractUniformsFromSrc'; -import defaultVertex from './defaultFilter.vert'; -import defaultFragment from './defaultFilter.frag'; - -// let math = require('../../math'); -/** - * @class - * @memberof PIXI - * @extends PIXI.Shader - */ -export default class Filter extends Shader -{ - /** - * @param {string} [vertexSrc] - The source of the vertex shader. - * @param {string} [fragmentSrc] - The source of the fragment shader. - * @param {object} [uniforms] - Custom uniforms to use to augment the built-in ones. - */ - constructor(vertexSrc, fragmentSrc, uniforms) - { - const program = Program.from(vertexSrc, fragmentSrc); - - super(program, uniforms); - - /** - * The padding of the filter. Some filters require extra space to breath such as a blur. - * Increasing this will add extra width and height to the bounds of the object that the - * filter is applied to. - * - * @member {number} - */ - this.padding = 0; - - /** - * The resolution of the filter. Setting this to be lower will lower the quality but - * increase the performance of the filter. - * - * @member {number} - */ - this.resolution = settings.RESOLUTION; - - /** - * If enabled is true the filter is applied, if false it will not. - * - * @member {boolean} - */ - this.enabled = true; - - /** - * If enabled, PixiJS will fit the filter area into boundaries for better performance. - * Switch it off if it does not work for specific shader. - * - * @member {boolean} - */ - this.autoFit = true; - - /** - * Legacy filters use position and uvs from attributes - * @member {boolean} - * @readonly - */ - this.legacy = !!this.program.attributeData.aTextureCoord; - - /** - * the webGL state the filter requires to render - * @member {PIXI.State} - */ - this.state = new State(); - } - - /** - * Applies the filter - * - * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from - * @param {PIXI.RenderTarget} input - The input render target. - * @param {PIXI.RenderTarget} output - The target to output to. - * @param {boolean} clear - Should the output be cleared before rendering to it - * @param {object} [currentState] - It's current state of filter. - * There are some useful properties in the currentState : - * target, filters, sourceFrame, destinationFrame, renderTarget, resolution - */ - apply(filterManager, input, output, clear, currentState, derp) // eslint-disable-line no-unused-vars - { - // do as you please! - - filterManager.applyFilter(this, input, output, clear, currentState, derp); - - // or just do a regular render.. - } - - /** - * Sets the blendmode of the filter - * - * @member {number} - * @default PIXI.BLEND_MODES.NORMAL - */ - get blendMode() - { - return this.state.blendMode; - } - - set blendMode(value) // eslint-disable-line require-jsdoc - { - this.state.blendMode = value; - } - - /** - * The default vertex shader source - * - * @static - * @constant - */ - static get defaultVertexSrc() - { - return defaultVertex; - } - - /** - * The default fragment shader source - * - * @static - * @constant - */ - static get defaultFragmentSrc() - { - return defaultFragment; - } -} - -/** - * Used for caching shader IDs - * - * @static - * @private - */ -Filter.SOURCE_KEY_MAP = {}; - diff --git a/packages/core/src/renderers/filters/defaultFilter.frag b/packages/core/src/renderers/filters/defaultFilter.frag deleted file mode 100644 index a14c1be..0000000 --- a/packages/core/src/renderers/filters/defaultFilter.frag +++ /dev/null @@ -1,22 +0,0 @@ -varying vec2 vTextureCoord; -varying vec2 vFilterCoord; - -uniform sampler2D uSampler; -uniform sampler2D filterSampler; - -void main(void){ - vec4 masky = texture2D(filterSampler, vFilterCoord); - vec4 sample = texture2D(uSampler, vTextureCoord); - vec4 color; - if(mod(vFilterCoord.x, 1.0) > 0.5) - { - color = vec4(1.0, 0.0, 0.0, 1.0); - } - else - { - color = vec4(0.0, 1.0, 0.0, 1.0); - } - // gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0); - gl_FragColor = mix(sample, masky, 0.5); - gl_FragColor *= sample.a; -} diff --git a/packages/core/src/renderers/filters/defaultFilter.vert b/packages/core/src/renderers/filters/defaultFilter.vert deleted file mode 100644 index e1febca..0000000 --- a/packages/core/src/renderers/filters/defaultFilter.vert +++ /dev/null @@ -1,14 +0,0 @@ -attribute vec2 aVertexPosition; -attribute vec2 aTextureCoord; - -uniform mat3 projectionMatrix; -uniform mat3 filterMatrix; - -varying vec2 vTextureCoord; -varying vec2 vFilterCoord; - -void main(void){ - gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); - vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy; - vTextureCoord = aTextureCoord ; -} diff --git a/packages/core/src/renderers/filters/filterTransforms.js b/packages/core/src/renderers/filters/filterTransforms.js deleted file mode 100644 index 87a3cd6..0000000 --- a/packages/core/src/renderers/filters/filterTransforms.js +++ /dev/null @@ -1,50 +0,0 @@ -import { Matrix } from '@pixi/math'; - -/** - * Calculates the mapped matrix - * @param filterArea {Rectangle} The filter area - * @param sprite {Sprite} the target sprite - * @param outputMatrix {Matrix} @alvin - * @private - */ -// this returns a matrix that will normalise map filter cords in the filter to screen space -export function calculateScreenSpaceMatrix(outputMatrix, filterArea, textureSize) -{ - // TODO unwrap? - const mappedMatrix = outputMatrix.identity(); - - mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); - - mappedMatrix.scale(textureSize.width, textureSize.height); - - return mappedMatrix; -} - -export function calculateNormalizedScreenSpaceMatrix(outputMatrix, filterArea, textureSize) -{ - const mappedMatrix = outputMatrix.identity(); - - mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); - - const translateScaleX = (textureSize.width / filterArea.width); - const translateScaleY = (textureSize.height / filterArea.height); - - mappedMatrix.scale(translateScaleX, translateScaleY); - - return mappedMatrix; -} - -// this will map the filter coord so that a texture can be used based on the transform of a sprite -export function calculateSpriteMatrix(outputMatrix, filterArea, textureSize, sprite) -{ - const orig = sprite._texture.orig; - const mappedMatrix = outputMatrix.set(textureSize.width, 0, 0, textureSize.height, filterArea.x, filterArea.y); - const worldTransform = sprite.worldTransform.copyTo(Matrix.TEMP_MATRIX); - - worldTransform.invert(); - mappedMatrix.prepend(worldTransform); - mappedMatrix.scale(1.0 / orig.width, 1.0 / orig.height); - mappedMatrix.translate(sprite.anchor.x, sprite.anchor.y); - - return mappedMatrix; -} diff --git a/packages/core/src/renderers/filters/spriteMask/SpriteMaskFilter.js b/packages/core/src/renderers/filters/spriteMask/SpriteMaskFilter.js deleted file mode 100644 index d5a91a4..0000000 --- a/packages/core/src/renderers/filters/spriteMask/SpriteMaskFilter.js +++ /dev/null @@ -1,63 +0,0 @@ -import Filter from '../Filter'; -import { Matrix } from '@pixi/math'; -import vertex from './spriteMaskFilter.vert'; -import fragment from './spriteMaskFilter.frag'; -import { default as TextureMatrix } from '../../../textures/TextureMatrix'; - -/** - * The SpriteMaskFilter class - * - * @class - * @extends PIXI.Filter - * @memberof PIXI - */ -export default class SpriteMaskFilter extends Filter -{ - /** - * @param {PIXI.Sprite} sprite - the target sprite - */ - constructor(sprite) - { - const maskMatrix = new Matrix(); - - super(vertex, fragment); - - sprite.renderable = false; - - this.maskSprite = sprite; - this.maskMatrix = maskMatrix; - } - - /** - * Applies the filter - * - * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from - * @param {PIXI.RenderTarget} input - The input render target. - * @param {PIXI.RenderTarget} output - The target to output to. - */ - apply(filterManager, input, output) - { - const maskSprite = this.maskSprite; - const tex = this.maskSprite.texture; - - if (!tex.valid) - { - return; - } - if (!tex.transform) - { - // margin = 0.0, let it bleed a bit, shader code becomes easier - // assuming that atlas textures were made with 1-pixel padding - tex.transform = new TextureMatrix(tex, 0.0); - } - tex.transform.update(); - - this.uniforms.mask = tex; - this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite) - .prepend(tex.transform.mapCoord); - this.uniforms.alpha = maskSprite.worldAlpha; - this.uniforms.maskClamp = tex.transform.uClampFrame; - - filterManager.applyFilter(this, input, output); - } -} diff --git a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.frag b/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.frag deleted file mode 100644 index 0e4aef8..0000000 --- a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.frag +++ /dev/null @@ -1,23 +0,0 @@ -varying vec2 vMaskCoord; -varying vec2 vTextureCoord; - -uniform sampler2D uSampler; -uniform sampler2D mask; -uniform float alpha; -uniform vec4 maskClamp; - -void main(void) -{ - float clip = step(3.5, - step(maskClamp.x, vMaskCoord.x) + - step(maskClamp.y, vMaskCoord.y) + - step(vMaskCoord.x, maskClamp.z) + - step(vMaskCoord.y, maskClamp.w)); - - vec4 original = texture2D(uSampler, vTextureCoord); - vec4 masky = texture2D(mask, vMaskCoord); - - original *= (masky.r * masky.a * alpha * clip); - - gl_FragColor = original; -} diff --git a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.vert b/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.vert deleted file mode 100644 index d23a30c..0000000 --- a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.vert +++ /dev/null @@ -1,16 +0,0 @@ -attribute vec2 aVertexPosition; -attribute vec2 aTextureCoord; - -uniform mat3 projectionMatrix; -uniform mat3 otherMatrix; - -varying vec2 vMaskCoord; -varying vec2 vTextureCoord; - -void main(void) -{ - gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); - - vTextureCoord = aTextureCoord; - vMaskCoord = ( otherMatrix * vec3( aTextureCoord, 1.0) ).xy; -} diff --git a/packages/core/src/renderers/systems/BatchSystem.js b/packages/core/src/renderers/systems/BatchSystem.js deleted file mode 100644 index 70e9518..0000000 --- a/packages/core/src/renderers/systems/BatchSystem.js +++ /dev/null @@ -1,68 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import ObjectRenderer from '../utils/ObjectRenderer'; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ - -export default class BatchSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this manager works for. - */ - constructor(renderer) - { - super(renderer); - - /** - * An empty renderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.emptyRenderer = new ObjectRenderer(renderer); - - /** - * The currently active ObjectRenderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.currentRenderer = this.emptyRenderer; - } - - /** - * Changes the current renderer to the one given in parameter - * - * @param {PIXI.ObjectRenderer} objectRenderer - The object renderer to use. - */ - setObjectRenderer(objectRenderer) - { - if (this.currentRenderer === objectRenderer) - { - return; - } - - this.currentRenderer.stop(); - this.currentRenderer = objectRenderer; - - this.renderer.state.setState(objectRenderer.state); - - this.currentRenderer.start(); - } - - /** - * This should be called if you wish to do some custom rendering - * It will basically render anything that may be batched up such as sprites - * - */ - flush() - { - this.setObjectRenderer(this.emptyRenderer); - } - - reset() - { - this.setObjectRenderer(this.emptyRenderer); - } -} diff --git a/packages/core/src/renderers/systems/ContextSystem.js b/packages/core/src/renderers/systems/ContextSystem.js deleted file mode 100644 index 43bf827..0000000 --- a/packages/core/src/renderers/systems/ContextSystem.js +++ /dev/null @@ -1,186 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { settings } from '@pixi/settings'; - -let CONTEXT_UID = 0; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class ContextSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this.webGLVersion = 1; - - this.handleContextLost = this.handleContextLost.bind(this); - this.handleContextRestored = this.handleContextRestored.bind(this); - - this.extensions = {}; - - renderer.view.addEventListener('webglcontextlost', this.handleContextLost, false); - renderer.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); - } - - get isLost() - { - return (!this.gl || this.gl.isContextLost()); - } - - contextChange(gl) - { - this.gl = gl; - - // restore a context if it was previously lost - if (gl.isContextLost() && gl.getExtension('WEBGL_lose_context')) - { - gl.getExtension('WEBGL_lose_context').restoreContext(); - } - } - - initFromContext(gl) - { - this.gl = gl; - this.validateContext(gl); - this.renderer.gl = gl; - this.renderer.CONTEXT_UID = CONTEXT_UID++; - this.renderer.runners.contextChange.run(gl); - } - - initFromOptions(options) - { - const gl = this.createContext(this.renderer.view, options); - - this.initFromContext(gl); - } - - /** - * Helper class to create a webGL Context - * - * @class - * @memberof PIXI.glCore - * @param canvas {HTMLCanvasElement} the canvas element that we will get the context from - * @param options {Object} An options object that gets passed in to the canvas element containing the context attributes, - * see https://developer.mozilla.org/en/docs/Web/API/HTMLCanvasElement/getContext for the options available - * @return {WebGLRenderingContext} the WebGL context - */ - createContext(canvas, options) - { - let gl; - - if (settings.PREFER_WEBGL_2) - { - gl = canvas.getContext('webgl2', options); - } - - if (gl) - { - this.webGLVersion = 2; - } - else - { - this.webGLVersion = 1; - - gl = canvas.getContext('webgl', options) - || canvas.getContext('experimental-webgl', options); - - if (!gl) - { - // fail, not able to get a context - throw new Error('This browser does not support webGL. Try using the canvas renderer'); - } - } - - this.gl = gl; - - this.getExtensions(); - - return gl; - } - - getExtensions() - { - // time to set up default etensions that pixi uses.. - const gl = this.gl; - const extensions = this.extensions; - - if (this.webGLVersion === 1) - { - extensions.drawBuffers = gl.getExtension('WEBGL_draw_buffers'); - extensions.depthTexture = gl.getExtension('WEBKIT_WEBGL_depth_texture'); - extensions.floatTexture = gl.getExtension('OES_texture_float'); - extensions.loseContext = gl.getExtension('WEBGL_lose_context'); - - extensions.vertexArrayObject = gl.getExtension('OES_vertex_array_object') - || gl.getExtension('MOZ_OES_vertex_array_object') - || gl.getExtension('WEBKIT_OES_vertex_array_object'); - } - - // we don't use any specific WebGL 2 ones yet! - } - - /** - * Handles a lost webgl context - * - * @private - * @param {WebGLContextEvent} event - The context lost event. - */ - handleContextLost(event) - { - event.preventDefault(); - } - - /** - * Handles a restored webgl context - * - * @private - */ - handleContextRestored() - { - this.renderer.runners.contextChange.run(this.gl); - } - - destroy() - { - const view = this.renderer.view; - - // remove listeners - view.removeEventListener('webglcontextlost', this.handleContextLost); - view.removeEventListener('webglcontextrestored', this.handleContextRestored); - - this.gl.useProgram(null); - - if (this.extensions.loseContext) - { - this.extensions.loseContext.loseContext(); - } - } - - postrender() - { - this.gl.flush(); - } - - validateContext(gl) - { - const attributes = gl.getContextAttributes(); - - // this is going to be fairly simple for now.. but at least we have room to grow! - if (!attributes.stencil) - { - /* eslint-disable max-len */ - - /* eslint-disable no-console */ - console.warn('Provided WebGL context does not have a stencil buffer, masks may not render correctly'); - /* eslint-enable no-console */ - - /* eslint-enable max-len */ - } - } -} diff --git a/packages/core/src/renderers/systems/FramebufferSystem.js b/packages/core/src/renderers/systems/FramebufferSystem.js deleted file mode 100644 index df7902b..0000000 --- a/packages/core/src/renderers/systems/FramebufferSystem.js +++ /dev/null @@ -1,246 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { Rectangle } from '@pixi/math'; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class FramebufferSystem extends WebGLSystem -{ - /** - * Sets up the renderer context and necessary buffers. - * - * @private - */ - contextChange() - { - this.gl = this.renderer.gl; - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - this.current = null; - this.viewport = new Rectangle(); - - this.drawBufferExtension = this.renderer.context.extensions.drawBuffers; - } - - bind(framebuffer, frame) - { - const gl = this.gl; - - this.current = framebuffer; - - if (framebuffer) - { - // TODO cacheing layer! - - const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID] || this.initFramebuffer(framebuffer); - - gl.bindFramebuffer(gl.FRAMEBUFFER, fbo.framebuffer); - // makesure all textures are unbound.. - - // now check for updates... - if (fbo.dirtyId !== framebuffer.dirtyId) - { - fbo.dirtyId = framebuffer.dirtyId; - - if (fbo.dirtyFormat !== framebuffer.dirtyFormat) - { - fbo.dirtyFormat = framebuffer.dirtyFormat; - this.updateFramebuffer(framebuffer); - } - else if (fbo.dirtySize !== framebuffer.dirtySize) - { - fbo.dirtySize = framebuffer.dirtySize; - this.resizeFramebuffer(framebuffer); - } - } - - for (let i = 0; i < framebuffer.colorTextures.length; i++) - { - if (framebuffer.colorTextures[i].texturePart) - { - this.renderer.texture.unbind(framebuffer.colorTextures[i].texture); - } - else - { - this.renderer.texture.unbind(framebuffer.colorTextures[i]); - } - } - - if (framebuffer.depthTexture) - { - this.renderer.texture.unbind(framebuffer.depthTexture); - } - - if (frame) - { - this.setViewport(frame.x, frame.y, frame.width, frame.height); - } - else - { - this.setViewport(0, 0, framebuffer.width, framebuffer.height); - } - } - else - { - gl.bindFramebuffer(gl.FRAMEBUFFER, null); - - if (frame) - { - this.setViewport(frame.x, frame.y, frame.width, frame.height); - } - else - { - this.setViewport(0, 0, this.renderer.width, this.renderer.height); - } - } - } - - setViewport(x, y, width, height) - { - const v = this.viewport; - - if (v.width !== width || v.height !== height || v.x !== x || v.y !== y) - { - v.x = x; - v.y = y; - v.width = width; - v.height = height; - - this.gl.viewport(x, y, width, height); - } - } - - get size() - { - if (this.current) - { - // TODO store temp - return { x: 0, y: 0, width: this.current.width, height: this.current.height }; - } - - return { x: 0, y: 0, width: this.renderer.width, height: this.renderer.height }; - } - - clear(r, g, b, a) - { - const gl = this.gl; - - // TODO clear color can be set only one right? - gl.clearColor(r, g, b, a); - gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); - } - - // private functions... - - initFramebuffer(framebuffer) - { - const gl = this.gl; - - // TODO - make this a class? - const fbo = { - framebuffer: gl.createFramebuffer(), - stencil: null, - dirtyId: 0, - dirtyFormat: 0, - dirtySize: 0, - }; - - framebuffer.glFrameBuffers[this.CONTEXT_UID] = fbo; - - return fbo; - } - - resizeFramebuffer(framebuffer) - { - const gl = this.gl; - - if (framebuffer.stencil || framebuffer.depth) - { - gl.bindRenderbuffer(gl.RENDERBUFFER, this.stencil); - gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); - } - } - - updateFramebuffer(framebuffer) - { - const gl = this.gl; - - const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID]; - - // bind the color texture - const colorTextures = framebuffer.colorTextures; - - let count = colorTextures.length; - - if (!this.drawBufferExtension) - { - count = Math.min(count, 1); - } - - const activeTextures = []; - - for (let i = 0; i < count; i++) - { - const texture = framebuffer.colorTextures[i]; - - if (texture.texturePart) - { - this.renderer.texture.bind(texture.texture, 0); - - gl.framebufferTexture2D(gl.FRAMEBUFFER, - gl.COLOR_ATTACHMENT0 + i, - gl.TEXTURE_CUBE_MAP_NEGATIVE_X + texture.side, - texture.texture._glTextures[this.CONTEXT_UID].texture, - 0); - } - else - { - this.renderer.texture.bind(texture, 0); - - gl.framebufferTexture2D(gl.FRAMEBUFFER, - gl.COLOR_ATTACHMENT0 + i, - gl.TEXTURE_2D, - texture._glTextures[this.CONTEXT_UID].texture, - 0); - } - - activeTextures.push(gl.COLOR_ATTACHMENT0 + i); - } - - if (this.drawBufferExtension && activeTextures.length > 1) - { - this.drawBufferExtension.drawBuffersWEBGL(activeTextures); - } - - if (framebuffer.depthTexture) - { - const depthTextureExt = this.renderer.context.extensions.depthTexture; - - if (depthTextureExt) - { - const depthTexture = framebuffer.depthTexture; - - this.renderer.texture.bind(depthTexture, 0); - - gl.framebufferTexture2D(gl.FRAMEBUFFER, - gl.DEPTH_ATTACHMENT, - gl.TEXTURE_2D, - depthTexture._glTextures[this.CONTEXT_UID].texture, - 0); - } - } - - if (framebuffer.stencil || framebuffer.depth) - { - fbo.stencil = gl.createRenderbuffer(); - - gl.bindRenderbuffer(gl.RENDERBUFFER, fbo.stencil); - - // TODO.. this is depth AND stencil? - gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, fbo.stencil); - gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); - // fbo.enableStencil(); - } - } -} diff --git a/packages/core/src/renderers/systems/MaskSystem.js b/packages/core/src/renderers/systems/MaskSystem.js deleted file mode 100644 index d9c4b1a..0000000 --- a/packages/core/src/renderers/systems/MaskSystem.js +++ /dev/null @@ -1,199 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import AlphaMaskFilter from '../filters/spriteMask/SpriteMaskFilter'; - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class MaskSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - // TODO - we don't need both! - this.scissor = false; - this.scissorData = null; - this.scissorRenderTarget = null; - - this.enableScissor = false; - - this.alphaMaskPool = []; - this.alphaMaskIndex = 0; - } - - /** - * Applies the Mask and adds it to the current filter stack. - * - * @param {PIXI.DisplayObject} target - Display Object to push the mask to - * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. - */ - push(target, maskData) - { - // TODO the root check means scissor rect will not - // be used on render textures more info here: - // https://github.com/pixijs/pixi.js/pull/3545 - - if (maskData.texture) - { - this.pushSpriteMask(target, maskData); - } - else if (this.enableScissor - && !this.scissor - && this.renderer._activeRenderTarget.root - && !this.renderer.stencil.stencilMaskStack.length - && maskData.isFastRect()) - { - const matrix = maskData.worldTransform; - - let rot = Math.atan2(matrix.b, matrix.a); - - // use the nearest degree! - rot = Math.round(rot * (180 / Math.PI)); - - if (rot % 90) - { - this.pushStencilMask(maskData); - } - else - { - this.pushScissorMask(target, maskData); - } - } - else - { - this.pushStencilMask(maskData); - } - } - - /** - * Removes the last mask from the mask stack and doesn't return it. - * - * @param {PIXI.DisplayObject} target - Display Object to pop the mask from - * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. - */ - pop(target, maskData) - { - if (maskData.texture) - { - this.popSpriteMask(target, maskData); - } - else if (this.enableScissor && !this.renderer.stencil.stencilMaskStack.length) - { - this.popScissorMask(target, maskData); - } - else - { - this.popStencilMask(target, maskData); - } - } - - /** - * Applies the Mask and adds it to the current filter stack. - * - * @param {PIXI.RenderTarget} target - Display Object to push the sprite mask to - * @param {PIXI.Sprite} maskData - Sprite to be used as the mask - */ - pushSpriteMask(target, maskData) - { - let alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; - - if (!alphaMaskFilter) - { - alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; - } - - alphaMaskFilter[0].resolution = this.renderer.resolution; - alphaMaskFilter[0].maskSprite = maskData; - - // TODO - may cause issues! - target.filterArea = maskData.getBounds(true); - - this.renderer.filterSystem.pushFilter(target, alphaMaskFilter); - - this.alphaMaskIndex++; - } - - /** - * Removes the last filter from the filter stack and doesn't return it. - * - */ - popSpriteMask() - { - this.renderer.filterSystem.popFilter(); - this.alphaMaskIndex--; - } - - /** - * Applies the Mask and adds it to the current filter stack. - * - * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. - */ - pushStencilMask(maskData) - { - this.renderer.batch.flush(); - this.renderer.stencil.pushStencil(maskData); - } - - /** - * Removes the last filter from the filter stack and doesn't return it. - * - */ - popStencilMask() - { - // this.renderer.currentRenderer.stop(); - this.renderer.stencil.popStencil(); - } - - /** - * - * @param {PIXI.DisplayObject} target - Display Object to push the mask to - * @param {PIXI.Graphics} maskData - The masking data. - */ - pushScissorMask(target, maskData) - { - maskData.renderable = true; - - const renderTarget = this.renderer._activeRenderTarget; - - const bounds = maskData.getBounds(); - - bounds.fit(renderTarget.size); - maskData.renderable = false; - - this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); - - const resolution = this.renderer.resolution; - - this.renderer.gl.scissor( - bounds.x * resolution, - (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, - bounds.width * resolution, - bounds.height * resolution - ); - - this.scissorRenderTarget = renderTarget; - this.scissorData = maskData; - this.scissor = true; - } - - /** - * - * - */ - popScissorMask() - { - this.scissorRenderTarget = null; - this.scissorData = null; - this.scissor = false; - - // must be scissor! - const gl = this.renderer.gl; - - gl.disable(gl.SCISSOR_TEST); - } -} diff --git a/packages/core/src/renderers/systems/ProjectionSystem.js b/packages/core/src/renderers/systems/ProjectionSystem.js deleted file mode 100644 index ec5efdb..0000000 --- a/packages/core/src/renderers/systems/ProjectionSystem.js +++ /dev/null @@ -1,73 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { Matrix } from '@pixi/math'; - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ - -export default class ProjectionSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this.projectionMatrix = new Matrix(); - } - - update(destinationFrame, sourceFrame, resolution, root) - { - this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; - this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; - - this.calculateProjection(this.destinationFrame, this.sourceFrame, resolution, root); - - this.renderer.globalUniforms.uniforms.projectionMatrix = this.projectionMatrix; - this.renderer.globalUniforms.update(); - } - - /** - * Updates the projection matrix based on a projection frame (which is a rectangle) - * - * @param {Rectangle} destinationFrame - The destination frame. - * @param {Rectangle} sourceFrame - The source frame. - */ - calculateProjection(destinationFrame, sourceFrame, resolution, root) - { - const pm = this.projectionMatrix; - - // I don't think we will need this line.. - // pm.identity(); - - if (!root) - { - pm.a = (1 / destinationFrame.width * 2) * resolution; - pm.d = (1 / destinationFrame.height * 2) * resolution; - - pm.tx = -1 - (sourceFrame.x * pm.a); - pm.ty = -1 - (sourceFrame.y * pm.d); - } - else - { - pm.a = (1 / destinationFrame.width * 2) * resolution; - pm.d = (-1 / destinationFrame.height * 2) * resolution; - - pm.tx = -1 - (sourceFrame.x * pm.a); - pm.ty = 1 - (sourceFrame.y * pm.d); - } - } - - /** - * Sets the transform of the active render target to the given matrix - * - * @param {PIXI.Matrix} matrix - The transformation matrix - */ - setTransform()// matrix) - { - // this._activeRenderTarget.transform = matrix; - } -} diff --git a/packages/core/src/renderers/systems/RenderTextureSystem.js b/packages/core/src/renderers/systems/RenderTextureSystem.js deleted file mode 100644 index a133ef1..0000000 --- a/packages/core/src/renderers/systems/RenderTextureSystem.js +++ /dev/null @@ -1,115 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { Rectangle } from '@pixi/math'; - -const tempRect = new Rectangle(); - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ - -export default class RenderTextureSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this.clearColor = renderer._backgroundColorRgba; - - // TODO moe this property somewhere else! - this.defaultMaskStack = []; - this.defaultFilterStack = [{}]; - - // empty render texture? - this.renderTexture = null; - - this.destinationFrame = new Rectangle(); - } - - bind(renderTexture, sourceFrame, destinationFrame) - { - // TODO - do we want this?? - if (this.renderTexture === renderTexture) return; - this.renderTexture = renderTexture; - - const renderer = this.renderer; - - if (renderTexture) - { - const baseTexture = renderTexture.baseTexture; - - if (!destinationFrame) - { - tempRect.width = baseTexture.realWidth; - tempRect.height = baseTexture.realHeight; - - destinationFrame = tempRect; - } - - if (!sourceFrame) - { - sourceFrame = destinationFrame; - } - - this.renderer.framebuffer.bind(baseTexture.frameBuffer, destinationFrame); - - this.renderer.projection.update(destinationFrame, sourceFrame, baseTexture.resolution, false); - this.renderer.stencil.setMaskStack(baseTexture.stencilMaskStack); - } - else - { - // TODO these validation checks happen deeper down.. - // thing they can be avoided.. - if (!destinationFrame) - { - tempRect.width = renderer.width; - tempRect.height = renderer.height; - - destinationFrame = tempRect; - } - - if (!sourceFrame) - { - sourceFrame = destinationFrame; - } - - renderer.framebuffer.bind(null, destinationFrame); - - // TODO store this.. - this.renderer.projection.update(destinationFrame, sourceFrame, this.renderer.resolution, true); - this.renderer.stencil.setMaskStack(this.defaultMaskStack); - } - - this.destinationFrame.copyFrom(destinationFrame); - } - - /** - * Erases the render texture and fills the drawing area with a colour - * - * @param {number} [clearColor] - The colour - * @return {PIXI.Renderer} Returns itself. - */ - clear(clearColor) - { - if (this.renderTexture) - { - clearColor = clearColor || this.renderTexture.baseTexture.clearColor; - } - else - { - clearColor = clearColor || this.clearColor; - } - - this.renderer.framebuffer.clear(clearColor[0], clearColor[1], clearColor[2], clearColor[3]); - } - - resize()// screenWidth, screenHeight) - { - // resize the root only! - this.bind(null); - } -} diff --git a/packages/core/src/renderers/systems/StateSystem.js b/packages/core/src/renderers/systems/StateSystem.js deleted file mode 100755 index d7f4caf..0000000 --- a/packages/core/src/renderers/systems/StateSystem.js +++ /dev/null @@ -1,288 +0,0 @@ -import mapWebGLBlendModesToPixi from '../utils/mapWebGLBlendModesToPixi'; -import WebGLSystem from './WebGLSystem'; -import WebGLState from '../State'; - -const BLEND = 0; -const OFFSET = 1; -const CULLING = 2; -const DEPTH_TEST = 3; -const WINDING = 4; - -/** - * A WebGL state machines - * - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class StateSystem extends WebGLSystem -{ - /** - * @param {WebGLRenderingContext} gl - The current WebGL rendering context - */ - constructor(renderer) - { - super(renderer); - - this.gl = null; - - this.maxAttribs = null; - - // check we have vao.. - this.nativeVaoExtension = null; - - this.attribState = null; - - this.stateId = 0; - this.polygonOffset = 0; - this.blendMode = 17; - - this.map = []; - - // map functions for when we set state.. - this.map[BLEND] = this.setBlend; - this.map[OFFSET] = this.setOffset; - this.map[CULLING] = this.setCullFace; - this.map[DEPTH_TEST] = this.setDepthTest; - this.map[WINDING] = this.setFrontFace; - - this.checks = []; - - this.defaultState = new WebGLState(); - this.defaultState.blend = true; - this.defaultState.depth = true; - } - - contextChange(gl) - { - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; - - this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); - - // check we have vao.. - this.nativeVaoExtension = ( - gl.getExtension('OES_vertex_array_object') - || gl.getExtension('MOZ_OES_vertex_array_object') - || gl.getExtension('WEBKIT_OES_vertex_array_object') - ); - - this.attribState = { - tempAttribState: new Array(this.maxAttribs), - attribState: new Array(this.maxAttribs), - }; - - this.blendModes = mapWebGLBlendModesToPixi(gl); - - this.setState(this.defaultState); - - this.reset(); - } - - /** - * Sets the current state - * - * @param {*} state - The state to set. - */ - setState(state) - { - state = state || this.defaultState; - - // TODO maybe to an object check? ( this.state === state )? - if (this.stateId !== state.data) - { - let diff = this.stateId ^ state.data; - let i = 0; - - // order from least to most common - while (diff) - { - if (diff & 1) - { - // state change! - this.map[i].call(this, !!(state.data & (1 << i))); - } - - diff = diff >> 1; - i++; - } - - this.stateId = state.data; - } - - // based on the above settings we check for specific modes.. - // for example if blend is active we check and set the blend modes - // or of polygon offset is active we check the poly depth. - for (let i = 0; i < this.checks.length; i++) - { - this.checks[i](this, state); - } - } - - /** - * Enables or disabled blending. - * - * @param {boolean} value - Turn on or off webgl blending. - */ - setBlend(value) - { - this.updateCheck(StateSystem.checkBlendMode, value); - - this.gl[value ? 'enable' : 'disable'](this.gl.BLEND); - } - - /** - * Enables or disable polygon offset fill - * - * @param {boolean} value - Turn on or off webgl polygon offset testing. - */ - setOffset(value) - { - this.gl[value ? 'enable' : 'disable'](this.gl.POLYGON_OFFSET_FILL); - } - - /** - * Sets whether to enable or disable depth test. - * - * @param {boolean} value - Turn on or off webgl depth testing. - */ - setDepthTest(value) - { - this.gl[value ? 'enable' : 'disable'](this.gl.DEPTH_TEST); - } - - /** - * Sets whether to enable or disable cull face. - * - * @param {boolean} value - Turn on or off webgl cull face. - */ - setCullFace(value) - { - this.gl[value ? 'enable' : 'disable'](this.gl.CULL_FACE); - } - - /** - * Sets the gl front face. - * - * @param {boolean} value - true is clockwise and false is counter-clockwise - */ - setFrontFace(value) - { - this.gl.frontFace(this.gl[value ? 'CW' : 'CCW']); - } - - /** - * Sets the blend mode. - * - * @param {number} value - The blend mode to set to. - */ - setBlendMode(value) - { - if (value === this.blendMode) - { - return; - } - - this.blendMode = value; - this.gl.blendFunc(this.blendModes[value][0], this.blendModes[value][1]); - } - - /** - * Sets the polygon offset. - * - * @param {number} value - the polygon offset - * @param {number} scale - the polygon offset scale - */ - setPolygonOffset(value, scale) - { - this.gl.polygonOffset(value, scale); - } - - /** - * Disables all the vaos in use - * - */ - resetAttributes() - { - for (let i = 0; i < this.attribState.tempAttribState.length; i++) - { - this.attribState.tempAttribState[i] = 0; - } - - for (let i = 0; i < this.attribState.attribState.length; i++) - { - this.attribState.attribState[i] = 0; - } - - // im going to assume one is always active for performance reasons. - for (let i = 1; i < this.maxAttribs; i++) - { - this.gl.disableVertexAttribArray(i); - } - } - - // used - /** - * Resets all the logic and disables the vaos - */ - reset() - { - // unbind any VAO if they exist.. - if (this.nativeVaoExtension) - { - this.nativeVaoExtension.bindVertexArrayOES(null); - } - - // reset all attributes.. - this.resetAttributes(); - - this.gl.pixelStorei(this.gl.UNPACK_FLIP_Y_WEBGL, false); - - this.setBlendMode(0); - - // TODO? - // this.setState(this.defaultState); - } - - /** - * checks to see which updates should be checked based on which settings have been activated - * for example if blend is enabled then we shold check the blend modes each time the state is changed - * or if poygon fill is activated then we need to check if the polygone offset changes. - * The idea is that we only check what we have too. - * - * @param {Function} func the checking function to add or remove - * @param {boolean} value should the check function be added or removed. - */ - updateCheck(func, value) - { - const index = this.checks.indexOf(func); - - if (value && index === -1) - { - this.checks.push(func); - } - else if (!value && index !== -1) - { - this.checks.splice(index, 1); - } - } - - /** - * A private little wrapper function that we call to check the blend mode. - * - * @static - * @private - * @param {PIXI.StateSystem} System the System to perform the state check on - * @param {PIXI.State} state the state that the blendMode will pulled from - */ - static checkBlendMode(system, state) - { - system.setBlendMode(state.blendMode); - } - - // TODO - add polygon offset? -} diff --git a/packages/core/src/renderers/systems/StencilSystem.js b/packages/core/src/renderers/systems/StencilSystem.js deleted file mode 100644 index 5d538cb..0000000 --- a/packages/core/src/renderers/systems/StencilSystem.js +++ /dev/null @@ -1,133 +0,0 @@ -import WebGLSystem from './WebGLSystem'; - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class StencilSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - this.stencilMaskStack = []; - } - - /** - * Changes the mask stack that is used by this System. - * - * @param {PIXI.Graphics[]} stencilMaskStack - The mask stack - */ - setMaskStack(stencilMaskStack) - { - const gl = this.renderer.gl; - - if (stencilMaskStack.length !== this.stencilMaskStack.length) - { - if (stencilMaskStack.length === 0) - { - gl.disable(gl.STENCIL_TEST); - } - else - { - gl.enable(gl.STENCIL_TEST); - } - } - - this.stencilMaskStack = stencilMaskStack; - } - - /** - * Applies the Mask and adds it to the current stencil stack. @alvin - * - * @param {PIXI.Graphics} graphics - The mask - */ - pushStencil(graphics) - { - this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); - - // this.renderer._activeRenderTarget.attachStencilBuffer(); - - const gl = this.renderer.gl; - const prevMaskCount = this.stencilMaskStack.length; - - if (prevMaskCount === 0) - { - gl.enable(gl.STENCIL_TEST); - } - - this.stencilMaskStack.push(graphics); - - // Increment the refference stencil value where the new mask overlaps with the old ones. - gl.colorMask(false, false, false, false); - gl.stencilFunc(gl.EQUAL, prevMaskCount, this._getBitwiseMask()); - gl.stencilOp(gl.KEEP, gl.KEEP, gl.INCR); - this.renderer.plugins.graphics.render(graphics); - - this._useCurrent(); - } - - /** - * Removes the last mask from the stencil stack. @alvin - */ - popStencil() - { - this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); - - const gl = this.renderer.gl; - const graphics = this.stencilMaskStack.pop(); - - if (this.stencilMaskStack.length === 0) - { - // the stack is empty! - gl.disable(gl.STENCIL_TEST); - gl.clear(gl.STENCIL_BUFFER_BIT); - gl.clearStencil(0); - } - else - { - // Decrement the refference stencil value where the popped mask overlaps with the other ones - gl.colorMask(false, false, false, false); - gl.stencilOp(gl.KEEP, gl.KEEP, gl.DECR); - this.renderer.plugins.graphics.render(graphics); - - this._useCurrent(); - } - } - - /** - * Setup renderer to use the current stencil data. - */ - _useCurrent() - { - const gl = this.renderer.gl; - - gl.colorMask(true, true, true, true); - gl.stencilFunc(gl.EQUAL, this.stencilMaskStack.length, this._getBitwiseMask()); - gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP); - } - - /** - * Fill 1s equal to the number of acitve stencil masks. - * - * @return {number} The bitwise mask. - */ - _getBitwiseMask() - { - return (1 << this.stencilMaskStack.length) - 1; - } - - /** - * Destroys the mask stack. - * - */ - destroy() - { - super.destroy(this); - - this.stencilMaskStack = null; - } -} diff --git a/packages/core/src/renderers/systems/WebGLSystem.js b/packages/core/src/renderers/systems/WebGLSystem.js deleted file mode 100644 index d3d5b31..0000000 --- a/packages/core/src/renderers/systems/WebGLSystem.js +++ /dev/null @@ -1,41 +0,0 @@ -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this manager works for. - */ - constructor(renderer) - { - /** - * The renderer this manager works for. - * - * @member {PIXI.Renderer} - */ - this.renderer = renderer; - - this.renderer.runners.contextChange.add(this); - } - - /** - * Generic method called when there is a WebGL context change. - * - */ - contextChange() - { - // do some codes init! - } - - /** - * Generic destroy methods to be overridden by the subclass - * - */ - destroy() - { - this.renderer.runners.contextChange.remove(this); - this.renderer = null; - } -} diff --git a/packages/core/src/renderers/systems/filter/FilterSystem.js b/packages/core/src/renderers/systems/filter/FilterSystem.js deleted file mode 100644 index e615037..0000000 --- a/packages/core/src/renderers/systems/filter/FilterSystem.js +++ /dev/null @@ -1,431 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; - -import RenderTexture from '../../../textures/RenderTexture'; -import Quad from '../../utils/Quad'; -import QuadUv from '../../utils/QuadUv'; -import { Rectangle } from '@pixi/math'; -import * as filterTransforms from '../../filters/filterTransforms'; -import bitTwiddle from 'bit-twiddle'; -import UniformGroup from '../../../shader/UniformGroup'; -import { DRAW_MODES } from '../../../../../constants'; - -// -/** - * @ignore - * @class - */ -class FilterState -{ - /** - * - */ - constructor() - { - this.renderTexture = null; - this.sourceFrame = new Rectangle(); - this.destinationFrame = new Rectangle(); - this.filters = []; - this.target = null; - this.legacy = false; - this.resolution = 1; - } -} - -/** - * @class - * @memberof PIXI.systems - * @extends PIXI.systems.WebGLSystem - */ -export default class FilterSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - /** - * stores a bunch of PO2 textures used for filtering - * @type {Object} - */ - this.texturePool = {}; - - /** - * a pool for storing filter states, save us creating new ones each tick - * @type {Array} - */ - this.statePool = []; - - /** - * A very simple geometry used when drawing a filter effect to the screen - * @type {Quad} - */ - this.quad = new Quad(); - - this.quadUv = new QuadUv(); - - /** - * Temporary rect for maths - * @type {PIXI.Rectangle} - */ - this.tempRect = new Rectangle(); - - this.activeState = {}; - - /** - * this uniform group is attached to filter uniforms when used - * @type {UniformGroup} - */ - this.globalUniforms = new UniformGroup({ - sourceFrame: this.tempRect, - destinationFrame: this.tempRect, - - // legacy variables - filterArea: new Float32Array(4), - filterClamp: new Float32Array(4), - }, true); - } - - /** - * Adds a new filter to the System. - * - * @param {PIXI.DisplayObject} target - The target of the filter to render. - * @param {PIXI.Filter[]} filters - The filters to apply. - */ - push(target, filters) - { - const renderer = this.renderer; - const filterStack = this.renderer.renderTexture.defaultFilterStack; - const state = this.statePool.pop() || new FilterState(); - - let resolution = filters[0].resolution; - let padding = filters[0].padding; - let autoFit = filters[0].autoFit; - let legacy = filters[0].legacy; - - for (let i = 1; i < filters.length; i++) - { - const filter = filters[i]; - - // lets use the lowest resolution.. - resolution = Math.min(resolution, filter.resolution); - // and the largest amount of padding! - padding = Math.max(padding, filter.padding); - // only auto fit if all filters are autofit - autoFit = autoFit || filter.autoFit; - - legacy = legacy || filter.legacy; - } - - filterStack.push(state); - - state.resolution = resolution; - - state.legacy = legacy; - - // round to whole number based on resolution - // TODO move that to the shader too? - state.sourceFrame = target.filterArea || target.getBounds(true); - - state.sourceFrame.pad(padding); - - if (autoFit) - { - state.sourceFrame.fit(this.renderer.renderTexture.destinationFrame); - } - - state.sourceFrame.round(resolution); - - state.renderTexture = this.getPotFilterTexture(state.sourceFrame.width, state.sourceFrame.height, resolution); - state.filters = filters; - - state.destinationFrame.width = state.renderTexture.width; - state.destinationFrame.height = state.renderTexture.height; - - state.renderTexture.filterFrame = state.sourceFrame; - renderer.renderTexture.bind(state.renderTexture, state.sourceFrame);// /, state.destinationFrame); - renderer.renderTexture.clear(); - } - - /** - * Pops off the filter and applies it. - * - */ - pop() - { - const renderer = this.renderer; - const filterStack = renderer.renderTexture.defaultFilterStack; - const state = filterStack.pop(); - const filters = state.filters; - - this.activeState = state; - - const globalUniforms = this.globalUniforms.uniforms; - - globalUniforms.sourceFrame = state.sourceFrame; - globalUniforms.destinationFrame = state.destinationFrame; - globalUniforms.resolution = state.resolution; - - // only update the rect if its legacy.. - if (state.legacy) - { - const filterArea = globalUniforms.filterArea; - const filterClamp = globalUniforms.filterClamp; - - filterArea[0] = state.destinationFrame.width; - filterArea[1] = state.destinationFrame.height; - filterArea[2] = state.sourceFrame.x; - filterArea[3] = state.sourceFrame.y; - - filterClamp[0] = 0.5 / state.resolution / state.destinationFrame.width; - filterClamp[1] = 0.5 / state.resolution / state.destinationFrame.height; - filterClamp[2] = (state.sourceFrame.width - 0.5) / state.resolution / state.destinationFrame.width; - filterClamp[3] = (state.sourceFrame.height - 0.5) / state.resolution / state.destinationFrame.height; - } - - this.globalUniforms.update(); - - const lastState = filterStack[filterStack.length - 1]; - - if (filters.length === 1) - { - filters[0].apply(this, state.renderTexture, lastState.renderTexture, false, state); - - this.returnFilterTexture(state.renderTexture); - } - else - { - let flip = state.renderTexture; - let flop = this.getPotFilterTexture( - flip.width, - flip.height, - state.resolution - ); - - flop.filterFrame = flip.filterFrame; - - let i = 0; - - for (i = 0; i < filters.length - 1; ++i) - { - filters[i].apply(this, flip, flop, true, state); - - const t = flip; - - flip = flop; - flop = t; - } - - filters[i].apply(this, flip, lastState.renderTexture, false, state); - - this.returnFilterTexture(flip); - this.returnFilterTexture(flop); - } - - this.statePool.push(state); - } - - /** - * Draws a filter. - * - * @param {PIXI.Filter} filter - The filter to draw. - * @param {PIXI.RenderTarget} input - The input render target. - * @param {PIXI.RenderTarget} output - The target to output to. - * @param {boolean} clear - Should the output be cleared before rendering to it - */ - applyFilter(filter, input, output, clear) - { - const renderer = this.renderer; - - renderer.renderTexture.bind(output, output ? output.filterFrame : null); - - if (clear) - { - // gl.disable(gl.SCISSOR_TEST); - renderer.renderTexture.clear(); - // gl.enable(gl.SCISSOR_TEST); - } - - // set the uniforms.. - filter.uniforms.uSampler = input; - filter.uniforms.filterGlobals = this.globalUniforms; - - // TODO make it so that the order of this does not matter.. - // because it does at the moment cos of global uniforms. - // they need to get resynced - - renderer.state.setState(filter.state); - renderer.shader.bind(filter); - - if (filter.legacy) - { - this.quadUv.map(input._frame, input.filterFrame); - - renderer.geometry.bind(this.quadUv); - renderer.geometry.draw(DRAW_MODES.TRIANGLES); - } - else - { - renderer.geometry.bind(this.quad); - renderer.geometry.draw(DRAW_MODES.TRIANGLE_STRIP); - } - } - - /** - * Calculates the mapped matrix. - * - * TODO playing around here.. this is temporary - (will end up in the shader) - * this returns a matrix that will normalise map filter cords in the filter to screen space - * - * @param {PIXI.Matrix} outputMatrix - the matrix to output to. - * @return {PIXI.Matrix} The mapped matrix. - */ - calculateScreenSpaceMatrix(outputMatrix) - { - const currentState = this.activeState; - - return filterTransforms.calculateScreenSpaceMatrix( - outputMatrix, - currentState.sourceFrame, - currentState.destinationFrame - ); - } - - /** - * This will map the filter coord so that a texture can be used based on the transform of a sprite - * - * @param {PIXI.Matrix} outputMatrix - The matrix to output to. - * @param {PIXI.Sprite} sprite - The sprite to map to. - * @return {PIXI.Matrix} The mapped matrix. - */ - calculateSpriteMatrix(outputMatrix, sprite) - { - const currentState = this.activeState; - - return filterTransforms.calculateSpriteMatrix( - outputMatrix, - currentState.sourceFrame, - currentState.destinationFrame, - sprite - ); - } - - /** - * Destroys this Filter System. - * - * @param {boolean} [contextLost=false] context was lost, do not free shaders - * - */ - destroy(contextLost = false) - { - if (!contextLost) - { - this.emptyPool(); - } - else - { - this.texturePool = {}; - } - } - - /** - * Gets a Power-of-Two render texture. - * - * TODO move to a seperate class could be on renderer? - * also - could cause issue with multiple contexts? - * - * @private - * @param {WebGLRenderingContext} gl - The webgl rendering context - * @param {number} minWidth - The minimum width of the render target. - * @param {number} minHeight - The minimum height of the render target. - * @param {number} resolution - The resolution of the render target. - * @return {PIXI.RenderTarget} The new render target. - */ - getPotFilterTexture(minWidth, minHeight, resolution) - { - minWidth = bitTwiddle.nextPow2(minWidth); - minHeight = bitTwiddle.nextPow2(minHeight); - resolution = resolution || 1; - - const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); - - if (!this.texturePool[key]) - { - this.texturePool[key] = []; - } - - let renderTexture = this.texturePool[key].pop(); - - if (!renderTexture) - { - // temporary bypass cache.. - // internally - this will cause a texture to be bound.. - renderTexture = RenderTexture.create({ - width: minWidth, - height: minHeight, - resolution, - }); - } - - return renderTexture; - } - - /** - * Gets extra render texture to use inside current filter - * - * @param {number} resolution resolution of the renderTexture - * @returns {PIXI.RenderTexture} - */ - getFilterTexture(resolution) - { - const rt = this.activeState.renderTexture; - - const filterTexture = this.getPotFilterTexture(rt.width, rt.height, resolution || rt.baseTexture.resolution); - - filterTexture.filterFrame = rt.filterFrame; - - return filterTexture; - } - - /** - * Frees a render texture back into the pool. - * - * @param {PIXI.RenderTarget} renderTexture - The renderTarget to free - */ - returnFilterTexture(renderTexture) - { - renderTexture.filterFrame = null; - - const base = renderTexture.baseTexture; - - const minWidth = base.width; - const minHeight = base.height; - - const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); - - this.texturePool[key].push(renderTexture); - } - - /** - * Empties the texture pool. - * - */ - emptyPool() - { - for (const i in this.texturePool) - { - const textures = this.texturePool[i]; - - if (textures) - { - for (let j = 0; j < textures.length; j++) - { - textures[j].destroy(true); - } - } - } - - this.texturePool = {}; - } -} diff --git a/packages/core/src/renderers/systems/geometry/GLBuffer.js b/packages/core/src/renderers/systems/geometry/GLBuffer.js deleted file mode 100644 index 0477e8a..0000000 --- a/packages/core/src/renderers/systems/geometry/GLBuffer.js +++ /dev/null @@ -1,9 +0,0 @@ -export default class GLBuffer -{ - constructor(buffer) - { - this.buffer = buffer; - this.updateID = -1; - this.byteLength = -1; - } -} diff --git a/packages/core/src/renderers/systems/geometry/GeometrySystem.js b/packages/core/src/renderers/systems/geometry/GeometrySystem.js deleted file mode 100644 index 26c8e9e..0000000 --- a/packages/core/src/renderers/systems/geometry/GeometrySystem.js +++ /dev/null @@ -1,388 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; -import GLBuffer from './GLBuffer'; - -const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class GeometrySystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this._activeGeometry = null; - this._activeVao = null; - - this.hasVao = true; - this.hasInstance = true; - } - - /** - * Sets up the renderer context and necessary buffers. - * - * @private - */ - contextChange() - { - const gl = this.gl = this.renderer.gl; - - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - - // webgl2 - if (!gl.createVertexArray) - { - // webgl 1! - let nativeVaoExtension = this.renderer.context.extensions.vertexArrayObject; - - if (this.renderer.legacy) - { - nativeVaoExtension = null; - } - - if (nativeVaoExtension) - { - gl.createVertexArray = () => - nativeVaoExtension.createVertexArrayOES(); - - gl.bindVertexArray = (vao) => - nativeVaoExtension.bindVertexArrayOES(vao); - - gl.deleteVertexArray = (vao) => - nativeVaoExtension.deleteVertexArrayOES(vao); - } - else - { - this.hasVao = false; - gl.createVertexArray = () => - { - // empty - }; - - gl.bindVertexArray = () => - { - // empty - }; - - gl.deleteVertexArray = () => - { - // empty - }; - } - } - - if (!gl.vertexAttribDivisor) - { - const instanceExt = gl.getExtension('ANGLE_instanced_arrays'); - - if (instanceExt) - { - gl.vertexAttribDivisor = (a, b) => - instanceExt.vertexAttribDivisorANGLE(a, b); - - gl.drawElementsInstanced = (a, b, c, d, e) => - instanceExt.drawElementsInstancedANGLE(a, b, c, d, e); - - gl.drawArraysInstanced = (a, b, c, d) => - instanceExt.drawArraysInstancedANGLE(a, b, c, d); - } - else - { - this.hasInstance = false; - } - } - } - - /** - * Binds geometry so that is can be drawn. Creating a Vao if required - * @private - * @param {PIXI.Geometry} geometry instance of geometry to bind - */ - bind(geometry, shader) - { - shader = shader || this.renderer.shader.shader; - - const gl = this.gl; - - // not sure the best way to address this.. - // currently different shaders require different VAOs for the same geometry - // Still mulling over the best way to solve this one.. - // will likely need to modify the shader attribute locations at run time! - let vaos = geometry.glVertexArrayObjects[this.CONTEXT_UID]; - - if (!vaos) - { - geometry.glVertexArrayObjects[this.CONTEXT_UID] = vaos = {}; - } - - const vao = vaos[shader.program.id] || this.initGeometryVao(geometry, shader.program); - - this._activeGeometry = geometry; - - if (this._activeVao !== vao) - { - this._activeVao = vao; - - if (this.hasVao) - { - gl.bindVertexArray(vao); - } - else - { - this.activateVao(geometry, shader.program); - } - } - - // TODO - optimise later! - // don't need to loop through if nothing changed! - // maybe look to add an 'autoupdate' to geometry? - this.updateBuffers(); - } - - reset() - { - this.unbind(); - } - - updateBuffers() - { - const geometry = this._activeGeometry; - const gl = this.gl; - - for (let i = 0; i < geometry.buffers.length; i++) - { - const buffer = geometry.buffers[i]; - - const glBuffer = buffer._glBuffers[this.CONTEXT_UID]; - - if (buffer._updateID !== glBuffer.updateID) - { - glBuffer.updateID = buffer._updateID; - - // TODO can cache this on buffer! maybe added a getter / setter? - const type = buffer.index ? gl.ELEMENT_ARRAY_BUFFER : gl.ARRAY_BUFFER; - const drawType = buffer.static ? gl.STATIC_DRAW : gl.DYNAMIC_DRAW; - - gl.bindBuffer(type, glBuffer.buffer); - - if (glBuffer.byteLength >= buffer.data.byteLength) - { - // offset is always zero for now! - gl.bufferSubData(type, 0, buffer.data); - } - else - { - gl.bufferData(type, buffer.data, drawType); - } - } - } - } - - checkCompatability(geometry, program) - { - // geometry must have at least all the attributes that the shader requires. - const geometryAttributes = geometry.attributes; - const shaderAttributes = program.attributeData; - - for (const j in shaderAttributes) - { - if (!geometryAttributes[j]) - { - throw new Error(`shader and geometry incompatible, geometry missing the "${j}" attribute`); - } - } - } - - /** - * Creates a Vao with the same structure as the geometry and stores it on the geometry. - * @private - * @param {PIXI.Geometry} geometry instance of geometry to to generate Vao for - */ - initGeometryVao(geometry, program) - { - this.checkCompatability(geometry, program); - - const gl = this.gl; - const CONTEXT_UID = this.CONTEXT_UID; - const buffers = geometry.buffers; - const attributes = geometry.attributes; - - const tempStride = {}; - const tempStart = {}; - - for (const j in buffers) - { - tempStride[j] = 0; - tempStart[j] = 0; - } - - for (const j in attributes) - { - if (!attributes[j].size && program.attributeData[j]) - { - attributes[j].size = program.attributeData[j].size; - } - - tempStride[attributes[j].buffer] += attributes[j].size * byteSizeMap[attributes[j].type]; - } - - for (const j in attributes) - { - const attribute = attributes[j]; - const attribSize = attribute.size; - - if (attribute.stride === undefined) - { - if (tempStride[attribute.buffer] === attribSize * byteSizeMap[attribute.type]) - { - attribute.stride = 0; - } - else - { - attribute.stride = tempStride[attribute.buffer]; - } - } - - if (attribute.start === undefined) - { - attribute.start = tempStart[attribute.buffer]; - - tempStart[attribute.buffer] += attribSize * byteSizeMap[attribute.type]; - } - } - - // first update - and create the buffers! - // only create a gl buffer if it actually gets - for (let i = 0; i < buffers.length; i++) - { - const buffer = buffers[i]; - - if (!buffer._glBuffers[CONTEXT_UID]) - { - buffer._glBuffers[CONTEXT_UID] = new GLBuffer(gl.createBuffer()); - } - } - - // TODO - maybe make this a data object? - // lets wait to see if we need to first! - const vao = gl.createVertexArray(); - - gl.bindVertexArray(vao); - - this.activateVao(geometry, program); - - gl.bindVertexArray(null); - - // add it to the cache! - geometry.glVertexArrayObjects[this.CONTEXT_UID][program.id] = vao; - - return vao; - } - - activateVao(geometry, program) - { - const gl = this.gl; - const CONTEXT_UID = this.CONTEXT_UID; - const buffers = geometry.buffers; - const attributes = geometry.attributes; - - if (geometry.indexBuffer) - { - // first update the index buffer if we have one.. - gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, geometry.indexBuffer._glBuffers[CONTEXT_UID].buffer); - } - - let lastBuffer = null; - - // add a new one! - for (const j in attributes) - { - const attribute = attributes[j]; - const buffer = buffers[attribute.buffer]; - const glBuffer = buffer._glBuffers[CONTEXT_UID]; - - if (program.attributeData[j]) - { - if (lastBuffer !== glBuffer) - { - gl.bindBuffer(gl.ARRAY_BUFFER, glBuffer.buffer); - - lastBuffer = glBuffer; - } - - const location = program.attributeData[j].location; - - // TODO introduce state again - // we can optimise this for older devices that have no VAOs - gl.enableVertexAttribArray(location); - - gl.vertexAttribPointer(location, - attribute.size, - attribute.type || gl.FLOAT, - attribute.normalized, - attribute.stride, - attribute.start); - - if (attribute.instance) - { - // TODO calculate instance count based of this... - if (this.hasInstance) - { - gl.vertexAttribDivisor(location, 1); - } - else - { - throw new Error('geometry error, GPU Instancing is not supported on this device'); - } - } - } - } - } - - draw(type, size, start, instanceCount) - { - const gl = this.gl; - const geometry = this._activeGeometry; - - // TODO.. this should not change so maybe cache the function? - - if (geometry.indexBuffer) - { - if (geometry.instanced) - { - /* eslint-disable max-len */ - gl.drawElementsInstanced(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2, instanceCount || 1); - /* eslint-enable max-len */ - } - else - { - gl.drawElements(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2); - } - } - else - if (geometry.instanced) - { - // TODO need a better way to calculate size.. - gl.drawArraysInstanced(type, start, size || geometry.getSize(), instanceCount || 1); - } - else - { - gl.drawArrays(type, start, size || geometry.getSize()); - } - - return this; - } - - unbind() - { - this.gl.bindVertexArray(null); - this._activeVao = null; - this._activeGeometry = null; - } -} diff --git a/packages/core/src/renderers/systems/geometry/setVertexAttribArrays.js b/packages/core/src/renderers/systems/geometry/setVertexAttribArrays.js deleted file mode 100644 index 23b6959..0000000 --- a/packages/core/src/renderers/systems/geometry/setVertexAttribArrays.js +++ /dev/null @@ -1,54 +0,0 @@ -// var GL_MAP = {}; - -/** - * @param gl {WebGLRenderingContext} The current WebGL context - * @param attribs {*} - * @param state {*} - */ -export default function setVertexAttribArrays(gl, attribs, state) -{ - let i; - - if (state) - { - const tempAttribState = state.tempAttribState; - const attribState = state.attribState; - - for (i = 0; i < tempAttribState.length; i++) - { - tempAttribState[i] = false; - } - - // set the new attribs - for (i = 0; i < attribs.length; i++) - { - tempAttribState[attribs[i].attribute.location] = true; - } - - for (i = 0; i < attribState.length; i++) - { - if (attribState[i] !== tempAttribState[i]) - { - attribState[i] = tempAttribState[i]; - - if (state.attribState[i]) - { - gl.enableVertexAttribArray(i); - } - else - { - gl.disableVertexAttribArray(i); - } - } - } - } - else - { - for (i = 0; i < attribs.length; i++) - { - const attrib = attribs[i]; - - gl.enableVertexAttribArray(attrib.attribute.location); - } - } -} diff --git a/packages/core/src/renderers/systems/index.js b/packages/core/src/renderers/systems/index.js deleted file mode 100644 index c843f03..0000000 --- a/packages/core/src/renderers/systems/index.js +++ /dev/null @@ -1,6 +0,0 @@ -/** - * Systems are individual components to the Renderer pipeline. - * @namespace PIXI.systems - */ -export { default as WebGLSystem } from './WebGLSystem'; -export { default as FilterSystem } from './filter/FilterSystem'; diff --git a/packages/core/src/renderers/systems/shader/GLProgram.js b/packages/core/src/renderers/systems/shader/GLProgram.js deleted file mode 100644 index 92980ca..0000000 --- a/packages/core/src/renderers/systems/shader/GLProgram.js +++ /dev/null @@ -1,43 +0,0 @@ -/** - * Helper class to create a webGL Program - * - * @class - * @private - */ -export default class GLProgram -{ - constructor(program, uniformData) - { - /** - * The shader program - * - * @member {WebGLProgram} - */ - this.program = program; - - /** - * holds the uniform data which contains uniform locations - * and current uniform values used for caching and preventing undeed GPU commands - * @type {Object} - */ - this.uniformData = uniformData; - - /** - * uniformGroups holds the various upload functions for the shader. Each uniform group - * and program have a unique upload function generated. - * @type {Object} - */ - this.uniformGroups = {}; - } - - /** - * Destroys this program - * TODO - */ - destroy() - { - this.uniformData = null; - this.uniformGroups = null; - this.program = null; - } -} diff --git a/packages/core/src/renderers/systems/shader/ShaderSystem.js b/packages/core/src/renderers/systems/shader/ShaderSystem.js deleted file mode 100644 index 6cf57b6..0000000 --- a/packages/core/src/renderers/systems/shader/ShaderSystem.js +++ /dev/null @@ -1,170 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; -import GLProgram from './GLProgram'; -import generateUniformsSync from '../../../shader/generateUniformsSync'; -import defaultValue from '../../../shader/defaultValue'; -import compileProgram from './shader/compileProgram'; - -let UID = 0; - -/** - * Helper class to create a webGL Texture - * - * @class - * @memberof PIXI - */ -export default class ShaderSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - A reference to the current renderer - */ - constructor(renderer) - { - super(renderer); - - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = null; - - this.shader = null; - this.program = null; - - this.id = UID++; - } - - contextChange(gl) - { - this.gl = gl; - } - - /** - * Changes the current shader to the one given in parameter - * - * @param {PIXI.Shader} shader - the new shader - * @param {boolean} dontSync - false if the shader should automatically sync its uniforms. - * @returns {PIXI.glCore.glProgram} the glProgram that belongs to the shader. - */ - bind(shader, dontSync) - { - shader.uniforms.globals = this.renderer.globalUniforms; - - const program = shader.program; - const glProgram = program.glPrograms[this.renderer.CONTEXT_UID] || this.generateShader(shader); - - this.shader = shader; - - // TODO - some current pixi plugins bypass this.. so it not safe to use yet.. - if (this.program !== program) - { - this.program = program; - this.gl.useProgram(glProgram.program); - } - - if (!dontSync) - { - this.syncUniformGroup(shader.uniformGroup); - } - - return glProgram; - } - - /** - * Uploads the uniforms values to the currently bound shader. - * - * @param {object} uniforms - the uniforms valiues that be applied to the current shader - */ - setUniforms(uniforms) - { - const shader = this.shader.program; - const glProgram = shader.glPrograms[this.renderer.CONTEXT_UID]; - - shader.syncUniforms(glProgram.uniformData, uniforms, this.renderer); - } - - syncUniformGroup(group) - { - const glProgram = this.getglProgram(); - - if (!group.static || group.dirtyId !== glProgram.uniformGroups[group.id]) - { - glProgram.uniformGroups[group.id] = group.dirtyId; - const syncFunc = group.syncUniforms[this.shader.program.id] || this.createSyncGroups(group); - - syncFunc(glProgram.uniformData, group.uniforms, this.renderer); - } - } - - createSyncGroups(group) - { - group.syncUniforms[this.shader.program.id] = generateUniformsSync(group, this.shader.program.uniformData); - - return group.syncUniforms[this.shader.program.id]; - } - - /** - * Returns the underlying GLShade rof the currently bound shader. - * This can be handy for when you to have a little more control over the setting of your uniforms. - * - * @return {PIXI.glCore.Shader} the glProgram for the currently bound Shader for this context - */ - getglProgram() - { - if (this.shader) - { - return this.shader.program.glPrograms[this.renderer.CONTEXT_UID]; - } - - return null; - } - - /** - * Generates a glProgram verion of the Shader provided. - * - * @private - * @param {PIXI.Shader} shader the shader that the glProgram will be based on. - * @return {PIXI.glCore.glProgram} A shiney new glProgram - */ - generateShader(shader) - { - const gl = this.gl; - - const program = shader.program; - - const attribMap = {}; - - for (const i in program.attributeData) - { - attribMap[i] = program.attributeData[i].location; - } - - const shaderProgram = compileProgram(gl, program.vertexSrc, program.fragmentSrc, attribMap); - const uniformData = {}; - - for (const i in program.uniformData) - { - const data = program.uniformData[i]; - - uniformData[i] = { - location: gl.getUniformLocation(shaderProgram, i), - value: defaultValue(data.type, data.size), - }; - } - - const glProgram = new GLProgram(shaderProgram, uniformData); - - program.glPrograms[this.renderer.CONTEXT_UID] = glProgram; - - return glProgram; - } - - /** - * Destroys this System and removes all its textures - */ - destroy() - { - // TODO implement destroy method for ShaderSystem - this.destroyed = true; - } -} diff --git a/packages/core/src/renderers/systems/shader/shader/compileProgram.js b/packages/core/src/renderers/systems/shader/shader/compileProgram.js deleted file mode 100644 index 8c6ccb9..0000000 --- a/packages/core/src/renderers/systems/shader/shader/compileProgram.js +++ /dev/null @@ -1,78 +0,0 @@ -/** - * @method compileProgram - * @memberof PIXI.glCore.shader - * @param gl {WebGLRenderingContext} The current WebGL context {WebGLProgram} - * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. - * @param fragmentSrc {string|string[]} The fragment shader source as an array of strings. - * @param attributeLocations {Object} An attribute location map that lets you manually set the attribute locations - * @return {WebGLProgram} the shader program - */ -export default function compileProgram(gl, vertexSrc, fragmentSrc, attributeLocations) -{ - const glVertShader = compileShader(gl, gl.VERTEX_SHADER, vertexSrc); - const glFragShader = compileShader(gl, gl.FRAGMENT_SHADER, fragmentSrc); - - let program = gl.createProgram(); - - gl.attachShader(program, glVertShader); - gl.attachShader(program, glFragShader); - - // optionally, set the attributes manually for the program rather than letting WebGL decide.. - if (attributeLocations) - { - for (const i in attributeLocations) - { - gl.bindAttribLocation(program, attributeLocations[i], i); - } - } - - gl.linkProgram(program); - - // if linking fails, then log and cleanup - if (!gl.getProgramParameter(program, gl.LINK_STATUS)) - { - console.error('Pixi.js Error: Could not initialize shader.'); - console.error('gl.VALIDATE_STATUS', gl.getProgramParameter(program, gl.VALIDATE_STATUS)); - console.error('gl.getError()', gl.getError()); - - // if there is a program info log, log it - if (gl.getProgramInfoLog(program) !== '') - { - console.warn('Pixi.js Warning: gl.getProgramInfoLog()', gl.getProgramInfoLog(program)); - } - - gl.deleteProgram(program); - program = null; - } - - // clean up some shaders - gl.deleteShader(glVertShader); - gl.deleteShader(glFragShader); - - return program; -} - -/** - * @private - * @param gl {WebGLRenderingContext} The current WebGL context {WebGLProgram} - * @param type {Number} the type, can be either VERTEX_SHADER or FRAGMENT_SHADER - * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. - * @return {WebGLShader} the shader - */ -function compileShader(gl, type, src) -{ - const shader = gl.createShader(type); - - gl.shaderSource(shader, src); - gl.compileShader(shader); - - if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) - { - console.warn(src); - console.error(gl.getShaderInfoLog(shader)); - - return null; - } - - return shader; -} diff --git a/packages/core/src/AbstractRenderer.js b/packages/core/src/AbstractRenderer.js new file mode 100644 index 0000000..64b2eb1 --- /dev/null +++ b/packages/core/src/AbstractRenderer.js @@ -0,0 +1,353 @@ +import { hex2string, hex2rgb } from '@pixi/utils'; +import { Matrix, Rectangle } from '@pixi/math'; +import { RENDERER_TYPE } from '@pixi/constants'; +import { settings } from '@pixi/settings'; +import { Container } from '@pixi/display'; +import RenderTexture from './renderTexture/RenderTexture'; +import EventEmitter from 'eventemitter3'; + +const tempMatrix = new Matrix(); + +/** + * 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 EventEmitter + * @memberof PIXI + */ +export default class AbstractRenderer extends EventEmitter +{ + // eslint-disable-next-line valid-jsdoc + /** + * @param {string} system - The name of the system this renderer is for. + * @param {object} [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.transparent=false] - If the render view is transparent, default false + * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false + * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. The + * resolution of the renderer retina would be 2. + * @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 {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when rendering, + * stopping pixel interpolation. + */ + constructor(system, options, arg2, arg3) + { + super(); + + // Support for constructor(system, screenWidth, screenHeight, options) + if (typeof options === 'number') + { + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + }, arg3); + } + + // 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 = RENDERER_TYPE.UNKNOWN; + + /** + * Measurements of the screen. (0, 0, screenWidth, screenHeight) + * + * Its safe to use as filterArea or hitArea for 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 1 + */ + this.resolution = options.resolution || settings.RESOLUTION; + + /** + * Whether the render view is transparent + * + * @member {boolean} + */ + this.transparent = options.transparent; + + /** + * Whether css dimensions of canvas view should be resized to screen dimensions automatically + * + * @member {boolean} + */ + this.autoResize = options.autoResize || false; + + /** + * Tracks the blend modes useful for this renderer. + * + * @member {object} + */ + this.blendModes = null; + + /** + * 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; + + /** + * If true PixiJS will Math.floor() x/y values when rendering, stopping pixel interpolation. + * Handy for crisp pixel art and speed on legacy devices. + * + * @member {boolean} + */ + this.roundPixels = options.roundPixels; + + /** + * The background color as a number. + * + * @member {number} + * @private + */ + this._backgroundColor = 0x000000; + + /** + * The background color as an [R, G, B] array. + * + * @member {number[]} + * @private + */ + this._backgroundColorRgba = [0, 0, 0, 0]; + + /** + * The background color as a string. + * + * @member {string} + * @private + */ + this._backgroundColorString = '#000000'; + + this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter + + /** + * This temporary display object used as the parent of the currently being rendered item + * + * @member {PIXI.DisplayObject} + * @private + */ + this._tempDisplayObjectParent = new Container(); + + /** + * The last root object that the renderer tried to render. + * + * @member {PIXI.DisplayObject} + * @private + */ + this._lastObjectRendered = this._tempDisplayObjectParent; + + /** + * Collection of plugins. + * @readonly + * @member {object} + */ + this.plugins = {}; + } + + /** + * Initialize the plugins. + * + * @protected + * @param {object} staticMap - The dictionary of staticly saved plugins. + */ + initPlugins(staticMap) + { + 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() + { + return this.view.width; + } + + /** + * Same as view.height, actual number of pixels in the canvas by vertical + * + * @member {number} + * @readonly + * @default 600 + */ + get height() + { + return this.view.height; + } + + /** + * Resizes the screen and canvas to the specified width and height + * Canvas dimensions are multiplied by resolution + * + * @param {number} screenWidth - the new width of the screen + * @param {number} screenHeight - the new height of the screen + */ + resize(screenWidth, screenHeight) + { + this.screen.width = screenWidth; + this.screen.height = screenHeight; + + this.view.width = screenWidth * this.resolution; + this.view.height = screenHeight * this.resolution; + + if (this.autoResize) + { + this.view.style.width = `${screenWidth}px`; + this.view.style.height = `${screenHeight}px`; + } + } + + /** + * 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 {PIXI.DisplayObject} displayObject - The displayObject the object will be generated from + * @param {number} scaleMode - Should be one of the scaleMode consts + * @param {number} resolution - The resolution / device pixel ratio of the texture being generated + * @param {PIXI.Rectangle} [region] - The region of the displayObject, that shall be rendered, + * if no region is specified, defaults to the local bounds of the displayObject. + * @return {PIXI.Texture} a texture of the graphics object + */ + generateTexture(displayObject, scaleMode, resolution, region) + { + region = region || displayObject.getLocalBounds(); + + // 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(region.width | 0, region.height | 0, scaleMode, resolution); + + tempMatrix.tx = -region.x; + tempMatrix.ty = -region.y; + + this.render(displayObject, renderTexture, false, tempMatrix, true); + + return renderTexture; + } + + /** + * Removes everything from the renderer and optionally removes the Canvas DOM element. + * + * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. + */ + destroy(removeView) + { + 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); + } + + this.plugins = null; + + this.type = RENDERER_TYPE.UNKNOWN; + + this.view = null; + + this.screen = null; + + this.resolution = 0; + + this.transparent = false; + + this.autoResize = false; + + this.blendModes = null; + + this.options = null; + + this.preserveDrawingBuffer = false; + this.clearBeforeRender = false; + + this.roundPixels = false; + + this._backgroundColor = 0; + this._backgroundColorRgba = null; + this._backgroundColorString = null; + + this._tempDisplayObjectParent = null; + this._lastObjectRendered = null; + } + + /** + * The background color to fill if not transparent + * + * @member {number} + */ + get backgroundColor() + { + return this._backgroundColor; + } + + set backgroundColor(value) // eslint-disable-line require-jsdoc + { + this._backgroundColor = value; + this._backgroundColorString = hex2string(value); + hex2rgb(value, this._backgroundColorRgba); + } +} diff --git a/packages/core/src/Renderer.js b/packages/core/src/Renderer.js new file mode 100644 index 0000000..2227e78 --- /dev/null +++ b/packages/core/src/Renderer.js @@ -0,0 +1,338 @@ +import AbstractRenderer from './AbstractRenderer'; +import { sayHello } from '@pixi/utils'; +import MaskSystem from './mask/MaskSystem'; +import StencilSystem from './mask/StencilSystem'; +import FilterSystem from './filters/FilterSystem'; +import FramebufferSystem from './framebuffer/FramebufferSystem'; +import RenderTextureSystem from './renderTexture/RenderTextureSystem'; +import TextureSystem from './textures/TextureSystem'; +import ProjectionSystem from './projection/ProjectionSystem'; +import StateSystem from './state/StateSystem'; +import GeometrySystem from './geometry/GeometrySystem'; +import ShaderSystem from './shader/ShaderSystem'; +import ContextSystem from './context/ContextSystem'; +import BatchSystem from './batch/BatchSystem'; +import TextureGCSystem from './textures/TextureGCSystem'; +import { RENDERER_TYPE } from '@pixi/constants'; +import UniformGroup from './shader/UniformGroup'; +import { Matrix } from '@pixi/math'; +import Runner from 'mini-runner'; + +/** + * 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 Render works by automatically managing webGLBatchs. + * 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 :) + * + * @class + * @memberof PIXI + * @extends PIXI.AbstractRenderer + */ +export default class Renderer extends AbstractRenderer +{ + // eslint-disable-next-line valid-jsdoc + /** + * + * @param {object} [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.transparent=false] - If the render view is transparent, default false + * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false + * @param {boolean} [options.antialias=false] - sets antialias. If not available natively then FXAA + * antialiasing is used + * @param {boolean} [options.forceFXAA=false] - forces FXAA antialiasing to be used over native. + * FXAA is faster, but may not always look as great + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. + * The resolution of the renderer retina would be 2. + * @param {boolean} [options.clearBeforeRender=true] - This sets if the renderer will clear + * the canvas or not before the new render pass. If you wish to set this to false, you *must* set + * preserveDrawingBuffer to `true`. + * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, + * enable this if you need to call toDataUrl on the webgl context. + * @param {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when + * rendering, stopping pixel interpolation. + * @param {number} [options.backgroundColor=0x000000] - The background color of the rendered area + * (shown if not transparent). + * @param {boolean} [options.legacy=false] - If true PixiJS will aim to ensure compatibility + * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. + * @param {string} [options.powerPreference] - Parameter passed to webgl context, set to "high-performance" + * for devices with dual graphics card + */ + constructor(options, arg2, arg3) + { + super('WebGL', options, arg2, arg3); + + /** + * The type of this renderer as a standardised const + * + * @member {number} + * @see PIXI.RENDERER_TYPE + */ + this.type = RENDERER_TYPE.WEBGL; + + // this will be set by the contextSystem (this.context) + this.gl = null; + this.CONTEXT_UID = 0; + this.legacy = !!options.legacy; + + // TODO legacy! + + // runners! + this.runners = { + destroy: new Runner('destroy'), + contextChange: new Runner('contextChange', 1), + reset: new Runner('reset'), + update: new Runner('update'), + postrender: new Runner('postrender'), + prerender: new Runner('prerender'), + resize: new Runner('resize', 2), + }; + + 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(GeometrySystem, 'geometry') + .addSystem(FramebufferSystem, 'framebuffer') + .addSystem(StencilSystem, 'stencil') + .addSystem(ProjectionSystem, 'projection') + .addSystem(TextureGCSystem, 'textureGC') + .addSystem(FilterSystem, 'filter') + .addSystem(RenderTextureSystem, 'renderTexture') + .addSystem(BatchSystem, 'batch'); + + this.initPlugins(Renderer.__plugins); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ + if (options.context) + { + this.context.initFromContext(options.context); + } + else + { + this.context.initFromOptions({ + alpha: this.transparent, + antialias: options.antialias, + premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', + stencil: true, + 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); + } + + /** + * Add a new system to the renderer. + * @param {class} ClassRef - Class reference + * @param {string} [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. + * @return {PIXI.Renderer} Return instance of renderer + */ + addSystem(ClassRef, name) + { + if (!name) + { + name = ClassRef.name; + } + + // TODO - read name from class.name.. + + /* + if(name.includes('System')) + { + name = name.replace('System', ''); + name = name.charAt(0).toLowerCase() + name.slice(1); + } + */ + + const system = new ClassRef(this); + + if (this[name]) + { + throw new Error(`Whoops! ${name} is already a manger`); + } + + this[name] = system; + + for (const i in this.runners) + { + this.runners[i].add(system); + } + + return this; + + /** + * 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. + */ + } + + /** + * Renders the object to its webGL view + * + * @param {PIXI.DisplayObject} displayObject - the object to be rendered + * @param {PIXI.RenderTexture} renderTexture - The render texture to render to. + * @param {boolean} [clear] - Should the canvas be cleared before the new render + * @param {PIXI.Transform} [transform] - A transform to apply to the render texture before rendering. + * @param {boolean} [skipUpdateTransform] - Should we skip the update transform pass? + */ + render(displayObject, renderTexture, clear, transform, skipUpdateTransform) + { + // can be handy to know! + this.renderingToScreen = !renderTexture; + + this.runners.prerender.run(); + this.emit('prerender'); + + // 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.parent; + + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = 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.run(); + + this.emit('postrender'); + } + + /** + * Resizes the webGL view to the specified width and height. + * + * @param {number} screenWidth - the new width of the screen + * @param {number} screenHeight - the new height of the screen + */ + resize(screenWidth, screenHeight) + { + AbstractRenderer.prototype.resize.call(this, screenWidth, screenHeight); + + this.runners.resize.run(screenWidth, screenHeight); + } + + /** + * Resets the WebGL state so you can render things however you fancy! + * + * @return {PIXI.Renderer} Returns itself. + */ + reset() + { + this.runners.reset.run(); + + return this; + } + + clear() + { + this.framebuffer.bind(); + this.framebuffer.clear(); + } + + /** + * Removes everything from the renderer (event listeners, spritebatch, etc...) + * + * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. + * See: https://github.com/pixijs/pixi.js/issues/2233 + */ + destroy(removeView) + { + this.runners.destroy.run(); + + // call base destroy + super.destroy(removeView); + + // TODO nullify all the managers.. + this.gl = null; + } + + /** + * 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 + * builds and excluding plugins. + * @name PIXI.Renderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. + */ + + /** + * Adds a plugin to the renderer. + * + * @method + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + static registerPlugin(pluginName, ctor) + { + Renderer.__plugins = Renderer.__plugins || {}; + Renderer.__plugins[pluginName] = ctor; + } +} diff --git a/packages/core/src/System.js b/packages/core/src/System.js new file mode 100644 index 0000000..05a10c8 --- /dev/null +++ b/packages/core/src/System.js @@ -0,0 +1,42 @@ +/** + * System is a base class used for extending systems used by the {@link PIXI.Renderer} + * @see PIXI.Renderer#addSystem + * @class + * @memberof PIXI + */ +export default class System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + /** + * The renderer this manager works for. + * + * @member {PIXI.Renderer} + */ + this.renderer = renderer; + + this.renderer.runners.contextChange.add(this); + } + + /** + * Generic method called when there is a WebGL context change. + * + */ + contextChange() + { + // do some codes init! + } + + /** + * Generic destroy methods to be overridden by the subclass + * + */ + destroy() + { + this.renderer.runners.contextChange.remove(this); + this.renderer = null; + } +} diff --git a/packages/core/src/batch/BatchSystem.js b/packages/core/src/batch/BatchSystem.js new file mode 100644 index 0000000..b04c249 --- /dev/null +++ b/packages/core/src/batch/BatchSystem.js @@ -0,0 +1,68 @@ +import System from '../System'; +import ObjectRenderer from './ObjectRenderer'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class BatchSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + super(renderer); + + /** + * An empty renderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.emptyRenderer = new ObjectRenderer(renderer); + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.currentRenderer = this.emptyRenderer; + } + + /** + * Changes the current renderer to the one given in parameter + * + * @param {PIXI.ObjectRenderer} objectRenderer - The object renderer to use. + */ + setObjectRenderer(objectRenderer) + { + if (this.currentRenderer === objectRenderer) + { + return; + } + + this.currentRenderer.stop(); + this.currentRenderer = objectRenderer; + + this.renderer.state.setState(objectRenderer.state); + + this.currentRenderer.start(); + } + + /** + * This should be called if you wish to do some custom rendering + * It will basically render anything that may be batched up such as sprites + * + */ + flush() + { + this.setObjectRenderer(this.emptyRenderer); + } + + reset() + { + this.setObjectRenderer(this.emptyRenderer); + } +} diff --git a/packages/core/src/batch/ObjectRenderer.js b/packages/core/src/batch/ObjectRenderer.js new file mode 100644 index 0000000..0c851a9 --- /dev/null +++ b/packages/core/src/batch/ObjectRenderer.js @@ -0,0 +1,48 @@ +import System from '../System'; + +/** + * Base for a common object renderer that can be used as a system renderer plugin. + * + * @class + * @extends PIXI.System + * @memberof PIXI + */ +export default class ObjectRenderer extends System +{ + /** + * Starts the renderer and sets the shader + * + */ + start() + { + // set the shader.. + } + + /** + * Stops the renderer + * + */ + stop() + { + this.flush(); + } + + /** + * Stub method for rendering content and emptying the current batch. + * + */ + flush() + { + // flush! + } + + /** + * Renders an object + * + * @param {PIXI.DisplayObject} object - The object to render. + */ + render(object) // eslint-disable-line no-unused-vars + { + // render the object + } +} diff --git a/packages/core/src/context/ContextSystem.js b/packages/core/src/context/ContextSystem.js new file mode 100644 index 0000000..301aab4 --- /dev/null +++ b/packages/core/src/context/ContextSystem.js @@ -0,0 +1,186 @@ +import System from '../System'; +import { settings } from '@pixi/settings'; + +let CONTEXT_UID = 0; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class ContextSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.webGLVersion = 1; + + this.handleContextLost = this.handleContextLost.bind(this); + this.handleContextRestored = this.handleContextRestored.bind(this); + + this.extensions = {}; + + renderer.view.addEventListener('webglcontextlost', this.handleContextLost, false); + renderer.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + } + + get isLost() + { + return (!this.gl || this.gl.isContextLost()); + } + + contextChange(gl) + { + this.gl = gl; + + // restore a context if it was previously lost + if (gl.isContextLost() && gl.getExtension('WEBGL_lose_context')) + { + gl.getExtension('WEBGL_lose_context').restoreContext(); + } + } + + initFromContext(gl) + { + this.gl = gl; + this.validateContext(gl); + this.renderer.gl = gl; + this.renderer.CONTEXT_UID = CONTEXT_UID++; + this.renderer.runners.contextChange.run(gl); + } + + initFromOptions(options) + { + const gl = this.createContext(this.renderer.view, options); + + this.initFromContext(gl); + } + + /** + * Helper class to create a webGL Context + * + * @class + * @memberof PIXI.glCore + * @param canvas {HTMLCanvasElement} the canvas element that we will get the context from + * @param options {Object} An options object that gets passed in to the canvas element containing the context attributes, + * see https://developer.mozilla.org/en/docs/Web/API/HTMLCanvasElement/getContext for the options available + * @return {WebGLRenderingContext} the WebGL context + */ + createContext(canvas, options) + { + let gl; + + if (settings.PREFER_WEBGL_2) + { + gl = canvas.getContext('webgl2', options); + } + + if (gl) + { + this.webGLVersion = 2; + } + else + { + this.webGLVersion = 1; + + gl = canvas.getContext('webgl', options) + || canvas.getContext('experimental-webgl', options); + + if (!gl) + { + // fail, not able to get a context + throw new Error('This browser does not support webGL. Try using the canvas renderer'); + } + } + + this.gl = gl; + + this.getExtensions(); + + return gl; + } + + getExtensions() + { + // time to set up default etensions that pixi uses.. + const gl = this.gl; + const extensions = this.extensions; + + if (this.webGLVersion === 1) + { + extensions.drawBuffers = gl.getExtension('WEBGL_draw_buffers'); + extensions.depthTexture = gl.getExtension('WEBKIT_WEBGL_depth_texture'); + extensions.floatTexture = gl.getExtension('OES_texture_float'); + extensions.loseContext = gl.getExtension('WEBGL_lose_context'); + + extensions.vertexArrayObject = gl.getExtension('OES_vertex_array_object') + || gl.getExtension('MOZ_OES_vertex_array_object') + || gl.getExtension('WEBKIT_OES_vertex_array_object'); + } + + // we don't use any specific WebGL 2 ones yet! + } + + /** + * Handles a lost webgl context + * + * @private + * @param {WebGLContextEvent} event - The context lost event. + */ + handleContextLost(event) + { + event.preventDefault(); + } + + /** + * Handles a restored webgl context + * + * @private + */ + handleContextRestored() + { + this.renderer.runners.contextChange.run(this.gl); + } + + destroy() + { + const view = this.renderer.view; + + // remove listeners + view.removeEventListener('webglcontextlost', this.handleContextLost); + view.removeEventListener('webglcontextrestored', this.handleContextRestored); + + this.gl.useProgram(null); + + if (this.extensions.loseContext) + { + this.extensions.loseContext.loseContext(); + } + } + + postrender() + { + this.gl.flush(); + } + + validateContext(gl) + { + const attributes = gl.getContextAttributes(); + + // this is going to be fairly simple for now.. but at least we have room to grow! + if (!attributes.stencil) + { + /* eslint-disable max-len */ + + /* eslint-disable no-console */ + console.warn('Provided WebGL context does not have a stencil buffer, masks may not render correctly'); + /* eslint-enable no-console */ + + /* eslint-enable max-len */ + } + } +} diff --git a/packages/core/src/filters/Filter.js b/packages/core/src/filters/Filter.js new file mode 100644 index 0000000..9d8cad8 --- /dev/null +++ b/packages/core/src/filters/Filter.js @@ -0,0 +1,140 @@ +import Shader from '../shader/Shader'; +import Program from '../shader/Program'; +import State from '../state/State'; +import { settings } from '@pixi/settings'; +// import extractUniformsFromSrc from './extractUniformsFromSrc'; +import defaultVertex from './defaultFilter.vert'; +import defaultFragment from './defaultFilter.frag'; + +// let math = require('../../math'); +/** + * @class + * @memberof PIXI + * @extends PIXI.Shader + */ +export default class Filter extends Shader +{ + /** + * @param {string} [vertexSrc] - The source of the vertex shader. + * @param {string} [fragmentSrc] - The source of the fragment shader. + * @param {object} [uniforms] - Custom uniforms to use to augment the built-in ones. + */ + constructor(vertexSrc, fragmentSrc, uniforms) + { + const program = Program.from(vertexSrc, fragmentSrc); + + super(program, uniforms); + + /** + * The padding of the filter. Some filters require extra space to breath such as a blur. + * Increasing this will add extra width and height to the bounds of the object that the + * filter is applied to. + * + * @member {number} + */ + this.padding = 0; + + /** + * The resolution of the filter. Setting this to be lower will lower the quality but + * increase the performance of the filter. + * + * @member {number} + */ + this.resolution = settings.RESOLUTION; + + /** + * If enabled is true the filter is applied, if false it will not. + * + * @member {boolean} + */ + this.enabled = true; + + /** + * If enabled, PixiJS will fit the filter area into boundaries for better performance. + * Switch it off if it does not work for specific shader. + * + * @member {boolean} + */ + this.autoFit = true; + + /** + * Legacy filters use position and uvs from attributes + * @member {boolean} + * @readonly + */ + this.legacy = !!this.program.attributeData.aTextureCoord; + + /** + * the webGL state the filter requires to render + * @member {PIXI.State} + */ + this.state = new State(); + } + + /** + * Applies the filter + * + * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + * @param {boolean} clear - Should the output be cleared before rendering to it + * @param {object} [currentState] - It's current state of filter. + * There are some useful properties in the currentState : + * target, filters, sourceFrame, destinationFrame, renderTarget, resolution + */ + apply(filterManager, input, output, clear, currentState, derp) // eslint-disable-line no-unused-vars + { + // do as you please! + + filterManager.applyFilter(this, input, output, clear, currentState, derp); + + // or just do a regular render.. + } + + /** + * Sets the blendmode of the filter + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL + */ + get blendMode() + { + return this.state.blendMode; + } + + set blendMode(value) // eslint-disable-line require-jsdoc + { + this.state.blendMode = value; + } + + /** + * The default vertex shader source + * + * @static + * @constant + */ + static get defaultVertexSrc() + { + return defaultVertex; + } + + /** + * The default fragment shader source + * + * @static + * @constant + */ + static get defaultFragmentSrc() + { + return defaultFragment; + } +} + +/** + * Used for caching shader IDs + * + * @static + * @private + */ +Filter.SOURCE_KEY_MAP = {}; + diff --git a/packages/core/src/filters/FilterSystem.js b/packages/core/src/filters/FilterSystem.js new file mode 100644 index 0000000..3174318 --- /dev/null +++ b/packages/core/src/filters/FilterSystem.js @@ -0,0 +1,431 @@ +import System from '../System'; + +import RenderTexture from '../renderTexture/RenderTexture'; +import Quad from '../utils/Quad'; +import QuadUv from '../utils/QuadUv'; +import { Rectangle } from '@pixi/math'; +import * as filterTransforms from './filterTransforms'; +import bitTwiddle from 'bit-twiddle'; +import UniformGroup from '../shader/UniformGroup'; +import { DRAW_MODES } from '@pixi/constants'; + +// +/** + * @ignore + * @class + */ +class FilterState +{ + /** + * + */ + constructor() + { + this.renderTexture = null; + this.sourceFrame = new Rectangle(); + this.destinationFrame = new Rectangle(); + this.filters = []; + this.target = null; + this.legacy = false; + this.resolution = 1; + } +} + +/** + * @class + * @memberof PIXI.systems + * @extends PIXI.System + */ +export default class FilterSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + /** + * stores a bunch of PO2 textures used for filtering + * @type {Object} + */ + this.texturePool = {}; + + /** + * a pool for storing filter states, save us creating new ones each tick + * @type {Array} + */ + this.statePool = []; + + /** + * A very simple geometry used when drawing a filter effect to the screen + * @type {Quad} + */ + this.quad = new Quad(); + + this.quadUv = new QuadUv(); + + /** + * Temporary rect for maths + * @type {PIXI.Rectangle} + */ + this.tempRect = new Rectangle(); + + this.activeState = {}; + + /** + * this uniform group is attached to filter uniforms when used + * @type {UniformGroup} + */ + this.globalUniforms = new UniformGroup({ + sourceFrame: this.tempRect, + destinationFrame: this.tempRect, + + // legacy variables + filterArea: new Float32Array(4), + filterClamp: new Float32Array(4), + }, true); + } + + /** + * Adds a new filter to the System. + * + * @param {PIXI.DisplayObject} target - The target of the filter to render. + * @param {PIXI.Filter[]} filters - The filters to apply. + */ + push(target, filters) + { + const renderer = this.renderer; + const filterStack = this.renderer.renderTexture.defaultFilterStack; + const state = this.statePool.pop() || new FilterState(); + + let resolution = filters[0].resolution; + let padding = filters[0].padding; + let autoFit = filters[0].autoFit; + let legacy = filters[0].legacy; + + for (let i = 1; i < filters.length; i++) + { + const filter = filters[i]; + + // lets use the lowest resolution.. + resolution = Math.min(resolution, filter.resolution); + // and the largest amount of padding! + padding = Math.max(padding, filter.padding); + // only auto fit if all filters are autofit + autoFit = autoFit || filter.autoFit; + + legacy = legacy || filter.legacy; + } + + filterStack.push(state); + + state.resolution = resolution; + + state.legacy = legacy; + + // round to whole number based on resolution + // TODO move that to the shader too? + state.sourceFrame = target.filterArea || target.getBounds(true); + + state.sourceFrame.pad(padding); + + if (autoFit) + { + state.sourceFrame.fit(this.renderer.renderTexture.destinationFrame); + } + + state.sourceFrame.round(resolution); + + state.renderTexture = this.getPotFilterTexture(state.sourceFrame.width, state.sourceFrame.height, resolution); + state.filters = filters; + + state.destinationFrame.width = state.renderTexture.width; + state.destinationFrame.height = state.renderTexture.height; + + state.renderTexture.filterFrame = state.sourceFrame; + renderer.renderTexture.bind(state.renderTexture, state.sourceFrame);// /, state.destinationFrame); + renderer.renderTexture.clear(); + } + + /** + * Pops off the filter and applies it. + * + */ + pop() + { + const renderer = this.renderer; + const filterStack = renderer.renderTexture.defaultFilterStack; + const state = filterStack.pop(); + const filters = state.filters; + + this.activeState = state; + + const globalUniforms = this.globalUniforms.uniforms; + + globalUniforms.sourceFrame = state.sourceFrame; + globalUniforms.destinationFrame = state.destinationFrame; + globalUniforms.resolution = state.resolution; + + // only update the rect if its legacy.. + if (state.legacy) + { + const filterArea = globalUniforms.filterArea; + const filterClamp = globalUniforms.filterClamp; + + filterArea[0] = state.destinationFrame.width; + filterArea[1] = state.destinationFrame.height; + filterArea[2] = state.sourceFrame.x; + filterArea[3] = state.sourceFrame.y; + + filterClamp[0] = 0.5 / state.resolution / state.destinationFrame.width; + filterClamp[1] = 0.5 / state.resolution / state.destinationFrame.height; + filterClamp[2] = (state.sourceFrame.width - 0.5) / state.resolution / state.destinationFrame.width; + filterClamp[3] = (state.sourceFrame.height - 0.5) / state.resolution / state.destinationFrame.height; + } + + this.globalUniforms.update(); + + const lastState = filterStack[filterStack.length - 1]; + + if (filters.length === 1) + { + filters[0].apply(this, state.renderTexture, lastState.renderTexture, false, state); + + this.returnFilterTexture(state.renderTexture); + } + else + { + let flip = state.renderTexture; + let flop = this.getPotFilterTexture( + flip.width, + flip.height, + state.resolution + ); + + flop.filterFrame = flip.filterFrame; + + let i = 0; + + for (i = 0; i < filters.length - 1; ++i) + { + filters[i].apply(this, flip, flop, true, state); + + const t = flip; + + flip = flop; + flop = t; + } + + filters[i].apply(this, flip, lastState.renderTexture, false, state); + + this.returnFilterTexture(flip); + this.returnFilterTexture(flop); + } + + this.statePool.push(state); + } + + /** + * Draws a filter. + * + * @param {PIXI.Filter} filter - The filter to draw. + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + * @param {boolean} clear - Should the output be cleared before rendering to it + */ + applyFilter(filter, input, output, clear) + { + const renderer = this.renderer; + + renderer.renderTexture.bind(output, output ? output.filterFrame : null); + + if (clear) + { + // gl.disable(gl.SCISSOR_TEST); + renderer.renderTexture.clear(); + // gl.enable(gl.SCISSOR_TEST); + } + + // set the uniforms.. + filter.uniforms.uSampler = input; + filter.uniforms.filterGlobals = this.globalUniforms; + + // TODO make it so that the order of this does not matter.. + // because it does at the moment cos of global uniforms. + // they need to get resynced + + renderer.state.setState(filter.state); + renderer.shader.bind(filter); + + if (filter.legacy) + { + this.quadUv.map(input._frame, input.filterFrame); + + renderer.geometry.bind(this.quadUv); + renderer.geometry.draw(DRAW_MODES.TRIANGLES); + } + else + { + renderer.geometry.bind(this.quad); + renderer.geometry.draw(DRAW_MODES.TRIANGLE_STRIP); + } + } + + /** + * Calculates the mapped matrix. + * + * TODO playing around here.. this is temporary - (will end up in the shader) + * this returns a matrix that will normalise map filter cords in the filter to screen space + * + * @param {PIXI.Matrix} outputMatrix - the matrix to output to. + * @return {PIXI.Matrix} The mapped matrix. + */ + calculateScreenSpaceMatrix(outputMatrix) + { + const currentState = this.activeState; + + return filterTransforms.calculateScreenSpaceMatrix( + outputMatrix, + currentState.sourceFrame, + currentState.destinationFrame + ); + } + + /** + * This will map the filter coord so that a texture can be used based on the transform of a sprite + * + * @param {PIXI.Matrix} outputMatrix - The matrix to output to. + * @param {PIXI.Sprite} sprite - The sprite to map to. + * @return {PIXI.Matrix} The mapped matrix. + */ + calculateSpriteMatrix(outputMatrix, sprite) + { + const currentState = this.activeState; + + return filterTransforms.calculateSpriteMatrix( + outputMatrix, + currentState.sourceFrame, + currentState.destinationFrame, + sprite + ); + } + + /** + * Destroys this Filter System. + * + * @param {boolean} [contextLost=false] context was lost, do not free shaders + * + */ + destroy(contextLost = false) + { + if (!contextLost) + { + this.emptyPool(); + } + else + { + this.texturePool = {}; + } + } + + /** + * Gets a Power-of-Two render texture. + * + * TODO move to a seperate class could be on renderer? + * also - could cause issue with multiple contexts? + * + * @private + * @param {WebGLRenderingContext} gl - The webgl rendering context + * @param {number} minWidth - The minimum width of the render target. + * @param {number} minHeight - The minimum height of the render target. + * @param {number} resolution - The resolution of the render target. + * @return {PIXI.RenderTarget} The new render target. + */ + getPotFilterTexture(minWidth, minHeight, resolution) + { + minWidth = bitTwiddle.nextPow2(minWidth); + minHeight = bitTwiddle.nextPow2(minHeight); + resolution = resolution || 1; + + const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + + if (!this.texturePool[key]) + { + this.texturePool[key] = []; + } + + let renderTexture = this.texturePool[key].pop(); + + if (!renderTexture) + { + // temporary bypass cache.. + // internally - this will cause a texture to be bound.. + renderTexture = RenderTexture.create({ + width: minWidth, + height: minHeight, + resolution, + }); + } + + return renderTexture; + } + + /** + * Gets extra render texture to use inside current filter + * + * @param {number} resolution resolution of the renderTexture + * @returns {PIXI.RenderTexture} + */ + getFilterTexture(resolution) + { + const rt = this.activeState.renderTexture; + + const filterTexture = this.getPotFilterTexture(rt.width, rt.height, resolution || rt.baseTexture.resolution); + + filterTexture.filterFrame = rt.filterFrame; + + return filterTexture; + } + + /** + * Frees a render texture back into the pool. + * + * @param {PIXI.RenderTarget} renderTexture - The renderTarget to free + */ + returnFilterTexture(renderTexture) + { + renderTexture.filterFrame = null; + + const base = renderTexture.baseTexture; + + const minWidth = base.width; + const minHeight = base.height; + + const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + + this.texturePool[key].push(renderTexture); + } + + /** + * Empties the texture pool. + * + */ + emptyPool() + { + for (const i in this.texturePool) + { + const textures = this.texturePool[i]; + + if (textures) + { + for (let j = 0; j < textures.length; j++) + { + textures[j].destroy(true); + } + } + } + + this.texturePool = {}; + } +} diff --git a/packages/core/src/filters/defaultFilter.frag b/packages/core/src/filters/defaultFilter.frag new file mode 100644 index 0000000..a14c1be --- /dev/null +++ b/packages/core/src/filters/defaultFilter.frag @@ -0,0 +1,22 @@ +varying vec2 vTextureCoord; +varying vec2 vFilterCoord; + +uniform sampler2D uSampler; +uniform sampler2D filterSampler; + +void main(void){ + vec4 masky = texture2D(filterSampler, vFilterCoord); + vec4 sample = texture2D(uSampler, vTextureCoord); + vec4 color; + if(mod(vFilterCoord.x, 1.0) > 0.5) + { + color = vec4(1.0, 0.0, 0.0, 1.0); + } + else + { + color = vec4(0.0, 1.0, 0.0, 1.0); + } + // gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0); + gl_FragColor = mix(sample, masky, 0.5); + gl_FragColor *= sample.a; +} diff --git a/packages/core/src/filters/defaultFilter.vert b/packages/core/src/filters/defaultFilter.vert new file mode 100644 index 0000000..e1febca --- /dev/null +++ b/packages/core/src/filters/defaultFilter.vert @@ -0,0 +1,14 @@ +attribute vec2 aVertexPosition; +attribute vec2 aTextureCoord; + +uniform mat3 projectionMatrix; +uniform mat3 filterMatrix; + +varying vec2 vTextureCoord; +varying vec2 vFilterCoord; + +void main(void){ + gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); + vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy; + vTextureCoord = aTextureCoord ; +} diff --git a/packages/core/src/filters/filterTransforms.js b/packages/core/src/filters/filterTransforms.js new file mode 100644 index 0000000..87a3cd6 --- /dev/null +++ b/packages/core/src/filters/filterTransforms.js @@ -0,0 +1,50 @@ +import { Matrix } from '@pixi/math'; + +/** + * Calculates the mapped matrix + * @param filterArea {Rectangle} The filter area + * @param sprite {Sprite} the target sprite + * @param outputMatrix {Matrix} @alvin + * @private + */ +// this returns a matrix that will normalise map filter cords in the filter to screen space +export function calculateScreenSpaceMatrix(outputMatrix, filterArea, textureSize) +{ + // TODO unwrap? + const mappedMatrix = outputMatrix.identity(); + + mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); + + mappedMatrix.scale(textureSize.width, textureSize.height); + + return mappedMatrix; +} + +export function calculateNormalizedScreenSpaceMatrix(outputMatrix, filterArea, textureSize) +{ + const mappedMatrix = outputMatrix.identity(); + + mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); + + const translateScaleX = (textureSize.width / filterArea.width); + const translateScaleY = (textureSize.height / filterArea.height); + + mappedMatrix.scale(translateScaleX, translateScaleY); + + return mappedMatrix; +} + +// this will map the filter coord so that a texture can be used based on the transform of a sprite +export function calculateSpriteMatrix(outputMatrix, filterArea, textureSize, sprite) +{ + const orig = sprite._texture.orig; + const mappedMatrix = outputMatrix.set(textureSize.width, 0, 0, textureSize.height, filterArea.x, filterArea.y); + const worldTransform = sprite.worldTransform.copyTo(Matrix.TEMP_MATRIX); + + worldTransform.invert(); + mappedMatrix.prepend(worldTransform); + mappedMatrix.scale(1.0 / orig.width, 1.0 / orig.height); + mappedMatrix.translate(sprite.anchor.x, sprite.anchor.y); + + return mappedMatrix; +} diff --git a/packages/core/src/filters/spriteMask/SpriteMaskFilter.js b/packages/core/src/filters/spriteMask/SpriteMaskFilter.js new file mode 100644 index 0000000..91697b7 --- /dev/null +++ b/packages/core/src/filters/spriteMask/SpriteMaskFilter.js @@ -0,0 +1,63 @@ +import Filter from '../Filter'; +import { Matrix } from '@pixi/math'; +import vertex from './spriteMaskFilter.vert'; +import fragment from './spriteMaskFilter.frag'; +import { default as TextureMatrix } from '../../textures/TextureMatrix'; + +/** + * The SpriteMaskFilter class + * + * @class + * @extends PIXI.Filter + * @memberof PIXI + */ +export default class SpriteMaskFilter extends Filter +{ + /** + * @param {PIXI.Sprite} sprite - the target sprite + */ + constructor(sprite) + { + const maskMatrix = new Matrix(); + + super(vertex, fragment); + + sprite.renderable = false; + + this.maskSprite = sprite; + this.maskMatrix = maskMatrix; + } + + /** + * Applies the filter + * + * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + */ + apply(filterManager, input, output) + { + const maskSprite = this.maskSprite; + const tex = this.maskSprite.texture; + + if (!tex.valid) + { + return; + } + if (!tex.transform) + { + // margin = 0.0, let it bleed a bit, shader code becomes easier + // assuming that atlas textures were made with 1-pixel padding + tex.transform = new TextureMatrix(tex, 0.0); + } + tex.transform.update(); + + this.uniforms.mask = tex; + this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite) + .prepend(tex.transform.mapCoord); + this.uniforms.alpha = maskSprite.worldAlpha; + this.uniforms.maskClamp = tex.transform.uClampFrame; + + filterManager.applyFilter(this, input, output); + } +} diff --git a/packages/core/src/filters/spriteMask/spriteMaskFilter.frag b/packages/core/src/filters/spriteMask/spriteMaskFilter.frag new file mode 100644 index 0000000..0e4aef8 --- /dev/null +++ b/packages/core/src/filters/spriteMask/spriteMaskFilter.frag @@ -0,0 +1,23 @@ +varying vec2 vMaskCoord; +varying vec2 vTextureCoord; + +uniform sampler2D uSampler; +uniform sampler2D mask; +uniform float alpha; +uniform vec4 maskClamp; + +void main(void) +{ + float clip = step(3.5, + step(maskClamp.x, vMaskCoord.x) + + step(maskClamp.y, vMaskCoord.y) + + step(vMaskCoord.x, maskClamp.z) + + step(vMaskCoord.y, maskClamp.w)); + + vec4 original = texture2D(uSampler, vTextureCoord); + vec4 masky = texture2D(mask, vMaskCoord); + + original *= (masky.r * masky.a * alpha * clip); + + gl_FragColor = original; +} diff --git a/packages/core/src/filters/spriteMask/spriteMaskFilter.vert b/packages/core/src/filters/spriteMask/spriteMaskFilter.vert new file mode 100644 index 0000000..d23a30c --- /dev/null +++ b/packages/core/src/filters/spriteMask/spriteMaskFilter.vert @@ -0,0 +1,16 @@ +attribute vec2 aVertexPosition; +attribute vec2 aTextureCoord; + +uniform mat3 projectionMatrix; +uniform mat3 otherMatrix; + +varying vec2 vMaskCoord; +varying vec2 vTextureCoord; + +void main(void) +{ + gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); + + vTextureCoord = aTextureCoord; + vMaskCoord = ( otherMatrix * vec3( aTextureCoord, 1.0) ).xy; +} diff --git a/packages/core/src/framebuffer/FrameBuffer.js b/packages/core/src/framebuffer/FrameBuffer.js new file mode 100644 index 0000000..84e9fcc --- /dev/null +++ b/packages/core/src/framebuffer/FrameBuffer.js @@ -0,0 +1,106 @@ +import Texture from '../textures/BaseTexture'; +import { FORMATS, TYPES } from '@pixi/constants'; + +/** + * Frame buffer + * @class + * @memberof PIXI + */ +export default class FrameBuffer +{ + constructor(width, height) + { + this.width = width || 100; + this.height = height || 100; + + this.stencil = false; + this.depth = false; + + this.dirtyId = 0; + this.dirtyFormat = 0; + this.dirtySize = 0; + + this.depthTexture = null; + this.colorTextures = []; + + this.glFrameBuffers = {}; + } + + get colorTexture() + { + return this.colorTextures[0]; + } + + addColorTexture(index, texture) + { + // TODO add some validation to the texture - same width / height etc? + this.colorTextures[index || 0] = texture || new Texture(null, { scaleMode: 0, + resolution: 1, + mipmap: false, + width: this.width, + height: this.height });// || new Texture(); + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + addDepthTexture(texture) + { + /* eslint-disable max-len */ + this.depthTexture = texture || new Texture(null, { scaleMode: 0, + resolution: 1, + width: this.width, + height: this.height, + mipmap: false, + format: FORMATS.DEPTH_COMPONENT, + type: TYPES.UNSIGNED_SHORT });// UNSIGNED_SHORT; + /* eslint-disable max-len */ + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + enableDepth() + { + this.depth = true; + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + enableStencil() + { + this.stencil = true; + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + resize(width, height) + { + if (width === this.width && height === this.height) return; + + this.width = width; + this.height = height; + + this.dirtyId++; + this.dirtySize++; + + for (let i = 0; i < this.colorTextures.length; i++) + { + this.colorTextures[i].setSize(width, height); + } + + if (this.depthTexture) + { + this.depthTexture.setSize(width, height); + } + } +} diff --git a/packages/core/src/framebuffer/FramebufferSystem.js b/packages/core/src/framebuffer/FramebufferSystem.js new file mode 100644 index 0000000..2cc2437 --- /dev/null +++ b/packages/core/src/framebuffer/FramebufferSystem.js @@ -0,0 +1,246 @@ +import System from '../System'; +import { Rectangle } from '@pixi/math'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class FramebufferSystem extends System +{ + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + contextChange() + { + this.gl = this.renderer.gl; + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + this.current = null; + this.viewport = new Rectangle(); + + this.drawBufferExtension = this.renderer.context.extensions.drawBuffers; + } + + bind(framebuffer, frame) + { + const gl = this.gl; + + this.current = framebuffer; + + if (framebuffer) + { + // TODO cacheing layer! + + const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID] || this.initFramebuffer(framebuffer); + + gl.bindFramebuffer(gl.FRAMEBUFFER, fbo.framebuffer); + // makesure all textures are unbound.. + + // now check for updates... + if (fbo.dirtyId !== framebuffer.dirtyId) + { + fbo.dirtyId = framebuffer.dirtyId; + + if (fbo.dirtyFormat !== framebuffer.dirtyFormat) + { + fbo.dirtyFormat = framebuffer.dirtyFormat; + this.updateFramebuffer(framebuffer); + } + else if (fbo.dirtySize !== framebuffer.dirtySize) + { + fbo.dirtySize = framebuffer.dirtySize; + this.resizeFramebuffer(framebuffer); + } + } + + for (let i = 0; i < framebuffer.colorTextures.length; i++) + { + if (framebuffer.colorTextures[i].texturePart) + { + this.renderer.texture.unbind(framebuffer.colorTextures[i].texture); + } + else + { + this.renderer.texture.unbind(framebuffer.colorTextures[i]); + } + } + + if (framebuffer.depthTexture) + { + this.renderer.texture.unbind(framebuffer.depthTexture); + } + + if (frame) + { + this.setViewport(frame.x, frame.y, frame.width, frame.height); + } + else + { + this.setViewport(0, 0, framebuffer.width, framebuffer.height); + } + } + else + { + gl.bindFramebuffer(gl.FRAMEBUFFER, null); + + if (frame) + { + this.setViewport(frame.x, frame.y, frame.width, frame.height); + } + else + { + this.setViewport(0, 0, this.renderer.width, this.renderer.height); + } + } + } + + setViewport(x, y, width, height) + { + const v = this.viewport; + + if (v.width !== width || v.height !== height || v.x !== x || v.y !== y) + { + v.x = x; + v.y = y; + v.width = width; + v.height = height; + + this.gl.viewport(x, y, width, height); + } + } + + get size() + { + if (this.current) + { + // TODO store temp + return { x: 0, y: 0, width: this.current.width, height: this.current.height }; + } + + return { x: 0, y: 0, width: this.renderer.width, height: this.renderer.height }; + } + + clear(r, g, b, a) + { + const gl = this.gl; + + // TODO clear color can be set only one right? + gl.clearColor(r, g, b, a); + gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); + } + + // private functions... + + initFramebuffer(framebuffer) + { + const gl = this.gl; + + // TODO - make this a class? + const fbo = { + framebuffer: gl.createFramebuffer(), + stencil: null, + dirtyId: 0, + dirtyFormat: 0, + dirtySize: 0, + }; + + framebuffer.glFrameBuffers[this.CONTEXT_UID] = fbo; + + return fbo; + } + + resizeFramebuffer(framebuffer) + { + const gl = this.gl; + + if (framebuffer.stencil || framebuffer.depth) + { + gl.bindRenderbuffer(gl.RENDERBUFFER, this.stencil); + gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); + } + } + + updateFramebuffer(framebuffer) + { + const gl = this.gl; + + const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID]; + + // bind the color texture + const colorTextures = framebuffer.colorTextures; + + let count = colorTextures.length; + + if (!this.drawBufferExtension) + { + count = Math.min(count, 1); + } + + const activeTextures = []; + + for (let i = 0; i < count; i++) + { + const texture = framebuffer.colorTextures[i]; + + if (texture.texturePart) + { + this.renderer.texture.bind(texture.texture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.COLOR_ATTACHMENT0 + i, + gl.TEXTURE_CUBE_MAP_NEGATIVE_X + texture.side, + texture.texture._glTextures[this.CONTEXT_UID].texture, + 0); + } + else + { + this.renderer.texture.bind(texture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.COLOR_ATTACHMENT0 + i, + gl.TEXTURE_2D, + texture._glTextures[this.CONTEXT_UID].texture, + 0); + } + + activeTextures.push(gl.COLOR_ATTACHMENT0 + i); + } + + if (this.drawBufferExtension && activeTextures.length > 1) + { + this.drawBufferExtension.drawBuffersWEBGL(activeTextures); + } + + if (framebuffer.depthTexture) + { + const depthTextureExt = this.renderer.context.extensions.depthTexture; + + if (depthTextureExt) + { + const depthTexture = framebuffer.depthTexture; + + this.renderer.texture.bind(depthTexture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.DEPTH_ATTACHMENT, + gl.TEXTURE_2D, + depthTexture._glTextures[this.CONTEXT_UID].texture, + 0); + } + } + + if (framebuffer.stencil || framebuffer.depth) + { + fbo.stencil = gl.createRenderbuffer(); + + gl.bindRenderbuffer(gl.RENDERBUFFER, fbo.stencil); + + // TODO.. this is depth AND stencil? + gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, fbo.stencil); + gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); + // fbo.enableStencil(); + } + } +} diff --git a/packages/core/src/geometry/GLBuffer.js b/packages/core/src/geometry/GLBuffer.js new file mode 100644 index 0000000..0477e8a --- /dev/null +++ b/packages/core/src/geometry/GLBuffer.js @@ -0,0 +1,9 @@ +export default class GLBuffer +{ + constructor(buffer) + { + this.buffer = buffer; + this.updateID = -1; + this.byteLength = -1; + } +} diff --git a/packages/core/src/geometry/GeometrySystem.js b/packages/core/src/geometry/GeometrySystem.js new file mode 100644 index 0000000..959f827 --- /dev/null +++ b/packages/core/src/geometry/GeometrySystem.js @@ -0,0 +1,388 @@ +import System from '../System'; +import GLBuffer from './GLBuffer'; + +const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class GeometrySystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this._activeGeometry = null; + this._activeVao = null; + + this.hasVao = true; + this.hasInstance = true; + } + + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + contextChange() + { + const gl = this.gl = this.renderer.gl; + + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + + // webgl2 + if (!gl.createVertexArray) + { + // webgl 1! + let nativeVaoExtension = this.renderer.context.extensions.vertexArrayObject; + + if (this.renderer.legacy) + { + nativeVaoExtension = null; + } + + if (nativeVaoExtension) + { + gl.createVertexArray = () => + nativeVaoExtension.createVertexArrayOES(); + + gl.bindVertexArray = (vao) => + nativeVaoExtension.bindVertexArrayOES(vao); + + gl.deleteVertexArray = (vao) => + nativeVaoExtension.deleteVertexArrayOES(vao); + } + else + { + this.hasVao = false; + gl.createVertexArray = () => + { + // empty + }; + + gl.bindVertexArray = () => + { + // empty + }; + + gl.deleteVertexArray = () => + { + // empty + }; + } + } + + if (!gl.vertexAttribDivisor) + { + const instanceExt = gl.getExtension('ANGLE_instanced_arrays'); + + if (instanceExt) + { + gl.vertexAttribDivisor = (a, b) => + instanceExt.vertexAttribDivisorANGLE(a, b); + + gl.drawElementsInstanced = (a, b, c, d, e) => + instanceExt.drawElementsInstancedANGLE(a, b, c, d, e); + + gl.drawArraysInstanced = (a, b, c, d) => + instanceExt.drawArraysInstancedANGLE(a, b, c, d); + } + else + { + this.hasInstance = false; + } + } + } + + /** + * Binds geometry so that is can be drawn. Creating a Vao if required + * @private + * @param {PIXI.Geometry} geometry instance of geometry to bind + */ + bind(geometry, shader) + { + shader = shader || this.renderer.shader.shader; + + const gl = this.gl; + + // not sure the best way to address this.. + // currently different shaders require different VAOs for the same geometry + // Still mulling over the best way to solve this one.. + // will likely need to modify the shader attribute locations at run time! + let vaos = geometry.glVertexArrayObjects[this.CONTEXT_UID]; + + if (!vaos) + { + geometry.glVertexArrayObjects[this.CONTEXT_UID] = vaos = {}; + } + + const vao = vaos[shader.program.id] || this.initGeometryVao(geometry, shader.program); + + this._activeGeometry = geometry; + + if (this._activeVao !== vao) + { + this._activeVao = vao; + + if (this.hasVao) + { + gl.bindVertexArray(vao); + } + else + { + this.activateVao(geometry, shader.program); + } + } + + // TODO - optimise later! + // don't need to loop through if nothing changed! + // maybe look to add an 'autoupdate' to geometry? + this.updateBuffers(); + } + + reset() + { + this.unbind(); + } + + updateBuffers() + { + const geometry = this._activeGeometry; + const gl = this.gl; + + for (let i = 0; i < geometry.buffers.length; i++) + { + const buffer = geometry.buffers[i]; + + const glBuffer = buffer._glBuffers[this.CONTEXT_UID]; + + if (buffer._updateID !== glBuffer.updateID) + { + glBuffer.updateID = buffer._updateID; + + // TODO can cache this on buffer! maybe added a getter / setter? + const type = buffer.index ? gl.ELEMENT_ARRAY_BUFFER : gl.ARRAY_BUFFER; + const drawType = buffer.static ? gl.STATIC_DRAW : gl.DYNAMIC_DRAW; + + gl.bindBuffer(type, glBuffer.buffer); + + if (glBuffer.byteLength >= buffer.data.byteLength) + { + // offset is always zero for now! + gl.bufferSubData(type, 0, buffer.data); + } + else + { + gl.bufferData(type, buffer.data, drawType); + } + } + } + } + + checkCompatability(geometry, program) + { + // geometry must have at least all the attributes that the shader requires. + const geometryAttributes = geometry.attributes; + const shaderAttributes = program.attributeData; + + for (const j in shaderAttributes) + { + if (!geometryAttributes[j]) + { + throw new Error(`shader and geometry incompatible, geometry missing the "${j}" attribute`); + } + } + } + + /** + * Creates a Vao with the same structure as the geometry and stores it on the geometry. + * @private + * @param {PIXI.Geometry} geometry instance of geometry to to generate Vao for + */ + initGeometryVao(geometry, program) + { + this.checkCompatability(geometry, program); + + const gl = this.gl; + const CONTEXT_UID = this.CONTEXT_UID; + const buffers = geometry.buffers; + const attributes = geometry.attributes; + + const tempStride = {}; + const tempStart = {}; + + for (const j in buffers) + { + tempStride[j] = 0; + tempStart[j] = 0; + } + + for (const j in attributes) + { + if (!attributes[j].size && program.attributeData[j]) + { + attributes[j].size = program.attributeData[j].size; + } + + tempStride[attributes[j].buffer] += attributes[j].size * byteSizeMap[attributes[j].type]; + } + + for (const j in attributes) + { + const attribute = attributes[j]; + const attribSize = attribute.size; + + if (attribute.stride === undefined) + { + if (tempStride[attribute.buffer] === attribSize * byteSizeMap[attribute.type]) + { + attribute.stride = 0; + } + else + { + attribute.stride = tempStride[attribute.buffer]; + } + } + + if (attribute.start === undefined) + { + attribute.start = tempStart[attribute.buffer]; + + tempStart[attribute.buffer] += attribSize * byteSizeMap[attribute.type]; + } + } + + // first update - and create the buffers! + // only create a gl buffer if it actually gets + for (let i = 0; i < buffers.length; i++) + { + const buffer = buffers[i]; + + if (!buffer._glBuffers[CONTEXT_UID]) + { + buffer._glBuffers[CONTEXT_UID] = new GLBuffer(gl.createBuffer()); + } + } + + // TODO - maybe make this a data object? + // lets wait to see if we need to first! + const vao = gl.createVertexArray(); + + gl.bindVertexArray(vao); + + this.activateVao(geometry, program); + + gl.bindVertexArray(null); + + // add it to the cache! + geometry.glVertexArrayObjects[this.CONTEXT_UID][program.id] = vao; + + return vao; + } + + activateVao(geometry, program) + { + const gl = this.gl; + const CONTEXT_UID = this.CONTEXT_UID; + const buffers = geometry.buffers; + const attributes = geometry.attributes; + + if (geometry.indexBuffer) + { + // first update the index buffer if we have one.. + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, geometry.indexBuffer._glBuffers[CONTEXT_UID].buffer); + } + + let lastBuffer = null; + + // add a new one! + for (const j in attributes) + { + const attribute = attributes[j]; + const buffer = buffers[attribute.buffer]; + const glBuffer = buffer._glBuffers[CONTEXT_UID]; + + if (program.attributeData[j]) + { + if (lastBuffer !== glBuffer) + { + gl.bindBuffer(gl.ARRAY_BUFFER, glBuffer.buffer); + + lastBuffer = glBuffer; + } + + const location = program.attributeData[j].location; + + // TODO introduce state again + // we can optimise this for older devices that have no VAOs + gl.enableVertexAttribArray(location); + + gl.vertexAttribPointer(location, + attribute.size, + attribute.type || gl.FLOAT, + attribute.normalized, + attribute.stride, + attribute.start); + + if (attribute.instance) + { + // TODO calculate instance count based of this... + if (this.hasInstance) + { + gl.vertexAttribDivisor(location, 1); + } + else + { + throw new Error('geometry error, GPU Instancing is not supported on this device'); + } + } + } + } + } + + draw(type, size, start, instanceCount) + { + const gl = this.gl; + const geometry = this._activeGeometry; + + // TODO.. this should not change so maybe cache the function? + + if (geometry.indexBuffer) + { + if (geometry.instanced) + { + /* eslint-disable max-len */ + gl.drawElementsInstanced(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2, instanceCount || 1); + /* eslint-enable max-len */ + } + else + { + gl.drawElements(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2); + } + } + else + if (geometry.instanced) + { + // TODO need a better way to calculate size.. + gl.drawArraysInstanced(type, start, size || geometry.getSize(), instanceCount || 1); + } + else + { + gl.drawArrays(type, start, size || geometry.getSize()); + } + + return this; + } + + unbind() + { + this.gl.bindVertexArray(null); + this._activeVao = null; + this._activeGeometry = null; + } +} diff --git a/packages/core/src/geometry/utils/setVertexAttribArrays.js b/packages/core/src/geometry/utils/setVertexAttribArrays.js new file mode 100644 index 0000000..23b6959 --- /dev/null +++ b/packages/core/src/geometry/utils/setVertexAttribArrays.js @@ -0,0 +1,54 @@ +// var GL_MAP = {}; + +/** + * @param gl {WebGLRenderingContext} The current WebGL context + * @param attribs {*} + * @param state {*} + */ +export default function setVertexAttribArrays(gl, attribs, state) +{ + let i; + + if (state) + { + const tempAttribState = state.tempAttribState; + const attribState = state.attribState; + + for (i = 0; i < tempAttribState.length; i++) + { + tempAttribState[i] = false; + } + + // set the new attribs + for (i = 0; i < attribs.length; i++) + { + tempAttribState[attribs[i].attribute.location] = true; + } + + for (i = 0; i < attribState.length; i++) + { + if (attribState[i] !== tempAttribState[i]) + { + attribState[i] = tempAttribState[i]; + + if (state.attribState[i]) + { + gl.enableVertexAttribArray(i); + } + else + { + gl.disableVertexAttribArray(i); + } + } + } + } + else + { + for (i = 0; i < attribs.length; i++) + { + const attrib = attribs[i]; + + gl.enableVertexAttribArray(attrib.attribute.location); + } + } +} diff --git a/packages/core/src/index.js b/packages/core/src/index.js index 58c456a..fd1f547 100644 --- a/packages/core/src/index.js +++ b/packages/core/src/index.js @@ -1,30 +1,30 @@ import * as resources from './textures/resources'; -import * as systems from './renderers/systems'; +import * as systems from './systems'; -export { resources }; export { systems }; +export { resources }; -export { default as Renderer } from './renderers/Renderer'; -export { default as AbstractRenderer } from './renderers/AbstractRenderer'; -export { default as FrameBuffer } from './textures/FrameBuffer'; +export { default as System } from './System'; +export { default as Renderer } from './Renderer'; +export { default as AbstractRenderer } from './AbstractRenderer'; +export { default as FrameBuffer } from './framebuffer/FrameBuffer'; export { default as CubeTexture } from './textures/CubeTexture'; export { default as BaseTexture } from './textures/BaseTexture'; export { default as Texture } from './textures/Texture'; export { default as TextureMatrix } from './textures/TextureMatrix'; -export { default as RenderTexture } from './textures/RenderTexture'; -export { default as BaseRenderTexture } from './textures/BaseRenderTexture'; +export { default as RenderTexture } from './renderTexture/RenderTexture'; +export { default as BaseRenderTexture } from './renderTexture/BaseRenderTexture'; export { default as TextureUvs } from './textures/TextureUvs'; -export { default as State } from './renderers/State'; -export { default as ObjectRenderer } from './renderers/utils/ObjectRenderer'; -export { default as Quad } from './renderers/utils/Quad'; -export { default as QuadUv } from './renderers/utils/QuadUv'; -export { default as checkMaxIfStatmentsInShader } from './renderers/utils/checkMaxIfStatmentsInShader'; +export { default as State } from './state/State'; +export { default as ObjectRenderer } from './batch/ObjectRenderer'; +export { default as Quad } from './utils/Quad'; +export { default as QuadUv } from './utils/QuadUv'; +export { default as checkMaxIfStatmentsInShader } from './shader/utils/checkMaxIfStatmentsInShader'; export { default as Shader } from './shader/Shader'; export { default as Program } from './shader/Program'; export { default as UniformGroup } from './shader/UniformGroup'; -export { default as SpriteMaskFilter } from './renderers/filters/spriteMask/SpriteMaskFilter'; -export { default as Filter } from './renderers/filters/Filter'; +export { default as SpriteMaskFilter } from './filters/spriteMask/SpriteMaskFilter'; +export { default as Filter } from './filters/Filter'; export { default as Attribute } from './geometry/Attribute'; export { default as Buffer } from './geometry/Buffer'; export { default as Geometry } from './geometry/Geometry'; - diff --git a/packages/core/src/mask/MaskSystem.js b/packages/core/src/mask/MaskSystem.js new file mode 100644 index 0000000..99174c2 --- /dev/null +++ b/packages/core/src/mask/MaskSystem.js @@ -0,0 +1,199 @@ +import System from '../System'; +import AlphaMaskFilter from '../filters/spriteMask/SpriteMaskFilter'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class MaskSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + // TODO - we don't need both! + this.scissor = false; + this.scissorData = null; + this.scissorRenderTarget = null; + + this.enableScissor = false; + + this.alphaMaskPool = []; + this.alphaMaskIndex = 0; + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.DisplayObject} target - Display Object to push the mask to + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + push(target, maskData) + { + // TODO the root check means scissor rect will not + // be used on render textures more info here: + // https://github.com/pixijs/pixi.js/pull/3545 + + if (maskData.texture) + { + this.pushSpriteMask(target, maskData); + } + else if (this.enableScissor + && !this.scissor + && this.renderer._activeRenderTarget.root + && !this.renderer.stencil.stencilMaskStack.length + && maskData.isFastRect()) + { + const matrix = maskData.worldTransform; + + let rot = Math.atan2(matrix.b, matrix.a); + + // use the nearest degree! + rot = Math.round(rot * (180 / Math.PI)); + + if (rot % 90) + { + this.pushStencilMask(maskData); + } + else + { + this.pushScissorMask(target, maskData); + } + } + else + { + this.pushStencilMask(maskData); + } + } + + /** + * Removes the last mask from the mask stack and doesn't return it. + * + * @param {PIXI.DisplayObject} target - Display Object to pop the mask from + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + pop(target, maskData) + { + if (maskData.texture) + { + this.popSpriteMask(target, maskData); + } + else if (this.enableScissor && !this.renderer.stencil.stencilMaskStack.length) + { + this.popScissorMask(target, maskData); + } + else + { + this.popStencilMask(target, maskData); + } + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.RenderTarget} target - Display Object to push the sprite mask to + * @param {PIXI.Sprite} maskData - Sprite to be used as the mask + */ + pushSpriteMask(target, maskData) + { + let alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; + + if (!alphaMaskFilter) + { + alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; + } + + alphaMaskFilter[0].resolution = this.renderer.resolution; + alphaMaskFilter[0].maskSprite = maskData; + + // TODO - may cause issues! + target.filterArea = maskData.getBounds(true); + + this.renderer.filterSystem.pushFilter(target, alphaMaskFilter); + + this.alphaMaskIndex++; + } + + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popSpriteMask() + { + this.renderer.filterSystem.popFilter(); + this.alphaMaskIndex--; + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + pushStencilMask(maskData) + { + this.renderer.batch.flush(); + this.renderer.stencil.pushStencil(maskData); + } + + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popStencilMask() + { + // this.renderer.currentRenderer.stop(); + this.renderer.stencil.popStencil(); + } + + /** + * + * @param {PIXI.DisplayObject} target - Display Object to push the mask to + * @param {PIXI.Graphics} maskData - The masking data. + */ + pushScissorMask(target, maskData) + { + maskData.renderable = true; + + const renderTarget = this.renderer._activeRenderTarget; + + const bounds = maskData.getBounds(); + + bounds.fit(renderTarget.size); + maskData.renderable = false; + + this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); + + const resolution = this.renderer.resolution; + + this.renderer.gl.scissor( + bounds.x * resolution, + (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, + bounds.width * resolution, + bounds.height * resolution + ); + + this.scissorRenderTarget = renderTarget; + this.scissorData = maskData; + this.scissor = true; + } + + /** + * + * + */ + popScissorMask() + { + this.scissorRenderTarget = null; + this.scissorData = null; + this.scissor = false; + + // must be scissor! + const gl = this.renderer.gl; + + gl.disable(gl.SCISSOR_TEST); + } +} diff --git a/packages/core/src/mask/StencilSystem.js b/packages/core/src/mask/StencilSystem.js new file mode 100644 index 0000000..62796a5 --- /dev/null +++ b/packages/core/src/mask/StencilSystem.js @@ -0,0 +1,133 @@ +import System from '../System'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class StencilSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + this.stencilMaskStack = []; + } + + /** + * Changes the mask stack that is used by this System. + * + * @param {PIXI.Graphics[]} stencilMaskStack - The mask stack + */ + setMaskStack(stencilMaskStack) + { + const gl = this.renderer.gl; + + if (stencilMaskStack.length !== this.stencilMaskStack.length) + { + if (stencilMaskStack.length === 0) + { + gl.disable(gl.STENCIL_TEST); + } + else + { + gl.enable(gl.STENCIL_TEST); + } + } + + this.stencilMaskStack = stencilMaskStack; + } + + /** + * Applies the Mask and adds it to the current stencil stack. @alvin + * + * @param {PIXI.Graphics} graphics - The mask + */ + pushStencil(graphics) + { + this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); + + // this.renderer._activeRenderTarget.attachStencilBuffer(); + + const gl = this.renderer.gl; + const prevMaskCount = this.stencilMaskStack.length; + + if (prevMaskCount === 0) + { + gl.enable(gl.STENCIL_TEST); + } + + this.stencilMaskStack.push(graphics); + + // Increment the refference stencil value where the new mask overlaps with the old ones. + gl.colorMask(false, false, false, false); + gl.stencilFunc(gl.EQUAL, prevMaskCount, this._getBitwiseMask()); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.INCR); + this.renderer.plugins.graphics.render(graphics); + + this._useCurrent(); + } + + /** + * Removes the last mask from the stencil stack. @alvin + */ + popStencil() + { + this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); + + const gl = this.renderer.gl; + const graphics = this.stencilMaskStack.pop(); + + if (this.stencilMaskStack.length === 0) + { + // the stack is empty! + gl.disable(gl.STENCIL_TEST); + gl.clear(gl.STENCIL_BUFFER_BIT); + gl.clearStencil(0); + } + else + { + // Decrement the refference stencil value where the popped mask overlaps with the other ones + gl.colorMask(false, false, false, false); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.DECR); + this.renderer.plugins.graphics.render(graphics); + + this._useCurrent(); + } + } + + /** + * Setup renderer to use the current stencil data. + */ + _useCurrent() + { + const gl = this.renderer.gl; + + gl.colorMask(true, true, true, true); + gl.stencilFunc(gl.EQUAL, this.stencilMaskStack.length, this._getBitwiseMask()); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP); + } + + /** + * Fill 1s equal to the number of acitve stencil masks. + * + * @return {number} The bitwise mask. + */ + _getBitwiseMask() + { + return (1 << this.stencilMaskStack.length) - 1; + } + + /** + * Destroys the mask stack. + * + */ + destroy() + { + super.destroy(this); + + this.stencilMaskStack = null; + } +} diff --git a/packages/core/src/projection/ProjectionSystem.js b/packages/core/src/projection/ProjectionSystem.js new file mode 100644 index 0000000..6e63b73 --- /dev/null +++ b/packages/core/src/projection/ProjectionSystem.js @@ -0,0 +1,73 @@ +import System from '../System'; +import { Matrix } from '@pixi/math'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class ProjectionSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.projectionMatrix = new Matrix(); + } + + update(destinationFrame, sourceFrame, resolution, root) + { + this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; + this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; + + this.calculateProjection(this.destinationFrame, this.sourceFrame, resolution, root); + + this.renderer.globalUniforms.uniforms.projectionMatrix = this.projectionMatrix; + this.renderer.globalUniforms.update(); + } + + /** + * Updates the projection matrix based on a projection frame (which is a rectangle) + * + * @param {Rectangle} destinationFrame - The destination frame. + * @param {Rectangle} sourceFrame - The source frame. + */ + calculateProjection(destinationFrame, sourceFrame, resolution, root) + { + const pm = this.projectionMatrix; + + // I don't think we will need this line.. + // pm.identity(); + + if (!root) + { + pm.a = (1 / destinationFrame.width * 2) * resolution; + pm.d = (1 / destinationFrame.height * 2) * resolution; + + pm.tx = -1 - (sourceFrame.x * pm.a); + pm.ty = -1 - (sourceFrame.y * pm.d); + } + else + { + pm.a = (1 / destinationFrame.width * 2) * resolution; + pm.d = (-1 / destinationFrame.height * 2) * resolution; + + pm.tx = -1 - (sourceFrame.x * pm.a); + pm.ty = 1 - (sourceFrame.y * pm.d); + } + } + + /** + * Sets the transform of the active render target to the given matrix + * + * @param {PIXI.Matrix} matrix - The transformation matrix + */ + setTransform()// matrix) + { + // this._activeRenderTarget.transform = matrix; + } +} diff --git a/packages/core/src/renderTexture/BaseRenderTexture.js b/packages/core/src/renderTexture/BaseRenderTexture.js new file mode 100644 index 0000000..88eb83b --- /dev/null +++ b/packages/core/src/renderTexture/BaseRenderTexture.js @@ -0,0 +1,137 @@ +import BaseTexture from '../textures/BaseTexture'; +import FrameBuffer from '../framebuffer/FrameBuffer'; + +/** + * A BaseRenderTexture is a special texture that allows any PixiJS display object to be rendered to it. + * + * __Hint__: All DisplayObjects (i.e. Sprites) that render to a BaseRenderTexture should be preloaded + * otherwise black rectangles will be drawn instead. + * + * A BaseRenderTexture takes a snapshot of any Display Object given to its render method. The position + * and rotation of the given Display Objects is ignored. For example: + * + * ```js + * let renderer = PIXI.autoDetectRenderer(1024, 1024, { view: canvas, ratio: 1 }); + * let baseRenderTexture = new PIXI.BaseRenderTexture(renderer, 800, 600); + * let sprite = PIXI.Sprite.fromImage("spinObj_01.png"); + * + * sprite.position.x = 800/2; + * sprite.position.y = 600/2; + * sprite.anchor.x = 0.5; + * sprite.anchor.y = 0.5; + * + * baseRenderTexture.render(sprite); + * ``` + * + * The Sprite in this case will be rendered using its local transform. To render this sprite at 0,0 + * you can clear the transform + * + * ```js + * + * sprite.setTransform() + * + * let baseRenderTexture = new PIXI.BaseRenderTexture(100, 100); + * let renderTexture = new PIXI.RenderTexture(baseRenderTexture); + * + * renderer.render(sprite, renderTexture); // Renders to center of RenderTexture + * ``` + * + * @class + * @extends PIXI.BaseTexture + * @memberof PIXI + */ +export default class BaseRenderTexture extends BaseTexture +{ + /** + * @param {object} [options] + * @param {number} [options.width=100] - The width of the base render texture + * @param {number} [options.height=100] - The height of the base render texture + * @param {PIXI.SCALE_MODES} [options.scaleMode] - See {@link PIXI.SCALE_MODES} for possible values + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the texture being generated + */ + constructor(options) + { + if (typeof options === 'number') + { + /* eslint-disable prefer-rest-params */ + // Backward compatibility of signature + const width = arguments[0]; + const height = arguments[1]; + const scaleMode = arguments[2]; + const resolution = arguments[3]; + + options = { width, height, scaleMode, resolution }; + /* eslint-enable prefer-rest-params */ + } + + super(null, options); + + const { width, height } = options || {}; + + // Set defaults + this.mipmap = false; + this.width = Math.ceil(width) || 100; + this.height = Math.ceil(height) || 100; + this.valid = true; + + /** + * A map of renderer IDs to webgl renderTargets + * + * @private + * @member {object} + */ + // this._glRenderTargets = {}; + + /** + * A reference to the canvas render target (we only need one as this can be shared across renderers) + * + * @private + * @member {object} + */ + this._canvasRenderTarget = null; + + this.clearColor = [0, 0, 0, 0]; + + this.frameBuffer = new FrameBuffer(width * this.resolution, height * this.resolution) + .addColorTexture(0, this); + + // TODO - could this be added the systems? + + /** + * The data structure for the stencil masks + * + * @member {PIXI.Graphics[]} + */ + this.stencilMaskStack = []; + + /** + * The data structure for the filters + * + * @member {PIXI.Graphics[]} + */ + this.filterStack = [{}]; + } + + /** + * Resizes the BaseRenderTexture. + * + * @param {number} width - The width to resize to. + * @param {number} height - The height to resize to. + */ + resize(width, height) + { + width = Math.ceil(width); + height = Math.ceil(height); + this.frameBuffer.resize(width * this.resolution, height * this.resolution); + } + + /** + * Destroys this texture + * + */ + destroy() + { + super.destroy(true); + this.renderer = null; + } +} diff --git a/packages/core/src/renderTexture/RenderTexture.js b/packages/core/src/renderTexture/RenderTexture.js new file mode 100644 index 0000000..f1ecf1f --- /dev/null +++ b/packages/core/src/renderTexture/RenderTexture.js @@ -0,0 +1,153 @@ +import BaseRenderTexture from './BaseRenderTexture'; +import Texture from '../textures/Texture'; + +/** + * A RenderTexture is a special texture that allows any PixiJS display object to be rendered to it. + * + * __Hint__: All DisplayObjects (i.e. Sprites) that render to a RenderTexture should be preloaded + * otherwise black rectangles will be drawn instead. + * + * A RenderTexture takes a snapshot of any Display Object given to its render method. For example: + * + * ```js + * let renderer = PIXI.autoDetectRenderer(1024, 1024, { view: canvas, ratio: 1 }); + * let renderTexture = PIXI.RenderTexture.create(800, 600); + * let sprite = PIXI.Sprite.fromImage("spinObj_01.png"); + * + * sprite.position.x = 800/2; + * sprite.position.y = 600/2; + * sprite.anchor.x = 0.5; + * sprite.anchor.y = 0.5; + * + * renderer.render(sprite, renderTexture); + * ``` + * + * The Sprite in this case will be rendered using its local transform. To render this sprite at 0,0 + * you can clear the transform + * + * ```js + * + * sprite.setTransform() + * + * let renderTexture = new PIXI.RenderTexture.create(100, 100); + * + * renderer.render(sprite, renderTexture); // Renders to center of RenderTexture + * ``` + * + * @class + * @extends PIXI.Texture + * @memberof PIXI + */ +export default class RenderTexture extends Texture +{ + /** + * @param {PIXI.BaseRenderTexture} baseRenderTexture - The renderer used for this RenderTexture + * @param {PIXI.Rectangle} [frame] - The rectangle frame of the texture to show + */ + constructor(baseRenderTexture, frame) + { + // support for legacy.. + let _legacyRenderer = null; + + if (!(baseRenderTexture instanceof BaseRenderTexture)) + { + /* eslint-disable prefer-rest-params, no-console */ + const width = arguments[1]; + const height = arguments[2]; + const scaleMode = arguments[3]; + const resolution = arguments[4]; + + // we have an old render texture.. + console.warn(`Please use RenderTexture.create(${width}, ${height}) instead of the ctor directly.`); + _legacyRenderer = arguments[0]; + /* eslint-enable prefer-rest-params, no-console */ + + frame = null; + baseRenderTexture = new BaseRenderTexture({ + width, + height, + scaleMode, + resolution, + }); + } + + /** + * The base texture object that this texture uses + * + * @member {BaseTexture} + */ + super(baseRenderTexture, frame); + + this.legacyRenderer = _legacyRenderer; + + /** + * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. + * + * @member {boolean} + */ + this.valid = true; + + /** + * FilterSystem temporary storage + * @private + * @member {PIXI.Rectangle} + */ + this.filterFrame = null; + + this._updateUvs(); + } + + /** + * Resizes the RenderTexture. + * + * @param {number} width - The width to resize to. + * @param {number} height - The height to resize to. + * @param {boolean} [resizeBaseTexture=true] - Should the baseTexture.width and height values be resized as well? + */ + resize(width, height, resizeBaseTexture = true) + { + width = Math.ceil(width); + height = Math.ceil(height); + + // TODO - could be not required.. + this.valid = (width > 0 && height > 0); + + this._frame.width = this.orig.width = width; + this._frame.height = this.orig.height = height; + + if (resizeBaseTexture) + { + this.baseTexture.resize(width, height); + } + + this._updateUvs(); + } + + /** + * A short hand way of creating a render texture. + * + * @param {object} [options] - Options + * @param {number} [options.width=100] - The width of the render texture + * @param {number} [options.height=100] - The height of the render texture + * @param {number} [options.scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the texture being generated + * @return {PIXI.RenderTexture} The new render texture + */ + static create(options) + { + // fallback, old-style: create(width, height, scaleMode, resolution) + if (typeof options === 'number') + { + /* eslint-disable prefer-rest-params */ + options = { + width: options, + height: arguments[1], + scaleMode: arguments[2], + resolution: arguments[3], + }; + /* eslint-enable prefer-rest-params */ + } + + return new RenderTexture(new BaseRenderTexture(options)); + } +} diff --git a/packages/core/src/renderTexture/RenderTextureSystem.js b/packages/core/src/renderTexture/RenderTextureSystem.js new file mode 100644 index 0000000..8d0ca61 --- /dev/null +++ b/packages/core/src/renderTexture/RenderTextureSystem.js @@ -0,0 +1,115 @@ +import System from '../System'; +import { Rectangle } from '@pixi/math'; + +const tempRect = new Rectangle(); + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class RenderTextureSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.clearColor = renderer._backgroundColorRgba; + + // TODO moe this property somewhere else! + this.defaultMaskStack = []; + this.defaultFilterStack = [{}]; + + // empty render texture? + this.renderTexture = null; + + this.destinationFrame = new Rectangle(); + } + + bind(renderTexture, sourceFrame, destinationFrame) + { + // TODO - do we want this?? + if (this.renderTexture === renderTexture) return; + this.renderTexture = renderTexture; + + const renderer = this.renderer; + + if (renderTexture) + { + const baseTexture = renderTexture.baseTexture; + + if (!destinationFrame) + { + tempRect.width = baseTexture.realWidth; + tempRect.height = baseTexture.realHeight; + + destinationFrame = tempRect; + } + + if (!sourceFrame) + { + sourceFrame = destinationFrame; + } + + this.renderer.framebuffer.bind(baseTexture.frameBuffer, destinationFrame); + + this.renderer.projection.update(destinationFrame, sourceFrame, baseTexture.resolution, false); + this.renderer.stencil.setMaskStack(baseTexture.stencilMaskStack); + } + else + { + // TODO these validation checks happen deeper down.. + // thing they can be avoided.. + if (!destinationFrame) + { + tempRect.width = renderer.width; + tempRect.height = renderer.height; + + destinationFrame = tempRect; + } + + if (!sourceFrame) + { + sourceFrame = destinationFrame; + } + + renderer.framebuffer.bind(null, destinationFrame); + + // TODO store this.. + this.renderer.projection.update(destinationFrame, sourceFrame, this.renderer.resolution, true); + this.renderer.stencil.setMaskStack(this.defaultMaskStack); + } + + this.destinationFrame.copyFrom(destinationFrame); + } + + /** + * Erases the render texture and fills the drawing area with a colour + * + * @param {number} [clearColor] - The colour + * @return {PIXI.Renderer} Returns itself. + */ + clear(clearColor) + { + if (this.renderTexture) + { + clearColor = clearColor || this.renderTexture.baseTexture.clearColor; + } + else + { + clearColor = clearColor || this.clearColor; + } + + this.renderer.framebuffer.clear(clearColor[0], clearColor[1], clearColor[2], clearColor[3]); + } + + resize()// screenWidth, screenHeight) + { + // resize the root only! + this.bind(null); + } +} diff --git a/packages/core/src/renderers/AbstractRenderer.js b/packages/core/src/renderers/AbstractRenderer.js deleted file mode 100644 index 928670f..0000000 --- a/packages/core/src/renderers/AbstractRenderer.js +++ /dev/null @@ -1,353 +0,0 @@ -import { hex2string, hex2rgb } from '@pixi/utils'; -import { Matrix, Rectangle } from '@pixi/math'; -import { RENDERER_TYPE } from '@pixi/constants'; -import { settings } from '@pixi/settings'; -import { Container } from '@pixi/display'; -import RenderTexture from '../textures/RenderTexture'; -import EventEmitter from 'eventemitter3'; - -const tempMatrix = new Matrix(); - -/** - * 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 EventEmitter - * @memberof PIXI - */ -export default class AbstractRenderer extends EventEmitter -{ - // eslint-disable-next-line valid-jsdoc - /** - * @param {string} system - The name of the system this renderer is for. - * @param {object} [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.transparent=false] - If the render view is transparent, default false - * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false - * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) - * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. The - * resolution of the renderer retina would be 2. - * @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 {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when rendering, - * stopping pixel interpolation. - */ - constructor(system, options, arg2, arg3) - { - super(); - - // Support for constructor(system, screenWidth, screenHeight, options) - if (typeof options === 'number') - { - options = Object.assign({ - width: options, - height: arg2 || settings.RENDER_OPTIONS.height, - }, arg3); - } - - // 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 = RENDERER_TYPE.UNKNOWN; - - /** - * Measurements of the screen. (0, 0, screenWidth, screenHeight) - * - * Its safe to use as filterArea or hitArea for 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 1 - */ - this.resolution = options.resolution || settings.RESOLUTION; - - /** - * Whether the render view is transparent - * - * @member {boolean} - */ - this.transparent = options.transparent; - - /** - * Whether css dimensions of canvas view should be resized to screen dimensions automatically - * - * @member {boolean} - */ - this.autoResize = options.autoResize || false; - - /** - * Tracks the blend modes useful for this renderer. - * - * @member {object} - */ - this.blendModes = null; - - /** - * 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; - - /** - * If true PixiJS will Math.floor() x/y values when rendering, stopping pixel interpolation. - * Handy for crisp pixel art and speed on legacy devices. - * - * @member {boolean} - */ - this.roundPixels = options.roundPixels; - - /** - * The background color as a number. - * - * @member {number} - * @private - */ - this._backgroundColor = 0x000000; - - /** - * The background color as an [R, G, B] array. - * - * @member {number[]} - * @private - */ - this._backgroundColorRgba = [0, 0, 0, 0]; - - /** - * The background color as a string. - * - * @member {string} - * @private - */ - this._backgroundColorString = '#000000'; - - this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter - - /** - * This temporary display object used as the parent of the currently being rendered item - * - * @member {PIXI.DisplayObject} - * @private - */ - this._tempDisplayObjectParent = new Container(); - - /** - * The last root object that the renderer tried to render. - * - * @member {PIXI.DisplayObject} - * @private - */ - this._lastObjectRendered = this._tempDisplayObjectParent; - - /** - * Collection of plugins. - * @readonly - * @member {object} - */ - this.plugins = {}; - } - - /** - * Initialize the plugins. - * - * @protected - * @param {object} staticMap - The dictionary of staticly saved plugins. - */ - initPlugins(staticMap) - { - 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() - { - return this.view.width; - } - - /** - * Same as view.height, actual number of pixels in the canvas by vertical - * - * @member {number} - * @readonly - * @default 600 - */ - get height() - { - return this.view.height; - } - - /** - * Resizes the screen and canvas to the specified width and height - * Canvas dimensions are multiplied by resolution - * - * @param {number} screenWidth - the new width of the screen - * @param {number} screenHeight - the new height of the screen - */ - resize(screenWidth, screenHeight) - { - this.screen.width = screenWidth; - this.screen.height = screenHeight; - - this.view.width = screenWidth * this.resolution; - this.view.height = screenHeight * this.resolution; - - if (this.autoResize) - { - this.view.style.width = `${screenWidth}px`; - this.view.style.height = `${screenHeight}px`; - } - } - - /** - * 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 {PIXI.DisplayObject} displayObject - The displayObject the object will be generated from - * @param {number} scaleMode - Should be one of the scaleMode consts - * @param {number} resolution - The resolution / device pixel ratio of the texture being generated - * @param {PIXI.Rectangle} [region] - The region of the displayObject, that shall be rendered, - * if no region is specified, defaults to the local bounds of the displayObject. - * @return {PIXI.Texture} a texture of the graphics object - */ - generateTexture(displayObject, scaleMode, resolution, region) - { - region = region || displayObject.getLocalBounds(); - - // 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(region.width | 0, region.height | 0, scaleMode, resolution); - - tempMatrix.tx = -region.x; - tempMatrix.ty = -region.y; - - this.render(displayObject, renderTexture, false, tempMatrix, true); - - return renderTexture; - } - - /** - * Removes everything from the renderer and optionally removes the Canvas DOM element. - * - * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. - */ - destroy(removeView) - { - 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); - } - - this.plugins = null; - - this.type = RENDERER_TYPE.UNKNOWN; - - this.view = null; - - this.screen = null; - - this.resolution = 0; - - this.transparent = false; - - this.autoResize = false; - - this.blendModes = null; - - this.options = null; - - this.preserveDrawingBuffer = false; - this.clearBeforeRender = false; - - this.roundPixels = false; - - this._backgroundColor = 0; - this._backgroundColorRgba = null; - this._backgroundColorString = null; - - this._tempDisplayObjectParent = null; - this._lastObjectRendered = null; - } - - /** - * The background color to fill if not transparent - * - * @member {number} - */ - get backgroundColor() - { - return this._backgroundColor; - } - - set backgroundColor(value) // eslint-disable-line require-jsdoc - { - this._backgroundColor = value; - this._backgroundColorString = hex2string(value); - hex2rgb(value, this._backgroundColorRgba); - } -} diff --git a/packages/core/src/renderers/Renderer.js b/packages/core/src/renderers/Renderer.js deleted file mode 100644 index a2020b1..0000000 --- a/packages/core/src/renderers/Renderer.js +++ /dev/null @@ -1,329 +0,0 @@ -import AbstractRenderer from './AbstractRenderer'; -import { sayHello } from '@pixi/utils'; -import MaskSystem from './systems/MaskSystem'; -import StencilSystem from './systems/StencilSystem'; -import FilterSystem from './systems/filter/FilterSystem'; -import FramebufferSystem from './systems/FramebufferSystem'; -import RenderTextureSystem from './systems/RenderTextureSystem'; -import TextureSystem from './systems/textures/TextureSystem'; -import ProjectionSystem from './systems/ProjectionSystem'; -import StateSystem from './systems/StateSystem'; -import GeometrySystem from './systems/geometry/GeometrySystem'; -import ShaderSystem from './systems/shader/ShaderSystem'; -import ContextSystem from './systems/ContextSystem'; -import BatchSystem from './systems/BatchSystem'; -import TextureGCSystem from './systems/textures/TextureGCSystem'; -import { RENDERER_TYPE } from '@pixi/constants'; -import UniformGroup from '../shader/UniformGroup'; -import { Matrix } from '@pixi/math'; -import Runner from 'mini-runner'; - -/** - * 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 Render works by automatically managing webGLBatchs. - * 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 :) - * - * @class - * @memberof PIXI - * @extends PIXI.AbstractRenderer - */ -export default class Renderer extends AbstractRenderer -{ - // eslint-disable-next-line valid-jsdoc - /** - * - * @param {object} [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.transparent=false] - If the render view is transparent, default false - * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false - * @param {boolean} [options.antialias=false] - sets antialias. If not available natively then FXAA - * antialiasing is used - * @param {boolean} [options.forceFXAA=false] - forces FXAA antialiasing to be used over native. - * FXAA is faster, but may not always look as great - * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. - * The resolution of the renderer retina would be 2. - * @param {boolean} [options.clearBeforeRender=true] - This sets if the renderer will clear - * the canvas or not before the new render pass. If you wish to set this to false, you *must* set - * preserveDrawingBuffer to `true`. - * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, - * enable this if you need to call toDataUrl on the webgl context. - * @param {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when - * rendering, stopping pixel interpolation. - * @param {number} [options.backgroundColor=0x000000] - The background color of the rendered area - * (shown if not transparent). - * @param {boolean} [options.legacy=false] - If true PixiJS will aim to ensure compatibility - * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. - * @param {string} [options.powerPreference] - Parameter passed to webgl context, set to "high-performance" - * for devices with dual graphics card - */ - constructor(options, arg2, arg3) - { - super('WebGL', options, arg2, arg3); - - /** - * The type of this renderer as a standardised const - * - * @member {number} - * @see PIXI.RENDERER_TYPE - */ - this.type = RENDERER_TYPE.WEBGL; - - // this will be set by the contextSystem (this.context) - this.gl = null; - this.CONTEXT_UID = 0; - this.legacy = !!options.legacy; - - // TODO legacy! - - // runners! - this.runners = { - destroy: new Runner('destroy'), - contextChange: new Runner('contextChange', 1), - reset: new Runner('reset'), - update: new Runner('update'), - postrender: new Runner('postrender'), - prerender: new Runner('prerender'), - resize: new Runner('resize', 2), - }; - - 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(GeometrySystem, 'geometry') - .addSystem(FramebufferSystem, 'framebuffer') - .addSystem(StencilSystem, 'stencil') - .addSystem(ProjectionSystem, 'projection') - .addSystem(TextureGCSystem, 'textureGC') - .addSystem(FilterSystem, 'filter') - .addSystem(RenderTextureSystem, 'renderTexture') - .addSystem(BatchSystem, 'batch'); - - this.initPlugins(Renderer.__plugins); - - /** - * The options passed in to create a new webgl context. - * - * @member {object} - * @private - */ - if (options.context) - { - this.context.initFromContext(options.context); - } - else - { - this.context.initFromOptions({ - alpha: this.transparent, - antialias: options.antialias, - premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', - stencil: true, - 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); - } - - addSystem(_class, name) - { - if (!name) - { - name = _class.name; - } - - // TODO - read name from class.name.. - - /* - if(name.includes('System')) - { - name = name.replace('System', ''); - name = name.charAt(0).toLowerCase() + name.slice(1); - } - */ - - const system = new _class(this); - - if (this[name]) - { - throw new Error(`Whoops! ${name} is already a manger`); - } - - this[name] = system; - - for (const i in this.runners) - { - this.runners[i].add(system); - } - - return this; - - /** - * 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. - */ - } - - /** - * Renders the object to its webGL view - * - * @param {PIXI.DisplayObject} displayObject - the object to be rendered - * @param {PIXI.RenderTexture} renderTexture - The render texture to render to. - * @param {boolean} [clear] - Should the canvas be cleared before the new render - * @param {PIXI.Transform} [transform] - A transform to apply to the render texture before rendering. - * @param {boolean} [skipUpdateTransform] - Should we skip the update transform pass? - */ - render(displayObject, renderTexture, clear, transform, skipUpdateTransform) - { - // can be handy to know! - this.renderingToScreen = !renderTexture; - - this.runners.prerender.run(); - this.emit('prerender'); - - // 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.parent; - - displayObject.parent = this._tempDisplayObjectParent; - displayObject.updateTransform(); - displayObject.parent = 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.run(); - - this.emit('postrender'); - } - - /** - * Resizes the webGL view to the specified width and height. - * - * @param {number} screenWidth - the new width of the screen - * @param {number} screenHeight - the new height of the screen - */ - resize(screenWidth, screenHeight) - { - AbstractRenderer.prototype.resize.call(this, screenWidth, screenHeight); - - this.runners.resize.run(screenWidth, screenHeight); - } - - /** - * Resets the WebGL state so you can render things however you fancy! - * - * @return {PIXI.Renderer} Returns itself. - */ - reset() - { - this.runners.reset.run(); - - return this; - } - - clear() - { - this.framebuffer.bind(); - this.framebuffer.clear(); - } - - /** - * Removes everything from the renderer (event listeners, spritebatch, etc...) - * - * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. - * See: https://github.com/pixijs/pixi.js/issues/2233 - */ - destroy(removeView) - { - this.runners.destroy.run(); - - // call base destroy - super.destroy(removeView); - - // TODO nullify all the managers.. - this.gl = null; - } - - /** - * 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 - * builds and excluding plugins. - * @name PIXI.Renderer#plugins - * @type {object} - * @readonly - * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. - * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. - * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. - * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. - */ - - /** - * Adds a plugin to the renderer. - * - * @method - * @param {string} pluginName - The name of the plugin. - * @param {Function} ctor - The constructor function or class for the plugin. - */ - static registerPlugin(pluginName, ctor) - { - Renderer.__plugins = Renderer.__plugins || {}; - Renderer.__plugins[pluginName] = ctor; - } -} diff --git a/packages/core/src/renderers/State.js b/packages/core/src/renderers/State.js deleted file mode 100644 index fa3f221..0000000 --- a/packages/core/src/renderers/State.js +++ /dev/null @@ -1,163 +0,0 @@ -/* eslint-disable max-len */ - -const BLEND = 0; -const OFFSET = 1; -const CULLING = 2; -const DEPTH_TEST = 3; -const WINDING = 4; - -/** - * This is a webGL state. It is passed The WebGL StateManager. - * Each mesh renderered may require webGL to be in a different state. - * For example you may want different blend mode or to enable polygon offsets - * - * @class - * @memberof PIXI - */ -export default class State -{ - /** - * - */ - constructor() - { - this.data = 0; - - this.blendMode = 0; - this.polygonOffset = 0; - - this.blend = true; - // this.depthTest = true; - } - - /** - * Activates blending of the computed fragment color values - * - * @member {boolean} - */ - get blend() - { - return !!(this.data & (1 << BLEND)); - } - - set blend(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << BLEND)) !== value) - { - this.data ^= (1 << BLEND); - } - } - - /** - * Activates adding an offset to depth values of polygon's fragments - * - * @member {boolean} - * @default false - */ - get offsets() - { - return !!(this.data & (1 << OFFSET)); - } - - set offsets(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << OFFSET)) !== value) - { - this.data ^= (1 << OFFSET); - } - } - - /** - * Activates culling of polygons. - * - * @member {boolean} - * @default false - */ - get culling() - { - return !!(this.data & (1 << CULLING)); - } - - set culling(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << CULLING)) !== value) - { - this.data ^= (1 << CULLING); - } - } - - /** - * Activates depth comparisons and updates to the depth buffer. - * - * @member {boolean} - * @default false - */ - get depthTest() - { - return !!(this.data & (1 << DEPTH_TEST)); - } - - set depthTest(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << DEPTH_TEST)) !== value) - { - this.data ^= (1 << DEPTH_TEST); - } - } - - /** - * Specifies whether or not front or back-facing polygons can be culled. - * @member {boolean} - * @default false - */ - get clockwiseFrontFace() - { - return !!(this.data & (1 << WINDING)); - } - - set clockwiseFrontFace(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << WINDING)) !== value) - { - this.data ^= (1 << WINDING); - } - } - - /** - * The blend mode to be applied when this state is set. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. - * Setting this mode to anything other than NO_BLEND will automatically switch blending on. - * - * @member {boolean} - * @default PIXI.BLEND_MODES.NORMAL - * @see PIXI.BLEND_MODES - */ - get blendMode() - { - return this._blendMode; - } - - set blendMode(value) // eslint-disable-line require-jsdoc - { - // 17 is NO BLEND - this.blend = (value !== 17); - this._blendMode = value; - } - - /** - * The polygon offset. Setting this property to anything other than 0 will automatically enable poygon offset fill. - * - * @member {number} - * @default 0 - */ - get polygonOffset() - { - return this._polygonOffset; - } - - set polygonOffset(value) // eslint-disable-line require-jsdoc - { - this.offsets = !!value; - this._polygonOffset = value; - } -} - diff --git a/packages/core/src/renderers/filters/Filter.js b/packages/core/src/renderers/filters/Filter.js deleted file mode 100644 index 2073b8d..0000000 --- a/packages/core/src/renderers/filters/Filter.js +++ /dev/null @@ -1,140 +0,0 @@ -import Shader from '../../shader/Shader'; -import Program from '../../shader/Program'; -import State from '../State'; -import { settings } from '@pixi/settings'; -// import extractUniformsFromSrc from './extractUniformsFromSrc'; -import defaultVertex from './defaultFilter.vert'; -import defaultFragment from './defaultFilter.frag'; - -// let math = require('../../math'); -/** - * @class - * @memberof PIXI - * @extends PIXI.Shader - */ -export default class Filter extends Shader -{ - /** - * @param {string} [vertexSrc] - The source of the vertex shader. - * @param {string} [fragmentSrc] - The source of the fragment shader. - * @param {object} [uniforms] - Custom uniforms to use to augment the built-in ones. - */ - constructor(vertexSrc, fragmentSrc, uniforms) - { - const program = Program.from(vertexSrc, fragmentSrc); - - super(program, uniforms); - - /** - * The padding of the filter. Some filters require extra space to breath such as a blur. - * Increasing this will add extra width and height to the bounds of the object that the - * filter is applied to. - * - * @member {number} - */ - this.padding = 0; - - /** - * The resolution of the filter. Setting this to be lower will lower the quality but - * increase the performance of the filter. - * - * @member {number} - */ - this.resolution = settings.RESOLUTION; - - /** - * If enabled is true the filter is applied, if false it will not. - * - * @member {boolean} - */ - this.enabled = true; - - /** - * If enabled, PixiJS will fit the filter area into boundaries for better performance. - * Switch it off if it does not work for specific shader. - * - * @member {boolean} - */ - this.autoFit = true; - - /** - * Legacy filters use position and uvs from attributes - * @member {boolean} - * @readonly - */ - this.legacy = !!this.program.attributeData.aTextureCoord; - - /** - * the webGL state the filter requires to render - * @member {PIXI.State} - */ - this.state = new State(); - } - - /** - * Applies the filter - * - * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from - * @param {PIXI.RenderTarget} input - The input render target. - * @param {PIXI.RenderTarget} output - The target to output to. - * @param {boolean} clear - Should the output be cleared before rendering to it - * @param {object} [currentState] - It's current state of filter. - * There are some useful properties in the currentState : - * target, filters, sourceFrame, destinationFrame, renderTarget, resolution - */ - apply(filterManager, input, output, clear, currentState, derp) // eslint-disable-line no-unused-vars - { - // do as you please! - - filterManager.applyFilter(this, input, output, clear, currentState, derp); - - // or just do a regular render.. - } - - /** - * Sets the blendmode of the filter - * - * @member {number} - * @default PIXI.BLEND_MODES.NORMAL - */ - get blendMode() - { - return this.state.blendMode; - } - - set blendMode(value) // eslint-disable-line require-jsdoc - { - this.state.blendMode = value; - } - - /** - * The default vertex shader source - * - * @static - * @constant - */ - static get defaultVertexSrc() - { - return defaultVertex; - } - - /** - * The default fragment shader source - * - * @static - * @constant - */ - static get defaultFragmentSrc() - { - return defaultFragment; - } -} - -/** - * Used for caching shader IDs - * - * @static - * @private - */ -Filter.SOURCE_KEY_MAP = {}; - diff --git a/packages/core/src/renderers/filters/defaultFilter.frag b/packages/core/src/renderers/filters/defaultFilter.frag deleted file mode 100644 index a14c1be..0000000 --- a/packages/core/src/renderers/filters/defaultFilter.frag +++ /dev/null @@ -1,22 +0,0 @@ -varying vec2 vTextureCoord; -varying vec2 vFilterCoord; - -uniform sampler2D uSampler; -uniform sampler2D filterSampler; - -void main(void){ - vec4 masky = texture2D(filterSampler, vFilterCoord); - vec4 sample = texture2D(uSampler, vTextureCoord); - vec4 color; - if(mod(vFilterCoord.x, 1.0) > 0.5) - { - color = vec4(1.0, 0.0, 0.0, 1.0); - } - else - { - color = vec4(0.0, 1.0, 0.0, 1.0); - } - // gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0); - gl_FragColor = mix(sample, masky, 0.5); - gl_FragColor *= sample.a; -} diff --git a/packages/core/src/renderers/filters/defaultFilter.vert b/packages/core/src/renderers/filters/defaultFilter.vert deleted file mode 100644 index e1febca..0000000 --- a/packages/core/src/renderers/filters/defaultFilter.vert +++ /dev/null @@ -1,14 +0,0 @@ -attribute vec2 aVertexPosition; -attribute vec2 aTextureCoord; - -uniform mat3 projectionMatrix; -uniform mat3 filterMatrix; - -varying vec2 vTextureCoord; -varying vec2 vFilterCoord; - -void main(void){ - gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); - vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy; - vTextureCoord = aTextureCoord ; -} diff --git a/packages/core/src/renderers/filters/filterTransforms.js b/packages/core/src/renderers/filters/filterTransforms.js deleted file mode 100644 index 87a3cd6..0000000 --- a/packages/core/src/renderers/filters/filterTransforms.js +++ /dev/null @@ -1,50 +0,0 @@ -import { Matrix } from '@pixi/math'; - -/** - * Calculates the mapped matrix - * @param filterArea {Rectangle} The filter area - * @param sprite {Sprite} the target sprite - * @param outputMatrix {Matrix} @alvin - * @private - */ -// this returns a matrix that will normalise map filter cords in the filter to screen space -export function calculateScreenSpaceMatrix(outputMatrix, filterArea, textureSize) -{ - // TODO unwrap? - const mappedMatrix = outputMatrix.identity(); - - mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); - - mappedMatrix.scale(textureSize.width, textureSize.height); - - return mappedMatrix; -} - -export function calculateNormalizedScreenSpaceMatrix(outputMatrix, filterArea, textureSize) -{ - const mappedMatrix = outputMatrix.identity(); - - mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); - - const translateScaleX = (textureSize.width / filterArea.width); - const translateScaleY = (textureSize.height / filterArea.height); - - mappedMatrix.scale(translateScaleX, translateScaleY); - - return mappedMatrix; -} - -// this will map the filter coord so that a texture can be used based on the transform of a sprite -export function calculateSpriteMatrix(outputMatrix, filterArea, textureSize, sprite) -{ - const orig = sprite._texture.orig; - const mappedMatrix = outputMatrix.set(textureSize.width, 0, 0, textureSize.height, filterArea.x, filterArea.y); - const worldTransform = sprite.worldTransform.copyTo(Matrix.TEMP_MATRIX); - - worldTransform.invert(); - mappedMatrix.prepend(worldTransform); - mappedMatrix.scale(1.0 / orig.width, 1.0 / orig.height); - mappedMatrix.translate(sprite.anchor.x, sprite.anchor.y); - - return mappedMatrix; -} diff --git a/packages/core/src/renderers/filters/spriteMask/SpriteMaskFilter.js b/packages/core/src/renderers/filters/spriteMask/SpriteMaskFilter.js deleted file mode 100644 index d5a91a4..0000000 --- a/packages/core/src/renderers/filters/spriteMask/SpriteMaskFilter.js +++ /dev/null @@ -1,63 +0,0 @@ -import Filter from '../Filter'; -import { Matrix } from '@pixi/math'; -import vertex from './spriteMaskFilter.vert'; -import fragment from './spriteMaskFilter.frag'; -import { default as TextureMatrix } from '../../../textures/TextureMatrix'; - -/** - * The SpriteMaskFilter class - * - * @class - * @extends PIXI.Filter - * @memberof PIXI - */ -export default class SpriteMaskFilter extends Filter -{ - /** - * @param {PIXI.Sprite} sprite - the target sprite - */ - constructor(sprite) - { - const maskMatrix = new Matrix(); - - super(vertex, fragment); - - sprite.renderable = false; - - this.maskSprite = sprite; - this.maskMatrix = maskMatrix; - } - - /** - * Applies the filter - * - * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from - * @param {PIXI.RenderTarget} input - The input render target. - * @param {PIXI.RenderTarget} output - The target to output to. - */ - apply(filterManager, input, output) - { - const maskSprite = this.maskSprite; - const tex = this.maskSprite.texture; - - if (!tex.valid) - { - return; - } - if (!tex.transform) - { - // margin = 0.0, let it bleed a bit, shader code becomes easier - // assuming that atlas textures were made with 1-pixel padding - tex.transform = new TextureMatrix(tex, 0.0); - } - tex.transform.update(); - - this.uniforms.mask = tex; - this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite) - .prepend(tex.transform.mapCoord); - this.uniforms.alpha = maskSprite.worldAlpha; - this.uniforms.maskClamp = tex.transform.uClampFrame; - - filterManager.applyFilter(this, input, output); - } -} diff --git a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.frag b/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.frag deleted file mode 100644 index 0e4aef8..0000000 --- a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.frag +++ /dev/null @@ -1,23 +0,0 @@ -varying vec2 vMaskCoord; -varying vec2 vTextureCoord; - -uniform sampler2D uSampler; -uniform sampler2D mask; -uniform float alpha; -uniform vec4 maskClamp; - -void main(void) -{ - float clip = step(3.5, - step(maskClamp.x, vMaskCoord.x) + - step(maskClamp.y, vMaskCoord.y) + - step(vMaskCoord.x, maskClamp.z) + - step(vMaskCoord.y, maskClamp.w)); - - vec4 original = texture2D(uSampler, vTextureCoord); - vec4 masky = texture2D(mask, vMaskCoord); - - original *= (masky.r * masky.a * alpha * clip); - - gl_FragColor = original; -} diff --git a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.vert b/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.vert deleted file mode 100644 index d23a30c..0000000 --- a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.vert +++ /dev/null @@ -1,16 +0,0 @@ -attribute vec2 aVertexPosition; -attribute vec2 aTextureCoord; - -uniform mat3 projectionMatrix; -uniform mat3 otherMatrix; - -varying vec2 vMaskCoord; -varying vec2 vTextureCoord; - -void main(void) -{ - gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); - - vTextureCoord = aTextureCoord; - vMaskCoord = ( otherMatrix * vec3( aTextureCoord, 1.0) ).xy; -} diff --git a/packages/core/src/renderers/systems/BatchSystem.js b/packages/core/src/renderers/systems/BatchSystem.js deleted file mode 100644 index 70e9518..0000000 --- a/packages/core/src/renderers/systems/BatchSystem.js +++ /dev/null @@ -1,68 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import ObjectRenderer from '../utils/ObjectRenderer'; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ - -export default class BatchSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this manager works for. - */ - constructor(renderer) - { - super(renderer); - - /** - * An empty renderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.emptyRenderer = new ObjectRenderer(renderer); - - /** - * The currently active ObjectRenderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.currentRenderer = this.emptyRenderer; - } - - /** - * Changes the current renderer to the one given in parameter - * - * @param {PIXI.ObjectRenderer} objectRenderer - The object renderer to use. - */ - setObjectRenderer(objectRenderer) - { - if (this.currentRenderer === objectRenderer) - { - return; - } - - this.currentRenderer.stop(); - this.currentRenderer = objectRenderer; - - this.renderer.state.setState(objectRenderer.state); - - this.currentRenderer.start(); - } - - /** - * This should be called if you wish to do some custom rendering - * It will basically render anything that may be batched up such as sprites - * - */ - flush() - { - this.setObjectRenderer(this.emptyRenderer); - } - - reset() - { - this.setObjectRenderer(this.emptyRenderer); - } -} diff --git a/packages/core/src/renderers/systems/ContextSystem.js b/packages/core/src/renderers/systems/ContextSystem.js deleted file mode 100644 index 43bf827..0000000 --- a/packages/core/src/renderers/systems/ContextSystem.js +++ /dev/null @@ -1,186 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { settings } from '@pixi/settings'; - -let CONTEXT_UID = 0; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class ContextSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this.webGLVersion = 1; - - this.handleContextLost = this.handleContextLost.bind(this); - this.handleContextRestored = this.handleContextRestored.bind(this); - - this.extensions = {}; - - renderer.view.addEventListener('webglcontextlost', this.handleContextLost, false); - renderer.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); - } - - get isLost() - { - return (!this.gl || this.gl.isContextLost()); - } - - contextChange(gl) - { - this.gl = gl; - - // restore a context if it was previously lost - if (gl.isContextLost() && gl.getExtension('WEBGL_lose_context')) - { - gl.getExtension('WEBGL_lose_context').restoreContext(); - } - } - - initFromContext(gl) - { - this.gl = gl; - this.validateContext(gl); - this.renderer.gl = gl; - this.renderer.CONTEXT_UID = CONTEXT_UID++; - this.renderer.runners.contextChange.run(gl); - } - - initFromOptions(options) - { - const gl = this.createContext(this.renderer.view, options); - - this.initFromContext(gl); - } - - /** - * Helper class to create a webGL Context - * - * @class - * @memberof PIXI.glCore - * @param canvas {HTMLCanvasElement} the canvas element that we will get the context from - * @param options {Object} An options object that gets passed in to the canvas element containing the context attributes, - * see https://developer.mozilla.org/en/docs/Web/API/HTMLCanvasElement/getContext for the options available - * @return {WebGLRenderingContext} the WebGL context - */ - createContext(canvas, options) - { - let gl; - - if (settings.PREFER_WEBGL_2) - { - gl = canvas.getContext('webgl2', options); - } - - if (gl) - { - this.webGLVersion = 2; - } - else - { - this.webGLVersion = 1; - - gl = canvas.getContext('webgl', options) - || canvas.getContext('experimental-webgl', options); - - if (!gl) - { - // fail, not able to get a context - throw new Error('This browser does not support webGL. Try using the canvas renderer'); - } - } - - this.gl = gl; - - this.getExtensions(); - - return gl; - } - - getExtensions() - { - // time to set up default etensions that pixi uses.. - const gl = this.gl; - const extensions = this.extensions; - - if (this.webGLVersion === 1) - { - extensions.drawBuffers = gl.getExtension('WEBGL_draw_buffers'); - extensions.depthTexture = gl.getExtension('WEBKIT_WEBGL_depth_texture'); - extensions.floatTexture = gl.getExtension('OES_texture_float'); - extensions.loseContext = gl.getExtension('WEBGL_lose_context'); - - extensions.vertexArrayObject = gl.getExtension('OES_vertex_array_object') - || gl.getExtension('MOZ_OES_vertex_array_object') - || gl.getExtension('WEBKIT_OES_vertex_array_object'); - } - - // we don't use any specific WebGL 2 ones yet! - } - - /** - * Handles a lost webgl context - * - * @private - * @param {WebGLContextEvent} event - The context lost event. - */ - handleContextLost(event) - { - event.preventDefault(); - } - - /** - * Handles a restored webgl context - * - * @private - */ - handleContextRestored() - { - this.renderer.runners.contextChange.run(this.gl); - } - - destroy() - { - const view = this.renderer.view; - - // remove listeners - view.removeEventListener('webglcontextlost', this.handleContextLost); - view.removeEventListener('webglcontextrestored', this.handleContextRestored); - - this.gl.useProgram(null); - - if (this.extensions.loseContext) - { - this.extensions.loseContext.loseContext(); - } - } - - postrender() - { - this.gl.flush(); - } - - validateContext(gl) - { - const attributes = gl.getContextAttributes(); - - // this is going to be fairly simple for now.. but at least we have room to grow! - if (!attributes.stencil) - { - /* eslint-disable max-len */ - - /* eslint-disable no-console */ - console.warn('Provided WebGL context does not have a stencil buffer, masks may not render correctly'); - /* eslint-enable no-console */ - - /* eslint-enable max-len */ - } - } -} diff --git a/packages/core/src/renderers/systems/FramebufferSystem.js b/packages/core/src/renderers/systems/FramebufferSystem.js deleted file mode 100644 index df7902b..0000000 --- a/packages/core/src/renderers/systems/FramebufferSystem.js +++ /dev/null @@ -1,246 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { Rectangle } from '@pixi/math'; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class FramebufferSystem extends WebGLSystem -{ - /** - * Sets up the renderer context and necessary buffers. - * - * @private - */ - contextChange() - { - this.gl = this.renderer.gl; - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - this.current = null; - this.viewport = new Rectangle(); - - this.drawBufferExtension = this.renderer.context.extensions.drawBuffers; - } - - bind(framebuffer, frame) - { - const gl = this.gl; - - this.current = framebuffer; - - if (framebuffer) - { - // TODO cacheing layer! - - const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID] || this.initFramebuffer(framebuffer); - - gl.bindFramebuffer(gl.FRAMEBUFFER, fbo.framebuffer); - // makesure all textures are unbound.. - - // now check for updates... - if (fbo.dirtyId !== framebuffer.dirtyId) - { - fbo.dirtyId = framebuffer.dirtyId; - - if (fbo.dirtyFormat !== framebuffer.dirtyFormat) - { - fbo.dirtyFormat = framebuffer.dirtyFormat; - this.updateFramebuffer(framebuffer); - } - else if (fbo.dirtySize !== framebuffer.dirtySize) - { - fbo.dirtySize = framebuffer.dirtySize; - this.resizeFramebuffer(framebuffer); - } - } - - for (let i = 0; i < framebuffer.colorTextures.length; i++) - { - if (framebuffer.colorTextures[i].texturePart) - { - this.renderer.texture.unbind(framebuffer.colorTextures[i].texture); - } - else - { - this.renderer.texture.unbind(framebuffer.colorTextures[i]); - } - } - - if (framebuffer.depthTexture) - { - this.renderer.texture.unbind(framebuffer.depthTexture); - } - - if (frame) - { - this.setViewport(frame.x, frame.y, frame.width, frame.height); - } - else - { - this.setViewport(0, 0, framebuffer.width, framebuffer.height); - } - } - else - { - gl.bindFramebuffer(gl.FRAMEBUFFER, null); - - if (frame) - { - this.setViewport(frame.x, frame.y, frame.width, frame.height); - } - else - { - this.setViewport(0, 0, this.renderer.width, this.renderer.height); - } - } - } - - setViewport(x, y, width, height) - { - const v = this.viewport; - - if (v.width !== width || v.height !== height || v.x !== x || v.y !== y) - { - v.x = x; - v.y = y; - v.width = width; - v.height = height; - - this.gl.viewport(x, y, width, height); - } - } - - get size() - { - if (this.current) - { - // TODO store temp - return { x: 0, y: 0, width: this.current.width, height: this.current.height }; - } - - return { x: 0, y: 0, width: this.renderer.width, height: this.renderer.height }; - } - - clear(r, g, b, a) - { - const gl = this.gl; - - // TODO clear color can be set only one right? - gl.clearColor(r, g, b, a); - gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); - } - - // private functions... - - initFramebuffer(framebuffer) - { - const gl = this.gl; - - // TODO - make this a class? - const fbo = { - framebuffer: gl.createFramebuffer(), - stencil: null, - dirtyId: 0, - dirtyFormat: 0, - dirtySize: 0, - }; - - framebuffer.glFrameBuffers[this.CONTEXT_UID] = fbo; - - return fbo; - } - - resizeFramebuffer(framebuffer) - { - const gl = this.gl; - - if (framebuffer.stencil || framebuffer.depth) - { - gl.bindRenderbuffer(gl.RENDERBUFFER, this.stencil); - gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); - } - } - - updateFramebuffer(framebuffer) - { - const gl = this.gl; - - const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID]; - - // bind the color texture - const colorTextures = framebuffer.colorTextures; - - let count = colorTextures.length; - - if (!this.drawBufferExtension) - { - count = Math.min(count, 1); - } - - const activeTextures = []; - - for (let i = 0; i < count; i++) - { - const texture = framebuffer.colorTextures[i]; - - if (texture.texturePart) - { - this.renderer.texture.bind(texture.texture, 0); - - gl.framebufferTexture2D(gl.FRAMEBUFFER, - gl.COLOR_ATTACHMENT0 + i, - gl.TEXTURE_CUBE_MAP_NEGATIVE_X + texture.side, - texture.texture._glTextures[this.CONTEXT_UID].texture, - 0); - } - else - { - this.renderer.texture.bind(texture, 0); - - gl.framebufferTexture2D(gl.FRAMEBUFFER, - gl.COLOR_ATTACHMENT0 + i, - gl.TEXTURE_2D, - texture._glTextures[this.CONTEXT_UID].texture, - 0); - } - - activeTextures.push(gl.COLOR_ATTACHMENT0 + i); - } - - if (this.drawBufferExtension && activeTextures.length > 1) - { - this.drawBufferExtension.drawBuffersWEBGL(activeTextures); - } - - if (framebuffer.depthTexture) - { - const depthTextureExt = this.renderer.context.extensions.depthTexture; - - if (depthTextureExt) - { - const depthTexture = framebuffer.depthTexture; - - this.renderer.texture.bind(depthTexture, 0); - - gl.framebufferTexture2D(gl.FRAMEBUFFER, - gl.DEPTH_ATTACHMENT, - gl.TEXTURE_2D, - depthTexture._glTextures[this.CONTEXT_UID].texture, - 0); - } - } - - if (framebuffer.stencil || framebuffer.depth) - { - fbo.stencil = gl.createRenderbuffer(); - - gl.bindRenderbuffer(gl.RENDERBUFFER, fbo.stencil); - - // TODO.. this is depth AND stencil? - gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, fbo.stencil); - gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); - // fbo.enableStencil(); - } - } -} diff --git a/packages/core/src/renderers/systems/MaskSystem.js b/packages/core/src/renderers/systems/MaskSystem.js deleted file mode 100644 index d9c4b1a..0000000 --- a/packages/core/src/renderers/systems/MaskSystem.js +++ /dev/null @@ -1,199 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import AlphaMaskFilter from '../filters/spriteMask/SpriteMaskFilter'; - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class MaskSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - // TODO - we don't need both! - this.scissor = false; - this.scissorData = null; - this.scissorRenderTarget = null; - - this.enableScissor = false; - - this.alphaMaskPool = []; - this.alphaMaskIndex = 0; - } - - /** - * Applies the Mask and adds it to the current filter stack. - * - * @param {PIXI.DisplayObject} target - Display Object to push the mask to - * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. - */ - push(target, maskData) - { - // TODO the root check means scissor rect will not - // be used on render textures more info here: - // https://github.com/pixijs/pixi.js/pull/3545 - - if (maskData.texture) - { - this.pushSpriteMask(target, maskData); - } - else if (this.enableScissor - && !this.scissor - && this.renderer._activeRenderTarget.root - && !this.renderer.stencil.stencilMaskStack.length - && maskData.isFastRect()) - { - const matrix = maskData.worldTransform; - - let rot = Math.atan2(matrix.b, matrix.a); - - // use the nearest degree! - rot = Math.round(rot * (180 / Math.PI)); - - if (rot % 90) - { - this.pushStencilMask(maskData); - } - else - { - this.pushScissorMask(target, maskData); - } - } - else - { - this.pushStencilMask(maskData); - } - } - - /** - * Removes the last mask from the mask stack and doesn't return it. - * - * @param {PIXI.DisplayObject} target - Display Object to pop the mask from - * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. - */ - pop(target, maskData) - { - if (maskData.texture) - { - this.popSpriteMask(target, maskData); - } - else if (this.enableScissor && !this.renderer.stencil.stencilMaskStack.length) - { - this.popScissorMask(target, maskData); - } - else - { - this.popStencilMask(target, maskData); - } - } - - /** - * Applies the Mask and adds it to the current filter stack. - * - * @param {PIXI.RenderTarget} target - Display Object to push the sprite mask to - * @param {PIXI.Sprite} maskData - Sprite to be used as the mask - */ - pushSpriteMask(target, maskData) - { - let alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; - - if (!alphaMaskFilter) - { - alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; - } - - alphaMaskFilter[0].resolution = this.renderer.resolution; - alphaMaskFilter[0].maskSprite = maskData; - - // TODO - may cause issues! - target.filterArea = maskData.getBounds(true); - - this.renderer.filterSystem.pushFilter(target, alphaMaskFilter); - - this.alphaMaskIndex++; - } - - /** - * Removes the last filter from the filter stack and doesn't return it. - * - */ - popSpriteMask() - { - this.renderer.filterSystem.popFilter(); - this.alphaMaskIndex--; - } - - /** - * Applies the Mask and adds it to the current filter stack. - * - * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. - */ - pushStencilMask(maskData) - { - this.renderer.batch.flush(); - this.renderer.stencil.pushStencil(maskData); - } - - /** - * Removes the last filter from the filter stack and doesn't return it. - * - */ - popStencilMask() - { - // this.renderer.currentRenderer.stop(); - this.renderer.stencil.popStencil(); - } - - /** - * - * @param {PIXI.DisplayObject} target - Display Object to push the mask to - * @param {PIXI.Graphics} maskData - The masking data. - */ - pushScissorMask(target, maskData) - { - maskData.renderable = true; - - const renderTarget = this.renderer._activeRenderTarget; - - const bounds = maskData.getBounds(); - - bounds.fit(renderTarget.size); - maskData.renderable = false; - - this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); - - const resolution = this.renderer.resolution; - - this.renderer.gl.scissor( - bounds.x * resolution, - (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, - bounds.width * resolution, - bounds.height * resolution - ); - - this.scissorRenderTarget = renderTarget; - this.scissorData = maskData; - this.scissor = true; - } - - /** - * - * - */ - popScissorMask() - { - this.scissorRenderTarget = null; - this.scissorData = null; - this.scissor = false; - - // must be scissor! - const gl = this.renderer.gl; - - gl.disable(gl.SCISSOR_TEST); - } -} diff --git a/packages/core/src/renderers/systems/ProjectionSystem.js b/packages/core/src/renderers/systems/ProjectionSystem.js deleted file mode 100644 index ec5efdb..0000000 --- a/packages/core/src/renderers/systems/ProjectionSystem.js +++ /dev/null @@ -1,73 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { Matrix } from '@pixi/math'; - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ - -export default class ProjectionSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this.projectionMatrix = new Matrix(); - } - - update(destinationFrame, sourceFrame, resolution, root) - { - this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; - this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; - - this.calculateProjection(this.destinationFrame, this.sourceFrame, resolution, root); - - this.renderer.globalUniforms.uniforms.projectionMatrix = this.projectionMatrix; - this.renderer.globalUniforms.update(); - } - - /** - * Updates the projection matrix based on a projection frame (which is a rectangle) - * - * @param {Rectangle} destinationFrame - The destination frame. - * @param {Rectangle} sourceFrame - The source frame. - */ - calculateProjection(destinationFrame, sourceFrame, resolution, root) - { - const pm = this.projectionMatrix; - - // I don't think we will need this line.. - // pm.identity(); - - if (!root) - { - pm.a = (1 / destinationFrame.width * 2) * resolution; - pm.d = (1 / destinationFrame.height * 2) * resolution; - - pm.tx = -1 - (sourceFrame.x * pm.a); - pm.ty = -1 - (sourceFrame.y * pm.d); - } - else - { - pm.a = (1 / destinationFrame.width * 2) * resolution; - pm.d = (-1 / destinationFrame.height * 2) * resolution; - - pm.tx = -1 - (sourceFrame.x * pm.a); - pm.ty = 1 - (sourceFrame.y * pm.d); - } - } - - /** - * Sets the transform of the active render target to the given matrix - * - * @param {PIXI.Matrix} matrix - The transformation matrix - */ - setTransform()// matrix) - { - // this._activeRenderTarget.transform = matrix; - } -} diff --git a/packages/core/src/renderers/systems/RenderTextureSystem.js b/packages/core/src/renderers/systems/RenderTextureSystem.js deleted file mode 100644 index a133ef1..0000000 --- a/packages/core/src/renderers/systems/RenderTextureSystem.js +++ /dev/null @@ -1,115 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { Rectangle } from '@pixi/math'; - -const tempRect = new Rectangle(); - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ - -export default class RenderTextureSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this.clearColor = renderer._backgroundColorRgba; - - // TODO moe this property somewhere else! - this.defaultMaskStack = []; - this.defaultFilterStack = [{}]; - - // empty render texture? - this.renderTexture = null; - - this.destinationFrame = new Rectangle(); - } - - bind(renderTexture, sourceFrame, destinationFrame) - { - // TODO - do we want this?? - if (this.renderTexture === renderTexture) return; - this.renderTexture = renderTexture; - - const renderer = this.renderer; - - if (renderTexture) - { - const baseTexture = renderTexture.baseTexture; - - if (!destinationFrame) - { - tempRect.width = baseTexture.realWidth; - tempRect.height = baseTexture.realHeight; - - destinationFrame = tempRect; - } - - if (!sourceFrame) - { - sourceFrame = destinationFrame; - } - - this.renderer.framebuffer.bind(baseTexture.frameBuffer, destinationFrame); - - this.renderer.projection.update(destinationFrame, sourceFrame, baseTexture.resolution, false); - this.renderer.stencil.setMaskStack(baseTexture.stencilMaskStack); - } - else - { - // TODO these validation checks happen deeper down.. - // thing they can be avoided.. - if (!destinationFrame) - { - tempRect.width = renderer.width; - tempRect.height = renderer.height; - - destinationFrame = tempRect; - } - - if (!sourceFrame) - { - sourceFrame = destinationFrame; - } - - renderer.framebuffer.bind(null, destinationFrame); - - // TODO store this.. - this.renderer.projection.update(destinationFrame, sourceFrame, this.renderer.resolution, true); - this.renderer.stencil.setMaskStack(this.defaultMaskStack); - } - - this.destinationFrame.copyFrom(destinationFrame); - } - - /** - * Erases the render texture and fills the drawing area with a colour - * - * @param {number} [clearColor] - The colour - * @return {PIXI.Renderer} Returns itself. - */ - clear(clearColor) - { - if (this.renderTexture) - { - clearColor = clearColor || this.renderTexture.baseTexture.clearColor; - } - else - { - clearColor = clearColor || this.clearColor; - } - - this.renderer.framebuffer.clear(clearColor[0], clearColor[1], clearColor[2], clearColor[3]); - } - - resize()// screenWidth, screenHeight) - { - // resize the root only! - this.bind(null); - } -} diff --git a/packages/core/src/renderers/systems/StateSystem.js b/packages/core/src/renderers/systems/StateSystem.js deleted file mode 100755 index d7f4caf..0000000 --- a/packages/core/src/renderers/systems/StateSystem.js +++ /dev/null @@ -1,288 +0,0 @@ -import mapWebGLBlendModesToPixi from '../utils/mapWebGLBlendModesToPixi'; -import WebGLSystem from './WebGLSystem'; -import WebGLState from '../State'; - -const BLEND = 0; -const OFFSET = 1; -const CULLING = 2; -const DEPTH_TEST = 3; -const WINDING = 4; - -/** - * A WebGL state machines - * - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class StateSystem extends WebGLSystem -{ - /** - * @param {WebGLRenderingContext} gl - The current WebGL rendering context - */ - constructor(renderer) - { - super(renderer); - - this.gl = null; - - this.maxAttribs = null; - - // check we have vao.. - this.nativeVaoExtension = null; - - this.attribState = null; - - this.stateId = 0; - this.polygonOffset = 0; - this.blendMode = 17; - - this.map = []; - - // map functions for when we set state.. - this.map[BLEND] = this.setBlend; - this.map[OFFSET] = this.setOffset; - this.map[CULLING] = this.setCullFace; - this.map[DEPTH_TEST] = this.setDepthTest; - this.map[WINDING] = this.setFrontFace; - - this.checks = []; - - this.defaultState = new WebGLState(); - this.defaultState.blend = true; - this.defaultState.depth = true; - } - - contextChange(gl) - { - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; - - this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); - - // check we have vao.. - this.nativeVaoExtension = ( - gl.getExtension('OES_vertex_array_object') - || gl.getExtension('MOZ_OES_vertex_array_object') - || gl.getExtension('WEBKIT_OES_vertex_array_object') - ); - - this.attribState = { - tempAttribState: new Array(this.maxAttribs), - attribState: new Array(this.maxAttribs), - }; - - this.blendModes = mapWebGLBlendModesToPixi(gl); - - this.setState(this.defaultState); - - this.reset(); - } - - /** - * Sets the current state - * - * @param {*} state - The state to set. - */ - setState(state) - { - state = state || this.defaultState; - - // TODO maybe to an object check? ( this.state === state )? - if (this.stateId !== state.data) - { - let diff = this.stateId ^ state.data; - let i = 0; - - // order from least to most common - while (diff) - { - if (diff & 1) - { - // state change! - this.map[i].call(this, !!(state.data & (1 << i))); - } - - diff = diff >> 1; - i++; - } - - this.stateId = state.data; - } - - // based on the above settings we check for specific modes.. - // for example if blend is active we check and set the blend modes - // or of polygon offset is active we check the poly depth. - for (let i = 0; i < this.checks.length; i++) - { - this.checks[i](this, state); - } - } - - /** - * Enables or disabled blending. - * - * @param {boolean} value - Turn on or off webgl blending. - */ - setBlend(value) - { - this.updateCheck(StateSystem.checkBlendMode, value); - - this.gl[value ? 'enable' : 'disable'](this.gl.BLEND); - } - - /** - * Enables or disable polygon offset fill - * - * @param {boolean} value - Turn on or off webgl polygon offset testing. - */ - setOffset(value) - { - this.gl[value ? 'enable' : 'disable'](this.gl.POLYGON_OFFSET_FILL); - } - - /** - * Sets whether to enable or disable depth test. - * - * @param {boolean} value - Turn on or off webgl depth testing. - */ - setDepthTest(value) - { - this.gl[value ? 'enable' : 'disable'](this.gl.DEPTH_TEST); - } - - /** - * Sets whether to enable or disable cull face. - * - * @param {boolean} value - Turn on or off webgl cull face. - */ - setCullFace(value) - { - this.gl[value ? 'enable' : 'disable'](this.gl.CULL_FACE); - } - - /** - * Sets the gl front face. - * - * @param {boolean} value - true is clockwise and false is counter-clockwise - */ - setFrontFace(value) - { - this.gl.frontFace(this.gl[value ? 'CW' : 'CCW']); - } - - /** - * Sets the blend mode. - * - * @param {number} value - The blend mode to set to. - */ - setBlendMode(value) - { - if (value === this.blendMode) - { - return; - } - - this.blendMode = value; - this.gl.blendFunc(this.blendModes[value][0], this.blendModes[value][1]); - } - - /** - * Sets the polygon offset. - * - * @param {number} value - the polygon offset - * @param {number} scale - the polygon offset scale - */ - setPolygonOffset(value, scale) - { - this.gl.polygonOffset(value, scale); - } - - /** - * Disables all the vaos in use - * - */ - resetAttributes() - { - for (let i = 0; i < this.attribState.tempAttribState.length; i++) - { - this.attribState.tempAttribState[i] = 0; - } - - for (let i = 0; i < this.attribState.attribState.length; i++) - { - this.attribState.attribState[i] = 0; - } - - // im going to assume one is always active for performance reasons. - for (let i = 1; i < this.maxAttribs; i++) - { - this.gl.disableVertexAttribArray(i); - } - } - - // used - /** - * Resets all the logic and disables the vaos - */ - reset() - { - // unbind any VAO if they exist.. - if (this.nativeVaoExtension) - { - this.nativeVaoExtension.bindVertexArrayOES(null); - } - - // reset all attributes.. - this.resetAttributes(); - - this.gl.pixelStorei(this.gl.UNPACK_FLIP_Y_WEBGL, false); - - this.setBlendMode(0); - - // TODO? - // this.setState(this.defaultState); - } - - /** - * checks to see which updates should be checked based on which settings have been activated - * for example if blend is enabled then we shold check the blend modes each time the state is changed - * or if poygon fill is activated then we need to check if the polygone offset changes. - * The idea is that we only check what we have too. - * - * @param {Function} func the checking function to add or remove - * @param {boolean} value should the check function be added or removed. - */ - updateCheck(func, value) - { - const index = this.checks.indexOf(func); - - if (value && index === -1) - { - this.checks.push(func); - } - else if (!value && index !== -1) - { - this.checks.splice(index, 1); - } - } - - /** - * A private little wrapper function that we call to check the blend mode. - * - * @static - * @private - * @param {PIXI.StateSystem} System the System to perform the state check on - * @param {PIXI.State} state the state that the blendMode will pulled from - */ - static checkBlendMode(system, state) - { - system.setBlendMode(state.blendMode); - } - - // TODO - add polygon offset? -} diff --git a/packages/core/src/renderers/systems/StencilSystem.js b/packages/core/src/renderers/systems/StencilSystem.js deleted file mode 100644 index 5d538cb..0000000 --- a/packages/core/src/renderers/systems/StencilSystem.js +++ /dev/null @@ -1,133 +0,0 @@ -import WebGLSystem from './WebGLSystem'; - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class StencilSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - this.stencilMaskStack = []; - } - - /** - * Changes the mask stack that is used by this System. - * - * @param {PIXI.Graphics[]} stencilMaskStack - The mask stack - */ - setMaskStack(stencilMaskStack) - { - const gl = this.renderer.gl; - - if (stencilMaskStack.length !== this.stencilMaskStack.length) - { - if (stencilMaskStack.length === 0) - { - gl.disable(gl.STENCIL_TEST); - } - else - { - gl.enable(gl.STENCIL_TEST); - } - } - - this.stencilMaskStack = stencilMaskStack; - } - - /** - * Applies the Mask and adds it to the current stencil stack. @alvin - * - * @param {PIXI.Graphics} graphics - The mask - */ - pushStencil(graphics) - { - this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); - - // this.renderer._activeRenderTarget.attachStencilBuffer(); - - const gl = this.renderer.gl; - const prevMaskCount = this.stencilMaskStack.length; - - if (prevMaskCount === 0) - { - gl.enable(gl.STENCIL_TEST); - } - - this.stencilMaskStack.push(graphics); - - // Increment the refference stencil value where the new mask overlaps with the old ones. - gl.colorMask(false, false, false, false); - gl.stencilFunc(gl.EQUAL, prevMaskCount, this._getBitwiseMask()); - gl.stencilOp(gl.KEEP, gl.KEEP, gl.INCR); - this.renderer.plugins.graphics.render(graphics); - - this._useCurrent(); - } - - /** - * Removes the last mask from the stencil stack. @alvin - */ - popStencil() - { - this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); - - const gl = this.renderer.gl; - const graphics = this.stencilMaskStack.pop(); - - if (this.stencilMaskStack.length === 0) - { - // the stack is empty! - gl.disable(gl.STENCIL_TEST); - gl.clear(gl.STENCIL_BUFFER_BIT); - gl.clearStencil(0); - } - else - { - // Decrement the refference stencil value where the popped mask overlaps with the other ones - gl.colorMask(false, false, false, false); - gl.stencilOp(gl.KEEP, gl.KEEP, gl.DECR); - this.renderer.plugins.graphics.render(graphics); - - this._useCurrent(); - } - } - - /** - * Setup renderer to use the current stencil data. - */ - _useCurrent() - { - const gl = this.renderer.gl; - - gl.colorMask(true, true, true, true); - gl.stencilFunc(gl.EQUAL, this.stencilMaskStack.length, this._getBitwiseMask()); - gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP); - } - - /** - * Fill 1s equal to the number of acitve stencil masks. - * - * @return {number} The bitwise mask. - */ - _getBitwiseMask() - { - return (1 << this.stencilMaskStack.length) - 1; - } - - /** - * Destroys the mask stack. - * - */ - destroy() - { - super.destroy(this); - - this.stencilMaskStack = null; - } -} diff --git a/packages/core/src/renderers/systems/WebGLSystem.js b/packages/core/src/renderers/systems/WebGLSystem.js deleted file mode 100644 index d3d5b31..0000000 --- a/packages/core/src/renderers/systems/WebGLSystem.js +++ /dev/null @@ -1,41 +0,0 @@ -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this manager works for. - */ - constructor(renderer) - { - /** - * The renderer this manager works for. - * - * @member {PIXI.Renderer} - */ - this.renderer = renderer; - - this.renderer.runners.contextChange.add(this); - } - - /** - * Generic method called when there is a WebGL context change. - * - */ - contextChange() - { - // do some codes init! - } - - /** - * Generic destroy methods to be overridden by the subclass - * - */ - destroy() - { - this.renderer.runners.contextChange.remove(this); - this.renderer = null; - } -} diff --git a/packages/core/src/renderers/systems/filter/FilterSystem.js b/packages/core/src/renderers/systems/filter/FilterSystem.js deleted file mode 100644 index e615037..0000000 --- a/packages/core/src/renderers/systems/filter/FilterSystem.js +++ /dev/null @@ -1,431 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; - -import RenderTexture from '../../../textures/RenderTexture'; -import Quad from '../../utils/Quad'; -import QuadUv from '../../utils/QuadUv'; -import { Rectangle } from '@pixi/math'; -import * as filterTransforms from '../../filters/filterTransforms'; -import bitTwiddle from 'bit-twiddle'; -import UniformGroup from '../../../shader/UniformGroup'; -import { DRAW_MODES } from '../../../../../constants'; - -// -/** - * @ignore - * @class - */ -class FilterState -{ - /** - * - */ - constructor() - { - this.renderTexture = null; - this.sourceFrame = new Rectangle(); - this.destinationFrame = new Rectangle(); - this.filters = []; - this.target = null; - this.legacy = false; - this.resolution = 1; - } -} - -/** - * @class - * @memberof PIXI.systems - * @extends PIXI.systems.WebGLSystem - */ -export default class FilterSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - /** - * stores a bunch of PO2 textures used for filtering - * @type {Object} - */ - this.texturePool = {}; - - /** - * a pool for storing filter states, save us creating new ones each tick - * @type {Array} - */ - this.statePool = []; - - /** - * A very simple geometry used when drawing a filter effect to the screen - * @type {Quad} - */ - this.quad = new Quad(); - - this.quadUv = new QuadUv(); - - /** - * Temporary rect for maths - * @type {PIXI.Rectangle} - */ - this.tempRect = new Rectangle(); - - this.activeState = {}; - - /** - * this uniform group is attached to filter uniforms when used - * @type {UniformGroup} - */ - this.globalUniforms = new UniformGroup({ - sourceFrame: this.tempRect, - destinationFrame: this.tempRect, - - // legacy variables - filterArea: new Float32Array(4), - filterClamp: new Float32Array(4), - }, true); - } - - /** - * Adds a new filter to the System. - * - * @param {PIXI.DisplayObject} target - The target of the filter to render. - * @param {PIXI.Filter[]} filters - The filters to apply. - */ - push(target, filters) - { - const renderer = this.renderer; - const filterStack = this.renderer.renderTexture.defaultFilterStack; - const state = this.statePool.pop() || new FilterState(); - - let resolution = filters[0].resolution; - let padding = filters[0].padding; - let autoFit = filters[0].autoFit; - let legacy = filters[0].legacy; - - for (let i = 1; i < filters.length; i++) - { - const filter = filters[i]; - - // lets use the lowest resolution.. - resolution = Math.min(resolution, filter.resolution); - // and the largest amount of padding! - padding = Math.max(padding, filter.padding); - // only auto fit if all filters are autofit - autoFit = autoFit || filter.autoFit; - - legacy = legacy || filter.legacy; - } - - filterStack.push(state); - - state.resolution = resolution; - - state.legacy = legacy; - - // round to whole number based on resolution - // TODO move that to the shader too? - state.sourceFrame = target.filterArea || target.getBounds(true); - - state.sourceFrame.pad(padding); - - if (autoFit) - { - state.sourceFrame.fit(this.renderer.renderTexture.destinationFrame); - } - - state.sourceFrame.round(resolution); - - state.renderTexture = this.getPotFilterTexture(state.sourceFrame.width, state.sourceFrame.height, resolution); - state.filters = filters; - - state.destinationFrame.width = state.renderTexture.width; - state.destinationFrame.height = state.renderTexture.height; - - state.renderTexture.filterFrame = state.sourceFrame; - renderer.renderTexture.bind(state.renderTexture, state.sourceFrame);// /, state.destinationFrame); - renderer.renderTexture.clear(); - } - - /** - * Pops off the filter and applies it. - * - */ - pop() - { - const renderer = this.renderer; - const filterStack = renderer.renderTexture.defaultFilterStack; - const state = filterStack.pop(); - const filters = state.filters; - - this.activeState = state; - - const globalUniforms = this.globalUniforms.uniforms; - - globalUniforms.sourceFrame = state.sourceFrame; - globalUniforms.destinationFrame = state.destinationFrame; - globalUniforms.resolution = state.resolution; - - // only update the rect if its legacy.. - if (state.legacy) - { - const filterArea = globalUniforms.filterArea; - const filterClamp = globalUniforms.filterClamp; - - filterArea[0] = state.destinationFrame.width; - filterArea[1] = state.destinationFrame.height; - filterArea[2] = state.sourceFrame.x; - filterArea[3] = state.sourceFrame.y; - - filterClamp[0] = 0.5 / state.resolution / state.destinationFrame.width; - filterClamp[1] = 0.5 / state.resolution / state.destinationFrame.height; - filterClamp[2] = (state.sourceFrame.width - 0.5) / state.resolution / state.destinationFrame.width; - filterClamp[3] = (state.sourceFrame.height - 0.5) / state.resolution / state.destinationFrame.height; - } - - this.globalUniforms.update(); - - const lastState = filterStack[filterStack.length - 1]; - - if (filters.length === 1) - { - filters[0].apply(this, state.renderTexture, lastState.renderTexture, false, state); - - this.returnFilterTexture(state.renderTexture); - } - else - { - let flip = state.renderTexture; - let flop = this.getPotFilterTexture( - flip.width, - flip.height, - state.resolution - ); - - flop.filterFrame = flip.filterFrame; - - let i = 0; - - for (i = 0; i < filters.length - 1; ++i) - { - filters[i].apply(this, flip, flop, true, state); - - const t = flip; - - flip = flop; - flop = t; - } - - filters[i].apply(this, flip, lastState.renderTexture, false, state); - - this.returnFilterTexture(flip); - this.returnFilterTexture(flop); - } - - this.statePool.push(state); - } - - /** - * Draws a filter. - * - * @param {PIXI.Filter} filter - The filter to draw. - * @param {PIXI.RenderTarget} input - The input render target. - * @param {PIXI.RenderTarget} output - The target to output to. - * @param {boolean} clear - Should the output be cleared before rendering to it - */ - applyFilter(filter, input, output, clear) - { - const renderer = this.renderer; - - renderer.renderTexture.bind(output, output ? output.filterFrame : null); - - if (clear) - { - // gl.disable(gl.SCISSOR_TEST); - renderer.renderTexture.clear(); - // gl.enable(gl.SCISSOR_TEST); - } - - // set the uniforms.. - filter.uniforms.uSampler = input; - filter.uniforms.filterGlobals = this.globalUniforms; - - // TODO make it so that the order of this does not matter.. - // because it does at the moment cos of global uniforms. - // they need to get resynced - - renderer.state.setState(filter.state); - renderer.shader.bind(filter); - - if (filter.legacy) - { - this.quadUv.map(input._frame, input.filterFrame); - - renderer.geometry.bind(this.quadUv); - renderer.geometry.draw(DRAW_MODES.TRIANGLES); - } - else - { - renderer.geometry.bind(this.quad); - renderer.geometry.draw(DRAW_MODES.TRIANGLE_STRIP); - } - } - - /** - * Calculates the mapped matrix. - * - * TODO playing around here.. this is temporary - (will end up in the shader) - * this returns a matrix that will normalise map filter cords in the filter to screen space - * - * @param {PIXI.Matrix} outputMatrix - the matrix to output to. - * @return {PIXI.Matrix} The mapped matrix. - */ - calculateScreenSpaceMatrix(outputMatrix) - { - const currentState = this.activeState; - - return filterTransforms.calculateScreenSpaceMatrix( - outputMatrix, - currentState.sourceFrame, - currentState.destinationFrame - ); - } - - /** - * This will map the filter coord so that a texture can be used based on the transform of a sprite - * - * @param {PIXI.Matrix} outputMatrix - The matrix to output to. - * @param {PIXI.Sprite} sprite - The sprite to map to. - * @return {PIXI.Matrix} The mapped matrix. - */ - calculateSpriteMatrix(outputMatrix, sprite) - { - const currentState = this.activeState; - - return filterTransforms.calculateSpriteMatrix( - outputMatrix, - currentState.sourceFrame, - currentState.destinationFrame, - sprite - ); - } - - /** - * Destroys this Filter System. - * - * @param {boolean} [contextLost=false] context was lost, do not free shaders - * - */ - destroy(contextLost = false) - { - if (!contextLost) - { - this.emptyPool(); - } - else - { - this.texturePool = {}; - } - } - - /** - * Gets a Power-of-Two render texture. - * - * TODO move to a seperate class could be on renderer? - * also - could cause issue with multiple contexts? - * - * @private - * @param {WebGLRenderingContext} gl - The webgl rendering context - * @param {number} minWidth - The minimum width of the render target. - * @param {number} minHeight - The minimum height of the render target. - * @param {number} resolution - The resolution of the render target. - * @return {PIXI.RenderTarget} The new render target. - */ - getPotFilterTexture(minWidth, minHeight, resolution) - { - minWidth = bitTwiddle.nextPow2(minWidth); - minHeight = bitTwiddle.nextPow2(minHeight); - resolution = resolution || 1; - - const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); - - if (!this.texturePool[key]) - { - this.texturePool[key] = []; - } - - let renderTexture = this.texturePool[key].pop(); - - if (!renderTexture) - { - // temporary bypass cache.. - // internally - this will cause a texture to be bound.. - renderTexture = RenderTexture.create({ - width: minWidth, - height: minHeight, - resolution, - }); - } - - return renderTexture; - } - - /** - * Gets extra render texture to use inside current filter - * - * @param {number} resolution resolution of the renderTexture - * @returns {PIXI.RenderTexture} - */ - getFilterTexture(resolution) - { - const rt = this.activeState.renderTexture; - - const filterTexture = this.getPotFilterTexture(rt.width, rt.height, resolution || rt.baseTexture.resolution); - - filterTexture.filterFrame = rt.filterFrame; - - return filterTexture; - } - - /** - * Frees a render texture back into the pool. - * - * @param {PIXI.RenderTarget} renderTexture - The renderTarget to free - */ - returnFilterTexture(renderTexture) - { - renderTexture.filterFrame = null; - - const base = renderTexture.baseTexture; - - const minWidth = base.width; - const minHeight = base.height; - - const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); - - this.texturePool[key].push(renderTexture); - } - - /** - * Empties the texture pool. - * - */ - emptyPool() - { - for (const i in this.texturePool) - { - const textures = this.texturePool[i]; - - if (textures) - { - for (let j = 0; j < textures.length; j++) - { - textures[j].destroy(true); - } - } - } - - this.texturePool = {}; - } -} diff --git a/packages/core/src/renderers/systems/geometry/GLBuffer.js b/packages/core/src/renderers/systems/geometry/GLBuffer.js deleted file mode 100644 index 0477e8a..0000000 --- a/packages/core/src/renderers/systems/geometry/GLBuffer.js +++ /dev/null @@ -1,9 +0,0 @@ -export default class GLBuffer -{ - constructor(buffer) - { - this.buffer = buffer; - this.updateID = -1; - this.byteLength = -1; - } -} diff --git a/packages/core/src/renderers/systems/geometry/GeometrySystem.js b/packages/core/src/renderers/systems/geometry/GeometrySystem.js deleted file mode 100644 index 26c8e9e..0000000 --- a/packages/core/src/renderers/systems/geometry/GeometrySystem.js +++ /dev/null @@ -1,388 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; -import GLBuffer from './GLBuffer'; - -const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class GeometrySystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this._activeGeometry = null; - this._activeVao = null; - - this.hasVao = true; - this.hasInstance = true; - } - - /** - * Sets up the renderer context and necessary buffers. - * - * @private - */ - contextChange() - { - const gl = this.gl = this.renderer.gl; - - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - - // webgl2 - if (!gl.createVertexArray) - { - // webgl 1! - let nativeVaoExtension = this.renderer.context.extensions.vertexArrayObject; - - if (this.renderer.legacy) - { - nativeVaoExtension = null; - } - - if (nativeVaoExtension) - { - gl.createVertexArray = () => - nativeVaoExtension.createVertexArrayOES(); - - gl.bindVertexArray = (vao) => - nativeVaoExtension.bindVertexArrayOES(vao); - - gl.deleteVertexArray = (vao) => - nativeVaoExtension.deleteVertexArrayOES(vao); - } - else - { - this.hasVao = false; - gl.createVertexArray = () => - { - // empty - }; - - gl.bindVertexArray = () => - { - // empty - }; - - gl.deleteVertexArray = () => - { - // empty - }; - } - } - - if (!gl.vertexAttribDivisor) - { - const instanceExt = gl.getExtension('ANGLE_instanced_arrays'); - - if (instanceExt) - { - gl.vertexAttribDivisor = (a, b) => - instanceExt.vertexAttribDivisorANGLE(a, b); - - gl.drawElementsInstanced = (a, b, c, d, e) => - instanceExt.drawElementsInstancedANGLE(a, b, c, d, e); - - gl.drawArraysInstanced = (a, b, c, d) => - instanceExt.drawArraysInstancedANGLE(a, b, c, d); - } - else - { - this.hasInstance = false; - } - } - } - - /** - * Binds geometry so that is can be drawn. Creating a Vao if required - * @private - * @param {PIXI.Geometry} geometry instance of geometry to bind - */ - bind(geometry, shader) - { - shader = shader || this.renderer.shader.shader; - - const gl = this.gl; - - // not sure the best way to address this.. - // currently different shaders require different VAOs for the same geometry - // Still mulling over the best way to solve this one.. - // will likely need to modify the shader attribute locations at run time! - let vaos = geometry.glVertexArrayObjects[this.CONTEXT_UID]; - - if (!vaos) - { - geometry.glVertexArrayObjects[this.CONTEXT_UID] = vaos = {}; - } - - const vao = vaos[shader.program.id] || this.initGeometryVao(geometry, shader.program); - - this._activeGeometry = geometry; - - if (this._activeVao !== vao) - { - this._activeVao = vao; - - if (this.hasVao) - { - gl.bindVertexArray(vao); - } - else - { - this.activateVao(geometry, shader.program); - } - } - - // TODO - optimise later! - // don't need to loop through if nothing changed! - // maybe look to add an 'autoupdate' to geometry? - this.updateBuffers(); - } - - reset() - { - this.unbind(); - } - - updateBuffers() - { - const geometry = this._activeGeometry; - const gl = this.gl; - - for (let i = 0; i < geometry.buffers.length; i++) - { - const buffer = geometry.buffers[i]; - - const glBuffer = buffer._glBuffers[this.CONTEXT_UID]; - - if (buffer._updateID !== glBuffer.updateID) - { - glBuffer.updateID = buffer._updateID; - - // TODO can cache this on buffer! maybe added a getter / setter? - const type = buffer.index ? gl.ELEMENT_ARRAY_BUFFER : gl.ARRAY_BUFFER; - const drawType = buffer.static ? gl.STATIC_DRAW : gl.DYNAMIC_DRAW; - - gl.bindBuffer(type, glBuffer.buffer); - - if (glBuffer.byteLength >= buffer.data.byteLength) - { - // offset is always zero for now! - gl.bufferSubData(type, 0, buffer.data); - } - else - { - gl.bufferData(type, buffer.data, drawType); - } - } - } - } - - checkCompatability(geometry, program) - { - // geometry must have at least all the attributes that the shader requires. - const geometryAttributes = geometry.attributes; - const shaderAttributes = program.attributeData; - - for (const j in shaderAttributes) - { - if (!geometryAttributes[j]) - { - throw new Error(`shader and geometry incompatible, geometry missing the "${j}" attribute`); - } - } - } - - /** - * Creates a Vao with the same structure as the geometry and stores it on the geometry. - * @private - * @param {PIXI.Geometry} geometry instance of geometry to to generate Vao for - */ - initGeometryVao(geometry, program) - { - this.checkCompatability(geometry, program); - - const gl = this.gl; - const CONTEXT_UID = this.CONTEXT_UID; - const buffers = geometry.buffers; - const attributes = geometry.attributes; - - const tempStride = {}; - const tempStart = {}; - - for (const j in buffers) - { - tempStride[j] = 0; - tempStart[j] = 0; - } - - for (const j in attributes) - { - if (!attributes[j].size && program.attributeData[j]) - { - attributes[j].size = program.attributeData[j].size; - } - - tempStride[attributes[j].buffer] += attributes[j].size * byteSizeMap[attributes[j].type]; - } - - for (const j in attributes) - { - const attribute = attributes[j]; - const attribSize = attribute.size; - - if (attribute.stride === undefined) - { - if (tempStride[attribute.buffer] === attribSize * byteSizeMap[attribute.type]) - { - attribute.stride = 0; - } - else - { - attribute.stride = tempStride[attribute.buffer]; - } - } - - if (attribute.start === undefined) - { - attribute.start = tempStart[attribute.buffer]; - - tempStart[attribute.buffer] += attribSize * byteSizeMap[attribute.type]; - } - } - - // first update - and create the buffers! - // only create a gl buffer if it actually gets - for (let i = 0; i < buffers.length; i++) - { - const buffer = buffers[i]; - - if (!buffer._glBuffers[CONTEXT_UID]) - { - buffer._glBuffers[CONTEXT_UID] = new GLBuffer(gl.createBuffer()); - } - } - - // TODO - maybe make this a data object? - // lets wait to see if we need to first! - const vao = gl.createVertexArray(); - - gl.bindVertexArray(vao); - - this.activateVao(geometry, program); - - gl.bindVertexArray(null); - - // add it to the cache! - geometry.glVertexArrayObjects[this.CONTEXT_UID][program.id] = vao; - - return vao; - } - - activateVao(geometry, program) - { - const gl = this.gl; - const CONTEXT_UID = this.CONTEXT_UID; - const buffers = geometry.buffers; - const attributes = geometry.attributes; - - if (geometry.indexBuffer) - { - // first update the index buffer if we have one.. - gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, geometry.indexBuffer._glBuffers[CONTEXT_UID].buffer); - } - - let lastBuffer = null; - - // add a new one! - for (const j in attributes) - { - const attribute = attributes[j]; - const buffer = buffers[attribute.buffer]; - const glBuffer = buffer._glBuffers[CONTEXT_UID]; - - if (program.attributeData[j]) - { - if (lastBuffer !== glBuffer) - { - gl.bindBuffer(gl.ARRAY_BUFFER, glBuffer.buffer); - - lastBuffer = glBuffer; - } - - const location = program.attributeData[j].location; - - // TODO introduce state again - // we can optimise this for older devices that have no VAOs - gl.enableVertexAttribArray(location); - - gl.vertexAttribPointer(location, - attribute.size, - attribute.type || gl.FLOAT, - attribute.normalized, - attribute.stride, - attribute.start); - - if (attribute.instance) - { - // TODO calculate instance count based of this... - if (this.hasInstance) - { - gl.vertexAttribDivisor(location, 1); - } - else - { - throw new Error('geometry error, GPU Instancing is not supported on this device'); - } - } - } - } - } - - draw(type, size, start, instanceCount) - { - const gl = this.gl; - const geometry = this._activeGeometry; - - // TODO.. this should not change so maybe cache the function? - - if (geometry.indexBuffer) - { - if (geometry.instanced) - { - /* eslint-disable max-len */ - gl.drawElementsInstanced(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2, instanceCount || 1); - /* eslint-enable max-len */ - } - else - { - gl.drawElements(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2); - } - } - else - if (geometry.instanced) - { - // TODO need a better way to calculate size.. - gl.drawArraysInstanced(type, start, size || geometry.getSize(), instanceCount || 1); - } - else - { - gl.drawArrays(type, start, size || geometry.getSize()); - } - - return this; - } - - unbind() - { - this.gl.bindVertexArray(null); - this._activeVao = null; - this._activeGeometry = null; - } -} diff --git a/packages/core/src/renderers/systems/geometry/setVertexAttribArrays.js b/packages/core/src/renderers/systems/geometry/setVertexAttribArrays.js deleted file mode 100644 index 23b6959..0000000 --- a/packages/core/src/renderers/systems/geometry/setVertexAttribArrays.js +++ /dev/null @@ -1,54 +0,0 @@ -// var GL_MAP = {}; - -/** - * @param gl {WebGLRenderingContext} The current WebGL context - * @param attribs {*} - * @param state {*} - */ -export default function setVertexAttribArrays(gl, attribs, state) -{ - let i; - - if (state) - { - const tempAttribState = state.tempAttribState; - const attribState = state.attribState; - - for (i = 0; i < tempAttribState.length; i++) - { - tempAttribState[i] = false; - } - - // set the new attribs - for (i = 0; i < attribs.length; i++) - { - tempAttribState[attribs[i].attribute.location] = true; - } - - for (i = 0; i < attribState.length; i++) - { - if (attribState[i] !== tempAttribState[i]) - { - attribState[i] = tempAttribState[i]; - - if (state.attribState[i]) - { - gl.enableVertexAttribArray(i); - } - else - { - gl.disableVertexAttribArray(i); - } - } - } - } - else - { - for (i = 0; i < attribs.length; i++) - { - const attrib = attribs[i]; - - gl.enableVertexAttribArray(attrib.attribute.location); - } - } -} diff --git a/packages/core/src/renderers/systems/index.js b/packages/core/src/renderers/systems/index.js deleted file mode 100644 index c843f03..0000000 --- a/packages/core/src/renderers/systems/index.js +++ /dev/null @@ -1,6 +0,0 @@ -/** - * Systems are individual components to the Renderer pipeline. - * @namespace PIXI.systems - */ -export { default as WebGLSystem } from './WebGLSystem'; -export { default as FilterSystem } from './filter/FilterSystem'; diff --git a/packages/core/src/renderers/systems/shader/GLProgram.js b/packages/core/src/renderers/systems/shader/GLProgram.js deleted file mode 100644 index 92980ca..0000000 --- a/packages/core/src/renderers/systems/shader/GLProgram.js +++ /dev/null @@ -1,43 +0,0 @@ -/** - * Helper class to create a webGL Program - * - * @class - * @private - */ -export default class GLProgram -{ - constructor(program, uniformData) - { - /** - * The shader program - * - * @member {WebGLProgram} - */ - this.program = program; - - /** - * holds the uniform data which contains uniform locations - * and current uniform values used for caching and preventing undeed GPU commands - * @type {Object} - */ - this.uniformData = uniformData; - - /** - * uniformGroups holds the various upload functions for the shader. Each uniform group - * and program have a unique upload function generated. - * @type {Object} - */ - this.uniformGroups = {}; - } - - /** - * Destroys this program - * TODO - */ - destroy() - { - this.uniformData = null; - this.uniformGroups = null; - this.program = null; - } -} diff --git a/packages/core/src/renderers/systems/shader/ShaderSystem.js b/packages/core/src/renderers/systems/shader/ShaderSystem.js deleted file mode 100644 index 6cf57b6..0000000 --- a/packages/core/src/renderers/systems/shader/ShaderSystem.js +++ /dev/null @@ -1,170 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; -import GLProgram from './GLProgram'; -import generateUniformsSync from '../../../shader/generateUniformsSync'; -import defaultValue from '../../../shader/defaultValue'; -import compileProgram from './shader/compileProgram'; - -let UID = 0; - -/** - * Helper class to create a webGL Texture - * - * @class - * @memberof PIXI - */ -export default class ShaderSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - A reference to the current renderer - */ - constructor(renderer) - { - super(renderer); - - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = null; - - this.shader = null; - this.program = null; - - this.id = UID++; - } - - contextChange(gl) - { - this.gl = gl; - } - - /** - * Changes the current shader to the one given in parameter - * - * @param {PIXI.Shader} shader - the new shader - * @param {boolean} dontSync - false if the shader should automatically sync its uniforms. - * @returns {PIXI.glCore.glProgram} the glProgram that belongs to the shader. - */ - bind(shader, dontSync) - { - shader.uniforms.globals = this.renderer.globalUniforms; - - const program = shader.program; - const glProgram = program.glPrograms[this.renderer.CONTEXT_UID] || this.generateShader(shader); - - this.shader = shader; - - // TODO - some current pixi plugins bypass this.. so it not safe to use yet.. - if (this.program !== program) - { - this.program = program; - this.gl.useProgram(glProgram.program); - } - - if (!dontSync) - { - this.syncUniformGroup(shader.uniformGroup); - } - - return glProgram; - } - - /** - * Uploads the uniforms values to the currently bound shader. - * - * @param {object} uniforms - the uniforms valiues that be applied to the current shader - */ - setUniforms(uniforms) - { - const shader = this.shader.program; - const glProgram = shader.glPrograms[this.renderer.CONTEXT_UID]; - - shader.syncUniforms(glProgram.uniformData, uniforms, this.renderer); - } - - syncUniformGroup(group) - { - const glProgram = this.getglProgram(); - - if (!group.static || group.dirtyId !== glProgram.uniformGroups[group.id]) - { - glProgram.uniformGroups[group.id] = group.dirtyId; - const syncFunc = group.syncUniforms[this.shader.program.id] || this.createSyncGroups(group); - - syncFunc(glProgram.uniformData, group.uniforms, this.renderer); - } - } - - createSyncGroups(group) - { - group.syncUniforms[this.shader.program.id] = generateUniformsSync(group, this.shader.program.uniformData); - - return group.syncUniforms[this.shader.program.id]; - } - - /** - * Returns the underlying GLShade rof the currently bound shader. - * This can be handy for when you to have a little more control over the setting of your uniforms. - * - * @return {PIXI.glCore.Shader} the glProgram for the currently bound Shader for this context - */ - getglProgram() - { - if (this.shader) - { - return this.shader.program.glPrograms[this.renderer.CONTEXT_UID]; - } - - return null; - } - - /** - * Generates a glProgram verion of the Shader provided. - * - * @private - * @param {PIXI.Shader} shader the shader that the glProgram will be based on. - * @return {PIXI.glCore.glProgram} A shiney new glProgram - */ - generateShader(shader) - { - const gl = this.gl; - - const program = shader.program; - - const attribMap = {}; - - for (const i in program.attributeData) - { - attribMap[i] = program.attributeData[i].location; - } - - const shaderProgram = compileProgram(gl, program.vertexSrc, program.fragmentSrc, attribMap); - const uniformData = {}; - - for (const i in program.uniformData) - { - const data = program.uniformData[i]; - - uniformData[i] = { - location: gl.getUniformLocation(shaderProgram, i), - value: defaultValue(data.type, data.size), - }; - } - - const glProgram = new GLProgram(shaderProgram, uniformData); - - program.glPrograms[this.renderer.CONTEXT_UID] = glProgram; - - return glProgram; - } - - /** - * Destroys this System and removes all its textures - */ - destroy() - { - // TODO implement destroy method for ShaderSystem - this.destroyed = true; - } -} diff --git a/packages/core/src/renderers/systems/shader/shader/compileProgram.js b/packages/core/src/renderers/systems/shader/shader/compileProgram.js deleted file mode 100644 index 8c6ccb9..0000000 --- a/packages/core/src/renderers/systems/shader/shader/compileProgram.js +++ /dev/null @@ -1,78 +0,0 @@ -/** - * @method compileProgram - * @memberof PIXI.glCore.shader - * @param gl {WebGLRenderingContext} The current WebGL context {WebGLProgram} - * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. - * @param fragmentSrc {string|string[]} The fragment shader source as an array of strings. - * @param attributeLocations {Object} An attribute location map that lets you manually set the attribute locations - * @return {WebGLProgram} the shader program - */ -export default function compileProgram(gl, vertexSrc, fragmentSrc, attributeLocations) -{ - const glVertShader = compileShader(gl, gl.VERTEX_SHADER, vertexSrc); - const glFragShader = compileShader(gl, gl.FRAGMENT_SHADER, fragmentSrc); - - let program = gl.createProgram(); - - gl.attachShader(program, glVertShader); - gl.attachShader(program, glFragShader); - - // optionally, set the attributes manually for the program rather than letting WebGL decide.. - if (attributeLocations) - { - for (const i in attributeLocations) - { - gl.bindAttribLocation(program, attributeLocations[i], i); - } - } - - gl.linkProgram(program); - - // if linking fails, then log and cleanup - if (!gl.getProgramParameter(program, gl.LINK_STATUS)) - { - console.error('Pixi.js Error: Could not initialize shader.'); - console.error('gl.VALIDATE_STATUS', gl.getProgramParameter(program, gl.VALIDATE_STATUS)); - console.error('gl.getError()', gl.getError()); - - // if there is a program info log, log it - if (gl.getProgramInfoLog(program) !== '') - { - console.warn('Pixi.js Warning: gl.getProgramInfoLog()', gl.getProgramInfoLog(program)); - } - - gl.deleteProgram(program); - program = null; - } - - // clean up some shaders - gl.deleteShader(glVertShader); - gl.deleteShader(glFragShader); - - return program; -} - -/** - * @private - * @param gl {WebGLRenderingContext} The current WebGL context {WebGLProgram} - * @param type {Number} the type, can be either VERTEX_SHADER or FRAGMENT_SHADER - * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. - * @return {WebGLShader} the shader - */ -function compileShader(gl, type, src) -{ - const shader = gl.createShader(type); - - gl.shaderSource(shader, src); - gl.compileShader(shader); - - if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) - { - console.warn(src); - console.error(gl.getShaderInfoLog(shader)); - - return null; - } - - return shader; -} diff --git a/packages/core/src/renderers/systems/shader/shader/defaultValue.js b/packages/core/src/renderers/systems/shader/shader/defaultValue.js deleted file mode 100644 index 97290bd..0000000 --- a/packages/core/src/renderers/systems/shader/shader/defaultValue.js +++ /dev/null @@ -1,79 +0,0 @@ -/** - * @method defaultValue - * @memberof PIXI.glCore.shader - * @param type {String} Type of value - * @param size {Number} - */ -export default function defaultValue(type, size) -{ - switch (type) - { - case 'float': - return 0; - - case 'vec2': - return new Float32Array(2 * size); - - case 'vec3': - return new Float32Array(3 * size); - - case 'vec4': - return new Float32Array(4 * size); - - case 'int': - case 'sampler2D': - case 'sampler2DArray': - return 0; - - case 'ivec2': - return new Int32Array(2 * size); - - case 'ivec3': - return new Int32Array(3 * size); - - case 'ivec4': - return new Int32Array(4 * size); - - case 'bool': - return false; - - case 'bvec2': - - return booleanArray(2 * size); - - case 'bvec3': - return booleanArray(3 * size); - - case 'bvec4': - return booleanArray(4 * size); - - case 'mat2': - return new Float32Array([1, 0, - 0, 1]); - - case 'mat3': - return new Float32Array([1, 0, 0, - 0, 1, 0, - 0, 0, 1]); - - case 'mat4': - return new Float32Array([1, 0, 0, 0, - 0, 1, 0, 0, - 0, 0, 1, 0, - 0, 0, 0, 1]); - } - - return null; -} - -function booleanArray(size) -{ - const array = new Array(size); - - for (let i = 0; i < array.length; i++) - { - array[i] = false; - } - - return array; -} diff --git a/packages/core/src/AbstractRenderer.js b/packages/core/src/AbstractRenderer.js new file mode 100644 index 0000000..64b2eb1 --- /dev/null +++ b/packages/core/src/AbstractRenderer.js @@ -0,0 +1,353 @@ +import { hex2string, hex2rgb } from '@pixi/utils'; +import { Matrix, Rectangle } from '@pixi/math'; +import { RENDERER_TYPE } from '@pixi/constants'; +import { settings } from '@pixi/settings'; +import { Container } from '@pixi/display'; +import RenderTexture from './renderTexture/RenderTexture'; +import EventEmitter from 'eventemitter3'; + +const tempMatrix = new Matrix(); + +/** + * 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 EventEmitter + * @memberof PIXI + */ +export default class AbstractRenderer extends EventEmitter +{ + // eslint-disable-next-line valid-jsdoc + /** + * @param {string} system - The name of the system this renderer is for. + * @param {object} [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.transparent=false] - If the render view is transparent, default false + * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false + * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. The + * resolution of the renderer retina would be 2. + * @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 {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when rendering, + * stopping pixel interpolation. + */ + constructor(system, options, arg2, arg3) + { + super(); + + // Support for constructor(system, screenWidth, screenHeight, options) + if (typeof options === 'number') + { + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + }, arg3); + } + + // 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 = RENDERER_TYPE.UNKNOWN; + + /** + * Measurements of the screen. (0, 0, screenWidth, screenHeight) + * + * Its safe to use as filterArea or hitArea for 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 1 + */ + this.resolution = options.resolution || settings.RESOLUTION; + + /** + * Whether the render view is transparent + * + * @member {boolean} + */ + this.transparent = options.transparent; + + /** + * Whether css dimensions of canvas view should be resized to screen dimensions automatically + * + * @member {boolean} + */ + this.autoResize = options.autoResize || false; + + /** + * Tracks the blend modes useful for this renderer. + * + * @member {object} + */ + this.blendModes = null; + + /** + * 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; + + /** + * If true PixiJS will Math.floor() x/y values when rendering, stopping pixel interpolation. + * Handy for crisp pixel art and speed on legacy devices. + * + * @member {boolean} + */ + this.roundPixels = options.roundPixels; + + /** + * The background color as a number. + * + * @member {number} + * @private + */ + this._backgroundColor = 0x000000; + + /** + * The background color as an [R, G, B] array. + * + * @member {number[]} + * @private + */ + this._backgroundColorRgba = [0, 0, 0, 0]; + + /** + * The background color as a string. + * + * @member {string} + * @private + */ + this._backgroundColorString = '#000000'; + + this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter + + /** + * This temporary display object used as the parent of the currently being rendered item + * + * @member {PIXI.DisplayObject} + * @private + */ + this._tempDisplayObjectParent = new Container(); + + /** + * The last root object that the renderer tried to render. + * + * @member {PIXI.DisplayObject} + * @private + */ + this._lastObjectRendered = this._tempDisplayObjectParent; + + /** + * Collection of plugins. + * @readonly + * @member {object} + */ + this.plugins = {}; + } + + /** + * Initialize the plugins. + * + * @protected + * @param {object} staticMap - The dictionary of staticly saved plugins. + */ + initPlugins(staticMap) + { + 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() + { + return this.view.width; + } + + /** + * Same as view.height, actual number of pixels in the canvas by vertical + * + * @member {number} + * @readonly + * @default 600 + */ + get height() + { + return this.view.height; + } + + /** + * Resizes the screen and canvas to the specified width and height + * Canvas dimensions are multiplied by resolution + * + * @param {number} screenWidth - the new width of the screen + * @param {number} screenHeight - the new height of the screen + */ + resize(screenWidth, screenHeight) + { + this.screen.width = screenWidth; + this.screen.height = screenHeight; + + this.view.width = screenWidth * this.resolution; + this.view.height = screenHeight * this.resolution; + + if (this.autoResize) + { + this.view.style.width = `${screenWidth}px`; + this.view.style.height = `${screenHeight}px`; + } + } + + /** + * 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 {PIXI.DisplayObject} displayObject - The displayObject the object will be generated from + * @param {number} scaleMode - Should be one of the scaleMode consts + * @param {number} resolution - The resolution / device pixel ratio of the texture being generated + * @param {PIXI.Rectangle} [region] - The region of the displayObject, that shall be rendered, + * if no region is specified, defaults to the local bounds of the displayObject. + * @return {PIXI.Texture} a texture of the graphics object + */ + generateTexture(displayObject, scaleMode, resolution, region) + { + region = region || displayObject.getLocalBounds(); + + // 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(region.width | 0, region.height | 0, scaleMode, resolution); + + tempMatrix.tx = -region.x; + tempMatrix.ty = -region.y; + + this.render(displayObject, renderTexture, false, tempMatrix, true); + + return renderTexture; + } + + /** + * Removes everything from the renderer and optionally removes the Canvas DOM element. + * + * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. + */ + destroy(removeView) + { + 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); + } + + this.plugins = null; + + this.type = RENDERER_TYPE.UNKNOWN; + + this.view = null; + + this.screen = null; + + this.resolution = 0; + + this.transparent = false; + + this.autoResize = false; + + this.blendModes = null; + + this.options = null; + + this.preserveDrawingBuffer = false; + this.clearBeforeRender = false; + + this.roundPixels = false; + + this._backgroundColor = 0; + this._backgroundColorRgba = null; + this._backgroundColorString = null; + + this._tempDisplayObjectParent = null; + this._lastObjectRendered = null; + } + + /** + * The background color to fill if not transparent + * + * @member {number} + */ + get backgroundColor() + { + return this._backgroundColor; + } + + set backgroundColor(value) // eslint-disable-line require-jsdoc + { + this._backgroundColor = value; + this._backgroundColorString = hex2string(value); + hex2rgb(value, this._backgroundColorRgba); + } +} diff --git a/packages/core/src/Renderer.js b/packages/core/src/Renderer.js new file mode 100644 index 0000000..2227e78 --- /dev/null +++ b/packages/core/src/Renderer.js @@ -0,0 +1,338 @@ +import AbstractRenderer from './AbstractRenderer'; +import { sayHello } from '@pixi/utils'; +import MaskSystem from './mask/MaskSystem'; +import StencilSystem from './mask/StencilSystem'; +import FilterSystem from './filters/FilterSystem'; +import FramebufferSystem from './framebuffer/FramebufferSystem'; +import RenderTextureSystem from './renderTexture/RenderTextureSystem'; +import TextureSystem from './textures/TextureSystem'; +import ProjectionSystem from './projection/ProjectionSystem'; +import StateSystem from './state/StateSystem'; +import GeometrySystem from './geometry/GeometrySystem'; +import ShaderSystem from './shader/ShaderSystem'; +import ContextSystem from './context/ContextSystem'; +import BatchSystem from './batch/BatchSystem'; +import TextureGCSystem from './textures/TextureGCSystem'; +import { RENDERER_TYPE } from '@pixi/constants'; +import UniformGroup from './shader/UniformGroup'; +import { Matrix } from '@pixi/math'; +import Runner from 'mini-runner'; + +/** + * 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 Render works by automatically managing webGLBatchs. + * 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 :) + * + * @class + * @memberof PIXI + * @extends PIXI.AbstractRenderer + */ +export default class Renderer extends AbstractRenderer +{ + // eslint-disable-next-line valid-jsdoc + /** + * + * @param {object} [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.transparent=false] - If the render view is transparent, default false + * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false + * @param {boolean} [options.antialias=false] - sets antialias. If not available natively then FXAA + * antialiasing is used + * @param {boolean} [options.forceFXAA=false] - forces FXAA antialiasing to be used over native. + * FXAA is faster, but may not always look as great + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. + * The resolution of the renderer retina would be 2. + * @param {boolean} [options.clearBeforeRender=true] - This sets if the renderer will clear + * the canvas or not before the new render pass. If you wish to set this to false, you *must* set + * preserveDrawingBuffer to `true`. + * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, + * enable this if you need to call toDataUrl on the webgl context. + * @param {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when + * rendering, stopping pixel interpolation. + * @param {number} [options.backgroundColor=0x000000] - The background color of the rendered area + * (shown if not transparent). + * @param {boolean} [options.legacy=false] - If true PixiJS will aim to ensure compatibility + * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. + * @param {string} [options.powerPreference] - Parameter passed to webgl context, set to "high-performance" + * for devices with dual graphics card + */ + constructor(options, arg2, arg3) + { + super('WebGL', options, arg2, arg3); + + /** + * The type of this renderer as a standardised const + * + * @member {number} + * @see PIXI.RENDERER_TYPE + */ + this.type = RENDERER_TYPE.WEBGL; + + // this will be set by the contextSystem (this.context) + this.gl = null; + this.CONTEXT_UID = 0; + this.legacy = !!options.legacy; + + // TODO legacy! + + // runners! + this.runners = { + destroy: new Runner('destroy'), + contextChange: new Runner('contextChange', 1), + reset: new Runner('reset'), + update: new Runner('update'), + postrender: new Runner('postrender'), + prerender: new Runner('prerender'), + resize: new Runner('resize', 2), + }; + + 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(GeometrySystem, 'geometry') + .addSystem(FramebufferSystem, 'framebuffer') + .addSystem(StencilSystem, 'stencil') + .addSystem(ProjectionSystem, 'projection') + .addSystem(TextureGCSystem, 'textureGC') + .addSystem(FilterSystem, 'filter') + .addSystem(RenderTextureSystem, 'renderTexture') + .addSystem(BatchSystem, 'batch'); + + this.initPlugins(Renderer.__plugins); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ + if (options.context) + { + this.context.initFromContext(options.context); + } + else + { + this.context.initFromOptions({ + alpha: this.transparent, + antialias: options.antialias, + premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', + stencil: true, + 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); + } + + /** + * Add a new system to the renderer. + * @param {class} ClassRef - Class reference + * @param {string} [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. + * @return {PIXI.Renderer} Return instance of renderer + */ + addSystem(ClassRef, name) + { + if (!name) + { + name = ClassRef.name; + } + + // TODO - read name from class.name.. + + /* + if(name.includes('System')) + { + name = name.replace('System', ''); + name = name.charAt(0).toLowerCase() + name.slice(1); + } + */ + + const system = new ClassRef(this); + + if (this[name]) + { + throw new Error(`Whoops! ${name} is already a manger`); + } + + this[name] = system; + + for (const i in this.runners) + { + this.runners[i].add(system); + } + + return this; + + /** + * 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. + */ + } + + /** + * Renders the object to its webGL view + * + * @param {PIXI.DisplayObject} displayObject - the object to be rendered + * @param {PIXI.RenderTexture} renderTexture - The render texture to render to. + * @param {boolean} [clear] - Should the canvas be cleared before the new render + * @param {PIXI.Transform} [transform] - A transform to apply to the render texture before rendering. + * @param {boolean} [skipUpdateTransform] - Should we skip the update transform pass? + */ + render(displayObject, renderTexture, clear, transform, skipUpdateTransform) + { + // can be handy to know! + this.renderingToScreen = !renderTexture; + + this.runners.prerender.run(); + this.emit('prerender'); + + // 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.parent; + + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = 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.run(); + + this.emit('postrender'); + } + + /** + * Resizes the webGL view to the specified width and height. + * + * @param {number} screenWidth - the new width of the screen + * @param {number} screenHeight - the new height of the screen + */ + resize(screenWidth, screenHeight) + { + AbstractRenderer.prototype.resize.call(this, screenWidth, screenHeight); + + this.runners.resize.run(screenWidth, screenHeight); + } + + /** + * Resets the WebGL state so you can render things however you fancy! + * + * @return {PIXI.Renderer} Returns itself. + */ + reset() + { + this.runners.reset.run(); + + return this; + } + + clear() + { + this.framebuffer.bind(); + this.framebuffer.clear(); + } + + /** + * Removes everything from the renderer (event listeners, spritebatch, etc...) + * + * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. + * See: https://github.com/pixijs/pixi.js/issues/2233 + */ + destroy(removeView) + { + this.runners.destroy.run(); + + // call base destroy + super.destroy(removeView); + + // TODO nullify all the managers.. + this.gl = null; + } + + /** + * 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 + * builds and excluding plugins. + * @name PIXI.Renderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. + */ + + /** + * Adds a plugin to the renderer. + * + * @method + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + static registerPlugin(pluginName, ctor) + { + Renderer.__plugins = Renderer.__plugins || {}; + Renderer.__plugins[pluginName] = ctor; + } +} diff --git a/packages/core/src/System.js b/packages/core/src/System.js new file mode 100644 index 0000000..05a10c8 --- /dev/null +++ b/packages/core/src/System.js @@ -0,0 +1,42 @@ +/** + * System is a base class used for extending systems used by the {@link PIXI.Renderer} + * @see PIXI.Renderer#addSystem + * @class + * @memberof PIXI + */ +export default class System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + /** + * The renderer this manager works for. + * + * @member {PIXI.Renderer} + */ + this.renderer = renderer; + + this.renderer.runners.contextChange.add(this); + } + + /** + * Generic method called when there is a WebGL context change. + * + */ + contextChange() + { + // do some codes init! + } + + /** + * Generic destroy methods to be overridden by the subclass + * + */ + destroy() + { + this.renderer.runners.contextChange.remove(this); + this.renderer = null; + } +} diff --git a/packages/core/src/batch/BatchSystem.js b/packages/core/src/batch/BatchSystem.js new file mode 100644 index 0000000..b04c249 --- /dev/null +++ b/packages/core/src/batch/BatchSystem.js @@ -0,0 +1,68 @@ +import System from '../System'; +import ObjectRenderer from './ObjectRenderer'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class BatchSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + super(renderer); + + /** + * An empty renderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.emptyRenderer = new ObjectRenderer(renderer); + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.currentRenderer = this.emptyRenderer; + } + + /** + * Changes the current renderer to the one given in parameter + * + * @param {PIXI.ObjectRenderer} objectRenderer - The object renderer to use. + */ + setObjectRenderer(objectRenderer) + { + if (this.currentRenderer === objectRenderer) + { + return; + } + + this.currentRenderer.stop(); + this.currentRenderer = objectRenderer; + + this.renderer.state.setState(objectRenderer.state); + + this.currentRenderer.start(); + } + + /** + * This should be called if you wish to do some custom rendering + * It will basically render anything that may be batched up such as sprites + * + */ + flush() + { + this.setObjectRenderer(this.emptyRenderer); + } + + reset() + { + this.setObjectRenderer(this.emptyRenderer); + } +} diff --git a/packages/core/src/batch/ObjectRenderer.js b/packages/core/src/batch/ObjectRenderer.js new file mode 100644 index 0000000..0c851a9 --- /dev/null +++ b/packages/core/src/batch/ObjectRenderer.js @@ -0,0 +1,48 @@ +import System from '../System'; + +/** + * Base for a common object renderer that can be used as a system renderer plugin. + * + * @class + * @extends PIXI.System + * @memberof PIXI + */ +export default class ObjectRenderer extends System +{ + /** + * Starts the renderer and sets the shader + * + */ + start() + { + // set the shader.. + } + + /** + * Stops the renderer + * + */ + stop() + { + this.flush(); + } + + /** + * Stub method for rendering content and emptying the current batch. + * + */ + flush() + { + // flush! + } + + /** + * Renders an object + * + * @param {PIXI.DisplayObject} object - The object to render. + */ + render(object) // eslint-disable-line no-unused-vars + { + // render the object + } +} diff --git a/packages/core/src/context/ContextSystem.js b/packages/core/src/context/ContextSystem.js new file mode 100644 index 0000000..301aab4 --- /dev/null +++ b/packages/core/src/context/ContextSystem.js @@ -0,0 +1,186 @@ +import System from '../System'; +import { settings } from '@pixi/settings'; + +let CONTEXT_UID = 0; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class ContextSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.webGLVersion = 1; + + this.handleContextLost = this.handleContextLost.bind(this); + this.handleContextRestored = this.handleContextRestored.bind(this); + + this.extensions = {}; + + renderer.view.addEventListener('webglcontextlost', this.handleContextLost, false); + renderer.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + } + + get isLost() + { + return (!this.gl || this.gl.isContextLost()); + } + + contextChange(gl) + { + this.gl = gl; + + // restore a context if it was previously lost + if (gl.isContextLost() && gl.getExtension('WEBGL_lose_context')) + { + gl.getExtension('WEBGL_lose_context').restoreContext(); + } + } + + initFromContext(gl) + { + this.gl = gl; + this.validateContext(gl); + this.renderer.gl = gl; + this.renderer.CONTEXT_UID = CONTEXT_UID++; + this.renderer.runners.contextChange.run(gl); + } + + initFromOptions(options) + { + const gl = this.createContext(this.renderer.view, options); + + this.initFromContext(gl); + } + + /** + * Helper class to create a webGL Context + * + * @class + * @memberof PIXI.glCore + * @param canvas {HTMLCanvasElement} the canvas element that we will get the context from + * @param options {Object} An options object that gets passed in to the canvas element containing the context attributes, + * see https://developer.mozilla.org/en/docs/Web/API/HTMLCanvasElement/getContext for the options available + * @return {WebGLRenderingContext} the WebGL context + */ + createContext(canvas, options) + { + let gl; + + if (settings.PREFER_WEBGL_2) + { + gl = canvas.getContext('webgl2', options); + } + + if (gl) + { + this.webGLVersion = 2; + } + else + { + this.webGLVersion = 1; + + gl = canvas.getContext('webgl', options) + || canvas.getContext('experimental-webgl', options); + + if (!gl) + { + // fail, not able to get a context + throw new Error('This browser does not support webGL. Try using the canvas renderer'); + } + } + + this.gl = gl; + + this.getExtensions(); + + return gl; + } + + getExtensions() + { + // time to set up default etensions that pixi uses.. + const gl = this.gl; + const extensions = this.extensions; + + if (this.webGLVersion === 1) + { + extensions.drawBuffers = gl.getExtension('WEBGL_draw_buffers'); + extensions.depthTexture = gl.getExtension('WEBKIT_WEBGL_depth_texture'); + extensions.floatTexture = gl.getExtension('OES_texture_float'); + extensions.loseContext = gl.getExtension('WEBGL_lose_context'); + + extensions.vertexArrayObject = gl.getExtension('OES_vertex_array_object') + || gl.getExtension('MOZ_OES_vertex_array_object') + || gl.getExtension('WEBKIT_OES_vertex_array_object'); + } + + // we don't use any specific WebGL 2 ones yet! + } + + /** + * Handles a lost webgl context + * + * @private + * @param {WebGLContextEvent} event - The context lost event. + */ + handleContextLost(event) + { + event.preventDefault(); + } + + /** + * Handles a restored webgl context + * + * @private + */ + handleContextRestored() + { + this.renderer.runners.contextChange.run(this.gl); + } + + destroy() + { + const view = this.renderer.view; + + // remove listeners + view.removeEventListener('webglcontextlost', this.handleContextLost); + view.removeEventListener('webglcontextrestored', this.handleContextRestored); + + this.gl.useProgram(null); + + if (this.extensions.loseContext) + { + this.extensions.loseContext.loseContext(); + } + } + + postrender() + { + this.gl.flush(); + } + + validateContext(gl) + { + const attributes = gl.getContextAttributes(); + + // this is going to be fairly simple for now.. but at least we have room to grow! + if (!attributes.stencil) + { + /* eslint-disable max-len */ + + /* eslint-disable no-console */ + console.warn('Provided WebGL context does not have a stencil buffer, masks may not render correctly'); + /* eslint-enable no-console */ + + /* eslint-enable max-len */ + } + } +} diff --git a/packages/core/src/filters/Filter.js b/packages/core/src/filters/Filter.js new file mode 100644 index 0000000..9d8cad8 --- /dev/null +++ b/packages/core/src/filters/Filter.js @@ -0,0 +1,140 @@ +import Shader from '../shader/Shader'; +import Program from '../shader/Program'; +import State from '../state/State'; +import { settings } from '@pixi/settings'; +// import extractUniformsFromSrc from './extractUniformsFromSrc'; +import defaultVertex from './defaultFilter.vert'; +import defaultFragment from './defaultFilter.frag'; + +// let math = require('../../math'); +/** + * @class + * @memberof PIXI + * @extends PIXI.Shader + */ +export default class Filter extends Shader +{ + /** + * @param {string} [vertexSrc] - The source of the vertex shader. + * @param {string} [fragmentSrc] - The source of the fragment shader. + * @param {object} [uniforms] - Custom uniforms to use to augment the built-in ones. + */ + constructor(vertexSrc, fragmentSrc, uniforms) + { + const program = Program.from(vertexSrc, fragmentSrc); + + super(program, uniforms); + + /** + * The padding of the filter. Some filters require extra space to breath such as a blur. + * Increasing this will add extra width and height to the bounds of the object that the + * filter is applied to. + * + * @member {number} + */ + this.padding = 0; + + /** + * The resolution of the filter. Setting this to be lower will lower the quality but + * increase the performance of the filter. + * + * @member {number} + */ + this.resolution = settings.RESOLUTION; + + /** + * If enabled is true the filter is applied, if false it will not. + * + * @member {boolean} + */ + this.enabled = true; + + /** + * If enabled, PixiJS will fit the filter area into boundaries for better performance. + * Switch it off if it does not work for specific shader. + * + * @member {boolean} + */ + this.autoFit = true; + + /** + * Legacy filters use position and uvs from attributes + * @member {boolean} + * @readonly + */ + this.legacy = !!this.program.attributeData.aTextureCoord; + + /** + * the webGL state the filter requires to render + * @member {PIXI.State} + */ + this.state = new State(); + } + + /** + * Applies the filter + * + * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + * @param {boolean} clear - Should the output be cleared before rendering to it + * @param {object} [currentState] - It's current state of filter. + * There are some useful properties in the currentState : + * target, filters, sourceFrame, destinationFrame, renderTarget, resolution + */ + apply(filterManager, input, output, clear, currentState, derp) // eslint-disable-line no-unused-vars + { + // do as you please! + + filterManager.applyFilter(this, input, output, clear, currentState, derp); + + // or just do a regular render.. + } + + /** + * Sets the blendmode of the filter + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL + */ + get blendMode() + { + return this.state.blendMode; + } + + set blendMode(value) // eslint-disable-line require-jsdoc + { + this.state.blendMode = value; + } + + /** + * The default vertex shader source + * + * @static + * @constant + */ + static get defaultVertexSrc() + { + return defaultVertex; + } + + /** + * The default fragment shader source + * + * @static + * @constant + */ + static get defaultFragmentSrc() + { + return defaultFragment; + } +} + +/** + * Used for caching shader IDs + * + * @static + * @private + */ +Filter.SOURCE_KEY_MAP = {}; + diff --git a/packages/core/src/filters/FilterSystem.js b/packages/core/src/filters/FilterSystem.js new file mode 100644 index 0000000..3174318 --- /dev/null +++ b/packages/core/src/filters/FilterSystem.js @@ -0,0 +1,431 @@ +import System from '../System'; + +import RenderTexture from '../renderTexture/RenderTexture'; +import Quad from '../utils/Quad'; +import QuadUv from '../utils/QuadUv'; +import { Rectangle } from '@pixi/math'; +import * as filterTransforms from './filterTransforms'; +import bitTwiddle from 'bit-twiddle'; +import UniformGroup from '../shader/UniformGroup'; +import { DRAW_MODES } from '@pixi/constants'; + +// +/** + * @ignore + * @class + */ +class FilterState +{ + /** + * + */ + constructor() + { + this.renderTexture = null; + this.sourceFrame = new Rectangle(); + this.destinationFrame = new Rectangle(); + this.filters = []; + this.target = null; + this.legacy = false; + this.resolution = 1; + } +} + +/** + * @class + * @memberof PIXI.systems + * @extends PIXI.System + */ +export default class FilterSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + /** + * stores a bunch of PO2 textures used for filtering + * @type {Object} + */ + this.texturePool = {}; + + /** + * a pool for storing filter states, save us creating new ones each tick + * @type {Array} + */ + this.statePool = []; + + /** + * A very simple geometry used when drawing a filter effect to the screen + * @type {Quad} + */ + this.quad = new Quad(); + + this.quadUv = new QuadUv(); + + /** + * Temporary rect for maths + * @type {PIXI.Rectangle} + */ + this.tempRect = new Rectangle(); + + this.activeState = {}; + + /** + * this uniform group is attached to filter uniforms when used + * @type {UniformGroup} + */ + this.globalUniforms = new UniformGroup({ + sourceFrame: this.tempRect, + destinationFrame: this.tempRect, + + // legacy variables + filterArea: new Float32Array(4), + filterClamp: new Float32Array(4), + }, true); + } + + /** + * Adds a new filter to the System. + * + * @param {PIXI.DisplayObject} target - The target of the filter to render. + * @param {PIXI.Filter[]} filters - The filters to apply. + */ + push(target, filters) + { + const renderer = this.renderer; + const filterStack = this.renderer.renderTexture.defaultFilterStack; + const state = this.statePool.pop() || new FilterState(); + + let resolution = filters[0].resolution; + let padding = filters[0].padding; + let autoFit = filters[0].autoFit; + let legacy = filters[0].legacy; + + for (let i = 1; i < filters.length; i++) + { + const filter = filters[i]; + + // lets use the lowest resolution.. + resolution = Math.min(resolution, filter.resolution); + // and the largest amount of padding! + padding = Math.max(padding, filter.padding); + // only auto fit if all filters are autofit + autoFit = autoFit || filter.autoFit; + + legacy = legacy || filter.legacy; + } + + filterStack.push(state); + + state.resolution = resolution; + + state.legacy = legacy; + + // round to whole number based on resolution + // TODO move that to the shader too? + state.sourceFrame = target.filterArea || target.getBounds(true); + + state.sourceFrame.pad(padding); + + if (autoFit) + { + state.sourceFrame.fit(this.renderer.renderTexture.destinationFrame); + } + + state.sourceFrame.round(resolution); + + state.renderTexture = this.getPotFilterTexture(state.sourceFrame.width, state.sourceFrame.height, resolution); + state.filters = filters; + + state.destinationFrame.width = state.renderTexture.width; + state.destinationFrame.height = state.renderTexture.height; + + state.renderTexture.filterFrame = state.sourceFrame; + renderer.renderTexture.bind(state.renderTexture, state.sourceFrame);// /, state.destinationFrame); + renderer.renderTexture.clear(); + } + + /** + * Pops off the filter and applies it. + * + */ + pop() + { + const renderer = this.renderer; + const filterStack = renderer.renderTexture.defaultFilterStack; + const state = filterStack.pop(); + const filters = state.filters; + + this.activeState = state; + + const globalUniforms = this.globalUniforms.uniforms; + + globalUniforms.sourceFrame = state.sourceFrame; + globalUniforms.destinationFrame = state.destinationFrame; + globalUniforms.resolution = state.resolution; + + // only update the rect if its legacy.. + if (state.legacy) + { + const filterArea = globalUniforms.filterArea; + const filterClamp = globalUniforms.filterClamp; + + filterArea[0] = state.destinationFrame.width; + filterArea[1] = state.destinationFrame.height; + filterArea[2] = state.sourceFrame.x; + filterArea[3] = state.sourceFrame.y; + + filterClamp[0] = 0.5 / state.resolution / state.destinationFrame.width; + filterClamp[1] = 0.5 / state.resolution / state.destinationFrame.height; + filterClamp[2] = (state.sourceFrame.width - 0.5) / state.resolution / state.destinationFrame.width; + filterClamp[3] = (state.sourceFrame.height - 0.5) / state.resolution / state.destinationFrame.height; + } + + this.globalUniforms.update(); + + const lastState = filterStack[filterStack.length - 1]; + + if (filters.length === 1) + { + filters[0].apply(this, state.renderTexture, lastState.renderTexture, false, state); + + this.returnFilterTexture(state.renderTexture); + } + else + { + let flip = state.renderTexture; + let flop = this.getPotFilterTexture( + flip.width, + flip.height, + state.resolution + ); + + flop.filterFrame = flip.filterFrame; + + let i = 0; + + for (i = 0; i < filters.length - 1; ++i) + { + filters[i].apply(this, flip, flop, true, state); + + const t = flip; + + flip = flop; + flop = t; + } + + filters[i].apply(this, flip, lastState.renderTexture, false, state); + + this.returnFilterTexture(flip); + this.returnFilterTexture(flop); + } + + this.statePool.push(state); + } + + /** + * Draws a filter. + * + * @param {PIXI.Filter} filter - The filter to draw. + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + * @param {boolean} clear - Should the output be cleared before rendering to it + */ + applyFilter(filter, input, output, clear) + { + const renderer = this.renderer; + + renderer.renderTexture.bind(output, output ? output.filterFrame : null); + + if (clear) + { + // gl.disable(gl.SCISSOR_TEST); + renderer.renderTexture.clear(); + // gl.enable(gl.SCISSOR_TEST); + } + + // set the uniforms.. + filter.uniforms.uSampler = input; + filter.uniforms.filterGlobals = this.globalUniforms; + + // TODO make it so that the order of this does not matter.. + // because it does at the moment cos of global uniforms. + // they need to get resynced + + renderer.state.setState(filter.state); + renderer.shader.bind(filter); + + if (filter.legacy) + { + this.quadUv.map(input._frame, input.filterFrame); + + renderer.geometry.bind(this.quadUv); + renderer.geometry.draw(DRAW_MODES.TRIANGLES); + } + else + { + renderer.geometry.bind(this.quad); + renderer.geometry.draw(DRAW_MODES.TRIANGLE_STRIP); + } + } + + /** + * Calculates the mapped matrix. + * + * TODO playing around here.. this is temporary - (will end up in the shader) + * this returns a matrix that will normalise map filter cords in the filter to screen space + * + * @param {PIXI.Matrix} outputMatrix - the matrix to output to. + * @return {PIXI.Matrix} The mapped matrix. + */ + calculateScreenSpaceMatrix(outputMatrix) + { + const currentState = this.activeState; + + return filterTransforms.calculateScreenSpaceMatrix( + outputMatrix, + currentState.sourceFrame, + currentState.destinationFrame + ); + } + + /** + * This will map the filter coord so that a texture can be used based on the transform of a sprite + * + * @param {PIXI.Matrix} outputMatrix - The matrix to output to. + * @param {PIXI.Sprite} sprite - The sprite to map to. + * @return {PIXI.Matrix} The mapped matrix. + */ + calculateSpriteMatrix(outputMatrix, sprite) + { + const currentState = this.activeState; + + return filterTransforms.calculateSpriteMatrix( + outputMatrix, + currentState.sourceFrame, + currentState.destinationFrame, + sprite + ); + } + + /** + * Destroys this Filter System. + * + * @param {boolean} [contextLost=false] context was lost, do not free shaders + * + */ + destroy(contextLost = false) + { + if (!contextLost) + { + this.emptyPool(); + } + else + { + this.texturePool = {}; + } + } + + /** + * Gets a Power-of-Two render texture. + * + * TODO move to a seperate class could be on renderer? + * also - could cause issue with multiple contexts? + * + * @private + * @param {WebGLRenderingContext} gl - The webgl rendering context + * @param {number} minWidth - The minimum width of the render target. + * @param {number} minHeight - The minimum height of the render target. + * @param {number} resolution - The resolution of the render target. + * @return {PIXI.RenderTarget} The new render target. + */ + getPotFilterTexture(minWidth, minHeight, resolution) + { + minWidth = bitTwiddle.nextPow2(minWidth); + minHeight = bitTwiddle.nextPow2(minHeight); + resolution = resolution || 1; + + const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + + if (!this.texturePool[key]) + { + this.texturePool[key] = []; + } + + let renderTexture = this.texturePool[key].pop(); + + if (!renderTexture) + { + // temporary bypass cache.. + // internally - this will cause a texture to be bound.. + renderTexture = RenderTexture.create({ + width: minWidth, + height: minHeight, + resolution, + }); + } + + return renderTexture; + } + + /** + * Gets extra render texture to use inside current filter + * + * @param {number} resolution resolution of the renderTexture + * @returns {PIXI.RenderTexture} + */ + getFilterTexture(resolution) + { + const rt = this.activeState.renderTexture; + + const filterTexture = this.getPotFilterTexture(rt.width, rt.height, resolution || rt.baseTexture.resolution); + + filterTexture.filterFrame = rt.filterFrame; + + return filterTexture; + } + + /** + * Frees a render texture back into the pool. + * + * @param {PIXI.RenderTarget} renderTexture - The renderTarget to free + */ + returnFilterTexture(renderTexture) + { + renderTexture.filterFrame = null; + + const base = renderTexture.baseTexture; + + const minWidth = base.width; + const minHeight = base.height; + + const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + + this.texturePool[key].push(renderTexture); + } + + /** + * Empties the texture pool. + * + */ + emptyPool() + { + for (const i in this.texturePool) + { + const textures = this.texturePool[i]; + + if (textures) + { + for (let j = 0; j < textures.length; j++) + { + textures[j].destroy(true); + } + } + } + + this.texturePool = {}; + } +} diff --git a/packages/core/src/filters/defaultFilter.frag b/packages/core/src/filters/defaultFilter.frag new file mode 100644 index 0000000..a14c1be --- /dev/null +++ b/packages/core/src/filters/defaultFilter.frag @@ -0,0 +1,22 @@ +varying vec2 vTextureCoord; +varying vec2 vFilterCoord; + +uniform sampler2D uSampler; +uniform sampler2D filterSampler; + +void main(void){ + vec4 masky = texture2D(filterSampler, vFilterCoord); + vec4 sample = texture2D(uSampler, vTextureCoord); + vec4 color; + if(mod(vFilterCoord.x, 1.0) > 0.5) + { + color = vec4(1.0, 0.0, 0.0, 1.0); + } + else + { + color = vec4(0.0, 1.0, 0.0, 1.0); + } + // gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0); + gl_FragColor = mix(sample, masky, 0.5); + gl_FragColor *= sample.a; +} diff --git a/packages/core/src/filters/defaultFilter.vert b/packages/core/src/filters/defaultFilter.vert new file mode 100644 index 0000000..e1febca --- /dev/null +++ b/packages/core/src/filters/defaultFilter.vert @@ -0,0 +1,14 @@ +attribute vec2 aVertexPosition; +attribute vec2 aTextureCoord; + +uniform mat3 projectionMatrix; +uniform mat3 filterMatrix; + +varying vec2 vTextureCoord; +varying vec2 vFilterCoord; + +void main(void){ + gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); + vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy; + vTextureCoord = aTextureCoord ; +} diff --git a/packages/core/src/filters/filterTransforms.js b/packages/core/src/filters/filterTransforms.js new file mode 100644 index 0000000..87a3cd6 --- /dev/null +++ b/packages/core/src/filters/filterTransforms.js @@ -0,0 +1,50 @@ +import { Matrix } from '@pixi/math'; + +/** + * Calculates the mapped matrix + * @param filterArea {Rectangle} The filter area + * @param sprite {Sprite} the target sprite + * @param outputMatrix {Matrix} @alvin + * @private + */ +// this returns a matrix that will normalise map filter cords in the filter to screen space +export function calculateScreenSpaceMatrix(outputMatrix, filterArea, textureSize) +{ + // TODO unwrap? + const mappedMatrix = outputMatrix.identity(); + + mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); + + mappedMatrix.scale(textureSize.width, textureSize.height); + + return mappedMatrix; +} + +export function calculateNormalizedScreenSpaceMatrix(outputMatrix, filterArea, textureSize) +{ + const mappedMatrix = outputMatrix.identity(); + + mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); + + const translateScaleX = (textureSize.width / filterArea.width); + const translateScaleY = (textureSize.height / filterArea.height); + + mappedMatrix.scale(translateScaleX, translateScaleY); + + return mappedMatrix; +} + +// this will map the filter coord so that a texture can be used based on the transform of a sprite +export function calculateSpriteMatrix(outputMatrix, filterArea, textureSize, sprite) +{ + const orig = sprite._texture.orig; + const mappedMatrix = outputMatrix.set(textureSize.width, 0, 0, textureSize.height, filterArea.x, filterArea.y); + const worldTransform = sprite.worldTransform.copyTo(Matrix.TEMP_MATRIX); + + worldTransform.invert(); + mappedMatrix.prepend(worldTransform); + mappedMatrix.scale(1.0 / orig.width, 1.0 / orig.height); + mappedMatrix.translate(sprite.anchor.x, sprite.anchor.y); + + return mappedMatrix; +} diff --git a/packages/core/src/filters/spriteMask/SpriteMaskFilter.js b/packages/core/src/filters/spriteMask/SpriteMaskFilter.js new file mode 100644 index 0000000..91697b7 --- /dev/null +++ b/packages/core/src/filters/spriteMask/SpriteMaskFilter.js @@ -0,0 +1,63 @@ +import Filter from '../Filter'; +import { Matrix } from '@pixi/math'; +import vertex from './spriteMaskFilter.vert'; +import fragment from './spriteMaskFilter.frag'; +import { default as TextureMatrix } from '../../textures/TextureMatrix'; + +/** + * The SpriteMaskFilter class + * + * @class + * @extends PIXI.Filter + * @memberof PIXI + */ +export default class SpriteMaskFilter extends Filter +{ + /** + * @param {PIXI.Sprite} sprite - the target sprite + */ + constructor(sprite) + { + const maskMatrix = new Matrix(); + + super(vertex, fragment); + + sprite.renderable = false; + + this.maskSprite = sprite; + this.maskMatrix = maskMatrix; + } + + /** + * Applies the filter + * + * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + */ + apply(filterManager, input, output) + { + const maskSprite = this.maskSprite; + const tex = this.maskSprite.texture; + + if (!tex.valid) + { + return; + } + if (!tex.transform) + { + // margin = 0.0, let it bleed a bit, shader code becomes easier + // assuming that atlas textures were made with 1-pixel padding + tex.transform = new TextureMatrix(tex, 0.0); + } + tex.transform.update(); + + this.uniforms.mask = tex; + this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite) + .prepend(tex.transform.mapCoord); + this.uniforms.alpha = maskSprite.worldAlpha; + this.uniforms.maskClamp = tex.transform.uClampFrame; + + filterManager.applyFilter(this, input, output); + } +} diff --git a/packages/core/src/filters/spriteMask/spriteMaskFilter.frag b/packages/core/src/filters/spriteMask/spriteMaskFilter.frag new file mode 100644 index 0000000..0e4aef8 --- /dev/null +++ b/packages/core/src/filters/spriteMask/spriteMaskFilter.frag @@ -0,0 +1,23 @@ +varying vec2 vMaskCoord; +varying vec2 vTextureCoord; + +uniform sampler2D uSampler; +uniform sampler2D mask; +uniform float alpha; +uniform vec4 maskClamp; + +void main(void) +{ + float clip = step(3.5, + step(maskClamp.x, vMaskCoord.x) + + step(maskClamp.y, vMaskCoord.y) + + step(vMaskCoord.x, maskClamp.z) + + step(vMaskCoord.y, maskClamp.w)); + + vec4 original = texture2D(uSampler, vTextureCoord); + vec4 masky = texture2D(mask, vMaskCoord); + + original *= (masky.r * masky.a * alpha * clip); + + gl_FragColor = original; +} diff --git a/packages/core/src/filters/spriteMask/spriteMaskFilter.vert b/packages/core/src/filters/spriteMask/spriteMaskFilter.vert new file mode 100644 index 0000000..d23a30c --- /dev/null +++ b/packages/core/src/filters/spriteMask/spriteMaskFilter.vert @@ -0,0 +1,16 @@ +attribute vec2 aVertexPosition; +attribute vec2 aTextureCoord; + +uniform mat3 projectionMatrix; +uniform mat3 otherMatrix; + +varying vec2 vMaskCoord; +varying vec2 vTextureCoord; + +void main(void) +{ + gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); + + vTextureCoord = aTextureCoord; + vMaskCoord = ( otherMatrix * vec3( aTextureCoord, 1.0) ).xy; +} diff --git a/packages/core/src/framebuffer/FrameBuffer.js b/packages/core/src/framebuffer/FrameBuffer.js new file mode 100644 index 0000000..84e9fcc --- /dev/null +++ b/packages/core/src/framebuffer/FrameBuffer.js @@ -0,0 +1,106 @@ +import Texture from '../textures/BaseTexture'; +import { FORMATS, TYPES } from '@pixi/constants'; + +/** + * Frame buffer + * @class + * @memberof PIXI + */ +export default class FrameBuffer +{ + constructor(width, height) + { + this.width = width || 100; + this.height = height || 100; + + this.stencil = false; + this.depth = false; + + this.dirtyId = 0; + this.dirtyFormat = 0; + this.dirtySize = 0; + + this.depthTexture = null; + this.colorTextures = []; + + this.glFrameBuffers = {}; + } + + get colorTexture() + { + return this.colorTextures[0]; + } + + addColorTexture(index, texture) + { + // TODO add some validation to the texture - same width / height etc? + this.colorTextures[index || 0] = texture || new Texture(null, { scaleMode: 0, + resolution: 1, + mipmap: false, + width: this.width, + height: this.height });// || new Texture(); + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + addDepthTexture(texture) + { + /* eslint-disable max-len */ + this.depthTexture = texture || new Texture(null, { scaleMode: 0, + resolution: 1, + width: this.width, + height: this.height, + mipmap: false, + format: FORMATS.DEPTH_COMPONENT, + type: TYPES.UNSIGNED_SHORT });// UNSIGNED_SHORT; + /* eslint-disable max-len */ + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + enableDepth() + { + this.depth = true; + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + enableStencil() + { + this.stencil = true; + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + resize(width, height) + { + if (width === this.width && height === this.height) return; + + this.width = width; + this.height = height; + + this.dirtyId++; + this.dirtySize++; + + for (let i = 0; i < this.colorTextures.length; i++) + { + this.colorTextures[i].setSize(width, height); + } + + if (this.depthTexture) + { + this.depthTexture.setSize(width, height); + } + } +} diff --git a/packages/core/src/framebuffer/FramebufferSystem.js b/packages/core/src/framebuffer/FramebufferSystem.js new file mode 100644 index 0000000..2cc2437 --- /dev/null +++ b/packages/core/src/framebuffer/FramebufferSystem.js @@ -0,0 +1,246 @@ +import System from '../System'; +import { Rectangle } from '@pixi/math'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class FramebufferSystem extends System +{ + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + contextChange() + { + this.gl = this.renderer.gl; + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + this.current = null; + this.viewport = new Rectangle(); + + this.drawBufferExtension = this.renderer.context.extensions.drawBuffers; + } + + bind(framebuffer, frame) + { + const gl = this.gl; + + this.current = framebuffer; + + if (framebuffer) + { + // TODO cacheing layer! + + const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID] || this.initFramebuffer(framebuffer); + + gl.bindFramebuffer(gl.FRAMEBUFFER, fbo.framebuffer); + // makesure all textures are unbound.. + + // now check for updates... + if (fbo.dirtyId !== framebuffer.dirtyId) + { + fbo.dirtyId = framebuffer.dirtyId; + + if (fbo.dirtyFormat !== framebuffer.dirtyFormat) + { + fbo.dirtyFormat = framebuffer.dirtyFormat; + this.updateFramebuffer(framebuffer); + } + else if (fbo.dirtySize !== framebuffer.dirtySize) + { + fbo.dirtySize = framebuffer.dirtySize; + this.resizeFramebuffer(framebuffer); + } + } + + for (let i = 0; i < framebuffer.colorTextures.length; i++) + { + if (framebuffer.colorTextures[i].texturePart) + { + this.renderer.texture.unbind(framebuffer.colorTextures[i].texture); + } + else + { + this.renderer.texture.unbind(framebuffer.colorTextures[i]); + } + } + + if (framebuffer.depthTexture) + { + this.renderer.texture.unbind(framebuffer.depthTexture); + } + + if (frame) + { + this.setViewport(frame.x, frame.y, frame.width, frame.height); + } + else + { + this.setViewport(0, 0, framebuffer.width, framebuffer.height); + } + } + else + { + gl.bindFramebuffer(gl.FRAMEBUFFER, null); + + if (frame) + { + this.setViewport(frame.x, frame.y, frame.width, frame.height); + } + else + { + this.setViewport(0, 0, this.renderer.width, this.renderer.height); + } + } + } + + setViewport(x, y, width, height) + { + const v = this.viewport; + + if (v.width !== width || v.height !== height || v.x !== x || v.y !== y) + { + v.x = x; + v.y = y; + v.width = width; + v.height = height; + + this.gl.viewport(x, y, width, height); + } + } + + get size() + { + if (this.current) + { + // TODO store temp + return { x: 0, y: 0, width: this.current.width, height: this.current.height }; + } + + return { x: 0, y: 0, width: this.renderer.width, height: this.renderer.height }; + } + + clear(r, g, b, a) + { + const gl = this.gl; + + // TODO clear color can be set only one right? + gl.clearColor(r, g, b, a); + gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); + } + + // private functions... + + initFramebuffer(framebuffer) + { + const gl = this.gl; + + // TODO - make this a class? + const fbo = { + framebuffer: gl.createFramebuffer(), + stencil: null, + dirtyId: 0, + dirtyFormat: 0, + dirtySize: 0, + }; + + framebuffer.glFrameBuffers[this.CONTEXT_UID] = fbo; + + return fbo; + } + + resizeFramebuffer(framebuffer) + { + const gl = this.gl; + + if (framebuffer.stencil || framebuffer.depth) + { + gl.bindRenderbuffer(gl.RENDERBUFFER, this.stencil); + gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); + } + } + + updateFramebuffer(framebuffer) + { + const gl = this.gl; + + const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID]; + + // bind the color texture + const colorTextures = framebuffer.colorTextures; + + let count = colorTextures.length; + + if (!this.drawBufferExtension) + { + count = Math.min(count, 1); + } + + const activeTextures = []; + + for (let i = 0; i < count; i++) + { + const texture = framebuffer.colorTextures[i]; + + if (texture.texturePart) + { + this.renderer.texture.bind(texture.texture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.COLOR_ATTACHMENT0 + i, + gl.TEXTURE_CUBE_MAP_NEGATIVE_X + texture.side, + texture.texture._glTextures[this.CONTEXT_UID].texture, + 0); + } + else + { + this.renderer.texture.bind(texture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.COLOR_ATTACHMENT0 + i, + gl.TEXTURE_2D, + texture._glTextures[this.CONTEXT_UID].texture, + 0); + } + + activeTextures.push(gl.COLOR_ATTACHMENT0 + i); + } + + if (this.drawBufferExtension && activeTextures.length > 1) + { + this.drawBufferExtension.drawBuffersWEBGL(activeTextures); + } + + if (framebuffer.depthTexture) + { + const depthTextureExt = this.renderer.context.extensions.depthTexture; + + if (depthTextureExt) + { + const depthTexture = framebuffer.depthTexture; + + this.renderer.texture.bind(depthTexture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.DEPTH_ATTACHMENT, + gl.TEXTURE_2D, + depthTexture._glTextures[this.CONTEXT_UID].texture, + 0); + } + } + + if (framebuffer.stencil || framebuffer.depth) + { + fbo.stencil = gl.createRenderbuffer(); + + gl.bindRenderbuffer(gl.RENDERBUFFER, fbo.stencil); + + // TODO.. this is depth AND stencil? + gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, fbo.stencil); + gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); + // fbo.enableStencil(); + } + } +} diff --git a/packages/core/src/geometry/GLBuffer.js b/packages/core/src/geometry/GLBuffer.js new file mode 100644 index 0000000..0477e8a --- /dev/null +++ b/packages/core/src/geometry/GLBuffer.js @@ -0,0 +1,9 @@ +export default class GLBuffer +{ + constructor(buffer) + { + this.buffer = buffer; + this.updateID = -1; + this.byteLength = -1; + } +} diff --git a/packages/core/src/geometry/GeometrySystem.js b/packages/core/src/geometry/GeometrySystem.js new file mode 100644 index 0000000..959f827 --- /dev/null +++ b/packages/core/src/geometry/GeometrySystem.js @@ -0,0 +1,388 @@ +import System from '../System'; +import GLBuffer from './GLBuffer'; + +const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class GeometrySystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this._activeGeometry = null; + this._activeVao = null; + + this.hasVao = true; + this.hasInstance = true; + } + + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + contextChange() + { + const gl = this.gl = this.renderer.gl; + + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + + // webgl2 + if (!gl.createVertexArray) + { + // webgl 1! + let nativeVaoExtension = this.renderer.context.extensions.vertexArrayObject; + + if (this.renderer.legacy) + { + nativeVaoExtension = null; + } + + if (nativeVaoExtension) + { + gl.createVertexArray = () => + nativeVaoExtension.createVertexArrayOES(); + + gl.bindVertexArray = (vao) => + nativeVaoExtension.bindVertexArrayOES(vao); + + gl.deleteVertexArray = (vao) => + nativeVaoExtension.deleteVertexArrayOES(vao); + } + else + { + this.hasVao = false; + gl.createVertexArray = () => + { + // empty + }; + + gl.bindVertexArray = () => + { + // empty + }; + + gl.deleteVertexArray = () => + { + // empty + }; + } + } + + if (!gl.vertexAttribDivisor) + { + const instanceExt = gl.getExtension('ANGLE_instanced_arrays'); + + if (instanceExt) + { + gl.vertexAttribDivisor = (a, b) => + instanceExt.vertexAttribDivisorANGLE(a, b); + + gl.drawElementsInstanced = (a, b, c, d, e) => + instanceExt.drawElementsInstancedANGLE(a, b, c, d, e); + + gl.drawArraysInstanced = (a, b, c, d) => + instanceExt.drawArraysInstancedANGLE(a, b, c, d); + } + else + { + this.hasInstance = false; + } + } + } + + /** + * Binds geometry so that is can be drawn. Creating a Vao if required + * @private + * @param {PIXI.Geometry} geometry instance of geometry to bind + */ + bind(geometry, shader) + { + shader = shader || this.renderer.shader.shader; + + const gl = this.gl; + + // not sure the best way to address this.. + // currently different shaders require different VAOs for the same geometry + // Still mulling over the best way to solve this one.. + // will likely need to modify the shader attribute locations at run time! + let vaos = geometry.glVertexArrayObjects[this.CONTEXT_UID]; + + if (!vaos) + { + geometry.glVertexArrayObjects[this.CONTEXT_UID] = vaos = {}; + } + + const vao = vaos[shader.program.id] || this.initGeometryVao(geometry, shader.program); + + this._activeGeometry = geometry; + + if (this._activeVao !== vao) + { + this._activeVao = vao; + + if (this.hasVao) + { + gl.bindVertexArray(vao); + } + else + { + this.activateVao(geometry, shader.program); + } + } + + // TODO - optimise later! + // don't need to loop through if nothing changed! + // maybe look to add an 'autoupdate' to geometry? + this.updateBuffers(); + } + + reset() + { + this.unbind(); + } + + updateBuffers() + { + const geometry = this._activeGeometry; + const gl = this.gl; + + for (let i = 0; i < geometry.buffers.length; i++) + { + const buffer = geometry.buffers[i]; + + const glBuffer = buffer._glBuffers[this.CONTEXT_UID]; + + if (buffer._updateID !== glBuffer.updateID) + { + glBuffer.updateID = buffer._updateID; + + // TODO can cache this on buffer! maybe added a getter / setter? + const type = buffer.index ? gl.ELEMENT_ARRAY_BUFFER : gl.ARRAY_BUFFER; + const drawType = buffer.static ? gl.STATIC_DRAW : gl.DYNAMIC_DRAW; + + gl.bindBuffer(type, glBuffer.buffer); + + if (glBuffer.byteLength >= buffer.data.byteLength) + { + // offset is always zero for now! + gl.bufferSubData(type, 0, buffer.data); + } + else + { + gl.bufferData(type, buffer.data, drawType); + } + } + } + } + + checkCompatability(geometry, program) + { + // geometry must have at least all the attributes that the shader requires. + const geometryAttributes = geometry.attributes; + const shaderAttributes = program.attributeData; + + for (const j in shaderAttributes) + { + if (!geometryAttributes[j]) + { + throw new Error(`shader and geometry incompatible, geometry missing the "${j}" attribute`); + } + } + } + + /** + * Creates a Vao with the same structure as the geometry and stores it on the geometry. + * @private + * @param {PIXI.Geometry} geometry instance of geometry to to generate Vao for + */ + initGeometryVao(geometry, program) + { + this.checkCompatability(geometry, program); + + const gl = this.gl; + const CONTEXT_UID = this.CONTEXT_UID; + const buffers = geometry.buffers; + const attributes = geometry.attributes; + + const tempStride = {}; + const tempStart = {}; + + for (const j in buffers) + { + tempStride[j] = 0; + tempStart[j] = 0; + } + + for (const j in attributes) + { + if (!attributes[j].size && program.attributeData[j]) + { + attributes[j].size = program.attributeData[j].size; + } + + tempStride[attributes[j].buffer] += attributes[j].size * byteSizeMap[attributes[j].type]; + } + + for (const j in attributes) + { + const attribute = attributes[j]; + const attribSize = attribute.size; + + if (attribute.stride === undefined) + { + if (tempStride[attribute.buffer] === attribSize * byteSizeMap[attribute.type]) + { + attribute.stride = 0; + } + else + { + attribute.stride = tempStride[attribute.buffer]; + } + } + + if (attribute.start === undefined) + { + attribute.start = tempStart[attribute.buffer]; + + tempStart[attribute.buffer] += attribSize * byteSizeMap[attribute.type]; + } + } + + // first update - and create the buffers! + // only create a gl buffer if it actually gets + for (let i = 0; i < buffers.length; i++) + { + const buffer = buffers[i]; + + if (!buffer._glBuffers[CONTEXT_UID]) + { + buffer._glBuffers[CONTEXT_UID] = new GLBuffer(gl.createBuffer()); + } + } + + // TODO - maybe make this a data object? + // lets wait to see if we need to first! + const vao = gl.createVertexArray(); + + gl.bindVertexArray(vao); + + this.activateVao(geometry, program); + + gl.bindVertexArray(null); + + // add it to the cache! + geometry.glVertexArrayObjects[this.CONTEXT_UID][program.id] = vao; + + return vao; + } + + activateVao(geometry, program) + { + const gl = this.gl; + const CONTEXT_UID = this.CONTEXT_UID; + const buffers = geometry.buffers; + const attributes = geometry.attributes; + + if (geometry.indexBuffer) + { + // first update the index buffer if we have one.. + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, geometry.indexBuffer._glBuffers[CONTEXT_UID].buffer); + } + + let lastBuffer = null; + + // add a new one! + for (const j in attributes) + { + const attribute = attributes[j]; + const buffer = buffers[attribute.buffer]; + const glBuffer = buffer._glBuffers[CONTEXT_UID]; + + if (program.attributeData[j]) + { + if (lastBuffer !== glBuffer) + { + gl.bindBuffer(gl.ARRAY_BUFFER, glBuffer.buffer); + + lastBuffer = glBuffer; + } + + const location = program.attributeData[j].location; + + // TODO introduce state again + // we can optimise this for older devices that have no VAOs + gl.enableVertexAttribArray(location); + + gl.vertexAttribPointer(location, + attribute.size, + attribute.type || gl.FLOAT, + attribute.normalized, + attribute.stride, + attribute.start); + + if (attribute.instance) + { + // TODO calculate instance count based of this... + if (this.hasInstance) + { + gl.vertexAttribDivisor(location, 1); + } + else + { + throw new Error('geometry error, GPU Instancing is not supported on this device'); + } + } + } + } + } + + draw(type, size, start, instanceCount) + { + const gl = this.gl; + const geometry = this._activeGeometry; + + // TODO.. this should not change so maybe cache the function? + + if (geometry.indexBuffer) + { + if (geometry.instanced) + { + /* eslint-disable max-len */ + gl.drawElementsInstanced(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2, instanceCount || 1); + /* eslint-enable max-len */ + } + else + { + gl.drawElements(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2); + } + } + else + if (geometry.instanced) + { + // TODO need a better way to calculate size.. + gl.drawArraysInstanced(type, start, size || geometry.getSize(), instanceCount || 1); + } + else + { + gl.drawArrays(type, start, size || geometry.getSize()); + } + + return this; + } + + unbind() + { + this.gl.bindVertexArray(null); + this._activeVao = null; + this._activeGeometry = null; + } +} diff --git a/packages/core/src/geometry/utils/setVertexAttribArrays.js b/packages/core/src/geometry/utils/setVertexAttribArrays.js new file mode 100644 index 0000000..23b6959 --- /dev/null +++ b/packages/core/src/geometry/utils/setVertexAttribArrays.js @@ -0,0 +1,54 @@ +// var GL_MAP = {}; + +/** + * @param gl {WebGLRenderingContext} The current WebGL context + * @param attribs {*} + * @param state {*} + */ +export default function setVertexAttribArrays(gl, attribs, state) +{ + let i; + + if (state) + { + const tempAttribState = state.tempAttribState; + const attribState = state.attribState; + + for (i = 0; i < tempAttribState.length; i++) + { + tempAttribState[i] = false; + } + + // set the new attribs + for (i = 0; i < attribs.length; i++) + { + tempAttribState[attribs[i].attribute.location] = true; + } + + for (i = 0; i < attribState.length; i++) + { + if (attribState[i] !== tempAttribState[i]) + { + attribState[i] = tempAttribState[i]; + + if (state.attribState[i]) + { + gl.enableVertexAttribArray(i); + } + else + { + gl.disableVertexAttribArray(i); + } + } + } + } + else + { + for (i = 0; i < attribs.length; i++) + { + const attrib = attribs[i]; + + gl.enableVertexAttribArray(attrib.attribute.location); + } + } +} diff --git a/packages/core/src/index.js b/packages/core/src/index.js index 58c456a..fd1f547 100644 --- a/packages/core/src/index.js +++ b/packages/core/src/index.js @@ -1,30 +1,30 @@ import * as resources from './textures/resources'; -import * as systems from './renderers/systems'; +import * as systems from './systems'; -export { resources }; export { systems }; +export { resources }; -export { default as Renderer } from './renderers/Renderer'; -export { default as AbstractRenderer } from './renderers/AbstractRenderer'; -export { default as FrameBuffer } from './textures/FrameBuffer'; +export { default as System } from './System'; +export { default as Renderer } from './Renderer'; +export { default as AbstractRenderer } from './AbstractRenderer'; +export { default as FrameBuffer } from './framebuffer/FrameBuffer'; export { default as CubeTexture } from './textures/CubeTexture'; export { default as BaseTexture } from './textures/BaseTexture'; export { default as Texture } from './textures/Texture'; export { default as TextureMatrix } from './textures/TextureMatrix'; -export { default as RenderTexture } from './textures/RenderTexture'; -export { default as BaseRenderTexture } from './textures/BaseRenderTexture'; +export { default as RenderTexture } from './renderTexture/RenderTexture'; +export { default as BaseRenderTexture } from './renderTexture/BaseRenderTexture'; export { default as TextureUvs } from './textures/TextureUvs'; -export { default as State } from './renderers/State'; -export { default as ObjectRenderer } from './renderers/utils/ObjectRenderer'; -export { default as Quad } from './renderers/utils/Quad'; -export { default as QuadUv } from './renderers/utils/QuadUv'; -export { default as checkMaxIfStatmentsInShader } from './renderers/utils/checkMaxIfStatmentsInShader'; +export { default as State } from './state/State'; +export { default as ObjectRenderer } from './batch/ObjectRenderer'; +export { default as Quad } from './utils/Quad'; +export { default as QuadUv } from './utils/QuadUv'; +export { default as checkMaxIfStatmentsInShader } from './shader/utils/checkMaxIfStatmentsInShader'; export { default as Shader } from './shader/Shader'; export { default as Program } from './shader/Program'; export { default as UniformGroup } from './shader/UniformGroup'; -export { default as SpriteMaskFilter } from './renderers/filters/spriteMask/SpriteMaskFilter'; -export { default as Filter } from './renderers/filters/Filter'; +export { default as SpriteMaskFilter } from './filters/spriteMask/SpriteMaskFilter'; +export { default as Filter } from './filters/Filter'; export { default as Attribute } from './geometry/Attribute'; export { default as Buffer } from './geometry/Buffer'; export { default as Geometry } from './geometry/Geometry'; - diff --git a/packages/core/src/mask/MaskSystem.js b/packages/core/src/mask/MaskSystem.js new file mode 100644 index 0000000..99174c2 --- /dev/null +++ b/packages/core/src/mask/MaskSystem.js @@ -0,0 +1,199 @@ +import System from '../System'; +import AlphaMaskFilter from '../filters/spriteMask/SpriteMaskFilter'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class MaskSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + // TODO - we don't need both! + this.scissor = false; + this.scissorData = null; + this.scissorRenderTarget = null; + + this.enableScissor = false; + + this.alphaMaskPool = []; + this.alphaMaskIndex = 0; + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.DisplayObject} target - Display Object to push the mask to + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + push(target, maskData) + { + // TODO the root check means scissor rect will not + // be used on render textures more info here: + // https://github.com/pixijs/pixi.js/pull/3545 + + if (maskData.texture) + { + this.pushSpriteMask(target, maskData); + } + else if (this.enableScissor + && !this.scissor + && this.renderer._activeRenderTarget.root + && !this.renderer.stencil.stencilMaskStack.length + && maskData.isFastRect()) + { + const matrix = maskData.worldTransform; + + let rot = Math.atan2(matrix.b, matrix.a); + + // use the nearest degree! + rot = Math.round(rot * (180 / Math.PI)); + + if (rot % 90) + { + this.pushStencilMask(maskData); + } + else + { + this.pushScissorMask(target, maskData); + } + } + else + { + this.pushStencilMask(maskData); + } + } + + /** + * Removes the last mask from the mask stack and doesn't return it. + * + * @param {PIXI.DisplayObject} target - Display Object to pop the mask from + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + pop(target, maskData) + { + if (maskData.texture) + { + this.popSpriteMask(target, maskData); + } + else if (this.enableScissor && !this.renderer.stencil.stencilMaskStack.length) + { + this.popScissorMask(target, maskData); + } + else + { + this.popStencilMask(target, maskData); + } + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.RenderTarget} target - Display Object to push the sprite mask to + * @param {PIXI.Sprite} maskData - Sprite to be used as the mask + */ + pushSpriteMask(target, maskData) + { + let alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; + + if (!alphaMaskFilter) + { + alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; + } + + alphaMaskFilter[0].resolution = this.renderer.resolution; + alphaMaskFilter[0].maskSprite = maskData; + + // TODO - may cause issues! + target.filterArea = maskData.getBounds(true); + + this.renderer.filterSystem.pushFilter(target, alphaMaskFilter); + + this.alphaMaskIndex++; + } + + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popSpriteMask() + { + this.renderer.filterSystem.popFilter(); + this.alphaMaskIndex--; + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + pushStencilMask(maskData) + { + this.renderer.batch.flush(); + this.renderer.stencil.pushStencil(maskData); + } + + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popStencilMask() + { + // this.renderer.currentRenderer.stop(); + this.renderer.stencil.popStencil(); + } + + /** + * + * @param {PIXI.DisplayObject} target - Display Object to push the mask to + * @param {PIXI.Graphics} maskData - The masking data. + */ + pushScissorMask(target, maskData) + { + maskData.renderable = true; + + const renderTarget = this.renderer._activeRenderTarget; + + const bounds = maskData.getBounds(); + + bounds.fit(renderTarget.size); + maskData.renderable = false; + + this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); + + const resolution = this.renderer.resolution; + + this.renderer.gl.scissor( + bounds.x * resolution, + (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, + bounds.width * resolution, + bounds.height * resolution + ); + + this.scissorRenderTarget = renderTarget; + this.scissorData = maskData; + this.scissor = true; + } + + /** + * + * + */ + popScissorMask() + { + this.scissorRenderTarget = null; + this.scissorData = null; + this.scissor = false; + + // must be scissor! + const gl = this.renderer.gl; + + gl.disable(gl.SCISSOR_TEST); + } +} diff --git a/packages/core/src/mask/StencilSystem.js b/packages/core/src/mask/StencilSystem.js new file mode 100644 index 0000000..62796a5 --- /dev/null +++ b/packages/core/src/mask/StencilSystem.js @@ -0,0 +1,133 @@ +import System from '../System'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class StencilSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + this.stencilMaskStack = []; + } + + /** + * Changes the mask stack that is used by this System. + * + * @param {PIXI.Graphics[]} stencilMaskStack - The mask stack + */ + setMaskStack(stencilMaskStack) + { + const gl = this.renderer.gl; + + if (stencilMaskStack.length !== this.stencilMaskStack.length) + { + if (stencilMaskStack.length === 0) + { + gl.disable(gl.STENCIL_TEST); + } + else + { + gl.enable(gl.STENCIL_TEST); + } + } + + this.stencilMaskStack = stencilMaskStack; + } + + /** + * Applies the Mask and adds it to the current stencil stack. @alvin + * + * @param {PIXI.Graphics} graphics - The mask + */ + pushStencil(graphics) + { + this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); + + // this.renderer._activeRenderTarget.attachStencilBuffer(); + + const gl = this.renderer.gl; + const prevMaskCount = this.stencilMaskStack.length; + + if (prevMaskCount === 0) + { + gl.enable(gl.STENCIL_TEST); + } + + this.stencilMaskStack.push(graphics); + + // Increment the refference stencil value where the new mask overlaps with the old ones. + gl.colorMask(false, false, false, false); + gl.stencilFunc(gl.EQUAL, prevMaskCount, this._getBitwiseMask()); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.INCR); + this.renderer.plugins.graphics.render(graphics); + + this._useCurrent(); + } + + /** + * Removes the last mask from the stencil stack. @alvin + */ + popStencil() + { + this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); + + const gl = this.renderer.gl; + const graphics = this.stencilMaskStack.pop(); + + if (this.stencilMaskStack.length === 0) + { + // the stack is empty! + gl.disable(gl.STENCIL_TEST); + gl.clear(gl.STENCIL_BUFFER_BIT); + gl.clearStencil(0); + } + else + { + // Decrement the refference stencil value where the popped mask overlaps with the other ones + gl.colorMask(false, false, false, false); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.DECR); + this.renderer.plugins.graphics.render(graphics); + + this._useCurrent(); + } + } + + /** + * Setup renderer to use the current stencil data. + */ + _useCurrent() + { + const gl = this.renderer.gl; + + gl.colorMask(true, true, true, true); + gl.stencilFunc(gl.EQUAL, this.stencilMaskStack.length, this._getBitwiseMask()); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP); + } + + /** + * Fill 1s equal to the number of acitve stencil masks. + * + * @return {number} The bitwise mask. + */ + _getBitwiseMask() + { + return (1 << this.stencilMaskStack.length) - 1; + } + + /** + * Destroys the mask stack. + * + */ + destroy() + { + super.destroy(this); + + this.stencilMaskStack = null; + } +} diff --git a/packages/core/src/projection/ProjectionSystem.js b/packages/core/src/projection/ProjectionSystem.js new file mode 100644 index 0000000..6e63b73 --- /dev/null +++ b/packages/core/src/projection/ProjectionSystem.js @@ -0,0 +1,73 @@ +import System from '../System'; +import { Matrix } from '@pixi/math'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class ProjectionSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.projectionMatrix = new Matrix(); + } + + update(destinationFrame, sourceFrame, resolution, root) + { + this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; + this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; + + this.calculateProjection(this.destinationFrame, this.sourceFrame, resolution, root); + + this.renderer.globalUniforms.uniforms.projectionMatrix = this.projectionMatrix; + this.renderer.globalUniforms.update(); + } + + /** + * Updates the projection matrix based on a projection frame (which is a rectangle) + * + * @param {Rectangle} destinationFrame - The destination frame. + * @param {Rectangle} sourceFrame - The source frame. + */ + calculateProjection(destinationFrame, sourceFrame, resolution, root) + { + const pm = this.projectionMatrix; + + // I don't think we will need this line.. + // pm.identity(); + + if (!root) + { + pm.a = (1 / destinationFrame.width * 2) * resolution; + pm.d = (1 / destinationFrame.height * 2) * resolution; + + pm.tx = -1 - (sourceFrame.x * pm.a); + pm.ty = -1 - (sourceFrame.y * pm.d); + } + else + { + pm.a = (1 / destinationFrame.width * 2) * resolution; + pm.d = (-1 / destinationFrame.height * 2) * resolution; + + pm.tx = -1 - (sourceFrame.x * pm.a); + pm.ty = 1 - (sourceFrame.y * pm.d); + } + } + + /** + * Sets the transform of the active render target to the given matrix + * + * @param {PIXI.Matrix} matrix - The transformation matrix + */ + setTransform()// matrix) + { + // this._activeRenderTarget.transform = matrix; + } +} diff --git a/packages/core/src/renderTexture/BaseRenderTexture.js b/packages/core/src/renderTexture/BaseRenderTexture.js new file mode 100644 index 0000000..88eb83b --- /dev/null +++ b/packages/core/src/renderTexture/BaseRenderTexture.js @@ -0,0 +1,137 @@ +import BaseTexture from '../textures/BaseTexture'; +import FrameBuffer from '../framebuffer/FrameBuffer'; + +/** + * A BaseRenderTexture is a special texture that allows any PixiJS display object to be rendered to it. + * + * __Hint__: All DisplayObjects (i.e. Sprites) that render to a BaseRenderTexture should be preloaded + * otherwise black rectangles will be drawn instead. + * + * A BaseRenderTexture takes a snapshot of any Display Object given to its render method. The position + * and rotation of the given Display Objects is ignored. For example: + * + * ```js + * let renderer = PIXI.autoDetectRenderer(1024, 1024, { view: canvas, ratio: 1 }); + * let baseRenderTexture = new PIXI.BaseRenderTexture(renderer, 800, 600); + * let sprite = PIXI.Sprite.fromImage("spinObj_01.png"); + * + * sprite.position.x = 800/2; + * sprite.position.y = 600/2; + * sprite.anchor.x = 0.5; + * sprite.anchor.y = 0.5; + * + * baseRenderTexture.render(sprite); + * ``` + * + * The Sprite in this case will be rendered using its local transform. To render this sprite at 0,0 + * you can clear the transform + * + * ```js + * + * sprite.setTransform() + * + * let baseRenderTexture = new PIXI.BaseRenderTexture(100, 100); + * let renderTexture = new PIXI.RenderTexture(baseRenderTexture); + * + * renderer.render(sprite, renderTexture); // Renders to center of RenderTexture + * ``` + * + * @class + * @extends PIXI.BaseTexture + * @memberof PIXI + */ +export default class BaseRenderTexture extends BaseTexture +{ + /** + * @param {object} [options] + * @param {number} [options.width=100] - The width of the base render texture + * @param {number} [options.height=100] - The height of the base render texture + * @param {PIXI.SCALE_MODES} [options.scaleMode] - See {@link PIXI.SCALE_MODES} for possible values + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the texture being generated + */ + constructor(options) + { + if (typeof options === 'number') + { + /* eslint-disable prefer-rest-params */ + // Backward compatibility of signature + const width = arguments[0]; + const height = arguments[1]; + const scaleMode = arguments[2]; + const resolution = arguments[3]; + + options = { width, height, scaleMode, resolution }; + /* eslint-enable prefer-rest-params */ + } + + super(null, options); + + const { width, height } = options || {}; + + // Set defaults + this.mipmap = false; + this.width = Math.ceil(width) || 100; + this.height = Math.ceil(height) || 100; + this.valid = true; + + /** + * A map of renderer IDs to webgl renderTargets + * + * @private + * @member {object} + */ + // this._glRenderTargets = {}; + + /** + * A reference to the canvas render target (we only need one as this can be shared across renderers) + * + * @private + * @member {object} + */ + this._canvasRenderTarget = null; + + this.clearColor = [0, 0, 0, 0]; + + this.frameBuffer = new FrameBuffer(width * this.resolution, height * this.resolution) + .addColorTexture(0, this); + + // TODO - could this be added the systems? + + /** + * The data structure for the stencil masks + * + * @member {PIXI.Graphics[]} + */ + this.stencilMaskStack = []; + + /** + * The data structure for the filters + * + * @member {PIXI.Graphics[]} + */ + this.filterStack = [{}]; + } + + /** + * Resizes the BaseRenderTexture. + * + * @param {number} width - The width to resize to. + * @param {number} height - The height to resize to. + */ + resize(width, height) + { + width = Math.ceil(width); + height = Math.ceil(height); + this.frameBuffer.resize(width * this.resolution, height * this.resolution); + } + + /** + * Destroys this texture + * + */ + destroy() + { + super.destroy(true); + this.renderer = null; + } +} diff --git a/packages/core/src/renderTexture/RenderTexture.js b/packages/core/src/renderTexture/RenderTexture.js new file mode 100644 index 0000000..f1ecf1f --- /dev/null +++ b/packages/core/src/renderTexture/RenderTexture.js @@ -0,0 +1,153 @@ +import BaseRenderTexture from './BaseRenderTexture'; +import Texture from '../textures/Texture'; + +/** + * A RenderTexture is a special texture that allows any PixiJS display object to be rendered to it. + * + * __Hint__: All DisplayObjects (i.e. Sprites) that render to a RenderTexture should be preloaded + * otherwise black rectangles will be drawn instead. + * + * A RenderTexture takes a snapshot of any Display Object given to its render method. For example: + * + * ```js + * let renderer = PIXI.autoDetectRenderer(1024, 1024, { view: canvas, ratio: 1 }); + * let renderTexture = PIXI.RenderTexture.create(800, 600); + * let sprite = PIXI.Sprite.fromImage("spinObj_01.png"); + * + * sprite.position.x = 800/2; + * sprite.position.y = 600/2; + * sprite.anchor.x = 0.5; + * sprite.anchor.y = 0.5; + * + * renderer.render(sprite, renderTexture); + * ``` + * + * The Sprite in this case will be rendered using its local transform. To render this sprite at 0,0 + * you can clear the transform + * + * ```js + * + * sprite.setTransform() + * + * let renderTexture = new PIXI.RenderTexture.create(100, 100); + * + * renderer.render(sprite, renderTexture); // Renders to center of RenderTexture + * ``` + * + * @class + * @extends PIXI.Texture + * @memberof PIXI + */ +export default class RenderTexture extends Texture +{ + /** + * @param {PIXI.BaseRenderTexture} baseRenderTexture - The renderer used for this RenderTexture + * @param {PIXI.Rectangle} [frame] - The rectangle frame of the texture to show + */ + constructor(baseRenderTexture, frame) + { + // support for legacy.. + let _legacyRenderer = null; + + if (!(baseRenderTexture instanceof BaseRenderTexture)) + { + /* eslint-disable prefer-rest-params, no-console */ + const width = arguments[1]; + const height = arguments[2]; + const scaleMode = arguments[3]; + const resolution = arguments[4]; + + // we have an old render texture.. + console.warn(`Please use RenderTexture.create(${width}, ${height}) instead of the ctor directly.`); + _legacyRenderer = arguments[0]; + /* eslint-enable prefer-rest-params, no-console */ + + frame = null; + baseRenderTexture = new BaseRenderTexture({ + width, + height, + scaleMode, + resolution, + }); + } + + /** + * The base texture object that this texture uses + * + * @member {BaseTexture} + */ + super(baseRenderTexture, frame); + + this.legacyRenderer = _legacyRenderer; + + /** + * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. + * + * @member {boolean} + */ + this.valid = true; + + /** + * FilterSystem temporary storage + * @private + * @member {PIXI.Rectangle} + */ + this.filterFrame = null; + + this._updateUvs(); + } + + /** + * Resizes the RenderTexture. + * + * @param {number} width - The width to resize to. + * @param {number} height - The height to resize to. + * @param {boolean} [resizeBaseTexture=true] - Should the baseTexture.width and height values be resized as well? + */ + resize(width, height, resizeBaseTexture = true) + { + width = Math.ceil(width); + height = Math.ceil(height); + + // TODO - could be not required.. + this.valid = (width > 0 && height > 0); + + this._frame.width = this.orig.width = width; + this._frame.height = this.orig.height = height; + + if (resizeBaseTexture) + { + this.baseTexture.resize(width, height); + } + + this._updateUvs(); + } + + /** + * A short hand way of creating a render texture. + * + * @param {object} [options] - Options + * @param {number} [options.width=100] - The width of the render texture + * @param {number} [options.height=100] - The height of the render texture + * @param {number} [options.scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the texture being generated + * @return {PIXI.RenderTexture} The new render texture + */ + static create(options) + { + // fallback, old-style: create(width, height, scaleMode, resolution) + if (typeof options === 'number') + { + /* eslint-disable prefer-rest-params */ + options = { + width: options, + height: arguments[1], + scaleMode: arguments[2], + resolution: arguments[3], + }; + /* eslint-enable prefer-rest-params */ + } + + return new RenderTexture(new BaseRenderTexture(options)); + } +} diff --git a/packages/core/src/renderTexture/RenderTextureSystem.js b/packages/core/src/renderTexture/RenderTextureSystem.js new file mode 100644 index 0000000..8d0ca61 --- /dev/null +++ b/packages/core/src/renderTexture/RenderTextureSystem.js @@ -0,0 +1,115 @@ +import System from '../System'; +import { Rectangle } from '@pixi/math'; + +const tempRect = new Rectangle(); + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class RenderTextureSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.clearColor = renderer._backgroundColorRgba; + + // TODO moe this property somewhere else! + this.defaultMaskStack = []; + this.defaultFilterStack = [{}]; + + // empty render texture? + this.renderTexture = null; + + this.destinationFrame = new Rectangle(); + } + + bind(renderTexture, sourceFrame, destinationFrame) + { + // TODO - do we want this?? + if (this.renderTexture === renderTexture) return; + this.renderTexture = renderTexture; + + const renderer = this.renderer; + + if (renderTexture) + { + const baseTexture = renderTexture.baseTexture; + + if (!destinationFrame) + { + tempRect.width = baseTexture.realWidth; + tempRect.height = baseTexture.realHeight; + + destinationFrame = tempRect; + } + + if (!sourceFrame) + { + sourceFrame = destinationFrame; + } + + this.renderer.framebuffer.bind(baseTexture.frameBuffer, destinationFrame); + + this.renderer.projection.update(destinationFrame, sourceFrame, baseTexture.resolution, false); + this.renderer.stencil.setMaskStack(baseTexture.stencilMaskStack); + } + else + { + // TODO these validation checks happen deeper down.. + // thing they can be avoided.. + if (!destinationFrame) + { + tempRect.width = renderer.width; + tempRect.height = renderer.height; + + destinationFrame = tempRect; + } + + if (!sourceFrame) + { + sourceFrame = destinationFrame; + } + + renderer.framebuffer.bind(null, destinationFrame); + + // TODO store this.. + this.renderer.projection.update(destinationFrame, sourceFrame, this.renderer.resolution, true); + this.renderer.stencil.setMaskStack(this.defaultMaskStack); + } + + this.destinationFrame.copyFrom(destinationFrame); + } + + /** + * Erases the render texture and fills the drawing area with a colour + * + * @param {number} [clearColor] - The colour + * @return {PIXI.Renderer} Returns itself. + */ + clear(clearColor) + { + if (this.renderTexture) + { + clearColor = clearColor || this.renderTexture.baseTexture.clearColor; + } + else + { + clearColor = clearColor || this.clearColor; + } + + this.renderer.framebuffer.clear(clearColor[0], clearColor[1], clearColor[2], clearColor[3]); + } + + resize()// screenWidth, screenHeight) + { + // resize the root only! + this.bind(null); + } +} diff --git a/packages/core/src/renderers/AbstractRenderer.js b/packages/core/src/renderers/AbstractRenderer.js deleted file mode 100644 index 928670f..0000000 --- a/packages/core/src/renderers/AbstractRenderer.js +++ /dev/null @@ -1,353 +0,0 @@ -import { hex2string, hex2rgb } from '@pixi/utils'; -import { Matrix, Rectangle } from '@pixi/math'; -import { RENDERER_TYPE } from '@pixi/constants'; -import { settings } from '@pixi/settings'; -import { Container } from '@pixi/display'; -import RenderTexture from '../textures/RenderTexture'; -import EventEmitter from 'eventemitter3'; - -const tempMatrix = new Matrix(); - -/** - * 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 EventEmitter - * @memberof PIXI - */ -export default class AbstractRenderer extends EventEmitter -{ - // eslint-disable-next-line valid-jsdoc - /** - * @param {string} system - The name of the system this renderer is for. - * @param {object} [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.transparent=false] - If the render view is transparent, default false - * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false - * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) - * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. The - * resolution of the renderer retina would be 2. - * @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 {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when rendering, - * stopping pixel interpolation. - */ - constructor(system, options, arg2, arg3) - { - super(); - - // Support for constructor(system, screenWidth, screenHeight, options) - if (typeof options === 'number') - { - options = Object.assign({ - width: options, - height: arg2 || settings.RENDER_OPTIONS.height, - }, arg3); - } - - // 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 = RENDERER_TYPE.UNKNOWN; - - /** - * Measurements of the screen. (0, 0, screenWidth, screenHeight) - * - * Its safe to use as filterArea or hitArea for 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 1 - */ - this.resolution = options.resolution || settings.RESOLUTION; - - /** - * Whether the render view is transparent - * - * @member {boolean} - */ - this.transparent = options.transparent; - - /** - * Whether css dimensions of canvas view should be resized to screen dimensions automatically - * - * @member {boolean} - */ - this.autoResize = options.autoResize || false; - - /** - * Tracks the blend modes useful for this renderer. - * - * @member {object} - */ - this.blendModes = null; - - /** - * 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; - - /** - * If true PixiJS will Math.floor() x/y values when rendering, stopping pixel interpolation. - * Handy for crisp pixel art and speed on legacy devices. - * - * @member {boolean} - */ - this.roundPixels = options.roundPixels; - - /** - * The background color as a number. - * - * @member {number} - * @private - */ - this._backgroundColor = 0x000000; - - /** - * The background color as an [R, G, B] array. - * - * @member {number[]} - * @private - */ - this._backgroundColorRgba = [0, 0, 0, 0]; - - /** - * The background color as a string. - * - * @member {string} - * @private - */ - this._backgroundColorString = '#000000'; - - this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter - - /** - * This temporary display object used as the parent of the currently being rendered item - * - * @member {PIXI.DisplayObject} - * @private - */ - this._tempDisplayObjectParent = new Container(); - - /** - * The last root object that the renderer tried to render. - * - * @member {PIXI.DisplayObject} - * @private - */ - this._lastObjectRendered = this._tempDisplayObjectParent; - - /** - * Collection of plugins. - * @readonly - * @member {object} - */ - this.plugins = {}; - } - - /** - * Initialize the plugins. - * - * @protected - * @param {object} staticMap - The dictionary of staticly saved plugins. - */ - initPlugins(staticMap) - { - 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() - { - return this.view.width; - } - - /** - * Same as view.height, actual number of pixels in the canvas by vertical - * - * @member {number} - * @readonly - * @default 600 - */ - get height() - { - return this.view.height; - } - - /** - * Resizes the screen and canvas to the specified width and height - * Canvas dimensions are multiplied by resolution - * - * @param {number} screenWidth - the new width of the screen - * @param {number} screenHeight - the new height of the screen - */ - resize(screenWidth, screenHeight) - { - this.screen.width = screenWidth; - this.screen.height = screenHeight; - - this.view.width = screenWidth * this.resolution; - this.view.height = screenHeight * this.resolution; - - if (this.autoResize) - { - this.view.style.width = `${screenWidth}px`; - this.view.style.height = `${screenHeight}px`; - } - } - - /** - * 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 {PIXI.DisplayObject} displayObject - The displayObject the object will be generated from - * @param {number} scaleMode - Should be one of the scaleMode consts - * @param {number} resolution - The resolution / device pixel ratio of the texture being generated - * @param {PIXI.Rectangle} [region] - The region of the displayObject, that shall be rendered, - * if no region is specified, defaults to the local bounds of the displayObject. - * @return {PIXI.Texture} a texture of the graphics object - */ - generateTexture(displayObject, scaleMode, resolution, region) - { - region = region || displayObject.getLocalBounds(); - - // 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(region.width | 0, region.height | 0, scaleMode, resolution); - - tempMatrix.tx = -region.x; - tempMatrix.ty = -region.y; - - this.render(displayObject, renderTexture, false, tempMatrix, true); - - return renderTexture; - } - - /** - * Removes everything from the renderer and optionally removes the Canvas DOM element. - * - * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. - */ - destroy(removeView) - { - 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); - } - - this.plugins = null; - - this.type = RENDERER_TYPE.UNKNOWN; - - this.view = null; - - this.screen = null; - - this.resolution = 0; - - this.transparent = false; - - this.autoResize = false; - - this.blendModes = null; - - this.options = null; - - this.preserveDrawingBuffer = false; - this.clearBeforeRender = false; - - this.roundPixels = false; - - this._backgroundColor = 0; - this._backgroundColorRgba = null; - this._backgroundColorString = null; - - this._tempDisplayObjectParent = null; - this._lastObjectRendered = null; - } - - /** - * The background color to fill if not transparent - * - * @member {number} - */ - get backgroundColor() - { - return this._backgroundColor; - } - - set backgroundColor(value) // eslint-disable-line require-jsdoc - { - this._backgroundColor = value; - this._backgroundColorString = hex2string(value); - hex2rgb(value, this._backgroundColorRgba); - } -} diff --git a/packages/core/src/renderers/Renderer.js b/packages/core/src/renderers/Renderer.js deleted file mode 100644 index a2020b1..0000000 --- a/packages/core/src/renderers/Renderer.js +++ /dev/null @@ -1,329 +0,0 @@ -import AbstractRenderer from './AbstractRenderer'; -import { sayHello } from '@pixi/utils'; -import MaskSystem from './systems/MaskSystem'; -import StencilSystem from './systems/StencilSystem'; -import FilterSystem from './systems/filter/FilterSystem'; -import FramebufferSystem from './systems/FramebufferSystem'; -import RenderTextureSystem from './systems/RenderTextureSystem'; -import TextureSystem from './systems/textures/TextureSystem'; -import ProjectionSystem from './systems/ProjectionSystem'; -import StateSystem from './systems/StateSystem'; -import GeometrySystem from './systems/geometry/GeometrySystem'; -import ShaderSystem from './systems/shader/ShaderSystem'; -import ContextSystem from './systems/ContextSystem'; -import BatchSystem from './systems/BatchSystem'; -import TextureGCSystem from './systems/textures/TextureGCSystem'; -import { RENDERER_TYPE } from '@pixi/constants'; -import UniformGroup from '../shader/UniformGroup'; -import { Matrix } from '@pixi/math'; -import Runner from 'mini-runner'; - -/** - * 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 Render works by automatically managing webGLBatchs. - * 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 :) - * - * @class - * @memberof PIXI - * @extends PIXI.AbstractRenderer - */ -export default class Renderer extends AbstractRenderer -{ - // eslint-disable-next-line valid-jsdoc - /** - * - * @param {object} [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.transparent=false] - If the render view is transparent, default false - * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false - * @param {boolean} [options.antialias=false] - sets antialias. If not available natively then FXAA - * antialiasing is used - * @param {boolean} [options.forceFXAA=false] - forces FXAA antialiasing to be used over native. - * FXAA is faster, but may not always look as great - * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. - * The resolution of the renderer retina would be 2. - * @param {boolean} [options.clearBeforeRender=true] - This sets if the renderer will clear - * the canvas or not before the new render pass. If you wish to set this to false, you *must* set - * preserveDrawingBuffer to `true`. - * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, - * enable this if you need to call toDataUrl on the webgl context. - * @param {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when - * rendering, stopping pixel interpolation. - * @param {number} [options.backgroundColor=0x000000] - The background color of the rendered area - * (shown if not transparent). - * @param {boolean} [options.legacy=false] - If true PixiJS will aim to ensure compatibility - * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. - * @param {string} [options.powerPreference] - Parameter passed to webgl context, set to "high-performance" - * for devices with dual graphics card - */ - constructor(options, arg2, arg3) - { - super('WebGL', options, arg2, arg3); - - /** - * The type of this renderer as a standardised const - * - * @member {number} - * @see PIXI.RENDERER_TYPE - */ - this.type = RENDERER_TYPE.WEBGL; - - // this will be set by the contextSystem (this.context) - this.gl = null; - this.CONTEXT_UID = 0; - this.legacy = !!options.legacy; - - // TODO legacy! - - // runners! - this.runners = { - destroy: new Runner('destroy'), - contextChange: new Runner('contextChange', 1), - reset: new Runner('reset'), - update: new Runner('update'), - postrender: new Runner('postrender'), - prerender: new Runner('prerender'), - resize: new Runner('resize', 2), - }; - - 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(GeometrySystem, 'geometry') - .addSystem(FramebufferSystem, 'framebuffer') - .addSystem(StencilSystem, 'stencil') - .addSystem(ProjectionSystem, 'projection') - .addSystem(TextureGCSystem, 'textureGC') - .addSystem(FilterSystem, 'filter') - .addSystem(RenderTextureSystem, 'renderTexture') - .addSystem(BatchSystem, 'batch'); - - this.initPlugins(Renderer.__plugins); - - /** - * The options passed in to create a new webgl context. - * - * @member {object} - * @private - */ - if (options.context) - { - this.context.initFromContext(options.context); - } - else - { - this.context.initFromOptions({ - alpha: this.transparent, - antialias: options.antialias, - premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', - stencil: true, - 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); - } - - addSystem(_class, name) - { - if (!name) - { - name = _class.name; - } - - // TODO - read name from class.name.. - - /* - if(name.includes('System')) - { - name = name.replace('System', ''); - name = name.charAt(0).toLowerCase() + name.slice(1); - } - */ - - const system = new _class(this); - - if (this[name]) - { - throw new Error(`Whoops! ${name} is already a manger`); - } - - this[name] = system; - - for (const i in this.runners) - { - this.runners[i].add(system); - } - - return this; - - /** - * 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. - */ - } - - /** - * Renders the object to its webGL view - * - * @param {PIXI.DisplayObject} displayObject - the object to be rendered - * @param {PIXI.RenderTexture} renderTexture - The render texture to render to. - * @param {boolean} [clear] - Should the canvas be cleared before the new render - * @param {PIXI.Transform} [transform] - A transform to apply to the render texture before rendering. - * @param {boolean} [skipUpdateTransform] - Should we skip the update transform pass? - */ - render(displayObject, renderTexture, clear, transform, skipUpdateTransform) - { - // can be handy to know! - this.renderingToScreen = !renderTexture; - - this.runners.prerender.run(); - this.emit('prerender'); - - // 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.parent; - - displayObject.parent = this._tempDisplayObjectParent; - displayObject.updateTransform(); - displayObject.parent = 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.run(); - - this.emit('postrender'); - } - - /** - * Resizes the webGL view to the specified width and height. - * - * @param {number} screenWidth - the new width of the screen - * @param {number} screenHeight - the new height of the screen - */ - resize(screenWidth, screenHeight) - { - AbstractRenderer.prototype.resize.call(this, screenWidth, screenHeight); - - this.runners.resize.run(screenWidth, screenHeight); - } - - /** - * Resets the WebGL state so you can render things however you fancy! - * - * @return {PIXI.Renderer} Returns itself. - */ - reset() - { - this.runners.reset.run(); - - return this; - } - - clear() - { - this.framebuffer.bind(); - this.framebuffer.clear(); - } - - /** - * Removes everything from the renderer (event listeners, spritebatch, etc...) - * - * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. - * See: https://github.com/pixijs/pixi.js/issues/2233 - */ - destroy(removeView) - { - this.runners.destroy.run(); - - // call base destroy - super.destroy(removeView); - - // TODO nullify all the managers.. - this.gl = null; - } - - /** - * 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 - * builds and excluding plugins. - * @name PIXI.Renderer#plugins - * @type {object} - * @readonly - * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. - * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. - * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. - * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. - */ - - /** - * Adds a plugin to the renderer. - * - * @method - * @param {string} pluginName - The name of the plugin. - * @param {Function} ctor - The constructor function or class for the plugin. - */ - static registerPlugin(pluginName, ctor) - { - Renderer.__plugins = Renderer.__plugins || {}; - Renderer.__plugins[pluginName] = ctor; - } -} diff --git a/packages/core/src/renderers/State.js b/packages/core/src/renderers/State.js deleted file mode 100644 index fa3f221..0000000 --- a/packages/core/src/renderers/State.js +++ /dev/null @@ -1,163 +0,0 @@ -/* eslint-disable max-len */ - -const BLEND = 0; -const OFFSET = 1; -const CULLING = 2; -const DEPTH_TEST = 3; -const WINDING = 4; - -/** - * This is a webGL state. It is passed The WebGL StateManager. - * Each mesh renderered may require webGL to be in a different state. - * For example you may want different blend mode or to enable polygon offsets - * - * @class - * @memberof PIXI - */ -export default class State -{ - /** - * - */ - constructor() - { - this.data = 0; - - this.blendMode = 0; - this.polygonOffset = 0; - - this.blend = true; - // this.depthTest = true; - } - - /** - * Activates blending of the computed fragment color values - * - * @member {boolean} - */ - get blend() - { - return !!(this.data & (1 << BLEND)); - } - - set blend(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << BLEND)) !== value) - { - this.data ^= (1 << BLEND); - } - } - - /** - * Activates adding an offset to depth values of polygon's fragments - * - * @member {boolean} - * @default false - */ - get offsets() - { - return !!(this.data & (1 << OFFSET)); - } - - set offsets(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << OFFSET)) !== value) - { - this.data ^= (1 << OFFSET); - } - } - - /** - * Activates culling of polygons. - * - * @member {boolean} - * @default false - */ - get culling() - { - return !!(this.data & (1 << CULLING)); - } - - set culling(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << CULLING)) !== value) - { - this.data ^= (1 << CULLING); - } - } - - /** - * Activates depth comparisons and updates to the depth buffer. - * - * @member {boolean} - * @default false - */ - get depthTest() - { - return !!(this.data & (1 << DEPTH_TEST)); - } - - set depthTest(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << DEPTH_TEST)) !== value) - { - this.data ^= (1 << DEPTH_TEST); - } - } - - /** - * Specifies whether or not front or back-facing polygons can be culled. - * @member {boolean} - * @default false - */ - get clockwiseFrontFace() - { - return !!(this.data & (1 << WINDING)); - } - - set clockwiseFrontFace(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << WINDING)) !== value) - { - this.data ^= (1 << WINDING); - } - } - - /** - * The blend mode to be applied when this state is set. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. - * Setting this mode to anything other than NO_BLEND will automatically switch blending on. - * - * @member {boolean} - * @default PIXI.BLEND_MODES.NORMAL - * @see PIXI.BLEND_MODES - */ - get blendMode() - { - return this._blendMode; - } - - set blendMode(value) // eslint-disable-line require-jsdoc - { - // 17 is NO BLEND - this.blend = (value !== 17); - this._blendMode = value; - } - - /** - * The polygon offset. Setting this property to anything other than 0 will automatically enable poygon offset fill. - * - * @member {number} - * @default 0 - */ - get polygonOffset() - { - return this._polygonOffset; - } - - set polygonOffset(value) // eslint-disable-line require-jsdoc - { - this.offsets = !!value; - this._polygonOffset = value; - } -} - diff --git a/packages/core/src/renderers/filters/Filter.js b/packages/core/src/renderers/filters/Filter.js deleted file mode 100644 index 2073b8d..0000000 --- a/packages/core/src/renderers/filters/Filter.js +++ /dev/null @@ -1,140 +0,0 @@ -import Shader from '../../shader/Shader'; -import Program from '../../shader/Program'; -import State from '../State'; -import { settings } from '@pixi/settings'; -// import extractUniformsFromSrc from './extractUniformsFromSrc'; -import defaultVertex from './defaultFilter.vert'; -import defaultFragment from './defaultFilter.frag'; - -// let math = require('../../math'); -/** - * @class - * @memberof PIXI - * @extends PIXI.Shader - */ -export default class Filter extends Shader -{ - /** - * @param {string} [vertexSrc] - The source of the vertex shader. - * @param {string} [fragmentSrc] - The source of the fragment shader. - * @param {object} [uniforms] - Custom uniforms to use to augment the built-in ones. - */ - constructor(vertexSrc, fragmentSrc, uniforms) - { - const program = Program.from(vertexSrc, fragmentSrc); - - super(program, uniforms); - - /** - * The padding of the filter. Some filters require extra space to breath such as a blur. - * Increasing this will add extra width and height to the bounds of the object that the - * filter is applied to. - * - * @member {number} - */ - this.padding = 0; - - /** - * The resolution of the filter. Setting this to be lower will lower the quality but - * increase the performance of the filter. - * - * @member {number} - */ - this.resolution = settings.RESOLUTION; - - /** - * If enabled is true the filter is applied, if false it will not. - * - * @member {boolean} - */ - this.enabled = true; - - /** - * If enabled, PixiJS will fit the filter area into boundaries for better performance. - * Switch it off if it does not work for specific shader. - * - * @member {boolean} - */ - this.autoFit = true; - - /** - * Legacy filters use position and uvs from attributes - * @member {boolean} - * @readonly - */ - this.legacy = !!this.program.attributeData.aTextureCoord; - - /** - * the webGL state the filter requires to render - * @member {PIXI.State} - */ - this.state = new State(); - } - - /** - * Applies the filter - * - * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from - * @param {PIXI.RenderTarget} input - The input render target. - * @param {PIXI.RenderTarget} output - The target to output to. - * @param {boolean} clear - Should the output be cleared before rendering to it - * @param {object} [currentState] - It's current state of filter. - * There are some useful properties in the currentState : - * target, filters, sourceFrame, destinationFrame, renderTarget, resolution - */ - apply(filterManager, input, output, clear, currentState, derp) // eslint-disable-line no-unused-vars - { - // do as you please! - - filterManager.applyFilter(this, input, output, clear, currentState, derp); - - // or just do a regular render.. - } - - /** - * Sets the blendmode of the filter - * - * @member {number} - * @default PIXI.BLEND_MODES.NORMAL - */ - get blendMode() - { - return this.state.blendMode; - } - - set blendMode(value) // eslint-disable-line require-jsdoc - { - this.state.blendMode = value; - } - - /** - * The default vertex shader source - * - * @static - * @constant - */ - static get defaultVertexSrc() - { - return defaultVertex; - } - - /** - * The default fragment shader source - * - * @static - * @constant - */ - static get defaultFragmentSrc() - { - return defaultFragment; - } -} - -/** - * Used for caching shader IDs - * - * @static - * @private - */ -Filter.SOURCE_KEY_MAP = {}; - diff --git a/packages/core/src/renderers/filters/defaultFilter.frag b/packages/core/src/renderers/filters/defaultFilter.frag deleted file mode 100644 index a14c1be..0000000 --- a/packages/core/src/renderers/filters/defaultFilter.frag +++ /dev/null @@ -1,22 +0,0 @@ -varying vec2 vTextureCoord; -varying vec2 vFilterCoord; - -uniform sampler2D uSampler; -uniform sampler2D filterSampler; - -void main(void){ - vec4 masky = texture2D(filterSampler, vFilterCoord); - vec4 sample = texture2D(uSampler, vTextureCoord); - vec4 color; - if(mod(vFilterCoord.x, 1.0) > 0.5) - { - color = vec4(1.0, 0.0, 0.0, 1.0); - } - else - { - color = vec4(0.0, 1.0, 0.0, 1.0); - } - // gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0); - gl_FragColor = mix(sample, masky, 0.5); - gl_FragColor *= sample.a; -} diff --git a/packages/core/src/renderers/filters/defaultFilter.vert b/packages/core/src/renderers/filters/defaultFilter.vert deleted file mode 100644 index e1febca..0000000 --- a/packages/core/src/renderers/filters/defaultFilter.vert +++ /dev/null @@ -1,14 +0,0 @@ -attribute vec2 aVertexPosition; -attribute vec2 aTextureCoord; - -uniform mat3 projectionMatrix; -uniform mat3 filterMatrix; - -varying vec2 vTextureCoord; -varying vec2 vFilterCoord; - -void main(void){ - gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); - vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy; - vTextureCoord = aTextureCoord ; -} diff --git a/packages/core/src/renderers/filters/filterTransforms.js b/packages/core/src/renderers/filters/filterTransforms.js deleted file mode 100644 index 87a3cd6..0000000 --- a/packages/core/src/renderers/filters/filterTransforms.js +++ /dev/null @@ -1,50 +0,0 @@ -import { Matrix } from '@pixi/math'; - -/** - * Calculates the mapped matrix - * @param filterArea {Rectangle} The filter area - * @param sprite {Sprite} the target sprite - * @param outputMatrix {Matrix} @alvin - * @private - */ -// this returns a matrix that will normalise map filter cords in the filter to screen space -export function calculateScreenSpaceMatrix(outputMatrix, filterArea, textureSize) -{ - // TODO unwrap? - const mappedMatrix = outputMatrix.identity(); - - mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); - - mappedMatrix.scale(textureSize.width, textureSize.height); - - return mappedMatrix; -} - -export function calculateNormalizedScreenSpaceMatrix(outputMatrix, filterArea, textureSize) -{ - const mappedMatrix = outputMatrix.identity(); - - mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); - - const translateScaleX = (textureSize.width / filterArea.width); - const translateScaleY = (textureSize.height / filterArea.height); - - mappedMatrix.scale(translateScaleX, translateScaleY); - - return mappedMatrix; -} - -// this will map the filter coord so that a texture can be used based on the transform of a sprite -export function calculateSpriteMatrix(outputMatrix, filterArea, textureSize, sprite) -{ - const orig = sprite._texture.orig; - const mappedMatrix = outputMatrix.set(textureSize.width, 0, 0, textureSize.height, filterArea.x, filterArea.y); - const worldTransform = sprite.worldTransform.copyTo(Matrix.TEMP_MATRIX); - - worldTransform.invert(); - mappedMatrix.prepend(worldTransform); - mappedMatrix.scale(1.0 / orig.width, 1.0 / orig.height); - mappedMatrix.translate(sprite.anchor.x, sprite.anchor.y); - - return mappedMatrix; -} diff --git a/packages/core/src/renderers/filters/spriteMask/SpriteMaskFilter.js b/packages/core/src/renderers/filters/spriteMask/SpriteMaskFilter.js deleted file mode 100644 index d5a91a4..0000000 --- a/packages/core/src/renderers/filters/spriteMask/SpriteMaskFilter.js +++ /dev/null @@ -1,63 +0,0 @@ -import Filter from '../Filter'; -import { Matrix } from '@pixi/math'; -import vertex from './spriteMaskFilter.vert'; -import fragment from './spriteMaskFilter.frag'; -import { default as TextureMatrix } from '../../../textures/TextureMatrix'; - -/** - * The SpriteMaskFilter class - * - * @class - * @extends PIXI.Filter - * @memberof PIXI - */ -export default class SpriteMaskFilter extends Filter -{ - /** - * @param {PIXI.Sprite} sprite - the target sprite - */ - constructor(sprite) - { - const maskMatrix = new Matrix(); - - super(vertex, fragment); - - sprite.renderable = false; - - this.maskSprite = sprite; - this.maskMatrix = maskMatrix; - } - - /** - * Applies the filter - * - * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from - * @param {PIXI.RenderTarget} input - The input render target. - * @param {PIXI.RenderTarget} output - The target to output to. - */ - apply(filterManager, input, output) - { - const maskSprite = this.maskSprite; - const tex = this.maskSprite.texture; - - if (!tex.valid) - { - return; - } - if (!tex.transform) - { - // margin = 0.0, let it bleed a bit, shader code becomes easier - // assuming that atlas textures were made with 1-pixel padding - tex.transform = new TextureMatrix(tex, 0.0); - } - tex.transform.update(); - - this.uniforms.mask = tex; - this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite) - .prepend(tex.transform.mapCoord); - this.uniforms.alpha = maskSprite.worldAlpha; - this.uniforms.maskClamp = tex.transform.uClampFrame; - - filterManager.applyFilter(this, input, output); - } -} diff --git a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.frag b/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.frag deleted file mode 100644 index 0e4aef8..0000000 --- a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.frag +++ /dev/null @@ -1,23 +0,0 @@ -varying vec2 vMaskCoord; -varying vec2 vTextureCoord; - -uniform sampler2D uSampler; -uniform sampler2D mask; -uniform float alpha; -uniform vec4 maskClamp; - -void main(void) -{ - float clip = step(3.5, - step(maskClamp.x, vMaskCoord.x) + - step(maskClamp.y, vMaskCoord.y) + - step(vMaskCoord.x, maskClamp.z) + - step(vMaskCoord.y, maskClamp.w)); - - vec4 original = texture2D(uSampler, vTextureCoord); - vec4 masky = texture2D(mask, vMaskCoord); - - original *= (masky.r * masky.a * alpha * clip); - - gl_FragColor = original; -} diff --git a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.vert b/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.vert deleted file mode 100644 index d23a30c..0000000 --- a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.vert +++ /dev/null @@ -1,16 +0,0 @@ -attribute vec2 aVertexPosition; -attribute vec2 aTextureCoord; - -uniform mat3 projectionMatrix; -uniform mat3 otherMatrix; - -varying vec2 vMaskCoord; -varying vec2 vTextureCoord; - -void main(void) -{ - gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); - - vTextureCoord = aTextureCoord; - vMaskCoord = ( otherMatrix * vec3( aTextureCoord, 1.0) ).xy; -} diff --git a/packages/core/src/renderers/systems/BatchSystem.js b/packages/core/src/renderers/systems/BatchSystem.js deleted file mode 100644 index 70e9518..0000000 --- a/packages/core/src/renderers/systems/BatchSystem.js +++ /dev/null @@ -1,68 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import ObjectRenderer from '../utils/ObjectRenderer'; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ - -export default class BatchSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this manager works for. - */ - constructor(renderer) - { - super(renderer); - - /** - * An empty renderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.emptyRenderer = new ObjectRenderer(renderer); - - /** - * The currently active ObjectRenderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.currentRenderer = this.emptyRenderer; - } - - /** - * Changes the current renderer to the one given in parameter - * - * @param {PIXI.ObjectRenderer} objectRenderer - The object renderer to use. - */ - setObjectRenderer(objectRenderer) - { - if (this.currentRenderer === objectRenderer) - { - return; - } - - this.currentRenderer.stop(); - this.currentRenderer = objectRenderer; - - this.renderer.state.setState(objectRenderer.state); - - this.currentRenderer.start(); - } - - /** - * This should be called if you wish to do some custom rendering - * It will basically render anything that may be batched up such as sprites - * - */ - flush() - { - this.setObjectRenderer(this.emptyRenderer); - } - - reset() - { - this.setObjectRenderer(this.emptyRenderer); - } -} diff --git a/packages/core/src/renderers/systems/ContextSystem.js b/packages/core/src/renderers/systems/ContextSystem.js deleted file mode 100644 index 43bf827..0000000 --- a/packages/core/src/renderers/systems/ContextSystem.js +++ /dev/null @@ -1,186 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { settings } from '@pixi/settings'; - -let CONTEXT_UID = 0; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class ContextSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this.webGLVersion = 1; - - this.handleContextLost = this.handleContextLost.bind(this); - this.handleContextRestored = this.handleContextRestored.bind(this); - - this.extensions = {}; - - renderer.view.addEventListener('webglcontextlost', this.handleContextLost, false); - renderer.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); - } - - get isLost() - { - return (!this.gl || this.gl.isContextLost()); - } - - contextChange(gl) - { - this.gl = gl; - - // restore a context if it was previously lost - if (gl.isContextLost() && gl.getExtension('WEBGL_lose_context')) - { - gl.getExtension('WEBGL_lose_context').restoreContext(); - } - } - - initFromContext(gl) - { - this.gl = gl; - this.validateContext(gl); - this.renderer.gl = gl; - this.renderer.CONTEXT_UID = CONTEXT_UID++; - this.renderer.runners.contextChange.run(gl); - } - - initFromOptions(options) - { - const gl = this.createContext(this.renderer.view, options); - - this.initFromContext(gl); - } - - /** - * Helper class to create a webGL Context - * - * @class - * @memberof PIXI.glCore - * @param canvas {HTMLCanvasElement} the canvas element that we will get the context from - * @param options {Object} An options object that gets passed in to the canvas element containing the context attributes, - * see https://developer.mozilla.org/en/docs/Web/API/HTMLCanvasElement/getContext for the options available - * @return {WebGLRenderingContext} the WebGL context - */ - createContext(canvas, options) - { - let gl; - - if (settings.PREFER_WEBGL_2) - { - gl = canvas.getContext('webgl2', options); - } - - if (gl) - { - this.webGLVersion = 2; - } - else - { - this.webGLVersion = 1; - - gl = canvas.getContext('webgl', options) - || canvas.getContext('experimental-webgl', options); - - if (!gl) - { - // fail, not able to get a context - throw new Error('This browser does not support webGL. Try using the canvas renderer'); - } - } - - this.gl = gl; - - this.getExtensions(); - - return gl; - } - - getExtensions() - { - // time to set up default etensions that pixi uses.. - const gl = this.gl; - const extensions = this.extensions; - - if (this.webGLVersion === 1) - { - extensions.drawBuffers = gl.getExtension('WEBGL_draw_buffers'); - extensions.depthTexture = gl.getExtension('WEBKIT_WEBGL_depth_texture'); - extensions.floatTexture = gl.getExtension('OES_texture_float'); - extensions.loseContext = gl.getExtension('WEBGL_lose_context'); - - extensions.vertexArrayObject = gl.getExtension('OES_vertex_array_object') - || gl.getExtension('MOZ_OES_vertex_array_object') - || gl.getExtension('WEBKIT_OES_vertex_array_object'); - } - - // we don't use any specific WebGL 2 ones yet! - } - - /** - * Handles a lost webgl context - * - * @private - * @param {WebGLContextEvent} event - The context lost event. - */ - handleContextLost(event) - { - event.preventDefault(); - } - - /** - * Handles a restored webgl context - * - * @private - */ - handleContextRestored() - { - this.renderer.runners.contextChange.run(this.gl); - } - - destroy() - { - const view = this.renderer.view; - - // remove listeners - view.removeEventListener('webglcontextlost', this.handleContextLost); - view.removeEventListener('webglcontextrestored', this.handleContextRestored); - - this.gl.useProgram(null); - - if (this.extensions.loseContext) - { - this.extensions.loseContext.loseContext(); - } - } - - postrender() - { - this.gl.flush(); - } - - validateContext(gl) - { - const attributes = gl.getContextAttributes(); - - // this is going to be fairly simple for now.. but at least we have room to grow! - if (!attributes.stencil) - { - /* eslint-disable max-len */ - - /* eslint-disable no-console */ - console.warn('Provided WebGL context does not have a stencil buffer, masks may not render correctly'); - /* eslint-enable no-console */ - - /* eslint-enable max-len */ - } - } -} diff --git a/packages/core/src/renderers/systems/FramebufferSystem.js b/packages/core/src/renderers/systems/FramebufferSystem.js deleted file mode 100644 index df7902b..0000000 --- a/packages/core/src/renderers/systems/FramebufferSystem.js +++ /dev/null @@ -1,246 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { Rectangle } from '@pixi/math'; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class FramebufferSystem extends WebGLSystem -{ - /** - * Sets up the renderer context and necessary buffers. - * - * @private - */ - contextChange() - { - this.gl = this.renderer.gl; - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - this.current = null; - this.viewport = new Rectangle(); - - this.drawBufferExtension = this.renderer.context.extensions.drawBuffers; - } - - bind(framebuffer, frame) - { - const gl = this.gl; - - this.current = framebuffer; - - if (framebuffer) - { - // TODO cacheing layer! - - const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID] || this.initFramebuffer(framebuffer); - - gl.bindFramebuffer(gl.FRAMEBUFFER, fbo.framebuffer); - // makesure all textures are unbound.. - - // now check for updates... - if (fbo.dirtyId !== framebuffer.dirtyId) - { - fbo.dirtyId = framebuffer.dirtyId; - - if (fbo.dirtyFormat !== framebuffer.dirtyFormat) - { - fbo.dirtyFormat = framebuffer.dirtyFormat; - this.updateFramebuffer(framebuffer); - } - else if (fbo.dirtySize !== framebuffer.dirtySize) - { - fbo.dirtySize = framebuffer.dirtySize; - this.resizeFramebuffer(framebuffer); - } - } - - for (let i = 0; i < framebuffer.colorTextures.length; i++) - { - if (framebuffer.colorTextures[i].texturePart) - { - this.renderer.texture.unbind(framebuffer.colorTextures[i].texture); - } - else - { - this.renderer.texture.unbind(framebuffer.colorTextures[i]); - } - } - - if (framebuffer.depthTexture) - { - this.renderer.texture.unbind(framebuffer.depthTexture); - } - - if (frame) - { - this.setViewport(frame.x, frame.y, frame.width, frame.height); - } - else - { - this.setViewport(0, 0, framebuffer.width, framebuffer.height); - } - } - else - { - gl.bindFramebuffer(gl.FRAMEBUFFER, null); - - if (frame) - { - this.setViewport(frame.x, frame.y, frame.width, frame.height); - } - else - { - this.setViewport(0, 0, this.renderer.width, this.renderer.height); - } - } - } - - setViewport(x, y, width, height) - { - const v = this.viewport; - - if (v.width !== width || v.height !== height || v.x !== x || v.y !== y) - { - v.x = x; - v.y = y; - v.width = width; - v.height = height; - - this.gl.viewport(x, y, width, height); - } - } - - get size() - { - if (this.current) - { - // TODO store temp - return { x: 0, y: 0, width: this.current.width, height: this.current.height }; - } - - return { x: 0, y: 0, width: this.renderer.width, height: this.renderer.height }; - } - - clear(r, g, b, a) - { - const gl = this.gl; - - // TODO clear color can be set only one right? - gl.clearColor(r, g, b, a); - gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); - } - - // private functions... - - initFramebuffer(framebuffer) - { - const gl = this.gl; - - // TODO - make this a class? - const fbo = { - framebuffer: gl.createFramebuffer(), - stencil: null, - dirtyId: 0, - dirtyFormat: 0, - dirtySize: 0, - }; - - framebuffer.glFrameBuffers[this.CONTEXT_UID] = fbo; - - return fbo; - } - - resizeFramebuffer(framebuffer) - { - const gl = this.gl; - - if (framebuffer.stencil || framebuffer.depth) - { - gl.bindRenderbuffer(gl.RENDERBUFFER, this.stencil); - gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); - } - } - - updateFramebuffer(framebuffer) - { - const gl = this.gl; - - const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID]; - - // bind the color texture - const colorTextures = framebuffer.colorTextures; - - let count = colorTextures.length; - - if (!this.drawBufferExtension) - { - count = Math.min(count, 1); - } - - const activeTextures = []; - - for (let i = 0; i < count; i++) - { - const texture = framebuffer.colorTextures[i]; - - if (texture.texturePart) - { - this.renderer.texture.bind(texture.texture, 0); - - gl.framebufferTexture2D(gl.FRAMEBUFFER, - gl.COLOR_ATTACHMENT0 + i, - gl.TEXTURE_CUBE_MAP_NEGATIVE_X + texture.side, - texture.texture._glTextures[this.CONTEXT_UID].texture, - 0); - } - else - { - this.renderer.texture.bind(texture, 0); - - gl.framebufferTexture2D(gl.FRAMEBUFFER, - gl.COLOR_ATTACHMENT0 + i, - gl.TEXTURE_2D, - texture._glTextures[this.CONTEXT_UID].texture, - 0); - } - - activeTextures.push(gl.COLOR_ATTACHMENT0 + i); - } - - if (this.drawBufferExtension && activeTextures.length > 1) - { - this.drawBufferExtension.drawBuffersWEBGL(activeTextures); - } - - if (framebuffer.depthTexture) - { - const depthTextureExt = this.renderer.context.extensions.depthTexture; - - if (depthTextureExt) - { - const depthTexture = framebuffer.depthTexture; - - this.renderer.texture.bind(depthTexture, 0); - - gl.framebufferTexture2D(gl.FRAMEBUFFER, - gl.DEPTH_ATTACHMENT, - gl.TEXTURE_2D, - depthTexture._glTextures[this.CONTEXT_UID].texture, - 0); - } - } - - if (framebuffer.stencil || framebuffer.depth) - { - fbo.stencil = gl.createRenderbuffer(); - - gl.bindRenderbuffer(gl.RENDERBUFFER, fbo.stencil); - - // TODO.. this is depth AND stencil? - gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, fbo.stencil); - gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); - // fbo.enableStencil(); - } - } -} diff --git a/packages/core/src/renderers/systems/MaskSystem.js b/packages/core/src/renderers/systems/MaskSystem.js deleted file mode 100644 index d9c4b1a..0000000 --- a/packages/core/src/renderers/systems/MaskSystem.js +++ /dev/null @@ -1,199 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import AlphaMaskFilter from '../filters/spriteMask/SpriteMaskFilter'; - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class MaskSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - // TODO - we don't need both! - this.scissor = false; - this.scissorData = null; - this.scissorRenderTarget = null; - - this.enableScissor = false; - - this.alphaMaskPool = []; - this.alphaMaskIndex = 0; - } - - /** - * Applies the Mask and adds it to the current filter stack. - * - * @param {PIXI.DisplayObject} target - Display Object to push the mask to - * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. - */ - push(target, maskData) - { - // TODO the root check means scissor rect will not - // be used on render textures more info here: - // https://github.com/pixijs/pixi.js/pull/3545 - - if (maskData.texture) - { - this.pushSpriteMask(target, maskData); - } - else if (this.enableScissor - && !this.scissor - && this.renderer._activeRenderTarget.root - && !this.renderer.stencil.stencilMaskStack.length - && maskData.isFastRect()) - { - const matrix = maskData.worldTransform; - - let rot = Math.atan2(matrix.b, matrix.a); - - // use the nearest degree! - rot = Math.round(rot * (180 / Math.PI)); - - if (rot % 90) - { - this.pushStencilMask(maskData); - } - else - { - this.pushScissorMask(target, maskData); - } - } - else - { - this.pushStencilMask(maskData); - } - } - - /** - * Removes the last mask from the mask stack and doesn't return it. - * - * @param {PIXI.DisplayObject} target - Display Object to pop the mask from - * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. - */ - pop(target, maskData) - { - if (maskData.texture) - { - this.popSpriteMask(target, maskData); - } - else if (this.enableScissor && !this.renderer.stencil.stencilMaskStack.length) - { - this.popScissorMask(target, maskData); - } - else - { - this.popStencilMask(target, maskData); - } - } - - /** - * Applies the Mask and adds it to the current filter stack. - * - * @param {PIXI.RenderTarget} target - Display Object to push the sprite mask to - * @param {PIXI.Sprite} maskData - Sprite to be used as the mask - */ - pushSpriteMask(target, maskData) - { - let alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; - - if (!alphaMaskFilter) - { - alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; - } - - alphaMaskFilter[0].resolution = this.renderer.resolution; - alphaMaskFilter[0].maskSprite = maskData; - - // TODO - may cause issues! - target.filterArea = maskData.getBounds(true); - - this.renderer.filterSystem.pushFilter(target, alphaMaskFilter); - - this.alphaMaskIndex++; - } - - /** - * Removes the last filter from the filter stack and doesn't return it. - * - */ - popSpriteMask() - { - this.renderer.filterSystem.popFilter(); - this.alphaMaskIndex--; - } - - /** - * Applies the Mask and adds it to the current filter stack. - * - * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. - */ - pushStencilMask(maskData) - { - this.renderer.batch.flush(); - this.renderer.stencil.pushStencil(maskData); - } - - /** - * Removes the last filter from the filter stack and doesn't return it. - * - */ - popStencilMask() - { - // this.renderer.currentRenderer.stop(); - this.renderer.stencil.popStencil(); - } - - /** - * - * @param {PIXI.DisplayObject} target - Display Object to push the mask to - * @param {PIXI.Graphics} maskData - The masking data. - */ - pushScissorMask(target, maskData) - { - maskData.renderable = true; - - const renderTarget = this.renderer._activeRenderTarget; - - const bounds = maskData.getBounds(); - - bounds.fit(renderTarget.size); - maskData.renderable = false; - - this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); - - const resolution = this.renderer.resolution; - - this.renderer.gl.scissor( - bounds.x * resolution, - (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, - bounds.width * resolution, - bounds.height * resolution - ); - - this.scissorRenderTarget = renderTarget; - this.scissorData = maskData; - this.scissor = true; - } - - /** - * - * - */ - popScissorMask() - { - this.scissorRenderTarget = null; - this.scissorData = null; - this.scissor = false; - - // must be scissor! - const gl = this.renderer.gl; - - gl.disable(gl.SCISSOR_TEST); - } -} diff --git a/packages/core/src/renderers/systems/ProjectionSystem.js b/packages/core/src/renderers/systems/ProjectionSystem.js deleted file mode 100644 index ec5efdb..0000000 --- a/packages/core/src/renderers/systems/ProjectionSystem.js +++ /dev/null @@ -1,73 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { Matrix } from '@pixi/math'; - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ - -export default class ProjectionSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this.projectionMatrix = new Matrix(); - } - - update(destinationFrame, sourceFrame, resolution, root) - { - this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; - this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; - - this.calculateProjection(this.destinationFrame, this.sourceFrame, resolution, root); - - this.renderer.globalUniforms.uniforms.projectionMatrix = this.projectionMatrix; - this.renderer.globalUniforms.update(); - } - - /** - * Updates the projection matrix based on a projection frame (which is a rectangle) - * - * @param {Rectangle} destinationFrame - The destination frame. - * @param {Rectangle} sourceFrame - The source frame. - */ - calculateProjection(destinationFrame, sourceFrame, resolution, root) - { - const pm = this.projectionMatrix; - - // I don't think we will need this line.. - // pm.identity(); - - if (!root) - { - pm.a = (1 / destinationFrame.width * 2) * resolution; - pm.d = (1 / destinationFrame.height * 2) * resolution; - - pm.tx = -1 - (sourceFrame.x * pm.a); - pm.ty = -1 - (sourceFrame.y * pm.d); - } - else - { - pm.a = (1 / destinationFrame.width * 2) * resolution; - pm.d = (-1 / destinationFrame.height * 2) * resolution; - - pm.tx = -1 - (sourceFrame.x * pm.a); - pm.ty = 1 - (sourceFrame.y * pm.d); - } - } - - /** - * Sets the transform of the active render target to the given matrix - * - * @param {PIXI.Matrix} matrix - The transformation matrix - */ - setTransform()// matrix) - { - // this._activeRenderTarget.transform = matrix; - } -} diff --git a/packages/core/src/renderers/systems/RenderTextureSystem.js b/packages/core/src/renderers/systems/RenderTextureSystem.js deleted file mode 100644 index a133ef1..0000000 --- a/packages/core/src/renderers/systems/RenderTextureSystem.js +++ /dev/null @@ -1,115 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { Rectangle } from '@pixi/math'; - -const tempRect = new Rectangle(); - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ - -export default class RenderTextureSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this.clearColor = renderer._backgroundColorRgba; - - // TODO moe this property somewhere else! - this.defaultMaskStack = []; - this.defaultFilterStack = [{}]; - - // empty render texture? - this.renderTexture = null; - - this.destinationFrame = new Rectangle(); - } - - bind(renderTexture, sourceFrame, destinationFrame) - { - // TODO - do we want this?? - if (this.renderTexture === renderTexture) return; - this.renderTexture = renderTexture; - - const renderer = this.renderer; - - if (renderTexture) - { - const baseTexture = renderTexture.baseTexture; - - if (!destinationFrame) - { - tempRect.width = baseTexture.realWidth; - tempRect.height = baseTexture.realHeight; - - destinationFrame = tempRect; - } - - if (!sourceFrame) - { - sourceFrame = destinationFrame; - } - - this.renderer.framebuffer.bind(baseTexture.frameBuffer, destinationFrame); - - this.renderer.projection.update(destinationFrame, sourceFrame, baseTexture.resolution, false); - this.renderer.stencil.setMaskStack(baseTexture.stencilMaskStack); - } - else - { - // TODO these validation checks happen deeper down.. - // thing they can be avoided.. - if (!destinationFrame) - { - tempRect.width = renderer.width; - tempRect.height = renderer.height; - - destinationFrame = tempRect; - } - - if (!sourceFrame) - { - sourceFrame = destinationFrame; - } - - renderer.framebuffer.bind(null, destinationFrame); - - // TODO store this.. - this.renderer.projection.update(destinationFrame, sourceFrame, this.renderer.resolution, true); - this.renderer.stencil.setMaskStack(this.defaultMaskStack); - } - - this.destinationFrame.copyFrom(destinationFrame); - } - - /** - * Erases the render texture and fills the drawing area with a colour - * - * @param {number} [clearColor] - The colour - * @return {PIXI.Renderer} Returns itself. - */ - clear(clearColor) - { - if (this.renderTexture) - { - clearColor = clearColor || this.renderTexture.baseTexture.clearColor; - } - else - { - clearColor = clearColor || this.clearColor; - } - - this.renderer.framebuffer.clear(clearColor[0], clearColor[1], clearColor[2], clearColor[3]); - } - - resize()// screenWidth, screenHeight) - { - // resize the root only! - this.bind(null); - } -} diff --git a/packages/core/src/renderers/systems/StateSystem.js b/packages/core/src/renderers/systems/StateSystem.js deleted file mode 100755 index d7f4caf..0000000 --- a/packages/core/src/renderers/systems/StateSystem.js +++ /dev/null @@ -1,288 +0,0 @@ -import mapWebGLBlendModesToPixi from '../utils/mapWebGLBlendModesToPixi'; -import WebGLSystem from './WebGLSystem'; -import WebGLState from '../State'; - -const BLEND = 0; -const OFFSET = 1; -const CULLING = 2; -const DEPTH_TEST = 3; -const WINDING = 4; - -/** - * A WebGL state machines - * - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class StateSystem extends WebGLSystem -{ - /** - * @param {WebGLRenderingContext} gl - The current WebGL rendering context - */ - constructor(renderer) - { - super(renderer); - - this.gl = null; - - this.maxAttribs = null; - - // check we have vao.. - this.nativeVaoExtension = null; - - this.attribState = null; - - this.stateId = 0; - this.polygonOffset = 0; - this.blendMode = 17; - - this.map = []; - - // map functions for when we set state.. - this.map[BLEND] = this.setBlend; - this.map[OFFSET] = this.setOffset; - this.map[CULLING] = this.setCullFace; - this.map[DEPTH_TEST] = this.setDepthTest; - this.map[WINDING] = this.setFrontFace; - - this.checks = []; - - this.defaultState = new WebGLState(); - this.defaultState.blend = true; - this.defaultState.depth = true; - } - - contextChange(gl) - { - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; - - this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); - - // check we have vao.. - this.nativeVaoExtension = ( - gl.getExtension('OES_vertex_array_object') - || gl.getExtension('MOZ_OES_vertex_array_object') - || gl.getExtension('WEBKIT_OES_vertex_array_object') - ); - - this.attribState = { - tempAttribState: new Array(this.maxAttribs), - attribState: new Array(this.maxAttribs), - }; - - this.blendModes = mapWebGLBlendModesToPixi(gl); - - this.setState(this.defaultState); - - this.reset(); - } - - /** - * Sets the current state - * - * @param {*} state - The state to set. - */ - setState(state) - { - state = state || this.defaultState; - - // TODO maybe to an object check? ( this.state === state )? - if (this.stateId !== state.data) - { - let diff = this.stateId ^ state.data; - let i = 0; - - // order from least to most common - while (diff) - { - if (diff & 1) - { - // state change! - this.map[i].call(this, !!(state.data & (1 << i))); - } - - diff = diff >> 1; - i++; - } - - this.stateId = state.data; - } - - // based on the above settings we check for specific modes.. - // for example if blend is active we check and set the blend modes - // or of polygon offset is active we check the poly depth. - for (let i = 0; i < this.checks.length; i++) - { - this.checks[i](this, state); - } - } - - /** - * Enables or disabled blending. - * - * @param {boolean} value - Turn on or off webgl blending. - */ - setBlend(value) - { - this.updateCheck(StateSystem.checkBlendMode, value); - - this.gl[value ? 'enable' : 'disable'](this.gl.BLEND); - } - - /** - * Enables or disable polygon offset fill - * - * @param {boolean} value - Turn on or off webgl polygon offset testing. - */ - setOffset(value) - { - this.gl[value ? 'enable' : 'disable'](this.gl.POLYGON_OFFSET_FILL); - } - - /** - * Sets whether to enable or disable depth test. - * - * @param {boolean} value - Turn on or off webgl depth testing. - */ - setDepthTest(value) - { - this.gl[value ? 'enable' : 'disable'](this.gl.DEPTH_TEST); - } - - /** - * Sets whether to enable or disable cull face. - * - * @param {boolean} value - Turn on or off webgl cull face. - */ - setCullFace(value) - { - this.gl[value ? 'enable' : 'disable'](this.gl.CULL_FACE); - } - - /** - * Sets the gl front face. - * - * @param {boolean} value - true is clockwise and false is counter-clockwise - */ - setFrontFace(value) - { - this.gl.frontFace(this.gl[value ? 'CW' : 'CCW']); - } - - /** - * Sets the blend mode. - * - * @param {number} value - The blend mode to set to. - */ - setBlendMode(value) - { - if (value === this.blendMode) - { - return; - } - - this.blendMode = value; - this.gl.blendFunc(this.blendModes[value][0], this.blendModes[value][1]); - } - - /** - * Sets the polygon offset. - * - * @param {number} value - the polygon offset - * @param {number} scale - the polygon offset scale - */ - setPolygonOffset(value, scale) - { - this.gl.polygonOffset(value, scale); - } - - /** - * Disables all the vaos in use - * - */ - resetAttributes() - { - for (let i = 0; i < this.attribState.tempAttribState.length; i++) - { - this.attribState.tempAttribState[i] = 0; - } - - for (let i = 0; i < this.attribState.attribState.length; i++) - { - this.attribState.attribState[i] = 0; - } - - // im going to assume one is always active for performance reasons. - for (let i = 1; i < this.maxAttribs; i++) - { - this.gl.disableVertexAttribArray(i); - } - } - - // used - /** - * Resets all the logic and disables the vaos - */ - reset() - { - // unbind any VAO if they exist.. - if (this.nativeVaoExtension) - { - this.nativeVaoExtension.bindVertexArrayOES(null); - } - - // reset all attributes.. - this.resetAttributes(); - - this.gl.pixelStorei(this.gl.UNPACK_FLIP_Y_WEBGL, false); - - this.setBlendMode(0); - - // TODO? - // this.setState(this.defaultState); - } - - /** - * checks to see which updates should be checked based on which settings have been activated - * for example if blend is enabled then we shold check the blend modes each time the state is changed - * or if poygon fill is activated then we need to check if the polygone offset changes. - * The idea is that we only check what we have too. - * - * @param {Function} func the checking function to add or remove - * @param {boolean} value should the check function be added or removed. - */ - updateCheck(func, value) - { - const index = this.checks.indexOf(func); - - if (value && index === -1) - { - this.checks.push(func); - } - else if (!value && index !== -1) - { - this.checks.splice(index, 1); - } - } - - /** - * A private little wrapper function that we call to check the blend mode. - * - * @static - * @private - * @param {PIXI.StateSystem} System the System to perform the state check on - * @param {PIXI.State} state the state that the blendMode will pulled from - */ - static checkBlendMode(system, state) - { - system.setBlendMode(state.blendMode); - } - - // TODO - add polygon offset? -} diff --git a/packages/core/src/renderers/systems/StencilSystem.js b/packages/core/src/renderers/systems/StencilSystem.js deleted file mode 100644 index 5d538cb..0000000 --- a/packages/core/src/renderers/systems/StencilSystem.js +++ /dev/null @@ -1,133 +0,0 @@ -import WebGLSystem from './WebGLSystem'; - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class StencilSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - this.stencilMaskStack = []; - } - - /** - * Changes the mask stack that is used by this System. - * - * @param {PIXI.Graphics[]} stencilMaskStack - The mask stack - */ - setMaskStack(stencilMaskStack) - { - const gl = this.renderer.gl; - - if (stencilMaskStack.length !== this.stencilMaskStack.length) - { - if (stencilMaskStack.length === 0) - { - gl.disable(gl.STENCIL_TEST); - } - else - { - gl.enable(gl.STENCIL_TEST); - } - } - - this.stencilMaskStack = stencilMaskStack; - } - - /** - * Applies the Mask and adds it to the current stencil stack. @alvin - * - * @param {PIXI.Graphics} graphics - The mask - */ - pushStencil(graphics) - { - this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); - - // this.renderer._activeRenderTarget.attachStencilBuffer(); - - const gl = this.renderer.gl; - const prevMaskCount = this.stencilMaskStack.length; - - if (prevMaskCount === 0) - { - gl.enable(gl.STENCIL_TEST); - } - - this.stencilMaskStack.push(graphics); - - // Increment the refference stencil value where the new mask overlaps with the old ones. - gl.colorMask(false, false, false, false); - gl.stencilFunc(gl.EQUAL, prevMaskCount, this._getBitwiseMask()); - gl.stencilOp(gl.KEEP, gl.KEEP, gl.INCR); - this.renderer.plugins.graphics.render(graphics); - - this._useCurrent(); - } - - /** - * Removes the last mask from the stencil stack. @alvin - */ - popStencil() - { - this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); - - const gl = this.renderer.gl; - const graphics = this.stencilMaskStack.pop(); - - if (this.stencilMaskStack.length === 0) - { - // the stack is empty! - gl.disable(gl.STENCIL_TEST); - gl.clear(gl.STENCIL_BUFFER_BIT); - gl.clearStencil(0); - } - else - { - // Decrement the refference stencil value where the popped mask overlaps with the other ones - gl.colorMask(false, false, false, false); - gl.stencilOp(gl.KEEP, gl.KEEP, gl.DECR); - this.renderer.plugins.graphics.render(graphics); - - this._useCurrent(); - } - } - - /** - * Setup renderer to use the current stencil data. - */ - _useCurrent() - { - const gl = this.renderer.gl; - - gl.colorMask(true, true, true, true); - gl.stencilFunc(gl.EQUAL, this.stencilMaskStack.length, this._getBitwiseMask()); - gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP); - } - - /** - * Fill 1s equal to the number of acitve stencil masks. - * - * @return {number} The bitwise mask. - */ - _getBitwiseMask() - { - return (1 << this.stencilMaskStack.length) - 1; - } - - /** - * Destroys the mask stack. - * - */ - destroy() - { - super.destroy(this); - - this.stencilMaskStack = null; - } -} diff --git a/packages/core/src/renderers/systems/WebGLSystem.js b/packages/core/src/renderers/systems/WebGLSystem.js deleted file mode 100644 index d3d5b31..0000000 --- a/packages/core/src/renderers/systems/WebGLSystem.js +++ /dev/null @@ -1,41 +0,0 @@ -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this manager works for. - */ - constructor(renderer) - { - /** - * The renderer this manager works for. - * - * @member {PIXI.Renderer} - */ - this.renderer = renderer; - - this.renderer.runners.contextChange.add(this); - } - - /** - * Generic method called when there is a WebGL context change. - * - */ - contextChange() - { - // do some codes init! - } - - /** - * Generic destroy methods to be overridden by the subclass - * - */ - destroy() - { - this.renderer.runners.contextChange.remove(this); - this.renderer = null; - } -} diff --git a/packages/core/src/renderers/systems/filter/FilterSystem.js b/packages/core/src/renderers/systems/filter/FilterSystem.js deleted file mode 100644 index e615037..0000000 --- a/packages/core/src/renderers/systems/filter/FilterSystem.js +++ /dev/null @@ -1,431 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; - -import RenderTexture from '../../../textures/RenderTexture'; -import Quad from '../../utils/Quad'; -import QuadUv from '../../utils/QuadUv'; -import { Rectangle } from '@pixi/math'; -import * as filterTransforms from '../../filters/filterTransforms'; -import bitTwiddle from 'bit-twiddle'; -import UniformGroup from '../../../shader/UniformGroup'; -import { DRAW_MODES } from '../../../../../constants'; - -// -/** - * @ignore - * @class - */ -class FilterState -{ - /** - * - */ - constructor() - { - this.renderTexture = null; - this.sourceFrame = new Rectangle(); - this.destinationFrame = new Rectangle(); - this.filters = []; - this.target = null; - this.legacy = false; - this.resolution = 1; - } -} - -/** - * @class - * @memberof PIXI.systems - * @extends PIXI.systems.WebGLSystem - */ -export default class FilterSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - /** - * stores a bunch of PO2 textures used for filtering - * @type {Object} - */ - this.texturePool = {}; - - /** - * a pool for storing filter states, save us creating new ones each tick - * @type {Array} - */ - this.statePool = []; - - /** - * A very simple geometry used when drawing a filter effect to the screen - * @type {Quad} - */ - this.quad = new Quad(); - - this.quadUv = new QuadUv(); - - /** - * Temporary rect for maths - * @type {PIXI.Rectangle} - */ - this.tempRect = new Rectangle(); - - this.activeState = {}; - - /** - * this uniform group is attached to filter uniforms when used - * @type {UniformGroup} - */ - this.globalUniforms = new UniformGroup({ - sourceFrame: this.tempRect, - destinationFrame: this.tempRect, - - // legacy variables - filterArea: new Float32Array(4), - filterClamp: new Float32Array(4), - }, true); - } - - /** - * Adds a new filter to the System. - * - * @param {PIXI.DisplayObject} target - The target of the filter to render. - * @param {PIXI.Filter[]} filters - The filters to apply. - */ - push(target, filters) - { - const renderer = this.renderer; - const filterStack = this.renderer.renderTexture.defaultFilterStack; - const state = this.statePool.pop() || new FilterState(); - - let resolution = filters[0].resolution; - let padding = filters[0].padding; - let autoFit = filters[0].autoFit; - let legacy = filters[0].legacy; - - for (let i = 1; i < filters.length; i++) - { - const filter = filters[i]; - - // lets use the lowest resolution.. - resolution = Math.min(resolution, filter.resolution); - // and the largest amount of padding! - padding = Math.max(padding, filter.padding); - // only auto fit if all filters are autofit - autoFit = autoFit || filter.autoFit; - - legacy = legacy || filter.legacy; - } - - filterStack.push(state); - - state.resolution = resolution; - - state.legacy = legacy; - - // round to whole number based on resolution - // TODO move that to the shader too? - state.sourceFrame = target.filterArea || target.getBounds(true); - - state.sourceFrame.pad(padding); - - if (autoFit) - { - state.sourceFrame.fit(this.renderer.renderTexture.destinationFrame); - } - - state.sourceFrame.round(resolution); - - state.renderTexture = this.getPotFilterTexture(state.sourceFrame.width, state.sourceFrame.height, resolution); - state.filters = filters; - - state.destinationFrame.width = state.renderTexture.width; - state.destinationFrame.height = state.renderTexture.height; - - state.renderTexture.filterFrame = state.sourceFrame; - renderer.renderTexture.bind(state.renderTexture, state.sourceFrame);// /, state.destinationFrame); - renderer.renderTexture.clear(); - } - - /** - * Pops off the filter and applies it. - * - */ - pop() - { - const renderer = this.renderer; - const filterStack = renderer.renderTexture.defaultFilterStack; - const state = filterStack.pop(); - const filters = state.filters; - - this.activeState = state; - - const globalUniforms = this.globalUniforms.uniforms; - - globalUniforms.sourceFrame = state.sourceFrame; - globalUniforms.destinationFrame = state.destinationFrame; - globalUniforms.resolution = state.resolution; - - // only update the rect if its legacy.. - if (state.legacy) - { - const filterArea = globalUniforms.filterArea; - const filterClamp = globalUniforms.filterClamp; - - filterArea[0] = state.destinationFrame.width; - filterArea[1] = state.destinationFrame.height; - filterArea[2] = state.sourceFrame.x; - filterArea[3] = state.sourceFrame.y; - - filterClamp[0] = 0.5 / state.resolution / state.destinationFrame.width; - filterClamp[1] = 0.5 / state.resolution / state.destinationFrame.height; - filterClamp[2] = (state.sourceFrame.width - 0.5) / state.resolution / state.destinationFrame.width; - filterClamp[3] = (state.sourceFrame.height - 0.5) / state.resolution / state.destinationFrame.height; - } - - this.globalUniforms.update(); - - const lastState = filterStack[filterStack.length - 1]; - - if (filters.length === 1) - { - filters[0].apply(this, state.renderTexture, lastState.renderTexture, false, state); - - this.returnFilterTexture(state.renderTexture); - } - else - { - let flip = state.renderTexture; - let flop = this.getPotFilterTexture( - flip.width, - flip.height, - state.resolution - ); - - flop.filterFrame = flip.filterFrame; - - let i = 0; - - for (i = 0; i < filters.length - 1; ++i) - { - filters[i].apply(this, flip, flop, true, state); - - const t = flip; - - flip = flop; - flop = t; - } - - filters[i].apply(this, flip, lastState.renderTexture, false, state); - - this.returnFilterTexture(flip); - this.returnFilterTexture(flop); - } - - this.statePool.push(state); - } - - /** - * Draws a filter. - * - * @param {PIXI.Filter} filter - The filter to draw. - * @param {PIXI.RenderTarget} input - The input render target. - * @param {PIXI.RenderTarget} output - The target to output to. - * @param {boolean} clear - Should the output be cleared before rendering to it - */ - applyFilter(filter, input, output, clear) - { - const renderer = this.renderer; - - renderer.renderTexture.bind(output, output ? output.filterFrame : null); - - if (clear) - { - // gl.disable(gl.SCISSOR_TEST); - renderer.renderTexture.clear(); - // gl.enable(gl.SCISSOR_TEST); - } - - // set the uniforms.. - filter.uniforms.uSampler = input; - filter.uniforms.filterGlobals = this.globalUniforms; - - // TODO make it so that the order of this does not matter.. - // because it does at the moment cos of global uniforms. - // they need to get resynced - - renderer.state.setState(filter.state); - renderer.shader.bind(filter); - - if (filter.legacy) - { - this.quadUv.map(input._frame, input.filterFrame); - - renderer.geometry.bind(this.quadUv); - renderer.geometry.draw(DRAW_MODES.TRIANGLES); - } - else - { - renderer.geometry.bind(this.quad); - renderer.geometry.draw(DRAW_MODES.TRIANGLE_STRIP); - } - } - - /** - * Calculates the mapped matrix. - * - * TODO playing around here.. this is temporary - (will end up in the shader) - * this returns a matrix that will normalise map filter cords in the filter to screen space - * - * @param {PIXI.Matrix} outputMatrix - the matrix to output to. - * @return {PIXI.Matrix} The mapped matrix. - */ - calculateScreenSpaceMatrix(outputMatrix) - { - const currentState = this.activeState; - - return filterTransforms.calculateScreenSpaceMatrix( - outputMatrix, - currentState.sourceFrame, - currentState.destinationFrame - ); - } - - /** - * This will map the filter coord so that a texture can be used based on the transform of a sprite - * - * @param {PIXI.Matrix} outputMatrix - The matrix to output to. - * @param {PIXI.Sprite} sprite - The sprite to map to. - * @return {PIXI.Matrix} The mapped matrix. - */ - calculateSpriteMatrix(outputMatrix, sprite) - { - const currentState = this.activeState; - - return filterTransforms.calculateSpriteMatrix( - outputMatrix, - currentState.sourceFrame, - currentState.destinationFrame, - sprite - ); - } - - /** - * Destroys this Filter System. - * - * @param {boolean} [contextLost=false] context was lost, do not free shaders - * - */ - destroy(contextLost = false) - { - if (!contextLost) - { - this.emptyPool(); - } - else - { - this.texturePool = {}; - } - } - - /** - * Gets a Power-of-Two render texture. - * - * TODO move to a seperate class could be on renderer? - * also - could cause issue with multiple contexts? - * - * @private - * @param {WebGLRenderingContext} gl - The webgl rendering context - * @param {number} minWidth - The minimum width of the render target. - * @param {number} minHeight - The minimum height of the render target. - * @param {number} resolution - The resolution of the render target. - * @return {PIXI.RenderTarget} The new render target. - */ - getPotFilterTexture(minWidth, minHeight, resolution) - { - minWidth = bitTwiddle.nextPow2(minWidth); - minHeight = bitTwiddle.nextPow2(minHeight); - resolution = resolution || 1; - - const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); - - if (!this.texturePool[key]) - { - this.texturePool[key] = []; - } - - let renderTexture = this.texturePool[key].pop(); - - if (!renderTexture) - { - // temporary bypass cache.. - // internally - this will cause a texture to be bound.. - renderTexture = RenderTexture.create({ - width: minWidth, - height: minHeight, - resolution, - }); - } - - return renderTexture; - } - - /** - * Gets extra render texture to use inside current filter - * - * @param {number} resolution resolution of the renderTexture - * @returns {PIXI.RenderTexture} - */ - getFilterTexture(resolution) - { - const rt = this.activeState.renderTexture; - - const filterTexture = this.getPotFilterTexture(rt.width, rt.height, resolution || rt.baseTexture.resolution); - - filterTexture.filterFrame = rt.filterFrame; - - return filterTexture; - } - - /** - * Frees a render texture back into the pool. - * - * @param {PIXI.RenderTarget} renderTexture - The renderTarget to free - */ - returnFilterTexture(renderTexture) - { - renderTexture.filterFrame = null; - - const base = renderTexture.baseTexture; - - const minWidth = base.width; - const minHeight = base.height; - - const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); - - this.texturePool[key].push(renderTexture); - } - - /** - * Empties the texture pool. - * - */ - emptyPool() - { - for (const i in this.texturePool) - { - const textures = this.texturePool[i]; - - if (textures) - { - for (let j = 0; j < textures.length; j++) - { - textures[j].destroy(true); - } - } - } - - this.texturePool = {}; - } -} diff --git a/packages/core/src/renderers/systems/geometry/GLBuffer.js b/packages/core/src/renderers/systems/geometry/GLBuffer.js deleted file mode 100644 index 0477e8a..0000000 --- a/packages/core/src/renderers/systems/geometry/GLBuffer.js +++ /dev/null @@ -1,9 +0,0 @@ -export default class GLBuffer -{ - constructor(buffer) - { - this.buffer = buffer; - this.updateID = -1; - this.byteLength = -1; - } -} diff --git a/packages/core/src/renderers/systems/geometry/GeometrySystem.js b/packages/core/src/renderers/systems/geometry/GeometrySystem.js deleted file mode 100644 index 26c8e9e..0000000 --- a/packages/core/src/renderers/systems/geometry/GeometrySystem.js +++ /dev/null @@ -1,388 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; -import GLBuffer from './GLBuffer'; - -const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class GeometrySystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this._activeGeometry = null; - this._activeVao = null; - - this.hasVao = true; - this.hasInstance = true; - } - - /** - * Sets up the renderer context and necessary buffers. - * - * @private - */ - contextChange() - { - const gl = this.gl = this.renderer.gl; - - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - - // webgl2 - if (!gl.createVertexArray) - { - // webgl 1! - let nativeVaoExtension = this.renderer.context.extensions.vertexArrayObject; - - if (this.renderer.legacy) - { - nativeVaoExtension = null; - } - - if (nativeVaoExtension) - { - gl.createVertexArray = () => - nativeVaoExtension.createVertexArrayOES(); - - gl.bindVertexArray = (vao) => - nativeVaoExtension.bindVertexArrayOES(vao); - - gl.deleteVertexArray = (vao) => - nativeVaoExtension.deleteVertexArrayOES(vao); - } - else - { - this.hasVao = false; - gl.createVertexArray = () => - { - // empty - }; - - gl.bindVertexArray = () => - { - // empty - }; - - gl.deleteVertexArray = () => - { - // empty - }; - } - } - - if (!gl.vertexAttribDivisor) - { - const instanceExt = gl.getExtension('ANGLE_instanced_arrays'); - - if (instanceExt) - { - gl.vertexAttribDivisor = (a, b) => - instanceExt.vertexAttribDivisorANGLE(a, b); - - gl.drawElementsInstanced = (a, b, c, d, e) => - instanceExt.drawElementsInstancedANGLE(a, b, c, d, e); - - gl.drawArraysInstanced = (a, b, c, d) => - instanceExt.drawArraysInstancedANGLE(a, b, c, d); - } - else - { - this.hasInstance = false; - } - } - } - - /** - * Binds geometry so that is can be drawn. Creating a Vao if required - * @private - * @param {PIXI.Geometry} geometry instance of geometry to bind - */ - bind(geometry, shader) - { - shader = shader || this.renderer.shader.shader; - - const gl = this.gl; - - // not sure the best way to address this.. - // currently different shaders require different VAOs for the same geometry - // Still mulling over the best way to solve this one.. - // will likely need to modify the shader attribute locations at run time! - let vaos = geometry.glVertexArrayObjects[this.CONTEXT_UID]; - - if (!vaos) - { - geometry.glVertexArrayObjects[this.CONTEXT_UID] = vaos = {}; - } - - const vao = vaos[shader.program.id] || this.initGeometryVao(geometry, shader.program); - - this._activeGeometry = geometry; - - if (this._activeVao !== vao) - { - this._activeVao = vao; - - if (this.hasVao) - { - gl.bindVertexArray(vao); - } - else - { - this.activateVao(geometry, shader.program); - } - } - - // TODO - optimise later! - // don't need to loop through if nothing changed! - // maybe look to add an 'autoupdate' to geometry? - this.updateBuffers(); - } - - reset() - { - this.unbind(); - } - - updateBuffers() - { - const geometry = this._activeGeometry; - const gl = this.gl; - - for (let i = 0; i < geometry.buffers.length; i++) - { - const buffer = geometry.buffers[i]; - - const glBuffer = buffer._glBuffers[this.CONTEXT_UID]; - - if (buffer._updateID !== glBuffer.updateID) - { - glBuffer.updateID = buffer._updateID; - - // TODO can cache this on buffer! maybe added a getter / setter? - const type = buffer.index ? gl.ELEMENT_ARRAY_BUFFER : gl.ARRAY_BUFFER; - const drawType = buffer.static ? gl.STATIC_DRAW : gl.DYNAMIC_DRAW; - - gl.bindBuffer(type, glBuffer.buffer); - - if (glBuffer.byteLength >= buffer.data.byteLength) - { - // offset is always zero for now! - gl.bufferSubData(type, 0, buffer.data); - } - else - { - gl.bufferData(type, buffer.data, drawType); - } - } - } - } - - checkCompatability(geometry, program) - { - // geometry must have at least all the attributes that the shader requires. - const geometryAttributes = geometry.attributes; - const shaderAttributes = program.attributeData; - - for (const j in shaderAttributes) - { - if (!geometryAttributes[j]) - { - throw new Error(`shader and geometry incompatible, geometry missing the "${j}" attribute`); - } - } - } - - /** - * Creates a Vao with the same structure as the geometry and stores it on the geometry. - * @private - * @param {PIXI.Geometry} geometry instance of geometry to to generate Vao for - */ - initGeometryVao(geometry, program) - { - this.checkCompatability(geometry, program); - - const gl = this.gl; - const CONTEXT_UID = this.CONTEXT_UID; - const buffers = geometry.buffers; - const attributes = geometry.attributes; - - const tempStride = {}; - const tempStart = {}; - - for (const j in buffers) - { - tempStride[j] = 0; - tempStart[j] = 0; - } - - for (const j in attributes) - { - if (!attributes[j].size && program.attributeData[j]) - { - attributes[j].size = program.attributeData[j].size; - } - - tempStride[attributes[j].buffer] += attributes[j].size * byteSizeMap[attributes[j].type]; - } - - for (const j in attributes) - { - const attribute = attributes[j]; - const attribSize = attribute.size; - - if (attribute.stride === undefined) - { - if (tempStride[attribute.buffer] === attribSize * byteSizeMap[attribute.type]) - { - attribute.stride = 0; - } - else - { - attribute.stride = tempStride[attribute.buffer]; - } - } - - if (attribute.start === undefined) - { - attribute.start = tempStart[attribute.buffer]; - - tempStart[attribute.buffer] += attribSize * byteSizeMap[attribute.type]; - } - } - - // first update - and create the buffers! - // only create a gl buffer if it actually gets - for (let i = 0; i < buffers.length; i++) - { - const buffer = buffers[i]; - - if (!buffer._glBuffers[CONTEXT_UID]) - { - buffer._glBuffers[CONTEXT_UID] = new GLBuffer(gl.createBuffer()); - } - } - - // TODO - maybe make this a data object? - // lets wait to see if we need to first! - const vao = gl.createVertexArray(); - - gl.bindVertexArray(vao); - - this.activateVao(geometry, program); - - gl.bindVertexArray(null); - - // add it to the cache! - geometry.glVertexArrayObjects[this.CONTEXT_UID][program.id] = vao; - - return vao; - } - - activateVao(geometry, program) - { - const gl = this.gl; - const CONTEXT_UID = this.CONTEXT_UID; - const buffers = geometry.buffers; - const attributes = geometry.attributes; - - if (geometry.indexBuffer) - { - // first update the index buffer if we have one.. - gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, geometry.indexBuffer._glBuffers[CONTEXT_UID].buffer); - } - - let lastBuffer = null; - - // add a new one! - for (const j in attributes) - { - const attribute = attributes[j]; - const buffer = buffers[attribute.buffer]; - const glBuffer = buffer._glBuffers[CONTEXT_UID]; - - if (program.attributeData[j]) - { - if (lastBuffer !== glBuffer) - { - gl.bindBuffer(gl.ARRAY_BUFFER, glBuffer.buffer); - - lastBuffer = glBuffer; - } - - const location = program.attributeData[j].location; - - // TODO introduce state again - // we can optimise this for older devices that have no VAOs - gl.enableVertexAttribArray(location); - - gl.vertexAttribPointer(location, - attribute.size, - attribute.type || gl.FLOAT, - attribute.normalized, - attribute.stride, - attribute.start); - - if (attribute.instance) - { - // TODO calculate instance count based of this... - if (this.hasInstance) - { - gl.vertexAttribDivisor(location, 1); - } - else - { - throw new Error('geometry error, GPU Instancing is not supported on this device'); - } - } - } - } - } - - draw(type, size, start, instanceCount) - { - const gl = this.gl; - const geometry = this._activeGeometry; - - // TODO.. this should not change so maybe cache the function? - - if (geometry.indexBuffer) - { - if (geometry.instanced) - { - /* eslint-disable max-len */ - gl.drawElementsInstanced(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2, instanceCount || 1); - /* eslint-enable max-len */ - } - else - { - gl.drawElements(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2); - } - } - else - if (geometry.instanced) - { - // TODO need a better way to calculate size.. - gl.drawArraysInstanced(type, start, size || geometry.getSize(), instanceCount || 1); - } - else - { - gl.drawArrays(type, start, size || geometry.getSize()); - } - - return this; - } - - unbind() - { - this.gl.bindVertexArray(null); - this._activeVao = null; - this._activeGeometry = null; - } -} diff --git a/packages/core/src/renderers/systems/geometry/setVertexAttribArrays.js b/packages/core/src/renderers/systems/geometry/setVertexAttribArrays.js deleted file mode 100644 index 23b6959..0000000 --- a/packages/core/src/renderers/systems/geometry/setVertexAttribArrays.js +++ /dev/null @@ -1,54 +0,0 @@ -// var GL_MAP = {}; - -/** - * @param gl {WebGLRenderingContext} The current WebGL context - * @param attribs {*} - * @param state {*} - */ -export default function setVertexAttribArrays(gl, attribs, state) -{ - let i; - - if (state) - { - const tempAttribState = state.tempAttribState; - const attribState = state.attribState; - - for (i = 0; i < tempAttribState.length; i++) - { - tempAttribState[i] = false; - } - - // set the new attribs - for (i = 0; i < attribs.length; i++) - { - tempAttribState[attribs[i].attribute.location] = true; - } - - for (i = 0; i < attribState.length; i++) - { - if (attribState[i] !== tempAttribState[i]) - { - attribState[i] = tempAttribState[i]; - - if (state.attribState[i]) - { - gl.enableVertexAttribArray(i); - } - else - { - gl.disableVertexAttribArray(i); - } - } - } - } - else - { - for (i = 0; i < attribs.length; i++) - { - const attrib = attribs[i]; - - gl.enableVertexAttribArray(attrib.attribute.location); - } - } -} diff --git a/packages/core/src/renderers/systems/index.js b/packages/core/src/renderers/systems/index.js deleted file mode 100644 index c843f03..0000000 --- a/packages/core/src/renderers/systems/index.js +++ /dev/null @@ -1,6 +0,0 @@ -/** - * Systems are individual components to the Renderer pipeline. - * @namespace PIXI.systems - */ -export { default as WebGLSystem } from './WebGLSystem'; -export { default as FilterSystem } from './filter/FilterSystem'; diff --git a/packages/core/src/renderers/systems/shader/GLProgram.js b/packages/core/src/renderers/systems/shader/GLProgram.js deleted file mode 100644 index 92980ca..0000000 --- a/packages/core/src/renderers/systems/shader/GLProgram.js +++ /dev/null @@ -1,43 +0,0 @@ -/** - * Helper class to create a webGL Program - * - * @class - * @private - */ -export default class GLProgram -{ - constructor(program, uniformData) - { - /** - * The shader program - * - * @member {WebGLProgram} - */ - this.program = program; - - /** - * holds the uniform data which contains uniform locations - * and current uniform values used for caching and preventing undeed GPU commands - * @type {Object} - */ - this.uniformData = uniformData; - - /** - * uniformGroups holds the various upload functions for the shader. Each uniform group - * and program have a unique upload function generated. - * @type {Object} - */ - this.uniformGroups = {}; - } - - /** - * Destroys this program - * TODO - */ - destroy() - { - this.uniformData = null; - this.uniformGroups = null; - this.program = null; - } -} diff --git a/packages/core/src/renderers/systems/shader/ShaderSystem.js b/packages/core/src/renderers/systems/shader/ShaderSystem.js deleted file mode 100644 index 6cf57b6..0000000 --- a/packages/core/src/renderers/systems/shader/ShaderSystem.js +++ /dev/null @@ -1,170 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; -import GLProgram from './GLProgram'; -import generateUniformsSync from '../../../shader/generateUniformsSync'; -import defaultValue from '../../../shader/defaultValue'; -import compileProgram from './shader/compileProgram'; - -let UID = 0; - -/** - * Helper class to create a webGL Texture - * - * @class - * @memberof PIXI - */ -export default class ShaderSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - A reference to the current renderer - */ - constructor(renderer) - { - super(renderer); - - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = null; - - this.shader = null; - this.program = null; - - this.id = UID++; - } - - contextChange(gl) - { - this.gl = gl; - } - - /** - * Changes the current shader to the one given in parameter - * - * @param {PIXI.Shader} shader - the new shader - * @param {boolean} dontSync - false if the shader should automatically sync its uniforms. - * @returns {PIXI.glCore.glProgram} the glProgram that belongs to the shader. - */ - bind(shader, dontSync) - { - shader.uniforms.globals = this.renderer.globalUniforms; - - const program = shader.program; - const glProgram = program.glPrograms[this.renderer.CONTEXT_UID] || this.generateShader(shader); - - this.shader = shader; - - // TODO - some current pixi plugins bypass this.. so it not safe to use yet.. - if (this.program !== program) - { - this.program = program; - this.gl.useProgram(glProgram.program); - } - - if (!dontSync) - { - this.syncUniformGroup(shader.uniformGroup); - } - - return glProgram; - } - - /** - * Uploads the uniforms values to the currently bound shader. - * - * @param {object} uniforms - the uniforms valiues that be applied to the current shader - */ - setUniforms(uniforms) - { - const shader = this.shader.program; - const glProgram = shader.glPrograms[this.renderer.CONTEXT_UID]; - - shader.syncUniforms(glProgram.uniformData, uniforms, this.renderer); - } - - syncUniformGroup(group) - { - const glProgram = this.getglProgram(); - - if (!group.static || group.dirtyId !== glProgram.uniformGroups[group.id]) - { - glProgram.uniformGroups[group.id] = group.dirtyId; - const syncFunc = group.syncUniforms[this.shader.program.id] || this.createSyncGroups(group); - - syncFunc(glProgram.uniformData, group.uniforms, this.renderer); - } - } - - createSyncGroups(group) - { - group.syncUniforms[this.shader.program.id] = generateUniformsSync(group, this.shader.program.uniformData); - - return group.syncUniforms[this.shader.program.id]; - } - - /** - * Returns the underlying GLShade rof the currently bound shader. - * This can be handy for when you to have a little more control over the setting of your uniforms. - * - * @return {PIXI.glCore.Shader} the glProgram for the currently bound Shader for this context - */ - getglProgram() - { - if (this.shader) - { - return this.shader.program.glPrograms[this.renderer.CONTEXT_UID]; - } - - return null; - } - - /** - * Generates a glProgram verion of the Shader provided. - * - * @private - * @param {PIXI.Shader} shader the shader that the glProgram will be based on. - * @return {PIXI.glCore.glProgram} A shiney new glProgram - */ - generateShader(shader) - { - const gl = this.gl; - - const program = shader.program; - - const attribMap = {}; - - for (const i in program.attributeData) - { - attribMap[i] = program.attributeData[i].location; - } - - const shaderProgram = compileProgram(gl, program.vertexSrc, program.fragmentSrc, attribMap); - const uniformData = {}; - - for (const i in program.uniformData) - { - const data = program.uniformData[i]; - - uniformData[i] = { - location: gl.getUniformLocation(shaderProgram, i), - value: defaultValue(data.type, data.size), - }; - } - - const glProgram = new GLProgram(shaderProgram, uniformData); - - program.glPrograms[this.renderer.CONTEXT_UID] = glProgram; - - return glProgram; - } - - /** - * Destroys this System and removes all its textures - */ - destroy() - { - // TODO implement destroy method for ShaderSystem - this.destroyed = true; - } -} diff --git a/packages/core/src/renderers/systems/shader/shader/compileProgram.js b/packages/core/src/renderers/systems/shader/shader/compileProgram.js deleted file mode 100644 index 8c6ccb9..0000000 --- a/packages/core/src/renderers/systems/shader/shader/compileProgram.js +++ /dev/null @@ -1,78 +0,0 @@ -/** - * @method compileProgram - * @memberof PIXI.glCore.shader - * @param gl {WebGLRenderingContext} The current WebGL context {WebGLProgram} - * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. - * @param fragmentSrc {string|string[]} The fragment shader source as an array of strings. - * @param attributeLocations {Object} An attribute location map that lets you manually set the attribute locations - * @return {WebGLProgram} the shader program - */ -export default function compileProgram(gl, vertexSrc, fragmentSrc, attributeLocations) -{ - const glVertShader = compileShader(gl, gl.VERTEX_SHADER, vertexSrc); - const glFragShader = compileShader(gl, gl.FRAGMENT_SHADER, fragmentSrc); - - let program = gl.createProgram(); - - gl.attachShader(program, glVertShader); - gl.attachShader(program, glFragShader); - - // optionally, set the attributes manually for the program rather than letting WebGL decide.. - if (attributeLocations) - { - for (const i in attributeLocations) - { - gl.bindAttribLocation(program, attributeLocations[i], i); - } - } - - gl.linkProgram(program); - - // if linking fails, then log and cleanup - if (!gl.getProgramParameter(program, gl.LINK_STATUS)) - { - console.error('Pixi.js Error: Could not initialize shader.'); - console.error('gl.VALIDATE_STATUS', gl.getProgramParameter(program, gl.VALIDATE_STATUS)); - console.error('gl.getError()', gl.getError()); - - // if there is a program info log, log it - if (gl.getProgramInfoLog(program) !== '') - { - console.warn('Pixi.js Warning: gl.getProgramInfoLog()', gl.getProgramInfoLog(program)); - } - - gl.deleteProgram(program); - program = null; - } - - // clean up some shaders - gl.deleteShader(glVertShader); - gl.deleteShader(glFragShader); - - return program; -} - -/** - * @private - * @param gl {WebGLRenderingContext} The current WebGL context {WebGLProgram} - * @param type {Number} the type, can be either VERTEX_SHADER or FRAGMENT_SHADER - * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. - * @return {WebGLShader} the shader - */ -function compileShader(gl, type, src) -{ - const shader = gl.createShader(type); - - gl.shaderSource(shader, src); - gl.compileShader(shader); - - if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) - { - console.warn(src); - console.error(gl.getShaderInfoLog(shader)); - - return null; - } - - return shader; -} diff --git a/packages/core/src/renderers/systems/shader/shader/defaultValue.js b/packages/core/src/renderers/systems/shader/shader/defaultValue.js deleted file mode 100644 index 97290bd..0000000 --- a/packages/core/src/renderers/systems/shader/shader/defaultValue.js +++ /dev/null @@ -1,79 +0,0 @@ -/** - * @method defaultValue - * @memberof PIXI.glCore.shader - * @param type {String} Type of value - * @param size {Number} - */ -export default function defaultValue(type, size) -{ - switch (type) - { - case 'float': - return 0; - - case 'vec2': - return new Float32Array(2 * size); - - case 'vec3': - return new Float32Array(3 * size); - - case 'vec4': - return new Float32Array(4 * size); - - case 'int': - case 'sampler2D': - case 'sampler2DArray': - return 0; - - case 'ivec2': - return new Int32Array(2 * size); - - case 'ivec3': - return new Int32Array(3 * size); - - case 'ivec4': - return new Int32Array(4 * size); - - case 'bool': - return false; - - case 'bvec2': - - return booleanArray(2 * size); - - case 'bvec3': - return booleanArray(3 * size); - - case 'bvec4': - return booleanArray(4 * size); - - case 'mat2': - return new Float32Array([1, 0, - 0, 1]); - - case 'mat3': - return new Float32Array([1, 0, 0, - 0, 1, 0, - 0, 0, 1]); - - case 'mat4': - return new Float32Array([1, 0, 0, 0, - 0, 1, 0, 0, - 0, 0, 1, 0, - 0, 0, 0, 1]); - } - - return null; -} - -function booleanArray(size) -{ - const array = new Array(size); - - for (let i = 0; i < array.length; i++) - { - array[i] = false; - } - - return array; -} diff --git a/packages/core/src/renderers/systems/shader/shader/index.js b/packages/core/src/renderers/systems/shader/shader/index.js deleted file mode 100644 index ba7a317..0000000 --- a/packages/core/src/renderers/systems/shader/shader/index.js +++ /dev/null @@ -1,8 +0,0 @@ -/** - * @namespace PIXI.glCore.shader - */ -export { default as compileProgram } from './compileProgram'; -export { default as defaultValue } from './defaultValue'; -export { default as setPrecision } from './setPrecision'; -export { default as mapSize } from './mapSize'; -export { default as mapType } from './mapType'; diff --git a/packages/core/src/AbstractRenderer.js b/packages/core/src/AbstractRenderer.js new file mode 100644 index 0000000..64b2eb1 --- /dev/null +++ b/packages/core/src/AbstractRenderer.js @@ -0,0 +1,353 @@ +import { hex2string, hex2rgb } from '@pixi/utils'; +import { Matrix, Rectangle } from '@pixi/math'; +import { RENDERER_TYPE } from '@pixi/constants'; +import { settings } from '@pixi/settings'; +import { Container } from '@pixi/display'; +import RenderTexture from './renderTexture/RenderTexture'; +import EventEmitter from 'eventemitter3'; + +const tempMatrix = new Matrix(); + +/** + * 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 EventEmitter + * @memberof PIXI + */ +export default class AbstractRenderer extends EventEmitter +{ + // eslint-disable-next-line valid-jsdoc + /** + * @param {string} system - The name of the system this renderer is for. + * @param {object} [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.transparent=false] - If the render view is transparent, default false + * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false + * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. The + * resolution of the renderer retina would be 2. + * @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 {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when rendering, + * stopping pixel interpolation. + */ + constructor(system, options, arg2, arg3) + { + super(); + + // Support for constructor(system, screenWidth, screenHeight, options) + if (typeof options === 'number') + { + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + }, arg3); + } + + // 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 = RENDERER_TYPE.UNKNOWN; + + /** + * Measurements of the screen. (0, 0, screenWidth, screenHeight) + * + * Its safe to use as filterArea or hitArea for 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 1 + */ + this.resolution = options.resolution || settings.RESOLUTION; + + /** + * Whether the render view is transparent + * + * @member {boolean} + */ + this.transparent = options.transparent; + + /** + * Whether css dimensions of canvas view should be resized to screen dimensions automatically + * + * @member {boolean} + */ + this.autoResize = options.autoResize || false; + + /** + * Tracks the blend modes useful for this renderer. + * + * @member {object} + */ + this.blendModes = null; + + /** + * 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; + + /** + * If true PixiJS will Math.floor() x/y values when rendering, stopping pixel interpolation. + * Handy for crisp pixel art and speed on legacy devices. + * + * @member {boolean} + */ + this.roundPixels = options.roundPixels; + + /** + * The background color as a number. + * + * @member {number} + * @private + */ + this._backgroundColor = 0x000000; + + /** + * The background color as an [R, G, B] array. + * + * @member {number[]} + * @private + */ + this._backgroundColorRgba = [0, 0, 0, 0]; + + /** + * The background color as a string. + * + * @member {string} + * @private + */ + this._backgroundColorString = '#000000'; + + this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter + + /** + * This temporary display object used as the parent of the currently being rendered item + * + * @member {PIXI.DisplayObject} + * @private + */ + this._tempDisplayObjectParent = new Container(); + + /** + * The last root object that the renderer tried to render. + * + * @member {PIXI.DisplayObject} + * @private + */ + this._lastObjectRendered = this._tempDisplayObjectParent; + + /** + * Collection of plugins. + * @readonly + * @member {object} + */ + this.plugins = {}; + } + + /** + * Initialize the plugins. + * + * @protected + * @param {object} staticMap - The dictionary of staticly saved plugins. + */ + initPlugins(staticMap) + { + 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() + { + return this.view.width; + } + + /** + * Same as view.height, actual number of pixels in the canvas by vertical + * + * @member {number} + * @readonly + * @default 600 + */ + get height() + { + return this.view.height; + } + + /** + * Resizes the screen and canvas to the specified width and height + * Canvas dimensions are multiplied by resolution + * + * @param {number} screenWidth - the new width of the screen + * @param {number} screenHeight - the new height of the screen + */ + resize(screenWidth, screenHeight) + { + this.screen.width = screenWidth; + this.screen.height = screenHeight; + + this.view.width = screenWidth * this.resolution; + this.view.height = screenHeight * this.resolution; + + if (this.autoResize) + { + this.view.style.width = `${screenWidth}px`; + this.view.style.height = `${screenHeight}px`; + } + } + + /** + * 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 {PIXI.DisplayObject} displayObject - The displayObject the object will be generated from + * @param {number} scaleMode - Should be one of the scaleMode consts + * @param {number} resolution - The resolution / device pixel ratio of the texture being generated + * @param {PIXI.Rectangle} [region] - The region of the displayObject, that shall be rendered, + * if no region is specified, defaults to the local bounds of the displayObject. + * @return {PIXI.Texture} a texture of the graphics object + */ + generateTexture(displayObject, scaleMode, resolution, region) + { + region = region || displayObject.getLocalBounds(); + + // 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(region.width | 0, region.height | 0, scaleMode, resolution); + + tempMatrix.tx = -region.x; + tempMatrix.ty = -region.y; + + this.render(displayObject, renderTexture, false, tempMatrix, true); + + return renderTexture; + } + + /** + * Removes everything from the renderer and optionally removes the Canvas DOM element. + * + * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. + */ + destroy(removeView) + { + 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); + } + + this.plugins = null; + + this.type = RENDERER_TYPE.UNKNOWN; + + this.view = null; + + this.screen = null; + + this.resolution = 0; + + this.transparent = false; + + this.autoResize = false; + + this.blendModes = null; + + this.options = null; + + this.preserveDrawingBuffer = false; + this.clearBeforeRender = false; + + this.roundPixels = false; + + this._backgroundColor = 0; + this._backgroundColorRgba = null; + this._backgroundColorString = null; + + this._tempDisplayObjectParent = null; + this._lastObjectRendered = null; + } + + /** + * The background color to fill if not transparent + * + * @member {number} + */ + get backgroundColor() + { + return this._backgroundColor; + } + + set backgroundColor(value) // eslint-disable-line require-jsdoc + { + this._backgroundColor = value; + this._backgroundColorString = hex2string(value); + hex2rgb(value, this._backgroundColorRgba); + } +} diff --git a/packages/core/src/Renderer.js b/packages/core/src/Renderer.js new file mode 100644 index 0000000..2227e78 --- /dev/null +++ b/packages/core/src/Renderer.js @@ -0,0 +1,338 @@ +import AbstractRenderer from './AbstractRenderer'; +import { sayHello } from '@pixi/utils'; +import MaskSystem from './mask/MaskSystem'; +import StencilSystem from './mask/StencilSystem'; +import FilterSystem from './filters/FilterSystem'; +import FramebufferSystem from './framebuffer/FramebufferSystem'; +import RenderTextureSystem from './renderTexture/RenderTextureSystem'; +import TextureSystem from './textures/TextureSystem'; +import ProjectionSystem from './projection/ProjectionSystem'; +import StateSystem from './state/StateSystem'; +import GeometrySystem from './geometry/GeometrySystem'; +import ShaderSystem from './shader/ShaderSystem'; +import ContextSystem from './context/ContextSystem'; +import BatchSystem from './batch/BatchSystem'; +import TextureGCSystem from './textures/TextureGCSystem'; +import { RENDERER_TYPE } from '@pixi/constants'; +import UniformGroup from './shader/UniformGroup'; +import { Matrix } from '@pixi/math'; +import Runner from 'mini-runner'; + +/** + * 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 Render works by automatically managing webGLBatchs. + * 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 :) + * + * @class + * @memberof PIXI + * @extends PIXI.AbstractRenderer + */ +export default class Renderer extends AbstractRenderer +{ + // eslint-disable-next-line valid-jsdoc + /** + * + * @param {object} [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.transparent=false] - If the render view is transparent, default false + * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false + * @param {boolean} [options.antialias=false] - sets antialias. If not available natively then FXAA + * antialiasing is used + * @param {boolean} [options.forceFXAA=false] - forces FXAA antialiasing to be used over native. + * FXAA is faster, but may not always look as great + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. + * The resolution of the renderer retina would be 2. + * @param {boolean} [options.clearBeforeRender=true] - This sets if the renderer will clear + * the canvas or not before the new render pass. If you wish to set this to false, you *must* set + * preserveDrawingBuffer to `true`. + * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, + * enable this if you need to call toDataUrl on the webgl context. + * @param {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when + * rendering, stopping pixel interpolation. + * @param {number} [options.backgroundColor=0x000000] - The background color of the rendered area + * (shown if not transparent). + * @param {boolean} [options.legacy=false] - If true PixiJS will aim to ensure compatibility + * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. + * @param {string} [options.powerPreference] - Parameter passed to webgl context, set to "high-performance" + * for devices with dual graphics card + */ + constructor(options, arg2, arg3) + { + super('WebGL', options, arg2, arg3); + + /** + * The type of this renderer as a standardised const + * + * @member {number} + * @see PIXI.RENDERER_TYPE + */ + this.type = RENDERER_TYPE.WEBGL; + + // this will be set by the contextSystem (this.context) + this.gl = null; + this.CONTEXT_UID = 0; + this.legacy = !!options.legacy; + + // TODO legacy! + + // runners! + this.runners = { + destroy: new Runner('destroy'), + contextChange: new Runner('contextChange', 1), + reset: new Runner('reset'), + update: new Runner('update'), + postrender: new Runner('postrender'), + prerender: new Runner('prerender'), + resize: new Runner('resize', 2), + }; + + 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(GeometrySystem, 'geometry') + .addSystem(FramebufferSystem, 'framebuffer') + .addSystem(StencilSystem, 'stencil') + .addSystem(ProjectionSystem, 'projection') + .addSystem(TextureGCSystem, 'textureGC') + .addSystem(FilterSystem, 'filter') + .addSystem(RenderTextureSystem, 'renderTexture') + .addSystem(BatchSystem, 'batch'); + + this.initPlugins(Renderer.__plugins); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ + if (options.context) + { + this.context.initFromContext(options.context); + } + else + { + this.context.initFromOptions({ + alpha: this.transparent, + antialias: options.antialias, + premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', + stencil: true, + 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); + } + + /** + * Add a new system to the renderer. + * @param {class} ClassRef - Class reference + * @param {string} [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. + * @return {PIXI.Renderer} Return instance of renderer + */ + addSystem(ClassRef, name) + { + if (!name) + { + name = ClassRef.name; + } + + // TODO - read name from class.name.. + + /* + if(name.includes('System')) + { + name = name.replace('System', ''); + name = name.charAt(0).toLowerCase() + name.slice(1); + } + */ + + const system = new ClassRef(this); + + if (this[name]) + { + throw new Error(`Whoops! ${name} is already a manger`); + } + + this[name] = system; + + for (const i in this.runners) + { + this.runners[i].add(system); + } + + return this; + + /** + * 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. + */ + } + + /** + * Renders the object to its webGL view + * + * @param {PIXI.DisplayObject} displayObject - the object to be rendered + * @param {PIXI.RenderTexture} renderTexture - The render texture to render to. + * @param {boolean} [clear] - Should the canvas be cleared before the new render + * @param {PIXI.Transform} [transform] - A transform to apply to the render texture before rendering. + * @param {boolean} [skipUpdateTransform] - Should we skip the update transform pass? + */ + render(displayObject, renderTexture, clear, transform, skipUpdateTransform) + { + // can be handy to know! + this.renderingToScreen = !renderTexture; + + this.runners.prerender.run(); + this.emit('prerender'); + + // 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.parent; + + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = 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.run(); + + this.emit('postrender'); + } + + /** + * Resizes the webGL view to the specified width and height. + * + * @param {number} screenWidth - the new width of the screen + * @param {number} screenHeight - the new height of the screen + */ + resize(screenWidth, screenHeight) + { + AbstractRenderer.prototype.resize.call(this, screenWidth, screenHeight); + + this.runners.resize.run(screenWidth, screenHeight); + } + + /** + * Resets the WebGL state so you can render things however you fancy! + * + * @return {PIXI.Renderer} Returns itself. + */ + reset() + { + this.runners.reset.run(); + + return this; + } + + clear() + { + this.framebuffer.bind(); + this.framebuffer.clear(); + } + + /** + * Removes everything from the renderer (event listeners, spritebatch, etc...) + * + * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. + * See: https://github.com/pixijs/pixi.js/issues/2233 + */ + destroy(removeView) + { + this.runners.destroy.run(); + + // call base destroy + super.destroy(removeView); + + // TODO nullify all the managers.. + this.gl = null; + } + + /** + * 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 + * builds and excluding plugins. + * @name PIXI.Renderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. + */ + + /** + * Adds a plugin to the renderer. + * + * @method + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + static registerPlugin(pluginName, ctor) + { + Renderer.__plugins = Renderer.__plugins || {}; + Renderer.__plugins[pluginName] = ctor; + } +} diff --git a/packages/core/src/System.js b/packages/core/src/System.js new file mode 100644 index 0000000..05a10c8 --- /dev/null +++ b/packages/core/src/System.js @@ -0,0 +1,42 @@ +/** + * System is a base class used for extending systems used by the {@link PIXI.Renderer} + * @see PIXI.Renderer#addSystem + * @class + * @memberof PIXI + */ +export default class System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + /** + * The renderer this manager works for. + * + * @member {PIXI.Renderer} + */ + this.renderer = renderer; + + this.renderer.runners.contextChange.add(this); + } + + /** + * Generic method called when there is a WebGL context change. + * + */ + contextChange() + { + // do some codes init! + } + + /** + * Generic destroy methods to be overridden by the subclass + * + */ + destroy() + { + this.renderer.runners.contextChange.remove(this); + this.renderer = null; + } +} diff --git a/packages/core/src/batch/BatchSystem.js b/packages/core/src/batch/BatchSystem.js new file mode 100644 index 0000000..b04c249 --- /dev/null +++ b/packages/core/src/batch/BatchSystem.js @@ -0,0 +1,68 @@ +import System from '../System'; +import ObjectRenderer from './ObjectRenderer'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class BatchSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + super(renderer); + + /** + * An empty renderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.emptyRenderer = new ObjectRenderer(renderer); + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.currentRenderer = this.emptyRenderer; + } + + /** + * Changes the current renderer to the one given in parameter + * + * @param {PIXI.ObjectRenderer} objectRenderer - The object renderer to use. + */ + setObjectRenderer(objectRenderer) + { + if (this.currentRenderer === objectRenderer) + { + return; + } + + this.currentRenderer.stop(); + this.currentRenderer = objectRenderer; + + this.renderer.state.setState(objectRenderer.state); + + this.currentRenderer.start(); + } + + /** + * This should be called if you wish to do some custom rendering + * It will basically render anything that may be batched up such as sprites + * + */ + flush() + { + this.setObjectRenderer(this.emptyRenderer); + } + + reset() + { + this.setObjectRenderer(this.emptyRenderer); + } +} diff --git a/packages/core/src/batch/ObjectRenderer.js b/packages/core/src/batch/ObjectRenderer.js new file mode 100644 index 0000000..0c851a9 --- /dev/null +++ b/packages/core/src/batch/ObjectRenderer.js @@ -0,0 +1,48 @@ +import System from '../System'; + +/** + * Base for a common object renderer that can be used as a system renderer plugin. + * + * @class + * @extends PIXI.System + * @memberof PIXI + */ +export default class ObjectRenderer extends System +{ + /** + * Starts the renderer and sets the shader + * + */ + start() + { + // set the shader.. + } + + /** + * Stops the renderer + * + */ + stop() + { + this.flush(); + } + + /** + * Stub method for rendering content and emptying the current batch. + * + */ + flush() + { + // flush! + } + + /** + * Renders an object + * + * @param {PIXI.DisplayObject} object - The object to render. + */ + render(object) // eslint-disable-line no-unused-vars + { + // render the object + } +} diff --git a/packages/core/src/context/ContextSystem.js b/packages/core/src/context/ContextSystem.js new file mode 100644 index 0000000..301aab4 --- /dev/null +++ b/packages/core/src/context/ContextSystem.js @@ -0,0 +1,186 @@ +import System from '../System'; +import { settings } from '@pixi/settings'; + +let CONTEXT_UID = 0; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class ContextSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.webGLVersion = 1; + + this.handleContextLost = this.handleContextLost.bind(this); + this.handleContextRestored = this.handleContextRestored.bind(this); + + this.extensions = {}; + + renderer.view.addEventListener('webglcontextlost', this.handleContextLost, false); + renderer.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + } + + get isLost() + { + return (!this.gl || this.gl.isContextLost()); + } + + contextChange(gl) + { + this.gl = gl; + + // restore a context if it was previously lost + if (gl.isContextLost() && gl.getExtension('WEBGL_lose_context')) + { + gl.getExtension('WEBGL_lose_context').restoreContext(); + } + } + + initFromContext(gl) + { + this.gl = gl; + this.validateContext(gl); + this.renderer.gl = gl; + this.renderer.CONTEXT_UID = CONTEXT_UID++; + this.renderer.runners.contextChange.run(gl); + } + + initFromOptions(options) + { + const gl = this.createContext(this.renderer.view, options); + + this.initFromContext(gl); + } + + /** + * Helper class to create a webGL Context + * + * @class + * @memberof PIXI.glCore + * @param canvas {HTMLCanvasElement} the canvas element that we will get the context from + * @param options {Object} An options object that gets passed in to the canvas element containing the context attributes, + * see https://developer.mozilla.org/en/docs/Web/API/HTMLCanvasElement/getContext for the options available + * @return {WebGLRenderingContext} the WebGL context + */ + createContext(canvas, options) + { + let gl; + + if (settings.PREFER_WEBGL_2) + { + gl = canvas.getContext('webgl2', options); + } + + if (gl) + { + this.webGLVersion = 2; + } + else + { + this.webGLVersion = 1; + + gl = canvas.getContext('webgl', options) + || canvas.getContext('experimental-webgl', options); + + if (!gl) + { + // fail, not able to get a context + throw new Error('This browser does not support webGL. Try using the canvas renderer'); + } + } + + this.gl = gl; + + this.getExtensions(); + + return gl; + } + + getExtensions() + { + // time to set up default etensions that pixi uses.. + const gl = this.gl; + const extensions = this.extensions; + + if (this.webGLVersion === 1) + { + extensions.drawBuffers = gl.getExtension('WEBGL_draw_buffers'); + extensions.depthTexture = gl.getExtension('WEBKIT_WEBGL_depth_texture'); + extensions.floatTexture = gl.getExtension('OES_texture_float'); + extensions.loseContext = gl.getExtension('WEBGL_lose_context'); + + extensions.vertexArrayObject = gl.getExtension('OES_vertex_array_object') + || gl.getExtension('MOZ_OES_vertex_array_object') + || gl.getExtension('WEBKIT_OES_vertex_array_object'); + } + + // we don't use any specific WebGL 2 ones yet! + } + + /** + * Handles a lost webgl context + * + * @private + * @param {WebGLContextEvent} event - The context lost event. + */ + handleContextLost(event) + { + event.preventDefault(); + } + + /** + * Handles a restored webgl context + * + * @private + */ + handleContextRestored() + { + this.renderer.runners.contextChange.run(this.gl); + } + + destroy() + { + const view = this.renderer.view; + + // remove listeners + view.removeEventListener('webglcontextlost', this.handleContextLost); + view.removeEventListener('webglcontextrestored', this.handleContextRestored); + + this.gl.useProgram(null); + + if (this.extensions.loseContext) + { + this.extensions.loseContext.loseContext(); + } + } + + postrender() + { + this.gl.flush(); + } + + validateContext(gl) + { + const attributes = gl.getContextAttributes(); + + // this is going to be fairly simple for now.. but at least we have room to grow! + if (!attributes.stencil) + { + /* eslint-disable max-len */ + + /* eslint-disable no-console */ + console.warn('Provided WebGL context does not have a stencil buffer, masks may not render correctly'); + /* eslint-enable no-console */ + + /* eslint-enable max-len */ + } + } +} diff --git a/packages/core/src/filters/Filter.js b/packages/core/src/filters/Filter.js new file mode 100644 index 0000000..9d8cad8 --- /dev/null +++ b/packages/core/src/filters/Filter.js @@ -0,0 +1,140 @@ +import Shader from '../shader/Shader'; +import Program from '../shader/Program'; +import State from '../state/State'; +import { settings } from '@pixi/settings'; +// import extractUniformsFromSrc from './extractUniformsFromSrc'; +import defaultVertex from './defaultFilter.vert'; +import defaultFragment from './defaultFilter.frag'; + +// let math = require('../../math'); +/** + * @class + * @memberof PIXI + * @extends PIXI.Shader + */ +export default class Filter extends Shader +{ + /** + * @param {string} [vertexSrc] - The source of the vertex shader. + * @param {string} [fragmentSrc] - The source of the fragment shader. + * @param {object} [uniforms] - Custom uniforms to use to augment the built-in ones. + */ + constructor(vertexSrc, fragmentSrc, uniforms) + { + const program = Program.from(vertexSrc, fragmentSrc); + + super(program, uniforms); + + /** + * The padding of the filter. Some filters require extra space to breath such as a blur. + * Increasing this will add extra width and height to the bounds of the object that the + * filter is applied to. + * + * @member {number} + */ + this.padding = 0; + + /** + * The resolution of the filter. Setting this to be lower will lower the quality but + * increase the performance of the filter. + * + * @member {number} + */ + this.resolution = settings.RESOLUTION; + + /** + * If enabled is true the filter is applied, if false it will not. + * + * @member {boolean} + */ + this.enabled = true; + + /** + * If enabled, PixiJS will fit the filter area into boundaries for better performance. + * Switch it off if it does not work for specific shader. + * + * @member {boolean} + */ + this.autoFit = true; + + /** + * Legacy filters use position and uvs from attributes + * @member {boolean} + * @readonly + */ + this.legacy = !!this.program.attributeData.aTextureCoord; + + /** + * the webGL state the filter requires to render + * @member {PIXI.State} + */ + this.state = new State(); + } + + /** + * Applies the filter + * + * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + * @param {boolean} clear - Should the output be cleared before rendering to it + * @param {object} [currentState] - It's current state of filter. + * There are some useful properties in the currentState : + * target, filters, sourceFrame, destinationFrame, renderTarget, resolution + */ + apply(filterManager, input, output, clear, currentState, derp) // eslint-disable-line no-unused-vars + { + // do as you please! + + filterManager.applyFilter(this, input, output, clear, currentState, derp); + + // or just do a regular render.. + } + + /** + * Sets the blendmode of the filter + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL + */ + get blendMode() + { + return this.state.blendMode; + } + + set blendMode(value) // eslint-disable-line require-jsdoc + { + this.state.blendMode = value; + } + + /** + * The default vertex shader source + * + * @static + * @constant + */ + static get defaultVertexSrc() + { + return defaultVertex; + } + + /** + * The default fragment shader source + * + * @static + * @constant + */ + static get defaultFragmentSrc() + { + return defaultFragment; + } +} + +/** + * Used for caching shader IDs + * + * @static + * @private + */ +Filter.SOURCE_KEY_MAP = {}; + diff --git a/packages/core/src/filters/FilterSystem.js b/packages/core/src/filters/FilterSystem.js new file mode 100644 index 0000000..3174318 --- /dev/null +++ b/packages/core/src/filters/FilterSystem.js @@ -0,0 +1,431 @@ +import System from '../System'; + +import RenderTexture from '../renderTexture/RenderTexture'; +import Quad from '../utils/Quad'; +import QuadUv from '../utils/QuadUv'; +import { Rectangle } from '@pixi/math'; +import * as filterTransforms from './filterTransforms'; +import bitTwiddle from 'bit-twiddle'; +import UniformGroup from '../shader/UniformGroup'; +import { DRAW_MODES } from '@pixi/constants'; + +// +/** + * @ignore + * @class + */ +class FilterState +{ + /** + * + */ + constructor() + { + this.renderTexture = null; + this.sourceFrame = new Rectangle(); + this.destinationFrame = new Rectangle(); + this.filters = []; + this.target = null; + this.legacy = false; + this.resolution = 1; + } +} + +/** + * @class + * @memberof PIXI.systems + * @extends PIXI.System + */ +export default class FilterSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + /** + * stores a bunch of PO2 textures used for filtering + * @type {Object} + */ + this.texturePool = {}; + + /** + * a pool for storing filter states, save us creating new ones each tick + * @type {Array} + */ + this.statePool = []; + + /** + * A very simple geometry used when drawing a filter effect to the screen + * @type {Quad} + */ + this.quad = new Quad(); + + this.quadUv = new QuadUv(); + + /** + * Temporary rect for maths + * @type {PIXI.Rectangle} + */ + this.tempRect = new Rectangle(); + + this.activeState = {}; + + /** + * this uniform group is attached to filter uniforms when used + * @type {UniformGroup} + */ + this.globalUniforms = new UniformGroup({ + sourceFrame: this.tempRect, + destinationFrame: this.tempRect, + + // legacy variables + filterArea: new Float32Array(4), + filterClamp: new Float32Array(4), + }, true); + } + + /** + * Adds a new filter to the System. + * + * @param {PIXI.DisplayObject} target - The target of the filter to render. + * @param {PIXI.Filter[]} filters - The filters to apply. + */ + push(target, filters) + { + const renderer = this.renderer; + const filterStack = this.renderer.renderTexture.defaultFilterStack; + const state = this.statePool.pop() || new FilterState(); + + let resolution = filters[0].resolution; + let padding = filters[0].padding; + let autoFit = filters[0].autoFit; + let legacy = filters[0].legacy; + + for (let i = 1; i < filters.length; i++) + { + const filter = filters[i]; + + // lets use the lowest resolution.. + resolution = Math.min(resolution, filter.resolution); + // and the largest amount of padding! + padding = Math.max(padding, filter.padding); + // only auto fit if all filters are autofit + autoFit = autoFit || filter.autoFit; + + legacy = legacy || filter.legacy; + } + + filterStack.push(state); + + state.resolution = resolution; + + state.legacy = legacy; + + // round to whole number based on resolution + // TODO move that to the shader too? + state.sourceFrame = target.filterArea || target.getBounds(true); + + state.sourceFrame.pad(padding); + + if (autoFit) + { + state.sourceFrame.fit(this.renderer.renderTexture.destinationFrame); + } + + state.sourceFrame.round(resolution); + + state.renderTexture = this.getPotFilterTexture(state.sourceFrame.width, state.sourceFrame.height, resolution); + state.filters = filters; + + state.destinationFrame.width = state.renderTexture.width; + state.destinationFrame.height = state.renderTexture.height; + + state.renderTexture.filterFrame = state.sourceFrame; + renderer.renderTexture.bind(state.renderTexture, state.sourceFrame);// /, state.destinationFrame); + renderer.renderTexture.clear(); + } + + /** + * Pops off the filter and applies it. + * + */ + pop() + { + const renderer = this.renderer; + const filterStack = renderer.renderTexture.defaultFilterStack; + const state = filterStack.pop(); + const filters = state.filters; + + this.activeState = state; + + const globalUniforms = this.globalUniforms.uniforms; + + globalUniforms.sourceFrame = state.sourceFrame; + globalUniforms.destinationFrame = state.destinationFrame; + globalUniforms.resolution = state.resolution; + + // only update the rect if its legacy.. + if (state.legacy) + { + const filterArea = globalUniforms.filterArea; + const filterClamp = globalUniforms.filterClamp; + + filterArea[0] = state.destinationFrame.width; + filterArea[1] = state.destinationFrame.height; + filterArea[2] = state.sourceFrame.x; + filterArea[3] = state.sourceFrame.y; + + filterClamp[0] = 0.5 / state.resolution / state.destinationFrame.width; + filterClamp[1] = 0.5 / state.resolution / state.destinationFrame.height; + filterClamp[2] = (state.sourceFrame.width - 0.5) / state.resolution / state.destinationFrame.width; + filterClamp[3] = (state.sourceFrame.height - 0.5) / state.resolution / state.destinationFrame.height; + } + + this.globalUniforms.update(); + + const lastState = filterStack[filterStack.length - 1]; + + if (filters.length === 1) + { + filters[0].apply(this, state.renderTexture, lastState.renderTexture, false, state); + + this.returnFilterTexture(state.renderTexture); + } + else + { + let flip = state.renderTexture; + let flop = this.getPotFilterTexture( + flip.width, + flip.height, + state.resolution + ); + + flop.filterFrame = flip.filterFrame; + + let i = 0; + + for (i = 0; i < filters.length - 1; ++i) + { + filters[i].apply(this, flip, flop, true, state); + + const t = flip; + + flip = flop; + flop = t; + } + + filters[i].apply(this, flip, lastState.renderTexture, false, state); + + this.returnFilterTexture(flip); + this.returnFilterTexture(flop); + } + + this.statePool.push(state); + } + + /** + * Draws a filter. + * + * @param {PIXI.Filter} filter - The filter to draw. + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + * @param {boolean} clear - Should the output be cleared before rendering to it + */ + applyFilter(filter, input, output, clear) + { + const renderer = this.renderer; + + renderer.renderTexture.bind(output, output ? output.filterFrame : null); + + if (clear) + { + // gl.disable(gl.SCISSOR_TEST); + renderer.renderTexture.clear(); + // gl.enable(gl.SCISSOR_TEST); + } + + // set the uniforms.. + filter.uniforms.uSampler = input; + filter.uniforms.filterGlobals = this.globalUniforms; + + // TODO make it so that the order of this does not matter.. + // because it does at the moment cos of global uniforms. + // they need to get resynced + + renderer.state.setState(filter.state); + renderer.shader.bind(filter); + + if (filter.legacy) + { + this.quadUv.map(input._frame, input.filterFrame); + + renderer.geometry.bind(this.quadUv); + renderer.geometry.draw(DRAW_MODES.TRIANGLES); + } + else + { + renderer.geometry.bind(this.quad); + renderer.geometry.draw(DRAW_MODES.TRIANGLE_STRIP); + } + } + + /** + * Calculates the mapped matrix. + * + * TODO playing around here.. this is temporary - (will end up in the shader) + * this returns a matrix that will normalise map filter cords in the filter to screen space + * + * @param {PIXI.Matrix} outputMatrix - the matrix to output to. + * @return {PIXI.Matrix} The mapped matrix. + */ + calculateScreenSpaceMatrix(outputMatrix) + { + const currentState = this.activeState; + + return filterTransforms.calculateScreenSpaceMatrix( + outputMatrix, + currentState.sourceFrame, + currentState.destinationFrame + ); + } + + /** + * This will map the filter coord so that a texture can be used based on the transform of a sprite + * + * @param {PIXI.Matrix} outputMatrix - The matrix to output to. + * @param {PIXI.Sprite} sprite - The sprite to map to. + * @return {PIXI.Matrix} The mapped matrix. + */ + calculateSpriteMatrix(outputMatrix, sprite) + { + const currentState = this.activeState; + + return filterTransforms.calculateSpriteMatrix( + outputMatrix, + currentState.sourceFrame, + currentState.destinationFrame, + sprite + ); + } + + /** + * Destroys this Filter System. + * + * @param {boolean} [contextLost=false] context was lost, do not free shaders + * + */ + destroy(contextLost = false) + { + if (!contextLost) + { + this.emptyPool(); + } + else + { + this.texturePool = {}; + } + } + + /** + * Gets a Power-of-Two render texture. + * + * TODO move to a seperate class could be on renderer? + * also - could cause issue with multiple contexts? + * + * @private + * @param {WebGLRenderingContext} gl - The webgl rendering context + * @param {number} minWidth - The minimum width of the render target. + * @param {number} minHeight - The minimum height of the render target. + * @param {number} resolution - The resolution of the render target. + * @return {PIXI.RenderTarget} The new render target. + */ + getPotFilterTexture(minWidth, minHeight, resolution) + { + minWidth = bitTwiddle.nextPow2(minWidth); + minHeight = bitTwiddle.nextPow2(minHeight); + resolution = resolution || 1; + + const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + + if (!this.texturePool[key]) + { + this.texturePool[key] = []; + } + + let renderTexture = this.texturePool[key].pop(); + + if (!renderTexture) + { + // temporary bypass cache.. + // internally - this will cause a texture to be bound.. + renderTexture = RenderTexture.create({ + width: minWidth, + height: minHeight, + resolution, + }); + } + + return renderTexture; + } + + /** + * Gets extra render texture to use inside current filter + * + * @param {number} resolution resolution of the renderTexture + * @returns {PIXI.RenderTexture} + */ + getFilterTexture(resolution) + { + const rt = this.activeState.renderTexture; + + const filterTexture = this.getPotFilterTexture(rt.width, rt.height, resolution || rt.baseTexture.resolution); + + filterTexture.filterFrame = rt.filterFrame; + + return filterTexture; + } + + /** + * Frees a render texture back into the pool. + * + * @param {PIXI.RenderTarget} renderTexture - The renderTarget to free + */ + returnFilterTexture(renderTexture) + { + renderTexture.filterFrame = null; + + const base = renderTexture.baseTexture; + + const minWidth = base.width; + const minHeight = base.height; + + const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + + this.texturePool[key].push(renderTexture); + } + + /** + * Empties the texture pool. + * + */ + emptyPool() + { + for (const i in this.texturePool) + { + const textures = this.texturePool[i]; + + if (textures) + { + for (let j = 0; j < textures.length; j++) + { + textures[j].destroy(true); + } + } + } + + this.texturePool = {}; + } +} diff --git a/packages/core/src/filters/defaultFilter.frag b/packages/core/src/filters/defaultFilter.frag new file mode 100644 index 0000000..a14c1be --- /dev/null +++ b/packages/core/src/filters/defaultFilter.frag @@ -0,0 +1,22 @@ +varying vec2 vTextureCoord; +varying vec2 vFilterCoord; + +uniform sampler2D uSampler; +uniform sampler2D filterSampler; + +void main(void){ + vec4 masky = texture2D(filterSampler, vFilterCoord); + vec4 sample = texture2D(uSampler, vTextureCoord); + vec4 color; + if(mod(vFilterCoord.x, 1.0) > 0.5) + { + color = vec4(1.0, 0.0, 0.0, 1.0); + } + else + { + color = vec4(0.0, 1.0, 0.0, 1.0); + } + // gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0); + gl_FragColor = mix(sample, masky, 0.5); + gl_FragColor *= sample.a; +} diff --git a/packages/core/src/filters/defaultFilter.vert b/packages/core/src/filters/defaultFilter.vert new file mode 100644 index 0000000..e1febca --- /dev/null +++ b/packages/core/src/filters/defaultFilter.vert @@ -0,0 +1,14 @@ +attribute vec2 aVertexPosition; +attribute vec2 aTextureCoord; + +uniform mat3 projectionMatrix; +uniform mat3 filterMatrix; + +varying vec2 vTextureCoord; +varying vec2 vFilterCoord; + +void main(void){ + gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); + vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy; + vTextureCoord = aTextureCoord ; +} diff --git a/packages/core/src/filters/filterTransforms.js b/packages/core/src/filters/filterTransforms.js new file mode 100644 index 0000000..87a3cd6 --- /dev/null +++ b/packages/core/src/filters/filterTransforms.js @@ -0,0 +1,50 @@ +import { Matrix } from '@pixi/math'; + +/** + * Calculates the mapped matrix + * @param filterArea {Rectangle} The filter area + * @param sprite {Sprite} the target sprite + * @param outputMatrix {Matrix} @alvin + * @private + */ +// this returns a matrix that will normalise map filter cords in the filter to screen space +export function calculateScreenSpaceMatrix(outputMatrix, filterArea, textureSize) +{ + // TODO unwrap? + const mappedMatrix = outputMatrix.identity(); + + mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); + + mappedMatrix.scale(textureSize.width, textureSize.height); + + return mappedMatrix; +} + +export function calculateNormalizedScreenSpaceMatrix(outputMatrix, filterArea, textureSize) +{ + const mappedMatrix = outputMatrix.identity(); + + mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); + + const translateScaleX = (textureSize.width / filterArea.width); + const translateScaleY = (textureSize.height / filterArea.height); + + mappedMatrix.scale(translateScaleX, translateScaleY); + + return mappedMatrix; +} + +// this will map the filter coord so that a texture can be used based on the transform of a sprite +export function calculateSpriteMatrix(outputMatrix, filterArea, textureSize, sprite) +{ + const orig = sprite._texture.orig; + const mappedMatrix = outputMatrix.set(textureSize.width, 0, 0, textureSize.height, filterArea.x, filterArea.y); + const worldTransform = sprite.worldTransform.copyTo(Matrix.TEMP_MATRIX); + + worldTransform.invert(); + mappedMatrix.prepend(worldTransform); + mappedMatrix.scale(1.0 / orig.width, 1.0 / orig.height); + mappedMatrix.translate(sprite.anchor.x, sprite.anchor.y); + + return mappedMatrix; +} diff --git a/packages/core/src/filters/spriteMask/SpriteMaskFilter.js b/packages/core/src/filters/spriteMask/SpriteMaskFilter.js new file mode 100644 index 0000000..91697b7 --- /dev/null +++ b/packages/core/src/filters/spriteMask/SpriteMaskFilter.js @@ -0,0 +1,63 @@ +import Filter from '../Filter'; +import { Matrix } from '@pixi/math'; +import vertex from './spriteMaskFilter.vert'; +import fragment from './spriteMaskFilter.frag'; +import { default as TextureMatrix } from '../../textures/TextureMatrix'; + +/** + * The SpriteMaskFilter class + * + * @class + * @extends PIXI.Filter + * @memberof PIXI + */ +export default class SpriteMaskFilter extends Filter +{ + /** + * @param {PIXI.Sprite} sprite - the target sprite + */ + constructor(sprite) + { + const maskMatrix = new Matrix(); + + super(vertex, fragment); + + sprite.renderable = false; + + this.maskSprite = sprite; + this.maskMatrix = maskMatrix; + } + + /** + * Applies the filter + * + * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + */ + apply(filterManager, input, output) + { + const maskSprite = this.maskSprite; + const tex = this.maskSprite.texture; + + if (!tex.valid) + { + return; + } + if (!tex.transform) + { + // margin = 0.0, let it bleed a bit, shader code becomes easier + // assuming that atlas textures were made with 1-pixel padding + tex.transform = new TextureMatrix(tex, 0.0); + } + tex.transform.update(); + + this.uniforms.mask = tex; + this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite) + .prepend(tex.transform.mapCoord); + this.uniforms.alpha = maskSprite.worldAlpha; + this.uniforms.maskClamp = tex.transform.uClampFrame; + + filterManager.applyFilter(this, input, output); + } +} diff --git a/packages/core/src/filters/spriteMask/spriteMaskFilter.frag b/packages/core/src/filters/spriteMask/spriteMaskFilter.frag new file mode 100644 index 0000000..0e4aef8 --- /dev/null +++ b/packages/core/src/filters/spriteMask/spriteMaskFilter.frag @@ -0,0 +1,23 @@ +varying vec2 vMaskCoord; +varying vec2 vTextureCoord; + +uniform sampler2D uSampler; +uniform sampler2D mask; +uniform float alpha; +uniform vec4 maskClamp; + +void main(void) +{ + float clip = step(3.5, + step(maskClamp.x, vMaskCoord.x) + + step(maskClamp.y, vMaskCoord.y) + + step(vMaskCoord.x, maskClamp.z) + + step(vMaskCoord.y, maskClamp.w)); + + vec4 original = texture2D(uSampler, vTextureCoord); + vec4 masky = texture2D(mask, vMaskCoord); + + original *= (masky.r * masky.a * alpha * clip); + + gl_FragColor = original; +} diff --git a/packages/core/src/filters/spriteMask/spriteMaskFilter.vert b/packages/core/src/filters/spriteMask/spriteMaskFilter.vert new file mode 100644 index 0000000..d23a30c --- /dev/null +++ b/packages/core/src/filters/spriteMask/spriteMaskFilter.vert @@ -0,0 +1,16 @@ +attribute vec2 aVertexPosition; +attribute vec2 aTextureCoord; + +uniform mat3 projectionMatrix; +uniform mat3 otherMatrix; + +varying vec2 vMaskCoord; +varying vec2 vTextureCoord; + +void main(void) +{ + gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); + + vTextureCoord = aTextureCoord; + vMaskCoord = ( otherMatrix * vec3( aTextureCoord, 1.0) ).xy; +} diff --git a/packages/core/src/framebuffer/FrameBuffer.js b/packages/core/src/framebuffer/FrameBuffer.js new file mode 100644 index 0000000..84e9fcc --- /dev/null +++ b/packages/core/src/framebuffer/FrameBuffer.js @@ -0,0 +1,106 @@ +import Texture from '../textures/BaseTexture'; +import { FORMATS, TYPES } from '@pixi/constants'; + +/** + * Frame buffer + * @class + * @memberof PIXI + */ +export default class FrameBuffer +{ + constructor(width, height) + { + this.width = width || 100; + this.height = height || 100; + + this.stencil = false; + this.depth = false; + + this.dirtyId = 0; + this.dirtyFormat = 0; + this.dirtySize = 0; + + this.depthTexture = null; + this.colorTextures = []; + + this.glFrameBuffers = {}; + } + + get colorTexture() + { + return this.colorTextures[0]; + } + + addColorTexture(index, texture) + { + // TODO add some validation to the texture - same width / height etc? + this.colorTextures[index || 0] = texture || new Texture(null, { scaleMode: 0, + resolution: 1, + mipmap: false, + width: this.width, + height: this.height });// || new Texture(); + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + addDepthTexture(texture) + { + /* eslint-disable max-len */ + this.depthTexture = texture || new Texture(null, { scaleMode: 0, + resolution: 1, + width: this.width, + height: this.height, + mipmap: false, + format: FORMATS.DEPTH_COMPONENT, + type: TYPES.UNSIGNED_SHORT });// UNSIGNED_SHORT; + /* eslint-disable max-len */ + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + enableDepth() + { + this.depth = true; + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + enableStencil() + { + this.stencil = true; + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + resize(width, height) + { + if (width === this.width && height === this.height) return; + + this.width = width; + this.height = height; + + this.dirtyId++; + this.dirtySize++; + + for (let i = 0; i < this.colorTextures.length; i++) + { + this.colorTextures[i].setSize(width, height); + } + + if (this.depthTexture) + { + this.depthTexture.setSize(width, height); + } + } +} diff --git a/packages/core/src/framebuffer/FramebufferSystem.js b/packages/core/src/framebuffer/FramebufferSystem.js new file mode 100644 index 0000000..2cc2437 --- /dev/null +++ b/packages/core/src/framebuffer/FramebufferSystem.js @@ -0,0 +1,246 @@ +import System from '../System'; +import { Rectangle } from '@pixi/math'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class FramebufferSystem extends System +{ + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + contextChange() + { + this.gl = this.renderer.gl; + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + this.current = null; + this.viewport = new Rectangle(); + + this.drawBufferExtension = this.renderer.context.extensions.drawBuffers; + } + + bind(framebuffer, frame) + { + const gl = this.gl; + + this.current = framebuffer; + + if (framebuffer) + { + // TODO cacheing layer! + + const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID] || this.initFramebuffer(framebuffer); + + gl.bindFramebuffer(gl.FRAMEBUFFER, fbo.framebuffer); + // makesure all textures are unbound.. + + // now check for updates... + if (fbo.dirtyId !== framebuffer.dirtyId) + { + fbo.dirtyId = framebuffer.dirtyId; + + if (fbo.dirtyFormat !== framebuffer.dirtyFormat) + { + fbo.dirtyFormat = framebuffer.dirtyFormat; + this.updateFramebuffer(framebuffer); + } + else if (fbo.dirtySize !== framebuffer.dirtySize) + { + fbo.dirtySize = framebuffer.dirtySize; + this.resizeFramebuffer(framebuffer); + } + } + + for (let i = 0; i < framebuffer.colorTextures.length; i++) + { + if (framebuffer.colorTextures[i].texturePart) + { + this.renderer.texture.unbind(framebuffer.colorTextures[i].texture); + } + else + { + this.renderer.texture.unbind(framebuffer.colorTextures[i]); + } + } + + if (framebuffer.depthTexture) + { + this.renderer.texture.unbind(framebuffer.depthTexture); + } + + if (frame) + { + this.setViewport(frame.x, frame.y, frame.width, frame.height); + } + else + { + this.setViewport(0, 0, framebuffer.width, framebuffer.height); + } + } + else + { + gl.bindFramebuffer(gl.FRAMEBUFFER, null); + + if (frame) + { + this.setViewport(frame.x, frame.y, frame.width, frame.height); + } + else + { + this.setViewport(0, 0, this.renderer.width, this.renderer.height); + } + } + } + + setViewport(x, y, width, height) + { + const v = this.viewport; + + if (v.width !== width || v.height !== height || v.x !== x || v.y !== y) + { + v.x = x; + v.y = y; + v.width = width; + v.height = height; + + this.gl.viewport(x, y, width, height); + } + } + + get size() + { + if (this.current) + { + // TODO store temp + return { x: 0, y: 0, width: this.current.width, height: this.current.height }; + } + + return { x: 0, y: 0, width: this.renderer.width, height: this.renderer.height }; + } + + clear(r, g, b, a) + { + const gl = this.gl; + + // TODO clear color can be set only one right? + gl.clearColor(r, g, b, a); + gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); + } + + // private functions... + + initFramebuffer(framebuffer) + { + const gl = this.gl; + + // TODO - make this a class? + const fbo = { + framebuffer: gl.createFramebuffer(), + stencil: null, + dirtyId: 0, + dirtyFormat: 0, + dirtySize: 0, + }; + + framebuffer.glFrameBuffers[this.CONTEXT_UID] = fbo; + + return fbo; + } + + resizeFramebuffer(framebuffer) + { + const gl = this.gl; + + if (framebuffer.stencil || framebuffer.depth) + { + gl.bindRenderbuffer(gl.RENDERBUFFER, this.stencil); + gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); + } + } + + updateFramebuffer(framebuffer) + { + const gl = this.gl; + + const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID]; + + // bind the color texture + const colorTextures = framebuffer.colorTextures; + + let count = colorTextures.length; + + if (!this.drawBufferExtension) + { + count = Math.min(count, 1); + } + + const activeTextures = []; + + for (let i = 0; i < count; i++) + { + const texture = framebuffer.colorTextures[i]; + + if (texture.texturePart) + { + this.renderer.texture.bind(texture.texture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.COLOR_ATTACHMENT0 + i, + gl.TEXTURE_CUBE_MAP_NEGATIVE_X + texture.side, + texture.texture._glTextures[this.CONTEXT_UID].texture, + 0); + } + else + { + this.renderer.texture.bind(texture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.COLOR_ATTACHMENT0 + i, + gl.TEXTURE_2D, + texture._glTextures[this.CONTEXT_UID].texture, + 0); + } + + activeTextures.push(gl.COLOR_ATTACHMENT0 + i); + } + + if (this.drawBufferExtension && activeTextures.length > 1) + { + this.drawBufferExtension.drawBuffersWEBGL(activeTextures); + } + + if (framebuffer.depthTexture) + { + const depthTextureExt = this.renderer.context.extensions.depthTexture; + + if (depthTextureExt) + { + const depthTexture = framebuffer.depthTexture; + + this.renderer.texture.bind(depthTexture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.DEPTH_ATTACHMENT, + gl.TEXTURE_2D, + depthTexture._glTextures[this.CONTEXT_UID].texture, + 0); + } + } + + if (framebuffer.stencil || framebuffer.depth) + { + fbo.stencil = gl.createRenderbuffer(); + + gl.bindRenderbuffer(gl.RENDERBUFFER, fbo.stencil); + + // TODO.. this is depth AND stencil? + gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, fbo.stencil); + gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); + // fbo.enableStencil(); + } + } +} diff --git a/packages/core/src/geometry/GLBuffer.js b/packages/core/src/geometry/GLBuffer.js new file mode 100644 index 0000000..0477e8a --- /dev/null +++ b/packages/core/src/geometry/GLBuffer.js @@ -0,0 +1,9 @@ +export default class GLBuffer +{ + constructor(buffer) + { + this.buffer = buffer; + this.updateID = -1; + this.byteLength = -1; + } +} diff --git a/packages/core/src/geometry/GeometrySystem.js b/packages/core/src/geometry/GeometrySystem.js new file mode 100644 index 0000000..959f827 --- /dev/null +++ b/packages/core/src/geometry/GeometrySystem.js @@ -0,0 +1,388 @@ +import System from '../System'; +import GLBuffer from './GLBuffer'; + +const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class GeometrySystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this._activeGeometry = null; + this._activeVao = null; + + this.hasVao = true; + this.hasInstance = true; + } + + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + contextChange() + { + const gl = this.gl = this.renderer.gl; + + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + + // webgl2 + if (!gl.createVertexArray) + { + // webgl 1! + let nativeVaoExtension = this.renderer.context.extensions.vertexArrayObject; + + if (this.renderer.legacy) + { + nativeVaoExtension = null; + } + + if (nativeVaoExtension) + { + gl.createVertexArray = () => + nativeVaoExtension.createVertexArrayOES(); + + gl.bindVertexArray = (vao) => + nativeVaoExtension.bindVertexArrayOES(vao); + + gl.deleteVertexArray = (vao) => + nativeVaoExtension.deleteVertexArrayOES(vao); + } + else + { + this.hasVao = false; + gl.createVertexArray = () => + { + // empty + }; + + gl.bindVertexArray = () => + { + // empty + }; + + gl.deleteVertexArray = () => + { + // empty + }; + } + } + + if (!gl.vertexAttribDivisor) + { + const instanceExt = gl.getExtension('ANGLE_instanced_arrays'); + + if (instanceExt) + { + gl.vertexAttribDivisor = (a, b) => + instanceExt.vertexAttribDivisorANGLE(a, b); + + gl.drawElementsInstanced = (a, b, c, d, e) => + instanceExt.drawElementsInstancedANGLE(a, b, c, d, e); + + gl.drawArraysInstanced = (a, b, c, d) => + instanceExt.drawArraysInstancedANGLE(a, b, c, d); + } + else + { + this.hasInstance = false; + } + } + } + + /** + * Binds geometry so that is can be drawn. Creating a Vao if required + * @private + * @param {PIXI.Geometry} geometry instance of geometry to bind + */ + bind(geometry, shader) + { + shader = shader || this.renderer.shader.shader; + + const gl = this.gl; + + // not sure the best way to address this.. + // currently different shaders require different VAOs for the same geometry + // Still mulling over the best way to solve this one.. + // will likely need to modify the shader attribute locations at run time! + let vaos = geometry.glVertexArrayObjects[this.CONTEXT_UID]; + + if (!vaos) + { + geometry.glVertexArrayObjects[this.CONTEXT_UID] = vaos = {}; + } + + const vao = vaos[shader.program.id] || this.initGeometryVao(geometry, shader.program); + + this._activeGeometry = geometry; + + if (this._activeVao !== vao) + { + this._activeVao = vao; + + if (this.hasVao) + { + gl.bindVertexArray(vao); + } + else + { + this.activateVao(geometry, shader.program); + } + } + + // TODO - optimise later! + // don't need to loop through if nothing changed! + // maybe look to add an 'autoupdate' to geometry? + this.updateBuffers(); + } + + reset() + { + this.unbind(); + } + + updateBuffers() + { + const geometry = this._activeGeometry; + const gl = this.gl; + + for (let i = 0; i < geometry.buffers.length; i++) + { + const buffer = geometry.buffers[i]; + + const glBuffer = buffer._glBuffers[this.CONTEXT_UID]; + + if (buffer._updateID !== glBuffer.updateID) + { + glBuffer.updateID = buffer._updateID; + + // TODO can cache this on buffer! maybe added a getter / setter? + const type = buffer.index ? gl.ELEMENT_ARRAY_BUFFER : gl.ARRAY_BUFFER; + const drawType = buffer.static ? gl.STATIC_DRAW : gl.DYNAMIC_DRAW; + + gl.bindBuffer(type, glBuffer.buffer); + + if (glBuffer.byteLength >= buffer.data.byteLength) + { + // offset is always zero for now! + gl.bufferSubData(type, 0, buffer.data); + } + else + { + gl.bufferData(type, buffer.data, drawType); + } + } + } + } + + checkCompatability(geometry, program) + { + // geometry must have at least all the attributes that the shader requires. + const geometryAttributes = geometry.attributes; + const shaderAttributes = program.attributeData; + + for (const j in shaderAttributes) + { + if (!geometryAttributes[j]) + { + throw new Error(`shader and geometry incompatible, geometry missing the "${j}" attribute`); + } + } + } + + /** + * Creates a Vao with the same structure as the geometry and stores it on the geometry. + * @private + * @param {PIXI.Geometry} geometry instance of geometry to to generate Vao for + */ + initGeometryVao(geometry, program) + { + this.checkCompatability(geometry, program); + + const gl = this.gl; + const CONTEXT_UID = this.CONTEXT_UID; + const buffers = geometry.buffers; + const attributes = geometry.attributes; + + const tempStride = {}; + const tempStart = {}; + + for (const j in buffers) + { + tempStride[j] = 0; + tempStart[j] = 0; + } + + for (const j in attributes) + { + if (!attributes[j].size && program.attributeData[j]) + { + attributes[j].size = program.attributeData[j].size; + } + + tempStride[attributes[j].buffer] += attributes[j].size * byteSizeMap[attributes[j].type]; + } + + for (const j in attributes) + { + const attribute = attributes[j]; + const attribSize = attribute.size; + + if (attribute.stride === undefined) + { + if (tempStride[attribute.buffer] === attribSize * byteSizeMap[attribute.type]) + { + attribute.stride = 0; + } + else + { + attribute.stride = tempStride[attribute.buffer]; + } + } + + if (attribute.start === undefined) + { + attribute.start = tempStart[attribute.buffer]; + + tempStart[attribute.buffer] += attribSize * byteSizeMap[attribute.type]; + } + } + + // first update - and create the buffers! + // only create a gl buffer if it actually gets + for (let i = 0; i < buffers.length; i++) + { + const buffer = buffers[i]; + + if (!buffer._glBuffers[CONTEXT_UID]) + { + buffer._glBuffers[CONTEXT_UID] = new GLBuffer(gl.createBuffer()); + } + } + + // TODO - maybe make this a data object? + // lets wait to see if we need to first! + const vao = gl.createVertexArray(); + + gl.bindVertexArray(vao); + + this.activateVao(geometry, program); + + gl.bindVertexArray(null); + + // add it to the cache! + geometry.glVertexArrayObjects[this.CONTEXT_UID][program.id] = vao; + + return vao; + } + + activateVao(geometry, program) + { + const gl = this.gl; + const CONTEXT_UID = this.CONTEXT_UID; + const buffers = geometry.buffers; + const attributes = geometry.attributes; + + if (geometry.indexBuffer) + { + // first update the index buffer if we have one.. + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, geometry.indexBuffer._glBuffers[CONTEXT_UID].buffer); + } + + let lastBuffer = null; + + // add a new one! + for (const j in attributes) + { + const attribute = attributes[j]; + const buffer = buffers[attribute.buffer]; + const glBuffer = buffer._glBuffers[CONTEXT_UID]; + + if (program.attributeData[j]) + { + if (lastBuffer !== glBuffer) + { + gl.bindBuffer(gl.ARRAY_BUFFER, glBuffer.buffer); + + lastBuffer = glBuffer; + } + + const location = program.attributeData[j].location; + + // TODO introduce state again + // we can optimise this for older devices that have no VAOs + gl.enableVertexAttribArray(location); + + gl.vertexAttribPointer(location, + attribute.size, + attribute.type || gl.FLOAT, + attribute.normalized, + attribute.stride, + attribute.start); + + if (attribute.instance) + { + // TODO calculate instance count based of this... + if (this.hasInstance) + { + gl.vertexAttribDivisor(location, 1); + } + else + { + throw new Error('geometry error, GPU Instancing is not supported on this device'); + } + } + } + } + } + + draw(type, size, start, instanceCount) + { + const gl = this.gl; + const geometry = this._activeGeometry; + + // TODO.. this should not change so maybe cache the function? + + if (geometry.indexBuffer) + { + if (geometry.instanced) + { + /* eslint-disable max-len */ + gl.drawElementsInstanced(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2, instanceCount || 1); + /* eslint-enable max-len */ + } + else + { + gl.drawElements(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2); + } + } + else + if (geometry.instanced) + { + // TODO need a better way to calculate size.. + gl.drawArraysInstanced(type, start, size || geometry.getSize(), instanceCount || 1); + } + else + { + gl.drawArrays(type, start, size || geometry.getSize()); + } + + return this; + } + + unbind() + { + this.gl.bindVertexArray(null); + this._activeVao = null; + this._activeGeometry = null; + } +} diff --git a/packages/core/src/geometry/utils/setVertexAttribArrays.js b/packages/core/src/geometry/utils/setVertexAttribArrays.js new file mode 100644 index 0000000..23b6959 --- /dev/null +++ b/packages/core/src/geometry/utils/setVertexAttribArrays.js @@ -0,0 +1,54 @@ +// var GL_MAP = {}; + +/** + * @param gl {WebGLRenderingContext} The current WebGL context + * @param attribs {*} + * @param state {*} + */ +export default function setVertexAttribArrays(gl, attribs, state) +{ + let i; + + if (state) + { + const tempAttribState = state.tempAttribState; + const attribState = state.attribState; + + for (i = 0; i < tempAttribState.length; i++) + { + tempAttribState[i] = false; + } + + // set the new attribs + for (i = 0; i < attribs.length; i++) + { + tempAttribState[attribs[i].attribute.location] = true; + } + + for (i = 0; i < attribState.length; i++) + { + if (attribState[i] !== tempAttribState[i]) + { + attribState[i] = tempAttribState[i]; + + if (state.attribState[i]) + { + gl.enableVertexAttribArray(i); + } + else + { + gl.disableVertexAttribArray(i); + } + } + } + } + else + { + for (i = 0; i < attribs.length; i++) + { + const attrib = attribs[i]; + + gl.enableVertexAttribArray(attrib.attribute.location); + } + } +} diff --git a/packages/core/src/index.js b/packages/core/src/index.js index 58c456a..fd1f547 100644 --- a/packages/core/src/index.js +++ b/packages/core/src/index.js @@ -1,30 +1,30 @@ import * as resources from './textures/resources'; -import * as systems from './renderers/systems'; +import * as systems from './systems'; -export { resources }; export { systems }; +export { resources }; -export { default as Renderer } from './renderers/Renderer'; -export { default as AbstractRenderer } from './renderers/AbstractRenderer'; -export { default as FrameBuffer } from './textures/FrameBuffer'; +export { default as System } from './System'; +export { default as Renderer } from './Renderer'; +export { default as AbstractRenderer } from './AbstractRenderer'; +export { default as FrameBuffer } from './framebuffer/FrameBuffer'; export { default as CubeTexture } from './textures/CubeTexture'; export { default as BaseTexture } from './textures/BaseTexture'; export { default as Texture } from './textures/Texture'; export { default as TextureMatrix } from './textures/TextureMatrix'; -export { default as RenderTexture } from './textures/RenderTexture'; -export { default as BaseRenderTexture } from './textures/BaseRenderTexture'; +export { default as RenderTexture } from './renderTexture/RenderTexture'; +export { default as BaseRenderTexture } from './renderTexture/BaseRenderTexture'; export { default as TextureUvs } from './textures/TextureUvs'; -export { default as State } from './renderers/State'; -export { default as ObjectRenderer } from './renderers/utils/ObjectRenderer'; -export { default as Quad } from './renderers/utils/Quad'; -export { default as QuadUv } from './renderers/utils/QuadUv'; -export { default as checkMaxIfStatmentsInShader } from './renderers/utils/checkMaxIfStatmentsInShader'; +export { default as State } from './state/State'; +export { default as ObjectRenderer } from './batch/ObjectRenderer'; +export { default as Quad } from './utils/Quad'; +export { default as QuadUv } from './utils/QuadUv'; +export { default as checkMaxIfStatmentsInShader } from './shader/utils/checkMaxIfStatmentsInShader'; export { default as Shader } from './shader/Shader'; export { default as Program } from './shader/Program'; export { default as UniformGroup } from './shader/UniformGroup'; -export { default as SpriteMaskFilter } from './renderers/filters/spriteMask/SpriteMaskFilter'; -export { default as Filter } from './renderers/filters/Filter'; +export { default as SpriteMaskFilter } from './filters/spriteMask/SpriteMaskFilter'; +export { default as Filter } from './filters/Filter'; export { default as Attribute } from './geometry/Attribute'; export { default as Buffer } from './geometry/Buffer'; export { default as Geometry } from './geometry/Geometry'; - diff --git a/packages/core/src/mask/MaskSystem.js b/packages/core/src/mask/MaskSystem.js new file mode 100644 index 0000000..99174c2 --- /dev/null +++ b/packages/core/src/mask/MaskSystem.js @@ -0,0 +1,199 @@ +import System from '../System'; +import AlphaMaskFilter from '../filters/spriteMask/SpriteMaskFilter'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class MaskSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + // TODO - we don't need both! + this.scissor = false; + this.scissorData = null; + this.scissorRenderTarget = null; + + this.enableScissor = false; + + this.alphaMaskPool = []; + this.alphaMaskIndex = 0; + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.DisplayObject} target - Display Object to push the mask to + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + push(target, maskData) + { + // TODO the root check means scissor rect will not + // be used on render textures more info here: + // https://github.com/pixijs/pixi.js/pull/3545 + + if (maskData.texture) + { + this.pushSpriteMask(target, maskData); + } + else if (this.enableScissor + && !this.scissor + && this.renderer._activeRenderTarget.root + && !this.renderer.stencil.stencilMaskStack.length + && maskData.isFastRect()) + { + const matrix = maskData.worldTransform; + + let rot = Math.atan2(matrix.b, matrix.a); + + // use the nearest degree! + rot = Math.round(rot * (180 / Math.PI)); + + if (rot % 90) + { + this.pushStencilMask(maskData); + } + else + { + this.pushScissorMask(target, maskData); + } + } + else + { + this.pushStencilMask(maskData); + } + } + + /** + * Removes the last mask from the mask stack and doesn't return it. + * + * @param {PIXI.DisplayObject} target - Display Object to pop the mask from + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + pop(target, maskData) + { + if (maskData.texture) + { + this.popSpriteMask(target, maskData); + } + else if (this.enableScissor && !this.renderer.stencil.stencilMaskStack.length) + { + this.popScissorMask(target, maskData); + } + else + { + this.popStencilMask(target, maskData); + } + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.RenderTarget} target - Display Object to push the sprite mask to + * @param {PIXI.Sprite} maskData - Sprite to be used as the mask + */ + pushSpriteMask(target, maskData) + { + let alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; + + if (!alphaMaskFilter) + { + alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; + } + + alphaMaskFilter[0].resolution = this.renderer.resolution; + alphaMaskFilter[0].maskSprite = maskData; + + // TODO - may cause issues! + target.filterArea = maskData.getBounds(true); + + this.renderer.filterSystem.pushFilter(target, alphaMaskFilter); + + this.alphaMaskIndex++; + } + + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popSpriteMask() + { + this.renderer.filterSystem.popFilter(); + this.alphaMaskIndex--; + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + pushStencilMask(maskData) + { + this.renderer.batch.flush(); + this.renderer.stencil.pushStencil(maskData); + } + + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popStencilMask() + { + // this.renderer.currentRenderer.stop(); + this.renderer.stencil.popStencil(); + } + + /** + * + * @param {PIXI.DisplayObject} target - Display Object to push the mask to + * @param {PIXI.Graphics} maskData - The masking data. + */ + pushScissorMask(target, maskData) + { + maskData.renderable = true; + + const renderTarget = this.renderer._activeRenderTarget; + + const bounds = maskData.getBounds(); + + bounds.fit(renderTarget.size); + maskData.renderable = false; + + this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); + + const resolution = this.renderer.resolution; + + this.renderer.gl.scissor( + bounds.x * resolution, + (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, + bounds.width * resolution, + bounds.height * resolution + ); + + this.scissorRenderTarget = renderTarget; + this.scissorData = maskData; + this.scissor = true; + } + + /** + * + * + */ + popScissorMask() + { + this.scissorRenderTarget = null; + this.scissorData = null; + this.scissor = false; + + // must be scissor! + const gl = this.renderer.gl; + + gl.disable(gl.SCISSOR_TEST); + } +} diff --git a/packages/core/src/mask/StencilSystem.js b/packages/core/src/mask/StencilSystem.js new file mode 100644 index 0000000..62796a5 --- /dev/null +++ b/packages/core/src/mask/StencilSystem.js @@ -0,0 +1,133 @@ +import System from '../System'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class StencilSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + this.stencilMaskStack = []; + } + + /** + * Changes the mask stack that is used by this System. + * + * @param {PIXI.Graphics[]} stencilMaskStack - The mask stack + */ + setMaskStack(stencilMaskStack) + { + const gl = this.renderer.gl; + + if (stencilMaskStack.length !== this.stencilMaskStack.length) + { + if (stencilMaskStack.length === 0) + { + gl.disable(gl.STENCIL_TEST); + } + else + { + gl.enable(gl.STENCIL_TEST); + } + } + + this.stencilMaskStack = stencilMaskStack; + } + + /** + * Applies the Mask and adds it to the current stencil stack. @alvin + * + * @param {PIXI.Graphics} graphics - The mask + */ + pushStencil(graphics) + { + this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); + + // this.renderer._activeRenderTarget.attachStencilBuffer(); + + const gl = this.renderer.gl; + const prevMaskCount = this.stencilMaskStack.length; + + if (prevMaskCount === 0) + { + gl.enable(gl.STENCIL_TEST); + } + + this.stencilMaskStack.push(graphics); + + // Increment the refference stencil value where the new mask overlaps with the old ones. + gl.colorMask(false, false, false, false); + gl.stencilFunc(gl.EQUAL, prevMaskCount, this._getBitwiseMask()); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.INCR); + this.renderer.plugins.graphics.render(graphics); + + this._useCurrent(); + } + + /** + * Removes the last mask from the stencil stack. @alvin + */ + popStencil() + { + this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); + + const gl = this.renderer.gl; + const graphics = this.stencilMaskStack.pop(); + + if (this.stencilMaskStack.length === 0) + { + // the stack is empty! + gl.disable(gl.STENCIL_TEST); + gl.clear(gl.STENCIL_BUFFER_BIT); + gl.clearStencil(0); + } + else + { + // Decrement the refference stencil value where the popped mask overlaps with the other ones + gl.colorMask(false, false, false, false); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.DECR); + this.renderer.plugins.graphics.render(graphics); + + this._useCurrent(); + } + } + + /** + * Setup renderer to use the current stencil data. + */ + _useCurrent() + { + const gl = this.renderer.gl; + + gl.colorMask(true, true, true, true); + gl.stencilFunc(gl.EQUAL, this.stencilMaskStack.length, this._getBitwiseMask()); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP); + } + + /** + * Fill 1s equal to the number of acitve stencil masks. + * + * @return {number} The bitwise mask. + */ + _getBitwiseMask() + { + return (1 << this.stencilMaskStack.length) - 1; + } + + /** + * Destroys the mask stack. + * + */ + destroy() + { + super.destroy(this); + + this.stencilMaskStack = null; + } +} diff --git a/packages/core/src/projection/ProjectionSystem.js b/packages/core/src/projection/ProjectionSystem.js new file mode 100644 index 0000000..6e63b73 --- /dev/null +++ b/packages/core/src/projection/ProjectionSystem.js @@ -0,0 +1,73 @@ +import System from '../System'; +import { Matrix } from '@pixi/math'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class ProjectionSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.projectionMatrix = new Matrix(); + } + + update(destinationFrame, sourceFrame, resolution, root) + { + this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; + this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; + + this.calculateProjection(this.destinationFrame, this.sourceFrame, resolution, root); + + this.renderer.globalUniforms.uniforms.projectionMatrix = this.projectionMatrix; + this.renderer.globalUniforms.update(); + } + + /** + * Updates the projection matrix based on a projection frame (which is a rectangle) + * + * @param {Rectangle} destinationFrame - The destination frame. + * @param {Rectangle} sourceFrame - The source frame. + */ + calculateProjection(destinationFrame, sourceFrame, resolution, root) + { + const pm = this.projectionMatrix; + + // I don't think we will need this line.. + // pm.identity(); + + if (!root) + { + pm.a = (1 / destinationFrame.width * 2) * resolution; + pm.d = (1 / destinationFrame.height * 2) * resolution; + + pm.tx = -1 - (sourceFrame.x * pm.a); + pm.ty = -1 - (sourceFrame.y * pm.d); + } + else + { + pm.a = (1 / destinationFrame.width * 2) * resolution; + pm.d = (-1 / destinationFrame.height * 2) * resolution; + + pm.tx = -1 - (sourceFrame.x * pm.a); + pm.ty = 1 - (sourceFrame.y * pm.d); + } + } + + /** + * Sets the transform of the active render target to the given matrix + * + * @param {PIXI.Matrix} matrix - The transformation matrix + */ + setTransform()// matrix) + { + // this._activeRenderTarget.transform = matrix; + } +} diff --git a/packages/core/src/renderTexture/BaseRenderTexture.js b/packages/core/src/renderTexture/BaseRenderTexture.js new file mode 100644 index 0000000..88eb83b --- /dev/null +++ b/packages/core/src/renderTexture/BaseRenderTexture.js @@ -0,0 +1,137 @@ +import BaseTexture from '../textures/BaseTexture'; +import FrameBuffer from '../framebuffer/FrameBuffer'; + +/** + * A BaseRenderTexture is a special texture that allows any PixiJS display object to be rendered to it. + * + * __Hint__: All DisplayObjects (i.e. Sprites) that render to a BaseRenderTexture should be preloaded + * otherwise black rectangles will be drawn instead. + * + * A BaseRenderTexture takes a snapshot of any Display Object given to its render method. The position + * and rotation of the given Display Objects is ignored. For example: + * + * ```js + * let renderer = PIXI.autoDetectRenderer(1024, 1024, { view: canvas, ratio: 1 }); + * let baseRenderTexture = new PIXI.BaseRenderTexture(renderer, 800, 600); + * let sprite = PIXI.Sprite.fromImage("spinObj_01.png"); + * + * sprite.position.x = 800/2; + * sprite.position.y = 600/2; + * sprite.anchor.x = 0.5; + * sprite.anchor.y = 0.5; + * + * baseRenderTexture.render(sprite); + * ``` + * + * The Sprite in this case will be rendered using its local transform. To render this sprite at 0,0 + * you can clear the transform + * + * ```js + * + * sprite.setTransform() + * + * let baseRenderTexture = new PIXI.BaseRenderTexture(100, 100); + * let renderTexture = new PIXI.RenderTexture(baseRenderTexture); + * + * renderer.render(sprite, renderTexture); // Renders to center of RenderTexture + * ``` + * + * @class + * @extends PIXI.BaseTexture + * @memberof PIXI + */ +export default class BaseRenderTexture extends BaseTexture +{ + /** + * @param {object} [options] + * @param {number} [options.width=100] - The width of the base render texture + * @param {number} [options.height=100] - The height of the base render texture + * @param {PIXI.SCALE_MODES} [options.scaleMode] - See {@link PIXI.SCALE_MODES} for possible values + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the texture being generated + */ + constructor(options) + { + if (typeof options === 'number') + { + /* eslint-disable prefer-rest-params */ + // Backward compatibility of signature + const width = arguments[0]; + const height = arguments[1]; + const scaleMode = arguments[2]; + const resolution = arguments[3]; + + options = { width, height, scaleMode, resolution }; + /* eslint-enable prefer-rest-params */ + } + + super(null, options); + + const { width, height } = options || {}; + + // Set defaults + this.mipmap = false; + this.width = Math.ceil(width) || 100; + this.height = Math.ceil(height) || 100; + this.valid = true; + + /** + * A map of renderer IDs to webgl renderTargets + * + * @private + * @member {object} + */ + // this._glRenderTargets = {}; + + /** + * A reference to the canvas render target (we only need one as this can be shared across renderers) + * + * @private + * @member {object} + */ + this._canvasRenderTarget = null; + + this.clearColor = [0, 0, 0, 0]; + + this.frameBuffer = new FrameBuffer(width * this.resolution, height * this.resolution) + .addColorTexture(0, this); + + // TODO - could this be added the systems? + + /** + * The data structure for the stencil masks + * + * @member {PIXI.Graphics[]} + */ + this.stencilMaskStack = []; + + /** + * The data structure for the filters + * + * @member {PIXI.Graphics[]} + */ + this.filterStack = [{}]; + } + + /** + * Resizes the BaseRenderTexture. + * + * @param {number} width - The width to resize to. + * @param {number} height - The height to resize to. + */ + resize(width, height) + { + width = Math.ceil(width); + height = Math.ceil(height); + this.frameBuffer.resize(width * this.resolution, height * this.resolution); + } + + /** + * Destroys this texture + * + */ + destroy() + { + super.destroy(true); + this.renderer = null; + } +} diff --git a/packages/core/src/renderTexture/RenderTexture.js b/packages/core/src/renderTexture/RenderTexture.js new file mode 100644 index 0000000..f1ecf1f --- /dev/null +++ b/packages/core/src/renderTexture/RenderTexture.js @@ -0,0 +1,153 @@ +import BaseRenderTexture from './BaseRenderTexture'; +import Texture from '../textures/Texture'; + +/** + * A RenderTexture is a special texture that allows any PixiJS display object to be rendered to it. + * + * __Hint__: All DisplayObjects (i.e. Sprites) that render to a RenderTexture should be preloaded + * otherwise black rectangles will be drawn instead. + * + * A RenderTexture takes a snapshot of any Display Object given to its render method. For example: + * + * ```js + * let renderer = PIXI.autoDetectRenderer(1024, 1024, { view: canvas, ratio: 1 }); + * let renderTexture = PIXI.RenderTexture.create(800, 600); + * let sprite = PIXI.Sprite.fromImage("spinObj_01.png"); + * + * sprite.position.x = 800/2; + * sprite.position.y = 600/2; + * sprite.anchor.x = 0.5; + * sprite.anchor.y = 0.5; + * + * renderer.render(sprite, renderTexture); + * ``` + * + * The Sprite in this case will be rendered using its local transform. To render this sprite at 0,0 + * you can clear the transform + * + * ```js + * + * sprite.setTransform() + * + * let renderTexture = new PIXI.RenderTexture.create(100, 100); + * + * renderer.render(sprite, renderTexture); // Renders to center of RenderTexture + * ``` + * + * @class + * @extends PIXI.Texture + * @memberof PIXI + */ +export default class RenderTexture extends Texture +{ + /** + * @param {PIXI.BaseRenderTexture} baseRenderTexture - The renderer used for this RenderTexture + * @param {PIXI.Rectangle} [frame] - The rectangle frame of the texture to show + */ + constructor(baseRenderTexture, frame) + { + // support for legacy.. + let _legacyRenderer = null; + + if (!(baseRenderTexture instanceof BaseRenderTexture)) + { + /* eslint-disable prefer-rest-params, no-console */ + const width = arguments[1]; + const height = arguments[2]; + const scaleMode = arguments[3]; + const resolution = arguments[4]; + + // we have an old render texture.. + console.warn(`Please use RenderTexture.create(${width}, ${height}) instead of the ctor directly.`); + _legacyRenderer = arguments[0]; + /* eslint-enable prefer-rest-params, no-console */ + + frame = null; + baseRenderTexture = new BaseRenderTexture({ + width, + height, + scaleMode, + resolution, + }); + } + + /** + * The base texture object that this texture uses + * + * @member {BaseTexture} + */ + super(baseRenderTexture, frame); + + this.legacyRenderer = _legacyRenderer; + + /** + * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. + * + * @member {boolean} + */ + this.valid = true; + + /** + * FilterSystem temporary storage + * @private + * @member {PIXI.Rectangle} + */ + this.filterFrame = null; + + this._updateUvs(); + } + + /** + * Resizes the RenderTexture. + * + * @param {number} width - The width to resize to. + * @param {number} height - The height to resize to. + * @param {boolean} [resizeBaseTexture=true] - Should the baseTexture.width and height values be resized as well? + */ + resize(width, height, resizeBaseTexture = true) + { + width = Math.ceil(width); + height = Math.ceil(height); + + // TODO - could be not required.. + this.valid = (width > 0 && height > 0); + + this._frame.width = this.orig.width = width; + this._frame.height = this.orig.height = height; + + if (resizeBaseTexture) + { + this.baseTexture.resize(width, height); + } + + this._updateUvs(); + } + + /** + * A short hand way of creating a render texture. + * + * @param {object} [options] - Options + * @param {number} [options.width=100] - The width of the render texture + * @param {number} [options.height=100] - The height of the render texture + * @param {number} [options.scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the texture being generated + * @return {PIXI.RenderTexture} The new render texture + */ + static create(options) + { + // fallback, old-style: create(width, height, scaleMode, resolution) + if (typeof options === 'number') + { + /* eslint-disable prefer-rest-params */ + options = { + width: options, + height: arguments[1], + scaleMode: arguments[2], + resolution: arguments[3], + }; + /* eslint-enable prefer-rest-params */ + } + + return new RenderTexture(new BaseRenderTexture(options)); + } +} diff --git a/packages/core/src/renderTexture/RenderTextureSystem.js b/packages/core/src/renderTexture/RenderTextureSystem.js new file mode 100644 index 0000000..8d0ca61 --- /dev/null +++ b/packages/core/src/renderTexture/RenderTextureSystem.js @@ -0,0 +1,115 @@ +import System from '../System'; +import { Rectangle } from '@pixi/math'; + +const tempRect = new Rectangle(); + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class RenderTextureSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.clearColor = renderer._backgroundColorRgba; + + // TODO moe this property somewhere else! + this.defaultMaskStack = []; + this.defaultFilterStack = [{}]; + + // empty render texture? + this.renderTexture = null; + + this.destinationFrame = new Rectangle(); + } + + bind(renderTexture, sourceFrame, destinationFrame) + { + // TODO - do we want this?? + if (this.renderTexture === renderTexture) return; + this.renderTexture = renderTexture; + + const renderer = this.renderer; + + if (renderTexture) + { + const baseTexture = renderTexture.baseTexture; + + if (!destinationFrame) + { + tempRect.width = baseTexture.realWidth; + tempRect.height = baseTexture.realHeight; + + destinationFrame = tempRect; + } + + if (!sourceFrame) + { + sourceFrame = destinationFrame; + } + + this.renderer.framebuffer.bind(baseTexture.frameBuffer, destinationFrame); + + this.renderer.projection.update(destinationFrame, sourceFrame, baseTexture.resolution, false); + this.renderer.stencil.setMaskStack(baseTexture.stencilMaskStack); + } + else + { + // TODO these validation checks happen deeper down.. + // thing they can be avoided.. + if (!destinationFrame) + { + tempRect.width = renderer.width; + tempRect.height = renderer.height; + + destinationFrame = tempRect; + } + + if (!sourceFrame) + { + sourceFrame = destinationFrame; + } + + renderer.framebuffer.bind(null, destinationFrame); + + // TODO store this.. + this.renderer.projection.update(destinationFrame, sourceFrame, this.renderer.resolution, true); + this.renderer.stencil.setMaskStack(this.defaultMaskStack); + } + + this.destinationFrame.copyFrom(destinationFrame); + } + + /** + * Erases the render texture and fills the drawing area with a colour + * + * @param {number} [clearColor] - The colour + * @return {PIXI.Renderer} Returns itself. + */ + clear(clearColor) + { + if (this.renderTexture) + { + clearColor = clearColor || this.renderTexture.baseTexture.clearColor; + } + else + { + clearColor = clearColor || this.clearColor; + } + + this.renderer.framebuffer.clear(clearColor[0], clearColor[1], clearColor[2], clearColor[3]); + } + + resize()// screenWidth, screenHeight) + { + // resize the root only! + this.bind(null); + } +} diff --git a/packages/core/src/renderers/AbstractRenderer.js b/packages/core/src/renderers/AbstractRenderer.js deleted file mode 100644 index 928670f..0000000 --- a/packages/core/src/renderers/AbstractRenderer.js +++ /dev/null @@ -1,353 +0,0 @@ -import { hex2string, hex2rgb } from '@pixi/utils'; -import { Matrix, Rectangle } from '@pixi/math'; -import { RENDERER_TYPE } from '@pixi/constants'; -import { settings } from '@pixi/settings'; -import { Container } from '@pixi/display'; -import RenderTexture from '../textures/RenderTexture'; -import EventEmitter from 'eventemitter3'; - -const tempMatrix = new Matrix(); - -/** - * 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 EventEmitter - * @memberof PIXI - */ -export default class AbstractRenderer extends EventEmitter -{ - // eslint-disable-next-line valid-jsdoc - /** - * @param {string} system - The name of the system this renderer is for. - * @param {object} [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.transparent=false] - If the render view is transparent, default false - * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false - * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) - * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. The - * resolution of the renderer retina would be 2. - * @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 {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when rendering, - * stopping pixel interpolation. - */ - constructor(system, options, arg2, arg3) - { - super(); - - // Support for constructor(system, screenWidth, screenHeight, options) - if (typeof options === 'number') - { - options = Object.assign({ - width: options, - height: arg2 || settings.RENDER_OPTIONS.height, - }, arg3); - } - - // 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 = RENDERER_TYPE.UNKNOWN; - - /** - * Measurements of the screen. (0, 0, screenWidth, screenHeight) - * - * Its safe to use as filterArea or hitArea for 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 1 - */ - this.resolution = options.resolution || settings.RESOLUTION; - - /** - * Whether the render view is transparent - * - * @member {boolean} - */ - this.transparent = options.transparent; - - /** - * Whether css dimensions of canvas view should be resized to screen dimensions automatically - * - * @member {boolean} - */ - this.autoResize = options.autoResize || false; - - /** - * Tracks the blend modes useful for this renderer. - * - * @member {object} - */ - this.blendModes = null; - - /** - * 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; - - /** - * If true PixiJS will Math.floor() x/y values when rendering, stopping pixel interpolation. - * Handy for crisp pixel art and speed on legacy devices. - * - * @member {boolean} - */ - this.roundPixels = options.roundPixels; - - /** - * The background color as a number. - * - * @member {number} - * @private - */ - this._backgroundColor = 0x000000; - - /** - * The background color as an [R, G, B] array. - * - * @member {number[]} - * @private - */ - this._backgroundColorRgba = [0, 0, 0, 0]; - - /** - * The background color as a string. - * - * @member {string} - * @private - */ - this._backgroundColorString = '#000000'; - - this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter - - /** - * This temporary display object used as the parent of the currently being rendered item - * - * @member {PIXI.DisplayObject} - * @private - */ - this._tempDisplayObjectParent = new Container(); - - /** - * The last root object that the renderer tried to render. - * - * @member {PIXI.DisplayObject} - * @private - */ - this._lastObjectRendered = this._tempDisplayObjectParent; - - /** - * Collection of plugins. - * @readonly - * @member {object} - */ - this.plugins = {}; - } - - /** - * Initialize the plugins. - * - * @protected - * @param {object} staticMap - The dictionary of staticly saved plugins. - */ - initPlugins(staticMap) - { - 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() - { - return this.view.width; - } - - /** - * Same as view.height, actual number of pixels in the canvas by vertical - * - * @member {number} - * @readonly - * @default 600 - */ - get height() - { - return this.view.height; - } - - /** - * Resizes the screen and canvas to the specified width and height - * Canvas dimensions are multiplied by resolution - * - * @param {number} screenWidth - the new width of the screen - * @param {number} screenHeight - the new height of the screen - */ - resize(screenWidth, screenHeight) - { - this.screen.width = screenWidth; - this.screen.height = screenHeight; - - this.view.width = screenWidth * this.resolution; - this.view.height = screenHeight * this.resolution; - - if (this.autoResize) - { - this.view.style.width = `${screenWidth}px`; - this.view.style.height = `${screenHeight}px`; - } - } - - /** - * 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 {PIXI.DisplayObject} displayObject - The displayObject the object will be generated from - * @param {number} scaleMode - Should be one of the scaleMode consts - * @param {number} resolution - The resolution / device pixel ratio of the texture being generated - * @param {PIXI.Rectangle} [region] - The region of the displayObject, that shall be rendered, - * if no region is specified, defaults to the local bounds of the displayObject. - * @return {PIXI.Texture} a texture of the graphics object - */ - generateTexture(displayObject, scaleMode, resolution, region) - { - region = region || displayObject.getLocalBounds(); - - // 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(region.width | 0, region.height | 0, scaleMode, resolution); - - tempMatrix.tx = -region.x; - tempMatrix.ty = -region.y; - - this.render(displayObject, renderTexture, false, tempMatrix, true); - - return renderTexture; - } - - /** - * Removes everything from the renderer and optionally removes the Canvas DOM element. - * - * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. - */ - destroy(removeView) - { - 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); - } - - this.plugins = null; - - this.type = RENDERER_TYPE.UNKNOWN; - - this.view = null; - - this.screen = null; - - this.resolution = 0; - - this.transparent = false; - - this.autoResize = false; - - this.blendModes = null; - - this.options = null; - - this.preserveDrawingBuffer = false; - this.clearBeforeRender = false; - - this.roundPixels = false; - - this._backgroundColor = 0; - this._backgroundColorRgba = null; - this._backgroundColorString = null; - - this._tempDisplayObjectParent = null; - this._lastObjectRendered = null; - } - - /** - * The background color to fill if not transparent - * - * @member {number} - */ - get backgroundColor() - { - return this._backgroundColor; - } - - set backgroundColor(value) // eslint-disable-line require-jsdoc - { - this._backgroundColor = value; - this._backgroundColorString = hex2string(value); - hex2rgb(value, this._backgroundColorRgba); - } -} diff --git a/packages/core/src/renderers/Renderer.js b/packages/core/src/renderers/Renderer.js deleted file mode 100644 index a2020b1..0000000 --- a/packages/core/src/renderers/Renderer.js +++ /dev/null @@ -1,329 +0,0 @@ -import AbstractRenderer from './AbstractRenderer'; -import { sayHello } from '@pixi/utils'; -import MaskSystem from './systems/MaskSystem'; -import StencilSystem from './systems/StencilSystem'; -import FilterSystem from './systems/filter/FilterSystem'; -import FramebufferSystem from './systems/FramebufferSystem'; -import RenderTextureSystem from './systems/RenderTextureSystem'; -import TextureSystem from './systems/textures/TextureSystem'; -import ProjectionSystem from './systems/ProjectionSystem'; -import StateSystem from './systems/StateSystem'; -import GeometrySystem from './systems/geometry/GeometrySystem'; -import ShaderSystem from './systems/shader/ShaderSystem'; -import ContextSystem from './systems/ContextSystem'; -import BatchSystem from './systems/BatchSystem'; -import TextureGCSystem from './systems/textures/TextureGCSystem'; -import { RENDERER_TYPE } from '@pixi/constants'; -import UniformGroup from '../shader/UniformGroup'; -import { Matrix } from '@pixi/math'; -import Runner from 'mini-runner'; - -/** - * 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 Render works by automatically managing webGLBatchs. - * 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 :) - * - * @class - * @memberof PIXI - * @extends PIXI.AbstractRenderer - */ -export default class Renderer extends AbstractRenderer -{ - // eslint-disable-next-line valid-jsdoc - /** - * - * @param {object} [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.transparent=false] - If the render view is transparent, default false - * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false - * @param {boolean} [options.antialias=false] - sets antialias. If not available natively then FXAA - * antialiasing is used - * @param {boolean} [options.forceFXAA=false] - forces FXAA antialiasing to be used over native. - * FXAA is faster, but may not always look as great - * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. - * The resolution of the renderer retina would be 2. - * @param {boolean} [options.clearBeforeRender=true] - This sets if the renderer will clear - * the canvas or not before the new render pass. If you wish to set this to false, you *must* set - * preserveDrawingBuffer to `true`. - * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, - * enable this if you need to call toDataUrl on the webgl context. - * @param {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when - * rendering, stopping pixel interpolation. - * @param {number} [options.backgroundColor=0x000000] - The background color of the rendered area - * (shown if not transparent). - * @param {boolean} [options.legacy=false] - If true PixiJS will aim to ensure compatibility - * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. - * @param {string} [options.powerPreference] - Parameter passed to webgl context, set to "high-performance" - * for devices with dual graphics card - */ - constructor(options, arg2, arg3) - { - super('WebGL', options, arg2, arg3); - - /** - * The type of this renderer as a standardised const - * - * @member {number} - * @see PIXI.RENDERER_TYPE - */ - this.type = RENDERER_TYPE.WEBGL; - - // this will be set by the contextSystem (this.context) - this.gl = null; - this.CONTEXT_UID = 0; - this.legacy = !!options.legacy; - - // TODO legacy! - - // runners! - this.runners = { - destroy: new Runner('destroy'), - contextChange: new Runner('contextChange', 1), - reset: new Runner('reset'), - update: new Runner('update'), - postrender: new Runner('postrender'), - prerender: new Runner('prerender'), - resize: new Runner('resize', 2), - }; - - 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(GeometrySystem, 'geometry') - .addSystem(FramebufferSystem, 'framebuffer') - .addSystem(StencilSystem, 'stencil') - .addSystem(ProjectionSystem, 'projection') - .addSystem(TextureGCSystem, 'textureGC') - .addSystem(FilterSystem, 'filter') - .addSystem(RenderTextureSystem, 'renderTexture') - .addSystem(BatchSystem, 'batch'); - - this.initPlugins(Renderer.__plugins); - - /** - * The options passed in to create a new webgl context. - * - * @member {object} - * @private - */ - if (options.context) - { - this.context.initFromContext(options.context); - } - else - { - this.context.initFromOptions({ - alpha: this.transparent, - antialias: options.antialias, - premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', - stencil: true, - 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); - } - - addSystem(_class, name) - { - if (!name) - { - name = _class.name; - } - - // TODO - read name from class.name.. - - /* - if(name.includes('System')) - { - name = name.replace('System', ''); - name = name.charAt(0).toLowerCase() + name.slice(1); - } - */ - - const system = new _class(this); - - if (this[name]) - { - throw new Error(`Whoops! ${name} is already a manger`); - } - - this[name] = system; - - for (const i in this.runners) - { - this.runners[i].add(system); - } - - return this; - - /** - * 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. - */ - } - - /** - * Renders the object to its webGL view - * - * @param {PIXI.DisplayObject} displayObject - the object to be rendered - * @param {PIXI.RenderTexture} renderTexture - The render texture to render to. - * @param {boolean} [clear] - Should the canvas be cleared before the new render - * @param {PIXI.Transform} [transform] - A transform to apply to the render texture before rendering. - * @param {boolean} [skipUpdateTransform] - Should we skip the update transform pass? - */ - render(displayObject, renderTexture, clear, transform, skipUpdateTransform) - { - // can be handy to know! - this.renderingToScreen = !renderTexture; - - this.runners.prerender.run(); - this.emit('prerender'); - - // 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.parent; - - displayObject.parent = this._tempDisplayObjectParent; - displayObject.updateTransform(); - displayObject.parent = 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.run(); - - this.emit('postrender'); - } - - /** - * Resizes the webGL view to the specified width and height. - * - * @param {number} screenWidth - the new width of the screen - * @param {number} screenHeight - the new height of the screen - */ - resize(screenWidth, screenHeight) - { - AbstractRenderer.prototype.resize.call(this, screenWidth, screenHeight); - - this.runners.resize.run(screenWidth, screenHeight); - } - - /** - * Resets the WebGL state so you can render things however you fancy! - * - * @return {PIXI.Renderer} Returns itself. - */ - reset() - { - this.runners.reset.run(); - - return this; - } - - clear() - { - this.framebuffer.bind(); - this.framebuffer.clear(); - } - - /** - * Removes everything from the renderer (event listeners, spritebatch, etc...) - * - * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. - * See: https://github.com/pixijs/pixi.js/issues/2233 - */ - destroy(removeView) - { - this.runners.destroy.run(); - - // call base destroy - super.destroy(removeView); - - // TODO nullify all the managers.. - this.gl = null; - } - - /** - * 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 - * builds and excluding plugins. - * @name PIXI.Renderer#plugins - * @type {object} - * @readonly - * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. - * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. - * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. - * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. - */ - - /** - * Adds a plugin to the renderer. - * - * @method - * @param {string} pluginName - The name of the plugin. - * @param {Function} ctor - The constructor function or class for the plugin. - */ - static registerPlugin(pluginName, ctor) - { - Renderer.__plugins = Renderer.__plugins || {}; - Renderer.__plugins[pluginName] = ctor; - } -} diff --git a/packages/core/src/renderers/State.js b/packages/core/src/renderers/State.js deleted file mode 100644 index fa3f221..0000000 --- a/packages/core/src/renderers/State.js +++ /dev/null @@ -1,163 +0,0 @@ -/* eslint-disable max-len */ - -const BLEND = 0; -const OFFSET = 1; -const CULLING = 2; -const DEPTH_TEST = 3; -const WINDING = 4; - -/** - * This is a webGL state. It is passed The WebGL StateManager. - * Each mesh renderered may require webGL to be in a different state. - * For example you may want different blend mode or to enable polygon offsets - * - * @class - * @memberof PIXI - */ -export default class State -{ - /** - * - */ - constructor() - { - this.data = 0; - - this.blendMode = 0; - this.polygonOffset = 0; - - this.blend = true; - // this.depthTest = true; - } - - /** - * Activates blending of the computed fragment color values - * - * @member {boolean} - */ - get blend() - { - return !!(this.data & (1 << BLEND)); - } - - set blend(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << BLEND)) !== value) - { - this.data ^= (1 << BLEND); - } - } - - /** - * Activates adding an offset to depth values of polygon's fragments - * - * @member {boolean} - * @default false - */ - get offsets() - { - return !!(this.data & (1 << OFFSET)); - } - - set offsets(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << OFFSET)) !== value) - { - this.data ^= (1 << OFFSET); - } - } - - /** - * Activates culling of polygons. - * - * @member {boolean} - * @default false - */ - get culling() - { - return !!(this.data & (1 << CULLING)); - } - - set culling(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << CULLING)) !== value) - { - this.data ^= (1 << CULLING); - } - } - - /** - * Activates depth comparisons and updates to the depth buffer. - * - * @member {boolean} - * @default false - */ - get depthTest() - { - return !!(this.data & (1 << DEPTH_TEST)); - } - - set depthTest(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << DEPTH_TEST)) !== value) - { - this.data ^= (1 << DEPTH_TEST); - } - } - - /** - * Specifies whether or not front or back-facing polygons can be culled. - * @member {boolean} - * @default false - */ - get clockwiseFrontFace() - { - return !!(this.data & (1 << WINDING)); - } - - set clockwiseFrontFace(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << WINDING)) !== value) - { - this.data ^= (1 << WINDING); - } - } - - /** - * The blend mode to be applied when this state is set. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. - * Setting this mode to anything other than NO_BLEND will automatically switch blending on. - * - * @member {boolean} - * @default PIXI.BLEND_MODES.NORMAL - * @see PIXI.BLEND_MODES - */ - get blendMode() - { - return this._blendMode; - } - - set blendMode(value) // eslint-disable-line require-jsdoc - { - // 17 is NO BLEND - this.blend = (value !== 17); - this._blendMode = value; - } - - /** - * The polygon offset. Setting this property to anything other than 0 will automatically enable poygon offset fill. - * - * @member {number} - * @default 0 - */ - get polygonOffset() - { - return this._polygonOffset; - } - - set polygonOffset(value) // eslint-disable-line require-jsdoc - { - this.offsets = !!value; - this._polygonOffset = value; - } -} - diff --git a/packages/core/src/renderers/filters/Filter.js b/packages/core/src/renderers/filters/Filter.js deleted file mode 100644 index 2073b8d..0000000 --- a/packages/core/src/renderers/filters/Filter.js +++ /dev/null @@ -1,140 +0,0 @@ -import Shader from '../../shader/Shader'; -import Program from '../../shader/Program'; -import State from '../State'; -import { settings } from '@pixi/settings'; -// import extractUniformsFromSrc from './extractUniformsFromSrc'; -import defaultVertex from './defaultFilter.vert'; -import defaultFragment from './defaultFilter.frag'; - -// let math = require('../../math'); -/** - * @class - * @memberof PIXI - * @extends PIXI.Shader - */ -export default class Filter extends Shader -{ - /** - * @param {string} [vertexSrc] - The source of the vertex shader. - * @param {string} [fragmentSrc] - The source of the fragment shader. - * @param {object} [uniforms] - Custom uniforms to use to augment the built-in ones. - */ - constructor(vertexSrc, fragmentSrc, uniforms) - { - const program = Program.from(vertexSrc, fragmentSrc); - - super(program, uniforms); - - /** - * The padding of the filter. Some filters require extra space to breath such as a blur. - * Increasing this will add extra width and height to the bounds of the object that the - * filter is applied to. - * - * @member {number} - */ - this.padding = 0; - - /** - * The resolution of the filter. Setting this to be lower will lower the quality but - * increase the performance of the filter. - * - * @member {number} - */ - this.resolution = settings.RESOLUTION; - - /** - * If enabled is true the filter is applied, if false it will not. - * - * @member {boolean} - */ - this.enabled = true; - - /** - * If enabled, PixiJS will fit the filter area into boundaries for better performance. - * Switch it off if it does not work for specific shader. - * - * @member {boolean} - */ - this.autoFit = true; - - /** - * Legacy filters use position and uvs from attributes - * @member {boolean} - * @readonly - */ - this.legacy = !!this.program.attributeData.aTextureCoord; - - /** - * the webGL state the filter requires to render - * @member {PIXI.State} - */ - this.state = new State(); - } - - /** - * Applies the filter - * - * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from - * @param {PIXI.RenderTarget} input - The input render target. - * @param {PIXI.RenderTarget} output - The target to output to. - * @param {boolean} clear - Should the output be cleared before rendering to it - * @param {object} [currentState] - It's current state of filter. - * There are some useful properties in the currentState : - * target, filters, sourceFrame, destinationFrame, renderTarget, resolution - */ - apply(filterManager, input, output, clear, currentState, derp) // eslint-disable-line no-unused-vars - { - // do as you please! - - filterManager.applyFilter(this, input, output, clear, currentState, derp); - - // or just do a regular render.. - } - - /** - * Sets the blendmode of the filter - * - * @member {number} - * @default PIXI.BLEND_MODES.NORMAL - */ - get blendMode() - { - return this.state.blendMode; - } - - set blendMode(value) // eslint-disable-line require-jsdoc - { - this.state.blendMode = value; - } - - /** - * The default vertex shader source - * - * @static - * @constant - */ - static get defaultVertexSrc() - { - return defaultVertex; - } - - /** - * The default fragment shader source - * - * @static - * @constant - */ - static get defaultFragmentSrc() - { - return defaultFragment; - } -} - -/** - * Used for caching shader IDs - * - * @static - * @private - */ -Filter.SOURCE_KEY_MAP = {}; - diff --git a/packages/core/src/renderers/filters/defaultFilter.frag b/packages/core/src/renderers/filters/defaultFilter.frag deleted file mode 100644 index a14c1be..0000000 --- a/packages/core/src/renderers/filters/defaultFilter.frag +++ /dev/null @@ -1,22 +0,0 @@ -varying vec2 vTextureCoord; -varying vec2 vFilterCoord; - -uniform sampler2D uSampler; -uniform sampler2D filterSampler; - -void main(void){ - vec4 masky = texture2D(filterSampler, vFilterCoord); - vec4 sample = texture2D(uSampler, vTextureCoord); - vec4 color; - if(mod(vFilterCoord.x, 1.0) > 0.5) - { - color = vec4(1.0, 0.0, 0.0, 1.0); - } - else - { - color = vec4(0.0, 1.0, 0.0, 1.0); - } - // gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0); - gl_FragColor = mix(sample, masky, 0.5); - gl_FragColor *= sample.a; -} diff --git a/packages/core/src/renderers/filters/defaultFilter.vert b/packages/core/src/renderers/filters/defaultFilter.vert deleted file mode 100644 index e1febca..0000000 --- a/packages/core/src/renderers/filters/defaultFilter.vert +++ /dev/null @@ -1,14 +0,0 @@ -attribute vec2 aVertexPosition; -attribute vec2 aTextureCoord; - -uniform mat3 projectionMatrix; -uniform mat3 filterMatrix; - -varying vec2 vTextureCoord; -varying vec2 vFilterCoord; - -void main(void){ - gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); - vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy; - vTextureCoord = aTextureCoord ; -} diff --git a/packages/core/src/renderers/filters/filterTransforms.js b/packages/core/src/renderers/filters/filterTransforms.js deleted file mode 100644 index 87a3cd6..0000000 --- a/packages/core/src/renderers/filters/filterTransforms.js +++ /dev/null @@ -1,50 +0,0 @@ -import { Matrix } from '@pixi/math'; - -/** - * Calculates the mapped matrix - * @param filterArea {Rectangle} The filter area - * @param sprite {Sprite} the target sprite - * @param outputMatrix {Matrix} @alvin - * @private - */ -// this returns a matrix that will normalise map filter cords in the filter to screen space -export function calculateScreenSpaceMatrix(outputMatrix, filterArea, textureSize) -{ - // TODO unwrap? - const mappedMatrix = outputMatrix.identity(); - - mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); - - mappedMatrix.scale(textureSize.width, textureSize.height); - - return mappedMatrix; -} - -export function calculateNormalizedScreenSpaceMatrix(outputMatrix, filterArea, textureSize) -{ - const mappedMatrix = outputMatrix.identity(); - - mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); - - const translateScaleX = (textureSize.width / filterArea.width); - const translateScaleY = (textureSize.height / filterArea.height); - - mappedMatrix.scale(translateScaleX, translateScaleY); - - return mappedMatrix; -} - -// this will map the filter coord so that a texture can be used based on the transform of a sprite -export function calculateSpriteMatrix(outputMatrix, filterArea, textureSize, sprite) -{ - const orig = sprite._texture.orig; - const mappedMatrix = outputMatrix.set(textureSize.width, 0, 0, textureSize.height, filterArea.x, filterArea.y); - const worldTransform = sprite.worldTransform.copyTo(Matrix.TEMP_MATRIX); - - worldTransform.invert(); - mappedMatrix.prepend(worldTransform); - mappedMatrix.scale(1.0 / orig.width, 1.0 / orig.height); - mappedMatrix.translate(sprite.anchor.x, sprite.anchor.y); - - return mappedMatrix; -} diff --git a/packages/core/src/renderers/filters/spriteMask/SpriteMaskFilter.js b/packages/core/src/renderers/filters/spriteMask/SpriteMaskFilter.js deleted file mode 100644 index d5a91a4..0000000 --- a/packages/core/src/renderers/filters/spriteMask/SpriteMaskFilter.js +++ /dev/null @@ -1,63 +0,0 @@ -import Filter from '../Filter'; -import { Matrix } from '@pixi/math'; -import vertex from './spriteMaskFilter.vert'; -import fragment from './spriteMaskFilter.frag'; -import { default as TextureMatrix } from '../../../textures/TextureMatrix'; - -/** - * The SpriteMaskFilter class - * - * @class - * @extends PIXI.Filter - * @memberof PIXI - */ -export default class SpriteMaskFilter extends Filter -{ - /** - * @param {PIXI.Sprite} sprite - the target sprite - */ - constructor(sprite) - { - const maskMatrix = new Matrix(); - - super(vertex, fragment); - - sprite.renderable = false; - - this.maskSprite = sprite; - this.maskMatrix = maskMatrix; - } - - /** - * Applies the filter - * - * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from - * @param {PIXI.RenderTarget} input - The input render target. - * @param {PIXI.RenderTarget} output - The target to output to. - */ - apply(filterManager, input, output) - { - const maskSprite = this.maskSprite; - const tex = this.maskSprite.texture; - - if (!tex.valid) - { - return; - } - if (!tex.transform) - { - // margin = 0.0, let it bleed a bit, shader code becomes easier - // assuming that atlas textures were made with 1-pixel padding - tex.transform = new TextureMatrix(tex, 0.0); - } - tex.transform.update(); - - this.uniforms.mask = tex; - this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite) - .prepend(tex.transform.mapCoord); - this.uniforms.alpha = maskSprite.worldAlpha; - this.uniforms.maskClamp = tex.transform.uClampFrame; - - filterManager.applyFilter(this, input, output); - } -} diff --git a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.frag b/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.frag deleted file mode 100644 index 0e4aef8..0000000 --- a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.frag +++ /dev/null @@ -1,23 +0,0 @@ -varying vec2 vMaskCoord; -varying vec2 vTextureCoord; - -uniform sampler2D uSampler; -uniform sampler2D mask; -uniform float alpha; -uniform vec4 maskClamp; - -void main(void) -{ - float clip = step(3.5, - step(maskClamp.x, vMaskCoord.x) + - step(maskClamp.y, vMaskCoord.y) + - step(vMaskCoord.x, maskClamp.z) + - step(vMaskCoord.y, maskClamp.w)); - - vec4 original = texture2D(uSampler, vTextureCoord); - vec4 masky = texture2D(mask, vMaskCoord); - - original *= (masky.r * masky.a * alpha * clip); - - gl_FragColor = original; -} diff --git a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.vert b/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.vert deleted file mode 100644 index d23a30c..0000000 --- a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.vert +++ /dev/null @@ -1,16 +0,0 @@ -attribute vec2 aVertexPosition; -attribute vec2 aTextureCoord; - -uniform mat3 projectionMatrix; -uniform mat3 otherMatrix; - -varying vec2 vMaskCoord; -varying vec2 vTextureCoord; - -void main(void) -{ - gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); - - vTextureCoord = aTextureCoord; - vMaskCoord = ( otherMatrix * vec3( aTextureCoord, 1.0) ).xy; -} diff --git a/packages/core/src/renderers/systems/BatchSystem.js b/packages/core/src/renderers/systems/BatchSystem.js deleted file mode 100644 index 70e9518..0000000 --- a/packages/core/src/renderers/systems/BatchSystem.js +++ /dev/null @@ -1,68 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import ObjectRenderer from '../utils/ObjectRenderer'; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ - -export default class BatchSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this manager works for. - */ - constructor(renderer) - { - super(renderer); - - /** - * An empty renderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.emptyRenderer = new ObjectRenderer(renderer); - - /** - * The currently active ObjectRenderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.currentRenderer = this.emptyRenderer; - } - - /** - * Changes the current renderer to the one given in parameter - * - * @param {PIXI.ObjectRenderer} objectRenderer - The object renderer to use. - */ - setObjectRenderer(objectRenderer) - { - if (this.currentRenderer === objectRenderer) - { - return; - } - - this.currentRenderer.stop(); - this.currentRenderer = objectRenderer; - - this.renderer.state.setState(objectRenderer.state); - - this.currentRenderer.start(); - } - - /** - * This should be called if you wish to do some custom rendering - * It will basically render anything that may be batched up such as sprites - * - */ - flush() - { - this.setObjectRenderer(this.emptyRenderer); - } - - reset() - { - this.setObjectRenderer(this.emptyRenderer); - } -} diff --git a/packages/core/src/renderers/systems/ContextSystem.js b/packages/core/src/renderers/systems/ContextSystem.js deleted file mode 100644 index 43bf827..0000000 --- a/packages/core/src/renderers/systems/ContextSystem.js +++ /dev/null @@ -1,186 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { settings } from '@pixi/settings'; - -let CONTEXT_UID = 0; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class ContextSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this.webGLVersion = 1; - - this.handleContextLost = this.handleContextLost.bind(this); - this.handleContextRestored = this.handleContextRestored.bind(this); - - this.extensions = {}; - - renderer.view.addEventListener('webglcontextlost', this.handleContextLost, false); - renderer.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); - } - - get isLost() - { - return (!this.gl || this.gl.isContextLost()); - } - - contextChange(gl) - { - this.gl = gl; - - // restore a context if it was previously lost - if (gl.isContextLost() && gl.getExtension('WEBGL_lose_context')) - { - gl.getExtension('WEBGL_lose_context').restoreContext(); - } - } - - initFromContext(gl) - { - this.gl = gl; - this.validateContext(gl); - this.renderer.gl = gl; - this.renderer.CONTEXT_UID = CONTEXT_UID++; - this.renderer.runners.contextChange.run(gl); - } - - initFromOptions(options) - { - const gl = this.createContext(this.renderer.view, options); - - this.initFromContext(gl); - } - - /** - * Helper class to create a webGL Context - * - * @class - * @memberof PIXI.glCore - * @param canvas {HTMLCanvasElement} the canvas element that we will get the context from - * @param options {Object} An options object that gets passed in to the canvas element containing the context attributes, - * see https://developer.mozilla.org/en/docs/Web/API/HTMLCanvasElement/getContext for the options available - * @return {WebGLRenderingContext} the WebGL context - */ - createContext(canvas, options) - { - let gl; - - if (settings.PREFER_WEBGL_2) - { - gl = canvas.getContext('webgl2', options); - } - - if (gl) - { - this.webGLVersion = 2; - } - else - { - this.webGLVersion = 1; - - gl = canvas.getContext('webgl', options) - || canvas.getContext('experimental-webgl', options); - - if (!gl) - { - // fail, not able to get a context - throw new Error('This browser does not support webGL. Try using the canvas renderer'); - } - } - - this.gl = gl; - - this.getExtensions(); - - return gl; - } - - getExtensions() - { - // time to set up default etensions that pixi uses.. - const gl = this.gl; - const extensions = this.extensions; - - if (this.webGLVersion === 1) - { - extensions.drawBuffers = gl.getExtension('WEBGL_draw_buffers'); - extensions.depthTexture = gl.getExtension('WEBKIT_WEBGL_depth_texture'); - extensions.floatTexture = gl.getExtension('OES_texture_float'); - extensions.loseContext = gl.getExtension('WEBGL_lose_context'); - - extensions.vertexArrayObject = gl.getExtension('OES_vertex_array_object') - || gl.getExtension('MOZ_OES_vertex_array_object') - || gl.getExtension('WEBKIT_OES_vertex_array_object'); - } - - // we don't use any specific WebGL 2 ones yet! - } - - /** - * Handles a lost webgl context - * - * @private - * @param {WebGLContextEvent} event - The context lost event. - */ - handleContextLost(event) - { - event.preventDefault(); - } - - /** - * Handles a restored webgl context - * - * @private - */ - handleContextRestored() - { - this.renderer.runners.contextChange.run(this.gl); - } - - destroy() - { - const view = this.renderer.view; - - // remove listeners - view.removeEventListener('webglcontextlost', this.handleContextLost); - view.removeEventListener('webglcontextrestored', this.handleContextRestored); - - this.gl.useProgram(null); - - if (this.extensions.loseContext) - { - this.extensions.loseContext.loseContext(); - } - } - - postrender() - { - this.gl.flush(); - } - - validateContext(gl) - { - const attributes = gl.getContextAttributes(); - - // this is going to be fairly simple for now.. but at least we have room to grow! - if (!attributes.stencil) - { - /* eslint-disable max-len */ - - /* eslint-disable no-console */ - console.warn('Provided WebGL context does not have a stencil buffer, masks may not render correctly'); - /* eslint-enable no-console */ - - /* eslint-enable max-len */ - } - } -} diff --git a/packages/core/src/renderers/systems/FramebufferSystem.js b/packages/core/src/renderers/systems/FramebufferSystem.js deleted file mode 100644 index df7902b..0000000 --- a/packages/core/src/renderers/systems/FramebufferSystem.js +++ /dev/null @@ -1,246 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { Rectangle } from '@pixi/math'; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class FramebufferSystem extends WebGLSystem -{ - /** - * Sets up the renderer context and necessary buffers. - * - * @private - */ - contextChange() - { - this.gl = this.renderer.gl; - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - this.current = null; - this.viewport = new Rectangle(); - - this.drawBufferExtension = this.renderer.context.extensions.drawBuffers; - } - - bind(framebuffer, frame) - { - const gl = this.gl; - - this.current = framebuffer; - - if (framebuffer) - { - // TODO cacheing layer! - - const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID] || this.initFramebuffer(framebuffer); - - gl.bindFramebuffer(gl.FRAMEBUFFER, fbo.framebuffer); - // makesure all textures are unbound.. - - // now check for updates... - if (fbo.dirtyId !== framebuffer.dirtyId) - { - fbo.dirtyId = framebuffer.dirtyId; - - if (fbo.dirtyFormat !== framebuffer.dirtyFormat) - { - fbo.dirtyFormat = framebuffer.dirtyFormat; - this.updateFramebuffer(framebuffer); - } - else if (fbo.dirtySize !== framebuffer.dirtySize) - { - fbo.dirtySize = framebuffer.dirtySize; - this.resizeFramebuffer(framebuffer); - } - } - - for (let i = 0; i < framebuffer.colorTextures.length; i++) - { - if (framebuffer.colorTextures[i].texturePart) - { - this.renderer.texture.unbind(framebuffer.colorTextures[i].texture); - } - else - { - this.renderer.texture.unbind(framebuffer.colorTextures[i]); - } - } - - if (framebuffer.depthTexture) - { - this.renderer.texture.unbind(framebuffer.depthTexture); - } - - if (frame) - { - this.setViewport(frame.x, frame.y, frame.width, frame.height); - } - else - { - this.setViewport(0, 0, framebuffer.width, framebuffer.height); - } - } - else - { - gl.bindFramebuffer(gl.FRAMEBUFFER, null); - - if (frame) - { - this.setViewport(frame.x, frame.y, frame.width, frame.height); - } - else - { - this.setViewport(0, 0, this.renderer.width, this.renderer.height); - } - } - } - - setViewport(x, y, width, height) - { - const v = this.viewport; - - if (v.width !== width || v.height !== height || v.x !== x || v.y !== y) - { - v.x = x; - v.y = y; - v.width = width; - v.height = height; - - this.gl.viewport(x, y, width, height); - } - } - - get size() - { - if (this.current) - { - // TODO store temp - return { x: 0, y: 0, width: this.current.width, height: this.current.height }; - } - - return { x: 0, y: 0, width: this.renderer.width, height: this.renderer.height }; - } - - clear(r, g, b, a) - { - const gl = this.gl; - - // TODO clear color can be set only one right? - gl.clearColor(r, g, b, a); - gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); - } - - // private functions... - - initFramebuffer(framebuffer) - { - const gl = this.gl; - - // TODO - make this a class? - const fbo = { - framebuffer: gl.createFramebuffer(), - stencil: null, - dirtyId: 0, - dirtyFormat: 0, - dirtySize: 0, - }; - - framebuffer.glFrameBuffers[this.CONTEXT_UID] = fbo; - - return fbo; - } - - resizeFramebuffer(framebuffer) - { - const gl = this.gl; - - if (framebuffer.stencil || framebuffer.depth) - { - gl.bindRenderbuffer(gl.RENDERBUFFER, this.stencil); - gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); - } - } - - updateFramebuffer(framebuffer) - { - const gl = this.gl; - - const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID]; - - // bind the color texture - const colorTextures = framebuffer.colorTextures; - - let count = colorTextures.length; - - if (!this.drawBufferExtension) - { - count = Math.min(count, 1); - } - - const activeTextures = []; - - for (let i = 0; i < count; i++) - { - const texture = framebuffer.colorTextures[i]; - - if (texture.texturePart) - { - this.renderer.texture.bind(texture.texture, 0); - - gl.framebufferTexture2D(gl.FRAMEBUFFER, - gl.COLOR_ATTACHMENT0 + i, - gl.TEXTURE_CUBE_MAP_NEGATIVE_X + texture.side, - texture.texture._glTextures[this.CONTEXT_UID].texture, - 0); - } - else - { - this.renderer.texture.bind(texture, 0); - - gl.framebufferTexture2D(gl.FRAMEBUFFER, - gl.COLOR_ATTACHMENT0 + i, - gl.TEXTURE_2D, - texture._glTextures[this.CONTEXT_UID].texture, - 0); - } - - activeTextures.push(gl.COLOR_ATTACHMENT0 + i); - } - - if (this.drawBufferExtension && activeTextures.length > 1) - { - this.drawBufferExtension.drawBuffersWEBGL(activeTextures); - } - - if (framebuffer.depthTexture) - { - const depthTextureExt = this.renderer.context.extensions.depthTexture; - - if (depthTextureExt) - { - const depthTexture = framebuffer.depthTexture; - - this.renderer.texture.bind(depthTexture, 0); - - gl.framebufferTexture2D(gl.FRAMEBUFFER, - gl.DEPTH_ATTACHMENT, - gl.TEXTURE_2D, - depthTexture._glTextures[this.CONTEXT_UID].texture, - 0); - } - } - - if (framebuffer.stencil || framebuffer.depth) - { - fbo.stencil = gl.createRenderbuffer(); - - gl.bindRenderbuffer(gl.RENDERBUFFER, fbo.stencil); - - // TODO.. this is depth AND stencil? - gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, fbo.stencil); - gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); - // fbo.enableStencil(); - } - } -} diff --git a/packages/core/src/renderers/systems/MaskSystem.js b/packages/core/src/renderers/systems/MaskSystem.js deleted file mode 100644 index d9c4b1a..0000000 --- a/packages/core/src/renderers/systems/MaskSystem.js +++ /dev/null @@ -1,199 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import AlphaMaskFilter from '../filters/spriteMask/SpriteMaskFilter'; - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class MaskSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - // TODO - we don't need both! - this.scissor = false; - this.scissorData = null; - this.scissorRenderTarget = null; - - this.enableScissor = false; - - this.alphaMaskPool = []; - this.alphaMaskIndex = 0; - } - - /** - * Applies the Mask and adds it to the current filter stack. - * - * @param {PIXI.DisplayObject} target - Display Object to push the mask to - * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. - */ - push(target, maskData) - { - // TODO the root check means scissor rect will not - // be used on render textures more info here: - // https://github.com/pixijs/pixi.js/pull/3545 - - if (maskData.texture) - { - this.pushSpriteMask(target, maskData); - } - else if (this.enableScissor - && !this.scissor - && this.renderer._activeRenderTarget.root - && !this.renderer.stencil.stencilMaskStack.length - && maskData.isFastRect()) - { - const matrix = maskData.worldTransform; - - let rot = Math.atan2(matrix.b, matrix.a); - - // use the nearest degree! - rot = Math.round(rot * (180 / Math.PI)); - - if (rot % 90) - { - this.pushStencilMask(maskData); - } - else - { - this.pushScissorMask(target, maskData); - } - } - else - { - this.pushStencilMask(maskData); - } - } - - /** - * Removes the last mask from the mask stack and doesn't return it. - * - * @param {PIXI.DisplayObject} target - Display Object to pop the mask from - * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. - */ - pop(target, maskData) - { - if (maskData.texture) - { - this.popSpriteMask(target, maskData); - } - else if (this.enableScissor && !this.renderer.stencil.stencilMaskStack.length) - { - this.popScissorMask(target, maskData); - } - else - { - this.popStencilMask(target, maskData); - } - } - - /** - * Applies the Mask and adds it to the current filter stack. - * - * @param {PIXI.RenderTarget} target - Display Object to push the sprite mask to - * @param {PIXI.Sprite} maskData - Sprite to be used as the mask - */ - pushSpriteMask(target, maskData) - { - let alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; - - if (!alphaMaskFilter) - { - alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; - } - - alphaMaskFilter[0].resolution = this.renderer.resolution; - alphaMaskFilter[0].maskSprite = maskData; - - // TODO - may cause issues! - target.filterArea = maskData.getBounds(true); - - this.renderer.filterSystem.pushFilter(target, alphaMaskFilter); - - this.alphaMaskIndex++; - } - - /** - * Removes the last filter from the filter stack and doesn't return it. - * - */ - popSpriteMask() - { - this.renderer.filterSystem.popFilter(); - this.alphaMaskIndex--; - } - - /** - * Applies the Mask and adds it to the current filter stack. - * - * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. - */ - pushStencilMask(maskData) - { - this.renderer.batch.flush(); - this.renderer.stencil.pushStencil(maskData); - } - - /** - * Removes the last filter from the filter stack and doesn't return it. - * - */ - popStencilMask() - { - // this.renderer.currentRenderer.stop(); - this.renderer.stencil.popStencil(); - } - - /** - * - * @param {PIXI.DisplayObject} target - Display Object to push the mask to - * @param {PIXI.Graphics} maskData - The masking data. - */ - pushScissorMask(target, maskData) - { - maskData.renderable = true; - - const renderTarget = this.renderer._activeRenderTarget; - - const bounds = maskData.getBounds(); - - bounds.fit(renderTarget.size); - maskData.renderable = false; - - this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); - - const resolution = this.renderer.resolution; - - this.renderer.gl.scissor( - bounds.x * resolution, - (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, - bounds.width * resolution, - bounds.height * resolution - ); - - this.scissorRenderTarget = renderTarget; - this.scissorData = maskData; - this.scissor = true; - } - - /** - * - * - */ - popScissorMask() - { - this.scissorRenderTarget = null; - this.scissorData = null; - this.scissor = false; - - // must be scissor! - const gl = this.renderer.gl; - - gl.disable(gl.SCISSOR_TEST); - } -} diff --git a/packages/core/src/renderers/systems/ProjectionSystem.js b/packages/core/src/renderers/systems/ProjectionSystem.js deleted file mode 100644 index ec5efdb..0000000 --- a/packages/core/src/renderers/systems/ProjectionSystem.js +++ /dev/null @@ -1,73 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { Matrix } from '@pixi/math'; - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ - -export default class ProjectionSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this.projectionMatrix = new Matrix(); - } - - update(destinationFrame, sourceFrame, resolution, root) - { - this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; - this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; - - this.calculateProjection(this.destinationFrame, this.sourceFrame, resolution, root); - - this.renderer.globalUniforms.uniforms.projectionMatrix = this.projectionMatrix; - this.renderer.globalUniforms.update(); - } - - /** - * Updates the projection matrix based on a projection frame (which is a rectangle) - * - * @param {Rectangle} destinationFrame - The destination frame. - * @param {Rectangle} sourceFrame - The source frame. - */ - calculateProjection(destinationFrame, sourceFrame, resolution, root) - { - const pm = this.projectionMatrix; - - // I don't think we will need this line.. - // pm.identity(); - - if (!root) - { - pm.a = (1 / destinationFrame.width * 2) * resolution; - pm.d = (1 / destinationFrame.height * 2) * resolution; - - pm.tx = -1 - (sourceFrame.x * pm.a); - pm.ty = -1 - (sourceFrame.y * pm.d); - } - else - { - pm.a = (1 / destinationFrame.width * 2) * resolution; - pm.d = (-1 / destinationFrame.height * 2) * resolution; - - pm.tx = -1 - (sourceFrame.x * pm.a); - pm.ty = 1 - (sourceFrame.y * pm.d); - } - } - - /** - * Sets the transform of the active render target to the given matrix - * - * @param {PIXI.Matrix} matrix - The transformation matrix - */ - setTransform()// matrix) - { - // this._activeRenderTarget.transform = matrix; - } -} diff --git a/packages/core/src/renderers/systems/RenderTextureSystem.js b/packages/core/src/renderers/systems/RenderTextureSystem.js deleted file mode 100644 index a133ef1..0000000 --- a/packages/core/src/renderers/systems/RenderTextureSystem.js +++ /dev/null @@ -1,115 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { Rectangle } from '@pixi/math'; - -const tempRect = new Rectangle(); - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ - -export default class RenderTextureSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this.clearColor = renderer._backgroundColorRgba; - - // TODO moe this property somewhere else! - this.defaultMaskStack = []; - this.defaultFilterStack = [{}]; - - // empty render texture? - this.renderTexture = null; - - this.destinationFrame = new Rectangle(); - } - - bind(renderTexture, sourceFrame, destinationFrame) - { - // TODO - do we want this?? - if (this.renderTexture === renderTexture) return; - this.renderTexture = renderTexture; - - const renderer = this.renderer; - - if (renderTexture) - { - const baseTexture = renderTexture.baseTexture; - - if (!destinationFrame) - { - tempRect.width = baseTexture.realWidth; - tempRect.height = baseTexture.realHeight; - - destinationFrame = tempRect; - } - - if (!sourceFrame) - { - sourceFrame = destinationFrame; - } - - this.renderer.framebuffer.bind(baseTexture.frameBuffer, destinationFrame); - - this.renderer.projection.update(destinationFrame, sourceFrame, baseTexture.resolution, false); - this.renderer.stencil.setMaskStack(baseTexture.stencilMaskStack); - } - else - { - // TODO these validation checks happen deeper down.. - // thing they can be avoided.. - if (!destinationFrame) - { - tempRect.width = renderer.width; - tempRect.height = renderer.height; - - destinationFrame = tempRect; - } - - if (!sourceFrame) - { - sourceFrame = destinationFrame; - } - - renderer.framebuffer.bind(null, destinationFrame); - - // TODO store this.. - this.renderer.projection.update(destinationFrame, sourceFrame, this.renderer.resolution, true); - this.renderer.stencil.setMaskStack(this.defaultMaskStack); - } - - this.destinationFrame.copyFrom(destinationFrame); - } - - /** - * Erases the render texture and fills the drawing area with a colour - * - * @param {number} [clearColor] - The colour - * @return {PIXI.Renderer} Returns itself. - */ - clear(clearColor) - { - if (this.renderTexture) - { - clearColor = clearColor || this.renderTexture.baseTexture.clearColor; - } - else - { - clearColor = clearColor || this.clearColor; - } - - this.renderer.framebuffer.clear(clearColor[0], clearColor[1], clearColor[2], clearColor[3]); - } - - resize()// screenWidth, screenHeight) - { - // resize the root only! - this.bind(null); - } -} diff --git a/packages/core/src/renderers/systems/StateSystem.js b/packages/core/src/renderers/systems/StateSystem.js deleted file mode 100755 index d7f4caf..0000000 --- a/packages/core/src/renderers/systems/StateSystem.js +++ /dev/null @@ -1,288 +0,0 @@ -import mapWebGLBlendModesToPixi from '../utils/mapWebGLBlendModesToPixi'; -import WebGLSystem from './WebGLSystem'; -import WebGLState from '../State'; - -const BLEND = 0; -const OFFSET = 1; -const CULLING = 2; -const DEPTH_TEST = 3; -const WINDING = 4; - -/** - * A WebGL state machines - * - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class StateSystem extends WebGLSystem -{ - /** - * @param {WebGLRenderingContext} gl - The current WebGL rendering context - */ - constructor(renderer) - { - super(renderer); - - this.gl = null; - - this.maxAttribs = null; - - // check we have vao.. - this.nativeVaoExtension = null; - - this.attribState = null; - - this.stateId = 0; - this.polygonOffset = 0; - this.blendMode = 17; - - this.map = []; - - // map functions for when we set state.. - this.map[BLEND] = this.setBlend; - this.map[OFFSET] = this.setOffset; - this.map[CULLING] = this.setCullFace; - this.map[DEPTH_TEST] = this.setDepthTest; - this.map[WINDING] = this.setFrontFace; - - this.checks = []; - - this.defaultState = new WebGLState(); - this.defaultState.blend = true; - this.defaultState.depth = true; - } - - contextChange(gl) - { - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; - - this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); - - // check we have vao.. - this.nativeVaoExtension = ( - gl.getExtension('OES_vertex_array_object') - || gl.getExtension('MOZ_OES_vertex_array_object') - || gl.getExtension('WEBKIT_OES_vertex_array_object') - ); - - this.attribState = { - tempAttribState: new Array(this.maxAttribs), - attribState: new Array(this.maxAttribs), - }; - - this.blendModes = mapWebGLBlendModesToPixi(gl); - - this.setState(this.defaultState); - - this.reset(); - } - - /** - * Sets the current state - * - * @param {*} state - The state to set. - */ - setState(state) - { - state = state || this.defaultState; - - // TODO maybe to an object check? ( this.state === state )? - if (this.stateId !== state.data) - { - let diff = this.stateId ^ state.data; - let i = 0; - - // order from least to most common - while (diff) - { - if (diff & 1) - { - // state change! - this.map[i].call(this, !!(state.data & (1 << i))); - } - - diff = diff >> 1; - i++; - } - - this.stateId = state.data; - } - - // based on the above settings we check for specific modes.. - // for example if blend is active we check and set the blend modes - // or of polygon offset is active we check the poly depth. - for (let i = 0; i < this.checks.length; i++) - { - this.checks[i](this, state); - } - } - - /** - * Enables or disabled blending. - * - * @param {boolean} value - Turn on or off webgl blending. - */ - setBlend(value) - { - this.updateCheck(StateSystem.checkBlendMode, value); - - this.gl[value ? 'enable' : 'disable'](this.gl.BLEND); - } - - /** - * Enables or disable polygon offset fill - * - * @param {boolean} value - Turn on or off webgl polygon offset testing. - */ - setOffset(value) - { - this.gl[value ? 'enable' : 'disable'](this.gl.POLYGON_OFFSET_FILL); - } - - /** - * Sets whether to enable or disable depth test. - * - * @param {boolean} value - Turn on or off webgl depth testing. - */ - setDepthTest(value) - { - this.gl[value ? 'enable' : 'disable'](this.gl.DEPTH_TEST); - } - - /** - * Sets whether to enable or disable cull face. - * - * @param {boolean} value - Turn on or off webgl cull face. - */ - setCullFace(value) - { - this.gl[value ? 'enable' : 'disable'](this.gl.CULL_FACE); - } - - /** - * Sets the gl front face. - * - * @param {boolean} value - true is clockwise and false is counter-clockwise - */ - setFrontFace(value) - { - this.gl.frontFace(this.gl[value ? 'CW' : 'CCW']); - } - - /** - * Sets the blend mode. - * - * @param {number} value - The blend mode to set to. - */ - setBlendMode(value) - { - if (value === this.blendMode) - { - return; - } - - this.blendMode = value; - this.gl.blendFunc(this.blendModes[value][0], this.blendModes[value][1]); - } - - /** - * Sets the polygon offset. - * - * @param {number} value - the polygon offset - * @param {number} scale - the polygon offset scale - */ - setPolygonOffset(value, scale) - { - this.gl.polygonOffset(value, scale); - } - - /** - * Disables all the vaos in use - * - */ - resetAttributes() - { - for (let i = 0; i < this.attribState.tempAttribState.length; i++) - { - this.attribState.tempAttribState[i] = 0; - } - - for (let i = 0; i < this.attribState.attribState.length; i++) - { - this.attribState.attribState[i] = 0; - } - - // im going to assume one is always active for performance reasons. - for (let i = 1; i < this.maxAttribs; i++) - { - this.gl.disableVertexAttribArray(i); - } - } - - // used - /** - * Resets all the logic and disables the vaos - */ - reset() - { - // unbind any VAO if they exist.. - if (this.nativeVaoExtension) - { - this.nativeVaoExtension.bindVertexArrayOES(null); - } - - // reset all attributes.. - this.resetAttributes(); - - this.gl.pixelStorei(this.gl.UNPACK_FLIP_Y_WEBGL, false); - - this.setBlendMode(0); - - // TODO? - // this.setState(this.defaultState); - } - - /** - * checks to see which updates should be checked based on which settings have been activated - * for example if blend is enabled then we shold check the blend modes each time the state is changed - * or if poygon fill is activated then we need to check if the polygone offset changes. - * The idea is that we only check what we have too. - * - * @param {Function} func the checking function to add or remove - * @param {boolean} value should the check function be added or removed. - */ - updateCheck(func, value) - { - const index = this.checks.indexOf(func); - - if (value && index === -1) - { - this.checks.push(func); - } - else if (!value && index !== -1) - { - this.checks.splice(index, 1); - } - } - - /** - * A private little wrapper function that we call to check the blend mode. - * - * @static - * @private - * @param {PIXI.StateSystem} System the System to perform the state check on - * @param {PIXI.State} state the state that the blendMode will pulled from - */ - static checkBlendMode(system, state) - { - system.setBlendMode(state.blendMode); - } - - // TODO - add polygon offset? -} diff --git a/packages/core/src/renderers/systems/StencilSystem.js b/packages/core/src/renderers/systems/StencilSystem.js deleted file mode 100644 index 5d538cb..0000000 --- a/packages/core/src/renderers/systems/StencilSystem.js +++ /dev/null @@ -1,133 +0,0 @@ -import WebGLSystem from './WebGLSystem'; - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class StencilSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - this.stencilMaskStack = []; - } - - /** - * Changes the mask stack that is used by this System. - * - * @param {PIXI.Graphics[]} stencilMaskStack - The mask stack - */ - setMaskStack(stencilMaskStack) - { - const gl = this.renderer.gl; - - if (stencilMaskStack.length !== this.stencilMaskStack.length) - { - if (stencilMaskStack.length === 0) - { - gl.disable(gl.STENCIL_TEST); - } - else - { - gl.enable(gl.STENCIL_TEST); - } - } - - this.stencilMaskStack = stencilMaskStack; - } - - /** - * Applies the Mask and adds it to the current stencil stack. @alvin - * - * @param {PIXI.Graphics} graphics - The mask - */ - pushStencil(graphics) - { - this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); - - // this.renderer._activeRenderTarget.attachStencilBuffer(); - - const gl = this.renderer.gl; - const prevMaskCount = this.stencilMaskStack.length; - - if (prevMaskCount === 0) - { - gl.enable(gl.STENCIL_TEST); - } - - this.stencilMaskStack.push(graphics); - - // Increment the refference stencil value where the new mask overlaps with the old ones. - gl.colorMask(false, false, false, false); - gl.stencilFunc(gl.EQUAL, prevMaskCount, this._getBitwiseMask()); - gl.stencilOp(gl.KEEP, gl.KEEP, gl.INCR); - this.renderer.plugins.graphics.render(graphics); - - this._useCurrent(); - } - - /** - * Removes the last mask from the stencil stack. @alvin - */ - popStencil() - { - this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); - - const gl = this.renderer.gl; - const graphics = this.stencilMaskStack.pop(); - - if (this.stencilMaskStack.length === 0) - { - // the stack is empty! - gl.disable(gl.STENCIL_TEST); - gl.clear(gl.STENCIL_BUFFER_BIT); - gl.clearStencil(0); - } - else - { - // Decrement the refference stencil value where the popped mask overlaps with the other ones - gl.colorMask(false, false, false, false); - gl.stencilOp(gl.KEEP, gl.KEEP, gl.DECR); - this.renderer.plugins.graphics.render(graphics); - - this._useCurrent(); - } - } - - /** - * Setup renderer to use the current stencil data. - */ - _useCurrent() - { - const gl = this.renderer.gl; - - gl.colorMask(true, true, true, true); - gl.stencilFunc(gl.EQUAL, this.stencilMaskStack.length, this._getBitwiseMask()); - gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP); - } - - /** - * Fill 1s equal to the number of acitve stencil masks. - * - * @return {number} The bitwise mask. - */ - _getBitwiseMask() - { - return (1 << this.stencilMaskStack.length) - 1; - } - - /** - * Destroys the mask stack. - * - */ - destroy() - { - super.destroy(this); - - this.stencilMaskStack = null; - } -} diff --git a/packages/core/src/renderers/systems/WebGLSystem.js b/packages/core/src/renderers/systems/WebGLSystem.js deleted file mode 100644 index d3d5b31..0000000 --- a/packages/core/src/renderers/systems/WebGLSystem.js +++ /dev/null @@ -1,41 +0,0 @@ -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this manager works for. - */ - constructor(renderer) - { - /** - * The renderer this manager works for. - * - * @member {PIXI.Renderer} - */ - this.renderer = renderer; - - this.renderer.runners.contextChange.add(this); - } - - /** - * Generic method called when there is a WebGL context change. - * - */ - contextChange() - { - // do some codes init! - } - - /** - * Generic destroy methods to be overridden by the subclass - * - */ - destroy() - { - this.renderer.runners.contextChange.remove(this); - this.renderer = null; - } -} diff --git a/packages/core/src/renderers/systems/filter/FilterSystem.js b/packages/core/src/renderers/systems/filter/FilterSystem.js deleted file mode 100644 index e615037..0000000 --- a/packages/core/src/renderers/systems/filter/FilterSystem.js +++ /dev/null @@ -1,431 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; - -import RenderTexture from '../../../textures/RenderTexture'; -import Quad from '../../utils/Quad'; -import QuadUv from '../../utils/QuadUv'; -import { Rectangle } from '@pixi/math'; -import * as filterTransforms from '../../filters/filterTransforms'; -import bitTwiddle from 'bit-twiddle'; -import UniformGroup from '../../../shader/UniformGroup'; -import { DRAW_MODES } from '../../../../../constants'; - -// -/** - * @ignore - * @class - */ -class FilterState -{ - /** - * - */ - constructor() - { - this.renderTexture = null; - this.sourceFrame = new Rectangle(); - this.destinationFrame = new Rectangle(); - this.filters = []; - this.target = null; - this.legacy = false; - this.resolution = 1; - } -} - -/** - * @class - * @memberof PIXI.systems - * @extends PIXI.systems.WebGLSystem - */ -export default class FilterSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - /** - * stores a bunch of PO2 textures used for filtering - * @type {Object} - */ - this.texturePool = {}; - - /** - * a pool for storing filter states, save us creating new ones each tick - * @type {Array} - */ - this.statePool = []; - - /** - * A very simple geometry used when drawing a filter effect to the screen - * @type {Quad} - */ - this.quad = new Quad(); - - this.quadUv = new QuadUv(); - - /** - * Temporary rect for maths - * @type {PIXI.Rectangle} - */ - this.tempRect = new Rectangle(); - - this.activeState = {}; - - /** - * this uniform group is attached to filter uniforms when used - * @type {UniformGroup} - */ - this.globalUniforms = new UniformGroup({ - sourceFrame: this.tempRect, - destinationFrame: this.tempRect, - - // legacy variables - filterArea: new Float32Array(4), - filterClamp: new Float32Array(4), - }, true); - } - - /** - * Adds a new filter to the System. - * - * @param {PIXI.DisplayObject} target - The target of the filter to render. - * @param {PIXI.Filter[]} filters - The filters to apply. - */ - push(target, filters) - { - const renderer = this.renderer; - const filterStack = this.renderer.renderTexture.defaultFilterStack; - const state = this.statePool.pop() || new FilterState(); - - let resolution = filters[0].resolution; - let padding = filters[0].padding; - let autoFit = filters[0].autoFit; - let legacy = filters[0].legacy; - - for (let i = 1; i < filters.length; i++) - { - const filter = filters[i]; - - // lets use the lowest resolution.. - resolution = Math.min(resolution, filter.resolution); - // and the largest amount of padding! - padding = Math.max(padding, filter.padding); - // only auto fit if all filters are autofit - autoFit = autoFit || filter.autoFit; - - legacy = legacy || filter.legacy; - } - - filterStack.push(state); - - state.resolution = resolution; - - state.legacy = legacy; - - // round to whole number based on resolution - // TODO move that to the shader too? - state.sourceFrame = target.filterArea || target.getBounds(true); - - state.sourceFrame.pad(padding); - - if (autoFit) - { - state.sourceFrame.fit(this.renderer.renderTexture.destinationFrame); - } - - state.sourceFrame.round(resolution); - - state.renderTexture = this.getPotFilterTexture(state.sourceFrame.width, state.sourceFrame.height, resolution); - state.filters = filters; - - state.destinationFrame.width = state.renderTexture.width; - state.destinationFrame.height = state.renderTexture.height; - - state.renderTexture.filterFrame = state.sourceFrame; - renderer.renderTexture.bind(state.renderTexture, state.sourceFrame);// /, state.destinationFrame); - renderer.renderTexture.clear(); - } - - /** - * Pops off the filter and applies it. - * - */ - pop() - { - const renderer = this.renderer; - const filterStack = renderer.renderTexture.defaultFilterStack; - const state = filterStack.pop(); - const filters = state.filters; - - this.activeState = state; - - const globalUniforms = this.globalUniforms.uniforms; - - globalUniforms.sourceFrame = state.sourceFrame; - globalUniforms.destinationFrame = state.destinationFrame; - globalUniforms.resolution = state.resolution; - - // only update the rect if its legacy.. - if (state.legacy) - { - const filterArea = globalUniforms.filterArea; - const filterClamp = globalUniforms.filterClamp; - - filterArea[0] = state.destinationFrame.width; - filterArea[1] = state.destinationFrame.height; - filterArea[2] = state.sourceFrame.x; - filterArea[3] = state.sourceFrame.y; - - filterClamp[0] = 0.5 / state.resolution / state.destinationFrame.width; - filterClamp[1] = 0.5 / state.resolution / state.destinationFrame.height; - filterClamp[2] = (state.sourceFrame.width - 0.5) / state.resolution / state.destinationFrame.width; - filterClamp[3] = (state.sourceFrame.height - 0.5) / state.resolution / state.destinationFrame.height; - } - - this.globalUniforms.update(); - - const lastState = filterStack[filterStack.length - 1]; - - if (filters.length === 1) - { - filters[0].apply(this, state.renderTexture, lastState.renderTexture, false, state); - - this.returnFilterTexture(state.renderTexture); - } - else - { - let flip = state.renderTexture; - let flop = this.getPotFilterTexture( - flip.width, - flip.height, - state.resolution - ); - - flop.filterFrame = flip.filterFrame; - - let i = 0; - - for (i = 0; i < filters.length - 1; ++i) - { - filters[i].apply(this, flip, flop, true, state); - - const t = flip; - - flip = flop; - flop = t; - } - - filters[i].apply(this, flip, lastState.renderTexture, false, state); - - this.returnFilterTexture(flip); - this.returnFilterTexture(flop); - } - - this.statePool.push(state); - } - - /** - * Draws a filter. - * - * @param {PIXI.Filter} filter - The filter to draw. - * @param {PIXI.RenderTarget} input - The input render target. - * @param {PIXI.RenderTarget} output - The target to output to. - * @param {boolean} clear - Should the output be cleared before rendering to it - */ - applyFilter(filter, input, output, clear) - { - const renderer = this.renderer; - - renderer.renderTexture.bind(output, output ? output.filterFrame : null); - - if (clear) - { - // gl.disable(gl.SCISSOR_TEST); - renderer.renderTexture.clear(); - // gl.enable(gl.SCISSOR_TEST); - } - - // set the uniforms.. - filter.uniforms.uSampler = input; - filter.uniforms.filterGlobals = this.globalUniforms; - - // TODO make it so that the order of this does not matter.. - // because it does at the moment cos of global uniforms. - // they need to get resynced - - renderer.state.setState(filter.state); - renderer.shader.bind(filter); - - if (filter.legacy) - { - this.quadUv.map(input._frame, input.filterFrame); - - renderer.geometry.bind(this.quadUv); - renderer.geometry.draw(DRAW_MODES.TRIANGLES); - } - else - { - renderer.geometry.bind(this.quad); - renderer.geometry.draw(DRAW_MODES.TRIANGLE_STRIP); - } - } - - /** - * Calculates the mapped matrix. - * - * TODO playing around here.. this is temporary - (will end up in the shader) - * this returns a matrix that will normalise map filter cords in the filter to screen space - * - * @param {PIXI.Matrix} outputMatrix - the matrix to output to. - * @return {PIXI.Matrix} The mapped matrix. - */ - calculateScreenSpaceMatrix(outputMatrix) - { - const currentState = this.activeState; - - return filterTransforms.calculateScreenSpaceMatrix( - outputMatrix, - currentState.sourceFrame, - currentState.destinationFrame - ); - } - - /** - * This will map the filter coord so that a texture can be used based on the transform of a sprite - * - * @param {PIXI.Matrix} outputMatrix - The matrix to output to. - * @param {PIXI.Sprite} sprite - The sprite to map to. - * @return {PIXI.Matrix} The mapped matrix. - */ - calculateSpriteMatrix(outputMatrix, sprite) - { - const currentState = this.activeState; - - return filterTransforms.calculateSpriteMatrix( - outputMatrix, - currentState.sourceFrame, - currentState.destinationFrame, - sprite - ); - } - - /** - * Destroys this Filter System. - * - * @param {boolean} [contextLost=false] context was lost, do not free shaders - * - */ - destroy(contextLost = false) - { - if (!contextLost) - { - this.emptyPool(); - } - else - { - this.texturePool = {}; - } - } - - /** - * Gets a Power-of-Two render texture. - * - * TODO move to a seperate class could be on renderer? - * also - could cause issue with multiple contexts? - * - * @private - * @param {WebGLRenderingContext} gl - The webgl rendering context - * @param {number} minWidth - The minimum width of the render target. - * @param {number} minHeight - The minimum height of the render target. - * @param {number} resolution - The resolution of the render target. - * @return {PIXI.RenderTarget} The new render target. - */ - getPotFilterTexture(minWidth, minHeight, resolution) - { - minWidth = bitTwiddle.nextPow2(minWidth); - minHeight = bitTwiddle.nextPow2(minHeight); - resolution = resolution || 1; - - const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); - - if (!this.texturePool[key]) - { - this.texturePool[key] = []; - } - - let renderTexture = this.texturePool[key].pop(); - - if (!renderTexture) - { - // temporary bypass cache.. - // internally - this will cause a texture to be bound.. - renderTexture = RenderTexture.create({ - width: minWidth, - height: minHeight, - resolution, - }); - } - - return renderTexture; - } - - /** - * Gets extra render texture to use inside current filter - * - * @param {number} resolution resolution of the renderTexture - * @returns {PIXI.RenderTexture} - */ - getFilterTexture(resolution) - { - const rt = this.activeState.renderTexture; - - const filterTexture = this.getPotFilterTexture(rt.width, rt.height, resolution || rt.baseTexture.resolution); - - filterTexture.filterFrame = rt.filterFrame; - - return filterTexture; - } - - /** - * Frees a render texture back into the pool. - * - * @param {PIXI.RenderTarget} renderTexture - The renderTarget to free - */ - returnFilterTexture(renderTexture) - { - renderTexture.filterFrame = null; - - const base = renderTexture.baseTexture; - - const minWidth = base.width; - const minHeight = base.height; - - const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); - - this.texturePool[key].push(renderTexture); - } - - /** - * Empties the texture pool. - * - */ - emptyPool() - { - for (const i in this.texturePool) - { - const textures = this.texturePool[i]; - - if (textures) - { - for (let j = 0; j < textures.length; j++) - { - textures[j].destroy(true); - } - } - } - - this.texturePool = {}; - } -} diff --git a/packages/core/src/renderers/systems/geometry/GLBuffer.js b/packages/core/src/renderers/systems/geometry/GLBuffer.js deleted file mode 100644 index 0477e8a..0000000 --- a/packages/core/src/renderers/systems/geometry/GLBuffer.js +++ /dev/null @@ -1,9 +0,0 @@ -export default class GLBuffer -{ - constructor(buffer) - { - this.buffer = buffer; - this.updateID = -1; - this.byteLength = -1; - } -} diff --git a/packages/core/src/renderers/systems/geometry/GeometrySystem.js b/packages/core/src/renderers/systems/geometry/GeometrySystem.js deleted file mode 100644 index 26c8e9e..0000000 --- a/packages/core/src/renderers/systems/geometry/GeometrySystem.js +++ /dev/null @@ -1,388 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; -import GLBuffer from './GLBuffer'; - -const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class GeometrySystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this._activeGeometry = null; - this._activeVao = null; - - this.hasVao = true; - this.hasInstance = true; - } - - /** - * Sets up the renderer context and necessary buffers. - * - * @private - */ - contextChange() - { - const gl = this.gl = this.renderer.gl; - - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - - // webgl2 - if (!gl.createVertexArray) - { - // webgl 1! - let nativeVaoExtension = this.renderer.context.extensions.vertexArrayObject; - - if (this.renderer.legacy) - { - nativeVaoExtension = null; - } - - if (nativeVaoExtension) - { - gl.createVertexArray = () => - nativeVaoExtension.createVertexArrayOES(); - - gl.bindVertexArray = (vao) => - nativeVaoExtension.bindVertexArrayOES(vao); - - gl.deleteVertexArray = (vao) => - nativeVaoExtension.deleteVertexArrayOES(vao); - } - else - { - this.hasVao = false; - gl.createVertexArray = () => - { - // empty - }; - - gl.bindVertexArray = () => - { - // empty - }; - - gl.deleteVertexArray = () => - { - // empty - }; - } - } - - if (!gl.vertexAttribDivisor) - { - const instanceExt = gl.getExtension('ANGLE_instanced_arrays'); - - if (instanceExt) - { - gl.vertexAttribDivisor = (a, b) => - instanceExt.vertexAttribDivisorANGLE(a, b); - - gl.drawElementsInstanced = (a, b, c, d, e) => - instanceExt.drawElementsInstancedANGLE(a, b, c, d, e); - - gl.drawArraysInstanced = (a, b, c, d) => - instanceExt.drawArraysInstancedANGLE(a, b, c, d); - } - else - { - this.hasInstance = false; - } - } - } - - /** - * Binds geometry so that is can be drawn. Creating a Vao if required - * @private - * @param {PIXI.Geometry} geometry instance of geometry to bind - */ - bind(geometry, shader) - { - shader = shader || this.renderer.shader.shader; - - const gl = this.gl; - - // not sure the best way to address this.. - // currently different shaders require different VAOs for the same geometry - // Still mulling over the best way to solve this one.. - // will likely need to modify the shader attribute locations at run time! - let vaos = geometry.glVertexArrayObjects[this.CONTEXT_UID]; - - if (!vaos) - { - geometry.glVertexArrayObjects[this.CONTEXT_UID] = vaos = {}; - } - - const vao = vaos[shader.program.id] || this.initGeometryVao(geometry, shader.program); - - this._activeGeometry = geometry; - - if (this._activeVao !== vao) - { - this._activeVao = vao; - - if (this.hasVao) - { - gl.bindVertexArray(vao); - } - else - { - this.activateVao(geometry, shader.program); - } - } - - // TODO - optimise later! - // don't need to loop through if nothing changed! - // maybe look to add an 'autoupdate' to geometry? - this.updateBuffers(); - } - - reset() - { - this.unbind(); - } - - updateBuffers() - { - const geometry = this._activeGeometry; - const gl = this.gl; - - for (let i = 0; i < geometry.buffers.length; i++) - { - const buffer = geometry.buffers[i]; - - const glBuffer = buffer._glBuffers[this.CONTEXT_UID]; - - if (buffer._updateID !== glBuffer.updateID) - { - glBuffer.updateID = buffer._updateID; - - // TODO can cache this on buffer! maybe added a getter / setter? - const type = buffer.index ? gl.ELEMENT_ARRAY_BUFFER : gl.ARRAY_BUFFER; - const drawType = buffer.static ? gl.STATIC_DRAW : gl.DYNAMIC_DRAW; - - gl.bindBuffer(type, glBuffer.buffer); - - if (glBuffer.byteLength >= buffer.data.byteLength) - { - // offset is always zero for now! - gl.bufferSubData(type, 0, buffer.data); - } - else - { - gl.bufferData(type, buffer.data, drawType); - } - } - } - } - - checkCompatability(geometry, program) - { - // geometry must have at least all the attributes that the shader requires. - const geometryAttributes = geometry.attributes; - const shaderAttributes = program.attributeData; - - for (const j in shaderAttributes) - { - if (!geometryAttributes[j]) - { - throw new Error(`shader and geometry incompatible, geometry missing the "${j}" attribute`); - } - } - } - - /** - * Creates a Vao with the same structure as the geometry and stores it on the geometry. - * @private - * @param {PIXI.Geometry} geometry instance of geometry to to generate Vao for - */ - initGeometryVao(geometry, program) - { - this.checkCompatability(geometry, program); - - const gl = this.gl; - const CONTEXT_UID = this.CONTEXT_UID; - const buffers = geometry.buffers; - const attributes = geometry.attributes; - - const tempStride = {}; - const tempStart = {}; - - for (const j in buffers) - { - tempStride[j] = 0; - tempStart[j] = 0; - } - - for (const j in attributes) - { - if (!attributes[j].size && program.attributeData[j]) - { - attributes[j].size = program.attributeData[j].size; - } - - tempStride[attributes[j].buffer] += attributes[j].size * byteSizeMap[attributes[j].type]; - } - - for (const j in attributes) - { - const attribute = attributes[j]; - const attribSize = attribute.size; - - if (attribute.stride === undefined) - { - if (tempStride[attribute.buffer] === attribSize * byteSizeMap[attribute.type]) - { - attribute.stride = 0; - } - else - { - attribute.stride = tempStride[attribute.buffer]; - } - } - - if (attribute.start === undefined) - { - attribute.start = tempStart[attribute.buffer]; - - tempStart[attribute.buffer] += attribSize * byteSizeMap[attribute.type]; - } - } - - // first update - and create the buffers! - // only create a gl buffer if it actually gets - for (let i = 0; i < buffers.length; i++) - { - const buffer = buffers[i]; - - if (!buffer._glBuffers[CONTEXT_UID]) - { - buffer._glBuffers[CONTEXT_UID] = new GLBuffer(gl.createBuffer()); - } - } - - // TODO - maybe make this a data object? - // lets wait to see if we need to first! - const vao = gl.createVertexArray(); - - gl.bindVertexArray(vao); - - this.activateVao(geometry, program); - - gl.bindVertexArray(null); - - // add it to the cache! - geometry.glVertexArrayObjects[this.CONTEXT_UID][program.id] = vao; - - return vao; - } - - activateVao(geometry, program) - { - const gl = this.gl; - const CONTEXT_UID = this.CONTEXT_UID; - const buffers = geometry.buffers; - const attributes = geometry.attributes; - - if (geometry.indexBuffer) - { - // first update the index buffer if we have one.. - gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, geometry.indexBuffer._glBuffers[CONTEXT_UID].buffer); - } - - let lastBuffer = null; - - // add a new one! - for (const j in attributes) - { - const attribute = attributes[j]; - const buffer = buffers[attribute.buffer]; - const glBuffer = buffer._glBuffers[CONTEXT_UID]; - - if (program.attributeData[j]) - { - if (lastBuffer !== glBuffer) - { - gl.bindBuffer(gl.ARRAY_BUFFER, glBuffer.buffer); - - lastBuffer = glBuffer; - } - - const location = program.attributeData[j].location; - - // TODO introduce state again - // we can optimise this for older devices that have no VAOs - gl.enableVertexAttribArray(location); - - gl.vertexAttribPointer(location, - attribute.size, - attribute.type || gl.FLOAT, - attribute.normalized, - attribute.stride, - attribute.start); - - if (attribute.instance) - { - // TODO calculate instance count based of this... - if (this.hasInstance) - { - gl.vertexAttribDivisor(location, 1); - } - else - { - throw new Error('geometry error, GPU Instancing is not supported on this device'); - } - } - } - } - } - - draw(type, size, start, instanceCount) - { - const gl = this.gl; - const geometry = this._activeGeometry; - - // TODO.. this should not change so maybe cache the function? - - if (geometry.indexBuffer) - { - if (geometry.instanced) - { - /* eslint-disable max-len */ - gl.drawElementsInstanced(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2, instanceCount || 1); - /* eslint-enable max-len */ - } - else - { - gl.drawElements(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2); - } - } - else - if (geometry.instanced) - { - // TODO need a better way to calculate size.. - gl.drawArraysInstanced(type, start, size || geometry.getSize(), instanceCount || 1); - } - else - { - gl.drawArrays(type, start, size || geometry.getSize()); - } - - return this; - } - - unbind() - { - this.gl.bindVertexArray(null); - this._activeVao = null; - this._activeGeometry = null; - } -} diff --git a/packages/core/src/renderers/systems/geometry/setVertexAttribArrays.js b/packages/core/src/renderers/systems/geometry/setVertexAttribArrays.js deleted file mode 100644 index 23b6959..0000000 --- a/packages/core/src/renderers/systems/geometry/setVertexAttribArrays.js +++ /dev/null @@ -1,54 +0,0 @@ -// var GL_MAP = {}; - -/** - * @param gl {WebGLRenderingContext} The current WebGL context - * @param attribs {*} - * @param state {*} - */ -export default function setVertexAttribArrays(gl, attribs, state) -{ - let i; - - if (state) - { - const tempAttribState = state.tempAttribState; - const attribState = state.attribState; - - for (i = 0; i < tempAttribState.length; i++) - { - tempAttribState[i] = false; - } - - // set the new attribs - for (i = 0; i < attribs.length; i++) - { - tempAttribState[attribs[i].attribute.location] = true; - } - - for (i = 0; i < attribState.length; i++) - { - if (attribState[i] !== tempAttribState[i]) - { - attribState[i] = tempAttribState[i]; - - if (state.attribState[i]) - { - gl.enableVertexAttribArray(i); - } - else - { - gl.disableVertexAttribArray(i); - } - } - } - } - else - { - for (i = 0; i < attribs.length; i++) - { - const attrib = attribs[i]; - - gl.enableVertexAttribArray(attrib.attribute.location); - } - } -} diff --git a/packages/core/src/renderers/systems/index.js b/packages/core/src/renderers/systems/index.js deleted file mode 100644 index c843f03..0000000 --- a/packages/core/src/renderers/systems/index.js +++ /dev/null @@ -1,6 +0,0 @@ -/** - * Systems are individual components to the Renderer pipeline. - * @namespace PIXI.systems - */ -export { default as WebGLSystem } from './WebGLSystem'; -export { default as FilterSystem } from './filter/FilterSystem'; diff --git a/packages/core/src/renderers/systems/shader/GLProgram.js b/packages/core/src/renderers/systems/shader/GLProgram.js deleted file mode 100644 index 92980ca..0000000 --- a/packages/core/src/renderers/systems/shader/GLProgram.js +++ /dev/null @@ -1,43 +0,0 @@ -/** - * Helper class to create a webGL Program - * - * @class - * @private - */ -export default class GLProgram -{ - constructor(program, uniformData) - { - /** - * The shader program - * - * @member {WebGLProgram} - */ - this.program = program; - - /** - * holds the uniform data which contains uniform locations - * and current uniform values used for caching and preventing undeed GPU commands - * @type {Object} - */ - this.uniformData = uniformData; - - /** - * uniformGroups holds the various upload functions for the shader. Each uniform group - * and program have a unique upload function generated. - * @type {Object} - */ - this.uniformGroups = {}; - } - - /** - * Destroys this program - * TODO - */ - destroy() - { - this.uniformData = null; - this.uniformGroups = null; - this.program = null; - } -} diff --git a/packages/core/src/renderers/systems/shader/ShaderSystem.js b/packages/core/src/renderers/systems/shader/ShaderSystem.js deleted file mode 100644 index 6cf57b6..0000000 --- a/packages/core/src/renderers/systems/shader/ShaderSystem.js +++ /dev/null @@ -1,170 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; -import GLProgram from './GLProgram'; -import generateUniformsSync from '../../../shader/generateUniformsSync'; -import defaultValue from '../../../shader/defaultValue'; -import compileProgram from './shader/compileProgram'; - -let UID = 0; - -/** - * Helper class to create a webGL Texture - * - * @class - * @memberof PIXI - */ -export default class ShaderSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - A reference to the current renderer - */ - constructor(renderer) - { - super(renderer); - - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = null; - - this.shader = null; - this.program = null; - - this.id = UID++; - } - - contextChange(gl) - { - this.gl = gl; - } - - /** - * Changes the current shader to the one given in parameter - * - * @param {PIXI.Shader} shader - the new shader - * @param {boolean} dontSync - false if the shader should automatically sync its uniforms. - * @returns {PIXI.glCore.glProgram} the glProgram that belongs to the shader. - */ - bind(shader, dontSync) - { - shader.uniforms.globals = this.renderer.globalUniforms; - - const program = shader.program; - const glProgram = program.glPrograms[this.renderer.CONTEXT_UID] || this.generateShader(shader); - - this.shader = shader; - - // TODO - some current pixi plugins bypass this.. so it not safe to use yet.. - if (this.program !== program) - { - this.program = program; - this.gl.useProgram(glProgram.program); - } - - if (!dontSync) - { - this.syncUniformGroup(shader.uniformGroup); - } - - return glProgram; - } - - /** - * Uploads the uniforms values to the currently bound shader. - * - * @param {object} uniforms - the uniforms valiues that be applied to the current shader - */ - setUniforms(uniforms) - { - const shader = this.shader.program; - const glProgram = shader.glPrograms[this.renderer.CONTEXT_UID]; - - shader.syncUniforms(glProgram.uniformData, uniforms, this.renderer); - } - - syncUniformGroup(group) - { - const glProgram = this.getglProgram(); - - if (!group.static || group.dirtyId !== glProgram.uniformGroups[group.id]) - { - glProgram.uniformGroups[group.id] = group.dirtyId; - const syncFunc = group.syncUniforms[this.shader.program.id] || this.createSyncGroups(group); - - syncFunc(glProgram.uniformData, group.uniforms, this.renderer); - } - } - - createSyncGroups(group) - { - group.syncUniforms[this.shader.program.id] = generateUniformsSync(group, this.shader.program.uniformData); - - return group.syncUniforms[this.shader.program.id]; - } - - /** - * Returns the underlying GLShade rof the currently bound shader. - * This can be handy for when you to have a little more control over the setting of your uniforms. - * - * @return {PIXI.glCore.Shader} the glProgram for the currently bound Shader for this context - */ - getglProgram() - { - if (this.shader) - { - return this.shader.program.glPrograms[this.renderer.CONTEXT_UID]; - } - - return null; - } - - /** - * Generates a glProgram verion of the Shader provided. - * - * @private - * @param {PIXI.Shader} shader the shader that the glProgram will be based on. - * @return {PIXI.glCore.glProgram} A shiney new glProgram - */ - generateShader(shader) - { - const gl = this.gl; - - const program = shader.program; - - const attribMap = {}; - - for (const i in program.attributeData) - { - attribMap[i] = program.attributeData[i].location; - } - - const shaderProgram = compileProgram(gl, program.vertexSrc, program.fragmentSrc, attribMap); - const uniformData = {}; - - for (const i in program.uniformData) - { - const data = program.uniformData[i]; - - uniformData[i] = { - location: gl.getUniformLocation(shaderProgram, i), - value: defaultValue(data.type, data.size), - }; - } - - const glProgram = new GLProgram(shaderProgram, uniformData); - - program.glPrograms[this.renderer.CONTEXT_UID] = glProgram; - - return glProgram; - } - - /** - * Destroys this System and removes all its textures - */ - destroy() - { - // TODO implement destroy method for ShaderSystem - this.destroyed = true; - } -} diff --git a/packages/core/src/renderers/systems/shader/shader/compileProgram.js b/packages/core/src/renderers/systems/shader/shader/compileProgram.js deleted file mode 100644 index 8c6ccb9..0000000 --- a/packages/core/src/renderers/systems/shader/shader/compileProgram.js +++ /dev/null @@ -1,78 +0,0 @@ -/** - * @method compileProgram - * @memberof PIXI.glCore.shader - * @param gl {WebGLRenderingContext} The current WebGL context {WebGLProgram} - * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. - * @param fragmentSrc {string|string[]} The fragment shader source as an array of strings. - * @param attributeLocations {Object} An attribute location map that lets you manually set the attribute locations - * @return {WebGLProgram} the shader program - */ -export default function compileProgram(gl, vertexSrc, fragmentSrc, attributeLocations) -{ - const glVertShader = compileShader(gl, gl.VERTEX_SHADER, vertexSrc); - const glFragShader = compileShader(gl, gl.FRAGMENT_SHADER, fragmentSrc); - - let program = gl.createProgram(); - - gl.attachShader(program, glVertShader); - gl.attachShader(program, glFragShader); - - // optionally, set the attributes manually for the program rather than letting WebGL decide.. - if (attributeLocations) - { - for (const i in attributeLocations) - { - gl.bindAttribLocation(program, attributeLocations[i], i); - } - } - - gl.linkProgram(program); - - // if linking fails, then log and cleanup - if (!gl.getProgramParameter(program, gl.LINK_STATUS)) - { - console.error('Pixi.js Error: Could not initialize shader.'); - console.error('gl.VALIDATE_STATUS', gl.getProgramParameter(program, gl.VALIDATE_STATUS)); - console.error('gl.getError()', gl.getError()); - - // if there is a program info log, log it - if (gl.getProgramInfoLog(program) !== '') - { - console.warn('Pixi.js Warning: gl.getProgramInfoLog()', gl.getProgramInfoLog(program)); - } - - gl.deleteProgram(program); - program = null; - } - - // clean up some shaders - gl.deleteShader(glVertShader); - gl.deleteShader(glFragShader); - - return program; -} - -/** - * @private - * @param gl {WebGLRenderingContext} The current WebGL context {WebGLProgram} - * @param type {Number} the type, can be either VERTEX_SHADER or FRAGMENT_SHADER - * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. - * @return {WebGLShader} the shader - */ -function compileShader(gl, type, src) -{ - const shader = gl.createShader(type); - - gl.shaderSource(shader, src); - gl.compileShader(shader); - - if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) - { - console.warn(src); - console.error(gl.getShaderInfoLog(shader)); - - return null; - } - - return shader; -} diff --git a/packages/core/src/renderers/systems/shader/shader/defaultValue.js b/packages/core/src/renderers/systems/shader/shader/defaultValue.js deleted file mode 100644 index 97290bd..0000000 --- a/packages/core/src/renderers/systems/shader/shader/defaultValue.js +++ /dev/null @@ -1,79 +0,0 @@ -/** - * @method defaultValue - * @memberof PIXI.glCore.shader - * @param type {String} Type of value - * @param size {Number} - */ -export default function defaultValue(type, size) -{ - switch (type) - { - case 'float': - return 0; - - case 'vec2': - return new Float32Array(2 * size); - - case 'vec3': - return new Float32Array(3 * size); - - case 'vec4': - return new Float32Array(4 * size); - - case 'int': - case 'sampler2D': - case 'sampler2DArray': - return 0; - - case 'ivec2': - return new Int32Array(2 * size); - - case 'ivec3': - return new Int32Array(3 * size); - - case 'ivec4': - return new Int32Array(4 * size); - - case 'bool': - return false; - - case 'bvec2': - - return booleanArray(2 * size); - - case 'bvec3': - return booleanArray(3 * size); - - case 'bvec4': - return booleanArray(4 * size); - - case 'mat2': - return new Float32Array([1, 0, - 0, 1]); - - case 'mat3': - return new Float32Array([1, 0, 0, - 0, 1, 0, - 0, 0, 1]); - - case 'mat4': - return new Float32Array([1, 0, 0, 0, - 0, 1, 0, 0, - 0, 0, 1, 0, - 0, 0, 0, 1]); - } - - return null; -} - -function booleanArray(size) -{ - const array = new Array(size); - - for (let i = 0; i < array.length; i++) - { - array[i] = false; - } - - return array; -} diff --git a/packages/core/src/renderers/systems/shader/shader/index.js b/packages/core/src/renderers/systems/shader/shader/index.js deleted file mode 100644 index ba7a317..0000000 --- a/packages/core/src/renderers/systems/shader/shader/index.js +++ /dev/null @@ -1,8 +0,0 @@ -/** - * @namespace PIXI.glCore.shader - */ -export { default as compileProgram } from './compileProgram'; -export { default as defaultValue } from './defaultValue'; -export { default as setPrecision } from './setPrecision'; -export { default as mapSize } from './mapSize'; -export { default as mapType } from './mapType'; diff --git a/packages/core/src/renderers/systems/shader/shader/mapSize.js b/packages/core/src/renderers/systems/shader/shader/mapSize.js deleted file mode 100644 index b681554..0000000 --- a/packages/core/src/renderers/systems/shader/shader/mapSize.js +++ /dev/null @@ -1,33 +0,0 @@ -const GLSL_TO_SIZE = { - float: 1, - vec2: 2, - vec3: 3, - vec4: 4, - - int: 1, - ivec2: 2, - ivec3: 3, - ivec4: 4, - - bool: 1, - bvec2: 2, - bvec3: 3, - bvec4: 4, - - mat2: 4, - mat3: 9, - mat4: 16, - - sampler2D: 1, -}; - -/** - * @method mapSize - * @memberof PIXI.glCore.shader - * @param type {String} - * @return {Number} - */ -export default function mapSize(type) -{ - return GLSL_TO_SIZE[type]; -} diff --git a/packages/core/src/AbstractRenderer.js b/packages/core/src/AbstractRenderer.js new file mode 100644 index 0000000..64b2eb1 --- /dev/null +++ b/packages/core/src/AbstractRenderer.js @@ -0,0 +1,353 @@ +import { hex2string, hex2rgb } from '@pixi/utils'; +import { Matrix, Rectangle } from '@pixi/math'; +import { RENDERER_TYPE } from '@pixi/constants'; +import { settings } from '@pixi/settings'; +import { Container } from '@pixi/display'; +import RenderTexture from './renderTexture/RenderTexture'; +import EventEmitter from 'eventemitter3'; + +const tempMatrix = new Matrix(); + +/** + * 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 EventEmitter + * @memberof PIXI + */ +export default class AbstractRenderer extends EventEmitter +{ + // eslint-disable-next-line valid-jsdoc + /** + * @param {string} system - The name of the system this renderer is for. + * @param {object} [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.transparent=false] - If the render view is transparent, default false + * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false + * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. The + * resolution of the renderer retina would be 2. + * @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 {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when rendering, + * stopping pixel interpolation. + */ + constructor(system, options, arg2, arg3) + { + super(); + + // Support for constructor(system, screenWidth, screenHeight, options) + if (typeof options === 'number') + { + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + }, arg3); + } + + // 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 = RENDERER_TYPE.UNKNOWN; + + /** + * Measurements of the screen. (0, 0, screenWidth, screenHeight) + * + * Its safe to use as filterArea or hitArea for 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 1 + */ + this.resolution = options.resolution || settings.RESOLUTION; + + /** + * Whether the render view is transparent + * + * @member {boolean} + */ + this.transparent = options.transparent; + + /** + * Whether css dimensions of canvas view should be resized to screen dimensions automatically + * + * @member {boolean} + */ + this.autoResize = options.autoResize || false; + + /** + * Tracks the blend modes useful for this renderer. + * + * @member {object} + */ + this.blendModes = null; + + /** + * 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; + + /** + * If true PixiJS will Math.floor() x/y values when rendering, stopping pixel interpolation. + * Handy for crisp pixel art and speed on legacy devices. + * + * @member {boolean} + */ + this.roundPixels = options.roundPixels; + + /** + * The background color as a number. + * + * @member {number} + * @private + */ + this._backgroundColor = 0x000000; + + /** + * The background color as an [R, G, B] array. + * + * @member {number[]} + * @private + */ + this._backgroundColorRgba = [0, 0, 0, 0]; + + /** + * The background color as a string. + * + * @member {string} + * @private + */ + this._backgroundColorString = '#000000'; + + this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter + + /** + * This temporary display object used as the parent of the currently being rendered item + * + * @member {PIXI.DisplayObject} + * @private + */ + this._tempDisplayObjectParent = new Container(); + + /** + * The last root object that the renderer tried to render. + * + * @member {PIXI.DisplayObject} + * @private + */ + this._lastObjectRendered = this._tempDisplayObjectParent; + + /** + * Collection of plugins. + * @readonly + * @member {object} + */ + this.plugins = {}; + } + + /** + * Initialize the plugins. + * + * @protected + * @param {object} staticMap - The dictionary of staticly saved plugins. + */ + initPlugins(staticMap) + { + 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() + { + return this.view.width; + } + + /** + * Same as view.height, actual number of pixels in the canvas by vertical + * + * @member {number} + * @readonly + * @default 600 + */ + get height() + { + return this.view.height; + } + + /** + * Resizes the screen and canvas to the specified width and height + * Canvas dimensions are multiplied by resolution + * + * @param {number} screenWidth - the new width of the screen + * @param {number} screenHeight - the new height of the screen + */ + resize(screenWidth, screenHeight) + { + this.screen.width = screenWidth; + this.screen.height = screenHeight; + + this.view.width = screenWidth * this.resolution; + this.view.height = screenHeight * this.resolution; + + if (this.autoResize) + { + this.view.style.width = `${screenWidth}px`; + this.view.style.height = `${screenHeight}px`; + } + } + + /** + * 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 {PIXI.DisplayObject} displayObject - The displayObject the object will be generated from + * @param {number} scaleMode - Should be one of the scaleMode consts + * @param {number} resolution - The resolution / device pixel ratio of the texture being generated + * @param {PIXI.Rectangle} [region] - The region of the displayObject, that shall be rendered, + * if no region is specified, defaults to the local bounds of the displayObject. + * @return {PIXI.Texture} a texture of the graphics object + */ + generateTexture(displayObject, scaleMode, resolution, region) + { + region = region || displayObject.getLocalBounds(); + + // 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(region.width | 0, region.height | 0, scaleMode, resolution); + + tempMatrix.tx = -region.x; + tempMatrix.ty = -region.y; + + this.render(displayObject, renderTexture, false, tempMatrix, true); + + return renderTexture; + } + + /** + * Removes everything from the renderer and optionally removes the Canvas DOM element. + * + * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. + */ + destroy(removeView) + { + 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); + } + + this.plugins = null; + + this.type = RENDERER_TYPE.UNKNOWN; + + this.view = null; + + this.screen = null; + + this.resolution = 0; + + this.transparent = false; + + this.autoResize = false; + + this.blendModes = null; + + this.options = null; + + this.preserveDrawingBuffer = false; + this.clearBeforeRender = false; + + this.roundPixels = false; + + this._backgroundColor = 0; + this._backgroundColorRgba = null; + this._backgroundColorString = null; + + this._tempDisplayObjectParent = null; + this._lastObjectRendered = null; + } + + /** + * The background color to fill if not transparent + * + * @member {number} + */ + get backgroundColor() + { + return this._backgroundColor; + } + + set backgroundColor(value) // eslint-disable-line require-jsdoc + { + this._backgroundColor = value; + this._backgroundColorString = hex2string(value); + hex2rgb(value, this._backgroundColorRgba); + } +} diff --git a/packages/core/src/Renderer.js b/packages/core/src/Renderer.js new file mode 100644 index 0000000..2227e78 --- /dev/null +++ b/packages/core/src/Renderer.js @@ -0,0 +1,338 @@ +import AbstractRenderer from './AbstractRenderer'; +import { sayHello } from '@pixi/utils'; +import MaskSystem from './mask/MaskSystem'; +import StencilSystem from './mask/StencilSystem'; +import FilterSystem from './filters/FilterSystem'; +import FramebufferSystem from './framebuffer/FramebufferSystem'; +import RenderTextureSystem from './renderTexture/RenderTextureSystem'; +import TextureSystem from './textures/TextureSystem'; +import ProjectionSystem from './projection/ProjectionSystem'; +import StateSystem from './state/StateSystem'; +import GeometrySystem from './geometry/GeometrySystem'; +import ShaderSystem from './shader/ShaderSystem'; +import ContextSystem from './context/ContextSystem'; +import BatchSystem from './batch/BatchSystem'; +import TextureGCSystem from './textures/TextureGCSystem'; +import { RENDERER_TYPE } from '@pixi/constants'; +import UniformGroup from './shader/UniformGroup'; +import { Matrix } from '@pixi/math'; +import Runner from 'mini-runner'; + +/** + * 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 Render works by automatically managing webGLBatchs. + * 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 :) + * + * @class + * @memberof PIXI + * @extends PIXI.AbstractRenderer + */ +export default class Renderer extends AbstractRenderer +{ + // eslint-disable-next-line valid-jsdoc + /** + * + * @param {object} [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.transparent=false] - If the render view is transparent, default false + * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false + * @param {boolean} [options.antialias=false] - sets antialias. If not available natively then FXAA + * antialiasing is used + * @param {boolean} [options.forceFXAA=false] - forces FXAA antialiasing to be used over native. + * FXAA is faster, but may not always look as great + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. + * The resolution of the renderer retina would be 2. + * @param {boolean} [options.clearBeforeRender=true] - This sets if the renderer will clear + * the canvas or not before the new render pass. If you wish to set this to false, you *must* set + * preserveDrawingBuffer to `true`. + * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, + * enable this if you need to call toDataUrl on the webgl context. + * @param {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when + * rendering, stopping pixel interpolation. + * @param {number} [options.backgroundColor=0x000000] - The background color of the rendered area + * (shown if not transparent). + * @param {boolean} [options.legacy=false] - If true PixiJS will aim to ensure compatibility + * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. + * @param {string} [options.powerPreference] - Parameter passed to webgl context, set to "high-performance" + * for devices with dual graphics card + */ + constructor(options, arg2, arg3) + { + super('WebGL', options, arg2, arg3); + + /** + * The type of this renderer as a standardised const + * + * @member {number} + * @see PIXI.RENDERER_TYPE + */ + this.type = RENDERER_TYPE.WEBGL; + + // this will be set by the contextSystem (this.context) + this.gl = null; + this.CONTEXT_UID = 0; + this.legacy = !!options.legacy; + + // TODO legacy! + + // runners! + this.runners = { + destroy: new Runner('destroy'), + contextChange: new Runner('contextChange', 1), + reset: new Runner('reset'), + update: new Runner('update'), + postrender: new Runner('postrender'), + prerender: new Runner('prerender'), + resize: new Runner('resize', 2), + }; + + 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(GeometrySystem, 'geometry') + .addSystem(FramebufferSystem, 'framebuffer') + .addSystem(StencilSystem, 'stencil') + .addSystem(ProjectionSystem, 'projection') + .addSystem(TextureGCSystem, 'textureGC') + .addSystem(FilterSystem, 'filter') + .addSystem(RenderTextureSystem, 'renderTexture') + .addSystem(BatchSystem, 'batch'); + + this.initPlugins(Renderer.__plugins); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ + if (options.context) + { + this.context.initFromContext(options.context); + } + else + { + this.context.initFromOptions({ + alpha: this.transparent, + antialias: options.antialias, + premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', + stencil: true, + 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); + } + + /** + * Add a new system to the renderer. + * @param {class} ClassRef - Class reference + * @param {string} [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. + * @return {PIXI.Renderer} Return instance of renderer + */ + addSystem(ClassRef, name) + { + if (!name) + { + name = ClassRef.name; + } + + // TODO - read name from class.name.. + + /* + if(name.includes('System')) + { + name = name.replace('System', ''); + name = name.charAt(0).toLowerCase() + name.slice(1); + } + */ + + const system = new ClassRef(this); + + if (this[name]) + { + throw new Error(`Whoops! ${name} is already a manger`); + } + + this[name] = system; + + for (const i in this.runners) + { + this.runners[i].add(system); + } + + return this; + + /** + * 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. + */ + } + + /** + * Renders the object to its webGL view + * + * @param {PIXI.DisplayObject} displayObject - the object to be rendered + * @param {PIXI.RenderTexture} renderTexture - The render texture to render to. + * @param {boolean} [clear] - Should the canvas be cleared before the new render + * @param {PIXI.Transform} [transform] - A transform to apply to the render texture before rendering. + * @param {boolean} [skipUpdateTransform] - Should we skip the update transform pass? + */ + render(displayObject, renderTexture, clear, transform, skipUpdateTransform) + { + // can be handy to know! + this.renderingToScreen = !renderTexture; + + this.runners.prerender.run(); + this.emit('prerender'); + + // 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.parent; + + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = 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.run(); + + this.emit('postrender'); + } + + /** + * Resizes the webGL view to the specified width and height. + * + * @param {number} screenWidth - the new width of the screen + * @param {number} screenHeight - the new height of the screen + */ + resize(screenWidth, screenHeight) + { + AbstractRenderer.prototype.resize.call(this, screenWidth, screenHeight); + + this.runners.resize.run(screenWidth, screenHeight); + } + + /** + * Resets the WebGL state so you can render things however you fancy! + * + * @return {PIXI.Renderer} Returns itself. + */ + reset() + { + this.runners.reset.run(); + + return this; + } + + clear() + { + this.framebuffer.bind(); + this.framebuffer.clear(); + } + + /** + * Removes everything from the renderer (event listeners, spritebatch, etc...) + * + * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. + * See: https://github.com/pixijs/pixi.js/issues/2233 + */ + destroy(removeView) + { + this.runners.destroy.run(); + + // call base destroy + super.destroy(removeView); + + // TODO nullify all the managers.. + this.gl = null; + } + + /** + * 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 + * builds and excluding plugins. + * @name PIXI.Renderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. + */ + + /** + * Adds a plugin to the renderer. + * + * @method + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + static registerPlugin(pluginName, ctor) + { + Renderer.__plugins = Renderer.__plugins || {}; + Renderer.__plugins[pluginName] = ctor; + } +} diff --git a/packages/core/src/System.js b/packages/core/src/System.js new file mode 100644 index 0000000..05a10c8 --- /dev/null +++ b/packages/core/src/System.js @@ -0,0 +1,42 @@ +/** + * System is a base class used for extending systems used by the {@link PIXI.Renderer} + * @see PIXI.Renderer#addSystem + * @class + * @memberof PIXI + */ +export default class System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + /** + * The renderer this manager works for. + * + * @member {PIXI.Renderer} + */ + this.renderer = renderer; + + this.renderer.runners.contextChange.add(this); + } + + /** + * Generic method called when there is a WebGL context change. + * + */ + contextChange() + { + // do some codes init! + } + + /** + * Generic destroy methods to be overridden by the subclass + * + */ + destroy() + { + this.renderer.runners.contextChange.remove(this); + this.renderer = null; + } +} diff --git a/packages/core/src/batch/BatchSystem.js b/packages/core/src/batch/BatchSystem.js new file mode 100644 index 0000000..b04c249 --- /dev/null +++ b/packages/core/src/batch/BatchSystem.js @@ -0,0 +1,68 @@ +import System from '../System'; +import ObjectRenderer from './ObjectRenderer'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class BatchSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + super(renderer); + + /** + * An empty renderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.emptyRenderer = new ObjectRenderer(renderer); + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.currentRenderer = this.emptyRenderer; + } + + /** + * Changes the current renderer to the one given in parameter + * + * @param {PIXI.ObjectRenderer} objectRenderer - The object renderer to use. + */ + setObjectRenderer(objectRenderer) + { + if (this.currentRenderer === objectRenderer) + { + return; + } + + this.currentRenderer.stop(); + this.currentRenderer = objectRenderer; + + this.renderer.state.setState(objectRenderer.state); + + this.currentRenderer.start(); + } + + /** + * This should be called if you wish to do some custom rendering + * It will basically render anything that may be batched up such as sprites + * + */ + flush() + { + this.setObjectRenderer(this.emptyRenderer); + } + + reset() + { + this.setObjectRenderer(this.emptyRenderer); + } +} diff --git a/packages/core/src/batch/ObjectRenderer.js b/packages/core/src/batch/ObjectRenderer.js new file mode 100644 index 0000000..0c851a9 --- /dev/null +++ b/packages/core/src/batch/ObjectRenderer.js @@ -0,0 +1,48 @@ +import System from '../System'; + +/** + * Base for a common object renderer that can be used as a system renderer plugin. + * + * @class + * @extends PIXI.System + * @memberof PIXI + */ +export default class ObjectRenderer extends System +{ + /** + * Starts the renderer and sets the shader + * + */ + start() + { + // set the shader.. + } + + /** + * Stops the renderer + * + */ + stop() + { + this.flush(); + } + + /** + * Stub method for rendering content and emptying the current batch. + * + */ + flush() + { + // flush! + } + + /** + * Renders an object + * + * @param {PIXI.DisplayObject} object - The object to render. + */ + render(object) // eslint-disable-line no-unused-vars + { + // render the object + } +} diff --git a/packages/core/src/context/ContextSystem.js b/packages/core/src/context/ContextSystem.js new file mode 100644 index 0000000..301aab4 --- /dev/null +++ b/packages/core/src/context/ContextSystem.js @@ -0,0 +1,186 @@ +import System from '../System'; +import { settings } from '@pixi/settings'; + +let CONTEXT_UID = 0; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class ContextSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.webGLVersion = 1; + + this.handleContextLost = this.handleContextLost.bind(this); + this.handleContextRestored = this.handleContextRestored.bind(this); + + this.extensions = {}; + + renderer.view.addEventListener('webglcontextlost', this.handleContextLost, false); + renderer.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + } + + get isLost() + { + return (!this.gl || this.gl.isContextLost()); + } + + contextChange(gl) + { + this.gl = gl; + + // restore a context if it was previously lost + if (gl.isContextLost() && gl.getExtension('WEBGL_lose_context')) + { + gl.getExtension('WEBGL_lose_context').restoreContext(); + } + } + + initFromContext(gl) + { + this.gl = gl; + this.validateContext(gl); + this.renderer.gl = gl; + this.renderer.CONTEXT_UID = CONTEXT_UID++; + this.renderer.runners.contextChange.run(gl); + } + + initFromOptions(options) + { + const gl = this.createContext(this.renderer.view, options); + + this.initFromContext(gl); + } + + /** + * Helper class to create a webGL Context + * + * @class + * @memberof PIXI.glCore + * @param canvas {HTMLCanvasElement} the canvas element that we will get the context from + * @param options {Object} An options object that gets passed in to the canvas element containing the context attributes, + * see https://developer.mozilla.org/en/docs/Web/API/HTMLCanvasElement/getContext for the options available + * @return {WebGLRenderingContext} the WebGL context + */ + createContext(canvas, options) + { + let gl; + + if (settings.PREFER_WEBGL_2) + { + gl = canvas.getContext('webgl2', options); + } + + if (gl) + { + this.webGLVersion = 2; + } + else + { + this.webGLVersion = 1; + + gl = canvas.getContext('webgl', options) + || canvas.getContext('experimental-webgl', options); + + if (!gl) + { + // fail, not able to get a context + throw new Error('This browser does not support webGL. Try using the canvas renderer'); + } + } + + this.gl = gl; + + this.getExtensions(); + + return gl; + } + + getExtensions() + { + // time to set up default etensions that pixi uses.. + const gl = this.gl; + const extensions = this.extensions; + + if (this.webGLVersion === 1) + { + extensions.drawBuffers = gl.getExtension('WEBGL_draw_buffers'); + extensions.depthTexture = gl.getExtension('WEBKIT_WEBGL_depth_texture'); + extensions.floatTexture = gl.getExtension('OES_texture_float'); + extensions.loseContext = gl.getExtension('WEBGL_lose_context'); + + extensions.vertexArrayObject = gl.getExtension('OES_vertex_array_object') + || gl.getExtension('MOZ_OES_vertex_array_object') + || gl.getExtension('WEBKIT_OES_vertex_array_object'); + } + + // we don't use any specific WebGL 2 ones yet! + } + + /** + * Handles a lost webgl context + * + * @private + * @param {WebGLContextEvent} event - The context lost event. + */ + handleContextLost(event) + { + event.preventDefault(); + } + + /** + * Handles a restored webgl context + * + * @private + */ + handleContextRestored() + { + this.renderer.runners.contextChange.run(this.gl); + } + + destroy() + { + const view = this.renderer.view; + + // remove listeners + view.removeEventListener('webglcontextlost', this.handleContextLost); + view.removeEventListener('webglcontextrestored', this.handleContextRestored); + + this.gl.useProgram(null); + + if (this.extensions.loseContext) + { + this.extensions.loseContext.loseContext(); + } + } + + postrender() + { + this.gl.flush(); + } + + validateContext(gl) + { + const attributes = gl.getContextAttributes(); + + // this is going to be fairly simple for now.. but at least we have room to grow! + if (!attributes.stencil) + { + /* eslint-disable max-len */ + + /* eslint-disable no-console */ + console.warn('Provided WebGL context does not have a stencil buffer, masks may not render correctly'); + /* eslint-enable no-console */ + + /* eslint-enable max-len */ + } + } +} diff --git a/packages/core/src/filters/Filter.js b/packages/core/src/filters/Filter.js new file mode 100644 index 0000000..9d8cad8 --- /dev/null +++ b/packages/core/src/filters/Filter.js @@ -0,0 +1,140 @@ +import Shader from '../shader/Shader'; +import Program from '../shader/Program'; +import State from '../state/State'; +import { settings } from '@pixi/settings'; +// import extractUniformsFromSrc from './extractUniformsFromSrc'; +import defaultVertex from './defaultFilter.vert'; +import defaultFragment from './defaultFilter.frag'; + +// let math = require('../../math'); +/** + * @class + * @memberof PIXI + * @extends PIXI.Shader + */ +export default class Filter extends Shader +{ + /** + * @param {string} [vertexSrc] - The source of the vertex shader. + * @param {string} [fragmentSrc] - The source of the fragment shader. + * @param {object} [uniforms] - Custom uniforms to use to augment the built-in ones. + */ + constructor(vertexSrc, fragmentSrc, uniforms) + { + const program = Program.from(vertexSrc, fragmentSrc); + + super(program, uniforms); + + /** + * The padding of the filter. Some filters require extra space to breath such as a blur. + * Increasing this will add extra width and height to the bounds of the object that the + * filter is applied to. + * + * @member {number} + */ + this.padding = 0; + + /** + * The resolution of the filter. Setting this to be lower will lower the quality but + * increase the performance of the filter. + * + * @member {number} + */ + this.resolution = settings.RESOLUTION; + + /** + * If enabled is true the filter is applied, if false it will not. + * + * @member {boolean} + */ + this.enabled = true; + + /** + * If enabled, PixiJS will fit the filter area into boundaries for better performance. + * Switch it off if it does not work for specific shader. + * + * @member {boolean} + */ + this.autoFit = true; + + /** + * Legacy filters use position and uvs from attributes + * @member {boolean} + * @readonly + */ + this.legacy = !!this.program.attributeData.aTextureCoord; + + /** + * the webGL state the filter requires to render + * @member {PIXI.State} + */ + this.state = new State(); + } + + /** + * Applies the filter + * + * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + * @param {boolean} clear - Should the output be cleared before rendering to it + * @param {object} [currentState] - It's current state of filter. + * There are some useful properties in the currentState : + * target, filters, sourceFrame, destinationFrame, renderTarget, resolution + */ + apply(filterManager, input, output, clear, currentState, derp) // eslint-disable-line no-unused-vars + { + // do as you please! + + filterManager.applyFilter(this, input, output, clear, currentState, derp); + + // or just do a regular render.. + } + + /** + * Sets the blendmode of the filter + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL + */ + get blendMode() + { + return this.state.blendMode; + } + + set blendMode(value) // eslint-disable-line require-jsdoc + { + this.state.blendMode = value; + } + + /** + * The default vertex shader source + * + * @static + * @constant + */ + static get defaultVertexSrc() + { + return defaultVertex; + } + + /** + * The default fragment shader source + * + * @static + * @constant + */ + static get defaultFragmentSrc() + { + return defaultFragment; + } +} + +/** + * Used for caching shader IDs + * + * @static + * @private + */ +Filter.SOURCE_KEY_MAP = {}; + diff --git a/packages/core/src/filters/FilterSystem.js b/packages/core/src/filters/FilterSystem.js new file mode 100644 index 0000000..3174318 --- /dev/null +++ b/packages/core/src/filters/FilterSystem.js @@ -0,0 +1,431 @@ +import System from '../System'; + +import RenderTexture from '../renderTexture/RenderTexture'; +import Quad from '../utils/Quad'; +import QuadUv from '../utils/QuadUv'; +import { Rectangle } from '@pixi/math'; +import * as filterTransforms from './filterTransforms'; +import bitTwiddle from 'bit-twiddle'; +import UniformGroup from '../shader/UniformGroup'; +import { DRAW_MODES } from '@pixi/constants'; + +// +/** + * @ignore + * @class + */ +class FilterState +{ + /** + * + */ + constructor() + { + this.renderTexture = null; + this.sourceFrame = new Rectangle(); + this.destinationFrame = new Rectangle(); + this.filters = []; + this.target = null; + this.legacy = false; + this.resolution = 1; + } +} + +/** + * @class + * @memberof PIXI.systems + * @extends PIXI.System + */ +export default class FilterSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + /** + * stores a bunch of PO2 textures used for filtering + * @type {Object} + */ + this.texturePool = {}; + + /** + * a pool for storing filter states, save us creating new ones each tick + * @type {Array} + */ + this.statePool = []; + + /** + * A very simple geometry used when drawing a filter effect to the screen + * @type {Quad} + */ + this.quad = new Quad(); + + this.quadUv = new QuadUv(); + + /** + * Temporary rect for maths + * @type {PIXI.Rectangle} + */ + this.tempRect = new Rectangle(); + + this.activeState = {}; + + /** + * this uniform group is attached to filter uniforms when used + * @type {UniformGroup} + */ + this.globalUniforms = new UniformGroup({ + sourceFrame: this.tempRect, + destinationFrame: this.tempRect, + + // legacy variables + filterArea: new Float32Array(4), + filterClamp: new Float32Array(4), + }, true); + } + + /** + * Adds a new filter to the System. + * + * @param {PIXI.DisplayObject} target - The target of the filter to render. + * @param {PIXI.Filter[]} filters - The filters to apply. + */ + push(target, filters) + { + const renderer = this.renderer; + const filterStack = this.renderer.renderTexture.defaultFilterStack; + const state = this.statePool.pop() || new FilterState(); + + let resolution = filters[0].resolution; + let padding = filters[0].padding; + let autoFit = filters[0].autoFit; + let legacy = filters[0].legacy; + + for (let i = 1; i < filters.length; i++) + { + const filter = filters[i]; + + // lets use the lowest resolution.. + resolution = Math.min(resolution, filter.resolution); + // and the largest amount of padding! + padding = Math.max(padding, filter.padding); + // only auto fit if all filters are autofit + autoFit = autoFit || filter.autoFit; + + legacy = legacy || filter.legacy; + } + + filterStack.push(state); + + state.resolution = resolution; + + state.legacy = legacy; + + // round to whole number based on resolution + // TODO move that to the shader too? + state.sourceFrame = target.filterArea || target.getBounds(true); + + state.sourceFrame.pad(padding); + + if (autoFit) + { + state.sourceFrame.fit(this.renderer.renderTexture.destinationFrame); + } + + state.sourceFrame.round(resolution); + + state.renderTexture = this.getPotFilterTexture(state.sourceFrame.width, state.sourceFrame.height, resolution); + state.filters = filters; + + state.destinationFrame.width = state.renderTexture.width; + state.destinationFrame.height = state.renderTexture.height; + + state.renderTexture.filterFrame = state.sourceFrame; + renderer.renderTexture.bind(state.renderTexture, state.sourceFrame);// /, state.destinationFrame); + renderer.renderTexture.clear(); + } + + /** + * Pops off the filter and applies it. + * + */ + pop() + { + const renderer = this.renderer; + const filterStack = renderer.renderTexture.defaultFilterStack; + const state = filterStack.pop(); + const filters = state.filters; + + this.activeState = state; + + const globalUniforms = this.globalUniforms.uniforms; + + globalUniforms.sourceFrame = state.sourceFrame; + globalUniforms.destinationFrame = state.destinationFrame; + globalUniforms.resolution = state.resolution; + + // only update the rect if its legacy.. + if (state.legacy) + { + const filterArea = globalUniforms.filterArea; + const filterClamp = globalUniforms.filterClamp; + + filterArea[0] = state.destinationFrame.width; + filterArea[1] = state.destinationFrame.height; + filterArea[2] = state.sourceFrame.x; + filterArea[3] = state.sourceFrame.y; + + filterClamp[0] = 0.5 / state.resolution / state.destinationFrame.width; + filterClamp[1] = 0.5 / state.resolution / state.destinationFrame.height; + filterClamp[2] = (state.sourceFrame.width - 0.5) / state.resolution / state.destinationFrame.width; + filterClamp[3] = (state.sourceFrame.height - 0.5) / state.resolution / state.destinationFrame.height; + } + + this.globalUniforms.update(); + + const lastState = filterStack[filterStack.length - 1]; + + if (filters.length === 1) + { + filters[0].apply(this, state.renderTexture, lastState.renderTexture, false, state); + + this.returnFilterTexture(state.renderTexture); + } + else + { + let flip = state.renderTexture; + let flop = this.getPotFilterTexture( + flip.width, + flip.height, + state.resolution + ); + + flop.filterFrame = flip.filterFrame; + + let i = 0; + + for (i = 0; i < filters.length - 1; ++i) + { + filters[i].apply(this, flip, flop, true, state); + + const t = flip; + + flip = flop; + flop = t; + } + + filters[i].apply(this, flip, lastState.renderTexture, false, state); + + this.returnFilterTexture(flip); + this.returnFilterTexture(flop); + } + + this.statePool.push(state); + } + + /** + * Draws a filter. + * + * @param {PIXI.Filter} filter - The filter to draw. + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + * @param {boolean} clear - Should the output be cleared before rendering to it + */ + applyFilter(filter, input, output, clear) + { + const renderer = this.renderer; + + renderer.renderTexture.bind(output, output ? output.filterFrame : null); + + if (clear) + { + // gl.disable(gl.SCISSOR_TEST); + renderer.renderTexture.clear(); + // gl.enable(gl.SCISSOR_TEST); + } + + // set the uniforms.. + filter.uniforms.uSampler = input; + filter.uniforms.filterGlobals = this.globalUniforms; + + // TODO make it so that the order of this does not matter.. + // because it does at the moment cos of global uniforms. + // they need to get resynced + + renderer.state.setState(filter.state); + renderer.shader.bind(filter); + + if (filter.legacy) + { + this.quadUv.map(input._frame, input.filterFrame); + + renderer.geometry.bind(this.quadUv); + renderer.geometry.draw(DRAW_MODES.TRIANGLES); + } + else + { + renderer.geometry.bind(this.quad); + renderer.geometry.draw(DRAW_MODES.TRIANGLE_STRIP); + } + } + + /** + * Calculates the mapped matrix. + * + * TODO playing around here.. this is temporary - (will end up in the shader) + * this returns a matrix that will normalise map filter cords in the filter to screen space + * + * @param {PIXI.Matrix} outputMatrix - the matrix to output to. + * @return {PIXI.Matrix} The mapped matrix. + */ + calculateScreenSpaceMatrix(outputMatrix) + { + const currentState = this.activeState; + + return filterTransforms.calculateScreenSpaceMatrix( + outputMatrix, + currentState.sourceFrame, + currentState.destinationFrame + ); + } + + /** + * This will map the filter coord so that a texture can be used based on the transform of a sprite + * + * @param {PIXI.Matrix} outputMatrix - The matrix to output to. + * @param {PIXI.Sprite} sprite - The sprite to map to. + * @return {PIXI.Matrix} The mapped matrix. + */ + calculateSpriteMatrix(outputMatrix, sprite) + { + const currentState = this.activeState; + + return filterTransforms.calculateSpriteMatrix( + outputMatrix, + currentState.sourceFrame, + currentState.destinationFrame, + sprite + ); + } + + /** + * Destroys this Filter System. + * + * @param {boolean} [contextLost=false] context was lost, do not free shaders + * + */ + destroy(contextLost = false) + { + if (!contextLost) + { + this.emptyPool(); + } + else + { + this.texturePool = {}; + } + } + + /** + * Gets a Power-of-Two render texture. + * + * TODO move to a seperate class could be on renderer? + * also - could cause issue with multiple contexts? + * + * @private + * @param {WebGLRenderingContext} gl - The webgl rendering context + * @param {number} minWidth - The minimum width of the render target. + * @param {number} minHeight - The minimum height of the render target. + * @param {number} resolution - The resolution of the render target. + * @return {PIXI.RenderTarget} The new render target. + */ + getPotFilterTexture(minWidth, minHeight, resolution) + { + minWidth = bitTwiddle.nextPow2(minWidth); + minHeight = bitTwiddle.nextPow2(minHeight); + resolution = resolution || 1; + + const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + + if (!this.texturePool[key]) + { + this.texturePool[key] = []; + } + + let renderTexture = this.texturePool[key].pop(); + + if (!renderTexture) + { + // temporary bypass cache.. + // internally - this will cause a texture to be bound.. + renderTexture = RenderTexture.create({ + width: minWidth, + height: minHeight, + resolution, + }); + } + + return renderTexture; + } + + /** + * Gets extra render texture to use inside current filter + * + * @param {number} resolution resolution of the renderTexture + * @returns {PIXI.RenderTexture} + */ + getFilterTexture(resolution) + { + const rt = this.activeState.renderTexture; + + const filterTexture = this.getPotFilterTexture(rt.width, rt.height, resolution || rt.baseTexture.resolution); + + filterTexture.filterFrame = rt.filterFrame; + + return filterTexture; + } + + /** + * Frees a render texture back into the pool. + * + * @param {PIXI.RenderTarget} renderTexture - The renderTarget to free + */ + returnFilterTexture(renderTexture) + { + renderTexture.filterFrame = null; + + const base = renderTexture.baseTexture; + + const minWidth = base.width; + const minHeight = base.height; + + const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + + this.texturePool[key].push(renderTexture); + } + + /** + * Empties the texture pool. + * + */ + emptyPool() + { + for (const i in this.texturePool) + { + const textures = this.texturePool[i]; + + if (textures) + { + for (let j = 0; j < textures.length; j++) + { + textures[j].destroy(true); + } + } + } + + this.texturePool = {}; + } +} diff --git a/packages/core/src/filters/defaultFilter.frag b/packages/core/src/filters/defaultFilter.frag new file mode 100644 index 0000000..a14c1be --- /dev/null +++ b/packages/core/src/filters/defaultFilter.frag @@ -0,0 +1,22 @@ +varying vec2 vTextureCoord; +varying vec2 vFilterCoord; + +uniform sampler2D uSampler; +uniform sampler2D filterSampler; + +void main(void){ + vec4 masky = texture2D(filterSampler, vFilterCoord); + vec4 sample = texture2D(uSampler, vTextureCoord); + vec4 color; + if(mod(vFilterCoord.x, 1.0) > 0.5) + { + color = vec4(1.0, 0.0, 0.0, 1.0); + } + else + { + color = vec4(0.0, 1.0, 0.0, 1.0); + } + // gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0); + gl_FragColor = mix(sample, masky, 0.5); + gl_FragColor *= sample.a; +} diff --git a/packages/core/src/filters/defaultFilter.vert b/packages/core/src/filters/defaultFilter.vert new file mode 100644 index 0000000..e1febca --- /dev/null +++ b/packages/core/src/filters/defaultFilter.vert @@ -0,0 +1,14 @@ +attribute vec2 aVertexPosition; +attribute vec2 aTextureCoord; + +uniform mat3 projectionMatrix; +uniform mat3 filterMatrix; + +varying vec2 vTextureCoord; +varying vec2 vFilterCoord; + +void main(void){ + gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); + vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy; + vTextureCoord = aTextureCoord ; +} diff --git a/packages/core/src/filters/filterTransforms.js b/packages/core/src/filters/filterTransforms.js new file mode 100644 index 0000000..87a3cd6 --- /dev/null +++ b/packages/core/src/filters/filterTransforms.js @@ -0,0 +1,50 @@ +import { Matrix } from '@pixi/math'; + +/** + * Calculates the mapped matrix + * @param filterArea {Rectangle} The filter area + * @param sprite {Sprite} the target sprite + * @param outputMatrix {Matrix} @alvin + * @private + */ +// this returns a matrix that will normalise map filter cords in the filter to screen space +export function calculateScreenSpaceMatrix(outputMatrix, filterArea, textureSize) +{ + // TODO unwrap? + const mappedMatrix = outputMatrix.identity(); + + mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); + + mappedMatrix.scale(textureSize.width, textureSize.height); + + return mappedMatrix; +} + +export function calculateNormalizedScreenSpaceMatrix(outputMatrix, filterArea, textureSize) +{ + const mappedMatrix = outputMatrix.identity(); + + mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); + + const translateScaleX = (textureSize.width / filterArea.width); + const translateScaleY = (textureSize.height / filterArea.height); + + mappedMatrix.scale(translateScaleX, translateScaleY); + + return mappedMatrix; +} + +// this will map the filter coord so that a texture can be used based on the transform of a sprite +export function calculateSpriteMatrix(outputMatrix, filterArea, textureSize, sprite) +{ + const orig = sprite._texture.orig; + const mappedMatrix = outputMatrix.set(textureSize.width, 0, 0, textureSize.height, filterArea.x, filterArea.y); + const worldTransform = sprite.worldTransform.copyTo(Matrix.TEMP_MATRIX); + + worldTransform.invert(); + mappedMatrix.prepend(worldTransform); + mappedMatrix.scale(1.0 / orig.width, 1.0 / orig.height); + mappedMatrix.translate(sprite.anchor.x, sprite.anchor.y); + + return mappedMatrix; +} diff --git a/packages/core/src/filters/spriteMask/SpriteMaskFilter.js b/packages/core/src/filters/spriteMask/SpriteMaskFilter.js new file mode 100644 index 0000000..91697b7 --- /dev/null +++ b/packages/core/src/filters/spriteMask/SpriteMaskFilter.js @@ -0,0 +1,63 @@ +import Filter from '../Filter'; +import { Matrix } from '@pixi/math'; +import vertex from './spriteMaskFilter.vert'; +import fragment from './spriteMaskFilter.frag'; +import { default as TextureMatrix } from '../../textures/TextureMatrix'; + +/** + * The SpriteMaskFilter class + * + * @class + * @extends PIXI.Filter + * @memberof PIXI + */ +export default class SpriteMaskFilter extends Filter +{ + /** + * @param {PIXI.Sprite} sprite - the target sprite + */ + constructor(sprite) + { + const maskMatrix = new Matrix(); + + super(vertex, fragment); + + sprite.renderable = false; + + this.maskSprite = sprite; + this.maskMatrix = maskMatrix; + } + + /** + * Applies the filter + * + * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + */ + apply(filterManager, input, output) + { + const maskSprite = this.maskSprite; + const tex = this.maskSprite.texture; + + if (!tex.valid) + { + return; + } + if (!tex.transform) + { + // margin = 0.0, let it bleed a bit, shader code becomes easier + // assuming that atlas textures were made with 1-pixel padding + tex.transform = new TextureMatrix(tex, 0.0); + } + tex.transform.update(); + + this.uniforms.mask = tex; + this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite) + .prepend(tex.transform.mapCoord); + this.uniforms.alpha = maskSprite.worldAlpha; + this.uniforms.maskClamp = tex.transform.uClampFrame; + + filterManager.applyFilter(this, input, output); + } +} diff --git a/packages/core/src/filters/spriteMask/spriteMaskFilter.frag b/packages/core/src/filters/spriteMask/spriteMaskFilter.frag new file mode 100644 index 0000000..0e4aef8 --- /dev/null +++ b/packages/core/src/filters/spriteMask/spriteMaskFilter.frag @@ -0,0 +1,23 @@ +varying vec2 vMaskCoord; +varying vec2 vTextureCoord; + +uniform sampler2D uSampler; +uniform sampler2D mask; +uniform float alpha; +uniform vec4 maskClamp; + +void main(void) +{ + float clip = step(3.5, + step(maskClamp.x, vMaskCoord.x) + + step(maskClamp.y, vMaskCoord.y) + + step(vMaskCoord.x, maskClamp.z) + + step(vMaskCoord.y, maskClamp.w)); + + vec4 original = texture2D(uSampler, vTextureCoord); + vec4 masky = texture2D(mask, vMaskCoord); + + original *= (masky.r * masky.a * alpha * clip); + + gl_FragColor = original; +} diff --git a/packages/core/src/filters/spriteMask/spriteMaskFilter.vert b/packages/core/src/filters/spriteMask/spriteMaskFilter.vert new file mode 100644 index 0000000..d23a30c --- /dev/null +++ b/packages/core/src/filters/spriteMask/spriteMaskFilter.vert @@ -0,0 +1,16 @@ +attribute vec2 aVertexPosition; +attribute vec2 aTextureCoord; + +uniform mat3 projectionMatrix; +uniform mat3 otherMatrix; + +varying vec2 vMaskCoord; +varying vec2 vTextureCoord; + +void main(void) +{ + gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); + + vTextureCoord = aTextureCoord; + vMaskCoord = ( otherMatrix * vec3( aTextureCoord, 1.0) ).xy; +} diff --git a/packages/core/src/framebuffer/FrameBuffer.js b/packages/core/src/framebuffer/FrameBuffer.js new file mode 100644 index 0000000..84e9fcc --- /dev/null +++ b/packages/core/src/framebuffer/FrameBuffer.js @@ -0,0 +1,106 @@ +import Texture from '../textures/BaseTexture'; +import { FORMATS, TYPES } from '@pixi/constants'; + +/** + * Frame buffer + * @class + * @memberof PIXI + */ +export default class FrameBuffer +{ + constructor(width, height) + { + this.width = width || 100; + this.height = height || 100; + + this.stencil = false; + this.depth = false; + + this.dirtyId = 0; + this.dirtyFormat = 0; + this.dirtySize = 0; + + this.depthTexture = null; + this.colorTextures = []; + + this.glFrameBuffers = {}; + } + + get colorTexture() + { + return this.colorTextures[0]; + } + + addColorTexture(index, texture) + { + // TODO add some validation to the texture - same width / height etc? + this.colorTextures[index || 0] = texture || new Texture(null, { scaleMode: 0, + resolution: 1, + mipmap: false, + width: this.width, + height: this.height });// || new Texture(); + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + addDepthTexture(texture) + { + /* eslint-disable max-len */ + this.depthTexture = texture || new Texture(null, { scaleMode: 0, + resolution: 1, + width: this.width, + height: this.height, + mipmap: false, + format: FORMATS.DEPTH_COMPONENT, + type: TYPES.UNSIGNED_SHORT });// UNSIGNED_SHORT; + /* eslint-disable max-len */ + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + enableDepth() + { + this.depth = true; + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + enableStencil() + { + this.stencil = true; + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + resize(width, height) + { + if (width === this.width && height === this.height) return; + + this.width = width; + this.height = height; + + this.dirtyId++; + this.dirtySize++; + + for (let i = 0; i < this.colorTextures.length; i++) + { + this.colorTextures[i].setSize(width, height); + } + + if (this.depthTexture) + { + this.depthTexture.setSize(width, height); + } + } +} diff --git a/packages/core/src/framebuffer/FramebufferSystem.js b/packages/core/src/framebuffer/FramebufferSystem.js new file mode 100644 index 0000000..2cc2437 --- /dev/null +++ b/packages/core/src/framebuffer/FramebufferSystem.js @@ -0,0 +1,246 @@ +import System from '../System'; +import { Rectangle } from '@pixi/math'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class FramebufferSystem extends System +{ + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + contextChange() + { + this.gl = this.renderer.gl; + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + this.current = null; + this.viewport = new Rectangle(); + + this.drawBufferExtension = this.renderer.context.extensions.drawBuffers; + } + + bind(framebuffer, frame) + { + const gl = this.gl; + + this.current = framebuffer; + + if (framebuffer) + { + // TODO cacheing layer! + + const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID] || this.initFramebuffer(framebuffer); + + gl.bindFramebuffer(gl.FRAMEBUFFER, fbo.framebuffer); + // makesure all textures are unbound.. + + // now check for updates... + if (fbo.dirtyId !== framebuffer.dirtyId) + { + fbo.dirtyId = framebuffer.dirtyId; + + if (fbo.dirtyFormat !== framebuffer.dirtyFormat) + { + fbo.dirtyFormat = framebuffer.dirtyFormat; + this.updateFramebuffer(framebuffer); + } + else if (fbo.dirtySize !== framebuffer.dirtySize) + { + fbo.dirtySize = framebuffer.dirtySize; + this.resizeFramebuffer(framebuffer); + } + } + + for (let i = 0; i < framebuffer.colorTextures.length; i++) + { + if (framebuffer.colorTextures[i].texturePart) + { + this.renderer.texture.unbind(framebuffer.colorTextures[i].texture); + } + else + { + this.renderer.texture.unbind(framebuffer.colorTextures[i]); + } + } + + if (framebuffer.depthTexture) + { + this.renderer.texture.unbind(framebuffer.depthTexture); + } + + if (frame) + { + this.setViewport(frame.x, frame.y, frame.width, frame.height); + } + else + { + this.setViewport(0, 0, framebuffer.width, framebuffer.height); + } + } + else + { + gl.bindFramebuffer(gl.FRAMEBUFFER, null); + + if (frame) + { + this.setViewport(frame.x, frame.y, frame.width, frame.height); + } + else + { + this.setViewport(0, 0, this.renderer.width, this.renderer.height); + } + } + } + + setViewport(x, y, width, height) + { + const v = this.viewport; + + if (v.width !== width || v.height !== height || v.x !== x || v.y !== y) + { + v.x = x; + v.y = y; + v.width = width; + v.height = height; + + this.gl.viewport(x, y, width, height); + } + } + + get size() + { + if (this.current) + { + // TODO store temp + return { x: 0, y: 0, width: this.current.width, height: this.current.height }; + } + + return { x: 0, y: 0, width: this.renderer.width, height: this.renderer.height }; + } + + clear(r, g, b, a) + { + const gl = this.gl; + + // TODO clear color can be set only one right? + gl.clearColor(r, g, b, a); + gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); + } + + // private functions... + + initFramebuffer(framebuffer) + { + const gl = this.gl; + + // TODO - make this a class? + const fbo = { + framebuffer: gl.createFramebuffer(), + stencil: null, + dirtyId: 0, + dirtyFormat: 0, + dirtySize: 0, + }; + + framebuffer.glFrameBuffers[this.CONTEXT_UID] = fbo; + + return fbo; + } + + resizeFramebuffer(framebuffer) + { + const gl = this.gl; + + if (framebuffer.stencil || framebuffer.depth) + { + gl.bindRenderbuffer(gl.RENDERBUFFER, this.stencil); + gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); + } + } + + updateFramebuffer(framebuffer) + { + const gl = this.gl; + + const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID]; + + // bind the color texture + const colorTextures = framebuffer.colorTextures; + + let count = colorTextures.length; + + if (!this.drawBufferExtension) + { + count = Math.min(count, 1); + } + + const activeTextures = []; + + for (let i = 0; i < count; i++) + { + const texture = framebuffer.colorTextures[i]; + + if (texture.texturePart) + { + this.renderer.texture.bind(texture.texture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.COLOR_ATTACHMENT0 + i, + gl.TEXTURE_CUBE_MAP_NEGATIVE_X + texture.side, + texture.texture._glTextures[this.CONTEXT_UID].texture, + 0); + } + else + { + this.renderer.texture.bind(texture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.COLOR_ATTACHMENT0 + i, + gl.TEXTURE_2D, + texture._glTextures[this.CONTEXT_UID].texture, + 0); + } + + activeTextures.push(gl.COLOR_ATTACHMENT0 + i); + } + + if (this.drawBufferExtension && activeTextures.length > 1) + { + this.drawBufferExtension.drawBuffersWEBGL(activeTextures); + } + + if (framebuffer.depthTexture) + { + const depthTextureExt = this.renderer.context.extensions.depthTexture; + + if (depthTextureExt) + { + const depthTexture = framebuffer.depthTexture; + + this.renderer.texture.bind(depthTexture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.DEPTH_ATTACHMENT, + gl.TEXTURE_2D, + depthTexture._glTextures[this.CONTEXT_UID].texture, + 0); + } + } + + if (framebuffer.stencil || framebuffer.depth) + { + fbo.stencil = gl.createRenderbuffer(); + + gl.bindRenderbuffer(gl.RENDERBUFFER, fbo.stencil); + + // TODO.. this is depth AND stencil? + gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, fbo.stencil); + gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); + // fbo.enableStencil(); + } + } +} diff --git a/packages/core/src/geometry/GLBuffer.js b/packages/core/src/geometry/GLBuffer.js new file mode 100644 index 0000000..0477e8a --- /dev/null +++ b/packages/core/src/geometry/GLBuffer.js @@ -0,0 +1,9 @@ +export default class GLBuffer +{ + constructor(buffer) + { + this.buffer = buffer; + this.updateID = -1; + this.byteLength = -1; + } +} diff --git a/packages/core/src/geometry/GeometrySystem.js b/packages/core/src/geometry/GeometrySystem.js new file mode 100644 index 0000000..959f827 --- /dev/null +++ b/packages/core/src/geometry/GeometrySystem.js @@ -0,0 +1,388 @@ +import System from '../System'; +import GLBuffer from './GLBuffer'; + +const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class GeometrySystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this._activeGeometry = null; + this._activeVao = null; + + this.hasVao = true; + this.hasInstance = true; + } + + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + contextChange() + { + const gl = this.gl = this.renderer.gl; + + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + + // webgl2 + if (!gl.createVertexArray) + { + // webgl 1! + let nativeVaoExtension = this.renderer.context.extensions.vertexArrayObject; + + if (this.renderer.legacy) + { + nativeVaoExtension = null; + } + + if (nativeVaoExtension) + { + gl.createVertexArray = () => + nativeVaoExtension.createVertexArrayOES(); + + gl.bindVertexArray = (vao) => + nativeVaoExtension.bindVertexArrayOES(vao); + + gl.deleteVertexArray = (vao) => + nativeVaoExtension.deleteVertexArrayOES(vao); + } + else + { + this.hasVao = false; + gl.createVertexArray = () => + { + // empty + }; + + gl.bindVertexArray = () => + { + // empty + }; + + gl.deleteVertexArray = () => + { + // empty + }; + } + } + + if (!gl.vertexAttribDivisor) + { + const instanceExt = gl.getExtension('ANGLE_instanced_arrays'); + + if (instanceExt) + { + gl.vertexAttribDivisor = (a, b) => + instanceExt.vertexAttribDivisorANGLE(a, b); + + gl.drawElementsInstanced = (a, b, c, d, e) => + instanceExt.drawElementsInstancedANGLE(a, b, c, d, e); + + gl.drawArraysInstanced = (a, b, c, d) => + instanceExt.drawArraysInstancedANGLE(a, b, c, d); + } + else + { + this.hasInstance = false; + } + } + } + + /** + * Binds geometry so that is can be drawn. Creating a Vao if required + * @private + * @param {PIXI.Geometry} geometry instance of geometry to bind + */ + bind(geometry, shader) + { + shader = shader || this.renderer.shader.shader; + + const gl = this.gl; + + // not sure the best way to address this.. + // currently different shaders require different VAOs for the same geometry + // Still mulling over the best way to solve this one.. + // will likely need to modify the shader attribute locations at run time! + let vaos = geometry.glVertexArrayObjects[this.CONTEXT_UID]; + + if (!vaos) + { + geometry.glVertexArrayObjects[this.CONTEXT_UID] = vaos = {}; + } + + const vao = vaos[shader.program.id] || this.initGeometryVao(geometry, shader.program); + + this._activeGeometry = geometry; + + if (this._activeVao !== vao) + { + this._activeVao = vao; + + if (this.hasVao) + { + gl.bindVertexArray(vao); + } + else + { + this.activateVao(geometry, shader.program); + } + } + + // TODO - optimise later! + // don't need to loop through if nothing changed! + // maybe look to add an 'autoupdate' to geometry? + this.updateBuffers(); + } + + reset() + { + this.unbind(); + } + + updateBuffers() + { + const geometry = this._activeGeometry; + const gl = this.gl; + + for (let i = 0; i < geometry.buffers.length; i++) + { + const buffer = geometry.buffers[i]; + + const glBuffer = buffer._glBuffers[this.CONTEXT_UID]; + + if (buffer._updateID !== glBuffer.updateID) + { + glBuffer.updateID = buffer._updateID; + + // TODO can cache this on buffer! maybe added a getter / setter? + const type = buffer.index ? gl.ELEMENT_ARRAY_BUFFER : gl.ARRAY_BUFFER; + const drawType = buffer.static ? gl.STATIC_DRAW : gl.DYNAMIC_DRAW; + + gl.bindBuffer(type, glBuffer.buffer); + + if (glBuffer.byteLength >= buffer.data.byteLength) + { + // offset is always zero for now! + gl.bufferSubData(type, 0, buffer.data); + } + else + { + gl.bufferData(type, buffer.data, drawType); + } + } + } + } + + checkCompatability(geometry, program) + { + // geometry must have at least all the attributes that the shader requires. + const geometryAttributes = geometry.attributes; + const shaderAttributes = program.attributeData; + + for (const j in shaderAttributes) + { + if (!geometryAttributes[j]) + { + throw new Error(`shader and geometry incompatible, geometry missing the "${j}" attribute`); + } + } + } + + /** + * Creates a Vao with the same structure as the geometry and stores it on the geometry. + * @private + * @param {PIXI.Geometry} geometry instance of geometry to to generate Vao for + */ + initGeometryVao(geometry, program) + { + this.checkCompatability(geometry, program); + + const gl = this.gl; + const CONTEXT_UID = this.CONTEXT_UID; + const buffers = geometry.buffers; + const attributes = geometry.attributes; + + const tempStride = {}; + const tempStart = {}; + + for (const j in buffers) + { + tempStride[j] = 0; + tempStart[j] = 0; + } + + for (const j in attributes) + { + if (!attributes[j].size && program.attributeData[j]) + { + attributes[j].size = program.attributeData[j].size; + } + + tempStride[attributes[j].buffer] += attributes[j].size * byteSizeMap[attributes[j].type]; + } + + for (const j in attributes) + { + const attribute = attributes[j]; + const attribSize = attribute.size; + + if (attribute.stride === undefined) + { + if (tempStride[attribute.buffer] === attribSize * byteSizeMap[attribute.type]) + { + attribute.stride = 0; + } + else + { + attribute.stride = tempStride[attribute.buffer]; + } + } + + if (attribute.start === undefined) + { + attribute.start = tempStart[attribute.buffer]; + + tempStart[attribute.buffer] += attribSize * byteSizeMap[attribute.type]; + } + } + + // first update - and create the buffers! + // only create a gl buffer if it actually gets + for (let i = 0; i < buffers.length; i++) + { + const buffer = buffers[i]; + + if (!buffer._glBuffers[CONTEXT_UID]) + { + buffer._glBuffers[CONTEXT_UID] = new GLBuffer(gl.createBuffer()); + } + } + + // TODO - maybe make this a data object? + // lets wait to see if we need to first! + const vao = gl.createVertexArray(); + + gl.bindVertexArray(vao); + + this.activateVao(geometry, program); + + gl.bindVertexArray(null); + + // add it to the cache! + geometry.glVertexArrayObjects[this.CONTEXT_UID][program.id] = vao; + + return vao; + } + + activateVao(geometry, program) + { + const gl = this.gl; + const CONTEXT_UID = this.CONTEXT_UID; + const buffers = geometry.buffers; + const attributes = geometry.attributes; + + if (geometry.indexBuffer) + { + // first update the index buffer if we have one.. + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, geometry.indexBuffer._glBuffers[CONTEXT_UID].buffer); + } + + let lastBuffer = null; + + // add a new one! + for (const j in attributes) + { + const attribute = attributes[j]; + const buffer = buffers[attribute.buffer]; + const glBuffer = buffer._glBuffers[CONTEXT_UID]; + + if (program.attributeData[j]) + { + if (lastBuffer !== glBuffer) + { + gl.bindBuffer(gl.ARRAY_BUFFER, glBuffer.buffer); + + lastBuffer = glBuffer; + } + + const location = program.attributeData[j].location; + + // TODO introduce state again + // we can optimise this for older devices that have no VAOs + gl.enableVertexAttribArray(location); + + gl.vertexAttribPointer(location, + attribute.size, + attribute.type || gl.FLOAT, + attribute.normalized, + attribute.stride, + attribute.start); + + if (attribute.instance) + { + // TODO calculate instance count based of this... + if (this.hasInstance) + { + gl.vertexAttribDivisor(location, 1); + } + else + { + throw new Error('geometry error, GPU Instancing is not supported on this device'); + } + } + } + } + } + + draw(type, size, start, instanceCount) + { + const gl = this.gl; + const geometry = this._activeGeometry; + + // TODO.. this should not change so maybe cache the function? + + if (geometry.indexBuffer) + { + if (geometry.instanced) + { + /* eslint-disable max-len */ + gl.drawElementsInstanced(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2, instanceCount || 1); + /* eslint-enable max-len */ + } + else + { + gl.drawElements(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2); + } + } + else + if (geometry.instanced) + { + // TODO need a better way to calculate size.. + gl.drawArraysInstanced(type, start, size || geometry.getSize(), instanceCount || 1); + } + else + { + gl.drawArrays(type, start, size || geometry.getSize()); + } + + return this; + } + + unbind() + { + this.gl.bindVertexArray(null); + this._activeVao = null; + this._activeGeometry = null; + } +} diff --git a/packages/core/src/geometry/utils/setVertexAttribArrays.js b/packages/core/src/geometry/utils/setVertexAttribArrays.js new file mode 100644 index 0000000..23b6959 --- /dev/null +++ b/packages/core/src/geometry/utils/setVertexAttribArrays.js @@ -0,0 +1,54 @@ +// var GL_MAP = {}; + +/** + * @param gl {WebGLRenderingContext} The current WebGL context + * @param attribs {*} + * @param state {*} + */ +export default function setVertexAttribArrays(gl, attribs, state) +{ + let i; + + if (state) + { + const tempAttribState = state.tempAttribState; + const attribState = state.attribState; + + for (i = 0; i < tempAttribState.length; i++) + { + tempAttribState[i] = false; + } + + // set the new attribs + for (i = 0; i < attribs.length; i++) + { + tempAttribState[attribs[i].attribute.location] = true; + } + + for (i = 0; i < attribState.length; i++) + { + if (attribState[i] !== tempAttribState[i]) + { + attribState[i] = tempAttribState[i]; + + if (state.attribState[i]) + { + gl.enableVertexAttribArray(i); + } + else + { + gl.disableVertexAttribArray(i); + } + } + } + } + else + { + for (i = 0; i < attribs.length; i++) + { + const attrib = attribs[i]; + + gl.enableVertexAttribArray(attrib.attribute.location); + } + } +} diff --git a/packages/core/src/index.js b/packages/core/src/index.js index 58c456a..fd1f547 100644 --- a/packages/core/src/index.js +++ b/packages/core/src/index.js @@ -1,30 +1,30 @@ import * as resources from './textures/resources'; -import * as systems from './renderers/systems'; +import * as systems from './systems'; -export { resources }; export { systems }; +export { resources }; -export { default as Renderer } from './renderers/Renderer'; -export { default as AbstractRenderer } from './renderers/AbstractRenderer'; -export { default as FrameBuffer } from './textures/FrameBuffer'; +export { default as System } from './System'; +export { default as Renderer } from './Renderer'; +export { default as AbstractRenderer } from './AbstractRenderer'; +export { default as FrameBuffer } from './framebuffer/FrameBuffer'; export { default as CubeTexture } from './textures/CubeTexture'; export { default as BaseTexture } from './textures/BaseTexture'; export { default as Texture } from './textures/Texture'; export { default as TextureMatrix } from './textures/TextureMatrix'; -export { default as RenderTexture } from './textures/RenderTexture'; -export { default as BaseRenderTexture } from './textures/BaseRenderTexture'; +export { default as RenderTexture } from './renderTexture/RenderTexture'; +export { default as BaseRenderTexture } from './renderTexture/BaseRenderTexture'; export { default as TextureUvs } from './textures/TextureUvs'; -export { default as State } from './renderers/State'; -export { default as ObjectRenderer } from './renderers/utils/ObjectRenderer'; -export { default as Quad } from './renderers/utils/Quad'; -export { default as QuadUv } from './renderers/utils/QuadUv'; -export { default as checkMaxIfStatmentsInShader } from './renderers/utils/checkMaxIfStatmentsInShader'; +export { default as State } from './state/State'; +export { default as ObjectRenderer } from './batch/ObjectRenderer'; +export { default as Quad } from './utils/Quad'; +export { default as QuadUv } from './utils/QuadUv'; +export { default as checkMaxIfStatmentsInShader } from './shader/utils/checkMaxIfStatmentsInShader'; export { default as Shader } from './shader/Shader'; export { default as Program } from './shader/Program'; export { default as UniformGroup } from './shader/UniformGroup'; -export { default as SpriteMaskFilter } from './renderers/filters/spriteMask/SpriteMaskFilter'; -export { default as Filter } from './renderers/filters/Filter'; +export { default as SpriteMaskFilter } from './filters/spriteMask/SpriteMaskFilter'; +export { default as Filter } from './filters/Filter'; export { default as Attribute } from './geometry/Attribute'; export { default as Buffer } from './geometry/Buffer'; export { default as Geometry } from './geometry/Geometry'; - diff --git a/packages/core/src/mask/MaskSystem.js b/packages/core/src/mask/MaskSystem.js new file mode 100644 index 0000000..99174c2 --- /dev/null +++ b/packages/core/src/mask/MaskSystem.js @@ -0,0 +1,199 @@ +import System from '../System'; +import AlphaMaskFilter from '../filters/spriteMask/SpriteMaskFilter'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class MaskSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + // TODO - we don't need both! + this.scissor = false; + this.scissorData = null; + this.scissorRenderTarget = null; + + this.enableScissor = false; + + this.alphaMaskPool = []; + this.alphaMaskIndex = 0; + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.DisplayObject} target - Display Object to push the mask to + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + push(target, maskData) + { + // TODO the root check means scissor rect will not + // be used on render textures more info here: + // https://github.com/pixijs/pixi.js/pull/3545 + + if (maskData.texture) + { + this.pushSpriteMask(target, maskData); + } + else if (this.enableScissor + && !this.scissor + && this.renderer._activeRenderTarget.root + && !this.renderer.stencil.stencilMaskStack.length + && maskData.isFastRect()) + { + const matrix = maskData.worldTransform; + + let rot = Math.atan2(matrix.b, matrix.a); + + // use the nearest degree! + rot = Math.round(rot * (180 / Math.PI)); + + if (rot % 90) + { + this.pushStencilMask(maskData); + } + else + { + this.pushScissorMask(target, maskData); + } + } + else + { + this.pushStencilMask(maskData); + } + } + + /** + * Removes the last mask from the mask stack and doesn't return it. + * + * @param {PIXI.DisplayObject} target - Display Object to pop the mask from + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + pop(target, maskData) + { + if (maskData.texture) + { + this.popSpriteMask(target, maskData); + } + else if (this.enableScissor && !this.renderer.stencil.stencilMaskStack.length) + { + this.popScissorMask(target, maskData); + } + else + { + this.popStencilMask(target, maskData); + } + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.RenderTarget} target - Display Object to push the sprite mask to + * @param {PIXI.Sprite} maskData - Sprite to be used as the mask + */ + pushSpriteMask(target, maskData) + { + let alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; + + if (!alphaMaskFilter) + { + alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; + } + + alphaMaskFilter[0].resolution = this.renderer.resolution; + alphaMaskFilter[0].maskSprite = maskData; + + // TODO - may cause issues! + target.filterArea = maskData.getBounds(true); + + this.renderer.filterSystem.pushFilter(target, alphaMaskFilter); + + this.alphaMaskIndex++; + } + + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popSpriteMask() + { + this.renderer.filterSystem.popFilter(); + this.alphaMaskIndex--; + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + pushStencilMask(maskData) + { + this.renderer.batch.flush(); + this.renderer.stencil.pushStencil(maskData); + } + + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popStencilMask() + { + // this.renderer.currentRenderer.stop(); + this.renderer.stencil.popStencil(); + } + + /** + * + * @param {PIXI.DisplayObject} target - Display Object to push the mask to + * @param {PIXI.Graphics} maskData - The masking data. + */ + pushScissorMask(target, maskData) + { + maskData.renderable = true; + + const renderTarget = this.renderer._activeRenderTarget; + + const bounds = maskData.getBounds(); + + bounds.fit(renderTarget.size); + maskData.renderable = false; + + this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); + + const resolution = this.renderer.resolution; + + this.renderer.gl.scissor( + bounds.x * resolution, + (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, + bounds.width * resolution, + bounds.height * resolution + ); + + this.scissorRenderTarget = renderTarget; + this.scissorData = maskData; + this.scissor = true; + } + + /** + * + * + */ + popScissorMask() + { + this.scissorRenderTarget = null; + this.scissorData = null; + this.scissor = false; + + // must be scissor! + const gl = this.renderer.gl; + + gl.disable(gl.SCISSOR_TEST); + } +} diff --git a/packages/core/src/mask/StencilSystem.js b/packages/core/src/mask/StencilSystem.js new file mode 100644 index 0000000..62796a5 --- /dev/null +++ b/packages/core/src/mask/StencilSystem.js @@ -0,0 +1,133 @@ +import System from '../System'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class StencilSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + this.stencilMaskStack = []; + } + + /** + * Changes the mask stack that is used by this System. + * + * @param {PIXI.Graphics[]} stencilMaskStack - The mask stack + */ + setMaskStack(stencilMaskStack) + { + const gl = this.renderer.gl; + + if (stencilMaskStack.length !== this.stencilMaskStack.length) + { + if (stencilMaskStack.length === 0) + { + gl.disable(gl.STENCIL_TEST); + } + else + { + gl.enable(gl.STENCIL_TEST); + } + } + + this.stencilMaskStack = stencilMaskStack; + } + + /** + * Applies the Mask and adds it to the current stencil stack. @alvin + * + * @param {PIXI.Graphics} graphics - The mask + */ + pushStencil(graphics) + { + this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); + + // this.renderer._activeRenderTarget.attachStencilBuffer(); + + const gl = this.renderer.gl; + const prevMaskCount = this.stencilMaskStack.length; + + if (prevMaskCount === 0) + { + gl.enable(gl.STENCIL_TEST); + } + + this.stencilMaskStack.push(graphics); + + // Increment the refference stencil value where the new mask overlaps with the old ones. + gl.colorMask(false, false, false, false); + gl.stencilFunc(gl.EQUAL, prevMaskCount, this._getBitwiseMask()); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.INCR); + this.renderer.plugins.graphics.render(graphics); + + this._useCurrent(); + } + + /** + * Removes the last mask from the stencil stack. @alvin + */ + popStencil() + { + this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); + + const gl = this.renderer.gl; + const graphics = this.stencilMaskStack.pop(); + + if (this.stencilMaskStack.length === 0) + { + // the stack is empty! + gl.disable(gl.STENCIL_TEST); + gl.clear(gl.STENCIL_BUFFER_BIT); + gl.clearStencil(0); + } + else + { + // Decrement the refference stencil value where the popped mask overlaps with the other ones + gl.colorMask(false, false, false, false); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.DECR); + this.renderer.plugins.graphics.render(graphics); + + this._useCurrent(); + } + } + + /** + * Setup renderer to use the current stencil data. + */ + _useCurrent() + { + const gl = this.renderer.gl; + + gl.colorMask(true, true, true, true); + gl.stencilFunc(gl.EQUAL, this.stencilMaskStack.length, this._getBitwiseMask()); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP); + } + + /** + * Fill 1s equal to the number of acitve stencil masks. + * + * @return {number} The bitwise mask. + */ + _getBitwiseMask() + { + return (1 << this.stencilMaskStack.length) - 1; + } + + /** + * Destroys the mask stack. + * + */ + destroy() + { + super.destroy(this); + + this.stencilMaskStack = null; + } +} diff --git a/packages/core/src/projection/ProjectionSystem.js b/packages/core/src/projection/ProjectionSystem.js new file mode 100644 index 0000000..6e63b73 --- /dev/null +++ b/packages/core/src/projection/ProjectionSystem.js @@ -0,0 +1,73 @@ +import System from '../System'; +import { Matrix } from '@pixi/math'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class ProjectionSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.projectionMatrix = new Matrix(); + } + + update(destinationFrame, sourceFrame, resolution, root) + { + this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; + this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; + + this.calculateProjection(this.destinationFrame, this.sourceFrame, resolution, root); + + this.renderer.globalUniforms.uniforms.projectionMatrix = this.projectionMatrix; + this.renderer.globalUniforms.update(); + } + + /** + * Updates the projection matrix based on a projection frame (which is a rectangle) + * + * @param {Rectangle} destinationFrame - The destination frame. + * @param {Rectangle} sourceFrame - The source frame. + */ + calculateProjection(destinationFrame, sourceFrame, resolution, root) + { + const pm = this.projectionMatrix; + + // I don't think we will need this line.. + // pm.identity(); + + if (!root) + { + pm.a = (1 / destinationFrame.width * 2) * resolution; + pm.d = (1 / destinationFrame.height * 2) * resolution; + + pm.tx = -1 - (sourceFrame.x * pm.a); + pm.ty = -1 - (sourceFrame.y * pm.d); + } + else + { + pm.a = (1 / destinationFrame.width * 2) * resolution; + pm.d = (-1 / destinationFrame.height * 2) * resolution; + + pm.tx = -1 - (sourceFrame.x * pm.a); + pm.ty = 1 - (sourceFrame.y * pm.d); + } + } + + /** + * Sets the transform of the active render target to the given matrix + * + * @param {PIXI.Matrix} matrix - The transformation matrix + */ + setTransform()// matrix) + { + // this._activeRenderTarget.transform = matrix; + } +} diff --git a/packages/core/src/renderTexture/BaseRenderTexture.js b/packages/core/src/renderTexture/BaseRenderTexture.js new file mode 100644 index 0000000..88eb83b --- /dev/null +++ b/packages/core/src/renderTexture/BaseRenderTexture.js @@ -0,0 +1,137 @@ +import BaseTexture from '../textures/BaseTexture'; +import FrameBuffer from '../framebuffer/FrameBuffer'; + +/** + * A BaseRenderTexture is a special texture that allows any PixiJS display object to be rendered to it. + * + * __Hint__: All DisplayObjects (i.e. Sprites) that render to a BaseRenderTexture should be preloaded + * otherwise black rectangles will be drawn instead. + * + * A BaseRenderTexture takes a snapshot of any Display Object given to its render method. The position + * and rotation of the given Display Objects is ignored. For example: + * + * ```js + * let renderer = PIXI.autoDetectRenderer(1024, 1024, { view: canvas, ratio: 1 }); + * let baseRenderTexture = new PIXI.BaseRenderTexture(renderer, 800, 600); + * let sprite = PIXI.Sprite.fromImage("spinObj_01.png"); + * + * sprite.position.x = 800/2; + * sprite.position.y = 600/2; + * sprite.anchor.x = 0.5; + * sprite.anchor.y = 0.5; + * + * baseRenderTexture.render(sprite); + * ``` + * + * The Sprite in this case will be rendered using its local transform. To render this sprite at 0,0 + * you can clear the transform + * + * ```js + * + * sprite.setTransform() + * + * let baseRenderTexture = new PIXI.BaseRenderTexture(100, 100); + * let renderTexture = new PIXI.RenderTexture(baseRenderTexture); + * + * renderer.render(sprite, renderTexture); // Renders to center of RenderTexture + * ``` + * + * @class + * @extends PIXI.BaseTexture + * @memberof PIXI + */ +export default class BaseRenderTexture extends BaseTexture +{ + /** + * @param {object} [options] + * @param {number} [options.width=100] - The width of the base render texture + * @param {number} [options.height=100] - The height of the base render texture + * @param {PIXI.SCALE_MODES} [options.scaleMode] - See {@link PIXI.SCALE_MODES} for possible values + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the texture being generated + */ + constructor(options) + { + if (typeof options === 'number') + { + /* eslint-disable prefer-rest-params */ + // Backward compatibility of signature + const width = arguments[0]; + const height = arguments[1]; + const scaleMode = arguments[2]; + const resolution = arguments[3]; + + options = { width, height, scaleMode, resolution }; + /* eslint-enable prefer-rest-params */ + } + + super(null, options); + + const { width, height } = options || {}; + + // Set defaults + this.mipmap = false; + this.width = Math.ceil(width) || 100; + this.height = Math.ceil(height) || 100; + this.valid = true; + + /** + * A map of renderer IDs to webgl renderTargets + * + * @private + * @member {object} + */ + // this._glRenderTargets = {}; + + /** + * A reference to the canvas render target (we only need one as this can be shared across renderers) + * + * @private + * @member {object} + */ + this._canvasRenderTarget = null; + + this.clearColor = [0, 0, 0, 0]; + + this.frameBuffer = new FrameBuffer(width * this.resolution, height * this.resolution) + .addColorTexture(0, this); + + // TODO - could this be added the systems? + + /** + * The data structure for the stencil masks + * + * @member {PIXI.Graphics[]} + */ + this.stencilMaskStack = []; + + /** + * The data structure for the filters + * + * @member {PIXI.Graphics[]} + */ + this.filterStack = [{}]; + } + + /** + * Resizes the BaseRenderTexture. + * + * @param {number} width - The width to resize to. + * @param {number} height - The height to resize to. + */ + resize(width, height) + { + width = Math.ceil(width); + height = Math.ceil(height); + this.frameBuffer.resize(width * this.resolution, height * this.resolution); + } + + /** + * Destroys this texture + * + */ + destroy() + { + super.destroy(true); + this.renderer = null; + } +} diff --git a/packages/core/src/renderTexture/RenderTexture.js b/packages/core/src/renderTexture/RenderTexture.js new file mode 100644 index 0000000..f1ecf1f --- /dev/null +++ b/packages/core/src/renderTexture/RenderTexture.js @@ -0,0 +1,153 @@ +import BaseRenderTexture from './BaseRenderTexture'; +import Texture from '../textures/Texture'; + +/** + * A RenderTexture is a special texture that allows any PixiJS display object to be rendered to it. + * + * __Hint__: All DisplayObjects (i.e. Sprites) that render to a RenderTexture should be preloaded + * otherwise black rectangles will be drawn instead. + * + * A RenderTexture takes a snapshot of any Display Object given to its render method. For example: + * + * ```js + * let renderer = PIXI.autoDetectRenderer(1024, 1024, { view: canvas, ratio: 1 }); + * let renderTexture = PIXI.RenderTexture.create(800, 600); + * let sprite = PIXI.Sprite.fromImage("spinObj_01.png"); + * + * sprite.position.x = 800/2; + * sprite.position.y = 600/2; + * sprite.anchor.x = 0.5; + * sprite.anchor.y = 0.5; + * + * renderer.render(sprite, renderTexture); + * ``` + * + * The Sprite in this case will be rendered using its local transform. To render this sprite at 0,0 + * you can clear the transform + * + * ```js + * + * sprite.setTransform() + * + * let renderTexture = new PIXI.RenderTexture.create(100, 100); + * + * renderer.render(sprite, renderTexture); // Renders to center of RenderTexture + * ``` + * + * @class + * @extends PIXI.Texture + * @memberof PIXI + */ +export default class RenderTexture extends Texture +{ + /** + * @param {PIXI.BaseRenderTexture} baseRenderTexture - The renderer used for this RenderTexture + * @param {PIXI.Rectangle} [frame] - The rectangle frame of the texture to show + */ + constructor(baseRenderTexture, frame) + { + // support for legacy.. + let _legacyRenderer = null; + + if (!(baseRenderTexture instanceof BaseRenderTexture)) + { + /* eslint-disable prefer-rest-params, no-console */ + const width = arguments[1]; + const height = arguments[2]; + const scaleMode = arguments[3]; + const resolution = arguments[4]; + + // we have an old render texture.. + console.warn(`Please use RenderTexture.create(${width}, ${height}) instead of the ctor directly.`); + _legacyRenderer = arguments[0]; + /* eslint-enable prefer-rest-params, no-console */ + + frame = null; + baseRenderTexture = new BaseRenderTexture({ + width, + height, + scaleMode, + resolution, + }); + } + + /** + * The base texture object that this texture uses + * + * @member {BaseTexture} + */ + super(baseRenderTexture, frame); + + this.legacyRenderer = _legacyRenderer; + + /** + * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. + * + * @member {boolean} + */ + this.valid = true; + + /** + * FilterSystem temporary storage + * @private + * @member {PIXI.Rectangle} + */ + this.filterFrame = null; + + this._updateUvs(); + } + + /** + * Resizes the RenderTexture. + * + * @param {number} width - The width to resize to. + * @param {number} height - The height to resize to. + * @param {boolean} [resizeBaseTexture=true] - Should the baseTexture.width and height values be resized as well? + */ + resize(width, height, resizeBaseTexture = true) + { + width = Math.ceil(width); + height = Math.ceil(height); + + // TODO - could be not required.. + this.valid = (width > 0 && height > 0); + + this._frame.width = this.orig.width = width; + this._frame.height = this.orig.height = height; + + if (resizeBaseTexture) + { + this.baseTexture.resize(width, height); + } + + this._updateUvs(); + } + + /** + * A short hand way of creating a render texture. + * + * @param {object} [options] - Options + * @param {number} [options.width=100] - The width of the render texture + * @param {number} [options.height=100] - The height of the render texture + * @param {number} [options.scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the texture being generated + * @return {PIXI.RenderTexture} The new render texture + */ + static create(options) + { + // fallback, old-style: create(width, height, scaleMode, resolution) + if (typeof options === 'number') + { + /* eslint-disable prefer-rest-params */ + options = { + width: options, + height: arguments[1], + scaleMode: arguments[2], + resolution: arguments[3], + }; + /* eslint-enable prefer-rest-params */ + } + + return new RenderTexture(new BaseRenderTexture(options)); + } +} diff --git a/packages/core/src/renderTexture/RenderTextureSystem.js b/packages/core/src/renderTexture/RenderTextureSystem.js new file mode 100644 index 0000000..8d0ca61 --- /dev/null +++ b/packages/core/src/renderTexture/RenderTextureSystem.js @@ -0,0 +1,115 @@ +import System from '../System'; +import { Rectangle } from '@pixi/math'; + +const tempRect = new Rectangle(); + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class RenderTextureSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.clearColor = renderer._backgroundColorRgba; + + // TODO moe this property somewhere else! + this.defaultMaskStack = []; + this.defaultFilterStack = [{}]; + + // empty render texture? + this.renderTexture = null; + + this.destinationFrame = new Rectangle(); + } + + bind(renderTexture, sourceFrame, destinationFrame) + { + // TODO - do we want this?? + if (this.renderTexture === renderTexture) return; + this.renderTexture = renderTexture; + + const renderer = this.renderer; + + if (renderTexture) + { + const baseTexture = renderTexture.baseTexture; + + if (!destinationFrame) + { + tempRect.width = baseTexture.realWidth; + tempRect.height = baseTexture.realHeight; + + destinationFrame = tempRect; + } + + if (!sourceFrame) + { + sourceFrame = destinationFrame; + } + + this.renderer.framebuffer.bind(baseTexture.frameBuffer, destinationFrame); + + this.renderer.projection.update(destinationFrame, sourceFrame, baseTexture.resolution, false); + this.renderer.stencil.setMaskStack(baseTexture.stencilMaskStack); + } + else + { + // TODO these validation checks happen deeper down.. + // thing they can be avoided.. + if (!destinationFrame) + { + tempRect.width = renderer.width; + tempRect.height = renderer.height; + + destinationFrame = tempRect; + } + + if (!sourceFrame) + { + sourceFrame = destinationFrame; + } + + renderer.framebuffer.bind(null, destinationFrame); + + // TODO store this.. + this.renderer.projection.update(destinationFrame, sourceFrame, this.renderer.resolution, true); + this.renderer.stencil.setMaskStack(this.defaultMaskStack); + } + + this.destinationFrame.copyFrom(destinationFrame); + } + + /** + * Erases the render texture and fills the drawing area with a colour + * + * @param {number} [clearColor] - The colour + * @return {PIXI.Renderer} Returns itself. + */ + clear(clearColor) + { + if (this.renderTexture) + { + clearColor = clearColor || this.renderTexture.baseTexture.clearColor; + } + else + { + clearColor = clearColor || this.clearColor; + } + + this.renderer.framebuffer.clear(clearColor[0], clearColor[1], clearColor[2], clearColor[3]); + } + + resize()// screenWidth, screenHeight) + { + // resize the root only! + this.bind(null); + } +} diff --git a/packages/core/src/renderers/AbstractRenderer.js b/packages/core/src/renderers/AbstractRenderer.js deleted file mode 100644 index 928670f..0000000 --- a/packages/core/src/renderers/AbstractRenderer.js +++ /dev/null @@ -1,353 +0,0 @@ -import { hex2string, hex2rgb } from '@pixi/utils'; -import { Matrix, Rectangle } from '@pixi/math'; -import { RENDERER_TYPE } from '@pixi/constants'; -import { settings } from '@pixi/settings'; -import { Container } from '@pixi/display'; -import RenderTexture from '../textures/RenderTexture'; -import EventEmitter from 'eventemitter3'; - -const tempMatrix = new Matrix(); - -/** - * 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 EventEmitter - * @memberof PIXI - */ -export default class AbstractRenderer extends EventEmitter -{ - // eslint-disable-next-line valid-jsdoc - /** - * @param {string} system - The name of the system this renderer is for. - * @param {object} [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.transparent=false] - If the render view is transparent, default false - * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false - * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) - * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. The - * resolution of the renderer retina would be 2. - * @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 {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when rendering, - * stopping pixel interpolation. - */ - constructor(system, options, arg2, arg3) - { - super(); - - // Support for constructor(system, screenWidth, screenHeight, options) - if (typeof options === 'number') - { - options = Object.assign({ - width: options, - height: arg2 || settings.RENDER_OPTIONS.height, - }, arg3); - } - - // 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 = RENDERER_TYPE.UNKNOWN; - - /** - * Measurements of the screen. (0, 0, screenWidth, screenHeight) - * - * Its safe to use as filterArea or hitArea for 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 1 - */ - this.resolution = options.resolution || settings.RESOLUTION; - - /** - * Whether the render view is transparent - * - * @member {boolean} - */ - this.transparent = options.transparent; - - /** - * Whether css dimensions of canvas view should be resized to screen dimensions automatically - * - * @member {boolean} - */ - this.autoResize = options.autoResize || false; - - /** - * Tracks the blend modes useful for this renderer. - * - * @member {object} - */ - this.blendModes = null; - - /** - * 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; - - /** - * If true PixiJS will Math.floor() x/y values when rendering, stopping pixel interpolation. - * Handy for crisp pixel art and speed on legacy devices. - * - * @member {boolean} - */ - this.roundPixels = options.roundPixels; - - /** - * The background color as a number. - * - * @member {number} - * @private - */ - this._backgroundColor = 0x000000; - - /** - * The background color as an [R, G, B] array. - * - * @member {number[]} - * @private - */ - this._backgroundColorRgba = [0, 0, 0, 0]; - - /** - * The background color as a string. - * - * @member {string} - * @private - */ - this._backgroundColorString = '#000000'; - - this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter - - /** - * This temporary display object used as the parent of the currently being rendered item - * - * @member {PIXI.DisplayObject} - * @private - */ - this._tempDisplayObjectParent = new Container(); - - /** - * The last root object that the renderer tried to render. - * - * @member {PIXI.DisplayObject} - * @private - */ - this._lastObjectRendered = this._tempDisplayObjectParent; - - /** - * Collection of plugins. - * @readonly - * @member {object} - */ - this.plugins = {}; - } - - /** - * Initialize the plugins. - * - * @protected - * @param {object} staticMap - The dictionary of staticly saved plugins. - */ - initPlugins(staticMap) - { - 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() - { - return this.view.width; - } - - /** - * Same as view.height, actual number of pixels in the canvas by vertical - * - * @member {number} - * @readonly - * @default 600 - */ - get height() - { - return this.view.height; - } - - /** - * Resizes the screen and canvas to the specified width and height - * Canvas dimensions are multiplied by resolution - * - * @param {number} screenWidth - the new width of the screen - * @param {number} screenHeight - the new height of the screen - */ - resize(screenWidth, screenHeight) - { - this.screen.width = screenWidth; - this.screen.height = screenHeight; - - this.view.width = screenWidth * this.resolution; - this.view.height = screenHeight * this.resolution; - - if (this.autoResize) - { - this.view.style.width = `${screenWidth}px`; - this.view.style.height = `${screenHeight}px`; - } - } - - /** - * 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 {PIXI.DisplayObject} displayObject - The displayObject the object will be generated from - * @param {number} scaleMode - Should be one of the scaleMode consts - * @param {number} resolution - The resolution / device pixel ratio of the texture being generated - * @param {PIXI.Rectangle} [region] - The region of the displayObject, that shall be rendered, - * if no region is specified, defaults to the local bounds of the displayObject. - * @return {PIXI.Texture} a texture of the graphics object - */ - generateTexture(displayObject, scaleMode, resolution, region) - { - region = region || displayObject.getLocalBounds(); - - // 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(region.width | 0, region.height | 0, scaleMode, resolution); - - tempMatrix.tx = -region.x; - tempMatrix.ty = -region.y; - - this.render(displayObject, renderTexture, false, tempMatrix, true); - - return renderTexture; - } - - /** - * Removes everything from the renderer and optionally removes the Canvas DOM element. - * - * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. - */ - destroy(removeView) - { - 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); - } - - this.plugins = null; - - this.type = RENDERER_TYPE.UNKNOWN; - - this.view = null; - - this.screen = null; - - this.resolution = 0; - - this.transparent = false; - - this.autoResize = false; - - this.blendModes = null; - - this.options = null; - - this.preserveDrawingBuffer = false; - this.clearBeforeRender = false; - - this.roundPixels = false; - - this._backgroundColor = 0; - this._backgroundColorRgba = null; - this._backgroundColorString = null; - - this._tempDisplayObjectParent = null; - this._lastObjectRendered = null; - } - - /** - * The background color to fill if not transparent - * - * @member {number} - */ - get backgroundColor() - { - return this._backgroundColor; - } - - set backgroundColor(value) // eslint-disable-line require-jsdoc - { - this._backgroundColor = value; - this._backgroundColorString = hex2string(value); - hex2rgb(value, this._backgroundColorRgba); - } -} diff --git a/packages/core/src/renderers/Renderer.js b/packages/core/src/renderers/Renderer.js deleted file mode 100644 index a2020b1..0000000 --- a/packages/core/src/renderers/Renderer.js +++ /dev/null @@ -1,329 +0,0 @@ -import AbstractRenderer from './AbstractRenderer'; -import { sayHello } from '@pixi/utils'; -import MaskSystem from './systems/MaskSystem'; -import StencilSystem from './systems/StencilSystem'; -import FilterSystem from './systems/filter/FilterSystem'; -import FramebufferSystem from './systems/FramebufferSystem'; -import RenderTextureSystem from './systems/RenderTextureSystem'; -import TextureSystem from './systems/textures/TextureSystem'; -import ProjectionSystem from './systems/ProjectionSystem'; -import StateSystem from './systems/StateSystem'; -import GeometrySystem from './systems/geometry/GeometrySystem'; -import ShaderSystem from './systems/shader/ShaderSystem'; -import ContextSystem from './systems/ContextSystem'; -import BatchSystem from './systems/BatchSystem'; -import TextureGCSystem from './systems/textures/TextureGCSystem'; -import { RENDERER_TYPE } from '@pixi/constants'; -import UniformGroup from '../shader/UniformGroup'; -import { Matrix } from '@pixi/math'; -import Runner from 'mini-runner'; - -/** - * 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 Render works by automatically managing webGLBatchs. - * 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 :) - * - * @class - * @memberof PIXI - * @extends PIXI.AbstractRenderer - */ -export default class Renderer extends AbstractRenderer -{ - // eslint-disable-next-line valid-jsdoc - /** - * - * @param {object} [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.transparent=false] - If the render view is transparent, default false - * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false - * @param {boolean} [options.antialias=false] - sets antialias. If not available natively then FXAA - * antialiasing is used - * @param {boolean} [options.forceFXAA=false] - forces FXAA antialiasing to be used over native. - * FXAA is faster, but may not always look as great - * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. - * The resolution of the renderer retina would be 2. - * @param {boolean} [options.clearBeforeRender=true] - This sets if the renderer will clear - * the canvas or not before the new render pass. If you wish to set this to false, you *must* set - * preserveDrawingBuffer to `true`. - * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, - * enable this if you need to call toDataUrl on the webgl context. - * @param {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when - * rendering, stopping pixel interpolation. - * @param {number} [options.backgroundColor=0x000000] - The background color of the rendered area - * (shown if not transparent). - * @param {boolean} [options.legacy=false] - If true PixiJS will aim to ensure compatibility - * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. - * @param {string} [options.powerPreference] - Parameter passed to webgl context, set to "high-performance" - * for devices with dual graphics card - */ - constructor(options, arg2, arg3) - { - super('WebGL', options, arg2, arg3); - - /** - * The type of this renderer as a standardised const - * - * @member {number} - * @see PIXI.RENDERER_TYPE - */ - this.type = RENDERER_TYPE.WEBGL; - - // this will be set by the contextSystem (this.context) - this.gl = null; - this.CONTEXT_UID = 0; - this.legacy = !!options.legacy; - - // TODO legacy! - - // runners! - this.runners = { - destroy: new Runner('destroy'), - contextChange: new Runner('contextChange', 1), - reset: new Runner('reset'), - update: new Runner('update'), - postrender: new Runner('postrender'), - prerender: new Runner('prerender'), - resize: new Runner('resize', 2), - }; - - 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(GeometrySystem, 'geometry') - .addSystem(FramebufferSystem, 'framebuffer') - .addSystem(StencilSystem, 'stencil') - .addSystem(ProjectionSystem, 'projection') - .addSystem(TextureGCSystem, 'textureGC') - .addSystem(FilterSystem, 'filter') - .addSystem(RenderTextureSystem, 'renderTexture') - .addSystem(BatchSystem, 'batch'); - - this.initPlugins(Renderer.__plugins); - - /** - * The options passed in to create a new webgl context. - * - * @member {object} - * @private - */ - if (options.context) - { - this.context.initFromContext(options.context); - } - else - { - this.context.initFromOptions({ - alpha: this.transparent, - antialias: options.antialias, - premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', - stencil: true, - 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); - } - - addSystem(_class, name) - { - if (!name) - { - name = _class.name; - } - - // TODO - read name from class.name.. - - /* - if(name.includes('System')) - { - name = name.replace('System', ''); - name = name.charAt(0).toLowerCase() + name.slice(1); - } - */ - - const system = new _class(this); - - if (this[name]) - { - throw new Error(`Whoops! ${name} is already a manger`); - } - - this[name] = system; - - for (const i in this.runners) - { - this.runners[i].add(system); - } - - return this; - - /** - * 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. - */ - } - - /** - * Renders the object to its webGL view - * - * @param {PIXI.DisplayObject} displayObject - the object to be rendered - * @param {PIXI.RenderTexture} renderTexture - The render texture to render to. - * @param {boolean} [clear] - Should the canvas be cleared before the new render - * @param {PIXI.Transform} [transform] - A transform to apply to the render texture before rendering. - * @param {boolean} [skipUpdateTransform] - Should we skip the update transform pass? - */ - render(displayObject, renderTexture, clear, transform, skipUpdateTransform) - { - // can be handy to know! - this.renderingToScreen = !renderTexture; - - this.runners.prerender.run(); - this.emit('prerender'); - - // 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.parent; - - displayObject.parent = this._tempDisplayObjectParent; - displayObject.updateTransform(); - displayObject.parent = 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.run(); - - this.emit('postrender'); - } - - /** - * Resizes the webGL view to the specified width and height. - * - * @param {number} screenWidth - the new width of the screen - * @param {number} screenHeight - the new height of the screen - */ - resize(screenWidth, screenHeight) - { - AbstractRenderer.prototype.resize.call(this, screenWidth, screenHeight); - - this.runners.resize.run(screenWidth, screenHeight); - } - - /** - * Resets the WebGL state so you can render things however you fancy! - * - * @return {PIXI.Renderer} Returns itself. - */ - reset() - { - this.runners.reset.run(); - - return this; - } - - clear() - { - this.framebuffer.bind(); - this.framebuffer.clear(); - } - - /** - * Removes everything from the renderer (event listeners, spritebatch, etc...) - * - * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. - * See: https://github.com/pixijs/pixi.js/issues/2233 - */ - destroy(removeView) - { - this.runners.destroy.run(); - - // call base destroy - super.destroy(removeView); - - // TODO nullify all the managers.. - this.gl = null; - } - - /** - * 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 - * builds and excluding plugins. - * @name PIXI.Renderer#plugins - * @type {object} - * @readonly - * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. - * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. - * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. - * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. - */ - - /** - * Adds a plugin to the renderer. - * - * @method - * @param {string} pluginName - The name of the plugin. - * @param {Function} ctor - The constructor function or class for the plugin. - */ - static registerPlugin(pluginName, ctor) - { - Renderer.__plugins = Renderer.__plugins || {}; - Renderer.__plugins[pluginName] = ctor; - } -} diff --git a/packages/core/src/renderers/State.js b/packages/core/src/renderers/State.js deleted file mode 100644 index fa3f221..0000000 --- a/packages/core/src/renderers/State.js +++ /dev/null @@ -1,163 +0,0 @@ -/* eslint-disable max-len */ - -const BLEND = 0; -const OFFSET = 1; -const CULLING = 2; -const DEPTH_TEST = 3; -const WINDING = 4; - -/** - * This is a webGL state. It is passed The WebGL StateManager. - * Each mesh renderered may require webGL to be in a different state. - * For example you may want different blend mode or to enable polygon offsets - * - * @class - * @memberof PIXI - */ -export default class State -{ - /** - * - */ - constructor() - { - this.data = 0; - - this.blendMode = 0; - this.polygonOffset = 0; - - this.blend = true; - // this.depthTest = true; - } - - /** - * Activates blending of the computed fragment color values - * - * @member {boolean} - */ - get blend() - { - return !!(this.data & (1 << BLEND)); - } - - set blend(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << BLEND)) !== value) - { - this.data ^= (1 << BLEND); - } - } - - /** - * Activates adding an offset to depth values of polygon's fragments - * - * @member {boolean} - * @default false - */ - get offsets() - { - return !!(this.data & (1 << OFFSET)); - } - - set offsets(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << OFFSET)) !== value) - { - this.data ^= (1 << OFFSET); - } - } - - /** - * Activates culling of polygons. - * - * @member {boolean} - * @default false - */ - get culling() - { - return !!(this.data & (1 << CULLING)); - } - - set culling(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << CULLING)) !== value) - { - this.data ^= (1 << CULLING); - } - } - - /** - * Activates depth comparisons and updates to the depth buffer. - * - * @member {boolean} - * @default false - */ - get depthTest() - { - return !!(this.data & (1 << DEPTH_TEST)); - } - - set depthTest(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << DEPTH_TEST)) !== value) - { - this.data ^= (1 << DEPTH_TEST); - } - } - - /** - * Specifies whether or not front or back-facing polygons can be culled. - * @member {boolean} - * @default false - */ - get clockwiseFrontFace() - { - return !!(this.data & (1 << WINDING)); - } - - set clockwiseFrontFace(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << WINDING)) !== value) - { - this.data ^= (1 << WINDING); - } - } - - /** - * The blend mode to be applied when this state is set. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. - * Setting this mode to anything other than NO_BLEND will automatically switch blending on. - * - * @member {boolean} - * @default PIXI.BLEND_MODES.NORMAL - * @see PIXI.BLEND_MODES - */ - get blendMode() - { - return this._blendMode; - } - - set blendMode(value) // eslint-disable-line require-jsdoc - { - // 17 is NO BLEND - this.blend = (value !== 17); - this._blendMode = value; - } - - /** - * The polygon offset. Setting this property to anything other than 0 will automatically enable poygon offset fill. - * - * @member {number} - * @default 0 - */ - get polygonOffset() - { - return this._polygonOffset; - } - - set polygonOffset(value) // eslint-disable-line require-jsdoc - { - this.offsets = !!value; - this._polygonOffset = value; - } -} - diff --git a/packages/core/src/renderers/filters/Filter.js b/packages/core/src/renderers/filters/Filter.js deleted file mode 100644 index 2073b8d..0000000 --- a/packages/core/src/renderers/filters/Filter.js +++ /dev/null @@ -1,140 +0,0 @@ -import Shader from '../../shader/Shader'; -import Program from '../../shader/Program'; -import State from '../State'; -import { settings } from '@pixi/settings'; -// import extractUniformsFromSrc from './extractUniformsFromSrc'; -import defaultVertex from './defaultFilter.vert'; -import defaultFragment from './defaultFilter.frag'; - -// let math = require('../../math'); -/** - * @class - * @memberof PIXI - * @extends PIXI.Shader - */ -export default class Filter extends Shader -{ - /** - * @param {string} [vertexSrc] - The source of the vertex shader. - * @param {string} [fragmentSrc] - The source of the fragment shader. - * @param {object} [uniforms] - Custom uniforms to use to augment the built-in ones. - */ - constructor(vertexSrc, fragmentSrc, uniforms) - { - const program = Program.from(vertexSrc, fragmentSrc); - - super(program, uniforms); - - /** - * The padding of the filter. Some filters require extra space to breath such as a blur. - * Increasing this will add extra width and height to the bounds of the object that the - * filter is applied to. - * - * @member {number} - */ - this.padding = 0; - - /** - * The resolution of the filter. Setting this to be lower will lower the quality but - * increase the performance of the filter. - * - * @member {number} - */ - this.resolution = settings.RESOLUTION; - - /** - * If enabled is true the filter is applied, if false it will not. - * - * @member {boolean} - */ - this.enabled = true; - - /** - * If enabled, PixiJS will fit the filter area into boundaries for better performance. - * Switch it off if it does not work for specific shader. - * - * @member {boolean} - */ - this.autoFit = true; - - /** - * Legacy filters use position and uvs from attributes - * @member {boolean} - * @readonly - */ - this.legacy = !!this.program.attributeData.aTextureCoord; - - /** - * the webGL state the filter requires to render - * @member {PIXI.State} - */ - this.state = new State(); - } - - /** - * Applies the filter - * - * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from - * @param {PIXI.RenderTarget} input - The input render target. - * @param {PIXI.RenderTarget} output - The target to output to. - * @param {boolean} clear - Should the output be cleared before rendering to it - * @param {object} [currentState] - It's current state of filter. - * There are some useful properties in the currentState : - * target, filters, sourceFrame, destinationFrame, renderTarget, resolution - */ - apply(filterManager, input, output, clear, currentState, derp) // eslint-disable-line no-unused-vars - { - // do as you please! - - filterManager.applyFilter(this, input, output, clear, currentState, derp); - - // or just do a regular render.. - } - - /** - * Sets the blendmode of the filter - * - * @member {number} - * @default PIXI.BLEND_MODES.NORMAL - */ - get blendMode() - { - return this.state.blendMode; - } - - set blendMode(value) // eslint-disable-line require-jsdoc - { - this.state.blendMode = value; - } - - /** - * The default vertex shader source - * - * @static - * @constant - */ - static get defaultVertexSrc() - { - return defaultVertex; - } - - /** - * The default fragment shader source - * - * @static - * @constant - */ - static get defaultFragmentSrc() - { - return defaultFragment; - } -} - -/** - * Used for caching shader IDs - * - * @static - * @private - */ -Filter.SOURCE_KEY_MAP = {}; - diff --git a/packages/core/src/renderers/filters/defaultFilter.frag b/packages/core/src/renderers/filters/defaultFilter.frag deleted file mode 100644 index a14c1be..0000000 --- a/packages/core/src/renderers/filters/defaultFilter.frag +++ /dev/null @@ -1,22 +0,0 @@ -varying vec2 vTextureCoord; -varying vec2 vFilterCoord; - -uniform sampler2D uSampler; -uniform sampler2D filterSampler; - -void main(void){ - vec4 masky = texture2D(filterSampler, vFilterCoord); - vec4 sample = texture2D(uSampler, vTextureCoord); - vec4 color; - if(mod(vFilterCoord.x, 1.0) > 0.5) - { - color = vec4(1.0, 0.0, 0.0, 1.0); - } - else - { - color = vec4(0.0, 1.0, 0.0, 1.0); - } - // gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0); - gl_FragColor = mix(sample, masky, 0.5); - gl_FragColor *= sample.a; -} diff --git a/packages/core/src/renderers/filters/defaultFilter.vert b/packages/core/src/renderers/filters/defaultFilter.vert deleted file mode 100644 index e1febca..0000000 --- a/packages/core/src/renderers/filters/defaultFilter.vert +++ /dev/null @@ -1,14 +0,0 @@ -attribute vec2 aVertexPosition; -attribute vec2 aTextureCoord; - -uniform mat3 projectionMatrix; -uniform mat3 filterMatrix; - -varying vec2 vTextureCoord; -varying vec2 vFilterCoord; - -void main(void){ - gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); - vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy; - vTextureCoord = aTextureCoord ; -} diff --git a/packages/core/src/renderers/filters/filterTransforms.js b/packages/core/src/renderers/filters/filterTransforms.js deleted file mode 100644 index 87a3cd6..0000000 --- a/packages/core/src/renderers/filters/filterTransforms.js +++ /dev/null @@ -1,50 +0,0 @@ -import { Matrix } from '@pixi/math'; - -/** - * Calculates the mapped matrix - * @param filterArea {Rectangle} The filter area - * @param sprite {Sprite} the target sprite - * @param outputMatrix {Matrix} @alvin - * @private - */ -// this returns a matrix that will normalise map filter cords in the filter to screen space -export function calculateScreenSpaceMatrix(outputMatrix, filterArea, textureSize) -{ - // TODO unwrap? - const mappedMatrix = outputMatrix.identity(); - - mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); - - mappedMatrix.scale(textureSize.width, textureSize.height); - - return mappedMatrix; -} - -export function calculateNormalizedScreenSpaceMatrix(outputMatrix, filterArea, textureSize) -{ - const mappedMatrix = outputMatrix.identity(); - - mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); - - const translateScaleX = (textureSize.width / filterArea.width); - const translateScaleY = (textureSize.height / filterArea.height); - - mappedMatrix.scale(translateScaleX, translateScaleY); - - return mappedMatrix; -} - -// this will map the filter coord so that a texture can be used based on the transform of a sprite -export function calculateSpriteMatrix(outputMatrix, filterArea, textureSize, sprite) -{ - const orig = sprite._texture.orig; - const mappedMatrix = outputMatrix.set(textureSize.width, 0, 0, textureSize.height, filterArea.x, filterArea.y); - const worldTransform = sprite.worldTransform.copyTo(Matrix.TEMP_MATRIX); - - worldTransform.invert(); - mappedMatrix.prepend(worldTransform); - mappedMatrix.scale(1.0 / orig.width, 1.0 / orig.height); - mappedMatrix.translate(sprite.anchor.x, sprite.anchor.y); - - return mappedMatrix; -} diff --git a/packages/core/src/renderers/filters/spriteMask/SpriteMaskFilter.js b/packages/core/src/renderers/filters/spriteMask/SpriteMaskFilter.js deleted file mode 100644 index d5a91a4..0000000 --- a/packages/core/src/renderers/filters/spriteMask/SpriteMaskFilter.js +++ /dev/null @@ -1,63 +0,0 @@ -import Filter from '../Filter'; -import { Matrix } from '@pixi/math'; -import vertex from './spriteMaskFilter.vert'; -import fragment from './spriteMaskFilter.frag'; -import { default as TextureMatrix } from '../../../textures/TextureMatrix'; - -/** - * The SpriteMaskFilter class - * - * @class - * @extends PIXI.Filter - * @memberof PIXI - */ -export default class SpriteMaskFilter extends Filter -{ - /** - * @param {PIXI.Sprite} sprite - the target sprite - */ - constructor(sprite) - { - const maskMatrix = new Matrix(); - - super(vertex, fragment); - - sprite.renderable = false; - - this.maskSprite = sprite; - this.maskMatrix = maskMatrix; - } - - /** - * Applies the filter - * - * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from - * @param {PIXI.RenderTarget} input - The input render target. - * @param {PIXI.RenderTarget} output - The target to output to. - */ - apply(filterManager, input, output) - { - const maskSprite = this.maskSprite; - const tex = this.maskSprite.texture; - - if (!tex.valid) - { - return; - } - if (!tex.transform) - { - // margin = 0.0, let it bleed a bit, shader code becomes easier - // assuming that atlas textures were made with 1-pixel padding - tex.transform = new TextureMatrix(tex, 0.0); - } - tex.transform.update(); - - this.uniforms.mask = tex; - this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite) - .prepend(tex.transform.mapCoord); - this.uniforms.alpha = maskSprite.worldAlpha; - this.uniforms.maskClamp = tex.transform.uClampFrame; - - filterManager.applyFilter(this, input, output); - } -} diff --git a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.frag b/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.frag deleted file mode 100644 index 0e4aef8..0000000 --- a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.frag +++ /dev/null @@ -1,23 +0,0 @@ -varying vec2 vMaskCoord; -varying vec2 vTextureCoord; - -uniform sampler2D uSampler; -uniform sampler2D mask; -uniform float alpha; -uniform vec4 maskClamp; - -void main(void) -{ - float clip = step(3.5, - step(maskClamp.x, vMaskCoord.x) + - step(maskClamp.y, vMaskCoord.y) + - step(vMaskCoord.x, maskClamp.z) + - step(vMaskCoord.y, maskClamp.w)); - - vec4 original = texture2D(uSampler, vTextureCoord); - vec4 masky = texture2D(mask, vMaskCoord); - - original *= (masky.r * masky.a * alpha * clip); - - gl_FragColor = original; -} diff --git a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.vert b/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.vert deleted file mode 100644 index d23a30c..0000000 --- a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.vert +++ /dev/null @@ -1,16 +0,0 @@ -attribute vec2 aVertexPosition; -attribute vec2 aTextureCoord; - -uniform mat3 projectionMatrix; -uniform mat3 otherMatrix; - -varying vec2 vMaskCoord; -varying vec2 vTextureCoord; - -void main(void) -{ - gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); - - vTextureCoord = aTextureCoord; - vMaskCoord = ( otherMatrix * vec3( aTextureCoord, 1.0) ).xy; -} diff --git a/packages/core/src/renderers/systems/BatchSystem.js b/packages/core/src/renderers/systems/BatchSystem.js deleted file mode 100644 index 70e9518..0000000 --- a/packages/core/src/renderers/systems/BatchSystem.js +++ /dev/null @@ -1,68 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import ObjectRenderer from '../utils/ObjectRenderer'; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ - -export default class BatchSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this manager works for. - */ - constructor(renderer) - { - super(renderer); - - /** - * An empty renderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.emptyRenderer = new ObjectRenderer(renderer); - - /** - * The currently active ObjectRenderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.currentRenderer = this.emptyRenderer; - } - - /** - * Changes the current renderer to the one given in parameter - * - * @param {PIXI.ObjectRenderer} objectRenderer - The object renderer to use. - */ - setObjectRenderer(objectRenderer) - { - if (this.currentRenderer === objectRenderer) - { - return; - } - - this.currentRenderer.stop(); - this.currentRenderer = objectRenderer; - - this.renderer.state.setState(objectRenderer.state); - - this.currentRenderer.start(); - } - - /** - * This should be called if you wish to do some custom rendering - * It will basically render anything that may be batched up such as sprites - * - */ - flush() - { - this.setObjectRenderer(this.emptyRenderer); - } - - reset() - { - this.setObjectRenderer(this.emptyRenderer); - } -} diff --git a/packages/core/src/renderers/systems/ContextSystem.js b/packages/core/src/renderers/systems/ContextSystem.js deleted file mode 100644 index 43bf827..0000000 --- a/packages/core/src/renderers/systems/ContextSystem.js +++ /dev/null @@ -1,186 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { settings } from '@pixi/settings'; - -let CONTEXT_UID = 0; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class ContextSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this.webGLVersion = 1; - - this.handleContextLost = this.handleContextLost.bind(this); - this.handleContextRestored = this.handleContextRestored.bind(this); - - this.extensions = {}; - - renderer.view.addEventListener('webglcontextlost', this.handleContextLost, false); - renderer.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); - } - - get isLost() - { - return (!this.gl || this.gl.isContextLost()); - } - - contextChange(gl) - { - this.gl = gl; - - // restore a context if it was previously lost - if (gl.isContextLost() && gl.getExtension('WEBGL_lose_context')) - { - gl.getExtension('WEBGL_lose_context').restoreContext(); - } - } - - initFromContext(gl) - { - this.gl = gl; - this.validateContext(gl); - this.renderer.gl = gl; - this.renderer.CONTEXT_UID = CONTEXT_UID++; - this.renderer.runners.contextChange.run(gl); - } - - initFromOptions(options) - { - const gl = this.createContext(this.renderer.view, options); - - this.initFromContext(gl); - } - - /** - * Helper class to create a webGL Context - * - * @class - * @memberof PIXI.glCore - * @param canvas {HTMLCanvasElement} the canvas element that we will get the context from - * @param options {Object} An options object that gets passed in to the canvas element containing the context attributes, - * see https://developer.mozilla.org/en/docs/Web/API/HTMLCanvasElement/getContext for the options available - * @return {WebGLRenderingContext} the WebGL context - */ - createContext(canvas, options) - { - let gl; - - if (settings.PREFER_WEBGL_2) - { - gl = canvas.getContext('webgl2', options); - } - - if (gl) - { - this.webGLVersion = 2; - } - else - { - this.webGLVersion = 1; - - gl = canvas.getContext('webgl', options) - || canvas.getContext('experimental-webgl', options); - - if (!gl) - { - // fail, not able to get a context - throw new Error('This browser does not support webGL. Try using the canvas renderer'); - } - } - - this.gl = gl; - - this.getExtensions(); - - return gl; - } - - getExtensions() - { - // time to set up default etensions that pixi uses.. - const gl = this.gl; - const extensions = this.extensions; - - if (this.webGLVersion === 1) - { - extensions.drawBuffers = gl.getExtension('WEBGL_draw_buffers'); - extensions.depthTexture = gl.getExtension('WEBKIT_WEBGL_depth_texture'); - extensions.floatTexture = gl.getExtension('OES_texture_float'); - extensions.loseContext = gl.getExtension('WEBGL_lose_context'); - - extensions.vertexArrayObject = gl.getExtension('OES_vertex_array_object') - || gl.getExtension('MOZ_OES_vertex_array_object') - || gl.getExtension('WEBKIT_OES_vertex_array_object'); - } - - // we don't use any specific WebGL 2 ones yet! - } - - /** - * Handles a lost webgl context - * - * @private - * @param {WebGLContextEvent} event - The context lost event. - */ - handleContextLost(event) - { - event.preventDefault(); - } - - /** - * Handles a restored webgl context - * - * @private - */ - handleContextRestored() - { - this.renderer.runners.contextChange.run(this.gl); - } - - destroy() - { - const view = this.renderer.view; - - // remove listeners - view.removeEventListener('webglcontextlost', this.handleContextLost); - view.removeEventListener('webglcontextrestored', this.handleContextRestored); - - this.gl.useProgram(null); - - if (this.extensions.loseContext) - { - this.extensions.loseContext.loseContext(); - } - } - - postrender() - { - this.gl.flush(); - } - - validateContext(gl) - { - const attributes = gl.getContextAttributes(); - - // this is going to be fairly simple for now.. but at least we have room to grow! - if (!attributes.stencil) - { - /* eslint-disable max-len */ - - /* eslint-disable no-console */ - console.warn('Provided WebGL context does not have a stencil buffer, masks may not render correctly'); - /* eslint-enable no-console */ - - /* eslint-enable max-len */ - } - } -} diff --git a/packages/core/src/renderers/systems/FramebufferSystem.js b/packages/core/src/renderers/systems/FramebufferSystem.js deleted file mode 100644 index df7902b..0000000 --- a/packages/core/src/renderers/systems/FramebufferSystem.js +++ /dev/null @@ -1,246 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { Rectangle } from '@pixi/math'; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class FramebufferSystem extends WebGLSystem -{ - /** - * Sets up the renderer context and necessary buffers. - * - * @private - */ - contextChange() - { - this.gl = this.renderer.gl; - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - this.current = null; - this.viewport = new Rectangle(); - - this.drawBufferExtension = this.renderer.context.extensions.drawBuffers; - } - - bind(framebuffer, frame) - { - const gl = this.gl; - - this.current = framebuffer; - - if (framebuffer) - { - // TODO cacheing layer! - - const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID] || this.initFramebuffer(framebuffer); - - gl.bindFramebuffer(gl.FRAMEBUFFER, fbo.framebuffer); - // makesure all textures are unbound.. - - // now check for updates... - if (fbo.dirtyId !== framebuffer.dirtyId) - { - fbo.dirtyId = framebuffer.dirtyId; - - if (fbo.dirtyFormat !== framebuffer.dirtyFormat) - { - fbo.dirtyFormat = framebuffer.dirtyFormat; - this.updateFramebuffer(framebuffer); - } - else if (fbo.dirtySize !== framebuffer.dirtySize) - { - fbo.dirtySize = framebuffer.dirtySize; - this.resizeFramebuffer(framebuffer); - } - } - - for (let i = 0; i < framebuffer.colorTextures.length; i++) - { - if (framebuffer.colorTextures[i].texturePart) - { - this.renderer.texture.unbind(framebuffer.colorTextures[i].texture); - } - else - { - this.renderer.texture.unbind(framebuffer.colorTextures[i]); - } - } - - if (framebuffer.depthTexture) - { - this.renderer.texture.unbind(framebuffer.depthTexture); - } - - if (frame) - { - this.setViewport(frame.x, frame.y, frame.width, frame.height); - } - else - { - this.setViewport(0, 0, framebuffer.width, framebuffer.height); - } - } - else - { - gl.bindFramebuffer(gl.FRAMEBUFFER, null); - - if (frame) - { - this.setViewport(frame.x, frame.y, frame.width, frame.height); - } - else - { - this.setViewport(0, 0, this.renderer.width, this.renderer.height); - } - } - } - - setViewport(x, y, width, height) - { - const v = this.viewport; - - if (v.width !== width || v.height !== height || v.x !== x || v.y !== y) - { - v.x = x; - v.y = y; - v.width = width; - v.height = height; - - this.gl.viewport(x, y, width, height); - } - } - - get size() - { - if (this.current) - { - // TODO store temp - return { x: 0, y: 0, width: this.current.width, height: this.current.height }; - } - - return { x: 0, y: 0, width: this.renderer.width, height: this.renderer.height }; - } - - clear(r, g, b, a) - { - const gl = this.gl; - - // TODO clear color can be set only one right? - gl.clearColor(r, g, b, a); - gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); - } - - // private functions... - - initFramebuffer(framebuffer) - { - const gl = this.gl; - - // TODO - make this a class? - const fbo = { - framebuffer: gl.createFramebuffer(), - stencil: null, - dirtyId: 0, - dirtyFormat: 0, - dirtySize: 0, - }; - - framebuffer.glFrameBuffers[this.CONTEXT_UID] = fbo; - - return fbo; - } - - resizeFramebuffer(framebuffer) - { - const gl = this.gl; - - if (framebuffer.stencil || framebuffer.depth) - { - gl.bindRenderbuffer(gl.RENDERBUFFER, this.stencil); - gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); - } - } - - updateFramebuffer(framebuffer) - { - const gl = this.gl; - - const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID]; - - // bind the color texture - const colorTextures = framebuffer.colorTextures; - - let count = colorTextures.length; - - if (!this.drawBufferExtension) - { - count = Math.min(count, 1); - } - - const activeTextures = []; - - for (let i = 0; i < count; i++) - { - const texture = framebuffer.colorTextures[i]; - - if (texture.texturePart) - { - this.renderer.texture.bind(texture.texture, 0); - - gl.framebufferTexture2D(gl.FRAMEBUFFER, - gl.COLOR_ATTACHMENT0 + i, - gl.TEXTURE_CUBE_MAP_NEGATIVE_X + texture.side, - texture.texture._glTextures[this.CONTEXT_UID].texture, - 0); - } - else - { - this.renderer.texture.bind(texture, 0); - - gl.framebufferTexture2D(gl.FRAMEBUFFER, - gl.COLOR_ATTACHMENT0 + i, - gl.TEXTURE_2D, - texture._glTextures[this.CONTEXT_UID].texture, - 0); - } - - activeTextures.push(gl.COLOR_ATTACHMENT0 + i); - } - - if (this.drawBufferExtension && activeTextures.length > 1) - { - this.drawBufferExtension.drawBuffersWEBGL(activeTextures); - } - - if (framebuffer.depthTexture) - { - const depthTextureExt = this.renderer.context.extensions.depthTexture; - - if (depthTextureExt) - { - const depthTexture = framebuffer.depthTexture; - - this.renderer.texture.bind(depthTexture, 0); - - gl.framebufferTexture2D(gl.FRAMEBUFFER, - gl.DEPTH_ATTACHMENT, - gl.TEXTURE_2D, - depthTexture._glTextures[this.CONTEXT_UID].texture, - 0); - } - } - - if (framebuffer.stencil || framebuffer.depth) - { - fbo.stencil = gl.createRenderbuffer(); - - gl.bindRenderbuffer(gl.RENDERBUFFER, fbo.stencil); - - // TODO.. this is depth AND stencil? - gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, fbo.stencil); - gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); - // fbo.enableStencil(); - } - } -} diff --git a/packages/core/src/renderers/systems/MaskSystem.js b/packages/core/src/renderers/systems/MaskSystem.js deleted file mode 100644 index d9c4b1a..0000000 --- a/packages/core/src/renderers/systems/MaskSystem.js +++ /dev/null @@ -1,199 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import AlphaMaskFilter from '../filters/spriteMask/SpriteMaskFilter'; - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class MaskSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - // TODO - we don't need both! - this.scissor = false; - this.scissorData = null; - this.scissorRenderTarget = null; - - this.enableScissor = false; - - this.alphaMaskPool = []; - this.alphaMaskIndex = 0; - } - - /** - * Applies the Mask and adds it to the current filter stack. - * - * @param {PIXI.DisplayObject} target - Display Object to push the mask to - * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. - */ - push(target, maskData) - { - // TODO the root check means scissor rect will not - // be used on render textures more info here: - // https://github.com/pixijs/pixi.js/pull/3545 - - if (maskData.texture) - { - this.pushSpriteMask(target, maskData); - } - else if (this.enableScissor - && !this.scissor - && this.renderer._activeRenderTarget.root - && !this.renderer.stencil.stencilMaskStack.length - && maskData.isFastRect()) - { - const matrix = maskData.worldTransform; - - let rot = Math.atan2(matrix.b, matrix.a); - - // use the nearest degree! - rot = Math.round(rot * (180 / Math.PI)); - - if (rot % 90) - { - this.pushStencilMask(maskData); - } - else - { - this.pushScissorMask(target, maskData); - } - } - else - { - this.pushStencilMask(maskData); - } - } - - /** - * Removes the last mask from the mask stack and doesn't return it. - * - * @param {PIXI.DisplayObject} target - Display Object to pop the mask from - * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. - */ - pop(target, maskData) - { - if (maskData.texture) - { - this.popSpriteMask(target, maskData); - } - else if (this.enableScissor && !this.renderer.stencil.stencilMaskStack.length) - { - this.popScissorMask(target, maskData); - } - else - { - this.popStencilMask(target, maskData); - } - } - - /** - * Applies the Mask and adds it to the current filter stack. - * - * @param {PIXI.RenderTarget} target - Display Object to push the sprite mask to - * @param {PIXI.Sprite} maskData - Sprite to be used as the mask - */ - pushSpriteMask(target, maskData) - { - let alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; - - if (!alphaMaskFilter) - { - alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; - } - - alphaMaskFilter[0].resolution = this.renderer.resolution; - alphaMaskFilter[0].maskSprite = maskData; - - // TODO - may cause issues! - target.filterArea = maskData.getBounds(true); - - this.renderer.filterSystem.pushFilter(target, alphaMaskFilter); - - this.alphaMaskIndex++; - } - - /** - * Removes the last filter from the filter stack and doesn't return it. - * - */ - popSpriteMask() - { - this.renderer.filterSystem.popFilter(); - this.alphaMaskIndex--; - } - - /** - * Applies the Mask and adds it to the current filter stack. - * - * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. - */ - pushStencilMask(maskData) - { - this.renderer.batch.flush(); - this.renderer.stencil.pushStencil(maskData); - } - - /** - * Removes the last filter from the filter stack and doesn't return it. - * - */ - popStencilMask() - { - // this.renderer.currentRenderer.stop(); - this.renderer.stencil.popStencil(); - } - - /** - * - * @param {PIXI.DisplayObject} target - Display Object to push the mask to - * @param {PIXI.Graphics} maskData - The masking data. - */ - pushScissorMask(target, maskData) - { - maskData.renderable = true; - - const renderTarget = this.renderer._activeRenderTarget; - - const bounds = maskData.getBounds(); - - bounds.fit(renderTarget.size); - maskData.renderable = false; - - this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); - - const resolution = this.renderer.resolution; - - this.renderer.gl.scissor( - bounds.x * resolution, - (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, - bounds.width * resolution, - bounds.height * resolution - ); - - this.scissorRenderTarget = renderTarget; - this.scissorData = maskData; - this.scissor = true; - } - - /** - * - * - */ - popScissorMask() - { - this.scissorRenderTarget = null; - this.scissorData = null; - this.scissor = false; - - // must be scissor! - const gl = this.renderer.gl; - - gl.disable(gl.SCISSOR_TEST); - } -} diff --git a/packages/core/src/renderers/systems/ProjectionSystem.js b/packages/core/src/renderers/systems/ProjectionSystem.js deleted file mode 100644 index ec5efdb..0000000 --- a/packages/core/src/renderers/systems/ProjectionSystem.js +++ /dev/null @@ -1,73 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { Matrix } from '@pixi/math'; - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ - -export default class ProjectionSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this.projectionMatrix = new Matrix(); - } - - update(destinationFrame, sourceFrame, resolution, root) - { - this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; - this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; - - this.calculateProjection(this.destinationFrame, this.sourceFrame, resolution, root); - - this.renderer.globalUniforms.uniforms.projectionMatrix = this.projectionMatrix; - this.renderer.globalUniforms.update(); - } - - /** - * Updates the projection matrix based on a projection frame (which is a rectangle) - * - * @param {Rectangle} destinationFrame - The destination frame. - * @param {Rectangle} sourceFrame - The source frame. - */ - calculateProjection(destinationFrame, sourceFrame, resolution, root) - { - const pm = this.projectionMatrix; - - // I don't think we will need this line.. - // pm.identity(); - - if (!root) - { - pm.a = (1 / destinationFrame.width * 2) * resolution; - pm.d = (1 / destinationFrame.height * 2) * resolution; - - pm.tx = -1 - (sourceFrame.x * pm.a); - pm.ty = -1 - (sourceFrame.y * pm.d); - } - else - { - pm.a = (1 / destinationFrame.width * 2) * resolution; - pm.d = (-1 / destinationFrame.height * 2) * resolution; - - pm.tx = -1 - (sourceFrame.x * pm.a); - pm.ty = 1 - (sourceFrame.y * pm.d); - } - } - - /** - * Sets the transform of the active render target to the given matrix - * - * @param {PIXI.Matrix} matrix - The transformation matrix - */ - setTransform()// matrix) - { - // this._activeRenderTarget.transform = matrix; - } -} diff --git a/packages/core/src/renderers/systems/RenderTextureSystem.js b/packages/core/src/renderers/systems/RenderTextureSystem.js deleted file mode 100644 index a133ef1..0000000 --- a/packages/core/src/renderers/systems/RenderTextureSystem.js +++ /dev/null @@ -1,115 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { Rectangle } from '@pixi/math'; - -const tempRect = new Rectangle(); - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ - -export default class RenderTextureSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this.clearColor = renderer._backgroundColorRgba; - - // TODO moe this property somewhere else! - this.defaultMaskStack = []; - this.defaultFilterStack = [{}]; - - // empty render texture? - this.renderTexture = null; - - this.destinationFrame = new Rectangle(); - } - - bind(renderTexture, sourceFrame, destinationFrame) - { - // TODO - do we want this?? - if (this.renderTexture === renderTexture) return; - this.renderTexture = renderTexture; - - const renderer = this.renderer; - - if (renderTexture) - { - const baseTexture = renderTexture.baseTexture; - - if (!destinationFrame) - { - tempRect.width = baseTexture.realWidth; - tempRect.height = baseTexture.realHeight; - - destinationFrame = tempRect; - } - - if (!sourceFrame) - { - sourceFrame = destinationFrame; - } - - this.renderer.framebuffer.bind(baseTexture.frameBuffer, destinationFrame); - - this.renderer.projection.update(destinationFrame, sourceFrame, baseTexture.resolution, false); - this.renderer.stencil.setMaskStack(baseTexture.stencilMaskStack); - } - else - { - // TODO these validation checks happen deeper down.. - // thing they can be avoided.. - if (!destinationFrame) - { - tempRect.width = renderer.width; - tempRect.height = renderer.height; - - destinationFrame = tempRect; - } - - if (!sourceFrame) - { - sourceFrame = destinationFrame; - } - - renderer.framebuffer.bind(null, destinationFrame); - - // TODO store this.. - this.renderer.projection.update(destinationFrame, sourceFrame, this.renderer.resolution, true); - this.renderer.stencil.setMaskStack(this.defaultMaskStack); - } - - this.destinationFrame.copyFrom(destinationFrame); - } - - /** - * Erases the render texture and fills the drawing area with a colour - * - * @param {number} [clearColor] - The colour - * @return {PIXI.Renderer} Returns itself. - */ - clear(clearColor) - { - if (this.renderTexture) - { - clearColor = clearColor || this.renderTexture.baseTexture.clearColor; - } - else - { - clearColor = clearColor || this.clearColor; - } - - this.renderer.framebuffer.clear(clearColor[0], clearColor[1], clearColor[2], clearColor[3]); - } - - resize()// screenWidth, screenHeight) - { - // resize the root only! - this.bind(null); - } -} diff --git a/packages/core/src/renderers/systems/StateSystem.js b/packages/core/src/renderers/systems/StateSystem.js deleted file mode 100755 index d7f4caf..0000000 --- a/packages/core/src/renderers/systems/StateSystem.js +++ /dev/null @@ -1,288 +0,0 @@ -import mapWebGLBlendModesToPixi from '../utils/mapWebGLBlendModesToPixi'; -import WebGLSystem from './WebGLSystem'; -import WebGLState from '../State'; - -const BLEND = 0; -const OFFSET = 1; -const CULLING = 2; -const DEPTH_TEST = 3; -const WINDING = 4; - -/** - * A WebGL state machines - * - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class StateSystem extends WebGLSystem -{ - /** - * @param {WebGLRenderingContext} gl - The current WebGL rendering context - */ - constructor(renderer) - { - super(renderer); - - this.gl = null; - - this.maxAttribs = null; - - // check we have vao.. - this.nativeVaoExtension = null; - - this.attribState = null; - - this.stateId = 0; - this.polygonOffset = 0; - this.blendMode = 17; - - this.map = []; - - // map functions for when we set state.. - this.map[BLEND] = this.setBlend; - this.map[OFFSET] = this.setOffset; - this.map[CULLING] = this.setCullFace; - this.map[DEPTH_TEST] = this.setDepthTest; - this.map[WINDING] = this.setFrontFace; - - this.checks = []; - - this.defaultState = new WebGLState(); - this.defaultState.blend = true; - this.defaultState.depth = true; - } - - contextChange(gl) - { - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; - - this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); - - // check we have vao.. - this.nativeVaoExtension = ( - gl.getExtension('OES_vertex_array_object') - || gl.getExtension('MOZ_OES_vertex_array_object') - || gl.getExtension('WEBKIT_OES_vertex_array_object') - ); - - this.attribState = { - tempAttribState: new Array(this.maxAttribs), - attribState: new Array(this.maxAttribs), - }; - - this.blendModes = mapWebGLBlendModesToPixi(gl); - - this.setState(this.defaultState); - - this.reset(); - } - - /** - * Sets the current state - * - * @param {*} state - The state to set. - */ - setState(state) - { - state = state || this.defaultState; - - // TODO maybe to an object check? ( this.state === state )? - if (this.stateId !== state.data) - { - let diff = this.stateId ^ state.data; - let i = 0; - - // order from least to most common - while (diff) - { - if (diff & 1) - { - // state change! - this.map[i].call(this, !!(state.data & (1 << i))); - } - - diff = diff >> 1; - i++; - } - - this.stateId = state.data; - } - - // based on the above settings we check for specific modes.. - // for example if blend is active we check and set the blend modes - // or of polygon offset is active we check the poly depth. - for (let i = 0; i < this.checks.length; i++) - { - this.checks[i](this, state); - } - } - - /** - * Enables or disabled blending. - * - * @param {boolean} value - Turn on or off webgl blending. - */ - setBlend(value) - { - this.updateCheck(StateSystem.checkBlendMode, value); - - this.gl[value ? 'enable' : 'disable'](this.gl.BLEND); - } - - /** - * Enables or disable polygon offset fill - * - * @param {boolean} value - Turn on or off webgl polygon offset testing. - */ - setOffset(value) - { - this.gl[value ? 'enable' : 'disable'](this.gl.POLYGON_OFFSET_FILL); - } - - /** - * Sets whether to enable or disable depth test. - * - * @param {boolean} value - Turn on or off webgl depth testing. - */ - setDepthTest(value) - { - this.gl[value ? 'enable' : 'disable'](this.gl.DEPTH_TEST); - } - - /** - * Sets whether to enable or disable cull face. - * - * @param {boolean} value - Turn on or off webgl cull face. - */ - setCullFace(value) - { - this.gl[value ? 'enable' : 'disable'](this.gl.CULL_FACE); - } - - /** - * Sets the gl front face. - * - * @param {boolean} value - true is clockwise and false is counter-clockwise - */ - setFrontFace(value) - { - this.gl.frontFace(this.gl[value ? 'CW' : 'CCW']); - } - - /** - * Sets the blend mode. - * - * @param {number} value - The blend mode to set to. - */ - setBlendMode(value) - { - if (value === this.blendMode) - { - return; - } - - this.blendMode = value; - this.gl.blendFunc(this.blendModes[value][0], this.blendModes[value][1]); - } - - /** - * Sets the polygon offset. - * - * @param {number} value - the polygon offset - * @param {number} scale - the polygon offset scale - */ - setPolygonOffset(value, scale) - { - this.gl.polygonOffset(value, scale); - } - - /** - * Disables all the vaos in use - * - */ - resetAttributes() - { - for (let i = 0; i < this.attribState.tempAttribState.length; i++) - { - this.attribState.tempAttribState[i] = 0; - } - - for (let i = 0; i < this.attribState.attribState.length; i++) - { - this.attribState.attribState[i] = 0; - } - - // im going to assume one is always active for performance reasons. - for (let i = 1; i < this.maxAttribs; i++) - { - this.gl.disableVertexAttribArray(i); - } - } - - // used - /** - * Resets all the logic and disables the vaos - */ - reset() - { - // unbind any VAO if they exist.. - if (this.nativeVaoExtension) - { - this.nativeVaoExtension.bindVertexArrayOES(null); - } - - // reset all attributes.. - this.resetAttributes(); - - this.gl.pixelStorei(this.gl.UNPACK_FLIP_Y_WEBGL, false); - - this.setBlendMode(0); - - // TODO? - // this.setState(this.defaultState); - } - - /** - * checks to see which updates should be checked based on which settings have been activated - * for example if blend is enabled then we shold check the blend modes each time the state is changed - * or if poygon fill is activated then we need to check if the polygone offset changes. - * The idea is that we only check what we have too. - * - * @param {Function} func the checking function to add or remove - * @param {boolean} value should the check function be added or removed. - */ - updateCheck(func, value) - { - const index = this.checks.indexOf(func); - - if (value && index === -1) - { - this.checks.push(func); - } - else if (!value && index !== -1) - { - this.checks.splice(index, 1); - } - } - - /** - * A private little wrapper function that we call to check the blend mode. - * - * @static - * @private - * @param {PIXI.StateSystem} System the System to perform the state check on - * @param {PIXI.State} state the state that the blendMode will pulled from - */ - static checkBlendMode(system, state) - { - system.setBlendMode(state.blendMode); - } - - // TODO - add polygon offset? -} diff --git a/packages/core/src/renderers/systems/StencilSystem.js b/packages/core/src/renderers/systems/StencilSystem.js deleted file mode 100644 index 5d538cb..0000000 --- a/packages/core/src/renderers/systems/StencilSystem.js +++ /dev/null @@ -1,133 +0,0 @@ -import WebGLSystem from './WebGLSystem'; - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class StencilSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - this.stencilMaskStack = []; - } - - /** - * Changes the mask stack that is used by this System. - * - * @param {PIXI.Graphics[]} stencilMaskStack - The mask stack - */ - setMaskStack(stencilMaskStack) - { - const gl = this.renderer.gl; - - if (stencilMaskStack.length !== this.stencilMaskStack.length) - { - if (stencilMaskStack.length === 0) - { - gl.disable(gl.STENCIL_TEST); - } - else - { - gl.enable(gl.STENCIL_TEST); - } - } - - this.stencilMaskStack = stencilMaskStack; - } - - /** - * Applies the Mask and adds it to the current stencil stack. @alvin - * - * @param {PIXI.Graphics} graphics - The mask - */ - pushStencil(graphics) - { - this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); - - // this.renderer._activeRenderTarget.attachStencilBuffer(); - - const gl = this.renderer.gl; - const prevMaskCount = this.stencilMaskStack.length; - - if (prevMaskCount === 0) - { - gl.enable(gl.STENCIL_TEST); - } - - this.stencilMaskStack.push(graphics); - - // Increment the refference stencil value where the new mask overlaps with the old ones. - gl.colorMask(false, false, false, false); - gl.stencilFunc(gl.EQUAL, prevMaskCount, this._getBitwiseMask()); - gl.stencilOp(gl.KEEP, gl.KEEP, gl.INCR); - this.renderer.plugins.graphics.render(graphics); - - this._useCurrent(); - } - - /** - * Removes the last mask from the stencil stack. @alvin - */ - popStencil() - { - this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); - - const gl = this.renderer.gl; - const graphics = this.stencilMaskStack.pop(); - - if (this.stencilMaskStack.length === 0) - { - // the stack is empty! - gl.disable(gl.STENCIL_TEST); - gl.clear(gl.STENCIL_BUFFER_BIT); - gl.clearStencil(0); - } - else - { - // Decrement the refference stencil value where the popped mask overlaps with the other ones - gl.colorMask(false, false, false, false); - gl.stencilOp(gl.KEEP, gl.KEEP, gl.DECR); - this.renderer.plugins.graphics.render(graphics); - - this._useCurrent(); - } - } - - /** - * Setup renderer to use the current stencil data. - */ - _useCurrent() - { - const gl = this.renderer.gl; - - gl.colorMask(true, true, true, true); - gl.stencilFunc(gl.EQUAL, this.stencilMaskStack.length, this._getBitwiseMask()); - gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP); - } - - /** - * Fill 1s equal to the number of acitve stencil masks. - * - * @return {number} The bitwise mask. - */ - _getBitwiseMask() - { - return (1 << this.stencilMaskStack.length) - 1; - } - - /** - * Destroys the mask stack. - * - */ - destroy() - { - super.destroy(this); - - this.stencilMaskStack = null; - } -} diff --git a/packages/core/src/renderers/systems/WebGLSystem.js b/packages/core/src/renderers/systems/WebGLSystem.js deleted file mode 100644 index d3d5b31..0000000 --- a/packages/core/src/renderers/systems/WebGLSystem.js +++ /dev/null @@ -1,41 +0,0 @@ -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this manager works for. - */ - constructor(renderer) - { - /** - * The renderer this manager works for. - * - * @member {PIXI.Renderer} - */ - this.renderer = renderer; - - this.renderer.runners.contextChange.add(this); - } - - /** - * Generic method called when there is a WebGL context change. - * - */ - contextChange() - { - // do some codes init! - } - - /** - * Generic destroy methods to be overridden by the subclass - * - */ - destroy() - { - this.renderer.runners.contextChange.remove(this); - this.renderer = null; - } -} diff --git a/packages/core/src/renderers/systems/filter/FilterSystem.js b/packages/core/src/renderers/systems/filter/FilterSystem.js deleted file mode 100644 index e615037..0000000 --- a/packages/core/src/renderers/systems/filter/FilterSystem.js +++ /dev/null @@ -1,431 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; - -import RenderTexture from '../../../textures/RenderTexture'; -import Quad from '../../utils/Quad'; -import QuadUv from '../../utils/QuadUv'; -import { Rectangle } from '@pixi/math'; -import * as filterTransforms from '../../filters/filterTransforms'; -import bitTwiddle from 'bit-twiddle'; -import UniformGroup from '../../../shader/UniformGroup'; -import { DRAW_MODES } from '../../../../../constants'; - -// -/** - * @ignore - * @class - */ -class FilterState -{ - /** - * - */ - constructor() - { - this.renderTexture = null; - this.sourceFrame = new Rectangle(); - this.destinationFrame = new Rectangle(); - this.filters = []; - this.target = null; - this.legacy = false; - this.resolution = 1; - } -} - -/** - * @class - * @memberof PIXI.systems - * @extends PIXI.systems.WebGLSystem - */ -export default class FilterSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - /** - * stores a bunch of PO2 textures used for filtering - * @type {Object} - */ - this.texturePool = {}; - - /** - * a pool for storing filter states, save us creating new ones each tick - * @type {Array} - */ - this.statePool = []; - - /** - * A very simple geometry used when drawing a filter effect to the screen - * @type {Quad} - */ - this.quad = new Quad(); - - this.quadUv = new QuadUv(); - - /** - * Temporary rect for maths - * @type {PIXI.Rectangle} - */ - this.tempRect = new Rectangle(); - - this.activeState = {}; - - /** - * this uniform group is attached to filter uniforms when used - * @type {UniformGroup} - */ - this.globalUniforms = new UniformGroup({ - sourceFrame: this.tempRect, - destinationFrame: this.tempRect, - - // legacy variables - filterArea: new Float32Array(4), - filterClamp: new Float32Array(4), - }, true); - } - - /** - * Adds a new filter to the System. - * - * @param {PIXI.DisplayObject} target - The target of the filter to render. - * @param {PIXI.Filter[]} filters - The filters to apply. - */ - push(target, filters) - { - const renderer = this.renderer; - const filterStack = this.renderer.renderTexture.defaultFilterStack; - const state = this.statePool.pop() || new FilterState(); - - let resolution = filters[0].resolution; - let padding = filters[0].padding; - let autoFit = filters[0].autoFit; - let legacy = filters[0].legacy; - - for (let i = 1; i < filters.length; i++) - { - const filter = filters[i]; - - // lets use the lowest resolution.. - resolution = Math.min(resolution, filter.resolution); - // and the largest amount of padding! - padding = Math.max(padding, filter.padding); - // only auto fit if all filters are autofit - autoFit = autoFit || filter.autoFit; - - legacy = legacy || filter.legacy; - } - - filterStack.push(state); - - state.resolution = resolution; - - state.legacy = legacy; - - // round to whole number based on resolution - // TODO move that to the shader too? - state.sourceFrame = target.filterArea || target.getBounds(true); - - state.sourceFrame.pad(padding); - - if (autoFit) - { - state.sourceFrame.fit(this.renderer.renderTexture.destinationFrame); - } - - state.sourceFrame.round(resolution); - - state.renderTexture = this.getPotFilterTexture(state.sourceFrame.width, state.sourceFrame.height, resolution); - state.filters = filters; - - state.destinationFrame.width = state.renderTexture.width; - state.destinationFrame.height = state.renderTexture.height; - - state.renderTexture.filterFrame = state.sourceFrame; - renderer.renderTexture.bind(state.renderTexture, state.sourceFrame);// /, state.destinationFrame); - renderer.renderTexture.clear(); - } - - /** - * Pops off the filter and applies it. - * - */ - pop() - { - const renderer = this.renderer; - const filterStack = renderer.renderTexture.defaultFilterStack; - const state = filterStack.pop(); - const filters = state.filters; - - this.activeState = state; - - const globalUniforms = this.globalUniforms.uniforms; - - globalUniforms.sourceFrame = state.sourceFrame; - globalUniforms.destinationFrame = state.destinationFrame; - globalUniforms.resolution = state.resolution; - - // only update the rect if its legacy.. - if (state.legacy) - { - const filterArea = globalUniforms.filterArea; - const filterClamp = globalUniforms.filterClamp; - - filterArea[0] = state.destinationFrame.width; - filterArea[1] = state.destinationFrame.height; - filterArea[2] = state.sourceFrame.x; - filterArea[3] = state.sourceFrame.y; - - filterClamp[0] = 0.5 / state.resolution / state.destinationFrame.width; - filterClamp[1] = 0.5 / state.resolution / state.destinationFrame.height; - filterClamp[2] = (state.sourceFrame.width - 0.5) / state.resolution / state.destinationFrame.width; - filterClamp[3] = (state.sourceFrame.height - 0.5) / state.resolution / state.destinationFrame.height; - } - - this.globalUniforms.update(); - - const lastState = filterStack[filterStack.length - 1]; - - if (filters.length === 1) - { - filters[0].apply(this, state.renderTexture, lastState.renderTexture, false, state); - - this.returnFilterTexture(state.renderTexture); - } - else - { - let flip = state.renderTexture; - let flop = this.getPotFilterTexture( - flip.width, - flip.height, - state.resolution - ); - - flop.filterFrame = flip.filterFrame; - - let i = 0; - - for (i = 0; i < filters.length - 1; ++i) - { - filters[i].apply(this, flip, flop, true, state); - - const t = flip; - - flip = flop; - flop = t; - } - - filters[i].apply(this, flip, lastState.renderTexture, false, state); - - this.returnFilterTexture(flip); - this.returnFilterTexture(flop); - } - - this.statePool.push(state); - } - - /** - * Draws a filter. - * - * @param {PIXI.Filter} filter - The filter to draw. - * @param {PIXI.RenderTarget} input - The input render target. - * @param {PIXI.RenderTarget} output - The target to output to. - * @param {boolean} clear - Should the output be cleared before rendering to it - */ - applyFilter(filter, input, output, clear) - { - const renderer = this.renderer; - - renderer.renderTexture.bind(output, output ? output.filterFrame : null); - - if (clear) - { - // gl.disable(gl.SCISSOR_TEST); - renderer.renderTexture.clear(); - // gl.enable(gl.SCISSOR_TEST); - } - - // set the uniforms.. - filter.uniforms.uSampler = input; - filter.uniforms.filterGlobals = this.globalUniforms; - - // TODO make it so that the order of this does not matter.. - // because it does at the moment cos of global uniforms. - // they need to get resynced - - renderer.state.setState(filter.state); - renderer.shader.bind(filter); - - if (filter.legacy) - { - this.quadUv.map(input._frame, input.filterFrame); - - renderer.geometry.bind(this.quadUv); - renderer.geometry.draw(DRAW_MODES.TRIANGLES); - } - else - { - renderer.geometry.bind(this.quad); - renderer.geometry.draw(DRAW_MODES.TRIANGLE_STRIP); - } - } - - /** - * Calculates the mapped matrix. - * - * TODO playing around here.. this is temporary - (will end up in the shader) - * this returns a matrix that will normalise map filter cords in the filter to screen space - * - * @param {PIXI.Matrix} outputMatrix - the matrix to output to. - * @return {PIXI.Matrix} The mapped matrix. - */ - calculateScreenSpaceMatrix(outputMatrix) - { - const currentState = this.activeState; - - return filterTransforms.calculateScreenSpaceMatrix( - outputMatrix, - currentState.sourceFrame, - currentState.destinationFrame - ); - } - - /** - * This will map the filter coord so that a texture can be used based on the transform of a sprite - * - * @param {PIXI.Matrix} outputMatrix - The matrix to output to. - * @param {PIXI.Sprite} sprite - The sprite to map to. - * @return {PIXI.Matrix} The mapped matrix. - */ - calculateSpriteMatrix(outputMatrix, sprite) - { - const currentState = this.activeState; - - return filterTransforms.calculateSpriteMatrix( - outputMatrix, - currentState.sourceFrame, - currentState.destinationFrame, - sprite - ); - } - - /** - * Destroys this Filter System. - * - * @param {boolean} [contextLost=false] context was lost, do not free shaders - * - */ - destroy(contextLost = false) - { - if (!contextLost) - { - this.emptyPool(); - } - else - { - this.texturePool = {}; - } - } - - /** - * Gets a Power-of-Two render texture. - * - * TODO move to a seperate class could be on renderer? - * also - could cause issue with multiple contexts? - * - * @private - * @param {WebGLRenderingContext} gl - The webgl rendering context - * @param {number} minWidth - The minimum width of the render target. - * @param {number} minHeight - The minimum height of the render target. - * @param {number} resolution - The resolution of the render target. - * @return {PIXI.RenderTarget} The new render target. - */ - getPotFilterTexture(minWidth, minHeight, resolution) - { - minWidth = bitTwiddle.nextPow2(minWidth); - minHeight = bitTwiddle.nextPow2(minHeight); - resolution = resolution || 1; - - const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); - - if (!this.texturePool[key]) - { - this.texturePool[key] = []; - } - - let renderTexture = this.texturePool[key].pop(); - - if (!renderTexture) - { - // temporary bypass cache.. - // internally - this will cause a texture to be bound.. - renderTexture = RenderTexture.create({ - width: minWidth, - height: minHeight, - resolution, - }); - } - - return renderTexture; - } - - /** - * Gets extra render texture to use inside current filter - * - * @param {number} resolution resolution of the renderTexture - * @returns {PIXI.RenderTexture} - */ - getFilterTexture(resolution) - { - const rt = this.activeState.renderTexture; - - const filterTexture = this.getPotFilterTexture(rt.width, rt.height, resolution || rt.baseTexture.resolution); - - filterTexture.filterFrame = rt.filterFrame; - - return filterTexture; - } - - /** - * Frees a render texture back into the pool. - * - * @param {PIXI.RenderTarget} renderTexture - The renderTarget to free - */ - returnFilterTexture(renderTexture) - { - renderTexture.filterFrame = null; - - const base = renderTexture.baseTexture; - - const minWidth = base.width; - const minHeight = base.height; - - const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); - - this.texturePool[key].push(renderTexture); - } - - /** - * Empties the texture pool. - * - */ - emptyPool() - { - for (const i in this.texturePool) - { - const textures = this.texturePool[i]; - - if (textures) - { - for (let j = 0; j < textures.length; j++) - { - textures[j].destroy(true); - } - } - } - - this.texturePool = {}; - } -} diff --git a/packages/core/src/renderers/systems/geometry/GLBuffer.js b/packages/core/src/renderers/systems/geometry/GLBuffer.js deleted file mode 100644 index 0477e8a..0000000 --- a/packages/core/src/renderers/systems/geometry/GLBuffer.js +++ /dev/null @@ -1,9 +0,0 @@ -export default class GLBuffer -{ - constructor(buffer) - { - this.buffer = buffer; - this.updateID = -1; - this.byteLength = -1; - } -} diff --git a/packages/core/src/renderers/systems/geometry/GeometrySystem.js b/packages/core/src/renderers/systems/geometry/GeometrySystem.js deleted file mode 100644 index 26c8e9e..0000000 --- a/packages/core/src/renderers/systems/geometry/GeometrySystem.js +++ /dev/null @@ -1,388 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; -import GLBuffer from './GLBuffer'; - -const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class GeometrySystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this._activeGeometry = null; - this._activeVao = null; - - this.hasVao = true; - this.hasInstance = true; - } - - /** - * Sets up the renderer context and necessary buffers. - * - * @private - */ - contextChange() - { - const gl = this.gl = this.renderer.gl; - - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - - // webgl2 - if (!gl.createVertexArray) - { - // webgl 1! - let nativeVaoExtension = this.renderer.context.extensions.vertexArrayObject; - - if (this.renderer.legacy) - { - nativeVaoExtension = null; - } - - if (nativeVaoExtension) - { - gl.createVertexArray = () => - nativeVaoExtension.createVertexArrayOES(); - - gl.bindVertexArray = (vao) => - nativeVaoExtension.bindVertexArrayOES(vao); - - gl.deleteVertexArray = (vao) => - nativeVaoExtension.deleteVertexArrayOES(vao); - } - else - { - this.hasVao = false; - gl.createVertexArray = () => - { - // empty - }; - - gl.bindVertexArray = () => - { - // empty - }; - - gl.deleteVertexArray = () => - { - // empty - }; - } - } - - if (!gl.vertexAttribDivisor) - { - const instanceExt = gl.getExtension('ANGLE_instanced_arrays'); - - if (instanceExt) - { - gl.vertexAttribDivisor = (a, b) => - instanceExt.vertexAttribDivisorANGLE(a, b); - - gl.drawElementsInstanced = (a, b, c, d, e) => - instanceExt.drawElementsInstancedANGLE(a, b, c, d, e); - - gl.drawArraysInstanced = (a, b, c, d) => - instanceExt.drawArraysInstancedANGLE(a, b, c, d); - } - else - { - this.hasInstance = false; - } - } - } - - /** - * Binds geometry so that is can be drawn. Creating a Vao if required - * @private - * @param {PIXI.Geometry} geometry instance of geometry to bind - */ - bind(geometry, shader) - { - shader = shader || this.renderer.shader.shader; - - const gl = this.gl; - - // not sure the best way to address this.. - // currently different shaders require different VAOs for the same geometry - // Still mulling over the best way to solve this one.. - // will likely need to modify the shader attribute locations at run time! - let vaos = geometry.glVertexArrayObjects[this.CONTEXT_UID]; - - if (!vaos) - { - geometry.glVertexArrayObjects[this.CONTEXT_UID] = vaos = {}; - } - - const vao = vaos[shader.program.id] || this.initGeometryVao(geometry, shader.program); - - this._activeGeometry = geometry; - - if (this._activeVao !== vao) - { - this._activeVao = vao; - - if (this.hasVao) - { - gl.bindVertexArray(vao); - } - else - { - this.activateVao(geometry, shader.program); - } - } - - // TODO - optimise later! - // don't need to loop through if nothing changed! - // maybe look to add an 'autoupdate' to geometry? - this.updateBuffers(); - } - - reset() - { - this.unbind(); - } - - updateBuffers() - { - const geometry = this._activeGeometry; - const gl = this.gl; - - for (let i = 0; i < geometry.buffers.length; i++) - { - const buffer = geometry.buffers[i]; - - const glBuffer = buffer._glBuffers[this.CONTEXT_UID]; - - if (buffer._updateID !== glBuffer.updateID) - { - glBuffer.updateID = buffer._updateID; - - // TODO can cache this on buffer! maybe added a getter / setter? - const type = buffer.index ? gl.ELEMENT_ARRAY_BUFFER : gl.ARRAY_BUFFER; - const drawType = buffer.static ? gl.STATIC_DRAW : gl.DYNAMIC_DRAW; - - gl.bindBuffer(type, glBuffer.buffer); - - if (glBuffer.byteLength >= buffer.data.byteLength) - { - // offset is always zero for now! - gl.bufferSubData(type, 0, buffer.data); - } - else - { - gl.bufferData(type, buffer.data, drawType); - } - } - } - } - - checkCompatability(geometry, program) - { - // geometry must have at least all the attributes that the shader requires. - const geometryAttributes = geometry.attributes; - const shaderAttributes = program.attributeData; - - for (const j in shaderAttributes) - { - if (!geometryAttributes[j]) - { - throw new Error(`shader and geometry incompatible, geometry missing the "${j}" attribute`); - } - } - } - - /** - * Creates a Vao with the same structure as the geometry and stores it on the geometry. - * @private - * @param {PIXI.Geometry} geometry instance of geometry to to generate Vao for - */ - initGeometryVao(geometry, program) - { - this.checkCompatability(geometry, program); - - const gl = this.gl; - const CONTEXT_UID = this.CONTEXT_UID; - const buffers = geometry.buffers; - const attributes = geometry.attributes; - - const tempStride = {}; - const tempStart = {}; - - for (const j in buffers) - { - tempStride[j] = 0; - tempStart[j] = 0; - } - - for (const j in attributes) - { - if (!attributes[j].size && program.attributeData[j]) - { - attributes[j].size = program.attributeData[j].size; - } - - tempStride[attributes[j].buffer] += attributes[j].size * byteSizeMap[attributes[j].type]; - } - - for (const j in attributes) - { - const attribute = attributes[j]; - const attribSize = attribute.size; - - if (attribute.stride === undefined) - { - if (tempStride[attribute.buffer] === attribSize * byteSizeMap[attribute.type]) - { - attribute.stride = 0; - } - else - { - attribute.stride = tempStride[attribute.buffer]; - } - } - - if (attribute.start === undefined) - { - attribute.start = tempStart[attribute.buffer]; - - tempStart[attribute.buffer] += attribSize * byteSizeMap[attribute.type]; - } - } - - // first update - and create the buffers! - // only create a gl buffer if it actually gets - for (let i = 0; i < buffers.length; i++) - { - const buffer = buffers[i]; - - if (!buffer._glBuffers[CONTEXT_UID]) - { - buffer._glBuffers[CONTEXT_UID] = new GLBuffer(gl.createBuffer()); - } - } - - // TODO - maybe make this a data object? - // lets wait to see if we need to first! - const vao = gl.createVertexArray(); - - gl.bindVertexArray(vao); - - this.activateVao(geometry, program); - - gl.bindVertexArray(null); - - // add it to the cache! - geometry.glVertexArrayObjects[this.CONTEXT_UID][program.id] = vao; - - return vao; - } - - activateVao(geometry, program) - { - const gl = this.gl; - const CONTEXT_UID = this.CONTEXT_UID; - const buffers = geometry.buffers; - const attributes = geometry.attributes; - - if (geometry.indexBuffer) - { - // first update the index buffer if we have one.. - gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, geometry.indexBuffer._glBuffers[CONTEXT_UID].buffer); - } - - let lastBuffer = null; - - // add a new one! - for (const j in attributes) - { - const attribute = attributes[j]; - const buffer = buffers[attribute.buffer]; - const glBuffer = buffer._glBuffers[CONTEXT_UID]; - - if (program.attributeData[j]) - { - if (lastBuffer !== glBuffer) - { - gl.bindBuffer(gl.ARRAY_BUFFER, glBuffer.buffer); - - lastBuffer = glBuffer; - } - - const location = program.attributeData[j].location; - - // TODO introduce state again - // we can optimise this for older devices that have no VAOs - gl.enableVertexAttribArray(location); - - gl.vertexAttribPointer(location, - attribute.size, - attribute.type || gl.FLOAT, - attribute.normalized, - attribute.stride, - attribute.start); - - if (attribute.instance) - { - // TODO calculate instance count based of this... - if (this.hasInstance) - { - gl.vertexAttribDivisor(location, 1); - } - else - { - throw new Error('geometry error, GPU Instancing is not supported on this device'); - } - } - } - } - } - - draw(type, size, start, instanceCount) - { - const gl = this.gl; - const geometry = this._activeGeometry; - - // TODO.. this should not change so maybe cache the function? - - if (geometry.indexBuffer) - { - if (geometry.instanced) - { - /* eslint-disable max-len */ - gl.drawElementsInstanced(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2, instanceCount || 1); - /* eslint-enable max-len */ - } - else - { - gl.drawElements(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2); - } - } - else - if (geometry.instanced) - { - // TODO need a better way to calculate size.. - gl.drawArraysInstanced(type, start, size || geometry.getSize(), instanceCount || 1); - } - else - { - gl.drawArrays(type, start, size || geometry.getSize()); - } - - return this; - } - - unbind() - { - this.gl.bindVertexArray(null); - this._activeVao = null; - this._activeGeometry = null; - } -} diff --git a/packages/core/src/renderers/systems/geometry/setVertexAttribArrays.js b/packages/core/src/renderers/systems/geometry/setVertexAttribArrays.js deleted file mode 100644 index 23b6959..0000000 --- a/packages/core/src/renderers/systems/geometry/setVertexAttribArrays.js +++ /dev/null @@ -1,54 +0,0 @@ -// var GL_MAP = {}; - -/** - * @param gl {WebGLRenderingContext} The current WebGL context - * @param attribs {*} - * @param state {*} - */ -export default function setVertexAttribArrays(gl, attribs, state) -{ - let i; - - if (state) - { - const tempAttribState = state.tempAttribState; - const attribState = state.attribState; - - for (i = 0; i < tempAttribState.length; i++) - { - tempAttribState[i] = false; - } - - // set the new attribs - for (i = 0; i < attribs.length; i++) - { - tempAttribState[attribs[i].attribute.location] = true; - } - - for (i = 0; i < attribState.length; i++) - { - if (attribState[i] !== tempAttribState[i]) - { - attribState[i] = tempAttribState[i]; - - if (state.attribState[i]) - { - gl.enableVertexAttribArray(i); - } - else - { - gl.disableVertexAttribArray(i); - } - } - } - } - else - { - for (i = 0; i < attribs.length; i++) - { - const attrib = attribs[i]; - - gl.enableVertexAttribArray(attrib.attribute.location); - } - } -} diff --git a/packages/core/src/renderers/systems/index.js b/packages/core/src/renderers/systems/index.js deleted file mode 100644 index c843f03..0000000 --- a/packages/core/src/renderers/systems/index.js +++ /dev/null @@ -1,6 +0,0 @@ -/** - * Systems are individual components to the Renderer pipeline. - * @namespace PIXI.systems - */ -export { default as WebGLSystem } from './WebGLSystem'; -export { default as FilterSystem } from './filter/FilterSystem'; diff --git a/packages/core/src/renderers/systems/shader/GLProgram.js b/packages/core/src/renderers/systems/shader/GLProgram.js deleted file mode 100644 index 92980ca..0000000 --- a/packages/core/src/renderers/systems/shader/GLProgram.js +++ /dev/null @@ -1,43 +0,0 @@ -/** - * Helper class to create a webGL Program - * - * @class - * @private - */ -export default class GLProgram -{ - constructor(program, uniformData) - { - /** - * The shader program - * - * @member {WebGLProgram} - */ - this.program = program; - - /** - * holds the uniform data which contains uniform locations - * and current uniform values used for caching and preventing undeed GPU commands - * @type {Object} - */ - this.uniformData = uniformData; - - /** - * uniformGroups holds the various upload functions for the shader. Each uniform group - * and program have a unique upload function generated. - * @type {Object} - */ - this.uniformGroups = {}; - } - - /** - * Destroys this program - * TODO - */ - destroy() - { - this.uniformData = null; - this.uniformGroups = null; - this.program = null; - } -} diff --git a/packages/core/src/renderers/systems/shader/ShaderSystem.js b/packages/core/src/renderers/systems/shader/ShaderSystem.js deleted file mode 100644 index 6cf57b6..0000000 --- a/packages/core/src/renderers/systems/shader/ShaderSystem.js +++ /dev/null @@ -1,170 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; -import GLProgram from './GLProgram'; -import generateUniformsSync from '../../../shader/generateUniformsSync'; -import defaultValue from '../../../shader/defaultValue'; -import compileProgram from './shader/compileProgram'; - -let UID = 0; - -/** - * Helper class to create a webGL Texture - * - * @class - * @memberof PIXI - */ -export default class ShaderSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - A reference to the current renderer - */ - constructor(renderer) - { - super(renderer); - - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = null; - - this.shader = null; - this.program = null; - - this.id = UID++; - } - - contextChange(gl) - { - this.gl = gl; - } - - /** - * Changes the current shader to the one given in parameter - * - * @param {PIXI.Shader} shader - the new shader - * @param {boolean} dontSync - false if the shader should automatically sync its uniforms. - * @returns {PIXI.glCore.glProgram} the glProgram that belongs to the shader. - */ - bind(shader, dontSync) - { - shader.uniforms.globals = this.renderer.globalUniforms; - - const program = shader.program; - const glProgram = program.glPrograms[this.renderer.CONTEXT_UID] || this.generateShader(shader); - - this.shader = shader; - - // TODO - some current pixi plugins bypass this.. so it not safe to use yet.. - if (this.program !== program) - { - this.program = program; - this.gl.useProgram(glProgram.program); - } - - if (!dontSync) - { - this.syncUniformGroup(shader.uniformGroup); - } - - return glProgram; - } - - /** - * Uploads the uniforms values to the currently bound shader. - * - * @param {object} uniforms - the uniforms valiues that be applied to the current shader - */ - setUniforms(uniforms) - { - const shader = this.shader.program; - const glProgram = shader.glPrograms[this.renderer.CONTEXT_UID]; - - shader.syncUniforms(glProgram.uniformData, uniforms, this.renderer); - } - - syncUniformGroup(group) - { - const glProgram = this.getglProgram(); - - if (!group.static || group.dirtyId !== glProgram.uniformGroups[group.id]) - { - glProgram.uniformGroups[group.id] = group.dirtyId; - const syncFunc = group.syncUniforms[this.shader.program.id] || this.createSyncGroups(group); - - syncFunc(glProgram.uniformData, group.uniforms, this.renderer); - } - } - - createSyncGroups(group) - { - group.syncUniforms[this.shader.program.id] = generateUniformsSync(group, this.shader.program.uniformData); - - return group.syncUniforms[this.shader.program.id]; - } - - /** - * Returns the underlying GLShade rof the currently bound shader. - * This can be handy for when you to have a little more control over the setting of your uniforms. - * - * @return {PIXI.glCore.Shader} the glProgram for the currently bound Shader for this context - */ - getglProgram() - { - if (this.shader) - { - return this.shader.program.glPrograms[this.renderer.CONTEXT_UID]; - } - - return null; - } - - /** - * Generates a glProgram verion of the Shader provided. - * - * @private - * @param {PIXI.Shader} shader the shader that the glProgram will be based on. - * @return {PIXI.glCore.glProgram} A shiney new glProgram - */ - generateShader(shader) - { - const gl = this.gl; - - const program = shader.program; - - const attribMap = {}; - - for (const i in program.attributeData) - { - attribMap[i] = program.attributeData[i].location; - } - - const shaderProgram = compileProgram(gl, program.vertexSrc, program.fragmentSrc, attribMap); - const uniformData = {}; - - for (const i in program.uniformData) - { - const data = program.uniformData[i]; - - uniformData[i] = { - location: gl.getUniformLocation(shaderProgram, i), - value: defaultValue(data.type, data.size), - }; - } - - const glProgram = new GLProgram(shaderProgram, uniformData); - - program.glPrograms[this.renderer.CONTEXT_UID] = glProgram; - - return glProgram; - } - - /** - * Destroys this System and removes all its textures - */ - destroy() - { - // TODO implement destroy method for ShaderSystem - this.destroyed = true; - } -} diff --git a/packages/core/src/renderers/systems/shader/shader/compileProgram.js b/packages/core/src/renderers/systems/shader/shader/compileProgram.js deleted file mode 100644 index 8c6ccb9..0000000 --- a/packages/core/src/renderers/systems/shader/shader/compileProgram.js +++ /dev/null @@ -1,78 +0,0 @@ -/** - * @method compileProgram - * @memberof PIXI.glCore.shader - * @param gl {WebGLRenderingContext} The current WebGL context {WebGLProgram} - * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. - * @param fragmentSrc {string|string[]} The fragment shader source as an array of strings. - * @param attributeLocations {Object} An attribute location map that lets you manually set the attribute locations - * @return {WebGLProgram} the shader program - */ -export default function compileProgram(gl, vertexSrc, fragmentSrc, attributeLocations) -{ - const glVertShader = compileShader(gl, gl.VERTEX_SHADER, vertexSrc); - const glFragShader = compileShader(gl, gl.FRAGMENT_SHADER, fragmentSrc); - - let program = gl.createProgram(); - - gl.attachShader(program, glVertShader); - gl.attachShader(program, glFragShader); - - // optionally, set the attributes manually for the program rather than letting WebGL decide.. - if (attributeLocations) - { - for (const i in attributeLocations) - { - gl.bindAttribLocation(program, attributeLocations[i], i); - } - } - - gl.linkProgram(program); - - // if linking fails, then log and cleanup - if (!gl.getProgramParameter(program, gl.LINK_STATUS)) - { - console.error('Pixi.js Error: Could not initialize shader.'); - console.error('gl.VALIDATE_STATUS', gl.getProgramParameter(program, gl.VALIDATE_STATUS)); - console.error('gl.getError()', gl.getError()); - - // if there is a program info log, log it - if (gl.getProgramInfoLog(program) !== '') - { - console.warn('Pixi.js Warning: gl.getProgramInfoLog()', gl.getProgramInfoLog(program)); - } - - gl.deleteProgram(program); - program = null; - } - - // clean up some shaders - gl.deleteShader(glVertShader); - gl.deleteShader(glFragShader); - - return program; -} - -/** - * @private - * @param gl {WebGLRenderingContext} The current WebGL context {WebGLProgram} - * @param type {Number} the type, can be either VERTEX_SHADER or FRAGMENT_SHADER - * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. - * @return {WebGLShader} the shader - */ -function compileShader(gl, type, src) -{ - const shader = gl.createShader(type); - - gl.shaderSource(shader, src); - gl.compileShader(shader); - - if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) - { - console.warn(src); - console.error(gl.getShaderInfoLog(shader)); - - return null; - } - - return shader; -} diff --git a/packages/core/src/renderers/systems/shader/shader/defaultValue.js b/packages/core/src/renderers/systems/shader/shader/defaultValue.js deleted file mode 100644 index 97290bd..0000000 --- a/packages/core/src/renderers/systems/shader/shader/defaultValue.js +++ /dev/null @@ -1,79 +0,0 @@ -/** - * @method defaultValue - * @memberof PIXI.glCore.shader - * @param type {String} Type of value - * @param size {Number} - */ -export default function defaultValue(type, size) -{ - switch (type) - { - case 'float': - return 0; - - case 'vec2': - return new Float32Array(2 * size); - - case 'vec3': - return new Float32Array(3 * size); - - case 'vec4': - return new Float32Array(4 * size); - - case 'int': - case 'sampler2D': - case 'sampler2DArray': - return 0; - - case 'ivec2': - return new Int32Array(2 * size); - - case 'ivec3': - return new Int32Array(3 * size); - - case 'ivec4': - return new Int32Array(4 * size); - - case 'bool': - return false; - - case 'bvec2': - - return booleanArray(2 * size); - - case 'bvec3': - return booleanArray(3 * size); - - case 'bvec4': - return booleanArray(4 * size); - - case 'mat2': - return new Float32Array([1, 0, - 0, 1]); - - case 'mat3': - return new Float32Array([1, 0, 0, - 0, 1, 0, - 0, 0, 1]); - - case 'mat4': - return new Float32Array([1, 0, 0, 0, - 0, 1, 0, 0, - 0, 0, 1, 0, - 0, 0, 0, 1]); - } - - return null; -} - -function booleanArray(size) -{ - const array = new Array(size); - - for (let i = 0; i < array.length; i++) - { - array[i] = false; - } - - return array; -} diff --git a/packages/core/src/renderers/systems/shader/shader/index.js b/packages/core/src/renderers/systems/shader/shader/index.js deleted file mode 100644 index ba7a317..0000000 --- a/packages/core/src/renderers/systems/shader/shader/index.js +++ /dev/null @@ -1,8 +0,0 @@ -/** - * @namespace PIXI.glCore.shader - */ -export { default as compileProgram } from './compileProgram'; -export { default as defaultValue } from './defaultValue'; -export { default as setPrecision } from './setPrecision'; -export { default as mapSize } from './mapSize'; -export { default as mapType } from './mapType'; diff --git a/packages/core/src/renderers/systems/shader/shader/mapSize.js b/packages/core/src/renderers/systems/shader/shader/mapSize.js deleted file mode 100644 index b681554..0000000 --- a/packages/core/src/renderers/systems/shader/shader/mapSize.js +++ /dev/null @@ -1,33 +0,0 @@ -const GLSL_TO_SIZE = { - float: 1, - vec2: 2, - vec3: 3, - vec4: 4, - - int: 1, - ivec2: 2, - ivec3: 3, - ivec4: 4, - - bool: 1, - bvec2: 2, - bvec3: 3, - bvec4: 4, - - mat2: 4, - mat3: 9, - mat4: 16, - - sampler2D: 1, -}; - -/** - * @method mapSize - * @memberof PIXI.glCore.shader - * @param type {String} - * @return {Number} - */ -export default function mapSize(type) -{ - return GLSL_TO_SIZE[type]; -} diff --git a/packages/core/src/renderers/systems/shader/shader/mapType.js b/packages/core/src/renderers/systems/shader/shader/mapType.js deleted file mode 100644 index d288b7c..0000000 --- a/packages/core/src/renderers/systems/shader/shader/mapType.js +++ /dev/null @@ -1,45 +0,0 @@ -let GL_TABLE = null; - -const GL_TO_GLSL_TYPES = { - FLOAT: 'float', - FLOAT_VEC2: 'vec2', - FLOAT_VEC3: 'vec3', - FLOAT_VEC4: 'vec4', - - INT: 'int', - INT_VEC2: 'ivec2', - INT_VEC3: 'ivec3', - INT_VEC4: 'ivec4', - - BOOL: 'bool', - BOOL_VEC2: 'bvec2', - BOOL_VEC3: 'bvec3', - BOOL_VEC4: 'bvec4', - - FLOAT_MAT2: 'mat2', - FLOAT_MAT3: 'mat3', - FLOAT_MAT4: 'mat4', - - SAMPLER_2D: 'sampler2D', - SAMPLER_CUBE: 'samplerCube', - SAMPLER_2D_ARRAY: 'sampler2DArray', -}; - -export default function mapType(gl, type) -{ - if (!GL_TABLE) - { - const typeNames = Object.keys(GL_TO_GLSL_TYPES); - - GL_TABLE = {}; - - for (let i = 0; i < typeNames.length; ++i) - { - const tn = typeNames[i]; - - GL_TABLE[gl[tn]] = GL_TO_GLSL_TYPES[tn]; - } - } - - return GL_TABLE[type]; -} diff --git a/packages/core/src/AbstractRenderer.js b/packages/core/src/AbstractRenderer.js new file mode 100644 index 0000000..64b2eb1 --- /dev/null +++ b/packages/core/src/AbstractRenderer.js @@ -0,0 +1,353 @@ +import { hex2string, hex2rgb } from '@pixi/utils'; +import { Matrix, Rectangle } from '@pixi/math'; +import { RENDERER_TYPE } from '@pixi/constants'; +import { settings } from '@pixi/settings'; +import { Container } from '@pixi/display'; +import RenderTexture from './renderTexture/RenderTexture'; +import EventEmitter from 'eventemitter3'; + +const tempMatrix = new Matrix(); + +/** + * 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 EventEmitter + * @memberof PIXI + */ +export default class AbstractRenderer extends EventEmitter +{ + // eslint-disable-next-line valid-jsdoc + /** + * @param {string} system - The name of the system this renderer is for. + * @param {object} [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.transparent=false] - If the render view is transparent, default false + * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false + * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. The + * resolution of the renderer retina would be 2. + * @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 {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when rendering, + * stopping pixel interpolation. + */ + constructor(system, options, arg2, arg3) + { + super(); + + // Support for constructor(system, screenWidth, screenHeight, options) + if (typeof options === 'number') + { + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + }, arg3); + } + + // 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 = RENDERER_TYPE.UNKNOWN; + + /** + * Measurements of the screen. (0, 0, screenWidth, screenHeight) + * + * Its safe to use as filterArea or hitArea for 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 1 + */ + this.resolution = options.resolution || settings.RESOLUTION; + + /** + * Whether the render view is transparent + * + * @member {boolean} + */ + this.transparent = options.transparent; + + /** + * Whether css dimensions of canvas view should be resized to screen dimensions automatically + * + * @member {boolean} + */ + this.autoResize = options.autoResize || false; + + /** + * Tracks the blend modes useful for this renderer. + * + * @member {object} + */ + this.blendModes = null; + + /** + * 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; + + /** + * If true PixiJS will Math.floor() x/y values when rendering, stopping pixel interpolation. + * Handy for crisp pixel art and speed on legacy devices. + * + * @member {boolean} + */ + this.roundPixels = options.roundPixels; + + /** + * The background color as a number. + * + * @member {number} + * @private + */ + this._backgroundColor = 0x000000; + + /** + * The background color as an [R, G, B] array. + * + * @member {number[]} + * @private + */ + this._backgroundColorRgba = [0, 0, 0, 0]; + + /** + * The background color as a string. + * + * @member {string} + * @private + */ + this._backgroundColorString = '#000000'; + + this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter + + /** + * This temporary display object used as the parent of the currently being rendered item + * + * @member {PIXI.DisplayObject} + * @private + */ + this._tempDisplayObjectParent = new Container(); + + /** + * The last root object that the renderer tried to render. + * + * @member {PIXI.DisplayObject} + * @private + */ + this._lastObjectRendered = this._tempDisplayObjectParent; + + /** + * Collection of plugins. + * @readonly + * @member {object} + */ + this.plugins = {}; + } + + /** + * Initialize the plugins. + * + * @protected + * @param {object} staticMap - The dictionary of staticly saved plugins. + */ + initPlugins(staticMap) + { + 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() + { + return this.view.width; + } + + /** + * Same as view.height, actual number of pixels in the canvas by vertical + * + * @member {number} + * @readonly + * @default 600 + */ + get height() + { + return this.view.height; + } + + /** + * Resizes the screen and canvas to the specified width and height + * Canvas dimensions are multiplied by resolution + * + * @param {number} screenWidth - the new width of the screen + * @param {number} screenHeight - the new height of the screen + */ + resize(screenWidth, screenHeight) + { + this.screen.width = screenWidth; + this.screen.height = screenHeight; + + this.view.width = screenWidth * this.resolution; + this.view.height = screenHeight * this.resolution; + + if (this.autoResize) + { + this.view.style.width = `${screenWidth}px`; + this.view.style.height = `${screenHeight}px`; + } + } + + /** + * 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 {PIXI.DisplayObject} displayObject - The displayObject the object will be generated from + * @param {number} scaleMode - Should be one of the scaleMode consts + * @param {number} resolution - The resolution / device pixel ratio of the texture being generated + * @param {PIXI.Rectangle} [region] - The region of the displayObject, that shall be rendered, + * if no region is specified, defaults to the local bounds of the displayObject. + * @return {PIXI.Texture} a texture of the graphics object + */ + generateTexture(displayObject, scaleMode, resolution, region) + { + region = region || displayObject.getLocalBounds(); + + // 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(region.width | 0, region.height | 0, scaleMode, resolution); + + tempMatrix.tx = -region.x; + tempMatrix.ty = -region.y; + + this.render(displayObject, renderTexture, false, tempMatrix, true); + + return renderTexture; + } + + /** + * Removes everything from the renderer and optionally removes the Canvas DOM element. + * + * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. + */ + destroy(removeView) + { + 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); + } + + this.plugins = null; + + this.type = RENDERER_TYPE.UNKNOWN; + + this.view = null; + + this.screen = null; + + this.resolution = 0; + + this.transparent = false; + + this.autoResize = false; + + this.blendModes = null; + + this.options = null; + + this.preserveDrawingBuffer = false; + this.clearBeforeRender = false; + + this.roundPixels = false; + + this._backgroundColor = 0; + this._backgroundColorRgba = null; + this._backgroundColorString = null; + + this._tempDisplayObjectParent = null; + this._lastObjectRendered = null; + } + + /** + * The background color to fill if not transparent + * + * @member {number} + */ + get backgroundColor() + { + return this._backgroundColor; + } + + set backgroundColor(value) // eslint-disable-line require-jsdoc + { + this._backgroundColor = value; + this._backgroundColorString = hex2string(value); + hex2rgb(value, this._backgroundColorRgba); + } +} diff --git a/packages/core/src/Renderer.js b/packages/core/src/Renderer.js new file mode 100644 index 0000000..2227e78 --- /dev/null +++ b/packages/core/src/Renderer.js @@ -0,0 +1,338 @@ +import AbstractRenderer from './AbstractRenderer'; +import { sayHello } from '@pixi/utils'; +import MaskSystem from './mask/MaskSystem'; +import StencilSystem from './mask/StencilSystem'; +import FilterSystem from './filters/FilterSystem'; +import FramebufferSystem from './framebuffer/FramebufferSystem'; +import RenderTextureSystem from './renderTexture/RenderTextureSystem'; +import TextureSystem from './textures/TextureSystem'; +import ProjectionSystem from './projection/ProjectionSystem'; +import StateSystem from './state/StateSystem'; +import GeometrySystem from './geometry/GeometrySystem'; +import ShaderSystem from './shader/ShaderSystem'; +import ContextSystem from './context/ContextSystem'; +import BatchSystem from './batch/BatchSystem'; +import TextureGCSystem from './textures/TextureGCSystem'; +import { RENDERER_TYPE } from '@pixi/constants'; +import UniformGroup from './shader/UniformGroup'; +import { Matrix } from '@pixi/math'; +import Runner from 'mini-runner'; + +/** + * 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 Render works by automatically managing webGLBatchs. + * 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 :) + * + * @class + * @memberof PIXI + * @extends PIXI.AbstractRenderer + */ +export default class Renderer extends AbstractRenderer +{ + // eslint-disable-next-line valid-jsdoc + /** + * + * @param {object} [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.transparent=false] - If the render view is transparent, default false + * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false + * @param {boolean} [options.antialias=false] - sets antialias. If not available natively then FXAA + * antialiasing is used + * @param {boolean} [options.forceFXAA=false] - forces FXAA antialiasing to be used over native. + * FXAA is faster, but may not always look as great + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. + * The resolution of the renderer retina would be 2. + * @param {boolean} [options.clearBeforeRender=true] - This sets if the renderer will clear + * the canvas or not before the new render pass. If you wish to set this to false, you *must* set + * preserveDrawingBuffer to `true`. + * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, + * enable this if you need to call toDataUrl on the webgl context. + * @param {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when + * rendering, stopping pixel interpolation. + * @param {number} [options.backgroundColor=0x000000] - The background color of the rendered area + * (shown if not transparent). + * @param {boolean} [options.legacy=false] - If true PixiJS will aim to ensure compatibility + * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. + * @param {string} [options.powerPreference] - Parameter passed to webgl context, set to "high-performance" + * for devices with dual graphics card + */ + constructor(options, arg2, arg3) + { + super('WebGL', options, arg2, arg3); + + /** + * The type of this renderer as a standardised const + * + * @member {number} + * @see PIXI.RENDERER_TYPE + */ + this.type = RENDERER_TYPE.WEBGL; + + // this will be set by the contextSystem (this.context) + this.gl = null; + this.CONTEXT_UID = 0; + this.legacy = !!options.legacy; + + // TODO legacy! + + // runners! + this.runners = { + destroy: new Runner('destroy'), + contextChange: new Runner('contextChange', 1), + reset: new Runner('reset'), + update: new Runner('update'), + postrender: new Runner('postrender'), + prerender: new Runner('prerender'), + resize: new Runner('resize', 2), + }; + + 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(GeometrySystem, 'geometry') + .addSystem(FramebufferSystem, 'framebuffer') + .addSystem(StencilSystem, 'stencil') + .addSystem(ProjectionSystem, 'projection') + .addSystem(TextureGCSystem, 'textureGC') + .addSystem(FilterSystem, 'filter') + .addSystem(RenderTextureSystem, 'renderTexture') + .addSystem(BatchSystem, 'batch'); + + this.initPlugins(Renderer.__plugins); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ + if (options.context) + { + this.context.initFromContext(options.context); + } + else + { + this.context.initFromOptions({ + alpha: this.transparent, + antialias: options.antialias, + premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', + stencil: true, + 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); + } + + /** + * Add a new system to the renderer. + * @param {class} ClassRef - Class reference + * @param {string} [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. + * @return {PIXI.Renderer} Return instance of renderer + */ + addSystem(ClassRef, name) + { + if (!name) + { + name = ClassRef.name; + } + + // TODO - read name from class.name.. + + /* + if(name.includes('System')) + { + name = name.replace('System', ''); + name = name.charAt(0).toLowerCase() + name.slice(1); + } + */ + + const system = new ClassRef(this); + + if (this[name]) + { + throw new Error(`Whoops! ${name} is already a manger`); + } + + this[name] = system; + + for (const i in this.runners) + { + this.runners[i].add(system); + } + + return this; + + /** + * 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. + */ + } + + /** + * Renders the object to its webGL view + * + * @param {PIXI.DisplayObject} displayObject - the object to be rendered + * @param {PIXI.RenderTexture} renderTexture - The render texture to render to. + * @param {boolean} [clear] - Should the canvas be cleared before the new render + * @param {PIXI.Transform} [transform] - A transform to apply to the render texture before rendering. + * @param {boolean} [skipUpdateTransform] - Should we skip the update transform pass? + */ + render(displayObject, renderTexture, clear, transform, skipUpdateTransform) + { + // can be handy to know! + this.renderingToScreen = !renderTexture; + + this.runners.prerender.run(); + this.emit('prerender'); + + // 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.parent; + + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = 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.run(); + + this.emit('postrender'); + } + + /** + * Resizes the webGL view to the specified width and height. + * + * @param {number} screenWidth - the new width of the screen + * @param {number} screenHeight - the new height of the screen + */ + resize(screenWidth, screenHeight) + { + AbstractRenderer.prototype.resize.call(this, screenWidth, screenHeight); + + this.runners.resize.run(screenWidth, screenHeight); + } + + /** + * Resets the WebGL state so you can render things however you fancy! + * + * @return {PIXI.Renderer} Returns itself. + */ + reset() + { + this.runners.reset.run(); + + return this; + } + + clear() + { + this.framebuffer.bind(); + this.framebuffer.clear(); + } + + /** + * Removes everything from the renderer (event listeners, spritebatch, etc...) + * + * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. + * See: https://github.com/pixijs/pixi.js/issues/2233 + */ + destroy(removeView) + { + this.runners.destroy.run(); + + // call base destroy + super.destroy(removeView); + + // TODO nullify all the managers.. + this.gl = null; + } + + /** + * 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 + * builds and excluding plugins. + * @name PIXI.Renderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. + */ + + /** + * Adds a plugin to the renderer. + * + * @method + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + static registerPlugin(pluginName, ctor) + { + Renderer.__plugins = Renderer.__plugins || {}; + Renderer.__plugins[pluginName] = ctor; + } +} diff --git a/packages/core/src/System.js b/packages/core/src/System.js new file mode 100644 index 0000000..05a10c8 --- /dev/null +++ b/packages/core/src/System.js @@ -0,0 +1,42 @@ +/** + * System is a base class used for extending systems used by the {@link PIXI.Renderer} + * @see PIXI.Renderer#addSystem + * @class + * @memberof PIXI + */ +export default class System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + /** + * The renderer this manager works for. + * + * @member {PIXI.Renderer} + */ + this.renderer = renderer; + + this.renderer.runners.contextChange.add(this); + } + + /** + * Generic method called when there is a WebGL context change. + * + */ + contextChange() + { + // do some codes init! + } + + /** + * Generic destroy methods to be overridden by the subclass + * + */ + destroy() + { + this.renderer.runners.contextChange.remove(this); + this.renderer = null; + } +} diff --git a/packages/core/src/batch/BatchSystem.js b/packages/core/src/batch/BatchSystem.js new file mode 100644 index 0000000..b04c249 --- /dev/null +++ b/packages/core/src/batch/BatchSystem.js @@ -0,0 +1,68 @@ +import System from '../System'; +import ObjectRenderer from './ObjectRenderer'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class BatchSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + super(renderer); + + /** + * An empty renderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.emptyRenderer = new ObjectRenderer(renderer); + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.currentRenderer = this.emptyRenderer; + } + + /** + * Changes the current renderer to the one given in parameter + * + * @param {PIXI.ObjectRenderer} objectRenderer - The object renderer to use. + */ + setObjectRenderer(objectRenderer) + { + if (this.currentRenderer === objectRenderer) + { + return; + } + + this.currentRenderer.stop(); + this.currentRenderer = objectRenderer; + + this.renderer.state.setState(objectRenderer.state); + + this.currentRenderer.start(); + } + + /** + * This should be called if you wish to do some custom rendering + * It will basically render anything that may be batched up such as sprites + * + */ + flush() + { + this.setObjectRenderer(this.emptyRenderer); + } + + reset() + { + this.setObjectRenderer(this.emptyRenderer); + } +} diff --git a/packages/core/src/batch/ObjectRenderer.js b/packages/core/src/batch/ObjectRenderer.js new file mode 100644 index 0000000..0c851a9 --- /dev/null +++ b/packages/core/src/batch/ObjectRenderer.js @@ -0,0 +1,48 @@ +import System from '../System'; + +/** + * Base for a common object renderer that can be used as a system renderer plugin. + * + * @class + * @extends PIXI.System + * @memberof PIXI + */ +export default class ObjectRenderer extends System +{ + /** + * Starts the renderer and sets the shader + * + */ + start() + { + // set the shader.. + } + + /** + * Stops the renderer + * + */ + stop() + { + this.flush(); + } + + /** + * Stub method for rendering content and emptying the current batch. + * + */ + flush() + { + // flush! + } + + /** + * Renders an object + * + * @param {PIXI.DisplayObject} object - The object to render. + */ + render(object) // eslint-disable-line no-unused-vars + { + // render the object + } +} diff --git a/packages/core/src/context/ContextSystem.js b/packages/core/src/context/ContextSystem.js new file mode 100644 index 0000000..301aab4 --- /dev/null +++ b/packages/core/src/context/ContextSystem.js @@ -0,0 +1,186 @@ +import System from '../System'; +import { settings } from '@pixi/settings'; + +let CONTEXT_UID = 0; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class ContextSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.webGLVersion = 1; + + this.handleContextLost = this.handleContextLost.bind(this); + this.handleContextRestored = this.handleContextRestored.bind(this); + + this.extensions = {}; + + renderer.view.addEventListener('webglcontextlost', this.handleContextLost, false); + renderer.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + } + + get isLost() + { + return (!this.gl || this.gl.isContextLost()); + } + + contextChange(gl) + { + this.gl = gl; + + // restore a context if it was previously lost + if (gl.isContextLost() && gl.getExtension('WEBGL_lose_context')) + { + gl.getExtension('WEBGL_lose_context').restoreContext(); + } + } + + initFromContext(gl) + { + this.gl = gl; + this.validateContext(gl); + this.renderer.gl = gl; + this.renderer.CONTEXT_UID = CONTEXT_UID++; + this.renderer.runners.contextChange.run(gl); + } + + initFromOptions(options) + { + const gl = this.createContext(this.renderer.view, options); + + this.initFromContext(gl); + } + + /** + * Helper class to create a webGL Context + * + * @class + * @memberof PIXI.glCore + * @param canvas {HTMLCanvasElement} the canvas element that we will get the context from + * @param options {Object} An options object that gets passed in to the canvas element containing the context attributes, + * see https://developer.mozilla.org/en/docs/Web/API/HTMLCanvasElement/getContext for the options available + * @return {WebGLRenderingContext} the WebGL context + */ + createContext(canvas, options) + { + let gl; + + if (settings.PREFER_WEBGL_2) + { + gl = canvas.getContext('webgl2', options); + } + + if (gl) + { + this.webGLVersion = 2; + } + else + { + this.webGLVersion = 1; + + gl = canvas.getContext('webgl', options) + || canvas.getContext('experimental-webgl', options); + + if (!gl) + { + // fail, not able to get a context + throw new Error('This browser does not support webGL. Try using the canvas renderer'); + } + } + + this.gl = gl; + + this.getExtensions(); + + return gl; + } + + getExtensions() + { + // time to set up default etensions that pixi uses.. + const gl = this.gl; + const extensions = this.extensions; + + if (this.webGLVersion === 1) + { + extensions.drawBuffers = gl.getExtension('WEBGL_draw_buffers'); + extensions.depthTexture = gl.getExtension('WEBKIT_WEBGL_depth_texture'); + extensions.floatTexture = gl.getExtension('OES_texture_float'); + extensions.loseContext = gl.getExtension('WEBGL_lose_context'); + + extensions.vertexArrayObject = gl.getExtension('OES_vertex_array_object') + || gl.getExtension('MOZ_OES_vertex_array_object') + || gl.getExtension('WEBKIT_OES_vertex_array_object'); + } + + // we don't use any specific WebGL 2 ones yet! + } + + /** + * Handles a lost webgl context + * + * @private + * @param {WebGLContextEvent} event - The context lost event. + */ + handleContextLost(event) + { + event.preventDefault(); + } + + /** + * Handles a restored webgl context + * + * @private + */ + handleContextRestored() + { + this.renderer.runners.contextChange.run(this.gl); + } + + destroy() + { + const view = this.renderer.view; + + // remove listeners + view.removeEventListener('webglcontextlost', this.handleContextLost); + view.removeEventListener('webglcontextrestored', this.handleContextRestored); + + this.gl.useProgram(null); + + if (this.extensions.loseContext) + { + this.extensions.loseContext.loseContext(); + } + } + + postrender() + { + this.gl.flush(); + } + + validateContext(gl) + { + const attributes = gl.getContextAttributes(); + + // this is going to be fairly simple for now.. but at least we have room to grow! + if (!attributes.stencil) + { + /* eslint-disable max-len */ + + /* eslint-disable no-console */ + console.warn('Provided WebGL context does not have a stencil buffer, masks may not render correctly'); + /* eslint-enable no-console */ + + /* eslint-enable max-len */ + } + } +} diff --git a/packages/core/src/filters/Filter.js b/packages/core/src/filters/Filter.js new file mode 100644 index 0000000..9d8cad8 --- /dev/null +++ b/packages/core/src/filters/Filter.js @@ -0,0 +1,140 @@ +import Shader from '../shader/Shader'; +import Program from '../shader/Program'; +import State from '../state/State'; +import { settings } from '@pixi/settings'; +// import extractUniformsFromSrc from './extractUniformsFromSrc'; +import defaultVertex from './defaultFilter.vert'; +import defaultFragment from './defaultFilter.frag'; + +// let math = require('../../math'); +/** + * @class + * @memberof PIXI + * @extends PIXI.Shader + */ +export default class Filter extends Shader +{ + /** + * @param {string} [vertexSrc] - The source of the vertex shader. + * @param {string} [fragmentSrc] - The source of the fragment shader. + * @param {object} [uniforms] - Custom uniforms to use to augment the built-in ones. + */ + constructor(vertexSrc, fragmentSrc, uniforms) + { + const program = Program.from(vertexSrc, fragmentSrc); + + super(program, uniforms); + + /** + * The padding of the filter. Some filters require extra space to breath such as a blur. + * Increasing this will add extra width and height to the bounds of the object that the + * filter is applied to. + * + * @member {number} + */ + this.padding = 0; + + /** + * The resolution of the filter. Setting this to be lower will lower the quality but + * increase the performance of the filter. + * + * @member {number} + */ + this.resolution = settings.RESOLUTION; + + /** + * If enabled is true the filter is applied, if false it will not. + * + * @member {boolean} + */ + this.enabled = true; + + /** + * If enabled, PixiJS will fit the filter area into boundaries for better performance. + * Switch it off if it does not work for specific shader. + * + * @member {boolean} + */ + this.autoFit = true; + + /** + * Legacy filters use position and uvs from attributes + * @member {boolean} + * @readonly + */ + this.legacy = !!this.program.attributeData.aTextureCoord; + + /** + * the webGL state the filter requires to render + * @member {PIXI.State} + */ + this.state = new State(); + } + + /** + * Applies the filter + * + * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + * @param {boolean} clear - Should the output be cleared before rendering to it + * @param {object} [currentState] - It's current state of filter. + * There are some useful properties in the currentState : + * target, filters, sourceFrame, destinationFrame, renderTarget, resolution + */ + apply(filterManager, input, output, clear, currentState, derp) // eslint-disable-line no-unused-vars + { + // do as you please! + + filterManager.applyFilter(this, input, output, clear, currentState, derp); + + // or just do a regular render.. + } + + /** + * Sets the blendmode of the filter + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL + */ + get blendMode() + { + return this.state.blendMode; + } + + set blendMode(value) // eslint-disable-line require-jsdoc + { + this.state.blendMode = value; + } + + /** + * The default vertex shader source + * + * @static + * @constant + */ + static get defaultVertexSrc() + { + return defaultVertex; + } + + /** + * The default fragment shader source + * + * @static + * @constant + */ + static get defaultFragmentSrc() + { + return defaultFragment; + } +} + +/** + * Used for caching shader IDs + * + * @static + * @private + */ +Filter.SOURCE_KEY_MAP = {}; + diff --git a/packages/core/src/filters/FilterSystem.js b/packages/core/src/filters/FilterSystem.js new file mode 100644 index 0000000..3174318 --- /dev/null +++ b/packages/core/src/filters/FilterSystem.js @@ -0,0 +1,431 @@ +import System from '../System'; + +import RenderTexture from '../renderTexture/RenderTexture'; +import Quad from '../utils/Quad'; +import QuadUv from '../utils/QuadUv'; +import { Rectangle } from '@pixi/math'; +import * as filterTransforms from './filterTransforms'; +import bitTwiddle from 'bit-twiddle'; +import UniformGroup from '../shader/UniformGroup'; +import { DRAW_MODES } from '@pixi/constants'; + +// +/** + * @ignore + * @class + */ +class FilterState +{ + /** + * + */ + constructor() + { + this.renderTexture = null; + this.sourceFrame = new Rectangle(); + this.destinationFrame = new Rectangle(); + this.filters = []; + this.target = null; + this.legacy = false; + this.resolution = 1; + } +} + +/** + * @class + * @memberof PIXI.systems + * @extends PIXI.System + */ +export default class FilterSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + /** + * stores a bunch of PO2 textures used for filtering + * @type {Object} + */ + this.texturePool = {}; + + /** + * a pool for storing filter states, save us creating new ones each tick + * @type {Array} + */ + this.statePool = []; + + /** + * A very simple geometry used when drawing a filter effect to the screen + * @type {Quad} + */ + this.quad = new Quad(); + + this.quadUv = new QuadUv(); + + /** + * Temporary rect for maths + * @type {PIXI.Rectangle} + */ + this.tempRect = new Rectangle(); + + this.activeState = {}; + + /** + * this uniform group is attached to filter uniforms when used + * @type {UniformGroup} + */ + this.globalUniforms = new UniformGroup({ + sourceFrame: this.tempRect, + destinationFrame: this.tempRect, + + // legacy variables + filterArea: new Float32Array(4), + filterClamp: new Float32Array(4), + }, true); + } + + /** + * Adds a new filter to the System. + * + * @param {PIXI.DisplayObject} target - The target of the filter to render. + * @param {PIXI.Filter[]} filters - The filters to apply. + */ + push(target, filters) + { + const renderer = this.renderer; + const filterStack = this.renderer.renderTexture.defaultFilterStack; + const state = this.statePool.pop() || new FilterState(); + + let resolution = filters[0].resolution; + let padding = filters[0].padding; + let autoFit = filters[0].autoFit; + let legacy = filters[0].legacy; + + for (let i = 1; i < filters.length; i++) + { + const filter = filters[i]; + + // lets use the lowest resolution.. + resolution = Math.min(resolution, filter.resolution); + // and the largest amount of padding! + padding = Math.max(padding, filter.padding); + // only auto fit if all filters are autofit + autoFit = autoFit || filter.autoFit; + + legacy = legacy || filter.legacy; + } + + filterStack.push(state); + + state.resolution = resolution; + + state.legacy = legacy; + + // round to whole number based on resolution + // TODO move that to the shader too? + state.sourceFrame = target.filterArea || target.getBounds(true); + + state.sourceFrame.pad(padding); + + if (autoFit) + { + state.sourceFrame.fit(this.renderer.renderTexture.destinationFrame); + } + + state.sourceFrame.round(resolution); + + state.renderTexture = this.getPotFilterTexture(state.sourceFrame.width, state.sourceFrame.height, resolution); + state.filters = filters; + + state.destinationFrame.width = state.renderTexture.width; + state.destinationFrame.height = state.renderTexture.height; + + state.renderTexture.filterFrame = state.sourceFrame; + renderer.renderTexture.bind(state.renderTexture, state.sourceFrame);// /, state.destinationFrame); + renderer.renderTexture.clear(); + } + + /** + * Pops off the filter and applies it. + * + */ + pop() + { + const renderer = this.renderer; + const filterStack = renderer.renderTexture.defaultFilterStack; + const state = filterStack.pop(); + const filters = state.filters; + + this.activeState = state; + + const globalUniforms = this.globalUniforms.uniforms; + + globalUniforms.sourceFrame = state.sourceFrame; + globalUniforms.destinationFrame = state.destinationFrame; + globalUniforms.resolution = state.resolution; + + // only update the rect if its legacy.. + if (state.legacy) + { + const filterArea = globalUniforms.filterArea; + const filterClamp = globalUniforms.filterClamp; + + filterArea[0] = state.destinationFrame.width; + filterArea[1] = state.destinationFrame.height; + filterArea[2] = state.sourceFrame.x; + filterArea[3] = state.sourceFrame.y; + + filterClamp[0] = 0.5 / state.resolution / state.destinationFrame.width; + filterClamp[1] = 0.5 / state.resolution / state.destinationFrame.height; + filterClamp[2] = (state.sourceFrame.width - 0.5) / state.resolution / state.destinationFrame.width; + filterClamp[3] = (state.sourceFrame.height - 0.5) / state.resolution / state.destinationFrame.height; + } + + this.globalUniforms.update(); + + const lastState = filterStack[filterStack.length - 1]; + + if (filters.length === 1) + { + filters[0].apply(this, state.renderTexture, lastState.renderTexture, false, state); + + this.returnFilterTexture(state.renderTexture); + } + else + { + let flip = state.renderTexture; + let flop = this.getPotFilterTexture( + flip.width, + flip.height, + state.resolution + ); + + flop.filterFrame = flip.filterFrame; + + let i = 0; + + for (i = 0; i < filters.length - 1; ++i) + { + filters[i].apply(this, flip, flop, true, state); + + const t = flip; + + flip = flop; + flop = t; + } + + filters[i].apply(this, flip, lastState.renderTexture, false, state); + + this.returnFilterTexture(flip); + this.returnFilterTexture(flop); + } + + this.statePool.push(state); + } + + /** + * Draws a filter. + * + * @param {PIXI.Filter} filter - The filter to draw. + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + * @param {boolean} clear - Should the output be cleared before rendering to it + */ + applyFilter(filter, input, output, clear) + { + const renderer = this.renderer; + + renderer.renderTexture.bind(output, output ? output.filterFrame : null); + + if (clear) + { + // gl.disable(gl.SCISSOR_TEST); + renderer.renderTexture.clear(); + // gl.enable(gl.SCISSOR_TEST); + } + + // set the uniforms.. + filter.uniforms.uSampler = input; + filter.uniforms.filterGlobals = this.globalUniforms; + + // TODO make it so that the order of this does not matter.. + // because it does at the moment cos of global uniforms. + // they need to get resynced + + renderer.state.setState(filter.state); + renderer.shader.bind(filter); + + if (filter.legacy) + { + this.quadUv.map(input._frame, input.filterFrame); + + renderer.geometry.bind(this.quadUv); + renderer.geometry.draw(DRAW_MODES.TRIANGLES); + } + else + { + renderer.geometry.bind(this.quad); + renderer.geometry.draw(DRAW_MODES.TRIANGLE_STRIP); + } + } + + /** + * Calculates the mapped matrix. + * + * TODO playing around here.. this is temporary - (will end up in the shader) + * this returns a matrix that will normalise map filter cords in the filter to screen space + * + * @param {PIXI.Matrix} outputMatrix - the matrix to output to. + * @return {PIXI.Matrix} The mapped matrix. + */ + calculateScreenSpaceMatrix(outputMatrix) + { + const currentState = this.activeState; + + return filterTransforms.calculateScreenSpaceMatrix( + outputMatrix, + currentState.sourceFrame, + currentState.destinationFrame + ); + } + + /** + * This will map the filter coord so that a texture can be used based on the transform of a sprite + * + * @param {PIXI.Matrix} outputMatrix - The matrix to output to. + * @param {PIXI.Sprite} sprite - The sprite to map to. + * @return {PIXI.Matrix} The mapped matrix. + */ + calculateSpriteMatrix(outputMatrix, sprite) + { + const currentState = this.activeState; + + return filterTransforms.calculateSpriteMatrix( + outputMatrix, + currentState.sourceFrame, + currentState.destinationFrame, + sprite + ); + } + + /** + * Destroys this Filter System. + * + * @param {boolean} [contextLost=false] context was lost, do not free shaders + * + */ + destroy(contextLost = false) + { + if (!contextLost) + { + this.emptyPool(); + } + else + { + this.texturePool = {}; + } + } + + /** + * Gets a Power-of-Two render texture. + * + * TODO move to a seperate class could be on renderer? + * also - could cause issue with multiple contexts? + * + * @private + * @param {WebGLRenderingContext} gl - The webgl rendering context + * @param {number} minWidth - The minimum width of the render target. + * @param {number} minHeight - The minimum height of the render target. + * @param {number} resolution - The resolution of the render target. + * @return {PIXI.RenderTarget} The new render target. + */ + getPotFilterTexture(minWidth, minHeight, resolution) + { + minWidth = bitTwiddle.nextPow2(minWidth); + minHeight = bitTwiddle.nextPow2(minHeight); + resolution = resolution || 1; + + const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + + if (!this.texturePool[key]) + { + this.texturePool[key] = []; + } + + let renderTexture = this.texturePool[key].pop(); + + if (!renderTexture) + { + // temporary bypass cache.. + // internally - this will cause a texture to be bound.. + renderTexture = RenderTexture.create({ + width: minWidth, + height: minHeight, + resolution, + }); + } + + return renderTexture; + } + + /** + * Gets extra render texture to use inside current filter + * + * @param {number} resolution resolution of the renderTexture + * @returns {PIXI.RenderTexture} + */ + getFilterTexture(resolution) + { + const rt = this.activeState.renderTexture; + + const filterTexture = this.getPotFilterTexture(rt.width, rt.height, resolution || rt.baseTexture.resolution); + + filterTexture.filterFrame = rt.filterFrame; + + return filterTexture; + } + + /** + * Frees a render texture back into the pool. + * + * @param {PIXI.RenderTarget} renderTexture - The renderTarget to free + */ + returnFilterTexture(renderTexture) + { + renderTexture.filterFrame = null; + + const base = renderTexture.baseTexture; + + const minWidth = base.width; + const minHeight = base.height; + + const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + + this.texturePool[key].push(renderTexture); + } + + /** + * Empties the texture pool. + * + */ + emptyPool() + { + for (const i in this.texturePool) + { + const textures = this.texturePool[i]; + + if (textures) + { + for (let j = 0; j < textures.length; j++) + { + textures[j].destroy(true); + } + } + } + + this.texturePool = {}; + } +} diff --git a/packages/core/src/filters/defaultFilter.frag b/packages/core/src/filters/defaultFilter.frag new file mode 100644 index 0000000..a14c1be --- /dev/null +++ b/packages/core/src/filters/defaultFilter.frag @@ -0,0 +1,22 @@ +varying vec2 vTextureCoord; +varying vec2 vFilterCoord; + +uniform sampler2D uSampler; +uniform sampler2D filterSampler; + +void main(void){ + vec4 masky = texture2D(filterSampler, vFilterCoord); + vec4 sample = texture2D(uSampler, vTextureCoord); + vec4 color; + if(mod(vFilterCoord.x, 1.0) > 0.5) + { + color = vec4(1.0, 0.0, 0.0, 1.0); + } + else + { + color = vec4(0.0, 1.0, 0.0, 1.0); + } + // gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0); + gl_FragColor = mix(sample, masky, 0.5); + gl_FragColor *= sample.a; +} diff --git a/packages/core/src/filters/defaultFilter.vert b/packages/core/src/filters/defaultFilter.vert new file mode 100644 index 0000000..e1febca --- /dev/null +++ b/packages/core/src/filters/defaultFilter.vert @@ -0,0 +1,14 @@ +attribute vec2 aVertexPosition; +attribute vec2 aTextureCoord; + +uniform mat3 projectionMatrix; +uniform mat3 filterMatrix; + +varying vec2 vTextureCoord; +varying vec2 vFilterCoord; + +void main(void){ + gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); + vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy; + vTextureCoord = aTextureCoord ; +} diff --git a/packages/core/src/filters/filterTransforms.js b/packages/core/src/filters/filterTransforms.js new file mode 100644 index 0000000..87a3cd6 --- /dev/null +++ b/packages/core/src/filters/filterTransforms.js @@ -0,0 +1,50 @@ +import { Matrix } from '@pixi/math'; + +/** + * Calculates the mapped matrix + * @param filterArea {Rectangle} The filter area + * @param sprite {Sprite} the target sprite + * @param outputMatrix {Matrix} @alvin + * @private + */ +// this returns a matrix that will normalise map filter cords in the filter to screen space +export function calculateScreenSpaceMatrix(outputMatrix, filterArea, textureSize) +{ + // TODO unwrap? + const mappedMatrix = outputMatrix.identity(); + + mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); + + mappedMatrix.scale(textureSize.width, textureSize.height); + + return mappedMatrix; +} + +export function calculateNormalizedScreenSpaceMatrix(outputMatrix, filterArea, textureSize) +{ + const mappedMatrix = outputMatrix.identity(); + + mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); + + const translateScaleX = (textureSize.width / filterArea.width); + const translateScaleY = (textureSize.height / filterArea.height); + + mappedMatrix.scale(translateScaleX, translateScaleY); + + return mappedMatrix; +} + +// this will map the filter coord so that a texture can be used based on the transform of a sprite +export function calculateSpriteMatrix(outputMatrix, filterArea, textureSize, sprite) +{ + const orig = sprite._texture.orig; + const mappedMatrix = outputMatrix.set(textureSize.width, 0, 0, textureSize.height, filterArea.x, filterArea.y); + const worldTransform = sprite.worldTransform.copyTo(Matrix.TEMP_MATRIX); + + worldTransform.invert(); + mappedMatrix.prepend(worldTransform); + mappedMatrix.scale(1.0 / orig.width, 1.0 / orig.height); + mappedMatrix.translate(sprite.anchor.x, sprite.anchor.y); + + return mappedMatrix; +} diff --git a/packages/core/src/filters/spriteMask/SpriteMaskFilter.js b/packages/core/src/filters/spriteMask/SpriteMaskFilter.js new file mode 100644 index 0000000..91697b7 --- /dev/null +++ b/packages/core/src/filters/spriteMask/SpriteMaskFilter.js @@ -0,0 +1,63 @@ +import Filter from '../Filter'; +import { Matrix } from '@pixi/math'; +import vertex from './spriteMaskFilter.vert'; +import fragment from './spriteMaskFilter.frag'; +import { default as TextureMatrix } from '../../textures/TextureMatrix'; + +/** + * The SpriteMaskFilter class + * + * @class + * @extends PIXI.Filter + * @memberof PIXI + */ +export default class SpriteMaskFilter extends Filter +{ + /** + * @param {PIXI.Sprite} sprite - the target sprite + */ + constructor(sprite) + { + const maskMatrix = new Matrix(); + + super(vertex, fragment); + + sprite.renderable = false; + + this.maskSprite = sprite; + this.maskMatrix = maskMatrix; + } + + /** + * Applies the filter + * + * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + */ + apply(filterManager, input, output) + { + const maskSprite = this.maskSprite; + const tex = this.maskSprite.texture; + + if (!tex.valid) + { + return; + } + if (!tex.transform) + { + // margin = 0.0, let it bleed a bit, shader code becomes easier + // assuming that atlas textures were made with 1-pixel padding + tex.transform = new TextureMatrix(tex, 0.0); + } + tex.transform.update(); + + this.uniforms.mask = tex; + this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite) + .prepend(tex.transform.mapCoord); + this.uniforms.alpha = maskSprite.worldAlpha; + this.uniforms.maskClamp = tex.transform.uClampFrame; + + filterManager.applyFilter(this, input, output); + } +} diff --git a/packages/core/src/filters/spriteMask/spriteMaskFilter.frag b/packages/core/src/filters/spriteMask/spriteMaskFilter.frag new file mode 100644 index 0000000..0e4aef8 --- /dev/null +++ b/packages/core/src/filters/spriteMask/spriteMaskFilter.frag @@ -0,0 +1,23 @@ +varying vec2 vMaskCoord; +varying vec2 vTextureCoord; + +uniform sampler2D uSampler; +uniform sampler2D mask; +uniform float alpha; +uniform vec4 maskClamp; + +void main(void) +{ + float clip = step(3.5, + step(maskClamp.x, vMaskCoord.x) + + step(maskClamp.y, vMaskCoord.y) + + step(vMaskCoord.x, maskClamp.z) + + step(vMaskCoord.y, maskClamp.w)); + + vec4 original = texture2D(uSampler, vTextureCoord); + vec4 masky = texture2D(mask, vMaskCoord); + + original *= (masky.r * masky.a * alpha * clip); + + gl_FragColor = original; +} diff --git a/packages/core/src/filters/spriteMask/spriteMaskFilter.vert b/packages/core/src/filters/spriteMask/spriteMaskFilter.vert new file mode 100644 index 0000000..d23a30c --- /dev/null +++ b/packages/core/src/filters/spriteMask/spriteMaskFilter.vert @@ -0,0 +1,16 @@ +attribute vec2 aVertexPosition; +attribute vec2 aTextureCoord; + +uniform mat3 projectionMatrix; +uniform mat3 otherMatrix; + +varying vec2 vMaskCoord; +varying vec2 vTextureCoord; + +void main(void) +{ + gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); + + vTextureCoord = aTextureCoord; + vMaskCoord = ( otherMatrix * vec3( aTextureCoord, 1.0) ).xy; +} diff --git a/packages/core/src/framebuffer/FrameBuffer.js b/packages/core/src/framebuffer/FrameBuffer.js new file mode 100644 index 0000000..84e9fcc --- /dev/null +++ b/packages/core/src/framebuffer/FrameBuffer.js @@ -0,0 +1,106 @@ +import Texture from '../textures/BaseTexture'; +import { FORMATS, TYPES } from '@pixi/constants'; + +/** + * Frame buffer + * @class + * @memberof PIXI + */ +export default class FrameBuffer +{ + constructor(width, height) + { + this.width = width || 100; + this.height = height || 100; + + this.stencil = false; + this.depth = false; + + this.dirtyId = 0; + this.dirtyFormat = 0; + this.dirtySize = 0; + + this.depthTexture = null; + this.colorTextures = []; + + this.glFrameBuffers = {}; + } + + get colorTexture() + { + return this.colorTextures[0]; + } + + addColorTexture(index, texture) + { + // TODO add some validation to the texture - same width / height etc? + this.colorTextures[index || 0] = texture || new Texture(null, { scaleMode: 0, + resolution: 1, + mipmap: false, + width: this.width, + height: this.height });// || new Texture(); + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + addDepthTexture(texture) + { + /* eslint-disable max-len */ + this.depthTexture = texture || new Texture(null, { scaleMode: 0, + resolution: 1, + width: this.width, + height: this.height, + mipmap: false, + format: FORMATS.DEPTH_COMPONENT, + type: TYPES.UNSIGNED_SHORT });// UNSIGNED_SHORT; + /* eslint-disable max-len */ + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + enableDepth() + { + this.depth = true; + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + enableStencil() + { + this.stencil = true; + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + resize(width, height) + { + if (width === this.width && height === this.height) return; + + this.width = width; + this.height = height; + + this.dirtyId++; + this.dirtySize++; + + for (let i = 0; i < this.colorTextures.length; i++) + { + this.colorTextures[i].setSize(width, height); + } + + if (this.depthTexture) + { + this.depthTexture.setSize(width, height); + } + } +} diff --git a/packages/core/src/framebuffer/FramebufferSystem.js b/packages/core/src/framebuffer/FramebufferSystem.js new file mode 100644 index 0000000..2cc2437 --- /dev/null +++ b/packages/core/src/framebuffer/FramebufferSystem.js @@ -0,0 +1,246 @@ +import System from '../System'; +import { Rectangle } from '@pixi/math'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class FramebufferSystem extends System +{ + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + contextChange() + { + this.gl = this.renderer.gl; + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + this.current = null; + this.viewport = new Rectangle(); + + this.drawBufferExtension = this.renderer.context.extensions.drawBuffers; + } + + bind(framebuffer, frame) + { + const gl = this.gl; + + this.current = framebuffer; + + if (framebuffer) + { + // TODO cacheing layer! + + const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID] || this.initFramebuffer(framebuffer); + + gl.bindFramebuffer(gl.FRAMEBUFFER, fbo.framebuffer); + // makesure all textures are unbound.. + + // now check for updates... + if (fbo.dirtyId !== framebuffer.dirtyId) + { + fbo.dirtyId = framebuffer.dirtyId; + + if (fbo.dirtyFormat !== framebuffer.dirtyFormat) + { + fbo.dirtyFormat = framebuffer.dirtyFormat; + this.updateFramebuffer(framebuffer); + } + else if (fbo.dirtySize !== framebuffer.dirtySize) + { + fbo.dirtySize = framebuffer.dirtySize; + this.resizeFramebuffer(framebuffer); + } + } + + for (let i = 0; i < framebuffer.colorTextures.length; i++) + { + if (framebuffer.colorTextures[i].texturePart) + { + this.renderer.texture.unbind(framebuffer.colorTextures[i].texture); + } + else + { + this.renderer.texture.unbind(framebuffer.colorTextures[i]); + } + } + + if (framebuffer.depthTexture) + { + this.renderer.texture.unbind(framebuffer.depthTexture); + } + + if (frame) + { + this.setViewport(frame.x, frame.y, frame.width, frame.height); + } + else + { + this.setViewport(0, 0, framebuffer.width, framebuffer.height); + } + } + else + { + gl.bindFramebuffer(gl.FRAMEBUFFER, null); + + if (frame) + { + this.setViewport(frame.x, frame.y, frame.width, frame.height); + } + else + { + this.setViewport(0, 0, this.renderer.width, this.renderer.height); + } + } + } + + setViewport(x, y, width, height) + { + const v = this.viewport; + + if (v.width !== width || v.height !== height || v.x !== x || v.y !== y) + { + v.x = x; + v.y = y; + v.width = width; + v.height = height; + + this.gl.viewport(x, y, width, height); + } + } + + get size() + { + if (this.current) + { + // TODO store temp + return { x: 0, y: 0, width: this.current.width, height: this.current.height }; + } + + return { x: 0, y: 0, width: this.renderer.width, height: this.renderer.height }; + } + + clear(r, g, b, a) + { + const gl = this.gl; + + // TODO clear color can be set only one right? + gl.clearColor(r, g, b, a); + gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); + } + + // private functions... + + initFramebuffer(framebuffer) + { + const gl = this.gl; + + // TODO - make this a class? + const fbo = { + framebuffer: gl.createFramebuffer(), + stencil: null, + dirtyId: 0, + dirtyFormat: 0, + dirtySize: 0, + }; + + framebuffer.glFrameBuffers[this.CONTEXT_UID] = fbo; + + return fbo; + } + + resizeFramebuffer(framebuffer) + { + const gl = this.gl; + + if (framebuffer.stencil || framebuffer.depth) + { + gl.bindRenderbuffer(gl.RENDERBUFFER, this.stencil); + gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); + } + } + + updateFramebuffer(framebuffer) + { + const gl = this.gl; + + const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID]; + + // bind the color texture + const colorTextures = framebuffer.colorTextures; + + let count = colorTextures.length; + + if (!this.drawBufferExtension) + { + count = Math.min(count, 1); + } + + const activeTextures = []; + + for (let i = 0; i < count; i++) + { + const texture = framebuffer.colorTextures[i]; + + if (texture.texturePart) + { + this.renderer.texture.bind(texture.texture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.COLOR_ATTACHMENT0 + i, + gl.TEXTURE_CUBE_MAP_NEGATIVE_X + texture.side, + texture.texture._glTextures[this.CONTEXT_UID].texture, + 0); + } + else + { + this.renderer.texture.bind(texture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.COLOR_ATTACHMENT0 + i, + gl.TEXTURE_2D, + texture._glTextures[this.CONTEXT_UID].texture, + 0); + } + + activeTextures.push(gl.COLOR_ATTACHMENT0 + i); + } + + if (this.drawBufferExtension && activeTextures.length > 1) + { + this.drawBufferExtension.drawBuffersWEBGL(activeTextures); + } + + if (framebuffer.depthTexture) + { + const depthTextureExt = this.renderer.context.extensions.depthTexture; + + if (depthTextureExt) + { + const depthTexture = framebuffer.depthTexture; + + this.renderer.texture.bind(depthTexture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.DEPTH_ATTACHMENT, + gl.TEXTURE_2D, + depthTexture._glTextures[this.CONTEXT_UID].texture, + 0); + } + } + + if (framebuffer.stencil || framebuffer.depth) + { + fbo.stencil = gl.createRenderbuffer(); + + gl.bindRenderbuffer(gl.RENDERBUFFER, fbo.stencil); + + // TODO.. this is depth AND stencil? + gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, fbo.stencil); + gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); + // fbo.enableStencil(); + } + } +} diff --git a/packages/core/src/geometry/GLBuffer.js b/packages/core/src/geometry/GLBuffer.js new file mode 100644 index 0000000..0477e8a --- /dev/null +++ b/packages/core/src/geometry/GLBuffer.js @@ -0,0 +1,9 @@ +export default class GLBuffer +{ + constructor(buffer) + { + this.buffer = buffer; + this.updateID = -1; + this.byteLength = -1; + } +} diff --git a/packages/core/src/geometry/GeometrySystem.js b/packages/core/src/geometry/GeometrySystem.js new file mode 100644 index 0000000..959f827 --- /dev/null +++ b/packages/core/src/geometry/GeometrySystem.js @@ -0,0 +1,388 @@ +import System from '../System'; +import GLBuffer from './GLBuffer'; + +const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class GeometrySystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this._activeGeometry = null; + this._activeVao = null; + + this.hasVao = true; + this.hasInstance = true; + } + + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + contextChange() + { + const gl = this.gl = this.renderer.gl; + + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + + // webgl2 + if (!gl.createVertexArray) + { + // webgl 1! + let nativeVaoExtension = this.renderer.context.extensions.vertexArrayObject; + + if (this.renderer.legacy) + { + nativeVaoExtension = null; + } + + if (nativeVaoExtension) + { + gl.createVertexArray = () => + nativeVaoExtension.createVertexArrayOES(); + + gl.bindVertexArray = (vao) => + nativeVaoExtension.bindVertexArrayOES(vao); + + gl.deleteVertexArray = (vao) => + nativeVaoExtension.deleteVertexArrayOES(vao); + } + else + { + this.hasVao = false; + gl.createVertexArray = () => + { + // empty + }; + + gl.bindVertexArray = () => + { + // empty + }; + + gl.deleteVertexArray = () => + { + // empty + }; + } + } + + if (!gl.vertexAttribDivisor) + { + const instanceExt = gl.getExtension('ANGLE_instanced_arrays'); + + if (instanceExt) + { + gl.vertexAttribDivisor = (a, b) => + instanceExt.vertexAttribDivisorANGLE(a, b); + + gl.drawElementsInstanced = (a, b, c, d, e) => + instanceExt.drawElementsInstancedANGLE(a, b, c, d, e); + + gl.drawArraysInstanced = (a, b, c, d) => + instanceExt.drawArraysInstancedANGLE(a, b, c, d); + } + else + { + this.hasInstance = false; + } + } + } + + /** + * Binds geometry so that is can be drawn. Creating a Vao if required + * @private + * @param {PIXI.Geometry} geometry instance of geometry to bind + */ + bind(geometry, shader) + { + shader = shader || this.renderer.shader.shader; + + const gl = this.gl; + + // not sure the best way to address this.. + // currently different shaders require different VAOs for the same geometry + // Still mulling over the best way to solve this one.. + // will likely need to modify the shader attribute locations at run time! + let vaos = geometry.glVertexArrayObjects[this.CONTEXT_UID]; + + if (!vaos) + { + geometry.glVertexArrayObjects[this.CONTEXT_UID] = vaos = {}; + } + + const vao = vaos[shader.program.id] || this.initGeometryVao(geometry, shader.program); + + this._activeGeometry = geometry; + + if (this._activeVao !== vao) + { + this._activeVao = vao; + + if (this.hasVao) + { + gl.bindVertexArray(vao); + } + else + { + this.activateVao(geometry, shader.program); + } + } + + // TODO - optimise later! + // don't need to loop through if nothing changed! + // maybe look to add an 'autoupdate' to geometry? + this.updateBuffers(); + } + + reset() + { + this.unbind(); + } + + updateBuffers() + { + const geometry = this._activeGeometry; + const gl = this.gl; + + for (let i = 0; i < geometry.buffers.length; i++) + { + const buffer = geometry.buffers[i]; + + const glBuffer = buffer._glBuffers[this.CONTEXT_UID]; + + if (buffer._updateID !== glBuffer.updateID) + { + glBuffer.updateID = buffer._updateID; + + // TODO can cache this on buffer! maybe added a getter / setter? + const type = buffer.index ? gl.ELEMENT_ARRAY_BUFFER : gl.ARRAY_BUFFER; + const drawType = buffer.static ? gl.STATIC_DRAW : gl.DYNAMIC_DRAW; + + gl.bindBuffer(type, glBuffer.buffer); + + if (glBuffer.byteLength >= buffer.data.byteLength) + { + // offset is always zero for now! + gl.bufferSubData(type, 0, buffer.data); + } + else + { + gl.bufferData(type, buffer.data, drawType); + } + } + } + } + + checkCompatability(geometry, program) + { + // geometry must have at least all the attributes that the shader requires. + const geometryAttributes = geometry.attributes; + const shaderAttributes = program.attributeData; + + for (const j in shaderAttributes) + { + if (!geometryAttributes[j]) + { + throw new Error(`shader and geometry incompatible, geometry missing the "${j}" attribute`); + } + } + } + + /** + * Creates a Vao with the same structure as the geometry and stores it on the geometry. + * @private + * @param {PIXI.Geometry} geometry instance of geometry to to generate Vao for + */ + initGeometryVao(geometry, program) + { + this.checkCompatability(geometry, program); + + const gl = this.gl; + const CONTEXT_UID = this.CONTEXT_UID; + const buffers = geometry.buffers; + const attributes = geometry.attributes; + + const tempStride = {}; + const tempStart = {}; + + for (const j in buffers) + { + tempStride[j] = 0; + tempStart[j] = 0; + } + + for (const j in attributes) + { + if (!attributes[j].size && program.attributeData[j]) + { + attributes[j].size = program.attributeData[j].size; + } + + tempStride[attributes[j].buffer] += attributes[j].size * byteSizeMap[attributes[j].type]; + } + + for (const j in attributes) + { + const attribute = attributes[j]; + const attribSize = attribute.size; + + if (attribute.stride === undefined) + { + if (tempStride[attribute.buffer] === attribSize * byteSizeMap[attribute.type]) + { + attribute.stride = 0; + } + else + { + attribute.stride = tempStride[attribute.buffer]; + } + } + + if (attribute.start === undefined) + { + attribute.start = tempStart[attribute.buffer]; + + tempStart[attribute.buffer] += attribSize * byteSizeMap[attribute.type]; + } + } + + // first update - and create the buffers! + // only create a gl buffer if it actually gets + for (let i = 0; i < buffers.length; i++) + { + const buffer = buffers[i]; + + if (!buffer._glBuffers[CONTEXT_UID]) + { + buffer._glBuffers[CONTEXT_UID] = new GLBuffer(gl.createBuffer()); + } + } + + // TODO - maybe make this a data object? + // lets wait to see if we need to first! + const vao = gl.createVertexArray(); + + gl.bindVertexArray(vao); + + this.activateVao(geometry, program); + + gl.bindVertexArray(null); + + // add it to the cache! + geometry.glVertexArrayObjects[this.CONTEXT_UID][program.id] = vao; + + return vao; + } + + activateVao(geometry, program) + { + const gl = this.gl; + const CONTEXT_UID = this.CONTEXT_UID; + const buffers = geometry.buffers; + const attributes = geometry.attributes; + + if (geometry.indexBuffer) + { + // first update the index buffer if we have one.. + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, geometry.indexBuffer._glBuffers[CONTEXT_UID].buffer); + } + + let lastBuffer = null; + + // add a new one! + for (const j in attributes) + { + const attribute = attributes[j]; + const buffer = buffers[attribute.buffer]; + const glBuffer = buffer._glBuffers[CONTEXT_UID]; + + if (program.attributeData[j]) + { + if (lastBuffer !== glBuffer) + { + gl.bindBuffer(gl.ARRAY_BUFFER, glBuffer.buffer); + + lastBuffer = glBuffer; + } + + const location = program.attributeData[j].location; + + // TODO introduce state again + // we can optimise this for older devices that have no VAOs + gl.enableVertexAttribArray(location); + + gl.vertexAttribPointer(location, + attribute.size, + attribute.type || gl.FLOAT, + attribute.normalized, + attribute.stride, + attribute.start); + + if (attribute.instance) + { + // TODO calculate instance count based of this... + if (this.hasInstance) + { + gl.vertexAttribDivisor(location, 1); + } + else + { + throw new Error('geometry error, GPU Instancing is not supported on this device'); + } + } + } + } + } + + draw(type, size, start, instanceCount) + { + const gl = this.gl; + const geometry = this._activeGeometry; + + // TODO.. this should not change so maybe cache the function? + + if (geometry.indexBuffer) + { + if (geometry.instanced) + { + /* eslint-disable max-len */ + gl.drawElementsInstanced(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2, instanceCount || 1); + /* eslint-enable max-len */ + } + else + { + gl.drawElements(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2); + } + } + else + if (geometry.instanced) + { + // TODO need a better way to calculate size.. + gl.drawArraysInstanced(type, start, size || geometry.getSize(), instanceCount || 1); + } + else + { + gl.drawArrays(type, start, size || geometry.getSize()); + } + + return this; + } + + unbind() + { + this.gl.bindVertexArray(null); + this._activeVao = null; + this._activeGeometry = null; + } +} diff --git a/packages/core/src/geometry/utils/setVertexAttribArrays.js b/packages/core/src/geometry/utils/setVertexAttribArrays.js new file mode 100644 index 0000000..23b6959 --- /dev/null +++ b/packages/core/src/geometry/utils/setVertexAttribArrays.js @@ -0,0 +1,54 @@ +// var GL_MAP = {}; + +/** + * @param gl {WebGLRenderingContext} The current WebGL context + * @param attribs {*} + * @param state {*} + */ +export default function setVertexAttribArrays(gl, attribs, state) +{ + let i; + + if (state) + { + const tempAttribState = state.tempAttribState; + const attribState = state.attribState; + + for (i = 0; i < tempAttribState.length; i++) + { + tempAttribState[i] = false; + } + + // set the new attribs + for (i = 0; i < attribs.length; i++) + { + tempAttribState[attribs[i].attribute.location] = true; + } + + for (i = 0; i < attribState.length; i++) + { + if (attribState[i] !== tempAttribState[i]) + { + attribState[i] = tempAttribState[i]; + + if (state.attribState[i]) + { + gl.enableVertexAttribArray(i); + } + else + { + gl.disableVertexAttribArray(i); + } + } + } + } + else + { + for (i = 0; i < attribs.length; i++) + { + const attrib = attribs[i]; + + gl.enableVertexAttribArray(attrib.attribute.location); + } + } +} diff --git a/packages/core/src/index.js b/packages/core/src/index.js index 58c456a..fd1f547 100644 --- a/packages/core/src/index.js +++ b/packages/core/src/index.js @@ -1,30 +1,30 @@ import * as resources from './textures/resources'; -import * as systems from './renderers/systems'; +import * as systems from './systems'; -export { resources }; export { systems }; +export { resources }; -export { default as Renderer } from './renderers/Renderer'; -export { default as AbstractRenderer } from './renderers/AbstractRenderer'; -export { default as FrameBuffer } from './textures/FrameBuffer'; +export { default as System } from './System'; +export { default as Renderer } from './Renderer'; +export { default as AbstractRenderer } from './AbstractRenderer'; +export { default as FrameBuffer } from './framebuffer/FrameBuffer'; export { default as CubeTexture } from './textures/CubeTexture'; export { default as BaseTexture } from './textures/BaseTexture'; export { default as Texture } from './textures/Texture'; export { default as TextureMatrix } from './textures/TextureMatrix'; -export { default as RenderTexture } from './textures/RenderTexture'; -export { default as BaseRenderTexture } from './textures/BaseRenderTexture'; +export { default as RenderTexture } from './renderTexture/RenderTexture'; +export { default as BaseRenderTexture } from './renderTexture/BaseRenderTexture'; export { default as TextureUvs } from './textures/TextureUvs'; -export { default as State } from './renderers/State'; -export { default as ObjectRenderer } from './renderers/utils/ObjectRenderer'; -export { default as Quad } from './renderers/utils/Quad'; -export { default as QuadUv } from './renderers/utils/QuadUv'; -export { default as checkMaxIfStatmentsInShader } from './renderers/utils/checkMaxIfStatmentsInShader'; +export { default as State } from './state/State'; +export { default as ObjectRenderer } from './batch/ObjectRenderer'; +export { default as Quad } from './utils/Quad'; +export { default as QuadUv } from './utils/QuadUv'; +export { default as checkMaxIfStatmentsInShader } from './shader/utils/checkMaxIfStatmentsInShader'; export { default as Shader } from './shader/Shader'; export { default as Program } from './shader/Program'; export { default as UniformGroup } from './shader/UniformGroup'; -export { default as SpriteMaskFilter } from './renderers/filters/spriteMask/SpriteMaskFilter'; -export { default as Filter } from './renderers/filters/Filter'; +export { default as SpriteMaskFilter } from './filters/spriteMask/SpriteMaskFilter'; +export { default as Filter } from './filters/Filter'; export { default as Attribute } from './geometry/Attribute'; export { default as Buffer } from './geometry/Buffer'; export { default as Geometry } from './geometry/Geometry'; - diff --git a/packages/core/src/mask/MaskSystem.js b/packages/core/src/mask/MaskSystem.js new file mode 100644 index 0000000..99174c2 --- /dev/null +++ b/packages/core/src/mask/MaskSystem.js @@ -0,0 +1,199 @@ +import System from '../System'; +import AlphaMaskFilter from '../filters/spriteMask/SpriteMaskFilter'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class MaskSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + // TODO - we don't need both! + this.scissor = false; + this.scissorData = null; + this.scissorRenderTarget = null; + + this.enableScissor = false; + + this.alphaMaskPool = []; + this.alphaMaskIndex = 0; + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.DisplayObject} target - Display Object to push the mask to + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + push(target, maskData) + { + // TODO the root check means scissor rect will not + // be used on render textures more info here: + // https://github.com/pixijs/pixi.js/pull/3545 + + if (maskData.texture) + { + this.pushSpriteMask(target, maskData); + } + else if (this.enableScissor + && !this.scissor + && this.renderer._activeRenderTarget.root + && !this.renderer.stencil.stencilMaskStack.length + && maskData.isFastRect()) + { + const matrix = maskData.worldTransform; + + let rot = Math.atan2(matrix.b, matrix.a); + + // use the nearest degree! + rot = Math.round(rot * (180 / Math.PI)); + + if (rot % 90) + { + this.pushStencilMask(maskData); + } + else + { + this.pushScissorMask(target, maskData); + } + } + else + { + this.pushStencilMask(maskData); + } + } + + /** + * Removes the last mask from the mask stack and doesn't return it. + * + * @param {PIXI.DisplayObject} target - Display Object to pop the mask from + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + pop(target, maskData) + { + if (maskData.texture) + { + this.popSpriteMask(target, maskData); + } + else if (this.enableScissor && !this.renderer.stencil.stencilMaskStack.length) + { + this.popScissorMask(target, maskData); + } + else + { + this.popStencilMask(target, maskData); + } + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.RenderTarget} target - Display Object to push the sprite mask to + * @param {PIXI.Sprite} maskData - Sprite to be used as the mask + */ + pushSpriteMask(target, maskData) + { + let alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; + + if (!alphaMaskFilter) + { + alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; + } + + alphaMaskFilter[0].resolution = this.renderer.resolution; + alphaMaskFilter[0].maskSprite = maskData; + + // TODO - may cause issues! + target.filterArea = maskData.getBounds(true); + + this.renderer.filterSystem.pushFilter(target, alphaMaskFilter); + + this.alphaMaskIndex++; + } + + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popSpriteMask() + { + this.renderer.filterSystem.popFilter(); + this.alphaMaskIndex--; + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + pushStencilMask(maskData) + { + this.renderer.batch.flush(); + this.renderer.stencil.pushStencil(maskData); + } + + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popStencilMask() + { + // this.renderer.currentRenderer.stop(); + this.renderer.stencil.popStencil(); + } + + /** + * + * @param {PIXI.DisplayObject} target - Display Object to push the mask to + * @param {PIXI.Graphics} maskData - The masking data. + */ + pushScissorMask(target, maskData) + { + maskData.renderable = true; + + const renderTarget = this.renderer._activeRenderTarget; + + const bounds = maskData.getBounds(); + + bounds.fit(renderTarget.size); + maskData.renderable = false; + + this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); + + const resolution = this.renderer.resolution; + + this.renderer.gl.scissor( + bounds.x * resolution, + (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, + bounds.width * resolution, + bounds.height * resolution + ); + + this.scissorRenderTarget = renderTarget; + this.scissorData = maskData; + this.scissor = true; + } + + /** + * + * + */ + popScissorMask() + { + this.scissorRenderTarget = null; + this.scissorData = null; + this.scissor = false; + + // must be scissor! + const gl = this.renderer.gl; + + gl.disable(gl.SCISSOR_TEST); + } +} diff --git a/packages/core/src/mask/StencilSystem.js b/packages/core/src/mask/StencilSystem.js new file mode 100644 index 0000000..62796a5 --- /dev/null +++ b/packages/core/src/mask/StencilSystem.js @@ -0,0 +1,133 @@ +import System from '../System'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class StencilSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + this.stencilMaskStack = []; + } + + /** + * Changes the mask stack that is used by this System. + * + * @param {PIXI.Graphics[]} stencilMaskStack - The mask stack + */ + setMaskStack(stencilMaskStack) + { + const gl = this.renderer.gl; + + if (stencilMaskStack.length !== this.stencilMaskStack.length) + { + if (stencilMaskStack.length === 0) + { + gl.disable(gl.STENCIL_TEST); + } + else + { + gl.enable(gl.STENCIL_TEST); + } + } + + this.stencilMaskStack = stencilMaskStack; + } + + /** + * Applies the Mask and adds it to the current stencil stack. @alvin + * + * @param {PIXI.Graphics} graphics - The mask + */ + pushStencil(graphics) + { + this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); + + // this.renderer._activeRenderTarget.attachStencilBuffer(); + + const gl = this.renderer.gl; + const prevMaskCount = this.stencilMaskStack.length; + + if (prevMaskCount === 0) + { + gl.enable(gl.STENCIL_TEST); + } + + this.stencilMaskStack.push(graphics); + + // Increment the refference stencil value where the new mask overlaps with the old ones. + gl.colorMask(false, false, false, false); + gl.stencilFunc(gl.EQUAL, prevMaskCount, this._getBitwiseMask()); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.INCR); + this.renderer.plugins.graphics.render(graphics); + + this._useCurrent(); + } + + /** + * Removes the last mask from the stencil stack. @alvin + */ + popStencil() + { + this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); + + const gl = this.renderer.gl; + const graphics = this.stencilMaskStack.pop(); + + if (this.stencilMaskStack.length === 0) + { + // the stack is empty! + gl.disable(gl.STENCIL_TEST); + gl.clear(gl.STENCIL_BUFFER_BIT); + gl.clearStencil(0); + } + else + { + // Decrement the refference stencil value where the popped mask overlaps with the other ones + gl.colorMask(false, false, false, false); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.DECR); + this.renderer.plugins.graphics.render(graphics); + + this._useCurrent(); + } + } + + /** + * Setup renderer to use the current stencil data. + */ + _useCurrent() + { + const gl = this.renderer.gl; + + gl.colorMask(true, true, true, true); + gl.stencilFunc(gl.EQUAL, this.stencilMaskStack.length, this._getBitwiseMask()); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP); + } + + /** + * Fill 1s equal to the number of acitve stencil masks. + * + * @return {number} The bitwise mask. + */ + _getBitwiseMask() + { + return (1 << this.stencilMaskStack.length) - 1; + } + + /** + * Destroys the mask stack. + * + */ + destroy() + { + super.destroy(this); + + this.stencilMaskStack = null; + } +} diff --git a/packages/core/src/projection/ProjectionSystem.js b/packages/core/src/projection/ProjectionSystem.js new file mode 100644 index 0000000..6e63b73 --- /dev/null +++ b/packages/core/src/projection/ProjectionSystem.js @@ -0,0 +1,73 @@ +import System from '../System'; +import { Matrix } from '@pixi/math'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class ProjectionSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.projectionMatrix = new Matrix(); + } + + update(destinationFrame, sourceFrame, resolution, root) + { + this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; + this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; + + this.calculateProjection(this.destinationFrame, this.sourceFrame, resolution, root); + + this.renderer.globalUniforms.uniforms.projectionMatrix = this.projectionMatrix; + this.renderer.globalUniforms.update(); + } + + /** + * Updates the projection matrix based on a projection frame (which is a rectangle) + * + * @param {Rectangle} destinationFrame - The destination frame. + * @param {Rectangle} sourceFrame - The source frame. + */ + calculateProjection(destinationFrame, sourceFrame, resolution, root) + { + const pm = this.projectionMatrix; + + // I don't think we will need this line.. + // pm.identity(); + + if (!root) + { + pm.a = (1 / destinationFrame.width * 2) * resolution; + pm.d = (1 / destinationFrame.height * 2) * resolution; + + pm.tx = -1 - (sourceFrame.x * pm.a); + pm.ty = -1 - (sourceFrame.y * pm.d); + } + else + { + pm.a = (1 / destinationFrame.width * 2) * resolution; + pm.d = (-1 / destinationFrame.height * 2) * resolution; + + pm.tx = -1 - (sourceFrame.x * pm.a); + pm.ty = 1 - (sourceFrame.y * pm.d); + } + } + + /** + * Sets the transform of the active render target to the given matrix + * + * @param {PIXI.Matrix} matrix - The transformation matrix + */ + setTransform()// matrix) + { + // this._activeRenderTarget.transform = matrix; + } +} diff --git a/packages/core/src/renderTexture/BaseRenderTexture.js b/packages/core/src/renderTexture/BaseRenderTexture.js new file mode 100644 index 0000000..88eb83b --- /dev/null +++ b/packages/core/src/renderTexture/BaseRenderTexture.js @@ -0,0 +1,137 @@ +import BaseTexture from '../textures/BaseTexture'; +import FrameBuffer from '../framebuffer/FrameBuffer'; + +/** + * A BaseRenderTexture is a special texture that allows any PixiJS display object to be rendered to it. + * + * __Hint__: All DisplayObjects (i.e. Sprites) that render to a BaseRenderTexture should be preloaded + * otherwise black rectangles will be drawn instead. + * + * A BaseRenderTexture takes a snapshot of any Display Object given to its render method. The position + * and rotation of the given Display Objects is ignored. For example: + * + * ```js + * let renderer = PIXI.autoDetectRenderer(1024, 1024, { view: canvas, ratio: 1 }); + * let baseRenderTexture = new PIXI.BaseRenderTexture(renderer, 800, 600); + * let sprite = PIXI.Sprite.fromImage("spinObj_01.png"); + * + * sprite.position.x = 800/2; + * sprite.position.y = 600/2; + * sprite.anchor.x = 0.5; + * sprite.anchor.y = 0.5; + * + * baseRenderTexture.render(sprite); + * ``` + * + * The Sprite in this case will be rendered using its local transform. To render this sprite at 0,0 + * you can clear the transform + * + * ```js + * + * sprite.setTransform() + * + * let baseRenderTexture = new PIXI.BaseRenderTexture(100, 100); + * let renderTexture = new PIXI.RenderTexture(baseRenderTexture); + * + * renderer.render(sprite, renderTexture); // Renders to center of RenderTexture + * ``` + * + * @class + * @extends PIXI.BaseTexture + * @memberof PIXI + */ +export default class BaseRenderTexture extends BaseTexture +{ + /** + * @param {object} [options] + * @param {number} [options.width=100] - The width of the base render texture + * @param {number} [options.height=100] - The height of the base render texture + * @param {PIXI.SCALE_MODES} [options.scaleMode] - See {@link PIXI.SCALE_MODES} for possible values + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the texture being generated + */ + constructor(options) + { + if (typeof options === 'number') + { + /* eslint-disable prefer-rest-params */ + // Backward compatibility of signature + const width = arguments[0]; + const height = arguments[1]; + const scaleMode = arguments[2]; + const resolution = arguments[3]; + + options = { width, height, scaleMode, resolution }; + /* eslint-enable prefer-rest-params */ + } + + super(null, options); + + const { width, height } = options || {}; + + // Set defaults + this.mipmap = false; + this.width = Math.ceil(width) || 100; + this.height = Math.ceil(height) || 100; + this.valid = true; + + /** + * A map of renderer IDs to webgl renderTargets + * + * @private + * @member {object} + */ + // this._glRenderTargets = {}; + + /** + * A reference to the canvas render target (we only need one as this can be shared across renderers) + * + * @private + * @member {object} + */ + this._canvasRenderTarget = null; + + this.clearColor = [0, 0, 0, 0]; + + this.frameBuffer = new FrameBuffer(width * this.resolution, height * this.resolution) + .addColorTexture(0, this); + + // TODO - could this be added the systems? + + /** + * The data structure for the stencil masks + * + * @member {PIXI.Graphics[]} + */ + this.stencilMaskStack = []; + + /** + * The data structure for the filters + * + * @member {PIXI.Graphics[]} + */ + this.filterStack = [{}]; + } + + /** + * Resizes the BaseRenderTexture. + * + * @param {number} width - The width to resize to. + * @param {number} height - The height to resize to. + */ + resize(width, height) + { + width = Math.ceil(width); + height = Math.ceil(height); + this.frameBuffer.resize(width * this.resolution, height * this.resolution); + } + + /** + * Destroys this texture + * + */ + destroy() + { + super.destroy(true); + this.renderer = null; + } +} diff --git a/packages/core/src/renderTexture/RenderTexture.js b/packages/core/src/renderTexture/RenderTexture.js new file mode 100644 index 0000000..f1ecf1f --- /dev/null +++ b/packages/core/src/renderTexture/RenderTexture.js @@ -0,0 +1,153 @@ +import BaseRenderTexture from './BaseRenderTexture'; +import Texture from '../textures/Texture'; + +/** + * A RenderTexture is a special texture that allows any PixiJS display object to be rendered to it. + * + * __Hint__: All DisplayObjects (i.e. Sprites) that render to a RenderTexture should be preloaded + * otherwise black rectangles will be drawn instead. + * + * A RenderTexture takes a snapshot of any Display Object given to its render method. For example: + * + * ```js + * let renderer = PIXI.autoDetectRenderer(1024, 1024, { view: canvas, ratio: 1 }); + * let renderTexture = PIXI.RenderTexture.create(800, 600); + * let sprite = PIXI.Sprite.fromImage("spinObj_01.png"); + * + * sprite.position.x = 800/2; + * sprite.position.y = 600/2; + * sprite.anchor.x = 0.5; + * sprite.anchor.y = 0.5; + * + * renderer.render(sprite, renderTexture); + * ``` + * + * The Sprite in this case will be rendered using its local transform. To render this sprite at 0,0 + * you can clear the transform + * + * ```js + * + * sprite.setTransform() + * + * let renderTexture = new PIXI.RenderTexture.create(100, 100); + * + * renderer.render(sprite, renderTexture); // Renders to center of RenderTexture + * ``` + * + * @class + * @extends PIXI.Texture + * @memberof PIXI + */ +export default class RenderTexture extends Texture +{ + /** + * @param {PIXI.BaseRenderTexture} baseRenderTexture - The renderer used for this RenderTexture + * @param {PIXI.Rectangle} [frame] - The rectangle frame of the texture to show + */ + constructor(baseRenderTexture, frame) + { + // support for legacy.. + let _legacyRenderer = null; + + if (!(baseRenderTexture instanceof BaseRenderTexture)) + { + /* eslint-disable prefer-rest-params, no-console */ + const width = arguments[1]; + const height = arguments[2]; + const scaleMode = arguments[3]; + const resolution = arguments[4]; + + // we have an old render texture.. + console.warn(`Please use RenderTexture.create(${width}, ${height}) instead of the ctor directly.`); + _legacyRenderer = arguments[0]; + /* eslint-enable prefer-rest-params, no-console */ + + frame = null; + baseRenderTexture = new BaseRenderTexture({ + width, + height, + scaleMode, + resolution, + }); + } + + /** + * The base texture object that this texture uses + * + * @member {BaseTexture} + */ + super(baseRenderTexture, frame); + + this.legacyRenderer = _legacyRenderer; + + /** + * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. + * + * @member {boolean} + */ + this.valid = true; + + /** + * FilterSystem temporary storage + * @private + * @member {PIXI.Rectangle} + */ + this.filterFrame = null; + + this._updateUvs(); + } + + /** + * Resizes the RenderTexture. + * + * @param {number} width - The width to resize to. + * @param {number} height - The height to resize to. + * @param {boolean} [resizeBaseTexture=true] - Should the baseTexture.width and height values be resized as well? + */ + resize(width, height, resizeBaseTexture = true) + { + width = Math.ceil(width); + height = Math.ceil(height); + + // TODO - could be not required.. + this.valid = (width > 0 && height > 0); + + this._frame.width = this.orig.width = width; + this._frame.height = this.orig.height = height; + + if (resizeBaseTexture) + { + this.baseTexture.resize(width, height); + } + + this._updateUvs(); + } + + /** + * A short hand way of creating a render texture. + * + * @param {object} [options] - Options + * @param {number} [options.width=100] - The width of the render texture + * @param {number} [options.height=100] - The height of the render texture + * @param {number} [options.scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the texture being generated + * @return {PIXI.RenderTexture} The new render texture + */ + static create(options) + { + // fallback, old-style: create(width, height, scaleMode, resolution) + if (typeof options === 'number') + { + /* eslint-disable prefer-rest-params */ + options = { + width: options, + height: arguments[1], + scaleMode: arguments[2], + resolution: arguments[3], + }; + /* eslint-enable prefer-rest-params */ + } + + return new RenderTexture(new BaseRenderTexture(options)); + } +} diff --git a/packages/core/src/renderTexture/RenderTextureSystem.js b/packages/core/src/renderTexture/RenderTextureSystem.js new file mode 100644 index 0000000..8d0ca61 --- /dev/null +++ b/packages/core/src/renderTexture/RenderTextureSystem.js @@ -0,0 +1,115 @@ +import System from '../System'; +import { Rectangle } from '@pixi/math'; + +const tempRect = new Rectangle(); + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class RenderTextureSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.clearColor = renderer._backgroundColorRgba; + + // TODO moe this property somewhere else! + this.defaultMaskStack = []; + this.defaultFilterStack = [{}]; + + // empty render texture? + this.renderTexture = null; + + this.destinationFrame = new Rectangle(); + } + + bind(renderTexture, sourceFrame, destinationFrame) + { + // TODO - do we want this?? + if (this.renderTexture === renderTexture) return; + this.renderTexture = renderTexture; + + const renderer = this.renderer; + + if (renderTexture) + { + const baseTexture = renderTexture.baseTexture; + + if (!destinationFrame) + { + tempRect.width = baseTexture.realWidth; + tempRect.height = baseTexture.realHeight; + + destinationFrame = tempRect; + } + + if (!sourceFrame) + { + sourceFrame = destinationFrame; + } + + this.renderer.framebuffer.bind(baseTexture.frameBuffer, destinationFrame); + + this.renderer.projection.update(destinationFrame, sourceFrame, baseTexture.resolution, false); + this.renderer.stencil.setMaskStack(baseTexture.stencilMaskStack); + } + else + { + // TODO these validation checks happen deeper down.. + // thing they can be avoided.. + if (!destinationFrame) + { + tempRect.width = renderer.width; + tempRect.height = renderer.height; + + destinationFrame = tempRect; + } + + if (!sourceFrame) + { + sourceFrame = destinationFrame; + } + + renderer.framebuffer.bind(null, destinationFrame); + + // TODO store this.. + this.renderer.projection.update(destinationFrame, sourceFrame, this.renderer.resolution, true); + this.renderer.stencil.setMaskStack(this.defaultMaskStack); + } + + this.destinationFrame.copyFrom(destinationFrame); + } + + /** + * Erases the render texture and fills the drawing area with a colour + * + * @param {number} [clearColor] - The colour + * @return {PIXI.Renderer} Returns itself. + */ + clear(clearColor) + { + if (this.renderTexture) + { + clearColor = clearColor || this.renderTexture.baseTexture.clearColor; + } + else + { + clearColor = clearColor || this.clearColor; + } + + this.renderer.framebuffer.clear(clearColor[0], clearColor[1], clearColor[2], clearColor[3]); + } + + resize()// screenWidth, screenHeight) + { + // resize the root only! + this.bind(null); + } +} diff --git a/packages/core/src/renderers/AbstractRenderer.js b/packages/core/src/renderers/AbstractRenderer.js deleted file mode 100644 index 928670f..0000000 --- a/packages/core/src/renderers/AbstractRenderer.js +++ /dev/null @@ -1,353 +0,0 @@ -import { hex2string, hex2rgb } from '@pixi/utils'; -import { Matrix, Rectangle } from '@pixi/math'; -import { RENDERER_TYPE } from '@pixi/constants'; -import { settings } from '@pixi/settings'; -import { Container } from '@pixi/display'; -import RenderTexture from '../textures/RenderTexture'; -import EventEmitter from 'eventemitter3'; - -const tempMatrix = new Matrix(); - -/** - * 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 EventEmitter - * @memberof PIXI - */ -export default class AbstractRenderer extends EventEmitter -{ - // eslint-disable-next-line valid-jsdoc - /** - * @param {string} system - The name of the system this renderer is for. - * @param {object} [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.transparent=false] - If the render view is transparent, default false - * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false - * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) - * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. The - * resolution of the renderer retina would be 2. - * @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 {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when rendering, - * stopping pixel interpolation. - */ - constructor(system, options, arg2, arg3) - { - super(); - - // Support for constructor(system, screenWidth, screenHeight, options) - if (typeof options === 'number') - { - options = Object.assign({ - width: options, - height: arg2 || settings.RENDER_OPTIONS.height, - }, arg3); - } - - // 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 = RENDERER_TYPE.UNKNOWN; - - /** - * Measurements of the screen. (0, 0, screenWidth, screenHeight) - * - * Its safe to use as filterArea or hitArea for 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 1 - */ - this.resolution = options.resolution || settings.RESOLUTION; - - /** - * Whether the render view is transparent - * - * @member {boolean} - */ - this.transparent = options.transparent; - - /** - * Whether css dimensions of canvas view should be resized to screen dimensions automatically - * - * @member {boolean} - */ - this.autoResize = options.autoResize || false; - - /** - * Tracks the blend modes useful for this renderer. - * - * @member {object} - */ - this.blendModes = null; - - /** - * 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; - - /** - * If true PixiJS will Math.floor() x/y values when rendering, stopping pixel interpolation. - * Handy for crisp pixel art and speed on legacy devices. - * - * @member {boolean} - */ - this.roundPixels = options.roundPixels; - - /** - * The background color as a number. - * - * @member {number} - * @private - */ - this._backgroundColor = 0x000000; - - /** - * The background color as an [R, G, B] array. - * - * @member {number[]} - * @private - */ - this._backgroundColorRgba = [0, 0, 0, 0]; - - /** - * The background color as a string. - * - * @member {string} - * @private - */ - this._backgroundColorString = '#000000'; - - this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter - - /** - * This temporary display object used as the parent of the currently being rendered item - * - * @member {PIXI.DisplayObject} - * @private - */ - this._tempDisplayObjectParent = new Container(); - - /** - * The last root object that the renderer tried to render. - * - * @member {PIXI.DisplayObject} - * @private - */ - this._lastObjectRendered = this._tempDisplayObjectParent; - - /** - * Collection of plugins. - * @readonly - * @member {object} - */ - this.plugins = {}; - } - - /** - * Initialize the plugins. - * - * @protected - * @param {object} staticMap - The dictionary of staticly saved plugins. - */ - initPlugins(staticMap) - { - 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() - { - return this.view.width; - } - - /** - * Same as view.height, actual number of pixels in the canvas by vertical - * - * @member {number} - * @readonly - * @default 600 - */ - get height() - { - return this.view.height; - } - - /** - * Resizes the screen and canvas to the specified width and height - * Canvas dimensions are multiplied by resolution - * - * @param {number} screenWidth - the new width of the screen - * @param {number} screenHeight - the new height of the screen - */ - resize(screenWidth, screenHeight) - { - this.screen.width = screenWidth; - this.screen.height = screenHeight; - - this.view.width = screenWidth * this.resolution; - this.view.height = screenHeight * this.resolution; - - if (this.autoResize) - { - this.view.style.width = `${screenWidth}px`; - this.view.style.height = `${screenHeight}px`; - } - } - - /** - * 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 {PIXI.DisplayObject} displayObject - The displayObject the object will be generated from - * @param {number} scaleMode - Should be one of the scaleMode consts - * @param {number} resolution - The resolution / device pixel ratio of the texture being generated - * @param {PIXI.Rectangle} [region] - The region of the displayObject, that shall be rendered, - * if no region is specified, defaults to the local bounds of the displayObject. - * @return {PIXI.Texture} a texture of the graphics object - */ - generateTexture(displayObject, scaleMode, resolution, region) - { - region = region || displayObject.getLocalBounds(); - - // 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(region.width | 0, region.height | 0, scaleMode, resolution); - - tempMatrix.tx = -region.x; - tempMatrix.ty = -region.y; - - this.render(displayObject, renderTexture, false, tempMatrix, true); - - return renderTexture; - } - - /** - * Removes everything from the renderer and optionally removes the Canvas DOM element. - * - * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. - */ - destroy(removeView) - { - 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); - } - - this.plugins = null; - - this.type = RENDERER_TYPE.UNKNOWN; - - this.view = null; - - this.screen = null; - - this.resolution = 0; - - this.transparent = false; - - this.autoResize = false; - - this.blendModes = null; - - this.options = null; - - this.preserveDrawingBuffer = false; - this.clearBeforeRender = false; - - this.roundPixels = false; - - this._backgroundColor = 0; - this._backgroundColorRgba = null; - this._backgroundColorString = null; - - this._tempDisplayObjectParent = null; - this._lastObjectRendered = null; - } - - /** - * The background color to fill if not transparent - * - * @member {number} - */ - get backgroundColor() - { - return this._backgroundColor; - } - - set backgroundColor(value) // eslint-disable-line require-jsdoc - { - this._backgroundColor = value; - this._backgroundColorString = hex2string(value); - hex2rgb(value, this._backgroundColorRgba); - } -} diff --git a/packages/core/src/renderers/Renderer.js b/packages/core/src/renderers/Renderer.js deleted file mode 100644 index a2020b1..0000000 --- a/packages/core/src/renderers/Renderer.js +++ /dev/null @@ -1,329 +0,0 @@ -import AbstractRenderer from './AbstractRenderer'; -import { sayHello } from '@pixi/utils'; -import MaskSystem from './systems/MaskSystem'; -import StencilSystem from './systems/StencilSystem'; -import FilterSystem from './systems/filter/FilterSystem'; -import FramebufferSystem from './systems/FramebufferSystem'; -import RenderTextureSystem from './systems/RenderTextureSystem'; -import TextureSystem from './systems/textures/TextureSystem'; -import ProjectionSystem from './systems/ProjectionSystem'; -import StateSystem from './systems/StateSystem'; -import GeometrySystem from './systems/geometry/GeometrySystem'; -import ShaderSystem from './systems/shader/ShaderSystem'; -import ContextSystem from './systems/ContextSystem'; -import BatchSystem from './systems/BatchSystem'; -import TextureGCSystem from './systems/textures/TextureGCSystem'; -import { RENDERER_TYPE } from '@pixi/constants'; -import UniformGroup from '../shader/UniformGroup'; -import { Matrix } from '@pixi/math'; -import Runner from 'mini-runner'; - -/** - * 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 Render works by automatically managing webGLBatchs. - * 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 :) - * - * @class - * @memberof PIXI - * @extends PIXI.AbstractRenderer - */ -export default class Renderer extends AbstractRenderer -{ - // eslint-disable-next-line valid-jsdoc - /** - * - * @param {object} [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.transparent=false] - If the render view is transparent, default false - * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false - * @param {boolean} [options.antialias=false] - sets antialias. If not available natively then FXAA - * antialiasing is used - * @param {boolean} [options.forceFXAA=false] - forces FXAA antialiasing to be used over native. - * FXAA is faster, but may not always look as great - * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. - * The resolution of the renderer retina would be 2. - * @param {boolean} [options.clearBeforeRender=true] - This sets if the renderer will clear - * the canvas or not before the new render pass. If you wish to set this to false, you *must* set - * preserveDrawingBuffer to `true`. - * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, - * enable this if you need to call toDataUrl on the webgl context. - * @param {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when - * rendering, stopping pixel interpolation. - * @param {number} [options.backgroundColor=0x000000] - The background color of the rendered area - * (shown if not transparent). - * @param {boolean} [options.legacy=false] - If true PixiJS will aim to ensure compatibility - * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. - * @param {string} [options.powerPreference] - Parameter passed to webgl context, set to "high-performance" - * for devices with dual graphics card - */ - constructor(options, arg2, arg3) - { - super('WebGL', options, arg2, arg3); - - /** - * The type of this renderer as a standardised const - * - * @member {number} - * @see PIXI.RENDERER_TYPE - */ - this.type = RENDERER_TYPE.WEBGL; - - // this will be set by the contextSystem (this.context) - this.gl = null; - this.CONTEXT_UID = 0; - this.legacy = !!options.legacy; - - // TODO legacy! - - // runners! - this.runners = { - destroy: new Runner('destroy'), - contextChange: new Runner('contextChange', 1), - reset: new Runner('reset'), - update: new Runner('update'), - postrender: new Runner('postrender'), - prerender: new Runner('prerender'), - resize: new Runner('resize', 2), - }; - - 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(GeometrySystem, 'geometry') - .addSystem(FramebufferSystem, 'framebuffer') - .addSystem(StencilSystem, 'stencil') - .addSystem(ProjectionSystem, 'projection') - .addSystem(TextureGCSystem, 'textureGC') - .addSystem(FilterSystem, 'filter') - .addSystem(RenderTextureSystem, 'renderTexture') - .addSystem(BatchSystem, 'batch'); - - this.initPlugins(Renderer.__plugins); - - /** - * The options passed in to create a new webgl context. - * - * @member {object} - * @private - */ - if (options.context) - { - this.context.initFromContext(options.context); - } - else - { - this.context.initFromOptions({ - alpha: this.transparent, - antialias: options.antialias, - premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', - stencil: true, - 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); - } - - addSystem(_class, name) - { - if (!name) - { - name = _class.name; - } - - // TODO - read name from class.name.. - - /* - if(name.includes('System')) - { - name = name.replace('System', ''); - name = name.charAt(0).toLowerCase() + name.slice(1); - } - */ - - const system = new _class(this); - - if (this[name]) - { - throw new Error(`Whoops! ${name} is already a manger`); - } - - this[name] = system; - - for (const i in this.runners) - { - this.runners[i].add(system); - } - - return this; - - /** - * 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. - */ - } - - /** - * Renders the object to its webGL view - * - * @param {PIXI.DisplayObject} displayObject - the object to be rendered - * @param {PIXI.RenderTexture} renderTexture - The render texture to render to. - * @param {boolean} [clear] - Should the canvas be cleared before the new render - * @param {PIXI.Transform} [transform] - A transform to apply to the render texture before rendering. - * @param {boolean} [skipUpdateTransform] - Should we skip the update transform pass? - */ - render(displayObject, renderTexture, clear, transform, skipUpdateTransform) - { - // can be handy to know! - this.renderingToScreen = !renderTexture; - - this.runners.prerender.run(); - this.emit('prerender'); - - // 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.parent; - - displayObject.parent = this._tempDisplayObjectParent; - displayObject.updateTransform(); - displayObject.parent = 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.run(); - - this.emit('postrender'); - } - - /** - * Resizes the webGL view to the specified width and height. - * - * @param {number} screenWidth - the new width of the screen - * @param {number} screenHeight - the new height of the screen - */ - resize(screenWidth, screenHeight) - { - AbstractRenderer.prototype.resize.call(this, screenWidth, screenHeight); - - this.runners.resize.run(screenWidth, screenHeight); - } - - /** - * Resets the WebGL state so you can render things however you fancy! - * - * @return {PIXI.Renderer} Returns itself. - */ - reset() - { - this.runners.reset.run(); - - return this; - } - - clear() - { - this.framebuffer.bind(); - this.framebuffer.clear(); - } - - /** - * Removes everything from the renderer (event listeners, spritebatch, etc...) - * - * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. - * See: https://github.com/pixijs/pixi.js/issues/2233 - */ - destroy(removeView) - { - this.runners.destroy.run(); - - // call base destroy - super.destroy(removeView); - - // TODO nullify all the managers.. - this.gl = null; - } - - /** - * 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 - * builds and excluding plugins. - * @name PIXI.Renderer#plugins - * @type {object} - * @readonly - * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. - * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. - * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. - * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. - */ - - /** - * Adds a plugin to the renderer. - * - * @method - * @param {string} pluginName - The name of the plugin. - * @param {Function} ctor - The constructor function or class for the plugin. - */ - static registerPlugin(pluginName, ctor) - { - Renderer.__plugins = Renderer.__plugins || {}; - Renderer.__plugins[pluginName] = ctor; - } -} diff --git a/packages/core/src/renderers/State.js b/packages/core/src/renderers/State.js deleted file mode 100644 index fa3f221..0000000 --- a/packages/core/src/renderers/State.js +++ /dev/null @@ -1,163 +0,0 @@ -/* eslint-disable max-len */ - -const BLEND = 0; -const OFFSET = 1; -const CULLING = 2; -const DEPTH_TEST = 3; -const WINDING = 4; - -/** - * This is a webGL state. It is passed The WebGL StateManager. - * Each mesh renderered may require webGL to be in a different state. - * For example you may want different blend mode or to enable polygon offsets - * - * @class - * @memberof PIXI - */ -export default class State -{ - /** - * - */ - constructor() - { - this.data = 0; - - this.blendMode = 0; - this.polygonOffset = 0; - - this.blend = true; - // this.depthTest = true; - } - - /** - * Activates blending of the computed fragment color values - * - * @member {boolean} - */ - get blend() - { - return !!(this.data & (1 << BLEND)); - } - - set blend(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << BLEND)) !== value) - { - this.data ^= (1 << BLEND); - } - } - - /** - * Activates adding an offset to depth values of polygon's fragments - * - * @member {boolean} - * @default false - */ - get offsets() - { - return !!(this.data & (1 << OFFSET)); - } - - set offsets(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << OFFSET)) !== value) - { - this.data ^= (1 << OFFSET); - } - } - - /** - * Activates culling of polygons. - * - * @member {boolean} - * @default false - */ - get culling() - { - return !!(this.data & (1 << CULLING)); - } - - set culling(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << CULLING)) !== value) - { - this.data ^= (1 << CULLING); - } - } - - /** - * Activates depth comparisons and updates to the depth buffer. - * - * @member {boolean} - * @default false - */ - get depthTest() - { - return !!(this.data & (1 << DEPTH_TEST)); - } - - set depthTest(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << DEPTH_TEST)) !== value) - { - this.data ^= (1 << DEPTH_TEST); - } - } - - /** - * Specifies whether or not front or back-facing polygons can be culled. - * @member {boolean} - * @default false - */ - get clockwiseFrontFace() - { - return !!(this.data & (1 << WINDING)); - } - - set clockwiseFrontFace(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << WINDING)) !== value) - { - this.data ^= (1 << WINDING); - } - } - - /** - * The blend mode to be applied when this state is set. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. - * Setting this mode to anything other than NO_BLEND will automatically switch blending on. - * - * @member {boolean} - * @default PIXI.BLEND_MODES.NORMAL - * @see PIXI.BLEND_MODES - */ - get blendMode() - { - return this._blendMode; - } - - set blendMode(value) // eslint-disable-line require-jsdoc - { - // 17 is NO BLEND - this.blend = (value !== 17); - this._blendMode = value; - } - - /** - * The polygon offset. Setting this property to anything other than 0 will automatically enable poygon offset fill. - * - * @member {number} - * @default 0 - */ - get polygonOffset() - { - return this._polygonOffset; - } - - set polygonOffset(value) // eslint-disable-line require-jsdoc - { - this.offsets = !!value; - this._polygonOffset = value; - } -} - diff --git a/packages/core/src/renderers/filters/Filter.js b/packages/core/src/renderers/filters/Filter.js deleted file mode 100644 index 2073b8d..0000000 --- a/packages/core/src/renderers/filters/Filter.js +++ /dev/null @@ -1,140 +0,0 @@ -import Shader from '../../shader/Shader'; -import Program from '../../shader/Program'; -import State from '../State'; -import { settings } from '@pixi/settings'; -// import extractUniformsFromSrc from './extractUniformsFromSrc'; -import defaultVertex from './defaultFilter.vert'; -import defaultFragment from './defaultFilter.frag'; - -// let math = require('../../math'); -/** - * @class - * @memberof PIXI - * @extends PIXI.Shader - */ -export default class Filter extends Shader -{ - /** - * @param {string} [vertexSrc] - The source of the vertex shader. - * @param {string} [fragmentSrc] - The source of the fragment shader. - * @param {object} [uniforms] - Custom uniforms to use to augment the built-in ones. - */ - constructor(vertexSrc, fragmentSrc, uniforms) - { - const program = Program.from(vertexSrc, fragmentSrc); - - super(program, uniforms); - - /** - * The padding of the filter. Some filters require extra space to breath such as a blur. - * Increasing this will add extra width and height to the bounds of the object that the - * filter is applied to. - * - * @member {number} - */ - this.padding = 0; - - /** - * The resolution of the filter. Setting this to be lower will lower the quality but - * increase the performance of the filter. - * - * @member {number} - */ - this.resolution = settings.RESOLUTION; - - /** - * If enabled is true the filter is applied, if false it will not. - * - * @member {boolean} - */ - this.enabled = true; - - /** - * If enabled, PixiJS will fit the filter area into boundaries for better performance. - * Switch it off if it does not work for specific shader. - * - * @member {boolean} - */ - this.autoFit = true; - - /** - * Legacy filters use position and uvs from attributes - * @member {boolean} - * @readonly - */ - this.legacy = !!this.program.attributeData.aTextureCoord; - - /** - * the webGL state the filter requires to render - * @member {PIXI.State} - */ - this.state = new State(); - } - - /** - * Applies the filter - * - * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from - * @param {PIXI.RenderTarget} input - The input render target. - * @param {PIXI.RenderTarget} output - The target to output to. - * @param {boolean} clear - Should the output be cleared before rendering to it - * @param {object} [currentState] - It's current state of filter. - * There are some useful properties in the currentState : - * target, filters, sourceFrame, destinationFrame, renderTarget, resolution - */ - apply(filterManager, input, output, clear, currentState, derp) // eslint-disable-line no-unused-vars - { - // do as you please! - - filterManager.applyFilter(this, input, output, clear, currentState, derp); - - // or just do a regular render.. - } - - /** - * Sets the blendmode of the filter - * - * @member {number} - * @default PIXI.BLEND_MODES.NORMAL - */ - get blendMode() - { - return this.state.blendMode; - } - - set blendMode(value) // eslint-disable-line require-jsdoc - { - this.state.blendMode = value; - } - - /** - * The default vertex shader source - * - * @static - * @constant - */ - static get defaultVertexSrc() - { - return defaultVertex; - } - - /** - * The default fragment shader source - * - * @static - * @constant - */ - static get defaultFragmentSrc() - { - return defaultFragment; - } -} - -/** - * Used for caching shader IDs - * - * @static - * @private - */ -Filter.SOURCE_KEY_MAP = {}; - diff --git a/packages/core/src/renderers/filters/defaultFilter.frag b/packages/core/src/renderers/filters/defaultFilter.frag deleted file mode 100644 index a14c1be..0000000 --- a/packages/core/src/renderers/filters/defaultFilter.frag +++ /dev/null @@ -1,22 +0,0 @@ -varying vec2 vTextureCoord; -varying vec2 vFilterCoord; - -uniform sampler2D uSampler; -uniform sampler2D filterSampler; - -void main(void){ - vec4 masky = texture2D(filterSampler, vFilterCoord); - vec4 sample = texture2D(uSampler, vTextureCoord); - vec4 color; - if(mod(vFilterCoord.x, 1.0) > 0.5) - { - color = vec4(1.0, 0.0, 0.0, 1.0); - } - else - { - color = vec4(0.0, 1.0, 0.0, 1.0); - } - // gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0); - gl_FragColor = mix(sample, masky, 0.5); - gl_FragColor *= sample.a; -} diff --git a/packages/core/src/renderers/filters/defaultFilter.vert b/packages/core/src/renderers/filters/defaultFilter.vert deleted file mode 100644 index e1febca..0000000 --- a/packages/core/src/renderers/filters/defaultFilter.vert +++ /dev/null @@ -1,14 +0,0 @@ -attribute vec2 aVertexPosition; -attribute vec2 aTextureCoord; - -uniform mat3 projectionMatrix; -uniform mat3 filterMatrix; - -varying vec2 vTextureCoord; -varying vec2 vFilterCoord; - -void main(void){ - gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); - vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy; - vTextureCoord = aTextureCoord ; -} diff --git a/packages/core/src/renderers/filters/filterTransforms.js b/packages/core/src/renderers/filters/filterTransforms.js deleted file mode 100644 index 87a3cd6..0000000 --- a/packages/core/src/renderers/filters/filterTransforms.js +++ /dev/null @@ -1,50 +0,0 @@ -import { Matrix } from '@pixi/math'; - -/** - * Calculates the mapped matrix - * @param filterArea {Rectangle} The filter area - * @param sprite {Sprite} the target sprite - * @param outputMatrix {Matrix} @alvin - * @private - */ -// this returns a matrix that will normalise map filter cords in the filter to screen space -export function calculateScreenSpaceMatrix(outputMatrix, filterArea, textureSize) -{ - // TODO unwrap? - const mappedMatrix = outputMatrix.identity(); - - mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); - - mappedMatrix.scale(textureSize.width, textureSize.height); - - return mappedMatrix; -} - -export function calculateNormalizedScreenSpaceMatrix(outputMatrix, filterArea, textureSize) -{ - const mappedMatrix = outputMatrix.identity(); - - mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); - - const translateScaleX = (textureSize.width / filterArea.width); - const translateScaleY = (textureSize.height / filterArea.height); - - mappedMatrix.scale(translateScaleX, translateScaleY); - - return mappedMatrix; -} - -// this will map the filter coord so that a texture can be used based on the transform of a sprite -export function calculateSpriteMatrix(outputMatrix, filterArea, textureSize, sprite) -{ - const orig = sprite._texture.orig; - const mappedMatrix = outputMatrix.set(textureSize.width, 0, 0, textureSize.height, filterArea.x, filterArea.y); - const worldTransform = sprite.worldTransform.copyTo(Matrix.TEMP_MATRIX); - - worldTransform.invert(); - mappedMatrix.prepend(worldTransform); - mappedMatrix.scale(1.0 / orig.width, 1.0 / orig.height); - mappedMatrix.translate(sprite.anchor.x, sprite.anchor.y); - - return mappedMatrix; -} diff --git a/packages/core/src/renderers/filters/spriteMask/SpriteMaskFilter.js b/packages/core/src/renderers/filters/spriteMask/SpriteMaskFilter.js deleted file mode 100644 index d5a91a4..0000000 --- a/packages/core/src/renderers/filters/spriteMask/SpriteMaskFilter.js +++ /dev/null @@ -1,63 +0,0 @@ -import Filter from '../Filter'; -import { Matrix } from '@pixi/math'; -import vertex from './spriteMaskFilter.vert'; -import fragment from './spriteMaskFilter.frag'; -import { default as TextureMatrix } from '../../../textures/TextureMatrix'; - -/** - * The SpriteMaskFilter class - * - * @class - * @extends PIXI.Filter - * @memberof PIXI - */ -export default class SpriteMaskFilter extends Filter -{ - /** - * @param {PIXI.Sprite} sprite - the target sprite - */ - constructor(sprite) - { - const maskMatrix = new Matrix(); - - super(vertex, fragment); - - sprite.renderable = false; - - this.maskSprite = sprite; - this.maskMatrix = maskMatrix; - } - - /** - * Applies the filter - * - * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from - * @param {PIXI.RenderTarget} input - The input render target. - * @param {PIXI.RenderTarget} output - The target to output to. - */ - apply(filterManager, input, output) - { - const maskSprite = this.maskSprite; - const tex = this.maskSprite.texture; - - if (!tex.valid) - { - return; - } - if (!tex.transform) - { - // margin = 0.0, let it bleed a bit, shader code becomes easier - // assuming that atlas textures were made with 1-pixel padding - tex.transform = new TextureMatrix(tex, 0.0); - } - tex.transform.update(); - - this.uniforms.mask = tex; - this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite) - .prepend(tex.transform.mapCoord); - this.uniforms.alpha = maskSprite.worldAlpha; - this.uniforms.maskClamp = tex.transform.uClampFrame; - - filterManager.applyFilter(this, input, output); - } -} diff --git a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.frag b/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.frag deleted file mode 100644 index 0e4aef8..0000000 --- a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.frag +++ /dev/null @@ -1,23 +0,0 @@ -varying vec2 vMaskCoord; -varying vec2 vTextureCoord; - -uniform sampler2D uSampler; -uniform sampler2D mask; -uniform float alpha; -uniform vec4 maskClamp; - -void main(void) -{ - float clip = step(3.5, - step(maskClamp.x, vMaskCoord.x) + - step(maskClamp.y, vMaskCoord.y) + - step(vMaskCoord.x, maskClamp.z) + - step(vMaskCoord.y, maskClamp.w)); - - vec4 original = texture2D(uSampler, vTextureCoord); - vec4 masky = texture2D(mask, vMaskCoord); - - original *= (masky.r * masky.a * alpha * clip); - - gl_FragColor = original; -} diff --git a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.vert b/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.vert deleted file mode 100644 index d23a30c..0000000 --- a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.vert +++ /dev/null @@ -1,16 +0,0 @@ -attribute vec2 aVertexPosition; -attribute vec2 aTextureCoord; - -uniform mat3 projectionMatrix; -uniform mat3 otherMatrix; - -varying vec2 vMaskCoord; -varying vec2 vTextureCoord; - -void main(void) -{ - gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); - - vTextureCoord = aTextureCoord; - vMaskCoord = ( otherMatrix * vec3( aTextureCoord, 1.0) ).xy; -} diff --git a/packages/core/src/renderers/systems/BatchSystem.js b/packages/core/src/renderers/systems/BatchSystem.js deleted file mode 100644 index 70e9518..0000000 --- a/packages/core/src/renderers/systems/BatchSystem.js +++ /dev/null @@ -1,68 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import ObjectRenderer from '../utils/ObjectRenderer'; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ - -export default class BatchSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this manager works for. - */ - constructor(renderer) - { - super(renderer); - - /** - * An empty renderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.emptyRenderer = new ObjectRenderer(renderer); - - /** - * The currently active ObjectRenderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.currentRenderer = this.emptyRenderer; - } - - /** - * Changes the current renderer to the one given in parameter - * - * @param {PIXI.ObjectRenderer} objectRenderer - The object renderer to use. - */ - setObjectRenderer(objectRenderer) - { - if (this.currentRenderer === objectRenderer) - { - return; - } - - this.currentRenderer.stop(); - this.currentRenderer = objectRenderer; - - this.renderer.state.setState(objectRenderer.state); - - this.currentRenderer.start(); - } - - /** - * This should be called if you wish to do some custom rendering - * It will basically render anything that may be batched up such as sprites - * - */ - flush() - { - this.setObjectRenderer(this.emptyRenderer); - } - - reset() - { - this.setObjectRenderer(this.emptyRenderer); - } -} diff --git a/packages/core/src/renderers/systems/ContextSystem.js b/packages/core/src/renderers/systems/ContextSystem.js deleted file mode 100644 index 43bf827..0000000 --- a/packages/core/src/renderers/systems/ContextSystem.js +++ /dev/null @@ -1,186 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { settings } from '@pixi/settings'; - -let CONTEXT_UID = 0; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class ContextSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this.webGLVersion = 1; - - this.handleContextLost = this.handleContextLost.bind(this); - this.handleContextRestored = this.handleContextRestored.bind(this); - - this.extensions = {}; - - renderer.view.addEventListener('webglcontextlost', this.handleContextLost, false); - renderer.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); - } - - get isLost() - { - return (!this.gl || this.gl.isContextLost()); - } - - contextChange(gl) - { - this.gl = gl; - - // restore a context if it was previously lost - if (gl.isContextLost() && gl.getExtension('WEBGL_lose_context')) - { - gl.getExtension('WEBGL_lose_context').restoreContext(); - } - } - - initFromContext(gl) - { - this.gl = gl; - this.validateContext(gl); - this.renderer.gl = gl; - this.renderer.CONTEXT_UID = CONTEXT_UID++; - this.renderer.runners.contextChange.run(gl); - } - - initFromOptions(options) - { - const gl = this.createContext(this.renderer.view, options); - - this.initFromContext(gl); - } - - /** - * Helper class to create a webGL Context - * - * @class - * @memberof PIXI.glCore - * @param canvas {HTMLCanvasElement} the canvas element that we will get the context from - * @param options {Object} An options object that gets passed in to the canvas element containing the context attributes, - * see https://developer.mozilla.org/en/docs/Web/API/HTMLCanvasElement/getContext for the options available - * @return {WebGLRenderingContext} the WebGL context - */ - createContext(canvas, options) - { - let gl; - - if (settings.PREFER_WEBGL_2) - { - gl = canvas.getContext('webgl2', options); - } - - if (gl) - { - this.webGLVersion = 2; - } - else - { - this.webGLVersion = 1; - - gl = canvas.getContext('webgl', options) - || canvas.getContext('experimental-webgl', options); - - if (!gl) - { - // fail, not able to get a context - throw new Error('This browser does not support webGL. Try using the canvas renderer'); - } - } - - this.gl = gl; - - this.getExtensions(); - - return gl; - } - - getExtensions() - { - // time to set up default etensions that pixi uses.. - const gl = this.gl; - const extensions = this.extensions; - - if (this.webGLVersion === 1) - { - extensions.drawBuffers = gl.getExtension('WEBGL_draw_buffers'); - extensions.depthTexture = gl.getExtension('WEBKIT_WEBGL_depth_texture'); - extensions.floatTexture = gl.getExtension('OES_texture_float'); - extensions.loseContext = gl.getExtension('WEBGL_lose_context'); - - extensions.vertexArrayObject = gl.getExtension('OES_vertex_array_object') - || gl.getExtension('MOZ_OES_vertex_array_object') - || gl.getExtension('WEBKIT_OES_vertex_array_object'); - } - - // we don't use any specific WebGL 2 ones yet! - } - - /** - * Handles a lost webgl context - * - * @private - * @param {WebGLContextEvent} event - The context lost event. - */ - handleContextLost(event) - { - event.preventDefault(); - } - - /** - * Handles a restored webgl context - * - * @private - */ - handleContextRestored() - { - this.renderer.runners.contextChange.run(this.gl); - } - - destroy() - { - const view = this.renderer.view; - - // remove listeners - view.removeEventListener('webglcontextlost', this.handleContextLost); - view.removeEventListener('webglcontextrestored', this.handleContextRestored); - - this.gl.useProgram(null); - - if (this.extensions.loseContext) - { - this.extensions.loseContext.loseContext(); - } - } - - postrender() - { - this.gl.flush(); - } - - validateContext(gl) - { - const attributes = gl.getContextAttributes(); - - // this is going to be fairly simple for now.. but at least we have room to grow! - if (!attributes.stencil) - { - /* eslint-disable max-len */ - - /* eslint-disable no-console */ - console.warn('Provided WebGL context does not have a stencil buffer, masks may not render correctly'); - /* eslint-enable no-console */ - - /* eslint-enable max-len */ - } - } -} diff --git a/packages/core/src/renderers/systems/FramebufferSystem.js b/packages/core/src/renderers/systems/FramebufferSystem.js deleted file mode 100644 index df7902b..0000000 --- a/packages/core/src/renderers/systems/FramebufferSystem.js +++ /dev/null @@ -1,246 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { Rectangle } from '@pixi/math'; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class FramebufferSystem extends WebGLSystem -{ - /** - * Sets up the renderer context and necessary buffers. - * - * @private - */ - contextChange() - { - this.gl = this.renderer.gl; - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - this.current = null; - this.viewport = new Rectangle(); - - this.drawBufferExtension = this.renderer.context.extensions.drawBuffers; - } - - bind(framebuffer, frame) - { - const gl = this.gl; - - this.current = framebuffer; - - if (framebuffer) - { - // TODO cacheing layer! - - const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID] || this.initFramebuffer(framebuffer); - - gl.bindFramebuffer(gl.FRAMEBUFFER, fbo.framebuffer); - // makesure all textures are unbound.. - - // now check for updates... - if (fbo.dirtyId !== framebuffer.dirtyId) - { - fbo.dirtyId = framebuffer.dirtyId; - - if (fbo.dirtyFormat !== framebuffer.dirtyFormat) - { - fbo.dirtyFormat = framebuffer.dirtyFormat; - this.updateFramebuffer(framebuffer); - } - else if (fbo.dirtySize !== framebuffer.dirtySize) - { - fbo.dirtySize = framebuffer.dirtySize; - this.resizeFramebuffer(framebuffer); - } - } - - for (let i = 0; i < framebuffer.colorTextures.length; i++) - { - if (framebuffer.colorTextures[i].texturePart) - { - this.renderer.texture.unbind(framebuffer.colorTextures[i].texture); - } - else - { - this.renderer.texture.unbind(framebuffer.colorTextures[i]); - } - } - - if (framebuffer.depthTexture) - { - this.renderer.texture.unbind(framebuffer.depthTexture); - } - - if (frame) - { - this.setViewport(frame.x, frame.y, frame.width, frame.height); - } - else - { - this.setViewport(0, 0, framebuffer.width, framebuffer.height); - } - } - else - { - gl.bindFramebuffer(gl.FRAMEBUFFER, null); - - if (frame) - { - this.setViewport(frame.x, frame.y, frame.width, frame.height); - } - else - { - this.setViewport(0, 0, this.renderer.width, this.renderer.height); - } - } - } - - setViewport(x, y, width, height) - { - const v = this.viewport; - - if (v.width !== width || v.height !== height || v.x !== x || v.y !== y) - { - v.x = x; - v.y = y; - v.width = width; - v.height = height; - - this.gl.viewport(x, y, width, height); - } - } - - get size() - { - if (this.current) - { - // TODO store temp - return { x: 0, y: 0, width: this.current.width, height: this.current.height }; - } - - return { x: 0, y: 0, width: this.renderer.width, height: this.renderer.height }; - } - - clear(r, g, b, a) - { - const gl = this.gl; - - // TODO clear color can be set only one right? - gl.clearColor(r, g, b, a); - gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); - } - - // private functions... - - initFramebuffer(framebuffer) - { - const gl = this.gl; - - // TODO - make this a class? - const fbo = { - framebuffer: gl.createFramebuffer(), - stencil: null, - dirtyId: 0, - dirtyFormat: 0, - dirtySize: 0, - }; - - framebuffer.glFrameBuffers[this.CONTEXT_UID] = fbo; - - return fbo; - } - - resizeFramebuffer(framebuffer) - { - const gl = this.gl; - - if (framebuffer.stencil || framebuffer.depth) - { - gl.bindRenderbuffer(gl.RENDERBUFFER, this.stencil); - gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); - } - } - - updateFramebuffer(framebuffer) - { - const gl = this.gl; - - const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID]; - - // bind the color texture - const colorTextures = framebuffer.colorTextures; - - let count = colorTextures.length; - - if (!this.drawBufferExtension) - { - count = Math.min(count, 1); - } - - const activeTextures = []; - - for (let i = 0; i < count; i++) - { - const texture = framebuffer.colorTextures[i]; - - if (texture.texturePart) - { - this.renderer.texture.bind(texture.texture, 0); - - gl.framebufferTexture2D(gl.FRAMEBUFFER, - gl.COLOR_ATTACHMENT0 + i, - gl.TEXTURE_CUBE_MAP_NEGATIVE_X + texture.side, - texture.texture._glTextures[this.CONTEXT_UID].texture, - 0); - } - else - { - this.renderer.texture.bind(texture, 0); - - gl.framebufferTexture2D(gl.FRAMEBUFFER, - gl.COLOR_ATTACHMENT0 + i, - gl.TEXTURE_2D, - texture._glTextures[this.CONTEXT_UID].texture, - 0); - } - - activeTextures.push(gl.COLOR_ATTACHMENT0 + i); - } - - if (this.drawBufferExtension && activeTextures.length > 1) - { - this.drawBufferExtension.drawBuffersWEBGL(activeTextures); - } - - if (framebuffer.depthTexture) - { - const depthTextureExt = this.renderer.context.extensions.depthTexture; - - if (depthTextureExt) - { - const depthTexture = framebuffer.depthTexture; - - this.renderer.texture.bind(depthTexture, 0); - - gl.framebufferTexture2D(gl.FRAMEBUFFER, - gl.DEPTH_ATTACHMENT, - gl.TEXTURE_2D, - depthTexture._glTextures[this.CONTEXT_UID].texture, - 0); - } - } - - if (framebuffer.stencil || framebuffer.depth) - { - fbo.stencil = gl.createRenderbuffer(); - - gl.bindRenderbuffer(gl.RENDERBUFFER, fbo.stencil); - - // TODO.. this is depth AND stencil? - gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, fbo.stencil); - gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); - // fbo.enableStencil(); - } - } -} diff --git a/packages/core/src/renderers/systems/MaskSystem.js b/packages/core/src/renderers/systems/MaskSystem.js deleted file mode 100644 index d9c4b1a..0000000 --- a/packages/core/src/renderers/systems/MaskSystem.js +++ /dev/null @@ -1,199 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import AlphaMaskFilter from '../filters/spriteMask/SpriteMaskFilter'; - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class MaskSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - // TODO - we don't need both! - this.scissor = false; - this.scissorData = null; - this.scissorRenderTarget = null; - - this.enableScissor = false; - - this.alphaMaskPool = []; - this.alphaMaskIndex = 0; - } - - /** - * Applies the Mask and adds it to the current filter stack. - * - * @param {PIXI.DisplayObject} target - Display Object to push the mask to - * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. - */ - push(target, maskData) - { - // TODO the root check means scissor rect will not - // be used on render textures more info here: - // https://github.com/pixijs/pixi.js/pull/3545 - - if (maskData.texture) - { - this.pushSpriteMask(target, maskData); - } - else if (this.enableScissor - && !this.scissor - && this.renderer._activeRenderTarget.root - && !this.renderer.stencil.stencilMaskStack.length - && maskData.isFastRect()) - { - const matrix = maskData.worldTransform; - - let rot = Math.atan2(matrix.b, matrix.a); - - // use the nearest degree! - rot = Math.round(rot * (180 / Math.PI)); - - if (rot % 90) - { - this.pushStencilMask(maskData); - } - else - { - this.pushScissorMask(target, maskData); - } - } - else - { - this.pushStencilMask(maskData); - } - } - - /** - * Removes the last mask from the mask stack and doesn't return it. - * - * @param {PIXI.DisplayObject} target - Display Object to pop the mask from - * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. - */ - pop(target, maskData) - { - if (maskData.texture) - { - this.popSpriteMask(target, maskData); - } - else if (this.enableScissor && !this.renderer.stencil.stencilMaskStack.length) - { - this.popScissorMask(target, maskData); - } - else - { - this.popStencilMask(target, maskData); - } - } - - /** - * Applies the Mask and adds it to the current filter stack. - * - * @param {PIXI.RenderTarget} target - Display Object to push the sprite mask to - * @param {PIXI.Sprite} maskData - Sprite to be used as the mask - */ - pushSpriteMask(target, maskData) - { - let alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; - - if (!alphaMaskFilter) - { - alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; - } - - alphaMaskFilter[0].resolution = this.renderer.resolution; - alphaMaskFilter[0].maskSprite = maskData; - - // TODO - may cause issues! - target.filterArea = maskData.getBounds(true); - - this.renderer.filterSystem.pushFilter(target, alphaMaskFilter); - - this.alphaMaskIndex++; - } - - /** - * Removes the last filter from the filter stack and doesn't return it. - * - */ - popSpriteMask() - { - this.renderer.filterSystem.popFilter(); - this.alphaMaskIndex--; - } - - /** - * Applies the Mask and adds it to the current filter stack. - * - * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. - */ - pushStencilMask(maskData) - { - this.renderer.batch.flush(); - this.renderer.stencil.pushStencil(maskData); - } - - /** - * Removes the last filter from the filter stack and doesn't return it. - * - */ - popStencilMask() - { - // this.renderer.currentRenderer.stop(); - this.renderer.stencil.popStencil(); - } - - /** - * - * @param {PIXI.DisplayObject} target - Display Object to push the mask to - * @param {PIXI.Graphics} maskData - The masking data. - */ - pushScissorMask(target, maskData) - { - maskData.renderable = true; - - const renderTarget = this.renderer._activeRenderTarget; - - const bounds = maskData.getBounds(); - - bounds.fit(renderTarget.size); - maskData.renderable = false; - - this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); - - const resolution = this.renderer.resolution; - - this.renderer.gl.scissor( - bounds.x * resolution, - (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, - bounds.width * resolution, - bounds.height * resolution - ); - - this.scissorRenderTarget = renderTarget; - this.scissorData = maskData; - this.scissor = true; - } - - /** - * - * - */ - popScissorMask() - { - this.scissorRenderTarget = null; - this.scissorData = null; - this.scissor = false; - - // must be scissor! - const gl = this.renderer.gl; - - gl.disable(gl.SCISSOR_TEST); - } -} diff --git a/packages/core/src/renderers/systems/ProjectionSystem.js b/packages/core/src/renderers/systems/ProjectionSystem.js deleted file mode 100644 index ec5efdb..0000000 --- a/packages/core/src/renderers/systems/ProjectionSystem.js +++ /dev/null @@ -1,73 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { Matrix } from '@pixi/math'; - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ - -export default class ProjectionSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this.projectionMatrix = new Matrix(); - } - - update(destinationFrame, sourceFrame, resolution, root) - { - this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; - this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; - - this.calculateProjection(this.destinationFrame, this.sourceFrame, resolution, root); - - this.renderer.globalUniforms.uniforms.projectionMatrix = this.projectionMatrix; - this.renderer.globalUniforms.update(); - } - - /** - * Updates the projection matrix based on a projection frame (which is a rectangle) - * - * @param {Rectangle} destinationFrame - The destination frame. - * @param {Rectangle} sourceFrame - The source frame. - */ - calculateProjection(destinationFrame, sourceFrame, resolution, root) - { - const pm = this.projectionMatrix; - - // I don't think we will need this line.. - // pm.identity(); - - if (!root) - { - pm.a = (1 / destinationFrame.width * 2) * resolution; - pm.d = (1 / destinationFrame.height * 2) * resolution; - - pm.tx = -1 - (sourceFrame.x * pm.a); - pm.ty = -1 - (sourceFrame.y * pm.d); - } - else - { - pm.a = (1 / destinationFrame.width * 2) * resolution; - pm.d = (-1 / destinationFrame.height * 2) * resolution; - - pm.tx = -1 - (sourceFrame.x * pm.a); - pm.ty = 1 - (sourceFrame.y * pm.d); - } - } - - /** - * Sets the transform of the active render target to the given matrix - * - * @param {PIXI.Matrix} matrix - The transformation matrix - */ - setTransform()// matrix) - { - // this._activeRenderTarget.transform = matrix; - } -} diff --git a/packages/core/src/renderers/systems/RenderTextureSystem.js b/packages/core/src/renderers/systems/RenderTextureSystem.js deleted file mode 100644 index a133ef1..0000000 --- a/packages/core/src/renderers/systems/RenderTextureSystem.js +++ /dev/null @@ -1,115 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { Rectangle } from '@pixi/math'; - -const tempRect = new Rectangle(); - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ - -export default class RenderTextureSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this.clearColor = renderer._backgroundColorRgba; - - // TODO moe this property somewhere else! - this.defaultMaskStack = []; - this.defaultFilterStack = [{}]; - - // empty render texture? - this.renderTexture = null; - - this.destinationFrame = new Rectangle(); - } - - bind(renderTexture, sourceFrame, destinationFrame) - { - // TODO - do we want this?? - if (this.renderTexture === renderTexture) return; - this.renderTexture = renderTexture; - - const renderer = this.renderer; - - if (renderTexture) - { - const baseTexture = renderTexture.baseTexture; - - if (!destinationFrame) - { - tempRect.width = baseTexture.realWidth; - tempRect.height = baseTexture.realHeight; - - destinationFrame = tempRect; - } - - if (!sourceFrame) - { - sourceFrame = destinationFrame; - } - - this.renderer.framebuffer.bind(baseTexture.frameBuffer, destinationFrame); - - this.renderer.projection.update(destinationFrame, sourceFrame, baseTexture.resolution, false); - this.renderer.stencil.setMaskStack(baseTexture.stencilMaskStack); - } - else - { - // TODO these validation checks happen deeper down.. - // thing they can be avoided.. - if (!destinationFrame) - { - tempRect.width = renderer.width; - tempRect.height = renderer.height; - - destinationFrame = tempRect; - } - - if (!sourceFrame) - { - sourceFrame = destinationFrame; - } - - renderer.framebuffer.bind(null, destinationFrame); - - // TODO store this.. - this.renderer.projection.update(destinationFrame, sourceFrame, this.renderer.resolution, true); - this.renderer.stencil.setMaskStack(this.defaultMaskStack); - } - - this.destinationFrame.copyFrom(destinationFrame); - } - - /** - * Erases the render texture and fills the drawing area with a colour - * - * @param {number} [clearColor] - The colour - * @return {PIXI.Renderer} Returns itself. - */ - clear(clearColor) - { - if (this.renderTexture) - { - clearColor = clearColor || this.renderTexture.baseTexture.clearColor; - } - else - { - clearColor = clearColor || this.clearColor; - } - - this.renderer.framebuffer.clear(clearColor[0], clearColor[1], clearColor[2], clearColor[3]); - } - - resize()// screenWidth, screenHeight) - { - // resize the root only! - this.bind(null); - } -} diff --git a/packages/core/src/renderers/systems/StateSystem.js b/packages/core/src/renderers/systems/StateSystem.js deleted file mode 100755 index d7f4caf..0000000 --- a/packages/core/src/renderers/systems/StateSystem.js +++ /dev/null @@ -1,288 +0,0 @@ -import mapWebGLBlendModesToPixi from '../utils/mapWebGLBlendModesToPixi'; -import WebGLSystem from './WebGLSystem'; -import WebGLState from '../State'; - -const BLEND = 0; -const OFFSET = 1; -const CULLING = 2; -const DEPTH_TEST = 3; -const WINDING = 4; - -/** - * A WebGL state machines - * - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class StateSystem extends WebGLSystem -{ - /** - * @param {WebGLRenderingContext} gl - The current WebGL rendering context - */ - constructor(renderer) - { - super(renderer); - - this.gl = null; - - this.maxAttribs = null; - - // check we have vao.. - this.nativeVaoExtension = null; - - this.attribState = null; - - this.stateId = 0; - this.polygonOffset = 0; - this.blendMode = 17; - - this.map = []; - - // map functions for when we set state.. - this.map[BLEND] = this.setBlend; - this.map[OFFSET] = this.setOffset; - this.map[CULLING] = this.setCullFace; - this.map[DEPTH_TEST] = this.setDepthTest; - this.map[WINDING] = this.setFrontFace; - - this.checks = []; - - this.defaultState = new WebGLState(); - this.defaultState.blend = true; - this.defaultState.depth = true; - } - - contextChange(gl) - { - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; - - this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); - - // check we have vao.. - this.nativeVaoExtension = ( - gl.getExtension('OES_vertex_array_object') - || gl.getExtension('MOZ_OES_vertex_array_object') - || gl.getExtension('WEBKIT_OES_vertex_array_object') - ); - - this.attribState = { - tempAttribState: new Array(this.maxAttribs), - attribState: new Array(this.maxAttribs), - }; - - this.blendModes = mapWebGLBlendModesToPixi(gl); - - this.setState(this.defaultState); - - this.reset(); - } - - /** - * Sets the current state - * - * @param {*} state - The state to set. - */ - setState(state) - { - state = state || this.defaultState; - - // TODO maybe to an object check? ( this.state === state )? - if (this.stateId !== state.data) - { - let diff = this.stateId ^ state.data; - let i = 0; - - // order from least to most common - while (diff) - { - if (diff & 1) - { - // state change! - this.map[i].call(this, !!(state.data & (1 << i))); - } - - diff = diff >> 1; - i++; - } - - this.stateId = state.data; - } - - // based on the above settings we check for specific modes.. - // for example if blend is active we check and set the blend modes - // or of polygon offset is active we check the poly depth. - for (let i = 0; i < this.checks.length; i++) - { - this.checks[i](this, state); - } - } - - /** - * Enables or disabled blending. - * - * @param {boolean} value - Turn on or off webgl blending. - */ - setBlend(value) - { - this.updateCheck(StateSystem.checkBlendMode, value); - - this.gl[value ? 'enable' : 'disable'](this.gl.BLEND); - } - - /** - * Enables or disable polygon offset fill - * - * @param {boolean} value - Turn on or off webgl polygon offset testing. - */ - setOffset(value) - { - this.gl[value ? 'enable' : 'disable'](this.gl.POLYGON_OFFSET_FILL); - } - - /** - * Sets whether to enable or disable depth test. - * - * @param {boolean} value - Turn on or off webgl depth testing. - */ - setDepthTest(value) - { - this.gl[value ? 'enable' : 'disable'](this.gl.DEPTH_TEST); - } - - /** - * Sets whether to enable or disable cull face. - * - * @param {boolean} value - Turn on or off webgl cull face. - */ - setCullFace(value) - { - this.gl[value ? 'enable' : 'disable'](this.gl.CULL_FACE); - } - - /** - * Sets the gl front face. - * - * @param {boolean} value - true is clockwise and false is counter-clockwise - */ - setFrontFace(value) - { - this.gl.frontFace(this.gl[value ? 'CW' : 'CCW']); - } - - /** - * Sets the blend mode. - * - * @param {number} value - The blend mode to set to. - */ - setBlendMode(value) - { - if (value === this.blendMode) - { - return; - } - - this.blendMode = value; - this.gl.blendFunc(this.blendModes[value][0], this.blendModes[value][1]); - } - - /** - * Sets the polygon offset. - * - * @param {number} value - the polygon offset - * @param {number} scale - the polygon offset scale - */ - setPolygonOffset(value, scale) - { - this.gl.polygonOffset(value, scale); - } - - /** - * Disables all the vaos in use - * - */ - resetAttributes() - { - for (let i = 0; i < this.attribState.tempAttribState.length; i++) - { - this.attribState.tempAttribState[i] = 0; - } - - for (let i = 0; i < this.attribState.attribState.length; i++) - { - this.attribState.attribState[i] = 0; - } - - // im going to assume one is always active for performance reasons. - for (let i = 1; i < this.maxAttribs; i++) - { - this.gl.disableVertexAttribArray(i); - } - } - - // used - /** - * Resets all the logic and disables the vaos - */ - reset() - { - // unbind any VAO if they exist.. - if (this.nativeVaoExtension) - { - this.nativeVaoExtension.bindVertexArrayOES(null); - } - - // reset all attributes.. - this.resetAttributes(); - - this.gl.pixelStorei(this.gl.UNPACK_FLIP_Y_WEBGL, false); - - this.setBlendMode(0); - - // TODO? - // this.setState(this.defaultState); - } - - /** - * checks to see which updates should be checked based on which settings have been activated - * for example if blend is enabled then we shold check the blend modes each time the state is changed - * or if poygon fill is activated then we need to check if the polygone offset changes. - * The idea is that we only check what we have too. - * - * @param {Function} func the checking function to add or remove - * @param {boolean} value should the check function be added or removed. - */ - updateCheck(func, value) - { - const index = this.checks.indexOf(func); - - if (value && index === -1) - { - this.checks.push(func); - } - else if (!value && index !== -1) - { - this.checks.splice(index, 1); - } - } - - /** - * A private little wrapper function that we call to check the blend mode. - * - * @static - * @private - * @param {PIXI.StateSystem} System the System to perform the state check on - * @param {PIXI.State} state the state that the blendMode will pulled from - */ - static checkBlendMode(system, state) - { - system.setBlendMode(state.blendMode); - } - - // TODO - add polygon offset? -} diff --git a/packages/core/src/renderers/systems/StencilSystem.js b/packages/core/src/renderers/systems/StencilSystem.js deleted file mode 100644 index 5d538cb..0000000 --- a/packages/core/src/renderers/systems/StencilSystem.js +++ /dev/null @@ -1,133 +0,0 @@ -import WebGLSystem from './WebGLSystem'; - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class StencilSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - this.stencilMaskStack = []; - } - - /** - * Changes the mask stack that is used by this System. - * - * @param {PIXI.Graphics[]} stencilMaskStack - The mask stack - */ - setMaskStack(stencilMaskStack) - { - const gl = this.renderer.gl; - - if (stencilMaskStack.length !== this.stencilMaskStack.length) - { - if (stencilMaskStack.length === 0) - { - gl.disable(gl.STENCIL_TEST); - } - else - { - gl.enable(gl.STENCIL_TEST); - } - } - - this.stencilMaskStack = stencilMaskStack; - } - - /** - * Applies the Mask and adds it to the current stencil stack. @alvin - * - * @param {PIXI.Graphics} graphics - The mask - */ - pushStencil(graphics) - { - this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); - - // this.renderer._activeRenderTarget.attachStencilBuffer(); - - const gl = this.renderer.gl; - const prevMaskCount = this.stencilMaskStack.length; - - if (prevMaskCount === 0) - { - gl.enable(gl.STENCIL_TEST); - } - - this.stencilMaskStack.push(graphics); - - // Increment the refference stencil value where the new mask overlaps with the old ones. - gl.colorMask(false, false, false, false); - gl.stencilFunc(gl.EQUAL, prevMaskCount, this._getBitwiseMask()); - gl.stencilOp(gl.KEEP, gl.KEEP, gl.INCR); - this.renderer.plugins.graphics.render(graphics); - - this._useCurrent(); - } - - /** - * Removes the last mask from the stencil stack. @alvin - */ - popStencil() - { - this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); - - const gl = this.renderer.gl; - const graphics = this.stencilMaskStack.pop(); - - if (this.stencilMaskStack.length === 0) - { - // the stack is empty! - gl.disable(gl.STENCIL_TEST); - gl.clear(gl.STENCIL_BUFFER_BIT); - gl.clearStencil(0); - } - else - { - // Decrement the refference stencil value where the popped mask overlaps with the other ones - gl.colorMask(false, false, false, false); - gl.stencilOp(gl.KEEP, gl.KEEP, gl.DECR); - this.renderer.plugins.graphics.render(graphics); - - this._useCurrent(); - } - } - - /** - * Setup renderer to use the current stencil data. - */ - _useCurrent() - { - const gl = this.renderer.gl; - - gl.colorMask(true, true, true, true); - gl.stencilFunc(gl.EQUAL, this.stencilMaskStack.length, this._getBitwiseMask()); - gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP); - } - - /** - * Fill 1s equal to the number of acitve stencil masks. - * - * @return {number} The bitwise mask. - */ - _getBitwiseMask() - { - return (1 << this.stencilMaskStack.length) - 1; - } - - /** - * Destroys the mask stack. - * - */ - destroy() - { - super.destroy(this); - - this.stencilMaskStack = null; - } -} diff --git a/packages/core/src/renderers/systems/WebGLSystem.js b/packages/core/src/renderers/systems/WebGLSystem.js deleted file mode 100644 index d3d5b31..0000000 --- a/packages/core/src/renderers/systems/WebGLSystem.js +++ /dev/null @@ -1,41 +0,0 @@ -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this manager works for. - */ - constructor(renderer) - { - /** - * The renderer this manager works for. - * - * @member {PIXI.Renderer} - */ - this.renderer = renderer; - - this.renderer.runners.contextChange.add(this); - } - - /** - * Generic method called when there is a WebGL context change. - * - */ - contextChange() - { - // do some codes init! - } - - /** - * Generic destroy methods to be overridden by the subclass - * - */ - destroy() - { - this.renderer.runners.contextChange.remove(this); - this.renderer = null; - } -} diff --git a/packages/core/src/renderers/systems/filter/FilterSystem.js b/packages/core/src/renderers/systems/filter/FilterSystem.js deleted file mode 100644 index e615037..0000000 --- a/packages/core/src/renderers/systems/filter/FilterSystem.js +++ /dev/null @@ -1,431 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; - -import RenderTexture from '../../../textures/RenderTexture'; -import Quad from '../../utils/Quad'; -import QuadUv from '../../utils/QuadUv'; -import { Rectangle } from '@pixi/math'; -import * as filterTransforms from '../../filters/filterTransforms'; -import bitTwiddle from 'bit-twiddle'; -import UniformGroup from '../../../shader/UniformGroup'; -import { DRAW_MODES } from '../../../../../constants'; - -// -/** - * @ignore - * @class - */ -class FilterState -{ - /** - * - */ - constructor() - { - this.renderTexture = null; - this.sourceFrame = new Rectangle(); - this.destinationFrame = new Rectangle(); - this.filters = []; - this.target = null; - this.legacy = false; - this.resolution = 1; - } -} - -/** - * @class - * @memberof PIXI.systems - * @extends PIXI.systems.WebGLSystem - */ -export default class FilterSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - /** - * stores a bunch of PO2 textures used for filtering - * @type {Object} - */ - this.texturePool = {}; - - /** - * a pool for storing filter states, save us creating new ones each tick - * @type {Array} - */ - this.statePool = []; - - /** - * A very simple geometry used when drawing a filter effect to the screen - * @type {Quad} - */ - this.quad = new Quad(); - - this.quadUv = new QuadUv(); - - /** - * Temporary rect for maths - * @type {PIXI.Rectangle} - */ - this.tempRect = new Rectangle(); - - this.activeState = {}; - - /** - * this uniform group is attached to filter uniforms when used - * @type {UniformGroup} - */ - this.globalUniforms = new UniformGroup({ - sourceFrame: this.tempRect, - destinationFrame: this.tempRect, - - // legacy variables - filterArea: new Float32Array(4), - filterClamp: new Float32Array(4), - }, true); - } - - /** - * Adds a new filter to the System. - * - * @param {PIXI.DisplayObject} target - The target of the filter to render. - * @param {PIXI.Filter[]} filters - The filters to apply. - */ - push(target, filters) - { - const renderer = this.renderer; - const filterStack = this.renderer.renderTexture.defaultFilterStack; - const state = this.statePool.pop() || new FilterState(); - - let resolution = filters[0].resolution; - let padding = filters[0].padding; - let autoFit = filters[0].autoFit; - let legacy = filters[0].legacy; - - for (let i = 1; i < filters.length; i++) - { - const filter = filters[i]; - - // lets use the lowest resolution.. - resolution = Math.min(resolution, filter.resolution); - // and the largest amount of padding! - padding = Math.max(padding, filter.padding); - // only auto fit if all filters are autofit - autoFit = autoFit || filter.autoFit; - - legacy = legacy || filter.legacy; - } - - filterStack.push(state); - - state.resolution = resolution; - - state.legacy = legacy; - - // round to whole number based on resolution - // TODO move that to the shader too? - state.sourceFrame = target.filterArea || target.getBounds(true); - - state.sourceFrame.pad(padding); - - if (autoFit) - { - state.sourceFrame.fit(this.renderer.renderTexture.destinationFrame); - } - - state.sourceFrame.round(resolution); - - state.renderTexture = this.getPotFilterTexture(state.sourceFrame.width, state.sourceFrame.height, resolution); - state.filters = filters; - - state.destinationFrame.width = state.renderTexture.width; - state.destinationFrame.height = state.renderTexture.height; - - state.renderTexture.filterFrame = state.sourceFrame; - renderer.renderTexture.bind(state.renderTexture, state.sourceFrame);// /, state.destinationFrame); - renderer.renderTexture.clear(); - } - - /** - * Pops off the filter and applies it. - * - */ - pop() - { - const renderer = this.renderer; - const filterStack = renderer.renderTexture.defaultFilterStack; - const state = filterStack.pop(); - const filters = state.filters; - - this.activeState = state; - - const globalUniforms = this.globalUniforms.uniforms; - - globalUniforms.sourceFrame = state.sourceFrame; - globalUniforms.destinationFrame = state.destinationFrame; - globalUniforms.resolution = state.resolution; - - // only update the rect if its legacy.. - if (state.legacy) - { - const filterArea = globalUniforms.filterArea; - const filterClamp = globalUniforms.filterClamp; - - filterArea[0] = state.destinationFrame.width; - filterArea[1] = state.destinationFrame.height; - filterArea[2] = state.sourceFrame.x; - filterArea[3] = state.sourceFrame.y; - - filterClamp[0] = 0.5 / state.resolution / state.destinationFrame.width; - filterClamp[1] = 0.5 / state.resolution / state.destinationFrame.height; - filterClamp[2] = (state.sourceFrame.width - 0.5) / state.resolution / state.destinationFrame.width; - filterClamp[3] = (state.sourceFrame.height - 0.5) / state.resolution / state.destinationFrame.height; - } - - this.globalUniforms.update(); - - const lastState = filterStack[filterStack.length - 1]; - - if (filters.length === 1) - { - filters[0].apply(this, state.renderTexture, lastState.renderTexture, false, state); - - this.returnFilterTexture(state.renderTexture); - } - else - { - let flip = state.renderTexture; - let flop = this.getPotFilterTexture( - flip.width, - flip.height, - state.resolution - ); - - flop.filterFrame = flip.filterFrame; - - let i = 0; - - for (i = 0; i < filters.length - 1; ++i) - { - filters[i].apply(this, flip, flop, true, state); - - const t = flip; - - flip = flop; - flop = t; - } - - filters[i].apply(this, flip, lastState.renderTexture, false, state); - - this.returnFilterTexture(flip); - this.returnFilterTexture(flop); - } - - this.statePool.push(state); - } - - /** - * Draws a filter. - * - * @param {PIXI.Filter} filter - The filter to draw. - * @param {PIXI.RenderTarget} input - The input render target. - * @param {PIXI.RenderTarget} output - The target to output to. - * @param {boolean} clear - Should the output be cleared before rendering to it - */ - applyFilter(filter, input, output, clear) - { - const renderer = this.renderer; - - renderer.renderTexture.bind(output, output ? output.filterFrame : null); - - if (clear) - { - // gl.disable(gl.SCISSOR_TEST); - renderer.renderTexture.clear(); - // gl.enable(gl.SCISSOR_TEST); - } - - // set the uniforms.. - filter.uniforms.uSampler = input; - filter.uniforms.filterGlobals = this.globalUniforms; - - // TODO make it so that the order of this does not matter.. - // because it does at the moment cos of global uniforms. - // they need to get resynced - - renderer.state.setState(filter.state); - renderer.shader.bind(filter); - - if (filter.legacy) - { - this.quadUv.map(input._frame, input.filterFrame); - - renderer.geometry.bind(this.quadUv); - renderer.geometry.draw(DRAW_MODES.TRIANGLES); - } - else - { - renderer.geometry.bind(this.quad); - renderer.geometry.draw(DRAW_MODES.TRIANGLE_STRIP); - } - } - - /** - * Calculates the mapped matrix. - * - * TODO playing around here.. this is temporary - (will end up in the shader) - * this returns a matrix that will normalise map filter cords in the filter to screen space - * - * @param {PIXI.Matrix} outputMatrix - the matrix to output to. - * @return {PIXI.Matrix} The mapped matrix. - */ - calculateScreenSpaceMatrix(outputMatrix) - { - const currentState = this.activeState; - - return filterTransforms.calculateScreenSpaceMatrix( - outputMatrix, - currentState.sourceFrame, - currentState.destinationFrame - ); - } - - /** - * This will map the filter coord so that a texture can be used based on the transform of a sprite - * - * @param {PIXI.Matrix} outputMatrix - The matrix to output to. - * @param {PIXI.Sprite} sprite - The sprite to map to. - * @return {PIXI.Matrix} The mapped matrix. - */ - calculateSpriteMatrix(outputMatrix, sprite) - { - const currentState = this.activeState; - - return filterTransforms.calculateSpriteMatrix( - outputMatrix, - currentState.sourceFrame, - currentState.destinationFrame, - sprite - ); - } - - /** - * Destroys this Filter System. - * - * @param {boolean} [contextLost=false] context was lost, do not free shaders - * - */ - destroy(contextLost = false) - { - if (!contextLost) - { - this.emptyPool(); - } - else - { - this.texturePool = {}; - } - } - - /** - * Gets a Power-of-Two render texture. - * - * TODO move to a seperate class could be on renderer? - * also - could cause issue with multiple contexts? - * - * @private - * @param {WebGLRenderingContext} gl - The webgl rendering context - * @param {number} minWidth - The minimum width of the render target. - * @param {number} minHeight - The minimum height of the render target. - * @param {number} resolution - The resolution of the render target. - * @return {PIXI.RenderTarget} The new render target. - */ - getPotFilterTexture(minWidth, minHeight, resolution) - { - minWidth = bitTwiddle.nextPow2(minWidth); - minHeight = bitTwiddle.nextPow2(minHeight); - resolution = resolution || 1; - - const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); - - if (!this.texturePool[key]) - { - this.texturePool[key] = []; - } - - let renderTexture = this.texturePool[key].pop(); - - if (!renderTexture) - { - // temporary bypass cache.. - // internally - this will cause a texture to be bound.. - renderTexture = RenderTexture.create({ - width: minWidth, - height: minHeight, - resolution, - }); - } - - return renderTexture; - } - - /** - * Gets extra render texture to use inside current filter - * - * @param {number} resolution resolution of the renderTexture - * @returns {PIXI.RenderTexture} - */ - getFilterTexture(resolution) - { - const rt = this.activeState.renderTexture; - - const filterTexture = this.getPotFilterTexture(rt.width, rt.height, resolution || rt.baseTexture.resolution); - - filterTexture.filterFrame = rt.filterFrame; - - return filterTexture; - } - - /** - * Frees a render texture back into the pool. - * - * @param {PIXI.RenderTarget} renderTexture - The renderTarget to free - */ - returnFilterTexture(renderTexture) - { - renderTexture.filterFrame = null; - - const base = renderTexture.baseTexture; - - const minWidth = base.width; - const minHeight = base.height; - - const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); - - this.texturePool[key].push(renderTexture); - } - - /** - * Empties the texture pool. - * - */ - emptyPool() - { - for (const i in this.texturePool) - { - const textures = this.texturePool[i]; - - if (textures) - { - for (let j = 0; j < textures.length; j++) - { - textures[j].destroy(true); - } - } - } - - this.texturePool = {}; - } -} diff --git a/packages/core/src/renderers/systems/geometry/GLBuffer.js b/packages/core/src/renderers/systems/geometry/GLBuffer.js deleted file mode 100644 index 0477e8a..0000000 --- a/packages/core/src/renderers/systems/geometry/GLBuffer.js +++ /dev/null @@ -1,9 +0,0 @@ -export default class GLBuffer -{ - constructor(buffer) - { - this.buffer = buffer; - this.updateID = -1; - this.byteLength = -1; - } -} diff --git a/packages/core/src/renderers/systems/geometry/GeometrySystem.js b/packages/core/src/renderers/systems/geometry/GeometrySystem.js deleted file mode 100644 index 26c8e9e..0000000 --- a/packages/core/src/renderers/systems/geometry/GeometrySystem.js +++ /dev/null @@ -1,388 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; -import GLBuffer from './GLBuffer'; - -const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class GeometrySystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this._activeGeometry = null; - this._activeVao = null; - - this.hasVao = true; - this.hasInstance = true; - } - - /** - * Sets up the renderer context and necessary buffers. - * - * @private - */ - contextChange() - { - const gl = this.gl = this.renderer.gl; - - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - - // webgl2 - if (!gl.createVertexArray) - { - // webgl 1! - let nativeVaoExtension = this.renderer.context.extensions.vertexArrayObject; - - if (this.renderer.legacy) - { - nativeVaoExtension = null; - } - - if (nativeVaoExtension) - { - gl.createVertexArray = () => - nativeVaoExtension.createVertexArrayOES(); - - gl.bindVertexArray = (vao) => - nativeVaoExtension.bindVertexArrayOES(vao); - - gl.deleteVertexArray = (vao) => - nativeVaoExtension.deleteVertexArrayOES(vao); - } - else - { - this.hasVao = false; - gl.createVertexArray = () => - { - // empty - }; - - gl.bindVertexArray = () => - { - // empty - }; - - gl.deleteVertexArray = () => - { - // empty - }; - } - } - - if (!gl.vertexAttribDivisor) - { - const instanceExt = gl.getExtension('ANGLE_instanced_arrays'); - - if (instanceExt) - { - gl.vertexAttribDivisor = (a, b) => - instanceExt.vertexAttribDivisorANGLE(a, b); - - gl.drawElementsInstanced = (a, b, c, d, e) => - instanceExt.drawElementsInstancedANGLE(a, b, c, d, e); - - gl.drawArraysInstanced = (a, b, c, d) => - instanceExt.drawArraysInstancedANGLE(a, b, c, d); - } - else - { - this.hasInstance = false; - } - } - } - - /** - * Binds geometry so that is can be drawn. Creating a Vao if required - * @private - * @param {PIXI.Geometry} geometry instance of geometry to bind - */ - bind(geometry, shader) - { - shader = shader || this.renderer.shader.shader; - - const gl = this.gl; - - // not sure the best way to address this.. - // currently different shaders require different VAOs for the same geometry - // Still mulling over the best way to solve this one.. - // will likely need to modify the shader attribute locations at run time! - let vaos = geometry.glVertexArrayObjects[this.CONTEXT_UID]; - - if (!vaos) - { - geometry.glVertexArrayObjects[this.CONTEXT_UID] = vaos = {}; - } - - const vao = vaos[shader.program.id] || this.initGeometryVao(geometry, shader.program); - - this._activeGeometry = geometry; - - if (this._activeVao !== vao) - { - this._activeVao = vao; - - if (this.hasVao) - { - gl.bindVertexArray(vao); - } - else - { - this.activateVao(geometry, shader.program); - } - } - - // TODO - optimise later! - // don't need to loop through if nothing changed! - // maybe look to add an 'autoupdate' to geometry? - this.updateBuffers(); - } - - reset() - { - this.unbind(); - } - - updateBuffers() - { - const geometry = this._activeGeometry; - const gl = this.gl; - - for (let i = 0; i < geometry.buffers.length; i++) - { - const buffer = geometry.buffers[i]; - - const glBuffer = buffer._glBuffers[this.CONTEXT_UID]; - - if (buffer._updateID !== glBuffer.updateID) - { - glBuffer.updateID = buffer._updateID; - - // TODO can cache this on buffer! maybe added a getter / setter? - const type = buffer.index ? gl.ELEMENT_ARRAY_BUFFER : gl.ARRAY_BUFFER; - const drawType = buffer.static ? gl.STATIC_DRAW : gl.DYNAMIC_DRAW; - - gl.bindBuffer(type, glBuffer.buffer); - - if (glBuffer.byteLength >= buffer.data.byteLength) - { - // offset is always zero for now! - gl.bufferSubData(type, 0, buffer.data); - } - else - { - gl.bufferData(type, buffer.data, drawType); - } - } - } - } - - checkCompatability(geometry, program) - { - // geometry must have at least all the attributes that the shader requires. - const geometryAttributes = geometry.attributes; - const shaderAttributes = program.attributeData; - - for (const j in shaderAttributes) - { - if (!geometryAttributes[j]) - { - throw new Error(`shader and geometry incompatible, geometry missing the "${j}" attribute`); - } - } - } - - /** - * Creates a Vao with the same structure as the geometry and stores it on the geometry. - * @private - * @param {PIXI.Geometry} geometry instance of geometry to to generate Vao for - */ - initGeometryVao(geometry, program) - { - this.checkCompatability(geometry, program); - - const gl = this.gl; - const CONTEXT_UID = this.CONTEXT_UID; - const buffers = geometry.buffers; - const attributes = geometry.attributes; - - const tempStride = {}; - const tempStart = {}; - - for (const j in buffers) - { - tempStride[j] = 0; - tempStart[j] = 0; - } - - for (const j in attributes) - { - if (!attributes[j].size && program.attributeData[j]) - { - attributes[j].size = program.attributeData[j].size; - } - - tempStride[attributes[j].buffer] += attributes[j].size * byteSizeMap[attributes[j].type]; - } - - for (const j in attributes) - { - const attribute = attributes[j]; - const attribSize = attribute.size; - - if (attribute.stride === undefined) - { - if (tempStride[attribute.buffer] === attribSize * byteSizeMap[attribute.type]) - { - attribute.stride = 0; - } - else - { - attribute.stride = tempStride[attribute.buffer]; - } - } - - if (attribute.start === undefined) - { - attribute.start = tempStart[attribute.buffer]; - - tempStart[attribute.buffer] += attribSize * byteSizeMap[attribute.type]; - } - } - - // first update - and create the buffers! - // only create a gl buffer if it actually gets - for (let i = 0; i < buffers.length; i++) - { - const buffer = buffers[i]; - - if (!buffer._glBuffers[CONTEXT_UID]) - { - buffer._glBuffers[CONTEXT_UID] = new GLBuffer(gl.createBuffer()); - } - } - - // TODO - maybe make this a data object? - // lets wait to see if we need to first! - const vao = gl.createVertexArray(); - - gl.bindVertexArray(vao); - - this.activateVao(geometry, program); - - gl.bindVertexArray(null); - - // add it to the cache! - geometry.glVertexArrayObjects[this.CONTEXT_UID][program.id] = vao; - - return vao; - } - - activateVao(geometry, program) - { - const gl = this.gl; - const CONTEXT_UID = this.CONTEXT_UID; - const buffers = geometry.buffers; - const attributes = geometry.attributes; - - if (geometry.indexBuffer) - { - // first update the index buffer if we have one.. - gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, geometry.indexBuffer._glBuffers[CONTEXT_UID].buffer); - } - - let lastBuffer = null; - - // add a new one! - for (const j in attributes) - { - const attribute = attributes[j]; - const buffer = buffers[attribute.buffer]; - const glBuffer = buffer._glBuffers[CONTEXT_UID]; - - if (program.attributeData[j]) - { - if (lastBuffer !== glBuffer) - { - gl.bindBuffer(gl.ARRAY_BUFFER, glBuffer.buffer); - - lastBuffer = glBuffer; - } - - const location = program.attributeData[j].location; - - // TODO introduce state again - // we can optimise this for older devices that have no VAOs - gl.enableVertexAttribArray(location); - - gl.vertexAttribPointer(location, - attribute.size, - attribute.type || gl.FLOAT, - attribute.normalized, - attribute.stride, - attribute.start); - - if (attribute.instance) - { - // TODO calculate instance count based of this... - if (this.hasInstance) - { - gl.vertexAttribDivisor(location, 1); - } - else - { - throw new Error('geometry error, GPU Instancing is not supported on this device'); - } - } - } - } - } - - draw(type, size, start, instanceCount) - { - const gl = this.gl; - const geometry = this._activeGeometry; - - // TODO.. this should not change so maybe cache the function? - - if (geometry.indexBuffer) - { - if (geometry.instanced) - { - /* eslint-disable max-len */ - gl.drawElementsInstanced(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2, instanceCount || 1); - /* eslint-enable max-len */ - } - else - { - gl.drawElements(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2); - } - } - else - if (geometry.instanced) - { - // TODO need a better way to calculate size.. - gl.drawArraysInstanced(type, start, size || geometry.getSize(), instanceCount || 1); - } - else - { - gl.drawArrays(type, start, size || geometry.getSize()); - } - - return this; - } - - unbind() - { - this.gl.bindVertexArray(null); - this._activeVao = null; - this._activeGeometry = null; - } -} diff --git a/packages/core/src/renderers/systems/geometry/setVertexAttribArrays.js b/packages/core/src/renderers/systems/geometry/setVertexAttribArrays.js deleted file mode 100644 index 23b6959..0000000 --- a/packages/core/src/renderers/systems/geometry/setVertexAttribArrays.js +++ /dev/null @@ -1,54 +0,0 @@ -// var GL_MAP = {}; - -/** - * @param gl {WebGLRenderingContext} The current WebGL context - * @param attribs {*} - * @param state {*} - */ -export default function setVertexAttribArrays(gl, attribs, state) -{ - let i; - - if (state) - { - const tempAttribState = state.tempAttribState; - const attribState = state.attribState; - - for (i = 0; i < tempAttribState.length; i++) - { - tempAttribState[i] = false; - } - - // set the new attribs - for (i = 0; i < attribs.length; i++) - { - tempAttribState[attribs[i].attribute.location] = true; - } - - for (i = 0; i < attribState.length; i++) - { - if (attribState[i] !== tempAttribState[i]) - { - attribState[i] = tempAttribState[i]; - - if (state.attribState[i]) - { - gl.enableVertexAttribArray(i); - } - else - { - gl.disableVertexAttribArray(i); - } - } - } - } - else - { - for (i = 0; i < attribs.length; i++) - { - const attrib = attribs[i]; - - gl.enableVertexAttribArray(attrib.attribute.location); - } - } -} diff --git a/packages/core/src/renderers/systems/index.js b/packages/core/src/renderers/systems/index.js deleted file mode 100644 index c843f03..0000000 --- a/packages/core/src/renderers/systems/index.js +++ /dev/null @@ -1,6 +0,0 @@ -/** - * Systems are individual components to the Renderer pipeline. - * @namespace PIXI.systems - */ -export { default as WebGLSystem } from './WebGLSystem'; -export { default as FilterSystem } from './filter/FilterSystem'; diff --git a/packages/core/src/renderers/systems/shader/GLProgram.js b/packages/core/src/renderers/systems/shader/GLProgram.js deleted file mode 100644 index 92980ca..0000000 --- a/packages/core/src/renderers/systems/shader/GLProgram.js +++ /dev/null @@ -1,43 +0,0 @@ -/** - * Helper class to create a webGL Program - * - * @class - * @private - */ -export default class GLProgram -{ - constructor(program, uniformData) - { - /** - * The shader program - * - * @member {WebGLProgram} - */ - this.program = program; - - /** - * holds the uniform data which contains uniform locations - * and current uniform values used for caching and preventing undeed GPU commands - * @type {Object} - */ - this.uniformData = uniformData; - - /** - * uniformGroups holds the various upload functions for the shader. Each uniform group - * and program have a unique upload function generated. - * @type {Object} - */ - this.uniformGroups = {}; - } - - /** - * Destroys this program - * TODO - */ - destroy() - { - this.uniformData = null; - this.uniformGroups = null; - this.program = null; - } -} diff --git a/packages/core/src/renderers/systems/shader/ShaderSystem.js b/packages/core/src/renderers/systems/shader/ShaderSystem.js deleted file mode 100644 index 6cf57b6..0000000 --- a/packages/core/src/renderers/systems/shader/ShaderSystem.js +++ /dev/null @@ -1,170 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; -import GLProgram from './GLProgram'; -import generateUniformsSync from '../../../shader/generateUniformsSync'; -import defaultValue from '../../../shader/defaultValue'; -import compileProgram from './shader/compileProgram'; - -let UID = 0; - -/** - * Helper class to create a webGL Texture - * - * @class - * @memberof PIXI - */ -export default class ShaderSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - A reference to the current renderer - */ - constructor(renderer) - { - super(renderer); - - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = null; - - this.shader = null; - this.program = null; - - this.id = UID++; - } - - contextChange(gl) - { - this.gl = gl; - } - - /** - * Changes the current shader to the one given in parameter - * - * @param {PIXI.Shader} shader - the new shader - * @param {boolean} dontSync - false if the shader should automatically sync its uniforms. - * @returns {PIXI.glCore.glProgram} the glProgram that belongs to the shader. - */ - bind(shader, dontSync) - { - shader.uniforms.globals = this.renderer.globalUniforms; - - const program = shader.program; - const glProgram = program.glPrograms[this.renderer.CONTEXT_UID] || this.generateShader(shader); - - this.shader = shader; - - // TODO - some current pixi plugins bypass this.. so it not safe to use yet.. - if (this.program !== program) - { - this.program = program; - this.gl.useProgram(glProgram.program); - } - - if (!dontSync) - { - this.syncUniformGroup(shader.uniformGroup); - } - - return glProgram; - } - - /** - * Uploads the uniforms values to the currently bound shader. - * - * @param {object} uniforms - the uniforms valiues that be applied to the current shader - */ - setUniforms(uniforms) - { - const shader = this.shader.program; - const glProgram = shader.glPrograms[this.renderer.CONTEXT_UID]; - - shader.syncUniforms(glProgram.uniformData, uniforms, this.renderer); - } - - syncUniformGroup(group) - { - const glProgram = this.getglProgram(); - - if (!group.static || group.dirtyId !== glProgram.uniformGroups[group.id]) - { - glProgram.uniformGroups[group.id] = group.dirtyId; - const syncFunc = group.syncUniforms[this.shader.program.id] || this.createSyncGroups(group); - - syncFunc(glProgram.uniformData, group.uniforms, this.renderer); - } - } - - createSyncGroups(group) - { - group.syncUniforms[this.shader.program.id] = generateUniformsSync(group, this.shader.program.uniformData); - - return group.syncUniforms[this.shader.program.id]; - } - - /** - * Returns the underlying GLShade rof the currently bound shader. - * This can be handy for when you to have a little more control over the setting of your uniforms. - * - * @return {PIXI.glCore.Shader} the glProgram for the currently bound Shader for this context - */ - getglProgram() - { - if (this.shader) - { - return this.shader.program.glPrograms[this.renderer.CONTEXT_UID]; - } - - return null; - } - - /** - * Generates a glProgram verion of the Shader provided. - * - * @private - * @param {PIXI.Shader} shader the shader that the glProgram will be based on. - * @return {PIXI.glCore.glProgram} A shiney new glProgram - */ - generateShader(shader) - { - const gl = this.gl; - - const program = shader.program; - - const attribMap = {}; - - for (const i in program.attributeData) - { - attribMap[i] = program.attributeData[i].location; - } - - const shaderProgram = compileProgram(gl, program.vertexSrc, program.fragmentSrc, attribMap); - const uniformData = {}; - - for (const i in program.uniformData) - { - const data = program.uniformData[i]; - - uniformData[i] = { - location: gl.getUniformLocation(shaderProgram, i), - value: defaultValue(data.type, data.size), - }; - } - - const glProgram = new GLProgram(shaderProgram, uniformData); - - program.glPrograms[this.renderer.CONTEXT_UID] = glProgram; - - return glProgram; - } - - /** - * Destroys this System and removes all its textures - */ - destroy() - { - // TODO implement destroy method for ShaderSystem - this.destroyed = true; - } -} diff --git a/packages/core/src/renderers/systems/shader/shader/compileProgram.js b/packages/core/src/renderers/systems/shader/shader/compileProgram.js deleted file mode 100644 index 8c6ccb9..0000000 --- a/packages/core/src/renderers/systems/shader/shader/compileProgram.js +++ /dev/null @@ -1,78 +0,0 @@ -/** - * @method compileProgram - * @memberof PIXI.glCore.shader - * @param gl {WebGLRenderingContext} The current WebGL context {WebGLProgram} - * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. - * @param fragmentSrc {string|string[]} The fragment shader source as an array of strings. - * @param attributeLocations {Object} An attribute location map that lets you manually set the attribute locations - * @return {WebGLProgram} the shader program - */ -export default function compileProgram(gl, vertexSrc, fragmentSrc, attributeLocations) -{ - const glVertShader = compileShader(gl, gl.VERTEX_SHADER, vertexSrc); - const glFragShader = compileShader(gl, gl.FRAGMENT_SHADER, fragmentSrc); - - let program = gl.createProgram(); - - gl.attachShader(program, glVertShader); - gl.attachShader(program, glFragShader); - - // optionally, set the attributes manually for the program rather than letting WebGL decide.. - if (attributeLocations) - { - for (const i in attributeLocations) - { - gl.bindAttribLocation(program, attributeLocations[i], i); - } - } - - gl.linkProgram(program); - - // if linking fails, then log and cleanup - if (!gl.getProgramParameter(program, gl.LINK_STATUS)) - { - console.error('Pixi.js Error: Could not initialize shader.'); - console.error('gl.VALIDATE_STATUS', gl.getProgramParameter(program, gl.VALIDATE_STATUS)); - console.error('gl.getError()', gl.getError()); - - // if there is a program info log, log it - if (gl.getProgramInfoLog(program) !== '') - { - console.warn('Pixi.js Warning: gl.getProgramInfoLog()', gl.getProgramInfoLog(program)); - } - - gl.deleteProgram(program); - program = null; - } - - // clean up some shaders - gl.deleteShader(glVertShader); - gl.deleteShader(glFragShader); - - return program; -} - -/** - * @private - * @param gl {WebGLRenderingContext} The current WebGL context {WebGLProgram} - * @param type {Number} the type, can be either VERTEX_SHADER or FRAGMENT_SHADER - * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. - * @return {WebGLShader} the shader - */ -function compileShader(gl, type, src) -{ - const shader = gl.createShader(type); - - gl.shaderSource(shader, src); - gl.compileShader(shader); - - if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) - { - console.warn(src); - console.error(gl.getShaderInfoLog(shader)); - - return null; - } - - return shader; -} diff --git a/packages/core/src/renderers/systems/shader/shader/defaultValue.js b/packages/core/src/renderers/systems/shader/shader/defaultValue.js deleted file mode 100644 index 97290bd..0000000 --- a/packages/core/src/renderers/systems/shader/shader/defaultValue.js +++ /dev/null @@ -1,79 +0,0 @@ -/** - * @method defaultValue - * @memberof PIXI.glCore.shader - * @param type {String} Type of value - * @param size {Number} - */ -export default function defaultValue(type, size) -{ - switch (type) - { - case 'float': - return 0; - - case 'vec2': - return new Float32Array(2 * size); - - case 'vec3': - return new Float32Array(3 * size); - - case 'vec4': - return new Float32Array(4 * size); - - case 'int': - case 'sampler2D': - case 'sampler2DArray': - return 0; - - case 'ivec2': - return new Int32Array(2 * size); - - case 'ivec3': - return new Int32Array(3 * size); - - case 'ivec4': - return new Int32Array(4 * size); - - case 'bool': - return false; - - case 'bvec2': - - return booleanArray(2 * size); - - case 'bvec3': - return booleanArray(3 * size); - - case 'bvec4': - return booleanArray(4 * size); - - case 'mat2': - return new Float32Array([1, 0, - 0, 1]); - - case 'mat3': - return new Float32Array([1, 0, 0, - 0, 1, 0, - 0, 0, 1]); - - case 'mat4': - return new Float32Array([1, 0, 0, 0, - 0, 1, 0, 0, - 0, 0, 1, 0, - 0, 0, 0, 1]); - } - - return null; -} - -function booleanArray(size) -{ - const array = new Array(size); - - for (let i = 0; i < array.length; i++) - { - array[i] = false; - } - - return array; -} diff --git a/packages/core/src/renderers/systems/shader/shader/index.js b/packages/core/src/renderers/systems/shader/shader/index.js deleted file mode 100644 index ba7a317..0000000 --- a/packages/core/src/renderers/systems/shader/shader/index.js +++ /dev/null @@ -1,8 +0,0 @@ -/** - * @namespace PIXI.glCore.shader - */ -export { default as compileProgram } from './compileProgram'; -export { default as defaultValue } from './defaultValue'; -export { default as setPrecision } from './setPrecision'; -export { default as mapSize } from './mapSize'; -export { default as mapType } from './mapType'; diff --git a/packages/core/src/renderers/systems/shader/shader/mapSize.js b/packages/core/src/renderers/systems/shader/shader/mapSize.js deleted file mode 100644 index b681554..0000000 --- a/packages/core/src/renderers/systems/shader/shader/mapSize.js +++ /dev/null @@ -1,33 +0,0 @@ -const GLSL_TO_SIZE = { - float: 1, - vec2: 2, - vec3: 3, - vec4: 4, - - int: 1, - ivec2: 2, - ivec3: 3, - ivec4: 4, - - bool: 1, - bvec2: 2, - bvec3: 3, - bvec4: 4, - - mat2: 4, - mat3: 9, - mat4: 16, - - sampler2D: 1, -}; - -/** - * @method mapSize - * @memberof PIXI.glCore.shader - * @param type {String} - * @return {Number} - */ -export default function mapSize(type) -{ - return GLSL_TO_SIZE[type]; -} diff --git a/packages/core/src/renderers/systems/shader/shader/mapType.js b/packages/core/src/renderers/systems/shader/shader/mapType.js deleted file mode 100644 index d288b7c..0000000 --- a/packages/core/src/renderers/systems/shader/shader/mapType.js +++ /dev/null @@ -1,45 +0,0 @@ -let GL_TABLE = null; - -const GL_TO_GLSL_TYPES = { - FLOAT: 'float', - FLOAT_VEC2: 'vec2', - FLOAT_VEC3: 'vec3', - FLOAT_VEC4: 'vec4', - - INT: 'int', - INT_VEC2: 'ivec2', - INT_VEC3: 'ivec3', - INT_VEC4: 'ivec4', - - BOOL: 'bool', - BOOL_VEC2: 'bvec2', - BOOL_VEC3: 'bvec3', - BOOL_VEC4: 'bvec4', - - FLOAT_MAT2: 'mat2', - FLOAT_MAT3: 'mat3', - FLOAT_MAT4: 'mat4', - - SAMPLER_2D: 'sampler2D', - SAMPLER_CUBE: 'samplerCube', - SAMPLER_2D_ARRAY: 'sampler2DArray', -}; - -export default function mapType(gl, type) -{ - if (!GL_TABLE) - { - const typeNames = Object.keys(GL_TO_GLSL_TYPES); - - GL_TABLE = {}; - - for (let i = 0; i < typeNames.length; ++i) - { - const tn = typeNames[i]; - - GL_TABLE[gl[tn]] = GL_TO_GLSL_TYPES[tn]; - } - } - - return GL_TABLE[type]; -} diff --git a/packages/core/src/renderers/systems/shader/shader/setPrecision.js b/packages/core/src/renderers/systems/shader/shader/setPrecision.js deleted file mode 100644 index e2c1f34..0000000 --- a/packages/core/src/renderers/systems/shader/shader/setPrecision.js +++ /dev/null @@ -1,18 +0,0 @@ -/** - * Sets the float precision on the shader. If the precision is already present this function will do nothing - * @param {string} src the shader source - * @param {string} precision The float precision of the shader. Options are 'lowp', 'mediump' or 'highp'. - * - * @return {string} modified shader source - */ -export default function setPrecision(src, precision) -{ - src = src.trim(); - - if (src.substring(0, 9) !== 'precision' && src.substring(0, 1) !== '#') - { - return `precision ${precision} float;\n${src}`; - } - - return src; -} diff --git a/packages/core/src/AbstractRenderer.js b/packages/core/src/AbstractRenderer.js new file mode 100644 index 0000000..64b2eb1 --- /dev/null +++ b/packages/core/src/AbstractRenderer.js @@ -0,0 +1,353 @@ +import { hex2string, hex2rgb } from '@pixi/utils'; +import { Matrix, Rectangle } from '@pixi/math'; +import { RENDERER_TYPE } from '@pixi/constants'; +import { settings } from '@pixi/settings'; +import { Container } from '@pixi/display'; +import RenderTexture from './renderTexture/RenderTexture'; +import EventEmitter from 'eventemitter3'; + +const tempMatrix = new Matrix(); + +/** + * 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 EventEmitter + * @memberof PIXI + */ +export default class AbstractRenderer extends EventEmitter +{ + // eslint-disable-next-line valid-jsdoc + /** + * @param {string} system - The name of the system this renderer is for. + * @param {object} [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.transparent=false] - If the render view is transparent, default false + * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false + * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. The + * resolution of the renderer retina would be 2. + * @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 {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when rendering, + * stopping pixel interpolation. + */ + constructor(system, options, arg2, arg3) + { + super(); + + // Support for constructor(system, screenWidth, screenHeight, options) + if (typeof options === 'number') + { + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + }, arg3); + } + + // 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 = RENDERER_TYPE.UNKNOWN; + + /** + * Measurements of the screen. (0, 0, screenWidth, screenHeight) + * + * Its safe to use as filterArea or hitArea for 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 1 + */ + this.resolution = options.resolution || settings.RESOLUTION; + + /** + * Whether the render view is transparent + * + * @member {boolean} + */ + this.transparent = options.transparent; + + /** + * Whether css dimensions of canvas view should be resized to screen dimensions automatically + * + * @member {boolean} + */ + this.autoResize = options.autoResize || false; + + /** + * Tracks the blend modes useful for this renderer. + * + * @member {object} + */ + this.blendModes = null; + + /** + * 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; + + /** + * If true PixiJS will Math.floor() x/y values when rendering, stopping pixel interpolation. + * Handy for crisp pixel art and speed on legacy devices. + * + * @member {boolean} + */ + this.roundPixels = options.roundPixels; + + /** + * The background color as a number. + * + * @member {number} + * @private + */ + this._backgroundColor = 0x000000; + + /** + * The background color as an [R, G, B] array. + * + * @member {number[]} + * @private + */ + this._backgroundColorRgba = [0, 0, 0, 0]; + + /** + * The background color as a string. + * + * @member {string} + * @private + */ + this._backgroundColorString = '#000000'; + + this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter + + /** + * This temporary display object used as the parent of the currently being rendered item + * + * @member {PIXI.DisplayObject} + * @private + */ + this._tempDisplayObjectParent = new Container(); + + /** + * The last root object that the renderer tried to render. + * + * @member {PIXI.DisplayObject} + * @private + */ + this._lastObjectRendered = this._tempDisplayObjectParent; + + /** + * Collection of plugins. + * @readonly + * @member {object} + */ + this.plugins = {}; + } + + /** + * Initialize the plugins. + * + * @protected + * @param {object} staticMap - The dictionary of staticly saved plugins. + */ + initPlugins(staticMap) + { + 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() + { + return this.view.width; + } + + /** + * Same as view.height, actual number of pixels in the canvas by vertical + * + * @member {number} + * @readonly + * @default 600 + */ + get height() + { + return this.view.height; + } + + /** + * Resizes the screen and canvas to the specified width and height + * Canvas dimensions are multiplied by resolution + * + * @param {number} screenWidth - the new width of the screen + * @param {number} screenHeight - the new height of the screen + */ + resize(screenWidth, screenHeight) + { + this.screen.width = screenWidth; + this.screen.height = screenHeight; + + this.view.width = screenWidth * this.resolution; + this.view.height = screenHeight * this.resolution; + + if (this.autoResize) + { + this.view.style.width = `${screenWidth}px`; + this.view.style.height = `${screenHeight}px`; + } + } + + /** + * 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 {PIXI.DisplayObject} displayObject - The displayObject the object will be generated from + * @param {number} scaleMode - Should be one of the scaleMode consts + * @param {number} resolution - The resolution / device pixel ratio of the texture being generated + * @param {PIXI.Rectangle} [region] - The region of the displayObject, that shall be rendered, + * if no region is specified, defaults to the local bounds of the displayObject. + * @return {PIXI.Texture} a texture of the graphics object + */ + generateTexture(displayObject, scaleMode, resolution, region) + { + region = region || displayObject.getLocalBounds(); + + // 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(region.width | 0, region.height | 0, scaleMode, resolution); + + tempMatrix.tx = -region.x; + tempMatrix.ty = -region.y; + + this.render(displayObject, renderTexture, false, tempMatrix, true); + + return renderTexture; + } + + /** + * Removes everything from the renderer and optionally removes the Canvas DOM element. + * + * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. + */ + destroy(removeView) + { + 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); + } + + this.plugins = null; + + this.type = RENDERER_TYPE.UNKNOWN; + + this.view = null; + + this.screen = null; + + this.resolution = 0; + + this.transparent = false; + + this.autoResize = false; + + this.blendModes = null; + + this.options = null; + + this.preserveDrawingBuffer = false; + this.clearBeforeRender = false; + + this.roundPixels = false; + + this._backgroundColor = 0; + this._backgroundColorRgba = null; + this._backgroundColorString = null; + + this._tempDisplayObjectParent = null; + this._lastObjectRendered = null; + } + + /** + * The background color to fill if not transparent + * + * @member {number} + */ + get backgroundColor() + { + return this._backgroundColor; + } + + set backgroundColor(value) // eslint-disable-line require-jsdoc + { + this._backgroundColor = value; + this._backgroundColorString = hex2string(value); + hex2rgb(value, this._backgroundColorRgba); + } +} diff --git a/packages/core/src/Renderer.js b/packages/core/src/Renderer.js new file mode 100644 index 0000000..2227e78 --- /dev/null +++ b/packages/core/src/Renderer.js @@ -0,0 +1,338 @@ +import AbstractRenderer from './AbstractRenderer'; +import { sayHello } from '@pixi/utils'; +import MaskSystem from './mask/MaskSystem'; +import StencilSystem from './mask/StencilSystem'; +import FilterSystem from './filters/FilterSystem'; +import FramebufferSystem from './framebuffer/FramebufferSystem'; +import RenderTextureSystem from './renderTexture/RenderTextureSystem'; +import TextureSystem from './textures/TextureSystem'; +import ProjectionSystem from './projection/ProjectionSystem'; +import StateSystem from './state/StateSystem'; +import GeometrySystem from './geometry/GeometrySystem'; +import ShaderSystem from './shader/ShaderSystem'; +import ContextSystem from './context/ContextSystem'; +import BatchSystem from './batch/BatchSystem'; +import TextureGCSystem from './textures/TextureGCSystem'; +import { RENDERER_TYPE } from '@pixi/constants'; +import UniformGroup from './shader/UniformGroup'; +import { Matrix } from '@pixi/math'; +import Runner from 'mini-runner'; + +/** + * 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 Render works by automatically managing webGLBatchs. + * 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 :) + * + * @class + * @memberof PIXI + * @extends PIXI.AbstractRenderer + */ +export default class Renderer extends AbstractRenderer +{ + // eslint-disable-next-line valid-jsdoc + /** + * + * @param {object} [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.transparent=false] - If the render view is transparent, default false + * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false + * @param {boolean} [options.antialias=false] - sets antialias. If not available natively then FXAA + * antialiasing is used + * @param {boolean} [options.forceFXAA=false] - forces FXAA antialiasing to be used over native. + * FXAA is faster, but may not always look as great + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. + * The resolution of the renderer retina would be 2. + * @param {boolean} [options.clearBeforeRender=true] - This sets if the renderer will clear + * the canvas or not before the new render pass. If you wish to set this to false, you *must* set + * preserveDrawingBuffer to `true`. + * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, + * enable this if you need to call toDataUrl on the webgl context. + * @param {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when + * rendering, stopping pixel interpolation. + * @param {number} [options.backgroundColor=0x000000] - The background color of the rendered area + * (shown if not transparent). + * @param {boolean} [options.legacy=false] - If true PixiJS will aim to ensure compatibility + * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. + * @param {string} [options.powerPreference] - Parameter passed to webgl context, set to "high-performance" + * for devices with dual graphics card + */ + constructor(options, arg2, arg3) + { + super('WebGL', options, arg2, arg3); + + /** + * The type of this renderer as a standardised const + * + * @member {number} + * @see PIXI.RENDERER_TYPE + */ + this.type = RENDERER_TYPE.WEBGL; + + // this will be set by the contextSystem (this.context) + this.gl = null; + this.CONTEXT_UID = 0; + this.legacy = !!options.legacy; + + // TODO legacy! + + // runners! + this.runners = { + destroy: new Runner('destroy'), + contextChange: new Runner('contextChange', 1), + reset: new Runner('reset'), + update: new Runner('update'), + postrender: new Runner('postrender'), + prerender: new Runner('prerender'), + resize: new Runner('resize', 2), + }; + + 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(GeometrySystem, 'geometry') + .addSystem(FramebufferSystem, 'framebuffer') + .addSystem(StencilSystem, 'stencil') + .addSystem(ProjectionSystem, 'projection') + .addSystem(TextureGCSystem, 'textureGC') + .addSystem(FilterSystem, 'filter') + .addSystem(RenderTextureSystem, 'renderTexture') + .addSystem(BatchSystem, 'batch'); + + this.initPlugins(Renderer.__plugins); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ + if (options.context) + { + this.context.initFromContext(options.context); + } + else + { + this.context.initFromOptions({ + alpha: this.transparent, + antialias: options.antialias, + premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', + stencil: true, + 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); + } + + /** + * Add a new system to the renderer. + * @param {class} ClassRef - Class reference + * @param {string} [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. + * @return {PIXI.Renderer} Return instance of renderer + */ + addSystem(ClassRef, name) + { + if (!name) + { + name = ClassRef.name; + } + + // TODO - read name from class.name.. + + /* + if(name.includes('System')) + { + name = name.replace('System', ''); + name = name.charAt(0).toLowerCase() + name.slice(1); + } + */ + + const system = new ClassRef(this); + + if (this[name]) + { + throw new Error(`Whoops! ${name} is already a manger`); + } + + this[name] = system; + + for (const i in this.runners) + { + this.runners[i].add(system); + } + + return this; + + /** + * 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. + */ + } + + /** + * Renders the object to its webGL view + * + * @param {PIXI.DisplayObject} displayObject - the object to be rendered + * @param {PIXI.RenderTexture} renderTexture - The render texture to render to. + * @param {boolean} [clear] - Should the canvas be cleared before the new render + * @param {PIXI.Transform} [transform] - A transform to apply to the render texture before rendering. + * @param {boolean} [skipUpdateTransform] - Should we skip the update transform pass? + */ + render(displayObject, renderTexture, clear, transform, skipUpdateTransform) + { + // can be handy to know! + this.renderingToScreen = !renderTexture; + + this.runners.prerender.run(); + this.emit('prerender'); + + // 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.parent; + + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = 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.run(); + + this.emit('postrender'); + } + + /** + * Resizes the webGL view to the specified width and height. + * + * @param {number} screenWidth - the new width of the screen + * @param {number} screenHeight - the new height of the screen + */ + resize(screenWidth, screenHeight) + { + AbstractRenderer.prototype.resize.call(this, screenWidth, screenHeight); + + this.runners.resize.run(screenWidth, screenHeight); + } + + /** + * Resets the WebGL state so you can render things however you fancy! + * + * @return {PIXI.Renderer} Returns itself. + */ + reset() + { + this.runners.reset.run(); + + return this; + } + + clear() + { + this.framebuffer.bind(); + this.framebuffer.clear(); + } + + /** + * Removes everything from the renderer (event listeners, spritebatch, etc...) + * + * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. + * See: https://github.com/pixijs/pixi.js/issues/2233 + */ + destroy(removeView) + { + this.runners.destroy.run(); + + // call base destroy + super.destroy(removeView); + + // TODO nullify all the managers.. + this.gl = null; + } + + /** + * 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 + * builds and excluding plugins. + * @name PIXI.Renderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. + */ + + /** + * Adds a plugin to the renderer. + * + * @method + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + static registerPlugin(pluginName, ctor) + { + Renderer.__plugins = Renderer.__plugins || {}; + Renderer.__plugins[pluginName] = ctor; + } +} diff --git a/packages/core/src/System.js b/packages/core/src/System.js new file mode 100644 index 0000000..05a10c8 --- /dev/null +++ b/packages/core/src/System.js @@ -0,0 +1,42 @@ +/** + * System is a base class used for extending systems used by the {@link PIXI.Renderer} + * @see PIXI.Renderer#addSystem + * @class + * @memberof PIXI + */ +export default class System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + /** + * The renderer this manager works for. + * + * @member {PIXI.Renderer} + */ + this.renderer = renderer; + + this.renderer.runners.contextChange.add(this); + } + + /** + * Generic method called when there is a WebGL context change. + * + */ + contextChange() + { + // do some codes init! + } + + /** + * Generic destroy methods to be overridden by the subclass + * + */ + destroy() + { + this.renderer.runners.contextChange.remove(this); + this.renderer = null; + } +} diff --git a/packages/core/src/batch/BatchSystem.js b/packages/core/src/batch/BatchSystem.js new file mode 100644 index 0000000..b04c249 --- /dev/null +++ b/packages/core/src/batch/BatchSystem.js @@ -0,0 +1,68 @@ +import System from '../System'; +import ObjectRenderer from './ObjectRenderer'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class BatchSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + super(renderer); + + /** + * An empty renderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.emptyRenderer = new ObjectRenderer(renderer); + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.currentRenderer = this.emptyRenderer; + } + + /** + * Changes the current renderer to the one given in parameter + * + * @param {PIXI.ObjectRenderer} objectRenderer - The object renderer to use. + */ + setObjectRenderer(objectRenderer) + { + if (this.currentRenderer === objectRenderer) + { + return; + } + + this.currentRenderer.stop(); + this.currentRenderer = objectRenderer; + + this.renderer.state.setState(objectRenderer.state); + + this.currentRenderer.start(); + } + + /** + * This should be called if you wish to do some custom rendering + * It will basically render anything that may be batched up such as sprites + * + */ + flush() + { + this.setObjectRenderer(this.emptyRenderer); + } + + reset() + { + this.setObjectRenderer(this.emptyRenderer); + } +} diff --git a/packages/core/src/batch/ObjectRenderer.js b/packages/core/src/batch/ObjectRenderer.js new file mode 100644 index 0000000..0c851a9 --- /dev/null +++ b/packages/core/src/batch/ObjectRenderer.js @@ -0,0 +1,48 @@ +import System from '../System'; + +/** + * Base for a common object renderer that can be used as a system renderer plugin. + * + * @class + * @extends PIXI.System + * @memberof PIXI + */ +export default class ObjectRenderer extends System +{ + /** + * Starts the renderer and sets the shader + * + */ + start() + { + // set the shader.. + } + + /** + * Stops the renderer + * + */ + stop() + { + this.flush(); + } + + /** + * Stub method for rendering content and emptying the current batch. + * + */ + flush() + { + // flush! + } + + /** + * Renders an object + * + * @param {PIXI.DisplayObject} object - The object to render. + */ + render(object) // eslint-disable-line no-unused-vars + { + // render the object + } +} diff --git a/packages/core/src/context/ContextSystem.js b/packages/core/src/context/ContextSystem.js new file mode 100644 index 0000000..301aab4 --- /dev/null +++ b/packages/core/src/context/ContextSystem.js @@ -0,0 +1,186 @@ +import System from '../System'; +import { settings } from '@pixi/settings'; + +let CONTEXT_UID = 0; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class ContextSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.webGLVersion = 1; + + this.handleContextLost = this.handleContextLost.bind(this); + this.handleContextRestored = this.handleContextRestored.bind(this); + + this.extensions = {}; + + renderer.view.addEventListener('webglcontextlost', this.handleContextLost, false); + renderer.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + } + + get isLost() + { + return (!this.gl || this.gl.isContextLost()); + } + + contextChange(gl) + { + this.gl = gl; + + // restore a context if it was previously lost + if (gl.isContextLost() && gl.getExtension('WEBGL_lose_context')) + { + gl.getExtension('WEBGL_lose_context').restoreContext(); + } + } + + initFromContext(gl) + { + this.gl = gl; + this.validateContext(gl); + this.renderer.gl = gl; + this.renderer.CONTEXT_UID = CONTEXT_UID++; + this.renderer.runners.contextChange.run(gl); + } + + initFromOptions(options) + { + const gl = this.createContext(this.renderer.view, options); + + this.initFromContext(gl); + } + + /** + * Helper class to create a webGL Context + * + * @class + * @memberof PIXI.glCore + * @param canvas {HTMLCanvasElement} the canvas element that we will get the context from + * @param options {Object} An options object that gets passed in to the canvas element containing the context attributes, + * see https://developer.mozilla.org/en/docs/Web/API/HTMLCanvasElement/getContext for the options available + * @return {WebGLRenderingContext} the WebGL context + */ + createContext(canvas, options) + { + let gl; + + if (settings.PREFER_WEBGL_2) + { + gl = canvas.getContext('webgl2', options); + } + + if (gl) + { + this.webGLVersion = 2; + } + else + { + this.webGLVersion = 1; + + gl = canvas.getContext('webgl', options) + || canvas.getContext('experimental-webgl', options); + + if (!gl) + { + // fail, not able to get a context + throw new Error('This browser does not support webGL. Try using the canvas renderer'); + } + } + + this.gl = gl; + + this.getExtensions(); + + return gl; + } + + getExtensions() + { + // time to set up default etensions that pixi uses.. + const gl = this.gl; + const extensions = this.extensions; + + if (this.webGLVersion === 1) + { + extensions.drawBuffers = gl.getExtension('WEBGL_draw_buffers'); + extensions.depthTexture = gl.getExtension('WEBKIT_WEBGL_depth_texture'); + extensions.floatTexture = gl.getExtension('OES_texture_float'); + extensions.loseContext = gl.getExtension('WEBGL_lose_context'); + + extensions.vertexArrayObject = gl.getExtension('OES_vertex_array_object') + || gl.getExtension('MOZ_OES_vertex_array_object') + || gl.getExtension('WEBKIT_OES_vertex_array_object'); + } + + // we don't use any specific WebGL 2 ones yet! + } + + /** + * Handles a lost webgl context + * + * @private + * @param {WebGLContextEvent} event - The context lost event. + */ + handleContextLost(event) + { + event.preventDefault(); + } + + /** + * Handles a restored webgl context + * + * @private + */ + handleContextRestored() + { + this.renderer.runners.contextChange.run(this.gl); + } + + destroy() + { + const view = this.renderer.view; + + // remove listeners + view.removeEventListener('webglcontextlost', this.handleContextLost); + view.removeEventListener('webglcontextrestored', this.handleContextRestored); + + this.gl.useProgram(null); + + if (this.extensions.loseContext) + { + this.extensions.loseContext.loseContext(); + } + } + + postrender() + { + this.gl.flush(); + } + + validateContext(gl) + { + const attributes = gl.getContextAttributes(); + + // this is going to be fairly simple for now.. but at least we have room to grow! + if (!attributes.stencil) + { + /* eslint-disable max-len */ + + /* eslint-disable no-console */ + console.warn('Provided WebGL context does not have a stencil buffer, masks may not render correctly'); + /* eslint-enable no-console */ + + /* eslint-enable max-len */ + } + } +} diff --git a/packages/core/src/filters/Filter.js b/packages/core/src/filters/Filter.js new file mode 100644 index 0000000..9d8cad8 --- /dev/null +++ b/packages/core/src/filters/Filter.js @@ -0,0 +1,140 @@ +import Shader from '../shader/Shader'; +import Program from '../shader/Program'; +import State from '../state/State'; +import { settings } from '@pixi/settings'; +// import extractUniformsFromSrc from './extractUniformsFromSrc'; +import defaultVertex from './defaultFilter.vert'; +import defaultFragment from './defaultFilter.frag'; + +// let math = require('../../math'); +/** + * @class + * @memberof PIXI + * @extends PIXI.Shader + */ +export default class Filter extends Shader +{ + /** + * @param {string} [vertexSrc] - The source of the vertex shader. + * @param {string} [fragmentSrc] - The source of the fragment shader. + * @param {object} [uniforms] - Custom uniforms to use to augment the built-in ones. + */ + constructor(vertexSrc, fragmentSrc, uniforms) + { + const program = Program.from(vertexSrc, fragmentSrc); + + super(program, uniforms); + + /** + * The padding of the filter. Some filters require extra space to breath such as a blur. + * Increasing this will add extra width and height to the bounds of the object that the + * filter is applied to. + * + * @member {number} + */ + this.padding = 0; + + /** + * The resolution of the filter. Setting this to be lower will lower the quality but + * increase the performance of the filter. + * + * @member {number} + */ + this.resolution = settings.RESOLUTION; + + /** + * If enabled is true the filter is applied, if false it will not. + * + * @member {boolean} + */ + this.enabled = true; + + /** + * If enabled, PixiJS will fit the filter area into boundaries for better performance. + * Switch it off if it does not work for specific shader. + * + * @member {boolean} + */ + this.autoFit = true; + + /** + * Legacy filters use position and uvs from attributes + * @member {boolean} + * @readonly + */ + this.legacy = !!this.program.attributeData.aTextureCoord; + + /** + * the webGL state the filter requires to render + * @member {PIXI.State} + */ + this.state = new State(); + } + + /** + * Applies the filter + * + * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + * @param {boolean} clear - Should the output be cleared before rendering to it + * @param {object} [currentState] - It's current state of filter. + * There are some useful properties in the currentState : + * target, filters, sourceFrame, destinationFrame, renderTarget, resolution + */ + apply(filterManager, input, output, clear, currentState, derp) // eslint-disable-line no-unused-vars + { + // do as you please! + + filterManager.applyFilter(this, input, output, clear, currentState, derp); + + // or just do a regular render.. + } + + /** + * Sets the blendmode of the filter + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL + */ + get blendMode() + { + return this.state.blendMode; + } + + set blendMode(value) // eslint-disable-line require-jsdoc + { + this.state.blendMode = value; + } + + /** + * The default vertex shader source + * + * @static + * @constant + */ + static get defaultVertexSrc() + { + return defaultVertex; + } + + /** + * The default fragment shader source + * + * @static + * @constant + */ + static get defaultFragmentSrc() + { + return defaultFragment; + } +} + +/** + * Used for caching shader IDs + * + * @static + * @private + */ +Filter.SOURCE_KEY_MAP = {}; + diff --git a/packages/core/src/filters/FilterSystem.js b/packages/core/src/filters/FilterSystem.js new file mode 100644 index 0000000..3174318 --- /dev/null +++ b/packages/core/src/filters/FilterSystem.js @@ -0,0 +1,431 @@ +import System from '../System'; + +import RenderTexture from '../renderTexture/RenderTexture'; +import Quad from '../utils/Quad'; +import QuadUv from '../utils/QuadUv'; +import { Rectangle } from '@pixi/math'; +import * as filterTransforms from './filterTransforms'; +import bitTwiddle from 'bit-twiddle'; +import UniformGroup from '../shader/UniformGroup'; +import { DRAW_MODES } from '@pixi/constants'; + +// +/** + * @ignore + * @class + */ +class FilterState +{ + /** + * + */ + constructor() + { + this.renderTexture = null; + this.sourceFrame = new Rectangle(); + this.destinationFrame = new Rectangle(); + this.filters = []; + this.target = null; + this.legacy = false; + this.resolution = 1; + } +} + +/** + * @class + * @memberof PIXI.systems + * @extends PIXI.System + */ +export default class FilterSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + /** + * stores a bunch of PO2 textures used for filtering + * @type {Object} + */ + this.texturePool = {}; + + /** + * a pool for storing filter states, save us creating new ones each tick + * @type {Array} + */ + this.statePool = []; + + /** + * A very simple geometry used when drawing a filter effect to the screen + * @type {Quad} + */ + this.quad = new Quad(); + + this.quadUv = new QuadUv(); + + /** + * Temporary rect for maths + * @type {PIXI.Rectangle} + */ + this.tempRect = new Rectangle(); + + this.activeState = {}; + + /** + * this uniform group is attached to filter uniforms when used + * @type {UniformGroup} + */ + this.globalUniforms = new UniformGroup({ + sourceFrame: this.tempRect, + destinationFrame: this.tempRect, + + // legacy variables + filterArea: new Float32Array(4), + filterClamp: new Float32Array(4), + }, true); + } + + /** + * Adds a new filter to the System. + * + * @param {PIXI.DisplayObject} target - The target of the filter to render. + * @param {PIXI.Filter[]} filters - The filters to apply. + */ + push(target, filters) + { + const renderer = this.renderer; + const filterStack = this.renderer.renderTexture.defaultFilterStack; + const state = this.statePool.pop() || new FilterState(); + + let resolution = filters[0].resolution; + let padding = filters[0].padding; + let autoFit = filters[0].autoFit; + let legacy = filters[0].legacy; + + for (let i = 1; i < filters.length; i++) + { + const filter = filters[i]; + + // lets use the lowest resolution.. + resolution = Math.min(resolution, filter.resolution); + // and the largest amount of padding! + padding = Math.max(padding, filter.padding); + // only auto fit if all filters are autofit + autoFit = autoFit || filter.autoFit; + + legacy = legacy || filter.legacy; + } + + filterStack.push(state); + + state.resolution = resolution; + + state.legacy = legacy; + + // round to whole number based on resolution + // TODO move that to the shader too? + state.sourceFrame = target.filterArea || target.getBounds(true); + + state.sourceFrame.pad(padding); + + if (autoFit) + { + state.sourceFrame.fit(this.renderer.renderTexture.destinationFrame); + } + + state.sourceFrame.round(resolution); + + state.renderTexture = this.getPotFilterTexture(state.sourceFrame.width, state.sourceFrame.height, resolution); + state.filters = filters; + + state.destinationFrame.width = state.renderTexture.width; + state.destinationFrame.height = state.renderTexture.height; + + state.renderTexture.filterFrame = state.sourceFrame; + renderer.renderTexture.bind(state.renderTexture, state.sourceFrame);// /, state.destinationFrame); + renderer.renderTexture.clear(); + } + + /** + * Pops off the filter and applies it. + * + */ + pop() + { + const renderer = this.renderer; + const filterStack = renderer.renderTexture.defaultFilterStack; + const state = filterStack.pop(); + const filters = state.filters; + + this.activeState = state; + + const globalUniforms = this.globalUniforms.uniforms; + + globalUniforms.sourceFrame = state.sourceFrame; + globalUniforms.destinationFrame = state.destinationFrame; + globalUniforms.resolution = state.resolution; + + // only update the rect if its legacy.. + if (state.legacy) + { + const filterArea = globalUniforms.filterArea; + const filterClamp = globalUniforms.filterClamp; + + filterArea[0] = state.destinationFrame.width; + filterArea[1] = state.destinationFrame.height; + filterArea[2] = state.sourceFrame.x; + filterArea[3] = state.sourceFrame.y; + + filterClamp[0] = 0.5 / state.resolution / state.destinationFrame.width; + filterClamp[1] = 0.5 / state.resolution / state.destinationFrame.height; + filterClamp[2] = (state.sourceFrame.width - 0.5) / state.resolution / state.destinationFrame.width; + filterClamp[3] = (state.sourceFrame.height - 0.5) / state.resolution / state.destinationFrame.height; + } + + this.globalUniforms.update(); + + const lastState = filterStack[filterStack.length - 1]; + + if (filters.length === 1) + { + filters[0].apply(this, state.renderTexture, lastState.renderTexture, false, state); + + this.returnFilterTexture(state.renderTexture); + } + else + { + let flip = state.renderTexture; + let flop = this.getPotFilterTexture( + flip.width, + flip.height, + state.resolution + ); + + flop.filterFrame = flip.filterFrame; + + let i = 0; + + for (i = 0; i < filters.length - 1; ++i) + { + filters[i].apply(this, flip, flop, true, state); + + const t = flip; + + flip = flop; + flop = t; + } + + filters[i].apply(this, flip, lastState.renderTexture, false, state); + + this.returnFilterTexture(flip); + this.returnFilterTexture(flop); + } + + this.statePool.push(state); + } + + /** + * Draws a filter. + * + * @param {PIXI.Filter} filter - The filter to draw. + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + * @param {boolean} clear - Should the output be cleared before rendering to it + */ + applyFilter(filter, input, output, clear) + { + const renderer = this.renderer; + + renderer.renderTexture.bind(output, output ? output.filterFrame : null); + + if (clear) + { + // gl.disable(gl.SCISSOR_TEST); + renderer.renderTexture.clear(); + // gl.enable(gl.SCISSOR_TEST); + } + + // set the uniforms.. + filter.uniforms.uSampler = input; + filter.uniforms.filterGlobals = this.globalUniforms; + + // TODO make it so that the order of this does not matter.. + // because it does at the moment cos of global uniforms. + // they need to get resynced + + renderer.state.setState(filter.state); + renderer.shader.bind(filter); + + if (filter.legacy) + { + this.quadUv.map(input._frame, input.filterFrame); + + renderer.geometry.bind(this.quadUv); + renderer.geometry.draw(DRAW_MODES.TRIANGLES); + } + else + { + renderer.geometry.bind(this.quad); + renderer.geometry.draw(DRAW_MODES.TRIANGLE_STRIP); + } + } + + /** + * Calculates the mapped matrix. + * + * TODO playing around here.. this is temporary - (will end up in the shader) + * this returns a matrix that will normalise map filter cords in the filter to screen space + * + * @param {PIXI.Matrix} outputMatrix - the matrix to output to. + * @return {PIXI.Matrix} The mapped matrix. + */ + calculateScreenSpaceMatrix(outputMatrix) + { + const currentState = this.activeState; + + return filterTransforms.calculateScreenSpaceMatrix( + outputMatrix, + currentState.sourceFrame, + currentState.destinationFrame + ); + } + + /** + * This will map the filter coord so that a texture can be used based on the transform of a sprite + * + * @param {PIXI.Matrix} outputMatrix - The matrix to output to. + * @param {PIXI.Sprite} sprite - The sprite to map to. + * @return {PIXI.Matrix} The mapped matrix. + */ + calculateSpriteMatrix(outputMatrix, sprite) + { + const currentState = this.activeState; + + return filterTransforms.calculateSpriteMatrix( + outputMatrix, + currentState.sourceFrame, + currentState.destinationFrame, + sprite + ); + } + + /** + * Destroys this Filter System. + * + * @param {boolean} [contextLost=false] context was lost, do not free shaders + * + */ + destroy(contextLost = false) + { + if (!contextLost) + { + this.emptyPool(); + } + else + { + this.texturePool = {}; + } + } + + /** + * Gets a Power-of-Two render texture. + * + * TODO move to a seperate class could be on renderer? + * also - could cause issue with multiple contexts? + * + * @private + * @param {WebGLRenderingContext} gl - The webgl rendering context + * @param {number} minWidth - The minimum width of the render target. + * @param {number} minHeight - The minimum height of the render target. + * @param {number} resolution - The resolution of the render target. + * @return {PIXI.RenderTarget} The new render target. + */ + getPotFilterTexture(minWidth, minHeight, resolution) + { + minWidth = bitTwiddle.nextPow2(minWidth); + minHeight = bitTwiddle.nextPow2(minHeight); + resolution = resolution || 1; + + const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + + if (!this.texturePool[key]) + { + this.texturePool[key] = []; + } + + let renderTexture = this.texturePool[key].pop(); + + if (!renderTexture) + { + // temporary bypass cache.. + // internally - this will cause a texture to be bound.. + renderTexture = RenderTexture.create({ + width: minWidth, + height: minHeight, + resolution, + }); + } + + return renderTexture; + } + + /** + * Gets extra render texture to use inside current filter + * + * @param {number} resolution resolution of the renderTexture + * @returns {PIXI.RenderTexture} + */ + getFilterTexture(resolution) + { + const rt = this.activeState.renderTexture; + + const filterTexture = this.getPotFilterTexture(rt.width, rt.height, resolution || rt.baseTexture.resolution); + + filterTexture.filterFrame = rt.filterFrame; + + return filterTexture; + } + + /** + * Frees a render texture back into the pool. + * + * @param {PIXI.RenderTarget} renderTexture - The renderTarget to free + */ + returnFilterTexture(renderTexture) + { + renderTexture.filterFrame = null; + + const base = renderTexture.baseTexture; + + const minWidth = base.width; + const minHeight = base.height; + + const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + + this.texturePool[key].push(renderTexture); + } + + /** + * Empties the texture pool. + * + */ + emptyPool() + { + for (const i in this.texturePool) + { + const textures = this.texturePool[i]; + + if (textures) + { + for (let j = 0; j < textures.length; j++) + { + textures[j].destroy(true); + } + } + } + + this.texturePool = {}; + } +} diff --git a/packages/core/src/filters/defaultFilter.frag b/packages/core/src/filters/defaultFilter.frag new file mode 100644 index 0000000..a14c1be --- /dev/null +++ b/packages/core/src/filters/defaultFilter.frag @@ -0,0 +1,22 @@ +varying vec2 vTextureCoord; +varying vec2 vFilterCoord; + +uniform sampler2D uSampler; +uniform sampler2D filterSampler; + +void main(void){ + vec4 masky = texture2D(filterSampler, vFilterCoord); + vec4 sample = texture2D(uSampler, vTextureCoord); + vec4 color; + if(mod(vFilterCoord.x, 1.0) > 0.5) + { + color = vec4(1.0, 0.0, 0.0, 1.0); + } + else + { + color = vec4(0.0, 1.0, 0.0, 1.0); + } + // gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0); + gl_FragColor = mix(sample, masky, 0.5); + gl_FragColor *= sample.a; +} diff --git a/packages/core/src/filters/defaultFilter.vert b/packages/core/src/filters/defaultFilter.vert new file mode 100644 index 0000000..e1febca --- /dev/null +++ b/packages/core/src/filters/defaultFilter.vert @@ -0,0 +1,14 @@ +attribute vec2 aVertexPosition; +attribute vec2 aTextureCoord; + +uniform mat3 projectionMatrix; +uniform mat3 filterMatrix; + +varying vec2 vTextureCoord; +varying vec2 vFilterCoord; + +void main(void){ + gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); + vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy; + vTextureCoord = aTextureCoord ; +} diff --git a/packages/core/src/filters/filterTransforms.js b/packages/core/src/filters/filterTransforms.js new file mode 100644 index 0000000..87a3cd6 --- /dev/null +++ b/packages/core/src/filters/filterTransforms.js @@ -0,0 +1,50 @@ +import { Matrix } from '@pixi/math'; + +/** + * Calculates the mapped matrix + * @param filterArea {Rectangle} The filter area + * @param sprite {Sprite} the target sprite + * @param outputMatrix {Matrix} @alvin + * @private + */ +// this returns a matrix that will normalise map filter cords in the filter to screen space +export function calculateScreenSpaceMatrix(outputMatrix, filterArea, textureSize) +{ + // TODO unwrap? + const mappedMatrix = outputMatrix.identity(); + + mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); + + mappedMatrix.scale(textureSize.width, textureSize.height); + + return mappedMatrix; +} + +export function calculateNormalizedScreenSpaceMatrix(outputMatrix, filterArea, textureSize) +{ + const mappedMatrix = outputMatrix.identity(); + + mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); + + const translateScaleX = (textureSize.width / filterArea.width); + const translateScaleY = (textureSize.height / filterArea.height); + + mappedMatrix.scale(translateScaleX, translateScaleY); + + return mappedMatrix; +} + +// this will map the filter coord so that a texture can be used based on the transform of a sprite +export function calculateSpriteMatrix(outputMatrix, filterArea, textureSize, sprite) +{ + const orig = sprite._texture.orig; + const mappedMatrix = outputMatrix.set(textureSize.width, 0, 0, textureSize.height, filterArea.x, filterArea.y); + const worldTransform = sprite.worldTransform.copyTo(Matrix.TEMP_MATRIX); + + worldTransform.invert(); + mappedMatrix.prepend(worldTransform); + mappedMatrix.scale(1.0 / orig.width, 1.0 / orig.height); + mappedMatrix.translate(sprite.anchor.x, sprite.anchor.y); + + return mappedMatrix; +} diff --git a/packages/core/src/filters/spriteMask/SpriteMaskFilter.js b/packages/core/src/filters/spriteMask/SpriteMaskFilter.js new file mode 100644 index 0000000..91697b7 --- /dev/null +++ b/packages/core/src/filters/spriteMask/SpriteMaskFilter.js @@ -0,0 +1,63 @@ +import Filter from '../Filter'; +import { Matrix } from '@pixi/math'; +import vertex from './spriteMaskFilter.vert'; +import fragment from './spriteMaskFilter.frag'; +import { default as TextureMatrix } from '../../textures/TextureMatrix'; + +/** + * The SpriteMaskFilter class + * + * @class + * @extends PIXI.Filter + * @memberof PIXI + */ +export default class SpriteMaskFilter extends Filter +{ + /** + * @param {PIXI.Sprite} sprite - the target sprite + */ + constructor(sprite) + { + const maskMatrix = new Matrix(); + + super(vertex, fragment); + + sprite.renderable = false; + + this.maskSprite = sprite; + this.maskMatrix = maskMatrix; + } + + /** + * Applies the filter + * + * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + */ + apply(filterManager, input, output) + { + const maskSprite = this.maskSprite; + const tex = this.maskSprite.texture; + + if (!tex.valid) + { + return; + } + if (!tex.transform) + { + // margin = 0.0, let it bleed a bit, shader code becomes easier + // assuming that atlas textures were made with 1-pixel padding + tex.transform = new TextureMatrix(tex, 0.0); + } + tex.transform.update(); + + this.uniforms.mask = tex; + this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite) + .prepend(tex.transform.mapCoord); + this.uniforms.alpha = maskSprite.worldAlpha; + this.uniforms.maskClamp = tex.transform.uClampFrame; + + filterManager.applyFilter(this, input, output); + } +} diff --git a/packages/core/src/filters/spriteMask/spriteMaskFilter.frag b/packages/core/src/filters/spriteMask/spriteMaskFilter.frag new file mode 100644 index 0000000..0e4aef8 --- /dev/null +++ b/packages/core/src/filters/spriteMask/spriteMaskFilter.frag @@ -0,0 +1,23 @@ +varying vec2 vMaskCoord; +varying vec2 vTextureCoord; + +uniform sampler2D uSampler; +uniform sampler2D mask; +uniform float alpha; +uniform vec4 maskClamp; + +void main(void) +{ + float clip = step(3.5, + step(maskClamp.x, vMaskCoord.x) + + step(maskClamp.y, vMaskCoord.y) + + step(vMaskCoord.x, maskClamp.z) + + step(vMaskCoord.y, maskClamp.w)); + + vec4 original = texture2D(uSampler, vTextureCoord); + vec4 masky = texture2D(mask, vMaskCoord); + + original *= (masky.r * masky.a * alpha * clip); + + gl_FragColor = original; +} diff --git a/packages/core/src/filters/spriteMask/spriteMaskFilter.vert b/packages/core/src/filters/spriteMask/spriteMaskFilter.vert new file mode 100644 index 0000000..d23a30c --- /dev/null +++ b/packages/core/src/filters/spriteMask/spriteMaskFilter.vert @@ -0,0 +1,16 @@ +attribute vec2 aVertexPosition; +attribute vec2 aTextureCoord; + +uniform mat3 projectionMatrix; +uniform mat3 otherMatrix; + +varying vec2 vMaskCoord; +varying vec2 vTextureCoord; + +void main(void) +{ + gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); + + vTextureCoord = aTextureCoord; + vMaskCoord = ( otherMatrix * vec3( aTextureCoord, 1.0) ).xy; +} diff --git a/packages/core/src/framebuffer/FrameBuffer.js b/packages/core/src/framebuffer/FrameBuffer.js new file mode 100644 index 0000000..84e9fcc --- /dev/null +++ b/packages/core/src/framebuffer/FrameBuffer.js @@ -0,0 +1,106 @@ +import Texture from '../textures/BaseTexture'; +import { FORMATS, TYPES } from '@pixi/constants'; + +/** + * Frame buffer + * @class + * @memberof PIXI + */ +export default class FrameBuffer +{ + constructor(width, height) + { + this.width = width || 100; + this.height = height || 100; + + this.stencil = false; + this.depth = false; + + this.dirtyId = 0; + this.dirtyFormat = 0; + this.dirtySize = 0; + + this.depthTexture = null; + this.colorTextures = []; + + this.glFrameBuffers = {}; + } + + get colorTexture() + { + return this.colorTextures[0]; + } + + addColorTexture(index, texture) + { + // TODO add some validation to the texture - same width / height etc? + this.colorTextures[index || 0] = texture || new Texture(null, { scaleMode: 0, + resolution: 1, + mipmap: false, + width: this.width, + height: this.height });// || new Texture(); + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + addDepthTexture(texture) + { + /* eslint-disable max-len */ + this.depthTexture = texture || new Texture(null, { scaleMode: 0, + resolution: 1, + width: this.width, + height: this.height, + mipmap: false, + format: FORMATS.DEPTH_COMPONENT, + type: TYPES.UNSIGNED_SHORT });// UNSIGNED_SHORT; + /* eslint-disable max-len */ + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + enableDepth() + { + this.depth = true; + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + enableStencil() + { + this.stencil = true; + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + resize(width, height) + { + if (width === this.width && height === this.height) return; + + this.width = width; + this.height = height; + + this.dirtyId++; + this.dirtySize++; + + for (let i = 0; i < this.colorTextures.length; i++) + { + this.colorTextures[i].setSize(width, height); + } + + if (this.depthTexture) + { + this.depthTexture.setSize(width, height); + } + } +} diff --git a/packages/core/src/framebuffer/FramebufferSystem.js b/packages/core/src/framebuffer/FramebufferSystem.js new file mode 100644 index 0000000..2cc2437 --- /dev/null +++ b/packages/core/src/framebuffer/FramebufferSystem.js @@ -0,0 +1,246 @@ +import System from '../System'; +import { Rectangle } from '@pixi/math'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class FramebufferSystem extends System +{ + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + contextChange() + { + this.gl = this.renderer.gl; + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + this.current = null; + this.viewport = new Rectangle(); + + this.drawBufferExtension = this.renderer.context.extensions.drawBuffers; + } + + bind(framebuffer, frame) + { + const gl = this.gl; + + this.current = framebuffer; + + if (framebuffer) + { + // TODO cacheing layer! + + const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID] || this.initFramebuffer(framebuffer); + + gl.bindFramebuffer(gl.FRAMEBUFFER, fbo.framebuffer); + // makesure all textures are unbound.. + + // now check for updates... + if (fbo.dirtyId !== framebuffer.dirtyId) + { + fbo.dirtyId = framebuffer.dirtyId; + + if (fbo.dirtyFormat !== framebuffer.dirtyFormat) + { + fbo.dirtyFormat = framebuffer.dirtyFormat; + this.updateFramebuffer(framebuffer); + } + else if (fbo.dirtySize !== framebuffer.dirtySize) + { + fbo.dirtySize = framebuffer.dirtySize; + this.resizeFramebuffer(framebuffer); + } + } + + for (let i = 0; i < framebuffer.colorTextures.length; i++) + { + if (framebuffer.colorTextures[i].texturePart) + { + this.renderer.texture.unbind(framebuffer.colorTextures[i].texture); + } + else + { + this.renderer.texture.unbind(framebuffer.colorTextures[i]); + } + } + + if (framebuffer.depthTexture) + { + this.renderer.texture.unbind(framebuffer.depthTexture); + } + + if (frame) + { + this.setViewport(frame.x, frame.y, frame.width, frame.height); + } + else + { + this.setViewport(0, 0, framebuffer.width, framebuffer.height); + } + } + else + { + gl.bindFramebuffer(gl.FRAMEBUFFER, null); + + if (frame) + { + this.setViewport(frame.x, frame.y, frame.width, frame.height); + } + else + { + this.setViewport(0, 0, this.renderer.width, this.renderer.height); + } + } + } + + setViewport(x, y, width, height) + { + const v = this.viewport; + + if (v.width !== width || v.height !== height || v.x !== x || v.y !== y) + { + v.x = x; + v.y = y; + v.width = width; + v.height = height; + + this.gl.viewport(x, y, width, height); + } + } + + get size() + { + if (this.current) + { + // TODO store temp + return { x: 0, y: 0, width: this.current.width, height: this.current.height }; + } + + return { x: 0, y: 0, width: this.renderer.width, height: this.renderer.height }; + } + + clear(r, g, b, a) + { + const gl = this.gl; + + // TODO clear color can be set only one right? + gl.clearColor(r, g, b, a); + gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); + } + + // private functions... + + initFramebuffer(framebuffer) + { + const gl = this.gl; + + // TODO - make this a class? + const fbo = { + framebuffer: gl.createFramebuffer(), + stencil: null, + dirtyId: 0, + dirtyFormat: 0, + dirtySize: 0, + }; + + framebuffer.glFrameBuffers[this.CONTEXT_UID] = fbo; + + return fbo; + } + + resizeFramebuffer(framebuffer) + { + const gl = this.gl; + + if (framebuffer.stencil || framebuffer.depth) + { + gl.bindRenderbuffer(gl.RENDERBUFFER, this.stencil); + gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); + } + } + + updateFramebuffer(framebuffer) + { + const gl = this.gl; + + const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID]; + + // bind the color texture + const colorTextures = framebuffer.colorTextures; + + let count = colorTextures.length; + + if (!this.drawBufferExtension) + { + count = Math.min(count, 1); + } + + const activeTextures = []; + + for (let i = 0; i < count; i++) + { + const texture = framebuffer.colorTextures[i]; + + if (texture.texturePart) + { + this.renderer.texture.bind(texture.texture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.COLOR_ATTACHMENT0 + i, + gl.TEXTURE_CUBE_MAP_NEGATIVE_X + texture.side, + texture.texture._glTextures[this.CONTEXT_UID].texture, + 0); + } + else + { + this.renderer.texture.bind(texture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.COLOR_ATTACHMENT0 + i, + gl.TEXTURE_2D, + texture._glTextures[this.CONTEXT_UID].texture, + 0); + } + + activeTextures.push(gl.COLOR_ATTACHMENT0 + i); + } + + if (this.drawBufferExtension && activeTextures.length > 1) + { + this.drawBufferExtension.drawBuffersWEBGL(activeTextures); + } + + if (framebuffer.depthTexture) + { + const depthTextureExt = this.renderer.context.extensions.depthTexture; + + if (depthTextureExt) + { + const depthTexture = framebuffer.depthTexture; + + this.renderer.texture.bind(depthTexture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.DEPTH_ATTACHMENT, + gl.TEXTURE_2D, + depthTexture._glTextures[this.CONTEXT_UID].texture, + 0); + } + } + + if (framebuffer.stencil || framebuffer.depth) + { + fbo.stencil = gl.createRenderbuffer(); + + gl.bindRenderbuffer(gl.RENDERBUFFER, fbo.stencil); + + // TODO.. this is depth AND stencil? + gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, fbo.stencil); + gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); + // fbo.enableStencil(); + } + } +} diff --git a/packages/core/src/geometry/GLBuffer.js b/packages/core/src/geometry/GLBuffer.js new file mode 100644 index 0000000..0477e8a --- /dev/null +++ b/packages/core/src/geometry/GLBuffer.js @@ -0,0 +1,9 @@ +export default class GLBuffer +{ + constructor(buffer) + { + this.buffer = buffer; + this.updateID = -1; + this.byteLength = -1; + } +} diff --git a/packages/core/src/geometry/GeometrySystem.js b/packages/core/src/geometry/GeometrySystem.js new file mode 100644 index 0000000..959f827 --- /dev/null +++ b/packages/core/src/geometry/GeometrySystem.js @@ -0,0 +1,388 @@ +import System from '../System'; +import GLBuffer from './GLBuffer'; + +const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class GeometrySystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this._activeGeometry = null; + this._activeVao = null; + + this.hasVao = true; + this.hasInstance = true; + } + + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + contextChange() + { + const gl = this.gl = this.renderer.gl; + + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + + // webgl2 + if (!gl.createVertexArray) + { + // webgl 1! + let nativeVaoExtension = this.renderer.context.extensions.vertexArrayObject; + + if (this.renderer.legacy) + { + nativeVaoExtension = null; + } + + if (nativeVaoExtension) + { + gl.createVertexArray = () => + nativeVaoExtension.createVertexArrayOES(); + + gl.bindVertexArray = (vao) => + nativeVaoExtension.bindVertexArrayOES(vao); + + gl.deleteVertexArray = (vao) => + nativeVaoExtension.deleteVertexArrayOES(vao); + } + else + { + this.hasVao = false; + gl.createVertexArray = () => + { + // empty + }; + + gl.bindVertexArray = () => + { + // empty + }; + + gl.deleteVertexArray = () => + { + // empty + }; + } + } + + if (!gl.vertexAttribDivisor) + { + const instanceExt = gl.getExtension('ANGLE_instanced_arrays'); + + if (instanceExt) + { + gl.vertexAttribDivisor = (a, b) => + instanceExt.vertexAttribDivisorANGLE(a, b); + + gl.drawElementsInstanced = (a, b, c, d, e) => + instanceExt.drawElementsInstancedANGLE(a, b, c, d, e); + + gl.drawArraysInstanced = (a, b, c, d) => + instanceExt.drawArraysInstancedANGLE(a, b, c, d); + } + else + { + this.hasInstance = false; + } + } + } + + /** + * Binds geometry so that is can be drawn. Creating a Vao if required + * @private + * @param {PIXI.Geometry} geometry instance of geometry to bind + */ + bind(geometry, shader) + { + shader = shader || this.renderer.shader.shader; + + const gl = this.gl; + + // not sure the best way to address this.. + // currently different shaders require different VAOs for the same geometry + // Still mulling over the best way to solve this one.. + // will likely need to modify the shader attribute locations at run time! + let vaos = geometry.glVertexArrayObjects[this.CONTEXT_UID]; + + if (!vaos) + { + geometry.glVertexArrayObjects[this.CONTEXT_UID] = vaos = {}; + } + + const vao = vaos[shader.program.id] || this.initGeometryVao(geometry, shader.program); + + this._activeGeometry = geometry; + + if (this._activeVao !== vao) + { + this._activeVao = vao; + + if (this.hasVao) + { + gl.bindVertexArray(vao); + } + else + { + this.activateVao(geometry, shader.program); + } + } + + // TODO - optimise later! + // don't need to loop through if nothing changed! + // maybe look to add an 'autoupdate' to geometry? + this.updateBuffers(); + } + + reset() + { + this.unbind(); + } + + updateBuffers() + { + const geometry = this._activeGeometry; + const gl = this.gl; + + for (let i = 0; i < geometry.buffers.length; i++) + { + const buffer = geometry.buffers[i]; + + const glBuffer = buffer._glBuffers[this.CONTEXT_UID]; + + if (buffer._updateID !== glBuffer.updateID) + { + glBuffer.updateID = buffer._updateID; + + // TODO can cache this on buffer! maybe added a getter / setter? + const type = buffer.index ? gl.ELEMENT_ARRAY_BUFFER : gl.ARRAY_BUFFER; + const drawType = buffer.static ? gl.STATIC_DRAW : gl.DYNAMIC_DRAW; + + gl.bindBuffer(type, glBuffer.buffer); + + if (glBuffer.byteLength >= buffer.data.byteLength) + { + // offset is always zero for now! + gl.bufferSubData(type, 0, buffer.data); + } + else + { + gl.bufferData(type, buffer.data, drawType); + } + } + } + } + + checkCompatability(geometry, program) + { + // geometry must have at least all the attributes that the shader requires. + const geometryAttributes = geometry.attributes; + const shaderAttributes = program.attributeData; + + for (const j in shaderAttributes) + { + if (!geometryAttributes[j]) + { + throw new Error(`shader and geometry incompatible, geometry missing the "${j}" attribute`); + } + } + } + + /** + * Creates a Vao with the same structure as the geometry and stores it on the geometry. + * @private + * @param {PIXI.Geometry} geometry instance of geometry to to generate Vao for + */ + initGeometryVao(geometry, program) + { + this.checkCompatability(geometry, program); + + const gl = this.gl; + const CONTEXT_UID = this.CONTEXT_UID; + const buffers = geometry.buffers; + const attributes = geometry.attributes; + + const tempStride = {}; + const tempStart = {}; + + for (const j in buffers) + { + tempStride[j] = 0; + tempStart[j] = 0; + } + + for (const j in attributes) + { + if (!attributes[j].size && program.attributeData[j]) + { + attributes[j].size = program.attributeData[j].size; + } + + tempStride[attributes[j].buffer] += attributes[j].size * byteSizeMap[attributes[j].type]; + } + + for (const j in attributes) + { + const attribute = attributes[j]; + const attribSize = attribute.size; + + if (attribute.stride === undefined) + { + if (tempStride[attribute.buffer] === attribSize * byteSizeMap[attribute.type]) + { + attribute.stride = 0; + } + else + { + attribute.stride = tempStride[attribute.buffer]; + } + } + + if (attribute.start === undefined) + { + attribute.start = tempStart[attribute.buffer]; + + tempStart[attribute.buffer] += attribSize * byteSizeMap[attribute.type]; + } + } + + // first update - and create the buffers! + // only create a gl buffer if it actually gets + for (let i = 0; i < buffers.length; i++) + { + const buffer = buffers[i]; + + if (!buffer._glBuffers[CONTEXT_UID]) + { + buffer._glBuffers[CONTEXT_UID] = new GLBuffer(gl.createBuffer()); + } + } + + // TODO - maybe make this a data object? + // lets wait to see if we need to first! + const vao = gl.createVertexArray(); + + gl.bindVertexArray(vao); + + this.activateVao(geometry, program); + + gl.bindVertexArray(null); + + // add it to the cache! + geometry.glVertexArrayObjects[this.CONTEXT_UID][program.id] = vao; + + return vao; + } + + activateVao(geometry, program) + { + const gl = this.gl; + const CONTEXT_UID = this.CONTEXT_UID; + const buffers = geometry.buffers; + const attributes = geometry.attributes; + + if (geometry.indexBuffer) + { + // first update the index buffer if we have one.. + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, geometry.indexBuffer._glBuffers[CONTEXT_UID].buffer); + } + + let lastBuffer = null; + + // add a new one! + for (const j in attributes) + { + const attribute = attributes[j]; + const buffer = buffers[attribute.buffer]; + const glBuffer = buffer._glBuffers[CONTEXT_UID]; + + if (program.attributeData[j]) + { + if (lastBuffer !== glBuffer) + { + gl.bindBuffer(gl.ARRAY_BUFFER, glBuffer.buffer); + + lastBuffer = glBuffer; + } + + const location = program.attributeData[j].location; + + // TODO introduce state again + // we can optimise this for older devices that have no VAOs + gl.enableVertexAttribArray(location); + + gl.vertexAttribPointer(location, + attribute.size, + attribute.type || gl.FLOAT, + attribute.normalized, + attribute.stride, + attribute.start); + + if (attribute.instance) + { + // TODO calculate instance count based of this... + if (this.hasInstance) + { + gl.vertexAttribDivisor(location, 1); + } + else + { + throw new Error('geometry error, GPU Instancing is not supported on this device'); + } + } + } + } + } + + draw(type, size, start, instanceCount) + { + const gl = this.gl; + const geometry = this._activeGeometry; + + // TODO.. this should not change so maybe cache the function? + + if (geometry.indexBuffer) + { + if (geometry.instanced) + { + /* eslint-disable max-len */ + gl.drawElementsInstanced(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2, instanceCount || 1); + /* eslint-enable max-len */ + } + else + { + gl.drawElements(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2); + } + } + else + if (geometry.instanced) + { + // TODO need a better way to calculate size.. + gl.drawArraysInstanced(type, start, size || geometry.getSize(), instanceCount || 1); + } + else + { + gl.drawArrays(type, start, size || geometry.getSize()); + } + + return this; + } + + unbind() + { + this.gl.bindVertexArray(null); + this._activeVao = null; + this._activeGeometry = null; + } +} diff --git a/packages/core/src/geometry/utils/setVertexAttribArrays.js b/packages/core/src/geometry/utils/setVertexAttribArrays.js new file mode 100644 index 0000000..23b6959 --- /dev/null +++ b/packages/core/src/geometry/utils/setVertexAttribArrays.js @@ -0,0 +1,54 @@ +// var GL_MAP = {}; + +/** + * @param gl {WebGLRenderingContext} The current WebGL context + * @param attribs {*} + * @param state {*} + */ +export default function setVertexAttribArrays(gl, attribs, state) +{ + let i; + + if (state) + { + const tempAttribState = state.tempAttribState; + const attribState = state.attribState; + + for (i = 0; i < tempAttribState.length; i++) + { + tempAttribState[i] = false; + } + + // set the new attribs + for (i = 0; i < attribs.length; i++) + { + tempAttribState[attribs[i].attribute.location] = true; + } + + for (i = 0; i < attribState.length; i++) + { + if (attribState[i] !== tempAttribState[i]) + { + attribState[i] = tempAttribState[i]; + + if (state.attribState[i]) + { + gl.enableVertexAttribArray(i); + } + else + { + gl.disableVertexAttribArray(i); + } + } + } + } + else + { + for (i = 0; i < attribs.length; i++) + { + const attrib = attribs[i]; + + gl.enableVertexAttribArray(attrib.attribute.location); + } + } +} diff --git a/packages/core/src/index.js b/packages/core/src/index.js index 58c456a..fd1f547 100644 --- a/packages/core/src/index.js +++ b/packages/core/src/index.js @@ -1,30 +1,30 @@ import * as resources from './textures/resources'; -import * as systems from './renderers/systems'; +import * as systems from './systems'; -export { resources }; export { systems }; +export { resources }; -export { default as Renderer } from './renderers/Renderer'; -export { default as AbstractRenderer } from './renderers/AbstractRenderer'; -export { default as FrameBuffer } from './textures/FrameBuffer'; +export { default as System } from './System'; +export { default as Renderer } from './Renderer'; +export { default as AbstractRenderer } from './AbstractRenderer'; +export { default as FrameBuffer } from './framebuffer/FrameBuffer'; export { default as CubeTexture } from './textures/CubeTexture'; export { default as BaseTexture } from './textures/BaseTexture'; export { default as Texture } from './textures/Texture'; export { default as TextureMatrix } from './textures/TextureMatrix'; -export { default as RenderTexture } from './textures/RenderTexture'; -export { default as BaseRenderTexture } from './textures/BaseRenderTexture'; +export { default as RenderTexture } from './renderTexture/RenderTexture'; +export { default as BaseRenderTexture } from './renderTexture/BaseRenderTexture'; export { default as TextureUvs } from './textures/TextureUvs'; -export { default as State } from './renderers/State'; -export { default as ObjectRenderer } from './renderers/utils/ObjectRenderer'; -export { default as Quad } from './renderers/utils/Quad'; -export { default as QuadUv } from './renderers/utils/QuadUv'; -export { default as checkMaxIfStatmentsInShader } from './renderers/utils/checkMaxIfStatmentsInShader'; +export { default as State } from './state/State'; +export { default as ObjectRenderer } from './batch/ObjectRenderer'; +export { default as Quad } from './utils/Quad'; +export { default as QuadUv } from './utils/QuadUv'; +export { default as checkMaxIfStatmentsInShader } from './shader/utils/checkMaxIfStatmentsInShader'; export { default as Shader } from './shader/Shader'; export { default as Program } from './shader/Program'; export { default as UniformGroup } from './shader/UniformGroup'; -export { default as SpriteMaskFilter } from './renderers/filters/spriteMask/SpriteMaskFilter'; -export { default as Filter } from './renderers/filters/Filter'; +export { default as SpriteMaskFilter } from './filters/spriteMask/SpriteMaskFilter'; +export { default as Filter } from './filters/Filter'; export { default as Attribute } from './geometry/Attribute'; export { default as Buffer } from './geometry/Buffer'; export { default as Geometry } from './geometry/Geometry'; - diff --git a/packages/core/src/mask/MaskSystem.js b/packages/core/src/mask/MaskSystem.js new file mode 100644 index 0000000..99174c2 --- /dev/null +++ b/packages/core/src/mask/MaskSystem.js @@ -0,0 +1,199 @@ +import System from '../System'; +import AlphaMaskFilter from '../filters/spriteMask/SpriteMaskFilter'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class MaskSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + // TODO - we don't need both! + this.scissor = false; + this.scissorData = null; + this.scissorRenderTarget = null; + + this.enableScissor = false; + + this.alphaMaskPool = []; + this.alphaMaskIndex = 0; + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.DisplayObject} target - Display Object to push the mask to + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + push(target, maskData) + { + // TODO the root check means scissor rect will not + // be used on render textures more info here: + // https://github.com/pixijs/pixi.js/pull/3545 + + if (maskData.texture) + { + this.pushSpriteMask(target, maskData); + } + else if (this.enableScissor + && !this.scissor + && this.renderer._activeRenderTarget.root + && !this.renderer.stencil.stencilMaskStack.length + && maskData.isFastRect()) + { + const matrix = maskData.worldTransform; + + let rot = Math.atan2(matrix.b, matrix.a); + + // use the nearest degree! + rot = Math.round(rot * (180 / Math.PI)); + + if (rot % 90) + { + this.pushStencilMask(maskData); + } + else + { + this.pushScissorMask(target, maskData); + } + } + else + { + this.pushStencilMask(maskData); + } + } + + /** + * Removes the last mask from the mask stack and doesn't return it. + * + * @param {PIXI.DisplayObject} target - Display Object to pop the mask from + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + pop(target, maskData) + { + if (maskData.texture) + { + this.popSpriteMask(target, maskData); + } + else if (this.enableScissor && !this.renderer.stencil.stencilMaskStack.length) + { + this.popScissorMask(target, maskData); + } + else + { + this.popStencilMask(target, maskData); + } + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.RenderTarget} target - Display Object to push the sprite mask to + * @param {PIXI.Sprite} maskData - Sprite to be used as the mask + */ + pushSpriteMask(target, maskData) + { + let alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; + + if (!alphaMaskFilter) + { + alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; + } + + alphaMaskFilter[0].resolution = this.renderer.resolution; + alphaMaskFilter[0].maskSprite = maskData; + + // TODO - may cause issues! + target.filterArea = maskData.getBounds(true); + + this.renderer.filterSystem.pushFilter(target, alphaMaskFilter); + + this.alphaMaskIndex++; + } + + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popSpriteMask() + { + this.renderer.filterSystem.popFilter(); + this.alphaMaskIndex--; + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + pushStencilMask(maskData) + { + this.renderer.batch.flush(); + this.renderer.stencil.pushStencil(maskData); + } + + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popStencilMask() + { + // this.renderer.currentRenderer.stop(); + this.renderer.stencil.popStencil(); + } + + /** + * + * @param {PIXI.DisplayObject} target - Display Object to push the mask to + * @param {PIXI.Graphics} maskData - The masking data. + */ + pushScissorMask(target, maskData) + { + maskData.renderable = true; + + const renderTarget = this.renderer._activeRenderTarget; + + const bounds = maskData.getBounds(); + + bounds.fit(renderTarget.size); + maskData.renderable = false; + + this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); + + const resolution = this.renderer.resolution; + + this.renderer.gl.scissor( + bounds.x * resolution, + (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, + bounds.width * resolution, + bounds.height * resolution + ); + + this.scissorRenderTarget = renderTarget; + this.scissorData = maskData; + this.scissor = true; + } + + /** + * + * + */ + popScissorMask() + { + this.scissorRenderTarget = null; + this.scissorData = null; + this.scissor = false; + + // must be scissor! + const gl = this.renderer.gl; + + gl.disable(gl.SCISSOR_TEST); + } +} diff --git a/packages/core/src/mask/StencilSystem.js b/packages/core/src/mask/StencilSystem.js new file mode 100644 index 0000000..62796a5 --- /dev/null +++ b/packages/core/src/mask/StencilSystem.js @@ -0,0 +1,133 @@ +import System from '../System'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class StencilSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + this.stencilMaskStack = []; + } + + /** + * Changes the mask stack that is used by this System. + * + * @param {PIXI.Graphics[]} stencilMaskStack - The mask stack + */ + setMaskStack(stencilMaskStack) + { + const gl = this.renderer.gl; + + if (stencilMaskStack.length !== this.stencilMaskStack.length) + { + if (stencilMaskStack.length === 0) + { + gl.disable(gl.STENCIL_TEST); + } + else + { + gl.enable(gl.STENCIL_TEST); + } + } + + this.stencilMaskStack = stencilMaskStack; + } + + /** + * Applies the Mask and adds it to the current stencil stack. @alvin + * + * @param {PIXI.Graphics} graphics - The mask + */ + pushStencil(graphics) + { + this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); + + // this.renderer._activeRenderTarget.attachStencilBuffer(); + + const gl = this.renderer.gl; + const prevMaskCount = this.stencilMaskStack.length; + + if (prevMaskCount === 0) + { + gl.enable(gl.STENCIL_TEST); + } + + this.stencilMaskStack.push(graphics); + + // Increment the refference stencil value where the new mask overlaps with the old ones. + gl.colorMask(false, false, false, false); + gl.stencilFunc(gl.EQUAL, prevMaskCount, this._getBitwiseMask()); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.INCR); + this.renderer.plugins.graphics.render(graphics); + + this._useCurrent(); + } + + /** + * Removes the last mask from the stencil stack. @alvin + */ + popStencil() + { + this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); + + const gl = this.renderer.gl; + const graphics = this.stencilMaskStack.pop(); + + if (this.stencilMaskStack.length === 0) + { + // the stack is empty! + gl.disable(gl.STENCIL_TEST); + gl.clear(gl.STENCIL_BUFFER_BIT); + gl.clearStencil(0); + } + else + { + // Decrement the refference stencil value where the popped mask overlaps with the other ones + gl.colorMask(false, false, false, false); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.DECR); + this.renderer.plugins.graphics.render(graphics); + + this._useCurrent(); + } + } + + /** + * Setup renderer to use the current stencil data. + */ + _useCurrent() + { + const gl = this.renderer.gl; + + gl.colorMask(true, true, true, true); + gl.stencilFunc(gl.EQUAL, this.stencilMaskStack.length, this._getBitwiseMask()); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP); + } + + /** + * Fill 1s equal to the number of acitve stencil masks. + * + * @return {number} The bitwise mask. + */ + _getBitwiseMask() + { + return (1 << this.stencilMaskStack.length) - 1; + } + + /** + * Destroys the mask stack. + * + */ + destroy() + { + super.destroy(this); + + this.stencilMaskStack = null; + } +} diff --git a/packages/core/src/projection/ProjectionSystem.js b/packages/core/src/projection/ProjectionSystem.js new file mode 100644 index 0000000..6e63b73 --- /dev/null +++ b/packages/core/src/projection/ProjectionSystem.js @@ -0,0 +1,73 @@ +import System from '../System'; +import { Matrix } from '@pixi/math'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class ProjectionSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.projectionMatrix = new Matrix(); + } + + update(destinationFrame, sourceFrame, resolution, root) + { + this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; + this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; + + this.calculateProjection(this.destinationFrame, this.sourceFrame, resolution, root); + + this.renderer.globalUniforms.uniforms.projectionMatrix = this.projectionMatrix; + this.renderer.globalUniforms.update(); + } + + /** + * Updates the projection matrix based on a projection frame (which is a rectangle) + * + * @param {Rectangle} destinationFrame - The destination frame. + * @param {Rectangle} sourceFrame - The source frame. + */ + calculateProjection(destinationFrame, sourceFrame, resolution, root) + { + const pm = this.projectionMatrix; + + // I don't think we will need this line.. + // pm.identity(); + + if (!root) + { + pm.a = (1 / destinationFrame.width * 2) * resolution; + pm.d = (1 / destinationFrame.height * 2) * resolution; + + pm.tx = -1 - (sourceFrame.x * pm.a); + pm.ty = -1 - (sourceFrame.y * pm.d); + } + else + { + pm.a = (1 / destinationFrame.width * 2) * resolution; + pm.d = (-1 / destinationFrame.height * 2) * resolution; + + pm.tx = -1 - (sourceFrame.x * pm.a); + pm.ty = 1 - (sourceFrame.y * pm.d); + } + } + + /** + * Sets the transform of the active render target to the given matrix + * + * @param {PIXI.Matrix} matrix - The transformation matrix + */ + setTransform()// matrix) + { + // this._activeRenderTarget.transform = matrix; + } +} diff --git a/packages/core/src/renderTexture/BaseRenderTexture.js b/packages/core/src/renderTexture/BaseRenderTexture.js new file mode 100644 index 0000000..88eb83b --- /dev/null +++ b/packages/core/src/renderTexture/BaseRenderTexture.js @@ -0,0 +1,137 @@ +import BaseTexture from '../textures/BaseTexture'; +import FrameBuffer from '../framebuffer/FrameBuffer'; + +/** + * A BaseRenderTexture is a special texture that allows any PixiJS display object to be rendered to it. + * + * __Hint__: All DisplayObjects (i.e. Sprites) that render to a BaseRenderTexture should be preloaded + * otherwise black rectangles will be drawn instead. + * + * A BaseRenderTexture takes a snapshot of any Display Object given to its render method. The position + * and rotation of the given Display Objects is ignored. For example: + * + * ```js + * let renderer = PIXI.autoDetectRenderer(1024, 1024, { view: canvas, ratio: 1 }); + * let baseRenderTexture = new PIXI.BaseRenderTexture(renderer, 800, 600); + * let sprite = PIXI.Sprite.fromImage("spinObj_01.png"); + * + * sprite.position.x = 800/2; + * sprite.position.y = 600/2; + * sprite.anchor.x = 0.5; + * sprite.anchor.y = 0.5; + * + * baseRenderTexture.render(sprite); + * ``` + * + * The Sprite in this case will be rendered using its local transform. To render this sprite at 0,0 + * you can clear the transform + * + * ```js + * + * sprite.setTransform() + * + * let baseRenderTexture = new PIXI.BaseRenderTexture(100, 100); + * let renderTexture = new PIXI.RenderTexture(baseRenderTexture); + * + * renderer.render(sprite, renderTexture); // Renders to center of RenderTexture + * ``` + * + * @class + * @extends PIXI.BaseTexture + * @memberof PIXI + */ +export default class BaseRenderTexture extends BaseTexture +{ + /** + * @param {object} [options] + * @param {number} [options.width=100] - The width of the base render texture + * @param {number} [options.height=100] - The height of the base render texture + * @param {PIXI.SCALE_MODES} [options.scaleMode] - See {@link PIXI.SCALE_MODES} for possible values + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the texture being generated + */ + constructor(options) + { + if (typeof options === 'number') + { + /* eslint-disable prefer-rest-params */ + // Backward compatibility of signature + const width = arguments[0]; + const height = arguments[1]; + const scaleMode = arguments[2]; + const resolution = arguments[3]; + + options = { width, height, scaleMode, resolution }; + /* eslint-enable prefer-rest-params */ + } + + super(null, options); + + const { width, height } = options || {}; + + // Set defaults + this.mipmap = false; + this.width = Math.ceil(width) || 100; + this.height = Math.ceil(height) || 100; + this.valid = true; + + /** + * A map of renderer IDs to webgl renderTargets + * + * @private + * @member {object} + */ + // this._glRenderTargets = {}; + + /** + * A reference to the canvas render target (we only need one as this can be shared across renderers) + * + * @private + * @member {object} + */ + this._canvasRenderTarget = null; + + this.clearColor = [0, 0, 0, 0]; + + this.frameBuffer = new FrameBuffer(width * this.resolution, height * this.resolution) + .addColorTexture(0, this); + + // TODO - could this be added the systems? + + /** + * The data structure for the stencil masks + * + * @member {PIXI.Graphics[]} + */ + this.stencilMaskStack = []; + + /** + * The data structure for the filters + * + * @member {PIXI.Graphics[]} + */ + this.filterStack = [{}]; + } + + /** + * Resizes the BaseRenderTexture. + * + * @param {number} width - The width to resize to. + * @param {number} height - The height to resize to. + */ + resize(width, height) + { + width = Math.ceil(width); + height = Math.ceil(height); + this.frameBuffer.resize(width * this.resolution, height * this.resolution); + } + + /** + * Destroys this texture + * + */ + destroy() + { + super.destroy(true); + this.renderer = null; + } +} diff --git a/packages/core/src/renderTexture/RenderTexture.js b/packages/core/src/renderTexture/RenderTexture.js new file mode 100644 index 0000000..f1ecf1f --- /dev/null +++ b/packages/core/src/renderTexture/RenderTexture.js @@ -0,0 +1,153 @@ +import BaseRenderTexture from './BaseRenderTexture'; +import Texture from '../textures/Texture'; + +/** + * A RenderTexture is a special texture that allows any PixiJS display object to be rendered to it. + * + * __Hint__: All DisplayObjects (i.e. Sprites) that render to a RenderTexture should be preloaded + * otherwise black rectangles will be drawn instead. + * + * A RenderTexture takes a snapshot of any Display Object given to its render method. For example: + * + * ```js + * let renderer = PIXI.autoDetectRenderer(1024, 1024, { view: canvas, ratio: 1 }); + * let renderTexture = PIXI.RenderTexture.create(800, 600); + * let sprite = PIXI.Sprite.fromImage("spinObj_01.png"); + * + * sprite.position.x = 800/2; + * sprite.position.y = 600/2; + * sprite.anchor.x = 0.5; + * sprite.anchor.y = 0.5; + * + * renderer.render(sprite, renderTexture); + * ``` + * + * The Sprite in this case will be rendered using its local transform. To render this sprite at 0,0 + * you can clear the transform + * + * ```js + * + * sprite.setTransform() + * + * let renderTexture = new PIXI.RenderTexture.create(100, 100); + * + * renderer.render(sprite, renderTexture); // Renders to center of RenderTexture + * ``` + * + * @class + * @extends PIXI.Texture + * @memberof PIXI + */ +export default class RenderTexture extends Texture +{ + /** + * @param {PIXI.BaseRenderTexture} baseRenderTexture - The renderer used for this RenderTexture + * @param {PIXI.Rectangle} [frame] - The rectangle frame of the texture to show + */ + constructor(baseRenderTexture, frame) + { + // support for legacy.. + let _legacyRenderer = null; + + if (!(baseRenderTexture instanceof BaseRenderTexture)) + { + /* eslint-disable prefer-rest-params, no-console */ + const width = arguments[1]; + const height = arguments[2]; + const scaleMode = arguments[3]; + const resolution = arguments[4]; + + // we have an old render texture.. + console.warn(`Please use RenderTexture.create(${width}, ${height}) instead of the ctor directly.`); + _legacyRenderer = arguments[0]; + /* eslint-enable prefer-rest-params, no-console */ + + frame = null; + baseRenderTexture = new BaseRenderTexture({ + width, + height, + scaleMode, + resolution, + }); + } + + /** + * The base texture object that this texture uses + * + * @member {BaseTexture} + */ + super(baseRenderTexture, frame); + + this.legacyRenderer = _legacyRenderer; + + /** + * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. + * + * @member {boolean} + */ + this.valid = true; + + /** + * FilterSystem temporary storage + * @private + * @member {PIXI.Rectangle} + */ + this.filterFrame = null; + + this._updateUvs(); + } + + /** + * Resizes the RenderTexture. + * + * @param {number} width - The width to resize to. + * @param {number} height - The height to resize to. + * @param {boolean} [resizeBaseTexture=true] - Should the baseTexture.width and height values be resized as well? + */ + resize(width, height, resizeBaseTexture = true) + { + width = Math.ceil(width); + height = Math.ceil(height); + + // TODO - could be not required.. + this.valid = (width > 0 && height > 0); + + this._frame.width = this.orig.width = width; + this._frame.height = this.orig.height = height; + + if (resizeBaseTexture) + { + this.baseTexture.resize(width, height); + } + + this._updateUvs(); + } + + /** + * A short hand way of creating a render texture. + * + * @param {object} [options] - Options + * @param {number} [options.width=100] - The width of the render texture + * @param {number} [options.height=100] - The height of the render texture + * @param {number} [options.scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the texture being generated + * @return {PIXI.RenderTexture} The new render texture + */ + static create(options) + { + // fallback, old-style: create(width, height, scaleMode, resolution) + if (typeof options === 'number') + { + /* eslint-disable prefer-rest-params */ + options = { + width: options, + height: arguments[1], + scaleMode: arguments[2], + resolution: arguments[3], + }; + /* eslint-enable prefer-rest-params */ + } + + return new RenderTexture(new BaseRenderTexture(options)); + } +} diff --git a/packages/core/src/renderTexture/RenderTextureSystem.js b/packages/core/src/renderTexture/RenderTextureSystem.js new file mode 100644 index 0000000..8d0ca61 --- /dev/null +++ b/packages/core/src/renderTexture/RenderTextureSystem.js @@ -0,0 +1,115 @@ +import System from '../System'; +import { Rectangle } from '@pixi/math'; + +const tempRect = new Rectangle(); + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class RenderTextureSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.clearColor = renderer._backgroundColorRgba; + + // TODO moe this property somewhere else! + this.defaultMaskStack = []; + this.defaultFilterStack = [{}]; + + // empty render texture? + this.renderTexture = null; + + this.destinationFrame = new Rectangle(); + } + + bind(renderTexture, sourceFrame, destinationFrame) + { + // TODO - do we want this?? + if (this.renderTexture === renderTexture) return; + this.renderTexture = renderTexture; + + const renderer = this.renderer; + + if (renderTexture) + { + const baseTexture = renderTexture.baseTexture; + + if (!destinationFrame) + { + tempRect.width = baseTexture.realWidth; + tempRect.height = baseTexture.realHeight; + + destinationFrame = tempRect; + } + + if (!sourceFrame) + { + sourceFrame = destinationFrame; + } + + this.renderer.framebuffer.bind(baseTexture.frameBuffer, destinationFrame); + + this.renderer.projection.update(destinationFrame, sourceFrame, baseTexture.resolution, false); + this.renderer.stencil.setMaskStack(baseTexture.stencilMaskStack); + } + else + { + // TODO these validation checks happen deeper down.. + // thing they can be avoided.. + if (!destinationFrame) + { + tempRect.width = renderer.width; + tempRect.height = renderer.height; + + destinationFrame = tempRect; + } + + if (!sourceFrame) + { + sourceFrame = destinationFrame; + } + + renderer.framebuffer.bind(null, destinationFrame); + + // TODO store this.. + this.renderer.projection.update(destinationFrame, sourceFrame, this.renderer.resolution, true); + this.renderer.stencil.setMaskStack(this.defaultMaskStack); + } + + this.destinationFrame.copyFrom(destinationFrame); + } + + /** + * Erases the render texture and fills the drawing area with a colour + * + * @param {number} [clearColor] - The colour + * @return {PIXI.Renderer} Returns itself. + */ + clear(clearColor) + { + if (this.renderTexture) + { + clearColor = clearColor || this.renderTexture.baseTexture.clearColor; + } + else + { + clearColor = clearColor || this.clearColor; + } + + this.renderer.framebuffer.clear(clearColor[0], clearColor[1], clearColor[2], clearColor[3]); + } + + resize()// screenWidth, screenHeight) + { + // resize the root only! + this.bind(null); + } +} diff --git a/packages/core/src/renderers/AbstractRenderer.js b/packages/core/src/renderers/AbstractRenderer.js deleted file mode 100644 index 928670f..0000000 --- a/packages/core/src/renderers/AbstractRenderer.js +++ /dev/null @@ -1,353 +0,0 @@ -import { hex2string, hex2rgb } from '@pixi/utils'; -import { Matrix, Rectangle } from '@pixi/math'; -import { RENDERER_TYPE } from '@pixi/constants'; -import { settings } from '@pixi/settings'; -import { Container } from '@pixi/display'; -import RenderTexture from '../textures/RenderTexture'; -import EventEmitter from 'eventemitter3'; - -const tempMatrix = new Matrix(); - -/** - * 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 EventEmitter - * @memberof PIXI - */ -export default class AbstractRenderer extends EventEmitter -{ - // eslint-disable-next-line valid-jsdoc - /** - * @param {string} system - The name of the system this renderer is for. - * @param {object} [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.transparent=false] - If the render view is transparent, default false - * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false - * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) - * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. The - * resolution of the renderer retina would be 2. - * @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 {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when rendering, - * stopping pixel interpolation. - */ - constructor(system, options, arg2, arg3) - { - super(); - - // Support for constructor(system, screenWidth, screenHeight, options) - if (typeof options === 'number') - { - options = Object.assign({ - width: options, - height: arg2 || settings.RENDER_OPTIONS.height, - }, arg3); - } - - // 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 = RENDERER_TYPE.UNKNOWN; - - /** - * Measurements of the screen. (0, 0, screenWidth, screenHeight) - * - * Its safe to use as filterArea or hitArea for 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 1 - */ - this.resolution = options.resolution || settings.RESOLUTION; - - /** - * Whether the render view is transparent - * - * @member {boolean} - */ - this.transparent = options.transparent; - - /** - * Whether css dimensions of canvas view should be resized to screen dimensions automatically - * - * @member {boolean} - */ - this.autoResize = options.autoResize || false; - - /** - * Tracks the blend modes useful for this renderer. - * - * @member {object} - */ - this.blendModes = null; - - /** - * 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; - - /** - * If true PixiJS will Math.floor() x/y values when rendering, stopping pixel interpolation. - * Handy for crisp pixel art and speed on legacy devices. - * - * @member {boolean} - */ - this.roundPixels = options.roundPixels; - - /** - * The background color as a number. - * - * @member {number} - * @private - */ - this._backgroundColor = 0x000000; - - /** - * The background color as an [R, G, B] array. - * - * @member {number[]} - * @private - */ - this._backgroundColorRgba = [0, 0, 0, 0]; - - /** - * The background color as a string. - * - * @member {string} - * @private - */ - this._backgroundColorString = '#000000'; - - this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter - - /** - * This temporary display object used as the parent of the currently being rendered item - * - * @member {PIXI.DisplayObject} - * @private - */ - this._tempDisplayObjectParent = new Container(); - - /** - * The last root object that the renderer tried to render. - * - * @member {PIXI.DisplayObject} - * @private - */ - this._lastObjectRendered = this._tempDisplayObjectParent; - - /** - * Collection of plugins. - * @readonly - * @member {object} - */ - this.plugins = {}; - } - - /** - * Initialize the plugins. - * - * @protected - * @param {object} staticMap - The dictionary of staticly saved plugins. - */ - initPlugins(staticMap) - { - 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() - { - return this.view.width; - } - - /** - * Same as view.height, actual number of pixels in the canvas by vertical - * - * @member {number} - * @readonly - * @default 600 - */ - get height() - { - return this.view.height; - } - - /** - * Resizes the screen and canvas to the specified width and height - * Canvas dimensions are multiplied by resolution - * - * @param {number} screenWidth - the new width of the screen - * @param {number} screenHeight - the new height of the screen - */ - resize(screenWidth, screenHeight) - { - this.screen.width = screenWidth; - this.screen.height = screenHeight; - - this.view.width = screenWidth * this.resolution; - this.view.height = screenHeight * this.resolution; - - if (this.autoResize) - { - this.view.style.width = `${screenWidth}px`; - this.view.style.height = `${screenHeight}px`; - } - } - - /** - * 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 {PIXI.DisplayObject} displayObject - The displayObject the object will be generated from - * @param {number} scaleMode - Should be one of the scaleMode consts - * @param {number} resolution - The resolution / device pixel ratio of the texture being generated - * @param {PIXI.Rectangle} [region] - The region of the displayObject, that shall be rendered, - * if no region is specified, defaults to the local bounds of the displayObject. - * @return {PIXI.Texture} a texture of the graphics object - */ - generateTexture(displayObject, scaleMode, resolution, region) - { - region = region || displayObject.getLocalBounds(); - - // 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(region.width | 0, region.height | 0, scaleMode, resolution); - - tempMatrix.tx = -region.x; - tempMatrix.ty = -region.y; - - this.render(displayObject, renderTexture, false, tempMatrix, true); - - return renderTexture; - } - - /** - * Removes everything from the renderer and optionally removes the Canvas DOM element. - * - * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. - */ - destroy(removeView) - { - 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); - } - - this.plugins = null; - - this.type = RENDERER_TYPE.UNKNOWN; - - this.view = null; - - this.screen = null; - - this.resolution = 0; - - this.transparent = false; - - this.autoResize = false; - - this.blendModes = null; - - this.options = null; - - this.preserveDrawingBuffer = false; - this.clearBeforeRender = false; - - this.roundPixels = false; - - this._backgroundColor = 0; - this._backgroundColorRgba = null; - this._backgroundColorString = null; - - this._tempDisplayObjectParent = null; - this._lastObjectRendered = null; - } - - /** - * The background color to fill if not transparent - * - * @member {number} - */ - get backgroundColor() - { - return this._backgroundColor; - } - - set backgroundColor(value) // eslint-disable-line require-jsdoc - { - this._backgroundColor = value; - this._backgroundColorString = hex2string(value); - hex2rgb(value, this._backgroundColorRgba); - } -} diff --git a/packages/core/src/renderers/Renderer.js b/packages/core/src/renderers/Renderer.js deleted file mode 100644 index a2020b1..0000000 --- a/packages/core/src/renderers/Renderer.js +++ /dev/null @@ -1,329 +0,0 @@ -import AbstractRenderer from './AbstractRenderer'; -import { sayHello } from '@pixi/utils'; -import MaskSystem from './systems/MaskSystem'; -import StencilSystem from './systems/StencilSystem'; -import FilterSystem from './systems/filter/FilterSystem'; -import FramebufferSystem from './systems/FramebufferSystem'; -import RenderTextureSystem from './systems/RenderTextureSystem'; -import TextureSystem from './systems/textures/TextureSystem'; -import ProjectionSystem from './systems/ProjectionSystem'; -import StateSystem from './systems/StateSystem'; -import GeometrySystem from './systems/geometry/GeometrySystem'; -import ShaderSystem from './systems/shader/ShaderSystem'; -import ContextSystem from './systems/ContextSystem'; -import BatchSystem from './systems/BatchSystem'; -import TextureGCSystem from './systems/textures/TextureGCSystem'; -import { RENDERER_TYPE } from '@pixi/constants'; -import UniformGroup from '../shader/UniformGroup'; -import { Matrix } from '@pixi/math'; -import Runner from 'mini-runner'; - -/** - * 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 Render works by automatically managing webGLBatchs. - * 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 :) - * - * @class - * @memberof PIXI - * @extends PIXI.AbstractRenderer - */ -export default class Renderer extends AbstractRenderer -{ - // eslint-disable-next-line valid-jsdoc - /** - * - * @param {object} [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.transparent=false] - If the render view is transparent, default false - * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false - * @param {boolean} [options.antialias=false] - sets antialias. If not available natively then FXAA - * antialiasing is used - * @param {boolean} [options.forceFXAA=false] - forces FXAA antialiasing to be used over native. - * FXAA is faster, but may not always look as great - * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. - * The resolution of the renderer retina would be 2. - * @param {boolean} [options.clearBeforeRender=true] - This sets if the renderer will clear - * the canvas or not before the new render pass. If you wish to set this to false, you *must* set - * preserveDrawingBuffer to `true`. - * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, - * enable this if you need to call toDataUrl on the webgl context. - * @param {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when - * rendering, stopping pixel interpolation. - * @param {number} [options.backgroundColor=0x000000] - The background color of the rendered area - * (shown if not transparent). - * @param {boolean} [options.legacy=false] - If true PixiJS will aim to ensure compatibility - * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. - * @param {string} [options.powerPreference] - Parameter passed to webgl context, set to "high-performance" - * for devices with dual graphics card - */ - constructor(options, arg2, arg3) - { - super('WebGL', options, arg2, arg3); - - /** - * The type of this renderer as a standardised const - * - * @member {number} - * @see PIXI.RENDERER_TYPE - */ - this.type = RENDERER_TYPE.WEBGL; - - // this will be set by the contextSystem (this.context) - this.gl = null; - this.CONTEXT_UID = 0; - this.legacy = !!options.legacy; - - // TODO legacy! - - // runners! - this.runners = { - destroy: new Runner('destroy'), - contextChange: new Runner('contextChange', 1), - reset: new Runner('reset'), - update: new Runner('update'), - postrender: new Runner('postrender'), - prerender: new Runner('prerender'), - resize: new Runner('resize', 2), - }; - - 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(GeometrySystem, 'geometry') - .addSystem(FramebufferSystem, 'framebuffer') - .addSystem(StencilSystem, 'stencil') - .addSystem(ProjectionSystem, 'projection') - .addSystem(TextureGCSystem, 'textureGC') - .addSystem(FilterSystem, 'filter') - .addSystem(RenderTextureSystem, 'renderTexture') - .addSystem(BatchSystem, 'batch'); - - this.initPlugins(Renderer.__plugins); - - /** - * The options passed in to create a new webgl context. - * - * @member {object} - * @private - */ - if (options.context) - { - this.context.initFromContext(options.context); - } - else - { - this.context.initFromOptions({ - alpha: this.transparent, - antialias: options.antialias, - premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', - stencil: true, - 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); - } - - addSystem(_class, name) - { - if (!name) - { - name = _class.name; - } - - // TODO - read name from class.name.. - - /* - if(name.includes('System')) - { - name = name.replace('System', ''); - name = name.charAt(0).toLowerCase() + name.slice(1); - } - */ - - const system = new _class(this); - - if (this[name]) - { - throw new Error(`Whoops! ${name} is already a manger`); - } - - this[name] = system; - - for (const i in this.runners) - { - this.runners[i].add(system); - } - - return this; - - /** - * 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. - */ - } - - /** - * Renders the object to its webGL view - * - * @param {PIXI.DisplayObject} displayObject - the object to be rendered - * @param {PIXI.RenderTexture} renderTexture - The render texture to render to. - * @param {boolean} [clear] - Should the canvas be cleared before the new render - * @param {PIXI.Transform} [transform] - A transform to apply to the render texture before rendering. - * @param {boolean} [skipUpdateTransform] - Should we skip the update transform pass? - */ - render(displayObject, renderTexture, clear, transform, skipUpdateTransform) - { - // can be handy to know! - this.renderingToScreen = !renderTexture; - - this.runners.prerender.run(); - this.emit('prerender'); - - // 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.parent; - - displayObject.parent = this._tempDisplayObjectParent; - displayObject.updateTransform(); - displayObject.parent = 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.run(); - - this.emit('postrender'); - } - - /** - * Resizes the webGL view to the specified width and height. - * - * @param {number} screenWidth - the new width of the screen - * @param {number} screenHeight - the new height of the screen - */ - resize(screenWidth, screenHeight) - { - AbstractRenderer.prototype.resize.call(this, screenWidth, screenHeight); - - this.runners.resize.run(screenWidth, screenHeight); - } - - /** - * Resets the WebGL state so you can render things however you fancy! - * - * @return {PIXI.Renderer} Returns itself. - */ - reset() - { - this.runners.reset.run(); - - return this; - } - - clear() - { - this.framebuffer.bind(); - this.framebuffer.clear(); - } - - /** - * Removes everything from the renderer (event listeners, spritebatch, etc...) - * - * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. - * See: https://github.com/pixijs/pixi.js/issues/2233 - */ - destroy(removeView) - { - this.runners.destroy.run(); - - // call base destroy - super.destroy(removeView); - - // TODO nullify all the managers.. - this.gl = null; - } - - /** - * 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 - * builds and excluding plugins. - * @name PIXI.Renderer#plugins - * @type {object} - * @readonly - * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. - * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. - * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. - * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. - */ - - /** - * Adds a plugin to the renderer. - * - * @method - * @param {string} pluginName - The name of the plugin. - * @param {Function} ctor - The constructor function or class for the plugin. - */ - static registerPlugin(pluginName, ctor) - { - Renderer.__plugins = Renderer.__plugins || {}; - Renderer.__plugins[pluginName] = ctor; - } -} diff --git a/packages/core/src/renderers/State.js b/packages/core/src/renderers/State.js deleted file mode 100644 index fa3f221..0000000 --- a/packages/core/src/renderers/State.js +++ /dev/null @@ -1,163 +0,0 @@ -/* eslint-disable max-len */ - -const BLEND = 0; -const OFFSET = 1; -const CULLING = 2; -const DEPTH_TEST = 3; -const WINDING = 4; - -/** - * This is a webGL state. It is passed The WebGL StateManager. - * Each mesh renderered may require webGL to be in a different state. - * For example you may want different blend mode or to enable polygon offsets - * - * @class - * @memberof PIXI - */ -export default class State -{ - /** - * - */ - constructor() - { - this.data = 0; - - this.blendMode = 0; - this.polygonOffset = 0; - - this.blend = true; - // this.depthTest = true; - } - - /** - * Activates blending of the computed fragment color values - * - * @member {boolean} - */ - get blend() - { - return !!(this.data & (1 << BLEND)); - } - - set blend(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << BLEND)) !== value) - { - this.data ^= (1 << BLEND); - } - } - - /** - * Activates adding an offset to depth values of polygon's fragments - * - * @member {boolean} - * @default false - */ - get offsets() - { - return !!(this.data & (1 << OFFSET)); - } - - set offsets(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << OFFSET)) !== value) - { - this.data ^= (1 << OFFSET); - } - } - - /** - * Activates culling of polygons. - * - * @member {boolean} - * @default false - */ - get culling() - { - return !!(this.data & (1 << CULLING)); - } - - set culling(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << CULLING)) !== value) - { - this.data ^= (1 << CULLING); - } - } - - /** - * Activates depth comparisons and updates to the depth buffer. - * - * @member {boolean} - * @default false - */ - get depthTest() - { - return !!(this.data & (1 << DEPTH_TEST)); - } - - set depthTest(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << DEPTH_TEST)) !== value) - { - this.data ^= (1 << DEPTH_TEST); - } - } - - /** - * Specifies whether or not front or back-facing polygons can be culled. - * @member {boolean} - * @default false - */ - get clockwiseFrontFace() - { - return !!(this.data & (1 << WINDING)); - } - - set clockwiseFrontFace(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << WINDING)) !== value) - { - this.data ^= (1 << WINDING); - } - } - - /** - * The blend mode to be applied when this state is set. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. - * Setting this mode to anything other than NO_BLEND will automatically switch blending on. - * - * @member {boolean} - * @default PIXI.BLEND_MODES.NORMAL - * @see PIXI.BLEND_MODES - */ - get blendMode() - { - return this._blendMode; - } - - set blendMode(value) // eslint-disable-line require-jsdoc - { - // 17 is NO BLEND - this.blend = (value !== 17); - this._blendMode = value; - } - - /** - * The polygon offset. Setting this property to anything other than 0 will automatically enable poygon offset fill. - * - * @member {number} - * @default 0 - */ - get polygonOffset() - { - return this._polygonOffset; - } - - set polygonOffset(value) // eslint-disable-line require-jsdoc - { - this.offsets = !!value; - this._polygonOffset = value; - } -} - diff --git a/packages/core/src/renderers/filters/Filter.js b/packages/core/src/renderers/filters/Filter.js deleted file mode 100644 index 2073b8d..0000000 --- a/packages/core/src/renderers/filters/Filter.js +++ /dev/null @@ -1,140 +0,0 @@ -import Shader from '../../shader/Shader'; -import Program from '../../shader/Program'; -import State from '../State'; -import { settings } from '@pixi/settings'; -// import extractUniformsFromSrc from './extractUniformsFromSrc'; -import defaultVertex from './defaultFilter.vert'; -import defaultFragment from './defaultFilter.frag'; - -// let math = require('../../math'); -/** - * @class - * @memberof PIXI - * @extends PIXI.Shader - */ -export default class Filter extends Shader -{ - /** - * @param {string} [vertexSrc] - The source of the vertex shader. - * @param {string} [fragmentSrc] - The source of the fragment shader. - * @param {object} [uniforms] - Custom uniforms to use to augment the built-in ones. - */ - constructor(vertexSrc, fragmentSrc, uniforms) - { - const program = Program.from(vertexSrc, fragmentSrc); - - super(program, uniforms); - - /** - * The padding of the filter. Some filters require extra space to breath such as a blur. - * Increasing this will add extra width and height to the bounds of the object that the - * filter is applied to. - * - * @member {number} - */ - this.padding = 0; - - /** - * The resolution of the filter. Setting this to be lower will lower the quality but - * increase the performance of the filter. - * - * @member {number} - */ - this.resolution = settings.RESOLUTION; - - /** - * If enabled is true the filter is applied, if false it will not. - * - * @member {boolean} - */ - this.enabled = true; - - /** - * If enabled, PixiJS will fit the filter area into boundaries for better performance. - * Switch it off if it does not work for specific shader. - * - * @member {boolean} - */ - this.autoFit = true; - - /** - * Legacy filters use position and uvs from attributes - * @member {boolean} - * @readonly - */ - this.legacy = !!this.program.attributeData.aTextureCoord; - - /** - * the webGL state the filter requires to render - * @member {PIXI.State} - */ - this.state = new State(); - } - - /** - * Applies the filter - * - * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from - * @param {PIXI.RenderTarget} input - The input render target. - * @param {PIXI.RenderTarget} output - The target to output to. - * @param {boolean} clear - Should the output be cleared before rendering to it - * @param {object} [currentState] - It's current state of filter. - * There are some useful properties in the currentState : - * target, filters, sourceFrame, destinationFrame, renderTarget, resolution - */ - apply(filterManager, input, output, clear, currentState, derp) // eslint-disable-line no-unused-vars - { - // do as you please! - - filterManager.applyFilter(this, input, output, clear, currentState, derp); - - // or just do a regular render.. - } - - /** - * Sets the blendmode of the filter - * - * @member {number} - * @default PIXI.BLEND_MODES.NORMAL - */ - get blendMode() - { - return this.state.blendMode; - } - - set blendMode(value) // eslint-disable-line require-jsdoc - { - this.state.blendMode = value; - } - - /** - * The default vertex shader source - * - * @static - * @constant - */ - static get defaultVertexSrc() - { - return defaultVertex; - } - - /** - * The default fragment shader source - * - * @static - * @constant - */ - static get defaultFragmentSrc() - { - return defaultFragment; - } -} - -/** - * Used for caching shader IDs - * - * @static - * @private - */ -Filter.SOURCE_KEY_MAP = {}; - diff --git a/packages/core/src/renderers/filters/defaultFilter.frag b/packages/core/src/renderers/filters/defaultFilter.frag deleted file mode 100644 index a14c1be..0000000 --- a/packages/core/src/renderers/filters/defaultFilter.frag +++ /dev/null @@ -1,22 +0,0 @@ -varying vec2 vTextureCoord; -varying vec2 vFilterCoord; - -uniform sampler2D uSampler; -uniform sampler2D filterSampler; - -void main(void){ - vec4 masky = texture2D(filterSampler, vFilterCoord); - vec4 sample = texture2D(uSampler, vTextureCoord); - vec4 color; - if(mod(vFilterCoord.x, 1.0) > 0.5) - { - color = vec4(1.0, 0.0, 0.0, 1.0); - } - else - { - color = vec4(0.0, 1.0, 0.0, 1.0); - } - // gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0); - gl_FragColor = mix(sample, masky, 0.5); - gl_FragColor *= sample.a; -} diff --git a/packages/core/src/renderers/filters/defaultFilter.vert b/packages/core/src/renderers/filters/defaultFilter.vert deleted file mode 100644 index e1febca..0000000 --- a/packages/core/src/renderers/filters/defaultFilter.vert +++ /dev/null @@ -1,14 +0,0 @@ -attribute vec2 aVertexPosition; -attribute vec2 aTextureCoord; - -uniform mat3 projectionMatrix; -uniform mat3 filterMatrix; - -varying vec2 vTextureCoord; -varying vec2 vFilterCoord; - -void main(void){ - gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); - vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy; - vTextureCoord = aTextureCoord ; -} diff --git a/packages/core/src/renderers/filters/filterTransforms.js b/packages/core/src/renderers/filters/filterTransforms.js deleted file mode 100644 index 87a3cd6..0000000 --- a/packages/core/src/renderers/filters/filterTransforms.js +++ /dev/null @@ -1,50 +0,0 @@ -import { Matrix } from '@pixi/math'; - -/** - * Calculates the mapped matrix - * @param filterArea {Rectangle} The filter area - * @param sprite {Sprite} the target sprite - * @param outputMatrix {Matrix} @alvin - * @private - */ -// this returns a matrix that will normalise map filter cords in the filter to screen space -export function calculateScreenSpaceMatrix(outputMatrix, filterArea, textureSize) -{ - // TODO unwrap? - const mappedMatrix = outputMatrix.identity(); - - mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); - - mappedMatrix.scale(textureSize.width, textureSize.height); - - return mappedMatrix; -} - -export function calculateNormalizedScreenSpaceMatrix(outputMatrix, filterArea, textureSize) -{ - const mappedMatrix = outputMatrix.identity(); - - mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); - - const translateScaleX = (textureSize.width / filterArea.width); - const translateScaleY = (textureSize.height / filterArea.height); - - mappedMatrix.scale(translateScaleX, translateScaleY); - - return mappedMatrix; -} - -// this will map the filter coord so that a texture can be used based on the transform of a sprite -export function calculateSpriteMatrix(outputMatrix, filterArea, textureSize, sprite) -{ - const orig = sprite._texture.orig; - const mappedMatrix = outputMatrix.set(textureSize.width, 0, 0, textureSize.height, filterArea.x, filterArea.y); - const worldTransform = sprite.worldTransform.copyTo(Matrix.TEMP_MATRIX); - - worldTransform.invert(); - mappedMatrix.prepend(worldTransform); - mappedMatrix.scale(1.0 / orig.width, 1.0 / orig.height); - mappedMatrix.translate(sprite.anchor.x, sprite.anchor.y); - - return mappedMatrix; -} diff --git a/packages/core/src/renderers/filters/spriteMask/SpriteMaskFilter.js b/packages/core/src/renderers/filters/spriteMask/SpriteMaskFilter.js deleted file mode 100644 index d5a91a4..0000000 --- a/packages/core/src/renderers/filters/spriteMask/SpriteMaskFilter.js +++ /dev/null @@ -1,63 +0,0 @@ -import Filter from '../Filter'; -import { Matrix } from '@pixi/math'; -import vertex from './spriteMaskFilter.vert'; -import fragment from './spriteMaskFilter.frag'; -import { default as TextureMatrix } from '../../../textures/TextureMatrix'; - -/** - * The SpriteMaskFilter class - * - * @class - * @extends PIXI.Filter - * @memberof PIXI - */ -export default class SpriteMaskFilter extends Filter -{ - /** - * @param {PIXI.Sprite} sprite - the target sprite - */ - constructor(sprite) - { - const maskMatrix = new Matrix(); - - super(vertex, fragment); - - sprite.renderable = false; - - this.maskSprite = sprite; - this.maskMatrix = maskMatrix; - } - - /** - * Applies the filter - * - * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from - * @param {PIXI.RenderTarget} input - The input render target. - * @param {PIXI.RenderTarget} output - The target to output to. - */ - apply(filterManager, input, output) - { - const maskSprite = this.maskSprite; - const tex = this.maskSprite.texture; - - if (!tex.valid) - { - return; - } - if (!tex.transform) - { - // margin = 0.0, let it bleed a bit, shader code becomes easier - // assuming that atlas textures were made with 1-pixel padding - tex.transform = new TextureMatrix(tex, 0.0); - } - tex.transform.update(); - - this.uniforms.mask = tex; - this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite) - .prepend(tex.transform.mapCoord); - this.uniforms.alpha = maskSprite.worldAlpha; - this.uniforms.maskClamp = tex.transform.uClampFrame; - - filterManager.applyFilter(this, input, output); - } -} diff --git a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.frag b/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.frag deleted file mode 100644 index 0e4aef8..0000000 --- a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.frag +++ /dev/null @@ -1,23 +0,0 @@ -varying vec2 vMaskCoord; -varying vec2 vTextureCoord; - -uniform sampler2D uSampler; -uniform sampler2D mask; -uniform float alpha; -uniform vec4 maskClamp; - -void main(void) -{ - float clip = step(3.5, - step(maskClamp.x, vMaskCoord.x) + - step(maskClamp.y, vMaskCoord.y) + - step(vMaskCoord.x, maskClamp.z) + - step(vMaskCoord.y, maskClamp.w)); - - vec4 original = texture2D(uSampler, vTextureCoord); - vec4 masky = texture2D(mask, vMaskCoord); - - original *= (masky.r * masky.a * alpha * clip); - - gl_FragColor = original; -} diff --git a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.vert b/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.vert deleted file mode 100644 index d23a30c..0000000 --- a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.vert +++ /dev/null @@ -1,16 +0,0 @@ -attribute vec2 aVertexPosition; -attribute vec2 aTextureCoord; - -uniform mat3 projectionMatrix; -uniform mat3 otherMatrix; - -varying vec2 vMaskCoord; -varying vec2 vTextureCoord; - -void main(void) -{ - gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); - - vTextureCoord = aTextureCoord; - vMaskCoord = ( otherMatrix * vec3( aTextureCoord, 1.0) ).xy; -} diff --git a/packages/core/src/renderers/systems/BatchSystem.js b/packages/core/src/renderers/systems/BatchSystem.js deleted file mode 100644 index 70e9518..0000000 --- a/packages/core/src/renderers/systems/BatchSystem.js +++ /dev/null @@ -1,68 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import ObjectRenderer from '../utils/ObjectRenderer'; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ - -export default class BatchSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this manager works for. - */ - constructor(renderer) - { - super(renderer); - - /** - * An empty renderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.emptyRenderer = new ObjectRenderer(renderer); - - /** - * The currently active ObjectRenderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.currentRenderer = this.emptyRenderer; - } - - /** - * Changes the current renderer to the one given in parameter - * - * @param {PIXI.ObjectRenderer} objectRenderer - The object renderer to use. - */ - setObjectRenderer(objectRenderer) - { - if (this.currentRenderer === objectRenderer) - { - return; - } - - this.currentRenderer.stop(); - this.currentRenderer = objectRenderer; - - this.renderer.state.setState(objectRenderer.state); - - this.currentRenderer.start(); - } - - /** - * This should be called if you wish to do some custom rendering - * It will basically render anything that may be batched up such as sprites - * - */ - flush() - { - this.setObjectRenderer(this.emptyRenderer); - } - - reset() - { - this.setObjectRenderer(this.emptyRenderer); - } -} diff --git a/packages/core/src/renderers/systems/ContextSystem.js b/packages/core/src/renderers/systems/ContextSystem.js deleted file mode 100644 index 43bf827..0000000 --- a/packages/core/src/renderers/systems/ContextSystem.js +++ /dev/null @@ -1,186 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { settings } from '@pixi/settings'; - -let CONTEXT_UID = 0; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class ContextSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this.webGLVersion = 1; - - this.handleContextLost = this.handleContextLost.bind(this); - this.handleContextRestored = this.handleContextRestored.bind(this); - - this.extensions = {}; - - renderer.view.addEventListener('webglcontextlost', this.handleContextLost, false); - renderer.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); - } - - get isLost() - { - return (!this.gl || this.gl.isContextLost()); - } - - contextChange(gl) - { - this.gl = gl; - - // restore a context if it was previously lost - if (gl.isContextLost() && gl.getExtension('WEBGL_lose_context')) - { - gl.getExtension('WEBGL_lose_context').restoreContext(); - } - } - - initFromContext(gl) - { - this.gl = gl; - this.validateContext(gl); - this.renderer.gl = gl; - this.renderer.CONTEXT_UID = CONTEXT_UID++; - this.renderer.runners.contextChange.run(gl); - } - - initFromOptions(options) - { - const gl = this.createContext(this.renderer.view, options); - - this.initFromContext(gl); - } - - /** - * Helper class to create a webGL Context - * - * @class - * @memberof PIXI.glCore - * @param canvas {HTMLCanvasElement} the canvas element that we will get the context from - * @param options {Object} An options object that gets passed in to the canvas element containing the context attributes, - * see https://developer.mozilla.org/en/docs/Web/API/HTMLCanvasElement/getContext for the options available - * @return {WebGLRenderingContext} the WebGL context - */ - createContext(canvas, options) - { - let gl; - - if (settings.PREFER_WEBGL_2) - { - gl = canvas.getContext('webgl2', options); - } - - if (gl) - { - this.webGLVersion = 2; - } - else - { - this.webGLVersion = 1; - - gl = canvas.getContext('webgl', options) - || canvas.getContext('experimental-webgl', options); - - if (!gl) - { - // fail, not able to get a context - throw new Error('This browser does not support webGL. Try using the canvas renderer'); - } - } - - this.gl = gl; - - this.getExtensions(); - - return gl; - } - - getExtensions() - { - // time to set up default etensions that pixi uses.. - const gl = this.gl; - const extensions = this.extensions; - - if (this.webGLVersion === 1) - { - extensions.drawBuffers = gl.getExtension('WEBGL_draw_buffers'); - extensions.depthTexture = gl.getExtension('WEBKIT_WEBGL_depth_texture'); - extensions.floatTexture = gl.getExtension('OES_texture_float'); - extensions.loseContext = gl.getExtension('WEBGL_lose_context'); - - extensions.vertexArrayObject = gl.getExtension('OES_vertex_array_object') - || gl.getExtension('MOZ_OES_vertex_array_object') - || gl.getExtension('WEBKIT_OES_vertex_array_object'); - } - - // we don't use any specific WebGL 2 ones yet! - } - - /** - * Handles a lost webgl context - * - * @private - * @param {WebGLContextEvent} event - The context lost event. - */ - handleContextLost(event) - { - event.preventDefault(); - } - - /** - * Handles a restored webgl context - * - * @private - */ - handleContextRestored() - { - this.renderer.runners.contextChange.run(this.gl); - } - - destroy() - { - const view = this.renderer.view; - - // remove listeners - view.removeEventListener('webglcontextlost', this.handleContextLost); - view.removeEventListener('webglcontextrestored', this.handleContextRestored); - - this.gl.useProgram(null); - - if (this.extensions.loseContext) - { - this.extensions.loseContext.loseContext(); - } - } - - postrender() - { - this.gl.flush(); - } - - validateContext(gl) - { - const attributes = gl.getContextAttributes(); - - // this is going to be fairly simple for now.. but at least we have room to grow! - if (!attributes.stencil) - { - /* eslint-disable max-len */ - - /* eslint-disable no-console */ - console.warn('Provided WebGL context does not have a stencil buffer, masks may not render correctly'); - /* eslint-enable no-console */ - - /* eslint-enable max-len */ - } - } -} diff --git a/packages/core/src/renderers/systems/FramebufferSystem.js b/packages/core/src/renderers/systems/FramebufferSystem.js deleted file mode 100644 index df7902b..0000000 --- a/packages/core/src/renderers/systems/FramebufferSystem.js +++ /dev/null @@ -1,246 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { Rectangle } from '@pixi/math'; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class FramebufferSystem extends WebGLSystem -{ - /** - * Sets up the renderer context and necessary buffers. - * - * @private - */ - contextChange() - { - this.gl = this.renderer.gl; - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - this.current = null; - this.viewport = new Rectangle(); - - this.drawBufferExtension = this.renderer.context.extensions.drawBuffers; - } - - bind(framebuffer, frame) - { - const gl = this.gl; - - this.current = framebuffer; - - if (framebuffer) - { - // TODO cacheing layer! - - const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID] || this.initFramebuffer(framebuffer); - - gl.bindFramebuffer(gl.FRAMEBUFFER, fbo.framebuffer); - // makesure all textures are unbound.. - - // now check for updates... - if (fbo.dirtyId !== framebuffer.dirtyId) - { - fbo.dirtyId = framebuffer.dirtyId; - - if (fbo.dirtyFormat !== framebuffer.dirtyFormat) - { - fbo.dirtyFormat = framebuffer.dirtyFormat; - this.updateFramebuffer(framebuffer); - } - else if (fbo.dirtySize !== framebuffer.dirtySize) - { - fbo.dirtySize = framebuffer.dirtySize; - this.resizeFramebuffer(framebuffer); - } - } - - for (let i = 0; i < framebuffer.colorTextures.length; i++) - { - if (framebuffer.colorTextures[i].texturePart) - { - this.renderer.texture.unbind(framebuffer.colorTextures[i].texture); - } - else - { - this.renderer.texture.unbind(framebuffer.colorTextures[i]); - } - } - - if (framebuffer.depthTexture) - { - this.renderer.texture.unbind(framebuffer.depthTexture); - } - - if (frame) - { - this.setViewport(frame.x, frame.y, frame.width, frame.height); - } - else - { - this.setViewport(0, 0, framebuffer.width, framebuffer.height); - } - } - else - { - gl.bindFramebuffer(gl.FRAMEBUFFER, null); - - if (frame) - { - this.setViewport(frame.x, frame.y, frame.width, frame.height); - } - else - { - this.setViewport(0, 0, this.renderer.width, this.renderer.height); - } - } - } - - setViewport(x, y, width, height) - { - const v = this.viewport; - - if (v.width !== width || v.height !== height || v.x !== x || v.y !== y) - { - v.x = x; - v.y = y; - v.width = width; - v.height = height; - - this.gl.viewport(x, y, width, height); - } - } - - get size() - { - if (this.current) - { - // TODO store temp - return { x: 0, y: 0, width: this.current.width, height: this.current.height }; - } - - return { x: 0, y: 0, width: this.renderer.width, height: this.renderer.height }; - } - - clear(r, g, b, a) - { - const gl = this.gl; - - // TODO clear color can be set only one right? - gl.clearColor(r, g, b, a); - gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); - } - - // private functions... - - initFramebuffer(framebuffer) - { - const gl = this.gl; - - // TODO - make this a class? - const fbo = { - framebuffer: gl.createFramebuffer(), - stencil: null, - dirtyId: 0, - dirtyFormat: 0, - dirtySize: 0, - }; - - framebuffer.glFrameBuffers[this.CONTEXT_UID] = fbo; - - return fbo; - } - - resizeFramebuffer(framebuffer) - { - const gl = this.gl; - - if (framebuffer.stencil || framebuffer.depth) - { - gl.bindRenderbuffer(gl.RENDERBUFFER, this.stencil); - gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); - } - } - - updateFramebuffer(framebuffer) - { - const gl = this.gl; - - const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID]; - - // bind the color texture - const colorTextures = framebuffer.colorTextures; - - let count = colorTextures.length; - - if (!this.drawBufferExtension) - { - count = Math.min(count, 1); - } - - const activeTextures = []; - - for (let i = 0; i < count; i++) - { - const texture = framebuffer.colorTextures[i]; - - if (texture.texturePart) - { - this.renderer.texture.bind(texture.texture, 0); - - gl.framebufferTexture2D(gl.FRAMEBUFFER, - gl.COLOR_ATTACHMENT0 + i, - gl.TEXTURE_CUBE_MAP_NEGATIVE_X + texture.side, - texture.texture._glTextures[this.CONTEXT_UID].texture, - 0); - } - else - { - this.renderer.texture.bind(texture, 0); - - gl.framebufferTexture2D(gl.FRAMEBUFFER, - gl.COLOR_ATTACHMENT0 + i, - gl.TEXTURE_2D, - texture._glTextures[this.CONTEXT_UID].texture, - 0); - } - - activeTextures.push(gl.COLOR_ATTACHMENT0 + i); - } - - if (this.drawBufferExtension && activeTextures.length > 1) - { - this.drawBufferExtension.drawBuffersWEBGL(activeTextures); - } - - if (framebuffer.depthTexture) - { - const depthTextureExt = this.renderer.context.extensions.depthTexture; - - if (depthTextureExt) - { - const depthTexture = framebuffer.depthTexture; - - this.renderer.texture.bind(depthTexture, 0); - - gl.framebufferTexture2D(gl.FRAMEBUFFER, - gl.DEPTH_ATTACHMENT, - gl.TEXTURE_2D, - depthTexture._glTextures[this.CONTEXT_UID].texture, - 0); - } - } - - if (framebuffer.stencil || framebuffer.depth) - { - fbo.stencil = gl.createRenderbuffer(); - - gl.bindRenderbuffer(gl.RENDERBUFFER, fbo.stencil); - - // TODO.. this is depth AND stencil? - gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, fbo.stencil); - gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); - // fbo.enableStencil(); - } - } -} diff --git a/packages/core/src/renderers/systems/MaskSystem.js b/packages/core/src/renderers/systems/MaskSystem.js deleted file mode 100644 index d9c4b1a..0000000 --- a/packages/core/src/renderers/systems/MaskSystem.js +++ /dev/null @@ -1,199 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import AlphaMaskFilter from '../filters/spriteMask/SpriteMaskFilter'; - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class MaskSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - // TODO - we don't need both! - this.scissor = false; - this.scissorData = null; - this.scissorRenderTarget = null; - - this.enableScissor = false; - - this.alphaMaskPool = []; - this.alphaMaskIndex = 0; - } - - /** - * Applies the Mask and adds it to the current filter stack. - * - * @param {PIXI.DisplayObject} target - Display Object to push the mask to - * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. - */ - push(target, maskData) - { - // TODO the root check means scissor rect will not - // be used on render textures more info here: - // https://github.com/pixijs/pixi.js/pull/3545 - - if (maskData.texture) - { - this.pushSpriteMask(target, maskData); - } - else if (this.enableScissor - && !this.scissor - && this.renderer._activeRenderTarget.root - && !this.renderer.stencil.stencilMaskStack.length - && maskData.isFastRect()) - { - const matrix = maskData.worldTransform; - - let rot = Math.atan2(matrix.b, matrix.a); - - // use the nearest degree! - rot = Math.round(rot * (180 / Math.PI)); - - if (rot % 90) - { - this.pushStencilMask(maskData); - } - else - { - this.pushScissorMask(target, maskData); - } - } - else - { - this.pushStencilMask(maskData); - } - } - - /** - * Removes the last mask from the mask stack and doesn't return it. - * - * @param {PIXI.DisplayObject} target - Display Object to pop the mask from - * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. - */ - pop(target, maskData) - { - if (maskData.texture) - { - this.popSpriteMask(target, maskData); - } - else if (this.enableScissor && !this.renderer.stencil.stencilMaskStack.length) - { - this.popScissorMask(target, maskData); - } - else - { - this.popStencilMask(target, maskData); - } - } - - /** - * Applies the Mask and adds it to the current filter stack. - * - * @param {PIXI.RenderTarget} target - Display Object to push the sprite mask to - * @param {PIXI.Sprite} maskData - Sprite to be used as the mask - */ - pushSpriteMask(target, maskData) - { - let alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; - - if (!alphaMaskFilter) - { - alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; - } - - alphaMaskFilter[0].resolution = this.renderer.resolution; - alphaMaskFilter[0].maskSprite = maskData; - - // TODO - may cause issues! - target.filterArea = maskData.getBounds(true); - - this.renderer.filterSystem.pushFilter(target, alphaMaskFilter); - - this.alphaMaskIndex++; - } - - /** - * Removes the last filter from the filter stack and doesn't return it. - * - */ - popSpriteMask() - { - this.renderer.filterSystem.popFilter(); - this.alphaMaskIndex--; - } - - /** - * Applies the Mask and adds it to the current filter stack. - * - * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. - */ - pushStencilMask(maskData) - { - this.renderer.batch.flush(); - this.renderer.stencil.pushStencil(maskData); - } - - /** - * Removes the last filter from the filter stack and doesn't return it. - * - */ - popStencilMask() - { - // this.renderer.currentRenderer.stop(); - this.renderer.stencil.popStencil(); - } - - /** - * - * @param {PIXI.DisplayObject} target - Display Object to push the mask to - * @param {PIXI.Graphics} maskData - The masking data. - */ - pushScissorMask(target, maskData) - { - maskData.renderable = true; - - const renderTarget = this.renderer._activeRenderTarget; - - const bounds = maskData.getBounds(); - - bounds.fit(renderTarget.size); - maskData.renderable = false; - - this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); - - const resolution = this.renderer.resolution; - - this.renderer.gl.scissor( - bounds.x * resolution, - (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, - bounds.width * resolution, - bounds.height * resolution - ); - - this.scissorRenderTarget = renderTarget; - this.scissorData = maskData; - this.scissor = true; - } - - /** - * - * - */ - popScissorMask() - { - this.scissorRenderTarget = null; - this.scissorData = null; - this.scissor = false; - - // must be scissor! - const gl = this.renderer.gl; - - gl.disable(gl.SCISSOR_TEST); - } -} diff --git a/packages/core/src/renderers/systems/ProjectionSystem.js b/packages/core/src/renderers/systems/ProjectionSystem.js deleted file mode 100644 index ec5efdb..0000000 --- a/packages/core/src/renderers/systems/ProjectionSystem.js +++ /dev/null @@ -1,73 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { Matrix } from '@pixi/math'; - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ - -export default class ProjectionSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this.projectionMatrix = new Matrix(); - } - - update(destinationFrame, sourceFrame, resolution, root) - { - this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; - this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; - - this.calculateProjection(this.destinationFrame, this.sourceFrame, resolution, root); - - this.renderer.globalUniforms.uniforms.projectionMatrix = this.projectionMatrix; - this.renderer.globalUniforms.update(); - } - - /** - * Updates the projection matrix based on a projection frame (which is a rectangle) - * - * @param {Rectangle} destinationFrame - The destination frame. - * @param {Rectangle} sourceFrame - The source frame. - */ - calculateProjection(destinationFrame, sourceFrame, resolution, root) - { - const pm = this.projectionMatrix; - - // I don't think we will need this line.. - // pm.identity(); - - if (!root) - { - pm.a = (1 / destinationFrame.width * 2) * resolution; - pm.d = (1 / destinationFrame.height * 2) * resolution; - - pm.tx = -1 - (sourceFrame.x * pm.a); - pm.ty = -1 - (sourceFrame.y * pm.d); - } - else - { - pm.a = (1 / destinationFrame.width * 2) * resolution; - pm.d = (-1 / destinationFrame.height * 2) * resolution; - - pm.tx = -1 - (sourceFrame.x * pm.a); - pm.ty = 1 - (sourceFrame.y * pm.d); - } - } - - /** - * Sets the transform of the active render target to the given matrix - * - * @param {PIXI.Matrix} matrix - The transformation matrix - */ - setTransform()// matrix) - { - // this._activeRenderTarget.transform = matrix; - } -} diff --git a/packages/core/src/renderers/systems/RenderTextureSystem.js b/packages/core/src/renderers/systems/RenderTextureSystem.js deleted file mode 100644 index a133ef1..0000000 --- a/packages/core/src/renderers/systems/RenderTextureSystem.js +++ /dev/null @@ -1,115 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { Rectangle } from '@pixi/math'; - -const tempRect = new Rectangle(); - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ - -export default class RenderTextureSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this.clearColor = renderer._backgroundColorRgba; - - // TODO moe this property somewhere else! - this.defaultMaskStack = []; - this.defaultFilterStack = [{}]; - - // empty render texture? - this.renderTexture = null; - - this.destinationFrame = new Rectangle(); - } - - bind(renderTexture, sourceFrame, destinationFrame) - { - // TODO - do we want this?? - if (this.renderTexture === renderTexture) return; - this.renderTexture = renderTexture; - - const renderer = this.renderer; - - if (renderTexture) - { - const baseTexture = renderTexture.baseTexture; - - if (!destinationFrame) - { - tempRect.width = baseTexture.realWidth; - tempRect.height = baseTexture.realHeight; - - destinationFrame = tempRect; - } - - if (!sourceFrame) - { - sourceFrame = destinationFrame; - } - - this.renderer.framebuffer.bind(baseTexture.frameBuffer, destinationFrame); - - this.renderer.projection.update(destinationFrame, sourceFrame, baseTexture.resolution, false); - this.renderer.stencil.setMaskStack(baseTexture.stencilMaskStack); - } - else - { - // TODO these validation checks happen deeper down.. - // thing they can be avoided.. - if (!destinationFrame) - { - tempRect.width = renderer.width; - tempRect.height = renderer.height; - - destinationFrame = tempRect; - } - - if (!sourceFrame) - { - sourceFrame = destinationFrame; - } - - renderer.framebuffer.bind(null, destinationFrame); - - // TODO store this.. - this.renderer.projection.update(destinationFrame, sourceFrame, this.renderer.resolution, true); - this.renderer.stencil.setMaskStack(this.defaultMaskStack); - } - - this.destinationFrame.copyFrom(destinationFrame); - } - - /** - * Erases the render texture and fills the drawing area with a colour - * - * @param {number} [clearColor] - The colour - * @return {PIXI.Renderer} Returns itself. - */ - clear(clearColor) - { - if (this.renderTexture) - { - clearColor = clearColor || this.renderTexture.baseTexture.clearColor; - } - else - { - clearColor = clearColor || this.clearColor; - } - - this.renderer.framebuffer.clear(clearColor[0], clearColor[1], clearColor[2], clearColor[3]); - } - - resize()// screenWidth, screenHeight) - { - // resize the root only! - this.bind(null); - } -} diff --git a/packages/core/src/renderers/systems/StateSystem.js b/packages/core/src/renderers/systems/StateSystem.js deleted file mode 100755 index d7f4caf..0000000 --- a/packages/core/src/renderers/systems/StateSystem.js +++ /dev/null @@ -1,288 +0,0 @@ -import mapWebGLBlendModesToPixi from '../utils/mapWebGLBlendModesToPixi'; -import WebGLSystem from './WebGLSystem'; -import WebGLState from '../State'; - -const BLEND = 0; -const OFFSET = 1; -const CULLING = 2; -const DEPTH_TEST = 3; -const WINDING = 4; - -/** - * A WebGL state machines - * - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class StateSystem extends WebGLSystem -{ - /** - * @param {WebGLRenderingContext} gl - The current WebGL rendering context - */ - constructor(renderer) - { - super(renderer); - - this.gl = null; - - this.maxAttribs = null; - - // check we have vao.. - this.nativeVaoExtension = null; - - this.attribState = null; - - this.stateId = 0; - this.polygonOffset = 0; - this.blendMode = 17; - - this.map = []; - - // map functions for when we set state.. - this.map[BLEND] = this.setBlend; - this.map[OFFSET] = this.setOffset; - this.map[CULLING] = this.setCullFace; - this.map[DEPTH_TEST] = this.setDepthTest; - this.map[WINDING] = this.setFrontFace; - - this.checks = []; - - this.defaultState = new WebGLState(); - this.defaultState.blend = true; - this.defaultState.depth = true; - } - - contextChange(gl) - { - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; - - this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); - - // check we have vao.. - this.nativeVaoExtension = ( - gl.getExtension('OES_vertex_array_object') - || gl.getExtension('MOZ_OES_vertex_array_object') - || gl.getExtension('WEBKIT_OES_vertex_array_object') - ); - - this.attribState = { - tempAttribState: new Array(this.maxAttribs), - attribState: new Array(this.maxAttribs), - }; - - this.blendModes = mapWebGLBlendModesToPixi(gl); - - this.setState(this.defaultState); - - this.reset(); - } - - /** - * Sets the current state - * - * @param {*} state - The state to set. - */ - setState(state) - { - state = state || this.defaultState; - - // TODO maybe to an object check? ( this.state === state )? - if (this.stateId !== state.data) - { - let diff = this.stateId ^ state.data; - let i = 0; - - // order from least to most common - while (diff) - { - if (diff & 1) - { - // state change! - this.map[i].call(this, !!(state.data & (1 << i))); - } - - diff = diff >> 1; - i++; - } - - this.stateId = state.data; - } - - // based on the above settings we check for specific modes.. - // for example if blend is active we check and set the blend modes - // or of polygon offset is active we check the poly depth. - for (let i = 0; i < this.checks.length; i++) - { - this.checks[i](this, state); - } - } - - /** - * Enables or disabled blending. - * - * @param {boolean} value - Turn on or off webgl blending. - */ - setBlend(value) - { - this.updateCheck(StateSystem.checkBlendMode, value); - - this.gl[value ? 'enable' : 'disable'](this.gl.BLEND); - } - - /** - * Enables or disable polygon offset fill - * - * @param {boolean} value - Turn on or off webgl polygon offset testing. - */ - setOffset(value) - { - this.gl[value ? 'enable' : 'disable'](this.gl.POLYGON_OFFSET_FILL); - } - - /** - * Sets whether to enable or disable depth test. - * - * @param {boolean} value - Turn on or off webgl depth testing. - */ - setDepthTest(value) - { - this.gl[value ? 'enable' : 'disable'](this.gl.DEPTH_TEST); - } - - /** - * Sets whether to enable or disable cull face. - * - * @param {boolean} value - Turn on or off webgl cull face. - */ - setCullFace(value) - { - this.gl[value ? 'enable' : 'disable'](this.gl.CULL_FACE); - } - - /** - * Sets the gl front face. - * - * @param {boolean} value - true is clockwise and false is counter-clockwise - */ - setFrontFace(value) - { - this.gl.frontFace(this.gl[value ? 'CW' : 'CCW']); - } - - /** - * Sets the blend mode. - * - * @param {number} value - The blend mode to set to. - */ - setBlendMode(value) - { - if (value === this.blendMode) - { - return; - } - - this.blendMode = value; - this.gl.blendFunc(this.blendModes[value][0], this.blendModes[value][1]); - } - - /** - * Sets the polygon offset. - * - * @param {number} value - the polygon offset - * @param {number} scale - the polygon offset scale - */ - setPolygonOffset(value, scale) - { - this.gl.polygonOffset(value, scale); - } - - /** - * Disables all the vaos in use - * - */ - resetAttributes() - { - for (let i = 0; i < this.attribState.tempAttribState.length; i++) - { - this.attribState.tempAttribState[i] = 0; - } - - for (let i = 0; i < this.attribState.attribState.length; i++) - { - this.attribState.attribState[i] = 0; - } - - // im going to assume one is always active for performance reasons. - for (let i = 1; i < this.maxAttribs; i++) - { - this.gl.disableVertexAttribArray(i); - } - } - - // used - /** - * Resets all the logic and disables the vaos - */ - reset() - { - // unbind any VAO if they exist.. - if (this.nativeVaoExtension) - { - this.nativeVaoExtension.bindVertexArrayOES(null); - } - - // reset all attributes.. - this.resetAttributes(); - - this.gl.pixelStorei(this.gl.UNPACK_FLIP_Y_WEBGL, false); - - this.setBlendMode(0); - - // TODO? - // this.setState(this.defaultState); - } - - /** - * checks to see which updates should be checked based on which settings have been activated - * for example if blend is enabled then we shold check the blend modes each time the state is changed - * or if poygon fill is activated then we need to check if the polygone offset changes. - * The idea is that we only check what we have too. - * - * @param {Function} func the checking function to add or remove - * @param {boolean} value should the check function be added or removed. - */ - updateCheck(func, value) - { - const index = this.checks.indexOf(func); - - if (value && index === -1) - { - this.checks.push(func); - } - else if (!value && index !== -1) - { - this.checks.splice(index, 1); - } - } - - /** - * A private little wrapper function that we call to check the blend mode. - * - * @static - * @private - * @param {PIXI.StateSystem} System the System to perform the state check on - * @param {PIXI.State} state the state that the blendMode will pulled from - */ - static checkBlendMode(system, state) - { - system.setBlendMode(state.blendMode); - } - - // TODO - add polygon offset? -} diff --git a/packages/core/src/renderers/systems/StencilSystem.js b/packages/core/src/renderers/systems/StencilSystem.js deleted file mode 100644 index 5d538cb..0000000 --- a/packages/core/src/renderers/systems/StencilSystem.js +++ /dev/null @@ -1,133 +0,0 @@ -import WebGLSystem from './WebGLSystem'; - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class StencilSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - this.stencilMaskStack = []; - } - - /** - * Changes the mask stack that is used by this System. - * - * @param {PIXI.Graphics[]} stencilMaskStack - The mask stack - */ - setMaskStack(stencilMaskStack) - { - const gl = this.renderer.gl; - - if (stencilMaskStack.length !== this.stencilMaskStack.length) - { - if (stencilMaskStack.length === 0) - { - gl.disable(gl.STENCIL_TEST); - } - else - { - gl.enable(gl.STENCIL_TEST); - } - } - - this.stencilMaskStack = stencilMaskStack; - } - - /** - * Applies the Mask and adds it to the current stencil stack. @alvin - * - * @param {PIXI.Graphics} graphics - The mask - */ - pushStencil(graphics) - { - this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); - - // this.renderer._activeRenderTarget.attachStencilBuffer(); - - const gl = this.renderer.gl; - const prevMaskCount = this.stencilMaskStack.length; - - if (prevMaskCount === 0) - { - gl.enable(gl.STENCIL_TEST); - } - - this.stencilMaskStack.push(graphics); - - // Increment the refference stencil value where the new mask overlaps with the old ones. - gl.colorMask(false, false, false, false); - gl.stencilFunc(gl.EQUAL, prevMaskCount, this._getBitwiseMask()); - gl.stencilOp(gl.KEEP, gl.KEEP, gl.INCR); - this.renderer.plugins.graphics.render(graphics); - - this._useCurrent(); - } - - /** - * Removes the last mask from the stencil stack. @alvin - */ - popStencil() - { - this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); - - const gl = this.renderer.gl; - const graphics = this.stencilMaskStack.pop(); - - if (this.stencilMaskStack.length === 0) - { - // the stack is empty! - gl.disable(gl.STENCIL_TEST); - gl.clear(gl.STENCIL_BUFFER_BIT); - gl.clearStencil(0); - } - else - { - // Decrement the refference stencil value where the popped mask overlaps with the other ones - gl.colorMask(false, false, false, false); - gl.stencilOp(gl.KEEP, gl.KEEP, gl.DECR); - this.renderer.plugins.graphics.render(graphics); - - this._useCurrent(); - } - } - - /** - * Setup renderer to use the current stencil data. - */ - _useCurrent() - { - const gl = this.renderer.gl; - - gl.colorMask(true, true, true, true); - gl.stencilFunc(gl.EQUAL, this.stencilMaskStack.length, this._getBitwiseMask()); - gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP); - } - - /** - * Fill 1s equal to the number of acitve stencil masks. - * - * @return {number} The bitwise mask. - */ - _getBitwiseMask() - { - return (1 << this.stencilMaskStack.length) - 1; - } - - /** - * Destroys the mask stack. - * - */ - destroy() - { - super.destroy(this); - - this.stencilMaskStack = null; - } -} diff --git a/packages/core/src/renderers/systems/WebGLSystem.js b/packages/core/src/renderers/systems/WebGLSystem.js deleted file mode 100644 index d3d5b31..0000000 --- a/packages/core/src/renderers/systems/WebGLSystem.js +++ /dev/null @@ -1,41 +0,0 @@ -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this manager works for. - */ - constructor(renderer) - { - /** - * The renderer this manager works for. - * - * @member {PIXI.Renderer} - */ - this.renderer = renderer; - - this.renderer.runners.contextChange.add(this); - } - - /** - * Generic method called when there is a WebGL context change. - * - */ - contextChange() - { - // do some codes init! - } - - /** - * Generic destroy methods to be overridden by the subclass - * - */ - destroy() - { - this.renderer.runners.contextChange.remove(this); - this.renderer = null; - } -} diff --git a/packages/core/src/renderers/systems/filter/FilterSystem.js b/packages/core/src/renderers/systems/filter/FilterSystem.js deleted file mode 100644 index e615037..0000000 --- a/packages/core/src/renderers/systems/filter/FilterSystem.js +++ /dev/null @@ -1,431 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; - -import RenderTexture from '../../../textures/RenderTexture'; -import Quad from '../../utils/Quad'; -import QuadUv from '../../utils/QuadUv'; -import { Rectangle } from '@pixi/math'; -import * as filterTransforms from '../../filters/filterTransforms'; -import bitTwiddle from 'bit-twiddle'; -import UniformGroup from '../../../shader/UniformGroup'; -import { DRAW_MODES } from '../../../../../constants'; - -// -/** - * @ignore - * @class - */ -class FilterState -{ - /** - * - */ - constructor() - { - this.renderTexture = null; - this.sourceFrame = new Rectangle(); - this.destinationFrame = new Rectangle(); - this.filters = []; - this.target = null; - this.legacy = false; - this.resolution = 1; - } -} - -/** - * @class - * @memberof PIXI.systems - * @extends PIXI.systems.WebGLSystem - */ -export default class FilterSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - /** - * stores a bunch of PO2 textures used for filtering - * @type {Object} - */ - this.texturePool = {}; - - /** - * a pool for storing filter states, save us creating new ones each tick - * @type {Array} - */ - this.statePool = []; - - /** - * A very simple geometry used when drawing a filter effect to the screen - * @type {Quad} - */ - this.quad = new Quad(); - - this.quadUv = new QuadUv(); - - /** - * Temporary rect for maths - * @type {PIXI.Rectangle} - */ - this.tempRect = new Rectangle(); - - this.activeState = {}; - - /** - * this uniform group is attached to filter uniforms when used - * @type {UniformGroup} - */ - this.globalUniforms = new UniformGroup({ - sourceFrame: this.tempRect, - destinationFrame: this.tempRect, - - // legacy variables - filterArea: new Float32Array(4), - filterClamp: new Float32Array(4), - }, true); - } - - /** - * Adds a new filter to the System. - * - * @param {PIXI.DisplayObject} target - The target of the filter to render. - * @param {PIXI.Filter[]} filters - The filters to apply. - */ - push(target, filters) - { - const renderer = this.renderer; - const filterStack = this.renderer.renderTexture.defaultFilterStack; - const state = this.statePool.pop() || new FilterState(); - - let resolution = filters[0].resolution; - let padding = filters[0].padding; - let autoFit = filters[0].autoFit; - let legacy = filters[0].legacy; - - for (let i = 1; i < filters.length; i++) - { - const filter = filters[i]; - - // lets use the lowest resolution.. - resolution = Math.min(resolution, filter.resolution); - // and the largest amount of padding! - padding = Math.max(padding, filter.padding); - // only auto fit if all filters are autofit - autoFit = autoFit || filter.autoFit; - - legacy = legacy || filter.legacy; - } - - filterStack.push(state); - - state.resolution = resolution; - - state.legacy = legacy; - - // round to whole number based on resolution - // TODO move that to the shader too? - state.sourceFrame = target.filterArea || target.getBounds(true); - - state.sourceFrame.pad(padding); - - if (autoFit) - { - state.sourceFrame.fit(this.renderer.renderTexture.destinationFrame); - } - - state.sourceFrame.round(resolution); - - state.renderTexture = this.getPotFilterTexture(state.sourceFrame.width, state.sourceFrame.height, resolution); - state.filters = filters; - - state.destinationFrame.width = state.renderTexture.width; - state.destinationFrame.height = state.renderTexture.height; - - state.renderTexture.filterFrame = state.sourceFrame; - renderer.renderTexture.bind(state.renderTexture, state.sourceFrame);// /, state.destinationFrame); - renderer.renderTexture.clear(); - } - - /** - * Pops off the filter and applies it. - * - */ - pop() - { - const renderer = this.renderer; - const filterStack = renderer.renderTexture.defaultFilterStack; - const state = filterStack.pop(); - const filters = state.filters; - - this.activeState = state; - - const globalUniforms = this.globalUniforms.uniforms; - - globalUniforms.sourceFrame = state.sourceFrame; - globalUniforms.destinationFrame = state.destinationFrame; - globalUniforms.resolution = state.resolution; - - // only update the rect if its legacy.. - if (state.legacy) - { - const filterArea = globalUniforms.filterArea; - const filterClamp = globalUniforms.filterClamp; - - filterArea[0] = state.destinationFrame.width; - filterArea[1] = state.destinationFrame.height; - filterArea[2] = state.sourceFrame.x; - filterArea[3] = state.sourceFrame.y; - - filterClamp[0] = 0.5 / state.resolution / state.destinationFrame.width; - filterClamp[1] = 0.5 / state.resolution / state.destinationFrame.height; - filterClamp[2] = (state.sourceFrame.width - 0.5) / state.resolution / state.destinationFrame.width; - filterClamp[3] = (state.sourceFrame.height - 0.5) / state.resolution / state.destinationFrame.height; - } - - this.globalUniforms.update(); - - const lastState = filterStack[filterStack.length - 1]; - - if (filters.length === 1) - { - filters[0].apply(this, state.renderTexture, lastState.renderTexture, false, state); - - this.returnFilterTexture(state.renderTexture); - } - else - { - let flip = state.renderTexture; - let flop = this.getPotFilterTexture( - flip.width, - flip.height, - state.resolution - ); - - flop.filterFrame = flip.filterFrame; - - let i = 0; - - for (i = 0; i < filters.length - 1; ++i) - { - filters[i].apply(this, flip, flop, true, state); - - const t = flip; - - flip = flop; - flop = t; - } - - filters[i].apply(this, flip, lastState.renderTexture, false, state); - - this.returnFilterTexture(flip); - this.returnFilterTexture(flop); - } - - this.statePool.push(state); - } - - /** - * Draws a filter. - * - * @param {PIXI.Filter} filter - The filter to draw. - * @param {PIXI.RenderTarget} input - The input render target. - * @param {PIXI.RenderTarget} output - The target to output to. - * @param {boolean} clear - Should the output be cleared before rendering to it - */ - applyFilter(filter, input, output, clear) - { - const renderer = this.renderer; - - renderer.renderTexture.bind(output, output ? output.filterFrame : null); - - if (clear) - { - // gl.disable(gl.SCISSOR_TEST); - renderer.renderTexture.clear(); - // gl.enable(gl.SCISSOR_TEST); - } - - // set the uniforms.. - filter.uniforms.uSampler = input; - filter.uniforms.filterGlobals = this.globalUniforms; - - // TODO make it so that the order of this does not matter.. - // because it does at the moment cos of global uniforms. - // they need to get resynced - - renderer.state.setState(filter.state); - renderer.shader.bind(filter); - - if (filter.legacy) - { - this.quadUv.map(input._frame, input.filterFrame); - - renderer.geometry.bind(this.quadUv); - renderer.geometry.draw(DRAW_MODES.TRIANGLES); - } - else - { - renderer.geometry.bind(this.quad); - renderer.geometry.draw(DRAW_MODES.TRIANGLE_STRIP); - } - } - - /** - * Calculates the mapped matrix. - * - * TODO playing around here.. this is temporary - (will end up in the shader) - * this returns a matrix that will normalise map filter cords in the filter to screen space - * - * @param {PIXI.Matrix} outputMatrix - the matrix to output to. - * @return {PIXI.Matrix} The mapped matrix. - */ - calculateScreenSpaceMatrix(outputMatrix) - { - const currentState = this.activeState; - - return filterTransforms.calculateScreenSpaceMatrix( - outputMatrix, - currentState.sourceFrame, - currentState.destinationFrame - ); - } - - /** - * This will map the filter coord so that a texture can be used based on the transform of a sprite - * - * @param {PIXI.Matrix} outputMatrix - The matrix to output to. - * @param {PIXI.Sprite} sprite - The sprite to map to. - * @return {PIXI.Matrix} The mapped matrix. - */ - calculateSpriteMatrix(outputMatrix, sprite) - { - const currentState = this.activeState; - - return filterTransforms.calculateSpriteMatrix( - outputMatrix, - currentState.sourceFrame, - currentState.destinationFrame, - sprite - ); - } - - /** - * Destroys this Filter System. - * - * @param {boolean} [contextLost=false] context was lost, do not free shaders - * - */ - destroy(contextLost = false) - { - if (!contextLost) - { - this.emptyPool(); - } - else - { - this.texturePool = {}; - } - } - - /** - * Gets a Power-of-Two render texture. - * - * TODO move to a seperate class could be on renderer? - * also - could cause issue with multiple contexts? - * - * @private - * @param {WebGLRenderingContext} gl - The webgl rendering context - * @param {number} minWidth - The minimum width of the render target. - * @param {number} minHeight - The minimum height of the render target. - * @param {number} resolution - The resolution of the render target. - * @return {PIXI.RenderTarget} The new render target. - */ - getPotFilterTexture(minWidth, minHeight, resolution) - { - minWidth = bitTwiddle.nextPow2(minWidth); - minHeight = bitTwiddle.nextPow2(minHeight); - resolution = resolution || 1; - - const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); - - if (!this.texturePool[key]) - { - this.texturePool[key] = []; - } - - let renderTexture = this.texturePool[key].pop(); - - if (!renderTexture) - { - // temporary bypass cache.. - // internally - this will cause a texture to be bound.. - renderTexture = RenderTexture.create({ - width: minWidth, - height: minHeight, - resolution, - }); - } - - return renderTexture; - } - - /** - * Gets extra render texture to use inside current filter - * - * @param {number} resolution resolution of the renderTexture - * @returns {PIXI.RenderTexture} - */ - getFilterTexture(resolution) - { - const rt = this.activeState.renderTexture; - - const filterTexture = this.getPotFilterTexture(rt.width, rt.height, resolution || rt.baseTexture.resolution); - - filterTexture.filterFrame = rt.filterFrame; - - return filterTexture; - } - - /** - * Frees a render texture back into the pool. - * - * @param {PIXI.RenderTarget} renderTexture - The renderTarget to free - */ - returnFilterTexture(renderTexture) - { - renderTexture.filterFrame = null; - - const base = renderTexture.baseTexture; - - const minWidth = base.width; - const minHeight = base.height; - - const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); - - this.texturePool[key].push(renderTexture); - } - - /** - * Empties the texture pool. - * - */ - emptyPool() - { - for (const i in this.texturePool) - { - const textures = this.texturePool[i]; - - if (textures) - { - for (let j = 0; j < textures.length; j++) - { - textures[j].destroy(true); - } - } - } - - this.texturePool = {}; - } -} diff --git a/packages/core/src/renderers/systems/geometry/GLBuffer.js b/packages/core/src/renderers/systems/geometry/GLBuffer.js deleted file mode 100644 index 0477e8a..0000000 --- a/packages/core/src/renderers/systems/geometry/GLBuffer.js +++ /dev/null @@ -1,9 +0,0 @@ -export default class GLBuffer -{ - constructor(buffer) - { - this.buffer = buffer; - this.updateID = -1; - this.byteLength = -1; - } -} diff --git a/packages/core/src/renderers/systems/geometry/GeometrySystem.js b/packages/core/src/renderers/systems/geometry/GeometrySystem.js deleted file mode 100644 index 26c8e9e..0000000 --- a/packages/core/src/renderers/systems/geometry/GeometrySystem.js +++ /dev/null @@ -1,388 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; -import GLBuffer from './GLBuffer'; - -const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class GeometrySystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this._activeGeometry = null; - this._activeVao = null; - - this.hasVao = true; - this.hasInstance = true; - } - - /** - * Sets up the renderer context and necessary buffers. - * - * @private - */ - contextChange() - { - const gl = this.gl = this.renderer.gl; - - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - - // webgl2 - if (!gl.createVertexArray) - { - // webgl 1! - let nativeVaoExtension = this.renderer.context.extensions.vertexArrayObject; - - if (this.renderer.legacy) - { - nativeVaoExtension = null; - } - - if (nativeVaoExtension) - { - gl.createVertexArray = () => - nativeVaoExtension.createVertexArrayOES(); - - gl.bindVertexArray = (vao) => - nativeVaoExtension.bindVertexArrayOES(vao); - - gl.deleteVertexArray = (vao) => - nativeVaoExtension.deleteVertexArrayOES(vao); - } - else - { - this.hasVao = false; - gl.createVertexArray = () => - { - // empty - }; - - gl.bindVertexArray = () => - { - // empty - }; - - gl.deleteVertexArray = () => - { - // empty - }; - } - } - - if (!gl.vertexAttribDivisor) - { - const instanceExt = gl.getExtension('ANGLE_instanced_arrays'); - - if (instanceExt) - { - gl.vertexAttribDivisor = (a, b) => - instanceExt.vertexAttribDivisorANGLE(a, b); - - gl.drawElementsInstanced = (a, b, c, d, e) => - instanceExt.drawElementsInstancedANGLE(a, b, c, d, e); - - gl.drawArraysInstanced = (a, b, c, d) => - instanceExt.drawArraysInstancedANGLE(a, b, c, d); - } - else - { - this.hasInstance = false; - } - } - } - - /** - * Binds geometry so that is can be drawn. Creating a Vao if required - * @private - * @param {PIXI.Geometry} geometry instance of geometry to bind - */ - bind(geometry, shader) - { - shader = shader || this.renderer.shader.shader; - - const gl = this.gl; - - // not sure the best way to address this.. - // currently different shaders require different VAOs for the same geometry - // Still mulling over the best way to solve this one.. - // will likely need to modify the shader attribute locations at run time! - let vaos = geometry.glVertexArrayObjects[this.CONTEXT_UID]; - - if (!vaos) - { - geometry.glVertexArrayObjects[this.CONTEXT_UID] = vaos = {}; - } - - const vao = vaos[shader.program.id] || this.initGeometryVao(geometry, shader.program); - - this._activeGeometry = geometry; - - if (this._activeVao !== vao) - { - this._activeVao = vao; - - if (this.hasVao) - { - gl.bindVertexArray(vao); - } - else - { - this.activateVao(geometry, shader.program); - } - } - - // TODO - optimise later! - // don't need to loop through if nothing changed! - // maybe look to add an 'autoupdate' to geometry? - this.updateBuffers(); - } - - reset() - { - this.unbind(); - } - - updateBuffers() - { - const geometry = this._activeGeometry; - const gl = this.gl; - - for (let i = 0; i < geometry.buffers.length; i++) - { - const buffer = geometry.buffers[i]; - - const glBuffer = buffer._glBuffers[this.CONTEXT_UID]; - - if (buffer._updateID !== glBuffer.updateID) - { - glBuffer.updateID = buffer._updateID; - - // TODO can cache this on buffer! maybe added a getter / setter? - const type = buffer.index ? gl.ELEMENT_ARRAY_BUFFER : gl.ARRAY_BUFFER; - const drawType = buffer.static ? gl.STATIC_DRAW : gl.DYNAMIC_DRAW; - - gl.bindBuffer(type, glBuffer.buffer); - - if (glBuffer.byteLength >= buffer.data.byteLength) - { - // offset is always zero for now! - gl.bufferSubData(type, 0, buffer.data); - } - else - { - gl.bufferData(type, buffer.data, drawType); - } - } - } - } - - checkCompatability(geometry, program) - { - // geometry must have at least all the attributes that the shader requires. - const geometryAttributes = geometry.attributes; - const shaderAttributes = program.attributeData; - - for (const j in shaderAttributes) - { - if (!geometryAttributes[j]) - { - throw new Error(`shader and geometry incompatible, geometry missing the "${j}" attribute`); - } - } - } - - /** - * Creates a Vao with the same structure as the geometry and stores it on the geometry. - * @private - * @param {PIXI.Geometry} geometry instance of geometry to to generate Vao for - */ - initGeometryVao(geometry, program) - { - this.checkCompatability(geometry, program); - - const gl = this.gl; - const CONTEXT_UID = this.CONTEXT_UID; - const buffers = geometry.buffers; - const attributes = geometry.attributes; - - const tempStride = {}; - const tempStart = {}; - - for (const j in buffers) - { - tempStride[j] = 0; - tempStart[j] = 0; - } - - for (const j in attributes) - { - if (!attributes[j].size && program.attributeData[j]) - { - attributes[j].size = program.attributeData[j].size; - } - - tempStride[attributes[j].buffer] += attributes[j].size * byteSizeMap[attributes[j].type]; - } - - for (const j in attributes) - { - const attribute = attributes[j]; - const attribSize = attribute.size; - - if (attribute.stride === undefined) - { - if (tempStride[attribute.buffer] === attribSize * byteSizeMap[attribute.type]) - { - attribute.stride = 0; - } - else - { - attribute.stride = tempStride[attribute.buffer]; - } - } - - if (attribute.start === undefined) - { - attribute.start = tempStart[attribute.buffer]; - - tempStart[attribute.buffer] += attribSize * byteSizeMap[attribute.type]; - } - } - - // first update - and create the buffers! - // only create a gl buffer if it actually gets - for (let i = 0; i < buffers.length; i++) - { - const buffer = buffers[i]; - - if (!buffer._glBuffers[CONTEXT_UID]) - { - buffer._glBuffers[CONTEXT_UID] = new GLBuffer(gl.createBuffer()); - } - } - - // TODO - maybe make this a data object? - // lets wait to see if we need to first! - const vao = gl.createVertexArray(); - - gl.bindVertexArray(vao); - - this.activateVao(geometry, program); - - gl.bindVertexArray(null); - - // add it to the cache! - geometry.glVertexArrayObjects[this.CONTEXT_UID][program.id] = vao; - - return vao; - } - - activateVao(geometry, program) - { - const gl = this.gl; - const CONTEXT_UID = this.CONTEXT_UID; - const buffers = geometry.buffers; - const attributes = geometry.attributes; - - if (geometry.indexBuffer) - { - // first update the index buffer if we have one.. - gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, geometry.indexBuffer._glBuffers[CONTEXT_UID].buffer); - } - - let lastBuffer = null; - - // add a new one! - for (const j in attributes) - { - const attribute = attributes[j]; - const buffer = buffers[attribute.buffer]; - const glBuffer = buffer._glBuffers[CONTEXT_UID]; - - if (program.attributeData[j]) - { - if (lastBuffer !== glBuffer) - { - gl.bindBuffer(gl.ARRAY_BUFFER, glBuffer.buffer); - - lastBuffer = glBuffer; - } - - const location = program.attributeData[j].location; - - // TODO introduce state again - // we can optimise this for older devices that have no VAOs - gl.enableVertexAttribArray(location); - - gl.vertexAttribPointer(location, - attribute.size, - attribute.type || gl.FLOAT, - attribute.normalized, - attribute.stride, - attribute.start); - - if (attribute.instance) - { - // TODO calculate instance count based of this... - if (this.hasInstance) - { - gl.vertexAttribDivisor(location, 1); - } - else - { - throw new Error('geometry error, GPU Instancing is not supported on this device'); - } - } - } - } - } - - draw(type, size, start, instanceCount) - { - const gl = this.gl; - const geometry = this._activeGeometry; - - // TODO.. this should not change so maybe cache the function? - - if (geometry.indexBuffer) - { - if (geometry.instanced) - { - /* eslint-disable max-len */ - gl.drawElementsInstanced(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2, instanceCount || 1); - /* eslint-enable max-len */ - } - else - { - gl.drawElements(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2); - } - } - else - if (geometry.instanced) - { - // TODO need a better way to calculate size.. - gl.drawArraysInstanced(type, start, size || geometry.getSize(), instanceCount || 1); - } - else - { - gl.drawArrays(type, start, size || geometry.getSize()); - } - - return this; - } - - unbind() - { - this.gl.bindVertexArray(null); - this._activeVao = null; - this._activeGeometry = null; - } -} diff --git a/packages/core/src/renderers/systems/geometry/setVertexAttribArrays.js b/packages/core/src/renderers/systems/geometry/setVertexAttribArrays.js deleted file mode 100644 index 23b6959..0000000 --- a/packages/core/src/renderers/systems/geometry/setVertexAttribArrays.js +++ /dev/null @@ -1,54 +0,0 @@ -// var GL_MAP = {}; - -/** - * @param gl {WebGLRenderingContext} The current WebGL context - * @param attribs {*} - * @param state {*} - */ -export default function setVertexAttribArrays(gl, attribs, state) -{ - let i; - - if (state) - { - const tempAttribState = state.tempAttribState; - const attribState = state.attribState; - - for (i = 0; i < tempAttribState.length; i++) - { - tempAttribState[i] = false; - } - - // set the new attribs - for (i = 0; i < attribs.length; i++) - { - tempAttribState[attribs[i].attribute.location] = true; - } - - for (i = 0; i < attribState.length; i++) - { - if (attribState[i] !== tempAttribState[i]) - { - attribState[i] = tempAttribState[i]; - - if (state.attribState[i]) - { - gl.enableVertexAttribArray(i); - } - else - { - gl.disableVertexAttribArray(i); - } - } - } - } - else - { - for (i = 0; i < attribs.length; i++) - { - const attrib = attribs[i]; - - gl.enableVertexAttribArray(attrib.attribute.location); - } - } -} diff --git a/packages/core/src/renderers/systems/index.js b/packages/core/src/renderers/systems/index.js deleted file mode 100644 index c843f03..0000000 --- a/packages/core/src/renderers/systems/index.js +++ /dev/null @@ -1,6 +0,0 @@ -/** - * Systems are individual components to the Renderer pipeline. - * @namespace PIXI.systems - */ -export { default as WebGLSystem } from './WebGLSystem'; -export { default as FilterSystem } from './filter/FilterSystem'; diff --git a/packages/core/src/renderers/systems/shader/GLProgram.js b/packages/core/src/renderers/systems/shader/GLProgram.js deleted file mode 100644 index 92980ca..0000000 --- a/packages/core/src/renderers/systems/shader/GLProgram.js +++ /dev/null @@ -1,43 +0,0 @@ -/** - * Helper class to create a webGL Program - * - * @class - * @private - */ -export default class GLProgram -{ - constructor(program, uniformData) - { - /** - * The shader program - * - * @member {WebGLProgram} - */ - this.program = program; - - /** - * holds the uniform data which contains uniform locations - * and current uniform values used for caching and preventing undeed GPU commands - * @type {Object} - */ - this.uniformData = uniformData; - - /** - * uniformGroups holds the various upload functions for the shader. Each uniform group - * and program have a unique upload function generated. - * @type {Object} - */ - this.uniformGroups = {}; - } - - /** - * Destroys this program - * TODO - */ - destroy() - { - this.uniformData = null; - this.uniformGroups = null; - this.program = null; - } -} diff --git a/packages/core/src/renderers/systems/shader/ShaderSystem.js b/packages/core/src/renderers/systems/shader/ShaderSystem.js deleted file mode 100644 index 6cf57b6..0000000 --- a/packages/core/src/renderers/systems/shader/ShaderSystem.js +++ /dev/null @@ -1,170 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; -import GLProgram from './GLProgram'; -import generateUniformsSync from '../../../shader/generateUniformsSync'; -import defaultValue from '../../../shader/defaultValue'; -import compileProgram from './shader/compileProgram'; - -let UID = 0; - -/** - * Helper class to create a webGL Texture - * - * @class - * @memberof PIXI - */ -export default class ShaderSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - A reference to the current renderer - */ - constructor(renderer) - { - super(renderer); - - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = null; - - this.shader = null; - this.program = null; - - this.id = UID++; - } - - contextChange(gl) - { - this.gl = gl; - } - - /** - * Changes the current shader to the one given in parameter - * - * @param {PIXI.Shader} shader - the new shader - * @param {boolean} dontSync - false if the shader should automatically sync its uniforms. - * @returns {PIXI.glCore.glProgram} the glProgram that belongs to the shader. - */ - bind(shader, dontSync) - { - shader.uniforms.globals = this.renderer.globalUniforms; - - const program = shader.program; - const glProgram = program.glPrograms[this.renderer.CONTEXT_UID] || this.generateShader(shader); - - this.shader = shader; - - // TODO - some current pixi plugins bypass this.. so it not safe to use yet.. - if (this.program !== program) - { - this.program = program; - this.gl.useProgram(glProgram.program); - } - - if (!dontSync) - { - this.syncUniformGroup(shader.uniformGroup); - } - - return glProgram; - } - - /** - * Uploads the uniforms values to the currently bound shader. - * - * @param {object} uniforms - the uniforms valiues that be applied to the current shader - */ - setUniforms(uniforms) - { - const shader = this.shader.program; - const glProgram = shader.glPrograms[this.renderer.CONTEXT_UID]; - - shader.syncUniforms(glProgram.uniformData, uniforms, this.renderer); - } - - syncUniformGroup(group) - { - const glProgram = this.getglProgram(); - - if (!group.static || group.dirtyId !== glProgram.uniformGroups[group.id]) - { - glProgram.uniformGroups[group.id] = group.dirtyId; - const syncFunc = group.syncUniforms[this.shader.program.id] || this.createSyncGroups(group); - - syncFunc(glProgram.uniformData, group.uniforms, this.renderer); - } - } - - createSyncGroups(group) - { - group.syncUniforms[this.shader.program.id] = generateUniformsSync(group, this.shader.program.uniformData); - - return group.syncUniforms[this.shader.program.id]; - } - - /** - * Returns the underlying GLShade rof the currently bound shader. - * This can be handy for when you to have a little more control over the setting of your uniforms. - * - * @return {PIXI.glCore.Shader} the glProgram for the currently bound Shader for this context - */ - getglProgram() - { - if (this.shader) - { - return this.shader.program.glPrograms[this.renderer.CONTEXT_UID]; - } - - return null; - } - - /** - * Generates a glProgram verion of the Shader provided. - * - * @private - * @param {PIXI.Shader} shader the shader that the glProgram will be based on. - * @return {PIXI.glCore.glProgram} A shiney new glProgram - */ - generateShader(shader) - { - const gl = this.gl; - - const program = shader.program; - - const attribMap = {}; - - for (const i in program.attributeData) - { - attribMap[i] = program.attributeData[i].location; - } - - const shaderProgram = compileProgram(gl, program.vertexSrc, program.fragmentSrc, attribMap); - const uniformData = {}; - - for (const i in program.uniformData) - { - const data = program.uniformData[i]; - - uniformData[i] = { - location: gl.getUniformLocation(shaderProgram, i), - value: defaultValue(data.type, data.size), - }; - } - - const glProgram = new GLProgram(shaderProgram, uniformData); - - program.glPrograms[this.renderer.CONTEXT_UID] = glProgram; - - return glProgram; - } - - /** - * Destroys this System and removes all its textures - */ - destroy() - { - // TODO implement destroy method for ShaderSystem - this.destroyed = true; - } -} diff --git a/packages/core/src/renderers/systems/shader/shader/compileProgram.js b/packages/core/src/renderers/systems/shader/shader/compileProgram.js deleted file mode 100644 index 8c6ccb9..0000000 --- a/packages/core/src/renderers/systems/shader/shader/compileProgram.js +++ /dev/null @@ -1,78 +0,0 @@ -/** - * @method compileProgram - * @memberof PIXI.glCore.shader - * @param gl {WebGLRenderingContext} The current WebGL context {WebGLProgram} - * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. - * @param fragmentSrc {string|string[]} The fragment shader source as an array of strings. - * @param attributeLocations {Object} An attribute location map that lets you manually set the attribute locations - * @return {WebGLProgram} the shader program - */ -export default function compileProgram(gl, vertexSrc, fragmentSrc, attributeLocations) -{ - const glVertShader = compileShader(gl, gl.VERTEX_SHADER, vertexSrc); - const glFragShader = compileShader(gl, gl.FRAGMENT_SHADER, fragmentSrc); - - let program = gl.createProgram(); - - gl.attachShader(program, glVertShader); - gl.attachShader(program, glFragShader); - - // optionally, set the attributes manually for the program rather than letting WebGL decide.. - if (attributeLocations) - { - for (const i in attributeLocations) - { - gl.bindAttribLocation(program, attributeLocations[i], i); - } - } - - gl.linkProgram(program); - - // if linking fails, then log and cleanup - if (!gl.getProgramParameter(program, gl.LINK_STATUS)) - { - console.error('Pixi.js Error: Could not initialize shader.'); - console.error('gl.VALIDATE_STATUS', gl.getProgramParameter(program, gl.VALIDATE_STATUS)); - console.error('gl.getError()', gl.getError()); - - // if there is a program info log, log it - if (gl.getProgramInfoLog(program) !== '') - { - console.warn('Pixi.js Warning: gl.getProgramInfoLog()', gl.getProgramInfoLog(program)); - } - - gl.deleteProgram(program); - program = null; - } - - // clean up some shaders - gl.deleteShader(glVertShader); - gl.deleteShader(glFragShader); - - return program; -} - -/** - * @private - * @param gl {WebGLRenderingContext} The current WebGL context {WebGLProgram} - * @param type {Number} the type, can be either VERTEX_SHADER or FRAGMENT_SHADER - * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. - * @return {WebGLShader} the shader - */ -function compileShader(gl, type, src) -{ - const shader = gl.createShader(type); - - gl.shaderSource(shader, src); - gl.compileShader(shader); - - if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) - { - console.warn(src); - console.error(gl.getShaderInfoLog(shader)); - - return null; - } - - return shader; -} diff --git a/packages/core/src/renderers/systems/shader/shader/defaultValue.js b/packages/core/src/renderers/systems/shader/shader/defaultValue.js deleted file mode 100644 index 97290bd..0000000 --- a/packages/core/src/renderers/systems/shader/shader/defaultValue.js +++ /dev/null @@ -1,79 +0,0 @@ -/** - * @method defaultValue - * @memberof PIXI.glCore.shader - * @param type {String} Type of value - * @param size {Number} - */ -export default function defaultValue(type, size) -{ - switch (type) - { - case 'float': - return 0; - - case 'vec2': - return new Float32Array(2 * size); - - case 'vec3': - return new Float32Array(3 * size); - - case 'vec4': - return new Float32Array(4 * size); - - case 'int': - case 'sampler2D': - case 'sampler2DArray': - return 0; - - case 'ivec2': - return new Int32Array(2 * size); - - case 'ivec3': - return new Int32Array(3 * size); - - case 'ivec4': - return new Int32Array(4 * size); - - case 'bool': - return false; - - case 'bvec2': - - return booleanArray(2 * size); - - case 'bvec3': - return booleanArray(3 * size); - - case 'bvec4': - return booleanArray(4 * size); - - case 'mat2': - return new Float32Array([1, 0, - 0, 1]); - - case 'mat3': - return new Float32Array([1, 0, 0, - 0, 1, 0, - 0, 0, 1]); - - case 'mat4': - return new Float32Array([1, 0, 0, 0, - 0, 1, 0, 0, - 0, 0, 1, 0, - 0, 0, 0, 1]); - } - - return null; -} - -function booleanArray(size) -{ - const array = new Array(size); - - for (let i = 0; i < array.length; i++) - { - array[i] = false; - } - - return array; -} diff --git a/packages/core/src/renderers/systems/shader/shader/index.js b/packages/core/src/renderers/systems/shader/shader/index.js deleted file mode 100644 index ba7a317..0000000 --- a/packages/core/src/renderers/systems/shader/shader/index.js +++ /dev/null @@ -1,8 +0,0 @@ -/** - * @namespace PIXI.glCore.shader - */ -export { default as compileProgram } from './compileProgram'; -export { default as defaultValue } from './defaultValue'; -export { default as setPrecision } from './setPrecision'; -export { default as mapSize } from './mapSize'; -export { default as mapType } from './mapType'; diff --git a/packages/core/src/renderers/systems/shader/shader/mapSize.js b/packages/core/src/renderers/systems/shader/shader/mapSize.js deleted file mode 100644 index b681554..0000000 --- a/packages/core/src/renderers/systems/shader/shader/mapSize.js +++ /dev/null @@ -1,33 +0,0 @@ -const GLSL_TO_SIZE = { - float: 1, - vec2: 2, - vec3: 3, - vec4: 4, - - int: 1, - ivec2: 2, - ivec3: 3, - ivec4: 4, - - bool: 1, - bvec2: 2, - bvec3: 3, - bvec4: 4, - - mat2: 4, - mat3: 9, - mat4: 16, - - sampler2D: 1, -}; - -/** - * @method mapSize - * @memberof PIXI.glCore.shader - * @param type {String} - * @return {Number} - */ -export default function mapSize(type) -{ - return GLSL_TO_SIZE[type]; -} diff --git a/packages/core/src/renderers/systems/shader/shader/mapType.js b/packages/core/src/renderers/systems/shader/shader/mapType.js deleted file mode 100644 index d288b7c..0000000 --- a/packages/core/src/renderers/systems/shader/shader/mapType.js +++ /dev/null @@ -1,45 +0,0 @@ -let GL_TABLE = null; - -const GL_TO_GLSL_TYPES = { - FLOAT: 'float', - FLOAT_VEC2: 'vec2', - FLOAT_VEC3: 'vec3', - FLOAT_VEC4: 'vec4', - - INT: 'int', - INT_VEC2: 'ivec2', - INT_VEC3: 'ivec3', - INT_VEC4: 'ivec4', - - BOOL: 'bool', - BOOL_VEC2: 'bvec2', - BOOL_VEC3: 'bvec3', - BOOL_VEC4: 'bvec4', - - FLOAT_MAT2: 'mat2', - FLOAT_MAT3: 'mat3', - FLOAT_MAT4: 'mat4', - - SAMPLER_2D: 'sampler2D', - SAMPLER_CUBE: 'samplerCube', - SAMPLER_2D_ARRAY: 'sampler2DArray', -}; - -export default function mapType(gl, type) -{ - if (!GL_TABLE) - { - const typeNames = Object.keys(GL_TO_GLSL_TYPES); - - GL_TABLE = {}; - - for (let i = 0; i < typeNames.length; ++i) - { - const tn = typeNames[i]; - - GL_TABLE[gl[tn]] = GL_TO_GLSL_TYPES[tn]; - } - } - - return GL_TABLE[type]; -} diff --git a/packages/core/src/renderers/systems/shader/shader/setPrecision.js b/packages/core/src/renderers/systems/shader/shader/setPrecision.js deleted file mode 100644 index e2c1f34..0000000 --- a/packages/core/src/renderers/systems/shader/shader/setPrecision.js +++ /dev/null @@ -1,18 +0,0 @@ -/** - * Sets the float precision on the shader. If the precision is already present this function will do nothing - * @param {string} src the shader source - * @param {string} precision The float precision of the shader. Options are 'lowp', 'mediump' or 'highp'. - * - * @return {string} modified shader source - */ -export default function setPrecision(src, precision) -{ - src = src.trim(); - - if (src.substring(0, 9) !== 'precision' && src.substring(0, 1) !== '#') - { - return `precision ${precision} float;\n${src}`; - } - - return src; -} diff --git a/packages/core/src/renderers/systems/textures/GLTexture.js b/packages/core/src/renderers/systems/textures/GLTexture.js deleted file mode 100644 index c3fb8dc..0000000 --- a/packages/core/src/renderers/systems/textures/GLTexture.js +++ /dev/null @@ -1,33 +0,0 @@ -export default class GLTexture -{ - constructor(texture) - { - /** - * The WebGL texture - * - * @member {WebGLTexture} - */ - this.texture = texture; - - this.width = -1; - this.height = -1; - - /** - * Texture contents dirty flag - * @member {number} - */ - this.dirtyId = -1; - - /** - * Texture style dirty flag - * @type {number} - */ - this.dirtyStyleId = -1; - - /** - * Whether mip levels has to be generated - * @type {boolean} - */ - this.mipmap = false; - } -} diff --git a/packages/core/src/AbstractRenderer.js b/packages/core/src/AbstractRenderer.js new file mode 100644 index 0000000..64b2eb1 --- /dev/null +++ b/packages/core/src/AbstractRenderer.js @@ -0,0 +1,353 @@ +import { hex2string, hex2rgb } from '@pixi/utils'; +import { Matrix, Rectangle } from '@pixi/math'; +import { RENDERER_TYPE } from '@pixi/constants'; +import { settings } from '@pixi/settings'; +import { Container } from '@pixi/display'; +import RenderTexture from './renderTexture/RenderTexture'; +import EventEmitter from 'eventemitter3'; + +const tempMatrix = new Matrix(); + +/** + * 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 EventEmitter + * @memberof PIXI + */ +export default class AbstractRenderer extends EventEmitter +{ + // eslint-disable-next-line valid-jsdoc + /** + * @param {string} system - The name of the system this renderer is for. + * @param {object} [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.transparent=false] - If the render view is transparent, default false + * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false + * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. The + * resolution of the renderer retina would be 2. + * @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 {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when rendering, + * stopping pixel interpolation. + */ + constructor(system, options, arg2, arg3) + { + super(); + + // Support for constructor(system, screenWidth, screenHeight, options) + if (typeof options === 'number') + { + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + }, arg3); + } + + // 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 = RENDERER_TYPE.UNKNOWN; + + /** + * Measurements of the screen. (0, 0, screenWidth, screenHeight) + * + * Its safe to use as filterArea or hitArea for 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 1 + */ + this.resolution = options.resolution || settings.RESOLUTION; + + /** + * Whether the render view is transparent + * + * @member {boolean} + */ + this.transparent = options.transparent; + + /** + * Whether css dimensions of canvas view should be resized to screen dimensions automatically + * + * @member {boolean} + */ + this.autoResize = options.autoResize || false; + + /** + * Tracks the blend modes useful for this renderer. + * + * @member {object} + */ + this.blendModes = null; + + /** + * 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; + + /** + * If true PixiJS will Math.floor() x/y values when rendering, stopping pixel interpolation. + * Handy for crisp pixel art and speed on legacy devices. + * + * @member {boolean} + */ + this.roundPixels = options.roundPixels; + + /** + * The background color as a number. + * + * @member {number} + * @private + */ + this._backgroundColor = 0x000000; + + /** + * The background color as an [R, G, B] array. + * + * @member {number[]} + * @private + */ + this._backgroundColorRgba = [0, 0, 0, 0]; + + /** + * The background color as a string. + * + * @member {string} + * @private + */ + this._backgroundColorString = '#000000'; + + this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter + + /** + * This temporary display object used as the parent of the currently being rendered item + * + * @member {PIXI.DisplayObject} + * @private + */ + this._tempDisplayObjectParent = new Container(); + + /** + * The last root object that the renderer tried to render. + * + * @member {PIXI.DisplayObject} + * @private + */ + this._lastObjectRendered = this._tempDisplayObjectParent; + + /** + * Collection of plugins. + * @readonly + * @member {object} + */ + this.plugins = {}; + } + + /** + * Initialize the plugins. + * + * @protected + * @param {object} staticMap - The dictionary of staticly saved plugins. + */ + initPlugins(staticMap) + { + 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() + { + return this.view.width; + } + + /** + * Same as view.height, actual number of pixels in the canvas by vertical + * + * @member {number} + * @readonly + * @default 600 + */ + get height() + { + return this.view.height; + } + + /** + * Resizes the screen and canvas to the specified width and height + * Canvas dimensions are multiplied by resolution + * + * @param {number} screenWidth - the new width of the screen + * @param {number} screenHeight - the new height of the screen + */ + resize(screenWidth, screenHeight) + { + this.screen.width = screenWidth; + this.screen.height = screenHeight; + + this.view.width = screenWidth * this.resolution; + this.view.height = screenHeight * this.resolution; + + if (this.autoResize) + { + this.view.style.width = `${screenWidth}px`; + this.view.style.height = `${screenHeight}px`; + } + } + + /** + * 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 {PIXI.DisplayObject} displayObject - The displayObject the object will be generated from + * @param {number} scaleMode - Should be one of the scaleMode consts + * @param {number} resolution - The resolution / device pixel ratio of the texture being generated + * @param {PIXI.Rectangle} [region] - The region of the displayObject, that shall be rendered, + * if no region is specified, defaults to the local bounds of the displayObject. + * @return {PIXI.Texture} a texture of the graphics object + */ + generateTexture(displayObject, scaleMode, resolution, region) + { + region = region || displayObject.getLocalBounds(); + + // 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(region.width | 0, region.height | 0, scaleMode, resolution); + + tempMatrix.tx = -region.x; + tempMatrix.ty = -region.y; + + this.render(displayObject, renderTexture, false, tempMatrix, true); + + return renderTexture; + } + + /** + * Removes everything from the renderer and optionally removes the Canvas DOM element. + * + * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. + */ + destroy(removeView) + { + 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); + } + + this.plugins = null; + + this.type = RENDERER_TYPE.UNKNOWN; + + this.view = null; + + this.screen = null; + + this.resolution = 0; + + this.transparent = false; + + this.autoResize = false; + + this.blendModes = null; + + this.options = null; + + this.preserveDrawingBuffer = false; + this.clearBeforeRender = false; + + this.roundPixels = false; + + this._backgroundColor = 0; + this._backgroundColorRgba = null; + this._backgroundColorString = null; + + this._tempDisplayObjectParent = null; + this._lastObjectRendered = null; + } + + /** + * The background color to fill if not transparent + * + * @member {number} + */ + get backgroundColor() + { + return this._backgroundColor; + } + + set backgroundColor(value) // eslint-disable-line require-jsdoc + { + this._backgroundColor = value; + this._backgroundColorString = hex2string(value); + hex2rgb(value, this._backgroundColorRgba); + } +} diff --git a/packages/core/src/Renderer.js b/packages/core/src/Renderer.js new file mode 100644 index 0000000..2227e78 --- /dev/null +++ b/packages/core/src/Renderer.js @@ -0,0 +1,338 @@ +import AbstractRenderer from './AbstractRenderer'; +import { sayHello } from '@pixi/utils'; +import MaskSystem from './mask/MaskSystem'; +import StencilSystem from './mask/StencilSystem'; +import FilterSystem from './filters/FilterSystem'; +import FramebufferSystem from './framebuffer/FramebufferSystem'; +import RenderTextureSystem from './renderTexture/RenderTextureSystem'; +import TextureSystem from './textures/TextureSystem'; +import ProjectionSystem from './projection/ProjectionSystem'; +import StateSystem from './state/StateSystem'; +import GeometrySystem from './geometry/GeometrySystem'; +import ShaderSystem from './shader/ShaderSystem'; +import ContextSystem from './context/ContextSystem'; +import BatchSystem from './batch/BatchSystem'; +import TextureGCSystem from './textures/TextureGCSystem'; +import { RENDERER_TYPE } from '@pixi/constants'; +import UniformGroup from './shader/UniformGroup'; +import { Matrix } from '@pixi/math'; +import Runner from 'mini-runner'; + +/** + * 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 Render works by automatically managing webGLBatchs. + * 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 :) + * + * @class + * @memberof PIXI + * @extends PIXI.AbstractRenderer + */ +export default class Renderer extends AbstractRenderer +{ + // eslint-disable-next-line valid-jsdoc + /** + * + * @param {object} [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.transparent=false] - If the render view is transparent, default false + * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false + * @param {boolean} [options.antialias=false] - sets antialias. If not available natively then FXAA + * antialiasing is used + * @param {boolean} [options.forceFXAA=false] - forces FXAA antialiasing to be used over native. + * FXAA is faster, but may not always look as great + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. + * The resolution of the renderer retina would be 2. + * @param {boolean} [options.clearBeforeRender=true] - This sets if the renderer will clear + * the canvas or not before the new render pass. If you wish to set this to false, you *must* set + * preserveDrawingBuffer to `true`. + * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, + * enable this if you need to call toDataUrl on the webgl context. + * @param {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when + * rendering, stopping pixel interpolation. + * @param {number} [options.backgroundColor=0x000000] - The background color of the rendered area + * (shown if not transparent). + * @param {boolean} [options.legacy=false] - If true PixiJS will aim to ensure compatibility + * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. + * @param {string} [options.powerPreference] - Parameter passed to webgl context, set to "high-performance" + * for devices with dual graphics card + */ + constructor(options, arg2, arg3) + { + super('WebGL', options, arg2, arg3); + + /** + * The type of this renderer as a standardised const + * + * @member {number} + * @see PIXI.RENDERER_TYPE + */ + this.type = RENDERER_TYPE.WEBGL; + + // this will be set by the contextSystem (this.context) + this.gl = null; + this.CONTEXT_UID = 0; + this.legacy = !!options.legacy; + + // TODO legacy! + + // runners! + this.runners = { + destroy: new Runner('destroy'), + contextChange: new Runner('contextChange', 1), + reset: new Runner('reset'), + update: new Runner('update'), + postrender: new Runner('postrender'), + prerender: new Runner('prerender'), + resize: new Runner('resize', 2), + }; + + 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(GeometrySystem, 'geometry') + .addSystem(FramebufferSystem, 'framebuffer') + .addSystem(StencilSystem, 'stencil') + .addSystem(ProjectionSystem, 'projection') + .addSystem(TextureGCSystem, 'textureGC') + .addSystem(FilterSystem, 'filter') + .addSystem(RenderTextureSystem, 'renderTexture') + .addSystem(BatchSystem, 'batch'); + + this.initPlugins(Renderer.__plugins); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ + if (options.context) + { + this.context.initFromContext(options.context); + } + else + { + this.context.initFromOptions({ + alpha: this.transparent, + antialias: options.antialias, + premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', + stencil: true, + 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); + } + + /** + * Add a new system to the renderer. + * @param {class} ClassRef - Class reference + * @param {string} [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. + * @return {PIXI.Renderer} Return instance of renderer + */ + addSystem(ClassRef, name) + { + if (!name) + { + name = ClassRef.name; + } + + // TODO - read name from class.name.. + + /* + if(name.includes('System')) + { + name = name.replace('System', ''); + name = name.charAt(0).toLowerCase() + name.slice(1); + } + */ + + const system = new ClassRef(this); + + if (this[name]) + { + throw new Error(`Whoops! ${name} is already a manger`); + } + + this[name] = system; + + for (const i in this.runners) + { + this.runners[i].add(system); + } + + return this; + + /** + * 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. + */ + } + + /** + * Renders the object to its webGL view + * + * @param {PIXI.DisplayObject} displayObject - the object to be rendered + * @param {PIXI.RenderTexture} renderTexture - The render texture to render to. + * @param {boolean} [clear] - Should the canvas be cleared before the new render + * @param {PIXI.Transform} [transform] - A transform to apply to the render texture before rendering. + * @param {boolean} [skipUpdateTransform] - Should we skip the update transform pass? + */ + render(displayObject, renderTexture, clear, transform, skipUpdateTransform) + { + // can be handy to know! + this.renderingToScreen = !renderTexture; + + this.runners.prerender.run(); + this.emit('prerender'); + + // 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.parent; + + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = 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.run(); + + this.emit('postrender'); + } + + /** + * Resizes the webGL view to the specified width and height. + * + * @param {number} screenWidth - the new width of the screen + * @param {number} screenHeight - the new height of the screen + */ + resize(screenWidth, screenHeight) + { + AbstractRenderer.prototype.resize.call(this, screenWidth, screenHeight); + + this.runners.resize.run(screenWidth, screenHeight); + } + + /** + * Resets the WebGL state so you can render things however you fancy! + * + * @return {PIXI.Renderer} Returns itself. + */ + reset() + { + this.runners.reset.run(); + + return this; + } + + clear() + { + this.framebuffer.bind(); + this.framebuffer.clear(); + } + + /** + * Removes everything from the renderer (event listeners, spritebatch, etc...) + * + * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. + * See: https://github.com/pixijs/pixi.js/issues/2233 + */ + destroy(removeView) + { + this.runners.destroy.run(); + + // call base destroy + super.destroy(removeView); + + // TODO nullify all the managers.. + this.gl = null; + } + + /** + * 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 + * builds and excluding plugins. + * @name PIXI.Renderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. + */ + + /** + * Adds a plugin to the renderer. + * + * @method + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + static registerPlugin(pluginName, ctor) + { + Renderer.__plugins = Renderer.__plugins || {}; + Renderer.__plugins[pluginName] = ctor; + } +} diff --git a/packages/core/src/System.js b/packages/core/src/System.js new file mode 100644 index 0000000..05a10c8 --- /dev/null +++ b/packages/core/src/System.js @@ -0,0 +1,42 @@ +/** + * System is a base class used for extending systems used by the {@link PIXI.Renderer} + * @see PIXI.Renderer#addSystem + * @class + * @memberof PIXI + */ +export default class System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + /** + * The renderer this manager works for. + * + * @member {PIXI.Renderer} + */ + this.renderer = renderer; + + this.renderer.runners.contextChange.add(this); + } + + /** + * Generic method called when there is a WebGL context change. + * + */ + contextChange() + { + // do some codes init! + } + + /** + * Generic destroy methods to be overridden by the subclass + * + */ + destroy() + { + this.renderer.runners.contextChange.remove(this); + this.renderer = null; + } +} diff --git a/packages/core/src/batch/BatchSystem.js b/packages/core/src/batch/BatchSystem.js new file mode 100644 index 0000000..b04c249 --- /dev/null +++ b/packages/core/src/batch/BatchSystem.js @@ -0,0 +1,68 @@ +import System from '../System'; +import ObjectRenderer from './ObjectRenderer'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class BatchSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + super(renderer); + + /** + * An empty renderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.emptyRenderer = new ObjectRenderer(renderer); + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.currentRenderer = this.emptyRenderer; + } + + /** + * Changes the current renderer to the one given in parameter + * + * @param {PIXI.ObjectRenderer} objectRenderer - The object renderer to use. + */ + setObjectRenderer(objectRenderer) + { + if (this.currentRenderer === objectRenderer) + { + return; + } + + this.currentRenderer.stop(); + this.currentRenderer = objectRenderer; + + this.renderer.state.setState(objectRenderer.state); + + this.currentRenderer.start(); + } + + /** + * This should be called if you wish to do some custom rendering + * It will basically render anything that may be batched up such as sprites + * + */ + flush() + { + this.setObjectRenderer(this.emptyRenderer); + } + + reset() + { + this.setObjectRenderer(this.emptyRenderer); + } +} diff --git a/packages/core/src/batch/ObjectRenderer.js b/packages/core/src/batch/ObjectRenderer.js new file mode 100644 index 0000000..0c851a9 --- /dev/null +++ b/packages/core/src/batch/ObjectRenderer.js @@ -0,0 +1,48 @@ +import System from '../System'; + +/** + * Base for a common object renderer that can be used as a system renderer plugin. + * + * @class + * @extends PIXI.System + * @memberof PIXI + */ +export default class ObjectRenderer extends System +{ + /** + * Starts the renderer and sets the shader + * + */ + start() + { + // set the shader.. + } + + /** + * Stops the renderer + * + */ + stop() + { + this.flush(); + } + + /** + * Stub method for rendering content and emptying the current batch. + * + */ + flush() + { + // flush! + } + + /** + * Renders an object + * + * @param {PIXI.DisplayObject} object - The object to render. + */ + render(object) // eslint-disable-line no-unused-vars + { + // render the object + } +} diff --git a/packages/core/src/context/ContextSystem.js b/packages/core/src/context/ContextSystem.js new file mode 100644 index 0000000..301aab4 --- /dev/null +++ b/packages/core/src/context/ContextSystem.js @@ -0,0 +1,186 @@ +import System from '../System'; +import { settings } from '@pixi/settings'; + +let CONTEXT_UID = 0; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class ContextSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.webGLVersion = 1; + + this.handleContextLost = this.handleContextLost.bind(this); + this.handleContextRestored = this.handleContextRestored.bind(this); + + this.extensions = {}; + + renderer.view.addEventListener('webglcontextlost', this.handleContextLost, false); + renderer.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + } + + get isLost() + { + return (!this.gl || this.gl.isContextLost()); + } + + contextChange(gl) + { + this.gl = gl; + + // restore a context if it was previously lost + if (gl.isContextLost() && gl.getExtension('WEBGL_lose_context')) + { + gl.getExtension('WEBGL_lose_context').restoreContext(); + } + } + + initFromContext(gl) + { + this.gl = gl; + this.validateContext(gl); + this.renderer.gl = gl; + this.renderer.CONTEXT_UID = CONTEXT_UID++; + this.renderer.runners.contextChange.run(gl); + } + + initFromOptions(options) + { + const gl = this.createContext(this.renderer.view, options); + + this.initFromContext(gl); + } + + /** + * Helper class to create a webGL Context + * + * @class + * @memberof PIXI.glCore + * @param canvas {HTMLCanvasElement} the canvas element that we will get the context from + * @param options {Object} An options object that gets passed in to the canvas element containing the context attributes, + * see https://developer.mozilla.org/en/docs/Web/API/HTMLCanvasElement/getContext for the options available + * @return {WebGLRenderingContext} the WebGL context + */ + createContext(canvas, options) + { + let gl; + + if (settings.PREFER_WEBGL_2) + { + gl = canvas.getContext('webgl2', options); + } + + if (gl) + { + this.webGLVersion = 2; + } + else + { + this.webGLVersion = 1; + + gl = canvas.getContext('webgl', options) + || canvas.getContext('experimental-webgl', options); + + if (!gl) + { + // fail, not able to get a context + throw new Error('This browser does not support webGL. Try using the canvas renderer'); + } + } + + this.gl = gl; + + this.getExtensions(); + + return gl; + } + + getExtensions() + { + // time to set up default etensions that pixi uses.. + const gl = this.gl; + const extensions = this.extensions; + + if (this.webGLVersion === 1) + { + extensions.drawBuffers = gl.getExtension('WEBGL_draw_buffers'); + extensions.depthTexture = gl.getExtension('WEBKIT_WEBGL_depth_texture'); + extensions.floatTexture = gl.getExtension('OES_texture_float'); + extensions.loseContext = gl.getExtension('WEBGL_lose_context'); + + extensions.vertexArrayObject = gl.getExtension('OES_vertex_array_object') + || gl.getExtension('MOZ_OES_vertex_array_object') + || gl.getExtension('WEBKIT_OES_vertex_array_object'); + } + + // we don't use any specific WebGL 2 ones yet! + } + + /** + * Handles a lost webgl context + * + * @private + * @param {WebGLContextEvent} event - The context lost event. + */ + handleContextLost(event) + { + event.preventDefault(); + } + + /** + * Handles a restored webgl context + * + * @private + */ + handleContextRestored() + { + this.renderer.runners.contextChange.run(this.gl); + } + + destroy() + { + const view = this.renderer.view; + + // remove listeners + view.removeEventListener('webglcontextlost', this.handleContextLost); + view.removeEventListener('webglcontextrestored', this.handleContextRestored); + + this.gl.useProgram(null); + + if (this.extensions.loseContext) + { + this.extensions.loseContext.loseContext(); + } + } + + postrender() + { + this.gl.flush(); + } + + validateContext(gl) + { + const attributes = gl.getContextAttributes(); + + // this is going to be fairly simple for now.. but at least we have room to grow! + if (!attributes.stencil) + { + /* eslint-disable max-len */ + + /* eslint-disable no-console */ + console.warn('Provided WebGL context does not have a stencil buffer, masks may not render correctly'); + /* eslint-enable no-console */ + + /* eslint-enable max-len */ + } + } +} diff --git a/packages/core/src/filters/Filter.js b/packages/core/src/filters/Filter.js new file mode 100644 index 0000000..9d8cad8 --- /dev/null +++ b/packages/core/src/filters/Filter.js @@ -0,0 +1,140 @@ +import Shader from '../shader/Shader'; +import Program from '../shader/Program'; +import State from '../state/State'; +import { settings } from '@pixi/settings'; +// import extractUniformsFromSrc from './extractUniformsFromSrc'; +import defaultVertex from './defaultFilter.vert'; +import defaultFragment from './defaultFilter.frag'; + +// let math = require('../../math'); +/** + * @class + * @memberof PIXI + * @extends PIXI.Shader + */ +export default class Filter extends Shader +{ + /** + * @param {string} [vertexSrc] - The source of the vertex shader. + * @param {string} [fragmentSrc] - The source of the fragment shader. + * @param {object} [uniforms] - Custom uniforms to use to augment the built-in ones. + */ + constructor(vertexSrc, fragmentSrc, uniforms) + { + const program = Program.from(vertexSrc, fragmentSrc); + + super(program, uniforms); + + /** + * The padding of the filter. Some filters require extra space to breath such as a blur. + * Increasing this will add extra width and height to the bounds of the object that the + * filter is applied to. + * + * @member {number} + */ + this.padding = 0; + + /** + * The resolution of the filter. Setting this to be lower will lower the quality but + * increase the performance of the filter. + * + * @member {number} + */ + this.resolution = settings.RESOLUTION; + + /** + * If enabled is true the filter is applied, if false it will not. + * + * @member {boolean} + */ + this.enabled = true; + + /** + * If enabled, PixiJS will fit the filter area into boundaries for better performance. + * Switch it off if it does not work for specific shader. + * + * @member {boolean} + */ + this.autoFit = true; + + /** + * Legacy filters use position and uvs from attributes + * @member {boolean} + * @readonly + */ + this.legacy = !!this.program.attributeData.aTextureCoord; + + /** + * the webGL state the filter requires to render + * @member {PIXI.State} + */ + this.state = new State(); + } + + /** + * Applies the filter + * + * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + * @param {boolean} clear - Should the output be cleared before rendering to it + * @param {object} [currentState] - It's current state of filter. + * There are some useful properties in the currentState : + * target, filters, sourceFrame, destinationFrame, renderTarget, resolution + */ + apply(filterManager, input, output, clear, currentState, derp) // eslint-disable-line no-unused-vars + { + // do as you please! + + filterManager.applyFilter(this, input, output, clear, currentState, derp); + + // or just do a regular render.. + } + + /** + * Sets the blendmode of the filter + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL + */ + get blendMode() + { + return this.state.blendMode; + } + + set blendMode(value) // eslint-disable-line require-jsdoc + { + this.state.blendMode = value; + } + + /** + * The default vertex shader source + * + * @static + * @constant + */ + static get defaultVertexSrc() + { + return defaultVertex; + } + + /** + * The default fragment shader source + * + * @static + * @constant + */ + static get defaultFragmentSrc() + { + return defaultFragment; + } +} + +/** + * Used for caching shader IDs + * + * @static + * @private + */ +Filter.SOURCE_KEY_MAP = {}; + diff --git a/packages/core/src/filters/FilterSystem.js b/packages/core/src/filters/FilterSystem.js new file mode 100644 index 0000000..3174318 --- /dev/null +++ b/packages/core/src/filters/FilterSystem.js @@ -0,0 +1,431 @@ +import System from '../System'; + +import RenderTexture from '../renderTexture/RenderTexture'; +import Quad from '../utils/Quad'; +import QuadUv from '../utils/QuadUv'; +import { Rectangle } from '@pixi/math'; +import * as filterTransforms from './filterTransforms'; +import bitTwiddle from 'bit-twiddle'; +import UniformGroup from '../shader/UniformGroup'; +import { DRAW_MODES } from '@pixi/constants'; + +// +/** + * @ignore + * @class + */ +class FilterState +{ + /** + * + */ + constructor() + { + this.renderTexture = null; + this.sourceFrame = new Rectangle(); + this.destinationFrame = new Rectangle(); + this.filters = []; + this.target = null; + this.legacy = false; + this.resolution = 1; + } +} + +/** + * @class + * @memberof PIXI.systems + * @extends PIXI.System + */ +export default class FilterSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + /** + * stores a bunch of PO2 textures used for filtering + * @type {Object} + */ + this.texturePool = {}; + + /** + * a pool for storing filter states, save us creating new ones each tick + * @type {Array} + */ + this.statePool = []; + + /** + * A very simple geometry used when drawing a filter effect to the screen + * @type {Quad} + */ + this.quad = new Quad(); + + this.quadUv = new QuadUv(); + + /** + * Temporary rect for maths + * @type {PIXI.Rectangle} + */ + this.tempRect = new Rectangle(); + + this.activeState = {}; + + /** + * this uniform group is attached to filter uniforms when used + * @type {UniformGroup} + */ + this.globalUniforms = new UniformGroup({ + sourceFrame: this.tempRect, + destinationFrame: this.tempRect, + + // legacy variables + filterArea: new Float32Array(4), + filterClamp: new Float32Array(4), + }, true); + } + + /** + * Adds a new filter to the System. + * + * @param {PIXI.DisplayObject} target - The target of the filter to render. + * @param {PIXI.Filter[]} filters - The filters to apply. + */ + push(target, filters) + { + const renderer = this.renderer; + const filterStack = this.renderer.renderTexture.defaultFilterStack; + const state = this.statePool.pop() || new FilterState(); + + let resolution = filters[0].resolution; + let padding = filters[0].padding; + let autoFit = filters[0].autoFit; + let legacy = filters[0].legacy; + + for (let i = 1; i < filters.length; i++) + { + const filter = filters[i]; + + // lets use the lowest resolution.. + resolution = Math.min(resolution, filter.resolution); + // and the largest amount of padding! + padding = Math.max(padding, filter.padding); + // only auto fit if all filters are autofit + autoFit = autoFit || filter.autoFit; + + legacy = legacy || filter.legacy; + } + + filterStack.push(state); + + state.resolution = resolution; + + state.legacy = legacy; + + // round to whole number based on resolution + // TODO move that to the shader too? + state.sourceFrame = target.filterArea || target.getBounds(true); + + state.sourceFrame.pad(padding); + + if (autoFit) + { + state.sourceFrame.fit(this.renderer.renderTexture.destinationFrame); + } + + state.sourceFrame.round(resolution); + + state.renderTexture = this.getPotFilterTexture(state.sourceFrame.width, state.sourceFrame.height, resolution); + state.filters = filters; + + state.destinationFrame.width = state.renderTexture.width; + state.destinationFrame.height = state.renderTexture.height; + + state.renderTexture.filterFrame = state.sourceFrame; + renderer.renderTexture.bind(state.renderTexture, state.sourceFrame);// /, state.destinationFrame); + renderer.renderTexture.clear(); + } + + /** + * Pops off the filter and applies it. + * + */ + pop() + { + const renderer = this.renderer; + const filterStack = renderer.renderTexture.defaultFilterStack; + const state = filterStack.pop(); + const filters = state.filters; + + this.activeState = state; + + const globalUniforms = this.globalUniforms.uniforms; + + globalUniforms.sourceFrame = state.sourceFrame; + globalUniforms.destinationFrame = state.destinationFrame; + globalUniforms.resolution = state.resolution; + + // only update the rect if its legacy.. + if (state.legacy) + { + const filterArea = globalUniforms.filterArea; + const filterClamp = globalUniforms.filterClamp; + + filterArea[0] = state.destinationFrame.width; + filterArea[1] = state.destinationFrame.height; + filterArea[2] = state.sourceFrame.x; + filterArea[3] = state.sourceFrame.y; + + filterClamp[0] = 0.5 / state.resolution / state.destinationFrame.width; + filterClamp[1] = 0.5 / state.resolution / state.destinationFrame.height; + filterClamp[2] = (state.sourceFrame.width - 0.5) / state.resolution / state.destinationFrame.width; + filterClamp[3] = (state.sourceFrame.height - 0.5) / state.resolution / state.destinationFrame.height; + } + + this.globalUniforms.update(); + + const lastState = filterStack[filterStack.length - 1]; + + if (filters.length === 1) + { + filters[0].apply(this, state.renderTexture, lastState.renderTexture, false, state); + + this.returnFilterTexture(state.renderTexture); + } + else + { + let flip = state.renderTexture; + let flop = this.getPotFilterTexture( + flip.width, + flip.height, + state.resolution + ); + + flop.filterFrame = flip.filterFrame; + + let i = 0; + + for (i = 0; i < filters.length - 1; ++i) + { + filters[i].apply(this, flip, flop, true, state); + + const t = flip; + + flip = flop; + flop = t; + } + + filters[i].apply(this, flip, lastState.renderTexture, false, state); + + this.returnFilterTexture(flip); + this.returnFilterTexture(flop); + } + + this.statePool.push(state); + } + + /** + * Draws a filter. + * + * @param {PIXI.Filter} filter - The filter to draw. + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + * @param {boolean} clear - Should the output be cleared before rendering to it + */ + applyFilter(filter, input, output, clear) + { + const renderer = this.renderer; + + renderer.renderTexture.bind(output, output ? output.filterFrame : null); + + if (clear) + { + // gl.disable(gl.SCISSOR_TEST); + renderer.renderTexture.clear(); + // gl.enable(gl.SCISSOR_TEST); + } + + // set the uniforms.. + filter.uniforms.uSampler = input; + filter.uniforms.filterGlobals = this.globalUniforms; + + // TODO make it so that the order of this does not matter.. + // because it does at the moment cos of global uniforms. + // they need to get resynced + + renderer.state.setState(filter.state); + renderer.shader.bind(filter); + + if (filter.legacy) + { + this.quadUv.map(input._frame, input.filterFrame); + + renderer.geometry.bind(this.quadUv); + renderer.geometry.draw(DRAW_MODES.TRIANGLES); + } + else + { + renderer.geometry.bind(this.quad); + renderer.geometry.draw(DRAW_MODES.TRIANGLE_STRIP); + } + } + + /** + * Calculates the mapped matrix. + * + * TODO playing around here.. this is temporary - (will end up in the shader) + * this returns a matrix that will normalise map filter cords in the filter to screen space + * + * @param {PIXI.Matrix} outputMatrix - the matrix to output to. + * @return {PIXI.Matrix} The mapped matrix. + */ + calculateScreenSpaceMatrix(outputMatrix) + { + const currentState = this.activeState; + + return filterTransforms.calculateScreenSpaceMatrix( + outputMatrix, + currentState.sourceFrame, + currentState.destinationFrame + ); + } + + /** + * This will map the filter coord so that a texture can be used based on the transform of a sprite + * + * @param {PIXI.Matrix} outputMatrix - The matrix to output to. + * @param {PIXI.Sprite} sprite - The sprite to map to. + * @return {PIXI.Matrix} The mapped matrix. + */ + calculateSpriteMatrix(outputMatrix, sprite) + { + const currentState = this.activeState; + + return filterTransforms.calculateSpriteMatrix( + outputMatrix, + currentState.sourceFrame, + currentState.destinationFrame, + sprite + ); + } + + /** + * Destroys this Filter System. + * + * @param {boolean} [contextLost=false] context was lost, do not free shaders + * + */ + destroy(contextLost = false) + { + if (!contextLost) + { + this.emptyPool(); + } + else + { + this.texturePool = {}; + } + } + + /** + * Gets a Power-of-Two render texture. + * + * TODO move to a seperate class could be on renderer? + * also - could cause issue with multiple contexts? + * + * @private + * @param {WebGLRenderingContext} gl - The webgl rendering context + * @param {number} minWidth - The minimum width of the render target. + * @param {number} minHeight - The minimum height of the render target. + * @param {number} resolution - The resolution of the render target. + * @return {PIXI.RenderTarget} The new render target. + */ + getPotFilterTexture(minWidth, minHeight, resolution) + { + minWidth = bitTwiddle.nextPow2(minWidth); + minHeight = bitTwiddle.nextPow2(minHeight); + resolution = resolution || 1; + + const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + + if (!this.texturePool[key]) + { + this.texturePool[key] = []; + } + + let renderTexture = this.texturePool[key].pop(); + + if (!renderTexture) + { + // temporary bypass cache.. + // internally - this will cause a texture to be bound.. + renderTexture = RenderTexture.create({ + width: minWidth, + height: minHeight, + resolution, + }); + } + + return renderTexture; + } + + /** + * Gets extra render texture to use inside current filter + * + * @param {number} resolution resolution of the renderTexture + * @returns {PIXI.RenderTexture} + */ + getFilterTexture(resolution) + { + const rt = this.activeState.renderTexture; + + const filterTexture = this.getPotFilterTexture(rt.width, rt.height, resolution || rt.baseTexture.resolution); + + filterTexture.filterFrame = rt.filterFrame; + + return filterTexture; + } + + /** + * Frees a render texture back into the pool. + * + * @param {PIXI.RenderTarget} renderTexture - The renderTarget to free + */ + returnFilterTexture(renderTexture) + { + renderTexture.filterFrame = null; + + const base = renderTexture.baseTexture; + + const minWidth = base.width; + const minHeight = base.height; + + const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + + this.texturePool[key].push(renderTexture); + } + + /** + * Empties the texture pool. + * + */ + emptyPool() + { + for (const i in this.texturePool) + { + const textures = this.texturePool[i]; + + if (textures) + { + for (let j = 0; j < textures.length; j++) + { + textures[j].destroy(true); + } + } + } + + this.texturePool = {}; + } +} diff --git a/packages/core/src/filters/defaultFilter.frag b/packages/core/src/filters/defaultFilter.frag new file mode 100644 index 0000000..a14c1be --- /dev/null +++ b/packages/core/src/filters/defaultFilter.frag @@ -0,0 +1,22 @@ +varying vec2 vTextureCoord; +varying vec2 vFilterCoord; + +uniform sampler2D uSampler; +uniform sampler2D filterSampler; + +void main(void){ + vec4 masky = texture2D(filterSampler, vFilterCoord); + vec4 sample = texture2D(uSampler, vTextureCoord); + vec4 color; + if(mod(vFilterCoord.x, 1.0) > 0.5) + { + color = vec4(1.0, 0.0, 0.0, 1.0); + } + else + { + color = vec4(0.0, 1.0, 0.0, 1.0); + } + // gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0); + gl_FragColor = mix(sample, masky, 0.5); + gl_FragColor *= sample.a; +} diff --git a/packages/core/src/filters/defaultFilter.vert b/packages/core/src/filters/defaultFilter.vert new file mode 100644 index 0000000..e1febca --- /dev/null +++ b/packages/core/src/filters/defaultFilter.vert @@ -0,0 +1,14 @@ +attribute vec2 aVertexPosition; +attribute vec2 aTextureCoord; + +uniform mat3 projectionMatrix; +uniform mat3 filterMatrix; + +varying vec2 vTextureCoord; +varying vec2 vFilterCoord; + +void main(void){ + gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); + vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy; + vTextureCoord = aTextureCoord ; +} diff --git a/packages/core/src/filters/filterTransforms.js b/packages/core/src/filters/filterTransforms.js new file mode 100644 index 0000000..87a3cd6 --- /dev/null +++ b/packages/core/src/filters/filterTransforms.js @@ -0,0 +1,50 @@ +import { Matrix } from '@pixi/math'; + +/** + * Calculates the mapped matrix + * @param filterArea {Rectangle} The filter area + * @param sprite {Sprite} the target sprite + * @param outputMatrix {Matrix} @alvin + * @private + */ +// this returns a matrix that will normalise map filter cords in the filter to screen space +export function calculateScreenSpaceMatrix(outputMatrix, filterArea, textureSize) +{ + // TODO unwrap? + const mappedMatrix = outputMatrix.identity(); + + mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); + + mappedMatrix.scale(textureSize.width, textureSize.height); + + return mappedMatrix; +} + +export function calculateNormalizedScreenSpaceMatrix(outputMatrix, filterArea, textureSize) +{ + const mappedMatrix = outputMatrix.identity(); + + mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); + + const translateScaleX = (textureSize.width / filterArea.width); + const translateScaleY = (textureSize.height / filterArea.height); + + mappedMatrix.scale(translateScaleX, translateScaleY); + + return mappedMatrix; +} + +// this will map the filter coord so that a texture can be used based on the transform of a sprite +export function calculateSpriteMatrix(outputMatrix, filterArea, textureSize, sprite) +{ + const orig = sprite._texture.orig; + const mappedMatrix = outputMatrix.set(textureSize.width, 0, 0, textureSize.height, filterArea.x, filterArea.y); + const worldTransform = sprite.worldTransform.copyTo(Matrix.TEMP_MATRIX); + + worldTransform.invert(); + mappedMatrix.prepend(worldTransform); + mappedMatrix.scale(1.0 / orig.width, 1.0 / orig.height); + mappedMatrix.translate(sprite.anchor.x, sprite.anchor.y); + + return mappedMatrix; +} diff --git a/packages/core/src/filters/spriteMask/SpriteMaskFilter.js b/packages/core/src/filters/spriteMask/SpriteMaskFilter.js new file mode 100644 index 0000000..91697b7 --- /dev/null +++ b/packages/core/src/filters/spriteMask/SpriteMaskFilter.js @@ -0,0 +1,63 @@ +import Filter from '../Filter'; +import { Matrix } from '@pixi/math'; +import vertex from './spriteMaskFilter.vert'; +import fragment from './spriteMaskFilter.frag'; +import { default as TextureMatrix } from '../../textures/TextureMatrix'; + +/** + * The SpriteMaskFilter class + * + * @class + * @extends PIXI.Filter + * @memberof PIXI + */ +export default class SpriteMaskFilter extends Filter +{ + /** + * @param {PIXI.Sprite} sprite - the target sprite + */ + constructor(sprite) + { + const maskMatrix = new Matrix(); + + super(vertex, fragment); + + sprite.renderable = false; + + this.maskSprite = sprite; + this.maskMatrix = maskMatrix; + } + + /** + * Applies the filter + * + * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + */ + apply(filterManager, input, output) + { + const maskSprite = this.maskSprite; + const tex = this.maskSprite.texture; + + if (!tex.valid) + { + return; + } + if (!tex.transform) + { + // margin = 0.0, let it bleed a bit, shader code becomes easier + // assuming that atlas textures were made with 1-pixel padding + tex.transform = new TextureMatrix(tex, 0.0); + } + tex.transform.update(); + + this.uniforms.mask = tex; + this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite) + .prepend(tex.transform.mapCoord); + this.uniforms.alpha = maskSprite.worldAlpha; + this.uniforms.maskClamp = tex.transform.uClampFrame; + + filterManager.applyFilter(this, input, output); + } +} diff --git a/packages/core/src/filters/spriteMask/spriteMaskFilter.frag b/packages/core/src/filters/spriteMask/spriteMaskFilter.frag new file mode 100644 index 0000000..0e4aef8 --- /dev/null +++ b/packages/core/src/filters/spriteMask/spriteMaskFilter.frag @@ -0,0 +1,23 @@ +varying vec2 vMaskCoord; +varying vec2 vTextureCoord; + +uniform sampler2D uSampler; +uniform sampler2D mask; +uniform float alpha; +uniform vec4 maskClamp; + +void main(void) +{ + float clip = step(3.5, + step(maskClamp.x, vMaskCoord.x) + + step(maskClamp.y, vMaskCoord.y) + + step(vMaskCoord.x, maskClamp.z) + + step(vMaskCoord.y, maskClamp.w)); + + vec4 original = texture2D(uSampler, vTextureCoord); + vec4 masky = texture2D(mask, vMaskCoord); + + original *= (masky.r * masky.a * alpha * clip); + + gl_FragColor = original; +} diff --git a/packages/core/src/filters/spriteMask/spriteMaskFilter.vert b/packages/core/src/filters/spriteMask/spriteMaskFilter.vert new file mode 100644 index 0000000..d23a30c --- /dev/null +++ b/packages/core/src/filters/spriteMask/spriteMaskFilter.vert @@ -0,0 +1,16 @@ +attribute vec2 aVertexPosition; +attribute vec2 aTextureCoord; + +uniform mat3 projectionMatrix; +uniform mat3 otherMatrix; + +varying vec2 vMaskCoord; +varying vec2 vTextureCoord; + +void main(void) +{ + gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); + + vTextureCoord = aTextureCoord; + vMaskCoord = ( otherMatrix * vec3( aTextureCoord, 1.0) ).xy; +} diff --git a/packages/core/src/framebuffer/FrameBuffer.js b/packages/core/src/framebuffer/FrameBuffer.js new file mode 100644 index 0000000..84e9fcc --- /dev/null +++ b/packages/core/src/framebuffer/FrameBuffer.js @@ -0,0 +1,106 @@ +import Texture from '../textures/BaseTexture'; +import { FORMATS, TYPES } from '@pixi/constants'; + +/** + * Frame buffer + * @class + * @memberof PIXI + */ +export default class FrameBuffer +{ + constructor(width, height) + { + this.width = width || 100; + this.height = height || 100; + + this.stencil = false; + this.depth = false; + + this.dirtyId = 0; + this.dirtyFormat = 0; + this.dirtySize = 0; + + this.depthTexture = null; + this.colorTextures = []; + + this.glFrameBuffers = {}; + } + + get colorTexture() + { + return this.colorTextures[0]; + } + + addColorTexture(index, texture) + { + // TODO add some validation to the texture - same width / height etc? + this.colorTextures[index || 0] = texture || new Texture(null, { scaleMode: 0, + resolution: 1, + mipmap: false, + width: this.width, + height: this.height });// || new Texture(); + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + addDepthTexture(texture) + { + /* eslint-disable max-len */ + this.depthTexture = texture || new Texture(null, { scaleMode: 0, + resolution: 1, + width: this.width, + height: this.height, + mipmap: false, + format: FORMATS.DEPTH_COMPONENT, + type: TYPES.UNSIGNED_SHORT });// UNSIGNED_SHORT; + /* eslint-disable max-len */ + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + enableDepth() + { + this.depth = true; + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + enableStencil() + { + this.stencil = true; + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + resize(width, height) + { + if (width === this.width && height === this.height) return; + + this.width = width; + this.height = height; + + this.dirtyId++; + this.dirtySize++; + + for (let i = 0; i < this.colorTextures.length; i++) + { + this.colorTextures[i].setSize(width, height); + } + + if (this.depthTexture) + { + this.depthTexture.setSize(width, height); + } + } +} diff --git a/packages/core/src/framebuffer/FramebufferSystem.js b/packages/core/src/framebuffer/FramebufferSystem.js new file mode 100644 index 0000000..2cc2437 --- /dev/null +++ b/packages/core/src/framebuffer/FramebufferSystem.js @@ -0,0 +1,246 @@ +import System from '../System'; +import { Rectangle } from '@pixi/math'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class FramebufferSystem extends System +{ + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + contextChange() + { + this.gl = this.renderer.gl; + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + this.current = null; + this.viewport = new Rectangle(); + + this.drawBufferExtension = this.renderer.context.extensions.drawBuffers; + } + + bind(framebuffer, frame) + { + const gl = this.gl; + + this.current = framebuffer; + + if (framebuffer) + { + // TODO cacheing layer! + + const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID] || this.initFramebuffer(framebuffer); + + gl.bindFramebuffer(gl.FRAMEBUFFER, fbo.framebuffer); + // makesure all textures are unbound.. + + // now check for updates... + if (fbo.dirtyId !== framebuffer.dirtyId) + { + fbo.dirtyId = framebuffer.dirtyId; + + if (fbo.dirtyFormat !== framebuffer.dirtyFormat) + { + fbo.dirtyFormat = framebuffer.dirtyFormat; + this.updateFramebuffer(framebuffer); + } + else if (fbo.dirtySize !== framebuffer.dirtySize) + { + fbo.dirtySize = framebuffer.dirtySize; + this.resizeFramebuffer(framebuffer); + } + } + + for (let i = 0; i < framebuffer.colorTextures.length; i++) + { + if (framebuffer.colorTextures[i].texturePart) + { + this.renderer.texture.unbind(framebuffer.colorTextures[i].texture); + } + else + { + this.renderer.texture.unbind(framebuffer.colorTextures[i]); + } + } + + if (framebuffer.depthTexture) + { + this.renderer.texture.unbind(framebuffer.depthTexture); + } + + if (frame) + { + this.setViewport(frame.x, frame.y, frame.width, frame.height); + } + else + { + this.setViewport(0, 0, framebuffer.width, framebuffer.height); + } + } + else + { + gl.bindFramebuffer(gl.FRAMEBUFFER, null); + + if (frame) + { + this.setViewport(frame.x, frame.y, frame.width, frame.height); + } + else + { + this.setViewport(0, 0, this.renderer.width, this.renderer.height); + } + } + } + + setViewport(x, y, width, height) + { + const v = this.viewport; + + if (v.width !== width || v.height !== height || v.x !== x || v.y !== y) + { + v.x = x; + v.y = y; + v.width = width; + v.height = height; + + this.gl.viewport(x, y, width, height); + } + } + + get size() + { + if (this.current) + { + // TODO store temp + return { x: 0, y: 0, width: this.current.width, height: this.current.height }; + } + + return { x: 0, y: 0, width: this.renderer.width, height: this.renderer.height }; + } + + clear(r, g, b, a) + { + const gl = this.gl; + + // TODO clear color can be set only one right? + gl.clearColor(r, g, b, a); + gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); + } + + // private functions... + + initFramebuffer(framebuffer) + { + const gl = this.gl; + + // TODO - make this a class? + const fbo = { + framebuffer: gl.createFramebuffer(), + stencil: null, + dirtyId: 0, + dirtyFormat: 0, + dirtySize: 0, + }; + + framebuffer.glFrameBuffers[this.CONTEXT_UID] = fbo; + + return fbo; + } + + resizeFramebuffer(framebuffer) + { + const gl = this.gl; + + if (framebuffer.stencil || framebuffer.depth) + { + gl.bindRenderbuffer(gl.RENDERBUFFER, this.stencil); + gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); + } + } + + updateFramebuffer(framebuffer) + { + const gl = this.gl; + + const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID]; + + // bind the color texture + const colorTextures = framebuffer.colorTextures; + + let count = colorTextures.length; + + if (!this.drawBufferExtension) + { + count = Math.min(count, 1); + } + + const activeTextures = []; + + for (let i = 0; i < count; i++) + { + const texture = framebuffer.colorTextures[i]; + + if (texture.texturePart) + { + this.renderer.texture.bind(texture.texture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.COLOR_ATTACHMENT0 + i, + gl.TEXTURE_CUBE_MAP_NEGATIVE_X + texture.side, + texture.texture._glTextures[this.CONTEXT_UID].texture, + 0); + } + else + { + this.renderer.texture.bind(texture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.COLOR_ATTACHMENT0 + i, + gl.TEXTURE_2D, + texture._glTextures[this.CONTEXT_UID].texture, + 0); + } + + activeTextures.push(gl.COLOR_ATTACHMENT0 + i); + } + + if (this.drawBufferExtension && activeTextures.length > 1) + { + this.drawBufferExtension.drawBuffersWEBGL(activeTextures); + } + + if (framebuffer.depthTexture) + { + const depthTextureExt = this.renderer.context.extensions.depthTexture; + + if (depthTextureExt) + { + const depthTexture = framebuffer.depthTexture; + + this.renderer.texture.bind(depthTexture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.DEPTH_ATTACHMENT, + gl.TEXTURE_2D, + depthTexture._glTextures[this.CONTEXT_UID].texture, + 0); + } + } + + if (framebuffer.stencil || framebuffer.depth) + { + fbo.stencil = gl.createRenderbuffer(); + + gl.bindRenderbuffer(gl.RENDERBUFFER, fbo.stencil); + + // TODO.. this is depth AND stencil? + gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, fbo.stencil); + gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); + // fbo.enableStencil(); + } + } +} diff --git a/packages/core/src/geometry/GLBuffer.js b/packages/core/src/geometry/GLBuffer.js new file mode 100644 index 0000000..0477e8a --- /dev/null +++ b/packages/core/src/geometry/GLBuffer.js @@ -0,0 +1,9 @@ +export default class GLBuffer +{ + constructor(buffer) + { + this.buffer = buffer; + this.updateID = -1; + this.byteLength = -1; + } +} diff --git a/packages/core/src/geometry/GeometrySystem.js b/packages/core/src/geometry/GeometrySystem.js new file mode 100644 index 0000000..959f827 --- /dev/null +++ b/packages/core/src/geometry/GeometrySystem.js @@ -0,0 +1,388 @@ +import System from '../System'; +import GLBuffer from './GLBuffer'; + +const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class GeometrySystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this._activeGeometry = null; + this._activeVao = null; + + this.hasVao = true; + this.hasInstance = true; + } + + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + contextChange() + { + const gl = this.gl = this.renderer.gl; + + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + + // webgl2 + if (!gl.createVertexArray) + { + // webgl 1! + let nativeVaoExtension = this.renderer.context.extensions.vertexArrayObject; + + if (this.renderer.legacy) + { + nativeVaoExtension = null; + } + + if (nativeVaoExtension) + { + gl.createVertexArray = () => + nativeVaoExtension.createVertexArrayOES(); + + gl.bindVertexArray = (vao) => + nativeVaoExtension.bindVertexArrayOES(vao); + + gl.deleteVertexArray = (vao) => + nativeVaoExtension.deleteVertexArrayOES(vao); + } + else + { + this.hasVao = false; + gl.createVertexArray = () => + { + // empty + }; + + gl.bindVertexArray = () => + { + // empty + }; + + gl.deleteVertexArray = () => + { + // empty + }; + } + } + + if (!gl.vertexAttribDivisor) + { + const instanceExt = gl.getExtension('ANGLE_instanced_arrays'); + + if (instanceExt) + { + gl.vertexAttribDivisor = (a, b) => + instanceExt.vertexAttribDivisorANGLE(a, b); + + gl.drawElementsInstanced = (a, b, c, d, e) => + instanceExt.drawElementsInstancedANGLE(a, b, c, d, e); + + gl.drawArraysInstanced = (a, b, c, d) => + instanceExt.drawArraysInstancedANGLE(a, b, c, d); + } + else + { + this.hasInstance = false; + } + } + } + + /** + * Binds geometry so that is can be drawn. Creating a Vao if required + * @private + * @param {PIXI.Geometry} geometry instance of geometry to bind + */ + bind(geometry, shader) + { + shader = shader || this.renderer.shader.shader; + + const gl = this.gl; + + // not sure the best way to address this.. + // currently different shaders require different VAOs for the same geometry + // Still mulling over the best way to solve this one.. + // will likely need to modify the shader attribute locations at run time! + let vaos = geometry.glVertexArrayObjects[this.CONTEXT_UID]; + + if (!vaos) + { + geometry.glVertexArrayObjects[this.CONTEXT_UID] = vaos = {}; + } + + const vao = vaos[shader.program.id] || this.initGeometryVao(geometry, shader.program); + + this._activeGeometry = geometry; + + if (this._activeVao !== vao) + { + this._activeVao = vao; + + if (this.hasVao) + { + gl.bindVertexArray(vao); + } + else + { + this.activateVao(geometry, shader.program); + } + } + + // TODO - optimise later! + // don't need to loop through if nothing changed! + // maybe look to add an 'autoupdate' to geometry? + this.updateBuffers(); + } + + reset() + { + this.unbind(); + } + + updateBuffers() + { + const geometry = this._activeGeometry; + const gl = this.gl; + + for (let i = 0; i < geometry.buffers.length; i++) + { + const buffer = geometry.buffers[i]; + + const glBuffer = buffer._glBuffers[this.CONTEXT_UID]; + + if (buffer._updateID !== glBuffer.updateID) + { + glBuffer.updateID = buffer._updateID; + + // TODO can cache this on buffer! maybe added a getter / setter? + const type = buffer.index ? gl.ELEMENT_ARRAY_BUFFER : gl.ARRAY_BUFFER; + const drawType = buffer.static ? gl.STATIC_DRAW : gl.DYNAMIC_DRAW; + + gl.bindBuffer(type, glBuffer.buffer); + + if (glBuffer.byteLength >= buffer.data.byteLength) + { + // offset is always zero for now! + gl.bufferSubData(type, 0, buffer.data); + } + else + { + gl.bufferData(type, buffer.data, drawType); + } + } + } + } + + checkCompatability(geometry, program) + { + // geometry must have at least all the attributes that the shader requires. + const geometryAttributes = geometry.attributes; + const shaderAttributes = program.attributeData; + + for (const j in shaderAttributes) + { + if (!geometryAttributes[j]) + { + throw new Error(`shader and geometry incompatible, geometry missing the "${j}" attribute`); + } + } + } + + /** + * Creates a Vao with the same structure as the geometry and stores it on the geometry. + * @private + * @param {PIXI.Geometry} geometry instance of geometry to to generate Vao for + */ + initGeometryVao(geometry, program) + { + this.checkCompatability(geometry, program); + + const gl = this.gl; + const CONTEXT_UID = this.CONTEXT_UID; + const buffers = geometry.buffers; + const attributes = geometry.attributes; + + const tempStride = {}; + const tempStart = {}; + + for (const j in buffers) + { + tempStride[j] = 0; + tempStart[j] = 0; + } + + for (const j in attributes) + { + if (!attributes[j].size && program.attributeData[j]) + { + attributes[j].size = program.attributeData[j].size; + } + + tempStride[attributes[j].buffer] += attributes[j].size * byteSizeMap[attributes[j].type]; + } + + for (const j in attributes) + { + const attribute = attributes[j]; + const attribSize = attribute.size; + + if (attribute.stride === undefined) + { + if (tempStride[attribute.buffer] === attribSize * byteSizeMap[attribute.type]) + { + attribute.stride = 0; + } + else + { + attribute.stride = tempStride[attribute.buffer]; + } + } + + if (attribute.start === undefined) + { + attribute.start = tempStart[attribute.buffer]; + + tempStart[attribute.buffer] += attribSize * byteSizeMap[attribute.type]; + } + } + + // first update - and create the buffers! + // only create a gl buffer if it actually gets + for (let i = 0; i < buffers.length; i++) + { + const buffer = buffers[i]; + + if (!buffer._glBuffers[CONTEXT_UID]) + { + buffer._glBuffers[CONTEXT_UID] = new GLBuffer(gl.createBuffer()); + } + } + + // TODO - maybe make this a data object? + // lets wait to see if we need to first! + const vao = gl.createVertexArray(); + + gl.bindVertexArray(vao); + + this.activateVao(geometry, program); + + gl.bindVertexArray(null); + + // add it to the cache! + geometry.glVertexArrayObjects[this.CONTEXT_UID][program.id] = vao; + + return vao; + } + + activateVao(geometry, program) + { + const gl = this.gl; + const CONTEXT_UID = this.CONTEXT_UID; + const buffers = geometry.buffers; + const attributes = geometry.attributes; + + if (geometry.indexBuffer) + { + // first update the index buffer if we have one.. + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, geometry.indexBuffer._glBuffers[CONTEXT_UID].buffer); + } + + let lastBuffer = null; + + // add a new one! + for (const j in attributes) + { + const attribute = attributes[j]; + const buffer = buffers[attribute.buffer]; + const glBuffer = buffer._glBuffers[CONTEXT_UID]; + + if (program.attributeData[j]) + { + if (lastBuffer !== glBuffer) + { + gl.bindBuffer(gl.ARRAY_BUFFER, glBuffer.buffer); + + lastBuffer = glBuffer; + } + + const location = program.attributeData[j].location; + + // TODO introduce state again + // we can optimise this for older devices that have no VAOs + gl.enableVertexAttribArray(location); + + gl.vertexAttribPointer(location, + attribute.size, + attribute.type || gl.FLOAT, + attribute.normalized, + attribute.stride, + attribute.start); + + if (attribute.instance) + { + // TODO calculate instance count based of this... + if (this.hasInstance) + { + gl.vertexAttribDivisor(location, 1); + } + else + { + throw new Error('geometry error, GPU Instancing is not supported on this device'); + } + } + } + } + } + + draw(type, size, start, instanceCount) + { + const gl = this.gl; + const geometry = this._activeGeometry; + + // TODO.. this should not change so maybe cache the function? + + if (geometry.indexBuffer) + { + if (geometry.instanced) + { + /* eslint-disable max-len */ + gl.drawElementsInstanced(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2, instanceCount || 1); + /* eslint-enable max-len */ + } + else + { + gl.drawElements(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2); + } + } + else + if (geometry.instanced) + { + // TODO need a better way to calculate size.. + gl.drawArraysInstanced(type, start, size || geometry.getSize(), instanceCount || 1); + } + else + { + gl.drawArrays(type, start, size || geometry.getSize()); + } + + return this; + } + + unbind() + { + this.gl.bindVertexArray(null); + this._activeVao = null; + this._activeGeometry = null; + } +} diff --git a/packages/core/src/geometry/utils/setVertexAttribArrays.js b/packages/core/src/geometry/utils/setVertexAttribArrays.js new file mode 100644 index 0000000..23b6959 --- /dev/null +++ b/packages/core/src/geometry/utils/setVertexAttribArrays.js @@ -0,0 +1,54 @@ +// var GL_MAP = {}; + +/** + * @param gl {WebGLRenderingContext} The current WebGL context + * @param attribs {*} + * @param state {*} + */ +export default function setVertexAttribArrays(gl, attribs, state) +{ + let i; + + if (state) + { + const tempAttribState = state.tempAttribState; + const attribState = state.attribState; + + for (i = 0; i < tempAttribState.length; i++) + { + tempAttribState[i] = false; + } + + // set the new attribs + for (i = 0; i < attribs.length; i++) + { + tempAttribState[attribs[i].attribute.location] = true; + } + + for (i = 0; i < attribState.length; i++) + { + if (attribState[i] !== tempAttribState[i]) + { + attribState[i] = tempAttribState[i]; + + if (state.attribState[i]) + { + gl.enableVertexAttribArray(i); + } + else + { + gl.disableVertexAttribArray(i); + } + } + } + } + else + { + for (i = 0; i < attribs.length; i++) + { + const attrib = attribs[i]; + + gl.enableVertexAttribArray(attrib.attribute.location); + } + } +} diff --git a/packages/core/src/index.js b/packages/core/src/index.js index 58c456a..fd1f547 100644 --- a/packages/core/src/index.js +++ b/packages/core/src/index.js @@ -1,30 +1,30 @@ import * as resources from './textures/resources'; -import * as systems from './renderers/systems'; +import * as systems from './systems'; -export { resources }; export { systems }; +export { resources }; -export { default as Renderer } from './renderers/Renderer'; -export { default as AbstractRenderer } from './renderers/AbstractRenderer'; -export { default as FrameBuffer } from './textures/FrameBuffer'; +export { default as System } from './System'; +export { default as Renderer } from './Renderer'; +export { default as AbstractRenderer } from './AbstractRenderer'; +export { default as FrameBuffer } from './framebuffer/FrameBuffer'; export { default as CubeTexture } from './textures/CubeTexture'; export { default as BaseTexture } from './textures/BaseTexture'; export { default as Texture } from './textures/Texture'; export { default as TextureMatrix } from './textures/TextureMatrix'; -export { default as RenderTexture } from './textures/RenderTexture'; -export { default as BaseRenderTexture } from './textures/BaseRenderTexture'; +export { default as RenderTexture } from './renderTexture/RenderTexture'; +export { default as BaseRenderTexture } from './renderTexture/BaseRenderTexture'; export { default as TextureUvs } from './textures/TextureUvs'; -export { default as State } from './renderers/State'; -export { default as ObjectRenderer } from './renderers/utils/ObjectRenderer'; -export { default as Quad } from './renderers/utils/Quad'; -export { default as QuadUv } from './renderers/utils/QuadUv'; -export { default as checkMaxIfStatmentsInShader } from './renderers/utils/checkMaxIfStatmentsInShader'; +export { default as State } from './state/State'; +export { default as ObjectRenderer } from './batch/ObjectRenderer'; +export { default as Quad } from './utils/Quad'; +export { default as QuadUv } from './utils/QuadUv'; +export { default as checkMaxIfStatmentsInShader } from './shader/utils/checkMaxIfStatmentsInShader'; export { default as Shader } from './shader/Shader'; export { default as Program } from './shader/Program'; export { default as UniformGroup } from './shader/UniformGroup'; -export { default as SpriteMaskFilter } from './renderers/filters/spriteMask/SpriteMaskFilter'; -export { default as Filter } from './renderers/filters/Filter'; +export { default as SpriteMaskFilter } from './filters/spriteMask/SpriteMaskFilter'; +export { default as Filter } from './filters/Filter'; export { default as Attribute } from './geometry/Attribute'; export { default as Buffer } from './geometry/Buffer'; export { default as Geometry } from './geometry/Geometry'; - diff --git a/packages/core/src/mask/MaskSystem.js b/packages/core/src/mask/MaskSystem.js new file mode 100644 index 0000000..99174c2 --- /dev/null +++ b/packages/core/src/mask/MaskSystem.js @@ -0,0 +1,199 @@ +import System from '../System'; +import AlphaMaskFilter from '../filters/spriteMask/SpriteMaskFilter'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class MaskSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + // TODO - we don't need both! + this.scissor = false; + this.scissorData = null; + this.scissorRenderTarget = null; + + this.enableScissor = false; + + this.alphaMaskPool = []; + this.alphaMaskIndex = 0; + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.DisplayObject} target - Display Object to push the mask to + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + push(target, maskData) + { + // TODO the root check means scissor rect will not + // be used on render textures more info here: + // https://github.com/pixijs/pixi.js/pull/3545 + + if (maskData.texture) + { + this.pushSpriteMask(target, maskData); + } + else if (this.enableScissor + && !this.scissor + && this.renderer._activeRenderTarget.root + && !this.renderer.stencil.stencilMaskStack.length + && maskData.isFastRect()) + { + const matrix = maskData.worldTransform; + + let rot = Math.atan2(matrix.b, matrix.a); + + // use the nearest degree! + rot = Math.round(rot * (180 / Math.PI)); + + if (rot % 90) + { + this.pushStencilMask(maskData); + } + else + { + this.pushScissorMask(target, maskData); + } + } + else + { + this.pushStencilMask(maskData); + } + } + + /** + * Removes the last mask from the mask stack and doesn't return it. + * + * @param {PIXI.DisplayObject} target - Display Object to pop the mask from + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + pop(target, maskData) + { + if (maskData.texture) + { + this.popSpriteMask(target, maskData); + } + else if (this.enableScissor && !this.renderer.stencil.stencilMaskStack.length) + { + this.popScissorMask(target, maskData); + } + else + { + this.popStencilMask(target, maskData); + } + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.RenderTarget} target - Display Object to push the sprite mask to + * @param {PIXI.Sprite} maskData - Sprite to be used as the mask + */ + pushSpriteMask(target, maskData) + { + let alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; + + if (!alphaMaskFilter) + { + alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; + } + + alphaMaskFilter[0].resolution = this.renderer.resolution; + alphaMaskFilter[0].maskSprite = maskData; + + // TODO - may cause issues! + target.filterArea = maskData.getBounds(true); + + this.renderer.filterSystem.pushFilter(target, alphaMaskFilter); + + this.alphaMaskIndex++; + } + + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popSpriteMask() + { + this.renderer.filterSystem.popFilter(); + this.alphaMaskIndex--; + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + pushStencilMask(maskData) + { + this.renderer.batch.flush(); + this.renderer.stencil.pushStencil(maskData); + } + + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popStencilMask() + { + // this.renderer.currentRenderer.stop(); + this.renderer.stencil.popStencil(); + } + + /** + * + * @param {PIXI.DisplayObject} target - Display Object to push the mask to + * @param {PIXI.Graphics} maskData - The masking data. + */ + pushScissorMask(target, maskData) + { + maskData.renderable = true; + + const renderTarget = this.renderer._activeRenderTarget; + + const bounds = maskData.getBounds(); + + bounds.fit(renderTarget.size); + maskData.renderable = false; + + this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); + + const resolution = this.renderer.resolution; + + this.renderer.gl.scissor( + bounds.x * resolution, + (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, + bounds.width * resolution, + bounds.height * resolution + ); + + this.scissorRenderTarget = renderTarget; + this.scissorData = maskData; + this.scissor = true; + } + + /** + * + * + */ + popScissorMask() + { + this.scissorRenderTarget = null; + this.scissorData = null; + this.scissor = false; + + // must be scissor! + const gl = this.renderer.gl; + + gl.disable(gl.SCISSOR_TEST); + } +} diff --git a/packages/core/src/mask/StencilSystem.js b/packages/core/src/mask/StencilSystem.js new file mode 100644 index 0000000..62796a5 --- /dev/null +++ b/packages/core/src/mask/StencilSystem.js @@ -0,0 +1,133 @@ +import System from '../System'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class StencilSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + this.stencilMaskStack = []; + } + + /** + * Changes the mask stack that is used by this System. + * + * @param {PIXI.Graphics[]} stencilMaskStack - The mask stack + */ + setMaskStack(stencilMaskStack) + { + const gl = this.renderer.gl; + + if (stencilMaskStack.length !== this.stencilMaskStack.length) + { + if (stencilMaskStack.length === 0) + { + gl.disable(gl.STENCIL_TEST); + } + else + { + gl.enable(gl.STENCIL_TEST); + } + } + + this.stencilMaskStack = stencilMaskStack; + } + + /** + * Applies the Mask and adds it to the current stencil stack. @alvin + * + * @param {PIXI.Graphics} graphics - The mask + */ + pushStencil(graphics) + { + this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); + + // this.renderer._activeRenderTarget.attachStencilBuffer(); + + const gl = this.renderer.gl; + const prevMaskCount = this.stencilMaskStack.length; + + if (prevMaskCount === 0) + { + gl.enable(gl.STENCIL_TEST); + } + + this.stencilMaskStack.push(graphics); + + // Increment the refference stencil value where the new mask overlaps with the old ones. + gl.colorMask(false, false, false, false); + gl.stencilFunc(gl.EQUAL, prevMaskCount, this._getBitwiseMask()); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.INCR); + this.renderer.plugins.graphics.render(graphics); + + this._useCurrent(); + } + + /** + * Removes the last mask from the stencil stack. @alvin + */ + popStencil() + { + this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); + + const gl = this.renderer.gl; + const graphics = this.stencilMaskStack.pop(); + + if (this.stencilMaskStack.length === 0) + { + // the stack is empty! + gl.disable(gl.STENCIL_TEST); + gl.clear(gl.STENCIL_BUFFER_BIT); + gl.clearStencil(0); + } + else + { + // Decrement the refference stencil value where the popped mask overlaps with the other ones + gl.colorMask(false, false, false, false); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.DECR); + this.renderer.plugins.graphics.render(graphics); + + this._useCurrent(); + } + } + + /** + * Setup renderer to use the current stencil data. + */ + _useCurrent() + { + const gl = this.renderer.gl; + + gl.colorMask(true, true, true, true); + gl.stencilFunc(gl.EQUAL, this.stencilMaskStack.length, this._getBitwiseMask()); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP); + } + + /** + * Fill 1s equal to the number of acitve stencil masks. + * + * @return {number} The bitwise mask. + */ + _getBitwiseMask() + { + return (1 << this.stencilMaskStack.length) - 1; + } + + /** + * Destroys the mask stack. + * + */ + destroy() + { + super.destroy(this); + + this.stencilMaskStack = null; + } +} diff --git a/packages/core/src/projection/ProjectionSystem.js b/packages/core/src/projection/ProjectionSystem.js new file mode 100644 index 0000000..6e63b73 --- /dev/null +++ b/packages/core/src/projection/ProjectionSystem.js @@ -0,0 +1,73 @@ +import System from '../System'; +import { Matrix } from '@pixi/math'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class ProjectionSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.projectionMatrix = new Matrix(); + } + + update(destinationFrame, sourceFrame, resolution, root) + { + this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; + this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; + + this.calculateProjection(this.destinationFrame, this.sourceFrame, resolution, root); + + this.renderer.globalUniforms.uniforms.projectionMatrix = this.projectionMatrix; + this.renderer.globalUniforms.update(); + } + + /** + * Updates the projection matrix based on a projection frame (which is a rectangle) + * + * @param {Rectangle} destinationFrame - The destination frame. + * @param {Rectangle} sourceFrame - The source frame. + */ + calculateProjection(destinationFrame, sourceFrame, resolution, root) + { + const pm = this.projectionMatrix; + + // I don't think we will need this line.. + // pm.identity(); + + if (!root) + { + pm.a = (1 / destinationFrame.width * 2) * resolution; + pm.d = (1 / destinationFrame.height * 2) * resolution; + + pm.tx = -1 - (sourceFrame.x * pm.a); + pm.ty = -1 - (sourceFrame.y * pm.d); + } + else + { + pm.a = (1 / destinationFrame.width * 2) * resolution; + pm.d = (-1 / destinationFrame.height * 2) * resolution; + + pm.tx = -1 - (sourceFrame.x * pm.a); + pm.ty = 1 - (sourceFrame.y * pm.d); + } + } + + /** + * Sets the transform of the active render target to the given matrix + * + * @param {PIXI.Matrix} matrix - The transformation matrix + */ + setTransform()// matrix) + { + // this._activeRenderTarget.transform = matrix; + } +} diff --git a/packages/core/src/renderTexture/BaseRenderTexture.js b/packages/core/src/renderTexture/BaseRenderTexture.js new file mode 100644 index 0000000..88eb83b --- /dev/null +++ b/packages/core/src/renderTexture/BaseRenderTexture.js @@ -0,0 +1,137 @@ +import BaseTexture from '../textures/BaseTexture'; +import FrameBuffer from '../framebuffer/FrameBuffer'; + +/** + * A BaseRenderTexture is a special texture that allows any PixiJS display object to be rendered to it. + * + * __Hint__: All DisplayObjects (i.e. Sprites) that render to a BaseRenderTexture should be preloaded + * otherwise black rectangles will be drawn instead. + * + * A BaseRenderTexture takes a snapshot of any Display Object given to its render method. The position + * and rotation of the given Display Objects is ignored. For example: + * + * ```js + * let renderer = PIXI.autoDetectRenderer(1024, 1024, { view: canvas, ratio: 1 }); + * let baseRenderTexture = new PIXI.BaseRenderTexture(renderer, 800, 600); + * let sprite = PIXI.Sprite.fromImage("spinObj_01.png"); + * + * sprite.position.x = 800/2; + * sprite.position.y = 600/2; + * sprite.anchor.x = 0.5; + * sprite.anchor.y = 0.5; + * + * baseRenderTexture.render(sprite); + * ``` + * + * The Sprite in this case will be rendered using its local transform. To render this sprite at 0,0 + * you can clear the transform + * + * ```js + * + * sprite.setTransform() + * + * let baseRenderTexture = new PIXI.BaseRenderTexture(100, 100); + * let renderTexture = new PIXI.RenderTexture(baseRenderTexture); + * + * renderer.render(sprite, renderTexture); // Renders to center of RenderTexture + * ``` + * + * @class + * @extends PIXI.BaseTexture + * @memberof PIXI + */ +export default class BaseRenderTexture extends BaseTexture +{ + /** + * @param {object} [options] + * @param {number} [options.width=100] - The width of the base render texture + * @param {number} [options.height=100] - The height of the base render texture + * @param {PIXI.SCALE_MODES} [options.scaleMode] - See {@link PIXI.SCALE_MODES} for possible values + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the texture being generated + */ + constructor(options) + { + if (typeof options === 'number') + { + /* eslint-disable prefer-rest-params */ + // Backward compatibility of signature + const width = arguments[0]; + const height = arguments[1]; + const scaleMode = arguments[2]; + const resolution = arguments[3]; + + options = { width, height, scaleMode, resolution }; + /* eslint-enable prefer-rest-params */ + } + + super(null, options); + + const { width, height } = options || {}; + + // Set defaults + this.mipmap = false; + this.width = Math.ceil(width) || 100; + this.height = Math.ceil(height) || 100; + this.valid = true; + + /** + * A map of renderer IDs to webgl renderTargets + * + * @private + * @member {object} + */ + // this._glRenderTargets = {}; + + /** + * A reference to the canvas render target (we only need one as this can be shared across renderers) + * + * @private + * @member {object} + */ + this._canvasRenderTarget = null; + + this.clearColor = [0, 0, 0, 0]; + + this.frameBuffer = new FrameBuffer(width * this.resolution, height * this.resolution) + .addColorTexture(0, this); + + // TODO - could this be added the systems? + + /** + * The data structure for the stencil masks + * + * @member {PIXI.Graphics[]} + */ + this.stencilMaskStack = []; + + /** + * The data structure for the filters + * + * @member {PIXI.Graphics[]} + */ + this.filterStack = [{}]; + } + + /** + * Resizes the BaseRenderTexture. + * + * @param {number} width - The width to resize to. + * @param {number} height - The height to resize to. + */ + resize(width, height) + { + width = Math.ceil(width); + height = Math.ceil(height); + this.frameBuffer.resize(width * this.resolution, height * this.resolution); + } + + /** + * Destroys this texture + * + */ + destroy() + { + super.destroy(true); + this.renderer = null; + } +} diff --git a/packages/core/src/renderTexture/RenderTexture.js b/packages/core/src/renderTexture/RenderTexture.js new file mode 100644 index 0000000..f1ecf1f --- /dev/null +++ b/packages/core/src/renderTexture/RenderTexture.js @@ -0,0 +1,153 @@ +import BaseRenderTexture from './BaseRenderTexture'; +import Texture from '../textures/Texture'; + +/** + * A RenderTexture is a special texture that allows any PixiJS display object to be rendered to it. + * + * __Hint__: All DisplayObjects (i.e. Sprites) that render to a RenderTexture should be preloaded + * otherwise black rectangles will be drawn instead. + * + * A RenderTexture takes a snapshot of any Display Object given to its render method. For example: + * + * ```js + * let renderer = PIXI.autoDetectRenderer(1024, 1024, { view: canvas, ratio: 1 }); + * let renderTexture = PIXI.RenderTexture.create(800, 600); + * let sprite = PIXI.Sprite.fromImage("spinObj_01.png"); + * + * sprite.position.x = 800/2; + * sprite.position.y = 600/2; + * sprite.anchor.x = 0.5; + * sprite.anchor.y = 0.5; + * + * renderer.render(sprite, renderTexture); + * ``` + * + * The Sprite in this case will be rendered using its local transform. To render this sprite at 0,0 + * you can clear the transform + * + * ```js + * + * sprite.setTransform() + * + * let renderTexture = new PIXI.RenderTexture.create(100, 100); + * + * renderer.render(sprite, renderTexture); // Renders to center of RenderTexture + * ``` + * + * @class + * @extends PIXI.Texture + * @memberof PIXI + */ +export default class RenderTexture extends Texture +{ + /** + * @param {PIXI.BaseRenderTexture} baseRenderTexture - The renderer used for this RenderTexture + * @param {PIXI.Rectangle} [frame] - The rectangle frame of the texture to show + */ + constructor(baseRenderTexture, frame) + { + // support for legacy.. + let _legacyRenderer = null; + + if (!(baseRenderTexture instanceof BaseRenderTexture)) + { + /* eslint-disable prefer-rest-params, no-console */ + const width = arguments[1]; + const height = arguments[2]; + const scaleMode = arguments[3]; + const resolution = arguments[4]; + + // we have an old render texture.. + console.warn(`Please use RenderTexture.create(${width}, ${height}) instead of the ctor directly.`); + _legacyRenderer = arguments[0]; + /* eslint-enable prefer-rest-params, no-console */ + + frame = null; + baseRenderTexture = new BaseRenderTexture({ + width, + height, + scaleMode, + resolution, + }); + } + + /** + * The base texture object that this texture uses + * + * @member {BaseTexture} + */ + super(baseRenderTexture, frame); + + this.legacyRenderer = _legacyRenderer; + + /** + * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. + * + * @member {boolean} + */ + this.valid = true; + + /** + * FilterSystem temporary storage + * @private + * @member {PIXI.Rectangle} + */ + this.filterFrame = null; + + this._updateUvs(); + } + + /** + * Resizes the RenderTexture. + * + * @param {number} width - The width to resize to. + * @param {number} height - The height to resize to. + * @param {boolean} [resizeBaseTexture=true] - Should the baseTexture.width and height values be resized as well? + */ + resize(width, height, resizeBaseTexture = true) + { + width = Math.ceil(width); + height = Math.ceil(height); + + // TODO - could be not required.. + this.valid = (width > 0 && height > 0); + + this._frame.width = this.orig.width = width; + this._frame.height = this.orig.height = height; + + if (resizeBaseTexture) + { + this.baseTexture.resize(width, height); + } + + this._updateUvs(); + } + + /** + * A short hand way of creating a render texture. + * + * @param {object} [options] - Options + * @param {number} [options.width=100] - The width of the render texture + * @param {number} [options.height=100] - The height of the render texture + * @param {number} [options.scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the texture being generated + * @return {PIXI.RenderTexture} The new render texture + */ + static create(options) + { + // fallback, old-style: create(width, height, scaleMode, resolution) + if (typeof options === 'number') + { + /* eslint-disable prefer-rest-params */ + options = { + width: options, + height: arguments[1], + scaleMode: arguments[2], + resolution: arguments[3], + }; + /* eslint-enable prefer-rest-params */ + } + + return new RenderTexture(new BaseRenderTexture(options)); + } +} diff --git a/packages/core/src/renderTexture/RenderTextureSystem.js b/packages/core/src/renderTexture/RenderTextureSystem.js new file mode 100644 index 0000000..8d0ca61 --- /dev/null +++ b/packages/core/src/renderTexture/RenderTextureSystem.js @@ -0,0 +1,115 @@ +import System from '../System'; +import { Rectangle } from '@pixi/math'; + +const tempRect = new Rectangle(); + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class RenderTextureSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.clearColor = renderer._backgroundColorRgba; + + // TODO moe this property somewhere else! + this.defaultMaskStack = []; + this.defaultFilterStack = [{}]; + + // empty render texture? + this.renderTexture = null; + + this.destinationFrame = new Rectangle(); + } + + bind(renderTexture, sourceFrame, destinationFrame) + { + // TODO - do we want this?? + if (this.renderTexture === renderTexture) return; + this.renderTexture = renderTexture; + + const renderer = this.renderer; + + if (renderTexture) + { + const baseTexture = renderTexture.baseTexture; + + if (!destinationFrame) + { + tempRect.width = baseTexture.realWidth; + tempRect.height = baseTexture.realHeight; + + destinationFrame = tempRect; + } + + if (!sourceFrame) + { + sourceFrame = destinationFrame; + } + + this.renderer.framebuffer.bind(baseTexture.frameBuffer, destinationFrame); + + this.renderer.projection.update(destinationFrame, sourceFrame, baseTexture.resolution, false); + this.renderer.stencil.setMaskStack(baseTexture.stencilMaskStack); + } + else + { + // TODO these validation checks happen deeper down.. + // thing they can be avoided.. + if (!destinationFrame) + { + tempRect.width = renderer.width; + tempRect.height = renderer.height; + + destinationFrame = tempRect; + } + + if (!sourceFrame) + { + sourceFrame = destinationFrame; + } + + renderer.framebuffer.bind(null, destinationFrame); + + // TODO store this.. + this.renderer.projection.update(destinationFrame, sourceFrame, this.renderer.resolution, true); + this.renderer.stencil.setMaskStack(this.defaultMaskStack); + } + + this.destinationFrame.copyFrom(destinationFrame); + } + + /** + * Erases the render texture and fills the drawing area with a colour + * + * @param {number} [clearColor] - The colour + * @return {PIXI.Renderer} Returns itself. + */ + clear(clearColor) + { + if (this.renderTexture) + { + clearColor = clearColor || this.renderTexture.baseTexture.clearColor; + } + else + { + clearColor = clearColor || this.clearColor; + } + + this.renderer.framebuffer.clear(clearColor[0], clearColor[1], clearColor[2], clearColor[3]); + } + + resize()// screenWidth, screenHeight) + { + // resize the root only! + this.bind(null); + } +} diff --git a/packages/core/src/renderers/AbstractRenderer.js b/packages/core/src/renderers/AbstractRenderer.js deleted file mode 100644 index 928670f..0000000 --- a/packages/core/src/renderers/AbstractRenderer.js +++ /dev/null @@ -1,353 +0,0 @@ -import { hex2string, hex2rgb } from '@pixi/utils'; -import { Matrix, Rectangle } from '@pixi/math'; -import { RENDERER_TYPE } from '@pixi/constants'; -import { settings } from '@pixi/settings'; -import { Container } from '@pixi/display'; -import RenderTexture from '../textures/RenderTexture'; -import EventEmitter from 'eventemitter3'; - -const tempMatrix = new Matrix(); - -/** - * 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 EventEmitter - * @memberof PIXI - */ -export default class AbstractRenderer extends EventEmitter -{ - // eslint-disable-next-line valid-jsdoc - /** - * @param {string} system - The name of the system this renderer is for. - * @param {object} [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.transparent=false] - If the render view is transparent, default false - * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false - * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) - * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. The - * resolution of the renderer retina would be 2. - * @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 {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when rendering, - * stopping pixel interpolation. - */ - constructor(system, options, arg2, arg3) - { - super(); - - // Support for constructor(system, screenWidth, screenHeight, options) - if (typeof options === 'number') - { - options = Object.assign({ - width: options, - height: arg2 || settings.RENDER_OPTIONS.height, - }, arg3); - } - - // 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 = RENDERER_TYPE.UNKNOWN; - - /** - * Measurements of the screen. (0, 0, screenWidth, screenHeight) - * - * Its safe to use as filterArea or hitArea for 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 1 - */ - this.resolution = options.resolution || settings.RESOLUTION; - - /** - * Whether the render view is transparent - * - * @member {boolean} - */ - this.transparent = options.transparent; - - /** - * Whether css dimensions of canvas view should be resized to screen dimensions automatically - * - * @member {boolean} - */ - this.autoResize = options.autoResize || false; - - /** - * Tracks the blend modes useful for this renderer. - * - * @member {object} - */ - this.blendModes = null; - - /** - * 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; - - /** - * If true PixiJS will Math.floor() x/y values when rendering, stopping pixel interpolation. - * Handy for crisp pixel art and speed on legacy devices. - * - * @member {boolean} - */ - this.roundPixels = options.roundPixels; - - /** - * The background color as a number. - * - * @member {number} - * @private - */ - this._backgroundColor = 0x000000; - - /** - * The background color as an [R, G, B] array. - * - * @member {number[]} - * @private - */ - this._backgroundColorRgba = [0, 0, 0, 0]; - - /** - * The background color as a string. - * - * @member {string} - * @private - */ - this._backgroundColorString = '#000000'; - - this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter - - /** - * This temporary display object used as the parent of the currently being rendered item - * - * @member {PIXI.DisplayObject} - * @private - */ - this._tempDisplayObjectParent = new Container(); - - /** - * The last root object that the renderer tried to render. - * - * @member {PIXI.DisplayObject} - * @private - */ - this._lastObjectRendered = this._tempDisplayObjectParent; - - /** - * Collection of plugins. - * @readonly - * @member {object} - */ - this.plugins = {}; - } - - /** - * Initialize the plugins. - * - * @protected - * @param {object} staticMap - The dictionary of staticly saved plugins. - */ - initPlugins(staticMap) - { - 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() - { - return this.view.width; - } - - /** - * Same as view.height, actual number of pixels in the canvas by vertical - * - * @member {number} - * @readonly - * @default 600 - */ - get height() - { - return this.view.height; - } - - /** - * Resizes the screen and canvas to the specified width and height - * Canvas dimensions are multiplied by resolution - * - * @param {number} screenWidth - the new width of the screen - * @param {number} screenHeight - the new height of the screen - */ - resize(screenWidth, screenHeight) - { - this.screen.width = screenWidth; - this.screen.height = screenHeight; - - this.view.width = screenWidth * this.resolution; - this.view.height = screenHeight * this.resolution; - - if (this.autoResize) - { - this.view.style.width = `${screenWidth}px`; - this.view.style.height = `${screenHeight}px`; - } - } - - /** - * 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 {PIXI.DisplayObject} displayObject - The displayObject the object will be generated from - * @param {number} scaleMode - Should be one of the scaleMode consts - * @param {number} resolution - The resolution / device pixel ratio of the texture being generated - * @param {PIXI.Rectangle} [region] - The region of the displayObject, that shall be rendered, - * if no region is specified, defaults to the local bounds of the displayObject. - * @return {PIXI.Texture} a texture of the graphics object - */ - generateTexture(displayObject, scaleMode, resolution, region) - { - region = region || displayObject.getLocalBounds(); - - // 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(region.width | 0, region.height | 0, scaleMode, resolution); - - tempMatrix.tx = -region.x; - tempMatrix.ty = -region.y; - - this.render(displayObject, renderTexture, false, tempMatrix, true); - - return renderTexture; - } - - /** - * Removes everything from the renderer and optionally removes the Canvas DOM element. - * - * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. - */ - destroy(removeView) - { - 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); - } - - this.plugins = null; - - this.type = RENDERER_TYPE.UNKNOWN; - - this.view = null; - - this.screen = null; - - this.resolution = 0; - - this.transparent = false; - - this.autoResize = false; - - this.blendModes = null; - - this.options = null; - - this.preserveDrawingBuffer = false; - this.clearBeforeRender = false; - - this.roundPixels = false; - - this._backgroundColor = 0; - this._backgroundColorRgba = null; - this._backgroundColorString = null; - - this._tempDisplayObjectParent = null; - this._lastObjectRendered = null; - } - - /** - * The background color to fill if not transparent - * - * @member {number} - */ - get backgroundColor() - { - return this._backgroundColor; - } - - set backgroundColor(value) // eslint-disable-line require-jsdoc - { - this._backgroundColor = value; - this._backgroundColorString = hex2string(value); - hex2rgb(value, this._backgroundColorRgba); - } -} diff --git a/packages/core/src/renderers/Renderer.js b/packages/core/src/renderers/Renderer.js deleted file mode 100644 index a2020b1..0000000 --- a/packages/core/src/renderers/Renderer.js +++ /dev/null @@ -1,329 +0,0 @@ -import AbstractRenderer from './AbstractRenderer'; -import { sayHello } from '@pixi/utils'; -import MaskSystem from './systems/MaskSystem'; -import StencilSystem from './systems/StencilSystem'; -import FilterSystem from './systems/filter/FilterSystem'; -import FramebufferSystem from './systems/FramebufferSystem'; -import RenderTextureSystem from './systems/RenderTextureSystem'; -import TextureSystem from './systems/textures/TextureSystem'; -import ProjectionSystem from './systems/ProjectionSystem'; -import StateSystem from './systems/StateSystem'; -import GeometrySystem from './systems/geometry/GeometrySystem'; -import ShaderSystem from './systems/shader/ShaderSystem'; -import ContextSystem from './systems/ContextSystem'; -import BatchSystem from './systems/BatchSystem'; -import TextureGCSystem from './systems/textures/TextureGCSystem'; -import { RENDERER_TYPE } from '@pixi/constants'; -import UniformGroup from '../shader/UniformGroup'; -import { Matrix } from '@pixi/math'; -import Runner from 'mini-runner'; - -/** - * 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 Render works by automatically managing webGLBatchs. - * 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 :) - * - * @class - * @memberof PIXI - * @extends PIXI.AbstractRenderer - */ -export default class Renderer extends AbstractRenderer -{ - // eslint-disable-next-line valid-jsdoc - /** - * - * @param {object} [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.transparent=false] - If the render view is transparent, default false - * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false - * @param {boolean} [options.antialias=false] - sets antialias. If not available natively then FXAA - * antialiasing is used - * @param {boolean} [options.forceFXAA=false] - forces FXAA antialiasing to be used over native. - * FXAA is faster, but may not always look as great - * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. - * The resolution of the renderer retina would be 2. - * @param {boolean} [options.clearBeforeRender=true] - This sets if the renderer will clear - * the canvas or not before the new render pass. If you wish to set this to false, you *must* set - * preserveDrawingBuffer to `true`. - * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, - * enable this if you need to call toDataUrl on the webgl context. - * @param {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when - * rendering, stopping pixel interpolation. - * @param {number} [options.backgroundColor=0x000000] - The background color of the rendered area - * (shown if not transparent). - * @param {boolean} [options.legacy=false] - If true PixiJS will aim to ensure compatibility - * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. - * @param {string} [options.powerPreference] - Parameter passed to webgl context, set to "high-performance" - * for devices with dual graphics card - */ - constructor(options, arg2, arg3) - { - super('WebGL', options, arg2, arg3); - - /** - * The type of this renderer as a standardised const - * - * @member {number} - * @see PIXI.RENDERER_TYPE - */ - this.type = RENDERER_TYPE.WEBGL; - - // this will be set by the contextSystem (this.context) - this.gl = null; - this.CONTEXT_UID = 0; - this.legacy = !!options.legacy; - - // TODO legacy! - - // runners! - this.runners = { - destroy: new Runner('destroy'), - contextChange: new Runner('contextChange', 1), - reset: new Runner('reset'), - update: new Runner('update'), - postrender: new Runner('postrender'), - prerender: new Runner('prerender'), - resize: new Runner('resize', 2), - }; - - 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(GeometrySystem, 'geometry') - .addSystem(FramebufferSystem, 'framebuffer') - .addSystem(StencilSystem, 'stencil') - .addSystem(ProjectionSystem, 'projection') - .addSystem(TextureGCSystem, 'textureGC') - .addSystem(FilterSystem, 'filter') - .addSystem(RenderTextureSystem, 'renderTexture') - .addSystem(BatchSystem, 'batch'); - - this.initPlugins(Renderer.__plugins); - - /** - * The options passed in to create a new webgl context. - * - * @member {object} - * @private - */ - if (options.context) - { - this.context.initFromContext(options.context); - } - else - { - this.context.initFromOptions({ - alpha: this.transparent, - antialias: options.antialias, - premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', - stencil: true, - 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); - } - - addSystem(_class, name) - { - if (!name) - { - name = _class.name; - } - - // TODO - read name from class.name.. - - /* - if(name.includes('System')) - { - name = name.replace('System', ''); - name = name.charAt(0).toLowerCase() + name.slice(1); - } - */ - - const system = new _class(this); - - if (this[name]) - { - throw new Error(`Whoops! ${name} is already a manger`); - } - - this[name] = system; - - for (const i in this.runners) - { - this.runners[i].add(system); - } - - return this; - - /** - * 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. - */ - } - - /** - * Renders the object to its webGL view - * - * @param {PIXI.DisplayObject} displayObject - the object to be rendered - * @param {PIXI.RenderTexture} renderTexture - The render texture to render to. - * @param {boolean} [clear] - Should the canvas be cleared before the new render - * @param {PIXI.Transform} [transform] - A transform to apply to the render texture before rendering. - * @param {boolean} [skipUpdateTransform] - Should we skip the update transform pass? - */ - render(displayObject, renderTexture, clear, transform, skipUpdateTransform) - { - // can be handy to know! - this.renderingToScreen = !renderTexture; - - this.runners.prerender.run(); - this.emit('prerender'); - - // 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.parent; - - displayObject.parent = this._tempDisplayObjectParent; - displayObject.updateTransform(); - displayObject.parent = 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.run(); - - this.emit('postrender'); - } - - /** - * Resizes the webGL view to the specified width and height. - * - * @param {number} screenWidth - the new width of the screen - * @param {number} screenHeight - the new height of the screen - */ - resize(screenWidth, screenHeight) - { - AbstractRenderer.prototype.resize.call(this, screenWidth, screenHeight); - - this.runners.resize.run(screenWidth, screenHeight); - } - - /** - * Resets the WebGL state so you can render things however you fancy! - * - * @return {PIXI.Renderer} Returns itself. - */ - reset() - { - this.runners.reset.run(); - - return this; - } - - clear() - { - this.framebuffer.bind(); - this.framebuffer.clear(); - } - - /** - * Removes everything from the renderer (event listeners, spritebatch, etc...) - * - * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. - * See: https://github.com/pixijs/pixi.js/issues/2233 - */ - destroy(removeView) - { - this.runners.destroy.run(); - - // call base destroy - super.destroy(removeView); - - // TODO nullify all the managers.. - this.gl = null; - } - - /** - * 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 - * builds and excluding plugins. - * @name PIXI.Renderer#plugins - * @type {object} - * @readonly - * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. - * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. - * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. - * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. - */ - - /** - * Adds a plugin to the renderer. - * - * @method - * @param {string} pluginName - The name of the plugin. - * @param {Function} ctor - The constructor function or class for the plugin. - */ - static registerPlugin(pluginName, ctor) - { - Renderer.__plugins = Renderer.__plugins || {}; - Renderer.__plugins[pluginName] = ctor; - } -} diff --git a/packages/core/src/renderers/State.js b/packages/core/src/renderers/State.js deleted file mode 100644 index fa3f221..0000000 --- a/packages/core/src/renderers/State.js +++ /dev/null @@ -1,163 +0,0 @@ -/* eslint-disable max-len */ - -const BLEND = 0; -const OFFSET = 1; -const CULLING = 2; -const DEPTH_TEST = 3; -const WINDING = 4; - -/** - * This is a webGL state. It is passed The WebGL StateManager. - * Each mesh renderered may require webGL to be in a different state. - * For example you may want different blend mode or to enable polygon offsets - * - * @class - * @memberof PIXI - */ -export default class State -{ - /** - * - */ - constructor() - { - this.data = 0; - - this.blendMode = 0; - this.polygonOffset = 0; - - this.blend = true; - // this.depthTest = true; - } - - /** - * Activates blending of the computed fragment color values - * - * @member {boolean} - */ - get blend() - { - return !!(this.data & (1 << BLEND)); - } - - set blend(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << BLEND)) !== value) - { - this.data ^= (1 << BLEND); - } - } - - /** - * Activates adding an offset to depth values of polygon's fragments - * - * @member {boolean} - * @default false - */ - get offsets() - { - return !!(this.data & (1 << OFFSET)); - } - - set offsets(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << OFFSET)) !== value) - { - this.data ^= (1 << OFFSET); - } - } - - /** - * Activates culling of polygons. - * - * @member {boolean} - * @default false - */ - get culling() - { - return !!(this.data & (1 << CULLING)); - } - - set culling(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << CULLING)) !== value) - { - this.data ^= (1 << CULLING); - } - } - - /** - * Activates depth comparisons and updates to the depth buffer. - * - * @member {boolean} - * @default false - */ - get depthTest() - { - return !!(this.data & (1 << DEPTH_TEST)); - } - - set depthTest(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << DEPTH_TEST)) !== value) - { - this.data ^= (1 << DEPTH_TEST); - } - } - - /** - * Specifies whether or not front or back-facing polygons can be culled. - * @member {boolean} - * @default false - */ - get clockwiseFrontFace() - { - return !!(this.data & (1 << WINDING)); - } - - set clockwiseFrontFace(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << WINDING)) !== value) - { - this.data ^= (1 << WINDING); - } - } - - /** - * The blend mode to be applied when this state is set. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. - * Setting this mode to anything other than NO_BLEND will automatically switch blending on. - * - * @member {boolean} - * @default PIXI.BLEND_MODES.NORMAL - * @see PIXI.BLEND_MODES - */ - get blendMode() - { - return this._blendMode; - } - - set blendMode(value) // eslint-disable-line require-jsdoc - { - // 17 is NO BLEND - this.blend = (value !== 17); - this._blendMode = value; - } - - /** - * The polygon offset. Setting this property to anything other than 0 will automatically enable poygon offset fill. - * - * @member {number} - * @default 0 - */ - get polygonOffset() - { - return this._polygonOffset; - } - - set polygonOffset(value) // eslint-disable-line require-jsdoc - { - this.offsets = !!value; - this._polygonOffset = value; - } -} - diff --git a/packages/core/src/renderers/filters/Filter.js b/packages/core/src/renderers/filters/Filter.js deleted file mode 100644 index 2073b8d..0000000 --- a/packages/core/src/renderers/filters/Filter.js +++ /dev/null @@ -1,140 +0,0 @@ -import Shader from '../../shader/Shader'; -import Program from '../../shader/Program'; -import State from '../State'; -import { settings } from '@pixi/settings'; -// import extractUniformsFromSrc from './extractUniformsFromSrc'; -import defaultVertex from './defaultFilter.vert'; -import defaultFragment from './defaultFilter.frag'; - -// let math = require('../../math'); -/** - * @class - * @memberof PIXI - * @extends PIXI.Shader - */ -export default class Filter extends Shader -{ - /** - * @param {string} [vertexSrc] - The source of the vertex shader. - * @param {string} [fragmentSrc] - The source of the fragment shader. - * @param {object} [uniforms] - Custom uniforms to use to augment the built-in ones. - */ - constructor(vertexSrc, fragmentSrc, uniforms) - { - const program = Program.from(vertexSrc, fragmentSrc); - - super(program, uniforms); - - /** - * The padding of the filter. Some filters require extra space to breath such as a blur. - * Increasing this will add extra width and height to the bounds of the object that the - * filter is applied to. - * - * @member {number} - */ - this.padding = 0; - - /** - * The resolution of the filter. Setting this to be lower will lower the quality but - * increase the performance of the filter. - * - * @member {number} - */ - this.resolution = settings.RESOLUTION; - - /** - * If enabled is true the filter is applied, if false it will not. - * - * @member {boolean} - */ - this.enabled = true; - - /** - * If enabled, PixiJS will fit the filter area into boundaries for better performance. - * Switch it off if it does not work for specific shader. - * - * @member {boolean} - */ - this.autoFit = true; - - /** - * Legacy filters use position and uvs from attributes - * @member {boolean} - * @readonly - */ - this.legacy = !!this.program.attributeData.aTextureCoord; - - /** - * the webGL state the filter requires to render - * @member {PIXI.State} - */ - this.state = new State(); - } - - /** - * Applies the filter - * - * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from - * @param {PIXI.RenderTarget} input - The input render target. - * @param {PIXI.RenderTarget} output - The target to output to. - * @param {boolean} clear - Should the output be cleared before rendering to it - * @param {object} [currentState] - It's current state of filter. - * There are some useful properties in the currentState : - * target, filters, sourceFrame, destinationFrame, renderTarget, resolution - */ - apply(filterManager, input, output, clear, currentState, derp) // eslint-disable-line no-unused-vars - { - // do as you please! - - filterManager.applyFilter(this, input, output, clear, currentState, derp); - - // or just do a regular render.. - } - - /** - * Sets the blendmode of the filter - * - * @member {number} - * @default PIXI.BLEND_MODES.NORMAL - */ - get blendMode() - { - return this.state.blendMode; - } - - set blendMode(value) // eslint-disable-line require-jsdoc - { - this.state.blendMode = value; - } - - /** - * The default vertex shader source - * - * @static - * @constant - */ - static get defaultVertexSrc() - { - return defaultVertex; - } - - /** - * The default fragment shader source - * - * @static - * @constant - */ - static get defaultFragmentSrc() - { - return defaultFragment; - } -} - -/** - * Used for caching shader IDs - * - * @static - * @private - */ -Filter.SOURCE_KEY_MAP = {}; - diff --git a/packages/core/src/renderers/filters/defaultFilter.frag b/packages/core/src/renderers/filters/defaultFilter.frag deleted file mode 100644 index a14c1be..0000000 --- a/packages/core/src/renderers/filters/defaultFilter.frag +++ /dev/null @@ -1,22 +0,0 @@ -varying vec2 vTextureCoord; -varying vec2 vFilterCoord; - -uniform sampler2D uSampler; -uniform sampler2D filterSampler; - -void main(void){ - vec4 masky = texture2D(filterSampler, vFilterCoord); - vec4 sample = texture2D(uSampler, vTextureCoord); - vec4 color; - if(mod(vFilterCoord.x, 1.0) > 0.5) - { - color = vec4(1.0, 0.0, 0.0, 1.0); - } - else - { - color = vec4(0.0, 1.0, 0.0, 1.0); - } - // gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0); - gl_FragColor = mix(sample, masky, 0.5); - gl_FragColor *= sample.a; -} diff --git a/packages/core/src/renderers/filters/defaultFilter.vert b/packages/core/src/renderers/filters/defaultFilter.vert deleted file mode 100644 index e1febca..0000000 --- a/packages/core/src/renderers/filters/defaultFilter.vert +++ /dev/null @@ -1,14 +0,0 @@ -attribute vec2 aVertexPosition; -attribute vec2 aTextureCoord; - -uniform mat3 projectionMatrix; -uniform mat3 filterMatrix; - -varying vec2 vTextureCoord; -varying vec2 vFilterCoord; - -void main(void){ - gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); - vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy; - vTextureCoord = aTextureCoord ; -} diff --git a/packages/core/src/renderers/filters/filterTransforms.js b/packages/core/src/renderers/filters/filterTransforms.js deleted file mode 100644 index 87a3cd6..0000000 --- a/packages/core/src/renderers/filters/filterTransforms.js +++ /dev/null @@ -1,50 +0,0 @@ -import { Matrix } from '@pixi/math'; - -/** - * Calculates the mapped matrix - * @param filterArea {Rectangle} The filter area - * @param sprite {Sprite} the target sprite - * @param outputMatrix {Matrix} @alvin - * @private - */ -// this returns a matrix that will normalise map filter cords in the filter to screen space -export function calculateScreenSpaceMatrix(outputMatrix, filterArea, textureSize) -{ - // TODO unwrap? - const mappedMatrix = outputMatrix.identity(); - - mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); - - mappedMatrix.scale(textureSize.width, textureSize.height); - - return mappedMatrix; -} - -export function calculateNormalizedScreenSpaceMatrix(outputMatrix, filterArea, textureSize) -{ - const mappedMatrix = outputMatrix.identity(); - - mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); - - const translateScaleX = (textureSize.width / filterArea.width); - const translateScaleY = (textureSize.height / filterArea.height); - - mappedMatrix.scale(translateScaleX, translateScaleY); - - return mappedMatrix; -} - -// this will map the filter coord so that a texture can be used based on the transform of a sprite -export function calculateSpriteMatrix(outputMatrix, filterArea, textureSize, sprite) -{ - const orig = sprite._texture.orig; - const mappedMatrix = outputMatrix.set(textureSize.width, 0, 0, textureSize.height, filterArea.x, filterArea.y); - const worldTransform = sprite.worldTransform.copyTo(Matrix.TEMP_MATRIX); - - worldTransform.invert(); - mappedMatrix.prepend(worldTransform); - mappedMatrix.scale(1.0 / orig.width, 1.0 / orig.height); - mappedMatrix.translate(sprite.anchor.x, sprite.anchor.y); - - return mappedMatrix; -} diff --git a/packages/core/src/renderers/filters/spriteMask/SpriteMaskFilter.js b/packages/core/src/renderers/filters/spriteMask/SpriteMaskFilter.js deleted file mode 100644 index d5a91a4..0000000 --- a/packages/core/src/renderers/filters/spriteMask/SpriteMaskFilter.js +++ /dev/null @@ -1,63 +0,0 @@ -import Filter from '../Filter'; -import { Matrix } from '@pixi/math'; -import vertex from './spriteMaskFilter.vert'; -import fragment from './spriteMaskFilter.frag'; -import { default as TextureMatrix } from '../../../textures/TextureMatrix'; - -/** - * The SpriteMaskFilter class - * - * @class - * @extends PIXI.Filter - * @memberof PIXI - */ -export default class SpriteMaskFilter extends Filter -{ - /** - * @param {PIXI.Sprite} sprite - the target sprite - */ - constructor(sprite) - { - const maskMatrix = new Matrix(); - - super(vertex, fragment); - - sprite.renderable = false; - - this.maskSprite = sprite; - this.maskMatrix = maskMatrix; - } - - /** - * Applies the filter - * - * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from - * @param {PIXI.RenderTarget} input - The input render target. - * @param {PIXI.RenderTarget} output - The target to output to. - */ - apply(filterManager, input, output) - { - const maskSprite = this.maskSprite; - const tex = this.maskSprite.texture; - - if (!tex.valid) - { - return; - } - if (!tex.transform) - { - // margin = 0.0, let it bleed a bit, shader code becomes easier - // assuming that atlas textures were made with 1-pixel padding - tex.transform = new TextureMatrix(tex, 0.0); - } - tex.transform.update(); - - this.uniforms.mask = tex; - this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite) - .prepend(tex.transform.mapCoord); - this.uniforms.alpha = maskSprite.worldAlpha; - this.uniforms.maskClamp = tex.transform.uClampFrame; - - filterManager.applyFilter(this, input, output); - } -} diff --git a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.frag b/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.frag deleted file mode 100644 index 0e4aef8..0000000 --- a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.frag +++ /dev/null @@ -1,23 +0,0 @@ -varying vec2 vMaskCoord; -varying vec2 vTextureCoord; - -uniform sampler2D uSampler; -uniform sampler2D mask; -uniform float alpha; -uniform vec4 maskClamp; - -void main(void) -{ - float clip = step(3.5, - step(maskClamp.x, vMaskCoord.x) + - step(maskClamp.y, vMaskCoord.y) + - step(vMaskCoord.x, maskClamp.z) + - step(vMaskCoord.y, maskClamp.w)); - - vec4 original = texture2D(uSampler, vTextureCoord); - vec4 masky = texture2D(mask, vMaskCoord); - - original *= (masky.r * masky.a * alpha * clip); - - gl_FragColor = original; -} diff --git a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.vert b/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.vert deleted file mode 100644 index d23a30c..0000000 --- a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.vert +++ /dev/null @@ -1,16 +0,0 @@ -attribute vec2 aVertexPosition; -attribute vec2 aTextureCoord; - -uniform mat3 projectionMatrix; -uniform mat3 otherMatrix; - -varying vec2 vMaskCoord; -varying vec2 vTextureCoord; - -void main(void) -{ - gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); - - vTextureCoord = aTextureCoord; - vMaskCoord = ( otherMatrix * vec3( aTextureCoord, 1.0) ).xy; -} diff --git a/packages/core/src/renderers/systems/BatchSystem.js b/packages/core/src/renderers/systems/BatchSystem.js deleted file mode 100644 index 70e9518..0000000 --- a/packages/core/src/renderers/systems/BatchSystem.js +++ /dev/null @@ -1,68 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import ObjectRenderer from '../utils/ObjectRenderer'; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ - -export default class BatchSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this manager works for. - */ - constructor(renderer) - { - super(renderer); - - /** - * An empty renderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.emptyRenderer = new ObjectRenderer(renderer); - - /** - * The currently active ObjectRenderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.currentRenderer = this.emptyRenderer; - } - - /** - * Changes the current renderer to the one given in parameter - * - * @param {PIXI.ObjectRenderer} objectRenderer - The object renderer to use. - */ - setObjectRenderer(objectRenderer) - { - if (this.currentRenderer === objectRenderer) - { - return; - } - - this.currentRenderer.stop(); - this.currentRenderer = objectRenderer; - - this.renderer.state.setState(objectRenderer.state); - - this.currentRenderer.start(); - } - - /** - * This should be called if you wish to do some custom rendering - * It will basically render anything that may be batched up such as sprites - * - */ - flush() - { - this.setObjectRenderer(this.emptyRenderer); - } - - reset() - { - this.setObjectRenderer(this.emptyRenderer); - } -} diff --git a/packages/core/src/renderers/systems/ContextSystem.js b/packages/core/src/renderers/systems/ContextSystem.js deleted file mode 100644 index 43bf827..0000000 --- a/packages/core/src/renderers/systems/ContextSystem.js +++ /dev/null @@ -1,186 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { settings } from '@pixi/settings'; - -let CONTEXT_UID = 0; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class ContextSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this.webGLVersion = 1; - - this.handleContextLost = this.handleContextLost.bind(this); - this.handleContextRestored = this.handleContextRestored.bind(this); - - this.extensions = {}; - - renderer.view.addEventListener('webglcontextlost', this.handleContextLost, false); - renderer.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); - } - - get isLost() - { - return (!this.gl || this.gl.isContextLost()); - } - - contextChange(gl) - { - this.gl = gl; - - // restore a context if it was previously lost - if (gl.isContextLost() && gl.getExtension('WEBGL_lose_context')) - { - gl.getExtension('WEBGL_lose_context').restoreContext(); - } - } - - initFromContext(gl) - { - this.gl = gl; - this.validateContext(gl); - this.renderer.gl = gl; - this.renderer.CONTEXT_UID = CONTEXT_UID++; - this.renderer.runners.contextChange.run(gl); - } - - initFromOptions(options) - { - const gl = this.createContext(this.renderer.view, options); - - this.initFromContext(gl); - } - - /** - * Helper class to create a webGL Context - * - * @class - * @memberof PIXI.glCore - * @param canvas {HTMLCanvasElement} the canvas element that we will get the context from - * @param options {Object} An options object that gets passed in to the canvas element containing the context attributes, - * see https://developer.mozilla.org/en/docs/Web/API/HTMLCanvasElement/getContext for the options available - * @return {WebGLRenderingContext} the WebGL context - */ - createContext(canvas, options) - { - let gl; - - if (settings.PREFER_WEBGL_2) - { - gl = canvas.getContext('webgl2', options); - } - - if (gl) - { - this.webGLVersion = 2; - } - else - { - this.webGLVersion = 1; - - gl = canvas.getContext('webgl', options) - || canvas.getContext('experimental-webgl', options); - - if (!gl) - { - // fail, not able to get a context - throw new Error('This browser does not support webGL. Try using the canvas renderer'); - } - } - - this.gl = gl; - - this.getExtensions(); - - return gl; - } - - getExtensions() - { - // time to set up default etensions that pixi uses.. - const gl = this.gl; - const extensions = this.extensions; - - if (this.webGLVersion === 1) - { - extensions.drawBuffers = gl.getExtension('WEBGL_draw_buffers'); - extensions.depthTexture = gl.getExtension('WEBKIT_WEBGL_depth_texture'); - extensions.floatTexture = gl.getExtension('OES_texture_float'); - extensions.loseContext = gl.getExtension('WEBGL_lose_context'); - - extensions.vertexArrayObject = gl.getExtension('OES_vertex_array_object') - || gl.getExtension('MOZ_OES_vertex_array_object') - || gl.getExtension('WEBKIT_OES_vertex_array_object'); - } - - // we don't use any specific WebGL 2 ones yet! - } - - /** - * Handles a lost webgl context - * - * @private - * @param {WebGLContextEvent} event - The context lost event. - */ - handleContextLost(event) - { - event.preventDefault(); - } - - /** - * Handles a restored webgl context - * - * @private - */ - handleContextRestored() - { - this.renderer.runners.contextChange.run(this.gl); - } - - destroy() - { - const view = this.renderer.view; - - // remove listeners - view.removeEventListener('webglcontextlost', this.handleContextLost); - view.removeEventListener('webglcontextrestored', this.handleContextRestored); - - this.gl.useProgram(null); - - if (this.extensions.loseContext) - { - this.extensions.loseContext.loseContext(); - } - } - - postrender() - { - this.gl.flush(); - } - - validateContext(gl) - { - const attributes = gl.getContextAttributes(); - - // this is going to be fairly simple for now.. but at least we have room to grow! - if (!attributes.stencil) - { - /* eslint-disable max-len */ - - /* eslint-disable no-console */ - console.warn('Provided WebGL context does not have a stencil buffer, masks may not render correctly'); - /* eslint-enable no-console */ - - /* eslint-enable max-len */ - } - } -} diff --git a/packages/core/src/renderers/systems/FramebufferSystem.js b/packages/core/src/renderers/systems/FramebufferSystem.js deleted file mode 100644 index df7902b..0000000 --- a/packages/core/src/renderers/systems/FramebufferSystem.js +++ /dev/null @@ -1,246 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { Rectangle } from '@pixi/math'; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class FramebufferSystem extends WebGLSystem -{ - /** - * Sets up the renderer context and necessary buffers. - * - * @private - */ - contextChange() - { - this.gl = this.renderer.gl; - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - this.current = null; - this.viewport = new Rectangle(); - - this.drawBufferExtension = this.renderer.context.extensions.drawBuffers; - } - - bind(framebuffer, frame) - { - const gl = this.gl; - - this.current = framebuffer; - - if (framebuffer) - { - // TODO cacheing layer! - - const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID] || this.initFramebuffer(framebuffer); - - gl.bindFramebuffer(gl.FRAMEBUFFER, fbo.framebuffer); - // makesure all textures are unbound.. - - // now check for updates... - if (fbo.dirtyId !== framebuffer.dirtyId) - { - fbo.dirtyId = framebuffer.dirtyId; - - if (fbo.dirtyFormat !== framebuffer.dirtyFormat) - { - fbo.dirtyFormat = framebuffer.dirtyFormat; - this.updateFramebuffer(framebuffer); - } - else if (fbo.dirtySize !== framebuffer.dirtySize) - { - fbo.dirtySize = framebuffer.dirtySize; - this.resizeFramebuffer(framebuffer); - } - } - - for (let i = 0; i < framebuffer.colorTextures.length; i++) - { - if (framebuffer.colorTextures[i].texturePart) - { - this.renderer.texture.unbind(framebuffer.colorTextures[i].texture); - } - else - { - this.renderer.texture.unbind(framebuffer.colorTextures[i]); - } - } - - if (framebuffer.depthTexture) - { - this.renderer.texture.unbind(framebuffer.depthTexture); - } - - if (frame) - { - this.setViewport(frame.x, frame.y, frame.width, frame.height); - } - else - { - this.setViewport(0, 0, framebuffer.width, framebuffer.height); - } - } - else - { - gl.bindFramebuffer(gl.FRAMEBUFFER, null); - - if (frame) - { - this.setViewport(frame.x, frame.y, frame.width, frame.height); - } - else - { - this.setViewport(0, 0, this.renderer.width, this.renderer.height); - } - } - } - - setViewport(x, y, width, height) - { - const v = this.viewport; - - if (v.width !== width || v.height !== height || v.x !== x || v.y !== y) - { - v.x = x; - v.y = y; - v.width = width; - v.height = height; - - this.gl.viewport(x, y, width, height); - } - } - - get size() - { - if (this.current) - { - // TODO store temp - return { x: 0, y: 0, width: this.current.width, height: this.current.height }; - } - - return { x: 0, y: 0, width: this.renderer.width, height: this.renderer.height }; - } - - clear(r, g, b, a) - { - const gl = this.gl; - - // TODO clear color can be set only one right? - gl.clearColor(r, g, b, a); - gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); - } - - // private functions... - - initFramebuffer(framebuffer) - { - const gl = this.gl; - - // TODO - make this a class? - const fbo = { - framebuffer: gl.createFramebuffer(), - stencil: null, - dirtyId: 0, - dirtyFormat: 0, - dirtySize: 0, - }; - - framebuffer.glFrameBuffers[this.CONTEXT_UID] = fbo; - - return fbo; - } - - resizeFramebuffer(framebuffer) - { - const gl = this.gl; - - if (framebuffer.stencil || framebuffer.depth) - { - gl.bindRenderbuffer(gl.RENDERBUFFER, this.stencil); - gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); - } - } - - updateFramebuffer(framebuffer) - { - const gl = this.gl; - - const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID]; - - // bind the color texture - const colorTextures = framebuffer.colorTextures; - - let count = colorTextures.length; - - if (!this.drawBufferExtension) - { - count = Math.min(count, 1); - } - - const activeTextures = []; - - for (let i = 0; i < count; i++) - { - const texture = framebuffer.colorTextures[i]; - - if (texture.texturePart) - { - this.renderer.texture.bind(texture.texture, 0); - - gl.framebufferTexture2D(gl.FRAMEBUFFER, - gl.COLOR_ATTACHMENT0 + i, - gl.TEXTURE_CUBE_MAP_NEGATIVE_X + texture.side, - texture.texture._glTextures[this.CONTEXT_UID].texture, - 0); - } - else - { - this.renderer.texture.bind(texture, 0); - - gl.framebufferTexture2D(gl.FRAMEBUFFER, - gl.COLOR_ATTACHMENT0 + i, - gl.TEXTURE_2D, - texture._glTextures[this.CONTEXT_UID].texture, - 0); - } - - activeTextures.push(gl.COLOR_ATTACHMENT0 + i); - } - - if (this.drawBufferExtension && activeTextures.length > 1) - { - this.drawBufferExtension.drawBuffersWEBGL(activeTextures); - } - - if (framebuffer.depthTexture) - { - const depthTextureExt = this.renderer.context.extensions.depthTexture; - - if (depthTextureExt) - { - const depthTexture = framebuffer.depthTexture; - - this.renderer.texture.bind(depthTexture, 0); - - gl.framebufferTexture2D(gl.FRAMEBUFFER, - gl.DEPTH_ATTACHMENT, - gl.TEXTURE_2D, - depthTexture._glTextures[this.CONTEXT_UID].texture, - 0); - } - } - - if (framebuffer.stencil || framebuffer.depth) - { - fbo.stencil = gl.createRenderbuffer(); - - gl.bindRenderbuffer(gl.RENDERBUFFER, fbo.stencil); - - // TODO.. this is depth AND stencil? - gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, fbo.stencil); - gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); - // fbo.enableStencil(); - } - } -} diff --git a/packages/core/src/renderers/systems/MaskSystem.js b/packages/core/src/renderers/systems/MaskSystem.js deleted file mode 100644 index d9c4b1a..0000000 --- a/packages/core/src/renderers/systems/MaskSystem.js +++ /dev/null @@ -1,199 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import AlphaMaskFilter from '../filters/spriteMask/SpriteMaskFilter'; - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class MaskSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - // TODO - we don't need both! - this.scissor = false; - this.scissorData = null; - this.scissorRenderTarget = null; - - this.enableScissor = false; - - this.alphaMaskPool = []; - this.alphaMaskIndex = 0; - } - - /** - * Applies the Mask and adds it to the current filter stack. - * - * @param {PIXI.DisplayObject} target - Display Object to push the mask to - * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. - */ - push(target, maskData) - { - // TODO the root check means scissor rect will not - // be used on render textures more info here: - // https://github.com/pixijs/pixi.js/pull/3545 - - if (maskData.texture) - { - this.pushSpriteMask(target, maskData); - } - else if (this.enableScissor - && !this.scissor - && this.renderer._activeRenderTarget.root - && !this.renderer.stencil.stencilMaskStack.length - && maskData.isFastRect()) - { - const matrix = maskData.worldTransform; - - let rot = Math.atan2(matrix.b, matrix.a); - - // use the nearest degree! - rot = Math.round(rot * (180 / Math.PI)); - - if (rot % 90) - { - this.pushStencilMask(maskData); - } - else - { - this.pushScissorMask(target, maskData); - } - } - else - { - this.pushStencilMask(maskData); - } - } - - /** - * Removes the last mask from the mask stack and doesn't return it. - * - * @param {PIXI.DisplayObject} target - Display Object to pop the mask from - * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. - */ - pop(target, maskData) - { - if (maskData.texture) - { - this.popSpriteMask(target, maskData); - } - else if (this.enableScissor && !this.renderer.stencil.stencilMaskStack.length) - { - this.popScissorMask(target, maskData); - } - else - { - this.popStencilMask(target, maskData); - } - } - - /** - * Applies the Mask and adds it to the current filter stack. - * - * @param {PIXI.RenderTarget} target - Display Object to push the sprite mask to - * @param {PIXI.Sprite} maskData - Sprite to be used as the mask - */ - pushSpriteMask(target, maskData) - { - let alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; - - if (!alphaMaskFilter) - { - alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; - } - - alphaMaskFilter[0].resolution = this.renderer.resolution; - alphaMaskFilter[0].maskSprite = maskData; - - // TODO - may cause issues! - target.filterArea = maskData.getBounds(true); - - this.renderer.filterSystem.pushFilter(target, alphaMaskFilter); - - this.alphaMaskIndex++; - } - - /** - * Removes the last filter from the filter stack and doesn't return it. - * - */ - popSpriteMask() - { - this.renderer.filterSystem.popFilter(); - this.alphaMaskIndex--; - } - - /** - * Applies the Mask and adds it to the current filter stack. - * - * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. - */ - pushStencilMask(maskData) - { - this.renderer.batch.flush(); - this.renderer.stencil.pushStencil(maskData); - } - - /** - * Removes the last filter from the filter stack and doesn't return it. - * - */ - popStencilMask() - { - // this.renderer.currentRenderer.stop(); - this.renderer.stencil.popStencil(); - } - - /** - * - * @param {PIXI.DisplayObject} target - Display Object to push the mask to - * @param {PIXI.Graphics} maskData - The masking data. - */ - pushScissorMask(target, maskData) - { - maskData.renderable = true; - - const renderTarget = this.renderer._activeRenderTarget; - - const bounds = maskData.getBounds(); - - bounds.fit(renderTarget.size); - maskData.renderable = false; - - this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); - - const resolution = this.renderer.resolution; - - this.renderer.gl.scissor( - bounds.x * resolution, - (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, - bounds.width * resolution, - bounds.height * resolution - ); - - this.scissorRenderTarget = renderTarget; - this.scissorData = maskData; - this.scissor = true; - } - - /** - * - * - */ - popScissorMask() - { - this.scissorRenderTarget = null; - this.scissorData = null; - this.scissor = false; - - // must be scissor! - const gl = this.renderer.gl; - - gl.disable(gl.SCISSOR_TEST); - } -} diff --git a/packages/core/src/renderers/systems/ProjectionSystem.js b/packages/core/src/renderers/systems/ProjectionSystem.js deleted file mode 100644 index ec5efdb..0000000 --- a/packages/core/src/renderers/systems/ProjectionSystem.js +++ /dev/null @@ -1,73 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { Matrix } from '@pixi/math'; - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ - -export default class ProjectionSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this.projectionMatrix = new Matrix(); - } - - update(destinationFrame, sourceFrame, resolution, root) - { - this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; - this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; - - this.calculateProjection(this.destinationFrame, this.sourceFrame, resolution, root); - - this.renderer.globalUniforms.uniforms.projectionMatrix = this.projectionMatrix; - this.renderer.globalUniforms.update(); - } - - /** - * Updates the projection matrix based on a projection frame (which is a rectangle) - * - * @param {Rectangle} destinationFrame - The destination frame. - * @param {Rectangle} sourceFrame - The source frame. - */ - calculateProjection(destinationFrame, sourceFrame, resolution, root) - { - const pm = this.projectionMatrix; - - // I don't think we will need this line.. - // pm.identity(); - - if (!root) - { - pm.a = (1 / destinationFrame.width * 2) * resolution; - pm.d = (1 / destinationFrame.height * 2) * resolution; - - pm.tx = -1 - (sourceFrame.x * pm.a); - pm.ty = -1 - (sourceFrame.y * pm.d); - } - else - { - pm.a = (1 / destinationFrame.width * 2) * resolution; - pm.d = (-1 / destinationFrame.height * 2) * resolution; - - pm.tx = -1 - (sourceFrame.x * pm.a); - pm.ty = 1 - (sourceFrame.y * pm.d); - } - } - - /** - * Sets the transform of the active render target to the given matrix - * - * @param {PIXI.Matrix} matrix - The transformation matrix - */ - setTransform()// matrix) - { - // this._activeRenderTarget.transform = matrix; - } -} diff --git a/packages/core/src/renderers/systems/RenderTextureSystem.js b/packages/core/src/renderers/systems/RenderTextureSystem.js deleted file mode 100644 index a133ef1..0000000 --- a/packages/core/src/renderers/systems/RenderTextureSystem.js +++ /dev/null @@ -1,115 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { Rectangle } from '@pixi/math'; - -const tempRect = new Rectangle(); - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ - -export default class RenderTextureSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this.clearColor = renderer._backgroundColorRgba; - - // TODO moe this property somewhere else! - this.defaultMaskStack = []; - this.defaultFilterStack = [{}]; - - // empty render texture? - this.renderTexture = null; - - this.destinationFrame = new Rectangle(); - } - - bind(renderTexture, sourceFrame, destinationFrame) - { - // TODO - do we want this?? - if (this.renderTexture === renderTexture) return; - this.renderTexture = renderTexture; - - const renderer = this.renderer; - - if (renderTexture) - { - const baseTexture = renderTexture.baseTexture; - - if (!destinationFrame) - { - tempRect.width = baseTexture.realWidth; - tempRect.height = baseTexture.realHeight; - - destinationFrame = tempRect; - } - - if (!sourceFrame) - { - sourceFrame = destinationFrame; - } - - this.renderer.framebuffer.bind(baseTexture.frameBuffer, destinationFrame); - - this.renderer.projection.update(destinationFrame, sourceFrame, baseTexture.resolution, false); - this.renderer.stencil.setMaskStack(baseTexture.stencilMaskStack); - } - else - { - // TODO these validation checks happen deeper down.. - // thing they can be avoided.. - if (!destinationFrame) - { - tempRect.width = renderer.width; - tempRect.height = renderer.height; - - destinationFrame = tempRect; - } - - if (!sourceFrame) - { - sourceFrame = destinationFrame; - } - - renderer.framebuffer.bind(null, destinationFrame); - - // TODO store this.. - this.renderer.projection.update(destinationFrame, sourceFrame, this.renderer.resolution, true); - this.renderer.stencil.setMaskStack(this.defaultMaskStack); - } - - this.destinationFrame.copyFrom(destinationFrame); - } - - /** - * Erases the render texture and fills the drawing area with a colour - * - * @param {number} [clearColor] - The colour - * @return {PIXI.Renderer} Returns itself. - */ - clear(clearColor) - { - if (this.renderTexture) - { - clearColor = clearColor || this.renderTexture.baseTexture.clearColor; - } - else - { - clearColor = clearColor || this.clearColor; - } - - this.renderer.framebuffer.clear(clearColor[0], clearColor[1], clearColor[2], clearColor[3]); - } - - resize()// screenWidth, screenHeight) - { - // resize the root only! - this.bind(null); - } -} diff --git a/packages/core/src/renderers/systems/StateSystem.js b/packages/core/src/renderers/systems/StateSystem.js deleted file mode 100755 index d7f4caf..0000000 --- a/packages/core/src/renderers/systems/StateSystem.js +++ /dev/null @@ -1,288 +0,0 @@ -import mapWebGLBlendModesToPixi from '../utils/mapWebGLBlendModesToPixi'; -import WebGLSystem from './WebGLSystem'; -import WebGLState from '../State'; - -const BLEND = 0; -const OFFSET = 1; -const CULLING = 2; -const DEPTH_TEST = 3; -const WINDING = 4; - -/** - * A WebGL state machines - * - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class StateSystem extends WebGLSystem -{ - /** - * @param {WebGLRenderingContext} gl - The current WebGL rendering context - */ - constructor(renderer) - { - super(renderer); - - this.gl = null; - - this.maxAttribs = null; - - // check we have vao.. - this.nativeVaoExtension = null; - - this.attribState = null; - - this.stateId = 0; - this.polygonOffset = 0; - this.blendMode = 17; - - this.map = []; - - // map functions for when we set state.. - this.map[BLEND] = this.setBlend; - this.map[OFFSET] = this.setOffset; - this.map[CULLING] = this.setCullFace; - this.map[DEPTH_TEST] = this.setDepthTest; - this.map[WINDING] = this.setFrontFace; - - this.checks = []; - - this.defaultState = new WebGLState(); - this.defaultState.blend = true; - this.defaultState.depth = true; - } - - contextChange(gl) - { - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; - - this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); - - // check we have vao.. - this.nativeVaoExtension = ( - gl.getExtension('OES_vertex_array_object') - || gl.getExtension('MOZ_OES_vertex_array_object') - || gl.getExtension('WEBKIT_OES_vertex_array_object') - ); - - this.attribState = { - tempAttribState: new Array(this.maxAttribs), - attribState: new Array(this.maxAttribs), - }; - - this.blendModes = mapWebGLBlendModesToPixi(gl); - - this.setState(this.defaultState); - - this.reset(); - } - - /** - * Sets the current state - * - * @param {*} state - The state to set. - */ - setState(state) - { - state = state || this.defaultState; - - // TODO maybe to an object check? ( this.state === state )? - if (this.stateId !== state.data) - { - let diff = this.stateId ^ state.data; - let i = 0; - - // order from least to most common - while (diff) - { - if (diff & 1) - { - // state change! - this.map[i].call(this, !!(state.data & (1 << i))); - } - - diff = diff >> 1; - i++; - } - - this.stateId = state.data; - } - - // based on the above settings we check for specific modes.. - // for example if blend is active we check and set the blend modes - // or of polygon offset is active we check the poly depth. - for (let i = 0; i < this.checks.length; i++) - { - this.checks[i](this, state); - } - } - - /** - * Enables or disabled blending. - * - * @param {boolean} value - Turn on or off webgl blending. - */ - setBlend(value) - { - this.updateCheck(StateSystem.checkBlendMode, value); - - this.gl[value ? 'enable' : 'disable'](this.gl.BLEND); - } - - /** - * Enables or disable polygon offset fill - * - * @param {boolean} value - Turn on or off webgl polygon offset testing. - */ - setOffset(value) - { - this.gl[value ? 'enable' : 'disable'](this.gl.POLYGON_OFFSET_FILL); - } - - /** - * Sets whether to enable or disable depth test. - * - * @param {boolean} value - Turn on or off webgl depth testing. - */ - setDepthTest(value) - { - this.gl[value ? 'enable' : 'disable'](this.gl.DEPTH_TEST); - } - - /** - * Sets whether to enable or disable cull face. - * - * @param {boolean} value - Turn on or off webgl cull face. - */ - setCullFace(value) - { - this.gl[value ? 'enable' : 'disable'](this.gl.CULL_FACE); - } - - /** - * Sets the gl front face. - * - * @param {boolean} value - true is clockwise and false is counter-clockwise - */ - setFrontFace(value) - { - this.gl.frontFace(this.gl[value ? 'CW' : 'CCW']); - } - - /** - * Sets the blend mode. - * - * @param {number} value - The blend mode to set to. - */ - setBlendMode(value) - { - if (value === this.blendMode) - { - return; - } - - this.blendMode = value; - this.gl.blendFunc(this.blendModes[value][0], this.blendModes[value][1]); - } - - /** - * Sets the polygon offset. - * - * @param {number} value - the polygon offset - * @param {number} scale - the polygon offset scale - */ - setPolygonOffset(value, scale) - { - this.gl.polygonOffset(value, scale); - } - - /** - * Disables all the vaos in use - * - */ - resetAttributes() - { - for (let i = 0; i < this.attribState.tempAttribState.length; i++) - { - this.attribState.tempAttribState[i] = 0; - } - - for (let i = 0; i < this.attribState.attribState.length; i++) - { - this.attribState.attribState[i] = 0; - } - - // im going to assume one is always active for performance reasons. - for (let i = 1; i < this.maxAttribs; i++) - { - this.gl.disableVertexAttribArray(i); - } - } - - // used - /** - * Resets all the logic and disables the vaos - */ - reset() - { - // unbind any VAO if they exist.. - if (this.nativeVaoExtension) - { - this.nativeVaoExtension.bindVertexArrayOES(null); - } - - // reset all attributes.. - this.resetAttributes(); - - this.gl.pixelStorei(this.gl.UNPACK_FLIP_Y_WEBGL, false); - - this.setBlendMode(0); - - // TODO? - // this.setState(this.defaultState); - } - - /** - * checks to see which updates should be checked based on which settings have been activated - * for example if blend is enabled then we shold check the blend modes each time the state is changed - * or if poygon fill is activated then we need to check if the polygone offset changes. - * The idea is that we only check what we have too. - * - * @param {Function} func the checking function to add or remove - * @param {boolean} value should the check function be added or removed. - */ - updateCheck(func, value) - { - const index = this.checks.indexOf(func); - - if (value && index === -1) - { - this.checks.push(func); - } - else if (!value && index !== -1) - { - this.checks.splice(index, 1); - } - } - - /** - * A private little wrapper function that we call to check the blend mode. - * - * @static - * @private - * @param {PIXI.StateSystem} System the System to perform the state check on - * @param {PIXI.State} state the state that the blendMode will pulled from - */ - static checkBlendMode(system, state) - { - system.setBlendMode(state.blendMode); - } - - // TODO - add polygon offset? -} diff --git a/packages/core/src/renderers/systems/StencilSystem.js b/packages/core/src/renderers/systems/StencilSystem.js deleted file mode 100644 index 5d538cb..0000000 --- a/packages/core/src/renderers/systems/StencilSystem.js +++ /dev/null @@ -1,133 +0,0 @@ -import WebGLSystem from './WebGLSystem'; - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class StencilSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - this.stencilMaskStack = []; - } - - /** - * Changes the mask stack that is used by this System. - * - * @param {PIXI.Graphics[]} stencilMaskStack - The mask stack - */ - setMaskStack(stencilMaskStack) - { - const gl = this.renderer.gl; - - if (stencilMaskStack.length !== this.stencilMaskStack.length) - { - if (stencilMaskStack.length === 0) - { - gl.disable(gl.STENCIL_TEST); - } - else - { - gl.enable(gl.STENCIL_TEST); - } - } - - this.stencilMaskStack = stencilMaskStack; - } - - /** - * Applies the Mask and adds it to the current stencil stack. @alvin - * - * @param {PIXI.Graphics} graphics - The mask - */ - pushStencil(graphics) - { - this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); - - // this.renderer._activeRenderTarget.attachStencilBuffer(); - - const gl = this.renderer.gl; - const prevMaskCount = this.stencilMaskStack.length; - - if (prevMaskCount === 0) - { - gl.enable(gl.STENCIL_TEST); - } - - this.stencilMaskStack.push(graphics); - - // Increment the refference stencil value where the new mask overlaps with the old ones. - gl.colorMask(false, false, false, false); - gl.stencilFunc(gl.EQUAL, prevMaskCount, this._getBitwiseMask()); - gl.stencilOp(gl.KEEP, gl.KEEP, gl.INCR); - this.renderer.plugins.graphics.render(graphics); - - this._useCurrent(); - } - - /** - * Removes the last mask from the stencil stack. @alvin - */ - popStencil() - { - this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); - - const gl = this.renderer.gl; - const graphics = this.stencilMaskStack.pop(); - - if (this.stencilMaskStack.length === 0) - { - // the stack is empty! - gl.disable(gl.STENCIL_TEST); - gl.clear(gl.STENCIL_BUFFER_BIT); - gl.clearStencil(0); - } - else - { - // Decrement the refference stencil value where the popped mask overlaps with the other ones - gl.colorMask(false, false, false, false); - gl.stencilOp(gl.KEEP, gl.KEEP, gl.DECR); - this.renderer.plugins.graphics.render(graphics); - - this._useCurrent(); - } - } - - /** - * Setup renderer to use the current stencil data. - */ - _useCurrent() - { - const gl = this.renderer.gl; - - gl.colorMask(true, true, true, true); - gl.stencilFunc(gl.EQUAL, this.stencilMaskStack.length, this._getBitwiseMask()); - gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP); - } - - /** - * Fill 1s equal to the number of acitve stencil masks. - * - * @return {number} The bitwise mask. - */ - _getBitwiseMask() - { - return (1 << this.stencilMaskStack.length) - 1; - } - - /** - * Destroys the mask stack. - * - */ - destroy() - { - super.destroy(this); - - this.stencilMaskStack = null; - } -} diff --git a/packages/core/src/renderers/systems/WebGLSystem.js b/packages/core/src/renderers/systems/WebGLSystem.js deleted file mode 100644 index d3d5b31..0000000 --- a/packages/core/src/renderers/systems/WebGLSystem.js +++ /dev/null @@ -1,41 +0,0 @@ -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this manager works for. - */ - constructor(renderer) - { - /** - * The renderer this manager works for. - * - * @member {PIXI.Renderer} - */ - this.renderer = renderer; - - this.renderer.runners.contextChange.add(this); - } - - /** - * Generic method called when there is a WebGL context change. - * - */ - contextChange() - { - // do some codes init! - } - - /** - * Generic destroy methods to be overridden by the subclass - * - */ - destroy() - { - this.renderer.runners.contextChange.remove(this); - this.renderer = null; - } -} diff --git a/packages/core/src/renderers/systems/filter/FilterSystem.js b/packages/core/src/renderers/systems/filter/FilterSystem.js deleted file mode 100644 index e615037..0000000 --- a/packages/core/src/renderers/systems/filter/FilterSystem.js +++ /dev/null @@ -1,431 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; - -import RenderTexture from '../../../textures/RenderTexture'; -import Quad from '../../utils/Quad'; -import QuadUv from '../../utils/QuadUv'; -import { Rectangle } from '@pixi/math'; -import * as filterTransforms from '../../filters/filterTransforms'; -import bitTwiddle from 'bit-twiddle'; -import UniformGroup from '../../../shader/UniformGroup'; -import { DRAW_MODES } from '../../../../../constants'; - -// -/** - * @ignore - * @class - */ -class FilterState -{ - /** - * - */ - constructor() - { - this.renderTexture = null; - this.sourceFrame = new Rectangle(); - this.destinationFrame = new Rectangle(); - this.filters = []; - this.target = null; - this.legacy = false; - this.resolution = 1; - } -} - -/** - * @class - * @memberof PIXI.systems - * @extends PIXI.systems.WebGLSystem - */ -export default class FilterSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - /** - * stores a bunch of PO2 textures used for filtering - * @type {Object} - */ - this.texturePool = {}; - - /** - * a pool for storing filter states, save us creating new ones each tick - * @type {Array} - */ - this.statePool = []; - - /** - * A very simple geometry used when drawing a filter effect to the screen - * @type {Quad} - */ - this.quad = new Quad(); - - this.quadUv = new QuadUv(); - - /** - * Temporary rect for maths - * @type {PIXI.Rectangle} - */ - this.tempRect = new Rectangle(); - - this.activeState = {}; - - /** - * this uniform group is attached to filter uniforms when used - * @type {UniformGroup} - */ - this.globalUniforms = new UniformGroup({ - sourceFrame: this.tempRect, - destinationFrame: this.tempRect, - - // legacy variables - filterArea: new Float32Array(4), - filterClamp: new Float32Array(4), - }, true); - } - - /** - * Adds a new filter to the System. - * - * @param {PIXI.DisplayObject} target - The target of the filter to render. - * @param {PIXI.Filter[]} filters - The filters to apply. - */ - push(target, filters) - { - const renderer = this.renderer; - const filterStack = this.renderer.renderTexture.defaultFilterStack; - const state = this.statePool.pop() || new FilterState(); - - let resolution = filters[0].resolution; - let padding = filters[0].padding; - let autoFit = filters[0].autoFit; - let legacy = filters[0].legacy; - - for (let i = 1; i < filters.length; i++) - { - const filter = filters[i]; - - // lets use the lowest resolution.. - resolution = Math.min(resolution, filter.resolution); - // and the largest amount of padding! - padding = Math.max(padding, filter.padding); - // only auto fit if all filters are autofit - autoFit = autoFit || filter.autoFit; - - legacy = legacy || filter.legacy; - } - - filterStack.push(state); - - state.resolution = resolution; - - state.legacy = legacy; - - // round to whole number based on resolution - // TODO move that to the shader too? - state.sourceFrame = target.filterArea || target.getBounds(true); - - state.sourceFrame.pad(padding); - - if (autoFit) - { - state.sourceFrame.fit(this.renderer.renderTexture.destinationFrame); - } - - state.sourceFrame.round(resolution); - - state.renderTexture = this.getPotFilterTexture(state.sourceFrame.width, state.sourceFrame.height, resolution); - state.filters = filters; - - state.destinationFrame.width = state.renderTexture.width; - state.destinationFrame.height = state.renderTexture.height; - - state.renderTexture.filterFrame = state.sourceFrame; - renderer.renderTexture.bind(state.renderTexture, state.sourceFrame);// /, state.destinationFrame); - renderer.renderTexture.clear(); - } - - /** - * Pops off the filter and applies it. - * - */ - pop() - { - const renderer = this.renderer; - const filterStack = renderer.renderTexture.defaultFilterStack; - const state = filterStack.pop(); - const filters = state.filters; - - this.activeState = state; - - const globalUniforms = this.globalUniforms.uniforms; - - globalUniforms.sourceFrame = state.sourceFrame; - globalUniforms.destinationFrame = state.destinationFrame; - globalUniforms.resolution = state.resolution; - - // only update the rect if its legacy.. - if (state.legacy) - { - const filterArea = globalUniforms.filterArea; - const filterClamp = globalUniforms.filterClamp; - - filterArea[0] = state.destinationFrame.width; - filterArea[1] = state.destinationFrame.height; - filterArea[2] = state.sourceFrame.x; - filterArea[3] = state.sourceFrame.y; - - filterClamp[0] = 0.5 / state.resolution / state.destinationFrame.width; - filterClamp[1] = 0.5 / state.resolution / state.destinationFrame.height; - filterClamp[2] = (state.sourceFrame.width - 0.5) / state.resolution / state.destinationFrame.width; - filterClamp[3] = (state.sourceFrame.height - 0.5) / state.resolution / state.destinationFrame.height; - } - - this.globalUniforms.update(); - - const lastState = filterStack[filterStack.length - 1]; - - if (filters.length === 1) - { - filters[0].apply(this, state.renderTexture, lastState.renderTexture, false, state); - - this.returnFilterTexture(state.renderTexture); - } - else - { - let flip = state.renderTexture; - let flop = this.getPotFilterTexture( - flip.width, - flip.height, - state.resolution - ); - - flop.filterFrame = flip.filterFrame; - - let i = 0; - - for (i = 0; i < filters.length - 1; ++i) - { - filters[i].apply(this, flip, flop, true, state); - - const t = flip; - - flip = flop; - flop = t; - } - - filters[i].apply(this, flip, lastState.renderTexture, false, state); - - this.returnFilterTexture(flip); - this.returnFilterTexture(flop); - } - - this.statePool.push(state); - } - - /** - * Draws a filter. - * - * @param {PIXI.Filter} filter - The filter to draw. - * @param {PIXI.RenderTarget} input - The input render target. - * @param {PIXI.RenderTarget} output - The target to output to. - * @param {boolean} clear - Should the output be cleared before rendering to it - */ - applyFilter(filter, input, output, clear) - { - const renderer = this.renderer; - - renderer.renderTexture.bind(output, output ? output.filterFrame : null); - - if (clear) - { - // gl.disable(gl.SCISSOR_TEST); - renderer.renderTexture.clear(); - // gl.enable(gl.SCISSOR_TEST); - } - - // set the uniforms.. - filter.uniforms.uSampler = input; - filter.uniforms.filterGlobals = this.globalUniforms; - - // TODO make it so that the order of this does not matter.. - // because it does at the moment cos of global uniforms. - // they need to get resynced - - renderer.state.setState(filter.state); - renderer.shader.bind(filter); - - if (filter.legacy) - { - this.quadUv.map(input._frame, input.filterFrame); - - renderer.geometry.bind(this.quadUv); - renderer.geometry.draw(DRAW_MODES.TRIANGLES); - } - else - { - renderer.geometry.bind(this.quad); - renderer.geometry.draw(DRAW_MODES.TRIANGLE_STRIP); - } - } - - /** - * Calculates the mapped matrix. - * - * TODO playing around here.. this is temporary - (will end up in the shader) - * this returns a matrix that will normalise map filter cords in the filter to screen space - * - * @param {PIXI.Matrix} outputMatrix - the matrix to output to. - * @return {PIXI.Matrix} The mapped matrix. - */ - calculateScreenSpaceMatrix(outputMatrix) - { - const currentState = this.activeState; - - return filterTransforms.calculateScreenSpaceMatrix( - outputMatrix, - currentState.sourceFrame, - currentState.destinationFrame - ); - } - - /** - * This will map the filter coord so that a texture can be used based on the transform of a sprite - * - * @param {PIXI.Matrix} outputMatrix - The matrix to output to. - * @param {PIXI.Sprite} sprite - The sprite to map to. - * @return {PIXI.Matrix} The mapped matrix. - */ - calculateSpriteMatrix(outputMatrix, sprite) - { - const currentState = this.activeState; - - return filterTransforms.calculateSpriteMatrix( - outputMatrix, - currentState.sourceFrame, - currentState.destinationFrame, - sprite - ); - } - - /** - * Destroys this Filter System. - * - * @param {boolean} [contextLost=false] context was lost, do not free shaders - * - */ - destroy(contextLost = false) - { - if (!contextLost) - { - this.emptyPool(); - } - else - { - this.texturePool = {}; - } - } - - /** - * Gets a Power-of-Two render texture. - * - * TODO move to a seperate class could be on renderer? - * also - could cause issue with multiple contexts? - * - * @private - * @param {WebGLRenderingContext} gl - The webgl rendering context - * @param {number} minWidth - The minimum width of the render target. - * @param {number} minHeight - The minimum height of the render target. - * @param {number} resolution - The resolution of the render target. - * @return {PIXI.RenderTarget} The new render target. - */ - getPotFilterTexture(minWidth, minHeight, resolution) - { - minWidth = bitTwiddle.nextPow2(minWidth); - minHeight = bitTwiddle.nextPow2(minHeight); - resolution = resolution || 1; - - const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); - - if (!this.texturePool[key]) - { - this.texturePool[key] = []; - } - - let renderTexture = this.texturePool[key].pop(); - - if (!renderTexture) - { - // temporary bypass cache.. - // internally - this will cause a texture to be bound.. - renderTexture = RenderTexture.create({ - width: minWidth, - height: minHeight, - resolution, - }); - } - - return renderTexture; - } - - /** - * Gets extra render texture to use inside current filter - * - * @param {number} resolution resolution of the renderTexture - * @returns {PIXI.RenderTexture} - */ - getFilterTexture(resolution) - { - const rt = this.activeState.renderTexture; - - const filterTexture = this.getPotFilterTexture(rt.width, rt.height, resolution || rt.baseTexture.resolution); - - filterTexture.filterFrame = rt.filterFrame; - - return filterTexture; - } - - /** - * Frees a render texture back into the pool. - * - * @param {PIXI.RenderTarget} renderTexture - The renderTarget to free - */ - returnFilterTexture(renderTexture) - { - renderTexture.filterFrame = null; - - const base = renderTexture.baseTexture; - - const minWidth = base.width; - const minHeight = base.height; - - const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); - - this.texturePool[key].push(renderTexture); - } - - /** - * Empties the texture pool. - * - */ - emptyPool() - { - for (const i in this.texturePool) - { - const textures = this.texturePool[i]; - - if (textures) - { - for (let j = 0; j < textures.length; j++) - { - textures[j].destroy(true); - } - } - } - - this.texturePool = {}; - } -} diff --git a/packages/core/src/renderers/systems/geometry/GLBuffer.js b/packages/core/src/renderers/systems/geometry/GLBuffer.js deleted file mode 100644 index 0477e8a..0000000 --- a/packages/core/src/renderers/systems/geometry/GLBuffer.js +++ /dev/null @@ -1,9 +0,0 @@ -export default class GLBuffer -{ - constructor(buffer) - { - this.buffer = buffer; - this.updateID = -1; - this.byteLength = -1; - } -} diff --git a/packages/core/src/renderers/systems/geometry/GeometrySystem.js b/packages/core/src/renderers/systems/geometry/GeometrySystem.js deleted file mode 100644 index 26c8e9e..0000000 --- a/packages/core/src/renderers/systems/geometry/GeometrySystem.js +++ /dev/null @@ -1,388 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; -import GLBuffer from './GLBuffer'; - -const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class GeometrySystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this._activeGeometry = null; - this._activeVao = null; - - this.hasVao = true; - this.hasInstance = true; - } - - /** - * Sets up the renderer context and necessary buffers. - * - * @private - */ - contextChange() - { - const gl = this.gl = this.renderer.gl; - - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - - // webgl2 - if (!gl.createVertexArray) - { - // webgl 1! - let nativeVaoExtension = this.renderer.context.extensions.vertexArrayObject; - - if (this.renderer.legacy) - { - nativeVaoExtension = null; - } - - if (nativeVaoExtension) - { - gl.createVertexArray = () => - nativeVaoExtension.createVertexArrayOES(); - - gl.bindVertexArray = (vao) => - nativeVaoExtension.bindVertexArrayOES(vao); - - gl.deleteVertexArray = (vao) => - nativeVaoExtension.deleteVertexArrayOES(vao); - } - else - { - this.hasVao = false; - gl.createVertexArray = () => - { - // empty - }; - - gl.bindVertexArray = () => - { - // empty - }; - - gl.deleteVertexArray = () => - { - // empty - }; - } - } - - if (!gl.vertexAttribDivisor) - { - const instanceExt = gl.getExtension('ANGLE_instanced_arrays'); - - if (instanceExt) - { - gl.vertexAttribDivisor = (a, b) => - instanceExt.vertexAttribDivisorANGLE(a, b); - - gl.drawElementsInstanced = (a, b, c, d, e) => - instanceExt.drawElementsInstancedANGLE(a, b, c, d, e); - - gl.drawArraysInstanced = (a, b, c, d) => - instanceExt.drawArraysInstancedANGLE(a, b, c, d); - } - else - { - this.hasInstance = false; - } - } - } - - /** - * Binds geometry so that is can be drawn. Creating a Vao if required - * @private - * @param {PIXI.Geometry} geometry instance of geometry to bind - */ - bind(geometry, shader) - { - shader = shader || this.renderer.shader.shader; - - const gl = this.gl; - - // not sure the best way to address this.. - // currently different shaders require different VAOs for the same geometry - // Still mulling over the best way to solve this one.. - // will likely need to modify the shader attribute locations at run time! - let vaos = geometry.glVertexArrayObjects[this.CONTEXT_UID]; - - if (!vaos) - { - geometry.glVertexArrayObjects[this.CONTEXT_UID] = vaos = {}; - } - - const vao = vaos[shader.program.id] || this.initGeometryVao(geometry, shader.program); - - this._activeGeometry = geometry; - - if (this._activeVao !== vao) - { - this._activeVao = vao; - - if (this.hasVao) - { - gl.bindVertexArray(vao); - } - else - { - this.activateVao(geometry, shader.program); - } - } - - // TODO - optimise later! - // don't need to loop through if nothing changed! - // maybe look to add an 'autoupdate' to geometry? - this.updateBuffers(); - } - - reset() - { - this.unbind(); - } - - updateBuffers() - { - const geometry = this._activeGeometry; - const gl = this.gl; - - for (let i = 0; i < geometry.buffers.length; i++) - { - const buffer = geometry.buffers[i]; - - const glBuffer = buffer._glBuffers[this.CONTEXT_UID]; - - if (buffer._updateID !== glBuffer.updateID) - { - glBuffer.updateID = buffer._updateID; - - // TODO can cache this on buffer! maybe added a getter / setter? - const type = buffer.index ? gl.ELEMENT_ARRAY_BUFFER : gl.ARRAY_BUFFER; - const drawType = buffer.static ? gl.STATIC_DRAW : gl.DYNAMIC_DRAW; - - gl.bindBuffer(type, glBuffer.buffer); - - if (glBuffer.byteLength >= buffer.data.byteLength) - { - // offset is always zero for now! - gl.bufferSubData(type, 0, buffer.data); - } - else - { - gl.bufferData(type, buffer.data, drawType); - } - } - } - } - - checkCompatability(geometry, program) - { - // geometry must have at least all the attributes that the shader requires. - const geometryAttributes = geometry.attributes; - const shaderAttributes = program.attributeData; - - for (const j in shaderAttributes) - { - if (!geometryAttributes[j]) - { - throw new Error(`shader and geometry incompatible, geometry missing the "${j}" attribute`); - } - } - } - - /** - * Creates a Vao with the same structure as the geometry and stores it on the geometry. - * @private - * @param {PIXI.Geometry} geometry instance of geometry to to generate Vao for - */ - initGeometryVao(geometry, program) - { - this.checkCompatability(geometry, program); - - const gl = this.gl; - const CONTEXT_UID = this.CONTEXT_UID; - const buffers = geometry.buffers; - const attributes = geometry.attributes; - - const tempStride = {}; - const tempStart = {}; - - for (const j in buffers) - { - tempStride[j] = 0; - tempStart[j] = 0; - } - - for (const j in attributes) - { - if (!attributes[j].size && program.attributeData[j]) - { - attributes[j].size = program.attributeData[j].size; - } - - tempStride[attributes[j].buffer] += attributes[j].size * byteSizeMap[attributes[j].type]; - } - - for (const j in attributes) - { - const attribute = attributes[j]; - const attribSize = attribute.size; - - if (attribute.stride === undefined) - { - if (tempStride[attribute.buffer] === attribSize * byteSizeMap[attribute.type]) - { - attribute.stride = 0; - } - else - { - attribute.stride = tempStride[attribute.buffer]; - } - } - - if (attribute.start === undefined) - { - attribute.start = tempStart[attribute.buffer]; - - tempStart[attribute.buffer] += attribSize * byteSizeMap[attribute.type]; - } - } - - // first update - and create the buffers! - // only create a gl buffer if it actually gets - for (let i = 0; i < buffers.length; i++) - { - const buffer = buffers[i]; - - if (!buffer._glBuffers[CONTEXT_UID]) - { - buffer._glBuffers[CONTEXT_UID] = new GLBuffer(gl.createBuffer()); - } - } - - // TODO - maybe make this a data object? - // lets wait to see if we need to first! - const vao = gl.createVertexArray(); - - gl.bindVertexArray(vao); - - this.activateVao(geometry, program); - - gl.bindVertexArray(null); - - // add it to the cache! - geometry.glVertexArrayObjects[this.CONTEXT_UID][program.id] = vao; - - return vao; - } - - activateVao(geometry, program) - { - const gl = this.gl; - const CONTEXT_UID = this.CONTEXT_UID; - const buffers = geometry.buffers; - const attributes = geometry.attributes; - - if (geometry.indexBuffer) - { - // first update the index buffer if we have one.. - gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, geometry.indexBuffer._glBuffers[CONTEXT_UID].buffer); - } - - let lastBuffer = null; - - // add a new one! - for (const j in attributes) - { - const attribute = attributes[j]; - const buffer = buffers[attribute.buffer]; - const glBuffer = buffer._glBuffers[CONTEXT_UID]; - - if (program.attributeData[j]) - { - if (lastBuffer !== glBuffer) - { - gl.bindBuffer(gl.ARRAY_BUFFER, glBuffer.buffer); - - lastBuffer = glBuffer; - } - - const location = program.attributeData[j].location; - - // TODO introduce state again - // we can optimise this for older devices that have no VAOs - gl.enableVertexAttribArray(location); - - gl.vertexAttribPointer(location, - attribute.size, - attribute.type || gl.FLOAT, - attribute.normalized, - attribute.stride, - attribute.start); - - if (attribute.instance) - { - // TODO calculate instance count based of this... - if (this.hasInstance) - { - gl.vertexAttribDivisor(location, 1); - } - else - { - throw new Error('geometry error, GPU Instancing is not supported on this device'); - } - } - } - } - } - - draw(type, size, start, instanceCount) - { - const gl = this.gl; - const geometry = this._activeGeometry; - - // TODO.. this should not change so maybe cache the function? - - if (geometry.indexBuffer) - { - if (geometry.instanced) - { - /* eslint-disable max-len */ - gl.drawElementsInstanced(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2, instanceCount || 1); - /* eslint-enable max-len */ - } - else - { - gl.drawElements(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2); - } - } - else - if (geometry.instanced) - { - // TODO need a better way to calculate size.. - gl.drawArraysInstanced(type, start, size || geometry.getSize(), instanceCount || 1); - } - else - { - gl.drawArrays(type, start, size || geometry.getSize()); - } - - return this; - } - - unbind() - { - this.gl.bindVertexArray(null); - this._activeVao = null; - this._activeGeometry = null; - } -} diff --git a/packages/core/src/renderers/systems/geometry/setVertexAttribArrays.js b/packages/core/src/renderers/systems/geometry/setVertexAttribArrays.js deleted file mode 100644 index 23b6959..0000000 --- a/packages/core/src/renderers/systems/geometry/setVertexAttribArrays.js +++ /dev/null @@ -1,54 +0,0 @@ -// var GL_MAP = {}; - -/** - * @param gl {WebGLRenderingContext} The current WebGL context - * @param attribs {*} - * @param state {*} - */ -export default function setVertexAttribArrays(gl, attribs, state) -{ - let i; - - if (state) - { - const tempAttribState = state.tempAttribState; - const attribState = state.attribState; - - for (i = 0; i < tempAttribState.length; i++) - { - tempAttribState[i] = false; - } - - // set the new attribs - for (i = 0; i < attribs.length; i++) - { - tempAttribState[attribs[i].attribute.location] = true; - } - - for (i = 0; i < attribState.length; i++) - { - if (attribState[i] !== tempAttribState[i]) - { - attribState[i] = tempAttribState[i]; - - if (state.attribState[i]) - { - gl.enableVertexAttribArray(i); - } - else - { - gl.disableVertexAttribArray(i); - } - } - } - } - else - { - for (i = 0; i < attribs.length; i++) - { - const attrib = attribs[i]; - - gl.enableVertexAttribArray(attrib.attribute.location); - } - } -} diff --git a/packages/core/src/renderers/systems/index.js b/packages/core/src/renderers/systems/index.js deleted file mode 100644 index c843f03..0000000 --- a/packages/core/src/renderers/systems/index.js +++ /dev/null @@ -1,6 +0,0 @@ -/** - * Systems are individual components to the Renderer pipeline. - * @namespace PIXI.systems - */ -export { default as WebGLSystem } from './WebGLSystem'; -export { default as FilterSystem } from './filter/FilterSystem'; diff --git a/packages/core/src/renderers/systems/shader/GLProgram.js b/packages/core/src/renderers/systems/shader/GLProgram.js deleted file mode 100644 index 92980ca..0000000 --- a/packages/core/src/renderers/systems/shader/GLProgram.js +++ /dev/null @@ -1,43 +0,0 @@ -/** - * Helper class to create a webGL Program - * - * @class - * @private - */ -export default class GLProgram -{ - constructor(program, uniformData) - { - /** - * The shader program - * - * @member {WebGLProgram} - */ - this.program = program; - - /** - * holds the uniform data which contains uniform locations - * and current uniform values used for caching and preventing undeed GPU commands - * @type {Object} - */ - this.uniformData = uniformData; - - /** - * uniformGroups holds the various upload functions for the shader. Each uniform group - * and program have a unique upload function generated. - * @type {Object} - */ - this.uniformGroups = {}; - } - - /** - * Destroys this program - * TODO - */ - destroy() - { - this.uniformData = null; - this.uniformGroups = null; - this.program = null; - } -} diff --git a/packages/core/src/renderers/systems/shader/ShaderSystem.js b/packages/core/src/renderers/systems/shader/ShaderSystem.js deleted file mode 100644 index 6cf57b6..0000000 --- a/packages/core/src/renderers/systems/shader/ShaderSystem.js +++ /dev/null @@ -1,170 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; -import GLProgram from './GLProgram'; -import generateUniformsSync from '../../../shader/generateUniformsSync'; -import defaultValue from '../../../shader/defaultValue'; -import compileProgram from './shader/compileProgram'; - -let UID = 0; - -/** - * Helper class to create a webGL Texture - * - * @class - * @memberof PIXI - */ -export default class ShaderSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - A reference to the current renderer - */ - constructor(renderer) - { - super(renderer); - - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = null; - - this.shader = null; - this.program = null; - - this.id = UID++; - } - - contextChange(gl) - { - this.gl = gl; - } - - /** - * Changes the current shader to the one given in parameter - * - * @param {PIXI.Shader} shader - the new shader - * @param {boolean} dontSync - false if the shader should automatically sync its uniforms. - * @returns {PIXI.glCore.glProgram} the glProgram that belongs to the shader. - */ - bind(shader, dontSync) - { - shader.uniforms.globals = this.renderer.globalUniforms; - - const program = shader.program; - const glProgram = program.glPrograms[this.renderer.CONTEXT_UID] || this.generateShader(shader); - - this.shader = shader; - - // TODO - some current pixi plugins bypass this.. so it not safe to use yet.. - if (this.program !== program) - { - this.program = program; - this.gl.useProgram(glProgram.program); - } - - if (!dontSync) - { - this.syncUniformGroup(shader.uniformGroup); - } - - return glProgram; - } - - /** - * Uploads the uniforms values to the currently bound shader. - * - * @param {object} uniforms - the uniforms valiues that be applied to the current shader - */ - setUniforms(uniforms) - { - const shader = this.shader.program; - const glProgram = shader.glPrograms[this.renderer.CONTEXT_UID]; - - shader.syncUniforms(glProgram.uniformData, uniforms, this.renderer); - } - - syncUniformGroup(group) - { - const glProgram = this.getglProgram(); - - if (!group.static || group.dirtyId !== glProgram.uniformGroups[group.id]) - { - glProgram.uniformGroups[group.id] = group.dirtyId; - const syncFunc = group.syncUniforms[this.shader.program.id] || this.createSyncGroups(group); - - syncFunc(glProgram.uniformData, group.uniforms, this.renderer); - } - } - - createSyncGroups(group) - { - group.syncUniforms[this.shader.program.id] = generateUniformsSync(group, this.shader.program.uniformData); - - return group.syncUniforms[this.shader.program.id]; - } - - /** - * Returns the underlying GLShade rof the currently bound shader. - * This can be handy for when you to have a little more control over the setting of your uniforms. - * - * @return {PIXI.glCore.Shader} the glProgram for the currently bound Shader for this context - */ - getglProgram() - { - if (this.shader) - { - return this.shader.program.glPrograms[this.renderer.CONTEXT_UID]; - } - - return null; - } - - /** - * Generates a glProgram verion of the Shader provided. - * - * @private - * @param {PIXI.Shader} shader the shader that the glProgram will be based on. - * @return {PIXI.glCore.glProgram} A shiney new glProgram - */ - generateShader(shader) - { - const gl = this.gl; - - const program = shader.program; - - const attribMap = {}; - - for (const i in program.attributeData) - { - attribMap[i] = program.attributeData[i].location; - } - - const shaderProgram = compileProgram(gl, program.vertexSrc, program.fragmentSrc, attribMap); - const uniformData = {}; - - for (const i in program.uniformData) - { - const data = program.uniformData[i]; - - uniformData[i] = { - location: gl.getUniformLocation(shaderProgram, i), - value: defaultValue(data.type, data.size), - }; - } - - const glProgram = new GLProgram(shaderProgram, uniformData); - - program.glPrograms[this.renderer.CONTEXT_UID] = glProgram; - - return glProgram; - } - - /** - * Destroys this System and removes all its textures - */ - destroy() - { - // TODO implement destroy method for ShaderSystem - this.destroyed = true; - } -} diff --git a/packages/core/src/renderers/systems/shader/shader/compileProgram.js b/packages/core/src/renderers/systems/shader/shader/compileProgram.js deleted file mode 100644 index 8c6ccb9..0000000 --- a/packages/core/src/renderers/systems/shader/shader/compileProgram.js +++ /dev/null @@ -1,78 +0,0 @@ -/** - * @method compileProgram - * @memberof PIXI.glCore.shader - * @param gl {WebGLRenderingContext} The current WebGL context {WebGLProgram} - * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. - * @param fragmentSrc {string|string[]} The fragment shader source as an array of strings. - * @param attributeLocations {Object} An attribute location map that lets you manually set the attribute locations - * @return {WebGLProgram} the shader program - */ -export default function compileProgram(gl, vertexSrc, fragmentSrc, attributeLocations) -{ - const glVertShader = compileShader(gl, gl.VERTEX_SHADER, vertexSrc); - const glFragShader = compileShader(gl, gl.FRAGMENT_SHADER, fragmentSrc); - - let program = gl.createProgram(); - - gl.attachShader(program, glVertShader); - gl.attachShader(program, glFragShader); - - // optionally, set the attributes manually for the program rather than letting WebGL decide.. - if (attributeLocations) - { - for (const i in attributeLocations) - { - gl.bindAttribLocation(program, attributeLocations[i], i); - } - } - - gl.linkProgram(program); - - // if linking fails, then log and cleanup - if (!gl.getProgramParameter(program, gl.LINK_STATUS)) - { - console.error('Pixi.js Error: Could not initialize shader.'); - console.error('gl.VALIDATE_STATUS', gl.getProgramParameter(program, gl.VALIDATE_STATUS)); - console.error('gl.getError()', gl.getError()); - - // if there is a program info log, log it - if (gl.getProgramInfoLog(program) !== '') - { - console.warn('Pixi.js Warning: gl.getProgramInfoLog()', gl.getProgramInfoLog(program)); - } - - gl.deleteProgram(program); - program = null; - } - - // clean up some shaders - gl.deleteShader(glVertShader); - gl.deleteShader(glFragShader); - - return program; -} - -/** - * @private - * @param gl {WebGLRenderingContext} The current WebGL context {WebGLProgram} - * @param type {Number} the type, can be either VERTEX_SHADER or FRAGMENT_SHADER - * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. - * @return {WebGLShader} the shader - */ -function compileShader(gl, type, src) -{ - const shader = gl.createShader(type); - - gl.shaderSource(shader, src); - gl.compileShader(shader); - - if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) - { - console.warn(src); - console.error(gl.getShaderInfoLog(shader)); - - return null; - } - - return shader; -} diff --git a/packages/core/src/renderers/systems/shader/shader/defaultValue.js b/packages/core/src/renderers/systems/shader/shader/defaultValue.js deleted file mode 100644 index 97290bd..0000000 --- a/packages/core/src/renderers/systems/shader/shader/defaultValue.js +++ /dev/null @@ -1,79 +0,0 @@ -/** - * @method defaultValue - * @memberof PIXI.glCore.shader - * @param type {String} Type of value - * @param size {Number} - */ -export default function defaultValue(type, size) -{ - switch (type) - { - case 'float': - return 0; - - case 'vec2': - return new Float32Array(2 * size); - - case 'vec3': - return new Float32Array(3 * size); - - case 'vec4': - return new Float32Array(4 * size); - - case 'int': - case 'sampler2D': - case 'sampler2DArray': - return 0; - - case 'ivec2': - return new Int32Array(2 * size); - - case 'ivec3': - return new Int32Array(3 * size); - - case 'ivec4': - return new Int32Array(4 * size); - - case 'bool': - return false; - - case 'bvec2': - - return booleanArray(2 * size); - - case 'bvec3': - return booleanArray(3 * size); - - case 'bvec4': - return booleanArray(4 * size); - - case 'mat2': - return new Float32Array([1, 0, - 0, 1]); - - case 'mat3': - return new Float32Array([1, 0, 0, - 0, 1, 0, - 0, 0, 1]); - - case 'mat4': - return new Float32Array([1, 0, 0, 0, - 0, 1, 0, 0, - 0, 0, 1, 0, - 0, 0, 0, 1]); - } - - return null; -} - -function booleanArray(size) -{ - const array = new Array(size); - - for (let i = 0; i < array.length; i++) - { - array[i] = false; - } - - return array; -} diff --git a/packages/core/src/renderers/systems/shader/shader/index.js b/packages/core/src/renderers/systems/shader/shader/index.js deleted file mode 100644 index ba7a317..0000000 --- a/packages/core/src/renderers/systems/shader/shader/index.js +++ /dev/null @@ -1,8 +0,0 @@ -/** - * @namespace PIXI.glCore.shader - */ -export { default as compileProgram } from './compileProgram'; -export { default as defaultValue } from './defaultValue'; -export { default as setPrecision } from './setPrecision'; -export { default as mapSize } from './mapSize'; -export { default as mapType } from './mapType'; diff --git a/packages/core/src/renderers/systems/shader/shader/mapSize.js b/packages/core/src/renderers/systems/shader/shader/mapSize.js deleted file mode 100644 index b681554..0000000 --- a/packages/core/src/renderers/systems/shader/shader/mapSize.js +++ /dev/null @@ -1,33 +0,0 @@ -const GLSL_TO_SIZE = { - float: 1, - vec2: 2, - vec3: 3, - vec4: 4, - - int: 1, - ivec2: 2, - ivec3: 3, - ivec4: 4, - - bool: 1, - bvec2: 2, - bvec3: 3, - bvec4: 4, - - mat2: 4, - mat3: 9, - mat4: 16, - - sampler2D: 1, -}; - -/** - * @method mapSize - * @memberof PIXI.glCore.shader - * @param type {String} - * @return {Number} - */ -export default function mapSize(type) -{ - return GLSL_TO_SIZE[type]; -} diff --git a/packages/core/src/renderers/systems/shader/shader/mapType.js b/packages/core/src/renderers/systems/shader/shader/mapType.js deleted file mode 100644 index d288b7c..0000000 --- a/packages/core/src/renderers/systems/shader/shader/mapType.js +++ /dev/null @@ -1,45 +0,0 @@ -let GL_TABLE = null; - -const GL_TO_GLSL_TYPES = { - FLOAT: 'float', - FLOAT_VEC2: 'vec2', - FLOAT_VEC3: 'vec3', - FLOAT_VEC4: 'vec4', - - INT: 'int', - INT_VEC2: 'ivec2', - INT_VEC3: 'ivec3', - INT_VEC4: 'ivec4', - - BOOL: 'bool', - BOOL_VEC2: 'bvec2', - BOOL_VEC3: 'bvec3', - BOOL_VEC4: 'bvec4', - - FLOAT_MAT2: 'mat2', - FLOAT_MAT3: 'mat3', - FLOAT_MAT4: 'mat4', - - SAMPLER_2D: 'sampler2D', - SAMPLER_CUBE: 'samplerCube', - SAMPLER_2D_ARRAY: 'sampler2DArray', -}; - -export default function mapType(gl, type) -{ - if (!GL_TABLE) - { - const typeNames = Object.keys(GL_TO_GLSL_TYPES); - - GL_TABLE = {}; - - for (let i = 0; i < typeNames.length; ++i) - { - const tn = typeNames[i]; - - GL_TABLE[gl[tn]] = GL_TO_GLSL_TYPES[tn]; - } - } - - return GL_TABLE[type]; -} diff --git a/packages/core/src/renderers/systems/shader/shader/setPrecision.js b/packages/core/src/renderers/systems/shader/shader/setPrecision.js deleted file mode 100644 index e2c1f34..0000000 --- a/packages/core/src/renderers/systems/shader/shader/setPrecision.js +++ /dev/null @@ -1,18 +0,0 @@ -/** - * Sets the float precision on the shader. If the precision is already present this function will do nothing - * @param {string} src the shader source - * @param {string} precision The float precision of the shader. Options are 'lowp', 'mediump' or 'highp'. - * - * @return {string} modified shader source - */ -export default function setPrecision(src, precision) -{ - src = src.trim(); - - if (src.substring(0, 9) !== 'precision' && src.substring(0, 1) !== '#') - { - return `precision ${precision} float;\n${src}`; - } - - return src; -} diff --git a/packages/core/src/renderers/systems/textures/GLTexture.js b/packages/core/src/renderers/systems/textures/GLTexture.js deleted file mode 100644 index c3fb8dc..0000000 --- a/packages/core/src/renderers/systems/textures/GLTexture.js +++ /dev/null @@ -1,33 +0,0 @@ -export default class GLTexture -{ - constructor(texture) - { - /** - * The WebGL texture - * - * @member {WebGLTexture} - */ - this.texture = texture; - - this.width = -1; - this.height = -1; - - /** - * Texture contents dirty flag - * @member {number} - */ - this.dirtyId = -1; - - /** - * Texture style dirty flag - * @type {number} - */ - this.dirtyStyleId = -1; - - /** - * Whether mip levels has to be generated - * @type {boolean} - */ - this.mipmap = false; - } -} diff --git a/packages/core/src/renderers/systems/textures/TextureGCSystem.js b/packages/core/src/renderers/systems/textures/TextureGCSystem.js deleted file mode 100644 index b089938..0000000 --- a/packages/core/src/renderers/systems/textures/TextureGCSystem.js +++ /dev/null @@ -1,110 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; -import { GC_MODES } from '@pixi/constants'; -import { settings } from '@pixi/settings'; - -/** - * TextureGarbageCollector. This class manages the GPU and ensures that it does not get clogged - * up with textures that are no longer being used. - * - * @class - * @memberof PIXI - */ -export default class TextureGCSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this.count = 0; - this.checkCount = 0; - this.maxIdle = settings.GC_MAX_IDLE; - this.checkCountMax = settings.GC_MAX_CHECK_COUNT; - this.mode = settings.GC_MODE; - } - - /** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ - postrender() - { - this.count++; - - if (this.mode === GC_MODES.MANUAL) - { - return; - } - - this.checkCount++; - - if (this.checkCount > this.checkCountMax) - { - this.checkCount = 0; - - this.run(); - } - } - - /** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ - run() - { - const tm = this.renderer.texture; - const managedTextures = tm.managedTextures; - let wasRemoved = false; - - for (let i = 0; i < managedTextures.length; i++) - { - const texture = managedTextures[i]; - - // only supports non generated textures at the moment! - if (!texture.frameBuffer && this.count - texture.touched > this.maxIdle) - { - tm.destroyTexture(texture, true); - managedTextures[i] = null; - wasRemoved = true; - } - } - - if (wasRemoved) - { - let j = 0; - - for (let i = 0; i < managedTextures.length; i++) - { - if (managedTextures[i] !== null) - { - managedTextures[j++] = managedTextures[i]; - } - } - - managedTextures.length = j; - } - } - - /** - * Removes all the textures within the specified displayObject and its children from the GPU - * - * @param {PIXI.DisplayObject} displayObject - the displayObject to remove the textures from. - */ - unload(displayObject) - { - const tm = this.renderer.textureSystem; - - // only destroy non generated textures - if (displayObject._texture && displayObject._texture._glRenderTargets) - { - tm.destroyTexture(displayObject._texture); - } - - for (let i = displayObject.children.length - 1; i >= 0; i--) - { - this.unload(displayObject.children[i]); - } - } -} diff --git a/packages/core/src/AbstractRenderer.js b/packages/core/src/AbstractRenderer.js new file mode 100644 index 0000000..64b2eb1 --- /dev/null +++ b/packages/core/src/AbstractRenderer.js @@ -0,0 +1,353 @@ +import { hex2string, hex2rgb } from '@pixi/utils'; +import { Matrix, Rectangle } from '@pixi/math'; +import { RENDERER_TYPE } from '@pixi/constants'; +import { settings } from '@pixi/settings'; +import { Container } from '@pixi/display'; +import RenderTexture from './renderTexture/RenderTexture'; +import EventEmitter from 'eventemitter3'; + +const tempMatrix = new Matrix(); + +/** + * 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 EventEmitter + * @memberof PIXI + */ +export default class AbstractRenderer extends EventEmitter +{ + // eslint-disable-next-line valid-jsdoc + /** + * @param {string} system - The name of the system this renderer is for. + * @param {object} [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.transparent=false] - If the render view is transparent, default false + * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false + * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. The + * resolution of the renderer retina would be 2. + * @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 {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when rendering, + * stopping pixel interpolation. + */ + constructor(system, options, arg2, arg3) + { + super(); + + // Support for constructor(system, screenWidth, screenHeight, options) + if (typeof options === 'number') + { + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + }, arg3); + } + + // 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 = RENDERER_TYPE.UNKNOWN; + + /** + * Measurements of the screen. (0, 0, screenWidth, screenHeight) + * + * Its safe to use as filterArea or hitArea for 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 1 + */ + this.resolution = options.resolution || settings.RESOLUTION; + + /** + * Whether the render view is transparent + * + * @member {boolean} + */ + this.transparent = options.transparent; + + /** + * Whether css dimensions of canvas view should be resized to screen dimensions automatically + * + * @member {boolean} + */ + this.autoResize = options.autoResize || false; + + /** + * Tracks the blend modes useful for this renderer. + * + * @member {object} + */ + this.blendModes = null; + + /** + * 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; + + /** + * If true PixiJS will Math.floor() x/y values when rendering, stopping pixel interpolation. + * Handy for crisp pixel art and speed on legacy devices. + * + * @member {boolean} + */ + this.roundPixels = options.roundPixels; + + /** + * The background color as a number. + * + * @member {number} + * @private + */ + this._backgroundColor = 0x000000; + + /** + * The background color as an [R, G, B] array. + * + * @member {number[]} + * @private + */ + this._backgroundColorRgba = [0, 0, 0, 0]; + + /** + * The background color as a string. + * + * @member {string} + * @private + */ + this._backgroundColorString = '#000000'; + + this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter + + /** + * This temporary display object used as the parent of the currently being rendered item + * + * @member {PIXI.DisplayObject} + * @private + */ + this._tempDisplayObjectParent = new Container(); + + /** + * The last root object that the renderer tried to render. + * + * @member {PIXI.DisplayObject} + * @private + */ + this._lastObjectRendered = this._tempDisplayObjectParent; + + /** + * Collection of plugins. + * @readonly + * @member {object} + */ + this.plugins = {}; + } + + /** + * Initialize the plugins. + * + * @protected + * @param {object} staticMap - The dictionary of staticly saved plugins. + */ + initPlugins(staticMap) + { + 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() + { + return this.view.width; + } + + /** + * Same as view.height, actual number of pixels in the canvas by vertical + * + * @member {number} + * @readonly + * @default 600 + */ + get height() + { + return this.view.height; + } + + /** + * Resizes the screen and canvas to the specified width and height + * Canvas dimensions are multiplied by resolution + * + * @param {number} screenWidth - the new width of the screen + * @param {number} screenHeight - the new height of the screen + */ + resize(screenWidth, screenHeight) + { + this.screen.width = screenWidth; + this.screen.height = screenHeight; + + this.view.width = screenWidth * this.resolution; + this.view.height = screenHeight * this.resolution; + + if (this.autoResize) + { + this.view.style.width = `${screenWidth}px`; + this.view.style.height = `${screenHeight}px`; + } + } + + /** + * 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 {PIXI.DisplayObject} displayObject - The displayObject the object will be generated from + * @param {number} scaleMode - Should be one of the scaleMode consts + * @param {number} resolution - The resolution / device pixel ratio of the texture being generated + * @param {PIXI.Rectangle} [region] - The region of the displayObject, that shall be rendered, + * if no region is specified, defaults to the local bounds of the displayObject. + * @return {PIXI.Texture} a texture of the graphics object + */ + generateTexture(displayObject, scaleMode, resolution, region) + { + region = region || displayObject.getLocalBounds(); + + // 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(region.width | 0, region.height | 0, scaleMode, resolution); + + tempMatrix.tx = -region.x; + tempMatrix.ty = -region.y; + + this.render(displayObject, renderTexture, false, tempMatrix, true); + + return renderTexture; + } + + /** + * Removes everything from the renderer and optionally removes the Canvas DOM element. + * + * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. + */ + destroy(removeView) + { + 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); + } + + this.plugins = null; + + this.type = RENDERER_TYPE.UNKNOWN; + + this.view = null; + + this.screen = null; + + this.resolution = 0; + + this.transparent = false; + + this.autoResize = false; + + this.blendModes = null; + + this.options = null; + + this.preserveDrawingBuffer = false; + this.clearBeforeRender = false; + + this.roundPixels = false; + + this._backgroundColor = 0; + this._backgroundColorRgba = null; + this._backgroundColorString = null; + + this._tempDisplayObjectParent = null; + this._lastObjectRendered = null; + } + + /** + * The background color to fill if not transparent + * + * @member {number} + */ + get backgroundColor() + { + return this._backgroundColor; + } + + set backgroundColor(value) // eslint-disable-line require-jsdoc + { + this._backgroundColor = value; + this._backgroundColorString = hex2string(value); + hex2rgb(value, this._backgroundColorRgba); + } +} diff --git a/packages/core/src/Renderer.js b/packages/core/src/Renderer.js new file mode 100644 index 0000000..2227e78 --- /dev/null +++ b/packages/core/src/Renderer.js @@ -0,0 +1,338 @@ +import AbstractRenderer from './AbstractRenderer'; +import { sayHello } from '@pixi/utils'; +import MaskSystem from './mask/MaskSystem'; +import StencilSystem from './mask/StencilSystem'; +import FilterSystem from './filters/FilterSystem'; +import FramebufferSystem from './framebuffer/FramebufferSystem'; +import RenderTextureSystem from './renderTexture/RenderTextureSystem'; +import TextureSystem from './textures/TextureSystem'; +import ProjectionSystem from './projection/ProjectionSystem'; +import StateSystem from './state/StateSystem'; +import GeometrySystem from './geometry/GeometrySystem'; +import ShaderSystem from './shader/ShaderSystem'; +import ContextSystem from './context/ContextSystem'; +import BatchSystem from './batch/BatchSystem'; +import TextureGCSystem from './textures/TextureGCSystem'; +import { RENDERER_TYPE } from '@pixi/constants'; +import UniformGroup from './shader/UniformGroup'; +import { Matrix } from '@pixi/math'; +import Runner from 'mini-runner'; + +/** + * 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 Render works by automatically managing webGLBatchs. + * 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 :) + * + * @class + * @memberof PIXI + * @extends PIXI.AbstractRenderer + */ +export default class Renderer extends AbstractRenderer +{ + // eslint-disable-next-line valid-jsdoc + /** + * + * @param {object} [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.transparent=false] - If the render view is transparent, default false + * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false + * @param {boolean} [options.antialias=false] - sets antialias. If not available natively then FXAA + * antialiasing is used + * @param {boolean} [options.forceFXAA=false] - forces FXAA antialiasing to be used over native. + * FXAA is faster, but may not always look as great + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. + * The resolution of the renderer retina would be 2. + * @param {boolean} [options.clearBeforeRender=true] - This sets if the renderer will clear + * the canvas or not before the new render pass. If you wish to set this to false, you *must* set + * preserveDrawingBuffer to `true`. + * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, + * enable this if you need to call toDataUrl on the webgl context. + * @param {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when + * rendering, stopping pixel interpolation. + * @param {number} [options.backgroundColor=0x000000] - The background color of the rendered area + * (shown if not transparent). + * @param {boolean} [options.legacy=false] - If true PixiJS will aim to ensure compatibility + * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. + * @param {string} [options.powerPreference] - Parameter passed to webgl context, set to "high-performance" + * for devices with dual graphics card + */ + constructor(options, arg2, arg3) + { + super('WebGL', options, arg2, arg3); + + /** + * The type of this renderer as a standardised const + * + * @member {number} + * @see PIXI.RENDERER_TYPE + */ + this.type = RENDERER_TYPE.WEBGL; + + // this will be set by the contextSystem (this.context) + this.gl = null; + this.CONTEXT_UID = 0; + this.legacy = !!options.legacy; + + // TODO legacy! + + // runners! + this.runners = { + destroy: new Runner('destroy'), + contextChange: new Runner('contextChange', 1), + reset: new Runner('reset'), + update: new Runner('update'), + postrender: new Runner('postrender'), + prerender: new Runner('prerender'), + resize: new Runner('resize', 2), + }; + + 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(GeometrySystem, 'geometry') + .addSystem(FramebufferSystem, 'framebuffer') + .addSystem(StencilSystem, 'stencil') + .addSystem(ProjectionSystem, 'projection') + .addSystem(TextureGCSystem, 'textureGC') + .addSystem(FilterSystem, 'filter') + .addSystem(RenderTextureSystem, 'renderTexture') + .addSystem(BatchSystem, 'batch'); + + this.initPlugins(Renderer.__plugins); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ + if (options.context) + { + this.context.initFromContext(options.context); + } + else + { + this.context.initFromOptions({ + alpha: this.transparent, + antialias: options.antialias, + premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', + stencil: true, + 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); + } + + /** + * Add a new system to the renderer. + * @param {class} ClassRef - Class reference + * @param {string} [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. + * @return {PIXI.Renderer} Return instance of renderer + */ + addSystem(ClassRef, name) + { + if (!name) + { + name = ClassRef.name; + } + + // TODO - read name from class.name.. + + /* + if(name.includes('System')) + { + name = name.replace('System', ''); + name = name.charAt(0).toLowerCase() + name.slice(1); + } + */ + + const system = new ClassRef(this); + + if (this[name]) + { + throw new Error(`Whoops! ${name} is already a manger`); + } + + this[name] = system; + + for (const i in this.runners) + { + this.runners[i].add(system); + } + + return this; + + /** + * 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. + */ + } + + /** + * Renders the object to its webGL view + * + * @param {PIXI.DisplayObject} displayObject - the object to be rendered + * @param {PIXI.RenderTexture} renderTexture - The render texture to render to. + * @param {boolean} [clear] - Should the canvas be cleared before the new render + * @param {PIXI.Transform} [transform] - A transform to apply to the render texture before rendering. + * @param {boolean} [skipUpdateTransform] - Should we skip the update transform pass? + */ + render(displayObject, renderTexture, clear, transform, skipUpdateTransform) + { + // can be handy to know! + this.renderingToScreen = !renderTexture; + + this.runners.prerender.run(); + this.emit('prerender'); + + // 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.parent; + + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = 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.run(); + + this.emit('postrender'); + } + + /** + * Resizes the webGL view to the specified width and height. + * + * @param {number} screenWidth - the new width of the screen + * @param {number} screenHeight - the new height of the screen + */ + resize(screenWidth, screenHeight) + { + AbstractRenderer.prototype.resize.call(this, screenWidth, screenHeight); + + this.runners.resize.run(screenWidth, screenHeight); + } + + /** + * Resets the WebGL state so you can render things however you fancy! + * + * @return {PIXI.Renderer} Returns itself. + */ + reset() + { + this.runners.reset.run(); + + return this; + } + + clear() + { + this.framebuffer.bind(); + this.framebuffer.clear(); + } + + /** + * Removes everything from the renderer (event listeners, spritebatch, etc...) + * + * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. + * See: https://github.com/pixijs/pixi.js/issues/2233 + */ + destroy(removeView) + { + this.runners.destroy.run(); + + // call base destroy + super.destroy(removeView); + + // TODO nullify all the managers.. + this.gl = null; + } + + /** + * 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 + * builds and excluding plugins. + * @name PIXI.Renderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. + */ + + /** + * Adds a plugin to the renderer. + * + * @method + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + static registerPlugin(pluginName, ctor) + { + Renderer.__plugins = Renderer.__plugins || {}; + Renderer.__plugins[pluginName] = ctor; + } +} diff --git a/packages/core/src/System.js b/packages/core/src/System.js new file mode 100644 index 0000000..05a10c8 --- /dev/null +++ b/packages/core/src/System.js @@ -0,0 +1,42 @@ +/** + * System is a base class used for extending systems used by the {@link PIXI.Renderer} + * @see PIXI.Renderer#addSystem + * @class + * @memberof PIXI + */ +export default class System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + /** + * The renderer this manager works for. + * + * @member {PIXI.Renderer} + */ + this.renderer = renderer; + + this.renderer.runners.contextChange.add(this); + } + + /** + * Generic method called when there is a WebGL context change. + * + */ + contextChange() + { + // do some codes init! + } + + /** + * Generic destroy methods to be overridden by the subclass + * + */ + destroy() + { + this.renderer.runners.contextChange.remove(this); + this.renderer = null; + } +} diff --git a/packages/core/src/batch/BatchSystem.js b/packages/core/src/batch/BatchSystem.js new file mode 100644 index 0000000..b04c249 --- /dev/null +++ b/packages/core/src/batch/BatchSystem.js @@ -0,0 +1,68 @@ +import System from '../System'; +import ObjectRenderer from './ObjectRenderer'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class BatchSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + super(renderer); + + /** + * An empty renderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.emptyRenderer = new ObjectRenderer(renderer); + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.currentRenderer = this.emptyRenderer; + } + + /** + * Changes the current renderer to the one given in parameter + * + * @param {PIXI.ObjectRenderer} objectRenderer - The object renderer to use. + */ + setObjectRenderer(objectRenderer) + { + if (this.currentRenderer === objectRenderer) + { + return; + } + + this.currentRenderer.stop(); + this.currentRenderer = objectRenderer; + + this.renderer.state.setState(objectRenderer.state); + + this.currentRenderer.start(); + } + + /** + * This should be called if you wish to do some custom rendering + * It will basically render anything that may be batched up such as sprites + * + */ + flush() + { + this.setObjectRenderer(this.emptyRenderer); + } + + reset() + { + this.setObjectRenderer(this.emptyRenderer); + } +} diff --git a/packages/core/src/batch/ObjectRenderer.js b/packages/core/src/batch/ObjectRenderer.js new file mode 100644 index 0000000..0c851a9 --- /dev/null +++ b/packages/core/src/batch/ObjectRenderer.js @@ -0,0 +1,48 @@ +import System from '../System'; + +/** + * Base for a common object renderer that can be used as a system renderer plugin. + * + * @class + * @extends PIXI.System + * @memberof PIXI + */ +export default class ObjectRenderer extends System +{ + /** + * Starts the renderer and sets the shader + * + */ + start() + { + // set the shader.. + } + + /** + * Stops the renderer + * + */ + stop() + { + this.flush(); + } + + /** + * Stub method for rendering content and emptying the current batch. + * + */ + flush() + { + // flush! + } + + /** + * Renders an object + * + * @param {PIXI.DisplayObject} object - The object to render. + */ + render(object) // eslint-disable-line no-unused-vars + { + // render the object + } +} diff --git a/packages/core/src/context/ContextSystem.js b/packages/core/src/context/ContextSystem.js new file mode 100644 index 0000000..301aab4 --- /dev/null +++ b/packages/core/src/context/ContextSystem.js @@ -0,0 +1,186 @@ +import System from '../System'; +import { settings } from '@pixi/settings'; + +let CONTEXT_UID = 0; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class ContextSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.webGLVersion = 1; + + this.handleContextLost = this.handleContextLost.bind(this); + this.handleContextRestored = this.handleContextRestored.bind(this); + + this.extensions = {}; + + renderer.view.addEventListener('webglcontextlost', this.handleContextLost, false); + renderer.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + } + + get isLost() + { + return (!this.gl || this.gl.isContextLost()); + } + + contextChange(gl) + { + this.gl = gl; + + // restore a context if it was previously lost + if (gl.isContextLost() && gl.getExtension('WEBGL_lose_context')) + { + gl.getExtension('WEBGL_lose_context').restoreContext(); + } + } + + initFromContext(gl) + { + this.gl = gl; + this.validateContext(gl); + this.renderer.gl = gl; + this.renderer.CONTEXT_UID = CONTEXT_UID++; + this.renderer.runners.contextChange.run(gl); + } + + initFromOptions(options) + { + const gl = this.createContext(this.renderer.view, options); + + this.initFromContext(gl); + } + + /** + * Helper class to create a webGL Context + * + * @class + * @memberof PIXI.glCore + * @param canvas {HTMLCanvasElement} the canvas element that we will get the context from + * @param options {Object} An options object that gets passed in to the canvas element containing the context attributes, + * see https://developer.mozilla.org/en/docs/Web/API/HTMLCanvasElement/getContext for the options available + * @return {WebGLRenderingContext} the WebGL context + */ + createContext(canvas, options) + { + let gl; + + if (settings.PREFER_WEBGL_2) + { + gl = canvas.getContext('webgl2', options); + } + + if (gl) + { + this.webGLVersion = 2; + } + else + { + this.webGLVersion = 1; + + gl = canvas.getContext('webgl', options) + || canvas.getContext('experimental-webgl', options); + + if (!gl) + { + // fail, not able to get a context + throw new Error('This browser does not support webGL. Try using the canvas renderer'); + } + } + + this.gl = gl; + + this.getExtensions(); + + return gl; + } + + getExtensions() + { + // time to set up default etensions that pixi uses.. + const gl = this.gl; + const extensions = this.extensions; + + if (this.webGLVersion === 1) + { + extensions.drawBuffers = gl.getExtension('WEBGL_draw_buffers'); + extensions.depthTexture = gl.getExtension('WEBKIT_WEBGL_depth_texture'); + extensions.floatTexture = gl.getExtension('OES_texture_float'); + extensions.loseContext = gl.getExtension('WEBGL_lose_context'); + + extensions.vertexArrayObject = gl.getExtension('OES_vertex_array_object') + || gl.getExtension('MOZ_OES_vertex_array_object') + || gl.getExtension('WEBKIT_OES_vertex_array_object'); + } + + // we don't use any specific WebGL 2 ones yet! + } + + /** + * Handles a lost webgl context + * + * @private + * @param {WebGLContextEvent} event - The context lost event. + */ + handleContextLost(event) + { + event.preventDefault(); + } + + /** + * Handles a restored webgl context + * + * @private + */ + handleContextRestored() + { + this.renderer.runners.contextChange.run(this.gl); + } + + destroy() + { + const view = this.renderer.view; + + // remove listeners + view.removeEventListener('webglcontextlost', this.handleContextLost); + view.removeEventListener('webglcontextrestored', this.handleContextRestored); + + this.gl.useProgram(null); + + if (this.extensions.loseContext) + { + this.extensions.loseContext.loseContext(); + } + } + + postrender() + { + this.gl.flush(); + } + + validateContext(gl) + { + const attributes = gl.getContextAttributes(); + + // this is going to be fairly simple for now.. but at least we have room to grow! + if (!attributes.stencil) + { + /* eslint-disable max-len */ + + /* eslint-disable no-console */ + console.warn('Provided WebGL context does not have a stencil buffer, masks may not render correctly'); + /* eslint-enable no-console */ + + /* eslint-enable max-len */ + } + } +} diff --git a/packages/core/src/filters/Filter.js b/packages/core/src/filters/Filter.js new file mode 100644 index 0000000..9d8cad8 --- /dev/null +++ b/packages/core/src/filters/Filter.js @@ -0,0 +1,140 @@ +import Shader from '../shader/Shader'; +import Program from '../shader/Program'; +import State from '../state/State'; +import { settings } from '@pixi/settings'; +// import extractUniformsFromSrc from './extractUniformsFromSrc'; +import defaultVertex from './defaultFilter.vert'; +import defaultFragment from './defaultFilter.frag'; + +// let math = require('../../math'); +/** + * @class + * @memberof PIXI + * @extends PIXI.Shader + */ +export default class Filter extends Shader +{ + /** + * @param {string} [vertexSrc] - The source of the vertex shader. + * @param {string} [fragmentSrc] - The source of the fragment shader. + * @param {object} [uniforms] - Custom uniforms to use to augment the built-in ones. + */ + constructor(vertexSrc, fragmentSrc, uniforms) + { + const program = Program.from(vertexSrc, fragmentSrc); + + super(program, uniforms); + + /** + * The padding of the filter. Some filters require extra space to breath such as a blur. + * Increasing this will add extra width and height to the bounds of the object that the + * filter is applied to. + * + * @member {number} + */ + this.padding = 0; + + /** + * The resolution of the filter. Setting this to be lower will lower the quality but + * increase the performance of the filter. + * + * @member {number} + */ + this.resolution = settings.RESOLUTION; + + /** + * If enabled is true the filter is applied, if false it will not. + * + * @member {boolean} + */ + this.enabled = true; + + /** + * If enabled, PixiJS will fit the filter area into boundaries for better performance. + * Switch it off if it does not work for specific shader. + * + * @member {boolean} + */ + this.autoFit = true; + + /** + * Legacy filters use position and uvs from attributes + * @member {boolean} + * @readonly + */ + this.legacy = !!this.program.attributeData.aTextureCoord; + + /** + * the webGL state the filter requires to render + * @member {PIXI.State} + */ + this.state = new State(); + } + + /** + * Applies the filter + * + * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + * @param {boolean} clear - Should the output be cleared before rendering to it + * @param {object} [currentState] - It's current state of filter. + * There are some useful properties in the currentState : + * target, filters, sourceFrame, destinationFrame, renderTarget, resolution + */ + apply(filterManager, input, output, clear, currentState, derp) // eslint-disable-line no-unused-vars + { + // do as you please! + + filterManager.applyFilter(this, input, output, clear, currentState, derp); + + // or just do a regular render.. + } + + /** + * Sets the blendmode of the filter + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL + */ + get blendMode() + { + return this.state.blendMode; + } + + set blendMode(value) // eslint-disable-line require-jsdoc + { + this.state.blendMode = value; + } + + /** + * The default vertex shader source + * + * @static + * @constant + */ + static get defaultVertexSrc() + { + return defaultVertex; + } + + /** + * The default fragment shader source + * + * @static + * @constant + */ + static get defaultFragmentSrc() + { + return defaultFragment; + } +} + +/** + * Used for caching shader IDs + * + * @static + * @private + */ +Filter.SOURCE_KEY_MAP = {}; + diff --git a/packages/core/src/filters/FilterSystem.js b/packages/core/src/filters/FilterSystem.js new file mode 100644 index 0000000..3174318 --- /dev/null +++ b/packages/core/src/filters/FilterSystem.js @@ -0,0 +1,431 @@ +import System from '../System'; + +import RenderTexture from '../renderTexture/RenderTexture'; +import Quad from '../utils/Quad'; +import QuadUv from '../utils/QuadUv'; +import { Rectangle } from '@pixi/math'; +import * as filterTransforms from './filterTransforms'; +import bitTwiddle from 'bit-twiddle'; +import UniformGroup from '../shader/UniformGroup'; +import { DRAW_MODES } from '@pixi/constants'; + +// +/** + * @ignore + * @class + */ +class FilterState +{ + /** + * + */ + constructor() + { + this.renderTexture = null; + this.sourceFrame = new Rectangle(); + this.destinationFrame = new Rectangle(); + this.filters = []; + this.target = null; + this.legacy = false; + this.resolution = 1; + } +} + +/** + * @class + * @memberof PIXI.systems + * @extends PIXI.System + */ +export default class FilterSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + /** + * stores a bunch of PO2 textures used for filtering + * @type {Object} + */ + this.texturePool = {}; + + /** + * a pool for storing filter states, save us creating new ones each tick + * @type {Array} + */ + this.statePool = []; + + /** + * A very simple geometry used when drawing a filter effect to the screen + * @type {Quad} + */ + this.quad = new Quad(); + + this.quadUv = new QuadUv(); + + /** + * Temporary rect for maths + * @type {PIXI.Rectangle} + */ + this.tempRect = new Rectangle(); + + this.activeState = {}; + + /** + * this uniform group is attached to filter uniforms when used + * @type {UniformGroup} + */ + this.globalUniforms = new UniformGroup({ + sourceFrame: this.tempRect, + destinationFrame: this.tempRect, + + // legacy variables + filterArea: new Float32Array(4), + filterClamp: new Float32Array(4), + }, true); + } + + /** + * Adds a new filter to the System. + * + * @param {PIXI.DisplayObject} target - The target of the filter to render. + * @param {PIXI.Filter[]} filters - The filters to apply. + */ + push(target, filters) + { + const renderer = this.renderer; + const filterStack = this.renderer.renderTexture.defaultFilterStack; + const state = this.statePool.pop() || new FilterState(); + + let resolution = filters[0].resolution; + let padding = filters[0].padding; + let autoFit = filters[0].autoFit; + let legacy = filters[0].legacy; + + for (let i = 1; i < filters.length; i++) + { + const filter = filters[i]; + + // lets use the lowest resolution.. + resolution = Math.min(resolution, filter.resolution); + // and the largest amount of padding! + padding = Math.max(padding, filter.padding); + // only auto fit if all filters are autofit + autoFit = autoFit || filter.autoFit; + + legacy = legacy || filter.legacy; + } + + filterStack.push(state); + + state.resolution = resolution; + + state.legacy = legacy; + + // round to whole number based on resolution + // TODO move that to the shader too? + state.sourceFrame = target.filterArea || target.getBounds(true); + + state.sourceFrame.pad(padding); + + if (autoFit) + { + state.sourceFrame.fit(this.renderer.renderTexture.destinationFrame); + } + + state.sourceFrame.round(resolution); + + state.renderTexture = this.getPotFilterTexture(state.sourceFrame.width, state.sourceFrame.height, resolution); + state.filters = filters; + + state.destinationFrame.width = state.renderTexture.width; + state.destinationFrame.height = state.renderTexture.height; + + state.renderTexture.filterFrame = state.sourceFrame; + renderer.renderTexture.bind(state.renderTexture, state.sourceFrame);// /, state.destinationFrame); + renderer.renderTexture.clear(); + } + + /** + * Pops off the filter and applies it. + * + */ + pop() + { + const renderer = this.renderer; + const filterStack = renderer.renderTexture.defaultFilterStack; + const state = filterStack.pop(); + const filters = state.filters; + + this.activeState = state; + + const globalUniforms = this.globalUniforms.uniforms; + + globalUniforms.sourceFrame = state.sourceFrame; + globalUniforms.destinationFrame = state.destinationFrame; + globalUniforms.resolution = state.resolution; + + // only update the rect if its legacy.. + if (state.legacy) + { + const filterArea = globalUniforms.filterArea; + const filterClamp = globalUniforms.filterClamp; + + filterArea[0] = state.destinationFrame.width; + filterArea[1] = state.destinationFrame.height; + filterArea[2] = state.sourceFrame.x; + filterArea[3] = state.sourceFrame.y; + + filterClamp[0] = 0.5 / state.resolution / state.destinationFrame.width; + filterClamp[1] = 0.5 / state.resolution / state.destinationFrame.height; + filterClamp[2] = (state.sourceFrame.width - 0.5) / state.resolution / state.destinationFrame.width; + filterClamp[3] = (state.sourceFrame.height - 0.5) / state.resolution / state.destinationFrame.height; + } + + this.globalUniforms.update(); + + const lastState = filterStack[filterStack.length - 1]; + + if (filters.length === 1) + { + filters[0].apply(this, state.renderTexture, lastState.renderTexture, false, state); + + this.returnFilterTexture(state.renderTexture); + } + else + { + let flip = state.renderTexture; + let flop = this.getPotFilterTexture( + flip.width, + flip.height, + state.resolution + ); + + flop.filterFrame = flip.filterFrame; + + let i = 0; + + for (i = 0; i < filters.length - 1; ++i) + { + filters[i].apply(this, flip, flop, true, state); + + const t = flip; + + flip = flop; + flop = t; + } + + filters[i].apply(this, flip, lastState.renderTexture, false, state); + + this.returnFilterTexture(flip); + this.returnFilterTexture(flop); + } + + this.statePool.push(state); + } + + /** + * Draws a filter. + * + * @param {PIXI.Filter} filter - The filter to draw. + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + * @param {boolean} clear - Should the output be cleared before rendering to it + */ + applyFilter(filter, input, output, clear) + { + const renderer = this.renderer; + + renderer.renderTexture.bind(output, output ? output.filterFrame : null); + + if (clear) + { + // gl.disable(gl.SCISSOR_TEST); + renderer.renderTexture.clear(); + // gl.enable(gl.SCISSOR_TEST); + } + + // set the uniforms.. + filter.uniforms.uSampler = input; + filter.uniforms.filterGlobals = this.globalUniforms; + + // TODO make it so that the order of this does not matter.. + // because it does at the moment cos of global uniforms. + // they need to get resynced + + renderer.state.setState(filter.state); + renderer.shader.bind(filter); + + if (filter.legacy) + { + this.quadUv.map(input._frame, input.filterFrame); + + renderer.geometry.bind(this.quadUv); + renderer.geometry.draw(DRAW_MODES.TRIANGLES); + } + else + { + renderer.geometry.bind(this.quad); + renderer.geometry.draw(DRAW_MODES.TRIANGLE_STRIP); + } + } + + /** + * Calculates the mapped matrix. + * + * TODO playing around here.. this is temporary - (will end up in the shader) + * this returns a matrix that will normalise map filter cords in the filter to screen space + * + * @param {PIXI.Matrix} outputMatrix - the matrix to output to. + * @return {PIXI.Matrix} The mapped matrix. + */ + calculateScreenSpaceMatrix(outputMatrix) + { + const currentState = this.activeState; + + return filterTransforms.calculateScreenSpaceMatrix( + outputMatrix, + currentState.sourceFrame, + currentState.destinationFrame + ); + } + + /** + * This will map the filter coord so that a texture can be used based on the transform of a sprite + * + * @param {PIXI.Matrix} outputMatrix - The matrix to output to. + * @param {PIXI.Sprite} sprite - The sprite to map to. + * @return {PIXI.Matrix} The mapped matrix. + */ + calculateSpriteMatrix(outputMatrix, sprite) + { + const currentState = this.activeState; + + return filterTransforms.calculateSpriteMatrix( + outputMatrix, + currentState.sourceFrame, + currentState.destinationFrame, + sprite + ); + } + + /** + * Destroys this Filter System. + * + * @param {boolean} [contextLost=false] context was lost, do not free shaders + * + */ + destroy(contextLost = false) + { + if (!contextLost) + { + this.emptyPool(); + } + else + { + this.texturePool = {}; + } + } + + /** + * Gets a Power-of-Two render texture. + * + * TODO move to a seperate class could be on renderer? + * also - could cause issue with multiple contexts? + * + * @private + * @param {WebGLRenderingContext} gl - The webgl rendering context + * @param {number} minWidth - The minimum width of the render target. + * @param {number} minHeight - The minimum height of the render target. + * @param {number} resolution - The resolution of the render target. + * @return {PIXI.RenderTarget} The new render target. + */ + getPotFilterTexture(minWidth, minHeight, resolution) + { + minWidth = bitTwiddle.nextPow2(minWidth); + minHeight = bitTwiddle.nextPow2(minHeight); + resolution = resolution || 1; + + const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + + if (!this.texturePool[key]) + { + this.texturePool[key] = []; + } + + let renderTexture = this.texturePool[key].pop(); + + if (!renderTexture) + { + // temporary bypass cache.. + // internally - this will cause a texture to be bound.. + renderTexture = RenderTexture.create({ + width: minWidth, + height: minHeight, + resolution, + }); + } + + return renderTexture; + } + + /** + * Gets extra render texture to use inside current filter + * + * @param {number} resolution resolution of the renderTexture + * @returns {PIXI.RenderTexture} + */ + getFilterTexture(resolution) + { + const rt = this.activeState.renderTexture; + + const filterTexture = this.getPotFilterTexture(rt.width, rt.height, resolution || rt.baseTexture.resolution); + + filterTexture.filterFrame = rt.filterFrame; + + return filterTexture; + } + + /** + * Frees a render texture back into the pool. + * + * @param {PIXI.RenderTarget} renderTexture - The renderTarget to free + */ + returnFilterTexture(renderTexture) + { + renderTexture.filterFrame = null; + + const base = renderTexture.baseTexture; + + const minWidth = base.width; + const minHeight = base.height; + + const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + + this.texturePool[key].push(renderTexture); + } + + /** + * Empties the texture pool. + * + */ + emptyPool() + { + for (const i in this.texturePool) + { + const textures = this.texturePool[i]; + + if (textures) + { + for (let j = 0; j < textures.length; j++) + { + textures[j].destroy(true); + } + } + } + + this.texturePool = {}; + } +} diff --git a/packages/core/src/filters/defaultFilter.frag b/packages/core/src/filters/defaultFilter.frag new file mode 100644 index 0000000..a14c1be --- /dev/null +++ b/packages/core/src/filters/defaultFilter.frag @@ -0,0 +1,22 @@ +varying vec2 vTextureCoord; +varying vec2 vFilterCoord; + +uniform sampler2D uSampler; +uniform sampler2D filterSampler; + +void main(void){ + vec4 masky = texture2D(filterSampler, vFilterCoord); + vec4 sample = texture2D(uSampler, vTextureCoord); + vec4 color; + if(mod(vFilterCoord.x, 1.0) > 0.5) + { + color = vec4(1.0, 0.0, 0.0, 1.0); + } + else + { + color = vec4(0.0, 1.0, 0.0, 1.0); + } + // gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0); + gl_FragColor = mix(sample, masky, 0.5); + gl_FragColor *= sample.a; +} diff --git a/packages/core/src/filters/defaultFilter.vert b/packages/core/src/filters/defaultFilter.vert new file mode 100644 index 0000000..e1febca --- /dev/null +++ b/packages/core/src/filters/defaultFilter.vert @@ -0,0 +1,14 @@ +attribute vec2 aVertexPosition; +attribute vec2 aTextureCoord; + +uniform mat3 projectionMatrix; +uniform mat3 filterMatrix; + +varying vec2 vTextureCoord; +varying vec2 vFilterCoord; + +void main(void){ + gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); + vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy; + vTextureCoord = aTextureCoord ; +} diff --git a/packages/core/src/filters/filterTransforms.js b/packages/core/src/filters/filterTransforms.js new file mode 100644 index 0000000..87a3cd6 --- /dev/null +++ b/packages/core/src/filters/filterTransforms.js @@ -0,0 +1,50 @@ +import { Matrix } from '@pixi/math'; + +/** + * Calculates the mapped matrix + * @param filterArea {Rectangle} The filter area + * @param sprite {Sprite} the target sprite + * @param outputMatrix {Matrix} @alvin + * @private + */ +// this returns a matrix that will normalise map filter cords in the filter to screen space +export function calculateScreenSpaceMatrix(outputMatrix, filterArea, textureSize) +{ + // TODO unwrap? + const mappedMatrix = outputMatrix.identity(); + + mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); + + mappedMatrix.scale(textureSize.width, textureSize.height); + + return mappedMatrix; +} + +export function calculateNormalizedScreenSpaceMatrix(outputMatrix, filterArea, textureSize) +{ + const mappedMatrix = outputMatrix.identity(); + + mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); + + const translateScaleX = (textureSize.width / filterArea.width); + const translateScaleY = (textureSize.height / filterArea.height); + + mappedMatrix.scale(translateScaleX, translateScaleY); + + return mappedMatrix; +} + +// this will map the filter coord so that a texture can be used based on the transform of a sprite +export function calculateSpriteMatrix(outputMatrix, filterArea, textureSize, sprite) +{ + const orig = sprite._texture.orig; + const mappedMatrix = outputMatrix.set(textureSize.width, 0, 0, textureSize.height, filterArea.x, filterArea.y); + const worldTransform = sprite.worldTransform.copyTo(Matrix.TEMP_MATRIX); + + worldTransform.invert(); + mappedMatrix.prepend(worldTransform); + mappedMatrix.scale(1.0 / orig.width, 1.0 / orig.height); + mappedMatrix.translate(sprite.anchor.x, sprite.anchor.y); + + return mappedMatrix; +} diff --git a/packages/core/src/filters/spriteMask/SpriteMaskFilter.js b/packages/core/src/filters/spriteMask/SpriteMaskFilter.js new file mode 100644 index 0000000..91697b7 --- /dev/null +++ b/packages/core/src/filters/spriteMask/SpriteMaskFilter.js @@ -0,0 +1,63 @@ +import Filter from '../Filter'; +import { Matrix } from '@pixi/math'; +import vertex from './spriteMaskFilter.vert'; +import fragment from './spriteMaskFilter.frag'; +import { default as TextureMatrix } from '../../textures/TextureMatrix'; + +/** + * The SpriteMaskFilter class + * + * @class + * @extends PIXI.Filter + * @memberof PIXI + */ +export default class SpriteMaskFilter extends Filter +{ + /** + * @param {PIXI.Sprite} sprite - the target sprite + */ + constructor(sprite) + { + const maskMatrix = new Matrix(); + + super(vertex, fragment); + + sprite.renderable = false; + + this.maskSprite = sprite; + this.maskMatrix = maskMatrix; + } + + /** + * Applies the filter + * + * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + */ + apply(filterManager, input, output) + { + const maskSprite = this.maskSprite; + const tex = this.maskSprite.texture; + + if (!tex.valid) + { + return; + } + if (!tex.transform) + { + // margin = 0.0, let it bleed a bit, shader code becomes easier + // assuming that atlas textures were made with 1-pixel padding + tex.transform = new TextureMatrix(tex, 0.0); + } + tex.transform.update(); + + this.uniforms.mask = tex; + this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite) + .prepend(tex.transform.mapCoord); + this.uniforms.alpha = maskSprite.worldAlpha; + this.uniforms.maskClamp = tex.transform.uClampFrame; + + filterManager.applyFilter(this, input, output); + } +} diff --git a/packages/core/src/filters/spriteMask/spriteMaskFilter.frag b/packages/core/src/filters/spriteMask/spriteMaskFilter.frag new file mode 100644 index 0000000..0e4aef8 --- /dev/null +++ b/packages/core/src/filters/spriteMask/spriteMaskFilter.frag @@ -0,0 +1,23 @@ +varying vec2 vMaskCoord; +varying vec2 vTextureCoord; + +uniform sampler2D uSampler; +uniform sampler2D mask; +uniform float alpha; +uniform vec4 maskClamp; + +void main(void) +{ + float clip = step(3.5, + step(maskClamp.x, vMaskCoord.x) + + step(maskClamp.y, vMaskCoord.y) + + step(vMaskCoord.x, maskClamp.z) + + step(vMaskCoord.y, maskClamp.w)); + + vec4 original = texture2D(uSampler, vTextureCoord); + vec4 masky = texture2D(mask, vMaskCoord); + + original *= (masky.r * masky.a * alpha * clip); + + gl_FragColor = original; +} diff --git a/packages/core/src/filters/spriteMask/spriteMaskFilter.vert b/packages/core/src/filters/spriteMask/spriteMaskFilter.vert new file mode 100644 index 0000000..d23a30c --- /dev/null +++ b/packages/core/src/filters/spriteMask/spriteMaskFilter.vert @@ -0,0 +1,16 @@ +attribute vec2 aVertexPosition; +attribute vec2 aTextureCoord; + +uniform mat3 projectionMatrix; +uniform mat3 otherMatrix; + +varying vec2 vMaskCoord; +varying vec2 vTextureCoord; + +void main(void) +{ + gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); + + vTextureCoord = aTextureCoord; + vMaskCoord = ( otherMatrix * vec3( aTextureCoord, 1.0) ).xy; +} diff --git a/packages/core/src/framebuffer/FrameBuffer.js b/packages/core/src/framebuffer/FrameBuffer.js new file mode 100644 index 0000000..84e9fcc --- /dev/null +++ b/packages/core/src/framebuffer/FrameBuffer.js @@ -0,0 +1,106 @@ +import Texture from '../textures/BaseTexture'; +import { FORMATS, TYPES } from '@pixi/constants'; + +/** + * Frame buffer + * @class + * @memberof PIXI + */ +export default class FrameBuffer +{ + constructor(width, height) + { + this.width = width || 100; + this.height = height || 100; + + this.stencil = false; + this.depth = false; + + this.dirtyId = 0; + this.dirtyFormat = 0; + this.dirtySize = 0; + + this.depthTexture = null; + this.colorTextures = []; + + this.glFrameBuffers = {}; + } + + get colorTexture() + { + return this.colorTextures[0]; + } + + addColorTexture(index, texture) + { + // TODO add some validation to the texture - same width / height etc? + this.colorTextures[index || 0] = texture || new Texture(null, { scaleMode: 0, + resolution: 1, + mipmap: false, + width: this.width, + height: this.height });// || new Texture(); + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + addDepthTexture(texture) + { + /* eslint-disable max-len */ + this.depthTexture = texture || new Texture(null, { scaleMode: 0, + resolution: 1, + width: this.width, + height: this.height, + mipmap: false, + format: FORMATS.DEPTH_COMPONENT, + type: TYPES.UNSIGNED_SHORT });// UNSIGNED_SHORT; + /* eslint-disable max-len */ + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + enableDepth() + { + this.depth = true; + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + enableStencil() + { + this.stencil = true; + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + resize(width, height) + { + if (width === this.width && height === this.height) return; + + this.width = width; + this.height = height; + + this.dirtyId++; + this.dirtySize++; + + for (let i = 0; i < this.colorTextures.length; i++) + { + this.colorTextures[i].setSize(width, height); + } + + if (this.depthTexture) + { + this.depthTexture.setSize(width, height); + } + } +} diff --git a/packages/core/src/framebuffer/FramebufferSystem.js b/packages/core/src/framebuffer/FramebufferSystem.js new file mode 100644 index 0000000..2cc2437 --- /dev/null +++ b/packages/core/src/framebuffer/FramebufferSystem.js @@ -0,0 +1,246 @@ +import System from '../System'; +import { Rectangle } from '@pixi/math'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class FramebufferSystem extends System +{ + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + contextChange() + { + this.gl = this.renderer.gl; + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + this.current = null; + this.viewport = new Rectangle(); + + this.drawBufferExtension = this.renderer.context.extensions.drawBuffers; + } + + bind(framebuffer, frame) + { + const gl = this.gl; + + this.current = framebuffer; + + if (framebuffer) + { + // TODO cacheing layer! + + const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID] || this.initFramebuffer(framebuffer); + + gl.bindFramebuffer(gl.FRAMEBUFFER, fbo.framebuffer); + // makesure all textures are unbound.. + + // now check for updates... + if (fbo.dirtyId !== framebuffer.dirtyId) + { + fbo.dirtyId = framebuffer.dirtyId; + + if (fbo.dirtyFormat !== framebuffer.dirtyFormat) + { + fbo.dirtyFormat = framebuffer.dirtyFormat; + this.updateFramebuffer(framebuffer); + } + else if (fbo.dirtySize !== framebuffer.dirtySize) + { + fbo.dirtySize = framebuffer.dirtySize; + this.resizeFramebuffer(framebuffer); + } + } + + for (let i = 0; i < framebuffer.colorTextures.length; i++) + { + if (framebuffer.colorTextures[i].texturePart) + { + this.renderer.texture.unbind(framebuffer.colorTextures[i].texture); + } + else + { + this.renderer.texture.unbind(framebuffer.colorTextures[i]); + } + } + + if (framebuffer.depthTexture) + { + this.renderer.texture.unbind(framebuffer.depthTexture); + } + + if (frame) + { + this.setViewport(frame.x, frame.y, frame.width, frame.height); + } + else + { + this.setViewport(0, 0, framebuffer.width, framebuffer.height); + } + } + else + { + gl.bindFramebuffer(gl.FRAMEBUFFER, null); + + if (frame) + { + this.setViewport(frame.x, frame.y, frame.width, frame.height); + } + else + { + this.setViewport(0, 0, this.renderer.width, this.renderer.height); + } + } + } + + setViewport(x, y, width, height) + { + const v = this.viewport; + + if (v.width !== width || v.height !== height || v.x !== x || v.y !== y) + { + v.x = x; + v.y = y; + v.width = width; + v.height = height; + + this.gl.viewport(x, y, width, height); + } + } + + get size() + { + if (this.current) + { + // TODO store temp + return { x: 0, y: 0, width: this.current.width, height: this.current.height }; + } + + return { x: 0, y: 0, width: this.renderer.width, height: this.renderer.height }; + } + + clear(r, g, b, a) + { + const gl = this.gl; + + // TODO clear color can be set only one right? + gl.clearColor(r, g, b, a); + gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); + } + + // private functions... + + initFramebuffer(framebuffer) + { + const gl = this.gl; + + // TODO - make this a class? + const fbo = { + framebuffer: gl.createFramebuffer(), + stencil: null, + dirtyId: 0, + dirtyFormat: 0, + dirtySize: 0, + }; + + framebuffer.glFrameBuffers[this.CONTEXT_UID] = fbo; + + return fbo; + } + + resizeFramebuffer(framebuffer) + { + const gl = this.gl; + + if (framebuffer.stencil || framebuffer.depth) + { + gl.bindRenderbuffer(gl.RENDERBUFFER, this.stencil); + gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); + } + } + + updateFramebuffer(framebuffer) + { + const gl = this.gl; + + const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID]; + + // bind the color texture + const colorTextures = framebuffer.colorTextures; + + let count = colorTextures.length; + + if (!this.drawBufferExtension) + { + count = Math.min(count, 1); + } + + const activeTextures = []; + + for (let i = 0; i < count; i++) + { + const texture = framebuffer.colorTextures[i]; + + if (texture.texturePart) + { + this.renderer.texture.bind(texture.texture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.COLOR_ATTACHMENT0 + i, + gl.TEXTURE_CUBE_MAP_NEGATIVE_X + texture.side, + texture.texture._glTextures[this.CONTEXT_UID].texture, + 0); + } + else + { + this.renderer.texture.bind(texture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.COLOR_ATTACHMENT0 + i, + gl.TEXTURE_2D, + texture._glTextures[this.CONTEXT_UID].texture, + 0); + } + + activeTextures.push(gl.COLOR_ATTACHMENT0 + i); + } + + if (this.drawBufferExtension && activeTextures.length > 1) + { + this.drawBufferExtension.drawBuffersWEBGL(activeTextures); + } + + if (framebuffer.depthTexture) + { + const depthTextureExt = this.renderer.context.extensions.depthTexture; + + if (depthTextureExt) + { + const depthTexture = framebuffer.depthTexture; + + this.renderer.texture.bind(depthTexture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.DEPTH_ATTACHMENT, + gl.TEXTURE_2D, + depthTexture._glTextures[this.CONTEXT_UID].texture, + 0); + } + } + + if (framebuffer.stencil || framebuffer.depth) + { + fbo.stencil = gl.createRenderbuffer(); + + gl.bindRenderbuffer(gl.RENDERBUFFER, fbo.stencil); + + // TODO.. this is depth AND stencil? + gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, fbo.stencil); + gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); + // fbo.enableStencil(); + } + } +} diff --git a/packages/core/src/geometry/GLBuffer.js b/packages/core/src/geometry/GLBuffer.js new file mode 100644 index 0000000..0477e8a --- /dev/null +++ b/packages/core/src/geometry/GLBuffer.js @@ -0,0 +1,9 @@ +export default class GLBuffer +{ + constructor(buffer) + { + this.buffer = buffer; + this.updateID = -1; + this.byteLength = -1; + } +} diff --git a/packages/core/src/geometry/GeometrySystem.js b/packages/core/src/geometry/GeometrySystem.js new file mode 100644 index 0000000..959f827 --- /dev/null +++ b/packages/core/src/geometry/GeometrySystem.js @@ -0,0 +1,388 @@ +import System from '../System'; +import GLBuffer from './GLBuffer'; + +const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class GeometrySystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this._activeGeometry = null; + this._activeVao = null; + + this.hasVao = true; + this.hasInstance = true; + } + + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + contextChange() + { + const gl = this.gl = this.renderer.gl; + + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + + // webgl2 + if (!gl.createVertexArray) + { + // webgl 1! + let nativeVaoExtension = this.renderer.context.extensions.vertexArrayObject; + + if (this.renderer.legacy) + { + nativeVaoExtension = null; + } + + if (nativeVaoExtension) + { + gl.createVertexArray = () => + nativeVaoExtension.createVertexArrayOES(); + + gl.bindVertexArray = (vao) => + nativeVaoExtension.bindVertexArrayOES(vao); + + gl.deleteVertexArray = (vao) => + nativeVaoExtension.deleteVertexArrayOES(vao); + } + else + { + this.hasVao = false; + gl.createVertexArray = () => + { + // empty + }; + + gl.bindVertexArray = () => + { + // empty + }; + + gl.deleteVertexArray = () => + { + // empty + }; + } + } + + if (!gl.vertexAttribDivisor) + { + const instanceExt = gl.getExtension('ANGLE_instanced_arrays'); + + if (instanceExt) + { + gl.vertexAttribDivisor = (a, b) => + instanceExt.vertexAttribDivisorANGLE(a, b); + + gl.drawElementsInstanced = (a, b, c, d, e) => + instanceExt.drawElementsInstancedANGLE(a, b, c, d, e); + + gl.drawArraysInstanced = (a, b, c, d) => + instanceExt.drawArraysInstancedANGLE(a, b, c, d); + } + else + { + this.hasInstance = false; + } + } + } + + /** + * Binds geometry so that is can be drawn. Creating a Vao if required + * @private + * @param {PIXI.Geometry} geometry instance of geometry to bind + */ + bind(geometry, shader) + { + shader = shader || this.renderer.shader.shader; + + const gl = this.gl; + + // not sure the best way to address this.. + // currently different shaders require different VAOs for the same geometry + // Still mulling over the best way to solve this one.. + // will likely need to modify the shader attribute locations at run time! + let vaos = geometry.glVertexArrayObjects[this.CONTEXT_UID]; + + if (!vaos) + { + geometry.glVertexArrayObjects[this.CONTEXT_UID] = vaos = {}; + } + + const vao = vaos[shader.program.id] || this.initGeometryVao(geometry, shader.program); + + this._activeGeometry = geometry; + + if (this._activeVao !== vao) + { + this._activeVao = vao; + + if (this.hasVao) + { + gl.bindVertexArray(vao); + } + else + { + this.activateVao(geometry, shader.program); + } + } + + // TODO - optimise later! + // don't need to loop through if nothing changed! + // maybe look to add an 'autoupdate' to geometry? + this.updateBuffers(); + } + + reset() + { + this.unbind(); + } + + updateBuffers() + { + const geometry = this._activeGeometry; + const gl = this.gl; + + for (let i = 0; i < geometry.buffers.length; i++) + { + const buffer = geometry.buffers[i]; + + const glBuffer = buffer._glBuffers[this.CONTEXT_UID]; + + if (buffer._updateID !== glBuffer.updateID) + { + glBuffer.updateID = buffer._updateID; + + // TODO can cache this on buffer! maybe added a getter / setter? + const type = buffer.index ? gl.ELEMENT_ARRAY_BUFFER : gl.ARRAY_BUFFER; + const drawType = buffer.static ? gl.STATIC_DRAW : gl.DYNAMIC_DRAW; + + gl.bindBuffer(type, glBuffer.buffer); + + if (glBuffer.byteLength >= buffer.data.byteLength) + { + // offset is always zero for now! + gl.bufferSubData(type, 0, buffer.data); + } + else + { + gl.bufferData(type, buffer.data, drawType); + } + } + } + } + + checkCompatability(geometry, program) + { + // geometry must have at least all the attributes that the shader requires. + const geometryAttributes = geometry.attributes; + const shaderAttributes = program.attributeData; + + for (const j in shaderAttributes) + { + if (!geometryAttributes[j]) + { + throw new Error(`shader and geometry incompatible, geometry missing the "${j}" attribute`); + } + } + } + + /** + * Creates a Vao with the same structure as the geometry and stores it on the geometry. + * @private + * @param {PIXI.Geometry} geometry instance of geometry to to generate Vao for + */ + initGeometryVao(geometry, program) + { + this.checkCompatability(geometry, program); + + const gl = this.gl; + const CONTEXT_UID = this.CONTEXT_UID; + const buffers = geometry.buffers; + const attributes = geometry.attributes; + + const tempStride = {}; + const tempStart = {}; + + for (const j in buffers) + { + tempStride[j] = 0; + tempStart[j] = 0; + } + + for (const j in attributes) + { + if (!attributes[j].size && program.attributeData[j]) + { + attributes[j].size = program.attributeData[j].size; + } + + tempStride[attributes[j].buffer] += attributes[j].size * byteSizeMap[attributes[j].type]; + } + + for (const j in attributes) + { + const attribute = attributes[j]; + const attribSize = attribute.size; + + if (attribute.stride === undefined) + { + if (tempStride[attribute.buffer] === attribSize * byteSizeMap[attribute.type]) + { + attribute.stride = 0; + } + else + { + attribute.stride = tempStride[attribute.buffer]; + } + } + + if (attribute.start === undefined) + { + attribute.start = tempStart[attribute.buffer]; + + tempStart[attribute.buffer] += attribSize * byteSizeMap[attribute.type]; + } + } + + // first update - and create the buffers! + // only create a gl buffer if it actually gets + for (let i = 0; i < buffers.length; i++) + { + const buffer = buffers[i]; + + if (!buffer._glBuffers[CONTEXT_UID]) + { + buffer._glBuffers[CONTEXT_UID] = new GLBuffer(gl.createBuffer()); + } + } + + // TODO - maybe make this a data object? + // lets wait to see if we need to first! + const vao = gl.createVertexArray(); + + gl.bindVertexArray(vao); + + this.activateVao(geometry, program); + + gl.bindVertexArray(null); + + // add it to the cache! + geometry.glVertexArrayObjects[this.CONTEXT_UID][program.id] = vao; + + return vao; + } + + activateVao(geometry, program) + { + const gl = this.gl; + const CONTEXT_UID = this.CONTEXT_UID; + const buffers = geometry.buffers; + const attributes = geometry.attributes; + + if (geometry.indexBuffer) + { + // first update the index buffer if we have one.. + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, geometry.indexBuffer._glBuffers[CONTEXT_UID].buffer); + } + + let lastBuffer = null; + + // add a new one! + for (const j in attributes) + { + const attribute = attributes[j]; + const buffer = buffers[attribute.buffer]; + const glBuffer = buffer._glBuffers[CONTEXT_UID]; + + if (program.attributeData[j]) + { + if (lastBuffer !== glBuffer) + { + gl.bindBuffer(gl.ARRAY_BUFFER, glBuffer.buffer); + + lastBuffer = glBuffer; + } + + const location = program.attributeData[j].location; + + // TODO introduce state again + // we can optimise this for older devices that have no VAOs + gl.enableVertexAttribArray(location); + + gl.vertexAttribPointer(location, + attribute.size, + attribute.type || gl.FLOAT, + attribute.normalized, + attribute.stride, + attribute.start); + + if (attribute.instance) + { + // TODO calculate instance count based of this... + if (this.hasInstance) + { + gl.vertexAttribDivisor(location, 1); + } + else + { + throw new Error('geometry error, GPU Instancing is not supported on this device'); + } + } + } + } + } + + draw(type, size, start, instanceCount) + { + const gl = this.gl; + const geometry = this._activeGeometry; + + // TODO.. this should not change so maybe cache the function? + + if (geometry.indexBuffer) + { + if (geometry.instanced) + { + /* eslint-disable max-len */ + gl.drawElementsInstanced(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2, instanceCount || 1); + /* eslint-enable max-len */ + } + else + { + gl.drawElements(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2); + } + } + else + if (geometry.instanced) + { + // TODO need a better way to calculate size.. + gl.drawArraysInstanced(type, start, size || geometry.getSize(), instanceCount || 1); + } + else + { + gl.drawArrays(type, start, size || geometry.getSize()); + } + + return this; + } + + unbind() + { + this.gl.bindVertexArray(null); + this._activeVao = null; + this._activeGeometry = null; + } +} diff --git a/packages/core/src/geometry/utils/setVertexAttribArrays.js b/packages/core/src/geometry/utils/setVertexAttribArrays.js new file mode 100644 index 0000000..23b6959 --- /dev/null +++ b/packages/core/src/geometry/utils/setVertexAttribArrays.js @@ -0,0 +1,54 @@ +// var GL_MAP = {}; + +/** + * @param gl {WebGLRenderingContext} The current WebGL context + * @param attribs {*} + * @param state {*} + */ +export default function setVertexAttribArrays(gl, attribs, state) +{ + let i; + + if (state) + { + const tempAttribState = state.tempAttribState; + const attribState = state.attribState; + + for (i = 0; i < tempAttribState.length; i++) + { + tempAttribState[i] = false; + } + + // set the new attribs + for (i = 0; i < attribs.length; i++) + { + tempAttribState[attribs[i].attribute.location] = true; + } + + for (i = 0; i < attribState.length; i++) + { + if (attribState[i] !== tempAttribState[i]) + { + attribState[i] = tempAttribState[i]; + + if (state.attribState[i]) + { + gl.enableVertexAttribArray(i); + } + else + { + gl.disableVertexAttribArray(i); + } + } + } + } + else + { + for (i = 0; i < attribs.length; i++) + { + const attrib = attribs[i]; + + gl.enableVertexAttribArray(attrib.attribute.location); + } + } +} diff --git a/packages/core/src/index.js b/packages/core/src/index.js index 58c456a..fd1f547 100644 --- a/packages/core/src/index.js +++ b/packages/core/src/index.js @@ -1,30 +1,30 @@ import * as resources from './textures/resources'; -import * as systems from './renderers/systems'; +import * as systems from './systems'; -export { resources }; export { systems }; +export { resources }; -export { default as Renderer } from './renderers/Renderer'; -export { default as AbstractRenderer } from './renderers/AbstractRenderer'; -export { default as FrameBuffer } from './textures/FrameBuffer'; +export { default as System } from './System'; +export { default as Renderer } from './Renderer'; +export { default as AbstractRenderer } from './AbstractRenderer'; +export { default as FrameBuffer } from './framebuffer/FrameBuffer'; export { default as CubeTexture } from './textures/CubeTexture'; export { default as BaseTexture } from './textures/BaseTexture'; export { default as Texture } from './textures/Texture'; export { default as TextureMatrix } from './textures/TextureMatrix'; -export { default as RenderTexture } from './textures/RenderTexture'; -export { default as BaseRenderTexture } from './textures/BaseRenderTexture'; +export { default as RenderTexture } from './renderTexture/RenderTexture'; +export { default as BaseRenderTexture } from './renderTexture/BaseRenderTexture'; export { default as TextureUvs } from './textures/TextureUvs'; -export { default as State } from './renderers/State'; -export { default as ObjectRenderer } from './renderers/utils/ObjectRenderer'; -export { default as Quad } from './renderers/utils/Quad'; -export { default as QuadUv } from './renderers/utils/QuadUv'; -export { default as checkMaxIfStatmentsInShader } from './renderers/utils/checkMaxIfStatmentsInShader'; +export { default as State } from './state/State'; +export { default as ObjectRenderer } from './batch/ObjectRenderer'; +export { default as Quad } from './utils/Quad'; +export { default as QuadUv } from './utils/QuadUv'; +export { default as checkMaxIfStatmentsInShader } from './shader/utils/checkMaxIfStatmentsInShader'; export { default as Shader } from './shader/Shader'; export { default as Program } from './shader/Program'; export { default as UniformGroup } from './shader/UniformGroup'; -export { default as SpriteMaskFilter } from './renderers/filters/spriteMask/SpriteMaskFilter'; -export { default as Filter } from './renderers/filters/Filter'; +export { default as SpriteMaskFilter } from './filters/spriteMask/SpriteMaskFilter'; +export { default as Filter } from './filters/Filter'; export { default as Attribute } from './geometry/Attribute'; export { default as Buffer } from './geometry/Buffer'; export { default as Geometry } from './geometry/Geometry'; - diff --git a/packages/core/src/mask/MaskSystem.js b/packages/core/src/mask/MaskSystem.js new file mode 100644 index 0000000..99174c2 --- /dev/null +++ b/packages/core/src/mask/MaskSystem.js @@ -0,0 +1,199 @@ +import System from '../System'; +import AlphaMaskFilter from '../filters/spriteMask/SpriteMaskFilter'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class MaskSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + // TODO - we don't need both! + this.scissor = false; + this.scissorData = null; + this.scissorRenderTarget = null; + + this.enableScissor = false; + + this.alphaMaskPool = []; + this.alphaMaskIndex = 0; + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.DisplayObject} target - Display Object to push the mask to + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + push(target, maskData) + { + // TODO the root check means scissor rect will not + // be used on render textures more info here: + // https://github.com/pixijs/pixi.js/pull/3545 + + if (maskData.texture) + { + this.pushSpriteMask(target, maskData); + } + else if (this.enableScissor + && !this.scissor + && this.renderer._activeRenderTarget.root + && !this.renderer.stencil.stencilMaskStack.length + && maskData.isFastRect()) + { + const matrix = maskData.worldTransform; + + let rot = Math.atan2(matrix.b, matrix.a); + + // use the nearest degree! + rot = Math.round(rot * (180 / Math.PI)); + + if (rot % 90) + { + this.pushStencilMask(maskData); + } + else + { + this.pushScissorMask(target, maskData); + } + } + else + { + this.pushStencilMask(maskData); + } + } + + /** + * Removes the last mask from the mask stack and doesn't return it. + * + * @param {PIXI.DisplayObject} target - Display Object to pop the mask from + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + pop(target, maskData) + { + if (maskData.texture) + { + this.popSpriteMask(target, maskData); + } + else if (this.enableScissor && !this.renderer.stencil.stencilMaskStack.length) + { + this.popScissorMask(target, maskData); + } + else + { + this.popStencilMask(target, maskData); + } + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.RenderTarget} target - Display Object to push the sprite mask to + * @param {PIXI.Sprite} maskData - Sprite to be used as the mask + */ + pushSpriteMask(target, maskData) + { + let alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; + + if (!alphaMaskFilter) + { + alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; + } + + alphaMaskFilter[0].resolution = this.renderer.resolution; + alphaMaskFilter[0].maskSprite = maskData; + + // TODO - may cause issues! + target.filterArea = maskData.getBounds(true); + + this.renderer.filterSystem.pushFilter(target, alphaMaskFilter); + + this.alphaMaskIndex++; + } + + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popSpriteMask() + { + this.renderer.filterSystem.popFilter(); + this.alphaMaskIndex--; + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + pushStencilMask(maskData) + { + this.renderer.batch.flush(); + this.renderer.stencil.pushStencil(maskData); + } + + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popStencilMask() + { + // this.renderer.currentRenderer.stop(); + this.renderer.stencil.popStencil(); + } + + /** + * + * @param {PIXI.DisplayObject} target - Display Object to push the mask to + * @param {PIXI.Graphics} maskData - The masking data. + */ + pushScissorMask(target, maskData) + { + maskData.renderable = true; + + const renderTarget = this.renderer._activeRenderTarget; + + const bounds = maskData.getBounds(); + + bounds.fit(renderTarget.size); + maskData.renderable = false; + + this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); + + const resolution = this.renderer.resolution; + + this.renderer.gl.scissor( + bounds.x * resolution, + (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, + bounds.width * resolution, + bounds.height * resolution + ); + + this.scissorRenderTarget = renderTarget; + this.scissorData = maskData; + this.scissor = true; + } + + /** + * + * + */ + popScissorMask() + { + this.scissorRenderTarget = null; + this.scissorData = null; + this.scissor = false; + + // must be scissor! + const gl = this.renderer.gl; + + gl.disable(gl.SCISSOR_TEST); + } +} diff --git a/packages/core/src/mask/StencilSystem.js b/packages/core/src/mask/StencilSystem.js new file mode 100644 index 0000000..62796a5 --- /dev/null +++ b/packages/core/src/mask/StencilSystem.js @@ -0,0 +1,133 @@ +import System from '../System'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class StencilSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + this.stencilMaskStack = []; + } + + /** + * Changes the mask stack that is used by this System. + * + * @param {PIXI.Graphics[]} stencilMaskStack - The mask stack + */ + setMaskStack(stencilMaskStack) + { + const gl = this.renderer.gl; + + if (stencilMaskStack.length !== this.stencilMaskStack.length) + { + if (stencilMaskStack.length === 0) + { + gl.disable(gl.STENCIL_TEST); + } + else + { + gl.enable(gl.STENCIL_TEST); + } + } + + this.stencilMaskStack = stencilMaskStack; + } + + /** + * Applies the Mask and adds it to the current stencil stack. @alvin + * + * @param {PIXI.Graphics} graphics - The mask + */ + pushStencil(graphics) + { + this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); + + // this.renderer._activeRenderTarget.attachStencilBuffer(); + + const gl = this.renderer.gl; + const prevMaskCount = this.stencilMaskStack.length; + + if (prevMaskCount === 0) + { + gl.enable(gl.STENCIL_TEST); + } + + this.stencilMaskStack.push(graphics); + + // Increment the refference stencil value where the new mask overlaps with the old ones. + gl.colorMask(false, false, false, false); + gl.stencilFunc(gl.EQUAL, prevMaskCount, this._getBitwiseMask()); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.INCR); + this.renderer.plugins.graphics.render(graphics); + + this._useCurrent(); + } + + /** + * Removes the last mask from the stencil stack. @alvin + */ + popStencil() + { + this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); + + const gl = this.renderer.gl; + const graphics = this.stencilMaskStack.pop(); + + if (this.stencilMaskStack.length === 0) + { + // the stack is empty! + gl.disable(gl.STENCIL_TEST); + gl.clear(gl.STENCIL_BUFFER_BIT); + gl.clearStencil(0); + } + else + { + // Decrement the refference stencil value where the popped mask overlaps with the other ones + gl.colorMask(false, false, false, false); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.DECR); + this.renderer.plugins.graphics.render(graphics); + + this._useCurrent(); + } + } + + /** + * Setup renderer to use the current stencil data. + */ + _useCurrent() + { + const gl = this.renderer.gl; + + gl.colorMask(true, true, true, true); + gl.stencilFunc(gl.EQUAL, this.stencilMaskStack.length, this._getBitwiseMask()); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP); + } + + /** + * Fill 1s equal to the number of acitve stencil masks. + * + * @return {number} The bitwise mask. + */ + _getBitwiseMask() + { + return (1 << this.stencilMaskStack.length) - 1; + } + + /** + * Destroys the mask stack. + * + */ + destroy() + { + super.destroy(this); + + this.stencilMaskStack = null; + } +} diff --git a/packages/core/src/projection/ProjectionSystem.js b/packages/core/src/projection/ProjectionSystem.js new file mode 100644 index 0000000..6e63b73 --- /dev/null +++ b/packages/core/src/projection/ProjectionSystem.js @@ -0,0 +1,73 @@ +import System from '../System'; +import { Matrix } from '@pixi/math'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class ProjectionSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.projectionMatrix = new Matrix(); + } + + update(destinationFrame, sourceFrame, resolution, root) + { + this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; + this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; + + this.calculateProjection(this.destinationFrame, this.sourceFrame, resolution, root); + + this.renderer.globalUniforms.uniforms.projectionMatrix = this.projectionMatrix; + this.renderer.globalUniforms.update(); + } + + /** + * Updates the projection matrix based on a projection frame (which is a rectangle) + * + * @param {Rectangle} destinationFrame - The destination frame. + * @param {Rectangle} sourceFrame - The source frame. + */ + calculateProjection(destinationFrame, sourceFrame, resolution, root) + { + const pm = this.projectionMatrix; + + // I don't think we will need this line.. + // pm.identity(); + + if (!root) + { + pm.a = (1 / destinationFrame.width * 2) * resolution; + pm.d = (1 / destinationFrame.height * 2) * resolution; + + pm.tx = -1 - (sourceFrame.x * pm.a); + pm.ty = -1 - (sourceFrame.y * pm.d); + } + else + { + pm.a = (1 / destinationFrame.width * 2) * resolution; + pm.d = (-1 / destinationFrame.height * 2) * resolution; + + pm.tx = -1 - (sourceFrame.x * pm.a); + pm.ty = 1 - (sourceFrame.y * pm.d); + } + } + + /** + * Sets the transform of the active render target to the given matrix + * + * @param {PIXI.Matrix} matrix - The transformation matrix + */ + setTransform()// matrix) + { + // this._activeRenderTarget.transform = matrix; + } +} diff --git a/packages/core/src/renderTexture/BaseRenderTexture.js b/packages/core/src/renderTexture/BaseRenderTexture.js new file mode 100644 index 0000000..88eb83b --- /dev/null +++ b/packages/core/src/renderTexture/BaseRenderTexture.js @@ -0,0 +1,137 @@ +import BaseTexture from '../textures/BaseTexture'; +import FrameBuffer from '../framebuffer/FrameBuffer'; + +/** + * A BaseRenderTexture is a special texture that allows any PixiJS display object to be rendered to it. + * + * __Hint__: All DisplayObjects (i.e. Sprites) that render to a BaseRenderTexture should be preloaded + * otherwise black rectangles will be drawn instead. + * + * A BaseRenderTexture takes a snapshot of any Display Object given to its render method. The position + * and rotation of the given Display Objects is ignored. For example: + * + * ```js + * let renderer = PIXI.autoDetectRenderer(1024, 1024, { view: canvas, ratio: 1 }); + * let baseRenderTexture = new PIXI.BaseRenderTexture(renderer, 800, 600); + * let sprite = PIXI.Sprite.fromImage("spinObj_01.png"); + * + * sprite.position.x = 800/2; + * sprite.position.y = 600/2; + * sprite.anchor.x = 0.5; + * sprite.anchor.y = 0.5; + * + * baseRenderTexture.render(sprite); + * ``` + * + * The Sprite in this case will be rendered using its local transform. To render this sprite at 0,0 + * you can clear the transform + * + * ```js + * + * sprite.setTransform() + * + * let baseRenderTexture = new PIXI.BaseRenderTexture(100, 100); + * let renderTexture = new PIXI.RenderTexture(baseRenderTexture); + * + * renderer.render(sprite, renderTexture); // Renders to center of RenderTexture + * ``` + * + * @class + * @extends PIXI.BaseTexture + * @memberof PIXI + */ +export default class BaseRenderTexture extends BaseTexture +{ + /** + * @param {object} [options] + * @param {number} [options.width=100] - The width of the base render texture + * @param {number} [options.height=100] - The height of the base render texture + * @param {PIXI.SCALE_MODES} [options.scaleMode] - See {@link PIXI.SCALE_MODES} for possible values + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the texture being generated + */ + constructor(options) + { + if (typeof options === 'number') + { + /* eslint-disable prefer-rest-params */ + // Backward compatibility of signature + const width = arguments[0]; + const height = arguments[1]; + const scaleMode = arguments[2]; + const resolution = arguments[3]; + + options = { width, height, scaleMode, resolution }; + /* eslint-enable prefer-rest-params */ + } + + super(null, options); + + const { width, height } = options || {}; + + // Set defaults + this.mipmap = false; + this.width = Math.ceil(width) || 100; + this.height = Math.ceil(height) || 100; + this.valid = true; + + /** + * A map of renderer IDs to webgl renderTargets + * + * @private + * @member {object} + */ + // this._glRenderTargets = {}; + + /** + * A reference to the canvas render target (we only need one as this can be shared across renderers) + * + * @private + * @member {object} + */ + this._canvasRenderTarget = null; + + this.clearColor = [0, 0, 0, 0]; + + this.frameBuffer = new FrameBuffer(width * this.resolution, height * this.resolution) + .addColorTexture(0, this); + + // TODO - could this be added the systems? + + /** + * The data structure for the stencil masks + * + * @member {PIXI.Graphics[]} + */ + this.stencilMaskStack = []; + + /** + * The data structure for the filters + * + * @member {PIXI.Graphics[]} + */ + this.filterStack = [{}]; + } + + /** + * Resizes the BaseRenderTexture. + * + * @param {number} width - The width to resize to. + * @param {number} height - The height to resize to. + */ + resize(width, height) + { + width = Math.ceil(width); + height = Math.ceil(height); + this.frameBuffer.resize(width * this.resolution, height * this.resolution); + } + + /** + * Destroys this texture + * + */ + destroy() + { + super.destroy(true); + this.renderer = null; + } +} diff --git a/packages/core/src/renderTexture/RenderTexture.js b/packages/core/src/renderTexture/RenderTexture.js new file mode 100644 index 0000000..f1ecf1f --- /dev/null +++ b/packages/core/src/renderTexture/RenderTexture.js @@ -0,0 +1,153 @@ +import BaseRenderTexture from './BaseRenderTexture'; +import Texture from '../textures/Texture'; + +/** + * A RenderTexture is a special texture that allows any PixiJS display object to be rendered to it. + * + * __Hint__: All DisplayObjects (i.e. Sprites) that render to a RenderTexture should be preloaded + * otherwise black rectangles will be drawn instead. + * + * A RenderTexture takes a snapshot of any Display Object given to its render method. For example: + * + * ```js + * let renderer = PIXI.autoDetectRenderer(1024, 1024, { view: canvas, ratio: 1 }); + * let renderTexture = PIXI.RenderTexture.create(800, 600); + * let sprite = PIXI.Sprite.fromImage("spinObj_01.png"); + * + * sprite.position.x = 800/2; + * sprite.position.y = 600/2; + * sprite.anchor.x = 0.5; + * sprite.anchor.y = 0.5; + * + * renderer.render(sprite, renderTexture); + * ``` + * + * The Sprite in this case will be rendered using its local transform. To render this sprite at 0,0 + * you can clear the transform + * + * ```js + * + * sprite.setTransform() + * + * let renderTexture = new PIXI.RenderTexture.create(100, 100); + * + * renderer.render(sprite, renderTexture); // Renders to center of RenderTexture + * ``` + * + * @class + * @extends PIXI.Texture + * @memberof PIXI + */ +export default class RenderTexture extends Texture +{ + /** + * @param {PIXI.BaseRenderTexture} baseRenderTexture - The renderer used for this RenderTexture + * @param {PIXI.Rectangle} [frame] - The rectangle frame of the texture to show + */ + constructor(baseRenderTexture, frame) + { + // support for legacy.. + let _legacyRenderer = null; + + if (!(baseRenderTexture instanceof BaseRenderTexture)) + { + /* eslint-disable prefer-rest-params, no-console */ + const width = arguments[1]; + const height = arguments[2]; + const scaleMode = arguments[3]; + const resolution = arguments[4]; + + // we have an old render texture.. + console.warn(`Please use RenderTexture.create(${width}, ${height}) instead of the ctor directly.`); + _legacyRenderer = arguments[0]; + /* eslint-enable prefer-rest-params, no-console */ + + frame = null; + baseRenderTexture = new BaseRenderTexture({ + width, + height, + scaleMode, + resolution, + }); + } + + /** + * The base texture object that this texture uses + * + * @member {BaseTexture} + */ + super(baseRenderTexture, frame); + + this.legacyRenderer = _legacyRenderer; + + /** + * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. + * + * @member {boolean} + */ + this.valid = true; + + /** + * FilterSystem temporary storage + * @private + * @member {PIXI.Rectangle} + */ + this.filterFrame = null; + + this._updateUvs(); + } + + /** + * Resizes the RenderTexture. + * + * @param {number} width - The width to resize to. + * @param {number} height - The height to resize to. + * @param {boolean} [resizeBaseTexture=true] - Should the baseTexture.width and height values be resized as well? + */ + resize(width, height, resizeBaseTexture = true) + { + width = Math.ceil(width); + height = Math.ceil(height); + + // TODO - could be not required.. + this.valid = (width > 0 && height > 0); + + this._frame.width = this.orig.width = width; + this._frame.height = this.orig.height = height; + + if (resizeBaseTexture) + { + this.baseTexture.resize(width, height); + } + + this._updateUvs(); + } + + /** + * A short hand way of creating a render texture. + * + * @param {object} [options] - Options + * @param {number} [options.width=100] - The width of the render texture + * @param {number} [options.height=100] - The height of the render texture + * @param {number} [options.scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the texture being generated + * @return {PIXI.RenderTexture} The new render texture + */ + static create(options) + { + // fallback, old-style: create(width, height, scaleMode, resolution) + if (typeof options === 'number') + { + /* eslint-disable prefer-rest-params */ + options = { + width: options, + height: arguments[1], + scaleMode: arguments[2], + resolution: arguments[3], + }; + /* eslint-enable prefer-rest-params */ + } + + return new RenderTexture(new BaseRenderTexture(options)); + } +} diff --git a/packages/core/src/renderTexture/RenderTextureSystem.js b/packages/core/src/renderTexture/RenderTextureSystem.js new file mode 100644 index 0000000..8d0ca61 --- /dev/null +++ b/packages/core/src/renderTexture/RenderTextureSystem.js @@ -0,0 +1,115 @@ +import System from '../System'; +import { Rectangle } from '@pixi/math'; + +const tempRect = new Rectangle(); + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class RenderTextureSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.clearColor = renderer._backgroundColorRgba; + + // TODO moe this property somewhere else! + this.defaultMaskStack = []; + this.defaultFilterStack = [{}]; + + // empty render texture? + this.renderTexture = null; + + this.destinationFrame = new Rectangle(); + } + + bind(renderTexture, sourceFrame, destinationFrame) + { + // TODO - do we want this?? + if (this.renderTexture === renderTexture) return; + this.renderTexture = renderTexture; + + const renderer = this.renderer; + + if (renderTexture) + { + const baseTexture = renderTexture.baseTexture; + + if (!destinationFrame) + { + tempRect.width = baseTexture.realWidth; + tempRect.height = baseTexture.realHeight; + + destinationFrame = tempRect; + } + + if (!sourceFrame) + { + sourceFrame = destinationFrame; + } + + this.renderer.framebuffer.bind(baseTexture.frameBuffer, destinationFrame); + + this.renderer.projection.update(destinationFrame, sourceFrame, baseTexture.resolution, false); + this.renderer.stencil.setMaskStack(baseTexture.stencilMaskStack); + } + else + { + // TODO these validation checks happen deeper down.. + // thing they can be avoided.. + if (!destinationFrame) + { + tempRect.width = renderer.width; + tempRect.height = renderer.height; + + destinationFrame = tempRect; + } + + if (!sourceFrame) + { + sourceFrame = destinationFrame; + } + + renderer.framebuffer.bind(null, destinationFrame); + + // TODO store this.. + this.renderer.projection.update(destinationFrame, sourceFrame, this.renderer.resolution, true); + this.renderer.stencil.setMaskStack(this.defaultMaskStack); + } + + this.destinationFrame.copyFrom(destinationFrame); + } + + /** + * Erases the render texture and fills the drawing area with a colour + * + * @param {number} [clearColor] - The colour + * @return {PIXI.Renderer} Returns itself. + */ + clear(clearColor) + { + if (this.renderTexture) + { + clearColor = clearColor || this.renderTexture.baseTexture.clearColor; + } + else + { + clearColor = clearColor || this.clearColor; + } + + this.renderer.framebuffer.clear(clearColor[0], clearColor[1], clearColor[2], clearColor[3]); + } + + resize()// screenWidth, screenHeight) + { + // resize the root only! + this.bind(null); + } +} diff --git a/packages/core/src/renderers/AbstractRenderer.js b/packages/core/src/renderers/AbstractRenderer.js deleted file mode 100644 index 928670f..0000000 --- a/packages/core/src/renderers/AbstractRenderer.js +++ /dev/null @@ -1,353 +0,0 @@ -import { hex2string, hex2rgb } from '@pixi/utils'; -import { Matrix, Rectangle } from '@pixi/math'; -import { RENDERER_TYPE } from '@pixi/constants'; -import { settings } from '@pixi/settings'; -import { Container } from '@pixi/display'; -import RenderTexture from '../textures/RenderTexture'; -import EventEmitter from 'eventemitter3'; - -const tempMatrix = new Matrix(); - -/** - * 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 EventEmitter - * @memberof PIXI - */ -export default class AbstractRenderer extends EventEmitter -{ - // eslint-disable-next-line valid-jsdoc - /** - * @param {string} system - The name of the system this renderer is for. - * @param {object} [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.transparent=false] - If the render view is transparent, default false - * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false - * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) - * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. The - * resolution of the renderer retina would be 2. - * @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 {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when rendering, - * stopping pixel interpolation. - */ - constructor(system, options, arg2, arg3) - { - super(); - - // Support for constructor(system, screenWidth, screenHeight, options) - if (typeof options === 'number') - { - options = Object.assign({ - width: options, - height: arg2 || settings.RENDER_OPTIONS.height, - }, arg3); - } - - // 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 = RENDERER_TYPE.UNKNOWN; - - /** - * Measurements of the screen. (0, 0, screenWidth, screenHeight) - * - * Its safe to use as filterArea or hitArea for 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 1 - */ - this.resolution = options.resolution || settings.RESOLUTION; - - /** - * Whether the render view is transparent - * - * @member {boolean} - */ - this.transparent = options.transparent; - - /** - * Whether css dimensions of canvas view should be resized to screen dimensions automatically - * - * @member {boolean} - */ - this.autoResize = options.autoResize || false; - - /** - * Tracks the blend modes useful for this renderer. - * - * @member {object} - */ - this.blendModes = null; - - /** - * 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; - - /** - * If true PixiJS will Math.floor() x/y values when rendering, stopping pixel interpolation. - * Handy for crisp pixel art and speed on legacy devices. - * - * @member {boolean} - */ - this.roundPixels = options.roundPixels; - - /** - * The background color as a number. - * - * @member {number} - * @private - */ - this._backgroundColor = 0x000000; - - /** - * The background color as an [R, G, B] array. - * - * @member {number[]} - * @private - */ - this._backgroundColorRgba = [0, 0, 0, 0]; - - /** - * The background color as a string. - * - * @member {string} - * @private - */ - this._backgroundColorString = '#000000'; - - this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter - - /** - * This temporary display object used as the parent of the currently being rendered item - * - * @member {PIXI.DisplayObject} - * @private - */ - this._tempDisplayObjectParent = new Container(); - - /** - * The last root object that the renderer tried to render. - * - * @member {PIXI.DisplayObject} - * @private - */ - this._lastObjectRendered = this._tempDisplayObjectParent; - - /** - * Collection of plugins. - * @readonly - * @member {object} - */ - this.plugins = {}; - } - - /** - * Initialize the plugins. - * - * @protected - * @param {object} staticMap - The dictionary of staticly saved plugins. - */ - initPlugins(staticMap) - { - 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() - { - return this.view.width; - } - - /** - * Same as view.height, actual number of pixels in the canvas by vertical - * - * @member {number} - * @readonly - * @default 600 - */ - get height() - { - return this.view.height; - } - - /** - * Resizes the screen and canvas to the specified width and height - * Canvas dimensions are multiplied by resolution - * - * @param {number} screenWidth - the new width of the screen - * @param {number} screenHeight - the new height of the screen - */ - resize(screenWidth, screenHeight) - { - this.screen.width = screenWidth; - this.screen.height = screenHeight; - - this.view.width = screenWidth * this.resolution; - this.view.height = screenHeight * this.resolution; - - if (this.autoResize) - { - this.view.style.width = `${screenWidth}px`; - this.view.style.height = `${screenHeight}px`; - } - } - - /** - * 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 {PIXI.DisplayObject} displayObject - The displayObject the object will be generated from - * @param {number} scaleMode - Should be one of the scaleMode consts - * @param {number} resolution - The resolution / device pixel ratio of the texture being generated - * @param {PIXI.Rectangle} [region] - The region of the displayObject, that shall be rendered, - * if no region is specified, defaults to the local bounds of the displayObject. - * @return {PIXI.Texture} a texture of the graphics object - */ - generateTexture(displayObject, scaleMode, resolution, region) - { - region = region || displayObject.getLocalBounds(); - - // 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(region.width | 0, region.height | 0, scaleMode, resolution); - - tempMatrix.tx = -region.x; - tempMatrix.ty = -region.y; - - this.render(displayObject, renderTexture, false, tempMatrix, true); - - return renderTexture; - } - - /** - * Removes everything from the renderer and optionally removes the Canvas DOM element. - * - * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. - */ - destroy(removeView) - { - 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); - } - - this.plugins = null; - - this.type = RENDERER_TYPE.UNKNOWN; - - this.view = null; - - this.screen = null; - - this.resolution = 0; - - this.transparent = false; - - this.autoResize = false; - - this.blendModes = null; - - this.options = null; - - this.preserveDrawingBuffer = false; - this.clearBeforeRender = false; - - this.roundPixels = false; - - this._backgroundColor = 0; - this._backgroundColorRgba = null; - this._backgroundColorString = null; - - this._tempDisplayObjectParent = null; - this._lastObjectRendered = null; - } - - /** - * The background color to fill if not transparent - * - * @member {number} - */ - get backgroundColor() - { - return this._backgroundColor; - } - - set backgroundColor(value) // eslint-disable-line require-jsdoc - { - this._backgroundColor = value; - this._backgroundColorString = hex2string(value); - hex2rgb(value, this._backgroundColorRgba); - } -} diff --git a/packages/core/src/renderers/Renderer.js b/packages/core/src/renderers/Renderer.js deleted file mode 100644 index a2020b1..0000000 --- a/packages/core/src/renderers/Renderer.js +++ /dev/null @@ -1,329 +0,0 @@ -import AbstractRenderer from './AbstractRenderer'; -import { sayHello } from '@pixi/utils'; -import MaskSystem from './systems/MaskSystem'; -import StencilSystem from './systems/StencilSystem'; -import FilterSystem from './systems/filter/FilterSystem'; -import FramebufferSystem from './systems/FramebufferSystem'; -import RenderTextureSystem from './systems/RenderTextureSystem'; -import TextureSystem from './systems/textures/TextureSystem'; -import ProjectionSystem from './systems/ProjectionSystem'; -import StateSystem from './systems/StateSystem'; -import GeometrySystem from './systems/geometry/GeometrySystem'; -import ShaderSystem from './systems/shader/ShaderSystem'; -import ContextSystem from './systems/ContextSystem'; -import BatchSystem from './systems/BatchSystem'; -import TextureGCSystem from './systems/textures/TextureGCSystem'; -import { RENDERER_TYPE } from '@pixi/constants'; -import UniformGroup from '../shader/UniformGroup'; -import { Matrix } from '@pixi/math'; -import Runner from 'mini-runner'; - -/** - * 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 Render works by automatically managing webGLBatchs. - * 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 :) - * - * @class - * @memberof PIXI - * @extends PIXI.AbstractRenderer - */ -export default class Renderer extends AbstractRenderer -{ - // eslint-disable-next-line valid-jsdoc - /** - * - * @param {object} [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.transparent=false] - If the render view is transparent, default false - * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false - * @param {boolean} [options.antialias=false] - sets antialias. If not available natively then FXAA - * antialiasing is used - * @param {boolean} [options.forceFXAA=false] - forces FXAA antialiasing to be used over native. - * FXAA is faster, but may not always look as great - * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. - * The resolution of the renderer retina would be 2. - * @param {boolean} [options.clearBeforeRender=true] - This sets if the renderer will clear - * the canvas or not before the new render pass. If you wish to set this to false, you *must* set - * preserveDrawingBuffer to `true`. - * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, - * enable this if you need to call toDataUrl on the webgl context. - * @param {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when - * rendering, stopping pixel interpolation. - * @param {number} [options.backgroundColor=0x000000] - The background color of the rendered area - * (shown if not transparent). - * @param {boolean} [options.legacy=false] - If true PixiJS will aim to ensure compatibility - * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. - * @param {string} [options.powerPreference] - Parameter passed to webgl context, set to "high-performance" - * for devices with dual graphics card - */ - constructor(options, arg2, arg3) - { - super('WebGL', options, arg2, arg3); - - /** - * The type of this renderer as a standardised const - * - * @member {number} - * @see PIXI.RENDERER_TYPE - */ - this.type = RENDERER_TYPE.WEBGL; - - // this will be set by the contextSystem (this.context) - this.gl = null; - this.CONTEXT_UID = 0; - this.legacy = !!options.legacy; - - // TODO legacy! - - // runners! - this.runners = { - destroy: new Runner('destroy'), - contextChange: new Runner('contextChange', 1), - reset: new Runner('reset'), - update: new Runner('update'), - postrender: new Runner('postrender'), - prerender: new Runner('prerender'), - resize: new Runner('resize', 2), - }; - - 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(GeometrySystem, 'geometry') - .addSystem(FramebufferSystem, 'framebuffer') - .addSystem(StencilSystem, 'stencil') - .addSystem(ProjectionSystem, 'projection') - .addSystem(TextureGCSystem, 'textureGC') - .addSystem(FilterSystem, 'filter') - .addSystem(RenderTextureSystem, 'renderTexture') - .addSystem(BatchSystem, 'batch'); - - this.initPlugins(Renderer.__plugins); - - /** - * The options passed in to create a new webgl context. - * - * @member {object} - * @private - */ - if (options.context) - { - this.context.initFromContext(options.context); - } - else - { - this.context.initFromOptions({ - alpha: this.transparent, - antialias: options.antialias, - premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', - stencil: true, - 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); - } - - addSystem(_class, name) - { - if (!name) - { - name = _class.name; - } - - // TODO - read name from class.name.. - - /* - if(name.includes('System')) - { - name = name.replace('System', ''); - name = name.charAt(0).toLowerCase() + name.slice(1); - } - */ - - const system = new _class(this); - - if (this[name]) - { - throw new Error(`Whoops! ${name} is already a manger`); - } - - this[name] = system; - - for (const i in this.runners) - { - this.runners[i].add(system); - } - - return this; - - /** - * 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. - */ - } - - /** - * Renders the object to its webGL view - * - * @param {PIXI.DisplayObject} displayObject - the object to be rendered - * @param {PIXI.RenderTexture} renderTexture - The render texture to render to. - * @param {boolean} [clear] - Should the canvas be cleared before the new render - * @param {PIXI.Transform} [transform] - A transform to apply to the render texture before rendering. - * @param {boolean} [skipUpdateTransform] - Should we skip the update transform pass? - */ - render(displayObject, renderTexture, clear, transform, skipUpdateTransform) - { - // can be handy to know! - this.renderingToScreen = !renderTexture; - - this.runners.prerender.run(); - this.emit('prerender'); - - // 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.parent; - - displayObject.parent = this._tempDisplayObjectParent; - displayObject.updateTransform(); - displayObject.parent = 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.run(); - - this.emit('postrender'); - } - - /** - * Resizes the webGL view to the specified width and height. - * - * @param {number} screenWidth - the new width of the screen - * @param {number} screenHeight - the new height of the screen - */ - resize(screenWidth, screenHeight) - { - AbstractRenderer.prototype.resize.call(this, screenWidth, screenHeight); - - this.runners.resize.run(screenWidth, screenHeight); - } - - /** - * Resets the WebGL state so you can render things however you fancy! - * - * @return {PIXI.Renderer} Returns itself. - */ - reset() - { - this.runners.reset.run(); - - return this; - } - - clear() - { - this.framebuffer.bind(); - this.framebuffer.clear(); - } - - /** - * Removes everything from the renderer (event listeners, spritebatch, etc...) - * - * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. - * See: https://github.com/pixijs/pixi.js/issues/2233 - */ - destroy(removeView) - { - this.runners.destroy.run(); - - // call base destroy - super.destroy(removeView); - - // TODO nullify all the managers.. - this.gl = null; - } - - /** - * 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 - * builds and excluding plugins. - * @name PIXI.Renderer#plugins - * @type {object} - * @readonly - * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. - * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. - * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. - * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. - */ - - /** - * Adds a plugin to the renderer. - * - * @method - * @param {string} pluginName - The name of the plugin. - * @param {Function} ctor - The constructor function or class for the plugin. - */ - static registerPlugin(pluginName, ctor) - { - Renderer.__plugins = Renderer.__plugins || {}; - Renderer.__plugins[pluginName] = ctor; - } -} diff --git a/packages/core/src/renderers/State.js b/packages/core/src/renderers/State.js deleted file mode 100644 index fa3f221..0000000 --- a/packages/core/src/renderers/State.js +++ /dev/null @@ -1,163 +0,0 @@ -/* eslint-disable max-len */ - -const BLEND = 0; -const OFFSET = 1; -const CULLING = 2; -const DEPTH_TEST = 3; -const WINDING = 4; - -/** - * This is a webGL state. It is passed The WebGL StateManager. - * Each mesh renderered may require webGL to be in a different state. - * For example you may want different blend mode or to enable polygon offsets - * - * @class - * @memberof PIXI - */ -export default class State -{ - /** - * - */ - constructor() - { - this.data = 0; - - this.blendMode = 0; - this.polygonOffset = 0; - - this.blend = true; - // this.depthTest = true; - } - - /** - * Activates blending of the computed fragment color values - * - * @member {boolean} - */ - get blend() - { - return !!(this.data & (1 << BLEND)); - } - - set blend(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << BLEND)) !== value) - { - this.data ^= (1 << BLEND); - } - } - - /** - * Activates adding an offset to depth values of polygon's fragments - * - * @member {boolean} - * @default false - */ - get offsets() - { - return !!(this.data & (1 << OFFSET)); - } - - set offsets(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << OFFSET)) !== value) - { - this.data ^= (1 << OFFSET); - } - } - - /** - * Activates culling of polygons. - * - * @member {boolean} - * @default false - */ - get culling() - { - return !!(this.data & (1 << CULLING)); - } - - set culling(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << CULLING)) !== value) - { - this.data ^= (1 << CULLING); - } - } - - /** - * Activates depth comparisons and updates to the depth buffer. - * - * @member {boolean} - * @default false - */ - get depthTest() - { - return !!(this.data & (1 << DEPTH_TEST)); - } - - set depthTest(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << DEPTH_TEST)) !== value) - { - this.data ^= (1 << DEPTH_TEST); - } - } - - /** - * Specifies whether or not front or back-facing polygons can be culled. - * @member {boolean} - * @default false - */ - get clockwiseFrontFace() - { - return !!(this.data & (1 << WINDING)); - } - - set clockwiseFrontFace(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << WINDING)) !== value) - { - this.data ^= (1 << WINDING); - } - } - - /** - * The blend mode to be applied when this state is set. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. - * Setting this mode to anything other than NO_BLEND will automatically switch blending on. - * - * @member {boolean} - * @default PIXI.BLEND_MODES.NORMAL - * @see PIXI.BLEND_MODES - */ - get blendMode() - { - return this._blendMode; - } - - set blendMode(value) // eslint-disable-line require-jsdoc - { - // 17 is NO BLEND - this.blend = (value !== 17); - this._blendMode = value; - } - - /** - * The polygon offset. Setting this property to anything other than 0 will automatically enable poygon offset fill. - * - * @member {number} - * @default 0 - */ - get polygonOffset() - { - return this._polygonOffset; - } - - set polygonOffset(value) // eslint-disable-line require-jsdoc - { - this.offsets = !!value; - this._polygonOffset = value; - } -} - diff --git a/packages/core/src/renderers/filters/Filter.js b/packages/core/src/renderers/filters/Filter.js deleted file mode 100644 index 2073b8d..0000000 --- a/packages/core/src/renderers/filters/Filter.js +++ /dev/null @@ -1,140 +0,0 @@ -import Shader from '../../shader/Shader'; -import Program from '../../shader/Program'; -import State from '../State'; -import { settings } from '@pixi/settings'; -// import extractUniformsFromSrc from './extractUniformsFromSrc'; -import defaultVertex from './defaultFilter.vert'; -import defaultFragment from './defaultFilter.frag'; - -// let math = require('../../math'); -/** - * @class - * @memberof PIXI - * @extends PIXI.Shader - */ -export default class Filter extends Shader -{ - /** - * @param {string} [vertexSrc] - The source of the vertex shader. - * @param {string} [fragmentSrc] - The source of the fragment shader. - * @param {object} [uniforms] - Custom uniforms to use to augment the built-in ones. - */ - constructor(vertexSrc, fragmentSrc, uniforms) - { - const program = Program.from(vertexSrc, fragmentSrc); - - super(program, uniforms); - - /** - * The padding of the filter. Some filters require extra space to breath such as a blur. - * Increasing this will add extra width and height to the bounds of the object that the - * filter is applied to. - * - * @member {number} - */ - this.padding = 0; - - /** - * The resolution of the filter. Setting this to be lower will lower the quality but - * increase the performance of the filter. - * - * @member {number} - */ - this.resolution = settings.RESOLUTION; - - /** - * If enabled is true the filter is applied, if false it will not. - * - * @member {boolean} - */ - this.enabled = true; - - /** - * If enabled, PixiJS will fit the filter area into boundaries for better performance. - * Switch it off if it does not work for specific shader. - * - * @member {boolean} - */ - this.autoFit = true; - - /** - * Legacy filters use position and uvs from attributes - * @member {boolean} - * @readonly - */ - this.legacy = !!this.program.attributeData.aTextureCoord; - - /** - * the webGL state the filter requires to render - * @member {PIXI.State} - */ - this.state = new State(); - } - - /** - * Applies the filter - * - * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from - * @param {PIXI.RenderTarget} input - The input render target. - * @param {PIXI.RenderTarget} output - The target to output to. - * @param {boolean} clear - Should the output be cleared before rendering to it - * @param {object} [currentState] - It's current state of filter. - * There are some useful properties in the currentState : - * target, filters, sourceFrame, destinationFrame, renderTarget, resolution - */ - apply(filterManager, input, output, clear, currentState, derp) // eslint-disable-line no-unused-vars - { - // do as you please! - - filterManager.applyFilter(this, input, output, clear, currentState, derp); - - // or just do a regular render.. - } - - /** - * Sets the blendmode of the filter - * - * @member {number} - * @default PIXI.BLEND_MODES.NORMAL - */ - get blendMode() - { - return this.state.blendMode; - } - - set blendMode(value) // eslint-disable-line require-jsdoc - { - this.state.blendMode = value; - } - - /** - * The default vertex shader source - * - * @static - * @constant - */ - static get defaultVertexSrc() - { - return defaultVertex; - } - - /** - * The default fragment shader source - * - * @static - * @constant - */ - static get defaultFragmentSrc() - { - return defaultFragment; - } -} - -/** - * Used for caching shader IDs - * - * @static - * @private - */ -Filter.SOURCE_KEY_MAP = {}; - diff --git a/packages/core/src/renderers/filters/defaultFilter.frag b/packages/core/src/renderers/filters/defaultFilter.frag deleted file mode 100644 index a14c1be..0000000 --- a/packages/core/src/renderers/filters/defaultFilter.frag +++ /dev/null @@ -1,22 +0,0 @@ -varying vec2 vTextureCoord; -varying vec2 vFilterCoord; - -uniform sampler2D uSampler; -uniform sampler2D filterSampler; - -void main(void){ - vec4 masky = texture2D(filterSampler, vFilterCoord); - vec4 sample = texture2D(uSampler, vTextureCoord); - vec4 color; - if(mod(vFilterCoord.x, 1.0) > 0.5) - { - color = vec4(1.0, 0.0, 0.0, 1.0); - } - else - { - color = vec4(0.0, 1.0, 0.0, 1.0); - } - // gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0); - gl_FragColor = mix(sample, masky, 0.5); - gl_FragColor *= sample.a; -} diff --git a/packages/core/src/renderers/filters/defaultFilter.vert b/packages/core/src/renderers/filters/defaultFilter.vert deleted file mode 100644 index e1febca..0000000 --- a/packages/core/src/renderers/filters/defaultFilter.vert +++ /dev/null @@ -1,14 +0,0 @@ -attribute vec2 aVertexPosition; -attribute vec2 aTextureCoord; - -uniform mat3 projectionMatrix; -uniform mat3 filterMatrix; - -varying vec2 vTextureCoord; -varying vec2 vFilterCoord; - -void main(void){ - gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); - vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy; - vTextureCoord = aTextureCoord ; -} diff --git a/packages/core/src/renderers/filters/filterTransforms.js b/packages/core/src/renderers/filters/filterTransforms.js deleted file mode 100644 index 87a3cd6..0000000 --- a/packages/core/src/renderers/filters/filterTransforms.js +++ /dev/null @@ -1,50 +0,0 @@ -import { Matrix } from '@pixi/math'; - -/** - * Calculates the mapped matrix - * @param filterArea {Rectangle} The filter area - * @param sprite {Sprite} the target sprite - * @param outputMatrix {Matrix} @alvin - * @private - */ -// this returns a matrix that will normalise map filter cords in the filter to screen space -export function calculateScreenSpaceMatrix(outputMatrix, filterArea, textureSize) -{ - // TODO unwrap? - const mappedMatrix = outputMatrix.identity(); - - mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); - - mappedMatrix.scale(textureSize.width, textureSize.height); - - return mappedMatrix; -} - -export function calculateNormalizedScreenSpaceMatrix(outputMatrix, filterArea, textureSize) -{ - const mappedMatrix = outputMatrix.identity(); - - mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); - - const translateScaleX = (textureSize.width / filterArea.width); - const translateScaleY = (textureSize.height / filterArea.height); - - mappedMatrix.scale(translateScaleX, translateScaleY); - - return mappedMatrix; -} - -// this will map the filter coord so that a texture can be used based on the transform of a sprite -export function calculateSpriteMatrix(outputMatrix, filterArea, textureSize, sprite) -{ - const orig = sprite._texture.orig; - const mappedMatrix = outputMatrix.set(textureSize.width, 0, 0, textureSize.height, filterArea.x, filterArea.y); - const worldTransform = sprite.worldTransform.copyTo(Matrix.TEMP_MATRIX); - - worldTransform.invert(); - mappedMatrix.prepend(worldTransform); - mappedMatrix.scale(1.0 / orig.width, 1.0 / orig.height); - mappedMatrix.translate(sprite.anchor.x, sprite.anchor.y); - - return mappedMatrix; -} diff --git a/packages/core/src/renderers/filters/spriteMask/SpriteMaskFilter.js b/packages/core/src/renderers/filters/spriteMask/SpriteMaskFilter.js deleted file mode 100644 index d5a91a4..0000000 --- a/packages/core/src/renderers/filters/spriteMask/SpriteMaskFilter.js +++ /dev/null @@ -1,63 +0,0 @@ -import Filter from '../Filter'; -import { Matrix } from '@pixi/math'; -import vertex from './spriteMaskFilter.vert'; -import fragment from './spriteMaskFilter.frag'; -import { default as TextureMatrix } from '../../../textures/TextureMatrix'; - -/** - * The SpriteMaskFilter class - * - * @class - * @extends PIXI.Filter - * @memberof PIXI - */ -export default class SpriteMaskFilter extends Filter -{ - /** - * @param {PIXI.Sprite} sprite - the target sprite - */ - constructor(sprite) - { - const maskMatrix = new Matrix(); - - super(vertex, fragment); - - sprite.renderable = false; - - this.maskSprite = sprite; - this.maskMatrix = maskMatrix; - } - - /** - * Applies the filter - * - * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from - * @param {PIXI.RenderTarget} input - The input render target. - * @param {PIXI.RenderTarget} output - The target to output to. - */ - apply(filterManager, input, output) - { - const maskSprite = this.maskSprite; - const tex = this.maskSprite.texture; - - if (!tex.valid) - { - return; - } - if (!tex.transform) - { - // margin = 0.0, let it bleed a bit, shader code becomes easier - // assuming that atlas textures were made with 1-pixel padding - tex.transform = new TextureMatrix(tex, 0.0); - } - tex.transform.update(); - - this.uniforms.mask = tex; - this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite) - .prepend(tex.transform.mapCoord); - this.uniforms.alpha = maskSprite.worldAlpha; - this.uniforms.maskClamp = tex.transform.uClampFrame; - - filterManager.applyFilter(this, input, output); - } -} diff --git a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.frag b/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.frag deleted file mode 100644 index 0e4aef8..0000000 --- a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.frag +++ /dev/null @@ -1,23 +0,0 @@ -varying vec2 vMaskCoord; -varying vec2 vTextureCoord; - -uniform sampler2D uSampler; -uniform sampler2D mask; -uniform float alpha; -uniform vec4 maskClamp; - -void main(void) -{ - float clip = step(3.5, - step(maskClamp.x, vMaskCoord.x) + - step(maskClamp.y, vMaskCoord.y) + - step(vMaskCoord.x, maskClamp.z) + - step(vMaskCoord.y, maskClamp.w)); - - vec4 original = texture2D(uSampler, vTextureCoord); - vec4 masky = texture2D(mask, vMaskCoord); - - original *= (masky.r * masky.a * alpha * clip); - - gl_FragColor = original; -} diff --git a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.vert b/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.vert deleted file mode 100644 index d23a30c..0000000 --- a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.vert +++ /dev/null @@ -1,16 +0,0 @@ -attribute vec2 aVertexPosition; -attribute vec2 aTextureCoord; - -uniform mat3 projectionMatrix; -uniform mat3 otherMatrix; - -varying vec2 vMaskCoord; -varying vec2 vTextureCoord; - -void main(void) -{ - gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); - - vTextureCoord = aTextureCoord; - vMaskCoord = ( otherMatrix * vec3( aTextureCoord, 1.0) ).xy; -} diff --git a/packages/core/src/renderers/systems/BatchSystem.js b/packages/core/src/renderers/systems/BatchSystem.js deleted file mode 100644 index 70e9518..0000000 --- a/packages/core/src/renderers/systems/BatchSystem.js +++ /dev/null @@ -1,68 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import ObjectRenderer from '../utils/ObjectRenderer'; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ - -export default class BatchSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this manager works for. - */ - constructor(renderer) - { - super(renderer); - - /** - * An empty renderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.emptyRenderer = new ObjectRenderer(renderer); - - /** - * The currently active ObjectRenderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.currentRenderer = this.emptyRenderer; - } - - /** - * Changes the current renderer to the one given in parameter - * - * @param {PIXI.ObjectRenderer} objectRenderer - The object renderer to use. - */ - setObjectRenderer(objectRenderer) - { - if (this.currentRenderer === objectRenderer) - { - return; - } - - this.currentRenderer.stop(); - this.currentRenderer = objectRenderer; - - this.renderer.state.setState(objectRenderer.state); - - this.currentRenderer.start(); - } - - /** - * This should be called if you wish to do some custom rendering - * It will basically render anything that may be batched up such as sprites - * - */ - flush() - { - this.setObjectRenderer(this.emptyRenderer); - } - - reset() - { - this.setObjectRenderer(this.emptyRenderer); - } -} diff --git a/packages/core/src/renderers/systems/ContextSystem.js b/packages/core/src/renderers/systems/ContextSystem.js deleted file mode 100644 index 43bf827..0000000 --- a/packages/core/src/renderers/systems/ContextSystem.js +++ /dev/null @@ -1,186 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { settings } from '@pixi/settings'; - -let CONTEXT_UID = 0; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class ContextSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this.webGLVersion = 1; - - this.handleContextLost = this.handleContextLost.bind(this); - this.handleContextRestored = this.handleContextRestored.bind(this); - - this.extensions = {}; - - renderer.view.addEventListener('webglcontextlost', this.handleContextLost, false); - renderer.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); - } - - get isLost() - { - return (!this.gl || this.gl.isContextLost()); - } - - contextChange(gl) - { - this.gl = gl; - - // restore a context if it was previously lost - if (gl.isContextLost() && gl.getExtension('WEBGL_lose_context')) - { - gl.getExtension('WEBGL_lose_context').restoreContext(); - } - } - - initFromContext(gl) - { - this.gl = gl; - this.validateContext(gl); - this.renderer.gl = gl; - this.renderer.CONTEXT_UID = CONTEXT_UID++; - this.renderer.runners.contextChange.run(gl); - } - - initFromOptions(options) - { - const gl = this.createContext(this.renderer.view, options); - - this.initFromContext(gl); - } - - /** - * Helper class to create a webGL Context - * - * @class - * @memberof PIXI.glCore - * @param canvas {HTMLCanvasElement} the canvas element that we will get the context from - * @param options {Object} An options object that gets passed in to the canvas element containing the context attributes, - * see https://developer.mozilla.org/en/docs/Web/API/HTMLCanvasElement/getContext for the options available - * @return {WebGLRenderingContext} the WebGL context - */ - createContext(canvas, options) - { - let gl; - - if (settings.PREFER_WEBGL_2) - { - gl = canvas.getContext('webgl2', options); - } - - if (gl) - { - this.webGLVersion = 2; - } - else - { - this.webGLVersion = 1; - - gl = canvas.getContext('webgl', options) - || canvas.getContext('experimental-webgl', options); - - if (!gl) - { - // fail, not able to get a context - throw new Error('This browser does not support webGL. Try using the canvas renderer'); - } - } - - this.gl = gl; - - this.getExtensions(); - - return gl; - } - - getExtensions() - { - // time to set up default etensions that pixi uses.. - const gl = this.gl; - const extensions = this.extensions; - - if (this.webGLVersion === 1) - { - extensions.drawBuffers = gl.getExtension('WEBGL_draw_buffers'); - extensions.depthTexture = gl.getExtension('WEBKIT_WEBGL_depth_texture'); - extensions.floatTexture = gl.getExtension('OES_texture_float'); - extensions.loseContext = gl.getExtension('WEBGL_lose_context'); - - extensions.vertexArrayObject = gl.getExtension('OES_vertex_array_object') - || gl.getExtension('MOZ_OES_vertex_array_object') - || gl.getExtension('WEBKIT_OES_vertex_array_object'); - } - - // we don't use any specific WebGL 2 ones yet! - } - - /** - * Handles a lost webgl context - * - * @private - * @param {WebGLContextEvent} event - The context lost event. - */ - handleContextLost(event) - { - event.preventDefault(); - } - - /** - * Handles a restored webgl context - * - * @private - */ - handleContextRestored() - { - this.renderer.runners.contextChange.run(this.gl); - } - - destroy() - { - const view = this.renderer.view; - - // remove listeners - view.removeEventListener('webglcontextlost', this.handleContextLost); - view.removeEventListener('webglcontextrestored', this.handleContextRestored); - - this.gl.useProgram(null); - - if (this.extensions.loseContext) - { - this.extensions.loseContext.loseContext(); - } - } - - postrender() - { - this.gl.flush(); - } - - validateContext(gl) - { - const attributes = gl.getContextAttributes(); - - // this is going to be fairly simple for now.. but at least we have room to grow! - if (!attributes.stencil) - { - /* eslint-disable max-len */ - - /* eslint-disable no-console */ - console.warn('Provided WebGL context does not have a stencil buffer, masks may not render correctly'); - /* eslint-enable no-console */ - - /* eslint-enable max-len */ - } - } -} diff --git a/packages/core/src/renderers/systems/FramebufferSystem.js b/packages/core/src/renderers/systems/FramebufferSystem.js deleted file mode 100644 index df7902b..0000000 --- a/packages/core/src/renderers/systems/FramebufferSystem.js +++ /dev/null @@ -1,246 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { Rectangle } from '@pixi/math'; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class FramebufferSystem extends WebGLSystem -{ - /** - * Sets up the renderer context and necessary buffers. - * - * @private - */ - contextChange() - { - this.gl = this.renderer.gl; - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - this.current = null; - this.viewport = new Rectangle(); - - this.drawBufferExtension = this.renderer.context.extensions.drawBuffers; - } - - bind(framebuffer, frame) - { - const gl = this.gl; - - this.current = framebuffer; - - if (framebuffer) - { - // TODO cacheing layer! - - const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID] || this.initFramebuffer(framebuffer); - - gl.bindFramebuffer(gl.FRAMEBUFFER, fbo.framebuffer); - // makesure all textures are unbound.. - - // now check for updates... - if (fbo.dirtyId !== framebuffer.dirtyId) - { - fbo.dirtyId = framebuffer.dirtyId; - - if (fbo.dirtyFormat !== framebuffer.dirtyFormat) - { - fbo.dirtyFormat = framebuffer.dirtyFormat; - this.updateFramebuffer(framebuffer); - } - else if (fbo.dirtySize !== framebuffer.dirtySize) - { - fbo.dirtySize = framebuffer.dirtySize; - this.resizeFramebuffer(framebuffer); - } - } - - for (let i = 0; i < framebuffer.colorTextures.length; i++) - { - if (framebuffer.colorTextures[i].texturePart) - { - this.renderer.texture.unbind(framebuffer.colorTextures[i].texture); - } - else - { - this.renderer.texture.unbind(framebuffer.colorTextures[i]); - } - } - - if (framebuffer.depthTexture) - { - this.renderer.texture.unbind(framebuffer.depthTexture); - } - - if (frame) - { - this.setViewport(frame.x, frame.y, frame.width, frame.height); - } - else - { - this.setViewport(0, 0, framebuffer.width, framebuffer.height); - } - } - else - { - gl.bindFramebuffer(gl.FRAMEBUFFER, null); - - if (frame) - { - this.setViewport(frame.x, frame.y, frame.width, frame.height); - } - else - { - this.setViewport(0, 0, this.renderer.width, this.renderer.height); - } - } - } - - setViewport(x, y, width, height) - { - const v = this.viewport; - - if (v.width !== width || v.height !== height || v.x !== x || v.y !== y) - { - v.x = x; - v.y = y; - v.width = width; - v.height = height; - - this.gl.viewport(x, y, width, height); - } - } - - get size() - { - if (this.current) - { - // TODO store temp - return { x: 0, y: 0, width: this.current.width, height: this.current.height }; - } - - return { x: 0, y: 0, width: this.renderer.width, height: this.renderer.height }; - } - - clear(r, g, b, a) - { - const gl = this.gl; - - // TODO clear color can be set only one right? - gl.clearColor(r, g, b, a); - gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); - } - - // private functions... - - initFramebuffer(framebuffer) - { - const gl = this.gl; - - // TODO - make this a class? - const fbo = { - framebuffer: gl.createFramebuffer(), - stencil: null, - dirtyId: 0, - dirtyFormat: 0, - dirtySize: 0, - }; - - framebuffer.glFrameBuffers[this.CONTEXT_UID] = fbo; - - return fbo; - } - - resizeFramebuffer(framebuffer) - { - const gl = this.gl; - - if (framebuffer.stencil || framebuffer.depth) - { - gl.bindRenderbuffer(gl.RENDERBUFFER, this.stencil); - gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); - } - } - - updateFramebuffer(framebuffer) - { - const gl = this.gl; - - const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID]; - - // bind the color texture - const colorTextures = framebuffer.colorTextures; - - let count = colorTextures.length; - - if (!this.drawBufferExtension) - { - count = Math.min(count, 1); - } - - const activeTextures = []; - - for (let i = 0; i < count; i++) - { - const texture = framebuffer.colorTextures[i]; - - if (texture.texturePart) - { - this.renderer.texture.bind(texture.texture, 0); - - gl.framebufferTexture2D(gl.FRAMEBUFFER, - gl.COLOR_ATTACHMENT0 + i, - gl.TEXTURE_CUBE_MAP_NEGATIVE_X + texture.side, - texture.texture._glTextures[this.CONTEXT_UID].texture, - 0); - } - else - { - this.renderer.texture.bind(texture, 0); - - gl.framebufferTexture2D(gl.FRAMEBUFFER, - gl.COLOR_ATTACHMENT0 + i, - gl.TEXTURE_2D, - texture._glTextures[this.CONTEXT_UID].texture, - 0); - } - - activeTextures.push(gl.COLOR_ATTACHMENT0 + i); - } - - if (this.drawBufferExtension && activeTextures.length > 1) - { - this.drawBufferExtension.drawBuffersWEBGL(activeTextures); - } - - if (framebuffer.depthTexture) - { - const depthTextureExt = this.renderer.context.extensions.depthTexture; - - if (depthTextureExt) - { - const depthTexture = framebuffer.depthTexture; - - this.renderer.texture.bind(depthTexture, 0); - - gl.framebufferTexture2D(gl.FRAMEBUFFER, - gl.DEPTH_ATTACHMENT, - gl.TEXTURE_2D, - depthTexture._glTextures[this.CONTEXT_UID].texture, - 0); - } - } - - if (framebuffer.stencil || framebuffer.depth) - { - fbo.stencil = gl.createRenderbuffer(); - - gl.bindRenderbuffer(gl.RENDERBUFFER, fbo.stencil); - - // TODO.. this is depth AND stencil? - gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, fbo.stencil); - gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); - // fbo.enableStencil(); - } - } -} diff --git a/packages/core/src/renderers/systems/MaskSystem.js b/packages/core/src/renderers/systems/MaskSystem.js deleted file mode 100644 index d9c4b1a..0000000 --- a/packages/core/src/renderers/systems/MaskSystem.js +++ /dev/null @@ -1,199 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import AlphaMaskFilter from '../filters/spriteMask/SpriteMaskFilter'; - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class MaskSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - // TODO - we don't need both! - this.scissor = false; - this.scissorData = null; - this.scissorRenderTarget = null; - - this.enableScissor = false; - - this.alphaMaskPool = []; - this.alphaMaskIndex = 0; - } - - /** - * Applies the Mask and adds it to the current filter stack. - * - * @param {PIXI.DisplayObject} target - Display Object to push the mask to - * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. - */ - push(target, maskData) - { - // TODO the root check means scissor rect will not - // be used on render textures more info here: - // https://github.com/pixijs/pixi.js/pull/3545 - - if (maskData.texture) - { - this.pushSpriteMask(target, maskData); - } - else if (this.enableScissor - && !this.scissor - && this.renderer._activeRenderTarget.root - && !this.renderer.stencil.stencilMaskStack.length - && maskData.isFastRect()) - { - const matrix = maskData.worldTransform; - - let rot = Math.atan2(matrix.b, matrix.a); - - // use the nearest degree! - rot = Math.round(rot * (180 / Math.PI)); - - if (rot % 90) - { - this.pushStencilMask(maskData); - } - else - { - this.pushScissorMask(target, maskData); - } - } - else - { - this.pushStencilMask(maskData); - } - } - - /** - * Removes the last mask from the mask stack and doesn't return it. - * - * @param {PIXI.DisplayObject} target - Display Object to pop the mask from - * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. - */ - pop(target, maskData) - { - if (maskData.texture) - { - this.popSpriteMask(target, maskData); - } - else if (this.enableScissor && !this.renderer.stencil.stencilMaskStack.length) - { - this.popScissorMask(target, maskData); - } - else - { - this.popStencilMask(target, maskData); - } - } - - /** - * Applies the Mask and adds it to the current filter stack. - * - * @param {PIXI.RenderTarget} target - Display Object to push the sprite mask to - * @param {PIXI.Sprite} maskData - Sprite to be used as the mask - */ - pushSpriteMask(target, maskData) - { - let alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; - - if (!alphaMaskFilter) - { - alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; - } - - alphaMaskFilter[0].resolution = this.renderer.resolution; - alphaMaskFilter[0].maskSprite = maskData; - - // TODO - may cause issues! - target.filterArea = maskData.getBounds(true); - - this.renderer.filterSystem.pushFilter(target, alphaMaskFilter); - - this.alphaMaskIndex++; - } - - /** - * Removes the last filter from the filter stack and doesn't return it. - * - */ - popSpriteMask() - { - this.renderer.filterSystem.popFilter(); - this.alphaMaskIndex--; - } - - /** - * Applies the Mask and adds it to the current filter stack. - * - * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. - */ - pushStencilMask(maskData) - { - this.renderer.batch.flush(); - this.renderer.stencil.pushStencil(maskData); - } - - /** - * Removes the last filter from the filter stack and doesn't return it. - * - */ - popStencilMask() - { - // this.renderer.currentRenderer.stop(); - this.renderer.stencil.popStencil(); - } - - /** - * - * @param {PIXI.DisplayObject} target - Display Object to push the mask to - * @param {PIXI.Graphics} maskData - The masking data. - */ - pushScissorMask(target, maskData) - { - maskData.renderable = true; - - const renderTarget = this.renderer._activeRenderTarget; - - const bounds = maskData.getBounds(); - - bounds.fit(renderTarget.size); - maskData.renderable = false; - - this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); - - const resolution = this.renderer.resolution; - - this.renderer.gl.scissor( - bounds.x * resolution, - (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, - bounds.width * resolution, - bounds.height * resolution - ); - - this.scissorRenderTarget = renderTarget; - this.scissorData = maskData; - this.scissor = true; - } - - /** - * - * - */ - popScissorMask() - { - this.scissorRenderTarget = null; - this.scissorData = null; - this.scissor = false; - - // must be scissor! - const gl = this.renderer.gl; - - gl.disable(gl.SCISSOR_TEST); - } -} diff --git a/packages/core/src/renderers/systems/ProjectionSystem.js b/packages/core/src/renderers/systems/ProjectionSystem.js deleted file mode 100644 index ec5efdb..0000000 --- a/packages/core/src/renderers/systems/ProjectionSystem.js +++ /dev/null @@ -1,73 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { Matrix } from '@pixi/math'; - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ - -export default class ProjectionSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this.projectionMatrix = new Matrix(); - } - - update(destinationFrame, sourceFrame, resolution, root) - { - this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; - this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; - - this.calculateProjection(this.destinationFrame, this.sourceFrame, resolution, root); - - this.renderer.globalUniforms.uniforms.projectionMatrix = this.projectionMatrix; - this.renderer.globalUniforms.update(); - } - - /** - * Updates the projection matrix based on a projection frame (which is a rectangle) - * - * @param {Rectangle} destinationFrame - The destination frame. - * @param {Rectangle} sourceFrame - The source frame. - */ - calculateProjection(destinationFrame, sourceFrame, resolution, root) - { - const pm = this.projectionMatrix; - - // I don't think we will need this line.. - // pm.identity(); - - if (!root) - { - pm.a = (1 / destinationFrame.width * 2) * resolution; - pm.d = (1 / destinationFrame.height * 2) * resolution; - - pm.tx = -1 - (sourceFrame.x * pm.a); - pm.ty = -1 - (sourceFrame.y * pm.d); - } - else - { - pm.a = (1 / destinationFrame.width * 2) * resolution; - pm.d = (-1 / destinationFrame.height * 2) * resolution; - - pm.tx = -1 - (sourceFrame.x * pm.a); - pm.ty = 1 - (sourceFrame.y * pm.d); - } - } - - /** - * Sets the transform of the active render target to the given matrix - * - * @param {PIXI.Matrix} matrix - The transformation matrix - */ - setTransform()// matrix) - { - // this._activeRenderTarget.transform = matrix; - } -} diff --git a/packages/core/src/renderers/systems/RenderTextureSystem.js b/packages/core/src/renderers/systems/RenderTextureSystem.js deleted file mode 100644 index a133ef1..0000000 --- a/packages/core/src/renderers/systems/RenderTextureSystem.js +++ /dev/null @@ -1,115 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { Rectangle } from '@pixi/math'; - -const tempRect = new Rectangle(); - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ - -export default class RenderTextureSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this.clearColor = renderer._backgroundColorRgba; - - // TODO moe this property somewhere else! - this.defaultMaskStack = []; - this.defaultFilterStack = [{}]; - - // empty render texture? - this.renderTexture = null; - - this.destinationFrame = new Rectangle(); - } - - bind(renderTexture, sourceFrame, destinationFrame) - { - // TODO - do we want this?? - if (this.renderTexture === renderTexture) return; - this.renderTexture = renderTexture; - - const renderer = this.renderer; - - if (renderTexture) - { - const baseTexture = renderTexture.baseTexture; - - if (!destinationFrame) - { - tempRect.width = baseTexture.realWidth; - tempRect.height = baseTexture.realHeight; - - destinationFrame = tempRect; - } - - if (!sourceFrame) - { - sourceFrame = destinationFrame; - } - - this.renderer.framebuffer.bind(baseTexture.frameBuffer, destinationFrame); - - this.renderer.projection.update(destinationFrame, sourceFrame, baseTexture.resolution, false); - this.renderer.stencil.setMaskStack(baseTexture.stencilMaskStack); - } - else - { - // TODO these validation checks happen deeper down.. - // thing they can be avoided.. - if (!destinationFrame) - { - tempRect.width = renderer.width; - tempRect.height = renderer.height; - - destinationFrame = tempRect; - } - - if (!sourceFrame) - { - sourceFrame = destinationFrame; - } - - renderer.framebuffer.bind(null, destinationFrame); - - // TODO store this.. - this.renderer.projection.update(destinationFrame, sourceFrame, this.renderer.resolution, true); - this.renderer.stencil.setMaskStack(this.defaultMaskStack); - } - - this.destinationFrame.copyFrom(destinationFrame); - } - - /** - * Erases the render texture and fills the drawing area with a colour - * - * @param {number} [clearColor] - The colour - * @return {PIXI.Renderer} Returns itself. - */ - clear(clearColor) - { - if (this.renderTexture) - { - clearColor = clearColor || this.renderTexture.baseTexture.clearColor; - } - else - { - clearColor = clearColor || this.clearColor; - } - - this.renderer.framebuffer.clear(clearColor[0], clearColor[1], clearColor[2], clearColor[3]); - } - - resize()// screenWidth, screenHeight) - { - // resize the root only! - this.bind(null); - } -} diff --git a/packages/core/src/renderers/systems/StateSystem.js b/packages/core/src/renderers/systems/StateSystem.js deleted file mode 100755 index d7f4caf..0000000 --- a/packages/core/src/renderers/systems/StateSystem.js +++ /dev/null @@ -1,288 +0,0 @@ -import mapWebGLBlendModesToPixi from '../utils/mapWebGLBlendModesToPixi'; -import WebGLSystem from './WebGLSystem'; -import WebGLState from '../State'; - -const BLEND = 0; -const OFFSET = 1; -const CULLING = 2; -const DEPTH_TEST = 3; -const WINDING = 4; - -/** - * A WebGL state machines - * - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class StateSystem extends WebGLSystem -{ - /** - * @param {WebGLRenderingContext} gl - The current WebGL rendering context - */ - constructor(renderer) - { - super(renderer); - - this.gl = null; - - this.maxAttribs = null; - - // check we have vao.. - this.nativeVaoExtension = null; - - this.attribState = null; - - this.stateId = 0; - this.polygonOffset = 0; - this.blendMode = 17; - - this.map = []; - - // map functions for when we set state.. - this.map[BLEND] = this.setBlend; - this.map[OFFSET] = this.setOffset; - this.map[CULLING] = this.setCullFace; - this.map[DEPTH_TEST] = this.setDepthTest; - this.map[WINDING] = this.setFrontFace; - - this.checks = []; - - this.defaultState = new WebGLState(); - this.defaultState.blend = true; - this.defaultState.depth = true; - } - - contextChange(gl) - { - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; - - this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); - - // check we have vao.. - this.nativeVaoExtension = ( - gl.getExtension('OES_vertex_array_object') - || gl.getExtension('MOZ_OES_vertex_array_object') - || gl.getExtension('WEBKIT_OES_vertex_array_object') - ); - - this.attribState = { - tempAttribState: new Array(this.maxAttribs), - attribState: new Array(this.maxAttribs), - }; - - this.blendModes = mapWebGLBlendModesToPixi(gl); - - this.setState(this.defaultState); - - this.reset(); - } - - /** - * Sets the current state - * - * @param {*} state - The state to set. - */ - setState(state) - { - state = state || this.defaultState; - - // TODO maybe to an object check? ( this.state === state )? - if (this.stateId !== state.data) - { - let diff = this.stateId ^ state.data; - let i = 0; - - // order from least to most common - while (diff) - { - if (diff & 1) - { - // state change! - this.map[i].call(this, !!(state.data & (1 << i))); - } - - diff = diff >> 1; - i++; - } - - this.stateId = state.data; - } - - // based on the above settings we check for specific modes.. - // for example if blend is active we check and set the blend modes - // or of polygon offset is active we check the poly depth. - for (let i = 0; i < this.checks.length; i++) - { - this.checks[i](this, state); - } - } - - /** - * Enables or disabled blending. - * - * @param {boolean} value - Turn on or off webgl blending. - */ - setBlend(value) - { - this.updateCheck(StateSystem.checkBlendMode, value); - - this.gl[value ? 'enable' : 'disable'](this.gl.BLEND); - } - - /** - * Enables or disable polygon offset fill - * - * @param {boolean} value - Turn on or off webgl polygon offset testing. - */ - setOffset(value) - { - this.gl[value ? 'enable' : 'disable'](this.gl.POLYGON_OFFSET_FILL); - } - - /** - * Sets whether to enable or disable depth test. - * - * @param {boolean} value - Turn on or off webgl depth testing. - */ - setDepthTest(value) - { - this.gl[value ? 'enable' : 'disable'](this.gl.DEPTH_TEST); - } - - /** - * Sets whether to enable or disable cull face. - * - * @param {boolean} value - Turn on or off webgl cull face. - */ - setCullFace(value) - { - this.gl[value ? 'enable' : 'disable'](this.gl.CULL_FACE); - } - - /** - * Sets the gl front face. - * - * @param {boolean} value - true is clockwise and false is counter-clockwise - */ - setFrontFace(value) - { - this.gl.frontFace(this.gl[value ? 'CW' : 'CCW']); - } - - /** - * Sets the blend mode. - * - * @param {number} value - The blend mode to set to. - */ - setBlendMode(value) - { - if (value === this.blendMode) - { - return; - } - - this.blendMode = value; - this.gl.blendFunc(this.blendModes[value][0], this.blendModes[value][1]); - } - - /** - * Sets the polygon offset. - * - * @param {number} value - the polygon offset - * @param {number} scale - the polygon offset scale - */ - setPolygonOffset(value, scale) - { - this.gl.polygonOffset(value, scale); - } - - /** - * Disables all the vaos in use - * - */ - resetAttributes() - { - for (let i = 0; i < this.attribState.tempAttribState.length; i++) - { - this.attribState.tempAttribState[i] = 0; - } - - for (let i = 0; i < this.attribState.attribState.length; i++) - { - this.attribState.attribState[i] = 0; - } - - // im going to assume one is always active for performance reasons. - for (let i = 1; i < this.maxAttribs; i++) - { - this.gl.disableVertexAttribArray(i); - } - } - - // used - /** - * Resets all the logic and disables the vaos - */ - reset() - { - // unbind any VAO if they exist.. - if (this.nativeVaoExtension) - { - this.nativeVaoExtension.bindVertexArrayOES(null); - } - - // reset all attributes.. - this.resetAttributes(); - - this.gl.pixelStorei(this.gl.UNPACK_FLIP_Y_WEBGL, false); - - this.setBlendMode(0); - - // TODO? - // this.setState(this.defaultState); - } - - /** - * checks to see which updates should be checked based on which settings have been activated - * for example if blend is enabled then we shold check the blend modes each time the state is changed - * or if poygon fill is activated then we need to check if the polygone offset changes. - * The idea is that we only check what we have too. - * - * @param {Function} func the checking function to add or remove - * @param {boolean} value should the check function be added or removed. - */ - updateCheck(func, value) - { - const index = this.checks.indexOf(func); - - if (value && index === -1) - { - this.checks.push(func); - } - else if (!value && index !== -1) - { - this.checks.splice(index, 1); - } - } - - /** - * A private little wrapper function that we call to check the blend mode. - * - * @static - * @private - * @param {PIXI.StateSystem} System the System to perform the state check on - * @param {PIXI.State} state the state that the blendMode will pulled from - */ - static checkBlendMode(system, state) - { - system.setBlendMode(state.blendMode); - } - - // TODO - add polygon offset? -} diff --git a/packages/core/src/renderers/systems/StencilSystem.js b/packages/core/src/renderers/systems/StencilSystem.js deleted file mode 100644 index 5d538cb..0000000 --- a/packages/core/src/renderers/systems/StencilSystem.js +++ /dev/null @@ -1,133 +0,0 @@ -import WebGLSystem from './WebGLSystem'; - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class StencilSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - this.stencilMaskStack = []; - } - - /** - * Changes the mask stack that is used by this System. - * - * @param {PIXI.Graphics[]} stencilMaskStack - The mask stack - */ - setMaskStack(stencilMaskStack) - { - const gl = this.renderer.gl; - - if (stencilMaskStack.length !== this.stencilMaskStack.length) - { - if (stencilMaskStack.length === 0) - { - gl.disable(gl.STENCIL_TEST); - } - else - { - gl.enable(gl.STENCIL_TEST); - } - } - - this.stencilMaskStack = stencilMaskStack; - } - - /** - * Applies the Mask and adds it to the current stencil stack. @alvin - * - * @param {PIXI.Graphics} graphics - The mask - */ - pushStencil(graphics) - { - this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); - - // this.renderer._activeRenderTarget.attachStencilBuffer(); - - const gl = this.renderer.gl; - const prevMaskCount = this.stencilMaskStack.length; - - if (prevMaskCount === 0) - { - gl.enable(gl.STENCIL_TEST); - } - - this.stencilMaskStack.push(graphics); - - // Increment the refference stencil value where the new mask overlaps with the old ones. - gl.colorMask(false, false, false, false); - gl.stencilFunc(gl.EQUAL, prevMaskCount, this._getBitwiseMask()); - gl.stencilOp(gl.KEEP, gl.KEEP, gl.INCR); - this.renderer.plugins.graphics.render(graphics); - - this._useCurrent(); - } - - /** - * Removes the last mask from the stencil stack. @alvin - */ - popStencil() - { - this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); - - const gl = this.renderer.gl; - const graphics = this.stencilMaskStack.pop(); - - if (this.stencilMaskStack.length === 0) - { - // the stack is empty! - gl.disable(gl.STENCIL_TEST); - gl.clear(gl.STENCIL_BUFFER_BIT); - gl.clearStencil(0); - } - else - { - // Decrement the refference stencil value where the popped mask overlaps with the other ones - gl.colorMask(false, false, false, false); - gl.stencilOp(gl.KEEP, gl.KEEP, gl.DECR); - this.renderer.plugins.graphics.render(graphics); - - this._useCurrent(); - } - } - - /** - * Setup renderer to use the current stencil data. - */ - _useCurrent() - { - const gl = this.renderer.gl; - - gl.colorMask(true, true, true, true); - gl.stencilFunc(gl.EQUAL, this.stencilMaskStack.length, this._getBitwiseMask()); - gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP); - } - - /** - * Fill 1s equal to the number of acitve stencil masks. - * - * @return {number} The bitwise mask. - */ - _getBitwiseMask() - { - return (1 << this.stencilMaskStack.length) - 1; - } - - /** - * Destroys the mask stack. - * - */ - destroy() - { - super.destroy(this); - - this.stencilMaskStack = null; - } -} diff --git a/packages/core/src/renderers/systems/WebGLSystem.js b/packages/core/src/renderers/systems/WebGLSystem.js deleted file mode 100644 index d3d5b31..0000000 --- a/packages/core/src/renderers/systems/WebGLSystem.js +++ /dev/null @@ -1,41 +0,0 @@ -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this manager works for. - */ - constructor(renderer) - { - /** - * The renderer this manager works for. - * - * @member {PIXI.Renderer} - */ - this.renderer = renderer; - - this.renderer.runners.contextChange.add(this); - } - - /** - * Generic method called when there is a WebGL context change. - * - */ - contextChange() - { - // do some codes init! - } - - /** - * Generic destroy methods to be overridden by the subclass - * - */ - destroy() - { - this.renderer.runners.contextChange.remove(this); - this.renderer = null; - } -} diff --git a/packages/core/src/renderers/systems/filter/FilterSystem.js b/packages/core/src/renderers/systems/filter/FilterSystem.js deleted file mode 100644 index e615037..0000000 --- a/packages/core/src/renderers/systems/filter/FilterSystem.js +++ /dev/null @@ -1,431 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; - -import RenderTexture from '../../../textures/RenderTexture'; -import Quad from '../../utils/Quad'; -import QuadUv from '../../utils/QuadUv'; -import { Rectangle } from '@pixi/math'; -import * as filterTransforms from '../../filters/filterTransforms'; -import bitTwiddle from 'bit-twiddle'; -import UniformGroup from '../../../shader/UniformGroup'; -import { DRAW_MODES } from '../../../../../constants'; - -// -/** - * @ignore - * @class - */ -class FilterState -{ - /** - * - */ - constructor() - { - this.renderTexture = null; - this.sourceFrame = new Rectangle(); - this.destinationFrame = new Rectangle(); - this.filters = []; - this.target = null; - this.legacy = false; - this.resolution = 1; - } -} - -/** - * @class - * @memberof PIXI.systems - * @extends PIXI.systems.WebGLSystem - */ -export default class FilterSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - /** - * stores a bunch of PO2 textures used for filtering - * @type {Object} - */ - this.texturePool = {}; - - /** - * a pool for storing filter states, save us creating new ones each tick - * @type {Array} - */ - this.statePool = []; - - /** - * A very simple geometry used when drawing a filter effect to the screen - * @type {Quad} - */ - this.quad = new Quad(); - - this.quadUv = new QuadUv(); - - /** - * Temporary rect for maths - * @type {PIXI.Rectangle} - */ - this.tempRect = new Rectangle(); - - this.activeState = {}; - - /** - * this uniform group is attached to filter uniforms when used - * @type {UniformGroup} - */ - this.globalUniforms = new UniformGroup({ - sourceFrame: this.tempRect, - destinationFrame: this.tempRect, - - // legacy variables - filterArea: new Float32Array(4), - filterClamp: new Float32Array(4), - }, true); - } - - /** - * Adds a new filter to the System. - * - * @param {PIXI.DisplayObject} target - The target of the filter to render. - * @param {PIXI.Filter[]} filters - The filters to apply. - */ - push(target, filters) - { - const renderer = this.renderer; - const filterStack = this.renderer.renderTexture.defaultFilterStack; - const state = this.statePool.pop() || new FilterState(); - - let resolution = filters[0].resolution; - let padding = filters[0].padding; - let autoFit = filters[0].autoFit; - let legacy = filters[0].legacy; - - for (let i = 1; i < filters.length; i++) - { - const filter = filters[i]; - - // lets use the lowest resolution.. - resolution = Math.min(resolution, filter.resolution); - // and the largest amount of padding! - padding = Math.max(padding, filter.padding); - // only auto fit if all filters are autofit - autoFit = autoFit || filter.autoFit; - - legacy = legacy || filter.legacy; - } - - filterStack.push(state); - - state.resolution = resolution; - - state.legacy = legacy; - - // round to whole number based on resolution - // TODO move that to the shader too? - state.sourceFrame = target.filterArea || target.getBounds(true); - - state.sourceFrame.pad(padding); - - if (autoFit) - { - state.sourceFrame.fit(this.renderer.renderTexture.destinationFrame); - } - - state.sourceFrame.round(resolution); - - state.renderTexture = this.getPotFilterTexture(state.sourceFrame.width, state.sourceFrame.height, resolution); - state.filters = filters; - - state.destinationFrame.width = state.renderTexture.width; - state.destinationFrame.height = state.renderTexture.height; - - state.renderTexture.filterFrame = state.sourceFrame; - renderer.renderTexture.bind(state.renderTexture, state.sourceFrame);// /, state.destinationFrame); - renderer.renderTexture.clear(); - } - - /** - * Pops off the filter and applies it. - * - */ - pop() - { - const renderer = this.renderer; - const filterStack = renderer.renderTexture.defaultFilterStack; - const state = filterStack.pop(); - const filters = state.filters; - - this.activeState = state; - - const globalUniforms = this.globalUniforms.uniforms; - - globalUniforms.sourceFrame = state.sourceFrame; - globalUniforms.destinationFrame = state.destinationFrame; - globalUniforms.resolution = state.resolution; - - // only update the rect if its legacy.. - if (state.legacy) - { - const filterArea = globalUniforms.filterArea; - const filterClamp = globalUniforms.filterClamp; - - filterArea[0] = state.destinationFrame.width; - filterArea[1] = state.destinationFrame.height; - filterArea[2] = state.sourceFrame.x; - filterArea[3] = state.sourceFrame.y; - - filterClamp[0] = 0.5 / state.resolution / state.destinationFrame.width; - filterClamp[1] = 0.5 / state.resolution / state.destinationFrame.height; - filterClamp[2] = (state.sourceFrame.width - 0.5) / state.resolution / state.destinationFrame.width; - filterClamp[3] = (state.sourceFrame.height - 0.5) / state.resolution / state.destinationFrame.height; - } - - this.globalUniforms.update(); - - const lastState = filterStack[filterStack.length - 1]; - - if (filters.length === 1) - { - filters[0].apply(this, state.renderTexture, lastState.renderTexture, false, state); - - this.returnFilterTexture(state.renderTexture); - } - else - { - let flip = state.renderTexture; - let flop = this.getPotFilterTexture( - flip.width, - flip.height, - state.resolution - ); - - flop.filterFrame = flip.filterFrame; - - let i = 0; - - for (i = 0; i < filters.length - 1; ++i) - { - filters[i].apply(this, flip, flop, true, state); - - const t = flip; - - flip = flop; - flop = t; - } - - filters[i].apply(this, flip, lastState.renderTexture, false, state); - - this.returnFilterTexture(flip); - this.returnFilterTexture(flop); - } - - this.statePool.push(state); - } - - /** - * Draws a filter. - * - * @param {PIXI.Filter} filter - The filter to draw. - * @param {PIXI.RenderTarget} input - The input render target. - * @param {PIXI.RenderTarget} output - The target to output to. - * @param {boolean} clear - Should the output be cleared before rendering to it - */ - applyFilter(filter, input, output, clear) - { - const renderer = this.renderer; - - renderer.renderTexture.bind(output, output ? output.filterFrame : null); - - if (clear) - { - // gl.disable(gl.SCISSOR_TEST); - renderer.renderTexture.clear(); - // gl.enable(gl.SCISSOR_TEST); - } - - // set the uniforms.. - filter.uniforms.uSampler = input; - filter.uniforms.filterGlobals = this.globalUniforms; - - // TODO make it so that the order of this does not matter.. - // because it does at the moment cos of global uniforms. - // they need to get resynced - - renderer.state.setState(filter.state); - renderer.shader.bind(filter); - - if (filter.legacy) - { - this.quadUv.map(input._frame, input.filterFrame); - - renderer.geometry.bind(this.quadUv); - renderer.geometry.draw(DRAW_MODES.TRIANGLES); - } - else - { - renderer.geometry.bind(this.quad); - renderer.geometry.draw(DRAW_MODES.TRIANGLE_STRIP); - } - } - - /** - * Calculates the mapped matrix. - * - * TODO playing around here.. this is temporary - (will end up in the shader) - * this returns a matrix that will normalise map filter cords in the filter to screen space - * - * @param {PIXI.Matrix} outputMatrix - the matrix to output to. - * @return {PIXI.Matrix} The mapped matrix. - */ - calculateScreenSpaceMatrix(outputMatrix) - { - const currentState = this.activeState; - - return filterTransforms.calculateScreenSpaceMatrix( - outputMatrix, - currentState.sourceFrame, - currentState.destinationFrame - ); - } - - /** - * This will map the filter coord so that a texture can be used based on the transform of a sprite - * - * @param {PIXI.Matrix} outputMatrix - The matrix to output to. - * @param {PIXI.Sprite} sprite - The sprite to map to. - * @return {PIXI.Matrix} The mapped matrix. - */ - calculateSpriteMatrix(outputMatrix, sprite) - { - const currentState = this.activeState; - - return filterTransforms.calculateSpriteMatrix( - outputMatrix, - currentState.sourceFrame, - currentState.destinationFrame, - sprite - ); - } - - /** - * Destroys this Filter System. - * - * @param {boolean} [contextLost=false] context was lost, do not free shaders - * - */ - destroy(contextLost = false) - { - if (!contextLost) - { - this.emptyPool(); - } - else - { - this.texturePool = {}; - } - } - - /** - * Gets a Power-of-Two render texture. - * - * TODO move to a seperate class could be on renderer? - * also - could cause issue with multiple contexts? - * - * @private - * @param {WebGLRenderingContext} gl - The webgl rendering context - * @param {number} minWidth - The minimum width of the render target. - * @param {number} minHeight - The minimum height of the render target. - * @param {number} resolution - The resolution of the render target. - * @return {PIXI.RenderTarget} The new render target. - */ - getPotFilterTexture(minWidth, minHeight, resolution) - { - minWidth = bitTwiddle.nextPow2(minWidth); - minHeight = bitTwiddle.nextPow2(minHeight); - resolution = resolution || 1; - - const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); - - if (!this.texturePool[key]) - { - this.texturePool[key] = []; - } - - let renderTexture = this.texturePool[key].pop(); - - if (!renderTexture) - { - // temporary bypass cache.. - // internally - this will cause a texture to be bound.. - renderTexture = RenderTexture.create({ - width: minWidth, - height: minHeight, - resolution, - }); - } - - return renderTexture; - } - - /** - * Gets extra render texture to use inside current filter - * - * @param {number} resolution resolution of the renderTexture - * @returns {PIXI.RenderTexture} - */ - getFilterTexture(resolution) - { - const rt = this.activeState.renderTexture; - - const filterTexture = this.getPotFilterTexture(rt.width, rt.height, resolution || rt.baseTexture.resolution); - - filterTexture.filterFrame = rt.filterFrame; - - return filterTexture; - } - - /** - * Frees a render texture back into the pool. - * - * @param {PIXI.RenderTarget} renderTexture - The renderTarget to free - */ - returnFilterTexture(renderTexture) - { - renderTexture.filterFrame = null; - - const base = renderTexture.baseTexture; - - const minWidth = base.width; - const minHeight = base.height; - - const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); - - this.texturePool[key].push(renderTexture); - } - - /** - * Empties the texture pool. - * - */ - emptyPool() - { - for (const i in this.texturePool) - { - const textures = this.texturePool[i]; - - if (textures) - { - for (let j = 0; j < textures.length; j++) - { - textures[j].destroy(true); - } - } - } - - this.texturePool = {}; - } -} diff --git a/packages/core/src/renderers/systems/geometry/GLBuffer.js b/packages/core/src/renderers/systems/geometry/GLBuffer.js deleted file mode 100644 index 0477e8a..0000000 --- a/packages/core/src/renderers/systems/geometry/GLBuffer.js +++ /dev/null @@ -1,9 +0,0 @@ -export default class GLBuffer -{ - constructor(buffer) - { - this.buffer = buffer; - this.updateID = -1; - this.byteLength = -1; - } -} diff --git a/packages/core/src/renderers/systems/geometry/GeometrySystem.js b/packages/core/src/renderers/systems/geometry/GeometrySystem.js deleted file mode 100644 index 26c8e9e..0000000 --- a/packages/core/src/renderers/systems/geometry/GeometrySystem.js +++ /dev/null @@ -1,388 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; -import GLBuffer from './GLBuffer'; - -const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class GeometrySystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this._activeGeometry = null; - this._activeVao = null; - - this.hasVao = true; - this.hasInstance = true; - } - - /** - * Sets up the renderer context and necessary buffers. - * - * @private - */ - contextChange() - { - const gl = this.gl = this.renderer.gl; - - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - - // webgl2 - if (!gl.createVertexArray) - { - // webgl 1! - let nativeVaoExtension = this.renderer.context.extensions.vertexArrayObject; - - if (this.renderer.legacy) - { - nativeVaoExtension = null; - } - - if (nativeVaoExtension) - { - gl.createVertexArray = () => - nativeVaoExtension.createVertexArrayOES(); - - gl.bindVertexArray = (vao) => - nativeVaoExtension.bindVertexArrayOES(vao); - - gl.deleteVertexArray = (vao) => - nativeVaoExtension.deleteVertexArrayOES(vao); - } - else - { - this.hasVao = false; - gl.createVertexArray = () => - { - // empty - }; - - gl.bindVertexArray = () => - { - // empty - }; - - gl.deleteVertexArray = () => - { - // empty - }; - } - } - - if (!gl.vertexAttribDivisor) - { - const instanceExt = gl.getExtension('ANGLE_instanced_arrays'); - - if (instanceExt) - { - gl.vertexAttribDivisor = (a, b) => - instanceExt.vertexAttribDivisorANGLE(a, b); - - gl.drawElementsInstanced = (a, b, c, d, e) => - instanceExt.drawElementsInstancedANGLE(a, b, c, d, e); - - gl.drawArraysInstanced = (a, b, c, d) => - instanceExt.drawArraysInstancedANGLE(a, b, c, d); - } - else - { - this.hasInstance = false; - } - } - } - - /** - * Binds geometry so that is can be drawn. Creating a Vao if required - * @private - * @param {PIXI.Geometry} geometry instance of geometry to bind - */ - bind(geometry, shader) - { - shader = shader || this.renderer.shader.shader; - - const gl = this.gl; - - // not sure the best way to address this.. - // currently different shaders require different VAOs for the same geometry - // Still mulling over the best way to solve this one.. - // will likely need to modify the shader attribute locations at run time! - let vaos = geometry.glVertexArrayObjects[this.CONTEXT_UID]; - - if (!vaos) - { - geometry.glVertexArrayObjects[this.CONTEXT_UID] = vaos = {}; - } - - const vao = vaos[shader.program.id] || this.initGeometryVao(geometry, shader.program); - - this._activeGeometry = geometry; - - if (this._activeVao !== vao) - { - this._activeVao = vao; - - if (this.hasVao) - { - gl.bindVertexArray(vao); - } - else - { - this.activateVao(geometry, shader.program); - } - } - - // TODO - optimise later! - // don't need to loop through if nothing changed! - // maybe look to add an 'autoupdate' to geometry? - this.updateBuffers(); - } - - reset() - { - this.unbind(); - } - - updateBuffers() - { - const geometry = this._activeGeometry; - const gl = this.gl; - - for (let i = 0; i < geometry.buffers.length; i++) - { - const buffer = geometry.buffers[i]; - - const glBuffer = buffer._glBuffers[this.CONTEXT_UID]; - - if (buffer._updateID !== glBuffer.updateID) - { - glBuffer.updateID = buffer._updateID; - - // TODO can cache this on buffer! maybe added a getter / setter? - const type = buffer.index ? gl.ELEMENT_ARRAY_BUFFER : gl.ARRAY_BUFFER; - const drawType = buffer.static ? gl.STATIC_DRAW : gl.DYNAMIC_DRAW; - - gl.bindBuffer(type, glBuffer.buffer); - - if (glBuffer.byteLength >= buffer.data.byteLength) - { - // offset is always zero for now! - gl.bufferSubData(type, 0, buffer.data); - } - else - { - gl.bufferData(type, buffer.data, drawType); - } - } - } - } - - checkCompatability(geometry, program) - { - // geometry must have at least all the attributes that the shader requires. - const geometryAttributes = geometry.attributes; - const shaderAttributes = program.attributeData; - - for (const j in shaderAttributes) - { - if (!geometryAttributes[j]) - { - throw new Error(`shader and geometry incompatible, geometry missing the "${j}" attribute`); - } - } - } - - /** - * Creates a Vao with the same structure as the geometry and stores it on the geometry. - * @private - * @param {PIXI.Geometry} geometry instance of geometry to to generate Vao for - */ - initGeometryVao(geometry, program) - { - this.checkCompatability(geometry, program); - - const gl = this.gl; - const CONTEXT_UID = this.CONTEXT_UID; - const buffers = geometry.buffers; - const attributes = geometry.attributes; - - const tempStride = {}; - const tempStart = {}; - - for (const j in buffers) - { - tempStride[j] = 0; - tempStart[j] = 0; - } - - for (const j in attributes) - { - if (!attributes[j].size && program.attributeData[j]) - { - attributes[j].size = program.attributeData[j].size; - } - - tempStride[attributes[j].buffer] += attributes[j].size * byteSizeMap[attributes[j].type]; - } - - for (const j in attributes) - { - const attribute = attributes[j]; - const attribSize = attribute.size; - - if (attribute.stride === undefined) - { - if (tempStride[attribute.buffer] === attribSize * byteSizeMap[attribute.type]) - { - attribute.stride = 0; - } - else - { - attribute.stride = tempStride[attribute.buffer]; - } - } - - if (attribute.start === undefined) - { - attribute.start = tempStart[attribute.buffer]; - - tempStart[attribute.buffer] += attribSize * byteSizeMap[attribute.type]; - } - } - - // first update - and create the buffers! - // only create a gl buffer if it actually gets - for (let i = 0; i < buffers.length; i++) - { - const buffer = buffers[i]; - - if (!buffer._glBuffers[CONTEXT_UID]) - { - buffer._glBuffers[CONTEXT_UID] = new GLBuffer(gl.createBuffer()); - } - } - - // TODO - maybe make this a data object? - // lets wait to see if we need to first! - const vao = gl.createVertexArray(); - - gl.bindVertexArray(vao); - - this.activateVao(geometry, program); - - gl.bindVertexArray(null); - - // add it to the cache! - geometry.glVertexArrayObjects[this.CONTEXT_UID][program.id] = vao; - - return vao; - } - - activateVao(geometry, program) - { - const gl = this.gl; - const CONTEXT_UID = this.CONTEXT_UID; - const buffers = geometry.buffers; - const attributes = geometry.attributes; - - if (geometry.indexBuffer) - { - // first update the index buffer if we have one.. - gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, geometry.indexBuffer._glBuffers[CONTEXT_UID].buffer); - } - - let lastBuffer = null; - - // add a new one! - for (const j in attributes) - { - const attribute = attributes[j]; - const buffer = buffers[attribute.buffer]; - const glBuffer = buffer._glBuffers[CONTEXT_UID]; - - if (program.attributeData[j]) - { - if (lastBuffer !== glBuffer) - { - gl.bindBuffer(gl.ARRAY_BUFFER, glBuffer.buffer); - - lastBuffer = glBuffer; - } - - const location = program.attributeData[j].location; - - // TODO introduce state again - // we can optimise this for older devices that have no VAOs - gl.enableVertexAttribArray(location); - - gl.vertexAttribPointer(location, - attribute.size, - attribute.type || gl.FLOAT, - attribute.normalized, - attribute.stride, - attribute.start); - - if (attribute.instance) - { - // TODO calculate instance count based of this... - if (this.hasInstance) - { - gl.vertexAttribDivisor(location, 1); - } - else - { - throw new Error('geometry error, GPU Instancing is not supported on this device'); - } - } - } - } - } - - draw(type, size, start, instanceCount) - { - const gl = this.gl; - const geometry = this._activeGeometry; - - // TODO.. this should not change so maybe cache the function? - - if (geometry.indexBuffer) - { - if (geometry.instanced) - { - /* eslint-disable max-len */ - gl.drawElementsInstanced(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2, instanceCount || 1); - /* eslint-enable max-len */ - } - else - { - gl.drawElements(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2); - } - } - else - if (geometry.instanced) - { - // TODO need a better way to calculate size.. - gl.drawArraysInstanced(type, start, size || geometry.getSize(), instanceCount || 1); - } - else - { - gl.drawArrays(type, start, size || geometry.getSize()); - } - - return this; - } - - unbind() - { - this.gl.bindVertexArray(null); - this._activeVao = null; - this._activeGeometry = null; - } -} diff --git a/packages/core/src/renderers/systems/geometry/setVertexAttribArrays.js b/packages/core/src/renderers/systems/geometry/setVertexAttribArrays.js deleted file mode 100644 index 23b6959..0000000 --- a/packages/core/src/renderers/systems/geometry/setVertexAttribArrays.js +++ /dev/null @@ -1,54 +0,0 @@ -// var GL_MAP = {}; - -/** - * @param gl {WebGLRenderingContext} The current WebGL context - * @param attribs {*} - * @param state {*} - */ -export default function setVertexAttribArrays(gl, attribs, state) -{ - let i; - - if (state) - { - const tempAttribState = state.tempAttribState; - const attribState = state.attribState; - - for (i = 0; i < tempAttribState.length; i++) - { - tempAttribState[i] = false; - } - - // set the new attribs - for (i = 0; i < attribs.length; i++) - { - tempAttribState[attribs[i].attribute.location] = true; - } - - for (i = 0; i < attribState.length; i++) - { - if (attribState[i] !== tempAttribState[i]) - { - attribState[i] = tempAttribState[i]; - - if (state.attribState[i]) - { - gl.enableVertexAttribArray(i); - } - else - { - gl.disableVertexAttribArray(i); - } - } - } - } - else - { - for (i = 0; i < attribs.length; i++) - { - const attrib = attribs[i]; - - gl.enableVertexAttribArray(attrib.attribute.location); - } - } -} diff --git a/packages/core/src/renderers/systems/index.js b/packages/core/src/renderers/systems/index.js deleted file mode 100644 index c843f03..0000000 --- a/packages/core/src/renderers/systems/index.js +++ /dev/null @@ -1,6 +0,0 @@ -/** - * Systems are individual components to the Renderer pipeline. - * @namespace PIXI.systems - */ -export { default as WebGLSystem } from './WebGLSystem'; -export { default as FilterSystem } from './filter/FilterSystem'; diff --git a/packages/core/src/renderers/systems/shader/GLProgram.js b/packages/core/src/renderers/systems/shader/GLProgram.js deleted file mode 100644 index 92980ca..0000000 --- a/packages/core/src/renderers/systems/shader/GLProgram.js +++ /dev/null @@ -1,43 +0,0 @@ -/** - * Helper class to create a webGL Program - * - * @class - * @private - */ -export default class GLProgram -{ - constructor(program, uniformData) - { - /** - * The shader program - * - * @member {WebGLProgram} - */ - this.program = program; - - /** - * holds the uniform data which contains uniform locations - * and current uniform values used for caching and preventing undeed GPU commands - * @type {Object} - */ - this.uniformData = uniformData; - - /** - * uniformGroups holds the various upload functions for the shader. Each uniform group - * and program have a unique upload function generated. - * @type {Object} - */ - this.uniformGroups = {}; - } - - /** - * Destroys this program - * TODO - */ - destroy() - { - this.uniformData = null; - this.uniformGroups = null; - this.program = null; - } -} diff --git a/packages/core/src/renderers/systems/shader/ShaderSystem.js b/packages/core/src/renderers/systems/shader/ShaderSystem.js deleted file mode 100644 index 6cf57b6..0000000 --- a/packages/core/src/renderers/systems/shader/ShaderSystem.js +++ /dev/null @@ -1,170 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; -import GLProgram from './GLProgram'; -import generateUniformsSync from '../../../shader/generateUniformsSync'; -import defaultValue from '../../../shader/defaultValue'; -import compileProgram from './shader/compileProgram'; - -let UID = 0; - -/** - * Helper class to create a webGL Texture - * - * @class - * @memberof PIXI - */ -export default class ShaderSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - A reference to the current renderer - */ - constructor(renderer) - { - super(renderer); - - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = null; - - this.shader = null; - this.program = null; - - this.id = UID++; - } - - contextChange(gl) - { - this.gl = gl; - } - - /** - * Changes the current shader to the one given in parameter - * - * @param {PIXI.Shader} shader - the new shader - * @param {boolean} dontSync - false if the shader should automatically sync its uniforms. - * @returns {PIXI.glCore.glProgram} the glProgram that belongs to the shader. - */ - bind(shader, dontSync) - { - shader.uniforms.globals = this.renderer.globalUniforms; - - const program = shader.program; - const glProgram = program.glPrograms[this.renderer.CONTEXT_UID] || this.generateShader(shader); - - this.shader = shader; - - // TODO - some current pixi plugins bypass this.. so it not safe to use yet.. - if (this.program !== program) - { - this.program = program; - this.gl.useProgram(glProgram.program); - } - - if (!dontSync) - { - this.syncUniformGroup(shader.uniformGroup); - } - - return glProgram; - } - - /** - * Uploads the uniforms values to the currently bound shader. - * - * @param {object} uniforms - the uniforms valiues that be applied to the current shader - */ - setUniforms(uniforms) - { - const shader = this.shader.program; - const glProgram = shader.glPrograms[this.renderer.CONTEXT_UID]; - - shader.syncUniforms(glProgram.uniformData, uniforms, this.renderer); - } - - syncUniformGroup(group) - { - const glProgram = this.getglProgram(); - - if (!group.static || group.dirtyId !== glProgram.uniformGroups[group.id]) - { - glProgram.uniformGroups[group.id] = group.dirtyId; - const syncFunc = group.syncUniforms[this.shader.program.id] || this.createSyncGroups(group); - - syncFunc(glProgram.uniformData, group.uniforms, this.renderer); - } - } - - createSyncGroups(group) - { - group.syncUniforms[this.shader.program.id] = generateUniformsSync(group, this.shader.program.uniformData); - - return group.syncUniforms[this.shader.program.id]; - } - - /** - * Returns the underlying GLShade rof the currently bound shader. - * This can be handy for when you to have a little more control over the setting of your uniforms. - * - * @return {PIXI.glCore.Shader} the glProgram for the currently bound Shader for this context - */ - getglProgram() - { - if (this.shader) - { - return this.shader.program.glPrograms[this.renderer.CONTEXT_UID]; - } - - return null; - } - - /** - * Generates a glProgram verion of the Shader provided. - * - * @private - * @param {PIXI.Shader} shader the shader that the glProgram will be based on. - * @return {PIXI.glCore.glProgram} A shiney new glProgram - */ - generateShader(shader) - { - const gl = this.gl; - - const program = shader.program; - - const attribMap = {}; - - for (const i in program.attributeData) - { - attribMap[i] = program.attributeData[i].location; - } - - const shaderProgram = compileProgram(gl, program.vertexSrc, program.fragmentSrc, attribMap); - const uniformData = {}; - - for (const i in program.uniformData) - { - const data = program.uniformData[i]; - - uniformData[i] = { - location: gl.getUniformLocation(shaderProgram, i), - value: defaultValue(data.type, data.size), - }; - } - - const glProgram = new GLProgram(shaderProgram, uniformData); - - program.glPrograms[this.renderer.CONTEXT_UID] = glProgram; - - return glProgram; - } - - /** - * Destroys this System and removes all its textures - */ - destroy() - { - // TODO implement destroy method for ShaderSystem - this.destroyed = true; - } -} diff --git a/packages/core/src/renderers/systems/shader/shader/compileProgram.js b/packages/core/src/renderers/systems/shader/shader/compileProgram.js deleted file mode 100644 index 8c6ccb9..0000000 --- a/packages/core/src/renderers/systems/shader/shader/compileProgram.js +++ /dev/null @@ -1,78 +0,0 @@ -/** - * @method compileProgram - * @memberof PIXI.glCore.shader - * @param gl {WebGLRenderingContext} The current WebGL context {WebGLProgram} - * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. - * @param fragmentSrc {string|string[]} The fragment shader source as an array of strings. - * @param attributeLocations {Object} An attribute location map that lets you manually set the attribute locations - * @return {WebGLProgram} the shader program - */ -export default function compileProgram(gl, vertexSrc, fragmentSrc, attributeLocations) -{ - const glVertShader = compileShader(gl, gl.VERTEX_SHADER, vertexSrc); - const glFragShader = compileShader(gl, gl.FRAGMENT_SHADER, fragmentSrc); - - let program = gl.createProgram(); - - gl.attachShader(program, glVertShader); - gl.attachShader(program, glFragShader); - - // optionally, set the attributes manually for the program rather than letting WebGL decide.. - if (attributeLocations) - { - for (const i in attributeLocations) - { - gl.bindAttribLocation(program, attributeLocations[i], i); - } - } - - gl.linkProgram(program); - - // if linking fails, then log and cleanup - if (!gl.getProgramParameter(program, gl.LINK_STATUS)) - { - console.error('Pixi.js Error: Could not initialize shader.'); - console.error('gl.VALIDATE_STATUS', gl.getProgramParameter(program, gl.VALIDATE_STATUS)); - console.error('gl.getError()', gl.getError()); - - // if there is a program info log, log it - if (gl.getProgramInfoLog(program) !== '') - { - console.warn('Pixi.js Warning: gl.getProgramInfoLog()', gl.getProgramInfoLog(program)); - } - - gl.deleteProgram(program); - program = null; - } - - // clean up some shaders - gl.deleteShader(glVertShader); - gl.deleteShader(glFragShader); - - return program; -} - -/** - * @private - * @param gl {WebGLRenderingContext} The current WebGL context {WebGLProgram} - * @param type {Number} the type, can be either VERTEX_SHADER or FRAGMENT_SHADER - * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. - * @return {WebGLShader} the shader - */ -function compileShader(gl, type, src) -{ - const shader = gl.createShader(type); - - gl.shaderSource(shader, src); - gl.compileShader(shader); - - if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) - { - console.warn(src); - console.error(gl.getShaderInfoLog(shader)); - - return null; - } - - return shader; -} diff --git a/packages/core/src/renderers/systems/shader/shader/defaultValue.js b/packages/core/src/renderers/systems/shader/shader/defaultValue.js deleted file mode 100644 index 97290bd..0000000 --- a/packages/core/src/renderers/systems/shader/shader/defaultValue.js +++ /dev/null @@ -1,79 +0,0 @@ -/** - * @method defaultValue - * @memberof PIXI.glCore.shader - * @param type {String} Type of value - * @param size {Number} - */ -export default function defaultValue(type, size) -{ - switch (type) - { - case 'float': - return 0; - - case 'vec2': - return new Float32Array(2 * size); - - case 'vec3': - return new Float32Array(3 * size); - - case 'vec4': - return new Float32Array(4 * size); - - case 'int': - case 'sampler2D': - case 'sampler2DArray': - return 0; - - case 'ivec2': - return new Int32Array(2 * size); - - case 'ivec3': - return new Int32Array(3 * size); - - case 'ivec4': - return new Int32Array(4 * size); - - case 'bool': - return false; - - case 'bvec2': - - return booleanArray(2 * size); - - case 'bvec3': - return booleanArray(3 * size); - - case 'bvec4': - return booleanArray(4 * size); - - case 'mat2': - return new Float32Array([1, 0, - 0, 1]); - - case 'mat3': - return new Float32Array([1, 0, 0, - 0, 1, 0, - 0, 0, 1]); - - case 'mat4': - return new Float32Array([1, 0, 0, 0, - 0, 1, 0, 0, - 0, 0, 1, 0, - 0, 0, 0, 1]); - } - - return null; -} - -function booleanArray(size) -{ - const array = new Array(size); - - for (let i = 0; i < array.length; i++) - { - array[i] = false; - } - - return array; -} diff --git a/packages/core/src/renderers/systems/shader/shader/index.js b/packages/core/src/renderers/systems/shader/shader/index.js deleted file mode 100644 index ba7a317..0000000 --- a/packages/core/src/renderers/systems/shader/shader/index.js +++ /dev/null @@ -1,8 +0,0 @@ -/** - * @namespace PIXI.glCore.shader - */ -export { default as compileProgram } from './compileProgram'; -export { default as defaultValue } from './defaultValue'; -export { default as setPrecision } from './setPrecision'; -export { default as mapSize } from './mapSize'; -export { default as mapType } from './mapType'; diff --git a/packages/core/src/renderers/systems/shader/shader/mapSize.js b/packages/core/src/renderers/systems/shader/shader/mapSize.js deleted file mode 100644 index b681554..0000000 --- a/packages/core/src/renderers/systems/shader/shader/mapSize.js +++ /dev/null @@ -1,33 +0,0 @@ -const GLSL_TO_SIZE = { - float: 1, - vec2: 2, - vec3: 3, - vec4: 4, - - int: 1, - ivec2: 2, - ivec3: 3, - ivec4: 4, - - bool: 1, - bvec2: 2, - bvec3: 3, - bvec4: 4, - - mat2: 4, - mat3: 9, - mat4: 16, - - sampler2D: 1, -}; - -/** - * @method mapSize - * @memberof PIXI.glCore.shader - * @param type {String} - * @return {Number} - */ -export default function mapSize(type) -{ - return GLSL_TO_SIZE[type]; -} diff --git a/packages/core/src/renderers/systems/shader/shader/mapType.js b/packages/core/src/renderers/systems/shader/shader/mapType.js deleted file mode 100644 index d288b7c..0000000 --- a/packages/core/src/renderers/systems/shader/shader/mapType.js +++ /dev/null @@ -1,45 +0,0 @@ -let GL_TABLE = null; - -const GL_TO_GLSL_TYPES = { - FLOAT: 'float', - FLOAT_VEC2: 'vec2', - FLOAT_VEC3: 'vec3', - FLOAT_VEC4: 'vec4', - - INT: 'int', - INT_VEC2: 'ivec2', - INT_VEC3: 'ivec3', - INT_VEC4: 'ivec4', - - BOOL: 'bool', - BOOL_VEC2: 'bvec2', - BOOL_VEC3: 'bvec3', - BOOL_VEC4: 'bvec4', - - FLOAT_MAT2: 'mat2', - FLOAT_MAT3: 'mat3', - FLOAT_MAT4: 'mat4', - - SAMPLER_2D: 'sampler2D', - SAMPLER_CUBE: 'samplerCube', - SAMPLER_2D_ARRAY: 'sampler2DArray', -}; - -export default function mapType(gl, type) -{ - if (!GL_TABLE) - { - const typeNames = Object.keys(GL_TO_GLSL_TYPES); - - GL_TABLE = {}; - - for (let i = 0; i < typeNames.length; ++i) - { - const tn = typeNames[i]; - - GL_TABLE[gl[tn]] = GL_TO_GLSL_TYPES[tn]; - } - } - - return GL_TABLE[type]; -} diff --git a/packages/core/src/renderers/systems/shader/shader/setPrecision.js b/packages/core/src/renderers/systems/shader/shader/setPrecision.js deleted file mode 100644 index e2c1f34..0000000 --- a/packages/core/src/renderers/systems/shader/shader/setPrecision.js +++ /dev/null @@ -1,18 +0,0 @@ -/** - * Sets the float precision on the shader. If the precision is already present this function will do nothing - * @param {string} src the shader source - * @param {string} precision The float precision of the shader. Options are 'lowp', 'mediump' or 'highp'. - * - * @return {string} modified shader source - */ -export default function setPrecision(src, precision) -{ - src = src.trim(); - - if (src.substring(0, 9) !== 'precision' && src.substring(0, 1) !== '#') - { - return `precision ${precision} float;\n${src}`; - } - - return src; -} diff --git a/packages/core/src/renderers/systems/textures/GLTexture.js b/packages/core/src/renderers/systems/textures/GLTexture.js deleted file mode 100644 index c3fb8dc..0000000 --- a/packages/core/src/renderers/systems/textures/GLTexture.js +++ /dev/null @@ -1,33 +0,0 @@ -export default class GLTexture -{ - constructor(texture) - { - /** - * The WebGL texture - * - * @member {WebGLTexture} - */ - this.texture = texture; - - this.width = -1; - this.height = -1; - - /** - * Texture contents dirty flag - * @member {number} - */ - this.dirtyId = -1; - - /** - * Texture style dirty flag - * @type {number} - */ - this.dirtyStyleId = -1; - - /** - * Whether mip levels has to be generated - * @type {boolean} - */ - this.mipmap = false; - } -} diff --git a/packages/core/src/renderers/systems/textures/TextureGCSystem.js b/packages/core/src/renderers/systems/textures/TextureGCSystem.js deleted file mode 100644 index b089938..0000000 --- a/packages/core/src/renderers/systems/textures/TextureGCSystem.js +++ /dev/null @@ -1,110 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; -import { GC_MODES } from '@pixi/constants'; -import { settings } from '@pixi/settings'; - -/** - * TextureGarbageCollector. This class manages the GPU and ensures that it does not get clogged - * up with textures that are no longer being used. - * - * @class - * @memberof PIXI - */ -export default class TextureGCSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this.count = 0; - this.checkCount = 0; - this.maxIdle = settings.GC_MAX_IDLE; - this.checkCountMax = settings.GC_MAX_CHECK_COUNT; - this.mode = settings.GC_MODE; - } - - /** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ - postrender() - { - this.count++; - - if (this.mode === GC_MODES.MANUAL) - { - return; - } - - this.checkCount++; - - if (this.checkCount > this.checkCountMax) - { - this.checkCount = 0; - - this.run(); - } - } - - /** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ - run() - { - const tm = this.renderer.texture; - const managedTextures = tm.managedTextures; - let wasRemoved = false; - - for (let i = 0; i < managedTextures.length; i++) - { - const texture = managedTextures[i]; - - // only supports non generated textures at the moment! - if (!texture.frameBuffer && this.count - texture.touched > this.maxIdle) - { - tm.destroyTexture(texture, true); - managedTextures[i] = null; - wasRemoved = true; - } - } - - if (wasRemoved) - { - let j = 0; - - for (let i = 0; i < managedTextures.length; i++) - { - if (managedTextures[i] !== null) - { - managedTextures[j++] = managedTextures[i]; - } - } - - managedTextures.length = j; - } - } - - /** - * Removes all the textures within the specified displayObject and its children from the GPU - * - * @param {PIXI.DisplayObject} displayObject - the displayObject to remove the textures from. - */ - unload(displayObject) - { - const tm = this.renderer.textureSystem; - - // only destroy non generated textures - if (displayObject._texture && displayObject._texture._glRenderTargets) - { - tm.destroyTexture(displayObject._texture); - } - - for (let i = displayObject.children.length - 1; i >= 0; i--) - { - this.unload(displayObject.children[i]); - } - } -} diff --git a/packages/core/src/renderers/systems/textures/TextureSystem.js b/packages/core/src/renderers/systems/textures/TextureSystem.js deleted file mode 100644 index ccc5de9..0000000 --- a/packages/core/src/renderers/systems/textures/TextureSystem.js +++ /dev/null @@ -1,281 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; -import GLTexture from './GLTexture'; -import { removeItems } from '@pixi/utils'; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class TextureSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - // TODO set to max textures... - this.boundTextures = [ - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - ]; - - this.currentLocation = -1; - - this.managedTextures = []; - } - - /** - * Sets up the renderer context and necessary buffers. - * - * @private - */ - contextChange() - { - const gl = this.gl = this.renderer.gl; - - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - - // TODO move this.. to a nice make empty textures class.. - this.emptyTextures = {}; - - const emptyTexture2D = new GLTexture(gl.createTexture()); - - gl.bindTexture(gl.TEXTURE_2D, emptyTexture2D.texture); - gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array(4)); - - this.emptyTextures[gl.TEXTURE_2D] = emptyTexture2D; - this.emptyTextures[gl.TEXTURE_CUBE_MAP] = new GLTexture(gl.createTexture()); - - gl.bindTexture(gl.TEXTURE_CUBE_MAP, this.emptyTextures[gl.TEXTURE_CUBE_MAP].texture); - - let i; - - for (i = 0; i < 6; i++) - { - gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, null); - } - - gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_MAG_FILTER, gl.LINEAR); - gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_MIN_FILTER, gl.LINEAR); - - for (i = 0; i < this.boundTextures.length; i++) - { - this.bind(null, i); - } - } - - bind(texture, location) - { - const gl = this.gl; - - location = location || 0; - - if (this.currentLocation !== location) - { - this.currentLocation = location; - gl.activeTexture(gl.TEXTURE0 + location); - } - - if (texture) - { - texture = texture.baseTexture || texture; - - if (texture.valid) - { - texture.touched = this.renderer.textureGC.count; - - const glTexture = texture._glTextures[this.CONTEXT_UID] || this.initTexture(texture); - - gl.bindTexture(texture.target, glTexture.texture); - - if (glTexture.dirtyId !== texture.dirtyId) - { - this.updateTexture(texture); - } - - this.boundTextures[location] = texture; - } - } - else - { - gl.bindTexture(gl.TEXTURE_2D, this.emptyTextures[gl.TEXTURE_2D].texture); - this.boundTextures[location] = null; - } - } - - unbind(texture) - { - const gl = this.gl; - - for (let i = 0; i < this.boundTextures.length; i++) - { - if (this.boundTextures[i] === texture) - { - if (this.currentLocation !== i) - { - gl.activeTexture(gl.TEXTURE0 + i); - this.currentLocation = i; - } - - gl.bindTexture(gl.TEXTURE_2D, this.emptyTextures[texture.target].texture); - this.boundTextures[i] = null; - } - } - } - - initTexture(texture) - { - const glTexture = new GLTexture(this.gl.createTexture()); - - // guarentee an update.. - glTexture.dirtyId = -1; - - texture._glTextures[this.CONTEXT_UID] = glTexture; - - this.managedTextures.push(texture); - texture.on('dispose', this.destroyTexture, this); - - return glTexture; - } - - updateTexture(texture) - { - const glTexture = texture._glTextures[this.CONTEXT_UID]; - const renderer = this.renderer; - - if (texture.resource && texture.resource.upload(renderer, texture, glTexture)) - { - // texture is uploaded, dont do anything! - } - else - { - // default, renderTexture-like logic - const width = texture.realWidth; - const height = texture.realHeight; - const gl = renderer.gl; - - if (glTexture.width !== width - || glTexture.height !== height - || glTexture.dirtyId < 0) - { - glTexture.width = width; - glTexture.height = height; - - gl.texImage2D(texture.target, 0, - texture.format, - width, - height, - 0, - texture.format, - texture.type, - null); - } - } - - // lets only update what changes.. - if (texture.dirtyStyleId !== glTexture.dirtyStyleId) - { - this.updateTextureStyle(texture); - } - glTexture.dirtyId = texture.dirtyId; - } - - /** - * Deletes the texture from WebGL - * - * @param {PIXI.BaseTexture|PIXI.Texture} texture - the texture to destroy - * @param {boolean} [skipRemove=false] - Whether to skip removing the texture from the TextureManager. - */ - destroyTexture(texture, skipRemove) - { - const gl = this.gl; - - texture = texture.baseTexture || texture; - - if (texture._glTextures[this.renderer.CONTEXT_UID]) - { - this.unbind(texture); - - gl.deleteTexture(texture._glTextures[this.renderer.CONTEXT_UID].texture); - texture.off('dispose', this.destroyTexture, this); - - delete texture._glTextures[this.renderer.CONTEXT_UID]; - - if (!skipRemove) - { - const i = this.managedTextures.indexOf(texture); - - if (i !== -1) - { - removeItems(this.managedTextures, i, 1); - } - } - } - } - - updateTextureStyle(texture) - { - const glTexture = texture._glTextures[this.CONTEXT_UID]; - - glTexture.mipmap = texture.mipmap && texture.isPowerOfTwo; - if (!glTexture) - { - return; - } - - if (texture.resource && texture.resource.style(this.renderer, texture, glTexture)) - { - // style is set, dont do anything! - } - else - { - this.setStyle(texture, glTexture); - } - - glTexture.dirtyStyleId = texture.dirtyStyleId; - } - - setStyle(texture, glTexture) - { - const gl = this.gl; - - if (glTexture.mipmap) - { - gl.generateMipmap(texture.target); - } - - gl.texParameteri(texture.target, gl.TEXTURE_WRAP_S, texture.wrapMode); - gl.texParameteri(texture.target, gl.TEXTURE_WRAP_T, texture.wrapMode); - - if (glTexture.mipmap) - { - /* eslint-disable max-len */ - gl.texParameteri(texture.target, gl.TEXTURE_MIN_FILTER, texture.scaleMode ? gl.LINEAR_MIPMAP_LINEAR : gl.NEAREST_MIPMAP_NEAREST); - /* eslint-disable max-len */ - } - else - { - gl.texParameteri(texture.target, gl.TEXTURE_MIN_FILTER, texture.scaleMode ? gl.LINEAR : gl.NEAREST); - } - - gl.texParameteri(texture.target, gl.TEXTURE_MAG_FILTER, texture.scaleMode ? gl.LINEAR : gl.NEAREST); - } -} diff --git a/packages/core/src/AbstractRenderer.js b/packages/core/src/AbstractRenderer.js new file mode 100644 index 0000000..64b2eb1 --- /dev/null +++ b/packages/core/src/AbstractRenderer.js @@ -0,0 +1,353 @@ +import { hex2string, hex2rgb } from '@pixi/utils'; +import { Matrix, Rectangle } from '@pixi/math'; +import { RENDERER_TYPE } from '@pixi/constants'; +import { settings } from '@pixi/settings'; +import { Container } from '@pixi/display'; +import RenderTexture from './renderTexture/RenderTexture'; +import EventEmitter from 'eventemitter3'; + +const tempMatrix = new Matrix(); + +/** + * 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 EventEmitter + * @memberof PIXI + */ +export default class AbstractRenderer extends EventEmitter +{ + // eslint-disable-next-line valid-jsdoc + /** + * @param {string} system - The name of the system this renderer is for. + * @param {object} [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.transparent=false] - If the render view is transparent, default false + * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false + * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. The + * resolution of the renderer retina would be 2. + * @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 {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when rendering, + * stopping pixel interpolation. + */ + constructor(system, options, arg2, arg3) + { + super(); + + // Support for constructor(system, screenWidth, screenHeight, options) + if (typeof options === 'number') + { + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + }, arg3); + } + + // 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 = RENDERER_TYPE.UNKNOWN; + + /** + * Measurements of the screen. (0, 0, screenWidth, screenHeight) + * + * Its safe to use as filterArea or hitArea for 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 1 + */ + this.resolution = options.resolution || settings.RESOLUTION; + + /** + * Whether the render view is transparent + * + * @member {boolean} + */ + this.transparent = options.transparent; + + /** + * Whether css dimensions of canvas view should be resized to screen dimensions automatically + * + * @member {boolean} + */ + this.autoResize = options.autoResize || false; + + /** + * Tracks the blend modes useful for this renderer. + * + * @member {object} + */ + this.blendModes = null; + + /** + * 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; + + /** + * If true PixiJS will Math.floor() x/y values when rendering, stopping pixel interpolation. + * Handy for crisp pixel art and speed on legacy devices. + * + * @member {boolean} + */ + this.roundPixels = options.roundPixels; + + /** + * The background color as a number. + * + * @member {number} + * @private + */ + this._backgroundColor = 0x000000; + + /** + * The background color as an [R, G, B] array. + * + * @member {number[]} + * @private + */ + this._backgroundColorRgba = [0, 0, 0, 0]; + + /** + * The background color as a string. + * + * @member {string} + * @private + */ + this._backgroundColorString = '#000000'; + + this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter + + /** + * This temporary display object used as the parent of the currently being rendered item + * + * @member {PIXI.DisplayObject} + * @private + */ + this._tempDisplayObjectParent = new Container(); + + /** + * The last root object that the renderer tried to render. + * + * @member {PIXI.DisplayObject} + * @private + */ + this._lastObjectRendered = this._tempDisplayObjectParent; + + /** + * Collection of plugins. + * @readonly + * @member {object} + */ + this.plugins = {}; + } + + /** + * Initialize the plugins. + * + * @protected + * @param {object} staticMap - The dictionary of staticly saved plugins. + */ + initPlugins(staticMap) + { + 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() + { + return this.view.width; + } + + /** + * Same as view.height, actual number of pixels in the canvas by vertical + * + * @member {number} + * @readonly + * @default 600 + */ + get height() + { + return this.view.height; + } + + /** + * Resizes the screen and canvas to the specified width and height + * Canvas dimensions are multiplied by resolution + * + * @param {number} screenWidth - the new width of the screen + * @param {number} screenHeight - the new height of the screen + */ + resize(screenWidth, screenHeight) + { + this.screen.width = screenWidth; + this.screen.height = screenHeight; + + this.view.width = screenWidth * this.resolution; + this.view.height = screenHeight * this.resolution; + + if (this.autoResize) + { + this.view.style.width = `${screenWidth}px`; + this.view.style.height = `${screenHeight}px`; + } + } + + /** + * 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 {PIXI.DisplayObject} displayObject - The displayObject the object will be generated from + * @param {number} scaleMode - Should be one of the scaleMode consts + * @param {number} resolution - The resolution / device pixel ratio of the texture being generated + * @param {PIXI.Rectangle} [region] - The region of the displayObject, that shall be rendered, + * if no region is specified, defaults to the local bounds of the displayObject. + * @return {PIXI.Texture} a texture of the graphics object + */ + generateTexture(displayObject, scaleMode, resolution, region) + { + region = region || displayObject.getLocalBounds(); + + // 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(region.width | 0, region.height | 0, scaleMode, resolution); + + tempMatrix.tx = -region.x; + tempMatrix.ty = -region.y; + + this.render(displayObject, renderTexture, false, tempMatrix, true); + + return renderTexture; + } + + /** + * Removes everything from the renderer and optionally removes the Canvas DOM element. + * + * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. + */ + destroy(removeView) + { + 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); + } + + this.plugins = null; + + this.type = RENDERER_TYPE.UNKNOWN; + + this.view = null; + + this.screen = null; + + this.resolution = 0; + + this.transparent = false; + + this.autoResize = false; + + this.blendModes = null; + + this.options = null; + + this.preserveDrawingBuffer = false; + this.clearBeforeRender = false; + + this.roundPixels = false; + + this._backgroundColor = 0; + this._backgroundColorRgba = null; + this._backgroundColorString = null; + + this._tempDisplayObjectParent = null; + this._lastObjectRendered = null; + } + + /** + * The background color to fill if not transparent + * + * @member {number} + */ + get backgroundColor() + { + return this._backgroundColor; + } + + set backgroundColor(value) // eslint-disable-line require-jsdoc + { + this._backgroundColor = value; + this._backgroundColorString = hex2string(value); + hex2rgb(value, this._backgroundColorRgba); + } +} diff --git a/packages/core/src/Renderer.js b/packages/core/src/Renderer.js new file mode 100644 index 0000000..2227e78 --- /dev/null +++ b/packages/core/src/Renderer.js @@ -0,0 +1,338 @@ +import AbstractRenderer from './AbstractRenderer'; +import { sayHello } from '@pixi/utils'; +import MaskSystem from './mask/MaskSystem'; +import StencilSystem from './mask/StencilSystem'; +import FilterSystem from './filters/FilterSystem'; +import FramebufferSystem from './framebuffer/FramebufferSystem'; +import RenderTextureSystem from './renderTexture/RenderTextureSystem'; +import TextureSystem from './textures/TextureSystem'; +import ProjectionSystem from './projection/ProjectionSystem'; +import StateSystem from './state/StateSystem'; +import GeometrySystem from './geometry/GeometrySystem'; +import ShaderSystem from './shader/ShaderSystem'; +import ContextSystem from './context/ContextSystem'; +import BatchSystem from './batch/BatchSystem'; +import TextureGCSystem from './textures/TextureGCSystem'; +import { RENDERER_TYPE } from '@pixi/constants'; +import UniformGroup from './shader/UniformGroup'; +import { Matrix } from '@pixi/math'; +import Runner from 'mini-runner'; + +/** + * 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 Render works by automatically managing webGLBatchs. + * 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 :) + * + * @class + * @memberof PIXI + * @extends PIXI.AbstractRenderer + */ +export default class Renderer extends AbstractRenderer +{ + // eslint-disable-next-line valid-jsdoc + /** + * + * @param {object} [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.transparent=false] - If the render view is transparent, default false + * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false + * @param {boolean} [options.antialias=false] - sets antialias. If not available natively then FXAA + * antialiasing is used + * @param {boolean} [options.forceFXAA=false] - forces FXAA antialiasing to be used over native. + * FXAA is faster, but may not always look as great + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. + * The resolution of the renderer retina would be 2. + * @param {boolean} [options.clearBeforeRender=true] - This sets if the renderer will clear + * the canvas or not before the new render pass. If you wish to set this to false, you *must* set + * preserveDrawingBuffer to `true`. + * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, + * enable this if you need to call toDataUrl on the webgl context. + * @param {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when + * rendering, stopping pixel interpolation. + * @param {number} [options.backgroundColor=0x000000] - The background color of the rendered area + * (shown if not transparent). + * @param {boolean} [options.legacy=false] - If true PixiJS will aim to ensure compatibility + * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. + * @param {string} [options.powerPreference] - Parameter passed to webgl context, set to "high-performance" + * for devices with dual graphics card + */ + constructor(options, arg2, arg3) + { + super('WebGL', options, arg2, arg3); + + /** + * The type of this renderer as a standardised const + * + * @member {number} + * @see PIXI.RENDERER_TYPE + */ + this.type = RENDERER_TYPE.WEBGL; + + // this will be set by the contextSystem (this.context) + this.gl = null; + this.CONTEXT_UID = 0; + this.legacy = !!options.legacy; + + // TODO legacy! + + // runners! + this.runners = { + destroy: new Runner('destroy'), + contextChange: new Runner('contextChange', 1), + reset: new Runner('reset'), + update: new Runner('update'), + postrender: new Runner('postrender'), + prerender: new Runner('prerender'), + resize: new Runner('resize', 2), + }; + + 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(GeometrySystem, 'geometry') + .addSystem(FramebufferSystem, 'framebuffer') + .addSystem(StencilSystem, 'stencil') + .addSystem(ProjectionSystem, 'projection') + .addSystem(TextureGCSystem, 'textureGC') + .addSystem(FilterSystem, 'filter') + .addSystem(RenderTextureSystem, 'renderTexture') + .addSystem(BatchSystem, 'batch'); + + this.initPlugins(Renderer.__plugins); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ + if (options.context) + { + this.context.initFromContext(options.context); + } + else + { + this.context.initFromOptions({ + alpha: this.transparent, + antialias: options.antialias, + premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', + stencil: true, + 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); + } + + /** + * Add a new system to the renderer. + * @param {class} ClassRef - Class reference + * @param {string} [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. + * @return {PIXI.Renderer} Return instance of renderer + */ + addSystem(ClassRef, name) + { + if (!name) + { + name = ClassRef.name; + } + + // TODO - read name from class.name.. + + /* + if(name.includes('System')) + { + name = name.replace('System', ''); + name = name.charAt(0).toLowerCase() + name.slice(1); + } + */ + + const system = new ClassRef(this); + + if (this[name]) + { + throw new Error(`Whoops! ${name} is already a manger`); + } + + this[name] = system; + + for (const i in this.runners) + { + this.runners[i].add(system); + } + + return this; + + /** + * 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. + */ + } + + /** + * Renders the object to its webGL view + * + * @param {PIXI.DisplayObject} displayObject - the object to be rendered + * @param {PIXI.RenderTexture} renderTexture - The render texture to render to. + * @param {boolean} [clear] - Should the canvas be cleared before the new render + * @param {PIXI.Transform} [transform] - A transform to apply to the render texture before rendering. + * @param {boolean} [skipUpdateTransform] - Should we skip the update transform pass? + */ + render(displayObject, renderTexture, clear, transform, skipUpdateTransform) + { + // can be handy to know! + this.renderingToScreen = !renderTexture; + + this.runners.prerender.run(); + this.emit('prerender'); + + // 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.parent; + + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = 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.run(); + + this.emit('postrender'); + } + + /** + * Resizes the webGL view to the specified width and height. + * + * @param {number} screenWidth - the new width of the screen + * @param {number} screenHeight - the new height of the screen + */ + resize(screenWidth, screenHeight) + { + AbstractRenderer.prototype.resize.call(this, screenWidth, screenHeight); + + this.runners.resize.run(screenWidth, screenHeight); + } + + /** + * Resets the WebGL state so you can render things however you fancy! + * + * @return {PIXI.Renderer} Returns itself. + */ + reset() + { + this.runners.reset.run(); + + return this; + } + + clear() + { + this.framebuffer.bind(); + this.framebuffer.clear(); + } + + /** + * Removes everything from the renderer (event listeners, spritebatch, etc...) + * + * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. + * See: https://github.com/pixijs/pixi.js/issues/2233 + */ + destroy(removeView) + { + this.runners.destroy.run(); + + // call base destroy + super.destroy(removeView); + + // TODO nullify all the managers.. + this.gl = null; + } + + /** + * 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 + * builds and excluding plugins. + * @name PIXI.Renderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. + */ + + /** + * Adds a plugin to the renderer. + * + * @method + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + static registerPlugin(pluginName, ctor) + { + Renderer.__plugins = Renderer.__plugins || {}; + Renderer.__plugins[pluginName] = ctor; + } +} diff --git a/packages/core/src/System.js b/packages/core/src/System.js new file mode 100644 index 0000000..05a10c8 --- /dev/null +++ b/packages/core/src/System.js @@ -0,0 +1,42 @@ +/** + * System is a base class used for extending systems used by the {@link PIXI.Renderer} + * @see PIXI.Renderer#addSystem + * @class + * @memberof PIXI + */ +export default class System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + /** + * The renderer this manager works for. + * + * @member {PIXI.Renderer} + */ + this.renderer = renderer; + + this.renderer.runners.contextChange.add(this); + } + + /** + * Generic method called when there is a WebGL context change. + * + */ + contextChange() + { + // do some codes init! + } + + /** + * Generic destroy methods to be overridden by the subclass + * + */ + destroy() + { + this.renderer.runners.contextChange.remove(this); + this.renderer = null; + } +} diff --git a/packages/core/src/batch/BatchSystem.js b/packages/core/src/batch/BatchSystem.js new file mode 100644 index 0000000..b04c249 --- /dev/null +++ b/packages/core/src/batch/BatchSystem.js @@ -0,0 +1,68 @@ +import System from '../System'; +import ObjectRenderer from './ObjectRenderer'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class BatchSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + super(renderer); + + /** + * An empty renderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.emptyRenderer = new ObjectRenderer(renderer); + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.currentRenderer = this.emptyRenderer; + } + + /** + * Changes the current renderer to the one given in parameter + * + * @param {PIXI.ObjectRenderer} objectRenderer - The object renderer to use. + */ + setObjectRenderer(objectRenderer) + { + if (this.currentRenderer === objectRenderer) + { + return; + } + + this.currentRenderer.stop(); + this.currentRenderer = objectRenderer; + + this.renderer.state.setState(objectRenderer.state); + + this.currentRenderer.start(); + } + + /** + * This should be called if you wish to do some custom rendering + * It will basically render anything that may be batched up such as sprites + * + */ + flush() + { + this.setObjectRenderer(this.emptyRenderer); + } + + reset() + { + this.setObjectRenderer(this.emptyRenderer); + } +} diff --git a/packages/core/src/batch/ObjectRenderer.js b/packages/core/src/batch/ObjectRenderer.js new file mode 100644 index 0000000..0c851a9 --- /dev/null +++ b/packages/core/src/batch/ObjectRenderer.js @@ -0,0 +1,48 @@ +import System from '../System'; + +/** + * Base for a common object renderer that can be used as a system renderer plugin. + * + * @class + * @extends PIXI.System + * @memberof PIXI + */ +export default class ObjectRenderer extends System +{ + /** + * Starts the renderer and sets the shader + * + */ + start() + { + // set the shader.. + } + + /** + * Stops the renderer + * + */ + stop() + { + this.flush(); + } + + /** + * Stub method for rendering content and emptying the current batch. + * + */ + flush() + { + // flush! + } + + /** + * Renders an object + * + * @param {PIXI.DisplayObject} object - The object to render. + */ + render(object) // eslint-disable-line no-unused-vars + { + // render the object + } +} diff --git a/packages/core/src/context/ContextSystem.js b/packages/core/src/context/ContextSystem.js new file mode 100644 index 0000000..301aab4 --- /dev/null +++ b/packages/core/src/context/ContextSystem.js @@ -0,0 +1,186 @@ +import System from '../System'; +import { settings } from '@pixi/settings'; + +let CONTEXT_UID = 0; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class ContextSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.webGLVersion = 1; + + this.handleContextLost = this.handleContextLost.bind(this); + this.handleContextRestored = this.handleContextRestored.bind(this); + + this.extensions = {}; + + renderer.view.addEventListener('webglcontextlost', this.handleContextLost, false); + renderer.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + } + + get isLost() + { + return (!this.gl || this.gl.isContextLost()); + } + + contextChange(gl) + { + this.gl = gl; + + // restore a context if it was previously lost + if (gl.isContextLost() && gl.getExtension('WEBGL_lose_context')) + { + gl.getExtension('WEBGL_lose_context').restoreContext(); + } + } + + initFromContext(gl) + { + this.gl = gl; + this.validateContext(gl); + this.renderer.gl = gl; + this.renderer.CONTEXT_UID = CONTEXT_UID++; + this.renderer.runners.contextChange.run(gl); + } + + initFromOptions(options) + { + const gl = this.createContext(this.renderer.view, options); + + this.initFromContext(gl); + } + + /** + * Helper class to create a webGL Context + * + * @class + * @memberof PIXI.glCore + * @param canvas {HTMLCanvasElement} the canvas element that we will get the context from + * @param options {Object} An options object that gets passed in to the canvas element containing the context attributes, + * see https://developer.mozilla.org/en/docs/Web/API/HTMLCanvasElement/getContext for the options available + * @return {WebGLRenderingContext} the WebGL context + */ + createContext(canvas, options) + { + let gl; + + if (settings.PREFER_WEBGL_2) + { + gl = canvas.getContext('webgl2', options); + } + + if (gl) + { + this.webGLVersion = 2; + } + else + { + this.webGLVersion = 1; + + gl = canvas.getContext('webgl', options) + || canvas.getContext('experimental-webgl', options); + + if (!gl) + { + // fail, not able to get a context + throw new Error('This browser does not support webGL. Try using the canvas renderer'); + } + } + + this.gl = gl; + + this.getExtensions(); + + return gl; + } + + getExtensions() + { + // time to set up default etensions that pixi uses.. + const gl = this.gl; + const extensions = this.extensions; + + if (this.webGLVersion === 1) + { + extensions.drawBuffers = gl.getExtension('WEBGL_draw_buffers'); + extensions.depthTexture = gl.getExtension('WEBKIT_WEBGL_depth_texture'); + extensions.floatTexture = gl.getExtension('OES_texture_float'); + extensions.loseContext = gl.getExtension('WEBGL_lose_context'); + + extensions.vertexArrayObject = gl.getExtension('OES_vertex_array_object') + || gl.getExtension('MOZ_OES_vertex_array_object') + || gl.getExtension('WEBKIT_OES_vertex_array_object'); + } + + // we don't use any specific WebGL 2 ones yet! + } + + /** + * Handles a lost webgl context + * + * @private + * @param {WebGLContextEvent} event - The context lost event. + */ + handleContextLost(event) + { + event.preventDefault(); + } + + /** + * Handles a restored webgl context + * + * @private + */ + handleContextRestored() + { + this.renderer.runners.contextChange.run(this.gl); + } + + destroy() + { + const view = this.renderer.view; + + // remove listeners + view.removeEventListener('webglcontextlost', this.handleContextLost); + view.removeEventListener('webglcontextrestored', this.handleContextRestored); + + this.gl.useProgram(null); + + if (this.extensions.loseContext) + { + this.extensions.loseContext.loseContext(); + } + } + + postrender() + { + this.gl.flush(); + } + + validateContext(gl) + { + const attributes = gl.getContextAttributes(); + + // this is going to be fairly simple for now.. but at least we have room to grow! + if (!attributes.stencil) + { + /* eslint-disable max-len */ + + /* eslint-disable no-console */ + console.warn('Provided WebGL context does not have a stencil buffer, masks may not render correctly'); + /* eslint-enable no-console */ + + /* eslint-enable max-len */ + } + } +} diff --git a/packages/core/src/filters/Filter.js b/packages/core/src/filters/Filter.js new file mode 100644 index 0000000..9d8cad8 --- /dev/null +++ b/packages/core/src/filters/Filter.js @@ -0,0 +1,140 @@ +import Shader from '../shader/Shader'; +import Program from '../shader/Program'; +import State from '../state/State'; +import { settings } from '@pixi/settings'; +// import extractUniformsFromSrc from './extractUniformsFromSrc'; +import defaultVertex from './defaultFilter.vert'; +import defaultFragment from './defaultFilter.frag'; + +// let math = require('../../math'); +/** + * @class + * @memberof PIXI + * @extends PIXI.Shader + */ +export default class Filter extends Shader +{ + /** + * @param {string} [vertexSrc] - The source of the vertex shader. + * @param {string} [fragmentSrc] - The source of the fragment shader. + * @param {object} [uniforms] - Custom uniforms to use to augment the built-in ones. + */ + constructor(vertexSrc, fragmentSrc, uniforms) + { + const program = Program.from(vertexSrc, fragmentSrc); + + super(program, uniforms); + + /** + * The padding of the filter. Some filters require extra space to breath such as a blur. + * Increasing this will add extra width and height to the bounds of the object that the + * filter is applied to. + * + * @member {number} + */ + this.padding = 0; + + /** + * The resolution of the filter. Setting this to be lower will lower the quality but + * increase the performance of the filter. + * + * @member {number} + */ + this.resolution = settings.RESOLUTION; + + /** + * If enabled is true the filter is applied, if false it will not. + * + * @member {boolean} + */ + this.enabled = true; + + /** + * If enabled, PixiJS will fit the filter area into boundaries for better performance. + * Switch it off if it does not work for specific shader. + * + * @member {boolean} + */ + this.autoFit = true; + + /** + * Legacy filters use position and uvs from attributes + * @member {boolean} + * @readonly + */ + this.legacy = !!this.program.attributeData.aTextureCoord; + + /** + * the webGL state the filter requires to render + * @member {PIXI.State} + */ + this.state = new State(); + } + + /** + * Applies the filter + * + * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + * @param {boolean} clear - Should the output be cleared before rendering to it + * @param {object} [currentState] - It's current state of filter. + * There are some useful properties in the currentState : + * target, filters, sourceFrame, destinationFrame, renderTarget, resolution + */ + apply(filterManager, input, output, clear, currentState, derp) // eslint-disable-line no-unused-vars + { + // do as you please! + + filterManager.applyFilter(this, input, output, clear, currentState, derp); + + // or just do a regular render.. + } + + /** + * Sets the blendmode of the filter + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL + */ + get blendMode() + { + return this.state.blendMode; + } + + set blendMode(value) // eslint-disable-line require-jsdoc + { + this.state.blendMode = value; + } + + /** + * The default vertex shader source + * + * @static + * @constant + */ + static get defaultVertexSrc() + { + return defaultVertex; + } + + /** + * The default fragment shader source + * + * @static + * @constant + */ + static get defaultFragmentSrc() + { + return defaultFragment; + } +} + +/** + * Used for caching shader IDs + * + * @static + * @private + */ +Filter.SOURCE_KEY_MAP = {}; + diff --git a/packages/core/src/filters/FilterSystem.js b/packages/core/src/filters/FilterSystem.js new file mode 100644 index 0000000..3174318 --- /dev/null +++ b/packages/core/src/filters/FilterSystem.js @@ -0,0 +1,431 @@ +import System from '../System'; + +import RenderTexture from '../renderTexture/RenderTexture'; +import Quad from '../utils/Quad'; +import QuadUv from '../utils/QuadUv'; +import { Rectangle } from '@pixi/math'; +import * as filterTransforms from './filterTransforms'; +import bitTwiddle from 'bit-twiddle'; +import UniformGroup from '../shader/UniformGroup'; +import { DRAW_MODES } from '@pixi/constants'; + +// +/** + * @ignore + * @class + */ +class FilterState +{ + /** + * + */ + constructor() + { + this.renderTexture = null; + this.sourceFrame = new Rectangle(); + this.destinationFrame = new Rectangle(); + this.filters = []; + this.target = null; + this.legacy = false; + this.resolution = 1; + } +} + +/** + * @class + * @memberof PIXI.systems + * @extends PIXI.System + */ +export default class FilterSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + /** + * stores a bunch of PO2 textures used for filtering + * @type {Object} + */ + this.texturePool = {}; + + /** + * a pool for storing filter states, save us creating new ones each tick + * @type {Array} + */ + this.statePool = []; + + /** + * A very simple geometry used when drawing a filter effect to the screen + * @type {Quad} + */ + this.quad = new Quad(); + + this.quadUv = new QuadUv(); + + /** + * Temporary rect for maths + * @type {PIXI.Rectangle} + */ + this.tempRect = new Rectangle(); + + this.activeState = {}; + + /** + * this uniform group is attached to filter uniforms when used + * @type {UniformGroup} + */ + this.globalUniforms = new UniformGroup({ + sourceFrame: this.tempRect, + destinationFrame: this.tempRect, + + // legacy variables + filterArea: new Float32Array(4), + filterClamp: new Float32Array(4), + }, true); + } + + /** + * Adds a new filter to the System. + * + * @param {PIXI.DisplayObject} target - The target of the filter to render. + * @param {PIXI.Filter[]} filters - The filters to apply. + */ + push(target, filters) + { + const renderer = this.renderer; + const filterStack = this.renderer.renderTexture.defaultFilterStack; + const state = this.statePool.pop() || new FilterState(); + + let resolution = filters[0].resolution; + let padding = filters[0].padding; + let autoFit = filters[0].autoFit; + let legacy = filters[0].legacy; + + for (let i = 1; i < filters.length; i++) + { + const filter = filters[i]; + + // lets use the lowest resolution.. + resolution = Math.min(resolution, filter.resolution); + // and the largest amount of padding! + padding = Math.max(padding, filter.padding); + // only auto fit if all filters are autofit + autoFit = autoFit || filter.autoFit; + + legacy = legacy || filter.legacy; + } + + filterStack.push(state); + + state.resolution = resolution; + + state.legacy = legacy; + + // round to whole number based on resolution + // TODO move that to the shader too? + state.sourceFrame = target.filterArea || target.getBounds(true); + + state.sourceFrame.pad(padding); + + if (autoFit) + { + state.sourceFrame.fit(this.renderer.renderTexture.destinationFrame); + } + + state.sourceFrame.round(resolution); + + state.renderTexture = this.getPotFilterTexture(state.sourceFrame.width, state.sourceFrame.height, resolution); + state.filters = filters; + + state.destinationFrame.width = state.renderTexture.width; + state.destinationFrame.height = state.renderTexture.height; + + state.renderTexture.filterFrame = state.sourceFrame; + renderer.renderTexture.bind(state.renderTexture, state.sourceFrame);// /, state.destinationFrame); + renderer.renderTexture.clear(); + } + + /** + * Pops off the filter and applies it. + * + */ + pop() + { + const renderer = this.renderer; + const filterStack = renderer.renderTexture.defaultFilterStack; + const state = filterStack.pop(); + const filters = state.filters; + + this.activeState = state; + + const globalUniforms = this.globalUniforms.uniforms; + + globalUniforms.sourceFrame = state.sourceFrame; + globalUniforms.destinationFrame = state.destinationFrame; + globalUniforms.resolution = state.resolution; + + // only update the rect if its legacy.. + if (state.legacy) + { + const filterArea = globalUniforms.filterArea; + const filterClamp = globalUniforms.filterClamp; + + filterArea[0] = state.destinationFrame.width; + filterArea[1] = state.destinationFrame.height; + filterArea[2] = state.sourceFrame.x; + filterArea[3] = state.sourceFrame.y; + + filterClamp[0] = 0.5 / state.resolution / state.destinationFrame.width; + filterClamp[1] = 0.5 / state.resolution / state.destinationFrame.height; + filterClamp[2] = (state.sourceFrame.width - 0.5) / state.resolution / state.destinationFrame.width; + filterClamp[3] = (state.sourceFrame.height - 0.5) / state.resolution / state.destinationFrame.height; + } + + this.globalUniforms.update(); + + const lastState = filterStack[filterStack.length - 1]; + + if (filters.length === 1) + { + filters[0].apply(this, state.renderTexture, lastState.renderTexture, false, state); + + this.returnFilterTexture(state.renderTexture); + } + else + { + let flip = state.renderTexture; + let flop = this.getPotFilterTexture( + flip.width, + flip.height, + state.resolution + ); + + flop.filterFrame = flip.filterFrame; + + let i = 0; + + for (i = 0; i < filters.length - 1; ++i) + { + filters[i].apply(this, flip, flop, true, state); + + const t = flip; + + flip = flop; + flop = t; + } + + filters[i].apply(this, flip, lastState.renderTexture, false, state); + + this.returnFilterTexture(flip); + this.returnFilterTexture(flop); + } + + this.statePool.push(state); + } + + /** + * Draws a filter. + * + * @param {PIXI.Filter} filter - The filter to draw. + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + * @param {boolean} clear - Should the output be cleared before rendering to it + */ + applyFilter(filter, input, output, clear) + { + const renderer = this.renderer; + + renderer.renderTexture.bind(output, output ? output.filterFrame : null); + + if (clear) + { + // gl.disable(gl.SCISSOR_TEST); + renderer.renderTexture.clear(); + // gl.enable(gl.SCISSOR_TEST); + } + + // set the uniforms.. + filter.uniforms.uSampler = input; + filter.uniforms.filterGlobals = this.globalUniforms; + + // TODO make it so that the order of this does not matter.. + // because it does at the moment cos of global uniforms. + // they need to get resynced + + renderer.state.setState(filter.state); + renderer.shader.bind(filter); + + if (filter.legacy) + { + this.quadUv.map(input._frame, input.filterFrame); + + renderer.geometry.bind(this.quadUv); + renderer.geometry.draw(DRAW_MODES.TRIANGLES); + } + else + { + renderer.geometry.bind(this.quad); + renderer.geometry.draw(DRAW_MODES.TRIANGLE_STRIP); + } + } + + /** + * Calculates the mapped matrix. + * + * TODO playing around here.. this is temporary - (will end up in the shader) + * this returns a matrix that will normalise map filter cords in the filter to screen space + * + * @param {PIXI.Matrix} outputMatrix - the matrix to output to. + * @return {PIXI.Matrix} The mapped matrix. + */ + calculateScreenSpaceMatrix(outputMatrix) + { + const currentState = this.activeState; + + return filterTransforms.calculateScreenSpaceMatrix( + outputMatrix, + currentState.sourceFrame, + currentState.destinationFrame + ); + } + + /** + * This will map the filter coord so that a texture can be used based on the transform of a sprite + * + * @param {PIXI.Matrix} outputMatrix - The matrix to output to. + * @param {PIXI.Sprite} sprite - The sprite to map to. + * @return {PIXI.Matrix} The mapped matrix. + */ + calculateSpriteMatrix(outputMatrix, sprite) + { + const currentState = this.activeState; + + return filterTransforms.calculateSpriteMatrix( + outputMatrix, + currentState.sourceFrame, + currentState.destinationFrame, + sprite + ); + } + + /** + * Destroys this Filter System. + * + * @param {boolean} [contextLost=false] context was lost, do not free shaders + * + */ + destroy(contextLost = false) + { + if (!contextLost) + { + this.emptyPool(); + } + else + { + this.texturePool = {}; + } + } + + /** + * Gets a Power-of-Two render texture. + * + * TODO move to a seperate class could be on renderer? + * also - could cause issue with multiple contexts? + * + * @private + * @param {WebGLRenderingContext} gl - The webgl rendering context + * @param {number} minWidth - The minimum width of the render target. + * @param {number} minHeight - The minimum height of the render target. + * @param {number} resolution - The resolution of the render target. + * @return {PIXI.RenderTarget} The new render target. + */ + getPotFilterTexture(minWidth, minHeight, resolution) + { + minWidth = bitTwiddle.nextPow2(minWidth); + minHeight = bitTwiddle.nextPow2(minHeight); + resolution = resolution || 1; + + const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + + if (!this.texturePool[key]) + { + this.texturePool[key] = []; + } + + let renderTexture = this.texturePool[key].pop(); + + if (!renderTexture) + { + // temporary bypass cache.. + // internally - this will cause a texture to be bound.. + renderTexture = RenderTexture.create({ + width: minWidth, + height: minHeight, + resolution, + }); + } + + return renderTexture; + } + + /** + * Gets extra render texture to use inside current filter + * + * @param {number} resolution resolution of the renderTexture + * @returns {PIXI.RenderTexture} + */ + getFilterTexture(resolution) + { + const rt = this.activeState.renderTexture; + + const filterTexture = this.getPotFilterTexture(rt.width, rt.height, resolution || rt.baseTexture.resolution); + + filterTexture.filterFrame = rt.filterFrame; + + return filterTexture; + } + + /** + * Frees a render texture back into the pool. + * + * @param {PIXI.RenderTarget} renderTexture - The renderTarget to free + */ + returnFilterTexture(renderTexture) + { + renderTexture.filterFrame = null; + + const base = renderTexture.baseTexture; + + const minWidth = base.width; + const minHeight = base.height; + + const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + + this.texturePool[key].push(renderTexture); + } + + /** + * Empties the texture pool. + * + */ + emptyPool() + { + for (const i in this.texturePool) + { + const textures = this.texturePool[i]; + + if (textures) + { + for (let j = 0; j < textures.length; j++) + { + textures[j].destroy(true); + } + } + } + + this.texturePool = {}; + } +} diff --git a/packages/core/src/filters/defaultFilter.frag b/packages/core/src/filters/defaultFilter.frag new file mode 100644 index 0000000..a14c1be --- /dev/null +++ b/packages/core/src/filters/defaultFilter.frag @@ -0,0 +1,22 @@ +varying vec2 vTextureCoord; +varying vec2 vFilterCoord; + +uniform sampler2D uSampler; +uniform sampler2D filterSampler; + +void main(void){ + vec4 masky = texture2D(filterSampler, vFilterCoord); + vec4 sample = texture2D(uSampler, vTextureCoord); + vec4 color; + if(mod(vFilterCoord.x, 1.0) > 0.5) + { + color = vec4(1.0, 0.0, 0.0, 1.0); + } + else + { + color = vec4(0.0, 1.0, 0.0, 1.0); + } + // gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0); + gl_FragColor = mix(sample, masky, 0.5); + gl_FragColor *= sample.a; +} diff --git a/packages/core/src/filters/defaultFilter.vert b/packages/core/src/filters/defaultFilter.vert new file mode 100644 index 0000000..e1febca --- /dev/null +++ b/packages/core/src/filters/defaultFilter.vert @@ -0,0 +1,14 @@ +attribute vec2 aVertexPosition; +attribute vec2 aTextureCoord; + +uniform mat3 projectionMatrix; +uniform mat3 filterMatrix; + +varying vec2 vTextureCoord; +varying vec2 vFilterCoord; + +void main(void){ + gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); + vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy; + vTextureCoord = aTextureCoord ; +} diff --git a/packages/core/src/filters/filterTransforms.js b/packages/core/src/filters/filterTransforms.js new file mode 100644 index 0000000..87a3cd6 --- /dev/null +++ b/packages/core/src/filters/filterTransforms.js @@ -0,0 +1,50 @@ +import { Matrix } from '@pixi/math'; + +/** + * Calculates the mapped matrix + * @param filterArea {Rectangle} The filter area + * @param sprite {Sprite} the target sprite + * @param outputMatrix {Matrix} @alvin + * @private + */ +// this returns a matrix that will normalise map filter cords in the filter to screen space +export function calculateScreenSpaceMatrix(outputMatrix, filterArea, textureSize) +{ + // TODO unwrap? + const mappedMatrix = outputMatrix.identity(); + + mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); + + mappedMatrix.scale(textureSize.width, textureSize.height); + + return mappedMatrix; +} + +export function calculateNormalizedScreenSpaceMatrix(outputMatrix, filterArea, textureSize) +{ + const mappedMatrix = outputMatrix.identity(); + + mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); + + const translateScaleX = (textureSize.width / filterArea.width); + const translateScaleY = (textureSize.height / filterArea.height); + + mappedMatrix.scale(translateScaleX, translateScaleY); + + return mappedMatrix; +} + +// this will map the filter coord so that a texture can be used based on the transform of a sprite +export function calculateSpriteMatrix(outputMatrix, filterArea, textureSize, sprite) +{ + const orig = sprite._texture.orig; + const mappedMatrix = outputMatrix.set(textureSize.width, 0, 0, textureSize.height, filterArea.x, filterArea.y); + const worldTransform = sprite.worldTransform.copyTo(Matrix.TEMP_MATRIX); + + worldTransform.invert(); + mappedMatrix.prepend(worldTransform); + mappedMatrix.scale(1.0 / orig.width, 1.0 / orig.height); + mappedMatrix.translate(sprite.anchor.x, sprite.anchor.y); + + return mappedMatrix; +} diff --git a/packages/core/src/filters/spriteMask/SpriteMaskFilter.js b/packages/core/src/filters/spriteMask/SpriteMaskFilter.js new file mode 100644 index 0000000..91697b7 --- /dev/null +++ b/packages/core/src/filters/spriteMask/SpriteMaskFilter.js @@ -0,0 +1,63 @@ +import Filter from '../Filter'; +import { Matrix } from '@pixi/math'; +import vertex from './spriteMaskFilter.vert'; +import fragment from './spriteMaskFilter.frag'; +import { default as TextureMatrix } from '../../textures/TextureMatrix'; + +/** + * The SpriteMaskFilter class + * + * @class + * @extends PIXI.Filter + * @memberof PIXI + */ +export default class SpriteMaskFilter extends Filter +{ + /** + * @param {PIXI.Sprite} sprite - the target sprite + */ + constructor(sprite) + { + const maskMatrix = new Matrix(); + + super(vertex, fragment); + + sprite.renderable = false; + + this.maskSprite = sprite; + this.maskMatrix = maskMatrix; + } + + /** + * Applies the filter + * + * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + */ + apply(filterManager, input, output) + { + const maskSprite = this.maskSprite; + const tex = this.maskSprite.texture; + + if (!tex.valid) + { + return; + } + if (!tex.transform) + { + // margin = 0.0, let it bleed a bit, shader code becomes easier + // assuming that atlas textures were made with 1-pixel padding + tex.transform = new TextureMatrix(tex, 0.0); + } + tex.transform.update(); + + this.uniforms.mask = tex; + this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite) + .prepend(tex.transform.mapCoord); + this.uniforms.alpha = maskSprite.worldAlpha; + this.uniforms.maskClamp = tex.transform.uClampFrame; + + filterManager.applyFilter(this, input, output); + } +} diff --git a/packages/core/src/filters/spriteMask/spriteMaskFilter.frag b/packages/core/src/filters/spriteMask/spriteMaskFilter.frag new file mode 100644 index 0000000..0e4aef8 --- /dev/null +++ b/packages/core/src/filters/spriteMask/spriteMaskFilter.frag @@ -0,0 +1,23 @@ +varying vec2 vMaskCoord; +varying vec2 vTextureCoord; + +uniform sampler2D uSampler; +uniform sampler2D mask; +uniform float alpha; +uniform vec4 maskClamp; + +void main(void) +{ + float clip = step(3.5, + step(maskClamp.x, vMaskCoord.x) + + step(maskClamp.y, vMaskCoord.y) + + step(vMaskCoord.x, maskClamp.z) + + step(vMaskCoord.y, maskClamp.w)); + + vec4 original = texture2D(uSampler, vTextureCoord); + vec4 masky = texture2D(mask, vMaskCoord); + + original *= (masky.r * masky.a * alpha * clip); + + gl_FragColor = original; +} diff --git a/packages/core/src/filters/spriteMask/spriteMaskFilter.vert b/packages/core/src/filters/spriteMask/spriteMaskFilter.vert new file mode 100644 index 0000000..d23a30c --- /dev/null +++ b/packages/core/src/filters/spriteMask/spriteMaskFilter.vert @@ -0,0 +1,16 @@ +attribute vec2 aVertexPosition; +attribute vec2 aTextureCoord; + +uniform mat3 projectionMatrix; +uniform mat3 otherMatrix; + +varying vec2 vMaskCoord; +varying vec2 vTextureCoord; + +void main(void) +{ + gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); + + vTextureCoord = aTextureCoord; + vMaskCoord = ( otherMatrix * vec3( aTextureCoord, 1.0) ).xy; +} diff --git a/packages/core/src/framebuffer/FrameBuffer.js b/packages/core/src/framebuffer/FrameBuffer.js new file mode 100644 index 0000000..84e9fcc --- /dev/null +++ b/packages/core/src/framebuffer/FrameBuffer.js @@ -0,0 +1,106 @@ +import Texture from '../textures/BaseTexture'; +import { FORMATS, TYPES } from '@pixi/constants'; + +/** + * Frame buffer + * @class + * @memberof PIXI + */ +export default class FrameBuffer +{ + constructor(width, height) + { + this.width = width || 100; + this.height = height || 100; + + this.stencil = false; + this.depth = false; + + this.dirtyId = 0; + this.dirtyFormat = 0; + this.dirtySize = 0; + + this.depthTexture = null; + this.colorTextures = []; + + this.glFrameBuffers = {}; + } + + get colorTexture() + { + return this.colorTextures[0]; + } + + addColorTexture(index, texture) + { + // TODO add some validation to the texture - same width / height etc? + this.colorTextures[index || 0] = texture || new Texture(null, { scaleMode: 0, + resolution: 1, + mipmap: false, + width: this.width, + height: this.height });// || new Texture(); + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + addDepthTexture(texture) + { + /* eslint-disable max-len */ + this.depthTexture = texture || new Texture(null, { scaleMode: 0, + resolution: 1, + width: this.width, + height: this.height, + mipmap: false, + format: FORMATS.DEPTH_COMPONENT, + type: TYPES.UNSIGNED_SHORT });// UNSIGNED_SHORT; + /* eslint-disable max-len */ + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + enableDepth() + { + this.depth = true; + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + enableStencil() + { + this.stencil = true; + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + resize(width, height) + { + if (width === this.width && height === this.height) return; + + this.width = width; + this.height = height; + + this.dirtyId++; + this.dirtySize++; + + for (let i = 0; i < this.colorTextures.length; i++) + { + this.colorTextures[i].setSize(width, height); + } + + if (this.depthTexture) + { + this.depthTexture.setSize(width, height); + } + } +} diff --git a/packages/core/src/framebuffer/FramebufferSystem.js b/packages/core/src/framebuffer/FramebufferSystem.js new file mode 100644 index 0000000..2cc2437 --- /dev/null +++ b/packages/core/src/framebuffer/FramebufferSystem.js @@ -0,0 +1,246 @@ +import System from '../System'; +import { Rectangle } from '@pixi/math'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class FramebufferSystem extends System +{ + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + contextChange() + { + this.gl = this.renderer.gl; + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + this.current = null; + this.viewport = new Rectangle(); + + this.drawBufferExtension = this.renderer.context.extensions.drawBuffers; + } + + bind(framebuffer, frame) + { + const gl = this.gl; + + this.current = framebuffer; + + if (framebuffer) + { + // TODO cacheing layer! + + const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID] || this.initFramebuffer(framebuffer); + + gl.bindFramebuffer(gl.FRAMEBUFFER, fbo.framebuffer); + // makesure all textures are unbound.. + + // now check for updates... + if (fbo.dirtyId !== framebuffer.dirtyId) + { + fbo.dirtyId = framebuffer.dirtyId; + + if (fbo.dirtyFormat !== framebuffer.dirtyFormat) + { + fbo.dirtyFormat = framebuffer.dirtyFormat; + this.updateFramebuffer(framebuffer); + } + else if (fbo.dirtySize !== framebuffer.dirtySize) + { + fbo.dirtySize = framebuffer.dirtySize; + this.resizeFramebuffer(framebuffer); + } + } + + for (let i = 0; i < framebuffer.colorTextures.length; i++) + { + if (framebuffer.colorTextures[i].texturePart) + { + this.renderer.texture.unbind(framebuffer.colorTextures[i].texture); + } + else + { + this.renderer.texture.unbind(framebuffer.colorTextures[i]); + } + } + + if (framebuffer.depthTexture) + { + this.renderer.texture.unbind(framebuffer.depthTexture); + } + + if (frame) + { + this.setViewport(frame.x, frame.y, frame.width, frame.height); + } + else + { + this.setViewport(0, 0, framebuffer.width, framebuffer.height); + } + } + else + { + gl.bindFramebuffer(gl.FRAMEBUFFER, null); + + if (frame) + { + this.setViewport(frame.x, frame.y, frame.width, frame.height); + } + else + { + this.setViewport(0, 0, this.renderer.width, this.renderer.height); + } + } + } + + setViewport(x, y, width, height) + { + const v = this.viewport; + + if (v.width !== width || v.height !== height || v.x !== x || v.y !== y) + { + v.x = x; + v.y = y; + v.width = width; + v.height = height; + + this.gl.viewport(x, y, width, height); + } + } + + get size() + { + if (this.current) + { + // TODO store temp + return { x: 0, y: 0, width: this.current.width, height: this.current.height }; + } + + return { x: 0, y: 0, width: this.renderer.width, height: this.renderer.height }; + } + + clear(r, g, b, a) + { + const gl = this.gl; + + // TODO clear color can be set only one right? + gl.clearColor(r, g, b, a); + gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); + } + + // private functions... + + initFramebuffer(framebuffer) + { + const gl = this.gl; + + // TODO - make this a class? + const fbo = { + framebuffer: gl.createFramebuffer(), + stencil: null, + dirtyId: 0, + dirtyFormat: 0, + dirtySize: 0, + }; + + framebuffer.glFrameBuffers[this.CONTEXT_UID] = fbo; + + return fbo; + } + + resizeFramebuffer(framebuffer) + { + const gl = this.gl; + + if (framebuffer.stencil || framebuffer.depth) + { + gl.bindRenderbuffer(gl.RENDERBUFFER, this.stencil); + gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); + } + } + + updateFramebuffer(framebuffer) + { + const gl = this.gl; + + const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID]; + + // bind the color texture + const colorTextures = framebuffer.colorTextures; + + let count = colorTextures.length; + + if (!this.drawBufferExtension) + { + count = Math.min(count, 1); + } + + const activeTextures = []; + + for (let i = 0; i < count; i++) + { + const texture = framebuffer.colorTextures[i]; + + if (texture.texturePart) + { + this.renderer.texture.bind(texture.texture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.COLOR_ATTACHMENT0 + i, + gl.TEXTURE_CUBE_MAP_NEGATIVE_X + texture.side, + texture.texture._glTextures[this.CONTEXT_UID].texture, + 0); + } + else + { + this.renderer.texture.bind(texture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.COLOR_ATTACHMENT0 + i, + gl.TEXTURE_2D, + texture._glTextures[this.CONTEXT_UID].texture, + 0); + } + + activeTextures.push(gl.COLOR_ATTACHMENT0 + i); + } + + if (this.drawBufferExtension && activeTextures.length > 1) + { + this.drawBufferExtension.drawBuffersWEBGL(activeTextures); + } + + if (framebuffer.depthTexture) + { + const depthTextureExt = this.renderer.context.extensions.depthTexture; + + if (depthTextureExt) + { + const depthTexture = framebuffer.depthTexture; + + this.renderer.texture.bind(depthTexture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.DEPTH_ATTACHMENT, + gl.TEXTURE_2D, + depthTexture._glTextures[this.CONTEXT_UID].texture, + 0); + } + } + + if (framebuffer.stencil || framebuffer.depth) + { + fbo.stencil = gl.createRenderbuffer(); + + gl.bindRenderbuffer(gl.RENDERBUFFER, fbo.stencil); + + // TODO.. this is depth AND stencil? + gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, fbo.stencil); + gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); + // fbo.enableStencil(); + } + } +} diff --git a/packages/core/src/geometry/GLBuffer.js b/packages/core/src/geometry/GLBuffer.js new file mode 100644 index 0000000..0477e8a --- /dev/null +++ b/packages/core/src/geometry/GLBuffer.js @@ -0,0 +1,9 @@ +export default class GLBuffer +{ + constructor(buffer) + { + this.buffer = buffer; + this.updateID = -1; + this.byteLength = -1; + } +} diff --git a/packages/core/src/geometry/GeometrySystem.js b/packages/core/src/geometry/GeometrySystem.js new file mode 100644 index 0000000..959f827 --- /dev/null +++ b/packages/core/src/geometry/GeometrySystem.js @@ -0,0 +1,388 @@ +import System from '../System'; +import GLBuffer from './GLBuffer'; + +const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class GeometrySystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this._activeGeometry = null; + this._activeVao = null; + + this.hasVao = true; + this.hasInstance = true; + } + + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + contextChange() + { + const gl = this.gl = this.renderer.gl; + + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + + // webgl2 + if (!gl.createVertexArray) + { + // webgl 1! + let nativeVaoExtension = this.renderer.context.extensions.vertexArrayObject; + + if (this.renderer.legacy) + { + nativeVaoExtension = null; + } + + if (nativeVaoExtension) + { + gl.createVertexArray = () => + nativeVaoExtension.createVertexArrayOES(); + + gl.bindVertexArray = (vao) => + nativeVaoExtension.bindVertexArrayOES(vao); + + gl.deleteVertexArray = (vao) => + nativeVaoExtension.deleteVertexArrayOES(vao); + } + else + { + this.hasVao = false; + gl.createVertexArray = () => + { + // empty + }; + + gl.bindVertexArray = () => + { + // empty + }; + + gl.deleteVertexArray = () => + { + // empty + }; + } + } + + if (!gl.vertexAttribDivisor) + { + const instanceExt = gl.getExtension('ANGLE_instanced_arrays'); + + if (instanceExt) + { + gl.vertexAttribDivisor = (a, b) => + instanceExt.vertexAttribDivisorANGLE(a, b); + + gl.drawElementsInstanced = (a, b, c, d, e) => + instanceExt.drawElementsInstancedANGLE(a, b, c, d, e); + + gl.drawArraysInstanced = (a, b, c, d) => + instanceExt.drawArraysInstancedANGLE(a, b, c, d); + } + else + { + this.hasInstance = false; + } + } + } + + /** + * Binds geometry so that is can be drawn. Creating a Vao if required + * @private + * @param {PIXI.Geometry} geometry instance of geometry to bind + */ + bind(geometry, shader) + { + shader = shader || this.renderer.shader.shader; + + const gl = this.gl; + + // not sure the best way to address this.. + // currently different shaders require different VAOs for the same geometry + // Still mulling over the best way to solve this one.. + // will likely need to modify the shader attribute locations at run time! + let vaos = geometry.glVertexArrayObjects[this.CONTEXT_UID]; + + if (!vaos) + { + geometry.glVertexArrayObjects[this.CONTEXT_UID] = vaos = {}; + } + + const vao = vaos[shader.program.id] || this.initGeometryVao(geometry, shader.program); + + this._activeGeometry = geometry; + + if (this._activeVao !== vao) + { + this._activeVao = vao; + + if (this.hasVao) + { + gl.bindVertexArray(vao); + } + else + { + this.activateVao(geometry, shader.program); + } + } + + // TODO - optimise later! + // don't need to loop through if nothing changed! + // maybe look to add an 'autoupdate' to geometry? + this.updateBuffers(); + } + + reset() + { + this.unbind(); + } + + updateBuffers() + { + const geometry = this._activeGeometry; + const gl = this.gl; + + for (let i = 0; i < geometry.buffers.length; i++) + { + const buffer = geometry.buffers[i]; + + const glBuffer = buffer._glBuffers[this.CONTEXT_UID]; + + if (buffer._updateID !== glBuffer.updateID) + { + glBuffer.updateID = buffer._updateID; + + // TODO can cache this on buffer! maybe added a getter / setter? + const type = buffer.index ? gl.ELEMENT_ARRAY_BUFFER : gl.ARRAY_BUFFER; + const drawType = buffer.static ? gl.STATIC_DRAW : gl.DYNAMIC_DRAW; + + gl.bindBuffer(type, glBuffer.buffer); + + if (glBuffer.byteLength >= buffer.data.byteLength) + { + // offset is always zero for now! + gl.bufferSubData(type, 0, buffer.data); + } + else + { + gl.bufferData(type, buffer.data, drawType); + } + } + } + } + + checkCompatability(geometry, program) + { + // geometry must have at least all the attributes that the shader requires. + const geometryAttributes = geometry.attributes; + const shaderAttributes = program.attributeData; + + for (const j in shaderAttributes) + { + if (!geometryAttributes[j]) + { + throw new Error(`shader and geometry incompatible, geometry missing the "${j}" attribute`); + } + } + } + + /** + * Creates a Vao with the same structure as the geometry and stores it on the geometry. + * @private + * @param {PIXI.Geometry} geometry instance of geometry to to generate Vao for + */ + initGeometryVao(geometry, program) + { + this.checkCompatability(geometry, program); + + const gl = this.gl; + const CONTEXT_UID = this.CONTEXT_UID; + const buffers = geometry.buffers; + const attributes = geometry.attributes; + + const tempStride = {}; + const tempStart = {}; + + for (const j in buffers) + { + tempStride[j] = 0; + tempStart[j] = 0; + } + + for (const j in attributes) + { + if (!attributes[j].size && program.attributeData[j]) + { + attributes[j].size = program.attributeData[j].size; + } + + tempStride[attributes[j].buffer] += attributes[j].size * byteSizeMap[attributes[j].type]; + } + + for (const j in attributes) + { + const attribute = attributes[j]; + const attribSize = attribute.size; + + if (attribute.stride === undefined) + { + if (tempStride[attribute.buffer] === attribSize * byteSizeMap[attribute.type]) + { + attribute.stride = 0; + } + else + { + attribute.stride = tempStride[attribute.buffer]; + } + } + + if (attribute.start === undefined) + { + attribute.start = tempStart[attribute.buffer]; + + tempStart[attribute.buffer] += attribSize * byteSizeMap[attribute.type]; + } + } + + // first update - and create the buffers! + // only create a gl buffer if it actually gets + for (let i = 0; i < buffers.length; i++) + { + const buffer = buffers[i]; + + if (!buffer._glBuffers[CONTEXT_UID]) + { + buffer._glBuffers[CONTEXT_UID] = new GLBuffer(gl.createBuffer()); + } + } + + // TODO - maybe make this a data object? + // lets wait to see if we need to first! + const vao = gl.createVertexArray(); + + gl.bindVertexArray(vao); + + this.activateVao(geometry, program); + + gl.bindVertexArray(null); + + // add it to the cache! + geometry.glVertexArrayObjects[this.CONTEXT_UID][program.id] = vao; + + return vao; + } + + activateVao(geometry, program) + { + const gl = this.gl; + const CONTEXT_UID = this.CONTEXT_UID; + const buffers = geometry.buffers; + const attributes = geometry.attributes; + + if (geometry.indexBuffer) + { + // first update the index buffer if we have one.. + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, geometry.indexBuffer._glBuffers[CONTEXT_UID].buffer); + } + + let lastBuffer = null; + + // add a new one! + for (const j in attributes) + { + const attribute = attributes[j]; + const buffer = buffers[attribute.buffer]; + const glBuffer = buffer._glBuffers[CONTEXT_UID]; + + if (program.attributeData[j]) + { + if (lastBuffer !== glBuffer) + { + gl.bindBuffer(gl.ARRAY_BUFFER, glBuffer.buffer); + + lastBuffer = glBuffer; + } + + const location = program.attributeData[j].location; + + // TODO introduce state again + // we can optimise this for older devices that have no VAOs + gl.enableVertexAttribArray(location); + + gl.vertexAttribPointer(location, + attribute.size, + attribute.type || gl.FLOAT, + attribute.normalized, + attribute.stride, + attribute.start); + + if (attribute.instance) + { + // TODO calculate instance count based of this... + if (this.hasInstance) + { + gl.vertexAttribDivisor(location, 1); + } + else + { + throw new Error('geometry error, GPU Instancing is not supported on this device'); + } + } + } + } + } + + draw(type, size, start, instanceCount) + { + const gl = this.gl; + const geometry = this._activeGeometry; + + // TODO.. this should not change so maybe cache the function? + + if (geometry.indexBuffer) + { + if (geometry.instanced) + { + /* eslint-disable max-len */ + gl.drawElementsInstanced(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2, instanceCount || 1); + /* eslint-enable max-len */ + } + else + { + gl.drawElements(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2); + } + } + else + if (geometry.instanced) + { + // TODO need a better way to calculate size.. + gl.drawArraysInstanced(type, start, size || geometry.getSize(), instanceCount || 1); + } + else + { + gl.drawArrays(type, start, size || geometry.getSize()); + } + + return this; + } + + unbind() + { + this.gl.bindVertexArray(null); + this._activeVao = null; + this._activeGeometry = null; + } +} diff --git a/packages/core/src/geometry/utils/setVertexAttribArrays.js b/packages/core/src/geometry/utils/setVertexAttribArrays.js new file mode 100644 index 0000000..23b6959 --- /dev/null +++ b/packages/core/src/geometry/utils/setVertexAttribArrays.js @@ -0,0 +1,54 @@ +// var GL_MAP = {}; + +/** + * @param gl {WebGLRenderingContext} The current WebGL context + * @param attribs {*} + * @param state {*} + */ +export default function setVertexAttribArrays(gl, attribs, state) +{ + let i; + + if (state) + { + const tempAttribState = state.tempAttribState; + const attribState = state.attribState; + + for (i = 0; i < tempAttribState.length; i++) + { + tempAttribState[i] = false; + } + + // set the new attribs + for (i = 0; i < attribs.length; i++) + { + tempAttribState[attribs[i].attribute.location] = true; + } + + for (i = 0; i < attribState.length; i++) + { + if (attribState[i] !== tempAttribState[i]) + { + attribState[i] = tempAttribState[i]; + + if (state.attribState[i]) + { + gl.enableVertexAttribArray(i); + } + else + { + gl.disableVertexAttribArray(i); + } + } + } + } + else + { + for (i = 0; i < attribs.length; i++) + { + const attrib = attribs[i]; + + gl.enableVertexAttribArray(attrib.attribute.location); + } + } +} diff --git a/packages/core/src/index.js b/packages/core/src/index.js index 58c456a..fd1f547 100644 --- a/packages/core/src/index.js +++ b/packages/core/src/index.js @@ -1,30 +1,30 @@ import * as resources from './textures/resources'; -import * as systems from './renderers/systems'; +import * as systems from './systems'; -export { resources }; export { systems }; +export { resources }; -export { default as Renderer } from './renderers/Renderer'; -export { default as AbstractRenderer } from './renderers/AbstractRenderer'; -export { default as FrameBuffer } from './textures/FrameBuffer'; +export { default as System } from './System'; +export { default as Renderer } from './Renderer'; +export { default as AbstractRenderer } from './AbstractRenderer'; +export { default as FrameBuffer } from './framebuffer/FrameBuffer'; export { default as CubeTexture } from './textures/CubeTexture'; export { default as BaseTexture } from './textures/BaseTexture'; export { default as Texture } from './textures/Texture'; export { default as TextureMatrix } from './textures/TextureMatrix'; -export { default as RenderTexture } from './textures/RenderTexture'; -export { default as BaseRenderTexture } from './textures/BaseRenderTexture'; +export { default as RenderTexture } from './renderTexture/RenderTexture'; +export { default as BaseRenderTexture } from './renderTexture/BaseRenderTexture'; export { default as TextureUvs } from './textures/TextureUvs'; -export { default as State } from './renderers/State'; -export { default as ObjectRenderer } from './renderers/utils/ObjectRenderer'; -export { default as Quad } from './renderers/utils/Quad'; -export { default as QuadUv } from './renderers/utils/QuadUv'; -export { default as checkMaxIfStatmentsInShader } from './renderers/utils/checkMaxIfStatmentsInShader'; +export { default as State } from './state/State'; +export { default as ObjectRenderer } from './batch/ObjectRenderer'; +export { default as Quad } from './utils/Quad'; +export { default as QuadUv } from './utils/QuadUv'; +export { default as checkMaxIfStatmentsInShader } from './shader/utils/checkMaxIfStatmentsInShader'; export { default as Shader } from './shader/Shader'; export { default as Program } from './shader/Program'; export { default as UniformGroup } from './shader/UniformGroup'; -export { default as SpriteMaskFilter } from './renderers/filters/spriteMask/SpriteMaskFilter'; -export { default as Filter } from './renderers/filters/Filter'; +export { default as SpriteMaskFilter } from './filters/spriteMask/SpriteMaskFilter'; +export { default as Filter } from './filters/Filter'; export { default as Attribute } from './geometry/Attribute'; export { default as Buffer } from './geometry/Buffer'; export { default as Geometry } from './geometry/Geometry'; - diff --git a/packages/core/src/mask/MaskSystem.js b/packages/core/src/mask/MaskSystem.js new file mode 100644 index 0000000..99174c2 --- /dev/null +++ b/packages/core/src/mask/MaskSystem.js @@ -0,0 +1,199 @@ +import System from '../System'; +import AlphaMaskFilter from '../filters/spriteMask/SpriteMaskFilter'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class MaskSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + // TODO - we don't need both! + this.scissor = false; + this.scissorData = null; + this.scissorRenderTarget = null; + + this.enableScissor = false; + + this.alphaMaskPool = []; + this.alphaMaskIndex = 0; + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.DisplayObject} target - Display Object to push the mask to + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + push(target, maskData) + { + // TODO the root check means scissor rect will not + // be used on render textures more info here: + // https://github.com/pixijs/pixi.js/pull/3545 + + if (maskData.texture) + { + this.pushSpriteMask(target, maskData); + } + else if (this.enableScissor + && !this.scissor + && this.renderer._activeRenderTarget.root + && !this.renderer.stencil.stencilMaskStack.length + && maskData.isFastRect()) + { + const matrix = maskData.worldTransform; + + let rot = Math.atan2(matrix.b, matrix.a); + + // use the nearest degree! + rot = Math.round(rot * (180 / Math.PI)); + + if (rot % 90) + { + this.pushStencilMask(maskData); + } + else + { + this.pushScissorMask(target, maskData); + } + } + else + { + this.pushStencilMask(maskData); + } + } + + /** + * Removes the last mask from the mask stack and doesn't return it. + * + * @param {PIXI.DisplayObject} target - Display Object to pop the mask from + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + pop(target, maskData) + { + if (maskData.texture) + { + this.popSpriteMask(target, maskData); + } + else if (this.enableScissor && !this.renderer.stencil.stencilMaskStack.length) + { + this.popScissorMask(target, maskData); + } + else + { + this.popStencilMask(target, maskData); + } + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.RenderTarget} target - Display Object to push the sprite mask to + * @param {PIXI.Sprite} maskData - Sprite to be used as the mask + */ + pushSpriteMask(target, maskData) + { + let alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; + + if (!alphaMaskFilter) + { + alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; + } + + alphaMaskFilter[0].resolution = this.renderer.resolution; + alphaMaskFilter[0].maskSprite = maskData; + + // TODO - may cause issues! + target.filterArea = maskData.getBounds(true); + + this.renderer.filterSystem.pushFilter(target, alphaMaskFilter); + + this.alphaMaskIndex++; + } + + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popSpriteMask() + { + this.renderer.filterSystem.popFilter(); + this.alphaMaskIndex--; + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + pushStencilMask(maskData) + { + this.renderer.batch.flush(); + this.renderer.stencil.pushStencil(maskData); + } + + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popStencilMask() + { + // this.renderer.currentRenderer.stop(); + this.renderer.stencil.popStencil(); + } + + /** + * + * @param {PIXI.DisplayObject} target - Display Object to push the mask to + * @param {PIXI.Graphics} maskData - The masking data. + */ + pushScissorMask(target, maskData) + { + maskData.renderable = true; + + const renderTarget = this.renderer._activeRenderTarget; + + const bounds = maskData.getBounds(); + + bounds.fit(renderTarget.size); + maskData.renderable = false; + + this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); + + const resolution = this.renderer.resolution; + + this.renderer.gl.scissor( + bounds.x * resolution, + (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, + bounds.width * resolution, + bounds.height * resolution + ); + + this.scissorRenderTarget = renderTarget; + this.scissorData = maskData; + this.scissor = true; + } + + /** + * + * + */ + popScissorMask() + { + this.scissorRenderTarget = null; + this.scissorData = null; + this.scissor = false; + + // must be scissor! + const gl = this.renderer.gl; + + gl.disable(gl.SCISSOR_TEST); + } +} diff --git a/packages/core/src/mask/StencilSystem.js b/packages/core/src/mask/StencilSystem.js new file mode 100644 index 0000000..62796a5 --- /dev/null +++ b/packages/core/src/mask/StencilSystem.js @@ -0,0 +1,133 @@ +import System from '../System'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class StencilSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + this.stencilMaskStack = []; + } + + /** + * Changes the mask stack that is used by this System. + * + * @param {PIXI.Graphics[]} stencilMaskStack - The mask stack + */ + setMaskStack(stencilMaskStack) + { + const gl = this.renderer.gl; + + if (stencilMaskStack.length !== this.stencilMaskStack.length) + { + if (stencilMaskStack.length === 0) + { + gl.disable(gl.STENCIL_TEST); + } + else + { + gl.enable(gl.STENCIL_TEST); + } + } + + this.stencilMaskStack = stencilMaskStack; + } + + /** + * Applies the Mask and adds it to the current stencil stack. @alvin + * + * @param {PIXI.Graphics} graphics - The mask + */ + pushStencil(graphics) + { + this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); + + // this.renderer._activeRenderTarget.attachStencilBuffer(); + + const gl = this.renderer.gl; + const prevMaskCount = this.stencilMaskStack.length; + + if (prevMaskCount === 0) + { + gl.enable(gl.STENCIL_TEST); + } + + this.stencilMaskStack.push(graphics); + + // Increment the refference stencil value where the new mask overlaps with the old ones. + gl.colorMask(false, false, false, false); + gl.stencilFunc(gl.EQUAL, prevMaskCount, this._getBitwiseMask()); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.INCR); + this.renderer.plugins.graphics.render(graphics); + + this._useCurrent(); + } + + /** + * Removes the last mask from the stencil stack. @alvin + */ + popStencil() + { + this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); + + const gl = this.renderer.gl; + const graphics = this.stencilMaskStack.pop(); + + if (this.stencilMaskStack.length === 0) + { + // the stack is empty! + gl.disable(gl.STENCIL_TEST); + gl.clear(gl.STENCIL_BUFFER_BIT); + gl.clearStencil(0); + } + else + { + // Decrement the refference stencil value where the popped mask overlaps with the other ones + gl.colorMask(false, false, false, false); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.DECR); + this.renderer.plugins.graphics.render(graphics); + + this._useCurrent(); + } + } + + /** + * Setup renderer to use the current stencil data. + */ + _useCurrent() + { + const gl = this.renderer.gl; + + gl.colorMask(true, true, true, true); + gl.stencilFunc(gl.EQUAL, this.stencilMaskStack.length, this._getBitwiseMask()); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP); + } + + /** + * Fill 1s equal to the number of acitve stencil masks. + * + * @return {number} The bitwise mask. + */ + _getBitwiseMask() + { + return (1 << this.stencilMaskStack.length) - 1; + } + + /** + * Destroys the mask stack. + * + */ + destroy() + { + super.destroy(this); + + this.stencilMaskStack = null; + } +} diff --git a/packages/core/src/projection/ProjectionSystem.js b/packages/core/src/projection/ProjectionSystem.js new file mode 100644 index 0000000..6e63b73 --- /dev/null +++ b/packages/core/src/projection/ProjectionSystem.js @@ -0,0 +1,73 @@ +import System from '../System'; +import { Matrix } from '@pixi/math'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class ProjectionSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.projectionMatrix = new Matrix(); + } + + update(destinationFrame, sourceFrame, resolution, root) + { + this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; + this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; + + this.calculateProjection(this.destinationFrame, this.sourceFrame, resolution, root); + + this.renderer.globalUniforms.uniforms.projectionMatrix = this.projectionMatrix; + this.renderer.globalUniforms.update(); + } + + /** + * Updates the projection matrix based on a projection frame (which is a rectangle) + * + * @param {Rectangle} destinationFrame - The destination frame. + * @param {Rectangle} sourceFrame - The source frame. + */ + calculateProjection(destinationFrame, sourceFrame, resolution, root) + { + const pm = this.projectionMatrix; + + // I don't think we will need this line.. + // pm.identity(); + + if (!root) + { + pm.a = (1 / destinationFrame.width * 2) * resolution; + pm.d = (1 / destinationFrame.height * 2) * resolution; + + pm.tx = -1 - (sourceFrame.x * pm.a); + pm.ty = -1 - (sourceFrame.y * pm.d); + } + else + { + pm.a = (1 / destinationFrame.width * 2) * resolution; + pm.d = (-1 / destinationFrame.height * 2) * resolution; + + pm.tx = -1 - (sourceFrame.x * pm.a); + pm.ty = 1 - (sourceFrame.y * pm.d); + } + } + + /** + * Sets the transform of the active render target to the given matrix + * + * @param {PIXI.Matrix} matrix - The transformation matrix + */ + setTransform()// matrix) + { + // this._activeRenderTarget.transform = matrix; + } +} diff --git a/packages/core/src/renderTexture/BaseRenderTexture.js b/packages/core/src/renderTexture/BaseRenderTexture.js new file mode 100644 index 0000000..88eb83b --- /dev/null +++ b/packages/core/src/renderTexture/BaseRenderTexture.js @@ -0,0 +1,137 @@ +import BaseTexture from '../textures/BaseTexture'; +import FrameBuffer from '../framebuffer/FrameBuffer'; + +/** + * A BaseRenderTexture is a special texture that allows any PixiJS display object to be rendered to it. + * + * __Hint__: All DisplayObjects (i.e. Sprites) that render to a BaseRenderTexture should be preloaded + * otherwise black rectangles will be drawn instead. + * + * A BaseRenderTexture takes a snapshot of any Display Object given to its render method. The position + * and rotation of the given Display Objects is ignored. For example: + * + * ```js + * let renderer = PIXI.autoDetectRenderer(1024, 1024, { view: canvas, ratio: 1 }); + * let baseRenderTexture = new PIXI.BaseRenderTexture(renderer, 800, 600); + * let sprite = PIXI.Sprite.fromImage("spinObj_01.png"); + * + * sprite.position.x = 800/2; + * sprite.position.y = 600/2; + * sprite.anchor.x = 0.5; + * sprite.anchor.y = 0.5; + * + * baseRenderTexture.render(sprite); + * ``` + * + * The Sprite in this case will be rendered using its local transform. To render this sprite at 0,0 + * you can clear the transform + * + * ```js + * + * sprite.setTransform() + * + * let baseRenderTexture = new PIXI.BaseRenderTexture(100, 100); + * let renderTexture = new PIXI.RenderTexture(baseRenderTexture); + * + * renderer.render(sprite, renderTexture); // Renders to center of RenderTexture + * ``` + * + * @class + * @extends PIXI.BaseTexture + * @memberof PIXI + */ +export default class BaseRenderTexture extends BaseTexture +{ + /** + * @param {object} [options] + * @param {number} [options.width=100] - The width of the base render texture + * @param {number} [options.height=100] - The height of the base render texture + * @param {PIXI.SCALE_MODES} [options.scaleMode] - See {@link PIXI.SCALE_MODES} for possible values + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the texture being generated + */ + constructor(options) + { + if (typeof options === 'number') + { + /* eslint-disable prefer-rest-params */ + // Backward compatibility of signature + const width = arguments[0]; + const height = arguments[1]; + const scaleMode = arguments[2]; + const resolution = arguments[3]; + + options = { width, height, scaleMode, resolution }; + /* eslint-enable prefer-rest-params */ + } + + super(null, options); + + const { width, height } = options || {}; + + // Set defaults + this.mipmap = false; + this.width = Math.ceil(width) || 100; + this.height = Math.ceil(height) || 100; + this.valid = true; + + /** + * A map of renderer IDs to webgl renderTargets + * + * @private + * @member {object} + */ + // this._glRenderTargets = {}; + + /** + * A reference to the canvas render target (we only need one as this can be shared across renderers) + * + * @private + * @member {object} + */ + this._canvasRenderTarget = null; + + this.clearColor = [0, 0, 0, 0]; + + this.frameBuffer = new FrameBuffer(width * this.resolution, height * this.resolution) + .addColorTexture(0, this); + + // TODO - could this be added the systems? + + /** + * The data structure for the stencil masks + * + * @member {PIXI.Graphics[]} + */ + this.stencilMaskStack = []; + + /** + * The data structure for the filters + * + * @member {PIXI.Graphics[]} + */ + this.filterStack = [{}]; + } + + /** + * Resizes the BaseRenderTexture. + * + * @param {number} width - The width to resize to. + * @param {number} height - The height to resize to. + */ + resize(width, height) + { + width = Math.ceil(width); + height = Math.ceil(height); + this.frameBuffer.resize(width * this.resolution, height * this.resolution); + } + + /** + * Destroys this texture + * + */ + destroy() + { + super.destroy(true); + this.renderer = null; + } +} diff --git a/packages/core/src/renderTexture/RenderTexture.js b/packages/core/src/renderTexture/RenderTexture.js new file mode 100644 index 0000000..f1ecf1f --- /dev/null +++ b/packages/core/src/renderTexture/RenderTexture.js @@ -0,0 +1,153 @@ +import BaseRenderTexture from './BaseRenderTexture'; +import Texture from '../textures/Texture'; + +/** + * A RenderTexture is a special texture that allows any PixiJS display object to be rendered to it. + * + * __Hint__: All DisplayObjects (i.e. Sprites) that render to a RenderTexture should be preloaded + * otherwise black rectangles will be drawn instead. + * + * A RenderTexture takes a snapshot of any Display Object given to its render method. For example: + * + * ```js + * let renderer = PIXI.autoDetectRenderer(1024, 1024, { view: canvas, ratio: 1 }); + * let renderTexture = PIXI.RenderTexture.create(800, 600); + * let sprite = PIXI.Sprite.fromImage("spinObj_01.png"); + * + * sprite.position.x = 800/2; + * sprite.position.y = 600/2; + * sprite.anchor.x = 0.5; + * sprite.anchor.y = 0.5; + * + * renderer.render(sprite, renderTexture); + * ``` + * + * The Sprite in this case will be rendered using its local transform. To render this sprite at 0,0 + * you can clear the transform + * + * ```js + * + * sprite.setTransform() + * + * let renderTexture = new PIXI.RenderTexture.create(100, 100); + * + * renderer.render(sprite, renderTexture); // Renders to center of RenderTexture + * ``` + * + * @class + * @extends PIXI.Texture + * @memberof PIXI + */ +export default class RenderTexture extends Texture +{ + /** + * @param {PIXI.BaseRenderTexture} baseRenderTexture - The renderer used for this RenderTexture + * @param {PIXI.Rectangle} [frame] - The rectangle frame of the texture to show + */ + constructor(baseRenderTexture, frame) + { + // support for legacy.. + let _legacyRenderer = null; + + if (!(baseRenderTexture instanceof BaseRenderTexture)) + { + /* eslint-disable prefer-rest-params, no-console */ + const width = arguments[1]; + const height = arguments[2]; + const scaleMode = arguments[3]; + const resolution = arguments[4]; + + // we have an old render texture.. + console.warn(`Please use RenderTexture.create(${width}, ${height}) instead of the ctor directly.`); + _legacyRenderer = arguments[0]; + /* eslint-enable prefer-rest-params, no-console */ + + frame = null; + baseRenderTexture = new BaseRenderTexture({ + width, + height, + scaleMode, + resolution, + }); + } + + /** + * The base texture object that this texture uses + * + * @member {BaseTexture} + */ + super(baseRenderTexture, frame); + + this.legacyRenderer = _legacyRenderer; + + /** + * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. + * + * @member {boolean} + */ + this.valid = true; + + /** + * FilterSystem temporary storage + * @private + * @member {PIXI.Rectangle} + */ + this.filterFrame = null; + + this._updateUvs(); + } + + /** + * Resizes the RenderTexture. + * + * @param {number} width - The width to resize to. + * @param {number} height - The height to resize to. + * @param {boolean} [resizeBaseTexture=true] - Should the baseTexture.width and height values be resized as well? + */ + resize(width, height, resizeBaseTexture = true) + { + width = Math.ceil(width); + height = Math.ceil(height); + + // TODO - could be not required.. + this.valid = (width > 0 && height > 0); + + this._frame.width = this.orig.width = width; + this._frame.height = this.orig.height = height; + + if (resizeBaseTexture) + { + this.baseTexture.resize(width, height); + } + + this._updateUvs(); + } + + /** + * A short hand way of creating a render texture. + * + * @param {object} [options] - Options + * @param {number} [options.width=100] - The width of the render texture + * @param {number} [options.height=100] - The height of the render texture + * @param {number} [options.scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the texture being generated + * @return {PIXI.RenderTexture} The new render texture + */ + static create(options) + { + // fallback, old-style: create(width, height, scaleMode, resolution) + if (typeof options === 'number') + { + /* eslint-disable prefer-rest-params */ + options = { + width: options, + height: arguments[1], + scaleMode: arguments[2], + resolution: arguments[3], + }; + /* eslint-enable prefer-rest-params */ + } + + return new RenderTexture(new BaseRenderTexture(options)); + } +} diff --git a/packages/core/src/renderTexture/RenderTextureSystem.js b/packages/core/src/renderTexture/RenderTextureSystem.js new file mode 100644 index 0000000..8d0ca61 --- /dev/null +++ b/packages/core/src/renderTexture/RenderTextureSystem.js @@ -0,0 +1,115 @@ +import System from '../System'; +import { Rectangle } from '@pixi/math'; + +const tempRect = new Rectangle(); + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class RenderTextureSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.clearColor = renderer._backgroundColorRgba; + + // TODO moe this property somewhere else! + this.defaultMaskStack = []; + this.defaultFilterStack = [{}]; + + // empty render texture? + this.renderTexture = null; + + this.destinationFrame = new Rectangle(); + } + + bind(renderTexture, sourceFrame, destinationFrame) + { + // TODO - do we want this?? + if (this.renderTexture === renderTexture) return; + this.renderTexture = renderTexture; + + const renderer = this.renderer; + + if (renderTexture) + { + const baseTexture = renderTexture.baseTexture; + + if (!destinationFrame) + { + tempRect.width = baseTexture.realWidth; + tempRect.height = baseTexture.realHeight; + + destinationFrame = tempRect; + } + + if (!sourceFrame) + { + sourceFrame = destinationFrame; + } + + this.renderer.framebuffer.bind(baseTexture.frameBuffer, destinationFrame); + + this.renderer.projection.update(destinationFrame, sourceFrame, baseTexture.resolution, false); + this.renderer.stencil.setMaskStack(baseTexture.stencilMaskStack); + } + else + { + // TODO these validation checks happen deeper down.. + // thing they can be avoided.. + if (!destinationFrame) + { + tempRect.width = renderer.width; + tempRect.height = renderer.height; + + destinationFrame = tempRect; + } + + if (!sourceFrame) + { + sourceFrame = destinationFrame; + } + + renderer.framebuffer.bind(null, destinationFrame); + + // TODO store this.. + this.renderer.projection.update(destinationFrame, sourceFrame, this.renderer.resolution, true); + this.renderer.stencil.setMaskStack(this.defaultMaskStack); + } + + this.destinationFrame.copyFrom(destinationFrame); + } + + /** + * Erases the render texture and fills the drawing area with a colour + * + * @param {number} [clearColor] - The colour + * @return {PIXI.Renderer} Returns itself. + */ + clear(clearColor) + { + if (this.renderTexture) + { + clearColor = clearColor || this.renderTexture.baseTexture.clearColor; + } + else + { + clearColor = clearColor || this.clearColor; + } + + this.renderer.framebuffer.clear(clearColor[0], clearColor[1], clearColor[2], clearColor[3]); + } + + resize()// screenWidth, screenHeight) + { + // resize the root only! + this.bind(null); + } +} diff --git a/packages/core/src/renderers/AbstractRenderer.js b/packages/core/src/renderers/AbstractRenderer.js deleted file mode 100644 index 928670f..0000000 --- a/packages/core/src/renderers/AbstractRenderer.js +++ /dev/null @@ -1,353 +0,0 @@ -import { hex2string, hex2rgb } from '@pixi/utils'; -import { Matrix, Rectangle } from '@pixi/math'; -import { RENDERER_TYPE } from '@pixi/constants'; -import { settings } from '@pixi/settings'; -import { Container } from '@pixi/display'; -import RenderTexture from '../textures/RenderTexture'; -import EventEmitter from 'eventemitter3'; - -const tempMatrix = new Matrix(); - -/** - * 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 EventEmitter - * @memberof PIXI - */ -export default class AbstractRenderer extends EventEmitter -{ - // eslint-disable-next-line valid-jsdoc - /** - * @param {string} system - The name of the system this renderer is for. - * @param {object} [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.transparent=false] - If the render view is transparent, default false - * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false - * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) - * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. The - * resolution of the renderer retina would be 2. - * @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 {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when rendering, - * stopping pixel interpolation. - */ - constructor(system, options, arg2, arg3) - { - super(); - - // Support for constructor(system, screenWidth, screenHeight, options) - if (typeof options === 'number') - { - options = Object.assign({ - width: options, - height: arg2 || settings.RENDER_OPTIONS.height, - }, arg3); - } - - // 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 = RENDERER_TYPE.UNKNOWN; - - /** - * Measurements of the screen. (0, 0, screenWidth, screenHeight) - * - * Its safe to use as filterArea or hitArea for 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 1 - */ - this.resolution = options.resolution || settings.RESOLUTION; - - /** - * Whether the render view is transparent - * - * @member {boolean} - */ - this.transparent = options.transparent; - - /** - * Whether css dimensions of canvas view should be resized to screen dimensions automatically - * - * @member {boolean} - */ - this.autoResize = options.autoResize || false; - - /** - * Tracks the blend modes useful for this renderer. - * - * @member {object} - */ - this.blendModes = null; - - /** - * 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; - - /** - * If true PixiJS will Math.floor() x/y values when rendering, stopping pixel interpolation. - * Handy for crisp pixel art and speed on legacy devices. - * - * @member {boolean} - */ - this.roundPixels = options.roundPixels; - - /** - * The background color as a number. - * - * @member {number} - * @private - */ - this._backgroundColor = 0x000000; - - /** - * The background color as an [R, G, B] array. - * - * @member {number[]} - * @private - */ - this._backgroundColorRgba = [0, 0, 0, 0]; - - /** - * The background color as a string. - * - * @member {string} - * @private - */ - this._backgroundColorString = '#000000'; - - this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter - - /** - * This temporary display object used as the parent of the currently being rendered item - * - * @member {PIXI.DisplayObject} - * @private - */ - this._tempDisplayObjectParent = new Container(); - - /** - * The last root object that the renderer tried to render. - * - * @member {PIXI.DisplayObject} - * @private - */ - this._lastObjectRendered = this._tempDisplayObjectParent; - - /** - * Collection of plugins. - * @readonly - * @member {object} - */ - this.plugins = {}; - } - - /** - * Initialize the plugins. - * - * @protected - * @param {object} staticMap - The dictionary of staticly saved plugins. - */ - initPlugins(staticMap) - { - 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() - { - return this.view.width; - } - - /** - * Same as view.height, actual number of pixels in the canvas by vertical - * - * @member {number} - * @readonly - * @default 600 - */ - get height() - { - return this.view.height; - } - - /** - * Resizes the screen and canvas to the specified width and height - * Canvas dimensions are multiplied by resolution - * - * @param {number} screenWidth - the new width of the screen - * @param {number} screenHeight - the new height of the screen - */ - resize(screenWidth, screenHeight) - { - this.screen.width = screenWidth; - this.screen.height = screenHeight; - - this.view.width = screenWidth * this.resolution; - this.view.height = screenHeight * this.resolution; - - if (this.autoResize) - { - this.view.style.width = `${screenWidth}px`; - this.view.style.height = `${screenHeight}px`; - } - } - - /** - * 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 {PIXI.DisplayObject} displayObject - The displayObject the object will be generated from - * @param {number} scaleMode - Should be one of the scaleMode consts - * @param {number} resolution - The resolution / device pixel ratio of the texture being generated - * @param {PIXI.Rectangle} [region] - The region of the displayObject, that shall be rendered, - * if no region is specified, defaults to the local bounds of the displayObject. - * @return {PIXI.Texture} a texture of the graphics object - */ - generateTexture(displayObject, scaleMode, resolution, region) - { - region = region || displayObject.getLocalBounds(); - - // 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(region.width | 0, region.height | 0, scaleMode, resolution); - - tempMatrix.tx = -region.x; - tempMatrix.ty = -region.y; - - this.render(displayObject, renderTexture, false, tempMatrix, true); - - return renderTexture; - } - - /** - * Removes everything from the renderer and optionally removes the Canvas DOM element. - * - * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. - */ - destroy(removeView) - { - 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); - } - - this.plugins = null; - - this.type = RENDERER_TYPE.UNKNOWN; - - this.view = null; - - this.screen = null; - - this.resolution = 0; - - this.transparent = false; - - this.autoResize = false; - - this.blendModes = null; - - this.options = null; - - this.preserveDrawingBuffer = false; - this.clearBeforeRender = false; - - this.roundPixels = false; - - this._backgroundColor = 0; - this._backgroundColorRgba = null; - this._backgroundColorString = null; - - this._tempDisplayObjectParent = null; - this._lastObjectRendered = null; - } - - /** - * The background color to fill if not transparent - * - * @member {number} - */ - get backgroundColor() - { - return this._backgroundColor; - } - - set backgroundColor(value) // eslint-disable-line require-jsdoc - { - this._backgroundColor = value; - this._backgroundColorString = hex2string(value); - hex2rgb(value, this._backgroundColorRgba); - } -} diff --git a/packages/core/src/renderers/Renderer.js b/packages/core/src/renderers/Renderer.js deleted file mode 100644 index a2020b1..0000000 --- a/packages/core/src/renderers/Renderer.js +++ /dev/null @@ -1,329 +0,0 @@ -import AbstractRenderer from './AbstractRenderer'; -import { sayHello } from '@pixi/utils'; -import MaskSystem from './systems/MaskSystem'; -import StencilSystem from './systems/StencilSystem'; -import FilterSystem from './systems/filter/FilterSystem'; -import FramebufferSystem from './systems/FramebufferSystem'; -import RenderTextureSystem from './systems/RenderTextureSystem'; -import TextureSystem from './systems/textures/TextureSystem'; -import ProjectionSystem from './systems/ProjectionSystem'; -import StateSystem from './systems/StateSystem'; -import GeometrySystem from './systems/geometry/GeometrySystem'; -import ShaderSystem from './systems/shader/ShaderSystem'; -import ContextSystem from './systems/ContextSystem'; -import BatchSystem from './systems/BatchSystem'; -import TextureGCSystem from './systems/textures/TextureGCSystem'; -import { RENDERER_TYPE } from '@pixi/constants'; -import UniformGroup from '../shader/UniformGroup'; -import { Matrix } from '@pixi/math'; -import Runner from 'mini-runner'; - -/** - * 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 Render works by automatically managing webGLBatchs. - * 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 :) - * - * @class - * @memberof PIXI - * @extends PIXI.AbstractRenderer - */ -export default class Renderer extends AbstractRenderer -{ - // eslint-disable-next-line valid-jsdoc - /** - * - * @param {object} [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.transparent=false] - If the render view is transparent, default false - * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false - * @param {boolean} [options.antialias=false] - sets antialias. If not available natively then FXAA - * antialiasing is used - * @param {boolean} [options.forceFXAA=false] - forces FXAA antialiasing to be used over native. - * FXAA is faster, but may not always look as great - * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. - * The resolution of the renderer retina would be 2. - * @param {boolean} [options.clearBeforeRender=true] - This sets if the renderer will clear - * the canvas or not before the new render pass. If you wish to set this to false, you *must* set - * preserveDrawingBuffer to `true`. - * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, - * enable this if you need to call toDataUrl on the webgl context. - * @param {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when - * rendering, stopping pixel interpolation. - * @param {number} [options.backgroundColor=0x000000] - The background color of the rendered area - * (shown if not transparent). - * @param {boolean} [options.legacy=false] - If true PixiJS will aim to ensure compatibility - * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. - * @param {string} [options.powerPreference] - Parameter passed to webgl context, set to "high-performance" - * for devices with dual graphics card - */ - constructor(options, arg2, arg3) - { - super('WebGL', options, arg2, arg3); - - /** - * The type of this renderer as a standardised const - * - * @member {number} - * @see PIXI.RENDERER_TYPE - */ - this.type = RENDERER_TYPE.WEBGL; - - // this will be set by the contextSystem (this.context) - this.gl = null; - this.CONTEXT_UID = 0; - this.legacy = !!options.legacy; - - // TODO legacy! - - // runners! - this.runners = { - destroy: new Runner('destroy'), - contextChange: new Runner('contextChange', 1), - reset: new Runner('reset'), - update: new Runner('update'), - postrender: new Runner('postrender'), - prerender: new Runner('prerender'), - resize: new Runner('resize', 2), - }; - - 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(GeometrySystem, 'geometry') - .addSystem(FramebufferSystem, 'framebuffer') - .addSystem(StencilSystem, 'stencil') - .addSystem(ProjectionSystem, 'projection') - .addSystem(TextureGCSystem, 'textureGC') - .addSystem(FilterSystem, 'filter') - .addSystem(RenderTextureSystem, 'renderTexture') - .addSystem(BatchSystem, 'batch'); - - this.initPlugins(Renderer.__plugins); - - /** - * The options passed in to create a new webgl context. - * - * @member {object} - * @private - */ - if (options.context) - { - this.context.initFromContext(options.context); - } - else - { - this.context.initFromOptions({ - alpha: this.transparent, - antialias: options.antialias, - premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', - stencil: true, - 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); - } - - addSystem(_class, name) - { - if (!name) - { - name = _class.name; - } - - // TODO - read name from class.name.. - - /* - if(name.includes('System')) - { - name = name.replace('System', ''); - name = name.charAt(0).toLowerCase() + name.slice(1); - } - */ - - const system = new _class(this); - - if (this[name]) - { - throw new Error(`Whoops! ${name} is already a manger`); - } - - this[name] = system; - - for (const i in this.runners) - { - this.runners[i].add(system); - } - - return this; - - /** - * 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. - */ - } - - /** - * Renders the object to its webGL view - * - * @param {PIXI.DisplayObject} displayObject - the object to be rendered - * @param {PIXI.RenderTexture} renderTexture - The render texture to render to. - * @param {boolean} [clear] - Should the canvas be cleared before the new render - * @param {PIXI.Transform} [transform] - A transform to apply to the render texture before rendering. - * @param {boolean} [skipUpdateTransform] - Should we skip the update transform pass? - */ - render(displayObject, renderTexture, clear, transform, skipUpdateTransform) - { - // can be handy to know! - this.renderingToScreen = !renderTexture; - - this.runners.prerender.run(); - this.emit('prerender'); - - // 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.parent; - - displayObject.parent = this._tempDisplayObjectParent; - displayObject.updateTransform(); - displayObject.parent = 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.run(); - - this.emit('postrender'); - } - - /** - * Resizes the webGL view to the specified width and height. - * - * @param {number} screenWidth - the new width of the screen - * @param {number} screenHeight - the new height of the screen - */ - resize(screenWidth, screenHeight) - { - AbstractRenderer.prototype.resize.call(this, screenWidth, screenHeight); - - this.runners.resize.run(screenWidth, screenHeight); - } - - /** - * Resets the WebGL state so you can render things however you fancy! - * - * @return {PIXI.Renderer} Returns itself. - */ - reset() - { - this.runners.reset.run(); - - return this; - } - - clear() - { - this.framebuffer.bind(); - this.framebuffer.clear(); - } - - /** - * Removes everything from the renderer (event listeners, spritebatch, etc...) - * - * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. - * See: https://github.com/pixijs/pixi.js/issues/2233 - */ - destroy(removeView) - { - this.runners.destroy.run(); - - // call base destroy - super.destroy(removeView); - - // TODO nullify all the managers.. - this.gl = null; - } - - /** - * 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 - * builds and excluding plugins. - * @name PIXI.Renderer#plugins - * @type {object} - * @readonly - * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. - * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. - * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. - * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. - */ - - /** - * Adds a plugin to the renderer. - * - * @method - * @param {string} pluginName - The name of the plugin. - * @param {Function} ctor - The constructor function or class for the plugin. - */ - static registerPlugin(pluginName, ctor) - { - Renderer.__plugins = Renderer.__plugins || {}; - Renderer.__plugins[pluginName] = ctor; - } -} diff --git a/packages/core/src/renderers/State.js b/packages/core/src/renderers/State.js deleted file mode 100644 index fa3f221..0000000 --- a/packages/core/src/renderers/State.js +++ /dev/null @@ -1,163 +0,0 @@ -/* eslint-disable max-len */ - -const BLEND = 0; -const OFFSET = 1; -const CULLING = 2; -const DEPTH_TEST = 3; -const WINDING = 4; - -/** - * This is a webGL state. It is passed The WebGL StateManager. - * Each mesh renderered may require webGL to be in a different state. - * For example you may want different blend mode or to enable polygon offsets - * - * @class - * @memberof PIXI - */ -export default class State -{ - /** - * - */ - constructor() - { - this.data = 0; - - this.blendMode = 0; - this.polygonOffset = 0; - - this.blend = true; - // this.depthTest = true; - } - - /** - * Activates blending of the computed fragment color values - * - * @member {boolean} - */ - get blend() - { - return !!(this.data & (1 << BLEND)); - } - - set blend(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << BLEND)) !== value) - { - this.data ^= (1 << BLEND); - } - } - - /** - * Activates adding an offset to depth values of polygon's fragments - * - * @member {boolean} - * @default false - */ - get offsets() - { - return !!(this.data & (1 << OFFSET)); - } - - set offsets(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << OFFSET)) !== value) - { - this.data ^= (1 << OFFSET); - } - } - - /** - * Activates culling of polygons. - * - * @member {boolean} - * @default false - */ - get culling() - { - return !!(this.data & (1 << CULLING)); - } - - set culling(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << CULLING)) !== value) - { - this.data ^= (1 << CULLING); - } - } - - /** - * Activates depth comparisons and updates to the depth buffer. - * - * @member {boolean} - * @default false - */ - get depthTest() - { - return !!(this.data & (1 << DEPTH_TEST)); - } - - set depthTest(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << DEPTH_TEST)) !== value) - { - this.data ^= (1 << DEPTH_TEST); - } - } - - /** - * Specifies whether or not front or back-facing polygons can be culled. - * @member {boolean} - * @default false - */ - get clockwiseFrontFace() - { - return !!(this.data & (1 << WINDING)); - } - - set clockwiseFrontFace(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << WINDING)) !== value) - { - this.data ^= (1 << WINDING); - } - } - - /** - * The blend mode to be applied when this state is set. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. - * Setting this mode to anything other than NO_BLEND will automatically switch blending on. - * - * @member {boolean} - * @default PIXI.BLEND_MODES.NORMAL - * @see PIXI.BLEND_MODES - */ - get blendMode() - { - return this._blendMode; - } - - set blendMode(value) // eslint-disable-line require-jsdoc - { - // 17 is NO BLEND - this.blend = (value !== 17); - this._blendMode = value; - } - - /** - * The polygon offset. Setting this property to anything other than 0 will automatically enable poygon offset fill. - * - * @member {number} - * @default 0 - */ - get polygonOffset() - { - return this._polygonOffset; - } - - set polygonOffset(value) // eslint-disable-line require-jsdoc - { - this.offsets = !!value; - this._polygonOffset = value; - } -} - diff --git a/packages/core/src/renderers/filters/Filter.js b/packages/core/src/renderers/filters/Filter.js deleted file mode 100644 index 2073b8d..0000000 --- a/packages/core/src/renderers/filters/Filter.js +++ /dev/null @@ -1,140 +0,0 @@ -import Shader from '../../shader/Shader'; -import Program from '../../shader/Program'; -import State from '../State'; -import { settings } from '@pixi/settings'; -// import extractUniformsFromSrc from './extractUniformsFromSrc'; -import defaultVertex from './defaultFilter.vert'; -import defaultFragment from './defaultFilter.frag'; - -// let math = require('../../math'); -/** - * @class - * @memberof PIXI - * @extends PIXI.Shader - */ -export default class Filter extends Shader -{ - /** - * @param {string} [vertexSrc] - The source of the vertex shader. - * @param {string} [fragmentSrc] - The source of the fragment shader. - * @param {object} [uniforms] - Custom uniforms to use to augment the built-in ones. - */ - constructor(vertexSrc, fragmentSrc, uniforms) - { - const program = Program.from(vertexSrc, fragmentSrc); - - super(program, uniforms); - - /** - * The padding of the filter. Some filters require extra space to breath such as a blur. - * Increasing this will add extra width and height to the bounds of the object that the - * filter is applied to. - * - * @member {number} - */ - this.padding = 0; - - /** - * The resolution of the filter. Setting this to be lower will lower the quality but - * increase the performance of the filter. - * - * @member {number} - */ - this.resolution = settings.RESOLUTION; - - /** - * If enabled is true the filter is applied, if false it will not. - * - * @member {boolean} - */ - this.enabled = true; - - /** - * If enabled, PixiJS will fit the filter area into boundaries for better performance. - * Switch it off if it does not work for specific shader. - * - * @member {boolean} - */ - this.autoFit = true; - - /** - * Legacy filters use position and uvs from attributes - * @member {boolean} - * @readonly - */ - this.legacy = !!this.program.attributeData.aTextureCoord; - - /** - * the webGL state the filter requires to render - * @member {PIXI.State} - */ - this.state = new State(); - } - - /** - * Applies the filter - * - * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from - * @param {PIXI.RenderTarget} input - The input render target. - * @param {PIXI.RenderTarget} output - The target to output to. - * @param {boolean} clear - Should the output be cleared before rendering to it - * @param {object} [currentState] - It's current state of filter. - * There are some useful properties in the currentState : - * target, filters, sourceFrame, destinationFrame, renderTarget, resolution - */ - apply(filterManager, input, output, clear, currentState, derp) // eslint-disable-line no-unused-vars - { - // do as you please! - - filterManager.applyFilter(this, input, output, clear, currentState, derp); - - // or just do a regular render.. - } - - /** - * Sets the blendmode of the filter - * - * @member {number} - * @default PIXI.BLEND_MODES.NORMAL - */ - get blendMode() - { - return this.state.blendMode; - } - - set blendMode(value) // eslint-disable-line require-jsdoc - { - this.state.blendMode = value; - } - - /** - * The default vertex shader source - * - * @static - * @constant - */ - static get defaultVertexSrc() - { - return defaultVertex; - } - - /** - * The default fragment shader source - * - * @static - * @constant - */ - static get defaultFragmentSrc() - { - return defaultFragment; - } -} - -/** - * Used for caching shader IDs - * - * @static - * @private - */ -Filter.SOURCE_KEY_MAP = {}; - diff --git a/packages/core/src/renderers/filters/defaultFilter.frag b/packages/core/src/renderers/filters/defaultFilter.frag deleted file mode 100644 index a14c1be..0000000 --- a/packages/core/src/renderers/filters/defaultFilter.frag +++ /dev/null @@ -1,22 +0,0 @@ -varying vec2 vTextureCoord; -varying vec2 vFilterCoord; - -uniform sampler2D uSampler; -uniform sampler2D filterSampler; - -void main(void){ - vec4 masky = texture2D(filterSampler, vFilterCoord); - vec4 sample = texture2D(uSampler, vTextureCoord); - vec4 color; - if(mod(vFilterCoord.x, 1.0) > 0.5) - { - color = vec4(1.0, 0.0, 0.0, 1.0); - } - else - { - color = vec4(0.0, 1.0, 0.0, 1.0); - } - // gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0); - gl_FragColor = mix(sample, masky, 0.5); - gl_FragColor *= sample.a; -} diff --git a/packages/core/src/renderers/filters/defaultFilter.vert b/packages/core/src/renderers/filters/defaultFilter.vert deleted file mode 100644 index e1febca..0000000 --- a/packages/core/src/renderers/filters/defaultFilter.vert +++ /dev/null @@ -1,14 +0,0 @@ -attribute vec2 aVertexPosition; -attribute vec2 aTextureCoord; - -uniform mat3 projectionMatrix; -uniform mat3 filterMatrix; - -varying vec2 vTextureCoord; -varying vec2 vFilterCoord; - -void main(void){ - gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); - vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy; - vTextureCoord = aTextureCoord ; -} diff --git a/packages/core/src/renderers/filters/filterTransforms.js b/packages/core/src/renderers/filters/filterTransforms.js deleted file mode 100644 index 87a3cd6..0000000 --- a/packages/core/src/renderers/filters/filterTransforms.js +++ /dev/null @@ -1,50 +0,0 @@ -import { Matrix } from '@pixi/math'; - -/** - * Calculates the mapped matrix - * @param filterArea {Rectangle} The filter area - * @param sprite {Sprite} the target sprite - * @param outputMatrix {Matrix} @alvin - * @private - */ -// this returns a matrix that will normalise map filter cords in the filter to screen space -export function calculateScreenSpaceMatrix(outputMatrix, filterArea, textureSize) -{ - // TODO unwrap? - const mappedMatrix = outputMatrix.identity(); - - mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); - - mappedMatrix.scale(textureSize.width, textureSize.height); - - return mappedMatrix; -} - -export function calculateNormalizedScreenSpaceMatrix(outputMatrix, filterArea, textureSize) -{ - const mappedMatrix = outputMatrix.identity(); - - mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); - - const translateScaleX = (textureSize.width / filterArea.width); - const translateScaleY = (textureSize.height / filterArea.height); - - mappedMatrix.scale(translateScaleX, translateScaleY); - - return mappedMatrix; -} - -// this will map the filter coord so that a texture can be used based on the transform of a sprite -export function calculateSpriteMatrix(outputMatrix, filterArea, textureSize, sprite) -{ - const orig = sprite._texture.orig; - const mappedMatrix = outputMatrix.set(textureSize.width, 0, 0, textureSize.height, filterArea.x, filterArea.y); - const worldTransform = sprite.worldTransform.copyTo(Matrix.TEMP_MATRIX); - - worldTransform.invert(); - mappedMatrix.prepend(worldTransform); - mappedMatrix.scale(1.0 / orig.width, 1.0 / orig.height); - mappedMatrix.translate(sprite.anchor.x, sprite.anchor.y); - - return mappedMatrix; -} diff --git a/packages/core/src/renderers/filters/spriteMask/SpriteMaskFilter.js b/packages/core/src/renderers/filters/spriteMask/SpriteMaskFilter.js deleted file mode 100644 index d5a91a4..0000000 --- a/packages/core/src/renderers/filters/spriteMask/SpriteMaskFilter.js +++ /dev/null @@ -1,63 +0,0 @@ -import Filter from '../Filter'; -import { Matrix } from '@pixi/math'; -import vertex from './spriteMaskFilter.vert'; -import fragment from './spriteMaskFilter.frag'; -import { default as TextureMatrix } from '../../../textures/TextureMatrix'; - -/** - * The SpriteMaskFilter class - * - * @class - * @extends PIXI.Filter - * @memberof PIXI - */ -export default class SpriteMaskFilter extends Filter -{ - /** - * @param {PIXI.Sprite} sprite - the target sprite - */ - constructor(sprite) - { - const maskMatrix = new Matrix(); - - super(vertex, fragment); - - sprite.renderable = false; - - this.maskSprite = sprite; - this.maskMatrix = maskMatrix; - } - - /** - * Applies the filter - * - * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from - * @param {PIXI.RenderTarget} input - The input render target. - * @param {PIXI.RenderTarget} output - The target to output to. - */ - apply(filterManager, input, output) - { - const maskSprite = this.maskSprite; - const tex = this.maskSprite.texture; - - if (!tex.valid) - { - return; - } - if (!tex.transform) - { - // margin = 0.0, let it bleed a bit, shader code becomes easier - // assuming that atlas textures were made with 1-pixel padding - tex.transform = new TextureMatrix(tex, 0.0); - } - tex.transform.update(); - - this.uniforms.mask = tex; - this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite) - .prepend(tex.transform.mapCoord); - this.uniforms.alpha = maskSprite.worldAlpha; - this.uniforms.maskClamp = tex.transform.uClampFrame; - - filterManager.applyFilter(this, input, output); - } -} diff --git a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.frag b/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.frag deleted file mode 100644 index 0e4aef8..0000000 --- a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.frag +++ /dev/null @@ -1,23 +0,0 @@ -varying vec2 vMaskCoord; -varying vec2 vTextureCoord; - -uniform sampler2D uSampler; -uniform sampler2D mask; -uniform float alpha; -uniform vec4 maskClamp; - -void main(void) -{ - float clip = step(3.5, - step(maskClamp.x, vMaskCoord.x) + - step(maskClamp.y, vMaskCoord.y) + - step(vMaskCoord.x, maskClamp.z) + - step(vMaskCoord.y, maskClamp.w)); - - vec4 original = texture2D(uSampler, vTextureCoord); - vec4 masky = texture2D(mask, vMaskCoord); - - original *= (masky.r * masky.a * alpha * clip); - - gl_FragColor = original; -} diff --git a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.vert b/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.vert deleted file mode 100644 index d23a30c..0000000 --- a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.vert +++ /dev/null @@ -1,16 +0,0 @@ -attribute vec2 aVertexPosition; -attribute vec2 aTextureCoord; - -uniform mat3 projectionMatrix; -uniform mat3 otherMatrix; - -varying vec2 vMaskCoord; -varying vec2 vTextureCoord; - -void main(void) -{ - gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); - - vTextureCoord = aTextureCoord; - vMaskCoord = ( otherMatrix * vec3( aTextureCoord, 1.0) ).xy; -} diff --git a/packages/core/src/renderers/systems/BatchSystem.js b/packages/core/src/renderers/systems/BatchSystem.js deleted file mode 100644 index 70e9518..0000000 --- a/packages/core/src/renderers/systems/BatchSystem.js +++ /dev/null @@ -1,68 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import ObjectRenderer from '../utils/ObjectRenderer'; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ - -export default class BatchSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this manager works for. - */ - constructor(renderer) - { - super(renderer); - - /** - * An empty renderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.emptyRenderer = new ObjectRenderer(renderer); - - /** - * The currently active ObjectRenderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.currentRenderer = this.emptyRenderer; - } - - /** - * Changes the current renderer to the one given in parameter - * - * @param {PIXI.ObjectRenderer} objectRenderer - The object renderer to use. - */ - setObjectRenderer(objectRenderer) - { - if (this.currentRenderer === objectRenderer) - { - return; - } - - this.currentRenderer.stop(); - this.currentRenderer = objectRenderer; - - this.renderer.state.setState(objectRenderer.state); - - this.currentRenderer.start(); - } - - /** - * This should be called if you wish to do some custom rendering - * It will basically render anything that may be batched up such as sprites - * - */ - flush() - { - this.setObjectRenderer(this.emptyRenderer); - } - - reset() - { - this.setObjectRenderer(this.emptyRenderer); - } -} diff --git a/packages/core/src/renderers/systems/ContextSystem.js b/packages/core/src/renderers/systems/ContextSystem.js deleted file mode 100644 index 43bf827..0000000 --- a/packages/core/src/renderers/systems/ContextSystem.js +++ /dev/null @@ -1,186 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { settings } from '@pixi/settings'; - -let CONTEXT_UID = 0; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class ContextSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this.webGLVersion = 1; - - this.handleContextLost = this.handleContextLost.bind(this); - this.handleContextRestored = this.handleContextRestored.bind(this); - - this.extensions = {}; - - renderer.view.addEventListener('webglcontextlost', this.handleContextLost, false); - renderer.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); - } - - get isLost() - { - return (!this.gl || this.gl.isContextLost()); - } - - contextChange(gl) - { - this.gl = gl; - - // restore a context if it was previously lost - if (gl.isContextLost() && gl.getExtension('WEBGL_lose_context')) - { - gl.getExtension('WEBGL_lose_context').restoreContext(); - } - } - - initFromContext(gl) - { - this.gl = gl; - this.validateContext(gl); - this.renderer.gl = gl; - this.renderer.CONTEXT_UID = CONTEXT_UID++; - this.renderer.runners.contextChange.run(gl); - } - - initFromOptions(options) - { - const gl = this.createContext(this.renderer.view, options); - - this.initFromContext(gl); - } - - /** - * Helper class to create a webGL Context - * - * @class - * @memberof PIXI.glCore - * @param canvas {HTMLCanvasElement} the canvas element that we will get the context from - * @param options {Object} An options object that gets passed in to the canvas element containing the context attributes, - * see https://developer.mozilla.org/en/docs/Web/API/HTMLCanvasElement/getContext for the options available - * @return {WebGLRenderingContext} the WebGL context - */ - createContext(canvas, options) - { - let gl; - - if (settings.PREFER_WEBGL_2) - { - gl = canvas.getContext('webgl2', options); - } - - if (gl) - { - this.webGLVersion = 2; - } - else - { - this.webGLVersion = 1; - - gl = canvas.getContext('webgl', options) - || canvas.getContext('experimental-webgl', options); - - if (!gl) - { - // fail, not able to get a context - throw new Error('This browser does not support webGL. Try using the canvas renderer'); - } - } - - this.gl = gl; - - this.getExtensions(); - - return gl; - } - - getExtensions() - { - // time to set up default etensions that pixi uses.. - const gl = this.gl; - const extensions = this.extensions; - - if (this.webGLVersion === 1) - { - extensions.drawBuffers = gl.getExtension('WEBGL_draw_buffers'); - extensions.depthTexture = gl.getExtension('WEBKIT_WEBGL_depth_texture'); - extensions.floatTexture = gl.getExtension('OES_texture_float'); - extensions.loseContext = gl.getExtension('WEBGL_lose_context'); - - extensions.vertexArrayObject = gl.getExtension('OES_vertex_array_object') - || gl.getExtension('MOZ_OES_vertex_array_object') - || gl.getExtension('WEBKIT_OES_vertex_array_object'); - } - - // we don't use any specific WebGL 2 ones yet! - } - - /** - * Handles a lost webgl context - * - * @private - * @param {WebGLContextEvent} event - The context lost event. - */ - handleContextLost(event) - { - event.preventDefault(); - } - - /** - * Handles a restored webgl context - * - * @private - */ - handleContextRestored() - { - this.renderer.runners.contextChange.run(this.gl); - } - - destroy() - { - const view = this.renderer.view; - - // remove listeners - view.removeEventListener('webglcontextlost', this.handleContextLost); - view.removeEventListener('webglcontextrestored', this.handleContextRestored); - - this.gl.useProgram(null); - - if (this.extensions.loseContext) - { - this.extensions.loseContext.loseContext(); - } - } - - postrender() - { - this.gl.flush(); - } - - validateContext(gl) - { - const attributes = gl.getContextAttributes(); - - // this is going to be fairly simple for now.. but at least we have room to grow! - if (!attributes.stencil) - { - /* eslint-disable max-len */ - - /* eslint-disable no-console */ - console.warn('Provided WebGL context does not have a stencil buffer, masks may not render correctly'); - /* eslint-enable no-console */ - - /* eslint-enable max-len */ - } - } -} diff --git a/packages/core/src/renderers/systems/FramebufferSystem.js b/packages/core/src/renderers/systems/FramebufferSystem.js deleted file mode 100644 index df7902b..0000000 --- a/packages/core/src/renderers/systems/FramebufferSystem.js +++ /dev/null @@ -1,246 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { Rectangle } from '@pixi/math'; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class FramebufferSystem extends WebGLSystem -{ - /** - * Sets up the renderer context and necessary buffers. - * - * @private - */ - contextChange() - { - this.gl = this.renderer.gl; - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - this.current = null; - this.viewport = new Rectangle(); - - this.drawBufferExtension = this.renderer.context.extensions.drawBuffers; - } - - bind(framebuffer, frame) - { - const gl = this.gl; - - this.current = framebuffer; - - if (framebuffer) - { - // TODO cacheing layer! - - const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID] || this.initFramebuffer(framebuffer); - - gl.bindFramebuffer(gl.FRAMEBUFFER, fbo.framebuffer); - // makesure all textures are unbound.. - - // now check for updates... - if (fbo.dirtyId !== framebuffer.dirtyId) - { - fbo.dirtyId = framebuffer.dirtyId; - - if (fbo.dirtyFormat !== framebuffer.dirtyFormat) - { - fbo.dirtyFormat = framebuffer.dirtyFormat; - this.updateFramebuffer(framebuffer); - } - else if (fbo.dirtySize !== framebuffer.dirtySize) - { - fbo.dirtySize = framebuffer.dirtySize; - this.resizeFramebuffer(framebuffer); - } - } - - for (let i = 0; i < framebuffer.colorTextures.length; i++) - { - if (framebuffer.colorTextures[i].texturePart) - { - this.renderer.texture.unbind(framebuffer.colorTextures[i].texture); - } - else - { - this.renderer.texture.unbind(framebuffer.colorTextures[i]); - } - } - - if (framebuffer.depthTexture) - { - this.renderer.texture.unbind(framebuffer.depthTexture); - } - - if (frame) - { - this.setViewport(frame.x, frame.y, frame.width, frame.height); - } - else - { - this.setViewport(0, 0, framebuffer.width, framebuffer.height); - } - } - else - { - gl.bindFramebuffer(gl.FRAMEBUFFER, null); - - if (frame) - { - this.setViewport(frame.x, frame.y, frame.width, frame.height); - } - else - { - this.setViewport(0, 0, this.renderer.width, this.renderer.height); - } - } - } - - setViewport(x, y, width, height) - { - const v = this.viewport; - - if (v.width !== width || v.height !== height || v.x !== x || v.y !== y) - { - v.x = x; - v.y = y; - v.width = width; - v.height = height; - - this.gl.viewport(x, y, width, height); - } - } - - get size() - { - if (this.current) - { - // TODO store temp - return { x: 0, y: 0, width: this.current.width, height: this.current.height }; - } - - return { x: 0, y: 0, width: this.renderer.width, height: this.renderer.height }; - } - - clear(r, g, b, a) - { - const gl = this.gl; - - // TODO clear color can be set only one right? - gl.clearColor(r, g, b, a); - gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); - } - - // private functions... - - initFramebuffer(framebuffer) - { - const gl = this.gl; - - // TODO - make this a class? - const fbo = { - framebuffer: gl.createFramebuffer(), - stencil: null, - dirtyId: 0, - dirtyFormat: 0, - dirtySize: 0, - }; - - framebuffer.glFrameBuffers[this.CONTEXT_UID] = fbo; - - return fbo; - } - - resizeFramebuffer(framebuffer) - { - const gl = this.gl; - - if (framebuffer.stencil || framebuffer.depth) - { - gl.bindRenderbuffer(gl.RENDERBUFFER, this.stencil); - gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); - } - } - - updateFramebuffer(framebuffer) - { - const gl = this.gl; - - const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID]; - - // bind the color texture - const colorTextures = framebuffer.colorTextures; - - let count = colorTextures.length; - - if (!this.drawBufferExtension) - { - count = Math.min(count, 1); - } - - const activeTextures = []; - - for (let i = 0; i < count; i++) - { - const texture = framebuffer.colorTextures[i]; - - if (texture.texturePart) - { - this.renderer.texture.bind(texture.texture, 0); - - gl.framebufferTexture2D(gl.FRAMEBUFFER, - gl.COLOR_ATTACHMENT0 + i, - gl.TEXTURE_CUBE_MAP_NEGATIVE_X + texture.side, - texture.texture._glTextures[this.CONTEXT_UID].texture, - 0); - } - else - { - this.renderer.texture.bind(texture, 0); - - gl.framebufferTexture2D(gl.FRAMEBUFFER, - gl.COLOR_ATTACHMENT0 + i, - gl.TEXTURE_2D, - texture._glTextures[this.CONTEXT_UID].texture, - 0); - } - - activeTextures.push(gl.COLOR_ATTACHMENT0 + i); - } - - if (this.drawBufferExtension && activeTextures.length > 1) - { - this.drawBufferExtension.drawBuffersWEBGL(activeTextures); - } - - if (framebuffer.depthTexture) - { - const depthTextureExt = this.renderer.context.extensions.depthTexture; - - if (depthTextureExt) - { - const depthTexture = framebuffer.depthTexture; - - this.renderer.texture.bind(depthTexture, 0); - - gl.framebufferTexture2D(gl.FRAMEBUFFER, - gl.DEPTH_ATTACHMENT, - gl.TEXTURE_2D, - depthTexture._glTextures[this.CONTEXT_UID].texture, - 0); - } - } - - if (framebuffer.stencil || framebuffer.depth) - { - fbo.stencil = gl.createRenderbuffer(); - - gl.bindRenderbuffer(gl.RENDERBUFFER, fbo.stencil); - - // TODO.. this is depth AND stencil? - gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, fbo.stencil); - gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); - // fbo.enableStencil(); - } - } -} diff --git a/packages/core/src/renderers/systems/MaskSystem.js b/packages/core/src/renderers/systems/MaskSystem.js deleted file mode 100644 index d9c4b1a..0000000 --- a/packages/core/src/renderers/systems/MaskSystem.js +++ /dev/null @@ -1,199 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import AlphaMaskFilter from '../filters/spriteMask/SpriteMaskFilter'; - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class MaskSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - // TODO - we don't need both! - this.scissor = false; - this.scissorData = null; - this.scissorRenderTarget = null; - - this.enableScissor = false; - - this.alphaMaskPool = []; - this.alphaMaskIndex = 0; - } - - /** - * Applies the Mask and adds it to the current filter stack. - * - * @param {PIXI.DisplayObject} target - Display Object to push the mask to - * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. - */ - push(target, maskData) - { - // TODO the root check means scissor rect will not - // be used on render textures more info here: - // https://github.com/pixijs/pixi.js/pull/3545 - - if (maskData.texture) - { - this.pushSpriteMask(target, maskData); - } - else if (this.enableScissor - && !this.scissor - && this.renderer._activeRenderTarget.root - && !this.renderer.stencil.stencilMaskStack.length - && maskData.isFastRect()) - { - const matrix = maskData.worldTransform; - - let rot = Math.atan2(matrix.b, matrix.a); - - // use the nearest degree! - rot = Math.round(rot * (180 / Math.PI)); - - if (rot % 90) - { - this.pushStencilMask(maskData); - } - else - { - this.pushScissorMask(target, maskData); - } - } - else - { - this.pushStencilMask(maskData); - } - } - - /** - * Removes the last mask from the mask stack and doesn't return it. - * - * @param {PIXI.DisplayObject} target - Display Object to pop the mask from - * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. - */ - pop(target, maskData) - { - if (maskData.texture) - { - this.popSpriteMask(target, maskData); - } - else if (this.enableScissor && !this.renderer.stencil.stencilMaskStack.length) - { - this.popScissorMask(target, maskData); - } - else - { - this.popStencilMask(target, maskData); - } - } - - /** - * Applies the Mask and adds it to the current filter stack. - * - * @param {PIXI.RenderTarget} target - Display Object to push the sprite mask to - * @param {PIXI.Sprite} maskData - Sprite to be used as the mask - */ - pushSpriteMask(target, maskData) - { - let alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; - - if (!alphaMaskFilter) - { - alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; - } - - alphaMaskFilter[0].resolution = this.renderer.resolution; - alphaMaskFilter[0].maskSprite = maskData; - - // TODO - may cause issues! - target.filterArea = maskData.getBounds(true); - - this.renderer.filterSystem.pushFilter(target, alphaMaskFilter); - - this.alphaMaskIndex++; - } - - /** - * Removes the last filter from the filter stack and doesn't return it. - * - */ - popSpriteMask() - { - this.renderer.filterSystem.popFilter(); - this.alphaMaskIndex--; - } - - /** - * Applies the Mask and adds it to the current filter stack. - * - * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. - */ - pushStencilMask(maskData) - { - this.renderer.batch.flush(); - this.renderer.stencil.pushStencil(maskData); - } - - /** - * Removes the last filter from the filter stack and doesn't return it. - * - */ - popStencilMask() - { - // this.renderer.currentRenderer.stop(); - this.renderer.stencil.popStencil(); - } - - /** - * - * @param {PIXI.DisplayObject} target - Display Object to push the mask to - * @param {PIXI.Graphics} maskData - The masking data. - */ - pushScissorMask(target, maskData) - { - maskData.renderable = true; - - const renderTarget = this.renderer._activeRenderTarget; - - const bounds = maskData.getBounds(); - - bounds.fit(renderTarget.size); - maskData.renderable = false; - - this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); - - const resolution = this.renderer.resolution; - - this.renderer.gl.scissor( - bounds.x * resolution, - (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, - bounds.width * resolution, - bounds.height * resolution - ); - - this.scissorRenderTarget = renderTarget; - this.scissorData = maskData; - this.scissor = true; - } - - /** - * - * - */ - popScissorMask() - { - this.scissorRenderTarget = null; - this.scissorData = null; - this.scissor = false; - - // must be scissor! - const gl = this.renderer.gl; - - gl.disable(gl.SCISSOR_TEST); - } -} diff --git a/packages/core/src/renderers/systems/ProjectionSystem.js b/packages/core/src/renderers/systems/ProjectionSystem.js deleted file mode 100644 index ec5efdb..0000000 --- a/packages/core/src/renderers/systems/ProjectionSystem.js +++ /dev/null @@ -1,73 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { Matrix } from '@pixi/math'; - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ - -export default class ProjectionSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this.projectionMatrix = new Matrix(); - } - - update(destinationFrame, sourceFrame, resolution, root) - { - this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; - this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; - - this.calculateProjection(this.destinationFrame, this.sourceFrame, resolution, root); - - this.renderer.globalUniforms.uniforms.projectionMatrix = this.projectionMatrix; - this.renderer.globalUniforms.update(); - } - - /** - * Updates the projection matrix based on a projection frame (which is a rectangle) - * - * @param {Rectangle} destinationFrame - The destination frame. - * @param {Rectangle} sourceFrame - The source frame. - */ - calculateProjection(destinationFrame, sourceFrame, resolution, root) - { - const pm = this.projectionMatrix; - - // I don't think we will need this line.. - // pm.identity(); - - if (!root) - { - pm.a = (1 / destinationFrame.width * 2) * resolution; - pm.d = (1 / destinationFrame.height * 2) * resolution; - - pm.tx = -1 - (sourceFrame.x * pm.a); - pm.ty = -1 - (sourceFrame.y * pm.d); - } - else - { - pm.a = (1 / destinationFrame.width * 2) * resolution; - pm.d = (-1 / destinationFrame.height * 2) * resolution; - - pm.tx = -1 - (sourceFrame.x * pm.a); - pm.ty = 1 - (sourceFrame.y * pm.d); - } - } - - /** - * Sets the transform of the active render target to the given matrix - * - * @param {PIXI.Matrix} matrix - The transformation matrix - */ - setTransform()// matrix) - { - // this._activeRenderTarget.transform = matrix; - } -} diff --git a/packages/core/src/renderers/systems/RenderTextureSystem.js b/packages/core/src/renderers/systems/RenderTextureSystem.js deleted file mode 100644 index a133ef1..0000000 --- a/packages/core/src/renderers/systems/RenderTextureSystem.js +++ /dev/null @@ -1,115 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { Rectangle } from '@pixi/math'; - -const tempRect = new Rectangle(); - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ - -export default class RenderTextureSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this.clearColor = renderer._backgroundColorRgba; - - // TODO moe this property somewhere else! - this.defaultMaskStack = []; - this.defaultFilterStack = [{}]; - - // empty render texture? - this.renderTexture = null; - - this.destinationFrame = new Rectangle(); - } - - bind(renderTexture, sourceFrame, destinationFrame) - { - // TODO - do we want this?? - if (this.renderTexture === renderTexture) return; - this.renderTexture = renderTexture; - - const renderer = this.renderer; - - if (renderTexture) - { - const baseTexture = renderTexture.baseTexture; - - if (!destinationFrame) - { - tempRect.width = baseTexture.realWidth; - tempRect.height = baseTexture.realHeight; - - destinationFrame = tempRect; - } - - if (!sourceFrame) - { - sourceFrame = destinationFrame; - } - - this.renderer.framebuffer.bind(baseTexture.frameBuffer, destinationFrame); - - this.renderer.projection.update(destinationFrame, sourceFrame, baseTexture.resolution, false); - this.renderer.stencil.setMaskStack(baseTexture.stencilMaskStack); - } - else - { - // TODO these validation checks happen deeper down.. - // thing they can be avoided.. - if (!destinationFrame) - { - tempRect.width = renderer.width; - tempRect.height = renderer.height; - - destinationFrame = tempRect; - } - - if (!sourceFrame) - { - sourceFrame = destinationFrame; - } - - renderer.framebuffer.bind(null, destinationFrame); - - // TODO store this.. - this.renderer.projection.update(destinationFrame, sourceFrame, this.renderer.resolution, true); - this.renderer.stencil.setMaskStack(this.defaultMaskStack); - } - - this.destinationFrame.copyFrom(destinationFrame); - } - - /** - * Erases the render texture and fills the drawing area with a colour - * - * @param {number} [clearColor] - The colour - * @return {PIXI.Renderer} Returns itself. - */ - clear(clearColor) - { - if (this.renderTexture) - { - clearColor = clearColor || this.renderTexture.baseTexture.clearColor; - } - else - { - clearColor = clearColor || this.clearColor; - } - - this.renderer.framebuffer.clear(clearColor[0], clearColor[1], clearColor[2], clearColor[3]); - } - - resize()// screenWidth, screenHeight) - { - // resize the root only! - this.bind(null); - } -} diff --git a/packages/core/src/renderers/systems/StateSystem.js b/packages/core/src/renderers/systems/StateSystem.js deleted file mode 100755 index d7f4caf..0000000 --- a/packages/core/src/renderers/systems/StateSystem.js +++ /dev/null @@ -1,288 +0,0 @@ -import mapWebGLBlendModesToPixi from '../utils/mapWebGLBlendModesToPixi'; -import WebGLSystem from './WebGLSystem'; -import WebGLState from '../State'; - -const BLEND = 0; -const OFFSET = 1; -const CULLING = 2; -const DEPTH_TEST = 3; -const WINDING = 4; - -/** - * A WebGL state machines - * - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class StateSystem extends WebGLSystem -{ - /** - * @param {WebGLRenderingContext} gl - The current WebGL rendering context - */ - constructor(renderer) - { - super(renderer); - - this.gl = null; - - this.maxAttribs = null; - - // check we have vao.. - this.nativeVaoExtension = null; - - this.attribState = null; - - this.stateId = 0; - this.polygonOffset = 0; - this.blendMode = 17; - - this.map = []; - - // map functions for when we set state.. - this.map[BLEND] = this.setBlend; - this.map[OFFSET] = this.setOffset; - this.map[CULLING] = this.setCullFace; - this.map[DEPTH_TEST] = this.setDepthTest; - this.map[WINDING] = this.setFrontFace; - - this.checks = []; - - this.defaultState = new WebGLState(); - this.defaultState.blend = true; - this.defaultState.depth = true; - } - - contextChange(gl) - { - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; - - this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); - - // check we have vao.. - this.nativeVaoExtension = ( - gl.getExtension('OES_vertex_array_object') - || gl.getExtension('MOZ_OES_vertex_array_object') - || gl.getExtension('WEBKIT_OES_vertex_array_object') - ); - - this.attribState = { - tempAttribState: new Array(this.maxAttribs), - attribState: new Array(this.maxAttribs), - }; - - this.blendModes = mapWebGLBlendModesToPixi(gl); - - this.setState(this.defaultState); - - this.reset(); - } - - /** - * Sets the current state - * - * @param {*} state - The state to set. - */ - setState(state) - { - state = state || this.defaultState; - - // TODO maybe to an object check? ( this.state === state )? - if (this.stateId !== state.data) - { - let diff = this.stateId ^ state.data; - let i = 0; - - // order from least to most common - while (diff) - { - if (diff & 1) - { - // state change! - this.map[i].call(this, !!(state.data & (1 << i))); - } - - diff = diff >> 1; - i++; - } - - this.stateId = state.data; - } - - // based on the above settings we check for specific modes.. - // for example if blend is active we check and set the blend modes - // or of polygon offset is active we check the poly depth. - for (let i = 0; i < this.checks.length; i++) - { - this.checks[i](this, state); - } - } - - /** - * Enables or disabled blending. - * - * @param {boolean} value - Turn on or off webgl blending. - */ - setBlend(value) - { - this.updateCheck(StateSystem.checkBlendMode, value); - - this.gl[value ? 'enable' : 'disable'](this.gl.BLEND); - } - - /** - * Enables or disable polygon offset fill - * - * @param {boolean} value - Turn on or off webgl polygon offset testing. - */ - setOffset(value) - { - this.gl[value ? 'enable' : 'disable'](this.gl.POLYGON_OFFSET_FILL); - } - - /** - * Sets whether to enable or disable depth test. - * - * @param {boolean} value - Turn on or off webgl depth testing. - */ - setDepthTest(value) - { - this.gl[value ? 'enable' : 'disable'](this.gl.DEPTH_TEST); - } - - /** - * Sets whether to enable or disable cull face. - * - * @param {boolean} value - Turn on or off webgl cull face. - */ - setCullFace(value) - { - this.gl[value ? 'enable' : 'disable'](this.gl.CULL_FACE); - } - - /** - * Sets the gl front face. - * - * @param {boolean} value - true is clockwise and false is counter-clockwise - */ - setFrontFace(value) - { - this.gl.frontFace(this.gl[value ? 'CW' : 'CCW']); - } - - /** - * Sets the blend mode. - * - * @param {number} value - The blend mode to set to. - */ - setBlendMode(value) - { - if (value === this.blendMode) - { - return; - } - - this.blendMode = value; - this.gl.blendFunc(this.blendModes[value][0], this.blendModes[value][1]); - } - - /** - * Sets the polygon offset. - * - * @param {number} value - the polygon offset - * @param {number} scale - the polygon offset scale - */ - setPolygonOffset(value, scale) - { - this.gl.polygonOffset(value, scale); - } - - /** - * Disables all the vaos in use - * - */ - resetAttributes() - { - for (let i = 0; i < this.attribState.tempAttribState.length; i++) - { - this.attribState.tempAttribState[i] = 0; - } - - for (let i = 0; i < this.attribState.attribState.length; i++) - { - this.attribState.attribState[i] = 0; - } - - // im going to assume one is always active for performance reasons. - for (let i = 1; i < this.maxAttribs; i++) - { - this.gl.disableVertexAttribArray(i); - } - } - - // used - /** - * Resets all the logic and disables the vaos - */ - reset() - { - // unbind any VAO if they exist.. - if (this.nativeVaoExtension) - { - this.nativeVaoExtension.bindVertexArrayOES(null); - } - - // reset all attributes.. - this.resetAttributes(); - - this.gl.pixelStorei(this.gl.UNPACK_FLIP_Y_WEBGL, false); - - this.setBlendMode(0); - - // TODO? - // this.setState(this.defaultState); - } - - /** - * checks to see which updates should be checked based on which settings have been activated - * for example if blend is enabled then we shold check the blend modes each time the state is changed - * or if poygon fill is activated then we need to check if the polygone offset changes. - * The idea is that we only check what we have too. - * - * @param {Function} func the checking function to add or remove - * @param {boolean} value should the check function be added or removed. - */ - updateCheck(func, value) - { - const index = this.checks.indexOf(func); - - if (value && index === -1) - { - this.checks.push(func); - } - else if (!value && index !== -1) - { - this.checks.splice(index, 1); - } - } - - /** - * A private little wrapper function that we call to check the blend mode. - * - * @static - * @private - * @param {PIXI.StateSystem} System the System to perform the state check on - * @param {PIXI.State} state the state that the blendMode will pulled from - */ - static checkBlendMode(system, state) - { - system.setBlendMode(state.blendMode); - } - - // TODO - add polygon offset? -} diff --git a/packages/core/src/renderers/systems/StencilSystem.js b/packages/core/src/renderers/systems/StencilSystem.js deleted file mode 100644 index 5d538cb..0000000 --- a/packages/core/src/renderers/systems/StencilSystem.js +++ /dev/null @@ -1,133 +0,0 @@ -import WebGLSystem from './WebGLSystem'; - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class StencilSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - this.stencilMaskStack = []; - } - - /** - * Changes the mask stack that is used by this System. - * - * @param {PIXI.Graphics[]} stencilMaskStack - The mask stack - */ - setMaskStack(stencilMaskStack) - { - const gl = this.renderer.gl; - - if (stencilMaskStack.length !== this.stencilMaskStack.length) - { - if (stencilMaskStack.length === 0) - { - gl.disable(gl.STENCIL_TEST); - } - else - { - gl.enable(gl.STENCIL_TEST); - } - } - - this.stencilMaskStack = stencilMaskStack; - } - - /** - * Applies the Mask and adds it to the current stencil stack. @alvin - * - * @param {PIXI.Graphics} graphics - The mask - */ - pushStencil(graphics) - { - this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); - - // this.renderer._activeRenderTarget.attachStencilBuffer(); - - const gl = this.renderer.gl; - const prevMaskCount = this.stencilMaskStack.length; - - if (prevMaskCount === 0) - { - gl.enable(gl.STENCIL_TEST); - } - - this.stencilMaskStack.push(graphics); - - // Increment the refference stencil value where the new mask overlaps with the old ones. - gl.colorMask(false, false, false, false); - gl.stencilFunc(gl.EQUAL, prevMaskCount, this._getBitwiseMask()); - gl.stencilOp(gl.KEEP, gl.KEEP, gl.INCR); - this.renderer.plugins.graphics.render(graphics); - - this._useCurrent(); - } - - /** - * Removes the last mask from the stencil stack. @alvin - */ - popStencil() - { - this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); - - const gl = this.renderer.gl; - const graphics = this.stencilMaskStack.pop(); - - if (this.stencilMaskStack.length === 0) - { - // the stack is empty! - gl.disable(gl.STENCIL_TEST); - gl.clear(gl.STENCIL_BUFFER_BIT); - gl.clearStencil(0); - } - else - { - // Decrement the refference stencil value where the popped mask overlaps with the other ones - gl.colorMask(false, false, false, false); - gl.stencilOp(gl.KEEP, gl.KEEP, gl.DECR); - this.renderer.plugins.graphics.render(graphics); - - this._useCurrent(); - } - } - - /** - * Setup renderer to use the current stencil data. - */ - _useCurrent() - { - const gl = this.renderer.gl; - - gl.colorMask(true, true, true, true); - gl.stencilFunc(gl.EQUAL, this.stencilMaskStack.length, this._getBitwiseMask()); - gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP); - } - - /** - * Fill 1s equal to the number of acitve stencil masks. - * - * @return {number} The bitwise mask. - */ - _getBitwiseMask() - { - return (1 << this.stencilMaskStack.length) - 1; - } - - /** - * Destroys the mask stack. - * - */ - destroy() - { - super.destroy(this); - - this.stencilMaskStack = null; - } -} diff --git a/packages/core/src/renderers/systems/WebGLSystem.js b/packages/core/src/renderers/systems/WebGLSystem.js deleted file mode 100644 index d3d5b31..0000000 --- a/packages/core/src/renderers/systems/WebGLSystem.js +++ /dev/null @@ -1,41 +0,0 @@ -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this manager works for. - */ - constructor(renderer) - { - /** - * The renderer this manager works for. - * - * @member {PIXI.Renderer} - */ - this.renderer = renderer; - - this.renderer.runners.contextChange.add(this); - } - - /** - * Generic method called when there is a WebGL context change. - * - */ - contextChange() - { - // do some codes init! - } - - /** - * Generic destroy methods to be overridden by the subclass - * - */ - destroy() - { - this.renderer.runners.contextChange.remove(this); - this.renderer = null; - } -} diff --git a/packages/core/src/renderers/systems/filter/FilterSystem.js b/packages/core/src/renderers/systems/filter/FilterSystem.js deleted file mode 100644 index e615037..0000000 --- a/packages/core/src/renderers/systems/filter/FilterSystem.js +++ /dev/null @@ -1,431 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; - -import RenderTexture from '../../../textures/RenderTexture'; -import Quad from '../../utils/Quad'; -import QuadUv from '../../utils/QuadUv'; -import { Rectangle } from '@pixi/math'; -import * as filterTransforms from '../../filters/filterTransforms'; -import bitTwiddle from 'bit-twiddle'; -import UniformGroup from '../../../shader/UniformGroup'; -import { DRAW_MODES } from '../../../../../constants'; - -// -/** - * @ignore - * @class - */ -class FilterState -{ - /** - * - */ - constructor() - { - this.renderTexture = null; - this.sourceFrame = new Rectangle(); - this.destinationFrame = new Rectangle(); - this.filters = []; - this.target = null; - this.legacy = false; - this.resolution = 1; - } -} - -/** - * @class - * @memberof PIXI.systems - * @extends PIXI.systems.WebGLSystem - */ -export default class FilterSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - /** - * stores a bunch of PO2 textures used for filtering - * @type {Object} - */ - this.texturePool = {}; - - /** - * a pool for storing filter states, save us creating new ones each tick - * @type {Array} - */ - this.statePool = []; - - /** - * A very simple geometry used when drawing a filter effect to the screen - * @type {Quad} - */ - this.quad = new Quad(); - - this.quadUv = new QuadUv(); - - /** - * Temporary rect for maths - * @type {PIXI.Rectangle} - */ - this.tempRect = new Rectangle(); - - this.activeState = {}; - - /** - * this uniform group is attached to filter uniforms when used - * @type {UniformGroup} - */ - this.globalUniforms = new UniformGroup({ - sourceFrame: this.tempRect, - destinationFrame: this.tempRect, - - // legacy variables - filterArea: new Float32Array(4), - filterClamp: new Float32Array(4), - }, true); - } - - /** - * Adds a new filter to the System. - * - * @param {PIXI.DisplayObject} target - The target of the filter to render. - * @param {PIXI.Filter[]} filters - The filters to apply. - */ - push(target, filters) - { - const renderer = this.renderer; - const filterStack = this.renderer.renderTexture.defaultFilterStack; - const state = this.statePool.pop() || new FilterState(); - - let resolution = filters[0].resolution; - let padding = filters[0].padding; - let autoFit = filters[0].autoFit; - let legacy = filters[0].legacy; - - for (let i = 1; i < filters.length; i++) - { - const filter = filters[i]; - - // lets use the lowest resolution.. - resolution = Math.min(resolution, filter.resolution); - // and the largest amount of padding! - padding = Math.max(padding, filter.padding); - // only auto fit if all filters are autofit - autoFit = autoFit || filter.autoFit; - - legacy = legacy || filter.legacy; - } - - filterStack.push(state); - - state.resolution = resolution; - - state.legacy = legacy; - - // round to whole number based on resolution - // TODO move that to the shader too? - state.sourceFrame = target.filterArea || target.getBounds(true); - - state.sourceFrame.pad(padding); - - if (autoFit) - { - state.sourceFrame.fit(this.renderer.renderTexture.destinationFrame); - } - - state.sourceFrame.round(resolution); - - state.renderTexture = this.getPotFilterTexture(state.sourceFrame.width, state.sourceFrame.height, resolution); - state.filters = filters; - - state.destinationFrame.width = state.renderTexture.width; - state.destinationFrame.height = state.renderTexture.height; - - state.renderTexture.filterFrame = state.sourceFrame; - renderer.renderTexture.bind(state.renderTexture, state.sourceFrame);// /, state.destinationFrame); - renderer.renderTexture.clear(); - } - - /** - * Pops off the filter and applies it. - * - */ - pop() - { - const renderer = this.renderer; - const filterStack = renderer.renderTexture.defaultFilterStack; - const state = filterStack.pop(); - const filters = state.filters; - - this.activeState = state; - - const globalUniforms = this.globalUniforms.uniforms; - - globalUniforms.sourceFrame = state.sourceFrame; - globalUniforms.destinationFrame = state.destinationFrame; - globalUniforms.resolution = state.resolution; - - // only update the rect if its legacy.. - if (state.legacy) - { - const filterArea = globalUniforms.filterArea; - const filterClamp = globalUniforms.filterClamp; - - filterArea[0] = state.destinationFrame.width; - filterArea[1] = state.destinationFrame.height; - filterArea[2] = state.sourceFrame.x; - filterArea[3] = state.sourceFrame.y; - - filterClamp[0] = 0.5 / state.resolution / state.destinationFrame.width; - filterClamp[1] = 0.5 / state.resolution / state.destinationFrame.height; - filterClamp[2] = (state.sourceFrame.width - 0.5) / state.resolution / state.destinationFrame.width; - filterClamp[3] = (state.sourceFrame.height - 0.5) / state.resolution / state.destinationFrame.height; - } - - this.globalUniforms.update(); - - const lastState = filterStack[filterStack.length - 1]; - - if (filters.length === 1) - { - filters[0].apply(this, state.renderTexture, lastState.renderTexture, false, state); - - this.returnFilterTexture(state.renderTexture); - } - else - { - let flip = state.renderTexture; - let flop = this.getPotFilterTexture( - flip.width, - flip.height, - state.resolution - ); - - flop.filterFrame = flip.filterFrame; - - let i = 0; - - for (i = 0; i < filters.length - 1; ++i) - { - filters[i].apply(this, flip, flop, true, state); - - const t = flip; - - flip = flop; - flop = t; - } - - filters[i].apply(this, flip, lastState.renderTexture, false, state); - - this.returnFilterTexture(flip); - this.returnFilterTexture(flop); - } - - this.statePool.push(state); - } - - /** - * Draws a filter. - * - * @param {PIXI.Filter} filter - The filter to draw. - * @param {PIXI.RenderTarget} input - The input render target. - * @param {PIXI.RenderTarget} output - The target to output to. - * @param {boolean} clear - Should the output be cleared before rendering to it - */ - applyFilter(filter, input, output, clear) - { - const renderer = this.renderer; - - renderer.renderTexture.bind(output, output ? output.filterFrame : null); - - if (clear) - { - // gl.disable(gl.SCISSOR_TEST); - renderer.renderTexture.clear(); - // gl.enable(gl.SCISSOR_TEST); - } - - // set the uniforms.. - filter.uniforms.uSampler = input; - filter.uniforms.filterGlobals = this.globalUniforms; - - // TODO make it so that the order of this does not matter.. - // because it does at the moment cos of global uniforms. - // they need to get resynced - - renderer.state.setState(filter.state); - renderer.shader.bind(filter); - - if (filter.legacy) - { - this.quadUv.map(input._frame, input.filterFrame); - - renderer.geometry.bind(this.quadUv); - renderer.geometry.draw(DRAW_MODES.TRIANGLES); - } - else - { - renderer.geometry.bind(this.quad); - renderer.geometry.draw(DRAW_MODES.TRIANGLE_STRIP); - } - } - - /** - * Calculates the mapped matrix. - * - * TODO playing around here.. this is temporary - (will end up in the shader) - * this returns a matrix that will normalise map filter cords in the filter to screen space - * - * @param {PIXI.Matrix} outputMatrix - the matrix to output to. - * @return {PIXI.Matrix} The mapped matrix. - */ - calculateScreenSpaceMatrix(outputMatrix) - { - const currentState = this.activeState; - - return filterTransforms.calculateScreenSpaceMatrix( - outputMatrix, - currentState.sourceFrame, - currentState.destinationFrame - ); - } - - /** - * This will map the filter coord so that a texture can be used based on the transform of a sprite - * - * @param {PIXI.Matrix} outputMatrix - The matrix to output to. - * @param {PIXI.Sprite} sprite - The sprite to map to. - * @return {PIXI.Matrix} The mapped matrix. - */ - calculateSpriteMatrix(outputMatrix, sprite) - { - const currentState = this.activeState; - - return filterTransforms.calculateSpriteMatrix( - outputMatrix, - currentState.sourceFrame, - currentState.destinationFrame, - sprite - ); - } - - /** - * Destroys this Filter System. - * - * @param {boolean} [contextLost=false] context was lost, do not free shaders - * - */ - destroy(contextLost = false) - { - if (!contextLost) - { - this.emptyPool(); - } - else - { - this.texturePool = {}; - } - } - - /** - * Gets a Power-of-Two render texture. - * - * TODO move to a seperate class could be on renderer? - * also - could cause issue with multiple contexts? - * - * @private - * @param {WebGLRenderingContext} gl - The webgl rendering context - * @param {number} minWidth - The minimum width of the render target. - * @param {number} minHeight - The minimum height of the render target. - * @param {number} resolution - The resolution of the render target. - * @return {PIXI.RenderTarget} The new render target. - */ - getPotFilterTexture(minWidth, minHeight, resolution) - { - minWidth = bitTwiddle.nextPow2(minWidth); - minHeight = bitTwiddle.nextPow2(minHeight); - resolution = resolution || 1; - - const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); - - if (!this.texturePool[key]) - { - this.texturePool[key] = []; - } - - let renderTexture = this.texturePool[key].pop(); - - if (!renderTexture) - { - // temporary bypass cache.. - // internally - this will cause a texture to be bound.. - renderTexture = RenderTexture.create({ - width: minWidth, - height: minHeight, - resolution, - }); - } - - return renderTexture; - } - - /** - * Gets extra render texture to use inside current filter - * - * @param {number} resolution resolution of the renderTexture - * @returns {PIXI.RenderTexture} - */ - getFilterTexture(resolution) - { - const rt = this.activeState.renderTexture; - - const filterTexture = this.getPotFilterTexture(rt.width, rt.height, resolution || rt.baseTexture.resolution); - - filterTexture.filterFrame = rt.filterFrame; - - return filterTexture; - } - - /** - * Frees a render texture back into the pool. - * - * @param {PIXI.RenderTarget} renderTexture - The renderTarget to free - */ - returnFilterTexture(renderTexture) - { - renderTexture.filterFrame = null; - - const base = renderTexture.baseTexture; - - const minWidth = base.width; - const minHeight = base.height; - - const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); - - this.texturePool[key].push(renderTexture); - } - - /** - * Empties the texture pool. - * - */ - emptyPool() - { - for (const i in this.texturePool) - { - const textures = this.texturePool[i]; - - if (textures) - { - for (let j = 0; j < textures.length; j++) - { - textures[j].destroy(true); - } - } - } - - this.texturePool = {}; - } -} diff --git a/packages/core/src/renderers/systems/geometry/GLBuffer.js b/packages/core/src/renderers/systems/geometry/GLBuffer.js deleted file mode 100644 index 0477e8a..0000000 --- a/packages/core/src/renderers/systems/geometry/GLBuffer.js +++ /dev/null @@ -1,9 +0,0 @@ -export default class GLBuffer -{ - constructor(buffer) - { - this.buffer = buffer; - this.updateID = -1; - this.byteLength = -1; - } -} diff --git a/packages/core/src/renderers/systems/geometry/GeometrySystem.js b/packages/core/src/renderers/systems/geometry/GeometrySystem.js deleted file mode 100644 index 26c8e9e..0000000 --- a/packages/core/src/renderers/systems/geometry/GeometrySystem.js +++ /dev/null @@ -1,388 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; -import GLBuffer from './GLBuffer'; - -const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class GeometrySystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this._activeGeometry = null; - this._activeVao = null; - - this.hasVao = true; - this.hasInstance = true; - } - - /** - * Sets up the renderer context and necessary buffers. - * - * @private - */ - contextChange() - { - const gl = this.gl = this.renderer.gl; - - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - - // webgl2 - if (!gl.createVertexArray) - { - // webgl 1! - let nativeVaoExtension = this.renderer.context.extensions.vertexArrayObject; - - if (this.renderer.legacy) - { - nativeVaoExtension = null; - } - - if (nativeVaoExtension) - { - gl.createVertexArray = () => - nativeVaoExtension.createVertexArrayOES(); - - gl.bindVertexArray = (vao) => - nativeVaoExtension.bindVertexArrayOES(vao); - - gl.deleteVertexArray = (vao) => - nativeVaoExtension.deleteVertexArrayOES(vao); - } - else - { - this.hasVao = false; - gl.createVertexArray = () => - { - // empty - }; - - gl.bindVertexArray = () => - { - // empty - }; - - gl.deleteVertexArray = () => - { - // empty - }; - } - } - - if (!gl.vertexAttribDivisor) - { - const instanceExt = gl.getExtension('ANGLE_instanced_arrays'); - - if (instanceExt) - { - gl.vertexAttribDivisor = (a, b) => - instanceExt.vertexAttribDivisorANGLE(a, b); - - gl.drawElementsInstanced = (a, b, c, d, e) => - instanceExt.drawElementsInstancedANGLE(a, b, c, d, e); - - gl.drawArraysInstanced = (a, b, c, d) => - instanceExt.drawArraysInstancedANGLE(a, b, c, d); - } - else - { - this.hasInstance = false; - } - } - } - - /** - * Binds geometry so that is can be drawn. Creating a Vao if required - * @private - * @param {PIXI.Geometry} geometry instance of geometry to bind - */ - bind(geometry, shader) - { - shader = shader || this.renderer.shader.shader; - - const gl = this.gl; - - // not sure the best way to address this.. - // currently different shaders require different VAOs for the same geometry - // Still mulling over the best way to solve this one.. - // will likely need to modify the shader attribute locations at run time! - let vaos = geometry.glVertexArrayObjects[this.CONTEXT_UID]; - - if (!vaos) - { - geometry.glVertexArrayObjects[this.CONTEXT_UID] = vaos = {}; - } - - const vao = vaos[shader.program.id] || this.initGeometryVao(geometry, shader.program); - - this._activeGeometry = geometry; - - if (this._activeVao !== vao) - { - this._activeVao = vao; - - if (this.hasVao) - { - gl.bindVertexArray(vao); - } - else - { - this.activateVao(geometry, shader.program); - } - } - - // TODO - optimise later! - // don't need to loop through if nothing changed! - // maybe look to add an 'autoupdate' to geometry? - this.updateBuffers(); - } - - reset() - { - this.unbind(); - } - - updateBuffers() - { - const geometry = this._activeGeometry; - const gl = this.gl; - - for (let i = 0; i < geometry.buffers.length; i++) - { - const buffer = geometry.buffers[i]; - - const glBuffer = buffer._glBuffers[this.CONTEXT_UID]; - - if (buffer._updateID !== glBuffer.updateID) - { - glBuffer.updateID = buffer._updateID; - - // TODO can cache this on buffer! maybe added a getter / setter? - const type = buffer.index ? gl.ELEMENT_ARRAY_BUFFER : gl.ARRAY_BUFFER; - const drawType = buffer.static ? gl.STATIC_DRAW : gl.DYNAMIC_DRAW; - - gl.bindBuffer(type, glBuffer.buffer); - - if (glBuffer.byteLength >= buffer.data.byteLength) - { - // offset is always zero for now! - gl.bufferSubData(type, 0, buffer.data); - } - else - { - gl.bufferData(type, buffer.data, drawType); - } - } - } - } - - checkCompatability(geometry, program) - { - // geometry must have at least all the attributes that the shader requires. - const geometryAttributes = geometry.attributes; - const shaderAttributes = program.attributeData; - - for (const j in shaderAttributes) - { - if (!geometryAttributes[j]) - { - throw new Error(`shader and geometry incompatible, geometry missing the "${j}" attribute`); - } - } - } - - /** - * Creates a Vao with the same structure as the geometry and stores it on the geometry. - * @private - * @param {PIXI.Geometry} geometry instance of geometry to to generate Vao for - */ - initGeometryVao(geometry, program) - { - this.checkCompatability(geometry, program); - - const gl = this.gl; - const CONTEXT_UID = this.CONTEXT_UID; - const buffers = geometry.buffers; - const attributes = geometry.attributes; - - const tempStride = {}; - const tempStart = {}; - - for (const j in buffers) - { - tempStride[j] = 0; - tempStart[j] = 0; - } - - for (const j in attributes) - { - if (!attributes[j].size && program.attributeData[j]) - { - attributes[j].size = program.attributeData[j].size; - } - - tempStride[attributes[j].buffer] += attributes[j].size * byteSizeMap[attributes[j].type]; - } - - for (const j in attributes) - { - const attribute = attributes[j]; - const attribSize = attribute.size; - - if (attribute.stride === undefined) - { - if (tempStride[attribute.buffer] === attribSize * byteSizeMap[attribute.type]) - { - attribute.stride = 0; - } - else - { - attribute.stride = tempStride[attribute.buffer]; - } - } - - if (attribute.start === undefined) - { - attribute.start = tempStart[attribute.buffer]; - - tempStart[attribute.buffer] += attribSize * byteSizeMap[attribute.type]; - } - } - - // first update - and create the buffers! - // only create a gl buffer if it actually gets - for (let i = 0; i < buffers.length; i++) - { - const buffer = buffers[i]; - - if (!buffer._glBuffers[CONTEXT_UID]) - { - buffer._glBuffers[CONTEXT_UID] = new GLBuffer(gl.createBuffer()); - } - } - - // TODO - maybe make this a data object? - // lets wait to see if we need to first! - const vao = gl.createVertexArray(); - - gl.bindVertexArray(vao); - - this.activateVao(geometry, program); - - gl.bindVertexArray(null); - - // add it to the cache! - geometry.glVertexArrayObjects[this.CONTEXT_UID][program.id] = vao; - - return vao; - } - - activateVao(geometry, program) - { - const gl = this.gl; - const CONTEXT_UID = this.CONTEXT_UID; - const buffers = geometry.buffers; - const attributes = geometry.attributes; - - if (geometry.indexBuffer) - { - // first update the index buffer if we have one.. - gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, geometry.indexBuffer._glBuffers[CONTEXT_UID].buffer); - } - - let lastBuffer = null; - - // add a new one! - for (const j in attributes) - { - const attribute = attributes[j]; - const buffer = buffers[attribute.buffer]; - const glBuffer = buffer._glBuffers[CONTEXT_UID]; - - if (program.attributeData[j]) - { - if (lastBuffer !== glBuffer) - { - gl.bindBuffer(gl.ARRAY_BUFFER, glBuffer.buffer); - - lastBuffer = glBuffer; - } - - const location = program.attributeData[j].location; - - // TODO introduce state again - // we can optimise this for older devices that have no VAOs - gl.enableVertexAttribArray(location); - - gl.vertexAttribPointer(location, - attribute.size, - attribute.type || gl.FLOAT, - attribute.normalized, - attribute.stride, - attribute.start); - - if (attribute.instance) - { - // TODO calculate instance count based of this... - if (this.hasInstance) - { - gl.vertexAttribDivisor(location, 1); - } - else - { - throw new Error('geometry error, GPU Instancing is not supported on this device'); - } - } - } - } - } - - draw(type, size, start, instanceCount) - { - const gl = this.gl; - const geometry = this._activeGeometry; - - // TODO.. this should not change so maybe cache the function? - - if (geometry.indexBuffer) - { - if (geometry.instanced) - { - /* eslint-disable max-len */ - gl.drawElementsInstanced(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2, instanceCount || 1); - /* eslint-enable max-len */ - } - else - { - gl.drawElements(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2); - } - } - else - if (geometry.instanced) - { - // TODO need a better way to calculate size.. - gl.drawArraysInstanced(type, start, size || geometry.getSize(), instanceCount || 1); - } - else - { - gl.drawArrays(type, start, size || geometry.getSize()); - } - - return this; - } - - unbind() - { - this.gl.bindVertexArray(null); - this._activeVao = null; - this._activeGeometry = null; - } -} diff --git a/packages/core/src/renderers/systems/geometry/setVertexAttribArrays.js b/packages/core/src/renderers/systems/geometry/setVertexAttribArrays.js deleted file mode 100644 index 23b6959..0000000 --- a/packages/core/src/renderers/systems/geometry/setVertexAttribArrays.js +++ /dev/null @@ -1,54 +0,0 @@ -// var GL_MAP = {}; - -/** - * @param gl {WebGLRenderingContext} The current WebGL context - * @param attribs {*} - * @param state {*} - */ -export default function setVertexAttribArrays(gl, attribs, state) -{ - let i; - - if (state) - { - const tempAttribState = state.tempAttribState; - const attribState = state.attribState; - - for (i = 0; i < tempAttribState.length; i++) - { - tempAttribState[i] = false; - } - - // set the new attribs - for (i = 0; i < attribs.length; i++) - { - tempAttribState[attribs[i].attribute.location] = true; - } - - for (i = 0; i < attribState.length; i++) - { - if (attribState[i] !== tempAttribState[i]) - { - attribState[i] = tempAttribState[i]; - - if (state.attribState[i]) - { - gl.enableVertexAttribArray(i); - } - else - { - gl.disableVertexAttribArray(i); - } - } - } - } - else - { - for (i = 0; i < attribs.length; i++) - { - const attrib = attribs[i]; - - gl.enableVertexAttribArray(attrib.attribute.location); - } - } -} diff --git a/packages/core/src/renderers/systems/index.js b/packages/core/src/renderers/systems/index.js deleted file mode 100644 index c843f03..0000000 --- a/packages/core/src/renderers/systems/index.js +++ /dev/null @@ -1,6 +0,0 @@ -/** - * Systems are individual components to the Renderer pipeline. - * @namespace PIXI.systems - */ -export { default as WebGLSystem } from './WebGLSystem'; -export { default as FilterSystem } from './filter/FilterSystem'; diff --git a/packages/core/src/renderers/systems/shader/GLProgram.js b/packages/core/src/renderers/systems/shader/GLProgram.js deleted file mode 100644 index 92980ca..0000000 --- a/packages/core/src/renderers/systems/shader/GLProgram.js +++ /dev/null @@ -1,43 +0,0 @@ -/** - * Helper class to create a webGL Program - * - * @class - * @private - */ -export default class GLProgram -{ - constructor(program, uniformData) - { - /** - * The shader program - * - * @member {WebGLProgram} - */ - this.program = program; - - /** - * holds the uniform data which contains uniform locations - * and current uniform values used for caching and preventing undeed GPU commands - * @type {Object} - */ - this.uniformData = uniformData; - - /** - * uniformGroups holds the various upload functions for the shader. Each uniform group - * and program have a unique upload function generated. - * @type {Object} - */ - this.uniformGroups = {}; - } - - /** - * Destroys this program - * TODO - */ - destroy() - { - this.uniformData = null; - this.uniformGroups = null; - this.program = null; - } -} diff --git a/packages/core/src/renderers/systems/shader/ShaderSystem.js b/packages/core/src/renderers/systems/shader/ShaderSystem.js deleted file mode 100644 index 6cf57b6..0000000 --- a/packages/core/src/renderers/systems/shader/ShaderSystem.js +++ /dev/null @@ -1,170 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; -import GLProgram from './GLProgram'; -import generateUniformsSync from '../../../shader/generateUniformsSync'; -import defaultValue from '../../../shader/defaultValue'; -import compileProgram from './shader/compileProgram'; - -let UID = 0; - -/** - * Helper class to create a webGL Texture - * - * @class - * @memberof PIXI - */ -export default class ShaderSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - A reference to the current renderer - */ - constructor(renderer) - { - super(renderer); - - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = null; - - this.shader = null; - this.program = null; - - this.id = UID++; - } - - contextChange(gl) - { - this.gl = gl; - } - - /** - * Changes the current shader to the one given in parameter - * - * @param {PIXI.Shader} shader - the new shader - * @param {boolean} dontSync - false if the shader should automatically sync its uniforms. - * @returns {PIXI.glCore.glProgram} the glProgram that belongs to the shader. - */ - bind(shader, dontSync) - { - shader.uniforms.globals = this.renderer.globalUniforms; - - const program = shader.program; - const glProgram = program.glPrograms[this.renderer.CONTEXT_UID] || this.generateShader(shader); - - this.shader = shader; - - // TODO - some current pixi plugins bypass this.. so it not safe to use yet.. - if (this.program !== program) - { - this.program = program; - this.gl.useProgram(glProgram.program); - } - - if (!dontSync) - { - this.syncUniformGroup(shader.uniformGroup); - } - - return glProgram; - } - - /** - * Uploads the uniforms values to the currently bound shader. - * - * @param {object} uniforms - the uniforms valiues that be applied to the current shader - */ - setUniforms(uniforms) - { - const shader = this.shader.program; - const glProgram = shader.glPrograms[this.renderer.CONTEXT_UID]; - - shader.syncUniforms(glProgram.uniformData, uniforms, this.renderer); - } - - syncUniformGroup(group) - { - const glProgram = this.getglProgram(); - - if (!group.static || group.dirtyId !== glProgram.uniformGroups[group.id]) - { - glProgram.uniformGroups[group.id] = group.dirtyId; - const syncFunc = group.syncUniforms[this.shader.program.id] || this.createSyncGroups(group); - - syncFunc(glProgram.uniformData, group.uniforms, this.renderer); - } - } - - createSyncGroups(group) - { - group.syncUniforms[this.shader.program.id] = generateUniformsSync(group, this.shader.program.uniformData); - - return group.syncUniforms[this.shader.program.id]; - } - - /** - * Returns the underlying GLShade rof the currently bound shader. - * This can be handy for when you to have a little more control over the setting of your uniforms. - * - * @return {PIXI.glCore.Shader} the glProgram for the currently bound Shader for this context - */ - getglProgram() - { - if (this.shader) - { - return this.shader.program.glPrograms[this.renderer.CONTEXT_UID]; - } - - return null; - } - - /** - * Generates a glProgram verion of the Shader provided. - * - * @private - * @param {PIXI.Shader} shader the shader that the glProgram will be based on. - * @return {PIXI.glCore.glProgram} A shiney new glProgram - */ - generateShader(shader) - { - const gl = this.gl; - - const program = shader.program; - - const attribMap = {}; - - for (const i in program.attributeData) - { - attribMap[i] = program.attributeData[i].location; - } - - const shaderProgram = compileProgram(gl, program.vertexSrc, program.fragmentSrc, attribMap); - const uniformData = {}; - - for (const i in program.uniformData) - { - const data = program.uniformData[i]; - - uniformData[i] = { - location: gl.getUniformLocation(shaderProgram, i), - value: defaultValue(data.type, data.size), - }; - } - - const glProgram = new GLProgram(shaderProgram, uniformData); - - program.glPrograms[this.renderer.CONTEXT_UID] = glProgram; - - return glProgram; - } - - /** - * Destroys this System and removes all its textures - */ - destroy() - { - // TODO implement destroy method for ShaderSystem - this.destroyed = true; - } -} diff --git a/packages/core/src/renderers/systems/shader/shader/compileProgram.js b/packages/core/src/renderers/systems/shader/shader/compileProgram.js deleted file mode 100644 index 8c6ccb9..0000000 --- a/packages/core/src/renderers/systems/shader/shader/compileProgram.js +++ /dev/null @@ -1,78 +0,0 @@ -/** - * @method compileProgram - * @memberof PIXI.glCore.shader - * @param gl {WebGLRenderingContext} The current WebGL context {WebGLProgram} - * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. - * @param fragmentSrc {string|string[]} The fragment shader source as an array of strings. - * @param attributeLocations {Object} An attribute location map that lets you manually set the attribute locations - * @return {WebGLProgram} the shader program - */ -export default function compileProgram(gl, vertexSrc, fragmentSrc, attributeLocations) -{ - const glVertShader = compileShader(gl, gl.VERTEX_SHADER, vertexSrc); - const glFragShader = compileShader(gl, gl.FRAGMENT_SHADER, fragmentSrc); - - let program = gl.createProgram(); - - gl.attachShader(program, glVertShader); - gl.attachShader(program, glFragShader); - - // optionally, set the attributes manually for the program rather than letting WebGL decide.. - if (attributeLocations) - { - for (const i in attributeLocations) - { - gl.bindAttribLocation(program, attributeLocations[i], i); - } - } - - gl.linkProgram(program); - - // if linking fails, then log and cleanup - if (!gl.getProgramParameter(program, gl.LINK_STATUS)) - { - console.error('Pixi.js Error: Could not initialize shader.'); - console.error('gl.VALIDATE_STATUS', gl.getProgramParameter(program, gl.VALIDATE_STATUS)); - console.error('gl.getError()', gl.getError()); - - // if there is a program info log, log it - if (gl.getProgramInfoLog(program) !== '') - { - console.warn('Pixi.js Warning: gl.getProgramInfoLog()', gl.getProgramInfoLog(program)); - } - - gl.deleteProgram(program); - program = null; - } - - // clean up some shaders - gl.deleteShader(glVertShader); - gl.deleteShader(glFragShader); - - return program; -} - -/** - * @private - * @param gl {WebGLRenderingContext} The current WebGL context {WebGLProgram} - * @param type {Number} the type, can be either VERTEX_SHADER or FRAGMENT_SHADER - * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. - * @return {WebGLShader} the shader - */ -function compileShader(gl, type, src) -{ - const shader = gl.createShader(type); - - gl.shaderSource(shader, src); - gl.compileShader(shader); - - if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) - { - console.warn(src); - console.error(gl.getShaderInfoLog(shader)); - - return null; - } - - return shader; -} diff --git a/packages/core/src/renderers/systems/shader/shader/defaultValue.js b/packages/core/src/renderers/systems/shader/shader/defaultValue.js deleted file mode 100644 index 97290bd..0000000 --- a/packages/core/src/renderers/systems/shader/shader/defaultValue.js +++ /dev/null @@ -1,79 +0,0 @@ -/** - * @method defaultValue - * @memberof PIXI.glCore.shader - * @param type {String} Type of value - * @param size {Number} - */ -export default function defaultValue(type, size) -{ - switch (type) - { - case 'float': - return 0; - - case 'vec2': - return new Float32Array(2 * size); - - case 'vec3': - return new Float32Array(3 * size); - - case 'vec4': - return new Float32Array(4 * size); - - case 'int': - case 'sampler2D': - case 'sampler2DArray': - return 0; - - case 'ivec2': - return new Int32Array(2 * size); - - case 'ivec3': - return new Int32Array(3 * size); - - case 'ivec4': - return new Int32Array(4 * size); - - case 'bool': - return false; - - case 'bvec2': - - return booleanArray(2 * size); - - case 'bvec3': - return booleanArray(3 * size); - - case 'bvec4': - return booleanArray(4 * size); - - case 'mat2': - return new Float32Array([1, 0, - 0, 1]); - - case 'mat3': - return new Float32Array([1, 0, 0, - 0, 1, 0, - 0, 0, 1]); - - case 'mat4': - return new Float32Array([1, 0, 0, 0, - 0, 1, 0, 0, - 0, 0, 1, 0, - 0, 0, 0, 1]); - } - - return null; -} - -function booleanArray(size) -{ - const array = new Array(size); - - for (let i = 0; i < array.length; i++) - { - array[i] = false; - } - - return array; -} diff --git a/packages/core/src/renderers/systems/shader/shader/index.js b/packages/core/src/renderers/systems/shader/shader/index.js deleted file mode 100644 index ba7a317..0000000 --- a/packages/core/src/renderers/systems/shader/shader/index.js +++ /dev/null @@ -1,8 +0,0 @@ -/** - * @namespace PIXI.glCore.shader - */ -export { default as compileProgram } from './compileProgram'; -export { default as defaultValue } from './defaultValue'; -export { default as setPrecision } from './setPrecision'; -export { default as mapSize } from './mapSize'; -export { default as mapType } from './mapType'; diff --git a/packages/core/src/renderers/systems/shader/shader/mapSize.js b/packages/core/src/renderers/systems/shader/shader/mapSize.js deleted file mode 100644 index b681554..0000000 --- a/packages/core/src/renderers/systems/shader/shader/mapSize.js +++ /dev/null @@ -1,33 +0,0 @@ -const GLSL_TO_SIZE = { - float: 1, - vec2: 2, - vec3: 3, - vec4: 4, - - int: 1, - ivec2: 2, - ivec3: 3, - ivec4: 4, - - bool: 1, - bvec2: 2, - bvec3: 3, - bvec4: 4, - - mat2: 4, - mat3: 9, - mat4: 16, - - sampler2D: 1, -}; - -/** - * @method mapSize - * @memberof PIXI.glCore.shader - * @param type {String} - * @return {Number} - */ -export default function mapSize(type) -{ - return GLSL_TO_SIZE[type]; -} diff --git a/packages/core/src/renderers/systems/shader/shader/mapType.js b/packages/core/src/renderers/systems/shader/shader/mapType.js deleted file mode 100644 index d288b7c..0000000 --- a/packages/core/src/renderers/systems/shader/shader/mapType.js +++ /dev/null @@ -1,45 +0,0 @@ -let GL_TABLE = null; - -const GL_TO_GLSL_TYPES = { - FLOAT: 'float', - FLOAT_VEC2: 'vec2', - FLOAT_VEC3: 'vec3', - FLOAT_VEC4: 'vec4', - - INT: 'int', - INT_VEC2: 'ivec2', - INT_VEC3: 'ivec3', - INT_VEC4: 'ivec4', - - BOOL: 'bool', - BOOL_VEC2: 'bvec2', - BOOL_VEC3: 'bvec3', - BOOL_VEC4: 'bvec4', - - FLOAT_MAT2: 'mat2', - FLOAT_MAT3: 'mat3', - FLOAT_MAT4: 'mat4', - - SAMPLER_2D: 'sampler2D', - SAMPLER_CUBE: 'samplerCube', - SAMPLER_2D_ARRAY: 'sampler2DArray', -}; - -export default function mapType(gl, type) -{ - if (!GL_TABLE) - { - const typeNames = Object.keys(GL_TO_GLSL_TYPES); - - GL_TABLE = {}; - - for (let i = 0; i < typeNames.length; ++i) - { - const tn = typeNames[i]; - - GL_TABLE[gl[tn]] = GL_TO_GLSL_TYPES[tn]; - } - } - - return GL_TABLE[type]; -} diff --git a/packages/core/src/renderers/systems/shader/shader/setPrecision.js b/packages/core/src/renderers/systems/shader/shader/setPrecision.js deleted file mode 100644 index e2c1f34..0000000 --- a/packages/core/src/renderers/systems/shader/shader/setPrecision.js +++ /dev/null @@ -1,18 +0,0 @@ -/** - * Sets the float precision on the shader. If the precision is already present this function will do nothing - * @param {string} src the shader source - * @param {string} precision The float precision of the shader. Options are 'lowp', 'mediump' or 'highp'. - * - * @return {string} modified shader source - */ -export default function setPrecision(src, precision) -{ - src = src.trim(); - - if (src.substring(0, 9) !== 'precision' && src.substring(0, 1) !== '#') - { - return `precision ${precision} float;\n${src}`; - } - - return src; -} diff --git a/packages/core/src/renderers/systems/textures/GLTexture.js b/packages/core/src/renderers/systems/textures/GLTexture.js deleted file mode 100644 index c3fb8dc..0000000 --- a/packages/core/src/renderers/systems/textures/GLTexture.js +++ /dev/null @@ -1,33 +0,0 @@ -export default class GLTexture -{ - constructor(texture) - { - /** - * The WebGL texture - * - * @member {WebGLTexture} - */ - this.texture = texture; - - this.width = -1; - this.height = -1; - - /** - * Texture contents dirty flag - * @member {number} - */ - this.dirtyId = -1; - - /** - * Texture style dirty flag - * @type {number} - */ - this.dirtyStyleId = -1; - - /** - * Whether mip levels has to be generated - * @type {boolean} - */ - this.mipmap = false; - } -} diff --git a/packages/core/src/renderers/systems/textures/TextureGCSystem.js b/packages/core/src/renderers/systems/textures/TextureGCSystem.js deleted file mode 100644 index b089938..0000000 --- a/packages/core/src/renderers/systems/textures/TextureGCSystem.js +++ /dev/null @@ -1,110 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; -import { GC_MODES } from '@pixi/constants'; -import { settings } from '@pixi/settings'; - -/** - * TextureGarbageCollector. This class manages the GPU and ensures that it does not get clogged - * up with textures that are no longer being used. - * - * @class - * @memberof PIXI - */ -export default class TextureGCSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this.count = 0; - this.checkCount = 0; - this.maxIdle = settings.GC_MAX_IDLE; - this.checkCountMax = settings.GC_MAX_CHECK_COUNT; - this.mode = settings.GC_MODE; - } - - /** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ - postrender() - { - this.count++; - - if (this.mode === GC_MODES.MANUAL) - { - return; - } - - this.checkCount++; - - if (this.checkCount > this.checkCountMax) - { - this.checkCount = 0; - - this.run(); - } - } - - /** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ - run() - { - const tm = this.renderer.texture; - const managedTextures = tm.managedTextures; - let wasRemoved = false; - - for (let i = 0; i < managedTextures.length; i++) - { - const texture = managedTextures[i]; - - // only supports non generated textures at the moment! - if (!texture.frameBuffer && this.count - texture.touched > this.maxIdle) - { - tm.destroyTexture(texture, true); - managedTextures[i] = null; - wasRemoved = true; - } - } - - if (wasRemoved) - { - let j = 0; - - for (let i = 0; i < managedTextures.length; i++) - { - if (managedTextures[i] !== null) - { - managedTextures[j++] = managedTextures[i]; - } - } - - managedTextures.length = j; - } - } - - /** - * Removes all the textures within the specified displayObject and its children from the GPU - * - * @param {PIXI.DisplayObject} displayObject - the displayObject to remove the textures from. - */ - unload(displayObject) - { - const tm = this.renderer.textureSystem; - - // only destroy non generated textures - if (displayObject._texture && displayObject._texture._glRenderTargets) - { - tm.destroyTexture(displayObject._texture); - } - - for (let i = displayObject.children.length - 1; i >= 0; i--) - { - this.unload(displayObject.children[i]); - } - } -} diff --git a/packages/core/src/renderers/systems/textures/TextureSystem.js b/packages/core/src/renderers/systems/textures/TextureSystem.js deleted file mode 100644 index ccc5de9..0000000 --- a/packages/core/src/renderers/systems/textures/TextureSystem.js +++ /dev/null @@ -1,281 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; -import GLTexture from './GLTexture'; -import { removeItems } from '@pixi/utils'; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class TextureSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - // TODO set to max textures... - this.boundTextures = [ - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - ]; - - this.currentLocation = -1; - - this.managedTextures = []; - } - - /** - * Sets up the renderer context and necessary buffers. - * - * @private - */ - contextChange() - { - const gl = this.gl = this.renderer.gl; - - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - - // TODO move this.. to a nice make empty textures class.. - this.emptyTextures = {}; - - const emptyTexture2D = new GLTexture(gl.createTexture()); - - gl.bindTexture(gl.TEXTURE_2D, emptyTexture2D.texture); - gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array(4)); - - this.emptyTextures[gl.TEXTURE_2D] = emptyTexture2D; - this.emptyTextures[gl.TEXTURE_CUBE_MAP] = new GLTexture(gl.createTexture()); - - gl.bindTexture(gl.TEXTURE_CUBE_MAP, this.emptyTextures[gl.TEXTURE_CUBE_MAP].texture); - - let i; - - for (i = 0; i < 6; i++) - { - gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, null); - } - - gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_MAG_FILTER, gl.LINEAR); - gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_MIN_FILTER, gl.LINEAR); - - for (i = 0; i < this.boundTextures.length; i++) - { - this.bind(null, i); - } - } - - bind(texture, location) - { - const gl = this.gl; - - location = location || 0; - - if (this.currentLocation !== location) - { - this.currentLocation = location; - gl.activeTexture(gl.TEXTURE0 + location); - } - - if (texture) - { - texture = texture.baseTexture || texture; - - if (texture.valid) - { - texture.touched = this.renderer.textureGC.count; - - const glTexture = texture._glTextures[this.CONTEXT_UID] || this.initTexture(texture); - - gl.bindTexture(texture.target, glTexture.texture); - - if (glTexture.dirtyId !== texture.dirtyId) - { - this.updateTexture(texture); - } - - this.boundTextures[location] = texture; - } - } - else - { - gl.bindTexture(gl.TEXTURE_2D, this.emptyTextures[gl.TEXTURE_2D].texture); - this.boundTextures[location] = null; - } - } - - unbind(texture) - { - const gl = this.gl; - - for (let i = 0; i < this.boundTextures.length; i++) - { - if (this.boundTextures[i] === texture) - { - if (this.currentLocation !== i) - { - gl.activeTexture(gl.TEXTURE0 + i); - this.currentLocation = i; - } - - gl.bindTexture(gl.TEXTURE_2D, this.emptyTextures[texture.target].texture); - this.boundTextures[i] = null; - } - } - } - - initTexture(texture) - { - const glTexture = new GLTexture(this.gl.createTexture()); - - // guarentee an update.. - glTexture.dirtyId = -1; - - texture._glTextures[this.CONTEXT_UID] = glTexture; - - this.managedTextures.push(texture); - texture.on('dispose', this.destroyTexture, this); - - return glTexture; - } - - updateTexture(texture) - { - const glTexture = texture._glTextures[this.CONTEXT_UID]; - const renderer = this.renderer; - - if (texture.resource && texture.resource.upload(renderer, texture, glTexture)) - { - // texture is uploaded, dont do anything! - } - else - { - // default, renderTexture-like logic - const width = texture.realWidth; - const height = texture.realHeight; - const gl = renderer.gl; - - if (glTexture.width !== width - || glTexture.height !== height - || glTexture.dirtyId < 0) - { - glTexture.width = width; - glTexture.height = height; - - gl.texImage2D(texture.target, 0, - texture.format, - width, - height, - 0, - texture.format, - texture.type, - null); - } - } - - // lets only update what changes.. - if (texture.dirtyStyleId !== glTexture.dirtyStyleId) - { - this.updateTextureStyle(texture); - } - glTexture.dirtyId = texture.dirtyId; - } - - /** - * Deletes the texture from WebGL - * - * @param {PIXI.BaseTexture|PIXI.Texture} texture - the texture to destroy - * @param {boolean} [skipRemove=false] - Whether to skip removing the texture from the TextureManager. - */ - destroyTexture(texture, skipRemove) - { - const gl = this.gl; - - texture = texture.baseTexture || texture; - - if (texture._glTextures[this.renderer.CONTEXT_UID]) - { - this.unbind(texture); - - gl.deleteTexture(texture._glTextures[this.renderer.CONTEXT_UID].texture); - texture.off('dispose', this.destroyTexture, this); - - delete texture._glTextures[this.renderer.CONTEXT_UID]; - - if (!skipRemove) - { - const i = this.managedTextures.indexOf(texture); - - if (i !== -1) - { - removeItems(this.managedTextures, i, 1); - } - } - } - } - - updateTextureStyle(texture) - { - const glTexture = texture._glTextures[this.CONTEXT_UID]; - - glTexture.mipmap = texture.mipmap && texture.isPowerOfTwo; - if (!glTexture) - { - return; - } - - if (texture.resource && texture.resource.style(this.renderer, texture, glTexture)) - { - // style is set, dont do anything! - } - else - { - this.setStyle(texture, glTexture); - } - - glTexture.dirtyStyleId = texture.dirtyStyleId; - } - - setStyle(texture, glTexture) - { - const gl = this.gl; - - if (glTexture.mipmap) - { - gl.generateMipmap(texture.target); - } - - gl.texParameteri(texture.target, gl.TEXTURE_WRAP_S, texture.wrapMode); - gl.texParameteri(texture.target, gl.TEXTURE_WRAP_T, texture.wrapMode); - - if (glTexture.mipmap) - { - /* eslint-disable max-len */ - gl.texParameteri(texture.target, gl.TEXTURE_MIN_FILTER, texture.scaleMode ? gl.LINEAR_MIPMAP_LINEAR : gl.NEAREST_MIPMAP_NEAREST); - /* eslint-disable max-len */ - } - else - { - gl.texParameteri(texture.target, gl.TEXTURE_MIN_FILTER, texture.scaleMode ? gl.LINEAR : gl.NEAREST); - } - - gl.texParameteri(texture.target, gl.TEXTURE_MAG_FILTER, texture.scaleMode ? gl.LINEAR : gl.NEAREST); - } -} diff --git a/packages/core/src/renderers/utils/ObjectRenderer.js b/packages/core/src/renderers/utils/ObjectRenderer.js deleted file mode 100644 index f67a273..0000000 --- a/packages/core/src/renderers/utils/ObjectRenderer.js +++ /dev/null @@ -1,48 +0,0 @@ -import WebGLSystem from '../systems/WebGLSystem'; - -/** - * Base for a common object renderer that can be used as a system renderer plugin. - * - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class ObjectRenderer extends WebGLSystem -{ - /** - * Starts the renderer and sets the shader - * - */ - start() - { - // set the shader.. - } - - /** - * Stops the renderer - * - */ - stop() - { - this.flush(); - } - - /** - * Stub method for rendering content and emptying the current batch. - * - */ - flush() - { - // flush! - } - - /** - * Renders an object - * - * @param {PIXI.DisplayObject} object - The object to render. - */ - render(object) // eslint-disable-line no-unused-vars - { - // render the object - } -} diff --git a/packages/core/src/AbstractRenderer.js b/packages/core/src/AbstractRenderer.js new file mode 100644 index 0000000..64b2eb1 --- /dev/null +++ b/packages/core/src/AbstractRenderer.js @@ -0,0 +1,353 @@ +import { hex2string, hex2rgb } from '@pixi/utils'; +import { Matrix, Rectangle } from '@pixi/math'; +import { RENDERER_TYPE } from '@pixi/constants'; +import { settings } from '@pixi/settings'; +import { Container } from '@pixi/display'; +import RenderTexture from './renderTexture/RenderTexture'; +import EventEmitter from 'eventemitter3'; + +const tempMatrix = new Matrix(); + +/** + * 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 EventEmitter + * @memberof PIXI + */ +export default class AbstractRenderer extends EventEmitter +{ + // eslint-disable-next-line valid-jsdoc + /** + * @param {string} system - The name of the system this renderer is for. + * @param {object} [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.transparent=false] - If the render view is transparent, default false + * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false + * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. The + * resolution of the renderer retina would be 2. + * @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 {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when rendering, + * stopping pixel interpolation. + */ + constructor(system, options, arg2, arg3) + { + super(); + + // Support for constructor(system, screenWidth, screenHeight, options) + if (typeof options === 'number') + { + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + }, arg3); + } + + // 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 = RENDERER_TYPE.UNKNOWN; + + /** + * Measurements of the screen. (0, 0, screenWidth, screenHeight) + * + * Its safe to use as filterArea or hitArea for 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 1 + */ + this.resolution = options.resolution || settings.RESOLUTION; + + /** + * Whether the render view is transparent + * + * @member {boolean} + */ + this.transparent = options.transparent; + + /** + * Whether css dimensions of canvas view should be resized to screen dimensions automatically + * + * @member {boolean} + */ + this.autoResize = options.autoResize || false; + + /** + * Tracks the blend modes useful for this renderer. + * + * @member {object} + */ + this.blendModes = null; + + /** + * 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; + + /** + * If true PixiJS will Math.floor() x/y values when rendering, stopping pixel interpolation. + * Handy for crisp pixel art and speed on legacy devices. + * + * @member {boolean} + */ + this.roundPixels = options.roundPixels; + + /** + * The background color as a number. + * + * @member {number} + * @private + */ + this._backgroundColor = 0x000000; + + /** + * The background color as an [R, G, B] array. + * + * @member {number[]} + * @private + */ + this._backgroundColorRgba = [0, 0, 0, 0]; + + /** + * The background color as a string. + * + * @member {string} + * @private + */ + this._backgroundColorString = '#000000'; + + this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter + + /** + * This temporary display object used as the parent of the currently being rendered item + * + * @member {PIXI.DisplayObject} + * @private + */ + this._tempDisplayObjectParent = new Container(); + + /** + * The last root object that the renderer tried to render. + * + * @member {PIXI.DisplayObject} + * @private + */ + this._lastObjectRendered = this._tempDisplayObjectParent; + + /** + * Collection of plugins. + * @readonly + * @member {object} + */ + this.plugins = {}; + } + + /** + * Initialize the plugins. + * + * @protected + * @param {object} staticMap - The dictionary of staticly saved plugins. + */ + initPlugins(staticMap) + { + 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() + { + return this.view.width; + } + + /** + * Same as view.height, actual number of pixels in the canvas by vertical + * + * @member {number} + * @readonly + * @default 600 + */ + get height() + { + return this.view.height; + } + + /** + * Resizes the screen and canvas to the specified width and height + * Canvas dimensions are multiplied by resolution + * + * @param {number} screenWidth - the new width of the screen + * @param {number} screenHeight - the new height of the screen + */ + resize(screenWidth, screenHeight) + { + this.screen.width = screenWidth; + this.screen.height = screenHeight; + + this.view.width = screenWidth * this.resolution; + this.view.height = screenHeight * this.resolution; + + if (this.autoResize) + { + this.view.style.width = `${screenWidth}px`; + this.view.style.height = `${screenHeight}px`; + } + } + + /** + * 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 {PIXI.DisplayObject} displayObject - The displayObject the object will be generated from + * @param {number} scaleMode - Should be one of the scaleMode consts + * @param {number} resolution - The resolution / device pixel ratio of the texture being generated + * @param {PIXI.Rectangle} [region] - The region of the displayObject, that shall be rendered, + * if no region is specified, defaults to the local bounds of the displayObject. + * @return {PIXI.Texture} a texture of the graphics object + */ + generateTexture(displayObject, scaleMode, resolution, region) + { + region = region || displayObject.getLocalBounds(); + + // 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(region.width | 0, region.height | 0, scaleMode, resolution); + + tempMatrix.tx = -region.x; + tempMatrix.ty = -region.y; + + this.render(displayObject, renderTexture, false, tempMatrix, true); + + return renderTexture; + } + + /** + * Removes everything from the renderer and optionally removes the Canvas DOM element. + * + * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. + */ + destroy(removeView) + { + 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); + } + + this.plugins = null; + + this.type = RENDERER_TYPE.UNKNOWN; + + this.view = null; + + this.screen = null; + + this.resolution = 0; + + this.transparent = false; + + this.autoResize = false; + + this.blendModes = null; + + this.options = null; + + this.preserveDrawingBuffer = false; + this.clearBeforeRender = false; + + this.roundPixels = false; + + this._backgroundColor = 0; + this._backgroundColorRgba = null; + this._backgroundColorString = null; + + this._tempDisplayObjectParent = null; + this._lastObjectRendered = null; + } + + /** + * The background color to fill if not transparent + * + * @member {number} + */ + get backgroundColor() + { + return this._backgroundColor; + } + + set backgroundColor(value) // eslint-disable-line require-jsdoc + { + this._backgroundColor = value; + this._backgroundColorString = hex2string(value); + hex2rgb(value, this._backgroundColorRgba); + } +} diff --git a/packages/core/src/Renderer.js b/packages/core/src/Renderer.js new file mode 100644 index 0000000..2227e78 --- /dev/null +++ b/packages/core/src/Renderer.js @@ -0,0 +1,338 @@ +import AbstractRenderer from './AbstractRenderer'; +import { sayHello } from '@pixi/utils'; +import MaskSystem from './mask/MaskSystem'; +import StencilSystem from './mask/StencilSystem'; +import FilterSystem from './filters/FilterSystem'; +import FramebufferSystem from './framebuffer/FramebufferSystem'; +import RenderTextureSystem from './renderTexture/RenderTextureSystem'; +import TextureSystem from './textures/TextureSystem'; +import ProjectionSystem from './projection/ProjectionSystem'; +import StateSystem from './state/StateSystem'; +import GeometrySystem from './geometry/GeometrySystem'; +import ShaderSystem from './shader/ShaderSystem'; +import ContextSystem from './context/ContextSystem'; +import BatchSystem from './batch/BatchSystem'; +import TextureGCSystem from './textures/TextureGCSystem'; +import { RENDERER_TYPE } from '@pixi/constants'; +import UniformGroup from './shader/UniformGroup'; +import { Matrix } from '@pixi/math'; +import Runner from 'mini-runner'; + +/** + * 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 Render works by automatically managing webGLBatchs. + * 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 :) + * + * @class + * @memberof PIXI + * @extends PIXI.AbstractRenderer + */ +export default class Renderer extends AbstractRenderer +{ + // eslint-disable-next-line valid-jsdoc + /** + * + * @param {object} [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.transparent=false] - If the render view is transparent, default false + * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false + * @param {boolean} [options.antialias=false] - sets antialias. If not available natively then FXAA + * antialiasing is used + * @param {boolean} [options.forceFXAA=false] - forces FXAA antialiasing to be used over native. + * FXAA is faster, but may not always look as great + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. + * The resolution of the renderer retina would be 2. + * @param {boolean} [options.clearBeforeRender=true] - This sets if the renderer will clear + * the canvas or not before the new render pass. If you wish to set this to false, you *must* set + * preserveDrawingBuffer to `true`. + * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, + * enable this if you need to call toDataUrl on the webgl context. + * @param {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when + * rendering, stopping pixel interpolation. + * @param {number} [options.backgroundColor=0x000000] - The background color of the rendered area + * (shown if not transparent). + * @param {boolean} [options.legacy=false] - If true PixiJS will aim to ensure compatibility + * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. + * @param {string} [options.powerPreference] - Parameter passed to webgl context, set to "high-performance" + * for devices with dual graphics card + */ + constructor(options, arg2, arg3) + { + super('WebGL', options, arg2, arg3); + + /** + * The type of this renderer as a standardised const + * + * @member {number} + * @see PIXI.RENDERER_TYPE + */ + this.type = RENDERER_TYPE.WEBGL; + + // this will be set by the contextSystem (this.context) + this.gl = null; + this.CONTEXT_UID = 0; + this.legacy = !!options.legacy; + + // TODO legacy! + + // runners! + this.runners = { + destroy: new Runner('destroy'), + contextChange: new Runner('contextChange', 1), + reset: new Runner('reset'), + update: new Runner('update'), + postrender: new Runner('postrender'), + prerender: new Runner('prerender'), + resize: new Runner('resize', 2), + }; + + 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(GeometrySystem, 'geometry') + .addSystem(FramebufferSystem, 'framebuffer') + .addSystem(StencilSystem, 'stencil') + .addSystem(ProjectionSystem, 'projection') + .addSystem(TextureGCSystem, 'textureGC') + .addSystem(FilterSystem, 'filter') + .addSystem(RenderTextureSystem, 'renderTexture') + .addSystem(BatchSystem, 'batch'); + + this.initPlugins(Renderer.__plugins); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ + if (options.context) + { + this.context.initFromContext(options.context); + } + else + { + this.context.initFromOptions({ + alpha: this.transparent, + antialias: options.antialias, + premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', + stencil: true, + 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); + } + + /** + * Add a new system to the renderer. + * @param {class} ClassRef - Class reference + * @param {string} [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. + * @return {PIXI.Renderer} Return instance of renderer + */ + addSystem(ClassRef, name) + { + if (!name) + { + name = ClassRef.name; + } + + // TODO - read name from class.name.. + + /* + if(name.includes('System')) + { + name = name.replace('System', ''); + name = name.charAt(0).toLowerCase() + name.slice(1); + } + */ + + const system = new ClassRef(this); + + if (this[name]) + { + throw new Error(`Whoops! ${name} is already a manger`); + } + + this[name] = system; + + for (const i in this.runners) + { + this.runners[i].add(system); + } + + return this; + + /** + * 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. + */ + } + + /** + * Renders the object to its webGL view + * + * @param {PIXI.DisplayObject} displayObject - the object to be rendered + * @param {PIXI.RenderTexture} renderTexture - The render texture to render to. + * @param {boolean} [clear] - Should the canvas be cleared before the new render + * @param {PIXI.Transform} [transform] - A transform to apply to the render texture before rendering. + * @param {boolean} [skipUpdateTransform] - Should we skip the update transform pass? + */ + render(displayObject, renderTexture, clear, transform, skipUpdateTransform) + { + // can be handy to know! + this.renderingToScreen = !renderTexture; + + this.runners.prerender.run(); + this.emit('prerender'); + + // 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.parent; + + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = 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.run(); + + this.emit('postrender'); + } + + /** + * Resizes the webGL view to the specified width and height. + * + * @param {number} screenWidth - the new width of the screen + * @param {number} screenHeight - the new height of the screen + */ + resize(screenWidth, screenHeight) + { + AbstractRenderer.prototype.resize.call(this, screenWidth, screenHeight); + + this.runners.resize.run(screenWidth, screenHeight); + } + + /** + * Resets the WebGL state so you can render things however you fancy! + * + * @return {PIXI.Renderer} Returns itself. + */ + reset() + { + this.runners.reset.run(); + + return this; + } + + clear() + { + this.framebuffer.bind(); + this.framebuffer.clear(); + } + + /** + * Removes everything from the renderer (event listeners, spritebatch, etc...) + * + * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. + * See: https://github.com/pixijs/pixi.js/issues/2233 + */ + destroy(removeView) + { + this.runners.destroy.run(); + + // call base destroy + super.destroy(removeView); + + // TODO nullify all the managers.. + this.gl = null; + } + + /** + * 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 + * builds and excluding plugins. + * @name PIXI.Renderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. + */ + + /** + * Adds a plugin to the renderer. + * + * @method + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + static registerPlugin(pluginName, ctor) + { + Renderer.__plugins = Renderer.__plugins || {}; + Renderer.__plugins[pluginName] = ctor; + } +} diff --git a/packages/core/src/System.js b/packages/core/src/System.js new file mode 100644 index 0000000..05a10c8 --- /dev/null +++ b/packages/core/src/System.js @@ -0,0 +1,42 @@ +/** + * System is a base class used for extending systems used by the {@link PIXI.Renderer} + * @see PIXI.Renderer#addSystem + * @class + * @memberof PIXI + */ +export default class System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + /** + * The renderer this manager works for. + * + * @member {PIXI.Renderer} + */ + this.renderer = renderer; + + this.renderer.runners.contextChange.add(this); + } + + /** + * Generic method called when there is a WebGL context change. + * + */ + contextChange() + { + // do some codes init! + } + + /** + * Generic destroy methods to be overridden by the subclass + * + */ + destroy() + { + this.renderer.runners.contextChange.remove(this); + this.renderer = null; + } +} diff --git a/packages/core/src/batch/BatchSystem.js b/packages/core/src/batch/BatchSystem.js new file mode 100644 index 0000000..b04c249 --- /dev/null +++ b/packages/core/src/batch/BatchSystem.js @@ -0,0 +1,68 @@ +import System from '../System'; +import ObjectRenderer from './ObjectRenderer'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class BatchSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + super(renderer); + + /** + * An empty renderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.emptyRenderer = new ObjectRenderer(renderer); + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.currentRenderer = this.emptyRenderer; + } + + /** + * Changes the current renderer to the one given in parameter + * + * @param {PIXI.ObjectRenderer} objectRenderer - The object renderer to use. + */ + setObjectRenderer(objectRenderer) + { + if (this.currentRenderer === objectRenderer) + { + return; + } + + this.currentRenderer.stop(); + this.currentRenderer = objectRenderer; + + this.renderer.state.setState(objectRenderer.state); + + this.currentRenderer.start(); + } + + /** + * This should be called if you wish to do some custom rendering + * It will basically render anything that may be batched up such as sprites + * + */ + flush() + { + this.setObjectRenderer(this.emptyRenderer); + } + + reset() + { + this.setObjectRenderer(this.emptyRenderer); + } +} diff --git a/packages/core/src/batch/ObjectRenderer.js b/packages/core/src/batch/ObjectRenderer.js new file mode 100644 index 0000000..0c851a9 --- /dev/null +++ b/packages/core/src/batch/ObjectRenderer.js @@ -0,0 +1,48 @@ +import System from '../System'; + +/** + * Base for a common object renderer that can be used as a system renderer plugin. + * + * @class + * @extends PIXI.System + * @memberof PIXI + */ +export default class ObjectRenderer extends System +{ + /** + * Starts the renderer and sets the shader + * + */ + start() + { + // set the shader.. + } + + /** + * Stops the renderer + * + */ + stop() + { + this.flush(); + } + + /** + * Stub method for rendering content and emptying the current batch. + * + */ + flush() + { + // flush! + } + + /** + * Renders an object + * + * @param {PIXI.DisplayObject} object - The object to render. + */ + render(object) // eslint-disable-line no-unused-vars + { + // render the object + } +} diff --git a/packages/core/src/context/ContextSystem.js b/packages/core/src/context/ContextSystem.js new file mode 100644 index 0000000..301aab4 --- /dev/null +++ b/packages/core/src/context/ContextSystem.js @@ -0,0 +1,186 @@ +import System from '../System'; +import { settings } from '@pixi/settings'; + +let CONTEXT_UID = 0; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class ContextSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.webGLVersion = 1; + + this.handleContextLost = this.handleContextLost.bind(this); + this.handleContextRestored = this.handleContextRestored.bind(this); + + this.extensions = {}; + + renderer.view.addEventListener('webglcontextlost', this.handleContextLost, false); + renderer.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + } + + get isLost() + { + return (!this.gl || this.gl.isContextLost()); + } + + contextChange(gl) + { + this.gl = gl; + + // restore a context if it was previously lost + if (gl.isContextLost() && gl.getExtension('WEBGL_lose_context')) + { + gl.getExtension('WEBGL_lose_context').restoreContext(); + } + } + + initFromContext(gl) + { + this.gl = gl; + this.validateContext(gl); + this.renderer.gl = gl; + this.renderer.CONTEXT_UID = CONTEXT_UID++; + this.renderer.runners.contextChange.run(gl); + } + + initFromOptions(options) + { + const gl = this.createContext(this.renderer.view, options); + + this.initFromContext(gl); + } + + /** + * Helper class to create a webGL Context + * + * @class + * @memberof PIXI.glCore + * @param canvas {HTMLCanvasElement} the canvas element that we will get the context from + * @param options {Object} An options object that gets passed in to the canvas element containing the context attributes, + * see https://developer.mozilla.org/en/docs/Web/API/HTMLCanvasElement/getContext for the options available + * @return {WebGLRenderingContext} the WebGL context + */ + createContext(canvas, options) + { + let gl; + + if (settings.PREFER_WEBGL_2) + { + gl = canvas.getContext('webgl2', options); + } + + if (gl) + { + this.webGLVersion = 2; + } + else + { + this.webGLVersion = 1; + + gl = canvas.getContext('webgl', options) + || canvas.getContext('experimental-webgl', options); + + if (!gl) + { + // fail, not able to get a context + throw new Error('This browser does not support webGL. Try using the canvas renderer'); + } + } + + this.gl = gl; + + this.getExtensions(); + + return gl; + } + + getExtensions() + { + // time to set up default etensions that pixi uses.. + const gl = this.gl; + const extensions = this.extensions; + + if (this.webGLVersion === 1) + { + extensions.drawBuffers = gl.getExtension('WEBGL_draw_buffers'); + extensions.depthTexture = gl.getExtension('WEBKIT_WEBGL_depth_texture'); + extensions.floatTexture = gl.getExtension('OES_texture_float'); + extensions.loseContext = gl.getExtension('WEBGL_lose_context'); + + extensions.vertexArrayObject = gl.getExtension('OES_vertex_array_object') + || gl.getExtension('MOZ_OES_vertex_array_object') + || gl.getExtension('WEBKIT_OES_vertex_array_object'); + } + + // we don't use any specific WebGL 2 ones yet! + } + + /** + * Handles a lost webgl context + * + * @private + * @param {WebGLContextEvent} event - The context lost event. + */ + handleContextLost(event) + { + event.preventDefault(); + } + + /** + * Handles a restored webgl context + * + * @private + */ + handleContextRestored() + { + this.renderer.runners.contextChange.run(this.gl); + } + + destroy() + { + const view = this.renderer.view; + + // remove listeners + view.removeEventListener('webglcontextlost', this.handleContextLost); + view.removeEventListener('webglcontextrestored', this.handleContextRestored); + + this.gl.useProgram(null); + + if (this.extensions.loseContext) + { + this.extensions.loseContext.loseContext(); + } + } + + postrender() + { + this.gl.flush(); + } + + validateContext(gl) + { + const attributes = gl.getContextAttributes(); + + // this is going to be fairly simple for now.. but at least we have room to grow! + if (!attributes.stencil) + { + /* eslint-disable max-len */ + + /* eslint-disable no-console */ + console.warn('Provided WebGL context does not have a stencil buffer, masks may not render correctly'); + /* eslint-enable no-console */ + + /* eslint-enable max-len */ + } + } +} diff --git a/packages/core/src/filters/Filter.js b/packages/core/src/filters/Filter.js new file mode 100644 index 0000000..9d8cad8 --- /dev/null +++ b/packages/core/src/filters/Filter.js @@ -0,0 +1,140 @@ +import Shader from '../shader/Shader'; +import Program from '../shader/Program'; +import State from '../state/State'; +import { settings } from '@pixi/settings'; +// import extractUniformsFromSrc from './extractUniformsFromSrc'; +import defaultVertex from './defaultFilter.vert'; +import defaultFragment from './defaultFilter.frag'; + +// let math = require('../../math'); +/** + * @class + * @memberof PIXI + * @extends PIXI.Shader + */ +export default class Filter extends Shader +{ + /** + * @param {string} [vertexSrc] - The source of the vertex shader. + * @param {string} [fragmentSrc] - The source of the fragment shader. + * @param {object} [uniforms] - Custom uniforms to use to augment the built-in ones. + */ + constructor(vertexSrc, fragmentSrc, uniforms) + { + const program = Program.from(vertexSrc, fragmentSrc); + + super(program, uniforms); + + /** + * The padding of the filter. Some filters require extra space to breath such as a blur. + * Increasing this will add extra width and height to the bounds of the object that the + * filter is applied to. + * + * @member {number} + */ + this.padding = 0; + + /** + * The resolution of the filter. Setting this to be lower will lower the quality but + * increase the performance of the filter. + * + * @member {number} + */ + this.resolution = settings.RESOLUTION; + + /** + * If enabled is true the filter is applied, if false it will not. + * + * @member {boolean} + */ + this.enabled = true; + + /** + * If enabled, PixiJS will fit the filter area into boundaries for better performance. + * Switch it off if it does not work for specific shader. + * + * @member {boolean} + */ + this.autoFit = true; + + /** + * Legacy filters use position and uvs from attributes + * @member {boolean} + * @readonly + */ + this.legacy = !!this.program.attributeData.aTextureCoord; + + /** + * the webGL state the filter requires to render + * @member {PIXI.State} + */ + this.state = new State(); + } + + /** + * Applies the filter + * + * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + * @param {boolean} clear - Should the output be cleared before rendering to it + * @param {object} [currentState] - It's current state of filter. + * There are some useful properties in the currentState : + * target, filters, sourceFrame, destinationFrame, renderTarget, resolution + */ + apply(filterManager, input, output, clear, currentState, derp) // eslint-disable-line no-unused-vars + { + // do as you please! + + filterManager.applyFilter(this, input, output, clear, currentState, derp); + + // or just do a regular render.. + } + + /** + * Sets the blendmode of the filter + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL + */ + get blendMode() + { + return this.state.blendMode; + } + + set blendMode(value) // eslint-disable-line require-jsdoc + { + this.state.blendMode = value; + } + + /** + * The default vertex shader source + * + * @static + * @constant + */ + static get defaultVertexSrc() + { + return defaultVertex; + } + + /** + * The default fragment shader source + * + * @static + * @constant + */ + static get defaultFragmentSrc() + { + return defaultFragment; + } +} + +/** + * Used for caching shader IDs + * + * @static + * @private + */ +Filter.SOURCE_KEY_MAP = {}; + diff --git a/packages/core/src/filters/FilterSystem.js b/packages/core/src/filters/FilterSystem.js new file mode 100644 index 0000000..3174318 --- /dev/null +++ b/packages/core/src/filters/FilterSystem.js @@ -0,0 +1,431 @@ +import System from '../System'; + +import RenderTexture from '../renderTexture/RenderTexture'; +import Quad from '../utils/Quad'; +import QuadUv from '../utils/QuadUv'; +import { Rectangle } from '@pixi/math'; +import * as filterTransforms from './filterTransforms'; +import bitTwiddle from 'bit-twiddle'; +import UniformGroup from '../shader/UniformGroup'; +import { DRAW_MODES } from '@pixi/constants'; + +// +/** + * @ignore + * @class + */ +class FilterState +{ + /** + * + */ + constructor() + { + this.renderTexture = null; + this.sourceFrame = new Rectangle(); + this.destinationFrame = new Rectangle(); + this.filters = []; + this.target = null; + this.legacy = false; + this.resolution = 1; + } +} + +/** + * @class + * @memberof PIXI.systems + * @extends PIXI.System + */ +export default class FilterSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + /** + * stores a bunch of PO2 textures used for filtering + * @type {Object} + */ + this.texturePool = {}; + + /** + * a pool for storing filter states, save us creating new ones each tick + * @type {Array} + */ + this.statePool = []; + + /** + * A very simple geometry used when drawing a filter effect to the screen + * @type {Quad} + */ + this.quad = new Quad(); + + this.quadUv = new QuadUv(); + + /** + * Temporary rect for maths + * @type {PIXI.Rectangle} + */ + this.tempRect = new Rectangle(); + + this.activeState = {}; + + /** + * this uniform group is attached to filter uniforms when used + * @type {UniformGroup} + */ + this.globalUniforms = new UniformGroup({ + sourceFrame: this.tempRect, + destinationFrame: this.tempRect, + + // legacy variables + filterArea: new Float32Array(4), + filterClamp: new Float32Array(4), + }, true); + } + + /** + * Adds a new filter to the System. + * + * @param {PIXI.DisplayObject} target - The target of the filter to render. + * @param {PIXI.Filter[]} filters - The filters to apply. + */ + push(target, filters) + { + const renderer = this.renderer; + const filterStack = this.renderer.renderTexture.defaultFilterStack; + const state = this.statePool.pop() || new FilterState(); + + let resolution = filters[0].resolution; + let padding = filters[0].padding; + let autoFit = filters[0].autoFit; + let legacy = filters[0].legacy; + + for (let i = 1; i < filters.length; i++) + { + const filter = filters[i]; + + // lets use the lowest resolution.. + resolution = Math.min(resolution, filter.resolution); + // and the largest amount of padding! + padding = Math.max(padding, filter.padding); + // only auto fit if all filters are autofit + autoFit = autoFit || filter.autoFit; + + legacy = legacy || filter.legacy; + } + + filterStack.push(state); + + state.resolution = resolution; + + state.legacy = legacy; + + // round to whole number based on resolution + // TODO move that to the shader too? + state.sourceFrame = target.filterArea || target.getBounds(true); + + state.sourceFrame.pad(padding); + + if (autoFit) + { + state.sourceFrame.fit(this.renderer.renderTexture.destinationFrame); + } + + state.sourceFrame.round(resolution); + + state.renderTexture = this.getPotFilterTexture(state.sourceFrame.width, state.sourceFrame.height, resolution); + state.filters = filters; + + state.destinationFrame.width = state.renderTexture.width; + state.destinationFrame.height = state.renderTexture.height; + + state.renderTexture.filterFrame = state.sourceFrame; + renderer.renderTexture.bind(state.renderTexture, state.sourceFrame);// /, state.destinationFrame); + renderer.renderTexture.clear(); + } + + /** + * Pops off the filter and applies it. + * + */ + pop() + { + const renderer = this.renderer; + const filterStack = renderer.renderTexture.defaultFilterStack; + const state = filterStack.pop(); + const filters = state.filters; + + this.activeState = state; + + const globalUniforms = this.globalUniforms.uniforms; + + globalUniforms.sourceFrame = state.sourceFrame; + globalUniforms.destinationFrame = state.destinationFrame; + globalUniforms.resolution = state.resolution; + + // only update the rect if its legacy.. + if (state.legacy) + { + const filterArea = globalUniforms.filterArea; + const filterClamp = globalUniforms.filterClamp; + + filterArea[0] = state.destinationFrame.width; + filterArea[1] = state.destinationFrame.height; + filterArea[2] = state.sourceFrame.x; + filterArea[3] = state.sourceFrame.y; + + filterClamp[0] = 0.5 / state.resolution / state.destinationFrame.width; + filterClamp[1] = 0.5 / state.resolution / state.destinationFrame.height; + filterClamp[2] = (state.sourceFrame.width - 0.5) / state.resolution / state.destinationFrame.width; + filterClamp[3] = (state.sourceFrame.height - 0.5) / state.resolution / state.destinationFrame.height; + } + + this.globalUniforms.update(); + + const lastState = filterStack[filterStack.length - 1]; + + if (filters.length === 1) + { + filters[0].apply(this, state.renderTexture, lastState.renderTexture, false, state); + + this.returnFilterTexture(state.renderTexture); + } + else + { + let flip = state.renderTexture; + let flop = this.getPotFilterTexture( + flip.width, + flip.height, + state.resolution + ); + + flop.filterFrame = flip.filterFrame; + + let i = 0; + + for (i = 0; i < filters.length - 1; ++i) + { + filters[i].apply(this, flip, flop, true, state); + + const t = flip; + + flip = flop; + flop = t; + } + + filters[i].apply(this, flip, lastState.renderTexture, false, state); + + this.returnFilterTexture(flip); + this.returnFilterTexture(flop); + } + + this.statePool.push(state); + } + + /** + * Draws a filter. + * + * @param {PIXI.Filter} filter - The filter to draw. + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + * @param {boolean} clear - Should the output be cleared before rendering to it + */ + applyFilter(filter, input, output, clear) + { + const renderer = this.renderer; + + renderer.renderTexture.bind(output, output ? output.filterFrame : null); + + if (clear) + { + // gl.disable(gl.SCISSOR_TEST); + renderer.renderTexture.clear(); + // gl.enable(gl.SCISSOR_TEST); + } + + // set the uniforms.. + filter.uniforms.uSampler = input; + filter.uniforms.filterGlobals = this.globalUniforms; + + // TODO make it so that the order of this does not matter.. + // because it does at the moment cos of global uniforms. + // they need to get resynced + + renderer.state.setState(filter.state); + renderer.shader.bind(filter); + + if (filter.legacy) + { + this.quadUv.map(input._frame, input.filterFrame); + + renderer.geometry.bind(this.quadUv); + renderer.geometry.draw(DRAW_MODES.TRIANGLES); + } + else + { + renderer.geometry.bind(this.quad); + renderer.geometry.draw(DRAW_MODES.TRIANGLE_STRIP); + } + } + + /** + * Calculates the mapped matrix. + * + * TODO playing around here.. this is temporary - (will end up in the shader) + * this returns a matrix that will normalise map filter cords in the filter to screen space + * + * @param {PIXI.Matrix} outputMatrix - the matrix to output to. + * @return {PIXI.Matrix} The mapped matrix. + */ + calculateScreenSpaceMatrix(outputMatrix) + { + const currentState = this.activeState; + + return filterTransforms.calculateScreenSpaceMatrix( + outputMatrix, + currentState.sourceFrame, + currentState.destinationFrame + ); + } + + /** + * This will map the filter coord so that a texture can be used based on the transform of a sprite + * + * @param {PIXI.Matrix} outputMatrix - The matrix to output to. + * @param {PIXI.Sprite} sprite - The sprite to map to. + * @return {PIXI.Matrix} The mapped matrix. + */ + calculateSpriteMatrix(outputMatrix, sprite) + { + const currentState = this.activeState; + + return filterTransforms.calculateSpriteMatrix( + outputMatrix, + currentState.sourceFrame, + currentState.destinationFrame, + sprite + ); + } + + /** + * Destroys this Filter System. + * + * @param {boolean} [contextLost=false] context was lost, do not free shaders + * + */ + destroy(contextLost = false) + { + if (!contextLost) + { + this.emptyPool(); + } + else + { + this.texturePool = {}; + } + } + + /** + * Gets a Power-of-Two render texture. + * + * TODO move to a seperate class could be on renderer? + * also - could cause issue with multiple contexts? + * + * @private + * @param {WebGLRenderingContext} gl - The webgl rendering context + * @param {number} minWidth - The minimum width of the render target. + * @param {number} minHeight - The minimum height of the render target. + * @param {number} resolution - The resolution of the render target. + * @return {PIXI.RenderTarget} The new render target. + */ + getPotFilterTexture(minWidth, minHeight, resolution) + { + minWidth = bitTwiddle.nextPow2(minWidth); + minHeight = bitTwiddle.nextPow2(minHeight); + resolution = resolution || 1; + + const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + + if (!this.texturePool[key]) + { + this.texturePool[key] = []; + } + + let renderTexture = this.texturePool[key].pop(); + + if (!renderTexture) + { + // temporary bypass cache.. + // internally - this will cause a texture to be bound.. + renderTexture = RenderTexture.create({ + width: minWidth, + height: minHeight, + resolution, + }); + } + + return renderTexture; + } + + /** + * Gets extra render texture to use inside current filter + * + * @param {number} resolution resolution of the renderTexture + * @returns {PIXI.RenderTexture} + */ + getFilterTexture(resolution) + { + const rt = this.activeState.renderTexture; + + const filterTexture = this.getPotFilterTexture(rt.width, rt.height, resolution || rt.baseTexture.resolution); + + filterTexture.filterFrame = rt.filterFrame; + + return filterTexture; + } + + /** + * Frees a render texture back into the pool. + * + * @param {PIXI.RenderTarget} renderTexture - The renderTarget to free + */ + returnFilterTexture(renderTexture) + { + renderTexture.filterFrame = null; + + const base = renderTexture.baseTexture; + + const minWidth = base.width; + const minHeight = base.height; + + const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + + this.texturePool[key].push(renderTexture); + } + + /** + * Empties the texture pool. + * + */ + emptyPool() + { + for (const i in this.texturePool) + { + const textures = this.texturePool[i]; + + if (textures) + { + for (let j = 0; j < textures.length; j++) + { + textures[j].destroy(true); + } + } + } + + this.texturePool = {}; + } +} diff --git a/packages/core/src/filters/defaultFilter.frag b/packages/core/src/filters/defaultFilter.frag new file mode 100644 index 0000000..a14c1be --- /dev/null +++ b/packages/core/src/filters/defaultFilter.frag @@ -0,0 +1,22 @@ +varying vec2 vTextureCoord; +varying vec2 vFilterCoord; + +uniform sampler2D uSampler; +uniform sampler2D filterSampler; + +void main(void){ + vec4 masky = texture2D(filterSampler, vFilterCoord); + vec4 sample = texture2D(uSampler, vTextureCoord); + vec4 color; + if(mod(vFilterCoord.x, 1.0) > 0.5) + { + color = vec4(1.0, 0.0, 0.0, 1.0); + } + else + { + color = vec4(0.0, 1.0, 0.0, 1.0); + } + // gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0); + gl_FragColor = mix(sample, masky, 0.5); + gl_FragColor *= sample.a; +} diff --git a/packages/core/src/filters/defaultFilter.vert b/packages/core/src/filters/defaultFilter.vert new file mode 100644 index 0000000..e1febca --- /dev/null +++ b/packages/core/src/filters/defaultFilter.vert @@ -0,0 +1,14 @@ +attribute vec2 aVertexPosition; +attribute vec2 aTextureCoord; + +uniform mat3 projectionMatrix; +uniform mat3 filterMatrix; + +varying vec2 vTextureCoord; +varying vec2 vFilterCoord; + +void main(void){ + gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); + vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy; + vTextureCoord = aTextureCoord ; +} diff --git a/packages/core/src/filters/filterTransforms.js b/packages/core/src/filters/filterTransforms.js new file mode 100644 index 0000000..87a3cd6 --- /dev/null +++ b/packages/core/src/filters/filterTransforms.js @@ -0,0 +1,50 @@ +import { Matrix } from '@pixi/math'; + +/** + * Calculates the mapped matrix + * @param filterArea {Rectangle} The filter area + * @param sprite {Sprite} the target sprite + * @param outputMatrix {Matrix} @alvin + * @private + */ +// this returns a matrix that will normalise map filter cords in the filter to screen space +export function calculateScreenSpaceMatrix(outputMatrix, filterArea, textureSize) +{ + // TODO unwrap? + const mappedMatrix = outputMatrix.identity(); + + mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); + + mappedMatrix.scale(textureSize.width, textureSize.height); + + return mappedMatrix; +} + +export function calculateNormalizedScreenSpaceMatrix(outputMatrix, filterArea, textureSize) +{ + const mappedMatrix = outputMatrix.identity(); + + mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); + + const translateScaleX = (textureSize.width / filterArea.width); + const translateScaleY = (textureSize.height / filterArea.height); + + mappedMatrix.scale(translateScaleX, translateScaleY); + + return mappedMatrix; +} + +// this will map the filter coord so that a texture can be used based on the transform of a sprite +export function calculateSpriteMatrix(outputMatrix, filterArea, textureSize, sprite) +{ + const orig = sprite._texture.orig; + const mappedMatrix = outputMatrix.set(textureSize.width, 0, 0, textureSize.height, filterArea.x, filterArea.y); + const worldTransform = sprite.worldTransform.copyTo(Matrix.TEMP_MATRIX); + + worldTransform.invert(); + mappedMatrix.prepend(worldTransform); + mappedMatrix.scale(1.0 / orig.width, 1.0 / orig.height); + mappedMatrix.translate(sprite.anchor.x, sprite.anchor.y); + + return mappedMatrix; +} diff --git a/packages/core/src/filters/spriteMask/SpriteMaskFilter.js b/packages/core/src/filters/spriteMask/SpriteMaskFilter.js new file mode 100644 index 0000000..91697b7 --- /dev/null +++ b/packages/core/src/filters/spriteMask/SpriteMaskFilter.js @@ -0,0 +1,63 @@ +import Filter from '../Filter'; +import { Matrix } from '@pixi/math'; +import vertex from './spriteMaskFilter.vert'; +import fragment from './spriteMaskFilter.frag'; +import { default as TextureMatrix } from '../../textures/TextureMatrix'; + +/** + * The SpriteMaskFilter class + * + * @class + * @extends PIXI.Filter + * @memberof PIXI + */ +export default class SpriteMaskFilter extends Filter +{ + /** + * @param {PIXI.Sprite} sprite - the target sprite + */ + constructor(sprite) + { + const maskMatrix = new Matrix(); + + super(vertex, fragment); + + sprite.renderable = false; + + this.maskSprite = sprite; + this.maskMatrix = maskMatrix; + } + + /** + * Applies the filter + * + * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + */ + apply(filterManager, input, output) + { + const maskSprite = this.maskSprite; + const tex = this.maskSprite.texture; + + if (!tex.valid) + { + return; + } + if (!tex.transform) + { + // margin = 0.0, let it bleed a bit, shader code becomes easier + // assuming that atlas textures were made with 1-pixel padding + tex.transform = new TextureMatrix(tex, 0.0); + } + tex.transform.update(); + + this.uniforms.mask = tex; + this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite) + .prepend(tex.transform.mapCoord); + this.uniforms.alpha = maskSprite.worldAlpha; + this.uniforms.maskClamp = tex.transform.uClampFrame; + + filterManager.applyFilter(this, input, output); + } +} diff --git a/packages/core/src/filters/spriteMask/spriteMaskFilter.frag b/packages/core/src/filters/spriteMask/spriteMaskFilter.frag new file mode 100644 index 0000000..0e4aef8 --- /dev/null +++ b/packages/core/src/filters/spriteMask/spriteMaskFilter.frag @@ -0,0 +1,23 @@ +varying vec2 vMaskCoord; +varying vec2 vTextureCoord; + +uniform sampler2D uSampler; +uniform sampler2D mask; +uniform float alpha; +uniform vec4 maskClamp; + +void main(void) +{ + float clip = step(3.5, + step(maskClamp.x, vMaskCoord.x) + + step(maskClamp.y, vMaskCoord.y) + + step(vMaskCoord.x, maskClamp.z) + + step(vMaskCoord.y, maskClamp.w)); + + vec4 original = texture2D(uSampler, vTextureCoord); + vec4 masky = texture2D(mask, vMaskCoord); + + original *= (masky.r * masky.a * alpha * clip); + + gl_FragColor = original; +} diff --git a/packages/core/src/filters/spriteMask/spriteMaskFilter.vert b/packages/core/src/filters/spriteMask/spriteMaskFilter.vert new file mode 100644 index 0000000..d23a30c --- /dev/null +++ b/packages/core/src/filters/spriteMask/spriteMaskFilter.vert @@ -0,0 +1,16 @@ +attribute vec2 aVertexPosition; +attribute vec2 aTextureCoord; + +uniform mat3 projectionMatrix; +uniform mat3 otherMatrix; + +varying vec2 vMaskCoord; +varying vec2 vTextureCoord; + +void main(void) +{ + gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); + + vTextureCoord = aTextureCoord; + vMaskCoord = ( otherMatrix * vec3( aTextureCoord, 1.0) ).xy; +} diff --git a/packages/core/src/framebuffer/FrameBuffer.js b/packages/core/src/framebuffer/FrameBuffer.js new file mode 100644 index 0000000..84e9fcc --- /dev/null +++ b/packages/core/src/framebuffer/FrameBuffer.js @@ -0,0 +1,106 @@ +import Texture from '../textures/BaseTexture'; +import { FORMATS, TYPES } from '@pixi/constants'; + +/** + * Frame buffer + * @class + * @memberof PIXI + */ +export default class FrameBuffer +{ + constructor(width, height) + { + this.width = width || 100; + this.height = height || 100; + + this.stencil = false; + this.depth = false; + + this.dirtyId = 0; + this.dirtyFormat = 0; + this.dirtySize = 0; + + this.depthTexture = null; + this.colorTextures = []; + + this.glFrameBuffers = {}; + } + + get colorTexture() + { + return this.colorTextures[0]; + } + + addColorTexture(index, texture) + { + // TODO add some validation to the texture - same width / height etc? + this.colorTextures[index || 0] = texture || new Texture(null, { scaleMode: 0, + resolution: 1, + mipmap: false, + width: this.width, + height: this.height });// || new Texture(); + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + addDepthTexture(texture) + { + /* eslint-disable max-len */ + this.depthTexture = texture || new Texture(null, { scaleMode: 0, + resolution: 1, + width: this.width, + height: this.height, + mipmap: false, + format: FORMATS.DEPTH_COMPONENT, + type: TYPES.UNSIGNED_SHORT });// UNSIGNED_SHORT; + /* eslint-disable max-len */ + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + enableDepth() + { + this.depth = true; + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + enableStencil() + { + this.stencil = true; + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + resize(width, height) + { + if (width === this.width && height === this.height) return; + + this.width = width; + this.height = height; + + this.dirtyId++; + this.dirtySize++; + + for (let i = 0; i < this.colorTextures.length; i++) + { + this.colorTextures[i].setSize(width, height); + } + + if (this.depthTexture) + { + this.depthTexture.setSize(width, height); + } + } +} diff --git a/packages/core/src/framebuffer/FramebufferSystem.js b/packages/core/src/framebuffer/FramebufferSystem.js new file mode 100644 index 0000000..2cc2437 --- /dev/null +++ b/packages/core/src/framebuffer/FramebufferSystem.js @@ -0,0 +1,246 @@ +import System from '../System'; +import { Rectangle } from '@pixi/math'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class FramebufferSystem extends System +{ + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + contextChange() + { + this.gl = this.renderer.gl; + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + this.current = null; + this.viewport = new Rectangle(); + + this.drawBufferExtension = this.renderer.context.extensions.drawBuffers; + } + + bind(framebuffer, frame) + { + const gl = this.gl; + + this.current = framebuffer; + + if (framebuffer) + { + // TODO cacheing layer! + + const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID] || this.initFramebuffer(framebuffer); + + gl.bindFramebuffer(gl.FRAMEBUFFER, fbo.framebuffer); + // makesure all textures are unbound.. + + // now check for updates... + if (fbo.dirtyId !== framebuffer.dirtyId) + { + fbo.dirtyId = framebuffer.dirtyId; + + if (fbo.dirtyFormat !== framebuffer.dirtyFormat) + { + fbo.dirtyFormat = framebuffer.dirtyFormat; + this.updateFramebuffer(framebuffer); + } + else if (fbo.dirtySize !== framebuffer.dirtySize) + { + fbo.dirtySize = framebuffer.dirtySize; + this.resizeFramebuffer(framebuffer); + } + } + + for (let i = 0; i < framebuffer.colorTextures.length; i++) + { + if (framebuffer.colorTextures[i].texturePart) + { + this.renderer.texture.unbind(framebuffer.colorTextures[i].texture); + } + else + { + this.renderer.texture.unbind(framebuffer.colorTextures[i]); + } + } + + if (framebuffer.depthTexture) + { + this.renderer.texture.unbind(framebuffer.depthTexture); + } + + if (frame) + { + this.setViewport(frame.x, frame.y, frame.width, frame.height); + } + else + { + this.setViewport(0, 0, framebuffer.width, framebuffer.height); + } + } + else + { + gl.bindFramebuffer(gl.FRAMEBUFFER, null); + + if (frame) + { + this.setViewport(frame.x, frame.y, frame.width, frame.height); + } + else + { + this.setViewport(0, 0, this.renderer.width, this.renderer.height); + } + } + } + + setViewport(x, y, width, height) + { + const v = this.viewport; + + if (v.width !== width || v.height !== height || v.x !== x || v.y !== y) + { + v.x = x; + v.y = y; + v.width = width; + v.height = height; + + this.gl.viewport(x, y, width, height); + } + } + + get size() + { + if (this.current) + { + // TODO store temp + return { x: 0, y: 0, width: this.current.width, height: this.current.height }; + } + + return { x: 0, y: 0, width: this.renderer.width, height: this.renderer.height }; + } + + clear(r, g, b, a) + { + const gl = this.gl; + + // TODO clear color can be set only one right? + gl.clearColor(r, g, b, a); + gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); + } + + // private functions... + + initFramebuffer(framebuffer) + { + const gl = this.gl; + + // TODO - make this a class? + const fbo = { + framebuffer: gl.createFramebuffer(), + stencil: null, + dirtyId: 0, + dirtyFormat: 0, + dirtySize: 0, + }; + + framebuffer.glFrameBuffers[this.CONTEXT_UID] = fbo; + + return fbo; + } + + resizeFramebuffer(framebuffer) + { + const gl = this.gl; + + if (framebuffer.stencil || framebuffer.depth) + { + gl.bindRenderbuffer(gl.RENDERBUFFER, this.stencil); + gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); + } + } + + updateFramebuffer(framebuffer) + { + const gl = this.gl; + + const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID]; + + // bind the color texture + const colorTextures = framebuffer.colorTextures; + + let count = colorTextures.length; + + if (!this.drawBufferExtension) + { + count = Math.min(count, 1); + } + + const activeTextures = []; + + for (let i = 0; i < count; i++) + { + const texture = framebuffer.colorTextures[i]; + + if (texture.texturePart) + { + this.renderer.texture.bind(texture.texture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.COLOR_ATTACHMENT0 + i, + gl.TEXTURE_CUBE_MAP_NEGATIVE_X + texture.side, + texture.texture._glTextures[this.CONTEXT_UID].texture, + 0); + } + else + { + this.renderer.texture.bind(texture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.COLOR_ATTACHMENT0 + i, + gl.TEXTURE_2D, + texture._glTextures[this.CONTEXT_UID].texture, + 0); + } + + activeTextures.push(gl.COLOR_ATTACHMENT0 + i); + } + + if (this.drawBufferExtension && activeTextures.length > 1) + { + this.drawBufferExtension.drawBuffersWEBGL(activeTextures); + } + + if (framebuffer.depthTexture) + { + const depthTextureExt = this.renderer.context.extensions.depthTexture; + + if (depthTextureExt) + { + const depthTexture = framebuffer.depthTexture; + + this.renderer.texture.bind(depthTexture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.DEPTH_ATTACHMENT, + gl.TEXTURE_2D, + depthTexture._glTextures[this.CONTEXT_UID].texture, + 0); + } + } + + if (framebuffer.stencil || framebuffer.depth) + { + fbo.stencil = gl.createRenderbuffer(); + + gl.bindRenderbuffer(gl.RENDERBUFFER, fbo.stencil); + + // TODO.. this is depth AND stencil? + gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, fbo.stencil); + gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); + // fbo.enableStencil(); + } + } +} diff --git a/packages/core/src/geometry/GLBuffer.js b/packages/core/src/geometry/GLBuffer.js new file mode 100644 index 0000000..0477e8a --- /dev/null +++ b/packages/core/src/geometry/GLBuffer.js @@ -0,0 +1,9 @@ +export default class GLBuffer +{ + constructor(buffer) + { + this.buffer = buffer; + this.updateID = -1; + this.byteLength = -1; + } +} diff --git a/packages/core/src/geometry/GeometrySystem.js b/packages/core/src/geometry/GeometrySystem.js new file mode 100644 index 0000000..959f827 --- /dev/null +++ b/packages/core/src/geometry/GeometrySystem.js @@ -0,0 +1,388 @@ +import System from '../System'; +import GLBuffer from './GLBuffer'; + +const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class GeometrySystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this._activeGeometry = null; + this._activeVao = null; + + this.hasVao = true; + this.hasInstance = true; + } + + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + contextChange() + { + const gl = this.gl = this.renderer.gl; + + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + + // webgl2 + if (!gl.createVertexArray) + { + // webgl 1! + let nativeVaoExtension = this.renderer.context.extensions.vertexArrayObject; + + if (this.renderer.legacy) + { + nativeVaoExtension = null; + } + + if (nativeVaoExtension) + { + gl.createVertexArray = () => + nativeVaoExtension.createVertexArrayOES(); + + gl.bindVertexArray = (vao) => + nativeVaoExtension.bindVertexArrayOES(vao); + + gl.deleteVertexArray = (vao) => + nativeVaoExtension.deleteVertexArrayOES(vao); + } + else + { + this.hasVao = false; + gl.createVertexArray = () => + { + // empty + }; + + gl.bindVertexArray = () => + { + // empty + }; + + gl.deleteVertexArray = () => + { + // empty + }; + } + } + + if (!gl.vertexAttribDivisor) + { + const instanceExt = gl.getExtension('ANGLE_instanced_arrays'); + + if (instanceExt) + { + gl.vertexAttribDivisor = (a, b) => + instanceExt.vertexAttribDivisorANGLE(a, b); + + gl.drawElementsInstanced = (a, b, c, d, e) => + instanceExt.drawElementsInstancedANGLE(a, b, c, d, e); + + gl.drawArraysInstanced = (a, b, c, d) => + instanceExt.drawArraysInstancedANGLE(a, b, c, d); + } + else + { + this.hasInstance = false; + } + } + } + + /** + * Binds geometry so that is can be drawn. Creating a Vao if required + * @private + * @param {PIXI.Geometry} geometry instance of geometry to bind + */ + bind(geometry, shader) + { + shader = shader || this.renderer.shader.shader; + + const gl = this.gl; + + // not sure the best way to address this.. + // currently different shaders require different VAOs for the same geometry + // Still mulling over the best way to solve this one.. + // will likely need to modify the shader attribute locations at run time! + let vaos = geometry.glVertexArrayObjects[this.CONTEXT_UID]; + + if (!vaos) + { + geometry.glVertexArrayObjects[this.CONTEXT_UID] = vaos = {}; + } + + const vao = vaos[shader.program.id] || this.initGeometryVao(geometry, shader.program); + + this._activeGeometry = geometry; + + if (this._activeVao !== vao) + { + this._activeVao = vao; + + if (this.hasVao) + { + gl.bindVertexArray(vao); + } + else + { + this.activateVao(geometry, shader.program); + } + } + + // TODO - optimise later! + // don't need to loop through if nothing changed! + // maybe look to add an 'autoupdate' to geometry? + this.updateBuffers(); + } + + reset() + { + this.unbind(); + } + + updateBuffers() + { + const geometry = this._activeGeometry; + const gl = this.gl; + + for (let i = 0; i < geometry.buffers.length; i++) + { + const buffer = geometry.buffers[i]; + + const glBuffer = buffer._glBuffers[this.CONTEXT_UID]; + + if (buffer._updateID !== glBuffer.updateID) + { + glBuffer.updateID = buffer._updateID; + + // TODO can cache this on buffer! maybe added a getter / setter? + const type = buffer.index ? gl.ELEMENT_ARRAY_BUFFER : gl.ARRAY_BUFFER; + const drawType = buffer.static ? gl.STATIC_DRAW : gl.DYNAMIC_DRAW; + + gl.bindBuffer(type, glBuffer.buffer); + + if (glBuffer.byteLength >= buffer.data.byteLength) + { + // offset is always zero for now! + gl.bufferSubData(type, 0, buffer.data); + } + else + { + gl.bufferData(type, buffer.data, drawType); + } + } + } + } + + checkCompatability(geometry, program) + { + // geometry must have at least all the attributes that the shader requires. + const geometryAttributes = geometry.attributes; + const shaderAttributes = program.attributeData; + + for (const j in shaderAttributes) + { + if (!geometryAttributes[j]) + { + throw new Error(`shader and geometry incompatible, geometry missing the "${j}" attribute`); + } + } + } + + /** + * Creates a Vao with the same structure as the geometry and stores it on the geometry. + * @private + * @param {PIXI.Geometry} geometry instance of geometry to to generate Vao for + */ + initGeometryVao(geometry, program) + { + this.checkCompatability(geometry, program); + + const gl = this.gl; + const CONTEXT_UID = this.CONTEXT_UID; + const buffers = geometry.buffers; + const attributes = geometry.attributes; + + const tempStride = {}; + const tempStart = {}; + + for (const j in buffers) + { + tempStride[j] = 0; + tempStart[j] = 0; + } + + for (const j in attributes) + { + if (!attributes[j].size && program.attributeData[j]) + { + attributes[j].size = program.attributeData[j].size; + } + + tempStride[attributes[j].buffer] += attributes[j].size * byteSizeMap[attributes[j].type]; + } + + for (const j in attributes) + { + const attribute = attributes[j]; + const attribSize = attribute.size; + + if (attribute.stride === undefined) + { + if (tempStride[attribute.buffer] === attribSize * byteSizeMap[attribute.type]) + { + attribute.stride = 0; + } + else + { + attribute.stride = tempStride[attribute.buffer]; + } + } + + if (attribute.start === undefined) + { + attribute.start = tempStart[attribute.buffer]; + + tempStart[attribute.buffer] += attribSize * byteSizeMap[attribute.type]; + } + } + + // first update - and create the buffers! + // only create a gl buffer if it actually gets + for (let i = 0; i < buffers.length; i++) + { + const buffer = buffers[i]; + + if (!buffer._glBuffers[CONTEXT_UID]) + { + buffer._glBuffers[CONTEXT_UID] = new GLBuffer(gl.createBuffer()); + } + } + + // TODO - maybe make this a data object? + // lets wait to see if we need to first! + const vao = gl.createVertexArray(); + + gl.bindVertexArray(vao); + + this.activateVao(geometry, program); + + gl.bindVertexArray(null); + + // add it to the cache! + geometry.glVertexArrayObjects[this.CONTEXT_UID][program.id] = vao; + + return vao; + } + + activateVao(geometry, program) + { + const gl = this.gl; + const CONTEXT_UID = this.CONTEXT_UID; + const buffers = geometry.buffers; + const attributes = geometry.attributes; + + if (geometry.indexBuffer) + { + // first update the index buffer if we have one.. + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, geometry.indexBuffer._glBuffers[CONTEXT_UID].buffer); + } + + let lastBuffer = null; + + // add a new one! + for (const j in attributes) + { + const attribute = attributes[j]; + const buffer = buffers[attribute.buffer]; + const glBuffer = buffer._glBuffers[CONTEXT_UID]; + + if (program.attributeData[j]) + { + if (lastBuffer !== glBuffer) + { + gl.bindBuffer(gl.ARRAY_BUFFER, glBuffer.buffer); + + lastBuffer = glBuffer; + } + + const location = program.attributeData[j].location; + + // TODO introduce state again + // we can optimise this for older devices that have no VAOs + gl.enableVertexAttribArray(location); + + gl.vertexAttribPointer(location, + attribute.size, + attribute.type || gl.FLOAT, + attribute.normalized, + attribute.stride, + attribute.start); + + if (attribute.instance) + { + // TODO calculate instance count based of this... + if (this.hasInstance) + { + gl.vertexAttribDivisor(location, 1); + } + else + { + throw new Error('geometry error, GPU Instancing is not supported on this device'); + } + } + } + } + } + + draw(type, size, start, instanceCount) + { + const gl = this.gl; + const geometry = this._activeGeometry; + + // TODO.. this should not change so maybe cache the function? + + if (geometry.indexBuffer) + { + if (geometry.instanced) + { + /* eslint-disable max-len */ + gl.drawElementsInstanced(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2, instanceCount || 1); + /* eslint-enable max-len */ + } + else + { + gl.drawElements(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2); + } + } + else + if (geometry.instanced) + { + // TODO need a better way to calculate size.. + gl.drawArraysInstanced(type, start, size || geometry.getSize(), instanceCount || 1); + } + else + { + gl.drawArrays(type, start, size || geometry.getSize()); + } + + return this; + } + + unbind() + { + this.gl.bindVertexArray(null); + this._activeVao = null; + this._activeGeometry = null; + } +} diff --git a/packages/core/src/geometry/utils/setVertexAttribArrays.js b/packages/core/src/geometry/utils/setVertexAttribArrays.js new file mode 100644 index 0000000..23b6959 --- /dev/null +++ b/packages/core/src/geometry/utils/setVertexAttribArrays.js @@ -0,0 +1,54 @@ +// var GL_MAP = {}; + +/** + * @param gl {WebGLRenderingContext} The current WebGL context + * @param attribs {*} + * @param state {*} + */ +export default function setVertexAttribArrays(gl, attribs, state) +{ + let i; + + if (state) + { + const tempAttribState = state.tempAttribState; + const attribState = state.attribState; + + for (i = 0; i < tempAttribState.length; i++) + { + tempAttribState[i] = false; + } + + // set the new attribs + for (i = 0; i < attribs.length; i++) + { + tempAttribState[attribs[i].attribute.location] = true; + } + + for (i = 0; i < attribState.length; i++) + { + if (attribState[i] !== tempAttribState[i]) + { + attribState[i] = tempAttribState[i]; + + if (state.attribState[i]) + { + gl.enableVertexAttribArray(i); + } + else + { + gl.disableVertexAttribArray(i); + } + } + } + } + else + { + for (i = 0; i < attribs.length; i++) + { + const attrib = attribs[i]; + + gl.enableVertexAttribArray(attrib.attribute.location); + } + } +} diff --git a/packages/core/src/index.js b/packages/core/src/index.js index 58c456a..fd1f547 100644 --- a/packages/core/src/index.js +++ b/packages/core/src/index.js @@ -1,30 +1,30 @@ import * as resources from './textures/resources'; -import * as systems from './renderers/systems'; +import * as systems from './systems'; -export { resources }; export { systems }; +export { resources }; -export { default as Renderer } from './renderers/Renderer'; -export { default as AbstractRenderer } from './renderers/AbstractRenderer'; -export { default as FrameBuffer } from './textures/FrameBuffer'; +export { default as System } from './System'; +export { default as Renderer } from './Renderer'; +export { default as AbstractRenderer } from './AbstractRenderer'; +export { default as FrameBuffer } from './framebuffer/FrameBuffer'; export { default as CubeTexture } from './textures/CubeTexture'; export { default as BaseTexture } from './textures/BaseTexture'; export { default as Texture } from './textures/Texture'; export { default as TextureMatrix } from './textures/TextureMatrix'; -export { default as RenderTexture } from './textures/RenderTexture'; -export { default as BaseRenderTexture } from './textures/BaseRenderTexture'; +export { default as RenderTexture } from './renderTexture/RenderTexture'; +export { default as BaseRenderTexture } from './renderTexture/BaseRenderTexture'; export { default as TextureUvs } from './textures/TextureUvs'; -export { default as State } from './renderers/State'; -export { default as ObjectRenderer } from './renderers/utils/ObjectRenderer'; -export { default as Quad } from './renderers/utils/Quad'; -export { default as QuadUv } from './renderers/utils/QuadUv'; -export { default as checkMaxIfStatmentsInShader } from './renderers/utils/checkMaxIfStatmentsInShader'; +export { default as State } from './state/State'; +export { default as ObjectRenderer } from './batch/ObjectRenderer'; +export { default as Quad } from './utils/Quad'; +export { default as QuadUv } from './utils/QuadUv'; +export { default as checkMaxIfStatmentsInShader } from './shader/utils/checkMaxIfStatmentsInShader'; export { default as Shader } from './shader/Shader'; export { default as Program } from './shader/Program'; export { default as UniformGroup } from './shader/UniformGroup'; -export { default as SpriteMaskFilter } from './renderers/filters/spriteMask/SpriteMaskFilter'; -export { default as Filter } from './renderers/filters/Filter'; +export { default as SpriteMaskFilter } from './filters/spriteMask/SpriteMaskFilter'; +export { default as Filter } from './filters/Filter'; export { default as Attribute } from './geometry/Attribute'; export { default as Buffer } from './geometry/Buffer'; export { default as Geometry } from './geometry/Geometry'; - diff --git a/packages/core/src/mask/MaskSystem.js b/packages/core/src/mask/MaskSystem.js new file mode 100644 index 0000000..99174c2 --- /dev/null +++ b/packages/core/src/mask/MaskSystem.js @@ -0,0 +1,199 @@ +import System from '../System'; +import AlphaMaskFilter from '../filters/spriteMask/SpriteMaskFilter'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class MaskSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + // TODO - we don't need both! + this.scissor = false; + this.scissorData = null; + this.scissorRenderTarget = null; + + this.enableScissor = false; + + this.alphaMaskPool = []; + this.alphaMaskIndex = 0; + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.DisplayObject} target - Display Object to push the mask to + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + push(target, maskData) + { + // TODO the root check means scissor rect will not + // be used on render textures more info here: + // https://github.com/pixijs/pixi.js/pull/3545 + + if (maskData.texture) + { + this.pushSpriteMask(target, maskData); + } + else if (this.enableScissor + && !this.scissor + && this.renderer._activeRenderTarget.root + && !this.renderer.stencil.stencilMaskStack.length + && maskData.isFastRect()) + { + const matrix = maskData.worldTransform; + + let rot = Math.atan2(matrix.b, matrix.a); + + // use the nearest degree! + rot = Math.round(rot * (180 / Math.PI)); + + if (rot % 90) + { + this.pushStencilMask(maskData); + } + else + { + this.pushScissorMask(target, maskData); + } + } + else + { + this.pushStencilMask(maskData); + } + } + + /** + * Removes the last mask from the mask stack and doesn't return it. + * + * @param {PIXI.DisplayObject} target - Display Object to pop the mask from + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + pop(target, maskData) + { + if (maskData.texture) + { + this.popSpriteMask(target, maskData); + } + else if (this.enableScissor && !this.renderer.stencil.stencilMaskStack.length) + { + this.popScissorMask(target, maskData); + } + else + { + this.popStencilMask(target, maskData); + } + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.RenderTarget} target - Display Object to push the sprite mask to + * @param {PIXI.Sprite} maskData - Sprite to be used as the mask + */ + pushSpriteMask(target, maskData) + { + let alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; + + if (!alphaMaskFilter) + { + alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; + } + + alphaMaskFilter[0].resolution = this.renderer.resolution; + alphaMaskFilter[0].maskSprite = maskData; + + // TODO - may cause issues! + target.filterArea = maskData.getBounds(true); + + this.renderer.filterSystem.pushFilter(target, alphaMaskFilter); + + this.alphaMaskIndex++; + } + + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popSpriteMask() + { + this.renderer.filterSystem.popFilter(); + this.alphaMaskIndex--; + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + pushStencilMask(maskData) + { + this.renderer.batch.flush(); + this.renderer.stencil.pushStencil(maskData); + } + + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popStencilMask() + { + // this.renderer.currentRenderer.stop(); + this.renderer.stencil.popStencil(); + } + + /** + * + * @param {PIXI.DisplayObject} target - Display Object to push the mask to + * @param {PIXI.Graphics} maskData - The masking data. + */ + pushScissorMask(target, maskData) + { + maskData.renderable = true; + + const renderTarget = this.renderer._activeRenderTarget; + + const bounds = maskData.getBounds(); + + bounds.fit(renderTarget.size); + maskData.renderable = false; + + this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); + + const resolution = this.renderer.resolution; + + this.renderer.gl.scissor( + bounds.x * resolution, + (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, + bounds.width * resolution, + bounds.height * resolution + ); + + this.scissorRenderTarget = renderTarget; + this.scissorData = maskData; + this.scissor = true; + } + + /** + * + * + */ + popScissorMask() + { + this.scissorRenderTarget = null; + this.scissorData = null; + this.scissor = false; + + // must be scissor! + const gl = this.renderer.gl; + + gl.disable(gl.SCISSOR_TEST); + } +} diff --git a/packages/core/src/mask/StencilSystem.js b/packages/core/src/mask/StencilSystem.js new file mode 100644 index 0000000..62796a5 --- /dev/null +++ b/packages/core/src/mask/StencilSystem.js @@ -0,0 +1,133 @@ +import System from '../System'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class StencilSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + this.stencilMaskStack = []; + } + + /** + * Changes the mask stack that is used by this System. + * + * @param {PIXI.Graphics[]} stencilMaskStack - The mask stack + */ + setMaskStack(stencilMaskStack) + { + const gl = this.renderer.gl; + + if (stencilMaskStack.length !== this.stencilMaskStack.length) + { + if (stencilMaskStack.length === 0) + { + gl.disable(gl.STENCIL_TEST); + } + else + { + gl.enable(gl.STENCIL_TEST); + } + } + + this.stencilMaskStack = stencilMaskStack; + } + + /** + * Applies the Mask and adds it to the current stencil stack. @alvin + * + * @param {PIXI.Graphics} graphics - The mask + */ + pushStencil(graphics) + { + this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); + + // this.renderer._activeRenderTarget.attachStencilBuffer(); + + const gl = this.renderer.gl; + const prevMaskCount = this.stencilMaskStack.length; + + if (prevMaskCount === 0) + { + gl.enable(gl.STENCIL_TEST); + } + + this.stencilMaskStack.push(graphics); + + // Increment the refference stencil value where the new mask overlaps with the old ones. + gl.colorMask(false, false, false, false); + gl.stencilFunc(gl.EQUAL, prevMaskCount, this._getBitwiseMask()); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.INCR); + this.renderer.plugins.graphics.render(graphics); + + this._useCurrent(); + } + + /** + * Removes the last mask from the stencil stack. @alvin + */ + popStencil() + { + this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); + + const gl = this.renderer.gl; + const graphics = this.stencilMaskStack.pop(); + + if (this.stencilMaskStack.length === 0) + { + // the stack is empty! + gl.disable(gl.STENCIL_TEST); + gl.clear(gl.STENCIL_BUFFER_BIT); + gl.clearStencil(0); + } + else + { + // Decrement the refference stencil value where the popped mask overlaps with the other ones + gl.colorMask(false, false, false, false); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.DECR); + this.renderer.plugins.graphics.render(graphics); + + this._useCurrent(); + } + } + + /** + * Setup renderer to use the current stencil data. + */ + _useCurrent() + { + const gl = this.renderer.gl; + + gl.colorMask(true, true, true, true); + gl.stencilFunc(gl.EQUAL, this.stencilMaskStack.length, this._getBitwiseMask()); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP); + } + + /** + * Fill 1s equal to the number of acitve stencil masks. + * + * @return {number} The bitwise mask. + */ + _getBitwiseMask() + { + return (1 << this.stencilMaskStack.length) - 1; + } + + /** + * Destroys the mask stack. + * + */ + destroy() + { + super.destroy(this); + + this.stencilMaskStack = null; + } +} diff --git a/packages/core/src/projection/ProjectionSystem.js b/packages/core/src/projection/ProjectionSystem.js new file mode 100644 index 0000000..6e63b73 --- /dev/null +++ b/packages/core/src/projection/ProjectionSystem.js @@ -0,0 +1,73 @@ +import System from '../System'; +import { Matrix } from '@pixi/math'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class ProjectionSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.projectionMatrix = new Matrix(); + } + + update(destinationFrame, sourceFrame, resolution, root) + { + this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; + this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; + + this.calculateProjection(this.destinationFrame, this.sourceFrame, resolution, root); + + this.renderer.globalUniforms.uniforms.projectionMatrix = this.projectionMatrix; + this.renderer.globalUniforms.update(); + } + + /** + * Updates the projection matrix based on a projection frame (which is a rectangle) + * + * @param {Rectangle} destinationFrame - The destination frame. + * @param {Rectangle} sourceFrame - The source frame. + */ + calculateProjection(destinationFrame, sourceFrame, resolution, root) + { + const pm = this.projectionMatrix; + + // I don't think we will need this line.. + // pm.identity(); + + if (!root) + { + pm.a = (1 / destinationFrame.width * 2) * resolution; + pm.d = (1 / destinationFrame.height * 2) * resolution; + + pm.tx = -1 - (sourceFrame.x * pm.a); + pm.ty = -1 - (sourceFrame.y * pm.d); + } + else + { + pm.a = (1 / destinationFrame.width * 2) * resolution; + pm.d = (-1 / destinationFrame.height * 2) * resolution; + + pm.tx = -1 - (sourceFrame.x * pm.a); + pm.ty = 1 - (sourceFrame.y * pm.d); + } + } + + /** + * Sets the transform of the active render target to the given matrix + * + * @param {PIXI.Matrix} matrix - The transformation matrix + */ + setTransform()// matrix) + { + // this._activeRenderTarget.transform = matrix; + } +} diff --git a/packages/core/src/renderTexture/BaseRenderTexture.js b/packages/core/src/renderTexture/BaseRenderTexture.js new file mode 100644 index 0000000..88eb83b --- /dev/null +++ b/packages/core/src/renderTexture/BaseRenderTexture.js @@ -0,0 +1,137 @@ +import BaseTexture from '../textures/BaseTexture'; +import FrameBuffer from '../framebuffer/FrameBuffer'; + +/** + * A BaseRenderTexture is a special texture that allows any PixiJS display object to be rendered to it. + * + * __Hint__: All DisplayObjects (i.e. Sprites) that render to a BaseRenderTexture should be preloaded + * otherwise black rectangles will be drawn instead. + * + * A BaseRenderTexture takes a snapshot of any Display Object given to its render method. The position + * and rotation of the given Display Objects is ignored. For example: + * + * ```js + * let renderer = PIXI.autoDetectRenderer(1024, 1024, { view: canvas, ratio: 1 }); + * let baseRenderTexture = new PIXI.BaseRenderTexture(renderer, 800, 600); + * let sprite = PIXI.Sprite.fromImage("spinObj_01.png"); + * + * sprite.position.x = 800/2; + * sprite.position.y = 600/2; + * sprite.anchor.x = 0.5; + * sprite.anchor.y = 0.5; + * + * baseRenderTexture.render(sprite); + * ``` + * + * The Sprite in this case will be rendered using its local transform. To render this sprite at 0,0 + * you can clear the transform + * + * ```js + * + * sprite.setTransform() + * + * let baseRenderTexture = new PIXI.BaseRenderTexture(100, 100); + * let renderTexture = new PIXI.RenderTexture(baseRenderTexture); + * + * renderer.render(sprite, renderTexture); // Renders to center of RenderTexture + * ``` + * + * @class + * @extends PIXI.BaseTexture + * @memberof PIXI + */ +export default class BaseRenderTexture extends BaseTexture +{ + /** + * @param {object} [options] + * @param {number} [options.width=100] - The width of the base render texture + * @param {number} [options.height=100] - The height of the base render texture + * @param {PIXI.SCALE_MODES} [options.scaleMode] - See {@link PIXI.SCALE_MODES} for possible values + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the texture being generated + */ + constructor(options) + { + if (typeof options === 'number') + { + /* eslint-disable prefer-rest-params */ + // Backward compatibility of signature + const width = arguments[0]; + const height = arguments[1]; + const scaleMode = arguments[2]; + const resolution = arguments[3]; + + options = { width, height, scaleMode, resolution }; + /* eslint-enable prefer-rest-params */ + } + + super(null, options); + + const { width, height } = options || {}; + + // Set defaults + this.mipmap = false; + this.width = Math.ceil(width) || 100; + this.height = Math.ceil(height) || 100; + this.valid = true; + + /** + * A map of renderer IDs to webgl renderTargets + * + * @private + * @member {object} + */ + // this._glRenderTargets = {}; + + /** + * A reference to the canvas render target (we only need one as this can be shared across renderers) + * + * @private + * @member {object} + */ + this._canvasRenderTarget = null; + + this.clearColor = [0, 0, 0, 0]; + + this.frameBuffer = new FrameBuffer(width * this.resolution, height * this.resolution) + .addColorTexture(0, this); + + // TODO - could this be added the systems? + + /** + * The data structure for the stencil masks + * + * @member {PIXI.Graphics[]} + */ + this.stencilMaskStack = []; + + /** + * The data structure for the filters + * + * @member {PIXI.Graphics[]} + */ + this.filterStack = [{}]; + } + + /** + * Resizes the BaseRenderTexture. + * + * @param {number} width - The width to resize to. + * @param {number} height - The height to resize to. + */ + resize(width, height) + { + width = Math.ceil(width); + height = Math.ceil(height); + this.frameBuffer.resize(width * this.resolution, height * this.resolution); + } + + /** + * Destroys this texture + * + */ + destroy() + { + super.destroy(true); + this.renderer = null; + } +} diff --git a/packages/core/src/renderTexture/RenderTexture.js b/packages/core/src/renderTexture/RenderTexture.js new file mode 100644 index 0000000..f1ecf1f --- /dev/null +++ b/packages/core/src/renderTexture/RenderTexture.js @@ -0,0 +1,153 @@ +import BaseRenderTexture from './BaseRenderTexture'; +import Texture from '../textures/Texture'; + +/** + * A RenderTexture is a special texture that allows any PixiJS display object to be rendered to it. + * + * __Hint__: All DisplayObjects (i.e. Sprites) that render to a RenderTexture should be preloaded + * otherwise black rectangles will be drawn instead. + * + * A RenderTexture takes a snapshot of any Display Object given to its render method. For example: + * + * ```js + * let renderer = PIXI.autoDetectRenderer(1024, 1024, { view: canvas, ratio: 1 }); + * let renderTexture = PIXI.RenderTexture.create(800, 600); + * let sprite = PIXI.Sprite.fromImage("spinObj_01.png"); + * + * sprite.position.x = 800/2; + * sprite.position.y = 600/2; + * sprite.anchor.x = 0.5; + * sprite.anchor.y = 0.5; + * + * renderer.render(sprite, renderTexture); + * ``` + * + * The Sprite in this case will be rendered using its local transform. To render this sprite at 0,0 + * you can clear the transform + * + * ```js + * + * sprite.setTransform() + * + * let renderTexture = new PIXI.RenderTexture.create(100, 100); + * + * renderer.render(sprite, renderTexture); // Renders to center of RenderTexture + * ``` + * + * @class + * @extends PIXI.Texture + * @memberof PIXI + */ +export default class RenderTexture extends Texture +{ + /** + * @param {PIXI.BaseRenderTexture} baseRenderTexture - The renderer used for this RenderTexture + * @param {PIXI.Rectangle} [frame] - The rectangle frame of the texture to show + */ + constructor(baseRenderTexture, frame) + { + // support for legacy.. + let _legacyRenderer = null; + + if (!(baseRenderTexture instanceof BaseRenderTexture)) + { + /* eslint-disable prefer-rest-params, no-console */ + const width = arguments[1]; + const height = arguments[2]; + const scaleMode = arguments[3]; + const resolution = arguments[4]; + + // we have an old render texture.. + console.warn(`Please use RenderTexture.create(${width}, ${height}) instead of the ctor directly.`); + _legacyRenderer = arguments[0]; + /* eslint-enable prefer-rest-params, no-console */ + + frame = null; + baseRenderTexture = new BaseRenderTexture({ + width, + height, + scaleMode, + resolution, + }); + } + + /** + * The base texture object that this texture uses + * + * @member {BaseTexture} + */ + super(baseRenderTexture, frame); + + this.legacyRenderer = _legacyRenderer; + + /** + * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. + * + * @member {boolean} + */ + this.valid = true; + + /** + * FilterSystem temporary storage + * @private + * @member {PIXI.Rectangle} + */ + this.filterFrame = null; + + this._updateUvs(); + } + + /** + * Resizes the RenderTexture. + * + * @param {number} width - The width to resize to. + * @param {number} height - The height to resize to. + * @param {boolean} [resizeBaseTexture=true] - Should the baseTexture.width and height values be resized as well? + */ + resize(width, height, resizeBaseTexture = true) + { + width = Math.ceil(width); + height = Math.ceil(height); + + // TODO - could be not required.. + this.valid = (width > 0 && height > 0); + + this._frame.width = this.orig.width = width; + this._frame.height = this.orig.height = height; + + if (resizeBaseTexture) + { + this.baseTexture.resize(width, height); + } + + this._updateUvs(); + } + + /** + * A short hand way of creating a render texture. + * + * @param {object} [options] - Options + * @param {number} [options.width=100] - The width of the render texture + * @param {number} [options.height=100] - The height of the render texture + * @param {number} [options.scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the texture being generated + * @return {PIXI.RenderTexture} The new render texture + */ + static create(options) + { + // fallback, old-style: create(width, height, scaleMode, resolution) + if (typeof options === 'number') + { + /* eslint-disable prefer-rest-params */ + options = { + width: options, + height: arguments[1], + scaleMode: arguments[2], + resolution: arguments[3], + }; + /* eslint-enable prefer-rest-params */ + } + + return new RenderTexture(new BaseRenderTexture(options)); + } +} diff --git a/packages/core/src/renderTexture/RenderTextureSystem.js b/packages/core/src/renderTexture/RenderTextureSystem.js new file mode 100644 index 0000000..8d0ca61 --- /dev/null +++ b/packages/core/src/renderTexture/RenderTextureSystem.js @@ -0,0 +1,115 @@ +import System from '../System'; +import { Rectangle } from '@pixi/math'; + +const tempRect = new Rectangle(); + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class RenderTextureSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.clearColor = renderer._backgroundColorRgba; + + // TODO moe this property somewhere else! + this.defaultMaskStack = []; + this.defaultFilterStack = [{}]; + + // empty render texture? + this.renderTexture = null; + + this.destinationFrame = new Rectangle(); + } + + bind(renderTexture, sourceFrame, destinationFrame) + { + // TODO - do we want this?? + if (this.renderTexture === renderTexture) return; + this.renderTexture = renderTexture; + + const renderer = this.renderer; + + if (renderTexture) + { + const baseTexture = renderTexture.baseTexture; + + if (!destinationFrame) + { + tempRect.width = baseTexture.realWidth; + tempRect.height = baseTexture.realHeight; + + destinationFrame = tempRect; + } + + if (!sourceFrame) + { + sourceFrame = destinationFrame; + } + + this.renderer.framebuffer.bind(baseTexture.frameBuffer, destinationFrame); + + this.renderer.projection.update(destinationFrame, sourceFrame, baseTexture.resolution, false); + this.renderer.stencil.setMaskStack(baseTexture.stencilMaskStack); + } + else + { + // TODO these validation checks happen deeper down.. + // thing they can be avoided.. + if (!destinationFrame) + { + tempRect.width = renderer.width; + tempRect.height = renderer.height; + + destinationFrame = tempRect; + } + + if (!sourceFrame) + { + sourceFrame = destinationFrame; + } + + renderer.framebuffer.bind(null, destinationFrame); + + // TODO store this.. + this.renderer.projection.update(destinationFrame, sourceFrame, this.renderer.resolution, true); + this.renderer.stencil.setMaskStack(this.defaultMaskStack); + } + + this.destinationFrame.copyFrom(destinationFrame); + } + + /** + * Erases the render texture and fills the drawing area with a colour + * + * @param {number} [clearColor] - The colour + * @return {PIXI.Renderer} Returns itself. + */ + clear(clearColor) + { + if (this.renderTexture) + { + clearColor = clearColor || this.renderTexture.baseTexture.clearColor; + } + else + { + clearColor = clearColor || this.clearColor; + } + + this.renderer.framebuffer.clear(clearColor[0], clearColor[1], clearColor[2], clearColor[3]); + } + + resize()// screenWidth, screenHeight) + { + // resize the root only! + this.bind(null); + } +} diff --git a/packages/core/src/renderers/AbstractRenderer.js b/packages/core/src/renderers/AbstractRenderer.js deleted file mode 100644 index 928670f..0000000 --- a/packages/core/src/renderers/AbstractRenderer.js +++ /dev/null @@ -1,353 +0,0 @@ -import { hex2string, hex2rgb } from '@pixi/utils'; -import { Matrix, Rectangle } from '@pixi/math'; -import { RENDERER_TYPE } from '@pixi/constants'; -import { settings } from '@pixi/settings'; -import { Container } from '@pixi/display'; -import RenderTexture from '../textures/RenderTexture'; -import EventEmitter from 'eventemitter3'; - -const tempMatrix = new Matrix(); - -/** - * 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 EventEmitter - * @memberof PIXI - */ -export default class AbstractRenderer extends EventEmitter -{ - // eslint-disable-next-line valid-jsdoc - /** - * @param {string} system - The name of the system this renderer is for. - * @param {object} [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.transparent=false] - If the render view is transparent, default false - * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false - * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) - * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. The - * resolution of the renderer retina would be 2. - * @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 {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when rendering, - * stopping pixel interpolation. - */ - constructor(system, options, arg2, arg3) - { - super(); - - // Support for constructor(system, screenWidth, screenHeight, options) - if (typeof options === 'number') - { - options = Object.assign({ - width: options, - height: arg2 || settings.RENDER_OPTIONS.height, - }, arg3); - } - - // 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 = RENDERER_TYPE.UNKNOWN; - - /** - * Measurements of the screen. (0, 0, screenWidth, screenHeight) - * - * Its safe to use as filterArea or hitArea for 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 1 - */ - this.resolution = options.resolution || settings.RESOLUTION; - - /** - * Whether the render view is transparent - * - * @member {boolean} - */ - this.transparent = options.transparent; - - /** - * Whether css dimensions of canvas view should be resized to screen dimensions automatically - * - * @member {boolean} - */ - this.autoResize = options.autoResize || false; - - /** - * Tracks the blend modes useful for this renderer. - * - * @member {object} - */ - this.blendModes = null; - - /** - * 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; - - /** - * If true PixiJS will Math.floor() x/y values when rendering, stopping pixel interpolation. - * Handy for crisp pixel art and speed on legacy devices. - * - * @member {boolean} - */ - this.roundPixels = options.roundPixels; - - /** - * The background color as a number. - * - * @member {number} - * @private - */ - this._backgroundColor = 0x000000; - - /** - * The background color as an [R, G, B] array. - * - * @member {number[]} - * @private - */ - this._backgroundColorRgba = [0, 0, 0, 0]; - - /** - * The background color as a string. - * - * @member {string} - * @private - */ - this._backgroundColorString = '#000000'; - - this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter - - /** - * This temporary display object used as the parent of the currently being rendered item - * - * @member {PIXI.DisplayObject} - * @private - */ - this._tempDisplayObjectParent = new Container(); - - /** - * The last root object that the renderer tried to render. - * - * @member {PIXI.DisplayObject} - * @private - */ - this._lastObjectRendered = this._tempDisplayObjectParent; - - /** - * Collection of plugins. - * @readonly - * @member {object} - */ - this.plugins = {}; - } - - /** - * Initialize the plugins. - * - * @protected - * @param {object} staticMap - The dictionary of staticly saved plugins. - */ - initPlugins(staticMap) - { - 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() - { - return this.view.width; - } - - /** - * Same as view.height, actual number of pixels in the canvas by vertical - * - * @member {number} - * @readonly - * @default 600 - */ - get height() - { - return this.view.height; - } - - /** - * Resizes the screen and canvas to the specified width and height - * Canvas dimensions are multiplied by resolution - * - * @param {number} screenWidth - the new width of the screen - * @param {number} screenHeight - the new height of the screen - */ - resize(screenWidth, screenHeight) - { - this.screen.width = screenWidth; - this.screen.height = screenHeight; - - this.view.width = screenWidth * this.resolution; - this.view.height = screenHeight * this.resolution; - - if (this.autoResize) - { - this.view.style.width = `${screenWidth}px`; - this.view.style.height = `${screenHeight}px`; - } - } - - /** - * 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 {PIXI.DisplayObject} displayObject - The displayObject the object will be generated from - * @param {number} scaleMode - Should be one of the scaleMode consts - * @param {number} resolution - The resolution / device pixel ratio of the texture being generated - * @param {PIXI.Rectangle} [region] - The region of the displayObject, that shall be rendered, - * if no region is specified, defaults to the local bounds of the displayObject. - * @return {PIXI.Texture} a texture of the graphics object - */ - generateTexture(displayObject, scaleMode, resolution, region) - { - region = region || displayObject.getLocalBounds(); - - // 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(region.width | 0, region.height | 0, scaleMode, resolution); - - tempMatrix.tx = -region.x; - tempMatrix.ty = -region.y; - - this.render(displayObject, renderTexture, false, tempMatrix, true); - - return renderTexture; - } - - /** - * Removes everything from the renderer and optionally removes the Canvas DOM element. - * - * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. - */ - destroy(removeView) - { - 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); - } - - this.plugins = null; - - this.type = RENDERER_TYPE.UNKNOWN; - - this.view = null; - - this.screen = null; - - this.resolution = 0; - - this.transparent = false; - - this.autoResize = false; - - this.blendModes = null; - - this.options = null; - - this.preserveDrawingBuffer = false; - this.clearBeforeRender = false; - - this.roundPixels = false; - - this._backgroundColor = 0; - this._backgroundColorRgba = null; - this._backgroundColorString = null; - - this._tempDisplayObjectParent = null; - this._lastObjectRendered = null; - } - - /** - * The background color to fill if not transparent - * - * @member {number} - */ - get backgroundColor() - { - return this._backgroundColor; - } - - set backgroundColor(value) // eslint-disable-line require-jsdoc - { - this._backgroundColor = value; - this._backgroundColorString = hex2string(value); - hex2rgb(value, this._backgroundColorRgba); - } -} diff --git a/packages/core/src/renderers/Renderer.js b/packages/core/src/renderers/Renderer.js deleted file mode 100644 index a2020b1..0000000 --- a/packages/core/src/renderers/Renderer.js +++ /dev/null @@ -1,329 +0,0 @@ -import AbstractRenderer from './AbstractRenderer'; -import { sayHello } from '@pixi/utils'; -import MaskSystem from './systems/MaskSystem'; -import StencilSystem from './systems/StencilSystem'; -import FilterSystem from './systems/filter/FilterSystem'; -import FramebufferSystem from './systems/FramebufferSystem'; -import RenderTextureSystem from './systems/RenderTextureSystem'; -import TextureSystem from './systems/textures/TextureSystem'; -import ProjectionSystem from './systems/ProjectionSystem'; -import StateSystem from './systems/StateSystem'; -import GeometrySystem from './systems/geometry/GeometrySystem'; -import ShaderSystem from './systems/shader/ShaderSystem'; -import ContextSystem from './systems/ContextSystem'; -import BatchSystem from './systems/BatchSystem'; -import TextureGCSystem from './systems/textures/TextureGCSystem'; -import { RENDERER_TYPE } from '@pixi/constants'; -import UniformGroup from '../shader/UniformGroup'; -import { Matrix } from '@pixi/math'; -import Runner from 'mini-runner'; - -/** - * 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 Render works by automatically managing webGLBatchs. - * 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 :) - * - * @class - * @memberof PIXI - * @extends PIXI.AbstractRenderer - */ -export default class Renderer extends AbstractRenderer -{ - // eslint-disable-next-line valid-jsdoc - /** - * - * @param {object} [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.transparent=false] - If the render view is transparent, default false - * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false - * @param {boolean} [options.antialias=false] - sets antialias. If not available natively then FXAA - * antialiasing is used - * @param {boolean} [options.forceFXAA=false] - forces FXAA antialiasing to be used over native. - * FXAA is faster, but may not always look as great - * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. - * The resolution of the renderer retina would be 2. - * @param {boolean} [options.clearBeforeRender=true] - This sets if the renderer will clear - * the canvas or not before the new render pass. If you wish to set this to false, you *must* set - * preserveDrawingBuffer to `true`. - * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, - * enable this if you need to call toDataUrl on the webgl context. - * @param {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when - * rendering, stopping pixel interpolation. - * @param {number} [options.backgroundColor=0x000000] - The background color of the rendered area - * (shown if not transparent). - * @param {boolean} [options.legacy=false] - If true PixiJS will aim to ensure compatibility - * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. - * @param {string} [options.powerPreference] - Parameter passed to webgl context, set to "high-performance" - * for devices with dual graphics card - */ - constructor(options, arg2, arg3) - { - super('WebGL', options, arg2, arg3); - - /** - * The type of this renderer as a standardised const - * - * @member {number} - * @see PIXI.RENDERER_TYPE - */ - this.type = RENDERER_TYPE.WEBGL; - - // this will be set by the contextSystem (this.context) - this.gl = null; - this.CONTEXT_UID = 0; - this.legacy = !!options.legacy; - - // TODO legacy! - - // runners! - this.runners = { - destroy: new Runner('destroy'), - contextChange: new Runner('contextChange', 1), - reset: new Runner('reset'), - update: new Runner('update'), - postrender: new Runner('postrender'), - prerender: new Runner('prerender'), - resize: new Runner('resize', 2), - }; - - 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(GeometrySystem, 'geometry') - .addSystem(FramebufferSystem, 'framebuffer') - .addSystem(StencilSystem, 'stencil') - .addSystem(ProjectionSystem, 'projection') - .addSystem(TextureGCSystem, 'textureGC') - .addSystem(FilterSystem, 'filter') - .addSystem(RenderTextureSystem, 'renderTexture') - .addSystem(BatchSystem, 'batch'); - - this.initPlugins(Renderer.__plugins); - - /** - * The options passed in to create a new webgl context. - * - * @member {object} - * @private - */ - if (options.context) - { - this.context.initFromContext(options.context); - } - else - { - this.context.initFromOptions({ - alpha: this.transparent, - antialias: options.antialias, - premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', - stencil: true, - 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); - } - - addSystem(_class, name) - { - if (!name) - { - name = _class.name; - } - - // TODO - read name from class.name.. - - /* - if(name.includes('System')) - { - name = name.replace('System', ''); - name = name.charAt(0).toLowerCase() + name.slice(1); - } - */ - - const system = new _class(this); - - if (this[name]) - { - throw new Error(`Whoops! ${name} is already a manger`); - } - - this[name] = system; - - for (const i in this.runners) - { - this.runners[i].add(system); - } - - return this; - - /** - * 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. - */ - } - - /** - * Renders the object to its webGL view - * - * @param {PIXI.DisplayObject} displayObject - the object to be rendered - * @param {PIXI.RenderTexture} renderTexture - The render texture to render to. - * @param {boolean} [clear] - Should the canvas be cleared before the new render - * @param {PIXI.Transform} [transform] - A transform to apply to the render texture before rendering. - * @param {boolean} [skipUpdateTransform] - Should we skip the update transform pass? - */ - render(displayObject, renderTexture, clear, transform, skipUpdateTransform) - { - // can be handy to know! - this.renderingToScreen = !renderTexture; - - this.runners.prerender.run(); - this.emit('prerender'); - - // 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.parent; - - displayObject.parent = this._tempDisplayObjectParent; - displayObject.updateTransform(); - displayObject.parent = 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.run(); - - this.emit('postrender'); - } - - /** - * Resizes the webGL view to the specified width and height. - * - * @param {number} screenWidth - the new width of the screen - * @param {number} screenHeight - the new height of the screen - */ - resize(screenWidth, screenHeight) - { - AbstractRenderer.prototype.resize.call(this, screenWidth, screenHeight); - - this.runners.resize.run(screenWidth, screenHeight); - } - - /** - * Resets the WebGL state so you can render things however you fancy! - * - * @return {PIXI.Renderer} Returns itself. - */ - reset() - { - this.runners.reset.run(); - - return this; - } - - clear() - { - this.framebuffer.bind(); - this.framebuffer.clear(); - } - - /** - * Removes everything from the renderer (event listeners, spritebatch, etc...) - * - * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. - * See: https://github.com/pixijs/pixi.js/issues/2233 - */ - destroy(removeView) - { - this.runners.destroy.run(); - - // call base destroy - super.destroy(removeView); - - // TODO nullify all the managers.. - this.gl = null; - } - - /** - * 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 - * builds and excluding plugins. - * @name PIXI.Renderer#plugins - * @type {object} - * @readonly - * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. - * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. - * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. - * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. - */ - - /** - * Adds a plugin to the renderer. - * - * @method - * @param {string} pluginName - The name of the plugin. - * @param {Function} ctor - The constructor function or class for the plugin. - */ - static registerPlugin(pluginName, ctor) - { - Renderer.__plugins = Renderer.__plugins || {}; - Renderer.__plugins[pluginName] = ctor; - } -} diff --git a/packages/core/src/renderers/State.js b/packages/core/src/renderers/State.js deleted file mode 100644 index fa3f221..0000000 --- a/packages/core/src/renderers/State.js +++ /dev/null @@ -1,163 +0,0 @@ -/* eslint-disable max-len */ - -const BLEND = 0; -const OFFSET = 1; -const CULLING = 2; -const DEPTH_TEST = 3; -const WINDING = 4; - -/** - * This is a webGL state. It is passed The WebGL StateManager. - * Each mesh renderered may require webGL to be in a different state. - * For example you may want different blend mode or to enable polygon offsets - * - * @class - * @memberof PIXI - */ -export default class State -{ - /** - * - */ - constructor() - { - this.data = 0; - - this.blendMode = 0; - this.polygonOffset = 0; - - this.blend = true; - // this.depthTest = true; - } - - /** - * Activates blending of the computed fragment color values - * - * @member {boolean} - */ - get blend() - { - return !!(this.data & (1 << BLEND)); - } - - set blend(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << BLEND)) !== value) - { - this.data ^= (1 << BLEND); - } - } - - /** - * Activates adding an offset to depth values of polygon's fragments - * - * @member {boolean} - * @default false - */ - get offsets() - { - return !!(this.data & (1 << OFFSET)); - } - - set offsets(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << OFFSET)) !== value) - { - this.data ^= (1 << OFFSET); - } - } - - /** - * Activates culling of polygons. - * - * @member {boolean} - * @default false - */ - get culling() - { - return !!(this.data & (1 << CULLING)); - } - - set culling(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << CULLING)) !== value) - { - this.data ^= (1 << CULLING); - } - } - - /** - * Activates depth comparisons and updates to the depth buffer. - * - * @member {boolean} - * @default false - */ - get depthTest() - { - return !!(this.data & (1 << DEPTH_TEST)); - } - - set depthTest(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << DEPTH_TEST)) !== value) - { - this.data ^= (1 << DEPTH_TEST); - } - } - - /** - * Specifies whether or not front or back-facing polygons can be culled. - * @member {boolean} - * @default false - */ - get clockwiseFrontFace() - { - return !!(this.data & (1 << WINDING)); - } - - set clockwiseFrontFace(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << WINDING)) !== value) - { - this.data ^= (1 << WINDING); - } - } - - /** - * The blend mode to be applied when this state is set. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. - * Setting this mode to anything other than NO_BLEND will automatically switch blending on. - * - * @member {boolean} - * @default PIXI.BLEND_MODES.NORMAL - * @see PIXI.BLEND_MODES - */ - get blendMode() - { - return this._blendMode; - } - - set blendMode(value) // eslint-disable-line require-jsdoc - { - // 17 is NO BLEND - this.blend = (value !== 17); - this._blendMode = value; - } - - /** - * The polygon offset. Setting this property to anything other than 0 will automatically enable poygon offset fill. - * - * @member {number} - * @default 0 - */ - get polygonOffset() - { - return this._polygonOffset; - } - - set polygonOffset(value) // eslint-disable-line require-jsdoc - { - this.offsets = !!value; - this._polygonOffset = value; - } -} - diff --git a/packages/core/src/renderers/filters/Filter.js b/packages/core/src/renderers/filters/Filter.js deleted file mode 100644 index 2073b8d..0000000 --- a/packages/core/src/renderers/filters/Filter.js +++ /dev/null @@ -1,140 +0,0 @@ -import Shader from '../../shader/Shader'; -import Program from '../../shader/Program'; -import State from '../State'; -import { settings } from '@pixi/settings'; -// import extractUniformsFromSrc from './extractUniformsFromSrc'; -import defaultVertex from './defaultFilter.vert'; -import defaultFragment from './defaultFilter.frag'; - -// let math = require('../../math'); -/** - * @class - * @memberof PIXI - * @extends PIXI.Shader - */ -export default class Filter extends Shader -{ - /** - * @param {string} [vertexSrc] - The source of the vertex shader. - * @param {string} [fragmentSrc] - The source of the fragment shader. - * @param {object} [uniforms] - Custom uniforms to use to augment the built-in ones. - */ - constructor(vertexSrc, fragmentSrc, uniforms) - { - const program = Program.from(vertexSrc, fragmentSrc); - - super(program, uniforms); - - /** - * The padding of the filter. Some filters require extra space to breath such as a blur. - * Increasing this will add extra width and height to the bounds of the object that the - * filter is applied to. - * - * @member {number} - */ - this.padding = 0; - - /** - * The resolution of the filter. Setting this to be lower will lower the quality but - * increase the performance of the filter. - * - * @member {number} - */ - this.resolution = settings.RESOLUTION; - - /** - * If enabled is true the filter is applied, if false it will not. - * - * @member {boolean} - */ - this.enabled = true; - - /** - * If enabled, PixiJS will fit the filter area into boundaries for better performance. - * Switch it off if it does not work for specific shader. - * - * @member {boolean} - */ - this.autoFit = true; - - /** - * Legacy filters use position and uvs from attributes - * @member {boolean} - * @readonly - */ - this.legacy = !!this.program.attributeData.aTextureCoord; - - /** - * the webGL state the filter requires to render - * @member {PIXI.State} - */ - this.state = new State(); - } - - /** - * Applies the filter - * - * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from - * @param {PIXI.RenderTarget} input - The input render target. - * @param {PIXI.RenderTarget} output - The target to output to. - * @param {boolean} clear - Should the output be cleared before rendering to it - * @param {object} [currentState] - It's current state of filter. - * There are some useful properties in the currentState : - * target, filters, sourceFrame, destinationFrame, renderTarget, resolution - */ - apply(filterManager, input, output, clear, currentState, derp) // eslint-disable-line no-unused-vars - { - // do as you please! - - filterManager.applyFilter(this, input, output, clear, currentState, derp); - - // or just do a regular render.. - } - - /** - * Sets the blendmode of the filter - * - * @member {number} - * @default PIXI.BLEND_MODES.NORMAL - */ - get blendMode() - { - return this.state.blendMode; - } - - set blendMode(value) // eslint-disable-line require-jsdoc - { - this.state.blendMode = value; - } - - /** - * The default vertex shader source - * - * @static - * @constant - */ - static get defaultVertexSrc() - { - return defaultVertex; - } - - /** - * The default fragment shader source - * - * @static - * @constant - */ - static get defaultFragmentSrc() - { - return defaultFragment; - } -} - -/** - * Used for caching shader IDs - * - * @static - * @private - */ -Filter.SOURCE_KEY_MAP = {}; - diff --git a/packages/core/src/renderers/filters/defaultFilter.frag b/packages/core/src/renderers/filters/defaultFilter.frag deleted file mode 100644 index a14c1be..0000000 --- a/packages/core/src/renderers/filters/defaultFilter.frag +++ /dev/null @@ -1,22 +0,0 @@ -varying vec2 vTextureCoord; -varying vec2 vFilterCoord; - -uniform sampler2D uSampler; -uniform sampler2D filterSampler; - -void main(void){ - vec4 masky = texture2D(filterSampler, vFilterCoord); - vec4 sample = texture2D(uSampler, vTextureCoord); - vec4 color; - if(mod(vFilterCoord.x, 1.0) > 0.5) - { - color = vec4(1.0, 0.0, 0.0, 1.0); - } - else - { - color = vec4(0.0, 1.0, 0.0, 1.0); - } - // gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0); - gl_FragColor = mix(sample, masky, 0.5); - gl_FragColor *= sample.a; -} diff --git a/packages/core/src/renderers/filters/defaultFilter.vert b/packages/core/src/renderers/filters/defaultFilter.vert deleted file mode 100644 index e1febca..0000000 --- a/packages/core/src/renderers/filters/defaultFilter.vert +++ /dev/null @@ -1,14 +0,0 @@ -attribute vec2 aVertexPosition; -attribute vec2 aTextureCoord; - -uniform mat3 projectionMatrix; -uniform mat3 filterMatrix; - -varying vec2 vTextureCoord; -varying vec2 vFilterCoord; - -void main(void){ - gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); - vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy; - vTextureCoord = aTextureCoord ; -} diff --git a/packages/core/src/renderers/filters/filterTransforms.js b/packages/core/src/renderers/filters/filterTransforms.js deleted file mode 100644 index 87a3cd6..0000000 --- a/packages/core/src/renderers/filters/filterTransforms.js +++ /dev/null @@ -1,50 +0,0 @@ -import { Matrix } from '@pixi/math'; - -/** - * Calculates the mapped matrix - * @param filterArea {Rectangle} The filter area - * @param sprite {Sprite} the target sprite - * @param outputMatrix {Matrix} @alvin - * @private - */ -// this returns a matrix that will normalise map filter cords in the filter to screen space -export function calculateScreenSpaceMatrix(outputMatrix, filterArea, textureSize) -{ - // TODO unwrap? - const mappedMatrix = outputMatrix.identity(); - - mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); - - mappedMatrix.scale(textureSize.width, textureSize.height); - - return mappedMatrix; -} - -export function calculateNormalizedScreenSpaceMatrix(outputMatrix, filterArea, textureSize) -{ - const mappedMatrix = outputMatrix.identity(); - - mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); - - const translateScaleX = (textureSize.width / filterArea.width); - const translateScaleY = (textureSize.height / filterArea.height); - - mappedMatrix.scale(translateScaleX, translateScaleY); - - return mappedMatrix; -} - -// this will map the filter coord so that a texture can be used based on the transform of a sprite -export function calculateSpriteMatrix(outputMatrix, filterArea, textureSize, sprite) -{ - const orig = sprite._texture.orig; - const mappedMatrix = outputMatrix.set(textureSize.width, 0, 0, textureSize.height, filterArea.x, filterArea.y); - const worldTransform = sprite.worldTransform.copyTo(Matrix.TEMP_MATRIX); - - worldTransform.invert(); - mappedMatrix.prepend(worldTransform); - mappedMatrix.scale(1.0 / orig.width, 1.0 / orig.height); - mappedMatrix.translate(sprite.anchor.x, sprite.anchor.y); - - return mappedMatrix; -} diff --git a/packages/core/src/renderers/filters/spriteMask/SpriteMaskFilter.js b/packages/core/src/renderers/filters/spriteMask/SpriteMaskFilter.js deleted file mode 100644 index d5a91a4..0000000 --- a/packages/core/src/renderers/filters/spriteMask/SpriteMaskFilter.js +++ /dev/null @@ -1,63 +0,0 @@ -import Filter from '../Filter'; -import { Matrix } from '@pixi/math'; -import vertex from './spriteMaskFilter.vert'; -import fragment from './spriteMaskFilter.frag'; -import { default as TextureMatrix } from '../../../textures/TextureMatrix'; - -/** - * The SpriteMaskFilter class - * - * @class - * @extends PIXI.Filter - * @memberof PIXI - */ -export default class SpriteMaskFilter extends Filter -{ - /** - * @param {PIXI.Sprite} sprite - the target sprite - */ - constructor(sprite) - { - const maskMatrix = new Matrix(); - - super(vertex, fragment); - - sprite.renderable = false; - - this.maskSprite = sprite; - this.maskMatrix = maskMatrix; - } - - /** - * Applies the filter - * - * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from - * @param {PIXI.RenderTarget} input - The input render target. - * @param {PIXI.RenderTarget} output - The target to output to. - */ - apply(filterManager, input, output) - { - const maskSprite = this.maskSprite; - const tex = this.maskSprite.texture; - - if (!tex.valid) - { - return; - } - if (!tex.transform) - { - // margin = 0.0, let it bleed a bit, shader code becomes easier - // assuming that atlas textures were made with 1-pixel padding - tex.transform = new TextureMatrix(tex, 0.0); - } - tex.transform.update(); - - this.uniforms.mask = tex; - this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite) - .prepend(tex.transform.mapCoord); - this.uniforms.alpha = maskSprite.worldAlpha; - this.uniforms.maskClamp = tex.transform.uClampFrame; - - filterManager.applyFilter(this, input, output); - } -} diff --git a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.frag b/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.frag deleted file mode 100644 index 0e4aef8..0000000 --- a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.frag +++ /dev/null @@ -1,23 +0,0 @@ -varying vec2 vMaskCoord; -varying vec2 vTextureCoord; - -uniform sampler2D uSampler; -uniform sampler2D mask; -uniform float alpha; -uniform vec4 maskClamp; - -void main(void) -{ - float clip = step(3.5, - step(maskClamp.x, vMaskCoord.x) + - step(maskClamp.y, vMaskCoord.y) + - step(vMaskCoord.x, maskClamp.z) + - step(vMaskCoord.y, maskClamp.w)); - - vec4 original = texture2D(uSampler, vTextureCoord); - vec4 masky = texture2D(mask, vMaskCoord); - - original *= (masky.r * masky.a * alpha * clip); - - gl_FragColor = original; -} diff --git a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.vert b/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.vert deleted file mode 100644 index d23a30c..0000000 --- a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.vert +++ /dev/null @@ -1,16 +0,0 @@ -attribute vec2 aVertexPosition; -attribute vec2 aTextureCoord; - -uniform mat3 projectionMatrix; -uniform mat3 otherMatrix; - -varying vec2 vMaskCoord; -varying vec2 vTextureCoord; - -void main(void) -{ - gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); - - vTextureCoord = aTextureCoord; - vMaskCoord = ( otherMatrix * vec3( aTextureCoord, 1.0) ).xy; -} diff --git a/packages/core/src/renderers/systems/BatchSystem.js b/packages/core/src/renderers/systems/BatchSystem.js deleted file mode 100644 index 70e9518..0000000 --- a/packages/core/src/renderers/systems/BatchSystem.js +++ /dev/null @@ -1,68 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import ObjectRenderer from '../utils/ObjectRenderer'; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ - -export default class BatchSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this manager works for. - */ - constructor(renderer) - { - super(renderer); - - /** - * An empty renderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.emptyRenderer = new ObjectRenderer(renderer); - - /** - * The currently active ObjectRenderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.currentRenderer = this.emptyRenderer; - } - - /** - * Changes the current renderer to the one given in parameter - * - * @param {PIXI.ObjectRenderer} objectRenderer - The object renderer to use. - */ - setObjectRenderer(objectRenderer) - { - if (this.currentRenderer === objectRenderer) - { - return; - } - - this.currentRenderer.stop(); - this.currentRenderer = objectRenderer; - - this.renderer.state.setState(objectRenderer.state); - - this.currentRenderer.start(); - } - - /** - * This should be called if you wish to do some custom rendering - * It will basically render anything that may be batched up such as sprites - * - */ - flush() - { - this.setObjectRenderer(this.emptyRenderer); - } - - reset() - { - this.setObjectRenderer(this.emptyRenderer); - } -} diff --git a/packages/core/src/renderers/systems/ContextSystem.js b/packages/core/src/renderers/systems/ContextSystem.js deleted file mode 100644 index 43bf827..0000000 --- a/packages/core/src/renderers/systems/ContextSystem.js +++ /dev/null @@ -1,186 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { settings } from '@pixi/settings'; - -let CONTEXT_UID = 0; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class ContextSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this.webGLVersion = 1; - - this.handleContextLost = this.handleContextLost.bind(this); - this.handleContextRestored = this.handleContextRestored.bind(this); - - this.extensions = {}; - - renderer.view.addEventListener('webglcontextlost', this.handleContextLost, false); - renderer.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); - } - - get isLost() - { - return (!this.gl || this.gl.isContextLost()); - } - - contextChange(gl) - { - this.gl = gl; - - // restore a context if it was previously lost - if (gl.isContextLost() && gl.getExtension('WEBGL_lose_context')) - { - gl.getExtension('WEBGL_lose_context').restoreContext(); - } - } - - initFromContext(gl) - { - this.gl = gl; - this.validateContext(gl); - this.renderer.gl = gl; - this.renderer.CONTEXT_UID = CONTEXT_UID++; - this.renderer.runners.contextChange.run(gl); - } - - initFromOptions(options) - { - const gl = this.createContext(this.renderer.view, options); - - this.initFromContext(gl); - } - - /** - * Helper class to create a webGL Context - * - * @class - * @memberof PIXI.glCore - * @param canvas {HTMLCanvasElement} the canvas element that we will get the context from - * @param options {Object} An options object that gets passed in to the canvas element containing the context attributes, - * see https://developer.mozilla.org/en/docs/Web/API/HTMLCanvasElement/getContext for the options available - * @return {WebGLRenderingContext} the WebGL context - */ - createContext(canvas, options) - { - let gl; - - if (settings.PREFER_WEBGL_2) - { - gl = canvas.getContext('webgl2', options); - } - - if (gl) - { - this.webGLVersion = 2; - } - else - { - this.webGLVersion = 1; - - gl = canvas.getContext('webgl', options) - || canvas.getContext('experimental-webgl', options); - - if (!gl) - { - // fail, not able to get a context - throw new Error('This browser does not support webGL. Try using the canvas renderer'); - } - } - - this.gl = gl; - - this.getExtensions(); - - return gl; - } - - getExtensions() - { - // time to set up default etensions that pixi uses.. - const gl = this.gl; - const extensions = this.extensions; - - if (this.webGLVersion === 1) - { - extensions.drawBuffers = gl.getExtension('WEBGL_draw_buffers'); - extensions.depthTexture = gl.getExtension('WEBKIT_WEBGL_depth_texture'); - extensions.floatTexture = gl.getExtension('OES_texture_float'); - extensions.loseContext = gl.getExtension('WEBGL_lose_context'); - - extensions.vertexArrayObject = gl.getExtension('OES_vertex_array_object') - || gl.getExtension('MOZ_OES_vertex_array_object') - || gl.getExtension('WEBKIT_OES_vertex_array_object'); - } - - // we don't use any specific WebGL 2 ones yet! - } - - /** - * Handles a lost webgl context - * - * @private - * @param {WebGLContextEvent} event - The context lost event. - */ - handleContextLost(event) - { - event.preventDefault(); - } - - /** - * Handles a restored webgl context - * - * @private - */ - handleContextRestored() - { - this.renderer.runners.contextChange.run(this.gl); - } - - destroy() - { - const view = this.renderer.view; - - // remove listeners - view.removeEventListener('webglcontextlost', this.handleContextLost); - view.removeEventListener('webglcontextrestored', this.handleContextRestored); - - this.gl.useProgram(null); - - if (this.extensions.loseContext) - { - this.extensions.loseContext.loseContext(); - } - } - - postrender() - { - this.gl.flush(); - } - - validateContext(gl) - { - const attributes = gl.getContextAttributes(); - - // this is going to be fairly simple for now.. but at least we have room to grow! - if (!attributes.stencil) - { - /* eslint-disable max-len */ - - /* eslint-disable no-console */ - console.warn('Provided WebGL context does not have a stencil buffer, masks may not render correctly'); - /* eslint-enable no-console */ - - /* eslint-enable max-len */ - } - } -} diff --git a/packages/core/src/renderers/systems/FramebufferSystem.js b/packages/core/src/renderers/systems/FramebufferSystem.js deleted file mode 100644 index df7902b..0000000 --- a/packages/core/src/renderers/systems/FramebufferSystem.js +++ /dev/null @@ -1,246 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { Rectangle } from '@pixi/math'; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class FramebufferSystem extends WebGLSystem -{ - /** - * Sets up the renderer context and necessary buffers. - * - * @private - */ - contextChange() - { - this.gl = this.renderer.gl; - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - this.current = null; - this.viewport = new Rectangle(); - - this.drawBufferExtension = this.renderer.context.extensions.drawBuffers; - } - - bind(framebuffer, frame) - { - const gl = this.gl; - - this.current = framebuffer; - - if (framebuffer) - { - // TODO cacheing layer! - - const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID] || this.initFramebuffer(framebuffer); - - gl.bindFramebuffer(gl.FRAMEBUFFER, fbo.framebuffer); - // makesure all textures are unbound.. - - // now check for updates... - if (fbo.dirtyId !== framebuffer.dirtyId) - { - fbo.dirtyId = framebuffer.dirtyId; - - if (fbo.dirtyFormat !== framebuffer.dirtyFormat) - { - fbo.dirtyFormat = framebuffer.dirtyFormat; - this.updateFramebuffer(framebuffer); - } - else if (fbo.dirtySize !== framebuffer.dirtySize) - { - fbo.dirtySize = framebuffer.dirtySize; - this.resizeFramebuffer(framebuffer); - } - } - - for (let i = 0; i < framebuffer.colorTextures.length; i++) - { - if (framebuffer.colorTextures[i].texturePart) - { - this.renderer.texture.unbind(framebuffer.colorTextures[i].texture); - } - else - { - this.renderer.texture.unbind(framebuffer.colorTextures[i]); - } - } - - if (framebuffer.depthTexture) - { - this.renderer.texture.unbind(framebuffer.depthTexture); - } - - if (frame) - { - this.setViewport(frame.x, frame.y, frame.width, frame.height); - } - else - { - this.setViewport(0, 0, framebuffer.width, framebuffer.height); - } - } - else - { - gl.bindFramebuffer(gl.FRAMEBUFFER, null); - - if (frame) - { - this.setViewport(frame.x, frame.y, frame.width, frame.height); - } - else - { - this.setViewport(0, 0, this.renderer.width, this.renderer.height); - } - } - } - - setViewport(x, y, width, height) - { - const v = this.viewport; - - if (v.width !== width || v.height !== height || v.x !== x || v.y !== y) - { - v.x = x; - v.y = y; - v.width = width; - v.height = height; - - this.gl.viewport(x, y, width, height); - } - } - - get size() - { - if (this.current) - { - // TODO store temp - return { x: 0, y: 0, width: this.current.width, height: this.current.height }; - } - - return { x: 0, y: 0, width: this.renderer.width, height: this.renderer.height }; - } - - clear(r, g, b, a) - { - const gl = this.gl; - - // TODO clear color can be set only one right? - gl.clearColor(r, g, b, a); - gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); - } - - // private functions... - - initFramebuffer(framebuffer) - { - const gl = this.gl; - - // TODO - make this a class? - const fbo = { - framebuffer: gl.createFramebuffer(), - stencil: null, - dirtyId: 0, - dirtyFormat: 0, - dirtySize: 0, - }; - - framebuffer.glFrameBuffers[this.CONTEXT_UID] = fbo; - - return fbo; - } - - resizeFramebuffer(framebuffer) - { - const gl = this.gl; - - if (framebuffer.stencil || framebuffer.depth) - { - gl.bindRenderbuffer(gl.RENDERBUFFER, this.stencil); - gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); - } - } - - updateFramebuffer(framebuffer) - { - const gl = this.gl; - - const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID]; - - // bind the color texture - const colorTextures = framebuffer.colorTextures; - - let count = colorTextures.length; - - if (!this.drawBufferExtension) - { - count = Math.min(count, 1); - } - - const activeTextures = []; - - for (let i = 0; i < count; i++) - { - const texture = framebuffer.colorTextures[i]; - - if (texture.texturePart) - { - this.renderer.texture.bind(texture.texture, 0); - - gl.framebufferTexture2D(gl.FRAMEBUFFER, - gl.COLOR_ATTACHMENT0 + i, - gl.TEXTURE_CUBE_MAP_NEGATIVE_X + texture.side, - texture.texture._glTextures[this.CONTEXT_UID].texture, - 0); - } - else - { - this.renderer.texture.bind(texture, 0); - - gl.framebufferTexture2D(gl.FRAMEBUFFER, - gl.COLOR_ATTACHMENT0 + i, - gl.TEXTURE_2D, - texture._glTextures[this.CONTEXT_UID].texture, - 0); - } - - activeTextures.push(gl.COLOR_ATTACHMENT0 + i); - } - - if (this.drawBufferExtension && activeTextures.length > 1) - { - this.drawBufferExtension.drawBuffersWEBGL(activeTextures); - } - - if (framebuffer.depthTexture) - { - const depthTextureExt = this.renderer.context.extensions.depthTexture; - - if (depthTextureExt) - { - const depthTexture = framebuffer.depthTexture; - - this.renderer.texture.bind(depthTexture, 0); - - gl.framebufferTexture2D(gl.FRAMEBUFFER, - gl.DEPTH_ATTACHMENT, - gl.TEXTURE_2D, - depthTexture._glTextures[this.CONTEXT_UID].texture, - 0); - } - } - - if (framebuffer.stencil || framebuffer.depth) - { - fbo.stencil = gl.createRenderbuffer(); - - gl.bindRenderbuffer(gl.RENDERBUFFER, fbo.stencil); - - // TODO.. this is depth AND stencil? - gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, fbo.stencil); - gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); - // fbo.enableStencil(); - } - } -} diff --git a/packages/core/src/renderers/systems/MaskSystem.js b/packages/core/src/renderers/systems/MaskSystem.js deleted file mode 100644 index d9c4b1a..0000000 --- a/packages/core/src/renderers/systems/MaskSystem.js +++ /dev/null @@ -1,199 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import AlphaMaskFilter from '../filters/spriteMask/SpriteMaskFilter'; - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class MaskSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - // TODO - we don't need both! - this.scissor = false; - this.scissorData = null; - this.scissorRenderTarget = null; - - this.enableScissor = false; - - this.alphaMaskPool = []; - this.alphaMaskIndex = 0; - } - - /** - * Applies the Mask and adds it to the current filter stack. - * - * @param {PIXI.DisplayObject} target - Display Object to push the mask to - * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. - */ - push(target, maskData) - { - // TODO the root check means scissor rect will not - // be used on render textures more info here: - // https://github.com/pixijs/pixi.js/pull/3545 - - if (maskData.texture) - { - this.pushSpriteMask(target, maskData); - } - else if (this.enableScissor - && !this.scissor - && this.renderer._activeRenderTarget.root - && !this.renderer.stencil.stencilMaskStack.length - && maskData.isFastRect()) - { - const matrix = maskData.worldTransform; - - let rot = Math.atan2(matrix.b, matrix.a); - - // use the nearest degree! - rot = Math.round(rot * (180 / Math.PI)); - - if (rot % 90) - { - this.pushStencilMask(maskData); - } - else - { - this.pushScissorMask(target, maskData); - } - } - else - { - this.pushStencilMask(maskData); - } - } - - /** - * Removes the last mask from the mask stack and doesn't return it. - * - * @param {PIXI.DisplayObject} target - Display Object to pop the mask from - * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. - */ - pop(target, maskData) - { - if (maskData.texture) - { - this.popSpriteMask(target, maskData); - } - else if (this.enableScissor && !this.renderer.stencil.stencilMaskStack.length) - { - this.popScissorMask(target, maskData); - } - else - { - this.popStencilMask(target, maskData); - } - } - - /** - * Applies the Mask and adds it to the current filter stack. - * - * @param {PIXI.RenderTarget} target - Display Object to push the sprite mask to - * @param {PIXI.Sprite} maskData - Sprite to be used as the mask - */ - pushSpriteMask(target, maskData) - { - let alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; - - if (!alphaMaskFilter) - { - alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; - } - - alphaMaskFilter[0].resolution = this.renderer.resolution; - alphaMaskFilter[0].maskSprite = maskData; - - // TODO - may cause issues! - target.filterArea = maskData.getBounds(true); - - this.renderer.filterSystem.pushFilter(target, alphaMaskFilter); - - this.alphaMaskIndex++; - } - - /** - * Removes the last filter from the filter stack and doesn't return it. - * - */ - popSpriteMask() - { - this.renderer.filterSystem.popFilter(); - this.alphaMaskIndex--; - } - - /** - * Applies the Mask and adds it to the current filter stack. - * - * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. - */ - pushStencilMask(maskData) - { - this.renderer.batch.flush(); - this.renderer.stencil.pushStencil(maskData); - } - - /** - * Removes the last filter from the filter stack and doesn't return it. - * - */ - popStencilMask() - { - // this.renderer.currentRenderer.stop(); - this.renderer.stencil.popStencil(); - } - - /** - * - * @param {PIXI.DisplayObject} target - Display Object to push the mask to - * @param {PIXI.Graphics} maskData - The masking data. - */ - pushScissorMask(target, maskData) - { - maskData.renderable = true; - - const renderTarget = this.renderer._activeRenderTarget; - - const bounds = maskData.getBounds(); - - bounds.fit(renderTarget.size); - maskData.renderable = false; - - this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); - - const resolution = this.renderer.resolution; - - this.renderer.gl.scissor( - bounds.x * resolution, - (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, - bounds.width * resolution, - bounds.height * resolution - ); - - this.scissorRenderTarget = renderTarget; - this.scissorData = maskData; - this.scissor = true; - } - - /** - * - * - */ - popScissorMask() - { - this.scissorRenderTarget = null; - this.scissorData = null; - this.scissor = false; - - // must be scissor! - const gl = this.renderer.gl; - - gl.disable(gl.SCISSOR_TEST); - } -} diff --git a/packages/core/src/renderers/systems/ProjectionSystem.js b/packages/core/src/renderers/systems/ProjectionSystem.js deleted file mode 100644 index ec5efdb..0000000 --- a/packages/core/src/renderers/systems/ProjectionSystem.js +++ /dev/null @@ -1,73 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { Matrix } from '@pixi/math'; - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ - -export default class ProjectionSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this.projectionMatrix = new Matrix(); - } - - update(destinationFrame, sourceFrame, resolution, root) - { - this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; - this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; - - this.calculateProjection(this.destinationFrame, this.sourceFrame, resolution, root); - - this.renderer.globalUniforms.uniforms.projectionMatrix = this.projectionMatrix; - this.renderer.globalUniforms.update(); - } - - /** - * Updates the projection matrix based on a projection frame (which is a rectangle) - * - * @param {Rectangle} destinationFrame - The destination frame. - * @param {Rectangle} sourceFrame - The source frame. - */ - calculateProjection(destinationFrame, sourceFrame, resolution, root) - { - const pm = this.projectionMatrix; - - // I don't think we will need this line.. - // pm.identity(); - - if (!root) - { - pm.a = (1 / destinationFrame.width * 2) * resolution; - pm.d = (1 / destinationFrame.height * 2) * resolution; - - pm.tx = -1 - (sourceFrame.x * pm.a); - pm.ty = -1 - (sourceFrame.y * pm.d); - } - else - { - pm.a = (1 / destinationFrame.width * 2) * resolution; - pm.d = (-1 / destinationFrame.height * 2) * resolution; - - pm.tx = -1 - (sourceFrame.x * pm.a); - pm.ty = 1 - (sourceFrame.y * pm.d); - } - } - - /** - * Sets the transform of the active render target to the given matrix - * - * @param {PIXI.Matrix} matrix - The transformation matrix - */ - setTransform()// matrix) - { - // this._activeRenderTarget.transform = matrix; - } -} diff --git a/packages/core/src/renderers/systems/RenderTextureSystem.js b/packages/core/src/renderers/systems/RenderTextureSystem.js deleted file mode 100644 index a133ef1..0000000 --- a/packages/core/src/renderers/systems/RenderTextureSystem.js +++ /dev/null @@ -1,115 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { Rectangle } from '@pixi/math'; - -const tempRect = new Rectangle(); - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ - -export default class RenderTextureSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this.clearColor = renderer._backgroundColorRgba; - - // TODO moe this property somewhere else! - this.defaultMaskStack = []; - this.defaultFilterStack = [{}]; - - // empty render texture? - this.renderTexture = null; - - this.destinationFrame = new Rectangle(); - } - - bind(renderTexture, sourceFrame, destinationFrame) - { - // TODO - do we want this?? - if (this.renderTexture === renderTexture) return; - this.renderTexture = renderTexture; - - const renderer = this.renderer; - - if (renderTexture) - { - const baseTexture = renderTexture.baseTexture; - - if (!destinationFrame) - { - tempRect.width = baseTexture.realWidth; - tempRect.height = baseTexture.realHeight; - - destinationFrame = tempRect; - } - - if (!sourceFrame) - { - sourceFrame = destinationFrame; - } - - this.renderer.framebuffer.bind(baseTexture.frameBuffer, destinationFrame); - - this.renderer.projection.update(destinationFrame, sourceFrame, baseTexture.resolution, false); - this.renderer.stencil.setMaskStack(baseTexture.stencilMaskStack); - } - else - { - // TODO these validation checks happen deeper down.. - // thing they can be avoided.. - if (!destinationFrame) - { - tempRect.width = renderer.width; - tempRect.height = renderer.height; - - destinationFrame = tempRect; - } - - if (!sourceFrame) - { - sourceFrame = destinationFrame; - } - - renderer.framebuffer.bind(null, destinationFrame); - - // TODO store this.. - this.renderer.projection.update(destinationFrame, sourceFrame, this.renderer.resolution, true); - this.renderer.stencil.setMaskStack(this.defaultMaskStack); - } - - this.destinationFrame.copyFrom(destinationFrame); - } - - /** - * Erases the render texture and fills the drawing area with a colour - * - * @param {number} [clearColor] - The colour - * @return {PIXI.Renderer} Returns itself. - */ - clear(clearColor) - { - if (this.renderTexture) - { - clearColor = clearColor || this.renderTexture.baseTexture.clearColor; - } - else - { - clearColor = clearColor || this.clearColor; - } - - this.renderer.framebuffer.clear(clearColor[0], clearColor[1], clearColor[2], clearColor[3]); - } - - resize()// screenWidth, screenHeight) - { - // resize the root only! - this.bind(null); - } -} diff --git a/packages/core/src/renderers/systems/StateSystem.js b/packages/core/src/renderers/systems/StateSystem.js deleted file mode 100755 index d7f4caf..0000000 --- a/packages/core/src/renderers/systems/StateSystem.js +++ /dev/null @@ -1,288 +0,0 @@ -import mapWebGLBlendModesToPixi from '../utils/mapWebGLBlendModesToPixi'; -import WebGLSystem from './WebGLSystem'; -import WebGLState from '../State'; - -const BLEND = 0; -const OFFSET = 1; -const CULLING = 2; -const DEPTH_TEST = 3; -const WINDING = 4; - -/** - * A WebGL state machines - * - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class StateSystem extends WebGLSystem -{ - /** - * @param {WebGLRenderingContext} gl - The current WebGL rendering context - */ - constructor(renderer) - { - super(renderer); - - this.gl = null; - - this.maxAttribs = null; - - // check we have vao.. - this.nativeVaoExtension = null; - - this.attribState = null; - - this.stateId = 0; - this.polygonOffset = 0; - this.blendMode = 17; - - this.map = []; - - // map functions for when we set state.. - this.map[BLEND] = this.setBlend; - this.map[OFFSET] = this.setOffset; - this.map[CULLING] = this.setCullFace; - this.map[DEPTH_TEST] = this.setDepthTest; - this.map[WINDING] = this.setFrontFace; - - this.checks = []; - - this.defaultState = new WebGLState(); - this.defaultState.blend = true; - this.defaultState.depth = true; - } - - contextChange(gl) - { - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; - - this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); - - // check we have vao.. - this.nativeVaoExtension = ( - gl.getExtension('OES_vertex_array_object') - || gl.getExtension('MOZ_OES_vertex_array_object') - || gl.getExtension('WEBKIT_OES_vertex_array_object') - ); - - this.attribState = { - tempAttribState: new Array(this.maxAttribs), - attribState: new Array(this.maxAttribs), - }; - - this.blendModes = mapWebGLBlendModesToPixi(gl); - - this.setState(this.defaultState); - - this.reset(); - } - - /** - * Sets the current state - * - * @param {*} state - The state to set. - */ - setState(state) - { - state = state || this.defaultState; - - // TODO maybe to an object check? ( this.state === state )? - if (this.stateId !== state.data) - { - let diff = this.stateId ^ state.data; - let i = 0; - - // order from least to most common - while (diff) - { - if (diff & 1) - { - // state change! - this.map[i].call(this, !!(state.data & (1 << i))); - } - - diff = diff >> 1; - i++; - } - - this.stateId = state.data; - } - - // based on the above settings we check for specific modes.. - // for example if blend is active we check and set the blend modes - // or of polygon offset is active we check the poly depth. - for (let i = 0; i < this.checks.length; i++) - { - this.checks[i](this, state); - } - } - - /** - * Enables or disabled blending. - * - * @param {boolean} value - Turn on or off webgl blending. - */ - setBlend(value) - { - this.updateCheck(StateSystem.checkBlendMode, value); - - this.gl[value ? 'enable' : 'disable'](this.gl.BLEND); - } - - /** - * Enables or disable polygon offset fill - * - * @param {boolean} value - Turn on or off webgl polygon offset testing. - */ - setOffset(value) - { - this.gl[value ? 'enable' : 'disable'](this.gl.POLYGON_OFFSET_FILL); - } - - /** - * Sets whether to enable or disable depth test. - * - * @param {boolean} value - Turn on or off webgl depth testing. - */ - setDepthTest(value) - { - this.gl[value ? 'enable' : 'disable'](this.gl.DEPTH_TEST); - } - - /** - * Sets whether to enable or disable cull face. - * - * @param {boolean} value - Turn on or off webgl cull face. - */ - setCullFace(value) - { - this.gl[value ? 'enable' : 'disable'](this.gl.CULL_FACE); - } - - /** - * Sets the gl front face. - * - * @param {boolean} value - true is clockwise and false is counter-clockwise - */ - setFrontFace(value) - { - this.gl.frontFace(this.gl[value ? 'CW' : 'CCW']); - } - - /** - * Sets the blend mode. - * - * @param {number} value - The blend mode to set to. - */ - setBlendMode(value) - { - if (value === this.blendMode) - { - return; - } - - this.blendMode = value; - this.gl.blendFunc(this.blendModes[value][0], this.blendModes[value][1]); - } - - /** - * Sets the polygon offset. - * - * @param {number} value - the polygon offset - * @param {number} scale - the polygon offset scale - */ - setPolygonOffset(value, scale) - { - this.gl.polygonOffset(value, scale); - } - - /** - * Disables all the vaos in use - * - */ - resetAttributes() - { - for (let i = 0; i < this.attribState.tempAttribState.length; i++) - { - this.attribState.tempAttribState[i] = 0; - } - - for (let i = 0; i < this.attribState.attribState.length; i++) - { - this.attribState.attribState[i] = 0; - } - - // im going to assume one is always active for performance reasons. - for (let i = 1; i < this.maxAttribs; i++) - { - this.gl.disableVertexAttribArray(i); - } - } - - // used - /** - * Resets all the logic and disables the vaos - */ - reset() - { - // unbind any VAO if they exist.. - if (this.nativeVaoExtension) - { - this.nativeVaoExtension.bindVertexArrayOES(null); - } - - // reset all attributes.. - this.resetAttributes(); - - this.gl.pixelStorei(this.gl.UNPACK_FLIP_Y_WEBGL, false); - - this.setBlendMode(0); - - // TODO? - // this.setState(this.defaultState); - } - - /** - * checks to see which updates should be checked based on which settings have been activated - * for example if blend is enabled then we shold check the blend modes each time the state is changed - * or if poygon fill is activated then we need to check if the polygone offset changes. - * The idea is that we only check what we have too. - * - * @param {Function} func the checking function to add or remove - * @param {boolean} value should the check function be added or removed. - */ - updateCheck(func, value) - { - const index = this.checks.indexOf(func); - - if (value && index === -1) - { - this.checks.push(func); - } - else if (!value && index !== -1) - { - this.checks.splice(index, 1); - } - } - - /** - * A private little wrapper function that we call to check the blend mode. - * - * @static - * @private - * @param {PIXI.StateSystem} System the System to perform the state check on - * @param {PIXI.State} state the state that the blendMode will pulled from - */ - static checkBlendMode(system, state) - { - system.setBlendMode(state.blendMode); - } - - // TODO - add polygon offset? -} diff --git a/packages/core/src/renderers/systems/StencilSystem.js b/packages/core/src/renderers/systems/StencilSystem.js deleted file mode 100644 index 5d538cb..0000000 --- a/packages/core/src/renderers/systems/StencilSystem.js +++ /dev/null @@ -1,133 +0,0 @@ -import WebGLSystem from './WebGLSystem'; - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class StencilSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - this.stencilMaskStack = []; - } - - /** - * Changes the mask stack that is used by this System. - * - * @param {PIXI.Graphics[]} stencilMaskStack - The mask stack - */ - setMaskStack(stencilMaskStack) - { - const gl = this.renderer.gl; - - if (stencilMaskStack.length !== this.stencilMaskStack.length) - { - if (stencilMaskStack.length === 0) - { - gl.disable(gl.STENCIL_TEST); - } - else - { - gl.enable(gl.STENCIL_TEST); - } - } - - this.stencilMaskStack = stencilMaskStack; - } - - /** - * Applies the Mask and adds it to the current stencil stack. @alvin - * - * @param {PIXI.Graphics} graphics - The mask - */ - pushStencil(graphics) - { - this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); - - // this.renderer._activeRenderTarget.attachStencilBuffer(); - - const gl = this.renderer.gl; - const prevMaskCount = this.stencilMaskStack.length; - - if (prevMaskCount === 0) - { - gl.enable(gl.STENCIL_TEST); - } - - this.stencilMaskStack.push(graphics); - - // Increment the refference stencil value where the new mask overlaps with the old ones. - gl.colorMask(false, false, false, false); - gl.stencilFunc(gl.EQUAL, prevMaskCount, this._getBitwiseMask()); - gl.stencilOp(gl.KEEP, gl.KEEP, gl.INCR); - this.renderer.plugins.graphics.render(graphics); - - this._useCurrent(); - } - - /** - * Removes the last mask from the stencil stack. @alvin - */ - popStencil() - { - this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); - - const gl = this.renderer.gl; - const graphics = this.stencilMaskStack.pop(); - - if (this.stencilMaskStack.length === 0) - { - // the stack is empty! - gl.disable(gl.STENCIL_TEST); - gl.clear(gl.STENCIL_BUFFER_BIT); - gl.clearStencil(0); - } - else - { - // Decrement the refference stencil value where the popped mask overlaps with the other ones - gl.colorMask(false, false, false, false); - gl.stencilOp(gl.KEEP, gl.KEEP, gl.DECR); - this.renderer.plugins.graphics.render(graphics); - - this._useCurrent(); - } - } - - /** - * Setup renderer to use the current stencil data. - */ - _useCurrent() - { - const gl = this.renderer.gl; - - gl.colorMask(true, true, true, true); - gl.stencilFunc(gl.EQUAL, this.stencilMaskStack.length, this._getBitwiseMask()); - gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP); - } - - /** - * Fill 1s equal to the number of acitve stencil masks. - * - * @return {number} The bitwise mask. - */ - _getBitwiseMask() - { - return (1 << this.stencilMaskStack.length) - 1; - } - - /** - * Destroys the mask stack. - * - */ - destroy() - { - super.destroy(this); - - this.stencilMaskStack = null; - } -} diff --git a/packages/core/src/renderers/systems/WebGLSystem.js b/packages/core/src/renderers/systems/WebGLSystem.js deleted file mode 100644 index d3d5b31..0000000 --- a/packages/core/src/renderers/systems/WebGLSystem.js +++ /dev/null @@ -1,41 +0,0 @@ -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this manager works for. - */ - constructor(renderer) - { - /** - * The renderer this manager works for. - * - * @member {PIXI.Renderer} - */ - this.renderer = renderer; - - this.renderer.runners.contextChange.add(this); - } - - /** - * Generic method called when there is a WebGL context change. - * - */ - contextChange() - { - // do some codes init! - } - - /** - * Generic destroy methods to be overridden by the subclass - * - */ - destroy() - { - this.renderer.runners.contextChange.remove(this); - this.renderer = null; - } -} diff --git a/packages/core/src/renderers/systems/filter/FilterSystem.js b/packages/core/src/renderers/systems/filter/FilterSystem.js deleted file mode 100644 index e615037..0000000 --- a/packages/core/src/renderers/systems/filter/FilterSystem.js +++ /dev/null @@ -1,431 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; - -import RenderTexture from '../../../textures/RenderTexture'; -import Quad from '../../utils/Quad'; -import QuadUv from '../../utils/QuadUv'; -import { Rectangle } from '@pixi/math'; -import * as filterTransforms from '../../filters/filterTransforms'; -import bitTwiddle from 'bit-twiddle'; -import UniformGroup from '../../../shader/UniformGroup'; -import { DRAW_MODES } from '../../../../../constants'; - -// -/** - * @ignore - * @class - */ -class FilterState -{ - /** - * - */ - constructor() - { - this.renderTexture = null; - this.sourceFrame = new Rectangle(); - this.destinationFrame = new Rectangle(); - this.filters = []; - this.target = null; - this.legacy = false; - this.resolution = 1; - } -} - -/** - * @class - * @memberof PIXI.systems - * @extends PIXI.systems.WebGLSystem - */ -export default class FilterSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - /** - * stores a bunch of PO2 textures used for filtering - * @type {Object} - */ - this.texturePool = {}; - - /** - * a pool for storing filter states, save us creating new ones each tick - * @type {Array} - */ - this.statePool = []; - - /** - * A very simple geometry used when drawing a filter effect to the screen - * @type {Quad} - */ - this.quad = new Quad(); - - this.quadUv = new QuadUv(); - - /** - * Temporary rect for maths - * @type {PIXI.Rectangle} - */ - this.tempRect = new Rectangle(); - - this.activeState = {}; - - /** - * this uniform group is attached to filter uniforms when used - * @type {UniformGroup} - */ - this.globalUniforms = new UniformGroup({ - sourceFrame: this.tempRect, - destinationFrame: this.tempRect, - - // legacy variables - filterArea: new Float32Array(4), - filterClamp: new Float32Array(4), - }, true); - } - - /** - * Adds a new filter to the System. - * - * @param {PIXI.DisplayObject} target - The target of the filter to render. - * @param {PIXI.Filter[]} filters - The filters to apply. - */ - push(target, filters) - { - const renderer = this.renderer; - const filterStack = this.renderer.renderTexture.defaultFilterStack; - const state = this.statePool.pop() || new FilterState(); - - let resolution = filters[0].resolution; - let padding = filters[0].padding; - let autoFit = filters[0].autoFit; - let legacy = filters[0].legacy; - - for (let i = 1; i < filters.length; i++) - { - const filter = filters[i]; - - // lets use the lowest resolution.. - resolution = Math.min(resolution, filter.resolution); - // and the largest amount of padding! - padding = Math.max(padding, filter.padding); - // only auto fit if all filters are autofit - autoFit = autoFit || filter.autoFit; - - legacy = legacy || filter.legacy; - } - - filterStack.push(state); - - state.resolution = resolution; - - state.legacy = legacy; - - // round to whole number based on resolution - // TODO move that to the shader too? - state.sourceFrame = target.filterArea || target.getBounds(true); - - state.sourceFrame.pad(padding); - - if (autoFit) - { - state.sourceFrame.fit(this.renderer.renderTexture.destinationFrame); - } - - state.sourceFrame.round(resolution); - - state.renderTexture = this.getPotFilterTexture(state.sourceFrame.width, state.sourceFrame.height, resolution); - state.filters = filters; - - state.destinationFrame.width = state.renderTexture.width; - state.destinationFrame.height = state.renderTexture.height; - - state.renderTexture.filterFrame = state.sourceFrame; - renderer.renderTexture.bind(state.renderTexture, state.sourceFrame);// /, state.destinationFrame); - renderer.renderTexture.clear(); - } - - /** - * Pops off the filter and applies it. - * - */ - pop() - { - const renderer = this.renderer; - const filterStack = renderer.renderTexture.defaultFilterStack; - const state = filterStack.pop(); - const filters = state.filters; - - this.activeState = state; - - const globalUniforms = this.globalUniforms.uniforms; - - globalUniforms.sourceFrame = state.sourceFrame; - globalUniforms.destinationFrame = state.destinationFrame; - globalUniforms.resolution = state.resolution; - - // only update the rect if its legacy.. - if (state.legacy) - { - const filterArea = globalUniforms.filterArea; - const filterClamp = globalUniforms.filterClamp; - - filterArea[0] = state.destinationFrame.width; - filterArea[1] = state.destinationFrame.height; - filterArea[2] = state.sourceFrame.x; - filterArea[3] = state.sourceFrame.y; - - filterClamp[0] = 0.5 / state.resolution / state.destinationFrame.width; - filterClamp[1] = 0.5 / state.resolution / state.destinationFrame.height; - filterClamp[2] = (state.sourceFrame.width - 0.5) / state.resolution / state.destinationFrame.width; - filterClamp[3] = (state.sourceFrame.height - 0.5) / state.resolution / state.destinationFrame.height; - } - - this.globalUniforms.update(); - - const lastState = filterStack[filterStack.length - 1]; - - if (filters.length === 1) - { - filters[0].apply(this, state.renderTexture, lastState.renderTexture, false, state); - - this.returnFilterTexture(state.renderTexture); - } - else - { - let flip = state.renderTexture; - let flop = this.getPotFilterTexture( - flip.width, - flip.height, - state.resolution - ); - - flop.filterFrame = flip.filterFrame; - - let i = 0; - - for (i = 0; i < filters.length - 1; ++i) - { - filters[i].apply(this, flip, flop, true, state); - - const t = flip; - - flip = flop; - flop = t; - } - - filters[i].apply(this, flip, lastState.renderTexture, false, state); - - this.returnFilterTexture(flip); - this.returnFilterTexture(flop); - } - - this.statePool.push(state); - } - - /** - * Draws a filter. - * - * @param {PIXI.Filter} filter - The filter to draw. - * @param {PIXI.RenderTarget} input - The input render target. - * @param {PIXI.RenderTarget} output - The target to output to. - * @param {boolean} clear - Should the output be cleared before rendering to it - */ - applyFilter(filter, input, output, clear) - { - const renderer = this.renderer; - - renderer.renderTexture.bind(output, output ? output.filterFrame : null); - - if (clear) - { - // gl.disable(gl.SCISSOR_TEST); - renderer.renderTexture.clear(); - // gl.enable(gl.SCISSOR_TEST); - } - - // set the uniforms.. - filter.uniforms.uSampler = input; - filter.uniforms.filterGlobals = this.globalUniforms; - - // TODO make it so that the order of this does not matter.. - // because it does at the moment cos of global uniforms. - // they need to get resynced - - renderer.state.setState(filter.state); - renderer.shader.bind(filter); - - if (filter.legacy) - { - this.quadUv.map(input._frame, input.filterFrame); - - renderer.geometry.bind(this.quadUv); - renderer.geometry.draw(DRAW_MODES.TRIANGLES); - } - else - { - renderer.geometry.bind(this.quad); - renderer.geometry.draw(DRAW_MODES.TRIANGLE_STRIP); - } - } - - /** - * Calculates the mapped matrix. - * - * TODO playing around here.. this is temporary - (will end up in the shader) - * this returns a matrix that will normalise map filter cords in the filter to screen space - * - * @param {PIXI.Matrix} outputMatrix - the matrix to output to. - * @return {PIXI.Matrix} The mapped matrix. - */ - calculateScreenSpaceMatrix(outputMatrix) - { - const currentState = this.activeState; - - return filterTransforms.calculateScreenSpaceMatrix( - outputMatrix, - currentState.sourceFrame, - currentState.destinationFrame - ); - } - - /** - * This will map the filter coord so that a texture can be used based on the transform of a sprite - * - * @param {PIXI.Matrix} outputMatrix - The matrix to output to. - * @param {PIXI.Sprite} sprite - The sprite to map to. - * @return {PIXI.Matrix} The mapped matrix. - */ - calculateSpriteMatrix(outputMatrix, sprite) - { - const currentState = this.activeState; - - return filterTransforms.calculateSpriteMatrix( - outputMatrix, - currentState.sourceFrame, - currentState.destinationFrame, - sprite - ); - } - - /** - * Destroys this Filter System. - * - * @param {boolean} [contextLost=false] context was lost, do not free shaders - * - */ - destroy(contextLost = false) - { - if (!contextLost) - { - this.emptyPool(); - } - else - { - this.texturePool = {}; - } - } - - /** - * Gets a Power-of-Two render texture. - * - * TODO move to a seperate class could be on renderer? - * also - could cause issue with multiple contexts? - * - * @private - * @param {WebGLRenderingContext} gl - The webgl rendering context - * @param {number} minWidth - The minimum width of the render target. - * @param {number} minHeight - The minimum height of the render target. - * @param {number} resolution - The resolution of the render target. - * @return {PIXI.RenderTarget} The new render target. - */ - getPotFilterTexture(minWidth, minHeight, resolution) - { - minWidth = bitTwiddle.nextPow2(minWidth); - minHeight = bitTwiddle.nextPow2(minHeight); - resolution = resolution || 1; - - const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); - - if (!this.texturePool[key]) - { - this.texturePool[key] = []; - } - - let renderTexture = this.texturePool[key].pop(); - - if (!renderTexture) - { - // temporary bypass cache.. - // internally - this will cause a texture to be bound.. - renderTexture = RenderTexture.create({ - width: minWidth, - height: minHeight, - resolution, - }); - } - - return renderTexture; - } - - /** - * Gets extra render texture to use inside current filter - * - * @param {number} resolution resolution of the renderTexture - * @returns {PIXI.RenderTexture} - */ - getFilterTexture(resolution) - { - const rt = this.activeState.renderTexture; - - const filterTexture = this.getPotFilterTexture(rt.width, rt.height, resolution || rt.baseTexture.resolution); - - filterTexture.filterFrame = rt.filterFrame; - - return filterTexture; - } - - /** - * Frees a render texture back into the pool. - * - * @param {PIXI.RenderTarget} renderTexture - The renderTarget to free - */ - returnFilterTexture(renderTexture) - { - renderTexture.filterFrame = null; - - const base = renderTexture.baseTexture; - - const minWidth = base.width; - const minHeight = base.height; - - const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); - - this.texturePool[key].push(renderTexture); - } - - /** - * Empties the texture pool. - * - */ - emptyPool() - { - for (const i in this.texturePool) - { - const textures = this.texturePool[i]; - - if (textures) - { - for (let j = 0; j < textures.length; j++) - { - textures[j].destroy(true); - } - } - } - - this.texturePool = {}; - } -} diff --git a/packages/core/src/renderers/systems/geometry/GLBuffer.js b/packages/core/src/renderers/systems/geometry/GLBuffer.js deleted file mode 100644 index 0477e8a..0000000 --- a/packages/core/src/renderers/systems/geometry/GLBuffer.js +++ /dev/null @@ -1,9 +0,0 @@ -export default class GLBuffer -{ - constructor(buffer) - { - this.buffer = buffer; - this.updateID = -1; - this.byteLength = -1; - } -} diff --git a/packages/core/src/renderers/systems/geometry/GeometrySystem.js b/packages/core/src/renderers/systems/geometry/GeometrySystem.js deleted file mode 100644 index 26c8e9e..0000000 --- a/packages/core/src/renderers/systems/geometry/GeometrySystem.js +++ /dev/null @@ -1,388 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; -import GLBuffer from './GLBuffer'; - -const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class GeometrySystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this._activeGeometry = null; - this._activeVao = null; - - this.hasVao = true; - this.hasInstance = true; - } - - /** - * Sets up the renderer context and necessary buffers. - * - * @private - */ - contextChange() - { - const gl = this.gl = this.renderer.gl; - - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - - // webgl2 - if (!gl.createVertexArray) - { - // webgl 1! - let nativeVaoExtension = this.renderer.context.extensions.vertexArrayObject; - - if (this.renderer.legacy) - { - nativeVaoExtension = null; - } - - if (nativeVaoExtension) - { - gl.createVertexArray = () => - nativeVaoExtension.createVertexArrayOES(); - - gl.bindVertexArray = (vao) => - nativeVaoExtension.bindVertexArrayOES(vao); - - gl.deleteVertexArray = (vao) => - nativeVaoExtension.deleteVertexArrayOES(vao); - } - else - { - this.hasVao = false; - gl.createVertexArray = () => - { - // empty - }; - - gl.bindVertexArray = () => - { - // empty - }; - - gl.deleteVertexArray = () => - { - // empty - }; - } - } - - if (!gl.vertexAttribDivisor) - { - const instanceExt = gl.getExtension('ANGLE_instanced_arrays'); - - if (instanceExt) - { - gl.vertexAttribDivisor = (a, b) => - instanceExt.vertexAttribDivisorANGLE(a, b); - - gl.drawElementsInstanced = (a, b, c, d, e) => - instanceExt.drawElementsInstancedANGLE(a, b, c, d, e); - - gl.drawArraysInstanced = (a, b, c, d) => - instanceExt.drawArraysInstancedANGLE(a, b, c, d); - } - else - { - this.hasInstance = false; - } - } - } - - /** - * Binds geometry so that is can be drawn. Creating a Vao if required - * @private - * @param {PIXI.Geometry} geometry instance of geometry to bind - */ - bind(geometry, shader) - { - shader = shader || this.renderer.shader.shader; - - const gl = this.gl; - - // not sure the best way to address this.. - // currently different shaders require different VAOs for the same geometry - // Still mulling over the best way to solve this one.. - // will likely need to modify the shader attribute locations at run time! - let vaos = geometry.glVertexArrayObjects[this.CONTEXT_UID]; - - if (!vaos) - { - geometry.glVertexArrayObjects[this.CONTEXT_UID] = vaos = {}; - } - - const vao = vaos[shader.program.id] || this.initGeometryVao(geometry, shader.program); - - this._activeGeometry = geometry; - - if (this._activeVao !== vao) - { - this._activeVao = vao; - - if (this.hasVao) - { - gl.bindVertexArray(vao); - } - else - { - this.activateVao(geometry, shader.program); - } - } - - // TODO - optimise later! - // don't need to loop through if nothing changed! - // maybe look to add an 'autoupdate' to geometry? - this.updateBuffers(); - } - - reset() - { - this.unbind(); - } - - updateBuffers() - { - const geometry = this._activeGeometry; - const gl = this.gl; - - for (let i = 0; i < geometry.buffers.length; i++) - { - const buffer = geometry.buffers[i]; - - const glBuffer = buffer._glBuffers[this.CONTEXT_UID]; - - if (buffer._updateID !== glBuffer.updateID) - { - glBuffer.updateID = buffer._updateID; - - // TODO can cache this on buffer! maybe added a getter / setter? - const type = buffer.index ? gl.ELEMENT_ARRAY_BUFFER : gl.ARRAY_BUFFER; - const drawType = buffer.static ? gl.STATIC_DRAW : gl.DYNAMIC_DRAW; - - gl.bindBuffer(type, glBuffer.buffer); - - if (glBuffer.byteLength >= buffer.data.byteLength) - { - // offset is always zero for now! - gl.bufferSubData(type, 0, buffer.data); - } - else - { - gl.bufferData(type, buffer.data, drawType); - } - } - } - } - - checkCompatability(geometry, program) - { - // geometry must have at least all the attributes that the shader requires. - const geometryAttributes = geometry.attributes; - const shaderAttributes = program.attributeData; - - for (const j in shaderAttributes) - { - if (!geometryAttributes[j]) - { - throw new Error(`shader and geometry incompatible, geometry missing the "${j}" attribute`); - } - } - } - - /** - * Creates a Vao with the same structure as the geometry and stores it on the geometry. - * @private - * @param {PIXI.Geometry} geometry instance of geometry to to generate Vao for - */ - initGeometryVao(geometry, program) - { - this.checkCompatability(geometry, program); - - const gl = this.gl; - const CONTEXT_UID = this.CONTEXT_UID; - const buffers = geometry.buffers; - const attributes = geometry.attributes; - - const tempStride = {}; - const tempStart = {}; - - for (const j in buffers) - { - tempStride[j] = 0; - tempStart[j] = 0; - } - - for (const j in attributes) - { - if (!attributes[j].size && program.attributeData[j]) - { - attributes[j].size = program.attributeData[j].size; - } - - tempStride[attributes[j].buffer] += attributes[j].size * byteSizeMap[attributes[j].type]; - } - - for (const j in attributes) - { - const attribute = attributes[j]; - const attribSize = attribute.size; - - if (attribute.stride === undefined) - { - if (tempStride[attribute.buffer] === attribSize * byteSizeMap[attribute.type]) - { - attribute.stride = 0; - } - else - { - attribute.stride = tempStride[attribute.buffer]; - } - } - - if (attribute.start === undefined) - { - attribute.start = tempStart[attribute.buffer]; - - tempStart[attribute.buffer] += attribSize * byteSizeMap[attribute.type]; - } - } - - // first update - and create the buffers! - // only create a gl buffer if it actually gets - for (let i = 0; i < buffers.length; i++) - { - const buffer = buffers[i]; - - if (!buffer._glBuffers[CONTEXT_UID]) - { - buffer._glBuffers[CONTEXT_UID] = new GLBuffer(gl.createBuffer()); - } - } - - // TODO - maybe make this a data object? - // lets wait to see if we need to first! - const vao = gl.createVertexArray(); - - gl.bindVertexArray(vao); - - this.activateVao(geometry, program); - - gl.bindVertexArray(null); - - // add it to the cache! - geometry.glVertexArrayObjects[this.CONTEXT_UID][program.id] = vao; - - return vao; - } - - activateVao(geometry, program) - { - const gl = this.gl; - const CONTEXT_UID = this.CONTEXT_UID; - const buffers = geometry.buffers; - const attributes = geometry.attributes; - - if (geometry.indexBuffer) - { - // first update the index buffer if we have one.. - gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, geometry.indexBuffer._glBuffers[CONTEXT_UID].buffer); - } - - let lastBuffer = null; - - // add a new one! - for (const j in attributes) - { - const attribute = attributes[j]; - const buffer = buffers[attribute.buffer]; - const glBuffer = buffer._glBuffers[CONTEXT_UID]; - - if (program.attributeData[j]) - { - if (lastBuffer !== glBuffer) - { - gl.bindBuffer(gl.ARRAY_BUFFER, glBuffer.buffer); - - lastBuffer = glBuffer; - } - - const location = program.attributeData[j].location; - - // TODO introduce state again - // we can optimise this for older devices that have no VAOs - gl.enableVertexAttribArray(location); - - gl.vertexAttribPointer(location, - attribute.size, - attribute.type || gl.FLOAT, - attribute.normalized, - attribute.stride, - attribute.start); - - if (attribute.instance) - { - // TODO calculate instance count based of this... - if (this.hasInstance) - { - gl.vertexAttribDivisor(location, 1); - } - else - { - throw new Error('geometry error, GPU Instancing is not supported on this device'); - } - } - } - } - } - - draw(type, size, start, instanceCount) - { - const gl = this.gl; - const geometry = this._activeGeometry; - - // TODO.. this should not change so maybe cache the function? - - if (geometry.indexBuffer) - { - if (geometry.instanced) - { - /* eslint-disable max-len */ - gl.drawElementsInstanced(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2, instanceCount || 1); - /* eslint-enable max-len */ - } - else - { - gl.drawElements(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2); - } - } - else - if (geometry.instanced) - { - // TODO need a better way to calculate size.. - gl.drawArraysInstanced(type, start, size || geometry.getSize(), instanceCount || 1); - } - else - { - gl.drawArrays(type, start, size || geometry.getSize()); - } - - return this; - } - - unbind() - { - this.gl.bindVertexArray(null); - this._activeVao = null; - this._activeGeometry = null; - } -} diff --git a/packages/core/src/renderers/systems/geometry/setVertexAttribArrays.js b/packages/core/src/renderers/systems/geometry/setVertexAttribArrays.js deleted file mode 100644 index 23b6959..0000000 --- a/packages/core/src/renderers/systems/geometry/setVertexAttribArrays.js +++ /dev/null @@ -1,54 +0,0 @@ -// var GL_MAP = {}; - -/** - * @param gl {WebGLRenderingContext} The current WebGL context - * @param attribs {*} - * @param state {*} - */ -export default function setVertexAttribArrays(gl, attribs, state) -{ - let i; - - if (state) - { - const tempAttribState = state.tempAttribState; - const attribState = state.attribState; - - for (i = 0; i < tempAttribState.length; i++) - { - tempAttribState[i] = false; - } - - // set the new attribs - for (i = 0; i < attribs.length; i++) - { - tempAttribState[attribs[i].attribute.location] = true; - } - - for (i = 0; i < attribState.length; i++) - { - if (attribState[i] !== tempAttribState[i]) - { - attribState[i] = tempAttribState[i]; - - if (state.attribState[i]) - { - gl.enableVertexAttribArray(i); - } - else - { - gl.disableVertexAttribArray(i); - } - } - } - } - else - { - for (i = 0; i < attribs.length; i++) - { - const attrib = attribs[i]; - - gl.enableVertexAttribArray(attrib.attribute.location); - } - } -} diff --git a/packages/core/src/renderers/systems/index.js b/packages/core/src/renderers/systems/index.js deleted file mode 100644 index c843f03..0000000 --- a/packages/core/src/renderers/systems/index.js +++ /dev/null @@ -1,6 +0,0 @@ -/** - * Systems are individual components to the Renderer pipeline. - * @namespace PIXI.systems - */ -export { default as WebGLSystem } from './WebGLSystem'; -export { default as FilterSystem } from './filter/FilterSystem'; diff --git a/packages/core/src/renderers/systems/shader/GLProgram.js b/packages/core/src/renderers/systems/shader/GLProgram.js deleted file mode 100644 index 92980ca..0000000 --- a/packages/core/src/renderers/systems/shader/GLProgram.js +++ /dev/null @@ -1,43 +0,0 @@ -/** - * Helper class to create a webGL Program - * - * @class - * @private - */ -export default class GLProgram -{ - constructor(program, uniformData) - { - /** - * The shader program - * - * @member {WebGLProgram} - */ - this.program = program; - - /** - * holds the uniform data which contains uniform locations - * and current uniform values used for caching and preventing undeed GPU commands - * @type {Object} - */ - this.uniformData = uniformData; - - /** - * uniformGroups holds the various upload functions for the shader. Each uniform group - * and program have a unique upload function generated. - * @type {Object} - */ - this.uniformGroups = {}; - } - - /** - * Destroys this program - * TODO - */ - destroy() - { - this.uniformData = null; - this.uniformGroups = null; - this.program = null; - } -} diff --git a/packages/core/src/renderers/systems/shader/ShaderSystem.js b/packages/core/src/renderers/systems/shader/ShaderSystem.js deleted file mode 100644 index 6cf57b6..0000000 --- a/packages/core/src/renderers/systems/shader/ShaderSystem.js +++ /dev/null @@ -1,170 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; -import GLProgram from './GLProgram'; -import generateUniformsSync from '../../../shader/generateUniformsSync'; -import defaultValue from '../../../shader/defaultValue'; -import compileProgram from './shader/compileProgram'; - -let UID = 0; - -/** - * Helper class to create a webGL Texture - * - * @class - * @memberof PIXI - */ -export default class ShaderSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - A reference to the current renderer - */ - constructor(renderer) - { - super(renderer); - - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = null; - - this.shader = null; - this.program = null; - - this.id = UID++; - } - - contextChange(gl) - { - this.gl = gl; - } - - /** - * Changes the current shader to the one given in parameter - * - * @param {PIXI.Shader} shader - the new shader - * @param {boolean} dontSync - false if the shader should automatically sync its uniforms. - * @returns {PIXI.glCore.glProgram} the glProgram that belongs to the shader. - */ - bind(shader, dontSync) - { - shader.uniforms.globals = this.renderer.globalUniforms; - - const program = shader.program; - const glProgram = program.glPrograms[this.renderer.CONTEXT_UID] || this.generateShader(shader); - - this.shader = shader; - - // TODO - some current pixi plugins bypass this.. so it not safe to use yet.. - if (this.program !== program) - { - this.program = program; - this.gl.useProgram(glProgram.program); - } - - if (!dontSync) - { - this.syncUniformGroup(shader.uniformGroup); - } - - return glProgram; - } - - /** - * Uploads the uniforms values to the currently bound shader. - * - * @param {object} uniforms - the uniforms valiues that be applied to the current shader - */ - setUniforms(uniforms) - { - const shader = this.shader.program; - const glProgram = shader.glPrograms[this.renderer.CONTEXT_UID]; - - shader.syncUniforms(glProgram.uniformData, uniforms, this.renderer); - } - - syncUniformGroup(group) - { - const glProgram = this.getglProgram(); - - if (!group.static || group.dirtyId !== glProgram.uniformGroups[group.id]) - { - glProgram.uniformGroups[group.id] = group.dirtyId; - const syncFunc = group.syncUniforms[this.shader.program.id] || this.createSyncGroups(group); - - syncFunc(glProgram.uniformData, group.uniforms, this.renderer); - } - } - - createSyncGroups(group) - { - group.syncUniforms[this.shader.program.id] = generateUniformsSync(group, this.shader.program.uniformData); - - return group.syncUniforms[this.shader.program.id]; - } - - /** - * Returns the underlying GLShade rof the currently bound shader. - * This can be handy for when you to have a little more control over the setting of your uniforms. - * - * @return {PIXI.glCore.Shader} the glProgram for the currently bound Shader for this context - */ - getglProgram() - { - if (this.shader) - { - return this.shader.program.glPrograms[this.renderer.CONTEXT_UID]; - } - - return null; - } - - /** - * Generates a glProgram verion of the Shader provided. - * - * @private - * @param {PIXI.Shader} shader the shader that the glProgram will be based on. - * @return {PIXI.glCore.glProgram} A shiney new glProgram - */ - generateShader(shader) - { - const gl = this.gl; - - const program = shader.program; - - const attribMap = {}; - - for (const i in program.attributeData) - { - attribMap[i] = program.attributeData[i].location; - } - - const shaderProgram = compileProgram(gl, program.vertexSrc, program.fragmentSrc, attribMap); - const uniformData = {}; - - for (const i in program.uniformData) - { - const data = program.uniformData[i]; - - uniformData[i] = { - location: gl.getUniformLocation(shaderProgram, i), - value: defaultValue(data.type, data.size), - }; - } - - const glProgram = new GLProgram(shaderProgram, uniformData); - - program.glPrograms[this.renderer.CONTEXT_UID] = glProgram; - - return glProgram; - } - - /** - * Destroys this System and removes all its textures - */ - destroy() - { - // TODO implement destroy method for ShaderSystem - this.destroyed = true; - } -} diff --git a/packages/core/src/renderers/systems/shader/shader/compileProgram.js b/packages/core/src/renderers/systems/shader/shader/compileProgram.js deleted file mode 100644 index 8c6ccb9..0000000 --- a/packages/core/src/renderers/systems/shader/shader/compileProgram.js +++ /dev/null @@ -1,78 +0,0 @@ -/** - * @method compileProgram - * @memberof PIXI.glCore.shader - * @param gl {WebGLRenderingContext} The current WebGL context {WebGLProgram} - * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. - * @param fragmentSrc {string|string[]} The fragment shader source as an array of strings. - * @param attributeLocations {Object} An attribute location map that lets you manually set the attribute locations - * @return {WebGLProgram} the shader program - */ -export default function compileProgram(gl, vertexSrc, fragmentSrc, attributeLocations) -{ - const glVertShader = compileShader(gl, gl.VERTEX_SHADER, vertexSrc); - const glFragShader = compileShader(gl, gl.FRAGMENT_SHADER, fragmentSrc); - - let program = gl.createProgram(); - - gl.attachShader(program, glVertShader); - gl.attachShader(program, glFragShader); - - // optionally, set the attributes manually for the program rather than letting WebGL decide.. - if (attributeLocations) - { - for (const i in attributeLocations) - { - gl.bindAttribLocation(program, attributeLocations[i], i); - } - } - - gl.linkProgram(program); - - // if linking fails, then log and cleanup - if (!gl.getProgramParameter(program, gl.LINK_STATUS)) - { - console.error('Pixi.js Error: Could not initialize shader.'); - console.error('gl.VALIDATE_STATUS', gl.getProgramParameter(program, gl.VALIDATE_STATUS)); - console.error('gl.getError()', gl.getError()); - - // if there is a program info log, log it - if (gl.getProgramInfoLog(program) !== '') - { - console.warn('Pixi.js Warning: gl.getProgramInfoLog()', gl.getProgramInfoLog(program)); - } - - gl.deleteProgram(program); - program = null; - } - - // clean up some shaders - gl.deleteShader(glVertShader); - gl.deleteShader(glFragShader); - - return program; -} - -/** - * @private - * @param gl {WebGLRenderingContext} The current WebGL context {WebGLProgram} - * @param type {Number} the type, can be either VERTEX_SHADER or FRAGMENT_SHADER - * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. - * @return {WebGLShader} the shader - */ -function compileShader(gl, type, src) -{ - const shader = gl.createShader(type); - - gl.shaderSource(shader, src); - gl.compileShader(shader); - - if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) - { - console.warn(src); - console.error(gl.getShaderInfoLog(shader)); - - return null; - } - - return shader; -} diff --git a/packages/core/src/renderers/systems/shader/shader/defaultValue.js b/packages/core/src/renderers/systems/shader/shader/defaultValue.js deleted file mode 100644 index 97290bd..0000000 --- a/packages/core/src/renderers/systems/shader/shader/defaultValue.js +++ /dev/null @@ -1,79 +0,0 @@ -/** - * @method defaultValue - * @memberof PIXI.glCore.shader - * @param type {String} Type of value - * @param size {Number} - */ -export default function defaultValue(type, size) -{ - switch (type) - { - case 'float': - return 0; - - case 'vec2': - return new Float32Array(2 * size); - - case 'vec3': - return new Float32Array(3 * size); - - case 'vec4': - return new Float32Array(4 * size); - - case 'int': - case 'sampler2D': - case 'sampler2DArray': - return 0; - - case 'ivec2': - return new Int32Array(2 * size); - - case 'ivec3': - return new Int32Array(3 * size); - - case 'ivec4': - return new Int32Array(4 * size); - - case 'bool': - return false; - - case 'bvec2': - - return booleanArray(2 * size); - - case 'bvec3': - return booleanArray(3 * size); - - case 'bvec4': - return booleanArray(4 * size); - - case 'mat2': - return new Float32Array([1, 0, - 0, 1]); - - case 'mat3': - return new Float32Array([1, 0, 0, - 0, 1, 0, - 0, 0, 1]); - - case 'mat4': - return new Float32Array([1, 0, 0, 0, - 0, 1, 0, 0, - 0, 0, 1, 0, - 0, 0, 0, 1]); - } - - return null; -} - -function booleanArray(size) -{ - const array = new Array(size); - - for (let i = 0; i < array.length; i++) - { - array[i] = false; - } - - return array; -} diff --git a/packages/core/src/renderers/systems/shader/shader/index.js b/packages/core/src/renderers/systems/shader/shader/index.js deleted file mode 100644 index ba7a317..0000000 --- a/packages/core/src/renderers/systems/shader/shader/index.js +++ /dev/null @@ -1,8 +0,0 @@ -/** - * @namespace PIXI.glCore.shader - */ -export { default as compileProgram } from './compileProgram'; -export { default as defaultValue } from './defaultValue'; -export { default as setPrecision } from './setPrecision'; -export { default as mapSize } from './mapSize'; -export { default as mapType } from './mapType'; diff --git a/packages/core/src/renderers/systems/shader/shader/mapSize.js b/packages/core/src/renderers/systems/shader/shader/mapSize.js deleted file mode 100644 index b681554..0000000 --- a/packages/core/src/renderers/systems/shader/shader/mapSize.js +++ /dev/null @@ -1,33 +0,0 @@ -const GLSL_TO_SIZE = { - float: 1, - vec2: 2, - vec3: 3, - vec4: 4, - - int: 1, - ivec2: 2, - ivec3: 3, - ivec4: 4, - - bool: 1, - bvec2: 2, - bvec3: 3, - bvec4: 4, - - mat2: 4, - mat3: 9, - mat4: 16, - - sampler2D: 1, -}; - -/** - * @method mapSize - * @memberof PIXI.glCore.shader - * @param type {String} - * @return {Number} - */ -export default function mapSize(type) -{ - return GLSL_TO_SIZE[type]; -} diff --git a/packages/core/src/renderers/systems/shader/shader/mapType.js b/packages/core/src/renderers/systems/shader/shader/mapType.js deleted file mode 100644 index d288b7c..0000000 --- a/packages/core/src/renderers/systems/shader/shader/mapType.js +++ /dev/null @@ -1,45 +0,0 @@ -let GL_TABLE = null; - -const GL_TO_GLSL_TYPES = { - FLOAT: 'float', - FLOAT_VEC2: 'vec2', - FLOAT_VEC3: 'vec3', - FLOAT_VEC4: 'vec4', - - INT: 'int', - INT_VEC2: 'ivec2', - INT_VEC3: 'ivec3', - INT_VEC4: 'ivec4', - - BOOL: 'bool', - BOOL_VEC2: 'bvec2', - BOOL_VEC3: 'bvec3', - BOOL_VEC4: 'bvec4', - - FLOAT_MAT2: 'mat2', - FLOAT_MAT3: 'mat3', - FLOAT_MAT4: 'mat4', - - SAMPLER_2D: 'sampler2D', - SAMPLER_CUBE: 'samplerCube', - SAMPLER_2D_ARRAY: 'sampler2DArray', -}; - -export default function mapType(gl, type) -{ - if (!GL_TABLE) - { - const typeNames = Object.keys(GL_TO_GLSL_TYPES); - - GL_TABLE = {}; - - for (let i = 0; i < typeNames.length; ++i) - { - const tn = typeNames[i]; - - GL_TABLE[gl[tn]] = GL_TO_GLSL_TYPES[tn]; - } - } - - return GL_TABLE[type]; -} diff --git a/packages/core/src/renderers/systems/shader/shader/setPrecision.js b/packages/core/src/renderers/systems/shader/shader/setPrecision.js deleted file mode 100644 index e2c1f34..0000000 --- a/packages/core/src/renderers/systems/shader/shader/setPrecision.js +++ /dev/null @@ -1,18 +0,0 @@ -/** - * Sets the float precision on the shader. If the precision is already present this function will do nothing - * @param {string} src the shader source - * @param {string} precision The float precision of the shader. Options are 'lowp', 'mediump' or 'highp'. - * - * @return {string} modified shader source - */ -export default function setPrecision(src, precision) -{ - src = src.trim(); - - if (src.substring(0, 9) !== 'precision' && src.substring(0, 1) !== '#') - { - return `precision ${precision} float;\n${src}`; - } - - return src; -} diff --git a/packages/core/src/renderers/systems/textures/GLTexture.js b/packages/core/src/renderers/systems/textures/GLTexture.js deleted file mode 100644 index c3fb8dc..0000000 --- a/packages/core/src/renderers/systems/textures/GLTexture.js +++ /dev/null @@ -1,33 +0,0 @@ -export default class GLTexture -{ - constructor(texture) - { - /** - * The WebGL texture - * - * @member {WebGLTexture} - */ - this.texture = texture; - - this.width = -1; - this.height = -1; - - /** - * Texture contents dirty flag - * @member {number} - */ - this.dirtyId = -1; - - /** - * Texture style dirty flag - * @type {number} - */ - this.dirtyStyleId = -1; - - /** - * Whether mip levels has to be generated - * @type {boolean} - */ - this.mipmap = false; - } -} diff --git a/packages/core/src/renderers/systems/textures/TextureGCSystem.js b/packages/core/src/renderers/systems/textures/TextureGCSystem.js deleted file mode 100644 index b089938..0000000 --- a/packages/core/src/renderers/systems/textures/TextureGCSystem.js +++ /dev/null @@ -1,110 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; -import { GC_MODES } from '@pixi/constants'; -import { settings } from '@pixi/settings'; - -/** - * TextureGarbageCollector. This class manages the GPU and ensures that it does not get clogged - * up with textures that are no longer being used. - * - * @class - * @memberof PIXI - */ -export default class TextureGCSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this.count = 0; - this.checkCount = 0; - this.maxIdle = settings.GC_MAX_IDLE; - this.checkCountMax = settings.GC_MAX_CHECK_COUNT; - this.mode = settings.GC_MODE; - } - - /** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ - postrender() - { - this.count++; - - if (this.mode === GC_MODES.MANUAL) - { - return; - } - - this.checkCount++; - - if (this.checkCount > this.checkCountMax) - { - this.checkCount = 0; - - this.run(); - } - } - - /** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ - run() - { - const tm = this.renderer.texture; - const managedTextures = tm.managedTextures; - let wasRemoved = false; - - for (let i = 0; i < managedTextures.length; i++) - { - const texture = managedTextures[i]; - - // only supports non generated textures at the moment! - if (!texture.frameBuffer && this.count - texture.touched > this.maxIdle) - { - tm.destroyTexture(texture, true); - managedTextures[i] = null; - wasRemoved = true; - } - } - - if (wasRemoved) - { - let j = 0; - - for (let i = 0; i < managedTextures.length; i++) - { - if (managedTextures[i] !== null) - { - managedTextures[j++] = managedTextures[i]; - } - } - - managedTextures.length = j; - } - } - - /** - * Removes all the textures within the specified displayObject and its children from the GPU - * - * @param {PIXI.DisplayObject} displayObject - the displayObject to remove the textures from. - */ - unload(displayObject) - { - const tm = this.renderer.textureSystem; - - // only destroy non generated textures - if (displayObject._texture && displayObject._texture._glRenderTargets) - { - tm.destroyTexture(displayObject._texture); - } - - for (let i = displayObject.children.length - 1; i >= 0; i--) - { - this.unload(displayObject.children[i]); - } - } -} diff --git a/packages/core/src/renderers/systems/textures/TextureSystem.js b/packages/core/src/renderers/systems/textures/TextureSystem.js deleted file mode 100644 index ccc5de9..0000000 --- a/packages/core/src/renderers/systems/textures/TextureSystem.js +++ /dev/null @@ -1,281 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; -import GLTexture from './GLTexture'; -import { removeItems } from '@pixi/utils'; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class TextureSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - // TODO set to max textures... - this.boundTextures = [ - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - ]; - - this.currentLocation = -1; - - this.managedTextures = []; - } - - /** - * Sets up the renderer context and necessary buffers. - * - * @private - */ - contextChange() - { - const gl = this.gl = this.renderer.gl; - - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - - // TODO move this.. to a nice make empty textures class.. - this.emptyTextures = {}; - - const emptyTexture2D = new GLTexture(gl.createTexture()); - - gl.bindTexture(gl.TEXTURE_2D, emptyTexture2D.texture); - gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array(4)); - - this.emptyTextures[gl.TEXTURE_2D] = emptyTexture2D; - this.emptyTextures[gl.TEXTURE_CUBE_MAP] = new GLTexture(gl.createTexture()); - - gl.bindTexture(gl.TEXTURE_CUBE_MAP, this.emptyTextures[gl.TEXTURE_CUBE_MAP].texture); - - let i; - - for (i = 0; i < 6; i++) - { - gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, null); - } - - gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_MAG_FILTER, gl.LINEAR); - gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_MIN_FILTER, gl.LINEAR); - - for (i = 0; i < this.boundTextures.length; i++) - { - this.bind(null, i); - } - } - - bind(texture, location) - { - const gl = this.gl; - - location = location || 0; - - if (this.currentLocation !== location) - { - this.currentLocation = location; - gl.activeTexture(gl.TEXTURE0 + location); - } - - if (texture) - { - texture = texture.baseTexture || texture; - - if (texture.valid) - { - texture.touched = this.renderer.textureGC.count; - - const glTexture = texture._glTextures[this.CONTEXT_UID] || this.initTexture(texture); - - gl.bindTexture(texture.target, glTexture.texture); - - if (glTexture.dirtyId !== texture.dirtyId) - { - this.updateTexture(texture); - } - - this.boundTextures[location] = texture; - } - } - else - { - gl.bindTexture(gl.TEXTURE_2D, this.emptyTextures[gl.TEXTURE_2D].texture); - this.boundTextures[location] = null; - } - } - - unbind(texture) - { - const gl = this.gl; - - for (let i = 0; i < this.boundTextures.length; i++) - { - if (this.boundTextures[i] === texture) - { - if (this.currentLocation !== i) - { - gl.activeTexture(gl.TEXTURE0 + i); - this.currentLocation = i; - } - - gl.bindTexture(gl.TEXTURE_2D, this.emptyTextures[texture.target].texture); - this.boundTextures[i] = null; - } - } - } - - initTexture(texture) - { - const glTexture = new GLTexture(this.gl.createTexture()); - - // guarentee an update.. - glTexture.dirtyId = -1; - - texture._glTextures[this.CONTEXT_UID] = glTexture; - - this.managedTextures.push(texture); - texture.on('dispose', this.destroyTexture, this); - - return glTexture; - } - - updateTexture(texture) - { - const glTexture = texture._glTextures[this.CONTEXT_UID]; - const renderer = this.renderer; - - if (texture.resource && texture.resource.upload(renderer, texture, glTexture)) - { - // texture is uploaded, dont do anything! - } - else - { - // default, renderTexture-like logic - const width = texture.realWidth; - const height = texture.realHeight; - const gl = renderer.gl; - - if (glTexture.width !== width - || glTexture.height !== height - || glTexture.dirtyId < 0) - { - glTexture.width = width; - glTexture.height = height; - - gl.texImage2D(texture.target, 0, - texture.format, - width, - height, - 0, - texture.format, - texture.type, - null); - } - } - - // lets only update what changes.. - if (texture.dirtyStyleId !== glTexture.dirtyStyleId) - { - this.updateTextureStyle(texture); - } - glTexture.dirtyId = texture.dirtyId; - } - - /** - * Deletes the texture from WebGL - * - * @param {PIXI.BaseTexture|PIXI.Texture} texture - the texture to destroy - * @param {boolean} [skipRemove=false] - Whether to skip removing the texture from the TextureManager. - */ - destroyTexture(texture, skipRemove) - { - const gl = this.gl; - - texture = texture.baseTexture || texture; - - if (texture._glTextures[this.renderer.CONTEXT_UID]) - { - this.unbind(texture); - - gl.deleteTexture(texture._glTextures[this.renderer.CONTEXT_UID].texture); - texture.off('dispose', this.destroyTexture, this); - - delete texture._glTextures[this.renderer.CONTEXT_UID]; - - if (!skipRemove) - { - const i = this.managedTextures.indexOf(texture); - - if (i !== -1) - { - removeItems(this.managedTextures, i, 1); - } - } - } - } - - updateTextureStyle(texture) - { - const glTexture = texture._glTextures[this.CONTEXT_UID]; - - glTexture.mipmap = texture.mipmap && texture.isPowerOfTwo; - if (!glTexture) - { - return; - } - - if (texture.resource && texture.resource.style(this.renderer, texture, glTexture)) - { - // style is set, dont do anything! - } - else - { - this.setStyle(texture, glTexture); - } - - glTexture.dirtyStyleId = texture.dirtyStyleId; - } - - setStyle(texture, glTexture) - { - const gl = this.gl; - - if (glTexture.mipmap) - { - gl.generateMipmap(texture.target); - } - - gl.texParameteri(texture.target, gl.TEXTURE_WRAP_S, texture.wrapMode); - gl.texParameteri(texture.target, gl.TEXTURE_WRAP_T, texture.wrapMode); - - if (glTexture.mipmap) - { - /* eslint-disable max-len */ - gl.texParameteri(texture.target, gl.TEXTURE_MIN_FILTER, texture.scaleMode ? gl.LINEAR_MIPMAP_LINEAR : gl.NEAREST_MIPMAP_NEAREST); - /* eslint-disable max-len */ - } - else - { - gl.texParameteri(texture.target, gl.TEXTURE_MIN_FILTER, texture.scaleMode ? gl.LINEAR : gl.NEAREST); - } - - gl.texParameteri(texture.target, gl.TEXTURE_MAG_FILTER, texture.scaleMode ? gl.LINEAR : gl.NEAREST); - } -} diff --git a/packages/core/src/renderers/utils/ObjectRenderer.js b/packages/core/src/renderers/utils/ObjectRenderer.js deleted file mode 100644 index f67a273..0000000 --- a/packages/core/src/renderers/utils/ObjectRenderer.js +++ /dev/null @@ -1,48 +0,0 @@ -import WebGLSystem from '../systems/WebGLSystem'; - -/** - * Base for a common object renderer that can be used as a system renderer plugin. - * - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class ObjectRenderer extends WebGLSystem -{ - /** - * Starts the renderer and sets the shader - * - */ - start() - { - // set the shader.. - } - - /** - * Stops the renderer - * - */ - stop() - { - this.flush(); - } - - /** - * Stub method for rendering content and emptying the current batch. - * - */ - flush() - { - // flush! - } - - /** - * Renders an object - * - * @param {PIXI.DisplayObject} object - The object to render. - */ - render(object) // eslint-disable-line no-unused-vars - { - // render the object - } -} diff --git a/packages/core/src/renderers/utils/Quad.js b/packages/core/src/renderers/utils/Quad.js deleted file mode 100644 index 5c1517b..0000000 --- a/packages/core/src/renderers/utils/Quad.js +++ /dev/null @@ -1,26 +0,0 @@ -import Geometry from '../../geometry/Geometry'; - -/** - * Helper class to create a quad - * - * @class - * @memberof PIXI - */ -export default class Quad extends Geometry -{ - /** - * just a quad - */ - constructor() - { - super(); - - this.addAttribute('aVertexPosition', [ - 0, 0, - 1, 0, - 1, 1, - 0, 1, - ]) - .addIndex([0, 1, 3, 2]); - } -} diff --git a/packages/core/src/AbstractRenderer.js b/packages/core/src/AbstractRenderer.js new file mode 100644 index 0000000..64b2eb1 --- /dev/null +++ b/packages/core/src/AbstractRenderer.js @@ -0,0 +1,353 @@ +import { hex2string, hex2rgb } from '@pixi/utils'; +import { Matrix, Rectangle } from '@pixi/math'; +import { RENDERER_TYPE } from '@pixi/constants'; +import { settings } from '@pixi/settings'; +import { Container } from '@pixi/display'; +import RenderTexture from './renderTexture/RenderTexture'; +import EventEmitter from 'eventemitter3'; + +const tempMatrix = new Matrix(); + +/** + * 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 EventEmitter + * @memberof PIXI + */ +export default class AbstractRenderer extends EventEmitter +{ + // eslint-disable-next-line valid-jsdoc + /** + * @param {string} system - The name of the system this renderer is for. + * @param {object} [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.transparent=false] - If the render view is transparent, default false + * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false + * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. The + * resolution of the renderer retina would be 2. + * @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 {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when rendering, + * stopping pixel interpolation. + */ + constructor(system, options, arg2, arg3) + { + super(); + + // Support for constructor(system, screenWidth, screenHeight, options) + if (typeof options === 'number') + { + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + }, arg3); + } + + // 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 = RENDERER_TYPE.UNKNOWN; + + /** + * Measurements of the screen. (0, 0, screenWidth, screenHeight) + * + * Its safe to use as filterArea or hitArea for 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 1 + */ + this.resolution = options.resolution || settings.RESOLUTION; + + /** + * Whether the render view is transparent + * + * @member {boolean} + */ + this.transparent = options.transparent; + + /** + * Whether css dimensions of canvas view should be resized to screen dimensions automatically + * + * @member {boolean} + */ + this.autoResize = options.autoResize || false; + + /** + * Tracks the blend modes useful for this renderer. + * + * @member {object} + */ + this.blendModes = null; + + /** + * 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; + + /** + * If true PixiJS will Math.floor() x/y values when rendering, stopping pixel interpolation. + * Handy for crisp pixel art and speed on legacy devices. + * + * @member {boolean} + */ + this.roundPixels = options.roundPixels; + + /** + * The background color as a number. + * + * @member {number} + * @private + */ + this._backgroundColor = 0x000000; + + /** + * The background color as an [R, G, B] array. + * + * @member {number[]} + * @private + */ + this._backgroundColorRgba = [0, 0, 0, 0]; + + /** + * The background color as a string. + * + * @member {string} + * @private + */ + this._backgroundColorString = '#000000'; + + this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter + + /** + * This temporary display object used as the parent of the currently being rendered item + * + * @member {PIXI.DisplayObject} + * @private + */ + this._tempDisplayObjectParent = new Container(); + + /** + * The last root object that the renderer tried to render. + * + * @member {PIXI.DisplayObject} + * @private + */ + this._lastObjectRendered = this._tempDisplayObjectParent; + + /** + * Collection of plugins. + * @readonly + * @member {object} + */ + this.plugins = {}; + } + + /** + * Initialize the plugins. + * + * @protected + * @param {object} staticMap - The dictionary of staticly saved plugins. + */ + initPlugins(staticMap) + { + 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() + { + return this.view.width; + } + + /** + * Same as view.height, actual number of pixels in the canvas by vertical + * + * @member {number} + * @readonly + * @default 600 + */ + get height() + { + return this.view.height; + } + + /** + * Resizes the screen and canvas to the specified width and height + * Canvas dimensions are multiplied by resolution + * + * @param {number} screenWidth - the new width of the screen + * @param {number} screenHeight - the new height of the screen + */ + resize(screenWidth, screenHeight) + { + this.screen.width = screenWidth; + this.screen.height = screenHeight; + + this.view.width = screenWidth * this.resolution; + this.view.height = screenHeight * this.resolution; + + if (this.autoResize) + { + this.view.style.width = `${screenWidth}px`; + this.view.style.height = `${screenHeight}px`; + } + } + + /** + * 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 {PIXI.DisplayObject} displayObject - The displayObject the object will be generated from + * @param {number} scaleMode - Should be one of the scaleMode consts + * @param {number} resolution - The resolution / device pixel ratio of the texture being generated + * @param {PIXI.Rectangle} [region] - The region of the displayObject, that shall be rendered, + * if no region is specified, defaults to the local bounds of the displayObject. + * @return {PIXI.Texture} a texture of the graphics object + */ + generateTexture(displayObject, scaleMode, resolution, region) + { + region = region || displayObject.getLocalBounds(); + + // 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(region.width | 0, region.height | 0, scaleMode, resolution); + + tempMatrix.tx = -region.x; + tempMatrix.ty = -region.y; + + this.render(displayObject, renderTexture, false, tempMatrix, true); + + return renderTexture; + } + + /** + * Removes everything from the renderer and optionally removes the Canvas DOM element. + * + * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. + */ + destroy(removeView) + { + 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); + } + + this.plugins = null; + + this.type = RENDERER_TYPE.UNKNOWN; + + this.view = null; + + this.screen = null; + + this.resolution = 0; + + this.transparent = false; + + this.autoResize = false; + + this.blendModes = null; + + this.options = null; + + this.preserveDrawingBuffer = false; + this.clearBeforeRender = false; + + this.roundPixels = false; + + this._backgroundColor = 0; + this._backgroundColorRgba = null; + this._backgroundColorString = null; + + this._tempDisplayObjectParent = null; + this._lastObjectRendered = null; + } + + /** + * The background color to fill if not transparent + * + * @member {number} + */ + get backgroundColor() + { + return this._backgroundColor; + } + + set backgroundColor(value) // eslint-disable-line require-jsdoc + { + this._backgroundColor = value; + this._backgroundColorString = hex2string(value); + hex2rgb(value, this._backgroundColorRgba); + } +} diff --git a/packages/core/src/Renderer.js b/packages/core/src/Renderer.js new file mode 100644 index 0000000..2227e78 --- /dev/null +++ b/packages/core/src/Renderer.js @@ -0,0 +1,338 @@ +import AbstractRenderer from './AbstractRenderer'; +import { sayHello } from '@pixi/utils'; +import MaskSystem from './mask/MaskSystem'; +import StencilSystem from './mask/StencilSystem'; +import FilterSystem from './filters/FilterSystem'; +import FramebufferSystem from './framebuffer/FramebufferSystem'; +import RenderTextureSystem from './renderTexture/RenderTextureSystem'; +import TextureSystem from './textures/TextureSystem'; +import ProjectionSystem from './projection/ProjectionSystem'; +import StateSystem from './state/StateSystem'; +import GeometrySystem from './geometry/GeometrySystem'; +import ShaderSystem from './shader/ShaderSystem'; +import ContextSystem from './context/ContextSystem'; +import BatchSystem from './batch/BatchSystem'; +import TextureGCSystem from './textures/TextureGCSystem'; +import { RENDERER_TYPE } from '@pixi/constants'; +import UniformGroup from './shader/UniformGroup'; +import { Matrix } from '@pixi/math'; +import Runner from 'mini-runner'; + +/** + * 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 Render works by automatically managing webGLBatchs. + * 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 :) + * + * @class + * @memberof PIXI + * @extends PIXI.AbstractRenderer + */ +export default class Renderer extends AbstractRenderer +{ + // eslint-disable-next-line valid-jsdoc + /** + * + * @param {object} [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.transparent=false] - If the render view is transparent, default false + * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false + * @param {boolean} [options.antialias=false] - sets antialias. If not available natively then FXAA + * antialiasing is used + * @param {boolean} [options.forceFXAA=false] - forces FXAA antialiasing to be used over native. + * FXAA is faster, but may not always look as great + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. + * The resolution of the renderer retina would be 2. + * @param {boolean} [options.clearBeforeRender=true] - This sets if the renderer will clear + * the canvas or not before the new render pass. If you wish to set this to false, you *must* set + * preserveDrawingBuffer to `true`. + * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, + * enable this if you need to call toDataUrl on the webgl context. + * @param {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when + * rendering, stopping pixel interpolation. + * @param {number} [options.backgroundColor=0x000000] - The background color of the rendered area + * (shown if not transparent). + * @param {boolean} [options.legacy=false] - If true PixiJS will aim to ensure compatibility + * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. + * @param {string} [options.powerPreference] - Parameter passed to webgl context, set to "high-performance" + * for devices with dual graphics card + */ + constructor(options, arg2, arg3) + { + super('WebGL', options, arg2, arg3); + + /** + * The type of this renderer as a standardised const + * + * @member {number} + * @see PIXI.RENDERER_TYPE + */ + this.type = RENDERER_TYPE.WEBGL; + + // this will be set by the contextSystem (this.context) + this.gl = null; + this.CONTEXT_UID = 0; + this.legacy = !!options.legacy; + + // TODO legacy! + + // runners! + this.runners = { + destroy: new Runner('destroy'), + contextChange: new Runner('contextChange', 1), + reset: new Runner('reset'), + update: new Runner('update'), + postrender: new Runner('postrender'), + prerender: new Runner('prerender'), + resize: new Runner('resize', 2), + }; + + 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(GeometrySystem, 'geometry') + .addSystem(FramebufferSystem, 'framebuffer') + .addSystem(StencilSystem, 'stencil') + .addSystem(ProjectionSystem, 'projection') + .addSystem(TextureGCSystem, 'textureGC') + .addSystem(FilterSystem, 'filter') + .addSystem(RenderTextureSystem, 'renderTexture') + .addSystem(BatchSystem, 'batch'); + + this.initPlugins(Renderer.__plugins); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ + if (options.context) + { + this.context.initFromContext(options.context); + } + else + { + this.context.initFromOptions({ + alpha: this.transparent, + antialias: options.antialias, + premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', + stencil: true, + 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); + } + + /** + * Add a new system to the renderer. + * @param {class} ClassRef - Class reference + * @param {string} [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. + * @return {PIXI.Renderer} Return instance of renderer + */ + addSystem(ClassRef, name) + { + if (!name) + { + name = ClassRef.name; + } + + // TODO - read name from class.name.. + + /* + if(name.includes('System')) + { + name = name.replace('System', ''); + name = name.charAt(0).toLowerCase() + name.slice(1); + } + */ + + const system = new ClassRef(this); + + if (this[name]) + { + throw new Error(`Whoops! ${name} is already a manger`); + } + + this[name] = system; + + for (const i in this.runners) + { + this.runners[i].add(system); + } + + return this; + + /** + * 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. + */ + } + + /** + * Renders the object to its webGL view + * + * @param {PIXI.DisplayObject} displayObject - the object to be rendered + * @param {PIXI.RenderTexture} renderTexture - The render texture to render to. + * @param {boolean} [clear] - Should the canvas be cleared before the new render + * @param {PIXI.Transform} [transform] - A transform to apply to the render texture before rendering. + * @param {boolean} [skipUpdateTransform] - Should we skip the update transform pass? + */ + render(displayObject, renderTexture, clear, transform, skipUpdateTransform) + { + // can be handy to know! + this.renderingToScreen = !renderTexture; + + this.runners.prerender.run(); + this.emit('prerender'); + + // 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.parent; + + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = 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.run(); + + this.emit('postrender'); + } + + /** + * Resizes the webGL view to the specified width and height. + * + * @param {number} screenWidth - the new width of the screen + * @param {number} screenHeight - the new height of the screen + */ + resize(screenWidth, screenHeight) + { + AbstractRenderer.prototype.resize.call(this, screenWidth, screenHeight); + + this.runners.resize.run(screenWidth, screenHeight); + } + + /** + * Resets the WebGL state so you can render things however you fancy! + * + * @return {PIXI.Renderer} Returns itself. + */ + reset() + { + this.runners.reset.run(); + + return this; + } + + clear() + { + this.framebuffer.bind(); + this.framebuffer.clear(); + } + + /** + * Removes everything from the renderer (event listeners, spritebatch, etc...) + * + * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. + * See: https://github.com/pixijs/pixi.js/issues/2233 + */ + destroy(removeView) + { + this.runners.destroy.run(); + + // call base destroy + super.destroy(removeView); + + // TODO nullify all the managers.. + this.gl = null; + } + + /** + * 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 + * builds and excluding plugins. + * @name PIXI.Renderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. + */ + + /** + * Adds a plugin to the renderer. + * + * @method + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + static registerPlugin(pluginName, ctor) + { + Renderer.__plugins = Renderer.__plugins || {}; + Renderer.__plugins[pluginName] = ctor; + } +} diff --git a/packages/core/src/System.js b/packages/core/src/System.js new file mode 100644 index 0000000..05a10c8 --- /dev/null +++ b/packages/core/src/System.js @@ -0,0 +1,42 @@ +/** + * System is a base class used for extending systems used by the {@link PIXI.Renderer} + * @see PIXI.Renderer#addSystem + * @class + * @memberof PIXI + */ +export default class System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + /** + * The renderer this manager works for. + * + * @member {PIXI.Renderer} + */ + this.renderer = renderer; + + this.renderer.runners.contextChange.add(this); + } + + /** + * Generic method called when there is a WebGL context change. + * + */ + contextChange() + { + // do some codes init! + } + + /** + * Generic destroy methods to be overridden by the subclass + * + */ + destroy() + { + this.renderer.runners.contextChange.remove(this); + this.renderer = null; + } +} diff --git a/packages/core/src/batch/BatchSystem.js b/packages/core/src/batch/BatchSystem.js new file mode 100644 index 0000000..b04c249 --- /dev/null +++ b/packages/core/src/batch/BatchSystem.js @@ -0,0 +1,68 @@ +import System from '../System'; +import ObjectRenderer from './ObjectRenderer'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class BatchSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + super(renderer); + + /** + * An empty renderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.emptyRenderer = new ObjectRenderer(renderer); + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.currentRenderer = this.emptyRenderer; + } + + /** + * Changes the current renderer to the one given in parameter + * + * @param {PIXI.ObjectRenderer} objectRenderer - The object renderer to use. + */ + setObjectRenderer(objectRenderer) + { + if (this.currentRenderer === objectRenderer) + { + return; + } + + this.currentRenderer.stop(); + this.currentRenderer = objectRenderer; + + this.renderer.state.setState(objectRenderer.state); + + this.currentRenderer.start(); + } + + /** + * This should be called if you wish to do some custom rendering + * It will basically render anything that may be batched up such as sprites + * + */ + flush() + { + this.setObjectRenderer(this.emptyRenderer); + } + + reset() + { + this.setObjectRenderer(this.emptyRenderer); + } +} diff --git a/packages/core/src/batch/ObjectRenderer.js b/packages/core/src/batch/ObjectRenderer.js new file mode 100644 index 0000000..0c851a9 --- /dev/null +++ b/packages/core/src/batch/ObjectRenderer.js @@ -0,0 +1,48 @@ +import System from '../System'; + +/** + * Base for a common object renderer that can be used as a system renderer plugin. + * + * @class + * @extends PIXI.System + * @memberof PIXI + */ +export default class ObjectRenderer extends System +{ + /** + * Starts the renderer and sets the shader + * + */ + start() + { + // set the shader.. + } + + /** + * Stops the renderer + * + */ + stop() + { + this.flush(); + } + + /** + * Stub method for rendering content and emptying the current batch. + * + */ + flush() + { + // flush! + } + + /** + * Renders an object + * + * @param {PIXI.DisplayObject} object - The object to render. + */ + render(object) // eslint-disable-line no-unused-vars + { + // render the object + } +} diff --git a/packages/core/src/context/ContextSystem.js b/packages/core/src/context/ContextSystem.js new file mode 100644 index 0000000..301aab4 --- /dev/null +++ b/packages/core/src/context/ContextSystem.js @@ -0,0 +1,186 @@ +import System from '../System'; +import { settings } from '@pixi/settings'; + +let CONTEXT_UID = 0; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class ContextSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.webGLVersion = 1; + + this.handleContextLost = this.handleContextLost.bind(this); + this.handleContextRestored = this.handleContextRestored.bind(this); + + this.extensions = {}; + + renderer.view.addEventListener('webglcontextlost', this.handleContextLost, false); + renderer.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + } + + get isLost() + { + return (!this.gl || this.gl.isContextLost()); + } + + contextChange(gl) + { + this.gl = gl; + + // restore a context if it was previously lost + if (gl.isContextLost() && gl.getExtension('WEBGL_lose_context')) + { + gl.getExtension('WEBGL_lose_context').restoreContext(); + } + } + + initFromContext(gl) + { + this.gl = gl; + this.validateContext(gl); + this.renderer.gl = gl; + this.renderer.CONTEXT_UID = CONTEXT_UID++; + this.renderer.runners.contextChange.run(gl); + } + + initFromOptions(options) + { + const gl = this.createContext(this.renderer.view, options); + + this.initFromContext(gl); + } + + /** + * Helper class to create a webGL Context + * + * @class + * @memberof PIXI.glCore + * @param canvas {HTMLCanvasElement} the canvas element that we will get the context from + * @param options {Object} An options object that gets passed in to the canvas element containing the context attributes, + * see https://developer.mozilla.org/en/docs/Web/API/HTMLCanvasElement/getContext for the options available + * @return {WebGLRenderingContext} the WebGL context + */ + createContext(canvas, options) + { + let gl; + + if (settings.PREFER_WEBGL_2) + { + gl = canvas.getContext('webgl2', options); + } + + if (gl) + { + this.webGLVersion = 2; + } + else + { + this.webGLVersion = 1; + + gl = canvas.getContext('webgl', options) + || canvas.getContext('experimental-webgl', options); + + if (!gl) + { + // fail, not able to get a context + throw new Error('This browser does not support webGL. Try using the canvas renderer'); + } + } + + this.gl = gl; + + this.getExtensions(); + + return gl; + } + + getExtensions() + { + // time to set up default etensions that pixi uses.. + const gl = this.gl; + const extensions = this.extensions; + + if (this.webGLVersion === 1) + { + extensions.drawBuffers = gl.getExtension('WEBGL_draw_buffers'); + extensions.depthTexture = gl.getExtension('WEBKIT_WEBGL_depth_texture'); + extensions.floatTexture = gl.getExtension('OES_texture_float'); + extensions.loseContext = gl.getExtension('WEBGL_lose_context'); + + extensions.vertexArrayObject = gl.getExtension('OES_vertex_array_object') + || gl.getExtension('MOZ_OES_vertex_array_object') + || gl.getExtension('WEBKIT_OES_vertex_array_object'); + } + + // we don't use any specific WebGL 2 ones yet! + } + + /** + * Handles a lost webgl context + * + * @private + * @param {WebGLContextEvent} event - The context lost event. + */ + handleContextLost(event) + { + event.preventDefault(); + } + + /** + * Handles a restored webgl context + * + * @private + */ + handleContextRestored() + { + this.renderer.runners.contextChange.run(this.gl); + } + + destroy() + { + const view = this.renderer.view; + + // remove listeners + view.removeEventListener('webglcontextlost', this.handleContextLost); + view.removeEventListener('webglcontextrestored', this.handleContextRestored); + + this.gl.useProgram(null); + + if (this.extensions.loseContext) + { + this.extensions.loseContext.loseContext(); + } + } + + postrender() + { + this.gl.flush(); + } + + validateContext(gl) + { + const attributes = gl.getContextAttributes(); + + // this is going to be fairly simple for now.. but at least we have room to grow! + if (!attributes.stencil) + { + /* eslint-disable max-len */ + + /* eslint-disable no-console */ + console.warn('Provided WebGL context does not have a stencil buffer, masks may not render correctly'); + /* eslint-enable no-console */ + + /* eslint-enable max-len */ + } + } +} diff --git a/packages/core/src/filters/Filter.js b/packages/core/src/filters/Filter.js new file mode 100644 index 0000000..9d8cad8 --- /dev/null +++ b/packages/core/src/filters/Filter.js @@ -0,0 +1,140 @@ +import Shader from '../shader/Shader'; +import Program from '../shader/Program'; +import State from '../state/State'; +import { settings } from '@pixi/settings'; +// import extractUniformsFromSrc from './extractUniformsFromSrc'; +import defaultVertex from './defaultFilter.vert'; +import defaultFragment from './defaultFilter.frag'; + +// let math = require('../../math'); +/** + * @class + * @memberof PIXI + * @extends PIXI.Shader + */ +export default class Filter extends Shader +{ + /** + * @param {string} [vertexSrc] - The source of the vertex shader. + * @param {string} [fragmentSrc] - The source of the fragment shader. + * @param {object} [uniforms] - Custom uniforms to use to augment the built-in ones. + */ + constructor(vertexSrc, fragmentSrc, uniforms) + { + const program = Program.from(vertexSrc, fragmentSrc); + + super(program, uniforms); + + /** + * The padding of the filter. Some filters require extra space to breath such as a blur. + * Increasing this will add extra width and height to the bounds of the object that the + * filter is applied to. + * + * @member {number} + */ + this.padding = 0; + + /** + * The resolution of the filter. Setting this to be lower will lower the quality but + * increase the performance of the filter. + * + * @member {number} + */ + this.resolution = settings.RESOLUTION; + + /** + * If enabled is true the filter is applied, if false it will not. + * + * @member {boolean} + */ + this.enabled = true; + + /** + * If enabled, PixiJS will fit the filter area into boundaries for better performance. + * Switch it off if it does not work for specific shader. + * + * @member {boolean} + */ + this.autoFit = true; + + /** + * Legacy filters use position and uvs from attributes + * @member {boolean} + * @readonly + */ + this.legacy = !!this.program.attributeData.aTextureCoord; + + /** + * the webGL state the filter requires to render + * @member {PIXI.State} + */ + this.state = new State(); + } + + /** + * Applies the filter + * + * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + * @param {boolean} clear - Should the output be cleared before rendering to it + * @param {object} [currentState] - It's current state of filter. + * There are some useful properties in the currentState : + * target, filters, sourceFrame, destinationFrame, renderTarget, resolution + */ + apply(filterManager, input, output, clear, currentState, derp) // eslint-disable-line no-unused-vars + { + // do as you please! + + filterManager.applyFilter(this, input, output, clear, currentState, derp); + + // or just do a regular render.. + } + + /** + * Sets the blendmode of the filter + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL + */ + get blendMode() + { + return this.state.blendMode; + } + + set blendMode(value) // eslint-disable-line require-jsdoc + { + this.state.blendMode = value; + } + + /** + * The default vertex shader source + * + * @static + * @constant + */ + static get defaultVertexSrc() + { + return defaultVertex; + } + + /** + * The default fragment shader source + * + * @static + * @constant + */ + static get defaultFragmentSrc() + { + return defaultFragment; + } +} + +/** + * Used for caching shader IDs + * + * @static + * @private + */ +Filter.SOURCE_KEY_MAP = {}; + diff --git a/packages/core/src/filters/FilterSystem.js b/packages/core/src/filters/FilterSystem.js new file mode 100644 index 0000000..3174318 --- /dev/null +++ b/packages/core/src/filters/FilterSystem.js @@ -0,0 +1,431 @@ +import System from '../System'; + +import RenderTexture from '../renderTexture/RenderTexture'; +import Quad from '../utils/Quad'; +import QuadUv from '../utils/QuadUv'; +import { Rectangle } from '@pixi/math'; +import * as filterTransforms from './filterTransforms'; +import bitTwiddle from 'bit-twiddle'; +import UniformGroup from '../shader/UniformGroup'; +import { DRAW_MODES } from '@pixi/constants'; + +// +/** + * @ignore + * @class + */ +class FilterState +{ + /** + * + */ + constructor() + { + this.renderTexture = null; + this.sourceFrame = new Rectangle(); + this.destinationFrame = new Rectangle(); + this.filters = []; + this.target = null; + this.legacy = false; + this.resolution = 1; + } +} + +/** + * @class + * @memberof PIXI.systems + * @extends PIXI.System + */ +export default class FilterSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + /** + * stores a bunch of PO2 textures used for filtering + * @type {Object} + */ + this.texturePool = {}; + + /** + * a pool for storing filter states, save us creating new ones each tick + * @type {Array} + */ + this.statePool = []; + + /** + * A very simple geometry used when drawing a filter effect to the screen + * @type {Quad} + */ + this.quad = new Quad(); + + this.quadUv = new QuadUv(); + + /** + * Temporary rect for maths + * @type {PIXI.Rectangle} + */ + this.tempRect = new Rectangle(); + + this.activeState = {}; + + /** + * this uniform group is attached to filter uniforms when used + * @type {UniformGroup} + */ + this.globalUniforms = new UniformGroup({ + sourceFrame: this.tempRect, + destinationFrame: this.tempRect, + + // legacy variables + filterArea: new Float32Array(4), + filterClamp: new Float32Array(4), + }, true); + } + + /** + * Adds a new filter to the System. + * + * @param {PIXI.DisplayObject} target - The target of the filter to render. + * @param {PIXI.Filter[]} filters - The filters to apply. + */ + push(target, filters) + { + const renderer = this.renderer; + const filterStack = this.renderer.renderTexture.defaultFilterStack; + const state = this.statePool.pop() || new FilterState(); + + let resolution = filters[0].resolution; + let padding = filters[0].padding; + let autoFit = filters[0].autoFit; + let legacy = filters[0].legacy; + + for (let i = 1; i < filters.length; i++) + { + const filter = filters[i]; + + // lets use the lowest resolution.. + resolution = Math.min(resolution, filter.resolution); + // and the largest amount of padding! + padding = Math.max(padding, filter.padding); + // only auto fit if all filters are autofit + autoFit = autoFit || filter.autoFit; + + legacy = legacy || filter.legacy; + } + + filterStack.push(state); + + state.resolution = resolution; + + state.legacy = legacy; + + // round to whole number based on resolution + // TODO move that to the shader too? + state.sourceFrame = target.filterArea || target.getBounds(true); + + state.sourceFrame.pad(padding); + + if (autoFit) + { + state.sourceFrame.fit(this.renderer.renderTexture.destinationFrame); + } + + state.sourceFrame.round(resolution); + + state.renderTexture = this.getPotFilterTexture(state.sourceFrame.width, state.sourceFrame.height, resolution); + state.filters = filters; + + state.destinationFrame.width = state.renderTexture.width; + state.destinationFrame.height = state.renderTexture.height; + + state.renderTexture.filterFrame = state.sourceFrame; + renderer.renderTexture.bind(state.renderTexture, state.sourceFrame);// /, state.destinationFrame); + renderer.renderTexture.clear(); + } + + /** + * Pops off the filter and applies it. + * + */ + pop() + { + const renderer = this.renderer; + const filterStack = renderer.renderTexture.defaultFilterStack; + const state = filterStack.pop(); + const filters = state.filters; + + this.activeState = state; + + const globalUniforms = this.globalUniforms.uniforms; + + globalUniforms.sourceFrame = state.sourceFrame; + globalUniforms.destinationFrame = state.destinationFrame; + globalUniforms.resolution = state.resolution; + + // only update the rect if its legacy.. + if (state.legacy) + { + const filterArea = globalUniforms.filterArea; + const filterClamp = globalUniforms.filterClamp; + + filterArea[0] = state.destinationFrame.width; + filterArea[1] = state.destinationFrame.height; + filterArea[2] = state.sourceFrame.x; + filterArea[3] = state.sourceFrame.y; + + filterClamp[0] = 0.5 / state.resolution / state.destinationFrame.width; + filterClamp[1] = 0.5 / state.resolution / state.destinationFrame.height; + filterClamp[2] = (state.sourceFrame.width - 0.5) / state.resolution / state.destinationFrame.width; + filterClamp[3] = (state.sourceFrame.height - 0.5) / state.resolution / state.destinationFrame.height; + } + + this.globalUniforms.update(); + + const lastState = filterStack[filterStack.length - 1]; + + if (filters.length === 1) + { + filters[0].apply(this, state.renderTexture, lastState.renderTexture, false, state); + + this.returnFilterTexture(state.renderTexture); + } + else + { + let flip = state.renderTexture; + let flop = this.getPotFilterTexture( + flip.width, + flip.height, + state.resolution + ); + + flop.filterFrame = flip.filterFrame; + + let i = 0; + + for (i = 0; i < filters.length - 1; ++i) + { + filters[i].apply(this, flip, flop, true, state); + + const t = flip; + + flip = flop; + flop = t; + } + + filters[i].apply(this, flip, lastState.renderTexture, false, state); + + this.returnFilterTexture(flip); + this.returnFilterTexture(flop); + } + + this.statePool.push(state); + } + + /** + * Draws a filter. + * + * @param {PIXI.Filter} filter - The filter to draw. + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + * @param {boolean} clear - Should the output be cleared before rendering to it + */ + applyFilter(filter, input, output, clear) + { + const renderer = this.renderer; + + renderer.renderTexture.bind(output, output ? output.filterFrame : null); + + if (clear) + { + // gl.disable(gl.SCISSOR_TEST); + renderer.renderTexture.clear(); + // gl.enable(gl.SCISSOR_TEST); + } + + // set the uniforms.. + filter.uniforms.uSampler = input; + filter.uniforms.filterGlobals = this.globalUniforms; + + // TODO make it so that the order of this does not matter.. + // because it does at the moment cos of global uniforms. + // they need to get resynced + + renderer.state.setState(filter.state); + renderer.shader.bind(filter); + + if (filter.legacy) + { + this.quadUv.map(input._frame, input.filterFrame); + + renderer.geometry.bind(this.quadUv); + renderer.geometry.draw(DRAW_MODES.TRIANGLES); + } + else + { + renderer.geometry.bind(this.quad); + renderer.geometry.draw(DRAW_MODES.TRIANGLE_STRIP); + } + } + + /** + * Calculates the mapped matrix. + * + * TODO playing around here.. this is temporary - (will end up in the shader) + * this returns a matrix that will normalise map filter cords in the filter to screen space + * + * @param {PIXI.Matrix} outputMatrix - the matrix to output to. + * @return {PIXI.Matrix} The mapped matrix. + */ + calculateScreenSpaceMatrix(outputMatrix) + { + const currentState = this.activeState; + + return filterTransforms.calculateScreenSpaceMatrix( + outputMatrix, + currentState.sourceFrame, + currentState.destinationFrame + ); + } + + /** + * This will map the filter coord so that a texture can be used based on the transform of a sprite + * + * @param {PIXI.Matrix} outputMatrix - The matrix to output to. + * @param {PIXI.Sprite} sprite - The sprite to map to. + * @return {PIXI.Matrix} The mapped matrix. + */ + calculateSpriteMatrix(outputMatrix, sprite) + { + const currentState = this.activeState; + + return filterTransforms.calculateSpriteMatrix( + outputMatrix, + currentState.sourceFrame, + currentState.destinationFrame, + sprite + ); + } + + /** + * Destroys this Filter System. + * + * @param {boolean} [contextLost=false] context was lost, do not free shaders + * + */ + destroy(contextLost = false) + { + if (!contextLost) + { + this.emptyPool(); + } + else + { + this.texturePool = {}; + } + } + + /** + * Gets a Power-of-Two render texture. + * + * TODO move to a seperate class could be on renderer? + * also - could cause issue with multiple contexts? + * + * @private + * @param {WebGLRenderingContext} gl - The webgl rendering context + * @param {number} minWidth - The minimum width of the render target. + * @param {number} minHeight - The minimum height of the render target. + * @param {number} resolution - The resolution of the render target. + * @return {PIXI.RenderTarget} The new render target. + */ + getPotFilterTexture(minWidth, minHeight, resolution) + { + minWidth = bitTwiddle.nextPow2(minWidth); + minHeight = bitTwiddle.nextPow2(minHeight); + resolution = resolution || 1; + + const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + + if (!this.texturePool[key]) + { + this.texturePool[key] = []; + } + + let renderTexture = this.texturePool[key].pop(); + + if (!renderTexture) + { + // temporary bypass cache.. + // internally - this will cause a texture to be bound.. + renderTexture = RenderTexture.create({ + width: minWidth, + height: minHeight, + resolution, + }); + } + + return renderTexture; + } + + /** + * Gets extra render texture to use inside current filter + * + * @param {number} resolution resolution of the renderTexture + * @returns {PIXI.RenderTexture} + */ + getFilterTexture(resolution) + { + const rt = this.activeState.renderTexture; + + const filterTexture = this.getPotFilterTexture(rt.width, rt.height, resolution || rt.baseTexture.resolution); + + filterTexture.filterFrame = rt.filterFrame; + + return filterTexture; + } + + /** + * Frees a render texture back into the pool. + * + * @param {PIXI.RenderTarget} renderTexture - The renderTarget to free + */ + returnFilterTexture(renderTexture) + { + renderTexture.filterFrame = null; + + const base = renderTexture.baseTexture; + + const minWidth = base.width; + const minHeight = base.height; + + const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + + this.texturePool[key].push(renderTexture); + } + + /** + * Empties the texture pool. + * + */ + emptyPool() + { + for (const i in this.texturePool) + { + const textures = this.texturePool[i]; + + if (textures) + { + for (let j = 0; j < textures.length; j++) + { + textures[j].destroy(true); + } + } + } + + this.texturePool = {}; + } +} diff --git a/packages/core/src/filters/defaultFilter.frag b/packages/core/src/filters/defaultFilter.frag new file mode 100644 index 0000000..a14c1be --- /dev/null +++ b/packages/core/src/filters/defaultFilter.frag @@ -0,0 +1,22 @@ +varying vec2 vTextureCoord; +varying vec2 vFilterCoord; + +uniform sampler2D uSampler; +uniform sampler2D filterSampler; + +void main(void){ + vec4 masky = texture2D(filterSampler, vFilterCoord); + vec4 sample = texture2D(uSampler, vTextureCoord); + vec4 color; + if(mod(vFilterCoord.x, 1.0) > 0.5) + { + color = vec4(1.0, 0.0, 0.0, 1.0); + } + else + { + color = vec4(0.0, 1.0, 0.0, 1.0); + } + // gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0); + gl_FragColor = mix(sample, masky, 0.5); + gl_FragColor *= sample.a; +} diff --git a/packages/core/src/filters/defaultFilter.vert b/packages/core/src/filters/defaultFilter.vert new file mode 100644 index 0000000..e1febca --- /dev/null +++ b/packages/core/src/filters/defaultFilter.vert @@ -0,0 +1,14 @@ +attribute vec2 aVertexPosition; +attribute vec2 aTextureCoord; + +uniform mat3 projectionMatrix; +uniform mat3 filterMatrix; + +varying vec2 vTextureCoord; +varying vec2 vFilterCoord; + +void main(void){ + gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); + vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy; + vTextureCoord = aTextureCoord ; +} diff --git a/packages/core/src/filters/filterTransforms.js b/packages/core/src/filters/filterTransforms.js new file mode 100644 index 0000000..87a3cd6 --- /dev/null +++ b/packages/core/src/filters/filterTransforms.js @@ -0,0 +1,50 @@ +import { Matrix } from '@pixi/math'; + +/** + * Calculates the mapped matrix + * @param filterArea {Rectangle} The filter area + * @param sprite {Sprite} the target sprite + * @param outputMatrix {Matrix} @alvin + * @private + */ +// this returns a matrix that will normalise map filter cords in the filter to screen space +export function calculateScreenSpaceMatrix(outputMatrix, filterArea, textureSize) +{ + // TODO unwrap? + const mappedMatrix = outputMatrix.identity(); + + mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); + + mappedMatrix.scale(textureSize.width, textureSize.height); + + return mappedMatrix; +} + +export function calculateNormalizedScreenSpaceMatrix(outputMatrix, filterArea, textureSize) +{ + const mappedMatrix = outputMatrix.identity(); + + mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); + + const translateScaleX = (textureSize.width / filterArea.width); + const translateScaleY = (textureSize.height / filterArea.height); + + mappedMatrix.scale(translateScaleX, translateScaleY); + + return mappedMatrix; +} + +// this will map the filter coord so that a texture can be used based on the transform of a sprite +export function calculateSpriteMatrix(outputMatrix, filterArea, textureSize, sprite) +{ + const orig = sprite._texture.orig; + const mappedMatrix = outputMatrix.set(textureSize.width, 0, 0, textureSize.height, filterArea.x, filterArea.y); + const worldTransform = sprite.worldTransform.copyTo(Matrix.TEMP_MATRIX); + + worldTransform.invert(); + mappedMatrix.prepend(worldTransform); + mappedMatrix.scale(1.0 / orig.width, 1.0 / orig.height); + mappedMatrix.translate(sprite.anchor.x, sprite.anchor.y); + + return mappedMatrix; +} diff --git a/packages/core/src/filters/spriteMask/SpriteMaskFilter.js b/packages/core/src/filters/spriteMask/SpriteMaskFilter.js new file mode 100644 index 0000000..91697b7 --- /dev/null +++ b/packages/core/src/filters/spriteMask/SpriteMaskFilter.js @@ -0,0 +1,63 @@ +import Filter from '../Filter'; +import { Matrix } from '@pixi/math'; +import vertex from './spriteMaskFilter.vert'; +import fragment from './spriteMaskFilter.frag'; +import { default as TextureMatrix } from '../../textures/TextureMatrix'; + +/** + * The SpriteMaskFilter class + * + * @class + * @extends PIXI.Filter + * @memberof PIXI + */ +export default class SpriteMaskFilter extends Filter +{ + /** + * @param {PIXI.Sprite} sprite - the target sprite + */ + constructor(sprite) + { + const maskMatrix = new Matrix(); + + super(vertex, fragment); + + sprite.renderable = false; + + this.maskSprite = sprite; + this.maskMatrix = maskMatrix; + } + + /** + * Applies the filter + * + * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + */ + apply(filterManager, input, output) + { + const maskSprite = this.maskSprite; + const tex = this.maskSprite.texture; + + if (!tex.valid) + { + return; + } + if (!tex.transform) + { + // margin = 0.0, let it bleed a bit, shader code becomes easier + // assuming that atlas textures were made with 1-pixel padding + tex.transform = new TextureMatrix(tex, 0.0); + } + tex.transform.update(); + + this.uniforms.mask = tex; + this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite) + .prepend(tex.transform.mapCoord); + this.uniforms.alpha = maskSprite.worldAlpha; + this.uniforms.maskClamp = tex.transform.uClampFrame; + + filterManager.applyFilter(this, input, output); + } +} diff --git a/packages/core/src/filters/spriteMask/spriteMaskFilter.frag b/packages/core/src/filters/spriteMask/spriteMaskFilter.frag new file mode 100644 index 0000000..0e4aef8 --- /dev/null +++ b/packages/core/src/filters/spriteMask/spriteMaskFilter.frag @@ -0,0 +1,23 @@ +varying vec2 vMaskCoord; +varying vec2 vTextureCoord; + +uniform sampler2D uSampler; +uniform sampler2D mask; +uniform float alpha; +uniform vec4 maskClamp; + +void main(void) +{ + float clip = step(3.5, + step(maskClamp.x, vMaskCoord.x) + + step(maskClamp.y, vMaskCoord.y) + + step(vMaskCoord.x, maskClamp.z) + + step(vMaskCoord.y, maskClamp.w)); + + vec4 original = texture2D(uSampler, vTextureCoord); + vec4 masky = texture2D(mask, vMaskCoord); + + original *= (masky.r * masky.a * alpha * clip); + + gl_FragColor = original; +} diff --git a/packages/core/src/filters/spriteMask/spriteMaskFilter.vert b/packages/core/src/filters/spriteMask/spriteMaskFilter.vert new file mode 100644 index 0000000..d23a30c --- /dev/null +++ b/packages/core/src/filters/spriteMask/spriteMaskFilter.vert @@ -0,0 +1,16 @@ +attribute vec2 aVertexPosition; +attribute vec2 aTextureCoord; + +uniform mat3 projectionMatrix; +uniform mat3 otherMatrix; + +varying vec2 vMaskCoord; +varying vec2 vTextureCoord; + +void main(void) +{ + gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); + + vTextureCoord = aTextureCoord; + vMaskCoord = ( otherMatrix * vec3( aTextureCoord, 1.0) ).xy; +} diff --git a/packages/core/src/framebuffer/FrameBuffer.js b/packages/core/src/framebuffer/FrameBuffer.js new file mode 100644 index 0000000..84e9fcc --- /dev/null +++ b/packages/core/src/framebuffer/FrameBuffer.js @@ -0,0 +1,106 @@ +import Texture from '../textures/BaseTexture'; +import { FORMATS, TYPES } from '@pixi/constants'; + +/** + * Frame buffer + * @class + * @memberof PIXI + */ +export default class FrameBuffer +{ + constructor(width, height) + { + this.width = width || 100; + this.height = height || 100; + + this.stencil = false; + this.depth = false; + + this.dirtyId = 0; + this.dirtyFormat = 0; + this.dirtySize = 0; + + this.depthTexture = null; + this.colorTextures = []; + + this.glFrameBuffers = {}; + } + + get colorTexture() + { + return this.colorTextures[0]; + } + + addColorTexture(index, texture) + { + // TODO add some validation to the texture - same width / height etc? + this.colorTextures[index || 0] = texture || new Texture(null, { scaleMode: 0, + resolution: 1, + mipmap: false, + width: this.width, + height: this.height });// || new Texture(); + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + addDepthTexture(texture) + { + /* eslint-disable max-len */ + this.depthTexture = texture || new Texture(null, { scaleMode: 0, + resolution: 1, + width: this.width, + height: this.height, + mipmap: false, + format: FORMATS.DEPTH_COMPONENT, + type: TYPES.UNSIGNED_SHORT });// UNSIGNED_SHORT; + /* eslint-disable max-len */ + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + enableDepth() + { + this.depth = true; + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + enableStencil() + { + this.stencil = true; + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + resize(width, height) + { + if (width === this.width && height === this.height) return; + + this.width = width; + this.height = height; + + this.dirtyId++; + this.dirtySize++; + + for (let i = 0; i < this.colorTextures.length; i++) + { + this.colorTextures[i].setSize(width, height); + } + + if (this.depthTexture) + { + this.depthTexture.setSize(width, height); + } + } +} diff --git a/packages/core/src/framebuffer/FramebufferSystem.js b/packages/core/src/framebuffer/FramebufferSystem.js new file mode 100644 index 0000000..2cc2437 --- /dev/null +++ b/packages/core/src/framebuffer/FramebufferSystem.js @@ -0,0 +1,246 @@ +import System from '../System'; +import { Rectangle } from '@pixi/math'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class FramebufferSystem extends System +{ + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + contextChange() + { + this.gl = this.renderer.gl; + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + this.current = null; + this.viewport = new Rectangle(); + + this.drawBufferExtension = this.renderer.context.extensions.drawBuffers; + } + + bind(framebuffer, frame) + { + const gl = this.gl; + + this.current = framebuffer; + + if (framebuffer) + { + // TODO cacheing layer! + + const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID] || this.initFramebuffer(framebuffer); + + gl.bindFramebuffer(gl.FRAMEBUFFER, fbo.framebuffer); + // makesure all textures are unbound.. + + // now check for updates... + if (fbo.dirtyId !== framebuffer.dirtyId) + { + fbo.dirtyId = framebuffer.dirtyId; + + if (fbo.dirtyFormat !== framebuffer.dirtyFormat) + { + fbo.dirtyFormat = framebuffer.dirtyFormat; + this.updateFramebuffer(framebuffer); + } + else if (fbo.dirtySize !== framebuffer.dirtySize) + { + fbo.dirtySize = framebuffer.dirtySize; + this.resizeFramebuffer(framebuffer); + } + } + + for (let i = 0; i < framebuffer.colorTextures.length; i++) + { + if (framebuffer.colorTextures[i].texturePart) + { + this.renderer.texture.unbind(framebuffer.colorTextures[i].texture); + } + else + { + this.renderer.texture.unbind(framebuffer.colorTextures[i]); + } + } + + if (framebuffer.depthTexture) + { + this.renderer.texture.unbind(framebuffer.depthTexture); + } + + if (frame) + { + this.setViewport(frame.x, frame.y, frame.width, frame.height); + } + else + { + this.setViewport(0, 0, framebuffer.width, framebuffer.height); + } + } + else + { + gl.bindFramebuffer(gl.FRAMEBUFFER, null); + + if (frame) + { + this.setViewport(frame.x, frame.y, frame.width, frame.height); + } + else + { + this.setViewport(0, 0, this.renderer.width, this.renderer.height); + } + } + } + + setViewport(x, y, width, height) + { + const v = this.viewport; + + if (v.width !== width || v.height !== height || v.x !== x || v.y !== y) + { + v.x = x; + v.y = y; + v.width = width; + v.height = height; + + this.gl.viewport(x, y, width, height); + } + } + + get size() + { + if (this.current) + { + // TODO store temp + return { x: 0, y: 0, width: this.current.width, height: this.current.height }; + } + + return { x: 0, y: 0, width: this.renderer.width, height: this.renderer.height }; + } + + clear(r, g, b, a) + { + const gl = this.gl; + + // TODO clear color can be set only one right? + gl.clearColor(r, g, b, a); + gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); + } + + // private functions... + + initFramebuffer(framebuffer) + { + const gl = this.gl; + + // TODO - make this a class? + const fbo = { + framebuffer: gl.createFramebuffer(), + stencil: null, + dirtyId: 0, + dirtyFormat: 0, + dirtySize: 0, + }; + + framebuffer.glFrameBuffers[this.CONTEXT_UID] = fbo; + + return fbo; + } + + resizeFramebuffer(framebuffer) + { + const gl = this.gl; + + if (framebuffer.stencil || framebuffer.depth) + { + gl.bindRenderbuffer(gl.RENDERBUFFER, this.stencil); + gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); + } + } + + updateFramebuffer(framebuffer) + { + const gl = this.gl; + + const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID]; + + // bind the color texture + const colorTextures = framebuffer.colorTextures; + + let count = colorTextures.length; + + if (!this.drawBufferExtension) + { + count = Math.min(count, 1); + } + + const activeTextures = []; + + for (let i = 0; i < count; i++) + { + const texture = framebuffer.colorTextures[i]; + + if (texture.texturePart) + { + this.renderer.texture.bind(texture.texture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.COLOR_ATTACHMENT0 + i, + gl.TEXTURE_CUBE_MAP_NEGATIVE_X + texture.side, + texture.texture._glTextures[this.CONTEXT_UID].texture, + 0); + } + else + { + this.renderer.texture.bind(texture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.COLOR_ATTACHMENT0 + i, + gl.TEXTURE_2D, + texture._glTextures[this.CONTEXT_UID].texture, + 0); + } + + activeTextures.push(gl.COLOR_ATTACHMENT0 + i); + } + + if (this.drawBufferExtension && activeTextures.length > 1) + { + this.drawBufferExtension.drawBuffersWEBGL(activeTextures); + } + + if (framebuffer.depthTexture) + { + const depthTextureExt = this.renderer.context.extensions.depthTexture; + + if (depthTextureExt) + { + const depthTexture = framebuffer.depthTexture; + + this.renderer.texture.bind(depthTexture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.DEPTH_ATTACHMENT, + gl.TEXTURE_2D, + depthTexture._glTextures[this.CONTEXT_UID].texture, + 0); + } + } + + if (framebuffer.stencil || framebuffer.depth) + { + fbo.stencil = gl.createRenderbuffer(); + + gl.bindRenderbuffer(gl.RENDERBUFFER, fbo.stencil); + + // TODO.. this is depth AND stencil? + gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, fbo.stencil); + gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); + // fbo.enableStencil(); + } + } +} diff --git a/packages/core/src/geometry/GLBuffer.js b/packages/core/src/geometry/GLBuffer.js new file mode 100644 index 0000000..0477e8a --- /dev/null +++ b/packages/core/src/geometry/GLBuffer.js @@ -0,0 +1,9 @@ +export default class GLBuffer +{ + constructor(buffer) + { + this.buffer = buffer; + this.updateID = -1; + this.byteLength = -1; + } +} diff --git a/packages/core/src/geometry/GeometrySystem.js b/packages/core/src/geometry/GeometrySystem.js new file mode 100644 index 0000000..959f827 --- /dev/null +++ b/packages/core/src/geometry/GeometrySystem.js @@ -0,0 +1,388 @@ +import System from '../System'; +import GLBuffer from './GLBuffer'; + +const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class GeometrySystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this._activeGeometry = null; + this._activeVao = null; + + this.hasVao = true; + this.hasInstance = true; + } + + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + contextChange() + { + const gl = this.gl = this.renderer.gl; + + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + + // webgl2 + if (!gl.createVertexArray) + { + // webgl 1! + let nativeVaoExtension = this.renderer.context.extensions.vertexArrayObject; + + if (this.renderer.legacy) + { + nativeVaoExtension = null; + } + + if (nativeVaoExtension) + { + gl.createVertexArray = () => + nativeVaoExtension.createVertexArrayOES(); + + gl.bindVertexArray = (vao) => + nativeVaoExtension.bindVertexArrayOES(vao); + + gl.deleteVertexArray = (vao) => + nativeVaoExtension.deleteVertexArrayOES(vao); + } + else + { + this.hasVao = false; + gl.createVertexArray = () => + { + // empty + }; + + gl.bindVertexArray = () => + { + // empty + }; + + gl.deleteVertexArray = () => + { + // empty + }; + } + } + + if (!gl.vertexAttribDivisor) + { + const instanceExt = gl.getExtension('ANGLE_instanced_arrays'); + + if (instanceExt) + { + gl.vertexAttribDivisor = (a, b) => + instanceExt.vertexAttribDivisorANGLE(a, b); + + gl.drawElementsInstanced = (a, b, c, d, e) => + instanceExt.drawElementsInstancedANGLE(a, b, c, d, e); + + gl.drawArraysInstanced = (a, b, c, d) => + instanceExt.drawArraysInstancedANGLE(a, b, c, d); + } + else + { + this.hasInstance = false; + } + } + } + + /** + * Binds geometry so that is can be drawn. Creating a Vao if required + * @private + * @param {PIXI.Geometry} geometry instance of geometry to bind + */ + bind(geometry, shader) + { + shader = shader || this.renderer.shader.shader; + + const gl = this.gl; + + // not sure the best way to address this.. + // currently different shaders require different VAOs for the same geometry + // Still mulling over the best way to solve this one.. + // will likely need to modify the shader attribute locations at run time! + let vaos = geometry.glVertexArrayObjects[this.CONTEXT_UID]; + + if (!vaos) + { + geometry.glVertexArrayObjects[this.CONTEXT_UID] = vaos = {}; + } + + const vao = vaos[shader.program.id] || this.initGeometryVao(geometry, shader.program); + + this._activeGeometry = geometry; + + if (this._activeVao !== vao) + { + this._activeVao = vao; + + if (this.hasVao) + { + gl.bindVertexArray(vao); + } + else + { + this.activateVao(geometry, shader.program); + } + } + + // TODO - optimise later! + // don't need to loop through if nothing changed! + // maybe look to add an 'autoupdate' to geometry? + this.updateBuffers(); + } + + reset() + { + this.unbind(); + } + + updateBuffers() + { + const geometry = this._activeGeometry; + const gl = this.gl; + + for (let i = 0; i < geometry.buffers.length; i++) + { + const buffer = geometry.buffers[i]; + + const glBuffer = buffer._glBuffers[this.CONTEXT_UID]; + + if (buffer._updateID !== glBuffer.updateID) + { + glBuffer.updateID = buffer._updateID; + + // TODO can cache this on buffer! maybe added a getter / setter? + const type = buffer.index ? gl.ELEMENT_ARRAY_BUFFER : gl.ARRAY_BUFFER; + const drawType = buffer.static ? gl.STATIC_DRAW : gl.DYNAMIC_DRAW; + + gl.bindBuffer(type, glBuffer.buffer); + + if (glBuffer.byteLength >= buffer.data.byteLength) + { + // offset is always zero for now! + gl.bufferSubData(type, 0, buffer.data); + } + else + { + gl.bufferData(type, buffer.data, drawType); + } + } + } + } + + checkCompatability(geometry, program) + { + // geometry must have at least all the attributes that the shader requires. + const geometryAttributes = geometry.attributes; + const shaderAttributes = program.attributeData; + + for (const j in shaderAttributes) + { + if (!geometryAttributes[j]) + { + throw new Error(`shader and geometry incompatible, geometry missing the "${j}" attribute`); + } + } + } + + /** + * Creates a Vao with the same structure as the geometry and stores it on the geometry. + * @private + * @param {PIXI.Geometry} geometry instance of geometry to to generate Vao for + */ + initGeometryVao(geometry, program) + { + this.checkCompatability(geometry, program); + + const gl = this.gl; + const CONTEXT_UID = this.CONTEXT_UID; + const buffers = geometry.buffers; + const attributes = geometry.attributes; + + const tempStride = {}; + const tempStart = {}; + + for (const j in buffers) + { + tempStride[j] = 0; + tempStart[j] = 0; + } + + for (const j in attributes) + { + if (!attributes[j].size && program.attributeData[j]) + { + attributes[j].size = program.attributeData[j].size; + } + + tempStride[attributes[j].buffer] += attributes[j].size * byteSizeMap[attributes[j].type]; + } + + for (const j in attributes) + { + const attribute = attributes[j]; + const attribSize = attribute.size; + + if (attribute.stride === undefined) + { + if (tempStride[attribute.buffer] === attribSize * byteSizeMap[attribute.type]) + { + attribute.stride = 0; + } + else + { + attribute.stride = tempStride[attribute.buffer]; + } + } + + if (attribute.start === undefined) + { + attribute.start = tempStart[attribute.buffer]; + + tempStart[attribute.buffer] += attribSize * byteSizeMap[attribute.type]; + } + } + + // first update - and create the buffers! + // only create a gl buffer if it actually gets + for (let i = 0; i < buffers.length; i++) + { + const buffer = buffers[i]; + + if (!buffer._glBuffers[CONTEXT_UID]) + { + buffer._glBuffers[CONTEXT_UID] = new GLBuffer(gl.createBuffer()); + } + } + + // TODO - maybe make this a data object? + // lets wait to see if we need to first! + const vao = gl.createVertexArray(); + + gl.bindVertexArray(vao); + + this.activateVao(geometry, program); + + gl.bindVertexArray(null); + + // add it to the cache! + geometry.glVertexArrayObjects[this.CONTEXT_UID][program.id] = vao; + + return vao; + } + + activateVao(geometry, program) + { + const gl = this.gl; + const CONTEXT_UID = this.CONTEXT_UID; + const buffers = geometry.buffers; + const attributes = geometry.attributes; + + if (geometry.indexBuffer) + { + // first update the index buffer if we have one.. + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, geometry.indexBuffer._glBuffers[CONTEXT_UID].buffer); + } + + let lastBuffer = null; + + // add a new one! + for (const j in attributes) + { + const attribute = attributes[j]; + const buffer = buffers[attribute.buffer]; + const glBuffer = buffer._glBuffers[CONTEXT_UID]; + + if (program.attributeData[j]) + { + if (lastBuffer !== glBuffer) + { + gl.bindBuffer(gl.ARRAY_BUFFER, glBuffer.buffer); + + lastBuffer = glBuffer; + } + + const location = program.attributeData[j].location; + + // TODO introduce state again + // we can optimise this for older devices that have no VAOs + gl.enableVertexAttribArray(location); + + gl.vertexAttribPointer(location, + attribute.size, + attribute.type || gl.FLOAT, + attribute.normalized, + attribute.stride, + attribute.start); + + if (attribute.instance) + { + // TODO calculate instance count based of this... + if (this.hasInstance) + { + gl.vertexAttribDivisor(location, 1); + } + else + { + throw new Error('geometry error, GPU Instancing is not supported on this device'); + } + } + } + } + } + + draw(type, size, start, instanceCount) + { + const gl = this.gl; + const geometry = this._activeGeometry; + + // TODO.. this should not change so maybe cache the function? + + if (geometry.indexBuffer) + { + if (geometry.instanced) + { + /* eslint-disable max-len */ + gl.drawElementsInstanced(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2, instanceCount || 1); + /* eslint-enable max-len */ + } + else + { + gl.drawElements(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2); + } + } + else + if (geometry.instanced) + { + // TODO need a better way to calculate size.. + gl.drawArraysInstanced(type, start, size || geometry.getSize(), instanceCount || 1); + } + else + { + gl.drawArrays(type, start, size || geometry.getSize()); + } + + return this; + } + + unbind() + { + this.gl.bindVertexArray(null); + this._activeVao = null; + this._activeGeometry = null; + } +} diff --git a/packages/core/src/geometry/utils/setVertexAttribArrays.js b/packages/core/src/geometry/utils/setVertexAttribArrays.js new file mode 100644 index 0000000..23b6959 --- /dev/null +++ b/packages/core/src/geometry/utils/setVertexAttribArrays.js @@ -0,0 +1,54 @@ +// var GL_MAP = {}; + +/** + * @param gl {WebGLRenderingContext} The current WebGL context + * @param attribs {*} + * @param state {*} + */ +export default function setVertexAttribArrays(gl, attribs, state) +{ + let i; + + if (state) + { + const tempAttribState = state.tempAttribState; + const attribState = state.attribState; + + for (i = 0; i < tempAttribState.length; i++) + { + tempAttribState[i] = false; + } + + // set the new attribs + for (i = 0; i < attribs.length; i++) + { + tempAttribState[attribs[i].attribute.location] = true; + } + + for (i = 0; i < attribState.length; i++) + { + if (attribState[i] !== tempAttribState[i]) + { + attribState[i] = tempAttribState[i]; + + if (state.attribState[i]) + { + gl.enableVertexAttribArray(i); + } + else + { + gl.disableVertexAttribArray(i); + } + } + } + } + else + { + for (i = 0; i < attribs.length; i++) + { + const attrib = attribs[i]; + + gl.enableVertexAttribArray(attrib.attribute.location); + } + } +} diff --git a/packages/core/src/index.js b/packages/core/src/index.js index 58c456a..fd1f547 100644 --- a/packages/core/src/index.js +++ b/packages/core/src/index.js @@ -1,30 +1,30 @@ import * as resources from './textures/resources'; -import * as systems from './renderers/systems'; +import * as systems from './systems'; -export { resources }; export { systems }; +export { resources }; -export { default as Renderer } from './renderers/Renderer'; -export { default as AbstractRenderer } from './renderers/AbstractRenderer'; -export { default as FrameBuffer } from './textures/FrameBuffer'; +export { default as System } from './System'; +export { default as Renderer } from './Renderer'; +export { default as AbstractRenderer } from './AbstractRenderer'; +export { default as FrameBuffer } from './framebuffer/FrameBuffer'; export { default as CubeTexture } from './textures/CubeTexture'; export { default as BaseTexture } from './textures/BaseTexture'; export { default as Texture } from './textures/Texture'; export { default as TextureMatrix } from './textures/TextureMatrix'; -export { default as RenderTexture } from './textures/RenderTexture'; -export { default as BaseRenderTexture } from './textures/BaseRenderTexture'; +export { default as RenderTexture } from './renderTexture/RenderTexture'; +export { default as BaseRenderTexture } from './renderTexture/BaseRenderTexture'; export { default as TextureUvs } from './textures/TextureUvs'; -export { default as State } from './renderers/State'; -export { default as ObjectRenderer } from './renderers/utils/ObjectRenderer'; -export { default as Quad } from './renderers/utils/Quad'; -export { default as QuadUv } from './renderers/utils/QuadUv'; -export { default as checkMaxIfStatmentsInShader } from './renderers/utils/checkMaxIfStatmentsInShader'; +export { default as State } from './state/State'; +export { default as ObjectRenderer } from './batch/ObjectRenderer'; +export { default as Quad } from './utils/Quad'; +export { default as QuadUv } from './utils/QuadUv'; +export { default as checkMaxIfStatmentsInShader } from './shader/utils/checkMaxIfStatmentsInShader'; export { default as Shader } from './shader/Shader'; export { default as Program } from './shader/Program'; export { default as UniformGroup } from './shader/UniformGroup'; -export { default as SpriteMaskFilter } from './renderers/filters/spriteMask/SpriteMaskFilter'; -export { default as Filter } from './renderers/filters/Filter'; +export { default as SpriteMaskFilter } from './filters/spriteMask/SpriteMaskFilter'; +export { default as Filter } from './filters/Filter'; export { default as Attribute } from './geometry/Attribute'; export { default as Buffer } from './geometry/Buffer'; export { default as Geometry } from './geometry/Geometry'; - diff --git a/packages/core/src/mask/MaskSystem.js b/packages/core/src/mask/MaskSystem.js new file mode 100644 index 0000000..99174c2 --- /dev/null +++ b/packages/core/src/mask/MaskSystem.js @@ -0,0 +1,199 @@ +import System from '../System'; +import AlphaMaskFilter from '../filters/spriteMask/SpriteMaskFilter'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class MaskSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + // TODO - we don't need both! + this.scissor = false; + this.scissorData = null; + this.scissorRenderTarget = null; + + this.enableScissor = false; + + this.alphaMaskPool = []; + this.alphaMaskIndex = 0; + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.DisplayObject} target - Display Object to push the mask to + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + push(target, maskData) + { + // TODO the root check means scissor rect will not + // be used on render textures more info here: + // https://github.com/pixijs/pixi.js/pull/3545 + + if (maskData.texture) + { + this.pushSpriteMask(target, maskData); + } + else if (this.enableScissor + && !this.scissor + && this.renderer._activeRenderTarget.root + && !this.renderer.stencil.stencilMaskStack.length + && maskData.isFastRect()) + { + const matrix = maskData.worldTransform; + + let rot = Math.atan2(matrix.b, matrix.a); + + // use the nearest degree! + rot = Math.round(rot * (180 / Math.PI)); + + if (rot % 90) + { + this.pushStencilMask(maskData); + } + else + { + this.pushScissorMask(target, maskData); + } + } + else + { + this.pushStencilMask(maskData); + } + } + + /** + * Removes the last mask from the mask stack and doesn't return it. + * + * @param {PIXI.DisplayObject} target - Display Object to pop the mask from + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + pop(target, maskData) + { + if (maskData.texture) + { + this.popSpriteMask(target, maskData); + } + else if (this.enableScissor && !this.renderer.stencil.stencilMaskStack.length) + { + this.popScissorMask(target, maskData); + } + else + { + this.popStencilMask(target, maskData); + } + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.RenderTarget} target - Display Object to push the sprite mask to + * @param {PIXI.Sprite} maskData - Sprite to be used as the mask + */ + pushSpriteMask(target, maskData) + { + let alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; + + if (!alphaMaskFilter) + { + alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; + } + + alphaMaskFilter[0].resolution = this.renderer.resolution; + alphaMaskFilter[0].maskSprite = maskData; + + // TODO - may cause issues! + target.filterArea = maskData.getBounds(true); + + this.renderer.filterSystem.pushFilter(target, alphaMaskFilter); + + this.alphaMaskIndex++; + } + + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popSpriteMask() + { + this.renderer.filterSystem.popFilter(); + this.alphaMaskIndex--; + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + pushStencilMask(maskData) + { + this.renderer.batch.flush(); + this.renderer.stencil.pushStencil(maskData); + } + + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popStencilMask() + { + // this.renderer.currentRenderer.stop(); + this.renderer.stencil.popStencil(); + } + + /** + * + * @param {PIXI.DisplayObject} target - Display Object to push the mask to + * @param {PIXI.Graphics} maskData - The masking data. + */ + pushScissorMask(target, maskData) + { + maskData.renderable = true; + + const renderTarget = this.renderer._activeRenderTarget; + + const bounds = maskData.getBounds(); + + bounds.fit(renderTarget.size); + maskData.renderable = false; + + this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); + + const resolution = this.renderer.resolution; + + this.renderer.gl.scissor( + bounds.x * resolution, + (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, + bounds.width * resolution, + bounds.height * resolution + ); + + this.scissorRenderTarget = renderTarget; + this.scissorData = maskData; + this.scissor = true; + } + + /** + * + * + */ + popScissorMask() + { + this.scissorRenderTarget = null; + this.scissorData = null; + this.scissor = false; + + // must be scissor! + const gl = this.renderer.gl; + + gl.disable(gl.SCISSOR_TEST); + } +} diff --git a/packages/core/src/mask/StencilSystem.js b/packages/core/src/mask/StencilSystem.js new file mode 100644 index 0000000..62796a5 --- /dev/null +++ b/packages/core/src/mask/StencilSystem.js @@ -0,0 +1,133 @@ +import System from '../System'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class StencilSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + this.stencilMaskStack = []; + } + + /** + * Changes the mask stack that is used by this System. + * + * @param {PIXI.Graphics[]} stencilMaskStack - The mask stack + */ + setMaskStack(stencilMaskStack) + { + const gl = this.renderer.gl; + + if (stencilMaskStack.length !== this.stencilMaskStack.length) + { + if (stencilMaskStack.length === 0) + { + gl.disable(gl.STENCIL_TEST); + } + else + { + gl.enable(gl.STENCIL_TEST); + } + } + + this.stencilMaskStack = stencilMaskStack; + } + + /** + * Applies the Mask and adds it to the current stencil stack. @alvin + * + * @param {PIXI.Graphics} graphics - The mask + */ + pushStencil(graphics) + { + this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); + + // this.renderer._activeRenderTarget.attachStencilBuffer(); + + const gl = this.renderer.gl; + const prevMaskCount = this.stencilMaskStack.length; + + if (prevMaskCount === 0) + { + gl.enable(gl.STENCIL_TEST); + } + + this.stencilMaskStack.push(graphics); + + // Increment the refference stencil value where the new mask overlaps with the old ones. + gl.colorMask(false, false, false, false); + gl.stencilFunc(gl.EQUAL, prevMaskCount, this._getBitwiseMask()); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.INCR); + this.renderer.plugins.graphics.render(graphics); + + this._useCurrent(); + } + + /** + * Removes the last mask from the stencil stack. @alvin + */ + popStencil() + { + this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); + + const gl = this.renderer.gl; + const graphics = this.stencilMaskStack.pop(); + + if (this.stencilMaskStack.length === 0) + { + // the stack is empty! + gl.disable(gl.STENCIL_TEST); + gl.clear(gl.STENCIL_BUFFER_BIT); + gl.clearStencil(0); + } + else + { + // Decrement the refference stencil value where the popped mask overlaps with the other ones + gl.colorMask(false, false, false, false); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.DECR); + this.renderer.plugins.graphics.render(graphics); + + this._useCurrent(); + } + } + + /** + * Setup renderer to use the current stencil data. + */ + _useCurrent() + { + const gl = this.renderer.gl; + + gl.colorMask(true, true, true, true); + gl.stencilFunc(gl.EQUAL, this.stencilMaskStack.length, this._getBitwiseMask()); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP); + } + + /** + * Fill 1s equal to the number of acitve stencil masks. + * + * @return {number} The bitwise mask. + */ + _getBitwiseMask() + { + return (1 << this.stencilMaskStack.length) - 1; + } + + /** + * Destroys the mask stack. + * + */ + destroy() + { + super.destroy(this); + + this.stencilMaskStack = null; + } +} diff --git a/packages/core/src/projection/ProjectionSystem.js b/packages/core/src/projection/ProjectionSystem.js new file mode 100644 index 0000000..6e63b73 --- /dev/null +++ b/packages/core/src/projection/ProjectionSystem.js @@ -0,0 +1,73 @@ +import System from '../System'; +import { Matrix } from '@pixi/math'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class ProjectionSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.projectionMatrix = new Matrix(); + } + + update(destinationFrame, sourceFrame, resolution, root) + { + this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; + this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; + + this.calculateProjection(this.destinationFrame, this.sourceFrame, resolution, root); + + this.renderer.globalUniforms.uniforms.projectionMatrix = this.projectionMatrix; + this.renderer.globalUniforms.update(); + } + + /** + * Updates the projection matrix based on a projection frame (which is a rectangle) + * + * @param {Rectangle} destinationFrame - The destination frame. + * @param {Rectangle} sourceFrame - The source frame. + */ + calculateProjection(destinationFrame, sourceFrame, resolution, root) + { + const pm = this.projectionMatrix; + + // I don't think we will need this line.. + // pm.identity(); + + if (!root) + { + pm.a = (1 / destinationFrame.width * 2) * resolution; + pm.d = (1 / destinationFrame.height * 2) * resolution; + + pm.tx = -1 - (sourceFrame.x * pm.a); + pm.ty = -1 - (sourceFrame.y * pm.d); + } + else + { + pm.a = (1 / destinationFrame.width * 2) * resolution; + pm.d = (-1 / destinationFrame.height * 2) * resolution; + + pm.tx = -1 - (sourceFrame.x * pm.a); + pm.ty = 1 - (sourceFrame.y * pm.d); + } + } + + /** + * Sets the transform of the active render target to the given matrix + * + * @param {PIXI.Matrix} matrix - The transformation matrix + */ + setTransform()// matrix) + { + // this._activeRenderTarget.transform = matrix; + } +} diff --git a/packages/core/src/renderTexture/BaseRenderTexture.js b/packages/core/src/renderTexture/BaseRenderTexture.js new file mode 100644 index 0000000..88eb83b --- /dev/null +++ b/packages/core/src/renderTexture/BaseRenderTexture.js @@ -0,0 +1,137 @@ +import BaseTexture from '../textures/BaseTexture'; +import FrameBuffer from '../framebuffer/FrameBuffer'; + +/** + * A BaseRenderTexture is a special texture that allows any PixiJS display object to be rendered to it. + * + * __Hint__: All DisplayObjects (i.e. Sprites) that render to a BaseRenderTexture should be preloaded + * otherwise black rectangles will be drawn instead. + * + * A BaseRenderTexture takes a snapshot of any Display Object given to its render method. The position + * and rotation of the given Display Objects is ignored. For example: + * + * ```js + * let renderer = PIXI.autoDetectRenderer(1024, 1024, { view: canvas, ratio: 1 }); + * let baseRenderTexture = new PIXI.BaseRenderTexture(renderer, 800, 600); + * let sprite = PIXI.Sprite.fromImage("spinObj_01.png"); + * + * sprite.position.x = 800/2; + * sprite.position.y = 600/2; + * sprite.anchor.x = 0.5; + * sprite.anchor.y = 0.5; + * + * baseRenderTexture.render(sprite); + * ``` + * + * The Sprite in this case will be rendered using its local transform. To render this sprite at 0,0 + * you can clear the transform + * + * ```js + * + * sprite.setTransform() + * + * let baseRenderTexture = new PIXI.BaseRenderTexture(100, 100); + * let renderTexture = new PIXI.RenderTexture(baseRenderTexture); + * + * renderer.render(sprite, renderTexture); // Renders to center of RenderTexture + * ``` + * + * @class + * @extends PIXI.BaseTexture + * @memberof PIXI + */ +export default class BaseRenderTexture extends BaseTexture +{ + /** + * @param {object} [options] + * @param {number} [options.width=100] - The width of the base render texture + * @param {number} [options.height=100] - The height of the base render texture + * @param {PIXI.SCALE_MODES} [options.scaleMode] - See {@link PIXI.SCALE_MODES} for possible values + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the texture being generated + */ + constructor(options) + { + if (typeof options === 'number') + { + /* eslint-disable prefer-rest-params */ + // Backward compatibility of signature + const width = arguments[0]; + const height = arguments[1]; + const scaleMode = arguments[2]; + const resolution = arguments[3]; + + options = { width, height, scaleMode, resolution }; + /* eslint-enable prefer-rest-params */ + } + + super(null, options); + + const { width, height } = options || {}; + + // Set defaults + this.mipmap = false; + this.width = Math.ceil(width) || 100; + this.height = Math.ceil(height) || 100; + this.valid = true; + + /** + * A map of renderer IDs to webgl renderTargets + * + * @private + * @member {object} + */ + // this._glRenderTargets = {}; + + /** + * A reference to the canvas render target (we only need one as this can be shared across renderers) + * + * @private + * @member {object} + */ + this._canvasRenderTarget = null; + + this.clearColor = [0, 0, 0, 0]; + + this.frameBuffer = new FrameBuffer(width * this.resolution, height * this.resolution) + .addColorTexture(0, this); + + // TODO - could this be added the systems? + + /** + * The data structure for the stencil masks + * + * @member {PIXI.Graphics[]} + */ + this.stencilMaskStack = []; + + /** + * The data structure for the filters + * + * @member {PIXI.Graphics[]} + */ + this.filterStack = [{}]; + } + + /** + * Resizes the BaseRenderTexture. + * + * @param {number} width - The width to resize to. + * @param {number} height - The height to resize to. + */ + resize(width, height) + { + width = Math.ceil(width); + height = Math.ceil(height); + this.frameBuffer.resize(width * this.resolution, height * this.resolution); + } + + /** + * Destroys this texture + * + */ + destroy() + { + super.destroy(true); + this.renderer = null; + } +} diff --git a/packages/core/src/renderTexture/RenderTexture.js b/packages/core/src/renderTexture/RenderTexture.js new file mode 100644 index 0000000..f1ecf1f --- /dev/null +++ b/packages/core/src/renderTexture/RenderTexture.js @@ -0,0 +1,153 @@ +import BaseRenderTexture from './BaseRenderTexture'; +import Texture from '../textures/Texture'; + +/** + * A RenderTexture is a special texture that allows any PixiJS display object to be rendered to it. + * + * __Hint__: All DisplayObjects (i.e. Sprites) that render to a RenderTexture should be preloaded + * otherwise black rectangles will be drawn instead. + * + * A RenderTexture takes a snapshot of any Display Object given to its render method. For example: + * + * ```js + * let renderer = PIXI.autoDetectRenderer(1024, 1024, { view: canvas, ratio: 1 }); + * let renderTexture = PIXI.RenderTexture.create(800, 600); + * let sprite = PIXI.Sprite.fromImage("spinObj_01.png"); + * + * sprite.position.x = 800/2; + * sprite.position.y = 600/2; + * sprite.anchor.x = 0.5; + * sprite.anchor.y = 0.5; + * + * renderer.render(sprite, renderTexture); + * ``` + * + * The Sprite in this case will be rendered using its local transform. To render this sprite at 0,0 + * you can clear the transform + * + * ```js + * + * sprite.setTransform() + * + * let renderTexture = new PIXI.RenderTexture.create(100, 100); + * + * renderer.render(sprite, renderTexture); // Renders to center of RenderTexture + * ``` + * + * @class + * @extends PIXI.Texture + * @memberof PIXI + */ +export default class RenderTexture extends Texture +{ + /** + * @param {PIXI.BaseRenderTexture} baseRenderTexture - The renderer used for this RenderTexture + * @param {PIXI.Rectangle} [frame] - The rectangle frame of the texture to show + */ + constructor(baseRenderTexture, frame) + { + // support for legacy.. + let _legacyRenderer = null; + + if (!(baseRenderTexture instanceof BaseRenderTexture)) + { + /* eslint-disable prefer-rest-params, no-console */ + const width = arguments[1]; + const height = arguments[2]; + const scaleMode = arguments[3]; + const resolution = arguments[4]; + + // we have an old render texture.. + console.warn(`Please use RenderTexture.create(${width}, ${height}) instead of the ctor directly.`); + _legacyRenderer = arguments[0]; + /* eslint-enable prefer-rest-params, no-console */ + + frame = null; + baseRenderTexture = new BaseRenderTexture({ + width, + height, + scaleMode, + resolution, + }); + } + + /** + * The base texture object that this texture uses + * + * @member {BaseTexture} + */ + super(baseRenderTexture, frame); + + this.legacyRenderer = _legacyRenderer; + + /** + * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. + * + * @member {boolean} + */ + this.valid = true; + + /** + * FilterSystem temporary storage + * @private + * @member {PIXI.Rectangle} + */ + this.filterFrame = null; + + this._updateUvs(); + } + + /** + * Resizes the RenderTexture. + * + * @param {number} width - The width to resize to. + * @param {number} height - The height to resize to. + * @param {boolean} [resizeBaseTexture=true] - Should the baseTexture.width and height values be resized as well? + */ + resize(width, height, resizeBaseTexture = true) + { + width = Math.ceil(width); + height = Math.ceil(height); + + // TODO - could be not required.. + this.valid = (width > 0 && height > 0); + + this._frame.width = this.orig.width = width; + this._frame.height = this.orig.height = height; + + if (resizeBaseTexture) + { + this.baseTexture.resize(width, height); + } + + this._updateUvs(); + } + + /** + * A short hand way of creating a render texture. + * + * @param {object} [options] - Options + * @param {number} [options.width=100] - The width of the render texture + * @param {number} [options.height=100] - The height of the render texture + * @param {number} [options.scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the texture being generated + * @return {PIXI.RenderTexture} The new render texture + */ + static create(options) + { + // fallback, old-style: create(width, height, scaleMode, resolution) + if (typeof options === 'number') + { + /* eslint-disable prefer-rest-params */ + options = { + width: options, + height: arguments[1], + scaleMode: arguments[2], + resolution: arguments[3], + }; + /* eslint-enable prefer-rest-params */ + } + + return new RenderTexture(new BaseRenderTexture(options)); + } +} diff --git a/packages/core/src/renderTexture/RenderTextureSystem.js b/packages/core/src/renderTexture/RenderTextureSystem.js new file mode 100644 index 0000000..8d0ca61 --- /dev/null +++ b/packages/core/src/renderTexture/RenderTextureSystem.js @@ -0,0 +1,115 @@ +import System from '../System'; +import { Rectangle } from '@pixi/math'; + +const tempRect = new Rectangle(); + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class RenderTextureSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.clearColor = renderer._backgroundColorRgba; + + // TODO moe this property somewhere else! + this.defaultMaskStack = []; + this.defaultFilterStack = [{}]; + + // empty render texture? + this.renderTexture = null; + + this.destinationFrame = new Rectangle(); + } + + bind(renderTexture, sourceFrame, destinationFrame) + { + // TODO - do we want this?? + if (this.renderTexture === renderTexture) return; + this.renderTexture = renderTexture; + + const renderer = this.renderer; + + if (renderTexture) + { + const baseTexture = renderTexture.baseTexture; + + if (!destinationFrame) + { + tempRect.width = baseTexture.realWidth; + tempRect.height = baseTexture.realHeight; + + destinationFrame = tempRect; + } + + if (!sourceFrame) + { + sourceFrame = destinationFrame; + } + + this.renderer.framebuffer.bind(baseTexture.frameBuffer, destinationFrame); + + this.renderer.projection.update(destinationFrame, sourceFrame, baseTexture.resolution, false); + this.renderer.stencil.setMaskStack(baseTexture.stencilMaskStack); + } + else + { + // TODO these validation checks happen deeper down.. + // thing they can be avoided.. + if (!destinationFrame) + { + tempRect.width = renderer.width; + tempRect.height = renderer.height; + + destinationFrame = tempRect; + } + + if (!sourceFrame) + { + sourceFrame = destinationFrame; + } + + renderer.framebuffer.bind(null, destinationFrame); + + // TODO store this.. + this.renderer.projection.update(destinationFrame, sourceFrame, this.renderer.resolution, true); + this.renderer.stencil.setMaskStack(this.defaultMaskStack); + } + + this.destinationFrame.copyFrom(destinationFrame); + } + + /** + * Erases the render texture and fills the drawing area with a colour + * + * @param {number} [clearColor] - The colour + * @return {PIXI.Renderer} Returns itself. + */ + clear(clearColor) + { + if (this.renderTexture) + { + clearColor = clearColor || this.renderTexture.baseTexture.clearColor; + } + else + { + clearColor = clearColor || this.clearColor; + } + + this.renderer.framebuffer.clear(clearColor[0], clearColor[1], clearColor[2], clearColor[3]); + } + + resize()// screenWidth, screenHeight) + { + // resize the root only! + this.bind(null); + } +} diff --git a/packages/core/src/renderers/AbstractRenderer.js b/packages/core/src/renderers/AbstractRenderer.js deleted file mode 100644 index 928670f..0000000 --- a/packages/core/src/renderers/AbstractRenderer.js +++ /dev/null @@ -1,353 +0,0 @@ -import { hex2string, hex2rgb } from '@pixi/utils'; -import { Matrix, Rectangle } from '@pixi/math'; -import { RENDERER_TYPE } from '@pixi/constants'; -import { settings } from '@pixi/settings'; -import { Container } from '@pixi/display'; -import RenderTexture from '../textures/RenderTexture'; -import EventEmitter from 'eventemitter3'; - -const tempMatrix = new Matrix(); - -/** - * 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 EventEmitter - * @memberof PIXI - */ -export default class AbstractRenderer extends EventEmitter -{ - // eslint-disable-next-line valid-jsdoc - /** - * @param {string} system - The name of the system this renderer is for. - * @param {object} [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.transparent=false] - If the render view is transparent, default false - * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false - * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) - * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. The - * resolution of the renderer retina would be 2. - * @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 {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when rendering, - * stopping pixel interpolation. - */ - constructor(system, options, arg2, arg3) - { - super(); - - // Support for constructor(system, screenWidth, screenHeight, options) - if (typeof options === 'number') - { - options = Object.assign({ - width: options, - height: arg2 || settings.RENDER_OPTIONS.height, - }, arg3); - } - - // 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 = RENDERER_TYPE.UNKNOWN; - - /** - * Measurements of the screen. (0, 0, screenWidth, screenHeight) - * - * Its safe to use as filterArea or hitArea for 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 1 - */ - this.resolution = options.resolution || settings.RESOLUTION; - - /** - * Whether the render view is transparent - * - * @member {boolean} - */ - this.transparent = options.transparent; - - /** - * Whether css dimensions of canvas view should be resized to screen dimensions automatically - * - * @member {boolean} - */ - this.autoResize = options.autoResize || false; - - /** - * Tracks the blend modes useful for this renderer. - * - * @member {object} - */ - this.blendModes = null; - - /** - * 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; - - /** - * If true PixiJS will Math.floor() x/y values when rendering, stopping pixel interpolation. - * Handy for crisp pixel art and speed on legacy devices. - * - * @member {boolean} - */ - this.roundPixels = options.roundPixels; - - /** - * The background color as a number. - * - * @member {number} - * @private - */ - this._backgroundColor = 0x000000; - - /** - * The background color as an [R, G, B] array. - * - * @member {number[]} - * @private - */ - this._backgroundColorRgba = [0, 0, 0, 0]; - - /** - * The background color as a string. - * - * @member {string} - * @private - */ - this._backgroundColorString = '#000000'; - - this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter - - /** - * This temporary display object used as the parent of the currently being rendered item - * - * @member {PIXI.DisplayObject} - * @private - */ - this._tempDisplayObjectParent = new Container(); - - /** - * The last root object that the renderer tried to render. - * - * @member {PIXI.DisplayObject} - * @private - */ - this._lastObjectRendered = this._tempDisplayObjectParent; - - /** - * Collection of plugins. - * @readonly - * @member {object} - */ - this.plugins = {}; - } - - /** - * Initialize the plugins. - * - * @protected - * @param {object} staticMap - The dictionary of staticly saved plugins. - */ - initPlugins(staticMap) - { - 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() - { - return this.view.width; - } - - /** - * Same as view.height, actual number of pixels in the canvas by vertical - * - * @member {number} - * @readonly - * @default 600 - */ - get height() - { - return this.view.height; - } - - /** - * Resizes the screen and canvas to the specified width and height - * Canvas dimensions are multiplied by resolution - * - * @param {number} screenWidth - the new width of the screen - * @param {number} screenHeight - the new height of the screen - */ - resize(screenWidth, screenHeight) - { - this.screen.width = screenWidth; - this.screen.height = screenHeight; - - this.view.width = screenWidth * this.resolution; - this.view.height = screenHeight * this.resolution; - - if (this.autoResize) - { - this.view.style.width = `${screenWidth}px`; - this.view.style.height = `${screenHeight}px`; - } - } - - /** - * 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 {PIXI.DisplayObject} displayObject - The displayObject the object will be generated from - * @param {number} scaleMode - Should be one of the scaleMode consts - * @param {number} resolution - The resolution / device pixel ratio of the texture being generated - * @param {PIXI.Rectangle} [region] - The region of the displayObject, that shall be rendered, - * if no region is specified, defaults to the local bounds of the displayObject. - * @return {PIXI.Texture} a texture of the graphics object - */ - generateTexture(displayObject, scaleMode, resolution, region) - { - region = region || displayObject.getLocalBounds(); - - // 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(region.width | 0, region.height | 0, scaleMode, resolution); - - tempMatrix.tx = -region.x; - tempMatrix.ty = -region.y; - - this.render(displayObject, renderTexture, false, tempMatrix, true); - - return renderTexture; - } - - /** - * Removes everything from the renderer and optionally removes the Canvas DOM element. - * - * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. - */ - destroy(removeView) - { - 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); - } - - this.plugins = null; - - this.type = RENDERER_TYPE.UNKNOWN; - - this.view = null; - - this.screen = null; - - this.resolution = 0; - - this.transparent = false; - - this.autoResize = false; - - this.blendModes = null; - - this.options = null; - - this.preserveDrawingBuffer = false; - this.clearBeforeRender = false; - - this.roundPixels = false; - - this._backgroundColor = 0; - this._backgroundColorRgba = null; - this._backgroundColorString = null; - - this._tempDisplayObjectParent = null; - this._lastObjectRendered = null; - } - - /** - * The background color to fill if not transparent - * - * @member {number} - */ - get backgroundColor() - { - return this._backgroundColor; - } - - set backgroundColor(value) // eslint-disable-line require-jsdoc - { - this._backgroundColor = value; - this._backgroundColorString = hex2string(value); - hex2rgb(value, this._backgroundColorRgba); - } -} diff --git a/packages/core/src/renderers/Renderer.js b/packages/core/src/renderers/Renderer.js deleted file mode 100644 index a2020b1..0000000 --- a/packages/core/src/renderers/Renderer.js +++ /dev/null @@ -1,329 +0,0 @@ -import AbstractRenderer from './AbstractRenderer'; -import { sayHello } from '@pixi/utils'; -import MaskSystem from './systems/MaskSystem'; -import StencilSystem from './systems/StencilSystem'; -import FilterSystem from './systems/filter/FilterSystem'; -import FramebufferSystem from './systems/FramebufferSystem'; -import RenderTextureSystem from './systems/RenderTextureSystem'; -import TextureSystem from './systems/textures/TextureSystem'; -import ProjectionSystem from './systems/ProjectionSystem'; -import StateSystem from './systems/StateSystem'; -import GeometrySystem from './systems/geometry/GeometrySystem'; -import ShaderSystem from './systems/shader/ShaderSystem'; -import ContextSystem from './systems/ContextSystem'; -import BatchSystem from './systems/BatchSystem'; -import TextureGCSystem from './systems/textures/TextureGCSystem'; -import { RENDERER_TYPE } from '@pixi/constants'; -import UniformGroup from '../shader/UniformGroup'; -import { Matrix } from '@pixi/math'; -import Runner from 'mini-runner'; - -/** - * 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 Render works by automatically managing webGLBatchs. - * 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 :) - * - * @class - * @memberof PIXI - * @extends PIXI.AbstractRenderer - */ -export default class Renderer extends AbstractRenderer -{ - // eslint-disable-next-line valid-jsdoc - /** - * - * @param {object} [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.transparent=false] - If the render view is transparent, default false - * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false - * @param {boolean} [options.antialias=false] - sets antialias. If not available natively then FXAA - * antialiasing is used - * @param {boolean} [options.forceFXAA=false] - forces FXAA antialiasing to be used over native. - * FXAA is faster, but may not always look as great - * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. - * The resolution of the renderer retina would be 2. - * @param {boolean} [options.clearBeforeRender=true] - This sets if the renderer will clear - * the canvas or not before the new render pass. If you wish to set this to false, you *must* set - * preserveDrawingBuffer to `true`. - * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, - * enable this if you need to call toDataUrl on the webgl context. - * @param {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when - * rendering, stopping pixel interpolation. - * @param {number} [options.backgroundColor=0x000000] - The background color of the rendered area - * (shown if not transparent). - * @param {boolean} [options.legacy=false] - If true PixiJS will aim to ensure compatibility - * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. - * @param {string} [options.powerPreference] - Parameter passed to webgl context, set to "high-performance" - * for devices with dual graphics card - */ - constructor(options, arg2, arg3) - { - super('WebGL', options, arg2, arg3); - - /** - * The type of this renderer as a standardised const - * - * @member {number} - * @see PIXI.RENDERER_TYPE - */ - this.type = RENDERER_TYPE.WEBGL; - - // this will be set by the contextSystem (this.context) - this.gl = null; - this.CONTEXT_UID = 0; - this.legacy = !!options.legacy; - - // TODO legacy! - - // runners! - this.runners = { - destroy: new Runner('destroy'), - contextChange: new Runner('contextChange', 1), - reset: new Runner('reset'), - update: new Runner('update'), - postrender: new Runner('postrender'), - prerender: new Runner('prerender'), - resize: new Runner('resize', 2), - }; - - 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(GeometrySystem, 'geometry') - .addSystem(FramebufferSystem, 'framebuffer') - .addSystem(StencilSystem, 'stencil') - .addSystem(ProjectionSystem, 'projection') - .addSystem(TextureGCSystem, 'textureGC') - .addSystem(FilterSystem, 'filter') - .addSystem(RenderTextureSystem, 'renderTexture') - .addSystem(BatchSystem, 'batch'); - - this.initPlugins(Renderer.__plugins); - - /** - * The options passed in to create a new webgl context. - * - * @member {object} - * @private - */ - if (options.context) - { - this.context.initFromContext(options.context); - } - else - { - this.context.initFromOptions({ - alpha: this.transparent, - antialias: options.antialias, - premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', - stencil: true, - 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); - } - - addSystem(_class, name) - { - if (!name) - { - name = _class.name; - } - - // TODO - read name from class.name.. - - /* - if(name.includes('System')) - { - name = name.replace('System', ''); - name = name.charAt(0).toLowerCase() + name.slice(1); - } - */ - - const system = new _class(this); - - if (this[name]) - { - throw new Error(`Whoops! ${name} is already a manger`); - } - - this[name] = system; - - for (const i in this.runners) - { - this.runners[i].add(system); - } - - return this; - - /** - * 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. - */ - } - - /** - * Renders the object to its webGL view - * - * @param {PIXI.DisplayObject} displayObject - the object to be rendered - * @param {PIXI.RenderTexture} renderTexture - The render texture to render to. - * @param {boolean} [clear] - Should the canvas be cleared before the new render - * @param {PIXI.Transform} [transform] - A transform to apply to the render texture before rendering. - * @param {boolean} [skipUpdateTransform] - Should we skip the update transform pass? - */ - render(displayObject, renderTexture, clear, transform, skipUpdateTransform) - { - // can be handy to know! - this.renderingToScreen = !renderTexture; - - this.runners.prerender.run(); - this.emit('prerender'); - - // 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.parent; - - displayObject.parent = this._tempDisplayObjectParent; - displayObject.updateTransform(); - displayObject.parent = 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.run(); - - this.emit('postrender'); - } - - /** - * Resizes the webGL view to the specified width and height. - * - * @param {number} screenWidth - the new width of the screen - * @param {number} screenHeight - the new height of the screen - */ - resize(screenWidth, screenHeight) - { - AbstractRenderer.prototype.resize.call(this, screenWidth, screenHeight); - - this.runners.resize.run(screenWidth, screenHeight); - } - - /** - * Resets the WebGL state so you can render things however you fancy! - * - * @return {PIXI.Renderer} Returns itself. - */ - reset() - { - this.runners.reset.run(); - - return this; - } - - clear() - { - this.framebuffer.bind(); - this.framebuffer.clear(); - } - - /** - * Removes everything from the renderer (event listeners, spritebatch, etc...) - * - * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. - * See: https://github.com/pixijs/pixi.js/issues/2233 - */ - destroy(removeView) - { - this.runners.destroy.run(); - - // call base destroy - super.destroy(removeView); - - // TODO nullify all the managers.. - this.gl = null; - } - - /** - * 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 - * builds and excluding plugins. - * @name PIXI.Renderer#plugins - * @type {object} - * @readonly - * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. - * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. - * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. - * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. - */ - - /** - * Adds a plugin to the renderer. - * - * @method - * @param {string} pluginName - The name of the plugin. - * @param {Function} ctor - The constructor function or class for the plugin. - */ - static registerPlugin(pluginName, ctor) - { - Renderer.__plugins = Renderer.__plugins || {}; - Renderer.__plugins[pluginName] = ctor; - } -} diff --git a/packages/core/src/renderers/State.js b/packages/core/src/renderers/State.js deleted file mode 100644 index fa3f221..0000000 --- a/packages/core/src/renderers/State.js +++ /dev/null @@ -1,163 +0,0 @@ -/* eslint-disable max-len */ - -const BLEND = 0; -const OFFSET = 1; -const CULLING = 2; -const DEPTH_TEST = 3; -const WINDING = 4; - -/** - * This is a webGL state. It is passed The WebGL StateManager. - * Each mesh renderered may require webGL to be in a different state. - * For example you may want different blend mode or to enable polygon offsets - * - * @class - * @memberof PIXI - */ -export default class State -{ - /** - * - */ - constructor() - { - this.data = 0; - - this.blendMode = 0; - this.polygonOffset = 0; - - this.blend = true; - // this.depthTest = true; - } - - /** - * Activates blending of the computed fragment color values - * - * @member {boolean} - */ - get blend() - { - return !!(this.data & (1 << BLEND)); - } - - set blend(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << BLEND)) !== value) - { - this.data ^= (1 << BLEND); - } - } - - /** - * Activates adding an offset to depth values of polygon's fragments - * - * @member {boolean} - * @default false - */ - get offsets() - { - return !!(this.data & (1 << OFFSET)); - } - - set offsets(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << OFFSET)) !== value) - { - this.data ^= (1 << OFFSET); - } - } - - /** - * Activates culling of polygons. - * - * @member {boolean} - * @default false - */ - get culling() - { - return !!(this.data & (1 << CULLING)); - } - - set culling(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << CULLING)) !== value) - { - this.data ^= (1 << CULLING); - } - } - - /** - * Activates depth comparisons and updates to the depth buffer. - * - * @member {boolean} - * @default false - */ - get depthTest() - { - return !!(this.data & (1 << DEPTH_TEST)); - } - - set depthTest(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << DEPTH_TEST)) !== value) - { - this.data ^= (1 << DEPTH_TEST); - } - } - - /** - * Specifies whether or not front or back-facing polygons can be culled. - * @member {boolean} - * @default false - */ - get clockwiseFrontFace() - { - return !!(this.data & (1 << WINDING)); - } - - set clockwiseFrontFace(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << WINDING)) !== value) - { - this.data ^= (1 << WINDING); - } - } - - /** - * The blend mode to be applied when this state is set. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. - * Setting this mode to anything other than NO_BLEND will automatically switch blending on. - * - * @member {boolean} - * @default PIXI.BLEND_MODES.NORMAL - * @see PIXI.BLEND_MODES - */ - get blendMode() - { - return this._blendMode; - } - - set blendMode(value) // eslint-disable-line require-jsdoc - { - // 17 is NO BLEND - this.blend = (value !== 17); - this._blendMode = value; - } - - /** - * The polygon offset. Setting this property to anything other than 0 will automatically enable poygon offset fill. - * - * @member {number} - * @default 0 - */ - get polygonOffset() - { - return this._polygonOffset; - } - - set polygonOffset(value) // eslint-disable-line require-jsdoc - { - this.offsets = !!value; - this._polygonOffset = value; - } -} - diff --git a/packages/core/src/renderers/filters/Filter.js b/packages/core/src/renderers/filters/Filter.js deleted file mode 100644 index 2073b8d..0000000 --- a/packages/core/src/renderers/filters/Filter.js +++ /dev/null @@ -1,140 +0,0 @@ -import Shader from '../../shader/Shader'; -import Program from '../../shader/Program'; -import State from '../State'; -import { settings } from '@pixi/settings'; -// import extractUniformsFromSrc from './extractUniformsFromSrc'; -import defaultVertex from './defaultFilter.vert'; -import defaultFragment from './defaultFilter.frag'; - -// let math = require('../../math'); -/** - * @class - * @memberof PIXI - * @extends PIXI.Shader - */ -export default class Filter extends Shader -{ - /** - * @param {string} [vertexSrc] - The source of the vertex shader. - * @param {string} [fragmentSrc] - The source of the fragment shader. - * @param {object} [uniforms] - Custom uniforms to use to augment the built-in ones. - */ - constructor(vertexSrc, fragmentSrc, uniforms) - { - const program = Program.from(vertexSrc, fragmentSrc); - - super(program, uniforms); - - /** - * The padding of the filter. Some filters require extra space to breath such as a blur. - * Increasing this will add extra width and height to the bounds of the object that the - * filter is applied to. - * - * @member {number} - */ - this.padding = 0; - - /** - * The resolution of the filter. Setting this to be lower will lower the quality but - * increase the performance of the filter. - * - * @member {number} - */ - this.resolution = settings.RESOLUTION; - - /** - * If enabled is true the filter is applied, if false it will not. - * - * @member {boolean} - */ - this.enabled = true; - - /** - * If enabled, PixiJS will fit the filter area into boundaries for better performance. - * Switch it off if it does not work for specific shader. - * - * @member {boolean} - */ - this.autoFit = true; - - /** - * Legacy filters use position and uvs from attributes - * @member {boolean} - * @readonly - */ - this.legacy = !!this.program.attributeData.aTextureCoord; - - /** - * the webGL state the filter requires to render - * @member {PIXI.State} - */ - this.state = new State(); - } - - /** - * Applies the filter - * - * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from - * @param {PIXI.RenderTarget} input - The input render target. - * @param {PIXI.RenderTarget} output - The target to output to. - * @param {boolean} clear - Should the output be cleared before rendering to it - * @param {object} [currentState] - It's current state of filter. - * There are some useful properties in the currentState : - * target, filters, sourceFrame, destinationFrame, renderTarget, resolution - */ - apply(filterManager, input, output, clear, currentState, derp) // eslint-disable-line no-unused-vars - { - // do as you please! - - filterManager.applyFilter(this, input, output, clear, currentState, derp); - - // or just do a regular render.. - } - - /** - * Sets the blendmode of the filter - * - * @member {number} - * @default PIXI.BLEND_MODES.NORMAL - */ - get blendMode() - { - return this.state.blendMode; - } - - set blendMode(value) // eslint-disable-line require-jsdoc - { - this.state.blendMode = value; - } - - /** - * The default vertex shader source - * - * @static - * @constant - */ - static get defaultVertexSrc() - { - return defaultVertex; - } - - /** - * The default fragment shader source - * - * @static - * @constant - */ - static get defaultFragmentSrc() - { - return defaultFragment; - } -} - -/** - * Used for caching shader IDs - * - * @static - * @private - */ -Filter.SOURCE_KEY_MAP = {}; - diff --git a/packages/core/src/renderers/filters/defaultFilter.frag b/packages/core/src/renderers/filters/defaultFilter.frag deleted file mode 100644 index a14c1be..0000000 --- a/packages/core/src/renderers/filters/defaultFilter.frag +++ /dev/null @@ -1,22 +0,0 @@ -varying vec2 vTextureCoord; -varying vec2 vFilterCoord; - -uniform sampler2D uSampler; -uniform sampler2D filterSampler; - -void main(void){ - vec4 masky = texture2D(filterSampler, vFilterCoord); - vec4 sample = texture2D(uSampler, vTextureCoord); - vec4 color; - if(mod(vFilterCoord.x, 1.0) > 0.5) - { - color = vec4(1.0, 0.0, 0.0, 1.0); - } - else - { - color = vec4(0.0, 1.0, 0.0, 1.0); - } - // gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0); - gl_FragColor = mix(sample, masky, 0.5); - gl_FragColor *= sample.a; -} diff --git a/packages/core/src/renderers/filters/defaultFilter.vert b/packages/core/src/renderers/filters/defaultFilter.vert deleted file mode 100644 index e1febca..0000000 --- a/packages/core/src/renderers/filters/defaultFilter.vert +++ /dev/null @@ -1,14 +0,0 @@ -attribute vec2 aVertexPosition; -attribute vec2 aTextureCoord; - -uniform mat3 projectionMatrix; -uniform mat3 filterMatrix; - -varying vec2 vTextureCoord; -varying vec2 vFilterCoord; - -void main(void){ - gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); - vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy; - vTextureCoord = aTextureCoord ; -} diff --git a/packages/core/src/renderers/filters/filterTransforms.js b/packages/core/src/renderers/filters/filterTransforms.js deleted file mode 100644 index 87a3cd6..0000000 --- a/packages/core/src/renderers/filters/filterTransforms.js +++ /dev/null @@ -1,50 +0,0 @@ -import { Matrix } from '@pixi/math'; - -/** - * Calculates the mapped matrix - * @param filterArea {Rectangle} The filter area - * @param sprite {Sprite} the target sprite - * @param outputMatrix {Matrix} @alvin - * @private - */ -// this returns a matrix that will normalise map filter cords in the filter to screen space -export function calculateScreenSpaceMatrix(outputMatrix, filterArea, textureSize) -{ - // TODO unwrap? - const mappedMatrix = outputMatrix.identity(); - - mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); - - mappedMatrix.scale(textureSize.width, textureSize.height); - - return mappedMatrix; -} - -export function calculateNormalizedScreenSpaceMatrix(outputMatrix, filterArea, textureSize) -{ - const mappedMatrix = outputMatrix.identity(); - - mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); - - const translateScaleX = (textureSize.width / filterArea.width); - const translateScaleY = (textureSize.height / filterArea.height); - - mappedMatrix.scale(translateScaleX, translateScaleY); - - return mappedMatrix; -} - -// this will map the filter coord so that a texture can be used based on the transform of a sprite -export function calculateSpriteMatrix(outputMatrix, filterArea, textureSize, sprite) -{ - const orig = sprite._texture.orig; - const mappedMatrix = outputMatrix.set(textureSize.width, 0, 0, textureSize.height, filterArea.x, filterArea.y); - const worldTransform = sprite.worldTransform.copyTo(Matrix.TEMP_MATRIX); - - worldTransform.invert(); - mappedMatrix.prepend(worldTransform); - mappedMatrix.scale(1.0 / orig.width, 1.0 / orig.height); - mappedMatrix.translate(sprite.anchor.x, sprite.anchor.y); - - return mappedMatrix; -} diff --git a/packages/core/src/renderers/filters/spriteMask/SpriteMaskFilter.js b/packages/core/src/renderers/filters/spriteMask/SpriteMaskFilter.js deleted file mode 100644 index d5a91a4..0000000 --- a/packages/core/src/renderers/filters/spriteMask/SpriteMaskFilter.js +++ /dev/null @@ -1,63 +0,0 @@ -import Filter from '../Filter'; -import { Matrix } from '@pixi/math'; -import vertex from './spriteMaskFilter.vert'; -import fragment from './spriteMaskFilter.frag'; -import { default as TextureMatrix } from '../../../textures/TextureMatrix'; - -/** - * The SpriteMaskFilter class - * - * @class - * @extends PIXI.Filter - * @memberof PIXI - */ -export default class SpriteMaskFilter extends Filter -{ - /** - * @param {PIXI.Sprite} sprite - the target sprite - */ - constructor(sprite) - { - const maskMatrix = new Matrix(); - - super(vertex, fragment); - - sprite.renderable = false; - - this.maskSprite = sprite; - this.maskMatrix = maskMatrix; - } - - /** - * Applies the filter - * - * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from - * @param {PIXI.RenderTarget} input - The input render target. - * @param {PIXI.RenderTarget} output - The target to output to. - */ - apply(filterManager, input, output) - { - const maskSprite = this.maskSprite; - const tex = this.maskSprite.texture; - - if (!tex.valid) - { - return; - } - if (!tex.transform) - { - // margin = 0.0, let it bleed a bit, shader code becomes easier - // assuming that atlas textures were made with 1-pixel padding - tex.transform = new TextureMatrix(tex, 0.0); - } - tex.transform.update(); - - this.uniforms.mask = tex; - this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite) - .prepend(tex.transform.mapCoord); - this.uniforms.alpha = maskSprite.worldAlpha; - this.uniforms.maskClamp = tex.transform.uClampFrame; - - filterManager.applyFilter(this, input, output); - } -} diff --git a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.frag b/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.frag deleted file mode 100644 index 0e4aef8..0000000 --- a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.frag +++ /dev/null @@ -1,23 +0,0 @@ -varying vec2 vMaskCoord; -varying vec2 vTextureCoord; - -uniform sampler2D uSampler; -uniform sampler2D mask; -uniform float alpha; -uniform vec4 maskClamp; - -void main(void) -{ - float clip = step(3.5, - step(maskClamp.x, vMaskCoord.x) + - step(maskClamp.y, vMaskCoord.y) + - step(vMaskCoord.x, maskClamp.z) + - step(vMaskCoord.y, maskClamp.w)); - - vec4 original = texture2D(uSampler, vTextureCoord); - vec4 masky = texture2D(mask, vMaskCoord); - - original *= (masky.r * masky.a * alpha * clip); - - gl_FragColor = original; -} diff --git a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.vert b/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.vert deleted file mode 100644 index d23a30c..0000000 --- a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.vert +++ /dev/null @@ -1,16 +0,0 @@ -attribute vec2 aVertexPosition; -attribute vec2 aTextureCoord; - -uniform mat3 projectionMatrix; -uniform mat3 otherMatrix; - -varying vec2 vMaskCoord; -varying vec2 vTextureCoord; - -void main(void) -{ - gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); - - vTextureCoord = aTextureCoord; - vMaskCoord = ( otherMatrix * vec3( aTextureCoord, 1.0) ).xy; -} diff --git a/packages/core/src/renderers/systems/BatchSystem.js b/packages/core/src/renderers/systems/BatchSystem.js deleted file mode 100644 index 70e9518..0000000 --- a/packages/core/src/renderers/systems/BatchSystem.js +++ /dev/null @@ -1,68 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import ObjectRenderer from '../utils/ObjectRenderer'; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ - -export default class BatchSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this manager works for. - */ - constructor(renderer) - { - super(renderer); - - /** - * An empty renderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.emptyRenderer = new ObjectRenderer(renderer); - - /** - * The currently active ObjectRenderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.currentRenderer = this.emptyRenderer; - } - - /** - * Changes the current renderer to the one given in parameter - * - * @param {PIXI.ObjectRenderer} objectRenderer - The object renderer to use. - */ - setObjectRenderer(objectRenderer) - { - if (this.currentRenderer === objectRenderer) - { - return; - } - - this.currentRenderer.stop(); - this.currentRenderer = objectRenderer; - - this.renderer.state.setState(objectRenderer.state); - - this.currentRenderer.start(); - } - - /** - * This should be called if you wish to do some custom rendering - * It will basically render anything that may be batched up such as sprites - * - */ - flush() - { - this.setObjectRenderer(this.emptyRenderer); - } - - reset() - { - this.setObjectRenderer(this.emptyRenderer); - } -} diff --git a/packages/core/src/renderers/systems/ContextSystem.js b/packages/core/src/renderers/systems/ContextSystem.js deleted file mode 100644 index 43bf827..0000000 --- a/packages/core/src/renderers/systems/ContextSystem.js +++ /dev/null @@ -1,186 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { settings } from '@pixi/settings'; - -let CONTEXT_UID = 0; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class ContextSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this.webGLVersion = 1; - - this.handleContextLost = this.handleContextLost.bind(this); - this.handleContextRestored = this.handleContextRestored.bind(this); - - this.extensions = {}; - - renderer.view.addEventListener('webglcontextlost', this.handleContextLost, false); - renderer.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); - } - - get isLost() - { - return (!this.gl || this.gl.isContextLost()); - } - - contextChange(gl) - { - this.gl = gl; - - // restore a context if it was previously lost - if (gl.isContextLost() && gl.getExtension('WEBGL_lose_context')) - { - gl.getExtension('WEBGL_lose_context').restoreContext(); - } - } - - initFromContext(gl) - { - this.gl = gl; - this.validateContext(gl); - this.renderer.gl = gl; - this.renderer.CONTEXT_UID = CONTEXT_UID++; - this.renderer.runners.contextChange.run(gl); - } - - initFromOptions(options) - { - const gl = this.createContext(this.renderer.view, options); - - this.initFromContext(gl); - } - - /** - * Helper class to create a webGL Context - * - * @class - * @memberof PIXI.glCore - * @param canvas {HTMLCanvasElement} the canvas element that we will get the context from - * @param options {Object} An options object that gets passed in to the canvas element containing the context attributes, - * see https://developer.mozilla.org/en/docs/Web/API/HTMLCanvasElement/getContext for the options available - * @return {WebGLRenderingContext} the WebGL context - */ - createContext(canvas, options) - { - let gl; - - if (settings.PREFER_WEBGL_2) - { - gl = canvas.getContext('webgl2', options); - } - - if (gl) - { - this.webGLVersion = 2; - } - else - { - this.webGLVersion = 1; - - gl = canvas.getContext('webgl', options) - || canvas.getContext('experimental-webgl', options); - - if (!gl) - { - // fail, not able to get a context - throw new Error('This browser does not support webGL. Try using the canvas renderer'); - } - } - - this.gl = gl; - - this.getExtensions(); - - return gl; - } - - getExtensions() - { - // time to set up default etensions that pixi uses.. - const gl = this.gl; - const extensions = this.extensions; - - if (this.webGLVersion === 1) - { - extensions.drawBuffers = gl.getExtension('WEBGL_draw_buffers'); - extensions.depthTexture = gl.getExtension('WEBKIT_WEBGL_depth_texture'); - extensions.floatTexture = gl.getExtension('OES_texture_float'); - extensions.loseContext = gl.getExtension('WEBGL_lose_context'); - - extensions.vertexArrayObject = gl.getExtension('OES_vertex_array_object') - || gl.getExtension('MOZ_OES_vertex_array_object') - || gl.getExtension('WEBKIT_OES_vertex_array_object'); - } - - // we don't use any specific WebGL 2 ones yet! - } - - /** - * Handles a lost webgl context - * - * @private - * @param {WebGLContextEvent} event - The context lost event. - */ - handleContextLost(event) - { - event.preventDefault(); - } - - /** - * Handles a restored webgl context - * - * @private - */ - handleContextRestored() - { - this.renderer.runners.contextChange.run(this.gl); - } - - destroy() - { - const view = this.renderer.view; - - // remove listeners - view.removeEventListener('webglcontextlost', this.handleContextLost); - view.removeEventListener('webglcontextrestored', this.handleContextRestored); - - this.gl.useProgram(null); - - if (this.extensions.loseContext) - { - this.extensions.loseContext.loseContext(); - } - } - - postrender() - { - this.gl.flush(); - } - - validateContext(gl) - { - const attributes = gl.getContextAttributes(); - - // this is going to be fairly simple for now.. but at least we have room to grow! - if (!attributes.stencil) - { - /* eslint-disable max-len */ - - /* eslint-disable no-console */ - console.warn('Provided WebGL context does not have a stencil buffer, masks may not render correctly'); - /* eslint-enable no-console */ - - /* eslint-enable max-len */ - } - } -} diff --git a/packages/core/src/renderers/systems/FramebufferSystem.js b/packages/core/src/renderers/systems/FramebufferSystem.js deleted file mode 100644 index df7902b..0000000 --- a/packages/core/src/renderers/systems/FramebufferSystem.js +++ /dev/null @@ -1,246 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { Rectangle } from '@pixi/math'; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class FramebufferSystem extends WebGLSystem -{ - /** - * Sets up the renderer context and necessary buffers. - * - * @private - */ - contextChange() - { - this.gl = this.renderer.gl; - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - this.current = null; - this.viewport = new Rectangle(); - - this.drawBufferExtension = this.renderer.context.extensions.drawBuffers; - } - - bind(framebuffer, frame) - { - const gl = this.gl; - - this.current = framebuffer; - - if (framebuffer) - { - // TODO cacheing layer! - - const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID] || this.initFramebuffer(framebuffer); - - gl.bindFramebuffer(gl.FRAMEBUFFER, fbo.framebuffer); - // makesure all textures are unbound.. - - // now check for updates... - if (fbo.dirtyId !== framebuffer.dirtyId) - { - fbo.dirtyId = framebuffer.dirtyId; - - if (fbo.dirtyFormat !== framebuffer.dirtyFormat) - { - fbo.dirtyFormat = framebuffer.dirtyFormat; - this.updateFramebuffer(framebuffer); - } - else if (fbo.dirtySize !== framebuffer.dirtySize) - { - fbo.dirtySize = framebuffer.dirtySize; - this.resizeFramebuffer(framebuffer); - } - } - - for (let i = 0; i < framebuffer.colorTextures.length; i++) - { - if (framebuffer.colorTextures[i].texturePart) - { - this.renderer.texture.unbind(framebuffer.colorTextures[i].texture); - } - else - { - this.renderer.texture.unbind(framebuffer.colorTextures[i]); - } - } - - if (framebuffer.depthTexture) - { - this.renderer.texture.unbind(framebuffer.depthTexture); - } - - if (frame) - { - this.setViewport(frame.x, frame.y, frame.width, frame.height); - } - else - { - this.setViewport(0, 0, framebuffer.width, framebuffer.height); - } - } - else - { - gl.bindFramebuffer(gl.FRAMEBUFFER, null); - - if (frame) - { - this.setViewport(frame.x, frame.y, frame.width, frame.height); - } - else - { - this.setViewport(0, 0, this.renderer.width, this.renderer.height); - } - } - } - - setViewport(x, y, width, height) - { - const v = this.viewport; - - if (v.width !== width || v.height !== height || v.x !== x || v.y !== y) - { - v.x = x; - v.y = y; - v.width = width; - v.height = height; - - this.gl.viewport(x, y, width, height); - } - } - - get size() - { - if (this.current) - { - // TODO store temp - return { x: 0, y: 0, width: this.current.width, height: this.current.height }; - } - - return { x: 0, y: 0, width: this.renderer.width, height: this.renderer.height }; - } - - clear(r, g, b, a) - { - const gl = this.gl; - - // TODO clear color can be set only one right? - gl.clearColor(r, g, b, a); - gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); - } - - // private functions... - - initFramebuffer(framebuffer) - { - const gl = this.gl; - - // TODO - make this a class? - const fbo = { - framebuffer: gl.createFramebuffer(), - stencil: null, - dirtyId: 0, - dirtyFormat: 0, - dirtySize: 0, - }; - - framebuffer.glFrameBuffers[this.CONTEXT_UID] = fbo; - - return fbo; - } - - resizeFramebuffer(framebuffer) - { - const gl = this.gl; - - if (framebuffer.stencil || framebuffer.depth) - { - gl.bindRenderbuffer(gl.RENDERBUFFER, this.stencil); - gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); - } - } - - updateFramebuffer(framebuffer) - { - const gl = this.gl; - - const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID]; - - // bind the color texture - const colorTextures = framebuffer.colorTextures; - - let count = colorTextures.length; - - if (!this.drawBufferExtension) - { - count = Math.min(count, 1); - } - - const activeTextures = []; - - for (let i = 0; i < count; i++) - { - const texture = framebuffer.colorTextures[i]; - - if (texture.texturePart) - { - this.renderer.texture.bind(texture.texture, 0); - - gl.framebufferTexture2D(gl.FRAMEBUFFER, - gl.COLOR_ATTACHMENT0 + i, - gl.TEXTURE_CUBE_MAP_NEGATIVE_X + texture.side, - texture.texture._glTextures[this.CONTEXT_UID].texture, - 0); - } - else - { - this.renderer.texture.bind(texture, 0); - - gl.framebufferTexture2D(gl.FRAMEBUFFER, - gl.COLOR_ATTACHMENT0 + i, - gl.TEXTURE_2D, - texture._glTextures[this.CONTEXT_UID].texture, - 0); - } - - activeTextures.push(gl.COLOR_ATTACHMENT0 + i); - } - - if (this.drawBufferExtension && activeTextures.length > 1) - { - this.drawBufferExtension.drawBuffersWEBGL(activeTextures); - } - - if (framebuffer.depthTexture) - { - const depthTextureExt = this.renderer.context.extensions.depthTexture; - - if (depthTextureExt) - { - const depthTexture = framebuffer.depthTexture; - - this.renderer.texture.bind(depthTexture, 0); - - gl.framebufferTexture2D(gl.FRAMEBUFFER, - gl.DEPTH_ATTACHMENT, - gl.TEXTURE_2D, - depthTexture._glTextures[this.CONTEXT_UID].texture, - 0); - } - } - - if (framebuffer.stencil || framebuffer.depth) - { - fbo.stencil = gl.createRenderbuffer(); - - gl.bindRenderbuffer(gl.RENDERBUFFER, fbo.stencil); - - // TODO.. this is depth AND stencil? - gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, fbo.stencil); - gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); - // fbo.enableStencil(); - } - } -} diff --git a/packages/core/src/renderers/systems/MaskSystem.js b/packages/core/src/renderers/systems/MaskSystem.js deleted file mode 100644 index d9c4b1a..0000000 --- a/packages/core/src/renderers/systems/MaskSystem.js +++ /dev/null @@ -1,199 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import AlphaMaskFilter from '../filters/spriteMask/SpriteMaskFilter'; - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class MaskSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - // TODO - we don't need both! - this.scissor = false; - this.scissorData = null; - this.scissorRenderTarget = null; - - this.enableScissor = false; - - this.alphaMaskPool = []; - this.alphaMaskIndex = 0; - } - - /** - * Applies the Mask and adds it to the current filter stack. - * - * @param {PIXI.DisplayObject} target - Display Object to push the mask to - * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. - */ - push(target, maskData) - { - // TODO the root check means scissor rect will not - // be used on render textures more info here: - // https://github.com/pixijs/pixi.js/pull/3545 - - if (maskData.texture) - { - this.pushSpriteMask(target, maskData); - } - else if (this.enableScissor - && !this.scissor - && this.renderer._activeRenderTarget.root - && !this.renderer.stencil.stencilMaskStack.length - && maskData.isFastRect()) - { - const matrix = maskData.worldTransform; - - let rot = Math.atan2(matrix.b, matrix.a); - - // use the nearest degree! - rot = Math.round(rot * (180 / Math.PI)); - - if (rot % 90) - { - this.pushStencilMask(maskData); - } - else - { - this.pushScissorMask(target, maskData); - } - } - else - { - this.pushStencilMask(maskData); - } - } - - /** - * Removes the last mask from the mask stack and doesn't return it. - * - * @param {PIXI.DisplayObject} target - Display Object to pop the mask from - * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. - */ - pop(target, maskData) - { - if (maskData.texture) - { - this.popSpriteMask(target, maskData); - } - else if (this.enableScissor && !this.renderer.stencil.stencilMaskStack.length) - { - this.popScissorMask(target, maskData); - } - else - { - this.popStencilMask(target, maskData); - } - } - - /** - * Applies the Mask and adds it to the current filter stack. - * - * @param {PIXI.RenderTarget} target - Display Object to push the sprite mask to - * @param {PIXI.Sprite} maskData - Sprite to be used as the mask - */ - pushSpriteMask(target, maskData) - { - let alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; - - if (!alphaMaskFilter) - { - alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; - } - - alphaMaskFilter[0].resolution = this.renderer.resolution; - alphaMaskFilter[0].maskSprite = maskData; - - // TODO - may cause issues! - target.filterArea = maskData.getBounds(true); - - this.renderer.filterSystem.pushFilter(target, alphaMaskFilter); - - this.alphaMaskIndex++; - } - - /** - * Removes the last filter from the filter stack and doesn't return it. - * - */ - popSpriteMask() - { - this.renderer.filterSystem.popFilter(); - this.alphaMaskIndex--; - } - - /** - * Applies the Mask and adds it to the current filter stack. - * - * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. - */ - pushStencilMask(maskData) - { - this.renderer.batch.flush(); - this.renderer.stencil.pushStencil(maskData); - } - - /** - * Removes the last filter from the filter stack and doesn't return it. - * - */ - popStencilMask() - { - // this.renderer.currentRenderer.stop(); - this.renderer.stencil.popStencil(); - } - - /** - * - * @param {PIXI.DisplayObject} target - Display Object to push the mask to - * @param {PIXI.Graphics} maskData - The masking data. - */ - pushScissorMask(target, maskData) - { - maskData.renderable = true; - - const renderTarget = this.renderer._activeRenderTarget; - - const bounds = maskData.getBounds(); - - bounds.fit(renderTarget.size); - maskData.renderable = false; - - this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); - - const resolution = this.renderer.resolution; - - this.renderer.gl.scissor( - bounds.x * resolution, - (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, - bounds.width * resolution, - bounds.height * resolution - ); - - this.scissorRenderTarget = renderTarget; - this.scissorData = maskData; - this.scissor = true; - } - - /** - * - * - */ - popScissorMask() - { - this.scissorRenderTarget = null; - this.scissorData = null; - this.scissor = false; - - // must be scissor! - const gl = this.renderer.gl; - - gl.disable(gl.SCISSOR_TEST); - } -} diff --git a/packages/core/src/renderers/systems/ProjectionSystem.js b/packages/core/src/renderers/systems/ProjectionSystem.js deleted file mode 100644 index ec5efdb..0000000 --- a/packages/core/src/renderers/systems/ProjectionSystem.js +++ /dev/null @@ -1,73 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { Matrix } from '@pixi/math'; - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ - -export default class ProjectionSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this.projectionMatrix = new Matrix(); - } - - update(destinationFrame, sourceFrame, resolution, root) - { - this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; - this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; - - this.calculateProjection(this.destinationFrame, this.sourceFrame, resolution, root); - - this.renderer.globalUniforms.uniforms.projectionMatrix = this.projectionMatrix; - this.renderer.globalUniforms.update(); - } - - /** - * Updates the projection matrix based on a projection frame (which is a rectangle) - * - * @param {Rectangle} destinationFrame - The destination frame. - * @param {Rectangle} sourceFrame - The source frame. - */ - calculateProjection(destinationFrame, sourceFrame, resolution, root) - { - const pm = this.projectionMatrix; - - // I don't think we will need this line.. - // pm.identity(); - - if (!root) - { - pm.a = (1 / destinationFrame.width * 2) * resolution; - pm.d = (1 / destinationFrame.height * 2) * resolution; - - pm.tx = -1 - (sourceFrame.x * pm.a); - pm.ty = -1 - (sourceFrame.y * pm.d); - } - else - { - pm.a = (1 / destinationFrame.width * 2) * resolution; - pm.d = (-1 / destinationFrame.height * 2) * resolution; - - pm.tx = -1 - (sourceFrame.x * pm.a); - pm.ty = 1 - (sourceFrame.y * pm.d); - } - } - - /** - * Sets the transform of the active render target to the given matrix - * - * @param {PIXI.Matrix} matrix - The transformation matrix - */ - setTransform()// matrix) - { - // this._activeRenderTarget.transform = matrix; - } -} diff --git a/packages/core/src/renderers/systems/RenderTextureSystem.js b/packages/core/src/renderers/systems/RenderTextureSystem.js deleted file mode 100644 index a133ef1..0000000 --- a/packages/core/src/renderers/systems/RenderTextureSystem.js +++ /dev/null @@ -1,115 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { Rectangle } from '@pixi/math'; - -const tempRect = new Rectangle(); - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ - -export default class RenderTextureSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this.clearColor = renderer._backgroundColorRgba; - - // TODO moe this property somewhere else! - this.defaultMaskStack = []; - this.defaultFilterStack = [{}]; - - // empty render texture? - this.renderTexture = null; - - this.destinationFrame = new Rectangle(); - } - - bind(renderTexture, sourceFrame, destinationFrame) - { - // TODO - do we want this?? - if (this.renderTexture === renderTexture) return; - this.renderTexture = renderTexture; - - const renderer = this.renderer; - - if (renderTexture) - { - const baseTexture = renderTexture.baseTexture; - - if (!destinationFrame) - { - tempRect.width = baseTexture.realWidth; - tempRect.height = baseTexture.realHeight; - - destinationFrame = tempRect; - } - - if (!sourceFrame) - { - sourceFrame = destinationFrame; - } - - this.renderer.framebuffer.bind(baseTexture.frameBuffer, destinationFrame); - - this.renderer.projection.update(destinationFrame, sourceFrame, baseTexture.resolution, false); - this.renderer.stencil.setMaskStack(baseTexture.stencilMaskStack); - } - else - { - // TODO these validation checks happen deeper down.. - // thing they can be avoided.. - if (!destinationFrame) - { - tempRect.width = renderer.width; - tempRect.height = renderer.height; - - destinationFrame = tempRect; - } - - if (!sourceFrame) - { - sourceFrame = destinationFrame; - } - - renderer.framebuffer.bind(null, destinationFrame); - - // TODO store this.. - this.renderer.projection.update(destinationFrame, sourceFrame, this.renderer.resolution, true); - this.renderer.stencil.setMaskStack(this.defaultMaskStack); - } - - this.destinationFrame.copyFrom(destinationFrame); - } - - /** - * Erases the render texture and fills the drawing area with a colour - * - * @param {number} [clearColor] - The colour - * @return {PIXI.Renderer} Returns itself. - */ - clear(clearColor) - { - if (this.renderTexture) - { - clearColor = clearColor || this.renderTexture.baseTexture.clearColor; - } - else - { - clearColor = clearColor || this.clearColor; - } - - this.renderer.framebuffer.clear(clearColor[0], clearColor[1], clearColor[2], clearColor[3]); - } - - resize()// screenWidth, screenHeight) - { - // resize the root only! - this.bind(null); - } -} diff --git a/packages/core/src/renderers/systems/StateSystem.js b/packages/core/src/renderers/systems/StateSystem.js deleted file mode 100755 index d7f4caf..0000000 --- a/packages/core/src/renderers/systems/StateSystem.js +++ /dev/null @@ -1,288 +0,0 @@ -import mapWebGLBlendModesToPixi from '../utils/mapWebGLBlendModesToPixi'; -import WebGLSystem from './WebGLSystem'; -import WebGLState from '../State'; - -const BLEND = 0; -const OFFSET = 1; -const CULLING = 2; -const DEPTH_TEST = 3; -const WINDING = 4; - -/** - * A WebGL state machines - * - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class StateSystem extends WebGLSystem -{ - /** - * @param {WebGLRenderingContext} gl - The current WebGL rendering context - */ - constructor(renderer) - { - super(renderer); - - this.gl = null; - - this.maxAttribs = null; - - // check we have vao.. - this.nativeVaoExtension = null; - - this.attribState = null; - - this.stateId = 0; - this.polygonOffset = 0; - this.blendMode = 17; - - this.map = []; - - // map functions for when we set state.. - this.map[BLEND] = this.setBlend; - this.map[OFFSET] = this.setOffset; - this.map[CULLING] = this.setCullFace; - this.map[DEPTH_TEST] = this.setDepthTest; - this.map[WINDING] = this.setFrontFace; - - this.checks = []; - - this.defaultState = new WebGLState(); - this.defaultState.blend = true; - this.defaultState.depth = true; - } - - contextChange(gl) - { - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; - - this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); - - // check we have vao.. - this.nativeVaoExtension = ( - gl.getExtension('OES_vertex_array_object') - || gl.getExtension('MOZ_OES_vertex_array_object') - || gl.getExtension('WEBKIT_OES_vertex_array_object') - ); - - this.attribState = { - tempAttribState: new Array(this.maxAttribs), - attribState: new Array(this.maxAttribs), - }; - - this.blendModes = mapWebGLBlendModesToPixi(gl); - - this.setState(this.defaultState); - - this.reset(); - } - - /** - * Sets the current state - * - * @param {*} state - The state to set. - */ - setState(state) - { - state = state || this.defaultState; - - // TODO maybe to an object check? ( this.state === state )? - if (this.stateId !== state.data) - { - let diff = this.stateId ^ state.data; - let i = 0; - - // order from least to most common - while (diff) - { - if (diff & 1) - { - // state change! - this.map[i].call(this, !!(state.data & (1 << i))); - } - - diff = diff >> 1; - i++; - } - - this.stateId = state.data; - } - - // based on the above settings we check for specific modes.. - // for example if blend is active we check and set the blend modes - // or of polygon offset is active we check the poly depth. - for (let i = 0; i < this.checks.length; i++) - { - this.checks[i](this, state); - } - } - - /** - * Enables or disabled blending. - * - * @param {boolean} value - Turn on or off webgl blending. - */ - setBlend(value) - { - this.updateCheck(StateSystem.checkBlendMode, value); - - this.gl[value ? 'enable' : 'disable'](this.gl.BLEND); - } - - /** - * Enables or disable polygon offset fill - * - * @param {boolean} value - Turn on or off webgl polygon offset testing. - */ - setOffset(value) - { - this.gl[value ? 'enable' : 'disable'](this.gl.POLYGON_OFFSET_FILL); - } - - /** - * Sets whether to enable or disable depth test. - * - * @param {boolean} value - Turn on or off webgl depth testing. - */ - setDepthTest(value) - { - this.gl[value ? 'enable' : 'disable'](this.gl.DEPTH_TEST); - } - - /** - * Sets whether to enable or disable cull face. - * - * @param {boolean} value - Turn on or off webgl cull face. - */ - setCullFace(value) - { - this.gl[value ? 'enable' : 'disable'](this.gl.CULL_FACE); - } - - /** - * Sets the gl front face. - * - * @param {boolean} value - true is clockwise and false is counter-clockwise - */ - setFrontFace(value) - { - this.gl.frontFace(this.gl[value ? 'CW' : 'CCW']); - } - - /** - * Sets the blend mode. - * - * @param {number} value - The blend mode to set to. - */ - setBlendMode(value) - { - if (value === this.blendMode) - { - return; - } - - this.blendMode = value; - this.gl.blendFunc(this.blendModes[value][0], this.blendModes[value][1]); - } - - /** - * Sets the polygon offset. - * - * @param {number} value - the polygon offset - * @param {number} scale - the polygon offset scale - */ - setPolygonOffset(value, scale) - { - this.gl.polygonOffset(value, scale); - } - - /** - * Disables all the vaos in use - * - */ - resetAttributes() - { - for (let i = 0; i < this.attribState.tempAttribState.length; i++) - { - this.attribState.tempAttribState[i] = 0; - } - - for (let i = 0; i < this.attribState.attribState.length; i++) - { - this.attribState.attribState[i] = 0; - } - - // im going to assume one is always active for performance reasons. - for (let i = 1; i < this.maxAttribs; i++) - { - this.gl.disableVertexAttribArray(i); - } - } - - // used - /** - * Resets all the logic and disables the vaos - */ - reset() - { - // unbind any VAO if they exist.. - if (this.nativeVaoExtension) - { - this.nativeVaoExtension.bindVertexArrayOES(null); - } - - // reset all attributes.. - this.resetAttributes(); - - this.gl.pixelStorei(this.gl.UNPACK_FLIP_Y_WEBGL, false); - - this.setBlendMode(0); - - // TODO? - // this.setState(this.defaultState); - } - - /** - * checks to see which updates should be checked based on which settings have been activated - * for example if blend is enabled then we shold check the blend modes each time the state is changed - * or if poygon fill is activated then we need to check if the polygone offset changes. - * The idea is that we only check what we have too. - * - * @param {Function} func the checking function to add or remove - * @param {boolean} value should the check function be added or removed. - */ - updateCheck(func, value) - { - const index = this.checks.indexOf(func); - - if (value && index === -1) - { - this.checks.push(func); - } - else if (!value && index !== -1) - { - this.checks.splice(index, 1); - } - } - - /** - * A private little wrapper function that we call to check the blend mode. - * - * @static - * @private - * @param {PIXI.StateSystem} System the System to perform the state check on - * @param {PIXI.State} state the state that the blendMode will pulled from - */ - static checkBlendMode(system, state) - { - system.setBlendMode(state.blendMode); - } - - // TODO - add polygon offset? -} diff --git a/packages/core/src/renderers/systems/StencilSystem.js b/packages/core/src/renderers/systems/StencilSystem.js deleted file mode 100644 index 5d538cb..0000000 --- a/packages/core/src/renderers/systems/StencilSystem.js +++ /dev/null @@ -1,133 +0,0 @@ -import WebGLSystem from './WebGLSystem'; - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class StencilSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - this.stencilMaskStack = []; - } - - /** - * Changes the mask stack that is used by this System. - * - * @param {PIXI.Graphics[]} stencilMaskStack - The mask stack - */ - setMaskStack(stencilMaskStack) - { - const gl = this.renderer.gl; - - if (stencilMaskStack.length !== this.stencilMaskStack.length) - { - if (stencilMaskStack.length === 0) - { - gl.disable(gl.STENCIL_TEST); - } - else - { - gl.enable(gl.STENCIL_TEST); - } - } - - this.stencilMaskStack = stencilMaskStack; - } - - /** - * Applies the Mask and adds it to the current stencil stack. @alvin - * - * @param {PIXI.Graphics} graphics - The mask - */ - pushStencil(graphics) - { - this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); - - // this.renderer._activeRenderTarget.attachStencilBuffer(); - - const gl = this.renderer.gl; - const prevMaskCount = this.stencilMaskStack.length; - - if (prevMaskCount === 0) - { - gl.enable(gl.STENCIL_TEST); - } - - this.stencilMaskStack.push(graphics); - - // Increment the refference stencil value where the new mask overlaps with the old ones. - gl.colorMask(false, false, false, false); - gl.stencilFunc(gl.EQUAL, prevMaskCount, this._getBitwiseMask()); - gl.stencilOp(gl.KEEP, gl.KEEP, gl.INCR); - this.renderer.plugins.graphics.render(graphics); - - this._useCurrent(); - } - - /** - * Removes the last mask from the stencil stack. @alvin - */ - popStencil() - { - this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); - - const gl = this.renderer.gl; - const graphics = this.stencilMaskStack.pop(); - - if (this.stencilMaskStack.length === 0) - { - // the stack is empty! - gl.disable(gl.STENCIL_TEST); - gl.clear(gl.STENCIL_BUFFER_BIT); - gl.clearStencil(0); - } - else - { - // Decrement the refference stencil value where the popped mask overlaps with the other ones - gl.colorMask(false, false, false, false); - gl.stencilOp(gl.KEEP, gl.KEEP, gl.DECR); - this.renderer.plugins.graphics.render(graphics); - - this._useCurrent(); - } - } - - /** - * Setup renderer to use the current stencil data. - */ - _useCurrent() - { - const gl = this.renderer.gl; - - gl.colorMask(true, true, true, true); - gl.stencilFunc(gl.EQUAL, this.stencilMaskStack.length, this._getBitwiseMask()); - gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP); - } - - /** - * Fill 1s equal to the number of acitve stencil masks. - * - * @return {number} The bitwise mask. - */ - _getBitwiseMask() - { - return (1 << this.stencilMaskStack.length) - 1; - } - - /** - * Destroys the mask stack. - * - */ - destroy() - { - super.destroy(this); - - this.stencilMaskStack = null; - } -} diff --git a/packages/core/src/renderers/systems/WebGLSystem.js b/packages/core/src/renderers/systems/WebGLSystem.js deleted file mode 100644 index d3d5b31..0000000 --- a/packages/core/src/renderers/systems/WebGLSystem.js +++ /dev/null @@ -1,41 +0,0 @@ -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this manager works for. - */ - constructor(renderer) - { - /** - * The renderer this manager works for. - * - * @member {PIXI.Renderer} - */ - this.renderer = renderer; - - this.renderer.runners.contextChange.add(this); - } - - /** - * Generic method called when there is a WebGL context change. - * - */ - contextChange() - { - // do some codes init! - } - - /** - * Generic destroy methods to be overridden by the subclass - * - */ - destroy() - { - this.renderer.runners.contextChange.remove(this); - this.renderer = null; - } -} diff --git a/packages/core/src/renderers/systems/filter/FilterSystem.js b/packages/core/src/renderers/systems/filter/FilterSystem.js deleted file mode 100644 index e615037..0000000 --- a/packages/core/src/renderers/systems/filter/FilterSystem.js +++ /dev/null @@ -1,431 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; - -import RenderTexture from '../../../textures/RenderTexture'; -import Quad from '../../utils/Quad'; -import QuadUv from '../../utils/QuadUv'; -import { Rectangle } from '@pixi/math'; -import * as filterTransforms from '../../filters/filterTransforms'; -import bitTwiddle from 'bit-twiddle'; -import UniformGroup from '../../../shader/UniformGroup'; -import { DRAW_MODES } from '../../../../../constants'; - -// -/** - * @ignore - * @class - */ -class FilterState -{ - /** - * - */ - constructor() - { - this.renderTexture = null; - this.sourceFrame = new Rectangle(); - this.destinationFrame = new Rectangle(); - this.filters = []; - this.target = null; - this.legacy = false; - this.resolution = 1; - } -} - -/** - * @class - * @memberof PIXI.systems - * @extends PIXI.systems.WebGLSystem - */ -export default class FilterSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - /** - * stores a bunch of PO2 textures used for filtering - * @type {Object} - */ - this.texturePool = {}; - - /** - * a pool for storing filter states, save us creating new ones each tick - * @type {Array} - */ - this.statePool = []; - - /** - * A very simple geometry used when drawing a filter effect to the screen - * @type {Quad} - */ - this.quad = new Quad(); - - this.quadUv = new QuadUv(); - - /** - * Temporary rect for maths - * @type {PIXI.Rectangle} - */ - this.tempRect = new Rectangle(); - - this.activeState = {}; - - /** - * this uniform group is attached to filter uniforms when used - * @type {UniformGroup} - */ - this.globalUniforms = new UniformGroup({ - sourceFrame: this.tempRect, - destinationFrame: this.tempRect, - - // legacy variables - filterArea: new Float32Array(4), - filterClamp: new Float32Array(4), - }, true); - } - - /** - * Adds a new filter to the System. - * - * @param {PIXI.DisplayObject} target - The target of the filter to render. - * @param {PIXI.Filter[]} filters - The filters to apply. - */ - push(target, filters) - { - const renderer = this.renderer; - const filterStack = this.renderer.renderTexture.defaultFilterStack; - const state = this.statePool.pop() || new FilterState(); - - let resolution = filters[0].resolution; - let padding = filters[0].padding; - let autoFit = filters[0].autoFit; - let legacy = filters[0].legacy; - - for (let i = 1; i < filters.length; i++) - { - const filter = filters[i]; - - // lets use the lowest resolution.. - resolution = Math.min(resolution, filter.resolution); - // and the largest amount of padding! - padding = Math.max(padding, filter.padding); - // only auto fit if all filters are autofit - autoFit = autoFit || filter.autoFit; - - legacy = legacy || filter.legacy; - } - - filterStack.push(state); - - state.resolution = resolution; - - state.legacy = legacy; - - // round to whole number based on resolution - // TODO move that to the shader too? - state.sourceFrame = target.filterArea || target.getBounds(true); - - state.sourceFrame.pad(padding); - - if (autoFit) - { - state.sourceFrame.fit(this.renderer.renderTexture.destinationFrame); - } - - state.sourceFrame.round(resolution); - - state.renderTexture = this.getPotFilterTexture(state.sourceFrame.width, state.sourceFrame.height, resolution); - state.filters = filters; - - state.destinationFrame.width = state.renderTexture.width; - state.destinationFrame.height = state.renderTexture.height; - - state.renderTexture.filterFrame = state.sourceFrame; - renderer.renderTexture.bind(state.renderTexture, state.sourceFrame);// /, state.destinationFrame); - renderer.renderTexture.clear(); - } - - /** - * Pops off the filter and applies it. - * - */ - pop() - { - const renderer = this.renderer; - const filterStack = renderer.renderTexture.defaultFilterStack; - const state = filterStack.pop(); - const filters = state.filters; - - this.activeState = state; - - const globalUniforms = this.globalUniforms.uniforms; - - globalUniforms.sourceFrame = state.sourceFrame; - globalUniforms.destinationFrame = state.destinationFrame; - globalUniforms.resolution = state.resolution; - - // only update the rect if its legacy.. - if (state.legacy) - { - const filterArea = globalUniforms.filterArea; - const filterClamp = globalUniforms.filterClamp; - - filterArea[0] = state.destinationFrame.width; - filterArea[1] = state.destinationFrame.height; - filterArea[2] = state.sourceFrame.x; - filterArea[3] = state.sourceFrame.y; - - filterClamp[0] = 0.5 / state.resolution / state.destinationFrame.width; - filterClamp[1] = 0.5 / state.resolution / state.destinationFrame.height; - filterClamp[2] = (state.sourceFrame.width - 0.5) / state.resolution / state.destinationFrame.width; - filterClamp[3] = (state.sourceFrame.height - 0.5) / state.resolution / state.destinationFrame.height; - } - - this.globalUniforms.update(); - - const lastState = filterStack[filterStack.length - 1]; - - if (filters.length === 1) - { - filters[0].apply(this, state.renderTexture, lastState.renderTexture, false, state); - - this.returnFilterTexture(state.renderTexture); - } - else - { - let flip = state.renderTexture; - let flop = this.getPotFilterTexture( - flip.width, - flip.height, - state.resolution - ); - - flop.filterFrame = flip.filterFrame; - - let i = 0; - - for (i = 0; i < filters.length - 1; ++i) - { - filters[i].apply(this, flip, flop, true, state); - - const t = flip; - - flip = flop; - flop = t; - } - - filters[i].apply(this, flip, lastState.renderTexture, false, state); - - this.returnFilterTexture(flip); - this.returnFilterTexture(flop); - } - - this.statePool.push(state); - } - - /** - * Draws a filter. - * - * @param {PIXI.Filter} filter - The filter to draw. - * @param {PIXI.RenderTarget} input - The input render target. - * @param {PIXI.RenderTarget} output - The target to output to. - * @param {boolean} clear - Should the output be cleared before rendering to it - */ - applyFilter(filter, input, output, clear) - { - const renderer = this.renderer; - - renderer.renderTexture.bind(output, output ? output.filterFrame : null); - - if (clear) - { - // gl.disable(gl.SCISSOR_TEST); - renderer.renderTexture.clear(); - // gl.enable(gl.SCISSOR_TEST); - } - - // set the uniforms.. - filter.uniforms.uSampler = input; - filter.uniforms.filterGlobals = this.globalUniforms; - - // TODO make it so that the order of this does not matter.. - // because it does at the moment cos of global uniforms. - // they need to get resynced - - renderer.state.setState(filter.state); - renderer.shader.bind(filter); - - if (filter.legacy) - { - this.quadUv.map(input._frame, input.filterFrame); - - renderer.geometry.bind(this.quadUv); - renderer.geometry.draw(DRAW_MODES.TRIANGLES); - } - else - { - renderer.geometry.bind(this.quad); - renderer.geometry.draw(DRAW_MODES.TRIANGLE_STRIP); - } - } - - /** - * Calculates the mapped matrix. - * - * TODO playing around here.. this is temporary - (will end up in the shader) - * this returns a matrix that will normalise map filter cords in the filter to screen space - * - * @param {PIXI.Matrix} outputMatrix - the matrix to output to. - * @return {PIXI.Matrix} The mapped matrix. - */ - calculateScreenSpaceMatrix(outputMatrix) - { - const currentState = this.activeState; - - return filterTransforms.calculateScreenSpaceMatrix( - outputMatrix, - currentState.sourceFrame, - currentState.destinationFrame - ); - } - - /** - * This will map the filter coord so that a texture can be used based on the transform of a sprite - * - * @param {PIXI.Matrix} outputMatrix - The matrix to output to. - * @param {PIXI.Sprite} sprite - The sprite to map to. - * @return {PIXI.Matrix} The mapped matrix. - */ - calculateSpriteMatrix(outputMatrix, sprite) - { - const currentState = this.activeState; - - return filterTransforms.calculateSpriteMatrix( - outputMatrix, - currentState.sourceFrame, - currentState.destinationFrame, - sprite - ); - } - - /** - * Destroys this Filter System. - * - * @param {boolean} [contextLost=false] context was lost, do not free shaders - * - */ - destroy(contextLost = false) - { - if (!contextLost) - { - this.emptyPool(); - } - else - { - this.texturePool = {}; - } - } - - /** - * Gets a Power-of-Two render texture. - * - * TODO move to a seperate class could be on renderer? - * also - could cause issue with multiple contexts? - * - * @private - * @param {WebGLRenderingContext} gl - The webgl rendering context - * @param {number} minWidth - The minimum width of the render target. - * @param {number} minHeight - The minimum height of the render target. - * @param {number} resolution - The resolution of the render target. - * @return {PIXI.RenderTarget} The new render target. - */ - getPotFilterTexture(minWidth, minHeight, resolution) - { - minWidth = bitTwiddle.nextPow2(minWidth); - minHeight = bitTwiddle.nextPow2(minHeight); - resolution = resolution || 1; - - const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); - - if (!this.texturePool[key]) - { - this.texturePool[key] = []; - } - - let renderTexture = this.texturePool[key].pop(); - - if (!renderTexture) - { - // temporary bypass cache.. - // internally - this will cause a texture to be bound.. - renderTexture = RenderTexture.create({ - width: minWidth, - height: minHeight, - resolution, - }); - } - - return renderTexture; - } - - /** - * Gets extra render texture to use inside current filter - * - * @param {number} resolution resolution of the renderTexture - * @returns {PIXI.RenderTexture} - */ - getFilterTexture(resolution) - { - const rt = this.activeState.renderTexture; - - const filterTexture = this.getPotFilterTexture(rt.width, rt.height, resolution || rt.baseTexture.resolution); - - filterTexture.filterFrame = rt.filterFrame; - - return filterTexture; - } - - /** - * Frees a render texture back into the pool. - * - * @param {PIXI.RenderTarget} renderTexture - The renderTarget to free - */ - returnFilterTexture(renderTexture) - { - renderTexture.filterFrame = null; - - const base = renderTexture.baseTexture; - - const minWidth = base.width; - const minHeight = base.height; - - const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); - - this.texturePool[key].push(renderTexture); - } - - /** - * Empties the texture pool. - * - */ - emptyPool() - { - for (const i in this.texturePool) - { - const textures = this.texturePool[i]; - - if (textures) - { - for (let j = 0; j < textures.length; j++) - { - textures[j].destroy(true); - } - } - } - - this.texturePool = {}; - } -} diff --git a/packages/core/src/renderers/systems/geometry/GLBuffer.js b/packages/core/src/renderers/systems/geometry/GLBuffer.js deleted file mode 100644 index 0477e8a..0000000 --- a/packages/core/src/renderers/systems/geometry/GLBuffer.js +++ /dev/null @@ -1,9 +0,0 @@ -export default class GLBuffer -{ - constructor(buffer) - { - this.buffer = buffer; - this.updateID = -1; - this.byteLength = -1; - } -} diff --git a/packages/core/src/renderers/systems/geometry/GeometrySystem.js b/packages/core/src/renderers/systems/geometry/GeometrySystem.js deleted file mode 100644 index 26c8e9e..0000000 --- a/packages/core/src/renderers/systems/geometry/GeometrySystem.js +++ /dev/null @@ -1,388 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; -import GLBuffer from './GLBuffer'; - -const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class GeometrySystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this._activeGeometry = null; - this._activeVao = null; - - this.hasVao = true; - this.hasInstance = true; - } - - /** - * Sets up the renderer context and necessary buffers. - * - * @private - */ - contextChange() - { - const gl = this.gl = this.renderer.gl; - - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - - // webgl2 - if (!gl.createVertexArray) - { - // webgl 1! - let nativeVaoExtension = this.renderer.context.extensions.vertexArrayObject; - - if (this.renderer.legacy) - { - nativeVaoExtension = null; - } - - if (nativeVaoExtension) - { - gl.createVertexArray = () => - nativeVaoExtension.createVertexArrayOES(); - - gl.bindVertexArray = (vao) => - nativeVaoExtension.bindVertexArrayOES(vao); - - gl.deleteVertexArray = (vao) => - nativeVaoExtension.deleteVertexArrayOES(vao); - } - else - { - this.hasVao = false; - gl.createVertexArray = () => - { - // empty - }; - - gl.bindVertexArray = () => - { - // empty - }; - - gl.deleteVertexArray = () => - { - // empty - }; - } - } - - if (!gl.vertexAttribDivisor) - { - const instanceExt = gl.getExtension('ANGLE_instanced_arrays'); - - if (instanceExt) - { - gl.vertexAttribDivisor = (a, b) => - instanceExt.vertexAttribDivisorANGLE(a, b); - - gl.drawElementsInstanced = (a, b, c, d, e) => - instanceExt.drawElementsInstancedANGLE(a, b, c, d, e); - - gl.drawArraysInstanced = (a, b, c, d) => - instanceExt.drawArraysInstancedANGLE(a, b, c, d); - } - else - { - this.hasInstance = false; - } - } - } - - /** - * Binds geometry so that is can be drawn. Creating a Vao if required - * @private - * @param {PIXI.Geometry} geometry instance of geometry to bind - */ - bind(geometry, shader) - { - shader = shader || this.renderer.shader.shader; - - const gl = this.gl; - - // not sure the best way to address this.. - // currently different shaders require different VAOs for the same geometry - // Still mulling over the best way to solve this one.. - // will likely need to modify the shader attribute locations at run time! - let vaos = geometry.glVertexArrayObjects[this.CONTEXT_UID]; - - if (!vaos) - { - geometry.glVertexArrayObjects[this.CONTEXT_UID] = vaos = {}; - } - - const vao = vaos[shader.program.id] || this.initGeometryVao(geometry, shader.program); - - this._activeGeometry = geometry; - - if (this._activeVao !== vao) - { - this._activeVao = vao; - - if (this.hasVao) - { - gl.bindVertexArray(vao); - } - else - { - this.activateVao(geometry, shader.program); - } - } - - // TODO - optimise later! - // don't need to loop through if nothing changed! - // maybe look to add an 'autoupdate' to geometry? - this.updateBuffers(); - } - - reset() - { - this.unbind(); - } - - updateBuffers() - { - const geometry = this._activeGeometry; - const gl = this.gl; - - for (let i = 0; i < geometry.buffers.length; i++) - { - const buffer = geometry.buffers[i]; - - const glBuffer = buffer._glBuffers[this.CONTEXT_UID]; - - if (buffer._updateID !== glBuffer.updateID) - { - glBuffer.updateID = buffer._updateID; - - // TODO can cache this on buffer! maybe added a getter / setter? - const type = buffer.index ? gl.ELEMENT_ARRAY_BUFFER : gl.ARRAY_BUFFER; - const drawType = buffer.static ? gl.STATIC_DRAW : gl.DYNAMIC_DRAW; - - gl.bindBuffer(type, glBuffer.buffer); - - if (glBuffer.byteLength >= buffer.data.byteLength) - { - // offset is always zero for now! - gl.bufferSubData(type, 0, buffer.data); - } - else - { - gl.bufferData(type, buffer.data, drawType); - } - } - } - } - - checkCompatability(geometry, program) - { - // geometry must have at least all the attributes that the shader requires. - const geometryAttributes = geometry.attributes; - const shaderAttributes = program.attributeData; - - for (const j in shaderAttributes) - { - if (!geometryAttributes[j]) - { - throw new Error(`shader and geometry incompatible, geometry missing the "${j}" attribute`); - } - } - } - - /** - * Creates a Vao with the same structure as the geometry and stores it on the geometry. - * @private - * @param {PIXI.Geometry} geometry instance of geometry to to generate Vao for - */ - initGeometryVao(geometry, program) - { - this.checkCompatability(geometry, program); - - const gl = this.gl; - const CONTEXT_UID = this.CONTEXT_UID; - const buffers = geometry.buffers; - const attributes = geometry.attributes; - - const tempStride = {}; - const tempStart = {}; - - for (const j in buffers) - { - tempStride[j] = 0; - tempStart[j] = 0; - } - - for (const j in attributes) - { - if (!attributes[j].size && program.attributeData[j]) - { - attributes[j].size = program.attributeData[j].size; - } - - tempStride[attributes[j].buffer] += attributes[j].size * byteSizeMap[attributes[j].type]; - } - - for (const j in attributes) - { - const attribute = attributes[j]; - const attribSize = attribute.size; - - if (attribute.stride === undefined) - { - if (tempStride[attribute.buffer] === attribSize * byteSizeMap[attribute.type]) - { - attribute.stride = 0; - } - else - { - attribute.stride = tempStride[attribute.buffer]; - } - } - - if (attribute.start === undefined) - { - attribute.start = tempStart[attribute.buffer]; - - tempStart[attribute.buffer] += attribSize * byteSizeMap[attribute.type]; - } - } - - // first update - and create the buffers! - // only create a gl buffer if it actually gets - for (let i = 0; i < buffers.length; i++) - { - const buffer = buffers[i]; - - if (!buffer._glBuffers[CONTEXT_UID]) - { - buffer._glBuffers[CONTEXT_UID] = new GLBuffer(gl.createBuffer()); - } - } - - // TODO - maybe make this a data object? - // lets wait to see if we need to first! - const vao = gl.createVertexArray(); - - gl.bindVertexArray(vao); - - this.activateVao(geometry, program); - - gl.bindVertexArray(null); - - // add it to the cache! - geometry.glVertexArrayObjects[this.CONTEXT_UID][program.id] = vao; - - return vao; - } - - activateVao(geometry, program) - { - const gl = this.gl; - const CONTEXT_UID = this.CONTEXT_UID; - const buffers = geometry.buffers; - const attributes = geometry.attributes; - - if (geometry.indexBuffer) - { - // first update the index buffer if we have one.. - gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, geometry.indexBuffer._glBuffers[CONTEXT_UID].buffer); - } - - let lastBuffer = null; - - // add a new one! - for (const j in attributes) - { - const attribute = attributes[j]; - const buffer = buffers[attribute.buffer]; - const glBuffer = buffer._glBuffers[CONTEXT_UID]; - - if (program.attributeData[j]) - { - if (lastBuffer !== glBuffer) - { - gl.bindBuffer(gl.ARRAY_BUFFER, glBuffer.buffer); - - lastBuffer = glBuffer; - } - - const location = program.attributeData[j].location; - - // TODO introduce state again - // we can optimise this for older devices that have no VAOs - gl.enableVertexAttribArray(location); - - gl.vertexAttribPointer(location, - attribute.size, - attribute.type || gl.FLOAT, - attribute.normalized, - attribute.stride, - attribute.start); - - if (attribute.instance) - { - // TODO calculate instance count based of this... - if (this.hasInstance) - { - gl.vertexAttribDivisor(location, 1); - } - else - { - throw new Error('geometry error, GPU Instancing is not supported on this device'); - } - } - } - } - } - - draw(type, size, start, instanceCount) - { - const gl = this.gl; - const geometry = this._activeGeometry; - - // TODO.. this should not change so maybe cache the function? - - if (geometry.indexBuffer) - { - if (geometry.instanced) - { - /* eslint-disable max-len */ - gl.drawElementsInstanced(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2, instanceCount || 1); - /* eslint-enable max-len */ - } - else - { - gl.drawElements(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2); - } - } - else - if (geometry.instanced) - { - // TODO need a better way to calculate size.. - gl.drawArraysInstanced(type, start, size || geometry.getSize(), instanceCount || 1); - } - else - { - gl.drawArrays(type, start, size || geometry.getSize()); - } - - return this; - } - - unbind() - { - this.gl.bindVertexArray(null); - this._activeVao = null; - this._activeGeometry = null; - } -} diff --git a/packages/core/src/renderers/systems/geometry/setVertexAttribArrays.js b/packages/core/src/renderers/systems/geometry/setVertexAttribArrays.js deleted file mode 100644 index 23b6959..0000000 --- a/packages/core/src/renderers/systems/geometry/setVertexAttribArrays.js +++ /dev/null @@ -1,54 +0,0 @@ -// var GL_MAP = {}; - -/** - * @param gl {WebGLRenderingContext} The current WebGL context - * @param attribs {*} - * @param state {*} - */ -export default function setVertexAttribArrays(gl, attribs, state) -{ - let i; - - if (state) - { - const tempAttribState = state.tempAttribState; - const attribState = state.attribState; - - for (i = 0; i < tempAttribState.length; i++) - { - tempAttribState[i] = false; - } - - // set the new attribs - for (i = 0; i < attribs.length; i++) - { - tempAttribState[attribs[i].attribute.location] = true; - } - - for (i = 0; i < attribState.length; i++) - { - if (attribState[i] !== tempAttribState[i]) - { - attribState[i] = tempAttribState[i]; - - if (state.attribState[i]) - { - gl.enableVertexAttribArray(i); - } - else - { - gl.disableVertexAttribArray(i); - } - } - } - } - else - { - for (i = 0; i < attribs.length; i++) - { - const attrib = attribs[i]; - - gl.enableVertexAttribArray(attrib.attribute.location); - } - } -} diff --git a/packages/core/src/renderers/systems/index.js b/packages/core/src/renderers/systems/index.js deleted file mode 100644 index c843f03..0000000 --- a/packages/core/src/renderers/systems/index.js +++ /dev/null @@ -1,6 +0,0 @@ -/** - * Systems are individual components to the Renderer pipeline. - * @namespace PIXI.systems - */ -export { default as WebGLSystem } from './WebGLSystem'; -export { default as FilterSystem } from './filter/FilterSystem'; diff --git a/packages/core/src/renderers/systems/shader/GLProgram.js b/packages/core/src/renderers/systems/shader/GLProgram.js deleted file mode 100644 index 92980ca..0000000 --- a/packages/core/src/renderers/systems/shader/GLProgram.js +++ /dev/null @@ -1,43 +0,0 @@ -/** - * Helper class to create a webGL Program - * - * @class - * @private - */ -export default class GLProgram -{ - constructor(program, uniformData) - { - /** - * The shader program - * - * @member {WebGLProgram} - */ - this.program = program; - - /** - * holds the uniform data which contains uniform locations - * and current uniform values used for caching and preventing undeed GPU commands - * @type {Object} - */ - this.uniformData = uniformData; - - /** - * uniformGroups holds the various upload functions for the shader. Each uniform group - * and program have a unique upload function generated. - * @type {Object} - */ - this.uniformGroups = {}; - } - - /** - * Destroys this program - * TODO - */ - destroy() - { - this.uniformData = null; - this.uniformGroups = null; - this.program = null; - } -} diff --git a/packages/core/src/renderers/systems/shader/ShaderSystem.js b/packages/core/src/renderers/systems/shader/ShaderSystem.js deleted file mode 100644 index 6cf57b6..0000000 --- a/packages/core/src/renderers/systems/shader/ShaderSystem.js +++ /dev/null @@ -1,170 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; -import GLProgram from './GLProgram'; -import generateUniformsSync from '../../../shader/generateUniformsSync'; -import defaultValue from '../../../shader/defaultValue'; -import compileProgram from './shader/compileProgram'; - -let UID = 0; - -/** - * Helper class to create a webGL Texture - * - * @class - * @memberof PIXI - */ -export default class ShaderSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - A reference to the current renderer - */ - constructor(renderer) - { - super(renderer); - - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = null; - - this.shader = null; - this.program = null; - - this.id = UID++; - } - - contextChange(gl) - { - this.gl = gl; - } - - /** - * Changes the current shader to the one given in parameter - * - * @param {PIXI.Shader} shader - the new shader - * @param {boolean} dontSync - false if the shader should automatically sync its uniforms. - * @returns {PIXI.glCore.glProgram} the glProgram that belongs to the shader. - */ - bind(shader, dontSync) - { - shader.uniforms.globals = this.renderer.globalUniforms; - - const program = shader.program; - const glProgram = program.glPrograms[this.renderer.CONTEXT_UID] || this.generateShader(shader); - - this.shader = shader; - - // TODO - some current pixi plugins bypass this.. so it not safe to use yet.. - if (this.program !== program) - { - this.program = program; - this.gl.useProgram(glProgram.program); - } - - if (!dontSync) - { - this.syncUniformGroup(shader.uniformGroup); - } - - return glProgram; - } - - /** - * Uploads the uniforms values to the currently bound shader. - * - * @param {object} uniforms - the uniforms valiues that be applied to the current shader - */ - setUniforms(uniforms) - { - const shader = this.shader.program; - const glProgram = shader.glPrograms[this.renderer.CONTEXT_UID]; - - shader.syncUniforms(glProgram.uniformData, uniforms, this.renderer); - } - - syncUniformGroup(group) - { - const glProgram = this.getglProgram(); - - if (!group.static || group.dirtyId !== glProgram.uniformGroups[group.id]) - { - glProgram.uniformGroups[group.id] = group.dirtyId; - const syncFunc = group.syncUniforms[this.shader.program.id] || this.createSyncGroups(group); - - syncFunc(glProgram.uniformData, group.uniforms, this.renderer); - } - } - - createSyncGroups(group) - { - group.syncUniforms[this.shader.program.id] = generateUniformsSync(group, this.shader.program.uniformData); - - return group.syncUniforms[this.shader.program.id]; - } - - /** - * Returns the underlying GLShade rof the currently bound shader. - * This can be handy for when you to have a little more control over the setting of your uniforms. - * - * @return {PIXI.glCore.Shader} the glProgram for the currently bound Shader for this context - */ - getglProgram() - { - if (this.shader) - { - return this.shader.program.glPrograms[this.renderer.CONTEXT_UID]; - } - - return null; - } - - /** - * Generates a glProgram verion of the Shader provided. - * - * @private - * @param {PIXI.Shader} shader the shader that the glProgram will be based on. - * @return {PIXI.glCore.glProgram} A shiney new glProgram - */ - generateShader(shader) - { - const gl = this.gl; - - const program = shader.program; - - const attribMap = {}; - - for (const i in program.attributeData) - { - attribMap[i] = program.attributeData[i].location; - } - - const shaderProgram = compileProgram(gl, program.vertexSrc, program.fragmentSrc, attribMap); - const uniformData = {}; - - for (const i in program.uniformData) - { - const data = program.uniformData[i]; - - uniformData[i] = { - location: gl.getUniformLocation(shaderProgram, i), - value: defaultValue(data.type, data.size), - }; - } - - const glProgram = new GLProgram(shaderProgram, uniformData); - - program.glPrograms[this.renderer.CONTEXT_UID] = glProgram; - - return glProgram; - } - - /** - * Destroys this System and removes all its textures - */ - destroy() - { - // TODO implement destroy method for ShaderSystem - this.destroyed = true; - } -} diff --git a/packages/core/src/renderers/systems/shader/shader/compileProgram.js b/packages/core/src/renderers/systems/shader/shader/compileProgram.js deleted file mode 100644 index 8c6ccb9..0000000 --- a/packages/core/src/renderers/systems/shader/shader/compileProgram.js +++ /dev/null @@ -1,78 +0,0 @@ -/** - * @method compileProgram - * @memberof PIXI.glCore.shader - * @param gl {WebGLRenderingContext} The current WebGL context {WebGLProgram} - * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. - * @param fragmentSrc {string|string[]} The fragment shader source as an array of strings. - * @param attributeLocations {Object} An attribute location map that lets you manually set the attribute locations - * @return {WebGLProgram} the shader program - */ -export default function compileProgram(gl, vertexSrc, fragmentSrc, attributeLocations) -{ - const glVertShader = compileShader(gl, gl.VERTEX_SHADER, vertexSrc); - const glFragShader = compileShader(gl, gl.FRAGMENT_SHADER, fragmentSrc); - - let program = gl.createProgram(); - - gl.attachShader(program, glVertShader); - gl.attachShader(program, glFragShader); - - // optionally, set the attributes manually for the program rather than letting WebGL decide.. - if (attributeLocations) - { - for (const i in attributeLocations) - { - gl.bindAttribLocation(program, attributeLocations[i], i); - } - } - - gl.linkProgram(program); - - // if linking fails, then log and cleanup - if (!gl.getProgramParameter(program, gl.LINK_STATUS)) - { - console.error('Pixi.js Error: Could not initialize shader.'); - console.error('gl.VALIDATE_STATUS', gl.getProgramParameter(program, gl.VALIDATE_STATUS)); - console.error('gl.getError()', gl.getError()); - - // if there is a program info log, log it - if (gl.getProgramInfoLog(program) !== '') - { - console.warn('Pixi.js Warning: gl.getProgramInfoLog()', gl.getProgramInfoLog(program)); - } - - gl.deleteProgram(program); - program = null; - } - - // clean up some shaders - gl.deleteShader(glVertShader); - gl.deleteShader(glFragShader); - - return program; -} - -/** - * @private - * @param gl {WebGLRenderingContext} The current WebGL context {WebGLProgram} - * @param type {Number} the type, can be either VERTEX_SHADER or FRAGMENT_SHADER - * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. - * @return {WebGLShader} the shader - */ -function compileShader(gl, type, src) -{ - const shader = gl.createShader(type); - - gl.shaderSource(shader, src); - gl.compileShader(shader); - - if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) - { - console.warn(src); - console.error(gl.getShaderInfoLog(shader)); - - return null; - } - - return shader; -} diff --git a/packages/core/src/renderers/systems/shader/shader/defaultValue.js b/packages/core/src/renderers/systems/shader/shader/defaultValue.js deleted file mode 100644 index 97290bd..0000000 --- a/packages/core/src/renderers/systems/shader/shader/defaultValue.js +++ /dev/null @@ -1,79 +0,0 @@ -/** - * @method defaultValue - * @memberof PIXI.glCore.shader - * @param type {String} Type of value - * @param size {Number} - */ -export default function defaultValue(type, size) -{ - switch (type) - { - case 'float': - return 0; - - case 'vec2': - return new Float32Array(2 * size); - - case 'vec3': - return new Float32Array(3 * size); - - case 'vec4': - return new Float32Array(4 * size); - - case 'int': - case 'sampler2D': - case 'sampler2DArray': - return 0; - - case 'ivec2': - return new Int32Array(2 * size); - - case 'ivec3': - return new Int32Array(3 * size); - - case 'ivec4': - return new Int32Array(4 * size); - - case 'bool': - return false; - - case 'bvec2': - - return booleanArray(2 * size); - - case 'bvec3': - return booleanArray(3 * size); - - case 'bvec4': - return booleanArray(4 * size); - - case 'mat2': - return new Float32Array([1, 0, - 0, 1]); - - case 'mat3': - return new Float32Array([1, 0, 0, - 0, 1, 0, - 0, 0, 1]); - - case 'mat4': - return new Float32Array([1, 0, 0, 0, - 0, 1, 0, 0, - 0, 0, 1, 0, - 0, 0, 0, 1]); - } - - return null; -} - -function booleanArray(size) -{ - const array = new Array(size); - - for (let i = 0; i < array.length; i++) - { - array[i] = false; - } - - return array; -} diff --git a/packages/core/src/renderers/systems/shader/shader/index.js b/packages/core/src/renderers/systems/shader/shader/index.js deleted file mode 100644 index ba7a317..0000000 --- a/packages/core/src/renderers/systems/shader/shader/index.js +++ /dev/null @@ -1,8 +0,0 @@ -/** - * @namespace PIXI.glCore.shader - */ -export { default as compileProgram } from './compileProgram'; -export { default as defaultValue } from './defaultValue'; -export { default as setPrecision } from './setPrecision'; -export { default as mapSize } from './mapSize'; -export { default as mapType } from './mapType'; diff --git a/packages/core/src/renderers/systems/shader/shader/mapSize.js b/packages/core/src/renderers/systems/shader/shader/mapSize.js deleted file mode 100644 index b681554..0000000 --- a/packages/core/src/renderers/systems/shader/shader/mapSize.js +++ /dev/null @@ -1,33 +0,0 @@ -const GLSL_TO_SIZE = { - float: 1, - vec2: 2, - vec3: 3, - vec4: 4, - - int: 1, - ivec2: 2, - ivec3: 3, - ivec4: 4, - - bool: 1, - bvec2: 2, - bvec3: 3, - bvec4: 4, - - mat2: 4, - mat3: 9, - mat4: 16, - - sampler2D: 1, -}; - -/** - * @method mapSize - * @memberof PIXI.glCore.shader - * @param type {String} - * @return {Number} - */ -export default function mapSize(type) -{ - return GLSL_TO_SIZE[type]; -} diff --git a/packages/core/src/renderers/systems/shader/shader/mapType.js b/packages/core/src/renderers/systems/shader/shader/mapType.js deleted file mode 100644 index d288b7c..0000000 --- a/packages/core/src/renderers/systems/shader/shader/mapType.js +++ /dev/null @@ -1,45 +0,0 @@ -let GL_TABLE = null; - -const GL_TO_GLSL_TYPES = { - FLOAT: 'float', - FLOAT_VEC2: 'vec2', - FLOAT_VEC3: 'vec3', - FLOAT_VEC4: 'vec4', - - INT: 'int', - INT_VEC2: 'ivec2', - INT_VEC3: 'ivec3', - INT_VEC4: 'ivec4', - - BOOL: 'bool', - BOOL_VEC2: 'bvec2', - BOOL_VEC3: 'bvec3', - BOOL_VEC4: 'bvec4', - - FLOAT_MAT2: 'mat2', - FLOAT_MAT3: 'mat3', - FLOAT_MAT4: 'mat4', - - SAMPLER_2D: 'sampler2D', - SAMPLER_CUBE: 'samplerCube', - SAMPLER_2D_ARRAY: 'sampler2DArray', -}; - -export default function mapType(gl, type) -{ - if (!GL_TABLE) - { - const typeNames = Object.keys(GL_TO_GLSL_TYPES); - - GL_TABLE = {}; - - for (let i = 0; i < typeNames.length; ++i) - { - const tn = typeNames[i]; - - GL_TABLE[gl[tn]] = GL_TO_GLSL_TYPES[tn]; - } - } - - return GL_TABLE[type]; -} diff --git a/packages/core/src/renderers/systems/shader/shader/setPrecision.js b/packages/core/src/renderers/systems/shader/shader/setPrecision.js deleted file mode 100644 index e2c1f34..0000000 --- a/packages/core/src/renderers/systems/shader/shader/setPrecision.js +++ /dev/null @@ -1,18 +0,0 @@ -/** - * Sets the float precision on the shader. If the precision is already present this function will do nothing - * @param {string} src the shader source - * @param {string} precision The float precision of the shader. Options are 'lowp', 'mediump' or 'highp'. - * - * @return {string} modified shader source - */ -export default function setPrecision(src, precision) -{ - src = src.trim(); - - if (src.substring(0, 9) !== 'precision' && src.substring(0, 1) !== '#') - { - return `precision ${precision} float;\n${src}`; - } - - return src; -} diff --git a/packages/core/src/renderers/systems/textures/GLTexture.js b/packages/core/src/renderers/systems/textures/GLTexture.js deleted file mode 100644 index c3fb8dc..0000000 --- a/packages/core/src/renderers/systems/textures/GLTexture.js +++ /dev/null @@ -1,33 +0,0 @@ -export default class GLTexture -{ - constructor(texture) - { - /** - * The WebGL texture - * - * @member {WebGLTexture} - */ - this.texture = texture; - - this.width = -1; - this.height = -1; - - /** - * Texture contents dirty flag - * @member {number} - */ - this.dirtyId = -1; - - /** - * Texture style dirty flag - * @type {number} - */ - this.dirtyStyleId = -1; - - /** - * Whether mip levels has to be generated - * @type {boolean} - */ - this.mipmap = false; - } -} diff --git a/packages/core/src/renderers/systems/textures/TextureGCSystem.js b/packages/core/src/renderers/systems/textures/TextureGCSystem.js deleted file mode 100644 index b089938..0000000 --- a/packages/core/src/renderers/systems/textures/TextureGCSystem.js +++ /dev/null @@ -1,110 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; -import { GC_MODES } from '@pixi/constants'; -import { settings } from '@pixi/settings'; - -/** - * TextureGarbageCollector. This class manages the GPU and ensures that it does not get clogged - * up with textures that are no longer being used. - * - * @class - * @memberof PIXI - */ -export default class TextureGCSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this.count = 0; - this.checkCount = 0; - this.maxIdle = settings.GC_MAX_IDLE; - this.checkCountMax = settings.GC_MAX_CHECK_COUNT; - this.mode = settings.GC_MODE; - } - - /** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ - postrender() - { - this.count++; - - if (this.mode === GC_MODES.MANUAL) - { - return; - } - - this.checkCount++; - - if (this.checkCount > this.checkCountMax) - { - this.checkCount = 0; - - this.run(); - } - } - - /** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ - run() - { - const tm = this.renderer.texture; - const managedTextures = tm.managedTextures; - let wasRemoved = false; - - for (let i = 0; i < managedTextures.length; i++) - { - const texture = managedTextures[i]; - - // only supports non generated textures at the moment! - if (!texture.frameBuffer && this.count - texture.touched > this.maxIdle) - { - tm.destroyTexture(texture, true); - managedTextures[i] = null; - wasRemoved = true; - } - } - - if (wasRemoved) - { - let j = 0; - - for (let i = 0; i < managedTextures.length; i++) - { - if (managedTextures[i] !== null) - { - managedTextures[j++] = managedTextures[i]; - } - } - - managedTextures.length = j; - } - } - - /** - * Removes all the textures within the specified displayObject and its children from the GPU - * - * @param {PIXI.DisplayObject} displayObject - the displayObject to remove the textures from. - */ - unload(displayObject) - { - const tm = this.renderer.textureSystem; - - // only destroy non generated textures - if (displayObject._texture && displayObject._texture._glRenderTargets) - { - tm.destroyTexture(displayObject._texture); - } - - for (let i = displayObject.children.length - 1; i >= 0; i--) - { - this.unload(displayObject.children[i]); - } - } -} diff --git a/packages/core/src/renderers/systems/textures/TextureSystem.js b/packages/core/src/renderers/systems/textures/TextureSystem.js deleted file mode 100644 index ccc5de9..0000000 --- a/packages/core/src/renderers/systems/textures/TextureSystem.js +++ /dev/null @@ -1,281 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; -import GLTexture from './GLTexture'; -import { removeItems } from '@pixi/utils'; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class TextureSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - // TODO set to max textures... - this.boundTextures = [ - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - ]; - - this.currentLocation = -1; - - this.managedTextures = []; - } - - /** - * Sets up the renderer context and necessary buffers. - * - * @private - */ - contextChange() - { - const gl = this.gl = this.renderer.gl; - - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - - // TODO move this.. to a nice make empty textures class.. - this.emptyTextures = {}; - - const emptyTexture2D = new GLTexture(gl.createTexture()); - - gl.bindTexture(gl.TEXTURE_2D, emptyTexture2D.texture); - gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array(4)); - - this.emptyTextures[gl.TEXTURE_2D] = emptyTexture2D; - this.emptyTextures[gl.TEXTURE_CUBE_MAP] = new GLTexture(gl.createTexture()); - - gl.bindTexture(gl.TEXTURE_CUBE_MAP, this.emptyTextures[gl.TEXTURE_CUBE_MAP].texture); - - let i; - - for (i = 0; i < 6; i++) - { - gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, null); - } - - gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_MAG_FILTER, gl.LINEAR); - gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_MIN_FILTER, gl.LINEAR); - - for (i = 0; i < this.boundTextures.length; i++) - { - this.bind(null, i); - } - } - - bind(texture, location) - { - const gl = this.gl; - - location = location || 0; - - if (this.currentLocation !== location) - { - this.currentLocation = location; - gl.activeTexture(gl.TEXTURE0 + location); - } - - if (texture) - { - texture = texture.baseTexture || texture; - - if (texture.valid) - { - texture.touched = this.renderer.textureGC.count; - - const glTexture = texture._glTextures[this.CONTEXT_UID] || this.initTexture(texture); - - gl.bindTexture(texture.target, glTexture.texture); - - if (glTexture.dirtyId !== texture.dirtyId) - { - this.updateTexture(texture); - } - - this.boundTextures[location] = texture; - } - } - else - { - gl.bindTexture(gl.TEXTURE_2D, this.emptyTextures[gl.TEXTURE_2D].texture); - this.boundTextures[location] = null; - } - } - - unbind(texture) - { - const gl = this.gl; - - for (let i = 0; i < this.boundTextures.length; i++) - { - if (this.boundTextures[i] === texture) - { - if (this.currentLocation !== i) - { - gl.activeTexture(gl.TEXTURE0 + i); - this.currentLocation = i; - } - - gl.bindTexture(gl.TEXTURE_2D, this.emptyTextures[texture.target].texture); - this.boundTextures[i] = null; - } - } - } - - initTexture(texture) - { - const glTexture = new GLTexture(this.gl.createTexture()); - - // guarentee an update.. - glTexture.dirtyId = -1; - - texture._glTextures[this.CONTEXT_UID] = glTexture; - - this.managedTextures.push(texture); - texture.on('dispose', this.destroyTexture, this); - - return glTexture; - } - - updateTexture(texture) - { - const glTexture = texture._glTextures[this.CONTEXT_UID]; - const renderer = this.renderer; - - if (texture.resource && texture.resource.upload(renderer, texture, glTexture)) - { - // texture is uploaded, dont do anything! - } - else - { - // default, renderTexture-like logic - const width = texture.realWidth; - const height = texture.realHeight; - const gl = renderer.gl; - - if (glTexture.width !== width - || glTexture.height !== height - || glTexture.dirtyId < 0) - { - glTexture.width = width; - glTexture.height = height; - - gl.texImage2D(texture.target, 0, - texture.format, - width, - height, - 0, - texture.format, - texture.type, - null); - } - } - - // lets only update what changes.. - if (texture.dirtyStyleId !== glTexture.dirtyStyleId) - { - this.updateTextureStyle(texture); - } - glTexture.dirtyId = texture.dirtyId; - } - - /** - * Deletes the texture from WebGL - * - * @param {PIXI.BaseTexture|PIXI.Texture} texture - the texture to destroy - * @param {boolean} [skipRemove=false] - Whether to skip removing the texture from the TextureManager. - */ - destroyTexture(texture, skipRemove) - { - const gl = this.gl; - - texture = texture.baseTexture || texture; - - if (texture._glTextures[this.renderer.CONTEXT_UID]) - { - this.unbind(texture); - - gl.deleteTexture(texture._glTextures[this.renderer.CONTEXT_UID].texture); - texture.off('dispose', this.destroyTexture, this); - - delete texture._glTextures[this.renderer.CONTEXT_UID]; - - if (!skipRemove) - { - const i = this.managedTextures.indexOf(texture); - - if (i !== -1) - { - removeItems(this.managedTextures, i, 1); - } - } - } - } - - updateTextureStyle(texture) - { - const glTexture = texture._glTextures[this.CONTEXT_UID]; - - glTexture.mipmap = texture.mipmap && texture.isPowerOfTwo; - if (!glTexture) - { - return; - } - - if (texture.resource && texture.resource.style(this.renderer, texture, glTexture)) - { - // style is set, dont do anything! - } - else - { - this.setStyle(texture, glTexture); - } - - glTexture.dirtyStyleId = texture.dirtyStyleId; - } - - setStyle(texture, glTexture) - { - const gl = this.gl; - - if (glTexture.mipmap) - { - gl.generateMipmap(texture.target); - } - - gl.texParameteri(texture.target, gl.TEXTURE_WRAP_S, texture.wrapMode); - gl.texParameteri(texture.target, gl.TEXTURE_WRAP_T, texture.wrapMode); - - if (glTexture.mipmap) - { - /* eslint-disable max-len */ - gl.texParameteri(texture.target, gl.TEXTURE_MIN_FILTER, texture.scaleMode ? gl.LINEAR_MIPMAP_LINEAR : gl.NEAREST_MIPMAP_NEAREST); - /* eslint-disable max-len */ - } - else - { - gl.texParameteri(texture.target, gl.TEXTURE_MIN_FILTER, texture.scaleMode ? gl.LINEAR : gl.NEAREST); - } - - gl.texParameteri(texture.target, gl.TEXTURE_MAG_FILTER, texture.scaleMode ? gl.LINEAR : gl.NEAREST); - } -} diff --git a/packages/core/src/renderers/utils/ObjectRenderer.js b/packages/core/src/renderers/utils/ObjectRenderer.js deleted file mode 100644 index f67a273..0000000 --- a/packages/core/src/renderers/utils/ObjectRenderer.js +++ /dev/null @@ -1,48 +0,0 @@ -import WebGLSystem from '../systems/WebGLSystem'; - -/** - * Base for a common object renderer that can be used as a system renderer plugin. - * - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class ObjectRenderer extends WebGLSystem -{ - /** - * Starts the renderer and sets the shader - * - */ - start() - { - // set the shader.. - } - - /** - * Stops the renderer - * - */ - stop() - { - this.flush(); - } - - /** - * Stub method for rendering content and emptying the current batch. - * - */ - flush() - { - // flush! - } - - /** - * Renders an object - * - * @param {PIXI.DisplayObject} object - The object to render. - */ - render(object) // eslint-disable-line no-unused-vars - { - // render the object - } -} diff --git a/packages/core/src/renderers/utils/Quad.js b/packages/core/src/renderers/utils/Quad.js deleted file mode 100644 index 5c1517b..0000000 --- a/packages/core/src/renderers/utils/Quad.js +++ /dev/null @@ -1,26 +0,0 @@ -import Geometry from '../../geometry/Geometry'; - -/** - * Helper class to create a quad - * - * @class - * @memberof PIXI - */ -export default class Quad extends Geometry -{ - /** - * just a quad - */ - constructor() - { - super(); - - this.addAttribute('aVertexPosition', [ - 0, 0, - 1, 0, - 1, 1, - 0, 1, - ]) - .addIndex([0, 1, 3, 2]); - } -} diff --git a/packages/core/src/renderers/utils/QuadUv.js b/packages/core/src/renderers/utils/QuadUv.js deleted file mode 100644 index 97bda89..0000000 --- a/packages/core/src/renderers/utils/QuadUv.js +++ /dev/null @@ -1,103 +0,0 @@ -import Geometry from '../../geometry/Geometry'; -import Buffer from '../../geometry/Buffer'; - -/** - * Helper class to create a quad with uvs like in v4 - * - * @class - * @memberof PIXI - */ -export default class QuadUv extends Geometry -{ - constructor() - { - super(); - - /** - * An array of vertices - * - * @member {Float32Array} - */ - this.vertices = new Float32Array([ - -1, -1, - 1, -1, - 1, 1, - -1, 1, - ]); - - /** - * The Uvs of the quad - * - * @member {Float32Array} - */ - this.uvs = new Float32Array([ - 0, 0, - 1, 0, - 1, 1, - 0, 1, - ]); - - this.vertexBuffer = new Buffer(this.vertices); - this.uvBuffer = new Buffer(this.uvs); - - this.addAttribute('aVertexPosition', this.vertexBuffer) - .addAttribute('aTextureCoord', this.uvBuffer) - .addIndex([0, 1, 2, 0, 2, 3]); - } - - /** - * Maps two Rectangle to the quad. - * - * @param {PIXI.Rectangle} targetTextureFrame - the first rectangle - * @param {PIXI.Rectangle} destinationFrame - the second rectangle - * @return {PIXI.Quad} Returns itself. - */ - map(targetTextureFrame, destinationFrame) - { - let x = 0; // destinationFrame.x / targetTextureFrame.width; - let y = 0; // destinationFrame.y / targetTextureFrame.height; - - this.uvs[0] = x; - this.uvs[1] = y; - - this.uvs[2] = x + (destinationFrame.width / targetTextureFrame.width); - this.uvs[3] = y; - - this.uvs[4] = x + (destinationFrame.width / targetTextureFrame.width); - this.uvs[5] = y + (destinationFrame.height / targetTextureFrame.height); - - this.uvs[6] = x; - this.uvs[7] = y + (destinationFrame.height / targetTextureFrame.height); - - x = destinationFrame.x; - y = destinationFrame.y; - - this.vertices[0] = x; - this.vertices[1] = y; - - this.vertices[2] = x + destinationFrame.width; - this.vertices[3] = y; - - this.vertices[4] = x + destinationFrame.width; - this.vertices[5] = y + destinationFrame.height; - - this.vertices[6] = x; - this.vertices[7] = y + destinationFrame.height; - - this.invalidate(); - - return this; - } - - /** - * legacy upload method, just marks buffers dirty - * @returns {PIXI.QuadUv} Returns itself. - */ - invalidate() - { - this.vertexBuffer._updateID++; - this.uvBuffer._updateID++; - - return this; - } -} diff --git a/packages/core/src/AbstractRenderer.js b/packages/core/src/AbstractRenderer.js new file mode 100644 index 0000000..64b2eb1 --- /dev/null +++ b/packages/core/src/AbstractRenderer.js @@ -0,0 +1,353 @@ +import { hex2string, hex2rgb } from '@pixi/utils'; +import { Matrix, Rectangle } from '@pixi/math'; +import { RENDERER_TYPE } from '@pixi/constants'; +import { settings } from '@pixi/settings'; +import { Container } from '@pixi/display'; +import RenderTexture from './renderTexture/RenderTexture'; +import EventEmitter from 'eventemitter3'; + +const tempMatrix = new Matrix(); + +/** + * 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 EventEmitter + * @memberof PIXI + */ +export default class AbstractRenderer extends EventEmitter +{ + // eslint-disable-next-line valid-jsdoc + /** + * @param {string} system - The name of the system this renderer is for. + * @param {object} [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.transparent=false] - If the render view is transparent, default false + * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false + * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. The + * resolution of the renderer retina would be 2. + * @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 {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when rendering, + * stopping pixel interpolation. + */ + constructor(system, options, arg2, arg3) + { + super(); + + // Support for constructor(system, screenWidth, screenHeight, options) + if (typeof options === 'number') + { + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + }, arg3); + } + + // 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 = RENDERER_TYPE.UNKNOWN; + + /** + * Measurements of the screen. (0, 0, screenWidth, screenHeight) + * + * Its safe to use as filterArea or hitArea for 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 1 + */ + this.resolution = options.resolution || settings.RESOLUTION; + + /** + * Whether the render view is transparent + * + * @member {boolean} + */ + this.transparent = options.transparent; + + /** + * Whether css dimensions of canvas view should be resized to screen dimensions automatically + * + * @member {boolean} + */ + this.autoResize = options.autoResize || false; + + /** + * Tracks the blend modes useful for this renderer. + * + * @member {object} + */ + this.blendModes = null; + + /** + * 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; + + /** + * If true PixiJS will Math.floor() x/y values when rendering, stopping pixel interpolation. + * Handy for crisp pixel art and speed on legacy devices. + * + * @member {boolean} + */ + this.roundPixels = options.roundPixels; + + /** + * The background color as a number. + * + * @member {number} + * @private + */ + this._backgroundColor = 0x000000; + + /** + * The background color as an [R, G, B] array. + * + * @member {number[]} + * @private + */ + this._backgroundColorRgba = [0, 0, 0, 0]; + + /** + * The background color as a string. + * + * @member {string} + * @private + */ + this._backgroundColorString = '#000000'; + + this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter + + /** + * This temporary display object used as the parent of the currently being rendered item + * + * @member {PIXI.DisplayObject} + * @private + */ + this._tempDisplayObjectParent = new Container(); + + /** + * The last root object that the renderer tried to render. + * + * @member {PIXI.DisplayObject} + * @private + */ + this._lastObjectRendered = this._tempDisplayObjectParent; + + /** + * Collection of plugins. + * @readonly + * @member {object} + */ + this.plugins = {}; + } + + /** + * Initialize the plugins. + * + * @protected + * @param {object} staticMap - The dictionary of staticly saved plugins. + */ + initPlugins(staticMap) + { + 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() + { + return this.view.width; + } + + /** + * Same as view.height, actual number of pixels in the canvas by vertical + * + * @member {number} + * @readonly + * @default 600 + */ + get height() + { + return this.view.height; + } + + /** + * Resizes the screen and canvas to the specified width and height + * Canvas dimensions are multiplied by resolution + * + * @param {number} screenWidth - the new width of the screen + * @param {number} screenHeight - the new height of the screen + */ + resize(screenWidth, screenHeight) + { + this.screen.width = screenWidth; + this.screen.height = screenHeight; + + this.view.width = screenWidth * this.resolution; + this.view.height = screenHeight * this.resolution; + + if (this.autoResize) + { + this.view.style.width = `${screenWidth}px`; + this.view.style.height = `${screenHeight}px`; + } + } + + /** + * 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 {PIXI.DisplayObject} displayObject - The displayObject the object will be generated from + * @param {number} scaleMode - Should be one of the scaleMode consts + * @param {number} resolution - The resolution / device pixel ratio of the texture being generated + * @param {PIXI.Rectangle} [region] - The region of the displayObject, that shall be rendered, + * if no region is specified, defaults to the local bounds of the displayObject. + * @return {PIXI.Texture} a texture of the graphics object + */ + generateTexture(displayObject, scaleMode, resolution, region) + { + region = region || displayObject.getLocalBounds(); + + // 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(region.width | 0, region.height | 0, scaleMode, resolution); + + tempMatrix.tx = -region.x; + tempMatrix.ty = -region.y; + + this.render(displayObject, renderTexture, false, tempMatrix, true); + + return renderTexture; + } + + /** + * Removes everything from the renderer and optionally removes the Canvas DOM element. + * + * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. + */ + destroy(removeView) + { + 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); + } + + this.plugins = null; + + this.type = RENDERER_TYPE.UNKNOWN; + + this.view = null; + + this.screen = null; + + this.resolution = 0; + + this.transparent = false; + + this.autoResize = false; + + this.blendModes = null; + + this.options = null; + + this.preserveDrawingBuffer = false; + this.clearBeforeRender = false; + + this.roundPixels = false; + + this._backgroundColor = 0; + this._backgroundColorRgba = null; + this._backgroundColorString = null; + + this._tempDisplayObjectParent = null; + this._lastObjectRendered = null; + } + + /** + * The background color to fill if not transparent + * + * @member {number} + */ + get backgroundColor() + { + return this._backgroundColor; + } + + set backgroundColor(value) // eslint-disable-line require-jsdoc + { + this._backgroundColor = value; + this._backgroundColorString = hex2string(value); + hex2rgb(value, this._backgroundColorRgba); + } +} diff --git a/packages/core/src/Renderer.js b/packages/core/src/Renderer.js new file mode 100644 index 0000000..2227e78 --- /dev/null +++ b/packages/core/src/Renderer.js @@ -0,0 +1,338 @@ +import AbstractRenderer from './AbstractRenderer'; +import { sayHello } from '@pixi/utils'; +import MaskSystem from './mask/MaskSystem'; +import StencilSystem from './mask/StencilSystem'; +import FilterSystem from './filters/FilterSystem'; +import FramebufferSystem from './framebuffer/FramebufferSystem'; +import RenderTextureSystem from './renderTexture/RenderTextureSystem'; +import TextureSystem from './textures/TextureSystem'; +import ProjectionSystem from './projection/ProjectionSystem'; +import StateSystem from './state/StateSystem'; +import GeometrySystem from './geometry/GeometrySystem'; +import ShaderSystem from './shader/ShaderSystem'; +import ContextSystem from './context/ContextSystem'; +import BatchSystem from './batch/BatchSystem'; +import TextureGCSystem from './textures/TextureGCSystem'; +import { RENDERER_TYPE } from '@pixi/constants'; +import UniformGroup from './shader/UniformGroup'; +import { Matrix } from '@pixi/math'; +import Runner from 'mini-runner'; + +/** + * 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 Render works by automatically managing webGLBatchs. + * 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 :) + * + * @class + * @memberof PIXI + * @extends PIXI.AbstractRenderer + */ +export default class Renderer extends AbstractRenderer +{ + // eslint-disable-next-line valid-jsdoc + /** + * + * @param {object} [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.transparent=false] - If the render view is transparent, default false + * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false + * @param {boolean} [options.antialias=false] - sets antialias. If not available natively then FXAA + * antialiasing is used + * @param {boolean} [options.forceFXAA=false] - forces FXAA antialiasing to be used over native. + * FXAA is faster, but may not always look as great + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. + * The resolution of the renderer retina would be 2. + * @param {boolean} [options.clearBeforeRender=true] - This sets if the renderer will clear + * the canvas or not before the new render pass. If you wish to set this to false, you *must* set + * preserveDrawingBuffer to `true`. + * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, + * enable this if you need to call toDataUrl on the webgl context. + * @param {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when + * rendering, stopping pixel interpolation. + * @param {number} [options.backgroundColor=0x000000] - The background color of the rendered area + * (shown if not transparent). + * @param {boolean} [options.legacy=false] - If true PixiJS will aim to ensure compatibility + * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. + * @param {string} [options.powerPreference] - Parameter passed to webgl context, set to "high-performance" + * for devices with dual graphics card + */ + constructor(options, arg2, arg3) + { + super('WebGL', options, arg2, arg3); + + /** + * The type of this renderer as a standardised const + * + * @member {number} + * @see PIXI.RENDERER_TYPE + */ + this.type = RENDERER_TYPE.WEBGL; + + // this will be set by the contextSystem (this.context) + this.gl = null; + this.CONTEXT_UID = 0; + this.legacy = !!options.legacy; + + // TODO legacy! + + // runners! + this.runners = { + destroy: new Runner('destroy'), + contextChange: new Runner('contextChange', 1), + reset: new Runner('reset'), + update: new Runner('update'), + postrender: new Runner('postrender'), + prerender: new Runner('prerender'), + resize: new Runner('resize', 2), + }; + + 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(GeometrySystem, 'geometry') + .addSystem(FramebufferSystem, 'framebuffer') + .addSystem(StencilSystem, 'stencil') + .addSystem(ProjectionSystem, 'projection') + .addSystem(TextureGCSystem, 'textureGC') + .addSystem(FilterSystem, 'filter') + .addSystem(RenderTextureSystem, 'renderTexture') + .addSystem(BatchSystem, 'batch'); + + this.initPlugins(Renderer.__plugins); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ + if (options.context) + { + this.context.initFromContext(options.context); + } + else + { + this.context.initFromOptions({ + alpha: this.transparent, + antialias: options.antialias, + premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', + stencil: true, + 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); + } + + /** + * Add a new system to the renderer. + * @param {class} ClassRef - Class reference + * @param {string} [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. + * @return {PIXI.Renderer} Return instance of renderer + */ + addSystem(ClassRef, name) + { + if (!name) + { + name = ClassRef.name; + } + + // TODO - read name from class.name.. + + /* + if(name.includes('System')) + { + name = name.replace('System', ''); + name = name.charAt(0).toLowerCase() + name.slice(1); + } + */ + + const system = new ClassRef(this); + + if (this[name]) + { + throw new Error(`Whoops! ${name} is already a manger`); + } + + this[name] = system; + + for (const i in this.runners) + { + this.runners[i].add(system); + } + + return this; + + /** + * 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. + */ + } + + /** + * Renders the object to its webGL view + * + * @param {PIXI.DisplayObject} displayObject - the object to be rendered + * @param {PIXI.RenderTexture} renderTexture - The render texture to render to. + * @param {boolean} [clear] - Should the canvas be cleared before the new render + * @param {PIXI.Transform} [transform] - A transform to apply to the render texture before rendering. + * @param {boolean} [skipUpdateTransform] - Should we skip the update transform pass? + */ + render(displayObject, renderTexture, clear, transform, skipUpdateTransform) + { + // can be handy to know! + this.renderingToScreen = !renderTexture; + + this.runners.prerender.run(); + this.emit('prerender'); + + // 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.parent; + + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = 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.run(); + + this.emit('postrender'); + } + + /** + * Resizes the webGL view to the specified width and height. + * + * @param {number} screenWidth - the new width of the screen + * @param {number} screenHeight - the new height of the screen + */ + resize(screenWidth, screenHeight) + { + AbstractRenderer.prototype.resize.call(this, screenWidth, screenHeight); + + this.runners.resize.run(screenWidth, screenHeight); + } + + /** + * Resets the WebGL state so you can render things however you fancy! + * + * @return {PIXI.Renderer} Returns itself. + */ + reset() + { + this.runners.reset.run(); + + return this; + } + + clear() + { + this.framebuffer.bind(); + this.framebuffer.clear(); + } + + /** + * Removes everything from the renderer (event listeners, spritebatch, etc...) + * + * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. + * See: https://github.com/pixijs/pixi.js/issues/2233 + */ + destroy(removeView) + { + this.runners.destroy.run(); + + // call base destroy + super.destroy(removeView); + + // TODO nullify all the managers.. + this.gl = null; + } + + /** + * 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 + * builds and excluding plugins. + * @name PIXI.Renderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. + */ + + /** + * Adds a plugin to the renderer. + * + * @method + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + static registerPlugin(pluginName, ctor) + { + Renderer.__plugins = Renderer.__plugins || {}; + Renderer.__plugins[pluginName] = ctor; + } +} diff --git a/packages/core/src/System.js b/packages/core/src/System.js new file mode 100644 index 0000000..05a10c8 --- /dev/null +++ b/packages/core/src/System.js @@ -0,0 +1,42 @@ +/** + * System is a base class used for extending systems used by the {@link PIXI.Renderer} + * @see PIXI.Renderer#addSystem + * @class + * @memberof PIXI + */ +export default class System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + /** + * The renderer this manager works for. + * + * @member {PIXI.Renderer} + */ + this.renderer = renderer; + + this.renderer.runners.contextChange.add(this); + } + + /** + * Generic method called when there is a WebGL context change. + * + */ + contextChange() + { + // do some codes init! + } + + /** + * Generic destroy methods to be overridden by the subclass + * + */ + destroy() + { + this.renderer.runners.contextChange.remove(this); + this.renderer = null; + } +} diff --git a/packages/core/src/batch/BatchSystem.js b/packages/core/src/batch/BatchSystem.js new file mode 100644 index 0000000..b04c249 --- /dev/null +++ b/packages/core/src/batch/BatchSystem.js @@ -0,0 +1,68 @@ +import System from '../System'; +import ObjectRenderer from './ObjectRenderer'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class BatchSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + super(renderer); + + /** + * An empty renderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.emptyRenderer = new ObjectRenderer(renderer); + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.currentRenderer = this.emptyRenderer; + } + + /** + * Changes the current renderer to the one given in parameter + * + * @param {PIXI.ObjectRenderer} objectRenderer - The object renderer to use. + */ + setObjectRenderer(objectRenderer) + { + if (this.currentRenderer === objectRenderer) + { + return; + } + + this.currentRenderer.stop(); + this.currentRenderer = objectRenderer; + + this.renderer.state.setState(objectRenderer.state); + + this.currentRenderer.start(); + } + + /** + * This should be called if you wish to do some custom rendering + * It will basically render anything that may be batched up such as sprites + * + */ + flush() + { + this.setObjectRenderer(this.emptyRenderer); + } + + reset() + { + this.setObjectRenderer(this.emptyRenderer); + } +} diff --git a/packages/core/src/batch/ObjectRenderer.js b/packages/core/src/batch/ObjectRenderer.js new file mode 100644 index 0000000..0c851a9 --- /dev/null +++ b/packages/core/src/batch/ObjectRenderer.js @@ -0,0 +1,48 @@ +import System from '../System'; + +/** + * Base for a common object renderer that can be used as a system renderer plugin. + * + * @class + * @extends PIXI.System + * @memberof PIXI + */ +export default class ObjectRenderer extends System +{ + /** + * Starts the renderer and sets the shader + * + */ + start() + { + // set the shader.. + } + + /** + * Stops the renderer + * + */ + stop() + { + this.flush(); + } + + /** + * Stub method for rendering content and emptying the current batch. + * + */ + flush() + { + // flush! + } + + /** + * Renders an object + * + * @param {PIXI.DisplayObject} object - The object to render. + */ + render(object) // eslint-disable-line no-unused-vars + { + // render the object + } +} diff --git a/packages/core/src/context/ContextSystem.js b/packages/core/src/context/ContextSystem.js new file mode 100644 index 0000000..301aab4 --- /dev/null +++ b/packages/core/src/context/ContextSystem.js @@ -0,0 +1,186 @@ +import System from '../System'; +import { settings } from '@pixi/settings'; + +let CONTEXT_UID = 0; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class ContextSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.webGLVersion = 1; + + this.handleContextLost = this.handleContextLost.bind(this); + this.handleContextRestored = this.handleContextRestored.bind(this); + + this.extensions = {}; + + renderer.view.addEventListener('webglcontextlost', this.handleContextLost, false); + renderer.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + } + + get isLost() + { + return (!this.gl || this.gl.isContextLost()); + } + + contextChange(gl) + { + this.gl = gl; + + // restore a context if it was previously lost + if (gl.isContextLost() && gl.getExtension('WEBGL_lose_context')) + { + gl.getExtension('WEBGL_lose_context').restoreContext(); + } + } + + initFromContext(gl) + { + this.gl = gl; + this.validateContext(gl); + this.renderer.gl = gl; + this.renderer.CONTEXT_UID = CONTEXT_UID++; + this.renderer.runners.contextChange.run(gl); + } + + initFromOptions(options) + { + const gl = this.createContext(this.renderer.view, options); + + this.initFromContext(gl); + } + + /** + * Helper class to create a webGL Context + * + * @class + * @memberof PIXI.glCore + * @param canvas {HTMLCanvasElement} the canvas element that we will get the context from + * @param options {Object} An options object that gets passed in to the canvas element containing the context attributes, + * see https://developer.mozilla.org/en/docs/Web/API/HTMLCanvasElement/getContext for the options available + * @return {WebGLRenderingContext} the WebGL context + */ + createContext(canvas, options) + { + let gl; + + if (settings.PREFER_WEBGL_2) + { + gl = canvas.getContext('webgl2', options); + } + + if (gl) + { + this.webGLVersion = 2; + } + else + { + this.webGLVersion = 1; + + gl = canvas.getContext('webgl', options) + || canvas.getContext('experimental-webgl', options); + + if (!gl) + { + // fail, not able to get a context + throw new Error('This browser does not support webGL. Try using the canvas renderer'); + } + } + + this.gl = gl; + + this.getExtensions(); + + return gl; + } + + getExtensions() + { + // time to set up default etensions that pixi uses.. + const gl = this.gl; + const extensions = this.extensions; + + if (this.webGLVersion === 1) + { + extensions.drawBuffers = gl.getExtension('WEBGL_draw_buffers'); + extensions.depthTexture = gl.getExtension('WEBKIT_WEBGL_depth_texture'); + extensions.floatTexture = gl.getExtension('OES_texture_float'); + extensions.loseContext = gl.getExtension('WEBGL_lose_context'); + + extensions.vertexArrayObject = gl.getExtension('OES_vertex_array_object') + || gl.getExtension('MOZ_OES_vertex_array_object') + || gl.getExtension('WEBKIT_OES_vertex_array_object'); + } + + // we don't use any specific WebGL 2 ones yet! + } + + /** + * Handles a lost webgl context + * + * @private + * @param {WebGLContextEvent} event - The context lost event. + */ + handleContextLost(event) + { + event.preventDefault(); + } + + /** + * Handles a restored webgl context + * + * @private + */ + handleContextRestored() + { + this.renderer.runners.contextChange.run(this.gl); + } + + destroy() + { + const view = this.renderer.view; + + // remove listeners + view.removeEventListener('webglcontextlost', this.handleContextLost); + view.removeEventListener('webglcontextrestored', this.handleContextRestored); + + this.gl.useProgram(null); + + if (this.extensions.loseContext) + { + this.extensions.loseContext.loseContext(); + } + } + + postrender() + { + this.gl.flush(); + } + + validateContext(gl) + { + const attributes = gl.getContextAttributes(); + + // this is going to be fairly simple for now.. but at least we have room to grow! + if (!attributes.stencil) + { + /* eslint-disable max-len */ + + /* eslint-disable no-console */ + console.warn('Provided WebGL context does not have a stencil buffer, masks may not render correctly'); + /* eslint-enable no-console */ + + /* eslint-enable max-len */ + } + } +} diff --git a/packages/core/src/filters/Filter.js b/packages/core/src/filters/Filter.js new file mode 100644 index 0000000..9d8cad8 --- /dev/null +++ b/packages/core/src/filters/Filter.js @@ -0,0 +1,140 @@ +import Shader from '../shader/Shader'; +import Program from '../shader/Program'; +import State from '../state/State'; +import { settings } from '@pixi/settings'; +// import extractUniformsFromSrc from './extractUniformsFromSrc'; +import defaultVertex from './defaultFilter.vert'; +import defaultFragment from './defaultFilter.frag'; + +// let math = require('../../math'); +/** + * @class + * @memberof PIXI + * @extends PIXI.Shader + */ +export default class Filter extends Shader +{ + /** + * @param {string} [vertexSrc] - The source of the vertex shader. + * @param {string} [fragmentSrc] - The source of the fragment shader. + * @param {object} [uniforms] - Custom uniforms to use to augment the built-in ones. + */ + constructor(vertexSrc, fragmentSrc, uniforms) + { + const program = Program.from(vertexSrc, fragmentSrc); + + super(program, uniforms); + + /** + * The padding of the filter. Some filters require extra space to breath such as a blur. + * Increasing this will add extra width and height to the bounds of the object that the + * filter is applied to. + * + * @member {number} + */ + this.padding = 0; + + /** + * The resolution of the filter. Setting this to be lower will lower the quality but + * increase the performance of the filter. + * + * @member {number} + */ + this.resolution = settings.RESOLUTION; + + /** + * If enabled is true the filter is applied, if false it will not. + * + * @member {boolean} + */ + this.enabled = true; + + /** + * If enabled, PixiJS will fit the filter area into boundaries for better performance. + * Switch it off if it does not work for specific shader. + * + * @member {boolean} + */ + this.autoFit = true; + + /** + * Legacy filters use position and uvs from attributes + * @member {boolean} + * @readonly + */ + this.legacy = !!this.program.attributeData.aTextureCoord; + + /** + * the webGL state the filter requires to render + * @member {PIXI.State} + */ + this.state = new State(); + } + + /** + * Applies the filter + * + * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + * @param {boolean} clear - Should the output be cleared before rendering to it + * @param {object} [currentState] - It's current state of filter. + * There are some useful properties in the currentState : + * target, filters, sourceFrame, destinationFrame, renderTarget, resolution + */ + apply(filterManager, input, output, clear, currentState, derp) // eslint-disable-line no-unused-vars + { + // do as you please! + + filterManager.applyFilter(this, input, output, clear, currentState, derp); + + // or just do a regular render.. + } + + /** + * Sets the blendmode of the filter + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL + */ + get blendMode() + { + return this.state.blendMode; + } + + set blendMode(value) // eslint-disable-line require-jsdoc + { + this.state.blendMode = value; + } + + /** + * The default vertex shader source + * + * @static + * @constant + */ + static get defaultVertexSrc() + { + return defaultVertex; + } + + /** + * The default fragment shader source + * + * @static + * @constant + */ + static get defaultFragmentSrc() + { + return defaultFragment; + } +} + +/** + * Used for caching shader IDs + * + * @static + * @private + */ +Filter.SOURCE_KEY_MAP = {}; + diff --git a/packages/core/src/filters/FilterSystem.js b/packages/core/src/filters/FilterSystem.js new file mode 100644 index 0000000..3174318 --- /dev/null +++ b/packages/core/src/filters/FilterSystem.js @@ -0,0 +1,431 @@ +import System from '../System'; + +import RenderTexture from '../renderTexture/RenderTexture'; +import Quad from '../utils/Quad'; +import QuadUv from '../utils/QuadUv'; +import { Rectangle } from '@pixi/math'; +import * as filterTransforms from './filterTransforms'; +import bitTwiddle from 'bit-twiddle'; +import UniformGroup from '../shader/UniformGroup'; +import { DRAW_MODES } from '@pixi/constants'; + +// +/** + * @ignore + * @class + */ +class FilterState +{ + /** + * + */ + constructor() + { + this.renderTexture = null; + this.sourceFrame = new Rectangle(); + this.destinationFrame = new Rectangle(); + this.filters = []; + this.target = null; + this.legacy = false; + this.resolution = 1; + } +} + +/** + * @class + * @memberof PIXI.systems + * @extends PIXI.System + */ +export default class FilterSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + /** + * stores a bunch of PO2 textures used for filtering + * @type {Object} + */ + this.texturePool = {}; + + /** + * a pool for storing filter states, save us creating new ones each tick + * @type {Array} + */ + this.statePool = []; + + /** + * A very simple geometry used when drawing a filter effect to the screen + * @type {Quad} + */ + this.quad = new Quad(); + + this.quadUv = new QuadUv(); + + /** + * Temporary rect for maths + * @type {PIXI.Rectangle} + */ + this.tempRect = new Rectangle(); + + this.activeState = {}; + + /** + * this uniform group is attached to filter uniforms when used + * @type {UniformGroup} + */ + this.globalUniforms = new UniformGroup({ + sourceFrame: this.tempRect, + destinationFrame: this.tempRect, + + // legacy variables + filterArea: new Float32Array(4), + filterClamp: new Float32Array(4), + }, true); + } + + /** + * Adds a new filter to the System. + * + * @param {PIXI.DisplayObject} target - The target of the filter to render. + * @param {PIXI.Filter[]} filters - The filters to apply. + */ + push(target, filters) + { + const renderer = this.renderer; + const filterStack = this.renderer.renderTexture.defaultFilterStack; + const state = this.statePool.pop() || new FilterState(); + + let resolution = filters[0].resolution; + let padding = filters[0].padding; + let autoFit = filters[0].autoFit; + let legacy = filters[0].legacy; + + for (let i = 1; i < filters.length; i++) + { + const filter = filters[i]; + + // lets use the lowest resolution.. + resolution = Math.min(resolution, filter.resolution); + // and the largest amount of padding! + padding = Math.max(padding, filter.padding); + // only auto fit if all filters are autofit + autoFit = autoFit || filter.autoFit; + + legacy = legacy || filter.legacy; + } + + filterStack.push(state); + + state.resolution = resolution; + + state.legacy = legacy; + + // round to whole number based on resolution + // TODO move that to the shader too? + state.sourceFrame = target.filterArea || target.getBounds(true); + + state.sourceFrame.pad(padding); + + if (autoFit) + { + state.sourceFrame.fit(this.renderer.renderTexture.destinationFrame); + } + + state.sourceFrame.round(resolution); + + state.renderTexture = this.getPotFilterTexture(state.sourceFrame.width, state.sourceFrame.height, resolution); + state.filters = filters; + + state.destinationFrame.width = state.renderTexture.width; + state.destinationFrame.height = state.renderTexture.height; + + state.renderTexture.filterFrame = state.sourceFrame; + renderer.renderTexture.bind(state.renderTexture, state.sourceFrame);// /, state.destinationFrame); + renderer.renderTexture.clear(); + } + + /** + * Pops off the filter and applies it. + * + */ + pop() + { + const renderer = this.renderer; + const filterStack = renderer.renderTexture.defaultFilterStack; + const state = filterStack.pop(); + const filters = state.filters; + + this.activeState = state; + + const globalUniforms = this.globalUniforms.uniforms; + + globalUniforms.sourceFrame = state.sourceFrame; + globalUniforms.destinationFrame = state.destinationFrame; + globalUniforms.resolution = state.resolution; + + // only update the rect if its legacy.. + if (state.legacy) + { + const filterArea = globalUniforms.filterArea; + const filterClamp = globalUniforms.filterClamp; + + filterArea[0] = state.destinationFrame.width; + filterArea[1] = state.destinationFrame.height; + filterArea[2] = state.sourceFrame.x; + filterArea[3] = state.sourceFrame.y; + + filterClamp[0] = 0.5 / state.resolution / state.destinationFrame.width; + filterClamp[1] = 0.5 / state.resolution / state.destinationFrame.height; + filterClamp[2] = (state.sourceFrame.width - 0.5) / state.resolution / state.destinationFrame.width; + filterClamp[3] = (state.sourceFrame.height - 0.5) / state.resolution / state.destinationFrame.height; + } + + this.globalUniforms.update(); + + const lastState = filterStack[filterStack.length - 1]; + + if (filters.length === 1) + { + filters[0].apply(this, state.renderTexture, lastState.renderTexture, false, state); + + this.returnFilterTexture(state.renderTexture); + } + else + { + let flip = state.renderTexture; + let flop = this.getPotFilterTexture( + flip.width, + flip.height, + state.resolution + ); + + flop.filterFrame = flip.filterFrame; + + let i = 0; + + for (i = 0; i < filters.length - 1; ++i) + { + filters[i].apply(this, flip, flop, true, state); + + const t = flip; + + flip = flop; + flop = t; + } + + filters[i].apply(this, flip, lastState.renderTexture, false, state); + + this.returnFilterTexture(flip); + this.returnFilterTexture(flop); + } + + this.statePool.push(state); + } + + /** + * Draws a filter. + * + * @param {PIXI.Filter} filter - The filter to draw. + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + * @param {boolean} clear - Should the output be cleared before rendering to it + */ + applyFilter(filter, input, output, clear) + { + const renderer = this.renderer; + + renderer.renderTexture.bind(output, output ? output.filterFrame : null); + + if (clear) + { + // gl.disable(gl.SCISSOR_TEST); + renderer.renderTexture.clear(); + // gl.enable(gl.SCISSOR_TEST); + } + + // set the uniforms.. + filter.uniforms.uSampler = input; + filter.uniforms.filterGlobals = this.globalUniforms; + + // TODO make it so that the order of this does not matter.. + // because it does at the moment cos of global uniforms. + // they need to get resynced + + renderer.state.setState(filter.state); + renderer.shader.bind(filter); + + if (filter.legacy) + { + this.quadUv.map(input._frame, input.filterFrame); + + renderer.geometry.bind(this.quadUv); + renderer.geometry.draw(DRAW_MODES.TRIANGLES); + } + else + { + renderer.geometry.bind(this.quad); + renderer.geometry.draw(DRAW_MODES.TRIANGLE_STRIP); + } + } + + /** + * Calculates the mapped matrix. + * + * TODO playing around here.. this is temporary - (will end up in the shader) + * this returns a matrix that will normalise map filter cords in the filter to screen space + * + * @param {PIXI.Matrix} outputMatrix - the matrix to output to. + * @return {PIXI.Matrix} The mapped matrix. + */ + calculateScreenSpaceMatrix(outputMatrix) + { + const currentState = this.activeState; + + return filterTransforms.calculateScreenSpaceMatrix( + outputMatrix, + currentState.sourceFrame, + currentState.destinationFrame + ); + } + + /** + * This will map the filter coord so that a texture can be used based on the transform of a sprite + * + * @param {PIXI.Matrix} outputMatrix - The matrix to output to. + * @param {PIXI.Sprite} sprite - The sprite to map to. + * @return {PIXI.Matrix} The mapped matrix. + */ + calculateSpriteMatrix(outputMatrix, sprite) + { + const currentState = this.activeState; + + return filterTransforms.calculateSpriteMatrix( + outputMatrix, + currentState.sourceFrame, + currentState.destinationFrame, + sprite + ); + } + + /** + * Destroys this Filter System. + * + * @param {boolean} [contextLost=false] context was lost, do not free shaders + * + */ + destroy(contextLost = false) + { + if (!contextLost) + { + this.emptyPool(); + } + else + { + this.texturePool = {}; + } + } + + /** + * Gets a Power-of-Two render texture. + * + * TODO move to a seperate class could be on renderer? + * also - could cause issue with multiple contexts? + * + * @private + * @param {WebGLRenderingContext} gl - The webgl rendering context + * @param {number} minWidth - The minimum width of the render target. + * @param {number} minHeight - The minimum height of the render target. + * @param {number} resolution - The resolution of the render target. + * @return {PIXI.RenderTarget} The new render target. + */ + getPotFilterTexture(minWidth, minHeight, resolution) + { + minWidth = bitTwiddle.nextPow2(minWidth); + minHeight = bitTwiddle.nextPow2(minHeight); + resolution = resolution || 1; + + const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + + if (!this.texturePool[key]) + { + this.texturePool[key] = []; + } + + let renderTexture = this.texturePool[key].pop(); + + if (!renderTexture) + { + // temporary bypass cache.. + // internally - this will cause a texture to be bound.. + renderTexture = RenderTexture.create({ + width: minWidth, + height: minHeight, + resolution, + }); + } + + return renderTexture; + } + + /** + * Gets extra render texture to use inside current filter + * + * @param {number} resolution resolution of the renderTexture + * @returns {PIXI.RenderTexture} + */ + getFilterTexture(resolution) + { + const rt = this.activeState.renderTexture; + + const filterTexture = this.getPotFilterTexture(rt.width, rt.height, resolution || rt.baseTexture.resolution); + + filterTexture.filterFrame = rt.filterFrame; + + return filterTexture; + } + + /** + * Frees a render texture back into the pool. + * + * @param {PIXI.RenderTarget} renderTexture - The renderTarget to free + */ + returnFilterTexture(renderTexture) + { + renderTexture.filterFrame = null; + + const base = renderTexture.baseTexture; + + const minWidth = base.width; + const minHeight = base.height; + + const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + + this.texturePool[key].push(renderTexture); + } + + /** + * Empties the texture pool. + * + */ + emptyPool() + { + for (const i in this.texturePool) + { + const textures = this.texturePool[i]; + + if (textures) + { + for (let j = 0; j < textures.length; j++) + { + textures[j].destroy(true); + } + } + } + + this.texturePool = {}; + } +} diff --git a/packages/core/src/filters/defaultFilter.frag b/packages/core/src/filters/defaultFilter.frag new file mode 100644 index 0000000..a14c1be --- /dev/null +++ b/packages/core/src/filters/defaultFilter.frag @@ -0,0 +1,22 @@ +varying vec2 vTextureCoord; +varying vec2 vFilterCoord; + +uniform sampler2D uSampler; +uniform sampler2D filterSampler; + +void main(void){ + vec4 masky = texture2D(filterSampler, vFilterCoord); + vec4 sample = texture2D(uSampler, vTextureCoord); + vec4 color; + if(mod(vFilterCoord.x, 1.0) > 0.5) + { + color = vec4(1.0, 0.0, 0.0, 1.0); + } + else + { + color = vec4(0.0, 1.0, 0.0, 1.0); + } + // gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0); + gl_FragColor = mix(sample, masky, 0.5); + gl_FragColor *= sample.a; +} diff --git a/packages/core/src/filters/defaultFilter.vert b/packages/core/src/filters/defaultFilter.vert new file mode 100644 index 0000000..e1febca --- /dev/null +++ b/packages/core/src/filters/defaultFilter.vert @@ -0,0 +1,14 @@ +attribute vec2 aVertexPosition; +attribute vec2 aTextureCoord; + +uniform mat3 projectionMatrix; +uniform mat3 filterMatrix; + +varying vec2 vTextureCoord; +varying vec2 vFilterCoord; + +void main(void){ + gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); + vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy; + vTextureCoord = aTextureCoord ; +} diff --git a/packages/core/src/filters/filterTransforms.js b/packages/core/src/filters/filterTransforms.js new file mode 100644 index 0000000..87a3cd6 --- /dev/null +++ b/packages/core/src/filters/filterTransforms.js @@ -0,0 +1,50 @@ +import { Matrix } from '@pixi/math'; + +/** + * Calculates the mapped matrix + * @param filterArea {Rectangle} The filter area + * @param sprite {Sprite} the target sprite + * @param outputMatrix {Matrix} @alvin + * @private + */ +// this returns a matrix that will normalise map filter cords in the filter to screen space +export function calculateScreenSpaceMatrix(outputMatrix, filterArea, textureSize) +{ + // TODO unwrap? + const mappedMatrix = outputMatrix.identity(); + + mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); + + mappedMatrix.scale(textureSize.width, textureSize.height); + + return mappedMatrix; +} + +export function calculateNormalizedScreenSpaceMatrix(outputMatrix, filterArea, textureSize) +{ + const mappedMatrix = outputMatrix.identity(); + + mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); + + const translateScaleX = (textureSize.width / filterArea.width); + const translateScaleY = (textureSize.height / filterArea.height); + + mappedMatrix.scale(translateScaleX, translateScaleY); + + return mappedMatrix; +} + +// this will map the filter coord so that a texture can be used based on the transform of a sprite +export function calculateSpriteMatrix(outputMatrix, filterArea, textureSize, sprite) +{ + const orig = sprite._texture.orig; + const mappedMatrix = outputMatrix.set(textureSize.width, 0, 0, textureSize.height, filterArea.x, filterArea.y); + const worldTransform = sprite.worldTransform.copyTo(Matrix.TEMP_MATRIX); + + worldTransform.invert(); + mappedMatrix.prepend(worldTransform); + mappedMatrix.scale(1.0 / orig.width, 1.0 / orig.height); + mappedMatrix.translate(sprite.anchor.x, sprite.anchor.y); + + return mappedMatrix; +} diff --git a/packages/core/src/filters/spriteMask/SpriteMaskFilter.js b/packages/core/src/filters/spriteMask/SpriteMaskFilter.js new file mode 100644 index 0000000..91697b7 --- /dev/null +++ b/packages/core/src/filters/spriteMask/SpriteMaskFilter.js @@ -0,0 +1,63 @@ +import Filter from '../Filter'; +import { Matrix } from '@pixi/math'; +import vertex from './spriteMaskFilter.vert'; +import fragment from './spriteMaskFilter.frag'; +import { default as TextureMatrix } from '../../textures/TextureMatrix'; + +/** + * The SpriteMaskFilter class + * + * @class + * @extends PIXI.Filter + * @memberof PIXI + */ +export default class SpriteMaskFilter extends Filter +{ + /** + * @param {PIXI.Sprite} sprite - the target sprite + */ + constructor(sprite) + { + const maskMatrix = new Matrix(); + + super(vertex, fragment); + + sprite.renderable = false; + + this.maskSprite = sprite; + this.maskMatrix = maskMatrix; + } + + /** + * Applies the filter + * + * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + */ + apply(filterManager, input, output) + { + const maskSprite = this.maskSprite; + const tex = this.maskSprite.texture; + + if (!tex.valid) + { + return; + } + if (!tex.transform) + { + // margin = 0.0, let it bleed a bit, shader code becomes easier + // assuming that atlas textures were made with 1-pixel padding + tex.transform = new TextureMatrix(tex, 0.0); + } + tex.transform.update(); + + this.uniforms.mask = tex; + this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite) + .prepend(tex.transform.mapCoord); + this.uniforms.alpha = maskSprite.worldAlpha; + this.uniforms.maskClamp = tex.transform.uClampFrame; + + filterManager.applyFilter(this, input, output); + } +} diff --git a/packages/core/src/filters/spriteMask/spriteMaskFilter.frag b/packages/core/src/filters/spriteMask/spriteMaskFilter.frag new file mode 100644 index 0000000..0e4aef8 --- /dev/null +++ b/packages/core/src/filters/spriteMask/spriteMaskFilter.frag @@ -0,0 +1,23 @@ +varying vec2 vMaskCoord; +varying vec2 vTextureCoord; + +uniform sampler2D uSampler; +uniform sampler2D mask; +uniform float alpha; +uniform vec4 maskClamp; + +void main(void) +{ + float clip = step(3.5, + step(maskClamp.x, vMaskCoord.x) + + step(maskClamp.y, vMaskCoord.y) + + step(vMaskCoord.x, maskClamp.z) + + step(vMaskCoord.y, maskClamp.w)); + + vec4 original = texture2D(uSampler, vTextureCoord); + vec4 masky = texture2D(mask, vMaskCoord); + + original *= (masky.r * masky.a * alpha * clip); + + gl_FragColor = original; +} diff --git a/packages/core/src/filters/spriteMask/spriteMaskFilter.vert b/packages/core/src/filters/spriteMask/spriteMaskFilter.vert new file mode 100644 index 0000000..d23a30c --- /dev/null +++ b/packages/core/src/filters/spriteMask/spriteMaskFilter.vert @@ -0,0 +1,16 @@ +attribute vec2 aVertexPosition; +attribute vec2 aTextureCoord; + +uniform mat3 projectionMatrix; +uniform mat3 otherMatrix; + +varying vec2 vMaskCoord; +varying vec2 vTextureCoord; + +void main(void) +{ + gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); + + vTextureCoord = aTextureCoord; + vMaskCoord = ( otherMatrix * vec3( aTextureCoord, 1.0) ).xy; +} diff --git a/packages/core/src/framebuffer/FrameBuffer.js b/packages/core/src/framebuffer/FrameBuffer.js new file mode 100644 index 0000000..84e9fcc --- /dev/null +++ b/packages/core/src/framebuffer/FrameBuffer.js @@ -0,0 +1,106 @@ +import Texture from '../textures/BaseTexture'; +import { FORMATS, TYPES } from '@pixi/constants'; + +/** + * Frame buffer + * @class + * @memberof PIXI + */ +export default class FrameBuffer +{ + constructor(width, height) + { + this.width = width || 100; + this.height = height || 100; + + this.stencil = false; + this.depth = false; + + this.dirtyId = 0; + this.dirtyFormat = 0; + this.dirtySize = 0; + + this.depthTexture = null; + this.colorTextures = []; + + this.glFrameBuffers = {}; + } + + get colorTexture() + { + return this.colorTextures[0]; + } + + addColorTexture(index, texture) + { + // TODO add some validation to the texture - same width / height etc? + this.colorTextures[index || 0] = texture || new Texture(null, { scaleMode: 0, + resolution: 1, + mipmap: false, + width: this.width, + height: this.height });// || new Texture(); + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + addDepthTexture(texture) + { + /* eslint-disable max-len */ + this.depthTexture = texture || new Texture(null, { scaleMode: 0, + resolution: 1, + width: this.width, + height: this.height, + mipmap: false, + format: FORMATS.DEPTH_COMPONENT, + type: TYPES.UNSIGNED_SHORT });// UNSIGNED_SHORT; + /* eslint-disable max-len */ + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + enableDepth() + { + this.depth = true; + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + enableStencil() + { + this.stencil = true; + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + resize(width, height) + { + if (width === this.width && height === this.height) return; + + this.width = width; + this.height = height; + + this.dirtyId++; + this.dirtySize++; + + for (let i = 0; i < this.colorTextures.length; i++) + { + this.colorTextures[i].setSize(width, height); + } + + if (this.depthTexture) + { + this.depthTexture.setSize(width, height); + } + } +} diff --git a/packages/core/src/framebuffer/FramebufferSystem.js b/packages/core/src/framebuffer/FramebufferSystem.js new file mode 100644 index 0000000..2cc2437 --- /dev/null +++ b/packages/core/src/framebuffer/FramebufferSystem.js @@ -0,0 +1,246 @@ +import System from '../System'; +import { Rectangle } from '@pixi/math'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class FramebufferSystem extends System +{ + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + contextChange() + { + this.gl = this.renderer.gl; + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + this.current = null; + this.viewport = new Rectangle(); + + this.drawBufferExtension = this.renderer.context.extensions.drawBuffers; + } + + bind(framebuffer, frame) + { + const gl = this.gl; + + this.current = framebuffer; + + if (framebuffer) + { + // TODO cacheing layer! + + const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID] || this.initFramebuffer(framebuffer); + + gl.bindFramebuffer(gl.FRAMEBUFFER, fbo.framebuffer); + // makesure all textures are unbound.. + + // now check for updates... + if (fbo.dirtyId !== framebuffer.dirtyId) + { + fbo.dirtyId = framebuffer.dirtyId; + + if (fbo.dirtyFormat !== framebuffer.dirtyFormat) + { + fbo.dirtyFormat = framebuffer.dirtyFormat; + this.updateFramebuffer(framebuffer); + } + else if (fbo.dirtySize !== framebuffer.dirtySize) + { + fbo.dirtySize = framebuffer.dirtySize; + this.resizeFramebuffer(framebuffer); + } + } + + for (let i = 0; i < framebuffer.colorTextures.length; i++) + { + if (framebuffer.colorTextures[i].texturePart) + { + this.renderer.texture.unbind(framebuffer.colorTextures[i].texture); + } + else + { + this.renderer.texture.unbind(framebuffer.colorTextures[i]); + } + } + + if (framebuffer.depthTexture) + { + this.renderer.texture.unbind(framebuffer.depthTexture); + } + + if (frame) + { + this.setViewport(frame.x, frame.y, frame.width, frame.height); + } + else + { + this.setViewport(0, 0, framebuffer.width, framebuffer.height); + } + } + else + { + gl.bindFramebuffer(gl.FRAMEBUFFER, null); + + if (frame) + { + this.setViewport(frame.x, frame.y, frame.width, frame.height); + } + else + { + this.setViewport(0, 0, this.renderer.width, this.renderer.height); + } + } + } + + setViewport(x, y, width, height) + { + const v = this.viewport; + + if (v.width !== width || v.height !== height || v.x !== x || v.y !== y) + { + v.x = x; + v.y = y; + v.width = width; + v.height = height; + + this.gl.viewport(x, y, width, height); + } + } + + get size() + { + if (this.current) + { + // TODO store temp + return { x: 0, y: 0, width: this.current.width, height: this.current.height }; + } + + return { x: 0, y: 0, width: this.renderer.width, height: this.renderer.height }; + } + + clear(r, g, b, a) + { + const gl = this.gl; + + // TODO clear color can be set only one right? + gl.clearColor(r, g, b, a); + gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); + } + + // private functions... + + initFramebuffer(framebuffer) + { + const gl = this.gl; + + // TODO - make this a class? + const fbo = { + framebuffer: gl.createFramebuffer(), + stencil: null, + dirtyId: 0, + dirtyFormat: 0, + dirtySize: 0, + }; + + framebuffer.glFrameBuffers[this.CONTEXT_UID] = fbo; + + return fbo; + } + + resizeFramebuffer(framebuffer) + { + const gl = this.gl; + + if (framebuffer.stencil || framebuffer.depth) + { + gl.bindRenderbuffer(gl.RENDERBUFFER, this.stencil); + gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); + } + } + + updateFramebuffer(framebuffer) + { + const gl = this.gl; + + const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID]; + + // bind the color texture + const colorTextures = framebuffer.colorTextures; + + let count = colorTextures.length; + + if (!this.drawBufferExtension) + { + count = Math.min(count, 1); + } + + const activeTextures = []; + + for (let i = 0; i < count; i++) + { + const texture = framebuffer.colorTextures[i]; + + if (texture.texturePart) + { + this.renderer.texture.bind(texture.texture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.COLOR_ATTACHMENT0 + i, + gl.TEXTURE_CUBE_MAP_NEGATIVE_X + texture.side, + texture.texture._glTextures[this.CONTEXT_UID].texture, + 0); + } + else + { + this.renderer.texture.bind(texture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.COLOR_ATTACHMENT0 + i, + gl.TEXTURE_2D, + texture._glTextures[this.CONTEXT_UID].texture, + 0); + } + + activeTextures.push(gl.COLOR_ATTACHMENT0 + i); + } + + if (this.drawBufferExtension && activeTextures.length > 1) + { + this.drawBufferExtension.drawBuffersWEBGL(activeTextures); + } + + if (framebuffer.depthTexture) + { + const depthTextureExt = this.renderer.context.extensions.depthTexture; + + if (depthTextureExt) + { + const depthTexture = framebuffer.depthTexture; + + this.renderer.texture.bind(depthTexture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.DEPTH_ATTACHMENT, + gl.TEXTURE_2D, + depthTexture._glTextures[this.CONTEXT_UID].texture, + 0); + } + } + + if (framebuffer.stencil || framebuffer.depth) + { + fbo.stencil = gl.createRenderbuffer(); + + gl.bindRenderbuffer(gl.RENDERBUFFER, fbo.stencil); + + // TODO.. this is depth AND stencil? + gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, fbo.stencil); + gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); + // fbo.enableStencil(); + } + } +} diff --git a/packages/core/src/geometry/GLBuffer.js b/packages/core/src/geometry/GLBuffer.js new file mode 100644 index 0000000..0477e8a --- /dev/null +++ b/packages/core/src/geometry/GLBuffer.js @@ -0,0 +1,9 @@ +export default class GLBuffer +{ + constructor(buffer) + { + this.buffer = buffer; + this.updateID = -1; + this.byteLength = -1; + } +} diff --git a/packages/core/src/geometry/GeometrySystem.js b/packages/core/src/geometry/GeometrySystem.js new file mode 100644 index 0000000..959f827 --- /dev/null +++ b/packages/core/src/geometry/GeometrySystem.js @@ -0,0 +1,388 @@ +import System from '../System'; +import GLBuffer from './GLBuffer'; + +const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class GeometrySystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this._activeGeometry = null; + this._activeVao = null; + + this.hasVao = true; + this.hasInstance = true; + } + + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + contextChange() + { + const gl = this.gl = this.renderer.gl; + + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + + // webgl2 + if (!gl.createVertexArray) + { + // webgl 1! + let nativeVaoExtension = this.renderer.context.extensions.vertexArrayObject; + + if (this.renderer.legacy) + { + nativeVaoExtension = null; + } + + if (nativeVaoExtension) + { + gl.createVertexArray = () => + nativeVaoExtension.createVertexArrayOES(); + + gl.bindVertexArray = (vao) => + nativeVaoExtension.bindVertexArrayOES(vao); + + gl.deleteVertexArray = (vao) => + nativeVaoExtension.deleteVertexArrayOES(vao); + } + else + { + this.hasVao = false; + gl.createVertexArray = () => + { + // empty + }; + + gl.bindVertexArray = () => + { + // empty + }; + + gl.deleteVertexArray = () => + { + // empty + }; + } + } + + if (!gl.vertexAttribDivisor) + { + const instanceExt = gl.getExtension('ANGLE_instanced_arrays'); + + if (instanceExt) + { + gl.vertexAttribDivisor = (a, b) => + instanceExt.vertexAttribDivisorANGLE(a, b); + + gl.drawElementsInstanced = (a, b, c, d, e) => + instanceExt.drawElementsInstancedANGLE(a, b, c, d, e); + + gl.drawArraysInstanced = (a, b, c, d) => + instanceExt.drawArraysInstancedANGLE(a, b, c, d); + } + else + { + this.hasInstance = false; + } + } + } + + /** + * Binds geometry so that is can be drawn. Creating a Vao if required + * @private + * @param {PIXI.Geometry} geometry instance of geometry to bind + */ + bind(geometry, shader) + { + shader = shader || this.renderer.shader.shader; + + const gl = this.gl; + + // not sure the best way to address this.. + // currently different shaders require different VAOs for the same geometry + // Still mulling over the best way to solve this one.. + // will likely need to modify the shader attribute locations at run time! + let vaos = geometry.glVertexArrayObjects[this.CONTEXT_UID]; + + if (!vaos) + { + geometry.glVertexArrayObjects[this.CONTEXT_UID] = vaos = {}; + } + + const vao = vaos[shader.program.id] || this.initGeometryVao(geometry, shader.program); + + this._activeGeometry = geometry; + + if (this._activeVao !== vao) + { + this._activeVao = vao; + + if (this.hasVao) + { + gl.bindVertexArray(vao); + } + else + { + this.activateVao(geometry, shader.program); + } + } + + // TODO - optimise later! + // don't need to loop through if nothing changed! + // maybe look to add an 'autoupdate' to geometry? + this.updateBuffers(); + } + + reset() + { + this.unbind(); + } + + updateBuffers() + { + const geometry = this._activeGeometry; + const gl = this.gl; + + for (let i = 0; i < geometry.buffers.length; i++) + { + const buffer = geometry.buffers[i]; + + const glBuffer = buffer._glBuffers[this.CONTEXT_UID]; + + if (buffer._updateID !== glBuffer.updateID) + { + glBuffer.updateID = buffer._updateID; + + // TODO can cache this on buffer! maybe added a getter / setter? + const type = buffer.index ? gl.ELEMENT_ARRAY_BUFFER : gl.ARRAY_BUFFER; + const drawType = buffer.static ? gl.STATIC_DRAW : gl.DYNAMIC_DRAW; + + gl.bindBuffer(type, glBuffer.buffer); + + if (glBuffer.byteLength >= buffer.data.byteLength) + { + // offset is always zero for now! + gl.bufferSubData(type, 0, buffer.data); + } + else + { + gl.bufferData(type, buffer.data, drawType); + } + } + } + } + + checkCompatability(geometry, program) + { + // geometry must have at least all the attributes that the shader requires. + const geometryAttributes = geometry.attributes; + const shaderAttributes = program.attributeData; + + for (const j in shaderAttributes) + { + if (!geometryAttributes[j]) + { + throw new Error(`shader and geometry incompatible, geometry missing the "${j}" attribute`); + } + } + } + + /** + * Creates a Vao with the same structure as the geometry and stores it on the geometry. + * @private + * @param {PIXI.Geometry} geometry instance of geometry to to generate Vao for + */ + initGeometryVao(geometry, program) + { + this.checkCompatability(geometry, program); + + const gl = this.gl; + const CONTEXT_UID = this.CONTEXT_UID; + const buffers = geometry.buffers; + const attributes = geometry.attributes; + + const tempStride = {}; + const tempStart = {}; + + for (const j in buffers) + { + tempStride[j] = 0; + tempStart[j] = 0; + } + + for (const j in attributes) + { + if (!attributes[j].size && program.attributeData[j]) + { + attributes[j].size = program.attributeData[j].size; + } + + tempStride[attributes[j].buffer] += attributes[j].size * byteSizeMap[attributes[j].type]; + } + + for (const j in attributes) + { + const attribute = attributes[j]; + const attribSize = attribute.size; + + if (attribute.stride === undefined) + { + if (tempStride[attribute.buffer] === attribSize * byteSizeMap[attribute.type]) + { + attribute.stride = 0; + } + else + { + attribute.stride = tempStride[attribute.buffer]; + } + } + + if (attribute.start === undefined) + { + attribute.start = tempStart[attribute.buffer]; + + tempStart[attribute.buffer] += attribSize * byteSizeMap[attribute.type]; + } + } + + // first update - and create the buffers! + // only create a gl buffer if it actually gets + for (let i = 0; i < buffers.length; i++) + { + const buffer = buffers[i]; + + if (!buffer._glBuffers[CONTEXT_UID]) + { + buffer._glBuffers[CONTEXT_UID] = new GLBuffer(gl.createBuffer()); + } + } + + // TODO - maybe make this a data object? + // lets wait to see if we need to first! + const vao = gl.createVertexArray(); + + gl.bindVertexArray(vao); + + this.activateVao(geometry, program); + + gl.bindVertexArray(null); + + // add it to the cache! + geometry.glVertexArrayObjects[this.CONTEXT_UID][program.id] = vao; + + return vao; + } + + activateVao(geometry, program) + { + const gl = this.gl; + const CONTEXT_UID = this.CONTEXT_UID; + const buffers = geometry.buffers; + const attributes = geometry.attributes; + + if (geometry.indexBuffer) + { + // first update the index buffer if we have one.. + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, geometry.indexBuffer._glBuffers[CONTEXT_UID].buffer); + } + + let lastBuffer = null; + + // add a new one! + for (const j in attributes) + { + const attribute = attributes[j]; + const buffer = buffers[attribute.buffer]; + const glBuffer = buffer._glBuffers[CONTEXT_UID]; + + if (program.attributeData[j]) + { + if (lastBuffer !== glBuffer) + { + gl.bindBuffer(gl.ARRAY_BUFFER, glBuffer.buffer); + + lastBuffer = glBuffer; + } + + const location = program.attributeData[j].location; + + // TODO introduce state again + // we can optimise this for older devices that have no VAOs + gl.enableVertexAttribArray(location); + + gl.vertexAttribPointer(location, + attribute.size, + attribute.type || gl.FLOAT, + attribute.normalized, + attribute.stride, + attribute.start); + + if (attribute.instance) + { + // TODO calculate instance count based of this... + if (this.hasInstance) + { + gl.vertexAttribDivisor(location, 1); + } + else + { + throw new Error('geometry error, GPU Instancing is not supported on this device'); + } + } + } + } + } + + draw(type, size, start, instanceCount) + { + const gl = this.gl; + const geometry = this._activeGeometry; + + // TODO.. this should not change so maybe cache the function? + + if (geometry.indexBuffer) + { + if (geometry.instanced) + { + /* eslint-disable max-len */ + gl.drawElementsInstanced(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2, instanceCount || 1); + /* eslint-enable max-len */ + } + else + { + gl.drawElements(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2); + } + } + else + if (geometry.instanced) + { + // TODO need a better way to calculate size.. + gl.drawArraysInstanced(type, start, size || geometry.getSize(), instanceCount || 1); + } + else + { + gl.drawArrays(type, start, size || geometry.getSize()); + } + + return this; + } + + unbind() + { + this.gl.bindVertexArray(null); + this._activeVao = null; + this._activeGeometry = null; + } +} diff --git a/packages/core/src/geometry/utils/setVertexAttribArrays.js b/packages/core/src/geometry/utils/setVertexAttribArrays.js new file mode 100644 index 0000000..23b6959 --- /dev/null +++ b/packages/core/src/geometry/utils/setVertexAttribArrays.js @@ -0,0 +1,54 @@ +// var GL_MAP = {}; + +/** + * @param gl {WebGLRenderingContext} The current WebGL context + * @param attribs {*} + * @param state {*} + */ +export default function setVertexAttribArrays(gl, attribs, state) +{ + let i; + + if (state) + { + const tempAttribState = state.tempAttribState; + const attribState = state.attribState; + + for (i = 0; i < tempAttribState.length; i++) + { + tempAttribState[i] = false; + } + + // set the new attribs + for (i = 0; i < attribs.length; i++) + { + tempAttribState[attribs[i].attribute.location] = true; + } + + for (i = 0; i < attribState.length; i++) + { + if (attribState[i] !== tempAttribState[i]) + { + attribState[i] = tempAttribState[i]; + + if (state.attribState[i]) + { + gl.enableVertexAttribArray(i); + } + else + { + gl.disableVertexAttribArray(i); + } + } + } + } + else + { + for (i = 0; i < attribs.length; i++) + { + const attrib = attribs[i]; + + gl.enableVertexAttribArray(attrib.attribute.location); + } + } +} diff --git a/packages/core/src/index.js b/packages/core/src/index.js index 58c456a..fd1f547 100644 --- a/packages/core/src/index.js +++ b/packages/core/src/index.js @@ -1,30 +1,30 @@ import * as resources from './textures/resources'; -import * as systems from './renderers/systems'; +import * as systems from './systems'; -export { resources }; export { systems }; +export { resources }; -export { default as Renderer } from './renderers/Renderer'; -export { default as AbstractRenderer } from './renderers/AbstractRenderer'; -export { default as FrameBuffer } from './textures/FrameBuffer'; +export { default as System } from './System'; +export { default as Renderer } from './Renderer'; +export { default as AbstractRenderer } from './AbstractRenderer'; +export { default as FrameBuffer } from './framebuffer/FrameBuffer'; export { default as CubeTexture } from './textures/CubeTexture'; export { default as BaseTexture } from './textures/BaseTexture'; export { default as Texture } from './textures/Texture'; export { default as TextureMatrix } from './textures/TextureMatrix'; -export { default as RenderTexture } from './textures/RenderTexture'; -export { default as BaseRenderTexture } from './textures/BaseRenderTexture'; +export { default as RenderTexture } from './renderTexture/RenderTexture'; +export { default as BaseRenderTexture } from './renderTexture/BaseRenderTexture'; export { default as TextureUvs } from './textures/TextureUvs'; -export { default as State } from './renderers/State'; -export { default as ObjectRenderer } from './renderers/utils/ObjectRenderer'; -export { default as Quad } from './renderers/utils/Quad'; -export { default as QuadUv } from './renderers/utils/QuadUv'; -export { default as checkMaxIfStatmentsInShader } from './renderers/utils/checkMaxIfStatmentsInShader'; +export { default as State } from './state/State'; +export { default as ObjectRenderer } from './batch/ObjectRenderer'; +export { default as Quad } from './utils/Quad'; +export { default as QuadUv } from './utils/QuadUv'; +export { default as checkMaxIfStatmentsInShader } from './shader/utils/checkMaxIfStatmentsInShader'; export { default as Shader } from './shader/Shader'; export { default as Program } from './shader/Program'; export { default as UniformGroup } from './shader/UniformGroup'; -export { default as SpriteMaskFilter } from './renderers/filters/spriteMask/SpriteMaskFilter'; -export { default as Filter } from './renderers/filters/Filter'; +export { default as SpriteMaskFilter } from './filters/spriteMask/SpriteMaskFilter'; +export { default as Filter } from './filters/Filter'; export { default as Attribute } from './geometry/Attribute'; export { default as Buffer } from './geometry/Buffer'; export { default as Geometry } from './geometry/Geometry'; - diff --git a/packages/core/src/mask/MaskSystem.js b/packages/core/src/mask/MaskSystem.js new file mode 100644 index 0000000..99174c2 --- /dev/null +++ b/packages/core/src/mask/MaskSystem.js @@ -0,0 +1,199 @@ +import System from '../System'; +import AlphaMaskFilter from '../filters/spriteMask/SpriteMaskFilter'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class MaskSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + // TODO - we don't need both! + this.scissor = false; + this.scissorData = null; + this.scissorRenderTarget = null; + + this.enableScissor = false; + + this.alphaMaskPool = []; + this.alphaMaskIndex = 0; + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.DisplayObject} target - Display Object to push the mask to + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + push(target, maskData) + { + // TODO the root check means scissor rect will not + // be used on render textures more info here: + // https://github.com/pixijs/pixi.js/pull/3545 + + if (maskData.texture) + { + this.pushSpriteMask(target, maskData); + } + else if (this.enableScissor + && !this.scissor + && this.renderer._activeRenderTarget.root + && !this.renderer.stencil.stencilMaskStack.length + && maskData.isFastRect()) + { + const matrix = maskData.worldTransform; + + let rot = Math.atan2(matrix.b, matrix.a); + + // use the nearest degree! + rot = Math.round(rot * (180 / Math.PI)); + + if (rot % 90) + { + this.pushStencilMask(maskData); + } + else + { + this.pushScissorMask(target, maskData); + } + } + else + { + this.pushStencilMask(maskData); + } + } + + /** + * Removes the last mask from the mask stack and doesn't return it. + * + * @param {PIXI.DisplayObject} target - Display Object to pop the mask from + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + pop(target, maskData) + { + if (maskData.texture) + { + this.popSpriteMask(target, maskData); + } + else if (this.enableScissor && !this.renderer.stencil.stencilMaskStack.length) + { + this.popScissorMask(target, maskData); + } + else + { + this.popStencilMask(target, maskData); + } + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.RenderTarget} target - Display Object to push the sprite mask to + * @param {PIXI.Sprite} maskData - Sprite to be used as the mask + */ + pushSpriteMask(target, maskData) + { + let alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; + + if (!alphaMaskFilter) + { + alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; + } + + alphaMaskFilter[0].resolution = this.renderer.resolution; + alphaMaskFilter[0].maskSprite = maskData; + + // TODO - may cause issues! + target.filterArea = maskData.getBounds(true); + + this.renderer.filterSystem.pushFilter(target, alphaMaskFilter); + + this.alphaMaskIndex++; + } + + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popSpriteMask() + { + this.renderer.filterSystem.popFilter(); + this.alphaMaskIndex--; + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + pushStencilMask(maskData) + { + this.renderer.batch.flush(); + this.renderer.stencil.pushStencil(maskData); + } + + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popStencilMask() + { + // this.renderer.currentRenderer.stop(); + this.renderer.stencil.popStencil(); + } + + /** + * + * @param {PIXI.DisplayObject} target - Display Object to push the mask to + * @param {PIXI.Graphics} maskData - The masking data. + */ + pushScissorMask(target, maskData) + { + maskData.renderable = true; + + const renderTarget = this.renderer._activeRenderTarget; + + const bounds = maskData.getBounds(); + + bounds.fit(renderTarget.size); + maskData.renderable = false; + + this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); + + const resolution = this.renderer.resolution; + + this.renderer.gl.scissor( + bounds.x * resolution, + (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, + bounds.width * resolution, + bounds.height * resolution + ); + + this.scissorRenderTarget = renderTarget; + this.scissorData = maskData; + this.scissor = true; + } + + /** + * + * + */ + popScissorMask() + { + this.scissorRenderTarget = null; + this.scissorData = null; + this.scissor = false; + + // must be scissor! + const gl = this.renderer.gl; + + gl.disable(gl.SCISSOR_TEST); + } +} diff --git a/packages/core/src/mask/StencilSystem.js b/packages/core/src/mask/StencilSystem.js new file mode 100644 index 0000000..62796a5 --- /dev/null +++ b/packages/core/src/mask/StencilSystem.js @@ -0,0 +1,133 @@ +import System from '../System'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class StencilSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + this.stencilMaskStack = []; + } + + /** + * Changes the mask stack that is used by this System. + * + * @param {PIXI.Graphics[]} stencilMaskStack - The mask stack + */ + setMaskStack(stencilMaskStack) + { + const gl = this.renderer.gl; + + if (stencilMaskStack.length !== this.stencilMaskStack.length) + { + if (stencilMaskStack.length === 0) + { + gl.disable(gl.STENCIL_TEST); + } + else + { + gl.enable(gl.STENCIL_TEST); + } + } + + this.stencilMaskStack = stencilMaskStack; + } + + /** + * Applies the Mask and adds it to the current stencil stack. @alvin + * + * @param {PIXI.Graphics} graphics - The mask + */ + pushStencil(graphics) + { + this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); + + // this.renderer._activeRenderTarget.attachStencilBuffer(); + + const gl = this.renderer.gl; + const prevMaskCount = this.stencilMaskStack.length; + + if (prevMaskCount === 0) + { + gl.enable(gl.STENCIL_TEST); + } + + this.stencilMaskStack.push(graphics); + + // Increment the refference stencil value where the new mask overlaps with the old ones. + gl.colorMask(false, false, false, false); + gl.stencilFunc(gl.EQUAL, prevMaskCount, this._getBitwiseMask()); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.INCR); + this.renderer.plugins.graphics.render(graphics); + + this._useCurrent(); + } + + /** + * Removes the last mask from the stencil stack. @alvin + */ + popStencil() + { + this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); + + const gl = this.renderer.gl; + const graphics = this.stencilMaskStack.pop(); + + if (this.stencilMaskStack.length === 0) + { + // the stack is empty! + gl.disable(gl.STENCIL_TEST); + gl.clear(gl.STENCIL_BUFFER_BIT); + gl.clearStencil(0); + } + else + { + // Decrement the refference stencil value where the popped mask overlaps with the other ones + gl.colorMask(false, false, false, false); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.DECR); + this.renderer.plugins.graphics.render(graphics); + + this._useCurrent(); + } + } + + /** + * Setup renderer to use the current stencil data. + */ + _useCurrent() + { + const gl = this.renderer.gl; + + gl.colorMask(true, true, true, true); + gl.stencilFunc(gl.EQUAL, this.stencilMaskStack.length, this._getBitwiseMask()); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP); + } + + /** + * Fill 1s equal to the number of acitve stencil masks. + * + * @return {number} The bitwise mask. + */ + _getBitwiseMask() + { + return (1 << this.stencilMaskStack.length) - 1; + } + + /** + * Destroys the mask stack. + * + */ + destroy() + { + super.destroy(this); + + this.stencilMaskStack = null; + } +} diff --git a/packages/core/src/projection/ProjectionSystem.js b/packages/core/src/projection/ProjectionSystem.js new file mode 100644 index 0000000..6e63b73 --- /dev/null +++ b/packages/core/src/projection/ProjectionSystem.js @@ -0,0 +1,73 @@ +import System from '../System'; +import { Matrix } from '@pixi/math'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class ProjectionSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.projectionMatrix = new Matrix(); + } + + update(destinationFrame, sourceFrame, resolution, root) + { + this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; + this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; + + this.calculateProjection(this.destinationFrame, this.sourceFrame, resolution, root); + + this.renderer.globalUniforms.uniforms.projectionMatrix = this.projectionMatrix; + this.renderer.globalUniforms.update(); + } + + /** + * Updates the projection matrix based on a projection frame (which is a rectangle) + * + * @param {Rectangle} destinationFrame - The destination frame. + * @param {Rectangle} sourceFrame - The source frame. + */ + calculateProjection(destinationFrame, sourceFrame, resolution, root) + { + const pm = this.projectionMatrix; + + // I don't think we will need this line.. + // pm.identity(); + + if (!root) + { + pm.a = (1 / destinationFrame.width * 2) * resolution; + pm.d = (1 / destinationFrame.height * 2) * resolution; + + pm.tx = -1 - (sourceFrame.x * pm.a); + pm.ty = -1 - (sourceFrame.y * pm.d); + } + else + { + pm.a = (1 / destinationFrame.width * 2) * resolution; + pm.d = (-1 / destinationFrame.height * 2) * resolution; + + pm.tx = -1 - (sourceFrame.x * pm.a); + pm.ty = 1 - (sourceFrame.y * pm.d); + } + } + + /** + * Sets the transform of the active render target to the given matrix + * + * @param {PIXI.Matrix} matrix - The transformation matrix + */ + setTransform()// matrix) + { + // this._activeRenderTarget.transform = matrix; + } +} diff --git a/packages/core/src/renderTexture/BaseRenderTexture.js b/packages/core/src/renderTexture/BaseRenderTexture.js new file mode 100644 index 0000000..88eb83b --- /dev/null +++ b/packages/core/src/renderTexture/BaseRenderTexture.js @@ -0,0 +1,137 @@ +import BaseTexture from '../textures/BaseTexture'; +import FrameBuffer from '../framebuffer/FrameBuffer'; + +/** + * A BaseRenderTexture is a special texture that allows any PixiJS display object to be rendered to it. + * + * __Hint__: All DisplayObjects (i.e. Sprites) that render to a BaseRenderTexture should be preloaded + * otherwise black rectangles will be drawn instead. + * + * A BaseRenderTexture takes a snapshot of any Display Object given to its render method. The position + * and rotation of the given Display Objects is ignored. For example: + * + * ```js + * let renderer = PIXI.autoDetectRenderer(1024, 1024, { view: canvas, ratio: 1 }); + * let baseRenderTexture = new PIXI.BaseRenderTexture(renderer, 800, 600); + * let sprite = PIXI.Sprite.fromImage("spinObj_01.png"); + * + * sprite.position.x = 800/2; + * sprite.position.y = 600/2; + * sprite.anchor.x = 0.5; + * sprite.anchor.y = 0.5; + * + * baseRenderTexture.render(sprite); + * ``` + * + * The Sprite in this case will be rendered using its local transform. To render this sprite at 0,0 + * you can clear the transform + * + * ```js + * + * sprite.setTransform() + * + * let baseRenderTexture = new PIXI.BaseRenderTexture(100, 100); + * let renderTexture = new PIXI.RenderTexture(baseRenderTexture); + * + * renderer.render(sprite, renderTexture); // Renders to center of RenderTexture + * ``` + * + * @class + * @extends PIXI.BaseTexture + * @memberof PIXI + */ +export default class BaseRenderTexture extends BaseTexture +{ + /** + * @param {object} [options] + * @param {number} [options.width=100] - The width of the base render texture + * @param {number} [options.height=100] - The height of the base render texture + * @param {PIXI.SCALE_MODES} [options.scaleMode] - See {@link PIXI.SCALE_MODES} for possible values + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the texture being generated + */ + constructor(options) + { + if (typeof options === 'number') + { + /* eslint-disable prefer-rest-params */ + // Backward compatibility of signature + const width = arguments[0]; + const height = arguments[1]; + const scaleMode = arguments[2]; + const resolution = arguments[3]; + + options = { width, height, scaleMode, resolution }; + /* eslint-enable prefer-rest-params */ + } + + super(null, options); + + const { width, height } = options || {}; + + // Set defaults + this.mipmap = false; + this.width = Math.ceil(width) || 100; + this.height = Math.ceil(height) || 100; + this.valid = true; + + /** + * A map of renderer IDs to webgl renderTargets + * + * @private + * @member {object} + */ + // this._glRenderTargets = {}; + + /** + * A reference to the canvas render target (we only need one as this can be shared across renderers) + * + * @private + * @member {object} + */ + this._canvasRenderTarget = null; + + this.clearColor = [0, 0, 0, 0]; + + this.frameBuffer = new FrameBuffer(width * this.resolution, height * this.resolution) + .addColorTexture(0, this); + + // TODO - could this be added the systems? + + /** + * The data structure for the stencil masks + * + * @member {PIXI.Graphics[]} + */ + this.stencilMaskStack = []; + + /** + * The data structure for the filters + * + * @member {PIXI.Graphics[]} + */ + this.filterStack = [{}]; + } + + /** + * Resizes the BaseRenderTexture. + * + * @param {number} width - The width to resize to. + * @param {number} height - The height to resize to. + */ + resize(width, height) + { + width = Math.ceil(width); + height = Math.ceil(height); + this.frameBuffer.resize(width * this.resolution, height * this.resolution); + } + + /** + * Destroys this texture + * + */ + destroy() + { + super.destroy(true); + this.renderer = null; + } +} diff --git a/packages/core/src/renderTexture/RenderTexture.js b/packages/core/src/renderTexture/RenderTexture.js new file mode 100644 index 0000000..f1ecf1f --- /dev/null +++ b/packages/core/src/renderTexture/RenderTexture.js @@ -0,0 +1,153 @@ +import BaseRenderTexture from './BaseRenderTexture'; +import Texture from '../textures/Texture'; + +/** + * A RenderTexture is a special texture that allows any PixiJS display object to be rendered to it. + * + * __Hint__: All DisplayObjects (i.e. Sprites) that render to a RenderTexture should be preloaded + * otherwise black rectangles will be drawn instead. + * + * A RenderTexture takes a snapshot of any Display Object given to its render method. For example: + * + * ```js + * let renderer = PIXI.autoDetectRenderer(1024, 1024, { view: canvas, ratio: 1 }); + * let renderTexture = PIXI.RenderTexture.create(800, 600); + * let sprite = PIXI.Sprite.fromImage("spinObj_01.png"); + * + * sprite.position.x = 800/2; + * sprite.position.y = 600/2; + * sprite.anchor.x = 0.5; + * sprite.anchor.y = 0.5; + * + * renderer.render(sprite, renderTexture); + * ``` + * + * The Sprite in this case will be rendered using its local transform. To render this sprite at 0,0 + * you can clear the transform + * + * ```js + * + * sprite.setTransform() + * + * let renderTexture = new PIXI.RenderTexture.create(100, 100); + * + * renderer.render(sprite, renderTexture); // Renders to center of RenderTexture + * ``` + * + * @class + * @extends PIXI.Texture + * @memberof PIXI + */ +export default class RenderTexture extends Texture +{ + /** + * @param {PIXI.BaseRenderTexture} baseRenderTexture - The renderer used for this RenderTexture + * @param {PIXI.Rectangle} [frame] - The rectangle frame of the texture to show + */ + constructor(baseRenderTexture, frame) + { + // support for legacy.. + let _legacyRenderer = null; + + if (!(baseRenderTexture instanceof BaseRenderTexture)) + { + /* eslint-disable prefer-rest-params, no-console */ + const width = arguments[1]; + const height = arguments[2]; + const scaleMode = arguments[3]; + const resolution = arguments[4]; + + // we have an old render texture.. + console.warn(`Please use RenderTexture.create(${width}, ${height}) instead of the ctor directly.`); + _legacyRenderer = arguments[0]; + /* eslint-enable prefer-rest-params, no-console */ + + frame = null; + baseRenderTexture = new BaseRenderTexture({ + width, + height, + scaleMode, + resolution, + }); + } + + /** + * The base texture object that this texture uses + * + * @member {BaseTexture} + */ + super(baseRenderTexture, frame); + + this.legacyRenderer = _legacyRenderer; + + /** + * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. + * + * @member {boolean} + */ + this.valid = true; + + /** + * FilterSystem temporary storage + * @private + * @member {PIXI.Rectangle} + */ + this.filterFrame = null; + + this._updateUvs(); + } + + /** + * Resizes the RenderTexture. + * + * @param {number} width - The width to resize to. + * @param {number} height - The height to resize to. + * @param {boolean} [resizeBaseTexture=true] - Should the baseTexture.width and height values be resized as well? + */ + resize(width, height, resizeBaseTexture = true) + { + width = Math.ceil(width); + height = Math.ceil(height); + + // TODO - could be not required.. + this.valid = (width > 0 && height > 0); + + this._frame.width = this.orig.width = width; + this._frame.height = this.orig.height = height; + + if (resizeBaseTexture) + { + this.baseTexture.resize(width, height); + } + + this._updateUvs(); + } + + /** + * A short hand way of creating a render texture. + * + * @param {object} [options] - Options + * @param {number} [options.width=100] - The width of the render texture + * @param {number} [options.height=100] - The height of the render texture + * @param {number} [options.scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the texture being generated + * @return {PIXI.RenderTexture} The new render texture + */ + static create(options) + { + // fallback, old-style: create(width, height, scaleMode, resolution) + if (typeof options === 'number') + { + /* eslint-disable prefer-rest-params */ + options = { + width: options, + height: arguments[1], + scaleMode: arguments[2], + resolution: arguments[3], + }; + /* eslint-enable prefer-rest-params */ + } + + return new RenderTexture(new BaseRenderTexture(options)); + } +} diff --git a/packages/core/src/renderTexture/RenderTextureSystem.js b/packages/core/src/renderTexture/RenderTextureSystem.js new file mode 100644 index 0000000..8d0ca61 --- /dev/null +++ b/packages/core/src/renderTexture/RenderTextureSystem.js @@ -0,0 +1,115 @@ +import System from '../System'; +import { Rectangle } from '@pixi/math'; + +const tempRect = new Rectangle(); + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class RenderTextureSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.clearColor = renderer._backgroundColorRgba; + + // TODO moe this property somewhere else! + this.defaultMaskStack = []; + this.defaultFilterStack = [{}]; + + // empty render texture? + this.renderTexture = null; + + this.destinationFrame = new Rectangle(); + } + + bind(renderTexture, sourceFrame, destinationFrame) + { + // TODO - do we want this?? + if (this.renderTexture === renderTexture) return; + this.renderTexture = renderTexture; + + const renderer = this.renderer; + + if (renderTexture) + { + const baseTexture = renderTexture.baseTexture; + + if (!destinationFrame) + { + tempRect.width = baseTexture.realWidth; + tempRect.height = baseTexture.realHeight; + + destinationFrame = tempRect; + } + + if (!sourceFrame) + { + sourceFrame = destinationFrame; + } + + this.renderer.framebuffer.bind(baseTexture.frameBuffer, destinationFrame); + + this.renderer.projection.update(destinationFrame, sourceFrame, baseTexture.resolution, false); + this.renderer.stencil.setMaskStack(baseTexture.stencilMaskStack); + } + else + { + // TODO these validation checks happen deeper down.. + // thing they can be avoided.. + if (!destinationFrame) + { + tempRect.width = renderer.width; + tempRect.height = renderer.height; + + destinationFrame = tempRect; + } + + if (!sourceFrame) + { + sourceFrame = destinationFrame; + } + + renderer.framebuffer.bind(null, destinationFrame); + + // TODO store this.. + this.renderer.projection.update(destinationFrame, sourceFrame, this.renderer.resolution, true); + this.renderer.stencil.setMaskStack(this.defaultMaskStack); + } + + this.destinationFrame.copyFrom(destinationFrame); + } + + /** + * Erases the render texture and fills the drawing area with a colour + * + * @param {number} [clearColor] - The colour + * @return {PIXI.Renderer} Returns itself. + */ + clear(clearColor) + { + if (this.renderTexture) + { + clearColor = clearColor || this.renderTexture.baseTexture.clearColor; + } + else + { + clearColor = clearColor || this.clearColor; + } + + this.renderer.framebuffer.clear(clearColor[0], clearColor[1], clearColor[2], clearColor[3]); + } + + resize()// screenWidth, screenHeight) + { + // resize the root only! + this.bind(null); + } +} diff --git a/packages/core/src/renderers/AbstractRenderer.js b/packages/core/src/renderers/AbstractRenderer.js deleted file mode 100644 index 928670f..0000000 --- a/packages/core/src/renderers/AbstractRenderer.js +++ /dev/null @@ -1,353 +0,0 @@ -import { hex2string, hex2rgb } from '@pixi/utils'; -import { Matrix, Rectangle } from '@pixi/math'; -import { RENDERER_TYPE } from '@pixi/constants'; -import { settings } from '@pixi/settings'; -import { Container } from '@pixi/display'; -import RenderTexture from '../textures/RenderTexture'; -import EventEmitter from 'eventemitter3'; - -const tempMatrix = new Matrix(); - -/** - * 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 EventEmitter - * @memberof PIXI - */ -export default class AbstractRenderer extends EventEmitter -{ - // eslint-disable-next-line valid-jsdoc - /** - * @param {string} system - The name of the system this renderer is for. - * @param {object} [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.transparent=false] - If the render view is transparent, default false - * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false - * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) - * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. The - * resolution of the renderer retina would be 2. - * @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 {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when rendering, - * stopping pixel interpolation. - */ - constructor(system, options, arg2, arg3) - { - super(); - - // Support for constructor(system, screenWidth, screenHeight, options) - if (typeof options === 'number') - { - options = Object.assign({ - width: options, - height: arg2 || settings.RENDER_OPTIONS.height, - }, arg3); - } - - // 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 = RENDERER_TYPE.UNKNOWN; - - /** - * Measurements of the screen. (0, 0, screenWidth, screenHeight) - * - * Its safe to use as filterArea or hitArea for 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 1 - */ - this.resolution = options.resolution || settings.RESOLUTION; - - /** - * Whether the render view is transparent - * - * @member {boolean} - */ - this.transparent = options.transparent; - - /** - * Whether css dimensions of canvas view should be resized to screen dimensions automatically - * - * @member {boolean} - */ - this.autoResize = options.autoResize || false; - - /** - * Tracks the blend modes useful for this renderer. - * - * @member {object} - */ - this.blendModes = null; - - /** - * 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; - - /** - * If true PixiJS will Math.floor() x/y values when rendering, stopping pixel interpolation. - * Handy for crisp pixel art and speed on legacy devices. - * - * @member {boolean} - */ - this.roundPixels = options.roundPixels; - - /** - * The background color as a number. - * - * @member {number} - * @private - */ - this._backgroundColor = 0x000000; - - /** - * The background color as an [R, G, B] array. - * - * @member {number[]} - * @private - */ - this._backgroundColorRgba = [0, 0, 0, 0]; - - /** - * The background color as a string. - * - * @member {string} - * @private - */ - this._backgroundColorString = '#000000'; - - this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter - - /** - * This temporary display object used as the parent of the currently being rendered item - * - * @member {PIXI.DisplayObject} - * @private - */ - this._tempDisplayObjectParent = new Container(); - - /** - * The last root object that the renderer tried to render. - * - * @member {PIXI.DisplayObject} - * @private - */ - this._lastObjectRendered = this._tempDisplayObjectParent; - - /** - * Collection of plugins. - * @readonly - * @member {object} - */ - this.plugins = {}; - } - - /** - * Initialize the plugins. - * - * @protected - * @param {object} staticMap - The dictionary of staticly saved plugins. - */ - initPlugins(staticMap) - { - 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() - { - return this.view.width; - } - - /** - * Same as view.height, actual number of pixels in the canvas by vertical - * - * @member {number} - * @readonly - * @default 600 - */ - get height() - { - return this.view.height; - } - - /** - * Resizes the screen and canvas to the specified width and height - * Canvas dimensions are multiplied by resolution - * - * @param {number} screenWidth - the new width of the screen - * @param {number} screenHeight - the new height of the screen - */ - resize(screenWidth, screenHeight) - { - this.screen.width = screenWidth; - this.screen.height = screenHeight; - - this.view.width = screenWidth * this.resolution; - this.view.height = screenHeight * this.resolution; - - if (this.autoResize) - { - this.view.style.width = `${screenWidth}px`; - this.view.style.height = `${screenHeight}px`; - } - } - - /** - * 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 {PIXI.DisplayObject} displayObject - The displayObject the object will be generated from - * @param {number} scaleMode - Should be one of the scaleMode consts - * @param {number} resolution - The resolution / device pixel ratio of the texture being generated - * @param {PIXI.Rectangle} [region] - The region of the displayObject, that shall be rendered, - * if no region is specified, defaults to the local bounds of the displayObject. - * @return {PIXI.Texture} a texture of the graphics object - */ - generateTexture(displayObject, scaleMode, resolution, region) - { - region = region || displayObject.getLocalBounds(); - - // 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(region.width | 0, region.height | 0, scaleMode, resolution); - - tempMatrix.tx = -region.x; - tempMatrix.ty = -region.y; - - this.render(displayObject, renderTexture, false, tempMatrix, true); - - return renderTexture; - } - - /** - * Removes everything from the renderer and optionally removes the Canvas DOM element. - * - * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. - */ - destroy(removeView) - { - 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); - } - - this.plugins = null; - - this.type = RENDERER_TYPE.UNKNOWN; - - this.view = null; - - this.screen = null; - - this.resolution = 0; - - this.transparent = false; - - this.autoResize = false; - - this.blendModes = null; - - this.options = null; - - this.preserveDrawingBuffer = false; - this.clearBeforeRender = false; - - this.roundPixels = false; - - this._backgroundColor = 0; - this._backgroundColorRgba = null; - this._backgroundColorString = null; - - this._tempDisplayObjectParent = null; - this._lastObjectRendered = null; - } - - /** - * The background color to fill if not transparent - * - * @member {number} - */ - get backgroundColor() - { - return this._backgroundColor; - } - - set backgroundColor(value) // eslint-disable-line require-jsdoc - { - this._backgroundColor = value; - this._backgroundColorString = hex2string(value); - hex2rgb(value, this._backgroundColorRgba); - } -} diff --git a/packages/core/src/renderers/Renderer.js b/packages/core/src/renderers/Renderer.js deleted file mode 100644 index a2020b1..0000000 --- a/packages/core/src/renderers/Renderer.js +++ /dev/null @@ -1,329 +0,0 @@ -import AbstractRenderer from './AbstractRenderer'; -import { sayHello } from '@pixi/utils'; -import MaskSystem from './systems/MaskSystem'; -import StencilSystem from './systems/StencilSystem'; -import FilterSystem from './systems/filter/FilterSystem'; -import FramebufferSystem from './systems/FramebufferSystem'; -import RenderTextureSystem from './systems/RenderTextureSystem'; -import TextureSystem from './systems/textures/TextureSystem'; -import ProjectionSystem from './systems/ProjectionSystem'; -import StateSystem from './systems/StateSystem'; -import GeometrySystem from './systems/geometry/GeometrySystem'; -import ShaderSystem from './systems/shader/ShaderSystem'; -import ContextSystem from './systems/ContextSystem'; -import BatchSystem from './systems/BatchSystem'; -import TextureGCSystem from './systems/textures/TextureGCSystem'; -import { RENDERER_TYPE } from '@pixi/constants'; -import UniformGroup from '../shader/UniformGroup'; -import { Matrix } from '@pixi/math'; -import Runner from 'mini-runner'; - -/** - * 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 Render works by automatically managing webGLBatchs. - * 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 :) - * - * @class - * @memberof PIXI - * @extends PIXI.AbstractRenderer - */ -export default class Renderer extends AbstractRenderer -{ - // eslint-disable-next-line valid-jsdoc - /** - * - * @param {object} [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.transparent=false] - If the render view is transparent, default false - * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false - * @param {boolean} [options.antialias=false] - sets antialias. If not available natively then FXAA - * antialiasing is used - * @param {boolean} [options.forceFXAA=false] - forces FXAA antialiasing to be used over native. - * FXAA is faster, but may not always look as great - * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. - * The resolution of the renderer retina would be 2. - * @param {boolean} [options.clearBeforeRender=true] - This sets if the renderer will clear - * the canvas or not before the new render pass. If you wish to set this to false, you *must* set - * preserveDrawingBuffer to `true`. - * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, - * enable this if you need to call toDataUrl on the webgl context. - * @param {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when - * rendering, stopping pixel interpolation. - * @param {number} [options.backgroundColor=0x000000] - The background color of the rendered area - * (shown if not transparent). - * @param {boolean} [options.legacy=false] - If true PixiJS will aim to ensure compatibility - * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. - * @param {string} [options.powerPreference] - Parameter passed to webgl context, set to "high-performance" - * for devices with dual graphics card - */ - constructor(options, arg2, arg3) - { - super('WebGL', options, arg2, arg3); - - /** - * The type of this renderer as a standardised const - * - * @member {number} - * @see PIXI.RENDERER_TYPE - */ - this.type = RENDERER_TYPE.WEBGL; - - // this will be set by the contextSystem (this.context) - this.gl = null; - this.CONTEXT_UID = 0; - this.legacy = !!options.legacy; - - // TODO legacy! - - // runners! - this.runners = { - destroy: new Runner('destroy'), - contextChange: new Runner('contextChange', 1), - reset: new Runner('reset'), - update: new Runner('update'), - postrender: new Runner('postrender'), - prerender: new Runner('prerender'), - resize: new Runner('resize', 2), - }; - - 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(GeometrySystem, 'geometry') - .addSystem(FramebufferSystem, 'framebuffer') - .addSystem(StencilSystem, 'stencil') - .addSystem(ProjectionSystem, 'projection') - .addSystem(TextureGCSystem, 'textureGC') - .addSystem(FilterSystem, 'filter') - .addSystem(RenderTextureSystem, 'renderTexture') - .addSystem(BatchSystem, 'batch'); - - this.initPlugins(Renderer.__plugins); - - /** - * The options passed in to create a new webgl context. - * - * @member {object} - * @private - */ - if (options.context) - { - this.context.initFromContext(options.context); - } - else - { - this.context.initFromOptions({ - alpha: this.transparent, - antialias: options.antialias, - premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', - stencil: true, - 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); - } - - addSystem(_class, name) - { - if (!name) - { - name = _class.name; - } - - // TODO - read name from class.name.. - - /* - if(name.includes('System')) - { - name = name.replace('System', ''); - name = name.charAt(0).toLowerCase() + name.slice(1); - } - */ - - const system = new _class(this); - - if (this[name]) - { - throw new Error(`Whoops! ${name} is already a manger`); - } - - this[name] = system; - - for (const i in this.runners) - { - this.runners[i].add(system); - } - - return this; - - /** - * 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. - */ - } - - /** - * Renders the object to its webGL view - * - * @param {PIXI.DisplayObject} displayObject - the object to be rendered - * @param {PIXI.RenderTexture} renderTexture - The render texture to render to. - * @param {boolean} [clear] - Should the canvas be cleared before the new render - * @param {PIXI.Transform} [transform] - A transform to apply to the render texture before rendering. - * @param {boolean} [skipUpdateTransform] - Should we skip the update transform pass? - */ - render(displayObject, renderTexture, clear, transform, skipUpdateTransform) - { - // can be handy to know! - this.renderingToScreen = !renderTexture; - - this.runners.prerender.run(); - this.emit('prerender'); - - // 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.parent; - - displayObject.parent = this._tempDisplayObjectParent; - displayObject.updateTransform(); - displayObject.parent = 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.run(); - - this.emit('postrender'); - } - - /** - * Resizes the webGL view to the specified width and height. - * - * @param {number} screenWidth - the new width of the screen - * @param {number} screenHeight - the new height of the screen - */ - resize(screenWidth, screenHeight) - { - AbstractRenderer.prototype.resize.call(this, screenWidth, screenHeight); - - this.runners.resize.run(screenWidth, screenHeight); - } - - /** - * Resets the WebGL state so you can render things however you fancy! - * - * @return {PIXI.Renderer} Returns itself. - */ - reset() - { - this.runners.reset.run(); - - return this; - } - - clear() - { - this.framebuffer.bind(); - this.framebuffer.clear(); - } - - /** - * Removes everything from the renderer (event listeners, spritebatch, etc...) - * - * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. - * See: https://github.com/pixijs/pixi.js/issues/2233 - */ - destroy(removeView) - { - this.runners.destroy.run(); - - // call base destroy - super.destroy(removeView); - - // TODO nullify all the managers.. - this.gl = null; - } - - /** - * 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 - * builds and excluding plugins. - * @name PIXI.Renderer#plugins - * @type {object} - * @readonly - * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. - * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. - * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. - * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. - */ - - /** - * Adds a plugin to the renderer. - * - * @method - * @param {string} pluginName - The name of the plugin. - * @param {Function} ctor - The constructor function or class for the plugin. - */ - static registerPlugin(pluginName, ctor) - { - Renderer.__plugins = Renderer.__plugins || {}; - Renderer.__plugins[pluginName] = ctor; - } -} diff --git a/packages/core/src/renderers/State.js b/packages/core/src/renderers/State.js deleted file mode 100644 index fa3f221..0000000 --- a/packages/core/src/renderers/State.js +++ /dev/null @@ -1,163 +0,0 @@ -/* eslint-disable max-len */ - -const BLEND = 0; -const OFFSET = 1; -const CULLING = 2; -const DEPTH_TEST = 3; -const WINDING = 4; - -/** - * This is a webGL state. It is passed The WebGL StateManager. - * Each mesh renderered may require webGL to be in a different state. - * For example you may want different blend mode or to enable polygon offsets - * - * @class - * @memberof PIXI - */ -export default class State -{ - /** - * - */ - constructor() - { - this.data = 0; - - this.blendMode = 0; - this.polygonOffset = 0; - - this.blend = true; - // this.depthTest = true; - } - - /** - * Activates blending of the computed fragment color values - * - * @member {boolean} - */ - get blend() - { - return !!(this.data & (1 << BLEND)); - } - - set blend(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << BLEND)) !== value) - { - this.data ^= (1 << BLEND); - } - } - - /** - * Activates adding an offset to depth values of polygon's fragments - * - * @member {boolean} - * @default false - */ - get offsets() - { - return !!(this.data & (1 << OFFSET)); - } - - set offsets(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << OFFSET)) !== value) - { - this.data ^= (1 << OFFSET); - } - } - - /** - * Activates culling of polygons. - * - * @member {boolean} - * @default false - */ - get culling() - { - return !!(this.data & (1 << CULLING)); - } - - set culling(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << CULLING)) !== value) - { - this.data ^= (1 << CULLING); - } - } - - /** - * Activates depth comparisons and updates to the depth buffer. - * - * @member {boolean} - * @default false - */ - get depthTest() - { - return !!(this.data & (1 << DEPTH_TEST)); - } - - set depthTest(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << DEPTH_TEST)) !== value) - { - this.data ^= (1 << DEPTH_TEST); - } - } - - /** - * Specifies whether or not front or back-facing polygons can be culled. - * @member {boolean} - * @default false - */ - get clockwiseFrontFace() - { - return !!(this.data & (1 << WINDING)); - } - - set clockwiseFrontFace(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << WINDING)) !== value) - { - this.data ^= (1 << WINDING); - } - } - - /** - * The blend mode to be applied when this state is set. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. - * Setting this mode to anything other than NO_BLEND will automatically switch blending on. - * - * @member {boolean} - * @default PIXI.BLEND_MODES.NORMAL - * @see PIXI.BLEND_MODES - */ - get blendMode() - { - return this._blendMode; - } - - set blendMode(value) // eslint-disable-line require-jsdoc - { - // 17 is NO BLEND - this.blend = (value !== 17); - this._blendMode = value; - } - - /** - * The polygon offset. Setting this property to anything other than 0 will automatically enable poygon offset fill. - * - * @member {number} - * @default 0 - */ - get polygonOffset() - { - return this._polygonOffset; - } - - set polygonOffset(value) // eslint-disable-line require-jsdoc - { - this.offsets = !!value; - this._polygonOffset = value; - } -} - diff --git a/packages/core/src/renderers/filters/Filter.js b/packages/core/src/renderers/filters/Filter.js deleted file mode 100644 index 2073b8d..0000000 --- a/packages/core/src/renderers/filters/Filter.js +++ /dev/null @@ -1,140 +0,0 @@ -import Shader from '../../shader/Shader'; -import Program from '../../shader/Program'; -import State from '../State'; -import { settings } from '@pixi/settings'; -// import extractUniformsFromSrc from './extractUniformsFromSrc'; -import defaultVertex from './defaultFilter.vert'; -import defaultFragment from './defaultFilter.frag'; - -// let math = require('../../math'); -/** - * @class - * @memberof PIXI - * @extends PIXI.Shader - */ -export default class Filter extends Shader -{ - /** - * @param {string} [vertexSrc] - The source of the vertex shader. - * @param {string} [fragmentSrc] - The source of the fragment shader. - * @param {object} [uniforms] - Custom uniforms to use to augment the built-in ones. - */ - constructor(vertexSrc, fragmentSrc, uniforms) - { - const program = Program.from(vertexSrc, fragmentSrc); - - super(program, uniforms); - - /** - * The padding of the filter. Some filters require extra space to breath such as a blur. - * Increasing this will add extra width and height to the bounds of the object that the - * filter is applied to. - * - * @member {number} - */ - this.padding = 0; - - /** - * The resolution of the filter. Setting this to be lower will lower the quality but - * increase the performance of the filter. - * - * @member {number} - */ - this.resolution = settings.RESOLUTION; - - /** - * If enabled is true the filter is applied, if false it will not. - * - * @member {boolean} - */ - this.enabled = true; - - /** - * If enabled, PixiJS will fit the filter area into boundaries for better performance. - * Switch it off if it does not work for specific shader. - * - * @member {boolean} - */ - this.autoFit = true; - - /** - * Legacy filters use position and uvs from attributes - * @member {boolean} - * @readonly - */ - this.legacy = !!this.program.attributeData.aTextureCoord; - - /** - * the webGL state the filter requires to render - * @member {PIXI.State} - */ - this.state = new State(); - } - - /** - * Applies the filter - * - * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from - * @param {PIXI.RenderTarget} input - The input render target. - * @param {PIXI.RenderTarget} output - The target to output to. - * @param {boolean} clear - Should the output be cleared before rendering to it - * @param {object} [currentState] - It's current state of filter. - * There are some useful properties in the currentState : - * target, filters, sourceFrame, destinationFrame, renderTarget, resolution - */ - apply(filterManager, input, output, clear, currentState, derp) // eslint-disable-line no-unused-vars - { - // do as you please! - - filterManager.applyFilter(this, input, output, clear, currentState, derp); - - // or just do a regular render.. - } - - /** - * Sets the blendmode of the filter - * - * @member {number} - * @default PIXI.BLEND_MODES.NORMAL - */ - get blendMode() - { - return this.state.blendMode; - } - - set blendMode(value) // eslint-disable-line require-jsdoc - { - this.state.blendMode = value; - } - - /** - * The default vertex shader source - * - * @static - * @constant - */ - static get defaultVertexSrc() - { - return defaultVertex; - } - - /** - * The default fragment shader source - * - * @static - * @constant - */ - static get defaultFragmentSrc() - { - return defaultFragment; - } -} - -/** - * Used for caching shader IDs - * - * @static - * @private - */ -Filter.SOURCE_KEY_MAP = {}; - diff --git a/packages/core/src/renderers/filters/defaultFilter.frag b/packages/core/src/renderers/filters/defaultFilter.frag deleted file mode 100644 index a14c1be..0000000 --- a/packages/core/src/renderers/filters/defaultFilter.frag +++ /dev/null @@ -1,22 +0,0 @@ -varying vec2 vTextureCoord; -varying vec2 vFilterCoord; - -uniform sampler2D uSampler; -uniform sampler2D filterSampler; - -void main(void){ - vec4 masky = texture2D(filterSampler, vFilterCoord); - vec4 sample = texture2D(uSampler, vTextureCoord); - vec4 color; - if(mod(vFilterCoord.x, 1.0) > 0.5) - { - color = vec4(1.0, 0.0, 0.0, 1.0); - } - else - { - color = vec4(0.0, 1.0, 0.0, 1.0); - } - // gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0); - gl_FragColor = mix(sample, masky, 0.5); - gl_FragColor *= sample.a; -} diff --git a/packages/core/src/renderers/filters/defaultFilter.vert b/packages/core/src/renderers/filters/defaultFilter.vert deleted file mode 100644 index e1febca..0000000 --- a/packages/core/src/renderers/filters/defaultFilter.vert +++ /dev/null @@ -1,14 +0,0 @@ -attribute vec2 aVertexPosition; -attribute vec2 aTextureCoord; - -uniform mat3 projectionMatrix; -uniform mat3 filterMatrix; - -varying vec2 vTextureCoord; -varying vec2 vFilterCoord; - -void main(void){ - gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); - vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy; - vTextureCoord = aTextureCoord ; -} diff --git a/packages/core/src/renderers/filters/filterTransforms.js b/packages/core/src/renderers/filters/filterTransforms.js deleted file mode 100644 index 87a3cd6..0000000 --- a/packages/core/src/renderers/filters/filterTransforms.js +++ /dev/null @@ -1,50 +0,0 @@ -import { Matrix } from '@pixi/math'; - -/** - * Calculates the mapped matrix - * @param filterArea {Rectangle} The filter area - * @param sprite {Sprite} the target sprite - * @param outputMatrix {Matrix} @alvin - * @private - */ -// this returns a matrix that will normalise map filter cords in the filter to screen space -export function calculateScreenSpaceMatrix(outputMatrix, filterArea, textureSize) -{ - // TODO unwrap? - const mappedMatrix = outputMatrix.identity(); - - mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); - - mappedMatrix.scale(textureSize.width, textureSize.height); - - return mappedMatrix; -} - -export function calculateNormalizedScreenSpaceMatrix(outputMatrix, filterArea, textureSize) -{ - const mappedMatrix = outputMatrix.identity(); - - mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); - - const translateScaleX = (textureSize.width / filterArea.width); - const translateScaleY = (textureSize.height / filterArea.height); - - mappedMatrix.scale(translateScaleX, translateScaleY); - - return mappedMatrix; -} - -// this will map the filter coord so that a texture can be used based on the transform of a sprite -export function calculateSpriteMatrix(outputMatrix, filterArea, textureSize, sprite) -{ - const orig = sprite._texture.orig; - const mappedMatrix = outputMatrix.set(textureSize.width, 0, 0, textureSize.height, filterArea.x, filterArea.y); - const worldTransform = sprite.worldTransform.copyTo(Matrix.TEMP_MATRIX); - - worldTransform.invert(); - mappedMatrix.prepend(worldTransform); - mappedMatrix.scale(1.0 / orig.width, 1.0 / orig.height); - mappedMatrix.translate(sprite.anchor.x, sprite.anchor.y); - - return mappedMatrix; -} diff --git a/packages/core/src/renderers/filters/spriteMask/SpriteMaskFilter.js b/packages/core/src/renderers/filters/spriteMask/SpriteMaskFilter.js deleted file mode 100644 index d5a91a4..0000000 --- a/packages/core/src/renderers/filters/spriteMask/SpriteMaskFilter.js +++ /dev/null @@ -1,63 +0,0 @@ -import Filter from '../Filter'; -import { Matrix } from '@pixi/math'; -import vertex from './spriteMaskFilter.vert'; -import fragment from './spriteMaskFilter.frag'; -import { default as TextureMatrix } from '../../../textures/TextureMatrix'; - -/** - * The SpriteMaskFilter class - * - * @class - * @extends PIXI.Filter - * @memberof PIXI - */ -export default class SpriteMaskFilter extends Filter -{ - /** - * @param {PIXI.Sprite} sprite - the target sprite - */ - constructor(sprite) - { - const maskMatrix = new Matrix(); - - super(vertex, fragment); - - sprite.renderable = false; - - this.maskSprite = sprite; - this.maskMatrix = maskMatrix; - } - - /** - * Applies the filter - * - * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from - * @param {PIXI.RenderTarget} input - The input render target. - * @param {PIXI.RenderTarget} output - The target to output to. - */ - apply(filterManager, input, output) - { - const maskSprite = this.maskSprite; - const tex = this.maskSprite.texture; - - if (!tex.valid) - { - return; - } - if (!tex.transform) - { - // margin = 0.0, let it bleed a bit, shader code becomes easier - // assuming that atlas textures were made with 1-pixel padding - tex.transform = new TextureMatrix(tex, 0.0); - } - tex.transform.update(); - - this.uniforms.mask = tex; - this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite) - .prepend(tex.transform.mapCoord); - this.uniforms.alpha = maskSprite.worldAlpha; - this.uniforms.maskClamp = tex.transform.uClampFrame; - - filterManager.applyFilter(this, input, output); - } -} diff --git a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.frag b/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.frag deleted file mode 100644 index 0e4aef8..0000000 --- a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.frag +++ /dev/null @@ -1,23 +0,0 @@ -varying vec2 vMaskCoord; -varying vec2 vTextureCoord; - -uniform sampler2D uSampler; -uniform sampler2D mask; -uniform float alpha; -uniform vec4 maskClamp; - -void main(void) -{ - float clip = step(3.5, - step(maskClamp.x, vMaskCoord.x) + - step(maskClamp.y, vMaskCoord.y) + - step(vMaskCoord.x, maskClamp.z) + - step(vMaskCoord.y, maskClamp.w)); - - vec4 original = texture2D(uSampler, vTextureCoord); - vec4 masky = texture2D(mask, vMaskCoord); - - original *= (masky.r * masky.a * alpha * clip); - - gl_FragColor = original; -} diff --git a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.vert b/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.vert deleted file mode 100644 index d23a30c..0000000 --- a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.vert +++ /dev/null @@ -1,16 +0,0 @@ -attribute vec2 aVertexPosition; -attribute vec2 aTextureCoord; - -uniform mat3 projectionMatrix; -uniform mat3 otherMatrix; - -varying vec2 vMaskCoord; -varying vec2 vTextureCoord; - -void main(void) -{ - gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); - - vTextureCoord = aTextureCoord; - vMaskCoord = ( otherMatrix * vec3( aTextureCoord, 1.0) ).xy; -} diff --git a/packages/core/src/renderers/systems/BatchSystem.js b/packages/core/src/renderers/systems/BatchSystem.js deleted file mode 100644 index 70e9518..0000000 --- a/packages/core/src/renderers/systems/BatchSystem.js +++ /dev/null @@ -1,68 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import ObjectRenderer from '../utils/ObjectRenderer'; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ - -export default class BatchSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this manager works for. - */ - constructor(renderer) - { - super(renderer); - - /** - * An empty renderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.emptyRenderer = new ObjectRenderer(renderer); - - /** - * The currently active ObjectRenderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.currentRenderer = this.emptyRenderer; - } - - /** - * Changes the current renderer to the one given in parameter - * - * @param {PIXI.ObjectRenderer} objectRenderer - The object renderer to use. - */ - setObjectRenderer(objectRenderer) - { - if (this.currentRenderer === objectRenderer) - { - return; - } - - this.currentRenderer.stop(); - this.currentRenderer = objectRenderer; - - this.renderer.state.setState(objectRenderer.state); - - this.currentRenderer.start(); - } - - /** - * This should be called if you wish to do some custom rendering - * It will basically render anything that may be batched up such as sprites - * - */ - flush() - { - this.setObjectRenderer(this.emptyRenderer); - } - - reset() - { - this.setObjectRenderer(this.emptyRenderer); - } -} diff --git a/packages/core/src/renderers/systems/ContextSystem.js b/packages/core/src/renderers/systems/ContextSystem.js deleted file mode 100644 index 43bf827..0000000 --- a/packages/core/src/renderers/systems/ContextSystem.js +++ /dev/null @@ -1,186 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { settings } from '@pixi/settings'; - -let CONTEXT_UID = 0; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class ContextSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this.webGLVersion = 1; - - this.handleContextLost = this.handleContextLost.bind(this); - this.handleContextRestored = this.handleContextRestored.bind(this); - - this.extensions = {}; - - renderer.view.addEventListener('webglcontextlost', this.handleContextLost, false); - renderer.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); - } - - get isLost() - { - return (!this.gl || this.gl.isContextLost()); - } - - contextChange(gl) - { - this.gl = gl; - - // restore a context if it was previously lost - if (gl.isContextLost() && gl.getExtension('WEBGL_lose_context')) - { - gl.getExtension('WEBGL_lose_context').restoreContext(); - } - } - - initFromContext(gl) - { - this.gl = gl; - this.validateContext(gl); - this.renderer.gl = gl; - this.renderer.CONTEXT_UID = CONTEXT_UID++; - this.renderer.runners.contextChange.run(gl); - } - - initFromOptions(options) - { - const gl = this.createContext(this.renderer.view, options); - - this.initFromContext(gl); - } - - /** - * Helper class to create a webGL Context - * - * @class - * @memberof PIXI.glCore - * @param canvas {HTMLCanvasElement} the canvas element that we will get the context from - * @param options {Object} An options object that gets passed in to the canvas element containing the context attributes, - * see https://developer.mozilla.org/en/docs/Web/API/HTMLCanvasElement/getContext for the options available - * @return {WebGLRenderingContext} the WebGL context - */ - createContext(canvas, options) - { - let gl; - - if (settings.PREFER_WEBGL_2) - { - gl = canvas.getContext('webgl2', options); - } - - if (gl) - { - this.webGLVersion = 2; - } - else - { - this.webGLVersion = 1; - - gl = canvas.getContext('webgl', options) - || canvas.getContext('experimental-webgl', options); - - if (!gl) - { - // fail, not able to get a context - throw new Error('This browser does not support webGL. Try using the canvas renderer'); - } - } - - this.gl = gl; - - this.getExtensions(); - - return gl; - } - - getExtensions() - { - // time to set up default etensions that pixi uses.. - const gl = this.gl; - const extensions = this.extensions; - - if (this.webGLVersion === 1) - { - extensions.drawBuffers = gl.getExtension('WEBGL_draw_buffers'); - extensions.depthTexture = gl.getExtension('WEBKIT_WEBGL_depth_texture'); - extensions.floatTexture = gl.getExtension('OES_texture_float'); - extensions.loseContext = gl.getExtension('WEBGL_lose_context'); - - extensions.vertexArrayObject = gl.getExtension('OES_vertex_array_object') - || gl.getExtension('MOZ_OES_vertex_array_object') - || gl.getExtension('WEBKIT_OES_vertex_array_object'); - } - - // we don't use any specific WebGL 2 ones yet! - } - - /** - * Handles a lost webgl context - * - * @private - * @param {WebGLContextEvent} event - The context lost event. - */ - handleContextLost(event) - { - event.preventDefault(); - } - - /** - * Handles a restored webgl context - * - * @private - */ - handleContextRestored() - { - this.renderer.runners.contextChange.run(this.gl); - } - - destroy() - { - const view = this.renderer.view; - - // remove listeners - view.removeEventListener('webglcontextlost', this.handleContextLost); - view.removeEventListener('webglcontextrestored', this.handleContextRestored); - - this.gl.useProgram(null); - - if (this.extensions.loseContext) - { - this.extensions.loseContext.loseContext(); - } - } - - postrender() - { - this.gl.flush(); - } - - validateContext(gl) - { - const attributes = gl.getContextAttributes(); - - // this is going to be fairly simple for now.. but at least we have room to grow! - if (!attributes.stencil) - { - /* eslint-disable max-len */ - - /* eslint-disable no-console */ - console.warn('Provided WebGL context does not have a stencil buffer, masks may not render correctly'); - /* eslint-enable no-console */ - - /* eslint-enable max-len */ - } - } -} diff --git a/packages/core/src/renderers/systems/FramebufferSystem.js b/packages/core/src/renderers/systems/FramebufferSystem.js deleted file mode 100644 index df7902b..0000000 --- a/packages/core/src/renderers/systems/FramebufferSystem.js +++ /dev/null @@ -1,246 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { Rectangle } from '@pixi/math'; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class FramebufferSystem extends WebGLSystem -{ - /** - * Sets up the renderer context and necessary buffers. - * - * @private - */ - contextChange() - { - this.gl = this.renderer.gl; - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - this.current = null; - this.viewport = new Rectangle(); - - this.drawBufferExtension = this.renderer.context.extensions.drawBuffers; - } - - bind(framebuffer, frame) - { - const gl = this.gl; - - this.current = framebuffer; - - if (framebuffer) - { - // TODO cacheing layer! - - const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID] || this.initFramebuffer(framebuffer); - - gl.bindFramebuffer(gl.FRAMEBUFFER, fbo.framebuffer); - // makesure all textures are unbound.. - - // now check for updates... - if (fbo.dirtyId !== framebuffer.dirtyId) - { - fbo.dirtyId = framebuffer.dirtyId; - - if (fbo.dirtyFormat !== framebuffer.dirtyFormat) - { - fbo.dirtyFormat = framebuffer.dirtyFormat; - this.updateFramebuffer(framebuffer); - } - else if (fbo.dirtySize !== framebuffer.dirtySize) - { - fbo.dirtySize = framebuffer.dirtySize; - this.resizeFramebuffer(framebuffer); - } - } - - for (let i = 0; i < framebuffer.colorTextures.length; i++) - { - if (framebuffer.colorTextures[i].texturePart) - { - this.renderer.texture.unbind(framebuffer.colorTextures[i].texture); - } - else - { - this.renderer.texture.unbind(framebuffer.colorTextures[i]); - } - } - - if (framebuffer.depthTexture) - { - this.renderer.texture.unbind(framebuffer.depthTexture); - } - - if (frame) - { - this.setViewport(frame.x, frame.y, frame.width, frame.height); - } - else - { - this.setViewport(0, 0, framebuffer.width, framebuffer.height); - } - } - else - { - gl.bindFramebuffer(gl.FRAMEBUFFER, null); - - if (frame) - { - this.setViewport(frame.x, frame.y, frame.width, frame.height); - } - else - { - this.setViewport(0, 0, this.renderer.width, this.renderer.height); - } - } - } - - setViewport(x, y, width, height) - { - const v = this.viewport; - - if (v.width !== width || v.height !== height || v.x !== x || v.y !== y) - { - v.x = x; - v.y = y; - v.width = width; - v.height = height; - - this.gl.viewport(x, y, width, height); - } - } - - get size() - { - if (this.current) - { - // TODO store temp - return { x: 0, y: 0, width: this.current.width, height: this.current.height }; - } - - return { x: 0, y: 0, width: this.renderer.width, height: this.renderer.height }; - } - - clear(r, g, b, a) - { - const gl = this.gl; - - // TODO clear color can be set only one right? - gl.clearColor(r, g, b, a); - gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); - } - - // private functions... - - initFramebuffer(framebuffer) - { - const gl = this.gl; - - // TODO - make this a class? - const fbo = { - framebuffer: gl.createFramebuffer(), - stencil: null, - dirtyId: 0, - dirtyFormat: 0, - dirtySize: 0, - }; - - framebuffer.glFrameBuffers[this.CONTEXT_UID] = fbo; - - return fbo; - } - - resizeFramebuffer(framebuffer) - { - const gl = this.gl; - - if (framebuffer.stencil || framebuffer.depth) - { - gl.bindRenderbuffer(gl.RENDERBUFFER, this.stencil); - gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); - } - } - - updateFramebuffer(framebuffer) - { - const gl = this.gl; - - const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID]; - - // bind the color texture - const colorTextures = framebuffer.colorTextures; - - let count = colorTextures.length; - - if (!this.drawBufferExtension) - { - count = Math.min(count, 1); - } - - const activeTextures = []; - - for (let i = 0; i < count; i++) - { - const texture = framebuffer.colorTextures[i]; - - if (texture.texturePart) - { - this.renderer.texture.bind(texture.texture, 0); - - gl.framebufferTexture2D(gl.FRAMEBUFFER, - gl.COLOR_ATTACHMENT0 + i, - gl.TEXTURE_CUBE_MAP_NEGATIVE_X + texture.side, - texture.texture._glTextures[this.CONTEXT_UID].texture, - 0); - } - else - { - this.renderer.texture.bind(texture, 0); - - gl.framebufferTexture2D(gl.FRAMEBUFFER, - gl.COLOR_ATTACHMENT0 + i, - gl.TEXTURE_2D, - texture._glTextures[this.CONTEXT_UID].texture, - 0); - } - - activeTextures.push(gl.COLOR_ATTACHMENT0 + i); - } - - if (this.drawBufferExtension && activeTextures.length > 1) - { - this.drawBufferExtension.drawBuffersWEBGL(activeTextures); - } - - if (framebuffer.depthTexture) - { - const depthTextureExt = this.renderer.context.extensions.depthTexture; - - if (depthTextureExt) - { - const depthTexture = framebuffer.depthTexture; - - this.renderer.texture.bind(depthTexture, 0); - - gl.framebufferTexture2D(gl.FRAMEBUFFER, - gl.DEPTH_ATTACHMENT, - gl.TEXTURE_2D, - depthTexture._glTextures[this.CONTEXT_UID].texture, - 0); - } - } - - if (framebuffer.stencil || framebuffer.depth) - { - fbo.stencil = gl.createRenderbuffer(); - - gl.bindRenderbuffer(gl.RENDERBUFFER, fbo.stencil); - - // TODO.. this is depth AND stencil? - gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, fbo.stencil); - gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); - // fbo.enableStencil(); - } - } -} diff --git a/packages/core/src/renderers/systems/MaskSystem.js b/packages/core/src/renderers/systems/MaskSystem.js deleted file mode 100644 index d9c4b1a..0000000 --- a/packages/core/src/renderers/systems/MaskSystem.js +++ /dev/null @@ -1,199 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import AlphaMaskFilter from '../filters/spriteMask/SpriteMaskFilter'; - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class MaskSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - // TODO - we don't need both! - this.scissor = false; - this.scissorData = null; - this.scissorRenderTarget = null; - - this.enableScissor = false; - - this.alphaMaskPool = []; - this.alphaMaskIndex = 0; - } - - /** - * Applies the Mask and adds it to the current filter stack. - * - * @param {PIXI.DisplayObject} target - Display Object to push the mask to - * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. - */ - push(target, maskData) - { - // TODO the root check means scissor rect will not - // be used on render textures more info here: - // https://github.com/pixijs/pixi.js/pull/3545 - - if (maskData.texture) - { - this.pushSpriteMask(target, maskData); - } - else if (this.enableScissor - && !this.scissor - && this.renderer._activeRenderTarget.root - && !this.renderer.stencil.stencilMaskStack.length - && maskData.isFastRect()) - { - const matrix = maskData.worldTransform; - - let rot = Math.atan2(matrix.b, matrix.a); - - // use the nearest degree! - rot = Math.round(rot * (180 / Math.PI)); - - if (rot % 90) - { - this.pushStencilMask(maskData); - } - else - { - this.pushScissorMask(target, maskData); - } - } - else - { - this.pushStencilMask(maskData); - } - } - - /** - * Removes the last mask from the mask stack and doesn't return it. - * - * @param {PIXI.DisplayObject} target - Display Object to pop the mask from - * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. - */ - pop(target, maskData) - { - if (maskData.texture) - { - this.popSpriteMask(target, maskData); - } - else if (this.enableScissor && !this.renderer.stencil.stencilMaskStack.length) - { - this.popScissorMask(target, maskData); - } - else - { - this.popStencilMask(target, maskData); - } - } - - /** - * Applies the Mask and adds it to the current filter stack. - * - * @param {PIXI.RenderTarget} target - Display Object to push the sprite mask to - * @param {PIXI.Sprite} maskData - Sprite to be used as the mask - */ - pushSpriteMask(target, maskData) - { - let alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; - - if (!alphaMaskFilter) - { - alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; - } - - alphaMaskFilter[0].resolution = this.renderer.resolution; - alphaMaskFilter[0].maskSprite = maskData; - - // TODO - may cause issues! - target.filterArea = maskData.getBounds(true); - - this.renderer.filterSystem.pushFilter(target, alphaMaskFilter); - - this.alphaMaskIndex++; - } - - /** - * Removes the last filter from the filter stack and doesn't return it. - * - */ - popSpriteMask() - { - this.renderer.filterSystem.popFilter(); - this.alphaMaskIndex--; - } - - /** - * Applies the Mask and adds it to the current filter stack. - * - * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. - */ - pushStencilMask(maskData) - { - this.renderer.batch.flush(); - this.renderer.stencil.pushStencil(maskData); - } - - /** - * Removes the last filter from the filter stack and doesn't return it. - * - */ - popStencilMask() - { - // this.renderer.currentRenderer.stop(); - this.renderer.stencil.popStencil(); - } - - /** - * - * @param {PIXI.DisplayObject} target - Display Object to push the mask to - * @param {PIXI.Graphics} maskData - The masking data. - */ - pushScissorMask(target, maskData) - { - maskData.renderable = true; - - const renderTarget = this.renderer._activeRenderTarget; - - const bounds = maskData.getBounds(); - - bounds.fit(renderTarget.size); - maskData.renderable = false; - - this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); - - const resolution = this.renderer.resolution; - - this.renderer.gl.scissor( - bounds.x * resolution, - (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, - bounds.width * resolution, - bounds.height * resolution - ); - - this.scissorRenderTarget = renderTarget; - this.scissorData = maskData; - this.scissor = true; - } - - /** - * - * - */ - popScissorMask() - { - this.scissorRenderTarget = null; - this.scissorData = null; - this.scissor = false; - - // must be scissor! - const gl = this.renderer.gl; - - gl.disable(gl.SCISSOR_TEST); - } -} diff --git a/packages/core/src/renderers/systems/ProjectionSystem.js b/packages/core/src/renderers/systems/ProjectionSystem.js deleted file mode 100644 index ec5efdb..0000000 --- a/packages/core/src/renderers/systems/ProjectionSystem.js +++ /dev/null @@ -1,73 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { Matrix } from '@pixi/math'; - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ - -export default class ProjectionSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this.projectionMatrix = new Matrix(); - } - - update(destinationFrame, sourceFrame, resolution, root) - { - this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; - this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; - - this.calculateProjection(this.destinationFrame, this.sourceFrame, resolution, root); - - this.renderer.globalUniforms.uniforms.projectionMatrix = this.projectionMatrix; - this.renderer.globalUniforms.update(); - } - - /** - * Updates the projection matrix based on a projection frame (which is a rectangle) - * - * @param {Rectangle} destinationFrame - The destination frame. - * @param {Rectangle} sourceFrame - The source frame. - */ - calculateProjection(destinationFrame, sourceFrame, resolution, root) - { - const pm = this.projectionMatrix; - - // I don't think we will need this line.. - // pm.identity(); - - if (!root) - { - pm.a = (1 / destinationFrame.width * 2) * resolution; - pm.d = (1 / destinationFrame.height * 2) * resolution; - - pm.tx = -1 - (sourceFrame.x * pm.a); - pm.ty = -1 - (sourceFrame.y * pm.d); - } - else - { - pm.a = (1 / destinationFrame.width * 2) * resolution; - pm.d = (-1 / destinationFrame.height * 2) * resolution; - - pm.tx = -1 - (sourceFrame.x * pm.a); - pm.ty = 1 - (sourceFrame.y * pm.d); - } - } - - /** - * Sets the transform of the active render target to the given matrix - * - * @param {PIXI.Matrix} matrix - The transformation matrix - */ - setTransform()// matrix) - { - // this._activeRenderTarget.transform = matrix; - } -} diff --git a/packages/core/src/renderers/systems/RenderTextureSystem.js b/packages/core/src/renderers/systems/RenderTextureSystem.js deleted file mode 100644 index a133ef1..0000000 --- a/packages/core/src/renderers/systems/RenderTextureSystem.js +++ /dev/null @@ -1,115 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { Rectangle } from '@pixi/math'; - -const tempRect = new Rectangle(); - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ - -export default class RenderTextureSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this.clearColor = renderer._backgroundColorRgba; - - // TODO moe this property somewhere else! - this.defaultMaskStack = []; - this.defaultFilterStack = [{}]; - - // empty render texture? - this.renderTexture = null; - - this.destinationFrame = new Rectangle(); - } - - bind(renderTexture, sourceFrame, destinationFrame) - { - // TODO - do we want this?? - if (this.renderTexture === renderTexture) return; - this.renderTexture = renderTexture; - - const renderer = this.renderer; - - if (renderTexture) - { - const baseTexture = renderTexture.baseTexture; - - if (!destinationFrame) - { - tempRect.width = baseTexture.realWidth; - tempRect.height = baseTexture.realHeight; - - destinationFrame = tempRect; - } - - if (!sourceFrame) - { - sourceFrame = destinationFrame; - } - - this.renderer.framebuffer.bind(baseTexture.frameBuffer, destinationFrame); - - this.renderer.projection.update(destinationFrame, sourceFrame, baseTexture.resolution, false); - this.renderer.stencil.setMaskStack(baseTexture.stencilMaskStack); - } - else - { - // TODO these validation checks happen deeper down.. - // thing they can be avoided.. - if (!destinationFrame) - { - tempRect.width = renderer.width; - tempRect.height = renderer.height; - - destinationFrame = tempRect; - } - - if (!sourceFrame) - { - sourceFrame = destinationFrame; - } - - renderer.framebuffer.bind(null, destinationFrame); - - // TODO store this.. - this.renderer.projection.update(destinationFrame, sourceFrame, this.renderer.resolution, true); - this.renderer.stencil.setMaskStack(this.defaultMaskStack); - } - - this.destinationFrame.copyFrom(destinationFrame); - } - - /** - * Erases the render texture and fills the drawing area with a colour - * - * @param {number} [clearColor] - The colour - * @return {PIXI.Renderer} Returns itself. - */ - clear(clearColor) - { - if (this.renderTexture) - { - clearColor = clearColor || this.renderTexture.baseTexture.clearColor; - } - else - { - clearColor = clearColor || this.clearColor; - } - - this.renderer.framebuffer.clear(clearColor[0], clearColor[1], clearColor[2], clearColor[3]); - } - - resize()// screenWidth, screenHeight) - { - // resize the root only! - this.bind(null); - } -} diff --git a/packages/core/src/renderers/systems/StateSystem.js b/packages/core/src/renderers/systems/StateSystem.js deleted file mode 100755 index d7f4caf..0000000 --- a/packages/core/src/renderers/systems/StateSystem.js +++ /dev/null @@ -1,288 +0,0 @@ -import mapWebGLBlendModesToPixi from '../utils/mapWebGLBlendModesToPixi'; -import WebGLSystem from './WebGLSystem'; -import WebGLState from '../State'; - -const BLEND = 0; -const OFFSET = 1; -const CULLING = 2; -const DEPTH_TEST = 3; -const WINDING = 4; - -/** - * A WebGL state machines - * - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class StateSystem extends WebGLSystem -{ - /** - * @param {WebGLRenderingContext} gl - The current WebGL rendering context - */ - constructor(renderer) - { - super(renderer); - - this.gl = null; - - this.maxAttribs = null; - - // check we have vao.. - this.nativeVaoExtension = null; - - this.attribState = null; - - this.stateId = 0; - this.polygonOffset = 0; - this.blendMode = 17; - - this.map = []; - - // map functions for when we set state.. - this.map[BLEND] = this.setBlend; - this.map[OFFSET] = this.setOffset; - this.map[CULLING] = this.setCullFace; - this.map[DEPTH_TEST] = this.setDepthTest; - this.map[WINDING] = this.setFrontFace; - - this.checks = []; - - this.defaultState = new WebGLState(); - this.defaultState.blend = true; - this.defaultState.depth = true; - } - - contextChange(gl) - { - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; - - this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); - - // check we have vao.. - this.nativeVaoExtension = ( - gl.getExtension('OES_vertex_array_object') - || gl.getExtension('MOZ_OES_vertex_array_object') - || gl.getExtension('WEBKIT_OES_vertex_array_object') - ); - - this.attribState = { - tempAttribState: new Array(this.maxAttribs), - attribState: new Array(this.maxAttribs), - }; - - this.blendModes = mapWebGLBlendModesToPixi(gl); - - this.setState(this.defaultState); - - this.reset(); - } - - /** - * Sets the current state - * - * @param {*} state - The state to set. - */ - setState(state) - { - state = state || this.defaultState; - - // TODO maybe to an object check? ( this.state === state )? - if (this.stateId !== state.data) - { - let diff = this.stateId ^ state.data; - let i = 0; - - // order from least to most common - while (diff) - { - if (diff & 1) - { - // state change! - this.map[i].call(this, !!(state.data & (1 << i))); - } - - diff = diff >> 1; - i++; - } - - this.stateId = state.data; - } - - // based on the above settings we check for specific modes.. - // for example if blend is active we check and set the blend modes - // or of polygon offset is active we check the poly depth. - for (let i = 0; i < this.checks.length; i++) - { - this.checks[i](this, state); - } - } - - /** - * Enables or disabled blending. - * - * @param {boolean} value - Turn on or off webgl blending. - */ - setBlend(value) - { - this.updateCheck(StateSystem.checkBlendMode, value); - - this.gl[value ? 'enable' : 'disable'](this.gl.BLEND); - } - - /** - * Enables or disable polygon offset fill - * - * @param {boolean} value - Turn on or off webgl polygon offset testing. - */ - setOffset(value) - { - this.gl[value ? 'enable' : 'disable'](this.gl.POLYGON_OFFSET_FILL); - } - - /** - * Sets whether to enable or disable depth test. - * - * @param {boolean} value - Turn on or off webgl depth testing. - */ - setDepthTest(value) - { - this.gl[value ? 'enable' : 'disable'](this.gl.DEPTH_TEST); - } - - /** - * Sets whether to enable or disable cull face. - * - * @param {boolean} value - Turn on or off webgl cull face. - */ - setCullFace(value) - { - this.gl[value ? 'enable' : 'disable'](this.gl.CULL_FACE); - } - - /** - * Sets the gl front face. - * - * @param {boolean} value - true is clockwise and false is counter-clockwise - */ - setFrontFace(value) - { - this.gl.frontFace(this.gl[value ? 'CW' : 'CCW']); - } - - /** - * Sets the blend mode. - * - * @param {number} value - The blend mode to set to. - */ - setBlendMode(value) - { - if (value === this.blendMode) - { - return; - } - - this.blendMode = value; - this.gl.blendFunc(this.blendModes[value][0], this.blendModes[value][1]); - } - - /** - * Sets the polygon offset. - * - * @param {number} value - the polygon offset - * @param {number} scale - the polygon offset scale - */ - setPolygonOffset(value, scale) - { - this.gl.polygonOffset(value, scale); - } - - /** - * Disables all the vaos in use - * - */ - resetAttributes() - { - for (let i = 0; i < this.attribState.tempAttribState.length; i++) - { - this.attribState.tempAttribState[i] = 0; - } - - for (let i = 0; i < this.attribState.attribState.length; i++) - { - this.attribState.attribState[i] = 0; - } - - // im going to assume one is always active for performance reasons. - for (let i = 1; i < this.maxAttribs; i++) - { - this.gl.disableVertexAttribArray(i); - } - } - - // used - /** - * Resets all the logic and disables the vaos - */ - reset() - { - // unbind any VAO if they exist.. - if (this.nativeVaoExtension) - { - this.nativeVaoExtension.bindVertexArrayOES(null); - } - - // reset all attributes.. - this.resetAttributes(); - - this.gl.pixelStorei(this.gl.UNPACK_FLIP_Y_WEBGL, false); - - this.setBlendMode(0); - - // TODO? - // this.setState(this.defaultState); - } - - /** - * checks to see which updates should be checked based on which settings have been activated - * for example if blend is enabled then we shold check the blend modes each time the state is changed - * or if poygon fill is activated then we need to check if the polygone offset changes. - * The idea is that we only check what we have too. - * - * @param {Function} func the checking function to add or remove - * @param {boolean} value should the check function be added or removed. - */ - updateCheck(func, value) - { - const index = this.checks.indexOf(func); - - if (value && index === -1) - { - this.checks.push(func); - } - else if (!value && index !== -1) - { - this.checks.splice(index, 1); - } - } - - /** - * A private little wrapper function that we call to check the blend mode. - * - * @static - * @private - * @param {PIXI.StateSystem} System the System to perform the state check on - * @param {PIXI.State} state the state that the blendMode will pulled from - */ - static checkBlendMode(system, state) - { - system.setBlendMode(state.blendMode); - } - - // TODO - add polygon offset? -} diff --git a/packages/core/src/renderers/systems/StencilSystem.js b/packages/core/src/renderers/systems/StencilSystem.js deleted file mode 100644 index 5d538cb..0000000 --- a/packages/core/src/renderers/systems/StencilSystem.js +++ /dev/null @@ -1,133 +0,0 @@ -import WebGLSystem from './WebGLSystem'; - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class StencilSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - this.stencilMaskStack = []; - } - - /** - * Changes the mask stack that is used by this System. - * - * @param {PIXI.Graphics[]} stencilMaskStack - The mask stack - */ - setMaskStack(stencilMaskStack) - { - const gl = this.renderer.gl; - - if (stencilMaskStack.length !== this.stencilMaskStack.length) - { - if (stencilMaskStack.length === 0) - { - gl.disable(gl.STENCIL_TEST); - } - else - { - gl.enable(gl.STENCIL_TEST); - } - } - - this.stencilMaskStack = stencilMaskStack; - } - - /** - * Applies the Mask and adds it to the current stencil stack. @alvin - * - * @param {PIXI.Graphics} graphics - The mask - */ - pushStencil(graphics) - { - this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); - - // this.renderer._activeRenderTarget.attachStencilBuffer(); - - const gl = this.renderer.gl; - const prevMaskCount = this.stencilMaskStack.length; - - if (prevMaskCount === 0) - { - gl.enable(gl.STENCIL_TEST); - } - - this.stencilMaskStack.push(graphics); - - // Increment the refference stencil value where the new mask overlaps with the old ones. - gl.colorMask(false, false, false, false); - gl.stencilFunc(gl.EQUAL, prevMaskCount, this._getBitwiseMask()); - gl.stencilOp(gl.KEEP, gl.KEEP, gl.INCR); - this.renderer.plugins.graphics.render(graphics); - - this._useCurrent(); - } - - /** - * Removes the last mask from the stencil stack. @alvin - */ - popStencil() - { - this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); - - const gl = this.renderer.gl; - const graphics = this.stencilMaskStack.pop(); - - if (this.stencilMaskStack.length === 0) - { - // the stack is empty! - gl.disable(gl.STENCIL_TEST); - gl.clear(gl.STENCIL_BUFFER_BIT); - gl.clearStencil(0); - } - else - { - // Decrement the refference stencil value where the popped mask overlaps with the other ones - gl.colorMask(false, false, false, false); - gl.stencilOp(gl.KEEP, gl.KEEP, gl.DECR); - this.renderer.plugins.graphics.render(graphics); - - this._useCurrent(); - } - } - - /** - * Setup renderer to use the current stencil data. - */ - _useCurrent() - { - const gl = this.renderer.gl; - - gl.colorMask(true, true, true, true); - gl.stencilFunc(gl.EQUAL, this.stencilMaskStack.length, this._getBitwiseMask()); - gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP); - } - - /** - * Fill 1s equal to the number of acitve stencil masks. - * - * @return {number} The bitwise mask. - */ - _getBitwiseMask() - { - return (1 << this.stencilMaskStack.length) - 1; - } - - /** - * Destroys the mask stack. - * - */ - destroy() - { - super.destroy(this); - - this.stencilMaskStack = null; - } -} diff --git a/packages/core/src/renderers/systems/WebGLSystem.js b/packages/core/src/renderers/systems/WebGLSystem.js deleted file mode 100644 index d3d5b31..0000000 --- a/packages/core/src/renderers/systems/WebGLSystem.js +++ /dev/null @@ -1,41 +0,0 @@ -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this manager works for. - */ - constructor(renderer) - { - /** - * The renderer this manager works for. - * - * @member {PIXI.Renderer} - */ - this.renderer = renderer; - - this.renderer.runners.contextChange.add(this); - } - - /** - * Generic method called when there is a WebGL context change. - * - */ - contextChange() - { - // do some codes init! - } - - /** - * Generic destroy methods to be overridden by the subclass - * - */ - destroy() - { - this.renderer.runners.contextChange.remove(this); - this.renderer = null; - } -} diff --git a/packages/core/src/renderers/systems/filter/FilterSystem.js b/packages/core/src/renderers/systems/filter/FilterSystem.js deleted file mode 100644 index e615037..0000000 --- a/packages/core/src/renderers/systems/filter/FilterSystem.js +++ /dev/null @@ -1,431 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; - -import RenderTexture from '../../../textures/RenderTexture'; -import Quad from '../../utils/Quad'; -import QuadUv from '../../utils/QuadUv'; -import { Rectangle } from '@pixi/math'; -import * as filterTransforms from '../../filters/filterTransforms'; -import bitTwiddle from 'bit-twiddle'; -import UniformGroup from '../../../shader/UniformGroup'; -import { DRAW_MODES } from '../../../../../constants'; - -// -/** - * @ignore - * @class - */ -class FilterState -{ - /** - * - */ - constructor() - { - this.renderTexture = null; - this.sourceFrame = new Rectangle(); - this.destinationFrame = new Rectangle(); - this.filters = []; - this.target = null; - this.legacy = false; - this.resolution = 1; - } -} - -/** - * @class - * @memberof PIXI.systems - * @extends PIXI.systems.WebGLSystem - */ -export default class FilterSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - /** - * stores a bunch of PO2 textures used for filtering - * @type {Object} - */ - this.texturePool = {}; - - /** - * a pool for storing filter states, save us creating new ones each tick - * @type {Array} - */ - this.statePool = []; - - /** - * A very simple geometry used when drawing a filter effect to the screen - * @type {Quad} - */ - this.quad = new Quad(); - - this.quadUv = new QuadUv(); - - /** - * Temporary rect for maths - * @type {PIXI.Rectangle} - */ - this.tempRect = new Rectangle(); - - this.activeState = {}; - - /** - * this uniform group is attached to filter uniforms when used - * @type {UniformGroup} - */ - this.globalUniforms = new UniformGroup({ - sourceFrame: this.tempRect, - destinationFrame: this.tempRect, - - // legacy variables - filterArea: new Float32Array(4), - filterClamp: new Float32Array(4), - }, true); - } - - /** - * Adds a new filter to the System. - * - * @param {PIXI.DisplayObject} target - The target of the filter to render. - * @param {PIXI.Filter[]} filters - The filters to apply. - */ - push(target, filters) - { - const renderer = this.renderer; - const filterStack = this.renderer.renderTexture.defaultFilterStack; - const state = this.statePool.pop() || new FilterState(); - - let resolution = filters[0].resolution; - let padding = filters[0].padding; - let autoFit = filters[0].autoFit; - let legacy = filters[0].legacy; - - for (let i = 1; i < filters.length; i++) - { - const filter = filters[i]; - - // lets use the lowest resolution.. - resolution = Math.min(resolution, filter.resolution); - // and the largest amount of padding! - padding = Math.max(padding, filter.padding); - // only auto fit if all filters are autofit - autoFit = autoFit || filter.autoFit; - - legacy = legacy || filter.legacy; - } - - filterStack.push(state); - - state.resolution = resolution; - - state.legacy = legacy; - - // round to whole number based on resolution - // TODO move that to the shader too? - state.sourceFrame = target.filterArea || target.getBounds(true); - - state.sourceFrame.pad(padding); - - if (autoFit) - { - state.sourceFrame.fit(this.renderer.renderTexture.destinationFrame); - } - - state.sourceFrame.round(resolution); - - state.renderTexture = this.getPotFilterTexture(state.sourceFrame.width, state.sourceFrame.height, resolution); - state.filters = filters; - - state.destinationFrame.width = state.renderTexture.width; - state.destinationFrame.height = state.renderTexture.height; - - state.renderTexture.filterFrame = state.sourceFrame; - renderer.renderTexture.bind(state.renderTexture, state.sourceFrame);// /, state.destinationFrame); - renderer.renderTexture.clear(); - } - - /** - * Pops off the filter and applies it. - * - */ - pop() - { - const renderer = this.renderer; - const filterStack = renderer.renderTexture.defaultFilterStack; - const state = filterStack.pop(); - const filters = state.filters; - - this.activeState = state; - - const globalUniforms = this.globalUniforms.uniforms; - - globalUniforms.sourceFrame = state.sourceFrame; - globalUniforms.destinationFrame = state.destinationFrame; - globalUniforms.resolution = state.resolution; - - // only update the rect if its legacy.. - if (state.legacy) - { - const filterArea = globalUniforms.filterArea; - const filterClamp = globalUniforms.filterClamp; - - filterArea[0] = state.destinationFrame.width; - filterArea[1] = state.destinationFrame.height; - filterArea[2] = state.sourceFrame.x; - filterArea[3] = state.sourceFrame.y; - - filterClamp[0] = 0.5 / state.resolution / state.destinationFrame.width; - filterClamp[1] = 0.5 / state.resolution / state.destinationFrame.height; - filterClamp[2] = (state.sourceFrame.width - 0.5) / state.resolution / state.destinationFrame.width; - filterClamp[3] = (state.sourceFrame.height - 0.5) / state.resolution / state.destinationFrame.height; - } - - this.globalUniforms.update(); - - const lastState = filterStack[filterStack.length - 1]; - - if (filters.length === 1) - { - filters[0].apply(this, state.renderTexture, lastState.renderTexture, false, state); - - this.returnFilterTexture(state.renderTexture); - } - else - { - let flip = state.renderTexture; - let flop = this.getPotFilterTexture( - flip.width, - flip.height, - state.resolution - ); - - flop.filterFrame = flip.filterFrame; - - let i = 0; - - for (i = 0; i < filters.length - 1; ++i) - { - filters[i].apply(this, flip, flop, true, state); - - const t = flip; - - flip = flop; - flop = t; - } - - filters[i].apply(this, flip, lastState.renderTexture, false, state); - - this.returnFilterTexture(flip); - this.returnFilterTexture(flop); - } - - this.statePool.push(state); - } - - /** - * Draws a filter. - * - * @param {PIXI.Filter} filter - The filter to draw. - * @param {PIXI.RenderTarget} input - The input render target. - * @param {PIXI.RenderTarget} output - The target to output to. - * @param {boolean} clear - Should the output be cleared before rendering to it - */ - applyFilter(filter, input, output, clear) - { - const renderer = this.renderer; - - renderer.renderTexture.bind(output, output ? output.filterFrame : null); - - if (clear) - { - // gl.disable(gl.SCISSOR_TEST); - renderer.renderTexture.clear(); - // gl.enable(gl.SCISSOR_TEST); - } - - // set the uniforms.. - filter.uniforms.uSampler = input; - filter.uniforms.filterGlobals = this.globalUniforms; - - // TODO make it so that the order of this does not matter.. - // because it does at the moment cos of global uniforms. - // they need to get resynced - - renderer.state.setState(filter.state); - renderer.shader.bind(filter); - - if (filter.legacy) - { - this.quadUv.map(input._frame, input.filterFrame); - - renderer.geometry.bind(this.quadUv); - renderer.geometry.draw(DRAW_MODES.TRIANGLES); - } - else - { - renderer.geometry.bind(this.quad); - renderer.geometry.draw(DRAW_MODES.TRIANGLE_STRIP); - } - } - - /** - * Calculates the mapped matrix. - * - * TODO playing around here.. this is temporary - (will end up in the shader) - * this returns a matrix that will normalise map filter cords in the filter to screen space - * - * @param {PIXI.Matrix} outputMatrix - the matrix to output to. - * @return {PIXI.Matrix} The mapped matrix. - */ - calculateScreenSpaceMatrix(outputMatrix) - { - const currentState = this.activeState; - - return filterTransforms.calculateScreenSpaceMatrix( - outputMatrix, - currentState.sourceFrame, - currentState.destinationFrame - ); - } - - /** - * This will map the filter coord so that a texture can be used based on the transform of a sprite - * - * @param {PIXI.Matrix} outputMatrix - The matrix to output to. - * @param {PIXI.Sprite} sprite - The sprite to map to. - * @return {PIXI.Matrix} The mapped matrix. - */ - calculateSpriteMatrix(outputMatrix, sprite) - { - const currentState = this.activeState; - - return filterTransforms.calculateSpriteMatrix( - outputMatrix, - currentState.sourceFrame, - currentState.destinationFrame, - sprite - ); - } - - /** - * Destroys this Filter System. - * - * @param {boolean} [contextLost=false] context was lost, do not free shaders - * - */ - destroy(contextLost = false) - { - if (!contextLost) - { - this.emptyPool(); - } - else - { - this.texturePool = {}; - } - } - - /** - * Gets a Power-of-Two render texture. - * - * TODO move to a seperate class could be on renderer? - * also - could cause issue with multiple contexts? - * - * @private - * @param {WebGLRenderingContext} gl - The webgl rendering context - * @param {number} minWidth - The minimum width of the render target. - * @param {number} minHeight - The minimum height of the render target. - * @param {number} resolution - The resolution of the render target. - * @return {PIXI.RenderTarget} The new render target. - */ - getPotFilterTexture(minWidth, minHeight, resolution) - { - minWidth = bitTwiddle.nextPow2(minWidth); - minHeight = bitTwiddle.nextPow2(minHeight); - resolution = resolution || 1; - - const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); - - if (!this.texturePool[key]) - { - this.texturePool[key] = []; - } - - let renderTexture = this.texturePool[key].pop(); - - if (!renderTexture) - { - // temporary bypass cache.. - // internally - this will cause a texture to be bound.. - renderTexture = RenderTexture.create({ - width: minWidth, - height: minHeight, - resolution, - }); - } - - return renderTexture; - } - - /** - * Gets extra render texture to use inside current filter - * - * @param {number} resolution resolution of the renderTexture - * @returns {PIXI.RenderTexture} - */ - getFilterTexture(resolution) - { - const rt = this.activeState.renderTexture; - - const filterTexture = this.getPotFilterTexture(rt.width, rt.height, resolution || rt.baseTexture.resolution); - - filterTexture.filterFrame = rt.filterFrame; - - return filterTexture; - } - - /** - * Frees a render texture back into the pool. - * - * @param {PIXI.RenderTarget} renderTexture - The renderTarget to free - */ - returnFilterTexture(renderTexture) - { - renderTexture.filterFrame = null; - - const base = renderTexture.baseTexture; - - const minWidth = base.width; - const minHeight = base.height; - - const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); - - this.texturePool[key].push(renderTexture); - } - - /** - * Empties the texture pool. - * - */ - emptyPool() - { - for (const i in this.texturePool) - { - const textures = this.texturePool[i]; - - if (textures) - { - for (let j = 0; j < textures.length; j++) - { - textures[j].destroy(true); - } - } - } - - this.texturePool = {}; - } -} diff --git a/packages/core/src/renderers/systems/geometry/GLBuffer.js b/packages/core/src/renderers/systems/geometry/GLBuffer.js deleted file mode 100644 index 0477e8a..0000000 --- a/packages/core/src/renderers/systems/geometry/GLBuffer.js +++ /dev/null @@ -1,9 +0,0 @@ -export default class GLBuffer -{ - constructor(buffer) - { - this.buffer = buffer; - this.updateID = -1; - this.byteLength = -1; - } -} diff --git a/packages/core/src/renderers/systems/geometry/GeometrySystem.js b/packages/core/src/renderers/systems/geometry/GeometrySystem.js deleted file mode 100644 index 26c8e9e..0000000 --- a/packages/core/src/renderers/systems/geometry/GeometrySystem.js +++ /dev/null @@ -1,388 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; -import GLBuffer from './GLBuffer'; - -const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class GeometrySystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this._activeGeometry = null; - this._activeVao = null; - - this.hasVao = true; - this.hasInstance = true; - } - - /** - * Sets up the renderer context and necessary buffers. - * - * @private - */ - contextChange() - { - const gl = this.gl = this.renderer.gl; - - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - - // webgl2 - if (!gl.createVertexArray) - { - // webgl 1! - let nativeVaoExtension = this.renderer.context.extensions.vertexArrayObject; - - if (this.renderer.legacy) - { - nativeVaoExtension = null; - } - - if (nativeVaoExtension) - { - gl.createVertexArray = () => - nativeVaoExtension.createVertexArrayOES(); - - gl.bindVertexArray = (vao) => - nativeVaoExtension.bindVertexArrayOES(vao); - - gl.deleteVertexArray = (vao) => - nativeVaoExtension.deleteVertexArrayOES(vao); - } - else - { - this.hasVao = false; - gl.createVertexArray = () => - { - // empty - }; - - gl.bindVertexArray = () => - { - // empty - }; - - gl.deleteVertexArray = () => - { - // empty - }; - } - } - - if (!gl.vertexAttribDivisor) - { - const instanceExt = gl.getExtension('ANGLE_instanced_arrays'); - - if (instanceExt) - { - gl.vertexAttribDivisor = (a, b) => - instanceExt.vertexAttribDivisorANGLE(a, b); - - gl.drawElementsInstanced = (a, b, c, d, e) => - instanceExt.drawElementsInstancedANGLE(a, b, c, d, e); - - gl.drawArraysInstanced = (a, b, c, d) => - instanceExt.drawArraysInstancedANGLE(a, b, c, d); - } - else - { - this.hasInstance = false; - } - } - } - - /** - * Binds geometry so that is can be drawn. Creating a Vao if required - * @private - * @param {PIXI.Geometry} geometry instance of geometry to bind - */ - bind(geometry, shader) - { - shader = shader || this.renderer.shader.shader; - - const gl = this.gl; - - // not sure the best way to address this.. - // currently different shaders require different VAOs for the same geometry - // Still mulling over the best way to solve this one.. - // will likely need to modify the shader attribute locations at run time! - let vaos = geometry.glVertexArrayObjects[this.CONTEXT_UID]; - - if (!vaos) - { - geometry.glVertexArrayObjects[this.CONTEXT_UID] = vaos = {}; - } - - const vao = vaos[shader.program.id] || this.initGeometryVao(geometry, shader.program); - - this._activeGeometry = geometry; - - if (this._activeVao !== vao) - { - this._activeVao = vao; - - if (this.hasVao) - { - gl.bindVertexArray(vao); - } - else - { - this.activateVao(geometry, shader.program); - } - } - - // TODO - optimise later! - // don't need to loop through if nothing changed! - // maybe look to add an 'autoupdate' to geometry? - this.updateBuffers(); - } - - reset() - { - this.unbind(); - } - - updateBuffers() - { - const geometry = this._activeGeometry; - const gl = this.gl; - - for (let i = 0; i < geometry.buffers.length; i++) - { - const buffer = geometry.buffers[i]; - - const glBuffer = buffer._glBuffers[this.CONTEXT_UID]; - - if (buffer._updateID !== glBuffer.updateID) - { - glBuffer.updateID = buffer._updateID; - - // TODO can cache this on buffer! maybe added a getter / setter? - const type = buffer.index ? gl.ELEMENT_ARRAY_BUFFER : gl.ARRAY_BUFFER; - const drawType = buffer.static ? gl.STATIC_DRAW : gl.DYNAMIC_DRAW; - - gl.bindBuffer(type, glBuffer.buffer); - - if (glBuffer.byteLength >= buffer.data.byteLength) - { - // offset is always zero for now! - gl.bufferSubData(type, 0, buffer.data); - } - else - { - gl.bufferData(type, buffer.data, drawType); - } - } - } - } - - checkCompatability(geometry, program) - { - // geometry must have at least all the attributes that the shader requires. - const geometryAttributes = geometry.attributes; - const shaderAttributes = program.attributeData; - - for (const j in shaderAttributes) - { - if (!geometryAttributes[j]) - { - throw new Error(`shader and geometry incompatible, geometry missing the "${j}" attribute`); - } - } - } - - /** - * Creates a Vao with the same structure as the geometry and stores it on the geometry. - * @private - * @param {PIXI.Geometry} geometry instance of geometry to to generate Vao for - */ - initGeometryVao(geometry, program) - { - this.checkCompatability(geometry, program); - - const gl = this.gl; - const CONTEXT_UID = this.CONTEXT_UID; - const buffers = geometry.buffers; - const attributes = geometry.attributes; - - const tempStride = {}; - const tempStart = {}; - - for (const j in buffers) - { - tempStride[j] = 0; - tempStart[j] = 0; - } - - for (const j in attributes) - { - if (!attributes[j].size && program.attributeData[j]) - { - attributes[j].size = program.attributeData[j].size; - } - - tempStride[attributes[j].buffer] += attributes[j].size * byteSizeMap[attributes[j].type]; - } - - for (const j in attributes) - { - const attribute = attributes[j]; - const attribSize = attribute.size; - - if (attribute.stride === undefined) - { - if (tempStride[attribute.buffer] === attribSize * byteSizeMap[attribute.type]) - { - attribute.stride = 0; - } - else - { - attribute.stride = tempStride[attribute.buffer]; - } - } - - if (attribute.start === undefined) - { - attribute.start = tempStart[attribute.buffer]; - - tempStart[attribute.buffer] += attribSize * byteSizeMap[attribute.type]; - } - } - - // first update - and create the buffers! - // only create a gl buffer if it actually gets - for (let i = 0; i < buffers.length; i++) - { - const buffer = buffers[i]; - - if (!buffer._glBuffers[CONTEXT_UID]) - { - buffer._glBuffers[CONTEXT_UID] = new GLBuffer(gl.createBuffer()); - } - } - - // TODO - maybe make this a data object? - // lets wait to see if we need to first! - const vao = gl.createVertexArray(); - - gl.bindVertexArray(vao); - - this.activateVao(geometry, program); - - gl.bindVertexArray(null); - - // add it to the cache! - geometry.glVertexArrayObjects[this.CONTEXT_UID][program.id] = vao; - - return vao; - } - - activateVao(geometry, program) - { - const gl = this.gl; - const CONTEXT_UID = this.CONTEXT_UID; - const buffers = geometry.buffers; - const attributes = geometry.attributes; - - if (geometry.indexBuffer) - { - // first update the index buffer if we have one.. - gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, geometry.indexBuffer._glBuffers[CONTEXT_UID].buffer); - } - - let lastBuffer = null; - - // add a new one! - for (const j in attributes) - { - const attribute = attributes[j]; - const buffer = buffers[attribute.buffer]; - const glBuffer = buffer._glBuffers[CONTEXT_UID]; - - if (program.attributeData[j]) - { - if (lastBuffer !== glBuffer) - { - gl.bindBuffer(gl.ARRAY_BUFFER, glBuffer.buffer); - - lastBuffer = glBuffer; - } - - const location = program.attributeData[j].location; - - // TODO introduce state again - // we can optimise this for older devices that have no VAOs - gl.enableVertexAttribArray(location); - - gl.vertexAttribPointer(location, - attribute.size, - attribute.type || gl.FLOAT, - attribute.normalized, - attribute.stride, - attribute.start); - - if (attribute.instance) - { - // TODO calculate instance count based of this... - if (this.hasInstance) - { - gl.vertexAttribDivisor(location, 1); - } - else - { - throw new Error('geometry error, GPU Instancing is not supported on this device'); - } - } - } - } - } - - draw(type, size, start, instanceCount) - { - const gl = this.gl; - const geometry = this._activeGeometry; - - // TODO.. this should not change so maybe cache the function? - - if (geometry.indexBuffer) - { - if (geometry.instanced) - { - /* eslint-disable max-len */ - gl.drawElementsInstanced(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2, instanceCount || 1); - /* eslint-enable max-len */ - } - else - { - gl.drawElements(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2); - } - } - else - if (geometry.instanced) - { - // TODO need a better way to calculate size.. - gl.drawArraysInstanced(type, start, size || geometry.getSize(), instanceCount || 1); - } - else - { - gl.drawArrays(type, start, size || geometry.getSize()); - } - - return this; - } - - unbind() - { - this.gl.bindVertexArray(null); - this._activeVao = null; - this._activeGeometry = null; - } -} diff --git a/packages/core/src/renderers/systems/geometry/setVertexAttribArrays.js b/packages/core/src/renderers/systems/geometry/setVertexAttribArrays.js deleted file mode 100644 index 23b6959..0000000 --- a/packages/core/src/renderers/systems/geometry/setVertexAttribArrays.js +++ /dev/null @@ -1,54 +0,0 @@ -// var GL_MAP = {}; - -/** - * @param gl {WebGLRenderingContext} The current WebGL context - * @param attribs {*} - * @param state {*} - */ -export default function setVertexAttribArrays(gl, attribs, state) -{ - let i; - - if (state) - { - const tempAttribState = state.tempAttribState; - const attribState = state.attribState; - - for (i = 0; i < tempAttribState.length; i++) - { - tempAttribState[i] = false; - } - - // set the new attribs - for (i = 0; i < attribs.length; i++) - { - tempAttribState[attribs[i].attribute.location] = true; - } - - for (i = 0; i < attribState.length; i++) - { - if (attribState[i] !== tempAttribState[i]) - { - attribState[i] = tempAttribState[i]; - - if (state.attribState[i]) - { - gl.enableVertexAttribArray(i); - } - else - { - gl.disableVertexAttribArray(i); - } - } - } - } - else - { - for (i = 0; i < attribs.length; i++) - { - const attrib = attribs[i]; - - gl.enableVertexAttribArray(attrib.attribute.location); - } - } -} diff --git a/packages/core/src/renderers/systems/index.js b/packages/core/src/renderers/systems/index.js deleted file mode 100644 index c843f03..0000000 --- a/packages/core/src/renderers/systems/index.js +++ /dev/null @@ -1,6 +0,0 @@ -/** - * Systems are individual components to the Renderer pipeline. - * @namespace PIXI.systems - */ -export { default as WebGLSystem } from './WebGLSystem'; -export { default as FilterSystem } from './filter/FilterSystem'; diff --git a/packages/core/src/renderers/systems/shader/GLProgram.js b/packages/core/src/renderers/systems/shader/GLProgram.js deleted file mode 100644 index 92980ca..0000000 --- a/packages/core/src/renderers/systems/shader/GLProgram.js +++ /dev/null @@ -1,43 +0,0 @@ -/** - * Helper class to create a webGL Program - * - * @class - * @private - */ -export default class GLProgram -{ - constructor(program, uniformData) - { - /** - * The shader program - * - * @member {WebGLProgram} - */ - this.program = program; - - /** - * holds the uniform data which contains uniform locations - * and current uniform values used for caching and preventing undeed GPU commands - * @type {Object} - */ - this.uniformData = uniformData; - - /** - * uniformGroups holds the various upload functions for the shader. Each uniform group - * and program have a unique upload function generated. - * @type {Object} - */ - this.uniformGroups = {}; - } - - /** - * Destroys this program - * TODO - */ - destroy() - { - this.uniformData = null; - this.uniformGroups = null; - this.program = null; - } -} diff --git a/packages/core/src/renderers/systems/shader/ShaderSystem.js b/packages/core/src/renderers/systems/shader/ShaderSystem.js deleted file mode 100644 index 6cf57b6..0000000 --- a/packages/core/src/renderers/systems/shader/ShaderSystem.js +++ /dev/null @@ -1,170 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; -import GLProgram from './GLProgram'; -import generateUniformsSync from '../../../shader/generateUniformsSync'; -import defaultValue from '../../../shader/defaultValue'; -import compileProgram from './shader/compileProgram'; - -let UID = 0; - -/** - * Helper class to create a webGL Texture - * - * @class - * @memberof PIXI - */ -export default class ShaderSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - A reference to the current renderer - */ - constructor(renderer) - { - super(renderer); - - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = null; - - this.shader = null; - this.program = null; - - this.id = UID++; - } - - contextChange(gl) - { - this.gl = gl; - } - - /** - * Changes the current shader to the one given in parameter - * - * @param {PIXI.Shader} shader - the new shader - * @param {boolean} dontSync - false if the shader should automatically sync its uniforms. - * @returns {PIXI.glCore.glProgram} the glProgram that belongs to the shader. - */ - bind(shader, dontSync) - { - shader.uniforms.globals = this.renderer.globalUniforms; - - const program = shader.program; - const glProgram = program.glPrograms[this.renderer.CONTEXT_UID] || this.generateShader(shader); - - this.shader = shader; - - // TODO - some current pixi plugins bypass this.. so it not safe to use yet.. - if (this.program !== program) - { - this.program = program; - this.gl.useProgram(glProgram.program); - } - - if (!dontSync) - { - this.syncUniformGroup(shader.uniformGroup); - } - - return glProgram; - } - - /** - * Uploads the uniforms values to the currently bound shader. - * - * @param {object} uniforms - the uniforms valiues that be applied to the current shader - */ - setUniforms(uniforms) - { - const shader = this.shader.program; - const glProgram = shader.glPrograms[this.renderer.CONTEXT_UID]; - - shader.syncUniforms(glProgram.uniformData, uniforms, this.renderer); - } - - syncUniformGroup(group) - { - const glProgram = this.getglProgram(); - - if (!group.static || group.dirtyId !== glProgram.uniformGroups[group.id]) - { - glProgram.uniformGroups[group.id] = group.dirtyId; - const syncFunc = group.syncUniforms[this.shader.program.id] || this.createSyncGroups(group); - - syncFunc(glProgram.uniformData, group.uniforms, this.renderer); - } - } - - createSyncGroups(group) - { - group.syncUniforms[this.shader.program.id] = generateUniformsSync(group, this.shader.program.uniformData); - - return group.syncUniforms[this.shader.program.id]; - } - - /** - * Returns the underlying GLShade rof the currently bound shader. - * This can be handy for when you to have a little more control over the setting of your uniforms. - * - * @return {PIXI.glCore.Shader} the glProgram for the currently bound Shader for this context - */ - getglProgram() - { - if (this.shader) - { - return this.shader.program.glPrograms[this.renderer.CONTEXT_UID]; - } - - return null; - } - - /** - * Generates a glProgram verion of the Shader provided. - * - * @private - * @param {PIXI.Shader} shader the shader that the glProgram will be based on. - * @return {PIXI.glCore.glProgram} A shiney new glProgram - */ - generateShader(shader) - { - const gl = this.gl; - - const program = shader.program; - - const attribMap = {}; - - for (const i in program.attributeData) - { - attribMap[i] = program.attributeData[i].location; - } - - const shaderProgram = compileProgram(gl, program.vertexSrc, program.fragmentSrc, attribMap); - const uniformData = {}; - - for (const i in program.uniformData) - { - const data = program.uniformData[i]; - - uniformData[i] = { - location: gl.getUniformLocation(shaderProgram, i), - value: defaultValue(data.type, data.size), - }; - } - - const glProgram = new GLProgram(shaderProgram, uniformData); - - program.glPrograms[this.renderer.CONTEXT_UID] = glProgram; - - return glProgram; - } - - /** - * Destroys this System and removes all its textures - */ - destroy() - { - // TODO implement destroy method for ShaderSystem - this.destroyed = true; - } -} diff --git a/packages/core/src/renderers/systems/shader/shader/compileProgram.js b/packages/core/src/renderers/systems/shader/shader/compileProgram.js deleted file mode 100644 index 8c6ccb9..0000000 --- a/packages/core/src/renderers/systems/shader/shader/compileProgram.js +++ /dev/null @@ -1,78 +0,0 @@ -/** - * @method compileProgram - * @memberof PIXI.glCore.shader - * @param gl {WebGLRenderingContext} The current WebGL context {WebGLProgram} - * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. - * @param fragmentSrc {string|string[]} The fragment shader source as an array of strings. - * @param attributeLocations {Object} An attribute location map that lets you manually set the attribute locations - * @return {WebGLProgram} the shader program - */ -export default function compileProgram(gl, vertexSrc, fragmentSrc, attributeLocations) -{ - const glVertShader = compileShader(gl, gl.VERTEX_SHADER, vertexSrc); - const glFragShader = compileShader(gl, gl.FRAGMENT_SHADER, fragmentSrc); - - let program = gl.createProgram(); - - gl.attachShader(program, glVertShader); - gl.attachShader(program, glFragShader); - - // optionally, set the attributes manually for the program rather than letting WebGL decide.. - if (attributeLocations) - { - for (const i in attributeLocations) - { - gl.bindAttribLocation(program, attributeLocations[i], i); - } - } - - gl.linkProgram(program); - - // if linking fails, then log and cleanup - if (!gl.getProgramParameter(program, gl.LINK_STATUS)) - { - console.error('Pixi.js Error: Could not initialize shader.'); - console.error('gl.VALIDATE_STATUS', gl.getProgramParameter(program, gl.VALIDATE_STATUS)); - console.error('gl.getError()', gl.getError()); - - // if there is a program info log, log it - if (gl.getProgramInfoLog(program) !== '') - { - console.warn('Pixi.js Warning: gl.getProgramInfoLog()', gl.getProgramInfoLog(program)); - } - - gl.deleteProgram(program); - program = null; - } - - // clean up some shaders - gl.deleteShader(glVertShader); - gl.deleteShader(glFragShader); - - return program; -} - -/** - * @private - * @param gl {WebGLRenderingContext} The current WebGL context {WebGLProgram} - * @param type {Number} the type, can be either VERTEX_SHADER or FRAGMENT_SHADER - * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. - * @return {WebGLShader} the shader - */ -function compileShader(gl, type, src) -{ - const shader = gl.createShader(type); - - gl.shaderSource(shader, src); - gl.compileShader(shader); - - if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) - { - console.warn(src); - console.error(gl.getShaderInfoLog(shader)); - - return null; - } - - return shader; -} diff --git a/packages/core/src/renderers/systems/shader/shader/defaultValue.js b/packages/core/src/renderers/systems/shader/shader/defaultValue.js deleted file mode 100644 index 97290bd..0000000 --- a/packages/core/src/renderers/systems/shader/shader/defaultValue.js +++ /dev/null @@ -1,79 +0,0 @@ -/** - * @method defaultValue - * @memberof PIXI.glCore.shader - * @param type {String} Type of value - * @param size {Number} - */ -export default function defaultValue(type, size) -{ - switch (type) - { - case 'float': - return 0; - - case 'vec2': - return new Float32Array(2 * size); - - case 'vec3': - return new Float32Array(3 * size); - - case 'vec4': - return new Float32Array(4 * size); - - case 'int': - case 'sampler2D': - case 'sampler2DArray': - return 0; - - case 'ivec2': - return new Int32Array(2 * size); - - case 'ivec3': - return new Int32Array(3 * size); - - case 'ivec4': - return new Int32Array(4 * size); - - case 'bool': - return false; - - case 'bvec2': - - return booleanArray(2 * size); - - case 'bvec3': - return booleanArray(3 * size); - - case 'bvec4': - return booleanArray(4 * size); - - case 'mat2': - return new Float32Array([1, 0, - 0, 1]); - - case 'mat3': - return new Float32Array([1, 0, 0, - 0, 1, 0, - 0, 0, 1]); - - case 'mat4': - return new Float32Array([1, 0, 0, 0, - 0, 1, 0, 0, - 0, 0, 1, 0, - 0, 0, 0, 1]); - } - - return null; -} - -function booleanArray(size) -{ - const array = new Array(size); - - for (let i = 0; i < array.length; i++) - { - array[i] = false; - } - - return array; -} diff --git a/packages/core/src/renderers/systems/shader/shader/index.js b/packages/core/src/renderers/systems/shader/shader/index.js deleted file mode 100644 index ba7a317..0000000 --- a/packages/core/src/renderers/systems/shader/shader/index.js +++ /dev/null @@ -1,8 +0,0 @@ -/** - * @namespace PIXI.glCore.shader - */ -export { default as compileProgram } from './compileProgram'; -export { default as defaultValue } from './defaultValue'; -export { default as setPrecision } from './setPrecision'; -export { default as mapSize } from './mapSize'; -export { default as mapType } from './mapType'; diff --git a/packages/core/src/renderers/systems/shader/shader/mapSize.js b/packages/core/src/renderers/systems/shader/shader/mapSize.js deleted file mode 100644 index b681554..0000000 --- a/packages/core/src/renderers/systems/shader/shader/mapSize.js +++ /dev/null @@ -1,33 +0,0 @@ -const GLSL_TO_SIZE = { - float: 1, - vec2: 2, - vec3: 3, - vec4: 4, - - int: 1, - ivec2: 2, - ivec3: 3, - ivec4: 4, - - bool: 1, - bvec2: 2, - bvec3: 3, - bvec4: 4, - - mat2: 4, - mat3: 9, - mat4: 16, - - sampler2D: 1, -}; - -/** - * @method mapSize - * @memberof PIXI.glCore.shader - * @param type {String} - * @return {Number} - */ -export default function mapSize(type) -{ - return GLSL_TO_SIZE[type]; -} diff --git a/packages/core/src/renderers/systems/shader/shader/mapType.js b/packages/core/src/renderers/systems/shader/shader/mapType.js deleted file mode 100644 index d288b7c..0000000 --- a/packages/core/src/renderers/systems/shader/shader/mapType.js +++ /dev/null @@ -1,45 +0,0 @@ -let GL_TABLE = null; - -const GL_TO_GLSL_TYPES = { - FLOAT: 'float', - FLOAT_VEC2: 'vec2', - FLOAT_VEC3: 'vec3', - FLOAT_VEC4: 'vec4', - - INT: 'int', - INT_VEC2: 'ivec2', - INT_VEC3: 'ivec3', - INT_VEC4: 'ivec4', - - BOOL: 'bool', - BOOL_VEC2: 'bvec2', - BOOL_VEC3: 'bvec3', - BOOL_VEC4: 'bvec4', - - FLOAT_MAT2: 'mat2', - FLOAT_MAT3: 'mat3', - FLOAT_MAT4: 'mat4', - - SAMPLER_2D: 'sampler2D', - SAMPLER_CUBE: 'samplerCube', - SAMPLER_2D_ARRAY: 'sampler2DArray', -}; - -export default function mapType(gl, type) -{ - if (!GL_TABLE) - { - const typeNames = Object.keys(GL_TO_GLSL_TYPES); - - GL_TABLE = {}; - - for (let i = 0; i < typeNames.length; ++i) - { - const tn = typeNames[i]; - - GL_TABLE[gl[tn]] = GL_TO_GLSL_TYPES[tn]; - } - } - - return GL_TABLE[type]; -} diff --git a/packages/core/src/renderers/systems/shader/shader/setPrecision.js b/packages/core/src/renderers/systems/shader/shader/setPrecision.js deleted file mode 100644 index e2c1f34..0000000 --- a/packages/core/src/renderers/systems/shader/shader/setPrecision.js +++ /dev/null @@ -1,18 +0,0 @@ -/** - * Sets the float precision on the shader. If the precision is already present this function will do nothing - * @param {string} src the shader source - * @param {string} precision The float precision of the shader. Options are 'lowp', 'mediump' or 'highp'. - * - * @return {string} modified shader source - */ -export default function setPrecision(src, precision) -{ - src = src.trim(); - - if (src.substring(0, 9) !== 'precision' && src.substring(0, 1) !== '#') - { - return `precision ${precision} float;\n${src}`; - } - - return src; -} diff --git a/packages/core/src/renderers/systems/textures/GLTexture.js b/packages/core/src/renderers/systems/textures/GLTexture.js deleted file mode 100644 index c3fb8dc..0000000 --- a/packages/core/src/renderers/systems/textures/GLTexture.js +++ /dev/null @@ -1,33 +0,0 @@ -export default class GLTexture -{ - constructor(texture) - { - /** - * The WebGL texture - * - * @member {WebGLTexture} - */ - this.texture = texture; - - this.width = -1; - this.height = -1; - - /** - * Texture contents dirty flag - * @member {number} - */ - this.dirtyId = -1; - - /** - * Texture style dirty flag - * @type {number} - */ - this.dirtyStyleId = -1; - - /** - * Whether mip levels has to be generated - * @type {boolean} - */ - this.mipmap = false; - } -} diff --git a/packages/core/src/renderers/systems/textures/TextureGCSystem.js b/packages/core/src/renderers/systems/textures/TextureGCSystem.js deleted file mode 100644 index b089938..0000000 --- a/packages/core/src/renderers/systems/textures/TextureGCSystem.js +++ /dev/null @@ -1,110 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; -import { GC_MODES } from '@pixi/constants'; -import { settings } from '@pixi/settings'; - -/** - * TextureGarbageCollector. This class manages the GPU and ensures that it does not get clogged - * up with textures that are no longer being used. - * - * @class - * @memberof PIXI - */ -export default class TextureGCSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this.count = 0; - this.checkCount = 0; - this.maxIdle = settings.GC_MAX_IDLE; - this.checkCountMax = settings.GC_MAX_CHECK_COUNT; - this.mode = settings.GC_MODE; - } - - /** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ - postrender() - { - this.count++; - - if (this.mode === GC_MODES.MANUAL) - { - return; - } - - this.checkCount++; - - if (this.checkCount > this.checkCountMax) - { - this.checkCount = 0; - - this.run(); - } - } - - /** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ - run() - { - const tm = this.renderer.texture; - const managedTextures = tm.managedTextures; - let wasRemoved = false; - - for (let i = 0; i < managedTextures.length; i++) - { - const texture = managedTextures[i]; - - // only supports non generated textures at the moment! - if (!texture.frameBuffer && this.count - texture.touched > this.maxIdle) - { - tm.destroyTexture(texture, true); - managedTextures[i] = null; - wasRemoved = true; - } - } - - if (wasRemoved) - { - let j = 0; - - for (let i = 0; i < managedTextures.length; i++) - { - if (managedTextures[i] !== null) - { - managedTextures[j++] = managedTextures[i]; - } - } - - managedTextures.length = j; - } - } - - /** - * Removes all the textures within the specified displayObject and its children from the GPU - * - * @param {PIXI.DisplayObject} displayObject - the displayObject to remove the textures from. - */ - unload(displayObject) - { - const tm = this.renderer.textureSystem; - - // only destroy non generated textures - if (displayObject._texture && displayObject._texture._glRenderTargets) - { - tm.destroyTexture(displayObject._texture); - } - - for (let i = displayObject.children.length - 1; i >= 0; i--) - { - this.unload(displayObject.children[i]); - } - } -} diff --git a/packages/core/src/renderers/systems/textures/TextureSystem.js b/packages/core/src/renderers/systems/textures/TextureSystem.js deleted file mode 100644 index ccc5de9..0000000 --- a/packages/core/src/renderers/systems/textures/TextureSystem.js +++ /dev/null @@ -1,281 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; -import GLTexture from './GLTexture'; -import { removeItems } from '@pixi/utils'; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class TextureSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - // TODO set to max textures... - this.boundTextures = [ - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - ]; - - this.currentLocation = -1; - - this.managedTextures = []; - } - - /** - * Sets up the renderer context and necessary buffers. - * - * @private - */ - contextChange() - { - const gl = this.gl = this.renderer.gl; - - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - - // TODO move this.. to a nice make empty textures class.. - this.emptyTextures = {}; - - const emptyTexture2D = new GLTexture(gl.createTexture()); - - gl.bindTexture(gl.TEXTURE_2D, emptyTexture2D.texture); - gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array(4)); - - this.emptyTextures[gl.TEXTURE_2D] = emptyTexture2D; - this.emptyTextures[gl.TEXTURE_CUBE_MAP] = new GLTexture(gl.createTexture()); - - gl.bindTexture(gl.TEXTURE_CUBE_MAP, this.emptyTextures[gl.TEXTURE_CUBE_MAP].texture); - - let i; - - for (i = 0; i < 6; i++) - { - gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, null); - } - - gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_MAG_FILTER, gl.LINEAR); - gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_MIN_FILTER, gl.LINEAR); - - for (i = 0; i < this.boundTextures.length; i++) - { - this.bind(null, i); - } - } - - bind(texture, location) - { - const gl = this.gl; - - location = location || 0; - - if (this.currentLocation !== location) - { - this.currentLocation = location; - gl.activeTexture(gl.TEXTURE0 + location); - } - - if (texture) - { - texture = texture.baseTexture || texture; - - if (texture.valid) - { - texture.touched = this.renderer.textureGC.count; - - const glTexture = texture._glTextures[this.CONTEXT_UID] || this.initTexture(texture); - - gl.bindTexture(texture.target, glTexture.texture); - - if (glTexture.dirtyId !== texture.dirtyId) - { - this.updateTexture(texture); - } - - this.boundTextures[location] = texture; - } - } - else - { - gl.bindTexture(gl.TEXTURE_2D, this.emptyTextures[gl.TEXTURE_2D].texture); - this.boundTextures[location] = null; - } - } - - unbind(texture) - { - const gl = this.gl; - - for (let i = 0; i < this.boundTextures.length; i++) - { - if (this.boundTextures[i] === texture) - { - if (this.currentLocation !== i) - { - gl.activeTexture(gl.TEXTURE0 + i); - this.currentLocation = i; - } - - gl.bindTexture(gl.TEXTURE_2D, this.emptyTextures[texture.target].texture); - this.boundTextures[i] = null; - } - } - } - - initTexture(texture) - { - const glTexture = new GLTexture(this.gl.createTexture()); - - // guarentee an update.. - glTexture.dirtyId = -1; - - texture._glTextures[this.CONTEXT_UID] = glTexture; - - this.managedTextures.push(texture); - texture.on('dispose', this.destroyTexture, this); - - return glTexture; - } - - updateTexture(texture) - { - const glTexture = texture._glTextures[this.CONTEXT_UID]; - const renderer = this.renderer; - - if (texture.resource && texture.resource.upload(renderer, texture, glTexture)) - { - // texture is uploaded, dont do anything! - } - else - { - // default, renderTexture-like logic - const width = texture.realWidth; - const height = texture.realHeight; - const gl = renderer.gl; - - if (glTexture.width !== width - || glTexture.height !== height - || glTexture.dirtyId < 0) - { - glTexture.width = width; - glTexture.height = height; - - gl.texImage2D(texture.target, 0, - texture.format, - width, - height, - 0, - texture.format, - texture.type, - null); - } - } - - // lets only update what changes.. - if (texture.dirtyStyleId !== glTexture.dirtyStyleId) - { - this.updateTextureStyle(texture); - } - glTexture.dirtyId = texture.dirtyId; - } - - /** - * Deletes the texture from WebGL - * - * @param {PIXI.BaseTexture|PIXI.Texture} texture - the texture to destroy - * @param {boolean} [skipRemove=false] - Whether to skip removing the texture from the TextureManager. - */ - destroyTexture(texture, skipRemove) - { - const gl = this.gl; - - texture = texture.baseTexture || texture; - - if (texture._glTextures[this.renderer.CONTEXT_UID]) - { - this.unbind(texture); - - gl.deleteTexture(texture._glTextures[this.renderer.CONTEXT_UID].texture); - texture.off('dispose', this.destroyTexture, this); - - delete texture._glTextures[this.renderer.CONTEXT_UID]; - - if (!skipRemove) - { - const i = this.managedTextures.indexOf(texture); - - if (i !== -1) - { - removeItems(this.managedTextures, i, 1); - } - } - } - } - - updateTextureStyle(texture) - { - const glTexture = texture._glTextures[this.CONTEXT_UID]; - - glTexture.mipmap = texture.mipmap && texture.isPowerOfTwo; - if (!glTexture) - { - return; - } - - if (texture.resource && texture.resource.style(this.renderer, texture, glTexture)) - { - // style is set, dont do anything! - } - else - { - this.setStyle(texture, glTexture); - } - - glTexture.dirtyStyleId = texture.dirtyStyleId; - } - - setStyle(texture, glTexture) - { - const gl = this.gl; - - if (glTexture.mipmap) - { - gl.generateMipmap(texture.target); - } - - gl.texParameteri(texture.target, gl.TEXTURE_WRAP_S, texture.wrapMode); - gl.texParameteri(texture.target, gl.TEXTURE_WRAP_T, texture.wrapMode); - - if (glTexture.mipmap) - { - /* eslint-disable max-len */ - gl.texParameteri(texture.target, gl.TEXTURE_MIN_FILTER, texture.scaleMode ? gl.LINEAR_MIPMAP_LINEAR : gl.NEAREST_MIPMAP_NEAREST); - /* eslint-disable max-len */ - } - else - { - gl.texParameteri(texture.target, gl.TEXTURE_MIN_FILTER, texture.scaleMode ? gl.LINEAR : gl.NEAREST); - } - - gl.texParameteri(texture.target, gl.TEXTURE_MAG_FILTER, texture.scaleMode ? gl.LINEAR : gl.NEAREST); - } -} diff --git a/packages/core/src/renderers/utils/ObjectRenderer.js b/packages/core/src/renderers/utils/ObjectRenderer.js deleted file mode 100644 index f67a273..0000000 --- a/packages/core/src/renderers/utils/ObjectRenderer.js +++ /dev/null @@ -1,48 +0,0 @@ -import WebGLSystem from '../systems/WebGLSystem'; - -/** - * Base for a common object renderer that can be used as a system renderer plugin. - * - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class ObjectRenderer extends WebGLSystem -{ - /** - * Starts the renderer and sets the shader - * - */ - start() - { - // set the shader.. - } - - /** - * Stops the renderer - * - */ - stop() - { - this.flush(); - } - - /** - * Stub method for rendering content and emptying the current batch. - * - */ - flush() - { - // flush! - } - - /** - * Renders an object - * - * @param {PIXI.DisplayObject} object - The object to render. - */ - render(object) // eslint-disable-line no-unused-vars - { - // render the object - } -} diff --git a/packages/core/src/renderers/utils/Quad.js b/packages/core/src/renderers/utils/Quad.js deleted file mode 100644 index 5c1517b..0000000 --- a/packages/core/src/renderers/utils/Quad.js +++ /dev/null @@ -1,26 +0,0 @@ -import Geometry from '../../geometry/Geometry'; - -/** - * Helper class to create a quad - * - * @class - * @memberof PIXI - */ -export default class Quad extends Geometry -{ - /** - * just a quad - */ - constructor() - { - super(); - - this.addAttribute('aVertexPosition', [ - 0, 0, - 1, 0, - 1, 1, - 0, 1, - ]) - .addIndex([0, 1, 3, 2]); - } -} diff --git a/packages/core/src/renderers/utils/QuadUv.js b/packages/core/src/renderers/utils/QuadUv.js deleted file mode 100644 index 97bda89..0000000 --- a/packages/core/src/renderers/utils/QuadUv.js +++ /dev/null @@ -1,103 +0,0 @@ -import Geometry from '../../geometry/Geometry'; -import Buffer from '../../geometry/Buffer'; - -/** - * Helper class to create a quad with uvs like in v4 - * - * @class - * @memberof PIXI - */ -export default class QuadUv extends Geometry -{ - constructor() - { - super(); - - /** - * An array of vertices - * - * @member {Float32Array} - */ - this.vertices = new Float32Array([ - -1, -1, - 1, -1, - 1, 1, - -1, 1, - ]); - - /** - * The Uvs of the quad - * - * @member {Float32Array} - */ - this.uvs = new Float32Array([ - 0, 0, - 1, 0, - 1, 1, - 0, 1, - ]); - - this.vertexBuffer = new Buffer(this.vertices); - this.uvBuffer = new Buffer(this.uvs); - - this.addAttribute('aVertexPosition', this.vertexBuffer) - .addAttribute('aTextureCoord', this.uvBuffer) - .addIndex([0, 1, 2, 0, 2, 3]); - } - - /** - * Maps two Rectangle to the quad. - * - * @param {PIXI.Rectangle} targetTextureFrame - the first rectangle - * @param {PIXI.Rectangle} destinationFrame - the second rectangle - * @return {PIXI.Quad} Returns itself. - */ - map(targetTextureFrame, destinationFrame) - { - let x = 0; // destinationFrame.x / targetTextureFrame.width; - let y = 0; // destinationFrame.y / targetTextureFrame.height; - - this.uvs[0] = x; - this.uvs[1] = y; - - this.uvs[2] = x + (destinationFrame.width / targetTextureFrame.width); - this.uvs[3] = y; - - this.uvs[4] = x + (destinationFrame.width / targetTextureFrame.width); - this.uvs[5] = y + (destinationFrame.height / targetTextureFrame.height); - - this.uvs[6] = x; - this.uvs[7] = y + (destinationFrame.height / targetTextureFrame.height); - - x = destinationFrame.x; - y = destinationFrame.y; - - this.vertices[0] = x; - this.vertices[1] = y; - - this.vertices[2] = x + destinationFrame.width; - this.vertices[3] = y; - - this.vertices[4] = x + destinationFrame.width; - this.vertices[5] = y + destinationFrame.height; - - this.vertices[6] = x; - this.vertices[7] = y + destinationFrame.height; - - this.invalidate(); - - return this; - } - - /** - * legacy upload method, just marks buffers dirty - * @returns {PIXI.QuadUv} Returns itself. - */ - invalidate() - { - this.vertexBuffer._updateID++; - this.uvBuffer._updateID++; - - return this; - } -} diff --git a/packages/core/src/renderers/utils/checkMaxIfStatmentsInShader.js b/packages/core/src/renderers/utils/checkMaxIfStatmentsInShader.js deleted file mode 100644 index 8047c96..0000000 --- a/packages/core/src/renderers/utils/checkMaxIfStatmentsInShader.js +++ /dev/null @@ -1,58 +0,0 @@ -const fragTemplate = [ - 'precision mediump float;', - 'void main(void){', - 'float test = 0.1;', - '%forloop%', - 'gl_FragColor = vec4(0.0);', - '}', -].join('\n'); - -export default function checkMaxIfStatmentsInShader(maxIfs, gl) -{ - if (maxIfs === 0) - { - throw new Error('Invalid value of `0` passed to `checkMaxIfStatementsInShader`'); - } - - const shader = gl.createShader(gl.FRAGMENT_SHADER); - - while (true) // eslint-disable-line no-constant-condition - { - const fragmentSrc = fragTemplate.replace(/%forloop%/gi, generateIfTestSrc(maxIfs)); - - gl.shaderSource(shader, fragmentSrc); - gl.compileShader(shader); - - if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) - { - maxIfs = (maxIfs / 2) | 0; - } - else - { - // valid! - break; - } - } - - return maxIfs; -} - -function generateIfTestSrc(maxIfs) -{ - let src = ''; - - for (let i = 0; i < maxIfs; ++i) - { - if (i > 0) - { - src += '\nelse '; - } - - if (i < maxIfs - 1) - { - src += `if(test == ${i}.0){}`; - } - } - - return src; -} diff --git a/packages/core/src/AbstractRenderer.js b/packages/core/src/AbstractRenderer.js new file mode 100644 index 0000000..64b2eb1 --- /dev/null +++ b/packages/core/src/AbstractRenderer.js @@ -0,0 +1,353 @@ +import { hex2string, hex2rgb } from '@pixi/utils'; +import { Matrix, Rectangle } from '@pixi/math'; +import { RENDERER_TYPE } from '@pixi/constants'; +import { settings } from '@pixi/settings'; +import { Container } from '@pixi/display'; +import RenderTexture from './renderTexture/RenderTexture'; +import EventEmitter from 'eventemitter3'; + +const tempMatrix = new Matrix(); + +/** + * 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 EventEmitter + * @memberof PIXI + */ +export default class AbstractRenderer extends EventEmitter +{ + // eslint-disable-next-line valid-jsdoc + /** + * @param {string} system - The name of the system this renderer is for. + * @param {object} [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.transparent=false] - If the render view is transparent, default false + * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false + * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. The + * resolution of the renderer retina would be 2. + * @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 {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when rendering, + * stopping pixel interpolation. + */ + constructor(system, options, arg2, arg3) + { + super(); + + // Support for constructor(system, screenWidth, screenHeight, options) + if (typeof options === 'number') + { + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + }, arg3); + } + + // 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 = RENDERER_TYPE.UNKNOWN; + + /** + * Measurements of the screen. (0, 0, screenWidth, screenHeight) + * + * Its safe to use as filterArea or hitArea for 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 1 + */ + this.resolution = options.resolution || settings.RESOLUTION; + + /** + * Whether the render view is transparent + * + * @member {boolean} + */ + this.transparent = options.transparent; + + /** + * Whether css dimensions of canvas view should be resized to screen dimensions automatically + * + * @member {boolean} + */ + this.autoResize = options.autoResize || false; + + /** + * Tracks the blend modes useful for this renderer. + * + * @member {object} + */ + this.blendModes = null; + + /** + * 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; + + /** + * If true PixiJS will Math.floor() x/y values when rendering, stopping pixel interpolation. + * Handy for crisp pixel art and speed on legacy devices. + * + * @member {boolean} + */ + this.roundPixels = options.roundPixels; + + /** + * The background color as a number. + * + * @member {number} + * @private + */ + this._backgroundColor = 0x000000; + + /** + * The background color as an [R, G, B] array. + * + * @member {number[]} + * @private + */ + this._backgroundColorRgba = [0, 0, 0, 0]; + + /** + * The background color as a string. + * + * @member {string} + * @private + */ + this._backgroundColorString = '#000000'; + + this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter + + /** + * This temporary display object used as the parent of the currently being rendered item + * + * @member {PIXI.DisplayObject} + * @private + */ + this._tempDisplayObjectParent = new Container(); + + /** + * The last root object that the renderer tried to render. + * + * @member {PIXI.DisplayObject} + * @private + */ + this._lastObjectRendered = this._tempDisplayObjectParent; + + /** + * Collection of plugins. + * @readonly + * @member {object} + */ + this.plugins = {}; + } + + /** + * Initialize the plugins. + * + * @protected + * @param {object} staticMap - The dictionary of staticly saved plugins. + */ + initPlugins(staticMap) + { + 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() + { + return this.view.width; + } + + /** + * Same as view.height, actual number of pixels in the canvas by vertical + * + * @member {number} + * @readonly + * @default 600 + */ + get height() + { + return this.view.height; + } + + /** + * Resizes the screen and canvas to the specified width and height + * Canvas dimensions are multiplied by resolution + * + * @param {number} screenWidth - the new width of the screen + * @param {number} screenHeight - the new height of the screen + */ + resize(screenWidth, screenHeight) + { + this.screen.width = screenWidth; + this.screen.height = screenHeight; + + this.view.width = screenWidth * this.resolution; + this.view.height = screenHeight * this.resolution; + + if (this.autoResize) + { + this.view.style.width = `${screenWidth}px`; + this.view.style.height = `${screenHeight}px`; + } + } + + /** + * 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 {PIXI.DisplayObject} displayObject - The displayObject the object will be generated from + * @param {number} scaleMode - Should be one of the scaleMode consts + * @param {number} resolution - The resolution / device pixel ratio of the texture being generated + * @param {PIXI.Rectangle} [region] - The region of the displayObject, that shall be rendered, + * if no region is specified, defaults to the local bounds of the displayObject. + * @return {PIXI.Texture} a texture of the graphics object + */ + generateTexture(displayObject, scaleMode, resolution, region) + { + region = region || displayObject.getLocalBounds(); + + // 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(region.width | 0, region.height | 0, scaleMode, resolution); + + tempMatrix.tx = -region.x; + tempMatrix.ty = -region.y; + + this.render(displayObject, renderTexture, false, tempMatrix, true); + + return renderTexture; + } + + /** + * Removes everything from the renderer and optionally removes the Canvas DOM element. + * + * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. + */ + destroy(removeView) + { + 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); + } + + this.plugins = null; + + this.type = RENDERER_TYPE.UNKNOWN; + + this.view = null; + + this.screen = null; + + this.resolution = 0; + + this.transparent = false; + + this.autoResize = false; + + this.blendModes = null; + + this.options = null; + + this.preserveDrawingBuffer = false; + this.clearBeforeRender = false; + + this.roundPixels = false; + + this._backgroundColor = 0; + this._backgroundColorRgba = null; + this._backgroundColorString = null; + + this._tempDisplayObjectParent = null; + this._lastObjectRendered = null; + } + + /** + * The background color to fill if not transparent + * + * @member {number} + */ + get backgroundColor() + { + return this._backgroundColor; + } + + set backgroundColor(value) // eslint-disable-line require-jsdoc + { + this._backgroundColor = value; + this._backgroundColorString = hex2string(value); + hex2rgb(value, this._backgroundColorRgba); + } +} diff --git a/packages/core/src/Renderer.js b/packages/core/src/Renderer.js new file mode 100644 index 0000000..2227e78 --- /dev/null +++ b/packages/core/src/Renderer.js @@ -0,0 +1,338 @@ +import AbstractRenderer from './AbstractRenderer'; +import { sayHello } from '@pixi/utils'; +import MaskSystem from './mask/MaskSystem'; +import StencilSystem from './mask/StencilSystem'; +import FilterSystem from './filters/FilterSystem'; +import FramebufferSystem from './framebuffer/FramebufferSystem'; +import RenderTextureSystem from './renderTexture/RenderTextureSystem'; +import TextureSystem from './textures/TextureSystem'; +import ProjectionSystem from './projection/ProjectionSystem'; +import StateSystem from './state/StateSystem'; +import GeometrySystem from './geometry/GeometrySystem'; +import ShaderSystem from './shader/ShaderSystem'; +import ContextSystem from './context/ContextSystem'; +import BatchSystem from './batch/BatchSystem'; +import TextureGCSystem from './textures/TextureGCSystem'; +import { RENDERER_TYPE } from '@pixi/constants'; +import UniformGroup from './shader/UniformGroup'; +import { Matrix } from '@pixi/math'; +import Runner from 'mini-runner'; + +/** + * 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 Render works by automatically managing webGLBatchs. + * 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 :) + * + * @class + * @memberof PIXI + * @extends PIXI.AbstractRenderer + */ +export default class Renderer extends AbstractRenderer +{ + // eslint-disable-next-line valid-jsdoc + /** + * + * @param {object} [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.transparent=false] - If the render view is transparent, default false + * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false + * @param {boolean} [options.antialias=false] - sets antialias. If not available natively then FXAA + * antialiasing is used + * @param {boolean} [options.forceFXAA=false] - forces FXAA antialiasing to be used over native. + * FXAA is faster, but may not always look as great + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. + * The resolution of the renderer retina would be 2. + * @param {boolean} [options.clearBeforeRender=true] - This sets if the renderer will clear + * the canvas or not before the new render pass. If you wish to set this to false, you *must* set + * preserveDrawingBuffer to `true`. + * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, + * enable this if you need to call toDataUrl on the webgl context. + * @param {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when + * rendering, stopping pixel interpolation. + * @param {number} [options.backgroundColor=0x000000] - The background color of the rendered area + * (shown if not transparent). + * @param {boolean} [options.legacy=false] - If true PixiJS will aim to ensure compatibility + * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. + * @param {string} [options.powerPreference] - Parameter passed to webgl context, set to "high-performance" + * for devices with dual graphics card + */ + constructor(options, arg2, arg3) + { + super('WebGL', options, arg2, arg3); + + /** + * The type of this renderer as a standardised const + * + * @member {number} + * @see PIXI.RENDERER_TYPE + */ + this.type = RENDERER_TYPE.WEBGL; + + // this will be set by the contextSystem (this.context) + this.gl = null; + this.CONTEXT_UID = 0; + this.legacy = !!options.legacy; + + // TODO legacy! + + // runners! + this.runners = { + destroy: new Runner('destroy'), + contextChange: new Runner('contextChange', 1), + reset: new Runner('reset'), + update: new Runner('update'), + postrender: new Runner('postrender'), + prerender: new Runner('prerender'), + resize: new Runner('resize', 2), + }; + + 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(GeometrySystem, 'geometry') + .addSystem(FramebufferSystem, 'framebuffer') + .addSystem(StencilSystem, 'stencil') + .addSystem(ProjectionSystem, 'projection') + .addSystem(TextureGCSystem, 'textureGC') + .addSystem(FilterSystem, 'filter') + .addSystem(RenderTextureSystem, 'renderTexture') + .addSystem(BatchSystem, 'batch'); + + this.initPlugins(Renderer.__plugins); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ + if (options.context) + { + this.context.initFromContext(options.context); + } + else + { + this.context.initFromOptions({ + alpha: this.transparent, + antialias: options.antialias, + premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', + stencil: true, + 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); + } + + /** + * Add a new system to the renderer. + * @param {class} ClassRef - Class reference + * @param {string} [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. + * @return {PIXI.Renderer} Return instance of renderer + */ + addSystem(ClassRef, name) + { + if (!name) + { + name = ClassRef.name; + } + + // TODO - read name from class.name.. + + /* + if(name.includes('System')) + { + name = name.replace('System', ''); + name = name.charAt(0).toLowerCase() + name.slice(1); + } + */ + + const system = new ClassRef(this); + + if (this[name]) + { + throw new Error(`Whoops! ${name} is already a manger`); + } + + this[name] = system; + + for (const i in this.runners) + { + this.runners[i].add(system); + } + + return this; + + /** + * 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. + */ + } + + /** + * Renders the object to its webGL view + * + * @param {PIXI.DisplayObject} displayObject - the object to be rendered + * @param {PIXI.RenderTexture} renderTexture - The render texture to render to. + * @param {boolean} [clear] - Should the canvas be cleared before the new render + * @param {PIXI.Transform} [transform] - A transform to apply to the render texture before rendering. + * @param {boolean} [skipUpdateTransform] - Should we skip the update transform pass? + */ + render(displayObject, renderTexture, clear, transform, skipUpdateTransform) + { + // can be handy to know! + this.renderingToScreen = !renderTexture; + + this.runners.prerender.run(); + this.emit('prerender'); + + // 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.parent; + + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = 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.run(); + + this.emit('postrender'); + } + + /** + * Resizes the webGL view to the specified width and height. + * + * @param {number} screenWidth - the new width of the screen + * @param {number} screenHeight - the new height of the screen + */ + resize(screenWidth, screenHeight) + { + AbstractRenderer.prototype.resize.call(this, screenWidth, screenHeight); + + this.runners.resize.run(screenWidth, screenHeight); + } + + /** + * Resets the WebGL state so you can render things however you fancy! + * + * @return {PIXI.Renderer} Returns itself. + */ + reset() + { + this.runners.reset.run(); + + return this; + } + + clear() + { + this.framebuffer.bind(); + this.framebuffer.clear(); + } + + /** + * Removes everything from the renderer (event listeners, spritebatch, etc...) + * + * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. + * See: https://github.com/pixijs/pixi.js/issues/2233 + */ + destroy(removeView) + { + this.runners.destroy.run(); + + // call base destroy + super.destroy(removeView); + + // TODO nullify all the managers.. + this.gl = null; + } + + /** + * 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 + * builds and excluding plugins. + * @name PIXI.Renderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. + */ + + /** + * Adds a plugin to the renderer. + * + * @method + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + static registerPlugin(pluginName, ctor) + { + Renderer.__plugins = Renderer.__plugins || {}; + Renderer.__plugins[pluginName] = ctor; + } +} diff --git a/packages/core/src/System.js b/packages/core/src/System.js new file mode 100644 index 0000000..05a10c8 --- /dev/null +++ b/packages/core/src/System.js @@ -0,0 +1,42 @@ +/** + * System is a base class used for extending systems used by the {@link PIXI.Renderer} + * @see PIXI.Renderer#addSystem + * @class + * @memberof PIXI + */ +export default class System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + /** + * The renderer this manager works for. + * + * @member {PIXI.Renderer} + */ + this.renderer = renderer; + + this.renderer.runners.contextChange.add(this); + } + + /** + * Generic method called when there is a WebGL context change. + * + */ + contextChange() + { + // do some codes init! + } + + /** + * Generic destroy methods to be overridden by the subclass + * + */ + destroy() + { + this.renderer.runners.contextChange.remove(this); + this.renderer = null; + } +} diff --git a/packages/core/src/batch/BatchSystem.js b/packages/core/src/batch/BatchSystem.js new file mode 100644 index 0000000..b04c249 --- /dev/null +++ b/packages/core/src/batch/BatchSystem.js @@ -0,0 +1,68 @@ +import System from '../System'; +import ObjectRenderer from './ObjectRenderer'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class BatchSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + super(renderer); + + /** + * An empty renderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.emptyRenderer = new ObjectRenderer(renderer); + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.currentRenderer = this.emptyRenderer; + } + + /** + * Changes the current renderer to the one given in parameter + * + * @param {PIXI.ObjectRenderer} objectRenderer - The object renderer to use. + */ + setObjectRenderer(objectRenderer) + { + if (this.currentRenderer === objectRenderer) + { + return; + } + + this.currentRenderer.stop(); + this.currentRenderer = objectRenderer; + + this.renderer.state.setState(objectRenderer.state); + + this.currentRenderer.start(); + } + + /** + * This should be called if you wish to do some custom rendering + * It will basically render anything that may be batched up such as sprites + * + */ + flush() + { + this.setObjectRenderer(this.emptyRenderer); + } + + reset() + { + this.setObjectRenderer(this.emptyRenderer); + } +} diff --git a/packages/core/src/batch/ObjectRenderer.js b/packages/core/src/batch/ObjectRenderer.js new file mode 100644 index 0000000..0c851a9 --- /dev/null +++ b/packages/core/src/batch/ObjectRenderer.js @@ -0,0 +1,48 @@ +import System from '../System'; + +/** + * Base for a common object renderer that can be used as a system renderer plugin. + * + * @class + * @extends PIXI.System + * @memberof PIXI + */ +export default class ObjectRenderer extends System +{ + /** + * Starts the renderer and sets the shader + * + */ + start() + { + // set the shader.. + } + + /** + * Stops the renderer + * + */ + stop() + { + this.flush(); + } + + /** + * Stub method for rendering content and emptying the current batch. + * + */ + flush() + { + // flush! + } + + /** + * Renders an object + * + * @param {PIXI.DisplayObject} object - The object to render. + */ + render(object) // eslint-disable-line no-unused-vars + { + // render the object + } +} diff --git a/packages/core/src/context/ContextSystem.js b/packages/core/src/context/ContextSystem.js new file mode 100644 index 0000000..301aab4 --- /dev/null +++ b/packages/core/src/context/ContextSystem.js @@ -0,0 +1,186 @@ +import System from '../System'; +import { settings } from '@pixi/settings'; + +let CONTEXT_UID = 0; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class ContextSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.webGLVersion = 1; + + this.handleContextLost = this.handleContextLost.bind(this); + this.handleContextRestored = this.handleContextRestored.bind(this); + + this.extensions = {}; + + renderer.view.addEventListener('webglcontextlost', this.handleContextLost, false); + renderer.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + } + + get isLost() + { + return (!this.gl || this.gl.isContextLost()); + } + + contextChange(gl) + { + this.gl = gl; + + // restore a context if it was previously lost + if (gl.isContextLost() && gl.getExtension('WEBGL_lose_context')) + { + gl.getExtension('WEBGL_lose_context').restoreContext(); + } + } + + initFromContext(gl) + { + this.gl = gl; + this.validateContext(gl); + this.renderer.gl = gl; + this.renderer.CONTEXT_UID = CONTEXT_UID++; + this.renderer.runners.contextChange.run(gl); + } + + initFromOptions(options) + { + const gl = this.createContext(this.renderer.view, options); + + this.initFromContext(gl); + } + + /** + * Helper class to create a webGL Context + * + * @class + * @memberof PIXI.glCore + * @param canvas {HTMLCanvasElement} the canvas element that we will get the context from + * @param options {Object} An options object that gets passed in to the canvas element containing the context attributes, + * see https://developer.mozilla.org/en/docs/Web/API/HTMLCanvasElement/getContext for the options available + * @return {WebGLRenderingContext} the WebGL context + */ + createContext(canvas, options) + { + let gl; + + if (settings.PREFER_WEBGL_2) + { + gl = canvas.getContext('webgl2', options); + } + + if (gl) + { + this.webGLVersion = 2; + } + else + { + this.webGLVersion = 1; + + gl = canvas.getContext('webgl', options) + || canvas.getContext('experimental-webgl', options); + + if (!gl) + { + // fail, not able to get a context + throw new Error('This browser does not support webGL. Try using the canvas renderer'); + } + } + + this.gl = gl; + + this.getExtensions(); + + return gl; + } + + getExtensions() + { + // time to set up default etensions that pixi uses.. + const gl = this.gl; + const extensions = this.extensions; + + if (this.webGLVersion === 1) + { + extensions.drawBuffers = gl.getExtension('WEBGL_draw_buffers'); + extensions.depthTexture = gl.getExtension('WEBKIT_WEBGL_depth_texture'); + extensions.floatTexture = gl.getExtension('OES_texture_float'); + extensions.loseContext = gl.getExtension('WEBGL_lose_context'); + + extensions.vertexArrayObject = gl.getExtension('OES_vertex_array_object') + || gl.getExtension('MOZ_OES_vertex_array_object') + || gl.getExtension('WEBKIT_OES_vertex_array_object'); + } + + // we don't use any specific WebGL 2 ones yet! + } + + /** + * Handles a lost webgl context + * + * @private + * @param {WebGLContextEvent} event - The context lost event. + */ + handleContextLost(event) + { + event.preventDefault(); + } + + /** + * Handles a restored webgl context + * + * @private + */ + handleContextRestored() + { + this.renderer.runners.contextChange.run(this.gl); + } + + destroy() + { + const view = this.renderer.view; + + // remove listeners + view.removeEventListener('webglcontextlost', this.handleContextLost); + view.removeEventListener('webglcontextrestored', this.handleContextRestored); + + this.gl.useProgram(null); + + if (this.extensions.loseContext) + { + this.extensions.loseContext.loseContext(); + } + } + + postrender() + { + this.gl.flush(); + } + + validateContext(gl) + { + const attributes = gl.getContextAttributes(); + + // this is going to be fairly simple for now.. but at least we have room to grow! + if (!attributes.stencil) + { + /* eslint-disable max-len */ + + /* eslint-disable no-console */ + console.warn('Provided WebGL context does not have a stencil buffer, masks may not render correctly'); + /* eslint-enable no-console */ + + /* eslint-enable max-len */ + } + } +} diff --git a/packages/core/src/filters/Filter.js b/packages/core/src/filters/Filter.js new file mode 100644 index 0000000..9d8cad8 --- /dev/null +++ b/packages/core/src/filters/Filter.js @@ -0,0 +1,140 @@ +import Shader from '../shader/Shader'; +import Program from '../shader/Program'; +import State from '../state/State'; +import { settings } from '@pixi/settings'; +// import extractUniformsFromSrc from './extractUniformsFromSrc'; +import defaultVertex from './defaultFilter.vert'; +import defaultFragment from './defaultFilter.frag'; + +// let math = require('../../math'); +/** + * @class + * @memberof PIXI + * @extends PIXI.Shader + */ +export default class Filter extends Shader +{ + /** + * @param {string} [vertexSrc] - The source of the vertex shader. + * @param {string} [fragmentSrc] - The source of the fragment shader. + * @param {object} [uniforms] - Custom uniforms to use to augment the built-in ones. + */ + constructor(vertexSrc, fragmentSrc, uniforms) + { + const program = Program.from(vertexSrc, fragmentSrc); + + super(program, uniforms); + + /** + * The padding of the filter. Some filters require extra space to breath such as a blur. + * Increasing this will add extra width and height to the bounds of the object that the + * filter is applied to. + * + * @member {number} + */ + this.padding = 0; + + /** + * The resolution of the filter. Setting this to be lower will lower the quality but + * increase the performance of the filter. + * + * @member {number} + */ + this.resolution = settings.RESOLUTION; + + /** + * If enabled is true the filter is applied, if false it will not. + * + * @member {boolean} + */ + this.enabled = true; + + /** + * If enabled, PixiJS will fit the filter area into boundaries for better performance. + * Switch it off if it does not work for specific shader. + * + * @member {boolean} + */ + this.autoFit = true; + + /** + * Legacy filters use position and uvs from attributes + * @member {boolean} + * @readonly + */ + this.legacy = !!this.program.attributeData.aTextureCoord; + + /** + * the webGL state the filter requires to render + * @member {PIXI.State} + */ + this.state = new State(); + } + + /** + * Applies the filter + * + * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + * @param {boolean} clear - Should the output be cleared before rendering to it + * @param {object} [currentState] - It's current state of filter. + * There are some useful properties in the currentState : + * target, filters, sourceFrame, destinationFrame, renderTarget, resolution + */ + apply(filterManager, input, output, clear, currentState, derp) // eslint-disable-line no-unused-vars + { + // do as you please! + + filterManager.applyFilter(this, input, output, clear, currentState, derp); + + // or just do a regular render.. + } + + /** + * Sets the blendmode of the filter + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL + */ + get blendMode() + { + return this.state.blendMode; + } + + set blendMode(value) // eslint-disable-line require-jsdoc + { + this.state.blendMode = value; + } + + /** + * The default vertex shader source + * + * @static + * @constant + */ + static get defaultVertexSrc() + { + return defaultVertex; + } + + /** + * The default fragment shader source + * + * @static + * @constant + */ + static get defaultFragmentSrc() + { + return defaultFragment; + } +} + +/** + * Used for caching shader IDs + * + * @static + * @private + */ +Filter.SOURCE_KEY_MAP = {}; + diff --git a/packages/core/src/filters/FilterSystem.js b/packages/core/src/filters/FilterSystem.js new file mode 100644 index 0000000..3174318 --- /dev/null +++ b/packages/core/src/filters/FilterSystem.js @@ -0,0 +1,431 @@ +import System from '../System'; + +import RenderTexture from '../renderTexture/RenderTexture'; +import Quad from '../utils/Quad'; +import QuadUv from '../utils/QuadUv'; +import { Rectangle } from '@pixi/math'; +import * as filterTransforms from './filterTransforms'; +import bitTwiddle from 'bit-twiddle'; +import UniformGroup from '../shader/UniformGroup'; +import { DRAW_MODES } from '@pixi/constants'; + +// +/** + * @ignore + * @class + */ +class FilterState +{ + /** + * + */ + constructor() + { + this.renderTexture = null; + this.sourceFrame = new Rectangle(); + this.destinationFrame = new Rectangle(); + this.filters = []; + this.target = null; + this.legacy = false; + this.resolution = 1; + } +} + +/** + * @class + * @memberof PIXI.systems + * @extends PIXI.System + */ +export default class FilterSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + /** + * stores a bunch of PO2 textures used for filtering + * @type {Object} + */ + this.texturePool = {}; + + /** + * a pool for storing filter states, save us creating new ones each tick + * @type {Array} + */ + this.statePool = []; + + /** + * A very simple geometry used when drawing a filter effect to the screen + * @type {Quad} + */ + this.quad = new Quad(); + + this.quadUv = new QuadUv(); + + /** + * Temporary rect for maths + * @type {PIXI.Rectangle} + */ + this.tempRect = new Rectangle(); + + this.activeState = {}; + + /** + * this uniform group is attached to filter uniforms when used + * @type {UniformGroup} + */ + this.globalUniforms = new UniformGroup({ + sourceFrame: this.tempRect, + destinationFrame: this.tempRect, + + // legacy variables + filterArea: new Float32Array(4), + filterClamp: new Float32Array(4), + }, true); + } + + /** + * Adds a new filter to the System. + * + * @param {PIXI.DisplayObject} target - The target of the filter to render. + * @param {PIXI.Filter[]} filters - The filters to apply. + */ + push(target, filters) + { + const renderer = this.renderer; + const filterStack = this.renderer.renderTexture.defaultFilterStack; + const state = this.statePool.pop() || new FilterState(); + + let resolution = filters[0].resolution; + let padding = filters[0].padding; + let autoFit = filters[0].autoFit; + let legacy = filters[0].legacy; + + for (let i = 1; i < filters.length; i++) + { + const filter = filters[i]; + + // lets use the lowest resolution.. + resolution = Math.min(resolution, filter.resolution); + // and the largest amount of padding! + padding = Math.max(padding, filter.padding); + // only auto fit if all filters are autofit + autoFit = autoFit || filter.autoFit; + + legacy = legacy || filter.legacy; + } + + filterStack.push(state); + + state.resolution = resolution; + + state.legacy = legacy; + + // round to whole number based on resolution + // TODO move that to the shader too? + state.sourceFrame = target.filterArea || target.getBounds(true); + + state.sourceFrame.pad(padding); + + if (autoFit) + { + state.sourceFrame.fit(this.renderer.renderTexture.destinationFrame); + } + + state.sourceFrame.round(resolution); + + state.renderTexture = this.getPotFilterTexture(state.sourceFrame.width, state.sourceFrame.height, resolution); + state.filters = filters; + + state.destinationFrame.width = state.renderTexture.width; + state.destinationFrame.height = state.renderTexture.height; + + state.renderTexture.filterFrame = state.sourceFrame; + renderer.renderTexture.bind(state.renderTexture, state.sourceFrame);// /, state.destinationFrame); + renderer.renderTexture.clear(); + } + + /** + * Pops off the filter and applies it. + * + */ + pop() + { + const renderer = this.renderer; + const filterStack = renderer.renderTexture.defaultFilterStack; + const state = filterStack.pop(); + const filters = state.filters; + + this.activeState = state; + + const globalUniforms = this.globalUniforms.uniforms; + + globalUniforms.sourceFrame = state.sourceFrame; + globalUniforms.destinationFrame = state.destinationFrame; + globalUniforms.resolution = state.resolution; + + // only update the rect if its legacy.. + if (state.legacy) + { + const filterArea = globalUniforms.filterArea; + const filterClamp = globalUniforms.filterClamp; + + filterArea[0] = state.destinationFrame.width; + filterArea[1] = state.destinationFrame.height; + filterArea[2] = state.sourceFrame.x; + filterArea[3] = state.sourceFrame.y; + + filterClamp[0] = 0.5 / state.resolution / state.destinationFrame.width; + filterClamp[1] = 0.5 / state.resolution / state.destinationFrame.height; + filterClamp[2] = (state.sourceFrame.width - 0.5) / state.resolution / state.destinationFrame.width; + filterClamp[3] = (state.sourceFrame.height - 0.5) / state.resolution / state.destinationFrame.height; + } + + this.globalUniforms.update(); + + const lastState = filterStack[filterStack.length - 1]; + + if (filters.length === 1) + { + filters[0].apply(this, state.renderTexture, lastState.renderTexture, false, state); + + this.returnFilterTexture(state.renderTexture); + } + else + { + let flip = state.renderTexture; + let flop = this.getPotFilterTexture( + flip.width, + flip.height, + state.resolution + ); + + flop.filterFrame = flip.filterFrame; + + let i = 0; + + for (i = 0; i < filters.length - 1; ++i) + { + filters[i].apply(this, flip, flop, true, state); + + const t = flip; + + flip = flop; + flop = t; + } + + filters[i].apply(this, flip, lastState.renderTexture, false, state); + + this.returnFilterTexture(flip); + this.returnFilterTexture(flop); + } + + this.statePool.push(state); + } + + /** + * Draws a filter. + * + * @param {PIXI.Filter} filter - The filter to draw. + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + * @param {boolean} clear - Should the output be cleared before rendering to it + */ + applyFilter(filter, input, output, clear) + { + const renderer = this.renderer; + + renderer.renderTexture.bind(output, output ? output.filterFrame : null); + + if (clear) + { + // gl.disable(gl.SCISSOR_TEST); + renderer.renderTexture.clear(); + // gl.enable(gl.SCISSOR_TEST); + } + + // set the uniforms.. + filter.uniforms.uSampler = input; + filter.uniforms.filterGlobals = this.globalUniforms; + + // TODO make it so that the order of this does not matter.. + // because it does at the moment cos of global uniforms. + // they need to get resynced + + renderer.state.setState(filter.state); + renderer.shader.bind(filter); + + if (filter.legacy) + { + this.quadUv.map(input._frame, input.filterFrame); + + renderer.geometry.bind(this.quadUv); + renderer.geometry.draw(DRAW_MODES.TRIANGLES); + } + else + { + renderer.geometry.bind(this.quad); + renderer.geometry.draw(DRAW_MODES.TRIANGLE_STRIP); + } + } + + /** + * Calculates the mapped matrix. + * + * TODO playing around here.. this is temporary - (will end up in the shader) + * this returns a matrix that will normalise map filter cords in the filter to screen space + * + * @param {PIXI.Matrix} outputMatrix - the matrix to output to. + * @return {PIXI.Matrix} The mapped matrix. + */ + calculateScreenSpaceMatrix(outputMatrix) + { + const currentState = this.activeState; + + return filterTransforms.calculateScreenSpaceMatrix( + outputMatrix, + currentState.sourceFrame, + currentState.destinationFrame + ); + } + + /** + * This will map the filter coord so that a texture can be used based on the transform of a sprite + * + * @param {PIXI.Matrix} outputMatrix - The matrix to output to. + * @param {PIXI.Sprite} sprite - The sprite to map to. + * @return {PIXI.Matrix} The mapped matrix. + */ + calculateSpriteMatrix(outputMatrix, sprite) + { + const currentState = this.activeState; + + return filterTransforms.calculateSpriteMatrix( + outputMatrix, + currentState.sourceFrame, + currentState.destinationFrame, + sprite + ); + } + + /** + * Destroys this Filter System. + * + * @param {boolean} [contextLost=false] context was lost, do not free shaders + * + */ + destroy(contextLost = false) + { + if (!contextLost) + { + this.emptyPool(); + } + else + { + this.texturePool = {}; + } + } + + /** + * Gets a Power-of-Two render texture. + * + * TODO move to a seperate class could be on renderer? + * also - could cause issue with multiple contexts? + * + * @private + * @param {WebGLRenderingContext} gl - The webgl rendering context + * @param {number} minWidth - The minimum width of the render target. + * @param {number} minHeight - The minimum height of the render target. + * @param {number} resolution - The resolution of the render target. + * @return {PIXI.RenderTarget} The new render target. + */ + getPotFilterTexture(minWidth, minHeight, resolution) + { + minWidth = bitTwiddle.nextPow2(minWidth); + minHeight = bitTwiddle.nextPow2(minHeight); + resolution = resolution || 1; + + const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + + if (!this.texturePool[key]) + { + this.texturePool[key] = []; + } + + let renderTexture = this.texturePool[key].pop(); + + if (!renderTexture) + { + // temporary bypass cache.. + // internally - this will cause a texture to be bound.. + renderTexture = RenderTexture.create({ + width: minWidth, + height: minHeight, + resolution, + }); + } + + return renderTexture; + } + + /** + * Gets extra render texture to use inside current filter + * + * @param {number} resolution resolution of the renderTexture + * @returns {PIXI.RenderTexture} + */ + getFilterTexture(resolution) + { + const rt = this.activeState.renderTexture; + + const filterTexture = this.getPotFilterTexture(rt.width, rt.height, resolution || rt.baseTexture.resolution); + + filterTexture.filterFrame = rt.filterFrame; + + return filterTexture; + } + + /** + * Frees a render texture back into the pool. + * + * @param {PIXI.RenderTarget} renderTexture - The renderTarget to free + */ + returnFilterTexture(renderTexture) + { + renderTexture.filterFrame = null; + + const base = renderTexture.baseTexture; + + const minWidth = base.width; + const minHeight = base.height; + + const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + + this.texturePool[key].push(renderTexture); + } + + /** + * Empties the texture pool. + * + */ + emptyPool() + { + for (const i in this.texturePool) + { + const textures = this.texturePool[i]; + + if (textures) + { + for (let j = 0; j < textures.length; j++) + { + textures[j].destroy(true); + } + } + } + + this.texturePool = {}; + } +} diff --git a/packages/core/src/filters/defaultFilter.frag b/packages/core/src/filters/defaultFilter.frag new file mode 100644 index 0000000..a14c1be --- /dev/null +++ b/packages/core/src/filters/defaultFilter.frag @@ -0,0 +1,22 @@ +varying vec2 vTextureCoord; +varying vec2 vFilterCoord; + +uniform sampler2D uSampler; +uniform sampler2D filterSampler; + +void main(void){ + vec4 masky = texture2D(filterSampler, vFilterCoord); + vec4 sample = texture2D(uSampler, vTextureCoord); + vec4 color; + if(mod(vFilterCoord.x, 1.0) > 0.5) + { + color = vec4(1.0, 0.0, 0.0, 1.0); + } + else + { + color = vec4(0.0, 1.0, 0.0, 1.0); + } + // gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0); + gl_FragColor = mix(sample, masky, 0.5); + gl_FragColor *= sample.a; +} diff --git a/packages/core/src/filters/defaultFilter.vert b/packages/core/src/filters/defaultFilter.vert new file mode 100644 index 0000000..e1febca --- /dev/null +++ b/packages/core/src/filters/defaultFilter.vert @@ -0,0 +1,14 @@ +attribute vec2 aVertexPosition; +attribute vec2 aTextureCoord; + +uniform mat3 projectionMatrix; +uniform mat3 filterMatrix; + +varying vec2 vTextureCoord; +varying vec2 vFilterCoord; + +void main(void){ + gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); + vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy; + vTextureCoord = aTextureCoord ; +} diff --git a/packages/core/src/filters/filterTransforms.js b/packages/core/src/filters/filterTransforms.js new file mode 100644 index 0000000..87a3cd6 --- /dev/null +++ b/packages/core/src/filters/filterTransforms.js @@ -0,0 +1,50 @@ +import { Matrix } from '@pixi/math'; + +/** + * Calculates the mapped matrix + * @param filterArea {Rectangle} The filter area + * @param sprite {Sprite} the target sprite + * @param outputMatrix {Matrix} @alvin + * @private + */ +// this returns a matrix that will normalise map filter cords in the filter to screen space +export function calculateScreenSpaceMatrix(outputMatrix, filterArea, textureSize) +{ + // TODO unwrap? + const mappedMatrix = outputMatrix.identity(); + + mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); + + mappedMatrix.scale(textureSize.width, textureSize.height); + + return mappedMatrix; +} + +export function calculateNormalizedScreenSpaceMatrix(outputMatrix, filterArea, textureSize) +{ + const mappedMatrix = outputMatrix.identity(); + + mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); + + const translateScaleX = (textureSize.width / filterArea.width); + const translateScaleY = (textureSize.height / filterArea.height); + + mappedMatrix.scale(translateScaleX, translateScaleY); + + return mappedMatrix; +} + +// this will map the filter coord so that a texture can be used based on the transform of a sprite +export function calculateSpriteMatrix(outputMatrix, filterArea, textureSize, sprite) +{ + const orig = sprite._texture.orig; + const mappedMatrix = outputMatrix.set(textureSize.width, 0, 0, textureSize.height, filterArea.x, filterArea.y); + const worldTransform = sprite.worldTransform.copyTo(Matrix.TEMP_MATRIX); + + worldTransform.invert(); + mappedMatrix.prepend(worldTransform); + mappedMatrix.scale(1.0 / orig.width, 1.0 / orig.height); + mappedMatrix.translate(sprite.anchor.x, sprite.anchor.y); + + return mappedMatrix; +} diff --git a/packages/core/src/filters/spriteMask/SpriteMaskFilter.js b/packages/core/src/filters/spriteMask/SpriteMaskFilter.js new file mode 100644 index 0000000..91697b7 --- /dev/null +++ b/packages/core/src/filters/spriteMask/SpriteMaskFilter.js @@ -0,0 +1,63 @@ +import Filter from '../Filter'; +import { Matrix } from '@pixi/math'; +import vertex from './spriteMaskFilter.vert'; +import fragment from './spriteMaskFilter.frag'; +import { default as TextureMatrix } from '../../textures/TextureMatrix'; + +/** + * The SpriteMaskFilter class + * + * @class + * @extends PIXI.Filter + * @memberof PIXI + */ +export default class SpriteMaskFilter extends Filter +{ + /** + * @param {PIXI.Sprite} sprite - the target sprite + */ + constructor(sprite) + { + const maskMatrix = new Matrix(); + + super(vertex, fragment); + + sprite.renderable = false; + + this.maskSprite = sprite; + this.maskMatrix = maskMatrix; + } + + /** + * Applies the filter + * + * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + */ + apply(filterManager, input, output) + { + const maskSprite = this.maskSprite; + const tex = this.maskSprite.texture; + + if (!tex.valid) + { + return; + } + if (!tex.transform) + { + // margin = 0.0, let it bleed a bit, shader code becomes easier + // assuming that atlas textures were made with 1-pixel padding + tex.transform = new TextureMatrix(tex, 0.0); + } + tex.transform.update(); + + this.uniforms.mask = tex; + this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite) + .prepend(tex.transform.mapCoord); + this.uniforms.alpha = maskSprite.worldAlpha; + this.uniforms.maskClamp = tex.transform.uClampFrame; + + filterManager.applyFilter(this, input, output); + } +} diff --git a/packages/core/src/filters/spriteMask/spriteMaskFilter.frag b/packages/core/src/filters/spriteMask/spriteMaskFilter.frag new file mode 100644 index 0000000..0e4aef8 --- /dev/null +++ b/packages/core/src/filters/spriteMask/spriteMaskFilter.frag @@ -0,0 +1,23 @@ +varying vec2 vMaskCoord; +varying vec2 vTextureCoord; + +uniform sampler2D uSampler; +uniform sampler2D mask; +uniform float alpha; +uniform vec4 maskClamp; + +void main(void) +{ + float clip = step(3.5, + step(maskClamp.x, vMaskCoord.x) + + step(maskClamp.y, vMaskCoord.y) + + step(vMaskCoord.x, maskClamp.z) + + step(vMaskCoord.y, maskClamp.w)); + + vec4 original = texture2D(uSampler, vTextureCoord); + vec4 masky = texture2D(mask, vMaskCoord); + + original *= (masky.r * masky.a * alpha * clip); + + gl_FragColor = original; +} diff --git a/packages/core/src/filters/spriteMask/spriteMaskFilter.vert b/packages/core/src/filters/spriteMask/spriteMaskFilter.vert new file mode 100644 index 0000000..d23a30c --- /dev/null +++ b/packages/core/src/filters/spriteMask/spriteMaskFilter.vert @@ -0,0 +1,16 @@ +attribute vec2 aVertexPosition; +attribute vec2 aTextureCoord; + +uniform mat3 projectionMatrix; +uniform mat3 otherMatrix; + +varying vec2 vMaskCoord; +varying vec2 vTextureCoord; + +void main(void) +{ + gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); + + vTextureCoord = aTextureCoord; + vMaskCoord = ( otherMatrix * vec3( aTextureCoord, 1.0) ).xy; +} diff --git a/packages/core/src/framebuffer/FrameBuffer.js b/packages/core/src/framebuffer/FrameBuffer.js new file mode 100644 index 0000000..84e9fcc --- /dev/null +++ b/packages/core/src/framebuffer/FrameBuffer.js @@ -0,0 +1,106 @@ +import Texture from '../textures/BaseTexture'; +import { FORMATS, TYPES } from '@pixi/constants'; + +/** + * Frame buffer + * @class + * @memberof PIXI + */ +export default class FrameBuffer +{ + constructor(width, height) + { + this.width = width || 100; + this.height = height || 100; + + this.stencil = false; + this.depth = false; + + this.dirtyId = 0; + this.dirtyFormat = 0; + this.dirtySize = 0; + + this.depthTexture = null; + this.colorTextures = []; + + this.glFrameBuffers = {}; + } + + get colorTexture() + { + return this.colorTextures[0]; + } + + addColorTexture(index, texture) + { + // TODO add some validation to the texture - same width / height etc? + this.colorTextures[index || 0] = texture || new Texture(null, { scaleMode: 0, + resolution: 1, + mipmap: false, + width: this.width, + height: this.height });// || new Texture(); + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + addDepthTexture(texture) + { + /* eslint-disable max-len */ + this.depthTexture = texture || new Texture(null, { scaleMode: 0, + resolution: 1, + width: this.width, + height: this.height, + mipmap: false, + format: FORMATS.DEPTH_COMPONENT, + type: TYPES.UNSIGNED_SHORT });// UNSIGNED_SHORT; + /* eslint-disable max-len */ + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + enableDepth() + { + this.depth = true; + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + enableStencil() + { + this.stencil = true; + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + resize(width, height) + { + if (width === this.width && height === this.height) return; + + this.width = width; + this.height = height; + + this.dirtyId++; + this.dirtySize++; + + for (let i = 0; i < this.colorTextures.length; i++) + { + this.colorTextures[i].setSize(width, height); + } + + if (this.depthTexture) + { + this.depthTexture.setSize(width, height); + } + } +} diff --git a/packages/core/src/framebuffer/FramebufferSystem.js b/packages/core/src/framebuffer/FramebufferSystem.js new file mode 100644 index 0000000..2cc2437 --- /dev/null +++ b/packages/core/src/framebuffer/FramebufferSystem.js @@ -0,0 +1,246 @@ +import System from '../System'; +import { Rectangle } from '@pixi/math'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class FramebufferSystem extends System +{ + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + contextChange() + { + this.gl = this.renderer.gl; + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + this.current = null; + this.viewport = new Rectangle(); + + this.drawBufferExtension = this.renderer.context.extensions.drawBuffers; + } + + bind(framebuffer, frame) + { + const gl = this.gl; + + this.current = framebuffer; + + if (framebuffer) + { + // TODO cacheing layer! + + const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID] || this.initFramebuffer(framebuffer); + + gl.bindFramebuffer(gl.FRAMEBUFFER, fbo.framebuffer); + // makesure all textures are unbound.. + + // now check for updates... + if (fbo.dirtyId !== framebuffer.dirtyId) + { + fbo.dirtyId = framebuffer.dirtyId; + + if (fbo.dirtyFormat !== framebuffer.dirtyFormat) + { + fbo.dirtyFormat = framebuffer.dirtyFormat; + this.updateFramebuffer(framebuffer); + } + else if (fbo.dirtySize !== framebuffer.dirtySize) + { + fbo.dirtySize = framebuffer.dirtySize; + this.resizeFramebuffer(framebuffer); + } + } + + for (let i = 0; i < framebuffer.colorTextures.length; i++) + { + if (framebuffer.colorTextures[i].texturePart) + { + this.renderer.texture.unbind(framebuffer.colorTextures[i].texture); + } + else + { + this.renderer.texture.unbind(framebuffer.colorTextures[i]); + } + } + + if (framebuffer.depthTexture) + { + this.renderer.texture.unbind(framebuffer.depthTexture); + } + + if (frame) + { + this.setViewport(frame.x, frame.y, frame.width, frame.height); + } + else + { + this.setViewport(0, 0, framebuffer.width, framebuffer.height); + } + } + else + { + gl.bindFramebuffer(gl.FRAMEBUFFER, null); + + if (frame) + { + this.setViewport(frame.x, frame.y, frame.width, frame.height); + } + else + { + this.setViewport(0, 0, this.renderer.width, this.renderer.height); + } + } + } + + setViewport(x, y, width, height) + { + const v = this.viewport; + + if (v.width !== width || v.height !== height || v.x !== x || v.y !== y) + { + v.x = x; + v.y = y; + v.width = width; + v.height = height; + + this.gl.viewport(x, y, width, height); + } + } + + get size() + { + if (this.current) + { + // TODO store temp + return { x: 0, y: 0, width: this.current.width, height: this.current.height }; + } + + return { x: 0, y: 0, width: this.renderer.width, height: this.renderer.height }; + } + + clear(r, g, b, a) + { + const gl = this.gl; + + // TODO clear color can be set only one right? + gl.clearColor(r, g, b, a); + gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); + } + + // private functions... + + initFramebuffer(framebuffer) + { + const gl = this.gl; + + // TODO - make this a class? + const fbo = { + framebuffer: gl.createFramebuffer(), + stencil: null, + dirtyId: 0, + dirtyFormat: 0, + dirtySize: 0, + }; + + framebuffer.glFrameBuffers[this.CONTEXT_UID] = fbo; + + return fbo; + } + + resizeFramebuffer(framebuffer) + { + const gl = this.gl; + + if (framebuffer.stencil || framebuffer.depth) + { + gl.bindRenderbuffer(gl.RENDERBUFFER, this.stencil); + gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); + } + } + + updateFramebuffer(framebuffer) + { + const gl = this.gl; + + const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID]; + + // bind the color texture + const colorTextures = framebuffer.colorTextures; + + let count = colorTextures.length; + + if (!this.drawBufferExtension) + { + count = Math.min(count, 1); + } + + const activeTextures = []; + + for (let i = 0; i < count; i++) + { + const texture = framebuffer.colorTextures[i]; + + if (texture.texturePart) + { + this.renderer.texture.bind(texture.texture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.COLOR_ATTACHMENT0 + i, + gl.TEXTURE_CUBE_MAP_NEGATIVE_X + texture.side, + texture.texture._glTextures[this.CONTEXT_UID].texture, + 0); + } + else + { + this.renderer.texture.bind(texture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.COLOR_ATTACHMENT0 + i, + gl.TEXTURE_2D, + texture._glTextures[this.CONTEXT_UID].texture, + 0); + } + + activeTextures.push(gl.COLOR_ATTACHMENT0 + i); + } + + if (this.drawBufferExtension && activeTextures.length > 1) + { + this.drawBufferExtension.drawBuffersWEBGL(activeTextures); + } + + if (framebuffer.depthTexture) + { + const depthTextureExt = this.renderer.context.extensions.depthTexture; + + if (depthTextureExt) + { + const depthTexture = framebuffer.depthTexture; + + this.renderer.texture.bind(depthTexture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.DEPTH_ATTACHMENT, + gl.TEXTURE_2D, + depthTexture._glTextures[this.CONTEXT_UID].texture, + 0); + } + } + + if (framebuffer.stencil || framebuffer.depth) + { + fbo.stencil = gl.createRenderbuffer(); + + gl.bindRenderbuffer(gl.RENDERBUFFER, fbo.stencil); + + // TODO.. this is depth AND stencil? + gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, fbo.stencil); + gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); + // fbo.enableStencil(); + } + } +} diff --git a/packages/core/src/geometry/GLBuffer.js b/packages/core/src/geometry/GLBuffer.js new file mode 100644 index 0000000..0477e8a --- /dev/null +++ b/packages/core/src/geometry/GLBuffer.js @@ -0,0 +1,9 @@ +export default class GLBuffer +{ + constructor(buffer) + { + this.buffer = buffer; + this.updateID = -1; + this.byteLength = -1; + } +} diff --git a/packages/core/src/geometry/GeometrySystem.js b/packages/core/src/geometry/GeometrySystem.js new file mode 100644 index 0000000..959f827 --- /dev/null +++ b/packages/core/src/geometry/GeometrySystem.js @@ -0,0 +1,388 @@ +import System from '../System'; +import GLBuffer from './GLBuffer'; + +const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class GeometrySystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this._activeGeometry = null; + this._activeVao = null; + + this.hasVao = true; + this.hasInstance = true; + } + + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + contextChange() + { + const gl = this.gl = this.renderer.gl; + + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + + // webgl2 + if (!gl.createVertexArray) + { + // webgl 1! + let nativeVaoExtension = this.renderer.context.extensions.vertexArrayObject; + + if (this.renderer.legacy) + { + nativeVaoExtension = null; + } + + if (nativeVaoExtension) + { + gl.createVertexArray = () => + nativeVaoExtension.createVertexArrayOES(); + + gl.bindVertexArray = (vao) => + nativeVaoExtension.bindVertexArrayOES(vao); + + gl.deleteVertexArray = (vao) => + nativeVaoExtension.deleteVertexArrayOES(vao); + } + else + { + this.hasVao = false; + gl.createVertexArray = () => + { + // empty + }; + + gl.bindVertexArray = () => + { + // empty + }; + + gl.deleteVertexArray = () => + { + // empty + }; + } + } + + if (!gl.vertexAttribDivisor) + { + const instanceExt = gl.getExtension('ANGLE_instanced_arrays'); + + if (instanceExt) + { + gl.vertexAttribDivisor = (a, b) => + instanceExt.vertexAttribDivisorANGLE(a, b); + + gl.drawElementsInstanced = (a, b, c, d, e) => + instanceExt.drawElementsInstancedANGLE(a, b, c, d, e); + + gl.drawArraysInstanced = (a, b, c, d) => + instanceExt.drawArraysInstancedANGLE(a, b, c, d); + } + else + { + this.hasInstance = false; + } + } + } + + /** + * Binds geometry so that is can be drawn. Creating a Vao if required + * @private + * @param {PIXI.Geometry} geometry instance of geometry to bind + */ + bind(geometry, shader) + { + shader = shader || this.renderer.shader.shader; + + const gl = this.gl; + + // not sure the best way to address this.. + // currently different shaders require different VAOs for the same geometry + // Still mulling over the best way to solve this one.. + // will likely need to modify the shader attribute locations at run time! + let vaos = geometry.glVertexArrayObjects[this.CONTEXT_UID]; + + if (!vaos) + { + geometry.glVertexArrayObjects[this.CONTEXT_UID] = vaos = {}; + } + + const vao = vaos[shader.program.id] || this.initGeometryVao(geometry, shader.program); + + this._activeGeometry = geometry; + + if (this._activeVao !== vao) + { + this._activeVao = vao; + + if (this.hasVao) + { + gl.bindVertexArray(vao); + } + else + { + this.activateVao(geometry, shader.program); + } + } + + // TODO - optimise later! + // don't need to loop through if nothing changed! + // maybe look to add an 'autoupdate' to geometry? + this.updateBuffers(); + } + + reset() + { + this.unbind(); + } + + updateBuffers() + { + const geometry = this._activeGeometry; + const gl = this.gl; + + for (let i = 0; i < geometry.buffers.length; i++) + { + const buffer = geometry.buffers[i]; + + const glBuffer = buffer._glBuffers[this.CONTEXT_UID]; + + if (buffer._updateID !== glBuffer.updateID) + { + glBuffer.updateID = buffer._updateID; + + // TODO can cache this on buffer! maybe added a getter / setter? + const type = buffer.index ? gl.ELEMENT_ARRAY_BUFFER : gl.ARRAY_BUFFER; + const drawType = buffer.static ? gl.STATIC_DRAW : gl.DYNAMIC_DRAW; + + gl.bindBuffer(type, glBuffer.buffer); + + if (glBuffer.byteLength >= buffer.data.byteLength) + { + // offset is always zero for now! + gl.bufferSubData(type, 0, buffer.data); + } + else + { + gl.bufferData(type, buffer.data, drawType); + } + } + } + } + + checkCompatability(geometry, program) + { + // geometry must have at least all the attributes that the shader requires. + const geometryAttributes = geometry.attributes; + const shaderAttributes = program.attributeData; + + for (const j in shaderAttributes) + { + if (!geometryAttributes[j]) + { + throw new Error(`shader and geometry incompatible, geometry missing the "${j}" attribute`); + } + } + } + + /** + * Creates a Vao with the same structure as the geometry and stores it on the geometry. + * @private + * @param {PIXI.Geometry} geometry instance of geometry to to generate Vao for + */ + initGeometryVao(geometry, program) + { + this.checkCompatability(geometry, program); + + const gl = this.gl; + const CONTEXT_UID = this.CONTEXT_UID; + const buffers = geometry.buffers; + const attributes = geometry.attributes; + + const tempStride = {}; + const tempStart = {}; + + for (const j in buffers) + { + tempStride[j] = 0; + tempStart[j] = 0; + } + + for (const j in attributes) + { + if (!attributes[j].size && program.attributeData[j]) + { + attributes[j].size = program.attributeData[j].size; + } + + tempStride[attributes[j].buffer] += attributes[j].size * byteSizeMap[attributes[j].type]; + } + + for (const j in attributes) + { + const attribute = attributes[j]; + const attribSize = attribute.size; + + if (attribute.stride === undefined) + { + if (tempStride[attribute.buffer] === attribSize * byteSizeMap[attribute.type]) + { + attribute.stride = 0; + } + else + { + attribute.stride = tempStride[attribute.buffer]; + } + } + + if (attribute.start === undefined) + { + attribute.start = tempStart[attribute.buffer]; + + tempStart[attribute.buffer] += attribSize * byteSizeMap[attribute.type]; + } + } + + // first update - and create the buffers! + // only create a gl buffer if it actually gets + for (let i = 0; i < buffers.length; i++) + { + const buffer = buffers[i]; + + if (!buffer._glBuffers[CONTEXT_UID]) + { + buffer._glBuffers[CONTEXT_UID] = new GLBuffer(gl.createBuffer()); + } + } + + // TODO - maybe make this a data object? + // lets wait to see if we need to first! + const vao = gl.createVertexArray(); + + gl.bindVertexArray(vao); + + this.activateVao(geometry, program); + + gl.bindVertexArray(null); + + // add it to the cache! + geometry.glVertexArrayObjects[this.CONTEXT_UID][program.id] = vao; + + return vao; + } + + activateVao(geometry, program) + { + const gl = this.gl; + const CONTEXT_UID = this.CONTEXT_UID; + const buffers = geometry.buffers; + const attributes = geometry.attributes; + + if (geometry.indexBuffer) + { + // first update the index buffer if we have one.. + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, geometry.indexBuffer._glBuffers[CONTEXT_UID].buffer); + } + + let lastBuffer = null; + + // add a new one! + for (const j in attributes) + { + const attribute = attributes[j]; + const buffer = buffers[attribute.buffer]; + const glBuffer = buffer._glBuffers[CONTEXT_UID]; + + if (program.attributeData[j]) + { + if (lastBuffer !== glBuffer) + { + gl.bindBuffer(gl.ARRAY_BUFFER, glBuffer.buffer); + + lastBuffer = glBuffer; + } + + const location = program.attributeData[j].location; + + // TODO introduce state again + // we can optimise this for older devices that have no VAOs + gl.enableVertexAttribArray(location); + + gl.vertexAttribPointer(location, + attribute.size, + attribute.type || gl.FLOAT, + attribute.normalized, + attribute.stride, + attribute.start); + + if (attribute.instance) + { + // TODO calculate instance count based of this... + if (this.hasInstance) + { + gl.vertexAttribDivisor(location, 1); + } + else + { + throw new Error('geometry error, GPU Instancing is not supported on this device'); + } + } + } + } + } + + draw(type, size, start, instanceCount) + { + const gl = this.gl; + const geometry = this._activeGeometry; + + // TODO.. this should not change so maybe cache the function? + + if (geometry.indexBuffer) + { + if (geometry.instanced) + { + /* eslint-disable max-len */ + gl.drawElementsInstanced(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2, instanceCount || 1); + /* eslint-enable max-len */ + } + else + { + gl.drawElements(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2); + } + } + else + if (geometry.instanced) + { + // TODO need a better way to calculate size.. + gl.drawArraysInstanced(type, start, size || geometry.getSize(), instanceCount || 1); + } + else + { + gl.drawArrays(type, start, size || geometry.getSize()); + } + + return this; + } + + unbind() + { + this.gl.bindVertexArray(null); + this._activeVao = null; + this._activeGeometry = null; + } +} diff --git a/packages/core/src/geometry/utils/setVertexAttribArrays.js b/packages/core/src/geometry/utils/setVertexAttribArrays.js new file mode 100644 index 0000000..23b6959 --- /dev/null +++ b/packages/core/src/geometry/utils/setVertexAttribArrays.js @@ -0,0 +1,54 @@ +// var GL_MAP = {}; + +/** + * @param gl {WebGLRenderingContext} The current WebGL context + * @param attribs {*} + * @param state {*} + */ +export default function setVertexAttribArrays(gl, attribs, state) +{ + let i; + + if (state) + { + const tempAttribState = state.tempAttribState; + const attribState = state.attribState; + + for (i = 0; i < tempAttribState.length; i++) + { + tempAttribState[i] = false; + } + + // set the new attribs + for (i = 0; i < attribs.length; i++) + { + tempAttribState[attribs[i].attribute.location] = true; + } + + for (i = 0; i < attribState.length; i++) + { + if (attribState[i] !== tempAttribState[i]) + { + attribState[i] = tempAttribState[i]; + + if (state.attribState[i]) + { + gl.enableVertexAttribArray(i); + } + else + { + gl.disableVertexAttribArray(i); + } + } + } + } + else + { + for (i = 0; i < attribs.length; i++) + { + const attrib = attribs[i]; + + gl.enableVertexAttribArray(attrib.attribute.location); + } + } +} diff --git a/packages/core/src/index.js b/packages/core/src/index.js index 58c456a..fd1f547 100644 --- a/packages/core/src/index.js +++ b/packages/core/src/index.js @@ -1,30 +1,30 @@ import * as resources from './textures/resources'; -import * as systems from './renderers/systems'; +import * as systems from './systems'; -export { resources }; export { systems }; +export { resources }; -export { default as Renderer } from './renderers/Renderer'; -export { default as AbstractRenderer } from './renderers/AbstractRenderer'; -export { default as FrameBuffer } from './textures/FrameBuffer'; +export { default as System } from './System'; +export { default as Renderer } from './Renderer'; +export { default as AbstractRenderer } from './AbstractRenderer'; +export { default as FrameBuffer } from './framebuffer/FrameBuffer'; export { default as CubeTexture } from './textures/CubeTexture'; export { default as BaseTexture } from './textures/BaseTexture'; export { default as Texture } from './textures/Texture'; export { default as TextureMatrix } from './textures/TextureMatrix'; -export { default as RenderTexture } from './textures/RenderTexture'; -export { default as BaseRenderTexture } from './textures/BaseRenderTexture'; +export { default as RenderTexture } from './renderTexture/RenderTexture'; +export { default as BaseRenderTexture } from './renderTexture/BaseRenderTexture'; export { default as TextureUvs } from './textures/TextureUvs'; -export { default as State } from './renderers/State'; -export { default as ObjectRenderer } from './renderers/utils/ObjectRenderer'; -export { default as Quad } from './renderers/utils/Quad'; -export { default as QuadUv } from './renderers/utils/QuadUv'; -export { default as checkMaxIfStatmentsInShader } from './renderers/utils/checkMaxIfStatmentsInShader'; +export { default as State } from './state/State'; +export { default as ObjectRenderer } from './batch/ObjectRenderer'; +export { default as Quad } from './utils/Quad'; +export { default as QuadUv } from './utils/QuadUv'; +export { default as checkMaxIfStatmentsInShader } from './shader/utils/checkMaxIfStatmentsInShader'; export { default as Shader } from './shader/Shader'; export { default as Program } from './shader/Program'; export { default as UniformGroup } from './shader/UniformGroup'; -export { default as SpriteMaskFilter } from './renderers/filters/spriteMask/SpriteMaskFilter'; -export { default as Filter } from './renderers/filters/Filter'; +export { default as SpriteMaskFilter } from './filters/spriteMask/SpriteMaskFilter'; +export { default as Filter } from './filters/Filter'; export { default as Attribute } from './geometry/Attribute'; export { default as Buffer } from './geometry/Buffer'; export { default as Geometry } from './geometry/Geometry'; - diff --git a/packages/core/src/mask/MaskSystem.js b/packages/core/src/mask/MaskSystem.js new file mode 100644 index 0000000..99174c2 --- /dev/null +++ b/packages/core/src/mask/MaskSystem.js @@ -0,0 +1,199 @@ +import System from '../System'; +import AlphaMaskFilter from '../filters/spriteMask/SpriteMaskFilter'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class MaskSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + // TODO - we don't need both! + this.scissor = false; + this.scissorData = null; + this.scissorRenderTarget = null; + + this.enableScissor = false; + + this.alphaMaskPool = []; + this.alphaMaskIndex = 0; + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.DisplayObject} target - Display Object to push the mask to + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + push(target, maskData) + { + // TODO the root check means scissor rect will not + // be used on render textures more info here: + // https://github.com/pixijs/pixi.js/pull/3545 + + if (maskData.texture) + { + this.pushSpriteMask(target, maskData); + } + else if (this.enableScissor + && !this.scissor + && this.renderer._activeRenderTarget.root + && !this.renderer.stencil.stencilMaskStack.length + && maskData.isFastRect()) + { + const matrix = maskData.worldTransform; + + let rot = Math.atan2(matrix.b, matrix.a); + + // use the nearest degree! + rot = Math.round(rot * (180 / Math.PI)); + + if (rot % 90) + { + this.pushStencilMask(maskData); + } + else + { + this.pushScissorMask(target, maskData); + } + } + else + { + this.pushStencilMask(maskData); + } + } + + /** + * Removes the last mask from the mask stack and doesn't return it. + * + * @param {PIXI.DisplayObject} target - Display Object to pop the mask from + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + pop(target, maskData) + { + if (maskData.texture) + { + this.popSpriteMask(target, maskData); + } + else if (this.enableScissor && !this.renderer.stencil.stencilMaskStack.length) + { + this.popScissorMask(target, maskData); + } + else + { + this.popStencilMask(target, maskData); + } + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.RenderTarget} target - Display Object to push the sprite mask to + * @param {PIXI.Sprite} maskData - Sprite to be used as the mask + */ + pushSpriteMask(target, maskData) + { + let alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; + + if (!alphaMaskFilter) + { + alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; + } + + alphaMaskFilter[0].resolution = this.renderer.resolution; + alphaMaskFilter[0].maskSprite = maskData; + + // TODO - may cause issues! + target.filterArea = maskData.getBounds(true); + + this.renderer.filterSystem.pushFilter(target, alphaMaskFilter); + + this.alphaMaskIndex++; + } + + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popSpriteMask() + { + this.renderer.filterSystem.popFilter(); + this.alphaMaskIndex--; + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + pushStencilMask(maskData) + { + this.renderer.batch.flush(); + this.renderer.stencil.pushStencil(maskData); + } + + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popStencilMask() + { + // this.renderer.currentRenderer.stop(); + this.renderer.stencil.popStencil(); + } + + /** + * + * @param {PIXI.DisplayObject} target - Display Object to push the mask to + * @param {PIXI.Graphics} maskData - The masking data. + */ + pushScissorMask(target, maskData) + { + maskData.renderable = true; + + const renderTarget = this.renderer._activeRenderTarget; + + const bounds = maskData.getBounds(); + + bounds.fit(renderTarget.size); + maskData.renderable = false; + + this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); + + const resolution = this.renderer.resolution; + + this.renderer.gl.scissor( + bounds.x * resolution, + (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, + bounds.width * resolution, + bounds.height * resolution + ); + + this.scissorRenderTarget = renderTarget; + this.scissorData = maskData; + this.scissor = true; + } + + /** + * + * + */ + popScissorMask() + { + this.scissorRenderTarget = null; + this.scissorData = null; + this.scissor = false; + + // must be scissor! + const gl = this.renderer.gl; + + gl.disable(gl.SCISSOR_TEST); + } +} diff --git a/packages/core/src/mask/StencilSystem.js b/packages/core/src/mask/StencilSystem.js new file mode 100644 index 0000000..62796a5 --- /dev/null +++ b/packages/core/src/mask/StencilSystem.js @@ -0,0 +1,133 @@ +import System from '../System'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class StencilSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + this.stencilMaskStack = []; + } + + /** + * Changes the mask stack that is used by this System. + * + * @param {PIXI.Graphics[]} stencilMaskStack - The mask stack + */ + setMaskStack(stencilMaskStack) + { + const gl = this.renderer.gl; + + if (stencilMaskStack.length !== this.stencilMaskStack.length) + { + if (stencilMaskStack.length === 0) + { + gl.disable(gl.STENCIL_TEST); + } + else + { + gl.enable(gl.STENCIL_TEST); + } + } + + this.stencilMaskStack = stencilMaskStack; + } + + /** + * Applies the Mask and adds it to the current stencil stack. @alvin + * + * @param {PIXI.Graphics} graphics - The mask + */ + pushStencil(graphics) + { + this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); + + // this.renderer._activeRenderTarget.attachStencilBuffer(); + + const gl = this.renderer.gl; + const prevMaskCount = this.stencilMaskStack.length; + + if (prevMaskCount === 0) + { + gl.enable(gl.STENCIL_TEST); + } + + this.stencilMaskStack.push(graphics); + + // Increment the refference stencil value where the new mask overlaps with the old ones. + gl.colorMask(false, false, false, false); + gl.stencilFunc(gl.EQUAL, prevMaskCount, this._getBitwiseMask()); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.INCR); + this.renderer.plugins.graphics.render(graphics); + + this._useCurrent(); + } + + /** + * Removes the last mask from the stencil stack. @alvin + */ + popStencil() + { + this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); + + const gl = this.renderer.gl; + const graphics = this.stencilMaskStack.pop(); + + if (this.stencilMaskStack.length === 0) + { + // the stack is empty! + gl.disable(gl.STENCIL_TEST); + gl.clear(gl.STENCIL_BUFFER_BIT); + gl.clearStencil(0); + } + else + { + // Decrement the refference stencil value where the popped mask overlaps with the other ones + gl.colorMask(false, false, false, false); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.DECR); + this.renderer.plugins.graphics.render(graphics); + + this._useCurrent(); + } + } + + /** + * Setup renderer to use the current stencil data. + */ + _useCurrent() + { + const gl = this.renderer.gl; + + gl.colorMask(true, true, true, true); + gl.stencilFunc(gl.EQUAL, this.stencilMaskStack.length, this._getBitwiseMask()); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP); + } + + /** + * Fill 1s equal to the number of acitve stencil masks. + * + * @return {number} The bitwise mask. + */ + _getBitwiseMask() + { + return (1 << this.stencilMaskStack.length) - 1; + } + + /** + * Destroys the mask stack. + * + */ + destroy() + { + super.destroy(this); + + this.stencilMaskStack = null; + } +} diff --git a/packages/core/src/projection/ProjectionSystem.js b/packages/core/src/projection/ProjectionSystem.js new file mode 100644 index 0000000..6e63b73 --- /dev/null +++ b/packages/core/src/projection/ProjectionSystem.js @@ -0,0 +1,73 @@ +import System from '../System'; +import { Matrix } from '@pixi/math'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class ProjectionSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.projectionMatrix = new Matrix(); + } + + update(destinationFrame, sourceFrame, resolution, root) + { + this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; + this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; + + this.calculateProjection(this.destinationFrame, this.sourceFrame, resolution, root); + + this.renderer.globalUniforms.uniforms.projectionMatrix = this.projectionMatrix; + this.renderer.globalUniforms.update(); + } + + /** + * Updates the projection matrix based on a projection frame (which is a rectangle) + * + * @param {Rectangle} destinationFrame - The destination frame. + * @param {Rectangle} sourceFrame - The source frame. + */ + calculateProjection(destinationFrame, sourceFrame, resolution, root) + { + const pm = this.projectionMatrix; + + // I don't think we will need this line.. + // pm.identity(); + + if (!root) + { + pm.a = (1 / destinationFrame.width * 2) * resolution; + pm.d = (1 / destinationFrame.height * 2) * resolution; + + pm.tx = -1 - (sourceFrame.x * pm.a); + pm.ty = -1 - (sourceFrame.y * pm.d); + } + else + { + pm.a = (1 / destinationFrame.width * 2) * resolution; + pm.d = (-1 / destinationFrame.height * 2) * resolution; + + pm.tx = -1 - (sourceFrame.x * pm.a); + pm.ty = 1 - (sourceFrame.y * pm.d); + } + } + + /** + * Sets the transform of the active render target to the given matrix + * + * @param {PIXI.Matrix} matrix - The transformation matrix + */ + setTransform()// matrix) + { + // this._activeRenderTarget.transform = matrix; + } +} diff --git a/packages/core/src/renderTexture/BaseRenderTexture.js b/packages/core/src/renderTexture/BaseRenderTexture.js new file mode 100644 index 0000000..88eb83b --- /dev/null +++ b/packages/core/src/renderTexture/BaseRenderTexture.js @@ -0,0 +1,137 @@ +import BaseTexture from '../textures/BaseTexture'; +import FrameBuffer from '../framebuffer/FrameBuffer'; + +/** + * A BaseRenderTexture is a special texture that allows any PixiJS display object to be rendered to it. + * + * __Hint__: All DisplayObjects (i.e. Sprites) that render to a BaseRenderTexture should be preloaded + * otherwise black rectangles will be drawn instead. + * + * A BaseRenderTexture takes a snapshot of any Display Object given to its render method. The position + * and rotation of the given Display Objects is ignored. For example: + * + * ```js + * let renderer = PIXI.autoDetectRenderer(1024, 1024, { view: canvas, ratio: 1 }); + * let baseRenderTexture = new PIXI.BaseRenderTexture(renderer, 800, 600); + * let sprite = PIXI.Sprite.fromImage("spinObj_01.png"); + * + * sprite.position.x = 800/2; + * sprite.position.y = 600/2; + * sprite.anchor.x = 0.5; + * sprite.anchor.y = 0.5; + * + * baseRenderTexture.render(sprite); + * ``` + * + * The Sprite in this case will be rendered using its local transform. To render this sprite at 0,0 + * you can clear the transform + * + * ```js + * + * sprite.setTransform() + * + * let baseRenderTexture = new PIXI.BaseRenderTexture(100, 100); + * let renderTexture = new PIXI.RenderTexture(baseRenderTexture); + * + * renderer.render(sprite, renderTexture); // Renders to center of RenderTexture + * ``` + * + * @class + * @extends PIXI.BaseTexture + * @memberof PIXI + */ +export default class BaseRenderTexture extends BaseTexture +{ + /** + * @param {object} [options] + * @param {number} [options.width=100] - The width of the base render texture + * @param {number} [options.height=100] - The height of the base render texture + * @param {PIXI.SCALE_MODES} [options.scaleMode] - See {@link PIXI.SCALE_MODES} for possible values + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the texture being generated + */ + constructor(options) + { + if (typeof options === 'number') + { + /* eslint-disable prefer-rest-params */ + // Backward compatibility of signature + const width = arguments[0]; + const height = arguments[1]; + const scaleMode = arguments[2]; + const resolution = arguments[3]; + + options = { width, height, scaleMode, resolution }; + /* eslint-enable prefer-rest-params */ + } + + super(null, options); + + const { width, height } = options || {}; + + // Set defaults + this.mipmap = false; + this.width = Math.ceil(width) || 100; + this.height = Math.ceil(height) || 100; + this.valid = true; + + /** + * A map of renderer IDs to webgl renderTargets + * + * @private + * @member {object} + */ + // this._glRenderTargets = {}; + + /** + * A reference to the canvas render target (we only need one as this can be shared across renderers) + * + * @private + * @member {object} + */ + this._canvasRenderTarget = null; + + this.clearColor = [0, 0, 0, 0]; + + this.frameBuffer = new FrameBuffer(width * this.resolution, height * this.resolution) + .addColorTexture(0, this); + + // TODO - could this be added the systems? + + /** + * The data structure for the stencil masks + * + * @member {PIXI.Graphics[]} + */ + this.stencilMaskStack = []; + + /** + * The data structure for the filters + * + * @member {PIXI.Graphics[]} + */ + this.filterStack = [{}]; + } + + /** + * Resizes the BaseRenderTexture. + * + * @param {number} width - The width to resize to. + * @param {number} height - The height to resize to. + */ + resize(width, height) + { + width = Math.ceil(width); + height = Math.ceil(height); + this.frameBuffer.resize(width * this.resolution, height * this.resolution); + } + + /** + * Destroys this texture + * + */ + destroy() + { + super.destroy(true); + this.renderer = null; + } +} diff --git a/packages/core/src/renderTexture/RenderTexture.js b/packages/core/src/renderTexture/RenderTexture.js new file mode 100644 index 0000000..f1ecf1f --- /dev/null +++ b/packages/core/src/renderTexture/RenderTexture.js @@ -0,0 +1,153 @@ +import BaseRenderTexture from './BaseRenderTexture'; +import Texture from '../textures/Texture'; + +/** + * A RenderTexture is a special texture that allows any PixiJS display object to be rendered to it. + * + * __Hint__: All DisplayObjects (i.e. Sprites) that render to a RenderTexture should be preloaded + * otherwise black rectangles will be drawn instead. + * + * A RenderTexture takes a snapshot of any Display Object given to its render method. For example: + * + * ```js + * let renderer = PIXI.autoDetectRenderer(1024, 1024, { view: canvas, ratio: 1 }); + * let renderTexture = PIXI.RenderTexture.create(800, 600); + * let sprite = PIXI.Sprite.fromImage("spinObj_01.png"); + * + * sprite.position.x = 800/2; + * sprite.position.y = 600/2; + * sprite.anchor.x = 0.5; + * sprite.anchor.y = 0.5; + * + * renderer.render(sprite, renderTexture); + * ``` + * + * The Sprite in this case will be rendered using its local transform. To render this sprite at 0,0 + * you can clear the transform + * + * ```js + * + * sprite.setTransform() + * + * let renderTexture = new PIXI.RenderTexture.create(100, 100); + * + * renderer.render(sprite, renderTexture); // Renders to center of RenderTexture + * ``` + * + * @class + * @extends PIXI.Texture + * @memberof PIXI + */ +export default class RenderTexture extends Texture +{ + /** + * @param {PIXI.BaseRenderTexture} baseRenderTexture - The renderer used for this RenderTexture + * @param {PIXI.Rectangle} [frame] - The rectangle frame of the texture to show + */ + constructor(baseRenderTexture, frame) + { + // support for legacy.. + let _legacyRenderer = null; + + if (!(baseRenderTexture instanceof BaseRenderTexture)) + { + /* eslint-disable prefer-rest-params, no-console */ + const width = arguments[1]; + const height = arguments[2]; + const scaleMode = arguments[3]; + const resolution = arguments[4]; + + // we have an old render texture.. + console.warn(`Please use RenderTexture.create(${width}, ${height}) instead of the ctor directly.`); + _legacyRenderer = arguments[0]; + /* eslint-enable prefer-rest-params, no-console */ + + frame = null; + baseRenderTexture = new BaseRenderTexture({ + width, + height, + scaleMode, + resolution, + }); + } + + /** + * The base texture object that this texture uses + * + * @member {BaseTexture} + */ + super(baseRenderTexture, frame); + + this.legacyRenderer = _legacyRenderer; + + /** + * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. + * + * @member {boolean} + */ + this.valid = true; + + /** + * FilterSystem temporary storage + * @private + * @member {PIXI.Rectangle} + */ + this.filterFrame = null; + + this._updateUvs(); + } + + /** + * Resizes the RenderTexture. + * + * @param {number} width - The width to resize to. + * @param {number} height - The height to resize to. + * @param {boolean} [resizeBaseTexture=true] - Should the baseTexture.width and height values be resized as well? + */ + resize(width, height, resizeBaseTexture = true) + { + width = Math.ceil(width); + height = Math.ceil(height); + + // TODO - could be not required.. + this.valid = (width > 0 && height > 0); + + this._frame.width = this.orig.width = width; + this._frame.height = this.orig.height = height; + + if (resizeBaseTexture) + { + this.baseTexture.resize(width, height); + } + + this._updateUvs(); + } + + /** + * A short hand way of creating a render texture. + * + * @param {object} [options] - Options + * @param {number} [options.width=100] - The width of the render texture + * @param {number} [options.height=100] - The height of the render texture + * @param {number} [options.scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the texture being generated + * @return {PIXI.RenderTexture} The new render texture + */ + static create(options) + { + // fallback, old-style: create(width, height, scaleMode, resolution) + if (typeof options === 'number') + { + /* eslint-disable prefer-rest-params */ + options = { + width: options, + height: arguments[1], + scaleMode: arguments[2], + resolution: arguments[3], + }; + /* eslint-enable prefer-rest-params */ + } + + return new RenderTexture(new BaseRenderTexture(options)); + } +} diff --git a/packages/core/src/renderTexture/RenderTextureSystem.js b/packages/core/src/renderTexture/RenderTextureSystem.js new file mode 100644 index 0000000..8d0ca61 --- /dev/null +++ b/packages/core/src/renderTexture/RenderTextureSystem.js @@ -0,0 +1,115 @@ +import System from '../System'; +import { Rectangle } from '@pixi/math'; + +const tempRect = new Rectangle(); + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class RenderTextureSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.clearColor = renderer._backgroundColorRgba; + + // TODO moe this property somewhere else! + this.defaultMaskStack = []; + this.defaultFilterStack = [{}]; + + // empty render texture? + this.renderTexture = null; + + this.destinationFrame = new Rectangle(); + } + + bind(renderTexture, sourceFrame, destinationFrame) + { + // TODO - do we want this?? + if (this.renderTexture === renderTexture) return; + this.renderTexture = renderTexture; + + const renderer = this.renderer; + + if (renderTexture) + { + const baseTexture = renderTexture.baseTexture; + + if (!destinationFrame) + { + tempRect.width = baseTexture.realWidth; + tempRect.height = baseTexture.realHeight; + + destinationFrame = tempRect; + } + + if (!sourceFrame) + { + sourceFrame = destinationFrame; + } + + this.renderer.framebuffer.bind(baseTexture.frameBuffer, destinationFrame); + + this.renderer.projection.update(destinationFrame, sourceFrame, baseTexture.resolution, false); + this.renderer.stencil.setMaskStack(baseTexture.stencilMaskStack); + } + else + { + // TODO these validation checks happen deeper down.. + // thing they can be avoided.. + if (!destinationFrame) + { + tempRect.width = renderer.width; + tempRect.height = renderer.height; + + destinationFrame = tempRect; + } + + if (!sourceFrame) + { + sourceFrame = destinationFrame; + } + + renderer.framebuffer.bind(null, destinationFrame); + + // TODO store this.. + this.renderer.projection.update(destinationFrame, sourceFrame, this.renderer.resolution, true); + this.renderer.stencil.setMaskStack(this.defaultMaskStack); + } + + this.destinationFrame.copyFrom(destinationFrame); + } + + /** + * Erases the render texture and fills the drawing area with a colour + * + * @param {number} [clearColor] - The colour + * @return {PIXI.Renderer} Returns itself. + */ + clear(clearColor) + { + if (this.renderTexture) + { + clearColor = clearColor || this.renderTexture.baseTexture.clearColor; + } + else + { + clearColor = clearColor || this.clearColor; + } + + this.renderer.framebuffer.clear(clearColor[0], clearColor[1], clearColor[2], clearColor[3]); + } + + resize()// screenWidth, screenHeight) + { + // resize the root only! + this.bind(null); + } +} diff --git a/packages/core/src/renderers/AbstractRenderer.js b/packages/core/src/renderers/AbstractRenderer.js deleted file mode 100644 index 928670f..0000000 --- a/packages/core/src/renderers/AbstractRenderer.js +++ /dev/null @@ -1,353 +0,0 @@ -import { hex2string, hex2rgb } from '@pixi/utils'; -import { Matrix, Rectangle } from '@pixi/math'; -import { RENDERER_TYPE } from '@pixi/constants'; -import { settings } from '@pixi/settings'; -import { Container } from '@pixi/display'; -import RenderTexture from '../textures/RenderTexture'; -import EventEmitter from 'eventemitter3'; - -const tempMatrix = new Matrix(); - -/** - * 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 EventEmitter - * @memberof PIXI - */ -export default class AbstractRenderer extends EventEmitter -{ - // eslint-disable-next-line valid-jsdoc - /** - * @param {string} system - The name of the system this renderer is for. - * @param {object} [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.transparent=false] - If the render view is transparent, default false - * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false - * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) - * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. The - * resolution of the renderer retina would be 2. - * @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 {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when rendering, - * stopping pixel interpolation. - */ - constructor(system, options, arg2, arg3) - { - super(); - - // Support for constructor(system, screenWidth, screenHeight, options) - if (typeof options === 'number') - { - options = Object.assign({ - width: options, - height: arg2 || settings.RENDER_OPTIONS.height, - }, arg3); - } - - // 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 = RENDERER_TYPE.UNKNOWN; - - /** - * Measurements of the screen. (0, 0, screenWidth, screenHeight) - * - * Its safe to use as filterArea or hitArea for 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 1 - */ - this.resolution = options.resolution || settings.RESOLUTION; - - /** - * Whether the render view is transparent - * - * @member {boolean} - */ - this.transparent = options.transparent; - - /** - * Whether css dimensions of canvas view should be resized to screen dimensions automatically - * - * @member {boolean} - */ - this.autoResize = options.autoResize || false; - - /** - * Tracks the blend modes useful for this renderer. - * - * @member {object} - */ - this.blendModes = null; - - /** - * 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; - - /** - * If true PixiJS will Math.floor() x/y values when rendering, stopping pixel interpolation. - * Handy for crisp pixel art and speed on legacy devices. - * - * @member {boolean} - */ - this.roundPixels = options.roundPixels; - - /** - * The background color as a number. - * - * @member {number} - * @private - */ - this._backgroundColor = 0x000000; - - /** - * The background color as an [R, G, B] array. - * - * @member {number[]} - * @private - */ - this._backgroundColorRgba = [0, 0, 0, 0]; - - /** - * The background color as a string. - * - * @member {string} - * @private - */ - this._backgroundColorString = '#000000'; - - this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter - - /** - * This temporary display object used as the parent of the currently being rendered item - * - * @member {PIXI.DisplayObject} - * @private - */ - this._tempDisplayObjectParent = new Container(); - - /** - * The last root object that the renderer tried to render. - * - * @member {PIXI.DisplayObject} - * @private - */ - this._lastObjectRendered = this._tempDisplayObjectParent; - - /** - * Collection of plugins. - * @readonly - * @member {object} - */ - this.plugins = {}; - } - - /** - * Initialize the plugins. - * - * @protected - * @param {object} staticMap - The dictionary of staticly saved plugins. - */ - initPlugins(staticMap) - { - 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() - { - return this.view.width; - } - - /** - * Same as view.height, actual number of pixels in the canvas by vertical - * - * @member {number} - * @readonly - * @default 600 - */ - get height() - { - return this.view.height; - } - - /** - * Resizes the screen and canvas to the specified width and height - * Canvas dimensions are multiplied by resolution - * - * @param {number} screenWidth - the new width of the screen - * @param {number} screenHeight - the new height of the screen - */ - resize(screenWidth, screenHeight) - { - this.screen.width = screenWidth; - this.screen.height = screenHeight; - - this.view.width = screenWidth * this.resolution; - this.view.height = screenHeight * this.resolution; - - if (this.autoResize) - { - this.view.style.width = `${screenWidth}px`; - this.view.style.height = `${screenHeight}px`; - } - } - - /** - * 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 {PIXI.DisplayObject} displayObject - The displayObject the object will be generated from - * @param {number} scaleMode - Should be one of the scaleMode consts - * @param {number} resolution - The resolution / device pixel ratio of the texture being generated - * @param {PIXI.Rectangle} [region] - The region of the displayObject, that shall be rendered, - * if no region is specified, defaults to the local bounds of the displayObject. - * @return {PIXI.Texture} a texture of the graphics object - */ - generateTexture(displayObject, scaleMode, resolution, region) - { - region = region || displayObject.getLocalBounds(); - - // 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(region.width | 0, region.height | 0, scaleMode, resolution); - - tempMatrix.tx = -region.x; - tempMatrix.ty = -region.y; - - this.render(displayObject, renderTexture, false, tempMatrix, true); - - return renderTexture; - } - - /** - * Removes everything from the renderer and optionally removes the Canvas DOM element. - * - * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. - */ - destroy(removeView) - { - 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); - } - - this.plugins = null; - - this.type = RENDERER_TYPE.UNKNOWN; - - this.view = null; - - this.screen = null; - - this.resolution = 0; - - this.transparent = false; - - this.autoResize = false; - - this.blendModes = null; - - this.options = null; - - this.preserveDrawingBuffer = false; - this.clearBeforeRender = false; - - this.roundPixels = false; - - this._backgroundColor = 0; - this._backgroundColorRgba = null; - this._backgroundColorString = null; - - this._tempDisplayObjectParent = null; - this._lastObjectRendered = null; - } - - /** - * The background color to fill if not transparent - * - * @member {number} - */ - get backgroundColor() - { - return this._backgroundColor; - } - - set backgroundColor(value) // eslint-disable-line require-jsdoc - { - this._backgroundColor = value; - this._backgroundColorString = hex2string(value); - hex2rgb(value, this._backgroundColorRgba); - } -} diff --git a/packages/core/src/renderers/Renderer.js b/packages/core/src/renderers/Renderer.js deleted file mode 100644 index a2020b1..0000000 --- a/packages/core/src/renderers/Renderer.js +++ /dev/null @@ -1,329 +0,0 @@ -import AbstractRenderer from './AbstractRenderer'; -import { sayHello } from '@pixi/utils'; -import MaskSystem from './systems/MaskSystem'; -import StencilSystem from './systems/StencilSystem'; -import FilterSystem from './systems/filter/FilterSystem'; -import FramebufferSystem from './systems/FramebufferSystem'; -import RenderTextureSystem from './systems/RenderTextureSystem'; -import TextureSystem from './systems/textures/TextureSystem'; -import ProjectionSystem from './systems/ProjectionSystem'; -import StateSystem from './systems/StateSystem'; -import GeometrySystem from './systems/geometry/GeometrySystem'; -import ShaderSystem from './systems/shader/ShaderSystem'; -import ContextSystem from './systems/ContextSystem'; -import BatchSystem from './systems/BatchSystem'; -import TextureGCSystem from './systems/textures/TextureGCSystem'; -import { RENDERER_TYPE } from '@pixi/constants'; -import UniformGroup from '../shader/UniformGroup'; -import { Matrix } from '@pixi/math'; -import Runner from 'mini-runner'; - -/** - * 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 Render works by automatically managing webGLBatchs. - * 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 :) - * - * @class - * @memberof PIXI - * @extends PIXI.AbstractRenderer - */ -export default class Renderer extends AbstractRenderer -{ - // eslint-disable-next-line valid-jsdoc - /** - * - * @param {object} [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.transparent=false] - If the render view is transparent, default false - * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false - * @param {boolean} [options.antialias=false] - sets antialias. If not available natively then FXAA - * antialiasing is used - * @param {boolean} [options.forceFXAA=false] - forces FXAA antialiasing to be used over native. - * FXAA is faster, but may not always look as great - * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. - * The resolution of the renderer retina would be 2. - * @param {boolean} [options.clearBeforeRender=true] - This sets if the renderer will clear - * the canvas or not before the new render pass. If you wish to set this to false, you *must* set - * preserveDrawingBuffer to `true`. - * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, - * enable this if you need to call toDataUrl on the webgl context. - * @param {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when - * rendering, stopping pixel interpolation. - * @param {number} [options.backgroundColor=0x000000] - The background color of the rendered area - * (shown if not transparent). - * @param {boolean} [options.legacy=false] - If true PixiJS will aim to ensure compatibility - * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. - * @param {string} [options.powerPreference] - Parameter passed to webgl context, set to "high-performance" - * for devices with dual graphics card - */ - constructor(options, arg2, arg3) - { - super('WebGL', options, arg2, arg3); - - /** - * The type of this renderer as a standardised const - * - * @member {number} - * @see PIXI.RENDERER_TYPE - */ - this.type = RENDERER_TYPE.WEBGL; - - // this will be set by the contextSystem (this.context) - this.gl = null; - this.CONTEXT_UID = 0; - this.legacy = !!options.legacy; - - // TODO legacy! - - // runners! - this.runners = { - destroy: new Runner('destroy'), - contextChange: new Runner('contextChange', 1), - reset: new Runner('reset'), - update: new Runner('update'), - postrender: new Runner('postrender'), - prerender: new Runner('prerender'), - resize: new Runner('resize', 2), - }; - - 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(GeometrySystem, 'geometry') - .addSystem(FramebufferSystem, 'framebuffer') - .addSystem(StencilSystem, 'stencil') - .addSystem(ProjectionSystem, 'projection') - .addSystem(TextureGCSystem, 'textureGC') - .addSystem(FilterSystem, 'filter') - .addSystem(RenderTextureSystem, 'renderTexture') - .addSystem(BatchSystem, 'batch'); - - this.initPlugins(Renderer.__plugins); - - /** - * The options passed in to create a new webgl context. - * - * @member {object} - * @private - */ - if (options.context) - { - this.context.initFromContext(options.context); - } - else - { - this.context.initFromOptions({ - alpha: this.transparent, - antialias: options.antialias, - premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', - stencil: true, - 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); - } - - addSystem(_class, name) - { - if (!name) - { - name = _class.name; - } - - // TODO - read name from class.name.. - - /* - if(name.includes('System')) - { - name = name.replace('System', ''); - name = name.charAt(0).toLowerCase() + name.slice(1); - } - */ - - const system = new _class(this); - - if (this[name]) - { - throw new Error(`Whoops! ${name} is already a manger`); - } - - this[name] = system; - - for (const i in this.runners) - { - this.runners[i].add(system); - } - - return this; - - /** - * 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. - */ - } - - /** - * Renders the object to its webGL view - * - * @param {PIXI.DisplayObject} displayObject - the object to be rendered - * @param {PIXI.RenderTexture} renderTexture - The render texture to render to. - * @param {boolean} [clear] - Should the canvas be cleared before the new render - * @param {PIXI.Transform} [transform] - A transform to apply to the render texture before rendering. - * @param {boolean} [skipUpdateTransform] - Should we skip the update transform pass? - */ - render(displayObject, renderTexture, clear, transform, skipUpdateTransform) - { - // can be handy to know! - this.renderingToScreen = !renderTexture; - - this.runners.prerender.run(); - this.emit('prerender'); - - // 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.parent; - - displayObject.parent = this._tempDisplayObjectParent; - displayObject.updateTransform(); - displayObject.parent = 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.run(); - - this.emit('postrender'); - } - - /** - * Resizes the webGL view to the specified width and height. - * - * @param {number} screenWidth - the new width of the screen - * @param {number} screenHeight - the new height of the screen - */ - resize(screenWidth, screenHeight) - { - AbstractRenderer.prototype.resize.call(this, screenWidth, screenHeight); - - this.runners.resize.run(screenWidth, screenHeight); - } - - /** - * Resets the WebGL state so you can render things however you fancy! - * - * @return {PIXI.Renderer} Returns itself. - */ - reset() - { - this.runners.reset.run(); - - return this; - } - - clear() - { - this.framebuffer.bind(); - this.framebuffer.clear(); - } - - /** - * Removes everything from the renderer (event listeners, spritebatch, etc...) - * - * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. - * See: https://github.com/pixijs/pixi.js/issues/2233 - */ - destroy(removeView) - { - this.runners.destroy.run(); - - // call base destroy - super.destroy(removeView); - - // TODO nullify all the managers.. - this.gl = null; - } - - /** - * 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 - * builds and excluding plugins. - * @name PIXI.Renderer#plugins - * @type {object} - * @readonly - * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. - * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. - * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. - * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. - */ - - /** - * Adds a plugin to the renderer. - * - * @method - * @param {string} pluginName - The name of the plugin. - * @param {Function} ctor - The constructor function or class for the plugin. - */ - static registerPlugin(pluginName, ctor) - { - Renderer.__plugins = Renderer.__plugins || {}; - Renderer.__plugins[pluginName] = ctor; - } -} diff --git a/packages/core/src/renderers/State.js b/packages/core/src/renderers/State.js deleted file mode 100644 index fa3f221..0000000 --- a/packages/core/src/renderers/State.js +++ /dev/null @@ -1,163 +0,0 @@ -/* eslint-disable max-len */ - -const BLEND = 0; -const OFFSET = 1; -const CULLING = 2; -const DEPTH_TEST = 3; -const WINDING = 4; - -/** - * This is a webGL state. It is passed The WebGL StateManager. - * Each mesh renderered may require webGL to be in a different state. - * For example you may want different blend mode or to enable polygon offsets - * - * @class - * @memberof PIXI - */ -export default class State -{ - /** - * - */ - constructor() - { - this.data = 0; - - this.blendMode = 0; - this.polygonOffset = 0; - - this.blend = true; - // this.depthTest = true; - } - - /** - * Activates blending of the computed fragment color values - * - * @member {boolean} - */ - get blend() - { - return !!(this.data & (1 << BLEND)); - } - - set blend(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << BLEND)) !== value) - { - this.data ^= (1 << BLEND); - } - } - - /** - * Activates adding an offset to depth values of polygon's fragments - * - * @member {boolean} - * @default false - */ - get offsets() - { - return !!(this.data & (1 << OFFSET)); - } - - set offsets(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << OFFSET)) !== value) - { - this.data ^= (1 << OFFSET); - } - } - - /** - * Activates culling of polygons. - * - * @member {boolean} - * @default false - */ - get culling() - { - return !!(this.data & (1 << CULLING)); - } - - set culling(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << CULLING)) !== value) - { - this.data ^= (1 << CULLING); - } - } - - /** - * Activates depth comparisons and updates to the depth buffer. - * - * @member {boolean} - * @default false - */ - get depthTest() - { - return !!(this.data & (1 << DEPTH_TEST)); - } - - set depthTest(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << DEPTH_TEST)) !== value) - { - this.data ^= (1 << DEPTH_TEST); - } - } - - /** - * Specifies whether or not front or back-facing polygons can be culled. - * @member {boolean} - * @default false - */ - get clockwiseFrontFace() - { - return !!(this.data & (1 << WINDING)); - } - - set clockwiseFrontFace(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << WINDING)) !== value) - { - this.data ^= (1 << WINDING); - } - } - - /** - * The blend mode to be applied when this state is set. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. - * Setting this mode to anything other than NO_BLEND will automatically switch blending on. - * - * @member {boolean} - * @default PIXI.BLEND_MODES.NORMAL - * @see PIXI.BLEND_MODES - */ - get blendMode() - { - return this._blendMode; - } - - set blendMode(value) // eslint-disable-line require-jsdoc - { - // 17 is NO BLEND - this.blend = (value !== 17); - this._blendMode = value; - } - - /** - * The polygon offset. Setting this property to anything other than 0 will automatically enable poygon offset fill. - * - * @member {number} - * @default 0 - */ - get polygonOffset() - { - return this._polygonOffset; - } - - set polygonOffset(value) // eslint-disable-line require-jsdoc - { - this.offsets = !!value; - this._polygonOffset = value; - } -} - diff --git a/packages/core/src/renderers/filters/Filter.js b/packages/core/src/renderers/filters/Filter.js deleted file mode 100644 index 2073b8d..0000000 --- a/packages/core/src/renderers/filters/Filter.js +++ /dev/null @@ -1,140 +0,0 @@ -import Shader from '../../shader/Shader'; -import Program from '../../shader/Program'; -import State from '../State'; -import { settings } from '@pixi/settings'; -// import extractUniformsFromSrc from './extractUniformsFromSrc'; -import defaultVertex from './defaultFilter.vert'; -import defaultFragment from './defaultFilter.frag'; - -// let math = require('../../math'); -/** - * @class - * @memberof PIXI - * @extends PIXI.Shader - */ -export default class Filter extends Shader -{ - /** - * @param {string} [vertexSrc] - The source of the vertex shader. - * @param {string} [fragmentSrc] - The source of the fragment shader. - * @param {object} [uniforms] - Custom uniforms to use to augment the built-in ones. - */ - constructor(vertexSrc, fragmentSrc, uniforms) - { - const program = Program.from(vertexSrc, fragmentSrc); - - super(program, uniforms); - - /** - * The padding of the filter. Some filters require extra space to breath such as a blur. - * Increasing this will add extra width and height to the bounds of the object that the - * filter is applied to. - * - * @member {number} - */ - this.padding = 0; - - /** - * The resolution of the filter. Setting this to be lower will lower the quality but - * increase the performance of the filter. - * - * @member {number} - */ - this.resolution = settings.RESOLUTION; - - /** - * If enabled is true the filter is applied, if false it will not. - * - * @member {boolean} - */ - this.enabled = true; - - /** - * If enabled, PixiJS will fit the filter area into boundaries for better performance. - * Switch it off if it does not work for specific shader. - * - * @member {boolean} - */ - this.autoFit = true; - - /** - * Legacy filters use position and uvs from attributes - * @member {boolean} - * @readonly - */ - this.legacy = !!this.program.attributeData.aTextureCoord; - - /** - * the webGL state the filter requires to render - * @member {PIXI.State} - */ - this.state = new State(); - } - - /** - * Applies the filter - * - * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from - * @param {PIXI.RenderTarget} input - The input render target. - * @param {PIXI.RenderTarget} output - The target to output to. - * @param {boolean} clear - Should the output be cleared before rendering to it - * @param {object} [currentState] - It's current state of filter. - * There are some useful properties in the currentState : - * target, filters, sourceFrame, destinationFrame, renderTarget, resolution - */ - apply(filterManager, input, output, clear, currentState, derp) // eslint-disable-line no-unused-vars - { - // do as you please! - - filterManager.applyFilter(this, input, output, clear, currentState, derp); - - // or just do a regular render.. - } - - /** - * Sets the blendmode of the filter - * - * @member {number} - * @default PIXI.BLEND_MODES.NORMAL - */ - get blendMode() - { - return this.state.blendMode; - } - - set blendMode(value) // eslint-disable-line require-jsdoc - { - this.state.blendMode = value; - } - - /** - * The default vertex shader source - * - * @static - * @constant - */ - static get defaultVertexSrc() - { - return defaultVertex; - } - - /** - * The default fragment shader source - * - * @static - * @constant - */ - static get defaultFragmentSrc() - { - return defaultFragment; - } -} - -/** - * Used for caching shader IDs - * - * @static - * @private - */ -Filter.SOURCE_KEY_MAP = {}; - diff --git a/packages/core/src/renderers/filters/defaultFilter.frag b/packages/core/src/renderers/filters/defaultFilter.frag deleted file mode 100644 index a14c1be..0000000 --- a/packages/core/src/renderers/filters/defaultFilter.frag +++ /dev/null @@ -1,22 +0,0 @@ -varying vec2 vTextureCoord; -varying vec2 vFilterCoord; - -uniform sampler2D uSampler; -uniform sampler2D filterSampler; - -void main(void){ - vec4 masky = texture2D(filterSampler, vFilterCoord); - vec4 sample = texture2D(uSampler, vTextureCoord); - vec4 color; - if(mod(vFilterCoord.x, 1.0) > 0.5) - { - color = vec4(1.0, 0.0, 0.0, 1.0); - } - else - { - color = vec4(0.0, 1.0, 0.0, 1.0); - } - // gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0); - gl_FragColor = mix(sample, masky, 0.5); - gl_FragColor *= sample.a; -} diff --git a/packages/core/src/renderers/filters/defaultFilter.vert b/packages/core/src/renderers/filters/defaultFilter.vert deleted file mode 100644 index e1febca..0000000 --- a/packages/core/src/renderers/filters/defaultFilter.vert +++ /dev/null @@ -1,14 +0,0 @@ -attribute vec2 aVertexPosition; -attribute vec2 aTextureCoord; - -uniform mat3 projectionMatrix; -uniform mat3 filterMatrix; - -varying vec2 vTextureCoord; -varying vec2 vFilterCoord; - -void main(void){ - gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); - vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy; - vTextureCoord = aTextureCoord ; -} diff --git a/packages/core/src/renderers/filters/filterTransforms.js b/packages/core/src/renderers/filters/filterTransforms.js deleted file mode 100644 index 87a3cd6..0000000 --- a/packages/core/src/renderers/filters/filterTransforms.js +++ /dev/null @@ -1,50 +0,0 @@ -import { Matrix } from '@pixi/math'; - -/** - * Calculates the mapped matrix - * @param filterArea {Rectangle} The filter area - * @param sprite {Sprite} the target sprite - * @param outputMatrix {Matrix} @alvin - * @private - */ -// this returns a matrix that will normalise map filter cords in the filter to screen space -export function calculateScreenSpaceMatrix(outputMatrix, filterArea, textureSize) -{ - // TODO unwrap? - const mappedMatrix = outputMatrix.identity(); - - mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); - - mappedMatrix.scale(textureSize.width, textureSize.height); - - return mappedMatrix; -} - -export function calculateNormalizedScreenSpaceMatrix(outputMatrix, filterArea, textureSize) -{ - const mappedMatrix = outputMatrix.identity(); - - mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); - - const translateScaleX = (textureSize.width / filterArea.width); - const translateScaleY = (textureSize.height / filterArea.height); - - mappedMatrix.scale(translateScaleX, translateScaleY); - - return mappedMatrix; -} - -// this will map the filter coord so that a texture can be used based on the transform of a sprite -export function calculateSpriteMatrix(outputMatrix, filterArea, textureSize, sprite) -{ - const orig = sprite._texture.orig; - const mappedMatrix = outputMatrix.set(textureSize.width, 0, 0, textureSize.height, filterArea.x, filterArea.y); - const worldTransform = sprite.worldTransform.copyTo(Matrix.TEMP_MATRIX); - - worldTransform.invert(); - mappedMatrix.prepend(worldTransform); - mappedMatrix.scale(1.0 / orig.width, 1.0 / orig.height); - mappedMatrix.translate(sprite.anchor.x, sprite.anchor.y); - - return mappedMatrix; -} diff --git a/packages/core/src/renderers/filters/spriteMask/SpriteMaskFilter.js b/packages/core/src/renderers/filters/spriteMask/SpriteMaskFilter.js deleted file mode 100644 index d5a91a4..0000000 --- a/packages/core/src/renderers/filters/spriteMask/SpriteMaskFilter.js +++ /dev/null @@ -1,63 +0,0 @@ -import Filter from '../Filter'; -import { Matrix } from '@pixi/math'; -import vertex from './spriteMaskFilter.vert'; -import fragment from './spriteMaskFilter.frag'; -import { default as TextureMatrix } from '../../../textures/TextureMatrix'; - -/** - * The SpriteMaskFilter class - * - * @class - * @extends PIXI.Filter - * @memberof PIXI - */ -export default class SpriteMaskFilter extends Filter -{ - /** - * @param {PIXI.Sprite} sprite - the target sprite - */ - constructor(sprite) - { - const maskMatrix = new Matrix(); - - super(vertex, fragment); - - sprite.renderable = false; - - this.maskSprite = sprite; - this.maskMatrix = maskMatrix; - } - - /** - * Applies the filter - * - * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from - * @param {PIXI.RenderTarget} input - The input render target. - * @param {PIXI.RenderTarget} output - The target to output to. - */ - apply(filterManager, input, output) - { - const maskSprite = this.maskSprite; - const tex = this.maskSprite.texture; - - if (!tex.valid) - { - return; - } - if (!tex.transform) - { - // margin = 0.0, let it bleed a bit, shader code becomes easier - // assuming that atlas textures were made with 1-pixel padding - tex.transform = new TextureMatrix(tex, 0.0); - } - tex.transform.update(); - - this.uniforms.mask = tex; - this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite) - .prepend(tex.transform.mapCoord); - this.uniforms.alpha = maskSprite.worldAlpha; - this.uniforms.maskClamp = tex.transform.uClampFrame; - - filterManager.applyFilter(this, input, output); - } -} diff --git a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.frag b/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.frag deleted file mode 100644 index 0e4aef8..0000000 --- a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.frag +++ /dev/null @@ -1,23 +0,0 @@ -varying vec2 vMaskCoord; -varying vec2 vTextureCoord; - -uniform sampler2D uSampler; -uniform sampler2D mask; -uniform float alpha; -uniform vec4 maskClamp; - -void main(void) -{ - float clip = step(3.5, - step(maskClamp.x, vMaskCoord.x) + - step(maskClamp.y, vMaskCoord.y) + - step(vMaskCoord.x, maskClamp.z) + - step(vMaskCoord.y, maskClamp.w)); - - vec4 original = texture2D(uSampler, vTextureCoord); - vec4 masky = texture2D(mask, vMaskCoord); - - original *= (masky.r * masky.a * alpha * clip); - - gl_FragColor = original; -} diff --git a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.vert b/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.vert deleted file mode 100644 index d23a30c..0000000 --- a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.vert +++ /dev/null @@ -1,16 +0,0 @@ -attribute vec2 aVertexPosition; -attribute vec2 aTextureCoord; - -uniform mat3 projectionMatrix; -uniform mat3 otherMatrix; - -varying vec2 vMaskCoord; -varying vec2 vTextureCoord; - -void main(void) -{ - gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); - - vTextureCoord = aTextureCoord; - vMaskCoord = ( otherMatrix * vec3( aTextureCoord, 1.0) ).xy; -} diff --git a/packages/core/src/renderers/systems/BatchSystem.js b/packages/core/src/renderers/systems/BatchSystem.js deleted file mode 100644 index 70e9518..0000000 --- a/packages/core/src/renderers/systems/BatchSystem.js +++ /dev/null @@ -1,68 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import ObjectRenderer from '../utils/ObjectRenderer'; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ - -export default class BatchSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this manager works for. - */ - constructor(renderer) - { - super(renderer); - - /** - * An empty renderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.emptyRenderer = new ObjectRenderer(renderer); - - /** - * The currently active ObjectRenderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.currentRenderer = this.emptyRenderer; - } - - /** - * Changes the current renderer to the one given in parameter - * - * @param {PIXI.ObjectRenderer} objectRenderer - The object renderer to use. - */ - setObjectRenderer(objectRenderer) - { - if (this.currentRenderer === objectRenderer) - { - return; - } - - this.currentRenderer.stop(); - this.currentRenderer = objectRenderer; - - this.renderer.state.setState(objectRenderer.state); - - this.currentRenderer.start(); - } - - /** - * This should be called if you wish to do some custom rendering - * It will basically render anything that may be batched up such as sprites - * - */ - flush() - { - this.setObjectRenderer(this.emptyRenderer); - } - - reset() - { - this.setObjectRenderer(this.emptyRenderer); - } -} diff --git a/packages/core/src/renderers/systems/ContextSystem.js b/packages/core/src/renderers/systems/ContextSystem.js deleted file mode 100644 index 43bf827..0000000 --- a/packages/core/src/renderers/systems/ContextSystem.js +++ /dev/null @@ -1,186 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { settings } from '@pixi/settings'; - -let CONTEXT_UID = 0; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class ContextSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this.webGLVersion = 1; - - this.handleContextLost = this.handleContextLost.bind(this); - this.handleContextRestored = this.handleContextRestored.bind(this); - - this.extensions = {}; - - renderer.view.addEventListener('webglcontextlost', this.handleContextLost, false); - renderer.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); - } - - get isLost() - { - return (!this.gl || this.gl.isContextLost()); - } - - contextChange(gl) - { - this.gl = gl; - - // restore a context if it was previously lost - if (gl.isContextLost() && gl.getExtension('WEBGL_lose_context')) - { - gl.getExtension('WEBGL_lose_context').restoreContext(); - } - } - - initFromContext(gl) - { - this.gl = gl; - this.validateContext(gl); - this.renderer.gl = gl; - this.renderer.CONTEXT_UID = CONTEXT_UID++; - this.renderer.runners.contextChange.run(gl); - } - - initFromOptions(options) - { - const gl = this.createContext(this.renderer.view, options); - - this.initFromContext(gl); - } - - /** - * Helper class to create a webGL Context - * - * @class - * @memberof PIXI.glCore - * @param canvas {HTMLCanvasElement} the canvas element that we will get the context from - * @param options {Object} An options object that gets passed in to the canvas element containing the context attributes, - * see https://developer.mozilla.org/en/docs/Web/API/HTMLCanvasElement/getContext for the options available - * @return {WebGLRenderingContext} the WebGL context - */ - createContext(canvas, options) - { - let gl; - - if (settings.PREFER_WEBGL_2) - { - gl = canvas.getContext('webgl2', options); - } - - if (gl) - { - this.webGLVersion = 2; - } - else - { - this.webGLVersion = 1; - - gl = canvas.getContext('webgl', options) - || canvas.getContext('experimental-webgl', options); - - if (!gl) - { - // fail, not able to get a context - throw new Error('This browser does not support webGL. Try using the canvas renderer'); - } - } - - this.gl = gl; - - this.getExtensions(); - - return gl; - } - - getExtensions() - { - // time to set up default etensions that pixi uses.. - const gl = this.gl; - const extensions = this.extensions; - - if (this.webGLVersion === 1) - { - extensions.drawBuffers = gl.getExtension('WEBGL_draw_buffers'); - extensions.depthTexture = gl.getExtension('WEBKIT_WEBGL_depth_texture'); - extensions.floatTexture = gl.getExtension('OES_texture_float'); - extensions.loseContext = gl.getExtension('WEBGL_lose_context'); - - extensions.vertexArrayObject = gl.getExtension('OES_vertex_array_object') - || gl.getExtension('MOZ_OES_vertex_array_object') - || gl.getExtension('WEBKIT_OES_vertex_array_object'); - } - - // we don't use any specific WebGL 2 ones yet! - } - - /** - * Handles a lost webgl context - * - * @private - * @param {WebGLContextEvent} event - The context lost event. - */ - handleContextLost(event) - { - event.preventDefault(); - } - - /** - * Handles a restored webgl context - * - * @private - */ - handleContextRestored() - { - this.renderer.runners.contextChange.run(this.gl); - } - - destroy() - { - const view = this.renderer.view; - - // remove listeners - view.removeEventListener('webglcontextlost', this.handleContextLost); - view.removeEventListener('webglcontextrestored', this.handleContextRestored); - - this.gl.useProgram(null); - - if (this.extensions.loseContext) - { - this.extensions.loseContext.loseContext(); - } - } - - postrender() - { - this.gl.flush(); - } - - validateContext(gl) - { - const attributes = gl.getContextAttributes(); - - // this is going to be fairly simple for now.. but at least we have room to grow! - if (!attributes.stencil) - { - /* eslint-disable max-len */ - - /* eslint-disable no-console */ - console.warn('Provided WebGL context does not have a stencil buffer, masks may not render correctly'); - /* eslint-enable no-console */ - - /* eslint-enable max-len */ - } - } -} diff --git a/packages/core/src/renderers/systems/FramebufferSystem.js b/packages/core/src/renderers/systems/FramebufferSystem.js deleted file mode 100644 index df7902b..0000000 --- a/packages/core/src/renderers/systems/FramebufferSystem.js +++ /dev/null @@ -1,246 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { Rectangle } from '@pixi/math'; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class FramebufferSystem extends WebGLSystem -{ - /** - * Sets up the renderer context and necessary buffers. - * - * @private - */ - contextChange() - { - this.gl = this.renderer.gl; - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - this.current = null; - this.viewport = new Rectangle(); - - this.drawBufferExtension = this.renderer.context.extensions.drawBuffers; - } - - bind(framebuffer, frame) - { - const gl = this.gl; - - this.current = framebuffer; - - if (framebuffer) - { - // TODO cacheing layer! - - const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID] || this.initFramebuffer(framebuffer); - - gl.bindFramebuffer(gl.FRAMEBUFFER, fbo.framebuffer); - // makesure all textures are unbound.. - - // now check for updates... - if (fbo.dirtyId !== framebuffer.dirtyId) - { - fbo.dirtyId = framebuffer.dirtyId; - - if (fbo.dirtyFormat !== framebuffer.dirtyFormat) - { - fbo.dirtyFormat = framebuffer.dirtyFormat; - this.updateFramebuffer(framebuffer); - } - else if (fbo.dirtySize !== framebuffer.dirtySize) - { - fbo.dirtySize = framebuffer.dirtySize; - this.resizeFramebuffer(framebuffer); - } - } - - for (let i = 0; i < framebuffer.colorTextures.length; i++) - { - if (framebuffer.colorTextures[i].texturePart) - { - this.renderer.texture.unbind(framebuffer.colorTextures[i].texture); - } - else - { - this.renderer.texture.unbind(framebuffer.colorTextures[i]); - } - } - - if (framebuffer.depthTexture) - { - this.renderer.texture.unbind(framebuffer.depthTexture); - } - - if (frame) - { - this.setViewport(frame.x, frame.y, frame.width, frame.height); - } - else - { - this.setViewport(0, 0, framebuffer.width, framebuffer.height); - } - } - else - { - gl.bindFramebuffer(gl.FRAMEBUFFER, null); - - if (frame) - { - this.setViewport(frame.x, frame.y, frame.width, frame.height); - } - else - { - this.setViewport(0, 0, this.renderer.width, this.renderer.height); - } - } - } - - setViewport(x, y, width, height) - { - const v = this.viewport; - - if (v.width !== width || v.height !== height || v.x !== x || v.y !== y) - { - v.x = x; - v.y = y; - v.width = width; - v.height = height; - - this.gl.viewport(x, y, width, height); - } - } - - get size() - { - if (this.current) - { - // TODO store temp - return { x: 0, y: 0, width: this.current.width, height: this.current.height }; - } - - return { x: 0, y: 0, width: this.renderer.width, height: this.renderer.height }; - } - - clear(r, g, b, a) - { - const gl = this.gl; - - // TODO clear color can be set only one right? - gl.clearColor(r, g, b, a); - gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); - } - - // private functions... - - initFramebuffer(framebuffer) - { - const gl = this.gl; - - // TODO - make this a class? - const fbo = { - framebuffer: gl.createFramebuffer(), - stencil: null, - dirtyId: 0, - dirtyFormat: 0, - dirtySize: 0, - }; - - framebuffer.glFrameBuffers[this.CONTEXT_UID] = fbo; - - return fbo; - } - - resizeFramebuffer(framebuffer) - { - const gl = this.gl; - - if (framebuffer.stencil || framebuffer.depth) - { - gl.bindRenderbuffer(gl.RENDERBUFFER, this.stencil); - gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); - } - } - - updateFramebuffer(framebuffer) - { - const gl = this.gl; - - const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID]; - - // bind the color texture - const colorTextures = framebuffer.colorTextures; - - let count = colorTextures.length; - - if (!this.drawBufferExtension) - { - count = Math.min(count, 1); - } - - const activeTextures = []; - - for (let i = 0; i < count; i++) - { - const texture = framebuffer.colorTextures[i]; - - if (texture.texturePart) - { - this.renderer.texture.bind(texture.texture, 0); - - gl.framebufferTexture2D(gl.FRAMEBUFFER, - gl.COLOR_ATTACHMENT0 + i, - gl.TEXTURE_CUBE_MAP_NEGATIVE_X + texture.side, - texture.texture._glTextures[this.CONTEXT_UID].texture, - 0); - } - else - { - this.renderer.texture.bind(texture, 0); - - gl.framebufferTexture2D(gl.FRAMEBUFFER, - gl.COLOR_ATTACHMENT0 + i, - gl.TEXTURE_2D, - texture._glTextures[this.CONTEXT_UID].texture, - 0); - } - - activeTextures.push(gl.COLOR_ATTACHMENT0 + i); - } - - if (this.drawBufferExtension && activeTextures.length > 1) - { - this.drawBufferExtension.drawBuffersWEBGL(activeTextures); - } - - if (framebuffer.depthTexture) - { - const depthTextureExt = this.renderer.context.extensions.depthTexture; - - if (depthTextureExt) - { - const depthTexture = framebuffer.depthTexture; - - this.renderer.texture.bind(depthTexture, 0); - - gl.framebufferTexture2D(gl.FRAMEBUFFER, - gl.DEPTH_ATTACHMENT, - gl.TEXTURE_2D, - depthTexture._glTextures[this.CONTEXT_UID].texture, - 0); - } - } - - if (framebuffer.stencil || framebuffer.depth) - { - fbo.stencil = gl.createRenderbuffer(); - - gl.bindRenderbuffer(gl.RENDERBUFFER, fbo.stencil); - - // TODO.. this is depth AND stencil? - gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, fbo.stencil); - gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); - // fbo.enableStencil(); - } - } -} diff --git a/packages/core/src/renderers/systems/MaskSystem.js b/packages/core/src/renderers/systems/MaskSystem.js deleted file mode 100644 index d9c4b1a..0000000 --- a/packages/core/src/renderers/systems/MaskSystem.js +++ /dev/null @@ -1,199 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import AlphaMaskFilter from '../filters/spriteMask/SpriteMaskFilter'; - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class MaskSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - // TODO - we don't need both! - this.scissor = false; - this.scissorData = null; - this.scissorRenderTarget = null; - - this.enableScissor = false; - - this.alphaMaskPool = []; - this.alphaMaskIndex = 0; - } - - /** - * Applies the Mask and adds it to the current filter stack. - * - * @param {PIXI.DisplayObject} target - Display Object to push the mask to - * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. - */ - push(target, maskData) - { - // TODO the root check means scissor rect will not - // be used on render textures more info here: - // https://github.com/pixijs/pixi.js/pull/3545 - - if (maskData.texture) - { - this.pushSpriteMask(target, maskData); - } - else if (this.enableScissor - && !this.scissor - && this.renderer._activeRenderTarget.root - && !this.renderer.stencil.stencilMaskStack.length - && maskData.isFastRect()) - { - const matrix = maskData.worldTransform; - - let rot = Math.atan2(matrix.b, matrix.a); - - // use the nearest degree! - rot = Math.round(rot * (180 / Math.PI)); - - if (rot % 90) - { - this.pushStencilMask(maskData); - } - else - { - this.pushScissorMask(target, maskData); - } - } - else - { - this.pushStencilMask(maskData); - } - } - - /** - * Removes the last mask from the mask stack and doesn't return it. - * - * @param {PIXI.DisplayObject} target - Display Object to pop the mask from - * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. - */ - pop(target, maskData) - { - if (maskData.texture) - { - this.popSpriteMask(target, maskData); - } - else if (this.enableScissor && !this.renderer.stencil.stencilMaskStack.length) - { - this.popScissorMask(target, maskData); - } - else - { - this.popStencilMask(target, maskData); - } - } - - /** - * Applies the Mask and adds it to the current filter stack. - * - * @param {PIXI.RenderTarget} target - Display Object to push the sprite mask to - * @param {PIXI.Sprite} maskData - Sprite to be used as the mask - */ - pushSpriteMask(target, maskData) - { - let alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; - - if (!alphaMaskFilter) - { - alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; - } - - alphaMaskFilter[0].resolution = this.renderer.resolution; - alphaMaskFilter[0].maskSprite = maskData; - - // TODO - may cause issues! - target.filterArea = maskData.getBounds(true); - - this.renderer.filterSystem.pushFilter(target, alphaMaskFilter); - - this.alphaMaskIndex++; - } - - /** - * Removes the last filter from the filter stack and doesn't return it. - * - */ - popSpriteMask() - { - this.renderer.filterSystem.popFilter(); - this.alphaMaskIndex--; - } - - /** - * Applies the Mask and adds it to the current filter stack. - * - * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. - */ - pushStencilMask(maskData) - { - this.renderer.batch.flush(); - this.renderer.stencil.pushStencil(maskData); - } - - /** - * Removes the last filter from the filter stack and doesn't return it. - * - */ - popStencilMask() - { - // this.renderer.currentRenderer.stop(); - this.renderer.stencil.popStencil(); - } - - /** - * - * @param {PIXI.DisplayObject} target - Display Object to push the mask to - * @param {PIXI.Graphics} maskData - The masking data. - */ - pushScissorMask(target, maskData) - { - maskData.renderable = true; - - const renderTarget = this.renderer._activeRenderTarget; - - const bounds = maskData.getBounds(); - - bounds.fit(renderTarget.size); - maskData.renderable = false; - - this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); - - const resolution = this.renderer.resolution; - - this.renderer.gl.scissor( - bounds.x * resolution, - (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, - bounds.width * resolution, - bounds.height * resolution - ); - - this.scissorRenderTarget = renderTarget; - this.scissorData = maskData; - this.scissor = true; - } - - /** - * - * - */ - popScissorMask() - { - this.scissorRenderTarget = null; - this.scissorData = null; - this.scissor = false; - - // must be scissor! - const gl = this.renderer.gl; - - gl.disable(gl.SCISSOR_TEST); - } -} diff --git a/packages/core/src/renderers/systems/ProjectionSystem.js b/packages/core/src/renderers/systems/ProjectionSystem.js deleted file mode 100644 index ec5efdb..0000000 --- a/packages/core/src/renderers/systems/ProjectionSystem.js +++ /dev/null @@ -1,73 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { Matrix } from '@pixi/math'; - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ - -export default class ProjectionSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this.projectionMatrix = new Matrix(); - } - - update(destinationFrame, sourceFrame, resolution, root) - { - this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; - this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; - - this.calculateProjection(this.destinationFrame, this.sourceFrame, resolution, root); - - this.renderer.globalUniforms.uniforms.projectionMatrix = this.projectionMatrix; - this.renderer.globalUniforms.update(); - } - - /** - * Updates the projection matrix based on a projection frame (which is a rectangle) - * - * @param {Rectangle} destinationFrame - The destination frame. - * @param {Rectangle} sourceFrame - The source frame. - */ - calculateProjection(destinationFrame, sourceFrame, resolution, root) - { - const pm = this.projectionMatrix; - - // I don't think we will need this line.. - // pm.identity(); - - if (!root) - { - pm.a = (1 / destinationFrame.width * 2) * resolution; - pm.d = (1 / destinationFrame.height * 2) * resolution; - - pm.tx = -1 - (sourceFrame.x * pm.a); - pm.ty = -1 - (sourceFrame.y * pm.d); - } - else - { - pm.a = (1 / destinationFrame.width * 2) * resolution; - pm.d = (-1 / destinationFrame.height * 2) * resolution; - - pm.tx = -1 - (sourceFrame.x * pm.a); - pm.ty = 1 - (sourceFrame.y * pm.d); - } - } - - /** - * Sets the transform of the active render target to the given matrix - * - * @param {PIXI.Matrix} matrix - The transformation matrix - */ - setTransform()// matrix) - { - // this._activeRenderTarget.transform = matrix; - } -} diff --git a/packages/core/src/renderers/systems/RenderTextureSystem.js b/packages/core/src/renderers/systems/RenderTextureSystem.js deleted file mode 100644 index a133ef1..0000000 --- a/packages/core/src/renderers/systems/RenderTextureSystem.js +++ /dev/null @@ -1,115 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { Rectangle } from '@pixi/math'; - -const tempRect = new Rectangle(); - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ - -export default class RenderTextureSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this.clearColor = renderer._backgroundColorRgba; - - // TODO moe this property somewhere else! - this.defaultMaskStack = []; - this.defaultFilterStack = [{}]; - - // empty render texture? - this.renderTexture = null; - - this.destinationFrame = new Rectangle(); - } - - bind(renderTexture, sourceFrame, destinationFrame) - { - // TODO - do we want this?? - if (this.renderTexture === renderTexture) return; - this.renderTexture = renderTexture; - - const renderer = this.renderer; - - if (renderTexture) - { - const baseTexture = renderTexture.baseTexture; - - if (!destinationFrame) - { - tempRect.width = baseTexture.realWidth; - tempRect.height = baseTexture.realHeight; - - destinationFrame = tempRect; - } - - if (!sourceFrame) - { - sourceFrame = destinationFrame; - } - - this.renderer.framebuffer.bind(baseTexture.frameBuffer, destinationFrame); - - this.renderer.projection.update(destinationFrame, sourceFrame, baseTexture.resolution, false); - this.renderer.stencil.setMaskStack(baseTexture.stencilMaskStack); - } - else - { - // TODO these validation checks happen deeper down.. - // thing they can be avoided.. - if (!destinationFrame) - { - tempRect.width = renderer.width; - tempRect.height = renderer.height; - - destinationFrame = tempRect; - } - - if (!sourceFrame) - { - sourceFrame = destinationFrame; - } - - renderer.framebuffer.bind(null, destinationFrame); - - // TODO store this.. - this.renderer.projection.update(destinationFrame, sourceFrame, this.renderer.resolution, true); - this.renderer.stencil.setMaskStack(this.defaultMaskStack); - } - - this.destinationFrame.copyFrom(destinationFrame); - } - - /** - * Erases the render texture and fills the drawing area with a colour - * - * @param {number} [clearColor] - The colour - * @return {PIXI.Renderer} Returns itself. - */ - clear(clearColor) - { - if (this.renderTexture) - { - clearColor = clearColor || this.renderTexture.baseTexture.clearColor; - } - else - { - clearColor = clearColor || this.clearColor; - } - - this.renderer.framebuffer.clear(clearColor[0], clearColor[1], clearColor[2], clearColor[3]); - } - - resize()// screenWidth, screenHeight) - { - // resize the root only! - this.bind(null); - } -} diff --git a/packages/core/src/renderers/systems/StateSystem.js b/packages/core/src/renderers/systems/StateSystem.js deleted file mode 100755 index d7f4caf..0000000 --- a/packages/core/src/renderers/systems/StateSystem.js +++ /dev/null @@ -1,288 +0,0 @@ -import mapWebGLBlendModesToPixi from '../utils/mapWebGLBlendModesToPixi'; -import WebGLSystem from './WebGLSystem'; -import WebGLState from '../State'; - -const BLEND = 0; -const OFFSET = 1; -const CULLING = 2; -const DEPTH_TEST = 3; -const WINDING = 4; - -/** - * A WebGL state machines - * - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class StateSystem extends WebGLSystem -{ - /** - * @param {WebGLRenderingContext} gl - The current WebGL rendering context - */ - constructor(renderer) - { - super(renderer); - - this.gl = null; - - this.maxAttribs = null; - - // check we have vao.. - this.nativeVaoExtension = null; - - this.attribState = null; - - this.stateId = 0; - this.polygonOffset = 0; - this.blendMode = 17; - - this.map = []; - - // map functions for when we set state.. - this.map[BLEND] = this.setBlend; - this.map[OFFSET] = this.setOffset; - this.map[CULLING] = this.setCullFace; - this.map[DEPTH_TEST] = this.setDepthTest; - this.map[WINDING] = this.setFrontFace; - - this.checks = []; - - this.defaultState = new WebGLState(); - this.defaultState.blend = true; - this.defaultState.depth = true; - } - - contextChange(gl) - { - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; - - this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); - - // check we have vao.. - this.nativeVaoExtension = ( - gl.getExtension('OES_vertex_array_object') - || gl.getExtension('MOZ_OES_vertex_array_object') - || gl.getExtension('WEBKIT_OES_vertex_array_object') - ); - - this.attribState = { - tempAttribState: new Array(this.maxAttribs), - attribState: new Array(this.maxAttribs), - }; - - this.blendModes = mapWebGLBlendModesToPixi(gl); - - this.setState(this.defaultState); - - this.reset(); - } - - /** - * Sets the current state - * - * @param {*} state - The state to set. - */ - setState(state) - { - state = state || this.defaultState; - - // TODO maybe to an object check? ( this.state === state )? - if (this.stateId !== state.data) - { - let diff = this.stateId ^ state.data; - let i = 0; - - // order from least to most common - while (diff) - { - if (diff & 1) - { - // state change! - this.map[i].call(this, !!(state.data & (1 << i))); - } - - diff = diff >> 1; - i++; - } - - this.stateId = state.data; - } - - // based on the above settings we check for specific modes.. - // for example if blend is active we check and set the blend modes - // or of polygon offset is active we check the poly depth. - for (let i = 0; i < this.checks.length; i++) - { - this.checks[i](this, state); - } - } - - /** - * Enables or disabled blending. - * - * @param {boolean} value - Turn on or off webgl blending. - */ - setBlend(value) - { - this.updateCheck(StateSystem.checkBlendMode, value); - - this.gl[value ? 'enable' : 'disable'](this.gl.BLEND); - } - - /** - * Enables or disable polygon offset fill - * - * @param {boolean} value - Turn on or off webgl polygon offset testing. - */ - setOffset(value) - { - this.gl[value ? 'enable' : 'disable'](this.gl.POLYGON_OFFSET_FILL); - } - - /** - * Sets whether to enable or disable depth test. - * - * @param {boolean} value - Turn on or off webgl depth testing. - */ - setDepthTest(value) - { - this.gl[value ? 'enable' : 'disable'](this.gl.DEPTH_TEST); - } - - /** - * Sets whether to enable or disable cull face. - * - * @param {boolean} value - Turn on or off webgl cull face. - */ - setCullFace(value) - { - this.gl[value ? 'enable' : 'disable'](this.gl.CULL_FACE); - } - - /** - * Sets the gl front face. - * - * @param {boolean} value - true is clockwise and false is counter-clockwise - */ - setFrontFace(value) - { - this.gl.frontFace(this.gl[value ? 'CW' : 'CCW']); - } - - /** - * Sets the blend mode. - * - * @param {number} value - The blend mode to set to. - */ - setBlendMode(value) - { - if (value === this.blendMode) - { - return; - } - - this.blendMode = value; - this.gl.blendFunc(this.blendModes[value][0], this.blendModes[value][1]); - } - - /** - * Sets the polygon offset. - * - * @param {number} value - the polygon offset - * @param {number} scale - the polygon offset scale - */ - setPolygonOffset(value, scale) - { - this.gl.polygonOffset(value, scale); - } - - /** - * Disables all the vaos in use - * - */ - resetAttributes() - { - for (let i = 0; i < this.attribState.tempAttribState.length; i++) - { - this.attribState.tempAttribState[i] = 0; - } - - for (let i = 0; i < this.attribState.attribState.length; i++) - { - this.attribState.attribState[i] = 0; - } - - // im going to assume one is always active for performance reasons. - for (let i = 1; i < this.maxAttribs; i++) - { - this.gl.disableVertexAttribArray(i); - } - } - - // used - /** - * Resets all the logic and disables the vaos - */ - reset() - { - // unbind any VAO if they exist.. - if (this.nativeVaoExtension) - { - this.nativeVaoExtension.bindVertexArrayOES(null); - } - - // reset all attributes.. - this.resetAttributes(); - - this.gl.pixelStorei(this.gl.UNPACK_FLIP_Y_WEBGL, false); - - this.setBlendMode(0); - - // TODO? - // this.setState(this.defaultState); - } - - /** - * checks to see which updates should be checked based on which settings have been activated - * for example if blend is enabled then we shold check the blend modes each time the state is changed - * or if poygon fill is activated then we need to check if the polygone offset changes. - * The idea is that we only check what we have too. - * - * @param {Function} func the checking function to add or remove - * @param {boolean} value should the check function be added or removed. - */ - updateCheck(func, value) - { - const index = this.checks.indexOf(func); - - if (value && index === -1) - { - this.checks.push(func); - } - else if (!value && index !== -1) - { - this.checks.splice(index, 1); - } - } - - /** - * A private little wrapper function that we call to check the blend mode. - * - * @static - * @private - * @param {PIXI.StateSystem} System the System to perform the state check on - * @param {PIXI.State} state the state that the blendMode will pulled from - */ - static checkBlendMode(system, state) - { - system.setBlendMode(state.blendMode); - } - - // TODO - add polygon offset? -} diff --git a/packages/core/src/renderers/systems/StencilSystem.js b/packages/core/src/renderers/systems/StencilSystem.js deleted file mode 100644 index 5d538cb..0000000 --- a/packages/core/src/renderers/systems/StencilSystem.js +++ /dev/null @@ -1,133 +0,0 @@ -import WebGLSystem from './WebGLSystem'; - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class StencilSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - this.stencilMaskStack = []; - } - - /** - * Changes the mask stack that is used by this System. - * - * @param {PIXI.Graphics[]} stencilMaskStack - The mask stack - */ - setMaskStack(stencilMaskStack) - { - const gl = this.renderer.gl; - - if (stencilMaskStack.length !== this.stencilMaskStack.length) - { - if (stencilMaskStack.length === 0) - { - gl.disable(gl.STENCIL_TEST); - } - else - { - gl.enable(gl.STENCIL_TEST); - } - } - - this.stencilMaskStack = stencilMaskStack; - } - - /** - * Applies the Mask and adds it to the current stencil stack. @alvin - * - * @param {PIXI.Graphics} graphics - The mask - */ - pushStencil(graphics) - { - this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); - - // this.renderer._activeRenderTarget.attachStencilBuffer(); - - const gl = this.renderer.gl; - const prevMaskCount = this.stencilMaskStack.length; - - if (prevMaskCount === 0) - { - gl.enable(gl.STENCIL_TEST); - } - - this.stencilMaskStack.push(graphics); - - // Increment the refference stencil value where the new mask overlaps with the old ones. - gl.colorMask(false, false, false, false); - gl.stencilFunc(gl.EQUAL, prevMaskCount, this._getBitwiseMask()); - gl.stencilOp(gl.KEEP, gl.KEEP, gl.INCR); - this.renderer.plugins.graphics.render(graphics); - - this._useCurrent(); - } - - /** - * Removes the last mask from the stencil stack. @alvin - */ - popStencil() - { - this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); - - const gl = this.renderer.gl; - const graphics = this.stencilMaskStack.pop(); - - if (this.stencilMaskStack.length === 0) - { - // the stack is empty! - gl.disable(gl.STENCIL_TEST); - gl.clear(gl.STENCIL_BUFFER_BIT); - gl.clearStencil(0); - } - else - { - // Decrement the refference stencil value where the popped mask overlaps with the other ones - gl.colorMask(false, false, false, false); - gl.stencilOp(gl.KEEP, gl.KEEP, gl.DECR); - this.renderer.plugins.graphics.render(graphics); - - this._useCurrent(); - } - } - - /** - * Setup renderer to use the current stencil data. - */ - _useCurrent() - { - const gl = this.renderer.gl; - - gl.colorMask(true, true, true, true); - gl.stencilFunc(gl.EQUAL, this.stencilMaskStack.length, this._getBitwiseMask()); - gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP); - } - - /** - * Fill 1s equal to the number of acitve stencil masks. - * - * @return {number} The bitwise mask. - */ - _getBitwiseMask() - { - return (1 << this.stencilMaskStack.length) - 1; - } - - /** - * Destroys the mask stack. - * - */ - destroy() - { - super.destroy(this); - - this.stencilMaskStack = null; - } -} diff --git a/packages/core/src/renderers/systems/WebGLSystem.js b/packages/core/src/renderers/systems/WebGLSystem.js deleted file mode 100644 index d3d5b31..0000000 --- a/packages/core/src/renderers/systems/WebGLSystem.js +++ /dev/null @@ -1,41 +0,0 @@ -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this manager works for. - */ - constructor(renderer) - { - /** - * The renderer this manager works for. - * - * @member {PIXI.Renderer} - */ - this.renderer = renderer; - - this.renderer.runners.contextChange.add(this); - } - - /** - * Generic method called when there is a WebGL context change. - * - */ - contextChange() - { - // do some codes init! - } - - /** - * Generic destroy methods to be overridden by the subclass - * - */ - destroy() - { - this.renderer.runners.contextChange.remove(this); - this.renderer = null; - } -} diff --git a/packages/core/src/renderers/systems/filter/FilterSystem.js b/packages/core/src/renderers/systems/filter/FilterSystem.js deleted file mode 100644 index e615037..0000000 --- a/packages/core/src/renderers/systems/filter/FilterSystem.js +++ /dev/null @@ -1,431 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; - -import RenderTexture from '../../../textures/RenderTexture'; -import Quad from '../../utils/Quad'; -import QuadUv from '../../utils/QuadUv'; -import { Rectangle } from '@pixi/math'; -import * as filterTransforms from '../../filters/filterTransforms'; -import bitTwiddle from 'bit-twiddle'; -import UniformGroup from '../../../shader/UniformGroup'; -import { DRAW_MODES } from '../../../../../constants'; - -// -/** - * @ignore - * @class - */ -class FilterState -{ - /** - * - */ - constructor() - { - this.renderTexture = null; - this.sourceFrame = new Rectangle(); - this.destinationFrame = new Rectangle(); - this.filters = []; - this.target = null; - this.legacy = false; - this.resolution = 1; - } -} - -/** - * @class - * @memberof PIXI.systems - * @extends PIXI.systems.WebGLSystem - */ -export default class FilterSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - /** - * stores a bunch of PO2 textures used for filtering - * @type {Object} - */ - this.texturePool = {}; - - /** - * a pool for storing filter states, save us creating new ones each tick - * @type {Array} - */ - this.statePool = []; - - /** - * A very simple geometry used when drawing a filter effect to the screen - * @type {Quad} - */ - this.quad = new Quad(); - - this.quadUv = new QuadUv(); - - /** - * Temporary rect for maths - * @type {PIXI.Rectangle} - */ - this.tempRect = new Rectangle(); - - this.activeState = {}; - - /** - * this uniform group is attached to filter uniforms when used - * @type {UniformGroup} - */ - this.globalUniforms = new UniformGroup({ - sourceFrame: this.tempRect, - destinationFrame: this.tempRect, - - // legacy variables - filterArea: new Float32Array(4), - filterClamp: new Float32Array(4), - }, true); - } - - /** - * Adds a new filter to the System. - * - * @param {PIXI.DisplayObject} target - The target of the filter to render. - * @param {PIXI.Filter[]} filters - The filters to apply. - */ - push(target, filters) - { - const renderer = this.renderer; - const filterStack = this.renderer.renderTexture.defaultFilterStack; - const state = this.statePool.pop() || new FilterState(); - - let resolution = filters[0].resolution; - let padding = filters[0].padding; - let autoFit = filters[0].autoFit; - let legacy = filters[0].legacy; - - for (let i = 1; i < filters.length; i++) - { - const filter = filters[i]; - - // lets use the lowest resolution.. - resolution = Math.min(resolution, filter.resolution); - // and the largest amount of padding! - padding = Math.max(padding, filter.padding); - // only auto fit if all filters are autofit - autoFit = autoFit || filter.autoFit; - - legacy = legacy || filter.legacy; - } - - filterStack.push(state); - - state.resolution = resolution; - - state.legacy = legacy; - - // round to whole number based on resolution - // TODO move that to the shader too? - state.sourceFrame = target.filterArea || target.getBounds(true); - - state.sourceFrame.pad(padding); - - if (autoFit) - { - state.sourceFrame.fit(this.renderer.renderTexture.destinationFrame); - } - - state.sourceFrame.round(resolution); - - state.renderTexture = this.getPotFilterTexture(state.sourceFrame.width, state.sourceFrame.height, resolution); - state.filters = filters; - - state.destinationFrame.width = state.renderTexture.width; - state.destinationFrame.height = state.renderTexture.height; - - state.renderTexture.filterFrame = state.sourceFrame; - renderer.renderTexture.bind(state.renderTexture, state.sourceFrame);// /, state.destinationFrame); - renderer.renderTexture.clear(); - } - - /** - * Pops off the filter and applies it. - * - */ - pop() - { - const renderer = this.renderer; - const filterStack = renderer.renderTexture.defaultFilterStack; - const state = filterStack.pop(); - const filters = state.filters; - - this.activeState = state; - - const globalUniforms = this.globalUniforms.uniforms; - - globalUniforms.sourceFrame = state.sourceFrame; - globalUniforms.destinationFrame = state.destinationFrame; - globalUniforms.resolution = state.resolution; - - // only update the rect if its legacy.. - if (state.legacy) - { - const filterArea = globalUniforms.filterArea; - const filterClamp = globalUniforms.filterClamp; - - filterArea[0] = state.destinationFrame.width; - filterArea[1] = state.destinationFrame.height; - filterArea[2] = state.sourceFrame.x; - filterArea[3] = state.sourceFrame.y; - - filterClamp[0] = 0.5 / state.resolution / state.destinationFrame.width; - filterClamp[1] = 0.5 / state.resolution / state.destinationFrame.height; - filterClamp[2] = (state.sourceFrame.width - 0.5) / state.resolution / state.destinationFrame.width; - filterClamp[3] = (state.sourceFrame.height - 0.5) / state.resolution / state.destinationFrame.height; - } - - this.globalUniforms.update(); - - const lastState = filterStack[filterStack.length - 1]; - - if (filters.length === 1) - { - filters[0].apply(this, state.renderTexture, lastState.renderTexture, false, state); - - this.returnFilterTexture(state.renderTexture); - } - else - { - let flip = state.renderTexture; - let flop = this.getPotFilterTexture( - flip.width, - flip.height, - state.resolution - ); - - flop.filterFrame = flip.filterFrame; - - let i = 0; - - for (i = 0; i < filters.length - 1; ++i) - { - filters[i].apply(this, flip, flop, true, state); - - const t = flip; - - flip = flop; - flop = t; - } - - filters[i].apply(this, flip, lastState.renderTexture, false, state); - - this.returnFilterTexture(flip); - this.returnFilterTexture(flop); - } - - this.statePool.push(state); - } - - /** - * Draws a filter. - * - * @param {PIXI.Filter} filter - The filter to draw. - * @param {PIXI.RenderTarget} input - The input render target. - * @param {PIXI.RenderTarget} output - The target to output to. - * @param {boolean} clear - Should the output be cleared before rendering to it - */ - applyFilter(filter, input, output, clear) - { - const renderer = this.renderer; - - renderer.renderTexture.bind(output, output ? output.filterFrame : null); - - if (clear) - { - // gl.disable(gl.SCISSOR_TEST); - renderer.renderTexture.clear(); - // gl.enable(gl.SCISSOR_TEST); - } - - // set the uniforms.. - filter.uniforms.uSampler = input; - filter.uniforms.filterGlobals = this.globalUniforms; - - // TODO make it so that the order of this does not matter.. - // because it does at the moment cos of global uniforms. - // they need to get resynced - - renderer.state.setState(filter.state); - renderer.shader.bind(filter); - - if (filter.legacy) - { - this.quadUv.map(input._frame, input.filterFrame); - - renderer.geometry.bind(this.quadUv); - renderer.geometry.draw(DRAW_MODES.TRIANGLES); - } - else - { - renderer.geometry.bind(this.quad); - renderer.geometry.draw(DRAW_MODES.TRIANGLE_STRIP); - } - } - - /** - * Calculates the mapped matrix. - * - * TODO playing around here.. this is temporary - (will end up in the shader) - * this returns a matrix that will normalise map filter cords in the filter to screen space - * - * @param {PIXI.Matrix} outputMatrix - the matrix to output to. - * @return {PIXI.Matrix} The mapped matrix. - */ - calculateScreenSpaceMatrix(outputMatrix) - { - const currentState = this.activeState; - - return filterTransforms.calculateScreenSpaceMatrix( - outputMatrix, - currentState.sourceFrame, - currentState.destinationFrame - ); - } - - /** - * This will map the filter coord so that a texture can be used based on the transform of a sprite - * - * @param {PIXI.Matrix} outputMatrix - The matrix to output to. - * @param {PIXI.Sprite} sprite - The sprite to map to. - * @return {PIXI.Matrix} The mapped matrix. - */ - calculateSpriteMatrix(outputMatrix, sprite) - { - const currentState = this.activeState; - - return filterTransforms.calculateSpriteMatrix( - outputMatrix, - currentState.sourceFrame, - currentState.destinationFrame, - sprite - ); - } - - /** - * Destroys this Filter System. - * - * @param {boolean} [contextLost=false] context was lost, do not free shaders - * - */ - destroy(contextLost = false) - { - if (!contextLost) - { - this.emptyPool(); - } - else - { - this.texturePool = {}; - } - } - - /** - * Gets a Power-of-Two render texture. - * - * TODO move to a seperate class could be on renderer? - * also - could cause issue with multiple contexts? - * - * @private - * @param {WebGLRenderingContext} gl - The webgl rendering context - * @param {number} minWidth - The minimum width of the render target. - * @param {number} minHeight - The minimum height of the render target. - * @param {number} resolution - The resolution of the render target. - * @return {PIXI.RenderTarget} The new render target. - */ - getPotFilterTexture(minWidth, minHeight, resolution) - { - minWidth = bitTwiddle.nextPow2(minWidth); - minHeight = bitTwiddle.nextPow2(minHeight); - resolution = resolution || 1; - - const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); - - if (!this.texturePool[key]) - { - this.texturePool[key] = []; - } - - let renderTexture = this.texturePool[key].pop(); - - if (!renderTexture) - { - // temporary bypass cache.. - // internally - this will cause a texture to be bound.. - renderTexture = RenderTexture.create({ - width: minWidth, - height: minHeight, - resolution, - }); - } - - return renderTexture; - } - - /** - * Gets extra render texture to use inside current filter - * - * @param {number} resolution resolution of the renderTexture - * @returns {PIXI.RenderTexture} - */ - getFilterTexture(resolution) - { - const rt = this.activeState.renderTexture; - - const filterTexture = this.getPotFilterTexture(rt.width, rt.height, resolution || rt.baseTexture.resolution); - - filterTexture.filterFrame = rt.filterFrame; - - return filterTexture; - } - - /** - * Frees a render texture back into the pool. - * - * @param {PIXI.RenderTarget} renderTexture - The renderTarget to free - */ - returnFilterTexture(renderTexture) - { - renderTexture.filterFrame = null; - - const base = renderTexture.baseTexture; - - const minWidth = base.width; - const minHeight = base.height; - - const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); - - this.texturePool[key].push(renderTexture); - } - - /** - * Empties the texture pool. - * - */ - emptyPool() - { - for (const i in this.texturePool) - { - const textures = this.texturePool[i]; - - if (textures) - { - for (let j = 0; j < textures.length; j++) - { - textures[j].destroy(true); - } - } - } - - this.texturePool = {}; - } -} diff --git a/packages/core/src/renderers/systems/geometry/GLBuffer.js b/packages/core/src/renderers/systems/geometry/GLBuffer.js deleted file mode 100644 index 0477e8a..0000000 --- a/packages/core/src/renderers/systems/geometry/GLBuffer.js +++ /dev/null @@ -1,9 +0,0 @@ -export default class GLBuffer -{ - constructor(buffer) - { - this.buffer = buffer; - this.updateID = -1; - this.byteLength = -1; - } -} diff --git a/packages/core/src/renderers/systems/geometry/GeometrySystem.js b/packages/core/src/renderers/systems/geometry/GeometrySystem.js deleted file mode 100644 index 26c8e9e..0000000 --- a/packages/core/src/renderers/systems/geometry/GeometrySystem.js +++ /dev/null @@ -1,388 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; -import GLBuffer from './GLBuffer'; - -const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class GeometrySystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this._activeGeometry = null; - this._activeVao = null; - - this.hasVao = true; - this.hasInstance = true; - } - - /** - * Sets up the renderer context and necessary buffers. - * - * @private - */ - contextChange() - { - const gl = this.gl = this.renderer.gl; - - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - - // webgl2 - if (!gl.createVertexArray) - { - // webgl 1! - let nativeVaoExtension = this.renderer.context.extensions.vertexArrayObject; - - if (this.renderer.legacy) - { - nativeVaoExtension = null; - } - - if (nativeVaoExtension) - { - gl.createVertexArray = () => - nativeVaoExtension.createVertexArrayOES(); - - gl.bindVertexArray = (vao) => - nativeVaoExtension.bindVertexArrayOES(vao); - - gl.deleteVertexArray = (vao) => - nativeVaoExtension.deleteVertexArrayOES(vao); - } - else - { - this.hasVao = false; - gl.createVertexArray = () => - { - // empty - }; - - gl.bindVertexArray = () => - { - // empty - }; - - gl.deleteVertexArray = () => - { - // empty - }; - } - } - - if (!gl.vertexAttribDivisor) - { - const instanceExt = gl.getExtension('ANGLE_instanced_arrays'); - - if (instanceExt) - { - gl.vertexAttribDivisor = (a, b) => - instanceExt.vertexAttribDivisorANGLE(a, b); - - gl.drawElementsInstanced = (a, b, c, d, e) => - instanceExt.drawElementsInstancedANGLE(a, b, c, d, e); - - gl.drawArraysInstanced = (a, b, c, d) => - instanceExt.drawArraysInstancedANGLE(a, b, c, d); - } - else - { - this.hasInstance = false; - } - } - } - - /** - * Binds geometry so that is can be drawn. Creating a Vao if required - * @private - * @param {PIXI.Geometry} geometry instance of geometry to bind - */ - bind(geometry, shader) - { - shader = shader || this.renderer.shader.shader; - - const gl = this.gl; - - // not sure the best way to address this.. - // currently different shaders require different VAOs for the same geometry - // Still mulling over the best way to solve this one.. - // will likely need to modify the shader attribute locations at run time! - let vaos = geometry.glVertexArrayObjects[this.CONTEXT_UID]; - - if (!vaos) - { - geometry.glVertexArrayObjects[this.CONTEXT_UID] = vaos = {}; - } - - const vao = vaos[shader.program.id] || this.initGeometryVao(geometry, shader.program); - - this._activeGeometry = geometry; - - if (this._activeVao !== vao) - { - this._activeVao = vao; - - if (this.hasVao) - { - gl.bindVertexArray(vao); - } - else - { - this.activateVao(geometry, shader.program); - } - } - - // TODO - optimise later! - // don't need to loop through if nothing changed! - // maybe look to add an 'autoupdate' to geometry? - this.updateBuffers(); - } - - reset() - { - this.unbind(); - } - - updateBuffers() - { - const geometry = this._activeGeometry; - const gl = this.gl; - - for (let i = 0; i < geometry.buffers.length; i++) - { - const buffer = geometry.buffers[i]; - - const glBuffer = buffer._glBuffers[this.CONTEXT_UID]; - - if (buffer._updateID !== glBuffer.updateID) - { - glBuffer.updateID = buffer._updateID; - - // TODO can cache this on buffer! maybe added a getter / setter? - const type = buffer.index ? gl.ELEMENT_ARRAY_BUFFER : gl.ARRAY_BUFFER; - const drawType = buffer.static ? gl.STATIC_DRAW : gl.DYNAMIC_DRAW; - - gl.bindBuffer(type, glBuffer.buffer); - - if (glBuffer.byteLength >= buffer.data.byteLength) - { - // offset is always zero for now! - gl.bufferSubData(type, 0, buffer.data); - } - else - { - gl.bufferData(type, buffer.data, drawType); - } - } - } - } - - checkCompatability(geometry, program) - { - // geometry must have at least all the attributes that the shader requires. - const geometryAttributes = geometry.attributes; - const shaderAttributes = program.attributeData; - - for (const j in shaderAttributes) - { - if (!geometryAttributes[j]) - { - throw new Error(`shader and geometry incompatible, geometry missing the "${j}" attribute`); - } - } - } - - /** - * Creates a Vao with the same structure as the geometry and stores it on the geometry. - * @private - * @param {PIXI.Geometry} geometry instance of geometry to to generate Vao for - */ - initGeometryVao(geometry, program) - { - this.checkCompatability(geometry, program); - - const gl = this.gl; - const CONTEXT_UID = this.CONTEXT_UID; - const buffers = geometry.buffers; - const attributes = geometry.attributes; - - const tempStride = {}; - const tempStart = {}; - - for (const j in buffers) - { - tempStride[j] = 0; - tempStart[j] = 0; - } - - for (const j in attributes) - { - if (!attributes[j].size && program.attributeData[j]) - { - attributes[j].size = program.attributeData[j].size; - } - - tempStride[attributes[j].buffer] += attributes[j].size * byteSizeMap[attributes[j].type]; - } - - for (const j in attributes) - { - const attribute = attributes[j]; - const attribSize = attribute.size; - - if (attribute.stride === undefined) - { - if (tempStride[attribute.buffer] === attribSize * byteSizeMap[attribute.type]) - { - attribute.stride = 0; - } - else - { - attribute.stride = tempStride[attribute.buffer]; - } - } - - if (attribute.start === undefined) - { - attribute.start = tempStart[attribute.buffer]; - - tempStart[attribute.buffer] += attribSize * byteSizeMap[attribute.type]; - } - } - - // first update - and create the buffers! - // only create a gl buffer if it actually gets - for (let i = 0; i < buffers.length; i++) - { - const buffer = buffers[i]; - - if (!buffer._glBuffers[CONTEXT_UID]) - { - buffer._glBuffers[CONTEXT_UID] = new GLBuffer(gl.createBuffer()); - } - } - - // TODO - maybe make this a data object? - // lets wait to see if we need to first! - const vao = gl.createVertexArray(); - - gl.bindVertexArray(vao); - - this.activateVao(geometry, program); - - gl.bindVertexArray(null); - - // add it to the cache! - geometry.glVertexArrayObjects[this.CONTEXT_UID][program.id] = vao; - - return vao; - } - - activateVao(geometry, program) - { - const gl = this.gl; - const CONTEXT_UID = this.CONTEXT_UID; - const buffers = geometry.buffers; - const attributes = geometry.attributes; - - if (geometry.indexBuffer) - { - // first update the index buffer if we have one.. - gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, geometry.indexBuffer._glBuffers[CONTEXT_UID].buffer); - } - - let lastBuffer = null; - - // add a new one! - for (const j in attributes) - { - const attribute = attributes[j]; - const buffer = buffers[attribute.buffer]; - const glBuffer = buffer._glBuffers[CONTEXT_UID]; - - if (program.attributeData[j]) - { - if (lastBuffer !== glBuffer) - { - gl.bindBuffer(gl.ARRAY_BUFFER, glBuffer.buffer); - - lastBuffer = glBuffer; - } - - const location = program.attributeData[j].location; - - // TODO introduce state again - // we can optimise this for older devices that have no VAOs - gl.enableVertexAttribArray(location); - - gl.vertexAttribPointer(location, - attribute.size, - attribute.type || gl.FLOAT, - attribute.normalized, - attribute.stride, - attribute.start); - - if (attribute.instance) - { - // TODO calculate instance count based of this... - if (this.hasInstance) - { - gl.vertexAttribDivisor(location, 1); - } - else - { - throw new Error('geometry error, GPU Instancing is not supported on this device'); - } - } - } - } - } - - draw(type, size, start, instanceCount) - { - const gl = this.gl; - const geometry = this._activeGeometry; - - // TODO.. this should not change so maybe cache the function? - - if (geometry.indexBuffer) - { - if (geometry.instanced) - { - /* eslint-disable max-len */ - gl.drawElementsInstanced(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2, instanceCount || 1); - /* eslint-enable max-len */ - } - else - { - gl.drawElements(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2); - } - } - else - if (geometry.instanced) - { - // TODO need a better way to calculate size.. - gl.drawArraysInstanced(type, start, size || geometry.getSize(), instanceCount || 1); - } - else - { - gl.drawArrays(type, start, size || geometry.getSize()); - } - - return this; - } - - unbind() - { - this.gl.bindVertexArray(null); - this._activeVao = null; - this._activeGeometry = null; - } -} diff --git a/packages/core/src/renderers/systems/geometry/setVertexAttribArrays.js b/packages/core/src/renderers/systems/geometry/setVertexAttribArrays.js deleted file mode 100644 index 23b6959..0000000 --- a/packages/core/src/renderers/systems/geometry/setVertexAttribArrays.js +++ /dev/null @@ -1,54 +0,0 @@ -// var GL_MAP = {}; - -/** - * @param gl {WebGLRenderingContext} The current WebGL context - * @param attribs {*} - * @param state {*} - */ -export default function setVertexAttribArrays(gl, attribs, state) -{ - let i; - - if (state) - { - const tempAttribState = state.tempAttribState; - const attribState = state.attribState; - - for (i = 0; i < tempAttribState.length; i++) - { - tempAttribState[i] = false; - } - - // set the new attribs - for (i = 0; i < attribs.length; i++) - { - tempAttribState[attribs[i].attribute.location] = true; - } - - for (i = 0; i < attribState.length; i++) - { - if (attribState[i] !== tempAttribState[i]) - { - attribState[i] = tempAttribState[i]; - - if (state.attribState[i]) - { - gl.enableVertexAttribArray(i); - } - else - { - gl.disableVertexAttribArray(i); - } - } - } - } - else - { - for (i = 0; i < attribs.length; i++) - { - const attrib = attribs[i]; - - gl.enableVertexAttribArray(attrib.attribute.location); - } - } -} diff --git a/packages/core/src/renderers/systems/index.js b/packages/core/src/renderers/systems/index.js deleted file mode 100644 index c843f03..0000000 --- a/packages/core/src/renderers/systems/index.js +++ /dev/null @@ -1,6 +0,0 @@ -/** - * Systems are individual components to the Renderer pipeline. - * @namespace PIXI.systems - */ -export { default as WebGLSystem } from './WebGLSystem'; -export { default as FilterSystem } from './filter/FilterSystem'; diff --git a/packages/core/src/renderers/systems/shader/GLProgram.js b/packages/core/src/renderers/systems/shader/GLProgram.js deleted file mode 100644 index 92980ca..0000000 --- a/packages/core/src/renderers/systems/shader/GLProgram.js +++ /dev/null @@ -1,43 +0,0 @@ -/** - * Helper class to create a webGL Program - * - * @class - * @private - */ -export default class GLProgram -{ - constructor(program, uniformData) - { - /** - * The shader program - * - * @member {WebGLProgram} - */ - this.program = program; - - /** - * holds the uniform data which contains uniform locations - * and current uniform values used for caching and preventing undeed GPU commands - * @type {Object} - */ - this.uniformData = uniformData; - - /** - * uniformGroups holds the various upload functions for the shader. Each uniform group - * and program have a unique upload function generated. - * @type {Object} - */ - this.uniformGroups = {}; - } - - /** - * Destroys this program - * TODO - */ - destroy() - { - this.uniformData = null; - this.uniformGroups = null; - this.program = null; - } -} diff --git a/packages/core/src/renderers/systems/shader/ShaderSystem.js b/packages/core/src/renderers/systems/shader/ShaderSystem.js deleted file mode 100644 index 6cf57b6..0000000 --- a/packages/core/src/renderers/systems/shader/ShaderSystem.js +++ /dev/null @@ -1,170 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; -import GLProgram from './GLProgram'; -import generateUniformsSync from '../../../shader/generateUniformsSync'; -import defaultValue from '../../../shader/defaultValue'; -import compileProgram from './shader/compileProgram'; - -let UID = 0; - -/** - * Helper class to create a webGL Texture - * - * @class - * @memberof PIXI - */ -export default class ShaderSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - A reference to the current renderer - */ - constructor(renderer) - { - super(renderer); - - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = null; - - this.shader = null; - this.program = null; - - this.id = UID++; - } - - contextChange(gl) - { - this.gl = gl; - } - - /** - * Changes the current shader to the one given in parameter - * - * @param {PIXI.Shader} shader - the new shader - * @param {boolean} dontSync - false if the shader should automatically sync its uniforms. - * @returns {PIXI.glCore.glProgram} the glProgram that belongs to the shader. - */ - bind(shader, dontSync) - { - shader.uniforms.globals = this.renderer.globalUniforms; - - const program = shader.program; - const glProgram = program.glPrograms[this.renderer.CONTEXT_UID] || this.generateShader(shader); - - this.shader = shader; - - // TODO - some current pixi plugins bypass this.. so it not safe to use yet.. - if (this.program !== program) - { - this.program = program; - this.gl.useProgram(glProgram.program); - } - - if (!dontSync) - { - this.syncUniformGroup(shader.uniformGroup); - } - - return glProgram; - } - - /** - * Uploads the uniforms values to the currently bound shader. - * - * @param {object} uniforms - the uniforms valiues that be applied to the current shader - */ - setUniforms(uniforms) - { - const shader = this.shader.program; - const glProgram = shader.glPrograms[this.renderer.CONTEXT_UID]; - - shader.syncUniforms(glProgram.uniformData, uniforms, this.renderer); - } - - syncUniformGroup(group) - { - const glProgram = this.getglProgram(); - - if (!group.static || group.dirtyId !== glProgram.uniformGroups[group.id]) - { - glProgram.uniformGroups[group.id] = group.dirtyId; - const syncFunc = group.syncUniforms[this.shader.program.id] || this.createSyncGroups(group); - - syncFunc(glProgram.uniformData, group.uniforms, this.renderer); - } - } - - createSyncGroups(group) - { - group.syncUniforms[this.shader.program.id] = generateUniformsSync(group, this.shader.program.uniformData); - - return group.syncUniforms[this.shader.program.id]; - } - - /** - * Returns the underlying GLShade rof the currently bound shader. - * This can be handy for when you to have a little more control over the setting of your uniforms. - * - * @return {PIXI.glCore.Shader} the glProgram for the currently bound Shader for this context - */ - getglProgram() - { - if (this.shader) - { - return this.shader.program.glPrograms[this.renderer.CONTEXT_UID]; - } - - return null; - } - - /** - * Generates a glProgram verion of the Shader provided. - * - * @private - * @param {PIXI.Shader} shader the shader that the glProgram will be based on. - * @return {PIXI.glCore.glProgram} A shiney new glProgram - */ - generateShader(shader) - { - const gl = this.gl; - - const program = shader.program; - - const attribMap = {}; - - for (const i in program.attributeData) - { - attribMap[i] = program.attributeData[i].location; - } - - const shaderProgram = compileProgram(gl, program.vertexSrc, program.fragmentSrc, attribMap); - const uniformData = {}; - - for (const i in program.uniformData) - { - const data = program.uniformData[i]; - - uniformData[i] = { - location: gl.getUniformLocation(shaderProgram, i), - value: defaultValue(data.type, data.size), - }; - } - - const glProgram = new GLProgram(shaderProgram, uniformData); - - program.glPrograms[this.renderer.CONTEXT_UID] = glProgram; - - return glProgram; - } - - /** - * Destroys this System and removes all its textures - */ - destroy() - { - // TODO implement destroy method for ShaderSystem - this.destroyed = true; - } -} diff --git a/packages/core/src/renderers/systems/shader/shader/compileProgram.js b/packages/core/src/renderers/systems/shader/shader/compileProgram.js deleted file mode 100644 index 8c6ccb9..0000000 --- a/packages/core/src/renderers/systems/shader/shader/compileProgram.js +++ /dev/null @@ -1,78 +0,0 @@ -/** - * @method compileProgram - * @memberof PIXI.glCore.shader - * @param gl {WebGLRenderingContext} The current WebGL context {WebGLProgram} - * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. - * @param fragmentSrc {string|string[]} The fragment shader source as an array of strings. - * @param attributeLocations {Object} An attribute location map that lets you manually set the attribute locations - * @return {WebGLProgram} the shader program - */ -export default function compileProgram(gl, vertexSrc, fragmentSrc, attributeLocations) -{ - const glVertShader = compileShader(gl, gl.VERTEX_SHADER, vertexSrc); - const glFragShader = compileShader(gl, gl.FRAGMENT_SHADER, fragmentSrc); - - let program = gl.createProgram(); - - gl.attachShader(program, glVertShader); - gl.attachShader(program, glFragShader); - - // optionally, set the attributes manually for the program rather than letting WebGL decide.. - if (attributeLocations) - { - for (const i in attributeLocations) - { - gl.bindAttribLocation(program, attributeLocations[i], i); - } - } - - gl.linkProgram(program); - - // if linking fails, then log and cleanup - if (!gl.getProgramParameter(program, gl.LINK_STATUS)) - { - console.error('Pixi.js Error: Could not initialize shader.'); - console.error('gl.VALIDATE_STATUS', gl.getProgramParameter(program, gl.VALIDATE_STATUS)); - console.error('gl.getError()', gl.getError()); - - // if there is a program info log, log it - if (gl.getProgramInfoLog(program) !== '') - { - console.warn('Pixi.js Warning: gl.getProgramInfoLog()', gl.getProgramInfoLog(program)); - } - - gl.deleteProgram(program); - program = null; - } - - // clean up some shaders - gl.deleteShader(glVertShader); - gl.deleteShader(glFragShader); - - return program; -} - -/** - * @private - * @param gl {WebGLRenderingContext} The current WebGL context {WebGLProgram} - * @param type {Number} the type, can be either VERTEX_SHADER or FRAGMENT_SHADER - * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. - * @return {WebGLShader} the shader - */ -function compileShader(gl, type, src) -{ - const shader = gl.createShader(type); - - gl.shaderSource(shader, src); - gl.compileShader(shader); - - if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) - { - console.warn(src); - console.error(gl.getShaderInfoLog(shader)); - - return null; - } - - return shader; -} diff --git a/packages/core/src/renderers/systems/shader/shader/defaultValue.js b/packages/core/src/renderers/systems/shader/shader/defaultValue.js deleted file mode 100644 index 97290bd..0000000 --- a/packages/core/src/renderers/systems/shader/shader/defaultValue.js +++ /dev/null @@ -1,79 +0,0 @@ -/** - * @method defaultValue - * @memberof PIXI.glCore.shader - * @param type {String} Type of value - * @param size {Number} - */ -export default function defaultValue(type, size) -{ - switch (type) - { - case 'float': - return 0; - - case 'vec2': - return new Float32Array(2 * size); - - case 'vec3': - return new Float32Array(3 * size); - - case 'vec4': - return new Float32Array(4 * size); - - case 'int': - case 'sampler2D': - case 'sampler2DArray': - return 0; - - case 'ivec2': - return new Int32Array(2 * size); - - case 'ivec3': - return new Int32Array(3 * size); - - case 'ivec4': - return new Int32Array(4 * size); - - case 'bool': - return false; - - case 'bvec2': - - return booleanArray(2 * size); - - case 'bvec3': - return booleanArray(3 * size); - - case 'bvec4': - return booleanArray(4 * size); - - case 'mat2': - return new Float32Array([1, 0, - 0, 1]); - - case 'mat3': - return new Float32Array([1, 0, 0, - 0, 1, 0, - 0, 0, 1]); - - case 'mat4': - return new Float32Array([1, 0, 0, 0, - 0, 1, 0, 0, - 0, 0, 1, 0, - 0, 0, 0, 1]); - } - - return null; -} - -function booleanArray(size) -{ - const array = new Array(size); - - for (let i = 0; i < array.length; i++) - { - array[i] = false; - } - - return array; -} diff --git a/packages/core/src/renderers/systems/shader/shader/index.js b/packages/core/src/renderers/systems/shader/shader/index.js deleted file mode 100644 index ba7a317..0000000 --- a/packages/core/src/renderers/systems/shader/shader/index.js +++ /dev/null @@ -1,8 +0,0 @@ -/** - * @namespace PIXI.glCore.shader - */ -export { default as compileProgram } from './compileProgram'; -export { default as defaultValue } from './defaultValue'; -export { default as setPrecision } from './setPrecision'; -export { default as mapSize } from './mapSize'; -export { default as mapType } from './mapType'; diff --git a/packages/core/src/renderers/systems/shader/shader/mapSize.js b/packages/core/src/renderers/systems/shader/shader/mapSize.js deleted file mode 100644 index b681554..0000000 --- a/packages/core/src/renderers/systems/shader/shader/mapSize.js +++ /dev/null @@ -1,33 +0,0 @@ -const GLSL_TO_SIZE = { - float: 1, - vec2: 2, - vec3: 3, - vec4: 4, - - int: 1, - ivec2: 2, - ivec3: 3, - ivec4: 4, - - bool: 1, - bvec2: 2, - bvec3: 3, - bvec4: 4, - - mat2: 4, - mat3: 9, - mat4: 16, - - sampler2D: 1, -}; - -/** - * @method mapSize - * @memberof PIXI.glCore.shader - * @param type {String} - * @return {Number} - */ -export default function mapSize(type) -{ - return GLSL_TO_SIZE[type]; -} diff --git a/packages/core/src/renderers/systems/shader/shader/mapType.js b/packages/core/src/renderers/systems/shader/shader/mapType.js deleted file mode 100644 index d288b7c..0000000 --- a/packages/core/src/renderers/systems/shader/shader/mapType.js +++ /dev/null @@ -1,45 +0,0 @@ -let GL_TABLE = null; - -const GL_TO_GLSL_TYPES = { - FLOAT: 'float', - FLOAT_VEC2: 'vec2', - FLOAT_VEC3: 'vec3', - FLOAT_VEC4: 'vec4', - - INT: 'int', - INT_VEC2: 'ivec2', - INT_VEC3: 'ivec3', - INT_VEC4: 'ivec4', - - BOOL: 'bool', - BOOL_VEC2: 'bvec2', - BOOL_VEC3: 'bvec3', - BOOL_VEC4: 'bvec4', - - FLOAT_MAT2: 'mat2', - FLOAT_MAT3: 'mat3', - FLOAT_MAT4: 'mat4', - - SAMPLER_2D: 'sampler2D', - SAMPLER_CUBE: 'samplerCube', - SAMPLER_2D_ARRAY: 'sampler2DArray', -}; - -export default function mapType(gl, type) -{ - if (!GL_TABLE) - { - const typeNames = Object.keys(GL_TO_GLSL_TYPES); - - GL_TABLE = {}; - - for (let i = 0; i < typeNames.length; ++i) - { - const tn = typeNames[i]; - - GL_TABLE[gl[tn]] = GL_TO_GLSL_TYPES[tn]; - } - } - - return GL_TABLE[type]; -} diff --git a/packages/core/src/renderers/systems/shader/shader/setPrecision.js b/packages/core/src/renderers/systems/shader/shader/setPrecision.js deleted file mode 100644 index e2c1f34..0000000 --- a/packages/core/src/renderers/systems/shader/shader/setPrecision.js +++ /dev/null @@ -1,18 +0,0 @@ -/** - * Sets the float precision on the shader. If the precision is already present this function will do nothing - * @param {string} src the shader source - * @param {string} precision The float precision of the shader. Options are 'lowp', 'mediump' or 'highp'. - * - * @return {string} modified shader source - */ -export default function setPrecision(src, precision) -{ - src = src.trim(); - - if (src.substring(0, 9) !== 'precision' && src.substring(0, 1) !== '#') - { - return `precision ${precision} float;\n${src}`; - } - - return src; -} diff --git a/packages/core/src/renderers/systems/textures/GLTexture.js b/packages/core/src/renderers/systems/textures/GLTexture.js deleted file mode 100644 index c3fb8dc..0000000 --- a/packages/core/src/renderers/systems/textures/GLTexture.js +++ /dev/null @@ -1,33 +0,0 @@ -export default class GLTexture -{ - constructor(texture) - { - /** - * The WebGL texture - * - * @member {WebGLTexture} - */ - this.texture = texture; - - this.width = -1; - this.height = -1; - - /** - * Texture contents dirty flag - * @member {number} - */ - this.dirtyId = -1; - - /** - * Texture style dirty flag - * @type {number} - */ - this.dirtyStyleId = -1; - - /** - * Whether mip levels has to be generated - * @type {boolean} - */ - this.mipmap = false; - } -} diff --git a/packages/core/src/renderers/systems/textures/TextureGCSystem.js b/packages/core/src/renderers/systems/textures/TextureGCSystem.js deleted file mode 100644 index b089938..0000000 --- a/packages/core/src/renderers/systems/textures/TextureGCSystem.js +++ /dev/null @@ -1,110 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; -import { GC_MODES } from '@pixi/constants'; -import { settings } from '@pixi/settings'; - -/** - * TextureGarbageCollector. This class manages the GPU and ensures that it does not get clogged - * up with textures that are no longer being used. - * - * @class - * @memberof PIXI - */ -export default class TextureGCSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this.count = 0; - this.checkCount = 0; - this.maxIdle = settings.GC_MAX_IDLE; - this.checkCountMax = settings.GC_MAX_CHECK_COUNT; - this.mode = settings.GC_MODE; - } - - /** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ - postrender() - { - this.count++; - - if (this.mode === GC_MODES.MANUAL) - { - return; - } - - this.checkCount++; - - if (this.checkCount > this.checkCountMax) - { - this.checkCount = 0; - - this.run(); - } - } - - /** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ - run() - { - const tm = this.renderer.texture; - const managedTextures = tm.managedTextures; - let wasRemoved = false; - - for (let i = 0; i < managedTextures.length; i++) - { - const texture = managedTextures[i]; - - // only supports non generated textures at the moment! - if (!texture.frameBuffer && this.count - texture.touched > this.maxIdle) - { - tm.destroyTexture(texture, true); - managedTextures[i] = null; - wasRemoved = true; - } - } - - if (wasRemoved) - { - let j = 0; - - for (let i = 0; i < managedTextures.length; i++) - { - if (managedTextures[i] !== null) - { - managedTextures[j++] = managedTextures[i]; - } - } - - managedTextures.length = j; - } - } - - /** - * Removes all the textures within the specified displayObject and its children from the GPU - * - * @param {PIXI.DisplayObject} displayObject - the displayObject to remove the textures from. - */ - unload(displayObject) - { - const tm = this.renderer.textureSystem; - - // only destroy non generated textures - if (displayObject._texture && displayObject._texture._glRenderTargets) - { - tm.destroyTexture(displayObject._texture); - } - - for (let i = displayObject.children.length - 1; i >= 0; i--) - { - this.unload(displayObject.children[i]); - } - } -} diff --git a/packages/core/src/renderers/systems/textures/TextureSystem.js b/packages/core/src/renderers/systems/textures/TextureSystem.js deleted file mode 100644 index ccc5de9..0000000 --- a/packages/core/src/renderers/systems/textures/TextureSystem.js +++ /dev/null @@ -1,281 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; -import GLTexture from './GLTexture'; -import { removeItems } from '@pixi/utils'; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class TextureSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - // TODO set to max textures... - this.boundTextures = [ - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - ]; - - this.currentLocation = -1; - - this.managedTextures = []; - } - - /** - * Sets up the renderer context and necessary buffers. - * - * @private - */ - contextChange() - { - const gl = this.gl = this.renderer.gl; - - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - - // TODO move this.. to a nice make empty textures class.. - this.emptyTextures = {}; - - const emptyTexture2D = new GLTexture(gl.createTexture()); - - gl.bindTexture(gl.TEXTURE_2D, emptyTexture2D.texture); - gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array(4)); - - this.emptyTextures[gl.TEXTURE_2D] = emptyTexture2D; - this.emptyTextures[gl.TEXTURE_CUBE_MAP] = new GLTexture(gl.createTexture()); - - gl.bindTexture(gl.TEXTURE_CUBE_MAP, this.emptyTextures[gl.TEXTURE_CUBE_MAP].texture); - - let i; - - for (i = 0; i < 6; i++) - { - gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, null); - } - - gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_MAG_FILTER, gl.LINEAR); - gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_MIN_FILTER, gl.LINEAR); - - for (i = 0; i < this.boundTextures.length; i++) - { - this.bind(null, i); - } - } - - bind(texture, location) - { - const gl = this.gl; - - location = location || 0; - - if (this.currentLocation !== location) - { - this.currentLocation = location; - gl.activeTexture(gl.TEXTURE0 + location); - } - - if (texture) - { - texture = texture.baseTexture || texture; - - if (texture.valid) - { - texture.touched = this.renderer.textureGC.count; - - const glTexture = texture._glTextures[this.CONTEXT_UID] || this.initTexture(texture); - - gl.bindTexture(texture.target, glTexture.texture); - - if (glTexture.dirtyId !== texture.dirtyId) - { - this.updateTexture(texture); - } - - this.boundTextures[location] = texture; - } - } - else - { - gl.bindTexture(gl.TEXTURE_2D, this.emptyTextures[gl.TEXTURE_2D].texture); - this.boundTextures[location] = null; - } - } - - unbind(texture) - { - const gl = this.gl; - - for (let i = 0; i < this.boundTextures.length; i++) - { - if (this.boundTextures[i] === texture) - { - if (this.currentLocation !== i) - { - gl.activeTexture(gl.TEXTURE0 + i); - this.currentLocation = i; - } - - gl.bindTexture(gl.TEXTURE_2D, this.emptyTextures[texture.target].texture); - this.boundTextures[i] = null; - } - } - } - - initTexture(texture) - { - const glTexture = new GLTexture(this.gl.createTexture()); - - // guarentee an update.. - glTexture.dirtyId = -1; - - texture._glTextures[this.CONTEXT_UID] = glTexture; - - this.managedTextures.push(texture); - texture.on('dispose', this.destroyTexture, this); - - return glTexture; - } - - updateTexture(texture) - { - const glTexture = texture._glTextures[this.CONTEXT_UID]; - const renderer = this.renderer; - - if (texture.resource && texture.resource.upload(renderer, texture, glTexture)) - { - // texture is uploaded, dont do anything! - } - else - { - // default, renderTexture-like logic - const width = texture.realWidth; - const height = texture.realHeight; - const gl = renderer.gl; - - if (glTexture.width !== width - || glTexture.height !== height - || glTexture.dirtyId < 0) - { - glTexture.width = width; - glTexture.height = height; - - gl.texImage2D(texture.target, 0, - texture.format, - width, - height, - 0, - texture.format, - texture.type, - null); - } - } - - // lets only update what changes.. - if (texture.dirtyStyleId !== glTexture.dirtyStyleId) - { - this.updateTextureStyle(texture); - } - glTexture.dirtyId = texture.dirtyId; - } - - /** - * Deletes the texture from WebGL - * - * @param {PIXI.BaseTexture|PIXI.Texture} texture - the texture to destroy - * @param {boolean} [skipRemove=false] - Whether to skip removing the texture from the TextureManager. - */ - destroyTexture(texture, skipRemove) - { - const gl = this.gl; - - texture = texture.baseTexture || texture; - - if (texture._glTextures[this.renderer.CONTEXT_UID]) - { - this.unbind(texture); - - gl.deleteTexture(texture._glTextures[this.renderer.CONTEXT_UID].texture); - texture.off('dispose', this.destroyTexture, this); - - delete texture._glTextures[this.renderer.CONTEXT_UID]; - - if (!skipRemove) - { - const i = this.managedTextures.indexOf(texture); - - if (i !== -1) - { - removeItems(this.managedTextures, i, 1); - } - } - } - } - - updateTextureStyle(texture) - { - const glTexture = texture._glTextures[this.CONTEXT_UID]; - - glTexture.mipmap = texture.mipmap && texture.isPowerOfTwo; - if (!glTexture) - { - return; - } - - if (texture.resource && texture.resource.style(this.renderer, texture, glTexture)) - { - // style is set, dont do anything! - } - else - { - this.setStyle(texture, glTexture); - } - - glTexture.dirtyStyleId = texture.dirtyStyleId; - } - - setStyle(texture, glTexture) - { - const gl = this.gl; - - if (glTexture.mipmap) - { - gl.generateMipmap(texture.target); - } - - gl.texParameteri(texture.target, gl.TEXTURE_WRAP_S, texture.wrapMode); - gl.texParameteri(texture.target, gl.TEXTURE_WRAP_T, texture.wrapMode); - - if (glTexture.mipmap) - { - /* eslint-disable max-len */ - gl.texParameteri(texture.target, gl.TEXTURE_MIN_FILTER, texture.scaleMode ? gl.LINEAR_MIPMAP_LINEAR : gl.NEAREST_MIPMAP_NEAREST); - /* eslint-disable max-len */ - } - else - { - gl.texParameteri(texture.target, gl.TEXTURE_MIN_FILTER, texture.scaleMode ? gl.LINEAR : gl.NEAREST); - } - - gl.texParameteri(texture.target, gl.TEXTURE_MAG_FILTER, texture.scaleMode ? gl.LINEAR : gl.NEAREST); - } -} diff --git a/packages/core/src/renderers/utils/ObjectRenderer.js b/packages/core/src/renderers/utils/ObjectRenderer.js deleted file mode 100644 index f67a273..0000000 --- a/packages/core/src/renderers/utils/ObjectRenderer.js +++ /dev/null @@ -1,48 +0,0 @@ -import WebGLSystem from '../systems/WebGLSystem'; - -/** - * Base for a common object renderer that can be used as a system renderer plugin. - * - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class ObjectRenderer extends WebGLSystem -{ - /** - * Starts the renderer and sets the shader - * - */ - start() - { - // set the shader.. - } - - /** - * Stops the renderer - * - */ - stop() - { - this.flush(); - } - - /** - * Stub method for rendering content and emptying the current batch. - * - */ - flush() - { - // flush! - } - - /** - * Renders an object - * - * @param {PIXI.DisplayObject} object - The object to render. - */ - render(object) // eslint-disable-line no-unused-vars - { - // render the object - } -} diff --git a/packages/core/src/renderers/utils/Quad.js b/packages/core/src/renderers/utils/Quad.js deleted file mode 100644 index 5c1517b..0000000 --- a/packages/core/src/renderers/utils/Quad.js +++ /dev/null @@ -1,26 +0,0 @@ -import Geometry from '../../geometry/Geometry'; - -/** - * Helper class to create a quad - * - * @class - * @memberof PIXI - */ -export default class Quad extends Geometry -{ - /** - * just a quad - */ - constructor() - { - super(); - - this.addAttribute('aVertexPosition', [ - 0, 0, - 1, 0, - 1, 1, - 0, 1, - ]) - .addIndex([0, 1, 3, 2]); - } -} diff --git a/packages/core/src/renderers/utils/QuadUv.js b/packages/core/src/renderers/utils/QuadUv.js deleted file mode 100644 index 97bda89..0000000 --- a/packages/core/src/renderers/utils/QuadUv.js +++ /dev/null @@ -1,103 +0,0 @@ -import Geometry from '../../geometry/Geometry'; -import Buffer from '../../geometry/Buffer'; - -/** - * Helper class to create a quad with uvs like in v4 - * - * @class - * @memberof PIXI - */ -export default class QuadUv extends Geometry -{ - constructor() - { - super(); - - /** - * An array of vertices - * - * @member {Float32Array} - */ - this.vertices = new Float32Array([ - -1, -1, - 1, -1, - 1, 1, - -1, 1, - ]); - - /** - * The Uvs of the quad - * - * @member {Float32Array} - */ - this.uvs = new Float32Array([ - 0, 0, - 1, 0, - 1, 1, - 0, 1, - ]); - - this.vertexBuffer = new Buffer(this.vertices); - this.uvBuffer = new Buffer(this.uvs); - - this.addAttribute('aVertexPosition', this.vertexBuffer) - .addAttribute('aTextureCoord', this.uvBuffer) - .addIndex([0, 1, 2, 0, 2, 3]); - } - - /** - * Maps two Rectangle to the quad. - * - * @param {PIXI.Rectangle} targetTextureFrame - the first rectangle - * @param {PIXI.Rectangle} destinationFrame - the second rectangle - * @return {PIXI.Quad} Returns itself. - */ - map(targetTextureFrame, destinationFrame) - { - let x = 0; // destinationFrame.x / targetTextureFrame.width; - let y = 0; // destinationFrame.y / targetTextureFrame.height; - - this.uvs[0] = x; - this.uvs[1] = y; - - this.uvs[2] = x + (destinationFrame.width / targetTextureFrame.width); - this.uvs[3] = y; - - this.uvs[4] = x + (destinationFrame.width / targetTextureFrame.width); - this.uvs[5] = y + (destinationFrame.height / targetTextureFrame.height); - - this.uvs[6] = x; - this.uvs[7] = y + (destinationFrame.height / targetTextureFrame.height); - - x = destinationFrame.x; - y = destinationFrame.y; - - this.vertices[0] = x; - this.vertices[1] = y; - - this.vertices[2] = x + destinationFrame.width; - this.vertices[3] = y; - - this.vertices[4] = x + destinationFrame.width; - this.vertices[5] = y + destinationFrame.height; - - this.vertices[6] = x; - this.vertices[7] = y + destinationFrame.height; - - this.invalidate(); - - return this; - } - - /** - * legacy upload method, just marks buffers dirty - * @returns {PIXI.QuadUv} Returns itself. - */ - invalidate() - { - this.vertexBuffer._updateID++; - this.uvBuffer._updateID++; - - return this; - } -} diff --git a/packages/core/src/renderers/utils/checkMaxIfStatmentsInShader.js b/packages/core/src/renderers/utils/checkMaxIfStatmentsInShader.js deleted file mode 100644 index 8047c96..0000000 --- a/packages/core/src/renderers/utils/checkMaxIfStatmentsInShader.js +++ /dev/null @@ -1,58 +0,0 @@ -const fragTemplate = [ - 'precision mediump float;', - 'void main(void){', - 'float test = 0.1;', - '%forloop%', - 'gl_FragColor = vec4(0.0);', - '}', -].join('\n'); - -export default function checkMaxIfStatmentsInShader(maxIfs, gl) -{ - if (maxIfs === 0) - { - throw new Error('Invalid value of `0` passed to `checkMaxIfStatementsInShader`'); - } - - const shader = gl.createShader(gl.FRAGMENT_SHADER); - - while (true) // eslint-disable-line no-constant-condition - { - const fragmentSrc = fragTemplate.replace(/%forloop%/gi, generateIfTestSrc(maxIfs)); - - gl.shaderSource(shader, fragmentSrc); - gl.compileShader(shader); - - if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) - { - maxIfs = (maxIfs / 2) | 0; - } - else - { - // valid! - break; - } - } - - return maxIfs; -} - -function generateIfTestSrc(maxIfs) -{ - let src = ''; - - for (let i = 0; i < maxIfs; ++i) - { - if (i > 0) - { - src += '\nelse '; - } - - if (i < maxIfs - 1) - { - src += `if(test == ${i}.0){}`; - } - } - - return src; -} diff --git a/packages/core/src/renderers/utils/mapWebGLBlendModesToPixi.js b/packages/core/src/renderers/utils/mapWebGLBlendModesToPixi.js deleted file mode 100644 index 10ffe4d..0000000 --- a/packages/core/src/renderers/utils/mapWebGLBlendModesToPixi.js +++ /dev/null @@ -1,47 +0,0 @@ -import { BLEND_MODES } from '@pixi/constants'; - -/** - * Maps gl blend combinations to WebGL. - * - * @memberof PIXI - * @function mapWebGLBlendModesToPixi - * @private - * @param {WebGLRenderingContext} gl - The rendering context. - * @param {string[]} [array=[]] - The array to output into. - * @return {string[]} Mapped modes. - */ -export default function mapWebGLBlendModesToPixi(gl, array = []) -{ - // TODO - premultiply alpha would be different. - // add a boolean for that! - array[BLEND_MODES.NORMAL] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.ADD] = [gl.ONE, gl.DST_ALPHA]; - array[BLEND_MODES.MULTIPLY] = [gl.DST_COLOR, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.SCREEN] = [gl.ONE, gl.ONE_MINUS_SRC_COLOR]; - array[BLEND_MODES.OVERLAY] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.DARKEN] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.LIGHTEN] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.COLOR_DODGE] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.COLOR_BURN] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.HARD_LIGHT] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.SOFT_LIGHT] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.DIFFERENCE] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.EXCLUSION] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.HUE] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.SATURATION] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.COLOR] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.LUMINOSITY] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.NONE] = [0, 0]; - - // not-premultiplied blend modes - array[BLEND_MODES.NORMAL_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.ADD_NPM] = [gl.SRC_ALPHA, gl.DST_ALPHA, gl.ONE, gl.DST_ALPHA]; - array[BLEND_MODES.SCREEN_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_COLOR, gl.ONE, gl.ONE_MINUS_SRC_COLOR]; - - // not-premultiplied blend modes - array[BLEND_MODES.NORMAL_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.ADD_NPM] = [gl.SRC_ALPHA, gl.DST_ALPHA, gl.ONE, gl.DST_ALPHA]; - array[BLEND_MODES.SCREEN_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_COLOR, gl.ONE, gl.ONE_MINUS_SRC_COLOR]; - - return array; -} diff --git a/packages/core/src/AbstractRenderer.js b/packages/core/src/AbstractRenderer.js new file mode 100644 index 0000000..64b2eb1 --- /dev/null +++ b/packages/core/src/AbstractRenderer.js @@ -0,0 +1,353 @@ +import { hex2string, hex2rgb } from '@pixi/utils'; +import { Matrix, Rectangle } from '@pixi/math'; +import { RENDERER_TYPE } from '@pixi/constants'; +import { settings } from '@pixi/settings'; +import { Container } from '@pixi/display'; +import RenderTexture from './renderTexture/RenderTexture'; +import EventEmitter from 'eventemitter3'; + +const tempMatrix = new Matrix(); + +/** + * 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 EventEmitter + * @memberof PIXI + */ +export default class AbstractRenderer extends EventEmitter +{ + // eslint-disable-next-line valid-jsdoc + /** + * @param {string} system - The name of the system this renderer is for. + * @param {object} [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.transparent=false] - If the render view is transparent, default false + * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false + * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. The + * resolution of the renderer retina would be 2. + * @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 {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when rendering, + * stopping pixel interpolation. + */ + constructor(system, options, arg2, arg3) + { + super(); + + // Support for constructor(system, screenWidth, screenHeight, options) + if (typeof options === 'number') + { + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + }, arg3); + } + + // 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 = RENDERER_TYPE.UNKNOWN; + + /** + * Measurements of the screen. (0, 0, screenWidth, screenHeight) + * + * Its safe to use as filterArea or hitArea for 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 1 + */ + this.resolution = options.resolution || settings.RESOLUTION; + + /** + * Whether the render view is transparent + * + * @member {boolean} + */ + this.transparent = options.transparent; + + /** + * Whether css dimensions of canvas view should be resized to screen dimensions automatically + * + * @member {boolean} + */ + this.autoResize = options.autoResize || false; + + /** + * Tracks the blend modes useful for this renderer. + * + * @member {object} + */ + this.blendModes = null; + + /** + * 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; + + /** + * If true PixiJS will Math.floor() x/y values when rendering, stopping pixel interpolation. + * Handy for crisp pixel art and speed on legacy devices. + * + * @member {boolean} + */ + this.roundPixels = options.roundPixels; + + /** + * The background color as a number. + * + * @member {number} + * @private + */ + this._backgroundColor = 0x000000; + + /** + * The background color as an [R, G, B] array. + * + * @member {number[]} + * @private + */ + this._backgroundColorRgba = [0, 0, 0, 0]; + + /** + * The background color as a string. + * + * @member {string} + * @private + */ + this._backgroundColorString = '#000000'; + + this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter + + /** + * This temporary display object used as the parent of the currently being rendered item + * + * @member {PIXI.DisplayObject} + * @private + */ + this._tempDisplayObjectParent = new Container(); + + /** + * The last root object that the renderer tried to render. + * + * @member {PIXI.DisplayObject} + * @private + */ + this._lastObjectRendered = this._tempDisplayObjectParent; + + /** + * Collection of plugins. + * @readonly + * @member {object} + */ + this.plugins = {}; + } + + /** + * Initialize the plugins. + * + * @protected + * @param {object} staticMap - The dictionary of staticly saved plugins. + */ + initPlugins(staticMap) + { + 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() + { + return this.view.width; + } + + /** + * Same as view.height, actual number of pixels in the canvas by vertical + * + * @member {number} + * @readonly + * @default 600 + */ + get height() + { + return this.view.height; + } + + /** + * Resizes the screen and canvas to the specified width and height + * Canvas dimensions are multiplied by resolution + * + * @param {number} screenWidth - the new width of the screen + * @param {number} screenHeight - the new height of the screen + */ + resize(screenWidth, screenHeight) + { + this.screen.width = screenWidth; + this.screen.height = screenHeight; + + this.view.width = screenWidth * this.resolution; + this.view.height = screenHeight * this.resolution; + + if (this.autoResize) + { + this.view.style.width = `${screenWidth}px`; + this.view.style.height = `${screenHeight}px`; + } + } + + /** + * 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 {PIXI.DisplayObject} displayObject - The displayObject the object will be generated from + * @param {number} scaleMode - Should be one of the scaleMode consts + * @param {number} resolution - The resolution / device pixel ratio of the texture being generated + * @param {PIXI.Rectangle} [region] - The region of the displayObject, that shall be rendered, + * if no region is specified, defaults to the local bounds of the displayObject. + * @return {PIXI.Texture} a texture of the graphics object + */ + generateTexture(displayObject, scaleMode, resolution, region) + { + region = region || displayObject.getLocalBounds(); + + // 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(region.width | 0, region.height | 0, scaleMode, resolution); + + tempMatrix.tx = -region.x; + tempMatrix.ty = -region.y; + + this.render(displayObject, renderTexture, false, tempMatrix, true); + + return renderTexture; + } + + /** + * Removes everything from the renderer and optionally removes the Canvas DOM element. + * + * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. + */ + destroy(removeView) + { + 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); + } + + this.plugins = null; + + this.type = RENDERER_TYPE.UNKNOWN; + + this.view = null; + + this.screen = null; + + this.resolution = 0; + + this.transparent = false; + + this.autoResize = false; + + this.blendModes = null; + + this.options = null; + + this.preserveDrawingBuffer = false; + this.clearBeforeRender = false; + + this.roundPixels = false; + + this._backgroundColor = 0; + this._backgroundColorRgba = null; + this._backgroundColorString = null; + + this._tempDisplayObjectParent = null; + this._lastObjectRendered = null; + } + + /** + * The background color to fill if not transparent + * + * @member {number} + */ + get backgroundColor() + { + return this._backgroundColor; + } + + set backgroundColor(value) // eslint-disable-line require-jsdoc + { + this._backgroundColor = value; + this._backgroundColorString = hex2string(value); + hex2rgb(value, this._backgroundColorRgba); + } +} diff --git a/packages/core/src/Renderer.js b/packages/core/src/Renderer.js new file mode 100644 index 0000000..2227e78 --- /dev/null +++ b/packages/core/src/Renderer.js @@ -0,0 +1,338 @@ +import AbstractRenderer from './AbstractRenderer'; +import { sayHello } from '@pixi/utils'; +import MaskSystem from './mask/MaskSystem'; +import StencilSystem from './mask/StencilSystem'; +import FilterSystem from './filters/FilterSystem'; +import FramebufferSystem from './framebuffer/FramebufferSystem'; +import RenderTextureSystem from './renderTexture/RenderTextureSystem'; +import TextureSystem from './textures/TextureSystem'; +import ProjectionSystem from './projection/ProjectionSystem'; +import StateSystem from './state/StateSystem'; +import GeometrySystem from './geometry/GeometrySystem'; +import ShaderSystem from './shader/ShaderSystem'; +import ContextSystem from './context/ContextSystem'; +import BatchSystem from './batch/BatchSystem'; +import TextureGCSystem from './textures/TextureGCSystem'; +import { RENDERER_TYPE } from '@pixi/constants'; +import UniformGroup from './shader/UniformGroup'; +import { Matrix } from '@pixi/math'; +import Runner from 'mini-runner'; + +/** + * 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 Render works by automatically managing webGLBatchs. + * 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 :) + * + * @class + * @memberof PIXI + * @extends PIXI.AbstractRenderer + */ +export default class Renderer extends AbstractRenderer +{ + // eslint-disable-next-line valid-jsdoc + /** + * + * @param {object} [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.transparent=false] - If the render view is transparent, default false + * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false + * @param {boolean} [options.antialias=false] - sets antialias. If not available natively then FXAA + * antialiasing is used + * @param {boolean} [options.forceFXAA=false] - forces FXAA antialiasing to be used over native. + * FXAA is faster, but may not always look as great + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. + * The resolution of the renderer retina would be 2. + * @param {boolean} [options.clearBeforeRender=true] - This sets if the renderer will clear + * the canvas or not before the new render pass. If you wish to set this to false, you *must* set + * preserveDrawingBuffer to `true`. + * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, + * enable this if you need to call toDataUrl on the webgl context. + * @param {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when + * rendering, stopping pixel interpolation. + * @param {number} [options.backgroundColor=0x000000] - The background color of the rendered area + * (shown if not transparent). + * @param {boolean} [options.legacy=false] - If true PixiJS will aim to ensure compatibility + * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. + * @param {string} [options.powerPreference] - Parameter passed to webgl context, set to "high-performance" + * for devices with dual graphics card + */ + constructor(options, arg2, arg3) + { + super('WebGL', options, arg2, arg3); + + /** + * The type of this renderer as a standardised const + * + * @member {number} + * @see PIXI.RENDERER_TYPE + */ + this.type = RENDERER_TYPE.WEBGL; + + // this will be set by the contextSystem (this.context) + this.gl = null; + this.CONTEXT_UID = 0; + this.legacy = !!options.legacy; + + // TODO legacy! + + // runners! + this.runners = { + destroy: new Runner('destroy'), + contextChange: new Runner('contextChange', 1), + reset: new Runner('reset'), + update: new Runner('update'), + postrender: new Runner('postrender'), + prerender: new Runner('prerender'), + resize: new Runner('resize', 2), + }; + + 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(GeometrySystem, 'geometry') + .addSystem(FramebufferSystem, 'framebuffer') + .addSystem(StencilSystem, 'stencil') + .addSystem(ProjectionSystem, 'projection') + .addSystem(TextureGCSystem, 'textureGC') + .addSystem(FilterSystem, 'filter') + .addSystem(RenderTextureSystem, 'renderTexture') + .addSystem(BatchSystem, 'batch'); + + this.initPlugins(Renderer.__plugins); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ + if (options.context) + { + this.context.initFromContext(options.context); + } + else + { + this.context.initFromOptions({ + alpha: this.transparent, + antialias: options.antialias, + premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', + stencil: true, + 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); + } + + /** + * Add a new system to the renderer. + * @param {class} ClassRef - Class reference + * @param {string} [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. + * @return {PIXI.Renderer} Return instance of renderer + */ + addSystem(ClassRef, name) + { + if (!name) + { + name = ClassRef.name; + } + + // TODO - read name from class.name.. + + /* + if(name.includes('System')) + { + name = name.replace('System', ''); + name = name.charAt(0).toLowerCase() + name.slice(1); + } + */ + + const system = new ClassRef(this); + + if (this[name]) + { + throw new Error(`Whoops! ${name} is already a manger`); + } + + this[name] = system; + + for (const i in this.runners) + { + this.runners[i].add(system); + } + + return this; + + /** + * 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. + */ + } + + /** + * Renders the object to its webGL view + * + * @param {PIXI.DisplayObject} displayObject - the object to be rendered + * @param {PIXI.RenderTexture} renderTexture - The render texture to render to. + * @param {boolean} [clear] - Should the canvas be cleared before the new render + * @param {PIXI.Transform} [transform] - A transform to apply to the render texture before rendering. + * @param {boolean} [skipUpdateTransform] - Should we skip the update transform pass? + */ + render(displayObject, renderTexture, clear, transform, skipUpdateTransform) + { + // can be handy to know! + this.renderingToScreen = !renderTexture; + + this.runners.prerender.run(); + this.emit('prerender'); + + // 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.parent; + + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = 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.run(); + + this.emit('postrender'); + } + + /** + * Resizes the webGL view to the specified width and height. + * + * @param {number} screenWidth - the new width of the screen + * @param {number} screenHeight - the new height of the screen + */ + resize(screenWidth, screenHeight) + { + AbstractRenderer.prototype.resize.call(this, screenWidth, screenHeight); + + this.runners.resize.run(screenWidth, screenHeight); + } + + /** + * Resets the WebGL state so you can render things however you fancy! + * + * @return {PIXI.Renderer} Returns itself. + */ + reset() + { + this.runners.reset.run(); + + return this; + } + + clear() + { + this.framebuffer.bind(); + this.framebuffer.clear(); + } + + /** + * Removes everything from the renderer (event listeners, spritebatch, etc...) + * + * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. + * See: https://github.com/pixijs/pixi.js/issues/2233 + */ + destroy(removeView) + { + this.runners.destroy.run(); + + // call base destroy + super.destroy(removeView); + + // TODO nullify all the managers.. + this.gl = null; + } + + /** + * 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 + * builds and excluding plugins. + * @name PIXI.Renderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. + */ + + /** + * Adds a plugin to the renderer. + * + * @method + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + static registerPlugin(pluginName, ctor) + { + Renderer.__plugins = Renderer.__plugins || {}; + Renderer.__plugins[pluginName] = ctor; + } +} diff --git a/packages/core/src/System.js b/packages/core/src/System.js new file mode 100644 index 0000000..05a10c8 --- /dev/null +++ b/packages/core/src/System.js @@ -0,0 +1,42 @@ +/** + * System is a base class used for extending systems used by the {@link PIXI.Renderer} + * @see PIXI.Renderer#addSystem + * @class + * @memberof PIXI + */ +export default class System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + /** + * The renderer this manager works for. + * + * @member {PIXI.Renderer} + */ + this.renderer = renderer; + + this.renderer.runners.contextChange.add(this); + } + + /** + * Generic method called when there is a WebGL context change. + * + */ + contextChange() + { + // do some codes init! + } + + /** + * Generic destroy methods to be overridden by the subclass + * + */ + destroy() + { + this.renderer.runners.contextChange.remove(this); + this.renderer = null; + } +} diff --git a/packages/core/src/batch/BatchSystem.js b/packages/core/src/batch/BatchSystem.js new file mode 100644 index 0000000..b04c249 --- /dev/null +++ b/packages/core/src/batch/BatchSystem.js @@ -0,0 +1,68 @@ +import System from '../System'; +import ObjectRenderer from './ObjectRenderer'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class BatchSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + super(renderer); + + /** + * An empty renderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.emptyRenderer = new ObjectRenderer(renderer); + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.currentRenderer = this.emptyRenderer; + } + + /** + * Changes the current renderer to the one given in parameter + * + * @param {PIXI.ObjectRenderer} objectRenderer - The object renderer to use. + */ + setObjectRenderer(objectRenderer) + { + if (this.currentRenderer === objectRenderer) + { + return; + } + + this.currentRenderer.stop(); + this.currentRenderer = objectRenderer; + + this.renderer.state.setState(objectRenderer.state); + + this.currentRenderer.start(); + } + + /** + * This should be called if you wish to do some custom rendering + * It will basically render anything that may be batched up such as sprites + * + */ + flush() + { + this.setObjectRenderer(this.emptyRenderer); + } + + reset() + { + this.setObjectRenderer(this.emptyRenderer); + } +} diff --git a/packages/core/src/batch/ObjectRenderer.js b/packages/core/src/batch/ObjectRenderer.js new file mode 100644 index 0000000..0c851a9 --- /dev/null +++ b/packages/core/src/batch/ObjectRenderer.js @@ -0,0 +1,48 @@ +import System from '../System'; + +/** + * Base for a common object renderer that can be used as a system renderer plugin. + * + * @class + * @extends PIXI.System + * @memberof PIXI + */ +export default class ObjectRenderer extends System +{ + /** + * Starts the renderer and sets the shader + * + */ + start() + { + // set the shader.. + } + + /** + * Stops the renderer + * + */ + stop() + { + this.flush(); + } + + /** + * Stub method for rendering content and emptying the current batch. + * + */ + flush() + { + // flush! + } + + /** + * Renders an object + * + * @param {PIXI.DisplayObject} object - The object to render. + */ + render(object) // eslint-disable-line no-unused-vars + { + // render the object + } +} diff --git a/packages/core/src/context/ContextSystem.js b/packages/core/src/context/ContextSystem.js new file mode 100644 index 0000000..301aab4 --- /dev/null +++ b/packages/core/src/context/ContextSystem.js @@ -0,0 +1,186 @@ +import System from '../System'; +import { settings } from '@pixi/settings'; + +let CONTEXT_UID = 0; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class ContextSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.webGLVersion = 1; + + this.handleContextLost = this.handleContextLost.bind(this); + this.handleContextRestored = this.handleContextRestored.bind(this); + + this.extensions = {}; + + renderer.view.addEventListener('webglcontextlost', this.handleContextLost, false); + renderer.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + } + + get isLost() + { + return (!this.gl || this.gl.isContextLost()); + } + + contextChange(gl) + { + this.gl = gl; + + // restore a context if it was previously lost + if (gl.isContextLost() && gl.getExtension('WEBGL_lose_context')) + { + gl.getExtension('WEBGL_lose_context').restoreContext(); + } + } + + initFromContext(gl) + { + this.gl = gl; + this.validateContext(gl); + this.renderer.gl = gl; + this.renderer.CONTEXT_UID = CONTEXT_UID++; + this.renderer.runners.contextChange.run(gl); + } + + initFromOptions(options) + { + const gl = this.createContext(this.renderer.view, options); + + this.initFromContext(gl); + } + + /** + * Helper class to create a webGL Context + * + * @class + * @memberof PIXI.glCore + * @param canvas {HTMLCanvasElement} the canvas element that we will get the context from + * @param options {Object} An options object that gets passed in to the canvas element containing the context attributes, + * see https://developer.mozilla.org/en/docs/Web/API/HTMLCanvasElement/getContext for the options available + * @return {WebGLRenderingContext} the WebGL context + */ + createContext(canvas, options) + { + let gl; + + if (settings.PREFER_WEBGL_2) + { + gl = canvas.getContext('webgl2', options); + } + + if (gl) + { + this.webGLVersion = 2; + } + else + { + this.webGLVersion = 1; + + gl = canvas.getContext('webgl', options) + || canvas.getContext('experimental-webgl', options); + + if (!gl) + { + // fail, not able to get a context + throw new Error('This browser does not support webGL. Try using the canvas renderer'); + } + } + + this.gl = gl; + + this.getExtensions(); + + return gl; + } + + getExtensions() + { + // time to set up default etensions that pixi uses.. + const gl = this.gl; + const extensions = this.extensions; + + if (this.webGLVersion === 1) + { + extensions.drawBuffers = gl.getExtension('WEBGL_draw_buffers'); + extensions.depthTexture = gl.getExtension('WEBKIT_WEBGL_depth_texture'); + extensions.floatTexture = gl.getExtension('OES_texture_float'); + extensions.loseContext = gl.getExtension('WEBGL_lose_context'); + + extensions.vertexArrayObject = gl.getExtension('OES_vertex_array_object') + || gl.getExtension('MOZ_OES_vertex_array_object') + || gl.getExtension('WEBKIT_OES_vertex_array_object'); + } + + // we don't use any specific WebGL 2 ones yet! + } + + /** + * Handles a lost webgl context + * + * @private + * @param {WebGLContextEvent} event - The context lost event. + */ + handleContextLost(event) + { + event.preventDefault(); + } + + /** + * Handles a restored webgl context + * + * @private + */ + handleContextRestored() + { + this.renderer.runners.contextChange.run(this.gl); + } + + destroy() + { + const view = this.renderer.view; + + // remove listeners + view.removeEventListener('webglcontextlost', this.handleContextLost); + view.removeEventListener('webglcontextrestored', this.handleContextRestored); + + this.gl.useProgram(null); + + if (this.extensions.loseContext) + { + this.extensions.loseContext.loseContext(); + } + } + + postrender() + { + this.gl.flush(); + } + + validateContext(gl) + { + const attributes = gl.getContextAttributes(); + + // this is going to be fairly simple for now.. but at least we have room to grow! + if (!attributes.stencil) + { + /* eslint-disable max-len */ + + /* eslint-disable no-console */ + console.warn('Provided WebGL context does not have a stencil buffer, masks may not render correctly'); + /* eslint-enable no-console */ + + /* eslint-enable max-len */ + } + } +} diff --git a/packages/core/src/filters/Filter.js b/packages/core/src/filters/Filter.js new file mode 100644 index 0000000..9d8cad8 --- /dev/null +++ b/packages/core/src/filters/Filter.js @@ -0,0 +1,140 @@ +import Shader from '../shader/Shader'; +import Program from '../shader/Program'; +import State from '../state/State'; +import { settings } from '@pixi/settings'; +// import extractUniformsFromSrc from './extractUniformsFromSrc'; +import defaultVertex from './defaultFilter.vert'; +import defaultFragment from './defaultFilter.frag'; + +// let math = require('../../math'); +/** + * @class + * @memberof PIXI + * @extends PIXI.Shader + */ +export default class Filter extends Shader +{ + /** + * @param {string} [vertexSrc] - The source of the vertex shader. + * @param {string} [fragmentSrc] - The source of the fragment shader. + * @param {object} [uniforms] - Custom uniforms to use to augment the built-in ones. + */ + constructor(vertexSrc, fragmentSrc, uniforms) + { + const program = Program.from(vertexSrc, fragmentSrc); + + super(program, uniforms); + + /** + * The padding of the filter. Some filters require extra space to breath such as a blur. + * Increasing this will add extra width and height to the bounds of the object that the + * filter is applied to. + * + * @member {number} + */ + this.padding = 0; + + /** + * The resolution of the filter. Setting this to be lower will lower the quality but + * increase the performance of the filter. + * + * @member {number} + */ + this.resolution = settings.RESOLUTION; + + /** + * If enabled is true the filter is applied, if false it will not. + * + * @member {boolean} + */ + this.enabled = true; + + /** + * If enabled, PixiJS will fit the filter area into boundaries for better performance. + * Switch it off if it does not work for specific shader. + * + * @member {boolean} + */ + this.autoFit = true; + + /** + * Legacy filters use position and uvs from attributes + * @member {boolean} + * @readonly + */ + this.legacy = !!this.program.attributeData.aTextureCoord; + + /** + * the webGL state the filter requires to render + * @member {PIXI.State} + */ + this.state = new State(); + } + + /** + * Applies the filter + * + * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + * @param {boolean} clear - Should the output be cleared before rendering to it + * @param {object} [currentState] - It's current state of filter. + * There are some useful properties in the currentState : + * target, filters, sourceFrame, destinationFrame, renderTarget, resolution + */ + apply(filterManager, input, output, clear, currentState, derp) // eslint-disable-line no-unused-vars + { + // do as you please! + + filterManager.applyFilter(this, input, output, clear, currentState, derp); + + // or just do a regular render.. + } + + /** + * Sets the blendmode of the filter + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL + */ + get blendMode() + { + return this.state.blendMode; + } + + set blendMode(value) // eslint-disable-line require-jsdoc + { + this.state.blendMode = value; + } + + /** + * The default vertex shader source + * + * @static + * @constant + */ + static get defaultVertexSrc() + { + return defaultVertex; + } + + /** + * The default fragment shader source + * + * @static + * @constant + */ + static get defaultFragmentSrc() + { + return defaultFragment; + } +} + +/** + * Used for caching shader IDs + * + * @static + * @private + */ +Filter.SOURCE_KEY_MAP = {}; + diff --git a/packages/core/src/filters/FilterSystem.js b/packages/core/src/filters/FilterSystem.js new file mode 100644 index 0000000..3174318 --- /dev/null +++ b/packages/core/src/filters/FilterSystem.js @@ -0,0 +1,431 @@ +import System from '../System'; + +import RenderTexture from '../renderTexture/RenderTexture'; +import Quad from '../utils/Quad'; +import QuadUv from '../utils/QuadUv'; +import { Rectangle } from '@pixi/math'; +import * as filterTransforms from './filterTransforms'; +import bitTwiddle from 'bit-twiddle'; +import UniformGroup from '../shader/UniformGroup'; +import { DRAW_MODES } from '@pixi/constants'; + +// +/** + * @ignore + * @class + */ +class FilterState +{ + /** + * + */ + constructor() + { + this.renderTexture = null; + this.sourceFrame = new Rectangle(); + this.destinationFrame = new Rectangle(); + this.filters = []; + this.target = null; + this.legacy = false; + this.resolution = 1; + } +} + +/** + * @class + * @memberof PIXI.systems + * @extends PIXI.System + */ +export default class FilterSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + /** + * stores a bunch of PO2 textures used for filtering + * @type {Object} + */ + this.texturePool = {}; + + /** + * a pool for storing filter states, save us creating new ones each tick + * @type {Array} + */ + this.statePool = []; + + /** + * A very simple geometry used when drawing a filter effect to the screen + * @type {Quad} + */ + this.quad = new Quad(); + + this.quadUv = new QuadUv(); + + /** + * Temporary rect for maths + * @type {PIXI.Rectangle} + */ + this.tempRect = new Rectangle(); + + this.activeState = {}; + + /** + * this uniform group is attached to filter uniforms when used + * @type {UniformGroup} + */ + this.globalUniforms = new UniformGroup({ + sourceFrame: this.tempRect, + destinationFrame: this.tempRect, + + // legacy variables + filterArea: new Float32Array(4), + filterClamp: new Float32Array(4), + }, true); + } + + /** + * Adds a new filter to the System. + * + * @param {PIXI.DisplayObject} target - The target of the filter to render. + * @param {PIXI.Filter[]} filters - The filters to apply. + */ + push(target, filters) + { + const renderer = this.renderer; + const filterStack = this.renderer.renderTexture.defaultFilterStack; + const state = this.statePool.pop() || new FilterState(); + + let resolution = filters[0].resolution; + let padding = filters[0].padding; + let autoFit = filters[0].autoFit; + let legacy = filters[0].legacy; + + for (let i = 1; i < filters.length; i++) + { + const filter = filters[i]; + + // lets use the lowest resolution.. + resolution = Math.min(resolution, filter.resolution); + // and the largest amount of padding! + padding = Math.max(padding, filter.padding); + // only auto fit if all filters are autofit + autoFit = autoFit || filter.autoFit; + + legacy = legacy || filter.legacy; + } + + filterStack.push(state); + + state.resolution = resolution; + + state.legacy = legacy; + + // round to whole number based on resolution + // TODO move that to the shader too? + state.sourceFrame = target.filterArea || target.getBounds(true); + + state.sourceFrame.pad(padding); + + if (autoFit) + { + state.sourceFrame.fit(this.renderer.renderTexture.destinationFrame); + } + + state.sourceFrame.round(resolution); + + state.renderTexture = this.getPotFilterTexture(state.sourceFrame.width, state.sourceFrame.height, resolution); + state.filters = filters; + + state.destinationFrame.width = state.renderTexture.width; + state.destinationFrame.height = state.renderTexture.height; + + state.renderTexture.filterFrame = state.sourceFrame; + renderer.renderTexture.bind(state.renderTexture, state.sourceFrame);// /, state.destinationFrame); + renderer.renderTexture.clear(); + } + + /** + * Pops off the filter and applies it. + * + */ + pop() + { + const renderer = this.renderer; + const filterStack = renderer.renderTexture.defaultFilterStack; + const state = filterStack.pop(); + const filters = state.filters; + + this.activeState = state; + + const globalUniforms = this.globalUniforms.uniforms; + + globalUniforms.sourceFrame = state.sourceFrame; + globalUniforms.destinationFrame = state.destinationFrame; + globalUniforms.resolution = state.resolution; + + // only update the rect if its legacy.. + if (state.legacy) + { + const filterArea = globalUniforms.filterArea; + const filterClamp = globalUniforms.filterClamp; + + filterArea[0] = state.destinationFrame.width; + filterArea[1] = state.destinationFrame.height; + filterArea[2] = state.sourceFrame.x; + filterArea[3] = state.sourceFrame.y; + + filterClamp[0] = 0.5 / state.resolution / state.destinationFrame.width; + filterClamp[1] = 0.5 / state.resolution / state.destinationFrame.height; + filterClamp[2] = (state.sourceFrame.width - 0.5) / state.resolution / state.destinationFrame.width; + filterClamp[3] = (state.sourceFrame.height - 0.5) / state.resolution / state.destinationFrame.height; + } + + this.globalUniforms.update(); + + const lastState = filterStack[filterStack.length - 1]; + + if (filters.length === 1) + { + filters[0].apply(this, state.renderTexture, lastState.renderTexture, false, state); + + this.returnFilterTexture(state.renderTexture); + } + else + { + let flip = state.renderTexture; + let flop = this.getPotFilterTexture( + flip.width, + flip.height, + state.resolution + ); + + flop.filterFrame = flip.filterFrame; + + let i = 0; + + for (i = 0; i < filters.length - 1; ++i) + { + filters[i].apply(this, flip, flop, true, state); + + const t = flip; + + flip = flop; + flop = t; + } + + filters[i].apply(this, flip, lastState.renderTexture, false, state); + + this.returnFilterTexture(flip); + this.returnFilterTexture(flop); + } + + this.statePool.push(state); + } + + /** + * Draws a filter. + * + * @param {PIXI.Filter} filter - The filter to draw. + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + * @param {boolean} clear - Should the output be cleared before rendering to it + */ + applyFilter(filter, input, output, clear) + { + const renderer = this.renderer; + + renderer.renderTexture.bind(output, output ? output.filterFrame : null); + + if (clear) + { + // gl.disable(gl.SCISSOR_TEST); + renderer.renderTexture.clear(); + // gl.enable(gl.SCISSOR_TEST); + } + + // set the uniforms.. + filter.uniforms.uSampler = input; + filter.uniforms.filterGlobals = this.globalUniforms; + + // TODO make it so that the order of this does not matter.. + // because it does at the moment cos of global uniforms. + // they need to get resynced + + renderer.state.setState(filter.state); + renderer.shader.bind(filter); + + if (filter.legacy) + { + this.quadUv.map(input._frame, input.filterFrame); + + renderer.geometry.bind(this.quadUv); + renderer.geometry.draw(DRAW_MODES.TRIANGLES); + } + else + { + renderer.geometry.bind(this.quad); + renderer.geometry.draw(DRAW_MODES.TRIANGLE_STRIP); + } + } + + /** + * Calculates the mapped matrix. + * + * TODO playing around here.. this is temporary - (will end up in the shader) + * this returns a matrix that will normalise map filter cords in the filter to screen space + * + * @param {PIXI.Matrix} outputMatrix - the matrix to output to. + * @return {PIXI.Matrix} The mapped matrix. + */ + calculateScreenSpaceMatrix(outputMatrix) + { + const currentState = this.activeState; + + return filterTransforms.calculateScreenSpaceMatrix( + outputMatrix, + currentState.sourceFrame, + currentState.destinationFrame + ); + } + + /** + * This will map the filter coord so that a texture can be used based on the transform of a sprite + * + * @param {PIXI.Matrix} outputMatrix - The matrix to output to. + * @param {PIXI.Sprite} sprite - The sprite to map to. + * @return {PIXI.Matrix} The mapped matrix. + */ + calculateSpriteMatrix(outputMatrix, sprite) + { + const currentState = this.activeState; + + return filterTransforms.calculateSpriteMatrix( + outputMatrix, + currentState.sourceFrame, + currentState.destinationFrame, + sprite + ); + } + + /** + * Destroys this Filter System. + * + * @param {boolean} [contextLost=false] context was lost, do not free shaders + * + */ + destroy(contextLost = false) + { + if (!contextLost) + { + this.emptyPool(); + } + else + { + this.texturePool = {}; + } + } + + /** + * Gets a Power-of-Two render texture. + * + * TODO move to a seperate class could be on renderer? + * also - could cause issue with multiple contexts? + * + * @private + * @param {WebGLRenderingContext} gl - The webgl rendering context + * @param {number} minWidth - The minimum width of the render target. + * @param {number} minHeight - The minimum height of the render target. + * @param {number} resolution - The resolution of the render target. + * @return {PIXI.RenderTarget} The new render target. + */ + getPotFilterTexture(minWidth, minHeight, resolution) + { + minWidth = bitTwiddle.nextPow2(minWidth); + minHeight = bitTwiddle.nextPow2(minHeight); + resolution = resolution || 1; + + const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + + if (!this.texturePool[key]) + { + this.texturePool[key] = []; + } + + let renderTexture = this.texturePool[key].pop(); + + if (!renderTexture) + { + // temporary bypass cache.. + // internally - this will cause a texture to be bound.. + renderTexture = RenderTexture.create({ + width: minWidth, + height: minHeight, + resolution, + }); + } + + return renderTexture; + } + + /** + * Gets extra render texture to use inside current filter + * + * @param {number} resolution resolution of the renderTexture + * @returns {PIXI.RenderTexture} + */ + getFilterTexture(resolution) + { + const rt = this.activeState.renderTexture; + + const filterTexture = this.getPotFilterTexture(rt.width, rt.height, resolution || rt.baseTexture.resolution); + + filterTexture.filterFrame = rt.filterFrame; + + return filterTexture; + } + + /** + * Frees a render texture back into the pool. + * + * @param {PIXI.RenderTarget} renderTexture - The renderTarget to free + */ + returnFilterTexture(renderTexture) + { + renderTexture.filterFrame = null; + + const base = renderTexture.baseTexture; + + const minWidth = base.width; + const minHeight = base.height; + + const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + + this.texturePool[key].push(renderTexture); + } + + /** + * Empties the texture pool. + * + */ + emptyPool() + { + for (const i in this.texturePool) + { + const textures = this.texturePool[i]; + + if (textures) + { + for (let j = 0; j < textures.length; j++) + { + textures[j].destroy(true); + } + } + } + + this.texturePool = {}; + } +} diff --git a/packages/core/src/filters/defaultFilter.frag b/packages/core/src/filters/defaultFilter.frag new file mode 100644 index 0000000..a14c1be --- /dev/null +++ b/packages/core/src/filters/defaultFilter.frag @@ -0,0 +1,22 @@ +varying vec2 vTextureCoord; +varying vec2 vFilterCoord; + +uniform sampler2D uSampler; +uniform sampler2D filterSampler; + +void main(void){ + vec4 masky = texture2D(filterSampler, vFilterCoord); + vec4 sample = texture2D(uSampler, vTextureCoord); + vec4 color; + if(mod(vFilterCoord.x, 1.0) > 0.5) + { + color = vec4(1.0, 0.0, 0.0, 1.0); + } + else + { + color = vec4(0.0, 1.0, 0.0, 1.0); + } + // gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0); + gl_FragColor = mix(sample, masky, 0.5); + gl_FragColor *= sample.a; +} diff --git a/packages/core/src/filters/defaultFilter.vert b/packages/core/src/filters/defaultFilter.vert new file mode 100644 index 0000000..e1febca --- /dev/null +++ b/packages/core/src/filters/defaultFilter.vert @@ -0,0 +1,14 @@ +attribute vec2 aVertexPosition; +attribute vec2 aTextureCoord; + +uniform mat3 projectionMatrix; +uniform mat3 filterMatrix; + +varying vec2 vTextureCoord; +varying vec2 vFilterCoord; + +void main(void){ + gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); + vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy; + vTextureCoord = aTextureCoord ; +} diff --git a/packages/core/src/filters/filterTransforms.js b/packages/core/src/filters/filterTransforms.js new file mode 100644 index 0000000..87a3cd6 --- /dev/null +++ b/packages/core/src/filters/filterTransforms.js @@ -0,0 +1,50 @@ +import { Matrix } from '@pixi/math'; + +/** + * Calculates the mapped matrix + * @param filterArea {Rectangle} The filter area + * @param sprite {Sprite} the target sprite + * @param outputMatrix {Matrix} @alvin + * @private + */ +// this returns a matrix that will normalise map filter cords in the filter to screen space +export function calculateScreenSpaceMatrix(outputMatrix, filterArea, textureSize) +{ + // TODO unwrap? + const mappedMatrix = outputMatrix.identity(); + + mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); + + mappedMatrix.scale(textureSize.width, textureSize.height); + + return mappedMatrix; +} + +export function calculateNormalizedScreenSpaceMatrix(outputMatrix, filterArea, textureSize) +{ + const mappedMatrix = outputMatrix.identity(); + + mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); + + const translateScaleX = (textureSize.width / filterArea.width); + const translateScaleY = (textureSize.height / filterArea.height); + + mappedMatrix.scale(translateScaleX, translateScaleY); + + return mappedMatrix; +} + +// this will map the filter coord so that a texture can be used based on the transform of a sprite +export function calculateSpriteMatrix(outputMatrix, filterArea, textureSize, sprite) +{ + const orig = sprite._texture.orig; + const mappedMatrix = outputMatrix.set(textureSize.width, 0, 0, textureSize.height, filterArea.x, filterArea.y); + const worldTransform = sprite.worldTransform.copyTo(Matrix.TEMP_MATRIX); + + worldTransform.invert(); + mappedMatrix.prepend(worldTransform); + mappedMatrix.scale(1.0 / orig.width, 1.0 / orig.height); + mappedMatrix.translate(sprite.anchor.x, sprite.anchor.y); + + return mappedMatrix; +} diff --git a/packages/core/src/filters/spriteMask/SpriteMaskFilter.js b/packages/core/src/filters/spriteMask/SpriteMaskFilter.js new file mode 100644 index 0000000..91697b7 --- /dev/null +++ b/packages/core/src/filters/spriteMask/SpriteMaskFilter.js @@ -0,0 +1,63 @@ +import Filter from '../Filter'; +import { Matrix } from '@pixi/math'; +import vertex from './spriteMaskFilter.vert'; +import fragment from './spriteMaskFilter.frag'; +import { default as TextureMatrix } from '../../textures/TextureMatrix'; + +/** + * The SpriteMaskFilter class + * + * @class + * @extends PIXI.Filter + * @memberof PIXI + */ +export default class SpriteMaskFilter extends Filter +{ + /** + * @param {PIXI.Sprite} sprite - the target sprite + */ + constructor(sprite) + { + const maskMatrix = new Matrix(); + + super(vertex, fragment); + + sprite.renderable = false; + + this.maskSprite = sprite; + this.maskMatrix = maskMatrix; + } + + /** + * Applies the filter + * + * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + */ + apply(filterManager, input, output) + { + const maskSprite = this.maskSprite; + const tex = this.maskSprite.texture; + + if (!tex.valid) + { + return; + } + if (!tex.transform) + { + // margin = 0.0, let it bleed a bit, shader code becomes easier + // assuming that atlas textures were made with 1-pixel padding + tex.transform = new TextureMatrix(tex, 0.0); + } + tex.transform.update(); + + this.uniforms.mask = tex; + this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite) + .prepend(tex.transform.mapCoord); + this.uniforms.alpha = maskSprite.worldAlpha; + this.uniforms.maskClamp = tex.transform.uClampFrame; + + filterManager.applyFilter(this, input, output); + } +} diff --git a/packages/core/src/filters/spriteMask/spriteMaskFilter.frag b/packages/core/src/filters/spriteMask/spriteMaskFilter.frag new file mode 100644 index 0000000..0e4aef8 --- /dev/null +++ b/packages/core/src/filters/spriteMask/spriteMaskFilter.frag @@ -0,0 +1,23 @@ +varying vec2 vMaskCoord; +varying vec2 vTextureCoord; + +uniform sampler2D uSampler; +uniform sampler2D mask; +uniform float alpha; +uniform vec4 maskClamp; + +void main(void) +{ + float clip = step(3.5, + step(maskClamp.x, vMaskCoord.x) + + step(maskClamp.y, vMaskCoord.y) + + step(vMaskCoord.x, maskClamp.z) + + step(vMaskCoord.y, maskClamp.w)); + + vec4 original = texture2D(uSampler, vTextureCoord); + vec4 masky = texture2D(mask, vMaskCoord); + + original *= (masky.r * masky.a * alpha * clip); + + gl_FragColor = original; +} diff --git a/packages/core/src/filters/spriteMask/spriteMaskFilter.vert b/packages/core/src/filters/spriteMask/spriteMaskFilter.vert new file mode 100644 index 0000000..d23a30c --- /dev/null +++ b/packages/core/src/filters/spriteMask/spriteMaskFilter.vert @@ -0,0 +1,16 @@ +attribute vec2 aVertexPosition; +attribute vec2 aTextureCoord; + +uniform mat3 projectionMatrix; +uniform mat3 otherMatrix; + +varying vec2 vMaskCoord; +varying vec2 vTextureCoord; + +void main(void) +{ + gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); + + vTextureCoord = aTextureCoord; + vMaskCoord = ( otherMatrix * vec3( aTextureCoord, 1.0) ).xy; +} diff --git a/packages/core/src/framebuffer/FrameBuffer.js b/packages/core/src/framebuffer/FrameBuffer.js new file mode 100644 index 0000000..84e9fcc --- /dev/null +++ b/packages/core/src/framebuffer/FrameBuffer.js @@ -0,0 +1,106 @@ +import Texture from '../textures/BaseTexture'; +import { FORMATS, TYPES } from '@pixi/constants'; + +/** + * Frame buffer + * @class + * @memberof PIXI + */ +export default class FrameBuffer +{ + constructor(width, height) + { + this.width = width || 100; + this.height = height || 100; + + this.stencil = false; + this.depth = false; + + this.dirtyId = 0; + this.dirtyFormat = 0; + this.dirtySize = 0; + + this.depthTexture = null; + this.colorTextures = []; + + this.glFrameBuffers = {}; + } + + get colorTexture() + { + return this.colorTextures[0]; + } + + addColorTexture(index, texture) + { + // TODO add some validation to the texture - same width / height etc? + this.colorTextures[index || 0] = texture || new Texture(null, { scaleMode: 0, + resolution: 1, + mipmap: false, + width: this.width, + height: this.height });// || new Texture(); + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + addDepthTexture(texture) + { + /* eslint-disable max-len */ + this.depthTexture = texture || new Texture(null, { scaleMode: 0, + resolution: 1, + width: this.width, + height: this.height, + mipmap: false, + format: FORMATS.DEPTH_COMPONENT, + type: TYPES.UNSIGNED_SHORT });// UNSIGNED_SHORT; + /* eslint-disable max-len */ + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + enableDepth() + { + this.depth = true; + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + enableStencil() + { + this.stencil = true; + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + resize(width, height) + { + if (width === this.width && height === this.height) return; + + this.width = width; + this.height = height; + + this.dirtyId++; + this.dirtySize++; + + for (let i = 0; i < this.colorTextures.length; i++) + { + this.colorTextures[i].setSize(width, height); + } + + if (this.depthTexture) + { + this.depthTexture.setSize(width, height); + } + } +} diff --git a/packages/core/src/framebuffer/FramebufferSystem.js b/packages/core/src/framebuffer/FramebufferSystem.js new file mode 100644 index 0000000..2cc2437 --- /dev/null +++ b/packages/core/src/framebuffer/FramebufferSystem.js @@ -0,0 +1,246 @@ +import System from '../System'; +import { Rectangle } from '@pixi/math'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class FramebufferSystem extends System +{ + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + contextChange() + { + this.gl = this.renderer.gl; + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + this.current = null; + this.viewport = new Rectangle(); + + this.drawBufferExtension = this.renderer.context.extensions.drawBuffers; + } + + bind(framebuffer, frame) + { + const gl = this.gl; + + this.current = framebuffer; + + if (framebuffer) + { + // TODO cacheing layer! + + const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID] || this.initFramebuffer(framebuffer); + + gl.bindFramebuffer(gl.FRAMEBUFFER, fbo.framebuffer); + // makesure all textures are unbound.. + + // now check for updates... + if (fbo.dirtyId !== framebuffer.dirtyId) + { + fbo.dirtyId = framebuffer.dirtyId; + + if (fbo.dirtyFormat !== framebuffer.dirtyFormat) + { + fbo.dirtyFormat = framebuffer.dirtyFormat; + this.updateFramebuffer(framebuffer); + } + else if (fbo.dirtySize !== framebuffer.dirtySize) + { + fbo.dirtySize = framebuffer.dirtySize; + this.resizeFramebuffer(framebuffer); + } + } + + for (let i = 0; i < framebuffer.colorTextures.length; i++) + { + if (framebuffer.colorTextures[i].texturePart) + { + this.renderer.texture.unbind(framebuffer.colorTextures[i].texture); + } + else + { + this.renderer.texture.unbind(framebuffer.colorTextures[i]); + } + } + + if (framebuffer.depthTexture) + { + this.renderer.texture.unbind(framebuffer.depthTexture); + } + + if (frame) + { + this.setViewport(frame.x, frame.y, frame.width, frame.height); + } + else + { + this.setViewport(0, 0, framebuffer.width, framebuffer.height); + } + } + else + { + gl.bindFramebuffer(gl.FRAMEBUFFER, null); + + if (frame) + { + this.setViewport(frame.x, frame.y, frame.width, frame.height); + } + else + { + this.setViewport(0, 0, this.renderer.width, this.renderer.height); + } + } + } + + setViewport(x, y, width, height) + { + const v = this.viewport; + + if (v.width !== width || v.height !== height || v.x !== x || v.y !== y) + { + v.x = x; + v.y = y; + v.width = width; + v.height = height; + + this.gl.viewport(x, y, width, height); + } + } + + get size() + { + if (this.current) + { + // TODO store temp + return { x: 0, y: 0, width: this.current.width, height: this.current.height }; + } + + return { x: 0, y: 0, width: this.renderer.width, height: this.renderer.height }; + } + + clear(r, g, b, a) + { + const gl = this.gl; + + // TODO clear color can be set only one right? + gl.clearColor(r, g, b, a); + gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); + } + + // private functions... + + initFramebuffer(framebuffer) + { + const gl = this.gl; + + // TODO - make this a class? + const fbo = { + framebuffer: gl.createFramebuffer(), + stencil: null, + dirtyId: 0, + dirtyFormat: 0, + dirtySize: 0, + }; + + framebuffer.glFrameBuffers[this.CONTEXT_UID] = fbo; + + return fbo; + } + + resizeFramebuffer(framebuffer) + { + const gl = this.gl; + + if (framebuffer.stencil || framebuffer.depth) + { + gl.bindRenderbuffer(gl.RENDERBUFFER, this.stencil); + gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); + } + } + + updateFramebuffer(framebuffer) + { + const gl = this.gl; + + const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID]; + + // bind the color texture + const colorTextures = framebuffer.colorTextures; + + let count = colorTextures.length; + + if (!this.drawBufferExtension) + { + count = Math.min(count, 1); + } + + const activeTextures = []; + + for (let i = 0; i < count; i++) + { + const texture = framebuffer.colorTextures[i]; + + if (texture.texturePart) + { + this.renderer.texture.bind(texture.texture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.COLOR_ATTACHMENT0 + i, + gl.TEXTURE_CUBE_MAP_NEGATIVE_X + texture.side, + texture.texture._glTextures[this.CONTEXT_UID].texture, + 0); + } + else + { + this.renderer.texture.bind(texture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.COLOR_ATTACHMENT0 + i, + gl.TEXTURE_2D, + texture._glTextures[this.CONTEXT_UID].texture, + 0); + } + + activeTextures.push(gl.COLOR_ATTACHMENT0 + i); + } + + if (this.drawBufferExtension && activeTextures.length > 1) + { + this.drawBufferExtension.drawBuffersWEBGL(activeTextures); + } + + if (framebuffer.depthTexture) + { + const depthTextureExt = this.renderer.context.extensions.depthTexture; + + if (depthTextureExt) + { + const depthTexture = framebuffer.depthTexture; + + this.renderer.texture.bind(depthTexture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.DEPTH_ATTACHMENT, + gl.TEXTURE_2D, + depthTexture._glTextures[this.CONTEXT_UID].texture, + 0); + } + } + + if (framebuffer.stencil || framebuffer.depth) + { + fbo.stencil = gl.createRenderbuffer(); + + gl.bindRenderbuffer(gl.RENDERBUFFER, fbo.stencil); + + // TODO.. this is depth AND stencil? + gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, fbo.stencil); + gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); + // fbo.enableStencil(); + } + } +} diff --git a/packages/core/src/geometry/GLBuffer.js b/packages/core/src/geometry/GLBuffer.js new file mode 100644 index 0000000..0477e8a --- /dev/null +++ b/packages/core/src/geometry/GLBuffer.js @@ -0,0 +1,9 @@ +export default class GLBuffer +{ + constructor(buffer) + { + this.buffer = buffer; + this.updateID = -1; + this.byteLength = -1; + } +} diff --git a/packages/core/src/geometry/GeometrySystem.js b/packages/core/src/geometry/GeometrySystem.js new file mode 100644 index 0000000..959f827 --- /dev/null +++ b/packages/core/src/geometry/GeometrySystem.js @@ -0,0 +1,388 @@ +import System from '../System'; +import GLBuffer from './GLBuffer'; + +const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class GeometrySystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this._activeGeometry = null; + this._activeVao = null; + + this.hasVao = true; + this.hasInstance = true; + } + + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + contextChange() + { + const gl = this.gl = this.renderer.gl; + + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + + // webgl2 + if (!gl.createVertexArray) + { + // webgl 1! + let nativeVaoExtension = this.renderer.context.extensions.vertexArrayObject; + + if (this.renderer.legacy) + { + nativeVaoExtension = null; + } + + if (nativeVaoExtension) + { + gl.createVertexArray = () => + nativeVaoExtension.createVertexArrayOES(); + + gl.bindVertexArray = (vao) => + nativeVaoExtension.bindVertexArrayOES(vao); + + gl.deleteVertexArray = (vao) => + nativeVaoExtension.deleteVertexArrayOES(vao); + } + else + { + this.hasVao = false; + gl.createVertexArray = () => + { + // empty + }; + + gl.bindVertexArray = () => + { + // empty + }; + + gl.deleteVertexArray = () => + { + // empty + }; + } + } + + if (!gl.vertexAttribDivisor) + { + const instanceExt = gl.getExtension('ANGLE_instanced_arrays'); + + if (instanceExt) + { + gl.vertexAttribDivisor = (a, b) => + instanceExt.vertexAttribDivisorANGLE(a, b); + + gl.drawElementsInstanced = (a, b, c, d, e) => + instanceExt.drawElementsInstancedANGLE(a, b, c, d, e); + + gl.drawArraysInstanced = (a, b, c, d) => + instanceExt.drawArraysInstancedANGLE(a, b, c, d); + } + else + { + this.hasInstance = false; + } + } + } + + /** + * Binds geometry so that is can be drawn. Creating a Vao if required + * @private + * @param {PIXI.Geometry} geometry instance of geometry to bind + */ + bind(geometry, shader) + { + shader = shader || this.renderer.shader.shader; + + const gl = this.gl; + + // not sure the best way to address this.. + // currently different shaders require different VAOs for the same geometry + // Still mulling over the best way to solve this one.. + // will likely need to modify the shader attribute locations at run time! + let vaos = geometry.glVertexArrayObjects[this.CONTEXT_UID]; + + if (!vaos) + { + geometry.glVertexArrayObjects[this.CONTEXT_UID] = vaos = {}; + } + + const vao = vaos[shader.program.id] || this.initGeometryVao(geometry, shader.program); + + this._activeGeometry = geometry; + + if (this._activeVao !== vao) + { + this._activeVao = vao; + + if (this.hasVao) + { + gl.bindVertexArray(vao); + } + else + { + this.activateVao(geometry, shader.program); + } + } + + // TODO - optimise later! + // don't need to loop through if nothing changed! + // maybe look to add an 'autoupdate' to geometry? + this.updateBuffers(); + } + + reset() + { + this.unbind(); + } + + updateBuffers() + { + const geometry = this._activeGeometry; + const gl = this.gl; + + for (let i = 0; i < geometry.buffers.length; i++) + { + const buffer = geometry.buffers[i]; + + const glBuffer = buffer._glBuffers[this.CONTEXT_UID]; + + if (buffer._updateID !== glBuffer.updateID) + { + glBuffer.updateID = buffer._updateID; + + // TODO can cache this on buffer! maybe added a getter / setter? + const type = buffer.index ? gl.ELEMENT_ARRAY_BUFFER : gl.ARRAY_BUFFER; + const drawType = buffer.static ? gl.STATIC_DRAW : gl.DYNAMIC_DRAW; + + gl.bindBuffer(type, glBuffer.buffer); + + if (glBuffer.byteLength >= buffer.data.byteLength) + { + // offset is always zero for now! + gl.bufferSubData(type, 0, buffer.data); + } + else + { + gl.bufferData(type, buffer.data, drawType); + } + } + } + } + + checkCompatability(geometry, program) + { + // geometry must have at least all the attributes that the shader requires. + const geometryAttributes = geometry.attributes; + const shaderAttributes = program.attributeData; + + for (const j in shaderAttributes) + { + if (!geometryAttributes[j]) + { + throw new Error(`shader and geometry incompatible, geometry missing the "${j}" attribute`); + } + } + } + + /** + * Creates a Vao with the same structure as the geometry and stores it on the geometry. + * @private + * @param {PIXI.Geometry} geometry instance of geometry to to generate Vao for + */ + initGeometryVao(geometry, program) + { + this.checkCompatability(geometry, program); + + const gl = this.gl; + const CONTEXT_UID = this.CONTEXT_UID; + const buffers = geometry.buffers; + const attributes = geometry.attributes; + + const tempStride = {}; + const tempStart = {}; + + for (const j in buffers) + { + tempStride[j] = 0; + tempStart[j] = 0; + } + + for (const j in attributes) + { + if (!attributes[j].size && program.attributeData[j]) + { + attributes[j].size = program.attributeData[j].size; + } + + tempStride[attributes[j].buffer] += attributes[j].size * byteSizeMap[attributes[j].type]; + } + + for (const j in attributes) + { + const attribute = attributes[j]; + const attribSize = attribute.size; + + if (attribute.stride === undefined) + { + if (tempStride[attribute.buffer] === attribSize * byteSizeMap[attribute.type]) + { + attribute.stride = 0; + } + else + { + attribute.stride = tempStride[attribute.buffer]; + } + } + + if (attribute.start === undefined) + { + attribute.start = tempStart[attribute.buffer]; + + tempStart[attribute.buffer] += attribSize * byteSizeMap[attribute.type]; + } + } + + // first update - and create the buffers! + // only create a gl buffer if it actually gets + for (let i = 0; i < buffers.length; i++) + { + const buffer = buffers[i]; + + if (!buffer._glBuffers[CONTEXT_UID]) + { + buffer._glBuffers[CONTEXT_UID] = new GLBuffer(gl.createBuffer()); + } + } + + // TODO - maybe make this a data object? + // lets wait to see if we need to first! + const vao = gl.createVertexArray(); + + gl.bindVertexArray(vao); + + this.activateVao(geometry, program); + + gl.bindVertexArray(null); + + // add it to the cache! + geometry.glVertexArrayObjects[this.CONTEXT_UID][program.id] = vao; + + return vao; + } + + activateVao(geometry, program) + { + const gl = this.gl; + const CONTEXT_UID = this.CONTEXT_UID; + const buffers = geometry.buffers; + const attributes = geometry.attributes; + + if (geometry.indexBuffer) + { + // first update the index buffer if we have one.. + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, geometry.indexBuffer._glBuffers[CONTEXT_UID].buffer); + } + + let lastBuffer = null; + + // add a new one! + for (const j in attributes) + { + const attribute = attributes[j]; + const buffer = buffers[attribute.buffer]; + const glBuffer = buffer._glBuffers[CONTEXT_UID]; + + if (program.attributeData[j]) + { + if (lastBuffer !== glBuffer) + { + gl.bindBuffer(gl.ARRAY_BUFFER, glBuffer.buffer); + + lastBuffer = glBuffer; + } + + const location = program.attributeData[j].location; + + // TODO introduce state again + // we can optimise this for older devices that have no VAOs + gl.enableVertexAttribArray(location); + + gl.vertexAttribPointer(location, + attribute.size, + attribute.type || gl.FLOAT, + attribute.normalized, + attribute.stride, + attribute.start); + + if (attribute.instance) + { + // TODO calculate instance count based of this... + if (this.hasInstance) + { + gl.vertexAttribDivisor(location, 1); + } + else + { + throw new Error('geometry error, GPU Instancing is not supported on this device'); + } + } + } + } + } + + draw(type, size, start, instanceCount) + { + const gl = this.gl; + const geometry = this._activeGeometry; + + // TODO.. this should not change so maybe cache the function? + + if (geometry.indexBuffer) + { + if (geometry.instanced) + { + /* eslint-disable max-len */ + gl.drawElementsInstanced(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2, instanceCount || 1); + /* eslint-enable max-len */ + } + else + { + gl.drawElements(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2); + } + } + else + if (geometry.instanced) + { + // TODO need a better way to calculate size.. + gl.drawArraysInstanced(type, start, size || geometry.getSize(), instanceCount || 1); + } + else + { + gl.drawArrays(type, start, size || geometry.getSize()); + } + + return this; + } + + unbind() + { + this.gl.bindVertexArray(null); + this._activeVao = null; + this._activeGeometry = null; + } +} diff --git a/packages/core/src/geometry/utils/setVertexAttribArrays.js b/packages/core/src/geometry/utils/setVertexAttribArrays.js new file mode 100644 index 0000000..23b6959 --- /dev/null +++ b/packages/core/src/geometry/utils/setVertexAttribArrays.js @@ -0,0 +1,54 @@ +// var GL_MAP = {}; + +/** + * @param gl {WebGLRenderingContext} The current WebGL context + * @param attribs {*} + * @param state {*} + */ +export default function setVertexAttribArrays(gl, attribs, state) +{ + let i; + + if (state) + { + const tempAttribState = state.tempAttribState; + const attribState = state.attribState; + + for (i = 0; i < tempAttribState.length; i++) + { + tempAttribState[i] = false; + } + + // set the new attribs + for (i = 0; i < attribs.length; i++) + { + tempAttribState[attribs[i].attribute.location] = true; + } + + for (i = 0; i < attribState.length; i++) + { + if (attribState[i] !== tempAttribState[i]) + { + attribState[i] = tempAttribState[i]; + + if (state.attribState[i]) + { + gl.enableVertexAttribArray(i); + } + else + { + gl.disableVertexAttribArray(i); + } + } + } + } + else + { + for (i = 0; i < attribs.length; i++) + { + const attrib = attribs[i]; + + gl.enableVertexAttribArray(attrib.attribute.location); + } + } +} diff --git a/packages/core/src/index.js b/packages/core/src/index.js index 58c456a..fd1f547 100644 --- a/packages/core/src/index.js +++ b/packages/core/src/index.js @@ -1,30 +1,30 @@ import * as resources from './textures/resources'; -import * as systems from './renderers/systems'; +import * as systems from './systems'; -export { resources }; export { systems }; +export { resources }; -export { default as Renderer } from './renderers/Renderer'; -export { default as AbstractRenderer } from './renderers/AbstractRenderer'; -export { default as FrameBuffer } from './textures/FrameBuffer'; +export { default as System } from './System'; +export { default as Renderer } from './Renderer'; +export { default as AbstractRenderer } from './AbstractRenderer'; +export { default as FrameBuffer } from './framebuffer/FrameBuffer'; export { default as CubeTexture } from './textures/CubeTexture'; export { default as BaseTexture } from './textures/BaseTexture'; export { default as Texture } from './textures/Texture'; export { default as TextureMatrix } from './textures/TextureMatrix'; -export { default as RenderTexture } from './textures/RenderTexture'; -export { default as BaseRenderTexture } from './textures/BaseRenderTexture'; +export { default as RenderTexture } from './renderTexture/RenderTexture'; +export { default as BaseRenderTexture } from './renderTexture/BaseRenderTexture'; export { default as TextureUvs } from './textures/TextureUvs'; -export { default as State } from './renderers/State'; -export { default as ObjectRenderer } from './renderers/utils/ObjectRenderer'; -export { default as Quad } from './renderers/utils/Quad'; -export { default as QuadUv } from './renderers/utils/QuadUv'; -export { default as checkMaxIfStatmentsInShader } from './renderers/utils/checkMaxIfStatmentsInShader'; +export { default as State } from './state/State'; +export { default as ObjectRenderer } from './batch/ObjectRenderer'; +export { default as Quad } from './utils/Quad'; +export { default as QuadUv } from './utils/QuadUv'; +export { default as checkMaxIfStatmentsInShader } from './shader/utils/checkMaxIfStatmentsInShader'; export { default as Shader } from './shader/Shader'; export { default as Program } from './shader/Program'; export { default as UniformGroup } from './shader/UniformGroup'; -export { default as SpriteMaskFilter } from './renderers/filters/spriteMask/SpriteMaskFilter'; -export { default as Filter } from './renderers/filters/Filter'; +export { default as SpriteMaskFilter } from './filters/spriteMask/SpriteMaskFilter'; +export { default as Filter } from './filters/Filter'; export { default as Attribute } from './geometry/Attribute'; export { default as Buffer } from './geometry/Buffer'; export { default as Geometry } from './geometry/Geometry'; - diff --git a/packages/core/src/mask/MaskSystem.js b/packages/core/src/mask/MaskSystem.js new file mode 100644 index 0000000..99174c2 --- /dev/null +++ b/packages/core/src/mask/MaskSystem.js @@ -0,0 +1,199 @@ +import System from '../System'; +import AlphaMaskFilter from '../filters/spriteMask/SpriteMaskFilter'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class MaskSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + // TODO - we don't need both! + this.scissor = false; + this.scissorData = null; + this.scissorRenderTarget = null; + + this.enableScissor = false; + + this.alphaMaskPool = []; + this.alphaMaskIndex = 0; + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.DisplayObject} target - Display Object to push the mask to + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + push(target, maskData) + { + // TODO the root check means scissor rect will not + // be used on render textures more info here: + // https://github.com/pixijs/pixi.js/pull/3545 + + if (maskData.texture) + { + this.pushSpriteMask(target, maskData); + } + else if (this.enableScissor + && !this.scissor + && this.renderer._activeRenderTarget.root + && !this.renderer.stencil.stencilMaskStack.length + && maskData.isFastRect()) + { + const matrix = maskData.worldTransform; + + let rot = Math.atan2(matrix.b, matrix.a); + + // use the nearest degree! + rot = Math.round(rot * (180 / Math.PI)); + + if (rot % 90) + { + this.pushStencilMask(maskData); + } + else + { + this.pushScissorMask(target, maskData); + } + } + else + { + this.pushStencilMask(maskData); + } + } + + /** + * Removes the last mask from the mask stack and doesn't return it. + * + * @param {PIXI.DisplayObject} target - Display Object to pop the mask from + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + pop(target, maskData) + { + if (maskData.texture) + { + this.popSpriteMask(target, maskData); + } + else if (this.enableScissor && !this.renderer.stencil.stencilMaskStack.length) + { + this.popScissorMask(target, maskData); + } + else + { + this.popStencilMask(target, maskData); + } + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.RenderTarget} target - Display Object to push the sprite mask to + * @param {PIXI.Sprite} maskData - Sprite to be used as the mask + */ + pushSpriteMask(target, maskData) + { + let alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; + + if (!alphaMaskFilter) + { + alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; + } + + alphaMaskFilter[0].resolution = this.renderer.resolution; + alphaMaskFilter[0].maskSprite = maskData; + + // TODO - may cause issues! + target.filterArea = maskData.getBounds(true); + + this.renderer.filterSystem.pushFilter(target, alphaMaskFilter); + + this.alphaMaskIndex++; + } + + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popSpriteMask() + { + this.renderer.filterSystem.popFilter(); + this.alphaMaskIndex--; + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + pushStencilMask(maskData) + { + this.renderer.batch.flush(); + this.renderer.stencil.pushStencil(maskData); + } + + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popStencilMask() + { + // this.renderer.currentRenderer.stop(); + this.renderer.stencil.popStencil(); + } + + /** + * + * @param {PIXI.DisplayObject} target - Display Object to push the mask to + * @param {PIXI.Graphics} maskData - The masking data. + */ + pushScissorMask(target, maskData) + { + maskData.renderable = true; + + const renderTarget = this.renderer._activeRenderTarget; + + const bounds = maskData.getBounds(); + + bounds.fit(renderTarget.size); + maskData.renderable = false; + + this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); + + const resolution = this.renderer.resolution; + + this.renderer.gl.scissor( + bounds.x * resolution, + (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, + bounds.width * resolution, + bounds.height * resolution + ); + + this.scissorRenderTarget = renderTarget; + this.scissorData = maskData; + this.scissor = true; + } + + /** + * + * + */ + popScissorMask() + { + this.scissorRenderTarget = null; + this.scissorData = null; + this.scissor = false; + + // must be scissor! + const gl = this.renderer.gl; + + gl.disable(gl.SCISSOR_TEST); + } +} diff --git a/packages/core/src/mask/StencilSystem.js b/packages/core/src/mask/StencilSystem.js new file mode 100644 index 0000000..62796a5 --- /dev/null +++ b/packages/core/src/mask/StencilSystem.js @@ -0,0 +1,133 @@ +import System from '../System'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class StencilSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + this.stencilMaskStack = []; + } + + /** + * Changes the mask stack that is used by this System. + * + * @param {PIXI.Graphics[]} stencilMaskStack - The mask stack + */ + setMaskStack(stencilMaskStack) + { + const gl = this.renderer.gl; + + if (stencilMaskStack.length !== this.stencilMaskStack.length) + { + if (stencilMaskStack.length === 0) + { + gl.disable(gl.STENCIL_TEST); + } + else + { + gl.enable(gl.STENCIL_TEST); + } + } + + this.stencilMaskStack = stencilMaskStack; + } + + /** + * Applies the Mask and adds it to the current stencil stack. @alvin + * + * @param {PIXI.Graphics} graphics - The mask + */ + pushStencil(graphics) + { + this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); + + // this.renderer._activeRenderTarget.attachStencilBuffer(); + + const gl = this.renderer.gl; + const prevMaskCount = this.stencilMaskStack.length; + + if (prevMaskCount === 0) + { + gl.enable(gl.STENCIL_TEST); + } + + this.stencilMaskStack.push(graphics); + + // Increment the refference stencil value where the new mask overlaps with the old ones. + gl.colorMask(false, false, false, false); + gl.stencilFunc(gl.EQUAL, prevMaskCount, this._getBitwiseMask()); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.INCR); + this.renderer.plugins.graphics.render(graphics); + + this._useCurrent(); + } + + /** + * Removes the last mask from the stencil stack. @alvin + */ + popStencil() + { + this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); + + const gl = this.renderer.gl; + const graphics = this.stencilMaskStack.pop(); + + if (this.stencilMaskStack.length === 0) + { + // the stack is empty! + gl.disable(gl.STENCIL_TEST); + gl.clear(gl.STENCIL_BUFFER_BIT); + gl.clearStencil(0); + } + else + { + // Decrement the refference stencil value where the popped mask overlaps with the other ones + gl.colorMask(false, false, false, false); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.DECR); + this.renderer.plugins.graphics.render(graphics); + + this._useCurrent(); + } + } + + /** + * Setup renderer to use the current stencil data. + */ + _useCurrent() + { + const gl = this.renderer.gl; + + gl.colorMask(true, true, true, true); + gl.stencilFunc(gl.EQUAL, this.stencilMaskStack.length, this._getBitwiseMask()); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP); + } + + /** + * Fill 1s equal to the number of acitve stencil masks. + * + * @return {number} The bitwise mask. + */ + _getBitwiseMask() + { + return (1 << this.stencilMaskStack.length) - 1; + } + + /** + * Destroys the mask stack. + * + */ + destroy() + { + super.destroy(this); + + this.stencilMaskStack = null; + } +} diff --git a/packages/core/src/projection/ProjectionSystem.js b/packages/core/src/projection/ProjectionSystem.js new file mode 100644 index 0000000..6e63b73 --- /dev/null +++ b/packages/core/src/projection/ProjectionSystem.js @@ -0,0 +1,73 @@ +import System from '../System'; +import { Matrix } from '@pixi/math'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class ProjectionSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.projectionMatrix = new Matrix(); + } + + update(destinationFrame, sourceFrame, resolution, root) + { + this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; + this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; + + this.calculateProjection(this.destinationFrame, this.sourceFrame, resolution, root); + + this.renderer.globalUniforms.uniforms.projectionMatrix = this.projectionMatrix; + this.renderer.globalUniforms.update(); + } + + /** + * Updates the projection matrix based on a projection frame (which is a rectangle) + * + * @param {Rectangle} destinationFrame - The destination frame. + * @param {Rectangle} sourceFrame - The source frame. + */ + calculateProjection(destinationFrame, sourceFrame, resolution, root) + { + const pm = this.projectionMatrix; + + // I don't think we will need this line.. + // pm.identity(); + + if (!root) + { + pm.a = (1 / destinationFrame.width * 2) * resolution; + pm.d = (1 / destinationFrame.height * 2) * resolution; + + pm.tx = -1 - (sourceFrame.x * pm.a); + pm.ty = -1 - (sourceFrame.y * pm.d); + } + else + { + pm.a = (1 / destinationFrame.width * 2) * resolution; + pm.d = (-1 / destinationFrame.height * 2) * resolution; + + pm.tx = -1 - (sourceFrame.x * pm.a); + pm.ty = 1 - (sourceFrame.y * pm.d); + } + } + + /** + * Sets the transform of the active render target to the given matrix + * + * @param {PIXI.Matrix} matrix - The transformation matrix + */ + setTransform()// matrix) + { + // this._activeRenderTarget.transform = matrix; + } +} diff --git a/packages/core/src/renderTexture/BaseRenderTexture.js b/packages/core/src/renderTexture/BaseRenderTexture.js new file mode 100644 index 0000000..88eb83b --- /dev/null +++ b/packages/core/src/renderTexture/BaseRenderTexture.js @@ -0,0 +1,137 @@ +import BaseTexture from '../textures/BaseTexture'; +import FrameBuffer from '../framebuffer/FrameBuffer'; + +/** + * A BaseRenderTexture is a special texture that allows any PixiJS display object to be rendered to it. + * + * __Hint__: All DisplayObjects (i.e. Sprites) that render to a BaseRenderTexture should be preloaded + * otherwise black rectangles will be drawn instead. + * + * A BaseRenderTexture takes a snapshot of any Display Object given to its render method. The position + * and rotation of the given Display Objects is ignored. For example: + * + * ```js + * let renderer = PIXI.autoDetectRenderer(1024, 1024, { view: canvas, ratio: 1 }); + * let baseRenderTexture = new PIXI.BaseRenderTexture(renderer, 800, 600); + * let sprite = PIXI.Sprite.fromImage("spinObj_01.png"); + * + * sprite.position.x = 800/2; + * sprite.position.y = 600/2; + * sprite.anchor.x = 0.5; + * sprite.anchor.y = 0.5; + * + * baseRenderTexture.render(sprite); + * ``` + * + * The Sprite in this case will be rendered using its local transform. To render this sprite at 0,0 + * you can clear the transform + * + * ```js + * + * sprite.setTransform() + * + * let baseRenderTexture = new PIXI.BaseRenderTexture(100, 100); + * let renderTexture = new PIXI.RenderTexture(baseRenderTexture); + * + * renderer.render(sprite, renderTexture); // Renders to center of RenderTexture + * ``` + * + * @class + * @extends PIXI.BaseTexture + * @memberof PIXI + */ +export default class BaseRenderTexture extends BaseTexture +{ + /** + * @param {object} [options] + * @param {number} [options.width=100] - The width of the base render texture + * @param {number} [options.height=100] - The height of the base render texture + * @param {PIXI.SCALE_MODES} [options.scaleMode] - See {@link PIXI.SCALE_MODES} for possible values + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the texture being generated + */ + constructor(options) + { + if (typeof options === 'number') + { + /* eslint-disable prefer-rest-params */ + // Backward compatibility of signature + const width = arguments[0]; + const height = arguments[1]; + const scaleMode = arguments[2]; + const resolution = arguments[3]; + + options = { width, height, scaleMode, resolution }; + /* eslint-enable prefer-rest-params */ + } + + super(null, options); + + const { width, height } = options || {}; + + // Set defaults + this.mipmap = false; + this.width = Math.ceil(width) || 100; + this.height = Math.ceil(height) || 100; + this.valid = true; + + /** + * A map of renderer IDs to webgl renderTargets + * + * @private + * @member {object} + */ + // this._glRenderTargets = {}; + + /** + * A reference to the canvas render target (we only need one as this can be shared across renderers) + * + * @private + * @member {object} + */ + this._canvasRenderTarget = null; + + this.clearColor = [0, 0, 0, 0]; + + this.frameBuffer = new FrameBuffer(width * this.resolution, height * this.resolution) + .addColorTexture(0, this); + + // TODO - could this be added the systems? + + /** + * The data structure for the stencil masks + * + * @member {PIXI.Graphics[]} + */ + this.stencilMaskStack = []; + + /** + * The data structure for the filters + * + * @member {PIXI.Graphics[]} + */ + this.filterStack = [{}]; + } + + /** + * Resizes the BaseRenderTexture. + * + * @param {number} width - The width to resize to. + * @param {number} height - The height to resize to. + */ + resize(width, height) + { + width = Math.ceil(width); + height = Math.ceil(height); + this.frameBuffer.resize(width * this.resolution, height * this.resolution); + } + + /** + * Destroys this texture + * + */ + destroy() + { + super.destroy(true); + this.renderer = null; + } +} diff --git a/packages/core/src/renderTexture/RenderTexture.js b/packages/core/src/renderTexture/RenderTexture.js new file mode 100644 index 0000000..f1ecf1f --- /dev/null +++ b/packages/core/src/renderTexture/RenderTexture.js @@ -0,0 +1,153 @@ +import BaseRenderTexture from './BaseRenderTexture'; +import Texture from '../textures/Texture'; + +/** + * A RenderTexture is a special texture that allows any PixiJS display object to be rendered to it. + * + * __Hint__: All DisplayObjects (i.e. Sprites) that render to a RenderTexture should be preloaded + * otherwise black rectangles will be drawn instead. + * + * A RenderTexture takes a snapshot of any Display Object given to its render method. For example: + * + * ```js + * let renderer = PIXI.autoDetectRenderer(1024, 1024, { view: canvas, ratio: 1 }); + * let renderTexture = PIXI.RenderTexture.create(800, 600); + * let sprite = PIXI.Sprite.fromImage("spinObj_01.png"); + * + * sprite.position.x = 800/2; + * sprite.position.y = 600/2; + * sprite.anchor.x = 0.5; + * sprite.anchor.y = 0.5; + * + * renderer.render(sprite, renderTexture); + * ``` + * + * The Sprite in this case will be rendered using its local transform. To render this sprite at 0,0 + * you can clear the transform + * + * ```js + * + * sprite.setTransform() + * + * let renderTexture = new PIXI.RenderTexture.create(100, 100); + * + * renderer.render(sprite, renderTexture); // Renders to center of RenderTexture + * ``` + * + * @class + * @extends PIXI.Texture + * @memberof PIXI + */ +export default class RenderTexture extends Texture +{ + /** + * @param {PIXI.BaseRenderTexture} baseRenderTexture - The renderer used for this RenderTexture + * @param {PIXI.Rectangle} [frame] - The rectangle frame of the texture to show + */ + constructor(baseRenderTexture, frame) + { + // support for legacy.. + let _legacyRenderer = null; + + if (!(baseRenderTexture instanceof BaseRenderTexture)) + { + /* eslint-disable prefer-rest-params, no-console */ + const width = arguments[1]; + const height = arguments[2]; + const scaleMode = arguments[3]; + const resolution = arguments[4]; + + // we have an old render texture.. + console.warn(`Please use RenderTexture.create(${width}, ${height}) instead of the ctor directly.`); + _legacyRenderer = arguments[0]; + /* eslint-enable prefer-rest-params, no-console */ + + frame = null; + baseRenderTexture = new BaseRenderTexture({ + width, + height, + scaleMode, + resolution, + }); + } + + /** + * The base texture object that this texture uses + * + * @member {BaseTexture} + */ + super(baseRenderTexture, frame); + + this.legacyRenderer = _legacyRenderer; + + /** + * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. + * + * @member {boolean} + */ + this.valid = true; + + /** + * FilterSystem temporary storage + * @private + * @member {PIXI.Rectangle} + */ + this.filterFrame = null; + + this._updateUvs(); + } + + /** + * Resizes the RenderTexture. + * + * @param {number} width - The width to resize to. + * @param {number} height - The height to resize to. + * @param {boolean} [resizeBaseTexture=true] - Should the baseTexture.width and height values be resized as well? + */ + resize(width, height, resizeBaseTexture = true) + { + width = Math.ceil(width); + height = Math.ceil(height); + + // TODO - could be not required.. + this.valid = (width > 0 && height > 0); + + this._frame.width = this.orig.width = width; + this._frame.height = this.orig.height = height; + + if (resizeBaseTexture) + { + this.baseTexture.resize(width, height); + } + + this._updateUvs(); + } + + /** + * A short hand way of creating a render texture. + * + * @param {object} [options] - Options + * @param {number} [options.width=100] - The width of the render texture + * @param {number} [options.height=100] - The height of the render texture + * @param {number} [options.scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the texture being generated + * @return {PIXI.RenderTexture} The new render texture + */ + static create(options) + { + // fallback, old-style: create(width, height, scaleMode, resolution) + if (typeof options === 'number') + { + /* eslint-disable prefer-rest-params */ + options = { + width: options, + height: arguments[1], + scaleMode: arguments[2], + resolution: arguments[3], + }; + /* eslint-enable prefer-rest-params */ + } + + return new RenderTexture(new BaseRenderTexture(options)); + } +} diff --git a/packages/core/src/renderTexture/RenderTextureSystem.js b/packages/core/src/renderTexture/RenderTextureSystem.js new file mode 100644 index 0000000..8d0ca61 --- /dev/null +++ b/packages/core/src/renderTexture/RenderTextureSystem.js @@ -0,0 +1,115 @@ +import System from '../System'; +import { Rectangle } from '@pixi/math'; + +const tempRect = new Rectangle(); + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class RenderTextureSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.clearColor = renderer._backgroundColorRgba; + + // TODO moe this property somewhere else! + this.defaultMaskStack = []; + this.defaultFilterStack = [{}]; + + // empty render texture? + this.renderTexture = null; + + this.destinationFrame = new Rectangle(); + } + + bind(renderTexture, sourceFrame, destinationFrame) + { + // TODO - do we want this?? + if (this.renderTexture === renderTexture) return; + this.renderTexture = renderTexture; + + const renderer = this.renderer; + + if (renderTexture) + { + const baseTexture = renderTexture.baseTexture; + + if (!destinationFrame) + { + tempRect.width = baseTexture.realWidth; + tempRect.height = baseTexture.realHeight; + + destinationFrame = tempRect; + } + + if (!sourceFrame) + { + sourceFrame = destinationFrame; + } + + this.renderer.framebuffer.bind(baseTexture.frameBuffer, destinationFrame); + + this.renderer.projection.update(destinationFrame, sourceFrame, baseTexture.resolution, false); + this.renderer.stencil.setMaskStack(baseTexture.stencilMaskStack); + } + else + { + // TODO these validation checks happen deeper down.. + // thing they can be avoided.. + if (!destinationFrame) + { + tempRect.width = renderer.width; + tempRect.height = renderer.height; + + destinationFrame = tempRect; + } + + if (!sourceFrame) + { + sourceFrame = destinationFrame; + } + + renderer.framebuffer.bind(null, destinationFrame); + + // TODO store this.. + this.renderer.projection.update(destinationFrame, sourceFrame, this.renderer.resolution, true); + this.renderer.stencil.setMaskStack(this.defaultMaskStack); + } + + this.destinationFrame.copyFrom(destinationFrame); + } + + /** + * Erases the render texture and fills the drawing area with a colour + * + * @param {number} [clearColor] - The colour + * @return {PIXI.Renderer} Returns itself. + */ + clear(clearColor) + { + if (this.renderTexture) + { + clearColor = clearColor || this.renderTexture.baseTexture.clearColor; + } + else + { + clearColor = clearColor || this.clearColor; + } + + this.renderer.framebuffer.clear(clearColor[0], clearColor[1], clearColor[2], clearColor[3]); + } + + resize()// screenWidth, screenHeight) + { + // resize the root only! + this.bind(null); + } +} diff --git a/packages/core/src/renderers/AbstractRenderer.js b/packages/core/src/renderers/AbstractRenderer.js deleted file mode 100644 index 928670f..0000000 --- a/packages/core/src/renderers/AbstractRenderer.js +++ /dev/null @@ -1,353 +0,0 @@ -import { hex2string, hex2rgb } from '@pixi/utils'; -import { Matrix, Rectangle } from '@pixi/math'; -import { RENDERER_TYPE } from '@pixi/constants'; -import { settings } from '@pixi/settings'; -import { Container } from '@pixi/display'; -import RenderTexture from '../textures/RenderTexture'; -import EventEmitter from 'eventemitter3'; - -const tempMatrix = new Matrix(); - -/** - * 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 EventEmitter - * @memberof PIXI - */ -export default class AbstractRenderer extends EventEmitter -{ - // eslint-disable-next-line valid-jsdoc - /** - * @param {string} system - The name of the system this renderer is for. - * @param {object} [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.transparent=false] - If the render view is transparent, default false - * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false - * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) - * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. The - * resolution of the renderer retina would be 2. - * @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 {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when rendering, - * stopping pixel interpolation. - */ - constructor(system, options, arg2, arg3) - { - super(); - - // Support for constructor(system, screenWidth, screenHeight, options) - if (typeof options === 'number') - { - options = Object.assign({ - width: options, - height: arg2 || settings.RENDER_OPTIONS.height, - }, arg3); - } - - // 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 = RENDERER_TYPE.UNKNOWN; - - /** - * Measurements of the screen. (0, 0, screenWidth, screenHeight) - * - * Its safe to use as filterArea or hitArea for 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 1 - */ - this.resolution = options.resolution || settings.RESOLUTION; - - /** - * Whether the render view is transparent - * - * @member {boolean} - */ - this.transparent = options.transparent; - - /** - * Whether css dimensions of canvas view should be resized to screen dimensions automatically - * - * @member {boolean} - */ - this.autoResize = options.autoResize || false; - - /** - * Tracks the blend modes useful for this renderer. - * - * @member {object} - */ - this.blendModes = null; - - /** - * 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; - - /** - * If true PixiJS will Math.floor() x/y values when rendering, stopping pixel interpolation. - * Handy for crisp pixel art and speed on legacy devices. - * - * @member {boolean} - */ - this.roundPixels = options.roundPixels; - - /** - * The background color as a number. - * - * @member {number} - * @private - */ - this._backgroundColor = 0x000000; - - /** - * The background color as an [R, G, B] array. - * - * @member {number[]} - * @private - */ - this._backgroundColorRgba = [0, 0, 0, 0]; - - /** - * The background color as a string. - * - * @member {string} - * @private - */ - this._backgroundColorString = '#000000'; - - this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter - - /** - * This temporary display object used as the parent of the currently being rendered item - * - * @member {PIXI.DisplayObject} - * @private - */ - this._tempDisplayObjectParent = new Container(); - - /** - * The last root object that the renderer tried to render. - * - * @member {PIXI.DisplayObject} - * @private - */ - this._lastObjectRendered = this._tempDisplayObjectParent; - - /** - * Collection of plugins. - * @readonly - * @member {object} - */ - this.plugins = {}; - } - - /** - * Initialize the plugins. - * - * @protected - * @param {object} staticMap - The dictionary of staticly saved plugins. - */ - initPlugins(staticMap) - { - 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() - { - return this.view.width; - } - - /** - * Same as view.height, actual number of pixels in the canvas by vertical - * - * @member {number} - * @readonly - * @default 600 - */ - get height() - { - return this.view.height; - } - - /** - * Resizes the screen and canvas to the specified width and height - * Canvas dimensions are multiplied by resolution - * - * @param {number} screenWidth - the new width of the screen - * @param {number} screenHeight - the new height of the screen - */ - resize(screenWidth, screenHeight) - { - this.screen.width = screenWidth; - this.screen.height = screenHeight; - - this.view.width = screenWidth * this.resolution; - this.view.height = screenHeight * this.resolution; - - if (this.autoResize) - { - this.view.style.width = `${screenWidth}px`; - this.view.style.height = `${screenHeight}px`; - } - } - - /** - * 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 {PIXI.DisplayObject} displayObject - The displayObject the object will be generated from - * @param {number} scaleMode - Should be one of the scaleMode consts - * @param {number} resolution - The resolution / device pixel ratio of the texture being generated - * @param {PIXI.Rectangle} [region] - The region of the displayObject, that shall be rendered, - * if no region is specified, defaults to the local bounds of the displayObject. - * @return {PIXI.Texture} a texture of the graphics object - */ - generateTexture(displayObject, scaleMode, resolution, region) - { - region = region || displayObject.getLocalBounds(); - - // 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(region.width | 0, region.height | 0, scaleMode, resolution); - - tempMatrix.tx = -region.x; - tempMatrix.ty = -region.y; - - this.render(displayObject, renderTexture, false, tempMatrix, true); - - return renderTexture; - } - - /** - * Removes everything from the renderer and optionally removes the Canvas DOM element. - * - * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. - */ - destroy(removeView) - { - 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); - } - - this.plugins = null; - - this.type = RENDERER_TYPE.UNKNOWN; - - this.view = null; - - this.screen = null; - - this.resolution = 0; - - this.transparent = false; - - this.autoResize = false; - - this.blendModes = null; - - this.options = null; - - this.preserveDrawingBuffer = false; - this.clearBeforeRender = false; - - this.roundPixels = false; - - this._backgroundColor = 0; - this._backgroundColorRgba = null; - this._backgroundColorString = null; - - this._tempDisplayObjectParent = null; - this._lastObjectRendered = null; - } - - /** - * The background color to fill if not transparent - * - * @member {number} - */ - get backgroundColor() - { - return this._backgroundColor; - } - - set backgroundColor(value) // eslint-disable-line require-jsdoc - { - this._backgroundColor = value; - this._backgroundColorString = hex2string(value); - hex2rgb(value, this._backgroundColorRgba); - } -} diff --git a/packages/core/src/renderers/Renderer.js b/packages/core/src/renderers/Renderer.js deleted file mode 100644 index a2020b1..0000000 --- a/packages/core/src/renderers/Renderer.js +++ /dev/null @@ -1,329 +0,0 @@ -import AbstractRenderer from './AbstractRenderer'; -import { sayHello } from '@pixi/utils'; -import MaskSystem from './systems/MaskSystem'; -import StencilSystem from './systems/StencilSystem'; -import FilterSystem from './systems/filter/FilterSystem'; -import FramebufferSystem from './systems/FramebufferSystem'; -import RenderTextureSystem from './systems/RenderTextureSystem'; -import TextureSystem from './systems/textures/TextureSystem'; -import ProjectionSystem from './systems/ProjectionSystem'; -import StateSystem from './systems/StateSystem'; -import GeometrySystem from './systems/geometry/GeometrySystem'; -import ShaderSystem from './systems/shader/ShaderSystem'; -import ContextSystem from './systems/ContextSystem'; -import BatchSystem from './systems/BatchSystem'; -import TextureGCSystem from './systems/textures/TextureGCSystem'; -import { RENDERER_TYPE } from '@pixi/constants'; -import UniformGroup from '../shader/UniformGroup'; -import { Matrix } from '@pixi/math'; -import Runner from 'mini-runner'; - -/** - * 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 Render works by automatically managing webGLBatchs. - * 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 :) - * - * @class - * @memberof PIXI - * @extends PIXI.AbstractRenderer - */ -export default class Renderer extends AbstractRenderer -{ - // eslint-disable-next-line valid-jsdoc - /** - * - * @param {object} [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.transparent=false] - If the render view is transparent, default false - * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false - * @param {boolean} [options.antialias=false] - sets antialias. If not available natively then FXAA - * antialiasing is used - * @param {boolean} [options.forceFXAA=false] - forces FXAA antialiasing to be used over native. - * FXAA is faster, but may not always look as great - * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. - * The resolution of the renderer retina would be 2. - * @param {boolean} [options.clearBeforeRender=true] - This sets if the renderer will clear - * the canvas or not before the new render pass. If you wish to set this to false, you *must* set - * preserveDrawingBuffer to `true`. - * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, - * enable this if you need to call toDataUrl on the webgl context. - * @param {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when - * rendering, stopping pixel interpolation. - * @param {number} [options.backgroundColor=0x000000] - The background color of the rendered area - * (shown if not transparent). - * @param {boolean} [options.legacy=false] - If true PixiJS will aim to ensure compatibility - * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. - * @param {string} [options.powerPreference] - Parameter passed to webgl context, set to "high-performance" - * for devices with dual graphics card - */ - constructor(options, arg2, arg3) - { - super('WebGL', options, arg2, arg3); - - /** - * The type of this renderer as a standardised const - * - * @member {number} - * @see PIXI.RENDERER_TYPE - */ - this.type = RENDERER_TYPE.WEBGL; - - // this will be set by the contextSystem (this.context) - this.gl = null; - this.CONTEXT_UID = 0; - this.legacy = !!options.legacy; - - // TODO legacy! - - // runners! - this.runners = { - destroy: new Runner('destroy'), - contextChange: new Runner('contextChange', 1), - reset: new Runner('reset'), - update: new Runner('update'), - postrender: new Runner('postrender'), - prerender: new Runner('prerender'), - resize: new Runner('resize', 2), - }; - - 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(GeometrySystem, 'geometry') - .addSystem(FramebufferSystem, 'framebuffer') - .addSystem(StencilSystem, 'stencil') - .addSystem(ProjectionSystem, 'projection') - .addSystem(TextureGCSystem, 'textureGC') - .addSystem(FilterSystem, 'filter') - .addSystem(RenderTextureSystem, 'renderTexture') - .addSystem(BatchSystem, 'batch'); - - this.initPlugins(Renderer.__plugins); - - /** - * The options passed in to create a new webgl context. - * - * @member {object} - * @private - */ - if (options.context) - { - this.context.initFromContext(options.context); - } - else - { - this.context.initFromOptions({ - alpha: this.transparent, - antialias: options.antialias, - premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', - stencil: true, - 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); - } - - addSystem(_class, name) - { - if (!name) - { - name = _class.name; - } - - // TODO - read name from class.name.. - - /* - if(name.includes('System')) - { - name = name.replace('System', ''); - name = name.charAt(0).toLowerCase() + name.slice(1); - } - */ - - const system = new _class(this); - - if (this[name]) - { - throw new Error(`Whoops! ${name} is already a manger`); - } - - this[name] = system; - - for (const i in this.runners) - { - this.runners[i].add(system); - } - - return this; - - /** - * 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. - */ - } - - /** - * Renders the object to its webGL view - * - * @param {PIXI.DisplayObject} displayObject - the object to be rendered - * @param {PIXI.RenderTexture} renderTexture - The render texture to render to. - * @param {boolean} [clear] - Should the canvas be cleared before the new render - * @param {PIXI.Transform} [transform] - A transform to apply to the render texture before rendering. - * @param {boolean} [skipUpdateTransform] - Should we skip the update transform pass? - */ - render(displayObject, renderTexture, clear, transform, skipUpdateTransform) - { - // can be handy to know! - this.renderingToScreen = !renderTexture; - - this.runners.prerender.run(); - this.emit('prerender'); - - // 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.parent; - - displayObject.parent = this._tempDisplayObjectParent; - displayObject.updateTransform(); - displayObject.parent = 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.run(); - - this.emit('postrender'); - } - - /** - * Resizes the webGL view to the specified width and height. - * - * @param {number} screenWidth - the new width of the screen - * @param {number} screenHeight - the new height of the screen - */ - resize(screenWidth, screenHeight) - { - AbstractRenderer.prototype.resize.call(this, screenWidth, screenHeight); - - this.runners.resize.run(screenWidth, screenHeight); - } - - /** - * Resets the WebGL state so you can render things however you fancy! - * - * @return {PIXI.Renderer} Returns itself. - */ - reset() - { - this.runners.reset.run(); - - return this; - } - - clear() - { - this.framebuffer.bind(); - this.framebuffer.clear(); - } - - /** - * Removes everything from the renderer (event listeners, spritebatch, etc...) - * - * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. - * See: https://github.com/pixijs/pixi.js/issues/2233 - */ - destroy(removeView) - { - this.runners.destroy.run(); - - // call base destroy - super.destroy(removeView); - - // TODO nullify all the managers.. - this.gl = null; - } - - /** - * 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 - * builds and excluding plugins. - * @name PIXI.Renderer#plugins - * @type {object} - * @readonly - * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. - * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. - * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. - * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. - */ - - /** - * Adds a plugin to the renderer. - * - * @method - * @param {string} pluginName - The name of the plugin. - * @param {Function} ctor - The constructor function or class for the plugin. - */ - static registerPlugin(pluginName, ctor) - { - Renderer.__plugins = Renderer.__plugins || {}; - Renderer.__plugins[pluginName] = ctor; - } -} diff --git a/packages/core/src/renderers/State.js b/packages/core/src/renderers/State.js deleted file mode 100644 index fa3f221..0000000 --- a/packages/core/src/renderers/State.js +++ /dev/null @@ -1,163 +0,0 @@ -/* eslint-disable max-len */ - -const BLEND = 0; -const OFFSET = 1; -const CULLING = 2; -const DEPTH_TEST = 3; -const WINDING = 4; - -/** - * This is a webGL state. It is passed The WebGL StateManager. - * Each mesh renderered may require webGL to be in a different state. - * For example you may want different blend mode or to enable polygon offsets - * - * @class - * @memberof PIXI - */ -export default class State -{ - /** - * - */ - constructor() - { - this.data = 0; - - this.blendMode = 0; - this.polygonOffset = 0; - - this.blend = true; - // this.depthTest = true; - } - - /** - * Activates blending of the computed fragment color values - * - * @member {boolean} - */ - get blend() - { - return !!(this.data & (1 << BLEND)); - } - - set blend(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << BLEND)) !== value) - { - this.data ^= (1 << BLEND); - } - } - - /** - * Activates adding an offset to depth values of polygon's fragments - * - * @member {boolean} - * @default false - */ - get offsets() - { - return !!(this.data & (1 << OFFSET)); - } - - set offsets(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << OFFSET)) !== value) - { - this.data ^= (1 << OFFSET); - } - } - - /** - * Activates culling of polygons. - * - * @member {boolean} - * @default false - */ - get culling() - { - return !!(this.data & (1 << CULLING)); - } - - set culling(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << CULLING)) !== value) - { - this.data ^= (1 << CULLING); - } - } - - /** - * Activates depth comparisons and updates to the depth buffer. - * - * @member {boolean} - * @default false - */ - get depthTest() - { - return !!(this.data & (1 << DEPTH_TEST)); - } - - set depthTest(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << DEPTH_TEST)) !== value) - { - this.data ^= (1 << DEPTH_TEST); - } - } - - /** - * Specifies whether or not front or back-facing polygons can be culled. - * @member {boolean} - * @default false - */ - get clockwiseFrontFace() - { - return !!(this.data & (1 << WINDING)); - } - - set clockwiseFrontFace(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << WINDING)) !== value) - { - this.data ^= (1 << WINDING); - } - } - - /** - * The blend mode to be applied when this state is set. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. - * Setting this mode to anything other than NO_BLEND will automatically switch blending on. - * - * @member {boolean} - * @default PIXI.BLEND_MODES.NORMAL - * @see PIXI.BLEND_MODES - */ - get blendMode() - { - return this._blendMode; - } - - set blendMode(value) // eslint-disable-line require-jsdoc - { - // 17 is NO BLEND - this.blend = (value !== 17); - this._blendMode = value; - } - - /** - * The polygon offset. Setting this property to anything other than 0 will automatically enable poygon offset fill. - * - * @member {number} - * @default 0 - */ - get polygonOffset() - { - return this._polygonOffset; - } - - set polygonOffset(value) // eslint-disable-line require-jsdoc - { - this.offsets = !!value; - this._polygonOffset = value; - } -} - diff --git a/packages/core/src/renderers/filters/Filter.js b/packages/core/src/renderers/filters/Filter.js deleted file mode 100644 index 2073b8d..0000000 --- a/packages/core/src/renderers/filters/Filter.js +++ /dev/null @@ -1,140 +0,0 @@ -import Shader from '../../shader/Shader'; -import Program from '../../shader/Program'; -import State from '../State'; -import { settings } from '@pixi/settings'; -// import extractUniformsFromSrc from './extractUniformsFromSrc'; -import defaultVertex from './defaultFilter.vert'; -import defaultFragment from './defaultFilter.frag'; - -// let math = require('../../math'); -/** - * @class - * @memberof PIXI - * @extends PIXI.Shader - */ -export default class Filter extends Shader -{ - /** - * @param {string} [vertexSrc] - The source of the vertex shader. - * @param {string} [fragmentSrc] - The source of the fragment shader. - * @param {object} [uniforms] - Custom uniforms to use to augment the built-in ones. - */ - constructor(vertexSrc, fragmentSrc, uniforms) - { - const program = Program.from(vertexSrc, fragmentSrc); - - super(program, uniforms); - - /** - * The padding of the filter. Some filters require extra space to breath such as a blur. - * Increasing this will add extra width and height to the bounds of the object that the - * filter is applied to. - * - * @member {number} - */ - this.padding = 0; - - /** - * The resolution of the filter. Setting this to be lower will lower the quality but - * increase the performance of the filter. - * - * @member {number} - */ - this.resolution = settings.RESOLUTION; - - /** - * If enabled is true the filter is applied, if false it will not. - * - * @member {boolean} - */ - this.enabled = true; - - /** - * If enabled, PixiJS will fit the filter area into boundaries for better performance. - * Switch it off if it does not work for specific shader. - * - * @member {boolean} - */ - this.autoFit = true; - - /** - * Legacy filters use position and uvs from attributes - * @member {boolean} - * @readonly - */ - this.legacy = !!this.program.attributeData.aTextureCoord; - - /** - * the webGL state the filter requires to render - * @member {PIXI.State} - */ - this.state = new State(); - } - - /** - * Applies the filter - * - * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from - * @param {PIXI.RenderTarget} input - The input render target. - * @param {PIXI.RenderTarget} output - The target to output to. - * @param {boolean} clear - Should the output be cleared before rendering to it - * @param {object} [currentState] - It's current state of filter. - * There are some useful properties in the currentState : - * target, filters, sourceFrame, destinationFrame, renderTarget, resolution - */ - apply(filterManager, input, output, clear, currentState, derp) // eslint-disable-line no-unused-vars - { - // do as you please! - - filterManager.applyFilter(this, input, output, clear, currentState, derp); - - // or just do a regular render.. - } - - /** - * Sets the blendmode of the filter - * - * @member {number} - * @default PIXI.BLEND_MODES.NORMAL - */ - get blendMode() - { - return this.state.blendMode; - } - - set blendMode(value) // eslint-disable-line require-jsdoc - { - this.state.blendMode = value; - } - - /** - * The default vertex shader source - * - * @static - * @constant - */ - static get defaultVertexSrc() - { - return defaultVertex; - } - - /** - * The default fragment shader source - * - * @static - * @constant - */ - static get defaultFragmentSrc() - { - return defaultFragment; - } -} - -/** - * Used for caching shader IDs - * - * @static - * @private - */ -Filter.SOURCE_KEY_MAP = {}; - diff --git a/packages/core/src/renderers/filters/defaultFilter.frag b/packages/core/src/renderers/filters/defaultFilter.frag deleted file mode 100644 index a14c1be..0000000 --- a/packages/core/src/renderers/filters/defaultFilter.frag +++ /dev/null @@ -1,22 +0,0 @@ -varying vec2 vTextureCoord; -varying vec2 vFilterCoord; - -uniform sampler2D uSampler; -uniform sampler2D filterSampler; - -void main(void){ - vec4 masky = texture2D(filterSampler, vFilterCoord); - vec4 sample = texture2D(uSampler, vTextureCoord); - vec4 color; - if(mod(vFilterCoord.x, 1.0) > 0.5) - { - color = vec4(1.0, 0.0, 0.0, 1.0); - } - else - { - color = vec4(0.0, 1.0, 0.0, 1.0); - } - // gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0); - gl_FragColor = mix(sample, masky, 0.5); - gl_FragColor *= sample.a; -} diff --git a/packages/core/src/renderers/filters/defaultFilter.vert b/packages/core/src/renderers/filters/defaultFilter.vert deleted file mode 100644 index e1febca..0000000 --- a/packages/core/src/renderers/filters/defaultFilter.vert +++ /dev/null @@ -1,14 +0,0 @@ -attribute vec2 aVertexPosition; -attribute vec2 aTextureCoord; - -uniform mat3 projectionMatrix; -uniform mat3 filterMatrix; - -varying vec2 vTextureCoord; -varying vec2 vFilterCoord; - -void main(void){ - gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); - vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy; - vTextureCoord = aTextureCoord ; -} diff --git a/packages/core/src/renderers/filters/filterTransforms.js b/packages/core/src/renderers/filters/filterTransforms.js deleted file mode 100644 index 87a3cd6..0000000 --- a/packages/core/src/renderers/filters/filterTransforms.js +++ /dev/null @@ -1,50 +0,0 @@ -import { Matrix } from '@pixi/math'; - -/** - * Calculates the mapped matrix - * @param filterArea {Rectangle} The filter area - * @param sprite {Sprite} the target sprite - * @param outputMatrix {Matrix} @alvin - * @private - */ -// this returns a matrix that will normalise map filter cords in the filter to screen space -export function calculateScreenSpaceMatrix(outputMatrix, filterArea, textureSize) -{ - // TODO unwrap? - const mappedMatrix = outputMatrix.identity(); - - mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); - - mappedMatrix.scale(textureSize.width, textureSize.height); - - return mappedMatrix; -} - -export function calculateNormalizedScreenSpaceMatrix(outputMatrix, filterArea, textureSize) -{ - const mappedMatrix = outputMatrix.identity(); - - mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); - - const translateScaleX = (textureSize.width / filterArea.width); - const translateScaleY = (textureSize.height / filterArea.height); - - mappedMatrix.scale(translateScaleX, translateScaleY); - - return mappedMatrix; -} - -// this will map the filter coord so that a texture can be used based on the transform of a sprite -export function calculateSpriteMatrix(outputMatrix, filterArea, textureSize, sprite) -{ - const orig = sprite._texture.orig; - const mappedMatrix = outputMatrix.set(textureSize.width, 0, 0, textureSize.height, filterArea.x, filterArea.y); - const worldTransform = sprite.worldTransform.copyTo(Matrix.TEMP_MATRIX); - - worldTransform.invert(); - mappedMatrix.prepend(worldTransform); - mappedMatrix.scale(1.0 / orig.width, 1.0 / orig.height); - mappedMatrix.translate(sprite.anchor.x, sprite.anchor.y); - - return mappedMatrix; -} diff --git a/packages/core/src/renderers/filters/spriteMask/SpriteMaskFilter.js b/packages/core/src/renderers/filters/spriteMask/SpriteMaskFilter.js deleted file mode 100644 index d5a91a4..0000000 --- a/packages/core/src/renderers/filters/spriteMask/SpriteMaskFilter.js +++ /dev/null @@ -1,63 +0,0 @@ -import Filter from '../Filter'; -import { Matrix } from '@pixi/math'; -import vertex from './spriteMaskFilter.vert'; -import fragment from './spriteMaskFilter.frag'; -import { default as TextureMatrix } from '../../../textures/TextureMatrix'; - -/** - * The SpriteMaskFilter class - * - * @class - * @extends PIXI.Filter - * @memberof PIXI - */ -export default class SpriteMaskFilter extends Filter -{ - /** - * @param {PIXI.Sprite} sprite - the target sprite - */ - constructor(sprite) - { - const maskMatrix = new Matrix(); - - super(vertex, fragment); - - sprite.renderable = false; - - this.maskSprite = sprite; - this.maskMatrix = maskMatrix; - } - - /** - * Applies the filter - * - * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from - * @param {PIXI.RenderTarget} input - The input render target. - * @param {PIXI.RenderTarget} output - The target to output to. - */ - apply(filterManager, input, output) - { - const maskSprite = this.maskSprite; - const tex = this.maskSprite.texture; - - if (!tex.valid) - { - return; - } - if (!tex.transform) - { - // margin = 0.0, let it bleed a bit, shader code becomes easier - // assuming that atlas textures were made with 1-pixel padding - tex.transform = new TextureMatrix(tex, 0.0); - } - tex.transform.update(); - - this.uniforms.mask = tex; - this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite) - .prepend(tex.transform.mapCoord); - this.uniforms.alpha = maskSprite.worldAlpha; - this.uniforms.maskClamp = tex.transform.uClampFrame; - - filterManager.applyFilter(this, input, output); - } -} diff --git a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.frag b/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.frag deleted file mode 100644 index 0e4aef8..0000000 --- a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.frag +++ /dev/null @@ -1,23 +0,0 @@ -varying vec2 vMaskCoord; -varying vec2 vTextureCoord; - -uniform sampler2D uSampler; -uniform sampler2D mask; -uniform float alpha; -uniform vec4 maskClamp; - -void main(void) -{ - float clip = step(3.5, - step(maskClamp.x, vMaskCoord.x) + - step(maskClamp.y, vMaskCoord.y) + - step(vMaskCoord.x, maskClamp.z) + - step(vMaskCoord.y, maskClamp.w)); - - vec4 original = texture2D(uSampler, vTextureCoord); - vec4 masky = texture2D(mask, vMaskCoord); - - original *= (masky.r * masky.a * alpha * clip); - - gl_FragColor = original; -} diff --git a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.vert b/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.vert deleted file mode 100644 index d23a30c..0000000 --- a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.vert +++ /dev/null @@ -1,16 +0,0 @@ -attribute vec2 aVertexPosition; -attribute vec2 aTextureCoord; - -uniform mat3 projectionMatrix; -uniform mat3 otherMatrix; - -varying vec2 vMaskCoord; -varying vec2 vTextureCoord; - -void main(void) -{ - gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); - - vTextureCoord = aTextureCoord; - vMaskCoord = ( otherMatrix * vec3( aTextureCoord, 1.0) ).xy; -} diff --git a/packages/core/src/renderers/systems/BatchSystem.js b/packages/core/src/renderers/systems/BatchSystem.js deleted file mode 100644 index 70e9518..0000000 --- a/packages/core/src/renderers/systems/BatchSystem.js +++ /dev/null @@ -1,68 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import ObjectRenderer from '../utils/ObjectRenderer'; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ - -export default class BatchSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this manager works for. - */ - constructor(renderer) - { - super(renderer); - - /** - * An empty renderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.emptyRenderer = new ObjectRenderer(renderer); - - /** - * The currently active ObjectRenderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.currentRenderer = this.emptyRenderer; - } - - /** - * Changes the current renderer to the one given in parameter - * - * @param {PIXI.ObjectRenderer} objectRenderer - The object renderer to use. - */ - setObjectRenderer(objectRenderer) - { - if (this.currentRenderer === objectRenderer) - { - return; - } - - this.currentRenderer.stop(); - this.currentRenderer = objectRenderer; - - this.renderer.state.setState(objectRenderer.state); - - this.currentRenderer.start(); - } - - /** - * This should be called if you wish to do some custom rendering - * It will basically render anything that may be batched up such as sprites - * - */ - flush() - { - this.setObjectRenderer(this.emptyRenderer); - } - - reset() - { - this.setObjectRenderer(this.emptyRenderer); - } -} diff --git a/packages/core/src/renderers/systems/ContextSystem.js b/packages/core/src/renderers/systems/ContextSystem.js deleted file mode 100644 index 43bf827..0000000 --- a/packages/core/src/renderers/systems/ContextSystem.js +++ /dev/null @@ -1,186 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { settings } from '@pixi/settings'; - -let CONTEXT_UID = 0; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class ContextSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this.webGLVersion = 1; - - this.handleContextLost = this.handleContextLost.bind(this); - this.handleContextRestored = this.handleContextRestored.bind(this); - - this.extensions = {}; - - renderer.view.addEventListener('webglcontextlost', this.handleContextLost, false); - renderer.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); - } - - get isLost() - { - return (!this.gl || this.gl.isContextLost()); - } - - contextChange(gl) - { - this.gl = gl; - - // restore a context if it was previously lost - if (gl.isContextLost() && gl.getExtension('WEBGL_lose_context')) - { - gl.getExtension('WEBGL_lose_context').restoreContext(); - } - } - - initFromContext(gl) - { - this.gl = gl; - this.validateContext(gl); - this.renderer.gl = gl; - this.renderer.CONTEXT_UID = CONTEXT_UID++; - this.renderer.runners.contextChange.run(gl); - } - - initFromOptions(options) - { - const gl = this.createContext(this.renderer.view, options); - - this.initFromContext(gl); - } - - /** - * Helper class to create a webGL Context - * - * @class - * @memberof PIXI.glCore - * @param canvas {HTMLCanvasElement} the canvas element that we will get the context from - * @param options {Object} An options object that gets passed in to the canvas element containing the context attributes, - * see https://developer.mozilla.org/en/docs/Web/API/HTMLCanvasElement/getContext for the options available - * @return {WebGLRenderingContext} the WebGL context - */ - createContext(canvas, options) - { - let gl; - - if (settings.PREFER_WEBGL_2) - { - gl = canvas.getContext('webgl2', options); - } - - if (gl) - { - this.webGLVersion = 2; - } - else - { - this.webGLVersion = 1; - - gl = canvas.getContext('webgl', options) - || canvas.getContext('experimental-webgl', options); - - if (!gl) - { - // fail, not able to get a context - throw new Error('This browser does not support webGL. Try using the canvas renderer'); - } - } - - this.gl = gl; - - this.getExtensions(); - - return gl; - } - - getExtensions() - { - // time to set up default etensions that pixi uses.. - const gl = this.gl; - const extensions = this.extensions; - - if (this.webGLVersion === 1) - { - extensions.drawBuffers = gl.getExtension('WEBGL_draw_buffers'); - extensions.depthTexture = gl.getExtension('WEBKIT_WEBGL_depth_texture'); - extensions.floatTexture = gl.getExtension('OES_texture_float'); - extensions.loseContext = gl.getExtension('WEBGL_lose_context'); - - extensions.vertexArrayObject = gl.getExtension('OES_vertex_array_object') - || gl.getExtension('MOZ_OES_vertex_array_object') - || gl.getExtension('WEBKIT_OES_vertex_array_object'); - } - - // we don't use any specific WebGL 2 ones yet! - } - - /** - * Handles a lost webgl context - * - * @private - * @param {WebGLContextEvent} event - The context lost event. - */ - handleContextLost(event) - { - event.preventDefault(); - } - - /** - * Handles a restored webgl context - * - * @private - */ - handleContextRestored() - { - this.renderer.runners.contextChange.run(this.gl); - } - - destroy() - { - const view = this.renderer.view; - - // remove listeners - view.removeEventListener('webglcontextlost', this.handleContextLost); - view.removeEventListener('webglcontextrestored', this.handleContextRestored); - - this.gl.useProgram(null); - - if (this.extensions.loseContext) - { - this.extensions.loseContext.loseContext(); - } - } - - postrender() - { - this.gl.flush(); - } - - validateContext(gl) - { - const attributes = gl.getContextAttributes(); - - // this is going to be fairly simple for now.. but at least we have room to grow! - if (!attributes.stencil) - { - /* eslint-disable max-len */ - - /* eslint-disable no-console */ - console.warn('Provided WebGL context does not have a stencil buffer, masks may not render correctly'); - /* eslint-enable no-console */ - - /* eslint-enable max-len */ - } - } -} diff --git a/packages/core/src/renderers/systems/FramebufferSystem.js b/packages/core/src/renderers/systems/FramebufferSystem.js deleted file mode 100644 index df7902b..0000000 --- a/packages/core/src/renderers/systems/FramebufferSystem.js +++ /dev/null @@ -1,246 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { Rectangle } from '@pixi/math'; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class FramebufferSystem extends WebGLSystem -{ - /** - * Sets up the renderer context and necessary buffers. - * - * @private - */ - contextChange() - { - this.gl = this.renderer.gl; - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - this.current = null; - this.viewport = new Rectangle(); - - this.drawBufferExtension = this.renderer.context.extensions.drawBuffers; - } - - bind(framebuffer, frame) - { - const gl = this.gl; - - this.current = framebuffer; - - if (framebuffer) - { - // TODO cacheing layer! - - const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID] || this.initFramebuffer(framebuffer); - - gl.bindFramebuffer(gl.FRAMEBUFFER, fbo.framebuffer); - // makesure all textures are unbound.. - - // now check for updates... - if (fbo.dirtyId !== framebuffer.dirtyId) - { - fbo.dirtyId = framebuffer.dirtyId; - - if (fbo.dirtyFormat !== framebuffer.dirtyFormat) - { - fbo.dirtyFormat = framebuffer.dirtyFormat; - this.updateFramebuffer(framebuffer); - } - else if (fbo.dirtySize !== framebuffer.dirtySize) - { - fbo.dirtySize = framebuffer.dirtySize; - this.resizeFramebuffer(framebuffer); - } - } - - for (let i = 0; i < framebuffer.colorTextures.length; i++) - { - if (framebuffer.colorTextures[i].texturePart) - { - this.renderer.texture.unbind(framebuffer.colorTextures[i].texture); - } - else - { - this.renderer.texture.unbind(framebuffer.colorTextures[i]); - } - } - - if (framebuffer.depthTexture) - { - this.renderer.texture.unbind(framebuffer.depthTexture); - } - - if (frame) - { - this.setViewport(frame.x, frame.y, frame.width, frame.height); - } - else - { - this.setViewport(0, 0, framebuffer.width, framebuffer.height); - } - } - else - { - gl.bindFramebuffer(gl.FRAMEBUFFER, null); - - if (frame) - { - this.setViewport(frame.x, frame.y, frame.width, frame.height); - } - else - { - this.setViewport(0, 0, this.renderer.width, this.renderer.height); - } - } - } - - setViewport(x, y, width, height) - { - const v = this.viewport; - - if (v.width !== width || v.height !== height || v.x !== x || v.y !== y) - { - v.x = x; - v.y = y; - v.width = width; - v.height = height; - - this.gl.viewport(x, y, width, height); - } - } - - get size() - { - if (this.current) - { - // TODO store temp - return { x: 0, y: 0, width: this.current.width, height: this.current.height }; - } - - return { x: 0, y: 0, width: this.renderer.width, height: this.renderer.height }; - } - - clear(r, g, b, a) - { - const gl = this.gl; - - // TODO clear color can be set only one right? - gl.clearColor(r, g, b, a); - gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); - } - - // private functions... - - initFramebuffer(framebuffer) - { - const gl = this.gl; - - // TODO - make this a class? - const fbo = { - framebuffer: gl.createFramebuffer(), - stencil: null, - dirtyId: 0, - dirtyFormat: 0, - dirtySize: 0, - }; - - framebuffer.glFrameBuffers[this.CONTEXT_UID] = fbo; - - return fbo; - } - - resizeFramebuffer(framebuffer) - { - const gl = this.gl; - - if (framebuffer.stencil || framebuffer.depth) - { - gl.bindRenderbuffer(gl.RENDERBUFFER, this.stencil); - gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); - } - } - - updateFramebuffer(framebuffer) - { - const gl = this.gl; - - const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID]; - - // bind the color texture - const colorTextures = framebuffer.colorTextures; - - let count = colorTextures.length; - - if (!this.drawBufferExtension) - { - count = Math.min(count, 1); - } - - const activeTextures = []; - - for (let i = 0; i < count; i++) - { - const texture = framebuffer.colorTextures[i]; - - if (texture.texturePart) - { - this.renderer.texture.bind(texture.texture, 0); - - gl.framebufferTexture2D(gl.FRAMEBUFFER, - gl.COLOR_ATTACHMENT0 + i, - gl.TEXTURE_CUBE_MAP_NEGATIVE_X + texture.side, - texture.texture._glTextures[this.CONTEXT_UID].texture, - 0); - } - else - { - this.renderer.texture.bind(texture, 0); - - gl.framebufferTexture2D(gl.FRAMEBUFFER, - gl.COLOR_ATTACHMENT0 + i, - gl.TEXTURE_2D, - texture._glTextures[this.CONTEXT_UID].texture, - 0); - } - - activeTextures.push(gl.COLOR_ATTACHMENT0 + i); - } - - if (this.drawBufferExtension && activeTextures.length > 1) - { - this.drawBufferExtension.drawBuffersWEBGL(activeTextures); - } - - if (framebuffer.depthTexture) - { - const depthTextureExt = this.renderer.context.extensions.depthTexture; - - if (depthTextureExt) - { - const depthTexture = framebuffer.depthTexture; - - this.renderer.texture.bind(depthTexture, 0); - - gl.framebufferTexture2D(gl.FRAMEBUFFER, - gl.DEPTH_ATTACHMENT, - gl.TEXTURE_2D, - depthTexture._glTextures[this.CONTEXT_UID].texture, - 0); - } - } - - if (framebuffer.stencil || framebuffer.depth) - { - fbo.stencil = gl.createRenderbuffer(); - - gl.bindRenderbuffer(gl.RENDERBUFFER, fbo.stencil); - - // TODO.. this is depth AND stencil? - gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, fbo.stencil); - gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); - // fbo.enableStencil(); - } - } -} diff --git a/packages/core/src/renderers/systems/MaskSystem.js b/packages/core/src/renderers/systems/MaskSystem.js deleted file mode 100644 index d9c4b1a..0000000 --- a/packages/core/src/renderers/systems/MaskSystem.js +++ /dev/null @@ -1,199 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import AlphaMaskFilter from '../filters/spriteMask/SpriteMaskFilter'; - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class MaskSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - // TODO - we don't need both! - this.scissor = false; - this.scissorData = null; - this.scissorRenderTarget = null; - - this.enableScissor = false; - - this.alphaMaskPool = []; - this.alphaMaskIndex = 0; - } - - /** - * Applies the Mask and adds it to the current filter stack. - * - * @param {PIXI.DisplayObject} target - Display Object to push the mask to - * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. - */ - push(target, maskData) - { - // TODO the root check means scissor rect will not - // be used on render textures more info here: - // https://github.com/pixijs/pixi.js/pull/3545 - - if (maskData.texture) - { - this.pushSpriteMask(target, maskData); - } - else if (this.enableScissor - && !this.scissor - && this.renderer._activeRenderTarget.root - && !this.renderer.stencil.stencilMaskStack.length - && maskData.isFastRect()) - { - const matrix = maskData.worldTransform; - - let rot = Math.atan2(matrix.b, matrix.a); - - // use the nearest degree! - rot = Math.round(rot * (180 / Math.PI)); - - if (rot % 90) - { - this.pushStencilMask(maskData); - } - else - { - this.pushScissorMask(target, maskData); - } - } - else - { - this.pushStencilMask(maskData); - } - } - - /** - * Removes the last mask from the mask stack and doesn't return it. - * - * @param {PIXI.DisplayObject} target - Display Object to pop the mask from - * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. - */ - pop(target, maskData) - { - if (maskData.texture) - { - this.popSpriteMask(target, maskData); - } - else if (this.enableScissor && !this.renderer.stencil.stencilMaskStack.length) - { - this.popScissorMask(target, maskData); - } - else - { - this.popStencilMask(target, maskData); - } - } - - /** - * Applies the Mask and adds it to the current filter stack. - * - * @param {PIXI.RenderTarget} target - Display Object to push the sprite mask to - * @param {PIXI.Sprite} maskData - Sprite to be used as the mask - */ - pushSpriteMask(target, maskData) - { - let alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; - - if (!alphaMaskFilter) - { - alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; - } - - alphaMaskFilter[0].resolution = this.renderer.resolution; - alphaMaskFilter[0].maskSprite = maskData; - - // TODO - may cause issues! - target.filterArea = maskData.getBounds(true); - - this.renderer.filterSystem.pushFilter(target, alphaMaskFilter); - - this.alphaMaskIndex++; - } - - /** - * Removes the last filter from the filter stack and doesn't return it. - * - */ - popSpriteMask() - { - this.renderer.filterSystem.popFilter(); - this.alphaMaskIndex--; - } - - /** - * Applies the Mask and adds it to the current filter stack. - * - * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. - */ - pushStencilMask(maskData) - { - this.renderer.batch.flush(); - this.renderer.stencil.pushStencil(maskData); - } - - /** - * Removes the last filter from the filter stack and doesn't return it. - * - */ - popStencilMask() - { - // this.renderer.currentRenderer.stop(); - this.renderer.stencil.popStencil(); - } - - /** - * - * @param {PIXI.DisplayObject} target - Display Object to push the mask to - * @param {PIXI.Graphics} maskData - The masking data. - */ - pushScissorMask(target, maskData) - { - maskData.renderable = true; - - const renderTarget = this.renderer._activeRenderTarget; - - const bounds = maskData.getBounds(); - - bounds.fit(renderTarget.size); - maskData.renderable = false; - - this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); - - const resolution = this.renderer.resolution; - - this.renderer.gl.scissor( - bounds.x * resolution, - (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, - bounds.width * resolution, - bounds.height * resolution - ); - - this.scissorRenderTarget = renderTarget; - this.scissorData = maskData; - this.scissor = true; - } - - /** - * - * - */ - popScissorMask() - { - this.scissorRenderTarget = null; - this.scissorData = null; - this.scissor = false; - - // must be scissor! - const gl = this.renderer.gl; - - gl.disable(gl.SCISSOR_TEST); - } -} diff --git a/packages/core/src/renderers/systems/ProjectionSystem.js b/packages/core/src/renderers/systems/ProjectionSystem.js deleted file mode 100644 index ec5efdb..0000000 --- a/packages/core/src/renderers/systems/ProjectionSystem.js +++ /dev/null @@ -1,73 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { Matrix } from '@pixi/math'; - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ - -export default class ProjectionSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this.projectionMatrix = new Matrix(); - } - - update(destinationFrame, sourceFrame, resolution, root) - { - this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; - this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; - - this.calculateProjection(this.destinationFrame, this.sourceFrame, resolution, root); - - this.renderer.globalUniforms.uniforms.projectionMatrix = this.projectionMatrix; - this.renderer.globalUniforms.update(); - } - - /** - * Updates the projection matrix based on a projection frame (which is a rectangle) - * - * @param {Rectangle} destinationFrame - The destination frame. - * @param {Rectangle} sourceFrame - The source frame. - */ - calculateProjection(destinationFrame, sourceFrame, resolution, root) - { - const pm = this.projectionMatrix; - - // I don't think we will need this line.. - // pm.identity(); - - if (!root) - { - pm.a = (1 / destinationFrame.width * 2) * resolution; - pm.d = (1 / destinationFrame.height * 2) * resolution; - - pm.tx = -1 - (sourceFrame.x * pm.a); - pm.ty = -1 - (sourceFrame.y * pm.d); - } - else - { - pm.a = (1 / destinationFrame.width * 2) * resolution; - pm.d = (-1 / destinationFrame.height * 2) * resolution; - - pm.tx = -1 - (sourceFrame.x * pm.a); - pm.ty = 1 - (sourceFrame.y * pm.d); - } - } - - /** - * Sets the transform of the active render target to the given matrix - * - * @param {PIXI.Matrix} matrix - The transformation matrix - */ - setTransform()// matrix) - { - // this._activeRenderTarget.transform = matrix; - } -} diff --git a/packages/core/src/renderers/systems/RenderTextureSystem.js b/packages/core/src/renderers/systems/RenderTextureSystem.js deleted file mode 100644 index a133ef1..0000000 --- a/packages/core/src/renderers/systems/RenderTextureSystem.js +++ /dev/null @@ -1,115 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { Rectangle } from '@pixi/math'; - -const tempRect = new Rectangle(); - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ - -export default class RenderTextureSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this.clearColor = renderer._backgroundColorRgba; - - // TODO moe this property somewhere else! - this.defaultMaskStack = []; - this.defaultFilterStack = [{}]; - - // empty render texture? - this.renderTexture = null; - - this.destinationFrame = new Rectangle(); - } - - bind(renderTexture, sourceFrame, destinationFrame) - { - // TODO - do we want this?? - if (this.renderTexture === renderTexture) return; - this.renderTexture = renderTexture; - - const renderer = this.renderer; - - if (renderTexture) - { - const baseTexture = renderTexture.baseTexture; - - if (!destinationFrame) - { - tempRect.width = baseTexture.realWidth; - tempRect.height = baseTexture.realHeight; - - destinationFrame = tempRect; - } - - if (!sourceFrame) - { - sourceFrame = destinationFrame; - } - - this.renderer.framebuffer.bind(baseTexture.frameBuffer, destinationFrame); - - this.renderer.projection.update(destinationFrame, sourceFrame, baseTexture.resolution, false); - this.renderer.stencil.setMaskStack(baseTexture.stencilMaskStack); - } - else - { - // TODO these validation checks happen deeper down.. - // thing they can be avoided.. - if (!destinationFrame) - { - tempRect.width = renderer.width; - tempRect.height = renderer.height; - - destinationFrame = tempRect; - } - - if (!sourceFrame) - { - sourceFrame = destinationFrame; - } - - renderer.framebuffer.bind(null, destinationFrame); - - // TODO store this.. - this.renderer.projection.update(destinationFrame, sourceFrame, this.renderer.resolution, true); - this.renderer.stencil.setMaskStack(this.defaultMaskStack); - } - - this.destinationFrame.copyFrom(destinationFrame); - } - - /** - * Erases the render texture and fills the drawing area with a colour - * - * @param {number} [clearColor] - The colour - * @return {PIXI.Renderer} Returns itself. - */ - clear(clearColor) - { - if (this.renderTexture) - { - clearColor = clearColor || this.renderTexture.baseTexture.clearColor; - } - else - { - clearColor = clearColor || this.clearColor; - } - - this.renderer.framebuffer.clear(clearColor[0], clearColor[1], clearColor[2], clearColor[3]); - } - - resize()// screenWidth, screenHeight) - { - // resize the root only! - this.bind(null); - } -} diff --git a/packages/core/src/renderers/systems/StateSystem.js b/packages/core/src/renderers/systems/StateSystem.js deleted file mode 100755 index d7f4caf..0000000 --- a/packages/core/src/renderers/systems/StateSystem.js +++ /dev/null @@ -1,288 +0,0 @@ -import mapWebGLBlendModesToPixi from '../utils/mapWebGLBlendModesToPixi'; -import WebGLSystem from './WebGLSystem'; -import WebGLState from '../State'; - -const BLEND = 0; -const OFFSET = 1; -const CULLING = 2; -const DEPTH_TEST = 3; -const WINDING = 4; - -/** - * A WebGL state machines - * - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class StateSystem extends WebGLSystem -{ - /** - * @param {WebGLRenderingContext} gl - The current WebGL rendering context - */ - constructor(renderer) - { - super(renderer); - - this.gl = null; - - this.maxAttribs = null; - - // check we have vao.. - this.nativeVaoExtension = null; - - this.attribState = null; - - this.stateId = 0; - this.polygonOffset = 0; - this.blendMode = 17; - - this.map = []; - - // map functions for when we set state.. - this.map[BLEND] = this.setBlend; - this.map[OFFSET] = this.setOffset; - this.map[CULLING] = this.setCullFace; - this.map[DEPTH_TEST] = this.setDepthTest; - this.map[WINDING] = this.setFrontFace; - - this.checks = []; - - this.defaultState = new WebGLState(); - this.defaultState.blend = true; - this.defaultState.depth = true; - } - - contextChange(gl) - { - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; - - this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); - - // check we have vao.. - this.nativeVaoExtension = ( - gl.getExtension('OES_vertex_array_object') - || gl.getExtension('MOZ_OES_vertex_array_object') - || gl.getExtension('WEBKIT_OES_vertex_array_object') - ); - - this.attribState = { - tempAttribState: new Array(this.maxAttribs), - attribState: new Array(this.maxAttribs), - }; - - this.blendModes = mapWebGLBlendModesToPixi(gl); - - this.setState(this.defaultState); - - this.reset(); - } - - /** - * Sets the current state - * - * @param {*} state - The state to set. - */ - setState(state) - { - state = state || this.defaultState; - - // TODO maybe to an object check? ( this.state === state )? - if (this.stateId !== state.data) - { - let diff = this.stateId ^ state.data; - let i = 0; - - // order from least to most common - while (diff) - { - if (diff & 1) - { - // state change! - this.map[i].call(this, !!(state.data & (1 << i))); - } - - diff = diff >> 1; - i++; - } - - this.stateId = state.data; - } - - // based on the above settings we check for specific modes.. - // for example if blend is active we check and set the blend modes - // or of polygon offset is active we check the poly depth. - for (let i = 0; i < this.checks.length; i++) - { - this.checks[i](this, state); - } - } - - /** - * Enables or disabled blending. - * - * @param {boolean} value - Turn on or off webgl blending. - */ - setBlend(value) - { - this.updateCheck(StateSystem.checkBlendMode, value); - - this.gl[value ? 'enable' : 'disable'](this.gl.BLEND); - } - - /** - * Enables or disable polygon offset fill - * - * @param {boolean} value - Turn on or off webgl polygon offset testing. - */ - setOffset(value) - { - this.gl[value ? 'enable' : 'disable'](this.gl.POLYGON_OFFSET_FILL); - } - - /** - * Sets whether to enable or disable depth test. - * - * @param {boolean} value - Turn on or off webgl depth testing. - */ - setDepthTest(value) - { - this.gl[value ? 'enable' : 'disable'](this.gl.DEPTH_TEST); - } - - /** - * Sets whether to enable or disable cull face. - * - * @param {boolean} value - Turn on or off webgl cull face. - */ - setCullFace(value) - { - this.gl[value ? 'enable' : 'disable'](this.gl.CULL_FACE); - } - - /** - * Sets the gl front face. - * - * @param {boolean} value - true is clockwise and false is counter-clockwise - */ - setFrontFace(value) - { - this.gl.frontFace(this.gl[value ? 'CW' : 'CCW']); - } - - /** - * Sets the blend mode. - * - * @param {number} value - The blend mode to set to. - */ - setBlendMode(value) - { - if (value === this.blendMode) - { - return; - } - - this.blendMode = value; - this.gl.blendFunc(this.blendModes[value][0], this.blendModes[value][1]); - } - - /** - * Sets the polygon offset. - * - * @param {number} value - the polygon offset - * @param {number} scale - the polygon offset scale - */ - setPolygonOffset(value, scale) - { - this.gl.polygonOffset(value, scale); - } - - /** - * Disables all the vaos in use - * - */ - resetAttributes() - { - for (let i = 0; i < this.attribState.tempAttribState.length; i++) - { - this.attribState.tempAttribState[i] = 0; - } - - for (let i = 0; i < this.attribState.attribState.length; i++) - { - this.attribState.attribState[i] = 0; - } - - // im going to assume one is always active for performance reasons. - for (let i = 1; i < this.maxAttribs; i++) - { - this.gl.disableVertexAttribArray(i); - } - } - - // used - /** - * Resets all the logic and disables the vaos - */ - reset() - { - // unbind any VAO if they exist.. - if (this.nativeVaoExtension) - { - this.nativeVaoExtension.bindVertexArrayOES(null); - } - - // reset all attributes.. - this.resetAttributes(); - - this.gl.pixelStorei(this.gl.UNPACK_FLIP_Y_WEBGL, false); - - this.setBlendMode(0); - - // TODO? - // this.setState(this.defaultState); - } - - /** - * checks to see which updates should be checked based on which settings have been activated - * for example if blend is enabled then we shold check the blend modes each time the state is changed - * or if poygon fill is activated then we need to check if the polygone offset changes. - * The idea is that we only check what we have too. - * - * @param {Function} func the checking function to add or remove - * @param {boolean} value should the check function be added or removed. - */ - updateCheck(func, value) - { - const index = this.checks.indexOf(func); - - if (value && index === -1) - { - this.checks.push(func); - } - else if (!value && index !== -1) - { - this.checks.splice(index, 1); - } - } - - /** - * A private little wrapper function that we call to check the blend mode. - * - * @static - * @private - * @param {PIXI.StateSystem} System the System to perform the state check on - * @param {PIXI.State} state the state that the blendMode will pulled from - */ - static checkBlendMode(system, state) - { - system.setBlendMode(state.blendMode); - } - - // TODO - add polygon offset? -} diff --git a/packages/core/src/renderers/systems/StencilSystem.js b/packages/core/src/renderers/systems/StencilSystem.js deleted file mode 100644 index 5d538cb..0000000 --- a/packages/core/src/renderers/systems/StencilSystem.js +++ /dev/null @@ -1,133 +0,0 @@ -import WebGLSystem from './WebGLSystem'; - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class StencilSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - this.stencilMaskStack = []; - } - - /** - * Changes the mask stack that is used by this System. - * - * @param {PIXI.Graphics[]} stencilMaskStack - The mask stack - */ - setMaskStack(stencilMaskStack) - { - const gl = this.renderer.gl; - - if (stencilMaskStack.length !== this.stencilMaskStack.length) - { - if (stencilMaskStack.length === 0) - { - gl.disable(gl.STENCIL_TEST); - } - else - { - gl.enable(gl.STENCIL_TEST); - } - } - - this.stencilMaskStack = stencilMaskStack; - } - - /** - * Applies the Mask and adds it to the current stencil stack. @alvin - * - * @param {PIXI.Graphics} graphics - The mask - */ - pushStencil(graphics) - { - this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); - - // this.renderer._activeRenderTarget.attachStencilBuffer(); - - const gl = this.renderer.gl; - const prevMaskCount = this.stencilMaskStack.length; - - if (prevMaskCount === 0) - { - gl.enable(gl.STENCIL_TEST); - } - - this.stencilMaskStack.push(graphics); - - // Increment the refference stencil value where the new mask overlaps with the old ones. - gl.colorMask(false, false, false, false); - gl.stencilFunc(gl.EQUAL, prevMaskCount, this._getBitwiseMask()); - gl.stencilOp(gl.KEEP, gl.KEEP, gl.INCR); - this.renderer.plugins.graphics.render(graphics); - - this._useCurrent(); - } - - /** - * Removes the last mask from the stencil stack. @alvin - */ - popStencil() - { - this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); - - const gl = this.renderer.gl; - const graphics = this.stencilMaskStack.pop(); - - if (this.stencilMaskStack.length === 0) - { - // the stack is empty! - gl.disable(gl.STENCIL_TEST); - gl.clear(gl.STENCIL_BUFFER_BIT); - gl.clearStencil(0); - } - else - { - // Decrement the refference stencil value where the popped mask overlaps with the other ones - gl.colorMask(false, false, false, false); - gl.stencilOp(gl.KEEP, gl.KEEP, gl.DECR); - this.renderer.plugins.graphics.render(graphics); - - this._useCurrent(); - } - } - - /** - * Setup renderer to use the current stencil data. - */ - _useCurrent() - { - const gl = this.renderer.gl; - - gl.colorMask(true, true, true, true); - gl.stencilFunc(gl.EQUAL, this.stencilMaskStack.length, this._getBitwiseMask()); - gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP); - } - - /** - * Fill 1s equal to the number of acitve stencil masks. - * - * @return {number} The bitwise mask. - */ - _getBitwiseMask() - { - return (1 << this.stencilMaskStack.length) - 1; - } - - /** - * Destroys the mask stack. - * - */ - destroy() - { - super.destroy(this); - - this.stencilMaskStack = null; - } -} diff --git a/packages/core/src/renderers/systems/WebGLSystem.js b/packages/core/src/renderers/systems/WebGLSystem.js deleted file mode 100644 index d3d5b31..0000000 --- a/packages/core/src/renderers/systems/WebGLSystem.js +++ /dev/null @@ -1,41 +0,0 @@ -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this manager works for. - */ - constructor(renderer) - { - /** - * The renderer this manager works for. - * - * @member {PIXI.Renderer} - */ - this.renderer = renderer; - - this.renderer.runners.contextChange.add(this); - } - - /** - * Generic method called when there is a WebGL context change. - * - */ - contextChange() - { - // do some codes init! - } - - /** - * Generic destroy methods to be overridden by the subclass - * - */ - destroy() - { - this.renderer.runners.contextChange.remove(this); - this.renderer = null; - } -} diff --git a/packages/core/src/renderers/systems/filter/FilterSystem.js b/packages/core/src/renderers/systems/filter/FilterSystem.js deleted file mode 100644 index e615037..0000000 --- a/packages/core/src/renderers/systems/filter/FilterSystem.js +++ /dev/null @@ -1,431 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; - -import RenderTexture from '../../../textures/RenderTexture'; -import Quad from '../../utils/Quad'; -import QuadUv from '../../utils/QuadUv'; -import { Rectangle } from '@pixi/math'; -import * as filterTransforms from '../../filters/filterTransforms'; -import bitTwiddle from 'bit-twiddle'; -import UniformGroup from '../../../shader/UniformGroup'; -import { DRAW_MODES } from '../../../../../constants'; - -// -/** - * @ignore - * @class - */ -class FilterState -{ - /** - * - */ - constructor() - { - this.renderTexture = null; - this.sourceFrame = new Rectangle(); - this.destinationFrame = new Rectangle(); - this.filters = []; - this.target = null; - this.legacy = false; - this.resolution = 1; - } -} - -/** - * @class - * @memberof PIXI.systems - * @extends PIXI.systems.WebGLSystem - */ -export default class FilterSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - /** - * stores a bunch of PO2 textures used for filtering - * @type {Object} - */ - this.texturePool = {}; - - /** - * a pool for storing filter states, save us creating new ones each tick - * @type {Array} - */ - this.statePool = []; - - /** - * A very simple geometry used when drawing a filter effect to the screen - * @type {Quad} - */ - this.quad = new Quad(); - - this.quadUv = new QuadUv(); - - /** - * Temporary rect for maths - * @type {PIXI.Rectangle} - */ - this.tempRect = new Rectangle(); - - this.activeState = {}; - - /** - * this uniform group is attached to filter uniforms when used - * @type {UniformGroup} - */ - this.globalUniforms = new UniformGroup({ - sourceFrame: this.tempRect, - destinationFrame: this.tempRect, - - // legacy variables - filterArea: new Float32Array(4), - filterClamp: new Float32Array(4), - }, true); - } - - /** - * Adds a new filter to the System. - * - * @param {PIXI.DisplayObject} target - The target of the filter to render. - * @param {PIXI.Filter[]} filters - The filters to apply. - */ - push(target, filters) - { - const renderer = this.renderer; - const filterStack = this.renderer.renderTexture.defaultFilterStack; - const state = this.statePool.pop() || new FilterState(); - - let resolution = filters[0].resolution; - let padding = filters[0].padding; - let autoFit = filters[0].autoFit; - let legacy = filters[0].legacy; - - for (let i = 1; i < filters.length; i++) - { - const filter = filters[i]; - - // lets use the lowest resolution.. - resolution = Math.min(resolution, filter.resolution); - // and the largest amount of padding! - padding = Math.max(padding, filter.padding); - // only auto fit if all filters are autofit - autoFit = autoFit || filter.autoFit; - - legacy = legacy || filter.legacy; - } - - filterStack.push(state); - - state.resolution = resolution; - - state.legacy = legacy; - - // round to whole number based on resolution - // TODO move that to the shader too? - state.sourceFrame = target.filterArea || target.getBounds(true); - - state.sourceFrame.pad(padding); - - if (autoFit) - { - state.sourceFrame.fit(this.renderer.renderTexture.destinationFrame); - } - - state.sourceFrame.round(resolution); - - state.renderTexture = this.getPotFilterTexture(state.sourceFrame.width, state.sourceFrame.height, resolution); - state.filters = filters; - - state.destinationFrame.width = state.renderTexture.width; - state.destinationFrame.height = state.renderTexture.height; - - state.renderTexture.filterFrame = state.sourceFrame; - renderer.renderTexture.bind(state.renderTexture, state.sourceFrame);// /, state.destinationFrame); - renderer.renderTexture.clear(); - } - - /** - * Pops off the filter and applies it. - * - */ - pop() - { - const renderer = this.renderer; - const filterStack = renderer.renderTexture.defaultFilterStack; - const state = filterStack.pop(); - const filters = state.filters; - - this.activeState = state; - - const globalUniforms = this.globalUniforms.uniforms; - - globalUniforms.sourceFrame = state.sourceFrame; - globalUniforms.destinationFrame = state.destinationFrame; - globalUniforms.resolution = state.resolution; - - // only update the rect if its legacy.. - if (state.legacy) - { - const filterArea = globalUniforms.filterArea; - const filterClamp = globalUniforms.filterClamp; - - filterArea[0] = state.destinationFrame.width; - filterArea[1] = state.destinationFrame.height; - filterArea[2] = state.sourceFrame.x; - filterArea[3] = state.sourceFrame.y; - - filterClamp[0] = 0.5 / state.resolution / state.destinationFrame.width; - filterClamp[1] = 0.5 / state.resolution / state.destinationFrame.height; - filterClamp[2] = (state.sourceFrame.width - 0.5) / state.resolution / state.destinationFrame.width; - filterClamp[3] = (state.sourceFrame.height - 0.5) / state.resolution / state.destinationFrame.height; - } - - this.globalUniforms.update(); - - const lastState = filterStack[filterStack.length - 1]; - - if (filters.length === 1) - { - filters[0].apply(this, state.renderTexture, lastState.renderTexture, false, state); - - this.returnFilterTexture(state.renderTexture); - } - else - { - let flip = state.renderTexture; - let flop = this.getPotFilterTexture( - flip.width, - flip.height, - state.resolution - ); - - flop.filterFrame = flip.filterFrame; - - let i = 0; - - for (i = 0; i < filters.length - 1; ++i) - { - filters[i].apply(this, flip, flop, true, state); - - const t = flip; - - flip = flop; - flop = t; - } - - filters[i].apply(this, flip, lastState.renderTexture, false, state); - - this.returnFilterTexture(flip); - this.returnFilterTexture(flop); - } - - this.statePool.push(state); - } - - /** - * Draws a filter. - * - * @param {PIXI.Filter} filter - The filter to draw. - * @param {PIXI.RenderTarget} input - The input render target. - * @param {PIXI.RenderTarget} output - The target to output to. - * @param {boolean} clear - Should the output be cleared before rendering to it - */ - applyFilter(filter, input, output, clear) - { - const renderer = this.renderer; - - renderer.renderTexture.bind(output, output ? output.filterFrame : null); - - if (clear) - { - // gl.disable(gl.SCISSOR_TEST); - renderer.renderTexture.clear(); - // gl.enable(gl.SCISSOR_TEST); - } - - // set the uniforms.. - filter.uniforms.uSampler = input; - filter.uniforms.filterGlobals = this.globalUniforms; - - // TODO make it so that the order of this does not matter.. - // because it does at the moment cos of global uniforms. - // they need to get resynced - - renderer.state.setState(filter.state); - renderer.shader.bind(filter); - - if (filter.legacy) - { - this.quadUv.map(input._frame, input.filterFrame); - - renderer.geometry.bind(this.quadUv); - renderer.geometry.draw(DRAW_MODES.TRIANGLES); - } - else - { - renderer.geometry.bind(this.quad); - renderer.geometry.draw(DRAW_MODES.TRIANGLE_STRIP); - } - } - - /** - * Calculates the mapped matrix. - * - * TODO playing around here.. this is temporary - (will end up in the shader) - * this returns a matrix that will normalise map filter cords in the filter to screen space - * - * @param {PIXI.Matrix} outputMatrix - the matrix to output to. - * @return {PIXI.Matrix} The mapped matrix. - */ - calculateScreenSpaceMatrix(outputMatrix) - { - const currentState = this.activeState; - - return filterTransforms.calculateScreenSpaceMatrix( - outputMatrix, - currentState.sourceFrame, - currentState.destinationFrame - ); - } - - /** - * This will map the filter coord so that a texture can be used based on the transform of a sprite - * - * @param {PIXI.Matrix} outputMatrix - The matrix to output to. - * @param {PIXI.Sprite} sprite - The sprite to map to. - * @return {PIXI.Matrix} The mapped matrix. - */ - calculateSpriteMatrix(outputMatrix, sprite) - { - const currentState = this.activeState; - - return filterTransforms.calculateSpriteMatrix( - outputMatrix, - currentState.sourceFrame, - currentState.destinationFrame, - sprite - ); - } - - /** - * Destroys this Filter System. - * - * @param {boolean} [contextLost=false] context was lost, do not free shaders - * - */ - destroy(contextLost = false) - { - if (!contextLost) - { - this.emptyPool(); - } - else - { - this.texturePool = {}; - } - } - - /** - * Gets a Power-of-Two render texture. - * - * TODO move to a seperate class could be on renderer? - * also - could cause issue with multiple contexts? - * - * @private - * @param {WebGLRenderingContext} gl - The webgl rendering context - * @param {number} minWidth - The minimum width of the render target. - * @param {number} minHeight - The minimum height of the render target. - * @param {number} resolution - The resolution of the render target. - * @return {PIXI.RenderTarget} The new render target. - */ - getPotFilterTexture(minWidth, minHeight, resolution) - { - minWidth = bitTwiddle.nextPow2(minWidth); - minHeight = bitTwiddle.nextPow2(minHeight); - resolution = resolution || 1; - - const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); - - if (!this.texturePool[key]) - { - this.texturePool[key] = []; - } - - let renderTexture = this.texturePool[key].pop(); - - if (!renderTexture) - { - // temporary bypass cache.. - // internally - this will cause a texture to be bound.. - renderTexture = RenderTexture.create({ - width: minWidth, - height: minHeight, - resolution, - }); - } - - return renderTexture; - } - - /** - * Gets extra render texture to use inside current filter - * - * @param {number} resolution resolution of the renderTexture - * @returns {PIXI.RenderTexture} - */ - getFilterTexture(resolution) - { - const rt = this.activeState.renderTexture; - - const filterTexture = this.getPotFilterTexture(rt.width, rt.height, resolution || rt.baseTexture.resolution); - - filterTexture.filterFrame = rt.filterFrame; - - return filterTexture; - } - - /** - * Frees a render texture back into the pool. - * - * @param {PIXI.RenderTarget} renderTexture - The renderTarget to free - */ - returnFilterTexture(renderTexture) - { - renderTexture.filterFrame = null; - - const base = renderTexture.baseTexture; - - const minWidth = base.width; - const minHeight = base.height; - - const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); - - this.texturePool[key].push(renderTexture); - } - - /** - * Empties the texture pool. - * - */ - emptyPool() - { - for (const i in this.texturePool) - { - const textures = this.texturePool[i]; - - if (textures) - { - for (let j = 0; j < textures.length; j++) - { - textures[j].destroy(true); - } - } - } - - this.texturePool = {}; - } -} diff --git a/packages/core/src/renderers/systems/geometry/GLBuffer.js b/packages/core/src/renderers/systems/geometry/GLBuffer.js deleted file mode 100644 index 0477e8a..0000000 --- a/packages/core/src/renderers/systems/geometry/GLBuffer.js +++ /dev/null @@ -1,9 +0,0 @@ -export default class GLBuffer -{ - constructor(buffer) - { - this.buffer = buffer; - this.updateID = -1; - this.byteLength = -1; - } -} diff --git a/packages/core/src/renderers/systems/geometry/GeometrySystem.js b/packages/core/src/renderers/systems/geometry/GeometrySystem.js deleted file mode 100644 index 26c8e9e..0000000 --- a/packages/core/src/renderers/systems/geometry/GeometrySystem.js +++ /dev/null @@ -1,388 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; -import GLBuffer from './GLBuffer'; - -const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class GeometrySystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this._activeGeometry = null; - this._activeVao = null; - - this.hasVao = true; - this.hasInstance = true; - } - - /** - * Sets up the renderer context and necessary buffers. - * - * @private - */ - contextChange() - { - const gl = this.gl = this.renderer.gl; - - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - - // webgl2 - if (!gl.createVertexArray) - { - // webgl 1! - let nativeVaoExtension = this.renderer.context.extensions.vertexArrayObject; - - if (this.renderer.legacy) - { - nativeVaoExtension = null; - } - - if (nativeVaoExtension) - { - gl.createVertexArray = () => - nativeVaoExtension.createVertexArrayOES(); - - gl.bindVertexArray = (vao) => - nativeVaoExtension.bindVertexArrayOES(vao); - - gl.deleteVertexArray = (vao) => - nativeVaoExtension.deleteVertexArrayOES(vao); - } - else - { - this.hasVao = false; - gl.createVertexArray = () => - { - // empty - }; - - gl.bindVertexArray = () => - { - // empty - }; - - gl.deleteVertexArray = () => - { - // empty - }; - } - } - - if (!gl.vertexAttribDivisor) - { - const instanceExt = gl.getExtension('ANGLE_instanced_arrays'); - - if (instanceExt) - { - gl.vertexAttribDivisor = (a, b) => - instanceExt.vertexAttribDivisorANGLE(a, b); - - gl.drawElementsInstanced = (a, b, c, d, e) => - instanceExt.drawElementsInstancedANGLE(a, b, c, d, e); - - gl.drawArraysInstanced = (a, b, c, d) => - instanceExt.drawArraysInstancedANGLE(a, b, c, d); - } - else - { - this.hasInstance = false; - } - } - } - - /** - * Binds geometry so that is can be drawn. Creating a Vao if required - * @private - * @param {PIXI.Geometry} geometry instance of geometry to bind - */ - bind(geometry, shader) - { - shader = shader || this.renderer.shader.shader; - - const gl = this.gl; - - // not sure the best way to address this.. - // currently different shaders require different VAOs for the same geometry - // Still mulling over the best way to solve this one.. - // will likely need to modify the shader attribute locations at run time! - let vaos = geometry.glVertexArrayObjects[this.CONTEXT_UID]; - - if (!vaos) - { - geometry.glVertexArrayObjects[this.CONTEXT_UID] = vaos = {}; - } - - const vao = vaos[shader.program.id] || this.initGeometryVao(geometry, shader.program); - - this._activeGeometry = geometry; - - if (this._activeVao !== vao) - { - this._activeVao = vao; - - if (this.hasVao) - { - gl.bindVertexArray(vao); - } - else - { - this.activateVao(geometry, shader.program); - } - } - - // TODO - optimise later! - // don't need to loop through if nothing changed! - // maybe look to add an 'autoupdate' to geometry? - this.updateBuffers(); - } - - reset() - { - this.unbind(); - } - - updateBuffers() - { - const geometry = this._activeGeometry; - const gl = this.gl; - - for (let i = 0; i < geometry.buffers.length; i++) - { - const buffer = geometry.buffers[i]; - - const glBuffer = buffer._glBuffers[this.CONTEXT_UID]; - - if (buffer._updateID !== glBuffer.updateID) - { - glBuffer.updateID = buffer._updateID; - - // TODO can cache this on buffer! maybe added a getter / setter? - const type = buffer.index ? gl.ELEMENT_ARRAY_BUFFER : gl.ARRAY_BUFFER; - const drawType = buffer.static ? gl.STATIC_DRAW : gl.DYNAMIC_DRAW; - - gl.bindBuffer(type, glBuffer.buffer); - - if (glBuffer.byteLength >= buffer.data.byteLength) - { - // offset is always zero for now! - gl.bufferSubData(type, 0, buffer.data); - } - else - { - gl.bufferData(type, buffer.data, drawType); - } - } - } - } - - checkCompatability(geometry, program) - { - // geometry must have at least all the attributes that the shader requires. - const geometryAttributes = geometry.attributes; - const shaderAttributes = program.attributeData; - - for (const j in shaderAttributes) - { - if (!geometryAttributes[j]) - { - throw new Error(`shader and geometry incompatible, geometry missing the "${j}" attribute`); - } - } - } - - /** - * Creates a Vao with the same structure as the geometry and stores it on the geometry. - * @private - * @param {PIXI.Geometry} geometry instance of geometry to to generate Vao for - */ - initGeometryVao(geometry, program) - { - this.checkCompatability(geometry, program); - - const gl = this.gl; - const CONTEXT_UID = this.CONTEXT_UID; - const buffers = geometry.buffers; - const attributes = geometry.attributes; - - const tempStride = {}; - const tempStart = {}; - - for (const j in buffers) - { - tempStride[j] = 0; - tempStart[j] = 0; - } - - for (const j in attributes) - { - if (!attributes[j].size && program.attributeData[j]) - { - attributes[j].size = program.attributeData[j].size; - } - - tempStride[attributes[j].buffer] += attributes[j].size * byteSizeMap[attributes[j].type]; - } - - for (const j in attributes) - { - const attribute = attributes[j]; - const attribSize = attribute.size; - - if (attribute.stride === undefined) - { - if (tempStride[attribute.buffer] === attribSize * byteSizeMap[attribute.type]) - { - attribute.stride = 0; - } - else - { - attribute.stride = tempStride[attribute.buffer]; - } - } - - if (attribute.start === undefined) - { - attribute.start = tempStart[attribute.buffer]; - - tempStart[attribute.buffer] += attribSize * byteSizeMap[attribute.type]; - } - } - - // first update - and create the buffers! - // only create a gl buffer if it actually gets - for (let i = 0; i < buffers.length; i++) - { - const buffer = buffers[i]; - - if (!buffer._glBuffers[CONTEXT_UID]) - { - buffer._glBuffers[CONTEXT_UID] = new GLBuffer(gl.createBuffer()); - } - } - - // TODO - maybe make this a data object? - // lets wait to see if we need to first! - const vao = gl.createVertexArray(); - - gl.bindVertexArray(vao); - - this.activateVao(geometry, program); - - gl.bindVertexArray(null); - - // add it to the cache! - geometry.glVertexArrayObjects[this.CONTEXT_UID][program.id] = vao; - - return vao; - } - - activateVao(geometry, program) - { - const gl = this.gl; - const CONTEXT_UID = this.CONTEXT_UID; - const buffers = geometry.buffers; - const attributes = geometry.attributes; - - if (geometry.indexBuffer) - { - // first update the index buffer if we have one.. - gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, geometry.indexBuffer._glBuffers[CONTEXT_UID].buffer); - } - - let lastBuffer = null; - - // add a new one! - for (const j in attributes) - { - const attribute = attributes[j]; - const buffer = buffers[attribute.buffer]; - const glBuffer = buffer._glBuffers[CONTEXT_UID]; - - if (program.attributeData[j]) - { - if (lastBuffer !== glBuffer) - { - gl.bindBuffer(gl.ARRAY_BUFFER, glBuffer.buffer); - - lastBuffer = glBuffer; - } - - const location = program.attributeData[j].location; - - // TODO introduce state again - // we can optimise this for older devices that have no VAOs - gl.enableVertexAttribArray(location); - - gl.vertexAttribPointer(location, - attribute.size, - attribute.type || gl.FLOAT, - attribute.normalized, - attribute.stride, - attribute.start); - - if (attribute.instance) - { - // TODO calculate instance count based of this... - if (this.hasInstance) - { - gl.vertexAttribDivisor(location, 1); - } - else - { - throw new Error('geometry error, GPU Instancing is not supported on this device'); - } - } - } - } - } - - draw(type, size, start, instanceCount) - { - const gl = this.gl; - const geometry = this._activeGeometry; - - // TODO.. this should not change so maybe cache the function? - - if (geometry.indexBuffer) - { - if (geometry.instanced) - { - /* eslint-disable max-len */ - gl.drawElementsInstanced(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2, instanceCount || 1); - /* eslint-enable max-len */ - } - else - { - gl.drawElements(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2); - } - } - else - if (geometry.instanced) - { - // TODO need a better way to calculate size.. - gl.drawArraysInstanced(type, start, size || geometry.getSize(), instanceCount || 1); - } - else - { - gl.drawArrays(type, start, size || geometry.getSize()); - } - - return this; - } - - unbind() - { - this.gl.bindVertexArray(null); - this._activeVao = null; - this._activeGeometry = null; - } -} diff --git a/packages/core/src/renderers/systems/geometry/setVertexAttribArrays.js b/packages/core/src/renderers/systems/geometry/setVertexAttribArrays.js deleted file mode 100644 index 23b6959..0000000 --- a/packages/core/src/renderers/systems/geometry/setVertexAttribArrays.js +++ /dev/null @@ -1,54 +0,0 @@ -// var GL_MAP = {}; - -/** - * @param gl {WebGLRenderingContext} The current WebGL context - * @param attribs {*} - * @param state {*} - */ -export default function setVertexAttribArrays(gl, attribs, state) -{ - let i; - - if (state) - { - const tempAttribState = state.tempAttribState; - const attribState = state.attribState; - - for (i = 0; i < tempAttribState.length; i++) - { - tempAttribState[i] = false; - } - - // set the new attribs - for (i = 0; i < attribs.length; i++) - { - tempAttribState[attribs[i].attribute.location] = true; - } - - for (i = 0; i < attribState.length; i++) - { - if (attribState[i] !== tempAttribState[i]) - { - attribState[i] = tempAttribState[i]; - - if (state.attribState[i]) - { - gl.enableVertexAttribArray(i); - } - else - { - gl.disableVertexAttribArray(i); - } - } - } - } - else - { - for (i = 0; i < attribs.length; i++) - { - const attrib = attribs[i]; - - gl.enableVertexAttribArray(attrib.attribute.location); - } - } -} diff --git a/packages/core/src/renderers/systems/index.js b/packages/core/src/renderers/systems/index.js deleted file mode 100644 index c843f03..0000000 --- a/packages/core/src/renderers/systems/index.js +++ /dev/null @@ -1,6 +0,0 @@ -/** - * Systems are individual components to the Renderer pipeline. - * @namespace PIXI.systems - */ -export { default as WebGLSystem } from './WebGLSystem'; -export { default as FilterSystem } from './filter/FilterSystem'; diff --git a/packages/core/src/renderers/systems/shader/GLProgram.js b/packages/core/src/renderers/systems/shader/GLProgram.js deleted file mode 100644 index 92980ca..0000000 --- a/packages/core/src/renderers/systems/shader/GLProgram.js +++ /dev/null @@ -1,43 +0,0 @@ -/** - * Helper class to create a webGL Program - * - * @class - * @private - */ -export default class GLProgram -{ - constructor(program, uniformData) - { - /** - * The shader program - * - * @member {WebGLProgram} - */ - this.program = program; - - /** - * holds the uniform data which contains uniform locations - * and current uniform values used for caching and preventing undeed GPU commands - * @type {Object} - */ - this.uniformData = uniformData; - - /** - * uniformGroups holds the various upload functions for the shader. Each uniform group - * and program have a unique upload function generated. - * @type {Object} - */ - this.uniformGroups = {}; - } - - /** - * Destroys this program - * TODO - */ - destroy() - { - this.uniformData = null; - this.uniformGroups = null; - this.program = null; - } -} diff --git a/packages/core/src/renderers/systems/shader/ShaderSystem.js b/packages/core/src/renderers/systems/shader/ShaderSystem.js deleted file mode 100644 index 6cf57b6..0000000 --- a/packages/core/src/renderers/systems/shader/ShaderSystem.js +++ /dev/null @@ -1,170 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; -import GLProgram from './GLProgram'; -import generateUniformsSync from '../../../shader/generateUniformsSync'; -import defaultValue from '../../../shader/defaultValue'; -import compileProgram from './shader/compileProgram'; - -let UID = 0; - -/** - * Helper class to create a webGL Texture - * - * @class - * @memberof PIXI - */ -export default class ShaderSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - A reference to the current renderer - */ - constructor(renderer) - { - super(renderer); - - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = null; - - this.shader = null; - this.program = null; - - this.id = UID++; - } - - contextChange(gl) - { - this.gl = gl; - } - - /** - * Changes the current shader to the one given in parameter - * - * @param {PIXI.Shader} shader - the new shader - * @param {boolean} dontSync - false if the shader should automatically sync its uniforms. - * @returns {PIXI.glCore.glProgram} the glProgram that belongs to the shader. - */ - bind(shader, dontSync) - { - shader.uniforms.globals = this.renderer.globalUniforms; - - const program = shader.program; - const glProgram = program.glPrograms[this.renderer.CONTEXT_UID] || this.generateShader(shader); - - this.shader = shader; - - // TODO - some current pixi plugins bypass this.. so it not safe to use yet.. - if (this.program !== program) - { - this.program = program; - this.gl.useProgram(glProgram.program); - } - - if (!dontSync) - { - this.syncUniformGroup(shader.uniformGroup); - } - - return glProgram; - } - - /** - * Uploads the uniforms values to the currently bound shader. - * - * @param {object} uniforms - the uniforms valiues that be applied to the current shader - */ - setUniforms(uniforms) - { - const shader = this.shader.program; - const glProgram = shader.glPrograms[this.renderer.CONTEXT_UID]; - - shader.syncUniforms(glProgram.uniformData, uniforms, this.renderer); - } - - syncUniformGroup(group) - { - const glProgram = this.getglProgram(); - - if (!group.static || group.dirtyId !== glProgram.uniformGroups[group.id]) - { - glProgram.uniformGroups[group.id] = group.dirtyId; - const syncFunc = group.syncUniforms[this.shader.program.id] || this.createSyncGroups(group); - - syncFunc(glProgram.uniformData, group.uniforms, this.renderer); - } - } - - createSyncGroups(group) - { - group.syncUniforms[this.shader.program.id] = generateUniformsSync(group, this.shader.program.uniformData); - - return group.syncUniforms[this.shader.program.id]; - } - - /** - * Returns the underlying GLShade rof the currently bound shader. - * This can be handy for when you to have a little more control over the setting of your uniforms. - * - * @return {PIXI.glCore.Shader} the glProgram for the currently bound Shader for this context - */ - getglProgram() - { - if (this.shader) - { - return this.shader.program.glPrograms[this.renderer.CONTEXT_UID]; - } - - return null; - } - - /** - * Generates a glProgram verion of the Shader provided. - * - * @private - * @param {PIXI.Shader} shader the shader that the glProgram will be based on. - * @return {PIXI.glCore.glProgram} A shiney new glProgram - */ - generateShader(shader) - { - const gl = this.gl; - - const program = shader.program; - - const attribMap = {}; - - for (const i in program.attributeData) - { - attribMap[i] = program.attributeData[i].location; - } - - const shaderProgram = compileProgram(gl, program.vertexSrc, program.fragmentSrc, attribMap); - const uniformData = {}; - - for (const i in program.uniformData) - { - const data = program.uniformData[i]; - - uniformData[i] = { - location: gl.getUniformLocation(shaderProgram, i), - value: defaultValue(data.type, data.size), - }; - } - - const glProgram = new GLProgram(shaderProgram, uniformData); - - program.glPrograms[this.renderer.CONTEXT_UID] = glProgram; - - return glProgram; - } - - /** - * Destroys this System and removes all its textures - */ - destroy() - { - // TODO implement destroy method for ShaderSystem - this.destroyed = true; - } -} diff --git a/packages/core/src/renderers/systems/shader/shader/compileProgram.js b/packages/core/src/renderers/systems/shader/shader/compileProgram.js deleted file mode 100644 index 8c6ccb9..0000000 --- a/packages/core/src/renderers/systems/shader/shader/compileProgram.js +++ /dev/null @@ -1,78 +0,0 @@ -/** - * @method compileProgram - * @memberof PIXI.glCore.shader - * @param gl {WebGLRenderingContext} The current WebGL context {WebGLProgram} - * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. - * @param fragmentSrc {string|string[]} The fragment shader source as an array of strings. - * @param attributeLocations {Object} An attribute location map that lets you manually set the attribute locations - * @return {WebGLProgram} the shader program - */ -export default function compileProgram(gl, vertexSrc, fragmentSrc, attributeLocations) -{ - const glVertShader = compileShader(gl, gl.VERTEX_SHADER, vertexSrc); - const glFragShader = compileShader(gl, gl.FRAGMENT_SHADER, fragmentSrc); - - let program = gl.createProgram(); - - gl.attachShader(program, glVertShader); - gl.attachShader(program, glFragShader); - - // optionally, set the attributes manually for the program rather than letting WebGL decide.. - if (attributeLocations) - { - for (const i in attributeLocations) - { - gl.bindAttribLocation(program, attributeLocations[i], i); - } - } - - gl.linkProgram(program); - - // if linking fails, then log and cleanup - if (!gl.getProgramParameter(program, gl.LINK_STATUS)) - { - console.error('Pixi.js Error: Could not initialize shader.'); - console.error('gl.VALIDATE_STATUS', gl.getProgramParameter(program, gl.VALIDATE_STATUS)); - console.error('gl.getError()', gl.getError()); - - // if there is a program info log, log it - if (gl.getProgramInfoLog(program) !== '') - { - console.warn('Pixi.js Warning: gl.getProgramInfoLog()', gl.getProgramInfoLog(program)); - } - - gl.deleteProgram(program); - program = null; - } - - // clean up some shaders - gl.deleteShader(glVertShader); - gl.deleteShader(glFragShader); - - return program; -} - -/** - * @private - * @param gl {WebGLRenderingContext} The current WebGL context {WebGLProgram} - * @param type {Number} the type, can be either VERTEX_SHADER or FRAGMENT_SHADER - * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. - * @return {WebGLShader} the shader - */ -function compileShader(gl, type, src) -{ - const shader = gl.createShader(type); - - gl.shaderSource(shader, src); - gl.compileShader(shader); - - if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) - { - console.warn(src); - console.error(gl.getShaderInfoLog(shader)); - - return null; - } - - return shader; -} diff --git a/packages/core/src/renderers/systems/shader/shader/defaultValue.js b/packages/core/src/renderers/systems/shader/shader/defaultValue.js deleted file mode 100644 index 97290bd..0000000 --- a/packages/core/src/renderers/systems/shader/shader/defaultValue.js +++ /dev/null @@ -1,79 +0,0 @@ -/** - * @method defaultValue - * @memberof PIXI.glCore.shader - * @param type {String} Type of value - * @param size {Number} - */ -export default function defaultValue(type, size) -{ - switch (type) - { - case 'float': - return 0; - - case 'vec2': - return new Float32Array(2 * size); - - case 'vec3': - return new Float32Array(3 * size); - - case 'vec4': - return new Float32Array(4 * size); - - case 'int': - case 'sampler2D': - case 'sampler2DArray': - return 0; - - case 'ivec2': - return new Int32Array(2 * size); - - case 'ivec3': - return new Int32Array(3 * size); - - case 'ivec4': - return new Int32Array(4 * size); - - case 'bool': - return false; - - case 'bvec2': - - return booleanArray(2 * size); - - case 'bvec3': - return booleanArray(3 * size); - - case 'bvec4': - return booleanArray(4 * size); - - case 'mat2': - return new Float32Array([1, 0, - 0, 1]); - - case 'mat3': - return new Float32Array([1, 0, 0, - 0, 1, 0, - 0, 0, 1]); - - case 'mat4': - return new Float32Array([1, 0, 0, 0, - 0, 1, 0, 0, - 0, 0, 1, 0, - 0, 0, 0, 1]); - } - - return null; -} - -function booleanArray(size) -{ - const array = new Array(size); - - for (let i = 0; i < array.length; i++) - { - array[i] = false; - } - - return array; -} diff --git a/packages/core/src/renderers/systems/shader/shader/index.js b/packages/core/src/renderers/systems/shader/shader/index.js deleted file mode 100644 index ba7a317..0000000 --- a/packages/core/src/renderers/systems/shader/shader/index.js +++ /dev/null @@ -1,8 +0,0 @@ -/** - * @namespace PIXI.glCore.shader - */ -export { default as compileProgram } from './compileProgram'; -export { default as defaultValue } from './defaultValue'; -export { default as setPrecision } from './setPrecision'; -export { default as mapSize } from './mapSize'; -export { default as mapType } from './mapType'; diff --git a/packages/core/src/renderers/systems/shader/shader/mapSize.js b/packages/core/src/renderers/systems/shader/shader/mapSize.js deleted file mode 100644 index b681554..0000000 --- a/packages/core/src/renderers/systems/shader/shader/mapSize.js +++ /dev/null @@ -1,33 +0,0 @@ -const GLSL_TO_SIZE = { - float: 1, - vec2: 2, - vec3: 3, - vec4: 4, - - int: 1, - ivec2: 2, - ivec3: 3, - ivec4: 4, - - bool: 1, - bvec2: 2, - bvec3: 3, - bvec4: 4, - - mat2: 4, - mat3: 9, - mat4: 16, - - sampler2D: 1, -}; - -/** - * @method mapSize - * @memberof PIXI.glCore.shader - * @param type {String} - * @return {Number} - */ -export default function mapSize(type) -{ - return GLSL_TO_SIZE[type]; -} diff --git a/packages/core/src/renderers/systems/shader/shader/mapType.js b/packages/core/src/renderers/systems/shader/shader/mapType.js deleted file mode 100644 index d288b7c..0000000 --- a/packages/core/src/renderers/systems/shader/shader/mapType.js +++ /dev/null @@ -1,45 +0,0 @@ -let GL_TABLE = null; - -const GL_TO_GLSL_TYPES = { - FLOAT: 'float', - FLOAT_VEC2: 'vec2', - FLOAT_VEC3: 'vec3', - FLOAT_VEC4: 'vec4', - - INT: 'int', - INT_VEC2: 'ivec2', - INT_VEC3: 'ivec3', - INT_VEC4: 'ivec4', - - BOOL: 'bool', - BOOL_VEC2: 'bvec2', - BOOL_VEC3: 'bvec3', - BOOL_VEC4: 'bvec4', - - FLOAT_MAT2: 'mat2', - FLOAT_MAT3: 'mat3', - FLOAT_MAT4: 'mat4', - - SAMPLER_2D: 'sampler2D', - SAMPLER_CUBE: 'samplerCube', - SAMPLER_2D_ARRAY: 'sampler2DArray', -}; - -export default function mapType(gl, type) -{ - if (!GL_TABLE) - { - const typeNames = Object.keys(GL_TO_GLSL_TYPES); - - GL_TABLE = {}; - - for (let i = 0; i < typeNames.length; ++i) - { - const tn = typeNames[i]; - - GL_TABLE[gl[tn]] = GL_TO_GLSL_TYPES[tn]; - } - } - - return GL_TABLE[type]; -} diff --git a/packages/core/src/renderers/systems/shader/shader/setPrecision.js b/packages/core/src/renderers/systems/shader/shader/setPrecision.js deleted file mode 100644 index e2c1f34..0000000 --- a/packages/core/src/renderers/systems/shader/shader/setPrecision.js +++ /dev/null @@ -1,18 +0,0 @@ -/** - * Sets the float precision on the shader. If the precision is already present this function will do nothing - * @param {string} src the shader source - * @param {string} precision The float precision of the shader. Options are 'lowp', 'mediump' or 'highp'. - * - * @return {string} modified shader source - */ -export default function setPrecision(src, precision) -{ - src = src.trim(); - - if (src.substring(0, 9) !== 'precision' && src.substring(0, 1) !== '#') - { - return `precision ${precision} float;\n${src}`; - } - - return src; -} diff --git a/packages/core/src/renderers/systems/textures/GLTexture.js b/packages/core/src/renderers/systems/textures/GLTexture.js deleted file mode 100644 index c3fb8dc..0000000 --- a/packages/core/src/renderers/systems/textures/GLTexture.js +++ /dev/null @@ -1,33 +0,0 @@ -export default class GLTexture -{ - constructor(texture) - { - /** - * The WebGL texture - * - * @member {WebGLTexture} - */ - this.texture = texture; - - this.width = -1; - this.height = -1; - - /** - * Texture contents dirty flag - * @member {number} - */ - this.dirtyId = -1; - - /** - * Texture style dirty flag - * @type {number} - */ - this.dirtyStyleId = -1; - - /** - * Whether mip levels has to be generated - * @type {boolean} - */ - this.mipmap = false; - } -} diff --git a/packages/core/src/renderers/systems/textures/TextureGCSystem.js b/packages/core/src/renderers/systems/textures/TextureGCSystem.js deleted file mode 100644 index b089938..0000000 --- a/packages/core/src/renderers/systems/textures/TextureGCSystem.js +++ /dev/null @@ -1,110 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; -import { GC_MODES } from '@pixi/constants'; -import { settings } from '@pixi/settings'; - -/** - * TextureGarbageCollector. This class manages the GPU and ensures that it does not get clogged - * up with textures that are no longer being used. - * - * @class - * @memberof PIXI - */ -export default class TextureGCSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this.count = 0; - this.checkCount = 0; - this.maxIdle = settings.GC_MAX_IDLE; - this.checkCountMax = settings.GC_MAX_CHECK_COUNT; - this.mode = settings.GC_MODE; - } - - /** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ - postrender() - { - this.count++; - - if (this.mode === GC_MODES.MANUAL) - { - return; - } - - this.checkCount++; - - if (this.checkCount > this.checkCountMax) - { - this.checkCount = 0; - - this.run(); - } - } - - /** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ - run() - { - const tm = this.renderer.texture; - const managedTextures = tm.managedTextures; - let wasRemoved = false; - - for (let i = 0; i < managedTextures.length; i++) - { - const texture = managedTextures[i]; - - // only supports non generated textures at the moment! - if (!texture.frameBuffer && this.count - texture.touched > this.maxIdle) - { - tm.destroyTexture(texture, true); - managedTextures[i] = null; - wasRemoved = true; - } - } - - if (wasRemoved) - { - let j = 0; - - for (let i = 0; i < managedTextures.length; i++) - { - if (managedTextures[i] !== null) - { - managedTextures[j++] = managedTextures[i]; - } - } - - managedTextures.length = j; - } - } - - /** - * Removes all the textures within the specified displayObject and its children from the GPU - * - * @param {PIXI.DisplayObject} displayObject - the displayObject to remove the textures from. - */ - unload(displayObject) - { - const tm = this.renderer.textureSystem; - - // only destroy non generated textures - if (displayObject._texture && displayObject._texture._glRenderTargets) - { - tm.destroyTexture(displayObject._texture); - } - - for (let i = displayObject.children.length - 1; i >= 0; i--) - { - this.unload(displayObject.children[i]); - } - } -} diff --git a/packages/core/src/renderers/systems/textures/TextureSystem.js b/packages/core/src/renderers/systems/textures/TextureSystem.js deleted file mode 100644 index ccc5de9..0000000 --- a/packages/core/src/renderers/systems/textures/TextureSystem.js +++ /dev/null @@ -1,281 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; -import GLTexture from './GLTexture'; -import { removeItems } from '@pixi/utils'; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class TextureSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - // TODO set to max textures... - this.boundTextures = [ - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - ]; - - this.currentLocation = -1; - - this.managedTextures = []; - } - - /** - * Sets up the renderer context and necessary buffers. - * - * @private - */ - contextChange() - { - const gl = this.gl = this.renderer.gl; - - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - - // TODO move this.. to a nice make empty textures class.. - this.emptyTextures = {}; - - const emptyTexture2D = new GLTexture(gl.createTexture()); - - gl.bindTexture(gl.TEXTURE_2D, emptyTexture2D.texture); - gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array(4)); - - this.emptyTextures[gl.TEXTURE_2D] = emptyTexture2D; - this.emptyTextures[gl.TEXTURE_CUBE_MAP] = new GLTexture(gl.createTexture()); - - gl.bindTexture(gl.TEXTURE_CUBE_MAP, this.emptyTextures[gl.TEXTURE_CUBE_MAP].texture); - - let i; - - for (i = 0; i < 6; i++) - { - gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, null); - } - - gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_MAG_FILTER, gl.LINEAR); - gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_MIN_FILTER, gl.LINEAR); - - for (i = 0; i < this.boundTextures.length; i++) - { - this.bind(null, i); - } - } - - bind(texture, location) - { - const gl = this.gl; - - location = location || 0; - - if (this.currentLocation !== location) - { - this.currentLocation = location; - gl.activeTexture(gl.TEXTURE0 + location); - } - - if (texture) - { - texture = texture.baseTexture || texture; - - if (texture.valid) - { - texture.touched = this.renderer.textureGC.count; - - const glTexture = texture._glTextures[this.CONTEXT_UID] || this.initTexture(texture); - - gl.bindTexture(texture.target, glTexture.texture); - - if (glTexture.dirtyId !== texture.dirtyId) - { - this.updateTexture(texture); - } - - this.boundTextures[location] = texture; - } - } - else - { - gl.bindTexture(gl.TEXTURE_2D, this.emptyTextures[gl.TEXTURE_2D].texture); - this.boundTextures[location] = null; - } - } - - unbind(texture) - { - const gl = this.gl; - - for (let i = 0; i < this.boundTextures.length; i++) - { - if (this.boundTextures[i] === texture) - { - if (this.currentLocation !== i) - { - gl.activeTexture(gl.TEXTURE0 + i); - this.currentLocation = i; - } - - gl.bindTexture(gl.TEXTURE_2D, this.emptyTextures[texture.target].texture); - this.boundTextures[i] = null; - } - } - } - - initTexture(texture) - { - const glTexture = new GLTexture(this.gl.createTexture()); - - // guarentee an update.. - glTexture.dirtyId = -1; - - texture._glTextures[this.CONTEXT_UID] = glTexture; - - this.managedTextures.push(texture); - texture.on('dispose', this.destroyTexture, this); - - return glTexture; - } - - updateTexture(texture) - { - const glTexture = texture._glTextures[this.CONTEXT_UID]; - const renderer = this.renderer; - - if (texture.resource && texture.resource.upload(renderer, texture, glTexture)) - { - // texture is uploaded, dont do anything! - } - else - { - // default, renderTexture-like logic - const width = texture.realWidth; - const height = texture.realHeight; - const gl = renderer.gl; - - if (glTexture.width !== width - || glTexture.height !== height - || glTexture.dirtyId < 0) - { - glTexture.width = width; - glTexture.height = height; - - gl.texImage2D(texture.target, 0, - texture.format, - width, - height, - 0, - texture.format, - texture.type, - null); - } - } - - // lets only update what changes.. - if (texture.dirtyStyleId !== glTexture.dirtyStyleId) - { - this.updateTextureStyle(texture); - } - glTexture.dirtyId = texture.dirtyId; - } - - /** - * Deletes the texture from WebGL - * - * @param {PIXI.BaseTexture|PIXI.Texture} texture - the texture to destroy - * @param {boolean} [skipRemove=false] - Whether to skip removing the texture from the TextureManager. - */ - destroyTexture(texture, skipRemove) - { - const gl = this.gl; - - texture = texture.baseTexture || texture; - - if (texture._glTextures[this.renderer.CONTEXT_UID]) - { - this.unbind(texture); - - gl.deleteTexture(texture._glTextures[this.renderer.CONTEXT_UID].texture); - texture.off('dispose', this.destroyTexture, this); - - delete texture._glTextures[this.renderer.CONTEXT_UID]; - - if (!skipRemove) - { - const i = this.managedTextures.indexOf(texture); - - if (i !== -1) - { - removeItems(this.managedTextures, i, 1); - } - } - } - } - - updateTextureStyle(texture) - { - const glTexture = texture._glTextures[this.CONTEXT_UID]; - - glTexture.mipmap = texture.mipmap && texture.isPowerOfTwo; - if (!glTexture) - { - return; - } - - if (texture.resource && texture.resource.style(this.renderer, texture, glTexture)) - { - // style is set, dont do anything! - } - else - { - this.setStyle(texture, glTexture); - } - - glTexture.dirtyStyleId = texture.dirtyStyleId; - } - - setStyle(texture, glTexture) - { - const gl = this.gl; - - if (glTexture.mipmap) - { - gl.generateMipmap(texture.target); - } - - gl.texParameteri(texture.target, gl.TEXTURE_WRAP_S, texture.wrapMode); - gl.texParameteri(texture.target, gl.TEXTURE_WRAP_T, texture.wrapMode); - - if (glTexture.mipmap) - { - /* eslint-disable max-len */ - gl.texParameteri(texture.target, gl.TEXTURE_MIN_FILTER, texture.scaleMode ? gl.LINEAR_MIPMAP_LINEAR : gl.NEAREST_MIPMAP_NEAREST); - /* eslint-disable max-len */ - } - else - { - gl.texParameteri(texture.target, gl.TEXTURE_MIN_FILTER, texture.scaleMode ? gl.LINEAR : gl.NEAREST); - } - - gl.texParameteri(texture.target, gl.TEXTURE_MAG_FILTER, texture.scaleMode ? gl.LINEAR : gl.NEAREST); - } -} diff --git a/packages/core/src/renderers/utils/ObjectRenderer.js b/packages/core/src/renderers/utils/ObjectRenderer.js deleted file mode 100644 index f67a273..0000000 --- a/packages/core/src/renderers/utils/ObjectRenderer.js +++ /dev/null @@ -1,48 +0,0 @@ -import WebGLSystem from '../systems/WebGLSystem'; - -/** - * Base for a common object renderer that can be used as a system renderer plugin. - * - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class ObjectRenderer extends WebGLSystem -{ - /** - * Starts the renderer and sets the shader - * - */ - start() - { - // set the shader.. - } - - /** - * Stops the renderer - * - */ - stop() - { - this.flush(); - } - - /** - * Stub method for rendering content and emptying the current batch. - * - */ - flush() - { - // flush! - } - - /** - * Renders an object - * - * @param {PIXI.DisplayObject} object - The object to render. - */ - render(object) // eslint-disable-line no-unused-vars - { - // render the object - } -} diff --git a/packages/core/src/renderers/utils/Quad.js b/packages/core/src/renderers/utils/Quad.js deleted file mode 100644 index 5c1517b..0000000 --- a/packages/core/src/renderers/utils/Quad.js +++ /dev/null @@ -1,26 +0,0 @@ -import Geometry from '../../geometry/Geometry'; - -/** - * Helper class to create a quad - * - * @class - * @memberof PIXI - */ -export default class Quad extends Geometry -{ - /** - * just a quad - */ - constructor() - { - super(); - - this.addAttribute('aVertexPosition', [ - 0, 0, - 1, 0, - 1, 1, - 0, 1, - ]) - .addIndex([0, 1, 3, 2]); - } -} diff --git a/packages/core/src/renderers/utils/QuadUv.js b/packages/core/src/renderers/utils/QuadUv.js deleted file mode 100644 index 97bda89..0000000 --- a/packages/core/src/renderers/utils/QuadUv.js +++ /dev/null @@ -1,103 +0,0 @@ -import Geometry from '../../geometry/Geometry'; -import Buffer from '../../geometry/Buffer'; - -/** - * Helper class to create a quad with uvs like in v4 - * - * @class - * @memberof PIXI - */ -export default class QuadUv extends Geometry -{ - constructor() - { - super(); - - /** - * An array of vertices - * - * @member {Float32Array} - */ - this.vertices = new Float32Array([ - -1, -1, - 1, -1, - 1, 1, - -1, 1, - ]); - - /** - * The Uvs of the quad - * - * @member {Float32Array} - */ - this.uvs = new Float32Array([ - 0, 0, - 1, 0, - 1, 1, - 0, 1, - ]); - - this.vertexBuffer = new Buffer(this.vertices); - this.uvBuffer = new Buffer(this.uvs); - - this.addAttribute('aVertexPosition', this.vertexBuffer) - .addAttribute('aTextureCoord', this.uvBuffer) - .addIndex([0, 1, 2, 0, 2, 3]); - } - - /** - * Maps two Rectangle to the quad. - * - * @param {PIXI.Rectangle} targetTextureFrame - the first rectangle - * @param {PIXI.Rectangle} destinationFrame - the second rectangle - * @return {PIXI.Quad} Returns itself. - */ - map(targetTextureFrame, destinationFrame) - { - let x = 0; // destinationFrame.x / targetTextureFrame.width; - let y = 0; // destinationFrame.y / targetTextureFrame.height; - - this.uvs[0] = x; - this.uvs[1] = y; - - this.uvs[2] = x + (destinationFrame.width / targetTextureFrame.width); - this.uvs[3] = y; - - this.uvs[4] = x + (destinationFrame.width / targetTextureFrame.width); - this.uvs[5] = y + (destinationFrame.height / targetTextureFrame.height); - - this.uvs[6] = x; - this.uvs[7] = y + (destinationFrame.height / targetTextureFrame.height); - - x = destinationFrame.x; - y = destinationFrame.y; - - this.vertices[0] = x; - this.vertices[1] = y; - - this.vertices[2] = x + destinationFrame.width; - this.vertices[3] = y; - - this.vertices[4] = x + destinationFrame.width; - this.vertices[5] = y + destinationFrame.height; - - this.vertices[6] = x; - this.vertices[7] = y + destinationFrame.height; - - this.invalidate(); - - return this; - } - - /** - * legacy upload method, just marks buffers dirty - * @returns {PIXI.QuadUv} Returns itself. - */ - invalidate() - { - this.vertexBuffer._updateID++; - this.uvBuffer._updateID++; - - return this; - } -} diff --git a/packages/core/src/renderers/utils/checkMaxIfStatmentsInShader.js b/packages/core/src/renderers/utils/checkMaxIfStatmentsInShader.js deleted file mode 100644 index 8047c96..0000000 --- a/packages/core/src/renderers/utils/checkMaxIfStatmentsInShader.js +++ /dev/null @@ -1,58 +0,0 @@ -const fragTemplate = [ - 'precision mediump float;', - 'void main(void){', - 'float test = 0.1;', - '%forloop%', - 'gl_FragColor = vec4(0.0);', - '}', -].join('\n'); - -export default function checkMaxIfStatmentsInShader(maxIfs, gl) -{ - if (maxIfs === 0) - { - throw new Error('Invalid value of `0` passed to `checkMaxIfStatementsInShader`'); - } - - const shader = gl.createShader(gl.FRAGMENT_SHADER); - - while (true) // eslint-disable-line no-constant-condition - { - const fragmentSrc = fragTemplate.replace(/%forloop%/gi, generateIfTestSrc(maxIfs)); - - gl.shaderSource(shader, fragmentSrc); - gl.compileShader(shader); - - if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) - { - maxIfs = (maxIfs / 2) | 0; - } - else - { - // valid! - break; - } - } - - return maxIfs; -} - -function generateIfTestSrc(maxIfs) -{ - let src = ''; - - for (let i = 0; i < maxIfs; ++i) - { - if (i > 0) - { - src += '\nelse '; - } - - if (i < maxIfs - 1) - { - src += `if(test == ${i}.0){}`; - } - } - - return src; -} diff --git a/packages/core/src/renderers/utils/mapWebGLBlendModesToPixi.js b/packages/core/src/renderers/utils/mapWebGLBlendModesToPixi.js deleted file mode 100644 index 10ffe4d..0000000 --- a/packages/core/src/renderers/utils/mapWebGLBlendModesToPixi.js +++ /dev/null @@ -1,47 +0,0 @@ -import { BLEND_MODES } from '@pixi/constants'; - -/** - * Maps gl blend combinations to WebGL. - * - * @memberof PIXI - * @function mapWebGLBlendModesToPixi - * @private - * @param {WebGLRenderingContext} gl - The rendering context. - * @param {string[]} [array=[]] - The array to output into. - * @return {string[]} Mapped modes. - */ -export default function mapWebGLBlendModesToPixi(gl, array = []) -{ - // TODO - premultiply alpha would be different. - // add a boolean for that! - array[BLEND_MODES.NORMAL] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.ADD] = [gl.ONE, gl.DST_ALPHA]; - array[BLEND_MODES.MULTIPLY] = [gl.DST_COLOR, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.SCREEN] = [gl.ONE, gl.ONE_MINUS_SRC_COLOR]; - array[BLEND_MODES.OVERLAY] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.DARKEN] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.LIGHTEN] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.COLOR_DODGE] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.COLOR_BURN] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.HARD_LIGHT] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.SOFT_LIGHT] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.DIFFERENCE] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.EXCLUSION] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.HUE] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.SATURATION] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.COLOR] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.LUMINOSITY] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.NONE] = [0, 0]; - - // not-premultiplied blend modes - array[BLEND_MODES.NORMAL_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.ADD_NPM] = [gl.SRC_ALPHA, gl.DST_ALPHA, gl.ONE, gl.DST_ALPHA]; - array[BLEND_MODES.SCREEN_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_COLOR, gl.ONE, gl.ONE_MINUS_SRC_COLOR]; - - // not-premultiplied blend modes - array[BLEND_MODES.NORMAL_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.ADD_NPM] = [gl.SRC_ALPHA, gl.DST_ALPHA, gl.ONE, gl.DST_ALPHA]; - array[BLEND_MODES.SCREEN_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_COLOR, gl.ONE, gl.ONE_MINUS_SRC_COLOR]; - - return array; -} diff --git a/packages/core/src/shader/GLProgram.js b/packages/core/src/shader/GLProgram.js new file mode 100644 index 0000000..92980ca --- /dev/null +++ b/packages/core/src/shader/GLProgram.js @@ -0,0 +1,43 @@ +/** + * Helper class to create a webGL Program + * + * @class + * @private + */ +export default class GLProgram +{ + constructor(program, uniformData) + { + /** + * The shader program + * + * @member {WebGLProgram} + */ + this.program = program; + + /** + * holds the uniform data which contains uniform locations + * and current uniform values used for caching and preventing undeed GPU commands + * @type {Object} + */ + this.uniformData = uniformData; + + /** + * uniformGroups holds the various upload functions for the shader. Each uniform group + * and program have a unique upload function generated. + * @type {Object} + */ + this.uniformGroups = {}; + } + + /** + * Destroys this program + * TODO + */ + destroy() + { + this.uniformData = null; + this.uniformGroups = null; + this.program = null; + } +} diff --git a/packages/core/src/AbstractRenderer.js b/packages/core/src/AbstractRenderer.js new file mode 100644 index 0000000..64b2eb1 --- /dev/null +++ b/packages/core/src/AbstractRenderer.js @@ -0,0 +1,353 @@ +import { hex2string, hex2rgb } from '@pixi/utils'; +import { Matrix, Rectangle } from '@pixi/math'; +import { RENDERER_TYPE } from '@pixi/constants'; +import { settings } from '@pixi/settings'; +import { Container } from '@pixi/display'; +import RenderTexture from './renderTexture/RenderTexture'; +import EventEmitter from 'eventemitter3'; + +const tempMatrix = new Matrix(); + +/** + * 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 EventEmitter + * @memberof PIXI + */ +export default class AbstractRenderer extends EventEmitter +{ + // eslint-disable-next-line valid-jsdoc + /** + * @param {string} system - The name of the system this renderer is for. + * @param {object} [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.transparent=false] - If the render view is transparent, default false + * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false + * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. The + * resolution of the renderer retina would be 2. + * @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 {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when rendering, + * stopping pixel interpolation. + */ + constructor(system, options, arg2, arg3) + { + super(); + + // Support for constructor(system, screenWidth, screenHeight, options) + if (typeof options === 'number') + { + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + }, arg3); + } + + // 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 = RENDERER_TYPE.UNKNOWN; + + /** + * Measurements of the screen. (0, 0, screenWidth, screenHeight) + * + * Its safe to use as filterArea or hitArea for 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 1 + */ + this.resolution = options.resolution || settings.RESOLUTION; + + /** + * Whether the render view is transparent + * + * @member {boolean} + */ + this.transparent = options.transparent; + + /** + * Whether css dimensions of canvas view should be resized to screen dimensions automatically + * + * @member {boolean} + */ + this.autoResize = options.autoResize || false; + + /** + * Tracks the blend modes useful for this renderer. + * + * @member {object} + */ + this.blendModes = null; + + /** + * 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; + + /** + * If true PixiJS will Math.floor() x/y values when rendering, stopping pixel interpolation. + * Handy for crisp pixel art and speed on legacy devices. + * + * @member {boolean} + */ + this.roundPixels = options.roundPixels; + + /** + * The background color as a number. + * + * @member {number} + * @private + */ + this._backgroundColor = 0x000000; + + /** + * The background color as an [R, G, B] array. + * + * @member {number[]} + * @private + */ + this._backgroundColorRgba = [0, 0, 0, 0]; + + /** + * The background color as a string. + * + * @member {string} + * @private + */ + this._backgroundColorString = '#000000'; + + this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter + + /** + * This temporary display object used as the parent of the currently being rendered item + * + * @member {PIXI.DisplayObject} + * @private + */ + this._tempDisplayObjectParent = new Container(); + + /** + * The last root object that the renderer tried to render. + * + * @member {PIXI.DisplayObject} + * @private + */ + this._lastObjectRendered = this._tempDisplayObjectParent; + + /** + * Collection of plugins. + * @readonly + * @member {object} + */ + this.plugins = {}; + } + + /** + * Initialize the plugins. + * + * @protected + * @param {object} staticMap - The dictionary of staticly saved plugins. + */ + initPlugins(staticMap) + { + 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() + { + return this.view.width; + } + + /** + * Same as view.height, actual number of pixels in the canvas by vertical + * + * @member {number} + * @readonly + * @default 600 + */ + get height() + { + return this.view.height; + } + + /** + * Resizes the screen and canvas to the specified width and height + * Canvas dimensions are multiplied by resolution + * + * @param {number} screenWidth - the new width of the screen + * @param {number} screenHeight - the new height of the screen + */ + resize(screenWidth, screenHeight) + { + this.screen.width = screenWidth; + this.screen.height = screenHeight; + + this.view.width = screenWidth * this.resolution; + this.view.height = screenHeight * this.resolution; + + if (this.autoResize) + { + this.view.style.width = `${screenWidth}px`; + this.view.style.height = `${screenHeight}px`; + } + } + + /** + * 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 {PIXI.DisplayObject} displayObject - The displayObject the object will be generated from + * @param {number} scaleMode - Should be one of the scaleMode consts + * @param {number} resolution - The resolution / device pixel ratio of the texture being generated + * @param {PIXI.Rectangle} [region] - The region of the displayObject, that shall be rendered, + * if no region is specified, defaults to the local bounds of the displayObject. + * @return {PIXI.Texture} a texture of the graphics object + */ + generateTexture(displayObject, scaleMode, resolution, region) + { + region = region || displayObject.getLocalBounds(); + + // 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(region.width | 0, region.height | 0, scaleMode, resolution); + + tempMatrix.tx = -region.x; + tempMatrix.ty = -region.y; + + this.render(displayObject, renderTexture, false, tempMatrix, true); + + return renderTexture; + } + + /** + * Removes everything from the renderer and optionally removes the Canvas DOM element. + * + * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. + */ + destroy(removeView) + { + 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); + } + + this.plugins = null; + + this.type = RENDERER_TYPE.UNKNOWN; + + this.view = null; + + this.screen = null; + + this.resolution = 0; + + this.transparent = false; + + this.autoResize = false; + + this.blendModes = null; + + this.options = null; + + this.preserveDrawingBuffer = false; + this.clearBeforeRender = false; + + this.roundPixels = false; + + this._backgroundColor = 0; + this._backgroundColorRgba = null; + this._backgroundColorString = null; + + this._tempDisplayObjectParent = null; + this._lastObjectRendered = null; + } + + /** + * The background color to fill if not transparent + * + * @member {number} + */ + get backgroundColor() + { + return this._backgroundColor; + } + + set backgroundColor(value) // eslint-disable-line require-jsdoc + { + this._backgroundColor = value; + this._backgroundColorString = hex2string(value); + hex2rgb(value, this._backgroundColorRgba); + } +} diff --git a/packages/core/src/Renderer.js b/packages/core/src/Renderer.js new file mode 100644 index 0000000..2227e78 --- /dev/null +++ b/packages/core/src/Renderer.js @@ -0,0 +1,338 @@ +import AbstractRenderer from './AbstractRenderer'; +import { sayHello } from '@pixi/utils'; +import MaskSystem from './mask/MaskSystem'; +import StencilSystem from './mask/StencilSystem'; +import FilterSystem from './filters/FilterSystem'; +import FramebufferSystem from './framebuffer/FramebufferSystem'; +import RenderTextureSystem from './renderTexture/RenderTextureSystem'; +import TextureSystem from './textures/TextureSystem'; +import ProjectionSystem from './projection/ProjectionSystem'; +import StateSystem from './state/StateSystem'; +import GeometrySystem from './geometry/GeometrySystem'; +import ShaderSystem from './shader/ShaderSystem'; +import ContextSystem from './context/ContextSystem'; +import BatchSystem from './batch/BatchSystem'; +import TextureGCSystem from './textures/TextureGCSystem'; +import { RENDERER_TYPE } from '@pixi/constants'; +import UniformGroup from './shader/UniformGroup'; +import { Matrix } from '@pixi/math'; +import Runner from 'mini-runner'; + +/** + * 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 Render works by automatically managing webGLBatchs. + * 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 :) + * + * @class + * @memberof PIXI + * @extends PIXI.AbstractRenderer + */ +export default class Renderer extends AbstractRenderer +{ + // eslint-disable-next-line valid-jsdoc + /** + * + * @param {object} [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.transparent=false] - If the render view is transparent, default false + * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false + * @param {boolean} [options.antialias=false] - sets antialias. If not available natively then FXAA + * antialiasing is used + * @param {boolean} [options.forceFXAA=false] - forces FXAA antialiasing to be used over native. + * FXAA is faster, but may not always look as great + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. + * The resolution of the renderer retina would be 2. + * @param {boolean} [options.clearBeforeRender=true] - This sets if the renderer will clear + * the canvas or not before the new render pass. If you wish to set this to false, you *must* set + * preserveDrawingBuffer to `true`. + * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, + * enable this if you need to call toDataUrl on the webgl context. + * @param {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when + * rendering, stopping pixel interpolation. + * @param {number} [options.backgroundColor=0x000000] - The background color of the rendered area + * (shown if not transparent). + * @param {boolean} [options.legacy=false] - If true PixiJS will aim to ensure compatibility + * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. + * @param {string} [options.powerPreference] - Parameter passed to webgl context, set to "high-performance" + * for devices with dual graphics card + */ + constructor(options, arg2, arg3) + { + super('WebGL', options, arg2, arg3); + + /** + * The type of this renderer as a standardised const + * + * @member {number} + * @see PIXI.RENDERER_TYPE + */ + this.type = RENDERER_TYPE.WEBGL; + + // this will be set by the contextSystem (this.context) + this.gl = null; + this.CONTEXT_UID = 0; + this.legacy = !!options.legacy; + + // TODO legacy! + + // runners! + this.runners = { + destroy: new Runner('destroy'), + contextChange: new Runner('contextChange', 1), + reset: new Runner('reset'), + update: new Runner('update'), + postrender: new Runner('postrender'), + prerender: new Runner('prerender'), + resize: new Runner('resize', 2), + }; + + 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(GeometrySystem, 'geometry') + .addSystem(FramebufferSystem, 'framebuffer') + .addSystem(StencilSystem, 'stencil') + .addSystem(ProjectionSystem, 'projection') + .addSystem(TextureGCSystem, 'textureGC') + .addSystem(FilterSystem, 'filter') + .addSystem(RenderTextureSystem, 'renderTexture') + .addSystem(BatchSystem, 'batch'); + + this.initPlugins(Renderer.__plugins); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ + if (options.context) + { + this.context.initFromContext(options.context); + } + else + { + this.context.initFromOptions({ + alpha: this.transparent, + antialias: options.antialias, + premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', + stencil: true, + 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); + } + + /** + * Add a new system to the renderer. + * @param {class} ClassRef - Class reference + * @param {string} [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. + * @return {PIXI.Renderer} Return instance of renderer + */ + addSystem(ClassRef, name) + { + if (!name) + { + name = ClassRef.name; + } + + // TODO - read name from class.name.. + + /* + if(name.includes('System')) + { + name = name.replace('System', ''); + name = name.charAt(0).toLowerCase() + name.slice(1); + } + */ + + const system = new ClassRef(this); + + if (this[name]) + { + throw new Error(`Whoops! ${name} is already a manger`); + } + + this[name] = system; + + for (const i in this.runners) + { + this.runners[i].add(system); + } + + return this; + + /** + * 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. + */ + } + + /** + * Renders the object to its webGL view + * + * @param {PIXI.DisplayObject} displayObject - the object to be rendered + * @param {PIXI.RenderTexture} renderTexture - The render texture to render to. + * @param {boolean} [clear] - Should the canvas be cleared before the new render + * @param {PIXI.Transform} [transform] - A transform to apply to the render texture before rendering. + * @param {boolean} [skipUpdateTransform] - Should we skip the update transform pass? + */ + render(displayObject, renderTexture, clear, transform, skipUpdateTransform) + { + // can be handy to know! + this.renderingToScreen = !renderTexture; + + this.runners.prerender.run(); + this.emit('prerender'); + + // 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.parent; + + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = 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.run(); + + this.emit('postrender'); + } + + /** + * Resizes the webGL view to the specified width and height. + * + * @param {number} screenWidth - the new width of the screen + * @param {number} screenHeight - the new height of the screen + */ + resize(screenWidth, screenHeight) + { + AbstractRenderer.prototype.resize.call(this, screenWidth, screenHeight); + + this.runners.resize.run(screenWidth, screenHeight); + } + + /** + * Resets the WebGL state so you can render things however you fancy! + * + * @return {PIXI.Renderer} Returns itself. + */ + reset() + { + this.runners.reset.run(); + + return this; + } + + clear() + { + this.framebuffer.bind(); + this.framebuffer.clear(); + } + + /** + * Removes everything from the renderer (event listeners, spritebatch, etc...) + * + * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. + * See: https://github.com/pixijs/pixi.js/issues/2233 + */ + destroy(removeView) + { + this.runners.destroy.run(); + + // call base destroy + super.destroy(removeView); + + // TODO nullify all the managers.. + this.gl = null; + } + + /** + * 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 + * builds and excluding plugins. + * @name PIXI.Renderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. + */ + + /** + * Adds a plugin to the renderer. + * + * @method + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + static registerPlugin(pluginName, ctor) + { + Renderer.__plugins = Renderer.__plugins || {}; + Renderer.__plugins[pluginName] = ctor; + } +} diff --git a/packages/core/src/System.js b/packages/core/src/System.js new file mode 100644 index 0000000..05a10c8 --- /dev/null +++ b/packages/core/src/System.js @@ -0,0 +1,42 @@ +/** + * System is a base class used for extending systems used by the {@link PIXI.Renderer} + * @see PIXI.Renderer#addSystem + * @class + * @memberof PIXI + */ +export default class System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + /** + * The renderer this manager works for. + * + * @member {PIXI.Renderer} + */ + this.renderer = renderer; + + this.renderer.runners.contextChange.add(this); + } + + /** + * Generic method called when there is a WebGL context change. + * + */ + contextChange() + { + // do some codes init! + } + + /** + * Generic destroy methods to be overridden by the subclass + * + */ + destroy() + { + this.renderer.runners.contextChange.remove(this); + this.renderer = null; + } +} diff --git a/packages/core/src/batch/BatchSystem.js b/packages/core/src/batch/BatchSystem.js new file mode 100644 index 0000000..b04c249 --- /dev/null +++ b/packages/core/src/batch/BatchSystem.js @@ -0,0 +1,68 @@ +import System from '../System'; +import ObjectRenderer from './ObjectRenderer'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class BatchSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + super(renderer); + + /** + * An empty renderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.emptyRenderer = new ObjectRenderer(renderer); + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.currentRenderer = this.emptyRenderer; + } + + /** + * Changes the current renderer to the one given in parameter + * + * @param {PIXI.ObjectRenderer} objectRenderer - The object renderer to use. + */ + setObjectRenderer(objectRenderer) + { + if (this.currentRenderer === objectRenderer) + { + return; + } + + this.currentRenderer.stop(); + this.currentRenderer = objectRenderer; + + this.renderer.state.setState(objectRenderer.state); + + this.currentRenderer.start(); + } + + /** + * This should be called if you wish to do some custom rendering + * It will basically render anything that may be batched up such as sprites + * + */ + flush() + { + this.setObjectRenderer(this.emptyRenderer); + } + + reset() + { + this.setObjectRenderer(this.emptyRenderer); + } +} diff --git a/packages/core/src/batch/ObjectRenderer.js b/packages/core/src/batch/ObjectRenderer.js new file mode 100644 index 0000000..0c851a9 --- /dev/null +++ b/packages/core/src/batch/ObjectRenderer.js @@ -0,0 +1,48 @@ +import System from '../System'; + +/** + * Base for a common object renderer that can be used as a system renderer plugin. + * + * @class + * @extends PIXI.System + * @memberof PIXI + */ +export default class ObjectRenderer extends System +{ + /** + * Starts the renderer and sets the shader + * + */ + start() + { + // set the shader.. + } + + /** + * Stops the renderer + * + */ + stop() + { + this.flush(); + } + + /** + * Stub method for rendering content and emptying the current batch. + * + */ + flush() + { + // flush! + } + + /** + * Renders an object + * + * @param {PIXI.DisplayObject} object - The object to render. + */ + render(object) // eslint-disable-line no-unused-vars + { + // render the object + } +} diff --git a/packages/core/src/context/ContextSystem.js b/packages/core/src/context/ContextSystem.js new file mode 100644 index 0000000..301aab4 --- /dev/null +++ b/packages/core/src/context/ContextSystem.js @@ -0,0 +1,186 @@ +import System from '../System'; +import { settings } from '@pixi/settings'; + +let CONTEXT_UID = 0; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class ContextSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.webGLVersion = 1; + + this.handleContextLost = this.handleContextLost.bind(this); + this.handleContextRestored = this.handleContextRestored.bind(this); + + this.extensions = {}; + + renderer.view.addEventListener('webglcontextlost', this.handleContextLost, false); + renderer.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + } + + get isLost() + { + return (!this.gl || this.gl.isContextLost()); + } + + contextChange(gl) + { + this.gl = gl; + + // restore a context if it was previously lost + if (gl.isContextLost() && gl.getExtension('WEBGL_lose_context')) + { + gl.getExtension('WEBGL_lose_context').restoreContext(); + } + } + + initFromContext(gl) + { + this.gl = gl; + this.validateContext(gl); + this.renderer.gl = gl; + this.renderer.CONTEXT_UID = CONTEXT_UID++; + this.renderer.runners.contextChange.run(gl); + } + + initFromOptions(options) + { + const gl = this.createContext(this.renderer.view, options); + + this.initFromContext(gl); + } + + /** + * Helper class to create a webGL Context + * + * @class + * @memberof PIXI.glCore + * @param canvas {HTMLCanvasElement} the canvas element that we will get the context from + * @param options {Object} An options object that gets passed in to the canvas element containing the context attributes, + * see https://developer.mozilla.org/en/docs/Web/API/HTMLCanvasElement/getContext for the options available + * @return {WebGLRenderingContext} the WebGL context + */ + createContext(canvas, options) + { + let gl; + + if (settings.PREFER_WEBGL_2) + { + gl = canvas.getContext('webgl2', options); + } + + if (gl) + { + this.webGLVersion = 2; + } + else + { + this.webGLVersion = 1; + + gl = canvas.getContext('webgl', options) + || canvas.getContext('experimental-webgl', options); + + if (!gl) + { + // fail, not able to get a context + throw new Error('This browser does not support webGL. Try using the canvas renderer'); + } + } + + this.gl = gl; + + this.getExtensions(); + + return gl; + } + + getExtensions() + { + // time to set up default etensions that pixi uses.. + const gl = this.gl; + const extensions = this.extensions; + + if (this.webGLVersion === 1) + { + extensions.drawBuffers = gl.getExtension('WEBGL_draw_buffers'); + extensions.depthTexture = gl.getExtension('WEBKIT_WEBGL_depth_texture'); + extensions.floatTexture = gl.getExtension('OES_texture_float'); + extensions.loseContext = gl.getExtension('WEBGL_lose_context'); + + extensions.vertexArrayObject = gl.getExtension('OES_vertex_array_object') + || gl.getExtension('MOZ_OES_vertex_array_object') + || gl.getExtension('WEBKIT_OES_vertex_array_object'); + } + + // we don't use any specific WebGL 2 ones yet! + } + + /** + * Handles a lost webgl context + * + * @private + * @param {WebGLContextEvent} event - The context lost event. + */ + handleContextLost(event) + { + event.preventDefault(); + } + + /** + * Handles a restored webgl context + * + * @private + */ + handleContextRestored() + { + this.renderer.runners.contextChange.run(this.gl); + } + + destroy() + { + const view = this.renderer.view; + + // remove listeners + view.removeEventListener('webglcontextlost', this.handleContextLost); + view.removeEventListener('webglcontextrestored', this.handleContextRestored); + + this.gl.useProgram(null); + + if (this.extensions.loseContext) + { + this.extensions.loseContext.loseContext(); + } + } + + postrender() + { + this.gl.flush(); + } + + validateContext(gl) + { + const attributes = gl.getContextAttributes(); + + // this is going to be fairly simple for now.. but at least we have room to grow! + if (!attributes.stencil) + { + /* eslint-disable max-len */ + + /* eslint-disable no-console */ + console.warn('Provided WebGL context does not have a stencil buffer, masks may not render correctly'); + /* eslint-enable no-console */ + + /* eslint-enable max-len */ + } + } +} diff --git a/packages/core/src/filters/Filter.js b/packages/core/src/filters/Filter.js new file mode 100644 index 0000000..9d8cad8 --- /dev/null +++ b/packages/core/src/filters/Filter.js @@ -0,0 +1,140 @@ +import Shader from '../shader/Shader'; +import Program from '../shader/Program'; +import State from '../state/State'; +import { settings } from '@pixi/settings'; +// import extractUniformsFromSrc from './extractUniformsFromSrc'; +import defaultVertex from './defaultFilter.vert'; +import defaultFragment from './defaultFilter.frag'; + +// let math = require('../../math'); +/** + * @class + * @memberof PIXI + * @extends PIXI.Shader + */ +export default class Filter extends Shader +{ + /** + * @param {string} [vertexSrc] - The source of the vertex shader. + * @param {string} [fragmentSrc] - The source of the fragment shader. + * @param {object} [uniforms] - Custom uniforms to use to augment the built-in ones. + */ + constructor(vertexSrc, fragmentSrc, uniforms) + { + const program = Program.from(vertexSrc, fragmentSrc); + + super(program, uniforms); + + /** + * The padding of the filter. Some filters require extra space to breath such as a blur. + * Increasing this will add extra width and height to the bounds of the object that the + * filter is applied to. + * + * @member {number} + */ + this.padding = 0; + + /** + * The resolution of the filter. Setting this to be lower will lower the quality but + * increase the performance of the filter. + * + * @member {number} + */ + this.resolution = settings.RESOLUTION; + + /** + * If enabled is true the filter is applied, if false it will not. + * + * @member {boolean} + */ + this.enabled = true; + + /** + * If enabled, PixiJS will fit the filter area into boundaries for better performance. + * Switch it off if it does not work for specific shader. + * + * @member {boolean} + */ + this.autoFit = true; + + /** + * Legacy filters use position and uvs from attributes + * @member {boolean} + * @readonly + */ + this.legacy = !!this.program.attributeData.aTextureCoord; + + /** + * the webGL state the filter requires to render + * @member {PIXI.State} + */ + this.state = new State(); + } + + /** + * Applies the filter + * + * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + * @param {boolean} clear - Should the output be cleared before rendering to it + * @param {object} [currentState] - It's current state of filter. + * There are some useful properties in the currentState : + * target, filters, sourceFrame, destinationFrame, renderTarget, resolution + */ + apply(filterManager, input, output, clear, currentState, derp) // eslint-disable-line no-unused-vars + { + // do as you please! + + filterManager.applyFilter(this, input, output, clear, currentState, derp); + + // or just do a regular render.. + } + + /** + * Sets the blendmode of the filter + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL + */ + get blendMode() + { + return this.state.blendMode; + } + + set blendMode(value) // eslint-disable-line require-jsdoc + { + this.state.blendMode = value; + } + + /** + * The default vertex shader source + * + * @static + * @constant + */ + static get defaultVertexSrc() + { + return defaultVertex; + } + + /** + * The default fragment shader source + * + * @static + * @constant + */ + static get defaultFragmentSrc() + { + return defaultFragment; + } +} + +/** + * Used for caching shader IDs + * + * @static + * @private + */ +Filter.SOURCE_KEY_MAP = {}; + diff --git a/packages/core/src/filters/FilterSystem.js b/packages/core/src/filters/FilterSystem.js new file mode 100644 index 0000000..3174318 --- /dev/null +++ b/packages/core/src/filters/FilterSystem.js @@ -0,0 +1,431 @@ +import System from '../System'; + +import RenderTexture from '../renderTexture/RenderTexture'; +import Quad from '../utils/Quad'; +import QuadUv from '../utils/QuadUv'; +import { Rectangle } from '@pixi/math'; +import * as filterTransforms from './filterTransforms'; +import bitTwiddle from 'bit-twiddle'; +import UniformGroup from '../shader/UniformGroup'; +import { DRAW_MODES } from '@pixi/constants'; + +// +/** + * @ignore + * @class + */ +class FilterState +{ + /** + * + */ + constructor() + { + this.renderTexture = null; + this.sourceFrame = new Rectangle(); + this.destinationFrame = new Rectangle(); + this.filters = []; + this.target = null; + this.legacy = false; + this.resolution = 1; + } +} + +/** + * @class + * @memberof PIXI.systems + * @extends PIXI.System + */ +export default class FilterSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + /** + * stores a bunch of PO2 textures used for filtering + * @type {Object} + */ + this.texturePool = {}; + + /** + * a pool for storing filter states, save us creating new ones each tick + * @type {Array} + */ + this.statePool = []; + + /** + * A very simple geometry used when drawing a filter effect to the screen + * @type {Quad} + */ + this.quad = new Quad(); + + this.quadUv = new QuadUv(); + + /** + * Temporary rect for maths + * @type {PIXI.Rectangle} + */ + this.tempRect = new Rectangle(); + + this.activeState = {}; + + /** + * this uniform group is attached to filter uniforms when used + * @type {UniformGroup} + */ + this.globalUniforms = new UniformGroup({ + sourceFrame: this.tempRect, + destinationFrame: this.tempRect, + + // legacy variables + filterArea: new Float32Array(4), + filterClamp: new Float32Array(4), + }, true); + } + + /** + * Adds a new filter to the System. + * + * @param {PIXI.DisplayObject} target - The target of the filter to render. + * @param {PIXI.Filter[]} filters - The filters to apply. + */ + push(target, filters) + { + const renderer = this.renderer; + const filterStack = this.renderer.renderTexture.defaultFilterStack; + const state = this.statePool.pop() || new FilterState(); + + let resolution = filters[0].resolution; + let padding = filters[0].padding; + let autoFit = filters[0].autoFit; + let legacy = filters[0].legacy; + + for (let i = 1; i < filters.length; i++) + { + const filter = filters[i]; + + // lets use the lowest resolution.. + resolution = Math.min(resolution, filter.resolution); + // and the largest amount of padding! + padding = Math.max(padding, filter.padding); + // only auto fit if all filters are autofit + autoFit = autoFit || filter.autoFit; + + legacy = legacy || filter.legacy; + } + + filterStack.push(state); + + state.resolution = resolution; + + state.legacy = legacy; + + // round to whole number based on resolution + // TODO move that to the shader too? + state.sourceFrame = target.filterArea || target.getBounds(true); + + state.sourceFrame.pad(padding); + + if (autoFit) + { + state.sourceFrame.fit(this.renderer.renderTexture.destinationFrame); + } + + state.sourceFrame.round(resolution); + + state.renderTexture = this.getPotFilterTexture(state.sourceFrame.width, state.sourceFrame.height, resolution); + state.filters = filters; + + state.destinationFrame.width = state.renderTexture.width; + state.destinationFrame.height = state.renderTexture.height; + + state.renderTexture.filterFrame = state.sourceFrame; + renderer.renderTexture.bind(state.renderTexture, state.sourceFrame);// /, state.destinationFrame); + renderer.renderTexture.clear(); + } + + /** + * Pops off the filter and applies it. + * + */ + pop() + { + const renderer = this.renderer; + const filterStack = renderer.renderTexture.defaultFilterStack; + const state = filterStack.pop(); + const filters = state.filters; + + this.activeState = state; + + const globalUniforms = this.globalUniforms.uniforms; + + globalUniforms.sourceFrame = state.sourceFrame; + globalUniforms.destinationFrame = state.destinationFrame; + globalUniforms.resolution = state.resolution; + + // only update the rect if its legacy.. + if (state.legacy) + { + const filterArea = globalUniforms.filterArea; + const filterClamp = globalUniforms.filterClamp; + + filterArea[0] = state.destinationFrame.width; + filterArea[1] = state.destinationFrame.height; + filterArea[2] = state.sourceFrame.x; + filterArea[3] = state.sourceFrame.y; + + filterClamp[0] = 0.5 / state.resolution / state.destinationFrame.width; + filterClamp[1] = 0.5 / state.resolution / state.destinationFrame.height; + filterClamp[2] = (state.sourceFrame.width - 0.5) / state.resolution / state.destinationFrame.width; + filterClamp[3] = (state.sourceFrame.height - 0.5) / state.resolution / state.destinationFrame.height; + } + + this.globalUniforms.update(); + + const lastState = filterStack[filterStack.length - 1]; + + if (filters.length === 1) + { + filters[0].apply(this, state.renderTexture, lastState.renderTexture, false, state); + + this.returnFilterTexture(state.renderTexture); + } + else + { + let flip = state.renderTexture; + let flop = this.getPotFilterTexture( + flip.width, + flip.height, + state.resolution + ); + + flop.filterFrame = flip.filterFrame; + + let i = 0; + + for (i = 0; i < filters.length - 1; ++i) + { + filters[i].apply(this, flip, flop, true, state); + + const t = flip; + + flip = flop; + flop = t; + } + + filters[i].apply(this, flip, lastState.renderTexture, false, state); + + this.returnFilterTexture(flip); + this.returnFilterTexture(flop); + } + + this.statePool.push(state); + } + + /** + * Draws a filter. + * + * @param {PIXI.Filter} filter - The filter to draw. + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + * @param {boolean} clear - Should the output be cleared before rendering to it + */ + applyFilter(filter, input, output, clear) + { + const renderer = this.renderer; + + renderer.renderTexture.bind(output, output ? output.filterFrame : null); + + if (clear) + { + // gl.disable(gl.SCISSOR_TEST); + renderer.renderTexture.clear(); + // gl.enable(gl.SCISSOR_TEST); + } + + // set the uniforms.. + filter.uniforms.uSampler = input; + filter.uniforms.filterGlobals = this.globalUniforms; + + // TODO make it so that the order of this does not matter.. + // because it does at the moment cos of global uniforms. + // they need to get resynced + + renderer.state.setState(filter.state); + renderer.shader.bind(filter); + + if (filter.legacy) + { + this.quadUv.map(input._frame, input.filterFrame); + + renderer.geometry.bind(this.quadUv); + renderer.geometry.draw(DRAW_MODES.TRIANGLES); + } + else + { + renderer.geometry.bind(this.quad); + renderer.geometry.draw(DRAW_MODES.TRIANGLE_STRIP); + } + } + + /** + * Calculates the mapped matrix. + * + * TODO playing around here.. this is temporary - (will end up in the shader) + * this returns a matrix that will normalise map filter cords in the filter to screen space + * + * @param {PIXI.Matrix} outputMatrix - the matrix to output to. + * @return {PIXI.Matrix} The mapped matrix. + */ + calculateScreenSpaceMatrix(outputMatrix) + { + const currentState = this.activeState; + + return filterTransforms.calculateScreenSpaceMatrix( + outputMatrix, + currentState.sourceFrame, + currentState.destinationFrame + ); + } + + /** + * This will map the filter coord so that a texture can be used based on the transform of a sprite + * + * @param {PIXI.Matrix} outputMatrix - The matrix to output to. + * @param {PIXI.Sprite} sprite - The sprite to map to. + * @return {PIXI.Matrix} The mapped matrix. + */ + calculateSpriteMatrix(outputMatrix, sprite) + { + const currentState = this.activeState; + + return filterTransforms.calculateSpriteMatrix( + outputMatrix, + currentState.sourceFrame, + currentState.destinationFrame, + sprite + ); + } + + /** + * Destroys this Filter System. + * + * @param {boolean} [contextLost=false] context was lost, do not free shaders + * + */ + destroy(contextLost = false) + { + if (!contextLost) + { + this.emptyPool(); + } + else + { + this.texturePool = {}; + } + } + + /** + * Gets a Power-of-Two render texture. + * + * TODO move to a seperate class could be on renderer? + * also - could cause issue with multiple contexts? + * + * @private + * @param {WebGLRenderingContext} gl - The webgl rendering context + * @param {number} minWidth - The minimum width of the render target. + * @param {number} minHeight - The minimum height of the render target. + * @param {number} resolution - The resolution of the render target. + * @return {PIXI.RenderTarget} The new render target. + */ + getPotFilterTexture(minWidth, minHeight, resolution) + { + minWidth = bitTwiddle.nextPow2(minWidth); + minHeight = bitTwiddle.nextPow2(minHeight); + resolution = resolution || 1; + + const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + + if (!this.texturePool[key]) + { + this.texturePool[key] = []; + } + + let renderTexture = this.texturePool[key].pop(); + + if (!renderTexture) + { + // temporary bypass cache.. + // internally - this will cause a texture to be bound.. + renderTexture = RenderTexture.create({ + width: minWidth, + height: minHeight, + resolution, + }); + } + + return renderTexture; + } + + /** + * Gets extra render texture to use inside current filter + * + * @param {number} resolution resolution of the renderTexture + * @returns {PIXI.RenderTexture} + */ + getFilterTexture(resolution) + { + const rt = this.activeState.renderTexture; + + const filterTexture = this.getPotFilterTexture(rt.width, rt.height, resolution || rt.baseTexture.resolution); + + filterTexture.filterFrame = rt.filterFrame; + + return filterTexture; + } + + /** + * Frees a render texture back into the pool. + * + * @param {PIXI.RenderTarget} renderTexture - The renderTarget to free + */ + returnFilterTexture(renderTexture) + { + renderTexture.filterFrame = null; + + const base = renderTexture.baseTexture; + + const minWidth = base.width; + const minHeight = base.height; + + const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + + this.texturePool[key].push(renderTexture); + } + + /** + * Empties the texture pool. + * + */ + emptyPool() + { + for (const i in this.texturePool) + { + const textures = this.texturePool[i]; + + if (textures) + { + for (let j = 0; j < textures.length; j++) + { + textures[j].destroy(true); + } + } + } + + this.texturePool = {}; + } +} diff --git a/packages/core/src/filters/defaultFilter.frag b/packages/core/src/filters/defaultFilter.frag new file mode 100644 index 0000000..a14c1be --- /dev/null +++ b/packages/core/src/filters/defaultFilter.frag @@ -0,0 +1,22 @@ +varying vec2 vTextureCoord; +varying vec2 vFilterCoord; + +uniform sampler2D uSampler; +uniform sampler2D filterSampler; + +void main(void){ + vec4 masky = texture2D(filterSampler, vFilterCoord); + vec4 sample = texture2D(uSampler, vTextureCoord); + vec4 color; + if(mod(vFilterCoord.x, 1.0) > 0.5) + { + color = vec4(1.0, 0.0, 0.0, 1.0); + } + else + { + color = vec4(0.0, 1.0, 0.0, 1.0); + } + // gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0); + gl_FragColor = mix(sample, masky, 0.5); + gl_FragColor *= sample.a; +} diff --git a/packages/core/src/filters/defaultFilter.vert b/packages/core/src/filters/defaultFilter.vert new file mode 100644 index 0000000..e1febca --- /dev/null +++ b/packages/core/src/filters/defaultFilter.vert @@ -0,0 +1,14 @@ +attribute vec2 aVertexPosition; +attribute vec2 aTextureCoord; + +uniform mat3 projectionMatrix; +uniform mat3 filterMatrix; + +varying vec2 vTextureCoord; +varying vec2 vFilterCoord; + +void main(void){ + gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); + vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy; + vTextureCoord = aTextureCoord ; +} diff --git a/packages/core/src/filters/filterTransforms.js b/packages/core/src/filters/filterTransforms.js new file mode 100644 index 0000000..87a3cd6 --- /dev/null +++ b/packages/core/src/filters/filterTransforms.js @@ -0,0 +1,50 @@ +import { Matrix } from '@pixi/math'; + +/** + * Calculates the mapped matrix + * @param filterArea {Rectangle} The filter area + * @param sprite {Sprite} the target sprite + * @param outputMatrix {Matrix} @alvin + * @private + */ +// this returns a matrix that will normalise map filter cords in the filter to screen space +export function calculateScreenSpaceMatrix(outputMatrix, filterArea, textureSize) +{ + // TODO unwrap? + const mappedMatrix = outputMatrix.identity(); + + mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); + + mappedMatrix.scale(textureSize.width, textureSize.height); + + return mappedMatrix; +} + +export function calculateNormalizedScreenSpaceMatrix(outputMatrix, filterArea, textureSize) +{ + const mappedMatrix = outputMatrix.identity(); + + mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); + + const translateScaleX = (textureSize.width / filterArea.width); + const translateScaleY = (textureSize.height / filterArea.height); + + mappedMatrix.scale(translateScaleX, translateScaleY); + + return mappedMatrix; +} + +// this will map the filter coord so that a texture can be used based on the transform of a sprite +export function calculateSpriteMatrix(outputMatrix, filterArea, textureSize, sprite) +{ + const orig = sprite._texture.orig; + const mappedMatrix = outputMatrix.set(textureSize.width, 0, 0, textureSize.height, filterArea.x, filterArea.y); + const worldTransform = sprite.worldTransform.copyTo(Matrix.TEMP_MATRIX); + + worldTransform.invert(); + mappedMatrix.prepend(worldTransform); + mappedMatrix.scale(1.0 / orig.width, 1.0 / orig.height); + mappedMatrix.translate(sprite.anchor.x, sprite.anchor.y); + + return mappedMatrix; +} diff --git a/packages/core/src/filters/spriteMask/SpriteMaskFilter.js b/packages/core/src/filters/spriteMask/SpriteMaskFilter.js new file mode 100644 index 0000000..91697b7 --- /dev/null +++ b/packages/core/src/filters/spriteMask/SpriteMaskFilter.js @@ -0,0 +1,63 @@ +import Filter from '../Filter'; +import { Matrix } from '@pixi/math'; +import vertex from './spriteMaskFilter.vert'; +import fragment from './spriteMaskFilter.frag'; +import { default as TextureMatrix } from '../../textures/TextureMatrix'; + +/** + * The SpriteMaskFilter class + * + * @class + * @extends PIXI.Filter + * @memberof PIXI + */ +export default class SpriteMaskFilter extends Filter +{ + /** + * @param {PIXI.Sprite} sprite - the target sprite + */ + constructor(sprite) + { + const maskMatrix = new Matrix(); + + super(vertex, fragment); + + sprite.renderable = false; + + this.maskSprite = sprite; + this.maskMatrix = maskMatrix; + } + + /** + * Applies the filter + * + * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + */ + apply(filterManager, input, output) + { + const maskSprite = this.maskSprite; + const tex = this.maskSprite.texture; + + if (!tex.valid) + { + return; + } + if (!tex.transform) + { + // margin = 0.0, let it bleed a bit, shader code becomes easier + // assuming that atlas textures were made with 1-pixel padding + tex.transform = new TextureMatrix(tex, 0.0); + } + tex.transform.update(); + + this.uniforms.mask = tex; + this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite) + .prepend(tex.transform.mapCoord); + this.uniforms.alpha = maskSprite.worldAlpha; + this.uniforms.maskClamp = tex.transform.uClampFrame; + + filterManager.applyFilter(this, input, output); + } +} diff --git a/packages/core/src/filters/spriteMask/spriteMaskFilter.frag b/packages/core/src/filters/spriteMask/spriteMaskFilter.frag new file mode 100644 index 0000000..0e4aef8 --- /dev/null +++ b/packages/core/src/filters/spriteMask/spriteMaskFilter.frag @@ -0,0 +1,23 @@ +varying vec2 vMaskCoord; +varying vec2 vTextureCoord; + +uniform sampler2D uSampler; +uniform sampler2D mask; +uniform float alpha; +uniform vec4 maskClamp; + +void main(void) +{ + float clip = step(3.5, + step(maskClamp.x, vMaskCoord.x) + + step(maskClamp.y, vMaskCoord.y) + + step(vMaskCoord.x, maskClamp.z) + + step(vMaskCoord.y, maskClamp.w)); + + vec4 original = texture2D(uSampler, vTextureCoord); + vec4 masky = texture2D(mask, vMaskCoord); + + original *= (masky.r * masky.a * alpha * clip); + + gl_FragColor = original; +} diff --git a/packages/core/src/filters/spriteMask/spriteMaskFilter.vert b/packages/core/src/filters/spriteMask/spriteMaskFilter.vert new file mode 100644 index 0000000..d23a30c --- /dev/null +++ b/packages/core/src/filters/spriteMask/spriteMaskFilter.vert @@ -0,0 +1,16 @@ +attribute vec2 aVertexPosition; +attribute vec2 aTextureCoord; + +uniform mat3 projectionMatrix; +uniform mat3 otherMatrix; + +varying vec2 vMaskCoord; +varying vec2 vTextureCoord; + +void main(void) +{ + gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); + + vTextureCoord = aTextureCoord; + vMaskCoord = ( otherMatrix * vec3( aTextureCoord, 1.0) ).xy; +} diff --git a/packages/core/src/framebuffer/FrameBuffer.js b/packages/core/src/framebuffer/FrameBuffer.js new file mode 100644 index 0000000..84e9fcc --- /dev/null +++ b/packages/core/src/framebuffer/FrameBuffer.js @@ -0,0 +1,106 @@ +import Texture from '../textures/BaseTexture'; +import { FORMATS, TYPES } from '@pixi/constants'; + +/** + * Frame buffer + * @class + * @memberof PIXI + */ +export default class FrameBuffer +{ + constructor(width, height) + { + this.width = width || 100; + this.height = height || 100; + + this.stencil = false; + this.depth = false; + + this.dirtyId = 0; + this.dirtyFormat = 0; + this.dirtySize = 0; + + this.depthTexture = null; + this.colorTextures = []; + + this.glFrameBuffers = {}; + } + + get colorTexture() + { + return this.colorTextures[0]; + } + + addColorTexture(index, texture) + { + // TODO add some validation to the texture - same width / height etc? + this.colorTextures[index || 0] = texture || new Texture(null, { scaleMode: 0, + resolution: 1, + mipmap: false, + width: this.width, + height: this.height });// || new Texture(); + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + addDepthTexture(texture) + { + /* eslint-disable max-len */ + this.depthTexture = texture || new Texture(null, { scaleMode: 0, + resolution: 1, + width: this.width, + height: this.height, + mipmap: false, + format: FORMATS.DEPTH_COMPONENT, + type: TYPES.UNSIGNED_SHORT });// UNSIGNED_SHORT; + /* eslint-disable max-len */ + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + enableDepth() + { + this.depth = true; + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + enableStencil() + { + this.stencil = true; + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + resize(width, height) + { + if (width === this.width && height === this.height) return; + + this.width = width; + this.height = height; + + this.dirtyId++; + this.dirtySize++; + + for (let i = 0; i < this.colorTextures.length; i++) + { + this.colorTextures[i].setSize(width, height); + } + + if (this.depthTexture) + { + this.depthTexture.setSize(width, height); + } + } +} diff --git a/packages/core/src/framebuffer/FramebufferSystem.js b/packages/core/src/framebuffer/FramebufferSystem.js new file mode 100644 index 0000000..2cc2437 --- /dev/null +++ b/packages/core/src/framebuffer/FramebufferSystem.js @@ -0,0 +1,246 @@ +import System from '../System'; +import { Rectangle } from '@pixi/math'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class FramebufferSystem extends System +{ + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + contextChange() + { + this.gl = this.renderer.gl; + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + this.current = null; + this.viewport = new Rectangle(); + + this.drawBufferExtension = this.renderer.context.extensions.drawBuffers; + } + + bind(framebuffer, frame) + { + const gl = this.gl; + + this.current = framebuffer; + + if (framebuffer) + { + // TODO cacheing layer! + + const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID] || this.initFramebuffer(framebuffer); + + gl.bindFramebuffer(gl.FRAMEBUFFER, fbo.framebuffer); + // makesure all textures are unbound.. + + // now check for updates... + if (fbo.dirtyId !== framebuffer.dirtyId) + { + fbo.dirtyId = framebuffer.dirtyId; + + if (fbo.dirtyFormat !== framebuffer.dirtyFormat) + { + fbo.dirtyFormat = framebuffer.dirtyFormat; + this.updateFramebuffer(framebuffer); + } + else if (fbo.dirtySize !== framebuffer.dirtySize) + { + fbo.dirtySize = framebuffer.dirtySize; + this.resizeFramebuffer(framebuffer); + } + } + + for (let i = 0; i < framebuffer.colorTextures.length; i++) + { + if (framebuffer.colorTextures[i].texturePart) + { + this.renderer.texture.unbind(framebuffer.colorTextures[i].texture); + } + else + { + this.renderer.texture.unbind(framebuffer.colorTextures[i]); + } + } + + if (framebuffer.depthTexture) + { + this.renderer.texture.unbind(framebuffer.depthTexture); + } + + if (frame) + { + this.setViewport(frame.x, frame.y, frame.width, frame.height); + } + else + { + this.setViewport(0, 0, framebuffer.width, framebuffer.height); + } + } + else + { + gl.bindFramebuffer(gl.FRAMEBUFFER, null); + + if (frame) + { + this.setViewport(frame.x, frame.y, frame.width, frame.height); + } + else + { + this.setViewport(0, 0, this.renderer.width, this.renderer.height); + } + } + } + + setViewport(x, y, width, height) + { + const v = this.viewport; + + if (v.width !== width || v.height !== height || v.x !== x || v.y !== y) + { + v.x = x; + v.y = y; + v.width = width; + v.height = height; + + this.gl.viewport(x, y, width, height); + } + } + + get size() + { + if (this.current) + { + // TODO store temp + return { x: 0, y: 0, width: this.current.width, height: this.current.height }; + } + + return { x: 0, y: 0, width: this.renderer.width, height: this.renderer.height }; + } + + clear(r, g, b, a) + { + const gl = this.gl; + + // TODO clear color can be set only one right? + gl.clearColor(r, g, b, a); + gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); + } + + // private functions... + + initFramebuffer(framebuffer) + { + const gl = this.gl; + + // TODO - make this a class? + const fbo = { + framebuffer: gl.createFramebuffer(), + stencil: null, + dirtyId: 0, + dirtyFormat: 0, + dirtySize: 0, + }; + + framebuffer.glFrameBuffers[this.CONTEXT_UID] = fbo; + + return fbo; + } + + resizeFramebuffer(framebuffer) + { + const gl = this.gl; + + if (framebuffer.stencil || framebuffer.depth) + { + gl.bindRenderbuffer(gl.RENDERBUFFER, this.stencil); + gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); + } + } + + updateFramebuffer(framebuffer) + { + const gl = this.gl; + + const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID]; + + // bind the color texture + const colorTextures = framebuffer.colorTextures; + + let count = colorTextures.length; + + if (!this.drawBufferExtension) + { + count = Math.min(count, 1); + } + + const activeTextures = []; + + for (let i = 0; i < count; i++) + { + const texture = framebuffer.colorTextures[i]; + + if (texture.texturePart) + { + this.renderer.texture.bind(texture.texture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.COLOR_ATTACHMENT0 + i, + gl.TEXTURE_CUBE_MAP_NEGATIVE_X + texture.side, + texture.texture._glTextures[this.CONTEXT_UID].texture, + 0); + } + else + { + this.renderer.texture.bind(texture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.COLOR_ATTACHMENT0 + i, + gl.TEXTURE_2D, + texture._glTextures[this.CONTEXT_UID].texture, + 0); + } + + activeTextures.push(gl.COLOR_ATTACHMENT0 + i); + } + + if (this.drawBufferExtension && activeTextures.length > 1) + { + this.drawBufferExtension.drawBuffersWEBGL(activeTextures); + } + + if (framebuffer.depthTexture) + { + const depthTextureExt = this.renderer.context.extensions.depthTexture; + + if (depthTextureExt) + { + const depthTexture = framebuffer.depthTexture; + + this.renderer.texture.bind(depthTexture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.DEPTH_ATTACHMENT, + gl.TEXTURE_2D, + depthTexture._glTextures[this.CONTEXT_UID].texture, + 0); + } + } + + if (framebuffer.stencil || framebuffer.depth) + { + fbo.stencil = gl.createRenderbuffer(); + + gl.bindRenderbuffer(gl.RENDERBUFFER, fbo.stencil); + + // TODO.. this is depth AND stencil? + gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, fbo.stencil); + gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); + // fbo.enableStencil(); + } + } +} diff --git a/packages/core/src/geometry/GLBuffer.js b/packages/core/src/geometry/GLBuffer.js new file mode 100644 index 0000000..0477e8a --- /dev/null +++ b/packages/core/src/geometry/GLBuffer.js @@ -0,0 +1,9 @@ +export default class GLBuffer +{ + constructor(buffer) + { + this.buffer = buffer; + this.updateID = -1; + this.byteLength = -1; + } +} diff --git a/packages/core/src/geometry/GeometrySystem.js b/packages/core/src/geometry/GeometrySystem.js new file mode 100644 index 0000000..959f827 --- /dev/null +++ b/packages/core/src/geometry/GeometrySystem.js @@ -0,0 +1,388 @@ +import System from '../System'; +import GLBuffer from './GLBuffer'; + +const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class GeometrySystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this._activeGeometry = null; + this._activeVao = null; + + this.hasVao = true; + this.hasInstance = true; + } + + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + contextChange() + { + const gl = this.gl = this.renderer.gl; + + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + + // webgl2 + if (!gl.createVertexArray) + { + // webgl 1! + let nativeVaoExtension = this.renderer.context.extensions.vertexArrayObject; + + if (this.renderer.legacy) + { + nativeVaoExtension = null; + } + + if (nativeVaoExtension) + { + gl.createVertexArray = () => + nativeVaoExtension.createVertexArrayOES(); + + gl.bindVertexArray = (vao) => + nativeVaoExtension.bindVertexArrayOES(vao); + + gl.deleteVertexArray = (vao) => + nativeVaoExtension.deleteVertexArrayOES(vao); + } + else + { + this.hasVao = false; + gl.createVertexArray = () => + { + // empty + }; + + gl.bindVertexArray = () => + { + // empty + }; + + gl.deleteVertexArray = () => + { + // empty + }; + } + } + + if (!gl.vertexAttribDivisor) + { + const instanceExt = gl.getExtension('ANGLE_instanced_arrays'); + + if (instanceExt) + { + gl.vertexAttribDivisor = (a, b) => + instanceExt.vertexAttribDivisorANGLE(a, b); + + gl.drawElementsInstanced = (a, b, c, d, e) => + instanceExt.drawElementsInstancedANGLE(a, b, c, d, e); + + gl.drawArraysInstanced = (a, b, c, d) => + instanceExt.drawArraysInstancedANGLE(a, b, c, d); + } + else + { + this.hasInstance = false; + } + } + } + + /** + * Binds geometry so that is can be drawn. Creating a Vao if required + * @private + * @param {PIXI.Geometry} geometry instance of geometry to bind + */ + bind(geometry, shader) + { + shader = shader || this.renderer.shader.shader; + + const gl = this.gl; + + // not sure the best way to address this.. + // currently different shaders require different VAOs for the same geometry + // Still mulling over the best way to solve this one.. + // will likely need to modify the shader attribute locations at run time! + let vaos = geometry.glVertexArrayObjects[this.CONTEXT_UID]; + + if (!vaos) + { + geometry.glVertexArrayObjects[this.CONTEXT_UID] = vaos = {}; + } + + const vao = vaos[shader.program.id] || this.initGeometryVao(geometry, shader.program); + + this._activeGeometry = geometry; + + if (this._activeVao !== vao) + { + this._activeVao = vao; + + if (this.hasVao) + { + gl.bindVertexArray(vao); + } + else + { + this.activateVao(geometry, shader.program); + } + } + + // TODO - optimise later! + // don't need to loop through if nothing changed! + // maybe look to add an 'autoupdate' to geometry? + this.updateBuffers(); + } + + reset() + { + this.unbind(); + } + + updateBuffers() + { + const geometry = this._activeGeometry; + const gl = this.gl; + + for (let i = 0; i < geometry.buffers.length; i++) + { + const buffer = geometry.buffers[i]; + + const glBuffer = buffer._glBuffers[this.CONTEXT_UID]; + + if (buffer._updateID !== glBuffer.updateID) + { + glBuffer.updateID = buffer._updateID; + + // TODO can cache this on buffer! maybe added a getter / setter? + const type = buffer.index ? gl.ELEMENT_ARRAY_BUFFER : gl.ARRAY_BUFFER; + const drawType = buffer.static ? gl.STATIC_DRAW : gl.DYNAMIC_DRAW; + + gl.bindBuffer(type, glBuffer.buffer); + + if (glBuffer.byteLength >= buffer.data.byteLength) + { + // offset is always zero for now! + gl.bufferSubData(type, 0, buffer.data); + } + else + { + gl.bufferData(type, buffer.data, drawType); + } + } + } + } + + checkCompatability(geometry, program) + { + // geometry must have at least all the attributes that the shader requires. + const geometryAttributes = geometry.attributes; + const shaderAttributes = program.attributeData; + + for (const j in shaderAttributes) + { + if (!geometryAttributes[j]) + { + throw new Error(`shader and geometry incompatible, geometry missing the "${j}" attribute`); + } + } + } + + /** + * Creates a Vao with the same structure as the geometry and stores it on the geometry. + * @private + * @param {PIXI.Geometry} geometry instance of geometry to to generate Vao for + */ + initGeometryVao(geometry, program) + { + this.checkCompatability(geometry, program); + + const gl = this.gl; + const CONTEXT_UID = this.CONTEXT_UID; + const buffers = geometry.buffers; + const attributes = geometry.attributes; + + const tempStride = {}; + const tempStart = {}; + + for (const j in buffers) + { + tempStride[j] = 0; + tempStart[j] = 0; + } + + for (const j in attributes) + { + if (!attributes[j].size && program.attributeData[j]) + { + attributes[j].size = program.attributeData[j].size; + } + + tempStride[attributes[j].buffer] += attributes[j].size * byteSizeMap[attributes[j].type]; + } + + for (const j in attributes) + { + const attribute = attributes[j]; + const attribSize = attribute.size; + + if (attribute.stride === undefined) + { + if (tempStride[attribute.buffer] === attribSize * byteSizeMap[attribute.type]) + { + attribute.stride = 0; + } + else + { + attribute.stride = tempStride[attribute.buffer]; + } + } + + if (attribute.start === undefined) + { + attribute.start = tempStart[attribute.buffer]; + + tempStart[attribute.buffer] += attribSize * byteSizeMap[attribute.type]; + } + } + + // first update - and create the buffers! + // only create a gl buffer if it actually gets + for (let i = 0; i < buffers.length; i++) + { + const buffer = buffers[i]; + + if (!buffer._glBuffers[CONTEXT_UID]) + { + buffer._glBuffers[CONTEXT_UID] = new GLBuffer(gl.createBuffer()); + } + } + + // TODO - maybe make this a data object? + // lets wait to see if we need to first! + const vao = gl.createVertexArray(); + + gl.bindVertexArray(vao); + + this.activateVao(geometry, program); + + gl.bindVertexArray(null); + + // add it to the cache! + geometry.glVertexArrayObjects[this.CONTEXT_UID][program.id] = vao; + + return vao; + } + + activateVao(geometry, program) + { + const gl = this.gl; + const CONTEXT_UID = this.CONTEXT_UID; + const buffers = geometry.buffers; + const attributes = geometry.attributes; + + if (geometry.indexBuffer) + { + // first update the index buffer if we have one.. + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, geometry.indexBuffer._glBuffers[CONTEXT_UID].buffer); + } + + let lastBuffer = null; + + // add a new one! + for (const j in attributes) + { + const attribute = attributes[j]; + const buffer = buffers[attribute.buffer]; + const glBuffer = buffer._glBuffers[CONTEXT_UID]; + + if (program.attributeData[j]) + { + if (lastBuffer !== glBuffer) + { + gl.bindBuffer(gl.ARRAY_BUFFER, glBuffer.buffer); + + lastBuffer = glBuffer; + } + + const location = program.attributeData[j].location; + + // TODO introduce state again + // we can optimise this for older devices that have no VAOs + gl.enableVertexAttribArray(location); + + gl.vertexAttribPointer(location, + attribute.size, + attribute.type || gl.FLOAT, + attribute.normalized, + attribute.stride, + attribute.start); + + if (attribute.instance) + { + // TODO calculate instance count based of this... + if (this.hasInstance) + { + gl.vertexAttribDivisor(location, 1); + } + else + { + throw new Error('geometry error, GPU Instancing is not supported on this device'); + } + } + } + } + } + + draw(type, size, start, instanceCount) + { + const gl = this.gl; + const geometry = this._activeGeometry; + + // TODO.. this should not change so maybe cache the function? + + if (geometry.indexBuffer) + { + if (geometry.instanced) + { + /* eslint-disable max-len */ + gl.drawElementsInstanced(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2, instanceCount || 1); + /* eslint-enable max-len */ + } + else + { + gl.drawElements(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2); + } + } + else + if (geometry.instanced) + { + // TODO need a better way to calculate size.. + gl.drawArraysInstanced(type, start, size || geometry.getSize(), instanceCount || 1); + } + else + { + gl.drawArrays(type, start, size || geometry.getSize()); + } + + return this; + } + + unbind() + { + this.gl.bindVertexArray(null); + this._activeVao = null; + this._activeGeometry = null; + } +} diff --git a/packages/core/src/geometry/utils/setVertexAttribArrays.js b/packages/core/src/geometry/utils/setVertexAttribArrays.js new file mode 100644 index 0000000..23b6959 --- /dev/null +++ b/packages/core/src/geometry/utils/setVertexAttribArrays.js @@ -0,0 +1,54 @@ +// var GL_MAP = {}; + +/** + * @param gl {WebGLRenderingContext} The current WebGL context + * @param attribs {*} + * @param state {*} + */ +export default function setVertexAttribArrays(gl, attribs, state) +{ + let i; + + if (state) + { + const tempAttribState = state.tempAttribState; + const attribState = state.attribState; + + for (i = 0; i < tempAttribState.length; i++) + { + tempAttribState[i] = false; + } + + // set the new attribs + for (i = 0; i < attribs.length; i++) + { + tempAttribState[attribs[i].attribute.location] = true; + } + + for (i = 0; i < attribState.length; i++) + { + if (attribState[i] !== tempAttribState[i]) + { + attribState[i] = tempAttribState[i]; + + if (state.attribState[i]) + { + gl.enableVertexAttribArray(i); + } + else + { + gl.disableVertexAttribArray(i); + } + } + } + } + else + { + for (i = 0; i < attribs.length; i++) + { + const attrib = attribs[i]; + + gl.enableVertexAttribArray(attrib.attribute.location); + } + } +} diff --git a/packages/core/src/index.js b/packages/core/src/index.js index 58c456a..fd1f547 100644 --- a/packages/core/src/index.js +++ b/packages/core/src/index.js @@ -1,30 +1,30 @@ import * as resources from './textures/resources'; -import * as systems from './renderers/systems'; +import * as systems from './systems'; -export { resources }; export { systems }; +export { resources }; -export { default as Renderer } from './renderers/Renderer'; -export { default as AbstractRenderer } from './renderers/AbstractRenderer'; -export { default as FrameBuffer } from './textures/FrameBuffer'; +export { default as System } from './System'; +export { default as Renderer } from './Renderer'; +export { default as AbstractRenderer } from './AbstractRenderer'; +export { default as FrameBuffer } from './framebuffer/FrameBuffer'; export { default as CubeTexture } from './textures/CubeTexture'; export { default as BaseTexture } from './textures/BaseTexture'; export { default as Texture } from './textures/Texture'; export { default as TextureMatrix } from './textures/TextureMatrix'; -export { default as RenderTexture } from './textures/RenderTexture'; -export { default as BaseRenderTexture } from './textures/BaseRenderTexture'; +export { default as RenderTexture } from './renderTexture/RenderTexture'; +export { default as BaseRenderTexture } from './renderTexture/BaseRenderTexture'; export { default as TextureUvs } from './textures/TextureUvs'; -export { default as State } from './renderers/State'; -export { default as ObjectRenderer } from './renderers/utils/ObjectRenderer'; -export { default as Quad } from './renderers/utils/Quad'; -export { default as QuadUv } from './renderers/utils/QuadUv'; -export { default as checkMaxIfStatmentsInShader } from './renderers/utils/checkMaxIfStatmentsInShader'; +export { default as State } from './state/State'; +export { default as ObjectRenderer } from './batch/ObjectRenderer'; +export { default as Quad } from './utils/Quad'; +export { default as QuadUv } from './utils/QuadUv'; +export { default as checkMaxIfStatmentsInShader } from './shader/utils/checkMaxIfStatmentsInShader'; export { default as Shader } from './shader/Shader'; export { default as Program } from './shader/Program'; export { default as UniformGroup } from './shader/UniformGroup'; -export { default as SpriteMaskFilter } from './renderers/filters/spriteMask/SpriteMaskFilter'; -export { default as Filter } from './renderers/filters/Filter'; +export { default as SpriteMaskFilter } from './filters/spriteMask/SpriteMaskFilter'; +export { default as Filter } from './filters/Filter'; export { default as Attribute } from './geometry/Attribute'; export { default as Buffer } from './geometry/Buffer'; export { default as Geometry } from './geometry/Geometry'; - diff --git a/packages/core/src/mask/MaskSystem.js b/packages/core/src/mask/MaskSystem.js new file mode 100644 index 0000000..99174c2 --- /dev/null +++ b/packages/core/src/mask/MaskSystem.js @@ -0,0 +1,199 @@ +import System from '../System'; +import AlphaMaskFilter from '../filters/spriteMask/SpriteMaskFilter'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class MaskSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + // TODO - we don't need both! + this.scissor = false; + this.scissorData = null; + this.scissorRenderTarget = null; + + this.enableScissor = false; + + this.alphaMaskPool = []; + this.alphaMaskIndex = 0; + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.DisplayObject} target - Display Object to push the mask to + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + push(target, maskData) + { + // TODO the root check means scissor rect will not + // be used on render textures more info here: + // https://github.com/pixijs/pixi.js/pull/3545 + + if (maskData.texture) + { + this.pushSpriteMask(target, maskData); + } + else if (this.enableScissor + && !this.scissor + && this.renderer._activeRenderTarget.root + && !this.renderer.stencil.stencilMaskStack.length + && maskData.isFastRect()) + { + const matrix = maskData.worldTransform; + + let rot = Math.atan2(matrix.b, matrix.a); + + // use the nearest degree! + rot = Math.round(rot * (180 / Math.PI)); + + if (rot % 90) + { + this.pushStencilMask(maskData); + } + else + { + this.pushScissorMask(target, maskData); + } + } + else + { + this.pushStencilMask(maskData); + } + } + + /** + * Removes the last mask from the mask stack and doesn't return it. + * + * @param {PIXI.DisplayObject} target - Display Object to pop the mask from + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + pop(target, maskData) + { + if (maskData.texture) + { + this.popSpriteMask(target, maskData); + } + else if (this.enableScissor && !this.renderer.stencil.stencilMaskStack.length) + { + this.popScissorMask(target, maskData); + } + else + { + this.popStencilMask(target, maskData); + } + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.RenderTarget} target - Display Object to push the sprite mask to + * @param {PIXI.Sprite} maskData - Sprite to be used as the mask + */ + pushSpriteMask(target, maskData) + { + let alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; + + if (!alphaMaskFilter) + { + alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; + } + + alphaMaskFilter[0].resolution = this.renderer.resolution; + alphaMaskFilter[0].maskSprite = maskData; + + // TODO - may cause issues! + target.filterArea = maskData.getBounds(true); + + this.renderer.filterSystem.pushFilter(target, alphaMaskFilter); + + this.alphaMaskIndex++; + } + + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popSpriteMask() + { + this.renderer.filterSystem.popFilter(); + this.alphaMaskIndex--; + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + pushStencilMask(maskData) + { + this.renderer.batch.flush(); + this.renderer.stencil.pushStencil(maskData); + } + + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popStencilMask() + { + // this.renderer.currentRenderer.stop(); + this.renderer.stencil.popStencil(); + } + + /** + * + * @param {PIXI.DisplayObject} target - Display Object to push the mask to + * @param {PIXI.Graphics} maskData - The masking data. + */ + pushScissorMask(target, maskData) + { + maskData.renderable = true; + + const renderTarget = this.renderer._activeRenderTarget; + + const bounds = maskData.getBounds(); + + bounds.fit(renderTarget.size); + maskData.renderable = false; + + this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); + + const resolution = this.renderer.resolution; + + this.renderer.gl.scissor( + bounds.x * resolution, + (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, + bounds.width * resolution, + bounds.height * resolution + ); + + this.scissorRenderTarget = renderTarget; + this.scissorData = maskData; + this.scissor = true; + } + + /** + * + * + */ + popScissorMask() + { + this.scissorRenderTarget = null; + this.scissorData = null; + this.scissor = false; + + // must be scissor! + const gl = this.renderer.gl; + + gl.disable(gl.SCISSOR_TEST); + } +} diff --git a/packages/core/src/mask/StencilSystem.js b/packages/core/src/mask/StencilSystem.js new file mode 100644 index 0000000..62796a5 --- /dev/null +++ b/packages/core/src/mask/StencilSystem.js @@ -0,0 +1,133 @@ +import System from '../System'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class StencilSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + this.stencilMaskStack = []; + } + + /** + * Changes the mask stack that is used by this System. + * + * @param {PIXI.Graphics[]} stencilMaskStack - The mask stack + */ + setMaskStack(stencilMaskStack) + { + const gl = this.renderer.gl; + + if (stencilMaskStack.length !== this.stencilMaskStack.length) + { + if (stencilMaskStack.length === 0) + { + gl.disable(gl.STENCIL_TEST); + } + else + { + gl.enable(gl.STENCIL_TEST); + } + } + + this.stencilMaskStack = stencilMaskStack; + } + + /** + * Applies the Mask and adds it to the current stencil stack. @alvin + * + * @param {PIXI.Graphics} graphics - The mask + */ + pushStencil(graphics) + { + this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); + + // this.renderer._activeRenderTarget.attachStencilBuffer(); + + const gl = this.renderer.gl; + const prevMaskCount = this.stencilMaskStack.length; + + if (prevMaskCount === 0) + { + gl.enable(gl.STENCIL_TEST); + } + + this.stencilMaskStack.push(graphics); + + // Increment the refference stencil value where the new mask overlaps with the old ones. + gl.colorMask(false, false, false, false); + gl.stencilFunc(gl.EQUAL, prevMaskCount, this._getBitwiseMask()); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.INCR); + this.renderer.plugins.graphics.render(graphics); + + this._useCurrent(); + } + + /** + * Removes the last mask from the stencil stack. @alvin + */ + popStencil() + { + this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); + + const gl = this.renderer.gl; + const graphics = this.stencilMaskStack.pop(); + + if (this.stencilMaskStack.length === 0) + { + // the stack is empty! + gl.disable(gl.STENCIL_TEST); + gl.clear(gl.STENCIL_BUFFER_BIT); + gl.clearStencil(0); + } + else + { + // Decrement the refference stencil value where the popped mask overlaps with the other ones + gl.colorMask(false, false, false, false); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.DECR); + this.renderer.plugins.graphics.render(graphics); + + this._useCurrent(); + } + } + + /** + * Setup renderer to use the current stencil data. + */ + _useCurrent() + { + const gl = this.renderer.gl; + + gl.colorMask(true, true, true, true); + gl.stencilFunc(gl.EQUAL, this.stencilMaskStack.length, this._getBitwiseMask()); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP); + } + + /** + * Fill 1s equal to the number of acitve stencil masks. + * + * @return {number} The bitwise mask. + */ + _getBitwiseMask() + { + return (1 << this.stencilMaskStack.length) - 1; + } + + /** + * Destroys the mask stack. + * + */ + destroy() + { + super.destroy(this); + + this.stencilMaskStack = null; + } +} diff --git a/packages/core/src/projection/ProjectionSystem.js b/packages/core/src/projection/ProjectionSystem.js new file mode 100644 index 0000000..6e63b73 --- /dev/null +++ b/packages/core/src/projection/ProjectionSystem.js @@ -0,0 +1,73 @@ +import System from '../System'; +import { Matrix } from '@pixi/math'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class ProjectionSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.projectionMatrix = new Matrix(); + } + + update(destinationFrame, sourceFrame, resolution, root) + { + this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; + this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; + + this.calculateProjection(this.destinationFrame, this.sourceFrame, resolution, root); + + this.renderer.globalUniforms.uniforms.projectionMatrix = this.projectionMatrix; + this.renderer.globalUniforms.update(); + } + + /** + * Updates the projection matrix based on a projection frame (which is a rectangle) + * + * @param {Rectangle} destinationFrame - The destination frame. + * @param {Rectangle} sourceFrame - The source frame. + */ + calculateProjection(destinationFrame, sourceFrame, resolution, root) + { + const pm = this.projectionMatrix; + + // I don't think we will need this line.. + // pm.identity(); + + if (!root) + { + pm.a = (1 / destinationFrame.width * 2) * resolution; + pm.d = (1 / destinationFrame.height * 2) * resolution; + + pm.tx = -1 - (sourceFrame.x * pm.a); + pm.ty = -1 - (sourceFrame.y * pm.d); + } + else + { + pm.a = (1 / destinationFrame.width * 2) * resolution; + pm.d = (-1 / destinationFrame.height * 2) * resolution; + + pm.tx = -1 - (sourceFrame.x * pm.a); + pm.ty = 1 - (sourceFrame.y * pm.d); + } + } + + /** + * Sets the transform of the active render target to the given matrix + * + * @param {PIXI.Matrix} matrix - The transformation matrix + */ + setTransform()// matrix) + { + // this._activeRenderTarget.transform = matrix; + } +} diff --git a/packages/core/src/renderTexture/BaseRenderTexture.js b/packages/core/src/renderTexture/BaseRenderTexture.js new file mode 100644 index 0000000..88eb83b --- /dev/null +++ b/packages/core/src/renderTexture/BaseRenderTexture.js @@ -0,0 +1,137 @@ +import BaseTexture from '../textures/BaseTexture'; +import FrameBuffer from '../framebuffer/FrameBuffer'; + +/** + * A BaseRenderTexture is a special texture that allows any PixiJS display object to be rendered to it. + * + * __Hint__: All DisplayObjects (i.e. Sprites) that render to a BaseRenderTexture should be preloaded + * otherwise black rectangles will be drawn instead. + * + * A BaseRenderTexture takes a snapshot of any Display Object given to its render method. The position + * and rotation of the given Display Objects is ignored. For example: + * + * ```js + * let renderer = PIXI.autoDetectRenderer(1024, 1024, { view: canvas, ratio: 1 }); + * let baseRenderTexture = new PIXI.BaseRenderTexture(renderer, 800, 600); + * let sprite = PIXI.Sprite.fromImage("spinObj_01.png"); + * + * sprite.position.x = 800/2; + * sprite.position.y = 600/2; + * sprite.anchor.x = 0.5; + * sprite.anchor.y = 0.5; + * + * baseRenderTexture.render(sprite); + * ``` + * + * The Sprite in this case will be rendered using its local transform. To render this sprite at 0,0 + * you can clear the transform + * + * ```js + * + * sprite.setTransform() + * + * let baseRenderTexture = new PIXI.BaseRenderTexture(100, 100); + * let renderTexture = new PIXI.RenderTexture(baseRenderTexture); + * + * renderer.render(sprite, renderTexture); // Renders to center of RenderTexture + * ``` + * + * @class + * @extends PIXI.BaseTexture + * @memberof PIXI + */ +export default class BaseRenderTexture extends BaseTexture +{ + /** + * @param {object} [options] + * @param {number} [options.width=100] - The width of the base render texture + * @param {number} [options.height=100] - The height of the base render texture + * @param {PIXI.SCALE_MODES} [options.scaleMode] - See {@link PIXI.SCALE_MODES} for possible values + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the texture being generated + */ + constructor(options) + { + if (typeof options === 'number') + { + /* eslint-disable prefer-rest-params */ + // Backward compatibility of signature + const width = arguments[0]; + const height = arguments[1]; + const scaleMode = arguments[2]; + const resolution = arguments[3]; + + options = { width, height, scaleMode, resolution }; + /* eslint-enable prefer-rest-params */ + } + + super(null, options); + + const { width, height } = options || {}; + + // Set defaults + this.mipmap = false; + this.width = Math.ceil(width) || 100; + this.height = Math.ceil(height) || 100; + this.valid = true; + + /** + * A map of renderer IDs to webgl renderTargets + * + * @private + * @member {object} + */ + // this._glRenderTargets = {}; + + /** + * A reference to the canvas render target (we only need one as this can be shared across renderers) + * + * @private + * @member {object} + */ + this._canvasRenderTarget = null; + + this.clearColor = [0, 0, 0, 0]; + + this.frameBuffer = new FrameBuffer(width * this.resolution, height * this.resolution) + .addColorTexture(0, this); + + // TODO - could this be added the systems? + + /** + * The data structure for the stencil masks + * + * @member {PIXI.Graphics[]} + */ + this.stencilMaskStack = []; + + /** + * The data structure for the filters + * + * @member {PIXI.Graphics[]} + */ + this.filterStack = [{}]; + } + + /** + * Resizes the BaseRenderTexture. + * + * @param {number} width - The width to resize to. + * @param {number} height - The height to resize to. + */ + resize(width, height) + { + width = Math.ceil(width); + height = Math.ceil(height); + this.frameBuffer.resize(width * this.resolution, height * this.resolution); + } + + /** + * Destroys this texture + * + */ + destroy() + { + super.destroy(true); + this.renderer = null; + } +} diff --git a/packages/core/src/renderTexture/RenderTexture.js b/packages/core/src/renderTexture/RenderTexture.js new file mode 100644 index 0000000..f1ecf1f --- /dev/null +++ b/packages/core/src/renderTexture/RenderTexture.js @@ -0,0 +1,153 @@ +import BaseRenderTexture from './BaseRenderTexture'; +import Texture from '../textures/Texture'; + +/** + * A RenderTexture is a special texture that allows any PixiJS display object to be rendered to it. + * + * __Hint__: All DisplayObjects (i.e. Sprites) that render to a RenderTexture should be preloaded + * otherwise black rectangles will be drawn instead. + * + * A RenderTexture takes a snapshot of any Display Object given to its render method. For example: + * + * ```js + * let renderer = PIXI.autoDetectRenderer(1024, 1024, { view: canvas, ratio: 1 }); + * let renderTexture = PIXI.RenderTexture.create(800, 600); + * let sprite = PIXI.Sprite.fromImage("spinObj_01.png"); + * + * sprite.position.x = 800/2; + * sprite.position.y = 600/2; + * sprite.anchor.x = 0.5; + * sprite.anchor.y = 0.5; + * + * renderer.render(sprite, renderTexture); + * ``` + * + * The Sprite in this case will be rendered using its local transform. To render this sprite at 0,0 + * you can clear the transform + * + * ```js + * + * sprite.setTransform() + * + * let renderTexture = new PIXI.RenderTexture.create(100, 100); + * + * renderer.render(sprite, renderTexture); // Renders to center of RenderTexture + * ``` + * + * @class + * @extends PIXI.Texture + * @memberof PIXI + */ +export default class RenderTexture extends Texture +{ + /** + * @param {PIXI.BaseRenderTexture} baseRenderTexture - The renderer used for this RenderTexture + * @param {PIXI.Rectangle} [frame] - The rectangle frame of the texture to show + */ + constructor(baseRenderTexture, frame) + { + // support for legacy.. + let _legacyRenderer = null; + + if (!(baseRenderTexture instanceof BaseRenderTexture)) + { + /* eslint-disable prefer-rest-params, no-console */ + const width = arguments[1]; + const height = arguments[2]; + const scaleMode = arguments[3]; + const resolution = arguments[4]; + + // we have an old render texture.. + console.warn(`Please use RenderTexture.create(${width}, ${height}) instead of the ctor directly.`); + _legacyRenderer = arguments[0]; + /* eslint-enable prefer-rest-params, no-console */ + + frame = null; + baseRenderTexture = new BaseRenderTexture({ + width, + height, + scaleMode, + resolution, + }); + } + + /** + * The base texture object that this texture uses + * + * @member {BaseTexture} + */ + super(baseRenderTexture, frame); + + this.legacyRenderer = _legacyRenderer; + + /** + * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. + * + * @member {boolean} + */ + this.valid = true; + + /** + * FilterSystem temporary storage + * @private + * @member {PIXI.Rectangle} + */ + this.filterFrame = null; + + this._updateUvs(); + } + + /** + * Resizes the RenderTexture. + * + * @param {number} width - The width to resize to. + * @param {number} height - The height to resize to. + * @param {boolean} [resizeBaseTexture=true] - Should the baseTexture.width and height values be resized as well? + */ + resize(width, height, resizeBaseTexture = true) + { + width = Math.ceil(width); + height = Math.ceil(height); + + // TODO - could be not required.. + this.valid = (width > 0 && height > 0); + + this._frame.width = this.orig.width = width; + this._frame.height = this.orig.height = height; + + if (resizeBaseTexture) + { + this.baseTexture.resize(width, height); + } + + this._updateUvs(); + } + + /** + * A short hand way of creating a render texture. + * + * @param {object} [options] - Options + * @param {number} [options.width=100] - The width of the render texture + * @param {number} [options.height=100] - The height of the render texture + * @param {number} [options.scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the texture being generated + * @return {PIXI.RenderTexture} The new render texture + */ + static create(options) + { + // fallback, old-style: create(width, height, scaleMode, resolution) + if (typeof options === 'number') + { + /* eslint-disable prefer-rest-params */ + options = { + width: options, + height: arguments[1], + scaleMode: arguments[2], + resolution: arguments[3], + }; + /* eslint-enable prefer-rest-params */ + } + + return new RenderTexture(new BaseRenderTexture(options)); + } +} diff --git a/packages/core/src/renderTexture/RenderTextureSystem.js b/packages/core/src/renderTexture/RenderTextureSystem.js new file mode 100644 index 0000000..8d0ca61 --- /dev/null +++ b/packages/core/src/renderTexture/RenderTextureSystem.js @@ -0,0 +1,115 @@ +import System from '../System'; +import { Rectangle } from '@pixi/math'; + +const tempRect = new Rectangle(); + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class RenderTextureSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.clearColor = renderer._backgroundColorRgba; + + // TODO moe this property somewhere else! + this.defaultMaskStack = []; + this.defaultFilterStack = [{}]; + + // empty render texture? + this.renderTexture = null; + + this.destinationFrame = new Rectangle(); + } + + bind(renderTexture, sourceFrame, destinationFrame) + { + // TODO - do we want this?? + if (this.renderTexture === renderTexture) return; + this.renderTexture = renderTexture; + + const renderer = this.renderer; + + if (renderTexture) + { + const baseTexture = renderTexture.baseTexture; + + if (!destinationFrame) + { + tempRect.width = baseTexture.realWidth; + tempRect.height = baseTexture.realHeight; + + destinationFrame = tempRect; + } + + if (!sourceFrame) + { + sourceFrame = destinationFrame; + } + + this.renderer.framebuffer.bind(baseTexture.frameBuffer, destinationFrame); + + this.renderer.projection.update(destinationFrame, sourceFrame, baseTexture.resolution, false); + this.renderer.stencil.setMaskStack(baseTexture.stencilMaskStack); + } + else + { + // TODO these validation checks happen deeper down.. + // thing they can be avoided.. + if (!destinationFrame) + { + tempRect.width = renderer.width; + tempRect.height = renderer.height; + + destinationFrame = tempRect; + } + + if (!sourceFrame) + { + sourceFrame = destinationFrame; + } + + renderer.framebuffer.bind(null, destinationFrame); + + // TODO store this.. + this.renderer.projection.update(destinationFrame, sourceFrame, this.renderer.resolution, true); + this.renderer.stencil.setMaskStack(this.defaultMaskStack); + } + + this.destinationFrame.copyFrom(destinationFrame); + } + + /** + * Erases the render texture and fills the drawing area with a colour + * + * @param {number} [clearColor] - The colour + * @return {PIXI.Renderer} Returns itself. + */ + clear(clearColor) + { + if (this.renderTexture) + { + clearColor = clearColor || this.renderTexture.baseTexture.clearColor; + } + else + { + clearColor = clearColor || this.clearColor; + } + + this.renderer.framebuffer.clear(clearColor[0], clearColor[1], clearColor[2], clearColor[3]); + } + + resize()// screenWidth, screenHeight) + { + // resize the root only! + this.bind(null); + } +} diff --git a/packages/core/src/renderers/AbstractRenderer.js b/packages/core/src/renderers/AbstractRenderer.js deleted file mode 100644 index 928670f..0000000 --- a/packages/core/src/renderers/AbstractRenderer.js +++ /dev/null @@ -1,353 +0,0 @@ -import { hex2string, hex2rgb } from '@pixi/utils'; -import { Matrix, Rectangle } from '@pixi/math'; -import { RENDERER_TYPE } from '@pixi/constants'; -import { settings } from '@pixi/settings'; -import { Container } from '@pixi/display'; -import RenderTexture from '../textures/RenderTexture'; -import EventEmitter from 'eventemitter3'; - -const tempMatrix = new Matrix(); - -/** - * 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 EventEmitter - * @memberof PIXI - */ -export default class AbstractRenderer extends EventEmitter -{ - // eslint-disable-next-line valid-jsdoc - /** - * @param {string} system - The name of the system this renderer is for. - * @param {object} [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.transparent=false] - If the render view is transparent, default false - * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false - * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) - * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. The - * resolution of the renderer retina would be 2. - * @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 {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when rendering, - * stopping pixel interpolation. - */ - constructor(system, options, arg2, arg3) - { - super(); - - // Support for constructor(system, screenWidth, screenHeight, options) - if (typeof options === 'number') - { - options = Object.assign({ - width: options, - height: arg2 || settings.RENDER_OPTIONS.height, - }, arg3); - } - - // 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 = RENDERER_TYPE.UNKNOWN; - - /** - * Measurements of the screen. (0, 0, screenWidth, screenHeight) - * - * Its safe to use as filterArea or hitArea for 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 1 - */ - this.resolution = options.resolution || settings.RESOLUTION; - - /** - * Whether the render view is transparent - * - * @member {boolean} - */ - this.transparent = options.transparent; - - /** - * Whether css dimensions of canvas view should be resized to screen dimensions automatically - * - * @member {boolean} - */ - this.autoResize = options.autoResize || false; - - /** - * Tracks the blend modes useful for this renderer. - * - * @member {object} - */ - this.blendModes = null; - - /** - * 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; - - /** - * If true PixiJS will Math.floor() x/y values when rendering, stopping pixel interpolation. - * Handy for crisp pixel art and speed on legacy devices. - * - * @member {boolean} - */ - this.roundPixels = options.roundPixels; - - /** - * The background color as a number. - * - * @member {number} - * @private - */ - this._backgroundColor = 0x000000; - - /** - * The background color as an [R, G, B] array. - * - * @member {number[]} - * @private - */ - this._backgroundColorRgba = [0, 0, 0, 0]; - - /** - * The background color as a string. - * - * @member {string} - * @private - */ - this._backgroundColorString = '#000000'; - - this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter - - /** - * This temporary display object used as the parent of the currently being rendered item - * - * @member {PIXI.DisplayObject} - * @private - */ - this._tempDisplayObjectParent = new Container(); - - /** - * The last root object that the renderer tried to render. - * - * @member {PIXI.DisplayObject} - * @private - */ - this._lastObjectRendered = this._tempDisplayObjectParent; - - /** - * Collection of plugins. - * @readonly - * @member {object} - */ - this.plugins = {}; - } - - /** - * Initialize the plugins. - * - * @protected - * @param {object} staticMap - The dictionary of staticly saved plugins. - */ - initPlugins(staticMap) - { - 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() - { - return this.view.width; - } - - /** - * Same as view.height, actual number of pixels in the canvas by vertical - * - * @member {number} - * @readonly - * @default 600 - */ - get height() - { - return this.view.height; - } - - /** - * Resizes the screen and canvas to the specified width and height - * Canvas dimensions are multiplied by resolution - * - * @param {number} screenWidth - the new width of the screen - * @param {number} screenHeight - the new height of the screen - */ - resize(screenWidth, screenHeight) - { - this.screen.width = screenWidth; - this.screen.height = screenHeight; - - this.view.width = screenWidth * this.resolution; - this.view.height = screenHeight * this.resolution; - - if (this.autoResize) - { - this.view.style.width = `${screenWidth}px`; - this.view.style.height = `${screenHeight}px`; - } - } - - /** - * 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 {PIXI.DisplayObject} displayObject - The displayObject the object will be generated from - * @param {number} scaleMode - Should be one of the scaleMode consts - * @param {number} resolution - The resolution / device pixel ratio of the texture being generated - * @param {PIXI.Rectangle} [region] - The region of the displayObject, that shall be rendered, - * if no region is specified, defaults to the local bounds of the displayObject. - * @return {PIXI.Texture} a texture of the graphics object - */ - generateTexture(displayObject, scaleMode, resolution, region) - { - region = region || displayObject.getLocalBounds(); - - // 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(region.width | 0, region.height | 0, scaleMode, resolution); - - tempMatrix.tx = -region.x; - tempMatrix.ty = -region.y; - - this.render(displayObject, renderTexture, false, tempMatrix, true); - - return renderTexture; - } - - /** - * Removes everything from the renderer and optionally removes the Canvas DOM element. - * - * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. - */ - destroy(removeView) - { - 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); - } - - this.plugins = null; - - this.type = RENDERER_TYPE.UNKNOWN; - - this.view = null; - - this.screen = null; - - this.resolution = 0; - - this.transparent = false; - - this.autoResize = false; - - this.blendModes = null; - - this.options = null; - - this.preserveDrawingBuffer = false; - this.clearBeforeRender = false; - - this.roundPixels = false; - - this._backgroundColor = 0; - this._backgroundColorRgba = null; - this._backgroundColorString = null; - - this._tempDisplayObjectParent = null; - this._lastObjectRendered = null; - } - - /** - * The background color to fill if not transparent - * - * @member {number} - */ - get backgroundColor() - { - return this._backgroundColor; - } - - set backgroundColor(value) // eslint-disable-line require-jsdoc - { - this._backgroundColor = value; - this._backgroundColorString = hex2string(value); - hex2rgb(value, this._backgroundColorRgba); - } -} diff --git a/packages/core/src/renderers/Renderer.js b/packages/core/src/renderers/Renderer.js deleted file mode 100644 index a2020b1..0000000 --- a/packages/core/src/renderers/Renderer.js +++ /dev/null @@ -1,329 +0,0 @@ -import AbstractRenderer from './AbstractRenderer'; -import { sayHello } from '@pixi/utils'; -import MaskSystem from './systems/MaskSystem'; -import StencilSystem from './systems/StencilSystem'; -import FilterSystem from './systems/filter/FilterSystem'; -import FramebufferSystem from './systems/FramebufferSystem'; -import RenderTextureSystem from './systems/RenderTextureSystem'; -import TextureSystem from './systems/textures/TextureSystem'; -import ProjectionSystem from './systems/ProjectionSystem'; -import StateSystem from './systems/StateSystem'; -import GeometrySystem from './systems/geometry/GeometrySystem'; -import ShaderSystem from './systems/shader/ShaderSystem'; -import ContextSystem from './systems/ContextSystem'; -import BatchSystem from './systems/BatchSystem'; -import TextureGCSystem from './systems/textures/TextureGCSystem'; -import { RENDERER_TYPE } from '@pixi/constants'; -import UniformGroup from '../shader/UniformGroup'; -import { Matrix } from '@pixi/math'; -import Runner from 'mini-runner'; - -/** - * 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 Render works by automatically managing webGLBatchs. - * 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 :) - * - * @class - * @memberof PIXI - * @extends PIXI.AbstractRenderer - */ -export default class Renderer extends AbstractRenderer -{ - // eslint-disable-next-line valid-jsdoc - /** - * - * @param {object} [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.transparent=false] - If the render view is transparent, default false - * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false - * @param {boolean} [options.antialias=false] - sets antialias. If not available natively then FXAA - * antialiasing is used - * @param {boolean} [options.forceFXAA=false] - forces FXAA antialiasing to be used over native. - * FXAA is faster, but may not always look as great - * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. - * The resolution of the renderer retina would be 2. - * @param {boolean} [options.clearBeforeRender=true] - This sets if the renderer will clear - * the canvas or not before the new render pass. If you wish to set this to false, you *must* set - * preserveDrawingBuffer to `true`. - * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, - * enable this if you need to call toDataUrl on the webgl context. - * @param {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when - * rendering, stopping pixel interpolation. - * @param {number} [options.backgroundColor=0x000000] - The background color of the rendered area - * (shown if not transparent). - * @param {boolean} [options.legacy=false] - If true PixiJS will aim to ensure compatibility - * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. - * @param {string} [options.powerPreference] - Parameter passed to webgl context, set to "high-performance" - * for devices with dual graphics card - */ - constructor(options, arg2, arg3) - { - super('WebGL', options, arg2, arg3); - - /** - * The type of this renderer as a standardised const - * - * @member {number} - * @see PIXI.RENDERER_TYPE - */ - this.type = RENDERER_TYPE.WEBGL; - - // this will be set by the contextSystem (this.context) - this.gl = null; - this.CONTEXT_UID = 0; - this.legacy = !!options.legacy; - - // TODO legacy! - - // runners! - this.runners = { - destroy: new Runner('destroy'), - contextChange: new Runner('contextChange', 1), - reset: new Runner('reset'), - update: new Runner('update'), - postrender: new Runner('postrender'), - prerender: new Runner('prerender'), - resize: new Runner('resize', 2), - }; - - 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(GeometrySystem, 'geometry') - .addSystem(FramebufferSystem, 'framebuffer') - .addSystem(StencilSystem, 'stencil') - .addSystem(ProjectionSystem, 'projection') - .addSystem(TextureGCSystem, 'textureGC') - .addSystem(FilterSystem, 'filter') - .addSystem(RenderTextureSystem, 'renderTexture') - .addSystem(BatchSystem, 'batch'); - - this.initPlugins(Renderer.__plugins); - - /** - * The options passed in to create a new webgl context. - * - * @member {object} - * @private - */ - if (options.context) - { - this.context.initFromContext(options.context); - } - else - { - this.context.initFromOptions({ - alpha: this.transparent, - antialias: options.antialias, - premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', - stencil: true, - 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); - } - - addSystem(_class, name) - { - if (!name) - { - name = _class.name; - } - - // TODO - read name from class.name.. - - /* - if(name.includes('System')) - { - name = name.replace('System', ''); - name = name.charAt(0).toLowerCase() + name.slice(1); - } - */ - - const system = new _class(this); - - if (this[name]) - { - throw new Error(`Whoops! ${name} is already a manger`); - } - - this[name] = system; - - for (const i in this.runners) - { - this.runners[i].add(system); - } - - return this; - - /** - * 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. - */ - } - - /** - * Renders the object to its webGL view - * - * @param {PIXI.DisplayObject} displayObject - the object to be rendered - * @param {PIXI.RenderTexture} renderTexture - The render texture to render to. - * @param {boolean} [clear] - Should the canvas be cleared before the new render - * @param {PIXI.Transform} [transform] - A transform to apply to the render texture before rendering. - * @param {boolean} [skipUpdateTransform] - Should we skip the update transform pass? - */ - render(displayObject, renderTexture, clear, transform, skipUpdateTransform) - { - // can be handy to know! - this.renderingToScreen = !renderTexture; - - this.runners.prerender.run(); - this.emit('prerender'); - - // 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.parent; - - displayObject.parent = this._tempDisplayObjectParent; - displayObject.updateTransform(); - displayObject.parent = 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.run(); - - this.emit('postrender'); - } - - /** - * Resizes the webGL view to the specified width and height. - * - * @param {number} screenWidth - the new width of the screen - * @param {number} screenHeight - the new height of the screen - */ - resize(screenWidth, screenHeight) - { - AbstractRenderer.prototype.resize.call(this, screenWidth, screenHeight); - - this.runners.resize.run(screenWidth, screenHeight); - } - - /** - * Resets the WebGL state so you can render things however you fancy! - * - * @return {PIXI.Renderer} Returns itself. - */ - reset() - { - this.runners.reset.run(); - - return this; - } - - clear() - { - this.framebuffer.bind(); - this.framebuffer.clear(); - } - - /** - * Removes everything from the renderer (event listeners, spritebatch, etc...) - * - * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. - * See: https://github.com/pixijs/pixi.js/issues/2233 - */ - destroy(removeView) - { - this.runners.destroy.run(); - - // call base destroy - super.destroy(removeView); - - // TODO nullify all the managers.. - this.gl = null; - } - - /** - * 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 - * builds and excluding plugins. - * @name PIXI.Renderer#plugins - * @type {object} - * @readonly - * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. - * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. - * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. - * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. - */ - - /** - * Adds a plugin to the renderer. - * - * @method - * @param {string} pluginName - The name of the plugin. - * @param {Function} ctor - The constructor function or class for the plugin. - */ - static registerPlugin(pluginName, ctor) - { - Renderer.__plugins = Renderer.__plugins || {}; - Renderer.__plugins[pluginName] = ctor; - } -} diff --git a/packages/core/src/renderers/State.js b/packages/core/src/renderers/State.js deleted file mode 100644 index fa3f221..0000000 --- a/packages/core/src/renderers/State.js +++ /dev/null @@ -1,163 +0,0 @@ -/* eslint-disable max-len */ - -const BLEND = 0; -const OFFSET = 1; -const CULLING = 2; -const DEPTH_TEST = 3; -const WINDING = 4; - -/** - * This is a webGL state. It is passed The WebGL StateManager. - * Each mesh renderered may require webGL to be in a different state. - * For example you may want different blend mode or to enable polygon offsets - * - * @class - * @memberof PIXI - */ -export default class State -{ - /** - * - */ - constructor() - { - this.data = 0; - - this.blendMode = 0; - this.polygonOffset = 0; - - this.blend = true; - // this.depthTest = true; - } - - /** - * Activates blending of the computed fragment color values - * - * @member {boolean} - */ - get blend() - { - return !!(this.data & (1 << BLEND)); - } - - set blend(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << BLEND)) !== value) - { - this.data ^= (1 << BLEND); - } - } - - /** - * Activates adding an offset to depth values of polygon's fragments - * - * @member {boolean} - * @default false - */ - get offsets() - { - return !!(this.data & (1 << OFFSET)); - } - - set offsets(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << OFFSET)) !== value) - { - this.data ^= (1 << OFFSET); - } - } - - /** - * Activates culling of polygons. - * - * @member {boolean} - * @default false - */ - get culling() - { - return !!(this.data & (1 << CULLING)); - } - - set culling(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << CULLING)) !== value) - { - this.data ^= (1 << CULLING); - } - } - - /** - * Activates depth comparisons and updates to the depth buffer. - * - * @member {boolean} - * @default false - */ - get depthTest() - { - return !!(this.data & (1 << DEPTH_TEST)); - } - - set depthTest(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << DEPTH_TEST)) !== value) - { - this.data ^= (1 << DEPTH_TEST); - } - } - - /** - * Specifies whether or not front or back-facing polygons can be culled. - * @member {boolean} - * @default false - */ - get clockwiseFrontFace() - { - return !!(this.data & (1 << WINDING)); - } - - set clockwiseFrontFace(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << WINDING)) !== value) - { - this.data ^= (1 << WINDING); - } - } - - /** - * The blend mode to be applied when this state is set. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. - * Setting this mode to anything other than NO_BLEND will automatically switch blending on. - * - * @member {boolean} - * @default PIXI.BLEND_MODES.NORMAL - * @see PIXI.BLEND_MODES - */ - get blendMode() - { - return this._blendMode; - } - - set blendMode(value) // eslint-disable-line require-jsdoc - { - // 17 is NO BLEND - this.blend = (value !== 17); - this._blendMode = value; - } - - /** - * The polygon offset. Setting this property to anything other than 0 will automatically enable poygon offset fill. - * - * @member {number} - * @default 0 - */ - get polygonOffset() - { - return this._polygonOffset; - } - - set polygonOffset(value) // eslint-disable-line require-jsdoc - { - this.offsets = !!value; - this._polygonOffset = value; - } -} - diff --git a/packages/core/src/renderers/filters/Filter.js b/packages/core/src/renderers/filters/Filter.js deleted file mode 100644 index 2073b8d..0000000 --- a/packages/core/src/renderers/filters/Filter.js +++ /dev/null @@ -1,140 +0,0 @@ -import Shader from '../../shader/Shader'; -import Program from '../../shader/Program'; -import State from '../State'; -import { settings } from '@pixi/settings'; -// import extractUniformsFromSrc from './extractUniformsFromSrc'; -import defaultVertex from './defaultFilter.vert'; -import defaultFragment from './defaultFilter.frag'; - -// let math = require('../../math'); -/** - * @class - * @memberof PIXI - * @extends PIXI.Shader - */ -export default class Filter extends Shader -{ - /** - * @param {string} [vertexSrc] - The source of the vertex shader. - * @param {string} [fragmentSrc] - The source of the fragment shader. - * @param {object} [uniforms] - Custom uniforms to use to augment the built-in ones. - */ - constructor(vertexSrc, fragmentSrc, uniforms) - { - const program = Program.from(vertexSrc, fragmentSrc); - - super(program, uniforms); - - /** - * The padding of the filter. Some filters require extra space to breath such as a blur. - * Increasing this will add extra width and height to the bounds of the object that the - * filter is applied to. - * - * @member {number} - */ - this.padding = 0; - - /** - * The resolution of the filter. Setting this to be lower will lower the quality but - * increase the performance of the filter. - * - * @member {number} - */ - this.resolution = settings.RESOLUTION; - - /** - * If enabled is true the filter is applied, if false it will not. - * - * @member {boolean} - */ - this.enabled = true; - - /** - * If enabled, PixiJS will fit the filter area into boundaries for better performance. - * Switch it off if it does not work for specific shader. - * - * @member {boolean} - */ - this.autoFit = true; - - /** - * Legacy filters use position and uvs from attributes - * @member {boolean} - * @readonly - */ - this.legacy = !!this.program.attributeData.aTextureCoord; - - /** - * the webGL state the filter requires to render - * @member {PIXI.State} - */ - this.state = new State(); - } - - /** - * Applies the filter - * - * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from - * @param {PIXI.RenderTarget} input - The input render target. - * @param {PIXI.RenderTarget} output - The target to output to. - * @param {boolean} clear - Should the output be cleared before rendering to it - * @param {object} [currentState] - It's current state of filter. - * There are some useful properties in the currentState : - * target, filters, sourceFrame, destinationFrame, renderTarget, resolution - */ - apply(filterManager, input, output, clear, currentState, derp) // eslint-disable-line no-unused-vars - { - // do as you please! - - filterManager.applyFilter(this, input, output, clear, currentState, derp); - - // or just do a regular render.. - } - - /** - * Sets the blendmode of the filter - * - * @member {number} - * @default PIXI.BLEND_MODES.NORMAL - */ - get blendMode() - { - return this.state.blendMode; - } - - set blendMode(value) // eslint-disable-line require-jsdoc - { - this.state.blendMode = value; - } - - /** - * The default vertex shader source - * - * @static - * @constant - */ - static get defaultVertexSrc() - { - return defaultVertex; - } - - /** - * The default fragment shader source - * - * @static - * @constant - */ - static get defaultFragmentSrc() - { - return defaultFragment; - } -} - -/** - * Used for caching shader IDs - * - * @static - * @private - */ -Filter.SOURCE_KEY_MAP = {}; - diff --git a/packages/core/src/renderers/filters/defaultFilter.frag b/packages/core/src/renderers/filters/defaultFilter.frag deleted file mode 100644 index a14c1be..0000000 --- a/packages/core/src/renderers/filters/defaultFilter.frag +++ /dev/null @@ -1,22 +0,0 @@ -varying vec2 vTextureCoord; -varying vec2 vFilterCoord; - -uniform sampler2D uSampler; -uniform sampler2D filterSampler; - -void main(void){ - vec4 masky = texture2D(filterSampler, vFilterCoord); - vec4 sample = texture2D(uSampler, vTextureCoord); - vec4 color; - if(mod(vFilterCoord.x, 1.0) > 0.5) - { - color = vec4(1.0, 0.0, 0.0, 1.0); - } - else - { - color = vec4(0.0, 1.0, 0.0, 1.0); - } - // gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0); - gl_FragColor = mix(sample, masky, 0.5); - gl_FragColor *= sample.a; -} diff --git a/packages/core/src/renderers/filters/defaultFilter.vert b/packages/core/src/renderers/filters/defaultFilter.vert deleted file mode 100644 index e1febca..0000000 --- a/packages/core/src/renderers/filters/defaultFilter.vert +++ /dev/null @@ -1,14 +0,0 @@ -attribute vec2 aVertexPosition; -attribute vec2 aTextureCoord; - -uniform mat3 projectionMatrix; -uniform mat3 filterMatrix; - -varying vec2 vTextureCoord; -varying vec2 vFilterCoord; - -void main(void){ - gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); - vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy; - vTextureCoord = aTextureCoord ; -} diff --git a/packages/core/src/renderers/filters/filterTransforms.js b/packages/core/src/renderers/filters/filterTransforms.js deleted file mode 100644 index 87a3cd6..0000000 --- a/packages/core/src/renderers/filters/filterTransforms.js +++ /dev/null @@ -1,50 +0,0 @@ -import { Matrix } from '@pixi/math'; - -/** - * Calculates the mapped matrix - * @param filterArea {Rectangle} The filter area - * @param sprite {Sprite} the target sprite - * @param outputMatrix {Matrix} @alvin - * @private - */ -// this returns a matrix that will normalise map filter cords in the filter to screen space -export function calculateScreenSpaceMatrix(outputMatrix, filterArea, textureSize) -{ - // TODO unwrap? - const mappedMatrix = outputMatrix.identity(); - - mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); - - mappedMatrix.scale(textureSize.width, textureSize.height); - - return mappedMatrix; -} - -export function calculateNormalizedScreenSpaceMatrix(outputMatrix, filterArea, textureSize) -{ - const mappedMatrix = outputMatrix.identity(); - - mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); - - const translateScaleX = (textureSize.width / filterArea.width); - const translateScaleY = (textureSize.height / filterArea.height); - - mappedMatrix.scale(translateScaleX, translateScaleY); - - return mappedMatrix; -} - -// this will map the filter coord so that a texture can be used based on the transform of a sprite -export function calculateSpriteMatrix(outputMatrix, filterArea, textureSize, sprite) -{ - const orig = sprite._texture.orig; - const mappedMatrix = outputMatrix.set(textureSize.width, 0, 0, textureSize.height, filterArea.x, filterArea.y); - const worldTransform = sprite.worldTransform.copyTo(Matrix.TEMP_MATRIX); - - worldTransform.invert(); - mappedMatrix.prepend(worldTransform); - mappedMatrix.scale(1.0 / orig.width, 1.0 / orig.height); - mappedMatrix.translate(sprite.anchor.x, sprite.anchor.y); - - return mappedMatrix; -} diff --git a/packages/core/src/renderers/filters/spriteMask/SpriteMaskFilter.js b/packages/core/src/renderers/filters/spriteMask/SpriteMaskFilter.js deleted file mode 100644 index d5a91a4..0000000 --- a/packages/core/src/renderers/filters/spriteMask/SpriteMaskFilter.js +++ /dev/null @@ -1,63 +0,0 @@ -import Filter from '../Filter'; -import { Matrix } from '@pixi/math'; -import vertex from './spriteMaskFilter.vert'; -import fragment from './spriteMaskFilter.frag'; -import { default as TextureMatrix } from '../../../textures/TextureMatrix'; - -/** - * The SpriteMaskFilter class - * - * @class - * @extends PIXI.Filter - * @memberof PIXI - */ -export default class SpriteMaskFilter extends Filter -{ - /** - * @param {PIXI.Sprite} sprite - the target sprite - */ - constructor(sprite) - { - const maskMatrix = new Matrix(); - - super(vertex, fragment); - - sprite.renderable = false; - - this.maskSprite = sprite; - this.maskMatrix = maskMatrix; - } - - /** - * Applies the filter - * - * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from - * @param {PIXI.RenderTarget} input - The input render target. - * @param {PIXI.RenderTarget} output - The target to output to. - */ - apply(filterManager, input, output) - { - const maskSprite = this.maskSprite; - const tex = this.maskSprite.texture; - - if (!tex.valid) - { - return; - } - if (!tex.transform) - { - // margin = 0.0, let it bleed a bit, shader code becomes easier - // assuming that atlas textures were made with 1-pixel padding - tex.transform = new TextureMatrix(tex, 0.0); - } - tex.transform.update(); - - this.uniforms.mask = tex; - this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite) - .prepend(tex.transform.mapCoord); - this.uniforms.alpha = maskSprite.worldAlpha; - this.uniforms.maskClamp = tex.transform.uClampFrame; - - filterManager.applyFilter(this, input, output); - } -} diff --git a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.frag b/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.frag deleted file mode 100644 index 0e4aef8..0000000 --- a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.frag +++ /dev/null @@ -1,23 +0,0 @@ -varying vec2 vMaskCoord; -varying vec2 vTextureCoord; - -uniform sampler2D uSampler; -uniform sampler2D mask; -uniform float alpha; -uniform vec4 maskClamp; - -void main(void) -{ - float clip = step(3.5, - step(maskClamp.x, vMaskCoord.x) + - step(maskClamp.y, vMaskCoord.y) + - step(vMaskCoord.x, maskClamp.z) + - step(vMaskCoord.y, maskClamp.w)); - - vec4 original = texture2D(uSampler, vTextureCoord); - vec4 masky = texture2D(mask, vMaskCoord); - - original *= (masky.r * masky.a * alpha * clip); - - gl_FragColor = original; -} diff --git a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.vert b/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.vert deleted file mode 100644 index d23a30c..0000000 --- a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.vert +++ /dev/null @@ -1,16 +0,0 @@ -attribute vec2 aVertexPosition; -attribute vec2 aTextureCoord; - -uniform mat3 projectionMatrix; -uniform mat3 otherMatrix; - -varying vec2 vMaskCoord; -varying vec2 vTextureCoord; - -void main(void) -{ - gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); - - vTextureCoord = aTextureCoord; - vMaskCoord = ( otherMatrix * vec3( aTextureCoord, 1.0) ).xy; -} diff --git a/packages/core/src/renderers/systems/BatchSystem.js b/packages/core/src/renderers/systems/BatchSystem.js deleted file mode 100644 index 70e9518..0000000 --- a/packages/core/src/renderers/systems/BatchSystem.js +++ /dev/null @@ -1,68 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import ObjectRenderer from '../utils/ObjectRenderer'; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ - -export default class BatchSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this manager works for. - */ - constructor(renderer) - { - super(renderer); - - /** - * An empty renderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.emptyRenderer = new ObjectRenderer(renderer); - - /** - * The currently active ObjectRenderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.currentRenderer = this.emptyRenderer; - } - - /** - * Changes the current renderer to the one given in parameter - * - * @param {PIXI.ObjectRenderer} objectRenderer - The object renderer to use. - */ - setObjectRenderer(objectRenderer) - { - if (this.currentRenderer === objectRenderer) - { - return; - } - - this.currentRenderer.stop(); - this.currentRenderer = objectRenderer; - - this.renderer.state.setState(objectRenderer.state); - - this.currentRenderer.start(); - } - - /** - * This should be called if you wish to do some custom rendering - * It will basically render anything that may be batched up such as sprites - * - */ - flush() - { - this.setObjectRenderer(this.emptyRenderer); - } - - reset() - { - this.setObjectRenderer(this.emptyRenderer); - } -} diff --git a/packages/core/src/renderers/systems/ContextSystem.js b/packages/core/src/renderers/systems/ContextSystem.js deleted file mode 100644 index 43bf827..0000000 --- a/packages/core/src/renderers/systems/ContextSystem.js +++ /dev/null @@ -1,186 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { settings } from '@pixi/settings'; - -let CONTEXT_UID = 0; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class ContextSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this.webGLVersion = 1; - - this.handleContextLost = this.handleContextLost.bind(this); - this.handleContextRestored = this.handleContextRestored.bind(this); - - this.extensions = {}; - - renderer.view.addEventListener('webglcontextlost', this.handleContextLost, false); - renderer.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); - } - - get isLost() - { - return (!this.gl || this.gl.isContextLost()); - } - - contextChange(gl) - { - this.gl = gl; - - // restore a context if it was previously lost - if (gl.isContextLost() && gl.getExtension('WEBGL_lose_context')) - { - gl.getExtension('WEBGL_lose_context').restoreContext(); - } - } - - initFromContext(gl) - { - this.gl = gl; - this.validateContext(gl); - this.renderer.gl = gl; - this.renderer.CONTEXT_UID = CONTEXT_UID++; - this.renderer.runners.contextChange.run(gl); - } - - initFromOptions(options) - { - const gl = this.createContext(this.renderer.view, options); - - this.initFromContext(gl); - } - - /** - * Helper class to create a webGL Context - * - * @class - * @memberof PIXI.glCore - * @param canvas {HTMLCanvasElement} the canvas element that we will get the context from - * @param options {Object} An options object that gets passed in to the canvas element containing the context attributes, - * see https://developer.mozilla.org/en/docs/Web/API/HTMLCanvasElement/getContext for the options available - * @return {WebGLRenderingContext} the WebGL context - */ - createContext(canvas, options) - { - let gl; - - if (settings.PREFER_WEBGL_2) - { - gl = canvas.getContext('webgl2', options); - } - - if (gl) - { - this.webGLVersion = 2; - } - else - { - this.webGLVersion = 1; - - gl = canvas.getContext('webgl', options) - || canvas.getContext('experimental-webgl', options); - - if (!gl) - { - // fail, not able to get a context - throw new Error('This browser does not support webGL. Try using the canvas renderer'); - } - } - - this.gl = gl; - - this.getExtensions(); - - return gl; - } - - getExtensions() - { - // time to set up default etensions that pixi uses.. - const gl = this.gl; - const extensions = this.extensions; - - if (this.webGLVersion === 1) - { - extensions.drawBuffers = gl.getExtension('WEBGL_draw_buffers'); - extensions.depthTexture = gl.getExtension('WEBKIT_WEBGL_depth_texture'); - extensions.floatTexture = gl.getExtension('OES_texture_float'); - extensions.loseContext = gl.getExtension('WEBGL_lose_context'); - - extensions.vertexArrayObject = gl.getExtension('OES_vertex_array_object') - || gl.getExtension('MOZ_OES_vertex_array_object') - || gl.getExtension('WEBKIT_OES_vertex_array_object'); - } - - // we don't use any specific WebGL 2 ones yet! - } - - /** - * Handles a lost webgl context - * - * @private - * @param {WebGLContextEvent} event - The context lost event. - */ - handleContextLost(event) - { - event.preventDefault(); - } - - /** - * Handles a restored webgl context - * - * @private - */ - handleContextRestored() - { - this.renderer.runners.contextChange.run(this.gl); - } - - destroy() - { - const view = this.renderer.view; - - // remove listeners - view.removeEventListener('webglcontextlost', this.handleContextLost); - view.removeEventListener('webglcontextrestored', this.handleContextRestored); - - this.gl.useProgram(null); - - if (this.extensions.loseContext) - { - this.extensions.loseContext.loseContext(); - } - } - - postrender() - { - this.gl.flush(); - } - - validateContext(gl) - { - const attributes = gl.getContextAttributes(); - - // this is going to be fairly simple for now.. but at least we have room to grow! - if (!attributes.stencil) - { - /* eslint-disable max-len */ - - /* eslint-disable no-console */ - console.warn('Provided WebGL context does not have a stencil buffer, masks may not render correctly'); - /* eslint-enable no-console */ - - /* eslint-enable max-len */ - } - } -} diff --git a/packages/core/src/renderers/systems/FramebufferSystem.js b/packages/core/src/renderers/systems/FramebufferSystem.js deleted file mode 100644 index df7902b..0000000 --- a/packages/core/src/renderers/systems/FramebufferSystem.js +++ /dev/null @@ -1,246 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { Rectangle } from '@pixi/math'; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class FramebufferSystem extends WebGLSystem -{ - /** - * Sets up the renderer context and necessary buffers. - * - * @private - */ - contextChange() - { - this.gl = this.renderer.gl; - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - this.current = null; - this.viewport = new Rectangle(); - - this.drawBufferExtension = this.renderer.context.extensions.drawBuffers; - } - - bind(framebuffer, frame) - { - const gl = this.gl; - - this.current = framebuffer; - - if (framebuffer) - { - // TODO cacheing layer! - - const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID] || this.initFramebuffer(framebuffer); - - gl.bindFramebuffer(gl.FRAMEBUFFER, fbo.framebuffer); - // makesure all textures are unbound.. - - // now check for updates... - if (fbo.dirtyId !== framebuffer.dirtyId) - { - fbo.dirtyId = framebuffer.dirtyId; - - if (fbo.dirtyFormat !== framebuffer.dirtyFormat) - { - fbo.dirtyFormat = framebuffer.dirtyFormat; - this.updateFramebuffer(framebuffer); - } - else if (fbo.dirtySize !== framebuffer.dirtySize) - { - fbo.dirtySize = framebuffer.dirtySize; - this.resizeFramebuffer(framebuffer); - } - } - - for (let i = 0; i < framebuffer.colorTextures.length; i++) - { - if (framebuffer.colorTextures[i].texturePart) - { - this.renderer.texture.unbind(framebuffer.colorTextures[i].texture); - } - else - { - this.renderer.texture.unbind(framebuffer.colorTextures[i]); - } - } - - if (framebuffer.depthTexture) - { - this.renderer.texture.unbind(framebuffer.depthTexture); - } - - if (frame) - { - this.setViewport(frame.x, frame.y, frame.width, frame.height); - } - else - { - this.setViewport(0, 0, framebuffer.width, framebuffer.height); - } - } - else - { - gl.bindFramebuffer(gl.FRAMEBUFFER, null); - - if (frame) - { - this.setViewport(frame.x, frame.y, frame.width, frame.height); - } - else - { - this.setViewport(0, 0, this.renderer.width, this.renderer.height); - } - } - } - - setViewport(x, y, width, height) - { - const v = this.viewport; - - if (v.width !== width || v.height !== height || v.x !== x || v.y !== y) - { - v.x = x; - v.y = y; - v.width = width; - v.height = height; - - this.gl.viewport(x, y, width, height); - } - } - - get size() - { - if (this.current) - { - // TODO store temp - return { x: 0, y: 0, width: this.current.width, height: this.current.height }; - } - - return { x: 0, y: 0, width: this.renderer.width, height: this.renderer.height }; - } - - clear(r, g, b, a) - { - const gl = this.gl; - - // TODO clear color can be set only one right? - gl.clearColor(r, g, b, a); - gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); - } - - // private functions... - - initFramebuffer(framebuffer) - { - const gl = this.gl; - - // TODO - make this a class? - const fbo = { - framebuffer: gl.createFramebuffer(), - stencil: null, - dirtyId: 0, - dirtyFormat: 0, - dirtySize: 0, - }; - - framebuffer.glFrameBuffers[this.CONTEXT_UID] = fbo; - - return fbo; - } - - resizeFramebuffer(framebuffer) - { - const gl = this.gl; - - if (framebuffer.stencil || framebuffer.depth) - { - gl.bindRenderbuffer(gl.RENDERBUFFER, this.stencil); - gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); - } - } - - updateFramebuffer(framebuffer) - { - const gl = this.gl; - - const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID]; - - // bind the color texture - const colorTextures = framebuffer.colorTextures; - - let count = colorTextures.length; - - if (!this.drawBufferExtension) - { - count = Math.min(count, 1); - } - - const activeTextures = []; - - for (let i = 0; i < count; i++) - { - const texture = framebuffer.colorTextures[i]; - - if (texture.texturePart) - { - this.renderer.texture.bind(texture.texture, 0); - - gl.framebufferTexture2D(gl.FRAMEBUFFER, - gl.COLOR_ATTACHMENT0 + i, - gl.TEXTURE_CUBE_MAP_NEGATIVE_X + texture.side, - texture.texture._glTextures[this.CONTEXT_UID].texture, - 0); - } - else - { - this.renderer.texture.bind(texture, 0); - - gl.framebufferTexture2D(gl.FRAMEBUFFER, - gl.COLOR_ATTACHMENT0 + i, - gl.TEXTURE_2D, - texture._glTextures[this.CONTEXT_UID].texture, - 0); - } - - activeTextures.push(gl.COLOR_ATTACHMENT0 + i); - } - - if (this.drawBufferExtension && activeTextures.length > 1) - { - this.drawBufferExtension.drawBuffersWEBGL(activeTextures); - } - - if (framebuffer.depthTexture) - { - const depthTextureExt = this.renderer.context.extensions.depthTexture; - - if (depthTextureExt) - { - const depthTexture = framebuffer.depthTexture; - - this.renderer.texture.bind(depthTexture, 0); - - gl.framebufferTexture2D(gl.FRAMEBUFFER, - gl.DEPTH_ATTACHMENT, - gl.TEXTURE_2D, - depthTexture._glTextures[this.CONTEXT_UID].texture, - 0); - } - } - - if (framebuffer.stencil || framebuffer.depth) - { - fbo.stencil = gl.createRenderbuffer(); - - gl.bindRenderbuffer(gl.RENDERBUFFER, fbo.stencil); - - // TODO.. this is depth AND stencil? - gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, fbo.stencil); - gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); - // fbo.enableStencil(); - } - } -} diff --git a/packages/core/src/renderers/systems/MaskSystem.js b/packages/core/src/renderers/systems/MaskSystem.js deleted file mode 100644 index d9c4b1a..0000000 --- a/packages/core/src/renderers/systems/MaskSystem.js +++ /dev/null @@ -1,199 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import AlphaMaskFilter from '../filters/spriteMask/SpriteMaskFilter'; - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class MaskSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - // TODO - we don't need both! - this.scissor = false; - this.scissorData = null; - this.scissorRenderTarget = null; - - this.enableScissor = false; - - this.alphaMaskPool = []; - this.alphaMaskIndex = 0; - } - - /** - * Applies the Mask and adds it to the current filter stack. - * - * @param {PIXI.DisplayObject} target - Display Object to push the mask to - * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. - */ - push(target, maskData) - { - // TODO the root check means scissor rect will not - // be used on render textures more info here: - // https://github.com/pixijs/pixi.js/pull/3545 - - if (maskData.texture) - { - this.pushSpriteMask(target, maskData); - } - else if (this.enableScissor - && !this.scissor - && this.renderer._activeRenderTarget.root - && !this.renderer.stencil.stencilMaskStack.length - && maskData.isFastRect()) - { - const matrix = maskData.worldTransform; - - let rot = Math.atan2(matrix.b, matrix.a); - - // use the nearest degree! - rot = Math.round(rot * (180 / Math.PI)); - - if (rot % 90) - { - this.pushStencilMask(maskData); - } - else - { - this.pushScissorMask(target, maskData); - } - } - else - { - this.pushStencilMask(maskData); - } - } - - /** - * Removes the last mask from the mask stack and doesn't return it. - * - * @param {PIXI.DisplayObject} target - Display Object to pop the mask from - * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. - */ - pop(target, maskData) - { - if (maskData.texture) - { - this.popSpriteMask(target, maskData); - } - else if (this.enableScissor && !this.renderer.stencil.stencilMaskStack.length) - { - this.popScissorMask(target, maskData); - } - else - { - this.popStencilMask(target, maskData); - } - } - - /** - * Applies the Mask and adds it to the current filter stack. - * - * @param {PIXI.RenderTarget} target - Display Object to push the sprite mask to - * @param {PIXI.Sprite} maskData - Sprite to be used as the mask - */ - pushSpriteMask(target, maskData) - { - let alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; - - if (!alphaMaskFilter) - { - alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; - } - - alphaMaskFilter[0].resolution = this.renderer.resolution; - alphaMaskFilter[0].maskSprite = maskData; - - // TODO - may cause issues! - target.filterArea = maskData.getBounds(true); - - this.renderer.filterSystem.pushFilter(target, alphaMaskFilter); - - this.alphaMaskIndex++; - } - - /** - * Removes the last filter from the filter stack and doesn't return it. - * - */ - popSpriteMask() - { - this.renderer.filterSystem.popFilter(); - this.alphaMaskIndex--; - } - - /** - * Applies the Mask and adds it to the current filter stack. - * - * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. - */ - pushStencilMask(maskData) - { - this.renderer.batch.flush(); - this.renderer.stencil.pushStencil(maskData); - } - - /** - * Removes the last filter from the filter stack and doesn't return it. - * - */ - popStencilMask() - { - // this.renderer.currentRenderer.stop(); - this.renderer.stencil.popStencil(); - } - - /** - * - * @param {PIXI.DisplayObject} target - Display Object to push the mask to - * @param {PIXI.Graphics} maskData - The masking data. - */ - pushScissorMask(target, maskData) - { - maskData.renderable = true; - - const renderTarget = this.renderer._activeRenderTarget; - - const bounds = maskData.getBounds(); - - bounds.fit(renderTarget.size); - maskData.renderable = false; - - this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); - - const resolution = this.renderer.resolution; - - this.renderer.gl.scissor( - bounds.x * resolution, - (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, - bounds.width * resolution, - bounds.height * resolution - ); - - this.scissorRenderTarget = renderTarget; - this.scissorData = maskData; - this.scissor = true; - } - - /** - * - * - */ - popScissorMask() - { - this.scissorRenderTarget = null; - this.scissorData = null; - this.scissor = false; - - // must be scissor! - const gl = this.renderer.gl; - - gl.disable(gl.SCISSOR_TEST); - } -} diff --git a/packages/core/src/renderers/systems/ProjectionSystem.js b/packages/core/src/renderers/systems/ProjectionSystem.js deleted file mode 100644 index ec5efdb..0000000 --- a/packages/core/src/renderers/systems/ProjectionSystem.js +++ /dev/null @@ -1,73 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { Matrix } from '@pixi/math'; - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ - -export default class ProjectionSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this.projectionMatrix = new Matrix(); - } - - update(destinationFrame, sourceFrame, resolution, root) - { - this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; - this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; - - this.calculateProjection(this.destinationFrame, this.sourceFrame, resolution, root); - - this.renderer.globalUniforms.uniforms.projectionMatrix = this.projectionMatrix; - this.renderer.globalUniforms.update(); - } - - /** - * Updates the projection matrix based on a projection frame (which is a rectangle) - * - * @param {Rectangle} destinationFrame - The destination frame. - * @param {Rectangle} sourceFrame - The source frame. - */ - calculateProjection(destinationFrame, sourceFrame, resolution, root) - { - const pm = this.projectionMatrix; - - // I don't think we will need this line.. - // pm.identity(); - - if (!root) - { - pm.a = (1 / destinationFrame.width * 2) * resolution; - pm.d = (1 / destinationFrame.height * 2) * resolution; - - pm.tx = -1 - (sourceFrame.x * pm.a); - pm.ty = -1 - (sourceFrame.y * pm.d); - } - else - { - pm.a = (1 / destinationFrame.width * 2) * resolution; - pm.d = (-1 / destinationFrame.height * 2) * resolution; - - pm.tx = -1 - (sourceFrame.x * pm.a); - pm.ty = 1 - (sourceFrame.y * pm.d); - } - } - - /** - * Sets the transform of the active render target to the given matrix - * - * @param {PIXI.Matrix} matrix - The transformation matrix - */ - setTransform()// matrix) - { - // this._activeRenderTarget.transform = matrix; - } -} diff --git a/packages/core/src/renderers/systems/RenderTextureSystem.js b/packages/core/src/renderers/systems/RenderTextureSystem.js deleted file mode 100644 index a133ef1..0000000 --- a/packages/core/src/renderers/systems/RenderTextureSystem.js +++ /dev/null @@ -1,115 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { Rectangle } from '@pixi/math'; - -const tempRect = new Rectangle(); - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ - -export default class RenderTextureSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this.clearColor = renderer._backgroundColorRgba; - - // TODO moe this property somewhere else! - this.defaultMaskStack = []; - this.defaultFilterStack = [{}]; - - // empty render texture? - this.renderTexture = null; - - this.destinationFrame = new Rectangle(); - } - - bind(renderTexture, sourceFrame, destinationFrame) - { - // TODO - do we want this?? - if (this.renderTexture === renderTexture) return; - this.renderTexture = renderTexture; - - const renderer = this.renderer; - - if (renderTexture) - { - const baseTexture = renderTexture.baseTexture; - - if (!destinationFrame) - { - tempRect.width = baseTexture.realWidth; - tempRect.height = baseTexture.realHeight; - - destinationFrame = tempRect; - } - - if (!sourceFrame) - { - sourceFrame = destinationFrame; - } - - this.renderer.framebuffer.bind(baseTexture.frameBuffer, destinationFrame); - - this.renderer.projection.update(destinationFrame, sourceFrame, baseTexture.resolution, false); - this.renderer.stencil.setMaskStack(baseTexture.stencilMaskStack); - } - else - { - // TODO these validation checks happen deeper down.. - // thing they can be avoided.. - if (!destinationFrame) - { - tempRect.width = renderer.width; - tempRect.height = renderer.height; - - destinationFrame = tempRect; - } - - if (!sourceFrame) - { - sourceFrame = destinationFrame; - } - - renderer.framebuffer.bind(null, destinationFrame); - - // TODO store this.. - this.renderer.projection.update(destinationFrame, sourceFrame, this.renderer.resolution, true); - this.renderer.stencil.setMaskStack(this.defaultMaskStack); - } - - this.destinationFrame.copyFrom(destinationFrame); - } - - /** - * Erases the render texture and fills the drawing area with a colour - * - * @param {number} [clearColor] - The colour - * @return {PIXI.Renderer} Returns itself. - */ - clear(clearColor) - { - if (this.renderTexture) - { - clearColor = clearColor || this.renderTexture.baseTexture.clearColor; - } - else - { - clearColor = clearColor || this.clearColor; - } - - this.renderer.framebuffer.clear(clearColor[0], clearColor[1], clearColor[2], clearColor[3]); - } - - resize()// screenWidth, screenHeight) - { - // resize the root only! - this.bind(null); - } -} diff --git a/packages/core/src/renderers/systems/StateSystem.js b/packages/core/src/renderers/systems/StateSystem.js deleted file mode 100755 index d7f4caf..0000000 --- a/packages/core/src/renderers/systems/StateSystem.js +++ /dev/null @@ -1,288 +0,0 @@ -import mapWebGLBlendModesToPixi from '../utils/mapWebGLBlendModesToPixi'; -import WebGLSystem from './WebGLSystem'; -import WebGLState from '../State'; - -const BLEND = 0; -const OFFSET = 1; -const CULLING = 2; -const DEPTH_TEST = 3; -const WINDING = 4; - -/** - * A WebGL state machines - * - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class StateSystem extends WebGLSystem -{ - /** - * @param {WebGLRenderingContext} gl - The current WebGL rendering context - */ - constructor(renderer) - { - super(renderer); - - this.gl = null; - - this.maxAttribs = null; - - // check we have vao.. - this.nativeVaoExtension = null; - - this.attribState = null; - - this.stateId = 0; - this.polygonOffset = 0; - this.blendMode = 17; - - this.map = []; - - // map functions for when we set state.. - this.map[BLEND] = this.setBlend; - this.map[OFFSET] = this.setOffset; - this.map[CULLING] = this.setCullFace; - this.map[DEPTH_TEST] = this.setDepthTest; - this.map[WINDING] = this.setFrontFace; - - this.checks = []; - - this.defaultState = new WebGLState(); - this.defaultState.blend = true; - this.defaultState.depth = true; - } - - contextChange(gl) - { - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; - - this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); - - // check we have vao.. - this.nativeVaoExtension = ( - gl.getExtension('OES_vertex_array_object') - || gl.getExtension('MOZ_OES_vertex_array_object') - || gl.getExtension('WEBKIT_OES_vertex_array_object') - ); - - this.attribState = { - tempAttribState: new Array(this.maxAttribs), - attribState: new Array(this.maxAttribs), - }; - - this.blendModes = mapWebGLBlendModesToPixi(gl); - - this.setState(this.defaultState); - - this.reset(); - } - - /** - * Sets the current state - * - * @param {*} state - The state to set. - */ - setState(state) - { - state = state || this.defaultState; - - // TODO maybe to an object check? ( this.state === state )? - if (this.stateId !== state.data) - { - let diff = this.stateId ^ state.data; - let i = 0; - - // order from least to most common - while (diff) - { - if (diff & 1) - { - // state change! - this.map[i].call(this, !!(state.data & (1 << i))); - } - - diff = diff >> 1; - i++; - } - - this.stateId = state.data; - } - - // based on the above settings we check for specific modes.. - // for example if blend is active we check and set the blend modes - // or of polygon offset is active we check the poly depth. - for (let i = 0; i < this.checks.length; i++) - { - this.checks[i](this, state); - } - } - - /** - * Enables or disabled blending. - * - * @param {boolean} value - Turn on or off webgl blending. - */ - setBlend(value) - { - this.updateCheck(StateSystem.checkBlendMode, value); - - this.gl[value ? 'enable' : 'disable'](this.gl.BLEND); - } - - /** - * Enables or disable polygon offset fill - * - * @param {boolean} value - Turn on or off webgl polygon offset testing. - */ - setOffset(value) - { - this.gl[value ? 'enable' : 'disable'](this.gl.POLYGON_OFFSET_FILL); - } - - /** - * Sets whether to enable or disable depth test. - * - * @param {boolean} value - Turn on or off webgl depth testing. - */ - setDepthTest(value) - { - this.gl[value ? 'enable' : 'disable'](this.gl.DEPTH_TEST); - } - - /** - * Sets whether to enable or disable cull face. - * - * @param {boolean} value - Turn on or off webgl cull face. - */ - setCullFace(value) - { - this.gl[value ? 'enable' : 'disable'](this.gl.CULL_FACE); - } - - /** - * Sets the gl front face. - * - * @param {boolean} value - true is clockwise and false is counter-clockwise - */ - setFrontFace(value) - { - this.gl.frontFace(this.gl[value ? 'CW' : 'CCW']); - } - - /** - * Sets the blend mode. - * - * @param {number} value - The blend mode to set to. - */ - setBlendMode(value) - { - if (value === this.blendMode) - { - return; - } - - this.blendMode = value; - this.gl.blendFunc(this.blendModes[value][0], this.blendModes[value][1]); - } - - /** - * Sets the polygon offset. - * - * @param {number} value - the polygon offset - * @param {number} scale - the polygon offset scale - */ - setPolygonOffset(value, scale) - { - this.gl.polygonOffset(value, scale); - } - - /** - * Disables all the vaos in use - * - */ - resetAttributes() - { - for (let i = 0; i < this.attribState.tempAttribState.length; i++) - { - this.attribState.tempAttribState[i] = 0; - } - - for (let i = 0; i < this.attribState.attribState.length; i++) - { - this.attribState.attribState[i] = 0; - } - - // im going to assume one is always active for performance reasons. - for (let i = 1; i < this.maxAttribs; i++) - { - this.gl.disableVertexAttribArray(i); - } - } - - // used - /** - * Resets all the logic and disables the vaos - */ - reset() - { - // unbind any VAO if they exist.. - if (this.nativeVaoExtension) - { - this.nativeVaoExtension.bindVertexArrayOES(null); - } - - // reset all attributes.. - this.resetAttributes(); - - this.gl.pixelStorei(this.gl.UNPACK_FLIP_Y_WEBGL, false); - - this.setBlendMode(0); - - // TODO? - // this.setState(this.defaultState); - } - - /** - * checks to see which updates should be checked based on which settings have been activated - * for example if blend is enabled then we shold check the blend modes each time the state is changed - * or if poygon fill is activated then we need to check if the polygone offset changes. - * The idea is that we only check what we have too. - * - * @param {Function} func the checking function to add or remove - * @param {boolean} value should the check function be added or removed. - */ - updateCheck(func, value) - { - const index = this.checks.indexOf(func); - - if (value && index === -1) - { - this.checks.push(func); - } - else if (!value && index !== -1) - { - this.checks.splice(index, 1); - } - } - - /** - * A private little wrapper function that we call to check the blend mode. - * - * @static - * @private - * @param {PIXI.StateSystem} System the System to perform the state check on - * @param {PIXI.State} state the state that the blendMode will pulled from - */ - static checkBlendMode(system, state) - { - system.setBlendMode(state.blendMode); - } - - // TODO - add polygon offset? -} diff --git a/packages/core/src/renderers/systems/StencilSystem.js b/packages/core/src/renderers/systems/StencilSystem.js deleted file mode 100644 index 5d538cb..0000000 --- a/packages/core/src/renderers/systems/StencilSystem.js +++ /dev/null @@ -1,133 +0,0 @@ -import WebGLSystem from './WebGLSystem'; - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class StencilSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - this.stencilMaskStack = []; - } - - /** - * Changes the mask stack that is used by this System. - * - * @param {PIXI.Graphics[]} stencilMaskStack - The mask stack - */ - setMaskStack(stencilMaskStack) - { - const gl = this.renderer.gl; - - if (stencilMaskStack.length !== this.stencilMaskStack.length) - { - if (stencilMaskStack.length === 0) - { - gl.disable(gl.STENCIL_TEST); - } - else - { - gl.enable(gl.STENCIL_TEST); - } - } - - this.stencilMaskStack = stencilMaskStack; - } - - /** - * Applies the Mask and adds it to the current stencil stack. @alvin - * - * @param {PIXI.Graphics} graphics - The mask - */ - pushStencil(graphics) - { - this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); - - // this.renderer._activeRenderTarget.attachStencilBuffer(); - - const gl = this.renderer.gl; - const prevMaskCount = this.stencilMaskStack.length; - - if (prevMaskCount === 0) - { - gl.enable(gl.STENCIL_TEST); - } - - this.stencilMaskStack.push(graphics); - - // Increment the refference stencil value where the new mask overlaps with the old ones. - gl.colorMask(false, false, false, false); - gl.stencilFunc(gl.EQUAL, prevMaskCount, this._getBitwiseMask()); - gl.stencilOp(gl.KEEP, gl.KEEP, gl.INCR); - this.renderer.plugins.graphics.render(graphics); - - this._useCurrent(); - } - - /** - * Removes the last mask from the stencil stack. @alvin - */ - popStencil() - { - this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); - - const gl = this.renderer.gl; - const graphics = this.stencilMaskStack.pop(); - - if (this.stencilMaskStack.length === 0) - { - // the stack is empty! - gl.disable(gl.STENCIL_TEST); - gl.clear(gl.STENCIL_BUFFER_BIT); - gl.clearStencil(0); - } - else - { - // Decrement the refference stencil value where the popped mask overlaps with the other ones - gl.colorMask(false, false, false, false); - gl.stencilOp(gl.KEEP, gl.KEEP, gl.DECR); - this.renderer.plugins.graphics.render(graphics); - - this._useCurrent(); - } - } - - /** - * Setup renderer to use the current stencil data. - */ - _useCurrent() - { - const gl = this.renderer.gl; - - gl.colorMask(true, true, true, true); - gl.stencilFunc(gl.EQUAL, this.stencilMaskStack.length, this._getBitwiseMask()); - gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP); - } - - /** - * Fill 1s equal to the number of acitve stencil masks. - * - * @return {number} The bitwise mask. - */ - _getBitwiseMask() - { - return (1 << this.stencilMaskStack.length) - 1; - } - - /** - * Destroys the mask stack. - * - */ - destroy() - { - super.destroy(this); - - this.stencilMaskStack = null; - } -} diff --git a/packages/core/src/renderers/systems/WebGLSystem.js b/packages/core/src/renderers/systems/WebGLSystem.js deleted file mode 100644 index d3d5b31..0000000 --- a/packages/core/src/renderers/systems/WebGLSystem.js +++ /dev/null @@ -1,41 +0,0 @@ -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this manager works for. - */ - constructor(renderer) - { - /** - * The renderer this manager works for. - * - * @member {PIXI.Renderer} - */ - this.renderer = renderer; - - this.renderer.runners.contextChange.add(this); - } - - /** - * Generic method called when there is a WebGL context change. - * - */ - contextChange() - { - // do some codes init! - } - - /** - * Generic destroy methods to be overridden by the subclass - * - */ - destroy() - { - this.renderer.runners.contextChange.remove(this); - this.renderer = null; - } -} diff --git a/packages/core/src/renderers/systems/filter/FilterSystem.js b/packages/core/src/renderers/systems/filter/FilterSystem.js deleted file mode 100644 index e615037..0000000 --- a/packages/core/src/renderers/systems/filter/FilterSystem.js +++ /dev/null @@ -1,431 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; - -import RenderTexture from '../../../textures/RenderTexture'; -import Quad from '../../utils/Quad'; -import QuadUv from '../../utils/QuadUv'; -import { Rectangle } from '@pixi/math'; -import * as filterTransforms from '../../filters/filterTransforms'; -import bitTwiddle from 'bit-twiddle'; -import UniformGroup from '../../../shader/UniformGroup'; -import { DRAW_MODES } from '../../../../../constants'; - -// -/** - * @ignore - * @class - */ -class FilterState -{ - /** - * - */ - constructor() - { - this.renderTexture = null; - this.sourceFrame = new Rectangle(); - this.destinationFrame = new Rectangle(); - this.filters = []; - this.target = null; - this.legacy = false; - this.resolution = 1; - } -} - -/** - * @class - * @memberof PIXI.systems - * @extends PIXI.systems.WebGLSystem - */ -export default class FilterSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - /** - * stores a bunch of PO2 textures used for filtering - * @type {Object} - */ - this.texturePool = {}; - - /** - * a pool for storing filter states, save us creating new ones each tick - * @type {Array} - */ - this.statePool = []; - - /** - * A very simple geometry used when drawing a filter effect to the screen - * @type {Quad} - */ - this.quad = new Quad(); - - this.quadUv = new QuadUv(); - - /** - * Temporary rect for maths - * @type {PIXI.Rectangle} - */ - this.tempRect = new Rectangle(); - - this.activeState = {}; - - /** - * this uniform group is attached to filter uniforms when used - * @type {UniformGroup} - */ - this.globalUniforms = new UniformGroup({ - sourceFrame: this.tempRect, - destinationFrame: this.tempRect, - - // legacy variables - filterArea: new Float32Array(4), - filterClamp: new Float32Array(4), - }, true); - } - - /** - * Adds a new filter to the System. - * - * @param {PIXI.DisplayObject} target - The target of the filter to render. - * @param {PIXI.Filter[]} filters - The filters to apply. - */ - push(target, filters) - { - const renderer = this.renderer; - const filterStack = this.renderer.renderTexture.defaultFilterStack; - const state = this.statePool.pop() || new FilterState(); - - let resolution = filters[0].resolution; - let padding = filters[0].padding; - let autoFit = filters[0].autoFit; - let legacy = filters[0].legacy; - - for (let i = 1; i < filters.length; i++) - { - const filter = filters[i]; - - // lets use the lowest resolution.. - resolution = Math.min(resolution, filter.resolution); - // and the largest amount of padding! - padding = Math.max(padding, filter.padding); - // only auto fit if all filters are autofit - autoFit = autoFit || filter.autoFit; - - legacy = legacy || filter.legacy; - } - - filterStack.push(state); - - state.resolution = resolution; - - state.legacy = legacy; - - // round to whole number based on resolution - // TODO move that to the shader too? - state.sourceFrame = target.filterArea || target.getBounds(true); - - state.sourceFrame.pad(padding); - - if (autoFit) - { - state.sourceFrame.fit(this.renderer.renderTexture.destinationFrame); - } - - state.sourceFrame.round(resolution); - - state.renderTexture = this.getPotFilterTexture(state.sourceFrame.width, state.sourceFrame.height, resolution); - state.filters = filters; - - state.destinationFrame.width = state.renderTexture.width; - state.destinationFrame.height = state.renderTexture.height; - - state.renderTexture.filterFrame = state.sourceFrame; - renderer.renderTexture.bind(state.renderTexture, state.sourceFrame);// /, state.destinationFrame); - renderer.renderTexture.clear(); - } - - /** - * Pops off the filter and applies it. - * - */ - pop() - { - const renderer = this.renderer; - const filterStack = renderer.renderTexture.defaultFilterStack; - const state = filterStack.pop(); - const filters = state.filters; - - this.activeState = state; - - const globalUniforms = this.globalUniforms.uniforms; - - globalUniforms.sourceFrame = state.sourceFrame; - globalUniforms.destinationFrame = state.destinationFrame; - globalUniforms.resolution = state.resolution; - - // only update the rect if its legacy.. - if (state.legacy) - { - const filterArea = globalUniforms.filterArea; - const filterClamp = globalUniforms.filterClamp; - - filterArea[0] = state.destinationFrame.width; - filterArea[1] = state.destinationFrame.height; - filterArea[2] = state.sourceFrame.x; - filterArea[3] = state.sourceFrame.y; - - filterClamp[0] = 0.5 / state.resolution / state.destinationFrame.width; - filterClamp[1] = 0.5 / state.resolution / state.destinationFrame.height; - filterClamp[2] = (state.sourceFrame.width - 0.5) / state.resolution / state.destinationFrame.width; - filterClamp[3] = (state.sourceFrame.height - 0.5) / state.resolution / state.destinationFrame.height; - } - - this.globalUniforms.update(); - - const lastState = filterStack[filterStack.length - 1]; - - if (filters.length === 1) - { - filters[0].apply(this, state.renderTexture, lastState.renderTexture, false, state); - - this.returnFilterTexture(state.renderTexture); - } - else - { - let flip = state.renderTexture; - let flop = this.getPotFilterTexture( - flip.width, - flip.height, - state.resolution - ); - - flop.filterFrame = flip.filterFrame; - - let i = 0; - - for (i = 0; i < filters.length - 1; ++i) - { - filters[i].apply(this, flip, flop, true, state); - - const t = flip; - - flip = flop; - flop = t; - } - - filters[i].apply(this, flip, lastState.renderTexture, false, state); - - this.returnFilterTexture(flip); - this.returnFilterTexture(flop); - } - - this.statePool.push(state); - } - - /** - * Draws a filter. - * - * @param {PIXI.Filter} filter - The filter to draw. - * @param {PIXI.RenderTarget} input - The input render target. - * @param {PIXI.RenderTarget} output - The target to output to. - * @param {boolean} clear - Should the output be cleared before rendering to it - */ - applyFilter(filter, input, output, clear) - { - const renderer = this.renderer; - - renderer.renderTexture.bind(output, output ? output.filterFrame : null); - - if (clear) - { - // gl.disable(gl.SCISSOR_TEST); - renderer.renderTexture.clear(); - // gl.enable(gl.SCISSOR_TEST); - } - - // set the uniforms.. - filter.uniforms.uSampler = input; - filter.uniforms.filterGlobals = this.globalUniforms; - - // TODO make it so that the order of this does not matter.. - // because it does at the moment cos of global uniforms. - // they need to get resynced - - renderer.state.setState(filter.state); - renderer.shader.bind(filter); - - if (filter.legacy) - { - this.quadUv.map(input._frame, input.filterFrame); - - renderer.geometry.bind(this.quadUv); - renderer.geometry.draw(DRAW_MODES.TRIANGLES); - } - else - { - renderer.geometry.bind(this.quad); - renderer.geometry.draw(DRAW_MODES.TRIANGLE_STRIP); - } - } - - /** - * Calculates the mapped matrix. - * - * TODO playing around here.. this is temporary - (will end up in the shader) - * this returns a matrix that will normalise map filter cords in the filter to screen space - * - * @param {PIXI.Matrix} outputMatrix - the matrix to output to. - * @return {PIXI.Matrix} The mapped matrix. - */ - calculateScreenSpaceMatrix(outputMatrix) - { - const currentState = this.activeState; - - return filterTransforms.calculateScreenSpaceMatrix( - outputMatrix, - currentState.sourceFrame, - currentState.destinationFrame - ); - } - - /** - * This will map the filter coord so that a texture can be used based on the transform of a sprite - * - * @param {PIXI.Matrix} outputMatrix - The matrix to output to. - * @param {PIXI.Sprite} sprite - The sprite to map to. - * @return {PIXI.Matrix} The mapped matrix. - */ - calculateSpriteMatrix(outputMatrix, sprite) - { - const currentState = this.activeState; - - return filterTransforms.calculateSpriteMatrix( - outputMatrix, - currentState.sourceFrame, - currentState.destinationFrame, - sprite - ); - } - - /** - * Destroys this Filter System. - * - * @param {boolean} [contextLost=false] context was lost, do not free shaders - * - */ - destroy(contextLost = false) - { - if (!contextLost) - { - this.emptyPool(); - } - else - { - this.texturePool = {}; - } - } - - /** - * Gets a Power-of-Two render texture. - * - * TODO move to a seperate class could be on renderer? - * also - could cause issue with multiple contexts? - * - * @private - * @param {WebGLRenderingContext} gl - The webgl rendering context - * @param {number} minWidth - The minimum width of the render target. - * @param {number} minHeight - The minimum height of the render target. - * @param {number} resolution - The resolution of the render target. - * @return {PIXI.RenderTarget} The new render target. - */ - getPotFilterTexture(minWidth, minHeight, resolution) - { - minWidth = bitTwiddle.nextPow2(minWidth); - minHeight = bitTwiddle.nextPow2(minHeight); - resolution = resolution || 1; - - const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); - - if (!this.texturePool[key]) - { - this.texturePool[key] = []; - } - - let renderTexture = this.texturePool[key].pop(); - - if (!renderTexture) - { - // temporary bypass cache.. - // internally - this will cause a texture to be bound.. - renderTexture = RenderTexture.create({ - width: minWidth, - height: minHeight, - resolution, - }); - } - - return renderTexture; - } - - /** - * Gets extra render texture to use inside current filter - * - * @param {number} resolution resolution of the renderTexture - * @returns {PIXI.RenderTexture} - */ - getFilterTexture(resolution) - { - const rt = this.activeState.renderTexture; - - const filterTexture = this.getPotFilterTexture(rt.width, rt.height, resolution || rt.baseTexture.resolution); - - filterTexture.filterFrame = rt.filterFrame; - - return filterTexture; - } - - /** - * Frees a render texture back into the pool. - * - * @param {PIXI.RenderTarget} renderTexture - The renderTarget to free - */ - returnFilterTexture(renderTexture) - { - renderTexture.filterFrame = null; - - const base = renderTexture.baseTexture; - - const minWidth = base.width; - const minHeight = base.height; - - const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); - - this.texturePool[key].push(renderTexture); - } - - /** - * Empties the texture pool. - * - */ - emptyPool() - { - for (const i in this.texturePool) - { - const textures = this.texturePool[i]; - - if (textures) - { - for (let j = 0; j < textures.length; j++) - { - textures[j].destroy(true); - } - } - } - - this.texturePool = {}; - } -} diff --git a/packages/core/src/renderers/systems/geometry/GLBuffer.js b/packages/core/src/renderers/systems/geometry/GLBuffer.js deleted file mode 100644 index 0477e8a..0000000 --- a/packages/core/src/renderers/systems/geometry/GLBuffer.js +++ /dev/null @@ -1,9 +0,0 @@ -export default class GLBuffer -{ - constructor(buffer) - { - this.buffer = buffer; - this.updateID = -1; - this.byteLength = -1; - } -} diff --git a/packages/core/src/renderers/systems/geometry/GeometrySystem.js b/packages/core/src/renderers/systems/geometry/GeometrySystem.js deleted file mode 100644 index 26c8e9e..0000000 --- a/packages/core/src/renderers/systems/geometry/GeometrySystem.js +++ /dev/null @@ -1,388 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; -import GLBuffer from './GLBuffer'; - -const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class GeometrySystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this._activeGeometry = null; - this._activeVao = null; - - this.hasVao = true; - this.hasInstance = true; - } - - /** - * Sets up the renderer context and necessary buffers. - * - * @private - */ - contextChange() - { - const gl = this.gl = this.renderer.gl; - - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - - // webgl2 - if (!gl.createVertexArray) - { - // webgl 1! - let nativeVaoExtension = this.renderer.context.extensions.vertexArrayObject; - - if (this.renderer.legacy) - { - nativeVaoExtension = null; - } - - if (nativeVaoExtension) - { - gl.createVertexArray = () => - nativeVaoExtension.createVertexArrayOES(); - - gl.bindVertexArray = (vao) => - nativeVaoExtension.bindVertexArrayOES(vao); - - gl.deleteVertexArray = (vao) => - nativeVaoExtension.deleteVertexArrayOES(vao); - } - else - { - this.hasVao = false; - gl.createVertexArray = () => - { - // empty - }; - - gl.bindVertexArray = () => - { - // empty - }; - - gl.deleteVertexArray = () => - { - // empty - }; - } - } - - if (!gl.vertexAttribDivisor) - { - const instanceExt = gl.getExtension('ANGLE_instanced_arrays'); - - if (instanceExt) - { - gl.vertexAttribDivisor = (a, b) => - instanceExt.vertexAttribDivisorANGLE(a, b); - - gl.drawElementsInstanced = (a, b, c, d, e) => - instanceExt.drawElementsInstancedANGLE(a, b, c, d, e); - - gl.drawArraysInstanced = (a, b, c, d) => - instanceExt.drawArraysInstancedANGLE(a, b, c, d); - } - else - { - this.hasInstance = false; - } - } - } - - /** - * Binds geometry so that is can be drawn. Creating a Vao if required - * @private - * @param {PIXI.Geometry} geometry instance of geometry to bind - */ - bind(geometry, shader) - { - shader = shader || this.renderer.shader.shader; - - const gl = this.gl; - - // not sure the best way to address this.. - // currently different shaders require different VAOs for the same geometry - // Still mulling over the best way to solve this one.. - // will likely need to modify the shader attribute locations at run time! - let vaos = geometry.glVertexArrayObjects[this.CONTEXT_UID]; - - if (!vaos) - { - geometry.glVertexArrayObjects[this.CONTEXT_UID] = vaos = {}; - } - - const vao = vaos[shader.program.id] || this.initGeometryVao(geometry, shader.program); - - this._activeGeometry = geometry; - - if (this._activeVao !== vao) - { - this._activeVao = vao; - - if (this.hasVao) - { - gl.bindVertexArray(vao); - } - else - { - this.activateVao(geometry, shader.program); - } - } - - // TODO - optimise later! - // don't need to loop through if nothing changed! - // maybe look to add an 'autoupdate' to geometry? - this.updateBuffers(); - } - - reset() - { - this.unbind(); - } - - updateBuffers() - { - const geometry = this._activeGeometry; - const gl = this.gl; - - for (let i = 0; i < geometry.buffers.length; i++) - { - const buffer = geometry.buffers[i]; - - const glBuffer = buffer._glBuffers[this.CONTEXT_UID]; - - if (buffer._updateID !== glBuffer.updateID) - { - glBuffer.updateID = buffer._updateID; - - // TODO can cache this on buffer! maybe added a getter / setter? - const type = buffer.index ? gl.ELEMENT_ARRAY_BUFFER : gl.ARRAY_BUFFER; - const drawType = buffer.static ? gl.STATIC_DRAW : gl.DYNAMIC_DRAW; - - gl.bindBuffer(type, glBuffer.buffer); - - if (glBuffer.byteLength >= buffer.data.byteLength) - { - // offset is always zero for now! - gl.bufferSubData(type, 0, buffer.data); - } - else - { - gl.bufferData(type, buffer.data, drawType); - } - } - } - } - - checkCompatability(geometry, program) - { - // geometry must have at least all the attributes that the shader requires. - const geometryAttributes = geometry.attributes; - const shaderAttributes = program.attributeData; - - for (const j in shaderAttributes) - { - if (!geometryAttributes[j]) - { - throw new Error(`shader and geometry incompatible, geometry missing the "${j}" attribute`); - } - } - } - - /** - * Creates a Vao with the same structure as the geometry and stores it on the geometry. - * @private - * @param {PIXI.Geometry} geometry instance of geometry to to generate Vao for - */ - initGeometryVao(geometry, program) - { - this.checkCompatability(geometry, program); - - const gl = this.gl; - const CONTEXT_UID = this.CONTEXT_UID; - const buffers = geometry.buffers; - const attributes = geometry.attributes; - - const tempStride = {}; - const tempStart = {}; - - for (const j in buffers) - { - tempStride[j] = 0; - tempStart[j] = 0; - } - - for (const j in attributes) - { - if (!attributes[j].size && program.attributeData[j]) - { - attributes[j].size = program.attributeData[j].size; - } - - tempStride[attributes[j].buffer] += attributes[j].size * byteSizeMap[attributes[j].type]; - } - - for (const j in attributes) - { - const attribute = attributes[j]; - const attribSize = attribute.size; - - if (attribute.stride === undefined) - { - if (tempStride[attribute.buffer] === attribSize * byteSizeMap[attribute.type]) - { - attribute.stride = 0; - } - else - { - attribute.stride = tempStride[attribute.buffer]; - } - } - - if (attribute.start === undefined) - { - attribute.start = tempStart[attribute.buffer]; - - tempStart[attribute.buffer] += attribSize * byteSizeMap[attribute.type]; - } - } - - // first update - and create the buffers! - // only create a gl buffer if it actually gets - for (let i = 0; i < buffers.length; i++) - { - const buffer = buffers[i]; - - if (!buffer._glBuffers[CONTEXT_UID]) - { - buffer._glBuffers[CONTEXT_UID] = new GLBuffer(gl.createBuffer()); - } - } - - // TODO - maybe make this a data object? - // lets wait to see if we need to first! - const vao = gl.createVertexArray(); - - gl.bindVertexArray(vao); - - this.activateVao(geometry, program); - - gl.bindVertexArray(null); - - // add it to the cache! - geometry.glVertexArrayObjects[this.CONTEXT_UID][program.id] = vao; - - return vao; - } - - activateVao(geometry, program) - { - const gl = this.gl; - const CONTEXT_UID = this.CONTEXT_UID; - const buffers = geometry.buffers; - const attributes = geometry.attributes; - - if (geometry.indexBuffer) - { - // first update the index buffer if we have one.. - gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, geometry.indexBuffer._glBuffers[CONTEXT_UID].buffer); - } - - let lastBuffer = null; - - // add a new one! - for (const j in attributes) - { - const attribute = attributes[j]; - const buffer = buffers[attribute.buffer]; - const glBuffer = buffer._glBuffers[CONTEXT_UID]; - - if (program.attributeData[j]) - { - if (lastBuffer !== glBuffer) - { - gl.bindBuffer(gl.ARRAY_BUFFER, glBuffer.buffer); - - lastBuffer = glBuffer; - } - - const location = program.attributeData[j].location; - - // TODO introduce state again - // we can optimise this for older devices that have no VAOs - gl.enableVertexAttribArray(location); - - gl.vertexAttribPointer(location, - attribute.size, - attribute.type || gl.FLOAT, - attribute.normalized, - attribute.stride, - attribute.start); - - if (attribute.instance) - { - // TODO calculate instance count based of this... - if (this.hasInstance) - { - gl.vertexAttribDivisor(location, 1); - } - else - { - throw new Error('geometry error, GPU Instancing is not supported on this device'); - } - } - } - } - } - - draw(type, size, start, instanceCount) - { - const gl = this.gl; - const geometry = this._activeGeometry; - - // TODO.. this should not change so maybe cache the function? - - if (geometry.indexBuffer) - { - if (geometry.instanced) - { - /* eslint-disable max-len */ - gl.drawElementsInstanced(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2, instanceCount || 1); - /* eslint-enable max-len */ - } - else - { - gl.drawElements(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2); - } - } - else - if (geometry.instanced) - { - // TODO need a better way to calculate size.. - gl.drawArraysInstanced(type, start, size || geometry.getSize(), instanceCount || 1); - } - else - { - gl.drawArrays(type, start, size || geometry.getSize()); - } - - return this; - } - - unbind() - { - this.gl.bindVertexArray(null); - this._activeVao = null; - this._activeGeometry = null; - } -} diff --git a/packages/core/src/renderers/systems/geometry/setVertexAttribArrays.js b/packages/core/src/renderers/systems/geometry/setVertexAttribArrays.js deleted file mode 100644 index 23b6959..0000000 --- a/packages/core/src/renderers/systems/geometry/setVertexAttribArrays.js +++ /dev/null @@ -1,54 +0,0 @@ -// var GL_MAP = {}; - -/** - * @param gl {WebGLRenderingContext} The current WebGL context - * @param attribs {*} - * @param state {*} - */ -export default function setVertexAttribArrays(gl, attribs, state) -{ - let i; - - if (state) - { - const tempAttribState = state.tempAttribState; - const attribState = state.attribState; - - for (i = 0; i < tempAttribState.length; i++) - { - tempAttribState[i] = false; - } - - // set the new attribs - for (i = 0; i < attribs.length; i++) - { - tempAttribState[attribs[i].attribute.location] = true; - } - - for (i = 0; i < attribState.length; i++) - { - if (attribState[i] !== tempAttribState[i]) - { - attribState[i] = tempAttribState[i]; - - if (state.attribState[i]) - { - gl.enableVertexAttribArray(i); - } - else - { - gl.disableVertexAttribArray(i); - } - } - } - } - else - { - for (i = 0; i < attribs.length; i++) - { - const attrib = attribs[i]; - - gl.enableVertexAttribArray(attrib.attribute.location); - } - } -} diff --git a/packages/core/src/renderers/systems/index.js b/packages/core/src/renderers/systems/index.js deleted file mode 100644 index c843f03..0000000 --- a/packages/core/src/renderers/systems/index.js +++ /dev/null @@ -1,6 +0,0 @@ -/** - * Systems are individual components to the Renderer pipeline. - * @namespace PIXI.systems - */ -export { default as WebGLSystem } from './WebGLSystem'; -export { default as FilterSystem } from './filter/FilterSystem'; diff --git a/packages/core/src/renderers/systems/shader/GLProgram.js b/packages/core/src/renderers/systems/shader/GLProgram.js deleted file mode 100644 index 92980ca..0000000 --- a/packages/core/src/renderers/systems/shader/GLProgram.js +++ /dev/null @@ -1,43 +0,0 @@ -/** - * Helper class to create a webGL Program - * - * @class - * @private - */ -export default class GLProgram -{ - constructor(program, uniformData) - { - /** - * The shader program - * - * @member {WebGLProgram} - */ - this.program = program; - - /** - * holds the uniform data which contains uniform locations - * and current uniform values used for caching and preventing undeed GPU commands - * @type {Object} - */ - this.uniformData = uniformData; - - /** - * uniformGroups holds the various upload functions for the shader. Each uniform group - * and program have a unique upload function generated. - * @type {Object} - */ - this.uniformGroups = {}; - } - - /** - * Destroys this program - * TODO - */ - destroy() - { - this.uniformData = null; - this.uniformGroups = null; - this.program = null; - } -} diff --git a/packages/core/src/renderers/systems/shader/ShaderSystem.js b/packages/core/src/renderers/systems/shader/ShaderSystem.js deleted file mode 100644 index 6cf57b6..0000000 --- a/packages/core/src/renderers/systems/shader/ShaderSystem.js +++ /dev/null @@ -1,170 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; -import GLProgram from './GLProgram'; -import generateUniformsSync from '../../../shader/generateUniformsSync'; -import defaultValue from '../../../shader/defaultValue'; -import compileProgram from './shader/compileProgram'; - -let UID = 0; - -/** - * Helper class to create a webGL Texture - * - * @class - * @memberof PIXI - */ -export default class ShaderSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - A reference to the current renderer - */ - constructor(renderer) - { - super(renderer); - - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = null; - - this.shader = null; - this.program = null; - - this.id = UID++; - } - - contextChange(gl) - { - this.gl = gl; - } - - /** - * Changes the current shader to the one given in parameter - * - * @param {PIXI.Shader} shader - the new shader - * @param {boolean} dontSync - false if the shader should automatically sync its uniforms. - * @returns {PIXI.glCore.glProgram} the glProgram that belongs to the shader. - */ - bind(shader, dontSync) - { - shader.uniforms.globals = this.renderer.globalUniforms; - - const program = shader.program; - const glProgram = program.glPrograms[this.renderer.CONTEXT_UID] || this.generateShader(shader); - - this.shader = shader; - - // TODO - some current pixi plugins bypass this.. so it not safe to use yet.. - if (this.program !== program) - { - this.program = program; - this.gl.useProgram(glProgram.program); - } - - if (!dontSync) - { - this.syncUniformGroup(shader.uniformGroup); - } - - return glProgram; - } - - /** - * Uploads the uniforms values to the currently bound shader. - * - * @param {object} uniforms - the uniforms valiues that be applied to the current shader - */ - setUniforms(uniforms) - { - const shader = this.shader.program; - const glProgram = shader.glPrograms[this.renderer.CONTEXT_UID]; - - shader.syncUniforms(glProgram.uniformData, uniforms, this.renderer); - } - - syncUniformGroup(group) - { - const glProgram = this.getglProgram(); - - if (!group.static || group.dirtyId !== glProgram.uniformGroups[group.id]) - { - glProgram.uniformGroups[group.id] = group.dirtyId; - const syncFunc = group.syncUniforms[this.shader.program.id] || this.createSyncGroups(group); - - syncFunc(glProgram.uniformData, group.uniforms, this.renderer); - } - } - - createSyncGroups(group) - { - group.syncUniforms[this.shader.program.id] = generateUniformsSync(group, this.shader.program.uniformData); - - return group.syncUniforms[this.shader.program.id]; - } - - /** - * Returns the underlying GLShade rof the currently bound shader. - * This can be handy for when you to have a little more control over the setting of your uniforms. - * - * @return {PIXI.glCore.Shader} the glProgram for the currently bound Shader for this context - */ - getglProgram() - { - if (this.shader) - { - return this.shader.program.glPrograms[this.renderer.CONTEXT_UID]; - } - - return null; - } - - /** - * Generates a glProgram verion of the Shader provided. - * - * @private - * @param {PIXI.Shader} shader the shader that the glProgram will be based on. - * @return {PIXI.glCore.glProgram} A shiney new glProgram - */ - generateShader(shader) - { - const gl = this.gl; - - const program = shader.program; - - const attribMap = {}; - - for (const i in program.attributeData) - { - attribMap[i] = program.attributeData[i].location; - } - - const shaderProgram = compileProgram(gl, program.vertexSrc, program.fragmentSrc, attribMap); - const uniformData = {}; - - for (const i in program.uniformData) - { - const data = program.uniformData[i]; - - uniformData[i] = { - location: gl.getUniformLocation(shaderProgram, i), - value: defaultValue(data.type, data.size), - }; - } - - const glProgram = new GLProgram(shaderProgram, uniformData); - - program.glPrograms[this.renderer.CONTEXT_UID] = glProgram; - - return glProgram; - } - - /** - * Destroys this System and removes all its textures - */ - destroy() - { - // TODO implement destroy method for ShaderSystem - this.destroyed = true; - } -} diff --git a/packages/core/src/renderers/systems/shader/shader/compileProgram.js b/packages/core/src/renderers/systems/shader/shader/compileProgram.js deleted file mode 100644 index 8c6ccb9..0000000 --- a/packages/core/src/renderers/systems/shader/shader/compileProgram.js +++ /dev/null @@ -1,78 +0,0 @@ -/** - * @method compileProgram - * @memberof PIXI.glCore.shader - * @param gl {WebGLRenderingContext} The current WebGL context {WebGLProgram} - * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. - * @param fragmentSrc {string|string[]} The fragment shader source as an array of strings. - * @param attributeLocations {Object} An attribute location map that lets you manually set the attribute locations - * @return {WebGLProgram} the shader program - */ -export default function compileProgram(gl, vertexSrc, fragmentSrc, attributeLocations) -{ - const glVertShader = compileShader(gl, gl.VERTEX_SHADER, vertexSrc); - const glFragShader = compileShader(gl, gl.FRAGMENT_SHADER, fragmentSrc); - - let program = gl.createProgram(); - - gl.attachShader(program, glVertShader); - gl.attachShader(program, glFragShader); - - // optionally, set the attributes manually for the program rather than letting WebGL decide.. - if (attributeLocations) - { - for (const i in attributeLocations) - { - gl.bindAttribLocation(program, attributeLocations[i], i); - } - } - - gl.linkProgram(program); - - // if linking fails, then log and cleanup - if (!gl.getProgramParameter(program, gl.LINK_STATUS)) - { - console.error('Pixi.js Error: Could not initialize shader.'); - console.error('gl.VALIDATE_STATUS', gl.getProgramParameter(program, gl.VALIDATE_STATUS)); - console.error('gl.getError()', gl.getError()); - - // if there is a program info log, log it - if (gl.getProgramInfoLog(program) !== '') - { - console.warn('Pixi.js Warning: gl.getProgramInfoLog()', gl.getProgramInfoLog(program)); - } - - gl.deleteProgram(program); - program = null; - } - - // clean up some shaders - gl.deleteShader(glVertShader); - gl.deleteShader(glFragShader); - - return program; -} - -/** - * @private - * @param gl {WebGLRenderingContext} The current WebGL context {WebGLProgram} - * @param type {Number} the type, can be either VERTEX_SHADER or FRAGMENT_SHADER - * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. - * @return {WebGLShader} the shader - */ -function compileShader(gl, type, src) -{ - const shader = gl.createShader(type); - - gl.shaderSource(shader, src); - gl.compileShader(shader); - - if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) - { - console.warn(src); - console.error(gl.getShaderInfoLog(shader)); - - return null; - } - - return shader; -} diff --git a/packages/core/src/renderers/systems/shader/shader/defaultValue.js b/packages/core/src/renderers/systems/shader/shader/defaultValue.js deleted file mode 100644 index 97290bd..0000000 --- a/packages/core/src/renderers/systems/shader/shader/defaultValue.js +++ /dev/null @@ -1,79 +0,0 @@ -/** - * @method defaultValue - * @memberof PIXI.glCore.shader - * @param type {String} Type of value - * @param size {Number} - */ -export default function defaultValue(type, size) -{ - switch (type) - { - case 'float': - return 0; - - case 'vec2': - return new Float32Array(2 * size); - - case 'vec3': - return new Float32Array(3 * size); - - case 'vec4': - return new Float32Array(4 * size); - - case 'int': - case 'sampler2D': - case 'sampler2DArray': - return 0; - - case 'ivec2': - return new Int32Array(2 * size); - - case 'ivec3': - return new Int32Array(3 * size); - - case 'ivec4': - return new Int32Array(4 * size); - - case 'bool': - return false; - - case 'bvec2': - - return booleanArray(2 * size); - - case 'bvec3': - return booleanArray(3 * size); - - case 'bvec4': - return booleanArray(4 * size); - - case 'mat2': - return new Float32Array([1, 0, - 0, 1]); - - case 'mat3': - return new Float32Array([1, 0, 0, - 0, 1, 0, - 0, 0, 1]); - - case 'mat4': - return new Float32Array([1, 0, 0, 0, - 0, 1, 0, 0, - 0, 0, 1, 0, - 0, 0, 0, 1]); - } - - return null; -} - -function booleanArray(size) -{ - const array = new Array(size); - - for (let i = 0; i < array.length; i++) - { - array[i] = false; - } - - return array; -} diff --git a/packages/core/src/renderers/systems/shader/shader/index.js b/packages/core/src/renderers/systems/shader/shader/index.js deleted file mode 100644 index ba7a317..0000000 --- a/packages/core/src/renderers/systems/shader/shader/index.js +++ /dev/null @@ -1,8 +0,0 @@ -/** - * @namespace PIXI.glCore.shader - */ -export { default as compileProgram } from './compileProgram'; -export { default as defaultValue } from './defaultValue'; -export { default as setPrecision } from './setPrecision'; -export { default as mapSize } from './mapSize'; -export { default as mapType } from './mapType'; diff --git a/packages/core/src/renderers/systems/shader/shader/mapSize.js b/packages/core/src/renderers/systems/shader/shader/mapSize.js deleted file mode 100644 index b681554..0000000 --- a/packages/core/src/renderers/systems/shader/shader/mapSize.js +++ /dev/null @@ -1,33 +0,0 @@ -const GLSL_TO_SIZE = { - float: 1, - vec2: 2, - vec3: 3, - vec4: 4, - - int: 1, - ivec2: 2, - ivec3: 3, - ivec4: 4, - - bool: 1, - bvec2: 2, - bvec3: 3, - bvec4: 4, - - mat2: 4, - mat3: 9, - mat4: 16, - - sampler2D: 1, -}; - -/** - * @method mapSize - * @memberof PIXI.glCore.shader - * @param type {String} - * @return {Number} - */ -export default function mapSize(type) -{ - return GLSL_TO_SIZE[type]; -} diff --git a/packages/core/src/renderers/systems/shader/shader/mapType.js b/packages/core/src/renderers/systems/shader/shader/mapType.js deleted file mode 100644 index d288b7c..0000000 --- a/packages/core/src/renderers/systems/shader/shader/mapType.js +++ /dev/null @@ -1,45 +0,0 @@ -let GL_TABLE = null; - -const GL_TO_GLSL_TYPES = { - FLOAT: 'float', - FLOAT_VEC2: 'vec2', - FLOAT_VEC3: 'vec3', - FLOAT_VEC4: 'vec4', - - INT: 'int', - INT_VEC2: 'ivec2', - INT_VEC3: 'ivec3', - INT_VEC4: 'ivec4', - - BOOL: 'bool', - BOOL_VEC2: 'bvec2', - BOOL_VEC3: 'bvec3', - BOOL_VEC4: 'bvec4', - - FLOAT_MAT2: 'mat2', - FLOAT_MAT3: 'mat3', - FLOAT_MAT4: 'mat4', - - SAMPLER_2D: 'sampler2D', - SAMPLER_CUBE: 'samplerCube', - SAMPLER_2D_ARRAY: 'sampler2DArray', -}; - -export default function mapType(gl, type) -{ - if (!GL_TABLE) - { - const typeNames = Object.keys(GL_TO_GLSL_TYPES); - - GL_TABLE = {}; - - for (let i = 0; i < typeNames.length; ++i) - { - const tn = typeNames[i]; - - GL_TABLE[gl[tn]] = GL_TO_GLSL_TYPES[tn]; - } - } - - return GL_TABLE[type]; -} diff --git a/packages/core/src/renderers/systems/shader/shader/setPrecision.js b/packages/core/src/renderers/systems/shader/shader/setPrecision.js deleted file mode 100644 index e2c1f34..0000000 --- a/packages/core/src/renderers/systems/shader/shader/setPrecision.js +++ /dev/null @@ -1,18 +0,0 @@ -/** - * Sets the float precision on the shader. If the precision is already present this function will do nothing - * @param {string} src the shader source - * @param {string} precision The float precision of the shader. Options are 'lowp', 'mediump' or 'highp'. - * - * @return {string} modified shader source - */ -export default function setPrecision(src, precision) -{ - src = src.trim(); - - if (src.substring(0, 9) !== 'precision' && src.substring(0, 1) !== '#') - { - return `precision ${precision} float;\n${src}`; - } - - return src; -} diff --git a/packages/core/src/renderers/systems/textures/GLTexture.js b/packages/core/src/renderers/systems/textures/GLTexture.js deleted file mode 100644 index c3fb8dc..0000000 --- a/packages/core/src/renderers/systems/textures/GLTexture.js +++ /dev/null @@ -1,33 +0,0 @@ -export default class GLTexture -{ - constructor(texture) - { - /** - * The WebGL texture - * - * @member {WebGLTexture} - */ - this.texture = texture; - - this.width = -1; - this.height = -1; - - /** - * Texture contents dirty flag - * @member {number} - */ - this.dirtyId = -1; - - /** - * Texture style dirty flag - * @type {number} - */ - this.dirtyStyleId = -1; - - /** - * Whether mip levels has to be generated - * @type {boolean} - */ - this.mipmap = false; - } -} diff --git a/packages/core/src/renderers/systems/textures/TextureGCSystem.js b/packages/core/src/renderers/systems/textures/TextureGCSystem.js deleted file mode 100644 index b089938..0000000 --- a/packages/core/src/renderers/systems/textures/TextureGCSystem.js +++ /dev/null @@ -1,110 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; -import { GC_MODES } from '@pixi/constants'; -import { settings } from '@pixi/settings'; - -/** - * TextureGarbageCollector. This class manages the GPU and ensures that it does not get clogged - * up with textures that are no longer being used. - * - * @class - * @memberof PIXI - */ -export default class TextureGCSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this.count = 0; - this.checkCount = 0; - this.maxIdle = settings.GC_MAX_IDLE; - this.checkCountMax = settings.GC_MAX_CHECK_COUNT; - this.mode = settings.GC_MODE; - } - - /** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ - postrender() - { - this.count++; - - if (this.mode === GC_MODES.MANUAL) - { - return; - } - - this.checkCount++; - - if (this.checkCount > this.checkCountMax) - { - this.checkCount = 0; - - this.run(); - } - } - - /** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ - run() - { - const tm = this.renderer.texture; - const managedTextures = tm.managedTextures; - let wasRemoved = false; - - for (let i = 0; i < managedTextures.length; i++) - { - const texture = managedTextures[i]; - - // only supports non generated textures at the moment! - if (!texture.frameBuffer && this.count - texture.touched > this.maxIdle) - { - tm.destroyTexture(texture, true); - managedTextures[i] = null; - wasRemoved = true; - } - } - - if (wasRemoved) - { - let j = 0; - - for (let i = 0; i < managedTextures.length; i++) - { - if (managedTextures[i] !== null) - { - managedTextures[j++] = managedTextures[i]; - } - } - - managedTextures.length = j; - } - } - - /** - * Removes all the textures within the specified displayObject and its children from the GPU - * - * @param {PIXI.DisplayObject} displayObject - the displayObject to remove the textures from. - */ - unload(displayObject) - { - const tm = this.renderer.textureSystem; - - // only destroy non generated textures - if (displayObject._texture && displayObject._texture._glRenderTargets) - { - tm.destroyTexture(displayObject._texture); - } - - for (let i = displayObject.children.length - 1; i >= 0; i--) - { - this.unload(displayObject.children[i]); - } - } -} diff --git a/packages/core/src/renderers/systems/textures/TextureSystem.js b/packages/core/src/renderers/systems/textures/TextureSystem.js deleted file mode 100644 index ccc5de9..0000000 --- a/packages/core/src/renderers/systems/textures/TextureSystem.js +++ /dev/null @@ -1,281 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; -import GLTexture from './GLTexture'; -import { removeItems } from '@pixi/utils'; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class TextureSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - // TODO set to max textures... - this.boundTextures = [ - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - ]; - - this.currentLocation = -1; - - this.managedTextures = []; - } - - /** - * Sets up the renderer context and necessary buffers. - * - * @private - */ - contextChange() - { - const gl = this.gl = this.renderer.gl; - - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - - // TODO move this.. to a nice make empty textures class.. - this.emptyTextures = {}; - - const emptyTexture2D = new GLTexture(gl.createTexture()); - - gl.bindTexture(gl.TEXTURE_2D, emptyTexture2D.texture); - gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array(4)); - - this.emptyTextures[gl.TEXTURE_2D] = emptyTexture2D; - this.emptyTextures[gl.TEXTURE_CUBE_MAP] = new GLTexture(gl.createTexture()); - - gl.bindTexture(gl.TEXTURE_CUBE_MAP, this.emptyTextures[gl.TEXTURE_CUBE_MAP].texture); - - let i; - - for (i = 0; i < 6; i++) - { - gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, null); - } - - gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_MAG_FILTER, gl.LINEAR); - gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_MIN_FILTER, gl.LINEAR); - - for (i = 0; i < this.boundTextures.length; i++) - { - this.bind(null, i); - } - } - - bind(texture, location) - { - const gl = this.gl; - - location = location || 0; - - if (this.currentLocation !== location) - { - this.currentLocation = location; - gl.activeTexture(gl.TEXTURE0 + location); - } - - if (texture) - { - texture = texture.baseTexture || texture; - - if (texture.valid) - { - texture.touched = this.renderer.textureGC.count; - - const glTexture = texture._glTextures[this.CONTEXT_UID] || this.initTexture(texture); - - gl.bindTexture(texture.target, glTexture.texture); - - if (glTexture.dirtyId !== texture.dirtyId) - { - this.updateTexture(texture); - } - - this.boundTextures[location] = texture; - } - } - else - { - gl.bindTexture(gl.TEXTURE_2D, this.emptyTextures[gl.TEXTURE_2D].texture); - this.boundTextures[location] = null; - } - } - - unbind(texture) - { - const gl = this.gl; - - for (let i = 0; i < this.boundTextures.length; i++) - { - if (this.boundTextures[i] === texture) - { - if (this.currentLocation !== i) - { - gl.activeTexture(gl.TEXTURE0 + i); - this.currentLocation = i; - } - - gl.bindTexture(gl.TEXTURE_2D, this.emptyTextures[texture.target].texture); - this.boundTextures[i] = null; - } - } - } - - initTexture(texture) - { - const glTexture = new GLTexture(this.gl.createTexture()); - - // guarentee an update.. - glTexture.dirtyId = -1; - - texture._glTextures[this.CONTEXT_UID] = glTexture; - - this.managedTextures.push(texture); - texture.on('dispose', this.destroyTexture, this); - - return glTexture; - } - - updateTexture(texture) - { - const glTexture = texture._glTextures[this.CONTEXT_UID]; - const renderer = this.renderer; - - if (texture.resource && texture.resource.upload(renderer, texture, glTexture)) - { - // texture is uploaded, dont do anything! - } - else - { - // default, renderTexture-like logic - const width = texture.realWidth; - const height = texture.realHeight; - const gl = renderer.gl; - - if (glTexture.width !== width - || glTexture.height !== height - || glTexture.dirtyId < 0) - { - glTexture.width = width; - glTexture.height = height; - - gl.texImage2D(texture.target, 0, - texture.format, - width, - height, - 0, - texture.format, - texture.type, - null); - } - } - - // lets only update what changes.. - if (texture.dirtyStyleId !== glTexture.dirtyStyleId) - { - this.updateTextureStyle(texture); - } - glTexture.dirtyId = texture.dirtyId; - } - - /** - * Deletes the texture from WebGL - * - * @param {PIXI.BaseTexture|PIXI.Texture} texture - the texture to destroy - * @param {boolean} [skipRemove=false] - Whether to skip removing the texture from the TextureManager. - */ - destroyTexture(texture, skipRemove) - { - const gl = this.gl; - - texture = texture.baseTexture || texture; - - if (texture._glTextures[this.renderer.CONTEXT_UID]) - { - this.unbind(texture); - - gl.deleteTexture(texture._glTextures[this.renderer.CONTEXT_UID].texture); - texture.off('dispose', this.destroyTexture, this); - - delete texture._glTextures[this.renderer.CONTEXT_UID]; - - if (!skipRemove) - { - const i = this.managedTextures.indexOf(texture); - - if (i !== -1) - { - removeItems(this.managedTextures, i, 1); - } - } - } - } - - updateTextureStyle(texture) - { - const glTexture = texture._glTextures[this.CONTEXT_UID]; - - glTexture.mipmap = texture.mipmap && texture.isPowerOfTwo; - if (!glTexture) - { - return; - } - - if (texture.resource && texture.resource.style(this.renderer, texture, glTexture)) - { - // style is set, dont do anything! - } - else - { - this.setStyle(texture, glTexture); - } - - glTexture.dirtyStyleId = texture.dirtyStyleId; - } - - setStyle(texture, glTexture) - { - const gl = this.gl; - - if (glTexture.mipmap) - { - gl.generateMipmap(texture.target); - } - - gl.texParameteri(texture.target, gl.TEXTURE_WRAP_S, texture.wrapMode); - gl.texParameteri(texture.target, gl.TEXTURE_WRAP_T, texture.wrapMode); - - if (glTexture.mipmap) - { - /* eslint-disable max-len */ - gl.texParameteri(texture.target, gl.TEXTURE_MIN_FILTER, texture.scaleMode ? gl.LINEAR_MIPMAP_LINEAR : gl.NEAREST_MIPMAP_NEAREST); - /* eslint-disable max-len */ - } - else - { - gl.texParameteri(texture.target, gl.TEXTURE_MIN_FILTER, texture.scaleMode ? gl.LINEAR : gl.NEAREST); - } - - gl.texParameteri(texture.target, gl.TEXTURE_MAG_FILTER, texture.scaleMode ? gl.LINEAR : gl.NEAREST); - } -} diff --git a/packages/core/src/renderers/utils/ObjectRenderer.js b/packages/core/src/renderers/utils/ObjectRenderer.js deleted file mode 100644 index f67a273..0000000 --- a/packages/core/src/renderers/utils/ObjectRenderer.js +++ /dev/null @@ -1,48 +0,0 @@ -import WebGLSystem from '../systems/WebGLSystem'; - -/** - * Base for a common object renderer that can be used as a system renderer plugin. - * - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class ObjectRenderer extends WebGLSystem -{ - /** - * Starts the renderer and sets the shader - * - */ - start() - { - // set the shader.. - } - - /** - * Stops the renderer - * - */ - stop() - { - this.flush(); - } - - /** - * Stub method for rendering content and emptying the current batch. - * - */ - flush() - { - // flush! - } - - /** - * Renders an object - * - * @param {PIXI.DisplayObject} object - The object to render. - */ - render(object) // eslint-disable-line no-unused-vars - { - // render the object - } -} diff --git a/packages/core/src/renderers/utils/Quad.js b/packages/core/src/renderers/utils/Quad.js deleted file mode 100644 index 5c1517b..0000000 --- a/packages/core/src/renderers/utils/Quad.js +++ /dev/null @@ -1,26 +0,0 @@ -import Geometry from '../../geometry/Geometry'; - -/** - * Helper class to create a quad - * - * @class - * @memberof PIXI - */ -export default class Quad extends Geometry -{ - /** - * just a quad - */ - constructor() - { - super(); - - this.addAttribute('aVertexPosition', [ - 0, 0, - 1, 0, - 1, 1, - 0, 1, - ]) - .addIndex([0, 1, 3, 2]); - } -} diff --git a/packages/core/src/renderers/utils/QuadUv.js b/packages/core/src/renderers/utils/QuadUv.js deleted file mode 100644 index 97bda89..0000000 --- a/packages/core/src/renderers/utils/QuadUv.js +++ /dev/null @@ -1,103 +0,0 @@ -import Geometry from '../../geometry/Geometry'; -import Buffer from '../../geometry/Buffer'; - -/** - * Helper class to create a quad with uvs like in v4 - * - * @class - * @memberof PIXI - */ -export default class QuadUv extends Geometry -{ - constructor() - { - super(); - - /** - * An array of vertices - * - * @member {Float32Array} - */ - this.vertices = new Float32Array([ - -1, -1, - 1, -1, - 1, 1, - -1, 1, - ]); - - /** - * The Uvs of the quad - * - * @member {Float32Array} - */ - this.uvs = new Float32Array([ - 0, 0, - 1, 0, - 1, 1, - 0, 1, - ]); - - this.vertexBuffer = new Buffer(this.vertices); - this.uvBuffer = new Buffer(this.uvs); - - this.addAttribute('aVertexPosition', this.vertexBuffer) - .addAttribute('aTextureCoord', this.uvBuffer) - .addIndex([0, 1, 2, 0, 2, 3]); - } - - /** - * Maps two Rectangle to the quad. - * - * @param {PIXI.Rectangle} targetTextureFrame - the first rectangle - * @param {PIXI.Rectangle} destinationFrame - the second rectangle - * @return {PIXI.Quad} Returns itself. - */ - map(targetTextureFrame, destinationFrame) - { - let x = 0; // destinationFrame.x / targetTextureFrame.width; - let y = 0; // destinationFrame.y / targetTextureFrame.height; - - this.uvs[0] = x; - this.uvs[1] = y; - - this.uvs[2] = x + (destinationFrame.width / targetTextureFrame.width); - this.uvs[3] = y; - - this.uvs[4] = x + (destinationFrame.width / targetTextureFrame.width); - this.uvs[5] = y + (destinationFrame.height / targetTextureFrame.height); - - this.uvs[6] = x; - this.uvs[7] = y + (destinationFrame.height / targetTextureFrame.height); - - x = destinationFrame.x; - y = destinationFrame.y; - - this.vertices[0] = x; - this.vertices[1] = y; - - this.vertices[2] = x + destinationFrame.width; - this.vertices[3] = y; - - this.vertices[4] = x + destinationFrame.width; - this.vertices[5] = y + destinationFrame.height; - - this.vertices[6] = x; - this.vertices[7] = y + destinationFrame.height; - - this.invalidate(); - - return this; - } - - /** - * legacy upload method, just marks buffers dirty - * @returns {PIXI.QuadUv} Returns itself. - */ - invalidate() - { - this.vertexBuffer._updateID++; - this.uvBuffer._updateID++; - - return this; - } -} diff --git a/packages/core/src/renderers/utils/checkMaxIfStatmentsInShader.js b/packages/core/src/renderers/utils/checkMaxIfStatmentsInShader.js deleted file mode 100644 index 8047c96..0000000 --- a/packages/core/src/renderers/utils/checkMaxIfStatmentsInShader.js +++ /dev/null @@ -1,58 +0,0 @@ -const fragTemplate = [ - 'precision mediump float;', - 'void main(void){', - 'float test = 0.1;', - '%forloop%', - 'gl_FragColor = vec4(0.0);', - '}', -].join('\n'); - -export default function checkMaxIfStatmentsInShader(maxIfs, gl) -{ - if (maxIfs === 0) - { - throw new Error('Invalid value of `0` passed to `checkMaxIfStatementsInShader`'); - } - - const shader = gl.createShader(gl.FRAGMENT_SHADER); - - while (true) // eslint-disable-line no-constant-condition - { - const fragmentSrc = fragTemplate.replace(/%forloop%/gi, generateIfTestSrc(maxIfs)); - - gl.shaderSource(shader, fragmentSrc); - gl.compileShader(shader); - - if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) - { - maxIfs = (maxIfs / 2) | 0; - } - else - { - // valid! - break; - } - } - - return maxIfs; -} - -function generateIfTestSrc(maxIfs) -{ - let src = ''; - - for (let i = 0; i < maxIfs; ++i) - { - if (i > 0) - { - src += '\nelse '; - } - - if (i < maxIfs - 1) - { - src += `if(test == ${i}.0){}`; - } - } - - return src; -} diff --git a/packages/core/src/renderers/utils/mapWebGLBlendModesToPixi.js b/packages/core/src/renderers/utils/mapWebGLBlendModesToPixi.js deleted file mode 100644 index 10ffe4d..0000000 --- a/packages/core/src/renderers/utils/mapWebGLBlendModesToPixi.js +++ /dev/null @@ -1,47 +0,0 @@ -import { BLEND_MODES } from '@pixi/constants'; - -/** - * Maps gl blend combinations to WebGL. - * - * @memberof PIXI - * @function mapWebGLBlendModesToPixi - * @private - * @param {WebGLRenderingContext} gl - The rendering context. - * @param {string[]} [array=[]] - The array to output into. - * @return {string[]} Mapped modes. - */ -export default function mapWebGLBlendModesToPixi(gl, array = []) -{ - // TODO - premultiply alpha would be different. - // add a boolean for that! - array[BLEND_MODES.NORMAL] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.ADD] = [gl.ONE, gl.DST_ALPHA]; - array[BLEND_MODES.MULTIPLY] = [gl.DST_COLOR, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.SCREEN] = [gl.ONE, gl.ONE_MINUS_SRC_COLOR]; - array[BLEND_MODES.OVERLAY] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.DARKEN] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.LIGHTEN] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.COLOR_DODGE] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.COLOR_BURN] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.HARD_LIGHT] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.SOFT_LIGHT] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.DIFFERENCE] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.EXCLUSION] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.HUE] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.SATURATION] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.COLOR] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.LUMINOSITY] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.NONE] = [0, 0]; - - // not-premultiplied blend modes - array[BLEND_MODES.NORMAL_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.ADD_NPM] = [gl.SRC_ALPHA, gl.DST_ALPHA, gl.ONE, gl.DST_ALPHA]; - array[BLEND_MODES.SCREEN_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_COLOR, gl.ONE, gl.ONE_MINUS_SRC_COLOR]; - - // not-premultiplied blend modes - array[BLEND_MODES.NORMAL_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.ADD_NPM] = [gl.SRC_ALPHA, gl.DST_ALPHA, gl.ONE, gl.DST_ALPHA]; - array[BLEND_MODES.SCREEN_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_COLOR, gl.ONE, gl.ONE_MINUS_SRC_COLOR]; - - return array; -} diff --git a/packages/core/src/shader/GLProgram.js b/packages/core/src/shader/GLProgram.js new file mode 100644 index 0000000..92980ca --- /dev/null +++ b/packages/core/src/shader/GLProgram.js @@ -0,0 +1,43 @@ +/** + * Helper class to create a webGL Program + * + * @class + * @private + */ +export default class GLProgram +{ + constructor(program, uniformData) + { + /** + * The shader program + * + * @member {WebGLProgram} + */ + this.program = program; + + /** + * holds the uniform data which contains uniform locations + * and current uniform values used for caching and preventing undeed GPU commands + * @type {Object} + */ + this.uniformData = uniformData; + + /** + * uniformGroups holds the various upload functions for the shader. Each uniform group + * and program have a unique upload function generated. + * @type {Object} + */ + this.uniformGroups = {}; + } + + /** + * Destroys this program + * TODO + */ + destroy() + { + this.uniformData = null; + this.uniformGroups = null; + this.program = null; + } +} diff --git a/packages/core/src/shader/Program.js b/packages/core/src/shader/Program.js index 4f9971e..4b4007a 100644 --- a/packages/core/src/shader/Program.js +++ b/packages/core/src/shader/Program.js @@ -1,6 +1,11 @@ -import * as shaderUtils from '../renderers/systems/shader/shader'; +// import * as from '../systems/shader/shader'; +import { setPrecision, + defaultValue, + compileProgram, + mapSize, + mapType, + getTestContext } from './utils'; import { ProgramCache } from '@pixi/utils'; -import getTestContext from './getTestContext'; import defaultFragment from './defaultProgram.frag'; import defaultVertex from './defaultProgram.vert'; import { settings } from '@pixi/settings'; @@ -34,8 +39,8 @@ */ this.fragmentSrc = fragmentSrc || Program.defaultFragmentSrc; - this.vertexSrc = shaderUtils.setPrecision(this.vertexSrc, settings.PRECISION_VERTEX); - this.fragmentSrc = shaderUtils.setPrecision(this.fragmentSrc, settings.PRECISION_FRAGMENT); + this.vertexSrc = setPrecision(this.vertexSrc, settings.PRECISION_VERTEX); + this.fragmentSrc = setPrecision(this.fragmentSrc, settings.PRECISION_FRAGMENT); // currently this does not extract structs only default types this.extractData(this.vertexSrc, this.fragmentSrc); @@ -62,7 +67,7 @@ if (gl) { - const program = shaderUtils.compileProgram(gl, vertexSrc, fragmentSrc); + const program = compileProgram(gl, vertexSrc, fragmentSrc); this.attributeData = this.getAttributeData(program, gl); this.uniformData = this.getUniformData(program, gl); @@ -95,13 +100,13 @@ for (let i = 0; i < totalAttributes; i++) { const attribData = gl.getActiveAttrib(program, i); - const type = shaderUtils.mapType(gl, attribData.type); + const type = mapType(gl, attribData.type); /*eslint-disable */ const data = { type: type, name: attribData.name, - size: shaderUtils.mapSize(type), + size: mapSize(type), location: 0, }; /* eslint-enable */ @@ -145,14 +150,14 @@ const name = uniformData.name.replace(/\[.*?\]/, ''); const isArray = uniformData.name.match(/\[.*?\]/, ''); - const type = shaderUtils.mapType(gl, uniformData.type); + const type = mapType(gl, uniformData.type); /*eslint-disable */ uniforms[name] = { type: type, size: uniformData.size, isArray:isArray, - value: shaderUtils.defaultValue(type, uniformData.size), + value: defaultValue(type, uniformData.size), }; /* eslint-enable */ } diff --git a/packages/core/src/AbstractRenderer.js b/packages/core/src/AbstractRenderer.js new file mode 100644 index 0000000..64b2eb1 --- /dev/null +++ b/packages/core/src/AbstractRenderer.js @@ -0,0 +1,353 @@ +import { hex2string, hex2rgb } from '@pixi/utils'; +import { Matrix, Rectangle } from '@pixi/math'; +import { RENDERER_TYPE } from '@pixi/constants'; +import { settings } from '@pixi/settings'; +import { Container } from '@pixi/display'; +import RenderTexture from './renderTexture/RenderTexture'; +import EventEmitter from 'eventemitter3'; + +const tempMatrix = new Matrix(); + +/** + * 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 EventEmitter + * @memberof PIXI + */ +export default class AbstractRenderer extends EventEmitter +{ + // eslint-disable-next-line valid-jsdoc + /** + * @param {string} system - The name of the system this renderer is for. + * @param {object} [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.transparent=false] - If the render view is transparent, default false + * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false + * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. The + * resolution of the renderer retina would be 2. + * @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 {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when rendering, + * stopping pixel interpolation. + */ + constructor(system, options, arg2, arg3) + { + super(); + + // Support for constructor(system, screenWidth, screenHeight, options) + if (typeof options === 'number') + { + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + }, arg3); + } + + // 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 = RENDERER_TYPE.UNKNOWN; + + /** + * Measurements of the screen. (0, 0, screenWidth, screenHeight) + * + * Its safe to use as filterArea or hitArea for 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 1 + */ + this.resolution = options.resolution || settings.RESOLUTION; + + /** + * Whether the render view is transparent + * + * @member {boolean} + */ + this.transparent = options.transparent; + + /** + * Whether css dimensions of canvas view should be resized to screen dimensions automatically + * + * @member {boolean} + */ + this.autoResize = options.autoResize || false; + + /** + * Tracks the blend modes useful for this renderer. + * + * @member {object} + */ + this.blendModes = null; + + /** + * 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; + + /** + * If true PixiJS will Math.floor() x/y values when rendering, stopping pixel interpolation. + * Handy for crisp pixel art and speed on legacy devices. + * + * @member {boolean} + */ + this.roundPixels = options.roundPixels; + + /** + * The background color as a number. + * + * @member {number} + * @private + */ + this._backgroundColor = 0x000000; + + /** + * The background color as an [R, G, B] array. + * + * @member {number[]} + * @private + */ + this._backgroundColorRgba = [0, 0, 0, 0]; + + /** + * The background color as a string. + * + * @member {string} + * @private + */ + this._backgroundColorString = '#000000'; + + this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter + + /** + * This temporary display object used as the parent of the currently being rendered item + * + * @member {PIXI.DisplayObject} + * @private + */ + this._tempDisplayObjectParent = new Container(); + + /** + * The last root object that the renderer tried to render. + * + * @member {PIXI.DisplayObject} + * @private + */ + this._lastObjectRendered = this._tempDisplayObjectParent; + + /** + * Collection of plugins. + * @readonly + * @member {object} + */ + this.plugins = {}; + } + + /** + * Initialize the plugins. + * + * @protected + * @param {object} staticMap - The dictionary of staticly saved plugins. + */ + initPlugins(staticMap) + { + 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() + { + return this.view.width; + } + + /** + * Same as view.height, actual number of pixels in the canvas by vertical + * + * @member {number} + * @readonly + * @default 600 + */ + get height() + { + return this.view.height; + } + + /** + * Resizes the screen and canvas to the specified width and height + * Canvas dimensions are multiplied by resolution + * + * @param {number} screenWidth - the new width of the screen + * @param {number} screenHeight - the new height of the screen + */ + resize(screenWidth, screenHeight) + { + this.screen.width = screenWidth; + this.screen.height = screenHeight; + + this.view.width = screenWidth * this.resolution; + this.view.height = screenHeight * this.resolution; + + if (this.autoResize) + { + this.view.style.width = `${screenWidth}px`; + this.view.style.height = `${screenHeight}px`; + } + } + + /** + * 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 {PIXI.DisplayObject} displayObject - The displayObject the object will be generated from + * @param {number} scaleMode - Should be one of the scaleMode consts + * @param {number} resolution - The resolution / device pixel ratio of the texture being generated + * @param {PIXI.Rectangle} [region] - The region of the displayObject, that shall be rendered, + * if no region is specified, defaults to the local bounds of the displayObject. + * @return {PIXI.Texture} a texture of the graphics object + */ + generateTexture(displayObject, scaleMode, resolution, region) + { + region = region || displayObject.getLocalBounds(); + + // 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(region.width | 0, region.height | 0, scaleMode, resolution); + + tempMatrix.tx = -region.x; + tempMatrix.ty = -region.y; + + this.render(displayObject, renderTexture, false, tempMatrix, true); + + return renderTexture; + } + + /** + * Removes everything from the renderer and optionally removes the Canvas DOM element. + * + * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. + */ + destroy(removeView) + { + 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); + } + + this.plugins = null; + + this.type = RENDERER_TYPE.UNKNOWN; + + this.view = null; + + this.screen = null; + + this.resolution = 0; + + this.transparent = false; + + this.autoResize = false; + + this.blendModes = null; + + this.options = null; + + this.preserveDrawingBuffer = false; + this.clearBeforeRender = false; + + this.roundPixels = false; + + this._backgroundColor = 0; + this._backgroundColorRgba = null; + this._backgroundColorString = null; + + this._tempDisplayObjectParent = null; + this._lastObjectRendered = null; + } + + /** + * The background color to fill if not transparent + * + * @member {number} + */ + get backgroundColor() + { + return this._backgroundColor; + } + + set backgroundColor(value) // eslint-disable-line require-jsdoc + { + this._backgroundColor = value; + this._backgroundColorString = hex2string(value); + hex2rgb(value, this._backgroundColorRgba); + } +} diff --git a/packages/core/src/Renderer.js b/packages/core/src/Renderer.js new file mode 100644 index 0000000..2227e78 --- /dev/null +++ b/packages/core/src/Renderer.js @@ -0,0 +1,338 @@ +import AbstractRenderer from './AbstractRenderer'; +import { sayHello } from '@pixi/utils'; +import MaskSystem from './mask/MaskSystem'; +import StencilSystem from './mask/StencilSystem'; +import FilterSystem from './filters/FilterSystem'; +import FramebufferSystem from './framebuffer/FramebufferSystem'; +import RenderTextureSystem from './renderTexture/RenderTextureSystem'; +import TextureSystem from './textures/TextureSystem'; +import ProjectionSystem from './projection/ProjectionSystem'; +import StateSystem from './state/StateSystem'; +import GeometrySystem from './geometry/GeometrySystem'; +import ShaderSystem from './shader/ShaderSystem'; +import ContextSystem from './context/ContextSystem'; +import BatchSystem from './batch/BatchSystem'; +import TextureGCSystem from './textures/TextureGCSystem'; +import { RENDERER_TYPE } from '@pixi/constants'; +import UniformGroup from './shader/UniformGroup'; +import { Matrix } from '@pixi/math'; +import Runner from 'mini-runner'; + +/** + * 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 Render works by automatically managing webGLBatchs. + * 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 :) + * + * @class + * @memberof PIXI + * @extends PIXI.AbstractRenderer + */ +export default class Renderer extends AbstractRenderer +{ + // eslint-disable-next-line valid-jsdoc + /** + * + * @param {object} [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.transparent=false] - If the render view is transparent, default false + * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false + * @param {boolean} [options.antialias=false] - sets antialias. If not available natively then FXAA + * antialiasing is used + * @param {boolean} [options.forceFXAA=false] - forces FXAA antialiasing to be used over native. + * FXAA is faster, but may not always look as great + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. + * The resolution of the renderer retina would be 2. + * @param {boolean} [options.clearBeforeRender=true] - This sets if the renderer will clear + * the canvas or not before the new render pass. If you wish to set this to false, you *must* set + * preserveDrawingBuffer to `true`. + * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, + * enable this if you need to call toDataUrl on the webgl context. + * @param {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when + * rendering, stopping pixel interpolation. + * @param {number} [options.backgroundColor=0x000000] - The background color of the rendered area + * (shown if not transparent). + * @param {boolean} [options.legacy=false] - If true PixiJS will aim to ensure compatibility + * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. + * @param {string} [options.powerPreference] - Parameter passed to webgl context, set to "high-performance" + * for devices with dual graphics card + */ + constructor(options, arg2, arg3) + { + super('WebGL', options, arg2, arg3); + + /** + * The type of this renderer as a standardised const + * + * @member {number} + * @see PIXI.RENDERER_TYPE + */ + this.type = RENDERER_TYPE.WEBGL; + + // this will be set by the contextSystem (this.context) + this.gl = null; + this.CONTEXT_UID = 0; + this.legacy = !!options.legacy; + + // TODO legacy! + + // runners! + this.runners = { + destroy: new Runner('destroy'), + contextChange: new Runner('contextChange', 1), + reset: new Runner('reset'), + update: new Runner('update'), + postrender: new Runner('postrender'), + prerender: new Runner('prerender'), + resize: new Runner('resize', 2), + }; + + 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(GeometrySystem, 'geometry') + .addSystem(FramebufferSystem, 'framebuffer') + .addSystem(StencilSystem, 'stencil') + .addSystem(ProjectionSystem, 'projection') + .addSystem(TextureGCSystem, 'textureGC') + .addSystem(FilterSystem, 'filter') + .addSystem(RenderTextureSystem, 'renderTexture') + .addSystem(BatchSystem, 'batch'); + + this.initPlugins(Renderer.__plugins); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ + if (options.context) + { + this.context.initFromContext(options.context); + } + else + { + this.context.initFromOptions({ + alpha: this.transparent, + antialias: options.antialias, + premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', + stencil: true, + 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); + } + + /** + * Add a new system to the renderer. + * @param {class} ClassRef - Class reference + * @param {string} [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. + * @return {PIXI.Renderer} Return instance of renderer + */ + addSystem(ClassRef, name) + { + if (!name) + { + name = ClassRef.name; + } + + // TODO - read name from class.name.. + + /* + if(name.includes('System')) + { + name = name.replace('System', ''); + name = name.charAt(0).toLowerCase() + name.slice(1); + } + */ + + const system = new ClassRef(this); + + if (this[name]) + { + throw new Error(`Whoops! ${name} is already a manger`); + } + + this[name] = system; + + for (const i in this.runners) + { + this.runners[i].add(system); + } + + return this; + + /** + * 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. + */ + } + + /** + * Renders the object to its webGL view + * + * @param {PIXI.DisplayObject} displayObject - the object to be rendered + * @param {PIXI.RenderTexture} renderTexture - The render texture to render to. + * @param {boolean} [clear] - Should the canvas be cleared before the new render + * @param {PIXI.Transform} [transform] - A transform to apply to the render texture before rendering. + * @param {boolean} [skipUpdateTransform] - Should we skip the update transform pass? + */ + render(displayObject, renderTexture, clear, transform, skipUpdateTransform) + { + // can be handy to know! + this.renderingToScreen = !renderTexture; + + this.runners.prerender.run(); + this.emit('prerender'); + + // 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.parent; + + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = 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.run(); + + this.emit('postrender'); + } + + /** + * Resizes the webGL view to the specified width and height. + * + * @param {number} screenWidth - the new width of the screen + * @param {number} screenHeight - the new height of the screen + */ + resize(screenWidth, screenHeight) + { + AbstractRenderer.prototype.resize.call(this, screenWidth, screenHeight); + + this.runners.resize.run(screenWidth, screenHeight); + } + + /** + * Resets the WebGL state so you can render things however you fancy! + * + * @return {PIXI.Renderer} Returns itself. + */ + reset() + { + this.runners.reset.run(); + + return this; + } + + clear() + { + this.framebuffer.bind(); + this.framebuffer.clear(); + } + + /** + * Removes everything from the renderer (event listeners, spritebatch, etc...) + * + * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. + * See: https://github.com/pixijs/pixi.js/issues/2233 + */ + destroy(removeView) + { + this.runners.destroy.run(); + + // call base destroy + super.destroy(removeView); + + // TODO nullify all the managers.. + this.gl = null; + } + + /** + * 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 + * builds and excluding plugins. + * @name PIXI.Renderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. + */ + + /** + * Adds a plugin to the renderer. + * + * @method + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + static registerPlugin(pluginName, ctor) + { + Renderer.__plugins = Renderer.__plugins || {}; + Renderer.__plugins[pluginName] = ctor; + } +} diff --git a/packages/core/src/System.js b/packages/core/src/System.js new file mode 100644 index 0000000..05a10c8 --- /dev/null +++ b/packages/core/src/System.js @@ -0,0 +1,42 @@ +/** + * System is a base class used for extending systems used by the {@link PIXI.Renderer} + * @see PIXI.Renderer#addSystem + * @class + * @memberof PIXI + */ +export default class System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + /** + * The renderer this manager works for. + * + * @member {PIXI.Renderer} + */ + this.renderer = renderer; + + this.renderer.runners.contextChange.add(this); + } + + /** + * Generic method called when there is a WebGL context change. + * + */ + contextChange() + { + // do some codes init! + } + + /** + * Generic destroy methods to be overridden by the subclass + * + */ + destroy() + { + this.renderer.runners.contextChange.remove(this); + this.renderer = null; + } +} diff --git a/packages/core/src/batch/BatchSystem.js b/packages/core/src/batch/BatchSystem.js new file mode 100644 index 0000000..b04c249 --- /dev/null +++ b/packages/core/src/batch/BatchSystem.js @@ -0,0 +1,68 @@ +import System from '../System'; +import ObjectRenderer from './ObjectRenderer'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class BatchSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + super(renderer); + + /** + * An empty renderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.emptyRenderer = new ObjectRenderer(renderer); + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.currentRenderer = this.emptyRenderer; + } + + /** + * Changes the current renderer to the one given in parameter + * + * @param {PIXI.ObjectRenderer} objectRenderer - The object renderer to use. + */ + setObjectRenderer(objectRenderer) + { + if (this.currentRenderer === objectRenderer) + { + return; + } + + this.currentRenderer.stop(); + this.currentRenderer = objectRenderer; + + this.renderer.state.setState(objectRenderer.state); + + this.currentRenderer.start(); + } + + /** + * This should be called if you wish to do some custom rendering + * It will basically render anything that may be batched up such as sprites + * + */ + flush() + { + this.setObjectRenderer(this.emptyRenderer); + } + + reset() + { + this.setObjectRenderer(this.emptyRenderer); + } +} diff --git a/packages/core/src/batch/ObjectRenderer.js b/packages/core/src/batch/ObjectRenderer.js new file mode 100644 index 0000000..0c851a9 --- /dev/null +++ b/packages/core/src/batch/ObjectRenderer.js @@ -0,0 +1,48 @@ +import System from '../System'; + +/** + * Base for a common object renderer that can be used as a system renderer plugin. + * + * @class + * @extends PIXI.System + * @memberof PIXI + */ +export default class ObjectRenderer extends System +{ + /** + * Starts the renderer and sets the shader + * + */ + start() + { + // set the shader.. + } + + /** + * Stops the renderer + * + */ + stop() + { + this.flush(); + } + + /** + * Stub method for rendering content and emptying the current batch. + * + */ + flush() + { + // flush! + } + + /** + * Renders an object + * + * @param {PIXI.DisplayObject} object - The object to render. + */ + render(object) // eslint-disable-line no-unused-vars + { + // render the object + } +} diff --git a/packages/core/src/context/ContextSystem.js b/packages/core/src/context/ContextSystem.js new file mode 100644 index 0000000..301aab4 --- /dev/null +++ b/packages/core/src/context/ContextSystem.js @@ -0,0 +1,186 @@ +import System from '../System'; +import { settings } from '@pixi/settings'; + +let CONTEXT_UID = 0; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class ContextSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.webGLVersion = 1; + + this.handleContextLost = this.handleContextLost.bind(this); + this.handleContextRestored = this.handleContextRestored.bind(this); + + this.extensions = {}; + + renderer.view.addEventListener('webglcontextlost', this.handleContextLost, false); + renderer.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + } + + get isLost() + { + return (!this.gl || this.gl.isContextLost()); + } + + contextChange(gl) + { + this.gl = gl; + + // restore a context if it was previously lost + if (gl.isContextLost() && gl.getExtension('WEBGL_lose_context')) + { + gl.getExtension('WEBGL_lose_context').restoreContext(); + } + } + + initFromContext(gl) + { + this.gl = gl; + this.validateContext(gl); + this.renderer.gl = gl; + this.renderer.CONTEXT_UID = CONTEXT_UID++; + this.renderer.runners.contextChange.run(gl); + } + + initFromOptions(options) + { + const gl = this.createContext(this.renderer.view, options); + + this.initFromContext(gl); + } + + /** + * Helper class to create a webGL Context + * + * @class + * @memberof PIXI.glCore + * @param canvas {HTMLCanvasElement} the canvas element that we will get the context from + * @param options {Object} An options object that gets passed in to the canvas element containing the context attributes, + * see https://developer.mozilla.org/en/docs/Web/API/HTMLCanvasElement/getContext for the options available + * @return {WebGLRenderingContext} the WebGL context + */ + createContext(canvas, options) + { + let gl; + + if (settings.PREFER_WEBGL_2) + { + gl = canvas.getContext('webgl2', options); + } + + if (gl) + { + this.webGLVersion = 2; + } + else + { + this.webGLVersion = 1; + + gl = canvas.getContext('webgl', options) + || canvas.getContext('experimental-webgl', options); + + if (!gl) + { + // fail, not able to get a context + throw new Error('This browser does not support webGL. Try using the canvas renderer'); + } + } + + this.gl = gl; + + this.getExtensions(); + + return gl; + } + + getExtensions() + { + // time to set up default etensions that pixi uses.. + const gl = this.gl; + const extensions = this.extensions; + + if (this.webGLVersion === 1) + { + extensions.drawBuffers = gl.getExtension('WEBGL_draw_buffers'); + extensions.depthTexture = gl.getExtension('WEBKIT_WEBGL_depth_texture'); + extensions.floatTexture = gl.getExtension('OES_texture_float'); + extensions.loseContext = gl.getExtension('WEBGL_lose_context'); + + extensions.vertexArrayObject = gl.getExtension('OES_vertex_array_object') + || gl.getExtension('MOZ_OES_vertex_array_object') + || gl.getExtension('WEBKIT_OES_vertex_array_object'); + } + + // we don't use any specific WebGL 2 ones yet! + } + + /** + * Handles a lost webgl context + * + * @private + * @param {WebGLContextEvent} event - The context lost event. + */ + handleContextLost(event) + { + event.preventDefault(); + } + + /** + * Handles a restored webgl context + * + * @private + */ + handleContextRestored() + { + this.renderer.runners.contextChange.run(this.gl); + } + + destroy() + { + const view = this.renderer.view; + + // remove listeners + view.removeEventListener('webglcontextlost', this.handleContextLost); + view.removeEventListener('webglcontextrestored', this.handleContextRestored); + + this.gl.useProgram(null); + + if (this.extensions.loseContext) + { + this.extensions.loseContext.loseContext(); + } + } + + postrender() + { + this.gl.flush(); + } + + validateContext(gl) + { + const attributes = gl.getContextAttributes(); + + // this is going to be fairly simple for now.. but at least we have room to grow! + if (!attributes.stencil) + { + /* eslint-disable max-len */ + + /* eslint-disable no-console */ + console.warn('Provided WebGL context does not have a stencil buffer, masks may not render correctly'); + /* eslint-enable no-console */ + + /* eslint-enable max-len */ + } + } +} diff --git a/packages/core/src/filters/Filter.js b/packages/core/src/filters/Filter.js new file mode 100644 index 0000000..9d8cad8 --- /dev/null +++ b/packages/core/src/filters/Filter.js @@ -0,0 +1,140 @@ +import Shader from '../shader/Shader'; +import Program from '../shader/Program'; +import State from '../state/State'; +import { settings } from '@pixi/settings'; +// import extractUniformsFromSrc from './extractUniformsFromSrc'; +import defaultVertex from './defaultFilter.vert'; +import defaultFragment from './defaultFilter.frag'; + +// let math = require('../../math'); +/** + * @class + * @memberof PIXI + * @extends PIXI.Shader + */ +export default class Filter extends Shader +{ + /** + * @param {string} [vertexSrc] - The source of the vertex shader. + * @param {string} [fragmentSrc] - The source of the fragment shader. + * @param {object} [uniforms] - Custom uniforms to use to augment the built-in ones. + */ + constructor(vertexSrc, fragmentSrc, uniforms) + { + const program = Program.from(vertexSrc, fragmentSrc); + + super(program, uniforms); + + /** + * The padding of the filter. Some filters require extra space to breath such as a blur. + * Increasing this will add extra width and height to the bounds of the object that the + * filter is applied to. + * + * @member {number} + */ + this.padding = 0; + + /** + * The resolution of the filter. Setting this to be lower will lower the quality but + * increase the performance of the filter. + * + * @member {number} + */ + this.resolution = settings.RESOLUTION; + + /** + * If enabled is true the filter is applied, if false it will not. + * + * @member {boolean} + */ + this.enabled = true; + + /** + * If enabled, PixiJS will fit the filter area into boundaries for better performance. + * Switch it off if it does not work for specific shader. + * + * @member {boolean} + */ + this.autoFit = true; + + /** + * Legacy filters use position and uvs from attributes + * @member {boolean} + * @readonly + */ + this.legacy = !!this.program.attributeData.aTextureCoord; + + /** + * the webGL state the filter requires to render + * @member {PIXI.State} + */ + this.state = new State(); + } + + /** + * Applies the filter + * + * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + * @param {boolean} clear - Should the output be cleared before rendering to it + * @param {object} [currentState] - It's current state of filter. + * There are some useful properties in the currentState : + * target, filters, sourceFrame, destinationFrame, renderTarget, resolution + */ + apply(filterManager, input, output, clear, currentState, derp) // eslint-disable-line no-unused-vars + { + // do as you please! + + filterManager.applyFilter(this, input, output, clear, currentState, derp); + + // or just do a regular render.. + } + + /** + * Sets the blendmode of the filter + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL + */ + get blendMode() + { + return this.state.blendMode; + } + + set blendMode(value) // eslint-disable-line require-jsdoc + { + this.state.blendMode = value; + } + + /** + * The default vertex shader source + * + * @static + * @constant + */ + static get defaultVertexSrc() + { + return defaultVertex; + } + + /** + * The default fragment shader source + * + * @static + * @constant + */ + static get defaultFragmentSrc() + { + return defaultFragment; + } +} + +/** + * Used for caching shader IDs + * + * @static + * @private + */ +Filter.SOURCE_KEY_MAP = {}; + diff --git a/packages/core/src/filters/FilterSystem.js b/packages/core/src/filters/FilterSystem.js new file mode 100644 index 0000000..3174318 --- /dev/null +++ b/packages/core/src/filters/FilterSystem.js @@ -0,0 +1,431 @@ +import System from '../System'; + +import RenderTexture from '../renderTexture/RenderTexture'; +import Quad from '../utils/Quad'; +import QuadUv from '../utils/QuadUv'; +import { Rectangle } from '@pixi/math'; +import * as filterTransforms from './filterTransforms'; +import bitTwiddle from 'bit-twiddle'; +import UniformGroup from '../shader/UniformGroup'; +import { DRAW_MODES } from '@pixi/constants'; + +// +/** + * @ignore + * @class + */ +class FilterState +{ + /** + * + */ + constructor() + { + this.renderTexture = null; + this.sourceFrame = new Rectangle(); + this.destinationFrame = new Rectangle(); + this.filters = []; + this.target = null; + this.legacy = false; + this.resolution = 1; + } +} + +/** + * @class + * @memberof PIXI.systems + * @extends PIXI.System + */ +export default class FilterSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + /** + * stores a bunch of PO2 textures used for filtering + * @type {Object} + */ + this.texturePool = {}; + + /** + * a pool for storing filter states, save us creating new ones each tick + * @type {Array} + */ + this.statePool = []; + + /** + * A very simple geometry used when drawing a filter effect to the screen + * @type {Quad} + */ + this.quad = new Quad(); + + this.quadUv = new QuadUv(); + + /** + * Temporary rect for maths + * @type {PIXI.Rectangle} + */ + this.tempRect = new Rectangle(); + + this.activeState = {}; + + /** + * this uniform group is attached to filter uniforms when used + * @type {UniformGroup} + */ + this.globalUniforms = new UniformGroup({ + sourceFrame: this.tempRect, + destinationFrame: this.tempRect, + + // legacy variables + filterArea: new Float32Array(4), + filterClamp: new Float32Array(4), + }, true); + } + + /** + * Adds a new filter to the System. + * + * @param {PIXI.DisplayObject} target - The target of the filter to render. + * @param {PIXI.Filter[]} filters - The filters to apply. + */ + push(target, filters) + { + const renderer = this.renderer; + const filterStack = this.renderer.renderTexture.defaultFilterStack; + const state = this.statePool.pop() || new FilterState(); + + let resolution = filters[0].resolution; + let padding = filters[0].padding; + let autoFit = filters[0].autoFit; + let legacy = filters[0].legacy; + + for (let i = 1; i < filters.length; i++) + { + const filter = filters[i]; + + // lets use the lowest resolution.. + resolution = Math.min(resolution, filter.resolution); + // and the largest amount of padding! + padding = Math.max(padding, filter.padding); + // only auto fit if all filters are autofit + autoFit = autoFit || filter.autoFit; + + legacy = legacy || filter.legacy; + } + + filterStack.push(state); + + state.resolution = resolution; + + state.legacy = legacy; + + // round to whole number based on resolution + // TODO move that to the shader too? + state.sourceFrame = target.filterArea || target.getBounds(true); + + state.sourceFrame.pad(padding); + + if (autoFit) + { + state.sourceFrame.fit(this.renderer.renderTexture.destinationFrame); + } + + state.sourceFrame.round(resolution); + + state.renderTexture = this.getPotFilterTexture(state.sourceFrame.width, state.sourceFrame.height, resolution); + state.filters = filters; + + state.destinationFrame.width = state.renderTexture.width; + state.destinationFrame.height = state.renderTexture.height; + + state.renderTexture.filterFrame = state.sourceFrame; + renderer.renderTexture.bind(state.renderTexture, state.sourceFrame);// /, state.destinationFrame); + renderer.renderTexture.clear(); + } + + /** + * Pops off the filter and applies it. + * + */ + pop() + { + const renderer = this.renderer; + const filterStack = renderer.renderTexture.defaultFilterStack; + const state = filterStack.pop(); + const filters = state.filters; + + this.activeState = state; + + const globalUniforms = this.globalUniforms.uniforms; + + globalUniforms.sourceFrame = state.sourceFrame; + globalUniforms.destinationFrame = state.destinationFrame; + globalUniforms.resolution = state.resolution; + + // only update the rect if its legacy.. + if (state.legacy) + { + const filterArea = globalUniforms.filterArea; + const filterClamp = globalUniforms.filterClamp; + + filterArea[0] = state.destinationFrame.width; + filterArea[1] = state.destinationFrame.height; + filterArea[2] = state.sourceFrame.x; + filterArea[3] = state.sourceFrame.y; + + filterClamp[0] = 0.5 / state.resolution / state.destinationFrame.width; + filterClamp[1] = 0.5 / state.resolution / state.destinationFrame.height; + filterClamp[2] = (state.sourceFrame.width - 0.5) / state.resolution / state.destinationFrame.width; + filterClamp[3] = (state.sourceFrame.height - 0.5) / state.resolution / state.destinationFrame.height; + } + + this.globalUniforms.update(); + + const lastState = filterStack[filterStack.length - 1]; + + if (filters.length === 1) + { + filters[0].apply(this, state.renderTexture, lastState.renderTexture, false, state); + + this.returnFilterTexture(state.renderTexture); + } + else + { + let flip = state.renderTexture; + let flop = this.getPotFilterTexture( + flip.width, + flip.height, + state.resolution + ); + + flop.filterFrame = flip.filterFrame; + + let i = 0; + + for (i = 0; i < filters.length - 1; ++i) + { + filters[i].apply(this, flip, flop, true, state); + + const t = flip; + + flip = flop; + flop = t; + } + + filters[i].apply(this, flip, lastState.renderTexture, false, state); + + this.returnFilterTexture(flip); + this.returnFilterTexture(flop); + } + + this.statePool.push(state); + } + + /** + * Draws a filter. + * + * @param {PIXI.Filter} filter - The filter to draw. + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + * @param {boolean} clear - Should the output be cleared before rendering to it + */ + applyFilter(filter, input, output, clear) + { + const renderer = this.renderer; + + renderer.renderTexture.bind(output, output ? output.filterFrame : null); + + if (clear) + { + // gl.disable(gl.SCISSOR_TEST); + renderer.renderTexture.clear(); + // gl.enable(gl.SCISSOR_TEST); + } + + // set the uniforms.. + filter.uniforms.uSampler = input; + filter.uniforms.filterGlobals = this.globalUniforms; + + // TODO make it so that the order of this does not matter.. + // because it does at the moment cos of global uniforms. + // they need to get resynced + + renderer.state.setState(filter.state); + renderer.shader.bind(filter); + + if (filter.legacy) + { + this.quadUv.map(input._frame, input.filterFrame); + + renderer.geometry.bind(this.quadUv); + renderer.geometry.draw(DRAW_MODES.TRIANGLES); + } + else + { + renderer.geometry.bind(this.quad); + renderer.geometry.draw(DRAW_MODES.TRIANGLE_STRIP); + } + } + + /** + * Calculates the mapped matrix. + * + * TODO playing around here.. this is temporary - (will end up in the shader) + * this returns a matrix that will normalise map filter cords in the filter to screen space + * + * @param {PIXI.Matrix} outputMatrix - the matrix to output to. + * @return {PIXI.Matrix} The mapped matrix. + */ + calculateScreenSpaceMatrix(outputMatrix) + { + const currentState = this.activeState; + + return filterTransforms.calculateScreenSpaceMatrix( + outputMatrix, + currentState.sourceFrame, + currentState.destinationFrame + ); + } + + /** + * This will map the filter coord so that a texture can be used based on the transform of a sprite + * + * @param {PIXI.Matrix} outputMatrix - The matrix to output to. + * @param {PIXI.Sprite} sprite - The sprite to map to. + * @return {PIXI.Matrix} The mapped matrix. + */ + calculateSpriteMatrix(outputMatrix, sprite) + { + const currentState = this.activeState; + + return filterTransforms.calculateSpriteMatrix( + outputMatrix, + currentState.sourceFrame, + currentState.destinationFrame, + sprite + ); + } + + /** + * Destroys this Filter System. + * + * @param {boolean} [contextLost=false] context was lost, do not free shaders + * + */ + destroy(contextLost = false) + { + if (!contextLost) + { + this.emptyPool(); + } + else + { + this.texturePool = {}; + } + } + + /** + * Gets a Power-of-Two render texture. + * + * TODO move to a seperate class could be on renderer? + * also - could cause issue with multiple contexts? + * + * @private + * @param {WebGLRenderingContext} gl - The webgl rendering context + * @param {number} minWidth - The minimum width of the render target. + * @param {number} minHeight - The minimum height of the render target. + * @param {number} resolution - The resolution of the render target. + * @return {PIXI.RenderTarget} The new render target. + */ + getPotFilterTexture(minWidth, minHeight, resolution) + { + minWidth = bitTwiddle.nextPow2(minWidth); + minHeight = bitTwiddle.nextPow2(minHeight); + resolution = resolution || 1; + + const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + + if (!this.texturePool[key]) + { + this.texturePool[key] = []; + } + + let renderTexture = this.texturePool[key].pop(); + + if (!renderTexture) + { + // temporary bypass cache.. + // internally - this will cause a texture to be bound.. + renderTexture = RenderTexture.create({ + width: minWidth, + height: minHeight, + resolution, + }); + } + + return renderTexture; + } + + /** + * Gets extra render texture to use inside current filter + * + * @param {number} resolution resolution of the renderTexture + * @returns {PIXI.RenderTexture} + */ + getFilterTexture(resolution) + { + const rt = this.activeState.renderTexture; + + const filterTexture = this.getPotFilterTexture(rt.width, rt.height, resolution || rt.baseTexture.resolution); + + filterTexture.filterFrame = rt.filterFrame; + + return filterTexture; + } + + /** + * Frees a render texture back into the pool. + * + * @param {PIXI.RenderTarget} renderTexture - The renderTarget to free + */ + returnFilterTexture(renderTexture) + { + renderTexture.filterFrame = null; + + const base = renderTexture.baseTexture; + + const minWidth = base.width; + const minHeight = base.height; + + const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + + this.texturePool[key].push(renderTexture); + } + + /** + * Empties the texture pool. + * + */ + emptyPool() + { + for (const i in this.texturePool) + { + const textures = this.texturePool[i]; + + if (textures) + { + for (let j = 0; j < textures.length; j++) + { + textures[j].destroy(true); + } + } + } + + this.texturePool = {}; + } +} diff --git a/packages/core/src/filters/defaultFilter.frag b/packages/core/src/filters/defaultFilter.frag new file mode 100644 index 0000000..a14c1be --- /dev/null +++ b/packages/core/src/filters/defaultFilter.frag @@ -0,0 +1,22 @@ +varying vec2 vTextureCoord; +varying vec2 vFilterCoord; + +uniform sampler2D uSampler; +uniform sampler2D filterSampler; + +void main(void){ + vec4 masky = texture2D(filterSampler, vFilterCoord); + vec4 sample = texture2D(uSampler, vTextureCoord); + vec4 color; + if(mod(vFilterCoord.x, 1.0) > 0.5) + { + color = vec4(1.0, 0.0, 0.0, 1.0); + } + else + { + color = vec4(0.0, 1.0, 0.0, 1.0); + } + // gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0); + gl_FragColor = mix(sample, masky, 0.5); + gl_FragColor *= sample.a; +} diff --git a/packages/core/src/filters/defaultFilter.vert b/packages/core/src/filters/defaultFilter.vert new file mode 100644 index 0000000..e1febca --- /dev/null +++ b/packages/core/src/filters/defaultFilter.vert @@ -0,0 +1,14 @@ +attribute vec2 aVertexPosition; +attribute vec2 aTextureCoord; + +uniform mat3 projectionMatrix; +uniform mat3 filterMatrix; + +varying vec2 vTextureCoord; +varying vec2 vFilterCoord; + +void main(void){ + gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); + vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy; + vTextureCoord = aTextureCoord ; +} diff --git a/packages/core/src/filters/filterTransforms.js b/packages/core/src/filters/filterTransforms.js new file mode 100644 index 0000000..87a3cd6 --- /dev/null +++ b/packages/core/src/filters/filterTransforms.js @@ -0,0 +1,50 @@ +import { Matrix } from '@pixi/math'; + +/** + * Calculates the mapped matrix + * @param filterArea {Rectangle} The filter area + * @param sprite {Sprite} the target sprite + * @param outputMatrix {Matrix} @alvin + * @private + */ +// this returns a matrix that will normalise map filter cords in the filter to screen space +export function calculateScreenSpaceMatrix(outputMatrix, filterArea, textureSize) +{ + // TODO unwrap? + const mappedMatrix = outputMatrix.identity(); + + mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); + + mappedMatrix.scale(textureSize.width, textureSize.height); + + return mappedMatrix; +} + +export function calculateNormalizedScreenSpaceMatrix(outputMatrix, filterArea, textureSize) +{ + const mappedMatrix = outputMatrix.identity(); + + mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); + + const translateScaleX = (textureSize.width / filterArea.width); + const translateScaleY = (textureSize.height / filterArea.height); + + mappedMatrix.scale(translateScaleX, translateScaleY); + + return mappedMatrix; +} + +// this will map the filter coord so that a texture can be used based on the transform of a sprite +export function calculateSpriteMatrix(outputMatrix, filterArea, textureSize, sprite) +{ + const orig = sprite._texture.orig; + const mappedMatrix = outputMatrix.set(textureSize.width, 0, 0, textureSize.height, filterArea.x, filterArea.y); + const worldTransform = sprite.worldTransform.copyTo(Matrix.TEMP_MATRIX); + + worldTransform.invert(); + mappedMatrix.prepend(worldTransform); + mappedMatrix.scale(1.0 / orig.width, 1.0 / orig.height); + mappedMatrix.translate(sprite.anchor.x, sprite.anchor.y); + + return mappedMatrix; +} diff --git a/packages/core/src/filters/spriteMask/SpriteMaskFilter.js b/packages/core/src/filters/spriteMask/SpriteMaskFilter.js new file mode 100644 index 0000000..91697b7 --- /dev/null +++ b/packages/core/src/filters/spriteMask/SpriteMaskFilter.js @@ -0,0 +1,63 @@ +import Filter from '../Filter'; +import { Matrix } from '@pixi/math'; +import vertex from './spriteMaskFilter.vert'; +import fragment from './spriteMaskFilter.frag'; +import { default as TextureMatrix } from '../../textures/TextureMatrix'; + +/** + * The SpriteMaskFilter class + * + * @class + * @extends PIXI.Filter + * @memberof PIXI + */ +export default class SpriteMaskFilter extends Filter +{ + /** + * @param {PIXI.Sprite} sprite - the target sprite + */ + constructor(sprite) + { + const maskMatrix = new Matrix(); + + super(vertex, fragment); + + sprite.renderable = false; + + this.maskSprite = sprite; + this.maskMatrix = maskMatrix; + } + + /** + * Applies the filter + * + * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + */ + apply(filterManager, input, output) + { + const maskSprite = this.maskSprite; + const tex = this.maskSprite.texture; + + if (!tex.valid) + { + return; + } + if (!tex.transform) + { + // margin = 0.0, let it bleed a bit, shader code becomes easier + // assuming that atlas textures were made with 1-pixel padding + tex.transform = new TextureMatrix(tex, 0.0); + } + tex.transform.update(); + + this.uniforms.mask = tex; + this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite) + .prepend(tex.transform.mapCoord); + this.uniforms.alpha = maskSprite.worldAlpha; + this.uniforms.maskClamp = tex.transform.uClampFrame; + + filterManager.applyFilter(this, input, output); + } +} diff --git a/packages/core/src/filters/spriteMask/spriteMaskFilter.frag b/packages/core/src/filters/spriteMask/spriteMaskFilter.frag new file mode 100644 index 0000000..0e4aef8 --- /dev/null +++ b/packages/core/src/filters/spriteMask/spriteMaskFilter.frag @@ -0,0 +1,23 @@ +varying vec2 vMaskCoord; +varying vec2 vTextureCoord; + +uniform sampler2D uSampler; +uniform sampler2D mask; +uniform float alpha; +uniform vec4 maskClamp; + +void main(void) +{ + float clip = step(3.5, + step(maskClamp.x, vMaskCoord.x) + + step(maskClamp.y, vMaskCoord.y) + + step(vMaskCoord.x, maskClamp.z) + + step(vMaskCoord.y, maskClamp.w)); + + vec4 original = texture2D(uSampler, vTextureCoord); + vec4 masky = texture2D(mask, vMaskCoord); + + original *= (masky.r * masky.a * alpha * clip); + + gl_FragColor = original; +} diff --git a/packages/core/src/filters/spriteMask/spriteMaskFilter.vert b/packages/core/src/filters/spriteMask/spriteMaskFilter.vert new file mode 100644 index 0000000..d23a30c --- /dev/null +++ b/packages/core/src/filters/spriteMask/spriteMaskFilter.vert @@ -0,0 +1,16 @@ +attribute vec2 aVertexPosition; +attribute vec2 aTextureCoord; + +uniform mat3 projectionMatrix; +uniform mat3 otherMatrix; + +varying vec2 vMaskCoord; +varying vec2 vTextureCoord; + +void main(void) +{ + gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); + + vTextureCoord = aTextureCoord; + vMaskCoord = ( otherMatrix * vec3( aTextureCoord, 1.0) ).xy; +} diff --git a/packages/core/src/framebuffer/FrameBuffer.js b/packages/core/src/framebuffer/FrameBuffer.js new file mode 100644 index 0000000..84e9fcc --- /dev/null +++ b/packages/core/src/framebuffer/FrameBuffer.js @@ -0,0 +1,106 @@ +import Texture from '../textures/BaseTexture'; +import { FORMATS, TYPES } from '@pixi/constants'; + +/** + * Frame buffer + * @class + * @memberof PIXI + */ +export default class FrameBuffer +{ + constructor(width, height) + { + this.width = width || 100; + this.height = height || 100; + + this.stencil = false; + this.depth = false; + + this.dirtyId = 0; + this.dirtyFormat = 0; + this.dirtySize = 0; + + this.depthTexture = null; + this.colorTextures = []; + + this.glFrameBuffers = {}; + } + + get colorTexture() + { + return this.colorTextures[0]; + } + + addColorTexture(index, texture) + { + // TODO add some validation to the texture - same width / height etc? + this.colorTextures[index || 0] = texture || new Texture(null, { scaleMode: 0, + resolution: 1, + mipmap: false, + width: this.width, + height: this.height });// || new Texture(); + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + addDepthTexture(texture) + { + /* eslint-disable max-len */ + this.depthTexture = texture || new Texture(null, { scaleMode: 0, + resolution: 1, + width: this.width, + height: this.height, + mipmap: false, + format: FORMATS.DEPTH_COMPONENT, + type: TYPES.UNSIGNED_SHORT });// UNSIGNED_SHORT; + /* eslint-disable max-len */ + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + enableDepth() + { + this.depth = true; + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + enableStencil() + { + this.stencil = true; + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + resize(width, height) + { + if (width === this.width && height === this.height) return; + + this.width = width; + this.height = height; + + this.dirtyId++; + this.dirtySize++; + + for (let i = 0; i < this.colorTextures.length; i++) + { + this.colorTextures[i].setSize(width, height); + } + + if (this.depthTexture) + { + this.depthTexture.setSize(width, height); + } + } +} diff --git a/packages/core/src/framebuffer/FramebufferSystem.js b/packages/core/src/framebuffer/FramebufferSystem.js new file mode 100644 index 0000000..2cc2437 --- /dev/null +++ b/packages/core/src/framebuffer/FramebufferSystem.js @@ -0,0 +1,246 @@ +import System from '../System'; +import { Rectangle } from '@pixi/math'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class FramebufferSystem extends System +{ + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + contextChange() + { + this.gl = this.renderer.gl; + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + this.current = null; + this.viewport = new Rectangle(); + + this.drawBufferExtension = this.renderer.context.extensions.drawBuffers; + } + + bind(framebuffer, frame) + { + const gl = this.gl; + + this.current = framebuffer; + + if (framebuffer) + { + // TODO cacheing layer! + + const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID] || this.initFramebuffer(framebuffer); + + gl.bindFramebuffer(gl.FRAMEBUFFER, fbo.framebuffer); + // makesure all textures are unbound.. + + // now check for updates... + if (fbo.dirtyId !== framebuffer.dirtyId) + { + fbo.dirtyId = framebuffer.dirtyId; + + if (fbo.dirtyFormat !== framebuffer.dirtyFormat) + { + fbo.dirtyFormat = framebuffer.dirtyFormat; + this.updateFramebuffer(framebuffer); + } + else if (fbo.dirtySize !== framebuffer.dirtySize) + { + fbo.dirtySize = framebuffer.dirtySize; + this.resizeFramebuffer(framebuffer); + } + } + + for (let i = 0; i < framebuffer.colorTextures.length; i++) + { + if (framebuffer.colorTextures[i].texturePart) + { + this.renderer.texture.unbind(framebuffer.colorTextures[i].texture); + } + else + { + this.renderer.texture.unbind(framebuffer.colorTextures[i]); + } + } + + if (framebuffer.depthTexture) + { + this.renderer.texture.unbind(framebuffer.depthTexture); + } + + if (frame) + { + this.setViewport(frame.x, frame.y, frame.width, frame.height); + } + else + { + this.setViewport(0, 0, framebuffer.width, framebuffer.height); + } + } + else + { + gl.bindFramebuffer(gl.FRAMEBUFFER, null); + + if (frame) + { + this.setViewport(frame.x, frame.y, frame.width, frame.height); + } + else + { + this.setViewport(0, 0, this.renderer.width, this.renderer.height); + } + } + } + + setViewport(x, y, width, height) + { + const v = this.viewport; + + if (v.width !== width || v.height !== height || v.x !== x || v.y !== y) + { + v.x = x; + v.y = y; + v.width = width; + v.height = height; + + this.gl.viewport(x, y, width, height); + } + } + + get size() + { + if (this.current) + { + // TODO store temp + return { x: 0, y: 0, width: this.current.width, height: this.current.height }; + } + + return { x: 0, y: 0, width: this.renderer.width, height: this.renderer.height }; + } + + clear(r, g, b, a) + { + const gl = this.gl; + + // TODO clear color can be set only one right? + gl.clearColor(r, g, b, a); + gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); + } + + // private functions... + + initFramebuffer(framebuffer) + { + const gl = this.gl; + + // TODO - make this a class? + const fbo = { + framebuffer: gl.createFramebuffer(), + stencil: null, + dirtyId: 0, + dirtyFormat: 0, + dirtySize: 0, + }; + + framebuffer.glFrameBuffers[this.CONTEXT_UID] = fbo; + + return fbo; + } + + resizeFramebuffer(framebuffer) + { + const gl = this.gl; + + if (framebuffer.stencil || framebuffer.depth) + { + gl.bindRenderbuffer(gl.RENDERBUFFER, this.stencil); + gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); + } + } + + updateFramebuffer(framebuffer) + { + const gl = this.gl; + + const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID]; + + // bind the color texture + const colorTextures = framebuffer.colorTextures; + + let count = colorTextures.length; + + if (!this.drawBufferExtension) + { + count = Math.min(count, 1); + } + + const activeTextures = []; + + for (let i = 0; i < count; i++) + { + const texture = framebuffer.colorTextures[i]; + + if (texture.texturePart) + { + this.renderer.texture.bind(texture.texture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.COLOR_ATTACHMENT0 + i, + gl.TEXTURE_CUBE_MAP_NEGATIVE_X + texture.side, + texture.texture._glTextures[this.CONTEXT_UID].texture, + 0); + } + else + { + this.renderer.texture.bind(texture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.COLOR_ATTACHMENT0 + i, + gl.TEXTURE_2D, + texture._glTextures[this.CONTEXT_UID].texture, + 0); + } + + activeTextures.push(gl.COLOR_ATTACHMENT0 + i); + } + + if (this.drawBufferExtension && activeTextures.length > 1) + { + this.drawBufferExtension.drawBuffersWEBGL(activeTextures); + } + + if (framebuffer.depthTexture) + { + const depthTextureExt = this.renderer.context.extensions.depthTexture; + + if (depthTextureExt) + { + const depthTexture = framebuffer.depthTexture; + + this.renderer.texture.bind(depthTexture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.DEPTH_ATTACHMENT, + gl.TEXTURE_2D, + depthTexture._glTextures[this.CONTEXT_UID].texture, + 0); + } + } + + if (framebuffer.stencil || framebuffer.depth) + { + fbo.stencil = gl.createRenderbuffer(); + + gl.bindRenderbuffer(gl.RENDERBUFFER, fbo.stencil); + + // TODO.. this is depth AND stencil? + gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, fbo.stencil); + gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); + // fbo.enableStencil(); + } + } +} diff --git a/packages/core/src/geometry/GLBuffer.js b/packages/core/src/geometry/GLBuffer.js new file mode 100644 index 0000000..0477e8a --- /dev/null +++ b/packages/core/src/geometry/GLBuffer.js @@ -0,0 +1,9 @@ +export default class GLBuffer +{ + constructor(buffer) + { + this.buffer = buffer; + this.updateID = -1; + this.byteLength = -1; + } +} diff --git a/packages/core/src/geometry/GeometrySystem.js b/packages/core/src/geometry/GeometrySystem.js new file mode 100644 index 0000000..959f827 --- /dev/null +++ b/packages/core/src/geometry/GeometrySystem.js @@ -0,0 +1,388 @@ +import System from '../System'; +import GLBuffer from './GLBuffer'; + +const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class GeometrySystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this._activeGeometry = null; + this._activeVao = null; + + this.hasVao = true; + this.hasInstance = true; + } + + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + contextChange() + { + const gl = this.gl = this.renderer.gl; + + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + + // webgl2 + if (!gl.createVertexArray) + { + // webgl 1! + let nativeVaoExtension = this.renderer.context.extensions.vertexArrayObject; + + if (this.renderer.legacy) + { + nativeVaoExtension = null; + } + + if (nativeVaoExtension) + { + gl.createVertexArray = () => + nativeVaoExtension.createVertexArrayOES(); + + gl.bindVertexArray = (vao) => + nativeVaoExtension.bindVertexArrayOES(vao); + + gl.deleteVertexArray = (vao) => + nativeVaoExtension.deleteVertexArrayOES(vao); + } + else + { + this.hasVao = false; + gl.createVertexArray = () => + { + // empty + }; + + gl.bindVertexArray = () => + { + // empty + }; + + gl.deleteVertexArray = () => + { + // empty + }; + } + } + + if (!gl.vertexAttribDivisor) + { + const instanceExt = gl.getExtension('ANGLE_instanced_arrays'); + + if (instanceExt) + { + gl.vertexAttribDivisor = (a, b) => + instanceExt.vertexAttribDivisorANGLE(a, b); + + gl.drawElementsInstanced = (a, b, c, d, e) => + instanceExt.drawElementsInstancedANGLE(a, b, c, d, e); + + gl.drawArraysInstanced = (a, b, c, d) => + instanceExt.drawArraysInstancedANGLE(a, b, c, d); + } + else + { + this.hasInstance = false; + } + } + } + + /** + * Binds geometry so that is can be drawn. Creating a Vao if required + * @private + * @param {PIXI.Geometry} geometry instance of geometry to bind + */ + bind(geometry, shader) + { + shader = shader || this.renderer.shader.shader; + + const gl = this.gl; + + // not sure the best way to address this.. + // currently different shaders require different VAOs for the same geometry + // Still mulling over the best way to solve this one.. + // will likely need to modify the shader attribute locations at run time! + let vaos = geometry.glVertexArrayObjects[this.CONTEXT_UID]; + + if (!vaos) + { + geometry.glVertexArrayObjects[this.CONTEXT_UID] = vaos = {}; + } + + const vao = vaos[shader.program.id] || this.initGeometryVao(geometry, shader.program); + + this._activeGeometry = geometry; + + if (this._activeVao !== vao) + { + this._activeVao = vao; + + if (this.hasVao) + { + gl.bindVertexArray(vao); + } + else + { + this.activateVao(geometry, shader.program); + } + } + + // TODO - optimise later! + // don't need to loop through if nothing changed! + // maybe look to add an 'autoupdate' to geometry? + this.updateBuffers(); + } + + reset() + { + this.unbind(); + } + + updateBuffers() + { + const geometry = this._activeGeometry; + const gl = this.gl; + + for (let i = 0; i < geometry.buffers.length; i++) + { + const buffer = geometry.buffers[i]; + + const glBuffer = buffer._glBuffers[this.CONTEXT_UID]; + + if (buffer._updateID !== glBuffer.updateID) + { + glBuffer.updateID = buffer._updateID; + + // TODO can cache this on buffer! maybe added a getter / setter? + const type = buffer.index ? gl.ELEMENT_ARRAY_BUFFER : gl.ARRAY_BUFFER; + const drawType = buffer.static ? gl.STATIC_DRAW : gl.DYNAMIC_DRAW; + + gl.bindBuffer(type, glBuffer.buffer); + + if (glBuffer.byteLength >= buffer.data.byteLength) + { + // offset is always zero for now! + gl.bufferSubData(type, 0, buffer.data); + } + else + { + gl.bufferData(type, buffer.data, drawType); + } + } + } + } + + checkCompatability(geometry, program) + { + // geometry must have at least all the attributes that the shader requires. + const geometryAttributes = geometry.attributes; + const shaderAttributes = program.attributeData; + + for (const j in shaderAttributes) + { + if (!geometryAttributes[j]) + { + throw new Error(`shader and geometry incompatible, geometry missing the "${j}" attribute`); + } + } + } + + /** + * Creates a Vao with the same structure as the geometry and stores it on the geometry. + * @private + * @param {PIXI.Geometry} geometry instance of geometry to to generate Vao for + */ + initGeometryVao(geometry, program) + { + this.checkCompatability(geometry, program); + + const gl = this.gl; + const CONTEXT_UID = this.CONTEXT_UID; + const buffers = geometry.buffers; + const attributes = geometry.attributes; + + const tempStride = {}; + const tempStart = {}; + + for (const j in buffers) + { + tempStride[j] = 0; + tempStart[j] = 0; + } + + for (const j in attributes) + { + if (!attributes[j].size && program.attributeData[j]) + { + attributes[j].size = program.attributeData[j].size; + } + + tempStride[attributes[j].buffer] += attributes[j].size * byteSizeMap[attributes[j].type]; + } + + for (const j in attributes) + { + const attribute = attributes[j]; + const attribSize = attribute.size; + + if (attribute.stride === undefined) + { + if (tempStride[attribute.buffer] === attribSize * byteSizeMap[attribute.type]) + { + attribute.stride = 0; + } + else + { + attribute.stride = tempStride[attribute.buffer]; + } + } + + if (attribute.start === undefined) + { + attribute.start = tempStart[attribute.buffer]; + + tempStart[attribute.buffer] += attribSize * byteSizeMap[attribute.type]; + } + } + + // first update - and create the buffers! + // only create a gl buffer if it actually gets + for (let i = 0; i < buffers.length; i++) + { + const buffer = buffers[i]; + + if (!buffer._glBuffers[CONTEXT_UID]) + { + buffer._glBuffers[CONTEXT_UID] = new GLBuffer(gl.createBuffer()); + } + } + + // TODO - maybe make this a data object? + // lets wait to see if we need to first! + const vao = gl.createVertexArray(); + + gl.bindVertexArray(vao); + + this.activateVao(geometry, program); + + gl.bindVertexArray(null); + + // add it to the cache! + geometry.glVertexArrayObjects[this.CONTEXT_UID][program.id] = vao; + + return vao; + } + + activateVao(geometry, program) + { + const gl = this.gl; + const CONTEXT_UID = this.CONTEXT_UID; + const buffers = geometry.buffers; + const attributes = geometry.attributes; + + if (geometry.indexBuffer) + { + // first update the index buffer if we have one.. + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, geometry.indexBuffer._glBuffers[CONTEXT_UID].buffer); + } + + let lastBuffer = null; + + // add a new one! + for (const j in attributes) + { + const attribute = attributes[j]; + const buffer = buffers[attribute.buffer]; + const glBuffer = buffer._glBuffers[CONTEXT_UID]; + + if (program.attributeData[j]) + { + if (lastBuffer !== glBuffer) + { + gl.bindBuffer(gl.ARRAY_BUFFER, glBuffer.buffer); + + lastBuffer = glBuffer; + } + + const location = program.attributeData[j].location; + + // TODO introduce state again + // we can optimise this for older devices that have no VAOs + gl.enableVertexAttribArray(location); + + gl.vertexAttribPointer(location, + attribute.size, + attribute.type || gl.FLOAT, + attribute.normalized, + attribute.stride, + attribute.start); + + if (attribute.instance) + { + // TODO calculate instance count based of this... + if (this.hasInstance) + { + gl.vertexAttribDivisor(location, 1); + } + else + { + throw new Error('geometry error, GPU Instancing is not supported on this device'); + } + } + } + } + } + + draw(type, size, start, instanceCount) + { + const gl = this.gl; + const geometry = this._activeGeometry; + + // TODO.. this should not change so maybe cache the function? + + if (geometry.indexBuffer) + { + if (geometry.instanced) + { + /* eslint-disable max-len */ + gl.drawElementsInstanced(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2, instanceCount || 1); + /* eslint-enable max-len */ + } + else + { + gl.drawElements(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2); + } + } + else + if (geometry.instanced) + { + // TODO need a better way to calculate size.. + gl.drawArraysInstanced(type, start, size || geometry.getSize(), instanceCount || 1); + } + else + { + gl.drawArrays(type, start, size || geometry.getSize()); + } + + return this; + } + + unbind() + { + this.gl.bindVertexArray(null); + this._activeVao = null; + this._activeGeometry = null; + } +} diff --git a/packages/core/src/geometry/utils/setVertexAttribArrays.js b/packages/core/src/geometry/utils/setVertexAttribArrays.js new file mode 100644 index 0000000..23b6959 --- /dev/null +++ b/packages/core/src/geometry/utils/setVertexAttribArrays.js @@ -0,0 +1,54 @@ +// var GL_MAP = {}; + +/** + * @param gl {WebGLRenderingContext} The current WebGL context + * @param attribs {*} + * @param state {*} + */ +export default function setVertexAttribArrays(gl, attribs, state) +{ + let i; + + if (state) + { + const tempAttribState = state.tempAttribState; + const attribState = state.attribState; + + for (i = 0; i < tempAttribState.length; i++) + { + tempAttribState[i] = false; + } + + // set the new attribs + for (i = 0; i < attribs.length; i++) + { + tempAttribState[attribs[i].attribute.location] = true; + } + + for (i = 0; i < attribState.length; i++) + { + if (attribState[i] !== tempAttribState[i]) + { + attribState[i] = tempAttribState[i]; + + if (state.attribState[i]) + { + gl.enableVertexAttribArray(i); + } + else + { + gl.disableVertexAttribArray(i); + } + } + } + } + else + { + for (i = 0; i < attribs.length; i++) + { + const attrib = attribs[i]; + + gl.enableVertexAttribArray(attrib.attribute.location); + } + } +} diff --git a/packages/core/src/index.js b/packages/core/src/index.js index 58c456a..fd1f547 100644 --- a/packages/core/src/index.js +++ b/packages/core/src/index.js @@ -1,30 +1,30 @@ import * as resources from './textures/resources'; -import * as systems from './renderers/systems'; +import * as systems from './systems'; -export { resources }; export { systems }; +export { resources }; -export { default as Renderer } from './renderers/Renderer'; -export { default as AbstractRenderer } from './renderers/AbstractRenderer'; -export { default as FrameBuffer } from './textures/FrameBuffer'; +export { default as System } from './System'; +export { default as Renderer } from './Renderer'; +export { default as AbstractRenderer } from './AbstractRenderer'; +export { default as FrameBuffer } from './framebuffer/FrameBuffer'; export { default as CubeTexture } from './textures/CubeTexture'; export { default as BaseTexture } from './textures/BaseTexture'; export { default as Texture } from './textures/Texture'; export { default as TextureMatrix } from './textures/TextureMatrix'; -export { default as RenderTexture } from './textures/RenderTexture'; -export { default as BaseRenderTexture } from './textures/BaseRenderTexture'; +export { default as RenderTexture } from './renderTexture/RenderTexture'; +export { default as BaseRenderTexture } from './renderTexture/BaseRenderTexture'; export { default as TextureUvs } from './textures/TextureUvs'; -export { default as State } from './renderers/State'; -export { default as ObjectRenderer } from './renderers/utils/ObjectRenderer'; -export { default as Quad } from './renderers/utils/Quad'; -export { default as QuadUv } from './renderers/utils/QuadUv'; -export { default as checkMaxIfStatmentsInShader } from './renderers/utils/checkMaxIfStatmentsInShader'; +export { default as State } from './state/State'; +export { default as ObjectRenderer } from './batch/ObjectRenderer'; +export { default as Quad } from './utils/Quad'; +export { default as QuadUv } from './utils/QuadUv'; +export { default as checkMaxIfStatmentsInShader } from './shader/utils/checkMaxIfStatmentsInShader'; export { default as Shader } from './shader/Shader'; export { default as Program } from './shader/Program'; export { default as UniformGroup } from './shader/UniformGroup'; -export { default as SpriteMaskFilter } from './renderers/filters/spriteMask/SpriteMaskFilter'; -export { default as Filter } from './renderers/filters/Filter'; +export { default as SpriteMaskFilter } from './filters/spriteMask/SpriteMaskFilter'; +export { default as Filter } from './filters/Filter'; export { default as Attribute } from './geometry/Attribute'; export { default as Buffer } from './geometry/Buffer'; export { default as Geometry } from './geometry/Geometry'; - diff --git a/packages/core/src/mask/MaskSystem.js b/packages/core/src/mask/MaskSystem.js new file mode 100644 index 0000000..99174c2 --- /dev/null +++ b/packages/core/src/mask/MaskSystem.js @@ -0,0 +1,199 @@ +import System from '../System'; +import AlphaMaskFilter from '../filters/spriteMask/SpriteMaskFilter'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class MaskSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + // TODO - we don't need both! + this.scissor = false; + this.scissorData = null; + this.scissorRenderTarget = null; + + this.enableScissor = false; + + this.alphaMaskPool = []; + this.alphaMaskIndex = 0; + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.DisplayObject} target - Display Object to push the mask to + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + push(target, maskData) + { + // TODO the root check means scissor rect will not + // be used on render textures more info here: + // https://github.com/pixijs/pixi.js/pull/3545 + + if (maskData.texture) + { + this.pushSpriteMask(target, maskData); + } + else if (this.enableScissor + && !this.scissor + && this.renderer._activeRenderTarget.root + && !this.renderer.stencil.stencilMaskStack.length + && maskData.isFastRect()) + { + const matrix = maskData.worldTransform; + + let rot = Math.atan2(matrix.b, matrix.a); + + // use the nearest degree! + rot = Math.round(rot * (180 / Math.PI)); + + if (rot % 90) + { + this.pushStencilMask(maskData); + } + else + { + this.pushScissorMask(target, maskData); + } + } + else + { + this.pushStencilMask(maskData); + } + } + + /** + * Removes the last mask from the mask stack and doesn't return it. + * + * @param {PIXI.DisplayObject} target - Display Object to pop the mask from + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + pop(target, maskData) + { + if (maskData.texture) + { + this.popSpriteMask(target, maskData); + } + else if (this.enableScissor && !this.renderer.stencil.stencilMaskStack.length) + { + this.popScissorMask(target, maskData); + } + else + { + this.popStencilMask(target, maskData); + } + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.RenderTarget} target - Display Object to push the sprite mask to + * @param {PIXI.Sprite} maskData - Sprite to be used as the mask + */ + pushSpriteMask(target, maskData) + { + let alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; + + if (!alphaMaskFilter) + { + alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; + } + + alphaMaskFilter[0].resolution = this.renderer.resolution; + alphaMaskFilter[0].maskSprite = maskData; + + // TODO - may cause issues! + target.filterArea = maskData.getBounds(true); + + this.renderer.filterSystem.pushFilter(target, alphaMaskFilter); + + this.alphaMaskIndex++; + } + + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popSpriteMask() + { + this.renderer.filterSystem.popFilter(); + this.alphaMaskIndex--; + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + pushStencilMask(maskData) + { + this.renderer.batch.flush(); + this.renderer.stencil.pushStencil(maskData); + } + + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popStencilMask() + { + // this.renderer.currentRenderer.stop(); + this.renderer.stencil.popStencil(); + } + + /** + * + * @param {PIXI.DisplayObject} target - Display Object to push the mask to + * @param {PIXI.Graphics} maskData - The masking data. + */ + pushScissorMask(target, maskData) + { + maskData.renderable = true; + + const renderTarget = this.renderer._activeRenderTarget; + + const bounds = maskData.getBounds(); + + bounds.fit(renderTarget.size); + maskData.renderable = false; + + this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); + + const resolution = this.renderer.resolution; + + this.renderer.gl.scissor( + bounds.x * resolution, + (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, + bounds.width * resolution, + bounds.height * resolution + ); + + this.scissorRenderTarget = renderTarget; + this.scissorData = maskData; + this.scissor = true; + } + + /** + * + * + */ + popScissorMask() + { + this.scissorRenderTarget = null; + this.scissorData = null; + this.scissor = false; + + // must be scissor! + const gl = this.renderer.gl; + + gl.disable(gl.SCISSOR_TEST); + } +} diff --git a/packages/core/src/mask/StencilSystem.js b/packages/core/src/mask/StencilSystem.js new file mode 100644 index 0000000..62796a5 --- /dev/null +++ b/packages/core/src/mask/StencilSystem.js @@ -0,0 +1,133 @@ +import System from '../System'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class StencilSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + this.stencilMaskStack = []; + } + + /** + * Changes the mask stack that is used by this System. + * + * @param {PIXI.Graphics[]} stencilMaskStack - The mask stack + */ + setMaskStack(stencilMaskStack) + { + const gl = this.renderer.gl; + + if (stencilMaskStack.length !== this.stencilMaskStack.length) + { + if (stencilMaskStack.length === 0) + { + gl.disable(gl.STENCIL_TEST); + } + else + { + gl.enable(gl.STENCIL_TEST); + } + } + + this.stencilMaskStack = stencilMaskStack; + } + + /** + * Applies the Mask and adds it to the current stencil stack. @alvin + * + * @param {PIXI.Graphics} graphics - The mask + */ + pushStencil(graphics) + { + this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); + + // this.renderer._activeRenderTarget.attachStencilBuffer(); + + const gl = this.renderer.gl; + const prevMaskCount = this.stencilMaskStack.length; + + if (prevMaskCount === 0) + { + gl.enable(gl.STENCIL_TEST); + } + + this.stencilMaskStack.push(graphics); + + // Increment the refference stencil value where the new mask overlaps with the old ones. + gl.colorMask(false, false, false, false); + gl.stencilFunc(gl.EQUAL, prevMaskCount, this._getBitwiseMask()); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.INCR); + this.renderer.plugins.graphics.render(graphics); + + this._useCurrent(); + } + + /** + * Removes the last mask from the stencil stack. @alvin + */ + popStencil() + { + this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); + + const gl = this.renderer.gl; + const graphics = this.stencilMaskStack.pop(); + + if (this.stencilMaskStack.length === 0) + { + // the stack is empty! + gl.disable(gl.STENCIL_TEST); + gl.clear(gl.STENCIL_BUFFER_BIT); + gl.clearStencil(0); + } + else + { + // Decrement the refference stencil value where the popped mask overlaps with the other ones + gl.colorMask(false, false, false, false); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.DECR); + this.renderer.plugins.graphics.render(graphics); + + this._useCurrent(); + } + } + + /** + * Setup renderer to use the current stencil data. + */ + _useCurrent() + { + const gl = this.renderer.gl; + + gl.colorMask(true, true, true, true); + gl.stencilFunc(gl.EQUAL, this.stencilMaskStack.length, this._getBitwiseMask()); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP); + } + + /** + * Fill 1s equal to the number of acitve stencil masks. + * + * @return {number} The bitwise mask. + */ + _getBitwiseMask() + { + return (1 << this.stencilMaskStack.length) - 1; + } + + /** + * Destroys the mask stack. + * + */ + destroy() + { + super.destroy(this); + + this.stencilMaskStack = null; + } +} diff --git a/packages/core/src/projection/ProjectionSystem.js b/packages/core/src/projection/ProjectionSystem.js new file mode 100644 index 0000000..6e63b73 --- /dev/null +++ b/packages/core/src/projection/ProjectionSystem.js @@ -0,0 +1,73 @@ +import System from '../System'; +import { Matrix } from '@pixi/math'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class ProjectionSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.projectionMatrix = new Matrix(); + } + + update(destinationFrame, sourceFrame, resolution, root) + { + this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; + this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; + + this.calculateProjection(this.destinationFrame, this.sourceFrame, resolution, root); + + this.renderer.globalUniforms.uniforms.projectionMatrix = this.projectionMatrix; + this.renderer.globalUniforms.update(); + } + + /** + * Updates the projection matrix based on a projection frame (which is a rectangle) + * + * @param {Rectangle} destinationFrame - The destination frame. + * @param {Rectangle} sourceFrame - The source frame. + */ + calculateProjection(destinationFrame, sourceFrame, resolution, root) + { + const pm = this.projectionMatrix; + + // I don't think we will need this line.. + // pm.identity(); + + if (!root) + { + pm.a = (1 / destinationFrame.width * 2) * resolution; + pm.d = (1 / destinationFrame.height * 2) * resolution; + + pm.tx = -1 - (sourceFrame.x * pm.a); + pm.ty = -1 - (sourceFrame.y * pm.d); + } + else + { + pm.a = (1 / destinationFrame.width * 2) * resolution; + pm.d = (-1 / destinationFrame.height * 2) * resolution; + + pm.tx = -1 - (sourceFrame.x * pm.a); + pm.ty = 1 - (sourceFrame.y * pm.d); + } + } + + /** + * Sets the transform of the active render target to the given matrix + * + * @param {PIXI.Matrix} matrix - The transformation matrix + */ + setTransform()// matrix) + { + // this._activeRenderTarget.transform = matrix; + } +} diff --git a/packages/core/src/renderTexture/BaseRenderTexture.js b/packages/core/src/renderTexture/BaseRenderTexture.js new file mode 100644 index 0000000..88eb83b --- /dev/null +++ b/packages/core/src/renderTexture/BaseRenderTexture.js @@ -0,0 +1,137 @@ +import BaseTexture from '../textures/BaseTexture'; +import FrameBuffer from '../framebuffer/FrameBuffer'; + +/** + * A BaseRenderTexture is a special texture that allows any PixiJS display object to be rendered to it. + * + * __Hint__: All DisplayObjects (i.e. Sprites) that render to a BaseRenderTexture should be preloaded + * otherwise black rectangles will be drawn instead. + * + * A BaseRenderTexture takes a snapshot of any Display Object given to its render method. The position + * and rotation of the given Display Objects is ignored. For example: + * + * ```js + * let renderer = PIXI.autoDetectRenderer(1024, 1024, { view: canvas, ratio: 1 }); + * let baseRenderTexture = new PIXI.BaseRenderTexture(renderer, 800, 600); + * let sprite = PIXI.Sprite.fromImage("spinObj_01.png"); + * + * sprite.position.x = 800/2; + * sprite.position.y = 600/2; + * sprite.anchor.x = 0.5; + * sprite.anchor.y = 0.5; + * + * baseRenderTexture.render(sprite); + * ``` + * + * The Sprite in this case will be rendered using its local transform. To render this sprite at 0,0 + * you can clear the transform + * + * ```js + * + * sprite.setTransform() + * + * let baseRenderTexture = new PIXI.BaseRenderTexture(100, 100); + * let renderTexture = new PIXI.RenderTexture(baseRenderTexture); + * + * renderer.render(sprite, renderTexture); // Renders to center of RenderTexture + * ``` + * + * @class + * @extends PIXI.BaseTexture + * @memberof PIXI + */ +export default class BaseRenderTexture extends BaseTexture +{ + /** + * @param {object} [options] + * @param {number} [options.width=100] - The width of the base render texture + * @param {number} [options.height=100] - The height of the base render texture + * @param {PIXI.SCALE_MODES} [options.scaleMode] - See {@link PIXI.SCALE_MODES} for possible values + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the texture being generated + */ + constructor(options) + { + if (typeof options === 'number') + { + /* eslint-disable prefer-rest-params */ + // Backward compatibility of signature + const width = arguments[0]; + const height = arguments[1]; + const scaleMode = arguments[2]; + const resolution = arguments[3]; + + options = { width, height, scaleMode, resolution }; + /* eslint-enable prefer-rest-params */ + } + + super(null, options); + + const { width, height } = options || {}; + + // Set defaults + this.mipmap = false; + this.width = Math.ceil(width) || 100; + this.height = Math.ceil(height) || 100; + this.valid = true; + + /** + * A map of renderer IDs to webgl renderTargets + * + * @private + * @member {object} + */ + // this._glRenderTargets = {}; + + /** + * A reference to the canvas render target (we only need one as this can be shared across renderers) + * + * @private + * @member {object} + */ + this._canvasRenderTarget = null; + + this.clearColor = [0, 0, 0, 0]; + + this.frameBuffer = new FrameBuffer(width * this.resolution, height * this.resolution) + .addColorTexture(0, this); + + // TODO - could this be added the systems? + + /** + * The data structure for the stencil masks + * + * @member {PIXI.Graphics[]} + */ + this.stencilMaskStack = []; + + /** + * The data structure for the filters + * + * @member {PIXI.Graphics[]} + */ + this.filterStack = [{}]; + } + + /** + * Resizes the BaseRenderTexture. + * + * @param {number} width - The width to resize to. + * @param {number} height - The height to resize to. + */ + resize(width, height) + { + width = Math.ceil(width); + height = Math.ceil(height); + this.frameBuffer.resize(width * this.resolution, height * this.resolution); + } + + /** + * Destroys this texture + * + */ + destroy() + { + super.destroy(true); + this.renderer = null; + } +} diff --git a/packages/core/src/renderTexture/RenderTexture.js b/packages/core/src/renderTexture/RenderTexture.js new file mode 100644 index 0000000..f1ecf1f --- /dev/null +++ b/packages/core/src/renderTexture/RenderTexture.js @@ -0,0 +1,153 @@ +import BaseRenderTexture from './BaseRenderTexture'; +import Texture from '../textures/Texture'; + +/** + * A RenderTexture is a special texture that allows any PixiJS display object to be rendered to it. + * + * __Hint__: All DisplayObjects (i.e. Sprites) that render to a RenderTexture should be preloaded + * otherwise black rectangles will be drawn instead. + * + * A RenderTexture takes a snapshot of any Display Object given to its render method. For example: + * + * ```js + * let renderer = PIXI.autoDetectRenderer(1024, 1024, { view: canvas, ratio: 1 }); + * let renderTexture = PIXI.RenderTexture.create(800, 600); + * let sprite = PIXI.Sprite.fromImage("spinObj_01.png"); + * + * sprite.position.x = 800/2; + * sprite.position.y = 600/2; + * sprite.anchor.x = 0.5; + * sprite.anchor.y = 0.5; + * + * renderer.render(sprite, renderTexture); + * ``` + * + * The Sprite in this case will be rendered using its local transform. To render this sprite at 0,0 + * you can clear the transform + * + * ```js + * + * sprite.setTransform() + * + * let renderTexture = new PIXI.RenderTexture.create(100, 100); + * + * renderer.render(sprite, renderTexture); // Renders to center of RenderTexture + * ``` + * + * @class + * @extends PIXI.Texture + * @memberof PIXI + */ +export default class RenderTexture extends Texture +{ + /** + * @param {PIXI.BaseRenderTexture} baseRenderTexture - The renderer used for this RenderTexture + * @param {PIXI.Rectangle} [frame] - The rectangle frame of the texture to show + */ + constructor(baseRenderTexture, frame) + { + // support for legacy.. + let _legacyRenderer = null; + + if (!(baseRenderTexture instanceof BaseRenderTexture)) + { + /* eslint-disable prefer-rest-params, no-console */ + const width = arguments[1]; + const height = arguments[2]; + const scaleMode = arguments[3]; + const resolution = arguments[4]; + + // we have an old render texture.. + console.warn(`Please use RenderTexture.create(${width}, ${height}) instead of the ctor directly.`); + _legacyRenderer = arguments[0]; + /* eslint-enable prefer-rest-params, no-console */ + + frame = null; + baseRenderTexture = new BaseRenderTexture({ + width, + height, + scaleMode, + resolution, + }); + } + + /** + * The base texture object that this texture uses + * + * @member {BaseTexture} + */ + super(baseRenderTexture, frame); + + this.legacyRenderer = _legacyRenderer; + + /** + * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. + * + * @member {boolean} + */ + this.valid = true; + + /** + * FilterSystem temporary storage + * @private + * @member {PIXI.Rectangle} + */ + this.filterFrame = null; + + this._updateUvs(); + } + + /** + * Resizes the RenderTexture. + * + * @param {number} width - The width to resize to. + * @param {number} height - The height to resize to. + * @param {boolean} [resizeBaseTexture=true] - Should the baseTexture.width and height values be resized as well? + */ + resize(width, height, resizeBaseTexture = true) + { + width = Math.ceil(width); + height = Math.ceil(height); + + // TODO - could be not required.. + this.valid = (width > 0 && height > 0); + + this._frame.width = this.orig.width = width; + this._frame.height = this.orig.height = height; + + if (resizeBaseTexture) + { + this.baseTexture.resize(width, height); + } + + this._updateUvs(); + } + + /** + * A short hand way of creating a render texture. + * + * @param {object} [options] - Options + * @param {number} [options.width=100] - The width of the render texture + * @param {number} [options.height=100] - The height of the render texture + * @param {number} [options.scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the texture being generated + * @return {PIXI.RenderTexture} The new render texture + */ + static create(options) + { + // fallback, old-style: create(width, height, scaleMode, resolution) + if (typeof options === 'number') + { + /* eslint-disable prefer-rest-params */ + options = { + width: options, + height: arguments[1], + scaleMode: arguments[2], + resolution: arguments[3], + }; + /* eslint-enable prefer-rest-params */ + } + + return new RenderTexture(new BaseRenderTexture(options)); + } +} diff --git a/packages/core/src/renderTexture/RenderTextureSystem.js b/packages/core/src/renderTexture/RenderTextureSystem.js new file mode 100644 index 0000000..8d0ca61 --- /dev/null +++ b/packages/core/src/renderTexture/RenderTextureSystem.js @@ -0,0 +1,115 @@ +import System from '../System'; +import { Rectangle } from '@pixi/math'; + +const tempRect = new Rectangle(); + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class RenderTextureSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.clearColor = renderer._backgroundColorRgba; + + // TODO moe this property somewhere else! + this.defaultMaskStack = []; + this.defaultFilterStack = [{}]; + + // empty render texture? + this.renderTexture = null; + + this.destinationFrame = new Rectangle(); + } + + bind(renderTexture, sourceFrame, destinationFrame) + { + // TODO - do we want this?? + if (this.renderTexture === renderTexture) return; + this.renderTexture = renderTexture; + + const renderer = this.renderer; + + if (renderTexture) + { + const baseTexture = renderTexture.baseTexture; + + if (!destinationFrame) + { + tempRect.width = baseTexture.realWidth; + tempRect.height = baseTexture.realHeight; + + destinationFrame = tempRect; + } + + if (!sourceFrame) + { + sourceFrame = destinationFrame; + } + + this.renderer.framebuffer.bind(baseTexture.frameBuffer, destinationFrame); + + this.renderer.projection.update(destinationFrame, sourceFrame, baseTexture.resolution, false); + this.renderer.stencil.setMaskStack(baseTexture.stencilMaskStack); + } + else + { + // TODO these validation checks happen deeper down.. + // thing they can be avoided.. + if (!destinationFrame) + { + tempRect.width = renderer.width; + tempRect.height = renderer.height; + + destinationFrame = tempRect; + } + + if (!sourceFrame) + { + sourceFrame = destinationFrame; + } + + renderer.framebuffer.bind(null, destinationFrame); + + // TODO store this.. + this.renderer.projection.update(destinationFrame, sourceFrame, this.renderer.resolution, true); + this.renderer.stencil.setMaskStack(this.defaultMaskStack); + } + + this.destinationFrame.copyFrom(destinationFrame); + } + + /** + * Erases the render texture and fills the drawing area with a colour + * + * @param {number} [clearColor] - The colour + * @return {PIXI.Renderer} Returns itself. + */ + clear(clearColor) + { + if (this.renderTexture) + { + clearColor = clearColor || this.renderTexture.baseTexture.clearColor; + } + else + { + clearColor = clearColor || this.clearColor; + } + + this.renderer.framebuffer.clear(clearColor[0], clearColor[1], clearColor[2], clearColor[3]); + } + + resize()// screenWidth, screenHeight) + { + // resize the root only! + this.bind(null); + } +} diff --git a/packages/core/src/renderers/AbstractRenderer.js b/packages/core/src/renderers/AbstractRenderer.js deleted file mode 100644 index 928670f..0000000 --- a/packages/core/src/renderers/AbstractRenderer.js +++ /dev/null @@ -1,353 +0,0 @@ -import { hex2string, hex2rgb } from '@pixi/utils'; -import { Matrix, Rectangle } from '@pixi/math'; -import { RENDERER_TYPE } from '@pixi/constants'; -import { settings } from '@pixi/settings'; -import { Container } from '@pixi/display'; -import RenderTexture from '../textures/RenderTexture'; -import EventEmitter from 'eventemitter3'; - -const tempMatrix = new Matrix(); - -/** - * 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 EventEmitter - * @memberof PIXI - */ -export default class AbstractRenderer extends EventEmitter -{ - // eslint-disable-next-line valid-jsdoc - /** - * @param {string} system - The name of the system this renderer is for. - * @param {object} [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.transparent=false] - If the render view is transparent, default false - * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false - * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) - * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. The - * resolution of the renderer retina would be 2. - * @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 {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when rendering, - * stopping pixel interpolation. - */ - constructor(system, options, arg2, arg3) - { - super(); - - // Support for constructor(system, screenWidth, screenHeight, options) - if (typeof options === 'number') - { - options = Object.assign({ - width: options, - height: arg2 || settings.RENDER_OPTIONS.height, - }, arg3); - } - - // 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 = RENDERER_TYPE.UNKNOWN; - - /** - * Measurements of the screen. (0, 0, screenWidth, screenHeight) - * - * Its safe to use as filterArea or hitArea for 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 1 - */ - this.resolution = options.resolution || settings.RESOLUTION; - - /** - * Whether the render view is transparent - * - * @member {boolean} - */ - this.transparent = options.transparent; - - /** - * Whether css dimensions of canvas view should be resized to screen dimensions automatically - * - * @member {boolean} - */ - this.autoResize = options.autoResize || false; - - /** - * Tracks the blend modes useful for this renderer. - * - * @member {object} - */ - this.blendModes = null; - - /** - * 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; - - /** - * If true PixiJS will Math.floor() x/y values when rendering, stopping pixel interpolation. - * Handy for crisp pixel art and speed on legacy devices. - * - * @member {boolean} - */ - this.roundPixels = options.roundPixels; - - /** - * The background color as a number. - * - * @member {number} - * @private - */ - this._backgroundColor = 0x000000; - - /** - * The background color as an [R, G, B] array. - * - * @member {number[]} - * @private - */ - this._backgroundColorRgba = [0, 0, 0, 0]; - - /** - * The background color as a string. - * - * @member {string} - * @private - */ - this._backgroundColorString = '#000000'; - - this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter - - /** - * This temporary display object used as the parent of the currently being rendered item - * - * @member {PIXI.DisplayObject} - * @private - */ - this._tempDisplayObjectParent = new Container(); - - /** - * The last root object that the renderer tried to render. - * - * @member {PIXI.DisplayObject} - * @private - */ - this._lastObjectRendered = this._tempDisplayObjectParent; - - /** - * Collection of plugins. - * @readonly - * @member {object} - */ - this.plugins = {}; - } - - /** - * Initialize the plugins. - * - * @protected - * @param {object} staticMap - The dictionary of staticly saved plugins. - */ - initPlugins(staticMap) - { - 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() - { - return this.view.width; - } - - /** - * Same as view.height, actual number of pixels in the canvas by vertical - * - * @member {number} - * @readonly - * @default 600 - */ - get height() - { - return this.view.height; - } - - /** - * Resizes the screen and canvas to the specified width and height - * Canvas dimensions are multiplied by resolution - * - * @param {number} screenWidth - the new width of the screen - * @param {number} screenHeight - the new height of the screen - */ - resize(screenWidth, screenHeight) - { - this.screen.width = screenWidth; - this.screen.height = screenHeight; - - this.view.width = screenWidth * this.resolution; - this.view.height = screenHeight * this.resolution; - - if (this.autoResize) - { - this.view.style.width = `${screenWidth}px`; - this.view.style.height = `${screenHeight}px`; - } - } - - /** - * 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 {PIXI.DisplayObject} displayObject - The displayObject the object will be generated from - * @param {number} scaleMode - Should be one of the scaleMode consts - * @param {number} resolution - The resolution / device pixel ratio of the texture being generated - * @param {PIXI.Rectangle} [region] - The region of the displayObject, that shall be rendered, - * if no region is specified, defaults to the local bounds of the displayObject. - * @return {PIXI.Texture} a texture of the graphics object - */ - generateTexture(displayObject, scaleMode, resolution, region) - { - region = region || displayObject.getLocalBounds(); - - // 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(region.width | 0, region.height | 0, scaleMode, resolution); - - tempMatrix.tx = -region.x; - tempMatrix.ty = -region.y; - - this.render(displayObject, renderTexture, false, tempMatrix, true); - - return renderTexture; - } - - /** - * Removes everything from the renderer and optionally removes the Canvas DOM element. - * - * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. - */ - destroy(removeView) - { - 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); - } - - this.plugins = null; - - this.type = RENDERER_TYPE.UNKNOWN; - - this.view = null; - - this.screen = null; - - this.resolution = 0; - - this.transparent = false; - - this.autoResize = false; - - this.blendModes = null; - - this.options = null; - - this.preserveDrawingBuffer = false; - this.clearBeforeRender = false; - - this.roundPixels = false; - - this._backgroundColor = 0; - this._backgroundColorRgba = null; - this._backgroundColorString = null; - - this._tempDisplayObjectParent = null; - this._lastObjectRendered = null; - } - - /** - * The background color to fill if not transparent - * - * @member {number} - */ - get backgroundColor() - { - return this._backgroundColor; - } - - set backgroundColor(value) // eslint-disable-line require-jsdoc - { - this._backgroundColor = value; - this._backgroundColorString = hex2string(value); - hex2rgb(value, this._backgroundColorRgba); - } -} diff --git a/packages/core/src/renderers/Renderer.js b/packages/core/src/renderers/Renderer.js deleted file mode 100644 index a2020b1..0000000 --- a/packages/core/src/renderers/Renderer.js +++ /dev/null @@ -1,329 +0,0 @@ -import AbstractRenderer from './AbstractRenderer'; -import { sayHello } from '@pixi/utils'; -import MaskSystem from './systems/MaskSystem'; -import StencilSystem from './systems/StencilSystem'; -import FilterSystem from './systems/filter/FilterSystem'; -import FramebufferSystem from './systems/FramebufferSystem'; -import RenderTextureSystem from './systems/RenderTextureSystem'; -import TextureSystem from './systems/textures/TextureSystem'; -import ProjectionSystem from './systems/ProjectionSystem'; -import StateSystem from './systems/StateSystem'; -import GeometrySystem from './systems/geometry/GeometrySystem'; -import ShaderSystem from './systems/shader/ShaderSystem'; -import ContextSystem from './systems/ContextSystem'; -import BatchSystem from './systems/BatchSystem'; -import TextureGCSystem from './systems/textures/TextureGCSystem'; -import { RENDERER_TYPE } from '@pixi/constants'; -import UniformGroup from '../shader/UniformGroup'; -import { Matrix } from '@pixi/math'; -import Runner from 'mini-runner'; - -/** - * 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 Render works by automatically managing webGLBatchs. - * 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 :) - * - * @class - * @memberof PIXI - * @extends PIXI.AbstractRenderer - */ -export default class Renderer extends AbstractRenderer -{ - // eslint-disable-next-line valid-jsdoc - /** - * - * @param {object} [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.transparent=false] - If the render view is transparent, default false - * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false - * @param {boolean} [options.antialias=false] - sets antialias. If not available natively then FXAA - * antialiasing is used - * @param {boolean} [options.forceFXAA=false] - forces FXAA antialiasing to be used over native. - * FXAA is faster, but may not always look as great - * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. - * The resolution of the renderer retina would be 2. - * @param {boolean} [options.clearBeforeRender=true] - This sets if the renderer will clear - * the canvas or not before the new render pass. If you wish to set this to false, you *must* set - * preserveDrawingBuffer to `true`. - * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, - * enable this if you need to call toDataUrl on the webgl context. - * @param {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when - * rendering, stopping pixel interpolation. - * @param {number} [options.backgroundColor=0x000000] - The background color of the rendered area - * (shown if not transparent). - * @param {boolean} [options.legacy=false] - If true PixiJS will aim to ensure compatibility - * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. - * @param {string} [options.powerPreference] - Parameter passed to webgl context, set to "high-performance" - * for devices with dual graphics card - */ - constructor(options, arg2, arg3) - { - super('WebGL', options, arg2, arg3); - - /** - * The type of this renderer as a standardised const - * - * @member {number} - * @see PIXI.RENDERER_TYPE - */ - this.type = RENDERER_TYPE.WEBGL; - - // this will be set by the contextSystem (this.context) - this.gl = null; - this.CONTEXT_UID = 0; - this.legacy = !!options.legacy; - - // TODO legacy! - - // runners! - this.runners = { - destroy: new Runner('destroy'), - contextChange: new Runner('contextChange', 1), - reset: new Runner('reset'), - update: new Runner('update'), - postrender: new Runner('postrender'), - prerender: new Runner('prerender'), - resize: new Runner('resize', 2), - }; - - 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(GeometrySystem, 'geometry') - .addSystem(FramebufferSystem, 'framebuffer') - .addSystem(StencilSystem, 'stencil') - .addSystem(ProjectionSystem, 'projection') - .addSystem(TextureGCSystem, 'textureGC') - .addSystem(FilterSystem, 'filter') - .addSystem(RenderTextureSystem, 'renderTexture') - .addSystem(BatchSystem, 'batch'); - - this.initPlugins(Renderer.__plugins); - - /** - * The options passed in to create a new webgl context. - * - * @member {object} - * @private - */ - if (options.context) - { - this.context.initFromContext(options.context); - } - else - { - this.context.initFromOptions({ - alpha: this.transparent, - antialias: options.antialias, - premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', - stencil: true, - 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); - } - - addSystem(_class, name) - { - if (!name) - { - name = _class.name; - } - - // TODO - read name from class.name.. - - /* - if(name.includes('System')) - { - name = name.replace('System', ''); - name = name.charAt(0).toLowerCase() + name.slice(1); - } - */ - - const system = new _class(this); - - if (this[name]) - { - throw new Error(`Whoops! ${name} is already a manger`); - } - - this[name] = system; - - for (const i in this.runners) - { - this.runners[i].add(system); - } - - return this; - - /** - * 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. - */ - } - - /** - * Renders the object to its webGL view - * - * @param {PIXI.DisplayObject} displayObject - the object to be rendered - * @param {PIXI.RenderTexture} renderTexture - The render texture to render to. - * @param {boolean} [clear] - Should the canvas be cleared before the new render - * @param {PIXI.Transform} [transform] - A transform to apply to the render texture before rendering. - * @param {boolean} [skipUpdateTransform] - Should we skip the update transform pass? - */ - render(displayObject, renderTexture, clear, transform, skipUpdateTransform) - { - // can be handy to know! - this.renderingToScreen = !renderTexture; - - this.runners.prerender.run(); - this.emit('prerender'); - - // 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.parent; - - displayObject.parent = this._tempDisplayObjectParent; - displayObject.updateTransform(); - displayObject.parent = 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.run(); - - this.emit('postrender'); - } - - /** - * Resizes the webGL view to the specified width and height. - * - * @param {number} screenWidth - the new width of the screen - * @param {number} screenHeight - the new height of the screen - */ - resize(screenWidth, screenHeight) - { - AbstractRenderer.prototype.resize.call(this, screenWidth, screenHeight); - - this.runners.resize.run(screenWidth, screenHeight); - } - - /** - * Resets the WebGL state so you can render things however you fancy! - * - * @return {PIXI.Renderer} Returns itself. - */ - reset() - { - this.runners.reset.run(); - - return this; - } - - clear() - { - this.framebuffer.bind(); - this.framebuffer.clear(); - } - - /** - * Removes everything from the renderer (event listeners, spritebatch, etc...) - * - * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. - * See: https://github.com/pixijs/pixi.js/issues/2233 - */ - destroy(removeView) - { - this.runners.destroy.run(); - - // call base destroy - super.destroy(removeView); - - // TODO nullify all the managers.. - this.gl = null; - } - - /** - * 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 - * builds and excluding plugins. - * @name PIXI.Renderer#plugins - * @type {object} - * @readonly - * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. - * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. - * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. - * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. - */ - - /** - * Adds a plugin to the renderer. - * - * @method - * @param {string} pluginName - The name of the plugin. - * @param {Function} ctor - The constructor function or class for the plugin. - */ - static registerPlugin(pluginName, ctor) - { - Renderer.__plugins = Renderer.__plugins || {}; - Renderer.__plugins[pluginName] = ctor; - } -} diff --git a/packages/core/src/renderers/State.js b/packages/core/src/renderers/State.js deleted file mode 100644 index fa3f221..0000000 --- a/packages/core/src/renderers/State.js +++ /dev/null @@ -1,163 +0,0 @@ -/* eslint-disable max-len */ - -const BLEND = 0; -const OFFSET = 1; -const CULLING = 2; -const DEPTH_TEST = 3; -const WINDING = 4; - -/** - * This is a webGL state. It is passed The WebGL StateManager. - * Each mesh renderered may require webGL to be in a different state. - * For example you may want different blend mode or to enable polygon offsets - * - * @class - * @memberof PIXI - */ -export default class State -{ - /** - * - */ - constructor() - { - this.data = 0; - - this.blendMode = 0; - this.polygonOffset = 0; - - this.blend = true; - // this.depthTest = true; - } - - /** - * Activates blending of the computed fragment color values - * - * @member {boolean} - */ - get blend() - { - return !!(this.data & (1 << BLEND)); - } - - set blend(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << BLEND)) !== value) - { - this.data ^= (1 << BLEND); - } - } - - /** - * Activates adding an offset to depth values of polygon's fragments - * - * @member {boolean} - * @default false - */ - get offsets() - { - return !!(this.data & (1 << OFFSET)); - } - - set offsets(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << OFFSET)) !== value) - { - this.data ^= (1 << OFFSET); - } - } - - /** - * Activates culling of polygons. - * - * @member {boolean} - * @default false - */ - get culling() - { - return !!(this.data & (1 << CULLING)); - } - - set culling(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << CULLING)) !== value) - { - this.data ^= (1 << CULLING); - } - } - - /** - * Activates depth comparisons and updates to the depth buffer. - * - * @member {boolean} - * @default false - */ - get depthTest() - { - return !!(this.data & (1 << DEPTH_TEST)); - } - - set depthTest(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << DEPTH_TEST)) !== value) - { - this.data ^= (1 << DEPTH_TEST); - } - } - - /** - * Specifies whether or not front or back-facing polygons can be culled. - * @member {boolean} - * @default false - */ - get clockwiseFrontFace() - { - return !!(this.data & (1 << WINDING)); - } - - set clockwiseFrontFace(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << WINDING)) !== value) - { - this.data ^= (1 << WINDING); - } - } - - /** - * The blend mode to be applied when this state is set. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. - * Setting this mode to anything other than NO_BLEND will automatically switch blending on. - * - * @member {boolean} - * @default PIXI.BLEND_MODES.NORMAL - * @see PIXI.BLEND_MODES - */ - get blendMode() - { - return this._blendMode; - } - - set blendMode(value) // eslint-disable-line require-jsdoc - { - // 17 is NO BLEND - this.blend = (value !== 17); - this._blendMode = value; - } - - /** - * The polygon offset. Setting this property to anything other than 0 will automatically enable poygon offset fill. - * - * @member {number} - * @default 0 - */ - get polygonOffset() - { - return this._polygonOffset; - } - - set polygonOffset(value) // eslint-disable-line require-jsdoc - { - this.offsets = !!value; - this._polygonOffset = value; - } -} - diff --git a/packages/core/src/renderers/filters/Filter.js b/packages/core/src/renderers/filters/Filter.js deleted file mode 100644 index 2073b8d..0000000 --- a/packages/core/src/renderers/filters/Filter.js +++ /dev/null @@ -1,140 +0,0 @@ -import Shader from '../../shader/Shader'; -import Program from '../../shader/Program'; -import State from '../State'; -import { settings } from '@pixi/settings'; -// import extractUniformsFromSrc from './extractUniformsFromSrc'; -import defaultVertex from './defaultFilter.vert'; -import defaultFragment from './defaultFilter.frag'; - -// let math = require('../../math'); -/** - * @class - * @memberof PIXI - * @extends PIXI.Shader - */ -export default class Filter extends Shader -{ - /** - * @param {string} [vertexSrc] - The source of the vertex shader. - * @param {string} [fragmentSrc] - The source of the fragment shader. - * @param {object} [uniforms] - Custom uniforms to use to augment the built-in ones. - */ - constructor(vertexSrc, fragmentSrc, uniforms) - { - const program = Program.from(vertexSrc, fragmentSrc); - - super(program, uniforms); - - /** - * The padding of the filter. Some filters require extra space to breath such as a blur. - * Increasing this will add extra width and height to the bounds of the object that the - * filter is applied to. - * - * @member {number} - */ - this.padding = 0; - - /** - * The resolution of the filter. Setting this to be lower will lower the quality but - * increase the performance of the filter. - * - * @member {number} - */ - this.resolution = settings.RESOLUTION; - - /** - * If enabled is true the filter is applied, if false it will not. - * - * @member {boolean} - */ - this.enabled = true; - - /** - * If enabled, PixiJS will fit the filter area into boundaries for better performance. - * Switch it off if it does not work for specific shader. - * - * @member {boolean} - */ - this.autoFit = true; - - /** - * Legacy filters use position and uvs from attributes - * @member {boolean} - * @readonly - */ - this.legacy = !!this.program.attributeData.aTextureCoord; - - /** - * the webGL state the filter requires to render - * @member {PIXI.State} - */ - this.state = new State(); - } - - /** - * Applies the filter - * - * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from - * @param {PIXI.RenderTarget} input - The input render target. - * @param {PIXI.RenderTarget} output - The target to output to. - * @param {boolean} clear - Should the output be cleared before rendering to it - * @param {object} [currentState] - It's current state of filter. - * There are some useful properties in the currentState : - * target, filters, sourceFrame, destinationFrame, renderTarget, resolution - */ - apply(filterManager, input, output, clear, currentState, derp) // eslint-disable-line no-unused-vars - { - // do as you please! - - filterManager.applyFilter(this, input, output, clear, currentState, derp); - - // or just do a regular render.. - } - - /** - * Sets the blendmode of the filter - * - * @member {number} - * @default PIXI.BLEND_MODES.NORMAL - */ - get blendMode() - { - return this.state.blendMode; - } - - set blendMode(value) // eslint-disable-line require-jsdoc - { - this.state.blendMode = value; - } - - /** - * The default vertex shader source - * - * @static - * @constant - */ - static get defaultVertexSrc() - { - return defaultVertex; - } - - /** - * The default fragment shader source - * - * @static - * @constant - */ - static get defaultFragmentSrc() - { - return defaultFragment; - } -} - -/** - * Used for caching shader IDs - * - * @static - * @private - */ -Filter.SOURCE_KEY_MAP = {}; - diff --git a/packages/core/src/renderers/filters/defaultFilter.frag b/packages/core/src/renderers/filters/defaultFilter.frag deleted file mode 100644 index a14c1be..0000000 --- a/packages/core/src/renderers/filters/defaultFilter.frag +++ /dev/null @@ -1,22 +0,0 @@ -varying vec2 vTextureCoord; -varying vec2 vFilterCoord; - -uniform sampler2D uSampler; -uniform sampler2D filterSampler; - -void main(void){ - vec4 masky = texture2D(filterSampler, vFilterCoord); - vec4 sample = texture2D(uSampler, vTextureCoord); - vec4 color; - if(mod(vFilterCoord.x, 1.0) > 0.5) - { - color = vec4(1.0, 0.0, 0.0, 1.0); - } - else - { - color = vec4(0.0, 1.0, 0.0, 1.0); - } - // gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0); - gl_FragColor = mix(sample, masky, 0.5); - gl_FragColor *= sample.a; -} diff --git a/packages/core/src/renderers/filters/defaultFilter.vert b/packages/core/src/renderers/filters/defaultFilter.vert deleted file mode 100644 index e1febca..0000000 --- a/packages/core/src/renderers/filters/defaultFilter.vert +++ /dev/null @@ -1,14 +0,0 @@ -attribute vec2 aVertexPosition; -attribute vec2 aTextureCoord; - -uniform mat3 projectionMatrix; -uniform mat3 filterMatrix; - -varying vec2 vTextureCoord; -varying vec2 vFilterCoord; - -void main(void){ - gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); - vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy; - vTextureCoord = aTextureCoord ; -} diff --git a/packages/core/src/renderers/filters/filterTransforms.js b/packages/core/src/renderers/filters/filterTransforms.js deleted file mode 100644 index 87a3cd6..0000000 --- a/packages/core/src/renderers/filters/filterTransforms.js +++ /dev/null @@ -1,50 +0,0 @@ -import { Matrix } from '@pixi/math'; - -/** - * Calculates the mapped matrix - * @param filterArea {Rectangle} The filter area - * @param sprite {Sprite} the target sprite - * @param outputMatrix {Matrix} @alvin - * @private - */ -// this returns a matrix that will normalise map filter cords in the filter to screen space -export function calculateScreenSpaceMatrix(outputMatrix, filterArea, textureSize) -{ - // TODO unwrap? - const mappedMatrix = outputMatrix.identity(); - - mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); - - mappedMatrix.scale(textureSize.width, textureSize.height); - - return mappedMatrix; -} - -export function calculateNormalizedScreenSpaceMatrix(outputMatrix, filterArea, textureSize) -{ - const mappedMatrix = outputMatrix.identity(); - - mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); - - const translateScaleX = (textureSize.width / filterArea.width); - const translateScaleY = (textureSize.height / filterArea.height); - - mappedMatrix.scale(translateScaleX, translateScaleY); - - return mappedMatrix; -} - -// this will map the filter coord so that a texture can be used based on the transform of a sprite -export function calculateSpriteMatrix(outputMatrix, filterArea, textureSize, sprite) -{ - const orig = sprite._texture.orig; - const mappedMatrix = outputMatrix.set(textureSize.width, 0, 0, textureSize.height, filterArea.x, filterArea.y); - const worldTransform = sprite.worldTransform.copyTo(Matrix.TEMP_MATRIX); - - worldTransform.invert(); - mappedMatrix.prepend(worldTransform); - mappedMatrix.scale(1.0 / orig.width, 1.0 / orig.height); - mappedMatrix.translate(sprite.anchor.x, sprite.anchor.y); - - return mappedMatrix; -} diff --git a/packages/core/src/renderers/filters/spriteMask/SpriteMaskFilter.js b/packages/core/src/renderers/filters/spriteMask/SpriteMaskFilter.js deleted file mode 100644 index d5a91a4..0000000 --- a/packages/core/src/renderers/filters/spriteMask/SpriteMaskFilter.js +++ /dev/null @@ -1,63 +0,0 @@ -import Filter from '../Filter'; -import { Matrix } from '@pixi/math'; -import vertex from './spriteMaskFilter.vert'; -import fragment from './spriteMaskFilter.frag'; -import { default as TextureMatrix } from '../../../textures/TextureMatrix'; - -/** - * The SpriteMaskFilter class - * - * @class - * @extends PIXI.Filter - * @memberof PIXI - */ -export default class SpriteMaskFilter extends Filter -{ - /** - * @param {PIXI.Sprite} sprite - the target sprite - */ - constructor(sprite) - { - const maskMatrix = new Matrix(); - - super(vertex, fragment); - - sprite.renderable = false; - - this.maskSprite = sprite; - this.maskMatrix = maskMatrix; - } - - /** - * Applies the filter - * - * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from - * @param {PIXI.RenderTarget} input - The input render target. - * @param {PIXI.RenderTarget} output - The target to output to. - */ - apply(filterManager, input, output) - { - const maskSprite = this.maskSprite; - const tex = this.maskSprite.texture; - - if (!tex.valid) - { - return; - } - if (!tex.transform) - { - // margin = 0.0, let it bleed a bit, shader code becomes easier - // assuming that atlas textures were made with 1-pixel padding - tex.transform = new TextureMatrix(tex, 0.0); - } - tex.transform.update(); - - this.uniforms.mask = tex; - this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite) - .prepend(tex.transform.mapCoord); - this.uniforms.alpha = maskSprite.worldAlpha; - this.uniforms.maskClamp = tex.transform.uClampFrame; - - filterManager.applyFilter(this, input, output); - } -} diff --git a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.frag b/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.frag deleted file mode 100644 index 0e4aef8..0000000 --- a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.frag +++ /dev/null @@ -1,23 +0,0 @@ -varying vec2 vMaskCoord; -varying vec2 vTextureCoord; - -uniform sampler2D uSampler; -uniform sampler2D mask; -uniform float alpha; -uniform vec4 maskClamp; - -void main(void) -{ - float clip = step(3.5, - step(maskClamp.x, vMaskCoord.x) + - step(maskClamp.y, vMaskCoord.y) + - step(vMaskCoord.x, maskClamp.z) + - step(vMaskCoord.y, maskClamp.w)); - - vec4 original = texture2D(uSampler, vTextureCoord); - vec4 masky = texture2D(mask, vMaskCoord); - - original *= (masky.r * masky.a * alpha * clip); - - gl_FragColor = original; -} diff --git a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.vert b/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.vert deleted file mode 100644 index d23a30c..0000000 --- a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.vert +++ /dev/null @@ -1,16 +0,0 @@ -attribute vec2 aVertexPosition; -attribute vec2 aTextureCoord; - -uniform mat3 projectionMatrix; -uniform mat3 otherMatrix; - -varying vec2 vMaskCoord; -varying vec2 vTextureCoord; - -void main(void) -{ - gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); - - vTextureCoord = aTextureCoord; - vMaskCoord = ( otherMatrix * vec3( aTextureCoord, 1.0) ).xy; -} diff --git a/packages/core/src/renderers/systems/BatchSystem.js b/packages/core/src/renderers/systems/BatchSystem.js deleted file mode 100644 index 70e9518..0000000 --- a/packages/core/src/renderers/systems/BatchSystem.js +++ /dev/null @@ -1,68 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import ObjectRenderer from '../utils/ObjectRenderer'; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ - -export default class BatchSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this manager works for. - */ - constructor(renderer) - { - super(renderer); - - /** - * An empty renderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.emptyRenderer = new ObjectRenderer(renderer); - - /** - * The currently active ObjectRenderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.currentRenderer = this.emptyRenderer; - } - - /** - * Changes the current renderer to the one given in parameter - * - * @param {PIXI.ObjectRenderer} objectRenderer - The object renderer to use. - */ - setObjectRenderer(objectRenderer) - { - if (this.currentRenderer === objectRenderer) - { - return; - } - - this.currentRenderer.stop(); - this.currentRenderer = objectRenderer; - - this.renderer.state.setState(objectRenderer.state); - - this.currentRenderer.start(); - } - - /** - * This should be called if you wish to do some custom rendering - * It will basically render anything that may be batched up such as sprites - * - */ - flush() - { - this.setObjectRenderer(this.emptyRenderer); - } - - reset() - { - this.setObjectRenderer(this.emptyRenderer); - } -} diff --git a/packages/core/src/renderers/systems/ContextSystem.js b/packages/core/src/renderers/systems/ContextSystem.js deleted file mode 100644 index 43bf827..0000000 --- a/packages/core/src/renderers/systems/ContextSystem.js +++ /dev/null @@ -1,186 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { settings } from '@pixi/settings'; - -let CONTEXT_UID = 0; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class ContextSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this.webGLVersion = 1; - - this.handleContextLost = this.handleContextLost.bind(this); - this.handleContextRestored = this.handleContextRestored.bind(this); - - this.extensions = {}; - - renderer.view.addEventListener('webglcontextlost', this.handleContextLost, false); - renderer.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); - } - - get isLost() - { - return (!this.gl || this.gl.isContextLost()); - } - - contextChange(gl) - { - this.gl = gl; - - // restore a context if it was previously lost - if (gl.isContextLost() && gl.getExtension('WEBGL_lose_context')) - { - gl.getExtension('WEBGL_lose_context').restoreContext(); - } - } - - initFromContext(gl) - { - this.gl = gl; - this.validateContext(gl); - this.renderer.gl = gl; - this.renderer.CONTEXT_UID = CONTEXT_UID++; - this.renderer.runners.contextChange.run(gl); - } - - initFromOptions(options) - { - const gl = this.createContext(this.renderer.view, options); - - this.initFromContext(gl); - } - - /** - * Helper class to create a webGL Context - * - * @class - * @memberof PIXI.glCore - * @param canvas {HTMLCanvasElement} the canvas element that we will get the context from - * @param options {Object} An options object that gets passed in to the canvas element containing the context attributes, - * see https://developer.mozilla.org/en/docs/Web/API/HTMLCanvasElement/getContext for the options available - * @return {WebGLRenderingContext} the WebGL context - */ - createContext(canvas, options) - { - let gl; - - if (settings.PREFER_WEBGL_2) - { - gl = canvas.getContext('webgl2', options); - } - - if (gl) - { - this.webGLVersion = 2; - } - else - { - this.webGLVersion = 1; - - gl = canvas.getContext('webgl', options) - || canvas.getContext('experimental-webgl', options); - - if (!gl) - { - // fail, not able to get a context - throw new Error('This browser does not support webGL. Try using the canvas renderer'); - } - } - - this.gl = gl; - - this.getExtensions(); - - return gl; - } - - getExtensions() - { - // time to set up default etensions that pixi uses.. - const gl = this.gl; - const extensions = this.extensions; - - if (this.webGLVersion === 1) - { - extensions.drawBuffers = gl.getExtension('WEBGL_draw_buffers'); - extensions.depthTexture = gl.getExtension('WEBKIT_WEBGL_depth_texture'); - extensions.floatTexture = gl.getExtension('OES_texture_float'); - extensions.loseContext = gl.getExtension('WEBGL_lose_context'); - - extensions.vertexArrayObject = gl.getExtension('OES_vertex_array_object') - || gl.getExtension('MOZ_OES_vertex_array_object') - || gl.getExtension('WEBKIT_OES_vertex_array_object'); - } - - // we don't use any specific WebGL 2 ones yet! - } - - /** - * Handles a lost webgl context - * - * @private - * @param {WebGLContextEvent} event - The context lost event. - */ - handleContextLost(event) - { - event.preventDefault(); - } - - /** - * Handles a restored webgl context - * - * @private - */ - handleContextRestored() - { - this.renderer.runners.contextChange.run(this.gl); - } - - destroy() - { - const view = this.renderer.view; - - // remove listeners - view.removeEventListener('webglcontextlost', this.handleContextLost); - view.removeEventListener('webglcontextrestored', this.handleContextRestored); - - this.gl.useProgram(null); - - if (this.extensions.loseContext) - { - this.extensions.loseContext.loseContext(); - } - } - - postrender() - { - this.gl.flush(); - } - - validateContext(gl) - { - const attributes = gl.getContextAttributes(); - - // this is going to be fairly simple for now.. but at least we have room to grow! - if (!attributes.stencil) - { - /* eslint-disable max-len */ - - /* eslint-disable no-console */ - console.warn('Provided WebGL context does not have a stencil buffer, masks may not render correctly'); - /* eslint-enable no-console */ - - /* eslint-enable max-len */ - } - } -} diff --git a/packages/core/src/renderers/systems/FramebufferSystem.js b/packages/core/src/renderers/systems/FramebufferSystem.js deleted file mode 100644 index df7902b..0000000 --- a/packages/core/src/renderers/systems/FramebufferSystem.js +++ /dev/null @@ -1,246 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { Rectangle } from '@pixi/math'; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class FramebufferSystem extends WebGLSystem -{ - /** - * Sets up the renderer context and necessary buffers. - * - * @private - */ - contextChange() - { - this.gl = this.renderer.gl; - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - this.current = null; - this.viewport = new Rectangle(); - - this.drawBufferExtension = this.renderer.context.extensions.drawBuffers; - } - - bind(framebuffer, frame) - { - const gl = this.gl; - - this.current = framebuffer; - - if (framebuffer) - { - // TODO cacheing layer! - - const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID] || this.initFramebuffer(framebuffer); - - gl.bindFramebuffer(gl.FRAMEBUFFER, fbo.framebuffer); - // makesure all textures are unbound.. - - // now check for updates... - if (fbo.dirtyId !== framebuffer.dirtyId) - { - fbo.dirtyId = framebuffer.dirtyId; - - if (fbo.dirtyFormat !== framebuffer.dirtyFormat) - { - fbo.dirtyFormat = framebuffer.dirtyFormat; - this.updateFramebuffer(framebuffer); - } - else if (fbo.dirtySize !== framebuffer.dirtySize) - { - fbo.dirtySize = framebuffer.dirtySize; - this.resizeFramebuffer(framebuffer); - } - } - - for (let i = 0; i < framebuffer.colorTextures.length; i++) - { - if (framebuffer.colorTextures[i].texturePart) - { - this.renderer.texture.unbind(framebuffer.colorTextures[i].texture); - } - else - { - this.renderer.texture.unbind(framebuffer.colorTextures[i]); - } - } - - if (framebuffer.depthTexture) - { - this.renderer.texture.unbind(framebuffer.depthTexture); - } - - if (frame) - { - this.setViewport(frame.x, frame.y, frame.width, frame.height); - } - else - { - this.setViewport(0, 0, framebuffer.width, framebuffer.height); - } - } - else - { - gl.bindFramebuffer(gl.FRAMEBUFFER, null); - - if (frame) - { - this.setViewport(frame.x, frame.y, frame.width, frame.height); - } - else - { - this.setViewport(0, 0, this.renderer.width, this.renderer.height); - } - } - } - - setViewport(x, y, width, height) - { - const v = this.viewport; - - if (v.width !== width || v.height !== height || v.x !== x || v.y !== y) - { - v.x = x; - v.y = y; - v.width = width; - v.height = height; - - this.gl.viewport(x, y, width, height); - } - } - - get size() - { - if (this.current) - { - // TODO store temp - return { x: 0, y: 0, width: this.current.width, height: this.current.height }; - } - - return { x: 0, y: 0, width: this.renderer.width, height: this.renderer.height }; - } - - clear(r, g, b, a) - { - const gl = this.gl; - - // TODO clear color can be set only one right? - gl.clearColor(r, g, b, a); - gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); - } - - // private functions... - - initFramebuffer(framebuffer) - { - const gl = this.gl; - - // TODO - make this a class? - const fbo = { - framebuffer: gl.createFramebuffer(), - stencil: null, - dirtyId: 0, - dirtyFormat: 0, - dirtySize: 0, - }; - - framebuffer.glFrameBuffers[this.CONTEXT_UID] = fbo; - - return fbo; - } - - resizeFramebuffer(framebuffer) - { - const gl = this.gl; - - if (framebuffer.stencil || framebuffer.depth) - { - gl.bindRenderbuffer(gl.RENDERBUFFER, this.stencil); - gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); - } - } - - updateFramebuffer(framebuffer) - { - const gl = this.gl; - - const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID]; - - // bind the color texture - const colorTextures = framebuffer.colorTextures; - - let count = colorTextures.length; - - if (!this.drawBufferExtension) - { - count = Math.min(count, 1); - } - - const activeTextures = []; - - for (let i = 0; i < count; i++) - { - const texture = framebuffer.colorTextures[i]; - - if (texture.texturePart) - { - this.renderer.texture.bind(texture.texture, 0); - - gl.framebufferTexture2D(gl.FRAMEBUFFER, - gl.COLOR_ATTACHMENT0 + i, - gl.TEXTURE_CUBE_MAP_NEGATIVE_X + texture.side, - texture.texture._glTextures[this.CONTEXT_UID].texture, - 0); - } - else - { - this.renderer.texture.bind(texture, 0); - - gl.framebufferTexture2D(gl.FRAMEBUFFER, - gl.COLOR_ATTACHMENT0 + i, - gl.TEXTURE_2D, - texture._glTextures[this.CONTEXT_UID].texture, - 0); - } - - activeTextures.push(gl.COLOR_ATTACHMENT0 + i); - } - - if (this.drawBufferExtension && activeTextures.length > 1) - { - this.drawBufferExtension.drawBuffersWEBGL(activeTextures); - } - - if (framebuffer.depthTexture) - { - const depthTextureExt = this.renderer.context.extensions.depthTexture; - - if (depthTextureExt) - { - const depthTexture = framebuffer.depthTexture; - - this.renderer.texture.bind(depthTexture, 0); - - gl.framebufferTexture2D(gl.FRAMEBUFFER, - gl.DEPTH_ATTACHMENT, - gl.TEXTURE_2D, - depthTexture._glTextures[this.CONTEXT_UID].texture, - 0); - } - } - - if (framebuffer.stencil || framebuffer.depth) - { - fbo.stencil = gl.createRenderbuffer(); - - gl.bindRenderbuffer(gl.RENDERBUFFER, fbo.stencil); - - // TODO.. this is depth AND stencil? - gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, fbo.stencil); - gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); - // fbo.enableStencil(); - } - } -} diff --git a/packages/core/src/renderers/systems/MaskSystem.js b/packages/core/src/renderers/systems/MaskSystem.js deleted file mode 100644 index d9c4b1a..0000000 --- a/packages/core/src/renderers/systems/MaskSystem.js +++ /dev/null @@ -1,199 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import AlphaMaskFilter from '../filters/spriteMask/SpriteMaskFilter'; - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class MaskSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - // TODO - we don't need both! - this.scissor = false; - this.scissorData = null; - this.scissorRenderTarget = null; - - this.enableScissor = false; - - this.alphaMaskPool = []; - this.alphaMaskIndex = 0; - } - - /** - * Applies the Mask and adds it to the current filter stack. - * - * @param {PIXI.DisplayObject} target - Display Object to push the mask to - * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. - */ - push(target, maskData) - { - // TODO the root check means scissor rect will not - // be used on render textures more info here: - // https://github.com/pixijs/pixi.js/pull/3545 - - if (maskData.texture) - { - this.pushSpriteMask(target, maskData); - } - else if (this.enableScissor - && !this.scissor - && this.renderer._activeRenderTarget.root - && !this.renderer.stencil.stencilMaskStack.length - && maskData.isFastRect()) - { - const matrix = maskData.worldTransform; - - let rot = Math.atan2(matrix.b, matrix.a); - - // use the nearest degree! - rot = Math.round(rot * (180 / Math.PI)); - - if (rot % 90) - { - this.pushStencilMask(maskData); - } - else - { - this.pushScissorMask(target, maskData); - } - } - else - { - this.pushStencilMask(maskData); - } - } - - /** - * Removes the last mask from the mask stack and doesn't return it. - * - * @param {PIXI.DisplayObject} target - Display Object to pop the mask from - * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. - */ - pop(target, maskData) - { - if (maskData.texture) - { - this.popSpriteMask(target, maskData); - } - else if (this.enableScissor && !this.renderer.stencil.stencilMaskStack.length) - { - this.popScissorMask(target, maskData); - } - else - { - this.popStencilMask(target, maskData); - } - } - - /** - * Applies the Mask and adds it to the current filter stack. - * - * @param {PIXI.RenderTarget} target - Display Object to push the sprite mask to - * @param {PIXI.Sprite} maskData - Sprite to be used as the mask - */ - pushSpriteMask(target, maskData) - { - let alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; - - if (!alphaMaskFilter) - { - alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; - } - - alphaMaskFilter[0].resolution = this.renderer.resolution; - alphaMaskFilter[0].maskSprite = maskData; - - // TODO - may cause issues! - target.filterArea = maskData.getBounds(true); - - this.renderer.filterSystem.pushFilter(target, alphaMaskFilter); - - this.alphaMaskIndex++; - } - - /** - * Removes the last filter from the filter stack and doesn't return it. - * - */ - popSpriteMask() - { - this.renderer.filterSystem.popFilter(); - this.alphaMaskIndex--; - } - - /** - * Applies the Mask and adds it to the current filter stack. - * - * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. - */ - pushStencilMask(maskData) - { - this.renderer.batch.flush(); - this.renderer.stencil.pushStencil(maskData); - } - - /** - * Removes the last filter from the filter stack and doesn't return it. - * - */ - popStencilMask() - { - // this.renderer.currentRenderer.stop(); - this.renderer.stencil.popStencil(); - } - - /** - * - * @param {PIXI.DisplayObject} target - Display Object to push the mask to - * @param {PIXI.Graphics} maskData - The masking data. - */ - pushScissorMask(target, maskData) - { - maskData.renderable = true; - - const renderTarget = this.renderer._activeRenderTarget; - - const bounds = maskData.getBounds(); - - bounds.fit(renderTarget.size); - maskData.renderable = false; - - this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); - - const resolution = this.renderer.resolution; - - this.renderer.gl.scissor( - bounds.x * resolution, - (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, - bounds.width * resolution, - bounds.height * resolution - ); - - this.scissorRenderTarget = renderTarget; - this.scissorData = maskData; - this.scissor = true; - } - - /** - * - * - */ - popScissorMask() - { - this.scissorRenderTarget = null; - this.scissorData = null; - this.scissor = false; - - // must be scissor! - const gl = this.renderer.gl; - - gl.disable(gl.SCISSOR_TEST); - } -} diff --git a/packages/core/src/renderers/systems/ProjectionSystem.js b/packages/core/src/renderers/systems/ProjectionSystem.js deleted file mode 100644 index ec5efdb..0000000 --- a/packages/core/src/renderers/systems/ProjectionSystem.js +++ /dev/null @@ -1,73 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { Matrix } from '@pixi/math'; - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ - -export default class ProjectionSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this.projectionMatrix = new Matrix(); - } - - update(destinationFrame, sourceFrame, resolution, root) - { - this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; - this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; - - this.calculateProjection(this.destinationFrame, this.sourceFrame, resolution, root); - - this.renderer.globalUniforms.uniforms.projectionMatrix = this.projectionMatrix; - this.renderer.globalUniforms.update(); - } - - /** - * Updates the projection matrix based on a projection frame (which is a rectangle) - * - * @param {Rectangle} destinationFrame - The destination frame. - * @param {Rectangle} sourceFrame - The source frame. - */ - calculateProjection(destinationFrame, sourceFrame, resolution, root) - { - const pm = this.projectionMatrix; - - // I don't think we will need this line.. - // pm.identity(); - - if (!root) - { - pm.a = (1 / destinationFrame.width * 2) * resolution; - pm.d = (1 / destinationFrame.height * 2) * resolution; - - pm.tx = -1 - (sourceFrame.x * pm.a); - pm.ty = -1 - (sourceFrame.y * pm.d); - } - else - { - pm.a = (1 / destinationFrame.width * 2) * resolution; - pm.d = (-1 / destinationFrame.height * 2) * resolution; - - pm.tx = -1 - (sourceFrame.x * pm.a); - pm.ty = 1 - (sourceFrame.y * pm.d); - } - } - - /** - * Sets the transform of the active render target to the given matrix - * - * @param {PIXI.Matrix} matrix - The transformation matrix - */ - setTransform()// matrix) - { - // this._activeRenderTarget.transform = matrix; - } -} diff --git a/packages/core/src/renderers/systems/RenderTextureSystem.js b/packages/core/src/renderers/systems/RenderTextureSystem.js deleted file mode 100644 index a133ef1..0000000 --- a/packages/core/src/renderers/systems/RenderTextureSystem.js +++ /dev/null @@ -1,115 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { Rectangle } from '@pixi/math'; - -const tempRect = new Rectangle(); - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ - -export default class RenderTextureSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this.clearColor = renderer._backgroundColorRgba; - - // TODO moe this property somewhere else! - this.defaultMaskStack = []; - this.defaultFilterStack = [{}]; - - // empty render texture? - this.renderTexture = null; - - this.destinationFrame = new Rectangle(); - } - - bind(renderTexture, sourceFrame, destinationFrame) - { - // TODO - do we want this?? - if (this.renderTexture === renderTexture) return; - this.renderTexture = renderTexture; - - const renderer = this.renderer; - - if (renderTexture) - { - const baseTexture = renderTexture.baseTexture; - - if (!destinationFrame) - { - tempRect.width = baseTexture.realWidth; - tempRect.height = baseTexture.realHeight; - - destinationFrame = tempRect; - } - - if (!sourceFrame) - { - sourceFrame = destinationFrame; - } - - this.renderer.framebuffer.bind(baseTexture.frameBuffer, destinationFrame); - - this.renderer.projection.update(destinationFrame, sourceFrame, baseTexture.resolution, false); - this.renderer.stencil.setMaskStack(baseTexture.stencilMaskStack); - } - else - { - // TODO these validation checks happen deeper down.. - // thing they can be avoided.. - if (!destinationFrame) - { - tempRect.width = renderer.width; - tempRect.height = renderer.height; - - destinationFrame = tempRect; - } - - if (!sourceFrame) - { - sourceFrame = destinationFrame; - } - - renderer.framebuffer.bind(null, destinationFrame); - - // TODO store this.. - this.renderer.projection.update(destinationFrame, sourceFrame, this.renderer.resolution, true); - this.renderer.stencil.setMaskStack(this.defaultMaskStack); - } - - this.destinationFrame.copyFrom(destinationFrame); - } - - /** - * Erases the render texture and fills the drawing area with a colour - * - * @param {number} [clearColor] - The colour - * @return {PIXI.Renderer} Returns itself. - */ - clear(clearColor) - { - if (this.renderTexture) - { - clearColor = clearColor || this.renderTexture.baseTexture.clearColor; - } - else - { - clearColor = clearColor || this.clearColor; - } - - this.renderer.framebuffer.clear(clearColor[0], clearColor[1], clearColor[2], clearColor[3]); - } - - resize()// screenWidth, screenHeight) - { - // resize the root only! - this.bind(null); - } -} diff --git a/packages/core/src/renderers/systems/StateSystem.js b/packages/core/src/renderers/systems/StateSystem.js deleted file mode 100755 index d7f4caf..0000000 --- a/packages/core/src/renderers/systems/StateSystem.js +++ /dev/null @@ -1,288 +0,0 @@ -import mapWebGLBlendModesToPixi from '../utils/mapWebGLBlendModesToPixi'; -import WebGLSystem from './WebGLSystem'; -import WebGLState from '../State'; - -const BLEND = 0; -const OFFSET = 1; -const CULLING = 2; -const DEPTH_TEST = 3; -const WINDING = 4; - -/** - * A WebGL state machines - * - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class StateSystem extends WebGLSystem -{ - /** - * @param {WebGLRenderingContext} gl - The current WebGL rendering context - */ - constructor(renderer) - { - super(renderer); - - this.gl = null; - - this.maxAttribs = null; - - // check we have vao.. - this.nativeVaoExtension = null; - - this.attribState = null; - - this.stateId = 0; - this.polygonOffset = 0; - this.blendMode = 17; - - this.map = []; - - // map functions for when we set state.. - this.map[BLEND] = this.setBlend; - this.map[OFFSET] = this.setOffset; - this.map[CULLING] = this.setCullFace; - this.map[DEPTH_TEST] = this.setDepthTest; - this.map[WINDING] = this.setFrontFace; - - this.checks = []; - - this.defaultState = new WebGLState(); - this.defaultState.blend = true; - this.defaultState.depth = true; - } - - contextChange(gl) - { - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; - - this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); - - // check we have vao.. - this.nativeVaoExtension = ( - gl.getExtension('OES_vertex_array_object') - || gl.getExtension('MOZ_OES_vertex_array_object') - || gl.getExtension('WEBKIT_OES_vertex_array_object') - ); - - this.attribState = { - tempAttribState: new Array(this.maxAttribs), - attribState: new Array(this.maxAttribs), - }; - - this.blendModes = mapWebGLBlendModesToPixi(gl); - - this.setState(this.defaultState); - - this.reset(); - } - - /** - * Sets the current state - * - * @param {*} state - The state to set. - */ - setState(state) - { - state = state || this.defaultState; - - // TODO maybe to an object check? ( this.state === state )? - if (this.stateId !== state.data) - { - let diff = this.stateId ^ state.data; - let i = 0; - - // order from least to most common - while (diff) - { - if (diff & 1) - { - // state change! - this.map[i].call(this, !!(state.data & (1 << i))); - } - - diff = diff >> 1; - i++; - } - - this.stateId = state.data; - } - - // based on the above settings we check for specific modes.. - // for example if blend is active we check and set the blend modes - // or of polygon offset is active we check the poly depth. - for (let i = 0; i < this.checks.length; i++) - { - this.checks[i](this, state); - } - } - - /** - * Enables or disabled blending. - * - * @param {boolean} value - Turn on or off webgl blending. - */ - setBlend(value) - { - this.updateCheck(StateSystem.checkBlendMode, value); - - this.gl[value ? 'enable' : 'disable'](this.gl.BLEND); - } - - /** - * Enables or disable polygon offset fill - * - * @param {boolean} value - Turn on or off webgl polygon offset testing. - */ - setOffset(value) - { - this.gl[value ? 'enable' : 'disable'](this.gl.POLYGON_OFFSET_FILL); - } - - /** - * Sets whether to enable or disable depth test. - * - * @param {boolean} value - Turn on or off webgl depth testing. - */ - setDepthTest(value) - { - this.gl[value ? 'enable' : 'disable'](this.gl.DEPTH_TEST); - } - - /** - * Sets whether to enable or disable cull face. - * - * @param {boolean} value - Turn on or off webgl cull face. - */ - setCullFace(value) - { - this.gl[value ? 'enable' : 'disable'](this.gl.CULL_FACE); - } - - /** - * Sets the gl front face. - * - * @param {boolean} value - true is clockwise and false is counter-clockwise - */ - setFrontFace(value) - { - this.gl.frontFace(this.gl[value ? 'CW' : 'CCW']); - } - - /** - * Sets the blend mode. - * - * @param {number} value - The blend mode to set to. - */ - setBlendMode(value) - { - if (value === this.blendMode) - { - return; - } - - this.blendMode = value; - this.gl.blendFunc(this.blendModes[value][0], this.blendModes[value][1]); - } - - /** - * Sets the polygon offset. - * - * @param {number} value - the polygon offset - * @param {number} scale - the polygon offset scale - */ - setPolygonOffset(value, scale) - { - this.gl.polygonOffset(value, scale); - } - - /** - * Disables all the vaos in use - * - */ - resetAttributes() - { - for (let i = 0; i < this.attribState.tempAttribState.length; i++) - { - this.attribState.tempAttribState[i] = 0; - } - - for (let i = 0; i < this.attribState.attribState.length; i++) - { - this.attribState.attribState[i] = 0; - } - - // im going to assume one is always active for performance reasons. - for (let i = 1; i < this.maxAttribs; i++) - { - this.gl.disableVertexAttribArray(i); - } - } - - // used - /** - * Resets all the logic and disables the vaos - */ - reset() - { - // unbind any VAO if they exist.. - if (this.nativeVaoExtension) - { - this.nativeVaoExtension.bindVertexArrayOES(null); - } - - // reset all attributes.. - this.resetAttributes(); - - this.gl.pixelStorei(this.gl.UNPACK_FLIP_Y_WEBGL, false); - - this.setBlendMode(0); - - // TODO? - // this.setState(this.defaultState); - } - - /** - * checks to see which updates should be checked based on which settings have been activated - * for example if blend is enabled then we shold check the blend modes each time the state is changed - * or if poygon fill is activated then we need to check if the polygone offset changes. - * The idea is that we only check what we have too. - * - * @param {Function} func the checking function to add or remove - * @param {boolean} value should the check function be added or removed. - */ - updateCheck(func, value) - { - const index = this.checks.indexOf(func); - - if (value && index === -1) - { - this.checks.push(func); - } - else if (!value && index !== -1) - { - this.checks.splice(index, 1); - } - } - - /** - * A private little wrapper function that we call to check the blend mode. - * - * @static - * @private - * @param {PIXI.StateSystem} System the System to perform the state check on - * @param {PIXI.State} state the state that the blendMode will pulled from - */ - static checkBlendMode(system, state) - { - system.setBlendMode(state.blendMode); - } - - // TODO - add polygon offset? -} diff --git a/packages/core/src/renderers/systems/StencilSystem.js b/packages/core/src/renderers/systems/StencilSystem.js deleted file mode 100644 index 5d538cb..0000000 --- a/packages/core/src/renderers/systems/StencilSystem.js +++ /dev/null @@ -1,133 +0,0 @@ -import WebGLSystem from './WebGLSystem'; - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class StencilSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - this.stencilMaskStack = []; - } - - /** - * Changes the mask stack that is used by this System. - * - * @param {PIXI.Graphics[]} stencilMaskStack - The mask stack - */ - setMaskStack(stencilMaskStack) - { - const gl = this.renderer.gl; - - if (stencilMaskStack.length !== this.stencilMaskStack.length) - { - if (stencilMaskStack.length === 0) - { - gl.disable(gl.STENCIL_TEST); - } - else - { - gl.enable(gl.STENCIL_TEST); - } - } - - this.stencilMaskStack = stencilMaskStack; - } - - /** - * Applies the Mask and adds it to the current stencil stack. @alvin - * - * @param {PIXI.Graphics} graphics - The mask - */ - pushStencil(graphics) - { - this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); - - // this.renderer._activeRenderTarget.attachStencilBuffer(); - - const gl = this.renderer.gl; - const prevMaskCount = this.stencilMaskStack.length; - - if (prevMaskCount === 0) - { - gl.enable(gl.STENCIL_TEST); - } - - this.stencilMaskStack.push(graphics); - - // Increment the refference stencil value where the new mask overlaps with the old ones. - gl.colorMask(false, false, false, false); - gl.stencilFunc(gl.EQUAL, prevMaskCount, this._getBitwiseMask()); - gl.stencilOp(gl.KEEP, gl.KEEP, gl.INCR); - this.renderer.plugins.graphics.render(graphics); - - this._useCurrent(); - } - - /** - * Removes the last mask from the stencil stack. @alvin - */ - popStencil() - { - this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); - - const gl = this.renderer.gl; - const graphics = this.stencilMaskStack.pop(); - - if (this.stencilMaskStack.length === 0) - { - // the stack is empty! - gl.disable(gl.STENCIL_TEST); - gl.clear(gl.STENCIL_BUFFER_BIT); - gl.clearStencil(0); - } - else - { - // Decrement the refference stencil value where the popped mask overlaps with the other ones - gl.colorMask(false, false, false, false); - gl.stencilOp(gl.KEEP, gl.KEEP, gl.DECR); - this.renderer.plugins.graphics.render(graphics); - - this._useCurrent(); - } - } - - /** - * Setup renderer to use the current stencil data. - */ - _useCurrent() - { - const gl = this.renderer.gl; - - gl.colorMask(true, true, true, true); - gl.stencilFunc(gl.EQUAL, this.stencilMaskStack.length, this._getBitwiseMask()); - gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP); - } - - /** - * Fill 1s equal to the number of acitve stencil masks. - * - * @return {number} The bitwise mask. - */ - _getBitwiseMask() - { - return (1 << this.stencilMaskStack.length) - 1; - } - - /** - * Destroys the mask stack. - * - */ - destroy() - { - super.destroy(this); - - this.stencilMaskStack = null; - } -} diff --git a/packages/core/src/renderers/systems/WebGLSystem.js b/packages/core/src/renderers/systems/WebGLSystem.js deleted file mode 100644 index d3d5b31..0000000 --- a/packages/core/src/renderers/systems/WebGLSystem.js +++ /dev/null @@ -1,41 +0,0 @@ -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this manager works for. - */ - constructor(renderer) - { - /** - * The renderer this manager works for. - * - * @member {PIXI.Renderer} - */ - this.renderer = renderer; - - this.renderer.runners.contextChange.add(this); - } - - /** - * Generic method called when there is a WebGL context change. - * - */ - contextChange() - { - // do some codes init! - } - - /** - * Generic destroy methods to be overridden by the subclass - * - */ - destroy() - { - this.renderer.runners.contextChange.remove(this); - this.renderer = null; - } -} diff --git a/packages/core/src/renderers/systems/filter/FilterSystem.js b/packages/core/src/renderers/systems/filter/FilterSystem.js deleted file mode 100644 index e615037..0000000 --- a/packages/core/src/renderers/systems/filter/FilterSystem.js +++ /dev/null @@ -1,431 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; - -import RenderTexture from '../../../textures/RenderTexture'; -import Quad from '../../utils/Quad'; -import QuadUv from '../../utils/QuadUv'; -import { Rectangle } from '@pixi/math'; -import * as filterTransforms from '../../filters/filterTransforms'; -import bitTwiddle from 'bit-twiddle'; -import UniformGroup from '../../../shader/UniformGroup'; -import { DRAW_MODES } from '../../../../../constants'; - -// -/** - * @ignore - * @class - */ -class FilterState -{ - /** - * - */ - constructor() - { - this.renderTexture = null; - this.sourceFrame = new Rectangle(); - this.destinationFrame = new Rectangle(); - this.filters = []; - this.target = null; - this.legacy = false; - this.resolution = 1; - } -} - -/** - * @class - * @memberof PIXI.systems - * @extends PIXI.systems.WebGLSystem - */ -export default class FilterSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - /** - * stores a bunch of PO2 textures used for filtering - * @type {Object} - */ - this.texturePool = {}; - - /** - * a pool for storing filter states, save us creating new ones each tick - * @type {Array} - */ - this.statePool = []; - - /** - * A very simple geometry used when drawing a filter effect to the screen - * @type {Quad} - */ - this.quad = new Quad(); - - this.quadUv = new QuadUv(); - - /** - * Temporary rect for maths - * @type {PIXI.Rectangle} - */ - this.tempRect = new Rectangle(); - - this.activeState = {}; - - /** - * this uniform group is attached to filter uniforms when used - * @type {UniformGroup} - */ - this.globalUniforms = new UniformGroup({ - sourceFrame: this.tempRect, - destinationFrame: this.tempRect, - - // legacy variables - filterArea: new Float32Array(4), - filterClamp: new Float32Array(4), - }, true); - } - - /** - * Adds a new filter to the System. - * - * @param {PIXI.DisplayObject} target - The target of the filter to render. - * @param {PIXI.Filter[]} filters - The filters to apply. - */ - push(target, filters) - { - const renderer = this.renderer; - const filterStack = this.renderer.renderTexture.defaultFilterStack; - const state = this.statePool.pop() || new FilterState(); - - let resolution = filters[0].resolution; - let padding = filters[0].padding; - let autoFit = filters[0].autoFit; - let legacy = filters[0].legacy; - - for (let i = 1; i < filters.length; i++) - { - const filter = filters[i]; - - // lets use the lowest resolution.. - resolution = Math.min(resolution, filter.resolution); - // and the largest amount of padding! - padding = Math.max(padding, filter.padding); - // only auto fit if all filters are autofit - autoFit = autoFit || filter.autoFit; - - legacy = legacy || filter.legacy; - } - - filterStack.push(state); - - state.resolution = resolution; - - state.legacy = legacy; - - // round to whole number based on resolution - // TODO move that to the shader too? - state.sourceFrame = target.filterArea || target.getBounds(true); - - state.sourceFrame.pad(padding); - - if (autoFit) - { - state.sourceFrame.fit(this.renderer.renderTexture.destinationFrame); - } - - state.sourceFrame.round(resolution); - - state.renderTexture = this.getPotFilterTexture(state.sourceFrame.width, state.sourceFrame.height, resolution); - state.filters = filters; - - state.destinationFrame.width = state.renderTexture.width; - state.destinationFrame.height = state.renderTexture.height; - - state.renderTexture.filterFrame = state.sourceFrame; - renderer.renderTexture.bind(state.renderTexture, state.sourceFrame);// /, state.destinationFrame); - renderer.renderTexture.clear(); - } - - /** - * Pops off the filter and applies it. - * - */ - pop() - { - const renderer = this.renderer; - const filterStack = renderer.renderTexture.defaultFilterStack; - const state = filterStack.pop(); - const filters = state.filters; - - this.activeState = state; - - const globalUniforms = this.globalUniforms.uniforms; - - globalUniforms.sourceFrame = state.sourceFrame; - globalUniforms.destinationFrame = state.destinationFrame; - globalUniforms.resolution = state.resolution; - - // only update the rect if its legacy.. - if (state.legacy) - { - const filterArea = globalUniforms.filterArea; - const filterClamp = globalUniforms.filterClamp; - - filterArea[0] = state.destinationFrame.width; - filterArea[1] = state.destinationFrame.height; - filterArea[2] = state.sourceFrame.x; - filterArea[3] = state.sourceFrame.y; - - filterClamp[0] = 0.5 / state.resolution / state.destinationFrame.width; - filterClamp[1] = 0.5 / state.resolution / state.destinationFrame.height; - filterClamp[2] = (state.sourceFrame.width - 0.5) / state.resolution / state.destinationFrame.width; - filterClamp[3] = (state.sourceFrame.height - 0.5) / state.resolution / state.destinationFrame.height; - } - - this.globalUniforms.update(); - - const lastState = filterStack[filterStack.length - 1]; - - if (filters.length === 1) - { - filters[0].apply(this, state.renderTexture, lastState.renderTexture, false, state); - - this.returnFilterTexture(state.renderTexture); - } - else - { - let flip = state.renderTexture; - let flop = this.getPotFilterTexture( - flip.width, - flip.height, - state.resolution - ); - - flop.filterFrame = flip.filterFrame; - - let i = 0; - - for (i = 0; i < filters.length - 1; ++i) - { - filters[i].apply(this, flip, flop, true, state); - - const t = flip; - - flip = flop; - flop = t; - } - - filters[i].apply(this, flip, lastState.renderTexture, false, state); - - this.returnFilterTexture(flip); - this.returnFilterTexture(flop); - } - - this.statePool.push(state); - } - - /** - * Draws a filter. - * - * @param {PIXI.Filter} filter - The filter to draw. - * @param {PIXI.RenderTarget} input - The input render target. - * @param {PIXI.RenderTarget} output - The target to output to. - * @param {boolean} clear - Should the output be cleared before rendering to it - */ - applyFilter(filter, input, output, clear) - { - const renderer = this.renderer; - - renderer.renderTexture.bind(output, output ? output.filterFrame : null); - - if (clear) - { - // gl.disable(gl.SCISSOR_TEST); - renderer.renderTexture.clear(); - // gl.enable(gl.SCISSOR_TEST); - } - - // set the uniforms.. - filter.uniforms.uSampler = input; - filter.uniforms.filterGlobals = this.globalUniforms; - - // TODO make it so that the order of this does not matter.. - // because it does at the moment cos of global uniforms. - // they need to get resynced - - renderer.state.setState(filter.state); - renderer.shader.bind(filter); - - if (filter.legacy) - { - this.quadUv.map(input._frame, input.filterFrame); - - renderer.geometry.bind(this.quadUv); - renderer.geometry.draw(DRAW_MODES.TRIANGLES); - } - else - { - renderer.geometry.bind(this.quad); - renderer.geometry.draw(DRAW_MODES.TRIANGLE_STRIP); - } - } - - /** - * Calculates the mapped matrix. - * - * TODO playing around here.. this is temporary - (will end up in the shader) - * this returns a matrix that will normalise map filter cords in the filter to screen space - * - * @param {PIXI.Matrix} outputMatrix - the matrix to output to. - * @return {PIXI.Matrix} The mapped matrix. - */ - calculateScreenSpaceMatrix(outputMatrix) - { - const currentState = this.activeState; - - return filterTransforms.calculateScreenSpaceMatrix( - outputMatrix, - currentState.sourceFrame, - currentState.destinationFrame - ); - } - - /** - * This will map the filter coord so that a texture can be used based on the transform of a sprite - * - * @param {PIXI.Matrix} outputMatrix - The matrix to output to. - * @param {PIXI.Sprite} sprite - The sprite to map to. - * @return {PIXI.Matrix} The mapped matrix. - */ - calculateSpriteMatrix(outputMatrix, sprite) - { - const currentState = this.activeState; - - return filterTransforms.calculateSpriteMatrix( - outputMatrix, - currentState.sourceFrame, - currentState.destinationFrame, - sprite - ); - } - - /** - * Destroys this Filter System. - * - * @param {boolean} [contextLost=false] context was lost, do not free shaders - * - */ - destroy(contextLost = false) - { - if (!contextLost) - { - this.emptyPool(); - } - else - { - this.texturePool = {}; - } - } - - /** - * Gets a Power-of-Two render texture. - * - * TODO move to a seperate class could be on renderer? - * also - could cause issue with multiple contexts? - * - * @private - * @param {WebGLRenderingContext} gl - The webgl rendering context - * @param {number} minWidth - The minimum width of the render target. - * @param {number} minHeight - The minimum height of the render target. - * @param {number} resolution - The resolution of the render target. - * @return {PIXI.RenderTarget} The new render target. - */ - getPotFilterTexture(minWidth, minHeight, resolution) - { - minWidth = bitTwiddle.nextPow2(minWidth); - minHeight = bitTwiddle.nextPow2(minHeight); - resolution = resolution || 1; - - const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); - - if (!this.texturePool[key]) - { - this.texturePool[key] = []; - } - - let renderTexture = this.texturePool[key].pop(); - - if (!renderTexture) - { - // temporary bypass cache.. - // internally - this will cause a texture to be bound.. - renderTexture = RenderTexture.create({ - width: minWidth, - height: minHeight, - resolution, - }); - } - - return renderTexture; - } - - /** - * Gets extra render texture to use inside current filter - * - * @param {number} resolution resolution of the renderTexture - * @returns {PIXI.RenderTexture} - */ - getFilterTexture(resolution) - { - const rt = this.activeState.renderTexture; - - const filterTexture = this.getPotFilterTexture(rt.width, rt.height, resolution || rt.baseTexture.resolution); - - filterTexture.filterFrame = rt.filterFrame; - - return filterTexture; - } - - /** - * Frees a render texture back into the pool. - * - * @param {PIXI.RenderTarget} renderTexture - The renderTarget to free - */ - returnFilterTexture(renderTexture) - { - renderTexture.filterFrame = null; - - const base = renderTexture.baseTexture; - - const minWidth = base.width; - const minHeight = base.height; - - const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); - - this.texturePool[key].push(renderTexture); - } - - /** - * Empties the texture pool. - * - */ - emptyPool() - { - for (const i in this.texturePool) - { - const textures = this.texturePool[i]; - - if (textures) - { - for (let j = 0; j < textures.length; j++) - { - textures[j].destroy(true); - } - } - } - - this.texturePool = {}; - } -} diff --git a/packages/core/src/renderers/systems/geometry/GLBuffer.js b/packages/core/src/renderers/systems/geometry/GLBuffer.js deleted file mode 100644 index 0477e8a..0000000 --- a/packages/core/src/renderers/systems/geometry/GLBuffer.js +++ /dev/null @@ -1,9 +0,0 @@ -export default class GLBuffer -{ - constructor(buffer) - { - this.buffer = buffer; - this.updateID = -1; - this.byteLength = -1; - } -} diff --git a/packages/core/src/renderers/systems/geometry/GeometrySystem.js b/packages/core/src/renderers/systems/geometry/GeometrySystem.js deleted file mode 100644 index 26c8e9e..0000000 --- a/packages/core/src/renderers/systems/geometry/GeometrySystem.js +++ /dev/null @@ -1,388 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; -import GLBuffer from './GLBuffer'; - -const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class GeometrySystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this._activeGeometry = null; - this._activeVao = null; - - this.hasVao = true; - this.hasInstance = true; - } - - /** - * Sets up the renderer context and necessary buffers. - * - * @private - */ - contextChange() - { - const gl = this.gl = this.renderer.gl; - - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - - // webgl2 - if (!gl.createVertexArray) - { - // webgl 1! - let nativeVaoExtension = this.renderer.context.extensions.vertexArrayObject; - - if (this.renderer.legacy) - { - nativeVaoExtension = null; - } - - if (nativeVaoExtension) - { - gl.createVertexArray = () => - nativeVaoExtension.createVertexArrayOES(); - - gl.bindVertexArray = (vao) => - nativeVaoExtension.bindVertexArrayOES(vao); - - gl.deleteVertexArray = (vao) => - nativeVaoExtension.deleteVertexArrayOES(vao); - } - else - { - this.hasVao = false; - gl.createVertexArray = () => - { - // empty - }; - - gl.bindVertexArray = () => - { - // empty - }; - - gl.deleteVertexArray = () => - { - // empty - }; - } - } - - if (!gl.vertexAttribDivisor) - { - const instanceExt = gl.getExtension('ANGLE_instanced_arrays'); - - if (instanceExt) - { - gl.vertexAttribDivisor = (a, b) => - instanceExt.vertexAttribDivisorANGLE(a, b); - - gl.drawElementsInstanced = (a, b, c, d, e) => - instanceExt.drawElementsInstancedANGLE(a, b, c, d, e); - - gl.drawArraysInstanced = (a, b, c, d) => - instanceExt.drawArraysInstancedANGLE(a, b, c, d); - } - else - { - this.hasInstance = false; - } - } - } - - /** - * Binds geometry so that is can be drawn. Creating a Vao if required - * @private - * @param {PIXI.Geometry} geometry instance of geometry to bind - */ - bind(geometry, shader) - { - shader = shader || this.renderer.shader.shader; - - const gl = this.gl; - - // not sure the best way to address this.. - // currently different shaders require different VAOs for the same geometry - // Still mulling over the best way to solve this one.. - // will likely need to modify the shader attribute locations at run time! - let vaos = geometry.glVertexArrayObjects[this.CONTEXT_UID]; - - if (!vaos) - { - geometry.glVertexArrayObjects[this.CONTEXT_UID] = vaos = {}; - } - - const vao = vaos[shader.program.id] || this.initGeometryVao(geometry, shader.program); - - this._activeGeometry = geometry; - - if (this._activeVao !== vao) - { - this._activeVao = vao; - - if (this.hasVao) - { - gl.bindVertexArray(vao); - } - else - { - this.activateVao(geometry, shader.program); - } - } - - // TODO - optimise later! - // don't need to loop through if nothing changed! - // maybe look to add an 'autoupdate' to geometry? - this.updateBuffers(); - } - - reset() - { - this.unbind(); - } - - updateBuffers() - { - const geometry = this._activeGeometry; - const gl = this.gl; - - for (let i = 0; i < geometry.buffers.length; i++) - { - const buffer = geometry.buffers[i]; - - const glBuffer = buffer._glBuffers[this.CONTEXT_UID]; - - if (buffer._updateID !== glBuffer.updateID) - { - glBuffer.updateID = buffer._updateID; - - // TODO can cache this on buffer! maybe added a getter / setter? - const type = buffer.index ? gl.ELEMENT_ARRAY_BUFFER : gl.ARRAY_BUFFER; - const drawType = buffer.static ? gl.STATIC_DRAW : gl.DYNAMIC_DRAW; - - gl.bindBuffer(type, glBuffer.buffer); - - if (glBuffer.byteLength >= buffer.data.byteLength) - { - // offset is always zero for now! - gl.bufferSubData(type, 0, buffer.data); - } - else - { - gl.bufferData(type, buffer.data, drawType); - } - } - } - } - - checkCompatability(geometry, program) - { - // geometry must have at least all the attributes that the shader requires. - const geometryAttributes = geometry.attributes; - const shaderAttributes = program.attributeData; - - for (const j in shaderAttributes) - { - if (!geometryAttributes[j]) - { - throw new Error(`shader and geometry incompatible, geometry missing the "${j}" attribute`); - } - } - } - - /** - * Creates a Vao with the same structure as the geometry and stores it on the geometry. - * @private - * @param {PIXI.Geometry} geometry instance of geometry to to generate Vao for - */ - initGeometryVao(geometry, program) - { - this.checkCompatability(geometry, program); - - const gl = this.gl; - const CONTEXT_UID = this.CONTEXT_UID; - const buffers = geometry.buffers; - const attributes = geometry.attributes; - - const tempStride = {}; - const tempStart = {}; - - for (const j in buffers) - { - tempStride[j] = 0; - tempStart[j] = 0; - } - - for (const j in attributes) - { - if (!attributes[j].size && program.attributeData[j]) - { - attributes[j].size = program.attributeData[j].size; - } - - tempStride[attributes[j].buffer] += attributes[j].size * byteSizeMap[attributes[j].type]; - } - - for (const j in attributes) - { - const attribute = attributes[j]; - const attribSize = attribute.size; - - if (attribute.stride === undefined) - { - if (tempStride[attribute.buffer] === attribSize * byteSizeMap[attribute.type]) - { - attribute.stride = 0; - } - else - { - attribute.stride = tempStride[attribute.buffer]; - } - } - - if (attribute.start === undefined) - { - attribute.start = tempStart[attribute.buffer]; - - tempStart[attribute.buffer] += attribSize * byteSizeMap[attribute.type]; - } - } - - // first update - and create the buffers! - // only create a gl buffer if it actually gets - for (let i = 0; i < buffers.length; i++) - { - const buffer = buffers[i]; - - if (!buffer._glBuffers[CONTEXT_UID]) - { - buffer._glBuffers[CONTEXT_UID] = new GLBuffer(gl.createBuffer()); - } - } - - // TODO - maybe make this a data object? - // lets wait to see if we need to first! - const vao = gl.createVertexArray(); - - gl.bindVertexArray(vao); - - this.activateVao(geometry, program); - - gl.bindVertexArray(null); - - // add it to the cache! - geometry.glVertexArrayObjects[this.CONTEXT_UID][program.id] = vao; - - return vao; - } - - activateVao(geometry, program) - { - const gl = this.gl; - const CONTEXT_UID = this.CONTEXT_UID; - const buffers = geometry.buffers; - const attributes = geometry.attributes; - - if (geometry.indexBuffer) - { - // first update the index buffer if we have one.. - gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, geometry.indexBuffer._glBuffers[CONTEXT_UID].buffer); - } - - let lastBuffer = null; - - // add a new one! - for (const j in attributes) - { - const attribute = attributes[j]; - const buffer = buffers[attribute.buffer]; - const glBuffer = buffer._glBuffers[CONTEXT_UID]; - - if (program.attributeData[j]) - { - if (lastBuffer !== glBuffer) - { - gl.bindBuffer(gl.ARRAY_BUFFER, glBuffer.buffer); - - lastBuffer = glBuffer; - } - - const location = program.attributeData[j].location; - - // TODO introduce state again - // we can optimise this for older devices that have no VAOs - gl.enableVertexAttribArray(location); - - gl.vertexAttribPointer(location, - attribute.size, - attribute.type || gl.FLOAT, - attribute.normalized, - attribute.stride, - attribute.start); - - if (attribute.instance) - { - // TODO calculate instance count based of this... - if (this.hasInstance) - { - gl.vertexAttribDivisor(location, 1); - } - else - { - throw new Error('geometry error, GPU Instancing is not supported on this device'); - } - } - } - } - } - - draw(type, size, start, instanceCount) - { - const gl = this.gl; - const geometry = this._activeGeometry; - - // TODO.. this should not change so maybe cache the function? - - if (geometry.indexBuffer) - { - if (geometry.instanced) - { - /* eslint-disable max-len */ - gl.drawElementsInstanced(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2, instanceCount || 1); - /* eslint-enable max-len */ - } - else - { - gl.drawElements(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2); - } - } - else - if (geometry.instanced) - { - // TODO need a better way to calculate size.. - gl.drawArraysInstanced(type, start, size || geometry.getSize(), instanceCount || 1); - } - else - { - gl.drawArrays(type, start, size || geometry.getSize()); - } - - return this; - } - - unbind() - { - this.gl.bindVertexArray(null); - this._activeVao = null; - this._activeGeometry = null; - } -} diff --git a/packages/core/src/renderers/systems/geometry/setVertexAttribArrays.js b/packages/core/src/renderers/systems/geometry/setVertexAttribArrays.js deleted file mode 100644 index 23b6959..0000000 --- a/packages/core/src/renderers/systems/geometry/setVertexAttribArrays.js +++ /dev/null @@ -1,54 +0,0 @@ -// var GL_MAP = {}; - -/** - * @param gl {WebGLRenderingContext} The current WebGL context - * @param attribs {*} - * @param state {*} - */ -export default function setVertexAttribArrays(gl, attribs, state) -{ - let i; - - if (state) - { - const tempAttribState = state.tempAttribState; - const attribState = state.attribState; - - for (i = 0; i < tempAttribState.length; i++) - { - tempAttribState[i] = false; - } - - // set the new attribs - for (i = 0; i < attribs.length; i++) - { - tempAttribState[attribs[i].attribute.location] = true; - } - - for (i = 0; i < attribState.length; i++) - { - if (attribState[i] !== tempAttribState[i]) - { - attribState[i] = tempAttribState[i]; - - if (state.attribState[i]) - { - gl.enableVertexAttribArray(i); - } - else - { - gl.disableVertexAttribArray(i); - } - } - } - } - else - { - for (i = 0; i < attribs.length; i++) - { - const attrib = attribs[i]; - - gl.enableVertexAttribArray(attrib.attribute.location); - } - } -} diff --git a/packages/core/src/renderers/systems/index.js b/packages/core/src/renderers/systems/index.js deleted file mode 100644 index c843f03..0000000 --- a/packages/core/src/renderers/systems/index.js +++ /dev/null @@ -1,6 +0,0 @@ -/** - * Systems are individual components to the Renderer pipeline. - * @namespace PIXI.systems - */ -export { default as WebGLSystem } from './WebGLSystem'; -export { default as FilterSystem } from './filter/FilterSystem'; diff --git a/packages/core/src/renderers/systems/shader/GLProgram.js b/packages/core/src/renderers/systems/shader/GLProgram.js deleted file mode 100644 index 92980ca..0000000 --- a/packages/core/src/renderers/systems/shader/GLProgram.js +++ /dev/null @@ -1,43 +0,0 @@ -/** - * Helper class to create a webGL Program - * - * @class - * @private - */ -export default class GLProgram -{ - constructor(program, uniformData) - { - /** - * The shader program - * - * @member {WebGLProgram} - */ - this.program = program; - - /** - * holds the uniform data which contains uniform locations - * and current uniform values used for caching and preventing undeed GPU commands - * @type {Object} - */ - this.uniformData = uniformData; - - /** - * uniformGroups holds the various upload functions for the shader. Each uniform group - * and program have a unique upload function generated. - * @type {Object} - */ - this.uniformGroups = {}; - } - - /** - * Destroys this program - * TODO - */ - destroy() - { - this.uniformData = null; - this.uniformGroups = null; - this.program = null; - } -} diff --git a/packages/core/src/renderers/systems/shader/ShaderSystem.js b/packages/core/src/renderers/systems/shader/ShaderSystem.js deleted file mode 100644 index 6cf57b6..0000000 --- a/packages/core/src/renderers/systems/shader/ShaderSystem.js +++ /dev/null @@ -1,170 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; -import GLProgram from './GLProgram'; -import generateUniformsSync from '../../../shader/generateUniformsSync'; -import defaultValue from '../../../shader/defaultValue'; -import compileProgram from './shader/compileProgram'; - -let UID = 0; - -/** - * Helper class to create a webGL Texture - * - * @class - * @memberof PIXI - */ -export default class ShaderSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - A reference to the current renderer - */ - constructor(renderer) - { - super(renderer); - - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = null; - - this.shader = null; - this.program = null; - - this.id = UID++; - } - - contextChange(gl) - { - this.gl = gl; - } - - /** - * Changes the current shader to the one given in parameter - * - * @param {PIXI.Shader} shader - the new shader - * @param {boolean} dontSync - false if the shader should automatically sync its uniforms. - * @returns {PIXI.glCore.glProgram} the glProgram that belongs to the shader. - */ - bind(shader, dontSync) - { - shader.uniforms.globals = this.renderer.globalUniforms; - - const program = shader.program; - const glProgram = program.glPrograms[this.renderer.CONTEXT_UID] || this.generateShader(shader); - - this.shader = shader; - - // TODO - some current pixi plugins bypass this.. so it not safe to use yet.. - if (this.program !== program) - { - this.program = program; - this.gl.useProgram(glProgram.program); - } - - if (!dontSync) - { - this.syncUniformGroup(shader.uniformGroup); - } - - return glProgram; - } - - /** - * Uploads the uniforms values to the currently bound shader. - * - * @param {object} uniforms - the uniforms valiues that be applied to the current shader - */ - setUniforms(uniforms) - { - const shader = this.shader.program; - const glProgram = shader.glPrograms[this.renderer.CONTEXT_UID]; - - shader.syncUniforms(glProgram.uniformData, uniforms, this.renderer); - } - - syncUniformGroup(group) - { - const glProgram = this.getglProgram(); - - if (!group.static || group.dirtyId !== glProgram.uniformGroups[group.id]) - { - glProgram.uniformGroups[group.id] = group.dirtyId; - const syncFunc = group.syncUniforms[this.shader.program.id] || this.createSyncGroups(group); - - syncFunc(glProgram.uniformData, group.uniforms, this.renderer); - } - } - - createSyncGroups(group) - { - group.syncUniforms[this.shader.program.id] = generateUniformsSync(group, this.shader.program.uniformData); - - return group.syncUniforms[this.shader.program.id]; - } - - /** - * Returns the underlying GLShade rof the currently bound shader. - * This can be handy for when you to have a little more control over the setting of your uniforms. - * - * @return {PIXI.glCore.Shader} the glProgram for the currently bound Shader for this context - */ - getglProgram() - { - if (this.shader) - { - return this.shader.program.glPrograms[this.renderer.CONTEXT_UID]; - } - - return null; - } - - /** - * Generates a glProgram verion of the Shader provided. - * - * @private - * @param {PIXI.Shader} shader the shader that the glProgram will be based on. - * @return {PIXI.glCore.glProgram} A shiney new glProgram - */ - generateShader(shader) - { - const gl = this.gl; - - const program = shader.program; - - const attribMap = {}; - - for (const i in program.attributeData) - { - attribMap[i] = program.attributeData[i].location; - } - - const shaderProgram = compileProgram(gl, program.vertexSrc, program.fragmentSrc, attribMap); - const uniformData = {}; - - for (const i in program.uniformData) - { - const data = program.uniformData[i]; - - uniformData[i] = { - location: gl.getUniformLocation(shaderProgram, i), - value: defaultValue(data.type, data.size), - }; - } - - const glProgram = new GLProgram(shaderProgram, uniformData); - - program.glPrograms[this.renderer.CONTEXT_UID] = glProgram; - - return glProgram; - } - - /** - * Destroys this System and removes all its textures - */ - destroy() - { - // TODO implement destroy method for ShaderSystem - this.destroyed = true; - } -} diff --git a/packages/core/src/renderers/systems/shader/shader/compileProgram.js b/packages/core/src/renderers/systems/shader/shader/compileProgram.js deleted file mode 100644 index 8c6ccb9..0000000 --- a/packages/core/src/renderers/systems/shader/shader/compileProgram.js +++ /dev/null @@ -1,78 +0,0 @@ -/** - * @method compileProgram - * @memberof PIXI.glCore.shader - * @param gl {WebGLRenderingContext} The current WebGL context {WebGLProgram} - * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. - * @param fragmentSrc {string|string[]} The fragment shader source as an array of strings. - * @param attributeLocations {Object} An attribute location map that lets you manually set the attribute locations - * @return {WebGLProgram} the shader program - */ -export default function compileProgram(gl, vertexSrc, fragmentSrc, attributeLocations) -{ - const glVertShader = compileShader(gl, gl.VERTEX_SHADER, vertexSrc); - const glFragShader = compileShader(gl, gl.FRAGMENT_SHADER, fragmentSrc); - - let program = gl.createProgram(); - - gl.attachShader(program, glVertShader); - gl.attachShader(program, glFragShader); - - // optionally, set the attributes manually for the program rather than letting WebGL decide.. - if (attributeLocations) - { - for (const i in attributeLocations) - { - gl.bindAttribLocation(program, attributeLocations[i], i); - } - } - - gl.linkProgram(program); - - // if linking fails, then log and cleanup - if (!gl.getProgramParameter(program, gl.LINK_STATUS)) - { - console.error('Pixi.js Error: Could not initialize shader.'); - console.error('gl.VALIDATE_STATUS', gl.getProgramParameter(program, gl.VALIDATE_STATUS)); - console.error('gl.getError()', gl.getError()); - - // if there is a program info log, log it - if (gl.getProgramInfoLog(program) !== '') - { - console.warn('Pixi.js Warning: gl.getProgramInfoLog()', gl.getProgramInfoLog(program)); - } - - gl.deleteProgram(program); - program = null; - } - - // clean up some shaders - gl.deleteShader(glVertShader); - gl.deleteShader(glFragShader); - - return program; -} - -/** - * @private - * @param gl {WebGLRenderingContext} The current WebGL context {WebGLProgram} - * @param type {Number} the type, can be either VERTEX_SHADER or FRAGMENT_SHADER - * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. - * @return {WebGLShader} the shader - */ -function compileShader(gl, type, src) -{ - const shader = gl.createShader(type); - - gl.shaderSource(shader, src); - gl.compileShader(shader); - - if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) - { - console.warn(src); - console.error(gl.getShaderInfoLog(shader)); - - return null; - } - - return shader; -} diff --git a/packages/core/src/renderers/systems/shader/shader/defaultValue.js b/packages/core/src/renderers/systems/shader/shader/defaultValue.js deleted file mode 100644 index 97290bd..0000000 --- a/packages/core/src/renderers/systems/shader/shader/defaultValue.js +++ /dev/null @@ -1,79 +0,0 @@ -/** - * @method defaultValue - * @memberof PIXI.glCore.shader - * @param type {String} Type of value - * @param size {Number} - */ -export default function defaultValue(type, size) -{ - switch (type) - { - case 'float': - return 0; - - case 'vec2': - return new Float32Array(2 * size); - - case 'vec3': - return new Float32Array(3 * size); - - case 'vec4': - return new Float32Array(4 * size); - - case 'int': - case 'sampler2D': - case 'sampler2DArray': - return 0; - - case 'ivec2': - return new Int32Array(2 * size); - - case 'ivec3': - return new Int32Array(3 * size); - - case 'ivec4': - return new Int32Array(4 * size); - - case 'bool': - return false; - - case 'bvec2': - - return booleanArray(2 * size); - - case 'bvec3': - return booleanArray(3 * size); - - case 'bvec4': - return booleanArray(4 * size); - - case 'mat2': - return new Float32Array([1, 0, - 0, 1]); - - case 'mat3': - return new Float32Array([1, 0, 0, - 0, 1, 0, - 0, 0, 1]); - - case 'mat4': - return new Float32Array([1, 0, 0, 0, - 0, 1, 0, 0, - 0, 0, 1, 0, - 0, 0, 0, 1]); - } - - return null; -} - -function booleanArray(size) -{ - const array = new Array(size); - - for (let i = 0; i < array.length; i++) - { - array[i] = false; - } - - return array; -} diff --git a/packages/core/src/renderers/systems/shader/shader/index.js b/packages/core/src/renderers/systems/shader/shader/index.js deleted file mode 100644 index ba7a317..0000000 --- a/packages/core/src/renderers/systems/shader/shader/index.js +++ /dev/null @@ -1,8 +0,0 @@ -/** - * @namespace PIXI.glCore.shader - */ -export { default as compileProgram } from './compileProgram'; -export { default as defaultValue } from './defaultValue'; -export { default as setPrecision } from './setPrecision'; -export { default as mapSize } from './mapSize'; -export { default as mapType } from './mapType'; diff --git a/packages/core/src/renderers/systems/shader/shader/mapSize.js b/packages/core/src/renderers/systems/shader/shader/mapSize.js deleted file mode 100644 index b681554..0000000 --- a/packages/core/src/renderers/systems/shader/shader/mapSize.js +++ /dev/null @@ -1,33 +0,0 @@ -const GLSL_TO_SIZE = { - float: 1, - vec2: 2, - vec3: 3, - vec4: 4, - - int: 1, - ivec2: 2, - ivec3: 3, - ivec4: 4, - - bool: 1, - bvec2: 2, - bvec3: 3, - bvec4: 4, - - mat2: 4, - mat3: 9, - mat4: 16, - - sampler2D: 1, -}; - -/** - * @method mapSize - * @memberof PIXI.glCore.shader - * @param type {String} - * @return {Number} - */ -export default function mapSize(type) -{ - return GLSL_TO_SIZE[type]; -} diff --git a/packages/core/src/renderers/systems/shader/shader/mapType.js b/packages/core/src/renderers/systems/shader/shader/mapType.js deleted file mode 100644 index d288b7c..0000000 --- a/packages/core/src/renderers/systems/shader/shader/mapType.js +++ /dev/null @@ -1,45 +0,0 @@ -let GL_TABLE = null; - -const GL_TO_GLSL_TYPES = { - FLOAT: 'float', - FLOAT_VEC2: 'vec2', - FLOAT_VEC3: 'vec3', - FLOAT_VEC4: 'vec4', - - INT: 'int', - INT_VEC2: 'ivec2', - INT_VEC3: 'ivec3', - INT_VEC4: 'ivec4', - - BOOL: 'bool', - BOOL_VEC2: 'bvec2', - BOOL_VEC3: 'bvec3', - BOOL_VEC4: 'bvec4', - - FLOAT_MAT2: 'mat2', - FLOAT_MAT3: 'mat3', - FLOAT_MAT4: 'mat4', - - SAMPLER_2D: 'sampler2D', - SAMPLER_CUBE: 'samplerCube', - SAMPLER_2D_ARRAY: 'sampler2DArray', -}; - -export default function mapType(gl, type) -{ - if (!GL_TABLE) - { - const typeNames = Object.keys(GL_TO_GLSL_TYPES); - - GL_TABLE = {}; - - for (let i = 0; i < typeNames.length; ++i) - { - const tn = typeNames[i]; - - GL_TABLE[gl[tn]] = GL_TO_GLSL_TYPES[tn]; - } - } - - return GL_TABLE[type]; -} diff --git a/packages/core/src/renderers/systems/shader/shader/setPrecision.js b/packages/core/src/renderers/systems/shader/shader/setPrecision.js deleted file mode 100644 index e2c1f34..0000000 --- a/packages/core/src/renderers/systems/shader/shader/setPrecision.js +++ /dev/null @@ -1,18 +0,0 @@ -/** - * Sets the float precision on the shader. If the precision is already present this function will do nothing - * @param {string} src the shader source - * @param {string} precision The float precision of the shader. Options are 'lowp', 'mediump' or 'highp'. - * - * @return {string} modified shader source - */ -export default function setPrecision(src, precision) -{ - src = src.trim(); - - if (src.substring(0, 9) !== 'precision' && src.substring(0, 1) !== '#') - { - return `precision ${precision} float;\n${src}`; - } - - return src; -} diff --git a/packages/core/src/renderers/systems/textures/GLTexture.js b/packages/core/src/renderers/systems/textures/GLTexture.js deleted file mode 100644 index c3fb8dc..0000000 --- a/packages/core/src/renderers/systems/textures/GLTexture.js +++ /dev/null @@ -1,33 +0,0 @@ -export default class GLTexture -{ - constructor(texture) - { - /** - * The WebGL texture - * - * @member {WebGLTexture} - */ - this.texture = texture; - - this.width = -1; - this.height = -1; - - /** - * Texture contents dirty flag - * @member {number} - */ - this.dirtyId = -1; - - /** - * Texture style dirty flag - * @type {number} - */ - this.dirtyStyleId = -1; - - /** - * Whether mip levels has to be generated - * @type {boolean} - */ - this.mipmap = false; - } -} diff --git a/packages/core/src/renderers/systems/textures/TextureGCSystem.js b/packages/core/src/renderers/systems/textures/TextureGCSystem.js deleted file mode 100644 index b089938..0000000 --- a/packages/core/src/renderers/systems/textures/TextureGCSystem.js +++ /dev/null @@ -1,110 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; -import { GC_MODES } from '@pixi/constants'; -import { settings } from '@pixi/settings'; - -/** - * TextureGarbageCollector. This class manages the GPU and ensures that it does not get clogged - * up with textures that are no longer being used. - * - * @class - * @memberof PIXI - */ -export default class TextureGCSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this.count = 0; - this.checkCount = 0; - this.maxIdle = settings.GC_MAX_IDLE; - this.checkCountMax = settings.GC_MAX_CHECK_COUNT; - this.mode = settings.GC_MODE; - } - - /** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ - postrender() - { - this.count++; - - if (this.mode === GC_MODES.MANUAL) - { - return; - } - - this.checkCount++; - - if (this.checkCount > this.checkCountMax) - { - this.checkCount = 0; - - this.run(); - } - } - - /** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ - run() - { - const tm = this.renderer.texture; - const managedTextures = tm.managedTextures; - let wasRemoved = false; - - for (let i = 0; i < managedTextures.length; i++) - { - const texture = managedTextures[i]; - - // only supports non generated textures at the moment! - if (!texture.frameBuffer && this.count - texture.touched > this.maxIdle) - { - tm.destroyTexture(texture, true); - managedTextures[i] = null; - wasRemoved = true; - } - } - - if (wasRemoved) - { - let j = 0; - - for (let i = 0; i < managedTextures.length; i++) - { - if (managedTextures[i] !== null) - { - managedTextures[j++] = managedTextures[i]; - } - } - - managedTextures.length = j; - } - } - - /** - * Removes all the textures within the specified displayObject and its children from the GPU - * - * @param {PIXI.DisplayObject} displayObject - the displayObject to remove the textures from. - */ - unload(displayObject) - { - const tm = this.renderer.textureSystem; - - // only destroy non generated textures - if (displayObject._texture && displayObject._texture._glRenderTargets) - { - tm.destroyTexture(displayObject._texture); - } - - for (let i = displayObject.children.length - 1; i >= 0; i--) - { - this.unload(displayObject.children[i]); - } - } -} diff --git a/packages/core/src/renderers/systems/textures/TextureSystem.js b/packages/core/src/renderers/systems/textures/TextureSystem.js deleted file mode 100644 index ccc5de9..0000000 --- a/packages/core/src/renderers/systems/textures/TextureSystem.js +++ /dev/null @@ -1,281 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; -import GLTexture from './GLTexture'; -import { removeItems } from '@pixi/utils'; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class TextureSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - // TODO set to max textures... - this.boundTextures = [ - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - ]; - - this.currentLocation = -1; - - this.managedTextures = []; - } - - /** - * Sets up the renderer context and necessary buffers. - * - * @private - */ - contextChange() - { - const gl = this.gl = this.renderer.gl; - - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - - // TODO move this.. to a nice make empty textures class.. - this.emptyTextures = {}; - - const emptyTexture2D = new GLTexture(gl.createTexture()); - - gl.bindTexture(gl.TEXTURE_2D, emptyTexture2D.texture); - gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array(4)); - - this.emptyTextures[gl.TEXTURE_2D] = emptyTexture2D; - this.emptyTextures[gl.TEXTURE_CUBE_MAP] = new GLTexture(gl.createTexture()); - - gl.bindTexture(gl.TEXTURE_CUBE_MAP, this.emptyTextures[gl.TEXTURE_CUBE_MAP].texture); - - let i; - - for (i = 0; i < 6; i++) - { - gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, null); - } - - gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_MAG_FILTER, gl.LINEAR); - gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_MIN_FILTER, gl.LINEAR); - - for (i = 0; i < this.boundTextures.length; i++) - { - this.bind(null, i); - } - } - - bind(texture, location) - { - const gl = this.gl; - - location = location || 0; - - if (this.currentLocation !== location) - { - this.currentLocation = location; - gl.activeTexture(gl.TEXTURE0 + location); - } - - if (texture) - { - texture = texture.baseTexture || texture; - - if (texture.valid) - { - texture.touched = this.renderer.textureGC.count; - - const glTexture = texture._glTextures[this.CONTEXT_UID] || this.initTexture(texture); - - gl.bindTexture(texture.target, glTexture.texture); - - if (glTexture.dirtyId !== texture.dirtyId) - { - this.updateTexture(texture); - } - - this.boundTextures[location] = texture; - } - } - else - { - gl.bindTexture(gl.TEXTURE_2D, this.emptyTextures[gl.TEXTURE_2D].texture); - this.boundTextures[location] = null; - } - } - - unbind(texture) - { - const gl = this.gl; - - for (let i = 0; i < this.boundTextures.length; i++) - { - if (this.boundTextures[i] === texture) - { - if (this.currentLocation !== i) - { - gl.activeTexture(gl.TEXTURE0 + i); - this.currentLocation = i; - } - - gl.bindTexture(gl.TEXTURE_2D, this.emptyTextures[texture.target].texture); - this.boundTextures[i] = null; - } - } - } - - initTexture(texture) - { - const glTexture = new GLTexture(this.gl.createTexture()); - - // guarentee an update.. - glTexture.dirtyId = -1; - - texture._glTextures[this.CONTEXT_UID] = glTexture; - - this.managedTextures.push(texture); - texture.on('dispose', this.destroyTexture, this); - - return glTexture; - } - - updateTexture(texture) - { - const glTexture = texture._glTextures[this.CONTEXT_UID]; - const renderer = this.renderer; - - if (texture.resource && texture.resource.upload(renderer, texture, glTexture)) - { - // texture is uploaded, dont do anything! - } - else - { - // default, renderTexture-like logic - const width = texture.realWidth; - const height = texture.realHeight; - const gl = renderer.gl; - - if (glTexture.width !== width - || glTexture.height !== height - || glTexture.dirtyId < 0) - { - glTexture.width = width; - glTexture.height = height; - - gl.texImage2D(texture.target, 0, - texture.format, - width, - height, - 0, - texture.format, - texture.type, - null); - } - } - - // lets only update what changes.. - if (texture.dirtyStyleId !== glTexture.dirtyStyleId) - { - this.updateTextureStyle(texture); - } - glTexture.dirtyId = texture.dirtyId; - } - - /** - * Deletes the texture from WebGL - * - * @param {PIXI.BaseTexture|PIXI.Texture} texture - the texture to destroy - * @param {boolean} [skipRemove=false] - Whether to skip removing the texture from the TextureManager. - */ - destroyTexture(texture, skipRemove) - { - const gl = this.gl; - - texture = texture.baseTexture || texture; - - if (texture._glTextures[this.renderer.CONTEXT_UID]) - { - this.unbind(texture); - - gl.deleteTexture(texture._glTextures[this.renderer.CONTEXT_UID].texture); - texture.off('dispose', this.destroyTexture, this); - - delete texture._glTextures[this.renderer.CONTEXT_UID]; - - if (!skipRemove) - { - const i = this.managedTextures.indexOf(texture); - - if (i !== -1) - { - removeItems(this.managedTextures, i, 1); - } - } - } - } - - updateTextureStyle(texture) - { - const glTexture = texture._glTextures[this.CONTEXT_UID]; - - glTexture.mipmap = texture.mipmap && texture.isPowerOfTwo; - if (!glTexture) - { - return; - } - - if (texture.resource && texture.resource.style(this.renderer, texture, glTexture)) - { - // style is set, dont do anything! - } - else - { - this.setStyle(texture, glTexture); - } - - glTexture.dirtyStyleId = texture.dirtyStyleId; - } - - setStyle(texture, glTexture) - { - const gl = this.gl; - - if (glTexture.mipmap) - { - gl.generateMipmap(texture.target); - } - - gl.texParameteri(texture.target, gl.TEXTURE_WRAP_S, texture.wrapMode); - gl.texParameteri(texture.target, gl.TEXTURE_WRAP_T, texture.wrapMode); - - if (glTexture.mipmap) - { - /* eslint-disable max-len */ - gl.texParameteri(texture.target, gl.TEXTURE_MIN_FILTER, texture.scaleMode ? gl.LINEAR_MIPMAP_LINEAR : gl.NEAREST_MIPMAP_NEAREST); - /* eslint-disable max-len */ - } - else - { - gl.texParameteri(texture.target, gl.TEXTURE_MIN_FILTER, texture.scaleMode ? gl.LINEAR : gl.NEAREST); - } - - gl.texParameteri(texture.target, gl.TEXTURE_MAG_FILTER, texture.scaleMode ? gl.LINEAR : gl.NEAREST); - } -} diff --git a/packages/core/src/renderers/utils/ObjectRenderer.js b/packages/core/src/renderers/utils/ObjectRenderer.js deleted file mode 100644 index f67a273..0000000 --- a/packages/core/src/renderers/utils/ObjectRenderer.js +++ /dev/null @@ -1,48 +0,0 @@ -import WebGLSystem from '../systems/WebGLSystem'; - -/** - * Base for a common object renderer that can be used as a system renderer plugin. - * - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class ObjectRenderer extends WebGLSystem -{ - /** - * Starts the renderer and sets the shader - * - */ - start() - { - // set the shader.. - } - - /** - * Stops the renderer - * - */ - stop() - { - this.flush(); - } - - /** - * Stub method for rendering content and emptying the current batch. - * - */ - flush() - { - // flush! - } - - /** - * Renders an object - * - * @param {PIXI.DisplayObject} object - The object to render. - */ - render(object) // eslint-disable-line no-unused-vars - { - // render the object - } -} diff --git a/packages/core/src/renderers/utils/Quad.js b/packages/core/src/renderers/utils/Quad.js deleted file mode 100644 index 5c1517b..0000000 --- a/packages/core/src/renderers/utils/Quad.js +++ /dev/null @@ -1,26 +0,0 @@ -import Geometry from '../../geometry/Geometry'; - -/** - * Helper class to create a quad - * - * @class - * @memberof PIXI - */ -export default class Quad extends Geometry -{ - /** - * just a quad - */ - constructor() - { - super(); - - this.addAttribute('aVertexPosition', [ - 0, 0, - 1, 0, - 1, 1, - 0, 1, - ]) - .addIndex([0, 1, 3, 2]); - } -} diff --git a/packages/core/src/renderers/utils/QuadUv.js b/packages/core/src/renderers/utils/QuadUv.js deleted file mode 100644 index 97bda89..0000000 --- a/packages/core/src/renderers/utils/QuadUv.js +++ /dev/null @@ -1,103 +0,0 @@ -import Geometry from '../../geometry/Geometry'; -import Buffer from '../../geometry/Buffer'; - -/** - * Helper class to create a quad with uvs like in v4 - * - * @class - * @memberof PIXI - */ -export default class QuadUv extends Geometry -{ - constructor() - { - super(); - - /** - * An array of vertices - * - * @member {Float32Array} - */ - this.vertices = new Float32Array([ - -1, -1, - 1, -1, - 1, 1, - -1, 1, - ]); - - /** - * The Uvs of the quad - * - * @member {Float32Array} - */ - this.uvs = new Float32Array([ - 0, 0, - 1, 0, - 1, 1, - 0, 1, - ]); - - this.vertexBuffer = new Buffer(this.vertices); - this.uvBuffer = new Buffer(this.uvs); - - this.addAttribute('aVertexPosition', this.vertexBuffer) - .addAttribute('aTextureCoord', this.uvBuffer) - .addIndex([0, 1, 2, 0, 2, 3]); - } - - /** - * Maps two Rectangle to the quad. - * - * @param {PIXI.Rectangle} targetTextureFrame - the first rectangle - * @param {PIXI.Rectangle} destinationFrame - the second rectangle - * @return {PIXI.Quad} Returns itself. - */ - map(targetTextureFrame, destinationFrame) - { - let x = 0; // destinationFrame.x / targetTextureFrame.width; - let y = 0; // destinationFrame.y / targetTextureFrame.height; - - this.uvs[0] = x; - this.uvs[1] = y; - - this.uvs[2] = x + (destinationFrame.width / targetTextureFrame.width); - this.uvs[3] = y; - - this.uvs[4] = x + (destinationFrame.width / targetTextureFrame.width); - this.uvs[5] = y + (destinationFrame.height / targetTextureFrame.height); - - this.uvs[6] = x; - this.uvs[7] = y + (destinationFrame.height / targetTextureFrame.height); - - x = destinationFrame.x; - y = destinationFrame.y; - - this.vertices[0] = x; - this.vertices[1] = y; - - this.vertices[2] = x + destinationFrame.width; - this.vertices[3] = y; - - this.vertices[4] = x + destinationFrame.width; - this.vertices[5] = y + destinationFrame.height; - - this.vertices[6] = x; - this.vertices[7] = y + destinationFrame.height; - - this.invalidate(); - - return this; - } - - /** - * legacy upload method, just marks buffers dirty - * @returns {PIXI.QuadUv} Returns itself. - */ - invalidate() - { - this.vertexBuffer._updateID++; - this.uvBuffer._updateID++; - - return this; - } -} diff --git a/packages/core/src/renderers/utils/checkMaxIfStatmentsInShader.js b/packages/core/src/renderers/utils/checkMaxIfStatmentsInShader.js deleted file mode 100644 index 8047c96..0000000 --- a/packages/core/src/renderers/utils/checkMaxIfStatmentsInShader.js +++ /dev/null @@ -1,58 +0,0 @@ -const fragTemplate = [ - 'precision mediump float;', - 'void main(void){', - 'float test = 0.1;', - '%forloop%', - 'gl_FragColor = vec4(0.0);', - '}', -].join('\n'); - -export default function checkMaxIfStatmentsInShader(maxIfs, gl) -{ - if (maxIfs === 0) - { - throw new Error('Invalid value of `0` passed to `checkMaxIfStatementsInShader`'); - } - - const shader = gl.createShader(gl.FRAGMENT_SHADER); - - while (true) // eslint-disable-line no-constant-condition - { - const fragmentSrc = fragTemplate.replace(/%forloop%/gi, generateIfTestSrc(maxIfs)); - - gl.shaderSource(shader, fragmentSrc); - gl.compileShader(shader); - - if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) - { - maxIfs = (maxIfs / 2) | 0; - } - else - { - // valid! - break; - } - } - - return maxIfs; -} - -function generateIfTestSrc(maxIfs) -{ - let src = ''; - - for (let i = 0; i < maxIfs; ++i) - { - if (i > 0) - { - src += '\nelse '; - } - - if (i < maxIfs - 1) - { - src += `if(test == ${i}.0){}`; - } - } - - return src; -} diff --git a/packages/core/src/renderers/utils/mapWebGLBlendModesToPixi.js b/packages/core/src/renderers/utils/mapWebGLBlendModesToPixi.js deleted file mode 100644 index 10ffe4d..0000000 --- a/packages/core/src/renderers/utils/mapWebGLBlendModesToPixi.js +++ /dev/null @@ -1,47 +0,0 @@ -import { BLEND_MODES } from '@pixi/constants'; - -/** - * Maps gl blend combinations to WebGL. - * - * @memberof PIXI - * @function mapWebGLBlendModesToPixi - * @private - * @param {WebGLRenderingContext} gl - The rendering context. - * @param {string[]} [array=[]] - The array to output into. - * @return {string[]} Mapped modes. - */ -export default function mapWebGLBlendModesToPixi(gl, array = []) -{ - // TODO - premultiply alpha would be different. - // add a boolean for that! - array[BLEND_MODES.NORMAL] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.ADD] = [gl.ONE, gl.DST_ALPHA]; - array[BLEND_MODES.MULTIPLY] = [gl.DST_COLOR, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.SCREEN] = [gl.ONE, gl.ONE_MINUS_SRC_COLOR]; - array[BLEND_MODES.OVERLAY] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.DARKEN] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.LIGHTEN] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.COLOR_DODGE] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.COLOR_BURN] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.HARD_LIGHT] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.SOFT_LIGHT] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.DIFFERENCE] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.EXCLUSION] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.HUE] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.SATURATION] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.COLOR] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.LUMINOSITY] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.NONE] = [0, 0]; - - // not-premultiplied blend modes - array[BLEND_MODES.NORMAL_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.ADD_NPM] = [gl.SRC_ALPHA, gl.DST_ALPHA, gl.ONE, gl.DST_ALPHA]; - array[BLEND_MODES.SCREEN_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_COLOR, gl.ONE, gl.ONE_MINUS_SRC_COLOR]; - - // not-premultiplied blend modes - array[BLEND_MODES.NORMAL_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.ADD_NPM] = [gl.SRC_ALPHA, gl.DST_ALPHA, gl.ONE, gl.DST_ALPHA]; - array[BLEND_MODES.SCREEN_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_COLOR, gl.ONE, gl.ONE_MINUS_SRC_COLOR]; - - return array; -} diff --git a/packages/core/src/shader/GLProgram.js b/packages/core/src/shader/GLProgram.js new file mode 100644 index 0000000..92980ca --- /dev/null +++ b/packages/core/src/shader/GLProgram.js @@ -0,0 +1,43 @@ +/** + * Helper class to create a webGL Program + * + * @class + * @private + */ +export default class GLProgram +{ + constructor(program, uniformData) + { + /** + * The shader program + * + * @member {WebGLProgram} + */ + this.program = program; + + /** + * holds the uniform data which contains uniform locations + * and current uniform values used for caching and preventing undeed GPU commands + * @type {Object} + */ + this.uniformData = uniformData; + + /** + * uniformGroups holds the various upload functions for the shader. Each uniform group + * and program have a unique upload function generated. + * @type {Object} + */ + this.uniformGroups = {}; + } + + /** + * Destroys this program + * TODO + */ + destroy() + { + this.uniformData = null; + this.uniformGroups = null; + this.program = null; + } +} diff --git a/packages/core/src/shader/Program.js b/packages/core/src/shader/Program.js index 4f9971e..4b4007a 100644 --- a/packages/core/src/shader/Program.js +++ b/packages/core/src/shader/Program.js @@ -1,6 +1,11 @@ -import * as shaderUtils from '../renderers/systems/shader/shader'; +// import * as from '../systems/shader/shader'; +import { setPrecision, + defaultValue, + compileProgram, + mapSize, + mapType, + getTestContext } from './utils'; import { ProgramCache } from '@pixi/utils'; -import getTestContext from './getTestContext'; import defaultFragment from './defaultProgram.frag'; import defaultVertex from './defaultProgram.vert'; import { settings } from '@pixi/settings'; @@ -34,8 +39,8 @@ */ this.fragmentSrc = fragmentSrc || Program.defaultFragmentSrc; - this.vertexSrc = shaderUtils.setPrecision(this.vertexSrc, settings.PRECISION_VERTEX); - this.fragmentSrc = shaderUtils.setPrecision(this.fragmentSrc, settings.PRECISION_FRAGMENT); + this.vertexSrc = setPrecision(this.vertexSrc, settings.PRECISION_VERTEX); + this.fragmentSrc = setPrecision(this.fragmentSrc, settings.PRECISION_FRAGMENT); // currently this does not extract structs only default types this.extractData(this.vertexSrc, this.fragmentSrc); @@ -62,7 +67,7 @@ if (gl) { - const program = shaderUtils.compileProgram(gl, vertexSrc, fragmentSrc); + const program = compileProgram(gl, vertexSrc, fragmentSrc); this.attributeData = this.getAttributeData(program, gl); this.uniformData = this.getUniformData(program, gl); @@ -95,13 +100,13 @@ for (let i = 0; i < totalAttributes; i++) { const attribData = gl.getActiveAttrib(program, i); - const type = shaderUtils.mapType(gl, attribData.type); + const type = mapType(gl, attribData.type); /*eslint-disable */ const data = { type: type, name: attribData.name, - size: shaderUtils.mapSize(type), + size: mapSize(type), location: 0, }; /* eslint-enable */ @@ -145,14 +150,14 @@ const name = uniformData.name.replace(/\[.*?\]/, ''); const isArray = uniformData.name.match(/\[.*?\]/, ''); - const type = shaderUtils.mapType(gl, uniformData.type); + const type = mapType(gl, uniformData.type); /*eslint-disable */ uniforms[name] = { type: type, size: uniformData.size, isArray:isArray, - value: shaderUtils.defaultValue(type, uniformData.size), + value: defaultValue(type, uniformData.size), }; /* eslint-enable */ } diff --git a/packages/core/src/shader/ShaderSystem.js b/packages/core/src/shader/ShaderSystem.js new file mode 100644 index 0000000..40b8af8 --- /dev/null +++ b/packages/core/src/shader/ShaderSystem.js @@ -0,0 +1,171 @@ +import System from '../System'; +import GLProgram from './GLProgram'; +import { generateUniformsSync, + defaultValue, + compileProgram } from './utils'; + +let UID = 0; + +/** + * Helper class to create a webGL Texture + * + * @class + * @memberof PIXI.systems + * @extends PIXI.System + */ +export default class ShaderSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - A reference to the current renderer + */ + constructor(renderer) + { + super(renderer); + + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = null; + + this.shader = null; + this.program = null; + + this.id = UID++; + } + + contextChange(gl) + { + this.gl = gl; + } + + /** + * Changes the current shader to the one given in parameter + * + * @param {PIXI.Shader} shader - the new shader + * @param {boolean} dontSync - false if the shader should automatically sync its uniforms. + * @returns {PIXI.glCore.glProgram} the glProgram that belongs to the shader. + */ + bind(shader, dontSync) + { + shader.uniforms.globals = this.renderer.globalUniforms; + + const program = shader.program; + const glProgram = program.glPrograms[this.renderer.CONTEXT_UID] || this.generateShader(shader); + + this.shader = shader; + + // TODO - some current pixi plugins bypass this.. so it not safe to use yet.. + if (this.program !== program) + { + this.program = program; + this.gl.useProgram(glProgram.program); + } + + if (!dontSync) + { + this.syncUniformGroup(shader.uniformGroup); + } + + return glProgram; + } + + /** + * Uploads the uniforms values to the currently bound shader. + * + * @param {object} uniforms - the uniforms valiues that be applied to the current shader + */ + setUniforms(uniforms) + { + const shader = this.shader.program; + const glProgram = shader.glPrograms[this.renderer.CONTEXT_UID]; + + shader.syncUniforms(glProgram.uniformData, uniforms, this.renderer); + } + + syncUniformGroup(group) + { + const glProgram = this.getglProgram(); + + if (!group.static || group.dirtyId !== glProgram.uniformGroups[group.id]) + { + glProgram.uniformGroups[group.id] = group.dirtyId; + const syncFunc = group.syncUniforms[this.shader.program.id] || this.createSyncGroups(group); + + syncFunc(glProgram.uniformData, group.uniforms, this.renderer); + } + } + + createSyncGroups(group) + { + group.syncUniforms[this.shader.program.id] = generateUniformsSync(group, this.shader.program.uniformData); + + return group.syncUniforms[this.shader.program.id]; + } + + /** + * Returns the underlying GLShade rof the currently bound shader. + * This can be handy for when you to have a little more control over the setting of your uniforms. + * + * @return {PIXI.glCore.Shader} the glProgram for the currently bound Shader for this context + */ + getglProgram() + { + if (this.shader) + { + return this.shader.program.glPrograms[this.renderer.CONTEXT_UID]; + } + + return null; + } + + /** + * Generates a glProgram verion of the Shader provided. + * + * @private + * @param {PIXI.Shader} shader the shader that the glProgram will be based on. + * @return {PIXI.glCore.glProgram} A shiney new glProgram + */ + generateShader(shader) + { + const gl = this.gl; + + const program = shader.program; + + const attribMap = {}; + + for (const i in program.attributeData) + { + attribMap[i] = program.attributeData[i].location; + } + + const shaderProgram = compileProgram(gl, program.vertexSrc, program.fragmentSrc, attribMap); + const uniformData = {}; + + for (const i in program.uniformData) + { + const data = program.uniformData[i]; + + uniformData[i] = { + location: gl.getUniformLocation(shaderProgram, i), + value: defaultValue(data.type, data.size), + }; + } + + const glProgram = new GLProgram(shaderProgram, uniformData); + + program.glPrograms[this.renderer.CONTEXT_UID] = glProgram; + + return glProgram; + } + + /** + * Destroys this System and removes all its textures + */ + destroy() + { + // TODO implement destroy method for ShaderSystem + this.destroyed = true; + } +} diff --git a/packages/core/src/AbstractRenderer.js b/packages/core/src/AbstractRenderer.js new file mode 100644 index 0000000..64b2eb1 --- /dev/null +++ b/packages/core/src/AbstractRenderer.js @@ -0,0 +1,353 @@ +import { hex2string, hex2rgb } from '@pixi/utils'; +import { Matrix, Rectangle } from '@pixi/math'; +import { RENDERER_TYPE } from '@pixi/constants'; +import { settings } from '@pixi/settings'; +import { Container } from '@pixi/display'; +import RenderTexture from './renderTexture/RenderTexture'; +import EventEmitter from 'eventemitter3'; + +const tempMatrix = new Matrix(); + +/** + * 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 EventEmitter + * @memberof PIXI + */ +export default class AbstractRenderer extends EventEmitter +{ + // eslint-disable-next-line valid-jsdoc + /** + * @param {string} system - The name of the system this renderer is for. + * @param {object} [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.transparent=false] - If the render view is transparent, default false + * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false + * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. The + * resolution of the renderer retina would be 2. + * @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 {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when rendering, + * stopping pixel interpolation. + */ + constructor(system, options, arg2, arg3) + { + super(); + + // Support for constructor(system, screenWidth, screenHeight, options) + if (typeof options === 'number') + { + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + }, arg3); + } + + // 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 = RENDERER_TYPE.UNKNOWN; + + /** + * Measurements of the screen. (0, 0, screenWidth, screenHeight) + * + * Its safe to use as filterArea or hitArea for 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 1 + */ + this.resolution = options.resolution || settings.RESOLUTION; + + /** + * Whether the render view is transparent + * + * @member {boolean} + */ + this.transparent = options.transparent; + + /** + * Whether css dimensions of canvas view should be resized to screen dimensions automatically + * + * @member {boolean} + */ + this.autoResize = options.autoResize || false; + + /** + * Tracks the blend modes useful for this renderer. + * + * @member {object} + */ + this.blendModes = null; + + /** + * 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; + + /** + * If true PixiJS will Math.floor() x/y values when rendering, stopping pixel interpolation. + * Handy for crisp pixel art and speed on legacy devices. + * + * @member {boolean} + */ + this.roundPixels = options.roundPixels; + + /** + * The background color as a number. + * + * @member {number} + * @private + */ + this._backgroundColor = 0x000000; + + /** + * The background color as an [R, G, B] array. + * + * @member {number[]} + * @private + */ + this._backgroundColorRgba = [0, 0, 0, 0]; + + /** + * The background color as a string. + * + * @member {string} + * @private + */ + this._backgroundColorString = '#000000'; + + this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter + + /** + * This temporary display object used as the parent of the currently being rendered item + * + * @member {PIXI.DisplayObject} + * @private + */ + this._tempDisplayObjectParent = new Container(); + + /** + * The last root object that the renderer tried to render. + * + * @member {PIXI.DisplayObject} + * @private + */ + this._lastObjectRendered = this._tempDisplayObjectParent; + + /** + * Collection of plugins. + * @readonly + * @member {object} + */ + this.plugins = {}; + } + + /** + * Initialize the plugins. + * + * @protected + * @param {object} staticMap - The dictionary of staticly saved plugins. + */ + initPlugins(staticMap) + { + 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() + { + return this.view.width; + } + + /** + * Same as view.height, actual number of pixels in the canvas by vertical + * + * @member {number} + * @readonly + * @default 600 + */ + get height() + { + return this.view.height; + } + + /** + * Resizes the screen and canvas to the specified width and height + * Canvas dimensions are multiplied by resolution + * + * @param {number} screenWidth - the new width of the screen + * @param {number} screenHeight - the new height of the screen + */ + resize(screenWidth, screenHeight) + { + this.screen.width = screenWidth; + this.screen.height = screenHeight; + + this.view.width = screenWidth * this.resolution; + this.view.height = screenHeight * this.resolution; + + if (this.autoResize) + { + this.view.style.width = `${screenWidth}px`; + this.view.style.height = `${screenHeight}px`; + } + } + + /** + * 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 {PIXI.DisplayObject} displayObject - The displayObject the object will be generated from + * @param {number} scaleMode - Should be one of the scaleMode consts + * @param {number} resolution - The resolution / device pixel ratio of the texture being generated + * @param {PIXI.Rectangle} [region] - The region of the displayObject, that shall be rendered, + * if no region is specified, defaults to the local bounds of the displayObject. + * @return {PIXI.Texture} a texture of the graphics object + */ + generateTexture(displayObject, scaleMode, resolution, region) + { + region = region || displayObject.getLocalBounds(); + + // 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(region.width | 0, region.height | 0, scaleMode, resolution); + + tempMatrix.tx = -region.x; + tempMatrix.ty = -region.y; + + this.render(displayObject, renderTexture, false, tempMatrix, true); + + return renderTexture; + } + + /** + * Removes everything from the renderer and optionally removes the Canvas DOM element. + * + * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. + */ + destroy(removeView) + { + 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); + } + + this.plugins = null; + + this.type = RENDERER_TYPE.UNKNOWN; + + this.view = null; + + this.screen = null; + + this.resolution = 0; + + this.transparent = false; + + this.autoResize = false; + + this.blendModes = null; + + this.options = null; + + this.preserveDrawingBuffer = false; + this.clearBeforeRender = false; + + this.roundPixels = false; + + this._backgroundColor = 0; + this._backgroundColorRgba = null; + this._backgroundColorString = null; + + this._tempDisplayObjectParent = null; + this._lastObjectRendered = null; + } + + /** + * The background color to fill if not transparent + * + * @member {number} + */ + get backgroundColor() + { + return this._backgroundColor; + } + + set backgroundColor(value) // eslint-disable-line require-jsdoc + { + this._backgroundColor = value; + this._backgroundColorString = hex2string(value); + hex2rgb(value, this._backgroundColorRgba); + } +} diff --git a/packages/core/src/Renderer.js b/packages/core/src/Renderer.js new file mode 100644 index 0000000..2227e78 --- /dev/null +++ b/packages/core/src/Renderer.js @@ -0,0 +1,338 @@ +import AbstractRenderer from './AbstractRenderer'; +import { sayHello } from '@pixi/utils'; +import MaskSystem from './mask/MaskSystem'; +import StencilSystem from './mask/StencilSystem'; +import FilterSystem from './filters/FilterSystem'; +import FramebufferSystem from './framebuffer/FramebufferSystem'; +import RenderTextureSystem from './renderTexture/RenderTextureSystem'; +import TextureSystem from './textures/TextureSystem'; +import ProjectionSystem from './projection/ProjectionSystem'; +import StateSystem from './state/StateSystem'; +import GeometrySystem from './geometry/GeometrySystem'; +import ShaderSystem from './shader/ShaderSystem'; +import ContextSystem from './context/ContextSystem'; +import BatchSystem from './batch/BatchSystem'; +import TextureGCSystem from './textures/TextureGCSystem'; +import { RENDERER_TYPE } from '@pixi/constants'; +import UniformGroup from './shader/UniformGroup'; +import { Matrix } from '@pixi/math'; +import Runner from 'mini-runner'; + +/** + * 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 Render works by automatically managing webGLBatchs. + * 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 :) + * + * @class + * @memberof PIXI + * @extends PIXI.AbstractRenderer + */ +export default class Renderer extends AbstractRenderer +{ + // eslint-disable-next-line valid-jsdoc + /** + * + * @param {object} [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.transparent=false] - If the render view is transparent, default false + * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false + * @param {boolean} [options.antialias=false] - sets antialias. If not available natively then FXAA + * antialiasing is used + * @param {boolean} [options.forceFXAA=false] - forces FXAA antialiasing to be used over native. + * FXAA is faster, but may not always look as great + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. + * The resolution of the renderer retina would be 2. + * @param {boolean} [options.clearBeforeRender=true] - This sets if the renderer will clear + * the canvas or not before the new render pass. If you wish to set this to false, you *must* set + * preserveDrawingBuffer to `true`. + * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, + * enable this if you need to call toDataUrl on the webgl context. + * @param {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when + * rendering, stopping pixel interpolation. + * @param {number} [options.backgroundColor=0x000000] - The background color of the rendered area + * (shown if not transparent). + * @param {boolean} [options.legacy=false] - If true PixiJS will aim to ensure compatibility + * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. + * @param {string} [options.powerPreference] - Parameter passed to webgl context, set to "high-performance" + * for devices with dual graphics card + */ + constructor(options, arg2, arg3) + { + super('WebGL', options, arg2, arg3); + + /** + * The type of this renderer as a standardised const + * + * @member {number} + * @see PIXI.RENDERER_TYPE + */ + this.type = RENDERER_TYPE.WEBGL; + + // this will be set by the contextSystem (this.context) + this.gl = null; + this.CONTEXT_UID = 0; + this.legacy = !!options.legacy; + + // TODO legacy! + + // runners! + this.runners = { + destroy: new Runner('destroy'), + contextChange: new Runner('contextChange', 1), + reset: new Runner('reset'), + update: new Runner('update'), + postrender: new Runner('postrender'), + prerender: new Runner('prerender'), + resize: new Runner('resize', 2), + }; + + 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(GeometrySystem, 'geometry') + .addSystem(FramebufferSystem, 'framebuffer') + .addSystem(StencilSystem, 'stencil') + .addSystem(ProjectionSystem, 'projection') + .addSystem(TextureGCSystem, 'textureGC') + .addSystem(FilterSystem, 'filter') + .addSystem(RenderTextureSystem, 'renderTexture') + .addSystem(BatchSystem, 'batch'); + + this.initPlugins(Renderer.__plugins); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ + if (options.context) + { + this.context.initFromContext(options.context); + } + else + { + this.context.initFromOptions({ + alpha: this.transparent, + antialias: options.antialias, + premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', + stencil: true, + 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); + } + + /** + * Add a new system to the renderer. + * @param {class} ClassRef - Class reference + * @param {string} [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. + * @return {PIXI.Renderer} Return instance of renderer + */ + addSystem(ClassRef, name) + { + if (!name) + { + name = ClassRef.name; + } + + // TODO - read name from class.name.. + + /* + if(name.includes('System')) + { + name = name.replace('System', ''); + name = name.charAt(0).toLowerCase() + name.slice(1); + } + */ + + const system = new ClassRef(this); + + if (this[name]) + { + throw new Error(`Whoops! ${name} is already a manger`); + } + + this[name] = system; + + for (const i in this.runners) + { + this.runners[i].add(system); + } + + return this; + + /** + * 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. + */ + } + + /** + * Renders the object to its webGL view + * + * @param {PIXI.DisplayObject} displayObject - the object to be rendered + * @param {PIXI.RenderTexture} renderTexture - The render texture to render to. + * @param {boolean} [clear] - Should the canvas be cleared before the new render + * @param {PIXI.Transform} [transform] - A transform to apply to the render texture before rendering. + * @param {boolean} [skipUpdateTransform] - Should we skip the update transform pass? + */ + render(displayObject, renderTexture, clear, transform, skipUpdateTransform) + { + // can be handy to know! + this.renderingToScreen = !renderTexture; + + this.runners.prerender.run(); + this.emit('prerender'); + + // 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.parent; + + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = 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.run(); + + this.emit('postrender'); + } + + /** + * Resizes the webGL view to the specified width and height. + * + * @param {number} screenWidth - the new width of the screen + * @param {number} screenHeight - the new height of the screen + */ + resize(screenWidth, screenHeight) + { + AbstractRenderer.prototype.resize.call(this, screenWidth, screenHeight); + + this.runners.resize.run(screenWidth, screenHeight); + } + + /** + * Resets the WebGL state so you can render things however you fancy! + * + * @return {PIXI.Renderer} Returns itself. + */ + reset() + { + this.runners.reset.run(); + + return this; + } + + clear() + { + this.framebuffer.bind(); + this.framebuffer.clear(); + } + + /** + * Removes everything from the renderer (event listeners, spritebatch, etc...) + * + * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. + * See: https://github.com/pixijs/pixi.js/issues/2233 + */ + destroy(removeView) + { + this.runners.destroy.run(); + + // call base destroy + super.destroy(removeView); + + // TODO nullify all the managers.. + this.gl = null; + } + + /** + * 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 + * builds and excluding plugins. + * @name PIXI.Renderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. + */ + + /** + * Adds a plugin to the renderer. + * + * @method + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + static registerPlugin(pluginName, ctor) + { + Renderer.__plugins = Renderer.__plugins || {}; + Renderer.__plugins[pluginName] = ctor; + } +} diff --git a/packages/core/src/System.js b/packages/core/src/System.js new file mode 100644 index 0000000..05a10c8 --- /dev/null +++ b/packages/core/src/System.js @@ -0,0 +1,42 @@ +/** + * System is a base class used for extending systems used by the {@link PIXI.Renderer} + * @see PIXI.Renderer#addSystem + * @class + * @memberof PIXI + */ +export default class System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + /** + * The renderer this manager works for. + * + * @member {PIXI.Renderer} + */ + this.renderer = renderer; + + this.renderer.runners.contextChange.add(this); + } + + /** + * Generic method called when there is a WebGL context change. + * + */ + contextChange() + { + // do some codes init! + } + + /** + * Generic destroy methods to be overridden by the subclass + * + */ + destroy() + { + this.renderer.runners.contextChange.remove(this); + this.renderer = null; + } +} diff --git a/packages/core/src/batch/BatchSystem.js b/packages/core/src/batch/BatchSystem.js new file mode 100644 index 0000000..b04c249 --- /dev/null +++ b/packages/core/src/batch/BatchSystem.js @@ -0,0 +1,68 @@ +import System from '../System'; +import ObjectRenderer from './ObjectRenderer'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class BatchSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + super(renderer); + + /** + * An empty renderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.emptyRenderer = new ObjectRenderer(renderer); + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.currentRenderer = this.emptyRenderer; + } + + /** + * Changes the current renderer to the one given in parameter + * + * @param {PIXI.ObjectRenderer} objectRenderer - The object renderer to use. + */ + setObjectRenderer(objectRenderer) + { + if (this.currentRenderer === objectRenderer) + { + return; + } + + this.currentRenderer.stop(); + this.currentRenderer = objectRenderer; + + this.renderer.state.setState(objectRenderer.state); + + this.currentRenderer.start(); + } + + /** + * This should be called if you wish to do some custom rendering + * It will basically render anything that may be batched up such as sprites + * + */ + flush() + { + this.setObjectRenderer(this.emptyRenderer); + } + + reset() + { + this.setObjectRenderer(this.emptyRenderer); + } +} diff --git a/packages/core/src/batch/ObjectRenderer.js b/packages/core/src/batch/ObjectRenderer.js new file mode 100644 index 0000000..0c851a9 --- /dev/null +++ b/packages/core/src/batch/ObjectRenderer.js @@ -0,0 +1,48 @@ +import System from '../System'; + +/** + * Base for a common object renderer that can be used as a system renderer plugin. + * + * @class + * @extends PIXI.System + * @memberof PIXI + */ +export default class ObjectRenderer extends System +{ + /** + * Starts the renderer and sets the shader + * + */ + start() + { + // set the shader.. + } + + /** + * Stops the renderer + * + */ + stop() + { + this.flush(); + } + + /** + * Stub method for rendering content and emptying the current batch. + * + */ + flush() + { + // flush! + } + + /** + * Renders an object + * + * @param {PIXI.DisplayObject} object - The object to render. + */ + render(object) // eslint-disable-line no-unused-vars + { + // render the object + } +} diff --git a/packages/core/src/context/ContextSystem.js b/packages/core/src/context/ContextSystem.js new file mode 100644 index 0000000..301aab4 --- /dev/null +++ b/packages/core/src/context/ContextSystem.js @@ -0,0 +1,186 @@ +import System from '../System'; +import { settings } from '@pixi/settings'; + +let CONTEXT_UID = 0; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class ContextSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.webGLVersion = 1; + + this.handleContextLost = this.handleContextLost.bind(this); + this.handleContextRestored = this.handleContextRestored.bind(this); + + this.extensions = {}; + + renderer.view.addEventListener('webglcontextlost', this.handleContextLost, false); + renderer.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + } + + get isLost() + { + return (!this.gl || this.gl.isContextLost()); + } + + contextChange(gl) + { + this.gl = gl; + + // restore a context if it was previously lost + if (gl.isContextLost() && gl.getExtension('WEBGL_lose_context')) + { + gl.getExtension('WEBGL_lose_context').restoreContext(); + } + } + + initFromContext(gl) + { + this.gl = gl; + this.validateContext(gl); + this.renderer.gl = gl; + this.renderer.CONTEXT_UID = CONTEXT_UID++; + this.renderer.runners.contextChange.run(gl); + } + + initFromOptions(options) + { + const gl = this.createContext(this.renderer.view, options); + + this.initFromContext(gl); + } + + /** + * Helper class to create a webGL Context + * + * @class + * @memberof PIXI.glCore + * @param canvas {HTMLCanvasElement} the canvas element that we will get the context from + * @param options {Object} An options object that gets passed in to the canvas element containing the context attributes, + * see https://developer.mozilla.org/en/docs/Web/API/HTMLCanvasElement/getContext for the options available + * @return {WebGLRenderingContext} the WebGL context + */ + createContext(canvas, options) + { + let gl; + + if (settings.PREFER_WEBGL_2) + { + gl = canvas.getContext('webgl2', options); + } + + if (gl) + { + this.webGLVersion = 2; + } + else + { + this.webGLVersion = 1; + + gl = canvas.getContext('webgl', options) + || canvas.getContext('experimental-webgl', options); + + if (!gl) + { + // fail, not able to get a context + throw new Error('This browser does not support webGL. Try using the canvas renderer'); + } + } + + this.gl = gl; + + this.getExtensions(); + + return gl; + } + + getExtensions() + { + // time to set up default etensions that pixi uses.. + const gl = this.gl; + const extensions = this.extensions; + + if (this.webGLVersion === 1) + { + extensions.drawBuffers = gl.getExtension('WEBGL_draw_buffers'); + extensions.depthTexture = gl.getExtension('WEBKIT_WEBGL_depth_texture'); + extensions.floatTexture = gl.getExtension('OES_texture_float'); + extensions.loseContext = gl.getExtension('WEBGL_lose_context'); + + extensions.vertexArrayObject = gl.getExtension('OES_vertex_array_object') + || gl.getExtension('MOZ_OES_vertex_array_object') + || gl.getExtension('WEBKIT_OES_vertex_array_object'); + } + + // we don't use any specific WebGL 2 ones yet! + } + + /** + * Handles a lost webgl context + * + * @private + * @param {WebGLContextEvent} event - The context lost event. + */ + handleContextLost(event) + { + event.preventDefault(); + } + + /** + * Handles a restored webgl context + * + * @private + */ + handleContextRestored() + { + this.renderer.runners.contextChange.run(this.gl); + } + + destroy() + { + const view = this.renderer.view; + + // remove listeners + view.removeEventListener('webglcontextlost', this.handleContextLost); + view.removeEventListener('webglcontextrestored', this.handleContextRestored); + + this.gl.useProgram(null); + + if (this.extensions.loseContext) + { + this.extensions.loseContext.loseContext(); + } + } + + postrender() + { + this.gl.flush(); + } + + validateContext(gl) + { + const attributes = gl.getContextAttributes(); + + // this is going to be fairly simple for now.. but at least we have room to grow! + if (!attributes.stencil) + { + /* eslint-disable max-len */ + + /* eslint-disable no-console */ + console.warn('Provided WebGL context does not have a stencil buffer, masks may not render correctly'); + /* eslint-enable no-console */ + + /* eslint-enable max-len */ + } + } +} diff --git a/packages/core/src/filters/Filter.js b/packages/core/src/filters/Filter.js new file mode 100644 index 0000000..9d8cad8 --- /dev/null +++ b/packages/core/src/filters/Filter.js @@ -0,0 +1,140 @@ +import Shader from '../shader/Shader'; +import Program from '../shader/Program'; +import State from '../state/State'; +import { settings } from '@pixi/settings'; +// import extractUniformsFromSrc from './extractUniformsFromSrc'; +import defaultVertex from './defaultFilter.vert'; +import defaultFragment from './defaultFilter.frag'; + +// let math = require('../../math'); +/** + * @class + * @memberof PIXI + * @extends PIXI.Shader + */ +export default class Filter extends Shader +{ + /** + * @param {string} [vertexSrc] - The source of the vertex shader. + * @param {string} [fragmentSrc] - The source of the fragment shader. + * @param {object} [uniforms] - Custom uniforms to use to augment the built-in ones. + */ + constructor(vertexSrc, fragmentSrc, uniforms) + { + const program = Program.from(vertexSrc, fragmentSrc); + + super(program, uniforms); + + /** + * The padding of the filter. Some filters require extra space to breath such as a blur. + * Increasing this will add extra width and height to the bounds of the object that the + * filter is applied to. + * + * @member {number} + */ + this.padding = 0; + + /** + * The resolution of the filter. Setting this to be lower will lower the quality but + * increase the performance of the filter. + * + * @member {number} + */ + this.resolution = settings.RESOLUTION; + + /** + * If enabled is true the filter is applied, if false it will not. + * + * @member {boolean} + */ + this.enabled = true; + + /** + * If enabled, PixiJS will fit the filter area into boundaries for better performance. + * Switch it off if it does not work for specific shader. + * + * @member {boolean} + */ + this.autoFit = true; + + /** + * Legacy filters use position and uvs from attributes + * @member {boolean} + * @readonly + */ + this.legacy = !!this.program.attributeData.aTextureCoord; + + /** + * the webGL state the filter requires to render + * @member {PIXI.State} + */ + this.state = new State(); + } + + /** + * Applies the filter + * + * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + * @param {boolean} clear - Should the output be cleared before rendering to it + * @param {object} [currentState] - It's current state of filter. + * There are some useful properties in the currentState : + * target, filters, sourceFrame, destinationFrame, renderTarget, resolution + */ + apply(filterManager, input, output, clear, currentState, derp) // eslint-disable-line no-unused-vars + { + // do as you please! + + filterManager.applyFilter(this, input, output, clear, currentState, derp); + + // or just do a regular render.. + } + + /** + * Sets the blendmode of the filter + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL + */ + get blendMode() + { + return this.state.blendMode; + } + + set blendMode(value) // eslint-disable-line require-jsdoc + { + this.state.blendMode = value; + } + + /** + * The default vertex shader source + * + * @static + * @constant + */ + static get defaultVertexSrc() + { + return defaultVertex; + } + + /** + * The default fragment shader source + * + * @static + * @constant + */ + static get defaultFragmentSrc() + { + return defaultFragment; + } +} + +/** + * Used for caching shader IDs + * + * @static + * @private + */ +Filter.SOURCE_KEY_MAP = {}; + diff --git a/packages/core/src/filters/FilterSystem.js b/packages/core/src/filters/FilterSystem.js new file mode 100644 index 0000000..3174318 --- /dev/null +++ b/packages/core/src/filters/FilterSystem.js @@ -0,0 +1,431 @@ +import System from '../System'; + +import RenderTexture from '../renderTexture/RenderTexture'; +import Quad from '../utils/Quad'; +import QuadUv from '../utils/QuadUv'; +import { Rectangle } from '@pixi/math'; +import * as filterTransforms from './filterTransforms'; +import bitTwiddle from 'bit-twiddle'; +import UniformGroup from '../shader/UniformGroup'; +import { DRAW_MODES } from '@pixi/constants'; + +// +/** + * @ignore + * @class + */ +class FilterState +{ + /** + * + */ + constructor() + { + this.renderTexture = null; + this.sourceFrame = new Rectangle(); + this.destinationFrame = new Rectangle(); + this.filters = []; + this.target = null; + this.legacy = false; + this.resolution = 1; + } +} + +/** + * @class + * @memberof PIXI.systems + * @extends PIXI.System + */ +export default class FilterSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + /** + * stores a bunch of PO2 textures used for filtering + * @type {Object} + */ + this.texturePool = {}; + + /** + * a pool for storing filter states, save us creating new ones each tick + * @type {Array} + */ + this.statePool = []; + + /** + * A very simple geometry used when drawing a filter effect to the screen + * @type {Quad} + */ + this.quad = new Quad(); + + this.quadUv = new QuadUv(); + + /** + * Temporary rect for maths + * @type {PIXI.Rectangle} + */ + this.tempRect = new Rectangle(); + + this.activeState = {}; + + /** + * this uniform group is attached to filter uniforms when used + * @type {UniformGroup} + */ + this.globalUniforms = new UniformGroup({ + sourceFrame: this.tempRect, + destinationFrame: this.tempRect, + + // legacy variables + filterArea: new Float32Array(4), + filterClamp: new Float32Array(4), + }, true); + } + + /** + * Adds a new filter to the System. + * + * @param {PIXI.DisplayObject} target - The target of the filter to render. + * @param {PIXI.Filter[]} filters - The filters to apply. + */ + push(target, filters) + { + const renderer = this.renderer; + const filterStack = this.renderer.renderTexture.defaultFilterStack; + const state = this.statePool.pop() || new FilterState(); + + let resolution = filters[0].resolution; + let padding = filters[0].padding; + let autoFit = filters[0].autoFit; + let legacy = filters[0].legacy; + + for (let i = 1; i < filters.length; i++) + { + const filter = filters[i]; + + // lets use the lowest resolution.. + resolution = Math.min(resolution, filter.resolution); + // and the largest amount of padding! + padding = Math.max(padding, filter.padding); + // only auto fit if all filters are autofit + autoFit = autoFit || filter.autoFit; + + legacy = legacy || filter.legacy; + } + + filterStack.push(state); + + state.resolution = resolution; + + state.legacy = legacy; + + // round to whole number based on resolution + // TODO move that to the shader too? + state.sourceFrame = target.filterArea || target.getBounds(true); + + state.sourceFrame.pad(padding); + + if (autoFit) + { + state.sourceFrame.fit(this.renderer.renderTexture.destinationFrame); + } + + state.sourceFrame.round(resolution); + + state.renderTexture = this.getPotFilterTexture(state.sourceFrame.width, state.sourceFrame.height, resolution); + state.filters = filters; + + state.destinationFrame.width = state.renderTexture.width; + state.destinationFrame.height = state.renderTexture.height; + + state.renderTexture.filterFrame = state.sourceFrame; + renderer.renderTexture.bind(state.renderTexture, state.sourceFrame);// /, state.destinationFrame); + renderer.renderTexture.clear(); + } + + /** + * Pops off the filter and applies it. + * + */ + pop() + { + const renderer = this.renderer; + const filterStack = renderer.renderTexture.defaultFilterStack; + const state = filterStack.pop(); + const filters = state.filters; + + this.activeState = state; + + const globalUniforms = this.globalUniforms.uniforms; + + globalUniforms.sourceFrame = state.sourceFrame; + globalUniforms.destinationFrame = state.destinationFrame; + globalUniforms.resolution = state.resolution; + + // only update the rect if its legacy.. + if (state.legacy) + { + const filterArea = globalUniforms.filterArea; + const filterClamp = globalUniforms.filterClamp; + + filterArea[0] = state.destinationFrame.width; + filterArea[1] = state.destinationFrame.height; + filterArea[2] = state.sourceFrame.x; + filterArea[3] = state.sourceFrame.y; + + filterClamp[0] = 0.5 / state.resolution / state.destinationFrame.width; + filterClamp[1] = 0.5 / state.resolution / state.destinationFrame.height; + filterClamp[2] = (state.sourceFrame.width - 0.5) / state.resolution / state.destinationFrame.width; + filterClamp[3] = (state.sourceFrame.height - 0.5) / state.resolution / state.destinationFrame.height; + } + + this.globalUniforms.update(); + + const lastState = filterStack[filterStack.length - 1]; + + if (filters.length === 1) + { + filters[0].apply(this, state.renderTexture, lastState.renderTexture, false, state); + + this.returnFilterTexture(state.renderTexture); + } + else + { + let flip = state.renderTexture; + let flop = this.getPotFilterTexture( + flip.width, + flip.height, + state.resolution + ); + + flop.filterFrame = flip.filterFrame; + + let i = 0; + + for (i = 0; i < filters.length - 1; ++i) + { + filters[i].apply(this, flip, flop, true, state); + + const t = flip; + + flip = flop; + flop = t; + } + + filters[i].apply(this, flip, lastState.renderTexture, false, state); + + this.returnFilterTexture(flip); + this.returnFilterTexture(flop); + } + + this.statePool.push(state); + } + + /** + * Draws a filter. + * + * @param {PIXI.Filter} filter - The filter to draw. + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + * @param {boolean} clear - Should the output be cleared before rendering to it + */ + applyFilter(filter, input, output, clear) + { + const renderer = this.renderer; + + renderer.renderTexture.bind(output, output ? output.filterFrame : null); + + if (clear) + { + // gl.disable(gl.SCISSOR_TEST); + renderer.renderTexture.clear(); + // gl.enable(gl.SCISSOR_TEST); + } + + // set the uniforms.. + filter.uniforms.uSampler = input; + filter.uniforms.filterGlobals = this.globalUniforms; + + // TODO make it so that the order of this does not matter.. + // because it does at the moment cos of global uniforms. + // they need to get resynced + + renderer.state.setState(filter.state); + renderer.shader.bind(filter); + + if (filter.legacy) + { + this.quadUv.map(input._frame, input.filterFrame); + + renderer.geometry.bind(this.quadUv); + renderer.geometry.draw(DRAW_MODES.TRIANGLES); + } + else + { + renderer.geometry.bind(this.quad); + renderer.geometry.draw(DRAW_MODES.TRIANGLE_STRIP); + } + } + + /** + * Calculates the mapped matrix. + * + * TODO playing around here.. this is temporary - (will end up in the shader) + * this returns a matrix that will normalise map filter cords in the filter to screen space + * + * @param {PIXI.Matrix} outputMatrix - the matrix to output to. + * @return {PIXI.Matrix} The mapped matrix. + */ + calculateScreenSpaceMatrix(outputMatrix) + { + const currentState = this.activeState; + + return filterTransforms.calculateScreenSpaceMatrix( + outputMatrix, + currentState.sourceFrame, + currentState.destinationFrame + ); + } + + /** + * This will map the filter coord so that a texture can be used based on the transform of a sprite + * + * @param {PIXI.Matrix} outputMatrix - The matrix to output to. + * @param {PIXI.Sprite} sprite - The sprite to map to. + * @return {PIXI.Matrix} The mapped matrix. + */ + calculateSpriteMatrix(outputMatrix, sprite) + { + const currentState = this.activeState; + + return filterTransforms.calculateSpriteMatrix( + outputMatrix, + currentState.sourceFrame, + currentState.destinationFrame, + sprite + ); + } + + /** + * Destroys this Filter System. + * + * @param {boolean} [contextLost=false] context was lost, do not free shaders + * + */ + destroy(contextLost = false) + { + if (!contextLost) + { + this.emptyPool(); + } + else + { + this.texturePool = {}; + } + } + + /** + * Gets a Power-of-Two render texture. + * + * TODO move to a seperate class could be on renderer? + * also - could cause issue with multiple contexts? + * + * @private + * @param {WebGLRenderingContext} gl - The webgl rendering context + * @param {number} minWidth - The minimum width of the render target. + * @param {number} minHeight - The minimum height of the render target. + * @param {number} resolution - The resolution of the render target. + * @return {PIXI.RenderTarget} The new render target. + */ + getPotFilterTexture(minWidth, minHeight, resolution) + { + minWidth = bitTwiddle.nextPow2(minWidth); + minHeight = bitTwiddle.nextPow2(minHeight); + resolution = resolution || 1; + + const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + + if (!this.texturePool[key]) + { + this.texturePool[key] = []; + } + + let renderTexture = this.texturePool[key].pop(); + + if (!renderTexture) + { + // temporary bypass cache.. + // internally - this will cause a texture to be bound.. + renderTexture = RenderTexture.create({ + width: minWidth, + height: minHeight, + resolution, + }); + } + + return renderTexture; + } + + /** + * Gets extra render texture to use inside current filter + * + * @param {number} resolution resolution of the renderTexture + * @returns {PIXI.RenderTexture} + */ + getFilterTexture(resolution) + { + const rt = this.activeState.renderTexture; + + const filterTexture = this.getPotFilterTexture(rt.width, rt.height, resolution || rt.baseTexture.resolution); + + filterTexture.filterFrame = rt.filterFrame; + + return filterTexture; + } + + /** + * Frees a render texture back into the pool. + * + * @param {PIXI.RenderTarget} renderTexture - The renderTarget to free + */ + returnFilterTexture(renderTexture) + { + renderTexture.filterFrame = null; + + const base = renderTexture.baseTexture; + + const minWidth = base.width; + const minHeight = base.height; + + const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + + this.texturePool[key].push(renderTexture); + } + + /** + * Empties the texture pool. + * + */ + emptyPool() + { + for (const i in this.texturePool) + { + const textures = this.texturePool[i]; + + if (textures) + { + for (let j = 0; j < textures.length; j++) + { + textures[j].destroy(true); + } + } + } + + this.texturePool = {}; + } +} diff --git a/packages/core/src/filters/defaultFilter.frag b/packages/core/src/filters/defaultFilter.frag new file mode 100644 index 0000000..a14c1be --- /dev/null +++ b/packages/core/src/filters/defaultFilter.frag @@ -0,0 +1,22 @@ +varying vec2 vTextureCoord; +varying vec2 vFilterCoord; + +uniform sampler2D uSampler; +uniform sampler2D filterSampler; + +void main(void){ + vec4 masky = texture2D(filterSampler, vFilterCoord); + vec4 sample = texture2D(uSampler, vTextureCoord); + vec4 color; + if(mod(vFilterCoord.x, 1.0) > 0.5) + { + color = vec4(1.0, 0.0, 0.0, 1.0); + } + else + { + color = vec4(0.0, 1.0, 0.0, 1.0); + } + // gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0); + gl_FragColor = mix(sample, masky, 0.5); + gl_FragColor *= sample.a; +} diff --git a/packages/core/src/filters/defaultFilter.vert b/packages/core/src/filters/defaultFilter.vert new file mode 100644 index 0000000..e1febca --- /dev/null +++ b/packages/core/src/filters/defaultFilter.vert @@ -0,0 +1,14 @@ +attribute vec2 aVertexPosition; +attribute vec2 aTextureCoord; + +uniform mat3 projectionMatrix; +uniform mat3 filterMatrix; + +varying vec2 vTextureCoord; +varying vec2 vFilterCoord; + +void main(void){ + gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); + vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy; + vTextureCoord = aTextureCoord ; +} diff --git a/packages/core/src/filters/filterTransforms.js b/packages/core/src/filters/filterTransforms.js new file mode 100644 index 0000000..87a3cd6 --- /dev/null +++ b/packages/core/src/filters/filterTransforms.js @@ -0,0 +1,50 @@ +import { Matrix } from '@pixi/math'; + +/** + * Calculates the mapped matrix + * @param filterArea {Rectangle} The filter area + * @param sprite {Sprite} the target sprite + * @param outputMatrix {Matrix} @alvin + * @private + */ +// this returns a matrix that will normalise map filter cords in the filter to screen space +export function calculateScreenSpaceMatrix(outputMatrix, filterArea, textureSize) +{ + // TODO unwrap? + const mappedMatrix = outputMatrix.identity(); + + mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); + + mappedMatrix.scale(textureSize.width, textureSize.height); + + return mappedMatrix; +} + +export function calculateNormalizedScreenSpaceMatrix(outputMatrix, filterArea, textureSize) +{ + const mappedMatrix = outputMatrix.identity(); + + mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); + + const translateScaleX = (textureSize.width / filterArea.width); + const translateScaleY = (textureSize.height / filterArea.height); + + mappedMatrix.scale(translateScaleX, translateScaleY); + + return mappedMatrix; +} + +// this will map the filter coord so that a texture can be used based on the transform of a sprite +export function calculateSpriteMatrix(outputMatrix, filterArea, textureSize, sprite) +{ + const orig = sprite._texture.orig; + const mappedMatrix = outputMatrix.set(textureSize.width, 0, 0, textureSize.height, filterArea.x, filterArea.y); + const worldTransform = sprite.worldTransform.copyTo(Matrix.TEMP_MATRIX); + + worldTransform.invert(); + mappedMatrix.prepend(worldTransform); + mappedMatrix.scale(1.0 / orig.width, 1.0 / orig.height); + mappedMatrix.translate(sprite.anchor.x, sprite.anchor.y); + + return mappedMatrix; +} diff --git a/packages/core/src/filters/spriteMask/SpriteMaskFilter.js b/packages/core/src/filters/spriteMask/SpriteMaskFilter.js new file mode 100644 index 0000000..91697b7 --- /dev/null +++ b/packages/core/src/filters/spriteMask/SpriteMaskFilter.js @@ -0,0 +1,63 @@ +import Filter from '../Filter'; +import { Matrix } from '@pixi/math'; +import vertex from './spriteMaskFilter.vert'; +import fragment from './spriteMaskFilter.frag'; +import { default as TextureMatrix } from '../../textures/TextureMatrix'; + +/** + * The SpriteMaskFilter class + * + * @class + * @extends PIXI.Filter + * @memberof PIXI + */ +export default class SpriteMaskFilter extends Filter +{ + /** + * @param {PIXI.Sprite} sprite - the target sprite + */ + constructor(sprite) + { + const maskMatrix = new Matrix(); + + super(vertex, fragment); + + sprite.renderable = false; + + this.maskSprite = sprite; + this.maskMatrix = maskMatrix; + } + + /** + * Applies the filter + * + * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + */ + apply(filterManager, input, output) + { + const maskSprite = this.maskSprite; + const tex = this.maskSprite.texture; + + if (!tex.valid) + { + return; + } + if (!tex.transform) + { + // margin = 0.0, let it bleed a bit, shader code becomes easier + // assuming that atlas textures were made with 1-pixel padding + tex.transform = new TextureMatrix(tex, 0.0); + } + tex.transform.update(); + + this.uniforms.mask = tex; + this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite) + .prepend(tex.transform.mapCoord); + this.uniforms.alpha = maskSprite.worldAlpha; + this.uniforms.maskClamp = tex.transform.uClampFrame; + + filterManager.applyFilter(this, input, output); + } +} diff --git a/packages/core/src/filters/spriteMask/spriteMaskFilter.frag b/packages/core/src/filters/spriteMask/spriteMaskFilter.frag new file mode 100644 index 0000000..0e4aef8 --- /dev/null +++ b/packages/core/src/filters/spriteMask/spriteMaskFilter.frag @@ -0,0 +1,23 @@ +varying vec2 vMaskCoord; +varying vec2 vTextureCoord; + +uniform sampler2D uSampler; +uniform sampler2D mask; +uniform float alpha; +uniform vec4 maskClamp; + +void main(void) +{ + float clip = step(3.5, + step(maskClamp.x, vMaskCoord.x) + + step(maskClamp.y, vMaskCoord.y) + + step(vMaskCoord.x, maskClamp.z) + + step(vMaskCoord.y, maskClamp.w)); + + vec4 original = texture2D(uSampler, vTextureCoord); + vec4 masky = texture2D(mask, vMaskCoord); + + original *= (masky.r * masky.a * alpha * clip); + + gl_FragColor = original; +} diff --git a/packages/core/src/filters/spriteMask/spriteMaskFilter.vert b/packages/core/src/filters/spriteMask/spriteMaskFilter.vert new file mode 100644 index 0000000..d23a30c --- /dev/null +++ b/packages/core/src/filters/spriteMask/spriteMaskFilter.vert @@ -0,0 +1,16 @@ +attribute vec2 aVertexPosition; +attribute vec2 aTextureCoord; + +uniform mat3 projectionMatrix; +uniform mat3 otherMatrix; + +varying vec2 vMaskCoord; +varying vec2 vTextureCoord; + +void main(void) +{ + gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); + + vTextureCoord = aTextureCoord; + vMaskCoord = ( otherMatrix * vec3( aTextureCoord, 1.0) ).xy; +} diff --git a/packages/core/src/framebuffer/FrameBuffer.js b/packages/core/src/framebuffer/FrameBuffer.js new file mode 100644 index 0000000..84e9fcc --- /dev/null +++ b/packages/core/src/framebuffer/FrameBuffer.js @@ -0,0 +1,106 @@ +import Texture from '../textures/BaseTexture'; +import { FORMATS, TYPES } from '@pixi/constants'; + +/** + * Frame buffer + * @class + * @memberof PIXI + */ +export default class FrameBuffer +{ + constructor(width, height) + { + this.width = width || 100; + this.height = height || 100; + + this.stencil = false; + this.depth = false; + + this.dirtyId = 0; + this.dirtyFormat = 0; + this.dirtySize = 0; + + this.depthTexture = null; + this.colorTextures = []; + + this.glFrameBuffers = {}; + } + + get colorTexture() + { + return this.colorTextures[0]; + } + + addColorTexture(index, texture) + { + // TODO add some validation to the texture - same width / height etc? + this.colorTextures[index || 0] = texture || new Texture(null, { scaleMode: 0, + resolution: 1, + mipmap: false, + width: this.width, + height: this.height });// || new Texture(); + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + addDepthTexture(texture) + { + /* eslint-disable max-len */ + this.depthTexture = texture || new Texture(null, { scaleMode: 0, + resolution: 1, + width: this.width, + height: this.height, + mipmap: false, + format: FORMATS.DEPTH_COMPONENT, + type: TYPES.UNSIGNED_SHORT });// UNSIGNED_SHORT; + /* eslint-disable max-len */ + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + enableDepth() + { + this.depth = true; + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + enableStencil() + { + this.stencil = true; + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + resize(width, height) + { + if (width === this.width && height === this.height) return; + + this.width = width; + this.height = height; + + this.dirtyId++; + this.dirtySize++; + + for (let i = 0; i < this.colorTextures.length; i++) + { + this.colorTextures[i].setSize(width, height); + } + + if (this.depthTexture) + { + this.depthTexture.setSize(width, height); + } + } +} diff --git a/packages/core/src/framebuffer/FramebufferSystem.js b/packages/core/src/framebuffer/FramebufferSystem.js new file mode 100644 index 0000000..2cc2437 --- /dev/null +++ b/packages/core/src/framebuffer/FramebufferSystem.js @@ -0,0 +1,246 @@ +import System from '../System'; +import { Rectangle } from '@pixi/math'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class FramebufferSystem extends System +{ + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + contextChange() + { + this.gl = this.renderer.gl; + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + this.current = null; + this.viewport = new Rectangle(); + + this.drawBufferExtension = this.renderer.context.extensions.drawBuffers; + } + + bind(framebuffer, frame) + { + const gl = this.gl; + + this.current = framebuffer; + + if (framebuffer) + { + // TODO cacheing layer! + + const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID] || this.initFramebuffer(framebuffer); + + gl.bindFramebuffer(gl.FRAMEBUFFER, fbo.framebuffer); + // makesure all textures are unbound.. + + // now check for updates... + if (fbo.dirtyId !== framebuffer.dirtyId) + { + fbo.dirtyId = framebuffer.dirtyId; + + if (fbo.dirtyFormat !== framebuffer.dirtyFormat) + { + fbo.dirtyFormat = framebuffer.dirtyFormat; + this.updateFramebuffer(framebuffer); + } + else if (fbo.dirtySize !== framebuffer.dirtySize) + { + fbo.dirtySize = framebuffer.dirtySize; + this.resizeFramebuffer(framebuffer); + } + } + + for (let i = 0; i < framebuffer.colorTextures.length; i++) + { + if (framebuffer.colorTextures[i].texturePart) + { + this.renderer.texture.unbind(framebuffer.colorTextures[i].texture); + } + else + { + this.renderer.texture.unbind(framebuffer.colorTextures[i]); + } + } + + if (framebuffer.depthTexture) + { + this.renderer.texture.unbind(framebuffer.depthTexture); + } + + if (frame) + { + this.setViewport(frame.x, frame.y, frame.width, frame.height); + } + else + { + this.setViewport(0, 0, framebuffer.width, framebuffer.height); + } + } + else + { + gl.bindFramebuffer(gl.FRAMEBUFFER, null); + + if (frame) + { + this.setViewport(frame.x, frame.y, frame.width, frame.height); + } + else + { + this.setViewport(0, 0, this.renderer.width, this.renderer.height); + } + } + } + + setViewport(x, y, width, height) + { + const v = this.viewport; + + if (v.width !== width || v.height !== height || v.x !== x || v.y !== y) + { + v.x = x; + v.y = y; + v.width = width; + v.height = height; + + this.gl.viewport(x, y, width, height); + } + } + + get size() + { + if (this.current) + { + // TODO store temp + return { x: 0, y: 0, width: this.current.width, height: this.current.height }; + } + + return { x: 0, y: 0, width: this.renderer.width, height: this.renderer.height }; + } + + clear(r, g, b, a) + { + const gl = this.gl; + + // TODO clear color can be set only one right? + gl.clearColor(r, g, b, a); + gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); + } + + // private functions... + + initFramebuffer(framebuffer) + { + const gl = this.gl; + + // TODO - make this a class? + const fbo = { + framebuffer: gl.createFramebuffer(), + stencil: null, + dirtyId: 0, + dirtyFormat: 0, + dirtySize: 0, + }; + + framebuffer.glFrameBuffers[this.CONTEXT_UID] = fbo; + + return fbo; + } + + resizeFramebuffer(framebuffer) + { + const gl = this.gl; + + if (framebuffer.stencil || framebuffer.depth) + { + gl.bindRenderbuffer(gl.RENDERBUFFER, this.stencil); + gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); + } + } + + updateFramebuffer(framebuffer) + { + const gl = this.gl; + + const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID]; + + // bind the color texture + const colorTextures = framebuffer.colorTextures; + + let count = colorTextures.length; + + if (!this.drawBufferExtension) + { + count = Math.min(count, 1); + } + + const activeTextures = []; + + for (let i = 0; i < count; i++) + { + const texture = framebuffer.colorTextures[i]; + + if (texture.texturePart) + { + this.renderer.texture.bind(texture.texture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.COLOR_ATTACHMENT0 + i, + gl.TEXTURE_CUBE_MAP_NEGATIVE_X + texture.side, + texture.texture._glTextures[this.CONTEXT_UID].texture, + 0); + } + else + { + this.renderer.texture.bind(texture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.COLOR_ATTACHMENT0 + i, + gl.TEXTURE_2D, + texture._glTextures[this.CONTEXT_UID].texture, + 0); + } + + activeTextures.push(gl.COLOR_ATTACHMENT0 + i); + } + + if (this.drawBufferExtension && activeTextures.length > 1) + { + this.drawBufferExtension.drawBuffersWEBGL(activeTextures); + } + + if (framebuffer.depthTexture) + { + const depthTextureExt = this.renderer.context.extensions.depthTexture; + + if (depthTextureExt) + { + const depthTexture = framebuffer.depthTexture; + + this.renderer.texture.bind(depthTexture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.DEPTH_ATTACHMENT, + gl.TEXTURE_2D, + depthTexture._glTextures[this.CONTEXT_UID].texture, + 0); + } + } + + if (framebuffer.stencil || framebuffer.depth) + { + fbo.stencil = gl.createRenderbuffer(); + + gl.bindRenderbuffer(gl.RENDERBUFFER, fbo.stencil); + + // TODO.. this is depth AND stencil? + gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, fbo.stencil); + gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); + // fbo.enableStencil(); + } + } +} diff --git a/packages/core/src/geometry/GLBuffer.js b/packages/core/src/geometry/GLBuffer.js new file mode 100644 index 0000000..0477e8a --- /dev/null +++ b/packages/core/src/geometry/GLBuffer.js @@ -0,0 +1,9 @@ +export default class GLBuffer +{ + constructor(buffer) + { + this.buffer = buffer; + this.updateID = -1; + this.byteLength = -1; + } +} diff --git a/packages/core/src/geometry/GeometrySystem.js b/packages/core/src/geometry/GeometrySystem.js new file mode 100644 index 0000000..959f827 --- /dev/null +++ b/packages/core/src/geometry/GeometrySystem.js @@ -0,0 +1,388 @@ +import System from '../System'; +import GLBuffer from './GLBuffer'; + +const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class GeometrySystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this._activeGeometry = null; + this._activeVao = null; + + this.hasVao = true; + this.hasInstance = true; + } + + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + contextChange() + { + const gl = this.gl = this.renderer.gl; + + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + + // webgl2 + if (!gl.createVertexArray) + { + // webgl 1! + let nativeVaoExtension = this.renderer.context.extensions.vertexArrayObject; + + if (this.renderer.legacy) + { + nativeVaoExtension = null; + } + + if (nativeVaoExtension) + { + gl.createVertexArray = () => + nativeVaoExtension.createVertexArrayOES(); + + gl.bindVertexArray = (vao) => + nativeVaoExtension.bindVertexArrayOES(vao); + + gl.deleteVertexArray = (vao) => + nativeVaoExtension.deleteVertexArrayOES(vao); + } + else + { + this.hasVao = false; + gl.createVertexArray = () => + { + // empty + }; + + gl.bindVertexArray = () => + { + // empty + }; + + gl.deleteVertexArray = () => + { + // empty + }; + } + } + + if (!gl.vertexAttribDivisor) + { + const instanceExt = gl.getExtension('ANGLE_instanced_arrays'); + + if (instanceExt) + { + gl.vertexAttribDivisor = (a, b) => + instanceExt.vertexAttribDivisorANGLE(a, b); + + gl.drawElementsInstanced = (a, b, c, d, e) => + instanceExt.drawElementsInstancedANGLE(a, b, c, d, e); + + gl.drawArraysInstanced = (a, b, c, d) => + instanceExt.drawArraysInstancedANGLE(a, b, c, d); + } + else + { + this.hasInstance = false; + } + } + } + + /** + * Binds geometry so that is can be drawn. Creating a Vao if required + * @private + * @param {PIXI.Geometry} geometry instance of geometry to bind + */ + bind(geometry, shader) + { + shader = shader || this.renderer.shader.shader; + + const gl = this.gl; + + // not sure the best way to address this.. + // currently different shaders require different VAOs for the same geometry + // Still mulling over the best way to solve this one.. + // will likely need to modify the shader attribute locations at run time! + let vaos = geometry.glVertexArrayObjects[this.CONTEXT_UID]; + + if (!vaos) + { + geometry.glVertexArrayObjects[this.CONTEXT_UID] = vaos = {}; + } + + const vao = vaos[shader.program.id] || this.initGeometryVao(geometry, shader.program); + + this._activeGeometry = geometry; + + if (this._activeVao !== vao) + { + this._activeVao = vao; + + if (this.hasVao) + { + gl.bindVertexArray(vao); + } + else + { + this.activateVao(geometry, shader.program); + } + } + + // TODO - optimise later! + // don't need to loop through if nothing changed! + // maybe look to add an 'autoupdate' to geometry? + this.updateBuffers(); + } + + reset() + { + this.unbind(); + } + + updateBuffers() + { + const geometry = this._activeGeometry; + const gl = this.gl; + + for (let i = 0; i < geometry.buffers.length; i++) + { + const buffer = geometry.buffers[i]; + + const glBuffer = buffer._glBuffers[this.CONTEXT_UID]; + + if (buffer._updateID !== glBuffer.updateID) + { + glBuffer.updateID = buffer._updateID; + + // TODO can cache this on buffer! maybe added a getter / setter? + const type = buffer.index ? gl.ELEMENT_ARRAY_BUFFER : gl.ARRAY_BUFFER; + const drawType = buffer.static ? gl.STATIC_DRAW : gl.DYNAMIC_DRAW; + + gl.bindBuffer(type, glBuffer.buffer); + + if (glBuffer.byteLength >= buffer.data.byteLength) + { + // offset is always zero for now! + gl.bufferSubData(type, 0, buffer.data); + } + else + { + gl.bufferData(type, buffer.data, drawType); + } + } + } + } + + checkCompatability(geometry, program) + { + // geometry must have at least all the attributes that the shader requires. + const geometryAttributes = geometry.attributes; + const shaderAttributes = program.attributeData; + + for (const j in shaderAttributes) + { + if (!geometryAttributes[j]) + { + throw new Error(`shader and geometry incompatible, geometry missing the "${j}" attribute`); + } + } + } + + /** + * Creates a Vao with the same structure as the geometry and stores it on the geometry. + * @private + * @param {PIXI.Geometry} geometry instance of geometry to to generate Vao for + */ + initGeometryVao(geometry, program) + { + this.checkCompatability(geometry, program); + + const gl = this.gl; + const CONTEXT_UID = this.CONTEXT_UID; + const buffers = geometry.buffers; + const attributes = geometry.attributes; + + const tempStride = {}; + const tempStart = {}; + + for (const j in buffers) + { + tempStride[j] = 0; + tempStart[j] = 0; + } + + for (const j in attributes) + { + if (!attributes[j].size && program.attributeData[j]) + { + attributes[j].size = program.attributeData[j].size; + } + + tempStride[attributes[j].buffer] += attributes[j].size * byteSizeMap[attributes[j].type]; + } + + for (const j in attributes) + { + const attribute = attributes[j]; + const attribSize = attribute.size; + + if (attribute.stride === undefined) + { + if (tempStride[attribute.buffer] === attribSize * byteSizeMap[attribute.type]) + { + attribute.stride = 0; + } + else + { + attribute.stride = tempStride[attribute.buffer]; + } + } + + if (attribute.start === undefined) + { + attribute.start = tempStart[attribute.buffer]; + + tempStart[attribute.buffer] += attribSize * byteSizeMap[attribute.type]; + } + } + + // first update - and create the buffers! + // only create a gl buffer if it actually gets + for (let i = 0; i < buffers.length; i++) + { + const buffer = buffers[i]; + + if (!buffer._glBuffers[CONTEXT_UID]) + { + buffer._glBuffers[CONTEXT_UID] = new GLBuffer(gl.createBuffer()); + } + } + + // TODO - maybe make this a data object? + // lets wait to see if we need to first! + const vao = gl.createVertexArray(); + + gl.bindVertexArray(vao); + + this.activateVao(geometry, program); + + gl.bindVertexArray(null); + + // add it to the cache! + geometry.glVertexArrayObjects[this.CONTEXT_UID][program.id] = vao; + + return vao; + } + + activateVao(geometry, program) + { + const gl = this.gl; + const CONTEXT_UID = this.CONTEXT_UID; + const buffers = geometry.buffers; + const attributes = geometry.attributes; + + if (geometry.indexBuffer) + { + // first update the index buffer if we have one.. + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, geometry.indexBuffer._glBuffers[CONTEXT_UID].buffer); + } + + let lastBuffer = null; + + // add a new one! + for (const j in attributes) + { + const attribute = attributes[j]; + const buffer = buffers[attribute.buffer]; + const glBuffer = buffer._glBuffers[CONTEXT_UID]; + + if (program.attributeData[j]) + { + if (lastBuffer !== glBuffer) + { + gl.bindBuffer(gl.ARRAY_BUFFER, glBuffer.buffer); + + lastBuffer = glBuffer; + } + + const location = program.attributeData[j].location; + + // TODO introduce state again + // we can optimise this for older devices that have no VAOs + gl.enableVertexAttribArray(location); + + gl.vertexAttribPointer(location, + attribute.size, + attribute.type || gl.FLOAT, + attribute.normalized, + attribute.stride, + attribute.start); + + if (attribute.instance) + { + // TODO calculate instance count based of this... + if (this.hasInstance) + { + gl.vertexAttribDivisor(location, 1); + } + else + { + throw new Error('geometry error, GPU Instancing is not supported on this device'); + } + } + } + } + } + + draw(type, size, start, instanceCount) + { + const gl = this.gl; + const geometry = this._activeGeometry; + + // TODO.. this should not change so maybe cache the function? + + if (geometry.indexBuffer) + { + if (geometry.instanced) + { + /* eslint-disable max-len */ + gl.drawElementsInstanced(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2, instanceCount || 1); + /* eslint-enable max-len */ + } + else + { + gl.drawElements(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2); + } + } + else + if (geometry.instanced) + { + // TODO need a better way to calculate size.. + gl.drawArraysInstanced(type, start, size || geometry.getSize(), instanceCount || 1); + } + else + { + gl.drawArrays(type, start, size || geometry.getSize()); + } + + return this; + } + + unbind() + { + this.gl.bindVertexArray(null); + this._activeVao = null; + this._activeGeometry = null; + } +} diff --git a/packages/core/src/geometry/utils/setVertexAttribArrays.js b/packages/core/src/geometry/utils/setVertexAttribArrays.js new file mode 100644 index 0000000..23b6959 --- /dev/null +++ b/packages/core/src/geometry/utils/setVertexAttribArrays.js @@ -0,0 +1,54 @@ +// var GL_MAP = {}; + +/** + * @param gl {WebGLRenderingContext} The current WebGL context + * @param attribs {*} + * @param state {*} + */ +export default function setVertexAttribArrays(gl, attribs, state) +{ + let i; + + if (state) + { + const tempAttribState = state.tempAttribState; + const attribState = state.attribState; + + for (i = 0; i < tempAttribState.length; i++) + { + tempAttribState[i] = false; + } + + // set the new attribs + for (i = 0; i < attribs.length; i++) + { + tempAttribState[attribs[i].attribute.location] = true; + } + + for (i = 0; i < attribState.length; i++) + { + if (attribState[i] !== tempAttribState[i]) + { + attribState[i] = tempAttribState[i]; + + if (state.attribState[i]) + { + gl.enableVertexAttribArray(i); + } + else + { + gl.disableVertexAttribArray(i); + } + } + } + } + else + { + for (i = 0; i < attribs.length; i++) + { + const attrib = attribs[i]; + + gl.enableVertexAttribArray(attrib.attribute.location); + } + } +} diff --git a/packages/core/src/index.js b/packages/core/src/index.js index 58c456a..fd1f547 100644 --- a/packages/core/src/index.js +++ b/packages/core/src/index.js @@ -1,30 +1,30 @@ import * as resources from './textures/resources'; -import * as systems from './renderers/systems'; +import * as systems from './systems'; -export { resources }; export { systems }; +export { resources }; -export { default as Renderer } from './renderers/Renderer'; -export { default as AbstractRenderer } from './renderers/AbstractRenderer'; -export { default as FrameBuffer } from './textures/FrameBuffer'; +export { default as System } from './System'; +export { default as Renderer } from './Renderer'; +export { default as AbstractRenderer } from './AbstractRenderer'; +export { default as FrameBuffer } from './framebuffer/FrameBuffer'; export { default as CubeTexture } from './textures/CubeTexture'; export { default as BaseTexture } from './textures/BaseTexture'; export { default as Texture } from './textures/Texture'; export { default as TextureMatrix } from './textures/TextureMatrix'; -export { default as RenderTexture } from './textures/RenderTexture'; -export { default as BaseRenderTexture } from './textures/BaseRenderTexture'; +export { default as RenderTexture } from './renderTexture/RenderTexture'; +export { default as BaseRenderTexture } from './renderTexture/BaseRenderTexture'; export { default as TextureUvs } from './textures/TextureUvs'; -export { default as State } from './renderers/State'; -export { default as ObjectRenderer } from './renderers/utils/ObjectRenderer'; -export { default as Quad } from './renderers/utils/Quad'; -export { default as QuadUv } from './renderers/utils/QuadUv'; -export { default as checkMaxIfStatmentsInShader } from './renderers/utils/checkMaxIfStatmentsInShader'; +export { default as State } from './state/State'; +export { default as ObjectRenderer } from './batch/ObjectRenderer'; +export { default as Quad } from './utils/Quad'; +export { default as QuadUv } from './utils/QuadUv'; +export { default as checkMaxIfStatmentsInShader } from './shader/utils/checkMaxIfStatmentsInShader'; export { default as Shader } from './shader/Shader'; export { default as Program } from './shader/Program'; export { default as UniformGroup } from './shader/UniformGroup'; -export { default as SpriteMaskFilter } from './renderers/filters/spriteMask/SpriteMaskFilter'; -export { default as Filter } from './renderers/filters/Filter'; +export { default as SpriteMaskFilter } from './filters/spriteMask/SpriteMaskFilter'; +export { default as Filter } from './filters/Filter'; export { default as Attribute } from './geometry/Attribute'; export { default as Buffer } from './geometry/Buffer'; export { default as Geometry } from './geometry/Geometry'; - diff --git a/packages/core/src/mask/MaskSystem.js b/packages/core/src/mask/MaskSystem.js new file mode 100644 index 0000000..99174c2 --- /dev/null +++ b/packages/core/src/mask/MaskSystem.js @@ -0,0 +1,199 @@ +import System from '../System'; +import AlphaMaskFilter from '../filters/spriteMask/SpriteMaskFilter'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class MaskSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + // TODO - we don't need both! + this.scissor = false; + this.scissorData = null; + this.scissorRenderTarget = null; + + this.enableScissor = false; + + this.alphaMaskPool = []; + this.alphaMaskIndex = 0; + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.DisplayObject} target - Display Object to push the mask to + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + push(target, maskData) + { + // TODO the root check means scissor rect will not + // be used on render textures more info here: + // https://github.com/pixijs/pixi.js/pull/3545 + + if (maskData.texture) + { + this.pushSpriteMask(target, maskData); + } + else if (this.enableScissor + && !this.scissor + && this.renderer._activeRenderTarget.root + && !this.renderer.stencil.stencilMaskStack.length + && maskData.isFastRect()) + { + const matrix = maskData.worldTransform; + + let rot = Math.atan2(matrix.b, matrix.a); + + // use the nearest degree! + rot = Math.round(rot * (180 / Math.PI)); + + if (rot % 90) + { + this.pushStencilMask(maskData); + } + else + { + this.pushScissorMask(target, maskData); + } + } + else + { + this.pushStencilMask(maskData); + } + } + + /** + * Removes the last mask from the mask stack and doesn't return it. + * + * @param {PIXI.DisplayObject} target - Display Object to pop the mask from + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + pop(target, maskData) + { + if (maskData.texture) + { + this.popSpriteMask(target, maskData); + } + else if (this.enableScissor && !this.renderer.stencil.stencilMaskStack.length) + { + this.popScissorMask(target, maskData); + } + else + { + this.popStencilMask(target, maskData); + } + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.RenderTarget} target - Display Object to push the sprite mask to + * @param {PIXI.Sprite} maskData - Sprite to be used as the mask + */ + pushSpriteMask(target, maskData) + { + let alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; + + if (!alphaMaskFilter) + { + alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; + } + + alphaMaskFilter[0].resolution = this.renderer.resolution; + alphaMaskFilter[0].maskSprite = maskData; + + // TODO - may cause issues! + target.filterArea = maskData.getBounds(true); + + this.renderer.filterSystem.pushFilter(target, alphaMaskFilter); + + this.alphaMaskIndex++; + } + + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popSpriteMask() + { + this.renderer.filterSystem.popFilter(); + this.alphaMaskIndex--; + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + pushStencilMask(maskData) + { + this.renderer.batch.flush(); + this.renderer.stencil.pushStencil(maskData); + } + + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popStencilMask() + { + // this.renderer.currentRenderer.stop(); + this.renderer.stencil.popStencil(); + } + + /** + * + * @param {PIXI.DisplayObject} target - Display Object to push the mask to + * @param {PIXI.Graphics} maskData - The masking data. + */ + pushScissorMask(target, maskData) + { + maskData.renderable = true; + + const renderTarget = this.renderer._activeRenderTarget; + + const bounds = maskData.getBounds(); + + bounds.fit(renderTarget.size); + maskData.renderable = false; + + this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); + + const resolution = this.renderer.resolution; + + this.renderer.gl.scissor( + bounds.x * resolution, + (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, + bounds.width * resolution, + bounds.height * resolution + ); + + this.scissorRenderTarget = renderTarget; + this.scissorData = maskData; + this.scissor = true; + } + + /** + * + * + */ + popScissorMask() + { + this.scissorRenderTarget = null; + this.scissorData = null; + this.scissor = false; + + // must be scissor! + const gl = this.renderer.gl; + + gl.disable(gl.SCISSOR_TEST); + } +} diff --git a/packages/core/src/mask/StencilSystem.js b/packages/core/src/mask/StencilSystem.js new file mode 100644 index 0000000..62796a5 --- /dev/null +++ b/packages/core/src/mask/StencilSystem.js @@ -0,0 +1,133 @@ +import System from '../System'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class StencilSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + this.stencilMaskStack = []; + } + + /** + * Changes the mask stack that is used by this System. + * + * @param {PIXI.Graphics[]} stencilMaskStack - The mask stack + */ + setMaskStack(stencilMaskStack) + { + const gl = this.renderer.gl; + + if (stencilMaskStack.length !== this.stencilMaskStack.length) + { + if (stencilMaskStack.length === 0) + { + gl.disable(gl.STENCIL_TEST); + } + else + { + gl.enable(gl.STENCIL_TEST); + } + } + + this.stencilMaskStack = stencilMaskStack; + } + + /** + * Applies the Mask and adds it to the current stencil stack. @alvin + * + * @param {PIXI.Graphics} graphics - The mask + */ + pushStencil(graphics) + { + this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); + + // this.renderer._activeRenderTarget.attachStencilBuffer(); + + const gl = this.renderer.gl; + const prevMaskCount = this.stencilMaskStack.length; + + if (prevMaskCount === 0) + { + gl.enable(gl.STENCIL_TEST); + } + + this.stencilMaskStack.push(graphics); + + // Increment the refference stencil value where the new mask overlaps with the old ones. + gl.colorMask(false, false, false, false); + gl.stencilFunc(gl.EQUAL, prevMaskCount, this._getBitwiseMask()); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.INCR); + this.renderer.plugins.graphics.render(graphics); + + this._useCurrent(); + } + + /** + * Removes the last mask from the stencil stack. @alvin + */ + popStencil() + { + this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); + + const gl = this.renderer.gl; + const graphics = this.stencilMaskStack.pop(); + + if (this.stencilMaskStack.length === 0) + { + // the stack is empty! + gl.disable(gl.STENCIL_TEST); + gl.clear(gl.STENCIL_BUFFER_BIT); + gl.clearStencil(0); + } + else + { + // Decrement the refference stencil value where the popped mask overlaps with the other ones + gl.colorMask(false, false, false, false); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.DECR); + this.renderer.plugins.graphics.render(graphics); + + this._useCurrent(); + } + } + + /** + * Setup renderer to use the current stencil data. + */ + _useCurrent() + { + const gl = this.renderer.gl; + + gl.colorMask(true, true, true, true); + gl.stencilFunc(gl.EQUAL, this.stencilMaskStack.length, this._getBitwiseMask()); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP); + } + + /** + * Fill 1s equal to the number of acitve stencil masks. + * + * @return {number} The bitwise mask. + */ + _getBitwiseMask() + { + return (1 << this.stencilMaskStack.length) - 1; + } + + /** + * Destroys the mask stack. + * + */ + destroy() + { + super.destroy(this); + + this.stencilMaskStack = null; + } +} diff --git a/packages/core/src/projection/ProjectionSystem.js b/packages/core/src/projection/ProjectionSystem.js new file mode 100644 index 0000000..6e63b73 --- /dev/null +++ b/packages/core/src/projection/ProjectionSystem.js @@ -0,0 +1,73 @@ +import System from '../System'; +import { Matrix } from '@pixi/math'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class ProjectionSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.projectionMatrix = new Matrix(); + } + + update(destinationFrame, sourceFrame, resolution, root) + { + this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; + this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; + + this.calculateProjection(this.destinationFrame, this.sourceFrame, resolution, root); + + this.renderer.globalUniforms.uniforms.projectionMatrix = this.projectionMatrix; + this.renderer.globalUniforms.update(); + } + + /** + * Updates the projection matrix based on a projection frame (which is a rectangle) + * + * @param {Rectangle} destinationFrame - The destination frame. + * @param {Rectangle} sourceFrame - The source frame. + */ + calculateProjection(destinationFrame, sourceFrame, resolution, root) + { + const pm = this.projectionMatrix; + + // I don't think we will need this line.. + // pm.identity(); + + if (!root) + { + pm.a = (1 / destinationFrame.width * 2) * resolution; + pm.d = (1 / destinationFrame.height * 2) * resolution; + + pm.tx = -1 - (sourceFrame.x * pm.a); + pm.ty = -1 - (sourceFrame.y * pm.d); + } + else + { + pm.a = (1 / destinationFrame.width * 2) * resolution; + pm.d = (-1 / destinationFrame.height * 2) * resolution; + + pm.tx = -1 - (sourceFrame.x * pm.a); + pm.ty = 1 - (sourceFrame.y * pm.d); + } + } + + /** + * Sets the transform of the active render target to the given matrix + * + * @param {PIXI.Matrix} matrix - The transformation matrix + */ + setTransform()// matrix) + { + // this._activeRenderTarget.transform = matrix; + } +} diff --git a/packages/core/src/renderTexture/BaseRenderTexture.js b/packages/core/src/renderTexture/BaseRenderTexture.js new file mode 100644 index 0000000..88eb83b --- /dev/null +++ b/packages/core/src/renderTexture/BaseRenderTexture.js @@ -0,0 +1,137 @@ +import BaseTexture from '../textures/BaseTexture'; +import FrameBuffer from '../framebuffer/FrameBuffer'; + +/** + * A BaseRenderTexture is a special texture that allows any PixiJS display object to be rendered to it. + * + * __Hint__: All DisplayObjects (i.e. Sprites) that render to a BaseRenderTexture should be preloaded + * otherwise black rectangles will be drawn instead. + * + * A BaseRenderTexture takes a snapshot of any Display Object given to its render method. The position + * and rotation of the given Display Objects is ignored. For example: + * + * ```js + * let renderer = PIXI.autoDetectRenderer(1024, 1024, { view: canvas, ratio: 1 }); + * let baseRenderTexture = new PIXI.BaseRenderTexture(renderer, 800, 600); + * let sprite = PIXI.Sprite.fromImage("spinObj_01.png"); + * + * sprite.position.x = 800/2; + * sprite.position.y = 600/2; + * sprite.anchor.x = 0.5; + * sprite.anchor.y = 0.5; + * + * baseRenderTexture.render(sprite); + * ``` + * + * The Sprite in this case will be rendered using its local transform. To render this sprite at 0,0 + * you can clear the transform + * + * ```js + * + * sprite.setTransform() + * + * let baseRenderTexture = new PIXI.BaseRenderTexture(100, 100); + * let renderTexture = new PIXI.RenderTexture(baseRenderTexture); + * + * renderer.render(sprite, renderTexture); // Renders to center of RenderTexture + * ``` + * + * @class + * @extends PIXI.BaseTexture + * @memberof PIXI + */ +export default class BaseRenderTexture extends BaseTexture +{ + /** + * @param {object} [options] + * @param {number} [options.width=100] - The width of the base render texture + * @param {number} [options.height=100] - The height of the base render texture + * @param {PIXI.SCALE_MODES} [options.scaleMode] - See {@link PIXI.SCALE_MODES} for possible values + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the texture being generated + */ + constructor(options) + { + if (typeof options === 'number') + { + /* eslint-disable prefer-rest-params */ + // Backward compatibility of signature + const width = arguments[0]; + const height = arguments[1]; + const scaleMode = arguments[2]; + const resolution = arguments[3]; + + options = { width, height, scaleMode, resolution }; + /* eslint-enable prefer-rest-params */ + } + + super(null, options); + + const { width, height } = options || {}; + + // Set defaults + this.mipmap = false; + this.width = Math.ceil(width) || 100; + this.height = Math.ceil(height) || 100; + this.valid = true; + + /** + * A map of renderer IDs to webgl renderTargets + * + * @private + * @member {object} + */ + // this._glRenderTargets = {}; + + /** + * A reference to the canvas render target (we only need one as this can be shared across renderers) + * + * @private + * @member {object} + */ + this._canvasRenderTarget = null; + + this.clearColor = [0, 0, 0, 0]; + + this.frameBuffer = new FrameBuffer(width * this.resolution, height * this.resolution) + .addColorTexture(0, this); + + // TODO - could this be added the systems? + + /** + * The data structure for the stencil masks + * + * @member {PIXI.Graphics[]} + */ + this.stencilMaskStack = []; + + /** + * The data structure for the filters + * + * @member {PIXI.Graphics[]} + */ + this.filterStack = [{}]; + } + + /** + * Resizes the BaseRenderTexture. + * + * @param {number} width - The width to resize to. + * @param {number} height - The height to resize to. + */ + resize(width, height) + { + width = Math.ceil(width); + height = Math.ceil(height); + this.frameBuffer.resize(width * this.resolution, height * this.resolution); + } + + /** + * Destroys this texture + * + */ + destroy() + { + super.destroy(true); + this.renderer = null; + } +} diff --git a/packages/core/src/renderTexture/RenderTexture.js b/packages/core/src/renderTexture/RenderTexture.js new file mode 100644 index 0000000..f1ecf1f --- /dev/null +++ b/packages/core/src/renderTexture/RenderTexture.js @@ -0,0 +1,153 @@ +import BaseRenderTexture from './BaseRenderTexture'; +import Texture from '../textures/Texture'; + +/** + * A RenderTexture is a special texture that allows any PixiJS display object to be rendered to it. + * + * __Hint__: All DisplayObjects (i.e. Sprites) that render to a RenderTexture should be preloaded + * otherwise black rectangles will be drawn instead. + * + * A RenderTexture takes a snapshot of any Display Object given to its render method. For example: + * + * ```js + * let renderer = PIXI.autoDetectRenderer(1024, 1024, { view: canvas, ratio: 1 }); + * let renderTexture = PIXI.RenderTexture.create(800, 600); + * let sprite = PIXI.Sprite.fromImage("spinObj_01.png"); + * + * sprite.position.x = 800/2; + * sprite.position.y = 600/2; + * sprite.anchor.x = 0.5; + * sprite.anchor.y = 0.5; + * + * renderer.render(sprite, renderTexture); + * ``` + * + * The Sprite in this case will be rendered using its local transform. To render this sprite at 0,0 + * you can clear the transform + * + * ```js + * + * sprite.setTransform() + * + * let renderTexture = new PIXI.RenderTexture.create(100, 100); + * + * renderer.render(sprite, renderTexture); // Renders to center of RenderTexture + * ``` + * + * @class + * @extends PIXI.Texture + * @memberof PIXI + */ +export default class RenderTexture extends Texture +{ + /** + * @param {PIXI.BaseRenderTexture} baseRenderTexture - The renderer used for this RenderTexture + * @param {PIXI.Rectangle} [frame] - The rectangle frame of the texture to show + */ + constructor(baseRenderTexture, frame) + { + // support for legacy.. + let _legacyRenderer = null; + + if (!(baseRenderTexture instanceof BaseRenderTexture)) + { + /* eslint-disable prefer-rest-params, no-console */ + const width = arguments[1]; + const height = arguments[2]; + const scaleMode = arguments[3]; + const resolution = arguments[4]; + + // we have an old render texture.. + console.warn(`Please use RenderTexture.create(${width}, ${height}) instead of the ctor directly.`); + _legacyRenderer = arguments[0]; + /* eslint-enable prefer-rest-params, no-console */ + + frame = null; + baseRenderTexture = new BaseRenderTexture({ + width, + height, + scaleMode, + resolution, + }); + } + + /** + * The base texture object that this texture uses + * + * @member {BaseTexture} + */ + super(baseRenderTexture, frame); + + this.legacyRenderer = _legacyRenderer; + + /** + * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. + * + * @member {boolean} + */ + this.valid = true; + + /** + * FilterSystem temporary storage + * @private + * @member {PIXI.Rectangle} + */ + this.filterFrame = null; + + this._updateUvs(); + } + + /** + * Resizes the RenderTexture. + * + * @param {number} width - The width to resize to. + * @param {number} height - The height to resize to. + * @param {boolean} [resizeBaseTexture=true] - Should the baseTexture.width and height values be resized as well? + */ + resize(width, height, resizeBaseTexture = true) + { + width = Math.ceil(width); + height = Math.ceil(height); + + // TODO - could be not required.. + this.valid = (width > 0 && height > 0); + + this._frame.width = this.orig.width = width; + this._frame.height = this.orig.height = height; + + if (resizeBaseTexture) + { + this.baseTexture.resize(width, height); + } + + this._updateUvs(); + } + + /** + * A short hand way of creating a render texture. + * + * @param {object} [options] - Options + * @param {number} [options.width=100] - The width of the render texture + * @param {number} [options.height=100] - The height of the render texture + * @param {number} [options.scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the texture being generated + * @return {PIXI.RenderTexture} The new render texture + */ + static create(options) + { + // fallback, old-style: create(width, height, scaleMode, resolution) + if (typeof options === 'number') + { + /* eslint-disable prefer-rest-params */ + options = { + width: options, + height: arguments[1], + scaleMode: arguments[2], + resolution: arguments[3], + }; + /* eslint-enable prefer-rest-params */ + } + + return new RenderTexture(new BaseRenderTexture(options)); + } +} diff --git a/packages/core/src/renderTexture/RenderTextureSystem.js b/packages/core/src/renderTexture/RenderTextureSystem.js new file mode 100644 index 0000000..8d0ca61 --- /dev/null +++ b/packages/core/src/renderTexture/RenderTextureSystem.js @@ -0,0 +1,115 @@ +import System from '../System'; +import { Rectangle } from '@pixi/math'; + +const tempRect = new Rectangle(); + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class RenderTextureSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.clearColor = renderer._backgroundColorRgba; + + // TODO moe this property somewhere else! + this.defaultMaskStack = []; + this.defaultFilterStack = [{}]; + + // empty render texture? + this.renderTexture = null; + + this.destinationFrame = new Rectangle(); + } + + bind(renderTexture, sourceFrame, destinationFrame) + { + // TODO - do we want this?? + if (this.renderTexture === renderTexture) return; + this.renderTexture = renderTexture; + + const renderer = this.renderer; + + if (renderTexture) + { + const baseTexture = renderTexture.baseTexture; + + if (!destinationFrame) + { + tempRect.width = baseTexture.realWidth; + tempRect.height = baseTexture.realHeight; + + destinationFrame = tempRect; + } + + if (!sourceFrame) + { + sourceFrame = destinationFrame; + } + + this.renderer.framebuffer.bind(baseTexture.frameBuffer, destinationFrame); + + this.renderer.projection.update(destinationFrame, sourceFrame, baseTexture.resolution, false); + this.renderer.stencil.setMaskStack(baseTexture.stencilMaskStack); + } + else + { + // TODO these validation checks happen deeper down.. + // thing they can be avoided.. + if (!destinationFrame) + { + tempRect.width = renderer.width; + tempRect.height = renderer.height; + + destinationFrame = tempRect; + } + + if (!sourceFrame) + { + sourceFrame = destinationFrame; + } + + renderer.framebuffer.bind(null, destinationFrame); + + // TODO store this.. + this.renderer.projection.update(destinationFrame, sourceFrame, this.renderer.resolution, true); + this.renderer.stencil.setMaskStack(this.defaultMaskStack); + } + + this.destinationFrame.copyFrom(destinationFrame); + } + + /** + * Erases the render texture and fills the drawing area with a colour + * + * @param {number} [clearColor] - The colour + * @return {PIXI.Renderer} Returns itself. + */ + clear(clearColor) + { + if (this.renderTexture) + { + clearColor = clearColor || this.renderTexture.baseTexture.clearColor; + } + else + { + clearColor = clearColor || this.clearColor; + } + + this.renderer.framebuffer.clear(clearColor[0], clearColor[1], clearColor[2], clearColor[3]); + } + + resize()// screenWidth, screenHeight) + { + // resize the root only! + this.bind(null); + } +} diff --git a/packages/core/src/renderers/AbstractRenderer.js b/packages/core/src/renderers/AbstractRenderer.js deleted file mode 100644 index 928670f..0000000 --- a/packages/core/src/renderers/AbstractRenderer.js +++ /dev/null @@ -1,353 +0,0 @@ -import { hex2string, hex2rgb } from '@pixi/utils'; -import { Matrix, Rectangle } from '@pixi/math'; -import { RENDERER_TYPE } from '@pixi/constants'; -import { settings } from '@pixi/settings'; -import { Container } from '@pixi/display'; -import RenderTexture from '../textures/RenderTexture'; -import EventEmitter from 'eventemitter3'; - -const tempMatrix = new Matrix(); - -/** - * 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 EventEmitter - * @memberof PIXI - */ -export default class AbstractRenderer extends EventEmitter -{ - // eslint-disable-next-line valid-jsdoc - /** - * @param {string} system - The name of the system this renderer is for. - * @param {object} [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.transparent=false] - If the render view is transparent, default false - * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false - * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) - * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. The - * resolution of the renderer retina would be 2. - * @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 {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when rendering, - * stopping pixel interpolation. - */ - constructor(system, options, arg2, arg3) - { - super(); - - // Support for constructor(system, screenWidth, screenHeight, options) - if (typeof options === 'number') - { - options = Object.assign({ - width: options, - height: arg2 || settings.RENDER_OPTIONS.height, - }, arg3); - } - - // 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 = RENDERER_TYPE.UNKNOWN; - - /** - * Measurements of the screen. (0, 0, screenWidth, screenHeight) - * - * Its safe to use as filterArea or hitArea for 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 1 - */ - this.resolution = options.resolution || settings.RESOLUTION; - - /** - * Whether the render view is transparent - * - * @member {boolean} - */ - this.transparent = options.transparent; - - /** - * Whether css dimensions of canvas view should be resized to screen dimensions automatically - * - * @member {boolean} - */ - this.autoResize = options.autoResize || false; - - /** - * Tracks the blend modes useful for this renderer. - * - * @member {object} - */ - this.blendModes = null; - - /** - * 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; - - /** - * If true PixiJS will Math.floor() x/y values when rendering, stopping pixel interpolation. - * Handy for crisp pixel art and speed on legacy devices. - * - * @member {boolean} - */ - this.roundPixels = options.roundPixels; - - /** - * The background color as a number. - * - * @member {number} - * @private - */ - this._backgroundColor = 0x000000; - - /** - * The background color as an [R, G, B] array. - * - * @member {number[]} - * @private - */ - this._backgroundColorRgba = [0, 0, 0, 0]; - - /** - * The background color as a string. - * - * @member {string} - * @private - */ - this._backgroundColorString = '#000000'; - - this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter - - /** - * This temporary display object used as the parent of the currently being rendered item - * - * @member {PIXI.DisplayObject} - * @private - */ - this._tempDisplayObjectParent = new Container(); - - /** - * The last root object that the renderer tried to render. - * - * @member {PIXI.DisplayObject} - * @private - */ - this._lastObjectRendered = this._tempDisplayObjectParent; - - /** - * Collection of plugins. - * @readonly - * @member {object} - */ - this.plugins = {}; - } - - /** - * Initialize the plugins. - * - * @protected - * @param {object} staticMap - The dictionary of staticly saved plugins. - */ - initPlugins(staticMap) - { - 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() - { - return this.view.width; - } - - /** - * Same as view.height, actual number of pixels in the canvas by vertical - * - * @member {number} - * @readonly - * @default 600 - */ - get height() - { - return this.view.height; - } - - /** - * Resizes the screen and canvas to the specified width and height - * Canvas dimensions are multiplied by resolution - * - * @param {number} screenWidth - the new width of the screen - * @param {number} screenHeight - the new height of the screen - */ - resize(screenWidth, screenHeight) - { - this.screen.width = screenWidth; - this.screen.height = screenHeight; - - this.view.width = screenWidth * this.resolution; - this.view.height = screenHeight * this.resolution; - - if (this.autoResize) - { - this.view.style.width = `${screenWidth}px`; - this.view.style.height = `${screenHeight}px`; - } - } - - /** - * 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 {PIXI.DisplayObject} displayObject - The displayObject the object will be generated from - * @param {number} scaleMode - Should be one of the scaleMode consts - * @param {number} resolution - The resolution / device pixel ratio of the texture being generated - * @param {PIXI.Rectangle} [region] - The region of the displayObject, that shall be rendered, - * if no region is specified, defaults to the local bounds of the displayObject. - * @return {PIXI.Texture} a texture of the graphics object - */ - generateTexture(displayObject, scaleMode, resolution, region) - { - region = region || displayObject.getLocalBounds(); - - // 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(region.width | 0, region.height | 0, scaleMode, resolution); - - tempMatrix.tx = -region.x; - tempMatrix.ty = -region.y; - - this.render(displayObject, renderTexture, false, tempMatrix, true); - - return renderTexture; - } - - /** - * Removes everything from the renderer and optionally removes the Canvas DOM element. - * - * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. - */ - destroy(removeView) - { - 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); - } - - this.plugins = null; - - this.type = RENDERER_TYPE.UNKNOWN; - - this.view = null; - - this.screen = null; - - this.resolution = 0; - - this.transparent = false; - - this.autoResize = false; - - this.blendModes = null; - - this.options = null; - - this.preserveDrawingBuffer = false; - this.clearBeforeRender = false; - - this.roundPixels = false; - - this._backgroundColor = 0; - this._backgroundColorRgba = null; - this._backgroundColorString = null; - - this._tempDisplayObjectParent = null; - this._lastObjectRendered = null; - } - - /** - * The background color to fill if not transparent - * - * @member {number} - */ - get backgroundColor() - { - return this._backgroundColor; - } - - set backgroundColor(value) // eslint-disable-line require-jsdoc - { - this._backgroundColor = value; - this._backgroundColorString = hex2string(value); - hex2rgb(value, this._backgroundColorRgba); - } -} diff --git a/packages/core/src/renderers/Renderer.js b/packages/core/src/renderers/Renderer.js deleted file mode 100644 index a2020b1..0000000 --- a/packages/core/src/renderers/Renderer.js +++ /dev/null @@ -1,329 +0,0 @@ -import AbstractRenderer from './AbstractRenderer'; -import { sayHello } from '@pixi/utils'; -import MaskSystem from './systems/MaskSystem'; -import StencilSystem from './systems/StencilSystem'; -import FilterSystem from './systems/filter/FilterSystem'; -import FramebufferSystem from './systems/FramebufferSystem'; -import RenderTextureSystem from './systems/RenderTextureSystem'; -import TextureSystem from './systems/textures/TextureSystem'; -import ProjectionSystem from './systems/ProjectionSystem'; -import StateSystem from './systems/StateSystem'; -import GeometrySystem from './systems/geometry/GeometrySystem'; -import ShaderSystem from './systems/shader/ShaderSystem'; -import ContextSystem from './systems/ContextSystem'; -import BatchSystem from './systems/BatchSystem'; -import TextureGCSystem from './systems/textures/TextureGCSystem'; -import { RENDERER_TYPE } from '@pixi/constants'; -import UniformGroup from '../shader/UniformGroup'; -import { Matrix } from '@pixi/math'; -import Runner from 'mini-runner'; - -/** - * 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 Render works by automatically managing webGLBatchs. - * 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 :) - * - * @class - * @memberof PIXI - * @extends PIXI.AbstractRenderer - */ -export default class Renderer extends AbstractRenderer -{ - // eslint-disable-next-line valid-jsdoc - /** - * - * @param {object} [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.transparent=false] - If the render view is transparent, default false - * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false - * @param {boolean} [options.antialias=false] - sets antialias. If not available natively then FXAA - * antialiasing is used - * @param {boolean} [options.forceFXAA=false] - forces FXAA antialiasing to be used over native. - * FXAA is faster, but may not always look as great - * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. - * The resolution of the renderer retina would be 2. - * @param {boolean} [options.clearBeforeRender=true] - This sets if the renderer will clear - * the canvas or not before the new render pass. If you wish to set this to false, you *must* set - * preserveDrawingBuffer to `true`. - * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, - * enable this if you need to call toDataUrl on the webgl context. - * @param {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when - * rendering, stopping pixel interpolation. - * @param {number} [options.backgroundColor=0x000000] - The background color of the rendered area - * (shown if not transparent). - * @param {boolean} [options.legacy=false] - If true PixiJS will aim to ensure compatibility - * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. - * @param {string} [options.powerPreference] - Parameter passed to webgl context, set to "high-performance" - * for devices with dual graphics card - */ - constructor(options, arg2, arg3) - { - super('WebGL', options, arg2, arg3); - - /** - * The type of this renderer as a standardised const - * - * @member {number} - * @see PIXI.RENDERER_TYPE - */ - this.type = RENDERER_TYPE.WEBGL; - - // this will be set by the contextSystem (this.context) - this.gl = null; - this.CONTEXT_UID = 0; - this.legacy = !!options.legacy; - - // TODO legacy! - - // runners! - this.runners = { - destroy: new Runner('destroy'), - contextChange: new Runner('contextChange', 1), - reset: new Runner('reset'), - update: new Runner('update'), - postrender: new Runner('postrender'), - prerender: new Runner('prerender'), - resize: new Runner('resize', 2), - }; - - 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(GeometrySystem, 'geometry') - .addSystem(FramebufferSystem, 'framebuffer') - .addSystem(StencilSystem, 'stencil') - .addSystem(ProjectionSystem, 'projection') - .addSystem(TextureGCSystem, 'textureGC') - .addSystem(FilterSystem, 'filter') - .addSystem(RenderTextureSystem, 'renderTexture') - .addSystem(BatchSystem, 'batch'); - - this.initPlugins(Renderer.__plugins); - - /** - * The options passed in to create a new webgl context. - * - * @member {object} - * @private - */ - if (options.context) - { - this.context.initFromContext(options.context); - } - else - { - this.context.initFromOptions({ - alpha: this.transparent, - antialias: options.antialias, - premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', - stencil: true, - 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); - } - - addSystem(_class, name) - { - if (!name) - { - name = _class.name; - } - - // TODO - read name from class.name.. - - /* - if(name.includes('System')) - { - name = name.replace('System', ''); - name = name.charAt(0).toLowerCase() + name.slice(1); - } - */ - - const system = new _class(this); - - if (this[name]) - { - throw new Error(`Whoops! ${name} is already a manger`); - } - - this[name] = system; - - for (const i in this.runners) - { - this.runners[i].add(system); - } - - return this; - - /** - * 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. - */ - } - - /** - * Renders the object to its webGL view - * - * @param {PIXI.DisplayObject} displayObject - the object to be rendered - * @param {PIXI.RenderTexture} renderTexture - The render texture to render to. - * @param {boolean} [clear] - Should the canvas be cleared before the new render - * @param {PIXI.Transform} [transform] - A transform to apply to the render texture before rendering. - * @param {boolean} [skipUpdateTransform] - Should we skip the update transform pass? - */ - render(displayObject, renderTexture, clear, transform, skipUpdateTransform) - { - // can be handy to know! - this.renderingToScreen = !renderTexture; - - this.runners.prerender.run(); - this.emit('prerender'); - - // 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.parent; - - displayObject.parent = this._tempDisplayObjectParent; - displayObject.updateTransform(); - displayObject.parent = 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.run(); - - this.emit('postrender'); - } - - /** - * Resizes the webGL view to the specified width and height. - * - * @param {number} screenWidth - the new width of the screen - * @param {number} screenHeight - the new height of the screen - */ - resize(screenWidth, screenHeight) - { - AbstractRenderer.prototype.resize.call(this, screenWidth, screenHeight); - - this.runners.resize.run(screenWidth, screenHeight); - } - - /** - * Resets the WebGL state so you can render things however you fancy! - * - * @return {PIXI.Renderer} Returns itself. - */ - reset() - { - this.runners.reset.run(); - - return this; - } - - clear() - { - this.framebuffer.bind(); - this.framebuffer.clear(); - } - - /** - * Removes everything from the renderer (event listeners, spritebatch, etc...) - * - * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. - * See: https://github.com/pixijs/pixi.js/issues/2233 - */ - destroy(removeView) - { - this.runners.destroy.run(); - - // call base destroy - super.destroy(removeView); - - // TODO nullify all the managers.. - this.gl = null; - } - - /** - * 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 - * builds and excluding plugins. - * @name PIXI.Renderer#plugins - * @type {object} - * @readonly - * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. - * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. - * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. - * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. - */ - - /** - * Adds a plugin to the renderer. - * - * @method - * @param {string} pluginName - The name of the plugin. - * @param {Function} ctor - The constructor function or class for the plugin. - */ - static registerPlugin(pluginName, ctor) - { - Renderer.__plugins = Renderer.__plugins || {}; - Renderer.__plugins[pluginName] = ctor; - } -} diff --git a/packages/core/src/renderers/State.js b/packages/core/src/renderers/State.js deleted file mode 100644 index fa3f221..0000000 --- a/packages/core/src/renderers/State.js +++ /dev/null @@ -1,163 +0,0 @@ -/* eslint-disable max-len */ - -const BLEND = 0; -const OFFSET = 1; -const CULLING = 2; -const DEPTH_TEST = 3; -const WINDING = 4; - -/** - * This is a webGL state. It is passed The WebGL StateManager. - * Each mesh renderered may require webGL to be in a different state. - * For example you may want different blend mode or to enable polygon offsets - * - * @class - * @memberof PIXI - */ -export default class State -{ - /** - * - */ - constructor() - { - this.data = 0; - - this.blendMode = 0; - this.polygonOffset = 0; - - this.blend = true; - // this.depthTest = true; - } - - /** - * Activates blending of the computed fragment color values - * - * @member {boolean} - */ - get blend() - { - return !!(this.data & (1 << BLEND)); - } - - set blend(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << BLEND)) !== value) - { - this.data ^= (1 << BLEND); - } - } - - /** - * Activates adding an offset to depth values of polygon's fragments - * - * @member {boolean} - * @default false - */ - get offsets() - { - return !!(this.data & (1 << OFFSET)); - } - - set offsets(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << OFFSET)) !== value) - { - this.data ^= (1 << OFFSET); - } - } - - /** - * Activates culling of polygons. - * - * @member {boolean} - * @default false - */ - get culling() - { - return !!(this.data & (1 << CULLING)); - } - - set culling(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << CULLING)) !== value) - { - this.data ^= (1 << CULLING); - } - } - - /** - * Activates depth comparisons and updates to the depth buffer. - * - * @member {boolean} - * @default false - */ - get depthTest() - { - return !!(this.data & (1 << DEPTH_TEST)); - } - - set depthTest(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << DEPTH_TEST)) !== value) - { - this.data ^= (1 << DEPTH_TEST); - } - } - - /** - * Specifies whether or not front or back-facing polygons can be culled. - * @member {boolean} - * @default false - */ - get clockwiseFrontFace() - { - return !!(this.data & (1 << WINDING)); - } - - set clockwiseFrontFace(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << WINDING)) !== value) - { - this.data ^= (1 << WINDING); - } - } - - /** - * The blend mode to be applied when this state is set. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. - * Setting this mode to anything other than NO_BLEND will automatically switch blending on. - * - * @member {boolean} - * @default PIXI.BLEND_MODES.NORMAL - * @see PIXI.BLEND_MODES - */ - get blendMode() - { - return this._blendMode; - } - - set blendMode(value) // eslint-disable-line require-jsdoc - { - // 17 is NO BLEND - this.blend = (value !== 17); - this._blendMode = value; - } - - /** - * The polygon offset. Setting this property to anything other than 0 will automatically enable poygon offset fill. - * - * @member {number} - * @default 0 - */ - get polygonOffset() - { - return this._polygonOffset; - } - - set polygonOffset(value) // eslint-disable-line require-jsdoc - { - this.offsets = !!value; - this._polygonOffset = value; - } -} - diff --git a/packages/core/src/renderers/filters/Filter.js b/packages/core/src/renderers/filters/Filter.js deleted file mode 100644 index 2073b8d..0000000 --- a/packages/core/src/renderers/filters/Filter.js +++ /dev/null @@ -1,140 +0,0 @@ -import Shader from '../../shader/Shader'; -import Program from '../../shader/Program'; -import State from '../State'; -import { settings } from '@pixi/settings'; -// import extractUniformsFromSrc from './extractUniformsFromSrc'; -import defaultVertex from './defaultFilter.vert'; -import defaultFragment from './defaultFilter.frag'; - -// let math = require('../../math'); -/** - * @class - * @memberof PIXI - * @extends PIXI.Shader - */ -export default class Filter extends Shader -{ - /** - * @param {string} [vertexSrc] - The source of the vertex shader. - * @param {string} [fragmentSrc] - The source of the fragment shader. - * @param {object} [uniforms] - Custom uniforms to use to augment the built-in ones. - */ - constructor(vertexSrc, fragmentSrc, uniforms) - { - const program = Program.from(vertexSrc, fragmentSrc); - - super(program, uniforms); - - /** - * The padding of the filter. Some filters require extra space to breath such as a blur. - * Increasing this will add extra width and height to the bounds of the object that the - * filter is applied to. - * - * @member {number} - */ - this.padding = 0; - - /** - * The resolution of the filter. Setting this to be lower will lower the quality but - * increase the performance of the filter. - * - * @member {number} - */ - this.resolution = settings.RESOLUTION; - - /** - * If enabled is true the filter is applied, if false it will not. - * - * @member {boolean} - */ - this.enabled = true; - - /** - * If enabled, PixiJS will fit the filter area into boundaries for better performance. - * Switch it off if it does not work for specific shader. - * - * @member {boolean} - */ - this.autoFit = true; - - /** - * Legacy filters use position and uvs from attributes - * @member {boolean} - * @readonly - */ - this.legacy = !!this.program.attributeData.aTextureCoord; - - /** - * the webGL state the filter requires to render - * @member {PIXI.State} - */ - this.state = new State(); - } - - /** - * Applies the filter - * - * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from - * @param {PIXI.RenderTarget} input - The input render target. - * @param {PIXI.RenderTarget} output - The target to output to. - * @param {boolean} clear - Should the output be cleared before rendering to it - * @param {object} [currentState] - It's current state of filter. - * There are some useful properties in the currentState : - * target, filters, sourceFrame, destinationFrame, renderTarget, resolution - */ - apply(filterManager, input, output, clear, currentState, derp) // eslint-disable-line no-unused-vars - { - // do as you please! - - filterManager.applyFilter(this, input, output, clear, currentState, derp); - - // or just do a regular render.. - } - - /** - * Sets the blendmode of the filter - * - * @member {number} - * @default PIXI.BLEND_MODES.NORMAL - */ - get blendMode() - { - return this.state.blendMode; - } - - set blendMode(value) // eslint-disable-line require-jsdoc - { - this.state.blendMode = value; - } - - /** - * The default vertex shader source - * - * @static - * @constant - */ - static get defaultVertexSrc() - { - return defaultVertex; - } - - /** - * The default fragment shader source - * - * @static - * @constant - */ - static get defaultFragmentSrc() - { - return defaultFragment; - } -} - -/** - * Used for caching shader IDs - * - * @static - * @private - */ -Filter.SOURCE_KEY_MAP = {}; - diff --git a/packages/core/src/renderers/filters/defaultFilter.frag b/packages/core/src/renderers/filters/defaultFilter.frag deleted file mode 100644 index a14c1be..0000000 --- a/packages/core/src/renderers/filters/defaultFilter.frag +++ /dev/null @@ -1,22 +0,0 @@ -varying vec2 vTextureCoord; -varying vec2 vFilterCoord; - -uniform sampler2D uSampler; -uniform sampler2D filterSampler; - -void main(void){ - vec4 masky = texture2D(filterSampler, vFilterCoord); - vec4 sample = texture2D(uSampler, vTextureCoord); - vec4 color; - if(mod(vFilterCoord.x, 1.0) > 0.5) - { - color = vec4(1.0, 0.0, 0.0, 1.0); - } - else - { - color = vec4(0.0, 1.0, 0.0, 1.0); - } - // gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0); - gl_FragColor = mix(sample, masky, 0.5); - gl_FragColor *= sample.a; -} diff --git a/packages/core/src/renderers/filters/defaultFilter.vert b/packages/core/src/renderers/filters/defaultFilter.vert deleted file mode 100644 index e1febca..0000000 --- a/packages/core/src/renderers/filters/defaultFilter.vert +++ /dev/null @@ -1,14 +0,0 @@ -attribute vec2 aVertexPosition; -attribute vec2 aTextureCoord; - -uniform mat3 projectionMatrix; -uniform mat3 filterMatrix; - -varying vec2 vTextureCoord; -varying vec2 vFilterCoord; - -void main(void){ - gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); - vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy; - vTextureCoord = aTextureCoord ; -} diff --git a/packages/core/src/renderers/filters/filterTransforms.js b/packages/core/src/renderers/filters/filterTransforms.js deleted file mode 100644 index 87a3cd6..0000000 --- a/packages/core/src/renderers/filters/filterTransforms.js +++ /dev/null @@ -1,50 +0,0 @@ -import { Matrix } from '@pixi/math'; - -/** - * Calculates the mapped matrix - * @param filterArea {Rectangle} The filter area - * @param sprite {Sprite} the target sprite - * @param outputMatrix {Matrix} @alvin - * @private - */ -// this returns a matrix that will normalise map filter cords in the filter to screen space -export function calculateScreenSpaceMatrix(outputMatrix, filterArea, textureSize) -{ - // TODO unwrap? - const mappedMatrix = outputMatrix.identity(); - - mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); - - mappedMatrix.scale(textureSize.width, textureSize.height); - - return mappedMatrix; -} - -export function calculateNormalizedScreenSpaceMatrix(outputMatrix, filterArea, textureSize) -{ - const mappedMatrix = outputMatrix.identity(); - - mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); - - const translateScaleX = (textureSize.width / filterArea.width); - const translateScaleY = (textureSize.height / filterArea.height); - - mappedMatrix.scale(translateScaleX, translateScaleY); - - return mappedMatrix; -} - -// this will map the filter coord so that a texture can be used based on the transform of a sprite -export function calculateSpriteMatrix(outputMatrix, filterArea, textureSize, sprite) -{ - const orig = sprite._texture.orig; - const mappedMatrix = outputMatrix.set(textureSize.width, 0, 0, textureSize.height, filterArea.x, filterArea.y); - const worldTransform = sprite.worldTransform.copyTo(Matrix.TEMP_MATRIX); - - worldTransform.invert(); - mappedMatrix.prepend(worldTransform); - mappedMatrix.scale(1.0 / orig.width, 1.0 / orig.height); - mappedMatrix.translate(sprite.anchor.x, sprite.anchor.y); - - return mappedMatrix; -} diff --git a/packages/core/src/renderers/filters/spriteMask/SpriteMaskFilter.js b/packages/core/src/renderers/filters/spriteMask/SpriteMaskFilter.js deleted file mode 100644 index d5a91a4..0000000 --- a/packages/core/src/renderers/filters/spriteMask/SpriteMaskFilter.js +++ /dev/null @@ -1,63 +0,0 @@ -import Filter from '../Filter'; -import { Matrix } from '@pixi/math'; -import vertex from './spriteMaskFilter.vert'; -import fragment from './spriteMaskFilter.frag'; -import { default as TextureMatrix } from '../../../textures/TextureMatrix'; - -/** - * The SpriteMaskFilter class - * - * @class - * @extends PIXI.Filter - * @memberof PIXI - */ -export default class SpriteMaskFilter extends Filter -{ - /** - * @param {PIXI.Sprite} sprite - the target sprite - */ - constructor(sprite) - { - const maskMatrix = new Matrix(); - - super(vertex, fragment); - - sprite.renderable = false; - - this.maskSprite = sprite; - this.maskMatrix = maskMatrix; - } - - /** - * Applies the filter - * - * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from - * @param {PIXI.RenderTarget} input - The input render target. - * @param {PIXI.RenderTarget} output - The target to output to. - */ - apply(filterManager, input, output) - { - const maskSprite = this.maskSprite; - const tex = this.maskSprite.texture; - - if (!tex.valid) - { - return; - } - if (!tex.transform) - { - // margin = 0.0, let it bleed a bit, shader code becomes easier - // assuming that atlas textures were made with 1-pixel padding - tex.transform = new TextureMatrix(tex, 0.0); - } - tex.transform.update(); - - this.uniforms.mask = tex; - this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite) - .prepend(tex.transform.mapCoord); - this.uniforms.alpha = maskSprite.worldAlpha; - this.uniforms.maskClamp = tex.transform.uClampFrame; - - filterManager.applyFilter(this, input, output); - } -} diff --git a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.frag b/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.frag deleted file mode 100644 index 0e4aef8..0000000 --- a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.frag +++ /dev/null @@ -1,23 +0,0 @@ -varying vec2 vMaskCoord; -varying vec2 vTextureCoord; - -uniform sampler2D uSampler; -uniform sampler2D mask; -uniform float alpha; -uniform vec4 maskClamp; - -void main(void) -{ - float clip = step(3.5, - step(maskClamp.x, vMaskCoord.x) + - step(maskClamp.y, vMaskCoord.y) + - step(vMaskCoord.x, maskClamp.z) + - step(vMaskCoord.y, maskClamp.w)); - - vec4 original = texture2D(uSampler, vTextureCoord); - vec4 masky = texture2D(mask, vMaskCoord); - - original *= (masky.r * masky.a * alpha * clip); - - gl_FragColor = original; -} diff --git a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.vert b/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.vert deleted file mode 100644 index d23a30c..0000000 --- a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.vert +++ /dev/null @@ -1,16 +0,0 @@ -attribute vec2 aVertexPosition; -attribute vec2 aTextureCoord; - -uniform mat3 projectionMatrix; -uniform mat3 otherMatrix; - -varying vec2 vMaskCoord; -varying vec2 vTextureCoord; - -void main(void) -{ - gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); - - vTextureCoord = aTextureCoord; - vMaskCoord = ( otherMatrix * vec3( aTextureCoord, 1.0) ).xy; -} diff --git a/packages/core/src/renderers/systems/BatchSystem.js b/packages/core/src/renderers/systems/BatchSystem.js deleted file mode 100644 index 70e9518..0000000 --- a/packages/core/src/renderers/systems/BatchSystem.js +++ /dev/null @@ -1,68 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import ObjectRenderer from '../utils/ObjectRenderer'; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ - -export default class BatchSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this manager works for. - */ - constructor(renderer) - { - super(renderer); - - /** - * An empty renderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.emptyRenderer = new ObjectRenderer(renderer); - - /** - * The currently active ObjectRenderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.currentRenderer = this.emptyRenderer; - } - - /** - * Changes the current renderer to the one given in parameter - * - * @param {PIXI.ObjectRenderer} objectRenderer - The object renderer to use. - */ - setObjectRenderer(objectRenderer) - { - if (this.currentRenderer === objectRenderer) - { - return; - } - - this.currentRenderer.stop(); - this.currentRenderer = objectRenderer; - - this.renderer.state.setState(objectRenderer.state); - - this.currentRenderer.start(); - } - - /** - * This should be called if you wish to do some custom rendering - * It will basically render anything that may be batched up such as sprites - * - */ - flush() - { - this.setObjectRenderer(this.emptyRenderer); - } - - reset() - { - this.setObjectRenderer(this.emptyRenderer); - } -} diff --git a/packages/core/src/renderers/systems/ContextSystem.js b/packages/core/src/renderers/systems/ContextSystem.js deleted file mode 100644 index 43bf827..0000000 --- a/packages/core/src/renderers/systems/ContextSystem.js +++ /dev/null @@ -1,186 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { settings } from '@pixi/settings'; - -let CONTEXT_UID = 0; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class ContextSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this.webGLVersion = 1; - - this.handleContextLost = this.handleContextLost.bind(this); - this.handleContextRestored = this.handleContextRestored.bind(this); - - this.extensions = {}; - - renderer.view.addEventListener('webglcontextlost', this.handleContextLost, false); - renderer.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); - } - - get isLost() - { - return (!this.gl || this.gl.isContextLost()); - } - - contextChange(gl) - { - this.gl = gl; - - // restore a context if it was previously lost - if (gl.isContextLost() && gl.getExtension('WEBGL_lose_context')) - { - gl.getExtension('WEBGL_lose_context').restoreContext(); - } - } - - initFromContext(gl) - { - this.gl = gl; - this.validateContext(gl); - this.renderer.gl = gl; - this.renderer.CONTEXT_UID = CONTEXT_UID++; - this.renderer.runners.contextChange.run(gl); - } - - initFromOptions(options) - { - const gl = this.createContext(this.renderer.view, options); - - this.initFromContext(gl); - } - - /** - * Helper class to create a webGL Context - * - * @class - * @memberof PIXI.glCore - * @param canvas {HTMLCanvasElement} the canvas element that we will get the context from - * @param options {Object} An options object that gets passed in to the canvas element containing the context attributes, - * see https://developer.mozilla.org/en/docs/Web/API/HTMLCanvasElement/getContext for the options available - * @return {WebGLRenderingContext} the WebGL context - */ - createContext(canvas, options) - { - let gl; - - if (settings.PREFER_WEBGL_2) - { - gl = canvas.getContext('webgl2', options); - } - - if (gl) - { - this.webGLVersion = 2; - } - else - { - this.webGLVersion = 1; - - gl = canvas.getContext('webgl', options) - || canvas.getContext('experimental-webgl', options); - - if (!gl) - { - // fail, not able to get a context - throw new Error('This browser does not support webGL. Try using the canvas renderer'); - } - } - - this.gl = gl; - - this.getExtensions(); - - return gl; - } - - getExtensions() - { - // time to set up default etensions that pixi uses.. - const gl = this.gl; - const extensions = this.extensions; - - if (this.webGLVersion === 1) - { - extensions.drawBuffers = gl.getExtension('WEBGL_draw_buffers'); - extensions.depthTexture = gl.getExtension('WEBKIT_WEBGL_depth_texture'); - extensions.floatTexture = gl.getExtension('OES_texture_float'); - extensions.loseContext = gl.getExtension('WEBGL_lose_context'); - - extensions.vertexArrayObject = gl.getExtension('OES_vertex_array_object') - || gl.getExtension('MOZ_OES_vertex_array_object') - || gl.getExtension('WEBKIT_OES_vertex_array_object'); - } - - // we don't use any specific WebGL 2 ones yet! - } - - /** - * Handles a lost webgl context - * - * @private - * @param {WebGLContextEvent} event - The context lost event. - */ - handleContextLost(event) - { - event.preventDefault(); - } - - /** - * Handles a restored webgl context - * - * @private - */ - handleContextRestored() - { - this.renderer.runners.contextChange.run(this.gl); - } - - destroy() - { - const view = this.renderer.view; - - // remove listeners - view.removeEventListener('webglcontextlost', this.handleContextLost); - view.removeEventListener('webglcontextrestored', this.handleContextRestored); - - this.gl.useProgram(null); - - if (this.extensions.loseContext) - { - this.extensions.loseContext.loseContext(); - } - } - - postrender() - { - this.gl.flush(); - } - - validateContext(gl) - { - const attributes = gl.getContextAttributes(); - - // this is going to be fairly simple for now.. but at least we have room to grow! - if (!attributes.stencil) - { - /* eslint-disable max-len */ - - /* eslint-disable no-console */ - console.warn('Provided WebGL context does not have a stencil buffer, masks may not render correctly'); - /* eslint-enable no-console */ - - /* eslint-enable max-len */ - } - } -} diff --git a/packages/core/src/renderers/systems/FramebufferSystem.js b/packages/core/src/renderers/systems/FramebufferSystem.js deleted file mode 100644 index df7902b..0000000 --- a/packages/core/src/renderers/systems/FramebufferSystem.js +++ /dev/null @@ -1,246 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { Rectangle } from '@pixi/math'; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class FramebufferSystem extends WebGLSystem -{ - /** - * Sets up the renderer context and necessary buffers. - * - * @private - */ - contextChange() - { - this.gl = this.renderer.gl; - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - this.current = null; - this.viewport = new Rectangle(); - - this.drawBufferExtension = this.renderer.context.extensions.drawBuffers; - } - - bind(framebuffer, frame) - { - const gl = this.gl; - - this.current = framebuffer; - - if (framebuffer) - { - // TODO cacheing layer! - - const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID] || this.initFramebuffer(framebuffer); - - gl.bindFramebuffer(gl.FRAMEBUFFER, fbo.framebuffer); - // makesure all textures are unbound.. - - // now check for updates... - if (fbo.dirtyId !== framebuffer.dirtyId) - { - fbo.dirtyId = framebuffer.dirtyId; - - if (fbo.dirtyFormat !== framebuffer.dirtyFormat) - { - fbo.dirtyFormat = framebuffer.dirtyFormat; - this.updateFramebuffer(framebuffer); - } - else if (fbo.dirtySize !== framebuffer.dirtySize) - { - fbo.dirtySize = framebuffer.dirtySize; - this.resizeFramebuffer(framebuffer); - } - } - - for (let i = 0; i < framebuffer.colorTextures.length; i++) - { - if (framebuffer.colorTextures[i].texturePart) - { - this.renderer.texture.unbind(framebuffer.colorTextures[i].texture); - } - else - { - this.renderer.texture.unbind(framebuffer.colorTextures[i]); - } - } - - if (framebuffer.depthTexture) - { - this.renderer.texture.unbind(framebuffer.depthTexture); - } - - if (frame) - { - this.setViewport(frame.x, frame.y, frame.width, frame.height); - } - else - { - this.setViewport(0, 0, framebuffer.width, framebuffer.height); - } - } - else - { - gl.bindFramebuffer(gl.FRAMEBUFFER, null); - - if (frame) - { - this.setViewport(frame.x, frame.y, frame.width, frame.height); - } - else - { - this.setViewport(0, 0, this.renderer.width, this.renderer.height); - } - } - } - - setViewport(x, y, width, height) - { - const v = this.viewport; - - if (v.width !== width || v.height !== height || v.x !== x || v.y !== y) - { - v.x = x; - v.y = y; - v.width = width; - v.height = height; - - this.gl.viewport(x, y, width, height); - } - } - - get size() - { - if (this.current) - { - // TODO store temp - return { x: 0, y: 0, width: this.current.width, height: this.current.height }; - } - - return { x: 0, y: 0, width: this.renderer.width, height: this.renderer.height }; - } - - clear(r, g, b, a) - { - const gl = this.gl; - - // TODO clear color can be set only one right? - gl.clearColor(r, g, b, a); - gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); - } - - // private functions... - - initFramebuffer(framebuffer) - { - const gl = this.gl; - - // TODO - make this a class? - const fbo = { - framebuffer: gl.createFramebuffer(), - stencil: null, - dirtyId: 0, - dirtyFormat: 0, - dirtySize: 0, - }; - - framebuffer.glFrameBuffers[this.CONTEXT_UID] = fbo; - - return fbo; - } - - resizeFramebuffer(framebuffer) - { - const gl = this.gl; - - if (framebuffer.stencil || framebuffer.depth) - { - gl.bindRenderbuffer(gl.RENDERBUFFER, this.stencil); - gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); - } - } - - updateFramebuffer(framebuffer) - { - const gl = this.gl; - - const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID]; - - // bind the color texture - const colorTextures = framebuffer.colorTextures; - - let count = colorTextures.length; - - if (!this.drawBufferExtension) - { - count = Math.min(count, 1); - } - - const activeTextures = []; - - for (let i = 0; i < count; i++) - { - const texture = framebuffer.colorTextures[i]; - - if (texture.texturePart) - { - this.renderer.texture.bind(texture.texture, 0); - - gl.framebufferTexture2D(gl.FRAMEBUFFER, - gl.COLOR_ATTACHMENT0 + i, - gl.TEXTURE_CUBE_MAP_NEGATIVE_X + texture.side, - texture.texture._glTextures[this.CONTEXT_UID].texture, - 0); - } - else - { - this.renderer.texture.bind(texture, 0); - - gl.framebufferTexture2D(gl.FRAMEBUFFER, - gl.COLOR_ATTACHMENT0 + i, - gl.TEXTURE_2D, - texture._glTextures[this.CONTEXT_UID].texture, - 0); - } - - activeTextures.push(gl.COLOR_ATTACHMENT0 + i); - } - - if (this.drawBufferExtension && activeTextures.length > 1) - { - this.drawBufferExtension.drawBuffersWEBGL(activeTextures); - } - - if (framebuffer.depthTexture) - { - const depthTextureExt = this.renderer.context.extensions.depthTexture; - - if (depthTextureExt) - { - const depthTexture = framebuffer.depthTexture; - - this.renderer.texture.bind(depthTexture, 0); - - gl.framebufferTexture2D(gl.FRAMEBUFFER, - gl.DEPTH_ATTACHMENT, - gl.TEXTURE_2D, - depthTexture._glTextures[this.CONTEXT_UID].texture, - 0); - } - } - - if (framebuffer.stencil || framebuffer.depth) - { - fbo.stencil = gl.createRenderbuffer(); - - gl.bindRenderbuffer(gl.RENDERBUFFER, fbo.stencil); - - // TODO.. this is depth AND stencil? - gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, fbo.stencil); - gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); - // fbo.enableStencil(); - } - } -} diff --git a/packages/core/src/renderers/systems/MaskSystem.js b/packages/core/src/renderers/systems/MaskSystem.js deleted file mode 100644 index d9c4b1a..0000000 --- a/packages/core/src/renderers/systems/MaskSystem.js +++ /dev/null @@ -1,199 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import AlphaMaskFilter from '../filters/spriteMask/SpriteMaskFilter'; - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class MaskSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - // TODO - we don't need both! - this.scissor = false; - this.scissorData = null; - this.scissorRenderTarget = null; - - this.enableScissor = false; - - this.alphaMaskPool = []; - this.alphaMaskIndex = 0; - } - - /** - * Applies the Mask and adds it to the current filter stack. - * - * @param {PIXI.DisplayObject} target - Display Object to push the mask to - * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. - */ - push(target, maskData) - { - // TODO the root check means scissor rect will not - // be used on render textures more info here: - // https://github.com/pixijs/pixi.js/pull/3545 - - if (maskData.texture) - { - this.pushSpriteMask(target, maskData); - } - else if (this.enableScissor - && !this.scissor - && this.renderer._activeRenderTarget.root - && !this.renderer.stencil.stencilMaskStack.length - && maskData.isFastRect()) - { - const matrix = maskData.worldTransform; - - let rot = Math.atan2(matrix.b, matrix.a); - - // use the nearest degree! - rot = Math.round(rot * (180 / Math.PI)); - - if (rot % 90) - { - this.pushStencilMask(maskData); - } - else - { - this.pushScissorMask(target, maskData); - } - } - else - { - this.pushStencilMask(maskData); - } - } - - /** - * Removes the last mask from the mask stack and doesn't return it. - * - * @param {PIXI.DisplayObject} target - Display Object to pop the mask from - * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. - */ - pop(target, maskData) - { - if (maskData.texture) - { - this.popSpriteMask(target, maskData); - } - else if (this.enableScissor && !this.renderer.stencil.stencilMaskStack.length) - { - this.popScissorMask(target, maskData); - } - else - { - this.popStencilMask(target, maskData); - } - } - - /** - * Applies the Mask and adds it to the current filter stack. - * - * @param {PIXI.RenderTarget} target - Display Object to push the sprite mask to - * @param {PIXI.Sprite} maskData - Sprite to be used as the mask - */ - pushSpriteMask(target, maskData) - { - let alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; - - if (!alphaMaskFilter) - { - alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; - } - - alphaMaskFilter[0].resolution = this.renderer.resolution; - alphaMaskFilter[0].maskSprite = maskData; - - // TODO - may cause issues! - target.filterArea = maskData.getBounds(true); - - this.renderer.filterSystem.pushFilter(target, alphaMaskFilter); - - this.alphaMaskIndex++; - } - - /** - * Removes the last filter from the filter stack and doesn't return it. - * - */ - popSpriteMask() - { - this.renderer.filterSystem.popFilter(); - this.alphaMaskIndex--; - } - - /** - * Applies the Mask and adds it to the current filter stack. - * - * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. - */ - pushStencilMask(maskData) - { - this.renderer.batch.flush(); - this.renderer.stencil.pushStencil(maskData); - } - - /** - * Removes the last filter from the filter stack and doesn't return it. - * - */ - popStencilMask() - { - // this.renderer.currentRenderer.stop(); - this.renderer.stencil.popStencil(); - } - - /** - * - * @param {PIXI.DisplayObject} target - Display Object to push the mask to - * @param {PIXI.Graphics} maskData - The masking data. - */ - pushScissorMask(target, maskData) - { - maskData.renderable = true; - - const renderTarget = this.renderer._activeRenderTarget; - - const bounds = maskData.getBounds(); - - bounds.fit(renderTarget.size); - maskData.renderable = false; - - this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); - - const resolution = this.renderer.resolution; - - this.renderer.gl.scissor( - bounds.x * resolution, - (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, - bounds.width * resolution, - bounds.height * resolution - ); - - this.scissorRenderTarget = renderTarget; - this.scissorData = maskData; - this.scissor = true; - } - - /** - * - * - */ - popScissorMask() - { - this.scissorRenderTarget = null; - this.scissorData = null; - this.scissor = false; - - // must be scissor! - const gl = this.renderer.gl; - - gl.disable(gl.SCISSOR_TEST); - } -} diff --git a/packages/core/src/renderers/systems/ProjectionSystem.js b/packages/core/src/renderers/systems/ProjectionSystem.js deleted file mode 100644 index ec5efdb..0000000 --- a/packages/core/src/renderers/systems/ProjectionSystem.js +++ /dev/null @@ -1,73 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { Matrix } from '@pixi/math'; - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ - -export default class ProjectionSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this.projectionMatrix = new Matrix(); - } - - update(destinationFrame, sourceFrame, resolution, root) - { - this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; - this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; - - this.calculateProjection(this.destinationFrame, this.sourceFrame, resolution, root); - - this.renderer.globalUniforms.uniforms.projectionMatrix = this.projectionMatrix; - this.renderer.globalUniforms.update(); - } - - /** - * Updates the projection matrix based on a projection frame (which is a rectangle) - * - * @param {Rectangle} destinationFrame - The destination frame. - * @param {Rectangle} sourceFrame - The source frame. - */ - calculateProjection(destinationFrame, sourceFrame, resolution, root) - { - const pm = this.projectionMatrix; - - // I don't think we will need this line.. - // pm.identity(); - - if (!root) - { - pm.a = (1 / destinationFrame.width * 2) * resolution; - pm.d = (1 / destinationFrame.height * 2) * resolution; - - pm.tx = -1 - (sourceFrame.x * pm.a); - pm.ty = -1 - (sourceFrame.y * pm.d); - } - else - { - pm.a = (1 / destinationFrame.width * 2) * resolution; - pm.d = (-1 / destinationFrame.height * 2) * resolution; - - pm.tx = -1 - (sourceFrame.x * pm.a); - pm.ty = 1 - (sourceFrame.y * pm.d); - } - } - - /** - * Sets the transform of the active render target to the given matrix - * - * @param {PIXI.Matrix} matrix - The transformation matrix - */ - setTransform()// matrix) - { - // this._activeRenderTarget.transform = matrix; - } -} diff --git a/packages/core/src/renderers/systems/RenderTextureSystem.js b/packages/core/src/renderers/systems/RenderTextureSystem.js deleted file mode 100644 index a133ef1..0000000 --- a/packages/core/src/renderers/systems/RenderTextureSystem.js +++ /dev/null @@ -1,115 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { Rectangle } from '@pixi/math'; - -const tempRect = new Rectangle(); - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ - -export default class RenderTextureSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this.clearColor = renderer._backgroundColorRgba; - - // TODO moe this property somewhere else! - this.defaultMaskStack = []; - this.defaultFilterStack = [{}]; - - // empty render texture? - this.renderTexture = null; - - this.destinationFrame = new Rectangle(); - } - - bind(renderTexture, sourceFrame, destinationFrame) - { - // TODO - do we want this?? - if (this.renderTexture === renderTexture) return; - this.renderTexture = renderTexture; - - const renderer = this.renderer; - - if (renderTexture) - { - const baseTexture = renderTexture.baseTexture; - - if (!destinationFrame) - { - tempRect.width = baseTexture.realWidth; - tempRect.height = baseTexture.realHeight; - - destinationFrame = tempRect; - } - - if (!sourceFrame) - { - sourceFrame = destinationFrame; - } - - this.renderer.framebuffer.bind(baseTexture.frameBuffer, destinationFrame); - - this.renderer.projection.update(destinationFrame, sourceFrame, baseTexture.resolution, false); - this.renderer.stencil.setMaskStack(baseTexture.stencilMaskStack); - } - else - { - // TODO these validation checks happen deeper down.. - // thing they can be avoided.. - if (!destinationFrame) - { - tempRect.width = renderer.width; - tempRect.height = renderer.height; - - destinationFrame = tempRect; - } - - if (!sourceFrame) - { - sourceFrame = destinationFrame; - } - - renderer.framebuffer.bind(null, destinationFrame); - - // TODO store this.. - this.renderer.projection.update(destinationFrame, sourceFrame, this.renderer.resolution, true); - this.renderer.stencil.setMaskStack(this.defaultMaskStack); - } - - this.destinationFrame.copyFrom(destinationFrame); - } - - /** - * Erases the render texture and fills the drawing area with a colour - * - * @param {number} [clearColor] - The colour - * @return {PIXI.Renderer} Returns itself. - */ - clear(clearColor) - { - if (this.renderTexture) - { - clearColor = clearColor || this.renderTexture.baseTexture.clearColor; - } - else - { - clearColor = clearColor || this.clearColor; - } - - this.renderer.framebuffer.clear(clearColor[0], clearColor[1], clearColor[2], clearColor[3]); - } - - resize()// screenWidth, screenHeight) - { - // resize the root only! - this.bind(null); - } -} diff --git a/packages/core/src/renderers/systems/StateSystem.js b/packages/core/src/renderers/systems/StateSystem.js deleted file mode 100755 index d7f4caf..0000000 --- a/packages/core/src/renderers/systems/StateSystem.js +++ /dev/null @@ -1,288 +0,0 @@ -import mapWebGLBlendModesToPixi from '../utils/mapWebGLBlendModesToPixi'; -import WebGLSystem from './WebGLSystem'; -import WebGLState from '../State'; - -const BLEND = 0; -const OFFSET = 1; -const CULLING = 2; -const DEPTH_TEST = 3; -const WINDING = 4; - -/** - * A WebGL state machines - * - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class StateSystem extends WebGLSystem -{ - /** - * @param {WebGLRenderingContext} gl - The current WebGL rendering context - */ - constructor(renderer) - { - super(renderer); - - this.gl = null; - - this.maxAttribs = null; - - // check we have vao.. - this.nativeVaoExtension = null; - - this.attribState = null; - - this.stateId = 0; - this.polygonOffset = 0; - this.blendMode = 17; - - this.map = []; - - // map functions for when we set state.. - this.map[BLEND] = this.setBlend; - this.map[OFFSET] = this.setOffset; - this.map[CULLING] = this.setCullFace; - this.map[DEPTH_TEST] = this.setDepthTest; - this.map[WINDING] = this.setFrontFace; - - this.checks = []; - - this.defaultState = new WebGLState(); - this.defaultState.blend = true; - this.defaultState.depth = true; - } - - contextChange(gl) - { - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; - - this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); - - // check we have vao.. - this.nativeVaoExtension = ( - gl.getExtension('OES_vertex_array_object') - || gl.getExtension('MOZ_OES_vertex_array_object') - || gl.getExtension('WEBKIT_OES_vertex_array_object') - ); - - this.attribState = { - tempAttribState: new Array(this.maxAttribs), - attribState: new Array(this.maxAttribs), - }; - - this.blendModes = mapWebGLBlendModesToPixi(gl); - - this.setState(this.defaultState); - - this.reset(); - } - - /** - * Sets the current state - * - * @param {*} state - The state to set. - */ - setState(state) - { - state = state || this.defaultState; - - // TODO maybe to an object check? ( this.state === state )? - if (this.stateId !== state.data) - { - let diff = this.stateId ^ state.data; - let i = 0; - - // order from least to most common - while (diff) - { - if (diff & 1) - { - // state change! - this.map[i].call(this, !!(state.data & (1 << i))); - } - - diff = diff >> 1; - i++; - } - - this.stateId = state.data; - } - - // based on the above settings we check for specific modes.. - // for example if blend is active we check and set the blend modes - // or of polygon offset is active we check the poly depth. - for (let i = 0; i < this.checks.length; i++) - { - this.checks[i](this, state); - } - } - - /** - * Enables or disabled blending. - * - * @param {boolean} value - Turn on or off webgl blending. - */ - setBlend(value) - { - this.updateCheck(StateSystem.checkBlendMode, value); - - this.gl[value ? 'enable' : 'disable'](this.gl.BLEND); - } - - /** - * Enables or disable polygon offset fill - * - * @param {boolean} value - Turn on or off webgl polygon offset testing. - */ - setOffset(value) - { - this.gl[value ? 'enable' : 'disable'](this.gl.POLYGON_OFFSET_FILL); - } - - /** - * Sets whether to enable or disable depth test. - * - * @param {boolean} value - Turn on or off webgl depth testing. - */ - setDepthTest(value) - { - this.gl[value ? 'enable' : 'disable'](this.gl.DEPTH_TEST); - } - - /** - * Sets whether to enable or disable cull face. - * - * @param {boolean} value - Turn on or off webgl cull face. - */ - setCullFace(value) - { - this.gl[value ? 'enable' : 'disable'](this.gl.CULL_FACE); - } - - /** - * Sets the gl front face. - * - * @param {boolean} value - true is clockwise and false is counter-clockwise - */ - setFrontFace(value) - { - this.gl.frontFace(this.gl[value ? 'CW' : 'CCW']); - } - - /** - * Sets the blend mode. - * - * @param {number} value - The blend mode to set to. - */ - setBlendMode(value) - { - if (value === this.blendMode) - { - return; - } - - this.blendMode = value; - this.gl.blendFunc(this.blendModes[value][0], this.blendModes[value][1]); - } - - /** - * Sets the polygon offset. - * - * @param {number} value - the polygon offset - * @param {number} scale - the polygon offset scale - */ - setPolygonOffset(value, scale) - { - this.gl.polygonOffset(value, scale); - } - - /** - * Disables all the vaos in use - * - */ - resetAttributes() - { - for (let i = 0; i < this.attribState.tempAttribState.length; i++) - { - this.attribState.tempAttribState[i] = 0; - } - - for (let i = 0; i < this.attribState.attribState.length; i++) - { - this.attribState.attribState[i] = 0; - } - - // im going to assume one is always active for performance reasons. - for (let i = 1; i < this.maxAttribs; i++) - { - this.gl.disableVertexAttribArray(i); - } - } - - // used - /** - * Resets all the logic and disables the vaos - */ - reset() - { - // unbind any VAO if they exist.. - if (this.nativeVaoExtension) - { - this.nativeVaoExtension.bindVertexArrayOES(null); - } - - // reset all attributes.. - this.resetAttributes(); - - this.gl.pixelStorei(this.gl.UNPACK_FLIP_Y_WEBGL, false); - - this.setBlendMode(0); - - // TODO? - // this.setState(this.defaultState); - } - - /** - * checks to see which updates should be checked based on which settings have been activated - * for example if blend is enabled then we shold check the blend modes each time the state is changed - * or if poygon fill is activated then we need to check if the polygone offset changes. - * The idea is that we only check what we have too. - * - * @param {Function} func the checking function to add or remove - * @param {boolean} value should the check function be added or removed. - */ - updateCheck(func, value) - { - const index = this.checks.indexOf(func); - - if (value && index === -1) - { - this.checks.push(func); - } - else if (!value && index !== -1) - { - this.checks.splice(index, 1); - } - } - - /** - * A private little wrapper function that we call to check the blend mode. - * - * @static - * @private - * @param {PIXI.StateSystem} System the System to perform the state check on - * @param {PIXI.State} state the state that the blendMode will pulled from - */ - static checkBlendMode(system, state) - { - system.setBlendMode(state.blendMode); - } - - // TODO - add polygon offset? -} diff --git a/packages/core/src/renderers/systems/StencilSystem.js b/packages/core/src/renderers/systems/StencilSystem.js deleted file mode 100644 index 5d538cb..0000000 --- a/packages/core/src/renderers/systems/StencilSystem.js +++ /dev/null @@ -1,133 +0,0 @@ -import WebGLSystem from './WebGLSystem'; - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class StencilSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - this.stencilMaskStack = []; - } - - /** - * Changes the mask stack that is used by this System. - * - * @param {PIXI.Graphics[]} stencilMaskStack - The mask stack - */ - setMaskStack(stencilMaskStack) - { - const gl = this.renderer.gl; - - if (stencilMaskStack.length !== this.stencilMaskStack.length) - { - if (stencilMaskStack.length === 0) - { - gl.disable(gl.STENCIL_TEST); - } - else - { - gl.enable(gl.STENCIL_TEST); - } - } - - this.stencilMaskStack = stencilMaskStack; - } - - /** - * Applies the Mask and adds it to the current stencil stack. @alvin - * - * @param {PIXI.Graphics} graphics - The mask - */ - pushStencil(graphics) - { - this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); - - // this.renderer._activeRenderTarget.attachStencilBuffer(); - - const gl = this.renderer.gl; - const prevMaskCount = this.stencilMaskStack.length; - - if (prevMaskCount === 0) - { - gl.enable(gl.STENCIL_TEST); - } - - this.stencilMaskStack.push(graphics); - - // Increment the refference stencil value where the new mask overlaps with the old ones. - gl.colorMask(false, false, false, false); - gl.stencilFunc(gl.EQUAL, prevMaskCount, this._getBitwiseMask()); - gl.stencilOp(gl.KEEP, gl.KEEP, gl.INCR); - this.renderer.plugins.graphics.render(graphics); - - this._useCurrent(); - } - - /** - * Removes the last mask from the stencil stack. @alvin - */ - popStencil() - { - this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); - - const gl = this.renderer.gl; - const graphics = this.stencilMaskStack.pop(); - - if (this.stencilMaskStack.length === 0) - { - // the stack is empty! - gl.disable(gl.STENCIL_TEST); - gl.clear(gl.STENCIL_BUFFER_BIT); - gl.clearStencil(0); - } - else - { - // Decrement the refference stencil value where the popped mask overlaps with the other ones - gl.colorMask(false, false, false, false); - gl.stencilOp(gl.KEEP, gl.KEEP, gl.DECR); - this.renderer.plugins.graphics.render(graphics); - - this._useCurrent(); - } - } - - /** - * Setup renderer to use the current stencil data. - */ - _useCurrent() - { - const gl = this.renderer.gl; - - gl.colorMask(true, true, true, true); - gl.stencilFunc(gl.EQUAL, this.stencilMaskStack.length, this._getBitwiseMask()); - gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP); - } - - /** - * Fill 1s equal to the number of acitve stencil masks. - * - * @return {number} The bitwise mask. - */ - _getBitwiseMask() - { - return (1 << this.stencilMaskStack.length) - 1; - } - - /** - * Destroys the mask stack. - * - */ - destroy() - { - super.destroy(this); - - this.stencilMaskStack = null; - } -} diff --git a/packages/core/src/renderers/systems/WebGLSystem.js b/packages/core/src/renderers/systems/WebGLSystem.js deleted file mode 100644 index d3d5b31..0000000 --- a/packages/core/src/renderers/systems/WebGLSystem.js +++ /dev/null @@ -1,41 +0,0 @@ -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this manager works for. - */ - constructor(renderer) - { - /** - * The renderer this manager works for. - * - * @member {PIXI.Renderer} - */ - this.renderer = renderer; - - this.renderer.runners.contextChange.add(this); - } - - /** - * Generic method called when there is a WebGL context change. - * - */ - contextChange() - { - // do some codes init! - } - - /** - * Generic destroy methods to be overridden by the subclass - * - */ - destroy() - { - this.renderer.runners.contextChange.remove(this); - this.renderer = null; - } -} diff --git a/packages/core/src/renderers/systems/filter/FilterSystem.js b/packages/core/src/renderers/systems/filter/FilterSystem.js deleted file mode 100644 index e615037..0000000 --- a/packages/core/src/renderers/systems/filter/FilterSystem.js +++ /dev/null @@ -1,431 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; - -import RenderTexture from '../../../textures/RenderTexture'; -import Quad from '../../utils/Quad'; -import QuadUv from '../../utils/QuadUv'; -import { Rectangle } from '@pixi/math'; -import * as filterTransforms from '../../filters/filterTransforms'; -import bitTwiddle from 'bit-twiddle'; -import UniformGroup from '../../../shader/UniformGroup'; -import { DRAW_MODES } from '../../../../../constants'; - -// -/** - * @ignore - * @class - */ -class FilterState -{ - /** - * - */ - constructor() - { - this.renderTexture = null; - this.sourceFrame = new Rectangle(); - this.destinationFrame = new Rectangle(); - this.filters = []; - this.target = null; - this.legacy = false; - this.resolution = 1; - } -} - -/** - * @class - * @memberof PIXI.systems - * @extends PIXI.systems.WebGLSystem - */ -export default class FilterSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - /** - * stores a bunch of PO2 textures used for filtering - * @type {Object} - */ - this.texturePool = {}; - - /** - * a pool for storing filter states, save us creating new ones each tick - * @type {Array} - */ - this.statePool = []; - - /** - * A very simple geometry used when drawing a filter effect to the screen - * @type {Quad} - */ - this.quad = new Quad(); - - this.quadUv = new QuadUv(); - - /** - * Temporary rect for maths - * @type {PIXI.Rectangle} - */ - this.tempRect = new Rectangle(); - - this.activeState = {}; - - /** - * this uniform group is attached to filter uniforms when used - * @type {UniformGroup} - */ - this.globalUniforms = new UniformGroup({ - sourceFrame: this.tempRect, - destinationFrame: this.tempRect, - - // legacy variables - filterArea: new Float32Array(4), - filterClamp: new Float32Array(4), - }, true); - } - - /** - * Adds a new filter to the System. - * - * @param {PIXI.DisplayObject} target - The target of the filter to render. - * @param {PIXI.Filter[]} filters - The filters to apply. - */ - push(target, filters) - { - const renderer = this.renderer; - const filterStack = this.renderer.renderTexture.defaultFilterStack; - const state = this.statePool.pop() || new FilterState(); - - let resolution = filters[0].resolution; - let padding = filters[0].padding; - let autoFit = filters[0].autoFit; - let legacy = filters[0].legacy; - - for (let i = 1; i < filters.length; i++) - { - const filter = filters[i]; - - // lets use the lowest resolution.. - resolution = Math.min(resolution, filter.resolution); - // and the largest amount of padding! - padding = Math.max(padding, filter.padding); - // only auto fit if all filters are autofit - autoFit = autoFit || filter.autoFit; - - legacy = legacy || filter.legacy; - } - - filterStack.push(state); - - state.resolution = resolution; - - state.legacy = legacy; - - // round to whole number based on resolution - // TODO move that to the shader too? - state.sourceFrame = target.filterArea || target.getBounds(true); - - state.sourceFrame.pad(padding); - - if (autoFit) - { - state.sourceFrame.fit(this.renderer.renderTexture.destinationFrame); - } - - state.sourceFrame.round(resolution); - - state.renderTexture = this.getPotFilterTexture(state.sourceFrame.width, state.sourceFrame.height, resolution); - state.filters = filters; - - state.destinationFrame.width = state.renderTexture.width; - state.destinationFrame.height = state.renderTexture.height; - - state.renderTexture.filterFrame = state.sourceFrame; - renderer.renderTexture.bind(state.renderTexture, state.sourceFrame);// /, state.destinationFrame); - renderer.renderTexture.clear(); - } - - /** - * Pops off the filter and applies it. - * - */ - pop() - { - const renderer = this.renderer; - const filterStack = renderer.renderTexture.defaultFilterStack; - const state = filterStack.pop(); - const filters = state.filters; - - this.activeState = state; - - const globalUniforms = this.globalUniforms.uniforms; - - globalUniforms.sourceFrame = state.sourceFrame; - globalUniforms.destinationFrame = state.destinationFrame; - globalUniforms.resolution = state.resolution; - - // only update the rect if its legacy.. - if (state.legacy) - { - const filterArea = globalUniforms.filterArea; - const filterClamp = globalUniforms.filterClamp; - - filterArea[0] = state.destinationFrame.width; - filterArea[1] = state.destinationFrame.height; - filterArea[2] = state.sourceFrame.x; - filterArea[3] = state.sourceFrame.y; - - filterClamp[0] = 0.5 / state.resolution / state.destinationFrame.width; - filterClamp[1] = 0.5 / state.resolution / state.destinationFrame.height; - filterClamp[2] = (state.sourceFrame.width - 0.5) / state.resolution / state.destinationFrame.width; - filterClamp[3] = (state.sourceFrame.height - 0.5) / state.resolution / state.destinationFrame.height; - } - - this.globalUniforms.update(); - - const lastState = filterStack[filterStack.length - 1]; - - if (filters.length === 1) - { - filters[0].apply(this, state.renderTexture, lastState.renderTexture, false, state); - - this.returnFilterTexture(state.renderTexture); - } - else - { - let flip = state.renderTexture; - let flop = this.getPotFilterTexture( - flip.width, - flip.height, - state.resolution - ); - - flop.filterFrame = flip.filterFrame; - - let i = 0; - - for (i = 0; i < filters.length - 1; ++i) - { - filters[i].apply(this, flip, flop, true, state); - - const t = flip; - - flip = flop; - flop = t; - } - - filters[i].apply(this, flip, lastState.renderTexture, false, state); - - this.returnFilterTexture(flip); - this.returnFilterTexture(flop); - } - - this.statePool.push(state); - } - - /** - * Draws a filter. - * - * @param {PIXI.Filter} filter - The filter to draw. - * @param {PIXI.RenderTarget} input - The input render target. - * @param {PIXI.RenderTarget} output - The target to output to. - * @param {boolean} clear - Should the output be cleared before rendering to it - */ - applyFilter(filter, input, output, clear) - { - const renderer = this.renderer; - - renderer.renderTexture.bind(output, output ? output.filterFrame : null); - - if (clear) - { - // gl.disable(gl.SCISSOR_TEST); - renderer.renderTexture.clear(); - // gl.enable(gl.SCISSOR_TEST); - } - - // set the uniforms.. - filter.uniforms.uSampler = input; - filter.uniforms.filterGlobals = this.globalUniforms; - - // TODO make it so that the order of this does not matter.. - // because it does at the moment cos of global uniforms. - // they need to get resynced - - renderer.state.setState(filter.state); - renderer.shader.bind(filter); - - if (filter.legacy) - { - this.quadUv.map(input._frame, input.filterFrame); - - renderer.geometry.bind(this.quadUv); - renderer.geometry.draw(DRAW_MODES.TRIANGLES); - } - else - { - renderer.geometry.bind(this.quad); - renderer.geometry.draw(DRAW_MODES.TRIANGLE_STRIP); - } - } - - /** - * Calculates the mapped matrix. - * - * TODO playing around here.. this is temporary - (will end up in the shader) - * this returns a matrix that will normalise map filter cords in the filter to screen space - * - * @param {PIXI.Matrix} outputMatrix - the matrix to output to. - * @return {PIXI.Matrix} The mapped matrix. - */ - calculateScreenSpaceMatrix(outputMatrix) - { - const currentState = this.activeState; - - return filterTransforms.calculateScreenSpaceMatrix( - outputMatrix, - currentState.sourceFrame, - currentState.destinationFrame - ); - } - - /** - * This will map the filter coord so that a texture can be used based on the transform of a sprite - * - * @param {PIXI.Matrix} outputMatrix - The matrix to output to. - * @param {PIXI.Sprite} sprite - The sprite to map to. - * @return {PIXI.Matrix} The mapped matrix. - */ - calculateSpriteMatrix(outputMatrix, sprite) - { - const currentState = this.activeState; - - return filterTransforms.calculateSpriteMatrix( - outputMatrix, - currentState.sourceFrame, - currentState.destinationFrame, - sprite - ); - } - - /** - * Destroys this Filter System. - * - * @param {boolean} [contextLost=false] context was lost, do not free shaders - * - */ - destroy(contextLost = false) - { - if (!contextLost) - { - this.emptyPool(); - } - else - { - this.texturePool = {}; - } - } - - /** - * Gets a Power-of-Two render texture. - * - * TODO move to a seperate class could be on renderer? - * also - could cause issue with multiple contexts? - * - * @private - * @param {WebGLRenderingContext} gl - The webgl rendering context - * @param {number} minWidth - The minimum width of the render target. - * @param {number} minHeight - The minimum height of the render target. - * @param {number} resolution - The resolution of the render target. - * @return {PIXI.RenderTarget} The new render target. - */ - getPotFilterTexture(minWidth, minHeight, resolution) - { - minWidth = bitTwiddle.nextPow2(minWidth); - minHeight = bitTwiddle.nextPow2(minHeight); - resolution = resolution || 1; - - const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); - - if (!this.texturePool[key]) - { - this.texturePool[key] = []; - } - - let renderTexture = this.texturePool[key].pop(); - - if (!renderTexture) - { - // temporary bypass cache.. - // internally - this will cause a texture to be bound.. - renderTexture = RenderTexture.create({ - width: minWidth, - height: minHeight, - resolution, - }); - } - - return renderTexture; - } - - /** - * Gets extra render texture to use inside current filter - * - * @param {number} resolution resolution of the renderTexture - * @returns {PIXI.RenderTexture} - */ - getFilterTexture(resolution) - { - const rt = this.activeState.renderTexture; - - const filterTexture = this.getPotFilterTexture(rt.width, rt.height, resolution || rt.baseTexture.resolution); - - filterTexture.filterFrame = rt.filterFrame; - - return filterTexture; - } - - /** - * Frees a render texture back into the pool. - * - * @param {PIXI.RenderTarget} renderTexture - The renderTarget to free - */ - returnFilterTexture(renderTexture) - { - renderTexture.filterFrame = null; - - const base = renderTexture.baseTexture; - - const minWidth = base.width; - const minHeight = base.height; - - const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); - - this.texturePool[key].push(renderTexture); - } - - /** - * Empties the texture pool. - * - */ - emptyPool() - { - for (const i in this.texturePool) - { - const textures = this.texturePool[i]; - - if (textures) - { - for (let j = 0; j < textures.length; j++) - { - textures[j].destroy(true); - } - } - } - - this.texturePool = {}; - } -} diff --git a/packages/core/src/renderers/systems/geometry/GLBuffer.js b/packages/core/src/renderers/systems/geometry/GLBuffer.js deleted file mode 100644 index 0477e8a..0000000 --- a/packages/core/src/renderers/systems/geometry/GLBuffer.js +++ /dev/null @@ -1,9 +0,0 @@ -export default class GLBuffer -{ - constructor(buffer) - { - this.buffer = buffer; - this.updateID = -1; - this.byteLength = -1; - } -} diff --git a/packages/core/src/renderers/systems/geometry/GeometrySystem.js b/packages/core/src/renderers/systems/geometry/GeometrySystem.js deleted file mode 100644 index 26c8e9e..0000000 --- a/packages/core/src/renderers/systems/geometry/GeometrySystem.js +++ /dev/null @@ -1,388 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; -import GLBuffer from './GLBuffer'; - -const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class GeometrySystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this._activeGeometry = null; - this._activeVao = null; - - this.hasVao = true; - this.hasInstance = true; - } - - /** - * Sets up the renderer context and necessary buffers. - * - * @private - */ - contextChange() - { - const gl = this.gl = this.renderer.gl; - - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - - // webgl2 - if (!gl.createVertexArray) - { - // webgl 1! - let nativeVaoExtension = this.renderer.context.extensions.vertexArrayObject; - - if (this.renderer.legacy) - { - nativeVaoExtension = null; - } - - if (nativeVaoExtension) - { - gl.createVertexArray = () => - nativeVaoExtension.createVertexArrayOES(); - - gl.bindVertexArray = (vao) => - nativeVaoExtension.bindVertexArrayOES(vao); - - gl.deleteVertexArray = (vao) => - nativeVaoExtension.deleteVertexArrayOES(vao); - } - else - { - this.hasVao = false; - gl.createVertexArray = () => - { - // empty - }; - - gl.bindVertexArray = () => - { - // empty - }; - - gl.deleteVertexArray = () => - { - // empty - }; - } - } - - if (!gl.vertexAttribDivisor) - { - const instanceExt = gl.getExtension('ANGLE_instanced_arrays'); - - if (instanceExt) - { - gl.vertexAttribDivisor = (a, b) => - instanceExt.vertexAttribDivisorANGLE(a, b); - - gl.drawElementsInstanced = (a, b, c, d, e) => - instanceExt.drawElementsInstancedANGLE(a, b, c, d, e); - - gl.drawArraysInstanced = (a, b, c, d) => - instanceExt.drawArraysInstancedANGLE(a, b, c, d); - } - else - { - this.hasInstance = false; - } - } - } - - /** - * Binds geometry so that is can be drawn. Creating a Vao if required - * @private - * @param {PIXI.Geometry} geometry instance of geometry to bind - */ - bind(geometry, shader) - { - shader = shader || this.renderer.shader.shader; - - const gl = this.gl; - - // not sure the best way to address this.. - // currently different shaders require different VAOs for the same geometry - // Still mulling over the best way to solve this one.. - // will likely need to modify the shader attribute locations at run time! - let vaos = geometry.glVertexArrayObjects[this.CONTEXT_UID]; - - if (!vaos) - { - geometry.glVertexArrayObjects[this.CONTEXT_UID] = vaos = {}; - } - - const vao = vaos[shader.program.id] || this.initGeometryVao(geometry, shader.program); - - this._activeGeometry = geometry; - - if (this._activeVao !== vao) - { - this._activeVao = vao; - - if (this.hasVao) - { - gl.bindVertexArray(vao); - } - else - { - this.activateVao(geometry, shader.program); - } - } - - // TODO - optimise later! - // don't need to loop through if nothing changed! - // maybe look to add an 'autoupdate' to geometry? - this.updateBuffers(); - } - - reset() - { - this.unbind(); - } - - updateBuffers() - { - const geometry = this._activeGeometry; - const gl = this.gl; - - for (let i = 0; i < geometry.buffers.length; i++) - { - const buffer = geometry.buffers[i]; - - const glBuffer = buffer._glBuffers[this.CONTEXT_UID]; - - if (buffer._updateID !== glBuffer.updateID) - { - glBuffer.updateID = buffer._updateID; - - // TODO can cache this on buffer! maybe added a getter / setter? - const type = buffer.index ? gl.ELEMENT_ARRAY_BUFFER : gl.ARRAY_BUFFER; - const drawType = buffer.static ? gl.STATIC_DRAW : gl.DYNAMIC_DRAW; - - gl.bindBuffer(type, glBuffer.buffer); - - if (glBuffer.byteLength >= buffer.data.byteLength) - { - // offset is always zero for now! - gl.bufferSubData(type, 0, buffer.data); - } - else - { - gl.bufferData(type, buffer.data, drawType); - } - } - } - } - - checkCompatability(geometry, program) - { - // geometry must have at least all the attributes that the shader requires. - const geometryAttributes = geometry.attributes; - const shaderAttributes = program.attributeData; - - for (const j in shaderAttributes) - { - if (!geometryAttributes[j]) - { - throw new Error(`shader and geometry incompatible, geometry missing the "${j}" attribute`); - } - } - } - - /** - * Creates a Vao with the same structure as the geometry and stores it on the geometry. - * @private - * @param {PIXI.Geometry} geometry instance of geometry to to generate Vao for - */ - initGeometryVao(geometry, program) - { - this.checkCompatability(geometry, program); - - const gl = this.gl; - const CONTEXT_UID = this.CONTEXT_UID; - const buffers = geometry.buffers; - const attributes = geometry.attributes; - - const tempStride = {}; - const tempStart = {}; - - for (const j in buffers) - { - tempStride[j] = 0; - tempStart[j] = 0; - } - - for (const j in attributes) - { - if (!attributes[j].size && program.attributeData[j]) - { - attributes[j].size = program.attributeData[j].size; - } - - tempStride[attributes[j].buffer] += attributes[j].size * byteSizeMap[attributes[j].type]; - } - - for (const j in attributes) - { - const attribute = attributes[j]; - const attribSize = attribute.size; - - if (attribute.stride === undefined) - { - if (tempStride[attribute.buffer] === attribSize * byteSizeMap[attribute.type]) - { - attribute.stride = 0; - } - else - { - attribute.stride = tempStride[attribute.buffer]; - } - } - - if (attribute.start === undefined) - { - attribute.start = tempStart[attribute.buffer]; - - tempStart[attribute.buffer] += attribSize * byteSizeMap[attribute.type]; - } - } - - // first update - and create the buffers! - // only create a gl buffer if it actually gets - for (let i = 0; i < buffers.length; i++) - { - const buffer = buffers[i]; - - if (!buffer._glBuffers[CONTEXT_UID]) - { - buffer._glBuffers[CONTEXT_UID] = new GLBuffer(gl.createBuffer()); - } - } - - // TODO - maybe make this a data object? - // lets wait to see if we need to first! - const vao = gl.createVertexArray(); - - gl.bindVertexArray(vao); - - this.activateVao(geometry, program); - - gl.bindVertexArray(null); - - // add it to the cache! - geometry.glVertexArrayObjects[this.CONTEXT_UID][program.id] = vao; - - return vao; - } - - activateVao(geometry, program) - { - const gl = this.gl; - const CONTEXT_UID = this.CONTEXT_UID; - const buffers = geometry.buffers; - const attributes = geometry.attributes; - - if (geometry.indexBuffer) - { - // first update the index buffer if we have one.. - gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, geometry.indexBuffer._glBuffers[CONTEXT_UID].buffer); - } - - let lastBuffer = null; - - // add a new one! - for (const j in attributes) - { - const attribute = attributes[j]; - const buffer = buffers[attribute.buffer]; - const glBuffer = buffer._glBuffers[CONTEXT_UID]; - - if (program.attributeData[j]) - { - if (lastBuffer !== glBuffer) - { - gl.bindBuffer(gl.ARRAY_BUFFER, glBuffer.buffer); - - lastBuffer = glBuffer; - } - - const location = program.attributeData[j].location; - - // TODO introduce state again - // we can optimise this for older devices that have no VAOs - gl.enableVertexAttribArray(location); - - gl.vertexAttribPointer(location, - attribute.size, - attribute.type || gl.FLOAT, - attribute.normalized, - attribute.stride, - attribute.start); - - if (attribute.instance) - { - // TODO calculate instance count based of this... - if (this.hasInstance) - { - gl.vertexAttribDivisor(location, 1); - } - else - { - throw new Error('geometry error, GPU Instancing is not supported on this device'); - } - } - } - } - } - - draw(type, size, start, instanceCount) - { - const gl = this.gl; - const geometry = this._activeGeometry; - - // TODO.. this should not change so maybe cache the function? - - if (geometry.indexBuffer) - { - if (geometry.instanced) - { - /* eslint-disable max-len */ - gl.drawElementsInstanced(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2, instanceCount || 1); - /* eslint-enable max-len */ - } - else - { - gl.drawElements(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2); - } - } - else - if (geometry.instanced) - { - // TODO need a better way to calculate size.. - gl.drawArraysInstanced(type, start, size || geometry.getSize(), instanceCount || 1); - } - else - { - gl.drawArrays(type, start, size || geometry.getSize()); - } - - return this; - } - - unbind() - { - this.gl.bindVertexArray(null); - this._activeVao = null; - this._activeGeometry = null; - } -} diff --git a/packages/core/src/renderers/systems/geometry/setVertexAttribArrays.js b/packages/core/src/renderers/systems/geometry/setVertexAttribArrays.js deleted file mode 100644 index 23b6959..0000000 --- a/packages/core/src/renderers/systems/geometry/setVertexAttribArrays.js +++ /dev/null @@ -1,54 +0,0 @@ -// var GL_MAP = {}; - -/** - * @param gl {WebGLRenderingContext} The current WebGL context - * @param attribs {*} - * @param state {*} - */ -export default function setVertexAttribArrays(gl, attribs, state) -{ - let i; - - if (state) - { - const tempAttribState = state.tempAttribState; - const attribState = state.attribState; - - for (i = 0; i < tempAttribState.length; i++) - { - tempAttribState[i] = false; - } - - // set the new attribs - for (i = 0; i < attribs.length; i++) - { - tempAttribState[attribs[i].attribute.location] = true; - } - - for (i = 0; i < attribState.length; i++) - { - if (attribState[i] !== tempAttribState[i]) - { - attribState[i] = tempAttribState[i]; - - if (state.attribState[i]) - { - gl.enableVertexAttribArray(i); - } - else - { - gl.disableVertexAttribArray(i); - } - } - } - } - else - { - for (i = 0; i < attribs.length; i++) - { - const attrib = attribs[i]; - - gl.enableVertexAttribArray(attrib.attribute.location); - } - } -} diff --git a/packages/core/src/renderers/systems/index.js b/packages/core/src/renderers/systems/index.js deleted file mode 100644 index c843f03..0000000 --- a/packages/core/src/renderers/systems/index.js +++ /dev/null @@ -1,6 +0,0 @@ -/** - * Systems are individual components to the Renderer pipeline. - * @namespace PIXI.systems - */ -export { default as WebGLSystem } from './WebGLSystem'; -export { default as FilterSystem } from './filter/FilterSystem'; diff --git a/packages/core/src/renderers/systems/shader/GLProgram.js b/packages/core/src/renderers/systems/shader/GLProgram.js deleted file mode 100644 index 92980ca..0000000 --- a/packages/core/src/renderers/systems/shader/GLProgram.js +++ /dev/null @@ -1,43 +0,0 @@ -/** - * Helper class to create a webGL Program - * - * @class - * @private - */ -export default class GLProgram -{ - constructor(program, uniformData) - { - /** - * The shader program - * - * @member {WebGLProgram} - */ - this.program = program; - - /** - * holds the uniform data which contains uniform locations - * and current uniform values used for caching and preventing undeed GPU commands - * @type {Object} - */ - this.uniformData = uniformData; - - /** - * uniformGroups holds the various upload functions for the shader. Each uniform group - * and program have a unique upload function generated. - * @type {Object} - */ - this.uniformGroups = {}; - } - - /** - * Destroys this program - * TODO - */ - destroy() - { - this.uniformData = null; - this.uniformGroups = null; - this.program = null; - } -} diff --git a/packages/core/src/renderers/systems/shader/ShaderSystem.js b/packages/core/src/renderers/systems/shader/ShaderSystem.js deleted file mode 100644 index 6cf57b6..0000000 --- a/packages/core/src/renderers/systems/shader/ShaderSystem.js +++ /dev/null @@ -1,170 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; -import GLProgram from './GLProgram'; -import generateUniformsSync from '../../../shader/generateUniformsSync'; -import defaultValue from '../../../shader/defaultValue'; -import compileProgram from './shader/compileProgram'; - -let UID = 0; - -/** - * Helper class to create a webGL Texture - * - * @class - * @memberof PIXI - */ -export default class ShaderSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - A reference to the current renderer - */ - constructor(renderer) - { - super(renderer); - - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = null; - - this.shader = null; - this.program = null; - - this.id = UID++; - } - - contextChange(gl) - { - this.gl = gl; - } - - /** - * Changes the current shader to the one given in parameter - * - * @param {PIXI.Shader} shader - the new shader - * @param {boolean} dontSync - false if the shader should automatically sync its uniforms. - * @returns {PIXI.glCore.glProgram} the glProgram that belongs to the shader. - */ - bind(shader, dontSync) - { - shader.uniforms.globals = this.renderer.globalUniforms; - - const program = shader.program; - const glProgram = program.glPrograms[this.renderer.CONTEXT_UID] || this.generateShader(shader); - - this.shader = shader; - - // TODO - some current pixi plugins bypass this.. so it not safe to use yet.. - if (this.program !== program) - { - this.program = program; - this.gl.useProgram(glProgram.program); - } - - if (!dontSync) - { - this.syncUniformGroup(shader.uniformGroup); - } - - return glProgram; - } - - /** - * Uploads the uniforms values to the currently bound shader. - * - * @param {object} uniforms - the uniforms valiues that be applied to the current shader - */ - setUniforms(uniforms) - { - const shader = this.shader.program; - const glProgram = shader.glPrograms[this.renderer.CONTEXT_UID]; - - shader.syncUniforms(glProgram.uniformData, uniforms, this.renderer); - } - - syncUniformGroup(group) - { - const glProgram = this.getglProgram(); - - if (!group.static || group.dirtyId !== glProgram.uniformGroups[group.id]) - { - glProgram.uniformGroups[group.id] = group.dirtyId; - const syncFunc = group.syncUniforms[this.shader.program.id] || this.createSyncGroups(group); - - syncFunc(glProgram.uniformData, group.uniforms, this.renderer); - } - } - - createSyncGroups(group) - { - group.syncUniforms[this.shader.program.id] = generateUniformsSync(group, this.shader.program.uniformData); - - return group.syncUniforms[this.shader.program.id]; - } - - /** - * Returns the underlying GLShade rof the currently bound shader. - * This can be handy for when you to have a little more control over the setting of your uniforms. - * - * @return {PIXI.glCore.Shader} the glProgram for the currently bound Shader for this context - */ - getglProgram() - { - if (this.shader) - { - return this.shader.program.glPrograms[this.renderer.CONTEXT_UID]; - } - - return null; - } - - /** - * Generates a glProgram verion of the Shader provided. - * - * @private - * @param {PIXI.Shader} shader the shader that the glProgram will be based on. - * @return {PIXI.glCore.glProgram} A shiney new glProgram - */ - generateShader(shader) - { - const gl = this.gl; - - const program = shader.program; - - const attribMap = {}; - - for (const i in program.attributeData) - { - attribMap[i] = program.attributeData[i].location; - } - - const shaderProgram = compileProgram(gl, program.vertexSrc, program.fragmentSrc, attribMap); - const uniformData = {}; - - for (const i in program.uniformData) - { - const data = program.uniformData[i]; - - uniformData[i] = { - location: gl.getUniformLocation(shaderProgram, i), - value: defaultValue(data.type, data.size), - }; - } - - const glProgram = new GLProgram(shaderProgram, uniformData); - - program.glPrograms[this.renderer.CONTEXT_UID] = glProgram; - - return glProgram; - } - - /** - * Destroys this System and removes all its textures - */ - destroy() - { - // TODO implement destroy method for ShaderSystem - this.destroyed = true; - } -} diff --git a/packages/core/src/renderers/systems/shader/shader/compileProgram.js b/packages/core/src/renderers/systems/shader/shader/compileProgram.js deleted file mode 100644 index 8c6ccb9..0000000 --- a/packages/core/src/renderers/systems/shader/shader/compileProgram.js +++ /dev/null @@ -1,78 +0,0 @@ -/** - * @method compileProgram - * @memberof PIXI.glCore.shader - * @param gl {WebGLRenderingContext} The current WebGL context {WebGLProgram} - * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. - * @param fragmentSrc {string|string[]} The fragment shader source as an array of strings. - * @param attributeLocations {Object} An attribute location map that lets you manually set the attribute locations - * @return {WebGLProgram} the shader program - */ -export default function compileProgram(gl, vertexSrc, fragmentSrc, attributeLocations) -{ - const glVertShader = compileShader(gl, gl.VERTEX_SHADER, vertexSrc); - const glFragShader = compileShader(gl, gl.FRAGMENT_SHADER, fragmentSrc); - - let program = gl.createProgram(); - - gl.attachShader(program, glVertShader); - gl.attachShader(program, glFragShader); - - // optionally, set the attributes manually for the program rather than letting WebGL decide.. - if (attributeLocations) - { - for (const i in attributeLocations) - { - gl.bindAttribLocation(program, attributeLocations[i], i); - } - } - - gl.linkProgram(program); - - // if linking fails, then log and cleanup - if (!gl.getProgramParameter(program, gl.LINK_STATUS)) - { - console.error('Pixi.js Error: Could not initialize shader.'); - console.error('gl.VALIDATE_STATUS', gl.getProgramParameter(program, gl.VALIDATE_STATUS)); - console.error('gl.getError()', gl.getError()); - - // if there is a program info log, log it - if (gl.getProgramInfoLog(program) !== '') - { - console.warn('Pixi.js Warning: gl.getProgramInfoLog()', gl.getProgramInfoLog(program)); - } - - gl.deleteProgram(program); - program = null; - } - - // clean up some shaders - gl.deleteShader(glVertShader); - gl.deleteShader(glFragShader); - - return program; -} - -/** - * @private - * @param gl {WebGLRenderingContext} The current WebGL context {WebGLProgram} - * @param type {Number} the type, can be either VERTEX_SHADER or FRAGMENT_SHADER - * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. - * @return {WebGLShader} the shader - */ -function compileShader(gl, type, src) -{ - const shader = gl.createShader(type); - - gl.shaderSource(shader, src); - gl.compileShader(shader); - - if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) - { - console.warn(src); - console.error(gl.getShaderInfoLog(shader)); - - return null; - } - - return shader; -} diff --git a/packages/core/src/renderers/systems/shader/shader/defaultValue.js b/packages/core/src/renderers/systems/shader/shader/defaultValue.js deleted file mode 100644 index 97290bd..0000000 --- a/packages/core/src/renderers/systems/shader/shader/defaultValue.js +++ /dev/null @@ -1,79 +0,0 @@ -/** - * @method defaultValue - * @memberof PIXI.glCore.shader - * @param type {String} Type of value - * @param size {Number} - */ -export default function defaultValue(type, size) -{ - switch (type) - { - case 'float': - return 0; - - case 'vec2': - return new Float32Array(2 * size); - - case 'vec3': - return new Float32Array(3 * size); - - case 'vec4': - return new Float32Array(4 * size); - - case 'int': - case 'sampler2D': - case 'sampler2DArray': - return 0; - - case 'ivec2': - return new Int32Array(2 * size); - - case 'ivec3': - return new Int32Array(3 * size); - - case 'ivec4': - return new Int32Array(4 * size); - - case 'bool': - return false; - - case 'bvec2': - - return booleanArray(2 * size); - - case 'bvec3': - return booleanArray(3 * size); - - case 'bvec4': - return booleanArray(4 * size); - - case 'mat2': - return new Float32Array([1, 0, - 0, 1]); - - case 'mat3': - return new Float32Array([1, 0, 0, - 0, 1, 0, - 0, 0, 1]); - - case 'mat4': - return new Float32Array([1, 0, 0, 0, - 0, 1, 0, 0, - 0, 0, 1, 0, - 0, 0, 0, 1]); - } - - return null; -} - -function booleanArray(size) -{ - const array = new Array(size); - - for (let i = 0; i < array.length; i++) - { - array[i] = false; - } - - return array; -} diff --git a/packages/core/src/renderers/systems/shader/shader/index.js b/packages/core/src/renderers/systems/shader/shader/index.js deleted file mode 100644 index ba7a317..0000000 --- a/packages/core/src/renderers/systems/shader/shader/index.js +++ /dev/null @@ -1,8 +0,0 @@ -/** - * @namespace PIXI.glCore.shader - */ -export { default as compileProgram } from './compileProgram'; -export { default as defaultValue } from './defaultValue'; -export { default as setPrecision } from './setPrecision'; -export { default as mapSize } from './mapSize'; -export { default as mapType } from './mapType'; diff --git a/packages/core/src/renderers/systems/shader/shader/mapSize.js b/packages/core/src/renderers/systems/shader/shader/mapSize.js deleted file mode 100644 index b681554..0000000 --- a/packages/core/src/renderers/systems/shader/shader/mapSize.js +++ /dev/null @@ -1,33 +0,0 @@ -const GLSL_TO_SIZE = { - float: 1, - vec2: 2, - vec3: 3, - vec4: 4, - - int: 1, - ivec2: 2, - ivec3: 3, - ivec4: 4, - - bool: 1, - bvec2: 2, - bvec3: 3, - bvec4: 4, - - mat2: 4, - mat3: 9, - mat4: 16, - - sampler2D: 1, -}; - -/** - * @method mapSize - * @memberof PIXI.glCore.shader - * @param type {String} - * @return {Number} - */ -export default function mapSize(type) -{ - return GLSL_TO_SIZE[type]; -} diff --git a/packages/core/src/renderers/systems/shader/shader/mapType.js b/packages/core/src/renderers/systems/shader/shader/mapType.js deleted file mode 100644 index d288b7c..0000000 --- a/packages/core/src/renderers/systems/shader/shader/mapType.js +++ /dev/null @@ -1,45 +0,0 @@ -let GL_TABLE = null; - -const GL_TO_GLSL_TYPES = { - FLOAT: 'float', - FLOAT_VEC2: 'vec2', - FLOAT_VEC3: 'vec3', - FLOAT_VEC4: 'vec4', - - INT: 'int', - INT_VEC2: 'ivec2', - INT_VEC3: 'ivec3', - INT_VEC4: 'ivec4', - - BOOL: 'bool', - BOOL_VEC2: 'bvec2', - BOOL_VEC3: 'bvec3', - BOOL_VEC4: 'bvec4', - - FLOAT_MAT2: 'mat2', - FLOAT_MAT3: 'mat3', - FLOAT_MAT4: 'mat4', - - SAMPLER_2D: 'sampler2D', - SAMPLER_CUBE: 'samplerCube', - SAMPLER_2D_ARRAY: 'sampler2DArray', -}; - -export default function mapType(gl, type) -{ - if (!GL_TABLE) - { - const typeNames = Object.keys(GL_TO_GLSL_TYPES); - - GL_TABLE = {}; - - for (let i = 0; i < typeNames.length; ++i) - { - const tn = typeNames[i]; - - GL_TABLE[gl[tn]] = GL_TO_GLSL_TYPES[tn]; - } - } - - return GL_TABLE[type]; -} diff --git a/packages/core/src/renderers/systems/shader/shader/setPrecision.js b/packages/core/src/renderers/systems/shader/shader/setPrecision.js deleted file mode 100644 index e2c1f34..0000000 --- a/packages/core/src/renderers/systems/shader/shader/setPrecision.js +++ /dev/null @@ -1,18 +0,0 @@ -/** - * Sets the float precision on the shader. If the precision is already present this function will do nothing - * @param {string} src the shader source - * @param {string} precision The float precision of the shader. Options are 'lowp', 'mediump' or 'highp'. - * - * @return {string} modified shader source - */ -export default function setPrecision(src, precision) -{ - src = src.trim(); - - if (src.substring(0, 9) !== 'precision' && src.substring(0, 1) !== '#') - { - return `precision ${precision} float;\n${src}`; - } - - return src; -} diff --git a/packages/core/src/renderers/systems/textures/GLTexture.js b/packages/core/src/renderers/systems/textures/GLTexture.js deleted file mode 100644 index c3fb8dc..0000000 --- a/packages/core/src/renderers/systems/textures/GLTexture.js +++ /dev/null @@ -1,33 +0,0 @@ -export default class GLTexture -{ - constructor(texture) - { - /** - * The WebGL texture - * - * @member {WebGLTexture} - */ - this.texture = texture; - - this.width = -1; - this.height = -1; - - /** - * Texture contents dirty flag - * @member {number} - */ - this.dirtyId = -1; - - /** - * Texture style dirty flag - * @type {number} - */ - this.dirtyStyleId = -1; - - /** - * Whether mip levels has to be generated - * @type {boolean} - */ - this.mipmap = false; - } -} diff --git a/packages/core/src/renderers/systems/textures/TextureGCSystem.js b/packages/core/src/renderers/systems/textures/TextureGCSystem.js deleted file mode 100644 index b089938..0000000 --- a/packages/core/src/renderers/systems/textures/TextureGCSystem.js +++ /dev/null @@ -1,110 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; -import { GC_MODES } from '@pixi/constants'; -import { settings } from '@pixi/settings'; - -/** - * TextureGarbageCollector. This class manages the GPU and ensures that it does not get clogged - * up with textures that are no longer being used. - * - * @class - * @memberof PIXI - */ -export default class TextureGCSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this.count = 0; - this.checkCount = 0; - this.maxIdle = settings.GC_MAX_IDLE; - this.checkCountMax = settings.GC_MAX_CHECK_COUNT; - this.mode = settings.GC_MODE; - } - - /** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ - postrender() - { - this.count++; - - if (this.mode === GC_MODES.MANUAL) - { - return; - } - - this.checkCount++; - - if (this.checkCount > this.checkCountMax) - { - this.checkCount = 0; - - this.run(); - } - } - - /** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ - run() - { - const tm = this.renderer.texture; - const managedTextures = tm.managedTextures; - let wasRemoved = false; - - for (let i = 0; i < managedTextures.length; i++) - { - const texture = managedTextures[i]; - - // only supports non generated textures at the moment! - if (!texture.frameBuffer && this.count - texture.touched > this.maxIdle) - { - tm.destroyTexture(texture, true); - managedTextures[i] = null; - wasRemoved = true; - } - } - - if (wasRemoved) - { - let j = 0; - - for (let i = 0; i < managedTextures.length; i++) - { - if (managedTextures[i] !== null) - { - managedTextures[j++] = managedTextures[i]; - } - } - - managedTextures.length = j; - } - } - - /** - * Removes all the textures within the specified displayObject and its children from the GPU - * - * @param {PIXI.DisplayObject} displayObject - the displayObject to remove the textures from. - */ - unload(displayObject) - { - const tm = this.renderer.textureSystem; - - // only destroy non generated textures - if (displayObject._texture && displayObject._texture._glRenderTargets) - { - tm.destroyTexture(displayObject._texture); - } - - for (let i = displayObject.children.length - 1; i >= 0; i--) - { - this.unload(displayObject.children[i]); - } - } -} diff --git a/packages/core/src/renderers/systems/textures/TextureSystem.js b/packages/core/src/renderers/systems/textures/TextureSystem.js deleted file mode 100644 index ccc5de9..0000000 --- a/packages/core/src/renderers/systems/textures/TextureSystem.js +++ /dev/null @@ -1,281 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; -import GLTexture from './GLTexture'; -import { removeItems } from '@pixi/utils'; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class TextureSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - // TODO set to max textures... - this.boundTextures = [ - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - ]; - - this.currentLocation = -1; - - this.managedTextures = []; - } - - /** - * Sets up the renderer context and necessary buffers. - * - * @private - */ - contextChange() - { - const gl = this.gl = this.renderer.gl; - - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - - // TODO move this.. to a nice make empty textures class.. - this.emptyTextures = {}; - - const emptyTexture2D = new GLTexture(gl.createTexture()); - - gl.bindTexture(gl.TEXTURE_2D, emptyTexture2D.texture); - gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array(4)); - - this.emptyTextures[gl.TEXTURE_2D] = emptyTexture2D; - this.emptyTextures[gl.TEXTURE_CUBE_MAP] = new GLTexture(gl.createTexture()); - - gl.bindTexture(gl.TEXTURE_CUBE_MAP, this.emptyTextures[gl.TEXTURE_CUBE_MAP].texture); - - let i; - - for (i = 0; i < 6; i++) - { - gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, null); - } - - gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_MAG_FILTER, gl.LINEAR); - gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_MIN_FILTER, gl.LINEAR); - - for (i = 0; i < this.boundTextures.length; i++) - { - this.bind(null, i); - } - } - - bind(texture, location) - { - const gl = this.gl; - - location = location || 0; - - if (this.currentLocation !== location) - { - this.currentLocation = location; - gl.activeTexture(gl.TEXTURE0 + location); - } - - if (texture) - { - texture = texture.baseTexture || texture; - - if (texture.valid) - { - texture.touched = this.renderer.textureGC.count; - - const glTexture = texture._glTextures[this.CONTEXT_UID] || this.initTexture(texture); - - gl.bindTexture(texture.target, glTexture.texture); - - if (glTexture.dirtyId !== texture.dirtyId) - { - this.updateTexture(texture); - } - - this.boundTextures[location] = texture; - } - } - else - { - gl.bindTexture(gl.TEXTURE_2D, this.emptyTextures[gl.TEXTURE_2D].texture); - this.boundTextures[location] = null; - } - } - - unbind(texture) - { - const gl = this.gl; - - for (let i = 0; i < this.boundTextures.length; i++) - { - if (this.boundTextures[i] === texture) - { - if (this.currentLocation !== i) - { - gl.activeTexture(gl.TEXTURE0 + i); - this.currentLocation = i; - } - - gl.bindTexture(gl.TEXTURE_2D, this.emptyTextures[texture.target].texture); - this.boundTextures[i] = null; - } - } - } - - initTexture(texture) - { - const glTexture = new GLTexture(this.gl.createTexture()); - - // guarentee an update.. - glTexture.dirtyId = -1; - - texture._glTextures[this.CONTEXT_UID] = glTexture; - - this.managedTextures.push(texture); - texture.on('dispose', this.destroyTexture, this); - - return glTexture; - } - - updateTexture(texture) - { - const glTexture = texture._glTextures[this.CONTEXT_UID]; - const renderer = this.renderer; - - if (texture.resource && texture.resource.upload(renderer, texture, glTexture)) - { - // texture is uploaded, dont do anything! - } - else - { - // default, renderTexture-like logic - const width = texture.realWidth; - const height = texture.realHeight; - const gl = renderer.gl; - - if (glTexture.width !== width - || glTexture.height !== height - || glTexture.dirtyId < 0) - { - glTexture.width = width; - glTexture.height = height; - - gl.texImage2D(texture.target, 0, - texture.format, - width, - height, - 0, - texture.format, - texture.type, - null); - } - } - - // lets only update what changes.. - if (texture.dirtyStyleId !== glTexture.dirtyStyleId) - { - this.updateTextureStyle(texture); - } - glTexture.dirtyId = texture.dirtyId; - } - - /** - * Deletes the texture from WebGL - * - * @param {PIXI.BaseTexture|PIXI.Texture} texture - the texture to destroy - * @param {boolean} [skipRemove=false] - Whether to skip removing the texture from the TextureManager. - */ - destroyTexture(texture, skipRemove) - { - const gl = this.gl; - - texture = texture.baseTexture || texture; - - if (texture._glTextures[this.renderer.CONTEXT_UID]) - { - this.unbind(texture); - - gl.deleteTexture(texture._glTextures[this.renderer.CONTEXT_UID].texture); - texture.off('dispose', this.destroyTexture, this); - - delete texture._glTextures[this.renderer.CONTEXT_UID]; - - if (!skipRemove) - { - const i = this.managedTextures.indexOf(texture); - - if (i !== -1) - { - removeItems(this.managedTextures, i, 1); - } - } - } - } - - updateTextureStyle(texture) - { - const glTexture = texture._glTextures[this.CONTEXT_UID]; - - glTexture.mipmap = texture.mipmap && texture.isPowerOfTwo; - if (!glTexture) - { - return; - } - - if (texture.resource && texture.resource.style(this.renderer, texture, glTexture)) - { - // style is set, dont do anything! - } - else - { - this.setStyle(texture, glTexture); - } - - glTexture.dirtyStyleId = texture.dirtyStyleId; - } - - setStyle(texture, glTexture) - { - const gl = this.gl; - - if (glTexture.mipmap) - { - gl.generateMipmap(texture.target); - } - - gl.texParameteri(texture.target, gl.TEXTURE_WRAP_S, texture.wrapMode); - gl.texParameteri(texture.target, gl.TEXTURE_WRAP_T, texture.wrapMode); - - if (glTexture.mipmap) - { - /* eslint-disable max-len */ - gl.texParameteri(texture.target, gl.TEXTURE_MIN_FILTER, texture.scaleMode ? gl.LINEAR_MIPMAP_LINEAR : gl.NEAREST_MIPMAP_NEAREST); - /* eslint-disable max-len */ - } - else - { - gl.texParameteri(texture.target, gl.TEXTURE_MIN_FILTER, texture.scaleMode ? gl.LINEAR : gl.NEAREST); - } - - gl.texParameteri(texture.target, gl.TEXTURE_MAG_FILTER, texture.scaleMode ? gl.LINEAR : gl.NEAREST); - } -} diff --git a/packages/core/src/renderers/utils/ObjectRenderer.js b/packages/core/src/renderers/utils/ObjectRenderer.js deleted file mode 100644 index f67a273..0000000 --- a/packages/core/src/renderers/utils/ObjectRenderer.js +++ /dev/null @@ -1,48 +0,0 @@ -import WebGLSystem from '../systems/WebGLSystem'; - -/** - * Base for a common object renderer that can be used as a system renderer plugin. - * - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class ObjectRenderer extends WebGLSystem -{ - /** - * Starts the renderer and sets the shader - * - */ - start() - { - // set the shader.. - } - - /** - * Stops the renderer - * - */ - stop() - { - this.flush(); - } - - /** - * Stub method for rendering content and emptying the current batch. - * - */ - flush() - { - // flush! - } - - /** - * Renders an object - * - * @param {PIXI.DisplayObject} object - The object to render. - */ - render(object) // eslint-disable-line no-unused-vars - { - // render the object - } -} diff --git a/packages/core/src/renderers/utils/Quad.js b/packages/core/src/renderers/utils/Quad.js deleted file mode 100644 index 5c1517b..0000000 --- a/packages/core/src/renderers/utils/Quad.js +++ /dev/null @@ -1,26 +0,0 @@ -import Geometry from '../../geometry/Geometry'; - -/** - * Helper class to create a quad - * - * @class - * @memberof PIXI - */ -export default class Quad extends Geometry -{ - /** - * just a quad - */ - constructor() - { - super(); - - this.addAttribute('aVertexPosition', [ - 0, 0, - 1, 0, - 1, 1, - 0, 1, - ]) - .addIndex([0, 1, 3, 2]); - } -} diff --git a/packages/core/src/renderers/utils/QuadUv.js b/packages/core/src/renderers/utils/QuadUv.js deleted file mode 100644 index 97bda89..0000000 --- a/packages/core/src/renderers/utils/QuadUv.js +++ /dev/null @@ -1,103 +0,0 @@ -import Geometry from '../../geometry/Geometry'; -import Buffer from '../../geometry/Buffer'; - -/** - * Helper class to create a quad with uvs like in v4 - * - * @class - * @memberof PIXI - */ -export default class QuadUv extends Geometry -{ - constructor() - { - super(); - - /** - * An array of vertices - * - * @member {Float32Array} - */ - this.vertices = new Float32Array([ - -1, -1, - 1, -1, - 1, 1, - -1, 1, - ]); - - /** - * The Uvs of the quad - * - * @member {Float32Array} - */ - this.uvs = new Float32Array([ - 0, 0, - 1, 0, - 1, 1, - 0, 1, - ]); - - this.vertexBuffer = new Buffer(this.vertices); - this.uvBuffer = new Buffer(this.uvs); - - this.addAttribute('aVertexPosition', this.vertexBuffer) - .addAttribute('aTextureCoord', this.uvBuffer) - .addIndex([0, 1, 2, 0, 2, 3]); - } - - /** - * Maps two Rectangle to the quad. - * - * @param {PIXI.Rectangle} targetTextureFrame - the first rectangle - * @param {PIXI.Rectangle} destinationFrame - the second rectangle - * @return {PIXI.Quad} Returns itself. - */ - map(targetTextureFrame, destinationFrame) - { - let x = 0; // destinationFrame.x / targetTextureFrame.width; - let y = 0; // destinationFrame.y / targetTextureFrame.height; - - this.uvs[0] = x; - this.uvs[1] = y; - - this.uvs[2] = x + (destinationFrame.width / targetTextureFrame.width); - this.uvs[3] = y; - - this.uvs[4] = x + (destinationFrame.width / targetTextureFrame.width); - this.uvs[5] = y + (destinationFrame.height / targetTextureFrame.height); - - this.uvs[6] = x; - this.uvs[7] = y + (destinationFrame.height / targetTextureFrame.height); - - x = destinationFrame.x; - y = destinationFrame.y; - - this.vertices[0] = x; - this.vertices[1] = y; - - this.vertices[2] = x + destinationFrame.width; - this.vertices[3] = y; - - this.vertices[4] = x + destinationFrame.width; - this.vertices[5] = y + destinationFrame.height; - - this.vertices[6] = x; - this.vertices[7] = y + destinationFrame.height; - - this.invalidate(); - - return this; - } - - /** - * legacy upload method, just marks buffers dirty - * @returns {PIXI.QuadUv} Returns itself. - */ - invalidate() - { - this.vertexBuffer._updateID++; - this.uvBuffer._updateID++; - - return this; - } -} diff --git a/packages/core/src/renderers/utils/checkMaxIfStatmentsInShader.js b/packages/core/src/renderers/utils/checkMaxIfStatmentsInShader.js deleted file mode 100644 index 8047c96..0000000 --- a/packages/core/src/renderers/utils/checkMaxIfStatmentsInShader.js +++ /dev/null @@ -1,58 +0,0 @@ -const fragTemplate = [ - 'precision mediump float;', - 'void main(void){', - 'float test = 0.1;', - '%forloop%', - 'gl_FragColor = vec4(0.0);', - '}', -].join('\n'); - -export default function checkMaxIfStatmentsInShader(maxIfs, gl) -{ - if (maxIfs === 0) - { - throw new Error('Invalid value of `0` passed to `checkMaxIfStatementsInShader`'); - } - - const shader = gl.createShader(gl.FRAGMENT_SHADER); - - while (true) // eslint-disable-line no-constant-condition - { - const fragmentSrc = fragTemplate.replace(/%forloop%/gi, generateIfTestSrc(maxIfs)); - - gl.shaderSource(shader, fragmentSrc); - gl.compileShader(shader); - - if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) - { - maxIfs = (maxIfs / 2) | 0; - } - else - { - // valid! - break; - } - } - - return maxIfs; -} - -function generateIfTestSrc(maxIfs) -{ - let src = ''; - - for (let i = 0; i < maxIfs; ++i) - { - if (i > 0) - { - src += '\nelse '; - } - - if (i < maxIfs - 1) - { - src += `if(test == ${i}.0){}`; - } - } - - return src; -} diff --git a/packages/core/src/renderers/utils/mapWebGLBlendModesToPixi.js b/packages/core/src/renderers/utils/mapWebGLBlendModesToPixi.js deleted file mode 100644 index 10ffe4d..0000000 --- a/packages/core/src/renderers/utils/mapWebGLBlendModesToPixi.js +++ /dev/null @@ -1,47 +0,0 @@ -import { BLEND_MODES } from '@pixi/constants'; - -/** - * Maps gl blend combinations to WebGL. - * - * @memberof PIXI - * @function mapWebGLBlendModesToPixi - * @private - * @param {WebGLRenderingContext} gl - The rendering context. - * @param {string[]} [array=[]] - The array to output into. - * @return {string[]} Mapped modes. - */ -export default function mapWebGLBlendModesToPixi(gl, array = []) -{ - // TODO - premultiply alpha would be different. - // add a boolean for that! - array[BLEND_MODES.NORMAL] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.ADD] = [gl.ONE, gl.DST_ALPHA]; - array[BLEND_MODES.MULTIPLY] = [gl.DST_COLOR, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.SCREEN] = [gl.ONE, gl.ONE_MINUS_SRC_COLOR]; - array[BLEND_MODES.OVERLAY] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.DARKEN] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.LIGHTEN] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.COLOR_DODGE] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.COLOR_BURN] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.HARD_LIGHT] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.SOFT_LIGHT] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.DIFFERENCE] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.EXCLUSION] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.HUE] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.SATURATION] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.COLOR] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.LUMINOSITY] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.NONE] = [0, 0]; - - // not-premultiplied blend modes - array[BLEND_MODES.NORMAL_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.ADD_NPM] = [gl.SRC_ALPHA, gl.DST_ALPHA, gl.ONE, gl.DST_ALPHA]; - array[BLEND_MODES.SCREEN_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_COLOR, gl.ONE, gl.ONE_MINUS_SRC_COLOR]; - - // not-premultiplied blend modes - array[BLEND_MODES.NORMAL_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.ADD_NPM] = [gl.SRC_ALPHA, gl.DST_ALPHA, gl.ONE, gl.DST_ALPHA]; - array[BLEND_MODES.SCREEN_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_COLOR, gl.ONE, gl.ONE_MINUS_SRC_COLOR]; - - return array; -} diff --git a/packages/core/src/shader/GLProgram.js b/packages/core/src/shader/GLProgram.js new file mode 100644 index 0000000..92980ca --- /dev/null +++ b/packages/core/src/shader/GLProgram.js @@ -0,0 +1,43 @@ +/** + * Helper class to create a webGL Program + * + * @class + * @private + */ +export default class GLProgram +{ + constructor(program, uniformData) + { + /** + * The shader program + * + * @member {WebGLProgram} + */ + this.program = program; + + /** + * holds the uniform data which contains uniform locations + * and current uniform values used for caching and preventing undeed GPU commands + * @type {Object} + */ + this.uniformData = uniformData; + + /** + * uniformGroups holds the various upload functions for the shader. Each uniform group + * and program have a unique upload function generated. + * @type {Object} + */ + this.uniformGroups = {}; + } + + /** + * Destroys this program + * TODO + */ + destroy() + { + this.uniformData = null; + this.uniformGroups = null; + this.program = null; + } +} diff --git a/packages/core/src/shader/Program.js b/packages/core/src/shader/Program.js index 4f9971e..4b4007a 100644 --- a/packages/core/src/shader/Program.js +++ b/packages/core/src/shader/Program.js @@ -1,6 +1,11 @@ -import * as shaderUtils from '../renderers/systems/shader/shader'; +// import * as from '../systems/shader/shader'; +import { setPrecision, + defaultValue, + compileProgram, + mapSize, + mapType, + getTestContext } from './utils'; import { ProgramCache } from '@pixi/utils'; -import getTestContext from './getTestContext'; import defaultFragment from './defaultProgram.frag'; import defaultVertex from './defaultProgram.vert'; import { settings } from '@pixi/settings'; @@ -34,8 +39,8 @@ */ this.fragmentSrc = fragmentSrc || Program.defaultFragmentSrc; - this.vertexSrc = shaderUtils.setPrecision(this.vertexSrc, settings.PRECISION_VERTEX); - this.fragmentSrc = shaderUtils.setPrecision(this.fragmentSrc, settings.PRECISION_FRAGMENT); + this.vertexSrc = setPrecision(this.vertexSrc, settings.PRECISION_VERTEX); + this.fragmentSrc = setPrecision(this.fragmentSrc, settings.PRECISION_FRAGMENT); // currently this does not extract structs only default types this.extractData(this.vertexSrc, this.fragmentSrc); @@ -62,7 +67,7 @@ if (gl) { - const program = shaderUtils.compileProgram(gl, vertexSrc, fragmentSrc); + const program = compileProgram(gl, vertexSrc, fragmentSrc); this.attributeData = this.getAttributeData(program, gl); this.uniformData = this.getUniformData(program, gl); @@ -95,13 +100,13 @@ for (let i = 0; i < totalAttributes; i++) { const attribData = gl.getActiveAttrib(program, i); - const type = shaderUtils.mapType(gl, attribData.type); + const type = mapType(gl, attribData.type); /*eslint-disable */ const data = { type: type, name: attribData.name, - size: shaderUtils.mapSize(type), + size: mapSize(type), location: 0, }; /* eslint-enable */ @@ -145,14 +150,14 @@ const name = uniformData.name.replace(/\[.*?\]/, ''); const isArray = uniformData.name.match(/\[.*?\]/, ''); - const type = shaderUtils.mapType(gl, uniformData.type); + const type = mapType(gl, uniformData.type); /*eslint-disable */ uniforms[name] = { type: type, size: uniformData.size, isArray:isArray, - value: shaderUtils.defaultValue(type, uniformData.size), + value: defaultValue(type, uniformData.size), }; /* eslint-enable */ } diff --git a/packages/core/src/shader/ShaderSystem.js b/packages/core/src/shader/ShaderSystem.js new file mode 100644 index 0000000..40b8af8 --- /dev/null +++ b/packages/core/src/shader/ShaderSystem.js @@ -0,0 +1,171 @@ +import System from '../System'; +import GLProgram from './GLProgram'; +import { generateUniformsSync, + defaultValue, + compileProgram } from './utils'; + +let UID = 0; + +/** + * Helper class to create a webGL Texture + * + * @class + * @memberof PIXI.systems + * @extends PIXI.System + */ +export default class ShaderSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - A reference to the current renderer + */ + constructor(renderer) + { + super(renderer); + + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = null; + + this.shader = null; + this.program = null; + + this.id = UID++; + } + + contextChange(gl) + { + this.gl = gl; + } + + /** + * Changes the current shader to the one given in parameter + * + * @param {PIXI.Shader} shader - the new shader + * @param {boolean} dontSync - false if the shader should automatically sync its uniforms. + * @returns {PIXI.glCore.glProgram} the glProgram that belongs to the shader. + */ + bind(shader, dontSync) + { + shader.uniforms.globals = this.renderer.globalUniforms; + + const program = shader.program; + const glProgram = program.glPrograms[this.renderer.CONTEXT_UID] || this.generateShader(shader); + + this.shader = shader; + + // TODO - some current pixi plugins bypass this.. so it not safe to use yet.. + if (this.program !== program) + { + this.program = program; + this.gl.useProgram(glProgram.program); + } + + if (!dontSync) + { + this.syncUniformGroup(shader.uniformGroup); + } + + return glProgram; + } + + /** + * Uploads the uniforms values to the currently bound shader. + * + * @param {object} uniforms - the uniforms valiues that be applied to the current shader + */ + setUniforms(uniforms) + { + const shader = this.shader.program; + const glProgram = shader.glPrograms[this.renderer.CONTEXT_UID]; + + shader.syncUniforms(glProgram.uniformData, uniforms, this.renderer); + } + + syncUniformGroup(group) + { + const glProgram = this.getglProgram(); + + if (!group.static || group.dirtyId !== glProgram.uniformGroups[group.id]) + { + glProgram.uniformGroups[group.id] = group.dirtyId; + const syncFunc = group.syncUniforms[this.shader.program.id] || this.createSyncGroups(group); + + syncFunc(glProgram.uniformData, group.uniforms, this.renderer); + } + } + + createSyncGroups(group) + { + group.syncUniforms[this.shader.program.id] = generateUniformsSync(group, this.shader.program.uniformData); + + return group.syncUniforms[this.shader.program.id]; + } + + /** + * Returns the underlying GLShade rof the currently bound shader. + * This can be handy for when you to have a little more control over the setting of your uniforms. + * + * @return {PIXI.glCore.Shader} the glProgram for the currently bound Shader for this context + */ + getglProgram() + { + if (this.shader) + { + return this.shader.program.glPrograms[this.renderer.CONTEXT_UID]; + } + + return null; + } + + /** + * Generates a glProgram verion of the Shader provided. + * + * @private + * @param {PIXI.Shader} shader the shader that the glProgram will be based on. + * @return {PIXI.glCore.glProgram} A shiney new glProgram + */ + generateShader(shader) + { + const gl = this.gl; + + const program = shader.program; + + const attribMap = {}; + + for (const i in program.attributeData) + { + attribMap[i] = program.attributeData[i].location; + } + + const shaderProgram = compileProgram(gl, program.vertexSrc, program.fragmentSrc, attribMap); + const uniformData = {}; + + for (const i in program.uniformData) + { + const data = program.uniformData[i]; + + uniformData[i] = { + location: gl.getUniformLocation(shaderProgram, i), + value: defaultValue(data.type, data.size), + }; + } + + const glProgram = new GLProgram(shaderProgram, uniformData); + + program.glPrograms[this.renderer.CONTEXT_UID] = glProgram; + + return glProgram; + } + + /** + * Destroys this System and removes all its textures + */ + destroy() + { + // TODO implement destroy method for ShaderSystem + this.destroyed = true; + } +} diff --git a/packages/core/src/shader/defaultValue.js b/packages/core/src/shader/defaultValue.js deleted file mode 100644 index 847a069..0000000 --- a/packages/core/src/shader/defaultValue.js +++ /dev/null @@ -1,78 +0,0 @@ -function booleanArray(size) -{ - const array = new Array(size); - - for (let i = 0; i < array.length; i++) - { - array[i] = false; - } - - return array; -} - -/** - * @class - * @memberof PIXI.glCore.shader - * @param type {String} Type of value - * @param size {Number} - */ -export default function defaultValue(type, size) -{ - switch (type) - { - case 'float': - return 0; - - case 'vec2': - return new Float32Array(2 * size); - - case 'vec3': - return new Float32Array(3 * size); - - case 'vec4': - return new Float32Array(4 * size); - - case 'int': - case 'sampler2D': - return 0; - - case 'ivec2': - return new Int32Array(2 * size); - - case 'ivec3': - return new Int32Array(3 * size); - - case 'ivec4': - return new Int32Array(4 * size); - - case 'bool': - return false; - - case 'bvec2': - - return booleanArray(2 * size); - - case 'bvec3': - return booleanArray(3 * size); - - case 'bvec4': - return booleanArray(4 * size); - - case 'mat2': - return new Float32Array([1, 0, - 0, 1]); - - case 'mat3': - return new Float32Array([1, 0, 0, - 0, 1, 0, - 0, 0, 1]); - - case 'mat4': - return new Float32Array([1, 0, 0, 0, - 0, 1, 0, 0, - 0, 0, 1, 0, - 0, 0, 0, 1]); - } - - return 0; -} diff --git a/packages/core/src/AbstractRenderer.js b/packages/core/src/AbstractRenderer.js new file mode 100644 index 0000000..64b2eb1 --- /dev/null +++ b/packages/core/src/AbstractRenderer.js @@ -0,0 +1,353 @@ +import { hex2string, hex2rgb } from '@pixi/utils'; +import { Matrix, Rectangle } from '@pixi/math'; +import { RENDERER_TYPE } from '@pixi/constants'; +import { settings } from '@pixi/settings'; +import { Container } from '@pixi/display'; +import RenderTexture from './renderTexture/RenderTexture'; +import EventEmitter from 'eventemitter3'; + +const tempMatrix = new Matrix(); + +/** + * 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 EventEmitter + * @memberof PIXI + */ +export default class AbstractRenderer extends EventEmitter +{ + // eslint-disable-next-line valid-jsdoc + /** + * @param {string} system - The name of the system this renderer is for. + * @param {object} [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.transparent=false] - If the render view is transparent, default false + * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false + * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. The + * resolution of the renderer retina would be 2. + * @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 {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when rendering, + * stopping pixel interpolation. + */ + constructor(system, options, arg2, arg3) + { + super(); + + // Support for constructor(system, screenWidth, screenHeight, options) + if (typeof options === 'number') + { + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + }, arg3); + } + + // 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 = RENDERER_TYPE.UNKNOWN; + + /** + * Measurements of the screen. (0, 0, screenWidth, screenHeight) + * + * Its safe to use as filterArea or hitArea for 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 1 + */ + this.resolution = options.resolution || settings.RESOLUTION; + + /** + * Whether the render view is transparent + * + * @member {boolean} + */ + this.transparent = options.transparent; + + /** + * Whether css dimensions of canvas view should be resized to screen dimensions automatically + * + * @member {boolean} + */ + this.autoResize = options.autoResize || false; + + /** + * Tracks the blend modes useful for this renderer. + * + * @member {object} + */ + this.blendModes = null; + + /** + * 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; + + /** + * If true PixiJS will Math.floor() x/y values when rendering, stopping pixel interpolation. + * Handy for crisp pixel art and speed on legacy devices. + * + * @member {boolean} + */ + this.roundPixels = options.roundPixels; + + /** + * The background color as a number. + * + * @member {number} + * @private + */ + this._backgroundColor = 0x000000; + + /** + * The background color as an [R, G, B] array. + * + * @member {number[]} + * @private + */ + this._backgroundColorRgba = [0, 0, 0, 0]; + + /** + * The background color as a string. + * + * @member {string} + * @private + */ + this._backgroundColorString = '#000000'; + + this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter + + /** + * This temporary display object used as the parent of the currently being rendered item + * + * @member {PIXI.DisplayObject} + * @private + */ + this._tempDisplayObjectParent = new Container(); + + /** + * The last root object that the renderer tried to render. + * + * @member {PIXI.DisplayObject} + * @private + */ + this._lastObjectRendered = this._tempDisplayObjectParent; + + /** + * Collection of plugins. + * @readonly + * @member {object} + */ + this.plugins = {}; + } + + /** + * Initialize the plugins. + * + * @protected + * @param {object} staticMap - The dictionary of staticly saved plugins. + */ + initPlugins(staticMap) + { + 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() + { + return this.view.width; + } + + /** + * Same as view.height, actual number of pixels in the canvas by vertical + * + * @member {number} + * @readonly + * @default 600 + */ + get height() + { + return this.view.height; + } + + /** + * Resizes the screen and canvas to the specified width and height + * Canvas dimensions are multiplied by resolution + * + * @param {number} screenWidth - the new width of the screen + * @param {number} screenHeight - the new height of the screen + */ + resize(screenWidth, screenHeight) + { + this.screen.width = screenWidth; + this.screen.height = screenHeight; + + this.view.width = screenWidth * this.resolution; + this.view.height = screenHeight * this.resolution; + + if (this.autoResize) + { + this.view.style.width = `${screenWidth}px`; + this.view.style.height = `${screenHeight}px`; + } + } + + /** + * 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 {PIXI.DisplayObject} displayObject - The displayObject the object will be generated from + * @param {number} scaleMode - Should be one of the scaleMode consts + * @param {number} resolution - The resolution / device pixel ratio of the texture being generated + * @param {PIXI.Rectangle} [region] - The region of the displayObject, that shall be rendered, + * if no region is specified, defaults to the local bounds of the displayObject. + * @return {PIXI.Texture} a texture of the graphics object + */ + generateTexture(displayObject, scaleMode, resolution, region) + { + region = region || displayObject.getLocalBounds(); + + // 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(region.width | 0, region.height | 0, scaleMode, resolution); + + tempMatrix.tx = -region.x; + tempMatrix.ty = -region.y; + + this.render(displayObject, renderTexture, false, tempMatrix, true); + + return renderTexture; + } + + /** + * Removes everything from the renderer and optionally removes the Canvas DOM element. + * + * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. + */ + destroy(removeView) + { + 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); + } + + this.plugins = null; + + this.type = RENDERER_TYPE.UNKNOWN; + + this.view = null; + + this.screen = null; + + this.resolution = 0; + + this.transparent = false; + + this.autoResize = false; + + this.blendModes = null; + + this.options = null; + + this.preserveDrawingBuffer = false; + this.clearBeforeRender = false; + + this.roundPixels = false; + + this._backgroundColor = 0; + this._backgroundColorRgba = null; + this._backgroundColorString = null; + + this._tempDisplayObjectParent = null; + this._lastObjectRendered = null; + } + + /** + * The background color to fill if not transparent + * + * @member {number} + */ + get backgroundColor() + { + return this._backgroundColor; + } + + set backgroundColor(value) // eslint-disable-line require-jsdoc + { + this._backgroundColor = value; + this._backgroundColorString = hex2string(value); + hex2rgb(value, this._backgroundColorRgba); + } +} diff --git a/packages/core/src/Renderer.js b/packages/core/src/Renderer.js new file mode 100644 index 0000000..2227e78 --- /dev/null +++ b/packages/core/src/Renderer.js @@ -0,0 +1,338 @@ +import AbstractRenderer from './AbstractRenderer'; +import { sayHello } from '@pixi/utils'; +import MaskSystem from './mask/MaskSystem'; +import StencilSystem from './mask/StencilSystem'; +import FilterSystem from './filters/FilterSystem'; +import FramebufferSystem from './framebuffer/FramebufferSystem'; +import RenderTextureSystem from './renderTexture/RenderTextureSystem'; +import TextureSystem from './textures/TextureSystem'; +import ProjectionSystem from './projection/ProjectionSystem'; +import StateSystem from './state/StateSystem'; +import GeometrySystem from './geometry/GeometrySystem'; +import ShaderSystem from './shader/ShaderSystem'; +import ContextSystem from './context/ContextSystem'; +import BatchSystem from './batch/BatchSystem'; +import TextureGCSystem from './textures/TextureGCSystem'; +import { RENDERER_TYPE } from '@pixi/constants'; +import UniformGroup from './shader/UniformGroup'; +import { Matrix } from '@pixi/math'; +import Runner from 'mini-runner'; + +/** + * 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 Render works by automatically managing webGLBatchs. + * 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 :) + * + * @class + * @memberof PIXI + * @extends PIXI.AbstractRenderer + */ +export default class Renderer extends AbstractRenderer +{ + // eslint-disable-next-line valid-jsdoc + /** + * + * @param {object} [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.transparent=false] - If the render view is transparent, default false + * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false + * @param {boolean} [options.antialias=false] - sets antialias. If not available natively then FXAA + * antialiasing is used + * @param {boolean} [options.forceFXAA=false] - forces FXAA antialiasing to be used over native. + * FXAA is faster, but may not always look as great + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. + * The resolution of the renderer retina would be 2. + * @param {boolean} [options.clearBeforeRender=true] - This sets if the renderer will clear + * the canvas or not before the new render pass. If you wish to set this to false, you *must* set + * preserveDrawingBuffer to `true`. + * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, + * enable this if you need to call toDataUrl on the webgl context. + * @param {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when + * rendering, stopping pixel interpolation. + * @param {number} [options.backgroundColor=0x000000] - The background color of the rendered area + * (shown if not transparent). + * @param {boolean} [options.legacy=false] - If true PixiJS will aim to ensure compatibility + * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. + * @param {string} [options.powerPreference] - Parameter passed to webgl context, set to "high-performance" + * for devices with dual graphics card + */ + constructor(options, arg2, arg3) + { + super('WebGL', options, arg2, arg3); + + /** + * The type of this renderer as a standardised const + * + * @member {number} + * @see PIXI.RENDERER_TYPE + */ + this.type = RENDERER_TYPE.WEBGL; + + // this will be set by the contextSystem (this.context) + this.gl = null; + this.CONTEXT_UID = 0; + this.legacy = !!options.legacy; + + // TODO legacy! + + // runners! + this.runners = { + destroy: new Runner('destroy'), + contextChange: new Runner('contextChange', 1), + reset: new Runner('reset'), + update: new Runner('update'), + postrender: new Runner('postrender'), + prerender: new Runner('prerender'), + resize: new Runner('resize', 2), + }; + + 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(GeometrySystem, 'geometry') + .addSystem(FramebufferSystem, 'framebuffer') + .addSystem(StencilSystem, 'stencil') + .addSystem(ProjectionSystem, 'projection') + .addSystem(TextureGCSystem, 'textureGC') + .addSystem(FilterSystem, 'filter') + .addSystem(RenderTextureSystem, 'renderTexture') + .addSystem(BatchSystem, 'batch'); + + this.initPlugins(Renderer.__plugins); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ + if (options.context) + { + this.context.initFromContext(options.context); + } + else + { + this.context.initFromOptions({ + alpha: this.transparent, + antialias: options.antialias, + premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', + stencil: true, + 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); + } + + /** + * Add a new system to the renderer. + * @param {class} ClassRef - Class reference + * @param {string} [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. + * @return {PIXI.Renderer} Return instance of renderer + */ + addSystem(ClassRef, name) + { + if (!name) + { + name = ClassRef.name; + } + + // TODO - read name from class.name.. + + /* + if(name.includes('System')) + { + name = name.replace('System', ''); + name = name.charAt(0).toLowerCase() + name.slice(1); + } + */ + + const system = new ClassRef(this); + + if (this[name]) + { + throw new Error(`Whoops! ${name} is already a manger`); + } + + this[name] = system; + + for (const i in this.runners) + { + this.runners[i].add(system); + } + + return this; + + /** + * 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. + */ + } + + /** + * Renders the object to its webGL view + * + * @param {PIXI.DisplayObject} displayObject - the object to be rendered + * @param {PIXI.RenderTexture} renderTexture - The render texture to render to. + * @param {boolean} [clear] - Should the canvas be cleared before the new render + * @param {PIXI.Transform} [transform] - A transform to apply to the render texture before rendering. + * @param {boolean} [skipUpdateTransform] - Should we skip the update transform pass? + */ + render(displayObject, renderTexture, clear, transform, skipUpdateTransform) + { + // can be handy to know! + this.renderingToScreen = !renderTexture; + + this.runners.prerender.run(); + this.emit('prerender'); + + // 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.parent; + + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = 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.run(); + + this.emit('postrender'); + } + + /** + * Resizes the webGL view to the specified width and height. + * + * @param {number} screenWidth - the new width of the screen + * @param {number} screenHeight - the new height of the screen + */ + resize(screenWidth, screenHeight) + { + AbstractRenderer.prototype.resize.call(this, screenWidth, screenHeight); + + this.runners.resize.run(screenWidth, screenHeight); + } + + /** + * Resets the WebGL state so you can render things however you fancy! + * + * @return {PIXI.Renderer} Returns itself. + */ + reset() + { + this.runners.reset.run(); + + return this; + } + + clear() + { + this.framebuffer.bind(); + this.framebuffer.clear(); + } + + /** + * Removes everything from the renderer (event listeners, spritebatch, etc...) + * + * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. + * See: https://github.com/pixijs/pixi.js/issues/2233 + */ + destroy(removeView) + { + this.runners.destroy.run(); + + // call base destroy + super.destroy(removeView); + + // TODO nullify all the managers.. + this.gl = null; + } + + /** + * 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 + * builds and excluding plugins. + * @name PIXI.Renderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. + */ + + /** + * Adds a plugin to the renderer. + * + * @method + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + static registerPlugin(pluginName, ctor) + { + Renderer.__plugins = Renderer.__plugins || {}; + Renderer.__plugins[pluginName] = ctor; + } +} diff --git a/packages/core/src/System.js b/packages/core/src/System.js new file mode 100644 index 0000000..05a10c8 --- /dev/null +++ b/packages/core/src/System.js @@ -0,0 +1,42 @@ +/** + * System is a base class used for extending systems used by the {@link PIXI.Renderer} + * @see PIXI.Renderer#addSystem + * @class + * @memberof PIXI + */ +export default class System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + /** + * The renderer this manager works for. + * + * @member {PIXI.Renderer} + */ + this.renderer = renderer; + + this.renderer.runners.contextChange.add(this); + } + + /** + * Generic method called when there is a WebGL context change. + * + */ + contextChange() + { + // do some codes init! + } + + /** + * Generic destroy methods to be overridden by the subclass + * + */ + destroy() + { + this.renderer.runners.contextChange.remove(this); + this.renderer = null; + } +} diff --git a/packages/core/src/batch/BatchSystem.js b/packages/core/src/batch/BatchSystem.js new file mode 100644 index 0000000..b04c249 --- /dev/null +++ b/packages/core/src/batch/BatchSystem.js @@ -0,0 +1,68 @@ +import System from '../System'; +import ObjectRenderer from './ObjectRenderer'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class BatchSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + super(renderer); + + /** + * An empty renderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.emptyRenderer = new ObjectRenderer(renderer); + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.currentRenderer = this.emptyRenderer; + } + + /** + * Changes the current renderer to the one given in parameter + * + * @param {PIXI.ObjectRenderer} objectRenderer - The object renderer to use. + */ + setObjectRenderer(objectRenderer) + { + if (this.currentRenderer === objectRenderer) + { + return; + } + + this.currentRenderer.stop(); + this.currentRenderer = objectRenderer; + + this.renderer.state.setState(objectRenderer.state); + + this.currentRenderer.start(); + } + + /** + * This should be called if you wish to do some custom rendering + * It will basically render anything that may be batched up such as sprites + * + */ + flush() + { + this.setObjectRenderer(this.emptyRenderer); + } + + reset() + { + this.setObjectRenderer(this.emptyRenderer); + } +} diff --git a/packages/core/src/batch/ObjectRenderer.js b/packages/core/src/batch/ObjectRenderer.js new file mode 100644 index 0000000..0c851a9 --- /dev/null +++ b/packages/core/src/batch/ObjectRenderer.js @@ -0,0 +1,48 @@ +import System from '../System'; + +/** + * Base for a common object renderer that can be used as a system renderer plugin. + * + * @class + * @extends PIXI.System + * @memberof PIXI + */ +export default class ObjectRenderer extends System +{ + /** + * Starts the renderer and sets the shader + * + */ + start() + { + // set the shader.. + } + + /** + * Stops the renderer + * + */ + stop() + { + this.flush(); + } + + /** + * Stub method for rendering content and emptying the current batch. + * + */ + flush() + { + // flush! + } + + /** + * Renders an object + * + * @param {PIXI.DisplayObject} object - The object to render. + */ + render(object) // eslint-disable-line no-unused-vars + { + // render the object + } +} diff --git a/packages/core/src/context/ContextSystem.js b/packages/core/src/context/ContextSystem.js new file mode 100644 index 0000000..301aab4 --- /dev/null +++ b/packages/core/src/context/ContextSystem.js @@ -0,0 +1,186 @@ +import System from '../System'; +import { settings } from '@pixi/settings'; + +let CONTEXT_UID = 0; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class ContextSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.webGLVersion = 1; + + this.handleContextLost = this.handleContextLost.bind(this); + this.handleContextRestored = this.handleContextRestored.bind(this); + + this.extensions = {}; + + renderer.view.addEventListener('webglcontextlost', this.handleContextLost, false); + renderer.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + } + + get isLost() + { + return (!this.gl || this.gl.isContextLost()); + } + + contextChange(gl) + { + this.gl = gl; + + // restore a context if it was previously lost + if (gl.isContextLost() && gl.getExtension('WEBGL_lose_context')) + { + gl.getExtension('WEBGL_lose_context').restoreContext(); + } + } + + initFromContext(gl) + { + this.gl = gl; + this.validateContext(gl); + this.renderer.gl = gl; + this.renderer.CONTEXT_UID = CONTEXT_UID++; + this.renderer.runners.contextChange.run(gl); + } + + initFromOptions(options) + { + const gl = this.createContext(this.renderer.view, options); + + this.initFromContext(gl); + } + + /** + * Helper class to create a webGL Context + * + * @class + * @memberof PIXI.glCore + * @param canvas {HTMLCanvasElement} the canvas element that we will get the context from + * @param options {Object} An options object that gets passed in to the canvas element containing the context attributes, + * see https://developer.mozilla.org/en/docs/Web/API/HTMLCanvasElement/getContext for the options available + * @return {WebGLRenderingContext} the WebGL context + */ + createContext(canvas, options) + { + let gl; + + if (settings.PREFER_WEBGL_2) + { + gl = canvas.getContext('webgl2', options); + } + + if (gl) + { + this.webGLVersion = 2; + } + else + { + this.webGLVersion = 1; + + gl = canvas.getContext('webgl', options) + || canvas.getContext('experimental-webgl', options); + + if (!gl) + { + // fail, not able to get a context + throw new Error('This browser does not support webGL. Try using the canvas renderer'); + } + } + + this.gl = gl; + + this.getExtensions(); + + return gl; + } + + getExtensions() + { + // time to set up default etensions that pixi uses.. + const gl = this.gl; + const extensions = this.extensions; + + if (this.webGLVersion === 1) + { + extensions.drawBuffers = gl.getExtension('WEBGL_draw_buffers'); + extensions.depthTexture = gl.getExtension('WEBKIT_WEBGL_depth_texture'); + extensions.floatTexture = gl.getExtension('OES_texture_float'); + extensions.loseContext = gl.getExtension('WEBGL_lose_context'); + + extensions.vertexArrayObject = gl.getExtension('OES_vertex_array_object') + || gl.getExtension('MOZ_OES_vertex_array_object') + || gl.getExtension('WEBKIT_OES_vertex_array_object'); + } + + // we don't use any specific WebGL 2 ones yet! + } + + /** + * Handles a lost webgl context + * + * @private + * @param {WebGLContextEvent} event - The context lost event. + */ + handleContextLost(event) + { + event.preventDefault(); + } + + /** + * Handles a restored webgl context + * + * @private + */ + handleContextRestored() + { + this.renderer.runners.contextChange.run(this.gl); + } + + destroy() + { + const view = this.renderer.view; + + // remove listeners + view.removeEventListener('webglcontextlost', this.handleContextLost); + view.removeEventListener('webglcontextrestored', this.handleContextRestored); + + this.gl.useProgram(null); + + if (this.extensions.loseContext) + { + this.extensions.loseContext.loseContext(); + } + } + + postrender() + { + this.gl.flush(); + } + + validateContext(gl) + { + const attributes = gl.getContextAttributes(); + + // this is going to be fairly simple for now.. but at least we have room to grow! + if (!attributes.stencil) + { + /* eslint-disable max-len */ + + /* eslint-disable no-console */ + console.warn('Provided WebGL context does not have a stencil buffer, masks may not render correctly'); + /* eslint-enable no-console */ + + /* eslint-enable max-len */ + } + } +} diff --git a/packages/core/src/filters/Filter.js b/packages/core/src/filters/Filter.js new file mode 100644 index 0000000..9d8cad8 --- /dev/null +++ b/packages/core/src/filters/Filter.js @@ -0,0 +1,140 @@ +import Shader from '../shader/Shader'; +import Program from '../shader/Program'; +import State from '../state/State'; +import { settings } from '@pixi/settings'; +// import extractUniformsFromSrc from './extractUniformsFromSrc'; +import defaultVertex from './defaultFilter.vert'; +import defaultFragment from './defaultFilter.frag'; + +// let math = require('../../math'); +/** + * @class + * @memberof PIXI + * @extends PIXI.Shader + */ +export default class Filter extends Shader +{ + /** + * @param {string} [vertexSrc] - The source of the vertex shader. + * @param {string} [fragmentSrc] - The source of the fragment shader. + * @param {object} [uniforms] - Custom uniforms to use to augment the built-in ones. + */ + constructor(vertexSrc, fragmentSrc, uniforms) + { + const program = Program.from(vertexSrc, fragmentSrc); + + super(program, uniforms); + + /** + * The padding of the filter. Some filters require extra space to breath such as a blur. + * Increasing this will add extra width and height to the bounds of the object that the + * filter is applied to. + * + * @member {number} + */ + this.padding = 0; + + /** + * The resolution of the filter. Setting this to be lower will lower the quality but + * increase the performance of the filter. + * + * @member {number} + */ + this.resolution = settings.RESOLUTION; + + /** + * If enabled is true the filter is applied, if false it will not. + * + * @member {boolean} + */ + this.enabled = true; + + /** + * If enabled, PixiJS will fit the filter area into boundaries for better performance. + * Switch it off if it does not work for specific shader. + * + * @member {boolean} + */ + this.autoFit = true; + + /** + * Legacy filters use position and uvs from attributes + * @member {boolean} + * @readonly + */ + this.legacy = !!this.program.attributeData.aTextureCoord; + + /** + * the webGL state the filter requires to render + * @member {PIXI.State} + */ + this.state = new State(); + } + + /** + * Applies the filter + * + * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + * @param {boolean} clear - Should the output be cleared before rendering to it + * @param {object} [currentState] - It's current state of filter. + * There are some useful properties in the currentState : + * target, filters, sourceFrame, destinationFrame, renderTarget, resolution + */ + apply(filterManager, input, output, clear, currentState, derp) // eslint-disable-line no-unused-vars + { + // do as you please! + + filterManager.applyFilter(this, input, output, clear, currentState, derp); + + // or just do a regular render.. + } + + /** + * Sets the blendmode of the filter + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL + */ + get blendMode() + { + return this.state.blendMode; + } + + set blendMode(value) // eslint-disable-line require-jsdoc + { + this.state.blendMode = value; + } + + /** + * The default vertex shader source + * + * @static + * @constant + */ + static get defaultVertexSrc() + { + return defaultVertex; + } + + /** + * The default fragment shader source + * + * @static + * @constant + */ + static get defaultFragmentSrc() + { + return defaultFragment; + } +} + +/** + * Used for caching shader IDs + * + * @static + * @private + */ +Filter.SOURCE_KEY_MAP = {}; + diff --git a/packages/core/src/filters/FilterSystem.js b/packages/core/src/filters/FilterSystem.js new file mode 100644 index 0000000..3174318 --- /dev/null +++ b/packages/core/src/filters/FilterSystem.js @@ -0,0 +1,431 @@ +import System from '../System'; + +import RenderTexture from '../renderTexture/RenderTexture'; +import Quad from '../utils/Quad'; +import QuadUv from '../utils/QuadUv'; +import { Rectangle } from '@pixi/math'; +import * as filterTransforms from './filterTransforms'; +import bitTwiddle from 'bit-twiddle'; +import UniformGroup from '../shader/UniformGroup'; +import { DRAW_MODES } from '@pixi/constants'; + +// +/** + * @ignore + * @class + */ +class FilterState +{ + /** + * + */ + constructor() + { + this.renderTexture = null; + this.sourceFrame = new Rectangle(); + this.destinationFrame = new Rectangle(); + this.filters = []; + this.target = null; + this.legacy = false; + this.resolution = 1; + } +} + +/** + * @class + * @memberof PIXI.systems + * @extends PIXI.System + */ +export default class FilterSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + /** + * stores a bunch of PO2 textures used for filtering + * @type {Object} + */ + this.texturePool = {}; + + /** + * a pool for storing filter states, save us creating new ones each tick + * @type {Array} + */ + this.statePool = []; + + /** + * A very simple geometry used when drawing a filter effect to the screen + * @type {Quad} + */ + this.quad = new Quad(); + + this.quadUv = new QuadUv(); + + /** + * Temporary rect for maths + * @type {PIXI.Rectangle} + */ + this.tempRect = new Rectangle(); + + this.activeState = {}; + + /** + * this uniform group is attached to filter uniforms when used + * @type {UniformGroup} + */ + this.globalUniforms = new UniformGroup({ + sourceFrame: this.tempRect, + destinationFrame: this.tempRect, + + // legacy variables + filterArea: new Float32Array(4), + filterClamp: new Float32Array(4), + }, true); + } + + /** + * Adds a new filter to the System. + * + * @param {PIXI.DisplayObject} target - The target of the filter to render. + * @param {PIXI.Filter[]} filters - The filters to apply. + */ + push(target, filters) + { + const renderer = this.renderer; + const filterStack = this.renderer.renderTexture.defaultFilterStack; + const state = this.statePool.pop() || new FilterState(); + + let resolution = filters[0].resolution; + let padding = filters[0].padding; + let autoFit = filters[0].autoFit; + let legacy = filters[0].legacy; + + for (let i = 1; i < filters.length; i++) + { + const filter = filters[i]; + + // lets use the lowest resolution.. + resolution = Math.min(resolution, filter.resolution); + // and the largest amount of padding! + padding = Math.max(padding, filter.padding); + // only auto fit if all filters are autofit + autoFit = autoFit || filter.autoFit; + + legacy = legacy || filter.legacy; + } + + filterStack.push(state); + + state.resolution = resolution; + + state.legacy = legacy; + + // round to whole number based on resolution + // TODO move that to the shader too? + state.sourceFrame = target.filterArea || target.getBounds(true); + + state.sourceFrame.pad(padding); + + if (autoFit) + { + state.sourceFrame.fit(this.renderer.renderTexture.destinationFrame); + } + + state.sourceFrame.round(resolution); + + state.renderTexture = this.getPotFilterTexture(state.sourceFrame.width, state.sourceFrame.height, resolution); + state.filters = filters; + + state.destinationFrame.width = state.renderTexture.width; + state.destinationFrame.height = state.renderTexture.height; + + state.renderTexture.filterFrame = state.sourceFrame; + renderer.renderTexture.bind(state.renderTexture, state.sourceFrame);// /, state.destinationFrame); + renderer.renderTexture.clear(); + } + + /** + * Pops off the filter and applies it. + * + */ + pop() + { + const renderer = this.renderer; + const filterStack = renderer.renderTexture.defaultFilterStack; + const state = filterStack.pop(); + const filters = state.filters; + + this.activeState = state; + + const globalUniforms = this.globalUniforms.uniforms; + + globalUniforms.sourceFrame = state.sourceFrame; + globalUniforms.destinationFrame = state.destinationFrame; + globalUniforms.resolution = state.resolution; + + // only update the rect if its legacy.. + if (state.legacy) + { + const filterArea = globalUniforms.filterArea; + const filterClamp = globalUniforms.filterClamp; + + filterArea[0] = state.destinationFrame.width; + filterArea[1] = state.destinationFrame.height; + filterArea[2] = state.sourceFrame.x; + filterArea[3] = state.sourceFrame.y; + + filterClamp[0] = 0.5 / state.resolution / state.destinationFrame.width; + filterClamp[1] = 0.5 / state.resolution / state.destinationFrame.height; + filterClamp[2] = (state.sourceFrame.width - 0.5) / state.resolution / state.destinationFrame.width; + filterClamp[3] = (state.sourceFrame.height - 0.5) / state.resolution / state.destinationFrame.height; + } + + this.globalUniforms.update(); + + const lastState = filterStack[filterStack.length - 1]; + + if (filters.length === 1) + { + filters[0].apply(this, state.renderTexture, lastState.renderTexture, false, state); + + this.returnFilterTexture(state.renderTexture); + } + else + { + let flip = state.renderTexture; + let flop = this.getPotFilterTexture( + flip.width, + flip.height, + state.resolution + ); + + flop.filterFrame = flip.filterFrame; + + let i = 0; + + for (i = 0; i < filters.length - 1; ++i) + { + filters[i].apply(this, flip, flop, true, state); + + const t = flip; + + flip = flop; + flop = t; + } + + filters[i].apply(this, flip, lastState.renderTexture, false, state); + + this.returnFilterTexture(flip); + this.returnFilterTexture(flop); + } + + this.statePool.push(state); + } + + /** + * Draws a filter. + * + * @param {PIXI.Filter} filter - The filter to draw. + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + * @param {boolean} clear - Should the output be cleared before rendering to it + */ + applyFilter(filter, input, output, clear) + { + const renderer = this.renderer; + + renderer.renderTexture.bind(output, output ? output.filterFrame : null); + + if (clear) + { + // gl.disable(gl.SCISSOR_TEST); + renderer.renderTexture.clear(); + // gl.enable(gl.SCISSOR_TEST); + } + + // set the uniforms.. + filter.uniforms.uSampler = input; + filter.uniforms.filterGlobals = this.globalUniforms; + + // TODO make it so that the order of this does not matter.. + // because it does at the moment cos of global uniforms. + // they need to get resynced + + renderer.state.setState(filter.state); + renderer.shader.bind(filter); + + if (filter.legacy) + { + this.quadUv.map(input._frame, input.filterFrame); + + renderer.geometry.bind(this.quadUv); + renderer.geometry.draw(DRAW_MODES.TRIANGLES); + } + else + { + renderer.geometry.bind(this.quad); + renderer.geometry.draw(DRAW_MODES.TRIANGLE_STRIP); + } + } + + /** + * Calculates the mapped matrix. + * + * TODO playing around here.. this is temporary - (will end up in the shader) + * this returns a matrix that will normalise map filter cords in the filter to screen space + * + * @param {PIXI.Matrix} outputMatrix - the matrix to output to. + * @return {PIXI.Matrix} The mapped matrix. + */ + calculateScreenSpaceMatrix(outputMatrix) + { + const currentState = this.activeState; + + return filterTransforms.calculateScreenSpaceMatrix( + outputMatrix, + currentState.sourceFrame, + currentState.destinationFrame + ); + } + + /** + * This will map the filter coord so that a texture can be used based on the transform of a sprite + * + * @param {PIXI.Matrix} outputMatrix - The matrix to output to. + * @param {PIXI.Sprite} sprite - The sprite to map to. + * @return {PIXI.Matrix} The mapped matrix. + */ + calculateSpriteMatrix(outputMatrix, sprite) + { + const currentState = this.activeState; + + return filterTransforms.calculateSpriteMatrix( + outputMatrix, + currentState.sourceFrame, + currentState.destinationFrame, + sprite + ); + } + + /** + * Destroys this Filter System. + * + * @param {boolean} [contextLost=false] context was lost, do not free shaders + * + */ + destroy(contextLost = false) + { + if (!contextLost) + { + this.emptyPool(); + } + else + { + this.texturePool = {}; + } + } + + /** + * Gets a Power-of-Two render texture. + * + * TODO move to a seperate class could be on renderer? + * also - could cause issue with multiple contexts? + * + * @private + * @param {WebGLRenderingContext} gl - The webgl rendering context + * @param {number} minWidth - The minimum width of the render target. + * @param {number} minHeight - The minimum height of the render target. + * @param {number} resolution - The resolution of the render target. + * @return {PIXI.RenderTarget} The new render target. + */ + getPotFilterTexture(minWidth, minHeight, resolution) + { + minWidth = bitTwiddle.nextPow2(minWidth); + minHeight = bitTwiddle.nextPow2(minHeight); + resolution = resolution || 1; + + const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + + if (!this.texturePool[key]) + { + this.texturePool[key] = []; + } + + let renderTexture = this.texturePool[key].pop(); + + if (!renderTexture) + { + // temporary bypass cache.. + // internally - this will cause a texture to be bound.. + renderTexture = RenderTexture.create({ + width: minWidth, + height: minHeight, + resolution, + }); + } + + return renderTexture; + } + + /** + * Gets extra render texture to use inside current filter + * + * @param {number} resolution resolution of the renderTexture + * @returns {PIXI.RenderTexture} + */ + getFilterTexture(resolution) + { + const rt = this.activeState.renderTexture; + + const filterTexture = this.getPotFilterTexture(rt.width, rt.height, resolution || rt.baseTexture.resolution); + + filterTexture.filterFrame = rt.filterFrame; + + return filterTexture; + } + + /** + * Frees a render texture back into the pool. + * + * @param {PIXI.RenderTarget} renderTexture - The renderTarget to free + */ + returnFilterTexture(renderTexture) + { + renderTexture.filterFrame = null; + + const base = renderTexture.baseTexture; + + const minWidth = base.width; + const minHeight = base.height; + + const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + + this.texturePool[key].push(renderTexture); + } + + /** + * Empties the texture pool. + * + */ + emptyPool() + { + for (const i in this.texturePool) + { + const textures = this.texturePool[i]; + + if (textures) + { + for (let j = 0; j < textures.length; j++) + { + textures[j].destroy(true); + } + } + } + + this.texturePool = {}; + } +} diff --git a/packages/core/src/filters/defaultFilter.frag b/packages/core/src/filters/defaultFilter.frag new file mode 100644 index 0000000..a14c1be --- /dev/null +++ b/packages/core/src/filters/defaultFilter.frag @@ -0,0 +1,22 @@ +varying vec2 vTextureCoord; +varying vec2 vFilterCoord; + +uniform sampler2D uSampler; +uniform sampler2D filterSampler; + +void main(void){ + vec4 masky = texture2D(filterSampler, vFilterCoord); + vec4 sample = texture2D(uSampler, vTextureCoord); + vec4 color; + if(mod(vFilterCoord.x, 1.0) > 0.5) + { + color = vec4(1.0, 0.0, 0.0, 1.0); + } + else + { + color = vec4(0.0, 1.0, 0.0, 1.0); + } + // gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0); + gl_FragColor = mix(sample, masky, 0.5); + gl_FragColor *= sample.a; +} diff --git a/packages/core/src/filters/defaultFilter.vert b/packages/core/src/filters/defaultFilter.vert new file mode 100644 index 0000000..e1febca --- /dev/null +++ b/packages/core/src/filters/defaultFilter.vert @@ -0,0 +1,14 @@ +attribute vec2 aVertexPosition; +attribute vec2 aTextureCoord; + +uniform mat3 projectionMatrix; +uniform mat3 filterMatrix; + +varying vec2 vTextureCoord; +varying vec2 vFilterCoord; + +void main(void){ + gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); + vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy; + vTextureCoord = aTextureCoord ; +} diff --git a/packages/core/src/filters/filterTransforms.js b/packages/core/src/filters/filterTransforms.js new file mode 100644 index 0000000..87a3cd6 --- /dev/null +++ b/packages/core/src/filters/filterTransforms.js @@ -0,0 +1,50 @@ +import { Matrix } from '@pixi/math'; + +/** + * Calculates the mapped matrix + * @param filterArea {Rectangle} The filter area + * @param sprite {Sprite} the target sprite + * @param outputMatrix {Matrix} @alvin + * @private + */ +// this returns a matrix that will normalise map filter cords in the filter to screen space +export function calculateScreenSpaceMatrix(outputMatrix, filterArea, textureSize) +{ + // TODO unwrap? + const mappedMatrix = outputMatrix.identity(); + + mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); + + mappedMatrix.scale(textureSize.width, textureSize.height); + + return mappedMatrix; +} + +export function calculateNormalizedScreenSpaceMatrix(outputMatrix, filterArea, textureSize) +{ + const mappedMatrix = outputMatrix.identity(); + + mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); + + const translateScaleX = (textureSize.width / filterArea.width); + const translateScaleY = (textureSize.height / filterArea.height); + + mappedMatrix.scale(translateScaleX, translateScaleY); + + return mappedMatrix; +} + +// this will map the filter coord so that a texture can be used based on the transform of a sprite +export function calculateSpriteMatrix(outputMatrix, filterArea, textureSize, sprite) +{ + const orig = sprite._texture.orig; + const mappedMatrix = outputMatrix.set(textureSize.width, 0, 0, textureSize.height, filterArea.x, filterArea.y); + const worldTransform = sprite.worldTransform.copyTo(Matrix.TEMP_MATRIX); + + worldTransform.invert(); + mappedMatrix.prepend(worldTransform); + mappedMatrix.scale(1.0 / orig.width, 1.0 / orig.height); + mappedMatrix.translate(sprite.anchor.x, sprite.anchor.y); + + return mappedMatrix; +} diff --git a/packages/core/src/filters/spriteMask/SpriteMaskFilter.js b/packages/core/src/filters/spriteMask/SpriteMaskFilter.js new file mode 100644 index 0000000..91697b7 --- /dev/null +++ b/packages/core/src/filters/spriteMask/SpriteMaskFilter.js @@ -0,0 +1,63 @@ +import Filter from '../Filter'; +import { Matrix } from '@pixi/math'; +import vertex from './spriteMaskFilter.vert'; +import fragment from './spriteMaskFilter.frag'; +import { default as TextureMatrix } from '../../textures/TextureMatrix'; + +/** + * The SpriteMaskFilter class + * + * @class + * @extends PIXI.Filter + * @memberof PIXI + */ +export default class SpriteMaskFilter extends Filter +{ + /** + * @param {PIXI.Sprite} sprite - the target sprite + */ + constructor(sprite) + { + const maskMatrix = new Matrix(); + + super(vertex, fragment); + + sprite.renderable = false; + + this.maskSprite = sprite; + this.maskMatrix = maskMatrix; + } + + /** + * Applies the filter + * + * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + */ + apply(filterManager, input, output) + { + const maskSprite = this.maskSprite; + const tex = this.maskSprite.texture; + + if (!tex.valid) + { + return; + } + if (!tex.transform) + { + // margin = 0.0, let it bleed a bit, shader code becomes easier + // assuming that atlas textures were made with 1-pixel padding + tex.transform = new TextureMatrix(tex, 0.0); + } + tex.transform.update(); + + this.uniforms.mask = tex; + this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite) + .prepend(tex.transform.mapCoord); + this.uniforms.alpha = maskSprite.worldAlpha; + this.uniforms.maskClamp = tex.transform.uClampFrame; + + filterManager.applyFilter(this, input, output); + } +} diff --git a/packages/core/src/filters/spriteMask/spriteMaskFilter.frag b/packages/core/src/filters/spriteMask/spriteMaskFilter.frag new file mode 100644 index 0000000..0e4aef8 --- /dev/null +++ b/packages/core/src/filters/spriteMask/spriteMaskFilter.frag @@ -0,0 +1,23 @@ +varying vec2 vMaskCoord; +varying vec2 vTextureCoord; + +uniform sampler2D uSampler; +uniform sampler2D mask; +uniform float alpha; +uniform vec4 maskClamp; + +void main(void) +{ + float clip = step(3.5, + step(maskClamp.x, vMaskCoord.x) + + step(maskClamp.y, vMaskCoord.y) + + step(vMaskCoord.x, maskClamp.z) + + step(vMaskCoord.y, maskClamp.w)); + + vec4 original = texture2D(uSampler, vTextureCoord); + vec4 masky = texture2D(mask, vMaskCoord); + + original *= (masky.r * masky.a * alpha * clip); + + gl_FragColor = original; +} diff --git a/packages/core/src/filters/spriteMask/spriteMaskFilter.vert b/packages/core/src/filters/spriteMask/spriteMaskFilter.vert new file mode 100644 index 0000000..d23a30c --- /dev/null +++ b/packages/core/src/filters/spriteMask/spriteMaskFilter.vert @@ -0,0 +1,16 @@ +attribute vec2 aVertexPosition; +attribute vec2 aTextureCoord; + +uniform mat3 projectionMatrix; +uniform mat3 otherMatrix; + +varying vec2 vMaskCoord; +varying vec2 vTextureCoord; + +void main(void) +{ + gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); + + vTextureCoord = aTextureCoord; + vMaskCoord = ( otherMatrix * vec3( aTextureCoord, 1.0) ).xy; +} diff --git a/packages/core/src/framebuffer/FrameBuffer.js b/packages/core/src/framebuffer/FrameBuffer.js new file mode 100644 index 0000000..84e9fcc --- /dev/null +++ b/packages/core/src/framebuffer/FrameBuffer.js @@ -0,0 +1,106 @@ +import Texture from '../textures/BaseTexture'; +import { FORMATS, TYPES } from '@pixi/constants'; + +/** + * Frame buffer + * @class + * @memberof PIXI + */ +export default class FrameBuffer +{ + constructor(width, height) + { + this.width = width || 100; + this.height = height || 100; + + this.stencil = false; + this.depth = false; + + this.dirtyId = 0; + this.dirtyFormat = 0; + this.dirtySize = 0; + + this.depthTexture = null; + this.colorTextures = []; + + this.glFrameBuffers = {}; + } + + get colorTexture() + { + return this.colorTextures[0]; + } + + addColorTexture(index, texture) + { + // TODO add some validation to the texture - same width / height etc? + this.colorTextures[index || 0] = texture || new Texture(null, { scaleMode: 0, + resolution: 1, + mipmap: false, + width: this.width, + height: this.height });// || new Texture(); + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + addDepthTexture(texture) + { + /* eslint-disable max-len */ + this.depthTexture = texture || new Texture(null, { scaleMode: 0, + resolution: 1, + width: this.width, + height: this.height, + mipmap: false, + format: FORMATS.DEPTH_COMPONENT, + type: TYPES.UNSIGNED_SHORT });// UNSIGNED_SHORT; + /* eslint-disable max-len */ + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + enableDepth() + { + this.depth = true; + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + enableStencil() + { + this.stencil = true; + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + resize(width, height) + { + if (width === this.width && height === this.height) return; + + this.width = width; + this.height = height; + + this.dirtyId++; + this.dirtySize++; + + for (let i = 0; i < this.colorTextures.length; i++) + { + this.colorTextures[i].setSize(width, height); + } + + if (this.depthTexture) + { + this.depthTexture.setSize(width, height); + } + } +} diff --git a/packages/core/src/framebuffer/FramebufferSystem.js b/packages/core/src/framebuffer/FramebufferSystem.js new file mode 100644 index 0000000..2cc2437 --- /dev/null +++ b/packages/core/src/framebuffer/FramebufferSystem.js @@ -0,0 +1,246 @@ +import System from '../System'; +import { Rectangle } from '@pixi/math'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class FramebufferSystem extends System +{ + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + contextChange() + { + this.gl = this.renderer.gl; + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + this.current = null; + this.viewport = new Rectangle(); + + this.drawBufferExtension = this.renderer.context.extensions.drawBuffers; + } + + bind(framebuffer, frame) + { + const gl = this.gl; + + this.current = framebuffer; + + if (framebuffer) + { + // TODO cacheing layer! + + const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID] || this.initFramebuffer(framebuffer); + + gl.bindFramebuffer(gl.FRAMEBUFFER, fbo.framebuffer); + // makesure all textures are unbound.. + + // now check for updates... + if (fbo.dirtyId !== framebuffer.dirtyId) + { + fbo.dirtyId = framebuffer.dirtyId; + + if (fbo.dirtyFormat !== framebuffer.dirtyFormat) + { + fbo.dirtyFormat = framebuffer.dirtyFormat; + this.updateFramebuffer(framebuffer); + } + else if (fbo.dirtySize !== framebuffer.dirtySize) + { + fbo.dirtySize = framebuffer.dirtySize; + this.resizeFramebuffer(framebuffer); + } + } + + for (let i = 0; i < framebuffer.colorTextures.length; i++) + { + if (framebuffer.colorTextures[i].texturePart) + { + this.renderer.texture.unbind(framebuffer.colorTextures[i].texture); + } + else + { + this.renderer.texture.unbind(framebuffer.colorTextures[i]); + } + } + + if (framebuffer.depthTexture) + { + this.renderer.texture.unbind(framebuffer.depthTexture); + } + + if (frame) + { + this.setViewport(frame.x, frame.y, frame.width, frame.height); + } + else + { + this.setViewport(0, 0, framebuffer.width, framebuffer.height); + } + } + else + { + gl.bindFramebuffer(gl.FRAMEBUFFER, null); + + if (frame) + { + this.setViewport(frame.x, frame.y, frame.width, frame.height); + } + else + { + this.setViewport(0, 0, this.renderer.width, this.renderer.height); + } + } + } + + setViewport(x, y, width, height) + { + const v = this.viewport; + + if (v.width !== width || v.height !== height || v.x !== x || v.y !== y) + { + v.x = x; + v.y = y; + v.width = width; + v.height = height; + + this.gl.viewport(x, y, width, height); + } + } + + get size() + { + if (this.current) + { + // TODO store temp + return { x: 0, y: 0, width: this.current.width, height: this.current.height }; + } + + return { x: 0, y: 0, width: this.renderer.width, height: this.renderer.height }; + } + + clear(r, g, b, a) + { + const gl = this.gl; + + // TODO clear color can be set only one right? + gl.clearColor(r, g, b, a); + gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); + } + + // private functions... + + initFramebuffer(framebuffer) + { + const gl = this.gl; + + // TODO - make this a class? + const fbo = { + framebuffer: gl.createFramebuffer(), + stencil: null, + dirtyId: 0, + dirtyFormat: 0, + dirtySize: 0, + }; + + framebuffer.glFrameBuffers[this.CONTEXT_UID] = fbo; + + return fbo; + } + + resizeFramebuffer(framebuffer) + { + const gl = this.gl; + + if (framebuffer.stencil || framebuffer.depth) + { + gl.bindRenderbuffer(gl.RENDERBUFFER, this.stencil); + gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); + } + } + + updateFramebuffer(framebuffer) + { + const gl = this.gl; + + const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID]; + + // bind the color texture + const colorTextures = framebuffer.colorTextures; + + let count = colorTextures.length; + + if (!this.drawBufferExtension) + { + count = Math.min(count, 1); + } + + const activeTextures = []; + + for (let i = 0; i < count; i++) + { + const texture = framebuffer.colorTextures[i]; + + if (texture.texturePart) + { + this.renderer.texture.bind(texture.texture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.COLOR_ATTACHMENT0 + i, + gl.TEXTURE_CUBE_MAP_NEGATIVE_X + texture.side, + texture.texture._glTextures[this.CONTEXT_UID].texture, + 0); + } + else + { + this.renderer.texture.bind(texture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.COLOR_ATTACHMENT0 + i, + gl.TEXTURE_2D, + texture._glTextures[this.CONTEXT_UID].texture, + 0); + } + + activeTextures.push(gl.COLOR_ATTACHMENT0 + i); + } + + if (this.drawBufferExtension && activeTextures.length > 1) + { + this.drawBufferExtension.drawBuffersWEBGL(activeTextures); + } + + if (framebuffer.depthTexture) + { + const depthTextureExt = this.renderer.context.extensions.depthTexture; + + if (depthTextureExt) + { + const depthTexture = framebuffer.depthTexture; + + this.renderer.texture.bind(depthTexture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.DEPTH_ATTACHMENT, + gl.TEXTURE_2D, + depthTexture._glTextures[this.CONTEXT_UID].texture, + 0); + } + } + + if (framebuffer.stencil || framebuffer.depth) + { + fbo.stencil = gl.createRenderbuffer(); + + gl.bindRenderbuffer(gl.RENDERBUFFER, fbo.stencil); + + // TODO.. this is depth AND stencil? + gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, fbo.stencil); + gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); + // fbo.enableStencil(); + } + } +} diff --git a/packages/core/src/geometry/GLBuffer.js b/packages/core/src/geometry/GLBuffer.js new file mode 100644 index 0000000..0477e8a --- /dev/null +++ b/packages/core/src/geometry/GLBuffer.js @@ -0,0 +1,9 @@ +export default class GLBuffer +{ + constructor(buffer) + { + this.buffer = buffer; + this.updateID = -1; + this.byteLength = -1; + } +} diff --git a/packages/core/src/geometry/GeometrySystem.js b/packages/core/src/geometry/GeometrySystem.js new file mode 100644 index 0000000..959f827 --- /dev/null +++ b/packages/core/src/geometry/GeometrySystem.js @@ -0,0 +1,388 @@ +import System from '../System'; +import GLBuffer from './GLBuffer'; + +const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class GeometrySystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this._activeGeometry = null; + this._activeVao = null; + + this.hasVao = true; + this.hasInstance = true; + } + + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + contextChange() + { + const gl = this.gl = this.renderer.gl; + + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + + // webgl2 + if (!gl.createVertexArray) + { + // webgl 1! + let nativeVaoExtension = this.renderer.context.extensions.vertexArrayObject; + + if (this.renderer.legacy) + { + nativeVaoExtension = null; + } + + if (nativeVaoExtension) + { + gl.createVertexArray = () => + nativeVaoExtension.createVertexArrayOES(); + + gl.bindVertexArray = (vao) => + nativeVaoExtension.bindVertexArrayOES(vao); + + gl.deleteVertexArray = (vao) => + nativeVaoExtension.deleteVertexArrayOES(vao); + } + else + { + this.hasVao = false; + gl.createVertexArray = () => + { + // empty + }; + + gl.bindVertexArray = () => + { + // empty + }; + + gl.deleteVertexArray = () => + { + // empty + }; + } + } + + if (!gl.vertexAttribDivisor) + { + const instanceExt = gl.getExtension('ANGLE_instanced_arrays'); + + if (instanceExt) + { + gl.vertexAttribDivisor = (a, b) => + instanceExt.vertexAttribDivisorANGLE(a, b); + + gl.drawElementsInstanced = (a, b, c, d, e) => + instanceExt.drawElementsInstancedANGLE(a, b, c, d, e); + + gl.drawArraysInstanced = (a, b, c, d) => + instanceExt.drawArraysInstancedANGLE(a, b, c, d); + } + else + { + this.hasInstance = false; + } + } + } + + /** + * Binds geometry so that is can be drawn. Creating a Vao if required + * @private + * @param {PIXI.Geometry} geometry instance of geometry to bind + */ + bind(geometry, shader) + { + shader = shader || this.renderer.shader.shader; + + const gl = this.gl; + + // not sure the best way to address this.. + // currently different shaders require different VAOs for the same geometry + // Still mulling over the best way to solve this one.. + // will likely need to modify the shader attribute locations at run time! + let vaos = geometry.glVertexArrayObjects[this.CONTEXT_UID]; + + if (!vaos) + { + geometry.glVertexArrayObjects[this.CONTEXT_UID] = vaos = {}; + } + + const vao = vaos[shader.program.id] || this.initGeometryVao(geometry, shader.program); + + this._activeGeometry = geometry; + + if (this._activeVao !== vao) + { + this._activeVao = vao; + + if (this.hasVao) + { + gl.bindVertexArray(vao); + } + else + { + this.activateVao(geometry, shader.program); + } + } + + // TODO - optimise later! + // don't need to loop through if nothing changed! + // maybe look to add an 'autoupdate' to geometry? + this.updateBuffers(); + } + + reset() + { + this.unbind(); + } + + updateBuffers() + { + const geometry = this._activeGeometry; + const gl = this.gl; + + for (let i = 0; i < geometry.buffers.length; i++) + { + const buffer = geometry.buffers[i]; + + const glBuffer = buffer._glBuffers[this.CONTEXT_UID]; + + if (buffer._updateID !== glBuffer.updateID) + { + glBuffer.updateID = buffer._updateID; + + // TODO can cache this on buffer! maybe added a getter / setter? + const type = buffer.index ? gl.ELEMENT_ARRAY_BUFFER : gl.ARRAY_BUFFER; + const drawType = buffer.static ? gl.STATIC_DRAW : gl.DYNAMIC_DRAW; + + gl.bindBuffer(type, glBuffer.buffer); + + if (glBuffer.byteLength >= buffer.data.byteLength) + { + // offset is always zero for now! + gl.bufferSubData(type, 0, buffer.data); + } + else + { + gl.bufferData(type, buffer.data, drawType); + } + } + } + } + + checkCompatability(geometry, program) + { + // geometry must have at least all the attributes that the shader requires. + const geometryAttributes = geometry.attributes; + const shaderAttributes = program.attributeData; + + for (const j in shaderAttributes) + { + if (!geometryAttributes[j]) + { + throw new Error(`shader and geometry incompatible, geometry missing the "${j}" attribute`); + } + } + } + + /** + * Creates a Vao with the same structure as the geometry and stores it on the geometry. + * @private + * @param {PIXI.Geometry} geometry instance of geometry to to generate Vao for + */ + initGeometryVao(geometry, program) + { + this.checkCompatability(geometry, program); + + const gl = this.gl; + const CONTEXT_UID = this.CONTEXT_UID; + const buffers = geometry.buffers; + const attributes = geometry.attributes; + + const tempStride = {}; + const tempStart = {}; + + for (const j in buffers) + { + tempStride[j] = 0; + tempStart[j] = 0; + } + + for (const j in attributes) + { + if (!attributes[j].size && program.attributeData[j]) + { + attributes[j].size = program.attributeData[j].size; + } + + tempStride[attributes[j].buffer] += attributes[j].size * byteSizeMap[attributes[j].type]; + } + + for (const j in attributes) + { + const attribute = attributes[j]; + const attribSize = attribute.size; + + if (attribute.stride === undefined) + { + if (tempStride[attribute.buffer] === attribSize * byteSizeMap[attribute.type]) + { + attribute.stride = 0; + } + else + { + attribute.stride = tempStride[attribute.buffer]; + } + } + + if (attribute.start === undefined) + { + attribute.start = tempStart[attribute.buffer]; + + tempStart[attribute.buffer] += attribSize * byteSizeMap[attribute.type]; + } + } + + // first update - and create the buffers! + // only create a gl buffer if it actually gets + for (let i = 0; i < buffers.length; i++) + { + const buffer = buffers[i]; + + if (!buffer._glBuffers[CONTEXT_UID]) + { + buffer._glBuffers[CONTEXT_UID] = new GLBuffer(gl.createBuffer()); + } + } + + // TODO - maybe make this a data object? + // lets wait to see if we need to first! + const vao = gl.createVertexArray(); + + gl.bindVertexArray(vao); + + this.activateVao(geometry, program); + + gl.bindVertexArray(null); + + // add it to the cache! + geometry.glVertexArrayObjects[this.CONTEXT_UID][program.id] = vao; + + return vao; + } + + activateVao(geometry, program) + { + const gl = this.gl; + const CONTEXT_UID = this.CONTEXT_UID; + const buffers = geometry.buffers; + const attributes = geometry.attributes; + + if (geometry.indexBuffer) + { + // first update the index buffer if we have one.. + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, geometry.indexBuffer._glBuffers[CONTEXT_UID].buffer); + } + + let lastBuffer = null; + + // add a new one! + for (const j in attributes) + { + const attribute = attributes[j]; + const buffer = buffers[attribute.buffer]; + const glBuffer = buffer._glBuffers[CONTEXT_UID]; + + if (program.attributeData[j]) + { + if (lastBuffer !== glBuffer) + { + gl.bindBuffer(gl.ARRAY_BUFFER, glBuffer.buffer); + + lastBuffer = glBuffer; + } + + const location = program.attributeData[j].location; + + // TODO introduce state again + // we can optimise this for older devices that have no VAOs + gl.enableVertexAttribArray(location); + + gl.vertexAttribPointer(location, + attribute.size, + attribute.type || gl.FLOAT, + attribute.normalized, + attribute.stride, + attribute.start); + + if (attribute.instance) + { + // TODO calculate instance count based of this... + if (this.hasInstance) + { + gl.vertexAttribDivisor(location, 1); + } + else + { + throw new Error('geometry error, GPU Instancing is not supported on this device'); + } + } + } + } + } + + draw(type, size, start, instanceCount) + { + const gl = this.gl; + const geometry = this._activeGeometry; + + // TODO.. this should not change so maybe cache the function? + + if (geometry.indexBuffer) + { + if (geometry.instanced) + { + /* eslint-disable max-len */ + gl.drawElementsInstanced(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2, instanceCount || 1); + /* eslint-enable max-len */ + } + else + { + gl.drawElements(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2); + } + } + else + if (geometry.instanced) + { + // TODO need a better way to calculate size.. + gl.drawArraysInstanced(type, start, size || geometry.getSize(), instanceCount || 1); + } + else + { + gl.drawArrays(type, start, size || geometry.getSize()); + } + + return this; + } + + unbind() + { + this.gl.bindVertexArray(null); + this._activeVao = null; + this._activeGeometry = null; + } +} diff --git a/packages/core/src/geometry/utils/setVertexAttribArrays.js b/packages/core/src/geometry/utils/setVertexAttribArrays.js new file mode 100644 index 0000000..23b6959 --- /dev/null +++ b/packages/core/src/geometry/utils/setVertexAttribArrays.js @@ -0,0 +1,54 @@ +// var GL_MAP = {}; + +/** + * @param gl {WebGLRenderingContext} The current WebGL context + * @param attribs {*} + * @param state {*} + */ +export default function setVertexAttribArrays(gl, attribs, state) +{ + let i; + + if (state) + { + const tempAttribState = state.tempAttribState; + const attribState = state.attribState; + + for (i = 0; i < tempAttribState.length; i++) + { + tempAttribState[i] = false; + } + + // set the new attribs + for (i = 0; i < attribs.length; i++) + { + tempAttribState[attribs[i].attribute.location] = true; + } + + for (i = 0; i < attribState.length; i++) + { + if (attribState[i] !== tempAttribState[i]) + { + attribState[i] = tempAttribState[i]; + + if (state.attribState[i]) + { + gl.enableVertexAttribArray(i); + } + else + { + gl.disableVertexAttribArray(i); + } + } + } + } + else + { + for (i = 0; i < attribs.length; i++) + { + const attrib = attribs[i]; + + gl.enableVertexAttribArray(attrib.attribute.location); + } + } +} diff --git a/packages/core/src/index.js b/packages/core/src/index.js index 58c456a..fd1f547 100644 --- a/packages/core/src/index.js +++ b/packages/core/src/index.js @@ -1,30 +1,30 @@ import * as resources from './textures/resources'; -import * as systems from './renderers/systems'; +import * as systems from './systems'; -export { resources }; export { systems }; +export { resources }; -export { default as Renderer } from './renderers/Renderer'; -export { default as AbstractRenderer } from './renderers/AbstractRenderer'; -export { default as FrameBuffer } from './textures/FrameBuffer'; +export { default as System } from './System'; +export { default as Renderer } from './Renderer'; +export { default as AbstractRenderer } from './AbstractRenderer'; +export { default as FrameBuffer } from './framebuffer/FrameBuffer'; export { default as CubeTexture } from './textures/CubeTexture'; export { default as BaseTexture } from './textures/BaseTexture'; export { default as Texture } from './textures/Texture'; export { default as TextureMatrix } from './textures/TextureMatrix'; -export { default as RenderTexture } from './textures/RenderTexture'; -export { default as BaseRenderTexture } from './textures/BaseRenderTexture'; +export { default as RenderTexture } from './renderTexture/RenderTexture'; +export { default as BaseRenderTexture } from './renderTexture/BaseRenderTexture'; export { default as TextureUvs } from './textures/TextureUvs'; -export { default as State } from './renderers/State'; -export { default as ObjectRenderer } from './renderers/utils/ObjectRenderer'; -export { default as Quad } from './renderers/utils/Quad'; -export { default as QuadUv } from './renderers/utils/QuadUv'; -export { default as checkMaxIfStatmentsInShader } from './renderers/utils/checkMaxIfStatmentsInShader'; +export { default as State } from './state/State'; +export { default as ObjectRenderer } from './batch/ObjectRenderer'; +export { default as Quad } from './utils/Quad'; +export { default as QuadUv } from './utils/QuadUv'; +export { default as checkMaxIfStatmentsInShader } from './shader/utils/checkMaxIfStatmentsInShader'; export { default as Shader } from './shader/Shader'; export { default as Program } from './shader/Program'; export { default as UniformGroup } from './shader/UniformGroup'; -export { default as SpriteMaskFilter } from './renderers/filters/spriteMask/SpriteMaskFilter'; -export { default as Filter } from './renderers/filters/Filter'; +export { default as SpriteMaskFilter } from './filters/spriteMask/SpriteMaskFilter'; +export { default as Filter } from './filters/Filter'; export { default as Attribute } from './geometry/Attribute'; export { default as Buffer } from './geometry/Buffer'; export { default as Geometry } from './geometry/Geometry'; - diff --git a/packages/core/src/mask/MaskSystem.js b/packages/core/src/mask/MaskSystem.js new file mode 100644 index 0000000..99174c2 --- /dev/null +++ b/packages/core/src/mask/MaskSystem.js @@ -0,0 +1,199 @@ +import System from '../System'; +import AlphaMaskFilter from '../filters/spriteMask/SpriteMaskFilter'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class MaskSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + // TODO - we don't need both! + this.scissor = false; + this.scissorData = null; + this.scissorRenderTarget = null; + + this.enableScissor = false; + + this.alphaMaskPool = []; + this.alphaMaskIndex = 0; + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.DisplayObject} target - Display Object to push the mask to + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + push(target, maskData) + { + // TODO the root check means scissor rect will not + // be used on render textures more info here: + // https://github.com/pixijs/pixi.js/pull/3545 + + if (maskData.texture) + { + this.pushSpriteMask(target, maskData); + } + else if (this.enableScissor + && !this.scissor + && this.renderer._activeRenderTarget.root + && !this.renderer.stencil.stencilMaskStack.length + && maskData.isFastRect()) + { + const matrix = maskData.worldTransform; + + let rot = Math.atan2(matrix.b, matrix.a); + + // use the nearest degree! + rot = Math.round(rot * (180 / Math.PI)); + + if (rot % 90) + { + this.pushStencilMask(maskData); + } + else + { + this.pushScissorMask(target, maskData); + } + } + else + { + this.pushStencilMask(maskData); + } + } + + /** + * Removes the last mask from the mask stack and doesn't return it. + * + * @param {PIXI.DisplayObject} target - Display Object to pop the mask from + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + pop(target, maskData) + { + if (maskData.texture) + { + this.popSpriteMask(target, maskData); + } + else if (this.enableScissor && !this.renderer.stencil.stencilMaskStack.length) + { + this.popScissorMask(target, maskData); + } + else + { + this.popStencilMask(target, maskData); + } + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.RenderTarget} target - Display Object to push the sprite mask to + * @param {PIXI.Sprite} maskData - Sprite to be used as the mask + */ + pushSpriteMask(target, maskData) + { + let alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; + + if (!alphaMaskFilter) + { + alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; + } + + alphaMaskFilter[0].resolution = this.renderer.resolution; + alphaMaskFilter[0].maskSprite = maskData; + + // TODO - may cause issues! + target.filterArea = maskData.getBounds(true); + + this.renderer.filterSystem.pushFilter(target, alphaMaskFilter); + + this.alphaMaskIndex++; + } + + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popSpriteMask() + { + this.renderer.filterSystem.popFilter(); + this.alphaMaskIndex--; + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + pushStencilMask(maskData) + { + this.renderer.batch.flush(); + this.renderer.stencil.pushStencil(maskData); + } + + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popStencilMask() + { + // this.renderer.currentRenderer.stop(); + this.renderer.stencil.popStencil(); + } + + /** + * + * @param {PIXI.DisplayObject} target - Display Object to push the mask to + * @param {PIXI.Graphics} maskData - The masking data. + */ + pushScissorMask(target, maskData) + { + maskData.renderable = true; + + const renderTarget = this.renderer._activeRenderTarget; + + const bounds = maskData.getBounds(); + + bounds.fit(renderTarget.size); + maskData.renderable = false; + + this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); + + const resolution = this.renderer.resolution; + + this.renderer.gl.scissor( + bounds.x * resolution, + (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, + bounds.width * resolution, + bounds.height * resolution + ); + + this.scissorRenderTarget = renderTarget; + this.scissorData = maskData; + this.scissor = true; + } + + /** + * + * + */ + popScissorMask() + { + this.scissorRenderTarget = null; + this.scissorData = null; + this.scissor = false; + + // must be scissor! + const gl = this.renderer.gl; + + gl.disable(gl.SCISSOR_TEST); + } +} diff --git a/packages/core/src/mask/StencilSystem.js b/packages/core/src/mask/StencilSystem.js new file mode 100644 index 0000000..62796a5 --- /dev/null +++ b/packages/core/src/mask/StencilSystem.js @@ -0,0 +1,133 @@ +import System from '../System'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class StencilSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + this.stencilMaskStack = []; + } + + /** + * Changes the mask stack that is used by this System. + * + * @param {PIXI.Graphics[]} stencilMaskStack - The mask stack + */ + setMaskStack(stencilMaskStack) + { + const gl = this.renderer.gl; + + if (stencilMaskStack.length !== this.stencilMaskStack.length) + { + if (stencilMaskStack.length === 0) + { + gl.disable(gl.STENCIL_TEST); + } + else + { + gl.enable(gl.STENCIL_TEST); + } + } + + this.stencilMaskStack = stencilMaskStack; + } + + /** + * Applies the Mask and adds it to the current stencil stack. @alvin + * + * @param {PIXI.Graphics} graphics - The mask + */ + pushStencil(graphics) + { + this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); + + // this.renderer._activeRenderTarget.attachStencilBuffer(); + + const gl = this.renderer.gl; + const prevMaskCount = this.stencilMaskStack.length; + + if (prevMaskCount === 0) + { + gl.enable(gl.STENCIL_TEST); + } + + this.stencilMaskStack.push(graphics); + + // Increment the refference stencil value where the new mask overlaps with the old ones. + gl.colorMask(false, false, false, false); + gl.stencilFunc(gl.EQUAL, prevMaskCount, this._getBitwiseMask()); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.INCR); + this.renderer.plugins.graphics.render(graphics); + + this._useCurrent(); + } + + /** + * Removes the last mask from the stencil stack. @alvin + */ + popStencil() + { + this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); + + const gl = this.renderer.gl; + const graphics = this.stencilMaskStack.pop(); + + if (this.stencilMaskStack.length === 0) + { + // the stack is empty! + gl.disable(gl.STENCIL_TEST); + gl.clear(gl.STENCIL_BUFFER_BIT); + gl.clearStencil(0); + } + else + { + // Decrement the refference stencil value where the popped mask overlaps with the other ones + gl.colorMask(false, false, false, false); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.DECR); + this.renderer.plugins.graphics.render(graphics); + + this._useCurrent(); + } + } + + /** + * Setup renderer to use the current stencil data. + */ + _useCurrent() + { + const gl = this.renderer.gl; + + gl.colorMask(true, true, true, true); + gl.stencilFunc(gl.EQUAL, this.stencilMaskStack.length, this._getBitwiseMask()); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP); + } + + /** + * Fill 1s equal to the number of acitve stencil masks. + * + * @return {number} The bitwise mask. + */ + _getBitwiseMask() + { + return (1 << this.stencilMaskStack.length) - 1; + } + + /** + * Destroys the mask stack. + * + */ + destroy() + { + super.destroy(this); + + this.stencilMaskStack = null; + } +} diff --git a/packages/core/src/projection/ProjectionSystem.js b/packages/core/src/projection/ProjectionSystem.js new file mode 100644 index 0000000..6e63b73 --- /dev/null +++ b/packages/core/src/projection/ProjectionSystem.js @@ -0,0 +1,73 @@ +import System from '../System'; +import { Matrix } from '@pixi/math'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class ProjectionSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.projectionMatrix = new Matrix(); + } + + update(destinationFrame, sourceFrame, resolution, root) + { + this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; + this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; + + this.calculateProjection(this.destinationFrame, this.sourceFrame, resolution, root); + + this.renderer.globalUniforms.uniforms.projectionMatrix = this.projectionMatrix; + this.renderer.globalUniforms.update(); + } + + /** + * Updates the projection matrix based on a projection frame (which is a rectangle) + * + * @param {Rectangle} destinationFrame - The destination frame. + * @param {Rectangle} sourceFrame - The source frame. + */ + calculateProjection(destinationFrame, sourceFrame, resolution, root) + { + const pm = this.projectionMatrix; + + // I don't think we will need this line.. + // pm.identity(); + + if (!root) + { + pm.a = (1 / destinationFrame.width * 2) * resolution; + pm.d = (1 / destinationFrame.height * 2) * resolution; + + pm.tx = -1 - (sourceFrame.x * pm.a); + pm.ty = -1 - (sourceFrame.y * pm.d); + } + else + { + pm.a = (1 / destinationFrame.width * 2) * resolution; + pm.d = (-1 / destinationFrame.height * 2) * resolution; + + pm.tx = -1 - (sourceFrame.x * pm.a); + pm.ty = 1 - (sourceFrame.y * pm.d); + } + } + + /** + * Sets the transform of the active render target to the given matrix + * + * @param {PIXI.Matrix} matrix - The transformation matrix + */ + setTransform()// matrix) + { + // this._activeRenderTarget.transform = matrix; + } +} diff --git a/packages/core/src/renderTexture/BaseRenderTexture.js b/packages/core/src/renderTexture/BaseRenderTexture.js new file mode 100644 index 0000000..88eb83b --- /dev/null +++ b/packages/core/src/renderTexture/BaseRenderTexture.js @@ -0,0 +1,137 @@ +import BaseTexture from '../textures/BaseTexture'; +import FrameBuffer from '../framebuffer/FrameBuffer'; + +/** + * A BaseRenderTexture is a special texture that allows any PixiJS display object to be rendered to it. + * + * __Hint__: All DisplayObjects (i.e. Sprites) that render to a BaseRenderTexture should be preloaded + * otherwise black rectangles will be drawn instead. + * + * A BaseRenderTexture takes a snapshot of any Display Object given to its render method. The position + * and rotation of the given Display Objects is ignored. For example: + * + * ```js + * let renderer = PIXI.autoDetectRenderer(1024, 1024, { view: canvas, ratio: 1 }); + * let baseRenderTexture = new PIXI.BaseRenderTexture(renderer, 800, 600); + * let sprite = PIXI.Sprite.fromImage("spinObj_01.png"); + * + * sprite.position.x = 800/2; + * sprite.position.y = 600/2; + * sprite.anchor.x = 0.5; + * sprite.anchor.y = 0.5; + * + * baseRenderTexture.render(sprite); + * ``` + * + * The Sprite in this case will be rendered using its local transform. To render this sprite at 0,0 + * you can clear the transform + * + * ```js + * + * sprite.setTransform() + * + * let baseRenderTexture = new PIXI.BaseRenderTexture(100, 100); + * let renderTexture = new PIXI.RenderTexture(baseRenderTexture); + * + * renderer.render(sprite, renderTexture); // Renders to center of RenderTexture + * ``` + * + * @class + * @extends PIXI.BaseTexture + * @memberof PIXI + */ +export default class BaseRenderTexture extends BaseTexture +{ + /** + * @param {object} [options] + * @param {number} [options.width=100] - The width of the base render texture + * @param {number} [options.height=100] - The height of the base render texture + * @param {PIXI.SCALE_MODES} [options.scaleMode] - See {@link PIXI.SCALE_MODES} for possible values + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the texture being generated + */ + constructor(options) + { + if (typeof options === 'number') + { + /* eslint-disable prefer-rest-params */ + // Backward compatibility of signature + const width = arguments[0]; + const height = arguments[1]; + const scaleMode = arguments[2]; + const resolution = arguments[3]; + + options = { width, height, scaleMode, resolution }; + /* eslint-enable prefer-rest-params */ + } + + super(null, options); + + const { width, height } = options || {}; + + // Set defaults + this.mipmap = false; + this.width = Math.ceil(width) || 100; + this.height = Math.ceil(height) || 100; + this.valid = true; + + /** + * A map of renderer IDs to webgl renderTargets + * + * @private + * @member {object} + */ + // this._glRenderTargets = {}; + + /** + * A reference to the canvas render target (we only need one as this can be shared across renderers) + * + * @private + * @member {object} + */ + this._canvasRenderTarget = null; + + this.clearColor = [0, 0, 0, 0]; + + this.frameBuffer = new FrameBuffer(width * this.resolution, height * this.resolution) + .addColorTexture(0, this); + + // TODO - could this be added the systems? + + /** + * The data structure for the stencil masks + * + * @member {PIXI.Graphics[]} + */ + this.stencilMaskStack = []; + + /** + * The data structure for the filters + * + * @member {PIXI.Graphics[]} + */ + this.filterStack = [{}]; + } + + /** + * Resizes the BaseRenderTexture. + * + * @param {number} width - The width to resize to. + * @param {number} height - The height to resize to. + */ + resize(width, height) + { + width = Math.ceil(width); + height = Math.ceil(height); + this.frameBuffer.resize(width * this.resolution, height * this.resolution); + } + + /** + * Destroys this texture + * + */ + destroy() + { + super.destroy(true); + this.renderer = null; + } +} diff --git a/packages/core/src/renderTexture/RenderTexture.js b/packages/core/src/renderTexture/RenderTexture.js new file mode 100644 index 0000000..f1ecf1f --- /dev/null +++ b/packages/core/src/renderTexture/RenderTexture.js @@ -0,0 +1,153 @@ +import BaseRenderTexture from './BaseRenderTexture'; +import Texture from '../textures/Texture'; + +/** + * A RenderTexture is a special texture that allows any PixiJS display object to be rendered to it. + * + * __Hint__: All DisplayObjects (i.e. Sprites) that render to a RenderTexture should be preloaded + * otherwise black rectangles will be drawn instead. + * + * A RenderTexture takes a snapshot of any Display Object given to its render method. For example: + * + * ```js + * let renderer = PIXI.autoDetectRenderer(1024, 1024, { view: canvas, ratio: 1 }); + * let renderTexture = PIXI.RenderTexture.create(800, 600); + * let sprite = PIXI.Sprite.fromImage("spinObj_01.png"); + * + * sprite.position.x = 800/2; + * sprite.position.y = 600/2; + * sprite.anchor.x = 0.5; + * sprite.anchor.y = 0.5; + * + * renderer.render(sprite, renderTexture); + * ``` + * + * The Sprite in this case will be rendered using its local transform. To render this sprite at 0,0 + * you can clear the transform + * + * ```js + * + * sprite.setTransform() + * + * let renderTexture = new PIXI.RenderTexture.create(100, 100); + * + * renderer.render(sprite, renderTexture); // Renders to center of RenderTexture + * ``` + * + * @class + * @extends PIXI.Texture + * @memberof PIXI + */ +export default class RenderTexture extends Texture +{ + /** + * @param {PIXI.BaseRenderTexture} baseRenderTexture - The renderer used for this RenderTexture + * @param {PIXI.Rectangle} [frame] - The rectangle frame of the texture to show + */ + constructor(baseRenderTexture, frame) + { + // support for legacy.. + let _legacyRenderer = null; + + if (!(baseRenderTexture instanceof BaseRenderTexture)) + { + /* eslint-disable prefer-rest-params, no-console */ + const width = arguments[1]; + const height = arguments[2]; + const scaleMode = arguments[3]; + const resolution = arguments[4]; + + // we have an old render texture.. + console.warn(`Please use RenderTexture.create(${width}, ${height}) instead of the ctor directly.`); + _legacyRenderer = arguments[0]; + /* eslint-enable prefer-rest-params, no-console */ + + frame = null; + baseRenderTexture = new BaseRenderTexture({ + width, + height, + scaleMode, + resolution, + }); + } + + /** + * The base texture object that this texture uses + * + * @member {BaseTexture} + */ + super(baseRenderTexture, frame); + + this.legacyRenderer = _legacyRenderer; + + /** + * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. + * + * @member {boolean} + */ + this.valid = true; + + /** + * FilterSystem temporary storage + * @private + * @member {PIXI.Rectangle} + */ + this.filterFrame = null; + + this._updateUvs(); + } + + /** + * Resizes the RenderTexture. + * + * @param {number} width - The width to resize to. + * @param {number} height - The height to resize to. + * @param {boolean} [resizeBaseTexture=true] - Should the baseTexture.width and height values be resized as well? + */ + resize(width, height, resizeBaseTexture = true) + { + width = Math.ceil(width); + height = Math.ceil(height); + + // TODO - could be not required.. + this.valid = (width > 0 && height > 0); + + this._frame.width = this.orig.width = width; + this._frame.height = this.orig.height = height; + + if (resizeBaseTexture) + { + this.baseTexture.resize(width, height); + } + + this._updateUvs(); + } + + /** + * A short hand way of creating a render texture. + * + * @param {object} [options] - Options + * @param {number} [options.width=100] - The width of the render texture + * @param {number} [options.height=100] - The height of the render texture + * @param {number} [options.scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the texture being generated + * @return {PIXI.RenderTexture} The new render texture + */ + static create(options) + { + // fallback, old-style: create(width, height, scaleMode, resolution) + if (typeof options === 'number') + { + /* eslint-disable prefer-rest-params */ + options = { + width: options, + height: arguments[1], + scaleMode: arguments[2], + resolution: arguments[3], + }; + /* eslint-enable prefer-rest-params */ + } + + return new RenderTexture(new BaseRenderTexture(options)); + } +} diff --git a/packages/core/src/renderTexture/RenderTextureSystem.js b/packages/core/src/renderTexture/RenderTextureSystem.js new file mode 100644 index 0000000..8d0ca61 --- /dev/null +++ b/packages/core/src/renderTexture/RenderTextureSystem.js @@ -0,0 +1,115 @@ +import System from '../System'; +import { Rectangle } from '@pixi/math'; + +const tempRect = new Rectangle(); + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class RenderTextureSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.clearColor = renderer._backgroundColorRgba; + + // TODO moe this property somewhere else! + this.defaultMaskStack = []; + this.defaultFilterStack = [{}]; + + // empty render texture? + this.renderTexture = null; + + this.destinationFrame = new Rectangle(); + } + + bind(renderTexture, sourceFrame, destinationFrame) + { + // TODO - do we want this?? + if (this.renderTexture === renderTexture) return; + this.renderTexture = renderTexture; + + const renderer = this.renderer; + + if (renderTexture) + { + const baseTexture = renderTexture.baseTexture; + + if (!destinationFrame) + { + tempRect.width = baseTexture.realWidth; + tempRect.height = baseTexture.realHeight; + + destinationFrame = tempRect; + } + + if (!sourceFrame) + { + sourceFrame = destinationFrame; + } + + this.renderer.framebuffer.bind(baseTexture.frameBuffer, destinationFrame); + + this.renderer.projection.update(destinationFrame, sourceFrame, baseTexture.resolution, false); + this.renderer.stencil.setMaskStack(baseTexture.stencilMaskStack); + } + else + { + // TODO these validation checks happen deeper down.. + // thing they can be avoided.. + if (!destinationFrame) + { + tempRect.width = renderer.width; + tempRect.height = renderer.height; + + destinationFrame = tempRect; + } + + if (!sourceFrame) + { + sourceFrame = destinationFrame; + } + + renderer.framebuffer.bind(null, destinationFrame); + + // TODO store this.. + this.renderer.projection.update(destinationFrame, sourceFrame, this.renderer.resolution, true); + this.renderer.stencil.setMaskStack(this.defaultMaskStack); + } + + this.destinationFrame.copyFrom(destinationFrame); + } + + /** + * Erases the render texture and fills the drawing area with a colour + * + * @param {number} [clearColor] - The colour + * @return {PIXI.Renderer} Returns itself. + */ + clear(clearColor) + { + if (this.renderTexture) + { + clearColor = clearColor || this.renderTexture.baseTexture.clearColor; + } + else + { + clearColor = clearColor || this.clearColor; + } + + this.renderer.framebuffer.clear(clearColor[0], clearColor[1], clearColor[2], clearColor[3]); + } + + resize()// screenWidth, screenHeight) + { + // resize the root only! + this.bind(null); + } +} diff --git a/packages/core/src/renderers/AbstractRenderer.js b/packages/core/src/renderers/AbstractRenderer.js deleted file mode 100644 index 928670f..0000000 --- a/packages/core/src/renderers/AbstractRenderer.js +++ /dev/null @@ -1,353 +0,0 @@ -import { hex2string, hex2rgb } from '@pixi/utils'; -import { Matrix, Rectangle } from '@pixi/math'; -import { RENDERER_TYPE } from '@pixi/constants'; -import { settings } from '@pixi/settings'; -import { Container } from '@pixi/display'; -import RenderTexture from '../textures/RenderTexture'; -import EventEmitter from 'eventemitter3'; - -const tempMatrix = new Matrix(); - -/** - * 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 EventEmitter - * @memberof PIXI - */ -export default class AbstractRenderer extends EventEmitter -{ - // eslint-disable-next-line valid-jsdoc - /** - * @param {string} system - The name of the system this renderer is for. - * @param {object} [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.transparent=false] - If the render view is transparent, default false - * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false - * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) - * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. The - * resolution of the renderer retina would be 2. - * @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 {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when rendering, - * stopping pixel interpolation. - */ - constructor(system, options, arg2, arg3) - { - super(); - - // Support for constructor(system, screenWidth, screenHeight, options) - if (typeof options === 'number') - { - options = Object.assign({ - width: options, - height: arg2 || settings.RENDER_OPTIONS.height, - }, arg3); - } - - // 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 = RENDERER_TYPE.UNKNOWN; - - /** - * Measurements of the screen. (0, 0, screenWidth, screenHeight) - * - * Its safe to use as filterArea or hitArea for 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 1 - */ - this.resolution = options.resolution || settings.RESOLUTION; - - /** - * Whether the render view is transparent - * - * @member {boolean} - */ - this.transparent = options.transparent; - - /** - * Whether css dimensions of canvas view should be resized to screen dimensions automatically - * - * @member {boolean} - */ - this.autoResize = options.autoResize || false; - - /** - * Tracks the blend modes useful for this renderer. - * - * @member {object} - */ - this.blendModes = null; - - /** - * 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; - - /** - * If true PixiJS will Math.floor() x/y values when rendering, stopping pixel interpolation. - * Handy for crisp pixel art and speed on legacy devices. - * - * @member {boolean} - */ - this.roundPixels = options.roundPixels; - - /** - * The background color as a number. - * - * @member {number} - * @private - */ - this._backgroundColor = 0x000000; - - /** - * The background color as an [R, G, B] array. - * - * @member {number[]} - * @private - */ - this._backgroundColorRgba = [0, 0, 0, 0]; - - /** - * The background color as a string. - * - * @member {string} - * @private - */ - this._backgroundColorString = '#000000'; - - this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter - - /** - * This temporary display object used as the parent of the currently being rendered item - * - * @member {PIXI.DisplayObject} - * @private - */ - this._tempDisplayObjectParent = new Container(); - - /** - * The last root object that the renderer tried to render. - * - * @member {PIXI.DisplayObject} - * @private - */ - this._lastObjectRendered = this._tempDisplayObjectParent; - - /** - * Collection of plugins. - * @readonly - * @member {object} - */ - this.plugins = {}; - } - - /** - * Initialize the plugins. - * - * @protected - * @param {object} staticMap - The dictionary of staticly saved plugins. - */ - initPlugins(staticMap) - { - 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() - { - return this.view.width; - } - - /** - * Same as view.height, actual number of pixels in the canvas by vertical - * - * @member {number} - * @readonly - * @default 600 - */ - get height() - { - return this.view.height; - } - - /** - * Resizes the screen and canvas to the specified width and height - * Canvas dimensions are multiplied by resolution - * - * @param {number} screenWidth - the new width of the screen - * @param {number} screenHeight - the new height of the screen - */ - resize(screenWidth, screenHeight) - { - this.screen.width = screenWidth; - this.screen.height = screenHeight; - - this.view.width = screenWidth * this.resolution; - this.view.height = screenHeight * this.resolution; - - if (this.autoResize) - { - this.view.style.width = `${screenWidth}px`; - this.view.style.height = `${screenHeight}px`; - } - } - - /** - * 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 {PIXI.DisplayObject} displayObject - The displayObject the object will be generated from - * @param {number} scaleMode - Should be one of the scaleMode consts - * @param {number} resolution - The resolution / device pixel ratio of the texture being generated - * @param {PIXI.Rectangle} [region] - The region of the displayObject, that shall be rendered, - * if no region is specified, defaults to the local bounds of the displayObject. - * @return {PIXI.Texture} a texture of the graphics object - */ - generateTexture(displayObject, scaleMode, resolution, region) - { - region = region || displayObject.getLocalBounds(); - - // 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(region.width | 0, region.height | 0, scaleMode, resolution); - - tempMatrix.tx = -region.x; - tempMatrix.ty = -region.y; - - this.render(displayObject, renderTexture, false, tempMatrix, true); - - return renderTexture; - } - - /** - * Removes everything from the renderer and optionally removes the Canvas DOM element. - * - * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. - */ - destroy(removeView) - { - 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); - } - - this.plugins = null; - - this.type = RENDERER_TYPE.UNKNOWN; - - this.view = null; - - this.screen = null; - - this.resolution = 0; - - this.transparent = false; - - this.autoResize = false; - - this.blendModes = null; - - this.options = null; - - this.preserveDrawingBuffer = false; - this.clearBeforeRender = false; - - this.roundPixels = false; - - this._backgroundColor = 0; - this._backgroundColorRgba = null; - this._backgroundColorString = null; - - this._tempDisplayObjectParent = null; - this._lastObjectRendered = null; - } - - /** - * The background color to fill if not transparent - * - * @member {number} - */ - get backgroundColor() - { - return this._backgroundColor; - } - - set backgroundColor(value) // eslint-disable-line require-jsdoc - { - this._backgroundColor = value; - this._backgroundColorString = hex2string(value); - hex2rgb(value, this._backgroundColorRgba); - } -} diff --git a/packages/core/src/renderers/Renderer.js b/packages/core/src/renderers/Renderer.js deleted file mode 100644 index a2020b1..0000000 --- a/packages/core/src/renderers/Renderer.js +++ /dev/null @@ -1,329 +0,0 @@ -import AbstractRenderer from './AbstractRenderer'; -import { sayHello } from '@pixi/utils'; -import MaskSystem from './systems/MaskSystem'; -import StencilSystem from './systems/StencilSystem'; -import FilterSystem from './systems/filter/FilterSystem'; -import FramebufferSystem from './systems/FramebufferSystem'; -import RenderTextureSystem from './systems/RenderTextureSystem'; -import TextureSystem from './systems/textures/TextureSystem'; -import ProjectionSystem from './systems/ProjectionSystem'; -import StateSystem from './systems/StateSystem'; -import GeometrySystem from './systems/geometry/GeometrySystem'; -import ShaderSystem from './systems/shader/ShaderSystem'; -import ContextSystem from './systems/ContextSystem'; -import BatchSystem from './systems/BatchSystem'; -import TextureGCSystem from './systems/textures/TextureGCSystem'; -import { RENDERER_TYPE } from '@pixi/constants'; -import UniformGroup from '../shader/UniformGroup'; -import { Matrix } from '@pixi/math'; -import Runner from 'mini-runner'; - -/** - * 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 Render works by automatically managing webGLBatchs. - * 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 :) - * - * @class - * @memberof PIXI - * @extends PIXI.AbstractRenderer - */ -export default class Renderer extends AbstractRenderer -{ - // eslint-disable-next-line valid-jsdoc - /** - * - * @param {object} [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.transparent=false] - If the render view is transparent, default false - * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false - * @param {boolean} [options.antialias=false] - sets antialias. If not available natively then FXAA - * antialiasing is used - * @param {boolean} [options.forceFXAA=false] - forces FXAA antialiasing to be used over native. - * FXAA is faster, but may not always look as great - * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. - * The resolution of the renderer retina would be 2. - * @param {boolean} [options.clearBeforeRender=true] - This sets if the renderer will clear - * the canvas or not before the new render pass. If you wish to set this to false, you *must* set - * preserveDrawingBuffer to `true`. - * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, - * enable this if you need to call toDataUrl on the webgl context. - * @param {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when - * rendering, stopping pixel interpolation. - * @param {number} [options.backgroundColor=0x000000] - The background color of the rendered area - * (shown if not transparent). - * @param {boolean} [options.legacy=false] - If true PixiJS will aim to ensure compatibility - * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. - * @param {string} [options.powerPreference] - Parameter passed to webgl context, set to "high-performance" - * for devices with dual graphics card - */ - constructor(options, arg2, arg3) - { - super('WebGL', options, arg2, arg3); - - /** - * The type of this renderer as a standardised const - * - * @member {number} - * @see PIXI.RENDERER_TYPE - */ - this.type = RENDERER_TYPE.WEBGL; - - // this will be set by the contextSystem (this.context) - this.gl = null; - this.CONTEXT_UID = 0; - this.legacy = !!options.legacy; - - // TODO legacy! - - // runners! - this.runners = { - destroy: new Runner('destroy'), - contextChange: new Runner('contextChange', 1), - reset: new Runner('reset'), - update: new Runner('update'), - postrender: new Runner('postrender'), - prerender: new Runner('prerender'), - resize: new Runner('resize', 2), - }; - - 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(GeometrySystem, 'geometry') - .addSystem(FramebufferSystem, 'framebuffer') - .addSystem(StencilSystem, 'stencil') - .addSystem(ProjectionSystem, 'projection') - .addSystem(TextureGCSystem, 'textureGC') - .addSystem(FilterSystem, 'filter') - .addSystem(RenderTextureSystem, 'renderTexture') - .addSystem(BatchSystem, 'batch'); - - this.initPlugins(Renderer.__plugins); - - /** - * The options passed in to create a new webgl context. - * - * @member {object} - * @private - */ - if (options.context) - { - this.context.initFromContext(options.context); - } - else - { - this.context.initFromOptions({ - alpha: this.transparent, - antialias: options.antialias, - premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', - stencil: true, - 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); - } - - addSystem(_class, name) - { - if (!name) - { - name = _class.name; - } - - // TODO - read name from class.name.. - - /* - if(name.includes('System')) - { - name = name.replace('System', ''); - name = name.charAt(0).toLowerCase() + name.slice(1); - } - */ - - const system = new _class(this); - - if (this[name]) - { - throw new Error(`Whoops! ${name} is already a manger`); - } - - this[name] = system; - - for (const i in this.runners) - { - this.runners[i].add(system); - } - - return this; - - /** - * 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. - */ - } - - /** - * Renders the object to its webGL view - * - * @param {PIXI.DisplayObject} displayObject - the object to be rendered - * @param {PIXI.RenderTexture} renderTexture - The render texture to render to. - * @param {boolean} [clear] - Should the canvas be cleared before the new render - * @param {PIXI.Transform} [transform] - A transform to apply to the render texture before rendering. - * @param {boolean} [skipUpdateTransform] - Should we skip the update transform pass? - */ - render(displayObject, renderTexture, clear, transform, skipUpdateTransform) - { - // can be handy to know! - this.renderingToScreen = !renderTexture; - - this.runners.prerender.run(); - this.emit('prerender'); - - // 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.parent; - - displayObject.parent = this._tempDisplayObjectParent; - displayObject.updateTransform(); - displayObject.parent = 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.run(); - - this.emit('postrender'); - } - - /** - * Resizes the webGL view to the specified width and height. - * - * @param {number} screenWidth - the new width of the screen - * @param {number} screenHeight - the new height of the screen - */ - resize(screenWidth, screenHeight) - { - AbstractRenderer.prototype.resize.call(this, screenWidth, screenHeight); - - this.runners.resize.run(screenWidth, screenHeight); - } - - /** - * Resets the WebGL state so you can render things however you fancy! - * - * @return {PIXI.Renderer} Returns itself. - */ - reset() - { - this.runners.reset.run(); - - return this; - } - - clear() - { - this.framebuffer.bind(); - this.framebuffer.clear(); - } - - /** - * Removes everything from the renderer (event listeners, spritebatch, etc...) - * - * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. - * See: https://github.com/pixijs/pixi.js/issues/2233 - */ - destroy(removeView) - { - this.runners.destroy.run(); - - // call base destroy - super.destroy(removeView); - - // TODO nullify all the managers.. - this.gl = null; - } - - /** - * 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 - * builds and excluding plugins. - * @name PIXI.Renderer#plugins - * @type {object} - * @readonly - * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. - * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. - * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. - * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. - */ - - /** - * Adds a plugin to the renderer. - * - * @method - * @param {string} pluginName - The name of the plugin. - * @param {Function} ctor - The constructor function or class for the plugin. - */ - static registerPlugin(pluginName, ctor) - { - Renderer.__plugins = Renderer.__plugins || {}; - Renderer.__plugins[pluginName] = ctor; - } -} diff --git a/packages/core/src/renderers/State.js b/packages/core/src/renderers/State.js deleted file mode 100644 index fa3f221..0000000 --- a/packages/core/src/renderers/State.js +++ /dev/null @@ -1,163 +0,0 @@ -/* eslint-disable max-len */ - -const BLEND = 0; -const OFFSET = 1; -const CULLING = 2; -const DEPTH_TEST = 3; -const WINDING = 4; - -/** - * This is a webGL state. It is passed The WebGL StateManager. - * Each mesh renderered may require webGL to be in a different state. - * For example you may want different blend mode or to enable polygon offsets - * - * @class - * @memberof PIXI - */ -export default class State -{ - /** - * - */ - constructor() - { - this.data = 0; - - this.blendMode = 0; - this.polygonOffset = 0; - - this.blend = true; - // this.depthTest = true; - } - - /** - * Activates blending of the computed fragment color values - * - * @member {boolean} - */ - get blend() - { - return !!(this.data & (1 << BLEND)); - } - - set blend(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << BLEND)) !== value) - { - this.data ^= (1 << BLEND); - } - } - - /** - * Activates adding an offset to depth values of polygon's fragments - * - * @member {boolean} - * @default false - */ - get offsets() - { - return !!(this.data & (1 << OFFSET)); - } - - set offsets(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << OFFSET)) !== value) - { - this.data ^= (1 << OFFSET); - } - } - - /** - * Activates culling of polygons. - * - * @member {boolean} - * @default false - */ - get culling() - { - return !!(this.data & (1 << CULLING)); - } - - set culling(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << CULLING)) !== value) - { - this.data ^= (1 << CULLING); - } - } - - /** - * Activates depth comparisons and updates to the depth buffer. - * - * @member {boolean} - * @default false - */ - get depthTest() - { - return !!(this.data & (1 << DEPTH_TEST)); - } - - set depthTest(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << DEPTH_TEST)) !== value) - { - this.data ^= (1 << DEPTH_TEST); - } - } - - /** - * Specifies whether or not front or back-facing polygons can be culled. - * @member {boolean} - * @default false - */ - get clockwiseFrontFace() - { - return !!(this.data & (1 << WINDING)); - } - - set clockwiseFrontFace(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << WINDING)) !== value) - { - this.data ^= (1 << WINDING); - } - } - - /** - * The blend mode to be applied when this state is set. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. - * Setting this mode to anything other than NO_BLEND will automatically switch blending on. - * - * @member {boolean} - * @default PIXI.BLEND_MODES.NORMAL - * @see PIXI.BLEND_MODES - */ - get blendMode() - { - return this._blendMode; - } - - set blendMode(value) // eslint-disable-line require-jsdoc - { - // 17 is NO BLEND - this.blend = (value !== 17); - this._blendMode = value; - } - - /** - * The polygon offset. Setting this property to anything other than 0 will automatically enable poygon offset fill. - * - * @member {number} - * @default 0 - */ - get polygonOffset() - { - return this._polygonOffset; - } - - set polygonOffset(value) // eslint-disable-line require-jsdoc - { - this.offsets = !!value; - this._polygonOffset = value; - } -} - diff --git a/packages/core/src/renderers/filters/Filter.js b/packages/core/src/renderers/filters/Filter.js deleted file mode 100644 index 2073b8d..0000000 --- a/packages/core/src/renderers/filters/Filter.js +++ /dev/null @@ -1,140 +0,0 @@ -import Shader from '../../shader/Shader'; -import Program from '../../shader/Program'; -import State from '../State'; -import { settings } from '@pixi/settings'; -// import extractUniformsFromSrc from './extractUniformsFromSrc'; -import defaultVertex from './defaultFilter.vert'; -import defaultFragment from './defaultFilter.frag'; - -// let math = require('../../math'); -/** - * @class - * @memberof PIXI - * @extends PIXI.Shader - */ -export default class Filter extends Shader -{ - /** - * @param {string} [vertexSrc] - The source of the vertex shader. - * @param {string} [fragmentSrc] - The source of the fragment shader. - * @param {object} [uniforms] - Custom uniforms to use to augment the built-in ones. - */ - constructor(vertexSrc, fragmentSrc, uniforms) - { - const program = Program.from(vertexSrc, fragmentSrc); - - super(program, uniforms); - - /** - * The padding of the filter. Some filters require extra space to breath such as a blur. - * Increasing this will add extra width and height to the bounds of the object that the - * filter is applied to. - * - * @member {number} - */ - this.padding = 0; - - /** - * The resolution of the filter. Setting this to be lower will lower the quality but - * increase the performance of the filter. - * - * @member {number} - */ - this.resolution = settings.RESOLUTION; - - /** - * If enabled is true the filter is applied, if false it will not. - * - * @member {boolean} - */ - this.enabled = true; - - /** - * If enabled, PixiJS will fit the filter area into boundaries for better performance. - * Switch it off if it does not work for specific shader. - * - * @member {boolean} - */ - this.autoFit = true; - - /** - * Legacy filters use position and uvs from attributes - * @member {boolean} - * @readonly - */ - this.legacy = !!this.program.attributeData.aTextureCoord; - - /** - * the webGL state the filter requires to render - * @member {PIXI.State} - */ - this.state = new State(); - } - - /** - * Applies the filter - * - * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from - * @param {PIXI.RenderTarget} input - The input render target. - * @param {PIXI.RenderTarget} output - The target to output to. - * @param {boolean} clear - Should the output be cleared before rendering to it - * @param {object} [currentState] - It's current state of filter. - * There are some useful properties in the currentState : - * target, filters, sourceFrame, destinationFrame, renderTarget, resolution - */ - apply(filterManager, input, output, clear, currentState, derp) // eslint-disable-line no-unused-vars - { - // do as you please! - - filterManager.applyFilter(this, input, output, clear, currentState, derp); - - // or just do a regular render.. - } - - /** - * Sets the blendmode of the filter - * - * @member {number} - * @default PIXI.BLEND_MODES.NORMAL - */ - get blendMode() - { - return this.state.blendMode; - } - - set blendMode(value) // eslint-disable-line require-jsdoc - { - this.state.blendMode = value; - } - - /** - * The default vertex shader source - * - * @static - * @constant - */ - static get defaultVertexSrc() - { - return defaultVertex; - } - - /** - * The default fragment shader source - * - * @static - * @constant - */ - static get defaultFragmentSrc() - { - return defaultFragment; - } -} - -/** - * Used for caching shader IDs - * - * @static - * @private - */ -Filter.SOURCE_KEY_MAP = {}; - diff --git a/packages/core/src/renderers/filters/defaultFilter.frag b/packages/core/src/renderers/filters/defaultFilter.frag deleted file mode 100644 index a14c1be..0000000 --- a/packages/core/src/renderers/filters/defaultFilter.frag +++ /dev/null @@ -1,22 +0,0 @@ -varying vec2 vTextureCoord; -varying vec2 vFilterCoord; - -uniform sampler2D uSampler; -uniform sampler2D filterSampler; - -void main(void){ - vec4 masky = texture2D(filterSampler, vFilterCoord); - vec4 sample = texture2D(uSampler, vTextureCoord); - vec4 color; - if(mod(vFilterCoord.x, 1.0) > 0.5) - { - color = vec4(1.0, 0.0, 0.0, 1.0); - } - else - { - color = vec4(0.0, 1.0, 0.0, 1.0); - } - // gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0); - gl_FragColor = mix(sample, masky, 0.5); - gl_FragColor *= sample.a; -} diff --git a/packages/core/src/renderers/filters/defaultFilter.vert b/packages/core/src/renderers/filters/defaultFilter.vert deleted file mode 100644 index e1febca..0000000 --- a/packages/core/src/renderers/filters/defaultFilter.vert +++ /dev/null @@ -1,14 +0,0 @@ -attribute vec2 aVertexPosition; -attribute vec2 aTextureCoord; - -uniform mat3 projectionMatrix; -uniform mat3 filterMatrix; - -varying vec2 vTextureCoord; -varying vec2 vFilterCoord; - -void main(void){ - gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); - vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy; - vTextureCoord = aTextureCoord ; -} diff --git a/packages/core/src/renderers/filters/filterTransforms.js b/packages/core/src/renderers/filters/filterTransforms.js deleted file mode 100644 index 87a3cd6..0000000 --- a/packages/core/src/renderers/filters/filterTransforms.js +++ /dev/null @@ -1,50 +0,0 @@ -import { Matrix } from '@pixi/math'; - -/** - * Calculates the mapped matrix - * @param filterArea {Rectangle} The filter area - * @param sprite {Sprite} the target sprite - * @param outputMatrix {Matrix} @alvin - * @private - */ -// this returns a matrix that will normalise map filter cords in the filter to screen space -export function calculateScreenSpaceMatrix(outputMatrix, filterArea, textureSize) -{ - // TODO unwrap? - const mappedMatrix = outputMatrix.identity(); - - mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); - - mappedMatrix.scale(textureSize.width, textureSize.height); - - return mappedMatrix; -} - -export function calculateNormalizedScreenSpaceMatrix(outputMatrix, filterArea, textureSize) -{ - const mappedMatrix = outputMatrix.identity(); - - mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); - - const translateScaleX = (textureSize.width / filterArea.width); - const translateScaleY = (textureSize.height / filterArea.height); - - mappedMatrix.scale(translateScaleX, translateScaleY); - - return mappedMatrix; -} - -// this will map the filter coord so that a texture can be used based on the transform of a sprite -export function calculateSpriteMatrix(outputMatrix, filterArea, textureSize, sprite) -{ - const orig = sprite._texture.orig; - const mappedMatrix = outputMatrix.set(textureSize.width, 0, 0, textureSize.height, filterArea.x, filterArea.y); - const worldTransform = sprite.worldTransform.copyTo(Matrix.TEMP_MATRIX); - - worldTransform.invert(); - mappedMatrix.prepend(worldTransform); - mappedMatrix.scale(1.0 / orig.width, 1.0 / orig.height); - mappedMatrix.translate(sprite.anchor.x, sprite.anchor.y); - - return mappedMatrix; -} diff --git a/packages/core/src/renderers/filters/spriteMask/SpriteMaskFilter.js b/packages/core/src/renderers/filters/spriteMask/SpriteMaskFilter.js deleted file mode 100644 index d5a91a4..0000000 --- a/packages/core/src/renderers/filters/spriteMask/SpriteMaskFilter.js +++ /dev/null @@ -1,63 +0,0 @@ -import Filter from '../Filter'; -import { Matrix } from '@pixi/math'; -import vertex from './spriteMaskFilter.vert'; -import fragment from './spriteMaskFilter.frag'; -import { default as TextureMatrix } from '../../../textures/TextureMatrix'; - -/** - * The SpriteMaskFilter class - * - * @class - * @extends PIXI.Filter - * @memberof PIXI - */ -export default class SpriteMaskFilter extends Filter -{ - /** - * @param {PIXI.Sprite} sprite - the target sprite - */ - constructor(sprite) - { - const maskMatrix = new Matrix(); - - super(vertex, fragment); - - sprite.renderable = false; - - this.maskSprite = sprite; - this.maskMatrix = maskMatrix; - } - - /** - * Applies the filter - * - * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from - * @param {PIXI.RenderTarget} input - The input render target. - * @param {PIXI.RenderTarget} output - The target to output to. - */ - apply(filterManager, input, output) - { - const maskSprite = this.maskSprite; - const tex = this.maskSprite.texture; - - if (!tex.valid) - { - return; - } - if (!tex.transform) - { - // margin = 0.0, let it bleed a bit, shader code becomes easier - // assuming that atlas textures were made with 1-pixel padding - tex.transform = new TextureMatrix(tex, 0.0); - } - tex.transform.update(); - - this.uniforms.mask = tex; - this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite) - .prepend(tex.transform.mapCoord); - this.uniforms.alpha = maskSprite.worldAlpha; - this.uniforms.maskClamp = tex.transform.uClampFrame; - - filterManager.applyFilter(this, input, output); - } -} diff --git a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.frag b/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.frag deleted file mode 100644 index 0e4aef8..0000000 --- a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.frag +++ /dev/null @@ -1,23 +0,0 @@ -varying vec2 vMaskCoord; -varying vec2 vTextureCoord; - -uniform sampler2D uSampler; -uniform sampler2D mask; -uniform float alpha; -uniform vec4 maskClamp; - -void main(void) -{ - float clip = step(3.5, - step(maskClamp.x, vMaskCoord.x) + - step(maskClamp.y, vMaskCoord.y) + - step(vMaskCoord.x, maskClamp.z) + - step(vMaskCoord.y, maskClamp.w)); - - vec4 original = texture2D(uSampler, vTextureCoord); - vec4 masky = texture2D(mask, vMaskCoord); - - original *= (masky.r * masky.a * alpha * clip); - - gl_FragColor = original; -} diff --git a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.vert b/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.vert deleted file mode 100644 index d23a30c..0000000 --- a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.vert +++ /dev/null @@ -1,16 +0,0 @@ -attribute vec2 aVertexPosition; -attribute vec2 aTextureCoord; - -uniform mat3 projectionMatrix; -uniform mat3 otherMatrix; - -varying vec2 vMaskCoord; -varying vec2 vTextureCoord; - -void main(void) -{ - gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); - - vTextureCoord = aTextureCoord; - vMaskCoord = ( otherMatrix * vec3( aTextureCoord, 1.0) ).xy; -} diff --git a/packages/core/src/renderers/systems/BatchSystem.js b/packages/core/src/renderers/systems/BatchSystem.js deleted file mode 100644 index 70e9518..0000000 --- a/packages/core/src/renderers/systems/BatchSystem.js +++ /dev/null @@ -1,68 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import ObjectRenderer from '../utils/ObjectRenderer'; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ - -export default class BatchSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this manager works for. - */ - constructor(renderer) - { - super(renderer); - - /** - * An empty renderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.emptyRenderer = new ObjectRenderer(renderer); - - /** - * The currently active ObjectRenderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.currentRenderer = this.emptyRenderer; - } - - /** - * Changes the current renderer to the one given in parameter - * - * @param {PIXI.ObjectRenderer} objectRenderer - The object renderer to use. - */ - setObjectRenderer(objectRenderer) - { - if (this.currentRenderer === objectRenderer) - { - return; - } - - this.currentRenderer.stop(); - this.currentRenderer = objectRenderer; - - this.renderer.state.setState(objectRenderer.state); - - this.currentRenderer.start(); - } - - /** - * This should be called if you wish to do some custom rendering - * It will basically render anything that may be batched up such as sprites - * - */ - flush() - { - this.setObjectRenderer(this.emptyRenderer); - } - - reset() - { - this.setObjectRenderer(this.emptyRenderer); - } -} diff --git a/packages/core/src/renderers/systems/ContextSystem.js b/packages/core/src/renderers/systems/ContextSystem.js deleted file mode 100644 index 43bf827..0000000 --- a/packages/core/src/renderers/systems/ContextSystem.js +++ /dev/null @@ -1,186 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { settings } from '@pixi/settings'; - -let CONTEXT_UID = 0; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class ContextSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this.webGLVersion = 1; - - this.handleContextLost = this.handleContextLost.bind(this); - this.handleContextRestored = this.handleContextRestored.bind(this); - - this.extensions = {}; - - renderer.view.addEventListener('webglcontextlost', this.handleContextLost, false); - renderer.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); - } - - get isLost() - { - return (!this.gl || this.gl.isContextLost()); - } - - contextChange(gl) - { - this.gl = gl; - - // restore a context if it was previously lost - if (gl.isContextLost() && gl.getExtension('WEBGL_lose_context')) - { - gl.getExtension('WEBGL_lose_context').restoreContext(); - } - } - - initFromContext(gl) - { - this.gl = gl; - this.validateContext(gl); - this.renderer.gl = gl; - this.renderer.CONTEXT_UID = CONTEXT_UID++; - this.renderer.runners.contextChange.run(gl); - } - - initFromOptions(options) - { - const gl = this.createContext(this.renderer.view, options); - - this.initFromContext(gl); - } - - /** - * Helper class to create a webGL Context - * - * @class - * @memberof PIXI.glCore - * @param canvas {HTMLCanvasElement} the canvas element that we will get the context from - * @param options {Object} An options object that gets passed in to the canvas element containing the context attributes, - * see https://developer.mozilla.org/en/docs/Web/API/HTMLCanvasElement/getContext for the options available - * @return {WebGLRenderingContext} the WebGL context - */ - createContext(canvas, options) - { - let gl; - - if (settings.PREFER_WEBGL_2) - { - gl = canvas.getContext('webgl2', options); - } - - if (gl) - { - this.webGLVersion = 2; - } - else - { - this.webGLVersion = 1; - - gl = canvas.getContext('webgl', options) - || canvas.getContext('experimental-webgl', options); - - if (!gl) - { - // fail, not able to get a context - throw new Error('This browser does not support webGL. Try using the canvas renderer'); - } - } - - this.gl = gl; - - this.getExtensions(); - - return gl; - } - - getExtensions() - { - // time to set up default etensions that pixi uses.. - const gl = this.gl; - const extensions = this.extensions; - - if (this.webGLVersion === 1) - { - extensions.drawBuffers = gl.getExtension('WEBGL_draw_buffers'); - extensions.depthTexture = gl.getExtension('WEBKIT_WEBGL_depth_texture'); - extensions.floatTexture = gl.getExtension('OES_texture_float'); - extensions.loseContext = gl.getExtension('WEBGL_lose_context'); - - extensions.vertexArrayObject = gl.getExtension('OES_vertex_array_object') - || gl.getExtension('MOZ_OES_vertex_array_object') - || gl.getExtension('WEBKIT_OES_vertex_array_object'); - } - - // we don't use any specific WebGL 2 ones yet! - } - - /** - * Handles a lost webgl context - * - * @private - * @param {WebGLContextEvent} event - The context lost event. - */ - handleContextLost(event) - { - event.preventDefault(); - } - - /** - * Handles a restored webgl context - * - * @private - */ - handleContextRestored() - { - this.renderer.runners.contextChange.run(this.gl); - } - - destroy() - { - const view = this.renderer.view; - - // remove listeners - view.removeEventListener('webglcontextlost', this.handleContextLost); - view.removeEventListener('webglcontextrestored', this.handleContextRestored); - - this.gl.useProgram(null); - - if (this.extensions.loseContext) - { - this.extensions.loseContext.loseContext(); - } - } - - postrender() - { - this.gl.flush(); - } - - validateContext(gl) - { - const attributes = gl.getContextAttributes(); - - // this is going to be fairly simple for now.. but at least we have room to grow! - if (!attributes.stencil) - { - /* eslint-disable max-len */ - - /* eslint-disable no-console */ - console.warn('Provided WebGL context does not have a stencil buffer, masks may not render correctly'); - /* eslint-enable no-console */ - - /* eslint-enable max-len */ - } - } -} diff --git a/packages/core/src/renderers/systems/FramebufferSystem.js b/packages/core/src/renderers/systems/FramebufferSystem.js deleted file mode 100644 index df7902b..0000000 --- a/packages/core/src/renderers/systems/FramebufferSystem.js +++ /dev/null @@ -1,246 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { Rectangle } from '@pixi/math'; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class FramebufferSystem extends WebGLSystem -{ - /** - * Sets up the renderer context and necessary buffers. - * - * @private - */ - contextChange() - { - this.gl = this.renderer.gl; - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - this.current = null; - this.viewport = new Rectangle(); - - this.drawBufferExtension = this.renderer.context.extensions.drawBuffers; - } - - bind(framebuffer, frame) - { - const gl = this.gl; - - this.current = framebuffer; - - if (framebuffer) - { - // TODO cacheing layer! - - const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID] || this.initFramebuffer(framebuffer); - - gl.bindFramebuffer(gl.FRAMEBUFFER, fbo.framebuffer); - // makesure all textures are unbound.. - - // now check for updates... - if (fbo.dirtyId !== framebuffer.dirtyId) - { - fbo.dirtyId = framebuffer.dirtyId; - - if (fbo.dirtyFormat !== framebuffer.dirtyFormat) - { - fbo.dirtyFormat = framebuffer.dirtyFormat; - this.updateFramebuffer(framebuffer); - } - else if (fbo.dirtySize !== framebuffer.dirtySize) - { - fbo.dirtySize = framebuffer.dirtySize; - this.resizeFramebuffer(framebuffer); - } - } - - for (let i = 0; i < framebuffer.colorTextures.length; i++) - { - if (framebuffer.colorTextures[i].texturePart) - { - this.renderer.texture.unbind(framebuffer.colorTextures[i].texture); - } - else - { - this.renderer.texture.unbind(framebuffer.colorTextures[i]); - } - } - - if (framebuffer.depthTexture) - { - this.renderer.texture.unbind(framebuffer.depthTexture); - } - - if (frame) - { - this.setViewport(frame.x, frame.y, frame.width, frame.height); - } - else - { - this.setViewport(0, 0, framebuffer.width, framebuffer.height); - } - } - else - { - gl.bindFramebuffer(gl.FRAMEBUFFER, null); - - if (frame) - { - this.setViewport(frame.x, frame.y, frame.width, frame.height); - } - else - { - this.setViewport(0, 0, this.renderer.width, this.renderer.height); - } - } - } - - setViewport(x, y, width, height) - { - const v = this.viewport; - - if (v.width !== width || v.height !== height || v.x !== x || v.y !== y) - { - v.x = x; - v.y = y; - v.width = width; - v.height = height; - - this.gl.viewport(x, y, width, height); - } - } - - get size() - { - if (this.current) - { - // TODO store temp - return { x: 0, y: 0, width: this.current.width, height: this.current.height }; - } - - return { x: 0, y: 0, width: this.renderer.width, height: this.renderer.height }; - } - - clear(r, g, b, a) - { - const gl = this.gl; - - // TODO clear color can be set only one right? - gl.clearColor(r, g, b, a); - gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); - } - - // private functions... - - initFramebuffer(framebuffer) - { - const gl = this.gl; - - // TODO - make this a class? - const fbo = { - framebuffer: gl.createFramebuffer(), - stencil: null, - dirtyId: 0, - dirtyFormat: 0, - dirtySize: 0, - }; - - framebuffer.glFrameBuffers[this.CONTEXT_UID] = fbo; - - return fbo; - } - - resizeFramebuffer(framebuffer) - { - const gl = this.gl; - - if (framebuffer.stencil || framebuffer.depth) - { - gl.bindRenderbuffer(gl.RENDERBUFFER, this.stencil); - gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); - } - } - - updateFramebuffer(framebuffer) - { - const gl = this.gl; - - const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID]; - - // bind the color texture - const colorTextures = framebuffer.colorTextures; - - let count = colorTextures.length; - - if (!this.drawBufferExtension) - { - count = Math.min(count, 1); - } - - const activeTextures = []; - - for (let i = 0; i < count; i++) - { - const texture = framebuffer.colorTextures[i]; - - if (texture.texturePart) - { - this.renderer.texture.bind(texture.texture, 0); - - gl.framebufferTexture2D(gl.FRAMEBUFFER, - gl.COLOR_ATTACHMENT0 + i, - gl.TEXTURE_CUBE_MAP_NEGATIVE_X + texture.side, - texture.texture._glTextures[this.CONTEXT_UID].texture, - 0); - } - else - { - this.renderer.texture.bind(texture, 0); - - gl.framebufferTexture2D(gl.FRAMEBUFFER, - gl.COLOR_ATTACHMENT0 + i, - gl.TEXTURE_2D, - texture._glTextures[this.CONTEXT_UID].texture, - 0); - } - - activeTextures.push(gl.COLOR_ATTACHMENT0 + i); - } - - if (this.drawBufferExtension && activeTextures.length > 1) - { - this.drawBufferExtension.drawBuffersWEBGL(activeTextures); - } - - if (framebuffer.depthTexture) - { - const depthTextureExt = this.renderer.context.extensions.depthTexture; - - if (depthTextureExt) - { - const depthTexture = framebuffer.depthTexture; - - this.renderer.texture.bind(depthTexture, 0); - - gl.framebufferTexture2D(gl.FRAMEBUFFER, - gl.DEPTH_ATTACHMENT, - gl.TEXTURE_2D, - depthTexture._glTextures[this.CONTEXT_UID].texture, - 0); - } - } - - if (framebuffer.stencil || framebuffer.depth) - { - fbo.stencil = gl.createRenderbuffer(); - - gl.bindRenderbuffer(gl.RENDERBUFFER, fbo.stencil); - - // TODO.. this is depth AND stencil? - gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, fbo.stencil); - gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); - // fbo.enableStencil(); - } - } -} diff --git a/packages/core/src/renderers/systems/MaskSystem.js b/packages/core/src/renderers/systems/MaskSystem.js deleted file mode 100644 index d9c4b1a..0000000 --- a/packages/core/src/renderers/systems/MaskSystem.js +++ /dev/null @@ -1,199 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import AlphaMaskFilter from '../filters/spriteMask/SpriteMaskFilter'; - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class MaskSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - // TODO - we don't need both! - this.scissor = false; - this.scissorData = null; - this.scissorRenderTarget = null; - - this.enableScissor = false; - - this.alphaMaskPool = []; - this.alphaMaskIndex = 0; - } - - /** - * Applies the Mask and adds it to the current filter stack. - * - * @param {PIXI.DisplayObject} target - Display Object to push the mask to - * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. - */ - push(target, maskData) - { - // TODO the root check means scissor rect will not - // be used on render textures more info here: - // https://github.com/pixijs/pixi.js/pull/3545 - - if (maskData.texture) - { - this.pushSpriteMask(target, maskData); - } - else if (this.enableScissor - && !this.scissor - && this.renderer._activeRenderTarget.root - && !this.renderer.stencil.stencilMaskStack.length - && maskData.isFastRect()) - { - const matrix = maskData.worldTransform; - - let rot = Math.atan2(matrix.b, matrix.a); - - // use the nearest degree! - rot = Math.round(rot * (180 / Math.PI)); - - if (rot % 90) - { - this.pushStencilMask(maskData); - } - else - { - this.pushScissorMask(target, maskData); - } - } - else - { - this.pushStencilMask(maskData); - } - } - - /** - * Removes the last mask from the mask stack and doesn't return it. - * - * @param {PIXI.DisplayObject} target - Display Object to pop the mask from - * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. - */ - pop(target, maskData) - { - if (maskData.texture) - { - this.popSpriteMask(target, maskData); - } - else if (this.enableScissor && !this.renderer.stencil.stencilMaskStack.length) - { - this.popScissorMask(target, maskData); - } - else - { - this.popStencilMask(target, maskData); - } - } - - /** - * Applies the Mask and adds it to the current filter stack. - * - * @param {PIXI.RenderTarget} target - Display Object to push the sprite mask to - * @param {PIXI.Sprite} maskData - Sprite to be used as the mask - */ - pushSpriteMask(target, maskData) - { - let alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; - - if (!alphaMaskFilter) - { - alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; - } - - alphaMaskFilter[0].resolution = this.renderer.resolution; - alphaMaskFilter[0].maskSprite = maskData; - - // TODO - may cause issues! - target.filterArea = maskData.getBounds(true); - - this.renderer.filterSystem.pushFilter(target, alphaMaskFilter); - - this.alphaMaskIndex++; - } - - /** - * Removes the last filter from the filter stack and doesn't return it. - * - */ - popSpriteMask() - { - this.renderer.filterSystem.popFilter(); - this.alphaMaskIndex--; - } - - /** - * Applies the Mask and adds it to the current filter stack. - * - * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. - */ - pushStencilMask(maskData) - { - this.renderer.batch.flush(); - this.renderer.stencil.pushStencil(maskData); - } - - /** - * Removes the last filter from the filter stack and doesn't return it. - * - */ - popStencilMask() - { - // this.renderer.currentRenderer.stop(); - this.renderer.stencil.popStencil(); - } - - /** - * - * @param {PIXI.DisplayObject} target - Display Object to push the mask to - * @param {PIXI.Graphics} maskData - The masking data. - */ - pushScissorMask(target, maskData) - { - maskData.renderable = true; - - const renderTarget = this.renderer._activeRenderTarget; - - const bounds = maskData.getBounds(); - - bounds.fit(renderTarget.size); - maskData.renderable = false; - - this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); - - const resolution = this.renderer.resolution; - - this.renderer.gl.scissor( - bounds.x * resolution, - (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, - bounds.width * resolution, - bounds.height * resolution - ); - - this.scissorRenderTarget = renderTarget; - this.scissorData = maskData; - this.scissor = true; - } - - /** - * - * - */ - popScissorMask() - { - this.scissorRenderTarget = null; - this.scissorData = null; - this.scissor = false; - - // must be scissor! - const gl = this.renderer.gl; - - gl.disable(gl.SCISSOR_TEST); - } -} diff --git a/packages/core/src/renderers/systems/ProjectionSystem.js b/packages/core/src/renderers/systems/ProjectionSystem.js deleted file mode 100644 index ec5efdb..0000000 --- a/packages/core/src/renderers/systems/ProjectionSystem.js +++ /dev/null @@ -1,73 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { Matrix } from '@pixi/math'; - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ - -export default class ProjectionSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this.projectionMatrix = new Matrix(); - } - - update(destinationFrame, sourceFrame, resolution, root) - { - this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; - this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; - - this.calculateProjection(this.destinationFrame, this.sourceFrame, resolution, root); - - this.renderer.globalUniforms.uniforms.projectionMatrix = this.projectionMatrix; - this.renderer.globalUniforms.update(); - } - - /** - * Updates the projection matrix based on a projection frame (which is a rectangle) - * - * @param {Rectangle} destinationFrame - The destination frame. - * @param {Rectangle} sourceFrame - The source frame. - */ - calculateProjection(destinationFrame, sourceFrame, resolution, root) - { - const pm = this.projectionMatrix; - - // I don't think we will need this line.. - // pm.identity(); - - if (!root) - { - pm.a = (1 / destinationFrame.width * 2) * resolution; - pm.d = (1 / destinationFrame.height * 2) * resolution; - - pm.tx = -1 - (sourceFrame.x * pm.a); - pm.ty = -1 - (sourceFrame.y * pm.d); - } - else - { - pm.a = (1 / destinationFrame.width * 2) * resolution; - pm.d = (-1 / destinationFrame.height * 2) * resolution; - - pm.tx = -1 - (sourceFrame.x * pm.a); - pm.ty = 1 - (sourceFrame.y * pm.d); - } - } - - /** - * Sets the transform of the active render target to the given matrix - * - * @param {PIXI.Matrix} matrix - The transformation matrix - */ - setTransform()// matrix) - { - // this._activeRenderTarget.transform = matrix; - } -} diff --git a/packages/core/src/renderers/systems/RenderTextureSystem.js b/packages/core/src/renderers/systems/RenderTextureSystem.js deleted file mode 100644 index a133ef1..0000000 --- a/packages/core/src/renderers/systems/RenderTextureSystem.js +++ /dev/null @@ -1,115 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { Rectangle } from '@pixi/math'; - -const tempRect = new Rectangle(); - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ - -export default class RenderTextureSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this.clearColor = renderer._backgroundColorRgba; - - // TODO moe this property somewhere else! - this.defaultMaskStack = []; - this.defaultFilterStack = [{}]; - - // empty render texture? - this.renderTexture = null; - - this.destinationFrame = new Rectangle(); - } - - bind(renderTexture, sourceFrame, destinationFrame) - { - // TODO - do we want this?? - if (this.renderTexture === renderTexture) return; - this.renderTexture = renderTexture; - - const renderer = this.renderer; - - if (renderTexture) - { - const baseTexture = renderTexture.baseTexture; - - if (!destinationFrame) - { - tempRect.width = baseTexture.realWidth; - tempRect.height = baseTexture.realHeight; - - destinationFrame = tempRect; - } - - if (!sourceFrame) - { - sourceFrame = destinationFrame; - } - - this.renderer.framebuffer.bind(baseTexture.frameBuffer, destinationFrame); - - this.renderer.projection.update(destinationFrame, sourceFrame, baseTexture.resolution, false); - this.renderer.stencil.setMaskStack(baseTexture.stencilMaskStack); - } - else - { - // TODO these validation checks happen deeper down.. - // thing they can be avoided.. - if (!destinationFrame) - { - tempRect.width = renderer.width; - tempRect.height = renderer.height; - - destinationFrame = tempRect; - } - - if (!sourceFrame) - { - sourceFrame = destinationFrame; - } - - renderer.framebuffer.bind(null, destinationFrame); - - // TODO store this.. - this.renderer.projection.update(destinationFrame, sourceFrame, this.renderer.resolution, true); - this.renderer.stencil.setMaskStack(this.defaultMaskStack); - } - - this.destinationFrame.copyFrom(destinationFrame); - } - - /** - * Erases the render texture and fills the drawing area with a colour - * - * @param {number} [clearColor] - The colour - * @return {PIXI.Renderer} Returns itself. - */ - clear(clearColor) - { - if (this.renderTexture) - { - clearColor = clearColor || this.renderTexture.baseTexture.clearColor; - } - else - { - clearColor = clearColor || this.clearColor; - } - - this.renderer.framebuffer.clear(clearColor[0], clearColor[1], clearColor[2], clearColor[3]); - } - - resize()// screenWidth, screenHeight) - { - // resize the root only! - this.bind(null); - } -} diff --git a/packages/core/src/renderers/systems/StateSystem.js b/packages/core/src/renderers/systems/StateSystem.js deleted file mode 100755 index d7f4caf..0000000 --- a/packages/core/src/renderers/systems/StateSystem.js +++ /dev/null @@ -1,288 +0,0 @@ -import mapWebGLBlendModesToPixi from '../utils/mapWebGLBlendModesToPixi'; -import WebGLSystem from './WebGLSystem'; -import WebGLState from '../State'; - -const BLEND = 0; -const OFFSET = 1; -const CULLING = 2; -const DEPTH_TEST = 3; -const WINDING = 4; - -/** - * A WebGL state machines - * - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class StateSystem extends WebGLSystem -{ - /** - * @param {WebGLRenderingContext} gl - The current WebGL rendering context - */ - constructor(renderer) - { - super(renderer); - - this.gl = null; - - this.maxAttribs = null; - - // check we have vao.. - this.nativeVaoExtension = null; - - this.attribState = null; - - this.stateId = 0; - this.polygonOffset = 0; - this.blendMode = 17; - - this.map = []; - - // map functions for when we set state.. - this.map[BLEND] = this.setBlend; - this.map[OFFSET] = this.setOffset; - this.map[CULLING] = this.setCullFace; - this.map[DEPTH_TEST] = this.setDepthTest; - this.map[WINDING] = this.setFrontFace; - - this.checks = []; - - this.defaultState = new WebGLState(); - this.defaultState.blend = true; - this.defaultState.depth = true; - } - - contextChange(gl) - { - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; - - this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); - - // check we have vao.. - this.nativeVaoExtension = ( - gl.getExtension('OES_vertex_array_object') - || gl.getExtension('MOZ_OES_vertex_array_object') - || gl.getExtension('WEBKIT_OES_vertex_array_object') - ); - - this.attribState = { - tempAttribState: new Array(this.maxAttribs), - attribState: new Array(this.maxAttribs), - }; - - this.blendModes = mapWebGLBlendModesToPixi(gl); - - this.setState(this.defaultState); - - this.reset(); - } - - /** - * Sets the current state - * - * @param {*} state - The state to set. - */ - setState(state) - { - state = state || this.defaultState; - - // TODO maybe to an object check? ( this.state === state )? - if (this.stateId !== state.data) - { - let diff = this.stateId ^ state.data; - let i = 0; - - // order from least to most common - while (diff) - { - if (diff & 1) - { - // state change! - this.map[i].call(this, !!(state.data & (1 << i))); - } - - diff = diff >> 1; - i++; - } - - this.stateId = state.data; - } - - // based on the above settings we check for specific modes.. - // for example if blend is active we check and set the blend modes - // or of polygon offset is active we check the poly depth. - for (let i = 0; i < this.checks.length; i++) - { - this.checks[i](this, state); - } - } - - /** - * Enables or disabled blending. - * - * @param {boolean} value - Turn on or off webgl blending. - */ - setBlend(value) - { - this.updateCheck(StateSystem.checkBlendMode, value); - - this.gl[value ? 'enable' : 'disable'](this.gl.BLEND); - } - - /** - * Enables or disable polygon offset fill - * - * @param {boolean} value - Turn on or off webgl polygon offset testing. - */ - setOffset(value) - { - this.gl[value ? 'enable' : 'disable'](this.gl.POLYGON_OFFSET_FILL); - } - - /** - * Sets whether to enable or disable depth test. - * - * @param {boolean} value - Turn on or off webgl depth testing. - */ - setDepthTest(value) - { - this.gl[value ? 'enable' : 'disable'](this.gl.DEPTH_TEST); - } - - /** - * Sets whether to enable or disable cull face. - * - * @param {boolean} value - Turn on or off webgl cull face. - */ - setCullFace(value) - { - this.gl[value ? 'enable' : 'disable'](this.gl.CULL_FACE); - } - - /** - * Sets the gl front face. - * - * @param {boolean} value - true is clockwise and false is counter-clockwise - */ - setFrontFace(value) - { - this.gl.frontFace(this.gl[value ? 'CW' : 'CCW']); - } - - /** - * Sets the blend mode. - * - * @param {number} value - The blend mode to set to. - */ - setBlendMode(value) - { - if (value === this.blendMode) - { - return; - } - - this.blendMode = value; - this.gl.blendFunc(this.blendModes[value][0], this.blendModes[value][1]); - } - - /** - * Sets the polygon offset. - * - * @param {number} value - the polygon offset - * @param {number} scale - the polygon offset scale - */ - setPolygonOffset(value, scale) - { - this.gl.polygonOffset(value, scale); - } - - /** - * Disables all the vaos in use - * - */ - resetAttributes() - { - for (let i = 0; i < this.attribState.tempAttribState.length; i++) - { - this.attribState.tempAttribState[i] = 0; - } - - for (let i = 0; i < this.attribState.attribState.length; i++) - { - this.attribState.attribState[i] = 0; - } - - // im going to assume one is always active for performance reasons. - for (let i = 1; i < this.maxAttribs; i++) - { - this.gl.disableVertexAttribArray(i); - } - } - - // used - /** - * Resets all the logic and disables the vaos - */ - reset() - { - // unbind any VAO if they exist.. - if (this.nativeVaoExtension) - { - this.nativeVaoExtension.bindVertexArrayOES(null); - } - - // reset all attributes.. - this.resetAttributes(); - - this.gl.pixelStorei(this.gl.UNPACK_FLIP_Y_WEBGL, false); - - this.setBlendMode(0); - - // TODO? - // this.setState(this.defaultState); - } - - /** - * checks to see which updates should be checked based on which settings have been activated - * for example if blend is enabled then we shold check the blend modes each time the state is changed - * or if poygon fill is activated then we need to check if the polygone offset changes. - * The idea is that we only check what we have too. - * - * @param {Function} func the checking function to add or remove - * @param {boolean} value should the check function be added or removed. - */ - updateCheck(func, value) - { - const index = this.checks.indexOf(func); - - if (value && index === -1) - { - this.checks.push(func); - } - else if (!value && index !== -1) - { - this.checks.splice(index, 1); - } - } - - /** - * A private little wrapper function that we call to check the blend mode. - * - * @static - * @private - * @param {PIXI.StateSystem} System the System to perform the state check on - * @param {PIXI.State} state the state that the blendMode will pulled from - */ - static checkBlendMode(system, state) - { - system.setBlendMode(state.blendMode); - } - - // TODO - add polygon offset? -} diff --git a/packages/core/src/renderers/systems/StencilSystem.js b/packages/core/src/renderers/systems/StencilSystem.js deleted file mode 100644 index 5d538cb..0000000 --- a/packages/core/src/renderers/systems/StencilSystem.js +++ /dev/null @@ -1,133 +0,0 @@ -import WebGLSystem from './WebGLSystem'; - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class StencilSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - this.stencilMaskStack = []; - } - - /** - * Changes the mask stack that is used by this System. - * - * @param {PIXI.Graphics[]} stencilMaskStack - The mask stack - */ - setMaskStack(stencilMaskStack) - { - const gl = this.renderer.gl; - - if (stencilMaskStack.length !== this.stencilMaskStack.length) - { - if (stencilMaskStack.length === 0) - { - gl.disable(gl.STENCIL_TEST); - } - else - { - gl.enable(gl.STENCIL_TEST); - } - } - - this.stencilMaskStack = stencilMaskStack; - } - - /** - * Applies the Mask and adds it to the current stencil stack. @alvin - * - * @param {PIXI.Graphics} graphics - The mask - */ - pushStencil(graphics) - { - this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); - - // this.renderer._activeRenderTarget.attachStencilBuffer(); - - const gl = this.renderer.gl; - const prevMaskCount = this.stencilMaskStack.length; - - if (prevMaskCount === 0) - { - gl.enable(gl.STENCIL_TEST); - } - - this.stencilMaskStack.push(graphics); - - // Increment the refference stencil value where the new mask overlaps with the old ones. - gl.colorMask(false, false, false, false); - gl.stencilFunc(gl.EQUAL, prevMaskCount, this._getBitwiseMask()); - gl.stencilOp(gl.KEEP, gl.KEEP, gl.INCR); - this.renderer.plugins.graphics.render(graphics); - - this._useCurrent(); - } - - /** - * Removes the last mask from the stencil stack. @alvin - */ - popStencil() - { - this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); - - const gl = this.renderer.gl; - const graphics = this.stencilMaskStack.pop(); - - if (this.stencilMaskStack.length === 0) - { - // the stack is empty! - gl.disable(gl.STENCIL_TEST); - gl.clear(gl.STENCIL_BUFFER_BIT); - gl.clearStencil(0); - } - else - { - // Decrement the refference stencil value where the popped mask overlaps with the other ones - gl.colorMask(false, false, false, false); - gl.stencilOp(gl.KEEP, gl.KEEP, gl.DECR); - this.renderer.plugins.graphics.render(graphics); - - this._useCurrent(); - } - } - - /** - * Setup renderer to use the current stencil data. - */ - _useCurrent() - { - const gl = this.renderer.gl; - - gl.colorMask(true, true, true, true); - gl.stencilFunc(gl.EQUAL, this.stencilMaskStack.length, this._getBitwiseMask()); - gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP); - } - - /** - * Fill 1s equal to the number of acitve stencil masks. - * - * @return {number} The bitwise mask. - */ - _getBitwiseMask() - { - return (1 << this.stencilMaskStack.length) - 1; - } - - /** - * Destroys the mask stack. - * - */ - destroy() - { - super.destroy(this); - - this.stencilMaskStack = null; - } -} diff --git a/packages/core/src/renderers/systems/WebGLSystem.js b/packages/core/src/renderers/systems/WebGLSystem.js deleted file mode 100644 index d3d5b31..0000000 --- a/packages/core/src/renderers/systems/WebGLSystem.js +++ /dev/null @@ -1,41 +0,0 @@ -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this manager works for. - */ - constructor(renderer) - { - /** - * The renderer this manager works for. - * - * @member {PIXI.Renderer} - */ - this.renderer = renderer; - - this.renderer.runners.contextChange.add(this); - } - - /** - * Generic method called when there is a WebGL context change. - * - */ - contextChange() - { - // do some codes init! - } - - /** - * Generic destroy methods to be overridden by the subclass - * - */ - destroy() - { - this.renderer.runners.contextChange.remove(this); - this.renderer = null; - } -} diff --git a/packages/core/src/renderers/systems/filter/FilterSystem.js b/packages/core/src/renderers/systems/filter/FilterSystem.js deleted file mode 100644 index e615037..0000000 --- a/packages/core/src/renderers/systems/filter/FilterSystem.js +++ /dev/null @@ -1,431 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; - -import RenderTexture from '../../../textures/RenderTexture'; -import Quad from '../../utils/Quad'; -import QuadUv from '../../utils/QuadUv'; -import { Rectangle } from '@pixi/math'; -import * as filterTransforms from '../../filters/filterTransforms'; -import bitTwiddle from 'bit-twiddle'; -import UniformGroup from '../../../shader/UniformGroup'; -import { DRAW_MODES } from '../../../../../constants'; - -// -/** - * @ignore - * @class - */ -class FilterState -{ - /** - * - */ - constructor() - { - this.renderTexture = null; - this.sourceFrame = new Rectangle(); - this.destinationFrame = new Rectangle(); - this.filters = []; - this.target = null; - this.legacy = false; - this.resolution = 1; - } -} - -/** - * @class - * @memberof PIXI.systems - * @extends PIXI.systems.WebGLSystem - */ -export default class FilterSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - /** - * stores a bunch of PO2 textures used for filtering - * @type {Object} - */ - this.texturePool = {}; - - /** - * a pool for storing filter states, save us creating new ones each tick - * @type {Array} - */ - this.statePool = []; - - /** - * A very simple geometry used when drawing a filter effect to the screen - * @type {Quad} - */ - this.quad = new Quad(); - - this.quadUv = new QuadUv(); - - /** - * Temporary rect for maths - * @type {PIXI.Rectangle} - */ - this.tempRect = new Rectangle(); - - this.activeState = {}; - - /** - * this uniform group is attached to filter uniforms when used - * @type {UniformGroup} - */ - this.globalUniforms = new UniformGroup({ - sourceFrame: this.tempRect, - destinationFrame: this.tempRect, - - // legacy variables - filterArea: new Float32Array(4), - filterClamp: new Float32Array(4), - }, true); - } - - /** - * Adds a new filter to the System. - * - * @param {PIXI.DisplayObject} target - The target of the filter to render. - * @param {PIXI.Filter[]} filters - The filters to apply. - */ - push(target, filters) - { - const renderer = this.renderer; - const filterStack = this.renderer.renderTexture.defaultFilterStack; - const state = this.statePool.pop() || new FilterState(); - - let resolution = filters[0].resolution; - let padding = filters[0].padding; - let autoFit = filters[0].autoFit; - let legacy = filters[0].legacy; - - for (let i = 1; i < filters.length; i++) - { - const filter = filters[i]; - - // lets use the lowest resolution.. - resolution = Math.min(resolution, filter.resolution); - // and the largest amount of padding! - padding = Math.max(padding, filter.padding); - // only auto fit if all filters are autofit - autoFit = autoFit || filter.autoFit; - - legacy = legacy || filter.legacy; - } - - filterStack.push(state); - - state.resolution = resolution; - - state.legacy = legacy; - - // round to whole number based on resolution - // TODO move that to the shader too? - state.sourceFrame = target.filterArea || target.getBounds(true); - - state.sourceFrame.pad(padding); - - if (autoFit) - { - state.sourceFrame.fit(this.renderer.renderTexture.destinationFrame); - } - - state.sourceFrame.round(resolution); - - state.renderTexture = this.getPotFilterTexture(state.sourceFrame.width, state.sourceFrame.height, resolution); - state.filters = filters; - - state.destinationFrame.width = state.renderTexture.width; - state.destinationFrame.height = state.renderTexture.height; - - state.renderTexture.filterFrame = state.sourceFrame; - renderer.renderTexture.bind(state.renderTexture, state.sourceFrame);// /, state.destinationFrame); - renderer.renderTexture.clear(); - } - - /** - * Pops off the filter and applies it. - * - */ - pop() - { - const renderer = this.renderer; - const filterStack = renderer.renderTexture.defaultFilterStack; - const state = filterStack.pop(); - const filters = state.filters; - - this.activeState = state; - - const globalUniforms = this.globalUniforms.uniforms; - - globalUniforms.sourceFrame = state.sourceFrame; - globalUniforms.destinationFrame = state.destinationFrame; - globalUniforms.resolution = state.resolution; - - // only update the rect if its legacy.. - if (state.legacy) - { - const filterArea = globalUniforms.filterArea; - const filterClamp = globalUniforms.filterClamp; - - filterArea[0] = state.destinationFrame.width; - filterArea[1] = state.destinationFrame.height; - filterArea[2] = state.sourceFrame.x; - filterArea[3] = state.sourceFrame.y; - - filterClamp[0] = 0.5 / state.resolution / state.destinationFrame.width; - filterClamp[1] = 0.5 / state.resolution / state.destinationFrame.height; - filterClamp[2] = (state.sourceFrame.width - 0.5) / state.resolution / state.destinationFrame.width; - filterClamp[3] = (state.sourceFrame.height - 0.5) / state.resolution / state.destinationFrame.height; - } - - this.globalUniforms.update(); - - const lastState = filterStack[filterStack.length - 1]; - - if (filters.length === 1) - { - filters[0].apply(this, state.renderTexture, lastState.renderTexture, false, state); - - this.returnFilterTexture(state.renderTexture); - } - else - { - let flip = state.renderTexture; - let flop = this.getPotFilterTexture( - flip.width, - flip.height, - state.resolution - ); - - flop.filterFrame = flip.filterFrame; - - let i = 0; - - for (i = 0; i < filters.length - 1; ++i) - { - filters[i].apply(this, flip, flop, true, state); - - const t = flip; - - flip = flop; - flop = t; - } - - filters[i].apply(this, flip, lastState.renderTexture, false, state); - - this.returnFilterTexture(flip); - this.returnFilterTexture(flop); - } - - this.statePool.push(state); - } - - /** - * Draws a filter. - * - * @param {PIXI.Filter} filter - The filter to draw. - * @param {PIXI.RenderTarget} input - The input render target. - * @param {PIXI.RenderTarget} output - The target to output to. - * @param {boolean} clear - Should the output be cleared before rendering to it - */ - applyFilter(filter, input, output, clear) - { - const renderer = this.renderer; - - renderer.renderTexture.bind(output, output ? output.filterFrame : null); - - if (clear) - { - // gl.disable(gl.SCISSOR_TEST); - renderer.renderTexture.clear(); - // gl.enable(gl.SCISSOR_TEST); - } - - // set the uniforms.. - filter.uniforms.uSampler = input; - filter.uniforms.filterGlobals = this.globalUniforms; - - // TODO make it so that the order of this does not matter.. - // because it does at the moment cos of global uniforms. - // they need to get resynced - - renderer.state.setState(filter.state); - renderer.shader.bind(filter); - - if (filter.legacy) - { - this.quadUv.map(input._frame, input.filterFrame); - - renderer.geometry.bind(this.quadUv); - renderer.geometry.draw(DRAW_MODES.TRIANGLES); - } - else - { - renderer.geometry.bind(this.quad); - renderer.geometry.draw(DRAW_MODES.TRIANGLE_STRIP); - } - } - - /** - * Calculates the mapped matrix. - * - * TODO playing around here.. this is temporary - (will end up in the shader) - * this returns a matrix that will normalise map filter cords in the filter to screen space - * - * @param {PIXI.Matrix} outputMatrix - the matrix to output to. - * @return {PIXI.Matrix} The mapped matrix. - */ - calculateScreenSpaceMatrix(outputMatrix) - { - const currentState = this.activeState; - - return filterTransforms.calculateScreenSpaceMatrix( - outputMatrix, - currentState.sourceFrame, - currentState.destinationFrame - ); - } - - /** - * This will map the filter coord so that a texture can be used based on the transform of a sprite - * - * @param {PIXI.Matrix} outputMatrix - The matrix to output to. - * @param {PIXI.Sprite} sprite - The sprite to map to. - * @return {PIXI.Matrix} The mapped matrix. - */ - calculateSpriteMatrix(outputMatrix, sprite) - { - const currentState = this.activeState; - - return filterTransforms.calculateSpriteMatrix( - outputMatrix, - currentState.sourceFrame, - currentState.destinationFrame, - sprite - ); - } - - /** - * Destroys this Filter System. - * - * @param {boolean} [contextLost=false] context was lost, do not free shaders - * - */ - destroy(contextLost = false) - { - if (!contextLost) - { - this.emptyPool(); - } - else - { - this.texturePool = {}; - } - } - - /** - * Gets a Power-of-Two render texture. - * - * TODO move to a seperate class could be on renderer? - * also - could cause issue with multiple contexts? - * - * @private - * @param {WebGLRenderingContext} gl - The webgl rendering context - * @param {number} minWidth - The minimum width of the render target. - * @param {number} minHeight - The minimum height of the render target. - * @param {number} resolution - The resolution of the render target. - * @return {PIXI.RenderTarget} The new render target. - */ - getPotFilterTexture(minWidth, minHeight, resolution) - { - minWidth = bitTwiddle.nextPow2(minWidth); - minHeight = bitTwiddle.nextPow2(minHeight); - resolution = resolution || 1; - - const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); - - if (!this.texturePool[key]) - { - this.texturePool[key] = []; - } - - let renderTexture = this.texturePool[key].pop(); - - if (!renderTexture) - { - // temporary bypass cache.. - // internally - this will cause a texture to be bound.. - renderTexture = RenderTexture.create({ - width: minWidth, - height: minHeight, - resolution, - }); - } - - return renderTexture; - } - - /** - * Gets extra render texture to use inside current filter - * - * @param {number} resolution resolution of the renderTexture - * @returns {PIXI.RenderTexture} - */ - getFilterTexture(resolution) - { - const rt = this.activeState.renderTexture; - - const filterTexture = this.getPotFilterTexture(rt.width, rt.height, resolution || rt.baseTexture.resolution); - - filterTexture.filterFrame = rt.filterFrame; - - return filterTexture; - } - - /** - * Frees a render texture back into the pool. - * - * @param {PIXI.RenderTarget} renderTexture - The renderTarget to free - */ - returnFilterTexture(renderTexture) - { - renderTexture.filterFrame = null; - - const base = renderTexture.baseTexture; - - const minWidth = base.width; - const minHeight = base.height; - - const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); - - this.texturePool[key].push(renderTexture); - } - - /** - * Empties the texture pool. - * - */ - emptyPool() - { - for (const i in this.texturePool) - { - const textures = this.texturePool[i]; - - if (textures) - { - for (let j = 0; j < textures.length; j++) - { - textures[j].destroy(true); - } - } - } - - this.texturePool = {}; - } -} diff --git a/packages/core/src/renderers/systems/geometry/GLBuffer.js b/packages/core/src/renderers/systems/geometry/GLBuffer.js deleted file mode 100644 index 0477e8a..0000000 --- a/packages/core/src/renderers/systems/geometry/GLBuffer.js +++ /dev/null @@ -1,9 +0,0 @@ -export default class GLBuffer -{ - constructor(buffer) - { - this.buffer = buffer; - this.updateID = -1; - this.byteLength = -1; - } -} diff --git a/packages/core/src/renderers/systems/geometry/GeometrySystem.js b/packages/core/src/renderers/systems/geometry/GeometrySystem.js deleted file mode 100644 index 26c8e9e..0000000 --- a/packages/core/src/renderers/systems/geometry/GeometrySystem.js +++ /dev/null @@ -1,388 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; -import GLBuffer from './GLBuffer'; - -const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class GeometrySystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this._activeGeometry = null; - this._activeVao = null; - - this.hasVao = true; - this.hasInstance = true; - } - - /** - * Sets up the renderer context and necessary buffers. - * - * @private - */ - contextChange() - { - const gl = this.gl = this.renderer.gl; - - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - - // webgl2 - if (!gl.createVertexArray) - { - // webgl 1! - let nativeVaoExtension = this.renderer.context.extensions.vertexArrayObject; - - if (this.renderer.legacy) - { - nativeVaoExtension = null; - } - - if (nativeVaoExtension) - { - gl.createVertexArray = () => - nativeVaoExtension.createVertexArrayOES(); - - gl.bindVertexArray = (vao) => - nativeVaoExtension.bindVertexArrayOES(vao); - - gl.deleteVertexArray = (vao) => - nativeVaoExtension.deleteVertexArrayOES(vao); - } - else - { - this.hasVao = false; - gl.createVertexArray = () => - { - // empty - }; - - gl.bindVertexArray = () => - { - // empty - }; - - gl.deleteVertexArray = () => - { - // empty - }; - } - } - - if (!gl.vertexAttribDivisor) - { - const instanceExt = gl.getExtension('ANGLE_instanced_arrays'); - - if (instanceExt) - { - gl.vertexAttribDivisor = (a, b) => - instanceExt.vertexAttribDivisorANGLE(a, b); - - gl.drawElementsInstanced = (a, b, c, d, e) => - instanceExt.drawElementsInstancedANGLE(a, b, c, d, e); - - gl.drawArraysInstanced = (a, b, c, d) => - instanceExt.drawArraysInstancedANGLE(a, b, c, d); - } - else - { - this.hasInstance = false; - } - } - } - - /** - * Binds geometry so that is can be drawn. Creating a Vao if required - * @private - * @param {PIXI.Geometry} geometry instance of geometry to bind - */ - bind(geometry, shader) - { - shader = shader || this.renderer.shader.shader; - - const gl = this.gl; - - // not sure the best way to address this.. - // currently different shaders require different VAOs for the same geometry - // Still mulling over the best way to solve this one.. - // will likely need to modify the shader attribute locations at run time! - let vaos = geometry.glVertexArrayObjects[this.CONTEXT_UID]; - - if (!vaos) - { - geometry.glVertexArrayObjects[this.CONTEXT_UID] = vaos = {}; - } - - const vao = vaos[shader.program.id] || this.initGeometryVao(geometry, shader.program); - - this._activeGeometry = geometry; - - if (this._activeVao !== vao) - { - this._activeVao = vao; - - if (this.hasVao) - { - gl.bindVertexArray(vao); - } - else - { - this.activateVao(geometry, shader.program); - } - } - - // TODO - optimise later! - // don't need to loop through if nothing changed! - // maybe look to add an 'autoupdate' to geometry? - this.updateBuffers(); - } - - reset() - { - this.unbind(); - } - - updateBuffers() - { - const geometry = this._activeGeometry; - const gl = this.gl; - - for (let i = 0; i < geometry.buffers.length; i++) - { - const buffer = geometry.buffers[i]; - - const glBuffer = buffer._glBuffers[this.CONTEXT_UID]; - - if (buffer._updateID !== glBuffer.updateID) - { - glBuffer.updateID = buffer._updateID; - - // TODO can cache this on buffer! maybe added a getter / setter? - const type = buffer.index ? gl.ELEMENT_ARRAY_BUFFER : gl.ARRAY_BUFFER; - const drawType = buffer.static ? gl.STATIC_DRAW : gl.DYNAMIC_DRAW; - - gl.bindBuffer(type, glBuffer.buffer); - - if (glBuffer.byteLength >= buffer.data.byteLength) - { - // offset is always zero for now! - gl.bufferSubData(type, 0, buffer.data); - } - else - { - gl.bufferData(type, buffer.data, drawType); - } - } - } - } - - checkCompatability(geometry, program) - { - // geometry must have at least all the attributes that the shader requires. - const geometryAttributes = geometry.attributes; - const shaderAttributes = program.attributeData; - - for (const j in shaderAttributes) - { - if (!geometryAttributes[j]) - { - throw new Error(`shader and geometry incompatible, geometry missing the "${j}" attribute`); - } - } - } - - /** - * Creates a Vao with the same structure as the geometry and stores it on the geometry. - * @private - * @param {PIXI.Geometry} geometry instance of geometry to to generate Vao for - */ - initGeometryVao(geometry, program) - { - this.checkCompatability(geometry, program); - - const gl = this.gl; - const CONTEXT_UID = this.CONTEXT_UID; - const buffers = geometry.buffers; - const attributes = geometry.attributes; - - const tempStride = {}; - const tempStart = {}; - - for (const j in buffers) - { - tempStride[j] = 0; - tempStart[j] = 0; - } - - for (const j in attributes) - { - if (!attributes[j].size && program.attributeData[j]) - { - attributes[j].size = program.attributeData[j].size; - } - - tempStride[attributes[j].buffer] += attributes[j].size * byteSizeMap[attributes[j].type]; - } - - for (const j in attributes) - { - const attribute = attributes[j]; - const attribSize = attribute.size; - - if (attribute.stride === undefined) - { - if (tempStride[attribute.buffer] === attribSize * byteSizeMap[attribute.type]) - { - attribute.stride = 0; - } - else - { - attribute.stride = tempStride[attribute.buffer]; - } - } - - if (attribute.start === undefined) - { - attribute.start = tempStart[attribute.buffer]; - - tempStart[attribute.buffer] += attribSize * byteSizeMap[attribute.type]; - } - } - - // first update - and create the buffers! - // only create a gl buffer if it actually gets - for (let i = 0; i < buffers.length; i++) - { - const buffer = buffers[i]; - - if (!buffer._glBuffers[CONTEXT_UID]) - { - buffer._glBuffers[CONTEXT_UID] = new GLBuffer(gl.createBuffer()); - } - } - - // TODO - maybe make this a data object? - // lets wait to see if we need to first! - const vao = gl.createVertexArray(); - - gl.bindVertexArray(vao); - - this.activateVao(geometry, program); - - gl.bindVertexArray(null); - - // add it to the cache! - geometry.glVertexArrayObjects[this.CONTEXT_UID][program.id] = vao; - - return vao; - } - - activateVao(geometry, program) - { - const gl = this.gl; - const CONTEXT_UID = this.CONTEXT_UID; - const buffers = geometry.buffers; - const attributes = geometry.attributes; - - if (geometry.indexBuffer) - { - // first update the index buffer if we have one.. - gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, geometry.indexBuffer._glBuffers[CONTEXT_UID].buffer); - } - - let lastBuffer = null; - - // add a new one! - for (const j in attributes) - { - const attribute = attributes[j]; - const buffer = buffers[attribute.buffer]; - const glBuffer = buffer._glBuffers[CONTEXT_UID]; - - if (program.attributeData[j]) - { - if (lastBuffer !== glBuffer) - { - gl.bindBuffer(gl.ARRAY_BUFFER, glBuffer.buffer); - - lastBuffer = glBuffer; - } - - const location = program.attributeData[j].location; - - // TODO introduce state again - // we can optimise this for older devices that have no VAOs - gl.enableVertexAttribArray(location); - - gl.vertexAttribPointer(location, - attribute.size, - attribute.type || gl.FLOAT, - attribute.normalized, - attribute.stride, - attribute.start); - - if (attribute.instance) - { - // TODO calculate instance count based of this... - if (this.hasInstance) - { - gl.vertexAttribDivisor(location, 1); - } - else - { - throw new Error('geometry error, GPU Instancing is not supported on this device'); - } - } - } - } - } - - draw(type, size, start, instanceCount) - { - const gl = this.gl; - const geometry = this._activeGeometry; - - // TODO.. this should not change so maybe cache the function? - - if (geometry.indexBuffer) - { - if (geometry.instanced) - { - /* eslint-disable max-len */ - gl.drawElementsInstanced(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2, instanceCount || 1); - /* eslint-enable max-len */ - } - else - { - gl.drawElements(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2); - } - } - else - if (geometry.instanced) - { - // TODO need a better way to calculate size.. - gl.drawArraysInstanced(type, start, size || geometry.getSize(), instanceCount || 1); - } - else - { - gl.drawArrays(type, start, size || geometry.getSize()); - } - - return this; - } - - unbind() - { - this.gl.bindVertexArray(null); - this._activeVao = null; - this._activeGeometry = null; - } -} diff --git a/packages/core/src/renderers/systems/geometry/setVertexAttribArrays.js b/packages/core/src/renderers/systems/geometry/setVertexAttribArrays.js deleted file mode 100644 index 23b6959..0000000 --- a/packages/core/src/renderers/systems/geometry/setVertexAttribArrays.js +++ /dev/null @@ -1,54 +0,0 @@ -// var GL_MAP = {}; - -/** - * @param gl {WebGLRenderingContext} The current WebGL context - * @param attribs {*} - * @param state {*} - */ -export default function setVertexAttribArrays(gl, attribs, state) -{ - let i; - - if (state) - { - const tempAttribState = state.tempAttribState; - const attribState = state.attribState; - - for (i = 0; i < tempAttribState.length; i++) - { - tempAttribState[i] = false; - } - - // set the new attribs - for (i = 0; i < attribs.length; i++) - { - tempAttribState[attribs[i].attribute.location] = true; - } - - for (i = 0; i < attribState.length; i++) - { - if (attribState[i] !== tempAttribState[i]) - { - attribState[i] = tempAttribState[i]; - - if (state.attribState[i]) - { - gl.enableVertexAttribArray(i); - } - else - { - gl.disableVertexAttribArray(i); - } - } - } - } - else - { - for (i = 0; i < attribs.length; i++) - { - const attrib = attribs[i]; - - gl.enableVertexAttribArray(attrib.attribute.location); - } - } -} diff --git a/packages/core/src/renderers/systems/index.js b/packages/core/src/renderers/systems/index.js deleted file mode 100644 index c843f03..0000000 --- a/packages/core/src/renderers/systems/index.js +++ /dev/null @@ -1,6 +0,0 @@ -/** - * Systems are individual components to the Renderer pipeline. - * @namespace PIXI.systems - */ -export { default as WebGLSystem } from './WebGLSystem'; -export { default as FilterSystem } from './filter/FilterSystem'; diff --git a/packages/core/src/renderers/systems/shader/GLProgram.js b/packages/core/src/renderers/systems/shader/GLProgram.js deleted file mode 100644 index 92980ca..0000000 --- a/packages/core/src/renderers/systems/shader/GLProgram.js +++ /dev/null @@ -1,43 +0,0 @@ -/** - * Helper class to create a webGL Program - * - * @class - * @private - */ -export default class GLProgram -{ - constructor(program, uniformData) - { - /** - * The shader program - * - * @member {WebGLProgram} - */ - this.program = program; - - /** - * holds the uniform data which contains uniform locations - * and current uniform values used for caching and preventing undeed GPU commands - * @type {Object} - */ - this.uniformData = uniformData; - - /** - * uniformGroups holds the various upload functions for the shader. Each uniform group - * and program have a unique upload function generated. - * @type {Object} - */ - this.uniformGroups = {}; - } - - /** - * Destroys this program - * TODO - */ - destroy() - { - this.uniformData = null; - this.uniformGroups = null; - this.program = null; - } -} diff --git a/packages/core/src/renderers/systems/shader/ShaderSystem.js b/packages/core/src/renderers/systems/shader/ShaderSystem.js deleted file mode 100644 index 6cf57b6..0000000 --- a/packages/core/src/renderers/systems/shader/ShaderSystem.js +++ /dev/null @@ -1,170 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; -import GLProgram from './GLProgram'; -import generateUniformsSync from '../../../shader/generateUniformsSync'; -import defaultValue from '../../../shader/defaultValue'; -import compileProgram from './shader/compileProgram'; - -let UID = 0; - -/** - * Helper class to create a webGL Texture - * - * @class - * @memberof PIXI - */ -export default class ShaderSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - A reference to the current renderer - */ - constructor(renderer) - { - super(renderer); - - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = null; - - this.shader = null; - this.program = null; - - this.id = UID++; - } - - contextChange(gl) - { - this.gl = gl; - } - - /** - * Changes the current shader to the one given in parameter - * - * @param {PIXI.Shader} shader - the new shader - * @param {boolean} dontSync - false if the shader should automatically sync its uniforms. - * @returns {PIXI.glCore.glProgram} the glProgram that belongs to the shader. - */ - bind(shader, dontSync) - { - shader.uniforms.globals = this.renderer.globalUniforms; - - const program = shader.program; - const glProgram = program.glPrograms[this.renderer.CONTEXT_UID] || this.generateShader(shader); - - this.shader = shader; - - // TODO - some current pixi plugins bypass this.. so it not safe to use yet.. - if (this.program !== program) - { - this.program = program; - this.gl.useProgram(glProgram.program); - } - - if (!dontSync) - { - this.syncUniformGroup(shader.uniformGroup); - } - - return glProgram; - } - - /** - * Uploads the uniforms values to the currently bound shader. - * - * @param {object} uniforms - the uniforms valiues that be applied to the current shader - */ - setUniforms(uniforms) - { - const shader = this.shader.program; - const glProgram = shader.glPrograms[this.renderer.CONTEXT_UID]; - - shader.syncUniforms(glProgram.uniformData, uniforms, this.renderer); - } - - syncUniformGroup(group) - { - const glProgram = this.getglProgram(); - - if (!group.static || group.dirtyId !== glProgram.uniformGroups[group.id]) - { - glProgram.uniformGroups[group.id] = group.dirtyId; - const syncFunc = group.syncUniforms[this.shader.program.id] || this.createSyncGroups(group); - - syncFunc(glProgram.uniformData, group.uniforms, this.renderer); - } - } - - createSyncGroups(group) - { - group.syncUniforms[this.shader.program.id] = generateUniformsSync(group, this.shader.program.uniformData); - - return group.syncUniforms[this.shader.program.id]; - } - - /** - * Returns the underlying GLShade rof the currently bound shader. - * This can be handy for when you to have a little more control over the setting of your uniforms. - * - * @return {PIXI.glCore.Shader} the glProgram for the currently bound Shader for this context - */ - getglProgram() - { - if (this.shader) - { - return this.shader.program.glPrograms[this.renderer.CONTEXT_UID]; - } - - return null; - } - - /** - * Generates a glProgram verion of the Shader provided. - * - * @private - * @param {PIXI.Shader} shader the shader that the glProgram will be based on. - * @return {PIXI.glCore.glProgram} A shiney new glProgram - */ - generateShader(shader) - { - const gl = this.gl; - - const program = shader.program; - - const attribMap = {}; - - for (const i in program.attributeData) - { - attribMap[i] = program.attributeData[i].location; - } - - const shaderProgram = compileProgram(gl, program.vertexSrc, program.fragmentSrc, attribMap); - const uniformData = {}; - - for (const i in program.uniformData) - { - const data = program.uniformData[i]; - - uniformData[i] = { - location: gl.getUniformLocation(shaderProgram, i), - value: defaultValue(data.type, data.size), - }; - } - - const glProgram = new GLProgram(shaderProgram, uniformData); - - program.glPrograms[this.renderer.CONTEXT_UID] = glProgram; - - return glProgram; - } - - /** - * Destroys this System and removes all its textures - */ - destroy() - { - // TODO implement destroy method for ShaderSystem - this.destroyed = true; - } -} diff --git a/packages/core/src/renderers/systems/shader/shader/compileProgram.js b/packages/core/src/renderers/systems/shader/shader/compileProgram.js deleted file mode 100644 index 8c6ccb9..0000000 --- a/packages/core/src/renderers/systems/shader/shader/compileProgram.js +++ /dev/null @@ -1,78 +0,0 @@ -/** - * @method compileProgram - * @memberof PIXI.glCore.shader - * @param gl {WebGLRenderingContext} The current WebGL context {WebGLProgram} - * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. - * @param fragmentSrc {string|string[]} The fragment shader source as an array of strings. - * @param attributeLocations {Object} An attribute location map that lets you manually set the attribute locations - * @return {WebGLProgram} the shader program - */ -export default function compileProgram(gl, vertexSrc, fragmentSrc, attributeLocations) -{ - const glVertShader = compileShader(gl, gl.VERTEX_SHADER, vertexSrc); - const glFragShader = compileShader(gl, gl.FRAGMENT_SHADER, fragmentSrc); - - let program = gl.createProgram(); - - gl.attachShader(program, glVertShader); - gl.attachShader(program, glFragShader); - - // optionally, set the attributes manually for the program rather than letting WebGL decide.. - if (attributeLocations) - { - for (const i in attributeLocations) - { - gl.bindAttribLocation(program, attributeLocations[i], i); - } - } - - gl.linkProgram(program); - - // if linking fails, then log and cleanup - if (!gl.getProgramParameter(program, gl.LINK_STATUS)) - { - console.error('Pixi.js Error: Could not initialize shader.'); - console.error('gl.VALIDATE_STATUS', gl.getProgramParameter(program, gl.VALIDATE_STATUS)); - console.error('gl.getError()', gl.getError()); - - // if there is a program info log, log it - if (gl.getProgramInfoLog(program) !== '') - { - console.warn('Pixi.js Warning: gl.getProgramInfoLog()', gl.getProgramInfoLog(program)); - } - - gl.deleteProgram(program); - program = null; - } - - // clean up some shaders - gl.deleteShader(glVertShader); - gl.deleteShader(glFragShader); - - return program; -} - -/** - * @private - * @param gl {WebGLRenderingContext} The current WebGL context {WebGLProgram} - * @param type {Number} the type, can be either VERTEX_SHADER or FRAGMENT_SHADER - * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. - * @return {WebGLShader} the shader - */ -function compileShader(gl, type, src) -{ - const shader = gl.createShader(type); - - gl.shaderSource(shader, src); - gl.compileShader(shader); - - if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) - { - console.warn(src); - console.error(gl.getShaderInfoLog(shader)); - - return null; - } - - return shader; -} diff --git a/packages/core/src/renderers/systems/shader/shader/defaultValue.js b/packages/core/src/renderers/systems/shader/shader/defaultValue.js deleted file mode 100644 index 97290bd..0000000 --- a/packages/core/src/renderers/systems/shader/shader/defaultValue.js +++ /dev/null @@ -1,79 +0,0 @@ -/** - * @method defaultValue - * @memberof PIXI.glCore.shader - * @param type {String} Type of value - * @param size {Number} - */ -export default function defaultValue(type, size) -{ - switch (type) - { - case 'float': - return 0; - - case 'vec2': - return new Float32Array(2 * size); - - case 'vec3': - return new Float32Array(3 * size); - - case 'vec4': - return new Float32Array(4 * size); - - case 'int': - case 'sampler2D': - case 'sampler2DArray': - return 0; - - case 'ivec2': - return new Int32Array(2 * size); - - case 'ivec3': - return new Int32Array(3 * size); - - case 'ivec4': - return new Int32Array(4 * size); - - case 'bool': - return false; - - case 'bvec2': - - return booleanArray(2 * size); - - case 'bvec3': - return booleanArray(3 * size); - - case 'bvec4': - return booleanArray(4 * size); - - case 'mat2': - return new Float32Array([1, 0, - 0, 1]); - - case 'mat3': - return new Float32Array([1, 0, 0, - 0, 1, 0, - 0, 0, 1]); - - case 'mat4': - return new Float32Array([1, 0, 0, 0, - 0, 1, 0, 0, - 0, 0, 1, 0, - 0, 0, 0, 1]); - } - - return null; -} - -function booleanArray(size) -{ - const array = new Array(size); - - for (let i = 0; i < array.length; i++) - { - array[i] = false; - } - - return array; -} diff --git a/packages/core/src/renderers/systems/shader/shader/index.js b/packages/core/src/renderers/systems/shader/shader/index.js deleted file mode 100644 index ba7a317..0000000 --- a/packages/core/src/renderers/systems/shader/shader/index.js +++ /dev/null @@ -1,8 +0,0 @@ -/** - * @namespace PIXI.glCore.shader - */ -export { default as compileProgram } from './compileProgram'; -export { default as defaultValue } from './defaultValue'; -export { default as setPrecision } from './setPrecision'; -export { default as mapSize } from './mapSize'; -export { default as mapType } from './mapType'; diff --git a/packages/core/src/renderers/systems/shader/shader/mapSize.js b/packages/core/src/renderers/systems/shader/shader/mapSize.js deleted file mode 100644 index b681554..0000000 --- a/packages/core/src/renderers/systems/shader/shader/mapSize.js +++ /dev/null @@ -1,33 +0,0 @@ -const GLSL_TO_SIZE = { - float: 1, - vec2: 2, - vec3: 3, - vec4: 4, - - int: 1, - ivec2: 2, - ivec3: 3, - ivec4: 4, - - bool: 1, - bvec2: 2, - bvec3: 3, - bvec4: 4, - - mat2: 4, - mat3: 9, - mat4: 16, - - sampler2D: 1, -}; - -/** - * @method mapSize - * @memberof PIXI.glCore.shader - * @param type {String} - * @return {Number} - */ -export default function mapSize(type) -{ - return GLSL_TO_SIZE[type]; -} diff --git a/packages/core/src/renderers/systems/shader/shader/mapType.js b/packages/core/src/renderers/systems/shader/shader/mapType.js deleted file mode 100644 index d288b7c..0000000 --- a/packages/core/src/renderers/systems/shader/shader/mapType.js +++ /dev/null @@ -1,45 +0,0 @@ -let GL_TABLE = null; - -const GL_TO_GLSL_TYPES = { - FLOAT: 'float', - FLOAT_VEC2: 'vec2', - FLOAT_VEC3: 'vec3', - FLOAT_VEC4: 'vec4', - - INT: 'int', - INT_VEC2: 'ivec2', - INT_VEC3: 'ivec3', - INT_VEC4: 'ivec4', - - BOOL: 'bool', - BOOL_VEC2: 'bvec2', - BOOL_VEC3: 'bvec3', - BOOL_VEC4: 'bvec4', - - FLOAT_MAT2: 'mat2', - FLOAT_MAT3: 'mat3', - FLOAT_MAT4: 'mat4', - - SAMPLER_2D: 'sampler2D', - SAMPLER_CUBE: 'samplerCube', - SAMPLER_2D_ARRAY: 'sampler2DArray', -}; - -export default function mapType(gl, type) -{ - if (!GL_TABLE) - { - const typeNames = Object.keys(GL_TO_GLSL_TYPES); - - GL_TABLE = {}; - - for (let i = 0; i < typeNames.length; ++i) - { - const tn = typeNames[i]; - - GL_TABLE[gl[tn]] = GL_TO_GLSL_TYPES[tn]; - } - } - - return GL_TABLE[type]; -} diff --git a/packages/core/src/renderers/systems/shader/shader/setPrecision.js b/packages/core/src/renderers/systems/shader/shader/setPrecision.js deleted file mode 100644 index e2c1f34..0000000 --- a/packages/core/src/renderers/systems/shader/shader/setPrecision.js +++ /dev/null @@ -1,18 +0,0 @@ -/** - * Sets the float precision on the shader. If the precision is already present this function will do nothing - * @param {string} src the shader source - * @param {string} precision The float precision of the shader. Options are 'lowp', 'mediump' or 'highp'. - * - * @return {string} modified shader source - */ -export default function setPrecision(src, precision) -{ - src = src.trim(); - - if (src.substring(0, 9) !== 'precision' && src.substring(0, 1) !== '#') - { - return `precision ${precision} float;\n${src}`; - } - - return src; -} diff --git a/packages/core/src/renderers/systems/textures/GLTexture.js b/packages/core/src/renderers/systems/textures/GLTexture.js deleted file mode 100644 index c3fb8dc..0000000 --- a/packages/core/src/renderers/systems/textures/GLTexture.js +++ /dev/null @@ -1,33 +0,0 @@ -export default class GLTexture -{ - constructor(texture) - { - /** - * The WebGL texture - * - * @member {WebGLTexture} - */ - this.texture = texture; - - this.width = -1; - this.height = -1; - - /** - * Texture contents dirty flag - * @member {number} - */ - this.dirtyId = -1; - - /** - * Texture style dirty flag - * @type {number} - */ - this.dirtyStyleId = -1; - - /** - * Whether mip levels has to be generated - * @type {boolean} - */ - this.mipmap = false; - } -} diff --git a/packages/core/src/renderers/systems/textures/TextureGCSystem.js b/packages/core/src/renderers/systems/textures/TextureGCSystem.js deleted file mode 100644 index b089938..0000000 --- a/packages/core/src/renderers/systems/textures/TextureGCSystem.js +++ /dev/null @@ -1,110 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; -import { GC_MODES } from '@pixi/constants'; -import { settings } from '@pixi/settings'; - -/** - * TextureGarbageCollector. This class manages the GPU and ensures that it does not get clogged - * up with textures that are no longer being used. - * - * @class - * @memberof PIXI - */ -export default class TextureGCSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this.count = 0; - this.checkCount = 0; - this.maxIdle = settings.GC_MAX_IDLE; - this.checkCountMax = settings.GC_MAX_CHECK_COUNT; - this.mode = settings.GC_MODE; - } - - /** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ - postrender() - { - this.count++; - - if (this.mode === GC_MODES.MANUAL) - { - return; - } - - this.checkCount++; - - if (this.checkCount > this.checkCountMax) - { - this.checkCount = 0; - - this.run(); - } - } - - /** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ - run() - { - const tm = this.renderer.texture; - const managedTextures = tm.managedTextures; - let wasRemoved = false; - - for (let i = 0; i < managedTextures.length; i++) - { - const texture = managedTextures[i]; - - // only supports non generated textures at the moment! - if (!texture.frameBuffer && this.count - texture.touched > this.maxIdle) - { - tm.destroyTexture(texture, true); - managedTextures[i] = null; - wasRemoved = true; - } - } - - if (wasRemoved) - { - let j = 0; - - for (let i = 0; i < managedTextures.length; i++) - { - if (managedTextures[i] !== null) - { - managedTextures[j++] = managedTextures[i]; - } - } - - managedTextures.length = j; - } - } - - /** - * Removes all the textures within the specified displayObject and its children from the GPU - * - * @param {PIXI.DisplayObject} displayObject - the displayObject to remove the textures from. - */ - unload(displayObject) - { - const tm = this.renderer.textureSystem; - - // only destroy non generated textures - if (displayObject._texture && displayObject._texture._glRenderTargets) - { - tm.destroyTexture(displayObject._texture); - } - - for (let i = displayObject.children.length - 1; i >= 0; i--) - { - this.unload(displayObject.children[i]); - } - } -} diff --git a/packages/core/src/renderers/systems/textures/TextureSystem.js b/packages/core/src/renderers/systems/textures/TextureSystem.js deleted file mode 100644 index ccc5de9..0000000 --- a/packages/core/src/renderers/systems/textures/TextureSystem.js +++ /dev/null @@ -1,281 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; -import GLTexture from './GLTexture'; -import { removeItems } from '@pixi/utils'; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class TextureSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - // TODO set to max textures... - this.boundTextures = [ - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - ]; - - this.currentLocation = -1; - - this.managedTextures = []; - } - - /** - * Sets up the renderer context and necessary buffers. - * - * @private - */ - contextChange() - { - const gl = this.gl = this.renderer.gl; - - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - - // TODO move this.. to a nice make empty textures class.. - this.emptyTextures = {}; - - const emptyTexture2D = new GLTexture(gl.createTexture()); - - gl.bindTexture(gl.TEXTURE_2D, emptyTexture2D.texture); - gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array(4)); - - this.emptyTextures[gl.TEXTURE_2D] = emptyTexture2D; - this.emptyTextures[gl.TEXTURE_CUBE_MAP] = new GLTexture(gl.createTexture()); - - gl.bindTexture(gl.TEXTURE_CUBE_MAP, this.emptyTextures[gl.TEXTURE_CUBE_MAP].texture); - - let i; - - for (i = 0; i < 6; i++) - { - gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, null); - } - - gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_MAG_FILTER, gl.LINEAR); - gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_MIN_FILTER, gl.LINEAR); - - for (i = 0; i < this.boundTextures.length; i++) - { - this.bind(null, i); - } - } - - bind(texture, location) - { - const gl = this.gl; - - location = location || 0; - - if (this.currentLocation !== location) - { - this.currentLocation = location; - gl.activeTexture(gl.TEXTURE0 + location); - } - - if (texture) - { - texture = texture.baseTexture || texture; - - if (texture.valid) - { - texture.touched = this.renderer.textureGC.count; - - const glTexture = texture._glTextures[this.CONTEXT_UID] || this.initTexture(texture); - - gl.bindTexture(texture.target, glTexture.texture); - - if (glTexture.dirtyId !== texture.dirtyId) - { - this.updateTexture(texture); - } - - this.boundTextures[location] = texture; - } - } - else - { - gl.bindTexture(gl.TEXTURE_2D, this.emptyTextures[gl.TEXTURE_2D].texture); - this.boundTextures[location] = null; - } - } - - unbind(texture) - { - const gl = this.gl; - - for (let i = 0; i < this.boundTextures.length; i++) - { - if (this.boundTextures[i] === texture) - { - if (this.currentLocation !== i) - { - gl.activeTexture(gl.TEXTURE0 + i); - this.currentLocation = i; - } - - gl.bindTexture(gl.TEXTURE_2D, this.emptyTextures[texture.target].texture); - this.boundTextures[i] = null; - } - } - } - - initTexture(texture) - { - const glTexture = new GLTexture(this.gl.createTexture()); - - // guarentee an update.. - glTexture.dirtyId = -1; - - texture._glTextures[this.CONTEXT_UID] = glTexture; - - this.managedTextures.push(texture); - texture.on('dispose', this.destroyTexture, this); - - return glTexture; - } - - updateTexture(texture) - { - const glTexture = texture._glTextures[this.CONTEXT_UID]; - const renderer = this.renderer; - - if (texture.resource && texture.resource.upload(renderer, texture, glTexture)) - { - // texture is uploaded, dont do anything! - } - else - { - // default, renderTexture-like logic - const width = texture.realWidth; - const height = texture.realHeight; - const gl = renderer.gl; - - if (glTexture.width !== width - || glTexture.height !== height - || glTexture.dirtyId < 0) - { - glTexture.width = width; - glTexture.height = height; - - gl.texImage2D(texture.target, 0, - texture.format, - width, - height, - 0, - texture.format, - texture.type, - null); - } - } - - // lets only update what changes.. - if (texture.dirtyStyleId !== glTexture.dirtyStyleId) - { - this.updateTextureStyle(texture); - } - glTexture.dirtyId = texture.dirtyId; - } - - /** - * Deletes the texture from WebGL - * - * @param {PIXI.BaseTexture|PIXI.Texture} texture - the texture to destroy - * @param {boolean} [skipRemove=false] - Whether to skip removing the texture from the TextureManager. - */ - destroyTexture(texture, skipRemove) - { - const gl = this.gl; - - texture = texture.baseTexture || texture; - - if (texture._glTextures[this.renderer.CONTEXT_UID]) - { - this.unbind(texture); - - gl.deleteTexture(texture._glTextures[this.renderer.CONTEXT_UID].texture); - texture.off('dispose', this.destroyTexture, this); - - delete texture._glTextures[this.renderer.CONTEXT_UID]; - - if (!skipRemove) - { - const i = this.managedTextures.indexOf(texture); - - if (i !== -1) - { - removeItems(this.managedTextures, i, 1); - } - } - } - } - - updateTextureStyle(texture) - { - const glTexture = texture._glTextures[this.CONTEXT_UID]; - - glTexture.mipmap = texture.mipmap && texture.isPowerOfTwo; - if (!glTexture) - { - return; - } - - if (texture.resource && texture.resource.style(this.renderer, texture, glTexture)) - { - // style is set, dont do anything! - } - else - { - this.setStyle(texture, glTexture); - } - - glTexture.dirtyStyleId = texture.dirtyStyleId; - } - - setStyle(texture, glTexture) - { - const gl = this.gl; - - if (glTexture.mipmap) - { - gl.generateMipmap(texture.target); - } - - gl.texParameteri(texture.target, gl.TEXTURE_WRAP_S, texture.wrapMode); - gl.texParameteri(texture.target, gl.TEXTURE_WRAP_T, texture.wrapMode); - - if (glTexture.mipmap) - { - /* eslint-disable max-len */ - gl.texParameteri(texture.target, gl.TEXTURE_MIN_FILTER, texture.scaleMode ? gl.LINEAR_MIPMAP_LINEAR : gl.NEAREST_MIPMAP_NEAREST); - /* eslint-disable max-len */ - } - else - { - gl.texParameteri(texture.target, gl.TEXTURE_MIN_FILTER, texture.scaleMode ? gl.LINEAR : gl.NEAREST); - } - - gl.texParameteri(texture.target, gl.TEXTURE_MAG_FILTER, texture.scaleMode ? gl.LINEAR : gl.NEAREST); - } -} diff --git a/packages/core/src/renderers/utils/ObjectRenderer.js b/packages/core/src/renderers/utils/ObjectRenderer.js deleted file mode 100644 index f67a273..0000000 --- a/packages/core/src/renderers/utils/ObjectRenderer.js +++ /dev/null @@ -1,48 +0,0 @@ -import WebGLSystem from '../systems/WebGLSystem'; - -/** - * Base for a common object renderer that can be used as a system renderer plugin. - * - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class ObjectRenderer extends WebGLSystem -{ - /** - * Starts the renderer and sets the shader - * - */ - start() - { - // set the shader.. - } - - /** - * Stops the renderer - * - */ - stop() - { - this.flush(); - } - - /** - * Stub method for rendering content and emptying the current batch. - * - */ - flush() - { - // flush! - } - - /** - * Renders an object - * - * @param {PIXI.DisplayObject} object - The object to render. - */ - render(object) // eslint-disable-line no-unused-vars - { - // render the object - } -} diff --git a/packages/core/src/renderers/utils/Quad.js b/packages/core/src/renderers/utils/Quad.js deleted file mode 100644 index 5c1517b..0000000 --- a/packages/core/src/renderers/utils/Quad.js +++ /dev/null @@ -1,26 +0,0 @@ -import Geometry from '../../geometry/Geometry'; - -/** - * Helper class to create a quad - * - * @class - * @memberof PIXI - */ -export default class Quad extends Geometry -{ - /** - * just a quad - */ - constructor() - { - super(); - - this.addAttribute('aVertexPosition', [ - 0, 0, - 1, 0, - 1, 1, - 0, 1, - ]) - .addIndex([0, 1, 3, 2]); - } -} diff --git a/packages/core/src/renderers/utils/QuadUv.js b/packages/core/src/renderers/utils/QuadUv.js deleted file mode 100644 index 97bda89..0000000 --- a/packages/core/src/renderers/utils/QuadUv.js +++ /dev/null @@ -1,103 +0,0 @@ -import Geometry from '../../geometry/Geometry'; -import Buffer from '../../geometry/Buffer'; - -/** - * Helper class to create a quad with uvs like in v4 - * - * @class - * @memberof PIXI - */ -export default class QuadUv extends Geometry -{ - constructor() - { - super(); - - /** - * An array of vertices - * - * @member {Float32Array} - */ - this.vertices = new Float32Array([ - -1, -1, - 1, -1, - 1, 1, - -1, 1, - ]); - - /** - * The Uvs of the quad - * - * @member {Float32Array} - */ - this.uvs = new Float32Array([ - 0, 0, - 1, 0, - 1, 1, - 0, 1, - ]); - - this.vertexBuffer = new Buffer(this.vertices); - this.uvBuffer = new Buffer(this.uvs); - - this.addAttribute('aVertexPosition', this.vertexBuffer) - .addAttribute('aTextureCoord', this.uvBuffer) - .addIndex([0, 1, 2, 0, 2, 3]); - } - - /** - * Maps two Rectangle to the quad. - * - * @param {PIXI.Rectangle} targetTextureFrame - the first rectangle - * @param {PIXI.Rectangle} destinationFrame - the second rectangle - * @return {PIXI.Quad} Returns itself. - */ - map(targetTextureFrame, destinationFrame) - { - let x = 0; // destinationFrame.x / targetTextureFrame.width; - let y = 0; // destinationFrame.y / targetTextureFrame.height; - - this.uvs[0] = x; - this.uvs[1] = y; - - this.uvs[2] = x + (destinationFrame.width / targetTextureFrame.width); - this.uvs[3] = y; - - this.uvs[4] = x + (destinationFrame.width / targetTextureFrame.width); - this.uvs[5] = y + (destinationFrame.height / targetTextureFrame.height); - - this.uvs[6] = x; - this.uvs[7] = y + (destinationFrame.height / targetTextureFrame.height); - - x = destinationFrame.x; - y = destinationFrame.y; - - this.vertices[0] = x; - this.vertices[1] = y; - - this.vertices[2] = x + destinationFrame.width; - this.vertices[3] = y; - - this.vertices[4] = x + destinationFrame.width; - this.vertices[5] = y + destinationFrame.height; - - this.vertices[6] = x; - this.vertices[7] = y + destinationFrame.height; - - this.invalidate(); - - return this; - } - - /** - * legacy upload method, just marks buffers dirty - * @returns {PIXI.QuadUv} Returns itself. - */ - invalidate() - { - this.vertexBuffer._updateID++; - this.uvBuffer._updateID++; - - return this; - } -} diff --git a/packages/core/src/renderers/utils/checkMaxIfStatmentsInShader.js b/packages/core/src/renderers/utils/checkMaxIfStatmentsInShader.js deleted file mode 100644 index 8047c96..0000000 --- a/packages/core/src/renderers/utils/checkMaxIfStatmentsInShader.js +++ /dev/null @@ -1,58 +0,0 @@ -const fragTemplate = [ - 'precision mediump float;', - 'void main(void){', - 'float test = 0.1;', - '%forloop%', - 'gl_FragColor = vec4(0.0);', - '}', -].join('\n'); - -export default function checkMaxIfStatmentsInShader(maxIfs, gl) -{ - if (maxIfs === 0) - { - throw new Error('Invalid value of `0` passed to `checkMaxIfStatementsInShader`'); - } - - const shader = gl.createShader(gl.FRAGMENT_SHADER); - - while (true) // eslint-disable-line no-constant-condition - { - const fragmentSrc = fragTemplate.replace(/%forloop%/gi, generateIfTestSrc(maxIfs)); - - gl.shaderSource(shader, fragmentSrc); - gl.compileShader(shader); - - if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) - { - maxIfs = (maxIfs / 2) | 0; - } - else - { - // valid! - break; - } - } - - return maxIfs; -} - -function generateIfTestSrc(maxIfs) -{ - let src = ''; - - for (let i = 0; i < maxIfs; ++i) - { - if (i > 0) - { - src += '\nelse '; - } - - if (i < maxIfs - 1) - { - src += `if(test == ${i}.0){}`; - } - } - - return src; -} diff --git a/packages/core/src/renderers/utils/mapWebGLBlendModesToPixi.js b/packages/core/src/renderers/utils/mapWebGLBlendModesToPixi.js deleted file mode 100644 index 10ffe4d..0000000 --- a/packages/core/src/renderers/utils/mapWebGLBlendModesToPixi.js +++ /dev/null @@ -1,47 +0,0 @@ -import { BLEND_MODES } from '@pixi/constants'; - -/** - * Maps gl blend combinations to WebGL. - * - * @memberof PIXI - * @function mapWebGLBlendModesToPixi - * @private - * @param {WebGLRenderingContext} gl - The rendering context. - * @param {string[]} [array=[]] - The array to output into. - * @return {string[]} Mapped modes. - */ -export default function mapWebGLBlendModesToPixi(gl, array = []) -{ - // TODO - premultiply alpha would be different. - // add a boolean for that! - array[BLEND_MODES.NORMAL] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.ADD] = [gl.ONE, gl.DST_ALPHA]; - array[BLEND_MODES.MULTIPLY] = [gl.DST_COLOR, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.SCREEN] = [gl.ONE, gl.ONE_MINUS_SRC_COLOR]; - array[BLEND_MODES.OVERLAY] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.DARKEN] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.LIGHTEN] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.COLOR_DODGE] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.COLOR_BURN] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.HARD_LIGHT] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.SOFT_LIGHT] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.DIFFERENCE] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.EXCLUSION] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.HUE] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.SATURATION] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.COLOR] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.LUMINOSITY] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.NONE] = [0, 0]; - - // not-premultiplied blend modes - array[BLEND_MODES.NORMAL_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.ADD_NPM] = [gl.SRC_ALPHA, gl.DST_ALPHA, gl.ONE, gl.DST_ALPHA]; - array[BLEND_MODES.SCREEN_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_COLOR, gl.ONE, gl.ONE_MINUS_SRC_COLOR]; - - // not-premultiplied blend modes - array[BLEND_MODES.NORMAL_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.ADD_NPM] = [gl.SRC_ALPHA, gl.DST_ALPHA, gl.ONE, gl.DST_ALPHA]; - array[BLEND_MODES.SCREEN_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_COLOR, gl.ONE, gl.ONE_MINUS_SRC_COLOR]; - - return array; -} diff --git a/packages/core/src/shader/GLProgram.js b/packages/core/src/shader/GLProgram.js new file mode 100644 index 0000000..92980ca --- /dev/null +++ b/packages/core/src/shader/GLProgram.js @@ -0,0 +1,43 @@ +/** + * Helper class to create a webGL Program + * + * @class + * @private + */ +export default class GLProgram +{ + constructor(program, uniformData) + { + /** + * The shader program + * + * @member {WebGLProgram} + */ + this.program = program; + + /** + * holds the uniform data which contains uniform locations + * and current uniform values used for caching and preventing undeed GPU commands + * @type {Object} + */ + this.uniformData = uniformData; + + /** + * uniformGroups holds the various upload functions for the shader. Each uniform group + * and program have a unique upload function generated. + * @type {Object} + */ + this.uniformGroups = {}; + } + + /** + * Destroys this program + * TODO + */ + destroy() + { + this.uniformData = null; + this.uniformGroups = null; + this.program = null; + } +} diff --git a/packages/core/src/shader/Program.js b/packages/core/src/shader/Program.js index 4f9971e..4b4007a 100644 --- a/packages/core/src/shader/Program.js +++ b/packages/core/src/shader/Program.js @@ -1,6 +1,11 @@ -import * as shaderUtils from '../renderers/systems/shader/shader'; +// import * as from '../systems/shader/shader'; +import { setPrecision, + defaultValue, + compileProgram, + mapSize, + mapType, + getTestContext } from './utils'; import { ProgramCache } from '@pixi/utils'; -import getTestContext from './getTestContext'; import defaultFragment from './defaultProgram.frag'; import defaultVertex from './defaultProgram.vert'; import { settings } from '@pixi/settings'; @@ -34,8 +39,8 @@ */ this.fragmentSrc = fragmentSrc || Program.defaultFragmentSrc; - this.vertexSrc = shaderUtils.setPrecision(this.vertexSrc, settings.PRECISION_VERTEX); - this.fragmentSrc = shaderUtils.setPrecision(this.fragmentSrc, settings.PRECISION_FRAGMENT); + this.vertexSrc = setPrecision(this.vertexSrc, settings.PRECISION_VERTEX); + this.fragmentSrc = setPrecision(this.fragmentSrc, settings.PRECISION_FRAGMENT); // currently this does not extract structs only default types this.extractData(this.vertexSrc, this.fragmentSrc); @@ -62,7 +67,7 @@ if (gl) { - const program = shaderUtils.compileProgram(gl, vertexSrc, fragmentSrc); + const program = compileProgram(gl, vertexSrc, fragmentSrc); this.attributeData = this.getAttributeData(program, gl); this.uniformData = this.getUniformData(program, gl); @@ -95,13 +100,13 @@ for (let i = 0; i < totalAttributes; i++) { const attribData = gl.getActiveAttrib(program, i); - const type = shaderUtils.mapType(gl, attribData.type); + const type = mapType(gl, attribData.type); /*eslint-disable */ const data = { type: type, name: attribData.name, - size: shaderUtils.mapSize(type), + size: mapSize(type), location: 0, }; /* eslint-enable */ @@ -145,14 +150,14 @@ const name = uniformData.name.replace(/\[.*?\]/, ''); const isArray = uniformData.name.match(/\[.*?\]/, ''); - const type = shaderUtils.mapType(gl, uniformData.type); + const type = mapType(gl, uniformData.type); /*eslint-disable */ uniforms[name] = { type: type, size: uniformData.size, isArray:isArray, - value: shaderUtils.defaultValue(type, uniformData.size), + value: defaultValue(type, uniformData.size), }; /* eslint-enable */ } diff --git a/packages/core/src/shader/ShaderSystem.js b/packages/core/src/shader/ShaderSystem.js new file mode 100644 index 0000000..40b8af8 --- /dev/null +++ b/packages/core/src/shader/ShaderSystem.js @@ -0,0 +1,171 @@ +import System from '../System'; +import GLProgram from './GLProgram'; +import { generateUniformsSync, + defaultValue, + compileProgram } from './utils'; + +let UID = 0; + +/** + * Helper class to create a webGL Texture + * + * @class + * @memberof PIXI.systems + * @extends PIXI.System + */ +export default class ShaderSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - A reference to the current renderer + */ + constructor(renderer) + { + super(renderer); + + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = null; + + this.shader = null; + this.program = null; + + this.id = UID++; + } + + contextChange(gl) + { + this.gl = gl; + } + + /** + * Changes the current shader to the one given in parameter + * + * @param {PIXI.Shader} shader - the new shader + * @param {boolean} dontSync - false if the shader should automatically sync its uniforms. + * @returns {PIXI.glCore.glProgram} the glProgram that belongs to the shader. + */ + bind(shader, dontSync) + { + shader.uniforms.globals = this.renderer.globalUniforms; + + const program = shader.program; + const glProgram = program.glPrograms[this.renderer.CONTEXT_UID] || this.generateShader(shader); + + this.shader = shader; + + // TODO - some current pixi plugins bypass this.. so it not safe to use yet.. + if (this.program !== program) + { + this.program = program; + this.gl.useProgram(glProgram.program); + } + + if (!dontSync) + { + this.syncUniformGroup(shader.uniformGroup); + } + + return glProgram; + } + + /** + * Uploads the uniforms values to the currently bound shader. + * + * @param {object} uniforms - the uniforms valiues that be applied to the current shader + */ + setUniforms(uniforms) + { + const shader = this.shader.program; + const glProgram = shader.glPrograms[this.renderer.CONTEXT_UID]; + + shader.syncUniforms(glProgram.uniformData, uniforms, this.renderer); + } + + syncUniformGroup(group) + { + const glProgram = this.getglProgram(); + + if (!group.static || group.dirtyId !== glProgram.uniformGroups[group.id]) + { + glProgram.uniformGroups[group.id] = group.dirtyId; + const syncFunc = group.syncUniforms[this.shader.program.id] || this.createSyncGroups(group); + + syncFunc(glProgram.uniformData, group.uniforms, this.renderer); + } + } + + createSyncGroups(group) + { + group.syncUniforms[this.shader.program.id] = generateUniformsSync(group, this.shader.program.uniformData); + + return group.syncUniforms[this.shader.program.id]; + } + + /** + * Returns the underlying GLShade rof the currently bound shader. + * This can be handy for when you to have a little more control over the setting of your uniforms. + * + * @return {PIXI.glCore.Shader} the glProgram for the currently bound Shader for this context + */ + getglProgram() + { + if (this.shader) + { + return this.shader.program.glPrograms[this.renderer.CONTEXT_UID]; + } + + return null; + } + + /** + * Generates a glProgram verion of the Shader provided. + * + * @private + * @param {PIXI.Shader} shader the shader that the glProgram will be based on. + * @return {PIXI.glCore.glProgram} A shiney new glProgram + */ + generateShader(shader) + { + const gl = this.gl; + + const program = shader.program; + + const attribMap = {}; + + for (const i in program.attributeData) + { + attribMap[i] = program.attributeData[i].location; + } + + const shaderProgram = compileProgram(gl, program.vertexSrc, program.fragmentSrc, attribMap); + const uniformData = {}; + + for (const i in program.uniformData) + { + const data = program.uniformData[i]; + + uniformData[i] = { + location: gl.getUniformLocation(shaderProgram, i), + value: defaultValue(data.type, data.size), + }; + } + + const glProgram = new GLProgram(shaderProgram, uniformData); + + program.glPrograms[this.renderer.CONTEXT_UID] = glProgram; + + return glProgram; + } + + /** + * Destroys this System and removes all its textures + */ + destroy() + { + // TODO implement destroy method for ShaderSystem + this.destroyed = true; + } +} diff --git a/packages/core/src/shader/defaultValue.js b/packages/core/src/shader/defaultValue.js deleted file mode 100644 index 847a069..0000000 --- a/packages/core/src/shader/defaultValue.js +++ /dev/null @@ -1,78 +0,0 @@ -function booleanArray(size) -{ - const array = new Array(size); - - for (let i = 0; i < array.length; i++) - { - array[i] = false; - } - - return array; -} - -/** - * @class - * @memberof PIXI.glCore.shader - * @param type {String} Type of value - * @param size {Number} - */ -export default function defaultValue(type, size) -{ - switch (type) - { - case 'float': - return 0; - - case 'vec2': - return new Float32Array(2 * size); - - case 'vec3': - return new Float32Array(3 * size); - - case 'vec4': - return new Float32Array(4 * size); - - case 'int': - case 'sampler2D': - return 0; - - case 'ivec2': - return new Int32Array(2 * size); - - case 'ivec3': - return new Int32Array(3 * size); - - case 'ivec4': - return new Int32Array(4 * size); - - case 'bool': - return false; - - case 'bvec2': - - return booleanArray(2 * size); - - case 'bvec3': - return booleanArray(3 * size); - - case 'bvec4': - return booleanArray(4 * size); - - case 'mat2': - return new Float32Array([1, 0, - 0, 1]); - - case 'mat3': - return new Float32Array([1, 0, 0, - 0, 1, 0, - 0, 0, 1]); - - case 'mat4': - return new Float32Array([1, 0, 0, 0, - 0, 1, 0, 0, - 0, 0, 1, 0, - 0, 0, 0, 1]); - } - - return 0; -} diff --git a/packages/core/src/shader/extractUniformsFromSrc.js b/packages/core/src/shader/extractUniformsFromSrc.js deleted file mode 100644 index 3d02c0a..0000000 --- a/packages/core/src/shader/extractUniformsFromSrc.js +++ /dev/null @@ -1,57 +0,0 @@ -import defaultValue from './defaultValue'; - -export default function extractUniformsFromSrc(vertexSrc, fragmentSrc, mask) -{ - const vertUniforms = extractUniformsFromString(vertexSrc, mask); - const fragUniforms = extractUniformsFromString(fragmentSrc, mask); - - return Object.assign(vertUniforms, fragUniforms); -} - -function extractUniformsFromString(string) -{ - const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); - - const uniforms = {}; - let nameSplit; - - // clean the lines a little - remove extra spaces / teabs etc - // then split along ';' - const lines = string.replace(/\s+/g, ' ') - .split(/\s*;\s*/); - - // loop through.. - for (let i = 0; i < lines.length; i++) - { - const line = lines[i].trim(); - - if (line.indexOf('uniform') > -1) - { - const splitLine = line.split(' '); - const type = splitLine[1]; - - let name = splitLine[2]; - let size = 1; - - if (name.indexOf('[') > -1) - { - // array! - nameSplit = name.split(/\[|]/); - name = nameSplit[0]; - size *= Number(nameSplit[1]); - } - - if (!name.match(maskRegex)) - { - uniforms[name] = { - value: defaultValue(type, size), - dirtyId: 0, - name, - type, - }; - } - } - } - - return uniforms; -} diff --git a/packages/core/src/AbstractRenderer.js b/packages/core/src/AbstractRenderer.js new file mode 100644 index 0000000..64b2eb1 --- /dev/null +++ b/packages/core/src/AbstractRenderer.js @@ -0,0 +1,353 @@ +import { hex2string, hex2rgb } from '@pixi/utils'; +import { Matrix, Rectangle } from '@pixi/math'; +import { RENDERER_TYPE } from '@pixi/constants'; +import { settings } from '@pixi/settings'; +import { Container } from '@pixi/display'; +import RenderTexture from './renderTexture/RenderTexture'; +import EventEmitter from 'eventemitter3'; + +const tempMatrix = new Matrix(); + +/** + * 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 EventEmitter + * @memberof PIXI + */ +export default class AbstractRenderer extends EventEmitter +{ + // eslint-disable-next-line valid-jsdoc + /** + * @param {string} system - The name of the system this renderer is for. + * @param {object} [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.transparent=false] - If the render view is transparent, default false + * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false + * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. The + * resolution of the renderer retina would be 2. + * @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 {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when rendering, + * stopping pixel interpolation. + */ + constructor(system, options, arg2, arg3) + { + super(); + + // Support for constructor(system, screenWidth, screenHeight, options) + if (typeof options === 'number') + { + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + }, arg3); + } + + // 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 = RENDERER_TYPE.UNKNOWN; + + /** + * Measurements of the screen. (0, 0, screenWidth, screenHeight) + * + * Its safe to use as filterArea or hitArea for 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 1 + */ + this.resolution = options.resolution || settings.RESOLUTION; + + /** + * Whether the render view is transparent + * + * @member {boolean} + */ + this.transparent = options.transparent; + + /** + * Whether css dimensions of canvas view should be resized to screen dimensions automatically + * + * @member {boolean} + */ + this.autoResize = options.autoResize || false; + + /** + * Tracks the blend modes useful for this renderer. + * + * @member {object} + */ + this.blendModes = null; + + /** + * 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; + + /** + * If true PixiJS will Math.floor() x/y values when rendering, stopping pixel interpolation. + * Handy for crisp pixel art and speed on legacy devices. + * + * @member {boolean} + */ + this.roundPixels = options.roundPixels; + + /** + * The background color as a number. + * + * @member {number} + * @private + */ + this._backgroundColor = 0x000000; + + /** + * The background color as an [R, G, B] array. + * + * @member {number[]} + * @private + */ + this._backgroundColorRgba = [0, 0, 0, 0]; + + /** + * The background color as a string. + * + * @member {string} + * @private + */ + this._backgroundColorString = '#000000'; + + this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter + + /** + * This temporary display object used as the parent of the currently being rendered item + * + * @member {PIXI.DisplayObject} + * @private + */ + this._tempDisplayObjectParent = new Container(); + + /** + * The last root object that the renderer tried to render. + * + * @member {PIXI.DisplayObject} + * @private + */ + this._lastObjectRendered = this._tempDisplayObjectParent; + + /** + * Collection of plugins. + * @readonly + * @member {object} + */ + this.plugins = {}; + } + + /** + * Initialize the plugins. + * + * @protected + * @param {object} staticMap - The dictionary of staticly saved plugins. + */ + initPlugins(staticMap) + { + 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() + { + return this.view.width; + } + + /** + * Same as view.height, actual number of pixels in the canvas by vertical + * + * @member {number} + * @readonly + * @default 600 + */ + get height() + { + return this.view.height; + } + + /** + * Resizes the screen and canvas to the specified width and height + * Canvas dimensions are multiplied by resolution + * + * @param {number} screenWidth - the new width of the screen + * @param {number} screenHeight - the new height of the screen + */ + resize(screenWidth, screenHeight) + { + this.screen.width = screenWidth; + this.screen.height = screenHeight; + + this.view.width = screenWidth * this.resolution; + this.view.height = screenHeight * this.resolution; + + if (this.autoResize) + { + this.view.style.width = `${screenWidth}px`; + this.view.style.height = `${screenHeight}px`; + } + } + + /** + * 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 {PIXI.DisplayObject} displayObject - The displayObject the object will be generated from + * @param {number} scaleMode - Should be one of the scaleMode consts + * @param {number} resolution - The resolution / device pixel ratio of the texture being generated + * @param {PIXI.Rectangle} [region] - The region of the displayObject, that shall be rendered, + * if no region is specified, defaults to the local bounds of the displayObject. + * @return {PIXI.Texture} a texture of the graphics object + */ + generateTexture(displayObject, scaleMode, resolution, region) + { + region = region || displayObject.getLocalBounds(); + + // 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(region.width | 0, region.height | 0, scaleMode, resolution); + + tempMatrix.tx = -region.x; + tempMatrix.ty = -region.y; + + this.render(displayObject, renderTexture, false, tempMatrix, true); + + return renderTexture; + } + + /** + * Removes everything from the renderer and optionally removes the Canvas DOM element. + * + * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. + */ + destroy(removeView) + { + 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); + } + + this.plugins = null; + + this.type = RENDERER_TYPE.UNKNOWN; + + this.view = null; + + this.screen = null; + + this.resolution = 0; + + this.transparent = false; + + this.autoResize = false; + + this.blendModes = null; + + this.options = null; + + this.preserveDrawingBuffer = false; + this.clearBeforeRender = false; + + this.roundPixels = false; + + this._backgroundColor = 0; + this._backgroundColorRgba = null; + this._backgroundColorString = null; + + this._tempDisplayObjectParent = null; + this._lastObjectRendered = null; + } + + /** + * The background color to fill if not transparent + * + * @member {number} + */ + get backgroundColor() + { + return this._backgroundColor; + } + + set backgroundColor(value) // eslint-disable-line require-jsdoc + { + this._backgroundColor = value; + this._backgroundColorString = hex2string(value); + hex2rgb(value, this._backgroundColorRgba); + } +} diff --git a/packages/core/src/Renderer.js b/packages/core/src/Renderer.js new file mode 100644 index 0000000..2227e78 --- /dev/null +++ b/packages/core/src/Renderer.js @@ -0,0 +1,338 @@ +import AbstractRenderer from './AbstractRenderer'; +import { sayHello } from '@pixi/utils'; +import MaskSystem from './mask/MaskSystem'; +import StencilSystem from './mask/StencilSystem'; +import FilterSystem from './filters/FilterSystem'; +import FramebufferSystem from './framebuffer/FramebufferSystem'; +import RenderTextureSystem from './renderTexture/RenderTextureSystem'; +import TextureSystem from './textures/TextureSystem'; +import ProjectionSystem from './projection/ProjectionSystem'; +import StateSystem from './state/StateSystem'; +import GeometrySystem from './geometry/GeometrySystem'; +import ShaderSystem from './shader/ShaderSystem'; +import ContextSystem from './context/ContextSystem'; +import BatchSystem from './batch/BatchSystem'; +import TextureGCSystem from './textures/TextureGCSystem'; +import { RENDERER_TYPE } from '@pixi/constants'; +import UniformGroup from './shader/UniformGroup'; +import { Matrix } from '@pixi/math'; +import Runner from 'mini-runner'; + +/** + * 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 Render works by automatically managing webGLBatchs. + * 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 :) + * + * @class + * @memberof PIXI + * @extends PIXI.AbstractRenderer + */ +export default class Renderer extends AbstractRenderer +{ + // eslint-disable-next-line valid-jsdoc + /** + * + * @param {object} [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.transparent=false] - If the render view is transparent, default false + * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false + * @param {boolean} [options.antialias=false] - sets antialias. If not available natively then FXAA + * antialiasing is used + * @param {boolean} [options.forceFXAA=false] - forces FXAA antialiasing to be used over native. + * FXAA is faster, but may not always look as great + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. + * The resolution of the renderer retina would be 2. + * @param {boolean} [options.clearBeforeRender=true] - This sets if the renderer will clear + * the canvas or not before the new render pass. If you wish to set this to false, you *must* set + * preserveDrawingBuffer to `true`. + * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, + * enable this if you need to call toDataUrl on the webgl context. + * @param {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when + * rendering, stopping pixel interpolation. + * @param {number} [options.backgroundColor=0x000000] - The background color of the rendered area + * (shown if not transparent). + * @param {boolean} [options.legacy=false] - If true PixiJS will aim to ensure compatibility + * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. + * @param {string} [options.powerPreference] - Parameter passed to webgl context, set to "high-performance" + * for devices with dual graphics card + */ + constructor(options, arg2, arg3) + { + super('WebGL', options, arg2, arg3); + + /** + * The type of this renderer as a standardised const + * + * @member {number} + * @see PIXI.RENDERER_TYPE + */ + this.type = RENDERER_TYPE.WEBGL; + + // this will be set by the contextSystem (this.context) + this.gl = null; + this.CONTEXT_UID = 0; + this.legacy = !!options.legacy; + + // TODO legacy! + + // runners! + this.runners = { + destroy: new Runner('destroy'), + contextChange: new Runner('contextChange', 1), + reset: new Runner('reset'), + update: new Runner('update'), + postrender: new Runner('postrender'), + prerender: new Runner('prerender'), + resize: new Runner('resize', 2), + }; + + 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(GeometrySystem, 'geometry') + .addSystem(FramebufferSystem, 'framebuffer') + .addSystem(StencilSystem, 'stencil') + .addSystem(ProjectionSystem, 'projection') + .addSystem(TextureGCSystem, 'textureGC') + .addSystem(FilterSystem, 'filter') + .addSystem(RenderTextureSystem, 'renderTexture') + .addSystem(BatchSystem, 'batch'); + + this.initPlugins(Renderer.__plugins); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ + if (options.context) + { + this.context.initFromContext(options.context); + } + else + { + this.context.initFromOptions({ + alpha: this.transparent, + antialias: options.antialias, + premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', + stencil: true, + 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); + } + + /** + * Add a new system to the renderer. + * @param {class} ClassRef - Class reference + * @param {string} [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. + * @return {PIXI.Renderer} Return instance of renderer + */ + addSystem(ClassRef, name) + { + if (!name) + { + name = ClassRef.name; + } + + // TODO - read name from class.name.. + + /* + if(name.includes('System')) + { + name = name.replace('System', ''); + name = name.charAt(0).toLowerCase() + name.slice(1); + } + */ + + const system = new ClassRef(this); + + if (this[name]) + { + throw new Error(`Whoops! ${name} is already a manger`); + } + + this[name] = system; + + for (const i in this.runners) + { + this.runners[i].add(system); + } + + return this; + + /** + * 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. + */ + } + + /** + * Renders the object to its webGL view + * + * @param {PIXI.DisplayObject} displayObject - the object to be rendered + * @param {PIXI.RenderTexture} renderTexture - The render texture to render to. + * @param {boolean} [clear] - Should the canvas be cleared before the new render + * @param {PIXI.Transform} [transform] - A transform to apply to the render texture before rendering. + * @param {boolean} [skipUpdateTransform] - Should we skip the update transform pass? + */ + render(displayObject, renderTexture, clear, transform, skipUpdateTransform) + { + // can be handy to know! + this.renderingToScreen = !renderTexture; + + this.runners.prerender.run(); + this.emit('prerender'); + + // 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.parent; + + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = 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.run(); + + this.emit('postrender'); + } + + /** + * Resizes the webGL view to the specified width and height. + * + * @param {number} screenWidth - the new width of the screen + * @param {number} screenHeight - the new height of the screen + */ + resize(screenWidth, screenHeight) + { + AbstractRenderer.prototype.resize.call(this, screenWidth, screenHeight); + + this.runners.resize.run(screenWidth, screenHeight); + } + + /** + * Resets the WebGL state so you can render things however you fancy! + * + * @return {PIXI.Renderer} Returns itself. + */ + reset() + { + this.runners.reset.run(); + + return this; + } + + clear() + { + this.framebuffer.bind(); + this.framebuffer.clear(); + } + + /** + * Removes everything from the renderer (event listeners, spritebatch, etc...) + * + * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. + * See: https://github.com/pixijs/pixi.js/issues/2233 + */ + destroy(removeView) + { + this.runners.destroy.run(); + + // call base destroy + super.destroy(removeView); + + // TODO nullify all the managers.. + this.gl = null; + } + + /** + * 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 + * builds and excluding plugins. + * @name PIXI.Renderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. + */ + + /** + * Adds a plugin to the renderer. + * + * @method + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + static registerPlugin(pluginName, ctor) + { + Renderer.__plugins = Renderer.__plugins || {}; + Renderer.__plugins[pluginName] = ctor; + } +} diff --git a/packages/core/src/System.js b/packages/core/src/System.js new file mode 100644 index 0000000..05a10c8 --- /dev/null +++ b/packages/core/src/System.js @@ -0,0 +1,42 @@ +/** + * System is a base class used for extending systems used by the {@link PIXI.Renderer} + * @see PIXI.Renderer#addSystem + * @class + * @memberof PIXI + */ +export default class System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + /** + * The renderer this manager works for. + * + * @member {PIXI.Renderer} + */ + this.renderer = renderer; + + this.renderer.runners.contextChange.add(this); + } + + /** + * Generic method called when there is a WebGL context change. + * + */ + contextChange() + { + // do some codes init! + } + + /** + * Generic destroy methods to be overridden by the subclass + * + */ + destroy() + { + this.renderer.runners.contextChange.remove(this); + this.renderer = null; + } +} diff --git a/packages/core/src/batch/BatchSystem.js b/packages/core/src/batch/BatchSystem.js new file mode 100644 index 0000000..b04c249 --- /dev/null +++ b/packages/core/src/batch/BatchSystem.js @@ -0,0 +1,68 @@ +import System from '../System'; +import ObjectRenderer from './ObjectRenderer'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class BatchSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + super(renderer); + + /** + * An empty renderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.emptyRenderer = new ObjectRenderer(renderer); + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.currentRenderer = this.emptyRenderer; + } + + /** + * Changes the current renderer to the one given in parameter + * + * @param {PIXI.ObjectRenderer} objectRenderer - The object renderer to use. + */ + setObjectRenderer(objectRenderer) + { + if (this.currentRenderer === objectRenderer) + { + return; + } + + this.currentRenderer.stop(); + this.currentRenderer = objectRenderer; + + this.renderer.state.setState(objectRenderer.state); + + this.currentRenderer.start(); + } + + /** + * This should be called if you wish to do some custom rendering + * It will basically render anything that may be batched up such as sprites + * + */ + flush() + { + this.setObjectRenderer(this.emptyRenderer); + } + + reset() + { + this.setObjectRenderer(this.emptyRenderer); + } +} diff --git a/packages/core/src/batch/ObjectRenderer.js b/packages/core/src/batch/ObjectRenderer.js new file mode 100644 index 0000000..0c851a9 --- /dev/null +++ b/packages/core/src/batch/ObjectRenderer.js @@ -0,0 +1,48 @@ +import System from '../System'; + +/** + * Base for a common object renderer that can be used as a system renderer plugin. + * + * @class + * @extends PIXI.System + * @memberof PIXI + */ +export default class ObjectRenderer extends System +{ + /** + * Starts the renderer and sets the shader + * + */ + start() + { + // set the shader.. + } + + /** + * Stops the renderer + * + */ + stop() + { + this.flush(); + } + + /** + * Stub method for rendering content and emptying the current batch. + * + */ + flush() + { + // flush! + } + + /** + * Renders an object + * + * @param {PIXI.DisplayObject} object - The object to render. + */ + render(object) // eslint-disable-line no-unused-vars + { + // render the object + } +} diff --git a/packages/core/src/context/ContextSystem.js b/packages/core/src/context/ContextSystem.js new file mode 100644 index 0000000..301aab4 --- /dev/null +++ b/packages/core/src/context/ContextSystem.js @@ -0,0 +1,186 @@ +import System from '../System'; +import { settings } from '@pixi/settings'; + +let CONTEXT_UID = 0; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class ContextSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.webGLVersion = 1; + + this.handleContextLost = this.handleContextLost.bind(this); + this.handleContextRestored = this.handleContextRestored.bind(this); + + this.extensions = {}; + + renderer.view.addEventListener('webglcontextlost', this.handleContextLost, false); + renderer.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + } + + get isLost() + { + return (!this.gl || this.gl.isContextLost()); + } + + contextChange(gl) + { + this.gl = gl; + + // restore a context if it was previously lost + if (gl.isContextLost() && gl.getExtension('WEBGL_lose_context')) + { + gl.getExtension('WEBGL_lose_context').restoreContext(); + } + } + + initFromContext(gl) + { + this.gl = gl; + this.validateContext(gl); + this.renderer.gl = gl; + this.renderer.CONTEXT_UID = CONTEXT_UID++; + this.renderer.runners.contextChange.run(gl); + } + + initFromOptions(options) + { + const gl = this.createContext(this.renderer.view, options); + + this.initFromContext(gl); + } + + /** + * Helper class to create a webGL Context + * + * @class + * @memberof PIXI.glCore + * @param canvas {HTMLCanvasElement} the canvas element that we will get the context from + * @param options {Object} An options object that gets passed in to the canvas element containing the context attributes, + * see https://developer.mozilla.org/en/docs/Web/API/HTMLCanvasElement/getContext for the options available + * @return {WebGLRenderingContext} the WebGL context + */ + createContext(canvas, options) + { + let gl; + + if (settings.PREFER_WEBGL_2) + { + gl = canvas.getContext('webgl2', options); + } + + if (gl) + { + this.webGLVersion = 2; + } + else + { + this.webGLVersion = 1; + + gl = canvas.getContext('webgl', options) + || canvas.getContext('experimental-webgl', options); + + if (!gl) + { + // fail, not able to get a context + throw new Error('This browser does not support webGL. Try using the canvas renderer'); + } + } + + this.gl = gl; + + this.getExtensions(); + + return gl; + } + + getExtensions() + { + // time to set up default etensions that pixi uses.. + const gl = this.gl; + const extensions = this.extensions; + + if (this.webGLVersion === 1) + { + extensions.drawBuffers = gl.getExtension('WEBGL_draw_buffers'); + extensions.depthTexture = gl.getExtension('WEBKIT_WEBGL_depth_texture'); + extensions.floatTexture = gl.getExtension('OES_texture_float'); + extensions.loseContext = gl.getExtension('WEBGL_lose_context'); + + extensions.vertexArrayObject = gl.getExtension('OES_vertex_array_object') + || gl.getExtension('MOZ_OES_vertex_array_object') + || gl.getExtension('WEBKIT_OES_vertex_array_object'); + } + + // we don't use any specific WebGL 2 ones yet! + } + + /** + * Handles a lost webgl context + * + * @private + * @param {WebGLContextEvent} event - The context lost event. + */ + handleContextLost(event) + { + event.preventDefault(); + } + + /** + * Handles a restored webgl context + * + * @private + */ + handleContextRestored() + { + this.renderer.runners.contextChange.run(this.gl); + } + + destroy() + { + const view = this.renderer.view; + + // remove listeners + view.removeEventListener('webglcontextlost', this.handleContextLost); + view.removeEventListener('webglcontextrestored', this.handleContextRestored); + + this.gl.useProgram(null); + + if (this.extensions.loseContext) + { + this.extensions.loseContext.loseContext(); + } + } + + postrender() + { + this.gl.flush(); + } + + validateContext(gl) + { + const attributes = gl.getContextAttributes(); + + // this is going to be fairly simple for now.. but at least we have room to grow! + if (!attributes.stencil) + { + /* eslint-disable max-len */ + + /* eslint-disable no-console */ + console.warn('Provided WebGL context does not have a stencil buffer, masks may not render correctly'); + /* eslint-enable no-console */ + + /* eslint-enable max-len */ + } + } +} diff --git a/packages/core/src/filters/Filter.js b/packages/core/src/filters/Filter.js new file mode 100644 index 0000000..9d8cad8 --- /dev/null +++ b/packages/core/src/filters/Filter.js @@ -0,0 +1,140 @@ +import Shader from '../shader/Shader'; +import Program from '../shader/Program'; +import State from '../state/State'; +import { settings } from '@pixi/settings'; +// import extractUniformsFromSrc from './extractUniformsFromSrc'; +import defaultVertex from './defaultFilter.vert'; +import defaultFragment from './defaultFilter.frag'; + +// let math = require('../../math'); +/** + * @class + * @memberof PIXI + * @extends PIXI.Shader + */ +export default class Filter extends Shader +{ + /** + * @param {string} [vertexSrc] - The source of the vertex shader. + * @param {string} [fragmentSrc] - The source of the fragment shader. + * @param {object} [uniforms] - Custom uniforms to use to augment the built-in ones. + */ + constructor(vertexSrc, fragmentSrc, uniforms) + { + const program = Program.from(vertexSrc, fragmentSrc); + + super(program, uniforms); + + /** + * The padding of the filter. Some filters require extra space to breath such as a blur. + * Increasing this will add extra width and height to the bounds of the object that the + * filter is applied to. + * + * @member {number} + */ + this.padding = 0; + + /** + * The resolution of the filter. Setting this to be lower will lower the quality but + * increase the performance of the filter. + * + * @member {number} + */ + this.resolution = settings.RESOLUTION; + + /** + * If enabled is true the filter is applied, if false it will not. + * + * @member {boolean} + */ + this.enabled = true; + + /** + * If enabled, PixiJS will fit the filter area into boundaries for better performance. + * Switch it off if it does not work for specific shader. + * + * @member {boolean} + */ + this.autoFit = true; + + /** + * Legacy filters use position and uvs from attributes + * @member {boolean} + * @readonly + */ + this.legacy = !!this.program.attributeData.aTextureCoord; + + /** + * the webGL state the filter requires to render + * @member {PIXI.State} + */ + this.state = new State(); + } + + /** + * Applies the filter + * + * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + * @param {boolean} clear - Should the output be cleared before rendering to it + * @param {object} [currentState] - It's current state of filter. + * There are some useful properties in the currentState : + * target, filters, sourceFrame, destinationFrame, renderTarget, resolution + */ + apply(filterManager, input, output, clear, currentState, derp) // eslint-disable-line no-unused-vars + { + // do as you please! + + filterManager.applyFilter(this, input, output, clear, currentState, derp); + + // or just do a regular render.. + } + + /** + * Sets the blendmode of the filter + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL + */ + get blendMode() + { + return this.state.blendMode; + } + + set blendMode(value) // eslint-disable-line require-jsdoc + { + this.state.blendMode = value; + } + + /** + * The default vertex shader source + * + * @static + * @constant + */ + static get defaultVertexSrc() + { + return defaultVertex; + } + + /** + * The default fragment shader source + * + * @static + * @constant + */ + static get defaultFragmentSrc() + { + return defaultFragment; + } +} + +/** + * Used for caching shader IDs + * + * @static + * @private + */ +Filter.SOURCE_KEY_MAP = {}; + diff --git a/packages/core/src/filters/FilterSystem.js b/packages/core/src/filters/FilterSystem.js new file mode 100644 index 0000000..3174318 --- /dev/null +++ b/packages/core/src/filters/FilterSystem.js @@ -0,0 +1,431 @@ +import System from '../System'; + +import RenderTexture from '../renderTexture/RenderTexture'; +import Quad from '../utils/Quad'; +import QuadUv from '../utils/QuadUv'; +import { Rectangle } from '@pixi/math'; +import * as filterTransforms from './filterTransforms'; +import bitTwiddle from 'bit-twiddle'; +import UniformGroup from '../shader/UniformGroup'; +import { DRAW_MODES } from '@pixi/constants'; + +// +/** + * @ignore + * @class + */ +class FilterState +{ + /** + * + */ + constructor() + { + this.renderTexture = null; + this.sourceFrame = new Rectangle(); + this.destinationFrame = new Rectangle(); + this.filters = []; + this.target = null; + this.legacy = false; + this.resolution = 1; + } +} + +/** + * @class + * @memberof PIXI.systems + * @extends PIXI.System + */ +export default class FilterSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + /** + * stores a bunch of PO2 textures used for filtering + * @type {Object} + */ + this.texturePool = {}; + + /** + * a pool for storing filter states, save us creating new ones each tick + * @type {Array} + */ + this.statePool = []; + + /** + * A very simple geometry used when drawing a filter effect to the screen + * @type {Quad} + */ + this.quad = new Quad(); + + this.quadUv = new QuadUv(); + + /** + * Temporary rect for maths + * @type {PIXI.Rectangle} + */ + this.tempRect = new Rectangle(); + + this.activeState = {}; + + /** + * this uniform group is attached to filter uniforms when used + * @type {UniformGroup} + */ + this.globalUniforms = new UniformGroup({ + sourceFrame: this.tempRect, + destinationFrame: this.tempRect, + + // legacy variables + filterArea: new Float32Array(4), + filterClamp: new Float32Array(4), + }, true); + } + + /** + * Adds a new filter to the System. + * + * @param {PIXI.DisplayObject} target - The target of the filter to render. + * @param {PIXI.Filter[]} filters - The filters to apply. + */ + push(target, filters) + { + const renderer = this.renderer; + const filterStack = this.renderer.renderTexture.defaultFilterStack; + const state = this.statePool.pop() || new FilterState(); + + let resolution = filters[0].resolution; + let padding = filters[0].padding; + let autoFit = filters[0].autoFit; + let legacy = filters[0].legacy; + + for (let i = 1; i < filters.length; i++) + { + const filter = filters[i]; + + // lets use the lowest resolution.. + resolution = Math.min(resolution, filter.resolution); + // and the largest amount of padding! + padding = Math.max(padding, filter.padding); + // only auto fit if all filters are autofit + autoFit = autoFit || filter.autoFit; + + legacy = legacy || filter.legacy; + } + + filterStack.push(state); + + state.resolution = resolution; + + state.legacy = legacy; + + // round to whole number based on resolution + // TODO move that to the shader too? + state.sourceFrame = target.filterArea || target.getBounds(true); + + state.sourceFrame.pad(padding); + + if (autoFit) + { + state.sourceFrame.fit(this.renderer.renderTexture.destinationFrame); + } + + state.sourceFrame.round(resolution); + + state.renderTexture = this.getPotFilterTexture(state.sourceFrame.width, state.sourceFrame.height, resolution); + state.filters = filters; + + state.destinationFrame.width = state.renderTexture.width; + state.destinationFrame.height = state.renderTexture.height; + + state.renderTexture.filterFrame = state.sourceFrame; + renderer.renderTexture.bind(state.renderTexture, state.sourceFrame);// /, state.destinationFrame); + renderer.renderTexture.clear(); + } + + /** + * Pops off the filter and applies it. + * + */ + pop() + { + const renderer = this.renderer; + const filterStack = renderer.renderTexture.defaultFilterStack; + const state = filterStack.pop(); + const filters = state.filters; + + this.activeState = state; + + const globalUniforms = this.globalUniforms.uniforms; + + globalUniforms.sourceFrame = state.sourceFrame; + globalUniforms.destinationFrame = state.destinationFrame; + globalUniforms.resolution = state.resolution; + + // only update the rect if its legacy.. + if (state.legacy) + { + const filterArea = globalUniforms.filterArea; + const filterClamp = globalUniforms.filterClamp; + + filterArea[0] = state.destinationFrame.width; + filterArea[1] = state.destinationFrame.height; + filterArea[2] = state.sourceFrame.x; + filterArea[3] = state.sourceFrame.y; + + filterClamp[0] = 0.5 / state.resolution / state.destinationFrame.width; + filterClamp[1] = 0.5 / state.resolution / state.destinationFrame.height; + filterClamp[2] = (state.sourceFrame.width - 0.5) / state.resolution / state.destinationFrame.width; + filterClamp[3] = (state.sourceFrame.height - 0.5) / state.resolution / state.destinationFrame.height; + } + + this.globalUniforms.update(); + + const lastState = filterStack[filterStack.length - 1]; + + if (filters.length === 1) + { + filters[0].apply(this, state.renderTexture, lastState.renderTexture, false, state); + + this.returnFilterTexture(state.renderTexture); + } + else + { + let flip = state.renderTexture; + let flop = this.getPotFilterTexture( + flip.width, + flip.height, + state.resolution + ); + + flop.filterFrame = flip.filterFrame; + + let i = 0; + + for (i = 0; i < filters.length - 1; ++i) + { + filters[i].apply(this, flip, flop, true, state); + + const t = flip; + + flip = flop; + flop = t; + } + + filters[i].apply(this, flip, lastState.renderTexture, false, state); + + this.returnFilterTexture(flip); + this.returnFilterTexture(flop); + } + + this.statePool.push(state); + } + + /** + * Draws a filter. + * + * @param {PIXI.Filter} filter - The filter to draw. + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + * @param {boolean} clear - Should the output be cleared before rendering to it + */ + applyFilter(filter, input, output, clear) + { + const renderer = this.renderer; + + renderer.renderTexture.bind(output, output ? output.filterFrame : null); + + if (clear) + { + // gl.disable(gl.SCISSOR_TEST); + renderer.renderTexture.clear(); + // gl.enable(gl.SCISSOR_TEST); + } + + // set the uniforms.. + filter.uniforms.uSampler = input; + filter.uniforms.filterGlobals = this.globalUniforms; + + // TODO make it so that the order of this does not matter.. + // because it does at the moment cos of global uniforms. + // they need to get resynced + + renderer.state.setState(filter.state); + renderer.shader.bind(filter); + + if (filter.legacy) + { + this.quadUv.map(input._frame, input.filterFrame); + + renderer.geometry.bind(this.quadUv); + renderer.geometry.draw(DRAW_MODES.TRIANGLES); + } + else + { + renderer.geometry.bind(this.quad); + renderer.geometry.draw(DRAW_MODES.TRIANGLE_STRIP); + } + } + + /** + * Calculates the mapped matrix. + * + * TODO playing around here.. this is temporary - (will end up in the shader) + * this returns a matrix that will normalise map filter cords in the filter to screen space + * + * @param {PIXI.Matrix} outputMatrix - the matrix to output to. + * @return {PIXI.Matrix} The mapped matrix. + */ + calculateScreenSpaceMatrix(outputMatrix) + { + const currentState = this.activeState; + + return filterTransforms.calculateScreenSpaceMatrix( + outputMatrix, + currentState.sourceFrame, + currentState.destinationFrame + ); + } + + /** + * This will map the filter coord so that a texture can be used based on the transform of a sprite + * + * @param {PIXI.Matrix} outputMatrix - The matrix to output to. + * @param {PIXI.Sprite} sprite - The sprite to map to. + * @return {PIXI.Matrix} The mapped matrix. + */ + calculateSpriteMatrix(outputMatrix, sprite) + { + const currentState = this.activeState; + + return filterTransforms.calculateSpriteMatrix( + outputMatrix, + currentState.sourceFrame, + currentState.destinationFrame, + sprite + ); + } + + /** + * Destroys this Filter System. + * + * @param {boolean} [contextLost=false] context was lost, do not free shaders + * + */ + destroy(contextLost = false) + { + if (!contextLost) + { + this.emptyPool(); + } + else + { + this.texturePool = {}; + } + } + + /** + * Gets a Power-of-Two render texture. + * + * TODO move to a seperate class could be on renderer? + * also - could cause issue with multiple contexts? + * + * @private + * @param {WebGLRenderingContext} gl - The webgl rendering context + * @param {number} minWidth - The minimum width of the render target. + * @param {number} minHeight - The minimum height of the render target. + * @param {number} resolution - The resolution of the render target. + * @return {PIXI.RenderTarget} The new render target. + */ + getPotFilterTexture(minWidth, minHeight, resolution) + { + minWidth = bitTwiddle.nextPow2(minWidth); + minHeight = bitTwiddle.nextPow2(minHeight); + resolution = resolution || 1; + + const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + + if (!this.texturePool[key]) + { + this.texturePool[key] = []; + } + + let renderTexture = this.texturePool[key].pop(); + + if (!renderTexture) + { + // temporary bypass cache.. + // internally - this will cause a texture to be bound.. + renderTexture = RenderTexture.create({ + width: minWidth, + height: minHeight, + resolution, + }); + } + + return renderTexture; + } + + /** + * Gets extra render texture to use inside current filter + * + * @param {number} resolution resolution of the renderTexture + * @returns {PIXI.RenderTexture} + */ + getFilterTexture(resolution) + { + const rt = this.activeState.renderTexture; + + const filterTexture = this.getPotFilterTexture(rt.width, rt.height, resolution || rt.baseTexture.resolution); + + filterTexture.filterFrame = rt.filterFrame; + + return filterTexture; + } + + /** + * Frees a render texture back into the pool. + * + * @param {PIXI.RenderTarget} renderTexture - The renderTarget to free + */ + returnFilterTexture(renderTexture) + { + renderTexture.filterFrame = null; + + const base = renderTexture.baseTexture; + + const minWidth = base.width; + const minHeight = base.height; + + const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + + this.texturePool[key].push(renderTexture); + } + + /** + * Empties the texture pool. + * + */ + emptyPool() + { + for (const i in this.texturePool) + { + const textures = this.texturePool[i]; + + if (textures) + { + for (let j = 0; j < textures.length; j++) + { + textures[j].destroy(true); + } + } + } + + this.texturePool = {}; + } +} diff --git a/packages/core/src/filters/defaultFilter.frag b/packages/core/src/filters/defaultFilter.frag new file mode 100644 index 0000000..a14c1be --- /dev/null +++ b/packages/core/src/filters/defaultFilter.frag @@ -0,0 +1,22 @@ +varying vec2 vTextureCoord; +varying vec2 vFilterCoord; + +uniform sampler2D uSampler; +uniform sampler2D filterSampler; + +void main(void){ + vec4 masky = texture2D(filterSampler, vFilterCoord); + vec4 sample = texture2D(uSampler, vTextureCoord); + vec4 color; + if(mod(vFilterCoord.x, 1.0) > 0.5) + { + color = vec4(1.0, 0.0, 0.0, 1.0); + } + else + { + color = vec4(0.0, 1.0, 0.0, 1.0); + } + // gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0); + gl_FragColor = mix(sample, masky, 0.5); + gl_FragColor *= sample.a; +} diff --git a/packages/core/src/filters/defaultFilter.vert b/packages/core/src/filters/defaultFilter.vert new file mode 100644 index 0000000..e1febca --- /dev/null +++ b/packages/core/src/filters/defaultFilter.vert @@ -0,0 +1,14 @@ +attribute vec2 aVertexPosition; +attribute vec2 aTextureCoord; + +uniform mat3 projectionMatrix; +uniform mat3 filterMatrix; + +varying vec2 vTextureCoord; +varying vec2 vFilterCoord; + +void main(void){ + gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); + vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy; + vTextureCoord = aTextureCoord ; +} diff --git a/packages/core/src/filters/filterTransforms.js b/packages/core/src/filters/filterTransforms.js new file mode 100644 index 0000000..87a3cd6 --- /dev/null +++ b/packages/core/src/filters/filterTransforms.js @@ -0,0 +1,50 @@ +import { Matrix } from '@pixi/math'; + +/** + * Calculates the mapped matrix + * @param filterArea {Rectangle} The filter area + * @param sprite {Sprite} the target sprite + * @param outputMatrix {Matrix} @alvin + * @private + */ +// this returns a matrix that will normalise map filter cords in the filter to screen space +export function calculateScreenSpaceMatrix(outputMatrix, filterArea, textureSize) +{ + // TODO unwrap? + const mappedMatrix = outputMatrix.identity(); + + mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); + + mappedMatrix.scale(textureSize.width, textureSize.height); + + return mappedMatrix; +} + +export function calculateNormalizedScreenSpaceMatrix(outputMatrix, filterArea, textureSize) +{ + const mappedMatrix = outputMatrix.identity(); + + mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); + + const translateScaleX = (textureSize.width / filterArea.width); + const translateScaleY = (textureSize.height / filterArea.height); + + mappedMatrix.scale(translateScaleX, translateScaleY); + + return mappedMatrix; +} + +// this will map the filter coord so that a texture can be used based on the transform of a sprite +export function calculateSpriteMatrix(outputMatrix, filterArea, textureSize, sprite) +{ + const orig = sprite._texture.orig; + const mappedMatrix = outputMatrix.set(textureSize.width, 0, 0, textureSize.height, filterArea.x, filterArea.y); + const worldTransform = sprite.worldTransform.copyTo(Matrix.TEMP_MATRIX); + + worldTransform.invert(); + mappedMatrix.prepend(worldTransform); + mappedMatrix.scale(1.0 / orig.width, 1.0 / orig.height); + mappedMatrix.translate(sprite.anchor.x, sprite.anchor.y); + + return mappedMatrix; +} diff --git a/packages/core/src/filters/spriteMask/SpriteMaskFilter.js b/packages/core/src/filters/spriteMask/SpriteMaskFilter.js new file mode 100644 index 0000000..91697b7 --- /dev/null +++ b/packages/core/src/filters/spriteMask/SpriteMaskFilter.js @@ -0,0 +1,63 @@ +import Filter from '../Filter'; +import { Matrix } from '@pixi/math'; +import vertex from './spriteMaskFilter.vert'; +import fragment from './spriteMaskFilter.frag'; +import { default as TextureMatrix } from '../../textures/TextureMatrix'; + +/** + * The SpriteMaskFilter class + * + * @class + * @extends PIXI.Filter + * @memberof PIXI + */ +export default class SpriteMaskFilter extends Filter +{ + /** + * @param {PIXI.Sprite} sprite - the target sprite + */ + constructor(sprite) + { + const maskMatrix = new Matrix(); + + super(vertex, fragment); + + sprite.renderable = false; + + this.maskSprite = sprite; + this.maskMatrix = maskMatrix; + } + + /** + * Applies the filter + * + * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + */ + apply(filterManager, input, output) + { + const maskSprite = this.maskSprite; + const tex = this.maskSprite.texture; + + if (!tex.valid) + { + return; + } + if (!tex.transform) + { + // margin = 0.0, let it bleed a bit, shader code becomes easier + // assuming that atlas textures were made with 1-pixel padding + tex.transform = new TextureMatrix(tex, 0.0); + } + tex.transform.update(); + + this.uniforms.mask = tex; + this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite) + .prepend(tex.transform.mapCoord); + this.uniforms.alpha = maskSprite.worldAlpha; + this.uniforms.maskClamp = tex.transform.uClampFrame; + + filterManager.applyFilter(this, input, output); + } +} diff --git a/packages/core/src/filters/spriteMask/spriteMaskFilter.frag b/packages/core/src/filters/spriteMask/spriteMaskFilter.frag new file mode 100644 index 0000000..0e4aef8 --- /dev/null +++ b/packages/core/src/filters/spriteMask/spriteMaskFilter.frag @@ -0,0 +1,23 @@ +varying vec2 vMaskCoord; +varying vec2 vTextureCoord; + +uniform sampler2D uSampler; +uniform sampler2D mask; +uniform float alpha; +uniform vec4 maskClamp; + +void main(void) +{ + float clip = step(3.5, + step(maskClamp.x, vMaskCoord.x) + + step(maskClamp.y, vMaskCoord.y) + + step(vMaskCoord.x, maskClamp.z) + + step(vMaskCoord.y, maskClamp.w)); + + vec4 original = texture2D(uSampler, vTextureCoord); + vec4 masky = texture2D(mask, vMaskCoord); + + original *= (masky.r * masky.a * alpha * clip); + + gl_FragColor = original; +} diff --git a/packages/core/src/filters/spriteMask/spriteMaskFilter.vert b/packages/core/src/filters/spriteMask/spriteMaskFilter.vert new file mode 100644 index 0000000..d23a30c --- /dev/null +++ b/packages/core/src/filters/spriteMask/spriteMaskFilter.vert @@ -0,0 +1,16 @@ +attribute vec2 aVertexPosition; +attribute vec2 aTextureCoord; + +uniform mat3 projectionMatrix; +uniform mat3 otherMatrix; + +varying vec2 vMaskCoord; +varying vec2 vTextureCoord; + +void main(void) +{ + gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); + + vTextureCoord = aTextureCoord; + vMaskCoord = ( otherMatrix * vec3( aTextureCoord, 1.0) ).xy; +} diff --git a/packages/core/src/framebuffer/FrameBuffer.js b/packages/core/src/framebuffer/FrameBuffer.js new file mode 100644 index 0000000..84e9fcc --- /dev/null +++ b/packages/core/src/framebuffer/FrameBuffer.js @@ -0,0 +1,106 @@ +import Texture from '../textures/BaseTexture'; +import { FORMATS, TYPES } from '@pixi/constants'; + +/** + * Frame buffer + * @class + * @memberof PIXI + */ +export default class FrameBuffer +{ + constructor(width, height) + { + this.width = width || 100; + this.height = height || 100; + + this.stencil = false; + this.depth = false; + + this.dirtyId = 0; + this.dirtyFormat = 0; + this.dirtySize = 0; + + this.depthTexture = null; + this.colorTextures = []; + + this.glFrameBuffers = {}; + } + + get colorTexture() + { + return this.colorTextures[0]; + } + + addColorTexture(index, texture) + { + // TODO add some validation to the texture - same width / height etc? + this.colorTextures[index || 0] = texture || new Texture(null, { scaleMode: 0, + resolution: 1, + mipmap: false, + width: this.width, + height: this.height });// || new Texture(); + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + addDepthTexture(texture) + { + /* eslint-disable max-len */ + this.depthTexture = texture || new Texture(null, { scaleMode: 0, + resolution: 1, + width: this.width, + height: this.height, + mipmap: false, + format: FORMATS.DEPTH_COMPONENT, + type: TYPES.UNSIGNED_SHORT });// UNSIGNED_SHORT; + /* eslint-disable max-len */ + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + enableDepth() + { + this.depth = true; + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + enableStencil() + { + this.stencil = true; + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + resize(width, height) + { + if (width === this.width && height === this.height) return; + + this.width = width; + this.height = height; + + this.dirtyId++; + this.dirtySize++; + + for (let i = 0; i < this.colorTextures.length; i++) + { + this.colorTextures[i].setSize(width, height); + } + + if (this.depthTexture) + { + this.depthTexture.setSize(width, height); + } + } +} diff --git a/packages/core/src/framebuffer/FramebufferSystem.js b/packages/core/src/framebuffer/FramebufferSystem.js new file mode 100644 index 0000000..2cc2437 --- /dev/null +++ b/packages/core/src/framebuffer/FramebufferSystem.js @@ -0,0 +1,246 @@ +import System from '../System'; +import { Rectangle } from '@pixi/math'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class FramebufferSystem extends System +{ + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + contextChange() + { + this.gl = this.renderer.gl; + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + this.current = null; + this.viewport = new Rectangle(); + + this.drawBufferExtension = this.renderer.context.extensions.drawBuffers; + } + + bind(framebuffer, frame) + { + const gl = this.gl; + + this.current = framebuffer; + + if (framebuffer) + { + // TODO cacheing layer! + + const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID] || this.initFramebuffer(framebuffer); + + gl.bindFramebuffer(gl.FRAMEBUFFER, fbo.framebuffer); + // makesure all textures are unbound.. + + // now check for updates... + if (fbo.dirtyId !== framebuffer.dirtyId) + { + fbo.dirtyId = framebuffer.dirtyId; + + if (fbo.dirtyFormat !== framebuffer.dirtyFormat) + { + fbo.dirtyFormat = framebuffer.dirtyFormat; + this.updateFramebuffer(framebuffer); + } + else if (fbo.dirtySize !== framebuffer.dirtySize) + { + fbo.dirtySize = framebuffer.dirtySize; + this.resizeFramebuffer(framebuffer); + } + } + + for (let i = 0; i < framebuffer.colorTextures.length; i++) + { + if (framebuffer.colorTextures[i].texturePart) + { + this.renderer.texture.unbind(framebuffer.colorTextures[i].texture); + } + else + { + this.renderer.texture.unbind(framebuffer.colorTextures[i]); + } + } + + if (framebuffer.depthTexture) + { + this.renderer.texture.unbind(framebuffer.depthTexture); + } + + if (frame) + { + this.setViewport(frame.x, frame.y, frame.width, frame.height); + } + else + { + this.setViewport(0, 0, framebuffer.width, framebuffer.height); + } + } + else + { + gl.bindFramebuffer(gl.FRAMEBUFFER, null); + + if (frame) + { + this.setViewport(frame.x, frame.y, frame.width, frame.height); + } + else + { + this.setViewport(0, 0, this.renderer.width, this.renderer.height); + } + } + } + + setViewport(x, y, width, height) + { + const v = this.viewport; + + if (v.width !== width || v.height !== height || v.x !== x || v.y !== y) + { + v.x = x; + v.y = y; + v.width = width; + v.height = height; + + this.gl.viewport(x, y, width, height); + } + } + + get size() + { + if (this.current) + { + // TODO store temp + return { x: 0, y: 0, width: this.current.width, height: this.current.height }; + } + + return { x: 0, y: 0, width: this.renderer.width, height: this.renderer.height }; + } + + clear(r, g, b, a) + { + const gl = this.gl; + + // TODO clear color can be set only one right? + gl.clearColor(r, g, b, a); + gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); + } + + // private functions... + + initFramebuffer(framebuffer) + { + const gl = this.gl; + + // TODO - make this a class? + const fbo = { + framebuffer: gl.createFramebuffer(), + stencil: null, + dirtyId: 0, + dirtyFormat: 0, + dirtySize: 0, + }; + + framebuffer.glFrameBuffers[this.CONTEXT_UID] = fbo; + + return fbo; + } + + resizeFramebuffer(framebuffer) + { + const gl = this.gl; + + if (framebuffer.stencil || framebuffer.depth) + { + gl.bindRenderbuffer(gl.RENDERBUFFER, this.stencil); + gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); + } + } + + updateFramebuffer(framebuffer) + { + const gl = this.gl; + + const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID]; + + // bind the color texture + const colorTextures = framebuffer.colorTextures; + + let count = colorTextures.length; + + if (!this.drawBufferExtension) + { + count = Math.min(count, 1); + } + + const activeTextures = []; + + for (let i = 0; i < count; i++) + { + const texture = framebuffer.colorTextures[i]; + + if (texture.texturePart) + { + this.renderer.texture.bind(texture.texture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.COLOR_ATTACHMENT0 + i, + gl.TEXTURE_CUBE_MAP_NEGATIVE_X + texture.side, + texture.texture._glTextures[this.CONTEXT_UID].texture, + 0); + } + else + { + this.renderer.texture.bind(texture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.COLOR_ATTACHMENT0 + i, + gl.TEXTURE_2D, + texture._glTextures[this.CONTEXT_UID].texture, + 0); + } + + activeTextures.push(gl.COLOR_ATTACHMENT0 + i); + } + + if (this.drawBufferExtension && activeTextures.length > 1) + { + this.drawBufferExtension.drawBuffersWEBGL(activeTextures); + } + + if (framebuffer.depthTexture) + { + const depthTextureExt = this.renderer.context.extensions.depthTexture; + + if (depthTextureExt) + { + const depthTexture = framebuffer.depthTexture; + + this.renderer.texture.bind(depthTexture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.DEPTH_ATTACHMENT, + gl.TEXTURE_2D, + depthTexture._glTextures[this.CONTEXT_UID].texture, + 0); + } + } + + if (framebuffer.stencil || framebuffer.depth) + { + fbo.stencil = gl.createRenderbuffer(); + + gl.bindRenderbuffer(gl.RENDERBUFFER, fbo.stencil); + + // TODO.. this is depth AND stencil? + gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, fbo.stencil); + gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); + // fbo.enableStencil(); + } + } +} diff --git a/packages/core/src/geometry/GLBuffer.js b/packages/core/src/geometry/GLBuffer.js new file mode 100644 index 0000000..0477e8a --- /dev/null +++ b/packages/core/src/geometry/GLBuffer.js @@ -0,0 +1,9 @@ +export default class GLBuffer +{ + constructor(buffer) + { + this.buffer = buffer; + this.updateID = -1; + this.byteLength = -1; + } +} diff --git a/packages/core/src/geometry/GeometrySystem.js b/packages/core/src/geometry/GeometrySystem.js new file mode 100644 index 0000000..959f827 --- /dev/null +++ b/packages/core/src/geometry/GeometrySystem.js @@ -0,0 +1,388 @@ +import System from '../System'; +import GLBuffer from './GLBuffer'; + +const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class GeometrySystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this._activeGeometry = null; + this._activeVao = null; + + this.hasVao = true; + this.hasInstance = true; + } + + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + contextChange() + { + const gl = this.gl = this.renderer.gl; + + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + + // webgl2 + if (!gl.createVertexArray) + { + // webgl 1! + let nativeVaoExtension = this.renderer.context.extensions.vertexArrayObject; + + if (this.renderer.legacy) + { + nativeVaoExtension = null; + } + + if (nativeVaoExtension) + { + gl.createVertexArray = () => + nativeVaoExtension.createVertexArrayOES(); + + gl.bindVertexArray = (vao) => + nativeVaoExtension.bindVertexArrayOES(vao); + + gl.deleteVertexArray = (vao) => + nativeVaoExtension.deleteVertexArrayOES(vao); + } + else + { + this.hasVao = false; + gl.createVertexArray = () => + { + // empty + }; + + gl.bindVertexArray = () => + { + // empty + }; + + gl.deleteVertexArray = () => + { + // empty + }; + } + } + + if (!gl.vertexAttribDivisor) + { + const instanceExt = gl.getExtension('ANGLE_instanced_arrays'); + + if (instanceExt) + { + gl.vertexAttribDivisor = (a, b) => + instanceExt.vertexAttribDivisorANGLE(a, b); + + gl.drawElementsInstanced = (a, b, c, d, e) => + instanceExt.drawElementsInstancedANGLE(a, b, c, d, e); + + gl.drawArraysInstanced = (a, b, c, d) => + instanceExt.drawArraysInstancedANGLE(a, b, c, d); + } + else + { + this.hasInstance = false; + } + } + } + + /** + * Binds geometry so that is can be drawn. Creating a Vao if required + * @private + * @param {PIXI.Geometry} geometry instance of geometry to bind + */ + bind(geometry, shader) + { + shader = shader || this.renderer.shader.shader; + + const gl = this.gl; + + // not sure the best way to address this.. + // currently different shaders require different VAOs for the same geometry + // Still mulling over the best way to solve this one.. + // will likely need to modify the shader attribute locations at run time! + let vaos = geometry.glVertexArrayObjects[this.CONTEXT_UID]; + + if (!vaos) + { + geometry.glVertexArrayObjects[this.CONTEXT_UID] = vaos = {}; + } + + const vao = vaos[shader.program.id] || this.initGeometryVao(geometry, shader.program); + + this._activeGeometry = geometry; + + if (this._activeVao !== vao) + { + this._activeVao = vao; + + if (this.hasVao) + { + gl.bindVertexArray(vao); + } + else + { + this.activateVao(geometry, shader.program); + } + } + + // TODO - optimise later! + // don't need to loop through if nothing changed! + // maybe look to add an 'autoupdate' to geometry? + this.updateBuffers(); + } + + reset() + { + this.unbind(); + } + + updateBuffers() + { + const geometry = this._activeGeometry; + const gl = this.gl; + + for (let i = 0; i < geometry.buffers.length; i++) + { + const buffer = geometry.buffers[i]; + + const glBuffer = buffer._glBuffers[this.CONTEXT_UID]; + + if (buffer._updateID !== glBuffer.updateID) + { + glBuffer.updateID = buffer._updateID; + + // TODO can cache this on buffer! maybe added a getter / setter? + const type = buffer.index ? gl.ELEMENT_ARRAY_BUFFER : gl.ARRAY_BUFFER; + const drawType = buffer.static ? gl.STATIC_DRAW : gl.DYNAMIC_DRAW; + + gl.bindBuffer(type, glBuffer.buffer); + + if (glBuffer.byteLength >= buffer.data.byteLength) + { + // offset is always zero for now! + gl.bufferSubData(type, 0, buffer.data); + } + else + { + gl.bufferData(type, buffer.data, drawType); + } + } + } + } + + checkCompatability(geometry, program) + { + // geometry must have at least all the attributes that the shader requires. + const geometryAttributes = geometry.attributes; + const shaderAttributes = program.attributeData; + + for (const j in shaderAttributes) + { + if (!geometryAttributes[j]) + { + throw new Error(`shader and geometry incompatible, geometry missing the "${j}" attribute`); + } + } + } + + /** + * Creates a Vao with the same structure as the geometry and stores it on the geometry. + * @private + * @param {PIXI.Geometry} geometry instance of geometry to to generate Vao for + */ + initGeometryVao(geometry, program) + { + this.checkCompatability(geometry, program); + + const gl = this.gl; + const CONTEXT_UID = this.CONTEXT_UID; + const buffers = geometry.buffers; + const attributes = geometry.attributes; + + const tempStride = {}; + const tempStart = {}; + + for (const j in buffers) + { + tempStride[j] = 0; + tempStart[j] = 0; + } + + for (const j in attributes) + { + if (!attributes[j].size && program.attributeData[j]) + { + attributes[j].size = program.attributeData[j].size; + } + + tempStride[attributes[j].buffer] += attributes[j].size * byteSizeMap[attributes[j].type]; + } + + for (const j in attributes) + { + const attribute = attributes[j]; + const attribSize = attribute.size; + + if (attribute.stride === undefined) + { + if (tempStride[attribute.buffer] === attribSize * byteSizeMap[attribute.type]) + { + attribute.stride = 0; + } + else + { + attribute.stride = tempStride[attribute.buffer]; + } + } + + if (attribute.start === undefined) + { + attribute.start = tempStart[attribute.buffer]; + + tempStart[attribute.buffer] += attribSize * byteSizeMap[attribute.type]; + } + } + + // first update - and create the buffers! + // only create a gl buffer if it actually gets + for (let i = 0; i < buffers.length; i++) + { + const buffer = buffers[i]; + + if (!buffer._glBuffers[CONTEXT_UID]) + { + buffer._glBuffers[CONTEXT_UID] = new GLBuffer(gl.createBuffer()); + } + } + + // TODO - maybe make this a data object? + // lets wait to see if we need to first! + const vao = gl.createVertexArray(); + + gl.bindVertexArray(vao); + + this.activateVao(geometry, program); + + gl.bindVertexArray(null); + + // add it to the cache! + geometry.glVertexArrayObjects[this.CONTEXT_UID][program.id] = vao; + + return vao; + } + + activateVao(geometry, program) + { + const gl = this.gl; + const CONTEXT_UID = this.CONTEXT_UID; + const buffers = geometry.buffers; + const attributes = geometry.attributes; + + if (geometry.indexBuffer) + { + // first update the index buffer if we have one.. + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, geometry.indexBuffer._glBuffers[CONTEXT_UID].buffer); + } + + let lastBuffer = null; + + // add a new one! + for (const j in attributes) + { + const attribute = attributes[j]; + const buffer = buffers[attribute.buffer]; + const glBuffer = buffer._glBuffers[CONTEXT_UID]; + + if (program.attributeData[j]) + { + if (lastBuffer !== glBuffer) + { + gl.bindBuffer(gl.ARRAY_BUFFER, glBuffer.buffer); + + lastBuffer = glBuffer; + } + + const location = program.attributeData[j].location; + + // TODO introduce state again + // we can optimise this for older devices that have no VAOs + gl.enableVertexAttribArray(location); + + gl.vertexAttribPointer(location, + attribute.size, + attribute.type || gl.FLOAT, + attribute.normalized, + attribute.stride, + attribute.start); + + if (attribute.instance) + { + // TODO calculate instance count based of this... + if (this.hasInstance) + { + gl.vertexAttribDivisor(location, 1); + } + else + { + throw new Error('geometry error, GPU Instancing is not supported on this device'); + } + } + } + } + } + + draw(type, size, start, instanceCount) + { + const gl = this.gl; + const geometry = this._activeGeometry; + + // TODO.. this should not change so maybe cache the function? + + if (geometry.indexBuffer) + { + if (geometry.instanced) + { + /* eslint-disable max-len */ + gl.drawElementsInstanced(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2, instanceCount || 1); + /* eslint-enable max-len */ + } + else + { + gl.drawElements(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2); + } + } + else + if (geometry.instanced) + { + // TODO need a better way to calculate size.. + gl.drawArraysInstanced(type, start, size || geometry.getSize(), instanceCount || 1); + } + else + { + gl.drawArrays(type, start, size || geometry.getSize()); + } + + return this; + } + + unbind() + { + this.gl.bindVertexArray(null); + this._activeVao = null; + this._activeGeometry = null; + } +} diff --git a/packages/core/src/geometry/utils/setVertexAttribArrays.js b/packages/core/src/geometry/utils/setVertexAttribArrays.js new file mode 100644 index 0000000..23b6959 --- /dev/null +++ b/packages/core/src/geometry/utils/setVertexAttribArrays.js @@ -0,0 +1,54 @@ +// var GL_MAP = {}; + +/** + * @param gl {WebGLRenderingContext} The current WebGL context + * @param attribs {*} + * @param state {*} + */ +export default function setVertexAttribArrays(gl, attribs, state) +{ + let i; + + if (state) + { + const tempAttribState = state.tempAttribState; + const attribState = state.attribState; + + for (i = 0; i < tempAttribState.length; i++) + { + tempAttribState[i] = false; + } + + // set the new attribs + for (i = 0; i < attribs.length; i++) + { + tempAttribState[attribs[i].attribute.location] = true; + } + + for (i = 0; i < attribState.length; i++) + { + if (attribState[i] !== tempAttribState[i]) + { + attribState[i] = tempAttribState[i]; + + if (state.attribState[i]) + { + gl.enableVertexAttribArray(i); + } + else + { + gl.disableVertexAttribArray(i); + } + } + } + } + else + { + for (i = 0; i < attribs.length; i++) + { + const attrib = attribs[i]; + + gl.enableVertexAttribArray(attrib.attribute.location); + } + } +} diff --git a/packages/core/src/index.js b/packages/core/src/index.js index 58c456a..fd1f547 100644 --- a/packages/core/src/index.js +++ b/packages/core/src/index.js @@ -1,30 +1,30 @@ import * as resources from './textures/resources'; -import * as systems from './renderers/systems'; +import * as systems from './systems'; -export { resources }; export { systems }; +export { resources }; -export { default as Renderer } from './renderers/Renderer'; -export { default as AbstractRenderer } from './renderers/AbstractRenderer'; -export { default as FrameBuffer } from './textures/FrameBuffer'; +export { default as System } from './System'; +export { default as Renderer } from './Renderer'; +export { default as AbstractRenderer } from './AbstractRenderer'; +export { default as FrameBuffer } from './framebuffer/FrameBuffer'; export { default as CubeTexture } from './textures/CubeTexture'; export { default as BaseTexture } from './textures/BaseTexture'; export { default as Texture } from './textures/Texture'; export { default as TextureMatrix } from './textures/TextureMatrix'; -export { default as RenderTexture } from './textures/RenderTexture'; -export { default as BaseRenderTexture } from './textures/BaseRenderTexture'; +export { default as RenderTexture } from './renderTexture/RenderTexture'; +export { default as BaseRenderTexture } from './renderTexture/BaseRenderTexture'; export { default as TextureUvs } from './textures/TextureUvs'; -export { default as State } from './renderers/State'; -export { default as ObjectRenderer } from './renderers/utils/ObjectRenderer'; -export { default as Quad } from './renderers/utils/Quad'; -export { default as QuadUv } from './renderers/utils/QuadUv'; -export { default as checkMaxIfStatmentsInShader } from './renderers/utils/checkMaxIfStatmentsInShader'; +export { default as State } from './state/State'; +export { default as ObjectRenderer } from './batch/ObjectRenderer'; +export { default as Quad } from './utils/Quad'; +export { default as QuadUv } from './utils/QuadUv'; +export { default as checkMaxIfStatmentsInShader } from './shader/utils/checkMaxIfStatmentsInShader'; export { default as Shader } from './shader/Shader'; export { default as Program } from './shader/Program'; export { default as UniformGroup } from './shader/UniformGroup'; -export { default as SpriteMaskFilter } from './renderers/filters/spriteMask/SpriteMaskFilter'; -export { default as Filter } from './renderers/filters/Filter'; +export { default as SpriteMaskFilter } from './filters/spriteMask/SpriteMaskFilter'; +export { default as Filter } from './filters/Filter'; export { default as Attribute } from './geometry/Attribute'; export { default as Buffer } from './geometry/Buffer'; export { default as Geometry } from './geometry/Geometry'; - diff --git a/packages/core/src/mask/MaskSystem.js b/packages/core/src/mask/MaskSystem.js new file mode 100644 index 0000000..99174c2 --- /dev/null +++ b/packages/core/src/mask/MaskSystem.js @@ -0,0 +1,199 @@ +import System from '../System'; +import AlphaMaskFilter from '../filters/spriteMask/SpriteMaskFilter'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class MaskSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + // TODO - we don't need both! + this.scissor = false; + this.scissorData = null; + this.scissorRenderTarget = null; + + this.enableScissor = false; + + this.alphaMaskPool = []; + this.alphaMaskIndex = 0; + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.DisplayObject} target - Display Object to push the mask to + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + push(target, maskData) + { + // TODO the root check means scissor rect will not + // be used on render textures more info here: + // https://github.com/pixijs/pixi.js/pull/3545 + + if (maskData.texture) + { + this.pushSpriteMask(target, maskData); + } + else if (this.enableScissor + && !this.scissor + && this.renderer._activeRenderTarget.root + && !this.renderer.stencil.stencilMaskStack.length + && maskData.isFastRect()) + { + const matrix = maskData.worldTransform; + + let rot = Math.atan2(matrix.b, matrix.a); + + // use the nearest degree! + rot = Math.round(rot * (180 / Math.PI)); + + if (rot % 90) + { + this.pushStencilMask(maskData); + } + else + { + this.pushScissorMask(target, maskData); + } + } + else + { + this.pushStencilMask(maskData); + } + } + + /** + * Removes the last mask from the mask stack and doesn't return it. + * + * @param {PIXI.DisplayObject} target - Display Object to pop the mask from + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + pop(target, maskData) + { + if (maskData.texture) + { + this.popSpriteMask(target, maskData); + } + else if (this.enableScissor && !this.renderer.stencil.stencilMaskStack.length) + { + this.popScissorMask(target, maskData); + } + else + { + this.popStencilMask(target, maskData); + } + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.RenderTarget} target - Display Object to push the sprite mask to + * @param {PIXI.Sprite} maskData - Sprite to be used as the mask + */ + pushSpriteMask(target, maskData) + { + let alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; + + if (!alphaMaskFilter) + { + alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; + } + + alphaMaskFilter[0].resolution = this.renderer.resolution; + alphaMaskFilter[0].maskSprite = maskData; + + // TODO - may cause issues! + target.filterArea = maskData.getBounds(true); + + this.renderer.filterSystem.pushFilter(target, alphaMaskFilter); + + this.alphaMaskIndex++; + } + + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popSpriteMask() + { + this.renderer.filterSystem.popFilter(); + this.alphaMaskIndex--; + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + pushStencilMask(maskData) + { + this.renderer.batch.flush(); + this.renderer.stencil.pushStencil(maskData); + } + + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popStencilMask() + { + // this.renderer.currentRenderer.stop(); + this.renderer.stencil.popStencil(); + } + + /** + * + * @param {PIXI.DisplayObject} target - Display Object to push the mask to + * @param {PIXI.Graphics} maskData - The masking data. + */ + pushScissorMask(target, maskData) + { + maskData.renderable = true; + + const renderTarget = this.renderer._activeRenderTarget; + + const bounds = maskData.getBounds(); + + bounds.fit(renderTarget.size); + maskData.renderable = false; + + this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); + + const resolution = this.renderer.resolution; + + this.renderer.gl.scissor( + bounds.x * resolution, + (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, + bounds.width * resolution, + bounds.height * resolution + ); + + this.scissorRenderTarget = renderTarget; + this.scissorData = maskData; + this.scissor = true; + } + + /** + * + * + */ + popScissorMask() + { + this.scissorRenderTarget = null; + this.scissorData = null; + this.scissor = false; + + // must be scissor! + const gl = this.renderer.gl; + + gl.disable(gl.SCISSOR_TEST); + } +} diff --git a/packages/core/src/mask/StencilSystem.js b/packages/core/src/mask/StencilSystem.js new file mode 100644 index 0000000..62796a5 --- /dev/null +++ b/packages/core/src/mask/StencilSystem.js @@ -0,0 +1,133 @@ +import System from '../System'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class StencilSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + this.stencilMaskStack = []; + } + + /** + * Changes the mask stack that is used by this System. + * + * @param {PIXI.Graphics[]} stencilMaskStack - The mask stack + */ + setMaskStack(stencilMaskStack) + { + const gl = this.renderer.gl; + + if (stencilMaskStack.length !== this.stencilMaskStack.length) + { + if (stencilMaskStack.length === 0) + { + gl.disable(gl.STENCIL_TEST); + } + else + { + gl.enable(gl.STENCIL_TEST); + } + } + + this.stencilMaskStack = stencilMaskStack; + } + + /** + * Applies the Mask and adds it to the current stencil stack. @alvin + * + * @param {PIXI.Graphics} graphics - The mask + */ + pushStencil(graphics) + { + this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); + + // this.renderer._activeRenderTarget.attachStencilBuffer(); + + const gl = this.renderer.gl; + const prevMaskCount = this.stencilMaskStack.length; + + if (prevMaskCount === 0) + { + gl.enable(gl.STENCIL_TEST); + } + + this.stencilMaskStack.push(graphics); + + // Increment the refference stencil value where the new mask overlaps with the old ones. + gl.colorMask(false, false, false, false); + gl.stencilFunc(gl.EQUAL, prevMaskCount, this._getBitwiseMask()); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.INCR); + this.renderer.plugins.graphics.render(graphics); + + this._useCurrent(); + } + + /** + * Removes the last mask from the stencil stack. @alvin + */ + popStencil() + { + this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); + + const gl = this.renderer.gl; + const graphics = this.stencilMaskStack.pop(); + + if (this.stencilMaskStack.length === 0) + { + // the stack is empty! + gl.disable(gl.STENCIL_TEST); + gl.clear(gl.STENCIL_BUFFER_BIT); + gl.clearStencil(0); + } + else + { + // Decrement the refference stencil value where the popped mask overlaps with the other ones + gl.colorMask(false, false, false, false); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.DECR); + this.renderer.plugins.graphics.render(graphics); + + this._useCurrent(); + } + } + + /** + * Setup renderer to use the current stencil data. + */ + _useCurrent() + { + const gl = this.renderer.gl; + + gl.colorMask(true, true, true, true); + gl.stencilFunc(gl.EQUAL, this.stencilMaskStack.length, this._getBitwiseMask()); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP); + } + + /** + * Fill 1s equal to the number of acitve stencil masks. + * + * @return {number} The bitwise mask. + */ + _getBitwiseMask() + { + return (1 << this.stencilMaskStack.length) - 1; + } + + /** + * Destroys the mask stack. + * + */ + destroy() + { + super.destroy(this); + + this.stencilMaskStack = null; + } +} diff --git a/packages/core/src/projection/ProjectionSystem.js b/packages/core/src/projection/ProjectionSystem.js new file mode 100644 index 0000000..6e63b73 --- /dev/null +++ b/packages/core/src/projection/ProjectionSystem.js @@ -0,0 +1,73 @@ +import System from '../System'; +import { Matrix } from '@pixi/math'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class ProjectionSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.projectionMatrix = new Matrix(); + } + + update(destinationFrame, sourceFrame, resolution, root) + { + this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; + this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; + + this.calculateProjection(this.destinationFrame, this.sourceFrame, resolution, root); + + this.renderer.globalUniforms.uniforms.projectionMatrix = this.projectionMatrix; + this.renderer.globalUniforms.update(); + } + + /** + * Updates the projection matrix based on a projection frame (which is a rectangle) + * + * @param {Rectangle} destinationFrame - The destination frame. + * @param {Rectangle} sourceFrame - The source frame. + */ + calculateProjection(destinationFrame, sourceFrame, resolution, root) + { + const pm = this.projectionMatrix; + + // I don't think we will need this line.. + // pm.identity(); + + if (!root) + { + pm.a = (1 / destinationFrame.width * 2) * resolution; + pm.d = (1 / destinationFrame.height * 2) * resolution; + + pm.tx = -1 - (sourceFrame.x * pm.a); + pm.ty = -1 - (sourceFrame.y * pm.d); + } + else + { + pm.a = (1 / destinationFrame.width * 2) * resolution; + pm.d = (-1 / destinationFrame.height * 2) * resolution; + + pm.tx = -1 - (sourceFrame.x * pm.a); + pm.ty = 1 - (sourceFrame.y * pm.d); + } + } + + /** + * Sets the transform of the active render target to the given matrix + * + * @param {PIXI.Matrix} matrix - The transformation matrix + */ + setTransform()// matrix) + { + // this._activeRenderTarget.transform = matrix; + } +} diff --git a/packages/core/src/renderTexture/BaseRenderTexture.js b/packages/core/src/renderTexture/BaseRenderTexture.js new file mode 100644 index 0000000..88eb83b --- /dev/null +++ b/packages/core/src/renderTexture/BaseRenderTexture.js @@ -0,0 +1,137 @@ +import BaseTexture from '../textures/BaseTexture'; +import FrameBuffer from '../framebuffer/FrameBuffer'; + +/** + * A BaseRenderTexture is a special texture that allows any PixiJS display object to be rendered to it. + * + * __Hint__: All DisplayObjects (i.e. Sprites) that render to a BaseRenderTexture should be preloaded + * otherwise black rectangles will be drawn instead. + * + * A BaseRenderTexture takes a snapshot of any Display Object given to its render method. The position + * and rotation of the given Display Objects is ignored. For example: + * + * ```js + * let renderer = PIXI.autoDetectRenderer(1024, 1024, { view: canvas, ratio: 1 }); + * let baseRenderTexture = new PIXI.BaseRenderTexture(renderer, 800, 600); + * let sprite = PIXI.Sprite.fromImage("spinObj_01.png"); + * + * sprite.position.x = 800/2; + * sprite.position.y = 600/2; + * sprite.anchor.x = 0.5; + * sprite.anchor.y = 0.5; + * + * baseRenderTexture.render(sprite); + * ``` + * + * The Sprite in this case will be rendered using its local transform. To render this sprite at 0,0 + * you can clear the transform + * + * ```js + * + * sprite.setTransform() + * + * let baseRenderTexture = new PIXI.BaseRenderTexture(100, 100); + * let renderTexture = new PIXI.RenderTexture(baseRenderTexture); + * + * renderer.render(sprite, renderTexture); // Renders to center of RenderTexture + * ``` + * + * @class + * @extends PIXI.BaseTexture + * @memberof PIXI + */ +export default class BaseRenderTexture extends BaseTexture +{ + /** + * @param {object} [options] + * @param {number} [options.width=100] - The width of the base render texture + * @param {number} [options.height=100] - The height of the base render texture + * @param {PIXI.SCALE_MODES} [options.scaleMode] - See {@link PIXI.SCALE_MODES} for possible values + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the texture being generated + */ + constructor(options) + { + if (typeof options === 'number') + { + /* eslint-disable prefer-rest-params */ + // Backward compatibility of signature + const width = arguments[0]; + const height = arguments[1]; + const scaleMode = arguments[2]; + const resolution = arguments[3]; + + options = { width, height, scaleMode, resolution }; + /* eslint-enable prefer-rest-params */ + } + + super(null, options); + + const { width, height } = options || {}; + + // Set defaults + this.mipmap = false; + this.width = Math.ceil(width) || 100; + this.height = Math.ceil(height) || 100; + this.valid = true; + + /** + * A map of renderer IDs to webgl renderTargets + * + * @private + * @member {object} + */ + // this._glRenderTargets = {}; + + /** + * A reference to the canvas render target (we only need one as this can be shared across renderers) + * + * @private + * @member {object} + */ + this._canvasRenderTarget = null; + + this.clearColor = [0, 0, 0, 0]; + + this.frameBuffer = new FrameBuffer(width * this.resolution, height * this.resolution) + .addColorTexture(0, this); + + // TODO - could this be added the systems? + + /** + * The data structure for the stencil masks + * + * @member {PIXI.Graphics[]} + */ + this.stencilMaskStack = []; + + /** + * The data structure for the filters + * + * @member {PIXI.Graphics[]} + */ + this.filterStack = [{}]; + } + + /** + * Resizes the BaseRenderTexture. + * + * @param {number} width - The width to resize to. + * @param {number} height - The height to resize to. + */ + resize(width, height) + { + width = Math.ceil(width); + height = Math.ceil(height); + this.frameBuffer.resize(width * this.resolution, height * this.resolution); + } + + /** + * Destroys this texture + * + */ + destroy() + { + super.destroy(true); + this.renderer = null; + } +} diff --git a/packages/core/src/renderTexture/RenderTexture.js b/packages/core/src/renderTexture/RenderTexture.js new file mode 100644 index 0000000..f1ecf1f --- /dev/null +++ b/packages/core/src/renderTexture/RenderTexture.js @@ -0,0 +1,153 @@ +import BaseRenderTexture from './BaseRenderTexture'; +import Texture from '../textures/Texture'; + +/** + * A RenderTexture is a special texture that allows any PixiJS display object to be rendered to it. + * + * __Hint__: All DisplayObjects (i.e. Sprites) that render to a RenderTexture should be preloaded + * otherwise black rectangles will be drawn instead. + * + * A RenderTexture takes a snapshot of any Display Object given to its render method. For example: + * + * ```js + * let renderer = PIXI.autoDetectRenderer(1024, 1024, { view: canvas, ratio: 1 }); + * let renderTexture = PIXI.RenderTexture.create(800, 600); + * let sprite = PIXI.Sprite.fromImage("spinObj_01.png"); + * + * sprite.position.x = 800/2; + * sprite.position.y = 600/2; + * sprite.anchor.x = 0.5; + * sprite.anchor.y = 0.5; + * + * renderer.render(sprite, renderTexture); + * ``` + * + * The Sprite in this case will be rendered using its local transform. To render this sprite at 0,0 + * you can clear the transform + * + * ```js + * + * sprite.setTransform() + * + * let renderTexture = new PIXI.RenderTexture.create(100, 100); + * + * renderer.render(sprite, renderTexture); // Renders to center of RenderTexture + * ``` + * + * @class + * @extends PIXI.Texture + * @memberof PIXI + */ +export default class RenderTexture extends Texture +{ + /** + * @param {PIXI.BaseRenderTexture} baseRenderTexture - The renderer used for this RenderTexture + * @param {PIXI.Rectangle} [frame] - The rectangle frame of the texture to show + */ + constructor(baseRenderTexture, frame) + { + // support for legacy.. + let _legacyRenderer = null; + + if (!(baseRenderTexture instanceof BaseRenderTexture)) + { + /* eslint-disable prefer-rest-params, no-console */ + const width = arguments[1]; + const height = arguments[2]; + const scaleMode = arguments[3]; + const resolution = arguments[4]; + + // we have an old render texture.. + console.warn(`Please use RenderTexture.create(${width}, ${height}) instead of the ctor directly.`); + _legacyRenderer = arguments[0]; + /* eslint-enable prefer-rest-params, no-console */ + + frame = null; + baseRenderTexture = new BaseRenderTexture({ + width, + height, + scaleMode, + resolution, + }); + } + + /** + * The base texture object that this texture uses + * + * @member {BaseTexture} + */ + super(baseRenderTexture, frame); + + this.legacyRenderer = _legacyRenderer; + + /** + * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. + * + * @member {boolean} + */ + this.valid = true; + + /** + * FilterSystem temporary storage + * @private + * @member {PIXI.Rectangle} + */ + this.filterFrame = null; + + this._updateUvs(); + } + + /** + * Resizes the RenderTexture. + * + * @param {number} width - The width to resize to. + * @param {number} height - The height to resize to. + * @param {boolean} [resizeBaseTexture=true] - Should the baseTexture.width and height values be resized as well? + */ + resize(width, height, resizeBaseTexture = true) + { + width = Math.ceil(width); + height = Math.ceil(height); + + // TODO - could be not required.. + this.valid = (width > 0 && height > 0); + + this._frame.width = this.orig.width = width; + this._frame.height = this.orig.height = height; + + if (resizeBaseTexture) + { + this.baseTexture.resize(width, height); + } + + this._updateUvs(); + } + + /** + * A short hand way of creating a render texture. + * + * @param {object} [options] - Options + * @param {number} [options.width=100] - The width of the render texture + * @param {number} [options.height=100] - The height of the render texture + * @param {number} [options.scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the texture being generated + * @return {PIXI.RenderTexture} The new render texture + */ + static create(options) + { + // fallback, old-style: create(width, height, scaleMode, resolution) + if (typeof options === 'number') + { + /* eslint-disable prefer-rest-params */ + options = { + width: options, + height: arguments[1], + scaleMode: arguments[2], + resolution: arguments[3], + }; + /* eslint-enable prefer-rest-params */ + } + + return new RenderTexture(new BaseRenderTexture(options)); + } +} diff --git a/packages/core/src/renderTexture/RenderTextureSystem.js b/packages/core/src/renderTexture/RenderTextureSystem.js new file mode 100644 index 0000000..8d0ca61 --- /dev/null +++ b/packages/core/src/renderTexture/RenderTextureSystem.js @@ -0,0 +1,115 @@ +import System from '../System'; +import { Rectangle } from '@pixi/math'; + +const tempRect = new Rectangle(); + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class RenderTextureSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.clearColor = renderer._backgroundColorRgba; + + // TODO moe this property somewhere else! + this.defaultMaskStack = []; + this.defaultFilterStack = [{}]; + + // empty render texture? + this.renderTexture = null; + + this.destinationFrame = new Rectangle(); + } + + bind(renderTexture, sourceFrame, destinationFrame) + { + // TODO - do we want this?? + if (this.renderTexture === renderTexture) return; + this.renderTexture = renderTexture; + + const renderer = this.renderer; + + if (renderTexture) + { + const baseTexture = renderTexture.baseTexture; + + if (!destinationFrame) + { + tempRect.width = baseTexture.realWidth; + tempRect.height = baseTexture.realHeight; + + destinationFrame = tempRect; + } + + if (!sourceFrame) + { + sourceFrame = destinationFrame; + } + + this.renderer.framebuffer.bind(baseTexture.frameBuffer, destinationFrame); + + this.renderer.projection.update(destinationFrame, sourceFrame, baseTexture.resolution, false); + this.renderer.stencil.setMaskStack(baseTexture.stencilMaskStack); + } + else + { + // TODO these validation checks happen deeper down.. + // thing they can be avoided.. + if (!destinationFrame) + { + tempRect.width = renderer.width; + tempRect.height = renderer.height; + + destinationFrame = tempRect; + } + + if (!sourceFrame) + { + sourceFrame = destinationFrame; + } + + renderer.framebuffer.bind(null, destinationFrame); + + // TODO store this.. + this.renderer.projection.update(destinationFrame, sourceFrame, this.renderer.resolution, true); + this.renderer.stencil.setMaskStack(this.defaultMaskStack); + } + + this.destinationFrame.copyFrom(destinationFrame); + } + + /** + * Erases the render texture and fills the drawing area with a colour + * + * @param {number} [clearColor] - The colour + * @return {PIXI.Renderer} Returns itself. + */ + clear(clearColor) + { + if (this.renderTexture) + { + clearColor = clearColor || this.renderTexture.baseTexture.clearColor; + } + else + { + clearColor = clearColor || this.clearColor; + } + + this.renderer.framebuffer.clear(clearColor[0], clearColor[1], clearColor[2], clearColor[3]); + } + + resize()// screenWidth, screenHeight) + { + // resize the root only! + this.bind(null); + } +} diff --git a/packages/core/src/renderers/AbstractRenderer.js b/packages/core/src/renderers/AbstractRenderer.js deleted file mode 100644 index 928670f..0000000 --- a/packages/core/src/renderers/AbstractRenderer.js +++ /dev/null @@ -1,353 +0,0 @@ -import { hex2string, hex2rgb } from '@pixi/utils'; -import { Matrix, Rectangle } from '@pixi/math'; -import { RENDERER_TYPE } from '@pixi/constants'; -import { settings } from '@pixi/settings'; -import { Container } from '@pixi/display'; -import RenderTexture from '../textures/RenderTexture'; -import EventEmitter from 'eventemitter3'; - -const tempMatrix = new Matrix(); - -/** - * 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 EventEmitter - * @memberof PIXI - */ -export default class AbstractRenderer extends EventEmitter -{ - // eslint-disable-next-line valid-jsdoc - /** - * @param {string} system - The name of the system this renderer is for. - * @param {object} [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.transparent=false] - If the render view is transparent, default false - * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false - * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) - * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. The - * resolution of the renderer retina would be 2. - * @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 {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when rendering, - * stopping pixel interpolation. - */ - constructor(system, options, arg2, arg3) - { - super(); - - // Support for constructor(system, screenWidth, screenHeight, options) - if (typeof options === 'number') - { - options = Object.assign({ - width: options, - height: arg2 || settings.RENDER_OPTIONS.height, - }, arg3); - } - - // 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 = RENDERER_TYPE.UNKNOWN; - - /** - * Measurements of the screen. (0, 0, screenWidth, screenHeight) - * - * Its safe to use as filterArea or hitArea for 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 1 - */ - this.resolution = options.resolution || settings.RESOLUTION; - - /** - * Whether the render view is transparent - * - * @member {boolean} - */ - this.transparent = options.transparent; - - /** - * Whether css dimensions of canvas view should be resized to screen dimensions automatically - * - * @member {boolean} - */ - this.autoResize = options.autoResize || false; - - /** - * Tracks the blend modes useful for this renderer. - * - * @member {object} - */ - this.blendModes = null; - - /** - * 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; - - /** - * If true PixiJS will Math.floor() x/y values when rendering, stopping pixel interpolation. - * Handy for crisp pixel art and speed on legacy devices. - * - * @member {boolean} - */ - this.roundPixels = options.roundPixels; - - /** - * The background color as a number. - * - * @member {number} - * @private - */ - this._backgroundColor = 0x000000; - - /** - * The background color as an [R, G, B] array. - * - * @member {number[]} - * @private - */ - this._backgroundColorRgba = [0, 0, 0, 0]; - - /** - * The background color as a string. - * - * @member {string} - * @private - */ - this._backgroundColorString = '#000000'; - - this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter - - /** - * This temporary display object used as the parent of the currently being rendered item - * - * @member {PIXI.DisplayObject} - * @private - */ - this._tempDisplayObjectParent = new Container(); - - /** - * The last root object that the renderer tried to render. - * - * @member {PIXI.DisplayObject} - * @private - */ - this._lastObjectRendered = this._tempDisplayObjectParent; - - /** - * Collection of plugins. - * @readonly - * @member {object} - */ - this.plugins = {}; - } - - /** - * Initialize the plugins. - * - * @protected - * @param {object} staticMap - The dictionary of staticly saved plugins. - */ - initPlugins(staticMap) - { - 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() - { - return this.view.width; - } - - /** - * Same as view.height, actual number of pixels in the canvas by vertical - * - * @member {number} - * @readonly - * @default 600 - */ - get height() - { - return this.view.height; - } - - /** - * Resizes the screen and canvas to the specified width and height - * Canvas dimensions are multiplied by resolution - * - * @param {number} screenWidth - the new width of the screen - * @param {number} screenHeight - the new height of the screen - */ - resize(screenWidth, screenHeight) - { - this.screen.width = screenWidth; - this.screen.height = screenHeight; - - this.view.width = screenWidth * this.resolution; - this.view.height = screenHeight * this.resolution; - - if (this.autoResize) - { - this.view.style.width = `${screenWidth}px`; - this.view.style.height = `${screenHeight}px`; - } - } - - /** - * 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 {PIXI.DisplayObject} displayObject - The displayObject the object will be generated from - * @param {number} scaleMode - Should be one of the scaleMode consts - * @param {number} resolution - The resolution / device pixel ratio of the texture being generated - * @param {PIXI.Rectangle} [region] - The region of the displayObject, that shall be rendered, - * if no region is specified, defaults to the local bounds of the displayObject. - * @return {PIXI.Texture} a texture of the graphics object - */ - generateTexture(displayObject, scaleMode, resolution, region) - { - region = region || displayObject.getLocalBounds(); - - // 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(region.width | 0, region.height | 0, scaleMode, resolution); - - tempMatrix.tx = -region.x; - tempMatrix.ty = -region.y; - - this.render(displayObject, renderTexture, false, tempMatrix, true); - - return renderTexture; - } - - /** - * Removes everything from the renderer and optionally removes the Canvas DOM element. - * - * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. - */ - destroy(removeView) - { - 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); - } - - this.plugins = null; - - this.type = RENDERER_TYPE.UNKNOWN; - - this.view = null; - - this.screen = null; - - this.resolution = 0; - - this.transparent = false; - - this.autoResize = false; - - this.blendModes = null; - - this.options = null; - - this.preserveDrawingBuffer = false; - this.clearBeforeRender = false; - - this.roundPixels = false; - - this._backgroundColor = 0; - this._backgroundColorRgba = null; - this._backgroundColorString = null; - - this._tempDisplayObjectParent = null; - this._lastObjectRendered = null; - } - - /** - * The background color to fill if not transparent - * - * @member {number} - */ - get backgroundColor() - { - return this._backgroundColor; - } - - set backgroundColor(value) // eslint-disable-line require-jsdoc - { - this._backgroundColor = value; - this._backgroundColorString = hex2string(value); - hex2rgb(value, this._backgroundColorRgba); - } -} diff --git a/packages/core/src/renderers/Renderer.js b/packages/core/src/renderers/Renderer.js deleted file mode 100644 index a2020b1..0000000 --- a/packages/core/src/renderers/Renderer.js +++ /dev/null @@ -1,329 +0,0 @@ -import AbstractRenderer from './AbstractRenderer'; -import { sayHello } from '@pixi/utils'; -import MaskSystem from './systems/MaskSystem'; -import StencilSystem from './systems/StencilSystem'; -import FilterSystem from './systems/filter/FilterSystem'; -import FramebufferSystem from './systems/FramebufferSystem'; -import RenderTextureSystem from './systems/RenderTextureSystem'; -import TextureSystem from './systems/textures/TextureSystem'; -import ProjectionSystem from './systems/ProjectionSystem'; -import StateSystem from './systems/StateSystem'; -import GeometrySystem from './systems/geometry/GeometrySystem'; -import ShaderSystem from './systems/shader/ShaderSystem'; -import ContextSystem from './systems/ContextSystem'; -import BatchSystem from './systems/BatchSystem'; -import TextureGCSystem from './systems/textures/TextureGCSystem'; -import { RENDERER_TYPE } from '@pixi/constants'; -import UniformGroup from '../shader/UniformGroup'; -import { Matrix } from '@pixi/math'; -import Runner from 'mini-runner'; - -/** - * 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 Render works by automatically managing webGLBatchs. - * 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 :) - * - * @class - * @memberof PIXI - * @extends PIXI.AbstractRenderer - */ -export default class Renderer extends AbstractRenderer -{ - // eslint-disable-next-line valid-jsdoc - /** - * - * @param {object} [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.transparent=false] - If the render view is transparent, default false - * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false - * @param {boolean} [options.antialias=false] - sets antialias. If not available natively then FXAA - * antialiasing is used - * @param {boolean} [options.forceFXAA=false] - forces FXAA antialiasing to be used over native. - * FXAA is faster, but may not always look as great - * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. - * The resolution of the renderer retina would be 2. - * @param {boolean} [options.clearBeforeRender=true] - This sets if the renderer will clear - * the canvas or not before the new render pass. If you wish to set this to false, you *must* set - * preserveDrawingBuffer to `true`. - * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, - * enable this if you need to call toDataUrl on the webgl context. - * @param {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when - * rendering, stopping pixel interpolation. - * @param {number} [options.backgroundColor=0x000000] - The background color of the rendered area - * (shown if not transparent). - * @param {boolean} [options.legacy=false] - If true PixiJS will aim to ensure compatibility - * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. - * @param {string} [options.powerPreference] - Parameter passed to webgl context, set to "high-performance" - * for devices with dual graphics card - */ - constructor(options, arg2, arg3) - { - super('WebGL', options, arg2, arg3); - - /** - * The type of this renderer as a standardised const - * - * @member {number} - * @see PIXI.RENDERER_TYPE - */ - this.type = RENDERER_TYPE.WEBGL; - - // this will be set by the contextSystem (this.context) - this.gl = null; - this.CONTEXT_UID = 0; - this.legacy = !!options.legacy; - - // TODO legacy! - - // runners! - this.runners = { - destroy: new Runner('destroy'), - contextChange: new Runner('contextChange', 1), - reset: new Runner('reset'), - update: new Runner('update'), - postrender: new Runner('postrender'), - prerender: new Runner('prerender'), - resize: new Runner('resize', 2), - }; - - 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(GeometrySystem, 'geometry') - .addSystem(FramebufferSystem, 'framebuffer') - .addSystem(StencilSystem, 'stencil') - .addSystem(ProjectionSystem, 'projection') - .addSystem(TextureGCSystem, 'textureGC') - .addSystem(FilterSystem, 'filter') - .addSystem(RenderTextureSystem, 'renderTexture') - .addSystem(BatchSystem, 'batch'); - - this.initPlugins(Renderer.__plugins); - - /** - * The options passed in to create a new webgl context. - * - * @member {object} - * @private - */ - if (options.context) - { - this.context.initFromContext(options.context); - } - else - { - this.context.initFromOptions({ - alpha: this.transparent, - antialias: options.antialias, - premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', - stencil: true, - 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); - } - - addSystem(_class, name) - { - if (!name) - { - name = _class.name; - } - - // TODO - read name from class.name.. - - /* - if(name.includes('System')) - { - name = name.replace('System', ''); - name = name.charAt(0).toLowerCase() + name.slice(1); - } - */ - - const system = new _class(this); - - if (this[name]) - { - throw new Error(`Whoops! ${name} is already a manger`); - } - - this[name] = system; - - for (const i in this.runners) - { - this.runners[i].add(system); - } - - return this; - - /** - * 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. - */ - } - - /** - * Renders the object to its webGL view - * - * @param {PIXI.DisplayObject} displayObject - the object to be rendered - * @param {PIXI.RenderTexture} renderTexture - The render texture to render to. - * @param {boolean} [clear] - Should the canvas be cleared before the new render - * @param {PIXI.Transform} [transform] - A transform to apply to the render texture before rendering. - * @param {boolean} [skipUpdateTransform] - Should we skip the update transform pass? - */ - render(displayObject, renderTexture, clear, transform, skipUpdateTransform) - { - // can be handy to know! - this.renderingToScreen = !renderTexture; - - this.runners.prerender.run(); - this.emit('prerender'); - - // 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.parent; - - displayObject.parent = this._tempDisplayObjectParent; - displayObject.updateTransform(); - displayObject.parent = 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.run(); - - this.emit('postrender'); - } - - /** - * Resizes the webGL view to the specified width and height. - * - * @param {number} screenWidth - the new width of the screen - * @param {number} screenHeight - the new height of the screen - */ - resize(screenWidth, screenHeight) - { - AbstractRenderer.prototype.resize.call(this, screenWidth, screenHeight); - - this.runners.resize.run(screenWidth, screenHeight); - } - - /** - * Resets the WebGL state so you can render things however you fancy! - * - * @return {PIXI.Renderer} Returns itself. - */ - reset() - { - this.runners.reset.run(); - - return this; - } - - clear() - { - this.framebuffer.bind(); - this.framebuffer.clear(); - } - - /** - * Removes everything from the renderer (event listeners, spritebatch, etc...) - * - * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. - * See: https://github.com/pixijs/pixi.js/issues/2233 - */ - destroy(removeView) - { - this.runners.destroy.run(); - - // call base destroy - super.destroy(removeView); - - // TODO nullify all the managers.. - this.gl = null; - } - - /** - * 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 - * builds and excluding plugins. - * @name PIXI.Renderer#plugins - * @type {object} - * @readonly - * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. - * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. - * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. - * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. - */ - - /** - * Adds a plugin to the renderer. - * - * @method - * @param {string} pluginName - The name of the plugin. - * @param {Function} ctor - The constructor function or class for the plugin. - */ - static registerPlugin(pluginName, ctor) - { - Renderer.__plugins = Renderer.__plugins || {}; - Renderer.__plugins[pluginName] = ctor; - } -} diff --git a/packages/core/src/renderers/State.js b/packages/core/src/renderers/State.js deleted file mode 100644 index fa3f221..0000000 --- a/packages/core/src/renderers/State.js +++ /dev/null @@ -1,163 +0,0 @@ -/* eslint-disable max-len */ - -const BLEND = 0; -const OFFSET = 1; -const CULLING = 2; -const DEPTH_TEST = 3; -const WINDING = 4; - -/** - * This is a webGL state. It is passed The WebGL StateManager. - * Each mesh renderered may require webGL to be in a different state. - * For example you may want different blend mode or to enable polygon offsets - * - * @class - * @memberof PIXI - */ -export default class State -{ - /** - * - */ - constructor() - { - this.data = 0; - - this.blendMode = 0; - this.polygonOffset = 0; - - this.blend = true; - // this.depthTest = true; - } - - /** - * Activates blending of the computed fragment color values - * - * @member {boolean} - */ - get blend() - { - return !!(this.data & (1 << BLEND)); - } - - set blend(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << BLEND)) !== value) - { - this.data ^= (1 << BLEND); - } - } - - /** - * Activates adding an offset to depth values of polygon's fragments - * - * @member {boolean} - * @default false - */ - get offsets() - { - return !!(this.data & (1 << OFFSET)); - } - - set offsets(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << OFFSET)) !== value) - { - this.data ^= (1 << OFFSET); - } - } - - /** - * Activates culling of polygons. - * - * @member {boolean} - * @default false - */ - get culling() - { - return !!(this.data & (1 << CULLING)); - } - - set culling(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << CULLING)) !== value) - { - this.data ^= (1 << CULLING); - } - } - - /** - * Activates depth comparisons and updates to the depth buffer. - * - * @member {boolean} - * @default false - */ - get depthTest() - { - return !!(this.data & (1 << DEPTH_TEST)); - } - - set depthTest(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << DEPTH_TEST)) !== value) - { - this.data ^= (1 << DEPTH_TEST); - } - } - - /** - * Specifies whether or not front or back-facing polygons can be culled. - * @member {boolean} - * @default false - */ - get clockwiseFrontFace() - { - return !!(this.data & (1 << WINDING)); - } - - set clockwiseFrontFace(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << WINDING)) !== value) - { - this.data ^= (1 << WINDING); - } - } - - /** - * The blend mode to be applied when this state is set. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. - * Setting this mode to anything other than NO_BLEND will automatically switch blending on. - * - * @member {boolean} - * @default PIXI.BLEND_MODES.NORMAL - * @see PIXI.BLEND_MODES - */ - get blendMode() - { - return this._blendMode; - } - - set blendMode(value) // eslint-disable-line require-jsdoc - { - // 17 is NO BLEND - this.blend = (value !== 17); - this._blendMode = value; - } - - /** - * The polygon offset. Setting this property to anything other than 0 will automatically enable poygon offset fill. - * - * @member {number} - * @default 0 - */ - get polygonOffset() - { - return this._polygonOffset; - } - - set polygonOffset(value) // eslint-disable-line require-jsdoc - { - this.offsets = !!value; - this._polygonOffset = value; - } -} - diff --git a/packages/core/src/renderers/filters/Filter.js b/packages/core/src/renderers/filters/Filter.js deleted file mode 100644 index 2073b8d..0000000 --- a/packages/core/src/renderers/filters/Filter.js +++ /dev/null @@ -1,140 +0,0 @@ -import Shader from '../../shader/Shader'; -import Program from '../../shader/Program'; -import State from '../State'; -import { settings } from '@pixi/settings'; -// import extractUniformsFromSrc from './extractUniformsFromSrc'; -import defaultVertex from './defaultFilter.vert'; -import defaultFragment from './defaultFilter.frag'; - -// let math = require('../../math'); -/** - * @class - * @memberof PIXI - * @extends PIXI.Shader - */ -export default class Filter extends Shader -{ - /** - * @param {string} [vertexSrc] - The source of the vertex shader. - * @param {string} [fragmentSrc] - The source of the fragment shader. - * @param {object} [uniforms] - Custom uniforms to use to augment the built-in ones. - */ - constructor(vertexSrc, fragmentSrc, uniforms) - { - const program = Program.from(vertexSrc, fragmentSrc); - - super(program, uniforms); - - /** - * The padding of the filter. Some filters require extra space to breath such as a blur. - * Increasing this will add extra width and height to the bounds of the object that the - * filter is applied to. - * - * @member {number} - */ - this.padding = 0; - - /** - * The resolution of the filter. Setting this to be lower will lower the quality but - * increase the performance of the filter. - * - * @member {number} - */ - this.resolution = settings.RESOLUTION; - - /** - * If enabled is true the filter is applied, if false it will not. - * - * @member {boolean} - */ - this.enabled = true; - - /** - * If enabled, PixiJS will fit the filter area into boundaries for better performance. - * Switch it off if it does not work for specific shader. - * - * @member {boolean} - */ - this.autoFit = true; - - /** - * Legacy filters use position and uvs from attributes - * @member {boolean} - * @readonly - */ - this.legacy = !!this.program.attributeData.aTextureCoord; - - /** - * the webGL state the filter requires to render - * @member {PIXI.State} - */ - this.state = new State(); - } - - /** - * Applies the filter - * - * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from - * @param {PIXI.RenderTarget} input - The input render target. - * @param {PIXI.RenderTarget} output - The target to output to. - * @param {boolean} clear - Should the output be cleared before rendering to it - * @param {object} [currentState] - It's current state of filter. - * There are some useful properties in the currentState : - * target, filters, sourceFrame, destinationFrame, renderTarget, resolution - */ - apply(filterManager, input, output, clear, currentState, derp) // eslint-disable-line no-unused-vars - { - // do as you please! - - filterManager.applyFilter(this, input, output, clear, currentState, derp); - - // or just do a regular render.. - } - - /** - * Sets the blendmode of the filter - * - * @member {number} - * @default PIXI.BLEND_MODES.NORMAL - */ - get blendMode() - { - return this.state.blendMode; - } - - set blendMode(value) // eslint-disable-line require-jsdoc - { - this.state.blendMode = value; - } - - /** - * The default vertex shader source - * - * @static - * @constant - */ - static get defaultVertexSrc() - { - return defaultVertex; - } - - /** - * The default fragment shader source - * - * @static - * @constant - */ - static get defaultFragmentSrc() - { - return defaultFragment; - } -} - -/** - * Used for caching shader IDs - * - * @static - * @private - */ -Filter.SOURCE_KEY_MAP = {}; - diff --git a/packages/core/src/renderers/filters/defaultFilter.frag b/packages/core/src/renderers/filters/defaultFilter.frag deleted file mode 100644 index a14c1be..0000000 --- a/packages/core/src/renderers/filters/defaultFilter.frag +++ /dev/null @@ -1,22 +0,0 @@ -varying vec2 vTextureCoord; -varying vec2 vFilterCoord; - -uniform sampler2D uSampler; -uniform sampler2D filterSampler; - -void main(void){ - vec4 masky = texture2D(filterSampler, vFilterCoord); - vec4 sample = texture2D(uSampler, vTextureCoord); - vec4 color; - if(mod(vFilterCoord.x, 1.0) > 0.5) - { - color = vec4(1.0, 0.0, 0.0, 1.0); - } - else - { - color = vec4(0.0, 1.0, 0.0, 1.0); - } - // gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0); - gl_FragColor = mix(sample, masky, 0.5); - gl_FragColor *= sample.a; -} diff --git a/packages/core/src/renderers/filters/defaultFilter.vert b/packages/core/src/renderers/filters/defaultFilter.vert deleted file mode 100644 index e1febca..0000000 --- a/packages/core/src/renderers/filters/defaultFilter.vert +++ /dev/null @@ -1,14 +0,0 @@ -attribute vec2 aVertexPosition; -attribute vec2 aTextureCoord; - -uniform mat3 projectionMatrix; -uniform mat3 filterMatrix; - -varying vec2 vTextureCoord; -varying vec2 vFilterCoord; - -void main(void){ - gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); - vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy; - vTextureCoord = aTextureCoord ; -} diff --git a/packages/core/src/renderers/filters/filterTransforms.js b/packages/core/src/renderers/filters/filterTransforms.js deleted file mode 100644 index 87a3cd6..0000000 --- a/packages/core/src/renderers/filters/filterTransforms.js +++ /dev/null @@ -1,50 +0,0 @@ -import { Matrix } from '@pixi/math'; - -/** - * Calculates the mapped matrix - * @param filterArea {Rectangle} The filter area - * @param sprite {Sprite} the target sprite - * @param outputMatrix {Matrix} @alvin - * @private - */ -// this returns a matrix that will normalise map filter cords in the filter to screen space -export function calculateScreenSpaceMatrix(outputMatrix, filterArea, textureSize) -{ - // TODO unwrap? - const mappedMatrix = outputMatrix.identity(); - - mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); - - mappedMatrix.scale(textureSize.width, textureSize.height); - - return mappedMatrix; -} - -export function calculateNormalizedScreenSpaceMatrix(outputMatrix, filterArea, textureSize) -{ - const mappedMatrix = outputMatrix.identity(); - - mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); - - const translateScaleX = (textureSize.width / filterArea.width); - const translateScaleY = (textureSize.height / filterArea.height); - - mappedMatrix.scale(translateScaleX, translateScaleY); - - return mappedMatrix; -} - -// this will map the filter coord so that a texture can be used based on the transform of a sprite -export function calculateSpriteMatrix(outputMatrix, filterArea, textureSize, sprite) -{ - const orig = sprite._texture.orig; - const mappedMatrix = outputMatrix.set(textureSize.width, 0, 0, textureSize.height, filterArea.x, filterArea.y); - const worldTransform = sprite.worldTransform.copyTo(Matrix.TEMP_MATRIX); - - worldTransform.invert(); - mappedMatrix.prepend(worldTransform); - mappedMatrix.scale(1.0 / orig.width, 1.0 / orig.height); - mappedMatrix.translate(sprite.anchor.x, sprite.anchor.y); - - return mappedMatrix; -} diff --git a/packages/core/src/renderers/filters/spriteMask/SpriteMaskFilter.js b/packages/core/src/renderers/filters/spriteMask/SpriteMaskFilter.js deleted file mode 100644 index d5a91a4..0000000 --- a/packages/core/src/renderers/filters/spriteMask/SpriteMaskFilter.js +++ /dev/null @@ -1,63 +0,0 @@ -import Filter from '../Filter'; -import { Matrix } from '@pixi/math'; -import vertex from './spriteMaskFilter.vert'; -import fragment from './spriteMaskFilter.frag'; -import { default as TextureMatrix } from '../../../textures/TextureMatrix'; - -/** - * The SpriteMaskFilter class - * - * @class - * @extends PIXI.Filter - * @memberof PIXI - */ -export default class SpriteMaskFilter extends Filter -{ - /** - * @param {PIXI.Sprite} sprite - the target sprite - */ - constructor(sprite) - { - const maskMatrix = new Matrix(); - - super(vertex, fragment); - - sprite.renderable = false; - - this.maskSprite = sprite; - this.maskMatrix = maskMatrix; - } - - /** - * Applies the filter - * - * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from - * @param {PIXI.RenderTarget} input - The input render target. - * @param {PIXI.RenderTarget} output - The target to output to. - */ - apply(filterManager, input, output) - { - const maskSprite = this.maskSprite; - const tex = this.maskSprite.texture; - - if (!tex.valid) - { - return; - } - if (!tex.transform) - { - // margin = 0.0, let it bleed a bit, shader code becomes easier - // assuming that atlas textures were made with 1-pixel padding - tex.transform = new TextureMatrix(tex, 0.0); - } - tex.transform.update(); - - this.uniforms.mask = tex; - this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite) - .prepend(tex.transform.mapCoord); - this.uniforms.alpha = maskSprite.worldAlpha; - this.uniforms.maskClamp = tex.transform.uClampFrame; - - filterManager.applyFilter(this, input, output); - } -} diff --git a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.frag b/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.frag deleted file mode 100644 index 0e4aef8..0000000 --- a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.frag +++ /dev/null @@ -1,23 +0,0 @@ -varying vec2 vMaskCoord; -varying vec2 vTextureCoord; - -uniform sampler2D uSampler; -uniform sampler2D mask; -uniform float alpha; -uniform vec4 maskClamp; - -void main(void) -{ - float clip = step(3.5, - step(maskClamp.x, vMaskCoord.x) + - step(maskClamp.y, vMaskCoord.y) + - step(vMaskCoord.x, maskClamp.z) + - step(vMaskCoord.y, maskClamp.w)); - - vec4 original = texture2D(uSampler, vTextureCoord); - vec4 masky = texture2D(mask, vMaskCoord); - - original *= (masky.r * masky.a * alpha * clip); - - gl_FragColor = original; -} diff --git a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.vert b/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.vert deleted file mode 100644 index d23a30c..0000000 --- a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.vert +++ /dev/null @@ -1,16 +0,0 @@ -attribute vec2 aVertexPosition; -attribute vec2 aTextureCoord; - -uniform mat3 projectionMatrix; -uniform mat3 otherMatrix; - -varying vec2 vMaskCoord; -varying vec2 vTextureCoord; - -void main(void) -{ - gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); - - vTextureCoord = aTextureCoord; - vMaskCoord = ( otherMatrix * vec3( aTextureCoord, 1.0) ).xy; -} diff --git a/packages/core/src/renderers/systems/BatchSystem.js b/packages/core/src/renderers/systems/BatchSystem.js deleted file mode 100644 index 70e9518..0000000 --- a/packages/core/src/renderers/systems/BatchSystem.js +++ /dev/null @@ -1,68 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import ObjectRenderer from '../utils/ObjectRenderer'; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ - -export default class BatchSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this manager works for. - */ - constructor(renderer) - { - super(renderer); - - /** - * An empty renderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.emptyRenderer = new ObjectRenderer(renderer); - - /** - * The currently active ObjectRenderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.currentRenderer = this.emptyRenderer; - } - - /** - * Changes the current renderer to the one given in parameter - * - * @param {PIXI.ObjectRenderer} objectRenderer - The object renderer to use. - */ - setObjectRenderer(objectRenderer) - { - if (this.currentRenderer === objectRenderer) - { - return; - } - - this.currentRenderer.stop(); - this.currentRenderer = objectRenderer; - - this.renderer.state.setState(objectRenderer.state); - - this.currentRenderer.start(); - } - - /** - * This should be called if you wish to do some custom rendering - * It will basically render anything that may be batched up such as sprites - * - */ - flush() - { - this.setObjectRenderer(this.emptyRenderer); - } - - reset() - { - this.setObjectRenderer(this.emptyRenderer); - } -} diff --git a/packages/core/src/renderers/systems/ContextSystem.js b/packages/core/src/renderers/systems/ContextSystem.js deleted file mode 100644 index 43bf827..0000000 --- a/packages/core/src/renderers/systems/ContextSystem.js +++ /dev/null @@ -1,186 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { settings } from '@pixi/settings'; - -let CONTEXT_UID = 0; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class ContextSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this.webGLVersion = 1; - - this.handleContextLost = this.handleContextLost.bind(this); - this.handleContextRestored = this.handleContextRestored.bind(this); - - this.extensions = {}; - - renderer.view.addEventListener('webglcontextlost', this.handleContextLost, false); - renderer.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); - } - - get isLost() - { - return (!this.gl || this.gl.isContextLost()); - } - - contextChange(gl) - { - this.gl = gl; - - // restore a context if it was previously lost - if (gl.isContextLost() && gl.getExtension('WEBGL_lose_context')) - { - gl.getExtension('WEBGL_lose_context').restoreContext(); - } - } - - initFromContext(gl) - { - this.gl = gl; - this.validateContext(gl); - this.renderer.gl = gl; - this.renderer.CONTEXT_UID = CONTEXT_UID++; - this.renderer.runners.contextChange.run(gl); - } - - initFromOptions(options) - { - const gl = this.createContext(this.renderer.view, options); - - this.initFromContext(gl); - } - - /** - * Helper class to create a webGL Context - * - * @class - * @memberof PIXI.glCore - * @param canvas {HTMLCanvasElement} the canvas element that we will get the context from - * @param options {Object} An options object that gets passed in to the canvas element containing the context attributes, - * see https://developer.mozilla.org/en/docs/Web/API/HTMLCanvasElement/getContext for the options available - * @return {WebGLRenderingContext} the WebGL context - */ - createContext(canvas, options) - { - let gl; - - if (settings.PREFER_WEBGL_2) - { - gl = canvas.getContext('webgl2', options); - } - - if (gl) - { - this.webGLVersion = 2; - } - else - { - this.webGLVersion = 1; - - gl = canvas.getContext('webgl', options) - || canvas.getContext('experimental-webgl', options); - - if (!gl) - { - // fail, not able to get a context - throw new Error('This browser does not support webGL. Try using the canvas renderer'); - } - } - - this.gl = gl; - - this.getExtensions(); - - return gl; - } - - getExtensions() - { - // time to set up default etensions that pixi uses.. - const gl = this.gl; - const extensions = this.extensions; - - if (this.webGLVersion === 1) - { - extensions.drawBuffers = gl.getExtension('WEBGL_draw_buffers'); - extensions.depthTexture = gl.getExtension('WEBKIT_WEBGL_depth_texture'); - extensions.floatTexture = gl.getExtension('OES_texture_float'); - extensions.loseContext = gl.getExtension('WEBGL_lose_context'); - - extensions.vertexArrayObject = gl.getExtension('OES_vertex_array_object') - || gl.getExtension('MOZ_OES_vertex_array_object') - || gl.getExtension('WEBKIT_OES_vertex_array_object'); - } - - // we don't use any specific WebGL 2 ones yet! - } - - /** - * Handles a lost webgl context - * - * @private - * @param {WebGLContextEvent} event - The context lost event. - */ - handleContextLost(event) - { - event.preventDefault(); - } - - /** - * Handles a restored webgl context - * - * @private - */ - handleContextRestored() - { - this.renderer.runners.contextChange.run(this.gl); - } - - destroy() - { - const view = this.renderer.view; - - // remove listeners - view.removeEventListener('webglcontextlost', this.handleContextLost); - view.removeEventListener('webglcontextrestored', this.handleContextRestored); - - this.gl.useProgram(null); - - if (this.extensions.loseContext) - { - this.extensions.loseContext.loseContext(); - } - } - - postrender() - { - this.gl.flush(); - } - - validateContext(gl) - { - const attributes = gl.getContextAttributes(); - - // this is going to be fairly simple for now.. but at least we have room to grow! - if (!attributes.stencil) - { - /* eslint-disable max-len */ - - /* eslint-disable no-console */ - console.warn('Provided WebGL context does not have a stencil buffer, masks may not render correctly'); - /* eslint-enable no-console */ - - /* eslint-enable max-len */ - } - } -} diff --git a/packages/core/src/renderers/systems/FramebufferSystem.js b/packages/core/src/renderers/systems/FramebufferSystem.js deleted file mode 100644 index df7902b..0000000 --- a/packages/core/src/renderers/systems/FramebufferSystem.js +++ /dev/null @@ -1,246 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { Rectangle } from '@pixi/math'; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class FramebufferSystem extends WebGLSystem -{ - /** - * Sets up the renderer context and necessary buffers. - * - * @private - */ - contextChange() - { - this.gl = this.renderer.gl; - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - this.current = null; - this.viewport = new Rectangle(); - - this.drawBufferExtension = this.renderer.context.extensions.drawBuffers; - } - - bind(framebuffer, frame) - { - const gl = this.gl; - - this.current = framebuffer; - - if (framebuffer) - { - // TODO cacheing layer! - - const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID] || this.initFramebuffer(framebuffer); - - gl.bindFramebuffer(gl.FRAMEBUFFER, fbo.framebuffer); - // makesure all textures are unbound.. - - // now check for updates... - if (fbo.dirtyId !== framebuffer.dirtyId) - { - fbo.dirtyId = framebuffer.dirtyId; - - if (fbo.dirtyFormat !== framebuffer.dirtyFormat) - { - fbo.dirtyFormat = framebuffer.dirtyFormat; - this.updateFramebuffer(framebuffer); - } - else if (fbo.dirtySize !== framebuffer.dirtySize) - { - fbo.dirtySize = framebuffer.dirtySize; - this.resizeFramebuffer(framebuffer); - } - } - - for (let i = 0; i < framebuffer.colorTextures.length; i++) - { - if (framebuffer.colorTextures[i].texturePart) - { - this.renderer.texture.unbind(framebuffer.colorTextures[i].texture); - } - else - { - this.renderer.texture.unbind(framebuffer.colorTextures[i]); - } - } - - if (framebuffer.depthTexture) - { - this.renderer.texture.unbind(framebuffer.depthTexture); - } - - if (frame) - { - this.setViewport(frame.x, frame.y, frame.width, frame.height); - } - else - { - this.setViewport(0, 0, framebuffer.width, framebuffer.height); - } - } - else - { - gl.bindFramebuffer(gl.FRAMEBUFFER, null); - - if (frame) - { - this.setViewport(frame.x, frame.y, frame.width, frame.height); - } - else - { - this.setViewport(0, 0, this.renderer.width, this.renderer.height); - } - } - } - - setViewport(x, y, width, height) - { - const v = this.viewport; - - if (v.width !== width || v.height !== height || v.x !== x || v.y !== y) - { - v.x = x; - v.y = y; - v.width = width; - v.height = height; - - this.gl.viewport(x, y, width, height); - } - } - - get size() - { - if (this.current) - { - // TODO store temp - return { x: 0, y: 0, width: this.current.width, height: this.current.height }; - } - - return { x: 0, y: 0, width: this.renderer.width, height: this.renderer.height }; - } - - clear(r, g, b, a) - { - const gl = this.gl; - - // TODO clear color can be set only one right? - gl.clearColor(r, g, b, a); - gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); - } - - // private functions... - - initFramebuffer(framebuffer) - { - const gl = this.gl; - - // TODO - make this a class? - const fbo = { - framebuffer: gl.createFramebuffer(), - stencil: null, - dirtyId: 0, - dirtyFormat: 0, - dirtySize: 0, - }; - - framebuffer.glFrameBuffers[this.CONTEXT_UID] = fbo; - - return fbo; - } - - resizeFramebuffer(framebuffer) - { - const gl = this.gl; - - if (framebuffer.stencil || framebuffer.depth) - { - gl.bindRenderbuffer(gl.RENDERBUFFER, this.stencil); - gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); - } - } - - updateFramebuffer(framebuffer) - { - const gl = this.gl; - - const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID]; - - // bind the color texture - const colorTextures = framebuffer.colorTextures; - - let count = colorTextures.length; - - if (!this.drawBufferExtension) - { - count = Math.min(count, 1); - } - - const activeTextures = []; - - for (let i = 0; i < count; i++) - { - const texture = framebuffer.colorTextures[i]; - - if (texture.texturePart) - { - this.renderer.texture.bind(texture.texture, 0); - - gl.framebufferTexture2D(gl.FRAMEBUFFER, - gl.COLOR_ATTACHMENT0 + i, - gl.TEXTURE_CUBE_MAP_NEGATIVE_X + texture.side, - texture.texture._glTextures[this.CONTEXT_UID].texture, - 0); - } - else - { - this.renderer.texture.bind(texture, 0); - - gl.framebufferTexture2D(gl.FRAMEBUFFER, - gl.COLOR_ATTACHMENT0 + i, - gl.TEXTURE_2D, - texture._glTextures[this.CONTEXT_UID].texture, - 0); - } - - activeTextures.push(gl.COLOR_ATTACHMENT0 + i); - } - - if (this.drawBufferExtension && activeTextures.length > 1) - { - this.drawBufferExtension.drawBuffersWEBGL(activeTextures); - } - - if (framebuffer.depthTexture) - { - const depthTextureExt = this.renderer.context.extensions.depthTexture; - - if (depthTextureExt) - { - const depthTexture = framebuffer.depthTexture; - - this.renderer.texture.bind(depthTexture, 0); - - gl.framebufferTexture2D(gl.FRAMEBUFFER, - gl.DEPTH_ATTACHMENT, - gl.TEXTURE_2D, - depthTexture._glTextures[this.CONTEXT_UID].texture, - 0); - } - } - - if (framebuffer.stencil || framebuffer.depth) - { - fbo.stencil = gl.createRenderbuffer(); - - gl.bindRenderbuffer(gl.RENDERBUFFER, fbo.stencil); - - // TODO.. this is depth AND stencil? - gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, fbo.stencil); - gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); - // fbo.enableStencil(); - } - } -} diff --git a/packages/core/src/renderers/systems/MaskSystem.js b/packages/core/src/renderers/systems/MaskSystem.js deleted file mode 100644 index d9c4b1a..0000000 --- a/packages/core/src/renderers/systems/MaskSystem.js +++ /dev/null @@ -1,199 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import AlphaMaskFilter from '../filters/spriteMask/SpriteMaskFilter'; - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class MaskSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - // TODO - we don't need both! - this.scissor = false; - this.scissorData = null; - this.scissorRenderTarget = null; - - this.enableScissor = false; - - this.alphaMaskPool = []; - this.alphaMaskIndex = 0; - } - - /** - * Applies the Mask and adds it to the current filter stack. - * - * @param {PIXI.DisplayObject} target - Display Object to push the mask to - * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. - */ - push(target, maskData) - { - // TODO the root check means scissor rect will not - // be used on render textures more info here: - // https://github.com/pixijs/pixi.js/pull/3545 - - if (maskData.texture) - { - this.pushSpriteMask(target, maskData); - } - else if (this.enableScissor - && !this.scissor - && this.renderer._activeRenderTarget.root - && !this.renderer.stencil.stencilMaskStack.length - && maskData.isFastRect()) - { - const matrix = maskData.worldTransform; - - let rot = Math.atan2(matrix.b, matrix.a); - - // use the nearest degree! - rot = Math.round(rot * (180 / Math.PI)); - - if (rot % 90) - { - this.pushStencilMask(maskData); - } - else - { - this.pushScissorMask(target, maskData); - } - } - else - { - this.pushStencilMask(maskData); - } - } - - /** - * Removes the last mask from the mask stack and doesn't return it. - * - * @param {PIXI.DisplayObject} target - Display Object to pop the mask from - * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. - */ - pop(target, maskData) - { - if (maskData.texture) - { - this.popSpriteMask(target, maskData); - } - else if (this.enableScissor && !this.renderer.stencil.stencilMaskStack.length) - { - this.popScissorMask(target, maskData); - } - else - { - this.popStencilMask(target, maskData); - } - } - - /** - * Applies the Mask and adds it to the current filter stack. - * - * @param {PIXI.RenderTarget} target - Display Object to push the sprite mask to - * @param {PIXI.Sprite} maskData - Sprite to be used as the mask - */ - pushSpriteMask(target, maskData) - { - let alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; - - if (!alphaMaskFilter) - { - alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; - } - - alphaMaskFilter[0].resolution = this.renderer.resolution; - alphaMaskFilter[0].maskSprite = maskData; - - // TODO - may cause issues! - target.filterArea = maskData.getBounds(true); - - this.renderer.filterSystem.pushFilter(target, alphaMaskFilter); - - this.alphaMaskIndex++; - } - - /** - * Removes the last filter from the filter stack and doesn't return it. - * - */ - popSpriteMask() - { - this.renderer.filterSystem.popFilter(); - this.alphaMaskIndex--; - } - - /** - * Applies the Mask and adds it to the current filter stack. - * - * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. - */ - pushStencilMask(maskData) - { - this.renderer.batch.flush(); - this.renderer.stencil.pushStencil(maskData); - } - - /** - * Removes the last filter from the filter stack and doesn't return it. - * - */ - popStencilMask() - { - // this.renderer.currentRenderer.stop(); - this.renderer.stencil.popStencil(); - } - - /** - * - * @param {PIXI.DisplayObject} target - Display Object to push the mask to - * @param {PIXI.Graphics} maskData - The masking data. - */ - pushScissorMask(target, maskData) - { - maskData.renderable = true; - - const renderTarget = this.renderer._activeRenderTarget; - - const bounds = maskData.getBounds(); - - bounds.fit(renderTarget.size); - maskData.renderable = false; - - this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); - - const resolution = this.renderer.resolution; - - this.renderer.gl.scissor( - bounds.x * resolution, - (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, - bounds.width * resolution, - bounds.height * resolution - ); - - this.scissorRenderTarget = renderTarget; - this.scissorData = maskData; - this.scissor = true; - } - - /** - * - * - */ - popScissorMask() - { - this.scissorRenderTarget = null; - this.scissorData = null; - this.scissor = false; - - // must be scissor! - const gl = this.renderer.gl; - - gl.disable(gl.SCISSOR_TEST); - } -} diff --git a/packages/core/src/renderers/systems/ProjectionSystem.js b/packages/core/src/renderers/systems/ProjectionSystem.js deleted file mode 100644 index ec5efdb..0000000 --- a/packages/core/src/renderers/systems/ProjectionSystem.js +++ /dev/null @@ -1,73 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { Matrix } from '@pixi/math'; - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ - -export default class ProjectionSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this.projectionMatrix = new Matrix(); - } - - update(destinationFrame, sourceFrame, resolution, root) - { - this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; - this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; - - this.calculateProjection(this.destinationFrame, this.sourceFrame, resolution, root); - - this.renderer.globalUniforms.uniforms.projectionMatrix = this.projectionMatrix; - this.renderer.globalUniforms.update(); - } - - /** - * Updates the projection matrix based on a projection frame (which is a rectangle) - * - * @param {Rectangle} destinationFrame - The destination frame. - * @param {Rectangle} sourceFrame - The source frame. - */ - calculateProjection(destinationFrame, sourceFrame, resolution, root) - { - const pm = this.projectionMatrix; - - // I don't think we will need this line.. - // pm.identity(); - - if (!root) - { - pm.a = (1 / destinationFrame.width * 2) * resolution; - pm.d = (1 / destinationFrame.height * 2) * resolution; - - pm.tx = -1 - (sourceFrame.x * pm.a); - pm.ty = -1 - (sourceFrame.y * pm.d); - } - else - { - pm.a = (1 / destinationFrame.width * 2) * resolution; - pm.d = (-1 / destinationFrame.height * 2) * resolution; - - pm.tx = -1 - (sourceFrame.x * pm.a); - pm.ty = 1 - (sourceFrame.y * pm.d); - } - } - - /** - * Sets the transform of the active render target to the given matrix - * - * @param {PIXI.Matrix} matrix - The transformation matrix - */ - setTransform()// matrix) - { - // this._activeRenderTarget.transform = matrix; - } -} diff --git a/packages/core/src/renderers/systems/RenderTextureSystem.js b/packages/core/src/renderers/systems/RenderTextureSystem.js deleted file mode 100644 index a133ef1..0000000 --- a/packages/core/src/renderers/systems/RenderTextureSystem.js +++ /dev/null @@ -1,115 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { Rectangle } from '@pixi/math'; - -const tempRect = new Rectangle(); - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ - -export default class RenderTextureSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this.clearColor = renderer._backgroundColorRgba; - - // TODO moe this property somewhere else! - this.defaultMaskStack = []; - this.defaultFilterStack = [{}]; - - // empty render texture? - this.renderTexture = null; - - this.destinationFrame = new Rectangle(); - } - - bind(renderTexture, sourceFrame, destinationFrame) - { - // TODO - do we want this?? - if (this.renderTexture === renderTexture) return; - this.renderTexture = renderTexture; - - const renderer = this.renderer; - - if (renderTexture) - { - const baseTexture = renderTexture.baseTexture; - - if (!destinationFrame) - { - tempRect.width = baseTexture.realWidth; - tempRect.height = baseTexture.realHeight; - - destinationFrame = tempRect; - } - - if (!sourceFrame) - { - sourceFrame = destinationFrame; - } - - this.renderer.framebuffer.bind(baseTexture.frameBuffer, destinationFrame); - - this.renderer.projection.update(destinationFrame, sourceFrame, baseTexture.resolution, false); - this.renderer.stencil.setMaskStack(baseTexture.stencilMaskStack); - } - else - { - // TODO these validation checks happen deeper down.. - // thing they can be avoided.. - if (!destinationFrame) - { - tempRect.width = renderer.width; - tempRect.height = renderer.height; - - destinationFrame = tempRect; - } - - if (!sourceFrame) - { - sourceFrame = destinationFrame; - } - - renderer.framebuffer.bind(null, destinationFrame); - - // TODO store this.. - this.renderer.projection.update(destinationFrame, sourceFrame, this.renderer.resolution, true); - this.renderer.stencil.setMaskStack(this.defaultMaskStack); - } - - this.destinationFrame.copyFrom(destinationFrame); - } - - /** - * Erases the render texture and fills the drawing area with a colour - * - * @param {number} [clearColor] - The colour - * @return {PIXI.Renderer} Returns itself. - */ - clear(clearColor) - { - if (this.renderTexture) - { - clearColor = clearColor || this.renderTexture.baseTexture.clearColor; - } - else - { - clearColor = clearColor || this.clearColor; - } - - this.renderer.framebuffer.clear(clearColor[0], clearColor[1], clearColor[2], clearColor[3]); - } - - resize()// screenWidth, screenHeight) - { - // resize the root only! - this.bind(null); - } -} diff --git a/packages/core/src/renderers/systems/StateSystem.js b/packages/core/src/renderers/systems/StateSystem.js deleted file mode 100755 index d7f4caf..0000000 --- a/packages/core/src/renderers/systems/StateSystem.js +++ /dev/null @@ -1,288 +0,0 @@ -import mapWebGLBlendModesToPixi from '../utils/mapWebGLBlendModesToPixi'; -import WebGLSystem from './WebGLSystem'; -import WebGLState from '../State'; - -const BLEND = 0; -const OFFSET = 1; -const CULLING = 2; -const DEPTH_TEST = 3; -const WINDING = 4; - -/** - * A WebGL state machines - * - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class StateSystem extends WebGLSystem -{ - /** - * @param {WebGLRenderingContext} gl - The current WebGL rendering context - */ - constructor(renderer) - { - super(renderer); - - this.gl = null; - - this.maxAttribs = null; - - // check we have vao.. - this.nativeVaoExtension = null; - - this.attribState = null; - - this.stateId = 0; - this.polygonOffset = 0; - this.blendMode = 17; - - this.map = []; - - // map functions for when we set state.. - this.map[BLEND] = this.setBlend; - this.map[OFFSET] = this.setOffset; - this.map[CULLING] = this.setCullFace; - this.map[DEPTH_TEST] = this.setDepthTest; - this.map[WINDING] = this.setFrontFace; - - this.checks = []; - - this.defaultState = new WebGLState(); - this.defaultState.blend = true; - this.defaultState.depth = true; - } - - contextChange(gl) - { - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; - - this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); - - // check we have vao.. - this.nativeVaoExtension = ( - gl.getExtension('OES_vertex_array_object') - || gl.getExtension('MOZ_OES_vertex_array_object') - || gl.getExtension('WEBKIT_OES_vertex_array_object') - ); - - this.attribState = { - tempAttribState: new Array(this.maxAttribs), - attribState: new Array(this.maxAttribs), - }; - - this.blendModes = mapWebGLBlendModesToPixi(gl); - - this.setState(this.defaultState); - - this.reset(); - } - - /** - * Sets the current state - * - * @param {*} state - The state to set. - */ - setState(state) - { - state = state || this.defaultState; - - // TODO maybe to an object check? ( this.state === state )? - if (this.stateId !== state.data) - { - let diff = this.stateId ^ state.data; - let i = 0; - - // order from least to most common - while (diff) - { - if (diff & 1) - { - // state change! - this.map[i].call(this, !!(state.data & (1 << i))); - } - - diff = diff >> 1; - i++; - } - - this.stateId = state.data; - } - - // based on the above settings we check for specific modes.. - // for example if blend is active we check and set the blend modes - // or of polygon offset is active we check the poly depth. - for (let i = 0; i < this.checks.length; i++) - { - this.checks[i](this, state); - } - } - - /** - * Enables or disabled blending. - * - * @param {boolean} value - Turn on or off webgl blending. - */ - setBlend(value) - { - this.updateCheck(StateSystem.checkBlendMode, value); - - this.gl[value ? 'enable' : 'disable'](this.gl.BLEND); - } - - /** - * Enables or disable polygon offset fill - * - * @param {boolean} value - Turn on or off webgl polygon offset testing. - */ - setOffset(value) - { - this.gl[value ? 'enable' : 'disable'](this.gl.POLYGON_OFFSET_FILL); - } - - /** - * Sets whether to enable or disable depth test. - * - * @param {boolean} value - Turn on or off webgl depth testing. - */ - setDepthTest(value) - { - this.gl[value ? 'enable' : 'disable'](this.gl.DEPTH_TEST); - } - - /** - * Sets whether to enable or disable cull face. - * - * @param {boolean} value - Turn on or off webgl cull face. - */ - setCullFace(value) - { - this.gl[value ? 'enable' : 'disable'](this.gl.CULL_FACE); - } - - /** - * Sets the gl front face. - * - * @param {boolean} value - true is clockwise and false is counter-clockwise - */ - setFrontFace(value) - { - this.gl.frontFace(this.gl[value ? 'CW' : 'CCW']); - } - - /** - * Sets the blend mode. - * - * @param {number} value - The blend mode to set to. - */ - setBlendMode(value) - { - if (value === this.blendMode) - { - return; - } - - this.blendMode = value; - this.gl.blendFunc(this.blendModes[value][0], this.blendModes[value][1]); - } - - /** - * Sets the polygon offset. - * - * @param {number} value - the polygon offset - * @param {number} scale - the polygon offset scale - */ - setPolygonOffset(value, scale) - { - this.gl.polygonOffset(value, scale); - } - - /** - * Disables all the vaos in use - * - */ - resetAttributes() - { - for (let i = 0; i < this.attribState.tempAttribState.length; i++) - { - this.attribState.tempAttribState[i] = 0; - } - - for (let i = 0; i < this.attribState.attribState.length; i++) - { - this.attribState.attribState[i] = 0; - } - - // im going to assume one is always active for performance reasons. - for (let i = 1; i < this.maxAttribs; i++) - { - this.gl.disableVertexAttribArray(i); - } - } - - // used - /** - * Resets all the logic and disables the vaos - */ - reset() - { - // unbind any VAO if they exist.. - if (this.nativeVaoExtension) - { - this.nativeVaoExtension.bindVertexArrayOES(null); - } - - // reset all attributes.. - this.resetAttributes(); - - this.gl.pixelStorei(this.gl.UNPACK_FLIP_Y_WEBGL, false); - - this.setBlendMode(0); - - // TODO? - // this.setState(this.defaultState); - } - - /** - * checks to see which updates should be checked based on which settings have been activated - * for example if blend is enabled then we shold check the blend modes each time the state is changed - * or if poygon fill is activated then we need to check if the polygone offset changes. - * The idea is that we only check what we have too. - * - * @param {Function} func the checking function to add or remove - * @param {boolean} value should the check function be added or removed. - */ - updateCheck(func, value) - { - const index = this.checks.indexOf(func); - - if (value && index === -1) - { - this.checks.push(func); - } - else if (!value && index !== -1) - { - this.checks.splice(index, 1); - } - } - - /** - * A private little wrapper function that we call to check the blend mode. - * - * @static - * @private - * @param {PIXI.StateSystem} System the System to perform the state check on - * @param {PIXI.State} state the state that the blendMode will pulled from - */ - static checkBlendMode(system, state) - { - system.setBlendMode(state.blendMode); - } - - // TODO - add polygon offset? -} diff --git a/packages/core/src/renderers/systems/StencilSystem.js b/packages/core/src/renderers/systems/StencilSystem.js deleted file mode 100644 index 5d538cb..0000000 --- a/packages/core/src/renderers/systems/StencilSystem.js +++ /dev/null @@ -1,133 +0,0 @@ -import WebGLSystem from './WebGLSystem'; - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class StencilSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - this.stencilMaskStack = []; - } - - /** - * Changes the mask stack that is used by this System. - * - * @param {PIXI.Graphics[]} stencilMaskStack - The mask stack - */ - setMaskStack(stencilMaskStack) - { - const gl = this.renderer.gl; - - if (stencilMaskStack.length !== this.stencilMaskStack.length) - { - if (stencilMaskStack.length === 0) - { - gl.disable(gl.STENCIL_TEST); - } - else - { - gl.enable(gl.STENCIL_TEST); - } - } - - this.stencilMaskStack = stencilMaskStack; - } - - /** - * Applies the Mask and adds it to the current stencil stack. @alvin - * - * @param {PIXI.Graphics} graphics - The mask - */ - pushStencil(graphics) - { - this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); - - // this.renderer._activeRenderTarget.attachStencilBuffer(); - - const gl = this.renderer.gl; - const prevMaskCount = this.stencilMaskStack.length; - - if (prevMaskCount === 0) - { - gl.enable(gl.STENCIL_TEST); - } - - this.stencilMaskStack.push(graphics); - - // Increment the refference stencil value where the new mask overlaps with the old ones. - gl.colorMask(false, false, false, false); - gl.stencilFunc(gl.EQUAL, prevMaskCount, this._getBitwiseMask()); - gl.stencilOp(gl.KEEP, gl.KEEP, gl.INCR); - this.renderer.plugins.graphics.render(graphics); - - this._useCurrent(); - } - - /** - * Removes the last mask from the stencil stack. @alvin - */ - popStencil() - { - this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); - - const gl = this.renderer.gl; - const graphics = this.stencilMaskStack.pop(); - - if (this.stencilMaskStack.length === 0) - { - // the stack is empty! - gl.disable(gl.STENCIL_TEST); - gl.clear(gl.STENCIL_BUFFER_BIT); - gl.clearStencil(0); - } - else - { - // Decrement the refference stencil value where the popped mask overlaps with the other ones - gl.colorMask(false, false, false, false); - gl.stencilOp(gl.KEEP, gl.KEEP, gl.DECR); - this.renderer.plugins.graphics.render(graphics); - - this._useCurrent(); - } - } - - /** - * Setup renderer to use the current stencil data. - */ - _useCurrent() - { - const gl = this.renderer.gl; - - gl.colorMask(true, true, true, true); - gl.stencilFunc(gl.EQUAL, this.stencilMaskStack.length, this._getBitwiseMask()); - gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP); - } - - /** - * Fill 1s equal to the number of acitve stencil masks. - * - * @return {number} The bitwise mask. - */ - _getBitwiseMask() - { - return (1 << this.stencilMaskStack.length) - 1; - } - - /** - * Destroys the mask stack. - * - */ - destroy() - { - super.destroy(this); - - this.stencilMaskStack = null; - } -} diff --git a/packages/core/src/renderers/systems/WebGLSystem.js b/packages/core/src/renderers/systems/WebGLSystem.js deleted file mode 100644 index d3d5b31..0000000 --- a/packages/core/src/renderers/systems/WebGLSystem.js +++ /dev/null @@ -1,41 +0,0 @@ -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this manager works for. - */ - constructor(renderer) - { - /** - * The renderer this manager works for. - * - * @member {PIXI.Renderer} - */ - this.renderer = renderer; - - this.renderer.runners.contextChange.add(this); - } - - /** - * Generic method called when there is a WebGL context change. - * - */ - contextChange() - { - // do some codes init! - } - - /** - * Generic destroy methods to be overridden by the subclass - * - */ - destroy() - { - this.renderer.runners.contextChange.remove(this); - this.renderer = null; - } -} diff --git a/packages/core/src/renderers/systems/filter/FilterSystem.js b/packages/core/src/renderers/systems/filter/FilterSystem.js deleted file mode 100644 index e615037..0000000 --- a/packages/core/src/renderers/systems/filter/FilterSystem.js +++ /dev/null @@ -1,431 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; - -import RenderTexture from '../../../textures/RenderTexture'; -import Quad from '../../utils/Quad'; -import QuadUv from '../../utils/QuadUv'; -import { Rectangle } from '@pixi/math'; -import * as filterTransforms from '../../filters/filterTransforms'; -import bitTwiddle from 'bit-twiddle'; -import UniformGroup from '../../../shader/UniformGroup'; -import { DRAW_MODES } from '../../../../../constants'; - -// -/** - * @ignore - * @class - */ -class FilterState -{ - /** - * - */ - constructor() - { - this.renderTexture = null; - this.sourceFrame = new Rectangle(); - this.destinationFrame = new Rectangle(); - this.filters = []; - this.target = null; - this.legacy = false; - this.resolution = 1; - } -} - -/** - * @class - * @memberof PIXI.systems - * @extends PIXI.systems.WebGLSystem - */ -export default class FilterSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - /** - * stores a bunch of PO2 textures used for filtering - * @type {Object} - */ - this.texturePool = {}; - - /** - * a pool for storing filter states, save us creating new ones each tick - * @type {Array} - */ - this.statePool = []; - - /** - * A very simple geometry used when drawing a filter effect to the screen - * @type {Quad} - */ - this.quad = new Quad(); - - this.quadUv = new QuadUv(); - - /** - * Temporary rect for maths - * @type {PIXI.Rectangle} - */ - this.tempRect = new Rectangle(); - - this.activeState = {}; - - /** - * this uniform group is attached to filter uniforms when used - * @type {UniformGroup} - */ - this.globalUniforms = new UniformGroup({ - sourceFrame: this.tempRect, - destinationFrame: this.tempRect, - - // legacy variables - filterArea: new Float32Array(4), - filterClamp: new Float32Array(4), - }, true); - } - - /** - * Adds a new filter to the System. - * - * @param {PIXI.DisplayObject} target - The target of the filter to render. - * @param {PIXI.Filter[]} filters - The filters to apply. - */ - push(target, filters) - { - const renderer = this.renderer; - const filterStack = this.renderer.renderTexture.defaultFilterStack; - const state = this.statePool.pop() || new FilterState(); - - let resolution = filters[0].resolution; - let padding = filters[0].padding; - let autoFit = filters[0].autoFit; - let legacy = filters[0].legacy; - - for (let i = 1; i < filters.length; i++) - { - const filter = filters[i]; - - // lets use the lowest resolution.. - resolution = Math.min(resolution, filter.resolution); - // and the largest amount of padding! - padding = Math.max(padding, filter.padding); - // only auto fit if all filters are autofit - autoFit = autoFit || filter.autoFit; - - legacy = legacy || filter.legacy; - } - - filterStack.push(state); - - state.resolution = resolution; - - state.legacy = legacy; - - // round to whole number based on resolution - // TODO move that to the shader too? - state.sourceFrame = target.filterArea || target.getBounds(true); - - state.sourceFrame.pad(padding); - - if (autoFit) - { - state.sourceFrame.fit(this.renderer.renderTexture.destinationFrame); - } - - state.sourceFrame.round(resolution); - - state.renderTexture = this.getPotFilterTexture(state.sourceFrame.width, state.sourceFrame.height, resolution); - state.filters = filters; - - state.destinationFrame.width = state.renderTexture.width; - state.destinationFrame.height = state.renderTexture.height; - - state.renderTexture.filterFrame = state.sourceFrame; - renderer.renderTexture.bind(state.renderTexture, state.sourceFrame);// /, state.destinationFrame); - renderer.renderTexture.clear(); - } - - /** - * Pops off the filter and applies it. - * - */ - pop() - { - const renderer = this.renderer; - const filterStack = renderer.renderTexture.defaultFilterStack; - const state = filterStack.pop(); - const filters = state.filters; - - this.activeState = state; - - const globalUniforms = this.globalUniforms.uniforms; - - globalUniforms.sourceFrame = state.sourceFrame; - globalUniforms.destinationFrame = state.destinationFrame; - globalUniforms.resolution = state.resolution; - - // only update the rect if its legacy.. - if (state.legacy) - { - const filterArea = globalUniforms.filterArea; - const filterClamp = globalUniforms.filterClamp; - - filterArea[0] = state.destinationFrame.width; - filterArea[1] = state.destinationFrame.height; - filterArea[2] = state.sourceFrame.x; - filterArea[3] = state.sourceFrame.y; - - filterClamp[0] = 0.5 / state.resolution / state.destinationFrame.width; - filterClamp[1] = 0.5 / state.resolution / state.destinationFrame.height; - filterClamp[2] = (state.sourceFrame.width - 0.5) / state.resolution / state.destinationFrame.width; - filterClamp[3] = (state.sourceFrame.height - 0.5) / state.resolution / state.destinationFrame.height; - } - - this.globalUniforms.update(); - - const lastState = filterStack[filterStack.length - 1]; - - if (filters.length === 1) - { - filters[0].apply(this, state.renderTexture, lastState.renderTexture, false, state); - - this.returnFilterTexture(state.renderTexture); - } - else - { - let flip = state.renderTexture; - let flop = this.getPotFilterTexture( - flip.width, - flip.height, - state.resolution - ); - - flop.filterFrame = flip.filterFrame; - - let i = 0; - - for (i = 0; i < filters.length - 1; ++i) - { - filters[i].apply(this, flip, flop, true, state); - - const t = flip; - - flip = flop; - flop = t; - } - - filters[i].apply(this, flip, lastState.renderTexture, false, state); - - this.returnFilterTexture(flip); - this.returnFilterTexture(flop); - } - - this.statePool.push(state); - } - - /** - * Draws a filter. - * - * @param {PIXI.Filter} filter - The filter to draw. - * @param {PIXI.RenderTarget} input - The input render target. - * @param {PIXI.RenderTarget} output - The target to output to. - * @param {boolean} clear - Should the output be cleared before rendering to it - */ - applyFilter(filter, input, output, clear) - { - const renderer = this.renderer; - - renderer.renderTexture.bind(output, output ? output.filterFrame : null); - - if (clear) - { - // gl.disable(gl.SCISSOR_TEST); - renderer.renderTexture.clear(); - // gl.enable(gl.SCISSOR_TEST); - } - - // set the uniforms.. - filter.uniforms.uSampler = input; - filter.uniforms.filterGlobals = this.globalUniforms; - - // TODO make it so that the order of this does not matter.. - // because it does at the moment cos of global uniforms. - // they need to get resynced - - renderer.state.setState(filter.state); - renderer.shader.bind(filter); - - if (filter.legacy) - { - this.quadUv.map(input._frame, input.filterFrame); - - renderer.geometry.bind(this.quadUv); - renderer.geometry.draw(DRAW_MODES.TRIANGLES); - } - else - { - renderer.geometry.bind(this.quad); - renderer.geometry.draw(DRAW_MODES.TRIANGLE_STRIP); - } - } - - /** - * Calculates the mapped matrix. - * - * TODO playing around here.. this is temporary - (will end up in the shader) - * this returns a matrix that will normalise map filter cords in the filter to screen space - * - * @param {PIXI.Matrix} outputMatrix - the matrix to output to. - * @return {PIXI.Matrix} The mapped matrix. - */ - calculateScreenSpaceMatrix(outputMatrix) - { - const currentState = this.activeState; - - return filterTransforms.calculateScreenSpaceMatrix( - outputMatrix, - currentState.sourceFrame, - currentState.destinationFrame - ); - } - - /** - * This will map the filter coord so that a texture can be used based on the transform of a sprite - * - * @param {PIXI.Matrix} outputMatrix - The matrix to output to. - * @param {PIXI.Sprite} sprite - The sprite to map to. - * @return {PIXI.Matrix} The mapped matrix. - */ - calculateSpriteMatrix(outputMatrix, sprite) - { - const currentState = this.activeState; - - return filterTransforms.calculateSpriteMatrix( - outputMatrix, - currentState.sourceFrame, - currentState.destinationFrame, - sprite - ); - } - - /** - * Destroys this Filter System. - * - * @param {boolean} [contextLost=false] context was lost, do not free shaders - * - */ - destroy(contextLost = false) - { - if (!contextLost) - { - this.emptyPool(); - } - else - { - this.texturePool = {}; - } - } - - /** - * Gets a Power-of-Two render texture. - * - * TODO move to a seperate class could be on renderer? - * also - could cause issue with multiple contexts? - * - * @private - * @param {WebGLRenderingContext} gl - The webgl rendering context - * @param {number} minWidth - The minimum width of the render target. - * @param {number} minHeight - The minimum height of the render target. - * @param {number} resolution - The resolution of the render target. - * @return {PIXI.RenderTarget} The new render target. - */ - getPotFilterTexture(minWidth, minHeight, resolution) - { - minWidth = bitTwiddle.nextPow2(minWidth); - minHeight = bitTwiddle.nextPow2(minHeight); - resolution = resolution || 1; - - const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); - - if (!this.texturePool[key]) - { - this.texturePool[key] = []; - } - - let renderTexture = this.texturePool[key].pop(); - - if (!renderTexture) - { - // temporary bypass cache.. - // internally - this will cause a texture to be bound.. - renderTexture = RenderTexture.create({ - width: minWidth, - height: minHeight, - resolution, - }); - } - - return renderTexture; - } - - /** - * Gets extra render texture to use inside current filter - * - * @param {number} resolution resolution of the renderTexture - * @returns {PIXI.RenderTexture} - */ - getFilterTexture(resolution) - { - const rt = this.activeState.renderTexture; - - const filterTexture = this.getPotFilterTexture(rt.width, rt.height, resolution || rt.baseTexture.resolution); - - filterTexture.filterFrame = rt.filterFrame; - - return filterTexture; - } - - /** - * Frees a render texture back into the pool. - * - * @param {PIXI.RenderTarget} renderTexture - The renderTarget to free - */ - returnFilterTexture(renderTexture) - { - renderTexture.filterFrame = null; - - const base = renderTexture.baseTexture; - - const minWidth = base.width; - const minHeight = base.height; - - const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); - - this.texturePool[key].push(renderTexture); - } - - /** - * Empties the texture pool. - * - */ - emptyPool() - { - for (const i in this.texturePool) - { - const textures = this.texturePool[i]; - - if (textures) - { - for (let j = 0; j < textures.length; j++) - { - textures[j].destroy(true); - } - } - } - - this.texturePool = {}; - } -} diff --git a/packages/core/src/renderers/systems/geometry/GLBuffer.js b/packages/core/src/renderers/systems/geometry/GLBuffer.js deleted file mode 100644 index 0477e8a..0000000 --- a/packages/core/src/renderers/systems/geometry/GLBuffer.js +++ /dev/null @@ -1,9 +0,0 @@ -export default class GLBuffer -{ - constructor(buffer) - { - this.buffer = buffer; - this.updateID = -1; - this.byteLength = -1; - } -} diff --git a/packages/core/src/renderers/systems/geometry/GeometrySystem.js b/packages/core/src/renderers/systems/geometry/GeometrySystem.js deleted file mode 100644 index 26c8e9e..0000000 --- a/packages/core/src/renderers/systems/geometry/GeometrySystem.js +++ /dev/null @@ -1,388 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; -import GLBuffer from './GLBuffer'; - -const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class GeometrySystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this._activeGeometry = null; - this._activeVao = null; - - this.hasVao = true; - this.hasInstance = true; - } - - /** - * Sets up the renderer context and necessary buffers. - * - * @private - */ - contextChange() - { - const gl = this.gl = this.renderer.gl; - - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - - // webgl2 - if (!gl.createVertexArray) - { - // webgl 1! - let nativeVaoExtension = this.renderer.context.extensions.vertexArrayObject; - - if (this.renderer.legacy) - { - nativeVaoExtension = null; - } - - if (nativeVaoExtension) - { - gl.createVertexArray = () => - nativeVaoExtension.createVertexArrayOES(); - - gl.bindVertexArray = (vao) => - nativeVaoExtension.bindVertexArrayOES(vao); - - gl.deleteVertexArray = (vao) => - nativeVaoExtension.deleteVertexArrayOES(vao); - } - else - { - this.hasVao = false; - gl.createVertexArray = () => - { - // empty - }; - - gl.bindVertexArray = () => - { - // empty - }; - - gl.deleteVertexArray = () => - { - // empty - }; - } - } - - if (!gl.vertexAttribDivisor) - { - const instanceExt = gl.getExtension('ANGLE_instanced_arrays'); - - if (instanceExt) - { - gl.vertexAttribDivisor = (a, b) => - instanceExt.vertexAttribDivisorANGLE(a, b); - - gl.drawElementsInstanced = (a, b, c, d, e) => - instanceExt.drawElementsInstancedANGLE(a, b, c, d, e); - - gl.drawArraysInstanced = (a, b, c, d) => - instanceExt.drawArraysInstancedANGLE(a, b, c, d); - } - else - { - this.hasInstance = false; - } - } - } - - /** - * Binds geometry so that is can be drawn. Creating a Vao if required - * @private - * @param {PIXI.Geometry} geometry instance of geometry to bind - */ - bind(geometry, shader) - { - shader = shader || this.renderer.shader.shader; - - const gl = this.gl; - - // not sure the best way to address this.. - // currently different shaders require different VAOs for the same geometry - // Still mulling over the best way to solve this one.. - // will likely need to modify the shader attribute locations at run time! - let vaos = geometry.glVertexArrayObjects[this.CONTEXT_UID]; - - if (!vaos) - { - geometry.glVertexArrayObjects[this.CONTEXT_UID] = vaos = {}; - } - - const vao = vaos[shader.program.id] || this.initGeometryVao(geometry, shader.program); - - this._activeGeometry = geometry; - - if (this._activeVao !== vao) - { - this._activeVao = vao; - - if (this.hasVao) - { - gl.bindVertexArray(vao); - } - else - { - this.activateVao(geometry, shader.program); - } - } - - // TODO - optimise later! - // don't need to loop through if nothing changed! - // maybe look to add an 'autoupdate' to geometry? - this.updateBuffers(); - } - - reset() - { - this.unbind(); - } - - updateBuffers() - { - const geometry = this._activeGeometry; - const gl = this.gl; - - for (let i = 0; i < geometry.buffers.length; i++) - { - const buffer = geometry.buffers[i]; - - const glBuffer = buffer._glBuffers[this.CONTEXT_UID]; - - if (buffer._updateID !== glBuffer.updateID) - { - glBuffer.updateID = buffer._updateID; - - // TODO can cache this on buffer! maybe added a getter / setter? - const type = buffer.index ? gl.ELEMENT_ARRAY_BUFFER : gl.ARRAY_BUFFER; - const drawType = buffer.static ? gl.STATIC_DRAW : gl.DYNAMIC_DRAW; - - gl.bindBuffer(type, glBuffer.buffer); - - if (glBuffer.byteLength >= buffer.data.byteLength) - { - // offset is always zero for now! - gl.bufferSubData(type, 0, buffer.data); - } - else - { - gl.bufferData(type, buffer.data, drawType); - } - } - } - } - - checkCompatability(geometry, program) - { - // geometry must have at least all the attributes that the shader requires. - const geometryAttributes = geometry.attributes; - const shaderAttributes = program.attributeData; - - for (const j in shaderAttributes) - { - if (!geometryAttributes[j]) - { - throw new Error(`shader and geometry incompatible, geometry missing the "${j}" attribute`); - } - } - } - - /** - * Creates a Vao with the same structure as the geometry and stores it on the geometry. - * @private - * @param {PIXI.Geometry} geometry instance of geometry to to generate Vao for - */ - initGeometryVao(geometry, program) - { - this.checkCompatability(geometry, program); - - const gl = this.gl; - const CONTEXT_UID = this.CONTEXT_UID; - const buffers = geometry.buffers; - const attributes = geometry.attributes; - - const tempStride = {}; - const tempStart = {}; - - for (const j in buffers) - { - tempStride[j] = 0; - tempStart[j] = 0; - } - - for (const j in attributes) - { - if (!attributes[j].size && program.attributeData[j]) - { - attributes[j].size = program.attributeData[j].size; - } - - tempStride[attributes[j].buffer] += attributes[j].size * byteSizeMap[attributes[j].type]; - } - - for (const j in attributes) - { - const attribute = attributes[j]; - const attribSize = attribute.size; - - if (attribute.stride === undefined) - { - if (tempStride[attribute.buffer] === attribSize * byteSizeMap[attribute.type]) - { - attribute.stride = 0; - } - else - { - attribute.stride = tempStride[attribute.buffer]; - } - } - - if (attribute.start === undefined) - { - attribute.start = tempStart[attribute.buffer]; - - tempStart[attribute.buffer] += attribSize * byteSizeMap[attribute.type]; - } - } - - // first update - and create the buffers! - // only create a gl buffer if it actually gets - for (let i = 0; i < buffers.length; i++) - { - const buffer = buffers[i]; - - if (!buffer._glBuffers[CONTEXT_UID]) - { - buffer._glBuffers[CONTEXT_UID] = new GLBuffer(gl.createBuffer()); - } - } - - // TODO - maybe make this a data object? - // lets wait to see if we need to first! - const vao = gl.createVertexArray(); - - gl.bindVertexArray(vao); - - this.activateVao(geometry, program); - - gl.bindVertexArray(null); - - // add it to the cache! - geometry.glVertexArrayObjects[this.CONTEXT_UID][program.id] = vao; - - return vao; - } - - activateVao(geometry, program) - { - const gl = this.gl; - const CONTEXT_UID = this.CONTEXT_UID; - const buffers = geometry.buffers; - const attributes = geometry.attributes; - - if (geometry.indexBuffer) - { - // first update the index buffer if we have one.. - gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, geometry.indexBuffer._glBuffers[CONTEXT_UID].buffer); - } - - let lastBuffer = null; - - // add a new one! - for (const j in attributes) - { - const attribute = attributes[j]; - const buffer = buffers[attribute.buffer]; - const glBuffer = buffer._glBuffers[CONTEXT_UID]; - - if (program.attributeData[j]) - { - if (lastBuffer !== glBuffer) - { - gl.bindBuffer(gl.ARRAY_BUFFER, glBuffer.buffer); - - lastBuffer = glBuffer; - } - - const location = program.attributeData[j].location; - - // TODO introduce state again - // we can optimise this for older devices that have no VAOs - gl.enableVertexAttribArray(location); - - gl.vertexAttribPointer(location, - attribute.size, - attribute.type || gl.FLOAT, - attribute.normalized, - attribute.stride, - attribute.start); - - if (attribute.instance) - { - // TODO calculate instance count based of this... - if (this.hasInstance) - { - gl.vertexAttribDivisor(location, 1); - } - else - { - throw new Error('geometry error, GPU Instancing is not supported on this device'); - } - } - } - } - } - - draw(type, size, start, instanceCount) - { - const gl = this.gl; - const geometry = this._activeGeometry; - - // TODO.. this should not change so maybe cache the function? - - if (geometry.indexBuffer) - { - if (geometry.instanced) - { - /* eslint-disable max-len */ - gl.drawElementsInstanced(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2, instanceCount || 1); - /* eslint-enable max-len */ - } - else - { - gl.drawElements(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2); - } - } - else - if (geometry.instanced) - { - // TODO need a better way to calculate size.. - gl.drawArraysInstanced(type, start, size || geometry.getSize(), instanceCount || 1); - } - else - { - gl.drawArrays(type, start, size || geometry.getSize()); - } - - return this; - } - - unbind() - { - this.gl.bindVertexArray(null); - this._activeVao = null; - this._activeGeometry = null; - } -} diff --git a/packages/core/src/renderers/systems/geometry/setVertexAttribArrays.js b/packages/core/src/renderers/systems/geometry/setVertexAttribArrays.js deleted file mode 100644 index 23b6959..0000000 --- a/packages/core/src/renderers/systems/geometry/setVertexAttribArrays.js +++ /dev/null @@ -1,54 +0,0 @@ -// var GL_MAP = {}; - -/** - * @param gl {WebGLRenderingContext} The current WebGL context - * @param attribs {*} - * @param state {*} - */ -export default function setVertexAttribArrays(gl, attribs, state) -{ - let i; - - if (state) - { - const tempAttribState = state.tempAttribState; - const attribState = state.attribState; - - for (i = 0; i < tempAttribState.length; i++) - { - tempAttribState[i] = false; - } - - // set the new attribs - for (i = 0; i < attribs.length; i++) - { - tempAttribState[attribs[i].attribute.location] = true; - } - - for (i = 0; i < attribState.length; i++) - { - if (attribState[i] !== tempAttribState[i]) - { - attribState[i] = tempAttribState[i]; - - if (state.attribState[i]) - { - gl.enableVertexAttribArray(i); - } - else - { - gl.disableVertexAttribArray(i); - } - } - } - } - else - { - for (i = 0; i < attribs.length; i++) - { - const attrib = attribs[i]; - - gl.enableVertexAttribArray(attrib.attribute.location); - } - } -} diff --git a/packages/core/src/renderers/systems/index.js b/packages/core/src/renderers/systems/index.js deleted file mode 100644 index c843f03..0000000 --- a/packages/core/src/renderers/systems/index.js +++ /dev/null @@ -1,6 +0,0 @@ -/** - * Systems are individual components to the Renderer pipeline. - * @namespace PIXI.systems - */ -export { default as WebGLSystem } from './WebGLSystem'; -export { default as FilterSystem } from './filter/FilterSystem'; diff --git a/packages/core/src/renderers/systems/shader/GLProgram.js b/packages/core/src/renderers/systems/shader/GLProgram.js deleted file mode 100644 index 92980ca..0000000 --- a/packages/core/src/renderers/systems/shader/GLProgram.js +++ /dev/null @@ -1,43 +0,0 @@ -/** - * Helper class to create a webGL Program - * - * @class - * @private - */ -export default class GLProgram -{ - constructor(program, uniformData) - { - /** - * The shader program - * - * @member {WebGLProgram} - */ - this.program = program; - - /** - * holds the uniform data which contains uniform locations - * and current uniform values used for caching and preventing undeed GPU commands - * @type {Object} - */ - this.uniformData = uniformData; - - /** - * uniformGroups holds the various upload functions for the shader. Each uniform group - * and program have a unique upload function generated. - * @type {Object} - */ - this.uniformGroups = {}; - } - - /** - * Destroys this program - * TODO - */ - destroy() - { - this.uniformData = null; - this.uniformGroups = null; - this.program = null; - } -} diff --git a/packages/core/src/renderers/systems/shader/ShaderSystem.js b/packages/core/src/renderers/systems/shader/ShaderSystem.js deleted file mode 100644 index 6cf57b6..0000000 --- a/packages/core/src/renderers/systems/shader/ShaderSystem.js +++ /dev/null @@ -1,170 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; -import GLProgram from './GLProgram'; -import generateUniformsSync from '../../../shader/generateUniformsSync'; -import defaultValue from '../../../shader/defaultValue'; -import compileProgram from './shader/compileProgram'; - -let UID = 0; - -/** - * Helper class to create a webGL Texture - * - * @class - * @memberof PIXI - */ -export default class ShaderSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - A reference to the current renderer - */ - constructor(renderer) - { - super(renderer); - - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = null; - - this.shader = null; - this.program = null; - - this.id = UID++; - } - - contextChange(gl) - { - this.gl = gl; - } - - /** - * Changes the current shader to the one given in parameter - * - * @param {PIXI.Shader} shader - the new shader - * @param {boolean} dontSync - false if the shader should automatically sync its uniforms. - * @returns {PIXI.glCore.glProgram} the glProgram that belongs to the shader. - */ - bind(shader, dontSync) - { - shader.uniforms.globals = this.renderer.globalUniforms; - - const program = shader.program; - const glProgram = program.glPrograms[this.renderer.CONTEXT_UID] || this.generateShader(shader); - - this.shader = shader; - - // TODO - some current pixi plugins bypass this.. so it not safe to use yet.. - if (this.program !== program) - { - this.program = program; - this.gl.useProgram(glProgram.program); - } - - if (!dontSync) - { - this.syncUniformGroup(shader.uniformGroup); - } - - return glProgram; - } - - /** - * Uploads the uniforms values to the currently bound shader. - * - * @param {object} uniforms - the uniforms valiues that be applied to the current shader - */ - setUniforms(uniforms) - { - const shader = this.shader.program; - const glProgram = shader.glPrograms[this.renderer.CONTEXT_UID]; - - shader.syncUniforms(glProgram.uniformData, uniforms, this.renderer); - } - - syncUniformGroup(group) - { - const glProgram = this.getglProgram(); - - if (!group.static || group.dirtyId !== glProgram.uniformGroups[group.id]) - { - glProgram.uniformGroups[group.id] = group.dirtyId; - const syncFunc = group.syncUniforms[this.shader.program.id] || this.createSyncGroups(group); - - syncFunc(glProgram.uniformData, group.uniforms, this.renderer); - } - } - - createSyncGroups(group) - { - group.syncUniforms[this.shader.program.id] = generateUniformsSync(group, this.shader.program.uniformData); - - return group.syncUniforms[this.shader.program.id]; - } - - /** - * Returns the underlying GLShade rof the currently bound shader. - * This can be handy for when you to have a little more control over the setting of your uniforms. - * - * @return {PIXI.glCore.Shader} the glProgram for the currently bound Shader for this context - */ - getglProgram() - { - if (this.shader) - { - return this.shader.program.glPrograms[this.renderer.CONTEXT_UID]; - } - - return null; - } - - /** - * Generates a glProgram verion of the Shader provided. - * - * @private - * @param {PIXI.Shader} shader the shader that the glProgram will be based on. - * @return {PIXI.glCore.glProgram} A shiney new glProgram - */ - generateShader(shader) - { - const gl = this.gl; - - const program = shader.program; - - const attribMap = {}; - - for (const i in program.attributeData) - { - attribMap[i] = program.attributeData[i].location; - } - - const shaderProgram = compileProgram(gl, program.vertexSrc, program.fragmentSrc, attribMap); - const uniformData = {}; - - for (const i in program.uniformData) - { - const data = program.uniformData[i]; - - uniformData[i] = { - location: gl.getUniformLocation(shaderProgram, i), - value: defaultValue(data.type, data.size), - }; - } - - const glProgram = new GLProgram(shaderProgram, uniformData); - - program.glPrograms[this.renderer.CONTEXT_UID] = glProgram; - - return glProgram; - } - - /** - * Destroys this System and removes all its textures - */ - destroy() - { - // TODO implement destroy method for ShaderSystem - this.destroyed = true; - } -} diff --git a/packages/core/src/renderers/systems/shader/shader/compileProgram.js b/packages/core/src/renderers/systems/shader/shader/compileProgram.js deleted file mode 100644 index 8c6ccb9..0000000 --- a/packages/core/src/renderers/systems/shader/shader/compileProgram.js +++ /dev/null @@ -1,78 +0,0 @@ -/** - * @method compileProgram - * @memberof PIXI.glCore.shader - * @param gl {WebGLRenderingContext} The current WebGL context {WebGLProgram} - * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. - * @param fragmentSrc {string|string[]} The fragment shader source as an array of strings. - * @param attributeLocations {Object} An attribute location map that lets you manually set the attribute locations - * @return {WebGLProgram} the shader program - */ -export default function compileProgram(gl, vertexSrc, fragmentSrc, attributeLocations) -{ - const glVertShader = compileShader(gl, gl.VERTEX_SHADER, vertexSrc); - const glFragShader = compileShader(gl, gl.FRAGMENT_SHADER, fragmentSrc); - - let program = gl.createProgram(); - - gl.attachShader(program, glVertShader); - gl.attachShader(program, glFragShader); - - // optionally, set the attributes manually for the program rather than letting WebGL decide.. - if (attributeLocations) - { - for (const i in attributeLocations) - { - gl.bindAttribLocation(program, attributeLocations[i], i); - } - } - - gl.linkProgram(program); - - // if linking fails, then log and cleanup - if (!gl.getProgramParameter(program, gl.LINK_STATUS)) - { - console.error('Pixi.js Error: Could not initialize shader.'); - console.error('gl.VALIDATE_STATUS', gl.getProgramParameter(program, gl.VALIDATE_STATUS)); - console.error('gl.getError()', gl.getError()); - - // if there is a program info log, log it - if (gl.getProgramInfoLog(program) !== '') - { - console.warn('Pixi.js Warning: gl.getProgramInfoLog()', gl.getProgramInfoLog(program)); - } - - gl.deleteProgram(program); - program = null; - } - - // clean up some shaders - gl.deleteShader(glVertShader); - gl.deleteShader(glFragShader); - - return program; -} - -/** - * @private - * @param gl {WebGLRenderingContext} The current WebGL context {WebGLProgram} - * @param type {Number} the type, can be either VERTEX_SHADER or FRAGMENT_SHADER - * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. - * @return {WebGLShader} the shader - */ -function compileShader(gl, type, src) -{ - const shader = gl.createShader(type); - - gl.shaderSource(shader, src); - gl.compileShader(shader); - - if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) - { - console.warn(src); - console.error(gl.getShaderInfoLog(shader)); - - return null; - } - - return shader; -} diff --git a/packages/core/src/renderers/systems/shader/shader/defaultValue.js b/packages/core/src/renderers/systems/shader/shader/defaultValue.js deleted file mode 100644 index 97290bd..0000000 --- a/packages/core/src/renderers/systems/shader/shader/defaultValue.js +++ /dev/null @@ -1,79 +0,0 @@ -/** - * @method defaultValue - * @memberof PIXI.glCore.shader - * @param type {String} Type of value - * @param size {Number} - */ -export default function defaultValue(type, size) -{ - switch (type) - { - case 'float': - return 0; - - case 'vec2': - return new Float32Array(2 * size); - - case 'vec3': - return new Float32Array(3 * size); - - case 'vec4': - return new Float32Array(4 * size); - - case 'int': - case 'sampler2D': - case 'sampler2DArray': - return 0; - - case 'ivec2': - return new Int32Array(2 * size); - - case 'ivec3': - return new Int32Array(3 * size); - - case 'ivec4': - return new Int32Array(4 * size); - - case 'bool': - return false; - - case 'bvec2': - - return booleanArray(2 * size); - - case 'bvec3': - return booleanArray(3 * size); - - case 'bvec4': - return booleanArray(4 * size); - - case 'mat2': - return new Float32Array([1, 0, - 0, 1]); - - case 'mat3': - return new Float32Array([1, 0, 0, - 0, 1, 0, - 0, 0, 1]); - - case 'mat4': - return new Float32Array([1, 0, 0, 0, - 0, 1, 0, 0, - 0, 0, 1, 0, - 0, 0, 0, 1]); - } - - return null; -} - -function booleanArray(size) -{ - const array = new Array(size); - - for (let i = 0; i < array.length; i++) - { - array[i] = false; - } - - return array; -} diff --git a/packages/core/src/renderers/systems/shader/shader/index.js b/packages/core/src/renderers/systems/shader/shader/index.js deleted file mode 100644 index ba7a317..0000000 --- a/packages/core/src/renderers/systems/shader/shader/index.js +++ /dev/null @@ -1,8 +0,0 @@ -/** - * @namespace PIXI.glCore.shader - */ -export { default as compileProgram } from './compileProgram'; -export { default as defaultValue } from './defaultValue'; -export { default as setPrecision } from './setPrecision'; -export { default as mapSize } from './mapSize'; -export { default as mapType } from './mapType'; diff --git a/packages/core/src/renderers/systems/shader/shader/mapSize.js b/packages/core/src/renderers/systems/shader/shader/mapSize.js deleted file mode 100644 index b681554..0000000 --- a/packages/core/src/renderers/systems/shader/shader/mapSize.js +++ /dev/null @@ -1,33 +0,0 @@ -const GLSL_TO_SIZE = { - float: 1, - vec2: 2, - vec3: 3, - vec4: 4, - - int: 1, - ivec2: 2, - ivec3: 3, - ivec4: 4, - - bool: 1, - bvec2: 2, - bvec3: 3, - bvec4: 4, - - mat2: 4, - mat3: 9, - mat4: 16, - - sampler2D: 1, -}; - -/** - * @method mapSize - * @memberof PIXI.glCore.shader - * @param type {String} - * @return {Number} - */ -export default function mapSize(type) -{ - return GLSL_TO_SIZE[type]; -} diff --git a/packages/core/src/renderers/systems/shader/shader/mapType.js b/packages/core/src/renderers/systems/shader/shader/mapType.js deleted file mode 100644 index d288b7c..0000000 --- a/packages/core/src/renderers/systems/shader/shader/mapType.js +++ /dev/null @@ -1,45 +0,0 @@ -let GL_TABLE = null; - -const GL_TO_GLSL_TYPES = { - FLOAT: 'float', - FLOAT_VEC2: 'vec2', - FLOAT_VEC3: 'vec3', - FLOAT_VEC4: 'vec4', - - INT: 'int', - INT_VEC2: 'ivec2', - INT_VEC3: 'ivec3', - INT_VEC4: 'ivec4', - - BOOL: 'bool', - BOOL_VEC2: 'bvec2', - BOOL_VEC3: 'bvec3', - BOOL_VEC4: 'bvec4', - - FLOAT_MAT2: 'mat2', - FLOAT_MAT3: 'mat3', - FLOAT_MAT4: 'mat4', - - SAMPLER_2D: 'sampler2D', - SAMPLER_CUBE: 'samplerCube', - SAMPLER_2D_ARRAY: 'sampler2DArray', -}; - -export default function mapType(gl, type) -{ - if (!GL_TABLE) - { - const typeNames = Object.keys(GL_TO_GLSL_TYPES); - - GL_TABLE = {}; - - for (let i = 0; i < typeNames.length; ++i) - { - const tn = typeNames[i]; - - GL_TABLE[gl[tn]] = GL_TO_GLSL_TYPES[tn]; - } - } - - return GL_TABLE[type]; -} diff --git a/packages/core/src/renderers/systems/shader/shader/setPrecision.js b/packages/core/src/renderers/systems/shader/shader/setPrecision.js deleted file mode 100644 index e2c1f34..0000000 --- a/packages/core/src/renderers/systems/shader/shader/setPrecision.js +++ /dev/null @@ -1,18 +0,0 @@ -/** - * Sets the float precision on the shader. If the precision is already present this function will do nothing - * @param {string} src the shader source - * @param {string} precision The float precision of the shader. Options are 'lowp', 'mediump' or 'highp'. - * - * @return {string} modified shader source - */ -export default function setPrecision(src, precision) -{ - src = src.trim(); - - if (src.substring(0, 9) !== 'precision' && src.substring(0, 1) !== '#') - { - return `precision ${precision} float;\n${src}`; - } - - return src; -} diff --git a/packages/core/src/renderers/systems/textures/GLTexture.js b/packages/core/src/renderers/systems/textures/GLTexture.js deleted file mode 100644 index c3fb8dc..0000000 --- a/packages/core/src/renderers/systems/textures/GLTexture.js +++ /dev/null @@ -1,33 +0,0 @@ -export default class GLTexture -{ - constructor(texture) - { - /** - * The WebGL texture - * - * @member {WebGLTexture} - */ - this.texture = texture; - - this.width = -1; - this.height = -1; - - /** - * Texture contents dirty flag - * @member {number} - */ - this.dirtyId = -1; - - /** - * Texture style dirty flag - * @type {number} - */ - this.dirtyStyleId = -1; - - /** - * Whether mip levels has to be generated - * @type {boolean} - */ - this.mipmap = false; - } -} diff --git a/packages/core/src/renderers/systems/textures/TextureGCSystem.js b/packages/core/src/renderers/systems/textures/TextureGCSystem.js deleted file mode 100644 index b089938..0000000 --- a/packages/core/src/renderers/systems/textures/TextureGCSystem.js +++ /dev/null @@ -1,110 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; -import { GC_MODES } from '@pixi/constants'; -import { settings } from '@pixi/settings'; - -/** - * TextureGarbageCollector. This class manages the GPU and ensures that it does not get clogged - * up with textures that are no longer being used. - * - * @class - * @memberof PIXI - */ -export default class TextureGCSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this.count = 0; - this.checkCount = 0; - this.maxIdle = settings.GC_MAX_IDLE; - this.checkCountMax = settings.GC_MAX_CHECK_COUNT; - this.mode = settings.GC_MODE; - } - - /** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ - postrender() - { - this.count++; - - if (this.mode === GC_MODES.MANUAL) - { - return; - } - - this.checkCount++; - - if (this.checkCount > this.checkCountMax) - { - this.checkCount = 0; - - this.run(); - } - } - - /** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ - run() - { - const tm = this.renderer.texture; - const managedTextures = tm.managedTextures; - let wasRemoved = false; - - for (let i = 0; i < managedTextures.length; i++) - { - const texture = managedTextures[i]; - - // only supports non generated textures at the moment! - if (!texture.frameBuffer && this.count - texture.touched > this.maxIdle) - { - tm.destroyTexture(texture, true); - managedTextures[i] = null; - wasRemoved = true; - } - } - - if (wasRemoved) - { - let j = 0; - - for (let i = 0; i < managedTextures.length; i++) - { - if (managedTextures[i] !== null) - { - managedTextures[j++] = managedTextures[i]; - } - } - - managedTextures.length = j; - } - } - - /** - * Removes all the textures within the specified displayObject and its children from the GPU - * - * @param {PIXI.DisplayObject} displayObject - the displayObject to remove the textures from. - */ - unload(displayObject) - { - const tm = this.renderer.textureSystem; - - // only destroy non generated textures - if (displayObject._texture && displayObject._texture._glRenderTargets) - { - tm.destroyTexture(displayObject._texture); - } - - for (let i = displayObject.children.length - 1; i >= 0; i--) - { - this.unload(displayObject.children[i]); - } - } -} diff --git a/packages/core/src/renderers/systems/textures/TextureSystem.js b/packages/core/src/renderers/systems/textures/TextureSystem.js deleted file mode 100644 index ccc5de9..0000000 --- a/packages/core/src/renderers/systems/textures/TextureSystem.js +++ /dev/null @@ -1,281 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; -import GLTexture from './GLTexture'; -import { removeItems } from '@pixi/utils'; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class TextureSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - // TODO set to max textures... - this.boundTextures = [ - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - ]; - - this.currentLocation = -1; - - this.managedTextures = []; - } - - /** - * Sets up the renderer context and necessary buffers. - * - * @private - */ - contextChange() - { - const gl = this.gl = this.renderer.gl; - - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - - // TODO move this.. to a nice make empty textures class.. - this.emptyTextures = {}; - - const emptyTexture2D = new GLTexture(gl.createTexture()); - - gl.bindTexture(gl.TEXTURE_2D, emptyTexture2D.texture); - gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array(4)); - - this.emptyTextures[gl.TEXTURE_2D] = emptyTexture2D; - this.emptyTextures[gl.TEXTURE_CUBE_MAP] = new GLTexture(gl.createTexture()); - - gl.bindTexture(gl.TEXTURE_CUBE_MAP, this.emptyTextures[gl.TEXTURE_CUBE_MAP].texture); - - let i; - - for (i = 0; i < 6; i++) - { - gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, null); - } - - gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_MAG_FILTER, gl.LINEAR); - gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_MIN_FILTER, gl.LINEAR); - - for (i = 0; i < this.boundTextures.length; i++) - { - this.bind(null, i); - } - } - - bind(texture, location) - { - const gl = this.gl; - - location = location || 0; - - if (this.currentLocation !== location) - { - this.currentLocation = location; - gl.activeTexture(gl.TEXTURE0 + location); - } - - if (texture) - { - texture = texture.baseTexture || texture; - - if (texture.valid) - { - texture.touched = this.renderer.textureGC.count; - - const glTexture = texture._glTextures[this.CONTEXT_UID] || this.initTexture(texture); - - gl.bindTexture(texture.target, glTexture.texture); - - if (glTexture.dirtyId !== texture.dirtyId) - { - this.updateTexture(texture); - } - - this.boundTextures[location] = texture; - } - } - else - { - gl.bindTexture(gl.TEXTURE_2D, this.emptyTextures[gl.TEXTURE_2D].texture); - this.boundTextures[location] = null; - } - } - - unbind(texture) - { - const gl = this.gl; - - for (let i = 0; i < this.boundTextures.length; i++) - { - if (this.boundTextures[i] === texture) - { - if (this.currentLocation !== i) - { - gl.activeTexture(gl.TEXTURE0 + i); - this.currentLocation = i; - } - - gl.bindTexture(gl.TEXTURE_2D, this.emptyTextures[texture.target].texture); - this.boundTextures[i] = null; - } - } - } - - initTexture(texture) - { - const glTexture = new GLTexture(this.gl.createTexture()); - - // guarentee an update.. - glTexture.dirtyId = -1; - - texture._glTextures[this.CONTEXT_UID] = glTexture; - - this.managedTextures.push(texture); - texture.on('dispose', this.destroyTexture, this); - - return glTexture; - } - - updateTexture(texture) - { - const glTexture = texture._glTextures[this.CONTEXT_UID]; - const renderer = this.renderer; - - if (texture.resource && texture.resource.upload(renderer, texture, glTexture)) - { - // texture is uploaded, dont do anything! - } - else - { - // default, renderTexture-like logic - const width = texture.realWidth; - const height = texture.realHeight; - const gl = renderer.gl; - - if (glTexture.width !== width - || glTexture.height !== height - || glTexture.dirtyId < 0) - { - glTexture.width = width; - glTexture.height = height; - - gl.texImage2D(texture.target, 0, - texture.format, - width, - height, - 0, - texture.format, - texture.type, - null); - } - } - - // lets only update what changes.. - if (texture.dirtyStyleId !== glTexture.dirtyStyleId) - { - this.updateTextureStyle(texture); - } - glTexture.dirtyId = texture.dirtyId; - } - - /** - * Deletes the texture from WebGL - * - * @param {PIXI.BaseTexture|PIXI.Texture} texture - the texture to destroy - * @param {boolean} [skipRemove=false] - Whether to skip removing the texture from the TextureManager. - */ - destroyTexture(texture, skipRemove) - { - const gl = this.gl; - - texture = texture.baseTexture || texture; - - if (texture._glTextures[this.renderer.CONTEXT_UID]) - { - this.unbind(texture); - - gl.deleteTexture(texture._glTextures[this.renderer.CONTEXT_UID].texture); - texture.off('dispose', this.destroyTexture, this); - - delete texture._glTextures[this.renderer.CONTEXT_UID]; - - if (!skipRemove) - { - const i = this.managedTextures.indexOf(texture); - - if (i !== -1) - { - removeItems(this.managedTextures, i, 1); - } - } - } - } - - updateTextureStyle(texture) - { - const glTexture = texture._glTextures[this.CONTEXT_UID]; - - glTexture.mipmap = texture.mipmap && texture.isPowerOfTwo; - if (!glTexture) - { - return; - } - - if (texture.resource && texture.resource.style(this.renderer, texture, glTexture)) - { - // style is set, dont do anything! - } - else - { - this.setStyle(texture, glTexture); - } - - glTexture.dirtyStyleId = texture.dirtyStyleId; - } - - setStyle(texture, glTexture) - { - const gl = this.gl; - - if (glTexture.mipmap) - { - gl.generateMipmap(texture.target); - } - - gl.texParameteri(texture.target, gl.TEXTURE_WRAP_S, texture.wrapMode); - gl.texParameteri(texture.target, gl.TEXTURE_WRAP_T, texture.wrapMode); - - if (glTexture.mipmap) - { - /* eslint-disable max-len */ - gl.texParameteri(texture.target, gl.TEXTURE_MIN_FILTER, texture.scaleMode ? gl.LINEAR_MIPMAP_LINEAR : gl.NEAREST_MIPMAP_NEAREST); - /* eslint-disable max-len */ - } - else - { - gl.texParameteri(texture.target, gl.TEXTURE_MIN_FILTER, texture.scaleMode ? gl.LINEAR : gl.NEAREST); - } - - gl.texParameteri(texture.target, gl.TEXTURE_MAG_FILTER, texture.scaleMode ? gl.LINEAR : gl.NEAREST); - } -} diff --git a/packages/core/src/renderers/utils/ObjectRenderer.js b/packages/core/src/renderers/utils/ObjectRenderer.js deleted file mode 100644 index f67a273..0000000 --- a/packages/core/src/renderers/utils/ObjectRenderer.js +++ /dev/null @@ -1,48 +0,0 @@ -import WebGLSystem from '../systems/WebGLSystem'; - -/** - * Base for a common object renderer that can be used as a system renderer plugin. - * - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class ObjectRenderer extends WebGLSystem -{ - /** - * Starts the renderer and sets the shader - * - */ - start() - { - // set the shader.. - } - - /** - * Stops the renderer - * - */ - stop() - { - this.flush(); - } - - /** - * Stub method for rendering content and emptying the current batch. - * - */ - flush() - { - // flush! - } - - /** - * Renders an object - * - * @param {PIXI.DisplayObject} object - The object to render. - */ - render(object) // eslint-disable-line no-unused-vars - { - // render the object - } -} diff --git a/packages/core/src/renderers/utils/Quad.js b/packages/core/src/renderers/utils/Quad.js deleted file mode 100644 index 5c1517b..0000000 --- a/packages/core/src/renderers/utils/Quad.js +++ /dev/null @@ -1,26 +0,0 @@ -import Geometry from '../../geometry/Geometry'; - -/** - * Helper class to create a quad - * - * @class - * @memberof PIXI - */ -export default class Quad extends Geometry -{ - /** - * just a quad - */ - constructor() - { - super(); - - this.addAttribute('aVertexPosition', [ - 0, 0, - 1, 0, - 1, 1, - 0, 1, - ]) - .addIndex([0, 1, 3, 2]); - } -} diff --git a/packages/core/src/renderers/utils/QuadUv.js b/packages/core/src/renderers/utils/QuadUv.js deleted file mode 100644 index 97bda89..0000000 --- a/packages/core/src/renderers/utils/QuadUv.js +++ /dev/null @@ -1,103 +0,0 @@ -import Geometry from '../../geometry/Geometry'; -import Buffer from '../../geometry/Buffer'; - -/** - * Helper class to create a quad with uvs like in v4 - * - * @class - * @memberof PIXI - */ -export default class QuadUv extends Geometry -{ - constructor() - { - super(); - - /** - * An array of vertices - * - * @member {Float32Array} - */ - this.vertices = new Float32Array([ - -1, -1, - 1, -1, - 1, 1, - -1, 1, - ]); - - /** - * The Uvs of the quad - * - * @member {Float32Array} - */ - this.uvs = new Float32Array([ - 0, 0, - 1, 0, - 1, 1, - 0, 1, - ]); - - this.vertexBuffer = new Buffer(this.vertices); - this.uvBuffer = new Buffer(this.uvs); - - this.addAttribute('aVertexPosition', this.vertexBuffer) - .addAttribute('aTextureCoord', this.uvBuffer) - .addIndex([0, 1, 2, 0, 2, 3]); - } - - /** - * Maps two Rectangle to the quad. - * - * @param {PIXI.Rectangle} targetTextureFrame - the first rectangle - * @param {PIXI.Rectangle} destinationFrame - the second rectangle - * @return {PIXI.Quad} Returns itself. - */ - map(targetTextureFrame, destinationFrame) - { - let x = 0; // destinationFrame.x / targetTextureFrame.width; - let y = 0; // destinationFrame.y / targetTextureFrame.height; - - this.uvs[0] = x; - this.uvs[1] = y; - - this.uvs[2] = x + (destinationFrame.width / targetTextureFrame.width); - this.uvs[3] = y; - - this.uvs[4] = x + (destinationFrame.width / targetTextureFrame.width); - this.uvs[5] = y + (destinationFrame.height / targetTextureFrame.height); - - this.uvs[6] = x; - this.uvs[7] = y + (destinationFrame.height / targetTextureFrame.height); - - x = destinationFrame.x; - y = destinationFrame.y; - - this.vertices[0] = x; - this.vertices[1] = y; - - this.vertices[2] = x + destinationFrame.width; - this.vertices[3] = y; - - this.vertices[4] = x + destinationFrame.width; - this.vertices[5] = y + destinationFrame.height; - - this.vertices[6] = x; - this.vertices[7] = y + destinationFrame.height; - - this.invalidate(); - - return this; - } - - /** - * legacy upload method, just marks buffers dirty - * @returns {PIXI.QuadUv} Returns itself. - */ - invalidate() - { - this.vertexBuffer._updateID++; - this.uvBuffer._updateID++; - - return this; - } -} diff --git a/packages/core/src/renderers/utils/checkMaxIfStatmentsInShader.js b/packages/core/src/renderers/utils/checkMaxIfStatmentsInShader.js deleted file mode 100644 index 8047c96..0000000 --- a/packages/core/src/renderers/utils/checkMaxIfStatmentsInShader.js +++ /dev/null @@ -1,58 +0,0 @@ -const fragTemplate = [ - 'precision mediump float;', - 'void main(void){', - 'float test = 0.1;', - '%forloop%', - 'gl_FragColor = vec4(0.0);', - '}', -].join('\n'); - -export default function checkMaxIfStatmentsInShader(maxIfs, gl) -{ - if (maxIfs === 0) - { - throw new Error('Invalid value of `0` passed to `checkMaxIfStatementsInShader`'); - } - - const shader = gl.createShader(gl.FRAGMENT_SHADER); - - while (true) // eslint-disable-line no-constant-condition - { - const fragmentSrc = fragTemplate.replace(/%forloop%/gi, generateIfTestSrc(maxIfs)); - - gl.shaderSource(shader, fragmentSrc); - gl.compileShader(shader); - - if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) - { - maxIfs = (maxIfs / 2) | 0; - } - else - { - // valid! - break; - } - } - - return maxIfs; -} - -function generateIfTestSrc(maxIfs) -{ - let src = ''; - - for (let i = 0; i < maxIfs; ++i) - { - if (i > 0) - { - src += '\nelse '; - } - - if (i < maxIfs - 1) - { - src += `if(test == ${i}.0){}`; - } - } - - return src; -} diff --git a/packages/core/src/renderers/utils/mapWebGLBlendModesToPixi.js b/packages/core/src/renderers/utils/mapWebGLBlendModesToPixi.js deleted file mode 100644 index 10ffe4d..0000000 --- a/packages/core/src/renderers/utils/mapWebGLBlendModesToPixi.js +++ /dev/null @@ -1,47 +0,0 @@ -import { BLEND_MODES } from '@pixi/constants'; - -/** - * Maps gl blend combinations to WebGL. - * - * @memberof PIXI - * @function mapWebGLBlendModesToPixi - * @private - * @param {WebGLRenderingContext} gl - The rendering context. - * @param {string[]} [array=[]] - The array to output into. - * @return {string[]} Mapped modes. - */ -export default function mapWebGLBlendModesToPixi(gl, array = []) -{ - // TODO - premultiply alpha would be different. - // add a boolean for that! - array[BLEND_MODES.NORMAL] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.ADD] = [gl.ONE, gl.DST_ALPHA]; - array[BLEND_MODES.MULTIPLY] = [gl.DST_COLOR, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.SCREEN] = [gl.ONE, gl.ONE_MINUS_SRC_COLOR]; - array[BLEND_MODES.OVERLAY] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.DARKEN] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.LIGHTEN] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.COLOR_DODGE] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.COLOR_BURN] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.HARD_LIGHT] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.SOFT_LIGHT] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.DIFFERENCE] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.EXCLUSION] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.HUE] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.SATURATION] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.COLOR] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.LUMINOSITY] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.NONE] = [0, 0]; - - // not-premultiplied blend modes - array[BLEND_MODES.NORMAL_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.ADD_NPM] = [gl.SRC_ALPHA, gl.DST_ALPHA, gl.ONE, gl.DST_ALPHA]; - array[BLEND_MODES.SCREEN_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_COLOR, gl.ONE, gl.ONE_MINUS_SRC_COLOR]; - - // not-premultiplied blend modes - array[BLEND_MODES.NORMAL_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.ADD_NPM] = [gl.SRC_ALPHA, gl.DST_ALPHA, gl.ONE, gl.DST_ALPHA]; - array[BLEND_MODES.SCREEN_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_COLOR, gl.ONE, gl.ONE_MINUS_SRC_COLOR]; - - return array; -} diff --git a/packages/core/src/shader/GLProgram.js b/packages/core/src/shader/GLProgram.js new file mode 100644 index 0000000..92980ca --- /dev/null +++ b/packages/core/src/shader/GLProgram.js @@ -0,0 +1,43 @@ +/** + * Helper class to create a webGL Program + * + * @class + * @private + */ +export default class GLProgram +{ + constructor(program, uniformData) + { + /** + * The shader program + * + * @member {WebGLProgram} + */ + this.program = program; + + /** + * holds the uniform data which contains uniform locations + * and current uniform values used for caching and preventing undeed GPU commands + * @type {Object} + */ + this.uniformData = uniformData; + + /** + * uniformGroups holds the various upload functions for the shader. Each uniform group + * and program have a unique upload function generated. + * @type {Object} + */ + this.uniformGroups = {}; + } + + /** + * Destroys this program + * TODO + */ + destroy() + { + this.uniformData = null; + this.uniformGroups = null; + this.program = null; + } +} diff --git a/packages/core/src/shader/Program.js b/packages/core/src/shader/Program.js index 4f9971e..4b4007a 100644 --- a/packages/core/src/shader/Program.js +++ b/packages/core/src/shader/Program.js @@ -1,6 +1,11 @@ -import * as shaderUtils from '../renderers/systems/shader/shader'; +// import * as from '../systems/shader/shader'; +import { setPrecision, + defaultValue, + compileProgram, + mapSize, + mapType, + getTestContext } from './utils'; import { ProgramCache } from '@pixi/utils'; -import getTestContext from './getTestContext'; import defaultFragment from './defaultProgram.frag'; import defaultVertex from './defaultProgram.vert'; import { settings } from '@pixi/settings'; @@ -34,8 +39,8 @@ */ this.fragmentSrc = fragmentSrc || Program.defaultFragmentSrc; - this.vertexSrc = shaderUtils.setPrecision(this.vertexSrc, settings.PRECISION_VERTEX); - this.fragmentSrc = shaderUtils.setPrecision(this.fragmentSrc, settings.PRECISION_FRAGMENT); + this.vertexSrc = setPrecision(this.vertexSrc, settings.PRECISION_VERTEX); + this.fragmentSrc = setPrecision(this.fragmentSrc, settings.PRECISION_FRAGMENT); // currently this does not extract structs only default types this.extractData(this.vertexSrc, this.fragmentSrc); @@ -62,7 +67,7 @@ if (gl) { - const program = shaderUtils.compileProgram(gl, vertexSrc, fragmentSrc); + const program = compileProgram(gl, vertexSrc, fragmentSrc); this.attributeData = this.getAttributeData(program, gl); this.uniformData = this.getUniformData(program, gl); @@ -95,13 +100,13 @@ for (let i = 0; i < totalAttributes; i++) { const attribData = gl.getActiveAttrib(program, i); - const type = shaderUtils.mapType(gl, attribData.type); + const type = mapType(gl, attribData.type); /*eslint-disable */ const data = { type: type, name: attribData.name, - size: shaderUtils.mapSize(type), + size: mapSize(type), location: 0, }; /* eslint-enable */ @@ -145,14 +150,14 @@ const name = uniformData.name.replace(/\[.*?\]/, ''); const isArray = uniformData.name.match(/\[.*?\]/, ''); - const type = shaderUtils.mapType(gl, uniformData.type); + const type = mapType(gl, uniformData.type); /*eslint-disable */ uniforms[name] = { type: type, size: uniformData.size, isArray:isArray, - value: shaderUtils.defaultValue(type, uniformData.size), + value: defaultValue(type, uniformData.size), }; /* eslint-enable */ } diff --git a/packages/core/src/shader/ShaderSystem.js b/packages/core/src/shader/ShaderSystem.js new file mode 100644 index 0000000..40b8af8 --- /dev/null +++ b/packages/core/src/shader/ShaderSystem.js @@ -0,0 +1,171 @@ +import System from '../System'; +import GLProgram from './GLProgram'; +import { generateUniformsSync, + defaultValue, + compileProgram } from './utils'; + +let UID = 0; + +/** + * Helper class to create a webGL Texture + * + * @class + * @memberof PIXI.systems + * @extends PIXI.System + */ +export default class ShaderSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - A reference to the current renderer + */ + constructor(renderer) + { + super(renderer); + + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = null; + + this.shader = null; + this.program = null; + + this.id = UID++; + } + + contextChange(gl) + { + this.gl = gl; + } + + /** + * Changes the current shader to the one given in parameter + * + * @param {PIXI.Shader} shader - the new shader + * @param {boolean} dontSync - false if the shader should automatically sync its uniforms. + * @returns {PIXI.glCore.glProgram} the glProgram that belongs to the shader. + */ + bind(shader, dontSync) + { + shader.uniforms.globals = this.renderer.globalUniforms; + + const program = shader.program; + const glProgram = program.glPrograms[this.renderer.CONTEXT_UID] || this.generateShader(shader); + + this.shader = shader; + + // TODO - some current pixi plugins bypass this.. so it not safe to use yet.. + if (this.program !== program) + { + this.program = program; + this.gl.useProgram(glProgram.program); + } + + if (!dontSync) + { + this.syncUniformGroup(shader.uniformGroup); + } + + return glProgram; + } + + /** + * Uploads the uniforms values to the currently bound shader. + * + * @param {object} uniforms - the uniforms valiues that be applied to the current shader + */ + setUniforms(uniforms) + { + const shader = this.shader.program; + const glProgram = shader.glPrograms[this.renderer.CONTEXT_UID]; + + shader.syncUniforms(glProgram.uniformData, uniforms, this.renderer); + } + + syncUniformGroup(group) + { + const glProgram = this.getglProgram(); + + if (!group.static || group.dirtyId !== glProgram.uniformGroups[group.id]) + { + glProgram.uniformGroups[group.id] = group.dirtyId; + const syncFunc = group.syncUniforms[this.shader.program.id] || this.createSyncGroups(group); + + syncFunc(glProgram.uniformData, group.uniforms, this.renderer); + } + } + + createSyncGroups(group) + { + group.syncUniforms[this.shader.program.id] = generateUniformsSync(group, this.shader.program.uniformData); + + return group.syncUniforms[this.shader.program.id]; + } + + /** + * Returns the underlying GLShade rof the currently bound shader. + * This can be handy for when you to have a little more control over the setting of your uniforms. + * + * @return {PIXI.glCore.Shader} the glProgram for the currently bound Shader for this context + */ + getglProgram() + { + if (this.shader) + { + return this.shader.program.glPrograms[this.renderer.CONTEXT_UID]; + } + + return null; + } + + /** + * Generates a glProgram verion of the Shader provided. + * + * @private + * @param {PIXI.Shader} shader the shader that the glProgram will be based on. + * @return {PIXI.glCore.glProgram} A shiney new glProgram + */ + generateShader(shader) + { + const gl = this.gl; + + const program = shader.program; + + const attribMap = {}; + + for (const i in program.attributeData) + { + attribMap[i] = program.attributeData[i].location; + } + + const shaderProgram = compileProgram(gl, program.vertexSrc, program.fragmentSrc, attribMap); + const uniformData = {}; + + for (const i in program.uniformData) + { + const data = program.uniformData[i]; + + uniformData[i] = { + location: gl.getUniformLocation(shaderProgram, i), + value: defaultValue(data.type, data.size), + }; + } + + const glProgram = new GLProgram(shaderProgram, uniformData); + + program.glPrograms[this.renderer.CONTEXT_UID] = glProgram; + + return glProgram; + } + + /** + * Destroys this System and removes all its textures + */ + destroy() + { + // TODO implement destroy method for ShaderSystem + this.destroyed = true; + } +} diff --git a/packages/core/src/shader/defaultValue.js b/packages/core/src/shader/defaultValue.js deleted file mode 100644 index 847a069..0000000 --- a/packages/core/src/shader/defaultValue.js +++ /dev/null @@ -1,78 +0,0 @@ -function booleanArray(size) -{ - const array = new Array(size); - - for (let i = 0; i < array.length; i++) - { - array[i] = false; - } - - return array; -} - -/** - * @class - * @memberof PIXI.glCore.shader - * @param type {String} Type of value - * @param size {Number} - */ -export default function defaultValue(type, size) -{ - switch (type) - { - case 'float': - return 0; - - case 'vec2': - return new Float32Array(2 * size); - - case 'vec3': - return new Float32Array(3 * size); - - case 'vec4': - return new Float32Array(4 * size); - - case 'int': - case 'sampler2D': - return 0; - - case 'ivec2': - return new Int32Array(2 * size); - - case 'ivec3': - return new Int32Array(3 * size); - - case 'ivec4': - return new Int32Array(4 * size); - - case 'bool': - return false; - - case 'bvec2': - - return booleanArray(2 * size); - - case 'bvec3': - return booleanArray(3 * size); - - case 'bvec4': - return booleanArray(4 * size); - - case 'mat2': - return new Float32Array([1, 0, - 0, 1]); - - case 'mat3': - return new Float32Array([1, 0, 0, - 0, 1, 0, - 0, 0, 1]); - - case 'mat4': - return new Float32Array([1, 0, 0, 0, - 0, 1, 0, 0, - 0, 0, 1, 0, - 0, 0, 0, 1]); - } - - return 0; -} diff --git a/packages/core/src/shader/extractUniformsFromSrc.js b/packages/core/src/shader/extractUniformsFromSrc.js deleted file mode 100644 index 3d02c0a..0000000 --- a/packages/core/src/shader/extractUniformsFromSrc.js +++ /dev/null @@ -1,57 +0,0 @@ -import defaultValue from './defaultValue'; - -export default function extractUniformsFromSrc(vertexSrc, fragmentSrc, mask) -{ - const vertUniforms = extractUniformsFromString(vertexSrc, mask); - const fragUniforms = extractUniformsFromString(fragmentSrc, mask); - - return Object.assign(vertUniforms, fragUniforms); -} - -function extractUniformsFromString(string) -{ - const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); - - const uniforms = {}; - let nameSplit; - - // clean the lines a little - remove extra spaces / teabs etc - // then split along ';' - const lines = string.replace(/\s+/g, ' ') - .split(/\s*;\s*/); - - // loop through.. - for (let i = 0; i < lines.length; i++) - { - const line = lines[i].trim(); - - if (line.indexOf('uniform') > -1) - { - const splitLine = line.split(' '); - const type = splitLine[1]; - - let name = splitLine[2]; - let size = 1; - - if (name.indexOf('[') > -1) - { - // array! - nameSplit = name.split(/\[|]/); - name = nameSplit[0]; - size *= Number(nameSplit[1]); - } - - if (!name.match(maskRegex)) - { - uniforms[name] = { - value: defaultValue(type, size), - dirtyId: 0, - name, - type, - }; - } - } - } - - return uniforms; -} diff --git a/packages/core/src/shader/generateUniformsSync.js b/packages/core/src/shader/generateUniformsSync.js deleted file mode 100644 index 98a357f..0000000 --- a/packages/core/src/shader/generateUniformsSync.js +++ /dev/null @@ -1,231 +0,0 @@ -// cv = CachedValue -// v = value -// ud = uniformData -// uv = uniformValue -// l = loaction -const GLSL_TO_SINGLE_SETTERS_CACHED = { - - float: ` - if(cv !== v) - { - cv.v = v; - gl.uniform1f(location, v) - }`, - - vec2: ` - if(cv[0] !== v[0] || cv[1] !== v[1]) - { - cv[0] = v[0]; - cv[1] = v[1]; - gl.uniform2f(location, v[0], v[1]) - }`, - - vec3: ` - if(cv[0] !== v[0] || cv[1] !== v[1] || cv[2] !== v[2]) - { - cv[0] = v[0]; - cv[1] = v[1]; - cv[2] = v[2]; - - gl.uniform3f(location, v[0], v[1], v[2]) - }`, - - vec4: 'gl.uniform4f(location, v[0], v[1], v[2], v[3])', - - int: 'gl.uniform1i(location, v)', - ivec2: 'gl.uniform2i(location, v[0], v[1])', - ivec3: 'gl.uniform3i(location, v[0], v[1], v[2])', - ivec4: 'gl.uniform4i(location, v[0], v[1], v[2], v[3])', - - bool: 'gl.uniform1i(location, v)', - bvec2: 'gl.uniform2i(location, v[0], v[1])', - bvec3: 'gl.uniform3i(location, v[0], v[1], v[2])', - bvec4: 'gl.uniform4i(location, v[0], v[1], v[2], v[3])', - - mat2: 'gl.uniformMatrix2fv(location, false, v)', - mat3: 'gl.uniformMatrix3fv(location, false, v)', - mat4: 'gl.uniformMatrix4fv(location, false, v)', - - sampler2D: 'gl.uniform1i(location, v)', - samplerCube: 'gl.uniform1i(location, v)', - sampler2DArray: 'gl.uniform1i(location, v)', -}; - -const GLSL_TO_ARRAY_SETTERS = { - - float: `gl.uniform1fv(location, v)`, - - vec2: `gl.uniform2fv(location, v)`, - vec3: `gl.uniform3fv(location, v)`, - vec4: 'gl.uniform4fv(location, v)', - - mat4: 'gl.uniformMatrix4fv(location, false, v)', - mat3: 'gl.uniformMatrix3fv(location, false, v)', - mat2: 'gl.uniformMatrix2fv(location, false, v)', - - int: 'gl.uniform1iv(location, v)', - ivec2: 'gl.uniform2iv(location, v)', - ivec3: 'gl.uniform3iv(location, v)', - ivec4: 'gl.uniform4iv(location, v)', - - bool: 'gl.uniform1iv(location, v)', - bvec2: 'gl.uniform2iv(location, v)', - bvec3: 'gl.uniform3iv(location, v)', - bvec4: 'gl.uniform4iv(location, v)', - - sampler2D: 'gl.uniform1iv(location, v)', - samplerCube: 'gl.uniform1iv(location, v)', - sampler2DArray: 'gl.uniform1iv(location, v)', -}; - -export default function generateUniformsSync(group, uniformData) -{ - let textureCount = 0; - let func = `var v = null; - var cv = null - var gl = renderer.gl`; - - for (const i in group.uniforms) - { - const data = uniformData[i]; - - if (!data) - { - if (group.uniforms[i].group) - { - func += ` - renderer.shader.syncUniformGroup(uv.${i}); - `; - } - - continue; - } - - // TODO && uniformData[i].value !== 0 <-- do we still need this? - if (data.type === 'float' && data.size === 1) - { - func += ` - if(uv.${i} !== ud.${i}.value) - { - ud.${i}.value = uv.${i} - gl.uniform1f(ud.${i}.location, uv.${i}) - }\n`; - } - /* eslint-disable max-len */ - else if ((data.type === 'sampler2D' || data.type === 'samplerCube' || data.type === 'sampler2DArray') && data.size === 1 && !data.isArray) - /* eslint-disable max-len */ - { - func += ` - renderer.texture.bind(uv.${i}, ${textureCount}); - - if(ud.${i}.value !== ${textureCount}) - { - ud.${i}.value = ${textureCount}; - gl.uniform1i(ud.${i}.location, ${textureCount});\n; // eslint-disable-line max-len - }\n`; - - textureCount++; - } - else if (data.type === 'mat3' && data.size === 1) - { - if (group.uniforms[i].a !== undefined) - { - // TODO and some smart caching dirty ids here! - func += ` - gl.uniformMatrix3fv(ud.${i}.location, false, uv.${i}.toArray(true)); - \n`; - } - else - { - func += ` - gl.uniformMatrix3fv(ud.${i}.location, false, uv.${i}); - \n`; - } - } - else if (data.type === 'vec2' && data.size === 1) - { - // TODO - do we need both here? - // maybe we can get away with only using points? - if (group.uniforms[i].x !== undefined) - { - func += ` - cv = ud.${i}.value; - v = uv.${i}; - - if(cv[0] !== v.x || cv[1] !== v.y) - { - cv[0] = v.x; - cv[1] = v.y; - gl.uniform2f(ud.${i}.location, v.x, v.y); - }\n`; - } - else - { - func += ` - cv = ud.${i}.value; - v = uv.${i}; - - if(cv[0] !== v[0] || cv[1] !== v[1]) - { - cv[0] = v[0]; - cv[1] = v[1]; - gl.uniform2f(ud.${i}.location, v[0], v[1]); - } - \n`; - } - } - else if (data.type === 'vec4' && data.size === 1) - { - // TODO - do we need both here? - // maybe we can get away with only using points? - if (group.uniforms[i].width !== undefined) - { - func += ` - cv = ud.${i}.value; - v = uv.${i}; - - if(cv[0] !== v.x || cv[1] !== v.y || cv[2] !== v.width || cv[3] !== v.height) - { - cv[0] = v.x; - cv[1] = v.y; - cv[2] = v.width; - cv[3] = v.height; - gl.uniform4f(ud.${i}.location, v.x, v.y, v.width, v.height) - }\n`; - } - else - { - func += ` - cv = ud.${i}.value; - v = uv.${i}; - - if(cv[0] !== v[0] || cv[1] !== v[1] || cv[2] !== v[2] || cv[3] !== v[3]) - { - cv[0] = v[0]; - cv[1] = v[1]; - cv[2] = v[2]; - cv[3] = v[3]; - - gl.uniform4f(ud.${i}.location, v[0], v[1], v[2], v[3]) - } - \n`; - } - } - else - { - const templateType = (data.size === 1) ? GLSL_TO_SINGLE_SETTERS_CACHED : GLSL_TO_ARRAY_SETTERS; - - const template = templateType[data.type].replace('location', `ud.${i}.location`); - - func += ` - cv = ud.${i}.value; - v = uv.${i}; - ${template};\n`; - } - } - - // console.log(' --------------- ') - // console.log(func); - - return new Function('ud', 'uv', 'renderer', func); // eslint-disable-line no-new-func -} diff --git a/packages/core/src/AbstractRenderer.js b/packages/core/src/AbstractRenderer.js new file mode 100644 index 0000000..64b2eb1 --- /dev/null +++ b/packages/core/src/AbstractRenderer.js @@ -0,0 +1,353 @@ +import { hex2string, hex2rgb } from '@pixi/utils'; +import { Matrix, Rectangle } from '@pixi/math'; +import { RENDERER_TYPE } from '@pixi/constants'; +import { settings } from '@pixi/settings'; +import { Container } from '@pixi/display'; +import RenderTexture from './renderTexture/RenderTexture'; +import EventEmitter from 'eventemitter3'; + +const tempMatrix = new Matrix(); + +/** + * 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 EventEmitter + * @memberof PIXI + */ +export default class AbstractRenderer extends EventEmitter +{ + // eslint-disable-next-line valid-jsdoc + /** + * @param {string} system - The name of the system this renderer is for. + * @param {object} [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.transparent=false] - If the render view is transparent, default false + * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false + * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. The + * resolution of the renderer retina would be 2. + * @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 {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when rendering, + * stopping pixel interpolation. + */ + constructor(system, options, arg2, arg3) + { + super(); + + // Support for constructor(system, screenWidth, screenHeight, options) + if (typeof options === 'number') + { + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + }, arg3); + } + + // 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 = RENDERER_TYPE.UNKNOWN; + + /** + * Measurements of the screen. (0, 0, screenWidth, screenHeight) + * + * Its safe to use as filterArea or hitArea for 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 1 + */ + this.resolution = options.resolution || settings.RESOLUTION; + + /** + * Whether the render view is transparent + * + * @member {boolean} + */ + this.transparent = options.transparent; + + /** + * Whether css dimensions of canvas view should be resized to screen dimensions automatically + * + * @member {boolean} + */ + this.autoResize = options.autoResize || false; + + /** + * Tracks the blend modes useful for this renderer. + * + * @member {object} + */ + this.blendModes = null; + + /** + * 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; + + /** + * If true PixiJS will Math.floor() x/y values when rendering, stopping pixel interpolation. + * Handy for crisp pixel art and speed on legacy devices. + * + * @member {boolean} + */ + this.roundPixels = options.roundPixels; + + /** + * The background color as a number. + * + * @member {number} + * @private + */ + this._backgroundColor = 0x000000; + + /** + * The background color as an [R, G, B] array. + * + * @member {number[]} + * @private + */ + this._backgroundColorRgba = [0, 0, 0, 0]; + + /** + * The background color as a string. + * + * @member {string} + * @private + */ + this._backgroundColorString = '#000000'; + + this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter + + /** + * This temporary display object used as the parent of the currently being rendered item + * + * @member {PIXI.DisplayObject} + * @private + */ + this._tempDisplayObjectParent = new Container(); + + /** + * The last root object that the renderer tried to render. + * + * @member {PIXI.DisplayObject} + * @private + */ + this._lastObjectRendered = this._tempDisplayObjectParent; + + /** + * Collection of plugins. + * @readonly + * @member {object} + */ + this.plugins = {}; + } + + /** + * Initialize the plugins. + * + * @protected + * @param {object} staticMap - The dictionary of staticly saved plugins. + */ + initPlugins(staticMap) + { + 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() + { + return this.view.width; + } + + /** + * Same as view.height, actual number of pixels in the canvas by vertical + * + * @member {number} + * @readonly + * @default 600 + */ + get height() + { + return this.view.height; + } + + /** + * Resizes the screen and canvas to the specified width and height + * Canvas dimensions are multiplied by resolution + * + * @param {number} screenWidth - the new width of the screen + * @param {number} screenHeight - the new height of the screen + */ + resize(screenWidth, screenHeight) + { + this.screen.width = screenWidth; + this.screen.height = screenHeight; + + this.view.width = screenWidth * this.resolution; + this.view.height = screenHeight * this.resolution; + + if (this.autoResize) + { + this.view.style.width = `${screenWidth}px`; + this.view.style.height = `${screenHeight}px`; + } + } + + /** + * 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 {PIXI.DisplayObject} displayObject - The displayObject the object will be generated from + * @param {number} scaleMode - Should be one of the scaleMode consts + * @param {number} resolution - The resolution / device pixel ratio of the texture being generated + * @param {PIXI.Rectangle} [region] - The region of the displayObject, that shall be rendered, + * if no region is specified, defaults to the local bounds of the displayObject. + * @return {PIXI.Texture} a texture of the graphics object + */ + generateTexture(displayObject, scaleMode, resolution, region) + { + region = region || displayObject.getLocalBounds(); + + // 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(region.width | 0, region.height | 0, scaleMode, resolution); + + tempMatrix.tx = -region.x; + tempMatrix.ty = -region.y; + + this.render(displayObject, renderTexture, false, tempMatrix, true); + + return renderTexture; + } + + /** + * Removes everything from the renderer and optionally removes the Canvas DOM element. + * + * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. + */ + destroy(removeView) + { + 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); + } + + this.plugins = null; + + this.type = RENDERER_TYPE.UNKNOWN; + + this.view = null; + + this.screen = null; + + this.resolution = 0; + + this.transparent = false; + + this.autoResize = false; + + this.blendModes = null; + + this.options = null; + + this.preserveDrawingBuffer = false; + this.clearBeforeRender = false; + + this.roundPixels = false; + + this._backgroundColor = 0; + this._backgroundColorRgba = null; + this._backgroundColorString = null; + + this._tempDisplayObjectParent = null; + this._lastObjectRendered = null; + } + + /** + * The background color to fill if not transparent + * + * @member {number} + */ + get backgroundColor() + { + return this._backgroundColor; + } + + set backgroundColor(value) // eslint-disable-line require-jsdoc + { + this._backgroundColor = value; + this._backgroundColorString = hex2string(value); + hex2rgb(value, this._backgroundColorRgba); + } +} diff --git a/packages/core/src/Renderer.js b/packages/core/src/Renderer.js new file mode 100644 index 0000000..2227e78 --- /dev/null +++ b/packages/core/src/Renderer.js @@ -0,0 +1,338 @@ +import AbstractRenderer from './AbstractRenderer'; +import { sayHello } from '@pixi/utils'; +import MaskSystem from './mask/MaskSystem'; +import StencilSystem from './mask/StencilSystem'; +import FilterSystem from './filters/FilterSystem'; +import FramebufferSystem from './framebuffer/FramebufferSystem'; +import RenderTextureSystem from './renderTexture/RenderTextureSystem'; +import TextureSystem from './textures/TextureSystem'; +import ProjectionSystem from './projection/ProjectionSystem'; +import StateSystem from './state/StateSystem'; +import GeometrySystem from './geometry/GeometrySystem'; +import ShaderSystem from './shader/ShaderSystem'; +import ContextSystem from './context/ContextSystem'; +import BatchSystem from './batch/BatchSystem'; +import TextureGCSystem from './textures/TextureGCSystem'; +import { RENDERER_TYPE } from '@pixi/constants'; +import UniformGroup from './shader/UniformGroup'; +import { Matrix } from '@pixi/math'; +import Runner from 'mini-runner'; + +/** + * 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 Render works by automatically managing webGLBatchs. + * 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 :) + * + * @class + * @memberof PIXI + * @extends PIXI.AbstractRenderer + */ +export default class Renderer extends AbstractRenderer +{ + // eslint-disable-next-line valid-jsdoc + /** + * + * @param {object} [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.transparent=false] - If the render view is transparent, default false + * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false + * @param {boolean} [options.antialias=false] - sets antialias. If not available natively then FXAA + * antialiasing is used + * @param {boolean} [options.forceFXAA=false] - forces FXAA antialiasing to be used over native. + * FXAA is faster, but may not always look as great + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. + * The resolution of the renderer retina would be 2. + * @param {boolean} [options.clearBeforeRender=true] - This sets if the renderer will clear + * the canvas or not before the new render pass. If you wish to set this to false, you *must* set + * preserveDrawingBuffer to `true`. + * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, + * enable this if you need to call toDataUrl on the webgl context. + * @param {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when + * rendering, stopping pixel interpolation. + * @param {number} [options.backgroundColor=0x000000] - The background color of the rendered area + * (shown if not transparent). + * @param {boolean} [options.legacy=false] - If true PixiJS will aim to ensure compatibility + * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. + * @param {string} [options.powerPreference] - Parameter passed to webgl context, set to "high-performance" + * for devices with dual graphics card + */ + constructor(options, arg2, arg3) + { + super('WebGL', options, arg2, arg3); + + /** + * The type of this renderer as a standardised const + * + * @member {number} + * @see PIXI.RENDERER_TYPE + */ + this.type = RENDERER_TYPE.WEBGL; + + // this will be set by the contextSystem (this.context) + this.gl = null; + this.CONTEXT_UID = 0; + this.legacy = !!options.legacy; + + // TODO legacy! + + // runners! + this.runners = { + destroy: new Runner('destroy'), + contextChange: new Runner('contextChange', 1), + reset: new Runner('reset'), + update: new Runner('update'), + postrender: new Runner('postrender'), + prerender: new Runner('prerender'), + resize: new Runner('resize', 2), + }; + + 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(GeometrySystem, 'geometry') + .addSystem(FramebufferSystem, 'framebuffer') + .addSystem(StencilSystem, 'stencil') + .addSystem(ProjectionSystem, 'projection') + .addSystem(TextureGCSystem, 'textureGC') + .addSystem(FilterSystem, 'filter') + .addSystem(RenderTextureSystem, 'renderTexture') + .addSystem(BatchSystem, 'batch'); + + this.initPlugins(Renderer.__plugins); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ + if (options.context) + { + this.context.initFromContext(options.context); + } + else + { + this.context.initFromOptions({ + alpha: this.transparent, + antialias: options.antialias, + premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', + stencil: true, + 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); + } + + /** + * Add a new system to the renderer. + * @param {class} ClassRef - Class reference + * @param {string} [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. + * @return {PIXI.Renderer} Return instance of renderer + */ + addSystem(ClassRef, name) + { + if (!name) + { + name = ClassRef.name; + } + + // TODO - read name from class.name.. + + /* + if(name.includes('System')) + { + name = name.replace('System', ''); + name = name.charAt(0).toLowerCase() + name.slice(1); + } + */ + + const system = new ClassRef(this); + + if (this[name]) + { + throw new Error(`Whoops! ${name} is already a manger`); + } + + this[name] = system; + + for (const i in this.runners) + { + this.runners[i].add(system); + } + + return this; + + /** + * 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. + */ + } + + /** + * Renders the object to its webGL view + * + * @param {PIXI.DisplayObject} displayObject - the object to be rendered + * @param {PIXI.RenderTexture} renderTexture - The render texture to render to. + * @param {boolean} [clear] - Should the canvas be cleared before the new render + * @param {PIXI.Transform} [transform] - A transform to apply to the render texture before rendering. + * @param {boolean} [skipUpdateTransform] - Should we skip the update transform pass? + */ + render(displayObject, renderTexture, clear, transform, skipUpdateTransform) + { + // can be handy to know! + this.renderingToScreen = !renderTexture; + + this.runners.prerender.run(); + this.emit('prerender'); + + // 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.parent; + + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = 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.run(); + + this.emit('postrender'); + } + + /** + * Resizes the webGL view to the specified width and height. + * + * @param {number} screenWidth - the new width of the screen + * @param {number} screenHeight - the new height of the screen + */ + resize(screenWidth, screenHeight) + { + AbstractRenderer.prototype.resize.call(this, screenWidth, screenHeight); + + this.runners.resize.run(screenWidth, screenHeight); + } + + /** + * Resets the WebGL state so you can render things however you fancy! + * + * @return {PIXI.Renderer} Returns itself. + */ + reset() + { + this.runners.reset.run(); + + return this; + } + + clear() + { + this.framebuffer.bind(); + this.framebuffer.clear(); + } + + /** + * Removes everything from the renderer (event listeners, spritebatch, etc...) + * + * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. + * See: https://github.com/pixijs/pixi.js/issues/2233 + */ + destroy(removeView) + { + this.runners.destroy.run(); + + // call base destroy + super.destroy(removeView); + + // TODO nullify all the managers.. + this.gl = null; + } + + /** + * 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 + * builds and excluding plugins. + * @name PIXI.Renderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. + */ + + /** + * Adds a plugin to the renderer. + * + * @method + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + static registerPlugin(pluginName, ctor) + { + Renderer.__plugins = Renderer.__plugins || {}; + Renderer.__plugins[pluginName] = ctor; + } +} diff --git a/packages/core/src/System.js b/packages/core/src/System.js new file mode 100644 index 0000000..05a10c8 --- /dev/null +++ b/packages/core/src/System.js @@ -0,0 +1,42 @@ +/** + * System is a base class used for extending systems used by the {@link PIXI.Renderer} + * @see PIXI.Renderer#addSystem + * @class + * @memberof PIXI + */ +export default class System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + /** + * The renderer this manager works for. + * + * @member {PIXI.Renderer} + */ + this.renderer = renderer; + + this.renderer.runners.contextChange.add(this); + } + + /** + * Generic method called when there is a WebGL context change. + * + */ + contextChange() + { + // do some codes init! + } + + /** + * Generic destroy methods to be overridden by the subclass + * + */ + destroy() + { + this.renderer.runners.contextChange.remove(this); + this.renderer = null; + } +} diff --git a/packages/core/src/batch/BatchSystem.js b/packages/core/src/batch/BatchSystem.js new file mode 100644 index 0000000..b04c249 --- /dev/null +++ b/packages/core/src/batch/BatchSystem.js @@ -0,0 +1,68 @@ +import System from '../System'; +import ObjectRenderer from './ObjectRenderer'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class BatchSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + super(renderer); + + /** + * An empty renderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.emptyRenderer = new ObjectRenderer(renderer); + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.currentRenderer = this.emptyRenderer; + } + + /** + * Changes the current renderer to the one given in parameter + * + * @param {PIXI.ObjectRenderer} objectRenderer - The object renderer to use. + */ + setObjectRenderer(objectRenderer) + { + if (this.currentRenderer === objectRenderer) + { + return; + } + + this.currentRenderer.stop(); + this.currentRenderer = objectRenderer; + + this.renderer.state.setState(objectRenderer.state); + + this.currentRenderer.start(); + } + + /** + * This should be called if you wish to do some custom rendering + * It will basically render anything that may be batched up such as sprites + * + */ + flush() + { + this.setObjectRenderer(this.emptyRenderer); + } + + reset() + { + this.setObjectRenderer(this.emptyRenderer); + } +} diff --git a/packages/core/src/batch/ObjectRenderer.js b/packages/core/src/batch/ObjectRenderer.js new file mode 100644 index 0000000..0c851a9 --- /dev/null +++ b/packages/core/src/batch/ObjectRenderer.js @@ -0,0 +1,48 @@ +import System from '../System'; + +/** + * Base for a common object renderer that can be used as a system renderer plugin. + * + * @class + * @extends PIXI.System + * @memberof PIXI + */ +export default class ObjectRenderer extends System +{ + /** + * Starts the renderer and sets the shader + * + */ + start() + { + // set the shader.. + } + + /** + * Stops the renderer + * + */ + stop() + { + this.flush(); + } + + /** + * Stub method for rendering content and emptying the current batch. + * + */ + flush() + { + // flush! + } + + /** + * Renders an object + * + * @param {PIXI.DisplayObject} object - The object to render. + */ + render(object) // eslint-disable-line no-unused-vars + { + // render the object + } +} diff --git a/packages/core/src/context/ContextSystem.js b/packages/core/src/context/ContextSystem.js new file mode 100644 index 0000000..301aab4 --- /dev/null +++ b/packages/core/src/context/ContextSystem.js @@ -0,0 +1,186 @@ +import System from '../System'; +import { settings } from '@pixi/settings'; + +let CONTEXT_UID = 0; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class ContextSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.webGLVersion = 1; + + this.handleContextLost = this.handleContextLost.bind(this); + this.handleContextRestored = this.handleContextRestored.bind(this); + + this.extensions = {}; + + renderer.view.addEventListener('webglcontextlost', this.handleContextLost, false); + renderer.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + } + + get isLost() + { + return (!this.gl || this.gl.isContextLost()); + } + + contextChange(gl) + { + this.gl = gl; + + // restore a context if it was previously lost + if (gl.isContextLost() && gl.getExtension('WEBGL_lose_context')) + { + gl.getExtension('WEBGL_lose_context').restoreContext(); + } + } + + initFromContext(gl) + { + this.gl = gl; + this.validateContext(gl); + this.renderer.gl = gl; + this.renderer.CONTEXT_UID = CONTEXT_UID++; + this.renderer.runners.contextChange.run(gl); + } + + initFromOptions(options) + { + const gl = this.createContext(this.renderer.view, options); + + this.initFromContext(gl); + } + + /** + * Helper class to create a webGL Context + * + * @class + * @memberof PIXI.glCore + * @param canvas {HTMLCanvasElement} the canvas element that we will get the context from + * @param options {Object} An options object that gets passed in to the canvas element containing the context attributes, + * see https://developer.mozilla.org/en/docs/Web/API/HTMLCanvasElement/getContext for the options available + * @return {WebGLRenderingContext} the WebGL context + */ + createContext(canvas, options) + { + let gl; + + if (settings.PREFER_WEBGL_2) + { + gl = canvas.getContext('webgl2', options); + } + + if (gl) + { + this.webGLVersion = 2; + } + else + { + this.webGLVersion = 1; + + gl = canvas.getContext('webgl', options) + || canvas.getContext('experimental-webgl', options); + + if (!gl) + { + // fail, not able to get a context + throw new Error('This browser does not support webGL. Try using the canvas renderer'); + } + } + + this.gl = gl; + + this.getExtensions(); + + return gl; + } + + getExtensions() + { + // time to set up default etensions that pixi uses.. + const gl = this.gl; + const extensions = this.extensions; + + if (this.webGLVersion === 1) + { + extensions.drawBuffers = gl.getExtension('WEBGL_draw_buffers'); + extensions.depthTexture = gl.getExtension('WEBKIT_WEBGL_depth_texture'); + extensions.floatTexture = gl.getExtension('OES_texture_float'); + extensions.loseContext = gl.getExtension('WEBGL_lose_context'); + + extensions.vertexArrayObject = gl.getExtension('OES_vertex_array_object') + || gl.getExtension('MOZ_OES_vertex_array_object') + || gl.getExtension('WEBKIT_OES_vertex_array_object'); + } + + // we don't use any specific WebGL 2 ones yet! + } + + /** + * Handles a lost webgl context + * + * @private + * @param {WebGLContextEvent} event - The context lost event. + */ + handleContextLost(event) + { + event.preventDefault(); + } + + /** + * Handles a restored webgl context + * + * @private + */ + handleContextRestored() + { + this.renderer.runners.contextChange.run(this.gl); + } + + destroy() + { + const view = this.renderer.view; + + // remove listeners + view.removeEventListener('webglcontextlost', this.handleContextLost); + view.removeEventListener('webglcontextrestored', this.handleContextRestored); + + this.gl.useProgram(null); + + if (this.extensions.loseContext) + { + this.extensions.loseContext.loseContext(); + } + } + + postrender() + { + this.gl.flush(); + } + + validateContext(gl) + { + const attributes = gl.getContextAttributes(); + + // this is going to be fairly simple for now.. but at least we have room to grow! + if (!attributes.stencil) + { + /* eslint-disable max-len */ + + /* eslint-disable no-console */ + console.warn('Provided WebGL context does not have a stencil buffer, masks may not render correctly'); + /* eslint-enable no-console */ + + /* eslint-enable max-len */ + } + } +} diff --git a/packages/core/src/filters/Filter.js b/packages/core/src/filters/Filter.js new file mode 100644 index 0000000..9d8cad8 --- /dev/null +++ b/packages/core/src/filters/Filter.js @@ -0,0 +1,140 @@ +import Shader from '../shader/Shader'; +import Program from '../shader/Program'; +import State from '../state/State'; +import { settings } from '@pixi/settings'; +// import extractUniformsFromSrc from './extractUniformsFromSrc'; +import defaultVertex from './defaultFilter.vert'; +import defaultFragment from './defaultFilter.frag'; + +// let math = require('../../math'); +/** + * @class + * @memberof PIXI + * @extends PIXI.Shader + */ +export default class Filter extends Shader +{ + /** + * @param {string} [vertexSrc] - The source of the vertex shader. + * @param {string} [fragmentSrc] - The source of the fragment shader. + * @param {object} [uniforms] - Custom uniforms to use to augment the built-in ones. + */ + constructor(vertexSrc, fragmentSrc, uniforms) + { + const program = Program.from(vertexSrc, fragmentSrc); + + super(program, uniforms); + + /** + * The padding of the filter. Some filters require extra space to breath such as a blur. + * Increasing this will add extra width and height to the bounds of the object that the + * filter is applied to. + * + * @member {number} + */ + this.padding = 0; + + /** + * The resolution of the filter. Setting this to be lower will lower the quality but + * increase the performance of the filter. + * + * @member {number} + */ + this.resolution = settings.RESOLUTION; + + /** + * If enabled is true the filter is applied, if false it will not. + * + * @member {boolean} + */ + this.enabled = true; + + /** + * If enabled, PixiJS will fit the filter area into boundaries for better performance. + * Switch it off if it does not work for specific shader. + * + * @member {boolean} + */ + this.autoFit = true; + + /** + * Legacy filters use position and uvs from attributes + * @member {boolean} + * @readonly + */ + this.legacy = !!this.program.attributeData.aTextureCoord; + + /** + * the webGL state the filter requires to render + * @member {PIXI.State} + */ + this.state = new State(); + } + + /** + * Applies the filter + * + * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + * @param {boolean} clear - Should the output be cleared before rendering to it + * @param {object} [currentState] - It's current state of filter. + * There are some useful properties in the currentState : + * target, filters, sourceFrame, destinationFrame, renderTarget, resolution + */ + apply(filterManager, input, output, clear, currentState, derp) // eslint-disable-line no-unused-vars + { + // do as you please! + + filterManager.applyFilter(this, input, output, clear, currentState, derp); + + // or just do a regular render.. + } + + /** + * Sets the blendmode of the filter + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL + */ + get blendMode() + { + return this.state.blendMode; + } + + set blendMode(value) // eslint-disable-line require-jsdoc + { + this.state.blendMode = value; + } + + /** + * The default vertex shader source + * + * @static + * @constant + */ + static get defaultVertexSrc() + { + return defaultVertex; + } + + /** + * The default fragment shader source + * + * @static + * @constant + */ + static get defaultFragmentSrc() + { + return defaultFragment; + } +} + +/** + * Used for caching shader IDs + * + * @static + * @private + */ +Filter.SOURCE_KEY_MAP = {}; + diff --git a/packages/core/src/filters/FilterSystem.js b/packages/core/src/filters/FilterSystem.js new file mode 100644 index 0000000..3174318 --- /dev/null +++ b/packages/core/src/filters/FilterSystem.js @@ -0,0 +1,431 @@ +import System from '../System'; + +import RenderTexture from '../renderTexture/RenderTexture'; +import Quad from '../utils/Quad'; +import QuadUv from '../utils/QuadUv'; +import { Rectangle } from '@pixi/math'; +import * as filterTransforms from './filterTransforms'; +import bitTwiddle from 'bit-twiddle'; +import UniformGroup from '../shader/UniformGroup'; +import { DRAW_MODES } from '@pixi/constants'; + +// +/** + * @ignore + * @class + */ +class FilterState +{ + /** + * + */ + constructor() + { + this.renderTexture = null; + this.sourceFrame = new Rectangle(); + this.destinationFrame = new Rectangle(); + this.filters = []; + this.target = null; + this.legacy = false; + this.resolution = 1; + } +} + +/** + * @class + * @memberof PIXI.systems + * @extends PIXI.System + */ +export default class FilterSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + /** + * stores a bunch of PO2 textures used for filtering + * @type {Object} + */ + this.texturePool = {}; + + /** + * a pool for storing filter states, save us creating new ones each tick + * @type {Array} + */ + this.statePool = []; + + /** + * A very simple geometry used when drawing a filter effect to the screen + * @type {Quad} + */ + this.quad = new Quad(); + + this.quadUv = new QuadUv(); + + /** + * Temporary rect for maths + * @type {PIXI.Rectangle} + */ + this.tempRect = new Rectangle(); + + this.activeState = {}; + + /** + * this uniform group is attached to filter uniforms when used + * @type {UniformGroup} + */ + this.globalUniforms = new UniformGroup({ + sourceFrame: this.tempRect, + destinationFrame: this.tempRect, + + // legacy variables + filterArea: new Float32Array(4), + filterClamp: new Float32Array(4), + }, true); + } + + /** + * Adds a new filter to the System. + * + * @param {PIXI.DisplayObject} target - The target of the filter to render. + * @param {PIXI.Filter[]} filters - The filters to apply. + */ + push(target, filters) + { + const renderer = this.renderer; + const filterStack = this.renderer.renderTexture.defaultFilterStack; + const state = this.statePool.pop() || new FilterState(); + + let resolution = filters[0].resolution; + let padding = filters[0].padding; + let autoFit = filters[0].autoFit; + let legacy = filters[0].legacy; + + for (let i = 1; i < filters.length; i++) + { + const filter = filters[i]; + + // lets use the lowest resolution.. + resolution = Math.min(resolution, filter.resolution); + // and the largest amount of padding! + padding = Math.max(padding, filter.padding); + // only auto fit if all filters are autofit + autoFit = autoFit || filter.autoFit; + + legacy = legacy || filter.legacy; + } + + filterStack.push(state); + + state.resolution = resolution; + + state.legacy = legacy; + + // round to whole number based on resolution + // TODO move that to the shader too? + state.sourceFrame = target.filterArea || target.getBounds(true); + + state.sourceFrame.pad(padding); + + if (autoFit) + { + state.sourceFrame.fit(this.renderer.renderTexture.destinationFrame); + } + + state.sourceFrame.round(resolution); + + state.renderTexture = this.getPotFilterTexture(state.sourceFrame.width, state.sourceFrame.height, resolution); + state.filters = filters; + + state.destinationFrame.width = state.renderTexture.width; + state.destinationFrame.height = state.renderTexture.height; + + state.renderTexture.filterFrame = state.sourceFrame; + renderer.renderTexture.bind(state.renderTexture, state.sourceFrame);// /, state.destinationFrame); + renderer.renderTexture.clear(); + } + + /** + * Pops off the filter and applies it. + * + */ + pop() + { + const renderer = this.renderer; + const filterStack = renderer.renderTexture.defaultFilterStack; + const state = filterStack.pop(); + const filters = state.filters; + + this.activeState = state; + + const globalUniforms = this.globalUniforms.uniforms; + + globalUniforms.sourceFrame = state.sourceFrame; + globalUniforms.destinationFrame = state.destinationFrame; + globalUniforms.resolution = state.resolution; + + // only update the rect if its legacy.. + if (state.legacy) + { + const filterArea = globalUniforms.filterArea; + const filterClamp = globalUniforms.filterClamp; + + filterArea[0] = state.destinationFrame.width; + filterArea[1] = state.destinationFrame.height; + filterArea[2] = state.sourceFrame.x; + filterArea[3] = state.sourceFrame.y; + + filterClamp[0] = 0.5 / state.resolution / state.destinationFrame.width; + filterClamp[1] = 0.5 / state.resolution / state.destinationFrame.height; + filterClamp[2] = (state.sourceFrame.width - 0.5) / state.resolution / state.destinationFrame.width; + filterClamp[3] = (state.sourceFrame.height - 0.5) / state.resolution / state.destinationFrame.height; + } + + this.globalUniforms.update(); + + const lastState = filterStack[filterStack.length - 1]; + + if (filters.length === 1) + { + filters[0].apply(this, state.renderTexture, lastState.renderTexture, false, state); + + this.returnFilterTexture(state.renderTexture); + } + else + { + let flip = state.renderTexture; + let flop = this.getPotFilterTexture( + flip.width, + flip.height, + state.resolution + ); + + flop.filterFrame = flip.filterFrame; + + let i = 0; + + for (i = 0; i < filters.length - 1; ++i) + { + filters[i].apply(this, flip, flop, true, state); + + const t = flip; + + flip = flop; + flop = t; + } + + filters[i].apply(this, flip, lastState.renderTexture, false, state); + + this.returnFilterTexture(flip); + this.returnFilterTexture(flop); + } + + this.statePool.push(state); + } + + /** + * Draws a filter. + * + * @param {PIXI.Filter} filter - The filter to draw. + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + * @param {boolean} clear - Should the output be cleared before rendering to it + */ + applyFilter(filter, input, output, clear) + { + const renderer = this.renderer; + + renderer.renderTexture.bind(output, output ? output.filterFrame : null); + + if (clear) + { + // gl.disable(gl.SCISSOR_TEST); + renderer.renderTexture.clear(); + // gl.enable(gl.SCISSOR_TEST); + } + + // set the uniforms.. + filter.uniforms.uSampler = input; + filter.uniforms.filterGlobals = this.globalUniforms; + + // TODO make it so that the order of this does not matter.. + // because it does at the moment cos of global uniforms. + // they need to get resynced + + renderer.state.setState(filter.state); + renderer.shader.bind(filter); + + if (filter.legacy) + { + this.quadUv.map(input._frame, input.filterFrame); + + renderer.geometry.bind(this.quadUv); + renderer.geometry.draw(DRAW_MODES.TRIANGLES); + } + else + { + renderer.geometry.bind(this.quad); + renderer.geometry.draw(DRAW_MODES.TRIANGLE_STRIP); + } + } + + /** + * Calculates the mapped matrix. + * + * TODO playing around here.. this is temporary - (will end up in the shader) + * this returns a matrix that will normalise map filter cords in the filter to screen space + * + * @param {PIXI.Matrix} outputMatrix - the matrix to output to. + * @return {PIXI.Matrix} The mapped matrix. + */ + calculateScreenSpaceMatrix(outputMatrix) + { + const currentState = this.activeState; + + return filterTransforms.calculateScreenSpaceMatrix( + outputMatrix, + currentState.sourceFrame, + currentState.destinationFrame + ); + } + + /** + * This will map the filter coord so that a texture can be used based on the transform of a sprite + * + * @param {PIXI.Matrix} outputMatrix - The matrix to output to. + * @param {PIXI.Sprite} sprite - The sprite to map to. + * @return {PIXI.Matrix} The mapped matrix. + */ + calculateSpriteMatrix(outputMatrix, sprite) + { + const currentState = this.activeState; + + return filterTransforms.calculateSpriteMatrix( + outputMatrix, + currentState.sourceFrame, + currentState.destinationFrame, + sprite + ); + } + + /** + * Destroys this Filter System. + * + * @param {boolean} [contextLost=false] context was lost, do not free shaders + * + */ + destroy(contextLost = false) + { + if (!contextLost) + { + this.emptyPool(); + } + else + { + this.texturePool = {}; + } + } + + /** + * Gets a Power-of-Two render texture. + * + * TODO move to a seperate class could be on renderer? + * also - could cause issue with multiple contexts? + * + * @private + * @param {WebGLRenderingContext} gl - The webgl rendering context + * @param {number} minWidth - The minimum width of the render target. + * @param {number} minHeight - The minimum height of the render target. + * @param {number} resolution - The resolution of the render target. + * @return {PIXI.RenderTarget} The new render target. + */ + getPotFilterTexture(minWidth, minHeight, resolution) + { + minWidth = bitTwiddle.nextPow2(minWidth); + minHeight = bitTwiddle.nextPow2(minHeight); + resolution = resolution || 1; + + const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + + if (!this.texturePool[key]) + { + this.texturePool[key] = []; + } + + let renderTexture = this.texturePool[key].pop(); + + if (!renderTexture) + { + // temporary bypass cache.. + // internally - this will cause a texture to be bound.. + renderTexture = RenderTexture.create({ + width: minWidth, + height: minHeight, + resolution, + }); + } + + return renderTexture; + } + + /** + * Gets extra render texture to use inside current filter + * + * @param {number} resolution resolution of the renderTexture + * @returns {PIXI.RenderTexture} + */ + getFilterTexture(resolution) + { + const rt = this.activeState.renderTexture; + + const filterTexture = this.getPotFilterTexture(rt.width, rt.height, resolution || rt.baseTexture.resolution); + + filterTexture.filterFrame = rt.filterFrame; + + return filterTexture; + } + + /** + * Frees a render texture back into the pool. + * + * @param {PIXI.RenderTarget} renderTexture - The renderTarget to free + */ + returnFilterTexture(renderTexture) + { + renderTexture.filterFrame = null; + + const base = renderTexture.baseTexture; + + const minWidth = base.width; + const minHeight = base.height; + + const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + + this.texturePool[key].push(renderTexture); + } + + /** + * Empties the texture pool. + * + */ + emptyPool() + { + for (const i in this.texturePool) + { + const textures = this.texturePool[i]; + + if (textures) + { + for (let j = 0; j < textures.length; j++) + { + textures[j].destroy(true); + } + } + } + + this.texturePool = {}; + } +} diff --git a/packages/core/src/filters/defaultFilter.frag b/packages/core/src/filters/defaultFilter.frag new file mode 100644 index 0000000..a14c1be --- /dev/null +++ b/packages/core/src/filters/defaultFilter.frag @@ -0,0 +1,22 @@ +varying vec2 vTextureCoord; +varying vec2 vFilterCoord; + +uniform sampler2D uSampler; +uniform sampler2D filterSampler; + +void main(void){ + vec4 masky = texture2D(filterSampler, vFilterCoord); + vec4 sample = texture2D(uSampler, vTextureCoord); + vec4 color; + if(mod(vFilterCoord.x, 1.0) > 0.5) + { + color = vec4(1.0, 0.0, 0.0, 1.0); + } + else + { + color = vec4(0.0, 1.0, 0.0, 1.0); + } + // gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0); + gl_FragColor = mix(sample, masky, 0.5); + gl_FragColor *= sample.a; +} diff --git a/packages/core/src/filters/defaultFilter.vert b/packages/core/src/filters/defaultFilter.vert new file mode 100644 index 0000000..e1febca --- /dev/null +++ b/packages/core/src/filters/defaultFilter.vert @@ -0,0 +1,14 @@ +attribute vec2 aVertexPosition; +attribute vec2 aTextureCoord; + +uniform mat3 projectionMatrix; +uniform mat3 filterMatrix; + +varying vec2 vTextureCoord; +varying vec2 vFilterCoord; + +void main(void){ + gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); + vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy; + vTextureCoord = aTextureCoord ; +} diff --git a/packages/core/src/filters/filterTransforms.js b/packages/core/src/filters/filterTransforms.js new file mode 100644 index 0000000..87a3cd6 --- /dev/null +++ b/packages/core/src/filters/filterTransforms.js @@ -0,0 +1,50 @@ +import { Matrix } from '@pixi/math'; + +/** + * Calculates the mapped matrix + * @param filterArea {Rectangle} The filter area + * @param sprite {Sprite} the target sprite + * @param outputMatrix {Matrix} @alvin + * @private + */ +// this returns a matrix that will normalise map filter cords in the filter to screen space +export function calculateScreenSpaceMatrix(outputMatrix, filterArea, textureSize) +{ + // TODO unwrap? + const mappedMatrix = outputMatrix.identity(); + + mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); + + mappedMatrix.scale(textureSize.width, textureSize.height); + + return mappedMatrix; +} + +export function calculateNormalizedScreenSpaceMatrix(outputMatrix, filterArea, textureSize) +{ + const mappedMatrix = outputMatrix.identity(); + + mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); + + const translateScaleX = (textureSize.width / filterArea.width); + const translateScaleY = (textureSize.height / filterArea.height); + + mappedMatrix.scale(translateScaleX, translateScaleY); + + return mappedMatrix; +} + +// this will map the filter coord so that a texture can be used based on the transform of a sprite +export function calculateSpriteMatrix(outputMatrix, filterArea, textureSize, sprite) +{ + const orig = sprite._texture.orig; + const mappedMatrix = outputMatrix.set(textureSize.width, 0, 0, textureSize.height, filterArea.x, filterArea.y); + const worldTransform = sprite.worldTransform.copyTo(Matrix.TEMP_MATRIX); + + worldTransform.invert(); + mappedMatrix.prepend(worldTransform); + mappedMatrix.scale(1.0 / orig.width, 1.0 / orig.height); + mappedMatrix.translate(sprite.anchor.x, sprite.anchor.y); + + return mappedMatrix; +} diff --git a/packages/core/src/filters/spriteMask/SpriteMaskFilter.js b/packages/core/src/filters/spriteMask/SpriteMaskFilter.js new file mode 100644 index 0000000..91697b7 --- /dev/null +++ b/packages/core/src/filters/spriteMask/SpriteMaskFilter.js @@ -0,0 +1,63 @@ +import Filter from '../Filter'; +import { Matrix } from '@pixi/math'; +import vertex from './spriteMaskFilter.vert'; +import fragment from './spriteMaskFilter.frag'; +import { default as TextureMatrix } from '../../textures/TextureMatrix'; + +/** + * The SpriteMaskFilter class + * + * @class + * @extends PIXI.Filter + * @memberof PIXI + */ +export default class SpriteMaskFilter extends Filter +{ + /** + * @param {PIXI.Sprite} sprite - the target sprite + */ + constructor(sprite) + { + const maskMatrix = new Matrix(); + + super(vertex, fragment); + + sprite.renderable = false; + + this.maskSprite = sprite; + this.maskMatrix = maskMatrix; + } + + /** + * Applies the filter + * + * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + */ + apply(filterManager, input, output) + { + const maskSprite = this.maskSprite; + const tex = this.maskSprite.texture; + + if (!tex.valid) + { + return; + } + if (!tex.transform) + { + // margin = 0.0, let it bleed a bit, shader code becomes easier + // assuming that atlas textures were made with 1-pixel padding + tex.transform = new TextureMatrix(tex, 0.0); + } + tex.transform.update(); + + this.uniforms.mask = tex; + this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite) + .prepend(tex.transform.mapCoord); + this.uniforms.alpha = maskSprite.worldAlpha; + this.uniforms.maskClamp = tex.transform.uClampFrame; + + filterManager.applyFilter(this, input, output); + } +} diff --git a/packages/core/src/filters/spriteMask/spriteMaskFilter.frag b/packages/core/src/filters/spriteMask/spriteMaskFilter.frag new file mode 100644 index 0000000..0e4aef8 --- /dev/null +++ b/packages/core/src/filters/spriteMask/spriteMaskFilter.frag @@ -0,0 +1,23 @@ +varying vec2 vMaskCoord; +varying vec2 vTextureCoord; + +uniform sampler2D uSampler; +uniform sampler2D mask; +uniform float alpha; +uniform vec4 maskClamp; + +void main(void) +{ + float clip = step(3.5, + step(maskClamp.x, vMaskCoord.x) + + step(maskClamp.y, vMaskCoord.y) + + step(vMaskCoord.x, maskClamp.z) + + step(vMaskCoord.y, maskClamp.w)); + + vec4 original = texture2D(uSampler, vTextureCoord); + vec4 masky = texture2D(mask, vMaskCoord); + + original *= (masky.r * masky.a * alpha * clip); + + gl_FragColor = original; +} diff --git a/packages/core/src/filters/spriteMask/spriteMaskFilter.vert b/packages/core/src/filters/spriteMask/spriteMaskFilter.vert new file mode 100644 index 0000000..d23a30c --- /dev/null +++ b/packages/core/src/filters/spriteMask/spriteMaskFilter.vert @@ -0,0 +1,16 @@ +attribute vec2 aVertexPosition; +attribute vec2 aTextureCoord; + +uniform mat3 projectionMatrix; +uniform mat3 otherMatrix; + +varying vec2 vMaskCoord; +varying vec2 vTextureCoord; + +void main(void) +{ + gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); + + vTextureCoord = aTextureCoord; + vMaskCoord = ( otherMatrix * vec3( aTextureCoord, 1.0) ).xy; +} diff --git a/packages/core/src/framebuffer/FrameBuffer.js b/packages/core/src/framebuffer/FrameBuffer.js new file mode 100644 index 0000000..84e9fcc --- /dev/null +++ b/packages/core/src/framebuffer/FrameBuffer.js @@ -0,0 +1,106 @@ +import Texture from '../textures/BaseTexture'; +import { FORMATS, TYPES } from '@pixi/constants'; + +/** + * Frame buffer + * @class + * @memberof PIXI + */ +export default class FrameBuffer +{ + constructor(width, height) + { + this.width = width || 100; + this.height = height || 100; + + this.stencil = false; + this.depth = false; + + this.dirtyId = 0; + this.dirtyFormat = 0; + this.dirtySize = 0; + + this.depthTexture = null; + this.colorTextures = []; + + this.glFrameBuffers = {}; + } + + get colorTexture() + { + return this.colorTextures[0]; + } + + addColorTexture(index, texture) + { + // TODO add some validation to the texture - same width / height etc? + this.colorTextures[index || 0] = texture || new Texture(null, { scaleMode: 0, + resolution: 1, + mipmap: false, + width: this.width, + height: this.height });// || new Texture(); + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + addDepthTexture(texture) + { + /* eslint-disable max-len */ + this.depthTexture = texture || new Texture(null, { scaleMode: 0, + resolution: 1, + width: this.width, + height: this.height, + mipmap: false, + format: FORMATS.DEPTH_COMPONENT, + type: TYPES.UNSIGNED_SHORT });// UNSIGNED_SHORT; + /* eslint-disable max-len */ + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + enableDepth() + { + this.depth = true; + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + enableStencil() + { + this.stencil = true; + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + resize(width, height) + { + if (width === this.width && height === this.height) return; + + this.width = width; + this.height = height; + + this.dirtyId++; + this.dirtySize++; + + for (let i = 0; i < this.colorTextures.length; i++) + { + this.colorTextures[i].setSize(width, height); + } + + if (this.depthTexture) + { + this.depthTexture.setSize(width, height); + } + } +} diff --git a/packages/core/src/framebuffer/FramebufferSystem.js b/packages/core/src/framebuffer/FramebufferSystem.js new file mode 100644 index 0000000..2cc2437 --- /dev/null +++ b/packages/core/src/framebuffer/FramebufferSystem.js @@ -0,0 +1,246 @@ +import System from '../System'; +import { Rectangle } from '@pixi/math'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class FramebufferSystem extends System +{ + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + contextChange() + { + this.gl = this.renderer.gl; + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + this.current = null; + this.viewport = new Rectangle(); + + this.drawBufferExtension = this.renderer.context.extensions.drawBuffers; + } + + bind(framebuffer, frame) + { + const gl = this.gl; + + this.current = framebuffer; + + if (framebuffer) + { + // TODO cacheing layer! + + const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID] || this.initFramebuffer(framebuffer); + + gl.bindFramebuffer(gl.FRAMEBUFFER, fbo.framebuffer); + // makesure all textures are unbound.. + + // now check for updates... + if (fbo.dirtyId !== framebuffer.dirtyId) + { + fbo.dirtyId = framebuffer.dirtyId; + + if (fbo.dirtyFormat !== framebuffer.dirtyFormat) + { + fbo.dirtyFormat = framebuffer.dirtyFormat; + this.updateFramebuffer(framebuffer); + } + else if (fbo.dirtySize !== framebuffer.dirtySize) + { + fbo.dirtySize = framebuffer.dirtySize; + this.resizeFramebuffer(framebuffer); + } + } + + for (let i = 0; i < framebuffer.colorTextures.length; i++) + { + if (framebuffer.colorTextures[i].texturePart) + { + this.renderer.texture.unbind(framebuffer.colorTextures[i].texture); + } + else + { + this.renderer.texture.unbind(framebuffer.colorTextures[i]); + } + } + + if (framebuffer.depthTexture) + { + this.renderer.texture.unbind(framebuffer.depthTexture); + } + + if (frame) + { + this.setViewport(frame.x, frame.y, frame.width, frame.height); + } + else + { + this.setViewport(0, 0, framebuffer.width, framebuffer.height); + } + } + else + { + gl.bindFramebuffer(gl.FRAMEBUFFER, null); + + if (frame) + { + this.setViewport(frame.x, frame.y, frame.width, frame.height); + } + else + { + this.setViewport(0, 0, this.renderer.width, this.renderer.height); + } + } + } + + setViewport(x, y, width, height) + { + const v = this.viewport; + + if (v.width !== width || v.height !== height || v.x !== x || v.y !== y) + { + v.x = x; + v.y = y; + v.width = width; + v.height = height; + + this.gl.viewport(x, y, width, height); + } + } + + get size() + { + if (this.current) + { + // TODO store temp + return { x: 0, y: 0, width: this.current.width, height: this.current.height }; + } + + return { x: 0, y: 0, width: this.renderer.width, height: this.renderer.height }; + } + + clear(r, g, b, a) + { + const gl = this.gl; + + // TODO clear color can be set only one right? + gl.clearColor(r, g, b, a); + gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); + } + + // private functions... + + initFramebuffer(framebuffer) + { + const gl = this.gl; + + // TODO - make this a class? + const fbo = { + framebuffer: gl.createFramebuffer(), + stencil: null, + dirtyId: 0, + dirtyFormat: 0, + dirtySize: 0, + }; + + framebuffer.glFrameBuffers[this.CONTEXT_UID] = fbo; + + return fbo; + } + + resizeFramebuffer(framebuffer) + { + const gl = this.gl; + + if (framebuffer.stencil || framebuffer.depth) + { + gl.bindRenderbuffer(gl.RENDERBUFFER, this.stencil); + gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); + } + } + + updateFramebuffer(framebuffer) + { + const gl = this.gl; + + const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID]; + + // bind the color texture + const colorTextures = framebuffer.colorTextures; + + let count = colorTextures.length; + + if (!this.drawBufferExtension) + { + count = Math.min(count, 1); + } + + const activeTextures = []; + + for (let i = 0; i < count; i++) + { + const texture = framebuffer.colorTextures[i]; + + if (texture.texturePart) + { + this.renderer.texture.bind(texture.texture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.COLOR_ATTACHMENT0 + i, + gl.TEXTURE_CUBE_MAP_NEGATIVE_X + texture.side, + texture.texture._glTextures[this.CONTEXT_UID].texture, + 0); + } + else + { + this.renderer.texture.bind(texture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.COLOR_ATTACHMENT0 + i, + gl.TEXTURE_2D, + texture._glTextures[this.CONTEXT_UID].texture, + 0); + } + + activeTextures.push(gl.COLOR_ATTACHMENT0 + i); + } + + if (this.drawBufferExtension && activeTextures.length > 1) + { + this.drawBufferExtension.drawBuffersWEBGL(activeTextures); + } + + if (framebuffer.depthTexture) + { + const depthTextureExt = this.renderer.context.extensions.depthTexture; + + if (depthTextureExt) + { + const depthTexture = framebuffer.depthTexture; + + this.renderer.texture.bind(depthTexture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.DEPTH_ATTACHMENT, + gl.TEXTURE_2D, + depthTexture._glTextures[this.CONTEXT_UID].texture, + 0); + } + } + + if (framebuffer.stencil || framebuffer.depth) + { + fbo.stencil = gl.createRenderbuffer(); + + gl.bindRenderbuffer(gl.RENDERBUFFER, fbo.stencil); + + // TODO.. this is depth AND stencil? + gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, fbo.stencil); + gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); + // fbo.enableStencil(); + } + } +} diff --git a/packages/core/src/geometry/GLBuffer.js b/packages/core/src/geometry/GLBuffer.js new file mode 100644 index 0000000..0477e8a --- /dev/null +++ b/packages/core/src/geometry/GLBuffer.js @@ -0,0 +1,9 @@ +export default class GLBuffer +{ + constructor(buffer) + { + this.buffer = buffer; + this.updateID = -1; + this.byteLength = -1; + } +} diff --git a/packages/core/src/geometry/GeometrySystem.js b/packages/core/src/geometry/GeometrySystem.js new file mode 100644 index 0000000..959f827 --- /dev/null +++ b/packages/core/src/geometry/GeometrySystem.js @@ -0,0 +1,388 @@ +import System from '../System'; +import GLBuffer from './GLBuffer'; + +const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class GeometrySystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this._activeGeometry = null; + this._activeVao = null; + + this.hasVao = true; + this.hasInstance = true; + } + + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + contextChange() + { + const gl = this.gl = this.renderer.gl; + + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + + // webgl2 + if (!gl.createVertexArray) + { + // webgl 1! + let nativeVaoExtension = this.renderer.context.extensions.vertexArrayObject; + + if (this.renderer.legacy) + { + nativeVaoExtension = null; + } + + if (nativeVaoExtension) + { + gl.createVertexArray = () => + nativeVaoExtension.createVertexArrayOES(); + + gl.bindVertexArray = (vao) => + nativeVaoExtension.bindVertexArrayOES(vao); + + gl.deleteVertexArray = (vao) => + nativeVaoExtension.deleteVertexArrayOES(vao); + } + else + { + this.hasVao = false; + gl.createVertexArray = () => + { + // empty + }; + + gl.bindVertexArray = () => + { + // empty + }; + + gl.deleteVertexArray = () => + { + // empty + }; + } + } + + if (!gl.vertexAttribDivisor) + { + const instanceExt = gl.getExtension('ANGLE_instanced_arrays'); + + if (instanceExt) + { + gl.vertexAttribDivisor = (a, b) => + instanceExt.vertexAttribDivisorANGLE(a, b); + + gl.drawElementsInstanced = (a, b, c, d, e) => + instanceExt.drawElementsInstancedANGLE(a, b, c, d, e); + + gl.drawArraysInstanced = (a, b, c, d) => + instanceExt.drawArraysInstancedANGLE(a, b, c, d); + } + else + { + this.hasInstance = false; + } + } + } + + /** + * Binds geometry so that is can be drawn. Creating a Vao if required + * @private + * @param {PIXI.Geometry} geometry instance of geometry to bind + */ + bind(geometry, shader) + { + shader = shader || this.renderer.shader.shader; + + const gl = this.gl; + + // not sure the best way to address this.. + // currently different shaders require different VAOs for the same geometry + // Still mulling over the best way to solve this one.. + // will likely need to modify the shader attribute locations at run time! + let vaos = geometry.glVertexArrayObjects[this.CONTEXT_UID]; + + if (!vaos) + { + geometry.glVertexArrayObjects[this.CONTEXT_UID] = vaos = {}; + } + + const vao = vaos[shader.program.id] || this.initGeometryVao(geometry, shader.program); + + this._activeGeometry = geometry; + + if (this._activeVao !== vao) + { + this._activeVao = vao; + + if (this.hasVao) + { + gl.bindVertexArray(vao); + } + else + { + this.activateVao(geometry, shader.program); + } + } + + // TODO - optimise later! + // don't need to loop through if nothing changed! + // maybe look to add an 'autoupdate' to geometry? + this.updateBuffers(); + } + + reset() + { + this.unbind(); + } + + updateBuffers() + { + const geometry = this._activeGeometry; + const gl = this.gl; + + for (let i = 0; i < geometry.buffers.length; i++) + { + const buffer = geometry.buffers[i]; + + const glBuffer = buffer._glBuffers[this.CONTEXT_UID]; + + if (buffer._updateID !== glBuffer.updateID) + { + glBuffer.updateID = buffer._updateID; + + // TODO can cache this on buffer! maybe added a getter / setter? + const type = buffer.index ? gl.ELEMENT_ARRAY_BUFFER : gl.ARRAY_BUFFER; + const drawType = buffer.static ? gl.STATIC_DRAW : gl.DYNAMIC_DRAW; + + gl.bindBuffer(type, glBuffer.buffer); + + if (glBuffer.byteLength >= buffer.data.byteLength) + { + // offset is always zero for now! + gl.bufferSubData(type, 0, buffer.data); + } + else + { + gl.bufferData(type, buffer.data, drawType); + } + } + } + } + + checkCompatability(geometry, program) + { + // geometry must have at least all the attributes that the shader requires. + const geometryAttributes = geometry.attributes; + const shaderAttributes = program.attributeData; + + for (const j in shaderAttributes) + { + if (!geometryAttributes[j]) + { + throw new Error(`shader and geometry incompatible, geometry missing the "${j}" attribute`); + } + } + } + + /** + * Creates a Vao with the same structure as the geometry and stores it on the geometry. + * @private + * @param {PIXI.Geometry} geometry instance of geometry to to generate Vao for + */ + initGeometryVao(geometry, program) + { + this.checkCompatability(geometry, program); + + const gl = this.gl; + const CONTEXT_UID = this.CONTEXT_UID; + const buffers = geometry.buffers; + const attributes = geometry.attributes; + + const tempStride = {}; + const tempStart = {}; + + for (const j in buffers) + { + tempStride[j] = 0; + tempStart[j] = 0; + } + + for (const j in attributes) + { + if (!attributes[j].size && program.attributeData[j]) + { + attributes[j].size = program.attributeData[j].size; + } + + tempStride[attributes[j].buffer] += attributes[j].size * byteSizeMap[attributes[j].type]; + } + + for (const j in attributes) + { + const attribute = attributes[j]; + const attribSize = attribute.size; + + if (attribute.stride === undefined) + { + if (tempStride[attribute.buffer] === attribSize * byteSizeMap[attribute.type]) + { + attribute.stride = 0; + } + else + { + attribute.stride = tempStride[attribute.buffer]; + } + } + + if (attribute.start === undefined) + { + attribute.start = tempStart[attribute.buffer]; + + tempStart[attribute.buffer] += attribSize * byteSizeMap[attribute.type]; + } + } + + // first update - and create the buffers! + // only create a gl buffer if it actually gets + for (let i = 0; i < buffers.length; i++) + { + const buffer = buffers[i]; + + if (!buffer._glBuffers[CONTEXT_UID]) + { + buffer._glBuffers[CONTEXT_UID] = new GLBuffer(gl.createBuffer()); + } + } + + // TODO - maybe make this a data object? + // lets wait to see if we need to first! + const vao = gl.createVertexArray(); + + gl.bindVertexArray(vao); + + this.activateVao(geometry, program); + + gl.bindVertexArray(null); + + // add it to the cache! + geometry.glVertexArrayObjects[this.CONTEXT_UID][program.id] = vao; + + return vao; + } + + activateVao(geometry, program) + { + const gl = this.gl; + const CONTEXT_UID = this.CONTEXT_UID; + const buffers = geometry.buffers; + const attributes = geometry.attributes; + + if (geometry.indexBuffer) + { + // first update the index buffer if we have one.. + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, geometry.indexBuffer._glBuffers[CONTEXT_UID].buffer); + } + + let lastBuffer = null; + + // add a new one! + for (const j in attributes) + { + const attribute = attributes[j]; + const buffer = buffers[attribute.buffer]; + const glBuffer = buffer._glBuffers[CONTEXT_UID]; + + if (program.attributeData[j]) + { + if (lastBuffer !== glBuffer) + { + gl.bindBuffer(gl.ARRAY_BUFFER, glBuffer.buffer); + + lastBuffer = glBuffer; + } + + const location = program.attributeData[j].location; + + // TODO introduce state again + // we can optimise this for older devices that have no VAOs + gl.enableVertexAttribArray(location); + + gl.vertexAttribPointer(location, + attribute.size, + attribute.type || gl.FLOAT, + attribute.normalized, + attribute.stride, + attribute.start); + + if (attribute.instance) + { + // TODO calculate instance count based of this... + if (this.hasInstance) + { + gl.vertexAttribDivisor(location, 1); + } + else + { + throw new Error('geometry error, GPU Instancing is not supported on this device'); + } + } + } + } + } + + draw(type, size, start, instanceCount) + { + const gl = this.gl; + const geometry = this._activeGeometry; + + // TODO.. this should not change so maybe cache the function? + + if (geometry.indexBuffer) + { + if (geometry.instanced) + { + /* eslint-disable max-len */ + gl.drawElementsInstanced(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2, instanceCount || 1); + /* eslint-enable max-len */ + } + else + { + gl.drawElements(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2); + } + } + else + if (geometry.instanced) + { + // TODO need a better way to calculate size.. + gl.drawArraysInstanced(type, start, size || geometry.getSize(), instanceCount || 1); + } + else + { + gl.drawArrays(type, start, size || geometry.getSize()); + } + + return this; + } + + unbind() + { + this.gl.bindVertexArray(null); + this._activeVao = null; + this._activeGeometry = null; + } +} diff --git a/packages/core/src/geometry/utils/setVertexAttribArrays.js b/packages/core/src/geometry/utils/setVertexAttribArrays.js new file mode 100644 index 0000000..23b6959 --- /dev/null +++ b/packages/core/src/geometry/utils/setVertexAttribArrays.js @@ -0,0 +1,54 @@ +// var GL_MAP = {}; + +/** + * @param gl {WebGLRenderingContext} The current WebGL context + * @param attribs {*} + * @param state {*} + */ +export default function setVertexAttribArrays(gl, attribs, state) +{ + let i; + + if (state) + { + const tempAttribState = state.tempAttribState; + const attribState = state.attribState; + + for (i = 0; i < tempAttribState.length; i++) + { + tempAttribState[i] = false; + } + + // set the new attribs + for (i = 0; i < attribs.length; i++) + { + tempAttribState[attribs[i].attribute.location] = true; + } + + for (i = 0; i < attribState.length; i++) + { + if (attribState[i] !== tempAttribState[i]) + { + attribState[i] = tempAttribState[i]; + + if (state.attribState[i]) + { + gl.enableVertexAttribArray(i); + } + else + { + gl.disableVertexAttribArray(i); + } + } + } + } + else + { + for (i = 0; i < attribs.length; i++) + { + const attrib = attribs[i]; + + gl.enableVertexAttribArray(attrib.attribute.location); + } + } +} diff --git a/packages/core/src/index.js b/packages/core/src/index.js index 58c456a..fd1f547 100644 --- a/packages/core/src/index.js +++ b/packages/core/src/index.js @@ -1,30 +1,30 @@ import * as resources from './textures/resources'; -import * as systems from './renderers/systems'; +import * as systems from './systems'; -export { resources }; export { systems }; +export { resources }; -export { default as Renderer } from './renderers/Renderer'; -export { default as AbstractRenderer } from './renderers/AbstractRenderer'; -export { default as FrameBuffer } from './textures/FrameBuffer'; +export { default as System } from './System'; +export { default as Renderer } from './Renderer'; +export { default as AbstractRenderer } from './AbstractRenderer'; +export { default as FrameBuffer } from './framebuffer/FrameBuffer'; export { default as CubeTexture } from './textures/CubeTexture'; export { default as BaseTexture } from './textures/BaseTexture'; export { default as Texture } from './textures/Texture'; export { default as TextureMatrix } from './textures/TextureMatrix'; -export { default as RenderTexture } from './textures/RenderTexture'; -export { default as BaseRenderTexture } from './textures/BaseRenderTexture'; +export { default as RenderTexture } from './renderTexture/RenderTexture'; +export { default as BaseRenderTexture } from './renderTexture/BaseRenderTexture'; export { default as TextureUvs } from './textures/TextureUvs'; -export { default as State } from './renderers/State'; -export { default as ObjectRenderer } from './renderers/utils/ObjectRenderer'; -export { default as Quad } from './renderers/utils/Quad'; -export { default as QuadUv } from './renderers/utils/QuadUv'; -export { default as checkMaxIfStatmentsInShader } from './renderers/utils/checkMaxIfStatmentsInShader'; +export { default as State } from './state/State'; +export { default as ObjectRenderer } from './batch/ObjectRenderer'; +export { default as Quad } from './utils/Quad'; +export { default as QuadUv } from './utils/QuadUv'; +export { default as checkMaxIfStatmentsInShader } from './shader/utils/checkMaxIfStatmentsInShader'; export { default as Shader } from './shader/Shader'; export { default as Program } from './shader/Program'; export { default as UniformGroup } from './shader/UniformGroup'; -export { default as SpriteMaskFilter } from './renderers/filters/spriteMask/SpriteMaskFilter'; -export { default as Filter } from './renderers/filters/Filter'; +export { default as SpriteMaskFilter } from './filters/spriteMask/SpriteMaskFilter'; +export { default as Filter } from './filters/Filter'; export { default as Attribute } from './geometry/Attribute'; export { default as Buffer } from './geometry/Buffer'; export { default as Geometry } from './geometry/Geometry'; - diff --git a/packages/core/src/mask/MaskSystem.js b/packages/core/src/mask/MaskSystem.js new file mode 100644 index 0000000..99174c2 --- /dev/null +++ b/packages/core/src/mask/MaskSystem.js @@ -0,0 +1,199 @@ +import System from '../System'; +import AlphaMaskFilter from '../filters/spriteMask/SpriteMaskFilter'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class MaskSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + // TODO - we don't need both! + this.scissor = false; + this.scissorData = null; + this.scissorRenderTarget = null; + + this.enableScissor = false; + + this.alphaMaskPool = []; + this.alphaMaskIndex = 0; + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.DisplayObject} target - Display Object to push the mask to + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + push(target, maskData) + { + // TODO the root check means scissor rect will not + // be used on render textures more info here: + // https://github.com/pixijs/pixi.js/pull/3545 + + if (maskData.texture) + { + this.pushSpriteMask(target, maskData); + } + else if (this.enableScissor + && !this.scissor + && this.renderer._activeRenderTarget.root + && !this.renderer.stencil.stencilMaskStack.length + && maskData.isFastRect()) + { + const matrix = maskData.worldTransform; + + let rot = Math.atan2(matrix.b, matrix.a); + + // use the nearest degree! + rot = Math.round(rot * (180 / Math.PI)); + + if (rot % 90) + { + this.pushStencilMask(maskData); + } + else + { + this.pushScissorMask(target, maskData); + } + } + else + { + this.pushStencilMask(maskData); + } + } + + /** + * Removes the last mask from the mask stack and doesn't return it. + * + * @param {PIXI.DisplayObject} target - Display Object to pop the mask from + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + pop(target, maskData) + { + if (maskData.texture) + { + this.popSpriteMask(target, maskData); + } + else if (this.enableScissor && !this.renderer.stencil.stencilMaskStack.length) + { + this.popScissorMask(target, maskData); + } + else + { + this.popStencilMask(target, maskData); + } + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.RenderTarget} target - Display Object to push the sprite mask to + * @param {PIXI.Sprite} maskData - Sprite to be used as the mask + */ + pushSpriteMask(target, maskData) + { + let alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; + + if (!alphaMaskFilter) + { + alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; + } + + alphaMaskFilter[0].resolution = this.renderer.resolution; + alphaMaskFilter[0].maskSprite = maskData; + + // TODO - may cause issues! + target.filterArea = maskData.getBounds(true); + + this.renderer.filterSystem.pushFilter(target, alphaMaskFilter); + + this.alphaMaskIndex++; + } + + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popSpriteMask() + { + this.renderer.filterSystem.popFilter(); + this.alphaMaskIndex--; + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + pushStencilMask(maskData) + { + this.renderer.batch.flush(); + this.renderer.stencil.pushStencil(maskData); + } + + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popStencilMask() + { + // this.renderer.currentRenderer.stop(); + this.renderer.stencil.popStencil(); + } + + /** + * + * @param {PIXI.DisplayObject} target - Display Object to push the mask to + * @param {PIXI.Graphics} maskData - The masking data. + */ + pushScissorMask(target, maskData) + { + maskData.renderable = true; + + const renderTarget = this.renderer._activeRenderTarget; + + const bounds = maskData.getBounds(); + + bounds.fit(renderTarget.size); + maskData.renderable = false; + + this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); + + const resolution = this.renderer.resolution; + + this.renderer.gl.scissor( + bounds.x * resolution, + (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, + bounds.width * resolution, + bounds.height * resolution + ); + + this.scissorRenderTarget = renderTarget; + this.scissorData = maskData; + this.scissor = true; + } + + /** + * + * + */ + popScissorMask() + { + this.scissorRenderTarget = null; + this.scissorData = null; + this.scissor = false; + + // must be scissor! + const gl = this.renderer.gl; + + gl.disable(gl.SCISSOR_TEST); + } +} diff --git a/packages/core/src/mask/StencilSystem.js b/packages/core/src/mask/StencilSystem.js new file mode 100644 index 0000000..62796a5 --- /dev/null +++ b/packages/core/src/mask/StencilSystem.js @@ -0,0 +1,133 @@ +import System from '../System'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class StencilSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + this.stencilMaskStack = []; + } + + /** + * Changes the mask stack that is used by this System. + * + * @param {PIXI.Graphics[]} stencilMaskStack - The mask stack + */ + setMaskStack(stencilMaskStack) + { + const gl = this.renderer.gl; + + if (stencilMaskStack.length !== this.stencilMaskStack.length) + { + if (stencilMaskStack.length === 0) + { + gl.disable(gl.STENCIL_TEST); + } + else + { + gl.enable(gl.STENCIL_TEST); + } + } + + this.stencilMaskStack = stencilMaskStack; + } + + /** + * Applies the Mask and adds it to the current stencil stack. @alvin + * + * @param {PIXI.Graphics} graphics - The mask + */ + pushStencil(graphics) + { + this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); + + // this.renderer._activeRenderTarget.attachStencilBuffer(); + + const gl = this.renderer.gl; + const prevMaskCount = this.stencilMaskStack.length; + + if (prevMaskCount === 0) + { + gl.enable(gl.STENCIL_TEST); + } + + this.stencilMaskStack.push(graphics); + + // Increment the refference stencil value where the new mask overlaps with the old ones. + gl.colorMask(false, false, false, false); + gl.stencilFunc(gl.EQUAL, prevMaskCount, this._getBitwiseMask()); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.INCR); + this.renderer.plugins.graphics.render(graphics); + + this._useCurrent(); + } + + /** + * Removes the last mask from the stencil stack. @alvin + */ + popStencil() + { + this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); + + const gl = this.renderer.gl; + const graphics = this.stencilMaskStack.pop(); + + if (this.stencilMaskStack.length === 0) + { + // the stack is empty! + gl.disable(gl.STENCIL_TEST); + gl.clear(gl.STENCIL_BUFFER_BIT); + gl.clearStencil(0); + } + else + { + // Decrement the refference stencil value where the popped mask overlaps with the other ones + gl.colorMask(false, false, false, false); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.DECR); + this.renderer.plugins.graphics.render(graphics); + + this._useCurrent(); + } + } + + /** + * Setup renderer to use the current stencil data. + */ + _useCurrent() + { + const gl = this.renderer.gl; + + gl.colorMask(true, true, true, true); + gl.stencilFunc(gl.EQUAL, this.stencilMaskStack.length, this._getBitwiseMask()); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP); + } + + /** + * Fill 1s equal to the number of acitve stencil masks. + * + * @return {number} The bitwise mask. + */ + _getBitwiseMask() + { + return (1 << this.stencilMaskStack.length) - 1; + } + + /** + * Destroys the mask stack. + * + */ + destroy() + { + super.destroy(this); + + this.stencilMaskStack = null; + } +} diff --git a/packages/core/src/projection/ProjectionSystem.js b/packages/core/src/projection/ProjectionSystem.js new file mode 100644 index 0000000..6e63b73 --- /dev/null +++ b/packages/core/src/projection/ProjectionSystem.js @@ -0,0 +1,73 @@ +import System from '../System'; +import { Matrix } from '@pixi/math'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class ProjectionSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.projectionMatrix = new Matrix(); + } + + update(destinationFrame, sourceFrame, resolution, root) + { + this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; + this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; + + this.calculateProjection(this.destinationFrame, this.sourceFrame, resolution, root); + + this.renderer.globalUniforms.uniforms.projectionMatrix = this.projectionMatrix; + this.renderer.globalUniforms.update(); + } + + /** + * Updates the projection matrix based on a projection frame (which is a rectangle) + * + * @param {Rectangle} destinationFrame - The destination frame. + * @param {Rectangle} sourceFrame - The source frame. + */ + calculateProjection(destinationFrame, sourceFrame, resolution, root) + { + const pm = this.projectionMatrix; + + // I don't think we will need this line.. + // pm.identity(); + + if (!root) + { + pm.a = (1 / destinationFrame.width * 2) * resolution; + pm.d = (1 / destinationFrame.height * 2) * resolution; + + pm.tx = -1 - (sourceFrame.x * pm.a); + pm.ty = -1 - (sourceFrame.y * pm.d); + } + else + { + pm.a = (1 / destinationFrame.width * 2) * resolution; + pm.d = (-1 / destinationFrame.height * 2) * resolution; + + pm.tx = -1 - (sourceFrame.x * pm.a); + pm.ty = 1 - (sourceFrame.y * pm.d); + } + } + + /** + * Sets the transform of the active render target to the given matrix + * + * @param {PIXI.Matrix} matrix - The transformation matrix + */ + setTransform()// matrix) + { + // this._activeRenderTarget.transform = matrix; + } +} diff --git a/packages/core/src/renderTexture/BaseRenderTexture.js b/packages/core/src/renderTexture/BaseRenderTexture.js new file mode 100644 index 0000000..88eb83b --- /dev/null +++ b/packages/core/src/renderTexture/BaseRenderTexture.js @@ -0,0 +1,137 @@ +import BaseTexture from '../textures/BaseTexture'; +import FrameBuffer from '../framebuffer/FrameBuffer'; + +/** + * A BaseRenderTexture is a special texture that allows any PixiJS display object to be rendered to it. + * + * __Hint__: All DisplayObjects (i.e. Sprites) that render to a BaseRenderTexture should be preloaded + * otherwise black rectangles will be drawn instead. + * + * A BaseRenderTexture takes a snapshot of any Display Object given to its render method. The position + * and rotation of the given Display Objects is ignored. For example: + * + * ```js + * let renderer = PIXI.autoDetectRenderer(1024, 1024, { view: canvas, ratio: 1 }); + * let baseRenderTexture = new PIXI.BaseRenderTexture(renderer, 800, 600); + * let sprite = PIXI.Sprite.fromImage("spinObj_01.png"); + * + * sprite.position.x = 800/2; + * sprite.position.y = 600/2; + * sprite.anchor.x = 0.5; + * sprite.anchor.y = 0.5; + * + * baseRenderTexture.render(sprite); + * ``` + * + * The Sprite in this case will be rendered using its local transform. To render this sprite at 0,0 + * you can clear the transform + * + * ```js + * + * sprite.setTransform() + * + * let baseRenderTexture = new PIXI.BaseRenderTexture(100, 100); + * let renderTexture = new PIXI.RenderTexture(baseRenderTexture); + * + * renderer.render(sprite, renderTexture); // Renders to center of RenderTexture + * ``` + * + * @class + * @extends PIXI.BaseTexture + * @memberof PIXI + */ +export default class BaseRenderTexture extends BaseTexture +{ + /** + * @param {object} [options] + * @param {number} [options.width=100] - The width of the base render texture + * @param {number} [options.height=100] - The height of the base render texture + * @param {PIXI.SCALE_MODES} [options.scaleMode] - See {@link PIXI.SCALE_MODES} for possible values + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the texture being generated + */ + constructor(options) + { + if (typeof options === 'number') + { + /* eslint-disable prefer-rest-params */ + // Backward compatibility of signature + const width = arguments[0]; + const height = arguments[1]; + const scaleMode = arguments[2]; + const resolution = arguments[3]; + + options = { width, height, scaleMode, resolution }; + /* eslint-enable prefer-rest-params */ + } + + super(null, options); + + const { width, height } = options || {}; + + // Set defaults + this.mipmap = false; + this.width = Math.ceil(width) || 100; + this.height = Math.ceil(height) || 100; + this.valid = true; + + /** + * A map of renderer IDs to webgl renderTargets + * + * @private + * @member {object} + */ + // this._glRenderTargets = {}; + + /** + * A reference to the canvas render target (we only need one as this can be shared across renderers) + * + * @private + * @member {object} + */ + this._canvasRenderTarget = null; + + this.clearColor = [0, 0, 0, 0]; + + this.frameBuffer = new FrameBuffer(width * this.resolution, height * this.resolution) + .addColorTexture(0, this); + + // TODO - could this be added the systems? + + /** + * The data structure for the stencil masks + * + * @member {PIXI.Graphics[]} + */ + this.stencilMaskStack = []; + + /** + * The data structure for the filters + * + * @member {PIXI.Graphics[]} + */ + this.filterStack = [{}]; + } + + /** + * Resizes the BaseRenderTexture. + * + * @param {number} width - The width to resize to. + * @param {number} height - The height to resize to. + */ + resize(width, height) + { + width = Math.ceil(width); + height = Math.ceil(height); + this.frameBuffer.resize(width * this.resolution, height * this.resolution); + } + + /** + * Destroys this texture + * + */ + destroy() + { + super.destroy(true); + this.renderer = null; + } +} diff --git a/packages/core/src/renderTexture/RenderTexture.js b/packages/core/src/renderTexture/RenderTexture.js new file mode 100644 index 0000000..f1ecf1f --- /dev/null +++ b/packages/core/src/renderTexture/RenderTexture.js @@ -0,0 +1,153 @@ +import BaseRenderTexture from './BaseRenderTexture'; +import Texture from '../textures/Texture'; + +/** + * A RenderTexture is a special texture that allows any PixiJS display object to be rendered to it. + * + * __Hint__: All DisplayObjects (i.e. Sprites) that render to a RenderTexture should be preloaded + * otherwise black rectangles will be drawn instead. + * + * A RenderTexture takes a snapshot of any Display Object given to its render method. For example: + * + * ```js + * let renderer = PIXI.autoDetectRenderer(1024, 1024, { view: canvas, ratio: 1 }); + * let renderTexture = PIXI.RenderTexture.create(800, 600); + * let sprite = PIXI.Sprite.fromImage("spinObj_01.png"); + * + * sprite.position.x = 800/2; + * sprite.position.y = 600/2; + * sprite.anchor.x = 0.5; + * sprite.anchor.y = 0.5; + * + * renderer.render(sprite, renderTexture); + * ``` + * + * The Sprite in this case will be rendered using its local transform. To render this sprite at 0,0 + * you can clear the transform + * + * ```js + * + * sprite.setTransform() + * + * let renderTexture = new PIXI.RenderTexture.create(100, 100); + * + * renderer.render(sprite, renderTexture); // Renders to center of RenderTexture + * ``` + * + * @class + * @extends PIXI.Texture + * @memberof PIXI + */ +export default class RenderTexture extends Texture +{ + /** + * @param {PIXI.BaseRenderTexture} baseRenderTexture - The renderer used for this RenderTexture + * @param {PIXI.Rectangle} [frame] - The rectangle frame of the texture to show + */ + constructor(baseRenderTexture, frame) + { + // support for legacy.. + let _legacyRenderer = null; + + if (!(baseRenderTexture instanceof BaseRenderTexture)) + { + /* eslint-disable prefer-rest-params, no-console */ + const width = arguments[1]; + const height = arguments[2]; + const scaleMode = arguments[3]; + const resolution = arguments[4]; + + // we have an old render texture.. + console.warn(`Please use RenderTexture.create(${width}, ${height}) instead of the ctor directly.`); + _legacyRenderer = arguments[0]; + /* eslint-enable prefer-rest-params, no-console */ + + frame = null; + baseRenderTexture = new BaseRenderTexture({ + width, + height, + scaleMode, + resolution, + }); + } + + /** + * The base texture object that this texture uses + * + * @member {BaseTexture} + */ + super(baseRenderTexture, frame); + + this.legacyRenderer = _legacyRenderer; + + /** + * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. + * + * @member {boolean} + */ + this.valid = true; + + /** + * FilterSystem temporary storage + * @private + * @member {PIXI.Rectangle} + */ + this.filterFrame = null; + + this._updateUvs(); + } + + /** + * Resizes the RenderTexture. + * + * @param {number} width - The width to resize to. + * @param {number} height - The height to resize to. + * @param {boolean} [resizeBaseTexture=true] - Should the baseTexture.width and height values be resized as well? + */ + resize(width, height, resizeBaseTexture = true) + { + width = Math.ceil(width); + height = Math.ceil(height); + + // TODO - could be not required.. + this.valid = (width > 0 && height > 0); + + this._frame.width = this.orig.width = width; + this._frame.height = this.orig.height = height; + + if (resizeBaseTexture) + { + this.baseTexture.resize(width, height); + } + + this._updateUvs(); + } + + /** + * A short hand way of creating a render texture. + * + * @param {object} [options] - Options + * @param {number} [options.width=100] - The width of the render texture + * @param {number} [options.height=100] - The height of the render texture + * @param {number} [options.scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the texture being generated + * @return {PIXI.RenderTexture} The new render texture + */ + static create(options) + { + // fallback, old-style: create(width, height, scaleMode, resolution) + if (typeof options === 'number') + { + /* eslint-disable prefer-rest-params */ + options = { + width: options, + height: arguments[1], + scaleMode: arguments[2], + resolution: arguments[3], + }; + /* eslint-enable prefer-rest-params */ + } + + return new RenderTexture(new BaseRenderTexture(options)); + } +} diff --git a/packages/core/src/renderTexture/RenderTextureSystem.js b/packages/core/src/renderTexture/RenderTextureSystem.js new file mode 100644 index 0000000..8d0ca61 --- /dev/null +++ b/packages/core/src/renderTexture/RenderTextureSystem.js @@ -0,0 +1,115 @@ +import System from '../System'; +import { Rectangle } from '@pixi/math'; + +const tempRect = new Rectangle(); + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class RenderTextureSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.clearColor = renderer._backgroundColorRgba; + + // TODO moe this property somewhere else! + this.defaultMaskStack = []; + this.defaultFilterStack = [{}]; + + // empty render texture? + this.renderTexture = null; + + this.destinationFrame = new Rectangle(); + } + + bind(renderTexture, sourceFrame, destinationFrame) + { + // TODO - do we want this?? + if (this.renderTexture === renderTexture) return; + this.renderTexture = renderTexture; + + const renderer = this.renderer; + + if (renderTexture) + { + const baseTexture = renderTexture.baseTexture; + + if (!destinationFrame) + { + tempRect.width = baseTexture.realWidth; + tempRect.height = baseTexture.realHeight; + + destinationFrame = tempRect; + } + + if (!sourceFrame) + { + sourceFrame = destinationFrame; + } + + this.renderer.framebuffer.bind(baseTexture.frameBuffer, destinationFrame); + + this.renderer.projection.update(destinationFrame, sourceFrame, baseTexture.resolution, false); + this.renderer.stencil.setMaskStack(baseTexture.stencilMaskStack); + } + else + { + // TODO these validation checks happen deeper down.. + // thing they can be avoided.. + if (!destinationFrame) + { + tempRect.width = renderer.width; + tempRect.height = renderer.height; + + destinationFrame = tempRect; + } + + if (!sourceFrame) + { + sourceFrame = destinationFrame; + } + + renderer.framebuffer.bind(null, destinationFrame); + + // TODO store this.. + this.renderer.projection.update(destinationFrame, sourceFrame, this.renderer.resolution, true); + this.renderer.stencil.setMaskStack(this.defaultMaskStack); + } + + this.destinationFrame.copyFrom(destinationFrame); + } + + /** + * Erases the render texture and fills the drawing area with a colour + * + * @param {number} [clearColor] - The colour + * @return {PIXI.Renderer} Returns itself. + */ + clear(clearColor) + { + if (this.renderTexture) + { + clearColor = clearColor || this.renderTexture.baseTexture.clearColor; + } + else + { + clearColor = clearColor || this.clearColor; + } + + this.renderer.framebuffer.clear(clearColor[0], clearColor[1], clearColor[2], clearColor[3]); + } + + resize()// screenWidth, screenHeight) + { + // resize the root only! + this.bind(null); + } +} diff --git a/packages/core/src/renderers/AbstractRenderer.js b/packages/core/src/renderers/AbstractRenderer.js deleted file mode 100644 index 928670f..0000000 --- a/packages/core/src/renderers/AbstractRenderer.js +++ /dev/null @@ -1,353 +0,0 @@ -import { hex2string, hex2rgb } from '@pixi/utils'; -import { Matrix, Rectangle } from '@pixi/math'; -import { RENDERER_TYPE } from '@pixi/constants'; -import { settings } from '@pixi/settings'; -import { Container } from '@pixi/display'; -import RenderTexture from '../textures/RenderTexture'; -import EventEmitter from 'eventemitter3'; - -const tempMatrix = new Matrix(); - -/** - * 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 EventEmitter - * @memberof PIXI - */ -export default class AbstractRenderer extends EventEmitter -{ - // eslint-disable-next-line valid-jsdoc - /** - * @param {string} system - The name of the system this renderer is for. - * @param {object} [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.transparent=false] - If the render view is transparent, default false - * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false - * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) - * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. The - * resolution of the renderer retina would be 2. - * @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 {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when rendering, - * stopping pixel interpolation. - */ - constructor(system, options, arg2, arg3) - { - super(); - - // Support for constructor(system, screenWidth, screenHeight, options) - if (typeof options === 'number') - { - options = Object.assign({ - width: options, - height: arg2 || settings.RENDER_OPTIONS.height, - }, arg3); - } - - // 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 = RENDERER_TYPE.UNKNOWN; - - /** - * Measurements of the screen. (0, 0, screenWidth, screenHeight) - * - * Its safe to use as filterArea or hitArea for 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 1 - */ - this.resolution = options.resolution || settings.RESOLUTION; - - /** - * Whether the render view is transparent - * - * @member {boolean} - */ - this.transparent = options.transparent; - - /** - * Whether css dimensions of canvas view should be resized to screen dimensions automatically - * - * @member {boolean} - */ - this.autoResize = options.autoResize || false; - - /** - * Tracks the blend modes useful for this renderer. - * - * @member {object} - */ - this.blendModes = null; - - /** - * 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; - - /** - * If true PixiJS will Math.floor() x/y values when rendering, stopping pixel interpolation. - * Handy for crisp pixel art and speed on legacy devices. - * - * @member {boolean} - */ - this.roundPixels = options.roundPixels; - - /** - * The background color as a number. - * - * @member {number} - * @private - */ - this._backgroundColor = 0x000000; - - /** - * The background color as an [R, G, B] array. - * - * @member {number[]} - * @private - */ - this._backgroundColorRgba = [0, 0, 0, 0]; - - /** - * The background color as a string. - * - * @member {string} - * @private - */ - this._backgroundColorString = '#000000'; - - this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter - - /** - * This temporary display object used as the parent of the currently being rendered item - * - * @member {PIXI.DisplayObject} - * @private - */ - this._tempDisplayObjectParent = new Container(); - - /** - * The last root object that the renderer tried to render. - * - * @member {PIXI.DisplayObject} - * @private - */ - this._lastObjectRendered = this._tempDisplayObjectParent; - - /** - * Collection of plugins. - * @readonly - * @member {object} - */ - this.plugins = {}; - } - - /** - * Initialize the plugins. - * - * @protected - * @param {object} staticMap - The dictionary of staticly saved plugins. - */ - initPlugins(staticMap) - { - 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() - { - return this.view.width; - } - - /** - * Same as view.height, actual number of pixels in the canvas by vertical - * - * @member {number} - * @readonly - * @default 600 - */ - get height() - { - return this.view.height; - } - - /** - * Resizes the screen and canvas to the specified width and height - * Canvas dimensions are multiplied by resolution - * - * @param {number} screenWidth - the new width of the screen - * @param {number} screenHeight - the new height of the screen - */ - resize(screenWidth, screenHeight) - { - this.screen.width = screenWidth; - this.screen.height = screenHeight; - - this.view.width = screenWidth * this.resolution; - this.view.height = screenHeight * this.resolution; - - if (this.autoResize) - { - this.view.style.width = `${screenWidth}px`; - this.view.style.height = `${screenHeight}px`; - } - } - - /** - * 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 {PIXI.DisplayObject} displayObject - The displayObject the object will be generated from - * @param {number} scaleMode - Should be one of the scaleMode consts - * @param {number} resolution - The resolution / device pixel ratio of the texture being generated - * @param {PIXI.Rectangle} [region] - The region of the displayObject, that shall be rendered, - * if no region is specified, defaults to the local bounds of the displayObject. - * @return {PIXI.Texture} a texture of the graphics object - */ - generateTexture(displayObject, scaleMode, resolution, region) - { - region = region || displayObject.getLocalBounds(); - - // 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(region.width | 0, region.height | 0, scaleMode, resolution); - - tempMatrix.tx = -region.x; - tempMatrix.ty = -region.y; - - this.render(displayObject, renderTexture, false, tempMatrix, true); - - return renderTexture; - } - - /** - * Removes everything from the renderer and optionally removes the Canvas DOM element. - * - * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. - */ - destroy(removeView) - { - 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); - } - - this.plugins = null; - - this.type = RENDERER_TYPE.UNKNOWN; - - this.view = null; - - this.screen = null; - - this.resolution = 0; - - this.transparent = false; - - this.autoResize = false; - - this.blendModes = null; - - this.options = null; - - this.preserveDrawingBuffer = false; - this.clearBeforeRender = false; - - this.roundPixels = false; - - this._backgroundColor = 0; - this._backgroundColorRgba = null; - this._backgroundColorString = null; - - this._tempDisplayObjectParent = null; - this._lastObjectRendered = null; - } - - /** - * The background color to fill if not transparent - * - * @member {number} - */ - get backgroundColor() - { - return this._backgroundColor; - } - - set backgroundColor(value) // eslint-disable-line require-jsdoc - { - this._backgroundColor = value; - this._backgroundColorString = hex2string(value); - hex2rgb(value, this._backgroundColorRgba); - } -} diff --git a/packages/core/src/renderers/Renderer.js b/packages/core/src/renderers/Renderer.js deleted file mode 100644 index a2020b1..0000000 --- a/packages/core/src/renderers/Renderer.js +++ /dev/null @@ -1,329 +0,0 @@ -import AbstractRenderer from './AbstractRenderer'; -import { sayHello } from '@pixi/utils'; -import MaskSystem from './systems/MaskSystem'; -import StencilSystem from './systems/StencilSystem'; -import FilterSystem from './systems/filter/FilterSystem'; -import FramebufferSystem from './systems/FramebufferSystem'; -import RenderTextureSystem from './systems/RenderTextureSystem'; -import TextureSystem from './systems/textures/TextureSystem'; -import ProjectionSystem from './systems/ProjectionSystem'; -import StateSystem from './systems/StateSystem'; -import GeometrySystem from './systems/geometry/GeometrySystem'; -import ShaderSystem from './systems/shader/ShaderSystem'; -import ContextSystem from './systems/ContextSystem'; -import BatchSystem from './systems/BatchSystem'; -import TextureGCSystem from './systems/textures/TextureGCSystem'; -import { RENDERER_TYPE } from '@pixi/constants'; -import UniformGroup from '../shader/UniformGroup'; -import { Matrix } from '@pixi/math'; -import Runner from 'mini-runner'; - -/** - * 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 Render works by automatically managing webGLBatchs. - * 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 :) - * - * @class - * @memberof PIXI - * @extends PIXI.AbstractRenderer - */ -export default class Renderer extends AbstractRenderer -{ - // eslint-disable-next-line valid-jsdoc - /** - * - * @param {object} [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.transparent=false] - If the render view is transparent, default false - * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false - * @param {boolean} [options.antialias=false] - sets antialias. If not available natively then FXAA - * antialiasing is used - * @param {boolean} [options.forceFXAA=false] - forces FXAA antialiasing to be used over native. - * FXAA is faster, but may not always look as great - * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. - * The resolution of the renderer retina would be 2. - * @param {boolean} [options.clearBeforeRender=true] - This sets if the renderer will clear - * the canvas or not before the new render pass. If you wish to set this to false, you *must* set - * preserveDrawingBuffer to `true`. - * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, - * enable this if you need to call toDataUrl on the webgl context. - * @param {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when - * rendering, stopping pixel interpolation. - * @param {number} [options.backgroundColor=0x000000] - The background color of the rendered area - * (shown if not transparent). - * @param {boolean} [options.legacy=false] - If true PixiJS will aim to ensure compatibility - * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. - * @param {string} [options.powerPreference] - Parameter passed to webgl context, set to "high-performance" - * for devices with dual graphics card - */ - constructor(options, arg2, arg3) - { - super('WebGL', options, arg2, arg3); - - /** - * The type of this renderer as a standardised const - * - * @member {number} - * @see PIXI.RENDERER_TYPE - */ - this.type = RENDERER_TYPE.WEBGL; - - // this will be set by the contextSystem (this.context) - this.gl = null; - this.CONTEXT_UID = 0; - this.legacy = !!options.legacy; - - // TODO legacy! - - // runners! - this.runners = { - destroy: new Runner('destroy'), - contextChange: new Runner('contextChange', 1), - reset: new Runner('reset'), - update: new Runner('update'), - postrender: new Runner('postrender'), - prerender: new Runner('prerender'), - resize: new Runner('resize', 2), - }; - - 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(GeometrySystem, 'geometry') - .addSystem(FramebufferSystem, 'framebuffer') - .addSystem(StencilSystem, 'stencil') - .addSystem(ProjectionSystem, 'projection') - .addSystem(TextureGCSystem, 'textureGC') - .addSystem(FilterSystem, 'filter') - .addSystem(RenderTextureSystem, 'renderTexture') - .addSystem(BatchSystem, 'batch'); - - this.initPlugins(Renderer.__plugins); - - /** - * The options passed in to create a new webgl context. - * - * @member {object} - * @private - */ - if (options.context) - { - this.context.initFromContext(options.context); - } - else - { - this.context.initFromOptions({ - alpha: this.transparent, - antialias: options.antialias, - premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', - stencil: true, - 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); - } - - addSystem(_class, name) - { - if (!name) - { - name = _class.name; - } - - // TODO - read name from class.name.. - - /* - if(name.includes('System')) - { - name = name.replace('System', ''); - name = name.charAt(0).toLowerCase() + name.slice(1); - } - */ - - const system = new _class(this); - - if (this[name]) - { - throw new Error(`Whoops! ${name} is already a manger`); - } - - this[name] = system; - - for (const i in this.runners) - { - this.runners[i].add(system); - } - - return this; - - /** - * 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. - */ - } - - /** - * Renders the object to its webGL view - * - * @param {PIXI.DisplayObject} displayObject - the object to be rendered - * @param {PIXI.RenderTexture} renderTexture - The render texture to render to. - * @param {boolean} [clear] - Should the canvas be cleared before the new render - * @param {PIXI.Transform} [transform] - A transform to apply to the render texture before rendering. - * @param {boolean} [skipUpdateTransform] - Should we skip the update transform pass? - */ - render(displayObject, renderTexture, clear, transform, skipUpdateTransform) - { - // can be handy to know! - this.renderingToScreen = !renderTexture; - - this.runners.prerender.run(); - this.emit('prerender'); - - // 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.parent; - - displayObject.parent = this._tempDisplayObjectParent; - displayObject.updateTransform(); - displayObject.parent = 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.run(); - - this.emit('postrender'); - } - - /** - * Resizes the webGL view to the specified width and height. - * - * @param {number} screenWidth - the new width of the screen - * @param {number} screenHeight - the new height of the screen - */ - resize(screenWidth, screenHeight) - { - AbstractRenderer.prototype.resize.call(this, screenWidth, screenHeight); - - this.runners.resize.run(screenWidth, screenHeight); - } - - /** - * Resets the WebGL state so you can render things however you fancy! - * - * @return {PIXI.Renderer} Returns itself. - */ - reset() - { - this.runners.reset.run(); - - return this; - } - - clear() - { - this.framebuffer.bind(); - this.framebuffer.clear(); - } - - /** - * Removes everything from the renderer (event listeners, spritebatch, etc...) - * - * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. - * See: https://github.com/pixijs/pixi.js/issues/2233 - */ - destroy(removeView) - { - this.runners.destroy.run(); - - // call base destroy - super.destroy(removeView); - - // TODO nullify all the managers.. - this.gl = null; - } - - /** - * 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 - * builds and excluding plugins. - * @name PIXI.Renderer#plugins - * @type {object} - * @readonly - * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. - * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. - * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. - * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. - */ - - /** - * Adds a plugin to the renderer. - * - * @method - * @param {string} pluginName - The name of the plugin. - * @param {Function} ctor - The constructor function or class for the plugin. - */ - static registerPlugin(pluginName, ctor) - { - Renderer.__plugins = Renderer.__plugins || {}; - Renderer.__plugins[pluginName] = ctor; - } -} diff --git a/packages/core/src/renderers/State.js b/packages/core/src/renderers/State.js deleted file mode 100644 index fa3f221..0000000 --- a/packages/core/src/renderers/State.js +++ /dev/null @@ -1,163 +0,0 @@ -/* eslint-disable max-len */ - -const BLEND = 0; -const OFFSET = 1; -const CULLING = 2; -const DEPTH_TEST = 3; -const WINDING = 4; - -/** - * This is a webGL state. It is passed The WebGL StateManager. - * Each mesh renderered may require webGL to be in a different state. - * For example you may want different blend mode or to enable polygon offsets - * - * @class - * @memberof PIXI - */ -export default class State -{ - /** - * - */ - constructor() - { - this.data = 0; - - this.blendMode = 0; - this.polygonOffset = 0; - - this.blend = true; - // this.depthTest = true; - } - - /** - * Activates blending of the computed fragment color values - * - * @member {boolean} - */ - get blend() - { - return !!(this.data & (1 << BLEND)); - } - - set blend(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << BLEND)) !== value) - { - this.data ^= (1 << BLEND); - } - } - - /** - * Activates adding an offset to depth values of polygon's fragments - * - * @member {boolean} - * @default false - */ - get offsets() - { - return !!(this.data & (1 << OFFSET)); - } - - set offsets(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << OFFSET)) !== value) - { - this.data ^= (1 << OFFSET); - } - } - - /** - * Activates culling of polygons. - * - * @member {boolean} - * @default false - */ - get culling() - { - return !!(this.data & (1 << CULLING)); - } - - set culling(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << CULLING)) !== value) - { - this.data ^= (1 << CULLING); - } - } - - /** - * Activates depth comparisons and updates to the depth buffer. - * - * @member {boolean} - * @default false - */ - get depthTest() - { - return !!(this.data & (1 << DEPTH_TEST)); - } - - set depthTest(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << DEPTH_TEST)) !== value) - { - this.data ^= (1 << DEPTH_TEST); - } - } - - /** - * Specifies whether or not front or back-facing polygons can be culled. - * @member {boolean} - * @default false - */ - get clockwiseFrontFace() - { - return !!(this.data & (1 << WINDING)); - } - - set clockwiseFrontFace(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << WINDING)) !== value) - { - this.data ^= (1 << WINDING); - } - } - - /** - * The blend mode to be applied when this state is set. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. - * Setting this mode to anything other than NO_BLEND will automatically switch blending on. - * - * @member {boolean} - * @default PIXI.BLEND_MODES.NORMAL - * @see PIXI.BLEND_MODES - */ - get blendMode() - { - return this._blendMode; - } - - set blendMode(value) // eslint-disable-line require-jsdoc - { - // 17 is NO BLEND - this.blend = (value !== 17); - this._blendMode = value; - } - - /** - * The polygon offset. Setting this property to anything other than 0 will automatically enable poygon offset fill. - * - * @member {number} - * @default 0 - */ - get polygonOffset() - { - return this._polygonOffset; - } - - set polygonOffset(value) // eslint-disable-line require-jsdoc - { - this.offsets = !!value; - this._polygonOffset = value; - } -} - diff --git a/packages/core/src/renderers/filters/Filter.js b/packages/core/src/renderers/filters/Filter.js deleted file mode 100644 index 2073b8d..0000000 --- a/packages/core/src/renderers/filters/Filter.js +++ /dev/null @@ -1,140 +0,0 @@ -import Shader from '../../shader/Shader'; -import Program from '../../shader/Program'; -import State from '../State'; -import { settings } from '@pixi/settings'; -// import extractUniformsFromSrc from './extractUniformsFromSrc'; -import defaultVertex from './defaultFilter.vert'; -import defaultFragment from './defaultFilter.frag'; - -// let math = require('../../math'); -/** - * @class - * @memberof PIXI - * @extends PIXI.Shader - */ -export default class Filter extends Shader -{ - /** - * @param {string} [vertexSrc] - The source of the vertex shader. - * @param {string} [fragmentSrc] - The source of the fragment shader. - * @param {object} [uniforms] - Custom uniforms to use to augment the built-in ones. - */ - constructor(vertexSrc, fragmentSrc, uniforms) - { - const program = Program.from(vertexSrc, fragmentSrc); - - super(program, uniforms); - - /** - * The padding of the filter. Some filters require extra space to breath such as a blur. - * Increasing this will add extra width and height to the bounds of the object that the - * filter is applied to. - * - * @member {number} - */ - this.padding = 0; - - /** - * The resolution of the filter. Setting this to be lower will lower the quality but - * increase the performance of the filter. - * - * @member {number} - */ - this.resolution = settings.RESOLUTION; - - /** - * If enabled is true the filter is applied, if false it will not. - * - * @member {boolean} - */ - this.enabled = true; - - /** - * If enabled, PixiJS will fit the filter area into boundaries for better performance. - * Switch it off if it does not work for specific shader. - * - * @member {boolean} - */ - this.autoFit = true; - - /** - * Legacy filters use position and uvs from attributes - * @member {boolean} - * @readonly - */ - this.legacy = !!this.program.attributeData.aTextureCoord; - - /** - * the webGL state the filter requires to render - * @member {PIXI.State} - */ - this.state = new State(); - } - - /** - * Applies the filter - * - * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from - * @param {PIXI.RenderTarget} input - The input render target. - * @param {PIXI.RenderTarget} output - The target to output to. - * @param {boolean} clear - Should the output be cleared before rendering to it - * @param {object} [currentState] - It's current state of filter. - * There are some useful properties in the currentState : - * target, filters, sourceFrame, destinationFrame, renderTarget, resolution - */ - apply(filterManager, input, output, clear, currentState, derp) // eslint-disable-line no-unused-vars - { - // do as you please! - - filterManager.applyFilter(this, input, output, clear, currentState, derp); - - // or just do a regular render.. - } - - /** - * Sets the blendmode of the filter - * - * @member {number} - * @default PIXI.BLEND_MODES.NORMAL - */ - get blendMode() - { - return this.state.blendMode; - } - - set blendMode(value) // eslint-disable-line require-jsdoc - { - this.state.blendMode = value; - } - - /** - * The default vertex shader source - * - * @static - * @constant - */ - static get defaultVertexSrc() - { - return defaultVertex; - } - - /** - * The default fragment shader source - * - * @static - * @constant - */ - static get defaultFragmentSrc() - { - return defaultFragment; - } -} - -/** - * Used for caching shader IDs - * - * @static - * @private - */ -Filter.SOURCE_KEY_MAP = {}; - diff --git a/packages/core/src/renderers/filters/defaultFilter.frag b/packages/core/src/renderers/filters/defaultFilter.frag deleted file mode 100644 index a14c1be..0000000 --- a/packages/core/src/renderers/filters/defaultFilter.frag +++ /dev/null @@ -1,22 +0,0 @@ -varying vec2 vTextureCoord; -varying vec2 vFilterCoord; - -uniform sampler2D uSampler; -uniform sampler2D filterSampler; - -void main(void){ - vec4 masky = texture2D(filterSampler, vFilterCoord); - vec4 sample = texture2D(uSampler, vTextureCoord); - vec4 color; - if(mod(vFilterCoord.x, 1.0) > 0.5) - { - color = vec4(1.0, 0.0, 0.0, 1.0); - } - else - { - color = vec4(0.0, 1.0, 0.0, 1.0); - } - // gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0); - gl_FragColor = mix(sample, masky, 0.5); - gl_FragColor *= sample.a; -} diff --git a/packages/core/src/renderers/filters/defaultFilter.vert b/packages/core/src/renderers/filters/defaultFilter.vert deleted file mode 100644 index e1febca..0000000 --- a/packages/core/src/renderers/filters/defaultFilter.vert +++ /dev/null @@ -1,14 +0,0 @@ -attribute vec2 aVertexPosition; -attribute vec2 aTextureCoord; - -uniform mat3 projectionMatrix; -uniform mat3 filterMatrix; - -varying vec2 vTextureCoord; -varying vec2 vFilterCoord; - -void main(void){ - gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); - vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy; - vTextureCoord = aTextureCoord ; -} diff --git a/packages/core/src/renderers/filters/filterTransforms.js b/packages/core/src/renderers/filters/filterTransforms.js deleted file mode 100644 index 87a3cd6..0000000 --- a/packages/core/src/renderers/filters/filterTransforms.js +++ /dev/null @@ -1,50 +0,0 @@ -import { Matrix } from '@pixi/math'; - -/** - * Calculates the mapped matrix - * @param filterArea {Rectangle} The filter area - * @param sprite {Sprite} the target sprite - * @param outputMatrix {Matrix} @alvin - * @private - */ -// this returns a matrix that will normalise map filter cords in the filter to screen space -export function calculateScreenSpaceMatrix(outputMatrix, filterArea, textureSize) -{ - // TODO unwrap? - const mappedMatrix = outputMatrix.identity(); - - mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); - - mappedMatrix.scale(textureSize.width, textureSize.height); - - return mappedMatrix; -} - -export function calculateNormalizedScreenSpaceMatrix(outputMatrix, filterArea, textureSize) -{ - const mappedMatrix = outputMatrix.identity(); - - mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); - - const translateScaleX = (textureSize.width / filterArea.width); - const translateScaleY = (textureSize.height / filterArea.height); - - mappedMatrix.scale(translateScaleX, translateScaleY); - - return mappedMatrix; -} - -// this will map the filter coord so that a texture can be used based on the transform of a sprite -export function calculateSpriteMatrix(outputMatrix, filterArea, textureSize, sprite) -{ - const orig = sprite._texture.orig; - const mappedMatrix = outputMatrix.set(textureSize.width, 0, 0, textureSize.height, filterArea.x, filterArea.y); - const worldTransform = sprite.worldTransform.copyTo(Matrix.TEMP_MATRIX); - - worldTransform.invert(); - mappedMatrix.prepend(worldTransform); - mappedMatrix.scale(1.0 / orig.width, 1.0 / orig.height); - mappedMatrix.translate(sprite.anchor.x, sprite.anchor.y); - - return mappedMatrix; -} diff --git a/packages/core/src/renderers/filters/spriteMask/SpriteMaskFilter.js b/packages/core/src/renderers/filters/spriteMask/SpriteMaskFilter.js deleted file mode 100644 index d5a91a4..0000000 --- a/packages/core/src/renderers/filters/spriteMask/SpriteMaskFilter.js +++ /dev/null @@ -1,63 +0,0 @@ -import Filter from '../Filter'; -import { Matrix } from '@pixi/math'; -import vertex from './spriteMaskFilter.vert'; -import fragment from './spriteMaskFilter.frag'; -import { default as TextureMatrix } from '../../../textures/TextureMatrix'; - -/** - * The SpriteMaskFilter class - * - * @class - * @extends PIXI.Filter - * @memberof PIXI - */ -export default class SpriteMaskFilter extends Filter -{ - /** - * @param {PIXI.Sprite} sprite - the target sprite - */ - constructor(sprite) - { - const maskMatrix = new Matrix(); - - super(vertex, fragment); - - sprite.renderable = false; - - this.maskSprite = sprite; - this.maskMatrix = maskMatrix; - } - - /** - * Applies the filter - * - * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from - * @param {PIXI.RenderTarget} input - The input render target. - * @param {PIXI.RenderTarget} output - The target to output to. - */ - apply(filterManager, input, output) - { - const maskSprite = this.maskSprite; - const tex = this.maskSprite.texture; - - if (!tex.valid) - { - return; - } - if (!tex.transform) - { - // margin = 0.0, let it bleed a bit, shader code becomes easier - // assuming that atlas textures were made with 1-pixel padding - tex.transform = new TextureMatrix(tex, 0.0); - } - tex.transform.update(); - - this.uniforms.mask = tex; - this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite) - .prepend(tex.transform.mapCoord); - this.uniforms.alpha = maskSprite.worldAlpha; - this.uniforms.maskClamp = tex.transform.uClampFrame; - - filterManager.applyFilter(this, input, output); - } -} diff --git a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.frag b/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.frag deleted file mode 100644 index 0e4aef8..0000000 --- a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.frag +++ /dev/null @@ -1,23 +0,0 @@ -varying vec2 vMaskCoord; -varying vec2 vTextureCoord; - -uniform sampler2D uSampler; -uniform sampler2D mask; -uniform float alpha; -uniform vec4 maskClamp; - -void main(void) -{ - float clip = step(3.5, - step(maskClamp.x, vMaskCoord.x) + - step(maskClamp.y, vMaskCoord.y) + - step(vMaskCoord.x, maskClamp.z) + - step(vMaskCoord.y, maskClamp.w)); - - vec4 original = texture2D(uSampler, vTextureCoord); - vec4 masky = texture2D(mask, vMaskCoord); - - original *= (masky.r * masky.a * alpha * clip); - - gl_FragColor = original; -} diff --git a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.vert b/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.vert deleted file mode 100644 index d23a30c..0000000 --- a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.vert +++ /dev/null @@ -1,16 +0,0 @@ -attribute vec2 aVertexPosition; -attribute vec2 aTextureCoord; - -uniform mat3 projectionMatrix; -uniform mat3 otherMatrix; - -varying vec2 vMaskCoord; -varying vec2 vTextureCoord; - -void main(void) -{ - gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); - - vTextureCoord = aTextureCoord; - vMaskCoord = ( otherMatrix * vec3( aTextureCoord, 1.0) ).xy; -} diff --git a/packages/core/src/renderers/systems/BatchSystem.js b/packages/core/src/renderers/systems/BatchSystem.js deleted file mode 100644 index 70e9518..0000000 --- a/packages/core/src/renderers/systems/BatchSystem.js +++ /dev/null @@ -1,68 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import ObjectRenderer from '../utils/ObjectRenderer'; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ - -export default class BatchSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this manager works for. - */ - constructor(renderer) - { - super(renderer); - - /** - * An empty renderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.emptyRenderer = new ObjectRenderer(renderer); - - /** - * The currently active ObjectRenderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.currentRenderer = this.emptyRenderer; - } - - /** - * Changes the current renderer to the one given in parameter - * - * @param {PIXI.ObjectRenderer} objectRenderer - The object renderer to use. - */ - setObjectRenderer(objectRenderer) - { - if (this.currentRenderer === objectRenderer) - { - return; - } - - this.currentRenderer.stop(); - this.currentRenderer = objectRenderer; - - this.renderer.state.setState(objectRenderer.state); - - this.currentRenderer.start(); - } - - /** - * This should be called if you wish to do some custom rendering - * It will basically render anything that may be batched up such as sprites - * - */ - flush() - { - this.setObjectRenderer(this.emptyRenderer); - } - - reset() - { - this.setObjectRenderer(this.emptyRenderer); - } -} diff --git a/packages/core/src/renderers/systems/ContextSystem.js b/packages/core/src/renderers/systems/ContextSystem.js deleted file mode 100644 index 43bf827..0000000 --- a/packages/core/src/renderers/systems/ContextSystem.js +++ /dev/null @@ -1,186 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { settings } from '@pixi/settings'; - -let CONTEXT_UID = 0; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class ContextSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this.webGLVersion = 1; - - this.handleContextLost = this.handleContextLost.bind(this); - this.handleContextRestored = this.handleContextRestored.bind(this); - - this.extensions = {}; - - renderer.view.addEventListener('webglcontextlost', this.handleContextLost, false); - renderer.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); - } - - get isLost() - { - return (!this.gl || this.gl.isContextLost()); - } - - contextChange(gl) - { - this.gl = gl; - - // restore a context if it was previously lost - if (gl.isContextLost() && gl.getExtension('WEBGL_lose_context')) - { - gl.getExtension('WEBGL_lose_context').restoreContext(); - } - } - - initFromContext(gl) - { - this.gl = gl; - this.validateContext(gl); - this.renderer.gl = gl; - this.renderer.CONTEXT_UID = CONTEXT_UID++; - this.renderer.runners.contextChange.run(gl); - } - - initFromOptions(options) - { - const gl = this.createContext(this.renderer.view, options); - - this.initFromContext(gl); - } - - /** - * Helper class to create a webGL Context - * - * @class - * @memberof PIXI.glCore - * @param canvas {HTMLCanvasElement} the canvas element that we will get the context from - * @param options {Object} An options object that gets passed in to the canvas element containing the context attributes, - * see https://developer.mozilla.org/en/docs/Web/API/HTMLCanvasElement/getContext for the options available - * @return {WebGLRenderingContext} the WebGL context - */ - createContext(canvas, options) - { - let gl; - - if (settings.PREFER_WEBGL_2) - { - gl = canvas.getContext('webgl2', options); - } - - if (gl) - { - this.webGLVersion = 2; - } - else - { - this.webGLVersion = 1; - - gl = canvas.getContext('webgl', options) - || canvas.getContext('experimental-webgl', options); - - if (!gl) - { - // fail, not able to get a context - throw new Error('This browser does not support webGL. Try using the canvas renderer'); - } - } - - this.gl = gl; - - this.getExtensions(); - - return gl; - } - - getExtensions() - { - // time to set up default etensions that pixi uses.. - const gl = this.gl; - const extensions = this.extensions; - - if (this.webGLVersion === 1) - { - extensions.drawBuffers = gl.getExtension('WEBGL_draw_buffers'); - extensions.depthTexture = gl.getExtension('WEBKIT_WEBGL_depth_texture'); - extensions.floatTexture = gl.getExtension('OES_texture_float'); - extensions.loseContext = gl.getExtension('WEBGL_lose_context'); - - extensions.vertexArrayObject = gl.getExtension('OES_vertex_array_object') - || gl.getExtension('MOZ_OES_vertex_array_object') - || gl.getExtension('WEBKIT_OES_vertex_array_object'); - } - - // we don't use any specific WebGL 2 ones yet! - } - - /** - * Handles a lost webgl context - * - * @private - * @param {WebGLContextEvent} event - The context lost event. - */ - handleContextLost(event) - { - event.preventDefault(); - } - - /** - * Handles a restored webgl context - * - * @private - */ - handleContextRestored() - { - this.renderer.runners.contextChange.run(this.gl); - } - - destroy() - { - const view = this.renderer.view; - - // remove listeners - view.removeEventListener('webglcontextlost', this.handleContextLost); - view.removeEventListener('webglcontextrestored', this.handleContextRestored); - - this.gl.useProgram(null); - - if (this.extensions.loseContext) - { - this.extensions.loseContext.loseContext(); - } - } - - postrender() - { - this.gl.flush(); - } - - validateContext(gl) - { - const attributes = gl.getContextAttributes(); - - // this is going to be fairly simple for now.. but at least we have room to grow! - if (!attributes.stencil) - { - /* eslint-disable max-len */ - - /* eslint-disable no-console */ - console.warn('Provided WebGL context does not have a stencil buffer, masks may not render correctly'); - /* eslint-enable no-console */ - - /* eslint-enable max-len */ - } - } -} diff --git a/packages/core/src/renderers/systems/FramebufferSystem.js b/packages/core/src/renderers/systems/FramebufferSystem.js deleted file mode 100644 index df7902b..0000000 --- a/packages/core/src/renderers/systems/FramebufferSystem.js +++ /dev/null @@ -1,246 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { Rectangle } from '@pixi/math'; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class FramebufferSystem extends WebGLSystem -{ - /** - * Sets up the renderer context and necessary buffers. - * - * @private - */ - contextChange() - { - this.gl = this.renderer.gl; - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - this.current = null; - this.viewport = new Rectangle(); - - this.drawBufferExtension = this.renderer.context.extensions.drawBuffers; - } - - bind(framebuffer, frame) - { - const gl = this.gl; - - this.current = framebuffer; - - if (framebuffer) - { - // TODO cacheing layer! - - const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID] || this.initFramebuffer(framebuffer); - - gl.bindFramebuffer(gl.FRAMEBUFFER, fbo.framebuffer); - // makesure all textures are unbound.. - - // now check for updates... - if (fbo.dirtyId !== framebuffer.dirtyId) - { - fbo.dirtyId = framebuffer.dirtyId; - - if (fbo.dirtyFormat !== framebuffer.dirtyFormat) - { - fbo.dirtyFormat = framebuffer.dirtyFormat; - this.updateFramebuffer(framebuffer); - } - else if (fbo.dirtySize !== framebuffer.dirtySize) - { - fbo.dirtySize = framebuffer.dirtySize; - this.resizeFramebuffer(framebuffer); - } - } - - for (let i = 0; i < framebuffer.colorTextures.length; i++) - { - if (framebuffer.colorTextures[i].texturePart) - { - this.renderer.texture.unbind(framebuffer.colorTextures[i].texture); - } - else - { - this.renderer.texture.unbind(framebuffer.colorTextures[i]); - } - } - - if (framebuffer.depthTexture) - { - this.renderer.texture.unbind(framebuffer.depthTexture); - } - - if (frame) - { - this.setViewport(frame.x, frame.y, frame.width, frame.height); - } - else - { - this.setViewport(0, 0, framebuffer.width, framebuffer.height); - } - } - else - { - gl.bindFramebuffer(gl.FRAMEBUFFER, null); - - if (frame) - { - this.setViewport(frame.x, frame.y, frame.width, frame.height); - } - else - { - this.setViewport(0, 0, this.renderer.width, this.renderer.height); - } - } - } - - setViewport(x, y, width, height) - { - const v = this.viewport; - - if (v.width !== width || v.height !== height || v.x !== x || v.y !== y) - { - v.x = x; - v.y = y; - v.width = width; - v.height = height; - - this.gl.viewport(x, y, width, height); - } - } - - get size() - { - if (this.current) - { - // TODO store temp - return { x: 0, y: 0, width: this.current.width, height: this.current.height }; - } - - return { x: 0, y: 0, width: this.renderer.width, height: this.renderer.height }; - } - - clear(r, g, b, a) - { - const gl = this.gl; - - // TODO clear color can be set only one right? - gl.clearColor(r, g, b, a); - gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); - } - - // private functions... - - initFramebuffer(framebuffer) - { - const gl = this.gl; - - // TODO - make this a class? - const fbo = { - framebuffer: gl.createFramebuffer(), - stencil: null, - dirtyId: 0, - dirtyFormat: 0, - dirtySize: 0, - }; - - framebuffer.glFrameBuffers[this.CONTEXT_UID] = fbo; - - return fbo; - } - - resizeFramebuffer(framebuffer) - { - const gl = this.gl; - - if (framebuffer.stencil || framebuffer.depth) - { - gl.bindRenderbuffer(gl.RENDERBUFFER, this.stencil); - gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); - } - } - - updateFramebuffer(framebuffer) - { - const gl = this.gl; - - const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID]; - - // bind the color texture - const colorTextures = framebuffer.colorTextures; - - let count = colorTextures.length; - - if (!this.drawBufferExtension) - { - count = Math.min(count, 1); - } - - const activeTextures = []; - - for (let i = 0; i < count; i++) - { - const texture = framebuffer.colorTextures[i]; - - if (texture.texturePart) - { - this.renderer.texture.bind(texture.texture, 0); - - gl.framebufferTexture2D(gl.FRAMEBUFFER, - gl.COLOR_ATTACHMENT0 + i, - gl.TEXTURE_CUBE_MAP_NEGATIVE_X + texture.side, - texture.texture._glTextures[this.CONTEXT_UID].texture, - 0); - } - else - { - this.renderer.texture.bind(texture, 0); - - gl.framebufferTexture2D(gl.FRAMEBUFFER, - gl.COLOR_ATTACHMENT0 + i, - gl.TEXTURE_2D, - texture._glTextures[this.CONTEXT_UID].texture, - 0); - } - - activeTextures.push(gl.COLOR_ATTACHMENT0 + i); - } - - if (this.drawBufferExtension && activeTextures.length > 1) - { - this.drawBufferExtension.drawBuffersWEBGL(activeTextures); - } - - if (framebuffer.depthTexture) - { - const depthTextureExt = this.renderer.context.extensions.depthTexture; - - if (depthTextureExt) - { - const depthTexture = framebuffer.depthTexture; - - this.renderer.texture.bind(depthTexture, 0); - - gl.framebufferTexture2D(gl.FRAMEBUFFER, - gl.DEPTH_ATTACHMENT, - gl.TEXTURE_2D, - depthTexture._glTextures[this.CONTEXT_UID].texture, - 0); - } - } - - if (framebuffer.stencil || framebuffer.depth) - { - fbo.stencil = gl.createRenderbuffer(); - - gl.bindRenderbuffer(gl.RENDERBUFFER, fbo.stencil); - - // TODO.. this is depth AND stencil? - gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, fbo.stencil); - gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); - // fbo.enableStencil(); - } - } -} diff --git a/packages/core/src/renderers/systems/MaskSystem.js b/packages/core/src/renderers/systems/MaskSystem.js deleted file mode 100644 index d9c4b1a..0000000 --- a/packages/core/src/renderers/systems/MaskSystem.js +++ /dev/null @@ -1,199 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import AlphaMaskFilter from '../filters/spriteMask/SpriteMaskFilter'; - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class MaskSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - // TODO - we don't need both! - this.scissor = false; - this.scissorData = null; - this.scissorRenderTarget = null; - - this.enableScissor = false; - - this.alphaMaskPool = []; - this.alphaMaskIndex = 0; - } - - /** - * Applies the Mask and adds it to the current filter stack. - * - * @param {PIXI.DisplayObject} target - Display Object to push the mask to - * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. - */ - push(target, maskData) - { - // TODO the root check means scissor rect will not - // be used on render textures more info here: - // https://github.com/pixijs/pixi.js/pull/3545 - - if (maskData.texture) - { - this.pushSpriteMask(target, maskData); - } - else if (this.enableScissor - && !this.scissor - && this.renderer._activeRenderTarget.root - && !this.renderer.stencil.stencilMaskStack.length - && maskData.isFastRect()) - { - const matrix = maskData.worldTransform; - - let rot = Math.atan2(matrix.b, matrix.a); - - // use the nearest degree! - rot = Math.round(rot * (180 / Math.PI)); - - if (rot % 90) - { - this.pushStencilMask(maskData); - } - else - { - this.pushScissorMask(target, maskData); - } - } - else - { - this.pushStencilMask(maskData); - } - } - - /** - * Removes the last mask from the mask stack and doesn't return it. - * - * @param {PIXI.DisplayObject} target - Display Object to pop the mask from - * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. - */ - pop(target, maskData) - { - if (maskData.texture) - { - this.popSpriteMask(target, maskData); - } - else if (this.enableScissor && !this.renderer.stencil.stencilMaskStack.length) - { - this.popScissorMask(target, maskData); - } - else - { - this.popStencilMask(target, maskData); - } - } - - /** - * Applies the Mask and adds it to the current filter stack. - * - * @param {PIXI.RenderTarget} target - Display Object to push the sprite mask to - * @param {PIXI.Sprite} maskData - Sprite to be used as the mask - */ - pushSpriteMask(target, maskData) - { - let alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; - - if (!alphaMaskFilter) - { - alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; - } - - alphaMaskFilter[0].resolution = this.renderer.resolution; - alphaMaskFilter[0].maskSprite = maskData; - - // TODO - may cause issues! - target.filterArea = maskData.getBounds(true); - - this.renderer.filterSystem.pushFilter(target, alphaMaskFilter); - - this.alphaMaskIndex++; - } - - /** - * Removes the last filter from the filter stack and doesn't return it. - * - */ - popSpriteMask() - { - this.renderer.filterSystem.popFilter(); - this.alphaMaskIndex--; - } - - /** - * Applies the Mask and adds it to the current filter stack. - * - * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. - */ - pushStencilMask(maskData) - { - this.renderer.batch.flush(); - this.renderer.stencil.pushStencil(maskData); - } - - /** - * Removes the last filter from the filter stack and doesn't return it. - * - */ - popStencilMask() - { - // this.renderer.currentRenderer.stop(); - this.renderer.stencil.popStencil(); - } - - /** - * - * @param {PIXI.DisplayObject} target - Display Object to push the mask to - * @param {PIXI.Graphics} maskData - The masking data. - */ - pushScissorMask(target, maskData) - { - maskData.renderable = true; - - const renderTarget = this.renderer._activeRenderTarget; - - const bounds = maskData.getBounds(); - - bounds.fit(renderTarget.size); - maskData.renderable = false; - - this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); - - const resolution = this.renderer.resolution; - - this.renderer.gl.scissor( - bounds.x * resolution, - (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, - bounds.width * resolution, - bounds.height * resolution - ); - - this.scissorRenderTarget = renderTarget; - this.scissorData = maskData; - this.scissor = true; - } - - /** - * - * - */ - popScissorMask() - { - this.scissorRenderTarget = null; - this.scissorData = null; - this.scissor = false; - - // must be scissor! - const gl = this.renderer.gl; - - gl.disable(gl.SCISSOR_TEST); - } -} diff --git a/packages/core/src/renderers/systems/ProjectionSystem.js b/packages/core/src/renderers/systems/ProjectionSystem.js deleted file mode 100644 index ec5efdb..0000000 --- a/packages/core/src/renderers/systems/ProjectionSystem.js +++ /dev/null @@ -1,73 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { Matrix } from '@pixi/math'; - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ - -export default class ProjectionSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this.projectionMatrix = new Matrix(); - } - - update(destinationFrame, sourceFrame, resolution, root) - { - this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; - this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; - - this.calculateProjection(this.destinationFrame, this.sourceFrame, resolution, root); - - this.renderer.globalUniforms.uniforms.projectionMatrix = this.projectionMatrix; - this.renderer.globalUniforms.update(); - } - - /** - * Updates the projection matrix based on a projection frame (which is a rectangle) - * - * @param {Rectangle} destinationFrame - The destination frame. - * @param {Rectangle} sourceFrame - The source frame. - */ - calculateProjection(destinationFrame, sourceFrame, resolution, root) - { - const pm = this.projectionMatrix; - - // I don't think we will need this line.. - // pm.identity(); - - if (!root) - { - pm.a = (1 / destinationFrame.width * 2) * resolution; - pm.d = (1 / destinationFrame.height * 2) * resolution; - - pm.tx = -1 - (sourceFrame.x * pm.a); - pm.ty = -1 - (sourceFrame.y * pm.d); - } - else - { - pm.a = (1 / destinationFrame.width * 2) * resolution; - pm.d = (-1 / destinationFrame.height * 2) * resolution; - - pm.tx = -1 - (sourceFrame.x * pm.a); - pm.ty = 1 - (sourceFrame.y * pm.d); - } - } - - /** - * Sets the transform of the active render target to the given matrix - * - * @param {PIXI.Matrix} matrix - The transformation matrix - */ - setTransform()// matrix) - { - // this._activeRenderTarget.transform = matrix; - } -} diff --git a/packages/core/src/renderers/systems/RenderTextureSystem.js b/packages/core/src/renderers/systems/RenderTextureSystem.js deleted file mode 100644 index a133ef1..0000000 --- a/packages/core/src/renderers/systems/RenderTextureSystem.js +++ /dev/null @@ -1,115 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { Rectangle } from '@pixi/math'; - -const tempRect = new Rectangle(); - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ - -export default class RenderTextureSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this.clearColor = renderer._backgroundColorRgba; - - // TODO moe this property somewhere else! - this.defaultMaskStack = []; - this.defaultFilterStack = [{}]; - - // empty render texture? - this.renderTexture = null; - - this.destinationFrame = new Rectangle(); - } - - bind(renderTexture, sourceFrame, destinationFrame) - { - // TODO - do we want this?? - if (this.renderTexture === renderTexture) return; - this.renderTexture = renderTexture; - - const renderer = this.renderer; - - if (renderTexture) - { - const baseTexture = renderTexture.baseTexture; - - if (!destinationFrame) - { - tempRect.width = baseTexture.realWidth; - tempRect.height = baseTexture.realHeight; - - destinationFrame = tempRect; - } - - if (!sourceFrame) - { - sourceFrame = destinationFrame; - } - - this.renderer.framebuffer.bind(baseTexture.frameBuffer, destinationFrame); - - this.renderer.projection.update(destinationFrame, sourceFrame, baseTexture.resolution, false); - this.renderer.stencil.setMaskStack(baseTexture.stencilMaskStack); - } - else - { - // TODO these validation checks happen deeper down.. - // thing they can be avoided.. - if (!destinationFrame) - { - tempRect.width = renderer.width; - tempRect.height = renderer.height; - - destinationFrame = tempRect; - } - - if (!sourceFrame) - { - sourceFrame = destinationFrame; - } - - renderer.framebuffer.bind(null, destinationFrame); - - // TODO store this.. - this.renderer.projection.update(destinationFrame, sourceFrame, this.renderer.resolution, true); - this.renderer.stencil.setMaskStack(this.defaultMaskStack); - } - - this.destinationFrame.copyFrom(destinationFrame); - } - - /** - * Erases the render texture and fills the drawing area with a colour - * - * @param {number} [clearColor] - The colour - * @return {PIXI.Renderer} Returns itself. - */ - clear(clearColor) - { - if (this.renderTexture) - { - clearColor = clearColor || this.renderTexture.baseTexture.clearColor; - } - else - { - clearColor = clearColor || this.clearColor; - } - - this.renderer.framebuffer.clear(clearColor[0], clearColor[1], clearColor[2], clearColor[3]); - } - - resize()// screenWidth, screenHeight) - { - // resize the root only! - this.bind(null); - } -} diff --git a/packages/core/src/renderers/systems/StateSystem.js b/packages/core/src/renderers/systems/StateSystem.js deleted file mode 100755 index d7f4caf..0000000 --- a/packages/core/src/renderers/systems/StateSystem.js +++ /dev/null @@ -1,288 +0,0 @@ -import mapWebGLBlendModesToPixi from '../utils/mapWebGLBlendModesToPixi'; -import WebGLSystem from './WebGLSystem'; -import WebGLState from '../State'; - -const BLEND = 0; -const OFFSET = 1; -const CULLING = 2; -const DEPTH_TEST = 3; -const WINDING = 4; - -/** - * A WebGL state machines - * - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class StateSystem extends WebGLSystem -{ - /** - * @param {WebGLRenderingContext} gl - The current WebGL rendering context - */ - constructor(renderer) - { - super(renderer); - - this.gl = null; - - this.maxAttribs = null; - - // check we have vao.. - this.nativeVaoExtension = null; - - this.attribState = null; - - this.stateId = 0; - this.polygonOffset = 0; - this.blendMode = 17; - - this.map = []; - - // map functions for when we set state.. - this.map[BLEND] = this.setBlend; - this.map[OFFSET] = this.setOffset; - this.map[CULLING] = this.setCullFace; - this.map[DEPTH_TEST] = this.setDepthTest; - this.map[WINDING] = this.setFrontFace; - - this.checks = []; - - this.defaultState = new WebGLState(); - this.defaultState.blend = true; - this.defaultState.depth = true; - } - - contextChange(gl) - { - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; - - this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); - - // check we have vao.. - this.nativeVaoExtension = ( - gl.getExtension('OES_vertex_array_object') - || gl.getExtension('MOZ_OES_vertex_array_object') - || gl.getExtension('WEBKIT_OES_vertex_array_object') - ); - - this.attribState = { - tempAttribState: new Array(this.maxAttribs), - attribState: new Array(this.maxAttribs), - }; - - this.blendModes = mapWebGLBlendModesToPixi(gl); - - this.setState(this.defaultState); - - this.reset(); - } - - /** - * Sets the current state - * - * @param {*} state - The state to set. - */ - setState(state) - { - state = state || this.defaultState; - - // TODO maybe to an object check? ( this.state === state )? - if (this.stateId !== state.data) - { - let diff = this.stateId ^ state.data; - let i = 0; - - // order from least to most common - while (diff) - { - if (diff & 1) - { - // state change! - this.map[i].call(this, !!(state.data & (1 << i))); - } - - diff = diff >> 1; - i++; - } - - this.stateId = state.data; - } - - // based on the above settings we check for specific modes.. - // for example if blend is active we check and set the blend modes - // or of polygon offset is active we check the poly depth. - for (let i = 0; i < this.checks.length; i++) - { - this.checks[i](this, state); - } - } - - /** - * Enables or disabled blending. - * - * @param {boolean} value - Turn on or off webgl blending. - */ - setBlend(value) - { - this.updateCheck(StateSystem.checkBlendMode, value); - - this.gl[value ? 'enable' : 'disable'](this.gl.BLEND); - } - - /** - * Enables or disable polygon offset fill - * - * @param {boolean} value - Turn on or off webgl polygon offset testing. - */ - setOffset(value) - { - this.gl[value ? 'enable' : 'disable'](this.gl.POLYGON_OFFSET_FILL); - } - - /** - * Sets whether to enable or disable depth test. - * - * @param {boolean} value - Turn on or off webgl depth testing. - */ - setDepthTest(value) - { - this.gl[value ? 'enable' : 'disable'](this.gl.DEPTH_TEST); - } - - /** - * Sets whether to enable or disable cull face. - * - * @param {boolean} value - Turn on or off webgl cull face. - */ - setCullFace(value) - { - this.gl[value ? 'enable' : 'disable'](this.gl.CULL_FACE); - } - - /** - * Sets the gl front face. - * - * @param {boolean} value - true is clockwise and false is counter-clockwise - */ - setFrontFace(value) - { - this.gl.frontFace(this.gl[value ? 'CW' : 'CCW']); - } - - /** - * Sets the blend mode. - * - * @param {number} value - The blend mode to set to. - */ - setBlendMode(value) - { - if (value === this.blendMode) - { - return; - } - - this.blendMode = value; - this.gl.blendFunc(this.blendModes[value][0], this.blendModes[value][1]); - } - - /** - * Sets the polygon offset. - * - * @param {number} value - the polygon offset - * @param {number} scale - the polygon offset scale - */ - setPolygonOffset(value, scale) - { - this.gl.polygonOffset(value, scale); - } - - /** - * Disables all the vaos in use - * - */ - resetAttributes() - { - for (let i = 0; i < this.attribState.tempAttribState.length; i++) - { - this.attribState.tempAttribState[i] = 0; - } - - for (let i = 0; i < this.attribState.attribState.length; i++) - { - this.attribState.attribState[i] = 0; - } - - // im going to assume one is always active for performance reasons. - for (let i = 1; i < this.maxAttribs; i++) - { - this.gl.disableVertexAttribArray(i); - } - } - - // used - /** - * Resets all the logic and disables the vaos - */ - reset() - { - // unbind any VAO if they exist.. - if (this.nativeVaoExtension) - { - this.nativeVaoExtension.bindVertexArrayOES(null); - } - - // reset all attributes.. - this.resetAttributes(); - - this.gl.pixelStorei(this.gl.UNPACK_FLIP_Y_WEBGL, false); - - this.setBlendMode(0); - - // TODO? - // this.setState(this.defaultState); - } - - /** - * checks to see which updates should be checked based on which settings have been activated - * for example if blend is enabled then we shold check the blend modes each time the state is changed - * or if poygon fill is activated then we need to check if the polygone offset changes. - * The idea is that we only check what we have too. - * - * @param {Function} func the checking function to add or remove - * @param {boolean} value should the check function be added or removed. - */ - updateCheck(func, value) - { - const index = this.checks.indexOf(func); - - if (value && index === -1) - { - this.checks.push(func); - } - else if (!value && index !== -1) - { - this.checks.splice(index, 1); - } - } - - /** - * A private little wrapper function that we call to check the blend mode. - * - * @static - * @private - * @param {PIXI.StateSystem} System the System to perform the state check on - * @param {PIXI.State} state the state that the blendMode will pulled from - */ - static checkBlendMode(system, state) - { - system.setBlendMode(state.blendMode); - } - - // TODO - add polygon offset? -} diff --git a/packages/core/src/renderers/systems/StencilSystem.js b/packages/core/src/renderers/systems/StencilSystem.js deleted file mode 100644 index 5d538cb..0000000 --- a/packages/core/src/renderers/systems/StencilSystem.js +++ /dev/null @@ -1,133 +0,0 @@ -import WebGLSystem from './WebGLSystem'; - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class StencilSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - this.stencilMaskStack = []; - } - - /** - * Changes the mask stack that is used by this System. - * - * @param {PIXI.Graphics[]} stencilMaskStack - The mask stack - */ - setMaskStack(stencilMaskStack) - { - const gl = this.renderer.gl; - - if (stencilMaskStack.length !== this.stencilMaskStack.length) - { - if (stencilMaskStack.length === 0) - { - gl.disable(gl.STENCIL_TEST); - } - else - { - gl.enable(gl.STENCIL_TEST); - } - } - - this.stencilMaskStack = stencilMaskStack; - } - - /** - * Applies the Mask and adds it to the current stencil stack. @alvin - * - * @param {PIXI.Graphics} graphics - The mask - */ - pushStencil(graphics) - { - this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); - - // this.renderer._activeRenderTarget.attachStencilBuffer(); - - const gl = this.renderer.gl; - const prevMaskCount = this.stencilMaskStack.length; - - if (prevMaskCount === 0) - { - gl.enable(gl.STENCIL_TEST); - } - - this.stencilMaskStack.push(graphics); - - // Increment the refference stencil value where the new mask overlaps with the old ones. - gl.colorMask(false, false, false, false); - gl.stencilFunc(gl.EQUAL, prevMaskCount, this._getBitwiseMask()); - gl.stencilOp(gl.KEEP, gl.KEEP, gl.INCR); - this.renderer.plugins.graphics.render(graphics); - - this._useCurrent(); - } - - /** - * Removes the last mask from the stencil stack. @alvin - */ - popStencil() - { - this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); - - const gl = this.renderer.gl; - const graphics = this.stencilMaskStack.pop(); - - if (this.stencilMaskStack.length === 0) - { - // the stack is empty! - gl.disable(gl.STENCIL_TEST); - gl.clear(gl.STENCIL_BUFFER_BIT); - gl.clearStencil(0); - } - else - { - // Decrement the refference stencil value where the popped mask overlaps with the other ones - gl.colorMask(false, false, false, false); - gl.stencilOp(gl.KEEP, gl.KEEP, gl.DECR); - this.renderer.plugins.graphics.render(graphics); - - this._useCurrent(); - } - } - - /** - * Setup renderer to use the current stencil data. - */ - _useCurrent() - { - const gl = this.renderer.gl; - - gl.colorMask(true, true, true, true); - gl.stencilFunc(gl.EQUAL, this.stencilMaskStack.length, this._getBitwiseMask()); - gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP); - } - - /** - * Fill 1s equal to the number of acitve stencil masks. - * - * @return {number} The bitwise mask. - */ - _getBitwiseMask() - { - return (1 << this.stencilMaskStack.length) - 1; - } - - /** - * Destroys the mask stack. - * - */ - destroy() - { - super.destroy(this); - - this.stencilMaskStack = null; - } -} diff --git a/packages/core/src/renderers/systems/WebGLSystem.js b/packages/core/src/renderers/systems/WebGLSystem.js deleted file mode 100644 index d3d5b31..0000000 --- a/packages/core/src/renderers/systems/WebGLSystem.js +++ /dev/null @@ -1,41 +0,0 @@ -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this manager works for. - */ - constructor(renderer) - { - /** - * The renderer this manager works for. - * - * @member {PIXI.Renderer} - */ - this.renderer = renderer; - - this.renderer.runners.contextChange.add(this); - } - - /** - * Generic method called when there is a WebGL context change. - * - */ - contextChange() - { - // do some codes init! - } - - /** - * Generic destroy methods to be overridden by the subclass - * - */ - destroy() - { - this.renderer.runners.contextChange.remove(this); - this.renderer = null; - } -} diff --git a/packages/core/src/renderers/systems/filter/FilterSystem.js b/packages/core/src/renderers/systems/filter/FilterSystem.js deleted file mode 100644 index e615037..0000000 --- a/packages/core/src/renderers/systems/filter/FilterSystem.js +++ /dev/null @@ -1,431 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; - -import RenderTexture from '../../../textures/RenderTexture'; -import Quad from '../../utils/Quad'; -import QuadUv from '../../utils/QuadUv'; -import { Rectangle } from '@pixi/math'; -import * as filterTransforms from '../../filters/filterTransforms'; -import bitTwiddle from 'bit-twiddle'; -import UniformGroup from '../../../shader/UniformGroup'; -import { DRAW_MODES } from '../../../../../constants'; - -// -/** - * @ignore - * @class - */ -class FilterState -{ - /** - * - */ - constructor() - { - this.renderTexture = null; - this.sourceFrame = new Rectangle(); - this.destinationFrame = new Rectangle(); - this.filters = []; - this.target = null; - this.legacy = false; - this.resolution = 1; - } -} - -/** - * @class - * @memberof PIXI.systems - * @extends PIXI.systems.WebGLSystem - */ -export default class FilterSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - /** - * stores a bunch of PO2 textures used for filtering - * @type {Object} - */ - this.texturePool = {}; - - /** - * a pool for storing filter states, save us creating new ones each tick - * @type {Array} - */ - this.statePool = []; - - /** - * A very simple geometry used when drawing a filter effect to the screen - * @type {Quad} - */ - this.quad = new Quad(); - - this.quadUv = new QuadUv(); - - /** - * Temporary rect for maths - * @type {PIXI.Rectangle} - */ - this.tempRect = new Rectangle(); - - this.activeState = {}; - - /** - * this uniform group is attached to filter uniforms when used - * @type {UniformGroup} - */ - this.globalUniforms = new UniformGroup({ - sourceFrame: this.tempRect, - destinationFrame: this.tempRect, - - // legacy variables - filterArea: new Float32Array(4), - filterClamp: new Float32Array(4), - }, true); - } - - /** - * Adds a new filter to the System. - * - * @param {PIXI.DisplayObject} target - The target of the filter to render. - * @param {PIXI.Filter[]} filters - The filters to apply. - */ - push(target, filters) - { - const renderer = this.renderer; - const filterStack = this.renderer.renderTexture.defaultFilterStack; - const state = this.statePool.pop() || new FilterState(); - - let resolution = filters[0].resolution; - let padding = filters[0].padding; - let autoFit = filters[0].autoFit; - let legacy = filters[0].legacy; - - for (let i = 1; i < filters.length; i++) - { - const filter = filters[i]; - - // lets use the lowest resolution.. - resolution = Math.min(resolution, filter.resolution); - // and the largest amount of padding! - padding = Math.max(padding, filter.padding); - // only auto fit if all filters are autofit - autoFit = autoFit || filter.autoFit; - - legacy = legacy || filter.legacy; - } - - filterStack.push(state); - - state.resolution = resolution; - - state.legacy = legacy; - - // round to whole number based on resolution - // TODO move that to the shader too? - state.sourceFrame = target.filterArea || target.getBounds(true); - - state.sourceFrame.pad(padding); - - if (autoFit) - { - state.sourceFrame.fit(this.renderer.renderTexture.destinationFrame); - } - - state.sourceFrame.round(resolution); - - state.renderTexture = this.getPotFilterTexture(state.sourceFrame.width, state.sourceFrame.height, resolution); - state.filters = filters; - - state.destinationFrame.width = state.renderTexture.width; - state.destinationFrame.height = state.renderTexture.height; - - state.renderTexture.filterFrame = state.sourceFrame; - renderer.renderTexture.bind(state.renderTexture, state.sourceFrame);// /, state.destinationFrame); - renderer.renderTexture.clear(); - } - - /** - * Pops off the filter and applies it. - * - */ - pop() - { - const renderer = this.renderer; - const filterStack = renderer.renderTexture.defaultFilterStack; - const state = filterStack.pop(); - const filters = state.filters; - - this.activeState = state; - - const globalUniforms = this.globalUniforms.uniforms; - - globalUniforms.sourceFrame = state.sourceFrame; - globalUniforms.destinationFrame = state.destinationFrame; - globalUniforms.resolution = state.resolution; - - // only update the rect if its legacy.. - if (state.legacy) - { - const filterArea = globalUniforms.filterArea; - const filterClamp = globalUniforms.filterClamp; - - filterArea[0] = state.destinationFrame.width; - filterArea[1] = state.destinationFrame.height; - filterArea[2] = state.sourceFrame.x; - filterArea[3] = state.sourceFrame.y; - - filterClamp[0] = 0.5 / state.resolution / state.destinationFrame.width; - filterClamp[1] = 0.5 / state.resolution / state.destinationFrame.height; - filterClamp[2] = (state.sourceFrame.width - 0.5) / state.resolution / state.destinationFrame.width; - filterClamp[3] = (state.sourceFrame.height - 0.5) / state.resolution / state.destinationFrame.height; - } - - this.globalUniforms.update(); - - const lastState = filterStack[filterStack.length - 1]; - - if (filters.length === 1) - { - filters[0].apply(this, state.renderTexture, lastState.renderTexture, false, state); - - this.returnFilterTexture(state.renderTexture); - } - else - { - let flip = state.renderTexture; - let flop = this.getPotFilterTexture( - flip.width, - flip.height, - state.resolution - ); - - flop.filterFrame = flip.filterFrame; - - let i = 0; - - for (i = 0; i < filters.length - 1; ++i) - { - filters[i].apply(this, flip, flop, true, state); - - const t = flip; - - flip = flop; - flop = t; - } - - filters[i].apply(this, flip, lastState.renderTexture, false, state); - - this.returnFilterTexture(flip); - this.returnFilterTexture(flop); - } - - this.statePool.push(state); - } - - /** - * Draws a filter. - * - * @param {PIXI.Filter} filter - The filter to draw. - * @param {PIXI.RenderTarget} input - The input render target. - * @param {PIXI.RenderTarget} output - The target to output to. - * @param {boolean} clear - Should the output be cleared before rendering to it - */ - applyFilter(filter, input, output, clear) - { - const renderer = this.renderer; - - renderer.renderTexture.bind(output, output ? output.filterFrame : null); - - if (clear) - { - // gl.disable(gl.SCISSOR_TEST); - renderer.renderTexture.clear(); - // gl.enable(gl.SCISSOR_TEST); - } - - // set the uniforms.. - filter.uniforms.uSampler = input; - filter.uniforms.filterGlobals = this.globalUniforms; - - // TODO make it so that the order of this does not matter.. - // because it does at the moment cos of global uniforms. - // they need to get resynced - - renderer.state.setState(filter.state); - renderer.shader.bind(filter); - - if (filter.legacy) - { - this.quadUv.map(input._frame, input.filterFrame); - - renderer.geometry.bind(this.quadUv); - renderer.geometry.draw(DRAW_MODES.TRIANGLES); - } - else - { - renderer.geometry.bind(this.quad); - renderer.geometry.draw(DRAW_MODES.TRIANGLE_STRIP); - } - } - - /** - * Calculates the mapped matrix. - * - * TODO playing around here.. this is temporary - (will end up in the shader) - * this returns a matrix that will normalise map filter cords in the filter to screen space - * - * @param {PIXI.Matrix} outputMatrix - the matrix to output to. - * @return {PIXI.Matrix} The mapped matrix. - */ - calculateScreenSpaceMatrix(outputMatrix) - { - const currentState = this.activeState; - - return filterTransforms.calculateScreenSpaceMatrix( - outputMatrix, - currentState.sourceFrame, - currentState.destinationFrame - ); - } - - /** - * This will map the filter coord so that a texture can be used based on the transform of a sprite - * - * @param {PIXI.Matrix} outputMatrix - The matrix to output to. - * @param {PIXI.Sprite} sprite - The sprite to map to. - * @return {PIXI.Matrix} The mapped matrix. - */ - calculateSpriteMatrix(outputMatrix, sprite) - { - const currentState = this.activeState; - - return filterTransforms.calculateSpriteMatrix( - outputMatrix, - currentState.sourceFrame, - currentState.destinationFrame, - sprite - ); - } - - /** - * Destroys this Filter System. - * - * @param {boolean} [contextLost=false] context was lost, do not free shaders - * - */ - destroy(contextLost = false) - { - if (!contextLost) - { - this.emptyPool(); - } - else - { - this.texturePool = {}; - } - } - - /** - * Gets a Power-of-Two render texture. - * - * TODO move to a seperate class could be on renderer? - * also - could cause issue with multiple contexts? - * - * @private - * @param {WebGLRenderingContext} gl - The webgl rendering context - * @param {number} minWidth - The minimum width of the render target. - * @param {number} minHeight - The minimum height of the render target. - * @param {number} resolution - The resolution of the render target. - * @return {PIXI.RenderTarget} The new render target. - */ - getPotFilterTexture(minWidth, minHeight, resolution) - { - minWidth = bitTwiddle.nextPow2(minWidth); - minHeight = bitTwiddle.nextPow2(minHeight); - resolution = resolution || 1; - - const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); - - if (!this.texturePool[key]) - { - this.texturePool[key] = []; - } - - let renderTexture = this.texturePool[key].pop(); - - if (!renderTexture) - { - // temporary bypass cache.. - // internally - this will cause a texture to be bound.. - renderTexture = RenderTexture.create({ - width: minWidth, - height: minHeight, - resolution, - }); - } - - return renderTexture; - } - - /** - * Gets extra render texture to use inside current filter - * - * @param {number} resolution resolution of the renderTexture - * @returns {PIXI.RenderTexture} - */ - getFilterTexture(resolution) - { - const rt = this.activeState.renderTexture; - - const filterTexture = this.getPotFilterTexture(rt.width, rt.height, resolution || rt.baseTexture.resolution); - - filterTexture.filterFrame = rt.filterFrame; - - return filterTexture; - } - - /** - * Frees a render texture back into the pool. - * - * @param {PIXI.RenderTarget} renderTexture - The renderTarget to free - */ - returnFilterTexture(renderTexture) - { - renderTexture.filterFrame = null; - - const base = renderTexture.baseTexture; - - const minWidth = base.width; - const minHeight = base.height; - - const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); - - this.texturePool[key].push(renderTexture); - } - - /** - * Empties the texture pool. - * - */ - emptyPool() - { - for (const i in this.texturePool) - { - const textures = this.texturePool[i]; - - if (textures) - { - for (let j = 0; j < textures.length; j++) - { - textures[j].destroy(true); - } - } - } - - this.texturePool = {}; - } -} diff --git a/packages/core/src/renderers/systems/geometry/GLBuffer.js b/packages/core/src/renderers/systems/geometry/GLBuffer.js deleted file mode 100644 index 0477e8a..0000000 --- a/packages/core/src/renderers/systems/geometry/GLBuffer.js +++ /dev/null @@ -1,9 +0,0 @@ -export default class GLBuffer -{ - constructor(buffer) - { - this.buffer = buffer; - this.updateID = -1; - this.byteLength = -1; - } -} diff --git a/packages/core/src/renderers/systems/geometry/GeometrySystem.js b/packages/core/src/renderers/systems/geometry/GeometrySystem.js deleted file mode 100644 index 26c8e9e..0000000 --- a/packages/core/src/renderers/systems/geometry/GeometrySystem.js +++ /dev/null @@ -1,388 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; -import GLBuffer from './GLBuffer'; - -const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class GeometrySystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this._activeGeometry = null; - this._activeVao = null; - - this.hasVao = true; - this.hasInstance = true; - } - - /** - * Sets up the renderer context and necessary buffers. - * - * @private - */ - contextChange() - { - const gl = this.gl = this.renderer.gl; - - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - - // webgl2 - if (!gl.createVertexArray) - { - // webgl 1! - let nativeVaoExtension = this.renderer.context.extensions.vertexArrayObject; - - if (this.renderer.legacy) - { - nativeVaoExtension = null; - } - - if (nativeVaoExtension) - { - gl.createVertexArray = () => - nativeVaoExtension.createVertexArrayOES(); - - gl.bindVertexArray = (vao) => - nativeVaoExtension.bindVertexArrayOES(vao); - - gl.deleteVertexArray = (vao) => - nativeVaoExtension.deleteVertexArrayOES(vao); - } - else - { - this.hasVao = false; - gl.createVertexArray = () => - { - // empty - }; - - gl.bindVertexArray = () => - { - // empty - }; - - gl.deleteVertexArray = () => - { - // empty - }; - } - } - - if (!gl.vertexAttribDivisor) - { - const instanceExt = gl.getExtension('ANGLE_instanced_arrays'); - - if (instanceExt) - { - gl.vertexAttribDivisor = (a, b) => - instanceExt.vertexAttribDivisorANGLE(a, b); - - gl.drawElementsInstanced = (a, b, c, d, e) => - instanceExt.drawElementsInstancedANGLE(a, b, c, d, e); - - gl.drawArraysInstanced = (a, b, c, d) => - instanceExt.drawArraysInstancedANGLE(a, b, c, d); - } - else - { - this.hasInstance = false; - } - } - } - - /** - * Binds geometry so that is can be drawn. Creating a Vao if required - * @private - * @param {PIXI.Geometry} geometry instance of geometry to bind - */ - bind(geometry, shader) - { - shader = shader || this.renderer.shader.shader; - - const gl = this.gl; - - // not sure the best way to address this.. - // currently different shaders require different VAOs for the same geometry - // Still mulling over the best way to solve this one.. - // will likely need to modify the shader attribute locations at run time! - let vaos = geometry.glVertexArrayObjects[this.CONTEXT_UID]; - - if (!vaos) - { - geometry.glVertexArrayObjects[this.CONTEXT_UID] = vaos = {}; - } - - const vao = vaos[shader.program.id] || this.initGeometryVao(geometry, shader.program); - - this._activeGeometry = geometry; - - if (this._activeVao !== vao) - { - this._activeVao = vao; - - if (this.hasVao) - { - gl.bindVertexArray(vao); - } - else - { - this.activateVao(geometry, shader.program); - } - } - - // TODO - optimise later! - // don't need to loop through if nothing changed! - // maybe look to add an 'autoupdate' to geometry? - this.updateBuffers(); - } - - reset() - { - this.unbind(); - } - - updateBuffers() - { - const geometry = this._activeGeometry; - const gl = this.gl; - - for (let i = 0; i < geometry.buffers.length; i++) - { - const buffer = geometry.buffers[i]; - - const glBuffer = buffer._glBuffers[this.CONTEXT_UID]; - - if (buffer._updateID !== glBuffer.updateID) - { - glBuffer.updateID = buffer._updateID; - - // TODO can cache this on buffer! maybe added a getter / setter? - const type = buffer.index ? gl.ELEMENT_ARRAY_BUFFER : gl.ARRAY_BUFFER; - const drawType = buffer.static ? gl.STATIC_DRAW : gl.DYNAMIC_DRAW; - - gl.bindBuffer(type, glBuffer.buffer); - - if (glBuffer.byteLength >= buffer.data.byteLength) - { - // offset is always zero for now! - gl.bufferSubData(type, 0, buffer.data); - } - else - { - gl.bufferData(type, buffer.data, drawType); - } - } - } - } - - checkCompatability(geometry, program) - { - // geometry must have at least all the attributes that the shader requires. - const geometryAttributes = geometry.attributes; - const shaderAttributes = program.attributeData; - - for (const j in shaderAttributes) - { - if (!geometryAttributes[j]) - { - throw new Error(`shader and geometry incompatible, geometry missing the "${j}" attribute`); - } - } - } - - /** - * Creates a Vao with the same structure as the geometry and stores it on the geometry. - * @private - * @param {PIXI.Geometry} geometry instance of geometry to to generate Vao for - */ - initGeometryVao(geometry, program) - { - this.checkCompatability(geometry, program); - - const gl = this.gl; - const CONTEXT_UID = this.CONTEXT_UID; - const buffers = geometry.buffers; - const attributes = geometry.attributes; - - const tempStride = {}; - const tempStart = {}; - - for (const j in buffers) - { - tempStride[j] = 0; - tempStart[j] = 0; - } - - for (const j in attributes) - { - if (!attributes[j].size && program.attributeData[j]) - { - attributes[j].size = program.attributeData[j].size; - } - - tempStride[attributes[j].buffer] += attributes[j].size * byteSizeMap[attributes[j].type]; - } - - for (const j in attributes) - { - const attribute = attributes[j]; - const attribSize = attribute.size; - - if (attribute.stride === undefined) - { - if (tempStride[attribute.buffer] === attribSize * byteSizeMap[attribute.type]) - { - attribute.stride = 0; - } - else - { - attribute.stride = tempStride[attribute.buffer]; - } - } - - if (attribute.start === undefined) - { - attribute.start = tempStart[attribute.buffer]; - - tempStart[attribute.buffer] += attribSize * byteSizeMap[attribute.type]; - } - } - - // first update - and create the buffers! - // only create a gl buffer if it actually gets - for (let i = 0; i < buffers.length; i++) - { - const buffer = buffers[i]; - - if (!buffer._glBuffers[CONTEXT_UID]) - { - buffer._glBuffers[CONTEXT_UID] = new GLBuffer(gl.createBuffer()); - } - } - - // TODO - maybe make this a data object? - // lets wait to see if we need to first! - const vao = gl.createVertexArray(); - - gl.bindVertexArray(vao); - - this.activateVao(geometry, program); - - gl.bindVertexArray(null); - - // add it to the cache! - geometry.glVertexArrayObjects[this.CONTEXT_UID][program.id] = vao; - - return vao; - } - - activateVao(geometry, program) - { - const gl = this.gl; - const CONTEXT_UID = this.CONTEXT_UID; - const buffers = geometry.buffers; - const attributes = geometry.attributes; - - if (geometry.indexBuffer) - { - // first update the index buffer if we have one.. - gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, geometry.indexBuffer._glBuffers[CONTEXT_UID].buffer); - } - - let lastBuffer = null; - - // add a new one! - for (const j in attributes) - { - const attribute = attributes[j]; - const buffer = buffers[attribute.buffer]; - const glBuffer = buffer._glBuffers[CONTEXT_UID]; - - if (program.attributeData[j]) - { - if (lastBuffer !== glBuffer) - { - gl.bindBuffer(gl.ARRAY_BUFFER, glBuffer.buffer); - - lastBuffer = glBuffer; - } - - const location = program.attributeData[j].location; - - // TODO introduce state again - // we can optimise this for older devices that have no VAOs - gl.enableVertexAttribArray(location); - - gl.vertexAttribPointer(location, - attribute.size, - attribute.type || gl.FLOAT, - attribute.normalized, - attribute.stride, - attribute.start); - - if (attribute.instance) - { - // TODO calculate instance count based of this... - if (this.hasInstance) - { - gl.vertexAttribDivisor(location, 1); - } - else - { - throw new Error('geometry error, GPU Instancing is not supported on this device'); - } - } - } - } - } - - draw(type, size, start, instanceCount) - { - const gl = this.gl; - const geometry = this._activeGeometry; - - // TODO.. this should not change so maybe cache the function? - - if (geometry.indexBuffer) - { - if (geometry.instanced) - { - /* eslint-disable max-len */ - gl.drawElementsInstanced(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2, instanceCount || 1); - /* eslint-enable max-len */ - } - else - { - gl.drawElements(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2); - } - } - else - if (geometry.instanced) - { - // TODO need a better way to calculate size.. - gl.drawArraysInstanced(type, start, size || geometry.getSize(), instanceCount || 1); - } - else - { - gl.drawArrays(type, start, size || geometry.getSize()); - } - - return this; - } - - unbind() - { - this.gl.bindVertexArray(null); - this._activeVao = null; - this._activeGeometry = null; - } -} diff --git a/packages/core/src/renderers/systems/geometry/setVertexAttribArrays.js b/packages/core/src/renderers/systems/geometry/setVertexAttribArrays.js deleted file mode 100644 index 23b6959..0000000 --- a/packages/core/src/renderers/systems/geometry/setVertexAttribArrays.js +++ /dev/null @@ -1,54 +0,0 @@ -// var GL_MAP = {}; - -/** - * @param gl {WebGLRenderingContext} The current WebGL context - * @param attribs {*} - * @param state {*} - */ -export default function setVertexAttribArrays(gl, attribs, state) -{ - let i; - - if (state) - { - const tempAttribState = state.tempAttribState; - const attribState = state.attribState; - - for (i = 0; i < tempAttribState.length; i++) - { - tempAttribState[i] = false; - } - - // set the new attribs - for (i = 0; i < attribs.length; i++) - { - tempAttribState[attribs[i].attribute.location] = true; - } - - for (i = 0; i < attribState.length; i++) - { - if (attribState[i] !== tempAttribState[i]) - { - attribState[i] = tempAttribState[i]; - - if (state.attribState[i]) - { - gl.enableVertexAttribArray(i); - } - else - { - gl.disableVertexAttribArray(i); - } - } - } - } - else - { - for (i = 0; i < attribs.length; i++) - { - const attrib = attribs[i]; - - gl.enableVertexAttribArray(attrib.attribute.location); - } - } -} diff --git a/packages/core/src/renderers/systems/index.js b/packages/core/src/renderers/systems/index.js deleted file mode 100644 index c843f03..0000000 --- a/packages/core/src/renderers/systems/index.js +++ /dev/null @@ -1,6 +0,0 @@ -/** - * Systems are individual components to the Renderer pipeline. - * @namespace PIXI.systems - */ -export { default as WebGLSystem } from './WebGLSystem'; -export { default as FilterSystem } from './filter/FilterSystem'; diff --git a/packages/core/src/renderers/systems/shader/GLProgram.js b/packages/core/src/renderers/systems/shader/GLProgram.js deleted file mode 100644 index 92980ca..0000000 --- a/packages/core/src/renderers/systems/shader/GLProgram.js +++ /dev/null @@ -1,43 +0,0 @@ -/** - * Helper class to create a webGL Program - * - * @class - * @private - */ -export default class GLProgram -{ - constructor(program, uniformData) - { - /** - * The shader program - * - * @member {WebGLProgram} - */ - this.program = program; - - /** - * holds the uniform data which contains uniform locations - * and current uniform values used for caching and preventing undeed GPU commands - * @type {Object} - */ - this.uniformData = uniformData; - - /** - * uniformGroups holds the various upload functions for the shader. Each uniform group - * and program have a unique upload function generated. - * @type {Object} - */ - this.uniformGroups = {}; - } - - /** - * Destroys this program - * TODO - */ - destroy() - { - this.uniformData = null; - this.uniformGroups = null; - this.program = null; - } -} diff --git a/packages/core/src/renderers/systems/shader/ShaderSystem.js b/packages/core/src/renderers/systems/shader/ShaderSystem.js deleted file mode 100644 index 6cf57b6..0000000 --- a/packages/core/src/renderers/systems/shader/ShaderSystem.js +++ /dev/null @@ -1,170 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; -import GLProgram from './GLProgram'; -import generateUniformsSync from '../../../shader/generateUniformsSync'; -import defaultValue from '../../../shader/defaultValue'; -import compileProgram from './shader/compileProgram'; - -let UID = 0; - -/** - * Helper class to create a webGL Texture - * - * @class - * @memberof PIXI - */ -export default class ShaderSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - A reference to the current renderer - */ - constructor(renderer) - { - super(renderer); - - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = null; - - this.shader = null; - this.program = null; - - this.id = UID++; - } - - contextChange(gl) - { - this.gl = gl; - } - - /** - * Changes the current shader to the one given in parameter - * - * @param {PIXI.Shader} shader - the new shader - * @param {boolean} dontSync - false if the shader should automatically sync its uniforms. - * @returns {PIXI.glCore.glProgram} the glProgram that belongs to the shader. - */ - bind(shader, dontSync) - { - shader.uniforms.globals = this.renderer.globalUniforms; - - const program = shader.program; - const glProgram = program.glPrograms[this.renderer.CONTEXT_UID] || this.generateShader(shader); - - this.shader = shader; - - // TODO - some current pixi plugins bypass this.. so it not safe to use yet.. - if (this.program !== program) - { - this.program = program; - this.gl.useProgram(glProgram.program); - } - - if (!dontSync) - { - this.syncUniformGroup(shader.uniformGroup); - } - - return glProgram; - } - - /** - * Uploads the uniforms values to the currently bound shader. - * - * @param {object} uniforms - the uniforms valiues that be applied to the current shader - */ - setUniforms(uniforms) - { - const shader = this.shader.program; - const glProgram = shader.glPrograms[this.renderer.CONTEXT_UID]; - - shader.syncUniforms(glProgram.uniformData, uniforms, this.renderer); - } - - syncUniformGroup(group) - { - const glProgram = this.getglProgram(); - - if (!group.static || group.dirtyId !== glProgram.uniformGroups[group.id]) - { - glProgram.uniformGroups[group.id] = group.dirtyId; - const syncFunc = group.syncUniforms[this.shader.program.id] || this.createSyncGroups(group); - - syncFunc(glProgram.uniformData, group.uniforms, this.renderer); - } - } - - createSyncGroups(group) - { - group.syncUniforms[this.shader.program.id] = generateUniformsSync(group, this.shader.program.uniformData); - - return group.syncUniforms[this.shader.program.id]; - } - - /** - * Returns the underlying GLShade rof the currently bound shader. - * This can be handy for when you to have a little more control over the setting of your uniforms. - * - * @return {PIXI.glCore.Shader} the glProgram for the currently bound Shader for this context - */ - getglProgram() - { - if (this.shader) - { - return this.shader.program.glPrograms[this.renderer.CONTEXT_UID]; - } - - return null; - } - - /** - * Generates a glProgram verion of the Shader provided. - * - * @private - * @param {PIXI.Shader} shader the shader that the glProgram will be based on. - * @return {PIXI.glCore.glProgram} A shiney new glProgram - */ - generateShader(shader) - { - const gl = this.gl; - - const program = shader.program; - - const attribMap = {}; - - for (const i in program.attributeData) - { - attribMap[i] = program.attributeData[i].location; - } - - const shaderProgram = compileProgram(gl, program.vertexSrc, program.fragmentSrc, attribMap); - const uniformData = {}; - - for (const i in program.uniformData) - { - const data = program.uniformData[i]; - - uniformData[i] = { - location: gl.getUniformLocation(shaderProgram, i), - value: defaultValue(data.type, data.size), - }; - } - - const glProgram = new GLProgram(shaderProgram, uniformData); - - program.glPrograms[this.renderer.CONTEXT_UID] = glProgram; - - return glProgram; - } - - /** - * Destroys this System and removes all its textures - */ - destroy() - { - // TODO implement destroy method for ShaderSystem - this.destroyed = true; - } -} diff --git a/packages/core/src/renderers/systems/shader/shader/compileProgram.js b/packages/core/src/renderers/systems/shader/shader/compileProgram.js deleted file mode 100644 index 8c6ccb9..0000000 --- a/packages/core/src/renderers/systems/shader/shader/compileProgram.js +++ /dev/null @@ -1,78 +0,0 @@ -/** - * @method compileProgram - * @memberof PIXI.glCore.shader - * @param gl {WebGLRenderingContext} The current WebGL context {WebGLProgram} - * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. - * @param fragmentSrc {string|string[]} The fragment shader source as an array of strings. - * @param attributeLocations {Object} An attribute location map that lets you manually set the attribute locations - * @return {WebGLProgram} the shader program - */ -export default function compileProgram(gl, vertexSrc, fragmentSrc, attributeLocations) -{ - const glVertShader = compileShader(gl, gl.VERTEX_SHADER, vertexSrc); - const glFragShader = compileShader(gl, gl.FRAGMENT_SHADER, fragmentSrc); - - let program = gl.createProgram(); - - gl.attachShader(program, glVertShader); - gl.attachShader(program, glFragShader); - - // optionally, set the attributes manually for the program rather than letting WebGL decide.. - if (attributeLocations) - { - for (const i in attributeLocations) - { - gl.bindAttribLocation(program, attributeLocations[i], i); - } - } - - gl.linkProgram(program); - - // if linking fails, then log and cleanup - if (!gl.getProgramParameter(program, gl.LINK_STATUS)) - { - console.error('Pixi.js Error: Could not initialize shader.'); - console.error('gl.VALIDATE_STATUS', gl.getProgramParameter(program, gl.VALIDATE_STATUS)); - console.error('gl.getError()', gl.getError()); - - // if there is a program info log, log it - if (gl.getProgramInfoLog(program) !== '') - { - console.warn('Pixi.js Warning: gl.getProgramInfoLog()', gl.getProgramInfoLog(program)); - } - - gl.deleteProgram(program); - program = null; - } - - // clean up some shaders - gl.deleteShader(glVertShader); - gl.deleteShader(glFragShader); - - return program; -} - -/** - * @private - * @param gl {WebGLRenderingContext} The current WebGL context {WebGLProgram} - * @param type {Number} the type, can be either VERTEX_SHADER or FRAGMENT_SHADER - * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. - * @return {WebGLShader} the shader - */ -function compileShader(gl, type, src) -{ - const shader = gl.createShader(type); - - gl.shaderSource(shader, src); - gl.compileShader(shader); - - if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) - { - console.warn(src); - console.error(gl.getShaderInfoLog(shader)); - - return null; - } - - return shader; -} diff --git a/packages/core/src/renderers/systems/shader/shader/defaultValue.js b/packages/core/src/renderers/systems/shader/shader/defaultValue.js deleted file mode 100644 index 97290bd..0000000 --- a/packages/core/src/renderers/systems/shader/shader/defaultValue.js +++ /dev/null @@ -1,79 +0,0 @@ -/** - * @method defaultValue - * @memberof PIXI.glCore.shader - * @param type {String} Type of value - * @param size {Number} - */ -export default function defaultValue(type, size) -{ - switch (type) - { - case 'float': - return 0; - - case 'vec2': - return new Float32Array(2 * size); - - case 'vec3': - return new Float32Array(3 * size); - - case 'vec4': - return new Float32Array(4 * size); - - case 'int': - case 'sampler2D': - case 'sampler2DArray': - return 0; - - case 'ivec2': - return new Int32Array(2 * size); - - case 'ivec3': - return new Int32Array(3 * size); - - case 'ivec4': - return new Int32Array(4 * size); - - case 'bool': - return false; - - case 'bvec2': - - return booleanArray(2 * size); - - case 'bvec3': - return booleanArray(3 * size); - - case 'bvec4': - return booleanArray(4 * size); - - case 'mat2': - return new Float32Array([1, 0, - 0, 1]); - - case 'mat3': - return new Float32Array([1, 0, 0, - 0, 1, 0, - 0, 0, 1]); - - case 'mat4': - return new Float32Array([1, 0, 0, 0, - 0, 1, 0, 0, - 0, 0, 1, 0, - 0, 0, 0, 1]); - } - - return null; -} - -function booleanArray(size) -{ - const array = new Array(size); - - for (let i = 0; i < array.length; i++) - { - array[i] = false; - } - - return array; -} diff --git a/packages/core/src/renderers/systems/shader/shader/index.js b/packages/core/src/renderers/systems/shader/shader/index.js deleted file mode 100644 index ba7a317..0000000 --- a/packages/core/src/renderers/systems/shader/shader/index.js +++ /dev/null @@ -1,8 +0,0 @@ -/** - * @namespace PIXI.glCore.shader - */ -export { default as compileProgram } from './compileProgram'; -export { default as defaultValue } from './defaultValue'; -export { default as setPrecision } from './setPrecision'; -export { default as mapSize } from './mapSize'; -export { default as mapType } from './mapType'; diff --git a/packages/core/src/renderers/systems/shader/shader/mapSize.js b/packages/core/src/renderers/systems/shader/shader/mapSize.js deleted file mode 100644 index b681554..0000000 --- a/packages/core/src/renderers/systems/shader/shader/mapSize.js +++ /dev/null @@ -1,33 +0,0 @@ -const GLSL_TO_SIZE = { - float: 1, - vec2: 2, - vec3: 3, - vec4: 4, - - int: 1, - ivec2: 2, - ivec3: 3, - ivec4: 4, - - bool: 1, - bvec2: 2, - bvec3: 3, - bvec4: 4, - - mat2: 4, - mat3: 9, - mat4: 16, - - sampler2D: 1, -}; - -/** - * @method mapSize - * @memberof PIXI.glCore.shader - * @param type {String} - * @return {Number} - */ -export default function mapSize(type) -{ - return GLSL_TO_SIZE[type]; -} diff --git a/packages/core/src/renderers/systems/shader/shader/mapType.js b/packages/core/src/renderers/systems/shader/shader/mapType.js deleted file mode 100644 index d288b7c..0000000 --- a/packages/core/src/renderers/systems/shader/shader/mapType.js +++ /dev/null @@ -1,45 +0,0 @@ -let GL_TABLE = null; - -const GL_TO_GLSL_TYPES = { - FLOAT: 'float', - FLOAT_VEC2: 'vec2', - FLOAT_VEC3: 'vec3', - FLOAT_VEC4: 'vec4', - - INT: 'int', - INT_VEC2: 'ivec2', - INT_VEC3: 'ivec3', - INT_VEC4: 'ivec4', - - BOOL: 'bool', - BOOL_VEC2: 'bvec2', - BOOL_VEC3: 'bvec3', - BOOL_VEC4: 'bvec4', - - FLOAT_MAT2: 'mat2', - FLOAT_MAT3: 'mat3', - FLOAT_MAT4: 'mat4', - - SAMPLER_2D: 'sampler2D', - SAMPLER_CUBE: 'samplerCube', - SAMPLER_2D_ARRAY: 'sampler2DArray', -}; - -export default function mapType(gl, type) -{ - if (!GL_TABLE) - { - const typeNames = Object.keys(GL_TO_GLSL_TYPES); - - GL_TABLE = {}; - - for (let i = 0; i < typeNames.length; ++i) - { - const tn = typeNames[i]; - - GL_TABLE[gl[tn]] = GL_TO_GLSL_TYPES[tn]; - } - } - - return GL_TABLE[type]; -} diff --git a/packages/core/src/renderers/systems/shader/shader/setPrecision.js b/packages/core/src/renderers/systems/shader/shader/setPrecision.js deleted file mode 100644 index e2c1f34..0000000 --- a/packages/core/src/renderers/systems/shader/shader/setPrecision.js +++ /dev/null @@ -1,18 +0,0 @@ -/** - * Sets the float precision on the shader. If the precision is already present this function will do nothing - * @param {string} src the shader source - * @param {string} precision The float precision of the shader. Options are 'lowp', 'mediump' or 'highp'. - * - * @return {string} modified shader source - */ -export default function setPrecision(src, precision) -{ - src = src.trim(); - - if (src.substring(0, 9) !== 'precision' && src.substring(0, 1) !== '#') - { - return `precision ${precision} float;\n${src}`; - } - - return src; -} diff --git a/packages/core/src/renderers/systems/textures/GLTexture.js b/packages/core/src/renderers/systems/textures/GLTexture.js deleted file mode 100644 index c3fb8dc..0000000 --- a/packages/core/src/renderers/systems/textures/GLTexture.js +++ /dev/null @@ -1,33 +0,0 @@ -export default class GLTexture -{ - constructor(texture) - { - /** - * The WebGL texture - * - * @member {WebGLTexture} - */ - this.texture = texture; - - this.width = -1; - this.height = -1; - - /** - * Texture contents dirty flag - * @member {number} - */ - this.dirtyId = -1; - - /** - * Texture style dirty flag - * @type {number} - */ - this.dirtyStyleId = -1; - - /** - * Whether mip levels has to be generated - * @type {boolean} - */ - this.mipmap = false; - } -} diff --git a/packages/core/src/renderers/systems/textures/TextureGCSystem.js b/packages/core/src/renderers/systems/textures/TextureGCSystem.js deleted file mode 100644 index b089938..0000000 --- a/packages/core/src/renderers/systems/textures/TextureGCSystem.js +++ /dev/null @@ -1,110 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; -import { GC_MODES } from '@pixi/constants'; -import { settings } from '@pixi/settings'; - -/** - * TextureGarbageCollector. This class manages the GPU and ensures that it does not get clogged - * up with textures that are no longer being used. - * - * @class - * @memberof PIXI - */ -export default class TextureGCSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this.count = 0; - this.checkCount = 0; - this.maxIdle = settings.GC_MAX_IDLE; - this.checkCountMax = settings.GC_MAX_CHECK_COUNT; - this.mode = settings.GC_MODE; - } - - /** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ - postrender() - { - this.count++; - - if (this.mode === GC_MODES.MANUAL) - { - return; - } - - this.checkCount++; - - if (this.checkCount > this.checkCountMax) - { - this.checkCount = 0; - - this.run(); - } - } - - /** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ - run() - { - const tm = this.renderer.texture; - const managedTextures = tm.managedTextures; - let wasRemoved = false; - - for (let i = 0; i < managedTextures.length; i++) - { - const texture = managedTextures[i]; - - // only supports non generated textures at the moment! - if (!texture.frameBuffer && this.count - texture.touched > this.maxIdle) - { - tm.destroyTexture(texture, true); - managedTextures[i] = null; - wasRemoved = true; - } - } - - if (wasRemoved) - { - let j = 0; - - for (let i = 0; i < managedTextures.length; i++) - { - if (managedTextures[i] !== null) - { - managedTextures[j++] = managedTextures[i]; - } - } - - managedTextures.length = j; - } - } - - /** - * Removes all the textures within the specified displayObject and its children from the GPU - * - * @param {PIXI.DisplayObject} displayObject - the displayObject to remove the textures from. - */ - unload(displayObject) - { - const tm = this.renderer.textureSystem; - - // only destroy non generated textures - if (displayObject._texture && displayObject._texture._glRenderTargets) - { - tm.destroyTexture(displayObject._texture); - } - - for (let i = displayObject.children.length - 1; i >= 0; i--) - { - this.unload(displayObject.children[i]); - } - } -} diff --git a/packages/core/src/renderers/systems/textures/TextureSystem.js b/packages/core/src/renderers/systems/textures/TextureSystem.js deleted file mode 100644 index ccc5de9..0000000 --- a/packages/core/src/renderers/systems/textures/TextureSystem.js +++ /dev/null @@ -1,281 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; -import GLTexture from './GLTexture'; -import { removeItems } from '@pixi/utils'; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class TextureSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - // TODO set to max textures... - this.boundTextures = [ - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - ]; - - this.currentLocation = -1; - - this.managedTextures = []; - } - - /** - * Sets up the renderer context and necessary buffers. - * - * @private - */ - contextChange() - { - const gl = this.gl = this.renderer.gl; - - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - - // TODO move this.. to a nice make empty textures class.. - this.emptyTextures = {}; - - const emptyTexture2D = new GLTexture(gl.createTexture()); - - gl.bindTexture(gl.TEXTURE_2D, emptyTexture2D.texture); - gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array(4)); - - this.emptyTextures[gl.TEXTURE_2D] = emptyTexture2D; - this.emptyTextures[gl.TEXTURE_CUBE_MAP] = new GLTexture(gl.createTexture()); - - gl.bindTexture(gl.TEXTURE_CUBE_MAP, this.emptyTextures[gl.TEXTURE_CUBE_MAP].texture); - - let i; - - for (i = 0; i < 6; i++) - { - gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, null); - } - - gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_MAG_FILTER, gl.LINEAR); - gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_MIN_FILTER, gl.LINEAR); - - for (i = 0; i < this.boundTextures.length; i++) - { - this.bind(null, i); - } - } - - bind(texture, location) - { - const gl = this.gl; - - location = location || 0; - - if (this.currentLocation !== location) - { - this.currentLocation = location; - gl.activeTexture(gl.TEXTURE0 + location); - } - - if (texture) - { - texture = texture.baseTexture || texture; - - if (texture.valid) - { - texture.touched = this.renderer.textureGC.count; - - const glTexture = texture._glTextures[this.CONTEXT_UID] || this.initTexture(texture); - - gl.bindTexture(texture.target, glTexture.texture); - - if (glTexture.dirtyId !== texture.dirtyId) - { - this.updateTexture(texture); - } - - this.boundTextures[location] = texture; - } - } - else - { - gl.bindTexture(gl.TEXTURE_2D, this.emptyTextures[gl.TEXTURE_2D].texture); - this.boundTextures[location] = null; - } - } - - unbind(texture) - { - const gl = this.gl; - - for (let i = 0; i < this.boundTextures.length; i++) - { - if (this.boundTextures[i] === texture) - { - if (this.currentLocation !== i) - { - gl.activeTexture(gl.TEXTURE0 + i); - this.currentLocation = i; - } - - gl.bindTexture(gl.TEXTURE_2D, this.emptyTextures[texture.target].texture); - this.boundTextures[i] = null; - } - } - } - - initTexture(texture) - { - const glTexture = new GLTexture(this.gl.createTexture()); - - // guarentee an update.. - glTexture.dirtyId = -1; - - texture._glTextures[this.CONTEXT_UID] = glTexture; - - this.managedTextures.push(texture); - texture.on('dispose', this.destroyTexture, this); - - return glTexture; - } - - updateTexture(texture) - { - const glTexture = texture._glTextures[this.CONTEXT_UID]; - const renderer = this.renderer; - - if (texture.resource && texture.resource.upload(renderer, texture, glTexture)) - { - // texture is uploaded, dont do anything! - } - else - { - // default, renderTexture-like logic - const width = texture.realWidth; - const height = texture.realHeight; - const gl = renderer.gl; - - if (glTexture.width !== width - || glTexture.height !== height - || glTexture.dirtyId < 0) - { - glTexture.width = width; - glTexture.height = height; - - gl.texImage2D(texture.target, 0, - texture.format, - width, - height, - 0, - texture.format, - texture.type, - null); - } - } - - // lets only update what changes.. - if (texture.dirtyStyleId !== glTexture.dirtyStyleId) - { - this.updateTextureStyle(texture); - } - glTexture.dirtyId = texture.dirtyId; - } - - /** - * Deletes the texture from WebGL - * - * @param {PIXI.BaseTexture|PIXI.Texture} texture - the texture to destroy - * @param {boolean} [skipRemove=false] - Whether to skip removing the texture from the TextureManager. - */ - destroyTexture(texture, skipRemove) - { - const gl = this.gl; - - texture = texture.baseTexture || texture; - - if (texture._glTextures[this.renderer.CONTEXT_UID]) - { - this.unbind(texture); - - gl.deleteTexture(texture._glTextures[this.renderer.CONTEXT_UID].texture); - texture.off('dispose', this.destroyTexture, this); - - delete texture._glTextures[this.renderer.CONTEXT_UID]; - - if (!skipRemove) - { - const i = this.managedTextures.indexOf(texture); - - if (i !== -1) - { - removeItems(this.managedTextures, i, 1); - } - } - } - } - - updateTextureStyle(texture) - { - const glTexture = texture._glTextures[this.CONTEXT_UID]; - - glTexture.mipmap = texture.mipmap && texture.isPowerOfTwo; - if (!glTexture) - { - return; - } - - if (texture.resource && texture.resource.style(this.renderer, texture, glTexture)) - { - // style is set, dont do anything! - } - else - { - this.setStyle(texture, glTexture); - } - - glTexture.dirtyStyleId = texture.dirtyStyleId; - } - - setStyle(texture, glTexture) - { - const gl = this.gl; - - if (glTexture.mipmap) - { - gl.generateMipmap(texture.target); - } - - gl.texParameteri(texture.target, gl.TEXTURE_WRAP_S, texture.wrapMode); - gl.texParameteri(texture.target, gl.TEXTURE_WRAP_T, texture.wrapMode); - - if (glTexture.mipmap) - { - /* eslint-disable max-len */ - gl.texParameteri(texture.target, gl.TEXTURE_MIN_FILTER, texture.scaleMode ? gl.LINEAR_MIPMAP_LINEAR : gl.NEAREST_MIPMAP_NEAREST); - /* eslint-disable max-len */ - } - else - { - gl.texParameteri(texture.target, gl.TEXTURE_MIN_FILTER, texture.scaleMode ? gl.LINEAR : gl.NEAREST); - } - - gl.texParameteri(texture.target, gl.TEXTURE_MAG_FILTER, texture.scaleMode ? gl.LINEAR : gl.NEAREST); - } -} diff --git a/packages/core/src/renderers/utils/ObjectRenderer.js b/packages/core/src/renderers/utils/ObjectRenderer.js deleted file mode 100644 index f67a273..0000000 --- a/packages/core/src/renderers/utils/ObjectRenderer.js +++ /dev/null @@ -1,48 +0,0 @@ -import WebGLSystem from '../systems/WebGLSystem'; - -/** - * Base for a common object renderer that can be used as a system renderer plugin. - * - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class ObjectRenderer extends WebGLSystem -{ - /** - * Starts the renderer and sets the shader - * - */ - start() - { - // set the shader.. - } - - /** - * Stops the renderer - * - */ - stop() - { - this.flush(); - } - - /** - * Stub method for rendering content and emptying the current batch. - * - */ - flush() - { - // flush! - } - - /** - * Renders an object - * - * @param {PIXI.DisplayObject} object - The object to render. - */ - render(object) // eslint-disable-line no-unused-vars - { - // render the object - } -} diff --git a/packages/core/src/renderers/utils/Quad.js b/packages/core/src/renderers/utils/Quad.js deleted file mode 100644 index 5c1517b..0000000 --- a/packages/core/src/renderers/utils/Quad.js +++ /dev/null @@ -1,26 +0,0 @@ -import Geometry from '../../geometry/Geometry'; - -/** - * Helper class to create a quad - * - * @class - * @memberof PIXI - */ -export default class Quad extends Geometry -{ - /** - * just a quad - */ - constructor() - { - super(); - - this.addAttribute('aVertexPosition', [ - 0, 0, - 1, 0, - 1, 1, - 0, 1, - ]) - .addIndex([0, 1, 3, 2]); - } -} diff --git a/packages/core/src/renderers/utils/QuadUv.js b/packages/core/src/renderers/utils/QuadUv.js deleted file mode 100644 index 97bda89..0000000 --- a/packages/core/src/renderers/utils/QuadUv.js +++ /dev/null @@ -1,103 +0,0 @@ -import Geometry from '../../geometry/Geometry'; -import Buffer from '../../geometry/Buffer'; - -/** - * Helper class to create a quad with uvs like in v4 - * - * @class - * @memberof PIXI - */ -export default class QuadUv extends Geometry -{ - constructor() - { - super(); - - /** - * An array of vertices - * - * @member {Float32Array} - */ - this.vertices = new Float32Array([ - -1, -1, - 1, -1, - 1, 1, - -1, 1, - ]); - - /** - * The Uvs of the quad - * - * @member {Float32Array} - */ - this.uvs = new Float32Array([ - 0, 0, - 1, 0, - 1, 1, - 0, 1, - ]); - - this.vertexBuffer = new Buffer(this.vertices); - this.uvBuffer = new Buffer(this.uvs); - - this.addAttribute('aVertexPosition', this.vertexBuffer) - .addAttribute('aTextureCoord', this.uvBuffer) - .addIndex([0, 1, 2, 0, 2, 3]); - } - - /** - * Maps two Rectangle to the quad. - * - * @param {PIXI.Rectangle} targetTextureFrame - the first rectangle - * @param {PIXI.Rectangle} destinationFrame - the second rectangle - * @return {PIXI.Quad} Returns itself. - */ - map(targetTextureFrame, destinationFrame) - { - let x = 0; // destinationFrame.x / targetTextureFrame.width; - let y = 0; // destinationFrame.y / targetTextureFrame.height; - - this.uvs[0] = x; - this.uvs[1] = y; - - this.uvs[2] = x + (destinationFrame.width / targetTextureFrame.width); - this.uvs[3] = y; - - this.uvs[4] = x + (destinationFrame.width / targetTextureFrame.width); - this.uvs[5] = y + (destinationFrame.height / targetTextureFrame.height); - - this.uvs[6] = x; - this.uvs[7] = y + (destinationFrame.height / targetTextureFrame.height); - - x = destinationFrame.x; - y = destinationFrame.y; - - this.vertices[0] = x; - this.vertices[1] = y; - - this.vertices[2] = x + destinationFrame.width; - this.vertices[3] = y; - - this.vertices[4] = x + destinationFrame.width; - this.vertices[5] = y + destinationFrame.height; - - this.vertices[6] = x; - this.vertices[7] = y + destinationFrame.height; - - this.invalidate(); - - return this; - } - - /** - * legacy upload method, just marks buffers dirty - * @returns {PIXI.QuadUv} Returns itself. - */ - invalidate() - { - this.vertexBuffer._updateID++; - this.uvBuffer._updateID++; - - return this; - } -} diff --git a/packages/core/src/renderers/utils/checkMaxIfStatmentsInShader.js b/packages/core/src/renderers/utils/checkMaxIfStatmentsInShader.js deleted file mode 100644 index 8047c96..0000000 --- a/packages/core/src/renderers/utils/checkMaxIfStatmentsInShader.js +++ /dev/null @@ -1,58 +0,0 @@ -const fragTemplate = [ - 'precision mediump float;', - 'void main(void){', - 'float test = 0.1;', - '%forloop%', - 'gl_FragColor = vec4(0.0);', - '}', -].join('\n'); - -export default function checkMaxIfStatmentsInShader(maxIfs, gl) -{ - if (maxIfs === 0) - { - throw new Error('Invalid value of `0` passed to `checkMaxIfStatementsInShader`'); - } - - const shader = gl.createShader(gl.FRAGMENT_SHADER); - - while (true) // eslint-disable-line no-constant-condition - { - const fragmentSrc = fragTemplate.replace(/%forloop%/gi, generateIfTestSrc(maxIfs)); - - gl.shaderSource(shader, fragmentSrc); - gl.compileShader(shader); - - if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) - { - maxIfs = (maxIfs / 2) | 0; - } - else - { - // valid! - break; - } - } - - return maxIfs; -} - -function generateIfTestSrc(maxIfs) -{ - let src = ''; - - for (let i = 0; i < maxIfs; ++i) - { - if (i > 0) - { - src += '\nelse '; - } - - if (i < maxIfs - 1) - { - src += `if(test == ${i}.0){}`; - } - } - - return src; -} diff --git a/packages/core/src/renderers/utils/mapWebGLBlendModesToPixi.js b/packages/core/src/renderers/utils/mapWebGLBlendModesToPixi.js deleted file mode 100644 index 10ffe4d..0000000 --- a/packages/core/src/renderers/utils/mapWebGLBlendModesToPixi.js +++ /dev/null @@ -1,47 +0,0 @@ -import { BLEND_MODES } from '@pixi/constants'; - -/** - * Maps gl blend combinations to WebGL. - * - * @memberof PIXI - * @function mapWebGLBlendModesToPixi - * @private - * @param {WebGLRenderingContext} gl - The rendering context. - * @param {string[]} [array=[]] - The array to output into. - * @return {string[]} Mapped modes. - */ -export default function mapWebGLBlendModesToPixi(gl, array = []) -{ - // TODO - premultiply alpha would be different. - // add a boolean for that! - array[BLEND_MODES.NORMAL] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.ADD] = [gl.ONE, gl.DST_ALPHA]; - array[BLEND_MODES.MULTIPLY] = [gl.DST_COLOR, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.SCREEN] = [gl.ONE, gl.ONE_MINUS_SRC_COLOR]; - array[BLEND_MODES.OVERLAY] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.DARKEN] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.LIGHTEN] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.COLOR_DODGE] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.COLOR_BURN] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.HARD_LIGHT] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.SOFT_LIGHT] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.DIFFERENCE] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.EXCLUSION] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.HUE] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.SATURATION] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.COLOR] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.LUMINOSITY] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.NONE] = [0, 0]; - - // not-premultiplied blend modes - array[BLEND_MODES.NORMAL_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.ADD_NPM] = [gl.SRC_ALPHA, gl.DST_ALPHA, gl.ONE, gl.DST_ALPHA]; - array[BLEND_MODES.SCREEN_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_COLOR, gl.ONE, gl.ONE_MINUS_SRC_COLOR]; - - // not-premultiplied blend modes - array[BLEND_MODES.NORMAL_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.ADD_NPM] = [gl.SRC_ALPHA, gl.DST_ALPHA, gl.ONE, gl.DST_ALPHA]; - array[BLEND_MODES.SCREEN_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_COLOR, gl.ONE, gl.ONE_MINUS_SRC_COLOR]; - - return array; -} diff --git a/packages/core/src/shader/GLProgram.js b/packages/core/src/shader/GLProgram.js new file mode 100644 index 0000000..92980ca --- /dev/null +++ b/packages/core/src/shader/GLProgram.js @@ -0,0 +1,43 @@ +/** + * Helper class to create a webGL Program + * + * @class + * @private + */ +export default class GLProgram +{ + constructor(program, uniformData) + { + /** + * The shader program + * + * @member {WebGLProgram} + */ + this.program = program; + + /** + * holds the uniform data which contains uniform locations + * and current uniform values used for caching and preventing undeed GPU commands + * @type {Object} + */ + this.uniformData = uniformData; + + /** + * uniformGroups holds the various upload functions for the shader. Each uniform group + * and program have a unique upload function generated. + * @type {Object} + */ + this.uniformGroups = {}; + } + + /** + * Destroys this program + * TODO + */ + destroy() + { + this.uniformData = null; + this.uniformGroups = null; + this.program = null; + } +} diff --git a/packages/core/src/shader/Program.js b/packages/core/src/shader/Program.js index 4f9971e..4b4007a 100644 --- a/packages/core/src/shader/Program.js +++ b/packages/core/src/shader/Program.js @@ -1,6 +1,11 @@ -import * as shaderUtils from '../renderers/systems/shader/shader'; +// import * as from '../systems/shader/shader'; +import { setPrecision, + defaultValue, + compileProgram, + mapSize, + mapType, + getTestContext } from './utils'; import { ProgramCache } from '@pixi/utils'; -import getTestContext from './getTestContext'; import defaultFragment from './defaultProgram.frag'; import defaultVertex from './defaultProgram.vert'; import { settings } from '@pixi/settings'; @@ -34,8 +39,8 @@ */ this.fragmentSrc = fragmentSrc || Program.defaultFragmentSrc; - this.vertexSrc = shaderUtils.setPrecision(this.vertexSrc, settings.PRECISION_VERTEX); - this.fragmentSrc = shaderUtils.setPrecision(this.fragmentSrc, settings.PRECISION_FRAGMENT); + this.vertexSrc = setPrecision(this.vertexSrc, settings.PRECISION_VERTEX); + this.fragmentSrc = setPrecision(this.fragmentSrc, settings.PRECISION_FRAGMENT); // currently this does not extract structs only default types this.extractData(this.vertexSrc, this.fragmentSrc); @@ -62,7 +67,7 @@ if (gl) { - const program = shaderUtils.compileProgram(gl, vertexSrc, fragmentSrc); + const program = compileProgram(gl, vertexSrc, fragmentSrc); this.attributeData = this.getAttributeData(program, gl); this.uniformData = this.getUniformData(program, gl); @@ -95,13 +100,13 @@ for (let i = 0; i < totalAttributes; i++) { const attribData = gl.getActiveAttrib(program, i); - const type = shaderUtils.mapType(gl, attribData.type); + const type = mapType(gl, attribData.type); /*eslint-disable */ const data = { type: type, name: attribData.name, - size: shaderUtils.mapSize(type), + size: mapSize(type), location: 0, }; /* eslint-enable */ @@ -145,14 +150,14 @@ const name = uniformData.name.replace(/\[.*?\]/, ''); const isArray = uniformData.name.match(/\[.*?\]/, ''); - const type = shaderUtils.mapType(gl, uniformData.type); + const type = mapType(gl, uniformData.type); /*eslint-disable */ uniforms[name] = { type: type, size: uniformData.size, isArray:isArray, - value: shaderUtils.defaultValue(type, uniformData.size), + value: defaultValue(type, uniformData.size), }; /* eslint-enable */ } diff --git a/packages/core/src/shader/ShaderSystem.js b/packages/core/src/shader/ShaderSystem.js new file mode 100644 index 0000000..40b8af8 --- /dev/null +++ b/packages/core/src/shader/ShaderSystem.js @@ -0,0 +1,171 @@ +import System from '../System'; +import GLProgram from './GLProgram'; +import { generateUniformsSync, + defaultValue, + compileProgram } from './utils'; + +let UID = 0; + +/** + * Helper class to create a webGL Texture + * + * @class + * @memberof PIXI.systems + * @extends PIXI.System + */ +export default class ShaderSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - A reference to the current renderer + */ + constructor(renderer) + { + super(renderer); + + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = null; + + this.shader = null; + this.program = null; + + this.id = UID++; + } + + contextChange(gl) + { + this.gl = gl; + } + + /** + * Changes the current shader to the one given in parameter + * + * @param {PIXI.Shader} shader - the new shader + * @param {boolean} dontSync - false if the shader should automatically sync its uniforms. + * @returns {PIXI.glCore.glProgram} the glProgram that belongs to the shader. + */ + bind(shader, dontSync) + { + shader.uniforms.globals = this.renderer.globalUniforms; + + const program = shader.program; + const glProgram = program.glPrograms[this.renderer.CONTEXT_UID] || this.generateShader(shader); + + this.shader = shader; + + // TODO - some current pixi plugins bypass this.. so it not safe to use yet.. + if (this.program !== program) + { + this.program = program; + this.gl.useProgram(glProgram.program); + } + + if (!dontSync) + { + this.syncUniformGroup(shader.uniformGroup); + } + + return glProgram; + } + + /** + * Uploads the uniforms values to the currently bound shader. + * + * @param {object} uniforms - the uniforms valiues that be applied to the current shader + */ + setUniforms(uniforms) + { + const shader = this.shader.program; + const glProgram = shader.glPrograms[this.renderer.CONTEXT_UID]; + + shader.syncUniforms(glProgram.uniformData, uniforms, this.renderer); + } + + syncUniformGroup(group) + { + const glProgram = this.getglProgram(); + + if (!group.static || group.dirtyId !== glProgram.uniformGroups[group.id]) + { + glProgram.uniformGroups[group.id] = group.dirtyId; + const syncFunc = group.syncUniforms[this.shader.program.id] || this.createSyncGroups(group); + + syncFunc(glProgram.uniformData, group.uniforms, this.renderer); + } + } + + createSyncGroups(group) + { + group.syncUniforms[this.shader.program.id] = generateUniformsSync(group, this.shader.program.uniformData); + + return group.syncUniforms[this.shader.program.id]; + } + + /** + * Returns the underlying GLShade rof the currently bound shader. + * This can be handy for when you to have a little more control over the setting of your uniforms. + * + * @return {PIXI.glCore.Shader} the glProgram for the currently bound Shader for this context + */ + getglProgram() + { + if (this.shader) + { + return this.shader.program.glPrograms[this.renderer.CONTEXT_UID]; + } + + return null; + } + + /** + * Generates a glProgram verion of the Shader provided. + * + * @private + * @param {PIXI.Shader} shader the shader that the glProgram will be based on. + * @return {PIXI.glCore.glProgram} A shiney new glProgram + */ + generateShader(shader) + { + const gl = this.gl; + + const program = shader.program; + + const attribMap = {}; + + for (const i in program.attributeData) + { + attribMap[i] = program.attributeData[i].location; + } + + const shaderProgram = compileProgram(gl, program.vertexSrc, program.fragmentSrc, attribMap); + const uniformData = {}; + + for (const i in program.uniformData) + { + const data = program.uniformData[i]; + + uniformData[i] = { + location: gl.getUniformLocation(shaderProgram, i), + value: defaultValue(data.type, data.size), + }; + } + + const glProgram = new GLProgram(shaderProgram, uniformData); + + program.glPrograms[this.renderer.CONTEXT_UID] = glProgram; + + return glProgram; + } + + /** + * Destroys this System and removes all its textures + */ + destroy() + { + // TODO implement destroy method for ShaderSystem + this.destroyed = true; + } +} diff --git a/packages/core/src/shader/defaultValue.js b/packages/core/src/shader/defaultValue.js deleted file mode 100644 index 847a069..0000000 --- a/packages/core/src/shader/defaultValue.js +++ /dev/null @@ -1,78 +0,0 @@ -function booleanArray(size) -{ - const array = new Array(size); - - for (let i = 0; i < array.length; i++) - { - array[i] = false; - } - - return array; -} - -/** - * @class - * @memberof PIXI.glCore.shader - * @param type {String} Type of value - * @param size {Number} - */ -export default function defaultValue(type, size) -{ - switch (type) - { - case 'float': - return 0; - - case 'vec2': - return new Float32Array(2 * size); - - case 'vec3': - return new Float32Array(3 * size); - - case 'vec4': - return new Float32Array(4 * size); - - case 'int': - case 'sampler2D': - return 0; - - case 'ivec2': - return new Int32Array(2 * size); - - case 'ivec3': - return new Int32Array(3 * size); - - case 'ivec4': - return new Int32Array(4 * size); - - case 'bool': - return false; - - case 'bvec2': - - return booleanArray(2 * size); - - case 'bvec3': - return booleanArray(3 * size); - - case 'bvec4': - return booleanArray(4 * size); - - case 'mat2': - return new Float32Array([1, 0, - 0, 1]); - - case 'mat3': - return new Float32Array([1, 0, 0, - 0, 1, 0, - 0, 0, 1]); - - case 'mat4': - return new Float32Array([1, 0, 0, 0, - 0, 1, 0, 0, - 0, 0, 1, 0, - 0, 0, 0, 1]); - } - - return 0; -} diff --git a/packages/core/src/shader/extractUniformsFromSrc.js b/packages/core/src/shader/extractUniformsFromSrc.js deleted file mode 100644 index 3d02c0a..0000000 --- a/packages/core/src/shader/extractUniformsFromSrc.js +++ /dev/null @@ -1,57 +0,0 @@ -import defaultValue from './defaultValue'; - -export default function extractUniformsFromSrc(vertexSrc, fragmentSrc, mask) -{ - const vertUniforms = extractUniformsFromString(vertexSrc, mask); - const fragUniforms = extractUniformsFromString(fragmentSrc, mask); - - return Object.assign(vertUniforms, fragUniforms); -} - -function extractUniformsFromString(string) -{ - const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); - - const uniforms = {}; - let nameSplit; - - // clean the lines a little - remove extra spaces / teabs etc - // then split along ';' - const lines = string.replace(/\s+/g, ' ') - .split(/\s*;\s*/); - - // loop through.. - for (let i = 0; i < lines.length; i++) - { - const line = lines[i].trim(); - - if (line.indexOf('uniform') > -1) - { - const splitLine = line.split(' '); - const type = splitLine[1]; - - let name = splitLine[2]; - let size = 1; - - if (name.indexOf('[') > -1) - { - // array! - nameSplit = name.split(/\[|]/); - name = nameSplit[0]; - size *= Number(nameSplit[1]); - } - - if (!name.match(maskRegex)) - { - uniforms[name] = { - value: defaultValue(type, size), - dirtyId: 0, - name, - type, - }; - } - } - } - - return uniforms; -} diff --git a/packages/core/src/shader/generateUniformsSync.js b/packages/core/src/shader/generateUniformsSync.js deleted file mode 100644 index 98a357f..0000000 --- a/packages/core/src/shader/generateUniformsSync.js +++ /dev/null @@ -1,231 +0,0 @@ -// cv = CachedValue -// v = value -// ud = uniformData -// uv = uniformValue -// l = loaction -const GLSL_TO_SINGLE_SETTERS_CACHED = { - - float: ` - if(cv !== v) - { - cv.v = v; - gl.uniform1f(location, v) - }`, - - vec2: ` - if(cv[0] !== v[0] || cv[1] !== v[1]) - { - cv[0] = v[0]; - cv[1] = v[1]; - gl.uniform2f(location, v[0], v[1]) - }`, - - vec3: ` - if(cv[0] !== v[0] || cv[1] !== v[1] || cv[2] !== v[2]) - { - cv[0] = v[0]; - cv[1] = v[1]; - cv[2] = v[2]; - - gl.uniform3f(location, v[0], v[1], v[2]) - }`, - - vec4: 'gl.uniform4f(location, v[0], v[1], v[2], v[3])', - - int: 'gl.uniform1i(location, v)', - ivec2: 'gl.uniform2i(location, v[0], v[1])', - ivec3: 'gl.uniform3i(location, v[0], v[1], v[2])', - ivec4: 'gl.uniform4i(location, v[0], v[1], v[2], v[3])', - - bool: 'gl.uniform1i(location, v)', - bvec2: 'gl.uniform2i(location, v[0], v[1])', - bvec3: 'gl.uniform3i(location, v[0], v[1], v[2])', - bvec4: 'gl.uniform4i(location, v[0], v[1], v[2], v[3])', - - mat2: 'gl.uniformMatrix2fv(location, false, v)', - mat3: 'gl.uniformMatrix3fv(location, false, v)', - mat4: 'gl.uniformMatrix4fv(location, false, v)', - - sampler2D: 'gl.uniform1i(location, v)', - samplerCube: 'gl.uniform1i(location, v)', - sampler2DArray: 'gl.uniform1i(location, v)', -}; - -const GLSL_TO_ARRAY_SETTERS = { - - float: `gl.uniform1fv(location, v)`, - - vec2: `gl.uniform2fv(location, v)`, - vec3: `gl.uniform3fv(location, v)`, - vec4: 'gl.uniform4fv(location, v)', - - mat4: 'gl.uniformMatrix4fv(location, false, v)', - mat3: 'gl.uniformMatrix3fv(location, false, v)', - mat2: 'gl.uniformMatrix2fv(location, false, v)', - - int: 'gl.uniform1iv(location, v)', - ivec2: 'gl.uniform2iv(location, v)', - ivec3: 'gl.uniform3iv(location, v)', - ivec4: 'gl.uniform4iv(location, v)', - - bool: 'gl.uniform1iv(location, v)', - bvec2: 'gl.uniform2iv(location, v)', - bvec3: 'gl.uniform3iv(location, v)', - bvec4: 'gl.uniform4iv(location, v)', - - sampler2D: 'gl.uniform1iv(location, v)', - samplerCube: 'gl.uniform1iv(location, v)', - sampler2DArray: 'gl.uniform1iv(location, v)', -}; - -export default function generateUniformsSync(group, uniformData) -{ - let textureCount = 0; - let func = `var v = null; - var cv = null - var gl = renderer.gl`; - - for (const i in group.uniforms) - { - const data = uniformData[i]; - - if (!data) - { - if (group.uniforms[i].group) - { - func += ` - renderer.shader.syncUniformGroup(uv.${i}); - `; - } - - continue; - } - - // TODO && uniformData[i].value !== 0 <-- do we still need this? - if (data.type === 'float' && data.size === 1) - { - func += ` - if(uv.${i} !== ud.${i}.value) - { - ud.${i}.value = uv.${i} - gl.uniform1f(ud.${i}.location, uv.${i}) - }\n`; - } - /* eslint-disable max-len */ - else if ((data.type === 'sampler2D' || data.type === 'samplerCube' || data.type === 'sampler2DArray') && data.size === 1 && !data.isArray) - /* eslint-disable max-len */ - { - func += ` - renderer.texture.bind(uv.${i}, ${textureCount}); - - if(ud.${i}.value !== ${textureCount}) - { - ud.${i}.value = ${textureCount}; - gl.uniform1i(ud.${i}.location, ${textureCount});\n; // eslint-disable-line max-len - }\n`; - - textureCount++; - } - else if (data.type === 'mat3' && data.size === 1) - { - if (group.uniforms[i].a !== undefined) - { - // TODO and some smart caching dirty ids here! - func += ` - gl.uniformMatrix3fv(ud.${i}.location, false, uv.${i}.toArray(true)); - \n`; - } - else - { - func += ` - gl.uniformMatrix3fv(ud.${i}.location, false, uv.${i}); - \n`; - } - } - else if (data.type === 'vec2' && data.size === 1) - { - // TODO - do we need both here? - // maybe we can get away with only using points? - if (group.uniforms[i].x !== undefined) - { - func += ` - cv = ud.${i}.value; - v = uv.${i}; - - if(cv[0] !== v.x || cv[1] !== v.y) - { - cv[0] = v.x; - cv[1] = v.y; - gl.uniform2f(ud.${i}.location, v.x, v.y); - }\n`; - } - else - { - func += ` - cv = ud.${i}.value; - v = uv.${i}; - - if(cv[0] !== v[0] || cv[1] !== v[1]) - { - cv[0] = v[0]; - cv[1] = v[1]; - gl.uniform2f(ud.${i}.location, v[0], v[1]); - } - \n`; - } - } - else if (data.type === 'vec4' && data.size === 1) - { - // TODO - do we need both here? - // maybe we can get away with only using points? - if (group.uniforms[i].width !== undefined) - { - func += ` - cv = ud.${i}.value; - v = uv.${i}; - - if(cv[0] !== v.x || cv[1] !== v.y || cv[2] !== v.width || cv[3] !== v.height) - { - cv[0] = v.x; - cv[1] = v.y; - cv[2] = v.width; - cv[3] = v.height; - gl.uniform4f(ud.${i}.location, v.x, v.y, v.width, v.height) - }\n`; - } - else - { - func += ` - cv = ud.${i}.value; - v = uv.${i}; - - if(cv[0] !== v[0] || cv[1] !== v[1] || cv[2] !== v[2] || cv[3] !== v[3]) - { - cv[0] = v[0]; - cv[1] = v[1]; - cv[2] = v[2]; - cv[3] = v[3]; - - gl.uniform4f(ud.${i}.location, v[0], v[1], v[2], v[3]) - } - \n`; - } - } - else - { - const templateType = (data.size === 1) ? GLSL_TO_SINGLE_SETTERS_CACHED : GLSL_TO_ARRAY_SETTERS; - - const template = templateType[data.type].replace('location', `ud.${i}.location`); - - func += ` - cv = ud.${i}.value; - v = uv.${i}; - ${template};\n`; - } - } - - // console.log(' --------------- ') - // console.log(func); - - return new Function('ud', 'uv', 'renderer', func); // eslint-disable-line no-new-func -} diff --git a/packages/core/src/shader/getTestContext.js b/packages/core/src/shader/getTestContext.js deleted file mode 100644 index ea174cc..0000000 --- a/packages/core/src/shader/getTestContext.js +++ /dev/null @@ -1,48 +0,0 @@ -import { settings } from '@pixi/settings'; - -let context = null; - -/** - * returns a little webGL context to use for program inspection. - * - * @static - * @private - * @returns {webGL-context} a gl context to test with - */ -export default function getTestContext() -{ - if (!context) - { - const canvas = document.createElement('canvas'); - - let gl; - - if (settings.PREFER_WEBGL_2) - { - gl = canvas.getContext('webgl2', {}); - } - - if (!gl) - { - gl = canvas.getContext('webgl', {}) - || canvas.getContext('experimental-webgl', {}); - - if (!gl) - { - // fail, not able to get a context - throw new Error('This browser does not support webGL. Try using the canvas renderer'); - } - else - { - // for shader testing.. - gl.getExtension('WEBGL_draw_buffers'); - } - } - - context = gl; - - return gl; - } - - return context; -} diff --git a/packages/core/src/AbstractRenderer.js b/packages/core/src/AbstractRenderer.js new file mode 100644 index 0000000..64b2eb1 --- /dev/null +++ b/packages/core/src/AbstractRenderer.js @@ -0,0 +1,353 @@ +import { hex2string, hex2rgb } from '@pixi/utils'; +import { Matrix, Rectangle } from '@pixi/math'; +import { RENDERER_TYPE } from '@pixi/constants'; +import { settings } from '@pixi/settings'; +import { Container } from '@pixi/display'; +import RenderTexture from './renderTexture/RenderTexture'; +import EventEmitter from 'eventemitter3'; + +const tempMatrix = new Matrix(); + +/** + * 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 EventEmitter + * @memberof PIXI + */ +export default class AbstractRenderer extends EventEmitter +{ + // eslint-disable-next-line valid-jsdoc + /** + * @param {string} system - The name of the system this renderer is for. + * @param {object} [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.transparent=false] - If the render view is transparent, default false + * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false + * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. The + * resolution of the renderer retina would be 2. + * @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 {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when rendering, + * stopping pixel interpolation. + */ + constructor(system, options, arg2, arg3) + { + super(); + + // Support for constructor(system, screenWidth, screenHeight, options) + if (typeof options === 'number') + { + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + }, arg3); + } + + // 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 = RENDERER_TYPE.UNKNOWN; + + /** + * Measurements of the screen. (0, 0, screenWidth, screenHeight) + * + * Its safe to use as filterArea or hitArea for 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 1 + */ + this.resolution = options.resolution || settings.RESOLUTION; + + /** + * Whether the render view is transparent + * + * @member {boolean} + */ + this.transparent = options.transparent; + + /** + * Whether css dimensions of canvas view should be resized to screen dimensions automatically + * + * @member {boolean} + */ + this.autoResize = options.autoResize || false; + + /** + * Tracks the blend modes useful for this renderer. + * + * @member {object} + */ + this.blendModes = null; + + /** + * 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; + + /** + * If true PixiJS will Math.floor() x/y values when rendering, stopping pixel interpolation. + * Handy for crisp pixel art and speed on legacy devices. + * + * @member {boolean} + */ + this.roundPixels = options.roundPixels; + + /** + * The background color as a number. + * + * @member {number} + * @private + */ + this._backgroundColor = 0x000000; + + /** + * The background color as an [R, G, B] array. + * + * @member {number[]} + * @private + */ + this._backgroundColorRgba = [0, 0, 0, 0]; + + /** + * The background color as a string. + * + * @member {string} + * @private + */ + this._backgroundColorString = '#000000'; + + this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter + + /** + * This temporary display object used as the parent of the currently being rendered item + * + * @member {PIXI.DisplayObject} + * @private + */ + this._tempDisplayObjectParent = new Container(); + + /** + * The last root object that the renderer tried to render. + * + * @member {PIXI.DisplayObject} + * @private + */ + this._lastObjectRendered = this._tempDisplayObjectParent; + + /** + * Collection of plugins. + * @readonly + * @member {object} + */ + this.plugins = {}; + } + + /** + * Initialize the plugins. + * + * @protected + * @param {object} staticMap - The dictionary of staticly saved plugins. + */ + initPlugins(staticMap) + { + 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() + { + return this.view.width; + } + + /** + * Same as view.height, actual number of pixels in the canvas by vertical + * + * @member {number} + * @readonly + * @default 600 + */ + get height() + { + return this.view.height; + } + + /** + * Resizes the screen and canvas to the specified width and height + * Canvas dimensions are multiplied by resolution + * + * @param {number} screenWidth - the new width of the screen + * @param {number} screenHeight - the new height of the screen + */ + resize(screenWidth, screenHeight) + { + this.screen.width = screenWidth; + this.screen.height = screenHeight; + + this.view.width = screenWidth * this.resolution; + this.view.height = screenHeight * this.resolution; + + if (this.autoResize) + { + this.view.style.width = `${screenWidth}px`; + this.view.style.height = `${screenHeight}px`; + } + } + + /** + * 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 {PIXI.DisplayObject} displayObject - The displayObject the object will be generated from + * @param {number} scaleMode - Should be one of the scaleMode consts + * @param {number} resolution - The resolution / device pixel ratio of the texture being generated + * @param {PIXI.Rectangle} [region] - The region of the displayObject, that shall be rendered, + * if no region is specified, defaults to the local bounds of the displayObject. + * @return {PIXI.Texture} a texture of the graphics object + */ + generateTexture(displayObject, scaleMode, resolution, region) + { + region = region || displayObject.getLocalBounds(); + + // 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(region.width | 0, region.height | 0, scaleMode, resolution); + + tempMatrix.tx = -region.x; + tempMatrix.ty = -region.y; + + this.render(displayObject, renderTexture, false, tempMatrix, true); + + return renderTexture; + } + + /** + * Removes everything from the renderer and optionally removes the Canvas DOM element. + * + * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. + */ + destroy(removeView) + { + 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); + } + + this.plugins = null; + + this.type = RENDERER_TYPE.UNKNOWN; + + this.view = null; + + this.screen = null; + + this.resolution = 0; + + this.transparent = false; + + this.autoResize = false; + + this.blendModes = null; + + this.options = null; + + this.preserveDrawingBuffer = false; + this.clearBeforeRender = false; + + this.roundPixels = false; + + this._backgroundColor = 0; + this._backgroundColorRgba = null; + this._backgroundColorString = null; + + this._tempDisplayObjectParent = null; + this._lastObjectRendered = null; + } + + /** + * The background color to fill if not transparent + * + * @member {number} + */ + get backgroundColor() + { + return this._backgroundColor; + } + + set backgroundColor(value) // eslint-disable-line require-jsdoc + { + this._backgroundColor = value; + this._backgroundColorString = hex2string(value); + hex2rgb(value, this._backgroundColorRgba); + } +} diff --git a/packages/core/src/Renderer.js b/packages/core/src/Renderer.js new file mode 100644 index 0000000..2227e78 --- /dev/null +++ b/packages/core/src/Renderer.js @@ -0,0 +1,338 @@ +import AbstractRenderer from './AbstractRenderer'; +import { sayHello } from '@pixi/utils'; +import MaskSystem from './mask/MaskSystem'; +import StencilSystem from './mask/StencilSystem'; +import FilterSystem from './filters/FilterSystem'; +import FramebufferSystem from './framebuffer/FramebufferSystem'; +import RenderTextureSystem from './renderTexture/RenderTextureSystem'; +import TextureSystem from './textures/TextureSystem'; +import ProjectionSystem from './projection/ProjectionSystem'; +import StateSystem from './state/StateSystem'; +import GeometrySystem from './geometry/GeometrySystem'; +import ShaderSystem from './shader/ShaderSystem'; +import ContextSystem from './context/ContextSystem'; +import BatchSystem from './batch/BatchSystem'; +import TextureGCSystem from './textures/TextureGCSystem'; +import { RENDERER_TYPE } from '@pixi/constants'; +import UniformGroup from './shader/UniformGroup'; +import { Matrix } from '@pixi/math'; +import Runner from 'mini-runner'; + +/** + * 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 Render works by automatically managing webGLBatchs. + * 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 :) + * + * @class + * @memberof PIXI + * @extends PIXI.AbstractRenderer + */ +export default class Renderer extends AbstractRenderer +{ + // eslint-disable-next-line valid-jsdoc + /** + * + * @param {object} [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.transparent=false] - If the render view is transparent, default false + * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false + * @param {boolean} [options.antialias=false] - sets antialias. If not available natively then FXAA + * antialiasing is used + * @param {boolean} [options.forceFXAA=false] - forces FXAA antialiasing to be used over native. + * FXAA is faster, but may not always look as great + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. + * The resolution of the renderer retina would be 2. + * @param {boolean} [options.clearBeforeRender=true] - This sets if the renderer will clear + * the canvas or not before the new render pass. If you wish to set this to false, you *must* set + * preserveDrawingBuffer to `true`. + * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, + * enable this if you need to call toDataUrl on the webgl context. + * @param {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when + * rendering, stopping pixel interpolation. + * @param {number} [options.backgroundColor=0x000000] - The background color of the rendered area + * (shown if not transparent). + * @param {boolean} [options.legacy=false] - If true PixiJS will aim to ensure compatibility + * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. + * @param {string} [options.powerPreference] - Parameter passed to webgl context, set to "high-performance" + * for devices with dual graphics card + */ + constructor(options, arg2, arg3) + { + super('WebGL', options, arg2, arg3); + + /** + * The type of this renderer as a standardised const + * + * @member {number} + * @see PIXI.RENDERER_TYPE + */ + this.type = RENDERER_TYPE.WEBGL; + + // this will be set by the contextSystem (this.context) + this.gl = null; + this.CONTEXT_UID = 0; + this.legacy = !!options.legacy; + + // TODO legacy! + + // runners! + this.runners = { + destroy: new Runner('destroy'), + contextChange: new Runner('contextChange', 1), + reset: new Runner('reset'), + update: new Runner('update'), + postrender: new Runner('postrender'), + prerender: new Runner('prerender'), + resize: new Runner('resize', 2), + }; + + 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(GeometrySystem, 'geometry') + .addSystem(FramebufferSystem, 'framebuffer') + .addSystem(StencilSystem, 'stencil') + .addSystem(ProjectionSystem, 'projection') + .addSystem(TextureGCSystem, 'textureGC') + .addSystem(FilterSystem, 'filter') + .addSystem(RenderTextureSystem, 'renderTexture') + .addSystem(BatchSystem, 'batch'); + + this.initPlugins(Renderer.__plugins); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ + if (options.context) + { + this.context.initFromContext(options.context); + } + else + { + this.context.initFromOptions({ + alpha: this.transparent, + antialias: options.antialias, + premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', + stencil: true, + 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); + } + + /** + * Add a new system to the renderer. + * @param {class} ClassRef - Class reference + * @param {string} [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. + * @return {PIXI.Renderer} Return instance of renderer + */ + addSystem(ClassRef, name) + { + if (!name) + { + name = ClassRef.name; + } + + // TODO - read name from class.name.. + + /* + if(name.includes('System')) + { + name = name.replace('System', ''); + name = name.charAt(0).toLowerCase() + name.slice(1); + } + */ + + const system = new ClassRef(this); + + if (this[name]) + { + throw new Error(`Whoops! ${name} is already a manger`); + } + + this[name] = system; + + for (const i in this.runners) + { + this.runners[i].add(system); + } + + return this; + + /** + * 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. + */ + } + + /** + * Renders the object to its webGL view + * + * @param {PIXI.DisplayObject} displayObject - the object to be rendered + * @param {PIXI.RenderTexture} renderTexture - The render texture to render to. + * @param {boolean} [clear] - Should the canvas be cleared before the new render + * @param {PIXI.Transform} [transform] - A transform to apply to the render texture before rendering. + * @param {boolean} [skipUpdateTransform] - Should we skip the update transform pass? + */ + render(displayObject, renderTexture, clear, transform, skipUpdateTransform) + { + // can be handy to know! + this.renderingToScreen = !renderTexture; + + this.runners.prerender.run(); + this.emit('prerender'); + + // 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.parent; + + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = 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.run(); + + this.emit('postrender'); + } + + /** + * Resizes the webGL view to the specified width and height. + * + * @param {number} screenWidth - the new width of the screen + * @param {number} screenHeight - the new height of the screen + */ + resize(screenWidth, screenHeight) + { + AbstractRenderer.prototype.resize.call(this, screenWidth, screenHeight); + + this.runners.resize.run(screenWidth, screenHeight); + } + + /** + * Resets the WebGL state so you can render things however you fancy! + * + * @return {PIXI.Renderer} Returns itself. + */ + reset() + { + this.runners.reset.run(); + + return this; + } + + clear() + { + this.framebuffer.bind(); + this.framebuffer.clear(); + } + + /** + * Removes everything from the renderer (event listeners, spritebatch, etc...) + * + * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. + * See: https://github.com/pixijs/pixi.js/issues/2233 + */ + destroy(removeView) + { + this.runners.destroy.run(); + + // call base destroy + super.destroy(removeView); + + // TODO nullify all the managers.. + this.gl = null; + } + + /** + * 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 + * builds and excluding plugins. + * @name PIXI.Renderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. + */ + + /** + * Adds a plugin to the renderer. + * + * @method + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + static registerPlugin(pluginName, ctor) + { + Renderer.__plugins = Renderer.__plugins || {}; + Renderer.__plugins[pluginName] = ctor; + } +} diff --git a/packages/core/src/System.js b/packages/core/src/System.js new file mode 100644 index 0000000..05a10c8 --- /dev/null +++ b/packages/core/src/System.js @@ -0,0 +1,42 @@ +/** + * System is a base class used for extending systems used by the {@link PIXI.Renderer} + * @see PIXI.Renderer#addSystem + * @class + * @memberof PIXI + */ +export default class System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + /** + * The renderer this manager works for. + * + * @member {PIXI.Renderer} + */ + this.renderer = renderer; + + this.renderer.runners.contextChange.add(this); + } + + /** + * Generic method called when there is a WebGL context change. + * + */ + contextChange() + { + // do some codes init! + } + + /** + * Generic destroy methods to be overridden by the subclass + * + */ + destroy() + { + this.renderer.runners.contextChange.remove(this); + this.renderer = null; + } +} diff --git a/packages/core/src/batch/BatchSystem.js b/packages/core/src/batch/BatchSystem.js new file mode 100644 index 0000000..b04c249 --- /dev/null +++ b/packages/core/src/batch/BatchSystem.js @@ -0,0 +1,68 @@ +import System from '../System'; +import ObjectRenderer from './ObjectRenderer'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class BatchSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + super(renderer); + + /** + * An empty renderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.emptyRenderer = new ObjectRenderer(renderer); + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.currentRenderer = this.emptyRenderer; + } + + /** + * Changes the current renderer to the one given in parameter + * + * @param {PIXI.ObjectRenderer} objectRenderer - The object renderer to use. + */ + setObjectRenderer(objectRenderer) + { + if (this.currentRenderer === objectRenderer) + { + return; + } + + this.currentRenderer.stop(); + this.currentRenderer = objectRenderer; + + this.renderer.state.setState(objectRenderer.state); + + this.currentRenderer.start(); + } + + /** + * This should be called if you wish to do some custom rendering + * It will basically render anything that may be batched up such as sprites + * + */ + flush() + { + this.setObjectRenderer(this.emptyRenderer); + } + + reset() + { + this.setObjectRenderer(this.emptyRenderer); + } +} diff --git a/packages/core/src/batch/ObjectRenderer.js b/packages/core/src/batch/ObjectRenderer.js new file mode 100644 index 0000000..0c851a9 --- /dev/null +++ b/packages/core/src/batch/ObjectRenderer.js @@ -0,0 +1,48 @@ +import System from '../System'; + +/** + * Base for a common object renderer that can be used as a system renderer plugin. + * + * @class + * @extends PIXI.System + * @memberof PIXI + */ +export default class ObjectRenderer extends System +{ + /** + * Starts the renderer and sets the shader + * + */ + start() + { + // set the shader.. + } + + /** + * Stops the renderer + * + */ + stop() + { + this.flush(); + } + + /** + * Stub method for rendering content and emptying the current batch. + * + */ + flush() + { + // flush! + } + + /** + * Renders an object + * + * @param {PIXI.DisplayObject} object - The object to render. + */ + render(object) // eslint-disable-line no-unused-vars + { + // render the object + } +} diff --git a/packages/core/src/context/ContextSystem.js b/packages/core/src/context/ContextSystem.js new file mode 100644 index 0000000..301aab4 --- /dev/null +++ b/packages/core/src/context/ContextSystem.js @@ -0,0 +1,186 @@ +import System from '../System'; +import { settings } from '@pixi/settings'; + +let CONTEXT_UID = 0; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class ContextSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.webGLVersion = 1; + + this.handleContextLost = this.handleContextLost.bind(this); + this.handleContextRestored = this.handleContextRestored.bind(this); + + this.extensions = {}; + + renderer.view.addEventListener('webglcontextlost', this.handleContextLost, false); + renderer.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + } + + get isLost() + { + return (!this.gl || this.gl.isContextLost()); + } + + contextChange(gl) + { + this.gl = gl; + + // restore a context if it was previously lost + if (gl.isContextLost() && gl.getExtension('WEBGL_lose_context')) + { + gl.getExtension('WEBGL_lose_context').restoreContext(); + } + } + + initFromContext(gl) + { + this.gl = gl; + this.validateContext(gl); + this.renderer.gl = gl; + this.renderer.CONTEXT_UID = CONTEXT_UID++; + this.renderer.runners.contextChange.run(gl); + } + + initFromOptions(options) + { + const gl = this.createContext(this.renderer.view, options); + + this.initFromContext(gl); + } + + /** + * Helper class to create a webGL Context + * + * @class + * @memberof PIXI.glCore + * @param canvas {HTMLCanvasElement} the canvas element that we will get the context from + * @param options {Object} An options object that gets passed in to the canvas element containing the context attributes, + * see https://developer.mozilla.org/en/docs/Web/API/HTMLCanvasElement/getContext for the options available + * @return {WebGLRenderingContext} the WebGL context + */ + createContext(canvas, options) + { + let gl; + + if (settings.PREFER_WEBGL_2) + { + gl = canvas.getContext('webgl2', options); + } + + if (gl) + { + this.webGLVersion = 2; + } + else + { + this.webGLVersion = 1; + + gl = canvas.getContext('webgl', options) + || canvas.getContext('experimental-webgl', options); + + if (!gl) + { + // fail, not able to get a context + throw new Error('This browser does not support webGL. Try using the canvas renderer'); + } + } + + this.gl = gl; + + this.getExtensions(); + + return gl; + } + + getExtensions() + { + // time to set up default etensions that pixi uses.. + const gl = this.gl; + const extensions = this.extensions; + + if (this.webGLVersion === 1) + { + extensions.drawBuffers = gl.getExtension('WEBGL_draw_buffers'); + extensions.depthTexture = gl.getExtension('WEBKIT_WEBGL_depth_texture'); + extensions.floatTexture = gl.getExtension('OES_texture_float'); + extensions.loseContext = gl.getExtension('WEBGL_lose_context'); + + extensions.vertexArrayObject = gl.getExtension('OES_vertex_array_object') + || gl.getExtension('MOZ_OES_vertex_array_object') + || gl.getExtension('WEBKIT_OES_vertex_array_object'); + } + + // we don't use any specific WebGL 2 ones yet! + } + + /** + * Handles a lost webgl context + * + * @private + * @param {WebGLContextEvent} event - The context lost event. + */ + handleContextLost(event) + { + event.preventDefault(); + } + + /** + * Handles a restored webgl context + * + * @private + */ + handleContextRestored() + { + this.renderer.runners.contextChange.run(this.gl); + } + + destroy() + { + const view = this.renderer.view; + + // remove listeners + view.removeEventListener('webglcontextlost', this.handleContextLost); + view.removeEventListener('webglcontextrestored', this.handleContextRestored); + + this.gl.useProgram(null); + + if (this.extensions.loseContext) + { + this.extensions.loseContext.loseContext(); + } + } + + postrender() + { + this.gl.flush(); + } + + validateContext(gl) + { + const attributes = gl.getContextAttributes(); + + // this is going to be fairly simple for now.. but at least we have room to grow! + if (!attributes.stencil) + { + /* eslint-disable max-len */ + + /* eslint-disable no-console */ + console.warn('Provided WebGL context does not have a stencil buffer, masks may not render correctly'); + /* eslint-enable no-console */ + + /* eslint-enable max-len */ + } + } +} diff --git a/packages/core/src/filters/Filter.js b/packages/core/src/filters/Filter.js new file mode 100644 index 0000000..9d8cad8 --- /dev/null +++ b/packages/core/src/filters/Filter.js @@ -0,0 +1,140 @@ +import Shader from '../shader/Shader'; +import Program from '../shader/Program'; +import State from '../state/State'; +import { settings } from '@pixi/settings'; +// import extractUniformsFromSrc from './extractUniformsFromSrc'; +import defaultVertex from './defaultFilter.vert'; +import defaultFragment from './defaultFilter.frag'; + +// let math = require('../../math'); +/** + * @class + * @memberof PIXI + * @extends PIXI.Shader + */ +export default class Filter extends Shader +{ + /** + * @param {string} [vertexSrc] - The source of the vertex shader. + * @param {string} [fragmentSrc] - The source of the fragment shader. + * @param {object} [uniforms] - Custom uniforms to use to augment the built-in ones. + */ + constructor(vertexSrc, fragmentSrc, uniforms) + { + const program = Program.from(vertexSrc, fragmentSrc); + + super(program, uniforms); + + /** + * The padding of the filter. Some filters require extra space to breath such as a blur. + * Increasing this will add extra width and height to the bounds of the object that the + * filter is applied to. + * + * @member {number} + */ + this.padding = 0; + + /** + * The resolution of the filter. Setting this to be lower will lower the quality but + * increase the performance of the filter. + * + * @member {number} + */ + this.resolution = settings.RESOLUTION; + + /** + * If enabled is true the filter is applied, if false it will not. + * + * @member {boolean} + */ + this.enabled = true; + + /** + * If enabled, PixiJS will fit the filter area into boundaries for better performance. + * Switch it off if it does not work for specific shader. + * + * @member {boolean} + */ + this.autoFit = true; + + /** + * Legacy filters use position and uvs from attributes + * @member {boolean} + * @readonly + */ + this.legacy = !!this.program.attributeData.aTextureCoord; + + /** + * the webGL state the filter requires to render + * @member {PIXI.State} + */ + this.state = new State(); + } + + /** + * Applies the filter + * + * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + * @param {boolean} clear - Should the output be cleared before rendering to it + * @param {object} [currentState] - It's current state of filter. + * There are some useful properties in the currentState : + * target, filters, sourceFrame, destinationFrame, renderTarget, resolution + */ + apply(filterManager, input, output, clear, currentState, derp) // eslint-disable-line no-unused-vars + { + // do as you please! + + filterManager.applyFilter(this, input, output, clear, currentState, derp); + + // or just do a regular render.. + } + + /** + * Sets the blendmode of the filter + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL + */ + get blendMode() + { + return this.state.blendMode; + } + + set blendMode(value) // eslint-disable-line require-jsdoc + { + this.state.blendMode = value; + } + + /** + * The default vertex shader source + * + * @static + * @constant + */ + static get defaultVertexSrc() + { + return defaultVertex; + } + + /** + * The default fragment shader source + * + * @static + * @constant + */ + static get defaultFragmentSrc() + { + return defaultFragment; + } +} + +/** + * Used for caching shader IDs + * + * @static + * @private + */ +Filter.SOURCE_KEY_MAP = {}; + diff --git a/packages/core/src/filters/FilterSystem.js b/packages/core/src/filters/FilterSystem.js new file mode 100644 index 0000000..3174318 --- /dev/null +++ b/packages/core/src/filters/FilterSystem.js @@ -0,0 +1,431 @@ +import System from '../System'; + +import RenderTexture from '../renderTexture/RenderTexture'; +import Quad from '../utils/Quad'; +import QuadUv from '../utils/QuadUv'; +import { Rectangle } from '@pixi/math'; +import * as filterTransforms from './filterTransforms'; +import bitTwiddle from 'bit-twiddle'; +import UniformGroup from '../shader/UniformGroup'; +import { DRAW_MODES } from '@pixi/constants'; + +// +/** + * @ignore + * @class + */ +class FilterState +{ + /** + * + */ + constructor() + { + this.renderTexture = null; + this.sourceFrame = new Rectangle(); + this.destinationFrame = new Rectangle(); + this.filters = []; + this.target = null; + this.legacy = false; + this.resolution = 1; + } +} + +/** + * @class + * @memberof PIXI.systems + * @extends PIXI.System + */ +export default class FilterSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + /** + * stores a bunch of PO2 textures used for filtering + * @type {Object} + */ + this.texturePool = {}; + + /** + * a pool for storing filter states, save us creating new ones each tick + * @type {Array} + */ + this.statePool = []; + + /** + * A very simple geometry used when drawing a filter effect to the screen + * @type {Quad} + */ + this.quad = new Quad(); + + this.quadUv = new QuadUv(); + + /** + * Temporary rect for maths + * @type {PIXI.Rectangle} + */ + this.tempRect = new Rectangle(); + + this.activeState = {}; + + /** + * this uniform group is attached to filter uniforms when used + * @type {UniformGroup} + */ + this.globalUniforms = new UniformGroup({ + sourceFrame: this.tempRect, + destinationFrame: this.tempRect, + + // legacy variables + filterArea: new Float32Array(4), + filterClamp: new Float32Array(4), + }, true); + } + + /** + * Adds a new filter to the System. + * + * @param {PIXI.DisplayObject} target - The target of the filter to render. + * @param {PIXI.Filter[]} filters - The filters to apply. + */ + push(target, filters) + { + const renderer = this.renderer; + const filterStack = this.renderer.renderTexture.defaultFilterStack; + const state = this.statePool.pop() || new FilterState(); + + let resolution = filters[0].resolution; + let padding = filters[0].padding; + let autoFit = filters[0].autoFit; + let legacy = filters[0].legacy; + + for (let i = 1; i < filters.length; i++) + { + const filter = filters[i]; + + // lets use the lowest resolution.. + resolution = Math.min(resolution, filter.resolution); + // and the largest amount of padding! + padding = Math.max(padding, filter.padding); + // only auto fit if all filters are autofit + autoFit = autoFit || filter.autoFit; + + legacy = legacy || filter.legacy; + } + + filterStack.push(state); + + state.resolution = resolution; + + state.legacy = legacy; + + // round to whole number based on resolution + // TODO move that to the shader too? + state.sourceFrame = target.filterArea || target.getBounds(true); + + state.sourceFrame.pad(padding); + + if (autoFit) + { + state.sourceFrame.fit(this.renderer.renderTexture.destinationFrame); + } + + state.sourceFrame.round(resolution); + + state.renderTexture = this.getPotFilterTexture(state.sourceFrame.width, state.sourceFrame.height, resolution); + state.filters = filters; + + state.destinationFrame.width = state.renderTexture.width; + state.destinationFrame.height = state.renderTexture.height; + + state.renderTexture.filterFrame = state.sourceFrame; + renderer.renderTexture.bind(state.renderTexture, state.sourceFrame);// /, state.destinationFrame); + renderer.renderTexture.clear(); + } + + /** + * Pops off the filter and applies it. + * + */ + pop() + { + const renderer = this.renderer; + const filterStack = renderer.renderTexture.defaultFilterStack; + const state = filterStack.pop(); + const filters = state.filters; + + this.activeState = state; + + const globalUniforms = this.globalUniforms.uniforms; + + globalUniforms.sourceFrame = state.sourceFrame; + globalUniforms.destinationFrame = state.destinationFrame; + globalUniforms.resolution = state.resolution; + + // only update the rect if its legacy.. + if (state.legacy) + { + const filterArea = globalUniforms.filterArea; + const filterClamp = globalUniforms.filterClamp; + + filterArea[0] = state.destinationFrame.width; + filterArea[1] = state.destinationFrame.height; + filterArea[2] = state.sourceFrame.x; + filterArea[3] = state.sourceFrame.y; + + filterClamp[0] = 0.5 / state.resolution / state.destinationFrame.width; + filterClamp[1] = 0.5 / state.resolution / state.destinationFrame.height; + filterClamp[2] = (state.sourceFrame.width - 0.5) / state.resolution / state.destinationFrame.width; + filterClamp[3] = (state.sourceFrame.height - 0.5) / state.resolution / state.destinationFrame.height; + } + + this.globalUniforms.update(); + + const lastState = filterStack[filterStack.length - 1]; + + if (filters.length === 1) + { + filters[0].apply(this, state.renderTexture, lastState.renderTexture, false, state); + + this.returnFilterTexture(state.renderTexture); + } + else + { + let flip = state.renderTexture; + let flop = this.getPotFilterTexture( + flip.width, + flip.height, + state.resolution + ); + + flop.filterFrame = flip.filterFrame; + + let i = 0; + + for (i = 0; i < filters.length - 1; ++i) + { + filters[i].apply(this, flip, flop, true, state); + + const t = flip; + + flip = flop; + flop = t; + } + + filters[i].apply(this, flip, lastState.renderTexture, false, state); + + this.returnFilterTexture(flip); + this.returnFilterTexture(flop); + } + + this.statePool.push(state); + } + + /** + * Draws a filter. + * + * @param {PIXI.Filter} filter - The filter to draw. + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + * @param {boolean} clear - Should the output be cleared before rendering to it + */ + applyFilter(filter, input, output, clear) + { + const renderer = this.renderer; + + renderer.renderTexture.bind(output, output ? output.filterFrame : null); + + if (clear) + { + // gl.disable(gl.SCISSOR_TEST); + renderer.renderTexture.clear(); + // gl.enable(gl.SCISSOR_TEST); + } + + // set the uniforms.. + filter.uniforms.uSampler = input; + filter.uniforms.filterGlobals = this.globalUniforms; + + // TODO make it so that the order of this does not matter.. + // because it does at the moment cos of global uniforms. + // they need to get resynced + + renderer.state.setState(filter.state); + renderer.shader.bind(filter); + + if (filter.legacy) + { + this.quadUv.map(input._frame, input.filterFrame); + + renderer.geometry.bind(this.quadUv); + renderer.geometry.draw(DRAW_MODES.TRIANGLES); + } + else + { + renderer.geometry.bind(this.quad); + renderer.geometry.draw(DRAW_MODES.TRIANGLE_STRIP); + } + } + + /** + * Calculates the mapped matrix. + * + * TODO playing around here.. this is temporary - (will end up in the shader) + * this returns a matrix that will normalise map filter cords in the filter to screen space + * + * @param {PIXI.Matrix} outputMatrix - the matrix to output to. + * @return {PIXI.Matrix} The mapped matrix. + */ + calculateScreenSpaceMatrix(outputMatrix) + { + const currentState = this.activeState; + + return filterTransforms.calculateScreenSpaceMatrix( + outputMatrix, + currentState.sourceFrame, + currentState.destinationFrame + ); + } + + /** + * This will map the filter coord so that a texture can be used based on the transform of a sprite + * + * @param {PIXI.Matrix} outputMatrix - The matrix to output to. + * @param {PIXI.Sprite} sprite - The sprite to map to. + * @return {PIXI.Matrix} The mapped matrix. + */ + calculateSpriteMatrix(outputMatrix, sprite) + { + const currentState = this.activeState; + + return filterTransforms.calculateSpriteMatrix( + outputMatrix, + currentState.sourceFrame, + currentState.destinationFrame, + sprite + ); + } + + /** + * Destroys this Filter System. + * + * @param {boolean} [contextLost=false] context was lost, do not free shaders + * + */ + destroy(contextLost = false) + { + if (!contextLost) + { + this.emptyPool(); + } + else + { + this.texturePool = {}; + } + } + + /** + * Gets a Power-of-Two render texture. + * + * TODO move to a seperate class could be on renderer? + * also - could cause issue with multiple contexts? + * + * @private + * @param {WebGLRenderingContext} gl - The webgl rendering context + * @param {number} minWidth - The minimum width of the render target. + * @param {number} minHeight - The minimum height of the render target. + * @param {number} resolution - The resolution of the render target. + * @return {PIXI.RenderTarget} The new render target. + */ + getPotFilterTexture(minWidth, minHeight, resolution) + { + minWidth = bitTwiddle.nextPow2(minWidth); + minHeight = bitTwiddle.nextPow2(minHeight); + resolution = resolution || 1; + + const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + + if (!this.texturePool[key]) + { + this.texturePool[key] = []; + } + + let renderTexture = this.texturePool[key].pop(); + + if (!renderTexture) + { + // temporary bypass cache.. + // internally - this will cause a texture to be bound.. + renderTexture = RenderTexture.create({ + width: minWidth, + height: minHeight, + resolution, + }); + } + + return renderTexture; + } + + /** + * Gets extra render texture to use inside current filter + * + * @param {number} resolution resolution of the renderTexture + * @returns {PIXI.RenderTexture} + */ + getFilterTexture(resolution) + { + const rt = this.activeState.renderTexture; + + const filterTexture = this.getPotFilterTexture(rt.width, rt.height, resolution || rt.baseTexture.resolution); + + filterTexture.filterFrame = rt.filterFrame; + + return filterTexture; + } + + /** + * Frees a render texture back into the pool. + * + * @param {PIXI.RenderTarget} renderTexture - The renderTarget to free + */ + returnFilterTexture(renderTexture) + { + renderTexture.filterFrame = null; + + const base = renderTexture.baseTexture; + + const minWidth = base.width; + const minHeight = base.height; + + const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + + this.texturePool[key].push(renderTexture); + } + + /** + * Empties the texture pool. + * + */ + emptyPool() + { + for (const i in this.texturePool) + { + const textures = this.texturePool[i]; + + if (textures) + { + for (let j = 0; j < textures.length; j++) + { + textures[j].destroy(true); + } + } + } + + this.texturePool = {}; + } +} diff --git a/packages/core/src/filters/defaultFilter.frag b/packages/core/src/filters/defaultFilter.frag new file mode 100644 index 0000000..a14c1be --- /dev/null +++ b/packages/core/src/filters/defaultFilter.frag @@ -0,0 +1,22 @@ +varying vec2 vTextureCoord; +varying vec2 vFilterCoord; + +uniform sampler2D uSampler; +uniform sampler2D filterSampler; + +void main(void){ + vec4 masky = texture2D(filterSampler, vFilterCoord); + vec4 sample = texture2D(uSampler, vTextureCoord); + vec4 color; + if(mod(vFilterCoord.x, 1.0) > 0.5) + { + color = vec4(1.0, 0.0, 0.0, 1.0); + } + else + { + color = vec4(0.0, 1.0, 0.0, 1.0); + } + // gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0); + gl_FragColor = mix(sample, masky, 0.5); + gl_FragColor *= sample.a; +} diff --git a/packages/core/src/filters/defaultFilter.vert b/packages/core/src/filters/defaultFilter.vert new file mode 100644 index 0000000..e1febca --- /dev/null +++ b/packages/core/src/filters/defaultFilter.vert @@ -0,0 +1,14 @@ +attribute vec2 aVertexPosition; +attribute vec2 aTextureCoord; + +uniform mat3 projectionMatrix; +uniform mat3 filterMatrix; + +varying vec2 vTextureCoord; +varying vec2 vFilterCoord; + +void main(void){ + gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); + vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy; + vTextureCoord = aTextureCoord ; +} diff --git a/packages/core/src/filters/filterTransforms.js b/packages/core/src/filters/filterTransforms.js new file mode 100644 index 0000000..87a3cd6 --- /dev/null +++ b/packages/core/src/filters/filterTransforms.js @@ -0,0 +1,50 @@ +import { Matrix } from '@pixi/math'; + +/** + * Calculates the mapped matrix + * @param filterArea {Rectangle} The filter area + * @param sprite {Sprite} the target sprite + * @param outputMatrix {Matrix} @alvin + * @private + */ +// this returns a matrix that will normalise map filter cords in the filter to screen space +export function calculateScreenSpaceMatrix(outputMatrix, filterArea, textureSize) +{ + // TODO unwrap? + const mappedMatrix = outputMatrix.identity(); + + mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); + + mappedMatrix.scale(textureSize.width, textureSize.height); + + return mappedMatrix; +} + +export function calculateNormalizedScreenSpaceMatrix(outputMatrix, filterArea, textureSize) +{ + const mappedMatrix = outputMatrix.identity(); + + mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); + + const translateScaleX = (textureSize.width / filterArea.width); + const translateScaleY = (textureSize.height / filterArea.height); + + mappedMatrix.scale(translateScaleX, translateScaleY); + + return mappedMatrix; +} + +// this will map the filter coord so that a texture can be used based on the transform of a sprite +export function calculateSpriteMatrix(outputMatrix, filterArea, textureSize, sprite) +{ + const orig = sprite._texture.orig; + const mappedMatrix = outputMatrix.set(textureSize.width, 0, 0, textureSize.height, filterArea.x, filterArea.y); + const worldTransform = sprite.worldTransform.copyTo(Matrix.TEMP_MATRIX); + + worldTransform.invert(); + mappedMatrix.prepend(worldTransform); + mappedMatrix.scale(1.0 / orig.width, 1.0 / orig.height); + mappedMatrix.translate(sprite.anchor.x, sprite.anchor.y); + + return mappedMatrix; +} diff --git a/packages/core/src/filters/spriteMask/SpriteMaskFilter.js b/packages/core/src/filters/spriteMask/SpriteMaskFilter.js new file mode 100644 index 0000000..91697b7 --- /dev/null +++ b/packages/core/src/filters/spriteMask/SpriteMaskFilter.js @@ -0,0 +1,63 @@ +import Filter from '../Filter'; +import { Matrix } from '@pixi/math'; +import vertex from './spriteMaskFilter.vert'; +import fragment from './spriteMaskFilter.frag'; +import { default as TextureMatrix } from '../../textures/TextureMatrix'; + +/** + * The SpriteMaskFilter class + * + * @class + * @extends PIXI.Filter + * @memberof PIXI + */ +export default class SpriteMaskFilter extends Filter +{ + /** + * @param {PIXI.Sprite} sprite - the target sprite + */ + constructor(sprite) + { + const maskMatrix = new Matrix(); + + super(vertex, fragment); + + sprite.renderable = false; + + this.maskSprite = sprite; + this.maskMatrix = maskMatrix; + } + + /** + * Applies the filter + * + * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + */ + apply(filterManager, input, output) + { + const maskSprite = this.maskSprite; + const tex = this.maskSprite.texture; + + if (!tex.valid) + { + return; + } + if (!tex.transform) + { + // margin = 0.0, let it bleed a bit, shader code becomes easier + // assuming that atlas textures were made with 1-pixel padding + tex.transform = new TextureMatrix(tex, 0.0); + } + tex.transform.update(); + + this.uniforms.mask = tex; + this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite) + .prepend(tex.transform.mapCoord); + this.uniforms.alpha = maskSprite.worldAlpha; + this.uniforms.maskClamp = tex.transform.uClampFrame; + + filterManager.applyFilter(this, input, output); + } +} diff --git a/packages/core/src/filters/spriteMask/spriteMaskFilter.frag b/packages/core/src/filters/spriteMask/spriteMaskFilter.frag new file mode 100644 index 0000000..0e4aef8 --- /dev/null +++ b/packages/core/src/filters/spriteMask/spriteMaskFilter.frag @@ -0,0 +1,23 @@ +varying vec2 vMaskCoord; +varying vec2 vTextureCoord; + +uniform sampler2D uSampler; +uniform sampler2D mask; +uniform float alpha; +uniform vec4 maskClamp; + +void main(void) +{ + float clip = step(3.5, + step(maskClamp.x, vMaskCoord.x) + + step(maskClamp.y, vMaskCoord.y) + + step(vMaskCoord.x, maskClamp.z) + + step(vMaskCoord.y, maskClamp.w)); + + vec4 original = texture2D(uSampler, vTextureCoord); + vec4 masky = texture2D(mask, vMaskCoord); + + original *= (masky.r * masky.a * alpha * clip); + + gl_FragColor = original; +} diff --git a/packages/core/src/filters/spriteMask/spriteMaskFilter.vert b/packages/core/src/filters/spriteMask/spriteMaskFilter.vert new file mode 100644 index 0000000..d23a30c --- /dev/null +++ b/packages/core/src/filters/spriteMask/spriteMaskFilter.vert @@ -0,0 +1,16 @@ +attribute vec2 aVertexPosition; +attribute vec2 aTextureCoord; + +uniform mat3 projectionMatrix; +uniform mat3 otherMatrix; + +varying vec2 vMaskCoord; +varying vec2 vTextureCoord; + +void main(void) +{ + gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); + + vTextureCoord = aTextureCoord; + vMaskCoord = ( otherMatrix * vec3( aTextureCoord, 1.0) ).xy; +} diff --git a/packages/core/src/framebuffer/FrameBuffer.js b/packages/core/src/framebuffer/FrameBuffer.js new file mode 100644 index 0000000..84e9fcc --- /dev/null +++ b/packages/core/src/framebuffer/FrameBuffer.js @@ -0,0 +1,106 @@ +import Texture from '../textures/BaseTexture'; +import { FORMATS, TYPES } from '@pixi/constants'; + +/** + * Frame buffer + * @class + * @memberof PIXI + */ +export default class FrameBuffer +{ + constructor(width, height) + { + this.width = width || 100; + this.height = height || 100; + + this.stencil = false; + this.depth = false; + + this.dirtyId = 0; + this.dirtyFormat = 0; + this.dirtySize = 0; + + this.depthTexture = null; + this.colorTextures = []; + + this.glFrameBuffers = {}; + } + + get colorTexture() + { + return this.colorTextures[0]; + } + + addColorTexture(index, texture) + { + // TODO add some validation to the texture - same width / height etc? + this.colorTextures[index || 0] = texture || new Texture(null, { scaleMode: 0, + resolution: 1, + mipmap: false, + width: this.width, + height: this.height });// || new Texture(); + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + addDepthTexture(texture) + { + /* eslint-disable max-len */ + this.depthTexture = texture || new Texture(null, { scaleMode: 0, + resolution: 1, + width: this.width, + height: this.height, + mipmap: false, + format: FORMATS.DEPTH_COMPONENT, + type: TYPES.UNSIGNED_SHORT });// UNSIGNED_SHORT; + /* eslint-disable max-len */ + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + enableDepth() + { + this.depth = true; + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + enableStencil() + { + this.stencil = true; + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + resize(width, height) + { + if (width === this.width && height === this.height) return; + + this.width = width; + this.height = height; + + this.dirtyId++; + this.dirtySize++; + + for (let i = 0; i < this.colorTextures.length; i++) + { + this.colorTextures[i].setSize(width, height); + } + + if (this.depthTexture) + { + this.depthTexture.setSize(width, height); + } + } +} diff --git a/packages/core/src/framebuffer/FramebufferSystem.js b/packages/core/src/framebuffer/FramebufferSystem.js new file mode 100644 index 0000000..2cc2437 --- /dev/null +++ b/packages/core/src/framebuffer/FramebufferSystem.js @@ -0,0 +1,246 @@ +import System from '../System'; +import { Rectangle } from '@pixi/math'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class FramebufferSystem extends System +{ + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + contextChange() + { + this.gl = this.renderer.gl; + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + this.current = null; + this.viewport = new Rectangle(); + + this.drawBufferExtension = this.renderer.context.extensions.drawBuffers; + } + + bind(framebuffer, frame) + { + const gl = this.gl; + + this.current = framebuffer; + + if (framebuffer) + { + // TODO cacheing layer! + + const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID] || this.initFramebuffer(framebuffer); + + gl.bindFramebuffer(gl.FRAMEBUFFER, fbo.framebuffer); + // makesure all textures are unbound.. + + // now check for updates... + if (fbo.dirtyId !== framebuffer.dirtyId) + { + fbo.dirtyId = framebuffer.dirtyId; + + if (fbo.dirtyFormat !== framebuffer.dirtyFormat) + { + fbo.dirtyFormat = framebuffer.dirtyFormat; + this.updateFramebuffer(framebuffer); + } + else if (fbo.dirtySize !== framebuffer.dirtySize) + { + fbo.dirtySize = framebuffer.dirtySize; + this.resizeFramebuffer(framebuffer); + } + } + + for (let i = 0; i < framebuffer.colorTextures.length; i++) + { + if (framebuffer.colorTextures[i].texturePart) + { + this.renderer.texture.unbind(framebuffer.colorTextures[i].texture); + } + else + { + this.renderer.texture.unbind(framebuffer.colorTextures[i]); + } + } + + if (framebuffer.depthTexture) + { + this.renderer.texture.unbind(framebuffer.depthTexture); + } + + if (frame) + { + this.setViewport(frame.x, frame.y, frame.width, frame.height); + } + else + { + this.setViewport(0, 0, framebuffer.width, framebuffer.height); + } + } + else + { + gl.bindFramebuffer(gl.FRAMEBUFFER, null); + + if (frame) + { + this.setViewport(frame.x, frame.y, frame.width, frame.height); + } + else + { + this.setViewport(0, 0, this.renderer.width, this.renderer.height); + } + } + } + + setViewport(x, y, width, height) + { + const v = this.viewport; + + if (v.width !== width || v.height !== height || v.x !== x || v.y !== y) + { + v.x = x; + v.y = y; + v.width = width; + v.height = height; + + this.gl.viewport(x, y, width, height); + } + } + + get size() + { + if (this.current) + { + // TODO store temp + return { x: 0, y: 0, width: this.current.width, height: this.current.height }; + } + + return { x: 0, y: 0, width: this.renderer.width, height: this.renderer.height }; + } + + clear(r, g, b, a) + { + const gl = this.gl; + + // TODO clear color can be set only one right? + gl.clearColor(r, g, b, a); + gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); + } + + // private functions... + + initFramebuffer(framebuffer) + { + const gl = this.gl; + + // TODO - make this a class? + const fbo = { + framebuffer: gl.createFramebuffer(), + stencil: null, + dirtyId: 0, + dirtyFormat: 0, + dirtySize: 0, + }; + + framebuffer.glFrameBuffers[this.CONTEXT_UID] = fbo; + + return fbo; + } + + resizeFramebuffer(framebuffer) + { + const gl = this.gl; + + if (framebuffer.stencil || framebuffer.depth) + { + gl.bindRenderbuffer(gl.RENDERBUFFER, this.stencil); + gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); + } + } + + updateFramebuffer(framebuffer) + { + const gl = this.gl; + + const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID]; + + // bind the color texture + const colorTextures = framebuffer.colorTextures; + + let count = colorTextures.length; + + if (!this.drawBufferExtension) + { + count = Math.min(count, 1); + } + + const activeTextures = []; + + for (let i = 0; i < count; i++) + { + const texture = framebuffer.colorTextures[i]; + + if (texture.texturePart) + { + this.renderer.texture.bind(texture.texture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.COLOR_ATTACHMENT0 + i, + gl.TEXTURE_CUBE_MAP_NEGATIVE_X + texture.side, + texture.texture._glTextures[this.CONTEXT_UID].texture, + 0); + } + else + { + this.renderer.texture.bind(texture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.COLOR_ATTACHMENT0 + i, + gl.TEXTURE_2D, + texture._glTextures[this.CONTEXT_UID].texture, + 0); + } + + activeTextures.push(gl.COLOR_ATTACHMENT0 + i); + } + + if (this.drawBufferExtension && activeTextures.length > 1) + { + this.drawBufferExtension.drawBuffersWEBGL(activeTextures); + } + + if (framebuffer.depthTexture) + { + const depthTextureExt = this.renderer.context.extensions.depthTexture; + + if (depthTextureExt) + { + const depthTexture = framebuffer.depthTexture; + + this.renderer.texture.bind(depthTexture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.DEPTH_ATTACHMENT, + gl.TEXTURE_2D, + depthTexture._glTextures[this.CONTEXT_UID].texture, + 0); + } + } + + if (framebuffer.stencil || framebuffer.depth) + { + fbo.stencil = gl.createRenderbuffer(); + + gl.bindRenderbuffer(gl.RENDERBUFFER, fbo.stencil); + + // TODO.. this is depth AND stencil? + gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, fbo.stencil); + gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); + // fbo.enableStencil(); + } + } +} diff --git a/packages/core/src/geometry/GLBuffer.js b/packages/core/src/geometry/GLBuffer.js new file mode 100644 index 0000000..0477e8a --- /dev/null +++ b/packages/core/src/geometry/GLBuffer.js @@ -0,0 +1,9 @@ +export default class GLBuffer +{ + constructor(buffer) + { + this.buffer = buffer; + this.updateID = -1; + this.byteLength = -1; + } +} diff --git a/packages/core/src/geometry/GeometrySystem.js b/packages/core/src/geometry/GeometrySystem.js new file mode 100644 index 0000000..959f827 --- /dev/null +++ b/packages/core/src/geometry/GeometrySystem.js @@ -0,0 +1,388 @@ +import System from '../System'; +import GLBuffer from './GLBuffer'; + +const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class GeometrySystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this._activeGeometry = null; + this._activeVao = null; + + this.hasVao = true; + this.hasInstance = true; + } + + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + contextChange() + { + const gl = this.gl = this.renderer.gl; + + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + + // webgl2 + if (!gl.createVertexArray) + { + // webgl 1! + let nativeVaoExtension = this.renderer.context.extensions.vertexArrayObject; + + if (this.renderer.legacy) + { + nativeVaoExtension = null; + } + + if (nativeVaoExtension) + { + gl.createVertexArray = () => + nativeVaoExtension.createVertexArrayOES(); + + gl.bindVertexArray = (vao) => + nativeVaoExtension.bindVertexArrayOES(vao); + + gl.deleteVertexArray = (vao) => + nativeVaoExtension.deleteVertexArrayOES(vao); + } + else + { + this.hasVao = false; + gl.createVertexArray = () => + { + // empty + }; + + gl.bindVertexArray = () => + { + // empty + }; + + gl.deleteVertexArray = () => + { + // empty + }; + } + } + + if (!gl.vertexAttribDivisor) + { + const instanceExt = gl.getExtension('ANGLE_instanced_arrays'); + + if (instanceExt) + { + gl.vertexAttribDivisor = (a, b) => + instanceExt.vertexAttribDivisorANGLE(a, b); + + gl.drawElementsInstanced = (a, b, c, d, e) => + instanceExt.drawElementsInstancedANGLE(a, b, c, d, e); + + gl.drawArraysInstanced = (a, b, c, d) => + instanceExt.drawArraysInstancedANGLE(a, b, c, d); + } + else + { + this.hasInstance = false; + } + } + } + + /** + * Binds geometry so that is can be drawn. Creating a Vao if required + * @private + * @param {PIXI.Geometry} geometry instance of geometry to bind + */ + bind(geometry, shader) + { + shader = shader || this.renderer.shader.shader; + + const gl = this.gl; + + // not sure the best way to address this.. + // currently different shaders require different VAOs for the same geometry + // Still mulling over the best way to solve this one.. + // will likely need to modify the shader attribute locations at run time! + let vaos = geometry.glVertexArrayObjects[this.CONTEXT_UID]; + + if (!vaos) + { + geometry.glVertexArrayObjects[this.CONTEXT_UID] = vaos = {}; + } + + const vao = vaos[shader.program.id] || this.initGeometryVao(geometry, shader.program); + + this._activeGeometry = geometry; + + if (this._activeVao !== vao) + { + this._activeVao = vao; + + if (this.hasVao) + { + gl.bindVertexArray(vao); + } + else + { + this.activateVao(geometry, shader.program); + } + } + + // TODO - optimise later! + // don't need to loop through if nothing changed! + // maybe look to add an 'autoupdate' to geometry? + this.updateBuffers(); + } + + reset() + { + this.unbind(); + } + + updateBuffers() + { + const geometry = this._activeGeometry; + const gl = this.gl; + + for (let i = 0; i < geometry.buffers.length; i++) + { + const buffer = geometry.buffers[i]; + + const glBuffer = buffer._glBuffers[this.CONTEXT_UID]; + + if (buffer._updateID !== glBuffer.updateID) + { + glBuffer.updateID = buffer._updateID; + + // TODO can cache this on buffer! maybe added a getter / setter? + const type = buffer.index ? gl.ELEMENT_ARRAY_BUFFER : gl.ARRAY_BUFFER; + const drawType = buffer.static ? gl.STATIC_DRAW : gl.DYNAMIC_DRAW; + + gl.bindBuffer(type, glBuffer.buffer); + + if (glBuffer.byteLength >= buffer.data.byteLength) + { + // offset is always zero for now! + gl.bufferSubData(type, 0, buffer.data); + } + else + { + gl.bufferData(type, buffer.data, drawType); + } + } + } + } + + checkCompatability(geometry, program) + { + // geometry must have at least all the attributes that the shader requires. + const geometryAttributes = geometry.attributes; + const shaderAttributes = program.attributeData; + + for (const j in shaderAttributes) + { + if (!geometryAttributes[j]) + { + throw new Error(`shader and geometry incompatible, geometry missing the "${j}" attribute`); + } + } + } + + /** + * Creates a Vao with the same structure as the geometry and stores it on the geometry. + * @private + * @param {PIXI.Geometry} geometry instance of geometry to to generate Vao for + */ + initGeometryVao(geometry, program) + { + this.checkCompatability(geometry, program); + + const gl = this.gl; + const CONTEXT_UID = this.CONTEXT_UID; + const buffers = geometry.buffers; + const attributes = geometry.attributes; + + const tempStride = {}; + const tempStart = {}; + + for (const j in buffers) + { + tempStride[j] = 0; + tempStart[j] = 0; + } + + for (const j in attributes) + { + if (!attributes[j].size && program.attributeData[j]) + { + attributes[j].size = program.attributeData[j].size; + } + + tempStride[attributes[j].buffer] += attributes[j].size * byteSizeMap[attributes[j].type]; + } + + for (const j in attributes) + { + const attribute = attributes[j]; + const attribSize = attribute.size; + + if (attribute.stride === undefined) + { + if (tempStride[attribute.buffer] === attribSize * byteSizeMap[attribute.type]) + { + attribute.stride = 0; + } + else + { + attribute.stride = tempStride[attribute.buffer]; + } + } + + if (attribute.start === undefined) + { + attribute.start = tempStart[attribute.buffer]; + + tempStart[attribute.buffer] += attribSize * byteSizeMap[attribute.type]; + } + } + + // first update - and create the buffers! + // only create a gl buffer if it actually gets + for (let i = 0; i < buffers.length; i++) + { + const buffer = buffers[i]; + + if (!buffer._glBuffers[CONTEXT_UID]) + { + buffer._glBuffers[CONTEXT_UID] = new GLBuffer(gl.createBuffer()); + } + } + + // TODO - maybe make this a data object? + // lets wait to see if we need to first! + const vao = gl.createVertexArray(); + + gl.bindVertexArray(vao); + + this.activateVao(geometry, program); + + gl.bindVertexArray(null); + + // add it to the cache! + geometry.glVertexArrayObjects[this.CONTEXT_UID][program.id] = vao; + + return vao; + } + + activateVao(geometry, program) + { + const gl = this.gl; + const CONTEXT_UID = this.CONTEXT_UID; + const buffers = geometry.buffers; + const attributes = geometry.attributes; + + if (geometry.indexBuffer) + { + // first update the index buffer if we have one.. + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, geometry.indexBuffer._glBuffers[CONTEXT_UID].buffer); + } + + let lastBuffer = null; + + // add a new one! + for (const j in attributes) + { + const attribute = attributes[j]; + const buffer = buffers[attribute.buffer]; + const glBuffer = buffer._glBuffers[CONTEXT_UID]; + + if (program.attributeData[j]) + { + if (lastBuffer !== glBuffer) + { + gl.bindBuffer(gl.ARRAY_BUFFER, glBuffer.buffer); + + lastBuffer = glBuffer; + } + + const location = program.attributeData[j].location; + + // TODO introduce state again + // we can optimise this for older devices that have no VAOs + gl.enableVertexAttribArray(location); + + gl.vertexAttribPointer(location, + attribute.size, + attribute.type || gl.FLOAT, + attribute.normalized, + attribute.stride, + attribute.start); + + if (attribute.instance) + { + // TODO calculate instance count based of this... + if (this.hasInstance) + { + gl.vertexAttribDivisor(location, 1); + } + else + { + throw new Error('geometry error, GPU Instancing is not supported on this device'); + } + } + } + } + } + + draw(type, size, start, instanceCount) + { + const gl = this.gl; + const geometry = this._activeGeometry; + + // TODO.. this should not change so maybe cache the function? + + if (geometry.indexBuffer) + { + if (geometry.instanced) + { + /* eslint-disable max-len */ + gl.drawElementsInstanced(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2, instanceCount || 1); + /* eslint-enable max-len */ + } + else + { + gl.drawElements(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2); + } + } + else + if (geometry.instanced) + { + // TODO need a better way to calculate size.. + gl.drawArraysInstanced(type, start, size || geometry.getSize(), instanceCount || 1); + } + else + { + gl.drawArrays(type, start, size || geometry.getSize()); + } + + return this; + } + + unbind() + { + this.gl.bindVertexArray(null); + this._activeVao = null; + this._activeGeometry = null; + } +} diff --git a/packages/core/src/geometry/utils/setVertexAttribArrays.js b/packages/core/src/geometry/utils/setVertexAttribArrays.js new file mode 100644 index 0000000..23b6959 --- /dev/null +++ b/packages/core/src/geometry/utils/setVertexAttribArrays.js @@ -0,0 +1,54 @@ +// var GL_MAP = {}; + +/** + * @param gl {WebGLRenderingContext} The current WebGL context + * @param attribs {*} + * @param state {*} + */ +export default function setVertexAttribArrays(gl, attribs, state) +{ + let i; + + if (state) + { + const tempAttribState = state.tempAttribState; + const attribState = state.attribState; + + for (i = 0; i < tempAttribState.length; i++) + { + tempAttribState[i] = false; + } + + // set the new attribs + for (i = 0; i < attribs.length; i++) + { + tempAttribState[attribs[i].attribute.location] = true; + } + + for (i = 0; i < attribState.length; i++) + { + if (attribState[i] !== tempAttribState[i]) + { + attribState[i] = tempAttribState[i]; + + if (state.attribState[i]) + { + gl.enableVertexAttribArray(i); + } + else + { + gl.disableVertexAttribArray(i); + } + } + } + } + else + { + for (i = 0; i < attribs.length; i++) + { + const attrib = attribs[i]; + + gl.enableVertexAttribArray(attrib.attribute.location); + } + } +} diff --git a/packages/core/src/index.js b/packages/core/src/index.js index 58c456a..fd1f547 100644 --- a/packages/core/src/index.js +++ b/packages/core/src/index.js @@ -1,30 +1,30 @@ import * as resources from './textures/resources'; -import * as systems from './renderers/systems'; +import * as systems from './systems'; -export { resources }; export { systems }; +export { resources }; -export { default as Renderer } from './renderers/Renderer'; -export { default as AbstractRenderer } from './renderers/AbstractRenderer'; -export { default as FrameBuffer } from './textures/FrameBuffer'; +export { default as System } from './System'; +export { default as Renderer } from './Renderer'; +export { default as AbstractRenderer } from './AbstractRenderer'; +export { default as FrameBuffer } from './framebuffer/FrameBuffer'; export { default as CubeTexture } from './textures/CubeTexture'; export { default as BaseTexture } from './textures/BaseTexture'; export { default as Texture } from './textures/Texture'; export { default as TextureMatrix } from './textures/TextureMatrix'; -export { default as RenderTexture } from './textures/RenderTexture'; -export { default as BaseRenderTexture } from './textures/BaseRenderTexture'; +export { default as RenderTexture } from './renderTexture/RenderTexture'; +export { default as BaseRenderTexture } from './renderTexture/BaseRenderTexture'; export { default as TextureUvs } from './textures/TextureUvs'; -export { default as State } from './renderers/State'; -export { default as ObjectRenderer } from './renderers/utils/ObjectRenderer'; -export { default as Quad } from './renderers/utils/Quad'; -export { default as QuadUv } from './renderers/utils/QuadUv'; -export { default as checkMaxIfStatmentsInShader } from './renderers/utils/checkMaxIfStatmentsInShader'; +export { default as State } from './state/State'; +export { default as ObjectRenderer } from './batch/ObjectRenderer'; +export { default as Quad } from './utils/Quad'; +export { default as QuadUv } from './utils/QuadUv'; +export { default as checkMaxIfStatmentsInShader } from './shader/utils/checkMaxIfStatmentsInShader'; export { default as Shader } from './shader/Shader'; export { default as Program } from './shader/Program'; export { default as UniformGroup } from './shader/UniformGroup'; -export { default as SpriteMaskFilter } from './renderers/filters/spriteMask/SpriteMaskFilter'; -export { default as Filter } from './renderers/filters/Filter'; +export { default as SpriteMaskFilter } from './filters/spriteMask/SpriteMaskFilter'; +export { default as Filter } from './filters/Filter'; export { default as Attribute } from './geometry/Attribute'; export { default as Buffer } from './geometry/Buffer'; export { default as Geometry } from './geometry/Geometry'; - diff --git a/packages/core/src/mask/MaskSystem.js b/packages/core/src/mask/MaskSystem.js new file mode 100644 index 0000000..99174c2 --- /dev/null +++ b/packages/core/src/mask/MaskSystem.js @@ -0,0 +1,199 @@ +import System from '../System'; +import AlphaMaskFilter from '../filters/spriteMask/SpriteMaskFilter'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class MaskSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + // TODO - we don't need both! + this.scissor = false; + this.scissorData = null; + this.scissorRenderTarget = null; + + this.enableScissor = false; + + this.alphaMaskPool = []; + this.alphaMaskIndex = 0; + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.DisplayObject} target - Display Object to push the mask to + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + push(target, maskData) + { + // TODO the root check means scissor rect will not + // be used on render textures more info here: + // https://github.com/pixijs/pixi.js/pull/3545 + + if (maskData.texture) + { + this.pushSpriteMask(target, maskData); + } + else if (this.enableScissor + && !this.scissor + && this.renderer._activeRenderTarget.root + && !this.renderer.stencil.stencilMaskStack.length + && maskData.isFastRect()) + { + const matrix = maskData.worldTransform; + + let rot = Math.atan2(matrix.b, matrix.a); + + // use the nearest degree! + rot = Math.round(rot * (180 / Math.PI)); + + if (rot % 90) + { + this.pushStencilMask(maskData); + } + else + { + this.pushScissorMask(target, maskData); + } + } + else + { + this.pushStencilMask(maskData); + } + } + + /** + * Removes the last mask from the mask stack and doesn't return it. + * + * @param {PIXI.DisplayObject} target - Display Object to pop the mask from + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + pop(target, maskData) + { + if (maskData.texture) + { + this.popSpriteMask(target, maskData); + } + else if (this.enableScissor && !this.renderer.stencil.stencilMaskStack.length) + { + this.popScissorMask(target, maskData); + } + else + { + this.popStencilMask(target, maskData); + } + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.RenderTarget} target - Display Object to push the sprite mask to + * @param {PIXI.Sprite} maskData - Sprite to be used as the mask + */ + pushSpriteMask(target, maskData) + { + let alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; + + if (!alphaMaskFilter) + { + alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; + } + + alphaMaskFilter[0].resolution = this.renderer.resolution; + alphaMaskFilter[0].maskSprite = maskData; + + // TODO - may cause issues! + target.filterArea = maskData.getBounds(true); + + this.renderer.filterSystem.pushFilter(target, alphaMaskFilter); + + this.alphaMaskIndex++; + } + + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popSpriteMask() + { + this.renderer.filterSystem.popFilter(); + this.alphaMaskIndex--; + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + pushStencilMask(maskData) + { + this.renderer.batch.flush(); + this.renderer.stencil.pushStencil(maskData); + } + + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popStencilMask() + { + // this.renderer.currentRenderer.stop(); + this.renderer.stencil.popStencil(); + } + + /** + * + * @param {PIXI.DisplayObject} target - Display Object to push the mask to + * @param {PIXI.Graphics} maskData - The masking data. + */ + pushScissorMask(target, maskData) + { + maskData.renderable = true; + + const renderTarget = this.renderer._activeRenderTarget; + + const bounds = maskData.getBounds(); + + bounds.fit(renderTarget.size); + maskData.renderable = false; + + this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); + + const resolution = this.renderer.resolution; + + this.renderer.gl.scissor( + bounds.x * resolution, + (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, + bounds.width * resolution, + bounds.height * resolution + ); + + this.scissorRenderTarget = renderTarget; + this.scissorData = maskData; + this.scissor = true; + } + + /** + * + * + */ + popScissorMask() + { + this.scissorRenderTarget = null; + this.scissorData = null; + this.scissor = false; + + // must be scissor! + const gl = this.renderer.gl; + + gl.disable(gl.SCISSOR_TEST); + } +} diff --git a/packages/core/src/mask/StencilSystem.js b/packages/core/src/mask/StencilSystem.js new file mode 100644 index 0000000..62796a5 --- /dev/null +++ b/packages/core/src/mask/StencilSystem.js @@ -0,0 +1,133 @@ +import System from '../System'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class StencilSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + this.stencilMaskStack = []; + } + + /** + * Changes the mask stack that is used by this System. + * + * @param {PIXI.Graphics[]} stencilMaskStack - The mask stack + */ + setMaskStack(stencilMaskStack) + { + const gl = this.renderer.gl; + + if (stencilMaskStack.length !== this.stencilMaskStack.length) + { + if (stencilMaskStack.length === 0) + { + gl.disable(gl.STENCIL_TEST); + } + else + { + gl.enable(gl.STENCIL_TEST); + } + } + + this.stencilMaskStack = stencilMaskStack; + } + + /** + * Applies the Mask and adds it to the current stencil stack. @alvin + * + * @param {PIXI.Graphics} graphics - The mask + */ + pushStencil(graphics) + { + this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); + + // this.renderer._activeRenderTarget.attachStencilBuffer(); + + const gl = this.renderer.gl; + const prevMaskCount = this.stencilMaskStack.length; + + if (prevMaskCount === 0) + { + gl.enable(gl.STENCIL_TEST); + } + + this.stencilMaskStack.push(graphics); + + // Increment the refference stencil value where the new mask overlaps with the old ones. + gl.colorMask(false, false, false, false); + gl.stencilFunc(gl.EQUAL, prevMaskCount, this._getBitwiseMask()); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.INCR); + this.renderer.plugins.graphics.render(graphics); + + this._useCurrent(); + } + + /** + * Removes the last mask from the stencil stack. @alvin + */ + popStencil() + { + this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); + + const gl = this.renderer.gl; + const graphics = this.stencilMaskStack.pop(); + + if (this.stencilMaskStack.length === 0) + { + // the stack is empty! + gl.disable(gl.STENCIL_TEST); + gl.clear(gl.STENCIL_BUFFER_BIT); + gl.clearStencil(0); + } + else + { + // Decrement the refference stencil value where the popped mask overlaps with the other ones + gl.colorMask(false, false, false, false); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.DECR); + this.renderer.plugins.graphics.render(graphics); + + this._useCurrent(); + } + } + + /** + * Setup renderer to use the current stencil data. + */ + _useCurrent() + { + const gl = this.renderer.gl; + + gl.colorMask(true, true, true, true); + gl.stencilFunc(gl.EQUAL, this.stencilMaskStack.length, this._getBitwiseMask()); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP); + } + + /** + * Fill 1s equal to the number of acitve stencil masks. + * + * @return {number} The bitwise mask. + */ + _getBitwiseMask() + { + return (1 << this.stencilMaskStack.length) - 1; + } + + /** + * Destroys the mask stack. + * + */ + destroy() + { + super.destroy(this); + + this.stencilMaskStack = null; + } +} diff --git a/packages/core/src/projection/ProjectionSystem.js b/packages/core/src/projection/ProjectionSystem.js new file mode 100644 index 0000000..6e63b73 --- /dev/null +++ b/packages/core/src/projection/ProjectionSystem.js @@ -0,0 +1,73 @@ +import System from '../System'; +import { Matrix } from '@pixi/math'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class ProjectionSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.projectionMatrix = new Matrix(); + } + + update(destinationFrame, sourceFrame, resolution, root) + { + this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; + this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; + + this.calculateProjection(this.destinationFrame, this.sourceFrame, resolution, root); + + this.renderer.globalUniforms.uniforms.projectionMatrix = this.projectionMatrix; + this.renderer.globalUniforms.update(); + } + + /** + * Updates the projection matrix based on a projection frame (which is a rectangle) + * + * @param {Rectangle} destinationFrame - The destination frame. + * @param {Rectangle} sourceFrame - The source frame. + */ + calculateProjection(destinationFrame, sourceFrame, resolution, root) + { + const pm = this.projectionMatrix; + + // I don't think we will need this line.. + // pm.identity(); + + if (!root) + { + pm.a = (1 / destinationFrame.width * 2) * resolution; + pm.d = (1 / destinationFrame.height * 2) * resolution; + + pm.tx = -1 - (sourceFrame.x * pm.a); + pm.ty = -1 - (sourceFrame.y * pm.d); + } + else + { + pm.a = (1 / destinationFrame.width * 2) * resolution; + pm.d = (-1 / destinationFrame.height * 2) * resolution; + + pm.tx = -1 - (sourceFrame.x * pm.a); + pm.ty = 1 - (sourceFrame.y * pm.d); + } + } + + /** + * Sets the transform of the active render target to the given matrix + * + * @param {PIXI.Matrix} matrix - The transformation matrix + */ + setTransform()// matrix) + { + // this._activeRenderTarget.transform = matrix; + } +} diff --git a/packages/core/src/renderTexture/BaseRenderTexture.js b/packages/core/src/renderTexture/BaseRenderTexture.js new file mode 100644 index 0000000..88eb83b --- /dev/null +++ b/packages/core/src/renderTexture/BaseRenderTexture.js @@ -0,0 +1,137 @@ +import BaseTexture from '../textures/BaseTexture'; +import FrameBuffer from '../framebuffer/FrameBuffer'; + +/** + * A BaseRenderTexture is a special texture that allows any PixiJS display object to be rendered to it. + * + * __Hint__: All DisplayObjects (i.e. Sprites) that render to a BaseRenderTexture should be preloaded + * otherwise black rectangles will be drawn instead. + * + * A BaseRenderTexture takes a snapshot of any Display Object given to its render method. The position + * and rotation of the given Display Objects is ignored. For example: + * + * ```js + * let renderer = PIXI.autoDetectRenderer(1024, 1024, { view: canvas, ratio: 1 }); + * let baseRenderTexture = new PIXI.BaseRenderTexture(renderer, 800, 600); + * let sprite = PIXI.Sprite.fromImage("spinObj_01.png"); + * + * sprite.position.x = 800/2; + * sprite.position.y = 600/2; + * sprite.anchor.x = 0.5; + * sprite.anchor.y = 0.5; + * + * baseRenderTexture.render(sprite); + * ``` + * + * The Sprite in this case will be rendered using its local transform. To render this sprite at 0,0 + * you can clear the transform + * + * ```js + * + * sprite.setTransform() + * + * let baseRenderTexture = new PIXI.BaseRenderTexture(100, 100); + * let renderTexture = new PIXI.RenderTexture(baseRenderTexture); + * + * renderer.render(sprite, renderTexture); // Renders to center of RenderTexture + * ``` + * + * @class + * @extends PIXI.BaseTexture + * @memberof PIXI + */ +export default class BaseRenderTexture extends BaseTexture +{ + /** + * @param {object} [options] + * @param {number} [options.width=100] - The width of the base render texture + * @param {number} [options.height=100] - The height of the base render texture + * @param {PIXI.SCALE_MODES} [options.scaleMode] - See {@link PIXI.SCALE_MODES} for possible values + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the texture being generated + */ + constructor(options) + { + if (typeof options === 'number') + { + /* eslint-disable prefer-rest-params */ + // Backward compatibility of signature + const width = arguments[0]; + const height = arguments[1]; + const scaleMode = arguments[2]; + const resolution = arguments[3]; + + options = { width, height, scaleMode, resolution }; + /* eslint-enable prefer-rest-params */ + } + + super(null, options); + + const { width, height } = options || {}; + + // Set defaults + this.mipmap = false; + this.width = Math.ceil(width) || 100; + this.height = Math.ceil(height) || 100; + this.valid = true; + + /** + * A map of renderer IDs to webgl renderTargets + * + * @private + * @member {object} + */ + // this._glRenderTargets = {}; + + /** + * A reference to the canvas render target (we only need one as this can be shared across renderers) + * + * @private + * @member {object} + */ + this._canvasRenderTarget = null; + + this.clearColor = [0, 0, 0, 0]; + + this.frameBuffer = new FrameBuffer(width * this.resolution, height * this.resolution) + .addColorTexture(0, this); + + // TODO - could this be added the systems? + + /** + * The data structure for the stencil masks + * + * @member {PIXI.Graphics[]} + */ + this.stencilMaskStack = []; + + /** + * The data structure for the filters + * + * @member {PIXI.Graphics[]} + */ + this.filterStack = [{}]; + } + + /** + * Resizes the BaseRenderTexture. + * + * @param {number} width - The width to resize to. + * @param {number} height - The height to resize to. + */ + resize(width, height) + { + width = Math.ceil(width); + height = Math.ceil(height); + this.frameBuffer.resize(width * this.resolution, height * this.resolution); + } + + /** + * Destroys this texture + * + */ + destroy() + { + super.destroy(true); + this.renderer = null; + } +} diff --git a/packages/core/src/renderTexture/RenderTexture.js b/packages/core/src/renderTexture/RenderTexture.js new file mode 100644 index 0000000..f1ecf1f --- /dev/null +++ b/packages/core/src/renderTexture/RenderTexture.js @@ -0,0 +1,153 @@ +import BaseRenderTexture from './BaseRenderTexture'; +import Texture from '../textures/Texture'; + +/** + * A RenderTexture is a special texture that allows any PixiJS display object to be rendered to it. + * + * __Hint__: All DisplayObjects (i.e. Sprites) that render to a RenderTexture should be preloaded + * otherwise black rectangles will be drawn instead. + * + * A RenderTexture takes a snapshot of any Display Object given to its render method. For example: + * + * ```js + * let renderer = PIXI.autoDetectRenderer(1024, 1024, { view: canvas, ratio: 1 }); + * let renderTexture = PIXI.RenderTexture.create(800, 600); + * let sprite = PIXI.Sprite.fromImage("spinObj_01.png"); + * + * sprite.position.x = 800/2; + * sprite.position.y = 600/2; + * sprite.anchor.x = 0.5; + * sprite.anchor.y = 0.5; + * + * renderer.render(sprite, renderTexture); + * ``` + * + * The Sprite in this case will be rendered using its local transform. To render this sprite at 0,0 + * you can clear the transform + * + * ```js + * + * sprite.setTransform() + * + * let renderTexture = new PIXI.RenderTexture.create(100, 100); + * + * renderer.render(sprite, renderTexture); // Renders to center of RenderTexture + * ``` + * + * @class + * @extends PIXI.Texture + * @memberof PIXI + */ +export default class RenderTexture extends Texture +{ + /** + * @param {PIXI.BaseRenderTexture} baseRenderTexture - The renderer used for this RenderTexture + * @param {PIXI.Rectangle} [frame] - The rectangle frame of the texture to show + */ + constructor(baseRenderTexture, frame) + { + // support for legacy.. + let _legacyRenderer = null; + + if (!(baseRenderTexture instanceof BaseRenderTexture)) + { + /* eslint-disable prefer-rest-params, no-console */ + const width = arguments[1]; + const height = arguments[2]; + const scaleMode = arguments[3]; + const resolution = arguments[4]; + + // we have an old render texture.. + console.warn(`Please use RenderTexture.create(${width}, ${height}) instead of the ctor directly.`); + _legacyRenderer = arguments[0]; + /* eslint-enable prefer-rest-params, no-console */ + + frame = null; + baseRenderTexture = new BaseRenderTexture({ + width, + height, + scaleMode, + resolution, + }); + } + + /** + * The base texture object that this texture uses + * + * @member {BaseTexture} + */ + super(baseRenderTexture, frame); + + this.legacyRenderer = _legacyRenderer; + + /** + * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. + * + * @member {boolean} + */ + this.valid = true; + + /** + * FilterSystem temporary storage + * @private + * @member {PIXI.Rectangle} + */ + this.filterFrame = null; + + this._updateUvs(); + } + + /** + * Resizes the RenderTexture. + * + * @param {number} width - The width to resize to. + * @param {number} height - The height to resize to. + * @param {boolean} [resizeBaseTexture=true] - Should the baseTexture.width and height values be resized as well? + */ + resize(width, height, resizeBaseTexture = true) + { + width = Math.ceil(width); + height = Math.ceil(height); + + // TODO - could be not required.. + this.valid = (width > 0 && height > 0); + + this._frame.width = this.orig.width = width; + this._frame.height = this.orig.height = height; + + if (resizeBaseTexture) + { + this.baseTexture.resize(width, height); + } + + this._updateUvs(); + } + + /** + * A short hand way of creating a render texture. + * + * @param {object} [options] - Options + * @param {number} [options.width=100] - The width of the render texture + * @param {number} [options.height=100] - The height of the render texture + * @param {number} [options.scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the texture being generated + * @return {PIXI.RenderTexture} The new render texture + */ + static create(options) + { + // fallback, old-style: create(width, height, scaleMode, resolution) + if (typeof options === 'number') + { + /* eslint-disable prefer-rest-params */ + options = { + width: options, + height: arguments[1], + scaleMode: arguments[2], + resolution: arguments[3], + }; + /* eslint-enable prefer-rest-params */ + } + + return new RenderTexture(new BaseRenderTexture(options)); + } +} diff --git a/packages/core/src/renderTexture/RenderTextureSystem.js b/packages/core/src/renderTexture/RenderTextureSystem.js new file mode 100644 index 0000000..8d0ca61 --- /dev/null +++ b/packages/core/src/renderTexture/RenderTextureSystem.js @@ -0,0 +1,115 @@ +import System from '../System'; +import { Rectangle } from '@pixi/math'; + +const tempRect = new Rectangle(); + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class RenderTextureSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.clearColor = renderer._backgroundColorRgba; + + // TODO moe this property somewhere else! + this.defaultMaskStack = []; + this.defaultFilterStack = [{}]; + + // empty render texture? + this.renderTexture = null; + + this.destinationFrame = new Rectangle(); + } + + bind(renderTexture, sourceFrame, destinationFrame) + { + // TODO - do we want this?? + if (this.renderTexture === renderTexture) return; + this.renderTexture = renderTexture; + + const renderer = this.renderer; + + if (renderTexture) + { + const baseTexture = renderTexture.baseTexture; + + if (!destinationFrame) + { + tempRect.width = baseTexture.realWidth; + tempRect.height = baseTexture.realHeight; + + destinationFrame = tempRect; + } + + if (!sourceFrame) + { + sourceFrame = destinationFrame; + } + + this.renderer.framebuffer.bind(baseTexture.frameBuffer, destinationFrame); + + this.renderer.projection.update(destinationFrame, sourceFrame, baseTexture.resolution, false); + this.renderer.stencil.setMaskStack(baseTexture.stencilMaskStack); + } + else + { + // TODO these validation checks happen deeper down.. + // thing they can be avoided.. + if (!destinationFrame) + { + tempRect.width = renderer.width; + tempRect.height = renderer.height; + + destinationFrame = tempRect; + } + + if (!sourceFrame) + { + sourceFrame = destinationFrame; + } + + renderer.framebuffer.bind(null, destinationFrame); + + // TODO store this.. + this.renderer.projection.update(destinationFrame, sourceFrame, this.renderer.resolution, true); + this.renderer.stencil.setMaskStack(this.defaultMaskStack); + } + + this.destinationFrame.copyFrom(destinationFrame); + } + + /** + * Erases the render texture and fills the drawing area with a colour + * + * @param {number} [clearColor] - The colour + * @return {PIXI.Renderer} Returns itself. + */ + clear(clearColor) + { + if (this.renderTexture) + { + clearColor = clearColor || this.renderTexture.baseTexture.clearColor; + } + else + { + clearColor = clearColor || this.clearColor; + } + + this.renderer.framebuffer.clear(clearColor[0], clearColor[1], clearColor[2], clearColor[3]); + } + + resize()// screenWidth, screenHeight) + { + // resize the root only! + this.bind(null); + } +} diff --git a/packages/core/src/renderers/AbstractRenderer.js b/packages/core/src/renderers/AbstractRenderer.js deleted file mode 100644 index 928670f..0000000 --- a/packages/core/src/renderers/AbstractRenderer.js +++ /dev/null @@ -1,353 +0,0 @@ -import { hex2string, hex2rgb } from '@pixi/utils'; -import { Matrix, Rectangle } from '@pixi/math'; -import { RENDERER_TYPE } from '@pixi/constants'; -import { settings } from '@pixi/settings'; -import { Container } from '@pixi/display'; -import RenderTexture from '../textures/RenderTexture'; -import EventEmitter from 'eventemitter3'; - -const tempMatrix = new Matrix(); - -/** - * 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 EventEmitter - * @memberof PIXI - */ -export default class AbstractRenderer extends EventEmitter -{ - // eslint-disable-next-line valid-jsdoc - /** - * @param {string} system - The name of the system this renderer is for. - * @param {object} [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.transparent=false] - If the render view is transparent, default false - * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false - * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) - * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. The - * resolution of the renderer retina would be 2. - * @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 {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when rendering, - * stopping pixel interpolation. - */ - constructor(system, options, arg2, arg3) - { - super(); - - // Support for constructor(system, screenWidth, screenHeight, options) - if (typeof options === 'number') - { - options = Object.assign({ - width: options, - height: arg2 || settings.RENDER_OPTIONS.height, - }, arg3); - } - - // 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 = RENDERER_TYPE.UNKNOWN; - - /** - * Measurements of the screen. (0, 0, screenWidth, screenHeight) - * - * Its safe to use as filterArea or hitArea for 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 1 - */ - this.resolution = options.resolution || settings.RESOLUTION; - - /** - * Whether the render view is transparent - * - * @member {boolean} - */ - this.transparent = options.transparent; - - /** - * Whether css dimensions of canvas view should be resized to screen dimensions automatically - * - * @member {boolean} - */ - this.autoResize = options.autoResize || false; - - /** - * Tracks the blend modes useful for this renderer. - * - * @member {object} - */ - this.blendModes = null; - - /** - * 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; - - /** - * If true PixiJS will Math.floor() x/y values when rendering, stopping pixel interpolation. - * Handy for crisp pixel art and speed on legacy devices. - * - * @member {boolean} - */ - this.roundPixels = options.roundPixels; - - /** - * The background color as a number. - * - * @member {number} - * @private - */ - this._backgroundColor = 0x000000; - - /** - * The background color as an [R, G, B] array. - * - * @member {number[]} - * @private - */ - this._backgroundColorRgba = [0, 0, 0, 0]; - - /** - * The background color as a string. - * - * @member {string} - * @private - */ - this._backgroundColorString = '#000000'; - - this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter - - /** - * This temporary display object used as the parent of the currently being rendered item - * - * @member {PIXI.DisplayObject} - * @private - */ - this._tempDisplayObjectParent = new Container(); - - /** - * The last root object that the renderer tried to render. - * - * @member {PIXI.DisplayObject} - * @private - */ - this._lastObjectRendered = this._tempDisplayObjectParent; - - /** - * Collection of plugins. - * @readonly - * @member {object} - */ - this.plugins = {}; - } - - /** - * Initialize the plugins. - * - * @protected - * @param {object} staticMap - The dictionary of staticly saved plugins. - */ - initPlugins(staticMap) - { - 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() - { - return this.view.width; - } - - /** - * Same as view.height, actual number of pixels in the canvas by vertical - * - * @member {number} - * @readonly - * @default 600 - */ - get height() - { - return this.view.height; - } - - /** - * Resizes the screen and canvas to the specified width and height - * Canvas dimensions are multiplied by resolution - * - * @param {number} screenWidth - the new width of the screen - * @param {number} screenHeight - the new height of the screen - */ - resize(screenWidth, screenHeight) - { - this.screen.width = screenWidth; - this.screen.height = screenHeight; - - this.view.width = screenWidth * this.resolution; - this.view.height = screenHeight * this.resolution; - - if (this.autoResize) - { - this.view.style.width = `${screenWidth}px`; - this.view.style.height = `${screenHeight}px`; - } - } - - /** - * 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 {PIXI.DisplayObject} displayObject - The displayObject the object will be generated from - * @param {number} scaleMode - Should be one of the scaleMode consts - * @param {number} resolution - The resolution / device pixel ratio of the texture being generated - * @param {PIXI.Rectangle} [region] - The region of the displayObject, that shall be rendered, - * if no region is specified, defaults to the local bounds of the displayObject. - * @return {PIXI.Texture} a texture of the graphics object - */ - generateTexture(displayObject, scaleMode, resolution, region) - { - region = region || displayObject.getLocalBounds(); - - // 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(region.width | 0, region.height | 0, scaleMode, resolution); - - tempMatrix.tx = -region.x; - tempMatrix.ty = -region.y; - - this.render(displayObject, renderTexture, false, tempMatrix, true); - - return renderTexture; - } - - /** - * Removes everything from the renderer and optionally removes the Canvas DOM element. - * - * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. - */ - destroy(removeView) - { - 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); - } - - this.plugins = null; - - this.type = RENDERER_TYPE.UNKNOWN; - - this.view = null; - - this.screen = null; - - this.resolution = 0; - - this.transparent = false; - - this.autoResize = false; - - this.blendModes = null; - - this.options = null; - - this.preserveDrawingBuffer = false; - this.clearBeforeRender = false; - - this.roundPixels = false; - - this._backgroundColor = 0; - this._backgroundColorRgba = null; - this._backgroundColorString = null; - - this._tempDisplayObjectParent = null; - this._lastObjectRendered = null; - } - - /** - * The background color to fill if not transparent - * - * @member {number} - */ - get backgroundColor() - { - return this._backgroundColor; - } - - set backgroundColor(value) // eslint-disable-line require-jsdoc - { - this._backgroundColor = value; - this._backgroundColorString = hex2string(value); - hex2rgb(value, this._backgroundColorRgba); - } -} diff --git a/packages/core/src/renderers/Renderer.js b/packages/core/src/renderers/Renderer.js deleted file mode 100644 index a2020b1..0000000 --- a/packages/core/src/renderers/Renderer.js +++ /dev/null @@ -1,329 +0,0 @@ -import AbstractRenderer from './AbstractRenderer'; -import { sayHello } from '@pixi/utils'; -import MaskSystem from './systems/MaskSystem'; -import StencilSystem from './systems/StencilSystem'; -import FilterSystem from './systems/filter/FilterSystem'; -import FramebufferSystem from './systems/FramebufferSystem'; -import RenderTextureSystem from './systems/RenderTextureSystem'; -import TextureSystem from './systems/textures/TextureSystem'; -import ProjectionSystem from './systems/ProjectionSystem'; -import StateSystem from './systems/StateSystem'; -import GeometrySystem from './systems/geometry/GeometrySystem'; -import ShaderSystem from './systems/shader/ShaderSystem'; -import ContextSystem from './systems/ContextSystem'; -import BatchSystem from './systems/BatchSystem'; -import TextureGCSystem from './systems/textures/TextureGCSystem'; -import { RENDERER_TYPE } from '@pixi/constants'; -import UniformGroup from '../shader/UniformGroup'; -import { Matrix } from '@pixi/math'; -import Runner from 'mini-runner'; - -/** - * 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 Render works by automatically managing webGLBatchs. - * 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 :) - * - * @class - * @memberof PIXI - * @extends PIXI.AbstractRenderer - */ -export default class Renderer extends AbstractRenderer -{ - // eslint-disable-next-line valid-jsdoc - /** - * - * @param {object} [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.transparent=false] - If the render view is transparent, default false - * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false - * @param {boolean} [options.antialias=false] - sets antialias. If not available natively then FXAA - * antialiasing is used - * @param {boolean} [options.forceFXAA=false] - forces FXAA antialiasing to be used over native. - * FXAA is faster, but may not always look as great - * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. - * The resolution of the renderer retina would be 2. - * @param {boolean} [options.clearBeforeRender=true] - This sets if the renderer will clear - * the canvas or not before the new render pass. If you wish to set this to false, you *must* set - * preserveDrawingBuffer to `true`. - * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, - * enable this if you need to call toDataUrl on the webgl context. - * @param {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when - * rendering, stopping pixel interpolation. - * @param {number} [options.backgroundColor=0x000000] - The background color of the rendered area - * (shown if not transparent). - * @param {boolean} [options.legacy=false] - If true PixiJS will aim to ensure compatibility - * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. - * @param {string} [options.powerPreference] - Parameter passed to webgl context, set to "high-performance" - * for devices with dual graphics card - */ - constructor(options, arg2, arg3) - { - super('WebGL', options, arg2, arg3); - - /** - * The type of this renderer as a standardised const - * - * @member {number} - * @see PIXI.RENDERER_TYPE - */ - this.type = RENDERER_TYPE.WEBGL; - - // this will be set by the contextSystem (this.context) - this.gl = null; - this.CONTEXT_UID = 0; - this.legacy = !!options.legacy; - - // TODO legacy! - - // runners! - this.runners = { - destroy: new Runner('destroy'), - contextChange: new Runner('contextChange', 1), - reset: new Runner('reset'), - update: new Runner('update'), - postrender: new Runner('postrender'), - prerender: new Runner('prerender'), - resize: new Runner('resize', 2), - }; - - 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(GeometrySystem, 'geometry') - .addSystem(FramebufferSystem, 'framebuffer') - .addSystem(StencilSystem, 'stencil') - .addSystem(ProjectionSystem, 'projection') - .addSystem(TextureGCSystem, 'textureGC') - .addSystem(FilterSystem, 'filter') - .addSystem(RenderTextureSystem, 'renderTexture') - .addSystem(BatchSystem, 'batch'); - - this.initPlugins(Renderer.__plugins); - - /** - * The options passed in to create a new webgl context. - * - * @member {object} - * @private - */ - if (options.context) - { - this.context.initFromContext(options.context); - } - else - { - this.context.initFromOptions({ - alpha: this.transparent, - antialias: options.antialias, - premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', - stencil: true, - 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); - } - - addSystem(_class, name) - { - if (!name) - { - name = _class.name; - } - - // TODO - read name from class.name.. - - /* - if(name.includes('System')) - { - name = name.replace('System', ''); - name = name.charAt(0).toLowerCase() + name.slice(1); - } - */ - - const system = new _class(this); - - if (this[name]) - { - throw new Error(`Whoops! ${name} is already a manger`); - } - - this[name] = system; - - for (const i in this.runners) - { - this.runners[i].add(system); - } - - return this; - - /** - * 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. - */ - } - - /** - * Renders the object to its webGL view - * - * @param {PIXI.DisplayObject} displayObject - the object to be rendered - * @param {PIXI.RenderTexture} renderTexture - The render texture to render to. - * @param {boolean} [clear] - Should the canvas be cleared before the new render - * @param {PIXI.Transform} [transform] - A transform to apply to the render texture before rendering. - * @param {boolean} [skipUpdateTransform] - Should we skip the update transform pass? - */ - render(displayObject, renderTexture, clear, transform, skipUpdateTransform) - { - // can be handy to know! - this.renderingToScreen = !renderTexture; - - this.runners.prerender.run(); - this.emit('prerender'); - - // 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.parent; - - displayObject.parent = this._tempDisplayObjectParent; - displayObject.updateTransform(); - displayObject.parent = 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.run(); - - this.emit('postrender'); - } - - /** - * Resizes the webGL view to the specified width and height. - * - * @param {number} screenWidth - the new width of the screen - * @param {number} screenHeight - the new height of the screen - */ - resize(screenWidth, screenHeight) - { - AbstractRenderer.prototype.resize.call(this, screenWidth, screenHeight); - - this.runners.resize.run(screenWidth, screenHeight); - } - - /** - * Resets the WebGL state so you can render things however you fancy! - * - * @return {PIXI.Renderer} Returns itself. - */ - reset() - { - this.runners.reset.run(); - - return this; - } - - clear() - { - this.framebuffer.bind(); - this.framebuffer.clear(); - } - - /** - * Removes everything from the renderer (event listeners, spritebatch, etc...) - * - * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. - * See: https://github.com/pixijs/pixi.js/issues/2233 - */ - destroy(removeView) - { - this.runners.destroy.run(); - - // call base destroy - super.destroy(removeView); - - // TODO nullify all the managers.. - this.gl = null; - } - - /** - * 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 - * builds and excluding plugins. - * @name PIXI.Renderer#plugins - * @type {object} - * @readonly - * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. - * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. - * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. - * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. - */ - - /** - * Adds a plugin to the renderer. - * - * @method - * @param {string} pluginName - The name of the plugin. - * @param {Function} ctor - The constructor function or class for the plugin. - */ - static registerPlugin(pluginName, ctor) - { - Renderer.__plugins = Renderer.__plugins || {}; - Renderer.__plugins[pluginName] = ctor; - } -} diff --git a/packages/core/src/renderers/State.js b/packages/core/src/renderers/State.js deleted file mode 100644 index fa3f221..0000000 --- a/packages/core/src/renderers/State.js +++ /dev/null @@ -1,163 +0,0 @@ -/* eslint-disable max-len */ - -const BLEND = 0; -const OFFSET = 1; -const CULLING = 2; -const DEPTH_TEST = 3; -const WINDING = 4; - -/** - * This is a webGL state. It is passed The WebGL StateManager. - * Each mesh renderered may require webGL to be in a different state. - * For example you may want different blend mode or to enable polygon offsets - * - * @class - * @memberof PIXI - */ -export default class State -{ - /** - * - */ - constructor() - { - this.data = 0; - - this.blendMode = 0; - this.polygonOffset = 0; - - this.blend = true; - // this.depthTest = true; - } - - /** - * Activates blending of the computed fragment color values - * - * @member {boolean} - */ - get blend() - { - return !!(this.data & (1 << BLEND)); - } - - set blend(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << BLEND)) !== value) - { - this.data ^= (1 << BLEND); - } - } - - /** - * Activates adding an offset to depth values of polygon's fragments - * - * @member {boolean} - * @default false - */ - get offsets() - { - return !!(this.data & (1 << OFFSET)); - } - - set offsets(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << OFFSET)) !== value) - { - this.data ^= (1 << OFFSET); - } - } - - /** - * Activates culling of polygons. - * - * @member {boolean} - * @default false - */ - get culling() - { - return !!(this.data & (1 << CULLING)); - } - - set culling(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << CULLING)) !== value) - { - this.data ^= (1 << CULLING); - } - } - - /** - * Activates depth comparisons and updates to the depth buffer. - * - * @member {boolean} - * @default false - */ - get depthTest() - { - return !!(this.data & (1 << DEPTH_TEST)); - } - - set depthTest(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << DEPTH_TEST)) !== value) - { - this.data ^= (1 << DEPTH_TEST); - } - } - - /** - * Specifies whether or not front or back-facing polygons can be culled. - * @member {boolean} - * @default false - */ - get clockwiseFrontFace() - { - return !!(this.data & (1 << WINDING)); - } - - set clockwiseFrontFace(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << WINDING)) !== value) - { - this.data ^= (1 << WINDING); - } - } - - /** - * The blend mode to be applied when this state is set. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. - * Setting this mode to anything other than NO_BLEND will automatically switch blending on. - * - * @member {boolean} - * @default PIXI.BLEND_MODES.NORMAL - * @see PIXI.BLEND_MODES - */ - get blendMode() - { - return this._blendMode; - } - - set blendMode(value) // eslint-disable-line require-jsdoc - { - // 17 is NO BLEND - this.blend = (value !== 17); - this._blendMode = value; - } - - /** - * The polygon offset. Setting this property to anything other than 0 will automatically enable poygon offset fill. - * - * @member {number} - * @default 0 - */ - get polygonOffset() - { - return this._polygonOffset; - } - - set polygonOffset(value) // eslint-disable-line require-jsdoc - { - this.offsets = !!value; - this._polygonOffset = value; - } -} - diff --git a/packages/core/src/renderers/filters/Filter.js b/packages/core/src/renderers/filters/Filter.js deleted file mode 100644 index 2073b8d..0000000 --- a/packages/core/src/renderers/filters/Filter.js +++ /dev/null @@ -1,140 +0,0 @@ -import Shader from '../../shader/Shader'; -import Program from '../../shader/Program'; -import State from '../State'; -import { settings } from '@pixi/settings'; -// import extractUniformsFromSrc from './extractUniformsFromSrc'; -import defaultVertex from './defaultFilter.vert'; -import defaultFragment from './defaultFilter.frag'; - -// let math = require('../../math'); -/** - * @class - * @memberof PIXI - * @extends PIXI.Shader - */ -export default class Filter extends Shader -{ - /** - * @param {string} [vertexSrc] - The source of the vertex shader. - * @param {string} [fragmentSrc] - The source of the fragment shader. - * @param {object} [uniforms] - Custom uniforms to use to augment the built-in ones. - */ - constructor(vertexSrc, fragmentSrc, uniforms) - { - const program = Program.from(vertexSrc, fragmentSrc); - - super(program, uniforms); - - /** - * The padding of the filter. Some filters require extra space to breath such as a blur. - * Increasing this will add extra width and height to the bounds of the object that the - * filter is applied to. - * - * @member {number} - */ - this.padding = 0; - - /** - * The resolution of the filter. Setting this to be lower will lower the quality but - * increase the performance of the filter. - * - * @member {number} - */ - this.resolution = settings.RESOLUTION; - - /** - * If enabled is true the filter is applied, if false it will not. - * - * @member {boolean} - */ - this.enabled = true; - - /** - * If enabled, PixiJS will fit the filter area into boundaries for better performance. - * Switch it off if it does not work for specific shader. - * - * @member {boolean} - */ - this.autoFit = true; - - /** - * Legacy filters use position and uvs from attributes - * @member {boolean} - * @readonly - */ - this.legacy = !!this.program.attributeData.aTextureCoord; - - /** - * the webGL state the filter requires to render - * @member {PIXI.State} - */ - this.state = new State(); - } - - /** - * Applies the filter - * - * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from - * @param {PIXI.RenderTarget} input - The input render target. - * @param {PIXI.RenderTarget} output - The target to output to. - * @param {boolean} clear - Should the output be cleared before rendering to it - * @param {object} [currentState] - It's current state of filter. - * There are some useful properties in the currentState : - * target, filters, sourceFrame, destinationFrame, renderTarget, resolution - */ - apply(filterManager, input, output, clear, currentState, derp) // eslint-disable-line no-unused-vars - { - // do as you please! - - filterManager.applyFilter(this, input, output, clear, currentState, derp); - - // or just do a regular render.. - } - - /** - * Sets the blendmode of the filter - * - * @member {number} - * @default PIXI.BLEND_MODES.NORMAL - */ - get blendMode() - { - return this.state.blendMode; - } - - set blendMode(value) // eslint-disable-line require-jsdoc - { - this.state.blendMode = value; - } - - /** - * The default vertex shader source - * - * @static - * @constant - */ - static get defaultVertexSrc() - { - return defaultVertex; - } - - /** - * The default fragment shader source - * - * @static - * @constant - */ - static get defaultFragmentSrc() - { - return defaultFragment; - } -} - -/** - * Used for caching shader IDs - * - * @static - * @private - */ -Filter.SOURCE_KEY_MAP = {}; - diff --git a/packages/core/src/renderers/filters/defaultFilter.frag b/packages/core/src/renderers/filters/defaultFilter.frag deleted file mode 100644 index a14c1be..0000000 --- a/packages/core/src/renderers/filters/defaultFilter.frag +++ /dev/null @@ -1,22 +0,0 @@ -varying vec2 vTextureCoord; -varying vec2 vFilterCoord; - -uniform sampler2D uSampler; -uniform sampler2D filterSampler; - -void main(void){ - vec4 masky = texture2D(filterSampler, vFilterCoord); - vec4 sample = texture2D(uSampler, vTextureCoord); - vec4 color; - if(mod(vFilterCoord.x, 1.0) > 0.5) - { - color = vec4(1.0, 0.0, 0.0, 1.0); - } - else - { - color = vec4(0.0, 1.0, 0.0, 1.0); - } - // gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0); - gl_FragColor = mix(sample, masky, 0.5); - gl_FragColor *= sample.a; -} diff --git a/packages/core/src/renderers/filters/defaultFilter.vert b/packages/core/src/renderers/filters/defaultFilter.vert deleted file mode 100644 index e1febca..0000000 --- a/packages/core/src/renderers/filters/defaultFilter.vert +++ /dev/null @@ -1,14 +0,0 @@ -attribute vec2 aVertexPosition; -attribute vec2 aTextureCoord; - -uniform mat3 projectionMatrix; -uniform mat3 filterMatrix; - -varying vec2 vTextureCoord; -varying vec2 vFilterCoord; - -void main(void){ - gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); - vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy; - vTextureCoord = aTextureCoord ; -} diff --git a/packages/core/src/renderers/filters/filterTransforms.js b/packages/core/src/renderers/filters/filterTransforms.js deleted file mode 100644 index 87a3cd6..0000000 --- a/packages/core/src/renderers/filters/filterTransforms.js +++ /dev/null @@ -1,50 +0,0 @@ -import { Matrix } from '@pixi/math'; - -/** - * Calculates the mapped matrix - * @param filterArea {Rectangle} The filter area - * @param sprite {Sprite} the target sprite - * @param outputMatrix {Matrix} @alvin - * @private - */ -// this returns a matrix that will normalise map filter cords in the filter to screen space -export function calculateScreenSpaceMatrix(outputMatrix, filterArea, textureSize) -{ - // TODO unwrap? - const mappedMatrix = outputMatrix.identity(); - - mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); - - mappedMatrix.scale(textureSize.width, textureSize.height); - - return mappedMatrix; -} - -export function calculateNormalizedScreenSpaceMatrix(outputMatrix, filterArea, textureSize) -{ - const mappedMatrix = outputMatrix.identity(); - - mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); - - const translateScaleX = (textureSize.width / filterArea.width); - const translateScaleY = (textureSize.height / filterArea.height); - - mappedMatrix.scale(translateScaleX, translateScaleY); - - return mappedMatrix; -} - -// this will map the filter coord so that a texture can be used based on the transform of a sprite -export function calculateSpriteMatrix(outputMatrix, filterArea, textureSize, sprite) -{ - const orig = sprite._texture.orig; - const mappedMatrix = outputMatrix.set(textureSize.width, 0, 0, textureSize.height, filterArea.x, filterArea.y); - const worldTransform = sprite.worldTransform.copyTo(Matrix.TEMP_MATRIX); - - worldTransform.invert(); - mappedMatrix.prepend(worldTransform); - mappedMatrix.scale(1.0 / orig.width, 1.0 / orig.height); - mappedMatrix.translate(sprite.anchor.x, sprite.anchor.y); - - return mappedMatrix; -} diff --git a/packages/core/src/renderers/filters/spriteMask/SpriteMaskFilter.js b/packages/core/src/renderers/filters/spriteMask/SpriteMaskFilter.js deleted file mode 100644 index d5a91a4..0000000 --- a/packages/core/src/renderers/filters/spriteMask/SpriteMaskFilter.js +++ /dev/null @@ -1,63 +0,0 @@ -import Filter from '../Filter'; -import { Matrix } from '@pixi/math'; -import vertex from './spriteMaskFilter.vert'; -import fragment from './spriteMaskFilter.frag'; -import { default as TextureMatrix } from '../../../textures/TextureMatrix'; - -/** - * The SpriteMaskFilter class - * - * @class - * @extends PIXI.Filter - * @memberof PIXI - */ -export default class SpriteMaskFilter extends Filter -{ - /** - * @param {PIXI.Sprite} sprite - the target sprite - */ - constructor(sprite) - { - const maskMatrix = new Matrix(); - - super(vertex, fragment); - - sprite.renderable = false; - - this.maskSprite = sprite; - this.maskMatrix = maskMatrix; - } - - /** - * Applies the filter - * - * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from - * @param {PIXI.RenderTarget} input - The input render target. - * @param {PIXI.RenderTarget} output - The target to output to. - */ - apply(filterManager, input, output) - { - const maskSprite = this.maskSprite; - const tex = this.maskSprite.texture; - - if (!tex.valid) - { - return; - } - if (!tex.transform) - { - // margin = 0.0, let it bleed a bit, shader code becomes easier - // assuming that atlas textures were made with 1-pixel padding - tex.transform = new TextureMatrix(tex, 0.0); - } - tex.transform.update(); - - this.uniforms.mask = tex; - this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite) - .prepend(tex.transform.mapCoord); - this.uniforms.alpha = maskSprite.worldAlpha; - this.uniforms.maskClamp = tex.transform.uClampFrame; - - filterManager.applyFilter(this, input, output); - } -} diff --git a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.frag b/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.frag deleted file mode 100644 index 0e4aef8..0000000 --- a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.frag +++ /dev/null @@ -1,23 +0,0 @@ -varying vec2 vMaskCoord; -varying vec2 vTextureCoord; - -uniform sampler2D uSampler; -uniform sampler2D mask; -uniform float alpha; -uniform vec4 maskClamp; - -void main(void) -{ - float clip = step(3.5, - step(maskClamp.x, vMaskCoord.x) + - step(maskClamp.y, vMaskCoord.y) + - step(vMaskCoord.x, maskClamp.z) + - step(vMaskCoord.y, maskClamp.w)); - - vec4 original = texture2D(uSampler, vTextureCoord); - vec4 masky = texture2D(mask, vMaskCoord); - - original *= (masky.r * masky.a * alpha * clip); - - gl_FragColor = original; -} diff --git a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.vert b/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.vert deleted file mode 100644 index d23a30c..0000000 --- a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.vert +++ /dev/null @@ -1,16 +0,0 @@ -attribute vec2 aVertexPosition; -attribute vec2 aTextureCoord; - -uniform mat3 projectionMatrix; -uniform mat3 otherMatrix; - -varying vec2 vMaskCoord; -varying vec2 vTextureCoord; - -void main(void) -{ - gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); - - vTextureCoord = aTextureCoord; - vMaskCoord = ( otherMatrix * vec3( aTextureCoord, 1.0) ).xy; -} diff --git a/packages/core/src/renderers/systems/BatchSystem.js b/packages/core/src/renderers/systems/BatchSystem.js deleted file mode 100644 index 70e9518..0000000 --- a/packages/core/src/renderers/systems/BatchSystem.js +++ /dev/null @@ -1,68 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import ObjectRenderer from '../utils/ObjectRenderer'; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ - -export default class BatchSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this manager works for. - */ - constructor(renderer) - { - super(renderer); - - /** - * An empty renderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.emptyRenderer = new ObjectRenderer(renderer); - - /** - * The currently active ObjectRenderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.currentRenderer = this.emptyRenderer; - } - - /** - * Changes the current renderer to the one given in parameter - * - * @param {PIXI.ObjectRenderer} objectRenderer - The object renderer to use. - */ - setObjectRenderer(objectRenderer) - { - if (this.currentRenderer === objectRenderer) - { - return; - } - - this.currentRenderer.stop(); - this.currentRenderer = objectRenderer; - - this.renderer.state.setState(objectRenderer.state); - - this.currentRenderer.start(); - } - - /** - * This should be called if you wish to do some custom rendering - * It will basically render anything that may be batched up such as sprites - * - */ - flush() - { - this.setObjectRenderer(this.emptyRenderer); - } - - reset() - { - this.setObjectRenderer(this.emptyRenderer); - } -} diff --git a/packages/core/src/renderers/systems/ContextSystem.js b/packages/core/src/renderers/systems/ContextSystem.js deleted file mode 100644 index 43bf827..0000000 --- a/packages/core/src/renderers/systems/ContextSystem.js +++ /dev/null @@ -1,186 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { settings } from '@pixi/settings'; - -let CONTEXT_UID = 0; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class ContextSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this.webGLVersion = 1; - - this.handleContextLost = this.handleContextLost.bind(this); - this.handleContextRestored = this.handleContextRestored.bind(this); - - this.extensions = {}; - - renderer.view.addEventListener('webglcontextlost', this.handleContextLost, false); - renderer.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); - } - - get isLost() - { - return (!this.gl || this.gl.isContextLost()); - } - - contextChange(gl) - { - this.gl = gl; - - // restore a context if it was previously lost - if (gl.isContextLost() && gl.getExtension('WEBGL_lose_context')) - { - gl.getExtension('WEBGL_lose_context').restoreContext(); - } - } - - initFromContext(gl) - { - this.gl = gl; - this.validateContext(gl); - this.renderer.gl = gl; - this.renderer.CONTEXT_UID = CONTEXT_UID++; - this.renderer.runners.contextChange.run(gl); - } - - initFromOptions(options) - { - const gl = this.createContext(this.renderer.view, options); - - this.initFromContext(gl); - } - - /** - * Helper class to create a webGL Context - * - * @class - * @memberof PIXI.glCore - * @param canvas {HTMLCanvasElement} the canvas element that we will get the context from - * @param options {Object} An options object that gets passed in to the canvas element containing the context attributes, - * see https://developer.mozilla.org/en/docs/Web/API/HTMLCanvasElement/getContext for the options available - * @return {WebGLRenderingContext} the WebGL context - */ - createContext(canvas, options) - { - let gl; - - if (settings.PREFER_WEBGL_2) - { - gl = canvas.getContext('webgl2', options); - } - - if (gl) - { - this.webGLVersion = 2; - } - else - { - this.webGLVersion = 1; - - gl = canvas.getContext('webgl', options) - || canvas.getContext('experimental-webgl', options); - - if (!gl) - { - // fail, not able to get a context - throw new Error('This browser does not support webGL. Try using the canvas renderer'); - } - } - - this.gl = gl; - - this.getExtensions(); - - return gl; - } - - getExtensions() - { - // time to set up default etensions that pixi uses.. - const gl = this.gl; - const extensions = this.extensions; - - if (this.webGLVersion === 1) - { - extensions.drawBuffers = gl.getExtension('WEBGL_draw_buffers'); - extensions.depthTexture = gl.getExtension('WEBKIT_WEBGL_depth_texture'); - extensions.floatTexture = gl.getExtension('OES_texture_float'); - extensions.loseContext = gl.getExtension('WEBGL_lose_context'); - - extensions.vertexArrayObject = gl.getExtension('OES_vertex_array_object') - || gl.getExtension('MOZ_OES_vertex_array_object') - || gl.getExtension('WEBKIT_OES_vertex_array_object'); - } - - // we don't use any specific WebGL 2 ones yet! - } - - /** - * Handles a lost webgl context - * - * @private - * @param {WebGLContextEvent} event - The context lost event. - */ - handleContextLost(event) - { - event.preventDefault(); - } - - /** - * Handles a restored webgl context - * - * @private - */ - handleContextRestored() - { - this.renderer.runners.contextChange.run(this.gl); - } - - destroy() - { - const view = this.renderer.view; - - // remove listeners - view.removeEventListener('webglcontextlost', this.handleContextLost); - view.removeEventListener('webglcontextrestored', this.handleContextRestored); - - this.gl.useProgram(null); - - if (this.extensions.loseContext) - { - this.extensions.loseContext.loseContext(); - } - } - - postrender() - { - this.gl.flush(); - } - - validateContext(gl) - { - const attributes = gl.getContextAttributes(); - - // this is going to be fairly simple for now.. but at least we have room to grow! - if (!attributes.stencil) - { - /* eslint-disable max-len */ - - /* eslint-disable no-console */ - console.warn('Provided WebGL context does not have a stencil buffer, masks may not render correctly'); - /* eslint-enable no-console */ - - /* eslint-enable max-len */ - } - } -} diff --git a/packages/core/src/renderers/systems/FramebufferSystem.js b/packages/core/src/renderers/systems/FramebufferSystem.js deleted file mode 100644 index df7902b..0000000 --- a/packages/core/src/renderers/systems/FramebufferSystem.js +++ /dev/null @@ -1,246 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { Rectangle } from '@pixi/math'; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class FramebufferSystem extends WebGLSystem -{ - /** - * Sets up the renderer context and necessary buffers. - * - * @private - */ - contextChange() - { - this.gl = this.renderer.gl; - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - this.current = null; - this.viewport = new Rectangle(); - - this.drawBufferExtension = this.renderer.context.extensions.drawBuffers; - } - - bind(framebuffer, frame) - { - const gl = this.gl; - - this.current = framebuffer; - - if (framebuffer) - { - // TODO cacheing layer! - - const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID] || this.initFramebuffer(framebuffer); - - gl.bindFramebuffer(gl.FRAMEBUFFER, fbo.framebuffer); - // makesure all textures are unbound.. - - // now check for updates... - if (fbo.dirtyId !== framebuffer.dirtyId) - { - fbo.dirtyId = framebuffer.dirtyId; - - if (fbo.dirtyFormat !== framebuffer.dirtyFormat) - { - fbo.dirtyFormat = framebuffer.dirtyFormat; - this.updateFramebuffer(framebuffer); - } - else if (fbo.dirtySize !== framebuffer.dirtySize) - { - fbo.dirtySize = framebuffer.dirtySize; - this.resizeFramebuffer(framebuffer); - } - } - - for (let i = 0; i < framebuffer.colorTextures.length; i++) - { - if (framebuffer.colorTextures[i].texturePart) - { - this.renderer.texture.unbind(framebuffer.colorTextures[i].texture); - } - else - { - this.renderer.texture.unbind(framebuffer.colorTextures[i]); - } - } - - if (framebuffer.depthTexture) - { - this.renderer.texture.unbind(framebuffer.depthTexture); - } - - if (frame) - { - this.setViewport(frame.x, frame.y, frame.width, frame.height); - } - else - { - this.setViewport(0, 0, framebuffer.width, framebuffer.height); - } - } - else - { - gl.bindFramebuffer(gl.FRAMEBUFFER, null); - - if (frame) - { - this.setViewport(frame.x, frame.y, frame.width, frame.height); - } - else - { - this.setViewport(0, 0, this.renderer.width, this.renderer.height); - } - } - } - - setViewport(x, y, width, height) - { - const v = this.viewport; - - if (v.width !== width || v.height !== height || v.x !== x || v.y !== y) - { - v.x = x; - v.y = y; - v.width = width; - v.height = height; - - this.gl.viewport(x, y, width, height); - } - } - - get size() - { - if (this.current) - { - // TODO store temp - return { x: 0, y: 0, width: this.current.width, height: this.current.height }; - } - - return { x: 0, y: 0, width: this.renderer.width, height: this.renderer.height }; - } - - clear(r, g, b, a) - { - const gl = this.gl; - - // TODO clear color can be set only one right? - gl.clearColor(r, g, b, a); - gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); - } - - // private functions... - - initFramebuffer(framebuffer) - { - const gl = this.gl; - - // TODO - make this a class? - const fbo = { - framebuffer: gl.createFramebuffer(), - stencil: null, - dirtyId: 0, - dirtyFormat: 0, - dirtySize: 0, - }; - - framebuffer.glFrameBuffers[this.CONTEXT_UID] = fbo; - - return fbo; - } - - resizeFramebuffer(framebuffer) - { - const gl = this.gl; - - if (framebuffer.stencil || framebuffer.depth) - { - gl.bindRenderbuffer(gl.RENDERBUFFER, this.stencil); - gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); - } - } - - updateFramebuffer(framebuffer) - { - const gl = this.gl; - - const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID]; - - // bind the color texture - const colorTextures = framebuffer.colorTextures; - - let count = colorTextures.length; - - if (!this.drawBufferExtension) - { - count = Math.min(count, 1); - } - - const activeTextures = []; - - for (let i = 0; i < count; i++) - { - const texture = framebuffer.colorTextures[i]; - - if (texture.texturePart) - { - this.renderer.texture.bind(texture.texture, 0); - - gl.framebufferTexture2D(gl.FRAMEBUFFER, - gl.COLOR_ATTACHMENT0 + i, - gl.TEXTURE_CUBE_MAP_NEGATIVE_X + texture.side, - texture.texture._glTextures[this.CONTEXT_UID].texture, - 0); - } - else - { - this.renderer.texture.bind(texture, 0); - - gl.framebufferTexture2D(gl.FRAMEBUFFER, - gl.COLOR_ATTACHMENT0 + i, - gl.TEXTURE_2D, - texture._glTextures[this.CONTEXT_UID].texture, - 0); - } - - activeTextures.push(gl.COLOR_ATTACHMENT0 + i); - } - - if (this.drawBufferExtension && activeTextures.length > 1) - { - this.drawBufferExtension.drawBuffersWEBGL(activeTextures); - } - - if (framebuffer.depthTexture) - { - const depthTextureExt = this.renderer.context.extensions.depthTexture; - - if (depthTextureExt) - { - const depthTexture = framebuffer.depthTexture; - - this.renderer.texture.bind(depthTexture, 0); - - gl.framebufferTexture2D(gl.FRAMEBUFFER, - gl.DEPTH_ATTACHMENT, - gl.TEXTURE_2D, - depthTexture._glTextures[this.CONTEXT_UID].texture, - 0); - } - } - - if (framebuffer.stencil || framebuffer.depth) - { - fbo.stencil = gl.createRenderbuffer(); - - gl.bindRenderbuffer(gl.RENDERBUFFER, fbo.stencil); - - // TODO.. this is depth AND stencil? - gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, fbo.stencil); - gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); - // fbo.enableStencil(); - } - } -} diff --git a/packages/core/src/renderers/systems/MaskSystem.js b/packages/core/src/renderers/systems/MaskSystem.js deleted file mode 100644 index d9c4b1a..0000000 --- a/packages/core/src/renderers/systems/MaskSystem.js +++ /dev/null @@ -1,199 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import AlphaMaskFilter from '../filters/spriteMask/SpriteMaskFilter'; - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class MaskSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - // TODO - we don't need both! - this.scissor = false; - this.scissorData = null; - this.scissorRenderTarget = null; - - this.enableScissor = false; - - this.alphaMaskPool = []; - this.alphaMaskIndex = 0; - } - - /** - * Applies the Mask and adds it to the current filter stack. - * - * @param {PIXI.DisplayObject} target - Display Object to push the mask to - * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. - */ - push(target, maskData) - { - // TODO the root check means scissor rect will not - // be used on render textures more info here: - // https://github.com/pixijs/pixi.js/pull/3545 - - if (maskData.texture) - { - this.pushSpriteMask(target, maskData); - } - else if (this.enableScissor - && !this.scissor - && this.renderer._activeRenderTarget.root - && !this.renderer.stencil.stencilMaskStack.length - && maskData.isFastRect()) - { - const matrix = maskData.worldTransform; - - let rot = Math.atan2(matrix.b, matrix.a); - - // use the nearest degree! - rot = Math.round(rot * (180 / Math.PI)); - - if (rot % 90) - { - this.pushStencilMask(maskData); - } - else - { - this.pushScissorMask(target, maskData); - } - } - else - { - this.pushStencilMask(maskData); - } - } - - /** - * Removes the last mask from the mask stack and doesn't return it. - * - * @param {PIXI.DisplayObject} target - Display Object to pop the mask from - * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. - */ - pop(target, maskData) - { - if (maskData.texture) - { - this.popSpriteMask(target, maskData); - } - else if (this.enableScissor && !this.renderer.stencil.stencilMaskStack.length) - { - this.popScissorMask(target, maskData); - } - else - { - this.popStencilMask(target, maskData); - } - } - - /** - * Applies the Mask and adds it to the current filter stack. - * - * @param {PIXI.RenderTarget} target - Display Object to push the sprite mask to - * @param {PIXI.Sprite} maskData - Sprite to be used as the mask - */ - pushSpriteMask(target, maskData) - { - let alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; - - if (!alphaMaskFilter) - { - alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; - } - - alphaMaskFilter[0].resolution = this.renderer.resolution; - alphaMaskFilter[0].maskSprite = maskData; - - // TODO - may cause issues! - target.filterArea = maskData.getBounds(true); - - this.renderer.filterSystem.pushFilter(target, alphaMaskFilter); - - this.alphaMaskIndex++; - } - - /** - * Removes the last filter from the filter stack and doesn't return it. - * - */ - popSpriteMask() - { - this.renderer.filterSystem.popFilter(); - this.alphaMaskIndex--; - } - - /** - * Applies the Mask and adds it to the current filter stack. - * - * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. - */ - pushStencilMask(maskData) - { - this.renderer.batch.flush(); - this.renderer.stencil.pushStencil(maskData); - } - - /** - * Removes the last filter from the filter stack and doesn't return it. - * - */ - popStencilMask() - { - // this.renderer.currentRenderer.stop(); - this.renderer.stencil.popStencil(); - } - - /** - * - * @param {PIXI.DisplayObject} target - Display Object to push the mask to - * @param {PIXI.Graphics} maskData - The masking data. - */ - pushScissorMask(target, maskData) - { - maskData.renderable = true; - - const renderTarget = this.renderer._activeRenderTarget; - - const bounds = maskData.getBounds(); - - bounds.fit(renderTarget.size); - maskData.renderable = false; - - this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); - - const resolution = this.renderer.resolution; - - this.renderer.gl.scissor( - bounds.x * resolution, - (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, - bounds.width * resolution, - bounds.height * resolution - ); - - this.scissorRenderTarget = renderTarget; - this.scissorData = maskData; - this.scissor = true; - } - - /** - * - * - */ - popScissorMask() - { - this.scissorRenderTarget = null; - this.scissorData = null; - this.scissor = false; - - // must be scissor! - const gl = this.renderer.gl; - - gl.disable(gl.SCISSOR_TEST); - } -} diff --git a/packages/core/src/renderers/systems/ProjectionSystem.js b/packages/core/src/renderers/systems/ProjectionSystem.js deleted file mode 100644 index ec5efdb..0000000 --- a/packages/core/src/renderers/systems/ProjectionSystem.js +++ /dev/null @@ -1,73 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { Matrix } from '@pixi/math'; - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ - -export default class ProjectionSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this.projectionMatrix = new Matrix(); - } - - update(destinationFrame, sourceFrame, resolution, root) - { - this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; - this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; - - this.calculateProjection(this.destinationFrame, this.sourceFrame, resolution, root); - - this.renderer.globalUniforms.uniforms.projectionMatrix = this.projectionMatrix; - this.renderer.globalUniforms.update(); - } - - /** - * Updates the projection matrix based on a projection frame (which is a rectangle) - * - * @param {Rectangle} destinationFrame - The destination frame. - * @param {Rectangle} sourceFrame - The source frame. - */ - calculateProjection(destinationFrame, sourceFrame, resolution, root) - { - const pm = this.projectionMatrix; - - // I don't think we will need this line.. - // pm.identity(); - - if (!root) - { - pm.a = (1 / destinationFrame.width * 2) * resolution; - pm.d = (1 / destinationFrame.height * 2) * resolution; - - pm.tx = -1 - (sourceFrame.x * pm.a); - pm.ty = -1 - (sourceFrame.y * pm.d); - } - else - { - pm.a = (1 / destinationFrame.width * 2) * resolution; - pm.d = (-1 / destinationFrame.height * 2) * resolution; - - pm.tx = -1 - (sourceFrame.x * pm.a); - pm.ty = 1 - (sourceFrame.y * pm.d); - } - } - - /** - * Sets the transform of the active render target to the given matrix - * - * @param {PIXI.Matrix} matrix - The transformation matrix - */ - setTransform()// matrix) - { - // this._activeRenderTarget.transform = matrix; - } -} diff --git a/packages/core/src/renderers/systems/RenderTextureSystem.js b/packages/core/src/renderers/systems/RenderTextureSystem.js deleted file mode 100644 index a133ef1..0000000 --- a/packages/core/src/renderers/systems/RenderTextureSystem.js +++ /dev/null @@ -1,115 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { Rectangle } from '@pixi/math'; - -const tempRect = new Rectangle(); - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ - -export default class RenderTextureSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this.clearColor = renderer._backgroundColorRgba; - - // TODO moe this property somewhere else! - this.defaultMaskStack = []; - this.defaultFilterStack = [{}]; - - // empty render texture? - this.renderTexture = null; - - this.destinationFrame = new Rectangle(); - } - - bind(renderTexture, sourceFrame, destinationFrame) - { - // TODO - do we want this?? - if (this.renderTexture === renderTexture) return; - this.renderTexture = renderTexture; - - const renderer = this.renderer; - - if (renderTexture) - { - const baseTexture = renderTexture.baseTexture; - - if (!destinationFrame) - { - tempRect.width = baseTexture.realWidth; - tempRect.height = baseTexture.realHeight; - - destinationFrame = tempRect; - } - - if (!sourceFrame) - { - sourceFrame = destinationFrame; - } - - this.renderer.framebuffer.bind(baseTexture.frameBuffer, destinationFrame); - - this.renderer.projection.update(destinationFrame, sourceFrame, baseTexture.resolution, false); - this.renderer.stencil.setMaskStack(baseTexture.stencilMaskStack); - } - else - { - // TODO these validation checks happen deeper down.. - // thing they can be avoided.. - if (!destinationFrame) - { - tempRect.width = renderer.width; - tempRect.height = renderer.height; - - destinationFrame = tempRect; - } - - if (!sourceFrame) - { - sourceFrame = destinationFrame; - } - - renderer.framebuffer.bind(null, destinationFrame); - - // TODO store this.. - this.renderer.projection.update(destinationFrame, sourceFrame, this.renderer.resolution, true); - this.renderer.stencil.setMaskStack(this.defaultMaskStack); - } - - this.destinationFrame.copyFrom(destinationFrame); - } - - /** - * Erases the render texture and fills the drawing area with a colour - * - * @param {number} [clearColor] - The colour - * @return {PIXI.Renderer} Returns itself. - */ - clear(clearColor) - { - if (this.renderTexture) - { - clearColor = clearColor || this.renderTexture.baseTexture.clearColor; - } - else - { - clearColor = clearColor || this.clearColor; - } - - this.renderer.framebuffer.clear(clearColor[0], clearColor[1], clearColor[2], clearColor[3]); - } - - resize()// screenWidth, screenHeight) - { - // resize the root only! - this.bind(null); - } -} diff --git a/packages/core/src/renderers/systems/StateSystem.js b/packages/core/src/renderers/systems/StateSystem.js deleted file mode 100755 index d7f4caf..0000000 --- a/packages/core/src/renderers/systems/StateSystem.js +++ /dev/null @@ -1,288 +0,0 @@ -import mapWebGLBlendModesToPixi from '../utils/mapWebGLBlendModesToPixi'; -import WebGLSystem from './WebGLSystem'; -import WebGLState from '../State'; - -const BLEND = 0; -const OFFSET = 1; -const CULLING = 2; -const DEPTH_TEST = 3; -const WINDING = 4; - -/** - * A WebGL state machines - * - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class StateSystem extends WebGLSystem -{ - /** - * @param {WebGLRenderingContext} gl - The current WebGL rendering context - */ - constructor(renderer) - { - super(renderer); - - this.gl = null; - - this.maxAttribs = null; - - // check we have vao.. - this.nativeVaoExtension = null; - - this.attribState = null; - - this.stateId = 0; - this.polygonOffset = 0; - this.blendMode = 17; - - this.map = []; - - // map functions for when we set state.. - this.map[BLEND] = this.setBlend; - this.map[OFFSET] = this.setOffset; - this.map[CULLING] = this.setCullFace; - this.map[DEPTH_TEST] = this.setDepthTest; - this.map[WINDING] = this.setFrontFace; - - this.checks = []; - - this.defaultState = new WebGLState(); - this.defaultState.blend = true; - this.defaultState.depth = true; - } - - contextChange(gl) - { - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; - - this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); - - // check we have vao.. - this.nativeVaoExtension = ( - gl.getExtension('OES_vertex_array_object') - || gl.getExtension('MOZ_OES_vertex_array_object') - || gl.getExtension('WEBKIT_OES_vertex_array_object') - ); - - this.attribState = { - tempAttribState: new Array(this.maxAttribs), - attribState: new Array(this.maxAttribs), - }; - - this.blendModes = mapWebGLBlendModesToPixi(gl); - - this.setState(this.defaultState); - - this.reset(); - } - - /** - * Sets the current state - * - * @param {*} state - The state to set. - */ - setState(state) - { - state = state || this.defaultState; - - // TODO maybe to an object check? ( this.state === state )? - if (this.stateId !== state.data) - { - let diff = this.stateId ^ state.data; - let i = 0; - - // order from least to most common - while (diff) - { - if (diff & 1) - { - // state change! - this.map[i].call(this, !!(state.data & (1 << i))); - } - - diff = diff >> 1; - i++; - } - - this.stateId = state.data; - } - - // based on the above settings we check for specific modes.. - // for example if blend is active we check and set the blend modes - // or of polygon offset is active we check the poly depth. - for (let i = 0; i < this.checks.length; i++) - { - this.checks[i](this, state); - } - } - - /** - * Enables or disabled blending. - * - * @param {boolean} value - Turn on or off webgl blending. - */ - setBlend(value) - { - this.updateCheck(StateSystem.checkBlendMode, value); - - this.gl[value ? 'enable' : 'disable'](this.gl.BLEND); - } - - /** - * Enables or disable polygon offset fill - * - * @param {boolean} value - Turn on or off webgl polygon offset testing. - */ - setOffset(value) - { - this.gl[value ? 'enable' : 'disable'](this.gl.POLYGON_OFFSET_FILL); - } - - /** - * Sets whether to enable or disable depth test. - * - * @param {boolean} value - Turn on or off webgl depth testing. - */ - setDepthTest(value) - { - this.gl[value ? 'enable' : 'disable'](this.gl.DEPTH_TEST); - } - - /** - * Sets whether to enable or disable cull face. - * - * @param {boolean} value - Turn on or off webgl cull face. - */ - setCullFace(value) - { - this.gl[value ? 'enable' : 'disable'](this.gl.CULL_FACE); - } - - /** - * Sets the gl front face. - * - * @param {boolean} value - true is clockwise and false is counter-clockwise - */ - setFrontFace(value) - { - this.gl.frontFace(this.gl[value ? 'CW' : 'CCW']); - } - - /** - * Sets the blend mode. - * - * @param {number} value - The blend mode to set to. - */ - setBlendMode(value) - { - if (value === this.blendMode) - { - return; - } - - this.blendMode = value; - this.gl.blendFunc(this.blendModes[value][0], this.blendModes[value][1]); - } - - /** - * Sets the polygon offset. - * - * @param {number} value - the polygon offset - * @param {number} scale - the polygon offset scale - */ - setPolygonOffset(value, scale) - { - this.gl.polygonOffset(value, scale); - } - - /** - * Disables all the vaos in use - * - */ - resetAttributes() - { - for (let i = 0; i < this.attribState.tempAttribState.length; i++) - { - this.attribState.tempAttribState[i] = 0; - } - - for (let i = 0; i < this.attribState.attribState.length; i++) - { - this.attribState.attribState[i] = 0; - } - - // im going to assume one is always active for performance reasons. - for (let i = 1; i < this.maxAttribs; i++) - { - this.gl.disableVertexAttribArray(i); - } - } - - // used - /** - * Resets all the logic and disables the vaos - */ - reset() - { - // unbind any VAO if they exist.. - if (this.nativeVaoExtension) - { - this.nativeVaoExtension.bindVertexArrayOES(null); - } - - // reset all attributes.. - this.resetAttributes(); - - this.gl.pixelStorei(this.gl.UNPACK_FLIP_Y_WEBGL, false); - - this.setBlendMode(0); - - // TODO? - // this.setState(this.defaultState); - } - - /** - * checks to see which updates should be checked based on which settings have been activated - * for example if blend is enabled then we shold check the blend modes each time the state is changed - * or if poygon fill is activated then we need to check if the polygone offset changes. - * The idea is that we only check what we have too. - * - * @param {Function} func the checking function to add or remove - * @param {boolean} value should the check function be added or removed. - */ - updateCheck(func, value) - { - const index = this.checks.indexOf(func); - - if (value && index === -1) - { - this.checks.push(func); - } - else if (!value && index !== -1) - { - this.checks.splice(index, 1); - } - } - - /** - * A private little wrapper function that we call to check the blend mode. - * - * @static - * @private - * @param {PIXI.StateSystem} System the System to perform the state check on - * @param {PIXI.State} state the state that the blendMode will pulled from - */ - static checkBlendMode(system, state) - { - system.setBlendMode(state.blendMode); - } - - // TODO - add polygon offset? -} diff --git a/packages/core/src/renderers/systems/StencilSystem.js b/packages/core/src/renderers/systems/StencilSystem.js deleted file mode 100644 index 5d538cb..0000000 --- a/packages/core/src/renderers/systems/StencilSystem.js +++ /dev/null @@ -1,133 +0,0 @@ -import WebGLSystem from './WebGLSystem'; - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class StencilSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - this.stencilMaskStack = []; - } - - /** - * Changes the mask stack that is used by this System. - * - * @param {PIXI.Graphics[]} stencilMaskStack - The mask stack - */ - setMaskStack(stencilMaskStack) - { - const gl = this.renderer.gl; - - if (stencilMaskStack.length !== this.stencilMaskStack.length) - { - if (stencilMaskStack.length === 0) - { - gl.disable(gl.STENCIL_TEST); - } - else - { - gl.enable(gl.STENCIL_TEST); - } - } - - this.stencilMaskStack = stencilMaskStack; - } - - /** - * Applies the Mask and adds it to the current stencil stack. @alvin - * - * @param {PIXI.Graphics} graphics - The mask - */ - pushStencil(graphics) - { - this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); - - // this.renderer._activeRenderTarget.attachStencilBuffer(); - - const gl = this.renderer.gl; - const prevMaskCount = this.stencilMaskStack.length; - - if (prevMaskCount === 0) - { - gl.enable(gl.STENCIL_TEST); - } - - this.stencilMaskStack.push(graphics); - - // Increment the refference stencil value where the new mask overlaps with the old ones. - gl.colorMask(false, false, false, false); - gl.stencilFunc(gl.EQUAL, prevMaskCount, this._getBitwiseMask()); - gl.stencilOp(gl.KEEP, gl.KEEP, gl.INCR); - this.renderer.plugins.graphics.render(graphics); - - this._useCurrent(); - } - - /** - * Removes the last mask from the stencil stack. @alvin - */ - popStencil() - { - this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); - - const gl = this.renderer.gl; - const graphics = this.stencilMaskStack.pop(); - - if (this.stencilMaskStack.length === 0) - { - // the stack is empty! - gl.disable(gl.STENCIL_TEST); - gl.clear(gl.STENCIL_BUFFER_BIT); - gl.clearStencil(0); - } - else - { - // Decrement the refference stencil value where the popped mask overlaps with the other ones - gl.colorMask(false, false, false, false); - gl.stencilOp(gl.KEEP, gl.KEEP, gl.DECR); - this.renderer.plugins.graphics.render(graphics); - - this._useCurrent(); - } - } - - /** - * Setup renderer to use the current stencil data. - */ - _useCurrent() - { - const gl = this.renderer.gl; - - gl.colorMask(true, true, true, true); - gl.stencilFunc(gl.EQUAL, this.stencilMaskStack.length, this._getBitwiseMask()); - gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP); - } - - /** - * Fill 1s equal to the number of acitve stencil masks. - * - * @return {number} The bitwise mask. - */ - _getBitwiseMask() - { - return (1 << this.stencilMaskStack.length) - 1; - } - - /** - * Destroys the mask stack. - * - */ - destroy() - { - super.destroy(this); - - this.stencilMaskStack = null; - } -} diff --git a/packages/core/src/renderers/systems/WebGLSystem.js b/packages/core/src/renderers/systems/WebGLSystem.js deleted file mode 100644 index d3d5b31..0000000 --- a/packages/core/src/renderers/systems/WebGLSystem.js +++ /dev/null @@ -1,41 +0,0 @@ -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this manager works for. - */ - constructor(renderer) - { - /** - * The renderer this manager works for. - * - * @member {PIXI.Renderer} - */ - this.renderer = renderer; - - this.renderer.runners.contextChange.add(this); - } - - /** - * Generic method called when there is a WebGL context change. - * - */ - contextChange() - { - // do some codes init! - } - - /** - * Generic destroy methods to be overridden by the subclass - * - */ - destroy() - { - this.renderer.runners.contextChange.remove(this); - this.renderer = null; - } -} diff --git a/packages/core/src/renderers/systems/filter/FilterSystem.js b/packages/core/src/renderers/systems/filter/FilterSystem.js deleted file mode 100644 index e615037..0000000 --- a/packages/core/src/renderers/systems/filter/FilterSystem.js +++ /dev/null @@ -1,431 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; - -import RenderTexture from '../../../textures/RenderTexture'; -import Quad from '../../utils/Quad'; -import QuadUv from '../../utils/QuadUv'; -import { Rectangle } from '@pixi/math'; -import * as filterTransforms from '../../filters/filterTransforms'; -import bitTwiddle from 'bit-twiddle'; -import UniformGroup from '../../../shader/UniformGroup'; -import { DRAW_MODES } from '../../../../../constants'; - -// -/** - * @ignore - * @class - */ -class FilterState -{ - /** - * - */ - constructor() - { - this.renderTexture = null; - this.sourceFrame = new Rectangle(); - this.destinationFrame = new Rectangle(); - this.filters = []; - this.target = null; - this.legacy = false; - this.resolution = 1; - } -} - -/** - * @class - * @memberof PIXI.systems - * @extends PIXI.systems.WebGLSystem - */ -export default class FilterSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - /** - * stores a bunch of PO2 textures used for filtering - * @type {Object} - */ - this.texturePool = {}; - - /** - * a pool for storing filter states, save us creating new ones each tick - * @type {Array} - */ - this.statePool = []; - - /** - * A very simple geometry used when drawing a filter effect to the screen - * @type {Quad} - */ - this.quad = new Quad(); - - this.quadUv = new QuadUv(); - - /** - * Temporary rect for maths - * @type {PIXI.Rectangle} - */ - this.tempRect = new Rectangle(); - - this.activeState = {}; - - /** - * this uniform group is attached to filter uniforms when used - * @type {UniformGroup} - */ - this.globalUniforms = new UniformGroup({ - sourceFrame: this.tempRect, - destinationFrame: this.tempRect, - - // legacy variables - filterArea: new Float32Array(4), - filterClamp: new Float32Array(4), - }, true); - } - - /** - * Adds a new filter to the System. - * - * @param {PIXI.DisplayObject} target - The target of the filter to render. - * @param {PIXI.Filter[]} filters - The filters to apply. - */ - push(target, filters) - { - const renderer = this.renderer; - const filterStack = this.renderer.renderTexture.defaultFilterStack; - const state = this.statePool.pop() || new FilterState(); - - let resolution = filters[0].resolution; - let padding = filters[0].padding; - let autoFit = filters[0].autoFit; - let legacy = filters[0].legacy; - - for (let i = 1; i < filters.length; i++) - { - const filter = filters[i]; - - // lets use the lowest resolution.. - resolution = Math.min(resolution, filter.resolution); - // and the largest amount of padding! - padding = Math.max(padding, filter.padding); - // only auto fit if all filters are autofit - autoFit = autoFit || filter.autoFit; - - legacy = legacy || filter.legacy; - } - - filterStack.push(state); - - state.resolution = resolution; - - state.legacy = legacy; - - // round to whole number based on resolution - // TODO move that to the shader too? - state.sourceFrame = target.filterArea || target.getBounds(true); - - state.sourceFrame.pad(padding); - - if (autoFit) - { - state.sourceFrame.fit(this.renderer.renderTexture.destinationFrame); - } - - state.sourceFrame.round(resolution); - - state.renderTexture = this.getPotFilterTexture(state.sourceFrame.width, state.sourceFrame.height, resolution); - state.filters = filters; - - state.destinationFrame.width = state.renderTexture.width; - state.destinationFrame.height = state.renderTexture.height; - - state.renderTexture.filterFrame = state.sourceFrame; - renderer.renderTexture.bind(state.renderTexture, state.sourceFrame);// /, state.destinationFrame); - renderer.renderTexture.clear(); - } - - /** - * Pops off the filter and applies it. - * - */ - pop() - { - const renderer = this.renderer; - const filterStack = renderer.renderTexture.defaultFilterStack; - const state = filterStack.pop(); - const filters = state.filters; - - this.activeState = state; - - const globalUniforms = this.globalUniforms.uniforms; - - globalUniforms.sourceFrame = state.sourceFrame; - globalUniforms.destinationFrame = state.destinationFrame; - globalUniforms.resolution = state.resolution; - - // only update the rect if its legacy.. - if (state.legacy) - { - const filterArea = globalUniforms.filterArea; - const filterClamp = globalUniforms.filterClamp; - - filterArea[0] = state.destinationFrame.width; - filterArea[1] = state.destinationFrame.height; - filterArea[2] = state.sourceFrame.x; - filterArea[3] = state.sourceFrame.y; - - filterClamp[0] = 0.5 / state.resolution / state.destinationFrame.width; - filterClamp[1] = 0.5 / state.resolution / state.destinationFrame.height; - filterClamp[2] = (state.sourceFrame.width - 0.5) / state.resolution / state.destinationFrame.width; - filterClamp[3] = (state.sourceFrame.height - 0.5) / state.resolution / state.destinationFrame.height; - } - - this.globalUniforms.update(); - - const lastState = filterStack[filterStack.length - 1]; - - if (filters.length === 1) - { - filters[0].apply(this, state.renderTexture, lastState.renderTexture, false, state); - - this.returnFilterTexture(state.renderTexture); - } - else - { - let flip = state.renderTexture; - let flop = this.getPotFilterTexture( - flip.width, - flip.height, - state.resolution - ); - - flop.filterFrame = flip.filterFrame; - - let i = 0; - - for (i = 0; i < filters.length - 1; ++i) - { - filters[i].apply(this, flip, flop, true, state); - - const t = flip; - - flip = flop; - flop = t; - } - - filters[i].apply(this, flip, lastState.renderTexture, false, state); - - this.returnFilterTexture(flip); - this.returnFilterTexture(flop); - } - - this.statePool.push(state); - } - - /** - * Draws a filter. - * - * @param {PIXI.Filter} filter - The filter to draw. - * @param {PIXI.RenderTarget} input - The input render target. - * @param {PIXI.RenderTarget} output - The target to output to. - * @param {boolean} clear - Should the output be cleared before rendering to it - */ - applyFilter(filter, input, output, clear) - { - const renderer = this.renderer; - - renderer.renderTexture.bind(output, output ? output.filterFrame : null); - - if (clear) - { - // gl.disable(gl.SCISSOR_TEST); - renderer.renderTexture.clear(); - // gl.enable(gl.SCISSOR_TEST); - } - - // set the uniforms.. - filter.uniforms.uSampler = input; - filter.uniforms.filterGlobals = this.globalUniforms; - - // TODO make it so that the order of this does not matter.. - // because it does at the moment cos of global uniforms. - // they need to get resynced - - renderer.state.setState(filter.state); - renderer.shader.bind(filter); - - if (filter.legacy) - { - this.quadUv.map(input._frame, input.filterFrame); - - renderer.geometry.bind(this.quadUv); - renderer.geometry.draw(DRAW_MODES.TRIANGLES); - } - else - { - renderer.geometry.bind(this.quad); - renderer.geometry.draw(DRAW_MODES.TRIANGLE_STRIP); - } - } - - /** - * Calculates the mapped matrix. - * - * TODO playing around here.. this is temporary - (will end up in the shader) - * this returns a matrix that will normalise map filter cords in the filter to screen space - * - * @param {PIXI.Matrix} outputMatrix - the matrix to output to. - * @return {PIXI.Matrix} The mapped matrix. - */ - calculateScreenSpaceMatrix(outputMatrix) - { - const currentState = this.activeState; - - return filterTransforms.calculateScreenSpaceMatrix( - outputMatrix, - currentState.sourceFrame, - currentState.destinationFrame - ); - } - - /** - * This will map the filter coord so that a texture can be used based on the transform of a sprite - * - * @param {PIXI.Matrix} outputMatrix - The matrix to output to. - * @param {PIXI.Sprite} sprite - The sprite to map to. - * @return {PIXI.Matrix} The mapped matrix. - */ - calculateSpriteMatrix(outputMatrix, sprite) - { - const currentState = this.activeState; - - return filterTransforms.calculateSpriteMatrix( - outputMatrix, - currentState.sourceFrame, - currentState.destinationFrame, - sprite - ); - } - - /** - * Destroys this Filter System. - * - * @param {boolean} [contextLost=false] context was lost, do not free shaders - * - */ - destroy(contextLost = false) - { - if (!contextLost) - { - this.emptyPool(); - } - else - { - this.texturePool = {}; - } - } - - /** - * Gets a Power-of-Two render texture. - * - * TODO move to a seperate class could be on renderer? - * also - could cause issue with multiple contexts? - * - * @private - * @param {WebGLRenderingContext} gl - The webgl rendering context - * @param {number} minWidth - The minimum width of the render target. - * @param {number} minHeight - The minimum height of the render target. - * @param {number} resolution - The resolution of the render target. - * @return {PIXI.RenderTarget} The new render target. - */ - getPotFilterTexture(minWidth, minHeight, resolution) - { - minWidth = bitTwiddle.nextPow2(minWidth); - minHeight = bitTwiddle.nextPow2(minHeight); - resolution = resolution || 1; - - const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); - - if (!this.texturePool[key]) - { - this.texturePool[key] = []; - } - - let renderTexture = this.texturePool[key].pop(); - - if (!renderTexture) - { - // temporary bypass cache.. - // internally - this will cause a texture to be bound.. - renderTexture = RenderTexture.create({ - width: minWidth, - height: minHeight, - resolution, - }); - } - - return renderTexture; - } - - /** - * Gets extra render texture to use inside current filter - * - * @param {number} resolution resolution of the renderTexture - * @returns {PIXI.RenderTexture} - */ - getFilterTexture(resolution) - { - const rt = this.activeState.renderTexture; - - const filterTexture = this.getPotFilterTexture(rt.width, rt.height, resolution || rt.baseTexture.resolution); - - filterTexture.filterFrame = rt.filterFrame; - - return filterTexture; - } - - /** - * Frees a render texture back into the pool. - * - * @param {PIXI.RenderTarget} renderTexture - The renderTarget to free - */ - returnFilterTexture(renderTexture) - { - renderTexture.filterFrame = null; - - const base = renderTexture.baseTexture; - - const minWidth = base.width; - const minHeight = base.height; - - const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); - - this.texturePool[key].push(renderTexture); - } - - /** - * Empties the texture pool. - * - */ - emptyPool() - { - for (const i in this.texturePool) - { - const textures = this.texturePool[i]; - - if (textures) - { - for (let j = 0; j < textures.length; j++) - { - textures[j].destroy(true); - } - } - } - - this.texturePool = {}; - } -} diff --git a/packages/core/src/renderers/systems/geometry/GLBuffer.js b/packages/core/src/renderers/systems/geometry/GLBuffer.js deleted file mode 100644 index 0477e8a..0000000 --- a/packages/core/src/renderers/systems/geometry/GLBuffer.js +++ /dev/null @@ -1,9 +0,0 @@ -export default class GLBuffer -{ - constructor(buffer) - { - this.buffer = buffer; - this.updateID = -1; - this.byteLength = -1; - } -} diff --git a/packages/core/src/renderers/systems/geometry/GeometrySystem.js b/packages/core/src/renderers/systems/geometry/GeometrySystem.js deleted file mode 100644 index 26c8e9e..0000000 --- a/packages/core/src/renderers/systems/geometry/GeometrySystem.js +++ /dev/null @@ -1,388 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; -import GLBuffer from './GLBuffer'; - -const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class GeometrySystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this._activeGeometry = null; - this._activeVao = null; - - this.hasVao = true; - this.hasInstance = true; - } - - /** - * Sets up the renderer context and necessary buffers. - * - * @private - */ - contextChange() - { - const gl = this.gl = this.renderer.gl; - - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - - // webgl2 - if (!gl.createVertexArray) - { - // webgl 1! - let nativeVaoExtension = this.renderer.context.extensions.vertexArrayObject; - - if (this.renderer.legacy) - { - nativeVaoExtension = null; - } - - if (nativeVaoExtension) - { - gl.createVertexArray = () => - nativeVaoExtension.createVertexArrayOES(); - - gl.bindVertexArray = (vao) => - nativeVaoExtension.bindVertexArrayOES(vao); - - gl.deleteVertexArray = (vao) => - nativeVaoExtension.deleteVertexArrayOES(vao); - } - else - { - this.hasVao = false; - gl.createVertexArray = () => - { - // empty - }; - - gl.bindVertexArray = () => - { - // empty - }; - - gl.deleteVertexArray = () => - { - // empty - }; - } - } - - if (!gl.vertexAttribDivisor) - { - const instanceExt = gl.getExtension('ANGLE_instanced_arrays'); - - if (instanceExt) - { - gl.vertexAttribDivisor = (a, b) => - instanceExt.vertexAttribDivisorANGLE(a, b); - - gl.drawElementsInstanced = (a, b, c, d, e) => - instanceExt.drawElementsInstancedANGLE(a, b, c, d, e); - - gl.drawArraysInstanced = (a, b, c, d) => - instanceExt.drawArraysInstancedANGLE(a, b, c, d); - } - else - { - this.hasInstance = false; - } - } - } - - /** - * Binds geometry so that is can be drawn. Creating a Vao if required - * @private - * @param {PIXI.Geometry} geometry instance of geometry to bind - */ - bind(geometry, shader) - { - shader = shader || this.renderer.shader.shader; - - const gl = this.gl; - - // not sure the best way to address this.. - // currently different shaders require different VAOs for the same geometry - // Still mulling over the best way to solve this one.. - // will likely need to modify the shader attribute locations at run time! - let vaos = geometry.glVertexArrayObjects[this.CONTEXT_UID]; - - if (!vaos) - { - geometry.glVertexArrayObjects[this.CONTEXT_UID] = vaos = {}; - } - - const vao = vaos[shader.program.id] || this.initGeometryVao(geometry, shader.program); - - this._activeGeometry = geometry; - - if (this._activeVao !== vao) - { - this._activeVao = vao; - - if (this.hasVao) - { - gl.bindVertexArray(vao); - } - else - { - this.activateVao(geometry, shader.program); - } - } - - // TODO - optimise later! - // don't need to loop through if nothing changed! - // maybe look to add an 'autoupdate' to geometry? - this.updateBuffers(); - } - - reset() - { - this.unbind(); - } - - updateBuffers() - { - const geometry = this._activeGeometry; - const gl = this.gl; - - for (let i = 0; i < geometry.buffers.length; i++) - { - const buffer = geometry.buffers[i]; - - const glBuffer = buffer._glBuffers[this.CONTEXT_UID]; - - if (buffer._updateID !== glBuffer.updateID) - { - glBuffer.updateID = buffer._updateID; - - // TODO can cache this on buffer! maybe added a getter / setter? - const type = buffer.index ? gl.ELEMENT_ARRAY_BUFFER : gl.ARRAY_BUFFER; - const drawType = buffer.static ? gl.STATIC_DRAW : gl.DYNAMIC_DRAW; - - gl.bindBuffer(type, glBuffer.buffer); - - if (glBuffer.byteLength >= buffer.data.byteLength) - { - // offset is always zero for now! - gl.bufferSubData(type, 0, buffer.data); - } - else - { - gl.bufferData(type, buffer.data, drawType); - } - } - } - } - - checkCompatability(geometry, program) - { - // geometry must have at least all the attributes that the shader requires. - const geometryAttributes = geometry.attributes; - const shaderAttributes = program.attributeData; - - for (const j in shaderAttributes) - { - if (!geometryAttributes[j]) - { - throw new Error(`shader and geometry incompatible, geometry missing the "${j}" attribute`); - } - } - } - - /** - * Creates a Vao with the same structure as the geometry and stores it on the geometry. - * @private - * @param {PIXI.Geometry} geometry instance of geometry to to generate Vao for - */ - initGeometryVao(geometry, program) - { - this.checkCompatability(geometry, program); - - const gl = this.gl; - const CONTEXT_UID = this.CONTEXT_UID; - const buffers = geometry.buffers; - const attributes = geometry.attributes; - - const tempStride = {}; - const tempStart = {}; - - for (const j in buffers) - { - tempStride[j] = 0; - tempStart[j] = 0; - } - - for (const j in attributes) - { - if (!attributes[j].size && program.attributeData[j]) - { - attributes[j].size = program.attributeData[j].size; - } - - tempStride[attributes[j].buffer] += attributes[j].size * byteSizeMap[attributes[j].type]; - } - - for (const j in attributes) - { - const attribute = attributes[j]; - const attribSize = attribute.size; - - if (attribute.stride === undefined) - { - if (tempStride[attribute.buffer] === attribSize * byteSizeMap[attribute.type]) - { - attribute.stride = 0; - } - else - { - attribute.stride = tempStride[attribute.buffer]; - } - } - - if (attribute.start === undefined) - { - attribute.start = tempStart[attribute.buffer]; - - tempStart[attribute.buffer] += attribSize * byteSizeMap[attribute.type]; - } - } - - // first update - and create the buffers! - // only create a gl buffer if it actually gets - for (let i = 0; i < buffers.length; i++) - { - const buffer = buffers[i]; - - if (!buffer._glBuffers[CONTEXT_UID]) - { - buffer._glBuffers[CONTEXT_UID] = new GLBuffer(gl.createBuffer()); - } - } - - // TODO - maybe make this a data object? - // lets wait to see if we need to first! - const vao = gl.createVertexArray(); - - gl.bindVertexArray(vao); - - this.activateVao(geometry, program); - - gl.bindVertexArray(null); - - // add it to the cache! - geometry.glVertexArrayObjects[this.CONTEXT_UID][program.id] = vao; - - return vao; - } - - activateVao(geometry, program) - { - const gl = this.gl; - const CONTEXT_UID = this.CONTEXT_UID; - const buffers = geometry.buffers; - const attributes = geometry.attributes; - - if (geometry.indexBuffer) - { - // first update the index buffer if we have one.. - gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, geometry.indexBuffer._glBuffers[CONTEXT_UID].buffer); - } - - let lastBuffer = null; - - // add a new one! - for (const j in attributes) - { - const attribute = attributes[j]; - const buffer = buffers[attribute.buffer]; - const glBuffer = buffer._glBuffers[CONTEXT_UID]; - - if (program.attributeData[j]) - { - if (lastBuffer !== glBuffer) - { - gl.bindBuffer(gl.ARRAY_BUFFER, glBuffer.buffer); - - lastBuffer = glBuffer; - } - - const location = program.attributeData[j].location; - - // TODO introduce state again - // we can optimise this for older devices that have no VAOs - gl.enableVertexAttribArray(location); - - gl.vertexAttribPointer(location, - attribute.size, - attribute.type || gl.FLOAT, - attribute.normalized, - attribute.stride, - attribute.start); - - if (attribute.instance) - { - // TODO calculate instance count based of this... - if (this.hasInstance) - { - gl.vertexAttribDivisor(location, 1); - } - else - { - throw new Error('geometry error, GPU Instancing is not supported on this device'); - } - } - } - } - } - - draw(type, size, start, instanceCount) - { - const gl = this.gl; - const geometry = this._activeGeometry; - - // TODO.. this should not change so maybe cache the function? - - if (geometry.indexBuffer) - { - if (geometry.instanced) - { - /* eslint-disable max-len */ - gl.drawElementsInstanced(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2, instanceCount || 1); - /* eslint-enable max-len */ - } - else - { - gl.drawElements(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2); - } - } - else - if (geometry.instanced) - { - // TODO need a better way to calculate size.. - gl.drawArraysInstanced(type, start, size || geometry.getSize(), instanceCount || 1); - } - else - { - gl.drawArrays(type, start, size || geometry.getSize()); - } - - return this; - } - - unbind() - { - this.gl.bindVertexArray(null); - this._activeVao = null; - this._activeGeometry = null; - } -} diff --git a/packages/core/src/renderers/systems/geometry/setVertexAttribArrays.js b/packages/core/src/renderers/systems/geometry/setVertexAttribArrays.js deleted file mode 100644 index 23b6959..0000000 --- a/packages/core/src/renderers/systems/geometry/setVertexAttribArrays.js +++ /dev/null @@ -1,54 +0,0 @@ -// var GL_MAP = {}; - -/** - * @param gl {WebGLRenderingContext} The current WebGL context - * @param attribs {*} - * @param state {*} - */ -export default function setVertexAttribArrays(gl, attribs, state) -{ - let i; - - if (state) - { - const tempAttribState = state.tempAttribState; - const attribState = state.attribState; - - for (i = 0; i < tempAttribState.length; i++) - { - tempAttribState[i] = false; - } - - // set the new attribs - for (i = 0; i < attribs.length; i++) - { - tempAttribState[attribs[i].attribute.location] = true; - } - - for (i = 0; i < attribState.length; i++) - { - if (attribState[i] !== tempAttribState[i]) - { - attribState[i] = tempAttribState[i]; - - if (state.attribState[i]) - { - gl.enableVertexAttribArray(i); - } - else - { - gl.disableVertexAttribArray(i); - } - } - } - } - else - { - for (i = 0; i < attribs.length; i++) - { - const attrib = attribs[i]; - - gl.enableVertexAttribArray(attrib.attribute.location); - } - } -} diff --git a/packages/core/src/renderers/systems/index.js b/packages/core/src/renderers/systems/index.js deleted file mode 100644 index c843f03..0000000 --- a/packages/core/src/renderers/systems/index.js +++ /dev/null @@ -1,6 +0,0 @@ -/** - * Systems are individual components to the Renderer pipeline. - * @namespace PIXI.systems - */ -export { default as WebGLSystem } from './WebGLSystem'; -export { default as FilterSystem } from './filter/FilterSystem'; diff --git a/packages/core/src/renderers/systems/shader/GLProgram.js b/packages/core/src/renderers/systems/shader/GLProgram.js deleted file mode 100644 index 92980ca..0000000 --- a/packages/core/src/renderers/systems/shader/GLProgram.js +++ /dev/null @@ -1,43 +0,0 @@ -/** - * Helper class to create a webGL Program - * - * @class - * @private - */ -export default class GLProgram -{ - constructor(program, uniformData) - { - /** - * The shader program - * - * @member {WebGLProgram} - */ - this.program = program; - - /** - * holds the uniform data which contains uniform locations - * and current uniform values used for caching and preventing undeed GPU commands - * @type {Object} - */ - this.uniformData = uniformData; - - /** - * uniformGroups holds the various upload functions for the shader. Each uniform group - * and program have a unique upload function generated. - * @type {Object} - */ - this.uniformGroups = {}; - } - - /** - * Destroys this program - * TODO - */ - destroy() - { - this.uniformData = null; - this.uniformGroups = null; - this.program = null; - } -} diff --git a/packages/core/src/renderers/systems/shader/ShaderSystem.js b/packages/core/src/renderers/systems/shader/ShaderSystem.js deleted file mode 100644 index 6cf57b6..0000000 --- a/packages/core/src/renderers/systems/shader/ShaderSystem.js +++ /dev/null @@ -1,170 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; -import GLProgram from './GLProgram'; -import generateUniformsSync from '../../../shader/generateUniformsSync'; -import defaultValue from '../../../shader/defaultValue'; -import compileProgram from './shader/compileProgram'; - -let UID = 0; - -/** - * Helper class to create a webGL Texture - * - * @class - * @memberof PIXI - */ -export default class ShaderSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - A reference to the current renderer - */ - constructor(renderer) - { - super(renderer); - - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = null; - - this.shader = null; - this.program = null; - - this.id = UID++; - } - - contextChange(gl) - { - this.gl = gl; - } - - /** - * Changes the current shader to the one given in parameter - * - * @param {PIXI.Shader} shader - the new shader - * @param {boolean} dontSync - false if the shader should automatically sync its uniforms. - * @returns {PIXI.glCore.glProgram} the glProgram that belongs to the shader. - */ - bind(shader, dontSync) - { - shader.uniforms.globals = this.renderer.globalUniforms; - - const program = shader.program; - const glProgram = program.glPrograms[this.renderer.CONTEXT_UID] || this.generateShader(shader); - - this.shader = shader; - - // TODO - some current pixi plugins bypass this.. so it not safe to use yet.. - if (this.program !== program) - { - this.program = program; - this.gl.useProgram(glProgram.program); - } - - if (!dontSync) - { - this.syncUniformGroup(shader.uniformGroup); - } - - return glProgram; - } - - /** - * Uploads the uniforms values to the currently bound shader. - * - * @param {object} uniforms - the uniforms valiues that be applied to the current shader - */ - setUniforms(uniforms) - { - const shader = this.shader.program; - const glProgram = shader.glPrograms[this.renderer.CONTEXT_UID]; - - shader.syncUniforms(glProgram.uniformData, uniforms, this.renderer); - } - - syncUniformGroup(group) - { - const glProgram = this.getglProgram(); - - if (!group.static || group.dirtyId !== glProgram.uniformGroups[group.id]) - { - glProgram.uniformGroups[group.id] = group.dirtyId; - const syncFunc = group.syncUniforms[this.shader.program.id] || this.createSyncGroups(group); - - syncFunc(glProgram.uniformData, group.uniforms, this.renderer); - } - } - - createSyncGroups(group) - { - group.syncUniforms[this.shader.program.id] = generateUniformsSync(group, this.shader.program.uniformData); - - return group.syncUniforms[this.shader.program.id]; - } - - /** - * Returns the underlying GLShade rof the currently bound shader. - * This can be handy for when you to have a little more control over the setting of your uniforms. - * - * @return {PIXI.glCore.Shader} the glProgram for the currently bound Shader for this context - */ - getglProgram() - { - if (this.shader) - { - return this.shader.program.glPrograms[this.renderer.CONTEXT_UID]; - } - - return null; - } - - /** - * Generates a glProgram verion of the Shader provided. - * - * @private - * @param {PIXI.Shader} shader the shader that the glProgram will be based on. - * @return {PIXI.glCore.glProgram} A shiney new glProgram - */ - generateShader(shader) - { - const gl = this.gl; - - const program = shader.program; - - const attribMap = {}; - - for (const i in program.attributeData) - { - attribMap[i] = program.attributeData[i].location; - } - - const shaderProgram = compileProgram(gl, program.vertexSrc, program.fragmentSrc, attribMap); - const uniformData = {}; - - for (const i in program.uniformData) - { - const data = program.uniformData[i]; - - uniformData[i] = { - location: gl.getUniformLocation(shaderProgram, i), - value: defaultValue(data.type, data.size), - }; - } - - const glProgram = new GLProgram(shaderProgram, uniformData); - - program.glPrograms[this.renderer.CONTEXT_UID] = glProgram; - - return glProgram; - } - - /** - * Destroys this System and removes all its textures - */ - destroy() - { - // TODO implement destroy method for ShaderSystem - this.destroyed = true; - } -} diff --git a/packages/core/src/renderers/systems/shader/shader/compileProgram.js b/packages/core/src/renderers/systems/shader/shader/compileProgram.js deleted file mode 100644 index 8c6ccb9..0000000 --- a/packages/core/src/renderers/systems/shader/shader/compileProgram.js +++ /dev/null @@ -1,78 +0,0 @@ -/** - * @method compileProgram - * @memberof PIXI.glCore.shader - * @param gl {WebGLRenderingContext} The current WebGL context {WebGLProgram} - * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. - * @param fragmentSrc {string|string[]} The fragment shader source as an array of strings. - * @param attributeLocations {Object} An attribute location map that lets you manually set the attribute locations - * @return {WebGLProgram} the shader program - */ -export default function compileProgram(gl, vertexSrc, fragmentSrc, attributeLocations) -{ - const glVertShader = compileShader(gl, gl.VERTEX_SHADER, vertexSrc); - const glFragShader = compileShader(gl, gl.FRAGMENT_SHADER, fragmentSrc); - - let program = gl.createProgram(); - - gl.attachShader(program, glVertShader); - gl.attachShader(program, glFragShader); - - // optionally, set the attributes manually for the program rather than letting WebGL decide.. - if (attributeLocations) - { - for (const i in attributeLocations) - { - gl.bindAttribLocation(program, attributeLocations[i], i); - } - } - - gl.linkProgram(program); - - // if linking fails, then log and cleanup - if (!gl.getProgramParameter(program, gl.LINK_STATUS)) - { - console.error('Pixi.js Error: Could not initialize shader.'); - console.error('gl.VALIDATE_STATUS', gl.getProgramParameter(program, gl.VALIDATE_STATUS)); - console.error('gl.getError()', gl.getError()); - - // if there is a program info log, log it - if (gl.getProgramInfoLog(program) !== '') - { - console.warn('Pixi.js Warning: gl.getProgramInfoLog()', gl.getProgramInfoLog(program)); - } - - gl.deleteProgram(program); - program = null; - } - - // clean up some shaders - gl.deleteShader(glVertShader); - gl.deleteShader(glFragShader); - - return program; -} - -/** - * @private - * @param gl {WebGLRenderingContext} The current WebGL context {WebGLProgram} - * @param type {Number} the type, can be either VERTEX_SHADER or FRAGMENT_SHADER - * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. - * @return {WebGLShader} the shader - */ -function compileShader(gl, type, src) -{ - const shader = gl.createShader(type); - - gl.shaderSource(shader, src); - gl.compileShader(shader); - - if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) - { - console.warn(src); - console.error(gl.getShaderInfoLog(shader)); - - return null; - } - - return shader; -} diff --git a/packages/core/src/renderers/systems/shader/shader/defaultValue.js b/packages/core/src/renderers/systems/shader/shader/defaultValue.js deleted file mode 100644 index 97290bd..0000000 --- a/packages/core/src/renderers/systems/shader/shader/defaultValue.js +++ /dev/null @@ -1,79 +0,0 @@ -/** - * @method defaultValue - * @memberof PIXI.glCore.shader - * @param type {String} Type of value - * @param size {Number} - */ -export default function defaultValue(type, size) -{ - switch (type) - { - case 'float': - return 0; - - case 'vec2': - return new Float32Array(2 * size); - - case 'vec3': - return new Float32Array(3 * size); - - case 'vec4': - return new Float32Array(4 * size); - - case 'int': - case 'sampler2D': - case 'sampler2DArray': - return 0; - - case 'ivec2': - return new Int32Array(2 * size); - - case 'ivec3': - return new Int32Array(3 * size); - - case 'ivec4': - return new Int32Array(4 * size); - - case 'bool': - return false; - - case 'bvec2': - - return booleanArray(2 * size); - - case 'bvec3': - return booleanArray(3 * size); - - case 'bvec4': - return booleanArray(4 * size); - - case 'mat2': - return new Float32Array([1, 0, - 0, 1]); - - case 'mat3': - return new Float32Array([1, 0, 0, - 0, 1, 0, - 0, 0, 1]); - - case 'mat4': - return new Float32Array([1, 0, 0, 0, - 0, 1, 0, 0, - 0, 0, 1, 0, - 0, 0, 0, 1]); - } - - return null; -} - -function booleanArray(size) -{ - const array = new Array(size); - - for (let i = 0; i < array.length; i++) - { - array[i] = false; - } - - return array; -} diff --git a/packages/core/src/renderers/systems/shader/shader/index.js b/packages/core/src/renderers/systems/shader/shader/index.js deleted file mode 100644 index ba7a317..0000000 --- a/packages/core/src/renderers/systems/shader/shader/index.js +++ /dev/null @@ -1,8 +0,0 @@ -/** - * @namespace PIXI.glCore.shader - */ -export { default as compileProgram } from './compileProgram'; -export { default as defaultValue } from './defaultValue'; -export { default as setPrecision } from './setPrecision'; -export { default as mapSize } from './mapSize'; -export { default as mapType } from './mapType'; diff --git a/packages/core/src/renderers/systems/shader/shader/mapSize.js b/packages/core/src/renderers/systems/shader/shader/mapSize.js deleted file mode 100644 index b681554..0000000 --- a/packages/core/src/renderers/systems/shader/shader/mapSize.js +++ /dev/null @@ -1,33 +0,0 @@ -const GLSL_TO_SIZE = { - float: 1, - vec2: 2, - vec3: 3, - vec4: 4, - - int: 1, - ivec2: 2, - ivec3: 3, - ivec4: 4, - - bool: 1, - bvec2: 2, - bvec3: 3, - bvec4: 4, - - mat2: 4, - mat3: 9, - mat4: 16, - - sampler2D: 1, -}; - -/** - * @method mapSize - * @memberof PIXI.glCore.shader - * @param type {String} - * @return {Number} - */ -export default function mapSize(type) -{ - return GLSL_TO_SIZE[type]; -} diff --git a/packages/core/src/renderers/systems/shader/shader/mapType.js b/packages/core/src/renderers/systems/shader/shader/mapType.js deleted file mode 100644 index d288b7c..0000000 --- a/packages/core/src/renderers/systems/shader/shader/mapType.js +++ /dev/null @@ -1,45 +0,0 @@ -let GL_TABLE = null; - -const GL_TO_GLSL_TYPES = { - FLOAT: 'float', - FLOAT_VEC2: 'vec2', - FLOAT_VEC3: 'vec3', - FLOAT_VEC4: 'vec4', - - INT: 'int', - INT_VEC2: 'ivec2', - INT_VEC3: 'ivec3', - INT_VEC4: 'ivec4', - - BOOL: 'bool', - BOOL_VEC2: 'bvec2', - BOOL_VEC3: 'bvec3', - BOOL_VEC4: 'bvec4', - - FLOAT_MAT2: 'mat2', - FLOAT_MAT3: 'mat3', - FLOAT_MAT4: 'mat4', - - SAMPLER_2D: 'sampler2D', - SAMPLER_CUBE: 'samplerCube', - SAMPLER_2D_ARRAY: 'sampler2DArray', -}; - -export default function mapType(gl, type) -{ - if (!GL_TABLE) - { - const typeNames = Object.keys(GL_TO_GLSL_TYPES); - - GL_TABLE = {}; - - for (let i = 0; i < typeNames.length; ++i) - { - const tn = typeNames[i]; - - GL_TABLE[gl[tn]] = GL_TO_GLSL_TYPES[tn]; - } - } - - return GL_TABLE[type]; -} diff --git a/packages/core/src/renderers/systems/shader/shader/setPrecision.js b/packages/core/src/renderers/systems/shader/shader/setPrecision.js deleted file mode 100644 index e2c1f34..0000000 --- a/packages/core/src/renderers/systems/shader/shader/setPrecision.js +++ /dev/null @@ -1,18 +0,0 @@ -/** - * Sets the float precision on the shader. If the precision is already present this function will do nothing - * @param {string} src the shader source - * @param {string} precision The float precision of the shader. Options are 'lowp', 'mediump' or 'highp'. - * - * @return {string} modified shader source - */ -export default function setPrecision(src, precision) -{ - src = src.trim(); - - if (src.substring(0, 9) !== 'precision' && src.substring(0, 1) !== '#') - { - return `precision ${precision} float;\n${src}`; - } - - return src; -} diff --git a/packages/core/src/renderers/systems/textures/GLTexture.js b/packages/core/src/renderers/systems/textures/GLTexture.js deleted file mode 100644 index c3fb8dc..0000000 --- a/packages/core/src/renderers/systems/textures/GLTexture.js +++ /dev/null @@ -1,33 +0,0 @@ -export default class GLTexture -{ - constructor(texture) - { - /** - * The WebGL texture - * - * @member {WebGLTexture} - */ - this.texture = texture; - - this.width = -1; - this.height = -1; - - /** - * Texture contents dirty flag - * @member {number} - */ - this.dirtyId = -1; - - /** - * Texture style dirty flag - * @type {number} - */ - this.dirtyStyleId = -1; - - /** - * Whether mip levels has to be generated - * @type {boolean} - */ - this.mipmap = false; - } -} diff --git a/packages/core/src/renderers/systems/textures/TextureGCSystem.js b/packages/core/src/renderers/systems/textures/TextureGCSystem.js deleted file mode 100644 index b089938..0000000 --- a/packages/core/src/renderers/systems/textures/TextureGCSystem.js +++ /dev/null @@ -1,110 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; -import { GC_MODES } from '@pixi/constants'; -import { settings } from '@pixi/settings'; - -/** - * TextureGarbageCollector. This class manages the GPU and ensures that it does not get clogged - * up with textures that are no longer being used. - * - * @class - * @memberof PIXI - */ -export default class TextureGCSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this.count = 0; - this.checkCount = 0; - this.maxIdle = settings.GC_MAX_IDLE; - this.checkCountMax = settings.GC_MAX_CHECK_COUNT; - this.mode = settings.GC_MODE; - } - - /** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ - postrender() - { - this.count++; - - if (this.mode === GC_MODES.MANUAL) - { - return; - } - - this.checkCount++; - - if (this.checkCount > this.checkCountMax) - { - this.checkCount = 0; - - this.run(); - } - } - - /** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ - run() - { - const tm = this.renderer.texture; - const managedTextures = tm.managedTextures; - let wasRemoved = false; - - for (let i = 0; i < managedTextures.length; i++) - { - const texture = managedTextures[i]; - - // only supports non generated textures at the moment! - if (!texture.frameBuffer && this.count - texture.touched > this.maxIdle) - { - tm.destroyTexture(texture, true); - managedTextures[i] = null; - wasRemoved = true; - } - } - - if (wasRemoved) - { - let j = 0; - - for (let i = 0; i < managedTextures.length; i++) - { - if (managedTextures[i] !== null) - { - managedTextures[j++] = managedTextures[i]; - } - } - - managedTextures.length = j; - } - } - - /** - * Removes all the textures within the specified displayObject and its children from the GPU - * - * @param {PIXI.DisplayObject} displayObject - the displayObject to remove the textures from. - */ - unload(displayObject) - { - const tm = this.renderer.textureSystem; - - // only destroy non generated textures - if (displayObject._texture && displayObject._texture._glRenderTargets) - { - tm.destroyTexture(displayObject._texture); - } - - for (let i = displayObject.children.length - 1; i >= 0; i--) - { - this.unload(displayObject.children[i]); - } - } -} diff --git a/packages/core/src/renderers/systems/textures/TextureSystem.js b/packages/core/src/renderers/systems/textures/TextureSystem.js deleted file mode 100644 index ccc5de9..0000000 --- a/packages/core/src/renderers/systems/textures/TextureSystem.js +++ /dev/null @@ -1,281 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; -import GLTexture from './GLTexture'; -import { removeItems } from '@pixi/utils'; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class TextureSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - // TODO set to max textures... - this.boundTextures = [ - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - ]; - - this.currentLocation = -1; - - this.managedTextures = []; - } - - /** - * Sets up the renderer context and necessary buffers. - * - * @private - */ - contextChange() - { - const gl = this.gl = this.renderer.gl; - - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - - // TODO move this.. to a nice make empty textures class.. - this.emptyTextures = {}; - - const emptyTexture2D = new GLTexture(gl.createTexture()); - - gl.bindTexture(gl.TEXTURE_2D, emptyTexture2D.texture); - gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array(4)); - - this.emptyTextures[gl.TEXTURE_2D] = emptyTexture2D; - this.emptyTextures[gl.TEXTURE_CUBE_MAP] = new GLTexture(gl.createTexture()); - - gl.bindTexture(gl.TEXTURE_CUBE_MAP, this.emptyTextures[gl.TEXTURE_CUBE_MAP].texture); - - let i; - - for (i = 0; i < 6; i++) - { - gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, null); - } - - gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_MAG_FILTER, gl.LINEAR); - gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_MIN_FILTER, gl.LINEAR); - - for (i = 0; i < this.boundTextures.length; i++) - { - this.bind(null, i); - } - } - - bind(texture, location) - { - const gl = this.gl; - - location = location || 0; - - if (this.currentLocation !== location) - { - this.currentLocation = location; - gl.activeTexture(gl.TEXTURE0 + location); - } - - if (texture) - { - texture = texture.baseTexture || texture; - - if (texture.valid) - { - texture.touched = this.renderer.textureGC.count; - - const glTexture = texture._glTextures[this.CONTEXT_UID] || this.initTexture(texture); - - gl.bindTexture(texture.target, glTexture.texture); - - if (glTexture.dirtyId !== texture.dirtyId) - { - this.updateTexture(texture); - } - - this.boundTextures[location] = texture; - } - } - else - { - gl.bindTexture(gl.TEXTURE_2D, this.emptyTextures[gl.TEXTURE_2D].texture); - this.boundTextures[location] = null; - } - } - - unbind(texture) - { - const gl = this.gl; - - for (let i = 0; i < this.boundTextures.length; i++) - { - if (this.boundTextures[i] === texture) - { - if (this.currentLocation !== i) - { - gl.activeTexture(gl.TEXTURE0 + i); - this.currentLocation = i; - } - - gl.bindTexture(gl.TEXTURE_2D, this.emptyTextures[texture.target].texture); - this.boundTextures[i] = null; - } - } - } - - initTexture(texture) - { - const glTexture = new GLTexture(this.gl.createTexture()); - - // guarentee an update.. - glTexture.dirtyId = -1; - - texture._glTextures[this.CONTEXT_UID] = glTexture; - - this.managedTextures.push(texture); - texture.on('dispose', this.destroyTexture, this); - - return glTexture; - } - - updateTexture(texture) - { - const glTexture = texture._glTextures[this.CONTEXT_UID]; - const renderer = this.renderer; - - if (texture.resource && texture.resource.upload(renderer, texture, glTexture)) - { - // texture is uploaded, dont do anything! - } - else - { - // default, renderTexture-like logic - const width = texture.realWidth; - const height = texture.realHeight; - const gl = renderer.gl; - - if (glTexture.width !== width - || glTexture.height !== height - || glTexture.dirtyId < 0) - { - glTexture.width = width; - glTexture.height = height; - - gl.texImage2D(texture.target, 0, - texture.format, - width, - height, - 0, - texture.format, - texture.type, - null); - } - } - - // lets only update what changes.. - if (texture.dirtyStyleId !== glTexture.dirtyStyleId) - { - this.updateTextureStyle(texture); - } - glTexture.dirtyId = texture.dirtyId; - } - - /** - * Deletes the texture from WebGL - * - * @param {PIXI.BaseTexture|PIXI.Texture} texture - the texture to destroy - * @param {boolean} [skipRemove=false] - Whether to skip removing the texture from the TextureManager. - */ - destroyTexture(texture, skipRemove) - { - const gl = this.gl; - - texture = texture.baseTexture || texture; - - if (texture._glTextures[this.renderer.CONTEXT_UID]) - { - this.unbind(texture); - - gl.deleteTexture(texture._glTextures[this.renderer.CONTEXT_UID].texture); - texture.off('dispose', this.destroyTexture, this); - - delete texture._glTextures[this.renderer.CONTEXT_UID]; - - if (!skipRemove) - { - const i = this.managedTextures.indexOf(texture); - - if (i !== -1) - { - removeItems(this.managedTextures, i, 1); - } - } - } - } - - updateTextureStyle(texture) - { - const glTexture = texture._glTextures[this.CONTEXT_UID]; - - glTexture.mipmap = texture.mipmap && texture.isPowerOfTwo; - if (!glTexture) - { - return; - } - - if (texture.resource && texture.resource.style(this.renderer, texture, glTexture)) - { - // style is set, dont do anything! - } - else - { - this.setStyle(texture, glTexture); - } - - glTexture.dirtyStyleId = texture.dirtyStyleId; - } - - setStyle(texture, glTexture) - { - const gl = this.gl; - - if (glTexture.mipmap) - { - gl.generateMipmap(texture.target); - } - - gl.texParameteri(texture.target, gl.TEXTURE_WRAP_S, texture.wrapMode); - gl.texParameteri(texture.target, gl.TEXTURE_WRAP_T, texture.wrapMode); - - if (glTexture.mipmap) - { - /* eslint-disable max-len */ - gl.texParameteri(texture.target, gl.TEXTURE_MIN_FILTER, texture.scaleMode ? gl.LINEAR_MIPMAP_LINEAR : gl.NEAREST_MIPMAP_NEAREST); - /* eslint-disable max-len */ - } - else - { - gl.texParameteri(texture.target, gl.TEXTURE_MIN_FILTER, texture.scaleMode ? gl.LINEAR : gl.NEAREST); - } - - gl.texParameteri(texture.target, gl.TEXTURE_MAG_FILTER, texture.scaleMode ? gl.LINEAR : gl.NEAREST); - } -} diff --git a/packages/core/src/renderers/utils/ObjectRenderer.js b/packages/core/src/renderers/utils/ObjectRenderer.js deleted file mode 100644 index f67a273..0000000 --- a/packages/core/src/renderers/utils/ObjectRenderer.js +++ /dev/null @@ -1,48 +0,0 @@ -import WebGLSystem from '../systems/WebGLSystem'; - -/** - * Base for a common object renderer that can be used as a system renderer plugin. - * - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class ObjectRenderer extends WebGLSystem -{ - /** - * Starts the renderer and sets the shader - * - */ - start() - { - // set the shader.. - } - - /** - * Stops the renderer - * - */ - stop() - { - this.flush(); - } - - /** - * Stub method for rendering content and emptying the current batch. - * - */ - flush() - { - // flush! - } - - /** - * Renders an object - * - * @param {PIXI.DisplayObject} object - The object to render. - */ - render(object) // eslint-disable-line no-unused-vars - { - // render the object - } -} diff --git a/packages/core/src/renderers/utils/Quad.js b/packages/core/src/renderers/utils/Quad.js deleted file mode 100644 index 5c1517b..0000000 --- a/packages/core/src/renderers/utils/Quad.js +++ /dev/null @@ -1,26 +0,0 @@ -import Geometry from '../../geometry/Geometry'; - -/** - * Helper class to create a quad - * - * @class - * @memberof PIXI - */ -export default class Quad extends Geometry -{ - /** - * just a quad - */ - constructor() - { - super(); - - this.addAttribute('aVertexPosition', [ - 0, 0, - 1, 0, - 1, 1, - 0, 1, - ]) - .addIndex([0, 1, 3, 2]); - } -} diff --git a/packages/core/src/renderers/utils/QuadUv.js b/packages/core/src/renderers/utils/QuadUv.js deleted file mode 100644 index 97bda89..0000000 --- a/packages/core/src/renderers/utils/QuadUv.js +++ /dev/null @@ -1,103 +0,0 @@ -import Geometry from '../../geometry/Geometry'; -import Buffer from '../../geometry/Buffer'; - -/** - * Helper class to create a quad with uvs like in v4 - * - * @class - * @memberof PIXI - */ -export default class QuadUv extends Geometry -{ - constructor() - { - super(); - - /** - * An array of vertices - * - * @member {Float32Array} - */ - this.vertices = new Float32Array([ - -1, -1, - 1, -1, - 1, 1, - -1, 1, - ]); - - /** - * The Uvs of the quad - * - * @member {Float32Array} - */ - this.uvs = new Float32Array([ - 0, 0, - 1, 0, - 1, 1, - 0, 1, - ]); - - this.vertexBuffer = new Buffer(this.vertices); - this.uvBuffer = new Buffer(this.uvs); - - this.addAttribute('aVertexPosition', this.vertexBuffer) - .addAttribute('aTextureCoord', this.uvBuffer) - .addIndex([0, 1, 2, 0, 2, 3]); - } - - /** - * Maps two Rectangle to the quad. - * - * @param {PIXI.Rectangle} targetTextureFrame - the first rectangle - * @param {PIXI.Rectangle} destinationFrame - the second rectangle - * @return {PIXI.Quad} Returns itself. - */ - map(targetTextureFrame, destinationFrame) - { - let x = 0; // destinationFrame.x / targetTextureFrame.width; - let y = 0; // destinationFrame.y / targetTextureFrame.height; - - this.uvs[0] = x; - this.uvs[1] = y; - - this.uvs[2] = x + (destinationFrame.width / targetTextureFrame.width); - this.uvs[3] = y; - - this.uvs[4] = x + (destinationFrame.width / targetTextureFrame.width); - this.uvs[5] = y + (destinationFrame.height / targetTextureFrame.height); - - this.uvs[6] = x; - this.uvs[7] = y + (destinationFrame.height / targetTextureFrame.height); - - x = destinationFrame.x; - y = destinationFrame.y; - - this.vertices[0] = x; - this.vertices[1] = y; - - this.vertices[2] = x + destinationFrame.width; - this.vertices[3] = y; - - this.vertices[4] = x + destinationFrame.width; - this.vertices[5] = y + destinationFrame.height; - - this.vertices[6] = x; - this.vertices[7] = y + destinationFrame.height; - - this.invalidate(); - - return this; - } - - /** - * legacy upload method, just marks buffers dirty - * @returns {PIXI.QuadUv} Returns itself. - */ - invalidate() - { - this.vertexBuffer._updateID++; - this.uvBuffer._updateID++; - - return this; - } -} diff --git a/packages/core/src/renderers/utils/checkMaxIfStatmentsInShader.js b/packages/core/src/renderers/utils/checkMaxIfStatmentsInShader.js deleted file mode 100644 index 8047c96..0000000 --- a/packages/core/src/renderers/utils/checkMaxIfStatmentsInShader.js +++ /dev/null @@ -1,58 +0,0 @@ -const fragTemplate = [ - 'precision mediump float;', - 'void main(void){', - 'float test = 0.1;', - '%forloop%', - 'gl_FragColor = vec4(0.0);', - '}', -].join('\n'); - -export default function checkMaxIfStatmentsInShader(maxIfs, gl) -{ - if (maxIfs === 0) - { - throw new Error('Invalid value of `0` passed to `checkMaxIfStatementsInShader`'); - } - - const shader = gl.createShader(gl.FRAGMENT_SHADER); - - while (true) // eslint-disable-line no-constant-condition - { - const fragmentSrc = fragTemplate.replace(/%forloop%/gi, generateIfTestSrc(maxIfs)); - - gl.shaderSource(shader, fragmentSrc); - gl.compileShader(shader); - - if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) - { - maxIfs = (maxIfs / 2) | 0; - } - else - { - // valid! - break; - } - } - - return maxIfs; -} - -function generateIfTestSrc(maxIfs) -{ - let src = ''; - - for (let i = 0; i < maxIfs; ++i) - { - if (i > 0) - { - src += '\nelse '; - } - - if (i < maxIfs - 1) - { - src += `if(test == ${i}.0){}`; - } - } - - return src; -} diff --git a/packages/core/src/renderers/utils/mapWebGLBlendModesToPixi.js b/packages/core/src/renderers/utils/mapWebGLBlendModesToPixi.js deleted file mode 100644 index 10ffe4d..0000000 --- a/packages/core/src/renderers/utils/mapWebGLBlendModesToPixi.js +++ /dev/null @@ -1,47 +0,0 @@ -import { BLEND_MODES } from '@pixi/constants'; - -/** - * Maps gl blend combinations to WebGL. - * - * @memberof PIXI - * @function mapWebGLBlendModesToPixi - * @private - * @param {WebGLRenderingContext} gl - The rendering context. - * @param {string[]} [array=[]] - The array to output into. - * @return {string[]} Mapped modes. - */ -export default function mapWebGLBlendModesToPixi(gl, array = []) -{ - // TODO - premultiply alpha would be different. - // add a boolean for that! - array[BLEND_MODES.NORMAL] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.ADD] = [gl.ONE, gl.DST_ALPHA]; - array[BLEND_MODES.MULTIPLY] = [gl.DST_COLOR, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.SCREEN] = [gl.ONE, gl.ONE_MINUS_SRC_COLOR]; - array[BLEND_MODES.OVERLAY] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.DARKEN] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.LIGHTEN] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.COLOR_DODGE] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.COLOR_BURN] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.HARD_LIGHT] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.SOFT_LIGHT] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.DIFFERENCE] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.EXCLUSION] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.HUE] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.SATURATION] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.COLOR] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.LUMINOSITY] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.NONE] = [0, 0]; - - // not-premultiplied blend modes - array[BLEND_MODES.NORMAL_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.ADD_NPM] = [gl.SRC_ALPHA, gl.DST_ALPHA, gl.ONE, gl.DST_ALPHA]; - array[BLEND_MODES.SCREEN_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_COLOR, gl.ONE, gl.ONE_MINUS_SRC_COLOR]; - - // not-premultiplied blend modes - array[BLEND_MODES.NORMAL_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.ADD_NPM] = [gl.SRC_ALPHA, gl.DST_ALPHA, gl.ONE, gl.DST_ALPHA]; - array[BLEND_MODES.SCREEN_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_COLOR, gl.ONE, gl.ONE_MINUS_SRC_COLOR]; - - return array; -} diff --git a/packages/core/src/shader/GLProgram.js b/packages/core/src/shader/GLProgram.js new file mode 100644 index 0000000..92980ca --- /dev/null +++ b/packages/core/src/shader/GLProgram.js @@ -0,0 +1,43 @@ +/** + * Helper class to create a webGL Program + * + * @class + * @private + */ +export default class GLProgram +{ + constructor(program, uniformData) + { + /** + * The shader program + * + * @member {WebGLProgram} + */ + this.program = program; + + /** + * holds the uniform data which contains uniform locations + * and current uniform values used for caching and preventing undeed GPU commands + * @type {Object} + */ + this.uniformData = uniformData; + + /** + * uniformGroups holds the various upload functions for the shader. Each uniform group + * and program have a unique upload function generated. + * @type {Object} + */ + this.uniformGroups = {}; + } + + /** + * Destroys this program + * TODO + */ + destroy() + { + this.uniformData = null; + this.uniformGroups = null; + this.program = null; + } +} diff --git a/packages/core/src/shader/Program.js b/packages/core/src/shader/Program.js index 4f9971e..4b4007a 100644 --- a/packages/core/src/shader/Program.js +++ b/packages/core/src/shader/Program.js @@ -1,6 +1,11 @@ -import * as shaderUtils from '../renderers/systems/shader/shader'; +// import * as from '../systems/shader/shader'; +import { setPrecision, + defaultValue, + compileProgram, + mapSize, + mapType, + getTestContext } from './utils'; import { ProgramCache } from '@pixi/utils'; -import getTestContext from './getTestContext'; import defaultFragment from './defaultProgram.frag'; import defaultVertex from './defaultProgram.vert'; import { settings } from '@pixi/settings'; @@ -34,8 +39,8 @@ */ this.fragmentSrc = fragmentSrc || Program.defaultFragmentSrc; - this.vertexSrc = shaderUtils.setPrecision(this.vertexSrc, settings.PRECISION_VERTEX); - this.fragmentSrc = shaderUtils.setPrecision(this.fragmentSrc, settings.PRECISION_FRAGMENT); + this.vertexSrc = setPrecision(this.vertexSrc, settings.PRECISION_VERTEX); + this.fragmentSrc = setPrecision(this.fragmentSrc, settings.PRECISION_FRAGMENT); // currently this does not extract structs only default types this.extractData(this.vertexSrc, this.fragmentSrc); @@ -62,7 +67,7 @@ if (gl) { - const program = shaderUtils.compileProgram(gl, vertexSrc, fragmentSrc); + const program = compileProgram(gl, vertexSrc, fragmentSrc); this.attributeData = this.getAttributeData(program, gl); this.uniformData = this.getUniformData(program, gl); @@ -95,13 +100,13 @@ for (let i = 0; i < totalAttributes; i++) { const attribData = gl.getActiveAttrib(program, i); - const type = shaderUtils.mapType(gl, attribData.type); + const type = mapType(gl, attribData.type); /*eslint-disable */ const data = { type: type, name: attribData.name, - size: shaderUtils.mapSize(type), + size: mapSize(type), location: 0, }; /* eslint-enable */ @@ -145,14 +150,14 @@ const name = uniformData.name.replace(/\[.*?\]/, ''); const isArray = uniformData.name.match(/\[.*?\]/, ''); - const type = shaderUtils.mapType(gl, uniformData.type); + const type = mapType(gl, uniformData.type); /*eslint-disable */ uniforms[name] = { type: type, size: uniformData.size, isArray:isArray, - value: shaderUtils.defaultValue(type, uniformData.size), + value: defaultValue(type, uniformData.size), }; /* eslint-enable */ } diff --git a/packages/core/src/shader/ShaderSystem.js b/packages/core/src/shader/ShaderSystem.js new file mode 100644 index 0000000..40b8af8 --- /dev/null +++ b/packages/core/src/shader/ShaderSystem.js @@ -0,0 +1,171 @@ +import System from '../System'; +import GLProgram from './GLProgram'; +import { generateUniformsSync, + defaultValue, + compileProgram } from './utils'; + +let UID = 0; + +/** + * Helper class to create a webGL Texture + * + * @class + * @memberof PIXI.systems + * @extends PIXI.System + */ +export default class ShaderSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - A reference to the current renderer + */ + constructor(renderer) + { + super(renderer); + + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = null; + + this.shader = null; + this.program = null; + + this.id = UID++; + } + + contextChange(gl) + { + this.gl = gl; + } + + /** + * Changes the current shader to the one given in parameter + * + * @param {PIXI.Shader} shader - the new shader + * @param {boolean} dontSync - false if the shader should automatically sync its uniforms. + * @returns {PIXI.glCore.glProgram} the glProgram that belongs to the shader. + */ + bind(shader, dontSync) + { + shader.uniforms.globals = this.renderer.globalUniforms; + + const program = shader.program; + const glProgram = program.glPrograms[this.renderer.CONTEXT_UID] || this.generateShader(shader); + + this.shader = shader; + + // TODO - some current pixi plugins bypass this.. so it not safe to use yet.. + if (this.program !== program) + { + this.program = program; + this.gl.useProgram(glProgram.program); + } + + if (!dontSync) + { + this.syncUniformGroup(shader.uniformGroup); + } + + return glProgram; + } + + /** + * Uploads the uniforms values to the currently bound shader. + * + * @param {object} uniforms - the uniforms valiues that be applied to the current shader + */ + setUniforms(uniforms) + { + const shader = this.shader.program; + const glProgram = shader.glPrograms[this.renderer.CONTEXT_UID]; + + shader.syncUniforms(glProgram.uniformData, uniforms, this.renderer); + } + + syncUniformGroup(group) + { + const glProgram = this.getglProgram(); + + if (!group.static || group.dirtyId !== glProgram.uniformGroups[group.id]) + { + glProgram.uniformGroups[group.id] = group.dirtyId; + const syncFunc = group.syncUniforms[this.shader.program.id] || this.createSyncGroups(group); + + syncFunc(glProgram.uniformData, group.uniforms, this.renderer); + } + } + + createSyncGroups(group) + { + group.syncUniforms[this.shader.program.id] = generateUniformsSync(group, this.shader.program.uniformData); + + return group.syncUniforms[this.shader.program.id]; + } + + /** + * Returns the underlying GLShade rof the currently bound shader. + * This can be handy for when you to have a little more control over the setting of your uniforms. + * + * @return {PIXI.glCore.Shader} the glProgram for the currently bound Shader for this context + */ + getglProgram() + { + if (this.shader) + { + return this.shader.program.glPrograms[this.renderer.CONTEXT_UID]; + } + + return null; + } + + /** + * Generates a glProgram verion of the Shader provided. + * + * @private + * @param {PIXI.Shader} shader the shader that the glProgram will be based on. + * @return {PIXI.glCore.glProgram} A shiney new glProgram + */ + generateShader(shader) + { + const gl = this.gl; + + const program = shader.program; + + const attribMap = {}; + + for (const i in program.attributeData) + { + attribMap[i] = program.attributeData[i].location; + } + + const shaderProgram = compileProgram(gl, program.vertexSrc, program.fragmentSrc, attribMap); + const uniformData = {}; + + for (const i in program.uniformData) + { + const data = program.uniformData[i]; + + uniformData[i] = { + location: gl.getUniformLocation(shaderProgram, i), + value: defaultValue(data.type, data.size), + }; + } + + const glProgram = new GLProgram(shaderProgram, uniformData); + + program.glPrograms[this.renderer.CONTEXT_UID] = glProgram; + + return glProgram; + } + + /** + * Destroys this System and removes all its textures + */ + destroy() + { + // TODO implement destroy method for ShaderSystem + this.destroyed = true; + } +} diff --git a/packages/core/src/shader/defaultValue.js b/packages/core/src/shader/defaultValue.js deleted file mode 100644 index 847a069..0000000 --- a/packages/core/src/shader/defaultValue.js +++ /dev/null @@ -1,78 +0,0 @@ -function booleanArray(size) -{ - const array = new Array(size); - - for (let i = 0; i < array.length; i++) - { - array[i] = false; - } - - return array; -} - -/** - * @class - * @memberof PIXI.glCore.shader - * @param type {String} Type of value - * @param size {Number} - */ -export default function defaultValue(type, size) -{ - switch (type) - { - case 'float': - return 0; - - case 'vec2': - return new Float32Array(2 * size); - - case 'vec3': - return new Float32Array(3 * size); - - case 'vec4': - return new Float32Array(4 * size); - - case 'int': - case 'sampler2D': - return 0; - - case 'ivec2': - return new Int32Array(2 * size); - - case 'ivec3': - return new Int32Array(3 * size); - - case 'ivec4': - return new Int32Array(4 * size); - - case 'bool': - return false; - - case 'bvec2': - - return booleanArray(2 * size); - - case 'bvec3': - return booleanArray(3 * size); - - case 'bvec4': - return booleanArray(4 * size); - - case 'mat2': - return new Float32Array([1, 0, - 0, 1]); - - case 'mat3': - return new Float32Array([1, 0, 0, - 0, 1, 0, - 0, 0, 1]); - - case 'mat4': - return new Float32Array([1, 0, 0, 0, - 0, 1, 0, 0, - 0, 0, 1, 0, - 0, 0, 0, 1]); - } - - return 0; -} diff --git a/packages/core/src/shader/extractUniformsFromSrc.js b/packages/core/src/shader/extractUniformsFromSrc.js deleted file mode 100644 index 3d02c0a..0000000 --- a/packages/core/src/shader/extractUniformsFromSrc.js +++ /dev/null @@ -1,57 +0,0 @@ -import defaultValue from './defaultValue'; - -export default function extractUniformsFromSrc(vertexSrc, fragmentSrc, mask) -{ - const vertUniforms = extractUniformsFromString(vertexSrc, mask); - const fragUniforms = extractUniformsFromString(fragmentSrc, mask); - - return Object.assign(vertUniforms, fragUniforms); -} - -function extractUniformsFromString(string) -{ - const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); - - const uniforms = {}; - let nameSplit; - - // clean the lines a little - remove extra spaces / teabs etc - // then split along ';' - const lines = string.replace(/\s+/g, ' ') - .split(/\s*;\s*/); - - // loop through.. - for (let i = 0; i < lines.length; i++) - { - const line = lines[i].trim(); - - if (line.indexOf('uniform') > -1) - { - const splitLine = line.split(' '); - const type = splitLine[1]; - - let name = splitLine[2]; - let size = 1; - - if (name.indexOf('[') > -1) - { - // array! - nameSplit = name.split(/\[|]/); - name = nameSplit[0]; - size *= Number(nameSplit[1]); - } - - if (!name.match(maskRegex)) - { - uniforms[name] = { - value: defaultValue(type, size), - dirtyId: 0, - name, - type, - }; - } - } - } - - return uniforms; -} diff --git a/packages/core/src/shader/generateUniformsSync.js b/packages/core/src/shader/generateUniformsSync.js deleted file mode 100644 index 98a357f..0000000 --- a/packages/core/src/shader/generateUniformsSync.js +++ /dev/null @@ -1,231 +0,0 @@ -// cv = CachedValue -// v = value -// ud = uniformData -// uv = uniformValue -// l = loaction -const GLSL_TO_SINGLE_SETTERS_CACHED = { - - float: ` - if(cv !== v) - { - cv.v = v; - gl.uniform1f(location, v) - }`, - - vec2: ` - if(cv[0] !== v[0] || cv[1] !== v[1]) - { - cv[0] = v[0]; - cv[1] = v[1]; - gl.uniform2f(location, v[0], v[1]) - }`, - - vec3: ` - if(cv[0] !== v[0] || cv[1] !== v[1] || cv[2] !== v[2]) - { - cv[0] = v[0]; - cv[1] = v[1]; - cv[2] = v[2]; - - gl.uniform3f(location, v[0], v[1], v[2]) - }`, - - vec4: 'gl.uniform4f(location, v[0], v[1], v[2], v[3])', - - int: 'gl.uniform1i(location, v)', - ivec2: 'gl.uniform2i(location, v[0], v[1])', - ivec3: 'gl.uniform3i(location, v[0], v[1], v[2])', - ivec4: 'gl.uniform4i(location, v[0], v[1], v[2], v[3])', - - bool: 'gl.uniform1i(location, v)', - bvec2: 'gl.uniform2i(location, v[0], v[1])', - bvec3: 'gl.uniform3i(location, v[0], v[1], v[2])', - bvec4: 'gl.uniform4i(location, v[0], v[1], v[2], v[3])', - - mat2: 'gl.uniformMatrix2fv(location, false, v)', - mat3: 'gl.uniformMatrix3fv(location, false, v)', - mat4: 'gl.uniformMatrix4fv(location, false, v)', - - sampler2D: 'gl.uniform1i(location, v)', - samplerCube: 'gl.uniform1i(location, v)', - sampler2DArray: 'gl.uniform1i(location, v)', -}; - -const GLSL_TO_ARRAY_SETTERS = { - - float: `gl.uniform1fv(location, v)`, - - vec2: `gl.uniform2fv(location, v)`, - vec3: `gl.uniform3fv(location, v)`, - vec4: 'gl.uniform4fv(location, v)', - - mat4: 'gl.uniformMatrix4fv(location, false, v)', - mat3: 'gl.uniformMatrix3fv(location, false, v)', - mat2: 'gl.uniformMatrix2fv(location, false, v)', - - int: 'gl.uniform1iv(location, v)', - ivec2: 'gl.uniform2iv(location, v)', - ivec3: 'gl.uniform3iv(location, v)', - ivec4: 'gl.uniform4iv(location, v)', - - bool: 'gl.uniform1iv(location, v)', - bvec2: 'gl.uniform2iv(location, v)', - bvec3: 'gl.uniform3iv(location, v)', - bvec4: 'gl.uniform4iv(location, v)', - - sampler2D: 'gl.uniform1iv(location, v)', - samplerCube: 'gl.uniform1iv(location, v)', - sampler2DArray: 'gl.uniform1iv(location, v)', -}; - -export default function generateUniformsSync(group, uniformData) -{ - let textureCount = 0; - let func = `var v = null; - var cv = null - var gl = renderer.gl`; - - for (const i in group.uniforms) - { - const data = uniformData[i]; - - if (!data) - { - if (group.uniforms[i].group) - { - func += ` - renderer.shader.syncUniformGroup(uv.${i}); - `; - } - - continue; - } - - // TODO && uniformData[i].value !== 0 <-- do we still need this? - if (data.type === 'float' && data.size === 1) - { - func += ` - if(uv.${i} !== ud.${i}.value) - { - ud.${i}.value = uv.${i} - gl.uniform1f(ud.${i}.location, uv.${i}) - }\n`; - } - /* eslint-disable max-len */ - else if ((data.type === 'sampler2D' || data.type === 'samplerCube' || data.type === 'sampler2DArray') && data.size === 1 && !data.isArray) - /* eslint-disable max-len */ - { - func += ` - renderer.texture.bind(uv.${i}, ${textureCount}); - - if(ud.${i}.value !== ${textureCount}) - { - ud.${i}.value = ${textureCount}; - gl.uniform1i(ud.${i}.location, ${textureCount});\n; // eslint-disable-line max-len - }\n`; - - textureCount++; - } - else if (data.type === 'mat3' && data.size === 1) - { - if (group.uniforms[i].a !== undefined) - { - // TODO and some smart caching dirty ids here! - func += ` - gl.uniformMatrix3fv(ud.${i}.location, false, uv.${i}.toArray(true)); - \n`; - } - else - { - func += ` - gl.uniformMatrix3fv(ud.${i}.location, false, uv.${i}); - \n`; - } - } - else if (data.type === 'vec2' && data.size === 1) - { - // TODO - do we need both here? - // maybe we can get away with only using points? - if (group.uniforms[i].x !== undefined) - { - func += ` - cv = ud.${i}.value; - v = uv.${i}; - - if(cv[0] !== v.x || cv[1] !== v.y) - { - cv[0] = v.x; - cv[1] = v.y; - gl.uniform2f(ud.${i}.location, v.x, v.y); - }\n`; - } - else - { - func += ` - cv = ud.${i}.value; - v = uv.${i}; - - if(cv[0] !== v[0] || cv[1] !== v[1]) - { - cv[0] = v[0]; - cv[1] = v[1]; - gl.uniform2f(ud.${i}.location, v[0], v[1]); - } - \n`; - } - } - else if (data.type === 'vec4' && data.size === 1) - { - // TODO - do we need both here? - // maybe we can get away with only using points? - if (group.uniforms[i].width !== undefined) - { - func += ` - cv = ud.${i}.value; - v = uv.${i}; - - if(cv[0] !== v.x || cv[1] !== v.y || cv[2] !== v.width || cv[3] !== v.height) - { - cv[0] = v.x; - cv[1] = v.y; - cv[2] = v.width; - cv[3] = v.height; - gl.uniform4f(ud.${i}.location, v.x, v.y, v.width, v.height) - }\n`; - } - else - { - func += ` - cv = ud.${i}.value; - v = uv.${i}; - - if(cv[0] !== v[0] || cv[1] !== v[1] || cv[2] !== v[2] || cv[3] !== v[3]) - { - cv[0] = v[0]; - cv[1] = v[1]; - cv[2] = v[2]; - cv[3] = v[3]; - - gl.uniform4f(ud.${i}.location, v[0], v[1], v[2], v[3]) - } - \n`; - } - } - else - { - const templateType = (data.size === 1) ? GLSL_TO_SINGLE_SETTERS_CACHED : GLSL_TO_ARRAY_SETTERS; - - const template = templateType[data.type].replace('location', `ud.${i}.location`); - - func += ` - cv = ud.${i}.value; - v = uv.${i}; - ${template};\n`; - } - } - - // console.log(' --------------- ') - // console.log(func); - - return new Function('ud', 'uv', 'renderer', func); // eslint-disable-line no-new-func -} diff --git a/packages/core/src/shader/getTestContext.js b/packages/core/src/shader/getTestContext.js deleted file mode 100644 index ea174cc..0000000 --- a/packages/core/src/shader/getTestContext.js +++ /dev/null @@ -1,48 +0,0 @@ -import { settings } from '@pixi/settings'; - -let context = null; - -/** - * returns a little webGL context to use for program inspection. - * - * @static - * @private - * @returns {webGL-context} a gl context to test with - */ -export default function getTestContext() -{ - if (!context) - { - const canvas = document.createElement('canvas'); - - let gl; - - if (settings.PREFER_WEBGL_2) - { - gl = canvas.getContext('webgl2', {}); - } - - if (!gl) - { - gl = canvas.getContext('webgl', {}) - || canvas.getContext('experimental-webgl', {}); - - if (!gl) - { - // fail, not able to get a context - throw new Error('This browser does not support webGL. Try using the canvas renderer'); - } - else - { - // for shader testing.. - gl.getExtension('WEBGL_draw_buffers'); - } - } - - context = gl; - - return gl; - } - - return context; -} diff --git a/packages/core/src/shader/utils/checkMaxIfStatmentsInShader.js b/packages/core/src/shader/utils/checkMaxIfStatmentsInShader.js new file mode 100644 index 0000000..8047c96 --- /dev/null +++ b/packages/core/src/shader/utils/checkMaxIfStatmentsInShader.js @@ -0,0 +1,58 @@ +const fragTemplate = [ + 'precision mediump float;', + 'void main(void){', + 'float test = 0.1;', + '%forloop%', + 'gl_FragColor = vec4(0.0);', + '}', +].join('\n'); + +export default function checkMaxIfStatmentsInShader(maxIfs, gl) +{ + if (maxIfs === 0) + { + throw new Error('Invalid value of `0` passed to `checkMaxIfStatementsInShader`'); + } + + const shader = gl.createShader(gl.FRAGMENT_SHADER); + + while (true) // eslint-disable-line no-constant-condition + { + const fragmentSrc = fragTemplate.replace(/%forloop%/gi, generateIfTestSrc(maxIfs)); + + gl.shaderSource(shader, fragmentSrc); + gl.compileShader(shader); + + if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) + { + maxIfs = (maxIfs / 2) | 0; + } + else + { + // valid! + break; + } + } + + return maxIfs; +} + +function generateIfTestSrc(maxIfs) +{ + let src = ''; + + for (let i = 0; i < maxIfs; ++i) + { + if (i > 0) + { + src += '\nelse '; + } + + if (i < maxIfs - 1) + { + src += `if(test == ${i}.0){}`; + } + } + + return src; +} diff --git a/packages/core/src/AbstractRenderer.js b/packages/core/src/AbstractRenderer.js new file mode 100644 index 0000000..64b2eb1 --- /dev/null +++ b/packages/core/src/AbstractRenderer.js @@ -0,0 +1,353 @@ +import { hex2string, hex2rgb } from '@pixi/utils'; +import { Matrix, Rectangle } from '@pixi/math'; +import { RENDERER_TYPE } from '@pixi/constants'; +import { settings } from '@pixi/settings'; +import { Container } from '@pixi/display'; +import RenderTexture from './renderTexture/RenderTexture'; +import EventEmitter from 'eventemitter3'; + +const tempMatrix = new Matrix(); + +/** + * 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 EventEmitter + * @memberof PIXI + */ +export default class AbstractRenderer extends EventEmitter +{ + // eslint-disable-next-line valid-jsdoc + /** + * @param {string} system - The name of the system this renderer is for. + * @param {object} [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.transparent=false] - If the render view is transparent, default false + * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false + * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. The + * resolution of the renderer retina would be 2. + * @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 {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when rendering, + * stopping pixel interpolation. + */ + constructor(system, options, arg2, arg3) + { + super(); + + // Support for constructor(system, screenWidth, screenHeight, options) + if (typeof options === 'number') + { + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + }, arg3); + } + + // 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 = RENDERER_TYPE.UNKNOWN; + + /** + * Measurements of the screen. (0, 0, screenWidth, screenHeight) + * + * Its safe to use as filterArea or hitArea for 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 1 + */ + this.resolution = options.resolution || settings.RESOLUTION; + + /** + * Whether the render view is transparent + * + * @member {boolean} + */ + this.transparent = options.transparent; + + /** + * Whether css dimensions of canvas view should be resized to screen dimensions automatically + * + * @member {boolean} + */ + this.autoResize = options.autoResize || false; + + /** + * Tracks the blend modes useful for this renderer. + * + * @member {object} + */ + this.blendModes = null; + + /** + * 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; + + /** + * If true PixiJS will Math.floor() x/y values when rendering, stopping pixel interpolation. + * Handy for crisp pixel art and speed on legacy devices. + * + * @member {boolean} + */ + this.roundPixels = options.roundPixels; + + /** + * The background color as a number. + * + * @member {number} + * @private + */ + this._backgroundColor = 0x000000; + + /** + * The background color as an [R, G, B] array. + * + * @member {number[]} + * @private + */ + this._backgroundColorRgba = [0, 0, 0, 0]; + + /** + * The background color as a string. + * + * @member {string} + * @private + */ + this._backgroundColorString = '#000000'; + + this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter + + /** + * This temporary display object used as the parent of the currently being rendered item + * + * @member {PIXI.DisplayObject} + * @private + */ + this._tempDisplayObjectParent = new Container(); + + /** + * The last root object that the renderer tried to render. + * + * @member {PIXI.DisplayObject} + * @private + */ + this._lastObjectRendered = this._tempDisplayObjectParent; + + /** + * Collection of plugins. + * @readonly + * @member {object} + */ + this.plugins = {}; + } + + /** + * Initialize the plugins. + * + * @protected + * @param {object} staticMap - The dictionary of staticly saved plugins. + */ + initPlugins(staticMap) + { + 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() + { + return this.view.width; + } + + /** + * Same as view.height, actual number of pixels in the canvas by vertical + * + * @member {number} + * @readonly + * @default 600 + */ + get height() + { + return this.view.height; + } + + /** + * Resizes the screen and canvas to the specified width and height + * Canvas dimensions are multiplied by resolution + * + * @param {number} screenWidth - the new width of the screen + * @param {number} screenHeight - the new height of the screen + */ + resize(screenWidth, screenHeight) + { + this.screen.width = screenWidth; + this.screen.height = screenHeight; + + this.view.width = screenWidth * this.resolution; + this.view.height = screenHeight * this.resolution; + + if (this.autoResize) + { + this.view.style.width = `${screenWidth}px`; + this.view.style.height = `${screenHeight}px`; + } + } + + /** + * 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 {PIXI.DisplayObject} displayObject - The displayObject the object will be generated from + * @param {number} scaleMode - Should be one of the scaleMode consts + * @param {number} resolution - The resolution / device pixel ratio of the texture being generated + * @param {PIXI.Rectangle} [region] - The region of the displayObject, that shall be rendered, + * if no region is specified, defaults to the local bounds of the displayObject. + * @return {PIXI.Texture} a texture of the graphics object + */ + generateTexture(displayObject, scaleMode, resolution, region) + { + region = region || displayObject.getLocalBounds(); + + // 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(region.width | 0, region.height | 0, scaleMode, resolution); + + tempMatrix.tx = -region.x; + tempMatrix.ty = -region.y; + + this.render(displayObject, renderTexture, false, tempMatrix, true); + + return renderTexture; + } + + /** + * Removes everything from the renderer and optionally removes the Canvas DOM element. + * + * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. + */ + destroy(removeView) + { + 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); + } + + this.plugins = null; + + this.type = RENDERER_TYPE.UNKNOWN; + + this.view = null; + + this.screen = null; + + this.resolution = 0; + + this.transparent = false; + + this.autoResize = false; + + this.blendModes = null; + + this.options = null; + + this.preserveDrawingBuffer = false; + this.clearBeforeRender = false; + + this.roundPixels = false; + + this._backgroundColor = 0; + this._backgroundColorRgba = null; + this._backgroundColorString = null; + + this._tempDisplayObjectParent = null; + this._lastObjectRendered = null; + } + + /** + * The background color to fill if not transparent + * + * @member {number} + */ + get backgroundColor() + { + return this._backgroundColor; + } + + set backgroundColor(value) // eslint-disable-line require-jsdoc + { + this._backgroundColor = value; + this._backgroundColorString = hex2string(value); + hex2rgb(value, this._backgroundColorRgba); + } +} diff --git a/packages/core/src/Renderer.js b/packages/core/src/Renderer.js new file mode 100644 index 0000000..2227e78 --- /dev/null +++ b/packages/core/src/Renderer.js @@ -0,0 +1,338 @@ +import AbstractRenderer from './AbstractRenderer'; +import { sayHello } from '@pixi/utils'; +import MaskSystem from './mask/MaskSystem'; +import StencilSystem from './mask/StencilSystem'; +import FilterSystem from './filters/FilterSystem'; +import FramebufferSystem from './framebuffer/FramebufferSystem'; +import RenderTextureSystem from './renderTexture/RenderTextureSystem'; +import TextureSystem from './textures/TextureSystem'; +import ProjectionSystem from './projection/ProjectionSystem'; +import StateSystem from './state/StateSystem'; +import GeometrySystem from './geometry/GeometrySystem'; +import ShaderSystem from './shader/ShaderSystem'; +import ContextSystem from './context/ContextSystem'; +import BatchSystem from './batch/BatchSystem'; +import TextureGCSystem from './textures/TextureGCSystem'; +import { RENDERER_TYPE } from '@pixi/constants'; +import UniformGroup from './shader/UniformGroup'; +import { Matrix } from '@pixi/math'; +import Runner from 'mini-runner'; + +/** + * 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 Render works by automatically managing webGLBatchs. + * 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 :) + * + * @class + * @memberof PIXI + * @extends PIXI.AbstractRenderer + */ +export default class Renderer extends AbstractRenderer +{ + // eslint-disable-next-line valid-jsdoc + /** + * + * @param {object} [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.transparent=false] - If the render view is transparent, default false + * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false + * @param {boolean} [options.antialias=false] - sets antialias. If not available natively then FXAA + * antialiasing is used + * @param {boolean} [options.forceFXAA=false] - forces FXAA antialiasing to be used over native. + * FXAA is faster, but may not always look as great + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. + * The resolution of the renderer retina would be 2. + * @param {boolean} [options.clearBeforeRender=true] - This sets if the renderer will clear + * the canvas or not before the new render pass. If you wish to set this to false, you *must* set + * preserveDrawingBuffer to `true`. + * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, + * enable this if you need to call toDataUrl on the webgl context. + * @param {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when + * rendering, stopping pixel interpolation. + * @param {number} [options.backgroundColor=0x000000] - The background color of the rendered area + * (shown if not transparent). + * @param {boolean} [options.legacy=false] - If true PixiJS will aim to ensure compatibility + * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. + * @param {string} [options.powerPreference] - Parameter passed to webgl context, set to "high-performance" + * for devices with dual graphics card + */ + constructor(options, arg2, arg3) + { + super('WebGL', options, arg2, arg3); + + /** + * The type of this renderer as a standardised const + * + * @member {number} + * @see PIXI.RENDERER_TYPE + */ + this.type = RENDERER_TYPE.WEBGL; + + // this will be set by the contextSystem (this.context) + this.gl = null; + this.CONTEXT_UID = 0; + this.legacy = !!options.legacy; + + // TODO legacy! + + // runners! + this.runners = { + destroy: new Runner('destroy'), + contextChange: new Runner('contextChange', 1), + reset: new Runner('reset'), + update: new Runner('update'), + postrender: new Runner('postrender'), + prerender: new Runner('prerender'), + resize: new Runner('resize', 2), + }; + + 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(GeometrySystem, 'geometry') + .addSystem(FramebufferSystem, 'framebuffer') + .addSystem(StencilSystem, 'stencil') + .addSystem(ProjectionSystem, 'projection') + .addSystem(TextureGCSystem, 'textureGC') + .addSystem(FilterSystem, 'filter') + .addSystem(RenderTextureSystem, 'renderTexture') + .addSystem(BatchSystem, 'batch'); + + this.initPlugins(Renderer.__plugins); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ + if (options.context) + { + this.context.initFromContext(options.context); + } + else + { + this.context.initFromOptions({ + alpha: this.transparent, + antialias: options.antialias, + premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', + stencil: true, + 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); + } + + /** + * Add a new system to the renderer. + * @param {class} ClassRef - Class reference + * @param {string} [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. + * @return {PIXI.Renderer} Return instance of renderer + */ + addSystem(ClassRef, name) + { + if (!name) + { + name = ClassRef.name; + } + + // TODO - read name from class.name.. + + /* + if(name.includes('System')) + { + name = name.replace('System', ''); + name = name.charAt(0).toLowerCase() + name.slice(1); + } + */ + + const system = new ClassRef(this); + + if (this[name]) + { + throw new Error(`Whoops! ${name} is already a manger`); + } + + this[name] = system; + + for (const i in this.runners) + { + this.runners[i].add(system); + } + + return this; + + /** + * 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. + */ + } + + /** + * Renders the object to its webGL view + * + * @param {PIXI.DisplayObject} displayObject - the object to be rendered + * @param {PIXI.RenderTexture} renderTexture - The render texture to render to. + * @param {boolean} [clear] - Should the canvas be cleared before the new render + * @param {PIXI.Transform} [transform] - A transform to apply to the render texture before rendering. + * @param {boolean} [skipUpdateTransform] - Should we skip the update transform pass? + */ + render(displayObject, renderTexture, clear, transform, skipUpdateTransform) + { + // can be handy to know! + this.renderingToScreen = !renderTexture; + + this.runners.prerender.run(); + this.emit('prerender'); + + // 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.parent; + + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = 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.run(); + + this.emit('postrender'); + } + + /** + * Resizes the webGL view to the specified width and height. + * + * @param {number} screenWidth - the new width of the screen + * @param {number} screenHeight - the new height of the screen + */ + resize(screenWidth, screenHeight) + { + AbstractRenderer.prototype.resize.call(this, screenWidth, screenHeight); + + this.runners.resize.run(screenWidth, screenHeight); + } + + /** + * Resets the WebGL state so you can render things however you fancy! + * + * @return {PIXI.Renderer} Returns itself. + */ + reset() + { + this.runners.reset.run(); + + return this; + } + + clear() + { + this.framebuffer.bind(); + this.framebuffer.clear(); + } + + /** + * Removes everything from the renderer (event listeners, spritebatch, etc...) + * + * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. + * See: https://github.com/pixijs/pixi.js/issues/2233 + */ + destroy(removeView) + { + this.runners.destroy.run(); + + // call base destroy + super.destroy(removeView); + + // TODO nullify all the managers.. + this.gl = null; + } + + /** + * 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 + * builds and excluding plugins. + * @name PIXI.Renderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. + */ + + /** + * Adds a plugin to the renderer. + * + * @method + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + static registerPlugin(pluginName, ctor) + { + Renderer.__plugins = Renderer.__plugins || {}; + Renderer.__plugins[pluginName] = ctor; + } +} diff --git a/packages/core/src/System.js b/packages/core/src/System.js new file mode 100644 index 0000000..05a10c8 --- /dev/null +++ b/packages/core/src/System.js @@ -0,0 +1,42 @@ +/** + * System is a base class used for extending systems used by the {@link PIXI.Renderer} + * @see PIXI.Renderer#addSystem + * @class + * @memberof PIXI + */ +export default class System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + /** + * The renderer this manager works for. + * + * @member {PIXI.Renderer} + */ + this.renderer = renderer; + + this.renderer.runners.contextChange.add(this); + } + + /** + * Generic method called when there is a WebGL context change. + * + */ + contextChange() + { + // do some codes init! + } + + /** + * Generic destroy methods to be overridden by the subclass + * + */ + destroy() + { + this.renderer.runners.contextChange.remove(this); + this.renderer = null; + } +} diff --git a/packages/core/src/batch/BatchSystem.js b/packages/core/src/batch/BatchSystem.js new file mode 100644 index 0000000..b04c249 --- /dev/null +++ b/packages/core/src/batch/BatchSystem.js @@ -0,0 +1,68 @@ +import System from '../System'; +import ObjectRenderer from './ObjectRenderer'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class BatchSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + super(renderer); + + /** + * An empty renderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.emptyRenderer = new ObjectRenderer(renderer); + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.currentRenderer = this.emptyRenderer; + } + + /** + * Changes the current renderer to the one given in parameter + * + * @param {PIXI.ObjectRenderer} objectRenderer - The object renderer to use. + */ + setObjectRenderer(objectRenderer) + { + if (this.currentRenderer === objectRenderer) + { + return; + } + + this.currentRenderer.stop(); + this.currentRenderer = objectRenderer; + + this.renderer.state.setState(objectRenderer.state); + + this.currentRenderer.start(); + } + + /** + * This should be called if you wish to do some custom rendering + * It will basically render anything that may be batched up such as sprites + * + */ + flush() + { + this.setObjectRenderer(this.emptyRenderer); + } + + reset() + { + this.setObjectRenderer(this.emptyRenderer); + } +} diff --git a/packages/core/src/batch/ObjectRenderer.js b/packages/core/src/batch/ObjectRenderer.js new file mode 100644 index 0000000..0c851a9 --- /dev/null +++ b/packages/core/src/batch/ObjectRenderer.js @@ -0,0 +1,48 @@ +import System from '../System'; + +/** + * Base for a common object renderer that can be used as a system renderer plugin. + * + * @class + * @extends PIXI.System + * @memberof PIXI + */ +export default class ObjectRenderer extends System +{ + /** + * Starts the renderer and sets the shader + * + */ + start() + { + // set the shader.. + } + + /** + * Stops the renderer + * + */ + stop() + { + this.flush(); + } + + /** + * Stub method for rendering content and emptying the current batch. + * + */ + flush() + { + // flush! + } + + /** + * Renders an object + * + * @param {PIXI.DisplayObject} object - The object to render. + */ + render(object) // eslint-disable-line no-unused-vars + { + // render the object + } +} diff --git a/packages/core/src/context/ContextSystem.js b/packages/core/src/context/ContextSystem.js new file mode 100644 index 0000000..301aab4 --- /dev/null +++ b/packages/core/src/context/ContextSystem.js @@ -0,0 +1,186 @@ +import System from '../System'; +import { settings } from '@pixi/settings'; + +let CONTEXT_UID = 0; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class ContextSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.webGLVersion = 1; + + this.handleContextLost = this.handleContextLost.bind(this); + this.handleContextRestored = this.handleContextRestored.bind(this); + + this.extensions = {}; + + renderer.view.addEventListener('webglcontextlost', this.handleContextLost, false); + renderer.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + } + + get isLost() + { + return (!this.gl || this.gl.isContextLost()); + } + + contextChange(gl) + { + this.gl = gl; + + // restore a context if it was previously lost + if (gl.isContextLost() && gl.getExtension('WEBGL_lose_context')) + { + gl.getExtension('WEBGL_lose_context').restoreContext(); + } + } + + initFromContext(gl) + { + this.gl = gl; + this.validateContext(gl); + this.renderer.gl = gl; + this.renderer.CONTEXT_UID = CONTEXT_UID++; + this.renderer.runners.contextChange.run(gl); + } + + initFromOptions(options) + { + const gl = this.createContext(this.renderer.view, options); + + this.initFromContext(gl); + } + + /** + * Helper class to create a webGL Context + * + * @class + * @memberof PIXI.glCore + * @param canvas {HTMLCanvasElement} the canvas element that we will get the context from + * @param options {Object} An options object that gets passed in to the canvas element containing the context attributes, + * see https://developer.mozilla.org/en/docs/Web/API/HTMLCanvasElement/getContext for the options available + * @return {WebGLRenderingContext} the WebGL context + */ + createContext(canvas, options) + { + let gl; + + if (settings.PREFER_WEBGL_2) + { + gl = canvas.getContext('webgl2', options); + } + + if (gl) + { + this.webGLVersion = 2; + } + else + { + this.webGLVersion = 1; + + gl = canvas.getContext('webgl', options) + || canvas.getContext('experimental-webgl', options); + + if (!gl) + { + // fail, not able to get a context + throw new Error('This browser does not support webGL. Try using the canvas renderer'); + } + } + + this.gl = gl; + + this.getExtensions(); + + return gl; + } + + getExtensions() + { + // time to set up default etensions that pixi uses.. + const gl = this.gl; + const extensions = this.extensions; + + if (this.webGLVersion === 1) + { + extensions.drawBuffers = gl.getExtension('WEBGL_draw_buffers'); + extensions.depthTexture = gl.getExtension('WEBKIT_WEBGL_depth_texture'); + extensions.floatTexture = gl.getExtension('OES_texture_float'); + extensions.loseContext = gl.getExtension('WEBGL_lose_context'); + + extensions.vertexArrayObject = gl.getExtension('OES_vertex_array_object') + || gl.getExtension('MOZ_OES_vertex_array_object') + || gl.getExtension('WEBKIT_OES_vertex_array_object'); + } + + // we don't use any specific WebGL 2 ones yet! + } + + /** + * Handles a lost webgl context + * + * @private + * @param {WebGLContextEvent} event - The context lost event. + */ + handleContextLost(event) + { + event.preventDefault(); + } + + /** + * Handles a restored webgl context + * + * @private + */ + handleContextRestored() + { + this.renderer.runners.contextChange.run(this.gl); + } + + destroy() + { + const view = this.renderer.view; + + // remove listeners + view.removeEventListener('webglcontextlost', this.handleContextLost); + view.removeEventListener('webglcontextrestored', this.handleContextRestored); + + this.gl.useProgram(null); + + if (this.extensions.loseContext) + { + this.extensions.loseContext.loseContext(); + } + } + + postrender() + { + this.gl.flush(); + } + + validateContext(gl) + { + const attributes = gl.getContextAttributes(); + + // this is going to be fairly simple for now.. but at least we have room to grow! + if (!attributes.stencil) + { + /* eslint-disable max-len */ + + /* eslint-disable no-console */ + console.warn('Provided WebGL context does not have a stencil buffer, masks may not render correctly'); + /* eslint-enable no-console */ + + /* eslint-enable max-len */ + } + } +} diff --git a/packages/core/src/filters/Filter.js b/packages/core/src/filters/Filter.js new file mode 100644 index 0000000..9d8cad8 --- /dev/null +++ b/packages/core/src/filters/Filter.js @@ -0,0 +1,140 @@ +import Shader from '../shader/Shader'; +import Program from '../shader/Program'; +import State from '../state/State'; +import { settings } from '@pixi/settings'; +// import extractUniformsFromSrc from './extractUniformsFromSrc'; +import defaultVertex from './defaultFilter.vert'; +import defaultFragment from './defaultFilter.frag'; + +// let math = require('../../math'); +/** + * @class + * @memberof PIXI + * @extends PIXI.Shader + */ +export default class Filter extends Shader +{ + /** + * @param {string} [vertexSrc] - The source of the vertex shader. + * @param {string} [fragmentSrc] - The source of the fragment shader. + * @param {object} [uniforms] - Custom uniforms to use to augment the built-in ones. + */ + constructor(vertexSrc, fragmentSrc, uniforms) + { + const program = Program.from(vertexSrc, fragmentSrc); + + super(program, uniforms); + + /** + * The padding of the filter. Some filters require extra space to breath such as a blur. + * Increasing this will add extra width and height to the bounds of the object that the + * filter is applied to. + * + * @member {number} + */ + this.padding = 0; + + /** + * The resolution of the filter. Setting this to be lower will lower the quality but + * increase the performance of the filter. + * + * @member {number} + */ + this.resolution = settings.RESOLUTION; + + /** + * If enabled is true the filter is applied, if false it will not. + * + * @member {boolean} + */ + this.enabled = true; + + /** + * If enabled, PixiJS will fit the filter area into boundaries for better performance. + * Switch it off if it does not work for specific shader. + * + * @member {boolean} + */ + this.autoFit = true; + + /** + * Legacy filters use position and uvs from attributes + * @member {boolean} + * @readonly + */ + this.legacy = !!this.program.attributeData.aTextureCoord; + + /** + * the webGL state the filter requires to render + * @member {PIXI.State} + */ + this.state = new State(); + } + + /** + * Applies the filter + * + * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + * @param {boolean} clear - Should the output be cleared before rendering to it + * @param {object} [currentState] - It's current state of filter. + * There are some useful properties in the currentState : + * target, filters, sourceFrame, destinationFrame, renderTarget, resolution + */ + apply(filterManager, input, output, clear, currentState, derp) // eslint-disable-line no-unused-vars + { + // do as you please! + + filterManager.applyFilter(this, input, output, clear, currentState, derp); + + // or just do a regular render.. + } + + /** + * Sets the blendmode of the filter + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL + */ + get blendMode() + { + return this.state.blendMode; + } + + set blendMode(value) // eslint-disable-line require-jsdoc + { + this.state.blendMode = value; + } + + /** + * The default vertex shader source + * + * @static + * @constant + */ + static get defaultVertexSrc() + { + return defaultVertex; + } + + /** + * The default fragment shader source + * + * @static + * @constant + */ + static get defaultFragmentSrc() + { + return defaultFragment; + } +} + +/** + * Used for caching shader IDs + * + * @static + * @private + */ +Filter.SOURCE_KEY_MAP = {}; + diff --git a/packages/core/src/filters/FilterSystem.js b/packages/core/src/filters/FilterSystem.js new file mode 100644 index 0000000..3174318 --- /dev/null +++ b/packages/core/src/filters/FilterSystem.js @@ -0,0 +1,431 @@ +import System from '../System'; + +import RenderTexture from '../renderTexture/RenderTexture'; +import Quad from '../utils/Quad'; +import QuadUv from '../utils/QuadUv'; +import { Rectangle } from '@pixi/math'; +import * as filterTransforms from './filterTransforms'; +import bitTwiddle from 'bit-twiddle'; +import UniformGroup from '../shader/UniformGroup'; +import { DRAW_MODES } from '@pixi/constants'; + +// +/** + * @ignore + * @class + */ +class FilterState +{ + /** + * + */ + constructor() + { + this.renderTexture = null; + this.sourceFrame = new Rectangle(); + this.destinationFrame = new Rectangle(); + this.filters = []; + this.target = null; + this.legacy = false; + this.resolution = 1; + } +} + +/** + * @class + * @memberof PIXI.systems + * @extends PIXI.System + */ +export default class FilterSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + /** + * stores a bunch of PO2 textures used for filtering + * @type {Object} + */ + this.texturePool = {}; + + /** + * a pool for storing filter states, save us creating new ones each tick + * @type {Array} + */ + this.statePool = []; + + /** + * A very simple geometry used when drawing a filter effect to the screen + * @type {Quad} + */ + this.quad = new Quad(); + + this.quadUv = new QuadUv(); + + /** + * Temporary rect for maths + * @type {PIXI.Rectangle} + */ + this.tempRect = new Rectangle(); + + this.activeState = {}; + + /** + * this uniform group is attached to filter uniforms when used + * @type {UniformGroup} + */ + this.globalUniforms = new UniformGroup({ + sourceFrame: this.tempRect, + destinationFrame: this.tempRect, + + // legacy variables + filterArea: new Float32Array(4), + filterClamp: new Float32Array(4), + }, true); + } + + /** + * Adds a new filter to the System. + * + * @param {PIXI.DisplayObject} target - The target of the filter to render. + * @param {PIXI.Filter[]} filters - The filters to apply. + */ + push(target, filters) + { + const renderer = this.renderer; + const filterStack = this.renderer.renderTexture.defaultFilterStack; + const state = this.statePool.pop() || new FilterState(); + + let resolution = filters[0].resolution; + let padding = filters[0].padding; + let autoFit = filters[0].autoFit; + let legacy = filters[0].legacy; + + for (let i = 1; i < filters.length; i++) + { + const filter = filters[i]; + + // lets use the lowest resolution.. + resolution = Math.min(resolution, filter.resolution); + // and the largest amount of padding! + padding = Math.max(padding, filter.padding); + // only auto fit if all filters are autofit + autoFit = autoFit || filter.autoFit; + + legacy = legacy || filter.legacy; + } + + filterStack.push(state); + + state.resolution = resolution; + + state.legacy = legacy; + + // round to whole number based on resolution + // TODO move that to the shader too? + state.sourceFrame = target.filterArea || target.getBounds(true); + + state.sourceFrame.pad(padding); + + if (autoFit) + { + state.sourceFrame.fit(this.renderer.renderTexture.destinationFrame); + } + + state.sourceFrame.round(resolution); + + state.renderTexture = this.getPotFilterTexture(state.sourceFrame.width, state.sourceFrame.height, resolution); + state.filters = filters; + + state.destinationFrame.width = state.renderTexture.width; + state.destinationFrame.height = state.renderTexture.height; + + state.renderTexture.filterFrame = state.sourceFrame; + renderer.renderTexture.bind(state.renderTexture, state.sourceFrame);// /, state.destinationFrame); + renderer.renderTexture.clear(); + } + + /** + * Pops off the filter and applies it. + * + */ + pop() + { + const renderer = this.renderer; + const filterStack = renderer.renderTexture.defaultFilterStack; + const state = filterStack.pop(); + const filters = state.filters; + + this.activeState = state; + + const globalUniforms = this.globalUniforms.uniforms; + + globalUniforms.sourceFrame = state.sourceFrame; + globalUniforms.destinationFrame = state.destinationFrame; + globalUniforms.resolution = state.resolution; + + // only update the rect if its legacy.. + if (state.legacy) + { + const filterArea = globalUniforms.filterArea; + const filterClamp = globalUniforms.filterClamp; + + filterArea[0] = state.destinationFrame.width; + filterArea[1] = state.destinationFrame.height; + filterArea[2] = state.sourceFrame.x; + filterArea[3] = state.sourceFrame.y; + + filterClamp[0] = 0.5 / state.resolution / state.destinationFrame.width; + filterClamp[1] = 0.5 / state.resolution / state.destinationFrame.height; + filterClamp[2] = (state.sourceFrame.width - 0.5) / state.resolution / state.destinationFrame.width; + filterClamp[3] = (state.sourceFrame.height - 0.5) / state.resolution / state.destinationFrame.height; + } + + this.globalUniforms.update(); + + const lastState = filterStack[filterStack.length - 1]; + + if (filters.length === 1) + { + filters[0].apply(this, state.renderTexture, lastState.renderTexture, false, state); + + this.returnFilterTexture(state.renderTexture); + } + else + { + let flip = state.renderTexture; + let flop = this.getPotFilterTexture( + flip.width, + flip.height, + state.resolution + ); + + flop.filterFrame = flip.filterFrame; + + let i = 0; + + for (i = 0; i < filters.length - 1; ++i) + { + filters[i].apply(this, flip, flop, true, state); + + const t = flip; + + flip = flop; + flop = t; + } + + filters[i].apply(this, flip, lastState.renderTexture, false, state); + + this.returnFilterTexture(flip); + this.returnFilterTexture(flop); + } + + this.statePool.push(state); + } + + /** + * Draws a filter. + * + * @param {PIXI.Filter} filter - The filter to draw. + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + * @param {boolean} clear - Should the output be cleared before rendering to it + */ + applyFilter(filter, input, output, clear) + { + const renderer = this.renderer; + + renderer.renderTexture.bind(output, output ? output.filterFrame : null); + + if (clear) + { + // gl.disable(gl.SCISSOR_TEST); + renderer.renderTexture.clear(); + // gl.enable(gl.SCISSOR_TEST); + } + + // set the uniforms.. + filter.uniforms.uSampler = input; + filter.uniforms.filterGlobals = this.globalUniforms; + + // TODO make it so that the order of this does not matter.. + // because it does at the moment cos of global uniforms. + // they need to get resynced + + renderer.state.setState(filter.state); + renderer.shader.bind(filter); + + if (filter.legacy) + { + this.quadUv.map(input._frame, input.filterFrame); + + renderer.geometry.bind(this.quadUv); + renderer.geometry.draw(DRAW_MODES.TRIANGLES); + } + else + { + renderer.geometry.bind(this.quad); + renderer.geometry.draw(DRAW_MODES.TRIANGLE_STRIP); + } + } + + /** + * Calculates the mapped matrix. + * + * TODO playing around here.. this is temporary - (will end up in the shader) + * this returns a matrix that will normalise map filter cords in the filter to screen space + * + * @param {PIXI.Matrix} outputMatrix - the matrix to output to. + * @return {PIXI.Matrix} The mapped matrix. + */ + calculateScreenSpaceMatrix(outputMatrix) + { + const currentState = this.activeState; + + return filterTransforms.calculateScreenSpaceMatrix( + outputMatrix, + currentState.sourceFrame, + currentState.destinationFrame + ); + } + + /** + * This will map the filter coord so that a texture can be used based on the transform of a sprite + * + * @param {PIXI.Matrix} outputMatrix - The matrix to output to. + * @param {PIXI.Sprite} sprite - The sprite to map to. + * @return {PIXI.Matrix} The mapped matrix. + */ + calculateSpriteMatrix(outputMatrix, sprite) + { + const currentState = this.activeState; + + return filterTransforms.calculateSpriteMatrix( + outputMatrix, + currentState.sourceFrame, + currentState.destinationFrame, + sprite + ); + } + + /** + * Destroys this Filter System. + * + * @param {boolean} [contextLost=false] context was lost, do not free shaders + * + */ + destroy(contextLost = false) + { + if (!contextLost) + { + this.emptyPool(); + } + else + { + this.texturePool = {}; + } + } + + /** + * Gets a Power-of-Two render texture. + * + * TODO move to a seperate class could be on renderer? + * also - could cause issue with multiple contexts? + * + * @private + * @param {WebGLRenderingContext} gl - The webgl rendering context + * @param {number} minWidth - The minimum width of the render target. + * @param {number} minHeight - The minimum height of the render target. + * @param {number} resolution - The resolution of the render target. + * @return {PIXI.RenderTarget} The new render target. + */ + getPotFilterTexture(minWidth, minHeight, resolution) + { + minWidth = bitTwiddle.nextPow2(minWidth); + minHeight = bitTwiddle.nextPow2(minHeight); + resolution = resolution || 1; + + const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + + if (!this.texturePool[key]) + { + this.texturePool[key] = []; + } + + let renderTexture = this.texturePool[key].pop(); + + if (!renderTexture) + { + // temporary bypass cache.. + // internally - this will cause a texture to be bound.. + renderTexture = RenderTexture.create({ + width: minWidth, + height: minHeight, + resolution, + }); + } + + return renderTexture; + } + + /** + * Gets extra render texture to use inside current filter + * + * @param {number} resolution resolution of the renderTexture + * @returns {PIXI.RenderTexture} + */ + getFilterTexture(resolution) + { + const rt = this.activeState.renderTexture; + + const filterTexture = this.getPotFilterTexture(rt.width, rt.height, resolution || rt.baseTexture.resolution); + + filterTexture.filterFrame = rt.filterFrame; + + return filterTexture; + } + + /** + * Frees a render texture back into the pool. + * + * @param {PIXI.RenderTarget} renderTexture - The renderTarget to free + */ + returnFilterTexture(renderTexture) + { + renderTexture.filterFrame = null; + + const base = renderTexture.baseTexture; + + const minWidth = base.width; + const minHeight = base.height; + + const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + + this.texturePool[key].push(renderTexture); + } + + /** + * Empties the texture pool. + * + */ + emptyPool() + { + for (const i in this.texturePool) + { + const textures = this.texturePool[i]; + + if (textures) + { + for (let j = 0; j < textures.length; j++) + { + textures[j].destroy(true); + } + } + } + + this.texturePool = {}; + } +} diff --git a/packages/core/src/filters/defaultFilter.frag b/packages/core/src/filters/defaultFilter.frag new file mode 100644 index 0000000..a14c1be --- /dev/null +++ b/packages/core/src/filters/defaultFilter.frag @@ -0,0 +1,22 @@ +varying vec2 vTextureCoord; +varying vec2 vFilterCoord; + +uniform sampler2D uSampler; +uniform sampler2D filterSampler; + +void main(void){ + vec4 masky = texture2D(filterSampler, vFilterCoord); + vec4 sample = texture2D(uSampler, vTextureCoord); + vec4 color; + if(mod(vFilterCoord.x, 1.0) > 0.5) + { + color = vec4(1.0, 0.0, 0.0, 1.0); + } + else + { + color = vec4(0.0, 1.0, 0.0, 1.0); + } + // gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0); + gl_FragColor = mix(sample, masky, 0.5); + gl_FragColor *= sample.a; +} diff --git a/packages/core/src/filters/defaultFilter.vert b/packages/core/src/filters/defaultFilter.vert new file mode 100644 index 0000000..e1febca --- /dev/null +++ b/packages/core/src/filters/defaultFilter.vert @@ -0,0 +1,14 @@ +attribute vec2 aVertexPosition; +attribute vec2 aTextureCoord; + +uniform mat3 projectionMatrix; +uniform mat3 filterMatrix; + +varying vec2 vTextureCoord; +varying vec2 vFilterCoord; + +void main(void){ + gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); + vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy; + vTextureCoord = aTextureCoord ; +} diff --git a/packages/core/src/filters/filterTransforms.js b/packages/core/src/filters/filterTransforms.js new file mode 100644 index 0000000..87a3cd6 --- /dev/null +++ b/packages/core/src/filters/filterTransforms.js @@ -0,0 +1,50 @@ +import { Matrix } from '@pixi/math'; + +/** + * Calculates the mapped matrix + * @param filterArea {Rectangle} The filter area + * @param sprite {Sprite} the target sprite + * @param outputMatrix {Matrix} @alvin + * @private + */ +// this returns a matrix that will normalise map filter cords in the filter to screen space +export function calculateScreenSpaceMatrix(outputMatrix, filterArea, textureSize) +{ + // TODO unwrap? + const mappedMatrix = outputMatrix.identity(); + + mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); + + mappedMatrix.scale(textureSize.width, textureSize.height); + + return mappedMatrix; +} + +export function calculateNormalizedScreenSpaceMatrix(outputMatrix, filterArea, textureSize) +{ + const mappedMatrix = outputMatrix.identity(); + + mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); + + const translateScaleX = (textureSize.width / filterArea.width); + const translateScaleY = (textureSize.height / filterArea.height); + + mappedMatrix.scale(translateScaleX, translateScaleY); + + return mappedMatrix; +} + +// this will map the filter coord so that a texture can be used based on the transform of a sprite +export function calculateSpriteMatrix(outputMatrix, filterArea, textureSize, sprite) +{ + const orig = sprite._texture.orig; + const mappedMatrix = outputMatrix.set(textureSize.width, 0, 0, textureSize.height, filterArea.x, filterArea.y); + const worldTransform = sprite.worldTransform.copyTo(Matrix.TEMP_MATRIX); + + worldTransform.invert(); + mappedMatrix.prepend(worldTransform); + mappedMatrix.scale(1.0 / orig.width, 1.0 / orig.height); + mappedMatrix.translate(sprite.anchor.x, sprite.anchor.y); + + return mappedMatrix; +} diff --git a/packages/core/src/filters/spriteMask/SpriteMaskFilter.js b/packages/core/src/filters/spriteMask/SpriteMaskFilter.js new file mode 100644 index 0000000..91697b7 --- /dev/null +++ b/packages/core/src/filters/spriteMask/SpriteMaskFilter.js @@ -0,0 +1,63 @@ +import Filter from '../Filter'; +import { Matrix } from '@pixi/math'; +import vertex from './spriteMaskFilter.vert'; +import fragment from './spriteMaskFilter.frag'; +import { default as TextureMatrix } from '../../textures/TextureMatrix'; + +/** + * The SpriteMaskFilter class + * + * @class + * @extends PIXI.Filter + * @memberof PIXI + */ +export default class SpriteMaskFilter extends Filter +{ + /** + * @param {PIXI.Sprite} sprite - the target sprite + */ + constructor(sprite) + { + const maskMatrix = new Matrix(); + + super(vertex, fragment); + + sprite.renderable = false; + + this.maskSprite = sprite; + this.maskMatrix = maskMatrix; + } + + /** + * Applies the filter + * + * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + */ + apply(filterManager, input, output) + { + const maskSprite = this.maskSprite; + const tex = this.maskSprite.texture; + + if (!tex.valid) + { + return; + } + if (!tex.transform) + { + // margin = 0.0, let it bleed a bit, shader code becomes easier + // assuming that atlas textures were made with 1-pixel padding + tex.transform = new TextureMatrix(tex, 0.0); + } + tex.transform.update(); + + this.uniforms.mask = tex; + this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite) + .prepend(tex.transform.mapCoord); + this.uniforms.alpha = maskSprite.worldAlpha; + this.uniforms.maskClamp = tex.transform.uClampFrame; + + filterManager.applyFilter(this, input, output); + } +} diff --git a/packages/core/src/filters/spriteMask/spriteMaskFilter.frag b/packages/core/src/filters/spriteMask/spriteMaskFilter.frag new file mode 100644 index 0000000..0e4aef8 --- /dev/null +++ b/packages/core/src/filters/spriteMask/spriteMaskFilter.frag @@ -0,0 +1,23 @@ +varying vec2 vMaskCoord; +varying vec2 vTextureCoord; + +uniform sampler2D uSampler; +uniform sampler2D mask; +uniform float alpha; +uniform vec4 maskClamp; + +void main(void) +{ + float clip = step(3.5, + step(maskClamp.x, vMaskCoord.x) + + step(maskClamp.y, vMaskCoord.y) + + step(vMaskCoord.x, maskClamp.z) + + step(vMaskCoord.y, maskClamp.w)); + + vec4 original = texture2D(uSampler, vTextureCoord); + vec4 masky = texture2D(mask, vMaskCoord); + + original *= (masky.r * masky.a * alpha * clip); + + gl_FragColor = original; +} diff --git a/packages/core/src/filters/spriteMask/spriteMaskFilter.vert b/packages/core/src/filters/spriteMask/spriteMaskFilter.vert new file mode 100644 index 0000000..d23a30c --- /dev/null +++ b/packages/core/src/filters/spriteMask/spriteMaskFilter.vert @@ -0,0 +1,16 @@ +attribute vec2 aVertexPosition; +attribute vec2 aTextureCoord; + +uniform mat3 projectionMatrix; +uniform mat3 otherMatrix; + +varying vec2 vMaskCoord; +varying vec2 vTextureCoord; + +void main(void) +{ + gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); + + vTextureCoord = aTextureCoord; + vMaskCoord = ( otherMatrix * vec3( aTextureCoord, 1.0) ).xy; +} diff --git a/packages/core/src/framebuffer/FrameBuffer.js b/packages/core/src/framebuffer/FrameBuffer.js new file mode 100644 index 0000000..84e9fcc --- /dev/null +++ b/packages/core/src/framebuffer/FrameBuffer.js @@ -0,0 +1,106 @@ +import Texture from '../textures/BaseTexture'; +import { FORMATS, TYPES } from '@pixi/constants'; + +/** + * Frame buffer + * @class + * @memberof PIXI + */ +export default class FrameBuffer +{ + constructor(width, height) + { + this.width = width || 100; + this.height = height || 100; + + this.stencil = false; + this.depth = false; + + this.dirtyId = 0; + this.dirtyFormat = 0; + this.dirtySize = 0; + + this.depthTexture = null; + this.colorTextures = []; + + this.glFrameBuffers = {}; + } + + get colorTexture() + { + return this.colorTextures[0]; + } + + addColorTexture(index, texture) + { + // TODO add some validation to the texture - same width / height etc? + this.colorTextures[index || 0] = texture || new Texture(null, { scaleMode: 0, + resolution: 1, + mipmap: false, + width: this.width, + height: this.height });// || new Texture(); + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + addDepthTexture(texture) + { + /* eslint-disable max-len */ + this.depthTexture = texture || new Texture(null, { scaleMode: 0, + resolution: 1, + width: this.width, + height: this.height, + mipmap: false, + format: FORMATS.DEPTH_COMPONENT, + type: TYPES.UNSIGNED_SHORT });// UNSIGNED_SHORT; + /* eslint-disable max-len */ + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + enableDepth() + { + this.depth = true; + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + enableStencil() + { + this.stencil = true; + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + resize(width, height) + { + if (width === this.width && height === this.height) return; + + this.width = width; + this.height = height; + + this.dirtyId++; + this.dirtySize++; + + for (let i = 0; i < this.colorTextures.length; i++) + { + this.colorTextures[i].setSize(width, height); + } + + if (this.depthTexture) + { + this.depthTexture.setSize(width, height); + } + } +} diff --git a/packages/core/src/framebuffer/FramebufferSystem.js b/packages/core/src/framebuffer/FramebufferSystem.js new file mode 100644 index 0000000..2cc2437 --- /dev/null +++ b/packages/core/src/framebuffer/FramebufferSystem.js @@ -0,0 +1,246 @@ +import System from '../System'; +import { Rectangle } from '@pixi/math'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class FramebufferSystem extends System +{ + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + contextChange() + { + this.gl = this.renderer.gl; + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + this.current = null; + this.viewport = new Rectangle(); + + this.drawBufferExtension = this.renderer.context.extensions.drawBuffers; + } + + bind(framebuffer, frame) + { + const gl = this.gl; + + this.current = framebuffer; + + if (framebuffer) + { + // TODO cacheing layer! + + const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID] || this.initFramebuffer(framebuffer); + + gl.bindFramebuffer(gl.FRAMEBUFFER, fbo.framebuffer); + // makesure all textures are unbound.. + + // now check for updates... + if (fbo.dirtyId !== framebuffer.dirtyId) + { + fbo.dirtyId = framebuffer.dirtyId; + + if (fbo.dirtyFormat !== framebuffer.dirtyFormat) + { + fbo.dirtyFormat = framebuffer.dirtyFormat; + this.updateFramebuffer(framebuffer); + } + else if (fbo.dirtySize !== framebuffer.dirtySize) + { + fbo.dirtySize = framebuffer.dirtySize; + this.resizeFramebuffer(framebuffer); + } + } + + for (let i = 0; i < framebuffer.colorTextures.length; i++) + { + if (framebuffer.colorTextures[i].texturePart) + { + this.renderer.texture.unbind(framebuffer.colorTextures[i].texture); + } + else + { + this.renderer.texture.unbind(framebuffer.colorTextures[i]); + } + } + + if (framebuffer.depthTexture) + { + this.renderer.texture.unbind(framebuffer.depthTexture); + } + + if (frame) + { + this.setViewport(frame.x, frame.y, frame.width, frame.height); + } + else + { + this.setViewport(0, 0, framebuffer.width, framebuffer.height); + } + } + else + { + gl.bindFramebuffer(gl.FRAMEBUFFER, null); + + if (frame) + { + this.setViewport(frame.x, frame.y, frame.width, frame.height); + } + else + { + this.setViewport(0, 0, this.renderer.width, this.renderer.height); + } + } + } + + setViewport(x, y, width, height) + { + const v = this.viewport; + + if (v.width !== width || v.height !== height || v.x !== x || v.y !== y) + { + v.x = x; + v.y = y; + v.width = width; + v.height = height; + + this.gl.viewport(x, y, width, height); + } + } + + get size() + { + if (this.current) + { + // TODO store temp + return { x: 0, y: 0, width: this.current.width, height: this.current.height }; + } + + return { x: 0, y: 0, width: this.renderer.width, height: this.renderer.height }; + } + + clear(r, g, b, a) + { + const gl = this.gl; + + // TODO clear color can be set only one right? + gl.clearColor(r, g, b, a); + gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); + } + + // private functions... + + initFramebuffer(framebuffer) + { + const gl = this.gl; + + // TODO - make this a class? + const fbo = { + framebuffer: gl.createFramebuffer(), + stencil: null, + dirtyId: 0, + dirtyFormat: 0, + dirtySize: 0, + }; + + framebuffer.glFrameBuffers[this.CONTEXT_UID] = fbo; + + return fbo; + } + + resizeFramebuffer(framebuffer) + { + const gl = this.gl; + + if (framebuffer.stencil || framebuffer.depth) + { + gl.bindRenderbuffer(gl.RENDERBUFFER, this.stencil); + gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); + } + } + + updateFramebuffer(framebuffer) + { + const gl = this.gl; + + const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID]; + + // bind the color texture + const colorTextures = framebuffer.colorTextures; + + let count = colorTextures.length; + + if (!this.drawBufferExtension) + { + count = Math.min(count, 1); + } + + const activeTextures = []; + + for (let i = 0; i < count; i++) + { + const texture = framebuffer.colorTextures[i]; + + if (texture.texturePart) + { + this.renderer.texture.bind(texture.texture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.COLOR_ATTACHMENT0 + i, + gl.TEXTURE_CUBE_MAP_NEGATIVE_X + texture.side, + texture.texture._glTextures[this.CONTEXT_UID].texture, + 0); + } + else + { + this.renderer.texture.bind(texture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.COLOR_ATTACHMENT0 + i, + gl.TEXTURE_2D, + texture._glTextures[this.CONTEXT_UID].texture, + 0); + } + + activeTextures.push(gl.COLOR_ATTACHMENT0 + i); + } + + if (this.drawBufferExtension && activeTextures.length > 1) + { + this.drawBufferExtension.drawBuffersWEBGL(activeTextures); + } + + if (framebuffer.depthTexture) + { + const depthTextureExt = this.renderer.context.extensions.depthTexture; + + if (depthTextureExt) + { + const depthTexture = framebuffer.depthTexture; + + this.renderer.texture.bind(depthTexture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.DEPTH_ATTACHMENT, + gl.TEXTURE_2D, + depthTexture._glTextures[this.CONTEXT_UID].texture, + 0); + } + } + + if (framebuffer.stencil || framebuffer.depth) + { + fbo.stencil = gl.createRenderbuffer(); + + gl.bindRenderbuffer(gl.RENDERBUFFER, fbo.stencil); + + // TODO.. this is depth AND stencil? + gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, fbo.stencil); + gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); + // fbo.enableStencil(); + } + } +} diff --git a/packages/core/src/geometry/GLBuffer.js b/packages/core/src/geometry/GLBuffer.js new file mode 100644 index 0000000..0477e8a --- /dev/null +++ b/packages/core/src/geometry/GLBuffer.js @@ -0,0 +1,9 @@ +export default class GLBuffer +{ + constructor(buffer) + { + this.buffer = buffer; + this.updateID = -1; + this.byteLength = -1; + } +} diff --git a/packages/core/src/geometry/GeometrySystem.js b/packages/core/src/geometry/GeometrySystem.js new file mode 100644 index 0000000..959f827 --- /dev/null +++ b/packages/core/src/geometry/GeometrySystem.js @@ -0,0 +1,388 @@ +import System from '../System'; +import GLBuffer from './GLBuffer'; + +const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class GeometrySystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this._activeGeometry = null; + this._activeVao = null; + + this.hasVao = true; + this.hasInstance = true; + } + + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + contextChange() + { + const gl = this.gl = this.renderer.gl; + + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + + // webgl2 + if (!gl.createVertexArray) + { + // webgl 1! + let nativeVaoExtension = this.renderer.context.extensions.vertexArrayObject; + + if (this.renderer.legacy) + { + nativeVaoExtension = null; + } + + if (nativeVaoExtension) + { + gl.createVertexArray = () => + nativeVaoExtension.createVertexArrayOES(); + + gl.bindVertexArray = (vao) => + nativeVaoExtension.bindVertexArrayOES(vao); + + gl.deleteVertexArray = (vao) => + nativeVaoExtension.deleteVertexArrayOES(vao); + } + else + { + this.hasVao = false; + gl.createVertexArray = () => + { + // empty + }; + + gl.bindVertexArray = () => + { + // empty + }; + + gl.deleteVertexArray = () => + { + // empty + }; + } + } + + if (!gl.vertexAttribDivisor) + { + const instanceExt = gl.getExtension('ANGLE_instanced_arrays'); + + if (instanceExt) + { + gl.vertexAttribDivisor = (a, b) => + instanceExt.vertexAttribDivisorANGLE(a, b); + + gl.drawElementsInstanced = (a, b, c, d, e) => + instanceExt.drawElementsInstancedANGLE(a, b, c, d, e); + + gl.drawArraysInstanced = (a, b, c, d) => + instanceExt.drawArraysInstancedANGLE(a, b, c, d); + } + else + { + this.hasInstance = false; + } + } + } + + /** + * Binds geometry so that is can be drawn. Creating a Vao if required + * @private + * @param {PIXI.Geometry} geometry instance of geometry to bind + */ + bind(geometry, shader) + { + shader = shader || this.renderer.shader.shader; + + const gl = this.gl; + + // not sure the best way to address this.. + // currently different shaders require different VAOs for the same geometry + // Still mulling over the best way to solve this one.. + // will likely need to modify the shader attribute locations at run time! + let vaos = geometry.glVertexArrayObjects[this.CONTEXT_UID]; + + if (!vaos) + { + geometry.glVertexArrayObjects[this.CONTEXT_UID] = vaos = {}; + } + + const vao = vaos[shader.program.id] || this.initGeometryVao(geometry, shader.program); + + this._activeGeometry = geometry; + + if (this._activeVao !== vao) + { + this._activeVao = vao; + + if (this.hasVao) + { + gl.bindVertexArray(vao); + } + else + { + this.activateVao(geometry, shader.program); + } + } + + // TODO - optimise later! + // don't need to loop through if nothing changed! + // maybe look to add an 'autoupdate' to geometry? + this.updateBuffers(); + } + + reset() + { + this.unbind(); + } + + updateBuffers() + { + const geometry = this._activeGeometry; + const gl = this.gl; + + for (let i = 0; i < geometry.buffers.length; i++) + { + const buffer = geometry.buffers[i]; + + const glBuffer = buffer._glBuffers[this.CONTEXT_UID]; + + if (buffer._updateID !== glBuffer.updateID) + { + glBuffer.updateID = buffer._updateID; + + // TODO can cache this on buffer! maybe added a getter / setter? + const type = buffer.index ? gl.ELEMENT_ARRAY_BUFFER : gl.ARRAY_BUFFER; + const drawType = buffer.static ? gl.STATIC_DRAW : gl.DYNAMIC_DRAW; + + gl.bindBuffer(type, glBuffer.buffer); + + if (glBuffer.byteLength >= buffer.data.byteLength) + { + // offset is always zero for now! + gl.bufferSubData(type, 0, buffer.data); + } + else + { + gl.bufferData(type, buffer.data, drawType); + } + } + } + } + + checkCompatability(geometry, program) + { + // geometry must have at least all the attributes that the shader requires. + const geometryAttributes = geometry.attributes; + const shaderAttributes = program.attributeData; + + for (const j in shaderAttributes) + { + if (!geometryAttributes[j]) + { + throw new Error(`shader and geometry incompatible, geometry missing the "${j}" attribute`); + } + } + } + + /** + * Creates a Vao with the same structure as the geometry and stores it on the geometry. + * @private + * @param {PIXI.Geometry} geometry instance of geometry to to generate Vao for + */ + initGeometryVao(geometry, program) + { + this.checkCompatability(geometry, program); + + const gl = this.gl; + const CONTEXT_UID = this.CONTEXT_UID; + const buffers = geometry.buffers; + const attributes = geometry.attributes; + + const tempStride = {}; + const tempStart = {}; + + for (const j in buffers) + { + tempStride[j] = 0; + tempStart[j] = 0; + } + + for (const j in attributes) + { + if (!attributes[j].size && program.attributeData[j]) + { + attributes[j].size = program.attributeData[j].size; + } + + tempStride[attributes[j].buffer] += attributes[j].size * byteSizeMap[attributes[j].type]; + } + + for (const j in attributes) + { + const attribute = attributes[j]; + const attribSize = attribute.size; + + if (attribute.stride === undefined) + { + if (tempStride[attribute.buffer] === attribSize * byteSizeMap[attribute.type]) + { + attribute.stride = 0; + } + else + { + attribute.stride = tempStride[attribute.buffer]; + } + } + + if (attribute.start === undefined) + { + attribute.start = tempStart[attribute.buffer]; + + tempStart[attribute.buffer] += attribSize * byteSizeMap[attribute.type]; + } + } + + // first update - and create the buffers! + // only create a gl buffer if it actually gets + for (let i = 0; i < buffers.length; i++) + { + const buffer = buffers[i]; + + if (!buffer._glBuffers[CONTEXT_UID]) + { + buffer._glBuffers[CONTEXT_UID] = new GLBuffer(gl.createBuffer()); + } + } + + // TODO - maybe make this a data object? + // lets wait to see if we need to first! + const vao = gl.createVertexArray(); + + gl.bindVertexArray(vao); + + this.activateVao(geometry, program); + + gl.bindVertexArray(null); + + // add it to the cache! + geometry.glVertexArrayObjects[this.CONTEXT_UID][program.id] = vao; + + return vao; + } + + activateVao(geometry, program) + { + const gl = this.gl; + const CONTEXT_UID = this.CONTEXT_UID; + const buffers = geometry.buffers; + const attributes = geometry.attributes; + + if (geometry.indexBuffer) + { + // first update the index buffer if we have one.. + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, geometry.indexBuffer._glBuffers[CONTEXT_UID].buffer); + } + + let lastBuffer = null; + + // add a new one! + for (const j in attributes) + { + const attribute = attributes[j]; + const buffer = buffers[attribute.buffer]; + const glBuffer = buffer._glBuffers[CONTEXT_UID]; + + if (program.attributeData[j]) + { + if (lastBuffer !== glBuffer) + { + gl.bindBuffer(gl.ARRAY_BUFFER, glBuffer.buffer); + + lastBuffer = glBuffer; + } + + const location = program.attributeData[j].location; + + // TODO introduce state again + // we can optimise this for older devices that have no VAOs + gl.enableVertexAttribArray(location); + + gl.vertexAttribPointer(location, + attribute.size, + attribute.type || gl.FLOAT, + attribute.normalized, + attribute.stride, + attribute.start); + + if (attribute.instance) + { + // TODO calculate instance count based of this... + if (this.hasInstance) + { + gl.vertexAttribDivisor(location, 1); + } + else + { + throw new Error('geometry error, GPU Instancing is not supported on this device'); + } + } + } + } + } + + draw(type, size, start, instanceCount) + { + const gl = this.gl; + const geometry = this._activeGeometry; + + // TODO.. this should not change so maybe cache the function? + + if (geometry.indexBuffer) + { + if (geometry.instanced) + { + /* eslint-disable max-len */ + gl.drawElementsInstanced(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2, instanceCount || 1); + /* eslint-enable max-len */ + } + else + { + gl.drawElements(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2); + } + } + else + if (geometry.instanced) + { + // TODO need a better way to calculate size.. + gl.drawArraysInstanced(type, start, size || geometry.getSize(), instanceCount || 1); + } + else + { + gl.drawArrays(type, start, size || geometry.getSize()); + } + + return this; + } + + unbind() + { + this.gl.bindVertexArray(null); + this._activeVao = null; + this._activeGeometry = null; + } +} diff --git a/packages/core/src/geometry/utils/setVertexAttribArrays.js b/packages/core/src/geometry/utils/setVertexAttribArrays.js new file mode 100644 index 0000000..23b6959 --- /dev/null +++ b/packages/core/src/geometry/utils/setVertexAttribArrays.js @@ -0,0 +1,54 @@ +// var GL_MAP = {}; + +/** + * @param gl {WebGLRenderingContext} The current WebGL context + * @param attribs {*} + * @param state {*} + */ +export default function setVertexAttribArrays(gl, attribs, state) +{ + let i; + + if (state) + { + const tempAttribState = state.tempAttribState; + const attribState = state.attribState; + + for (i = 0; i < tempAttribState.length; i++) + { + tempAttribState[i] = false; + } + + // set the new attribs + for (i = 0; i < attribs.length; i++) + { + tempAttribState[attribs[i].attribute.location] = true; + } + + for (i = 0; i < attribState.length; i++) + { + if (attribState[i] !== tempAttribState[i]) + { + attribState[i] = tempAttribState[i]; + + if (state.attribState[i]) + { + gl.enableVertexAttribArray(i); + } + else + { + gl.disableVertexAttribArray(i); + } + } + } + } + else + { + for (i = 0; i < attribs.length; i++) + { + const attrib = attribs[i]; + + gl.enableVertexAttribArray(attrib.attribute.location); + } + } +} diff --git a/packages/core/src/index.js b/packages/core/src/index.js index 58c456a..fd1f547 100644 --- a/packages/core/src/index.js +++ b/packages/core/src/index.js @@ -1,30 +1,30 @@ import * as resources from './textures/resources'; -import * as systems from './renderers/systems'; +import * as systems from './systems'; -export { resources }; export { systems }; +export { resources }; -export { default as Renderer } from './renderers/Renderer'; -export { default as AbstractRenderer } from './renderers/AbstractRenderer'; -export { default as FrameBuffer } from './textures/FrameBuffer'; +export { default as System } from './System'; +export { default as Renderer } from './Renderer'; +export { default as AbstractRenderer } from './AbstractRenderer'; +export { default as FrameBuffer } from './framebuffer/FrameBuffer'; export { default as CubeTexture } from './textures/CubeTexture'; export { default as BaseTexture } from './textures/BaseTexture'; export { default as Texture } from './textures/Texture'; export { default as TextureMatrix } from './textures/TextureMatrix'; -export { default as RenderTexture } from './textures/RenderTexture'; -export { default as BaseRenderTexture } from './textures/BaseRenderTexture'; +export { default as RenderTexture } from './renderTexture/RenderTexture'; +export { default as BaseRenderTexture } from './renderTexture/BaseRenderTexture'; export { default as TextureUvs } from './textures/TextureUvs'; -export { default as State } from './renderers/State'; -export { default as ObjectRenderer } from './renderers/utils/ObjectRenderer'; -export { default as Quad } from './renderers/utils/Quad'; -export { default as QuadUv } from './renderers/utils/QuadUv'; -export { default as checkMaxIfStatmentsInShader } from './renderers/utils/checkMaxIfStatmentsInShader'; +export { default as State } from './state/State'; +export { default as ObjectRenderer } from './batch/ObjectRenderer'; +export { default as Quad } from './utils/Quad'; +export { default as QuadUv } from './utils/QuadUv'; +export { default as checkMaxIfStatmentsInShader } from './shader/utils/checkMaxIfStatmentsInShader'; export { default as Shader } from './shader/Shader'; export { default as Program } from './shader/Program'; export { default as UniformGroup } from './shader/UniformGroup'; -export { default as SpriteMaskFilter } from './renderers/filters/spriteMask/SpriteMaskFilter'; -export { default as Filter } from './renderers/filters/Filter'; +export { default as SpriteMaskFilter } from './filters/spriteMask/SpriteMaskFilter'; +export { default as Filter } from './filters/Filter'; export { default as Attribute } from './geometry/Attribute'; export { default as Buffer } from './geometry/Buffer'; export { default as Geometry } from './geometry/Geometry'; - diff --git a/packages/core/src/mask/MaskSystem.js b/packages/core/src/mask/MaskSystem.js new file mode 100644 index 0000000..99174c2 --- /dev/null +++ b/packages/core/src/mask/MaskSystem.js @@ -0,0 +1,199 @@ +import System from '../System'; +import AlphaMaskFilter from '../filters/spriteMask/SpriteMaskFilter'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class MaskSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + // TODO - we don't need both! + this.scissor = false; + this.scissorData = null; + this.scissorRenderTarget = null; + + this.enableScissor = false; + + this.alphaMaskPool = []; + this.alphaMaskIndex = 0; + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.DisplayObject} target - Display Object to push the mask to + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + push(target, maskData) + { + // TODO the root check means scissor rect will not + // be used on render textures more info here: + // https://github.com/pixijs/pixi.js/pull/3545 + + if (maskData.texture) + { + this.pushSpriteMask(target, maskData); + } + else if (this.enableScissor + && !this.scissor + && this.renderer._activeRenderTarget.root + && !this.renderer.stencil.stencilMaskStack.length + && maskData.isFastRect()) + { + const matrix = maskData.worldTransform; + + let rot = Math.atan2(matrix.b, matrix.a); + + // use the nearest degree! + rot = Math.round(rot * (180 / Math.PI)); + + if (rot % 90) + { + this.pushStencilMask(maskData); + } + else + { + this.pushScissorMask(target, maskData); + } + } + else + { + this.pushStencilMask(maskData); + } + } + + /** + * Removes the last mask from the mask stack and doesn't return it. + * + * @param {PIXI.DisplayObject} target - Display Object to pop the mask from + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + pop(target, maskData) + { + if (maskData.texture) + { + this.popSpriteMask(target, maskData); + } + else if (this.enableScissor && !this.renderer.stencil.stencilMaskStack.length) + { + this.popScissorMask(target, maskData); + } + else + { + this.popStencilMask(target, maskData); + } + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.RenderTarget} target - Display Object to push the sprite mask to + * @param {PIXI.Sprite} maskData - Sprite to be used as the mask + */ + pushSpriteMask(target, maskData) + { + let alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; + + if (!alphaMaskFilter) + { + alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; + } + + alphaMaskFilter[0].resolution = this.renderer.resolution; + alphaMaskFilter[0].maskSprite = maskData; + + // TODO - may cause issues! + target.filterArea = maskData.getBounds(true); + + this.renderer.filterSystem.pushFilter(target, alphaMaskFilter); + + this.alphaMaskIndex++; + } + + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popSpriteMask() + { + this.renderer.filterSystem.popFilter(); + this.alphaMaskIndex--; + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + pushStencilMask(maskData) + { + this.renderer.batch.flush(); + this.renderer.stencil.pushStencil(maskData); + } + + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popStencilMask() + { + // this.renderer.currentRenderer.stop(); + this.renderer.stencil.popStencil(); + } + + /** + * + * @param {PIXI.DisplayObject} target - Display Object to push the mask to + * @param {PIXI.Graphics} maskData - The masking data. + */ + pushScissorMask(target, maskData) + { + maskData.renderable = true; + + const renderTarget = this.renderer._activeRenderTarget; + + const bounds = maskData.getBounds(); + + bounds.fit(renderTarget.size); + maskData.renderable = false; + + this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); + + const resolution = this.renderer.resolution; + + this.renderer.gl.scissor( + bounds.x * resolution, + (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, + bounds.width * resolution, + bounds.height * resolution + ); + + this.scissorRenderTarget = renderTarget; + this.scissorData = maskData; + this.scissor = true; + } + + /** + * + * + */ + popScissorMask() + { + this.scissorRenderTarget = null; + this.scissorData = null; + this.scissor = false; + + // must be scissor! + const gl = this.renderer.gl; + + gl.disable(gl.SCISSOR_TEST); + } +} diff --git a/packages/core/src/mask/StencilSystem.js b/packages/core/src/mask/StencilSystem.js new file mode 100644 index 0000000..62796a5 --- /dev/null +++ b/packages/core/src/mask/StencilSystem.js @@ -0,0 +1,133 @@ +import System from '../System'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class StencilSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + this.stencilMaskStack = []; + } + + /** + * Changes the mask stack that is used by this System. + * + * @param {PIXI.Graphics[]} stencilMaskStack - The mask stack + */ + setMaskStack(stencilMaskStack) + { + const gl = this.renderer.gl; + + if (stencilMaskStack.length !== this.stencilMaskStack.length) + { + if (stencilMaskStack.length === 0) + { + gl.disable(gl.STENCIL_TEST); + } + else + { + gl.enable(gl.STENCIL_TEST); + } + } + + this.stencilMaskStack = stencilMaskStack; + } + + /** + * Applies the Mask and adds it to the current stencil stack. @alvin + * + * @param {PIXI.Graphics} graphics - The mask + */ + pushStencil(graphics) + { + this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); + + // this.renderer._activeRenderTarget.attachStencilBuffer(); + + const gl = this.renderer.gl; + const prevMaskCount = this.stencilMaskStack.length; + + if (prevMaskCount === 0) + { + gl.enable(gl.STENCIL_TEST); + } + + this.stencilMaskStack.push(graphics); + + // Increment the refference stencil value where the new mask overlaps with the old ones. + gl.colorMask(false, false, false, false); + gl.stencilFunc(gl.EQUAL, prevMaskCount, this._getBitwiseMask()); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.INCR); + this.renderer.plugins.graphics.render(graphics); + + this._useCurrent(); + } + + /** + * Removes the last mask from the stencil stack. @alvin + */ + popStencil() + { + this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); + + const gl = this.renderer.gl; + const graphics = this.stencilMaskStack.pop(); + + if (this.stencilMaskStack.length === 0) + { + // the stack is empty! + gl.disable(gl.STENCIL_TEST); + gl.clear(gl.STENCIL_BUFFER_BIT); + gl.clearStencil(0); + } + else + { + // Decrement the refference stencil value where the popped mask overlaps with the other ones + gl.colorMask(false, false, false, false); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.DECR); + this.renderer.plugins.graphics.render(graphics); + + this._useCurrent(); + } + } + + /** + * Setup renderer to use the current stencil data. + */ + _useCurrent() + { + const gl = this.renderer.gl; + + gl.colorMask(true, true, true, true); + gl.stencilFunc(gl.EQUAL, this.stencilMaskStack.length, this._getBitwiseMask()); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP); + } + + /** + * Fill 1s equal to the number of acitve stencil masks. + * + * @return {number} The bitwise mask. + */ + _getBitwiseMask() + { + return (1 << this.stencilMaskStack.length) - 1; + } + + /** + * Destroys the mask stack. + * + */ + destroy() + { + super.destroy(this); + + this.stencilMaskStack = null; + } +} diff --git a/packages/core/src/projection/ProjectionSystem.js b/packages/core/src/projection/ProjectionSystem.js new file mode 100644 index 0000000..6e63b73 --- /dev/null +++ b/packages/core/src/projection/ProjectionSystem.js @@ -0,0 +1,73 @@ +import System from '../System'; +import { Matrix } from '@pixi/math'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class ProjectionSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.projectionMatrix = new Matrix(); + } + + update(destinationFrame, sourceFrame, resolution, root) + { + this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; + this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; + + this.calculateProjection(this.destinationFrame, this.sourceFrame, resolution, root); + + this.renderer.globalUniforms.uniforms.projectionMatrix = this.projectionMatrix; + this.renderer.globalUniforms.update(); + } + + /** + * Updates the projection matrix based on a projection frame (which is a rectangle) + * + * @param {Rectangle} destinationFrame - The destination frame. + * @param {Rectangle} sourceFrame - The source frame. + */ + calculateProjection(destinationFrame, sourceFrame, resolution, root) + { + const pm = this.projectionMatrix; + + // I don't think we will need this line.. + // pm.identity(); + + if (!root) + { + pm.a = (1 / destinationFrame.width * 2) * resolution; + pm.d = (1 / destinationFrame.height * 2) * resolution; + + pm.tx = -1 - (sourceFrame.x * pm.a); + pm.ty = -1 - (sourceFrame.y * pm.d); + } + else + { + pm.a = (1 / destinationFrame.width * 2) * resolution; + pm.d = (-1 / destinationFrame.height * 2) * resolution; + + pm.tx = -1 - (sourceFrame.x * pm.a); + pm.ty = 1 - (sourceFrame.y * pm.d); + } + } + + /** + * Sets the transform of the active render target to the given matrix + * + * @param {PIXI.Matrix} matrix - The transformation matrix + */ + setTransform()// matrix) + { + // this._activeRenderTarget.transform = matrix; + } +} diff --git a/packages/core/src/renderTexture/BaseRenderTexture.js b/packages/core/src/renderTexture/BaseRenderTexture.js new file mode 100644 index 0000000..88eb83b --- /dev/null +++ b/packages/core/src/renderTexture/BaseRenderTexture.js @@ -0,0 +1,137 @@ +import BaseTexture from '../textures/BaseTexture'; +import FrameBuffer from '../framebuffer/FrameBuffer'; + +/** + * A BaseRenderTexture is a special texture that allows any PixiJS display object to be rendered to it. + * + * __Hint__: All DisplayObjects (i.e. Sprites) that render to a BaseRenderTexture should be preloaded + * otherwise black rectangles will be drawn instead. + * + * A BaseRenderTexture takes a snapshot of any Display Object given to its render method. The position + * and rotation of the given Display Objects is ignored. For example: + * + * ```js + * let renderer = PIXI.autoDetectRenderer(1024, 1024, { view: canvas, ratio: 1 }); + * let baseRenderTexture = new PIXI.BaseRenderTexture(renderer, 800, 600); + * let sprite = PIXI.Sprite.fromImage("spinObj_01.png"); + * + * sprite.position.x = 800/2; + * sprite.position.y = 600/2; + * sprite.anchor.x = 0.5; + * sprite.anchor.y = 0.5; + * + * baseRenderTexture.render(sprite); + * ``` + * + * The Sprite in this case will be rendered using its local transform. To render this sprite at 0,0 + * you can clear the transform + * + * ```js + * + * sprite.setTransform() + * + * let baseRenderTexture = new PIXI.BaseRenderTexture(100, 100); + * let renderTexture = new PIXI.RenderTexture(baseRenderTexture); + * + * renderer.render(sprite, renderTexture); // Renders to center of RenderTexture + * ``` + * + * @class + * @extends PIXI.BaseTexture + * @memberof PIXI + */ +export default class BaseRenderTexture extends BaseTexture +{ + /** + * @param {object} [options] + * @param {number} [options.width=100] - The width of the base render texture + * @param {number} [options.height=100] - The height of the base render texture + * @param {PIXI.SCALE_MODES} [options.scaleMode] - See {@link PIXI.SCALE_MODES} for possible values + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the texture being generated + */ + constructor(options) + { + if (typeof options === 'number') + { + /* eslint-disable prefer-rest-params */ + // Backward compatibility of signature + const width = arguments[0]; + const height = arguments[1]; + const scaleMode = arguments[2]; + const resolution = arguments[3]; + + options = { width, height, scaleMode, resolution }; + /* eslint-enable prefer-rest-params */ + } + + super(null, options); + + const { width, height } = options || {}; + + // Set defaults + this.mipmap = false; + this.width = Math.ceil(width) || 100; + this.height = Math.ceil(height) || 100; + this.valid = true; + + /** + * A map of renderer IDs to webgl renderTargets + * + * @private + * @member {object} + */ + // this._glRenderTargets = {}; + + /** + * A reference to the canvas render target (we only need one as this can be shared across renderers) + * + * @private + * @member {object} + */ + this._canvasRenderTarget = null; + + this.clearColor = [0, 0, 0, 0]; + + this.frameBuffer = new FrameBuffer(width * this.resolution, height * this.resolution) + .addColorTexture(0, this); + + // TODO - could this be added the systems? + + /** + * The data structure for the stencil masks + * + * @member {PIXI.Graphics[]} + */ + this.stencilMaskStack = []; + + /** + * The data structure for the filters + * + * @member {PIXI.Graphics[]} + */ + this.filterStack = [{}]; + } + + /** + * Resizes the BaseRenderTexture. + * + * @param {number} width - The width to resize to. + * @param {number} height - The height to resize to. + */ + resize(width, height) + { + width = Math.ceil(width); + height = Math.ceil(height); + this.frameBuffer.resize(width * this.resolution, height * this.resolution); + } + + /** + * Destroys this texture + * + */ + destroy() + { + super.destroy(true); + this.renderer = null; + } +} diff --git a/packages/core/src/renderTexture/RenderTexture.js b/packages/core/src/renderTexture/RenderTexture.js new file mode 100644 index 0000000..f1ecf1f --- /dev/null +++ b/packages/core/src/renderTexture/RenderTexture.js @@ -0,0 +1,153 @@ +import BaseRenderTexture from './BaseRenderTexture'; +import Texture from '../textures/Texture'; + +/** + * A RenderTexture is a special texture that allows any PixiJS display object to be rendered to it. + * + * __Hint__: All DisplayObjects (i.e. Sprites) that render to a RenderTexture should be preloaded + * otherwise black rectangles will be drawn instead. + * + * A RenderTexture takes a snapshot of any Display Object given to its render method. For example: + * + * ```js + * let renderer = PIXI.autoDetectRenderer(1024, 1024, { view: canvas, ratio: 1 }); + * let renderTexture = PIXI.RenderTexture.create(800, 600); + * let sprite = PIXI.Sprite.fromImage("spinObj_01.png"); + * + * sprite.position.x = 800/2; + * sprite.position.y = 600/2; + * sprite.anchor.x = 0.5; + * sprite.anchor.y = 0.5; + * + * renderer.render(sprite, renderTexture); + * ``` + * + * The Sprite in this case will be rendered using its local transform. To render this sprite at 0,0 + * you can clear the transform + * + * ```js + * + * sprite.setTransform() + * + * let renderTexture = new PIXI.RenderTexture.create(100, 100); + * + * renderer.render(sprite, renderTexture); // Renders to center of RenderTexture + * ``` + * + * @class + * @extends PIXI.Texture + * @memberof PIXI + */ +export default class RenderTexture extends Texture +{ + /** + * @param {PIXI.BaseRenderTexture} baseRenderTexture - The renderer used for this RenderTexture + * @param {PIXI.Rectangle} [frame] - The rectangle frame of the texture to show + */ + constructor(baseRenderTexture, frame) + { + // support for legacy.. + let _legacyRenderer = null; + + if (!(baseRenderTexture instanceof BaseRenderTexture)) + { + /* eslint-disable prefer-rest-params, no-console */ + const width = arguments[1]; + const height = arguments[2]; + const scaleMode = arguments[3]; + const resolution = arguments[4]; + + // we have an old render texture.. + console.warn(`Please use RenderTexture.create(${width}, ${height}) instead of the ctor directly.`); + _legacyRenderer = arguments[0]; + /* eslint-enable prefer-rest-params, no-console */ + + frame = null; + baseRenderTexture = new BaseRenderTexture({ + width, + height, + scaleMode, + resolution, + }); + } + + /** + * The base texture object that this texture uses + * + * @member {BaseTexture} + */ + super(baseRenderTexture, frame); + + this.legacyRenderer = _legacyRenderer; + + /** + * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. + * + * @member {boolean} + */ + this.valid = true; + + /** + * FilterSystem temporary storage + * @private + * @member {PIXI.Rectangle} + */ + this.filterFrame = null; + + this._updateUvs(); + } + + /** + * Resizes the RenderTexture. + * + * @param {number} width - The width to resize to. + * @param {number} height - The height to resize to. + * @param {boolean} [resizeBaseTexture=true] - Should the baseTexture.width and height values be resized as well? + */ + resize(width, height, resizeBaseTexture = true) + { + width = Math.ceil(width); + height = Math.ceil(height); + + // TODO - could be not required.. + this.valid = (width > 0 && height > 0); + + this._frame.width = this.orig.width = width; + this._frame.height = this.orig.height = height; + + if (resizeBaseTexture) + { + this.baseTexture.resize(width, height); + } + + this._updateUvs(); + } + + /** + * A short hand way of creating a render texture. + * + * @param {object} [options] - Options + * @param {number} [options.width=100] - The width of the render texture + * @param {number} [options.height=100] - The height of the render texture + * @param {number} [options.scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the texture being generated + * @return {PIXI.RenderTexture} The new render texture + */ + static create(options) + { + // fallback, old-style: create(width, height, scaleMode, resolution) + if (typeof options === 'number') + { + /* eslint-disable prefer-rest-params */ + options = { + width: options, + height: arguments[1], + scaleMode: arguments[2], + resolution: arguments[3], + }; + /* eslint-enable prefer-rest-params */ + } + + return new RenderTexture(new BaseRenderTexture(options)); + } +} diff --git a/packages/core/src/renderTexture/RenderTextureSystem.js b/packages/core/src/renderTexture/RenderTextureSystem.js new file mode 100644 index 0000000..8d0ca61 --- /dev/null +++ b/packages/core/src/renderTexture/RenderTextureSystem.js @@ -0,0 +1,115 @@ +import System from '../System'; +import { Rectangle } from '@pixi/math'; + +const tempRect = new Rectangle(); + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class RenderTextureSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.clearColor = renderer._backgroundColorRgba; + + // TODO moe this property somewhere else! + this.defaultMaskStack = []; + this.defaultFilterStack = [{}]; + + // empty render texture? + this.renderTexture = null; + + this.destinationFrame = new Rectangle(); + } + + bind(renderTexture, sourceFrame, destinationFrame) + { + // TODO - do we want this?? + if (this.renderTexture === renderTexture) return; + this.renderTexture = renderTexture; + + const renderer = this.renderer; + + if (renderTexture) + { + const baseTexture = renderTexture.baseTexture; + + if (!destinationFrame) + { + tempRect.width = baseTexture.realWidth; + tempRect.height = baseTexture.realHeight; + + destinationFrame = tempRect; + } + + if (!sourceFrame) + { + sourceFrame = destinationFrame; + } + + this.renderer.framebuffer.bind(baseTexture.frameBuffer, destinationFrame); + + this.renderer.projection.update(destinationFrame, sourceFrame, baseTexture.resolution, false); + this.renderer.stencil.setMaskStack(baseTexture.stencilMaskStack); + } + else + { + // TODO these validation checks happen deeper down.. + // thing they can be avoided.. + if (!destinationFrame) + { + tempRect.width = renderer.width; + tempRect.height = renderer.height; + + destinationFrame = tempRect; + } + + if (!sourceFrame) + { + sourceFrame = destinationFrame; + } + + renderer.framebuffer.bind(null, destinationFrame); + + // TODO store this.. + this.renderer.projection.update(destinationFrame, sourceFrame, this.renderer.resolution, true); + this.renderer.stencil.setMaskStack(this.defaultMaskStack); + } + + this.destinationFrame.copyFrom(destinationFrame); + } + + /** + * Erases the render texture and fills the drawing area with a colour + * + * @param {number} [clearColor] - The colour + * @return {PIXI.Renderer} Returns itself. + */ + clear(clearColor) + { + if (this.renderTexture) + { + clearColor = clearColor || this.renderTexture.baseTexture.clearColor; + } + else + { + clearColor = clearColor || this.clearColor; + } + + this.renderer.framebuffer.clear(clearColor[0], clearColor[1], clearColor[2], clearColor[3]); + } + + resize()// screenWidth, screenHeight) + { + // resize the root only! + this.bind(null); + } +} diff --git a/packages/core/src/renderers/AbstractRenderer.js b/packages/core/src/renderers/AbstractRenderer.js deleted file mode 100644 index 928670f..0000000 --- a/packages/core/src/renderers/AbstractRenderer.js +++ /dev/null @@ -1,353 +0,0 @@ -import { hex2string, hex2rgb } from '@pixi/utils'; -import { Matrix, Rectangle } from '@pixi/math'; -import { RENDERER_TYPE } from '@pixi/constants'; -import { settings } from '@pixi/settings'; -import { Container } from '@pixi/display'; -import RenderTexture from '../textures/RenderTexture'; -import EventEmitter from 'eventemitter3'; - -const tempMatrix = new Matrix(); - -/** - * 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 EventEmitter - * @memberof PIXI - */ -export default class AbstractRenderer extends EventEmitter -{ - // eslint-disable-next-line valid-jsdoc - /** - * @param {string} system - The name of the system this renderer is for. - * @param {object} [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.transparent=false] - If the render view is transparent, default false - * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false - * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) - * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. The - * resolution of the renderer retina would be 2. - * @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 {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when rendering, - * stopping pixel interpolation. - */ - constructor(system, options, arg2, arg3) - { - super(); - - // Support for constructor(system, screenWidth, screenHeight, options) - if (typeof options === 'number') - { - options = Object.assign({ - width: options, - height: arg2 || settings.RENDER_OPTIONS.height, - }, arg3); - } - - // 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 = RENDERER_TYPE.UNKNOWN; - - /** - * Measurements of the screen. (0, 0, screenWidth, screenHeight) - * - * Its safe to use as filterArea or hitArea for 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 1 - */ - this.resolution = options.resolution || settings.RESOLUTION; - - /** - * Whether the render view is transparent - * - * @member {boolean} - */ - this.transparent = options.transparent; - - /** - * Whether css dimensions of canvas view should be resized to screen dimensions automatically - * - * @member {boolean} - */ - this.autoResize = options.autoResize || false; - - /** - * Tracks the blend modes useful for this renderer. - * - * @member {object} - */ - this.blendModes = null; - - /** - * 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; - - /** - * If true PixiJS will Math.floor() x/y values when rendering, stopping pixel interpolation. - * Handy for crisp pixel art and speed on legacy devices. - * - * @member {boolean} - */ - this.roundPixels = options.roundPixels; - - /** - * The background color as a number. - * - * @member {number} - * @private - */ - this._backgroundColor = 0x000000; - - /** - * The background color as an [R, G, B] array. - * - * @member {number[]} - * @private - */ - this._backgroundColorRgba = [0, 0, 0, 0]; - - /** - * The background color as a string. - * - * @member {string} - * @private - */ - this._backgroundColorString = '#000000'; - - this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter - - /** - * This temporary display object used as the parent of the currently being rendered item - * - * @member {PIXI.DisplayObject} - * @private - */ - this._tempDisplayObjectParent = new Container(); - - /** - * The last root object that the renderer tried to render. - * - * @member {PIXI.DisplayObject} - * @private - */ - this._lastObjectRendered = this._tempDisplayObjectParent; - - /** - * Collection of plugins. - * @readonly - * @member {object} - */ - this.plugins = {}; - } - - /** - * Initialize the plugins. - * - * @protected - * @param {object} staticMap - The dictionary of staticly saved plugins. - */ - initPlugins(staticMap) - { - 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() - { - return this.view.width; - } - - /** - * Same as view.height, actual number of pixels in the canvas by vertical - * - * @member {number} - * @readonly - * @default 600 - */ - get height() - { - return this.view.height; - } - - /** - * Resizes the screen and canvas to the specified width and height - * Canvas dimensions are multiplied by resolution - * - * @param {number} screenWidth - the new width of the screen - * @param {number} screenHeight - the new height of the screen - */ - resize(screenWidth, screenHeight) - { - this.screen.width = screenWidth; - this.screen.height = screenHeight; - - this.view.width = screenWidth * this.resolution; - this.view.height = screenHeight * this.resolution; - - if (this.autoResize) - { - this.view.style.width = `${screenWidth}px`; - this.view.style.height = `${screenHeight}px`; - } - } - - /** - * 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 {PIXI.DisplayObject} displayObject - The displayObject the object will be generated from - * @param {number} scaleMode - Should be one of the scaleMode consts - * @param {number} resolution - The resolution / device pixel ratio of the texture being generated - * @param {PIXI.Rectangle} [region] - The region of the displayObject, that shall be rendered, - * if no region is specified, defaults to the local bounds of the displayObject. - * @return {PIXI.Texture} a texture of the graphics object - */ - generateTexture(displayObject, scaleMode, resolution, region) - { - region = region || displayObject.getLocalBounds(); - - // 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(region.width | 0, region.height | 0, scaleMode, resolution); - - tempMatrix.tx = -region.x; - tempMatrix.ty = -region.y; - - this.render(displayObject, renderTexture, false, tempMatrix, true); - - return renderTexture; - } - - /** - * Removes everything from the renderer and optionally removes the Canvas DOM element. - * - * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. - */ - destroy(removeView) - { - 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); - } - - this.plugins = null; - - this.type = RENDERER_TYPE.UNKNOWN; - - this.view = null; - - this.screen = null; - - this.resolution = 0; - - this.transparent = false; - - this.autoResize = false; - - this.blendModes = null; - - this.options = null; - - this.preserveDrawingBuffer = false; - this.clearBeforeRender = false; - - this.roundPixels = false; - - this._backgroundColor = 0; - this._backgroundColorRgba = null; - this._backgroundColorString = null; - - this._tempDisplayObjectParent = null; - this._lastObjectRendered = null; - } - - /** - * The background color to fill if not transparent - * - * @member {number} - */ - get backgroundColor() - { - return this._backgroundColor; - } - - set backgroundColor(value) // eslint-disable-line require-jsdoc - { - this._backgroundColor = value; - this._backgroundColorString = hex2string(value); - hex2rgb(value, this._backgroundColorRgba); - } -} diff --git a/packages/core/src/renderers/Renderer.js b/packages/core/src/renderers/Renderer.js deleted file mode 100644 index a2020b1..0000000 --- a/packages/core/src/renderers/Renderer.js +++ /dev/null @@ -1,329 +0,0 @@ -import AbstractRenderer from './AbstractRenderer'; -import { sayHello } from '@pixi/utils'; -import MaskSystem from './systems/MaskSystem'; -import StencilSystem from './systems/StencilSystem'; -import FilterSystem from './systems/filter/FilterSystem'; -import FramebufferSystem from './systems/FramebufferSystem'; -import RenderTextureSystem from './systems/RenderTextureSystem'; -import TextureSystem from './systems/textures/TextureSystem'; -import ProjectionSystem from './systems/ProjectionSystem'; -import StateSystem from './systems/StateSystem'; -import GeometrySystem from './systems/geometry/GeometrySystem'; -import ShaderSystem from './systems/shader/ShaderSystem'; -import ContextSystem from './systems/ContextSystem'; -import BatchSystem from './systems/BatchSystem'; -import TextureGCSystem from './systems/textures/TextureGCSystem'; -import { RENDERER_TYPE } from '@pixi/constants'; -import UniformGroup from '../shader/UniformGroup'; -import { Matrix } from '@pixi/math'; -import Runner from 'mini-runner'; - -/** - * 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 Render works by automatically managing webGLBatchs. - * 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 :) - * - * @class - * @memberof PIXI - * @extends PIXI.AbstractRenderer - */ -export default class Renderer extends AbstractRenderer -{ - // eslint-disable-next-line valid-jsdoc - /** - * - * @param {object} [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.transparent=false] - If the render view is transparent, default false - * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false - * @param {boolean} [options.antialias=false] - sets antialias. If not available natively then FXAA - * antialiasing is used - * @param {boolean} [options.forceFXAA=false] - forces FXAA antialiasing to be used over native. - * FXAA is faster, but may not always look as great - * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. - * The resolution of the renderer retina would be 2. - * @param {boolean} [options.clearBeforeRender=true] - This sets if the renderer will clear - * the canvas or not before the new render pass. If you wish to set this to false, you *must* set - * preserveDrawingBuffer to `true`. - * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, - * enable this if you need to call toDataUrl on the webgl context. - * @param {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when - * rendering, stopping pixel interpolation. - * @param {number} [options.backgroundColor=0x000000] - The background color of the rendered area - * (shown if not transparent). - * @param {boolean} [options.legacy=false] - If true PixiJS will aim to ensure compatibility - * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. - * @param {string} [options.powerPreference] - Parameter passed to webgl context, set to "high-performance" - * for devices with dual graphics card - */ - constructor(options, arg2, arg3) - { - super('WebGL', options, arg2, arg3); - - /** - * The type of this renderer as a standardised const - * - * @member {number} - * @see PIXI.RENDERER_TYPE - */ - this.type = RENDERER_TYPE.WEBGL; - - // this will be set by the contextSystem (this.context) - this.gl = null; - this.CONTEXT_UID = 0; - this.legacy = !!options.legacy; - - // TODO legacy! - - // runners! - this.runners = { - destroy: new Runner('destroy'), - contextChange: new Runner('contextChange', 1), - reset: new Runner('reset'), - update: new Runner('update'), - postrender: new Runner('postrender'), - prerender: new Runner('prerender'), - resize: new Runner('resize', 2), - }; - - 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(GeometrySystem, 'geometry') - .addSystem(FramebufferSystem, 'framebuffer') - .addSystem(StencilSystem, 'stencil') - .addSystem(ProjectionSystem, 'projection') - .addSystem(TextureGCSystem, 'textureGC') - .addSystem(FilterSystem, 'filter') - .addSystem(RenderTextureSystem, 'renderTexture') - .addSystem(BatchSystem, 'batch'); - - this.initPlugins(Renderer.__plugins); - - /** - * The options passed in to create a new webgl context. - * - * @member {object} - * @private - */ - if (options.context) - { - this.context.initFromContext(options.context); - } - else - { - this.context.initFromOptions({ - alpha: this.transparent, - antialias: options.antialias, - premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', - stencil: true, - 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); - } - - addSystem(_class, name) - { - if (!name) - { - name = _class.name; - } - - // TODO - read name from class.name.. - - /* - if(name.includes('System')) - { - name = name.replace('System', ''); - name = name.charAt(0).toLowerCase() + name.slice(1); - } - */ - - const system = new _class(this); - - if (this[name]) - { - throw new Error(`Whoops! ${name} is already a manger`); - } - - this[name] = system; - - for (const i in this.runners) - { - this.runners[i].add(system); - } - - return this; - - /** - * 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. - */ - } - - /** - * Renders the object to its webGL view - * - * @param {PIXI.DisplayObject} displayObject - the object to be rendered - * @param {PIXI.RenderTexture} renderTexture - The render texture to render to. - * @param {boolean} [clear] - Should the canvas be cleared before the new render - * @param {PIXI.Transform} [transform] - A transform to apply to the render texture before rendering. - * @param {boolean} [skipUpdateTransform] - Should we skip the update transform pass? - */ - render(displayObject, renderTexture, clear, transform, skipUpdateTransform) - { - // can be handy to know! - this.renderingToScreen = !renderTexture; - - this.runners.prerender.run(); - this.emit('prerender'); - - // 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.parent; - - displayObject.parent = this._tempDisplayObjectParent; - displayObject.updateTransform(); - displayObject.parent = 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.run(); - - this.emit('postrender'); - } - - /** - * Resizes the webGL view to the specified width and height. - * - * @param {number} screenWidth - the new width of the screen - * @param {number} screenHeight - the new height of the screen - */ - resize(screenWidth, screenHeight) - { - AbstractRenderer.prototype.resize.call(this, screenWidth, screenHeight); - - this.runners.resize.run(screenWidth, screenHeight); - } - - /** - * Resets the WebGL state so you can render things however you fancy! - * - * @return {PIXI.Renderer} Returns itself. - */ - reset() - { - this.runners.reset.run(); - - return this; - } - - clear() - { - this.framebuffer.bind(); - this.framebuffer.clear(); - } - - /** - * Removes everything from the renderer (event listeners, spritebatch, etc...) - * - * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. - * See: https://github.com/pixijs/pixi.js/issues/2233 - */ - destroy(removeView) - { - this.runners.destroy.run(); - - // call base destroy - super.destroy(removeView); - - // TODO nullify all the managers.. - this.gl = null; - } - - /** - * 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 - * builds and excluding plugins. - * @name PIXI.Renderer#plugins - * @type {object} - * @readonly - * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. - * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. - * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. - * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. - */ - - /** - * Adds a plugin to the renderer. - * - * @method - * @param {string} pluginName - The name of the plugin. - * @param {Function} ctor - The constructor function or class for the plugin. - */ - static registerPlugin(pluginName, ctor) - { - Renderer.__plugins = Renderer.__plugins || {}; - Renderer.__plugins[pluginName] = ctor; - } -} diff --git a/packages/core/src/renderers/State.js b/packages/core/src/renderers/State.js deleted file mode 100644 index fa3f221..0000000 --- a/packages/core/src/renderers/State.js +++ /dev/null @@ -1,163 +0,0 @@ -/* eslint-disable max-len */ - -const BLEND = 0; -const OFFSET = 1; -const CULLING = 2; -const DEPTH_TEST = 3; -const WINDING = 4; - -/** - * This is a webGL state. It is passed The WebGL StateManager. - * Each mesh renderered may require webGL to be in a different state. - * For example you may want different blend mode or to enable polygon offsets - * - * @class - * @memberof PIXI - */ -export default class State -{ - /** - * - */ - constructor() - { - this.data = 0; - - this.blendMode = 0; - this.polygonOffset = 0; - - this.blend = true; - // this.depthTest = true; - } - - /** - * Activates blending of the computed fragment color values - * - * @member {boolean} - */ - get blend() - { - return !!(this.data & (1 << BLEND)); - } - - set blend(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << BLEND)) !== value) - { - this.data ^= (1 << BLEND); - } - } - - /** - * Activates adding an offset to depth values of polygon's fragments - * - * @member {boolean} - * @default false - */ - get offsets() - { - return !!(this.data & (1 << OFFSET)); - } - - set offsets(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << OFFSET)) !== value) - { - this.data ^= (1 << OFFSET); - } - } - - /** - * Activates culling of polygons. - * - * @member {boolean} - * @default false - */ - get culling() - { - return !!(this.data & (1 << CULLING)); - } - - set culling(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << CULLING)) !== value) - { - this.data ^= (1 << CULLING); - } - } - - /** - * Activates depth comparisons and updates to the depth buffer. - * - * @member {boolean} - * @default false - */ - get depthTest() - { - return !!(this.data & (1 << DEPTH_TEST)); - } - - set depthTest(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << DEPTH_TEST)) !== value) - { - this.data ^= (1 << DEPTH_TEST); - } - } - - /** - * Specifies whether or not front or back-facing polygons can be culled. - * @member {boolean} - * @default false - */ - get clockwiseFrontFace() - { - return !!(this.data & (1 << WINDING)); - } - - set clockwiseFrontFace(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << WINDING)) !== value) - { - this.data ^= (1 << WINDING); - } - } - - /** - * The blend mode to be applied when this state is set. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. - * Setting this mode to anything other than NO_BLEND will automatically switch blending on. - * - * @member {boolean} - * @default PIXI.BLEND_MODES.NORMAL - * @see PIXI.BLEND_MODES - */ - get blendMode() - { - return this._blendMode; - } - - set blendMode(value) // eslint-disable-line require-jsdoc - { - // 17 is NO BLEND - this.blend = (value !== 17); - this._blendMode = value; - } - - /** - * The polygon offset. Setting this property to anything other than 0 will automatically enable poygon offset fill. - * - * @member {number} - * @default 0 - */ - get polygonOffset() - { - return this._polygonOffset; - } - - set polygonOffset(value) // eslint-disable-line require-jsdoc - { - this.offsets = !!value; - this._polygonOffset = value; - } -} - diff --git a/packages/core/src/renderers/filters/Filter.js b/packages/core/src/renderers/filters/Filter.js deleted file mode 100644 index 2073b8d..0000000 --- a/packages/core/src/renderers/filters/Filter.js +++ /dev/null @@ -1,140 +0,0 @@ -import Shader from '../../shader/Shader'; -import Program from '../../shader/Program'; -import State from '../State'; -import { settings } from '@pixi/settings'; -// import extractUniformsFromSrc from './extractUniformsFromSrc'; -import defaultVertex from './defaultFilter.vert'; -import defaultFragment from './defaultFilter.frag'; - -// let math = require('../../math'); -/** - * @class - * @memberof PIXI - * @extends PIXI.Shader - */ -export default class Filter extends Shader -{ - /** - * @param {string} [vertexSrc] - The source of the vertex shader. - * @param {string} [fragmentSrc] - The source of the fragment shader. - * @param {object} [uniforms] - Custom uniforms to use to augment the built-in ones. - */ - constructor(vertexSrc, fragmentSrc, uniforms) - { - const program = Program.from(vertexSrc, fragmentSrc); - - super(program, uniforms); - - /** - * The padding of the filter. Some filters require extra space to breath such as a blur. - * Increasing this will add extra width and height to the bounds of the object that the - * filter is applied to. - * - * @member {number} - */ - this.padding = 0; - - /** - * The resolution of the filter. Setting this to be lower will lower the quality but - * increase the performance of the filter. - * - * @member {number} - */ - this.resolution = settings.RESOLUTION; - - /** - * If enabled is true the filter is applied, if false it will not. - * - * @member {boolean} - */ - this.enabled = true; - - /** - * If enabled, PixiJS will fit the filter area into boundaries for better performance. - * Switch it off if it does not work for specific shader. - * - * @member {boolean} - */ - this.autoFit = true; - - /** - * Legacy filters use position and uvs from attributes - * @member {boolean} - * @readonly - */ - this.legacy = !!this.program.attributeData.aTextureCoord; - - /** - * the webGL state the filter requires to render - * @member {PIXI.State} - */ - this.state = new State(); - } - - /** - * Applies the filter - * - * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from - * @param {PIXI.RenderTarget} input - The input render target. - * @param {PIXI.RenderTarget} output - The target to output to. - * @param {boolean} clear - Should the output be cleared before rendering to it - * @param {object} [currentState] - It's current state of filter. - * There are some useful properties in the currentState : - * target, filters, sourceFrame, destinationFrame, renderTarget, resolution - */ - apply(filterManager, input, output, clear, currentState, derp) // eslint-disable-line no-unused-vars - { - // do as you please! - - filterManager.applyFilter(this, input, output, clear, currentState, derp); - - // or just do a regular render.. - } - - /** - * Sets the blendmode of the filter - * - * @member {number} - * @default PIXI.BLEND_MODES.NORMAL - */ - get blendMode() - { - return this.state.blendMode; - } - - set blendMode(value) // eslint-disable-line require-jsdoc - { - this.state.blendMode = value; - } - - /** - * The default vertex shader source - * - * @static - * @constant - */ - static get defaultVertexSrc() - { - return defaultVertex; - } - - /** - * The default fragment shader source - * - * @static - * @constant - */ - static get defaultFragmentSrc() - { - return defaultFragment; - } -} - -/** - * Used for caching shader IDs - * - * @static - * @private - */ -Filter.SOURCE_KEY_MAP = {}; - diff --git a/packages/core/src/renderers/filters/defaultFilter.frag b/packages/core/src/renderers/filters/defaultFilter.frag deleted file mode 100644 index a14c1be..0000000 --- a/packages/core/src/renderers/filters/defaultFilter.frag +++ /dev/null @@ -1,22 +0,0 @@ -varying vec2 vTextureCoord; -varying vec2 vFilterCoord; - -uniform sampler2D uSampler; -uniform sampler2D filterSampler; - -void main(void){ - vec4 masky = texture2D(filterSampler, vFilterCoord); - vec4 sample = texture2D(uSampler, vTextureCoord); - vec4 color; - if(mod(vFilterCoord.x, 1.0) > 0.5) - { - color = vec4(1.0, 0.0, 0.0, 1.0); - } - else - { - color = vec4(0.0, 1.0, 0.0, 1.0); - } - // gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0); - gl_FragColor = mix(sample, masky, 0.5); - gl_FragColor *= sample.a; -} diff --git a/packages/core/src/renderers/filters/defaultFilter.vert b/packages/core/src/renderers/filters/defaultFilter.vert deleted file mode 100644 index e1febca..0000000 --- a/packages/core/src/renderers/filters/defaultFilter.vert +++ /dev/null @@ -1,14 +0,0 @@ -attribute vec2 aVertexPosition; -attribute vec2 aTextureCoord; - -uniform mat3 projectionMatrix; -uniform mat3 filterMatrix; - -varying vec2 vTextureCoord; -varying vec2 vFilterCoord; - -void main(void){ - gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); - vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy; - vTextureCoord = aTextureCoord ; -} diff --git a/packages/core/src/renderers/filters/filterTransforms.js b/packages/core/src/renderers/filters/filterTransforms.js deleted file mode 100644 index 87a3cd6..0000000 --- a/packages/core/src/renderers/filters/filterTransforms.js +++ /dev/null @@ -1,50 +0,0 @@ -import { Matrix } from '@pixi/math'; - -/** - * Calculates the mapped matrix - * @param filterArea {Rectangle} The filter area - * @param sprite {Sprite} the target sprite - * @param outputMatrix {Matrix} @alvin - * @private - */ -// this returns a matrix that will normalise map filter cords in the filter to screen space -export function calculateScreenSpaceMatrix(outputMatrix, filterArea, textureSize) -{ - // TODO unwrap? - const mappedMatrix = outputMatrix.identity(); - - mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); - - mappedMatrix.scale(textureSize.width, textureSize.height); - - return mappedMatrix; -} - -export function calculateNormalizedScreenSpaceMatrix(outputMatrix, filterArea, textureSize) -{ - const mappedMatrix = outputMatrix.identity(); - - mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); - - const translateScaleX = (textureSize.width / filterArea.width); - const translateScaleY = (textureSize.height / filterArea.height); - - mappedMatrix.scale(translateScaleX, translateScaleY); - - return mappedMatrix; -} - -// this will map the filter coord so that a texture can be used based on the transform of a sprite -export function calculateSpriteMatrix(outputMatrix, filterArea, textureSize, sprite) -{ - const orig = sprite._texture.orig; - const mappedMatrix = outputMatrix.set(textureSize.width, 0, 0, textureSize.height, filterArea.x, filterArea.y); - const worldTransform = sprite.worldTransform.copyTo(Matrix.TEMP_MATRIX); - - worldTransform.invert(); - mappedMatrix.prepend(worldTransform); - mappedMatrix.scale(1.0 / orig.width, 1.0 / orig.height); - mappedMatrix.translate(sprite.anchor.x, sprite.anchor.y); - - return mappedMatrix; -} diff --git a/packages/core/src/renderers/filters/spriteMask/SpriteMaskFilter.js b/packages/core/src/renderers/filters/spriteMask/SpriteMaskFilter.js deleted file mode 100644 index d5a91a4..0000000 --- a/packages/core/src/renderers/filters/spriteMask/SpriteMaskFilter.js +++ /dev/null @@ -1,63 +0,0 @@ -import Filter from '../Filter'; -import { Matrix } from '@pixi/math'; -import vertex from './spriteMaskFilter.vert'; -import fragment from './spriteMaskFilter.frag'; -import { default as TextureMatrix } from '../../../textures/TextureMatrix'; - -/** - * The SpriteMaskFilter class - * - * @class - * @extends PIXI.Filter - * @memberof PIXI - */ -export default class SpriteMaskFilter extends Filter -{ - /** - * @param {PIXI.Sprite} sprite - the target sprite - */ - constructor(sprite) - { - const maskMatrix = new Matrix(); - - super(vertex, fragment); - - sprite.renderable = false; - - this.maskSprite = sprite; - this.maskMatrix = maskMatrix; - } - - /** - * Applies the filter - * - * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from - * @param {PIXI.RenderTarget} input - The input render target. - * @param {PIXI.RenderTarget} output - The target to output to. - */ - apply(filterManager, input, output) - { - const maskSprite = this.maskSprite; - const tex = this.maskSprite.texture; - - if (!tex.valid) - { - return; - } - if (!tex.transform) - { - // margin = 0.0, let it bleed a bit, shader code becomes easier - // assuming that atlas textures were made with 1-pixel padding - tex.transform = new TextureMatrix(tex, 0.0); - } - tex.transform.update(); - - this.uniforms.mask = tex; - this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite) - .prepend(tex.transform.mapCoord); - this.uniforms.alpha = maskSprite.worldAlpha; - this.uniforms.maskClamp = tex.transform.uClampFrame; - - filterManager.applyFilter(this, input, output); - } -} diff --git a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.frag b/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.frag deleted file mode 100644 index 0e4aef8..0000000 --- a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.frag +++ /dev/null @@ -1,23 +0,0 @@ -varying vec2 vMaskCoord; -varying vec2 vTextureCoord; - -uniform sampler2D uSampler; -uniform sampler2D mask; -uniform float alpha; -uniform vec4 maskClamp; - -void main(void) -{ - float clip = step(3.5, - step(maskClamp.x, vMaskCoord.x) + - step(maskClamp.y, vMaskCoord.y) + - step(vMaskCoord.x, maskClamp.z) + - step(vMaskCoord.y, maskClamp.w)); - - vec4 original = texture2D(uSampler, vTextureCoord); - vec4 masky = texture2D(mask, vMaskCoord); - - original *= (masky.r * masky.a * alpha * clip); - - gl_FragColor = original; -} diff --git a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.vert b/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.vert deleted file mode 100644 index d23a30c..0000000 --- a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.vert +++ /dev/null @@ -1,16 +0,0 @@ -attribute vec2 aVertexPosition; -attribute vec2 aTextureCoord; - -uniform mat3 projectionMatrix; -uniform mat3 otherMatrix; - -varying vec2 vMaskCoord; -varying vec2 vTextureCoord; - -void main(void) -{ - gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); - - vTextureCoord = aTextureCoord; - vMaskCoord = ( otherMatrix * vec3( aTextureCoord, 1.0) ).xy; -} diff --git a/packages/core/src/renderers/systems/BatchSystem.js b/packages/core/src/renderers/systems/BatchSystem.js deleted file mode 100644 index 70e9518..0000000 --- a/packages/core/src/renderers/systems/BatchSystem.js +++ /dev/null @@ -1,68 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import ObjectRenderer from '../utils/ObjectRenderer'; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ - -export default class BatchSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this manager works for. - */ - constructor(renderer) - { - super(renderer); - - /** - * An empty renderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.emptyRenderer = new ObjectRenderer(renderer); - - /** - * The currently active ObjectRenderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.currentRenderer = this.emptyRenderer; - } - - /** - * Changes the current renderer to the one given in parameter - * - * @param {PIXI.ObjectRenderer} objectRenderer - The object renderer to use. - */ - setObjectRenderer(objectRenderer) - { - if (this.currentRenderer === objectRenderer) - { - return; - } - - this.currentRenderer.stop(); - this.currentRenderer = objectRenderer; - - this.renderer.state.setState(objectRenderer.state); - - this.currentRenderer.start(); - } - - /** - * This should be called if you wish to do some custom rendering - * It will basically render anything that may be batched up such as sprites - * - */ - flush() - { - this.setObjectRenderer(this.emptyRenderer); - } - - reset() - { - this.setObjectRenderer(this.emptyRenderer); - } -} diff --git a/packages/core/src/renderers/systems/ContextSystem.js b/packages/core/src/renderers/systems/ContextSystem.js deleted file mode 100644 index 43bf827..0000000 --- a/packages/core/src/renderers/systems/ContextSystem.js +++ /dev/null @@ -1,186 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { settings } from '@pixi/settings'; - -let CONTEXT_UID = 0; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class ContextSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this.webGLVersion = 1; - - this.handleContextLost = this.handleContextLost.bind(this); - this.handleContextRestored = this.handleContextRestored.bind(this); - - this.extensions = {}; - - renderer.view.addEventListener('webglcontextlost', this.handleContextLost, false); - renderer.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); - } - - get isLost() - { - return (!this.gl || this.gl.isContextLost()); - } - - contextChange(gl) - { - this.gl = gl; - - // restore a context if it was previously lost - if (gl.isContextLost() && gl.getExtension('WEBGL_lose_context')) - { - gl.getExtension('WEBGL_lose_context').restoreContext(); - } - } - - initFromContext(gl) - { - this.gl = gl; - this.validateContext(gl); - this.renderer.gl = gl; - this.renderer.CONTEXT_UID = CONTEXT_UID++; - this.renderer.runners.contextChange.run(gl); - } - - initFromOptions(options) - { - const gl = this.createContext(this.renderer.view, options); - - this.initFromContext(gl); - } - - /** - * Helper class to create a webGL Context - * - * @class - * @memberof PIXI.glCore - * @param canvas {HTMLCanvasElement} the canvas element that we will get the context from - * @param options {Object} An options object that gets passed in to the canvas element containing the context attributes, - * see https://developer.mozilla.org/en/docs/Web/API/HTMLCanvasElement/getContext for the options available - * @return {WebGLRenderingContext} the WebGL context - */ - createContext(canvas, options) - { - let gl; - - if (settings.PREFER_WEBGL_2) - { - gl = canvas.getContext('webgl2', options); - } - - if (gl) - { - this.webGLVersion = 2; - } - else - { - this.webGLVersion = 1; - - gl = canvas.getContext('webgl', options) - || canvas.getContext('experimental-webgl', options); - - if (!gl) - { - // fail, not able to get a context - throw new Error('This browser does not support webGL. Try using the canvas renderer'); - } - } - - this.gl = gl; - - this.getExtensions(); - - return gl; - } - - getExtensions() - { - // time to set up default etensions that pixi uses.. - const gl = this.gl; - const extensions = this.extensions; - - if (this.webGLVersion === 1) - { - extensions.drawBuffers = gl.getExtension('WEBGL_draw_buffers'); - extensions.depthTexture = gl.getExtension('WEBKIT_WEBGL_depth_texture'); - extensions.floatTexture = gl.getExtension('OES_texture_float'); - extensions.loseContext = gl.getExtension('WEBGL_lose_context'); - - extensions.vertexArrayObject = gl.getExtension('OES_vertex_array_object') - || gl.getExtension('MOZ_OES_vertex_array_object') - || gl.getExtension('WEBKIT_OES_vertex_array_object'); - } - - // we don't use any specific WebGL 2 ones yet! - } - - /** - * Handles a lost webgl context - * - * @private - * @param {WebGLContextEvent} event - The context lost event. - */ - handleContextLost(event) - { - event.preventDefault(); - } - - /** - * Handles a restored webgl context - * - * @private - */ - handleContextRestored() - { - this.renderer.runners.contextChange.run(this.gl); - } - - destroy() - { - const view = this.renderer.view; - - // remove listeners - view.removeEventListener('webglcontextlost', this.handleContextLost); - view.removeEventListener('webglcontextrestored', this.handleContextRestored); - - this.gl.useProgram(null); - - if (this.extensions.loseContext) - { - this.extensions.loseContext.loseContext(); - } - } - - postrender() - { - this.gl.flush(); - } - - validateContext(gl) - { - const attributes = gl.getContextAttributes(); - - // this is going to be fairly simple for now.. but at least we have room to grow! - if (!attributes.stencil) - { - /* eslint-disable max-len */ - - /* eslint-disable no-console */ - console.warn('Provided WebGL context does not have a stencil buffer, masks may not render correctly'); - /* eslint-enable no-console */ - - /* eslint-enable max-len */ - } - } -} diff --git a/packages/core/src/renderers/systems/FramebufferSystem.js b/packages/core/src/renderers/systems/FramebufferSystem.js deleted file mode 100644 index df7902b..0000000 --- a/packages/core/src/renderers/systems/FramebufferSystem.js +++ /dev/null @@ -1,246 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { Rectangle } from '@pixi/math'; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class FramebufferSystem extends WebGLSystem -{ - /** - * Sets up the renderer context and necessary buffers. - * - * @private - */ - contextChange() - { - this.gl = this.renderer.gl; - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - this.current = null; - this.viewport = new Rectangle(); - - this.drawBufferExtension = this.renderer.context.extensions.drawBuffers; - } - - bind(framebuffer, frame) - { - const gl = this.gl; - - this.current = framebuffer; - - if (framebuffer) - { - // TODO cacheing layer! - - const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID] || this.initFramebuffer(framebuffer); - - gl.bindFramebuffer(gl.FRAMEBUFFER, fbo.framebuffer); - // makesure all textures are unbound.. - - // now check for updates... - if (fbo.dirtyId !== framebuffer.dirtyId) - { - fbo.dirtyId = framebuffer.dirtyId; - - if (fbo.dirtyFormat !== framebuffer.dirtyFormat) - { - fbo.dirtyFormat = framebuffer.dirtyFormat; - this.updateFramebuffer(framebuffer); - } - else if (fbo.dirtySize !== framebuffer.dirtySize) - { - fbo.dirtySize = framebuffer.dirtySize; - this.resizeFramebuffer(framebuffer); - } - } - - for (let i = 0; i < framebuffer.colorTextures.length; i++) - { - if (framebuffer.colorTextures[i].texturePart) - { - this.renderer.texture.unbind(framebuffer.colorTextures[i].texture); - } - else - { - this.renderer.texture.unbind(framebuffer.colorTextures[i]); - } - } - - if (framebuffer.depthTexture) - { - this.renderer.texture.unbind(framebuffer.depthTexture); - } - - if (frame) - { - this.setViewport(frame.x, frame.y, frame.width, frame.height); - } - else - { - this.setViewport(0, 0, framebuffer.width, framebuffer.height); - } - } - else - { - gl.bindFramebuffer(gl.FRAMEBUFFER, null); - - if (frame) - { - this.setViewport(frame.x, frame.y, frame.width, frame.height); - } - else - { - this.setViewport(0, 0, this.renderer.width, this.renderer.height); - } - } - } - - setViewport(x, y, width, height) - { - const v = this.viewport; - - if (v.width !== width || v.height !== height || v.x !== x || v.y !== y) - { - v.x = x; - v.y = y; - v.width = width; - v.height = height; - - this.gl.viewport(x, y, width, height); - } - } - - get size() - { - if (this.current) - { - // TODO store temp - return { x: 0, y: 0, width: this.current.width, height: this.current.height }; - } - - return { x: 0, y: 0, width: this.renderer.width, height: this.renderer.height }; - } - - clear(r, g, b, a) - { - const gl = this.gl; - - // TODO clear color can be set only one right? - gl.clearColor(r, g, b, a); - gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); - } - - // private functions... - - initFramebuffer(framebuffer) - { - const gl = this.gl; - - // TODO - make this a class? - const fbo = { - framebuffer: gl.createFramebuffer(), - stencil: null, - dirtyId: 0, - dirtyFormat: 0, - dirtySize: 0, - }; - - framebuffer.glFrameBuffers[this.CONTEXT_UID] = fbo; - - return fbo; - } - - resizeFramebuffer(framebuffer) - { - const gl = this.gl; - - if (framebuffer.stencil || framebuffer.depth) - { - gl.bindRenderbuffer(gl.RENDERBUFFER, this.stencil); - gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); - } - } - - updateFramebuffer(framebuffer) - { - const gl = this.gl; - - const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID]; - - // bind the color texture - const colorTextures = framebuffer.colorTextures; - - let count = colorTextures.length; - - if (!this.drawBufferExtension) - { - count = Math.min(count, 1); - } - - const activeTextures = []; - - for (let i = 0; i < count; i++) - { - const texture = framebuffer.colorTextures[i]; - - if (texture.texturePart) - { - this.renderer.texture.bind(texture.texture, 0); - - gl.framebufferTexture2D(gl.FRAMEBUFFER, - gl.COLOR_ATTACHMENT0 + i, - gl.TEXTURE_CUBE_MAP_NEGATIVE_X + texture.side, - texture.texture._glTextures[this.CONTEXT_UID].texture, - 0); - } - else - { - this.renderer.texture.bind(texture, 0); - - gl.framebufferTexture2D(gl.FRAMEBUFFER, - gl.COLOR_ATTACHMENT0 + i, - gl.TEXTURE_2D, - texture._glTextures[this.CONTEXT_UID].texture, - 0); - } - - activeTextures.push(gl.COLOR_ATTACHMENT0 + i); - } - - if (this.drawBufferExtension && activeTextures.length > 1) - { - this.drawBufferExtension.drawBuffersWEBGL(activeTextures); - } - - if (framebuffer.depthTexture) - { - const depthTextureExt = this.renderer.context.extensions.depthTexture; - - if (depthTextureExt) - { - const depthTexture = framebuffer.depthTexture; - - this.renderer.texture.bind(depthTexture, 0); - - gl.framebufferTexture2D(gl.FRAMEBUFFER, - gl.DEPTH_ATTACHMENT, - gl.TEXTURE_2D, - depthTexture._glTextures[this.CONTEXT_UID].texture, - 0); - } - } - - if (framebuffer.stencil || framebuffer.depth) - { - fbo.stencil = gl.createRenderbuffer(); - - gl.bindRenderbuffer(gl.RENDERBUFFER, fbo.stencil); - - // TODO.. this is depth AND stencil? - gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, fbo.stencil); - gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); - // fbo.enableStencil(); - } - } -} diff --git a/packages/core/src/renderers/systems/MaskSystem.js b/packages/core/src/renderers/systems/MaskSystem.js deleted file mode 100644 index d9c4b1a..0000000 --- a/packages/core/src/renderers/systems/MaskSystem.js +++ /dev/null @@ -1,199 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import AlphaMaskFilter from '../filters/spriteMask/SpriteMaskFilter'; - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class MaskSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - // TODO - we don't need both! - this.scissor = false; - this.scissorData = null; - this.scissorRenderTarget = null; - - this.enableScissor = false; - - this.alphaMaskPool = []; - this.alphaMaskIndex = 0; - } - - /** - * Applies the Mask and adds it to the current filter stack. - * - * @param {PIXI.DisplayObject} target - Display Object to push the mask to - * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. - */ - push(target, maskData) - { - // TODO the root check means scissor rect will not - // be used on render textures more info here: - // https://github.com/pixijs/pixi.js/pull/3545 - - if (maskData.texture) - { - this.pushSpriteMask(target, maskData); - } - else if (this.enableScissor - && !this.scissor - && this.renderer._activeRenderTarget.root - && !this.renderer.stencil.stencilMaskStack.length - && maskData.isFastRect()) - { - const matrix = maskData.worldTransform; - - let rot = Math.atan2(matrix.b, matrix.a); - - // use the nearest degree! - rot = Math.round(rot * (180 / Math.PI)); - - if (rot % 90) - { - this.pushStencilMask(maskData); - } - else - { - this.pushScissorMask(target, maskData); - } - } - else - { - this.pushStencilMask(maskData); - } - } - - /** - * Removes the last mask from the mask stack and doesn't return it. - * - * @param {PIXI.DisplayObject} target - Display Object to pop the mask from - * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. - */ - pop(target, maskData) - { - if (maskData.texture) - { - this.popSpriteMask(target, maskData); - } - else if (this.enableScissor && !this.renderer.stencil.stencilMaskStack.length) - { - this.popScissorMask(target, maskData); - } - else - { - this.popStencilMask(target, maskData); - } - } - - /** - * Applies the Mask and adds it to the current filter stack. - * - * @param {PIXI.RenderTarget} target - Display Object to push the sprite mask to - * @param {PIXI.Sprite} maskData - Sprite to be used as the mask - */ - pushSpriteMask(target, maskData) - { - let alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; - - if (!alphaMaskFilter) - { - alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; - } - - alphaMaskFilter[0].resolution = this.renderer.resolution; - alphaMaskFilter[0].maskSprite = maskData; - - // TODO - may cause issues! - target.filterArea = maskData.getBounds(true); - - this.renderer.filterSystem.pushFilter(target, alphaMaskFilter); - - this.alphaMaskIndex++; - } - - /** - * Removes the last filter from the filter stack and doesn't return it. - * - */ - popSpriteMask() - { - this.renderer.filterSystem.popFilter(); - this.alphaMaskIndex--; - } - - /** - * Applies the Mask and adds it to the current filter stack. - * - * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. - */ - pushStencilMask(maskData) - { - this.renderer.batch.flush(); - this.renderer.stencil.pushStencil(maskData); - } - - /** - * Removes the last filter from the filter stack and doesn't return it. - * - */ - popStencilMask() - { - // this.renderer.currentRenderer.stop(); - this.renderer.stencil.popStencil(); - } - - /** - * - * @param {PIXI.DisplayObject} target - Display Object to push the mask to - * @param {PIXI.Graphics} maskData - The masking data. - */ - pushScissorMask(target, maskData) - { - maskData.renderable = true; - - const renderTarget = this.renderer._activeRenderTarget; - - const bounds = maskData.getBounds(); - - bounds.fit(renderTarget.size); - maskData.renderable = false; - - this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); - - const resolution = this.renderer.resolution; - - this.renderer.gl.scissor( - bounds.x * resolution, - (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, - bounds.width * resolution, - bounds.height * resolution - ); - - this.scissorRenderTarget = renderTarget; - this.scissorData = maskData; - this.scissor = true; - } - - /** - * - * - */ - popScissorMask() - { - this.scissorRenderTarget = null; - this.scissorData = null; - this.scissor = false; - - // must be scissor! - const gl = this.renderer.gl; - - gl.disable(gl.SCISSOR_TEST); - } -} diff --git a/packages/core/src/renderers/systems/ProjectionSystem.js b/packages/core/src/renderers/systems/ProjectionSystem.js deleted file mode 100644 index ec5efdb..0000000 --- a/packages/core/src/renderers/systems/ProjectionSystem.js +++ /dev/null @@ -1,73 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { Matrix } from '@pixi/math'; - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ - -export default class ProjectionSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this.projectionMatrix = new Matrix(); - } - - update(destinationFrame, sourceFrame, resolution, root) - { - this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; - this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; - - this.calculateProjection(this.destinationFrame, this.sourceFrame, resolution, root); - - this.renderer.globalUniforms.uniforms.projectionMatrix = this.projectionMatrix; - this.renderer.globalUniforms.update(); - } - - /** - * Updates the projection matrix based on a projection frame (which is a rectangle) - * - * @param {Rectangle} destinationFrame - The destination frame. - * @param {Rectangle} sourceFrame - The source frame. - */ - calculateProjection(destinationFrame, sourceFrame, resolution, root) - { - const pm = this.projectionMatrix; - - // I don't think we will need this line.. - // pm.identity(); - - if (!root) - { - pm.a = (1 / destinationFrame.width * 2) * resolution; - pm.d = (1 / destinationFrame.height * 2) * resolution; - - pm.tx = -1 - (sourceFrame.x * pm.a); - pm.ty = -1 - (sourceFrame.y * pm.d); - } - else - { - pm.a = (1 / destinationFrame.width * 2) * resolution; - pm.d = (-1 / destinationFrame.height * 2) * resolution; - - pm.tx = -1 - (sourceFrame.x * pm.a); - pm.ty = 1 - (sourceFrame.y * pm.d); - } - } - - /** - * Sets the transform of the active render target to the given matrix - * - * @param {PIXI.Matrix} matrix - The transformation matrix - */ - setTransform()// matrix) - { - // this._activeRenderTarget.transform = matrix; - } -} diff --git a/packages/core/src/renderers/systems/RenderTextureSystem.js b/packages/core/src/renderers/systems/RenderTextureSystem.js deleted file mode 100644 index a133ef1..0000000 --- a/packages/core/src/renderers/systems/RenderTextureSystem.js +++ /dev/null @@ -1,115 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { Rectangle } from '@pixi/math'; - -const tempRect = new Rectangle(); - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ - -export default class RenderTextureSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this.clearColor = renderer._backgroundColorRgba; - - // TODO moe this property somewhere else! - this.defaultMaskStack = []; - this.defaultFilterStack = [{}]; - - // empty render texture? - this.renderTexture = null; - - this.destinationFrame = new Rectangle(); - } - - bind(renderTexture, sourceFrame, destinationFrame) - { - // TODO - do we want this?? - if (this.renderTexture === renderTexture) return; - this.renderTexture = renderTexture; - - const renderer = this.renderer; - - if (renderTexture) - { - const baseTexture = renderTexture.baseTexture; - - if (!destinationFrame) - { - tempRect.width = baseTexture.realWidth; - tempRect.height = baseTexture.realHeight; - - destinationFrame = tempRect; - } - - if (!sourceFrame) - { - sourceFrame = destinationFrame; - } - - this.renderer.framebuffer.bind(baseTexture.frameBuffer, destinationFrame); - - this.renderer.projection.update(destinationFrame, sourceFrame, baseTexture.resolution, false); - this.renderer.stencil.setMaskStack(baseTexture.stencilMaskStack); - } - else - { - // TODO these validation checks happen deeper down.. - // thing they can be avoided.. - if (!destinationFrame) - { - tempRect.width = renderer.width; - tempRect.height = renderer.height; - - destinationFrame = tempRect; - } - - if (!sourceFrame) - { - sourceFrame = destinationFrame; - } - - renderer.framebuffer.bind(null, destinationFrame); - - // TODO store this.. - this.renderer.projection.update(destinationFrame, sourceFrame, this.renderer.resolution, true); - this.renderer.stencil.setMaskStack(this.defaultMaskStack); - } - - this.destinationFrame.copyFrom(destinationFrame); - } - - /** - * Erases the render texture and fills the drawing area with a colour - * - * @param {number} [clearColor] - The colour - * @return {PIXI.Renderer} Returns itself. - */ - clear(clearColor) - { - if (this.renderTexture) - { - clearColor = clearColor || this.renderTexture.baseTexture.clearColor; - } - else - { - clearColor = clearColor || this.clearColor; - } - - this.renderer.framebuffer.clear(clearColor[0], clearColor[1], clearColor[2], clearColor[3]); - } - - resize()// screenWidth, screenHeight) - { - // resize the root only! - this.bind(null); - } -} diff --git a/packages/core/src/renderers/systems/StateSystem.js b/packages/core/src/renderers/systems/StateSystem.js deleted file mode 100755 index d7f4caf..0000000 --- a/packages/core/src/renderers/systems/StateSystem.js +++ /dev/null @@ -1,288 +0,0 @@ -import mapWebGLBlendModesToPixi from '../utils/mapWebGLBlendModesToPixi'; -import WebGLSystem from './WebGLSystem'; -import WebGLState from '../State'; - -const BLEND = 0; -const OFFSET = 1; -const CULLING = 2; -const DEPTH_TEST = 3; -const WINDING = 4; - -/** - * A WebGL state machines - * - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class StateSystem extends WebGLSystem -{ - /** - * @param {WebGLRenderingContext} gl - The current WebGL rendering context - */ - constructor(renderer) - { - super(renderer); - - this.gl = null; - - this.maxAttribs = null; - - // check we have vao.. - this.nativeVaoExtension = null; - - this.attribState = null; - - this.stateId = 0; - this.polygonOffset = 0; - this.blendMode = 17; - - this.map = []; - - // map functions for when we set state.. - this.map[BLEND] = this.setBlend; - this.map[OFFSET] = this.setOffset; - this.map[CULLING] = this.setCullFace; - this.map[DEPTH_TEST] = this.setDepthTest; - this.map[WINDING] = this.setFrontFace; - - this.checks = []; - - this.defaultState = new WebGLState(); - this.defaultState.blend = true; - this.defaultState.depth = true; - } - - contextChange(gl) - { - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; - - this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); - - // check we have vao.. - this.nativeVaoExtension = ( - gl.getExtension('OES_vertex_array_object') - || gl.getExtension('MOZ_OES_vertex_array_object') - || gl.getExtension('WEBKIT_OES_vertex_array_object') - ); - - this.attribState = { - tempAttribState: new Array(this.maxAttribs), - attribState: new Array(this.maxAttribs), - }; - - this.blendModes = mapWebGLBlendModesToPixi(gl); - - this.setState(this.defaultState); - - this.reset(); - } - - /** - * Sets the current state - * - * @param {*} state - The state to set. - */ - setState(state) - { - state = state || this.defaultState; - - // TODO maybe to an object check? ( this.state === state )? - if (this.stateId !== state.data) - { - let diff = this.stateId ^ state.data; - let i = 0; - - // order from least to most common - while (diff) - { - if (diff & 1) - { - // state change! - this.map[i].call(this, !!(state.data & (1 << i))); - } - - diff = diff >> 1; - i++; - } - - this.stateId = state.data; - } - - // based on the above settings we check for specific modes.. - // for example if blend is active we check and set the blend modes - // or of polygon offset is active we check the poly depth. - for (let i = 0; i < this.checks.length; i++) - { - this.checks[i](this, state); - } - } - - /** - * Enables or disabled blending. - * - * @param {boolean} value - Turn on or off webgl blending. - */ - setBlend(value) - { - this.updateCheck(StateSystem.checkBlendMode, value); - - this.gl[value ? 'enable' : 'disable'](this.gl.BLEND); - } - - /** - * Enables or disable polygon offset fill - * - * @param {boolean} value - Turn on or off webgl polygon offset testing. - */ - setOffset(value) - { - this.gl[value ? 'enable' : 'disable'](this.gl.POLYGON_OFFSET_FILL); - } - - /** - * Sets whether to enable or disable depth test. - * - * @param {boolean} value - Turn on or off webgl depth testing. - */ - setDepthTest(value) - { - this.gl[value ? 'enable' : 'disable'](this.gl.DEPTH_TEST); - } - - /** - * Sets whether to enable or disable cull face. - * - * @param {boolean} value - Turn on or off webgl cull face. - */ - setCullFace(value) - { - this.gl[value ? 'enable' : 'disable'](this.gl.CULL_FACE); - } - - /** - * Sets the gl front face. - * - * @param {boolean} value - true is clockwise and false is counter-clockwise - */ - setFrontFace(value) - { - this.gl.frontFace(this.gl[value ? 'CW' : 'CCW']); - } - - /** - * Sets the blend mode. - * - * @param {number} value - The blend mode to set to. - */ - setBlendMode(value) - { - if (value === this.blendMode) - { - return; - } - - this.blendMode = value; - this.gl.blendFunc(this.blendModes[value][0], this.blendModes[value][1]); - } - - /** - * Sets the polygon offset. - * - * @param {number} value - the polygon offset - * @param {number} scale - the polygon offset scale - */ - setPolygonOffset(value, scale) - { - this.gl.polygonOffset(value, scale); - } - - /** - * Disables all the vaos in use - * - */ - resetAttributes() - { - for (let i = 0; i < this.attribState.tempAttribState.length; i++) - { - this.attribState.tempAttribState[i] = 0; - } - - for (let i = 0; i < this.attribState.attribState.length; i++) - { - this.attribState.attribState[i] = 0; - } - - // im going to assume one is always active for performance reasons. - for (let i = 1; i < this.maxAttribs; i++) - { - this.gl.disableVertexAttribArray(i); - } - } - - // used - /** - * Resets all the logic and disables the vaos - */ - reset() - { - // unbind any VAO if they exist.. - if (this.nativeVaoExtension) - { - this.nativeVaoExtension.bindVertexArrayOES(null); - } - - // reset all attributes.. - this.resetAttributes(); - - this.gl.pixelStorei(this.gl.UNPACK_FLIP_Y_WEBGL, false); - - this.setBlendMode(0); - - // TODO? - // this.setState(this.defaultState); - } - - /** - * checks to see which updates should be checked based on which settings have been activated - * for example if blend is enabled then we shold check the blend modes each time the state is changed - * or if poygon fill is activated then we need to check if the polygone offset changes. - * The idea is that we only check what we have too. - * - * @param {Function} func the checking function to add or remove - * @param {boolean} value should the check function be added or removed. - */ - updateCheck(func, value) - { - const index = this.checks.indexOf(func); - - if (value && index === -1) - { - this.checks.push(func); - } - else if (!value && index !== -1) - { - this.checks.splice(index, 1); - } - } - - /** - * A private little wrapper function that we call to check the blend mode. - * - * @static - * @private - * @param {PIXI.StateSystem} System the System to perform the state check on - * @param {PIXI.State} state the state that the blendMode will pulled from - */ - static checkBlendMode(system, state) - { - system.setBlendMode(state.blendMode); - } - - // TODO - add polygon offset? -} diff --git a/packages/core/src/renderers/systems/StencilSystem.js b/packages/core/src/renderers/systems/StencilSystem.js deleted file mode 100644 index 5d538cb..0000000 --- a/packages/core/src/renderers/systems/StencilSystem.js +++ /dev/null @@ -1,133 +0,0 @@ -import WebGLSystem from './WebGLSystem'; - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class StencilSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - this.stencilMaskStack = []; - } - - /** - * Changes the mask stack that is used by this System. - * - * @param {PIXI.Graphics[]} stencilMaskStack - The mask stack - */ - setMaskStack(stencilMaskStack) - { - const gl = this.renderer.gl; - - if (stencilMaskStack.length !== this.stencilMaskStack.length) - { - if (stencilMaskStack.length === 0) - { - gl.disable(gl.STENCIL_TEST); - } - else - { - gl.enable(gl.STENCIL_TEST); - } - } - - this.stencilMaskStack = stencilMaskStack; - } - - /** - * Applies the Mask and adds it to the current stencil stack. @alvin - * - * @param {PIXI.Graphics} graphics - The mask - */ - pushStencil(graphics) - { - this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); - - // this.renderer._activeRenderTarget.attachStencilBuffer(); - - const gl = this.renderer.gl; - const prevMaskCount = this.stencilMaskStack.length; - - if (prevMaskCount === 0) - { - gl.enable(gl.STENCIL_TEST); - } - - this.stencilMaskStack.push(graphics); - - // Increment the refference stencil value where the new mask overlaps with the old ones. - gl.colorMask(false, false, false, false); - gl.stencilFunc(gl.EQUAL, prevMaskCount, this._getBitwiseMask()); - gl.stencilOp(gl.KEEP, gl.KEEP, gl.INCR); - this.renderer.plugins.graphics.render(graphics); - - this._useCurrent(); - } - - /** - * Removes the last mask from the stencil stack. @alvin - */ - popStencil() - { - this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); - - const gl = this.renderer.gl; - const graphics = this.stencilMaskStack.pop(); - - if (this.stencilMaskStack.length === 0) - { - // the stack is empty! - gl.disable(gl.STENCIL_TEST); - gl.clear(gl.STENCIL_BUFFER_BIT); - gl.clearStencil(0); - } - else - { - // Decrement the refference stencil value where the popped mask overlaps with the other ones - gl.colorMask(false, false, false, false); - gl.stencilOp(gl.KEEP, gl.KEEP, gl.DECR); - this.renderer.plugins.graphics.render(graphics); - - this._useCurrent(); - } - } - - /** - * Setup renderer to use the current stencil data. - */ - _useCurrent() - { - const gl = this.renderer.gl; - - gl.colorMask(true, true, true, true); - gl.stencilFunc(gl.EQUAL, this.stencilMaskStack.length, this._getBitwiseMask()); - gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP); - } - - /** - * Fill 1s equal to the number of acitve stencil masks. - * - * @return {number} The bitwise mask. - */ - _getBitwiseMask() - { - return (1 << this.stencilMaskStack.length) - 1; - } - - /** - * Destroys the mask stack. - * - */ - destroy() - { - super.destroy(this); - - this.stencilMaskStack = null; - } -} diff --git a/packages/core/src/renderers/systems/WebGLSystem.js b/packages/core/src/renderers/systems/WebGLSystem.js deleted file mode 100644 index d3d5b31..0000000 --- a/packages/core/src/renderers/systems/WebGLSystem.js +++ /dev/null @@ -1,41 +0,0 @@ -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this manager works for. - */ - constructor(renderer) - { - /** - * The renderer this manager works for. - * - * @member {PIXI.Renderer} - */ - this.renderer = renderer; - - this.renderer.runners.contextChange.add(this); - } - - /** - * Generic method called when there is a WebGL context change. - * - */ - contextChange() - { - // do some codes init! - } - - /** - * Generic destroy methods to be overridden by the subclass - * - */ - destroy() - { - this.renderer.runners.contextChange.remove(this); - this.renderer = null; - } -} diff --git a/packages/core/src/renderers/systems/filter/FilterSystem.js b/packages/core/src/renderers/systems/filter/FilterSystem.js deleted file mode 100644 index e615037..0000000 --- a/packages/core/src/renderers/systems/filter/FilterSystem.js +++ /dev/null @@ -1,431 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; - -import RenderTexture from '../../../textures/RenderTexture'; -import Quad from '../../utils/Quad'; -import QuadUv from '../../utils/QuadUv'; -import { Rectangle } from '@pixi/math'; -import * as filterTransforms from '../../filters/filterTransforms'; -import bitTwiddle from 'bit-twiddle'; -import UniformGroup from '../../../shader/UniformGroup'; -import { DRAW_MODES } from '../../../../../constants'; - -// -/** - * @ignore - * @class - */ -class FilterState -{ - /** - * - */ - constructor() - { - this.renderTexture = null; - this.sourceFrame = new Rectangle(); - this.destinationFrame = new Rectangle(); - this.filters = []; - this.target = null; - this.legacy = false; - this.resolution = 1; - } -} - -/** - * @class - * @memberof PIXI.systems - * @extends PIXI.systems.WebGLSystem - */ -export default class FilterSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - /** - * stores a bunch of PO2 textures used for filtering - * @type {Object} - */ - this.texturePool = {}; - - /** - * a pool for storing filter states, save us creating new ones each tick - * @type {Array} - */ - this.statePool = []; - - /** - * A very simple geometry used when drawing a filter effect to the screen - * @type {Quad} - */ - this.quad = new Quad(); - - this.quadUv = new QuadUv(); - - /** - * Temporary rect for maths - * @type {PIXI.Rectangle} - */ - this.tempRect = new Rectangle(); - - this.activeState = {}; - - /** - * this uniform group is attached to filter uniforms when used - * @type {UniformGroup} - */ - this.globalUniforms = new UniformGroup({ - sourceFrame: this.tempRect, - destinationFrame: this.tempRect, - - // legacy variables - filterArea: new Float32Array(4), - filterClamp: new Float32Array(4), - }, true); - } - - /** - * Adds a new filter to the System. - * - * @param {PIXI.DisplayObject} target - The target of the filter to render. - * @param {PIXI.Filter[]} filters - The filters to apply. - */ - push(target, filters) - { - const renderer = this.renderer; - const filterStack = this.renderer.renderTexture.defaultFilterStack; - const state = this.statePool.pop() || new FilterState(); - - let resolution = filters[0].resolution; - let padding = filters[0].padding; - let autoFit = filters[0].autoFit; - let legacy = filters[0].legacy; - - for (let i = 1; i < filters.length; i++) - { - const filter = filters[i]; - - // lets use the lowest resolution.. - resolution = Math.min(resolution, filter.resolution); - // and the largest amount of padding! - padding = Math.max(padding, filter.padding); - // only auto fit if all filters are autofit - autoFit = autoFit || filter.autoFit; - - legacy = legacy || filter.legacy; - } - - filterStack.push(state); - - state.resolution = resolution; - - state.legacy = legacy; - - // round to whole number based on resolution - // TODO move that to the shader too? - state.sourceFrame = target.filterArea || target.getBounds(true); - - state.sourceFrame.pad(padding); - - if (autoFit) - { - state.sourceFrame.fit(this.renderer.renderTexture.destinationFrame); - } - - state.sourceFrame.round(resolution); - - state.renderTexture = this.getPotFilterTexture(state.sourceFrame.width, state.sourceFrame.height, resolution); - state.filters = filters; - - state.destinationFrame.width = state.renderTexture.width; - state.destinationFrame.height = state.renderTexture.height; - - state.renderTexture.filterFrame = state.sourceFrame; - renderer.renderTexture.bind(state.renderTexture, state.sourceFrame);// /, state.destinationFrame); - renderer.renderTexture.clear(); - } - - /** - * Pops off the filter and applies it. - * - */ - pop() - { - const renderer = this.renderer; - const filterStack = renderer.renderTexture.defaultFilterStack; - const state = filterStack.pop(); - const filters = state.filters; - - this.activeState = state; - - const globalUniforms = this.globalUniforms.uniforms; - - globalUniforms.sourceFrame = state.sourceFrame; - globalUniforms.destinationFrame = state.destinationFrame; - globalUniforms.resolution = state.resolution; - - // only update the rect if its legacy.. - if (state.legacy) - { - const filterArea = globalUniforms.filterArea; - const filterClamp = globalUniforms.filterClamp; - - filterArea[0] = state.destinationFrame.width; - filterArea[1] = state.destinationFrame.height; - filterArea[2] = state.sourceFrame.x; - filterArea[3] = state.sourceFrame.y; - - filterClamp[0] = 0.5 / state.resolution / state.destinationFrame.width; - filterClamp[1] = 0.5 / state.resolution / state.destinationFrame.height; - filterClamp[2] = (state.sourceFrame.width - 0.5) / state.resolution / state.destinationFrame.width; - filterClamp[3] = (state.sourceFrame.height - 0.5) / state.resolution / state.destinationFrame.height; - } - - this.globalUniforms.update(); - - const lastState = filterStack[filterStack.length - 1]; - - if (filters.length === 1) - { - filters[0].apply(this, state.renderTexture, lastState.renderTexture, false, state); - - this.returnFilterTexture(state.renderTexture); - } - else - { - let flip = state.renderTexture; - let flop = this.getPotFilterTexture( - flip.width, - flip.height, - state.resolution - ); - - flop.filterFrame = flip.filterFrame; - - let i = 0; - - for (i = 0; i < filters.length - 1; ++i) - { - filters[i].apply(this, flip, flop, true, state); - - const t = flip; - - flip = flop; - flop = t; - } - - filters[i].apply(this, flip, lastState.renderTexture, false, state); - - this.returnFilterTexture(flip); - this.returnFilterTexture(flop); - } - - this.statePool.push(state); - } - - /** - * Draws a filter. - * - * @param {PIXI.Filter} filter - The filter to draw. - * @param {PIXI.RenderTarget} input - The input render target. - * @param {PIXI.RenderTarget} output - The target to output to. - * @param {boolean} clear - Should the output be cleared before rendering to it - */ - applyFilter(filter, input, output, clear) - { - const renderer = this.renderer; - - renderer.renderTexture.bind(output, output ? output.filterFrame : null); - - if (clear) - { - // gl.disable(gl.SCISSOR_TEST); - renderer.renderTexture.clear(); - // gl.enable(gl.SCISSOR_TEST); - } - - // set the uniforms.. - filter.uniforms.uSampler = input; - filter.uniforms.filterGlobals = this.globalUniforms; - - // TODO make it so that the order of this does not matter.. - // because it does at the moment cos of global uniforms. - // they need to get resynced - - renderer.state.setState(filter.state); - renderer.shader.bind(filter); - - if (filter.legacy) - { - this.quadUv.map(input._frame, input.filterFrame); - - renderer.geometry.bind(this.quadUv); - renderer.geometry.draw(DRAW_MODES.TRIANGLES); - } - else - { - renderer.geometry.bind(this.quad); - renderer.geometry.draw(DRAW_MODES.TRIANGLE_STRIP); - } - } - - /** - * Calculates the mapped matrix. - * - * TODO playing around here.. this is temporary - (will end up in the shader) - * this returns a matrix that will normalise map filter cords in the filter to screen space - * - * @param {PIXI.Matrix} outputMatrix - the matrix to output to. - * @return {PIXI.Matrix} The mapped matrix. - */ - calculateScreenSpaceMatrix(outputMatrix) - { - const currentState = this.activeState; - - return filterTransforms.calculateScreenSpaceMatrix( - outputMatrix, - currentState.sourceFrame, - currentState.destinationFrame - ); - } - - /** - * This will map the filter coord so that a texture can be used based on the transform of a sprite - * - * @param {PIXI.Matrix} outputMatrix - The matrix to output to. - * @param {PIXI.Sprite} sprite - The sprite to map to. - * @return {PIXI.Matrix} The mapped matrix. - */ - calculateSpriteMatrix(outputMatrix, sprite) - { - const currentState = this.activeState; - - return filterTransforms.calculateSpriteMatrix( - outputMatrix, - currentState.sourceFrame, - currentState.destinationFrame, - sprite - ); - } - - /** - * Destroys this Filter System. - * - * @param {boolean} [contextLost=false] context was lost, do not free shaders - * - */ - destroy(contextLost = false) - { - if (!contextLost) - { - this.emptyPool(); - } - else - { - this.texturePool = {}; - } - } - - /** - * Gets a Power-of-Two render texture. - * - * TODO move to a seperate class could be on renderer? - * also - could cause issue with multiple contexts? - * - * @private - * @param {WebGLRenderingContext} gl - The webgl rendering context - * @param {number} minWidth - The minimum width of the render target. - * @param {number} minHeight - The minimum height of the render target. - * @param {number} resolution - The resolution of the render target. - * @return {PIXI.RenderTarget} The new render target. - */ - getPotFilterTexture(minWidth, minHeight, resolution) - { - minWidth = bitTwiddle.nextPow2(minWidth); - minHeight = bitTwiddle.nextPow2(minHeight); - resolution = resolution || 1; - - const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); - - if (!this.texturePool[key]) - { - this.texturePool[key] = []; - } - - let renderTexture = this.texturePool[key].pop(); - - if (!renderTexture) - { - // temporary bypass cache.. - // internally - this will cause a texture to be bound.. - renderTexture = RenderTexture.create({ - width: minWidth, - height: minHeight, - resolution, - }); - } - - return renderTexture; - } - - /** - * Gets extra render texture to use inside current filter - * - * @param {number} resolution resolution of the renderTexture - * @returns {PIXI.RenderTexture} - */ - getFilterTexture(resolution) - { - const rt = this.activeState.renderTexture; - - const filterTexture = this.getPotFilterTexture(rt.width, rt.height, resolution || rt.baseTexture.resolution); - - filterTexture.filterFrame = rt.filterFrame; - - return filterTexture; - } - - /** - * Frees a render texture back into the pool. - * - * @param {PIXI.RenderTarget} renderTexture - The renderTarget to free - */ - returnFilterTexture(renderTexture) - { - renderTexture.filterFrame = null; - - const base = renderTexture.baseTexture; - - const minWidth = base.width; - const minHeight = base.height; - - const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); - - this.texturePool[key].push(renderTexture); - } - - /** - * Empties the texture pool. - * - */ - emptyPool() - { - for (const i in this.texturePool) - { - const textures = this.texturePool[i]; - - if (textures) - { - for (let j = 0; j < textures.length; j++) - { - textures[j].destroy(true); - } - } - } - - this.texturePool = {}; - } -} diff --git a/packages/core/src/renderers/systems/geometry/GLBuffer.js b/packages/core/src/renderers/systems/geometry/GLBuffer.js deleted file mode 100644 index 0477e8a..0000000 --- a/packages/core/src/renderers/systems/geometry/GLBuffer.js +++ /dev/null @@ -1,9 +0,0 @@ -export default class GLBuffer -{ - constructor(buffer) - { - this.buffer = buffer; - this.updateID = -1; - this.byteLength = -1; - } -} diff --git a/packages/core/src/renderers/systems/geometry/GeometrySystem.js b/packages/core/src/renderers/systems/geometry/GeometrySystem.js deleted file mode 100644 index 26c8e9e..0000000 --- a/packages/core/src/renderers/systems/geometry/GeometrySystem.js +++ /dev/null @@ -1,388 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; -import GLBuffer from './GLBuffer'; - -const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class GeometrySystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this._activeGeometry = null; - this._activeVao = null; - - this.hasVao = true; - this.hasInstance = true; - } - - /** - * Sets up the renderer context and necessary buffers. - * - * @private - */ - contextChange() - { - const gl = this.gl = this.renderer.gl; - - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - - // webgl2 - if (!gl.createVertexArray) - { - // webgl 1! - let nativeVaoExtension = this.renderer.context.extensions.vertexArrayObject; - - if (this.renderer.legacy) - { - nativeVaoExtension = null; - } - - if (nativeVaoExtension) - { - gl.createVertexArray = () => - nativeVaoExtension.createVertexArrayOES(); - - gl.bindVertexArray = (vao) => - nativeVaoExtension.bindVertexArrayOES(vao); - - gl.deleteVertexArray = (vao) => - nativeVaoExtension.deleteVertexArrayOES(vao); - } - else - { - this.hasVao = false; - gl.createVertexArray = () => - { - // empty - }; - - gl.bindVertexArray = () => - { - // empty - }; - - gl.deleteVertexArray = () => - { - // empty - }; - } - } - - if (!gl.vertexAttribDivisor) - { - const instanceExt = gl.getExtension('ANGLE_instanced_arrays'); - - if (instanceExt) - { - gl.vertexAttribDivisor = (a, b) => - instanceExt.vertexAttribDivisorANGLE(a, b); - - gl.drawElementsInstanced = (a, b, c, d, e) => - instanceExt.drawElementsInstancedANGLE(a, b, c, d, e); - - gl.drawArraysInstanced = (a, b, c, d) => - instanceExt.drawArraysInstancedANGLE(a, b, c, d); - } - else - { - this.hasInstance = false; - } - } - } - - /** - * Binds geometry so that is can be drawn. Creating a Vao if required - * @private - * @param {PIXI.Geometry} geometry instance of geometry to bind - */ - bind(geometry, shader) - { - shader = shader || this.renderer.shader.shader; - - const gl = this.gl; - - // not sure the best way to address this.. - // currently different shaders require different VAOs for the same geometry - // Still mulling over the best way to solve this one.. - // will likely need to modify the shader attribute locations at run time! - let vaos = geometry.glVertexArrayObjects[this.CONTEXT_UID]; - - if (!vaos) - { - geometry.glVertexArrayObjects[this.CONTEXT_UID] = vaos = {}; - } - - const vao = vaos[shader.program.id] || this.initGeometryVao(geometry, shader.program); - - this._activeGeometry = geometry; - - if (this._activeVao !== vao) - { - this._activeVao = vao; - - if (this.hasVao) - { - gl.bindVertexArray(vao); - } - else - { - this.activateVao(geometry, shader.program); - } - } - - // TODO - optimise later! - // don't need to loop through if nothing changed! - // maybe look to add an 'autoupdate' to geometry? - this.updateBuffers(); - } - - reset() - { - this.unbind(); - } - - updateBuffers() - { - const geometry = this._activeGeometry; - const gl = this.gl; - - for (let i = 0; i < geometry.buffers.length; i++) - { - const buffer = geometry.buffers[i]; - - const glBuffer = buffer._glBuffers[this.CONTEXT_UID]; - - if (buffer._updateID !== glBuffer.updateID) - { - glBuffer.updateID = buffer._updateID; - - // TODO can cache this on buffer! maybe added a getter / setter? - const type = buffer.index ? gl.ELEMENT_ARRAY_BUFFER : gl.ARRAY_BUFFER; - const drawType = buffer.static ? gl.STATIC_DRAW : gl.DYNAMIC_DRAW; - - gl.bindBuffer(type, glBuffer.buffer); - - if (glBuffer.byteLength >= buffer.data.byteLength) - { - // offset is always zero for now! - gl.bufferSubData(type, 0, buffer.data); - } - else - { - gl.bufferData(type, buffer.data, drawType); - } - } - } - } - - checkCompatability(geometry, program) - { - // geometry must have at least all the attributes that the shader requires. - const geometryAttributes = geometry.attributes; - const shaderAttributes = program.attributeData; - - for (const j in shaderAttributes) - { - if (!geometryAttributes[j]) - { - throw new Error(`shader and geometry incompatible, geometry missing the "${j}" attribute`); - } - } - } - - /** - * Creates a Vao with the same structure as the geometry and stores it on the geometry. - * @private - * @param {PIXI.Geometry} geometry instance of geometry to to generate Vao for - */ - initGeometryVao(geometry, program) - { - this.checkCompatability(geometry, program); - - const gl = this.gl; - const CONTEXT_UID = this.CONTEXT_UID; - const buffers = geometry.buffers; - const attributes = geometry.attributes; - - const tempStride = {}; - const tempStart = {}; - - for (const j in buffers) - { - tempStride[j] = 0; - tempStart[j] = 0; - } - - for (const j in attributes) - { - if (!attributes[j].size && program.attributeData[j]) - { - attributes[j].size = program.attributeData[j].size; - } - - tempStride[attributes[j].buffer] += attributes[j].size * byteSizeMap[attributes[j].type]; - } - - for (const j in attributes) - { - const attribute = attributes[j]; - const attribSize = attribute.size; - - if (attribute.stride === undefined) - { - if (tempStride[attribute.buffer] === attribSize * byteSizeMap[attribute.type]) - { - attribute.stride = 0; - } - else - { - attribute.stride = tempStride[attribute.buffer]; - } - } - - if (attribute.start === undefined) - { - attribute.start = tempStart[attribute.buffer]; - - tempStart[attribute.buffer] += attribSize * byteSizeMap[attribute.type]; - } - } - - // first update - and create the buffers! - // only create a gl buffer if it actually gets - for (let i = 0; i < buffers.length; i++) - { - const buffer = buffers[i]; - - if (!buffer._glBuffers[CONTEXT_UID]) - { - buffer._glBuffers[CONTEXT_UID] = new GLBuffer(gl.createBuffer()); - } - } - - // TODO - maybe make this a data object? - // lets wait to see if we need to first! - const vao = gl.createVertexArray(); - - gl.bindVertexArray(vao); - - this.activateVao(geometry, program); - - gl.bindVertexArray(null); - - // add it to the cache! - geometry.glVertexArrayObjects[this.CONTEXT_UID][program.id] = vao; - - return vao; - } - - activateVao(geometry, program) - { - const gl = this.gl; - const CONTEXT_UID = this.CONTEXT_UID; - const buffers = geometry.buffers; - const attributes = geometry.attributes; - - if (geometry.indexBuffer) - { - // first update the index buffer if we have one.. - gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, geometry.indexBuffer._glBuffers[CONTEXT_UID].buffer); - } - - let lastBuffer = null; - - // add a new one! - for (const j in attributes) - { - const attribute = attributes[j]; - const buffer = buffers[attribute.buffer]; - const glBuffer = buffer._glBuffers[CONTEXT_UID]; - - if (program.attributeData[j]) - { - if (lastBuffer !== glBuffer) - { - gl.bindBuffer(gl.ARRAY_BUFFER, glBuffer.buffer); - - lastBuffer = glBuffer; - } - - const location = program.attributeData[j].location; - - // TODO introduce state again - // we can optimise this for older devices that have no VAOs - gl.enableVertexAttribArray(location); - - gl.vertexAttribPointer(location, - attribute.size, - attribute.type || gl.FLOAT, - attribute.normalized, - attribute.stride, - attribute.start); - - if (attribute.instance) - { - // TODO calculate instance count based of this... - if (this.hasInstance) - { - gl.vertexAttribDivisor(location, 1); - } - else - { - throw new Error('geometry error, GPU Instancing is not supported on this device'); - } - } - } - } - } - - draw(type, size, start, instanceCount) - { - const gl = this.gl; - const geometry = this._activeGeometry; - - // TODO.. this should not change so maybe cache the function? - - if (geometry.indexBuffer) - { - if (geometry.instanced) - { - /* eslint-disable max-len */ - gl.drawElementsInstanced(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2, instanceCount || 1); - /* eslint-enable max-len */ - } - else - { - gl.drawElements(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2); - } - } - else - if (geometry.instanced) - { - // TODO need a better way to calculate size.. - gl.drawArraysInstanced(type, start, size || geometry.getSize(), instanceCount || 1); - } - else - { - gl.drawArrays(type, start, size || geometry.getSize()); - } - - return this; - } - - unbind() - { - this.gl.bindVertexArray(null); - this._activeVao = null; - this._activeGeometry = null; - } -} diff --git a/packages/core/src/renderers/systems/geometry/setVertexAttribArrays.js b/packages/core/src/renderers/systems/geometry/setVertexAttribArrays.js deleted file mode 100644 index 23b6959..0000000 --- a/packages/core/src/renderers/systems/geometry/setVertexAttribArrays.js +++ /dev/null @@ -1,54 +0,0 @@ -// var GL_MAP = {}; - -/** - * @param gl {WebGLRenderingContext} The current WebGL context - * @param attribs {*} - * @param state {*} - */ -export default function setVertexAttribArrays(gl, attribs, state) -{ - let i; - - if (state) - { - const tempAttribState = state.tempAttribState; - const attribState = state.attribState; - - for (i = 0; i < tempAttribState.length; i++) - { - tempAttribState[i] = false; - } - - // set the new attribs - for (i = 0; i < attribs.length; i++) - { - tempAttribState[attribs[i].attribute.location] = true; - } - - for (i = 0; i < attribState.length; i++) - { - if (attribState[i] !== tempAttribState[i]) - { - attribState[i] = tempAttribState[i]; - - if (state.attribState[i]) - { - gl.enableVertexAttribArray(i); - } - else - { - gl.disableVertexAttribArray(i); - } - } - } - } - else - { - for (i = 0; i < attribs.length; i++) - { - const attrib = attribs[i]; - - gl.enableVertexAttribArray(attrib.attribute.location); - } - } -} diff --git a/packages/core/src/renderers/systems/index.js b/packages/core/src/renderers/systems/index.js deleted file mode 100644 index c843f03..0000000 --- a/packages/core/src/renderers/systems/index.js +++ /dev/null @@ -1,6 +0,0 @@ -/** - * Systems are individual components to the Renderer pipeline. - * @namespace PIXI.systems - */ -export { default as WebGLSystem } from './WebGLSystem'; -export { default as FilterSystem } from './filter/FilterSystem'; diff --git a/packages/core/src/renderers/systems/shader/GLProgram.js b/packages/core/src/renderers/systems/shader/GLProgram.js deleted file mode 100644 index 92980ca..0000000 --- a/packages/core/src/renderers/systems/shader/GLProgram.js +++ /dev/null @@ -1,43 +0,0 @@ -/** - * Helper class to create a webGL Program - * - * @class - * @private - */ -export default class GLProgram -{ - constructor(program, uniformData) - { - /** - * The shader program - * - * @member {WebGLProgram} - */ - this.program = program; - - /** - * holds the uniform data which contains uniform locations - * and current uniform values used for caching and preventing undeed GPU commands - * @type {Object} - */ - this.uniformData = uniformData; - - /** - * uniformGroups holds the various upload functions for the shader. Each uniform group - * and program have a unique upload function generated. - * @type {Object} - */ - this.uniformGroups = {}; - } - - /** - * Destroys this program - * TODO - */ - destroy() - { - this.uniformData = null; - this.uniformGroups = null; - this.program = null; - } -} diff --git a/packages/core/src/renderers/systems/shader/ShaderSystem.js b/packages/core/src/renderers/systems/shader/ShaderSystem.js deleted file mode 100644 index 6cf57b6..0000000 --- a/packages/core/src/renderers/systems/shader/ShaderSystem.js +++ /dev/null @@ -1,170 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; -import GLProgram from './GLProgram'; -import generateUniformsSync from '../../../shader/generateUniformsSync'; -import defaultValue from '../../../shader/defaultValue'; -import compileProgram from './shader/compileProgram'; - -let UID = 0; - -/** - * Helper class to create a webGL Texture - * - * @class - * @memberof PIXI - */ -export default class ShaderSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - A reference to the current renderer - */ - constructor(renderer) - { - super(renderer); - - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = null; - - this.shader = null; - this.program = null; - - this.id = UID++; - } - - contextChange(gl) - { - this.gl = gl; - } - - /** - * Changes the current shader to the one given in parameter - * - * @param {PIXI.Shader} shader - the new shader - * @param {boolean} dontSync - false if the shader should automatically sync its uniforms. - * @returns {PIXI.glCore.glProgram} the glProgram that belongs to the shader. - */ - bind(shader, dontSync) - { - shader.uniforms.globals = this.renderer.globalUniforms; - - const program = shader.program; - const glProgram = program.glPrograms[this.renderer.CONTEXT_UID] || this.generateShader(shader); - - this.shader = shader; - - // TODO - some current pixi plugins bypass this.. so it not safe to use yet.. - if (this.program !== program) - { - this.program = program; - this.gl.useProgram(glProgram.program); - } - - if (!dontSync) - { - this.syncUniformGroup(shader.uniformGroup); - } - - return glProgram; - } - - /** - * Uploads the uniforms values to the currently bound shader. - * - * @param {object} uniforms - the uniforms valiues that be applied to the current shader - */ - setUniforms(uniforms) - { - const shader = this.shader.program; - const glProgram = shader.glPrograms[this.renderer.CONTEXT_UID]; - - shader.syncUniforms(glProgram.uniformData, uniforms, this.renderer); - } - - syncUniformGroup(group) - { - const glProgram = this.getglProgram(); - - if (!group.static || group.dirtyId !== glProgram.uniformGroups[group.id]) - { - glProgram.uniformGroups[group.id] = group.dirtyId; - const syncFunc = group.syncUniforms[this.shader.program.id] || this.createSyncGroups(group); - - syncFunc(glProgram.uniformData, group.uniforms, this.renderer); - } - } - - createSyncGroups(group) - { - group.syncUniforms[this.shader.program.id] = generateUniformsSync(group, this.shader.program.uniformData); - - return group.syncUniforms[this.shader.program.id]; - } - - /** - * Returns the underlying GLShade rof the currently bound shader. - * This can be handy for when you to have a little more control over the setting of your uniforms. - * - * @return {PIXI.glCore.Shader} the glProgram for the currently bound Shader for this context - */ - getglProgram() - { - if (this.shader) - { - return this.shader.program.glPrograms[this.renderer.CONTEXT_UID]; - } - - return null; - } - - /** - * Generates a glProgram verion of the Shader provided. - * - * @private - * @param {PIXI.Shader} shader the shader that the glProgram will be based on. - * @return {PIXI.glCore.glProgram} A shiney new glProgram - */ - generateShader(shader) - { - const gl = this.gl; - - const program = shader.program; - - const attribMap = {}; - - for (const i in program.attributeData) - { - attribMap[i] = program.attributeData[i].location; - } - - const shaderProgram = compileProgram(gl, program.vertexSrc, program.fragmentSrc, attribMap); - const uniformData = {}; - - for (const i in program.uniformData) - { - const data = program.uniformData[i]; - - uniformData[i] = { - location: gl.getUniformLocation(shaderProgram, i), - value: defaultValue(data.type, data.size), - }; - } - - const glProgram = new GLProgram(shaderProgram, uniformData); - - program.glPrograms[this.renderer.CONTEXT_UID] = glProgram; - - return glProgram; - } - - /** - * Destroys this System and removes all its textures - */ - destroy() - { - // TODO implement destroy method for ShaderSystem - this.destroyed = true; - } -} diff --git a/packages/core/src/renderers/systems/shader/shader/compileProgram.js b/packages/core/src/renderers/systems/shader/shader/compileProgram.js deleted file mode 100644 index 8c6ccb9..0000000 --- a/packages/core/src/renderers/systems/shader/shader/compileProgram.js +++ /dev/null @@ -1,78 +0,0 @@ -/** - * @method compileProgram - * @memberof PIXI.glCore.shader - * @param gl {WebGLRenderingContext} The current WebGL context {WebGLProgram} - * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. - * @param fragmentSrc {string|string[]} The fragment shader source as an array of strings. - * @param attributeLocations {Object} An attribute location map that lets you manually set the attribute locations - * @return {WebGLProgram} the shader program - */ -export default function compileProgram(gl, vertexSrc, fragmentSrc, attributeLocations) -{ - const glVertShader = compileShader(gl, gl.VERTEX_SHADER, vertexSrc); - const glFragShader = compileShader(gl, gl.FRAGMENT_SHADER, fragmentSrc); - - let program = gl.createProgram(); - - gl.attachShader(program, glVertShader); - gl.attachShader(program, glFragShader); - - // optionally, set the attributes manually for the program rather than letting WebGL decide.. - if (attributeLocations) - { - for (const i in attributeLocations) - { - gl.bindAttribLocation(program, attributeLocations[i], i); - } - } - - gl.linkProgram(program); - - // if linking fails, then log and cleanup - if (!gl.getProgramParameter(program, gl.LINK_STATUS)) - { - console.error('Pixi.js Error: Could not initialize shader.'); - console.error('gl.VALIDATE_STATUS', gl.getProgramParameter(program, gl.VALIDATE_STATUS)); - console.error('gl.getError()', gl.getError()); - - // if there is a program info log, log it - if (gl.getProgramInfoLog(program) !== '') - { - console.warn('Pixi.js Warning: gl.getProgramInfoLog()', gl.getProgramInfoLog(program)); - } - - gl.deleteProgram(program); - program = null; - } - - // clean up some shaders - gl.deleteShader(glVertShader); - gl.deleteShader(glFragShader); - - return program; -} - -/** - * @private - * @param gl {WebGLRenderingContext} The current WebGL context {WebGLProgram} - * @param type {Number} the type, can be either VERTEX_SHADER or FRAGMENT_SHADER - * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. - * @return {WebGLShader} the shader - */ -function compileShader(gl, type, src) -{ - const shader = gl.createShader(type); - - gl.shaderSource(shader, src); - gl.compileShader(shader); - - if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) - { - console.warn(src); - console.error(gl.getShaderInfoLog(shader)); - - return null; - } - - return shader; -} diff --git a/packages/core/src/renderers/systems/shader/shader/defaultValue.js b/packages/core/src/renderers/systems/shader/shader/defaultValue.js deleted file mode 100644 index 97290bd..0000000 --- a/packages/core/src/renderers/systems/shader/shader/defaultValue.js +++ /dev/null @@ -1,79 +0,0 @@ -/** - * @method defaultValue - * @memberof PIXI.glCore.shader - * @param type {String} Type of value - * @param size {Number} - */ -export default function defaultValue(type, size) -{ - switch (type) - { - case 'float': - return 0; - - case 'vec2': - return new Float32Array(2 * size); - - case 'vec3': - return new Float32Array(3 * size); - - case 'vec4': - return new Float32Array(4 * size); - - case 'int': - case 'sampler2D': - case 'sampler2DArray': - return 0; - - case 'ivec2': - return new Int32Array(2 * size); - - case 'ivec3': - return new Int32Array(3 * size); - - case 'ivec4': - return new Int32Array(4 * size); - - case 'bool': - return false; - - case 'bvec2': - - return booleanArray(2 * size); - - case 'bvec3': - return booleanArray(3 * size); - - case 'bvec4': - return booleanArray(4 * size); - - case 'mat2': - return new Float32Array([1, 0, - 0, 1]); - - case 'mat3': - return new Float32Array([1, 0, 0, - 0, 1, 0, - 0, 0, 1]); - - case 'mat4': - return new Float32Array([1, 0, 0, 0, - 0, 1, 0, 0, - 0, 0, 1, 0, - 0, 0, 0, 1]); - } - - return null; -} - -function booleanArray(size) -{ - const array = new Array(size); - - for (let i = 0; i < array.length; i++) - { - array[i] = false; - } - - return array; -} diff --git a/packages/core/src/renderers/systems/shader/shader/index.js b/packages/core/src/renderers/systems/shader/shader/index.js deleted file mode 100644 index ba7a317..0000000 --- a/packages/core/src/renderers/systems/shader/shader/index.js +++ /dev/null @@ -1,8 +0,0 @@ -/** - * @namespace PIXI.glCore.shader - */ -export { default as compileProgram } from './compileProgram'; -export { default as defaultValue } from './defaultValue'; -export { default as setPrecision } from './setPrecision'; -export { default as mapSize } from './mapSize'; -export { default as mapType } from './mapType'; diff --git a/packages/core/src/renderers/systems/shader/shader/mapSize.js b/packages/core/src/renderers/systems/shader/shader/mapSize.js deleted file mode 100644 index b681554..0000000 --- a/packages/core/src/renderers/systems/shader/shader/mapSize.js +++ /dev/null @@ -1,33 +0,0 @@ -const GLSL_TO_SIZE = { - float: 1, - vec2: 2, - vec3: 3, - vec4: 4, - - int: 1, - ivec2: 2, - ivec3: 3, - ivec4: 4, - - bool: 1, - bvec2: 2, - bvec3: 3, - bvec4: 4, - - mat2: 4, - mat3: 9, - mat4: 16, - - sampler2D: 1, -}; - -/** - * @method mapSize - * @memberof PIXI.glCore.shader - * @param type {String} - * @return {Number} - */ -export default function mapSize(type) -{ - return GLSL_TO_SIZE[type]; -} diff --git a/packages/core/src/renderers/systems/shader/shader/mapType.js b/packages/core/src/renderers/systems/shader/shader/mapType.js deleted file mode 100644 index d288b7c..0000000 --- a/packages/core/src/renderers/systems/shader/shader/mapType.js +++ /dev/null @@ -1,45 +0,0 @@ -let GL_TABLE = null; - -const GL_TO_GLSL_TYPES = { - FLOAT: 'float', - FLOAT_VEC2: 'vec2', - FLOAT_VEC3: 'vec3', - FLOAT_VEC4: 'vec4', - - INT: 'int', - INT_VEC2: 'ivec2', - INT_VEC3: 'ivec3', - INT_VEC4: 'ivec4', - - BOOL: 'bool', - BOOL_VEC2: 'bvec2', - BOOL_VEC3: 'bvec3', - BOOL_VEC4: 'bvec4', - - FLOAT_MAT2: 'mat2', - FLOAT_MAT3: 'mat3', - FLOAT_MAT4: 'mat4', - - SAMPLER_2D: 'sampler2D', - SAMPLER_CUBE: 'samplerCube', - SAMPLER_2D_ARRAY: 'sampler2DArray', -}; - -export default function mapType(gl, type) -{ - if (!GL_TABLE) - { - const typeNames = Object.keys(GL_TO_GLSL_TYPES); - - GL_TABLE = {}; - - for (let i = 0; i < typeNames.length; ++i) - { - const tn = typeNames[i]; - - GL_TABLE[gl[tn]] = GL_TO_GLSL_TYPES[tn]; - } - } - - return GL_TABLE[type]; -} diff --git a/packages/core/src/renderers/systems/shader/shader/setPrecision.js b/packages/core/src/renderers/systems/shader/shader/setPrecision.js deleted file mode 100644 index e2c1f34..0000000 --- a/packages/core/src/renderers/systems/shader/shader/setPrecision.js +++ /dev/null @@ -1,18 +0,0 @@ -/** - * Sets the float precision on the shader. If the precision is already present this function will do nothing - * @param {string} src the shader source - * @param {string} precision The float precision of the shader. Options are 'lowp', 'mediump' or 'highp'. - * - * @return {string} modified shader source - */ -export default function setPrecision(src, precision) -{ - src = src.trim(); - - if (src.substring(0, 9) !== 'precision' && src.substring(0, 1) !== '#') - { - return `precision ${precision} float;\n${src}`; - } - - return src; -} diff --git a/packages/core/src/renderers/systems/textures/GLTexture.js b/packages/core/src/renderers/systems/textures/GLTexture.js deleted file mode 100644 index c3fb8dc..0000000 --- a/packages/core/src/renderers/systems/textures/GLTexture.js +++ /dev/null @@ -1,33 +0,0 @@ -export default class GLTexture -{ - constructor(texture) - { - /** - * The WebGL texture - * - * @member {WebGLTexture} - */ - this.texture = texture; - - this.width = -1; - this.height = -1; - - /** - * Texture contents dirty flag - * @member {number} - */ - this.dirtyId = -1; - - /** - * Texture style dirty flag - * @type {number} - */ - this.dirtyStyleId = -1; - - /** - * Whether mip levels has to be generated - * @type {boolean} - */ - this.mipmap = false; - } -} diff --git a/packages/core/src/renderers/systems/textures/TextureGCSystem.js b/packages/core/src/renderers/systems/textures/TextureGCSystem.js deleted file mode 100644 index b089938..0000000 --- a/packages/core/src/renderers/systems/textures/TextureGCSystem.js +++ /dev/null @@ -1,110 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; -import { GC_MODES } from '@pixi/constants'; -import { settings } from '@pixi/settings'; - -/** - * TextureGarbageCollector. This class manages the GPU and ensures that it does not get clogged - * up with textures that are no longer being used. - * - * @class - * @memberof PIXI - */ -export default class TextureGCSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this.count = 0; - this.checkCount = 0; - this.maxIdle = settings.GC_MAX_IDLE; - this.checkCountMax = settings.GC_MAX_CHECK_COUNT; - this.mode = settings.GC_MODE; - } - - /** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ - postrender() - { - this.count++; - - if (this.mode === GC_MODES.MANUAL) - { - return; - } - - this.checkCount++; - - if (this.checkCount > this.checkCountMax) - { - this.checkCount = 0; - - this.run(); - } - } - - /** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ - run() - { - const tm = this.renderer.texture; - const managedTextures = tm.managedTextures; - let wasRemoved = false; - - for (let i = 0; i < managedTextures.length; i++) - { - const texture = managedTextures[i]; - - // only supports non generated textures at the moment! - if (!texture.frameBuffer && this.count - texture.touched > this.maxIdle) - { - tm.destroyTexture(texture, true); - managedTextures[i] = null; - wasRemoved = true; - } - } - - if (wasRemoved) - { - let j = 0; - - for (let i = 0; i < managedTextures.length; i++) - { - if (managedTextures[i] !== null) - { - managedTextures[j++] = managedTextures[i]; - } - } - - managedTextures.length = j; - } - } - - /** - * Removes all the textures within the specified displayObject and its children from the GPU - * - * @param {PIXI.DisplayObject} displayObject - the displayObject to remove the textures from. - */ - unload(displayObject) - { - const tm = this.renderer.textureSystem; - - // only destroy non generated textures - if (displayObject._texture && displayObject._texture._glRenderTargets) - { - tm.destroyTexture(displayObject._texture); - } - - for (let i = displayObject.children.length - 1; i >= 0; i--) - { - this.unload(displayObject.children[i]); - } - } -} diff --git a/packages/core/src/renderers/systems/textures/TextureSystem.js b/packages/core/src/renderers/systems/textures/TextureSystem.js deleted file mode 100644 index ccc5de9..0000000 --- a/packages/core/src/renderers/systems/textures/TextureSystem.js +++ /dev/null @@ -1,281 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; -import GLTexture from './GLTexture'; -import { removeItems } from '@pixi/utils'; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class TextureSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - // TODO set to max textures... - this.boundTextures = [ - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - ]; - - this.currentLocation = -1; - - this.managedTextures = []; - } - - /** - * Sets up the renderer context and necessary buffers. - * - * @private - */ - contextChange() - { - const gl = this.gl = this.renderer.gl; - - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - - // TODO move this.. to a nice make empty textures class.. - this.emptyTextures = {}; - - const emptyTexture2D = new GLTexture(gl.createTexture()); - - gl.bindTexture(gl.TEXTURE_2D, emptyTexture2D.texture); - gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array(4)); - - this.emptyTextures[gl.TEXTURE_2D] = emptyTexture2D; - this.emptyTextures[gl.TEXTURE_CUBE_MAP] = new GLTexture(gl.createTexture()); - - gl.bindTexture(gl.TEXTURE_CUBE_MAP, this.emptyTextures[gl.TEXTURE_CUBE_MAP].texture); - - let i; - - for (i = 0; i < 6; i++) - { - gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, null); - } - - gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_MAG_FILTER, gl.LINEAR); - gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_MIN_FILTER, gl.LINEAR); - - for (i = 0; i < this.boundTextures.length; i++) - { - this.bind(null, i); - } - } - - bind(texture, location) - { - const gl = this.gl; - - location = location || 0; - - if (this.currentLocation !== location) - { - this.currentLocation = location; - gl.activeTexture(gl.TEXTURE0 + location); - } - - if (texture) - { - texture = texture.baseTexture || texture; - - if (texture.valid) - { - texture.touched = this.renderer.textureGC.count; - - const glTexture = texture._glTextures[this.CONTEXT_UID] || this.initTexture(texture); - - gl.bindTexture(texture.target, glTexture.texture); - - if (glTexture.dirtyId !== texture.dirtyId) - { - this.updateTexture(texture); - } - - this.boundTextures[location] = texture; - } - } - else - { - gl.bindTexture(gl.TEXTURE_2D, this.emptyTextures[gl.TEXTURE_2D].texture); - this.boundTextures[location] = null; - } - } - - unbind(texture) - { - const gl = this.gl; - - for (let i = 0; i < this.boundTextures.length; i++) - { - if (this.boundTextures[i] === texture) - { - if (this.currentLocation !== i) - { - gl.activeTexture(gl.TEXTURE0 + i); - this.currentLocation = i; - } - - gl.bindTexture(gl.TEXTURE_2D, this.emptyTextures[texture.target].texture); - this.boundTextures[i] = null; - } - } - } - - initTexture(texture) - { - const glTexture = new GLTexture(this.gl.createTexture()); - - // guarentee an update.. - glTexture.dirtyId = -1; - - texture._glTextures[this.CONTEXT_UID] = glTexture; - - this.managedTextures.push(texture); - texture.on('dispose', this.destroyTexture, this); - - return glTexture; - } - - updateTexture(texture) - { - const glTexture = texture._glTextures[this.CONTEXT_UID]; - const renderer = this.renderer; - - if (texture.resource && texture.resource.upload(renderer, texture, glTexture)) - { - // texture is uploaded, dont do anything! - } - else - { - // default, renderTexture-like logic - const width = texture.realWidth; - const height = texture.realHeight; - const gl = renderer.gl; - - if (glTexture.width !== width - || glTexture.height !== height - || glTexture.dirtyId < 0) - { - glTexture.width = width; - glTexture.height = height; - - gl.texImage2D(texture.target, 0, - texture.format, - width, - height, - 0, - texture.format, - texture.type, - null); - } - } - - // lets only update what changes.. - if (texture.dirtyStyleId !== glTexture.dirtyStyleId) - { - this.updateTextureStyle(texture); - } - glTexture.dirtyId = texture.dirtyId; - } - - /** - * Deletes the texture from WebGL - * - * @param {PIXI.BaseTexture|PIXI.Texture} texture - the texture to destroy - * @param {boolean} [skipRemove=false] - Whether to skip removing the texture from the TextureManager. - */ - destroyTexture(texture, skipRemove) - { - const gl = this.gl; - - texture = texture.baseTexture || texture; - - if (texture._glTextures[this.renderer.CONTEXT_UID]) - { - this.unbind(texture); - - gl.deleteTexture(texture._glTextures[this.renderer.CONTEXT_UID].texture); - texture.off('dispose', this.destroyTexture, this); - - delete texture._glTextures[this.renderer.CONTEXT_UID]; - - if (!skipRemove) - { - const i = this.managedTextures.indexOf(texture); - - if (i !== -1) - { - removeItems(this.managedTextures, i, 1); - } - } - } - } - - updateTextureStyle(texture) - { - const glTexture = texture._glTextures[this.CONTEXT_UID]; - - glTexture.mipmap = texture.mipmap && texture.isPowerOfTwo; - if (!glTexture) - { - return; - } - - if (texture.resource && texture.resource.style(this.renderer, texture, glTexture)) - { - // style is set, dont do anything! - } - else - { - this.setStyle(texture, glTexture); - } - - glTexture.dirtyStyleId = texture.dirtyStyleId; - } - - setStyle(texture, glTexture) - { - const gl = this.gl; - - if (glTexture.mipmap) - { - gl.generateMipmap(texture.target); - } - - gl.texParameteri(texture.target, gl.TEXTURE_WRAP_S, texture.wrapMode); - gl.texParameteri(texture.target, gl.TEXTURE_WRAP_T, texture.wrapMode); - - if (glTexture.mipmap) - { - /* eslint-disable max-len */ - gl.texParameteri(texture.target, gl.TEXTURE_MIN_FILTER, texture.scaleMode ? gl.LINEAR_MIPMAP_LINEAR : gl.NEAREST_MIPMAP_NEAREST); - /* eslint-disable max-len */ - } - else - { - gl.texParameteri(texture.target, gl.TEXTURE_MIN_FILTER, texture.scaleMode ? gl.LINEAR : gl.NEAREST); - } - - gl.texParameteri(texture.target, gl.TEXTURE_MAG_FILTER, texture.scaleMode ? gl.LINEAR : gl.NEAREST); - } -} diff --git a/packages/core/src/renderers/utils/ObjectRenderer.js b/packages/core/src/renderers/utils/ObjectRenderer.js deleted file mode 100644 index f67a273..0000000 --- a/packages/core/src/renderers/utils/ObjectRenderer.js +++ /dev/null @@ -1,48 +0,0 @@ -import WebGLSystem from '../systems/WebGLSystem'; - -/** - * Base for a common object renderer that can be used as a system renderer plugin. - * - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class ObjectRenderer extends WebGLSystem -{ - /** - * Starts the renderer and sets the shader - * - */ - start() - { - // set the shader.. - } - - /** - * Stops the renderer - * - */ - stop() - { - this.flush(); - } - - /** - * Stub method for rendering content and emptying the current batch. - * - */ - flush() - { - // flush! - } - - /** - * Renders an object - * - * @param {PIXI.DisplayObject} object - The object to render. - */ - render(object) // eslint-disable-line no-unused-vars - { - // render the object - } -} diff --git a/packages/core/src/renderers/utils/Quad.js b/packages/core/src/renderers/utils/Quad.js deleted file mode 100644 index 5c1517b..0000000 --- a/packages/core/src/renderers/utils/Quad.js +++ /dev/null @@ -1,26 +0,0 @@ -import Geometry from '../../geometry/Geometry'; - -/** - * Helper class to create a quad - * - * @class - * @memberof PIXI - */ -export default class Quad extends Geometry -{ - /** - * just a quad - */ - constructor() - { - super(); - - this.addAttribute('aVertexPosition', [ - 0, 0, - 1, 0, - 1, 1, - 0, 1, - ]) - .addIndex([0, 1, 3, 2]); - } -} diff --git a/packages/core/src/renderers/utils/QuadUv.js b/packages/core/src/renderers/utils/QuadUv.js deleted file mode 100644 index 97bda89..0000000 --- a/packages/core/src/renderers/utils/QuadUv.js +++ /dev/null @@ -1,103 +0,0 @@ -import Geometry from '../../geometry/Geometry'; -import Buffer from '../../geometry/Buffer'; - -/** - * Helper class to create a quad with uvs like in v4 - * - * @class - * @memberof PIXI - */ -export default class QuadUv extends Geometry -{ - constructor() - { - super(); - - /** - * An array of vertices - * - * @member {Float32Array} - */ - this.vertices = new Float32Array([ - -1, -1, - 1, -1, - 1, 1, - -1, 1, - ]); - - /** - * The Uvs of the quad - * - * @member {Float32Array} - */ - this.uvs = new Float32Array([ - 0, 0, - 1, 0, - 1, 1, - 0, 1, - ]); - - this.vertexBuffer = new Buffer(this.vertices); - this.uvBuffer = new Buffer(this.uvs); - - this.addAttribute('aVertexPosition', this.vertexBuffer) - .addAttribute('aTextureCoord', this.uvBuffer) - .addIndex([0, 1, 2, 0, 2, 3]); - } - - /** - * Maps two Rectangle to the quad. - * - * @param {PIXI.Rectangle} targetTextureFrame - the first rectangle - * @param {PIXI.Rectangle} destinationFrame - the second rectangle - * @return {PIXI.Quad} Returns itself. - */ - map(targetTextureFrame, destinationFrame) - { - let x = 0; // destinationFrame.x / targetTextureFrame.width; - let y = 0; // destinationFrame.y / targetTextureFrame.height; - - this.uvs[0] = x; - this.uvs[1] = y; - - this.uvs[2] = x + (destinationFrame.width / targetTextureFrame.width); - this.uvs[3] = y; - - this.uvs[4] = x + (destinationFrame.width / targetTextureFrame.width); - this.uvs[5] = y + (destinationFrame.height / targetTextureFrame.height); - - this.uvs[6] = x; - this.uvs[7] = y + (destinationFrame.height / targetTextureFrame.height); - - x = destinationFrame.x; - y = destinationFrame.y; - - this.vertices[0] = x; - this.vertices[1] = y; - - this.vertices[2] = x + destinationFrame.width; - this.vertices[3] = y; - - this.vertices[4] = x + destinationFrame.width; - this.vertices[5] = y + destinationFrame.height; - - this.vertices[6] = x; - this.vertices[7] = y + destinationFrame.height; - - this.invalidate(); - - return this; - } - - /** - * legacy upload method, just marks buffers dirty - * @returns {PIXI.QuadUv} Returns itself. - */ - invalidate() - { - this.vertexBuffer._updateID++; - this.uvBuffer._updateID++; - - return this; - } -} diff --git a/packages/core/src/renderers/utils/checkMaxIfStatmentsInShader.js b/packages/core/src/renderers/utils/checkMaxIfStatmentsInShader.js deleted file mode 100644 index 8047c96..0000000 --- a/packages/core/src/renderers/utils/checkMaxIfStatmentsInShader.js +++ /dev/null @@ -1,58 +0,0 @@ -const fragTemplate = [ - 'precision mediump float;', - 'void main(void){', - 'float test = 0.1;', - '%forloop%', - 'gl_FragColor = vec4(0.0);', - '}', -].join('\n'); - -export default function checkMaxIfStatmentsInShader(maxIfs, gl) -{ - if (maxIfs === 0) - { - throw new Error('Invalid value of `0` passed to `checkMaxIfStatementsInShader`'); - } - - const shader = gl.createShader(gl.FRAGMENT_SHADER); - - while (true) // eslint-disable-line no-constant-condition - { - const fragmentSrc = fragTemplate.replace(/%forloop%/gi, generateIfTestSrc(maxIfs)); - - gl.shaderSource(shader, fragmentSrc); - gl.compileShader(shader); - - if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) - { - maxIfs = (maxIfs / 2) | 0; - } - else - { - // valid! - break; - } - } - - return maxIfs; -} - -function generateIfTestSrc(maxIfs) -{ - let src = ''; - - for (let i = 0; i < maxIfs; ++i) - { - if (i > 0) - { - src += '\nelse '; - } - - if (i < maxIfs - 1) - { - src += `if(test == ${i}.0){}`; - } - } - - return src; -} diff --git a/packages/core/src/renderers/utils/mapWebGLBlendModesToPixi.js b/packages/core/src/renderers/utils/mapWebGLBlendModesToPixi.js deleted file mode 100644 index 10ffe4d..0000000 --- a/packages/core/src/renderers/utils/mapWebGLBlendModesToPixi.js +++ /dev/null @@ -1,47 +0,0 @@ -import { BLEND_MODES } from '@pixi/constants'; - -/** - * Maps gl blend combinations to WebGL. - * - * @memberof PIXI - * @function mapWebGLBlendModesToPixi - * @private - * @param {WebGLRenderingContext} gl - The rendering context. - * @param {string[]} [array=[]] - The array to output into. - * @return {string[]} Mapped modes. - */ -export default function mapWebGLBlendModesToPixi(gl, array = []) -{ - // TODO - premultiply alpha would be different. - // add a boolean for that! - array[BLEND_MODES.NORMAL] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.ADD] = [gl.ONE, gl.DST_ALPHA]; - array[BLEND_MODES.MULTIPLY] = [gl.DST_COLOR, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.SCREEN] = [gl.ONE, gl.ONE_MINUS_SRC_COLOR]; - array[BLEND_MODES.OVERLAY] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.DARKEN] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.LIGHTEN] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.COLOR_DODGE] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.COLOR_BURN] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.HARD_LIGHT] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.SOFT_LIGHT] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.DIFFERENCE] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.EXCLUSION] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.HUE] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.SATURATION] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.COLOR] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.LUMINOSITY] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.NONE] = [0, 0]; - - // not-premultiplied blend modes - array[BLEND_MODES.NORMAL_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.ADD_NPM] = [gl.SRC_ALPHA, gl.DST_ALPHA, gl.ONE, gl.DST_ALPHA]; - array[BLEND_MODES.SCREEN_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_COLOR, gl.ONE, gl.ONE_MINUS_SRC_COLOR]; - - // not-premultiplied blend modes - array[BLEND_MODES.NORMAL_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.ADD_NPM] = [gl.SRC_ALPHA, gl.DST_ALPHA, gl.ONE, gl.DST_ALPHA]; - array[BLEND_MODES.SCREEN_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_COLOR, gl.ONE, gl.ONE_MINUS_SRC_COLOR]; - - return array; -} diff --git a/packages/core/src/shader/GLProgram.js b/packages/core/src/shader/GLProgram.js new file mode 100644 index 0000000..92980ca --- /dev/null +++ b/packages/core/src/shader/GLProgram.js @@ -0,0 +1,43 @@ +/** + * Helper class to create a webGL Program + * + * @class + * @private + */ +export default class GLProgram +{ + constructor(program, uniformData) + { + /** + * The shader program + * + * @member {WebGLProgram} + */ + this.program = program; + + /** + * holds the uniform data which contains uniform locations + * and current uniform values used for caching and preventing undeed GPU commands + * @type {Object} + */ + this.uniformData = uniformData; + + /** + * uniformGroups holds the various upload functions for the shader. Each uniform group + * and program have a unique upload function generated. + * @type {Object} + */ + this.uniformGroups = {}; + } + + /** + * Destroys this program + * TODO + */ + destroy() + { + this.uniformData = null; + this.uniformGroups = null; + this.program = null; + } +} diff --git a/packages/core/src/shader/Program.js b/packages/core/src/shader/Program.js index 4f9971e..4b4007a 100644 --- a/packages/core/src/shader/Program.js +++ b/packages/core/src/shader/Program.js @@ -1,6 +1,11 @@ -import * as shaderUtils from '../renderers/systems/shader/shader'; +// import * as from '../systems/shader/shader'; +import { setPrecision, + defaultValue, + compileProgram, + mapSize, + mapType, + getTestContext } from './utils'; import { ProgramCache } from '@pixi/utils'; -import getTestContext from './getTestContext'; import defaultFragment from './defaultProgram.frag'; import defaultVertex from './defaultProgram.vert'; import { settings } from '@pixi/settings'; @@ -34,8 +39,8 @@ */ this.fragmentSrc = fragmentSrc || Program.defaultFragmentSrc; - this.vertexSrc = shaderUtils.setPrecision(this.vertexSrc, settings.PRECISION_VERTEX); - this.fragmentSrc = shaderUtils.setPrecision(this.fragmentSrc, settings.PRECISION_FRAGMENT); + this.vertexSrc = setPrecision(this.vertexSrc, settings.PRECISION_VERTEX); + this.fragmentSrc = setPrecision(this.fragmentSrc, settings.PRECISION_FRAGMENT); // currently this does not extract structs only default types this.extractData(this.vertexSrc, this.fragmentSrc); @@ -62,7 +67,7 @@ if (gl) { - const program = shaderUtils.compileProgram(gl, vertexSrc, fragmentSrc); + const program = compileProgram(gl, vertexSrc, fragmentSrc); this.attributeData = this.getAttributeData(program, gl); this.uniformData = this.getUniformData(program, gl); @@ -95,13 +100,13 @@ for (let i = 0; i < totalAttributes; i++) { const attribData = gl.getActiveAttrib(program, i); - const type = shaderUtils.mapType(gl, attribData.type); + const type = mapType(gl, attribData.type); /*eslint-disable */ const data = { type: type, name: attribData.name, - size: shaderUtils.mapSize(type), + size: mapSize(type), location: 0, }; /* eslint-enable */ @@ -145,14 +150,14 @@ const name = uniformData.name.replace(/\[.*?\]/, ''); const isArray = uniformData.name.match(/\[.*?\]/, ''); - const type = shaderUtils.mapType(gl, uniformData.type); + const type = mapType(gl, uniformData.type); /*eslint-disable */ uniforms[name] = { type: type, size: uniformData.size, isArray:isArray, - value: shaderUtils.defaultValue(type, uniformData.size), + value: defaultValue(type, uniformData.size), }; /* eslint-enable */ } diff --git a/packages/core/src/shader/ShaderSystem.js b/packages/core/src/shader/ShaderSystem.js new file mode 100644 index 0000000..40b8af8 --- /dev/null +++ b/packages/core/src/shader/ShaderSystem.js @@ -0,0 +1,171 @@ +import System from '../System'; +import GLProgram from './GLProgram'; +import { generateUniformsSync, + defaultValue, + compileProgram } from './utils'; + +let UID = 0; + +/** + * Helper class to create a webGL Texture + * + * @class + * @memberof PIXI.systems + * @extends PIXI.System + */ +export default class ShaderSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - A reference to the current renderer + */ + constructor(renderer) + { + super(renderer); + + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = null; + + this.shader = null; + this.program = null; + + this.id = UID++; + } + + contextChange(gl) + { + this.gl = gl; + } + + /** + * Changes the current shader to the one given in parameter + * + * @param {PIXI.Shader} shader - the new shader + * @param {boolean} dontSync - false if the shader should automatically sync its uniforms. + * @returns {PIXI.glCore.glProgram} the glProgram that belongs to the shader. + */ + bind(shader, dontSync) + { + shader.uniforms.globals = this.renderer.globalUniforms; + + const program = shader.program; + const glProgram = program.glPrograms[this.renderer.CONTEXT_UID] || this.generateShader(shader); + + this.shader = shader; + + // TODO - some current pixi plugins bypass this.. so it not safe to use yet.. + if (this.program !== program) + { + this.program = program; + this.gl.useProgram(glProgram.program); + } + + if (!dontSync) + { + this.syncUniformGroup(shader.uniformGroup); + } + + return glProgram; + } + + /** + * Uploads the uniforms values to the currently bound shader. + * + * @param {object} uniforms - the uniforms valiues that be applied to the current shader + */ + setUniforms(uniforms) + { + const shader = this.shader.program; + const glProgram = shader.glPrograms[this.renderer.CONTEXT_UID]; + + shader.syncUniforms(glProgram.uniformData, uniforms, this.renderer); + } + + syncUniformGroup(group) + { + const glProgram = this.getglProgram(); + + if (!group.static || group.dirtyId !== glProgram.uniformGroups[group.id]) + { + glProgram.uniformGroups[group.id] = group.dirtyId; + const syncFunc = group.syncUniforms[this.shader.program.id] || this.createSyncGroups(group); + + syncFunc(glProgram.uniformData, group.uniforms, this.renderer); + } + } + + createSyncGroups(group) + { + group.syncUniforms[this.shader.program.id] = generateUniformsSync(group, this.shader.program.uniformData); + + return group.syncUniforms[this.shader.program.id]; + } + + /** + * Returns the underlying GLShade rof the currently bound shader. + * This can be handy for when you to have a little more control over the setting of your uniforms. + * + * @return {PIXI.glCore.Shader} the glProgram for the currently bound Shader for this context + */ + getglProgram() + { + if (this.shader) + { + return this.shader.program.glPrograms[this.renderer.CONTEXT_UID]; + } + + return null; + } + + /** + * Generates a glProgram verion of the Shader provided. + * + * @private + * @param {PIXI.Shader} shader the shader that the glProgram will be based on. + * @return {PIXI.glCore.glProgram} A shiney new glProgram + */ + generateShader(shader) + { + const gl = this.gl; + + const program = shader.program; + + const attribMap = {}; + + for (const i in program.attributeData) + { + attribMap[i] = program.attributeData[i].location; + } + + const shaderProgram = compileProgram(gl, program.vertexSrc, program.fragmentSrc, attribMap); + const uniformData = {}; + + for (const i in program.uniformData) + { + const data = program.uniformData[i]; + + uniformData[i] = { + location: gl.getUniformLocation(shaderProgram, i), + value: defaultValue(data.type, data.size), + }; + } + + const glProgram = new GLProgram(shaderProgram, uniformData); + + program.glPrograms[this.renderer.CONTEXT_UID] = glProgram; + + return glProgram; + } + + /** + * Destroys this System and removes all its textures + */ + destroy() + { + // TODO implement destroy method for ShaderSystem + this.destroyed = true; + } +} diff --git a/packages/core/src/shader/defaultValue.js b/packages/core/src/shader/defaultValue.js deleted file mode 100644 index 847a069..0000000 --- a/packages/core/src/shader/defaultValue.js +++ /dev/null @@ -1,78 +0,0 @@ -function booleanArray(size) -{ - const array = new Array(size); - - for (let i = 0; i < array.length; i++) - { - array[i] = false; - } - - return array; -} - -/** - * @class - * @memberof PIXI.glCore.shader - * @param type {String} Type of value - * @param size {Number} - */ -export default function defaultValue(type, size) -{ - switch (type) - { - case 'float': - return 0; - - case 'vec2': - return new Float32Array(2 * size); - - case 'vec3': - return new Float32Array(3 * size); - - case 'vec4': - return new Float32Array(4 * size); - - case 'int': - case 'sampler2D': - return 0; - - case 'ivec2': - return new Int32Array(2 * size); - - case 'ivec3': - return new Int32Array(3 * size); - - case 'ivec4': - return new Int32Array(4 * size); - - case 'bool': - return false; - - case 'bvec2': - - return booleanArray(2 * size); - - case 'bvec3': - return booleanArray(3 * size); - - case 'bvec4': - return booleanArray(4 * size); - - case 'mat2': - return new Float32Array([1, 0, - 0, 1]); - - case 'mat3': - return new Float32Array([1, 0, 0, - 0, 1, 0, - 0, 0, 1]); - - case 'mat4': - return new Float32Array([1, 0, 0, 0, - 0, 1, 0, 0, - 0, 0, 1, 0, - 0, 0, 0, 1]); - } - - return 0; -} diff --git a/packages/core/src/shader/extractUniformsFromSrc.js b/packages/core/src/shader/extractUniformsFromSrc.js deleted file mode 100644 index 3d02c0a..0000000 --- a/packages/core/src/shader/extractUniformsFromSrc.js +++ /dev/null @@ -1,57 +0,0 @@ -import defaultValue from './defaultValue'; - -export default function extractUniformsFromSrc(vertexSrc, fragmentSrc, mask) -{ - const vertUniforms = extractUniformsFromString(vertexSrc, mask); - const fragUniforms = extractUniformsFromString(fragmentSrc, mask); - - return Object.assign(vertUniforms, fragUniforms); -} - -function extractUniformsFromString(string) -{ - const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); - - const uniforms = {}; - let nameSplit; - - // clean the lines a little - remove extra spaces / teabs etc - // then split along ';' - const lines = string.replace(/\s+/g, ' ') - .split(/\s*;\s*/); - - // loop through.. - for (let i = 0; i < lines.length; i++) - { - const line = lines[i].trim(); - - if (line.indexOf('uniform') > -1) - { - const splitLine = line.split(' '); - const type = splitLine[1]; - - let name = splitLine[2]; - let size = 1; - - if (name.indexOf('[') > -1) - { - // array! - nameSplit = name.split(/\[|]/); - name = nameSplit[0]; - size *= Number(nameSplit[1]); - } - - if (!name.match(maskRegex)) - { - uniforms[name] = { - value: defaultValue(type, size), - dirtyId: 0, - name, - type, - }; - } - } - } - - return uniforms; -} diff --git a/packages/core/src/shader/generateUniformsSync.js b/packages/core/src/shader/generateUniformsSync.js deleted file mode 100644 index 98a357f..0000000 --- a/packages/core/src/shader/generateUniformsSync.js +++ /dev/null @@ -1,231 +0,0 @@ -// cv = CachedValue -// v = value -// ud = uniformData -// uv = uniformValue -// l = loaction -const GLSL_TO_SINGLE_SETTERS_CACHED = { - - float: ` - if(cv !== v) - { - cv.v = v; - gl.uniform1f(location, v) - }`, - - vec2: ` - if(cv[0] !== v[0] || cv[1] !== v[1]) - { - cv[0] = v[0]; - cv[1] = v[1]; - gl.uniform2f(location, v[0], v[1]) - }`, - - vec3: ` - if(cv[0] !== v[0] || cv[1] !== v[1] || cv[2] !== v[2]) - { - cv[0] = v[0]; - cv[1] = v[1]; - cv[2] = v[2]; - - gl.uniform3f(location, v[0], v[1], v[2]) - }`, - - vec4: 'gl.uniform4f(location, v[0], v[1], v[2], v[3])', - - int: 'gl.uniform1i(location, v)', - ivec2: 'gl.uniform2i(location, v[0], v[1])', - ivec3: 'gl.uniform3i(location, v[0], v[1], v[2])', - ivec4: 'gl.uniform4i(location, v[0], v[1], v[2], v[3])', - - bool: 'gl.uniform1i(location, v)', - bvec2: 'gl.uniform2i(location, v[0], v[1])', - bvec3: 'gl.uniform3i(location, v[0], v[1], v[2])', - bvec4: 'gl.uniform4i(location, v[0], v[1], v[2], v[3])', - - mat2: 'gl.uniformMatrix2fv(location, false, v)', - mat3: 'gl.uniformMatrix3fv(location, false, v)', - mat4: 'gl.uniformMatrix4fv(location, false, v)', - - sampler2D: 'gl.uniform1i(location, v)', - samplerCube: 'gl.uniform1i(location, v)', - sampler2DArray: 'gl.uniform1i(location, v)', -}; - -const GLSL_TO_ARRAY_SETTERS = { - - float: `gl.uniform1fv(location, v)`, - - vec2: `gl.uniform2fv(location, v)`, - vec3: `gl.uniform3fv(location, v)`, - vec4: 'gl.uniform4fv(location, v)', - - mat4: 'gl.uniformMatrix4fv(location, false, v)', - mat3: 'gl.uniformMatrix3fv(location, false, v)', - mat2: 'gl.uniformMatrix2fv(location, false, v)', - - int: 'gl.uniform1iv(location, v)', - ivec2: 'gl.uniform2iv(location, v)', - ivec3: 'gl.uniform3iv(location, v)', - ivec4: 'gl.uniform4iv(location, v)', - - bool: 'gl.uniform1iv(location, v)', - bvec2: 'gl.uniform2iv(location, v)', - bvec3: 'gl.uniform3iv(location, v)', - bvec4: 'gl.uniform4iv(location, v)', - - sampler2D: 'gl.uniform1iv(location, v)', - samplerCube: 'gl.uniform1iv(location, v)', - sampler2DArray: 'gl.uniform1iv(location, v)', -}; - -export default function generateUniformsSync(group, uniformData) -{ - let textureCount = 0; - let func = `var v = null; - var cv = null - var gl = renderer.gl`; - - for (const i in group.uniforms) - { - const data = uniformData[i]; - - if (!data) - { - if (group.uniforms[i].group) - { - func += ` - renderer.shader.syncUniformGroup(uv.${i}); - `; - } - - continue; - } - - // TODO && uniformData[i].value !== 0 <-- do we still need this? - if (data.type === 'float' && data.size === 1) - { - func += ` - if(uv.${i} !== ud.${i}.value) - { - ud.${i}.value = uv.${i} - gl.uniform1f(ud.${i}.location, uv.${i}) - }\n`; - } - /* eslint-disable max-len */ - else if ((data.type === 'sampler2D' || data.type === 'samplerCube' || data.type === 'sampler2DArray') && data.size === 1 && !data.isArray) - /* eslint-disable max-len */ - { - func += ` - renderer.texture.bind(uv.${i}, ${textureCount}); - - if(ud.${i}.value !== ${textureCount}) - { - ud.${i}.value = ${textureCount}; - gl.uniform1i(ud.${i}.location, ${textureCount});\n; // eslint-disable-line max-len - }\n`; - - textureCount++; - } - else if (data.type === 'mat3' && data.size === 1) - { - if (group.uniforms[i].a !== undefined) - { - // TODO and some smart caching dirty ids here! - func += ` - gl.uniformMatrix3fv(ud.${i}.location, false, uv.${i}.toArray(true)); - \n`; - } - else - { - func += ` - gl.uniformMatrix3fv(ud.${i}.location, false, uv.${i}); - \n`; - } - } - else if (data.type === 'vec2' && data.size === 1) - { - // TODO - do we need both here? - // maybe we can get away with only using points? - if (group.uniforms[i].x !== undefined) - { - func += ` - cv = ud.${i}.value; - v = uv.${i}; - - if(cv[0] !== v.x || cv[1] !== v.y) - { - cv[0] = v.x; - cv[1] = v.y; - gl.uniform2f(ud.${i}.location, v.x, v.y); - }\n`; - } - else - { - func += ` - cv = ud.${i}.value; - v = uv.${i}; - - if(cv[0] !== v[0] || cv[1] !== v[1]) - { - cv[0] = v[0]; - cv[1] = v[1]; - gl.uniform2f(ud.${i}.location, v[0], v[1]); - } - \n`; - } - } - else if (data.type === 'vec4' && data.size === 1) - { - // TODO - do we need both here? - // maybe we can get away with only using points? - if (group.uniforms[i].width !== undefined) - { - func += ` - cv = ud.${i}.value; - v = uv.${i}; - - if(cv[0] !== v.x || cv[1] !== v.y || cv[2] !== v.width || cv[3] !== v.height) - { - cv[0] = v.x; - cv[1] = v.y; - cv[2] = v.width; - cv[3] = v.height; - gl.uniform4f(ud.${i}.location, v.x, v.y, v.width, v.height) - }\n`; - } - else - { - func += ` - cv = ud.${i}.value; - v = uv.${i}; - - if(cv[0] !== v[0] || cv[1] !== v[1] || cv[2] !== v[2] || cv[3] !== v[3]) - { - cv[0] = v[0]; - cv[1] = v[1]; - cv[2] = v[2]; - cv[3] = v[3]; - - gl.uniform4f(ud.${i}.location, v[0], v[1], v[2], v[3]) - } - \n`; - } - } - else - { - const templateType = (data.size === 1) ? GLSL_TO_SINGLE_SETTERS_CACHED : GLSL_TO_ARRAY_SETTERS; - - const template = templateType[data.type].replace('location', `ud.${i}.location`); - - func += ` - cv = ud.${i}.value; - v = uv.${i}; - ${template};\n`; - } - } - - // console.log(' --------------- ') - // console.log(func); - - return new Function('ud', 'uv', 'renderer', func); // eslint-disable-line no-new-func -} diff --git a/packages/core/src/shader/getTestContext.js b/packages/core/src/shader/getTestContext.js deleted file mode 100644 index ea174cc..0000000 --- a/packages/core/src/shader/getTestContext.js +++ /dev/null @@ -1,48 +0,0 @@ -import { settings } from '@pixi/settings'; - -let context = null; - -/** - * returns a little webGL context to use for program inspection. - * - * @static - * @private - * @returns {webGL-context} a gl context to test with - */ -export default function getTestContext() -{ - if (!context) - { - const canvas = document.createElement('canvas'); - - let gl; - - if (settings.PREFER_WEBGL_2) - { - gl = canvas.getContext('webgl2', {}); - } - - if (!gl) - { - gl = canvas.getContext('webgl', {}) - || canvas.getContext('experimental-webgl', {}); - - if (!gl) - { - // fail, not able to get a context - throw new Error('This browser does not support webGL. Try using the canvas renderer'); - } - else - { - // for shader testing.. - gl.getExtension('WEBGL_draw_buffers'); - } - } - - context = gl; - - return gl; - } - - return context; -} diff --git a/packages/core/src/shader/utils/checkMaxIfStatmentsInShader.js b/packages/core/src/shader/utils/checkMaxIfStatmentsInShader.js new file mode 100644 index 0000000..8047c96 --- /dev/null +++ b/packages/core/src/shader/utils/checkMaxIfStatmentsInShader.js @@ -0,0 +1,58 @@ +const fragTemplate = [ + 'precision mediump float;', + 'void main(void){', + 'float test = 0.1;', + '%forloop%', + 'gl_FragColor = vec4(0.0);', + '}', +].join('\n'); + +export default function checkMaxIfStatmentsInShader(maxIfs, gl) +{ + if (maxIfs === 0) + { + throw new Error('Invalid value of `0` passed to `checkMaxIfStatementsInShader`'); + } + + const shader = gl.createShader(gl.FRAGMENT_SHADER); + + while (true) // eslint-disable-line no-constant-condition + { + const fragmentSrc = fragTemplate.replace(/%forloop%/gi, generateIfTestSrc(maxIfs)); + + gl.shaderSource(shader, fragmentSrc); + gl.compileShader(shader); + + if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) + { + maxIfs = (maxIfs / 2) | 0; + } + else + { + // valid! + break; + } + } + + return maxIfs; +} + +function generateIfTestSrc(maxIfs) +{ + let src = ''; + + for (let i = 0; i < maxIfs; ++i) + { + if (i > 0) + { + src += '\nelse '; + } + + if (i < maxIfs - 1) + { + src += `if(test == ${i}.0){}`; + } + } + + return src; +} diff --git a/packages/core/src/shader/utils/compileProgram.js b/packages/core/src/shader/utils/compileProgram.js new file mode 100644 index 0000000..8c6ccb9 --- /dev/null +++ b/packages/core/src/shader/utils/compileProgram.js @@ -0,0 +1,78 @@ +/** + * @method compileProgram + * @memberof PIXI.glCore.shader + * @param gl {WebGLRenderingContext} The current WebGL context {WebGLProgram} + * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. + * @param fragmentSrc {string|string[]} The fragment shader source as an array of strings. + * @param attributeLocations {Object} An attribute location map that lets you manually set the attribute locations + * @return {WebGLProgram} the shader program + */ +export default function compileProgram(gl, vertexSrc, fragmentSrc, attributeLocations) +{ + const glVertShader = compileShader(gl, gl.VERTEX_SHADER, vertexSrc); + const glFragShader = compileShader(gl, gl.FRAGMENT_SHADER, fragmentSrc); + + let program = gl.createProgram(); + + gl.attachShader(program, glVertShader); + gl.attachShader(program, glFragShader); + + // optionally, set the attributes manually for the program rather than letting WebGL decide.. + if (attributeLocations) + { + for (const i in attributeLocations) + { + gl.bindAttribLocation(program, attributeLocations[i], i); + } + } + + gl.linkProgram(program); + + // if linking fails, then log and cleanup + if (!gl.getProgramParameter(program, gl.LINK_STATUS)) + { + console.error('Pixi.js Error: Could not initialize shader.'); + console.error('gl.VALIDATE_STATUS', gl.getProgramParameter(program, gl.VALIDATE_STATUS)); + console.error('gl.getError()', gl.getError()); + + // if there is a program info log, log it + if (gl.getProgramInfoLog(program) !== '') + { + console.warn('Pixi.js Warning: gl.getProgramInfoLog()', gl.getProgramInfoLog(program)); + } + + gl.deleteProgram(program); + program = null; + } + + // clean up some shaders + gl.deleteShader(glVertShader); + gl.deleteShader(glFragShader); + + return program; +} + +/** + * @private + * @param gl {WebGLRenderingContext} The current WebGL context {WebGLProgram} + * @param type {Number} the type, can be either VERTEX_SHADER or FRAGMENT_SHADER + * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. + * @return {WebGLShader} the shader + */ +function compileShader(gl, type, src) +{ + const shader = gl.createShader(type); + + gl.shaderSource(shader, src); + gl.compileShader(shader); + + if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) + { + console.warn(src); + console.error(gl.getShaderInfoLog(shader)); + + return null; + } + + return shader; +} diff --git a/packages/core/src/AbstractRenderer.js b/packages/core/src/AbstractRenderer.js new file mode 100644 index 0000000..64b2eb1 --- /dev/null +++ b/packages/core/src/AbstractRenderer.js @@ -0,0 +1,353 @@ +import { hex2string, hex2rgb } from '@pixi/utils'; +import { Matrix, Rectangle } from '@pixi/math'; +import { RENDERER_TYPE } from '@pixi/constants'; +import { settings } from '@pixi/settings'; +import { Container } from '@pixi/display'; +import RenderTexture from './renderTexture/RenderTexture'; +import EventEmitter from 'eventemitter3'; + +const tempMatrix = new Matrix(); + +/** + * 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 EventEmitter + * @memberof PIXI + */ +export default class AbstractRenderer extends EventEmitter +{ + // eslint-disable-next-line valid-jsdoc + /** + * @param {string} system - The name of the system this renderer is for. + * @param {object} [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.transparent=false] - If the render view is transparent, default false + * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false + * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. The + * resolution of the renderer retina would be 2. + * @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 {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when rendering, + * stopping pixel interpolation. + */ + constructor(system, options, arg2, arg3) + { + super(); + + // Support for constructor(system, screenWidth, screenHeight, options) + if (typeof options === 'number') + { + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + }, arg3); + } + + // 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 = RENDERER_TYPE.UNKNOWN; + + /** + * Measurements of the screen. (0, 0, screenWidth, screenHeight) + * + * Its safe to use as filterArea or hitArea for 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 1 + */ + this.resolution = options.resolution || settings.RESOLUTION; + + /** + * Whether the render view is transparent + * + * @member {boolean} + */ + this.transparent = options.transparent; + + /** + * Whether css dimensions of canvas view should be resized to screen dimensions automatically + * + * @member {boolean} + */ + this.autoResize = options.autoResize || false; + + /** + * Tracks the blend modes useful for this renderer. + * + * @member {object} + */ + this.blendModes = null; + + /** + * 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; + + /** + * If true PixiJS will Math.floor() x/y values when rendering, stopping pixel interpolation. + * Handy for crisp pixel art and speed on legacy devices. + * + * @member {boolean} + */ + this.roundPixels = options.roundPixels; + + /** + * The background color as a number. + * + * @member {number} + * @private + */ + this._backgroundColor = 0x000000; + + /** + * The background color as an [R, G, B] array. + * + * @member {number[]} + * @private + */ + this._backgroundColorRgba = [0, 0, 0, 0]; + + /** + * The background color as a string. + * + * @member {string} + * @private + */ + this._backgroundColorString = '#000000'; + + this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter + + /** + * This temporary display object used as the parent of the currently being rendered item + * + * @member {PIXI.DisplayObject} + * @private + */ + this._tempDisplayObjectParent = new Container(); + + /** + * The last root object that the renderer tried to render. + * + * @member {PIXI.DisplayObject} + * @private + */ + this._lastObjectRendered = this._tempDisplayObjectParent; + + /** + * Collection of plugins. + * @readonly + * @member {object} + */ + this.plugins = {}; + } + + /** + * Initialize the plugins. + * + * @protected + * @param {object} staticMap - The dictionary of staticly saved plugins. + */ + initPlugins(staticMap) + { + 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() + { + return this.view.width; + } + + /** + * Same as view.height, actual number of pixels in the canvas by vertical + * + * @member {number} + * @readonly + * @default 600 + */ + get height() + { + return this.view.height; + } + + /** + * Resizes the screen and canvas to the specified width and height + * Canvas dimensions are multiplied by resolution + * + * @param {number} screenWidth - the new width of the screen + * @param {number} screenHeight - the new height of the screen + */ + resize(screenWidth, screenHeight) + { + this.screen.width = screenWidth; + this.screen.height = screenHeight; + + this.view.width = screenWidth * this.resolution; + this.view.height = screenHeight * this.resolution; + + if (this.autoResize) + { + this.view.style.width = `${screenWidth}px`; + this.view.style.height = `${screenHeight}px`; + } + } + + /** + * 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 {PIXI.DisplayObject} displayObject - The displayObject the object will be generated from + * @param {number} scaleMode - Should be one of the scaleMode consts + * @param {number} resolution - The resolution / device pixel ratio of the texture being generated + * @param {PIXI.Rectangle} [region] - The region of the displayObject, that shall be rendered, + * if no region is specified, defaults to the local bounds of the displayObject. + * @return {PIXI.Texture} a texture of the graphics object + */ + generateTexture(displayObject, scaleMode, resolution, region) + { + region = region || displayObject.getLocalBounds(); + + // 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(region.width | 0, region.height | 0, scaleMode, resolution); + + tempMatrix.tx = -region.x; + tempMatrix.ty = -region.y; + + this.render(displayObject, renderTexture, false, tempMatrix, true); + + return renderTexture; + } + + /** + * Removes everything from the renderer and optionally removes the Canvas DOM element. + * + * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. + */ + destroy(removeView) + { + 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); + } + + this.plugins = null; + + this.type = RENDERER_TYPE.UNKNOWN; + + this.view = null; + + this.screen = null; + + this.resolution = 0; + + this.transparent = false; + + this.autoResize = false; + + this.blendModes = null; + + this.options = null; + + this.preserveDrawingBuffer = false; + this.clearBeforeRender = false; + + this.roundPixels = false; + + this._backgroundColor = 0; + this._backgroundColorRgba = null; + this._backgroundColorString = null; + + this._tempDisplayObjectParent = null; + this._lastObjectRendered = null; + } + + /** + * The background color to fill if not transparent + * + * @member {number} + */ + get backgroundColor() + { + return this._backgroundColor; + } + + set backgroundColor(value) // eslint-disable-line require-jsdoc + { + this._backgroundColor = value; + this._backgroundColorString = hex2string(value); + hex2rgb(value, this._backgroundColorRgba); + } +} diff --git a/packages/core/src/Renderer.js b/packages/core/src/Renderer.js new file mode 100644 index 0000000..2227e78 --- /dev/null +++ b/packages/core/src/Renderer.js @@ -0,0 +1,338 @@ +import AbstractRenderer from './AbstractRenderer'; +import { sayHello } from '@pixi/utils'; +import MaskSystem from './mask/MaskSystem'; +import StencilSystem from './mask/StencilSystem'; +import FilterSystem from './filters/FilterSystem'; +import FramebufferSystem from './framebuffer/FramebufferSystem'; +import RenderTextureSystem from './renderTexture/RenderTextureSystem'; +import TextureSystem from './textures/TextureSystem'; +import ProjectionSystem from './projection/ProjectionSystem'; +import StateSystem from './state/StateSystem'; +import GeometrySystem from './geometry/GeometrySystem'; +import ShaderSystem from './shader/ShaderSystem'; +import ContextSystem from './context/ContextSystem'; +import BatchSystem from './batch/BatchSystem'; +import TextureGCSystem from './textures/TextureGCSystem'; +import { RENDERER_TYPE } from '@pixi/constants'; +import UniformGroup from './shader/UniformGroup'; +import { Matrix } from '@pixi/math'; +import Runner from 'mini-runner'; + +/** + * 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 Render works by automatically managing webGLBatchs. + * 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 :) + * + * @class + * @memberof PIXI + * @extends PIXI.AbstractRenderer + */ +export default class Renderer extends AbstractRenderer +{ + // eslint-disable-next-line valid-jsdoc + /** + * + * @param {object} [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.transparent=false] - If the render view is transparent, default false + * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false + * @param {boolean} [options.antialias=false] - sets antialias. If not available natively then FXAA + * antialiasing is used + * @param {boolean} [options.forceFXAA=false] - forces FXAA antialiasing to be used over native. + * FXAA is faster, but may not always look as great + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. + * The resolution of the renderer retina would be 2. + * @param {boolean} [options.clearBeforeRender=true] - This sets if the renderer will clear + * the canvas or not before the new render pass. If you wish to set this to false, you *must* set + * preserveDrawingBuffer to `true`. + * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, + * enable this if you need to call toDataUrl on the webgl context. + * @param {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when + * rendering, stopping pixel interpolation. + * @param {number} [options.backgroundColor=0x000000] - The background color of the rendered area + * (shown if not transparent). + * @param {boolean} [options.legacy=false] - If true PixiJS will aim to ensure compatibility + * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. + * @param {string} [options.powerPreference] - Parameter passed to webgl context, set to "high-performance" + * for devices with dual graphics card + */ + constructor(options, arg2, arg3) + { + super('WebGL', options, arg2, arg3); + + /** + * The type of this renderer as a standardised const + * + * @member {number} + * @see PIXI.RENDERER_TYPE + */ + this.type = RENDERER_TYPE.WEBGL; + + // this will be set by the contextSystem (this.context) + this.gl = null; + this.CONTEXT_UID = 0; + this.legacy = !!options.legacy; + + // TODO legacy! + + // runners! + this.runners = { + destroy: new Runner('destroy'), + contextChange: new Runner('contextChange', 1), + reset: new Runner('reset'), + update: new Runner('update'), + postrender: new Runner('postrender'), + prerender: new Runner('prerender'), + resize: new Runner('resize', 2), + }; + + 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(GeometrySystem, 'geometry') + .addSystem(FramebufferSystem, 'framebuffer') + .addSystem(StencilSystem, 'stencil') + .addSystem(ProjectionSystem, 'projection') + .addSystem(TextureGCSystem, 'textureGC') + .addSystem(FilterSystem, 'filter') + .addSystem(RenderTextureSystem, 'renderTexture') + .addSystem(BatchSystem, 'batch'); + + this.initPlugins(Renderer.__plugins); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ + if (options.context) + { + this.context.initFromContext(options.context); + } + else + { + this.context.initFromOptions({ + alpha: this.transparent, + antialias: options.antialias, + premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', + stencil: true, + 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); + } + + /** + * Add a new system to the renderer. + * @param {class} ClassRef - Class reference + * @param {string} [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. + * @return {PIXI.Renderer} Return instance of renderer + */ + addSystem(ClassRef, name) + { + if (!name) + { + name = ClassRef.name; + } + + // TODO - read name from class.name.. + + /* + if(name.includes('System')) + { + name = name.replace('System', ''); + name = name.charAt(0).toLowerCase() + name.slice(1); + } + */ + + const system = new ClassRef(this); + + if (this[name]) + { + throw new Error(`Whoops! ${name} is already a manger`); + } + + this[name] = system; + + for (const i in this.runners) + { + this.runners[i].add(system); + } + + return this; + + /** + * 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. + */ + } + + /** + * Renders the object to its webGL view + * + * @param {PIXI.DisplayObject} displayObject - the object to be rendered + * @param {PIXI.RenderTexture} renderTexture - The render texture to render to. + * @param {boolean} [clear] - Should the canvas be cleared before the new render + * @param {PIXI.Transform} [transform] - A transform to apply to the render texture before rendering. + * @param {boolean} [skipUpdateTransform] - Should we skip the update transform pass? + */ + render(displayObject, renderTexture, clear, transform, skipUpdateTransform) + { + // can be handy to know! + this.renderingToScreen = !renderTexture; + + this.runners.prerender.run(); + this.emit('prerender'); + + // 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.parent; + + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = 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.run(); + + this.emit('postrender'); + } + + /** + * Resizes the webGL view to the specified width and height. + * + * @param {number} screenWidth - the new width of the screen + * @param {number} screenHeight - the new height of the screen + */ + resize(screenWidth, screenHeight) + { + AbstractRenderer.prototype.resize.call(this, screenWidth, screenHeight); + + this.runners.resize.run(screenWidth, screenHeight); + } + + /** + * Resets the WebGL state so you can render things however you fancy! + * + * @return {PIXI.Renderer} Returns itself. + */ + reset() + { + this.runners.reset.run(); + + return this; + } + + clear() + { + this.framebuffer.bind(); + this.framebuffer.clear(); + } + + /** + * Removes everything from the renderer (event listeners, spritebatch, etc...) + * + * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. + * See: https://github.com/pixijs/pixi.js/issues/2233 + */ + destroy(removeView) + { + this.runners.destroy.run(); + + // call base destroy + super.destroy(removeView); + + // TODO nullify all the managers.. + this.gl = null; + } + + /** + * 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 + * builds and excluding plugins. + * @name PIXI.Renderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. + */ + + /** + * Adds a plugin to the renderer. + * + * @method + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + static registerPlugin(pluginName, ctor) + { + Renderer.__plugins = Renderer.__plugins || {}; + Renderer.__plugins[pluginName] = ctor; + } +} diff --git a/packages/core/src/System.js b/packages/core/src/System.js new file mode 100644 index 0000000..05a10c8 --- /dev/null +++ b/packages/core/src/System.js @@ -0,0 +1,42 @@ +/** + * System is a base class used for extending systems used by the {@link PIXI.Renderer} + * @see PIXI.Renderer#addSystem + * @class + * @memberof PIXI + */ +export default class System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + /** + * The renderer this manager works for. + * + * @member {PIXI.Renderer} + */ + this.renderer = renderer; + + this.renderer.runners.contextChange.add(this); + } + + /** + * Generic method called when there is a WebGL context change. + * + */ + contextChange() + { + // do some codes init! + } + + /** + * Generic destroy methods to be overridden by the subclass + * + */ + destroy() + { + this.renderer.runners.contextChange.remove(this); + this.renderer = null; + } +} diff --git a/packages/core/src/batch/BatchSystem.js b/packages/core/src/batch/BatchSystem.js new file mode 100644 index 0000000..b04c249 --- /dev/null +++ b/packages/core/src/batch/BatchSystem.js @@ -0,0 +1,68 @@ +import System from '../System'; +import ObjectRenderer from './ObjectRenderer'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class BatchSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + super(renderer); + + /** + * An empty renderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.emptyRenderer = new ObjectRenderer(renderer); + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.currentRenderer = this.emptyRenderer; + } + + /** + * Changes the current renderer to the one given in parameter + * + * @param {PIXI.ObjectRenderer} objectRenderer - The object renderer to use. + */ + setObjectRenderer(objectRenderer) + { + if (this.currentRenderer === objectRenderer) + { + return; + } + + this.currentRenderer.stop(); + this.currentRenderer = objectRenderer; + + this.renderer.state.setState(objectRenderer.state); + + this.currentRenderer.start(); + } + + /** + * This should be called if you wish to do some custom rendering + * It will basically render anything that may be batched up such as sprites + * + */ + flush() + { + this.setObjectRenderer(this.emptyRenderer); + } + + reset() + { + this.setObjectRenderer(this.emptyRenderer); + } +} diff --git a/packages/core/src/batch/ObjectRenderer.js b/packages/core/src/batch/ObjectRenderer.js new file mode 100644 index 0000000..0c851a9 --- /dev/null +++ b/packages/core/src/batch/ObjectRenderer.js @@ -0,0 +1,48 @@ +import System from '../System'; + +/** + * Base for a common object renderer that can be used as a system renderer plugin. + * + * @class + * @extends PIXI.System + * @memberof PIXI + */ +export default class ObjectRenderer extends System +{ + /** + * Starts the renderer and sets the shader + * + */ + start() + { + // set the shader.. + } + + /** + * Stops the renderer + * + */ + stop() + { + this.flush(); + } + + /** + * Stub method for rendering content and emptying the current batch. + * + */ + flush() + { + // flush! + } + + /** + * Renders an object + * + * @param {PIXI.DisplayObject} object - The object to render. + */ + render(object) // eslint-disable-line no-unused-vars + { + // render the object + } +} diff --git a/packages/core/src/context/ContextSystem.js b/packages/core/src/context/ContextSystem.js new file mode 100644 index 0000000..301aab4 --- /dev/null +++ b/packages/core/src/context/ContextSystem.js @@ -0,0 +1,186 @@ +import System from '../System'; +import { settings } from '@pixi/settings'; + +let CONTEXT_UID = 0; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class ContextSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.webGLVersion = 1; + + this.handleContextLost = this.handleContextLost.bind(this); + this.handleContextRestored = this.handleContextRestored.bind(this); + + this.extensions = {}; + + renderer.view.addEventListener('webglcontextlost', this.handleContextLost, false); + renderer.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + } + + get isLost() + { + return (!this.gl || this.gl.isContextLost()); + } + + contextChange(gl) + { + this.gl = gl; + + // restore a context if it was previously lost + if (gl.isContextLost() && gl.getExtension('WEBGL_lose_context')) + { + gl.getExtension('WEBGL_lose_context').restoreContext(); + } + } + + initFromContext(gl) + { + this.gl = gl; + this.validateContext(gl); + this.renderer.gl = gl; + this.renderer.CONTEXT_UID = CONTEXT_UID++; + this.renderer.runners.contextChange.run(gl); + } + + initFromOptions(options) + { + const gl = this.createContext(this.renderer.view, options); + + this.initFromContext(gl); + } + + /** + * Helper class to create a webGL Context + * + * @class + * @memberof PIXI.glCore + * @param canvas {HTMLCanvasElement} the canvas element that we will get the context from + * @param options {Object} An options object that gets passed in to the canvas element containing the context attributes, + * see https://developer.mozilla.org/en/docs/Web/API/HTMLCanvasElement/getContext for the options available + * @return {WebGLRenderingContext} the WebGL context + */ + createContext(canvas, options) + { + let gl; + + if (settings.PREFER_WEBGL_2) + { + gl = canvas.getContext('webgl2', options); + } + + if (gl) + { + this.webGLVersion = 2; + } + else + { + this.webGLVersion = 1; + + gl = canvas.getContext('webgl', options) + || canvas.getContext('experimental-webgl', options); + + if (!gl) + { + // fail, not able to get a context + throw new Error('This browser does not support webGL. Try using the canvas renderer'); + } + } + + this.gl = gl; + + this.getExtensions(); + + return gl; + } + + getExtensions() + { + // time to set up default etensions that pixi uses.. + const gl = this.gl; + const extensions = this.extensions; + + if (this.webGLVersion === 1) + { + extensions.drawBuffers = gl.getExtension('WEBGL_draw_buffers'); + extensions.depthTexture = gl.getExtension('WEBKIT_WEBGL_depth_texture'); + extensions.floatTexture = gl.getExtension('OES_texture_float'); + extensions.loseContext = gl.getExtension('WEBGL_lose_context'); + + extensions.vertexArrayObject = gl.getExtension('OES_vertex_array_object') + || gl.getExtension('MOZ_OES_vertex_array_object') + || gl.getExtension('WEBKIT_OES_vertex_array_object'); + } + + // we don't use any specific WebGL 2 ones yet! + } + + /** + * Handles a lost webgl context + * + * @private + * @param {WebGLContextEvent} event - The context lost event. + */ + handleContextLost(event) + { + event.preventDefault(); + } + + /** + * Handles a restored webgl context + * + * @private + */ + handleContextRestored() + { + this.renderer.runners.contextChange.run(this.gl); + } + + destroy() + { + const view = this.renderer.view; + + // remove listeners + view.removeEventListener('webglcontextlost', this.handleContextLost); + view.removeEventListener('webglcontextrestored', this.handleContextRestored); + + this.gl.useProgram(null); + + if (this.extensions.loseContext) + { + this.extensions.loseContext.loseContext(); + } + } + + postrender() + { + this.gl.flush(); + } + + validateContext(gl) + { + const attributes = gl.getContextAttributes(); + + // this is going to be fairly simple for now.. but at least we have room to grow! + if (!attributes.stencil) + { + /* eslint-disable max-len */ + + /* eslint-disable no-console */ + console.warn('Provided WebGL context does not have a stencil buffer, masks may not render correctly'); + /* eslint-enable no-console */ + + /* eslint-enable max-len */ + } + } +} diff --git a/packages/core/src/filters/Filter.js b/packages/core/src/filters/Filter.js new file mode 100644 index 0000000..9d8cad8 --- /dev/null +++ b/packages/core/src/filters/Filter.js @@ -0,0 +1,140 @@ +import Shader from '../shader/Shader'; +import Program from '../shader/Program'; +import State from '../state/State'; +import { settings } from '@pixi/settings'; +// import extractUniformsFromSrc from './extractUniformsFromSrc'; +import defaultVertex from './defaultFilter.vert'; +import defaultFragment from './defaultFilter.frag'; + +// let math = require('../../math'); +/** + * @class + * @memberof PIXI + * @extends PIXI.Shader + */ +export default class Filter extends Shader +{ + /** + * @param {string} [vertexSrc] - The source of the vertex shader. + * @param {string} [fragmentSrc] - The source of the fragment shader. + * @param {object} [uniforms] - Custom uniforms to use to augment the built-in ones. + */ + constructor(vertexSrc, fragmentSrc, uniforms) + { + const program = Program.from(vertexSrc, fragmentSrc); + + super(program, uniforms); + + /** + * The padding of the filter. Some filters require extra space to breath such as a blur. + * Increasing this will add extra width and height to the bounds of the object that the + * filter is applied to. + * + * @member {number} + */ + this.padding = 0; + + /** + * The resolution of the filter. Setting this to be lower will lower the quality but + * increase the performance of the filter. + * + * @member {number} + */ + this.resolution = settings.RESOLUTION; + + /** + * If enabled is true the filter is applied, if false it will not. + * + * @member {boolean} + */ + this.enabled = true; + + /** + * If enabled, PixiJS will fit the filter area into boundaries for better performance. + * Switch it off if it does not work for specific shader. + * + * @member {boolean} + */ + this.autoFit = true; + + /** + * Legacy filters use position and uvs from attributes + * @member {boolean} + * @readonly + */ + this.legacy = !!this.program.attributeData.aTextureCoord; + + /** + * the webGL state the filter requires to render + * @member {PIXI.State} + */ + this.state = new State(); + } + + /** + * Applies the filter + * + * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + * @param {boolean} clear - Should the output be cleared before rendering to it + * @param {object} [currentState] - It's current state of filter. + * There are some useful properties in the currentState : + * target, filters, sourceFrame, destinationFrame, renderTarget, resolution + */ + apply(filterManager, input, output, clear, currentState, derp) // eslint-disable-line no-unused-vars + { + // do as you please! + + filterManager.applyFilter(this, input, output, clear, currentState, derp); + + // or just do a regular render.. + } + + /** + * Sets the blendmode of the filter + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL + */ + get blendMode() + { + return this.state.blendMode; + } + + set blendMode(value) // eslint-disable-line require-jsdoc + { + this.state.blendMode = value; + } + + /** + * The default vertex shader source + * + * @static + * @constant + */ + static get defaultVertexSrc() + { + return defaultVertex; + } + + /** + * The default fragment shader source + * + * @static + * @constant + */ + static get defaultFragmentSrc() + { + return defaultFragment; + } +} + +/** + * Used for caching shader IDs + * + * @static + * @private + */ +Filter.SOURCE_KEY_MAP = {}; + diff --git a/packages/core/src/filters/FilterSystem.js b/packages/core/src/filters/FilterSystem.js new file mode 100644 index 0000000..3174318 --- /dev/null +++ b/packages/core/src/filters/FilterSystem.js @@ -0,0 +1,431 @@ +import System from '../System'; + +import RenderTexture from '../renderTexture/RenderTexture'; +import Quad from '../utils/Quad'; +import QuadUv from '../utils/QuadUv'; +import { Rectangle } from '@pixi/math'; +import * as filterTransforms from './filterTransforms'; +import bitTwiddle from 'bit-twiddle'; +import UniformGroup from '../shader/UniformGroup'; +import { DRAW_MODES } from '@pixi/constants'; + +// +/** + * @ignore + * @class + */ +class FilterState +{ + /** + * + */ + constructor() + { + this.renderTexture = null; + this.sourceFrame = new Rectangle(); + this.destinationFrame = new Rectangle(); + this.filters = []; + this.target = null; + this.legacy = false; + this.resolution = 1; + } +} + +/** + * @class + * @memberof PIXI.systems + * @extends PIXI.System + */ +export default class FilterSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + /** + * stores a bunch of PO2 textures used for filtering + * @type {Object} + */ + this.texturePool = {}; + + /** + * a pool for storing filter states, save us creating new ones each tick + * @type {Array} + */ + this.statePool = []; + + /** + * A very simple geometry used when drawing a filter effect to the screen + * @type {Quad} + */ + this.quad = new Quad(); + + this.quadUv = new QuadUv(); + + /** + * Temporary rect for maths + * @type {PIXI.Rectangle} + */ + this.tempRect = new Rectangle(); + + this.activeState = {}; + + /** + * this uniform group is attached to filter uniforms when used + * @type {UniformGroup} + */ + this.globalUniforms = new UniformGroup({ + sourceFrame: this.tempRect, + destinationFrame: this.tempRect, + + // legacy variables + filterArea: new Float32Array(4), + filterClamp: new Float32Array(4), + }, true); + } + + /** + * Adds a new filter to the System. + * + * @param {PIXI.DisplayObject} target - The target of the filter to render. + * @param {PIXI.Filter[]} filters - The filters to apply. + */ + push(target, filters) + { + const renderer = this.renderer; + const filterStack = this.renderer.renderTexture.defaultFilterStack; + const state = this.statePool.pop() || new FilterState(); + + let resolution = filters[0].resolution; + let padding = filters[0].padding; + let autoFit = filters[0].autoFit; + let legacy = filters[0].legacy; + + for (let i = 1; i < filters.length; i++) + { + const filter = filters[i]; + + // lets use the lowest resolution.. + resolution = Math.min(resolution, filter.resolution); + // and the largest amount of padding! + padding = Math.max(padding, filter.padding); + // only auto fit if all filters are autofit + autoFit = autoFit || filter.autoFit; + + legacy = legacy || filter.legacy; + } + + filterStack.push(state); + + state.resolution = resolution; + + state.legacy = legacy; + + // round to whole number based on resolution + // TODO move that to the shader too? + state.sourceFrame = target.filterArea || target.getBounds(true); + + state.sourceFrame.pad(padding); + + if (autoFit) + { + state.sourceFrame.fit(this.renderer.renderTexture.destinationFrame); + } + + state.sourceFrame.round(resolution); + + state.renderTexture = this.getPotFilterTexture(state.sourceFrame.width, state.sourceFrame.height, resolution); + state.filters = filters; + + state.destinationFrame.width = state.renderTexture.width; + state.destinationFrame.height = state.renderTexture.height; + + state.renderTexture.filterFrame = state.sourceFrame; + renderer.renderTexture.bind(state.renderTexture, state.sourceFrame);// /, state.destinationFrame); + renderer.renderTexture.clear(); + } + + /** + * Pops off the filter and applies it. + * + */ + pop() + { + const renderer = this.renderer; + const filterStack = renderer.renderTexture.defaultFilterStack; + const state = filterStack.pop(); + const filters = state.filters; + + this.activeState = state; + + const globalUniforms = this.globalUniforms.uniforms; + + globalUniforms.sourceFrame = state.sourceFrame; + globalUniforms.destinationFrame = state.destinationFrame; + globalUniforms.resolution = state.resolution; + + // only update the rect if its legacy.. + if (state.legacy) + { + const filterArea = globalUniforms.filterArea; + const filterClamp = globalUniforms.filterClamp; + + filterArea[0] = state.destinationFrame.width; + filterArea[1] = state.destinationFrame.height; + filterArea[2] = state.sourceFrame.x; + filterArea[3] = state.sourceFrame.y; + + filterClamp[0] = 0.5 / state.resolution / state.destinationFrame.width; + filterClamp[1] = 0.5 / state.resolution / state.destinationFrame.height; + filterClamp[2] = (state.sourceFrame.width - 0.5) / state.resolution / state.destinationFrame.width; + filterClamp[3] = (state.sourceFrame.height - 0.5) / state.resolution / state.destinationFrame.height; + } + + this.globalUniforms.update(); + + const lastState = filterStack[filterStack.length - 1]; + + if (filters.length === 1) + { + filters[0].apply(this, state.renderTexture, lastState.renderTexture, false, state); + + this.returnFilterTexture(state.renderTexture); + } + else + { + let flip = state.renderTexture; + let flop = this.getPotFilterTexture( + flip.width, + flip.height, + state.resolution + ); + + flop.filterFrame = flip.filterFrame; + + let i = 0; + + for (i = 0; i < filters.length - 1; ++i) + { + filters[i].apply(this, flip, flop, true, state); + + const t = flip; + + flip = flop; + flop = t; + } + + filters[i].apply(this, flip, lastState.renderTexture, false, state); + + this.returnFilterTexture(flip); + this.returnFilterTexture(flop); + } + + this.statePool.push(state); + } + + /** + * Draws a filter. + * + * @param {PIXI.Filter} filter - The filter to draw. + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + * @param {boolean} clear - Should the output be cleared before rendering to it + */ + applyFilter(filter, input, output, clear) + { + const renderer = this.renderer; + + renderer.renderTexture.bind(output, output ? output.filterFrame : null); + + if (clear) + { + // gl.disable(gl.SCISSOR_TEST); + renderer.renderTexture.clear(); + // gl.enable(gl.SCISSOR_TEST); + } + + // set the uniforms.. + filter.uniforms.uSampler = input; + filter.uniforms.filterGlobals = this.globalUniforms; + + // TODO make it so that the order of this does not matter.. + // because it does at the moment cos of global uniforms. + // they need to get resynced + + renderer.state.setState(filter.state); + renderer.shader.bind(filter); + + if (filter.legacy) + { + this.quadUv.map(input._frame, input.filterFrame); + + renderer.geometry.bind(this.quadUv); + renderer.geometry.draw(DRAW_MODES.TRIANGLES); + } + else + { + renderer.geometry.bind(this.quad); + renderer.geometry.draw(DRAW_MODES.TRIANGLE_STRIP); + } + } + + /** + * Calculates the mapped matrix. + * + * TODO playing around here.. this is temporary - (will end up in the shader) + * this returns a matrix that will normalise map filter cords in the filter to screen space + * + * @param {PIXI.Matrix} outputMatrix - the matrix to output to. + * @return {PIXI.Matrix} The mapped matrix. + */ + calculateScreenSpaceMatrix(outputMatrix) + { + const currentState = this.activeState; + + return filterTransforms.calculateScreenSpaceMatrix( + outputMatrix, + currentState.sourceFrame, + currentState.destinationFrame + ); + } + + /** + * This will map the filter coord so that a texture can be used based on the transform of a sprite + * + * @param {PIXI.Matrix} outputMatrix - The matrix to output to. + * @param {PIXI.Sprite} sprite - The sprite to map to. + * @return {PIXI.Matrix} The mapped matrix. + */ + calculateSpriteMatrix(outputMatrix, sprite) + { + const currentState = this.activeState; + + return filterTransforms.calculateSpriteMatrix( + outputMatrix, + currentState.sourceFrame, + currentState.destinationFrame, + sprite + ); + } + + /** + * Destroys this Filter System. + * + * @param {boolean} [contextLost=false] context was lost, do not free shaders + * + */ + destroy(contextLost = false) + { + if (!contextLost) + { + this.emptyPool(); + } + else + { + this.texturePool = {}; + } + } + + /** + * Gets a Power-of-Two render texture. + * + * TODO move to a seperate class could be on renderer? + * also - could cause issue with multiple contexts? + * + * @private + * @param {WebGLRenderingContext} gl - The webgl rendering context + * @param {number} minWidth - The minimum width of the render target. + * @param {number} minHeight - The minimum height of the render target. + * @param {number} resolution - The resolution of the render target. + * @return {PIXI.RenderTarget} The new render target. + */ + getPotFilterTexture(minWidth, minHeight, resolution) + { + minWidth = bitTwiddle.nextPow2(minWidth); + minHeight = bitTwiddle.nextPow2(minHeight); + resolution = resolution || 1; + + const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + + if (!this.texturePool[key]) + { + this.texturePool[key] = []; + } + + let renderTexture = this.texturePool[key].pop(); + + if (!renderTexture) + { + // temporary bypass cache.. + // internally - this will cause a texture to be bound.. + renderTexture = RenderTexture.create({ + width: minWidth, + height: minHeight, + resolution, + }); + } + + return renderTexture; + } + + /** + * Gets extra render texture to use inside current filter + * + * @param {number} resolution resolution of the renderTexture + * @returns {PIXI.RenderTexture} + */ + getFilterTexture(resolution) + { + const rt = this.activeState.renderTexture; + + const filterTexture = this.getPotFilterTexture(rt.width, rt.height, resolution || rt.baseTexture.resolution); + + filterTexture.filterFrame = rt.filterFrame; + + return filterTexture; + } + + /** + * Frees a render texture back into the pool. + * + * @param {PIXI.RenderTarget} renderTexture - The renderTarget to free + */ + returnFilterTexture(renderTexture) + { + renderTexture.filterFrame = null; + + const base = renderTexture.baseTexture; + + const minWidth = base.width; + const minHeight = base.height; + + const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + + this.texturePool[key].push(renderTexture); + } + + /** + * Empties the texture pool. + * + */ + emptyPool() + { + for (const i in this.texturePool) + { + const textures = this.texturePool[i]; + + if (textures) + { + for (let j = 0; j < textures.length; j++) + { + textures[j].destroy(true); + } + } + } + + this.texturePool = {}; + } +} diff --git a/packages/core/src/filters/defaultFilter.frag b/packages/core/src/filters/defaultFilter.frag new file mode 100644 index 0000000..a14c1be --- /dev/null +++ b/packages/core/src/filters/defaultFilter.frag @@ -0,0 +1,22 @@ +varying vec2 vTextureCoord; +varying vec2 vFilterCoord; + +uniform sampler2D uSampler; +uniform sampler2D filterSampler; + +void main(void){ + vec4 masky = texture2D(filterSampler, vFilterCoord); + vec4 sample = texture2D(uSampler, vTextureCoord); + vec4 color; + if(mod(vFilterCoord.x, 1.0) > 0.5) + { + color = vec4(1.0, 0.0, 0.0, 1.0); + } + else + { + color = vec4(0.0, 1.0, 0.0, 1.0); + } + // gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0); + gl_FragColor = mix(sample, masky, 0.5); + gl_FragColor *= sample.a; +} diff --git a/packages/core/src/filters/defaultFilter.vert b/packages/core/src/filters/defaultFilter.vert new file mode 100644 index 0000000..e1febca --- /dev/null +++ b/packages/core/src/filters/defaultFilter.vert @@ -0,0 +1,14 @@ +attribute vec2 aVertexPosition; +attribute vec2 aTextureCoord; + +uniform mat3 projectionMatrix; +uniform mat3 filterMatrix; + +varying vec2 vTextureCoord; +varying vec2 vFilterCoord; + +void main(void){ + gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); + vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy; + vTextureCoord = aTextureCoord ; +} diff --git a/packages/core/src/filters/filterTransforms.js b/packages/core/src/filters/filterTransforms.js new file mode 100644 index 0000000..87a3cd6 --- /dev/null +++ b/packages/core/src/filters/filterTransforms.js @@ -0,0 +1,50 @@ +import { Matrix } from '@pixi/math'; + +/** + * Calculates the mapped matrix + * @param filterArea {Rectangle} The filter area + * @param sprite {Sprite} the target sprite + * @param outputMatrix {Matrix} @alvin + * @private + */ +// this returns a matrix that will normalise map filter cords in the filter to screen space +export function calculateScreenSpaceMatrix(outputMatrix, filterArea, textureSize) +{ + // TODO unwrap? + const mappedMatrix = outputMatrix.identity(); + + mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); + + mappedMatrix.scale(textureSize.width, textureSize.height); + + return mappedMatrix; +} + +export function calculateNormalizedScreenSpaceMatrix(outputMatrix, filterArea, textureSize) +{ + const mappedMatrix = outputMatrix.identity(); + + mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); + + const translateScaleX = (textureSize.width / filterArea.width); + const translateScaleY = (textureSize.height / filterArea.height); + + mappedMatrix.scale(translateScaleX, translateScaleY); + + return mappedMatrix; +} + +// this will map the filter coord so that a texture can be used based on the transform of a sprite +export function calculateSpriteMatrix(outputMatrix, filterArea, textureSize, sprite) +{ + const orig = sprite._texture.orig; + const mappedMatrix = outputMatrix.set(textureSize.width, 0, 0, textureSize.height, filterArea.x, filterArea.y); + const worldTransform = sprite.worldTransform.copyTo(Matrix.TEMP_MATRIX); + + worldTransform.invert(); + mappedMatrix.prepend(worldTransform); + mappedMatrix.scale(1.0 / orig.width, 1.0 / orig.height); + mappedMatrix.translate(sprite.anchor.x, sprite.anchor.y); + + return mappedMatrix; +} diff --git a/packages/core/src/filters/spriteMask/SpriteMaskFilter.js b/packages/core/src/filters/spriteMask/SpriteMaskFilter.js new file mode 100644 index 0000000..91697b7 --- /dev/null +++ b/packages/core/src/filters/spriteMask/SpriteMaskFilter.js @@ -0,0 +1,63 @@ +import Filter from '../Filter'; +import { Matrix } from '@pixi/math'; +import vertex from './spriteMaskFilter.vert'; +import fragment from './spriteMaskFilter.frag'; +import { default as TextureMatrix } from '../../textures/TextureMatrix'; + +/** + * The SpriteMaskFilter class + * + * @class + * @extends PIXI.Filter + * @memberof PIXI + */ +export default class SpriteMaskFilter extends Filter +{ + /** + * @param {PIXI.Sprite} sprite - the target sprite + */ + constructor(sprite) + { + const maskMatrix = new Matrix(); + + super(vertex, fragment); + + sprite.renderable = false; + + this.maskSprite = sprite; + this.maskMatrix = maskMatrix; + } + + /** + * Applies the filter + * + * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + */ + apply(filterManager, input, output) + { + const maskSprite = this.maskSprite; + const tex = this.maskSprite.texture; + + if (!tex.valid) + { + return; + } + if (!tex.transform) + { + // margin = 0.0, let it bleed a bit, shader code becomes easier + // assuming that atlas textures were made with 1-pixel padding + tex.transform = new TextureMatrix(tex, 0.0); + } + tex.transform.update(); + + this.uniforms.mask = tex; + this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite) + .prepend(tex.transform.mapCoord); + this.uniforms.alpha = maskSprite.worldAlpha; + this.uniforms.maskClamp = tex.transform.uClampFrame; + + filterManager.applyFilter(this, input, output); + } +} diff --git a/packages/core/src/filters/spriteMask/spriteMaskFilter.frag b/packages/core/src/filters/spriteMask/spriteMaskFilter.frag new file mode 100644 index 0000000..0e4aef8 --- /dev/null +++ b/packages/core/src/filters/spriteMask/spriteMaskFilter.frag @@ -0,0 +1,23 @@ +varying vec2 vMaskCoord; +varying vec2 vTextureCoord; + +uniform sampler2D uSampler; +uniform sampler2D mask; +uniform float alpha; +uniform vec4 maskClamp; + +void main(void) +{ + float clip = step(3.5, + step(maskClamp.x, vMaskCoord.x) + + step(maskClamp.y, vMaskCoord.y) + + step(vMaskCoord.x, maskClamp.z) + + step(vMaskCoord.y, maskClamp.w)); + + vec4 original = texture2D(uSampler, vTextureCoord); + vec4 masky = texture2D(mask, vMaskCoord); + + original *= (masky.r * masky.a * alpha * clip); + + gl_FragColor = original; +} diff --git a/packages/core/src/filters/spriteMask/spriteMaskFilter.vert b/packages/core/src/filters/spriteMask/spriteMaskFilter.vert new file mode 100644 index 0000000..d23a30c --- /dev/null +++ b/packages/core/src/filters/spriteMask/spriteMaskFilter.vert @@ -0,0 +1,16 @@ +attribute vec2 aVertexPosition; +attribute vec2 aTextureCoord; + +uniform mat3 projectionMatrix; +uniform mat3 otherMatrix; + +varying vec2 vMaskCoord; +varying vec2 vTextureCoord; + +void main(void) +{ + gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); + + vTextureCoord = aTextureCoord; + vMaskCoord = ( otherMatrix * vec3( aTextureCoord, 1.0) ).xy; +} diff --git a/packages/core/src/framebuffer/FrameBuffer.js b/packages/core/src/framebuffer/FrameBuffer.js new file mode 100644 index 0000000..84e9fcc --- /dev/null +++ b/packages/core/src/framebuffer/FrameBuffer.js @@ -0,0 +1,106 @@ +import Texture from '../textures/BaseTexture'; +import { FORMATS, TYPES } from '@pixi/constants'; + +/** + * Frame buffer + * @class + * @memberof PIXI + */ +export default class FrameBuffer +{ + constructor(width, height) + { + this.width = width || 100; + this.height = height || 100; + + this.stencil = false; + this.depth = false; + + this.dirtyId = 0; + this.dirtyFormat = 0; + this.dirtySize = 0; + + this.depthTexture = null; + this.colorTextures = []; + + this.glFrameBuffers = {}; + } + + get colorTexture() + { + return this.colorTextures[0]; + } + + addColorTexture(index, texture) + { + // TODO add some validation to the texture - same width / height etc? + this.colorTextures[index || 0] = texture || new Texture(null, { scaleMode: 0, + resolution: 1, + mipmap: false, + width: this.width, + height: this.height });// || new Texture(); + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + addDepthTexture(texture) + { + /* eslint-disable max-len */ + this.depthTexture = texture || new Texture(null, { scaleMode: 0, + resolution: 1, + width: this.width, + height: this.height, + mipmap: false, + format: FORMATS.DEPTH_COMPONENT, + type: TYPES.UNSIGNED_SHORT });// UNSIGNED_SHORT; + /* eslint-disable max-len */ + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + enableDepth() + { + this.depth = true; + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + enableStencil() + { + this.stencil = true; + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + resize(width, height) + { + if (width === this.width && height === this.height) return; + + this.width = width; + this.height = height; + + this.dirtyId++; + this.dirtySize++; + + for (let i = 0; i < this.colorTextures.length; i++) + { + this.colorTextures[i].setSize(width, height); + } + + if (this.depthTexture) + { + this.depthTexture.setSize(width, height); + } + } +} diff --git a/packages/core/src/framebuffer/FramebufferSystem.js b/packages/core/src/framebuffer/FramebufferSystem.js new file mode 100644 index 0000000..2cc2437 --- /dev/null +++ b/packages/core/src/framebuffer/FramebufferSystem.js @@ -0,0 +1,246 @@ +import System from '../System'; +import { Rectangle } from '@pixi/math'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class FramebufferSystem extends System +{ + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + contextChange() + { + this.gl = this.renderer.gl; + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + this.current = null; + this.viewport = new Rectangle(); + + this.drawBufferExtension = this.renderer.context.extensions.drawBuffers; + } + + bind(framebuffer, frame) + { + const gl = this.gl; + + this.current = framebuffer; + + if (framebuffer) + { + // TODO cacheing layer! + + const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID] || this.initFramebuffer(framebuffer); + + gl.bindFramebuffer(gl.FRAMEBUFFER, fbo.framebuffer); + // makesure all textures are unbound.. + + // now check for updates... + if (fbo.dirtyId !== framebuffer.dirtyId) + { + fbo.dirtyId = framebuffer.dirtyId; + + if (fbo.dirtyFormat !== framebuffer.dirtyFormat) + { + fbo.dirtyFormat = framebuffer.dirtyFormat; + this.updateFramebuffer(framebuffer); + } + else if (fbo.dirtySize !== framebuffer.dirtySize) + { + fbo.dirtySize = framebuffer.dirtySize; + this.resizeFramebuffer(framebuffer); + } + } + + for (let i = 0; i < framebuffer.colorTextures.length; i++) + { + if (framebuffer.colorTextures[i].texturePart) + { + this.renderer.texture.unbind(framebuffer.colorTextures[i].texture); + } + else + { + this.renderer.texture.unbind(framebuffer.colorTextures[i]); + } + } + + if (framebuffer.depthTexture) + { + this.renderer.texture.unbind(framebuffer.depthTexture); + } + + if (frame) + { + this.setViewport(frame.x, frame.y, frame.width, frame.height); + } + else + { + this.setViewport(0, 0, framebuffer.width, framebuffer.height); + } + } + else + { + gl.bindFramebuffer(gl.FRAMEBUFFER, null); + + if (frame) + { + this.setViewport(frame.x, frame.y, frame.width, frame.height); + } + else + { + this.setViewport(0, 0, this.renderer.width, this.renderer.height); + } + } + } + + setViewport(x, y, width, height) + { + const v = this.viewport; + + if (v.width !== width || v.height !== height || v.x !== x || v.y !== y) + { + v.x = x; + v.y = y; + v.width = width; + v.height = height; + + this.gl.viewport(x, y, width, height); + } + } + + get size() + { + if (this.current) + { + // TODO store temp + return { x: 0, y: 0, width: this.current.width, height: this.current.height }; + } + + return { x: 0, y: 0, width: this.renderer.width, height: this.renderer.height }; + } + + clear(r, g, b, a) + { + const gl = this.gl; + + // TODO clear color can be set only one right? + gl.clearColor(r, g, b, a); + gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); + } + + // private functions... + + initFramebuffer(framebuffer) + { + const gl = this.gl; + + // TODO - make this a class? + const fbo = { + framebuffer: gl.createFramebuffer(), + stencil: null, + dirtyId: 0, + dirtyFormat: 0, + dirtySize: 0, + }; + + framebuffer.glFrameBuffers[this.CONTEXT_UID] = fbo; + + return fbo; + } + + resizeFramebuffer(framebuffer) + { + const gl = this.gl; + + if (framebuffer.stencil || framebuffer.depth) + { + gl.bindRenderbuffer(gl.RENDERBUFFER, this.stencil); + gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); + } + } + + updateFramebuffer(framebuffer) + { + const gl = this.gl; + + const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID]; + + // bind the color texture + const colorTextures = framebuffer.colorTextures; + + let count = colorTextures.length; + + if (!this.drawBufferExtension) + { + count = Math.min(count, 1); + } + + const activeTextures = []; + + for (let i = 0; i < count; i++) + { + const texture = framebuffer.colorTextures[i]; + + if (texture.texturePart) + { + this.renderer.texture.bind(texture.texture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.COLOR_ATTACHMENT0 + i, + gl.TEXTURE_CUBE_MAP_NEGATIVE_X + texture.side, + texture.texture._glTextures[this.CONTEXT_UID].texture, + 0); + } + else + { + this.renderer.texture.bind(texture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.COLOR_ATTACHMENT0 + i, + gl.TEXTURE_2D, + texture._glTextures[this.CONTEXT_UID].texture, + 0); + } + + activeTextures.push(gl.COLOR_ATTACHMENT0 + i); + } + + if (this.drawBufferExtension && activeTextures.length > 1) + { + this.drawBufferExtension.drawBuffersWEBGL(activeTextures); + } + + if (framebuffer.depthTexture) + { + const depthTextureExt = this.renderer.context.extensions.depthTexture; + + if (depthTextureExt) + { + const depthTexture = framebuffer.depthTexture; + + this.renderer.texture.bind(depthTexture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.DEPTH_ATTACHMENT, + gl.TEXTURE_2D, + depthTexture._glTextures[this.CONTEXT_UID].texture, + 0); + } + } + + if (framebuffer.stencil || framebuffer.depth) + { + fbo.stencil = gl.createRenderbuffer(); + + gl.bindRenderbuffer(gl.RENDERBUFFER, fbo.stencil); + + // TODO.. this is depth AND stencil? + gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, fbo.stencil); + gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); + // fbo.enableStencil(); + } + } +} diff --git a/packages/core/src/geometry/GLBuffer.js b/packages/core/src/geometry/GLBuffer.js new file mode 100644 index 0000000..0477e8a --- /dev/null +++ b/packages/core/src/geometry/GLBuffer.js @@ -0,0 +1,9 @@ +export default class GLBuffer +{ + constructor(buffer) + { + this.buffer = buffer; + this.updateID = -1; + this.byteLength = -1; + } +} diff --git a/packages/core/src/geometry/GeometrySystem.js b/packages/core/src/geometry/GeometrySystem.js new file mode 100644 index 0000000..959f827 --- /dev/null +++ b/packages/core/src/geometry/GeometrySystem.js @@ -0,0 +1,388 @@ +import System from '../System'; +import GLBuffer from './GLBuffer'; + +const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class GeometrySystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this._activeGeometry = null; + this._activeVao = null; + + this.hasVao = true; + this.hasInstance = true; + } + + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + contextChange() + { + const gl = this.gl = this.renderer.gl; + + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + + // webgl2 + if (!gl.createVertexArray) + { + // webgl 1! + let nativeVaoExtension = this.renderer.context.extensions.vertexArrayObject; + + if (this.renderer.legacy) + { + nativeVaoExtension = null; + } + + if (nativeVaoExtension) + { + gl.createVertexArray = () => + nativeVaoExtension.createVertexArrayOES(); + + gl.bindVertexArray = (vao) => + nativeVaoExtension.bindVertexArrayOES(vao); + + gl.deleteVertexArray = (vao) => + nativeVaoExtension.deleteVertexArrayOES(vao); + } + else + { + this.hasVao = false; + gl.createVertexArray = () => + { + // empty + }; + + gl.bindVertexArray = () => + { + // empty + }; + + gl.deleteVertexArray = () => + { + // empty + }; + } + } + + if (!gl.vertexAttribDivisor) + { + const instanceExt = gl.getExtension('ANGLE_instanced_arrays'); + + if (instanceExt) + { + gl.vertexAttribDivisor = (a, b) => + instanceExt.vertexAttribDivisorANGLE(a, b); + + gl.drawElementsInstanced = (a, b, c, d, e) => + instanceExt.drawElementsInstancedANGLE(a, b, c, d, e); + + gl.drawArraysInstanced = (a, b, c, d) => + instanceExt.drawArraysInstancedANGLE(a, b, c, d); + } + else + { + this.hasInstance = false; + } + } + } + + /** + * Binds geometry so that is can be drawn. Creating a Vao if required + * @private + * @param {PIXI.Geometry} geometry instance of geometry to bind + */ + bind(geometry, shader) + { + shader = shader || this.renderer.shader.shader; + + const gl = this.gl; + + // not sure the best way to address this.. + // currently different shaders require different VAOs for the same geometry + // Still mulling over the best way to solve this one.. + // will likely need to modify the shader attribute locations at run time! + let vaos = geometry.glVertexArrayObjects[this.CONTEXT_UID]; + + if (!vaos) + { + geometry.glVertexArrayObjects[this.CONTEXT_UID] = vaos = {}; + } + + const vao = vaos[shader.program.id] || this.initGeometryVao(geometry, shader.program); + + this._activeGeometry = geometry; + + if (this._activeVao !== vao) + { + this._activeVao = vao; + + if (this.hasVao) + { + gl.bindVertexArray(vao); + } + else + { + this.activateVao(geometry, shader.program); + } + } + + // TODO - optimise later! + // don't need to loop through if nothing changed! + // maybe look to add an 'autoupdate' to geometry? + this.updateBuffers(); + } + + reset() + { + this.unbind(); + } + + updateBuffers() + { + const geometry = this._activeGeometry; + const gl = this.gl; + + for (let i = 0; i < geometry.buffers.length; i++) + { + const buffer = geometry.buffers[i]; + + const glBuffer = buffer._glBuffers[this.CONTEXT_UID]; + + if (buffer._updateID !== glBuffer.updateID) + { + glBuffer.updateID = buffer._updateID; + + // TODO can cache this on buffer! maybe added a getter / setter? + const type = buffer.index ? gl.ELEMENT_ARRAY_BUFFER : gl.ARRAY_BUFFER; + const drawType = buffer.static ? gl.STATIC_DRAW : gl.DYNAMIC_DRAW; + + gl.bindBuffer(type, glBuffer.buffer); + + if (glBuffer.byteLength >= buffer.data.byteLength) + { + // offset is always zero for now! + gl.bufferSubData(type, 0, buffer.data); + } + else + { + gl.bufferData(type, buffer.data, drawType); + } + } + } + } + + checkCompatability(geometry, program) + { + // geometry must have at least all the attributes that the shader requires. + const geometryAttributes = geometry.attributes; + const shaderAttributes = program.attributeData; + + for (const j in shaderAttributes) + { + if (!geometryAttributes[j]) + { + throw new Error(`shader and geometry incompatible, geometry missing the "${j}" attribute`); + } + } + } + + /** + * Creates a Vao with the same structure as the geometry and stores it on the geometry. + * @private + * @param {PIXI.Geometry} geometry instance of geometry to to generate Vao for + */ + initGeometryVao(geometry, program) + { + this.checkCompatability(geometry, program); + + const gl = this.gl; + const CONTEXT_UID = this.CONTEXT_UID; + const buffers = geometry.buffers; + const attributes = geometry.attributes; + + const tempStride = {}; + const tempStart = {}; + + for (const j in buffers) + { + tempStride[j] = 0; + tempStart[j] = 0; + } + + for (const j in attributes) + { + if (!attributes[j].size && program.attributeData[j]) + { + attributes[j].size = program.attributeData[j].size; + } + + tempStride[attributes[j].buffer] += attributes[j].size * byteSizeMap[attributes[j].type]; + } + + for (const j in attributes) + { + const attribute = attributes[j]; + const attribSize = attribute.size; + + if (attribute.stride === undefined) + { + if (tempStride[attribute.buffer] === attribSize * byteSizeMap[attribute.type]) + { + attribute.stride = 0; + } + else + { + attribute.stride = tempStride[attribute.buffer]; + } + } + + if (attribute.start === undefined) + { + attribute.start = tempStart[attribute.buffer]; + + tempStart[attribute.buffer] += attribSize * byteSizeMap[attribute.type]; + } + } + + // first update - and create the buffers! + // only create a gl buffer if it actually gets + for (let i = 0; i < buffers.length; i++) + { + const buffer = buffers[i]; + + if (!buffer._glBuffers[CONTEXT_UID]) + { + buffer._glBuffers[CONTEXT_UID] = new GLBuffer(gl.createBuffer()); + } + } + + // TODO - maybe make this a data object? + // lets wait to see if we need to first! + const vao = gl.createVertexArray(); + + gl.bindVertexArray(vao); + + this.activateVao(geometry, program); + + gl.bindVertexArray(null); + + // add it to the cache! + geometry.glVertexArrayObjects[this.CONTEXT_UID][program.id] = vao; + + return vao; + } + + activateVao(geometry, program) + { + const gl = this.gl; + const CONTEXT_UID = this.CONTEXT_UID; + const buffers = geometry.buffers; + const attributes = geometry.attributes; + + if (geometry.indexBuffer) + { + // first update the index buffer if we have one.. + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, geometry.indexBuffer._glBuffers[CONTEXT_UID].buffer); + } + + let lastBuffer = null; + + // add a new one! + for (const j in attributes) + { + const attribute = attributes[j]; + const buffer = buffers[attribute.buffer]; + const glBuffer = buffer._glBuffers[CONTEXT_UID]; + + if (program.attributeData[j]) + { + if (lastBuffer !== glBuffer) + { + gl.bindBuffer(gl.ARRAY_BUFFER, glBuffer.buffer); + + lastBuffer = glBuffer; + } + + const location = program.attributeData[j].location; + + // TODO introduce state again + // we can optimise this for older devices that have no VAOs + gl.enableVertexAttribArray(location); + + gl.vertexAttribPointer(location, + attribute.size, + attribute.type || gl.FLOAT, + attribute.normalized, + attribute.stride, + attribute.start); + + if (attribute.instance) + { + // TODO calculate instance count based of this... + if (this.hasInstance) + { + gl.vertexAttribDivisor(location, 1); + } + else + { + throw new Error('geometry error, GPU Instancing is not supported on this device'); + } + } + } + } + } + + draw(type, size, start, instanceCount) + { + const gl = this.gl; + const geometry = this._activeGeometry; + + // TODO.. this should not change so maybe cache the function? + + if (geometry.indexBuffer) + { + if (geometry.instanced) + { + /* eslint-disable max-len */ + gl.drawElementsInstanced(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2, instanceCount || 1); + /* eslint-enable max-len */ + } + else + { + gl.drawElements(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2); + } + } + else + if (geometry.instanced) + { + // TODO need a better way to calculate size.. + gl.drawArraysInstanced(type, start, size || geometry.getSize(), instanceCount || 1); + } + else + { + gl.drawArrays(type, start, size || geometry.getSize()); + } + + return this; + } + + unbind() + { + this.gl.bindVertexArray(null); + this._activeVao = null; + this._activeGeometry = null; + } +} diff --git a/packages/core/src/geometry/utils/setVertexAttribArrays.js b/packages/core/src/geometry/utils/setVertexAttribArrays.js new file mode 100644 index 0000000..23b6959 --- /dev/null +++ b/packages/core/src/geometry/utils/setVertexAttribArrays.js @@ -0,0 +1,54 @@ +// var GL_MAP = {}; + +/** + * @param gl {WebGLRenderingContext} The current WebGL context + * @param attribs {*} + * @param state {*} + */ +export default function setVertexAttribArrays(gl, attribs, state) +{ + let i; + + if (state) + { + const tempAttribState = state.tempAttribState; + const attribState = state.attribState; + + for (i = 0; i < tempAttribState.length; i++) + { + tempAttribState[i] = false; + } + + // set the new attribs + for (i = 0; i < attribs.length; i++) + { + tempAttribState[attribs[i].attribute.location] = true; + } + + for (i = 0; i < attribState.length; i++) + { + if (attribState[i] !== tempAttribState[i]) + { + attribState[i] = tempAttribState[i]; + + if (state.attribState[i]) + { + gl.enableVertexAttribArray(i); + } + else + { + gl.disableVertexAttribArray(i); + } + } + } + } + else + { + for (i = 0; i < attribs.length; i++) + { + const attrib = attribs[i]; + + gl.enableVertexAttribArray(attrib.attribute.location); + } + } +} diff --git a/packages/core/src/index.js b/packages/core/src/index.js index 58c456a..fd1f547 100644 --- a/packages/core/src/index.js +++ b/packages/core/src/index.js @@ -1,30 +1,30 @@ import * as resources from './textures/resources'; -import * as systems from './renderers/systems'; +import * as systems from './systems'; -export { resources }; export { systems }; +export { resources }; -export { default as Renderer } from './renderers/Renderer'; -export { default as AbstractRenderer } from './renderers/AbstractRenderer'; -export { default as FrameBuffer } from './textures/FrameBuffer'; +export { default as System } from './System'; +export { default as Renderer } from './Renderer'; +export { default as AbstractRenderer } from './AbstractRenderer'; +export { default as FrameBuffer } from './framebuffer/FrameBuffer'; export { default as CubeTexture } from './textures/CubeTexture'; export { default as BaseTexture } from './textures/BaseTexture'; export { default as Texture } from './textures/Texture'; export { default as TextureMatrix } from './textures/TextureMatrix'; -export { default as RenderTexture } from './textures/RenderTexture'; -export { default as BaseRenderTexture } from './textures/BaseRenderTexture'; +export { default as RenderTexture } from './renderTexture/RenderTexture'; +export { default as BaseRenderTexture } from './renderTexture/BaseRenderTexture'; export { default as TextureUvs } from './textures/TextureUvs'; -export { default as State } from './renderers/State'; -export { default as ObjectRenderer } from './renderers/utils/ObjectRenderer'; -export { default as Quad } from './renderers/utils/Quad'; -export { default as QuadUv } from './renderers/utils/QuadUv'; -export { default as checkMaxIfStatmentsInShader } from './renderers/utils/checkMaxIfStatmentsInShader'; +export { default as State } from './state/State'; +export { default as ObjectRenderer } from './batch/ObjectRenderer'; +export { default as Quad } from './utils/Quad'; +export { default as QuadUv } from './utils/QuadUv'; +export { default as checkMaxIfStatmentsInShader } from './shader/utils/checkMaxIfStatmentsInShader'; export { default as Shader } from './shader/Shader'; export { default as Program } from './shader/Program'; export { default as UniformGroup } from './shader/UniformGroup'; -export { default as SpriteMaskFilter } from './renderers/filters/spriteMask/SpriteMaskFilter'; -export { default as Filter } from './renderers/filters/Filter'; +export { default as SpriteMaskFilter } from './filters/spriteMask/SpriteMaskFilter'; +export { default as Filter } from './filters/Filter'; export { default as Attribute } from './geometry/Attribute'; export { default as Buffer } from './geometry/Buffer'; export { default as Geometry } from './geometry/Geometry'; - diff --git a/packages/core/src/mask/MaskSystem.js b/packages/core/src/mask/MaskSystem.js new file mode 100644 index 0000000..99174c2 --- /dev/null +++ b/packages/core/src/mask/MaskSystem.js @@ -0,0 +1,199 @@ +import System from '../System'; +import AlphaMaskFilter from '../filters/spriteMask/SpriteMaskFilter'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class MaskSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + // TODO - we don't need both! + this.scissor = false; + this.scissorData = null; + this.scissorRenderTarget = null; + + this.enableScissor = false; + + this.alphaMaskPool = []; + this.alphaMaskIndex = 0; + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.DisplayObject} target - Display Object to push the mask to + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + push(target, maskData) + { + // TODO the root check means scissor rect will not + // be used on render textures more info here: + // https://github.com/pixijs/pixi.js/pull/3545 + + if (maskData.texture) + { + this.pushSpriteMask(target, maskData); + } + else if (this.enableScissor + && !this.scissor + && this.renderer._activeRenderTarget.root + && !this.renderer.stencil.stencilMaskStack.length + && maskData.isFastRect()) + { + const matrix = maskData.worldTransform; + + let rot = Math.atan2(matrix.b, matrix.a); + + // use the nearest degree! + rot = Math.round(rot * (180 / Math.PI)); + + if (rot % 90) + { + this.pushStencilMask(maskData); + } + else + { + this.pushScissorMask(target, maskData); + } + } + else + { + this.pushStencilMask(maskData); + } + } + + /** + * Removes the last mask from the mask stack and doesn't return it. + * + * @param {PIXI.DisplayObject} target - Display Object to pop the mask from + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + pop(target, maskData) + { + if (maskData.texture) + { + this.popSpriteMask(target, maskData); + } + else if (this.enableScissor && !this.renderer.stencil.stencilMaskStack.length) + { + this.popScissorMask(target, maskData); + } + else + { + this.popStencilMask(target, maskData); + } + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.RenderTarget} target - Display Object to push the sprite mask to + * @param {PIXI.Sprite} maskData - Sprite to be used as the mask + */ + pushSpriteMask(target, maskData) + { + let alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; + + if (!alphaMaskFilter) + { + alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; + } + + alphaMaskFilter[0].resolution = this.renderer.resolution; + alphaMaskFilter[0].maskSprite = maskData; + + // TODO - may cause issues! + target.filterArea = maskData.getBounds(true); + + this.renderer.filterSystem.pushFilter(target, alphaMaskFilter); + + this.alphaMaskIndex++; + } + + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popSpriteMask() + { + this.renderer.filterSystem.popFilter(); + this.alphaMaskIndex--; + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + pushStencilMask(maskData) + { + this.renderer.batch.flush(); + this.renderer.stencil.pushStencil(maskData); + } + + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popStencilMask() + { + // this.renderer.currentRenderer.stop(); + this.renderer.stencil.popStencil(); + } + + /** + * + * @param {PIXI.DisplayObject} target - Display Object to push the mask to + * @param {PIXI.Graphics} maskData - The masking data. + */ + pushScissorMask(target, maskData) + { + maskData.renderable = true; + + const renderTarget = this.renderer._activeRenderTarget; + + const bounds = maskData.getBounds(); + + bounds.fit(renderTarget.size); + maskData.renderable = false; + + this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); + + const resolution = this.renderer.resolution; + + this.renderer.gl.scissor( + bounds.x * resolution, + (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, + bounds.width * resolution, + bounds.height * resolution + ); + + this.scissorRenderTarget = renderTarget; + this.scissorData = maskData; + this.scissor = true; + } + + /** + * + * + */ + popScissorMask() + { + this.scissorRenderTarget = null; + this.scissorData = null; + this.scissor = false; + + // must be scissor! + const gl = this.renderer.gl; + + gl.disable(gl.SCISSOR_TEST); + } +} diff --git a/packages/core/src/mask/StencilSystem.js b/packages/core/src/mask/StencilSystem.js new file mode 100644 index 0000000..62796a5 --- /dev/null +++ b/packages/core/src/mask/StencilSystem.js @@ -0,0 +1,133 @@ +import System from '../System'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class StencilSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + this.stencilMaskStack = []; + } + + /** + * Changes the mask stack that is used by this System. + * + * @param {PIXI.Graphics[]} stencilMaskStack - The mask stack + */ + setMaskStack(stencilMaskStack) + { + const gl = this.renderer.gl; + + if (stencilMaskStack.length !== this.stencilMaskStack.length) + { + if (stencilMaskStack.length === 0) + { + gl.disable(gl.STENCIL_TEST); + } + else + { + gl.enable(gl.STENCIL_TEST); + } + } + + this.stencilMaskStack = stencilMaskStack; + } + + /** + * Applies the Mask and adds it to the current stencil stack. @alvin + * + * @param {PIXI.Graphics} graphics - The mask + */ + pushStencil(graphics) + { + this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); + + // this.renderer._activeRenderTarget.attachStencilBuffer(); + + const gl = this.renderer.gl; + const prevMaskCount = this.stencilMaskStack.length; + + if (prevMaskCount === 0) + { + gl.enable(gl.STENCIL_TEST); + } + + this.stencilMaskStack.push(graphics); + + // Increment the refference stencil value where the new mask overlaps with the old ones. + gl.colorMask(false, false, false, false); + gl.stencilFunc(gl.EQUAL, prevMaskCount, this._getBitwiseMask()); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.INCR); + this.renderer.plugins.graphics.render(graphics); + + this._useCurrent(); + } + + /** + * Removes the last mask from the stencil stack. @alvin + */ + popStencil() + { + this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); + + const gl = this.renderer.gl; + const graphics = this.stencilMaskStack.pop(); + + if (this.stencilMaskStack.length === 0) + { + // the stack is empty! + gl.disable(gl.STENCIL_TEST); + gl.clear(gl.STENCIL_BUFFER_BIT); + gl.clearStencil(0); + } + else + { + // Decrement the refference stencil value where the popped mask overlaps with the other ones + gl.colorMask(false, false, false, false); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.DECR); + this.renderer.plugins.graphics.render(graphics); + + this._useCurrent(); + } + } + + /** + * Setup renderer to use the current stencil data. + */ + _useCurrent() + { + const gl = this.renderer.gl; + + gl.colorMask(true, true, true, true); + gl.stencilFunc(gl.EQUAL, this.stencilMaskStack.length, this._getBitwiseMask()); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP); + } + + /** + * Fill 1s equal to the number of acitve stencil masks. + * + * @return {number} The bitwise mask. + */ + _getBitwiseMask() + { + return (1 << this.stencilMaskStack.length) - 1; + } + + /** + * Destroys the mask stack. + * + */ + destroy() + { + super.destroy(this); + + this.stencilMaskStack = null; + } +} diff --git a/packages/core/src/projection/ProjectionSystem.js b/packages/core/src/projection/ProjectionSystem.js new file mode 100644 index 0000000..6e63b73 --- /dev/null +++ b/packages/core/src/projection/ProjectionSystem.js @@ -0,0 +1,73 @@ +import System from '../System'; +import { Matrix } from '@pixi/math'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class ProjectionSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.projectionMatrix = new Matrix(); + } + + update(destinationFrame, sourceFrame, resolution, root) + { + this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; + this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; + + this.calculateProjection(this.destinationFrame, this.sourceFrame, resolution, root); + + this.renderer.globalUniforms.uniforms.projectionMatrix = this.projectionMatrix; + this.renderer.globalUniforms.update(); + } + + /** + * Updates the projection matrix based on a projection frame (which is a rectangle) + * + * @param {Rectangle} destinationFrame - The destination frame. + * @param {Rectangle} sourceFrame - The source frame. + */ + calculateProjection(destinationFrame, sourceFrame, resolution, root) + { + const pm = this.projectionMatrix; + + // I don't think we will need this line.. + // pm.identity(); + + if (!root) + { + pm.a = (1 / destinationFrame.width * 2) * resolution; + pm.d = (1 / destinationFrame.height * 2) * resolution; + + pm.tx = -1 - (sourceFrame.x * pm.a); + pm.ty = -1 - (sourceFrame.y * pm.d); + } + else + { + pm.a = (1 / destinationFrame.width * 2) * resolution; + pm.d = (-1 / destinationFrame.height * 2) * resolution; + + pm.tx = -1 - (sourceFrame.x * pm.a); + pm.ty = 1 - (sourceFrame.y * pm.d); + } + } + + /** + * Sets the transform of the active render target to the given matrix + * + * @param {PIXI.Matrix} matrix - The transformation matrix + */ + setTransform()// matrix) + { + // this._activeRenderTarget.transform = matrix; + } +} diff --git a/packages/core/src/renderTexture/BaseRenderTexture.js b/packages/core/src/renderTexture/BaseRenderTexture.js new file mode 100644 index 0000000..88eb83b --- /dev/null +++ b/packages/core/src/renderTexture/BaseRenderTexture.js @@ -0,0 +1,137 @@ +import BaseTexture from '../textures/BaseTexture'; +import FrameBuffer from '../framebuffer/FrameBuffer'; + +/** + * A BaseRenderTexture is a special texture that allows any PixiJS display object to be rendered to it. + * + * __Hint__: All DisplayObjects (i.e. Sprites) that render to a BaseRenderTexture should be preloaded + * otherwise black rectangles will be drawn instead. + * + * A BaseRenderTexture takes a snapshot of any Display Object given to its render method. The position + * and rotation of the given Display Objects is ignored. For example: + * + * ```js + * let renderer = PIXI.autoDetectRenderer(1024, 1024, { view: canvas, ratio: 1 }); + * let baseRenderTexture = new PIXI.BaseRenderTexture(renderer, 800, 600); + * let sprite = PIXI.Sprite.fromImage("spinObj_01.png"); + * + * sprite.position.x = 800/2; + * sprite.position.y = 600/2; + * sprite.anchor.x = 0.5; + * sprite.anchor.y = 0.5; + * + * baseRenderTexture.render(sprite); + * ``` + * + * The Sprite in this case will be rendered using its local transform. To render this sprite at 0,0 + * you can clear the transform + * + * ```js + * + * sprite.setTransform() + * + * let baseRenderTexture = new PIXI.BaseRenderTexture(100, 100); + * let renderTexture = new PIXI.RenderTexture(baseRenderTexture); + * + * renderer.render(sprite, renderTexture); // Renders to center of RenderTexture + * ``` + * + * @class + * @extends PIXI.BaseTexture + * @memberof PIXI + */ +export default class BaseRenderTexture extends BaseTexture +{ + /** + * @param {object} [options] + * @param {number} [options.width=100] - The width of the base render texture + * @param {number} [options.height=100] - The height of the base render texture + * @param {PIXI.SCALE_MODES} [options.scaleMode] - See {@link PIXI.SCALE_MODES} for possible values + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the texture being generated + */ + constructor(options) + { + if (typeof options === 'number') + { + /* eslint-disable prefer-rest-params */ + // Backward compatibility of signature + const width = arguments[0]; + const height = arguments[1]; + const scaleMode = arguments[2]; + const resolution = arguments[3]; + + options = { width, height, scaleMode, resolution }; + /* eslint-enable prefer-rest-params */ + } + + super(null, options); + + const { width, height } = options || {}; + + // Set defaults + this.mipmap = false; + this.width = Math.ceil(width) || 100; + this.height = Math.ceil(height) || 100; + this.valid = true; + + /** + * A map of renderer IDs to webgl renderTargets + * + * @private + * @member {object} + */ + // this._glRenderTargets = {}; + + /** + * A reference to the canvas render target (we only need one as this can be shared across renderers) + * + * @private + * @member {object} + */ + this._canvasRenderTarget = null; + + this.clearColor = [0, 0, 0, 0]; + + this.frameBuffer = new FrameBuffer(width * this.resolution, height * this.resolution) + .addColorTexture(0, this); + + // TODO - could this be added the systems? + + /** + * The data structure for the stencil masks + * + * @member {PIXI.Graphics[]} + */ + this.stencilMaskStack = []; + + /** + * The data structure for the filters + * + * @member {PIXI.Graphics[]} + */ + this.filterStack = [{}]; + } + + /** + * Resizes the BaseRenderTexture. + * + * @param {number} width - The width to resize to. + * @param {number} height - The height to resize to. + */ + resize(width, height) + { + width = Math.ceil(width); + height = Math.ceil(height); + this.frameBuffer.resize(width * this.resolution, height * this.resolution); + } + + /** + * Destroys this texture + * + */ + destroy() + { + super.destroy(true); + this.renderer = null; + } +} diff --git a/packages/core/src/renderTexture/RenderTexture.js b/packages/core/src/renderTexture/RenderTexture.js new file mode 100644 index 0000000..f1ecf1f --- /dev/null +++ b/packages/core/src/renderTexture/RenderTexture.js @@ -0,0 +1,153 @@ +import BaseRenderTexture from './BaseRenderTexture'; +import Texture from '../textures/Texture'; + +/** + * A RenderTexture is a special texture that allows any PixiJS display object to be rendered to it. + * + * __Hint__: All DisplayObjects (i.e. Sprites) that render to a RenderTexture should be preloaded + * otherwise black rectangles will be drawn instead. + * + * A RenderTexture takes a snapshot of any Display Object given to its render method. For example: + * + * ```js + * let renderer = PIXI.autoDetectRenderer(1024, 1024, { view: canvas, ratio: 1 }); + * let renderTexture = PIXI.RenderTexture.create(800, 600); + * let sprite = PIXI.Sprite.fromImage("spinObj_01.png"); + * + * sprite.position.x = 800/2; + * sprite.position.y = 600/2; + * sprite.anchor.x = 0.5; + * sprite.anchor.y = 0.5; + * + * renderer.render(sprite, renderTexture); + * ``` + * + * The Sprite in this case will be rendered using its local transform. To render this sprite at 0,0 + * you can clear the transform + * + * ```js + * + * sprite.setTransform() + * + * let renderTexture = new PIXI.RenderTexture.create(100, 100); + * + * renderer.render(sprite, renderTexture); // Renders to center of RenderTexture + * ``` + * + * @class + * @extends PIXI.Texture + * @memberof PIXI + */ +export default class RenderTexture extends Texture +{ + /** + * @param {PIXI.BaseRenderTexture} baseRenderTexture - The renderer used for this RenderTexture + * @param {PIXI.Rectangle} [frame] - The rectangle frame of the texture to show + */ + constructor(baseRenderTexture, frame) + { + // support for legacy.. + let _legacyRenderer = null; + + if (!(baseRenderTexture instanceof BaseRenderTexture)) + { + /* eslint-disable prefer-rest-params, no-console */ + const width = arguments[1]; + const height = arguments[2]; + const scaleMode = arguments[3]; + const resolution = arguments[4]; + + // we have an old render texture.. + console.warn(`Please use RenderTexture.create(${width}, ${height}) instead of the ctor directly.`); + _legacyRenderer = arguments[0]; + /* eslint-enable prefer-rest-params, no-console */ + + frame = null; + baseRenderTexture = new BaseRenderTexture({ + width, + height, + scaleMode, + resolution, + }); + } + + /** + * The base texture object that this texture uses + * + * @member {BaseTexture} + */ + super(baseRenderTexture, frame); + + this.legacyRenderer = _legacyRenderer; + + /** + * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. + * + * @member {boolean} + */ + this.valid = true; + + /** + * FilterSystem temporary storage + * @private + * @member {PIXI.Rectangle} + */ + this.filterFrame = null; + + this._updateUvs(); + } + + /** + * Resizes the RenderTexture. + * + * @param {number} width - The width to resize to. + * @param {number} height - The height to resize to. + * @param {boolean} [resizeBaseTexture=true] - Should the baseTexture.width and height values be resized as well? + */ + resize(width, height, resizeBaseTexture = true) + { + width = Math.ceil(width); + height = Math.ceil(height); + + // TODO - could be not required.. + this.valid = (width > 0 && height > 0); + + this._frame.width = this.orig.width = width; + this._frame.height = this.orig.height = height; + + if (resizeBaseTexture) + { + this.baseTexture.resize(width, height); + } + + this._updateUvs(); + } + + /** + * A short hand way of creating a render texture. + * + * @param {object} [options] - Options + * @param {number} [options.width=100] - The width of the render texture + * @param {number} [options.height=100] - The height of the render texture + * @param {number} [options.scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the texture being generated + * @return {PIXI.RenderTexture} The new render texture + */ + static create(options) + { + // fallback, old-style: create(width, height, scaleMode, resolution) + if (typeof options === 'number') + { + /* eslint-disable prefer-rest-params */ + options = { + width: options, + height: arguments[1], + scaleMode: arguments[2], + resolution: arguments[3], + }; + /* eslint-enable prefer-rest-params */ + } + + return new RenderTexture(new BaseRenderTexture(options)); + } +} diff --git a/packages/core/src/renderTexture/RenderTextureSystem.js b/packages/core/src/renderTexture/RenderTextureSystem.js new file mode 100644 index 0000000..8d0ca61 --- /dev/null +++ b/packages/core/src/renderTexture/RenderTextureSystem.js @@ -0,0 +1,115 @@ +import System from '../System'; +import { Rectangle } from '@pixi/math'; + +const tempRect = new Rectangle(); + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class RenderTextureSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.clearColor = renderer._backgroundColorRgba; + + // TODO moe this property somewhere else! + this.defaultMaskStack = []; + this.defaultFilterStack = [{}]; + + // empty render texture? + this.renderTexture = null; + + this.destinationFrame = new Rectangle(); + } + + bind(renderTexture, sourceFrame, destinationFrame) + { + // TODO - do we want this?? + if (this.renderTexture === renderTexture) return; + this.renderTexture = renderTexture; + + const renderer = this.renderer; + + if (renderTexture) + { + const baseTexture = renderTexture.baseTexture; + + if (!destinationFrame) + { + tempRect.width = baseTexture.realWidth; + tempRect.height = baseTexture.realHeight; + + destinationFrame = tempRect; + } + + if (!sourceFrame) + { + sourceFrame = destinationFrame; + } + + this.renderer.framebuffer.bind(baseTexture.frameBuffer, destinationFrame); + + this.renderer.projection.update(destinationFrame, sourceFrame, baseTexture.resolution, false); + this.renderer.stencil.setMaskStack(baseTexture.stencilMaskStack); + } + else + { + // TODO these validation checks happen deeper down.. + // thing they can be avoided.. + if (!destinationFrame) + { + tempRect.width = renderer.width; + tempRect.height = renderer.height; + + destinationFrame = tempRect; + } + + if (!sourceFrame) + { + sourceFrame = destinationFrame; + } + + renderer.framebuffer.bind(null, destinationFrame); + + // TODO store this.. + this.renderer.projection.update(destinationFrame, sourceFrame, this.renderer.resolution, true); + this.renderer.stencil.setMaskStack(this.defaultMaskStack); + } + + this.destinationFrame.copyFrom(destinationFrame); + } + + /** + * Erases the render texture and fills the drawing area with a colour + * + * @param {number} [clearColor] - The colour + * @return {PIXI.Renderer} Returns itself. + */ + clear(clearColor) + { + if (this.renderTexture) + { + clearColor = clearColor || this.renderTexture.baseTexture.clearColor; + } + else + { + clearColor = clearColor || this.clearColor; + } + + this.renderer.framebuffer.clear(clearColor[0], clearColor[1], clearColor[2], clearColor[3]); + } + + resize()// screenWidth, screenHeight) + { + // resize the root only! + this.bind(null); + } +} diff --git a/packages/core/src/renderers/AbstractRenderer.js b/packages/core/src/renderers/AbstractRenderer.js deleted file mode 100644 index 928670f..0000000 --- a/packages/core/src/renderers/AbstractRenderer.js +++ /dev/null @@ -1,353 +0,0 @@ -import { hex2string, hex2rgb } from '@pixi/utils'; -import { Matrix, Rectangle } from '@pixi/math'; -import { RENDERER_TYPE } from '@pixi/constants'; -import { settings } from '@pixi/settings'; -import { Container } from '@pixi/display'; -import RenderTexture from '../textures/RenderTexture'; -import EventEmitter from 'eventemitter3'; - -const tempMatrix = new Matrix(); - -/** - * 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 EventEmitter - * @memberof PIXI - */ -export default class AbstractRenderer extends EventEmitter -{ - // eslint-disable-next-line valid-jsdoc - /** - * @param {string} system - The name of the system this renderer is for. - * @param {object} [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.transparent=false] - If the render view is transparent, default false - * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false - * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) - * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. The - * resolution of the renderer retina would be 2. - * @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 {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when rendering, - * stopping pixel interpolation. - */ - constructor(system, options, arg2, arg3) - { - super(); - - // Support for constructor(system, screenWidth, screenHeight, options) - if (typeof options === 'number') - { - options = Object.assign({ - width: options, - height: arg2 || settings.RENDER_OPTIONS.height, - }, arg3); - } - - // 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 = RENDERER_TYPE.UNKNOWN; - - /** - * Measurements of the screen. (0, 0, screenWidth, screenHeight) - * - * Its safe to use as filterArea or hitArea for 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 1 - */ - this.resolution = options.resolution || settings.RESOLUTION; - - /** - * Whether the render view is transparent - * - * @member {boolean} - */ - this.transparent = options.transparent; - - /** - * Whether css dimensions of canvas view should be resized to screen dimensions automatically - * - * @member {boolean} - */ - this.autoResize = options.autoResize || false; - - /** - * Tracks the blend modes useful for this renderer. - * - * @member {object} - */ - this.blendModes = null; - - /** - * 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; - - /** - * If true PixiJS will Math.floor() x/y values when rendering, stopping pixel interpolation. - * Handy for crisp pixel art and speed on legacy devices. - * - * @member {boolean} - */ - this.roundPixels = options.roundPixels; - - /** - * The background color as a number. - * - * @member {number} - * @private - */ - this._backgroundColor = 0x000000; - - /** - * The background color as an [R, G, B] array. - * - * @member {number[]} - * @private - */ - this._backgroundColorRgba = [0, 0, 0, 0]; - - /** - * The background color as a string. - * - * @member {string} - * @private - */ - this._backgroundColorString = '#000000'; - - this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter - - /** - * This temporary display object used as the parent of the currently being rendered item - * - * @member {PIXI.DisplayObject} - * @private - */ - this._tempDisplayObjectParent = new Container(); - - /** - * The last root object that the renderer tried to render. - * - * @member {PIXI.DisplayObject} - * @private - */ - this._lastObjectRendered = this._tempDisplayObjectParent; - - /** - * Collection of plugins. - * @readonly - * @member {object} - */ - this.plugins = {}; - } - - /** - * Initialize the plugins. - * - * @protected - * @param {object} staticMap - The dictionary of staticly saved plugins. - */ - initPlugins(staticMap) - { - 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() - { - return this.view.width; - } - - /** - * Same as view.height, actual number of pixels in the canvas by vertical - * - * @member {number} - * @readonly - * @default 600 - */ - get height() - { - return this.view.height; - } - - /** - * Resizes the screen and canvas to the specified width and height - * Canvas dimensions are multiplied by resolution - * - * @param {number} screenWidth - the new width of the screen - * @param {number} screenHeight - the new height of the screen - */ - resize(screenWidth, screenHeight) - { - this.screen.width = screenWidth; - this.screen.height = screenHeight; - - this.view.width = screenWidth * this.resolution; - this.view.height = screenHeight * this.resolution; - - if (this.autoResize) - { - this.view.style.width = `${screenWidth}px`; - this.view.style.height = `${screenHeight}px`; - } - } - - /** - * 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 {PIXI.DisplayObject} displayObject - The displayObject the object will be generated from - * @param {number} scaleMode - Should be one of the scaleMode consts - * @param {number} resolution - The resolution / device pixel ratio of the texture being generated - * @param {PIXI.Rectangle} [region] - The region of the displayObject, that shall be rendered, - * if no region is specified, defaults to the local bounds of the displayObject. - * @return {PIXI.Texture} a texture of the graphics object - */ - generateTexture(displayObject, scaleMode, resolution, region) - { - region = region || displayObject.getLocalBounds(); - - // 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(region.width | 0, region.height | 0, scaleMode, resolution); - - tempMatrix.tx = -region.x; - tempMatrix.ty = -region.y; - - this.render(displayObject, renderTexture, false, tempMatrix, true); - - return renderTexture; - } - - /** - * Removes everything from the renderer and optionally removes the Canvas DOM element. - * - * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. - */ - destroy(removeView) - { - 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); - } - - this.plugins = null; - - this.type = RENDERER_TYPE.UNKNOWN; - - this.view = null; - - this.screen = null; - - this.resolution = 0; - - this.transparent = false; - - this.autoResize = false; - - this.blendModes = null; - - this.options = null; - - this.preserveDrawingBuffer = false; - this.clearBeforeRender = false; - - this.roundPixels = false; - - this._backgroundColor = 0; - this._backgroundColorRgba = null; - this._backgroundColorString = null; - - this._tempDisplayObjectParent = null; - this._lastObjectRendered = null; - } - - /** - * The background color to fill if not transparent - * - * @member {number} - */ - get backgroundColor() - { - return this._backgroundColor; - } - - set backgroundColor(value) // eslint-disable-line require-jsdoc - { - this._backgroundColor = value; - this._backgroundColorString = hex2string(value); - hex2rgb(value, this._backgroundColorRgba); - } -} diff --git a/packages/core/src/renderers/Renderer.js b/packages/core/src/renderers/Renderer.js deleted file mode 100644 index a2020b1..0000000 --- a/packages/core/src/renderers/Renderer.js +++ /dev/null @@ -1,329 +0,0 @@ -import AbstractRenderer from './AbstractRenderer'; -import { sayHello } from '@pixi/utils'; -import MaskSystem from './systems/MaskSystem'; -import StencilSystem from './systems/StencilSystem'; -import FilterSystem from './systems/filter/FilterSystem'; -import FramebufferSystem from './systems/FramebufferSystem'; -import RenderTextureSystem from './systems/RenderTextureSystem'; -import TextureSystem from './systems/textures/TextureSystem'; -import ProjectionSystem from './systems/ProjectionSystem'; -import StateSystem from './systems/StateSystem'; -import GeometrySystem from './systems/geometry/GeometrySystem'; -import ShaderSystem from './systems/shader/ShaderSystem'; -import ContextSystem from './systems/ContextSystem'; -import BatchSystem from './systems/BatchSystem'; -import TextureGCSystem from './systems/textures/TextureGCSystem'; -import { RENDERER_TYPE } from '@pixi/constants'; -import UniformGroup from '../shader/UniformGroup'; -import { Matrix } from '@pixi/math'; -import Runner from 'mini-runner'; - -/** - * 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 Render works by automatically managing webGLBatchs. - * 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 :) - * - * @class - * @memberof PIXI - * @extends PIXI.AbstractRenderer - */ -export default class Renderer extends AbstractRenderer -{ - // eslint-disable-next-line valid-jsdoc - /** - * - * @param {object} [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.transparent=false] - If the render view is transparent, default false - * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false - * @param {boolean} [options.antialias=false] - sets antialias. If not available natively then FXAA - * antialiasing is used - * @param {boolean} [options.forceFXAA=false] - forces FXAA antialiasing to be used over native. - * FXAA is faster, but may not always look as great - * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. - * The resolution of the renderer retina would be 2. - * @param {boolean} [options.clearBeforeRender=true] - This sets if the renderer will clear - * the canvas or not before the new render pass. If you wish to set this to false, you *must* set - * preserveDrawingBuffer to `true`. - * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, - * enable this if you need to call toDataUrl on the webgl context. - * @param {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when - * rendering, stopping pixel interpolation. - * @param {number} [options.backgroundColor=0x000000] - The background color of the rendered area - * (shown if not transparent). - * @param {boolean} [options.legacy=false] - If true PixiJS will aim to ensure compatibility - * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. - * @param {string} [options.powerPreference] - Parameter passed to webgl context, set to "high-performance" - * for devices with dual graphics card - */ - constructor(options, arg2, arg3) - { - super('WebGL', options, arg2, arg3); - - /** - * The type of this renderer as a standardised const - * - * @member {number} - * @see PIXI.RENDERER_TYPE - */ - this.type = RENDERER_TYPE.WEBGL; - - // this will be set by the contextSystem (this.context) - this.gl = null; - this.CONTEXT_UID = 0; - this.legacy = !!options.legacy; - - // TODO legacy! - - // runners! - this.runners = { - destroy: new Runner('destroy'), - contextChange: new Runner('contextChange', 1), - reset: new Runner('reset'), - update: new Runner('update'), - postrender: new Runner('postrender'), - prerender: new Runner('prerender'), - resize: new Runner('resize', 2), - }; - - 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(GeometrySystem, 'geometry') - .addSystem(FramebufferSystem, 'framebuffer') - .addSystem(StencilSystem, 'stencil') - .addSystem(ProjectionSystem, 'projection') - .addSystem(TextureGCSystem, 'textureGC') - .addSystem(FilterSystem, 'filter') - .addSystem(RenderTextureSystem, 'renderTexture') - .addSystem(BatchSystem, 'batch'); - - this.initPlugins(Renderer.__plugins); - - /** - * The options passed in to create a new webgl context. - * - * @member {object} - * @private - */ - if (options.context) - { - this.context.initFromContext(options.context); - } - else - { - this.context.initFromOptions({ - alpha: this.transparent, - antialias: options.antialias, - premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', - stencil: true, - 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); - } - - addSystem(_class, name) - { - if (!name) - { - name = _class.name; - } - - // TODO - read name from class.name.. - - /* - if(name.includes('System')) - { - name = name.replace('System', ''); - name = name.charAt(0).toLowerCase() + name.slice(1); - } - */ - - const system = new _class(this); - - if (this[name]) - { - throw new Error(`Whoops! ${name} is already a manger`); - } - - this[name] = system; - - for (const i in this.runners) - { - this.runners[i].add(system); - } - - return this; - - /** - * 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. - */ - } - - /** - * Renders the object to its webGL view - * - * @param {PIXI.DisplayObject} displayObject - the object to be rendered - * @param {PIXI.RenderTexture} renderTexture - The render texture to render to. - * @param {boolean} [clear] - Should the canvas be cleared before the new render - * @param {PIXI.Transform} [transform] - A transform to apply to the render texture before rendering. - * @param {boolean} [skipUpdateTransform] - Should we skip the update transform pass? - */ - render(displayObject, renderTexture, clear, transform, skipUpdateTransform) - { - // can be handy to know! - this.renderingToScreen = !renderTexture; - - this.runners.prerender.run(); - this.emit('prerender'); - - // 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.parent; - - displayObject.parent = this._tempDisplayObjectParent; - displayObject.updateTransform(); - displayObject.parent = 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.run(); - - this.emit('postrender'); - } - - /** - * Resizes the webGL view to the specified width and height. - * - * @param {number} screenWidth - the new width of the screen - * @param {number} screenHeight - the new height of the screen - */ - resize(screenWidth, screenHeight) - { - AbstractRenderer.prototype.resize.call(this, screenWidth, screenHeight); - - this.runners.resize.run(screenWidth, screenHeight); - } - - /** - * Resets the WebGL state so you can render things however you fancy! - * - * @return {PIXI.Renderer} Returns itself. - */ - reset() - { - this.runners.reset.run(); - - return this; - } - - clear() - { - this.framebuffer.bind(); - this.framebuffer.clear(); - } - - /** - * Removes everything from the renderer (event listeners, spritebatch, etc...) - * - * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. - * See: https://github.com/pixijs/pixi.js/issues/2233 - */ - destroy(removeView) - { - this.runners.destroy.run(); - - // call base destroy - super.destroy(removeView); - - // TODO nullify all the managers.. - this.gl = null; - } - - /** - * 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 - * builds and excluding plugins. - * @name PIXI.Renderer#plugins - * @type {object} - * @readonly - * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. - * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. - * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. - * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. - */ - - /** - * Adds a plugin to the renderer. - * - * @method - * @param {string} pluginName - The name of the plugin. - * @param {Function} ctor - The constructor function or class for the plugin. - */ - static registerPlugin(pluginName, ctor) - { - Renderer.__plugins = Renderer.__plugins || {}; - Renderer.__plugins[pluginName] = ctor; - } -} diff --git a/packages/core/src/renderers/State.js b/packages/core/src/renderers/State.js deleted file mode 100644 index fa3f221..0000000 --- a/packages/core/src/renderers/State.js +++ /dev/null @@ -1,163 +0,0 @@ -/* eslint-disable max-len */ - -const BLEND = 0; -const OFFSET = 1; -const CULLING = 2; -const DEPTH_TEST = 3; -const WINDING = 4; - -/** - * This is a webGL state. It is passed The WebGL StateManager. - * Each mesh renderered may require webGL to be in a different state. - * For example you may want different blend mode or to enable polygon offsets - * - * @class - * @memberof PIXI - */ -export default class State -{ - /** - * - */ - constructor() - { - this.data = 0; - - this.blendMode = 0; - this.polygonOffset = 0; - - this.blend = true; - // this.depthTest = true; - } - - /** - * Activates blending of the computed fragment color values - * - * @member {boolean} - */ - get blend() - { - return !!(this.data & (1 << BLEND)); - } - - set blend(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << BLEND)) !== value) - { - this.data ^= (1 << BLEND); - } - } - - /** - * Activates adding an offset to depth values of polygon's fragments - * - * @member {boolean} - * @default false - */ - get offsets() - { - return !!(this.data & (1 << OFFSET)); - } - - set offsets(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << OFFSET)) !== value) - { - this.data ^= (1 << OFFSET); - } - } - - /** - * Activates culling of polygons. - * - * @member {boolean} - * @default false - */ - get culling() - { - return !!(this.data & (1 << CULLING)); - } - - set culling(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << CULLING)) !== value) - { - this.data ^= (1 << CULLING); - } - } - - /** - * Activates depth comparisons and updates to the depth buffer. - * - * @member {boolean} - * @default false - */ - get depthTest() - { - return !!(this.data & (1 << DEPTH_TEST)); - } - - set depthTest(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << DEPTH_TEST)) !== value) - { - this.data ^= (1 << DEPTH_TEST); - } - } - - /** - * Specifies whether or not front or back-facing polygons can be culled. - * @member {boolean} - * @default false - */ - get clockwiseFrontFace() - { - return !!(this.data & (1 << WINDING)); - } - - set clockwiseFrontFace(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << WINDING)) !== value) - { - this.data ^= (1 << WINDING); - } - } - - /** - * The blend mode to be applied when this state is set. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. - * Setting this mode to anything other than NO_BLEND will automatically switch blending on. - * - * @member {boolean} - * @default PIXI.BLEND_MODES.NORMAL - * @see PIXI.BLEND_MODES - */ - get blendMode() - { - return this._blendMode; - } - - set blendMode(value) // eslint-disable-line require-jsdoc - { - // 17 is NO BLEND - this.blend = (value !== 17); - this._blendMode = value; - } - - /** - * The polygon offset. Setting this property to anything other than 0 will automatically enable poygon offset fill. - * - * @member {number} - * @default 0 - */ - get polygonOffset() - { - return this._polygonOffset; - } - - set polygonOffset(value) // eslint-disable-line require-jsdoc - { - this.offsets = !!value; - this._polygonOffset = value; - } -} - diff --git a/packages/core/src/renderers/filters/Filter.js b/packages/core/src/renderers/filters/Filter.js deleted file mode 100644 index 2073b8d..0000000 --- a/packages/core/src/renderers/filters/Filter.js +++ /dev/null @@ -1,140 +0,0 @@ -import Shader from '../../shader/Shader'; -import Program from '../../shader/Program'; -import State from '../State'; -import { settings } from '@pixi/settings'; -// import extractUniformsFromSrc from './extractUniformsFromSrc'; -import defaultVertex from './defaultFilter.vert'; -import defaultFragment from './defaultFilter.frag'; - -// let math = require('../../math'); -/** - * @class - * @memberof PIXI - * @extends PIXI.Shader - */ -export default class Filter extends Shader -{ - /** - * @param {string} [vertexSrc] - The source of the vertex shader. - * @param {string} [fragmentSrc] - The source of the fragment shader. - * @param {object} [uniforms] - Custom uniforms to use to augment the built-in ones. - */ - constructor(vertexSrc, fragmentSrc, uniforms) - { - const program = Program.from(vertexSrc, fragmentSrc); - - super(program, uniforms); - - /** - * The padding of the filter. Some filters require extra space to breath such as a blur. - * Increasing this will add extra width and height to the bounds of the object that the - * filter is applied to. - * - * @member {number} - */ - this.padding = 0; - - /** - * The resolution of the filter. Setting this to be lower will lower the quality but - * increase the performance of the filter. - * - * @member {number} - */ - this.resolution = settings.RESOLUTION; - - /** - * If enabled is true the filter is applied, if false it will not. - * - * @member {boolean} - */ - this.enabled = true; - - /** - * If enabled, PixiJS will fit the filter area into boundaries for better performance. - * Switch it off if it does not work for specific shader. - * - * @member {boolean} - */ - this.autoFit = true; - - /** - * Legacy filters use position and uvs from attributes - * @member {boolean} - * @readonly - */ - this.legacy = !!this.program.attributeData.aTextureCoord; - - /** - * the webGL state the filter requires to render - * @member {PIXI.State} - */ - this.state = new State(); - } - - /** - * Applies the filter - * - * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from - * @param {PIXI.RenderTarget} input - The input render target. - * @param {PIXI.RenderTarget} output - The target to output to. - * @param {boolean} clear - Should the output be cleared before rendering to it - * @param {object} [currentState] - It's current state of filter. - * There are some useful properties in the currentState : - * target, filters, sourceFrame, destinationFrame, renderTarget, resolution - */ - apply(filterManager, input, output, clear, currentState, derp) // eslint-disable-line no-unused-vars - { - // do as you please! - - filterManager.applyFilter(this, input, output, clear, currentState, derp); - - // or just do a regular render.. - } - - /** - * Sets the blendmode of the filter - * - * @member {number} - * @default PIXI.BLEND_MODES.NORMAL - */ - get blendMode() - { - return this.state.blendMode; - } - - set blendMode(value) // eslint-disable-line require-jsdoc - { - this.state.blendMode = value; - } - - /** - * The default vertex shader source - * - * @static - * @constant - */ - static get defaultVertexSrc() - { - return defaultVertex; - } - - /** - * The default fragment shader source - * - * @static - * @constant - */ - static get defaultFragmentSrc() - { - return defaultFragment; - } -} - -/** - * Used for caching shader IDs - * - * @static - * @private - */ -Filter.SOURCE_KEY_MAP = {}; - diff --git a/packages/core/src/renderers/filters/defaultFilter.frag b/packages/core/src/renderers/filters/defaultFilter.frag deleted file mode 100644 index a14c1be..0000000 --- a/packages/core/src/renderers/filters/defaultFilter.frag +++ /dev/null @@ -1,22 +0,0 @@ -varying vec2 vTextureCoord; -varying vec2 vFilterCoord; - -uniform sampler2D uSampler; -uniform sampler2D filterSampler; - -void main(void){ - vec4 masky = texture2D(filterSampler, vFilterCoord); - vec4 sample = texture2D(uSampler, vTextureCoord); - vec4 color; - if(mod(vFilterCoord.x, 1.0) > 0.5) - { - color = vec4(1.0, 0.0, 0.0, 1.0); - } - else - { - color = vec4(0.0, 1.0, 0.0, 1.0); - } - // gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0); - gl_FragColor = mix(sample, masky, 0.5); - gl_FragColor *= sample.a; -} diff --git a/packages/core/src/renderers/filters/defaultFilter.vert b/packages/core/src/renderers/filters/defaultFilter.vert deleted file mode 100644 index e1febca..0000000 --- a/packages/core/src/renderers/filters/defaultFilter.vert +++ /dev/null @@ -1,14 +0,0 @@ -attribute vec2 aVertexPosition; -attribute vec2 aTextureCoord; - -uniform mat3 projectionMatrix; -uniform mat3 filterMatrix; - -varying vec2 vTextureCoord; -varying vec2 vFilterCoord; - -void main(void){ - gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); - vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy; - vTextureCoord = aTextureCoord ; -} diff --git a/packages/core/src/renderers/filters/filterTransforms.js b/packages/core/src/renderers/filters/filterTransforms.js deleted file mode 100644 index 87a3cd6..0000000 --- a/packages/core/src/renderers/filters/filterTransforms.js +++ /dev/null @@ -1,50 +0,0 @@ -import { Matrix } from '@pixi/math'; - -/** - * Calculates the mapped matrix - * @param filterArea {Rectangle} The filter area - * @param sprite {Sprite} the target sprite - * @param outputMatrix {Matrix} @alvin - * @private - */ -// this returns a matrix that will normalise map filter cords in the filter to screen space -export function calculateScreenSpaceMatrix(outputMatrix, filterArea, textureSize) -{ - // TODO unwrap? - const mappedMatrix = outputMatrix.identity(); - - mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); - - mappedMatrix.scale(textureSize.width, textureSize.height); - - return mappedMatrix; -} - -export function calculateNormalizedScreenSpaceMatrix(outputMatrix, filterArea, textureSize) -{ - const mappedMatrix = outputMatrix.identity(); - - mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); - - const translateScaleX = (textureSize.width / filterArea.width); - const translateScaleY = (textureSize.height / filterArea.height); - - mappedMatrix.scale(translateScaleX, translateScaleY); - - return mappedMatrix; -} - -// this will map the filter coord so that a texture can be used based on the transform of a sprite -export function calculateSpriteMatrix(outputMatrix, filterArea, textureSize, sprite) -{ - const orig = sprite._texture.orig; - const mappedMatrix = outputMatrix.set(textureSize.width, 0, 0, textureSize.height, filterArea.x, filterArea.y); - const worldTransform = sprite.worldTransform.copyTo(Matrix.TEMP_MATRIX); - - worldTransform.invert(); - mappedMatrix.prepend(worldTransform); - mappedMatrix.scale(1.0 / orig.width, 1.0 / orig.height); - mappedMatrix.translate(sprite.anchor.x, sprite.anchor.y); - - return mappedMatrix; -} diff --git a/packages/core/src/renderers/filters/spriteMask/SpriteMaskFilter.js b/packages/core/src/renderers/filters/spriteMask/SpriteMaskFilter.js deleted file mode 100644 index d5a91a4..0000000 --- a/packages/core/src/renderers/filters/spriteMask/SpriteMaskFilter.js +++ /dev/null @@ -1,63 +0,0 @@ -import Filter from '../Filter'; -import { Matrix } from '@pixi/math'; -import vertex from './spriteMaskFilter.vert'; -import fragment from './spriteMaskFilter.frag'; -import { default as TextureMatrix } from '../../../textures/TextureMatrix'; - -/** - * The SpriteMaskFilter class - * - * @class - * @extends PIXI.Filter - * @memberof PIXI - */ -export default class SpriteMaskFilter extends Filter -{ - /** - * @param {PIXI.Sprite} sprite - the target sprite - */ - constructor(sprite) - { - const maskMatrix = new Matrix(); - - super(vertex, fragment); - - sprite.renderable = false; - - this.maskSprite = sprite; - this.maskMatrix = maskMatrix; - } - - /** - * Applies the filter - * - * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from - * @param {PIXI.RenderTarget} input - The input render target. - * @param {PIXI.RenderTarget} output - The target to output to. - */ - apply(filterManager, input, output) - { - const maskSprite = this.maskSprite; - const tex = this.maskSprite.texture; - - if (!tex.valid) - { - return; - } - if (!tex.transform) - { - // margin = 0.0, let it bleed a bit, shader code becomes easier - // assuming that atlas textures were made with 1-pixel padding - tex.transform = new TextureMatrix(tex, 0.0); - } - tex.transform.update(); - - this.uniforms.mask = tex; - this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite) - .prepend(tex.transform.mapCoord); - this.uniforms.alpha = maskSprite.worldAlpha; - this.uniforms.maskClamp = tex.transform.uClampFrame; - - filterManager.applyFilter(this, input, output); - } -} diff --git a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.frag b/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.frag deleted file mode 100644 index 0e4aef8..0000000 --- a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.frag +++ /dev/null @@ -1,23 +0,0 @@ -varying vec2 vMaskCoord; -varying vec2 vTextureCoord; - -uniform sampler2D uSampler; -uniform sampler2D mask; -uniform float alpha; -uniform vec4 maskClamp; - -void main(void) -{ - float clip = step(3.5, - step(maskClamp.x, vMaskCoord.x) + - step(maskClamp.y, vMaskCoord.y) + - step(vMaskCoord.x, maskClamp.z) + - step(vMaskCoord.y, maskClamp.w)); - - vec4 original = texture2D(uSampler, vTextureCoord); - vec4 masky = texture2D(mask, vMaskCoord); - - original *= (masky.r * masky.a * alpha * clip); - - gl_FragColor = original; -} diff --git a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.vert b/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.vert deleted file mode 100644 index d23a30c..0000000 --- a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.vert +++ /dev/null @@ -1,16 +0,0 @@ -attribute vec2 aVertexPosition; -attribute vec2 aTextureCoord; - -uniform mat3 projectionMatrix; -uniform mat3 otherMatrix; - -varying vec2 vMaskCoord; -varying vec2 vTextureCoord; - -void main(void) -{ - gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); - - vTextureCoord = aTextureCoord; - vMaskCoord = ( otherMatrix * vec3( aTextureCoord, 1.0) ).xy; -} diff --git a/packages/core/src/renderers/systems/BatchSystem.js b/packages/core/src/renderers/systems/BatchSystem.js deleted file mode 100644 index 70e9518..0000000 --- a/packages/core/src/renderers/systems/BatchSystem.js +++ /dev/null @@ -1,68 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import ObjectRenderer from '../utils/ObjectRenderer'; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ - -export default class BatchSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this manager works for. - */ - constructor(renderer) - { - super(renderer); - - /** - * An empty renderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.emptyRenderer = new ObjectRenderer(renderer); - - /** - * The currently active ObjectRenderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.currentRenderer = this.emptyRenderer; - } - - /** - * Changes the current renderer to the one given in parameter - * - * @param {PIXI.ObjectRenderer} objectRenderer - The object renderer to use. - */ - setObjectRenderer(objectRenderer) - { - if (this.currentRenderer === objectRenderer) - { - return; - } - - this.currentRenderer.stop(); - this.currentRenderer = objectRenderer; - - this.renderer.state.setState(objectRenderer.state); - - this.currentRenderer.start(); - } - - /** - * This should be called if you wish to do some custom rendering - * It will basically render anything that may be batched up such as sprites - * - */ - flush() - { - this.setObjectRenderer(this.emptyRenderer); - } - - reset() - { - this.setObjectRenderer(this.emptyRenderer); - } -} diff --git a/packages/core/src/renderers/systems/ContextSystem.js b/packages/core/src/renderers/systems/ContextSystem.js deleted file mode 100644 index 43bf827..0000000 --- a/packages/core/src/renderers/systems/ContextSystem.js +++ /dev/null @@ -1,186 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { settings } from '@pixi/settings'; - -let CONTEXT_UID = 0; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class ContextSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this.webGLVersion = 1; - - this.handleContextLost = this.handleContextLost.bind(this); - this.handleContextRestored = this.handleContextRestored.bind(this); - - this.extensions = {}; - - renderer.view.addEventListener('webglcontextlost', this.handleContextLost, false); - renderer.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); - } - - get isLost() - { - return (!this.gl || this.gl.isContextLost()); - } - - contextChange(gl) - { - this.gl = gl; - - // restore a context if it was previously lost - if (gl.isContextLost() && gl.getExtension('WEBGL_lose_context')) - { - gl.getExtension('WEBGL_lose_context').restoreContext(); - } - } - - initFromContext(gl) - { - this.gl = gl; - this.validateContext(gl); - this.renderer.gl = gl; - this.renderer.CONTEXT_UID = CONTEXT_UID++; - this.renderer.runners.contextChange.run(gl); - } - - initFromOptions(options) - { - const gl = this.createContext(this.renderer.view, options); - - this.initFromContext(gl); - } - - /** - * Helper class to create a webGL Context - * - * @class - * @memberof PIXI.glCore - * @param canvas {HTMLCanvasElement} the canvas element that we will get the context from - * @param options {Object} An options object that gets passed in to the canvas element containing the context attributes, - * see https://developer.mozilla.org/en/docs/Web/API/HTMLCanvasElement/getContext for the options available - * @return {WebGLRenderingContext} the WebGL context - */ - createContext(canvas, options) - { - let gl; - - if (settings.PREFER_WEBGL_2) - { - gl = canvas.getContext('webgl2', options); - } - - if (gl) - { - this.webGLVersion = 2; - } - else - { - this.webGLVersion = 1; - - gl = canvas.getContext('webgl', options) - || canvas.getContext('experimental-webgl', options); - - if (!gl) - { - // fail, not able to get a context - throw new Error('This browser does not support webGL. Try using the canvas renderer'); - } - } - - this.gl = gl; - - this.getExtensions(); - - return gl; - } - - getExtensions() - { - // time to set up default etensions that pixi uses.. - const gl = this.gl; - const extensions = this.extensions; - - if (this.webGLVersion === 1) - { - extensions.drawBuffers = gl.getExtension('WEBGL_draw_buffers'); - extensions.depthTexture = gl.getExtension('WEBKIT_WEBGL_depth_texture'); - extensions.floatTexture = gl.getExtension('OES_texture_float'); - extensions.loseContext = gl.getExtension('WEBGL_lose_context'); - - extensions.vertexArrayObject = gl.getExtension('OES_vertex_array_object') - || gl.getExtension('MOZ_OES_vertex_array_object') - || gl.getExtension('WEBKIT_OES_vertex_array_object'); - } - - // we don't use any specific WebGL 2 ones yet! - } - - /** - * Handles a lost webgl context - * - * @private - * @param {WebGLContextEvent} event - The context lost event. - */ - handleContextLost(event) - { - event.preventDefault(); - } - - /** - * Handles a restored webgl context - * - * @private - */ - handleContextRestored() - { - this.renderer.runners.contextChange.run(this.gl); - } - - destroy() - { - const view = this.renderer.view; - - // remove listeners - view.removeEventListener('webglcontextlost', this.handleContextLost); - view.removeEventListener('webglcontextrestored', this.handleContextRestored); - - this.gl.useProgram(null); - - if (this.extensions.loseContext) - { - this.extensions.loseContext.loseContext(); - } - } - - postrender() - { - this.gl.flush(); - } - - validateContext(gl) - { - const attributes = gl.getContextAttributes(); - - // this is going to be fairly simple for now.. but at least we have room to grow! - if (!attributes.stencil) - { - /* eslint-disable max-len */ - - /* eslint-disable no-console */ - console.warn('Provided WebGL context does not have a stencil buffer, masks may not render correctly'); - /* eslint-enable no-console */ - - /* eslint-enable max-len */ - } - } -} diff --git a/packages/core/src/renderers/systems/FramebufferSystem.js b/packages/core/src/renderers/systems/FramebufferSystem.js deleted file mode 100644 index df7902b..0000000 --- a/packages/core/src/renderers/systems/FramebufferSystem.js +++ /dev/null @@ -1,246 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { Rectangle } from '@pixi/math'; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class FramebufferSystem extends WebGLSystem -{ - /** - * Sets up the renderer context and necessary buffers. - * - * @private - */ - contextChange() - { - this.gl = this.renderer.gl; - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - this.current = null; - this.viewport = new Rectangle(); - - this.drawBufferExtension = this.renderer.context.extensions.drawBuffers; - } - - bind(framebuffer, frame) - { - const gl = this.gl; - - this.current = framebuffer; - - if (framebuffer) - { - // TODO cacheing layer! - - const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID] || this.initFramebuffer(framebuffer); - - gl.bindFramebuffer(gl.FRAMEBUFFER, fbo.framebuffer); - // makesure all textures are unbound.. - - // now check for updates... - if (fbo.dirtyId !== framebuffer.dirtyId) - { - fbo.dirtyId = framebuffer.dirtyId; - - if (fbo.dirtyFormat !== framebuffer.dirtyFormat) - { - fbo.dirtyFormat = framebuffer.dirtyFormat; - this.updateFramebuffer(framebuffer); - } - else if (fbo.dirtySize !== framebuffer.dirtySize) - { - fbo.dirtySize = framebuffer.dirtySize; - this.resizeFramebuffer(framebuffer); - } - } - - for (let i = 0; i < framebuffer.colorTextures.length; i++) - { - if (framebuffer.colorTextures[i].texturePart) - { - this.renderer.texture.unbind(framebuffer.colorTextures[i].texture); - } - else - { - this.renderer.texture.unbind(framebuffer.colorTextures[i]); - } - } - - if (framebuffer.depthTexture) - { - this.renderer.texture.unbind(framebuffer.depthTexture); - } - - if (frame) - { - this.setViewport(frame.x, frame.y, frame.width, frame.height); - } - else - { - this.setViewport(0, 0, framebuffer.width, framebuffer.height); - } - } - else - { - gl.bindFramebuffer(gl.FRAMEBUFFER, null); - - if (frame) - { - this.setViewport(frame.x, frame.y, frame.width, frame.height); - } - else - { - this.setViewport(0, 0, this.renderer.width, this.renderer.height); - } - } - } - - setViewport(x, y, width, height) - { - const v = this.viewport; - - if (v.width !== width || v.height !== height || v.x !== x || v.y !== y) - { - v.x = x; - v.y = y; - v.width = width; - v.height = height; - - this.gl.viewport(x, y, width, height); - } - } - - get size() - { - if (this.current) - { - // TODO store temp - return { x: 0, y: 0, width: this.current.width, height: this.current.height }; - } - - return { x: 0, y: 0, width: this.renderer.width, height: this.renderer.height }; - } - - clear(r, g, b, a) - { - const gl = this.gl; - - // TODO clear color can be set only one right? - gl.clearColor(r, g, b, a); - gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); - } - - // private functions... - - initFramebuffer(framebuffer) - { - const gl = this.gl; - - // TODO - make this a class? - const fbo = { - framebuffer: gl.createFramebuffer(), - stencil: null, - dirtyId: 0, - dirtyFormat: 0, - dirtySize: 0, - }; - - framebuffer.glFrameBuffers[this.CONTEXT_UID] = fbo; - - return fbo; - } - - resizeFramebuffer(framebuffer) - { - const gl = this.gl; - - if (framebuffer.stencil || framebuffer.depth) - { - gl.bindRenderbuffer(gl.RENDERBUFFER, this.stencil); - gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); - } - } - - updateFramebuffer(framebuffer) - { - const gl = this.gl; - - const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID]; - - // bind the color texture - const colorTextures = framebuffer.colorTextures; - - let count = colorTextures.length; - - if (!this.drawBufferExtension) - { - count = Math.min(count, 1); - } - - const activeTextures = []; - - for (let i = 0; i < count; i++) - { - const texture = framebuffer.colorTextures[i]; - - if (texture.texturePart) - { - this.renderer.texture.bind(texture.texture, 0); - - gl.framebufferTexture2D(gl.FRAMEBUFFER, - gl.COLOR_ATTACHMENT0 + i, - gl.TEXTURE_CUBE_MAP_NEGATIVE_X + texture.side, - texture.texture._glTextures[this.CONTEXT_UID].texture, - 0); - } - else - { - this.renderer.texture.bind(texture, 0); - - gl.framebufferTexture2D(gl.FRAMEBUFFER, - gl.COLOR_ATTACHMENT0 + i, - gl.TEXTURE_2D, - texture._glTextures[this.CONTEXT_UID].texture, - 0); - } - - activeTextures.push(gl.COLOR_ATTACHMENT0 + i); - } - - if (this.drawBufferExtension && activeTextures.length > 1) - { - this.drawBufferExtension.drawBuffersWEBGL(activeTextures); - } - - if (framebuffer.depthTexture) - { - const depthTextureExt = this.renderer.context.extensions.depthTexture; - - if (depthTextureExt) - { - const depthTexture = framebuffer.depthTexture; - - this.renderer.texture.bind(depthTexture, 0); - - gl.framebufferTexture2D(gl.FRAMEBUFFER, - gl.DEPTH_ATTACHMENT, - gl.TEXTURE_2D, - depthTexture._glTextures[this.CONTEXT_UID].texture, - 0); - } - } - - if (framebuffer.stencil || framebuffer.depth) - { - fbo.stencil = gl.createRenderbuffer(); - - gl.bindRenderbuffer(gl.RENDERBUFFER, fbo.stencil); - - // TODO.. this is depth AND stencil? - gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, fbo.stencil); - gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); - // fbo.enableStencil(); - } - } -} diff --git a/packages/core/src/renderers/systems/MaskSystem.js b/packages/core/src/renderers/systems/MaskSystem.js deleted file mode 100644 index d9c4b1a..0000000 --- a/packages/core/src/renderers/systems/MaskSystem.js +++ /dev/null @@ -1,199 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import AlphaMaskFilter from '../filters/spriteMask/SpriteMaskFilter'; - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class MaskSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - // TODO - we don't need both! - this.scissor = false; - this.scissorData = null; - this.scissorRenderTarget = null; - - this.enableScissor = false; - - this.alphaMaskPool = []; - this.alphaMaskIndex = 0; - } - - /** - * Applies the Mask and adds it to the current filter stack. - * - * @param {PIXI.DisplayObject} target - Display Object to push the mask to - * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. - */ - push(target, maskData) - { - // TODO the root check means scissor rect will not - // be used on render textures more info here: - // https://github.com/pixijs/pixi.js/pull/3545 - - if (maskData.texture) - { - this.pushSpriteMask(target, maskData); - } - else if (this.enableScissor - && !this.scissor - && this.renderer._activeRenderTarget.root - && !this.renderer.stencil.stencilMaskStack.length - && maskData.isFastRect()) - { - const matrix = maskData.worldTransform; - - let rot = Math.atan2(matrix.b, matrix.a); - - // use the nearest degree! - rot = Math.round(rot * (180 / Math.PI)); - - if (rot % 90) - { - this.pushStencilMask(maskData); - } - else - { - this.pushScissorMask(target, maskData); - } - } - else - { - this.pushStencilMask(maskData); - } - } - - /** - * Removes the last mask from the mask stack and doesn't return it. - * - * @param {PIXI.DisplayObject} target - Display Object to pop the mask from - * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. - */ - pop(target, maskData) - { - if (maskData.texture) - { - this.popSpriteMask(target, maskData); - } - else if (this.enableScissor && !this.renderer.stencil.stencilMaskStack.length) - { - this.popScissorMask(target, maskData); - } - else - { - this.popStencilMask(target, maskData); - } - } - - /** - * Applies the Mask and adds it to the current filter stack. - * - * @param {PIXI.RenderTarget} target - Display Object to push the sprite mask to - * @param {PIXI.Sprite} maskData - Sprite to be used as the mask - */ - pushSpriteMask(target, maskData) - { - let alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; - - if (!alphaMaskFilter) - { - alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; - } - - alphaMaskFilter[0].resolution = this.renderer.resolution; - alphaMaskFilter[0].maskSprite = maskData; - - // TODO - may cause issues! - target.filterArea = maskData.getBounds(true); - - this.renderer.filterSystem.pushFilter(target, alphaMaskFilter); - - this.alphaMaskIndex++; - } - - /** - * Removes the last filter from the filter stack and doesn't return it. - * - */ - popSpriteMask() - { - this.renderer.filterSystem.popFilter(); - this.alphaMaskIndex--; - } - - /** - * Applies the Mask and adds it to the current filter stack. - * - * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. - */ - pushStencilMask(maskData) - { - this.renderer.batch.flush(); - this.renderer.stencil.pushStencil(maskData); - } - - /** - * Removes the last filter from the filter stack and doesn't return it. - * - */ - popStencilMask() - { - // this.renderer.currentRenderer.stop(); - this.renderer.stencil.popStencil(); - } - - /** - * - * @param {PIXI.DisplayObject} target - Display Object to push the mask to - * @param {PIXI.Graphics} maskData - The masking data. - */ - pushScissorMask(target, maskData) - { - maskData.renderable = true; - - const renderTarget = this.renderer._activeRenderTarget; - - const bounds = maskData.getBounds(); - - bounds.fit(renderTarget.size); - maskData.renderable = false; - - this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); - - const resolution = this.renderer.resolution; - - this.renderer.gl.scissor( - bounds.x * resolution, - (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, - bounds.width * resolution, - bounds.height * resolution - ); - - this.scissorRenderTarget = renderTarget; - this.scissorData = maskData; - this.scissor = true; - } - - /** - * - * - */ - popScissorMask() - { - this.scissorRenderTarget = null; - this.scissorData = null; - this.scissor = false; - - // must be scissor! - const gl = this.renderer.gl; - - gl.disable(gl.SCISSOR_TEST); - } -} diff --git a/packages/core/src/renderers/systems/ProjectionSystem.js b/packages/core/src/renderers/systems/ProjectionSystem.js deleted file mode 100644 index ec5efdb..0000000 --- a/packages/core/src/renderers/systems/ProjectionSystem.js +++ /dev/null @@ -1,73 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { Matrix } from '@pixi/math'; - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ - -export default class ProjectionSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this.projectionMatrix = new Matrix(); - } - - update(destinationFrame, sourceFrame, resolution, root) - { - this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; - this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; - - this.calculateProjection(this.destinationFrame, this.sourceFrame, resolution, root); - - this.renderer.globalUniforms.uniforms.projectionMatrix = this.projectionMatrix; - this.renderer.globalUniforms.update(); - } - - /** - * Updates the projection matrix based on a projection frame (which is a rectangle) - * - * @param {Rectangle} destinationFrame - The destination frame. - * @param {Rectangle} sourceFrame - The source frame. - */ - calculateProjection(destinationFrame, sourceFrame, resolution, root) - { - const pm = this.projectionMatrix; - - // I don't think we will need this line.. - // pm.identity(); - - if (!root) - { - pm.a = (1 / destinationFrame.width * 2) * resolution; - pm.d = (1 / destinationFrame.height * 2) * resolution; - - pm.tx = -1 - (sourceFrame.x * pm.a); - pm.ty = -1 - (sourceFrame.y * pm.d); - } - else - { - pm.a = (1 / destinationFrame.width * 2) * resolution; - pm.d = (-1 / destinationFrame.height * 2) * resolution; - - pm.tx = -1 - (sourceFrame.x * pm.a); - pm.ty = 1 - (sourceFrame.y * pm.d); - } - } - - /** - * Sets the transform of the active render target to the given matrix - * - * @param {PIXI.Matrix} matrix - The transformation matrix - */ - setTransform()// matrix) - { - // this._activeRenderTarget.transform = matrix; - } -} diff --git a/packages/core/src/renderers/systems/RenderTextureSystem.js b/packages/core/src/renderers/systems/RenderTextureSystem.js deleted file mode 100644 index a133ef1..0000000 --- a/packages/core/src/renderers/systems/RenderTextureSystem.js +++ /dev/null @@ -1,115 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { Rectangle } from '@pixi/math'; - -const tempRect = new Rectangle(); - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ - -export default class RenderTextureSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this.clearColor = renderer._backgroundColorRgba; - - // TODO moe this property somewhere else! - this.defaultMaskStack = []; - this.defaultFilterStack = [{}]; - - // empty render texture? - this.renderTexture = null; - - this.destinationFrame = new Rectangle(); - } - - bind(renderTexture, sourceFrame, destinationFrame) - { - // TODO - do we want this?? - if (this.renderTexture === renderTexture) return; - this.renderTexture = renderTexture; - - const renderer = this.renderer; - - if (renderTexture) - { - const baseTexture = renderTexture.baseTexture; - - if (!destinationFrame) - { - tempRect.width = baseTexture.realWidth; - tempRect.height = baseTexture.realHeight; - - destinationFrame = tempRect; - } - - if (!sourceFrame) - { - sourceFrame = destinationFrame; - } - - this.renderer.framebuffer.bind(baseTexture.frameBuffer, destinationFrame); - - this.renderer.projection.update(destinationFrame, sourceFrame, baseTexture.resolution, false); - this.renderer.stencil.setMaskStack(baseTexture.stencilMaskStack); - } - else - { - // TODO these validation checks happen deeper down.. - // thing they can be avoided.. - if (!destinationFrame) - { - tempRect.width = renderer.width; - tempRect.height = renderer.height; - - destinationFrame = tempRect; - } - - if (!sourceFrame) - { - sourceFrame = destinationFrame; - } - - renderer.framebuffer.bind(null, destinationFrame); - - // TODO store this.. - this.renderer.projection.update(destinationFrame, sourceFrame, this.renderer.resolution, true); - this.renderer.stencil.setMaskStack(this.defaultMaskStack); - } - - this.destinationFrame.copyFrom(destinationFrame); - } - - /** - * Erases the render texture and fills the drawing area with a colour - * - * @param {number} [clearColor] - The colour - * @return {PIXI.Renderer} Returns itself. - */ - clear(clearColor) - { - if (this.renderTexture) - { - clearColor = clearColor || this.renderTexture.baseTexture.clearColor; - } - else - { - clearColor = clearColor || this.clearColor; - } - - this.renderer.framebuffer.clear(clearColor[0], clearColor[1], clearColor[2], clearColor[3]); - } - - resize()// screenWidth, screenHeight) - { - // resize the root only! - this.bind(null); - } -} diff --git a/packages/core/src/renderers/systems/StateSystem.js b/packages/core/src/renderers/systems/StateSystem.js deleted file mode 100755 index d7f4caf..0000000 --- a/packages/core/src/renderers/systems/StateSystem.js +++ /dev/null @@ -1,288 +0,0 @@ -import mapWebGLBlendModesToPixi from '../utils/mapWebGLBlendModesToPixi'; -import WebGLSystem from './WebGLSystem'; -import WebGLState from '../State'; - -const BLEND = 0; -const OFFSET = 1; -const CULLING = 2; -const DEPTH_TEST = 3; -const WINDING = 4; - -/** - * A WebGL state machines - * - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class StateSystem extends WebGLSystem -{ - /** - * @param {WebGLRenderingContext} gl - The current WebGL rendering context - */ - constructor(renderer) - { - super(renderer); - - this.gl = null; - - this.maxAttribs = null; - - // check we have vao.. - this.nativeVaoExtension = null; - - this.attribState = null; - - this.stateId = 0; - this.polygonOffset = 0; - this.blendMode = 17; - - this.map = []; - - // map functions for when we set state.. - this.map[BLEND] = this.setBlend; - this.map[OFFSET] = this.setOffset; - this.map[CULLING] = this.setCullFace; - this.map[DEPTH_TEST] = this.setDepthTest; - this.map[WINDING] = this.setFrontFace; - - this.checks = []; - - this.defaultState = new WebGLState(); - this.defaultState.blend = true; - this.defaultState.depth = true; - } - - contextChange(gl) - { - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; - - this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); - - // check we have vao.. - this.nativeVaoExtension = ( - gl.getExtension('OES_vertex_array_object') - || gl.getExtension('MOZ_OES_vertex_array_object') - || gl.getExtension('WEBKIT_OES_vertex_array_object') - ); - - this.attribState = { - tempAttribState: new Array(this.maxAttribs), - attribState: new Array(this.maxAttribs), - }; - - this.blendModes = mapWebGLBlendModesToPixi(gl); - - this.setState(this.defaultState); - - this.reset(); - } - - /** - * Sets the current state - * - * @param {*} state - The state to set. - */ - setState(state) - { - state = state || this.defaultState; - - // TODO maybe to an object check? ( this.state === state )? - if (this.stateId !== state.data) - { - let diff = this.stateId ^ state.data; - let i = 0; - - // order from least to most common - while (diff) - { - if (diff & 1) - { - // state change! - this.map[i].call(this, !!(state.data & (1 << i))); - } - - diff = diff >> 1; - i++; - } - - this.stateId = state.data; - } - - // based on the above settings we check for specific modes.. - // for example if blend is active we check and set the blend modes - // or of polygon offset is active we check the poly depth. - for (let i = 0; i < this.checks.length; i++) - { - this.checks[i](this, state); - } - } - - /** - * Enables or disabled blending. - * - * @param {boolean} value - Turn on or off webgl blending. - */ - setBlend(value) - { - this.updateCheck(StateSystem.checkBlendMode, value); - - this.gl[value ? 'enable' : 'disable'](this.gl.BLEND); - } - - /** - * Enables or disable polygon offset fill - * - * @param {boolean} value - Turn on or off webgl polygon offset testing. - */ - setOffset(value) - { - this.gl[value ? 'enable' : 'disable'](this.gl.POLYGON_OFFSET_FILL); - } - - /** - * Sets whether to enable or disable depth test. - * - * @param {boolean} value - Turn on or off webgl depth testing. - */ - setDepthTest(value) - { - this.gl[value ? 'enable' : 'disable'](this.gl.DEPTH_TEST); - } - - /** - * Sets whether to enable or disable cull face. - * - * @param {boolean} value - Turn on or off webgl cull face. - */ - setCullFace(value) - { - this.gl[value ? 'enable' : 'disable'](this.gl.CULL_FACE); - } - - /** - * Sets the gl front face. - * - * @param {boolean} value - true is clockwise and false is counter-clockwise - */ - setFrontFace(value) - { - this.gl.frontFace(this.gl[value ? 'CW' : 'CCW']); - } - - /** - * Sets the blend mode. - * - * @param {number} value - The blend mode to set to. - */ - setBlendMode(value) - { - if (value === this.blendMode) - { - return; - } - - this.blendMode = value; - this.gl.blendFunc(this.blendModes[value][0], this.blendModes[value][1]); - } - - /** - * Sets the polygon offset. - * - * @param {number} value - the polygon offset - * @param {number} scale - the polygon offset scale - */ - setPolygonOffset(value, scale) - { - this.gl.polygonOffset(value, scale); - } - - /** - * Disables all the vaos in use - * - */ - resetAttributes() - { - for (let i = 0; i < this.attribState.tempAttribState.length; i++) - { - this.attribState.tempAttribState[i] = 0; - } - - for (let i = 0; i < this.attribState.attribState.length; i++) - { - this.attribState.attribState[i] = 0; - } - - // im going to assume one is always active for performance reasons. - for (let i = 1; i < this.maxAttribs; i++) - { - this.gl.disableVertexAttribArray(i); - } - } - - // used - /** - * Resets all the logic and disables the vaos - */ - reset() - { - // unbind any VAO if they exist.. - if (this.nativeVaoExtension) - { - this.nativeVaoExtension.bindVertexArrayOES(null); - } - - // reset all attributes.. - this.resetAttributes(); - - this.gl.pixelStorei(this.gl.UNPACK_FLIP_Y_WEBGL, false); - - this.setBlendMode(0); - - // TODO? - // this.setState(this.defaultState); - } - - /** - * checks to see which updates should be checked based on which settings have been activated - * for example if blend is enabled then we shold check the blend modes each time the state is changed - * or if poygon fill is activated then we need to check if the polygone offset changes. - * The idea is that we only check what we have too. - * - * @param {Function} func the checking function to add or remove - * @param {boolean} value should the check function be added or removed. - */ - updateCheck(func, value) - { - const index = this.checks.indexOf(func); - - if (value && index === -1) - { - this.checks.push(func); - } - else if (!value && index !== -1) - { - this.checks.splice(index, 1); - } - } - - /** - * A private little wrapper function that we call to check the blend mode. - * - * @static - * @private - * @param {PIXI.StateSystem} System the System to perform the state check on - * @param {PIXI.State} state the state that the blendMode will pulled from - */ - static checkBlendMode(system, state) - { - system.setBlendMode(state.blendMode); - } - - // TODO - add polygon offset? -} diff --git a/packages/core/src/renderers/systems/StencilSystem.js b/packages/core/src/renderers/systems/StencilSystem.js deleted file mode 100644 index 5d538cb..0000000 --- a/packages/core/src/renderers/systems/StencilSystem.js +++ /dev/null @@ -1,133 +0,0 @@ -import WebGLSystem from './WebGLSystem'; - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class StencilSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - this.stencilMaskStack = []; - } - - /** - * Changes the mask stack that is used by this System. - * - * @param {PIXI.Graphics[]} stencilMaskStack - The mask stack - */ - setMaskStack(stencilMaskStack) - { - const gl = this.renderer.gl; - - if (stencilMaskStack.length !== this.stencilMaskStack.length) - { - if (stencilMaskStack.length === 0) - { - gl.disable(gl.STENCIL_TEST); - } - else - { - gl.enable(gl.STENCIL_TEST); - } - } - - this.stencilMaskStack = stencilMaskStack; - } - - /** - * Applies the Mask and adds it to the current stencil stack. @alvin - * - * @param {PIXI.Graphics} graphics - The mask - */ - pushStencil(graphics) - { - this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); - - // this.renderer._activeRenderTarget.attachStencilBuffer(); - - const gl = this.renderer.gl; - const prevMaskCount = this.stencilMaskStack.length; - - if (prevMaskCount === 0) - { - gl.enable(gl.STENCIL_TEST); - } - - this.stencilMaskStack.push(graphics); - - // Increment the refference stencil value where the new mask overlaps with the old ones. - gl.colorMask(false, false, false, false); - gl.stencilFunc(gl.EQUAL, prevMaskCount, this._getBitwiseMask()); - gl.stencilOp(gl.KEEP, gl.KEEP, gl.INCR); - this.renderer.plugins.graphics.render(graphics); - - this._useCurrent(); - } - - /** - * Removes the last mask from the stencil stack. @alvin - */ - popStencil() - { - this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); - - const gl = this.renderer.gl; - const graphics = this.stencilMaskStack.pop(); - - if (this.stencilMaskStack.length === 0) - { - // the stack is empty! - gl.disable(gl.STENCIL_TEST); - gl.clear(gl.STENCIL_BUFFER_BIT); - gl.clearStencil(0); - } - else - { - // Decrement the refference stencil value where the popped mask overlaps with the other ones - gl.colorMask(false, false, false, false); - gl.stencilOp(gl.KEEP, gl.KEEP, gl.DECR); - this.renderer.plugins.graphics.render(graphics); - - this._useCurrent(); - } - } - - /** - * Setup renderer to use the current stencil data. - */ - _useCurrent() - { - const gl = this.renderer.gl; - - gl.colorMask(true, true, true, true); - gl.stencilFunc(gl.EQUAL, this.stencilMaskStack.length, this._getBitwiseMask()); - gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP); - } - - /** - * Fill 1s equal to the number of acitve stencil masks. - * - * @return {number} The bitwise mask. - */ - _getBitwiseMask() - { - return (1 << this.stencilMaskStack.length) - 1; - } - - /** - * Destroys the mask stack. - * - */ - destroy() - { - super.destroy(this); - - this.stencilMaskStack = null; - } -} diff --git a/packages/core/src/renderers/systems/WebGLSystem.js b/packages/core/src/renderers/systems/WebGLSystem.js deleted file mode 100644 index d3d5b31..0000000 --- a/packages/core/src/renderers/systems/WebGLSystem.js +++ /dev/null @@ -1,41 +0,0 @@ -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this manager works for. - */ - constructor(renderer) - { - /** - * The renderer this manager works for. - * - * @member {PIXI.Renderer} - */ - this.renderer = renderer; - - this.renderer.runners.contextChange.add(this); - } - - /** - * Generic method called when there is a WebGL context change. - * - */ - contextChange() - { - // do some codes init! - } - - /** - * Generic destroy methods to be overridden by the subclass - * - */ - destroy() - { - this.renderer.runners.contextChange.remove(this); - this.renderer = null; - } -} diff --git a/packages/core/src/renderers/systems/filter/FilterSystem.js b/packages/core/src/renderers/systems/filter/FilterSystem.js deleted file mode 100644 index e615037..0000000 --- a/packages/core/src/renderers/systems/filter/FilterSystem.js +++ /dev/null @@ -1,431 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; - -import RenderTexture from '../../../textures/RenderTexture'; -import Quad from '../../utils/Quad'; -import QuadUv from '../../utils/QuadUv'; -import { Rectangle } from '@pixi/math'; -import * as filterTransforms from '../../filters/filterTransforms'; -import bitTwiddle from 'bit-twiddle'; -import UniformGroup from '../../../shader/UniformGroup'; -import { DRAW_MODES } from '../../../../../constants'; - -// -/** - * @ignore - * @class - */ -class FilterState -{ - /** - * - */ - constructor() - { - this.renderTexture = null; - this.sourceFrame = new Rectangle(); - this.destinationFrame = new Rectangle(); - this.filters = []; - this.target = null; - this.legacy = false; - this.resolution = 1; - } -} - -/** - * @class - * @memberof PIXI.systems - * @extends PIXI.systems.WebGLSystem - */ -export default class FilterSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - /** - * stores a bunch of PO2 textures used for filtering - * @type {Object} - */ - this.texturePool = {}; - - /** - * a pool for storing filter states, save us creating new ones each tick - * @type {Array} - */ - this.statePool = []; - - /** - * A very simple geometry used when drawing a filter effect to the screen - * @type {Quad} - */ - this.quad = new Quad(); - - this.quadUv = new QuadUv(); - - /** - * Temporary rect for maths - * @type {PIXI.Rectangle} - */ - this.tempRect = new Rectangle(); - - this.activeState = {}; - - /** - * this uniform group is attached to filter uniforms when used - * @type {UniformGroup} - */ - this.globalUniforms = new UniformGroup({ - sourceFrame: this.tempRect, - destinationFrame: this.tempRect, - - // legacy variables - filterArea: new Float32Array(4), - filterClamp: new Float32Array(4), - }, true); - } - - /** - * Adds a new filter to the System. - * - * @param {PIXI.DisplayObject} target - The target of the filter to render. - * @param {PIXI.Filter[]} filters - The filters to apply. - */ - push(target, filters) - { - const renderer = this.renderer; - const filterStack = this.renderer.renderTexture.defaultFilterStack; - const state = this.statePool.pop() || new FilterState(); - - let resolution = filters[0].resolution; - let padding = filters[0].padding; - let autoFit = filters[0].autoFit; - let legacy = filters[0].legacy; - - for (let i = 1; i < filters.length; i++) - { - const filter = filters[i]; - - // lets use the lowest resolution.. - resolution = Math.min(resolution, filter.resolution); - // and the largest amount of padding! - padding = Math.max(padding, filter.padding); - // only auto fit if all filters are autofit - autoFit = autoFit || filter.autoFit; - - legacy = legacy || filter.legacy; - } - - filterStack.push(state); - - state.resolution = resolution; - - state.legacy = legacy; - - // round to whole number based on resolution - // TODO move that to the shader too? - state.sourceFrame = target.filterArea || target.getBounds(true); - - state.sourceFrame.pad(padding); - - if (autoFit) - { - state.sourceFrame.fit(this.renderer.renderTexture.destinationFrame); - } - - state.sourceFrame.round(resolution); - - state.renderTexture = this.getPotFilterTexture(state.sourceFrame.width, state.sourceFrame.height, resolution); - state.filters = filters; - - state.destinationFrame.width = state.renderTexture.width; - state.destinationFrame.height = state.renderTexture.height; - - state.renderTexture.filterFrame = state.sourceFrame; - renderer.renderTexture.bind(state.renderTexture, state.sourceFrame);// /, state.destinationFrame); - renderer.renderTexture.clear(); - } - - /** - * Pops off the filter and applies it. - * - */ - pop() - { - const renderer = this.renderer; - const filterStack = renderer.renderTexture.defaultFilterStack; - const state = filterStack.pop(); - const filters = state.filters; - - this.activeState = state; - - const globalUniforms = this.globalUniforms.uniforms; - - globalUniforms.sourceFrame = state.sourceFrame; - globalUniforms.destinationFrame = state.destinationFrame; - globalUniforms.resolution = state.resolution; - - // only update the rect if its legacy.. - if (state.legacy) - { - const filterArea = globalUniforms.filterArea; - const filterClamp = globalUniforms.filterClamp; - - filterArea[0] = state.destinationFrame.width; - filterArea[1] = state.destinationFrame.height; - filterArea[2] = state.sourceFrame.x; - filterArea[3] = state.sourceFrame.y; - - filterClamp[0] = 0.5 / state.resolution / state.destinationFrame.width; - filterClamp[1] = 0.5 / state.resolution / state.destinationFrame.height; - filterClamp[2] = (state.sourceFrame.width - 0.5) / state.resolution / state.destinationFrame.width; - filterClamp[3] = (state.sourceFrame.height - 0.5) / state.resolution / state.destinationFrame.height; - } - - this.globalUniforms.update(); - - const lastState = filterStack[filterStack.length - 1]; - - if (filters.length === 1) - { - filters[0].apply(this, state.renderTexture, lastState.renderTexture, false, state); - - this.returnFilterTexture(state.renderTexture); - } - else - { - let flip = state.renderTexture; - let flop = this.getPotFilterTexture( - flip.width, - flip.height, - state.resolution - ); - - flop.filterFrame = flip.filterFrame; - - let i = 0; - - for (i = 0; i < filters.length - 1; ++i) - { - filters[i].apply(this, flip, flop, true, state); - - const t = flip; - - flip = flop; - flop = t; - } - - filters[i].apply(this, flip, lastState.renderTexture, false, state); - - this.returnFilterTexture(flip); - this.returnFilterTexture(flop); - } - - this.statePool.push(state); - } - - /** - * Draws a filter. - * - * @param {PIXI.Filter} filter - The filter to draw. - * @param {PIXI.RenderTarget} input - The input render target. - * @param {PIXI.RenderTarget} output - The target to output to. - * @param {boolean} clear - Should the output be cleared before rendering to it - */ - applyFilter(filter, input, output, clear) - { - const renderer = this.renderer; - - renderer.renderTexture.bind(output, output ? output.filterFrame : null); - - if (clear) - { - // gl.disable(gl.SCISSOR_TEST); - renderer.renderTexture.clear(); - // gl.enable(gl.SCISSOR_TEST); - } - - // set the uniforms.. - filter.uniforms.uSampler = input; - filter.uniforms.filterGlobals = this.globalUniforms; - - // TODO make it so that the order of this does not matter.. - // because it does at the moment cos of global uniforms. - // they need to get resynced - - renderer.state.setState(filter.state); - renderer.shader.bind(filter); - - if (filter.legacy) - { - this.quadUv.map(input._frame, input.filterFrame); - - renderer.geometry.bind(this.quadUv); - renderer.geometry.draw(DRAW_MODES.TRIANGLES); - } - else - { - renderer.geometry.bind(this.quad); - renderer.geometry.draw(DRAW_MODES.TRIANGLE_STRIP); - } - } - - /** - * Calculates the mapped matrix. - * - * TODO playing around here.. this is temporary - (will end up in the shader) - * this returns a matrix that will normalise map filter cords in the filter to screen space - * - * @param {PIXI.Matrix} outputMatrix - the matrix to output to. - * @return {PIXI.Matrix} The mapped matrix. - */ - calculateScreenSpaceMatrix(outputMatrix) - { - const currentState = this.activeState; - - return filterTransforms.calculateScreenSpaceMatrix( - outputMatrix, - currentState.sourceFrame, - currentState.destinationFrame - ); - } - - /** - * This will map the filter coord so that a texture can be used based on the transform of a sprite - * - * @param {PIXI.Matrix} outputMatrix - The matrix to output to. - * @param {PIXI.Sprite} sprite - The sprite to map to. - * @return {PIXI.Matrix} The mapped matrix. - */ - calculateSpriteMatrix(outputMatrix, sprite) - { - const currentState = this.activeState; - - return filterTransforms.calculateSpriteMatrix( - outputMatrix, - currentState.sourceFrame, - currentState.destinationFrame, - sprite - ); - } - - /** - * Destroys this Filter System. - * - * @param {boolean} [contextLost=false] context was lost, do not free shaders - * - */ - destroy(contextLost = false) - { - if (!contextLost) - { - this.emptyPool(); - } - else - { - this.texturePool = {}; - } - } - - /** - * Gets a Power-of-Two render texture. - * - * TODO move to a seperate class could be on renderer? - * also - could cause issue with multiple contexts? - * - * @private - * @param {WebGLRenderingContext} gl - The webgl rendering context - * @param {number} minWidth - The minimum width of the render target. - * @param {number} minHeight - The minimum height of the render target. - * @param {number} resolution - The resolution of the render target. - * @return {PIXI.RenderTarget} The new render target. - */ - getPotFilterTexture(minWidth, minHeight, resolution) - { - minWidth = bitTwiddle.nextPow2(minWidth); - minHeight = bitTwiddle.nextPow2(minHeight); - resolution = resolution || 1; - - const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); - - if (!this.texturePool[key]) - { - this.texturePool[key] = []; - } - - let renderTexture = this.texturePool[key].pop(); - - if (!renderTexture) - { - // temporary bypass cache.. - // internally - this will cause a texture to be bound.. - renderTexture = RenderTexture.create({ - width: minWidth, - height: minHeight, - resolution, - }); - } - - return renderTexture; - } - - /** - * Gets extra render texture to use inside current filter - * - * @param {number} resolution resolution of the renderTexture - * @returns {PIXI.RenderTexture} - */ - getFilterTexture(resolution) - { - const rt = this.activeState.renderTexture; - - const filterTexture = this.getPotFilterTexture(rt.width, rt.height, resolution || rt.baseTexture.resolution); - - filterTexture.filterFrame = rt.filterFrame; - - return filterTexture; - } - - /** - * Frees a render texture back into the pool. - * - * @param {PIXI.RenderTarget} renderTexture - The renderTarget to free - */ - returnFilterTexture(renderTexture) - { - renderTexture.filterFrame = null; - - const base = renderTexture.baseTexture; - - const minWidth = base.width; - const minHeight = base.height; - - const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); - - this.texturePool[key].push(renderTexture); - } - - /** - * Empties the texture pool. - * - */ - emptyPool() - { - for (const i in this.texturePool) - { - const textures = this.texturePool[i]; - - if (textures) - { - for (let j = 0; j < textures.length; j++) - { - textures[j].destroy(true); - } - } - } - - this.texturePool = {}; - } -} diff --git a/packages/core/src/renderers/systems/geometry/GLBuffer.js b/packages/core/src/renderers/systems/geometry/GLBuffer.js deleted file mode 100644 index 0477e8a..0000000 --- a/packages/core/src/renderers/systems/geometry/GLBuffer.js +++ /dev/null @@ -1,9 +0,0 @@ -export default class GLBuffer -{ - constructor(buffer) - { - this.buffer = buffer; - this.updateID = -1; - this.byteLength = -1; - } -} diff --git a/packages/core/src/renderers/systems/geometry/GeometrySystem.js b/packages/core/src/renderers/systems/geometry/GeometrySystem.js deleted file mode 100644 index 26c8e9e..0000000 --- a/packages/core/src/renderers/systems/geometry/GeometrySystem.js +++ /dev/null @@ -1,388 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; -import GLBuffer from './GLBuffer'; - -const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class GeometrySystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this._activeGeometry = null; - this._activeVao = null; - - this.hasVao = true; - this.hasInstance = true; - } - - /** - * Sets up the renderer context and necessary buffers. - * - * @private - */ - contextChange() - { - const gl = this.gl = this.renderer.gl; - - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - - // webgl2 - if (!gl.createVertexArray) - { - // webgl 1! - let nativeVaoExtension = this.renderer.context.extensions.vertexArrayObject; - - if (this.renderer.legacy) - { - nativeVaoExtension = null; - } - - if (nativeVaoExtension) - { - gl.createVertexArray = () => - nativeVaoExtension.createVertexArrayOES(); - - gl.bindVertexArray = (vao) => - nativeVaoExtension.bindVertexArrayOES(vao); - - gl.deleteVertexArray = (vao) => - nativeVaoExtension.deleteVertexArrayOES(vao); - } - else - { - this.hasVao = false; - gl.createVertexArray = () => - { - // empty - }; - - gl.bindVertexArray = () => - { - // empty - }; - - gl.deleteVertexArray = () => - { - // empty - }; - } - } - - if (!gl.vertexAttribDivisor) - { - const instanceExt = gl.getExtension('ANGLE_instanced_arrays'); - - if (instanceExt) - { - gl.vertexAttribDivisor = (a, b) => - instanceExt.vertexAttribDivisorANGLE(a, b); - - gl.drawElementsInstanced = (a, b, c, d, e) => - instanceExt.drawElementsInstancedANGLE(a, b, c, d, e); - - gl.drawArraysInstanced = (a, b, c, d) => - instanceExt.drawArraysInstancedANGLE(a, b, c, d); - } - else - { - this.hasInstance = false; - } - } - } - - /** - * Binds geometry so that is can be drawn. Creating a Vao if required - * @private - * @param {PIXI.Geometry} geometry instance of geometry to bind - */ - bind(geometry, shader) - { - shader = shader || this.renderer.shader.shader; - - const gl = this.gl; - - // not sure the best way to address this.. - // currently different shaders require different VAOs for the same geometry - // Still mulling over the best way to solve this one.. - // will likely need to modify the shader attribute locations at run time! - let vaos = geometry.glVertexArrayObjects[this.CONTEXT_UID]; - - if (!vaos) - { - geometry.glVertexArrayObjects[this.CONTEXT_UID] = vaos = {}; - } - - const vao = vaos[shader.program.id] || this.initGeometryVao(geometry, shader.program); - - this._activeGeometry = geometry; - - if (this._activeVao !== vao) - { - this._activeVao = vao; - - if (this.hasVao) - { - gl.bindVertexArray(vao); - } - else - { - this.activateVao(geometry, shader.program); - } - } - - // TODO - optimise later! - // don't need to loop through if nothing changed! - // maybe look to add an 'autoupdate' to geometry? - this.updateBuffers(); - } - - reset() - { - this.unbind(); - } - - updateBuffers() - { - const geometry = this._activeGeometry; - const gl = this.gl; - - for (let i = 0; i < geometry.buffers.length; i++) - { - const buffer = geometry.buffers[i]; - - const glBuffer = buffer._glBuffers[this.CONTEXT_UID]; - - if (buffer._updateID !== glBuffer.updateID) - { - glBuffer.updateID = buffer._updateID; - - // TODO can cache this on buffer! maybe added a getter / setter? - const type = buffer.index ? gl.ELEMENT_ARRAY_BUFFER : gl.ARRAY_BUFFER; - const drawType = buffer.static ? gl.STATIC_DRAW : gl.DYNAMIC_DRAW; - - gl.bindBuffer(type, glBuffer.buffer); - - if (glBuffer.byteLength >= buffer.data.byteLength) - { - // offset is always zero for now! - gl.bufferSubData(type, 0, buffer.data); - } - else - { - gl.bufferData(type, buffer.data, drawType); - } - } - } - } - - checkCompatability(geometry, program) - { - // geometry must have at least all the attributes that the shader requires. - const geometryAttributes = geometry.attributes; - const shaderAttributes = program.attributeData; - - for (const j in shaderAttributes) - { - if (!geometryAttributes[j]) - { - throw new Error(`shader and geometry incompatible, geometry missing the "${j}" attribute`); - } - } - } - - /** - * Creates a Vao with the same structure as the geometry and stores it on the geometry. - * @private - * @param {PIXI.Geometry} geometry instance of geometry to to generate Vao for - */ - initGeometryVao(geometry, program) - { - this.checkCompatability(geometry, program); - - const gl = this.gl; - const CONTEXT_UID = this.CONTEXT_UID; - const buffers = geometry.buffers; - const attributes = geometry.attributes; - - const tempStride = {}; - const tempStart = {}; - - for (const j in buffers) - { - tempStride[j] = 0; - tempStart[j] = 0; - } - - for (const j in attributes) - { - if (!attributes[j].size && program.attributeData[j]) - { - attributes[j].size = program.attributeData[j].size; - } - - tempStride[attributes[j].buffer] += attributes[j].size * byteSizeMap[attributes[j].type]; - } - - for (const j in attributes) - { - const attribute = attributes[j]; - const attribSize = attribute.size; - - if (attribute.stride === undefined) - { - if (tempStride[attribute.buffer] === attribSize * byteSizeMap[attribute.type]) - { - attribute.stride = 0; - } - else - { - attribute.stride = tempStride[attribute.buffer]; - } - } - - if (attribute.start === undefined) - { - attribute.start = tempStart[attribute.buffer]; - - tempStart[attribute.buffer] += attribSize * byteSizeMap[attribute.type]; - } - } - - // first update - and create the buffers! - // only create a gl buffer if it actually gets - for (let i = 0; i < buffers.length; i++) - { - const buffer = buffers[i]; - - if (!buffer._glBuffers[CONTEXT_UID]) - { - buffer._glBuffers[CONTEXT_UID] = new GLBuffer(gl.createBuffer()); - } - } - - // TODO - maybe make this a data object? - // lets wait to see if we need to first! - const vao = gl.createVertexArray(); - - gl.bindVertexArray(vao); - - this.activateVao(geometry, program); - - gl.bindVertexArray(null); - - // add it to the cache! - geometry.glVertexArrayObjects[this.CONTEXT_UID][program.id] = vao; - - return vao; - } - - activateVao(geometry, program) - { - const gl = this.gl; - const CONTEXT_UID = this.CONTEXT_UID; - const buffers = geometry.buffers; - const attributes = geometry.attributes; - - if (geometry.indexBuffer) - { - // first update the index buffer if we have one.. - gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, geometry.indexBuffer._glBuffers[CONTEXT_UID].buffer); - } - - let lastBuffer = null; - - // add a new one! - for (const j in attributes) - { - const attribute = attributes[j]; - const buffer = buffers[attribute.buffer]; - const glBuffer = buffer._glBuffers[CONTEXT_UID]; - - if (program.attributeData[j]) - { - if (lastBuffer !== glBuffer) - { - gl.bindBuffer(gl.ARRAY_BUFFER, glBuffer.buffer); - - lastBuffer = glBuffer; - } - - const location = program.attributeData[j].location; - - // TODO introduce state again - // we can optimise this for older devices that have no VAOs - gl.enableVertexAttribArray(location); - - gl.vertexAttribPointer(location, - attribute.size, - attribute.type || gl.FLOAT, - attribute.normalized, - attribute.stride, - attribute.start); - - if (attribute.instance) - { - // TODO calculate instance count based of this... - if (this.hasInstance) - { - gl.vertexAttribDivisor(location, 1); - } - else - { - throw new Error('geometry error, GPU Instancing is not supported on this device'); - } - } - } - } - } - - draw(type, size, start, instanceCount) - { - const gl = this.gl; - const geometry = this._activeGeometry; - - // TODO.. this should not change so maybe cache the function? - - if (geometry.indexBuffer) - { - if (geometry.instanced) - { - /* eslint-disable max-len */ - gl.drawElementsInstanced(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2, instanceCount || 1); - /* eslint-enable max-len */ - } - else - { - gl.drawElements(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2); - } - } - else - if (geometry.instanced) - { - // TODO need a better way to calculate size.. - gl.drawArraysInstanced(type, start, size || geometry.getSize(), instanceCount || 1); - } - else - { - gl.drawArrays(type, start, size || geometry.getSize()); - } - - return this; - } - - unbind() - { - this.gl.bindVertexArray(null); - this._activeVao = null; - this._activeGeometry = null; - } -} diff --git a/packages/core/src/renderers/systems/geometry/setVertexAttribArrays.js b/packages/core/src/renderers/systems/geometry/setVertexAttribArrays.js deleted file mode 100644 index 23b6959..0000000 --- a/packages/core/src/renderers/systems/geometry/setVertexAttribArrays.js +++ /dev/null @@ -1,54 +0,0 @@ -// var GL_MAP = {}; - -/** - * @param gl {WebGLRenderingContext} The current WebGL context - * @param attribs {*} - * @param state {*} - */ -export default function setVertexAttribArrays(gl, attribs, state) -{ - let i; - - if (state) - { - const tempAttribState = state.tempAttribState; - const attribState = state.attribState; - - for (i = 0; i < tempAttribState.length; i++) - { - tempAttribState[i] = false; - } - - // set the new attribs - for (i = 0; i < attribs.length; i++) - { - tempAttribState[attribs[i].attribute.location] = true; - } - - for (i = 0; i < attribState.length; i++) - { - if (attribState[i] !== tempAttribState[i]) - { - attribState[i] = tempAttribState[i]; - - if (state.attribState[i]) - { - gl.enableVertexAttribArray(i); - } - else - { - gl.disableVertexAttribArray(i); - } - } - } - } - else - { - for (i = 0; i < attribs.length; i++) - { - const attrib = attribs[i]; - - gl.enableVertexAttribArray(attrib.attribute.location); - } - } -} diff --git a/packages/core/src/renderers/systems/index.js b/packages/core/src/renderers/systems/index.js deleted file mode 100644 index c843f03..0000000 --- a/packages/core/src/renderers/systems/index.js +++ /dev/null @@ -1,6 +0,0 @@ -/** - * Systems are individual components to the Renderer pipeline. - * @namespace PIXI.systems - */ -export { default as WebGLSystem } from './WebGLSystem'; -export { default as FilterSystem } from './filter/FilterSystem'; diff --git a/packages/core/src/renderers/systems/shader/GLProgram.js b/packages/core/src/renderers/systems/shader/GLProgram.js deleted file mode 100644 index 92980ca..0000000 --- a/packages/core/src/renderers/systems/shader/GLProgram.js +++ /dev/null @@ -1,43 +0,0 @@ -/** - * Helper class to create a webGL Program - * - * @class - * @private - */ -export default class GLProgram -{ - constructor(program, uniformData) - { - /** - * The shader program - * - * @member {WebGLProgram} - */ - this.program = program; - - /** - * holds the uniform data which contains uniform locations - * and current uniform values used for caching and preventing undeed GPU commands - * @type {Object} - */ - this.uniformData = uniformData; - - /** - * uniformGroups holds the various upload functions for the shader. Each uniform group - * and program have a unique upload function generated. - * @type {Object} - */ - this.uniformGroups = {}; - } - - /** - * Destroys this program - * TODO - */ - destroy() - { - this.uniformData = null; - this.uniformGroups = null; - this.program = null; - } -} diff --git a/packages/core/src/renderers/systems/shader/ShaderSystem.js b/packages/core/src/renderers/systems/shader/ShaderSystem.js deleted file mode 100644 index 6cf57b6..0000000 --- a/packages/core/src/renderers/systems/shader/ShaderSystem.js +++ /dev/null @@ -1,170 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; -import GLProgram from './GLProgram'; -import generateUniformsSync from '../../../shader/generateUniformsSync'; -import defaultValue from '../../../shader/defaultValue'; -import compileProgram from './shader/compileProgram'; - -let UID = 0; - -/** - * Helper class to create a webGL Texture - * - * @class - * @memberof PIXI - */ -export default class ShaderSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - A reference to the current renderer - */ - constructor(renderer) - { - super(renderer); - - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = null; - - this.shader = null; - this.program = null; - - this.id = UID++; - } - - contextChange(gl) - { - this.gl = gl; - } - - /** - * Changes the current shader to the one given in parameter - * - * @param {PIXI.Shader} shader - the new shader - * @param {boolean} dontSync - false if the shader should automatically sync its uniforms. - * @returns {PIXI.glCore.glProgram} the glProgram that belongs to the shader. - */ - bind(shader, dontSync) - { - shader.uniforms.globals = this.renderer.globalUniforms; - - const program = shader.program; - const glProgram = program.glPrograms[this.renderer.CONTEXT_UID] || this.generateShader(shader); - - this.shader = shader; - - // TODO - some current pixi plugins bypass this.. so it not safe to use yet.. - if (this.program !== program) - { - this.program = program; - this.gl.useProgram(glProgram.program); - } - - if (!dontSync) - { - this.syncUniformGroup(shader.uniformGroup); - } - - return glProgram; - } - - /** - * Uploads the uniforms values to the currently bound shader. - * - * @param {object} uniforms - the uniforms valiues that be applied to the current shader - */ - setUniforms(uniforms) - { - const shader = this.shader.program; - const glProgram = shader.glPrograms[this.renderer.CONTEXT_UID]; - - shader.syncUniforms(glProgram.uniformData, uniforms, this.renderer); - } - - syncUniformGroup(group) - { - const glProgram = this.getglProgram(); - - if (!group.static || group.dirtyId !== glProgram.uniformGroups[group.id]) - { - glProgram.uniformGroups[group.id] = group.dirtyId; - const syncFunc = group.syncUniforms[this.shader.program.id] || this.createSyncGroups(group); - - syncFunc(glProgram.uniformData, group.uniforms, this.renderer); - } - } - - createSyncGroups(group) - { - group.syncUniforms[this.shader.program.id] = generateUniformsSync(group, this.shader.program.uniformData); - - return group.syncUniforms[this.shader.program.id]; - } - - /** - * Returns the underlying GLShade rof the currently bound shader. - * This can be handy for when you to have a little more control over the setting of your uniforms. - * - * @return {PIXI.glCore.Shader} the glProgram for the currently bound Shader for this context - */ - getglProgram() - { - if (this.shader) - { - return this.shader.program.glPrograms[this.renderer.CONTEXT_UID]; - } - - return null; - } - - /** - * Generates a glProgram verion of the Shader provided. - * - * @private - * @param {PIXI.Shader} shader the shader that the glProgram will be based on. - * @return {PIXI.glCore.glProgram} A shiney new glProgram - */ - generateShader(shader) - { - const gl = this.gl; - - const program = shader.program; - - const attribMap = {}; - - for (const i in program.attributeData) - { - attribMap[i] = program.attributeData[i].location; - } - - const shaderProgram = compileProgram(gl, program.vertexSrc, program.fragmentSrc, attribMap); - const uniformData = {}; - - for (const i in program.uniformData) - { - const data = program.uniformData[i]; - - uniformData[i] = { - location: gl.getUniformLocation(shaderProgram, i), - value: defaultValue(data.type, data.size), - }; - } - - const glProgram = new GLProgram(shaderProgram, uniformData); - - program.glPrograms[this.renderer.CONTEXT_UID] = glProgram; - - return glProgram; - } - - /** - * Destroys this System and removes all its textures - */ - destroy() - { - // TODO implement destroy method for ShaderSystem - this.destroyed = true; - } -} diff --git a/packages/core/src/renderers/systems/shader/shader/compileProgram.js b/packages/core/src/renderers/systems/shader/shader/compileProgram.js deleted file mode 100644 index 8c6ccb9..0000000 --- a/packages/core/src/renderers/systems/shader/shader/compileProgram.js +++ /dev/null @@ -1,78 +0,0 @@ -/** - * @method compileProgram - * @memberof PIXI.glCore.shader - * @param gl {WebGLRenderingContext} The current WebGL context {WebGLProgram} - * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. - * @param fragmentSrc {string|string[]} The fragment shader source as an array of strings. - * @param attributeLocations {Object} An attribute location map that lets you manually set the attribute locations - * @return {WebGLProgram} the shader program - */ -export default function compileProgram(gl, vertexSrc, fragmentSrc, attributeLocations) -{ - const glVertShader = compileShader(gl, gl.VERTEX_SHADER, vertexSrc); - const glFragShader = compileShader(gl, gl.FRAGMENT_SHADER, fragmentSrc); - - let program = gl.createProgram(); - - gl.attachShader(program, glVertShader); - gl.attachShader(program, glFragShader); - - // optionally, set the attributes manually for the program rather than letting WebGL decide.. - if (attributeLocations) - { - for (const i in attributeLocations) - { - gl.bindAttribLocation(program, attributeLocations[i], i); - } - } - - gl.linkProgram(program); - - // if linking fails, then log and cleanup - if (!gl.getProgramParameter(program, gl.LINK_STATUS)) - { - console.error('Pixi.js Error: Could not initialize shader.'); - console.error('gl.VALIDATE_STATUS', gl.getProgramParameter(program, gl.VALIDATE_STATUS)); - console.error('gl.getError()', gl.getError()); - - // if there is a program info log, log it - if (gl.getProgramInfoLog(program) !== '') - { - console.warn('Pixi.js Warning: gl.getProgramInfoLog()', gl.getProgramInfoLog(program)); - } - - gl.deleteProgram(program); - program = null; - } - - // clean up some shaders - gl.deleteShader(glVertShader); - gl.deleteShader(glFragShader); - - return program; -} - -/** - * @private - * @param gl {WebGLRenderingContext} The current WebGL context {WebGLProgram} - * @param type {Number} the type, can be either VERTEX_SHADER or FRAGMENT_SHADER - * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. - * @return {WebGLShader} the shader - */ -function compileShader(gl, type, src) -{ - const shader = gl.createShader(type); - - gl.shaderSource(shader, src); - gl.compileShader(shader); - - if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) - { - console.warn(src); - console.error(gl.getShaderInfoLog(shader)); - - return null; - } - - return shader; -} diff --git a/packages/core/src/renderers/systems/shader/shader/defaultValue.js b/packages/core/src/renderers/systems/shader/shader/defaultValue.js deleted file mode 100644 index 97290bd..0000000 --- a/packages/core/src/renderers/systems/shader/shader/defaultValue.js +++ /dev/null @@ -1,79 +0,0 @@ -/** - * @method defaultValue - * @memberof PIXI.glCore.shader - * @param type {String} Type of value - * @param size {Number} - */ -export default function defaultValue(type, size) -{ - switch (type) - { - case 'float': - return 0; - - case 'vec2': - return new Float32Array(2 * size); - - case 'vec3': - return new Float32Array(3 * size); - - case 'vec4': - return new Float32Array(4 * size); - - case 'int': - case 'sampler2D': - case 'sampler2DArray': - return 0; - - case 'ivec2': - return new Int32Array(2 * size); - - case 'ivec3': - return new Int32Array(3 * size); - - case 'ivec4': - return new Int32Array(4 * size); - - case 'bool': - return false; - - case 'bvec2': - - return booleanArray(2 * size); - - case 'bvec3': - return booleanArray(3 * size); - - case 'bvec4': - return booleanArray(4 * size); - - case 'mat2': - return new Float32Array([1, 0, - 0, 1]); - - case 'mat3': - return new Float32Array([1, 0, 0, - 0, 1, 0, - 0, 0, 1]); - - case 'mat4': - return new Float32Array([1, 0, 0, 0, - 0, 1, 0, 0, - 0, 0, 1, 0, - 0, 0, 0, 1]); - } - - return null; -} - -function booleanArray(size) -{ - const array = new Array(size); - - for (let i = 0; i < array.length; i++) - { - array[i] = false; - } - - return array; -} diff --git a/packages/core/src/renderers/systems/shader/shader/index.js b/packages/core/src/renderers/systems/shader/shader/index.js deleted file mode 100644 index ba7a317..0000000 --- a/packages/core/src/renderers/systems/shader/shader/index.js +++ /dev/null @@ -1,8 +0,0 @@ -/** - * @namespace PIXI.glCore.shader - */ -export { default as compileProgram } from './compileProgram'; -export { default as defaultValue } from './defaultValue'; -export { default as setPrecision } from './setPrecision'; -export { default as mapSize } from './mapSize'; -export { default as mapType } from './mapType'; diff --git a/packages/core/src/renderers/systems/shader/shader/mapSize.js b/packages/core/src/renderers/systems/shader/shader/mapSize.js deleted file mode 100644 index b681554..0000000 --- a/packages/core/src/renderers/systems/shader/shader/mapSize.js +++ /dev/null @@ -1,33 +0,0 @@ -const GLSL_TO_SIZE = { - float: 1, - vec2: 2, - vec3: 3, - vec4: 4, - - int: 1, - ivec2: 2, - ivec3: 3, - ivec4: 4, - - bool: 1, - bvec2: 2, - bvec3: 3, - bvec4: 4, - - mat2: 4, - mat3: 9, - mat4: 16, - - sampler2D: 1, -}; - -/** - * @method mapSize - * @memberof PIXI.glCore.shader - * @param type {String} - * @return {Number} - */ -export default function mapSize(type) -{ - return GLSL_TO_SIZE[type]; -} diff --git a/packages/core/src/renderers/systems/shader/shader/mapType.js b/packages/core/src/renderers/systems/shader/shader/mapType.js deleted file mode 100644 index d288b7c..0000000 --- a/packages/core/src/renderers/systems/shader/shader/mapType.js +++ /dev/null @@ -1,45 +0,0 @@ -let GL_TABLE = null; - -const GL_TO_GLSL_TYPES = { - FLOAT: 'float', - FLOAT_VEC2: 'vec2', - FLOAT_VEC3: 'vec3', - FLOAT_VEC4: 'vec4', - - INT: 'int', - INT_VEC2: 'ivec2', - INT_VEC3: 'ivec3', - INT_VEC4: 'ivec4', - - BOOL: 'bool', - BOOL_VEC2: 'bvec2', - BOOL_VEC3: 'bvec3', - BOOL_VEC4: 'bvec4', - - FLOAT_MAT2: 'mat2', - FLOAT_MAT3: 'mat3', - FLOAT_MAT4: 'mat4', - - SAMPLER_2D: 'sampler2D', - SAMPLER_CUBE: 'samplerCube', - SAMPLER_2D_ARRAY: 'sampler2DArray', -}; - -export default function mapType(gl, type) -{ - if (!GL_TABLE) - { - const typeNames = Object.keys(GL_TO_GLSL_TYPES); - - GL_TABLE = {}; - - for (let i = 0; i < typeNames.length; ++i) - { - const tn = typeNames[i]; - - GL_TABLE[gl[tn]] = GL_TO_GLSL_TYPES[tn]; - } - } - - return GL_TABLE[type]; -} diff --git a/packages/core/src/renderers/systems/shader/shader/setPrecision.js b/packages/core/src/renderers/systems/shader/shader/setPrecision.js deleted file mode 100644 index e2c1f34..0000000 --- a/packages/core/src/renderers/systems/shader/shader/setPrecision.js +++ /dev/null @@ -1,18 +0,0 @@ -/** - * Sets the float precision on the shader. If the precision is already present this function will do nothing - * @param {string} src the shader source - * @param {string} precision The float precision of the shader. Options are 'lowp', 'mediump' or 'highp'. - * - * @return {string} modified shader source - */ -export default function setPrecision(src, precision) -{ - src = src.trim(); - - if (src.substring(0, 9) !== 'precision' && src.substring(0, 1) !== '#') - { - return `precision ${precision} float;\n${src}`; - } - - return src; -} diff --git a/packages/core/src/renderers/systems/textures/GLTexture.js b/packages/core/src/renderers/systems/textures/GLTexture.js deleted file mode 100644 index c3fb8dc..0000000 --- a/packages/core/src/renderers/systems/textures/GLTexture.js +++ /dev/null @@ -1,33 +0,0 @@ -export default class GLTexture -{ - constructor(texture) - { - /** - * The WebGL texture - * - * @member {WebGLTexture} - */ - this.texture = texture; - - this.width = -1; - this.height = -1; - - /** - * Texture contents dirty flag - * @member {number} - */ - this.dirtyId = -1; - - /** - * Texture style dirty flag - * @type {number} - */ - this.dirtyStyleId = -1; - - /** - * Whether mip levels has to be generated - * @type {boolean} - */ - this.mipmap = false; - } -} diff --git a/packages/core/src/renderers/systems/textures/TextureGCSystem.js b/packages/core/src/renderers/systems/textures/TextureGCSystem.js deleted file mode 100644 index b089938..0000000 --- a/packages/core/src/renderers/systems/textures/TextureGCSystem.js +++ /dev/null @@ -1,110 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; -import { GC_MODES } from '@pixi/constants'; -import { settings } from '@pixi/settings'; - -/** - * TextureGarbageCollector. This class manages the GPU and ensures that it does not get clogged - * up with textures that are no longer being used. - * - * @class - * @memberof PIXI - */ -export default class TextureGCSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this.count = 0; - this.checkCount = 0; - this.maxIdle = settings.GC_MAX_IDLE; - this.checkCountMax = settings.GC_MAX_CHECK_COUNT; - this.mode = settings.GC_MODE; - } - - /** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ - postrender() - { - this.count++; - - if (this.mode === GC_MODES.MANUAL) - { - return; - } - - this.checkCount++; - - if (this.checkCount > this.checkCountMax) - { - this.checkCount = 0; - - this.run(); - } - } - - /** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ - run() - { - const tm = this.renderer.texture; - const managedTextures = tm.managedTextures; - let wasRemoved = false; - - for (let i = 0; i < managedTextures.length; i++) - { - const texture = managedTextures[i]; - - // only supports non generated textures at the moment! - if (!texture.frameBuffer && this.count - texture.touched > this.maxIdle) - { - tm.destroyTexture(texture, true); - managedTextures[i] = null; - wasRemoved = true; - } - } - - if (wasRemoved) - { - let j = 0; - - for (let i = 0; i < managedTextures.length; i++) - { - if (managedTextures[i] !== null) - { - managedTextures[j++] = managedTextures[i]; - } - } - - managedTextures.length = j; - } - } - - /** - * Removes all the textures within the specified displayObject and its children from the GPU - * - * @param {PIXI.DisplayObject} displayObject - the displayObject to remove the textures from. - */ - unload(displayObject) - { - const tm = this.renderer.textureSystem; - - // only destroy non generated textures - if (displayObject._texture && displayObject._texture._glRenderTargets) - { - tm.destroyTexture(displayObject._texture); - } - - for (let i = displayObject.children.length - 1; i >= 0; i--) - { - this.unload(displayObject.children[i]); - } - } -} diff --git a/packages/core/src/renderers/systems/textures/TextureSystem.js b/packages/core/src/renderers/systems/textures/TextureSystem.js deleted file mode 100644 index ccc5de9..0000000 --- a/packages/core/src/renderers/systems/textures/TextureSystem.js +++ /dev/null @@ -1,281 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; -import GLTexture from './GLTexture'; -import { removeItems } from '@pixi/utils'; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class TextureSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - // TODO set to max textures... - this.boundTextures = [ - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - ]; - - this.currentLocation = -1; - - this.managedTextures = []; - } - - /** - * Sets up the renderer context and necessary buffers. - * - * @private - */ - contextChange() - { - const gl = this.gl = this.renderer.gl; - - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - - // TODO move this.. to a nice make empty textures class.. - this.emptyTextures = {}; - - const emptyTexture2D = new GLTexture(gl.createTexture()); - - gl.bindTexture(gl.TEXTURE_2D, emptyTexture2D.texture); - gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array(4)); - - this.emptyTextures[gl.TEXTURE_2D] = emptyTexture2D; - this.emptyTextures[gl.TEXTURE_CUBE_MAP] = new GLTexture(gl.createTexture()); - - gl.bindTexture(gl.TEXTURE_CUBE_MAP, this.emptyTextures[gl.TEXTURE_CUBE_MAP].texture); - - let i; - - for (i = 0; i < 6; i++) - { - gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, null); - } - - gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_MAG_FILTER, gl.LINEAR); - gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_MIN_FILTER, gl.LINEAR); - - for (i = 0; i < this.boundTextures.length; i++) - { - this.bind(null, i); - } - } - - bind(texture, location) - { - const gl = this.gl; - - location = location || 0; - - if (this.currentLocation !== location) - { - this.currentLocation = location; - gl.activeTexture(gl.TEXTURE0 + location); - } - - if (texture) - { - texture = texture.baseTexture || texture; - - if (texture.valid) - { - texture.touched = this.renderer.textureGC.count; - - const glTexture = texture._glTextures[this.CONTEXT_UID] || this.initTexture(texture); - - gl.bindTexture(texture.target, glTexture.texture); - - if (glTexture.dirtyId !== texture.dirtyId) - { - this.updateTexture(texture); - } - - this.boundTextures[location] = texture; - } - } - else - { - gl.bindTexture(gl.TEXTURE_2D, this.emptyTextures[gl.TEXTURE_2D].texture); - this.boundTextures[location] = null; - } - } - - unbind(texture) - { - const gl = this.gl; - - for (let i = 0; i < this.boundTextures.length; i++) - { - if (this.boundTextures[i] === texture) - { - if (this.currentLocation !== i) - { - gl.activeTexture(gl.TEXTURE0 + i); - this.currentLocation = i; - } - - gl.bindTexture(gl.TEXTURE_2D, this.emptyTextures[texture.target].texture); - this.boundTextures[i] = null; - } - } - } - - initTexture(texture) - { - const glTexture = new GLTexture(this.gl.createTexture()); - - // guarentee an update.. - glTexture.dirtyId = -1; - - texture._glTextures[this.CONTEXT_UID] = glTexture; - - this.managedTextures.push(texture); - texture.on('dispose', this.destroyTexture, this); - - return glTexture; - } - - updateTexture(texture) - { - const glTexture = texture._glTextures[this.CONTEXT_UID]; - const renderer = this.renderer; - - if (texture.resource && texture.resource.upload(renderer, texture, glTexture)) - { - // texture is uploaded, dont do anything! - } - else - { - // default, renderTexture-like logic - const width = texture.realWidth; - const height = texture.realHeight; - const gl = renderer.gl; - - if (glTexture.width !== width - || glTexture.height !== height - || glTexture.dirtyId < 0) - { - glTexture.width = width; - glTexture.height = height; - - gl.texImage2D(texture.target, 0, - texture.format, - width, - height, - 0, - texture.format, - texture.type, - null); - } - } - - // lets only update what changes.. - if (texture.dirtyStyleId !== glTexture.dirtyStyleId) - { - this.updateTextureStyle(texture); - } - glTexture.dirtyId = texture.dirtyId; - } - - /** - * Deletes the texture from WebGL - * - * @param {PIXI.BaseTexture|PIXI.Texture} texture - the texture to destroy - * @param {boolean} [skipRemove=false] - Whether to skip removing the texture from the TextureManager. - */ - destroyTexture(texture, skipRemove) - { - const gl = this.gl; - - texture = texture.baseTexture || texture; - - if (texture._glTextures[this.renderer.CONTEXT_UID]) - { - this.unbind(texture); - - gl.deleteTexture(texture._glTextures[this.renderer.CONTEXT_UID].texture); - texture.off('dispose', this.destroyTexture, this); - - delete texture._glTextures[this.renderer.CONTEXT_UID]; - - if (!skipRemove) - { - const i = this.managedTextures.indexOf(texture); - - if (i !== -1) - { - removeItems(this.managedTextures, i, 1); - } - } - } - } - - updateTextureStyle(texture) - { - const glTexture = texture._glTextures[this.CONTEXT_UID]; - - glTexture.mipmap = texture.mipmap && texture.isPowerOfTwo; - if (!glTexture) - { - return; - } - - if (texture.resource && texture.resource.style(this.renderer, texture, glTexture)) - { - // style is set, dont do anything! - } - else - { - this.setStyle(texture, glTexture); - } - - glTexture.dirtyStyleId = texture.dirtyStyleId; - } - - setStyle(texture, glTexture) - { - const gl = this.gl; - - if (glTexture.mipmap) - { - gl.generateMipmap(texture.target); - } - - gl.texParameteri(texture.target, gl.TEXTURE_WRAP_S, texture.wrapMode); - gl.texParameteri(texture.target, gl.TEXTURE_WRAP_T, texture.wrapMode); - - if (glTexture.mipmap) - { - /* eslint-disable max-len */ - gl.texParameteri(texture.target, gl.TEXTURE_MIN_FILTER, texture.scaleMode ? gl.LINEAR_MIPMAP_LINEAR : gl.NEAREST_MIPMAP_NEAREST); - /* eslint-disable max-len */ - } - else - { - gl.texParameteri(texture.target, gl.TEXTURE_MIN_FILTER, texture.scaleMode ? gl.LINEAR : gl.NEAREST); - } - - gl.texParameteri(texture.target, gl.TEXTURE_MAG_FILTER, texture.scaleMode ? gl.LINEAR : gl.NEAREST); - } -} diff --git a/packages/core/src/renderers/utils/ObjectRenderer.js b/packages/core/src/renderers/utils/ObjectRenderer.js deleted file mode 100644 index f67a273..0000000 --- a/packages/core/src/renderers/utils/ObjectRenderer.js +++ /dev/null @@ -1,48 +0,0 @@ -import WebGLSystem from '../systems/WebGLSystem'; - -/** - * Base for a common object renderer that can be used as a system renderer plugin. - * - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class ObjectRenderer extends WebGLSystem -{ - /** - * Starts the renderer and sets the shader - * - */ - start() - { - // set the shader.. - } - - /** - * Stops the renderer - * - */ - stop() - { - this.flush(); - } - - /** - * Stub method for rendering content and emptying the current batch. - * - */ - flush() - { - // flush! - } - - /** - * Renders an object - * - * @param {PIXI.DisplayObject} object - The object to render. - */ - render(object) // eslint-disable-line no-unused-vars - { - // render the object - } -} diff --git a/packages/core/src/renderers/utils/Quad.js b/packages/core/src/renderers/utils/Quad.js deleted file mode 100644 index 5c1517b..0000000 --- a/packages/core/src/renderers/utils/Quad.js +++ /dev/null @@ -1,26 +0,0 @@ -import Geometry from '../../geometry/Geometry'; - -/** - * Helper class to create a quad - * - * @class - * @memberof PIXI - */ -export default class Quad extends Geometry -{ - /** - * just a quad - */ - constructor() - { - super(); - - this.addAttribute('aVertexPosition', [ - 0, 0, - 1, 0, - 1, 1, - 0, 1, - ]) - .addIndex([0, 1, 3, 2]); - } -} diff --git a/packages/core/src/renderers/utils/QuadUv.js b/packages/core/src/renderers/utils/QuadUv.js deleted file mode 100644 index 97bda89..0000000 --- a/packages/core/src/renderers/utils/QuadUv.js +++ /dev/null @@ -1,103 +0,0 @@ -import Geometry from '../../geometry/Geometry'; -import Buffer from '../../geometry/Buffer'; - -/** - * Helper class to create a quad with uvs like in v4 - * - * @class - * @memberof PIXI - */ -export default class QuadUv extends Geometry -{ - constructor() - { - super(); - - /** - * An array of vertices - * - * @member {Float32Array} - */ - this.vertices = new Float32Array([ - -1, -1, - 1, -1, - 1, 1, - -1, 1, - ]); - - /** - * The Uvs of the quad - * - * @member {Float32Array} - */ - this.uvs = new Float32Array([ - 0, 0, - 1, 0, - 1, 1, - 0, 1, - ]); - - this.vertexBuffer = new Buffer(this.vertices); - this.uvBuffer = new Buffer(this.uvs); - - this.addAttribute('aVertexPosition', this.vertexBuffer) - .addAttribute('aTextureCoord', this.uvBuffer) - .addIndex([0, 1, 2, 0, 2, 3]); - } - - /** - * Maps two Rectangle to the quad. - * - * @param {PIXI.Rectangle} targetTextureFrame - the first rectangle - * @param {PIXI.Rectangle} destinationFrame - the second rectangle - * @return {PIXI.Quad} Returns itself. - */ - map(targetTextureFrame, destinationFrame) - { - let x = 0; // destinationFrame.x / targetTextureFrame.width; - let y = 0; // destinationFrame.y / targetTextureFrame.height; - - this.uvs[0] = x; - this.uvs[1] = y; - - this.uvs[2] = x + (destinationFrame.width / targetTextureFrame.width); - this.uvs[3] = y; - - this.uvs[4] = x + (destinationFrame.width / targetTextureFrame.width); - this.uvs[5] = y + (destinationFrame.height / targetTextureFrame.height); - - this.uvs[6] = x; - this.uvs[7] = y + (destinationFrame.height / targetTextureFrame.height); - - x = destinationFrame.x; - y = destinationFrame.y; - - this.vertices[0] = x; - this.vertices[1] = y; - - this.vertices[2] = x + destinationFrame.width; - this.vertices[3] = y; - - this.vertices[4] = x + destinationFrame.width; - this.vertices[5] = y + destinationFrame.height; - - this.vertices[6] = x; - this.vertices[7] = y + destinationFrame.height; - - this.invalidate(); - - return this; - } - - /** - * legacy upload method, just marks buffers dirty - * @returns {PIXI.QuadUv} Returns itself. - */ - invalidate() - { - this.vertexBuffer._updateID++; - this.uvBuffer._updateID++; - - return this; - } -} diff --git a/packages/core/src/renderers/utils/checkMaxIfStatmentsInShader.js b/packages/core/src/renderers/utils/checkMaxIfStatmentsInShader.js deleted file mode 100644 index 8047c96..0000000 --- a/packages/core/src/renderers/utils/checkMaxIfStatmentsInShader.js +++ /dev/null @@ -1,58 +0,0 @@ -const fragTemplate = [ - 'precision mediump float;', - 'void main(void){', - 'float test = 0.1;', - '%forloop%', - 'gl_FragColor = vec4(0.0);', - '}', -].join('\n'); - -export default function checkMaxIfStatmentsInShader(maxIfs, gl) -{ - if (maxIfs === 0) - { - throw new Error('Invalid value of `0` passed to `checkMaxIfStatementsInShader`'); - } - - const shader = gl.createShader(gl.FRAGMENT_SHADER); - - while (true) // eslint-disable-line no-constant-condition - { - const fragmentSrc = fragTemplate.replace(/%forloop%/gi, generateIfTestSrc(maxIfs)); - - gl.shaderSource(shader, fragmentSrc); - gl.compileShader(shader); - - if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) - { - maxIfs = (maxIfs / 2) | 0; - } - else - { - // valid! - break; - } - } - - return maxIfs; -} - -function generateIfTestSrc(maxIfs) -{ - let src = ''; - - for (let i = 0; i < maxIfs; ++i) - { - if (i > 0) - { - src += '\nelse '; - } - - if (i < maxIfs - 1) - { - src += `if(test == ${i}.0){}`; - } - } - - return src; -} diff --git a/packages/core/src/renderers/utils/mapWebGLBlendModesToPixi.js b/packages/core/src/renderers/utils/mapWebGLBlendModesToPixi.js deleted file mode 100644 index 10ffe4d..0000000 --- a/packages/core/src/renderers/utils/mapWebGLBlendModesToPixi.js +++ /dev/null @@ -1,47 +0,0 @@ -import { BLEND_MODES } from '@pixi/constants'; - -/** - * Maps gl blend combinations to WebGL. - * - * @memberof PIXI - * @function mapWebGLBlendModesToPixi - * @private - * @param {WebGLRenderingContext} gl - The rendering context. - * @param {string[]} [array=[]] - The array to output into. - * @return {string[]} Mapped modes. - */ -export default function mapWebGLBlendModesToPixi(gl, array = []) -{ - // TODO - premultiply alpha would be different. - // add a boolean for that! - array[BLEND_MODES.NORMAL] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.ADD] = [gl.ONE, gl.DST_ALPHA]; - array[BLEND_MODES.MULTIPLY] = [gl.DST_COLOR, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.SCREEN] = [gl.ONE, gl.ONE_MINUS_SRC_COLOR]; - array[BLEND_MODES.OVERLAY] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.DARKEN] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.LIGHTEN] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.COLOR_DODGE] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.COLOR_BURN] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.HARD_LIGHT] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.SOFT_LIGHT] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.DIFFERENCE] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.EXCLUSION] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.HUE] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.SATURATION] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.COLOR] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.LUMINOSITY] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.NONE] = [0, 0]; - - // not-premultiplied blend modes - array[BLEND_MODES.NORMAL_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.ADD_NPM] = [gl.SRC_ALPHA, gl.DST_ALPHA, gl.ONE, gl.DST_ALPHA]; - array[BLEND_MODES.SCREEN_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_COLOR, gl.ONE, gl.ONE_MINUS_SRC_COLOR]; - - // not-premultiplied blend modes - array[BLEND_MODES.NORMAL_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.ADD_NPM] = [gl.SRC_ALPHA, gl.DST_ALPHA, gl.ONE, gl.DST_ALPHA]; - array[BLEND_MODES.SCREEN_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_COLOR, gl.ONE, gl.ONE_MINUS_SRC_COLOR]; - - return array; -} diff --git a/packages/core/src/shader/GLProgram.js b/packages/core/src/shader/GLProgram.js new file mode 100644 index 0000000..92980ca --- /dev/null +++ b/packages/core/src/shader/GLProgram.js @@ -0,0 +1,43 @@ +/** + * Helper class to create a webGL Program + * + * @class + * @private + */ +export default class GLProgram +{ + constructor(program, uniformData) + { + /** + * The shader program + * + * @member {WebGLProgram} + */ + this.program = program; + + /** + * holds the uniform data which contains uniform locations + * and current uniform values used for caching and preventing undeed GPU commands + * @type {Object} + */ + this.uniformData = uniformData; + + /** + * uniformGroups holds the various upload functions for the shader. Each uniform group + * and program have a unique upload function generated. + * @type {Object} + */ + this.uniformGroups = {}; + } + + /** + * Destroys this program + * TODO + */ + destroy() + { + this.uniformData = null; + this.uniformGroups = null; + this.program = null; + } +} diff --git a/packages/core/src/shader/Program.js b/packages/core/src/shader/Program.js index 4f9971e..4b4007a 100644 --- a/packages/core/src/shader/Program.js +++ b/packages/core/src/shader/Program.js @@ -1,6 +1,11 @@ -import * as shaderUtils from '../renderers/systems/shader/shader'; +// import * as from '../systems/shader/shader'; +import { setPrecision, + defaultValue, + compileProgram, + mapSize, + mapType, + getTestContext } from './utils'; import { ProgramCache } from '@pixi/utils'; -import getTestContext from './getTestContext'; import defaultFragment from './defaultProgram.frag'; import defaultVertex from './defaultProgram.vert'; import { settings } from '@pixi/settings'; @@ -34,8 +39,8 @@ */ this.fragmentSrc = fragmentSrc || Program.defaultFragmentSrc; - this.vertexSrc = shaderUtils.setPrecision(this.vertexSrc, settings.PRECISION_VERTEX); - this.fragmentSrc = shaderUtils.setPrecision(this.fragmentSrc, settings.PRECISION_FRAGMENT); + this.vertexSrc = setPrecision(this.vertexSrc, settings.PRECISION_VERTEX); + this.fragmentSrc = setPrecision(this.fragmentSrc, settings.PRECISION_FRAGMENT); // currently this does not extract structs only default types this.extractData(this.vertexSrc, this.fragmentSrc); @@ -62,7 +67,7 @@ if (gl) { - const program = shaderUtils.compileProgram(gl, vertexSrc, fragmentSrc); + const program = compileProgram(gl, vertexSrc, fragmentSrc); this.attributeData = this.getAttributeData(program, gl); this.uniformData = this.getUniformData(program, gl); @@ -95,13 +100,13 @@ for (let i = 0; i < totalAttributes; i++) { const attribData = gl.getActiveAttrib(program, i); - const type = shaderUtils.mapType(gl, attribData.type); + const type = mapType(gl, attribData.type); /*eslint-disable */ const data = { type: type, name: attribData.name, - size: shaderUtils.mapSize(type), + size: mapSize(type), location: 0, }; /* eslint-enable */ @@ -145,14 +150,14 @@ const name = uniformData.name.replace(/\[.*?\]/, ''); const isArray = uniformData.name.match(/\[.*?\]/, ''); - const type = shaderUtils.mapType(gl, uniformData.type); + const type = mapType(gl, uniformData.type); /*eslint-disable */ uniforms[name] = { type: type, size: uniformData.size, isArray:isArray, - value: shaderUtils.defaultValue(type, uniformData.size), + value: defaultValue(type, uniformData.size), }; /* eslint-enable */ } diff --git a/packages/core/src/shader/ShaderSystem.js b/packages/core/src/shader/ShaderSystem.js new file mode 100644 index 0000000..40b8af8 --- /dev/null +++ b/packages/core/src/shader/ShaderSystem.js @@ -0,0 +1,171 @@ +import System from '../System'; +import GLProgram from './GLProgram'; +import { generateUniformsSync, + defaultValue, + compileProgram } from './utils'; + +let UID = 0; + +/** + * Helper class to create a webGL Texture + * + * @class + * @memberof PIXI.systems + * @extends PIXI.System + */ +export default class ShaderSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - A reference to the current renderer + */ + constructor(renderer) + { + super(renderer); + + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = null; + + this.shader = null; + this.program = null; + + this.id = UID++; + } + + contextChange(gl) + { + this.gl = gl; + } + + /** + * Changes the current shader to the one given in parameter + * + * @param {PIXI.Shader} shader - the new shader + * @param {boolean} dontSync - false if the shader should automatically sync its uniforms. + * @returns {PIXI.glCore.glProgram} the glProgram that belongs to the shader. + */ + bind(shader, dontSync) + { + shader.uniforms.globals = this.renderer.globalUniforms; + + const program = shader.program; + const glProgram = program.glPrograms[this.renderer.CONTEXT_UID] || this.generateShader(shader); + + this.shader = shader; + + // TODO - some current pixi plugins bypass this.. so it not safe to use yet.. + if (this.program !== program) + { + this.program = program; + this.gl.useProgram(glProgram.program); + } + + if (!dontSync) + { + this.syncUniformGroup(shader.uniformGroup); + } + + return glProgram; + } + + /** + * Uploads the uniforms values to the currently bound shader. + * + * @param {object} uniforms - the uniforms valiues that be applied to the current shader + */ + setUniforms(uniforms) + { + const shader = this.shader.program; + const glProgram = shader.glPrograms[this.renderer.CONTEXT_UID]; + + shader.syncUniforms(glProgram.uniformData, uniforms, this.renderer); + } + + syncUniformGroup(group) + { + const glProgram = this.getglProgram(); + + if (!group.static || group.dirtyId !== glProgram.uniformGroups[group.id]) + { + glProgram.uniformGroups[group.id] = group.dirtyId; + const syncFunc = group.syncUniforms[this.shader.program.id] || this.createSyncGroups(group); + + syncFunc(glProgram.uniformData, group.uniforms, this.renderer); + } + } + + createSyncGroups(group) + { + group.syncUniforms[this.shader.program.id] = generateUniformsSync(group, this.shader.program.uniformData); + + return group.syncUniforms[this.shader.program.id]; + } + + /** + * Returns the underlying GLShade rof the currently bound shader. + * This can be handy for when you to have a little more control over the setting of your uniforms. + * + * @return {PIXI.glCore.Shader} the glProgram for the currently bound Shader for this context + */ + getglProgram() + { + if (this.shader) + { + return this.shader.program.glPrograms[this.renderer.CONTEXT_UID]; + } + + return null; + } + + /** + * Generates a glProgram verion of the Shader provided. + * + * @private + * @param {PIXI.Shader} shader the shader that the glProgram will be based on. + * @return {PIXI.glCore.glProgram} A shiney new glProgram + */ + generateShader(shader) + { + const gl = this.gl; + + const program = shader.program; + + const attribMap = {}; + + for (const i in program.attributeData) + { + attribMap[i] = program.attributeData[i].location; + } + + const shaderProgram = compileProgram(gl, program.vertexSrc, program.fragmentSrc, attribMap); + const uniformData = {}; + + for (const i in program.uniformData) + { + const data = program.uniformData[i]; + + uniformData[i] = { + location: gl.getUniformLocation(shaderProgram, i), + value: defaultValue(data.type, data.size), + }; + } + + const glProgram = new GLProgram(shaderProgram, uniformData); + + program.glPrograms[this.renderer.CONTEXT_UID] = glProgram; + + return glProgram; + } + + /** + * Destroys this System and removes all its textures + */ + destroy() + { + // TODO implement destroy method for ShaderSystem + this.destroyed = true; + } +} diff --git a/packages/core/src/shader/defaultValue.js b/packages/core/src/shader/defaultValue.js deleted file mode 100644 index 847a069..0000000 --- a/packages/core/src/shader/defaultValue.js +++ /dev/null @@ -1,78 +0,0 @@ -function booleanArray(size) -{ - const array = new Array(size); - - for (let i = 0; i < array.length; i++) - { - array[i] = false; - } - - return array; -} - -/** - * @class - * @memberof PIXI.glCore.shader - * @param type {String} Type of value - * @param size {Number} - */ -export default function defaultValue(type, size) -{ - switch (type) - { - case 'float': - return 0; - - case 'vec2': - return new Float32Array(2 * size); - - case 'vec3': - return new Float32Array(3 * size); - - case 'vec4': - return new Float32Array(4 * size); - - case 'int': - case 'sampler2D': - return 0; - - case 'ivec2': - return new Int32Array(2 * size); - - case 'ivec3': - return new Int32Array(3 * size); - - case 'ivec4': - return new Int32Array(4 * size); - - case 'bool': - return false; - - case 'bvec2': - - return booleanArray(2 * size); - - case 'bvec3': - return booleanArray(3 * size); - - case 'bvec4': - return booleanArray(4 * size); - - case 'mat2': - return new Float32Array([1, 0, - 0, 1]); - - case 'mat3': - return new Float32Array([1, 0, 0, - 0, 1, 0, - 0, 0, 1]); - - case 'mat4': - return new Float32Array([1, 0, 0, 0, - 0, 1, 0, 0, - 0, 0, 1, 0, - 0, 0, 0, 1]); - } - - return 0; -} diff --git a/packages/core/src/shader/extractUniformsFromSrc.js b/packages/core/src/shader/extractUniformsFromSrc.js deleted file mode 100644 index 3d02c0a..0000000 --- a/packages/core/src/shader/extractUniformsFromSrc.js +++ /dev/null @@ -1,57 +0,0 @@ -import defaultValue from './defaultValue'; - -export default function extractUniformsFromSrc(vertexSrc, fragmentSrc, mask) -{ - const vertUniforms = extractUniformsFromString(vertexSrc, mask); - const fragUniforms = extractUniformsFromString(fragmentSrc, mask); - - return Object.assign(vertUniforms, fragUniforms); -} - -function extractUniformsFromString(string) -{ - const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); - - const uniforms = {}; - let nameSplit; - - // clean the lines a little - remove extra spaces / teabs etc - // then split along ';' - const lines = string.replace(/\s+/g, ' ') - .split(/\s*;\s*/); - - // loop through.. - for (let i = 0; i < lines.length; i++) - { - const line = lines[i].trim(); - - if (line.indexOf('uniform') > -1) - { - const splitLine = line.split(' '); - const type = splitLine[1]; - - let name = splitLine[2]; - let size = 1; - - if (name.indexOf('[') > -1) - { - // array! - nameSplit = name.split(/\[|]/); - name = nameSplit[0]; - size *= Number(nameSplit[1]); - } - - if (!name.match(maskRegex)) - { - uniforms[name] = { - value: defaultValue(type, size), - dirtyId: 0, - name, - type, - }; - } - } - } - - return uniforms; -} diff --git a/packages/core/src/shader/generateUniformsSync.js b/packages/core/src/shader/generateUniformsSync.js deleted file mode 100644 index 98a357f..0000000 --- a/packages/core/src/shader/generateUniformsSync.js +++ /dev/null @@ -1,231 +0,0 @@ -// cv = CachedValue -// v = value -// ud = uniformData -// uv = uniformValue -// l = loaction -const GLSL_TO_SINGLE_SETTERS_CACHED = { - - float: ` - if(cv !== v) - { - cv.v = v; - gl.uniform1f(location, v) - }`, - - vec2: ` - if(cv[0] !== v[0] || cv[1] !== v[1]) - { - cv[0] = v[0]; - cv[1] = v[1]; - gl.uniform2f(location, v[0], v[1]) - }`, - - vec3: ` - if(cv[0] !== v[0] || cv[1] !== v[1] || cv[2] !== v[2]) - { - cv[0] = v[0]; - cv[1] = v[1]; - cv[2] = v[2]; - - gl.uniform3f(location, v[0], v[1], v[2]) - }`, - - vec4: 'gl.uniform4f(location, v[0], v[1], v[2], v[3])', - - int: 'gl.uniform1i(location, v)', - ivec2: 'gl.uniform2i(location, v[0], v[1])', - ivec3: 'gl.uniform3i(location, v[0], v[1], v[2])', - ivec4: 'gl.uniform4i(location, v[0], v[1], v[2], v[3])', - - bool: 'gl.uniform1i(location, v)', - bvec2: 'gl.uniform2i(location, v[0], v[1])', - bvec3: 'gl.uniform3i(location, v[0], v[1], v[2])', - bvec4: 'gl.uniform4i(location, v[0], v[1], v[2], v[3])', - - mat2: 'gl.uniformMatrix2fv(location, false, v)', - mat3: 'gl.uniformMatrix3fv(location, false, v)', - mat4: 'gl.uniformMatrix4fv(location, false, v)', - - sampler2D: 'gl.uniform1i(location, v)', - samplerCube: 'gl.uniform1i(location, v)', - sampler2DArray: 'gl.uniform1i(location, v)', -}; - -const GLSL_TO_ARRAY_SETTERS = { - - float: `gl.uniform1fv(location, v)`, - - vec2: `gl.uniform2fv(location, v)`, - vec3: `gl.uniform3fv(location, v)`, - vec4: 'gl.uniform4fv(location, v)', - - mat4: 'gl.uniformMatrix4fv(location, false, v)', - mat3: 'gl.uniformMatrix3fv(location, false, v)', - mat2: 'gl.uniformMatrix2fv(location, false, v)', - - int: 'gl.uniform1iv(location, v)', - ivec2: 'gl.uniform2iv(location, v)', - ivec3: 'gl.uniform3iv(location, v)', - ivec4: 'gl.uniform4iv(location, v)', - - bool: 'gl.uniform1iv(location, v)', - bvec2: 'gl.uniform2iv(location, v)', - bvec3: 'gl.uniform3iv(location, v)', - bvec4: 'gl.uniform4iv(location, v)', - - sampler2D: 'gl.uniform1iv(location, v)', - samplerCube: 'gl.uniform1iv(location, v)', - sampler2DArray: 'gl.uniform1iv(location, v)', -}; - -export default function generateUniformsSync(group, uniformData) -{ - let textureCount = 0; - let func = `var v = null; - var cv = null - var gl = renderer.gl`; - - for (const i in group.uniforms) - { - const data = uniformData[i]; - - if (!data) - { - if (group.uniforms[i].group) - { - func += ` - renderer.shader.syncUniformGroup(uv.${i}); - `; - } - - continue; - } - - // TODO && uniformData[i].value !== 0 <-- do we still need this? - if (data.type === 'float' && data.size === 1) - { - func += ` - if(uv.${i} !== ud.${i}.value) - { - ud.${i}.value = uv.${i} - gl.uniform1f(ud.${i}.location, uv.${i}) - }\n`; - } - /* eslint-disable max-len */ - else if ((data.type === 'sampler2D' || data.type === 'samplerCube' || data.type === 'sampler2DArray') && data.size === 1 && !data.isArray) - /* eslint-disable max-len */ - { - func += ` - renderer.texture.bind(uv.${i}, ${textureCount}); - - if(ud.${i}.value !== ${textureCount}) - { - ud.${i}.value = ${textureCount}; - gl.uniform1i(ud.${i}.location, ${textureCount});\n; // eslint-disable-line max-len - }\n`; - - textureCount++; - } - else if (data.type === 'mat3' && data.size === 1) - { - if (group.uniforms[i].a !== undefined) - { - // TODO and some smart caching dirty ids here! - func += ` - gl.uniformMatrix3fv(ud.${i}.location, false, uv.${i}.toArray(true)); - \n`; - } - else - { - func += ` - gl.uniformMatrix3fv(ud.${i}.location, false, uv.${i}); - \n`; - } - } - else if (data.type === 'vec2' && data.size === 1) - { - // TODO - do we need both here? - // maybe we can get away with only using points? - if (group.uniforms[i].x !== undefined) - { - func += ` - cv = ud.${i}.value; - v = uv.${i}; - - if(cv[0] !== v.x || cv[1] !== v.y) - { - cv[0] = v.x; - cv[1] = v.y; - gl.uniform2f(ud.${i}.location, v.x, v.y); - }\n`; - } - else - { - func += ` - cv = ud.${i}.value; - v = uv.${i}; - - if(cv[0] !== v[0] || cv[1] !== v[1]) - { - cv[0] = v[0]; - cv[1] = v[1]; - gl.uniform2f(ud.${i}.location, v[0], v[1]); - } - \n`; - } - } - else if (data.type === 'vec4' && data.size === 1) - { - // TODO - do we need both here? - // maybe we can get away with only using points? - if (group.uniforms[i].width !== undefined) - { - func += ` - cv = ud.${i}.value; - v = uv.${i}; - - if(cv[0] !== v.x || cv[1] !== v.y || cv[2] !== v.width || cv[3] !== v.height) - { - cv[0] = v.x; - cv[1] = v.y; - cv[2] = v.width; - cv[3] = v.height; - gl.uniform4f(ud.${i}.location, v.x, v.y, v.width, v.height) - }\n`; - } - else - { - func += ` - cv = ud.${i}.value; - v = uv.${i}; - - if(cv[0] !== v[0] || cv[1] !== v[1] || cv[2] !== v[2] || cv[3] !== v[3]) - { - cv[0] = v[0]; - cv[1] = v[1]; - cv[2] = v[2]; - cv[3] = v[3]; - - gl.uniform4f(ud.${i}.location, v[0], v[1], v[2], v[3]) - } - \n`; - } - } - else - { - const templateType = (data.size === 1) ? GLSL_TO_SINGLE_SETTERS_CACHED : GLSL_TO_ARRAY_SETTERS; - - const template = templateType[data.type].replace('location', `ud.${i}.location`); - - func += ` - cv = ud.${i}.value; - v = uv.${i}; - ${template};\n`; - } - } - - // console.log(' --------------- ') - // console.log(func); - - return new Function('ud', 'uv', 'renderer', func); // eslint-disable-line no-new-func -} diff --git a/packages/core/src/shader/getTestContext.js b/packages/core/src/shader/getTestContext.js deleted file mode 100644 index ea174cc..0000000 --- a/packages/core/src/shader/getTestContext.js +++ /dev/null @@ -1,48 +0,0 @@ -import { settings } from '@pixi/settings'; - -let context = null; - -/** - * returns a little webGL context to use for program inspection. - * - * @static - * @private - * @returns {webGL-context} a gl context to test with - */ -export default function getTestContext() -{ - if (!context) - { - const canvas = document.createElement('canvas'); - - let gl; - - if (settings.PREFER_WEBGL_2) - { - gl = canvas.getContext('webgl2', {}); - } - - if (!gl) - { - gl = canvas.getContext('webgl', {}) - || canvas.getContext('experimental-webgl', {}); - - if (!gl) - { - // fail, not able to get a context - throw new Error('This browser does not support webGL. Try using the canvas renderer'); - } - else - { - // for shader testing.. - gl.getExtension('WEBGL_draw_buffers'); - } - } - - context = gl; - - return gl; - } - - return context; -} diff --git a/packages/core/src/shader/utils/checkMaxIfStatmentsInShader.js b/packages/core/src/shader/utils/checkMaxIfStatmentsInShader.js new file mode 100644 index 0000000..8047c96 --- /dev/null +++ b/packages/core/src/shader/utils/checkMaxIfStatmentsInShader.js @@ -0,0 +1,58 @@ +const fragTemplate = [ + 'precision mediump float;', + 'void main(void){', + 'float test = 0.1;', + '%forloop%', + 'gl_FragColor = vec4(0.0);', + '}', +].join('\n'); + +export default function checkMaxIfStatmentsInShader(maxIfs, gl) +{ + if (maxIfs === 0) + { + throw new Error('Invalid value of `0` passed to `checkMaxIfStatementsInShader`'); + } + + const shader = gl.createShader(gl.FRAGMENT_SHADER); + + while (true) // eslint-disable-line no-constant-condition + { + const fragmentSrc = fragTemplate.replace(/%forloop%/gi, generateIfTestSrc(maxIfs)); + + gl.shaderSource(shader, fragmentSrc); + gl.compileShader(shader); + + if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) + { + maxIfs = (maxIfs / 2) | 0; + } + else + { + // valid! + break; + } + } + + return maxIfs; +} + +function generateIfTestSrc(maxIfs) +{ + let src = ''; + + for (let i = 0; i < maxIfs; ++i) + { + if (i > 0) + { + src += '\nelse '; + } + + if (i < maxIfs - 1) + { + src += `if(test == ${i}.0){}`; + } + } + + return src; +} diff --git a/packages/core/src/shader/utils/compileProgram.js b/packages/core/src/shader/utils/compileProgram.js new file mode 100644 index 0000000..8c6ccb9 --- /dev/null +++ b/packages/core/src/shader/utils/compileProgram.js @@ -0,0 +1,78 @@ +/** + * @method compileProgram + * @memberof PIXI.glCore.shader + * @param gl {WebGLRenderingContext} The current WebGL context {WebGLProgram} + * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. + * @param fragmentSrc {string|string[]} The fragment shader source as an array of strings. + * @param attributeLocations {Object} An attribute location map that lets you manually set the attribute locations + * @return {WebGLProgram} the shader program + */ +export default function compileProgram(gl, vertexSrc, fragmentSrc, attributeLocations) +{ + const glVertShader = compileShader(gl, gl.VERTEX_SHADER, vertexSrc); + const glFragShader = compileShader(gl, gl.FRAGMENT_SHADER, fragmentSrc); + + let program = gl.createProgram(); + + gl.attachShader(program, glVertShader); + gl.attachShader(program, glFragShader); + + // optionally, set the attributes manually for the program rather than letting WebGL decide.. + if (attributeLocations) + { + for (const i in attributeLocations) + { + gl.bindAttribLocation(program, attributeLocations[i], i); + } + } + + gl.linkProgram(program); + + // if linking fails, then log and cleanup + if (!gl.getProgramParameter(program, gl.LINK_STATUS)) + { + console.error('Pixi.js Error: Could not initialize shader.'); + console.error('gl.VALIDATE_STATUS', gl.getProgramParameter(program, gl.VALIDATE_STATUS)); + console.error('gl.getError()', gl.getError()); + + // if there is a program info log, log it + if (gl.getProgramInfoLog(program) !== '') + { + console.warn('Pixi.js Warning: gl.getProgramInfoLog()', gl.getProgramInfoLog(program)); + } + + gl.deleteProgram(program); + program = null; + } + + // clean up some shaders + gl.deleteShader(glVertShader); + gl.deleteShader(glFragShader); + + return program; +} + +/** + * @private + * @param gl {WebGLRenderingContext} The current WebGL context {WebGLProgram} + * @param type {Number} the type, can be either VERTEX_SHADER or FRAGMENT_SHADER + * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. + * @return {WebGLShader} the shader + */ +function compileShader(gl, type, src) +{ + const shader = gl.createShader(type); + + gl.shaderSource(shader, src); + gl.compileShader(shader); + + if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) + { + console.warn(src); + console.error(gl.getShaderInfoLog(shader)); + + return null; + } + + return shader; +} diff --git a/packages/core/src/shader/utils/defaultValue.js b/packages/core/src/shader/utils/defaultValue.js new file mode 100644 index 0000000..97290bd --- /dev/null +++ b/packages/core/src/shader/utils/defaultValue.js @@ -0,0 +1,79 @@ +/** + * @method defaultValue + * @memberof PIXI.glCore.shader + * @param type {String} Type of value + * @param size {Number} + */ +export default function defaultValue(type, size) +{ + switch (type) + { + case 'float': + return 0; + + case 'vec2': + return new Float32Array(2 * size); + + case 'vec3': + return new Float32Array(3 * size); + + case 'vec4': + return new Float32Array(4 * size); + + case 'int': + case 'sampler2D': + case 'sampler2DArray': + return 0; + + case 'ivec2': + return new Int32Array(2 * size); + + case 'ivec3': + return new Int32Array(3 * size); + + case 'ivec4': + return new Int32Array(4 * size); + + case 'bool': + return false; + + case 'bvec2': + + return booleanArray(2 * size); + + case 'bvec3': + return booleanArray(3 * size); + + case 'bvec4': + return booleanArray(4 * size); + + case 'mat2': + return new Float32Array([1, 0, + 0, 1]); + + case 'mat3': + return new Float32Array([1, 0, 0, + 0, 1, 0, + 0, 0, 1]); + + case 'mat4': + return new Float32Array([1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1]); + } + + return null; +} + +function booleanArray(size) +{ + const array = new Array(size); + + for (let i = 0; i < array.length; i++) + { + array[i] = false; + } + + return array; +} diff --git a/packages/core/src/AbstractRenderer.js b/packages/core/src/AbstractRenderer.js new file mode 100644 index 0000000..64b2eb1 --- /dev/null +++ b/packages/core/src/AbstractRenderer.js @@ -0,0 +1,353 @@ +import { hex2string, hex2rgb } from '@pixi/utils'; +import { Matrix, Rectangle } from '@pixi/math'; +import { RENDERER_TYPE } from '@pixi/constants'; +import { settings } from '@pixi/settings'; +import { Container } from '@pixi/display'; +import RenderTexture from './renderTexture/RenderTexture'; +import EventEmitter from 'eventemitter3'; + +const tempMatrix = new Matrix(); + +/** + * 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 EventEmitter + * @memberof PIXI + */ +export default class AbstractRenderer extends EventEmitter +{ + // eslint-disable-next-line valid-jsdoc + /** + * @param {string} system - The name of the system this renderer is for. + * @param {object} [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.transparent=false] - If the render view is transparent, default false + * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false + * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. The + * resolution of the renderer retina would be 2. + * @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 {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when rendering, + * stopping pixel interpolation. + */ + constructor(system, options, arg2, arg3) + { + super(); + + // Support for constructor(system, screenWidth, screenHeight, options) + if (typeof options === 'number') + { + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + }, arg3); + } + + // 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 = RENDERER_TYPE.UNKNOWN; + + /** + * Measurements of the screen. (0, 0, screenWidth, screenHeight) + * + * Its safe to use as filterArea or hitArea for 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 1 + */ + this.resolution = options.resolution || settings.RESOLUTION; + + /** + * Whether the render view is transparent + * + * @member {boolean} + */ + this.transparent = options.transparent; + + /** + * Whether css dimensions of canvas view should be resized to screen dimensions automatically + * + * @member {boolean} + */ + this.autoResize = options.autoResize || false; + + /** + * Tracks the blend modes useful for this renderer. + * + * @member {object} + */ + this.blendModes = null; + + /** + * 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; + + /** + * If true PixiJS will Math.floor() x/y values when rendering, stopping pixel interpolation. + * Handy for crisp pixel art and speed on legacy devices. + * + * @member {boolean} + */ + this.roundPixels = options.roundPixels; + + /** + * The background color as a number. + * + * @member {number} + * @private + */ + this._backgroundColor = 0x000000; + + /** + * The background color as an [R, G, B] array. + * + * @member {number[]} + * @private + */ + this._backgroundColorRgba = [0, 0, 0, 0]; + + /** + * The background color as a string. + * + * @member {string} + * @private + */ + this._backgroundColorString = '#000000'; + + this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter + + /** + * This temporary display object used as the parent of the currently being rendered item + * + * @member {PIXI.DisplayObject} + * @private + */ + this._tempDisplayObjectParent = new Container(); + + /** + * The last root object that the renderer tried to render. + * + * @member {PIXI.DisplayObject} + * @private + */ + this._lastObjectRendered = this._tempDisplayObjectParent; + + /** + * Collection of plugins. + * @readonly + * @member {object} + */ + this.plugins = {}; + } + + /** + * Initialize the plugins. + * + * @protected + * @param {object} staticMap - The dictionary of staticly saved plugins. + */ + initPlugins(staticMap) + { + 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() + { + return this.view.width; + } + + /** + * Same as view.height, actual number of pixels in the canvas by vertical + * + * @member {number} + * @readonly + * @default 600 + */ + get height() + { + return this.view.height; + } + + /** + * Resizes the screen and canvas to the specified width and height + * Canvas dimensions are multiplied by resolution + * + * @param {number} screenWidth - the new width of the screen + * @param {number} screenHeight - the new height of the screen + */ + resize(screenWidth, screenHeight) + { + this.screen.width = screenWidth; + this.screen.height = screenHeight; + + this.view.width = screenWidth * this.resolution; + this.view.height = screenHeight * this.resolution; + + if (this.autoResize) + { + this.view.style.width = `${screenWidth}px`; + this.view.style.height = `${screenHeight}px`; + } + } + + /** + * 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 {PIXI.DisplayObject} displayObject - The displayObject the object will be generated from + * @param {number} scaleMode - Should be one of the scaleMode consts + * @param {number} resolution - The resolution / device pixel ratio of the texture being generated + * @param {PIXI.Rectangle} [region] - The region of the displayObject, that shall be rendered, + * if no region is specified, defaults to the local bounds of the displayObject. + * @return {PIXI.Texture} a texture of the graphics object + */ + generateTexture(displayObject, scaleMode, resolution, region) + { + region = region || displayObject.getLocalBounds(); + + // 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(region.width | 0, region.height | 0, scaleMode, resolution); + + tempMatrix.tx = -region.x; + tempMatrix.ty = -region.y; + + this.render(displayObject, renderTexture, false, tempMatrix, true); + + return renderTexture; + } + + /** + * Removes everything from the renderer and optionally removes the Canvas DOM element. + * + * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. + */ + destroy(removeView) + { + 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); + } + + this.plugins = null; + + this.type = RENDERER_TYPE.UNKNOWN; + + this.view = null; + + this.screen = null; + + this.resolution = 0; + + this.transparent = false; + + this.autoResize = false; + + this.blendModes = null; + + this.options = null; + + this.preserveDrawingBuffer = false; + this.clearBeforeRender = false; + + this.roundPixels = false; + + this._backgroundColor = 0; + this._backgroundColorRgba = null; + this._backgroundColorString = null; + + this._tempDisplayObjectParent = null; + this._lastObjectRendered = null; + } + + /** + * The background color to fill if not transparent + * + * @member {number} + */ + get backgroundColor() + { + return this._backgroundColor; + } + + set backgroundColor(value) // eslint-disable-line require-jsdoc + { + this._backgroundColor = value; + this._backgroundColorString = hex2string(value); + hex2rgb(value, this._backgroundColorRgba); + } +} diff --git a/packages/core/src/Renderer.js b/packages/core/src/Renderer.js new file mode 100644 index 0000000..2227e78 --- /dev/null +++ b/packages/core/src/Renderer.js @@ -0,0 +1,338 @@ +import AbstractRenderer from './AbstractRenderer'; +import { sayHello } from '@pixi/utils'; +import MaskSystem from './mask/MaskSystem'; +import StencilSystem from './mask/StencilSystem'; +import FilterSystem from './filters/FilterSystem'; +import FramebufferSystem from './framebuffer/FramebufferSystem'; +import RenderTextureSystem from './renderTexture/RenderTextureSystem'; +import TextureSystem from './textures/TextureSystem'; +import ProjectionSystem from './projection/ProjectionSystem'; +import StateSystem from './state/StateSystem'; +import GeometrySystem from './geometry/GeometrySystem'; +import ShaderSystem from './shader/ShaderSystem'; +import ContextSystem from './context/ContextSystem'; +import BatchSystem from './batch/BatchSystem'; +import TextureGCSystem from './textures/TextureGCSystem'; +import { RENDERER_TYPE } from '@pixi/constants'; +import UniformGroup from './shader/UniformGroup'; +import { Matrix } from '@pixi/math'; +import Runner from 'mini-runner'; + +/** + * 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 Render works by automatically managing webGLBatchs. + * 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 :) + * + * @class + * @memberof PIXI + * @extends PIXI.AbstractRenderer + */ +export default class Renderer extends AbstractRenderer +{ + // eslint-disable-next-line valid-jsdoc + /** + * + * @param {object} [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.transparent=false] - If the render view is transparent, default false + * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false + * @param {boolean} [options.antialias=false] - sets antialias. If not available natively then FXAA + * antialiasing is used + * @param {boolean} [options.forceFXAA=false] - forces FXAA antialiasing to be used over native. + * FXAA is faster, but may not always look as great + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. + * The resolution of the renderer retina would be 2. + * @param {boolean} [options.clearBeforeRender=true] - This sets if the renderer will clear + * the canvas or not before the new render pass. If you wish to set this to false, you *must* set + * preserveDrawingBuffer to `true`. + * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, + * enable this if you need to call toDataUrl on the webgl context. + * @param {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when + * rendering, stopping pixel interpolation. + * @param {number} [options.backgroundColor=0x000000] - The background color of the rendered area + * (shown if not transparent). + * @param {boolean} [options.legacy=false] - If true PixiJS will aim to ensure compatibility + * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. + * @param {string} [options.powerPreference] - Parameter passed to webgl context, set to "high-performance" + * for devices with dual graphics card + */ + constructor(options, arg2, arg3) + { + super('WebGL', options, arg2, arg3); + + /** + * The type of this renderer as a standardised const + * + * @member {number} + * @see PIXI.RENDERER_TYPE + */ + this.type = RENDERER_TYPE.WEBGL; + + // this will be set by the contextSystem (this.context) + this.gl = null; + this.CONTEXT_UID = 0; + this.legacy = !!options.legacy; + + // TODO legacy! + + // runners! + this.runners = { + destroy: new Runner('destroy'), + contextChange: new Runner('contextChange', 1), + reset: new Runner('reset'), + update: new Runner('update'), + postrender: new Runner('postrender'), + prerender: new Runner('prerender'), + resize: new Runner('resize', 2), + }; + + 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(GeometrySystem, 'geometry') + .addSystem(FramebufferSystem, 'framebuffer') + .addSystem(StencilSystem, 'stencil') + .addSystem(ProjectionSystem, 'projection') + .addSystem(TextureGCSystem, 'textureGC') + .addSystem(FilterSystem, 'filter') + .addSystem(RenderTextureSystem, 'renderTexture') + .addSystem(BatchSystem, 'batch'); + + this.initPlugins(Renderer.__plugins); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ + if (options.context) + { + this.context.initFromContext(options.context); + } + else + { + this.context.initFromOptions({ + alpha: this.transparent, + antialias: options.antialias, + premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', + stencil: true, + 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); + } + + /** + * Add a new system to the renderer. + * @param {class} ClassRef - Class reference + * @param {string} [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. + * @return {PIXI.Renderer} Return instance of renderer + */ + addSystem(ClassRef, name) + { + if (!name) + { + name = ClassRef.name; + } + + // TODO - read name from class.name.. + + /* + if(name.includes('System')) + { + name = name.replace('System', ''); + name = name.charAt(0).toLowerCase() + name.slice(1); + } + */ + + const system = new ClassRef(this); + + if (this[name]) + { + throw new Error(`Whoops! ${name} is already a manger`); + } + + this[name] = system; + + for (const i in this.runners) + { + this.runners[i].add(system); + } + + return this; + + /** + * 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. + */ + } + + /** + * Renders the object to its webGL view + * + * @param {PIXI.DisplayObject} displayObject - the object to be rendered + * @param {PIXI.RenderTexture} renderTexture - The render texture to render to. + * @param {boolean} [clear] - Should the canvas be cleared before the new render + * @param {PIXI.Transform} [transform] - A transform to apply to the render texture before rendering. + * @param {boolean} [skipUpdateTransform] - Should we skip the update transform pass? + */ + render(displayObject, renderTexture, clear, transform, skipUpdateTransform) + { + // can be handy to know! + this.renderingToScreen = !renderTexture; + + this.runners.prerender.run(); + this.emit('prerender'); + + // 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.parent; + + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = 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.run(); + + this.emit('postrender'); + } + + /** + * Resizes the webGL view to the specified width and height. + * + * @param {number} screenWidth - the new width of the screen + * @param {number} screenHeight - the new height of the screen + */ + resize(screenWidth, screenHeight) + { + AbstractRenderer.prototype.resize.call(this, screenWidth, screenHeight); + + this.runners.resize.run(screenWidth, screenHeight); + } + + /** + * Resets the WebGL state so you can render things however you fancy! + * + * @return {PIXI.Renderer} Returns itself. + */ + reset() + { + this.runners.reset.run(); + + return this; + } + + clear() + { + this.framebuffer.bind(); + this.framebuffer.clear(); + } + + /** + * Removes everything from the renderer (event listeners, spritebatch, etc...) + * + * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. + * See: https://github.com/pixijs/pixi.js/issues/2233 + */ + destroy(removeView) + { + this.runners.destroy.run(); + + // call base destroy + super.destroy(removeView); + + // TODO nullify all the managers.. + this.gl = null; + } + + /** + * 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 + * builds and excluding plugins. + * @name PIXI.Renderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. + */ + + /** + * Adds a plugin to the renderer. + * + * @method + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + static registerPlugin(pluginName, ctor) + { + Renderer.__plugins = Renderer.__plugins || {}; + Renderer.__plugins[pluginName] = ctor; + } +} diff --git a/packages/core/src/System.js b/packages/core/src/System.js new file mode 100644 index 0000000..05a10c8 --- /dev/null +++ b/packages/core/src/System.js @@ -0,0 +1,42 @@ +/** + * System is a base class used for extending systems used by the {@link PIXI.Renderer} + * @see PIXI.Renderer#addSystem + * @class + * @memberof PIXI + */ +export default class System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + /** + * The renderer this manager works for. + * + * @member {PIXI.Renderer} + */ + this.renderer = renderer; + + this.renderer.runners.contextChange.add(this); + } + + /** + * Generic method called when there is a WebGL context change. + * + */ + contextChange() + { + // do some codes init! + } + + /** + * Generic destroy methods to be overridden by the subclass + * + */ + destroy() + { + this.renderer.runners.contextChange.remove(this); + this.renderer = null; + } +} diff --git a/packages/core/src/batch/BatchSystem.js b/packages/core/src/batch/BatchSystem.js new file mode 100644 index 0000000..b04c249 --- /dev/null +++ b/packages/core/src/batch/BatchSystem.js @@ -0,0 +1,68 @@ +import System from '../System'; +import ObjectRenderer from './ObjectRenderer'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class BatchSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + super(renderer); + + /** + * An empty renderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.emptyRenderer = new ObjectRenderer(renderer); + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.currentRenderer = this.emptyRenderer; + } + + /** + * Changes the current renderer to the one given in parameter + * + * @param {PIXI.ObjectRenderer} objectRenderer - The object renderer to use. + */ + setObjectRenderer(objectRenderer) + { + if (this.currentRenderer === objectRenderer) + { + return; + } + + this.currentRenderer.stop(); + this.currentRenderer = objectRenderer; + + this.renderer.state.setState(objectRenderer.state); + + this.currentRenderer.start(); + } + + /** + * This should be called if you wish to do some custom rendering + * It will basically render anything that may be batched up such as sprites + * + */ + flush() + { + this.setObjectRenderer(this.emptyRenderer); + } + + reset() + { + this.setObjectRenderer(this.emptyRenderer); + } +} diff --git a/packages/core/src/batch/ObjectRenderer.js b/packages/core/src/batch/ObjectRenderer.js new file mode 100644 index 0000000..0c851a9 --- /dev/null +++ b/packages/core/src/batch/ObjectRenderer.js @@ -0,0 +1,48 @@ +import System from '../System'; + +/** + * Base for a common object renderer that can be used as a system renderer plugin. + * + * @class + * @extends PIXI.System + * @memberof PIXI + */ +export default class ObjectRenderer extends System +{ + /** + * Starts the renderer and sets the shader + * + */ + start() + { + // set the shader.. + } + + /** + * Stops the renderer + * + */ + stop() + { + this.flush(); + } + + /** + * Stub method for rendering content and emptying the current batch. + * + */ + flush() + { + // flush! + } + + /** + * Renders an object + * + * @param {PIXI.DisplayObject} object - The object to render. + */ + render(object) // eslint-disable-line no-unused-vars + { + // render the object + } +} diff --git a/packages/core/src/context/ContextSystem.js b/packages/core/src/context/ContextSystem.js new file mode 100644 index 0000000..301aab4 --- /dev/null +++ b/packages/core/src/context/ContextSystem.js @@ -0,0 +1,186 @@ +import System from '../System'; +import { settings } from '@pixi/settings'; + +let CONTEXT_UID = 0; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class ContextSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.webGLVersion = 1; + + this.handleContextLost = this.handleContextLost.bind(this); + this.handleContextRestored = this.handleContextRestored.bind(this); + + this.extensions = {}; + + renderer.view.addEventListener('webglcontextlost', this.handleContextLost, false); + renderer.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + } + + get isLost() + { + return (!this.gl || this.gl.isContextLost()); + } + + contextChange(gl) + { + this.gl = gl; + + // restore a context if it was previously lost + if (gl.isContextLost() && gl.getExtension('WEBGL_lose_context')) + { + gl.getExtension('WEBGL_lose_context').restoreContext(); + } + } + + initFromContext(gl) + { + this.gl = gl; + this.validateContext(gl); + this.renderer.gl = gl; + this.renderer.CONTEXT_UID = CONTEXT_UID++; + this.renderer.runners.contextChange.run(gl); + } + + initFromOptions(options) + { + const gl = this.createContext(this.renderer.view, options); + + this.initFromContext(gl); + } + + /** + * Helper class to create a webGL Context + * + * @class + * @memberof PIXI.glCore + * @param canvas {HTMLCanvasElement} the canvas element that we will get the context from + * @param options {Object} An options object that gets passed in to the canvas element containing the context attributes, + * see https://developer.mozilla.org/en/docs/Web/API/HTMLCanvasElement/getContext for the options available + * @return {WebGLRenderingContext} the WebGL context + */ + createContext(canvas, options) + { + let gl; + + if (settings.PREFER_WEBGL_2) + { + gl = canvas.getContext('webgl2', options); + } + + if (gl) + { + this.webGLVersion = 2; + } + else + { + this.webGLVersion = 1; + + gl = canvas.getContext('webgl', options) + || canvas.getContext('experimental-webgl', options); + + if (!gl) + { + // fail, not able to get a context + throw new Error('This browser does not support webGL. Try using the canvas renderer'); + } + } + + this.gl = gl; + + this.getExtensions(); + + return gl; + } + + getExtensions() + { + // time to set up default etensions that pixi uses.. + const gl = this.gl; + const extensions = this.extensions; + + if (this.webGLVersion === 1) + { + extensions.drawBuffers = gl.getExtension('WEBGL_draw_buffers'); + extensions.depthTexture = gl.getExtension('WEBKIT_WEBGL_depth_texture'); + extensions.floatTexture = gl.getExtension('OES_texture_float'); + extensions.loseContext = gl.getExtension('WEBGL_lose_context'); + + extensions.vertexArrayObject = gl.getExtension('OES_vertex_array_object') + || gl.getExtension('MOZ_OES_vertex_array_object') + || gl.getExtension('WEBKIT_OES_vertex_array_object'); + } + + // we don't use any specific WebGL 2 ones yet! + } + + /** + * Handles a lost webgl context + * + * @private + * @param {WebGLContextEvent} event - The context lost event. + */ + handleContextLost(event) + { + event.preventDefault(); + } + + /** + * Handles a restored webgl context + * + * @private + */ + handleContextRestored() + { + this.renderer.runners.contextChange.run(this.gl); + } + + destroy() + { + const view = this.renderer.view; + + // remove listeners + view.removeEventListener('webglcontextlost', this.handleContextLost); + view.removeEventListener('webglcontextrestored', this.handleContextRestored); + + this.gl.useProgram(null); + + if (this.extensions.loseContext) + { + this.extensions.loseContext.loseContext(); + } + } + + postrender() + { + this.gl.flush(); + } + + validateContext(gl) + { + const attributes = gl.getContextAttributes(); + + // this is going to be fairly simple for now.. but at least we have room to grow! + if (!attributes.stencil) + { + /* eslint-disable max-len */ + + /* eslint-disable no-console */ + console.warn('Provided WebGL context does not have a stencil buffer, masks may not render correctly'); + /* eslint-enable no-console */ + + /* eslint-enable max-len */ + } + } +} diff --git a/packages/core/src/filters/Filter.js b/packages/core/src/filters/Filter.js new file mode 100644 index 0000000..9d8cad8 --- /dev/null +++ b/packages/core/src/filters/Filter.js @@ -0,0 +1,140 @@ +import Shader from '../shader/Shader'; +import Program from '../shader/Program'; +import State from '../state/State'; +import { settings } from '@pixi/settings'; +// import extractUniformsFromSrc from './extractUniformsFromSrc'; +import defaultVertex from './defaultFilter.vert'; +import defaultFragment from './defaultFilter.frag'; + +// let math = require('../../math'); +/** + * @class + * @memberof PIXI + * @extends PIXI.Shader + */ +export default class Filter extends Shader +{ + /** + * @param {string} [vertexSrc] - The source of the vertex shader. + * @param {string} [fragmentSrc] - The source of the fragment shader. + * @param {object} [uniforms] - Custom uniforms to use to augment the built-in ones. + */ + constructor(vertexSrc, fragmentSrc, uniforms) + { + const program = Program.from(vertexSrc, fragmentSrc); + + super(program, uniforms); + + /** + * The padding of the filter. Some filters require extra space to breath such as a blur. + * Increasing this will add extra width and height to the bounds of the object that the + * filter is applied to. + * + * @member {number} + */ + this.padding = 0; + + /** + * The resolution of the filter. Setting this to be lower will lower the quality but + * increase the performance of the filter. + * + * @member {number} + */ + this.resolution = settings.RESOLUTION; + + /** + * If enabled is true the filter is applied, if false it will not. + * + * @member {boolean} + */ + this.enabled = true; + + /** + * If enabled, PixiJS will fit the filter area into boundaries for better performance. + * Switch it off if it does not work for specific shader. + * + * @member {boolean} + */ + this.autoFit = true; + + /** + * Legacy filters use position and uvs from attributes + * @member {boolean} + * @readonly + */ + this.legacy = !!this.program.attributeData.aTextureCoord; + + /** + * the webGL state the filter requires to render + * @member {PIXI.State} + */ + this.state = new State(); + } + + /** + * Applies the filter + * + * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + * @param {boolean} clear - Should the output be cleared before rendering to it + * @param {object} [currentState] - It's current state of filter. + * There are some useful properties in the currentState : + * target, filters, sourceFrame, destinationFrame, renderTarget, resolution + */ + apply(filterManager, input, output, clear, currentState, derp) // eslint-disable-line no-unused-vars + { + // do as you please! + + filterManager.applyFilter(this, input, output, clear, currentState, derp); + + // or just do a regular render.. + } + + /** + * Sets the blendmode of the filter + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL + */ + get blendMode() + { + return this.state.blendMode; + } + + set blendMode(value) // eslint-disable-line require-jsdoc + { + this.state.blendMode = value; + } + + /** + * The default vertex shader source + * + * @static + * @constant + */ + static get defaultVertexSrc() + { + return defaultVertex; + } + + /** + * The default fragment shader source + * + * @static + * @constant + */ + static get defaultFragmentSrc() + { + return defaultFragment; + } +} + +/** + * Used for caching shader IDs + * + * @static + * @private + */ +Filter.SOURCE_KEY_MAP = {}; + diff --git a/packages/core/src/filters/FilterSystem.js b/packages/core/src/filters/FilterSystem.js new file mode 100644 index 0000000..3174318 --- /dev/null +++ b/packages/core/src/filters/FilterSystem.js @@ -0,0 +1,431 @@ +import System from '../System'; + +import RenderTexture from '../renderTexture/RenderTexture'; +import Quad from '../utils/Quad'; +import QuadUv from '../utils/QuadUv'; +import { Rectangle } from '@pixi/math'; +import * as filterTransforms from './filterTransforms'; +import bitTwiddle from 'bit-twiddle'; +import UniformGroup from '../shader/UniformGroup'; +import { DRAW_MODES } from '@pixi/constants'; + +// +/** + * @ignore + * @class + */ +class FilterState +{ + /** + * + */ + constructor() + { + this.renderTexture = null; + this.sourceFrame = new Rectangle(); + this.destinationFrame = new Rectangle(); + this.filters = []; + this.target = null; + this.legacy = false; + this.resolution = 1; + } +} + +/** + * @class + * @memberof PIXI.systems + * @extends PIXI.System + */ +export default class FilterSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + /** + * stores a bunch of PO2 textures used for filtering + * @type {Object} + */ + this.texturePool = {}; + + /** + * a pool for storing filter states, save us creating new ones each tick + * @type {Array} + */ + this.statePool = []; + + /** + * A very simple geometry used when drawing a filter effect to the screen + * @type {Quad} + */ + this.quad = new Quad(); + + this.quadUv = new QuadUv(); + + /** + * Temporary rect for maths + * @type {PIXI.Rectangle} + */ + this.tempRect = new Rectangle(); + + this.activeState = {}; + + /** + * this uniform group is attached to filter uniforms when used + * @type {UniformGroup} + */ + this.globalUniforms = new UniformGroup({ + sourceFrame: this.tempRect, + destinationFrame: this.tempRect, + + // legacy variables + filterArea: new Float32Array(4), + filterClamp: new Float32Array(4), + }, true); + } + + /** + * Adds a new filter to the System. + * + * @param {PIXI.DisplayObject} target - The target of the filter to render. + * @param {PIXI.Filter[]} filters - The filters to apply. + */ + push(target, filters) + { + const renderer = this.renderer; + const filterStack = this.renderer.renderTexture.defaultFilterStack; + const state = this.statePool.pop() || new FilterState(); + + let resolution = filters[0].resolution; + let padding = filters[0].padding; + let autoFit = filters[0].autoFit; + let legacy = filters[0].legacy; + + for (let i = 1; i < filters.length; i++) + { + const filter = filters[i]; + + // lets use the lowest resolution.. + resolution = Math.min(resolution, filter.resolution); + // and the largest amount of padding! + padding = Math.max(padding, filter.padding); + // only auto fit if all filters are autofit + autoFit = autoFit || filter.autoFit; + + legacy = legacy || filter.legacy; + } + + filterStack.push(state); + + state.resolution = resolution; + + state.legacy = legacy; + + // round to whole number based on resolution + // TODO move that to the shader too? + state.sourceFrame = target.filterArea || target.getBounds(true); + + state.sourceFrame.pad(padding); + + if (autoFit) + { + state.sourceFrame.fit(this.renderer.renderTexture.destinationFrame); + } + + state.sourceFrame.round(resolution); + + state.renderTexture = this.getPotFilterTexture(state.sourceFrame.width, state.sourceFrame.height, resolution); + state.filters = filters; + + state.destinationFrame.width = state.renderTexture.width; + state.destinationFrame.height = state.renderTexture.height; + + state.renderTexture.filterFrame = state.sourceFrame; + renderer.renderTexture.bind(state.renderTexture, state.sourceFrame);// /, state.destinationFrame); + renderer.renderTexture.clear(); + } + + /** + * Pops off the filter and applies it. + * + */ + pop() + { + const renderer = this.renderer; + const filterStack = renderer.renderTexture.defaultFilterStack; + const state = filterStack.pop(); + const filters = state.filters; + + this.activeState = state; + + const globalUniforms = this.globalUniforms.uniforms; + + globalUniforms.sourceFrame = state.sourceFrame; + globalUniforms.destinationFrame = state.destinationFrame; + globalUniforms.resolution = state.resolution; + + // only update the rect if its legacy.. + if (state.legacy) + { + const filterArea = globalUniforms.filterArea; + const filterClamp = globalUniforms.filterClamp; + + filterArea[0] = state.destinationFrame.width; + filterArea[1] = state.destinationFrame.height; + filterArea[2] = state.sourceFrame.x; + filterArea[3] = state.sourceFrame.y; + + filterClamp[0] = 0.5 / state.resolution / state.destinationFrame.width; + filterClamp[1] = 0.5 / state.resolution / state.destinationFrame.height; + filterClamp[2] = (state.sourceFrame.width - 0.5) / state.resolution / state.destinationFrame.width; + filterClamp[3] = (state.sourceFrame.height - 0.5) / state.resolution / state.destinationFrame.height; + } + + this.globalUniforms.update(); + + const lastState = filterStack[filterStack.length - 1]; + + if (filters.length === 1) + { + filters[0].apply(this, state.renderTexture, lastState.renderTexture, false, state); + + this.returnFilterTexture(state.renderTexture); + } + else + { + let flip = state.renderTexture; + let flop = this.getPotFilterTexture( + flip.width, + flip.height, + state.resolution + ); + + flop.filterFrame = flip.filterFrame; + + let i = 0; + + for (i = 0; i < filters.length - 1; ++i) + { + filters[i].apply(this, flip, flop, true, state); + + const t = flip; + + flip = flop; + flop = t; + } + + filters[i].apply(this, flip, lastState.renderTexture, false, state); + + this.returnFilterTexture(flip); + this.returnFilterTexture(flop); + } + + this.statePool.push(state); + } + + /** + * Draws a filter. + * + * @param {PIXI.Filter} filter - The filter to draw. + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + * @param {boolean} clear - Should the output be cleared before rendering to it + */ + applyFilter(filter, input, output, clear) + { + const renderer = this.renderer; + + renderer.renderTexture.bind(output, output ? output.filterFrame : null); + + if (clear) + { + // gl.disable(gl.SCISSOR_TEST); + renderer.renderTexture.clear(); + // gl.enable(gl.SCISSOR_TEST); + } + + // set the uniforms.. + filter.uniforms.uSampler = input; + filter.uniforms.filterGlobals = this.globalUniforms; + + // TODO make it so that the order of this does not matter.. + // because it does at the moment cos of global uniforms. + // they need to get resynced + + renderer.state.setState(filter.state); + renderer.shader.bind(filter); + + if (filter.legacy) + { + this.quadUv.map(input._frame, input.filterFrame); + + renderer.geometry.bind(this.quadUv); + renderer.geometry.draw(DRAW_MODES.TRIANGLES); + } + else + { + renderer.geometry.bind(this.quad); + renderer.geometry.draw(DRAW_MODES.TRIANGLE_STRIP); + } + } + + /** + * Calculates the mapped matrix. + * + * TODO playing around here.. this is temporary - (will end up in the shader) + * this returns a matrix that will normalise map filter cords in the filter to screen space + * + * @param {PIXI.Matrix} outputMatrix - the matrix to output to. + * @return {PIXI.Matrix} The mapped matrix. + */ + calculateScreenSpaceMatrix(outputMatrix) + { + const currentState = this.activeState; + + return filterTransforms.calculateScreenSpaceMatrix( + outputMatrix, + currentState.sourceFrame, + currentState.destinationFrame + ); + } + + /** + * This will map the filter coord so that a texture can be used based on the transform of a sprite + * + * @param {PIXI.Matrix} outputMatrix - The matrix to output to. + * @param {PIXI.Sprite} sprite - The sprite to map to. + * @return {PIXI.Matrix} The mapped matrix. + */ + calculateSpriteMatrix(outputMatrix, sprite) + { + const currentState = this.activeState; + + return filterTransforms.calculateSpriteMatrix( + outputMatrix, + currentState.sourceFrame, + currentState.destinationFrame, + sprite + ); + } + + /** + * Destroys this Filter System. + * + * @param {boolean} [contextLost=false] context was lost, do not free shaders + * + */ + destroy(contextLost = false) + { + if (!contextLost) + { + this.emptyPool(); + } + else + { + this.texturePool = {}; + } + } + + /** + * Gets a Power-of-Two render texture. + * + * TODO move to a seperate class could be on renderer? + * also - could cause issue with multiple contexts? + * + * @private + * @param {WebGLRenderingContext} gl - The webgl rendering context + * @param {number} minWidth - The minimum width of the render target. + * @param {number} minHeight - The minimum height of the render target. + * @param {number} resolution - The resolution of the render target. + * @return {PIXI.RenderTarget} The new render target. + */ + getPotFilterTexture(minWidth, minHeight, resolution) + { + minWidth = bitTwiddle.nextPow2(minWidth); + minHeight = bitTwiddle.nextPow2(minHeight); + resolution = resolution || 1; + + const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + + if (!this.texturePool[key]) + { + this.texturePool[key] = []; + } + + let renderTexture = this.texturePool[key].pop(); + + if (!renderTexture) + { + // temporary bypass cache.. + // internally - this will cause a texture to be bound.. + renderTexture = RenderTexture.create({ + width: minWidth, + height: minHeight, + resolution, + }); + } + + return renderTexture; + } + + /** + * Gets extra render texture to use inside current filter + * + * @param {number} resolution resolution of the renderTexture + * @returns {PIXI.RenderTexture} + */ + getFilterTexture(resolution) + { + const rt = this.activeState.renderTexture; + + const filterTexture = this.getPotFilterTexture(rt.width, rt.height, resolution || rt.baseTexture.resolution); + + filterTexture.filterFrame = rt.filterFrame; + + return filterTexture; + } + + /** + * Frees a render texture back into the pool. + * + * @param {PIXI.RenderTarget} renderTexture - The renderTarget to free + */ + returnFilterTexture(renderTexture) + { + renderTexture.filterFrame = null; + + const base = renderTexture.baseTexture; + + const minWidth = base.width; + const minHeight = base.height; + + const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + + this.texturePool[key].push(renderTexture); + } + + /** + * Empties the texture pool. + * + */ + emptyPool() + { + for (const i in this.texturePool) + { + const textures = this.texturePool[i]; + + if (textures) + { + for (let j = 0; j < textures.length; j++) + { + textures[j].destroy(true); + } + } + } + + this.texturePool = {}; + } +} diff --git a/packages/core/src/filters/defaultFilter.frag b/packages/core/src/filters/defaultFilter.frag new file mode 100644 index 0000000..a14c1be --- /dev/null +++ b/packages/core/src/filters/defaultFilter.frag @@ -0,0 +1,22 @@ +varying vec2 vTextureCoord; +varying vec2 vFilterCoord; + +uniform sampler2D uSampler; +uniform sampler2D filterSampler; + +void main(void){ + vec4 masky = texture2D(filterSampler, vFilterCoord); + vec4 sample = texture2D(uSampler, vTextureCoord); + vec4 color; + if(mod(vFilterCoord.x, 1.0) > 0.5) + { + color = vec4(1.0, 0.0, 0.0, 1.0); + } + else + { + color = vec4(0.0, 1.0, 0.0, 1.0); + } + // gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0); + gl_FragColor = mix(sample, masky, 0.5); + gl_FragColor *= sample.a; +} diff --git a/packages/core/src/filters/defaultFilter.vert b/packages/core/src/filters/defaultFilter.vert new file mode 100644 index 0000000..e1febca --- /dev/null +++ b/packages/core/src/filters/defaultFilter.vert @@ -0,0 +1,14 @@ +attribute vec2 aVertexPosition; +attribute vec2 aTextureCoord; + +uniform mat3 projectionMatrix; +uniform mat3 filterMatrix; + +varying vec2 vTextureCoord; +varying vec2 vFilterCoord; + +void main(void){ + gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); + vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy; + vTextureCoord = aTextureCoord ; +} diff --git a/packages/core/src/filters/filterTransforms.js b/packages/core/src/filters/filterTransforms.js new file mode 100644 index 0000000..87a3cd6 --- /dev/null +++ b/packages/core/src/filters/filterTransforms.js @@ -0,0 +1,50 @@ +import { Matrix } from '@pixi/math'; + +/** + * Calculates the mapped matrix + * @param filterArea {Rectangle} The filter area + * @param sprite {Sprite} the target sprite + * @param outputMatrix {Matrix} @alvin + * @private + */ +// this returns a matrix that will normalise map filter cords in the filter to screen space +export function calculateScreenSpaceMatrix(outputMatrix, filterArea, textureSize) +{ + // TODO unwrap? + const mappedMatrix = outputMatrix.identity(); + + mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); + + mappedMatrix.scale(textureSize.width, textureSize.height); + + return mappedMatrix; +} + +export function calculateNormalizedScreenSpaceMatrix(outputMatrix, filterArea, textureSize) +{ + const mappedMatrix = outputMatrix.identity(); + + mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); + + const translateScaleX = (textureSize.width / filterArea.width); + const translateScaleY = (textureSize.height / filterArea.height); + + mappedMatrix.scale(translateScaleX, translateScaleY); + + return mappedMatrix; +} + +// this will map the filter coord so that a texture can be used based on the transform of a sprite +export function calculateSpriteMatrix(outputMatrix, filterArea, textureSize, sprite) +{ + const orig = sprite._texture.orig; + const mappedMatrix = outputMatrix.set(textureSize.width, 0, 0, textureSize.height, filterArea.x, filterArea.y); + const worldTransform = sprite.worldTransform.copyTo(Matrix.TEMP_MATRIX); + + worldTransform.invert(); + mappedMatrix.prepend(worldTransform); + mappedMatrix.scale(1.0 / orig.width, 1.0 / orig.height); + mappedMatrix.translate(sprite.anchor.x, sprite.anchor.y); + + return mappedMatrix; +} diff --git a/packages/core/src/filters/spriteMask/SpriteMaskFilter.js b/packages/core/src/filters/spriteMask/SpriteMaskFilter.js new file mode 100644 index 0000000..91697b7 --- /dev/null +++ b/packages/core/src/filters/spriteMask/SpriteMaskFilter.js @@ -0,0 +1,63 @@ +import Filter from '../Filter'; +import { Matrix } from '@pixi/math'; +import vertex from './spriteMaskFilter.vert'; +import fragment from './spriteMaskFilter.frag'; +import { default as TextureMatrix } from '../../textures/TextureMatrix'; + +/** + * The SpriteMaskFilter class + * + * @class + * @extends PIXI.Filter + * @memberof PIXI + */ +export default class SpriteMaskFilter extends Filter +{ + /** + * @param {PIXI.Sprite} sprite - the target sprite + */ + constructor(sprite) + { + const maskMatrix = new Matrix(); + + super(vertex, fragment); + + sprite.renderable = false; + + this.maskSprite = sprite; + this.maskMatrix = maskMatrix; + } + + /** + * Applies the filter + * + * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + */ + apply(filterManager, input, output) + { + const maskSprite = this.maskSprite; + const tex = this.maskSprite.texture; + + if (!tex.valid) + { + return; + } + if (!tex.transform) + { + // margin = 0.0, let it bleed a bit, shader code becomes easier + // assuming that atlas textures were made with 1-pixel padding + tex.transform = new TextureMatrix(tex, 0.0); + } + tex.transform.update(); + + this.uniforms.mask = tex; + this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite) + .prepend(tex.transform.mapCoord); + this.uniforms.alpha = maskSprite.worldAlpha; + this.uniforms.maskClamp = tex.transform.uClampFrame; + + filterManager.applyFilter(this, input, output); + } +} diff --git a/packages/core/src/filters/spriteMask/spriteMaskFilter.frag b/packages/core/src/filters/spriteMask/spriteMaskFilter.frag new file mode 100644 index 0000000..0e4aef8 --- /dev/null +++ b/packages/core/src/filters/spriteMask/spriteMaskFilter.frag @@ -0,0 +1,23 @@ +varying vec2 vMaskCoord; +varying vec2 vTextureCoord; + +uniform sampler2D uSampler; +uniform sampler2D mask; +uniform float alpha; +uniform vec4 maskClamp; + +void main(void) +{ + float clip = step(3.5, + step(maskClamp.x, vMaskCoord.x) + + step(maskClamp.y, vMaskCoord.y) + + step(vMaskCoord.x, maskClamp.z) + + step(vMaskCoord.y, maskClamp.w)); + + vec4 original = texture2D(uSampler, vTextureCoord); + vec4 masky = texture2D(mask, vMaskCoord); + + original *= (masky.r * masky.a * alpha * clip); + + gl_FragColor = original; +} diff --git a/packages/core/src/filters/spriteMask/spriteMaskFilter.vert b/packages/core/src/filters/spriteMask/spriteMaskFilter.vert new file mode 100644 index 0000000..d23a30c --- /dev/null +++ b/packages/core/src/filters/spriteMask/spriteMaskFilter.vert @@ -0,0 +1,16 @@ +attribute vec2 aVertexPosition; +attribute vec2 aTextureCoord; + +uniform mat3 projectionMatrix; +uniform mat3 otherMatrix; + +varying vec2 vMaskCoord; +varying vec2 vTextureCoord; + +void main(void) +{ + gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); + + vTextureCoord = aTextureCoord; + vMaskCoord = ( otherMatrix * vec3( aTextureCoord, 1.0) ).xy; +} diff --git a/packages/core/src/framebuffer/FrameBuffer.js b/packages/core/src/framebuffer/FrameBuffer.js new file mode 100644 index 0000000..84e9fcc --- /dev/null +++ b/packages/core/src/framebuffer/FrameBuffer.js @@ -0,0 +1,106 @@ +import Texture from '../textures/BaseTexture'; +import { FORMATS, TYPES } from '@pixi/constants'; + +/** + * Frame buffer + * @class + * @memberof PIXI + */ +export default class FrameBuffer +{ + constructor(width, height) + { + this.width = width || 100; + this.height = height || 100; + + this.stencil = false; + this.depth = false; + + this.dirtyId = 0; + this.dirtyFormat = 0; + this.dirtySize = 0; + + this.depthTexture = null; + this.colorTextures = []; + + this.glFrameBuffers = {}; + } + + get colorTexture() + { + return this.colorTextures[0]; + } + + addColorTexture(index, texture) + { + // TODO add some validation to the texture - same width / height etc? + this.colorTextures[index || 0] = texture || new Texture(null, { scaleMode: 0, + resolution: 1, + mipmap: false, + width: this.width, + height: this.height });// || new Texture(); + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + addDepthTexture(texture) + { + /* eslint-disable max-len */ + this.depthTexture = texture || new Texture(null, { scaleMode: 0, + resolution: 1, + width: this.width, + height: this.height, + mipmap: false, + format: FORMATS.DEPTH_COMPONENT, + type: TYPES.UNSIGNED_SHORT });// UNSIGNED_SHORT; + /* eslint-disable max-len */ + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + enableDepth() + { + this.depth = true; + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + enableStencil() + { + this.stencil = true; + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + resize(width, height) + { + if (width === this.width && height === this.height) return; + + this.width = width; + this.height = height; + + this.dirtyId++; + this.dirtySize++; + + for (let i = 0; i < this.colorTextures.length; i++) + { + this.colorTextures[i].setSize(width, height); + } + + if (this.depthTexture) + { + this.depthTexture.setSize(width, height); + } + } +} diff --git a/packages/core/src/framebuffer/FramebufferSystem.js b/packages/core/src/framebuffer/FramebufferSystem.js new file mode 100644 index 0000000..2cc2437 --- /dev/null +++ b/packages/core/src/framebuffer/FramebufferSystem.js @@ -0,0 +1,246 @@ +import System from '../System'; +import { Rectangle } from '@pixi/math'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class FramebufferSystem extends System +{ + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + contextChange() + { + this.gl = this.renderer.gl; + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + this.current = null; + this.viewport = new Rectangle(); + + this.drawBufferExtension = this.renderer.context.extensions.drawBuffers; + } + + bind(framebuffer, frame) + { + const gl = this.gl; + + this.current = framebuffer; + + if (framebuffer) + { + // TODO cacheing layer! + + const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID] || this.initFramebuffer(framebuffer); + + gl.bindFramebuffer(gl.FRAMEBUFFER, fbo.framebuffer); + // makesure all textures are unbound.. + + // now check for updates... + if (fbo.dirtyId !== framebuffer.dirtyId) + { + fbo.dirtyId = framebuffer.dirtyId; + + if (fbo.dirtyFormat !== framebuffer.dirtyFormat) + { + fbo.dirtyFormat = framebuffer.dirtyFormat; + this.updateFramebuffer(framebuffer); + } + else if (fbo.dirtySize !== framebuffer.dirtySize) + { + fbo.dirtySize = framebuffer.dirtySize; + this.resizeFramebuffer(framebuffer); + } + } + + for (let i = 0; i < framebuffer.colorTextures.length; i++) + { + if (framebuffer.colorTextures[i].texturePart) + { + this.renderer.texture.unbind(framebuffer.colorTextures[i].texture); + } + else + { + this.renderer.texture.unbind(framebuffer.colorTextures[i]); + } + } + + if (framebuffer.depthTexture) + { + this.renderer.texture.unbind(framebuffer.depthTexture); + } + + if (frame) + { + this.setViewport(frame.x, frame.y, frame.width, frame.height); + } + else + { + this.setViewport(0, 0, framebuffer.width, framebuffer.height); + } + } + else + { + gl.bindFramebuffer(gl.FRAMEBUFFER, null); + + if (frame) + { + this.setViewport(frame.x, frame.y, frame.width, frame.height); + } + else + { + this.setViewport(0, 0, this.renderer.width, this.renderer.height); + } + } + } + + setViewport(x, y, width, height) + { + const v = this.viewport; + + if (v.width !== width || v.height !== height || v.x !== x || v.y !== y) + { + v.x = x; + v.y = y; + v.width = width; + v.height = height; + + this.gl.viewport(x, y, width, height); + } + } + + get size() + { + if (this.current) + { + // TODO store temp + return { x: 0, y: 0, width: this.current.width, height: this.current.height }; + } + + return { x: 0, y: 0, width: this.renderer.width, height: this.renderer.height }; + } + + clear(r, g, b, a) + { + const gl = this.gl; + + // TODO clear color can be set only one right? + gl.clearColor(r, g, b, a); + gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); + } + + // private functions... + + initFramebuffer(framebuffer) + { + const gl = this.gl; + + // TODO - make this a class? + const fbo = { + framebuffer: gl.createFramebuffer(), + stencil: null, + dirtyId: 0, + dirtyFormat: 0, + dirtySize: 0, + }; + + framebuffer.glFrameBuffers[this.CONTEXT_UID] = fbo; + + return fbo; + } + + resizeFramebuffer(framebuffer) + { + const gl = this.gl; + + if (framebuffer.stencil || framebuffer.depth) + { + gl.bindRenderbuffer(gl.RENDERBUFFER, this.stencil); + gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); + } + } + + updateFramebuffer(framebuffer) + { + const gl = this.gl; + + const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID]; + + // bind the color texture + const colorTextures = framebuffer.colorTextures; + + let count = colorTextures.length; + + if (!this.drawBufferExtension) + { + count = Math.min(count, 1); + } + + const activeTextures = []; + + for (let i = 0; i < count; i++) + { + const texture = framebuffer.colorTextures[i]; + + if (texture.texturePart) + { + this.renderer.texture.bind(texture.texture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.COLOR_ATTACHMENT0 + i, + gl.TEXTURE_CUBE_MAP_NEGATIVE_X + texture.side, + texture.texture._glTextures[this.CONTEXT_UID].texture, + 0); + } + else + { + this.renderer.texture.bind(texture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.COLOR_ATTACHMENT0 + i, + gl.TEXTURE_2D, + texture._glTextures[this.CONTEXT_UID].texture, + 0); + } + + activeTextures.push(gl.COLOR_ATTACHMENT0 + i); + } + + if (this.drawBufferExtension && activeTextures.length > 1) + { + this.drawBufferExtension.drawBuffersWEBGL(activeTextures); + } + + if (framebuffer.depthTexture) + { + const depthTextureExt = this.renderer.context.extensions.depthTexture; + + if (depthTextureExt) + { + const depthTexture = framebuffer.depthTexture; + + this.renderer.texture.bind(depthTexture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.DEPTH_ATTACHMENT, + gl.TEXTURE_2D, + depthTexture._glTextures[this.CONTEXT_UID].texture, + 0); + } + } + + if (framebuffer.stencil || framebuffer.depth) + { + fbo.stencil = gl.createRenderbuffer(); + + gl.bindRenderbuffer(gl.RENDERBUFFER, fbo.stencil); + + // TODO.. this is depth AND stencil? + gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, fbo.stencil); + gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); + // fbo.enableStencil(); + } + } +} diff --git a/packages/core/src/geometry/GLBuffer.js b/packages/core/src/geometry/GLBuffer.js new file mode 100644 index 0000000..0477e8a --- /dev/null +++ b/packages/core/src/geometry/GLBuffer.js @@ -0,0 +1,9 @@ +export default class GLBuffer +{ + constructor(buffer) + { + this.buffer = buffer; + this.updateID = -1; + this.byteLength = -1; + } +} diff --git a/packages/core/src/geometry/GeometrySystem.js b/packages/core/src/geometry/GeometrySystem.js new file mode 100644 index 0000000..959f827 --- /dev/null +++ b/packages/core/src/geometry/GeometrySystem.js @@ -0,0 +1,388 @@ +import System from '../System'; +import GLBuffer from './GLBuffer'; + +const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class GeometrySystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this._activeGeometry = null; + this._activeVao = null; + + this.hasVao = true; + this.hasInstance = true; + } + + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + contextChange() + { + const gl = this.gl = this.renderer.gl; + + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + + // webgl2 + if (!gl.createVertexArray) + { + // webgl 1! + let nativeVaoExtension = this.renderer.context.extensions.vertexArrayObject; + + if (this.renderer.legacy) + { + nativeVaoExtension = null; + } + + if (nativeVaoExtension) + { + gl.createVertexArray = () => + nativeVaoExtension.createVertexArrayOES(); + + gl.bindVertexArray = (vao) => + nativeVaoExtension.bindVertexArrayOES(vao); + + gl.deleteVertexArray = (vao) => + nativeVaoExtension.deleteVertexArrayOES(vao); + } + else + { + this.hasVao = false; + gl.createVertexArray = () => + { + // empty + }; + + gl.bindVertexArray = () => + { + // empty + }; + + gl.deleteVertexArray = () => + { + // empty + }; + } + } + + if (!gl.vertexAttribDivisor) + { + const instanceExt = gl.getExtension('ANGLE_instanced_arrays'); + + if (instanceExt) + { + gl.vertexAttribDivisor = (a, b) => + instanceExt.vertexAttribDivisorANGLE(a, b); + + gl.drawElementsInstanced = (a, b, c, d, e) => + instanceExt.drawElementsInstancedANGLE(a, b, c, d, e); + + gl.drawArraysInstanced = (a, b, c, d) => + instanceExt.drawArraysInstancedANGLE(a, b, c, d); + } + else + { + this.hasInstance = false; + } + } + } + + /** + * Binds geometry so that is can be drawn. Creating a Vao if required + * @private + * @param {PIXI.Geometry} geometry instance of geometry to bind + */ + bind(geometry, shader) + { + shader = shader || this.renderer.shader.shader; + + const gl = this.gl; + + // not sure the best way to address this.. + // currently different shaders require different VAOs for the same geometry + // Still mulling over the best way to solve this one.. + // will likely need to modify the shader attribute locations at run time! + let vaos = geometry.glVertexArrayObjects[this.CONTEXT_UID]; + + if (!vaos) + { + geometry.glVertexArrayObjects[this.CONTEXT_UID] = vaos = {}; + } + + const vao = vaos[shader.program.id] || this.initGeometryVao(geometry, shader.program); + + this._activeGeometry = geometry; + + if (this._activeVao !== vao) + { + this._activeVao = vao; + + if (this.hasVao) + { + gl.bindVertexArray(vao); + } + else + { + this.activateVao(geometry, shader.program); + } + } + + // TODO - optimise later! + // don't need to loop through if nothing changed! + // maybe look to add an 'autoupdate' to geometry? + this.updateBuffers(); + } + + reset() + { + this.unbind(); + } + + updateBuffers() + { + const geometry = this._activeGeometry; + const gl = this.gl; + + for (let i = 0; i < geometry.buffers.length; i++) + { + const buffer = geometry.buffers[i]; + + const glBuffer = buffer._glBuffers[this.CONTEXT_UID]; + + if (buffer._updateID !== glBuffer.updateID) + { + glBuffer.updateID = buffer._updateID; + + // TODO can cache this on buffer! maybe added a getter / setter? + const type = buffer.index ? gl.ELEMENT_ARRAY_BUFFER : gl.ARRAY_BUFFER; + const drawType = buffer.static ? gl.STATIC_DRAW : gl.DYNAMIC_DRAW; + + gl.bindBuffer(type, glBuffer.buffer); + + if (glBuffer.byteLength >= buffer.data.byteLength) + { + // offset is always zero for now! + gl.bufferSubData(type, 0, buffer.data); + } + else + { + gl.bufferData(type, buffer.data, drawType); + } + } + } + } + + checkCompatability(geometry, program) + { + // geometry must have at least all the attributes that the shader requires. + const geometryAttributes = geometry.attributes; + const shaderAttributes = program.attributeData; + + for (const j in shaderAttributes) + { + if (!geometryAttributes[j]) + { + throw new Error(`shader and geometry incompatible, geometry missing the "${j}" attribute`); + } + } + } + + /** + * Creates a Vao with the same structure as the geometry and stores it on the geometry. + * @private + * @param {PIXI.Geometry} geometry instance of geometry to to generate Vao for + */ + initGeometryVao(geometry, program) + { + this.checkCompatability(geometry, program); + + const gl = this.gl; + const CONTEXT_UID = this.CONTEXT_UID; + const buffers = geometry.buffers; + const attributes = geometry.attributes; + + const tempStride = {}; + const tempStart = {}; + + for (const j in buffers) + { + tempStride[j] = 0; + tempStart[j] = 0; + } + + for (const j in attributes) + { + if (!attributes[j].size && program.attributeData[j]) + { + attributes[j].size = program.attributeData[j].size; + } + + tempStride[attributes[j].buffer] += attributes[j].size * byteSizeMap[attributes[j].type]; + } + + for (const j in attributes) + { + const attribute = attributes[j]; + const attribSize = attribute.size; + + if (attribute.stride === undefined) + { + if (tempStride[attribute.buffer] === attribSize * byteSizeMap[attribute.type]) + { + attribute.stride = 0; + } + else + { + attribute.stride = tempStride[attribute.buffer]; + } + } + + if (attribute.start === undefined) + { + attribute.start = tempStart[attribute.buffer]; + + tempStart[attribute.buffer] += attribSize * byteSizeMap[attribute.type]; + } + } + + // first update - and create the buffers! + // only create a gl buffer if it actually gets + for (let i = 0; i < buffers.length; i++) + { + const buffer = buffers[i]; + + if (!buffer._glBuffers[CONTEXT_UID]) + { + buffer._glBuffers[CONTEXT_UID] = new GLBuffer(gl.createBuffer()); + } + } + + // TODO - maybe make this a data object? + // lets wait to see if we need to first! + const vao = gl.createVertexArray(); + + gl.bindVertexArray(vao); + + this.activateVao(geometry, program); + + gl.bindVertexArray(null); + + // add it to the cache! + geometry.glVertexArrayObjects[this.CONTEXT_UID][program.id] = vao; + + return vao; + } + + activateVao(geometry, program) + { + const gl = this.gl; + const CONTEXT_UID = this.CONTEXT_UID; + const buffers = geometry.buffers; + const attributes = geometry.attributes; + + if (geometry.indexBuffer) + { + // first update the index buffer if we have one.. + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, geometry.indexBuffer._glBuffers[CONTEXT_UID].buffer); + } + + let lastBuffer = null; + + // add a new one! + for (const j in attributes) + { + const attribute = attributes[j]; + const buffer = buffers[attribute.buffer]; + const glBuffer = buffer._glBuffers[CONTEXT_UID]; + + if (program.attributeData[j]) + { + if (lastBuffer !== glBuffer) + { + gl.bindBuffer(gl.ARRAY_BUFFER, glBuffer.buffer); + + lastBuffer = glBuffer; + } + + const location = program.attributeData[j].location; + + // TODO introduce state again + // we can optimise this for older devices that have no VAOs + gl.enableVertexAttribArray(location); + + gl.vertexAttribPointer(location, + attribute.size, + attribute.type || gl.FLOAT, + attribute.normalized, + attribute.stride, + attribute.start); + + if (attribute.instance) + { + // TODO calculate instance count based of this... + if (this.hasInstance) + { + gl.vertexAttribDivisor(location, 1); + } + else + { + throw new Error('geometry error, GPU Instancing is not supported on this device'); + } + } + } + } + } + + draw(type, size, start, instanceCount) + { + const gl = this.gl; + const geometry = this._activeGeometry; + + // TODO.. this should not change so maybe cache the function? + + if (geometry.indexBuffer) + { + if (geometry.instanced) + { + /* eslint-disable max-len */ + gl.drawElementsInstanced(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2, instanceCount || 1); + /* eslint-enable max-len */ + } + else + { + gl.drawElements(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2); + } + } + else + if (geometry.instanced) + { + // TODO need a better way to calculate size.. + gl.drawArraysInstanced(type, start, size || geometry.getSize(), instanceCount || 1); + } + else + { + gl.drawArrays(type, start, size || geometry.getSize()); + } + + return this; + } + + unbind() + { + this.gl.bindVertexArray(null); + this._activeVao = null; + this._activeGeometry = null; + } +} diff --git a/packages/core/src/geometry/utils/setVertexAttribArrays.js b/packages/core/src/geometry/utils/setVertexAttribArrays.js new file mode 100644 index 0000000..23b6959 --- /dev/null +++ b/packages/core/src/geometry/utils/setVertexAttribArrays.js @@ -0,0 +1,54 @@ +// var GL_MAP = {}; + +/** + * @param gl {WebGLRenderingContext} The current WebGL context + * @param attribs {*} + * @param state {*} + */ +export default function setVertexAttribArrays(gl, attribs, state) +{ + let i; + + if (state) + { + const tempAttribState = state.tempAttribState; + const attribState = state.attribState; + + for (i = 0; i < tempAttribState.length; i++) + { + tempAttribState[i] = false; + } + + // set the new attribs + for (i = 0; i < attribs.length; i++) + { + tempAttribState[attribs[i].attribute.location] = true; + } + + for (i = 0; i < attribState.length; i++) + { + if (attribState[i] !== tempAttribState[i]) + { + attribState[i] = tempAttribState[i]; + + if (state.attribState[i]) + { + gl.enableVertexAttribArray(i); + } + else + { + gl.disableVertexAttribArray(i); + } + } + } + } + else + { + for (i = 0; i < attribs.length; i++) + { + const attrib = attribs[i]; + + gl.enableVertexAttribArray(attrib.attribute.location); + } + } +} diff --git a/packages/core/src/index.js b/packages/core/src/index.js index 58c456a..fd1f547 100644 --- a/packages/core/src/index.js +++ b/packages/core/src/index.js @@ -1,30 +1,30 @@ import * as resources from './textures/resources'; -import * as systems from './renderers/systems'; +import * as systems from './systems'; -export { resources }; export { systems }; +export { resources }; -export { default as Renderer } from './renderers/Renderer'; -export { default as AbstractRenderer } from './renderers/AbstractRenderer'; -export { default as FrameBuffer } from './textures/FrameBuffer'; +export { default as System } from './System'; +export { default as Renderer } from './Renderer'; +export { default as AbstractRenderer } from './AbstractRenderer'; +export { default as FrameBuffer } from './framebuffer/FrameBuffer'; export { default as CubeTexture } from './textures/CubeTexture'; export { default as BaseTexture } from './textures/BaseTexture'; export { default as Texture } from './textures/Texture'; export { default as TextureMatrix } from './textures/TextureMatrix'; -export { default as RenderTexture } from './textures/RenderTexture'; -export { default as BaseRenderTexture } from './textures/BaseRenderTexture'; +export { default as RenderTexture } from './renderTexture/RenderTexture'; +export { default as BaseRenderTexture } from './renderTexture/BaseRenderTexture'; export { default as TextureUvs } from './textures/TextureUvs'; -export { default as State } from './renderers/State'; -export { default as ObjectRenderer } from './renderers/utils/ObjectRenderer'; -export { default as Quad } from './renderers/utils/Quad'; -export { default as QuadUv } from './renderers/utils/QuadUv'; -export { default as checkMaxIfStatmentsInShader } from './renderers/utils/checkMaxIfStatmentsInShader'; +export { default as State } from './state/State'; +export { default as ObjectRenderer } from './batch/ObjectRenderer'; +export { default as Quad } from './utils/Quad'; +export { default as QuadUv } from './utils/QuadUv'; +export { default as checkMaxIfStatmentsInShader } from './shader/utils/checkMaxIfStatmentsInShader'; export { default as Shader } from './shader/Shader'; export { default as Program } from './shader/Program'; export { default as UniformGroup } from './shader/UniformGroup'; -export { default as SpriteMaskFilter } from './renderers/filters/spriteMask/SpriteMaskFilter'; -export { default as Filter } from './renderers/filters/Filter'; +export { default as SpriteMaskFilter } from './filters/spriteMask/SpriteMaskFilter'; +export { default as Filter } from './filters/Filter'; export { default as Attribute } from './geometry/Attribute'; export { default as Buffer } from './geometry/Buffer'; export { default as Geometry } from './geometry/Geometry'; - diff --git a/packages/core/src/mask/MaskSystem.js b/packages/core/src/mask/MaskSystem.js new file mode 100644 index 0000000..99174c2 --- /dev/null +++ b/packages/core/src/mask/MaskSystem.js @@ -0,0 +1,199 @@ +import System from '../System'; +import AlphaMaskFilter from '../filters/spriteMask/SpriteMaskFilter'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class MaskSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + // TODO - we don't need both! + this.scissor = false; + this.scissorData = null; + this.scissorRenderTarget = null; + + this.enableScissor = false; + + this.alphaMaskPool = []; + this.alphaMaskIndex = 0; + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.DisplayObject} target - Display Object to push the mask to + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + push(target, maskData) + { + // TODO the root check means scissor rect will not + // be used on render textures more info here: + // https://github.com/pixijs/pixi.js/pull/3545 + + if (maskData.texture) + { + this.pushSpriteMask(target, maskData); + } + else if (this.enableScissor + && !this.scissor + && this.renderer._activeRenderTarget.root + && !this.renderer.stencil.stencilMaskStack.length + && maskData.isFastRect()) + { + const matrix = maskData.worldTransform; + + let rot = Math.atan2(matrix.b, matrix.a); + + // use the nearest degree! + rot = Math.round(rot * (180 / Math.PI)); + + if (rot % 90) + { + this.pushStencilMask(maskData); + } + else + { + this.pushScissorMask(target, maskData); + } + } + else + { + this.pushStencilMask(maskData); + } + } + + /** + * Removes the last mask from the mask stack and doesn't return it. + * + * @param {PIXI.DisplayObject} target - Display Object to pop the mask from + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + pop(target, maskData) + { + if (maskData.texture) + { + this.popSpriteMask(target, maskData); + } + else if (this.enableScissor && !this.renderer.stencil.stencilMaskStack.length) + { + this.popScissorMask(target, maskData); + } + else + { + this.popStencilMask(target, maskData); + } + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.RenderTarget} target - Display Object to push the sprite mask to + * @param {PIXI.Sprite} maskData - Sprite to be used as the mask + */ + pushSpriteMask(target, maskData) + { + let alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; + + if (!alphaMaskFilter) + { + alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; + } + + alphaMaskFilter[0].resolution = this.renderer.resolution; + alphaMaskFilter[0].maskSprite = maskData; + + // TODO - may cause issues! + target.filterArea = maskData.getBounds(true); + + this.renderer.filterSystem.pushFilter(target, alphaMaskFilter); + + this.alphaMaskIndex++; + } + + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popSpriteMask() + { + this.renderer.filterSystem.popFilter(); + this.alphaMaskIndex--; + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + pushStencilMask(maskData) + { + this.renderer.batch.flush(); + this.renderer.stencil.pushStencil(maskData); + } + + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popStencilMask() + { + // this.renderer.currentRenderer.stop(); + this.renderer.stencil.popStencil(); + } + + /** + * + * @param {PIXI.DisplayObject} target - Display Object to push the mask to + * @param {PIXI.Graphics} maskData - The masking data. + */ + pushScissorMask(target, maskData) + { + maskData.renderable = true; + + const renderTarget = this.renderer._activeRenderTarget; + + const bounds = maskData.getBounds(); + + bounds.fit(renderTarget.size); + maskData.renderable = false; + + this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); + + const resolution = this.renderer.resolution; + + this.renderer.gl.scissor( + bounds.x * resolution, + (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, + bounds.width * resolution, + bounds.height * resolution + ); + + this.scissorRenderTarget = renderTarget; + this.scissorData = maskData; + this.scissor = true; + } + + /** + * + * + */ + popScissorMask() + { + this.scissorRenderTarget = null; + this.scissorData = null; + this.scissor = false; + + // must be scissor! + const gl = this.renderer.gl; + + gl.disable(gl.SCISSOR_TEST); + } +} diff --git a/packages/core/src/mask/StencilSystem.js b/packages/core/src/mask/StencilSystem.js new file mode 100644 index 0000000..62796a5 --- /dev/null +++ b/packages/core/src/mask/StencilSystem.js @@ -0,0 +1,133 @@ +import System from '../System'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class StencilSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + this.stencilMaskStack = []; + } + + /** + * Changes the mask stack that is used by this System. + * + * @param {PIXI.Graphics[]} stencilMaskStack - The mask stack + */ + setMaskStack(stencilMaskStack) + { + const gl = this.renderer.gl; + + if (stencilMaskStack.length !== this.stencilMaskStack.length) + { + if (stencilMaskStack.length === 0) + { + gl.disable(gl.STENCIL_TEST); + } + else + { + gl.enable(gl.STENCIL_TEST); + } + } + + this.stencilMaskStack = stencilMaskStack; + } + + /** + * Applies the Mask and adds it to the current stencil stack. @alvin + * + * @param {PIXI.Graphics} graphics - The mask + */ + pushStencil(graphics) + { + this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); + + // this.renderer._activeRenderTarget.attachStencilBuffer(); + + const gl = this.renderer.gl; + const prevMaskCount = this.stencilMaskStack.length; + + if (prevMaskCount === 0) + { + gl.enable(gl.STENCIL_TEST); + } + + this.stencilMaskStack.push(graphics); + + // Increment the refference stencil value where the new mask overlaps with the old ones. + gl.colorMask(false, false, false, false); + gl.stencilFunc(gl.EQUAL, prevMaskCount, this._getBitwiseMask()); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.INCR); + this.renderer.plugins.graphics.render(graphics); + + this._useCurrent(); + } + + /** + * Removes the last mask from the stencil stack. @alvin + */ + popStencil() + { + this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); + + const gl = this.renderer.gl; + const graphics = this.stencilMaskStack.pop(); + + if (this.stencilMaskStack.length === 0) + { + // the stack is empty! + gl.disable(gl.STENCIL_TEST); + gl.clear(gl.STENCIL_BUFFER_BIT); + gl.clearStencil(0); + } + else + { + // Decrement the refference stencil value where the popped mask overlaps with the other ones + gl.colorMask(false, false, false, false); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.DECR); + this.renderer.plugins.graphics.render(graphics); + + this._useCurrent(); + } + } + + /** + * Setup renderer to use the current stencil data. + */ + _useCurrent() + { + const gl = this.renderer.gl; + + gl.colorMask(true, true, true, true); + gl.stencilFunc(gl.EQUAL, this.stencilMaskStack.length, this._getBitwiseMask()); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP); + } + + /** + * Fill 1s equal to the number of acitve stencil masks. + * + * @return {number} The bitwise mask. + */ + _getBitwiseMask() + { + return (1 << this.stencilMaskStack.length) - 1; + } + + /** + * Destroys the mask stack. + * + */ + destroy() + { + super.destroy(this); + + this.stencilMaskStack = null; + } +} diff --git a/packages/core/src/projection/ProjectionSystem.js b/packages/core/src/projection/ProjectionSystem.js new file mode 100644 index 0000000..6e63b73 --- /dev/null +++ b/packages/core/src/projection/ProjectionSystem.js @@ -0,0 +1,73 @@ +import System from '../System'; +import { Matrix } from '@pixi/math'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class ProjectionSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.projectionMatrix = new Matrix(); + } + + update(destinationFrame, sourceFrame, resolution, root) + { + this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; + this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; + + this.calculateProjection(this.destinationFrame, this.sourceFrame, resolution, root); + + this.renderer.globalUniforms.uniforms.projectionMatrix = this.projectionMatrix; + this.renderer.globalUniforms.update(); + } + + /** + * Updates the projection matrix based on a projection frame (which is a rectangle) + * + * @param {Rectangle} destinationFrame - The destination frame. + * @param {Rectangle} sourceFrame - The source frame. + */ + calculateProjection(destinationFrame, sourceFrame, resolution, root) + { + const pm = this.projectionMatrix; + + // I don't think we will need this line.. + // pm.identity(); + + if (!root) + { + pm.a = (1 / destinationFrame.width * 2) * resolution; + pm.d = (1 / destinationFrame.height * 2) * resolution; + + pm.tx = -1 - (sourceFrame.x * pm.a); + pm.ty = -1 - (sourceFrame.y * pm.d); + } + else + { + pm.a = (1 / destinationFrame.width * 2) * resolution; + pm.d = (-1 / destinationFrame.height * 2) * resolution; + + pm.tx = -1 - (sourceFrame.x * pm.a); + pm.ty = 1 - (sourceFrame.y * pm.d); + } + } + + /** + * Sets the transform of the active render target to the given matrix + * + * @param {PIXI.Matrix} matrix - The transformation matrix + */ + setTransform()// matrix) + { + // this._activeRenderTarget.transform = matrix; + } +} diff --git a/packages/core/src/renderTexture/BaseRenderTexture.js b/packages/core/src/renderTexture/BaseRenderTexture.js new file mode 100644 index 0000000..88eb83b --- /dev/null +++ b/packages/core/src/renderTexture/BaseRenderTexture.js @@ -0,0 +1,137 @@ +import BaseTexture from '../textures/BaseTexture'; +import FrameBuffer from '../framebuffer/FrameBuffer'; + +/** + * A BaseRenderTexture is a special texture that allows any PixiJS display object to be rendered to it. + * + * __Hint__: All DisplayObjects (i.e. Sprites) that render to a BaseRenderTexture should be preloaded + * otherwise black rectangles will be drawn instead. + * + * A BaseRenderTexture takes a snapshot of any Display Object given to its render method. The position + * and rotation of the given Display Objects is ignored. For example: + * + * ```js + * let renderer = PIXI.autoDetectRenderer(1024, 1024, { view: canvas, ratio: 1 }); + * let baseRenderTexture = new PIXI.BaseRenderTexture(renderer, 800, 600); + * let sprite = PIXI.Sprite.fromImage("spinObj_01.png"); + * + * sprite.position.x = 800/2; + * sprite.position.y = 600/2; + * sprite.anchor.x = 0.5; + * sprite.anchor.y = 0.5; + * + * baseRenderTexture.render(sprite); + * ``` + * + * The Sprite in this case will be rendered using its local transform. To render this sprite at 0,0 + * you can clear the transform + * + * ```js + * + * sprite.setTransform() + * + * let baseRenderTexture = new PIXI.BaseRenderTexture(100, 100); + * let renderTexture = new PIXI.RenderTexture(baseRenderTexture); + * + * renderer.render(sprite, renderTexture); // Renders to center of RenderTexture + * ``` + * + * @class + * @extends PIXI.BaseTexture + * @memberof PIXI + */ +export default class BaseRenderTexture extends BaseTexture +{ + /** + * @param {object} [options] + * @param {number} [options.width=100] - The width of the base render texture + * @param {number} [options.height=100] - The height of the base render texture + * @param {PIXI.SCALE_MODES} [options.scaleMode] - See {@link PIXI.SCALE_MODES} for possible values + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the texture being generated + */ + constructor(options) + { + if (typeof options === 'number') + { + /* eslint-disable prefer-rest-params */ + // Backward compatibility of signature + const width = arguments[0]; + const height = arguments[1]; + const scaleMode = arguments[2]; + const resolution = arguments[3]; + + options = { width, height, scaleMode, resolution }; + /* eslint-enable prefer-rest-params */ + } + + super(null, options); + + const { width, height } = options || {}; + + // Set defaults + this.mipmap = false; + this.width = Math.ceil(width) || 100; + this.height = Math.ceil(height) || 100; + this.valid = true; + + /** + * A map of renderer IDs to webgl renderTargets + * + * @private + * @member {object} + */ + // this._glRenderTargets = {}; + + /** + * A reference to the canvas render target (we only need one as this can be shared across renderers) + * + * @private + * @member {object} + */ + this._canvasRenderTarget = null; + + this.clearColor = [0, 0, 0, 0]; + + this.frameBuffer = new FrameBuffer(width * this.resolution, height * this.resolution) + .addColorTexture(0, this); + + // TODO - could this be added the systems? + + /** + * The data structure for the stencil masks + * + * @member {PIXI.Graphics[]} + */ + this.stencilMaskStack = []; + + /** + * The data structure for the filters + * + * @member {PIXI.Graphics[]} + */ + this.filterStack = [{}]; + } + + /** + * Resizes the BaseRenderTexture. + * + * @param {number} width - The width to resize to. + * @param {number} height - The height to resize to. + */ + resize(width, height) + { + width = Math.ceil(width); + height = Math.ceil(height); + this.frameBuffer.resize(width * this.resolution, height * this.resolution); + } + + /** + * Destroys this texture + * + */ + destroy() + { + super.destroy(true); + this.renderer = null; + } +} diff --git a/packages/core/src/renderTexture/RenderTexture.js b/packages/core/src/renderTexture/RenderTexture.js new file mode 100644 index 0000000..f1ecf1f --- /dev/null +++ b/packages/core/src/renderTexture/RenderTexture.js @@ -0,0 +1,153 @@ +import BaseRenderTexture from './BaseRenderTexture'; +import Texture from '../textures/Texture'; + +/** + * A RenderTexture is a special texture that allows any PixiJS display object to be rendered to it. + * + * __Hint__: All DisplayObjects (i.e. Sprites) that render to a RenderTexture should be preloaded + * otherwise black rectangles will be drawn instead. + * + * A RenderTexture takes a snapshot of any Display Object given to its render method. For example: + * + * ```js + * let renderer = PIXI.autoDetectRenderer(1024, 1024, { view: canvas, ratio: 1 }); + * let renderTexture = PIXI.RenderTexture.create(800, 600); + * let sprite = PIXI.Sprite.fromImage("spinObj_01.png"); + * + * sprite.position.x = 800/2; + * sprite.position.y = 600/2; + * sprite.anchor.x = 0.5; + * sprite.anchor.y = 0.5; + * + * renderer.render(sprite, renderTexture); + * ``` + * + * The Sprite in this case will be rendered using its local transform. To render this sprite at 0,0 + * you can clear the transform + * + * ```js + * + * sprite.setTransform() + * + * let renderTexture = new PIXI.RenderTexture.create(100, 100); + * + * renderer.render(sprite, renderTexture); // Renders to center of RenderTexture + * ``` + * + * @class + * @extends PIXI.Texture + * @memberof PIXI + */ +export default class RenderTexture extends Texture +{ + /** + * @param {PIXI.BaseRenderTexture} baseRenderTexture - The renderer used for this RenderTexture + * @param {PIXI.Rectangle} [frame] - The rectangle frame of the texture to show + */ + constructor(baseRenderTexture, frame) + { + // support for legacy.. + let _legacyRenderer = null; + + if (!(baseRenderTexture instanceof BaseRenderTexture)) + { + /* eslint-disable prefer-rest-params, no-console */ + const width = arguments[1]; + const height = arguments[2]; + const scaleMode = arguments[3]; + const resolution = arguments[4]; + + // we have an old render texture.. + console.warn(`Please use RenderTexture.create(${width}, ${height}) instead of the ctor directly.`); + _legacyRenderer = arguments[0]; + /* eslint-enable prefer-rest-params, no-console */ + + frame = null; + baseRenderTexture = new BaseRenderTexture({ + width, + height, + scaleMode, + resolution, + }); + } + + /** + * The base texture object that this texture uses + * + * @member {BaseTexture} + */ + super(baseRenderTexture, frame); + + this.legacyRenderer = _legacyRenderer; + + /** + * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. + * + * @member {boolean} + */ + this.valid = true; + + /** + * FilterSystem temporary storage + * @private + * @member {PIXI.Rectangle} + */ + this.filterFrame = null; + + this._updateUvs(); + } + + /** + * Resizes the RenderTexture. + * + * @param {number} width - The width to resize to. + * @param {number} height - The height to resize to. + * @param {boolean} [resizeBaseTexture=true] - Should the baseTexture.width and height values be resized as well? + */ + resize(width, height, resizeBaseTexture = true) + { + width = Math.ceil(width); + height = Math.ceil(height); + + // TODO - could be not required.. + this.valid = (width > 0 && height > 0); + + this._frame.width = this.orig.width = width; + this._frame.height = this.orig.height = height; + + if (resizeBaseTexture) + { + this.baseTexture.resize(width, height); + } + + this._updateUvs(); + } + + /** + * A short hand way of creating a render texture. + * + * @param {object} [options] - Options + * @param {number} [options.width=100] - The width of the render texture + * @param {number} [options.height=100] - The height of the render texture + * @param {number} [options.scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the texture being generated + * @return {PIXI.RenderTexture} The new render texture + */ + static create(options) + { + // fallback, old-style: create(width, height, scaleMode, resolution) + if (typeof options === 'number') + { + /* eslint-disable prefer-rest-params */ + options = { + width: options, + height: arguments[1], + scaleMode: arguments[2], + resolution: arguments[3], + }; + /* eslint-enable prefer-rest-params */ + } + + return new RenderTexture(new BaseRenderTexture(options)); + } +} diff --git a/packages/core/src/renderTexture/RenderTextureSystem.js b/packages/core/src/renderTexture/RenderTextureSystem.js new file mode 100644 index 0000000..8d0ca61 --- /dev/null +++ b/packages/core/src/renderTexture/RenderTextureSystem.js @@ -0,0 +1,115 @@ +import System from '../System'; +import { Rectangle } from '@pixi/math'; + +const tempRect = new Rectangle(); + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class RenderTextureSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.clearColor = renderer._backgroundColorRgba; + + // TODO moe this property somewhere else! + this.defaultMaskStack = []; + this.defaultFilterStack = [{}]; + + // empty render texture? + this.renderTexture = null; + + this.destinationFrame = new Rectangle(); + } + + bind(renderTexture, sourceFrame, destinationFrame) + { + // TODO - do we want this?? + if (this.renderTexture === renderTexture) return; + this.renderTexture = renderTexture; + + const renderer = this.renderer; + + if (renderTexture) + { + const baseTexture = renderTexture.baseTexture; + + if (!destinationFrame) + { + tempRect.width = baseTexture.realWidth; + tempRect.height = baseTexture.realHeight; + + destinationFrame = tempRect; + } + + if (!sourceFrame) + { + sourceFrame = destinationFrame; + } + + this.renderer.framebuffer.bind(baseTexture.frameBuffer, destinationFrame); + + this.renderer.projection.update(destinationFrame, sourceFrame, baseTexture.resolution, false); + this.renderer.stencil.setMaskStack(baseTexture.stencilMaskStack); + } + else + { + // TODO these validation checks happen deeper down.. + // thing they can be avoided.. + if (!destinationFrame) + { + tempRect.width = renderer.width; + tempRect.height = renderer.height; + + destinationFrame = tempRect; + } + + if (!sourceFrame) + { + sourceFrame = destinationFrame; + } + + renderer.framebuffer.bind(null, destinationFrame); + + // TODO store this.. + this.renderer.projection.update(destinationFrame, sourceFrame, this.renderer.resolution, true); + this.renderer.stencil.setMaskStack(this.defaultMaskStack); + } + + this.destinationFrame.copyFrom(destinationFrame); + } + + /** + * Erases the render texture and fills the drawing area with a colour + * + * @param {number} [clearColor] - The colour + * @return {PIXI.Renderer} Returns itself. + */ + clear(clearColor) + { + if (this.renderTexture) + { + clearColor = clearColor || this.renderTexture.baseTexture.clearColor; + } + else + { + clearColor = clearColor || this.clearColor; + } + + this.renderer.framebuffer.clear(clearColor[0], clearColor[1], clearColor[2], clearColor[3]); + } + + resize()// screenWidth, screenHeight) + { + // resize the root only! + this.bind(null); + } +} diff --git a/packages/core/src/renderers/AbstractRenderer.js b/packages/core/src/renderers/AbstractRenderer.js deleted file mode 100644 index 928670f..0000000 --- a/packages/core/src/renderers/AbstractRenderer.js +++ /dev/null @@ -1,353 +0,0 @@ -import { hex2string, hex2rgb } from '@pixi/utils'; -import { Matrix, Rectangle } from '@pixi/math'; -import { RENDERER_TYPE } from '@pixi/constants'; -import { settings } from '@pixi/settings'; -import { Container } from '@pixi/display'; -import RenderTexture from '../textures/RenderTexture'; -import EventEmitter from 'eventemitter3'; - -const tempMatrix = new Matrix(); - -/** - * 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 EventEmitter - * @memberof PIXI - */ -export default class AbstractRenderer extends EventEmitter -{ - // eslint-disable-next-line valid-jsdoc - /** - * @param {string} system - The name of the system this renderer is for. - * @param {object} [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.transparent=false] - If the render view is transparent, default false - * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false - * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) - * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. The - * resolution of the renderer retina would be 2. - * @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 {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when rendering, - * stopping pixel interpolation. - */ - constructor(system, options, arg2, arg3) - { - super(); - - // Support for constructor(system, screenWidth, screenHeight, options) - if (typeof options === 'number') - { - options = Object.assign({ - width: options, - height: arg2 || settings.RENDER_OPTIONS.height, - }, arg3); - } - - // 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 = RENDERER_TYPE.UNKNOWN; - - /** - * Measurements of the screen. (0, 0, screenWidth, screenHeight) - * - * Its safe to use as filterArea or hitArea for 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 1 - */ - this.resolution = options.resolution || settings.RESOLUTION; - - /** - * Whether the render view is transparent - * - * @member {boolean} - */ - this.transparent = options.transparent; - - /** - * Whether css dimensions of canvas view should be resized to screen dimensions automatically - * - * @member {boolean} - */ - this.autoResize = options.autoResize || false; - - /** - * Tracks the blend modes useful for this renderer. - * - * @member {object} - */ - this.blendModes = null; - - /** - * 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; - - /** - * If true PixiJS will Math.floor() x/y values when rendering, stopping pixel interpolation. - * Handy for crisp pixel art and speed on legacy devices. - * - * @member {boolean} - */ - this.roundPixels = options.roundPixels; - - /** - * The background color as a number. - * - * @member {number} - * @private - */ - this._backgroundColor = 0x000000; - - /** - * The background color as an [R, G, B] array. - * - * @member {number[]} - * @private - */ - this._backgroundColorRgba = [0, 0, 0, 0]; - - /** - * The background color as a string. - * - * @member {string} - * @private - */ - this._backgroundColorString = '#000000'; - - this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter - - /** - * This temporary display object used as the parent of the currently being rendered item - * - * @member {PIXI.DisplayObject} - * @private - */ - this._tempDisplayObjectParent = new Container(); - - /** - * The last root object that the renderer tried to render. - * - * @member {PIXI.DisplayObject} - * @private - */ - this._lastObjectRendered = this._tempDisplayObjectParent; - - /** - * Collection of plugins. - * @readonly - * @member {object} - */ - this.plugins = {}; - } - - /** - * Initialize the plugins. - * - * @protected - * @param {object} staticMap - The dictionary of staticly saved plugins. - */ - initPlugins(staticMap) - { - 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() - { - return this.view.width; - } - - /** - * Same as view.height, actual number of pixels in the canvas by vertical - * - * @member {number} - * @readonly - * @default 600 - */ - get height() - { - return this.view.height; - } - - /** - * Resizes the screen and canvas to the specified width and height - * Canvas dimensions are multiplied by resolution - * - * @param {number} screenWidth - the new width of the screen - * @param {number} screenHeight - the new height of the screen - */ - resize(screenWidth, screenHeight) - { - this.screen.width = screenWidth; - this.screen.height = screenHeight; - - this.view.width = screenWidth * this.resolution; - this.view.height = screenHeight * this.resolution; - - if (this.autoResize) - { - this.view.style.width = `${screenWidth}px`; - this.view.style.height = `${screenHeight}px`; - } - } - - /** - * 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 {PIXI.DisplayObject} displayObject - The displayObject the object will be generated from - * @param {number} scaleMode - Should be one of the scaleMode consts - * @param {number} resolution - The resolution / device pixel ratio of the texture being generated - * @param {PIXI.Rectangle} [region] - The region of the displayObject, that shall be rendered, - * if no region is specified, defaults to the local bounds of the displayObject. - * @return {PIXI.Texture} a texture of the graphics object - */ - generateTexture(displayObject, scaleMode, resolution, region) - { - region = region || displayObject.getLocalBounds(); - - // 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(region.width | 0, region.height | 0, scaleMode, resolution); - - tempMatrix.tx = -region.x; - tempMatrix.ty = -region.y; - - this.render(displayObject, renderTexture, false, tempMatrix, true); - - return renderTexture; - } - - /** - * Removes everything from the renderer and optionally removes the Canvas DOM element. - * - * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. - */ - destroy(removeView) - { - 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); - } - - this.plugins = null; - - this.type = RENDERER_TYPE.UNKNOWN; - - this.view = null; - - this.screen = null; - - this.resolution = 0; - - this.transparent = false; - - this.autoResize = false; - - this.blendModes = null; - - this.options = null; - - this.preserveDrawingBuffer = false; - this.clearBeforeRender = false; - - this.roundPixels = false; - - this._backgroundColor = 0; - this._backgroundColorRgba = null; - this._backgroundColorString = null; - - this._tempDisplayObjectParent = null; - this._lastObjectRendered = null; - } - - /** - * The background color to fill if not transparent - * - * @member {number} - */ - get backgroundColor() - { - return this._backgroundColor; - } - - set backgroundColor(value) // eslint-disable-line require-jsdoc - { - this._backgroundColor = value; - this._backgroundColorString = hex2string(value); - hex2rgb(value, this._backgroundColorRgba); - } -} diff --git a/packages/core/src/renderers/Renderer.js b/packages/core/src/renderers/Renderer.js deleted file mode 100644 index a2020b1..0000000 --- a/packages/core/src/renderers/Renderer.js +++ /dev/null @@ -1,329 +0,0 @@ -import AbstractRenderer from './AbstractRenderer'; -import { sayHello } from '@pixi/utils'; -import MaskSystem from './systems/MaskSystem'; -import StencilSystem from './systems/StencilSystem'; -import FilterSystem from './systems/filter/FilterSystem'; -import FramebufferSystem from './systems/FramebufferSystem'; -import RenderTextureSystem from './systems/RenderTextureSystem'; -import TextureSystem from './systems/textures/TextureSystem'; -import ProjectionSystem from './systems/ProjectionSystem'; -import StateSystem from './systems/StateSystem'; -import GeometrySystem from './systems/geometry/GeometrySystem'; -import ShaderSystem from './systems/shader/ShaderSystem'; -import ContextSystem from './systems/ContextSystem'; -import BatchSystem from './systems/BatchSystem'; -import TextureGCSystem from './systems/textures/TextureGCSystem'; -import { RENDERER_TYPE } from '@pixi/constants'; -import UniformGroup from '../shader/UniformGroup'; -import { Matrix } from '@pixi/math'; -import Runner from 'mini-runner'; - -/** - * 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 Render works by automatically managing webGLBatchs. - * 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 :) - * - * @class - * @memberof PIXI - * @extends PIXI.AbstractRenderer - */ -export default class Renderer extends AbstractRenderer -{ - // eslint-disable-next-line valid-jsdoc - /** - * - * @param {object} [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.transparent=false] - If the render view is transparent, default false - * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false - * @param {boolean} [options.antialias=false] - sets antialias. If not available natively then FXAA - * antialiasing is used - * @param {boolean} [options.forceFXAA=false] - forces FXAA antialiasing to be used over native. - * FXAA is faster, but may not always look as great - * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. - * The resolution of the renderer retina would be 2. - * @param {boolean} [options.clearBeforeRender=true] - This sets if the renderer will clear - * the canvas or not before the new render pass. If you wish to set this to false, you *must* set - * preserveDrawingBuffer to `true`. - * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, - * enable this if you need to call toDataUrl on the webgl context. - * @param {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when - * rendering, stopping pixel interpolation. - * @param {number} [options.backgroundColor=0x000000] - The background color of the rendered area - * (shown if not transparent). - * @param {boolean} [options.legacy=false] - If true PixiJS will aim to ensure compatibility - * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. - * @param {string} [options.powerPreference] - Parameter passed to webgl context, set to "high-performance" - * for devices with dual graphics card - */ - constructor(options, arg2, arg3) - { - super('WebGL', options, arg2, arg3); - - /** - * The type of this renderer as a standardised const - * - * @member {number} - * @see PIXI.RENDERER_TYPE - */ - this.type = RENDERER_TYPE.WEBGL; - - // this will be set by the contextSystem (this.context) - this.gl = null; - this.CONTEXT_UID = 0; - this.legacy = !!options.legacy; - - // TODO legacy! - - // runners! - this.runners = { - destroy: new Runner('destroy'), - contextChange: new Runner('contextChange', 1), - reset: new Runner('reset'), - update: new Runner('update'), - postrender: new Runner('postrender'), - prerender: new Runner('prerender'), - resize: new Runner('resize', 2), - }; - - 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(GeometrySystem, 'geometry') - .addSystem(FramebufferSystem, 'framebuffer') - .addSystem(StencilSystem, 'stencil') - .addSystem(ProjectionSystem, 'projection') - .addSystem(TextureGCSystem, 'textureGC') - .addSystem(FilterSystem, 'filter') - .addSystem(RenderTextureSystem, 'renderTexture') - .addSystem(BatchSystem, 'batch'); - - this.initPlugins(Renderer.__plugins); - - /** - * The options passed in to create a new webgl context. - * - * @member {object} - * @private - */ - if (options.context) - { - this.context.initFromContext(options.context); - } - else - { - this.context.initFromOptions({ - alpha: this.transparent, - antialias: options.antialias, - premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', - stencil: true, - 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); - } - - addSystem(_class, name) - { - if (!name) - { - name = _class.name; - } - - // TODO - read name from class.name.. - - /* - if(name.includes('System')) - { - name = name.replace('System', ''); - name = name.charAt(0).toLowerCase() + name.slice(1); - } - */ - - const system = new _class(this); - - if (this[name]) - { - throw new Error(`Whoops! ${name} is already a manger`); - } - - this[name] = system; - - for (const i in this.runners) - { - this.runners[i].add(system); - } - - return this; - - /** - * 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. - */ - } - - /** - * Renders the object to its webGL view - * - * @param {PIXI.DisplayObject} displayObject - the object to be rendered - * @param {PIXI.RenderTexture} renderTexture - The render texture to render to. - * @param {boolean} [clear] - Should the canvas be cleared before the new render - * @param {PIXI.Transform} [transform] - A transform to apply to the render texture before rendering. - * @param {boolean} [skipUpdateTransform] - Should we skip the update transform pass? - */ - render(displayObject, renderTexture, clear, transform, skipUpdateTransform) - { - // can be handy to know! - this.renderingToScreen = !renderTexture; - - this.runners.prerender.run(); - this.emit('prerender'); - - // 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.parent; - - displayObject.parent = this._tempDisplayObjectParent; - displayObject.updateTransform(); - displayObject.parent = 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.run(); - - this.emit('postrender'); - } - - /** - * Resizes the webGL view to the specified width and height. - * - * @param {number} screenWidth - the new width of the screen - * @param {number} screenHeight - the new height of the screen - */ - resize(screenWidth, screenHeight) - { - AbstractRenderer.prototype.resize.call(this, screenWidth, screenHeight); - - this.runners.resize.run(screenWidth, screenHeight); - } - - /** - * Resets the WebGL state so you can render things however you fancy! - * - * @return {PIXI.Renderer} Returns itself. - */ - reset() - { - this.runners.reset.run(); - - return this; - } - - clear() - { - this.framebuffer.bind(); - this.framebuffer.clear(); - } - - /** - * Removes everything from the renderer (event listeners, spritebatch, etc...) - * - * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. - * See: https://github.com/pixijs/pixi.js/issues/2233 - */ - destroy(removeView) - { - this.runners.destroy.run(); - - // call base destroy - super.destroy(removeView); - - // TODO nullify all the managers.. - this.gl = null; - } - - /** - * 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 - * builds and excluding plugins. - * @name PIXI.Renderer#plugins - * @type {object} - * @readonly - * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. - * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. - * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. - * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. - */ - - /** - * Adds a plugin to the renderer. - * - * @method - * @param {string} pluginName - The name of the plugin. - * @param {Function} ctor - The constructor function or class for the plugin. - */ - static registerPlugin(pluginName, ctor) - { - Renderer.__plugins = Renderer.__plugins || {}; - Renderer.__plugins[pluginName] = ctor; - } -} diff --git a/packages/core/src/renderers/State.js b/packages/core/src/renderers/State.js deleted file mode 100644 index fa3f221..0000000 --- a/packages/core/src/renderers/State.js +++ /dev/null @@ -1,163 +0,0 @@ -/* eslint-disable max-len */ - -const BLEND = 0; -const OFFSET = 1; -const CULLING = 2; -const DEPTH_TEST = 3; -const WINDING = 4; - -/** - * This is a webGL state. It is passed The WebGL StateManager. - * Each mesh renderered may require webGL to be in a different state. - * For example you may want different blend mode or to enable polygon offsets - * - * @class - * @memberof PIXI - */ -export default class State -{ - /** - * - */ - constructor() - { - this.data = 0; - - this.blendMode = 0; - this.polygonOffset = 0; - - this.blend = true; - // this.depthTest = true; - } - - /** - * Activates blending of the computed fragment color values - * - * @member {boolean} - */ - get blend() - { - return !!(this.data & (1 << BLEND)); - } - - set blend(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << BLEND)) !== value) - { - this.data ^= (1 << BLEND); - } - } - - /** - * Activates adding an offset to depth values of polygon's fragments - * - * @member {boolean} - * @default false - */ - get offsets() - { - return !!(this.data & (1 << OFFSET)); - } - - set offsets(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << OFFSET)) !== value) - { - this.data ^= (1 << OFFSET); - } - } - - /** - * Activates culling of polygons. - * - * @member {boolean} - * @default false - */ - get culling() - { - return !!(this.data & (1 << CULLING)); - } - - set culling(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << CULLING)) !== value) - { - this.data ^= (1 << CULLING); - } - } - - /** - * Activates depth comparisons and updates to the depth buffer. - * - * @member {boolean} - * @default false - */ - get depthTest() - { - return !!(this.data & (1 << DEPTH_TEST)); - } - - set depthTest(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << DEPTH_TEST)) !== value) - { - this.data ^= (1 << DEPTH_TEST); - } - } - - /** - * Specifies whether or not front or back-facing polygons can be culled. - * @member {boolean} - * @default false - */ - get clockwiseFrontFace() - { - return !!(this.data & (1 << WINDING)); - } - - set clockwiseFrontFace(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << WINDING)) !== value) - { - this.data ^= (1 << WINDING); - } - } - - /** - * The blend mode to be applied when this state is set. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. - * Setting this mode to anything other than NO_BLEND will automatically switch blending on. - * - * @member {boolean} - * @default PIXI.BLEND_MODES.NORMAL - * @see PIXI.BLEND_MODES - */ - get blendMode() - { - return this._blendMode; - } - - set blendMode(value) // eslint-disable-line require-jsdoc - { - // 17 is NO BLEND - this.blend = (value !== 17); - this._blendMode = value; - } - - /** - * The polygon offset. Setting this property to anything other than 0 will automatically enable poygon offset fill. - * - * @member {number} - * @default 0 - */ - get polygonOffset() - { - return this._polygonOffset; - } - - set polygonOffset(value) // eslint-disable-line require-jsdoc - { - this.offsets = !!value; - this._polygonOffset = value; - } -} - diff --git a/packages/core/src/renderers/filters/Filter.js b/packages/core/src/renderers/filters/Filter.js deleted file mode 100644 index 2073b8d..0000000 --- a/packages/core/src/renderers/filters/Filter.js +++ /dev/null @@ -1,140 +0,0 @@ -import Shader from '../../shader/Shader'; -import Program from '../../shader/Program'; -import State from '../State'; -import { settings } from '@pixi/settings'; -// import extractUniformsFromSrc from './extractUniformsFromSrc'; -import defaultVertex from './defaultFilter.vert'; -import defaultFragment from './defaultFilter.frag'; - -// let math = require('../../math'); -/** - * @class - * @memberof PIXI - * @extends PIXI.Shader - */ -export default class Filter extends Shader -{ - /** - * @param {string} [vertexSrc] - The source of the vertex shader. - * @param {string} [fragmentSrc] - The source of the fragment shader. - * @param {object} [uniforms] - Custom uniforms to use to augment the built-in ones. - */ - constructor(vertexSrc, fragmentSrc, uniforms) - { - const program = Program.from(vertexSrc, fragmentSrc); - - super(program, uniforms); - - /** - * The padding of the filter. Some filters require extra space to breath such as a blur. - * Increasing this will add extra width and height to the bounds of the object that the - * filter is applied to. - * - * @member {number} - */ - this.padding = 0; - - /** - * The resolution of the filter. Setting this to be lower will lower the quality but - * increase the performance of the filter. - * - * @member {number} - */ - this.resolution = settings.RESOLUTION; - - /** - * If enabled is true the filter is applied, if false it will not. - * - * @member {boolean} - */ - this.enabled = true; - - /** - * If enabled, PixiJS will fit the filter area into boundaries for better performance. - * Switch it off if it does not work for specific shader. - * - * @member {boolean} - */ - this.autoFit = true; - - /** - * Legacy filters use position and uvs from attributes - * @member {boolean} - * @readonly - */ - this.legacy = !!this.program.attributeData.aTextureCoord; - - /** - * the webGL state the filter requires to render - * @member {PIXI.State} - */ - this.state = new State(); - } - - /** - * Applies the filter - * - * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from - * @param {PIXI.RenderTarget} input - The input render target. - * @param {PIXI.RenderTarget} output - The target to output to. - * @param {boolean} clear - Should the output be cleared before rendering to it - * @param {object} [currentState] - It's current state of filter. - * There are some useful properties in the currentState : - * target, filters, sourceFrame, destinationFrame, renderTarget, resolution - */ - apply(filterManager, input, output, clear, currentState, derp) // eslint-disable-line no-unused-vars - { - // do as you please! - - filterManager.applyFilter(this, input, output, clear, currentState, derp); - - // or just do a regular render.. - } - - /** - * Sets the blendmode of the filter - * - * @member {number} - * @default PIXI.BLEND_MODES.NORMAL - */ - get blendMode() - { - return this.state.blendMode; - } - - set blendMode(value) // eslint-disable-line require-jsdoc - { - this.state.blendMode = value; - } - - /** - * The default vertex shader source - * - * @static - * @constant - */ - static get defaultVertexSrc() - { - return defaultVertex; - } - - /** - * The default fragment shader source - * - * @static - * @constant - */ - static get defaultFragmentSrc() - { - return defaultFragment; - } -} - -/** - * Used for caching shader IDs - * - * @static - * @private - */ -Filter.SOURCE_KEY_MAP = {}; - diff --git a/packages/core/src/renderers/filters/defaultFilter.frag b/packages/core/src/renderers/filters/defaultFilter.frag deleted file mode 100644 index a14c1be..0000000 --- a/packages/core/src/renderers/filters/defaultFilter.frag +++ /dev/null @@ -1,22 +0,0 @@ -varying vec2 vTextureCoord; -varying vec2 vFilterCoord; - -uniform sampler2D uSampler; -uniform sampler2D filterSampler; - -void main(void){ - vec4 masky = texture2D(filterSampler, vFilterCoord); - vec4 sample = texture2D(uSampler, vTextureCoord); - vec4 color; - if(mod(vFilterCoord.x, 1.0) > 0.5) - { - color = vec4(1.0, 0.0, 0.0, 1.0); - } - else - { - color = vec4(0.0, 1.0, 0.0, 1.0); - } - // gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0); - gl_FragColor = mix(sample, masky, 0.5); - gl_FragColor *= sample.a; -} diff --git a/packages/core/src/renderers/filters/defaultFilter.vert b/packages/core/src/renderers/filters/defaultFilter.vert deleted file mode 100644 index e1febca..0000000 --- a/packages/core/src/renderers/filters/defaultFilter.vert +++ /dev/null @@ -1,14 +0,0 @@ -attribute vec2 aVertexPosition; -attribute vec2 aTextureCoord; - -uniform mat3 projectionMatrix; -uniform mat3 filterMatrix; - -varying vec2 vTextureCoord; -varying vec2 vFilterCoord; - -void main(void){ - gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); - vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy; - vTextureCoord = aTextureCoord ; -} diff --git a/packages/core/src/renderers/filters/filterTransforms.js b/packages/core/src/renderers/filters/filterTransforms.js deleted file mode 100644 index 87a3cd6..0000000 --- a/packages/core/src/renderers/filters/filterTransforms.js +++ /dev/null @@ -1,50 +0,0 @@ -import { Matrix } from '@pixi/math'; - -/** - * Calculates the mapped matrix - * @param filterArea {Rectangle} The filter area - * @param sprite {Sprite} the target sprite - * @param outputMatrix {Matrix} @alvin - * @private - */ -// this returns a matrix that will normalise map filter cords in the filter to screen space -export function calculateScreenSpaceMatrix(outputMatrix, filterArea, textureSize) -{ - // TODO unwrap? - const mappedMatrix = outputMatrix.identity(); - - mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); - - mappedMatrix.scale(textureSize.width, textureSize.height); - - return mappedMatrix; -} - -export function calculateNormalizedScreenSpaceMatrix(outputMatrix, filterArea, textureSize) -{ - const mappedMatrix = outputMatrix.identity(); - - mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); - - const translateScaleX = (textureSize.width / filterArea.width); - const translateScaleY = (textureSize.height / filterArea.height); - - mappedMatrix.scale(translateScaleX, translateScaleY); - - return mappedMatrix; -} - -// this will map the filter coord so that a texture can be used based on the transform of a sprite -export function calculateSpriteMatrix(outputMatrix, filterArea, textureSize, sprite) -{ - const orig = sprite._texture.orig; - const mappedMatrix = outputMatrix.set(textureSize.width, 0, 0, textureSize.height, filterArea.x, filterArea.y); - const worldTransform = sprite.worldTransform.copyTo(Matrix.TEMP_MATRIX); - - worldTransform.invert(); - mappedMatrix.prepend(worldTransform); - mappedMatrix.scale(1.0 / orig.width, 1.0 / orig.height); - mappedMatrix.translate(sprite.anchor.x, sprite.anchor.y); - - return mappedMatrix; -} diff --git a/packages/core/src/renderers/filters/spriteMask/SpriteMaskFilter.js b/packages/core/src/renderers/filters/spriteMask/SpriteMaskFilter.js deleted file mode 100644 index d5a91a4..0000000 --- a/packages/core/src/renderers/filters/spriteMask/SpriteMaskFilter.js +++ /dev/null @@ -1,63 +0,0 @@ -import Filter from '../Filter'; -import { Matrix } from '@pixi/math'; -import vertex from './spriteMaskFilter.vert'; -import fragment from './spriteMaskFilter.frag'; -import { default as TextureMatrix } from '../../../textures/TextureMatrix'; - -/** - * The SpriteMaskFilter class - * - * @class - * @extends PIXI.Filter - * @memberof PIXI - */ -export default class SpriteMaskFilter extends Filter -{ - /** - * @param {PIXI.Sprite} sprite - the target sprite - */ - constructor(sprite) - { - const maskMatrix = new Matrix(); - - super(vertex, fragment); - - sprite.renderable = false; - - this.maskSprite = sprite; - this.maskMatrix = maskMatrix; - } - - /** - * Applies the filter - * - * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from - * @param {PIXI.RenderTarget} input - The input render target. - * @param {PIXI.RenderTarget} output - The target to output to. - */ - apply(filterManager, input, output) - { - const maskSprite = this.maskSprite; - const tex = this.maskSprite.texture; - - if (!tex.valid) - { - return; - } - if (!tex.transform) - { - // margin = 0.0, let it bleed a bit, shader code becomes easier - // assuming that atlas textures were made with 1-pixel padding - tex.transform = new TextureMatrix(tex, 0.0); - } - tex.transform.update(); - - this.uniforms.mask = tex; - this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite) - .prepend(tex.transform.mapCoord); - this.uniforms.alpha = maskSprite.worldAlpha; - this.uniforms.maskClamp = tex.transform.uClampFrame; - - filterManager.applyFilter(this, input, output); - } -} diff --git a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.frag b/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.frag deleted file mode 100644 index 0e4aef8..0000000 --- a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.frag +++ /dev/null @@ -1,23 +0,0 @@ -varying vec2 vMaskCoord; -varying vec2 vTextureCoord; - -uniform sampler2D uSampler; -uniform sampler2D mask; -uniform float alpha; -uniform vec4 maskClamp; - -void main(void) -{ - float clip = step(3.5, - step(maskClamp.x, vMaskCoord.x) + - step(maskClamp.y, vMaskCoord.y) + - step(vMaskCoord.x, maskClamp.z) + - step(vMaskCoord.y, maskClamp.w)); - - vec4 original = texture2D(uSampler, vTextureCoord); - vec4 masky = texture2D(mask, vMaskCoord); - - original *= (masky.r * masky.a * alpha * clip); - - gl_FragColor = original; -} diff --git a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.vert b/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.vert deleted file mode 100644 index d23a30c..0000000 --- a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.vert +++ /dev/null @@ -1,16 +0,0 @@ -attribute vec2 aVertexPosition; -attribute vec2 aTextureCoord; - -uniform mat3 projectionMatrix; -uniform mat3 otherMatrix; - -varying vec2 vMaskCoord; -varying vec2 vTextureCoord; - -void main(void) -{ - gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); - - vTextureCoord = aTextureCoord; - vMaskCoord = ( otherMatrix * vec3( aTextureCoord, 1.0) ).xy; -} diff --git a/packages/core/src/renderers/systems/BatchSystem.js b/packages/core/src/renderers/systems/BatchSystem.js deleted file mode 100644 index 70e9518..0000000 --- a/packages/core/src/renderers/systems/BatchSystem.js +++ /dev/null @@ -1,68 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import ObjectRenderer from '../utils/ObjectRenderer'; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ - -export default class BatchSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this manager works for. - */ - constructor(renderer) - { - super(renderer); - - /** - * An empty renderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.emptyRenderer = new ObjectRenderer(renderer); - - /** - * The currently active ObjectRenderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.currentRenderer = this.emptyRenderer; - } - - /** - * Changes the current renderer to the one given in parameter - * - * @param {PIXI.ObjectRenderer} objectRenderer - The object renderer to use. - */ - setObjectRenderer(objectRenderer) - { - if (this.currentRenderer === objectRenderer) - { - return; - } - - this.currentRenderer.stop(); - this.currentRenderer = objectRenderer; - - this.renderer.state.setState(objectRenderer.state); - - this.currentRenderer.start(); - } - - /** - * This should be called if you wish to do some custom rendering - * It will basically render anything that may be batched up such as sprites - * - */ - flush() - { - this.setObjectRenderer(this.emptyRenderer); - } - - reset() - { - this.setObjectRenderer(this.emptyRenderer); - } -} diff --git a/packages/core/src/renderers/systems/ContextSystem.js b/packages/core/src/renderers/systems/ContextSystem.js deleted file mode 100644 index 43bf827..0000000 --- a/packages/core/src/renderers/systems/ContextSystem.js +++ /dev/null @@ -1,186 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { settings } from '@pixi/settings'; - -let CONTEXT_UID = 0; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class ContextSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this.webGLVersion = 1; - - this.handleContextLost = this.handleContextLost.bind(this); - this.handleContextRestored = this.handleContextRestored.bind(this); - - this.extensions = {}; - - renderer.view.addEventListener('webglcontextlost', this.handleContextLost, false); - renderer.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); - } - - get isLost() - { - return (!this.gl || this.gl.isContextLost()); - } - - contextChange(gl) - { - this.gl = gl; - - // restore a context if it was previously lost - if (gl.isContextLost() && gl.getExtension('WEBGL_lose_context')) - { - gl.getExtension('WEBGL_lose_context').restoreContext(); - } - } - - initFromContext(gl) - { - this.gl = gl; - this.validateContext(gl); - this.renderer.gl = gl; - this.renderer.CONTEXT_UID = CONTEXT_UID++; - this.renderer.runners.contextChange.run(gl); - } - - initFromOptions(options) - { - const gl = this.createContext(this.renderer.view, options); - - this.initFromContext(gl); - } - - /** - * Helper class to create a webGL Context - * - * @class - * @memberof PIXI.glCore - * @param canvas {HTMLCanvasElement} the canvas element that we will get the context from - * @param options {Object} An options object that gets passed in to the canvas element containing the context attributes, - * see https://developer.mozilla.org/en/docs/Web/API/HTMLCanvasElement/getContext for the options available - * @return {WebGLRenderingContext} the WebGL context - */ - createContext(canvas, options) - { - let gl; - - if (settings.PREFER_WEBGL_2) - { - gl = canvas.getContext('webgl2', options); - } - - if (gl) - { - this.webGLVersion = 2; - } - else - { - this.webGLVersion = 1; - - gl = canvas.getContext('webgl', options) - || canvas.getContext('experimental-webgl', options); - - if (!gl) - { - // fail, not able to get a context - throw new Error('This browser does not support webGL. Try using the canvas renderer'); - } - } - - this.gl = gl; - - this.getExtensions(); - - return gl; - } - - getExtensions() - { - // time to set up default etensions that pixi uses.. - const gl = this.gl; - const extensions = this.extensions; - - if (this.webGLVersion === 1) - { - extensions.drawBuffers = gl.getExtension('WEBGL_draw_buffers'); - extensions.depthTexture = gl.getExtension('WEBKIT_WEBGL_depth_texture'); - extensions.floatTexture = gl.getExtension('OES_texture_float'); - extensions.loseContext = gl.getExtension('WEBGL_lose_context'); - - extensions.vertexArrayObject = gl.getExtension('OES_vertex_array_object') - || gl.getExtension('MOZ_OES_vertex_array_object') - || gl.getExtension('WEBKIT_OES_vertex_array_object'); - } - - // we don't use any specific WebGL 2 ones yet! - } - - /** - * Handles a lost webgl context - * - * @private - * @param {WebGLContextEvent} event - The context lost event. - */ - handleContextLost(event) - { - event.preventDefault(); - } - - /** - * Handles a restored webgl context - * - * @private - */ - handleContextRestored() - { - this.renderer.runners.contextChange.run(this.gl); - } - - destroy() - { - const view = this.renderer.view; - - // remove listeners - view.removeEventListener('webglcontextlost', this.handleContextLost); - view.removeEventListener('webglcontextrestored', this.handleContextRestored); - - this.gl.useProgram(null); - - if (this.extensions.loseContext) - { - this.extensions.loseContext.loseContext(); - } - } - - postrender() - { - this.gl.flush(); - } - - validateContext(gl) - { - const attributes = gl.getContextAttributes(); - - // this is going to be fairly simple for now.. but at least we have room to grow! - if (!attributes.stencil) - { - /* eslint-disable max-len */ - - /* eslint-disable no-console */ - console.warn('Provided WebGL context does not have a stencil buffer, masks may not render correctly'); - /* eslint-enable no-console */ - - /* eslint-enable max-len */ - } - } -} diff --git a/packages/core/src/renderers/systems/FramebufferSystem.js b/packages/core/src/renderers/systems/FramebufferSystem.js deleted file mode 100644 index df7902b..0000000 --- a/packages/core/src/renderers/systems/FramebufferSystem.js +++ /dev/null @@ -1,246 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { Rectangle } from '@pixi/math'; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class FramebufferSystem extends WebGLSystem -{ - /** - * Sets up the renderer context and necessary buffers. - * - * @private - */ - contextChange() - { - this.gl = this.renderer.gl; - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - this.current = null; - this.viewport = new Rectangle(); - - this.drawBufferExtension = this.renderer.context.extensions.drawBuffers; - } - - bind(framebuffer, frame) - { - const gl = this.gl; - - this.current = framebuffer; - - if (framebuffer) - { - // TODO cacheing layer! - - const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID] || this.initFramebuffer(framebuffer); - - gl.bindFramebuffer(gl.FRAMEBUFFER, fbo.framebuffer); - // makesure all textures are unbound.. - - // now check for updates... - if (fbo.dirtyId !== framebuffer.dirtyId) - { - fbo.dirtyId = framebuffer.dirtyId; - - if (fbo.dirtyFormat !== framebuffer.dirtyFormat) - { - fbo.dirtyFormat = framebuffer.dirtyFormat; - this.updateFramebuffer(framebuffer); - } - else if (fbo.dirtySize !== framebuffer.dirtySize) - { - fbo.dirtySize = framebuffer.dirtySize; - this.resizeFramebuffer(framebuffer); - } - } - - for (let i = 0; i < framebuffer.colorTextures.length; i++) - { - if (framebuffer.colorTextures[i].texturePart) - { - this.renderer.texture.unbind(framebuffer.colorTextures[i].texture); - } - else - { - this.renderer.texture.unbind(framebuffer.colorTextures[i]); - } - } - - if (framebuffer.depthTexture) - { - this.renderer.texture.unbind(framebuffer.depthTexture); - } - - if (frame) - { - this.setViewport(frame.x, frame.y, frame.width, frame.height); - } - else - { - this.setViewport(0, 0, framebuffer.width, framebuffer.height); - } - } - else - { - gl.bindFramebuffer(gl.FRAMEBUFFER, null); - - if (frame) - { - this.setViewport(frame.x, frame.y, frame.width, frame.height); - } - else - { - this.setViewport(0, 0, this.renderer.width, this.renderer.height); - } - } - } - - setViewport(x, y, width, height) - { - const v = this.viewport; - - if (v.width !== width || v.height !== height || v.x !== x || v.y !== y) - { - v.x = x; - v.y = y; - v.width = width; - v.height = height; - - this.gl.viewport(x, y, width, height); - } - } - - get size() - { - if (this.current) - { - // TODO store temp - return { x: 0, y: 0, width: this.current.width, height: this.current.height }; - } - - return { x: 0, y: 0, width: this.renderer.width, height: this.renderer.height }; - } - - clear(r, g, b, a) - { - const gl = this.gl; - - // TODO clear color can be set only one right? - gl.clearColor(r, g, b, a); - gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); - } - - // private functions... - - initFramebuffer(framebuffer) - { - const gl = this.gl; - - // TODO - make this a class? - const fbo = { - framebuffer: gl.createFramebuffer(), - stencil: null, - dirtyId: 0, - dirtyFormat: 0, - dirtySize: 0, - }; - - framebuffer.glFrameBuffers[this.CONTEXT_UID] = fbo; - - return fbo; - } - - resizeFramebuffer(framebuffer) - { - const gl = this.gl; - - if (framebuffer.stencil || framebuffer.depth) - { - gl.bindRenderbuffer(gl.RENDERBUFFER, this.stencil); - gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); - } - } - - updateFramebuffer(framebuffer) - { - const gl = this.gl; - - const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID]; - - // bind the color texture - const colorTextures = framebuffer.colorTextures; - - let count = colorTextures.length; - - if (!this.drawBufferExtension) - { - count = Math.min(count, 1); - } - - const activeTextures = []; - - for (let i = 0; i < count; i++) - { - const texture = framebuffer.colorTextures[i]; - - if (texture.texturePart) - { - this.renderer.texture.bind(texture.texture, 0); - - gl.framebufferTexture2D(gl.FRAMEBUFFER, - gl.COLOR_ATTACHMENT0 + i, - gl.TEXTURE_CUBE_MAP_NEGATIVE_X + texture.side, - texture.texture._glTextures[this.CONTEXT_UID].texture, - 0); - } - else - { - this.renderer.texture.bind(texture, 0); - - gl.framebufferTexture2D(gl.FRAMEBUFFER, - gl.COLOR_ATTACHMENT0 + i, - gl.TEXTURE_2D, - texture._glTextures[this.CONTEXT_UID].texture, - 0); - } - - activeTextures.push(gl.COLOR_ATTACHMENT0 + i); - } - - if (this.drawBufferExtension && activeTextures.length > 1) - { - this.drawBufferExtension.drawBuffersWEBGL(activeTextures); - } - - if (framebuffer.depthTexture) - { - const depthTextureExt = this.renderer.context.extensions.depthTexture; - - if (depthTextureExt) - { - const depthTexture = framebuffer.depthTexture; - - this.renderer.texture.bind(depthTexture, 0); - - gl.framebufferTexture2D(gl.FRAMEBUFFER, - gl.DEPTH_ATTACHMENT, - gl.TEXTURE_2D, - depthTexture._glTextures[this.CONTEXT_UID].texture, - 0); - } - } - - if (framebuffer.stencil || framebuffer.depth) - { - fbo.stencil = gl.createRenderbuffer(); - - gl.bindRenderbuffer(gl.RENDERBUFFER, fbo.stencil); - - // TODO.. this is depth AND stencil? - gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, fbo.stencil); - gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); - // fbo.enableStencil(); - } - } -} diff --git a/packages/core/src/renderers/systems/MaskSystem.js b/packages/core/src/renderers/systems/MaskSystem.js deleted file mode 100644 index d9c4b1a..0000000 --- a/packages/core/src/renderers/systems/MaskSystem.js +++ /dev/null @@ -1,199 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import AlphaMaskFilter from '../filters/spriteMask/SpriteMaskFilter'; - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class MaskSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - // TODO - we don't need both! - this.scissor = false; - this.scissorData = null; - this.scissorRenderTarget = null; - - this.enableScissor = false; - - this.alphaMaskPool = []; - this.alphaMaskIndex = 0; - } - - /** - * Applies the Mask and adds it to the current filter stack. - * - * @param {PIXI.DisplayObject} target - Display Object to push the mask to - * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. - */ - push(target, maskData) - { - // TODO the root check means scissor rect will not - // be used on render textures more info here: - // https://github.com/pixijs/pixi.js/pull/3545 - - if (maskData.texture) - { - this.pushSpriteMask(target, maskData); - } - else if (this.enableScissor - && !this.scissor - && this.renderer._activeRenderTarget.root - && !this.renderer.stencil.stencilMaskStack.length - && maskData.isFastRect()) - { - const matrix = maskData.worldTransform; - - let rot = Math.atan2(matrix.b, matrix.a); - - // use the nearest degree! - rot = Math.round(rot * (180 / Math.PI)); - - if (rot % 90) - { - this.pushStencilMask(maskData); - } - else - { - this.pushScissorMask(target, maskData); - } - } - else - { - this.pushStencilMask(maskData); - } - } - - /** - * Removes the last mask from the mask stack and doesn't return it. - * - * @param {PIXI.DisplayObject} target - Display Object to pop the mask from - * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. - */ - pop(target, maskData) - { - if (maskData.texture) - { - this.popSpriteMask(target, maskData); - } - else if (this.enableScissor && !this.renderer.stencil.stencilMaskStack.length) - { - this.popScissorMask(target, maskData); - } - else - { - this.popStencilMask(target, maskData); - } - } - - /** - * Applies the Mask and adds it to the current filter stack. - * - * @param {PIXI.RenderTarget} target - Display Object to push the sprite mask to - * @param {PIXI.Sprite} maskData - Sprite to be used as the mask - */ - pushSpriteMask(target, maskData) - { - let alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; - - if (!alphaMaskFilter) - { - alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; - } - - alphaMaskFilter[0].resolution = this.renderer.resolution; - alphaMaskFilter[0].maskSprite = maskData; - - // TODO - may cause issues! - target.filterArea = maskData.getBounds(true); - - this.renderer.filterSystem.pushFilter(target, alphaMaskFilter); - - this.alphaMaskIndex++; - } - - /** - * Removes the last filter from the filter stack and doesn't return it. - * - */ - popSpriteMask() - { - this.renderer.filterSystem.popFilter(); - this.alphaMaskIndex--; - } - - /** - * Applies the Mask and adds it to the current filter stack. - * - * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. - */ - pushStencilMask(maskData) - { - this.renderer.batch.flush(); - this.renderer.stencil.pushStencil(maskData); - } - - /** - * Removes the last filter from the filter stack and doesn't return it. - * - */ - popStencilMask() - { - // this.renderer.currentRenderer.stop(); - this.renderer.stencil.popStencil(); - } - - /** - * - * @param {PIXI.DisplayObject} target - Display Object to push the mask to - * @param {PIXI.Graphics} maskData - The masking data. - */ - pushScissorMask(target, maskData) - { - maskData.renderable = true; - - const renderTarget = this.renderer._activeRenderTarget; - - const bounds = maskData.getBounds(); - - bounds.fit(renderTarget.size); - maskData.renderable = false; - - this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); - - const resolution = this.renderer.resolution; - - this.renderer.gl.scissor( - bounds.x * resolution, - (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, - bounds.width * resolution, - bounds.height * resolution - ); - - this.scissorRenderTarget = renderTarget; - this.scissorData = maskData; - this.scissor = true; - } - - /** - * - * - */ - popScissorMask() - { - this.scissorRenderTarget = null; - this.scissorData = null; - this.scissor = false; - - // must be scissor! - const gl = this.renderer.gl; - - gl.disable(gl.SCISSOR_TEST); - } -} diff --git a/packages/core/src/renderers/systems/ProjectionSystem.js b/packages/core/src/renderers/systems/ProjectionSystem.js deleted file mode 100644 index ec5efdb..0000000 --- a/packages/core/src/renderers/systems/ProjectionSystem.js +++ /dev/null @@ -1,73 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { Matrix } from '@pixi/math'; - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ - -export default class ProjectionSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this.projectionMatrix = new Matrix(); - } - - update(destinationFrame, sourceFrame, resolution, root) - { - this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; - this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; - - this.calculateProjection(this.destinationFrame, this.sourceFrame, resolution, root); - - this.renderer.globalUniforms.uniforms.projectionMatrix = this.projectionMatrix; - this.renderer.globalUniforms.update(); - } - - /** - * Updates the projection matrix based on a projection frame (which is a rectangle) - * - * @param {Rectangle} destinationFrame - The destination frame. - * @param {Rectangle} sourceFrame - The source frame. - */ - calculateProjection(destinationFrame, sourceFrame, resolution, root) - { - const pm = this.projectionMatrix; - - // I don't think we will need this line.. - // pm.identity(); - - if (!root) - { - pm.a = (1 / destinationFrame.width * 2) * resolution; - pm.d = (1 / destinationFrame.height * 2) * resolution; - - pm.tx = -1 - (sourceFrame.x * pm.a); - pm.ty = -1 - (sourceFrame.y * pm.d); - } - else - { - pm.a = (1 / destinationFrame.width * 2) * resolution; - pm.d = (-1 / destinationFrame.height * 2) * resolution; - - pm.tx = -1 - (sourceFrame.x * pm.a); - pm.ty = 1 - (sourceFrame.y * pm.d); - } - } - - /** - * Sets the transform of the active render target to the given matrix - * - * @param {PIXI.Matrix} matrix - The transformation matrix - */ - setTransform()// matrix) - { - // this._activeRenderTarget.transform = matrix; - } -} diff --git a/packages/core/src/renderers/systems/RenderTextureSystem.js b/packages/core/src/renderers/systems/RenderTextureSystem.js deleted file mode 100644 index a133ef1..0000000 --- a/packages/core/src/renderers/systems/RenderTextureSystem.js +++ /dev/null @@ -1,115 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { Rectangle } from '@pixi/math'; - -const tempRect = new Rectangle(); - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ - -export default class RenderTextureSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this.clearColor = renderer._backgroundColorRgba; - - // TODO moe this property somewhere else! - this.defaultMaskStack = []; - this.defaultFilterStack = [{}]; - - // empty render texture? - this.renderTexture = null; - - this.destinationFrame = new Rectangle(); - } - - bind(renderTexture, sourceFrame, destinationFrame) - { - // TODO - do we want this?? - if (this.renderTexture === renderTexture) return; - this.renderTexture = renderTexture; - - const renderer = this.renderer; - - if (renderTexture) - { - const baseTexture = renderTexture.baseTexture; - - if (!destinationFrame) - { - tempRect.width = baseTexture.realWidth; - tempRect.height = baseTexture.realHeight; - - destinationFrame = tempRect; - } - - if (!sourceFrame) - { - sourceFrame = destinationFrame; - } - - this.renderer.framebuffer.bind(baseTexture.frameBuffer, destinationFrame); - - this.renderer.projection.update(destinationFrame, sourceFrame, baseTexture.resolution, false); - this.renderer.stencil.setMaskStack(baseTexture.stencilMaskStack); - } - else - { - // TODO these validation checks happen deeper down.. - // thing they can be avoided.. - if (!destinationFrame) - { - tempRect.width = renderer.width; - tempRect.height = renderer.height; - - destinationFrame = tempRect; - } - - if (!sourceFrame) - { - sourceFrame = destinationFrame; - } - - renderer.framebuffer.bind(null, destinationFrame); - - // TODO store this.. - this.renderer.projection.update(destinationFrame, sourceFrame, this.renderer.resolution, true); - this.renderer.stencil.setMaskStack(this.defaultMaskStack); - } - - this.destinationFrame.copyFrom(destinationFrame); - } - - /** - * Erases the render texture and fills the drawing area with a colour - * - * @param {number} [clearColor] - The colour - * @return {PIXI.Renderer} Returns itself. - */ - clear(clearColor) - { - if (this.renderTexture) - { - clearColor = clearColor || this.renderTexture.baseTexture.clearColor; - } - else - { - clearColor = clearColor || this.clearColor; - } - - this.renderer.framebuffer.clear(clearColor[0], clearColor[1], clearColor[2], clearColor[3]); - } - - resize()// screenWidth, screenHeight) - { - // resize the root only! - this.bind(null); - } -} diff --git a/packages/core/src/renderers/systems/StateSystem.js b/packages/core/src/renderers/systems/StateSystem.js deleted file mode 100755 index d7f4caf..0000000 --- a/packages/core/src/renderers/systems/StateSystem.js +++ /dev/null @@ -1,288 +0,0 @@ -import mapWebGLBlendModesToPixi from '../utils/mapWebGLBlendModesToPixi'; -import WebGLSystem from './WebGLSystem'; -import WebGLState from '../State'; - -const BLEND = 0; -const OFFSET = 1; -const CULLING = 2; -const DEPTH_TEST = 3; -const WINDING = 4; - -/** - * A WebGL state machines - * - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class StateSystem extends WebGLSystem -{ - /** - * @param {WebGLRenderingContext} gl - The current WebGL rendering context - */ - constructor(renderer) - { - super(renderer); - - this.gl = null; - - this.maxAttribs = null; - - // check we have vao.. - this.nativeVaoExtension = null; - - this.attribState = null; - - this.stateId = 0; - this.polygonOffset = 0; - this.blendMode = 17; - - this.map = []; - - // map functions for when we set state.. - this.map[BLEND] = this.setBlend; - this.map[OFFSET] = this.setOffset; - this.map[CULLING] = this.setCullFace; - this.map[DEPTH_TEST] = this.setDepthTest; - this.map[WINDING] = this.setFrontFace; - - this.checks = []; - - this.defaultState = new WebGLState(); - this.defaultState.blend = true; - this.defaultState.depth = true; - } - - contextChange(gl) - { - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; - - this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); - - // check we have vao.. - this.nativeVaoExtension = ( - gl.getExtension('OES_vertex_array_object') - || gl.getExtension('MOZ_OES_vertex_array_object') - || gl.getExtension('WEBKIT_OES_vertex_array_object') - ); - - this.attribState = { - tempAttribState: new Array(this.maxAttribs), - attribState: new Array(this.maxAttribs), - }; - - this.blendModes = mapWebGLBlendModesToPixi(gl); - - this.setState(this.defaultState); - - this.reset(); - } - - /** - * Sets the current state - * - * @param {*} state - The state to set. - */ - setState(state) - { - state = state || this.defaultState; - - // TODO maybe to an object check? ( this.state === state )? - if (this.stateId !== state.data) - { - let diff = this.stateId ^ state.data; - let i = 0; - - // order from least to most common - while (diff) - { - if (diff & 1) - { - // state change! - this.map[i].call(this, !!(state.data & (1 << i))); - } - - diff = diff >> 1; - i++; - } - - this.stateId = state.data; - } - - // based on the above settings we check for specific modes.. - // for example if blend is active we check and set the blend modes - // or of polygon offset is active we check the poly depth. - for (let i = 0; i < this.checks.length; i++) - { - this.checks[i](this, state); - } - } - - /** - * Enables or disabled blending. - * - * @param {boolean} value - Turn on or off webgl blending. - */ - setBlend(value) - { - this.updateCheck(StateSystem.checkBlendMode, value); - - this.gl[value ? 'enable' : 'disable'](this.gl.BLEND); - } - - /** - * Enables or disable polygon offset fill - * - * @param {boolean} value - Turn on or off webgl polygon offset testing. - */ - setOffset(value) - { - this.gl[value ? 'enable' : 'disable'](this.gl.POLYGON_OFFSET_FILL); - } - - /** - * Sets whether to enable or disable depth test. - * - * @param {boolean} value - Turn on or off webgl depth testing. - */ - setDepthTest(value) - { - this.gl[value ? 'enable' : 'disable'](this.gl.DEPTH_TEST); - } - - /** - * Sets whether to enable or disable cull face. - * - * @param {boolean} value - Turn on or off webgl cull face. - */ - setCullFace(value) - { - this.gl[value ? 'enable' : 'disable'](this.gl.CULL_FACE); - } - - /** - * Sets the gl front face. - * - * @param {boolean} value - true is clockwise and false is counter-clockwise - */ - setFrontFace(value) - { - this.gl.frontFace(this.gl[value ? 'CW' : 'CCW']); - } - - /** - * Sets the blend mode. - * - * @param {number} value - The blend mode to set to. - */ - setBlendMode(value) - { - if (value === this.blendMode) - { - return; - } - - this.blendMode = value; - this.gl.blendFunc(this.blendModes[value][0], this.blendModes[value][1]); - } - - /** - * Sets the polygon offset. - * - * @param {number} value - the polygon offset - * @param {number} scale - the polygon offset scale - */ - setPolygonOffset(value, scale) - { - this.gl.polygonOffset(value, scale); - } - - /** - * Disables all the vaos in use - * - */ - resetAttributes() - { - for (let i = 0; i < this.attribState.tempAttribState.length; i++) - { - this.attribState.tempAttribState[i] = 0; - } - - for (let i = 0; i < this.attribState.attribState.length; i++) - { - this.attribState.attribState[i] = 0; - } - - // im going to assume one is always active for performance reasons. - for (let i = 1; i < this.maxAttribs; i++) - { - this.gl.disableVertexAttribArray(i); - } - } - - // used - /** - * Resets all the logic and disables the vaos - */ - reset() - { - // unbind any VAO if they exist.. - if (this.nativeVaoExtension) - { - this.nativeVaoExtension.bindVertexArrayOES(null); - } - - // reset all attributes.. - this.resetAttributes(); - - this.gl.pixelStorei(this.gl.UNPACK_FLIP_Y_WEBGL, false); - - this.setBlendMode(0); - - // TODO? - // this.setState(this.defaultState); - } - - /** - * checks to see which updates should be checked based on which settings have been activated - * for example if blend is enabled then we shold check the blend modes each time the state is changed - * or if poygon fill is activated then we need to check if the polygone offset changes. - * The idea is that we only check what we have too. - * - * @param {Function} func the checking function to add or remove - * @param {boolean} value should the check function be added or removed. - */ - updateCheck(func, value) - { - const index = this.checks.indexOf(func); - - if (value && index === -1) - { - this.checks.push(func); - } - else if (!value && index !== -1) - { - this.checks.splice(index, 1); - } - } - - /** - * A private little wrapper function that we call to check the blend mode. - * - * @static - * @private - * @param {PIXI.StateSystem} System the System to perform the state check on - * @param {PIXI.State} state the state that the blendMode will pulled from - */ - static checkBlendMode(system, state) - { - system.setBlendMode(state.blendMode); - } - - // TODO - add polygon offset? -} diff --git a/packages/core/src/renderers/systems/StencilSystem.js b/packages/core/src/renderers/systems/StencilSystem.js deleted file mode 100644 index 5d538cb..0000000 --- a/packages/core/src/renderers/systems/StencilSystem.js +++ /dev/null @@ -1,133 +0,0 @@ -import WebGLSystem from './WebGLSystem'; - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class StencilSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - this.stencilMaskStack = []; - } - - /** - * Changes the mask stack that is used by this System. - * - * @param {PIXI.Graphics[]} stencilMaskStack - The mask stack - */ - setMaskStack(stencilMaskStack) - { - const gl = this.renderer.gl; - - if (stencilMaskStack.length !== this.stencilMaskStack.length) - { - if (stencilMaskStack.length === 0) - { - gl.disable(gl.STENCIL_TEST); - } - else - { - gl.enable(gl.STENCIL_TEST); - } - } - - this.stencilMaskStack = stencilMaskStack; - } - - /** - * Applies the Mask and adds it to the current stencil stack. @alvin - * - * @param {PIXI.Graphics} graphics - The mask - */ - pushStencil(graphics) - { - this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); - - // this.renderer._activeRenderTarget.attachStencilBuffer(); - - const gl = this.renderer.gl; - const prevMaskCount = this.stencilMaskStack.length; - - if (prevMaskCount === 0) - { - gl.enable(gl.STENCIL_TEST); - } - - this.stencilMaskStack.push(graphics); - - // Increment the refference stencil value where the new mask overlaps with the old ones. - gl.colorMask(false, false, false, false); - gl.stencilFunc(gl.EQUAL, prevMaskCount, this._getBitwiseMask()); - gl.stencilOp(gl.KEEP, gl.KEEP, gl.INCR); - this.renderer.plugins.graphics.render(graphics); - - this._useCurrent(); - } - - /** - * Removes the last mask from the stencil stack. @alvin - */ - popStencil() - { - this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); - - const gl = this.renderer.gl; - const graphics = this.stencilMaskStack.pop(); - - if (this.stencilMaskStack.length === 0) - { - // the stack is empty! - gl.disable(gl.STENCIL_TEST); - gl.clear(gl.STENCIL_BUFFER_BIT); - gl.clearStencil(0); - } - else - { - // Decrement the refference stencil value where the popped mask overlaps with the other ones - gl.colorMask(false, false, false, false); - gl.stencilOp(gl.KEEP, gl.KEEP, gl.DECR); - this.renderer.plugins.graphics.render(graphics); - - this._useCurrent(); - } - } - - /** - * Setup renderer to use the current stencil data. - */ - _useCurrent() - { - const gl = this.renderer.gl; - - gl.colorMask(true, true, true, true); - gl.stencilFunc(gl.EQUAL, this.stencilMaskStack.length, this._getBitwiseMask()); - gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP); - } - - /** - * Fill 1s equal to the number of acitve stencil masks. - * - * @return {number} The bitwise mask. - */ - _getBitwiseMask() - { - return (1 << this.stencilMaskStack.length) - 1; - } - - /** - * Destroys the mask stack. - * - */ - destroy() - { - super.destroy(this); - - this.stencilMaskStack = null; - } -} diff --git a/packages/core/src/renderers/systems/WebGLSystem.js b/packages/core/src/renderers/systems/WebGLSystem.js deleted file mode 100644 index d3d5b31..0000000 --- a/packages/core/src/renderers/systems/WebGLSystem.js +++ /dev/null @@ -1,41 +0,0 @@ -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this manager works for. - */ - constructor(renderer) - { - /** - * The renderer this manager works for. - * - * @member {PIXI.Renderer} - */ - this.renderer = renderer; - - this.renderer.runners.contextChange.add(this); - } - - /** - * Generic method called when there is a WebGL context change. - * - */ - contextChange() - { - // do some codes init! - } - - /** - * Generic destroy methods to be overridden by the subclass - * - */ - destroy() - { - this.renderer.runners.contextChange.remove(this); - this.renderer = null; - } -} diff --git a/packages/core/src/renderers/systems/filter/FilterSystem.js b/packages/core/src/renderers/systems/filter/FilterSystem.js deleted file mode 100644 index e615037..0000000 --- a/packages/core/src/renderers/systems/filter/FilterSystem.js +++ /dev/null @@ -1,431 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; - -import RenderTexture from '../../../textures/RenderTexture'; -import Quad from '../../utils/Quad'; -import QuadUv from '../../utils/QuadUv'; -import { Rectangle } from '@pixi/math'; -import * as filterTransforms from '../../filters/filterTransforms'; -import bitTwiddle from 'bit-twiddle'; -import UniformGroup from '../../../shader/UniformGroup'; -import { DRAW_MODES } from '../../../../../constants'; - -// -/** - * @ignore - * @class - */ -class FilterState -{ - /** - * - */ - constructor() - { - this.renderTexture = null; - this.sourceFrame = new Rectangle(); - this.destinationFrame = new Rectangle(); - this.filters = []; - this.target = null; - this.legacy = false; - this.resolution = 1; - } -} - -/** - * @class - * @memberof PIXI.systems - * @extends PIXI.systems.WebGLSystem - */ -export default class FilterSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - /** - * stores a bunch of PO2 textures used for filtering - * @type {Object} - */ - this.texturePool = {}; - - /** - * a pool for storing filter states, save us creating new ones each tick - * @type {Array} - */ - this.statePool = []; - - /** - * A very simple geometry used when drawing a filter effect to the screen - * @type {Quad} - */ - this.quad = new Quad(); - - this.quadUv = new QuadUv(); - - /** - * Temporary rect for maths - * @type {PIXI.Rectangle} - */ - this.tempRect = new Rectangle(); - - this.activeState = {}; - - /** - * this uniform group is attached to filter uniforms when used - * @type {UniformGroup} - */ - this.globalUniforms = new UniformGroup({ - sourceFrame: this.tempRect, - destinationFrame: this.tempRect, - - // legacy variables - filterArea: new Float32Array(4), - filterClamp: new Float32Array(4), - }, true); - } - - /** - * Adds a new filter to the System. - * - * @param {PIXI.DisplayObject} target - The target of the filter to render. - * @param {PIXI.Filter[]} filters - The filters to apply. - */ - push(target, filters) - { - const renderer = this.renderer; - const filterStack = this.renderer.renderTexture.defaultFilterStack; - const state = this.statePool.pop() || new FilterState(); - - let resolution = filters[0].resolution; - let padding = filters[0].padding; - let autoFit = filters[0].autoFit; - let legacy = filters[0].legacy; - - for (let i = 1; i < filters.length; i++) - { - const filter = filters[i]; - - // lets use the lowest resolution.. - resolution = Math.min(resolution, filter.resolution); - // and the largest amount of padding! - padding = Math.max(padding, filter.padding); - // only auto fit if all filters are autofit - autoFit = autoFit || filter.autoFit; - - legacy = legacy || filter.legacy; - } - - filterStack.push(state); - - state.resolution = resolution; - - state.legacy = legacy; - - // round to whole number based on resolution - // TODO move that to the shader too? - state.sourceFrame = target.filterArea || target.getBounds(true); - - state.sourceFrame.pad(padding); - - if (autoFit) - { - state.sourceFrame.fit(this.renderer.renderTexture.destinationFrame); - } - - state.sourceFrame.round(resolution); - - state.renderTexture = this.getPotFilterTexture(state.sourceFrame.width, state.sourceFrame.height, resolution); - state.filters = filters; - - state.destinationFrame.width = state.renderTexture.width; - state.destinationFrame.height = state.renderTexture.height; - - state.renderTexture.filterFrame = state.sourceFrame; - renderer.renderTexture.bind(state.renderTexture, state.sourceFrame);// /, state.destinationFrame); - renderer.renderTexture.clear(); - } - - /** - * Pops off the filter and applies it. - * - */ - pop() - { - const renderer = this.renderer; - const filterStack = renderer.renderTexture.defaultFilterStack; - const state = filterStack.pop(); - const filters = state.filters; - - this.activeState = state; - - const globalUniforms = this.globalUniforms.uniforms; - - globalUniforms.sourceFrame = state.sourceFrame; - globalUniforms.destinationFrame = state.destinationFrame; - globalUniforms.resolution = state.resolution; - - // only update the rect if its legacy.. - if (state.legacy) - { - const filterArea = globalUniforms.filterArea; - const filterClamp = globalUniforms.filterClamp; - - filterArea[0] = state.destinationFrame.width; - filterArea[1] = state.destinationFrame.height; - filterArea[2] = state.sourceFrame.x; - filterArea[3] = state.sourceFrame.y; - - filterClamp[0] = 0.5 / state.resolution / state.destinationFrame.width; - filterClamp[1] = 0.5 / state.resolution / state.destinationFrame.height; - filterClamp[2] = (state.sourceFrame.width - 0.5) / state.resolution / state.destinationFrame.width; - filterClamp[3] = (state.sourceFrame.height - 0.5) / state.resolution / state.destinationFrame.height; - } - - this.globalUniforms.update(); - - const lastState = filterStack[filterStack.length - 1]; - - if (filters.length === 1) - { - filters[0].apply(this, state.renderTexture, lastState.renderTexture, false, state); - - this.returnFilterTexture(state.renderTexture); - } - else - { - let flip = state.renderTexture; - let flop = this.getPotFilterTexture( - flip.width, - flip.height, - state.resolution - ); - - flop.filterFrame = flip.filterFrame; - - let i = 0; - - for (i = 0; i < filters.length - 1; ++i) - { - filters[i].apply(this, flip, flop, true, state); - - const t = flip; - - flip = flop; - flop = t; - } - - filters[i].apply(this, flip, lastState.renderTexture, false, state); - - this.returnFilterTexture(flip); - this.returnFilterTexture(flop); - } - - this.statePool.push(state); - } - - /** - * Draws a filter. - * - * @param {PIXI.Filter} filter - The filter to draw. - * @param {PIXI.RenderTarget} input - The input render target. - * @param {PIXI.RenderTarget} output - The target to output to. - * @param {boolean} clear - Should the output be cleared before rendering to it - */ - applyFilter(filter, input, output, clear) - { - const renderer = this.renderer; - - renderer.renderTexture.bind(output, output ? output.filterFrame : null); - - if (clear) - { - // gl.disable(gl.SCISSOR_TEST); - renderer.renderTexture.clear(); - // gl.enable(gl.SCISSOR_TEST); - } - - // set the uniforms.. - filter.uniforms.uSampler = input; - filter.uniforms.filterGlobals = this.globalUniforms; - - // TODO make it so that the order of this does not matter.. - // because it does at the moment cos of global uniforms. - // they need to get resynced - - renderer.state.setState(filter.state); - renderer.shader.bind(filter); - - if (filter.legacy) - { - this.quadUv.map(input._frame, input.filterFrame); - - renderer.geometry.bind(this.quadUv); - renderer.geometry.draw(DRAW_MODES.TRIANGLES); - } - else - { - renderer.geometry.bind(this.quad); - renderer.geometry.draw(DRAW_MODES.TRIANGLE_STRIP); - } - } - - /** - * Calculates the mapped matrix. - * - * TODO playing around here.. this is temporary - (will end up in the shader) - * this returns a matrix that will normalise map filter cords in the filter to screen space - * - * @param {PIXI.Matrix} outputMatrix - the matrix to output to. - * @return {PIXI.Matrix} The mapped matrix. - */ - calculateScreenSpaceMatrix(outputMatrix) - { - const currentState = this.activeState; - - return filterTransforms.calculateScreenSpaceMatrix( - outputMatrix, - currentState.sourceFrame, - currentState.destinationFrame - ); - } - - /** - * This will map the filter coord so that a texture can be used based on the transform of a sprite - * - * @param {PIXI.Matrix} outputMatrix - The matrix to output to. - * @param {PIXI.Sprite} sprite - The sprite to map to. - * @return {PIXI.Matrix} The mapped matrix. - */ - calculateSpriteMatrix(outputMatrix, sprite) - { - const currentState = this.activeState; - - return filterTransforms.calculateSpriteMatrix( - outputMatrix, - currentState.sourceFrame, - currentState.destinationFrame, - sprite - ); - } - - /** - * Destroys this Filter System. - * - * @param {boolean} [contextLost=false] context was lost, do not free shaders - * - */ - destroy(contextLost = false) - { - if (!contextLost) - { - this.emptyPool(); - } - else - { - this.texturePool = {}; - } - } - - /** - * Gets a Power-of-Two render texture. - * - * TODO move to a seperate class could be on renderer? - * also - could cause issue with multiple contexts? - * - * @private - * @param {WebGLRenderingContext} gl - The webgl rendering context - * @param {number} minWidth - The minimum width of the render target. - * @param {number} minHeight - The minimum height of the render target. - * @param {number} resolution - The resolution of the render target. - * @return {PIXI.RenderTarget} The new render target. - */ - getPotFilterTexture(minWidth, minHeight, resolution) - { - minWidth = bitTwiddle.nextPow2(minWidth); - minHeight = bitTwiddle.nextPow2(minHeight); - resolution = resolution || 1; - - const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); - - if (!this.texturePool[key]) - { - this.texturePool[key] = []; - } - - let renderTexture = this.texturePool[key].pop(); - - if (!renderTexture) - { - // temporary bypass cache.. - // internally - this will cause a texture to be bound.. - renderTexture = RenderTexture.create({ - width: minWidth, - height: minHeight, - resolution, - }); - } - - return renderTexture; - } - - /** - * Gets extra render texture to use inside current filter - * - * @param {number} resolution resolution of the renderTexture - * @returns {PIXI.RenderTexture} - */ - getFilterTexture(resolution) - { - const rt = this.activeState.renderTexture; - - const filterTexture = this.getPotFilterTexture(rt.width, rt.height, resolution || rt.baseTexture.resolution); - - filterTexture.filterFrame = rt.filterFrame; - - return filterTexture; - } - - /** - * Frees a render texture back into the pool. - * - * @param {PIXI.RenderTarget} renderTexture - The renderTarget to free - */ - returnFilterTexture(renderTexture) - { - renderTexture.filterFrame = null; - - const base = renderTexture.baseTexture; - - const minWidth = base.width; - const minHeight = base.height; - - const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); - - this.texturePool[key].push(renderTexture); - } - - /** - * Empties the texture pool. - * - */ - emptyPool() - { - for (const i in this.texturePool) - { - const textures = this.texturePool[i]; - - if (textures) - { - for (let j = 0; j < textures.length; j++) - { - textures[j].destroy(true); - } - } - } - - this.texturePool = {}; - } -} diff --git a/packages/core/src/renderers/systems/geometry/GLBuffer.js b/packages/core/src/renderers/systems/geometry/GLBuffer.js deleted file mode 100644 index 0477e8a..0000000 --- a/packages/core/src/renderers/systems/geometry/GLBuffer.js +++ /dev/null @@ -1,9 +0,0 @@ -export default class GLBuffer -{ - constructor(buffer) - { - this.buffer = buffer; - this.updateID = -1; - this.byteLength = -1; - } -} diff --git a/packages/core/src/renderers/systems/geometry/GeometrySystem.js b/packages/core/src/renderers/systems/geometry/GeometrySystem.js deleted file mode 100644 index 26c8e9e..0000000 --- a/packages/core/src/renderers/systems/geometry/GeometrySystem.js +++ /dev/null @@ -1,388 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; -import GLBuffer from './GLBuffer'; - -const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class GeometrySystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this._activeGeometry = null; - this._activeVao = null; - - this.hasVao = true; - this.hasInstance = true; - } - - /** - * Sets up the renderer context and necessary buffers. - * - * @private - */ - contextChange() - { - const gl = this.gl = this.renderer.gl; - - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - - // webgl2 - if (!gl.createVertexArray) - { - // webgl 1! - let nativeVaoExtension = this.renderer.context.extensions.vertexArrayObject; - - if (this.renderer.legacy) - { - nativeVaoExtension = null; - } - - if (nativeVaoExtension) - { - gl.createVertexArray = () => - nativeVaoExtension.createVertexArrayOES(); - - gl.bindVertexArray = (vao) => - nativeVaoExtension.bindVertexArrayOES(vao); - - gl.deleteVertexArray = (vao) => - nativeVaoExtension.deleteVertexArrayOES(vao); - } - else - { - this.hasVao = false; - gl.createVertexArray = () => - { - // empty - }; - - gl.bindVertexArray = () => - { - // empty - }; - - gl.deleteVertexArray = () => - { - // empty - }; - } - } - - if (!gl.vertexAttribDivisor) - { - const instanceExt = gl.getExtension('ANGLE_instanced_arrays'); - - if (instanceExt) - { - gl.vertexAttribDivisor = (a, b) => - instanceExt.vertexAttribDivisorANGLE(a, b); - - gl.drawElementsInstanced = (a, b, c, d, e) => - instanceExt.drawElementsInstancedANGLE(a, b, c, d, e); - - gl.drawArraysInstanced = (a, b, c, d) => - instanceExt.drawArraysInstancedANGLE(a, b, c, d); - } - else - { - this.hasInstance = false; - } - } - } - - /** - * Binds geometry so that is can be drawn. Creating a Vao if required - * @private - * @param {PIXI.Geometry} geometry instance of geometry to bind - */ - bind(geometry, shader) - { - shader = shader || this.renderer.shader.shader; - - const gl = this.gl; - - // not sure the best way to address this.. - // currently different shaders require different VAOs for the same geometry - // Still mulling over the best way to solve this one.. - // will likely need to modify the shader attribute locations at run time! - let vaos = geometry.glVertexArrayObjects[this.CONTEXT_UID]; - - if (!vaos) - { - geometry.glVertexArrayObjects[this.CONTEXT_UID] = vaos = {}; - } - - const vao = vaos[shader.program.id] || this.initGeometryVao(geometry, shader.program); - - this._activeGeometry = geometry; - - if (this._activeVao !== vao) - { - this._activeVao = vao; - - if (this.hasVao) - { - gl.bindVertexArray(vao); - } - else - { - this.activateVao(geometry, shader.program); - } - } - - // TODO - optimise later! - // don't need to loop through if nothing changed! - // maybe look to add an 'autoupdate' to geometry? - this.updateBuffers(); - } - - reset() - { - this.unbind(); - } - - updateBuffers() - { - const geometry = this._activeGeometry; - const gl = this.gl; - - for (let i = 0; i < geometry.buffers.length; i++) - { - const buffer = geometry.buffers[i]; - - const glBuffer = buffer._glBuffers[this.CONTEXT_UID]; - - if (buffer._updateID !== glBuffer.updateID) - { - glBuffer.updateID = buffer._updateID; - - // TODO can cache this on buffer! maybe added a getter / setter? - const type = buffer.index ? gl.ELEMENT_ARRAY_BUFFER : gl.ARRAY_BUFFER; - const drawType = buffer.static ? gl.STATIC_DRAW : gl.DYNAMIC_DRAW; - - gl.bindBuffer(type, glBuffer.buffer); - - if (glBuffer.byteLength >= buffer.data.byteLength) - { - // offset is always zero for now! - gl.bufferSubData(type, 0, buffer.data); - } - else - { - gl.bufferData(type, buffer.data, drawType); - } - } - } - } - - checkCompatability(geometry, program) - { - // geometry must have at least all the attributes that the shader requires. - const geometryAttributes = geometry.attributes; - const shaderAttributes = program.attributeData; - - for (const j in shaderAttributes) - { - if (!geometryAttributes[j]) - { - throw new Error(`shader and geometry incompatible, geometry missing the "${j}" attribute`); - } - } - } - - /** - * Creates a Vao with the same structure as the geometry and stores it on the geometry. - * @private - * @param {PIXI.Geometry} geometry instance of geometry to to generate Vao for - */ - initGeometryVao(geometry, program) - { - this.checkCompatability(geometry, program); - - const gl = this.gl; - const CONTEXT_UID = this.CONTEXT_UID; - const buffers = geometry.buffers; - const attributes = geometry.attributes; - - const tempStride = {}; - const tempStart = {}; - - for (const j in buffers) - { - tempStride[j] = 0; - tempStart[j] = 0; - } - - for (const j in attributes) - { - if (!attributes[j].size && program.attributeData[j]) - { - attributes[j].size = program.attributeData[j].size; - } - - tempStride[attributes[j].buffer] += attributes[j].size * byteSizeMap[attributes[j].type]; - } - - for (const j in attributes) - { - const attribute = attributes[j]; - const attribSize = attribute.size; - - if (attribute.stride === undefined) - { - if (tempStride[attribute.buffer] === attribSize * byteSizeMap[attribute.type]) - { - attribute.stride = 0; - } - else - { - attribute.stride = tempStride[attribute.buffer]; - } - } - - if (attribute.start === undefined) - { - attribute.start = tempStart[attribute.buffer]; - - tempStart[attribute.buffer] += attribSize * byteSizeMap[attribute.type]; - } - } - - // first update - and create the buffers! - // only create a gl buffer if it actually gets - for (let i = 0; i < buffers.length; i++) - { - const buffer = buffers[i]; - - if (!buffer._glBuffers[CONTEXT_UID]) - { - buffer._glBuffers[CONTEXT_UID] = new GLBuffer(gl.createBuffer()); - } - } - - // TODO - maybe make this a data object? - // lets wait to see if we need to first! - const vao = gl.createVertexArray(); - - gl.bindVertexArray(vao); - - this.activateVao(geometry, program); - - gl.bindVertexArray(null); - - // add it to the cache! - geometry.glVertexArrayObjects[this.CONTEXT_UID][program.id] = vao; - - return vao; - } - - activateVao(geometry, program) - { - const gl = this.gl; - const CONTEXT_UID = this.CONTEXT_UID; - const buffers = geometry.buffers; - const attributes = geometry.attributes; - - if (geometry.indexBuffer) - { - // first update the index buffer if we have one.. - gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, geometry.indexBuffer._glBuffers[CONTEXT_UID].buffer); - } - - let lastBuffer = null; - - // add a new one! - for (const j in attributes) - { - const attribute = attributes[j]; - const buffer = buffers[attribute.buffer]; - const glBuffer = buffer._glBuffers[CONTEXT_UID]; - - if (program.attributeData[j]) - { - if (lastBuffer !== glBuffer) - { - gl.bindBuffer(gl.ARRAY_BUFFER, glBuffer.buffer); - - lastBuffer = glBuffer; - } - - const location = program.attributeData[j].location; - - // TODO introduce state again - // we can optimise this for older devices that have no VAOs - gl.enableVertexAttribArray(location); - - gl.vertexAttribPointer(location, - attribute.size, - attribute.type || gl.FLOAT, - attribute.normalized, - attribute.stride, - attribute.start); - - if (attribute.instance) - { - // TODO calculate instance count based of this... - if (this.hasInstance) - { - gl.vertexAttribDivisor(location, 1); - } - else - { - throw new Error('geometry error, GPU Instancing is not supported on this device'); - } - } - } - } - } - - draw(type, size, start, instanceCount) - { - const gl = this.gl; - const geometry = this._activeGeometry; - - // TODO.. this should not change so maybe cache the function? - - if (geometry.indexBuffer) - { - if (geometry.instanced) - { - /* eslint-disable max-len */ - gl.drawElementsInstanced(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2, instanceCount || 1); - /* eslint-enable max-len */ - } - else - { - gl.drawElements(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2); - } - } - else - if (geometry.instanced) - { - // TODO need a better way to calculate size.. - gl.drawArraysInstanced(type, start, size || geometry.getSize(), instanceCount || 1); - } - else - { - gl.drawArrays(type, start, size || geometry.getSize()); - } - - return this; - } - - unbind() - { - this.gl.bindVertexArray(null); - this._activeVao = null; - this._activeGeometry = null; - } -} diff --git a/packages/core/src/renderers/systems/geometry/setVertexAttribArrays.js b/packages/core/src/renderers/systems/geometry/setVertexAttribArrays.js deleted file mode 100644 index 23b6959..0000000 --- a/packages/core/src/renderers/systems/geometry/setVertexAttribArrays.js +++ /dev/null @@ -1,54 +0,0 @@ -// var GL_MAP = {}; - -/** - * @param gl {WebGLRenderingContext} The current WebGL context - * @param attribs {*} - * @param state {*} - */ -export default function setVertexAttribArrays(gl, attribs, state) -{ - let i; - - if (state) - { - const tempAttribState = state.tempAttribState; - const attribState = state.attribState; - - for (i = 0; i < tempAttribState.length; i++) - { - tempAttribState[i] = false; - } - - // set the new attribs - for (i = 0; i < attribs.length; i++) - { - tempAttribState[attribs[i].attribute.location] = true; - } - - for (i = 0; i < attribState.length; i++) - { - if (attribState[i] !== tempAttribState[i]) - { - attribState[i] = tempAttribState[i]; - - if (state.attribState[i]) - { - gl.enableVertexAttribArray(i); - } - else - { - gl.disableVertexAttribArray(i); - } - } - } - } - else - { - for (i = 0; i < attribs.length; i++) - { - const attrib = attribs[i]; - - gl.enableVertexAttribArray(attrib.attribute.location); - } - } -} diff --git a/packages/core/src/renderers/systems/index.js b/packages/core/src/renderers/systems/index.js deleted file mode 100644 index c843f03..0000000 --- a/packages/core/src/renderers/systems/index.js +++ /dev/null @@ -1,6 +0,0 @@ -/** - * Systems are individual components to the Renderer pipeline. - * @namespace PIXI.systems - */ -export { default as WebGLSystem } from './WebGLSystem'; -export { default as FilterSystem } from './filter/FilterSystem'; diff --git a/packages/core/src/renderers/systems/shader/GLProgram.js b/packages/core/src/renderers/systems/shader/GLProgram.js deleted file mode 100644 index 92980ca..0000000 --- a/packages/core/src/renderers/systems/shader/GLProgram.js +++ /dev/null @@ -1,43 +0,0 @@ -/** - * Helper class to create a webGL Program - * - * @class - * @private - */ -export default class GLProgram -{ - constructor(program, uniformData) - { - /** - * The shader program - * - * @member {WebGLProgram} - */ - this.program = program; - - /** - * holds the uniform data which contains uniform locations - * and current uniform values used for caching and preventing undeed GPU commands - * @type {Object} - */ - this.uniformData = uniformData; - - /** - * uniformGroups holds the various upload functions for the shader. Each uniform group - * and program have a unique upload function generated. - * @type {Object} - */ - this.uniformGroups = {}; - } - - /** - * Destroys this program - * TODO - */ - destroy() - { - this.uniformData = null; - this.uniformGroups = null; - this.program = null; - } -} diff --git a/packages/core/src/renderers/systems/shader/ShaderSystem.js b/packages/core/src/renderers/systems/shader/ShaderSystem.js deleted file mode 100644 index 6cf57b6..0000000 --- a/packages/core/src/renderers/systems/shader/ShaderSystem.js +++ /dev/null @@ -1,170 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; -import GLProgram from './GLProgram'; -import generateUniformsSync from '../../../shader/generateUniformsSync'; -import defaultValue from '../../../shader/defaultValue'; -import compileProgram from './shader/compileProgram'; - -let UID = 0; - -/** - * Helper class to create a webGL Texture - * - * @class - * @memberof PIXI - */ -export default class ShaderSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - A reference to the current renderer - */ - constructor(renderer) - { - super(renderer); - - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = null; - - this.shader = null; - this.program = null; - - this.id = UID++; - } - - contextChange(gl) - { - this.gl = gl; - } - - /** - * Changes the current shader to the one given in parameter - * - * @param {PIXI.Shader} shader - the new shader - * @param {boolean} dontSync - false if the shader should automatically sync its uniforms. - * @returns {PIXI.glCore.glProgram} the glProgram that belongs to the shader. - */ - bind(shader, dontSync) - { - shader.uniforms.globals = this.renderer.globalUniforms; - - const program = shader.program; - const glProgram = program.glPrograms[this.renderer.CONTEXT_UID] || this.generateShader(shader); - - this.shader = shader; - - // TODO - some current pixi plugins bypass this.. so it not safe to use yet.. - if (this.program !== program) - { - this.program = program; - this.gl.useProgram(glProgram.program); - } - - if (!dontSync) - { - this.syncUniformGroup(shader.uniformGroup); - } - - return glProgram; - } - - /** - * Uploads the uniforms values to the currently bound shader. - * - * @param {object} uniforms - the uniforms valiues that be applied to the current shader - */ - setUniforms(uniforms) - { - const shader = this.shader.program; - const glProgram = shader.glPrograms[this.renderer.CONTEXT_UID]; - - shader.syncUniforms(glProgram.uniformData, uniforms, this.renderer); - } - - syncUniformGroup(group) - { - const glProgram = this.getglProgram(); - - if (!group.static || group.dirtyId !== glProgram.uniformGroups[group.id]) - { - glProgram.uniformGroups[group.id] = group.dirtyId; - const syncFunc = group.syncUniforms[this.shader.program.id] || this.createSyncGroups(group); - - syncFunc(glProgram.uniformData, group.uniforms, this.renderer); - } - } - - createSyncGroups(group) - { - group.syncUniforms[this.shader.program.id] = generateUniformsSync(group, this.shader.program.uniformData); - - return group.syncUniforms[this.shader.program.id]; - } - - /** - * Returns the underlying GLShade rof the currently bound shader. - * This can be handy for when you to have a little more control over the setting of your uniforms. - * - * @return {PIXI.glCore.Shader} the glProgram for the currently bound Shader for this context - */ - getglProgram() - { - if (this.shader) - { - return this.shader.program.glPrograms[this.renderer.CONTEXT_UID]; - } - - return null; - } - - /** - * Generates a glProgram verion of the Shader provided. - * - * @private - * @param {PIXI.Shader} shader the shader that the glProgram will be based on. - * @return {PIXI.glCore.glProgram} A shiney new glProgram - */ - generateShader(shader) - { - const gl = this.gl; - - const program = shader.program; - - const attribMap = {}; - - for (const i in program.attributeData) - { - attribMap[i] = program.attributeData[i].location; - } - - const shaderProgram = compileProgram(gl, program.vertexSrc, program.fragmentSrc, attribMap); - const uniformData = {}; - - for (const i in program.uniformData) - { - const data = program.uniformData[i]; - - uniformData[i] = { - location: gl.getUniformLocation(shaderProgram, i), - value: defaultValue(data.type, data.size), - }; - } - - const glProgram = new GLProgram(shaderProgram, uniformData); - - program.glPrograms[this.renderer.CONTEXT_UID] = glProgram; - - return glProgram; - } - - /** - * Destroys this System and removes all its textures - */ - destroy() - { - // TODO implement destroy method for ShaderSystem - this.destroyed = true; - } -} diff --git a/packages/core/src/renderers/systems/shader/shader/compileProgram.js b/packages/core/src/renderers/systems/shader/shader/compileProgram.js deleted file mode 100644 index 8c6ccb9..0000000 --- a/packages/core/src/renderers/systems/shader/shader/compileProgram.js +++ /dev/null @@ -1,78 +0,0 @@ -/** - * @method compileProgram - * @memberof PIXI.glCore.shader - * @param gl {WebGLRenderingContext} The current WebGL context {WebGLProgram} - * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. - * @param fragmentSrc {string|string[]} The fragment shader source as an array of strings. - * @param attributeLocations {Object} An attribute location map that lets you manually set the attribute locations - * @return {WebGLProgram} the shader program - */ -export default function compileProgram(gl, vertexSrc, fragmentSrc, attributeLocations) -{ - const glVertShader = compileShader(gl, gl.VERTEX_SHADER, vertexSrc); - const glFragShader = compileShader(gl, gl.FRAGMENT_SHADER, fragmentSrc); - - let program = gl.createProgram(); - - gl.attachShader(program, glVertShader); - gl.attachShader(program, glFragShader); - - // optionally, set the attributes manually for the program rather than letting WebGL decide.. - if (attributeLocations) - { - for (const i in attributeLocations) - { - gl.bindAttribLocation(program, attributeLocations[i], i); - } - } - - gl.linkProgram(program); - - // if linking fails, then log and cleanup - if (!gl.getProgramParameter(program, gl.LINK_STATUS)) - { - console.error('Pixi.js Error: Could not initialize shader.'); - console.error('gl.VALIDATE_STATUS', gl.getProgramParameter(program, gl.VALIDATE_STATUS)); - console.error('gl.getError()', gl.getError()); - - // if there is a program info log, log it - if (gl.getProgramInfoLog(program) !== '') - { - console.warn('Pixi.js Warning: gl.getProgramInfoLog()', gl.getProgramInfoLog(program)); - } - - gl.deleteProgram(program); - program = null; - } - - // clean up some shaders - gl.deleteShader(glVertShader); - gl.deleteShader(glFragShader); - - return program; -} - -/** - * @private - * @param gl {WebGLRenderingContext} The current WebGL context {WebGLProgram} - * @param type {Number} the type, can be either VERTEX_SHADER or FRAGMENT_SHADER - * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. - * @return {WebGLShader} the shader - */ -function compileShader(gl, type, src) -{ - const shader = gl.createShader(type); - - gl.shaderSource(shader, src); - gl.compileShader(shader); - - if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) - { - console.warn(src); - console.error(gl.getShaderInfoLog(shader)); - - return null; - } - - return shader; -} diff --git a/packages/core/src/renderers/systems/shader/shader/defaultValue.js b/packages/core/src/renderers/systems/shader/shader/defaultValue.js deleted file mode 100644 index 97290bd..0000000 --- a/packages/core/src/renderers/systems/shader/shader/defaultValue.js +++ /dev/null @@ -1,79 +0,0 @@ -/** - * @method defaultValue - * @memberof PIXI.glCore.shader - * @param type {String} Type of value - * @param size {Number} - */ -export default function defaultValue(type, size) -{ - switch (type) - { - case 'float': - return 0; - - case 'vec2': - return new Float32Array(2 * size); - - case 'vec3': - return new Float32Array(3 * size); - - case 'vec4': - return new Float32Array(4 * size); - - case 'int': - case 'sampler2D': - case 'sampler2DArray': - return 0; - - case 'ivec2': - return new Int32Array(2 * size); - - case 'ivec3': - return new Int32Array(3 * size); - - case 'ivec4': - return new Int32Array(4 * size); - - case 'bool': - return false; - - case 'bvec2': - - return booleanArray(2 * size); - - case 'bvec3': - return booleanArray(3 * size); - - case 'bvec4': - return booleanArray(4 * size); - - case 'mat2': - return new Float32Array([1, 0, - 0, 1]); - - case 'mat3': - return new Float32Array([1, 0, 0, - 0, 1, 0, - 0, 0, 1]); - - case 'mat4': - return new Float32Array([1, 0, 0, 0, - 0, 1, 0, 0, - 0, 0, 1, 0, - 0, 0, 0, 1]); - } - - return null; -} - -function booleanArray(size) -{ - const array = new Array(size); - - for (let i = 0; i < array.length; i++) - { - array[i] = false; - } - - return array; -} diff --git a/packages/core/src/renderers/systems/shader/shader/index.js b/packages/core/src/renderers/systems/shader/shader/index.js deleted file mode 100644 index ba7a317..0000000 --- a/packages/core/src/renderers/systems/shader/shader/index.js +++ /dev/null @@ -1,8 +0,0 @@ -/** - * @namespace PIXI.glCore.shader - */ -export { default as compileProgram } from './compileProgram'; -export { default as defaultValue } from './defaultValue'; -export { default as setPrecision } from './setPrecision'; -export { default as mapSize } from './mapSize'; -export { default as mapType } from './mapType'; diff --git a/packages/core/src/renderers/systems/shader/shader/mapSize.js b/packages/core/src/renderers/systems/shader/shader/mapSize.js deleted file mode 100644 index b681554..0000000 --- a/packages/core/src/renderers/systems/shader/shader/mapSize.js +++ /dev/null @@ -1,33 +0,0 @@ -const GLSL_TO_SIZE = { - float: 1, - vec2: 2, - vec3: 3, - vec4: 4, - - int: 1, - ivec2: 2, - ivec3: 3, - ivec4: 4, - - bool: 1, - bvec2: 2, - bvec3: 3, - bvec4: 4, - - mat2: 4, - mat3: 9, - mat4: 16, - - sampler2D: 1, -}; - -/** - * @method mapSize - * @memberof PIXI.glCore.shader - * @param type {String} - * @return {Number} - */ -export default function mapSize(type) -{ - return GLSL_TO_SIZE[type]; -} diff --git a/packages/core/src/renderers/systems/shader/shader/mapType.js b/packages/core/src/renderers/systems/shader/shader/mapType.js deleted file mode 100644 index d288b7c..0000000 --- a/packages/core/src/renderers/systems/shader/shader/mapType.js +++ /dev/null @@ -1,45 +0,0 @@ -let GL_TABLE = null; - -const GL_TO_GLSL_TYPES = { - FLOAT: 'float', - FLOAT_VEC2: 'vec2', - FLOAT_VEC3: 'vec3', - FLOAT_VEC4: 'vec4', - - INT: 'int', - INT_VEC2: 'ivec2', - INT_VEC3: 'ivec3', - INT_VEC4: 'ivec4', - - BOOL: 'bool', - BOOL_VEC2: 'bvec2', - BOOL_VEC3: 'bvec3', - BOOL_VEC4: 'bvec4', - - FLOAT_MAT2: 'mat2', - FLOAT_MAT3: 'mat3', - FLOAT_MAT4: 'mat4', - - SAMPLER_2D: 'sampler2D', - SAMPLER_CUBE: 'samplerCube', - SAMPLER_2D_ARRAY: 'sampler2DArray', -}; - -export default function mapType(gl, type) -{ - if (!GL_TABLE) - { - const typeNames = Object.keys(GL_TO_GLSL_TYPES); - - GL_TABLE = {}; - - for (let i = 0; i < typeNames.length; ++i) - { - const tn = typeNames[i]; - - GL_TABLE[gl[tn]] = GL_TO_GLSL_TYPES[tn]; - } - } - - return GL_TABLE[type]; -} diff --git a/packages/core/src/renderers/systems/shader/shader/setPrecision.js b/packages/core/src/renderers/systems/shader/shader/setPrecision.js deleted file mode 100644 index e2c1f34..0000000 --- a/packages/core/src/renderers/systems/shader/shader/setPrecision.js +++ /dev/null @@ -1,18 +0,0 @@ -/** - * Sets the float precision on the shader. If the precision is already present this function will do nothing - * @param {string} src the shader source - * @param {string} precision The float precision of the shader. Options are 'lowp', 'mediump' or 'highp'. - * - * @return {string} modified shader source - */ -export default function setPrecision(src, precision) -{ - src = src.trim(); - - if (src.substring(0, 9) !== 'precision' && src.substring(0, 1) !== '#') - { - return `precision ${precision} float;\n${src}`; - } - - return src; -} diff --git a/packages/core/src/renderers/systems/textures/GLTexture.js b/packages/core/src/renderers/systems/textures/GLTexture.js deleted file mode 100644 index c3fb8dc..0000000 --- a/packages/core/src/renderers/systems/textures/GLTexture.js +++ /dev/null @@ -1,33 +0,0 @@ -export default class GLTexture -{ - constructor(texture) - { - /** - * The WebGL texture - * - * @member {WebGLTexture} - */ - this.texture = texture; - - this.width = -1; - this.height = -1; - - /** - * Texture contents dirty flag - * @member {number} - */ - this.dirtyId = -1; - - /** - * Texture style dirty flag - * @type {number} - */ - this.dirtyStyleId = -1; - - /** - * Whether mip levels has to be generated - * @type {boolean} - */ - this.mipmap = false; - } -} diff --git a/packages/core/src/renderers/systems/textures/TextureGCSystem.js b/packages/core/src/renderers/systems/textures/TextureGCSystem.js deleted file mode 100644 index b089938..0000000 --- a/packages/core/src/renderers/systems/textures/TextureGCSystem.js +++ /dev/null @@ -1,110 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; -import { GC_MODES } from '@pixi/constants'; -import { settings } from '@pixi/settings'; - -/** - * TextureGarbageCollector. This class manages the GPU and ensures that it does not get clogged - * up with textures that are no longer being used. - * - * @class - * @memberof PIXI - */ -export default class TextureGCSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this.count = 0; - this.checkCount = 0; - this.maxIdle = settings.GC_MAX_IDLE; - this.checkCountMax = settings.GC_MAX_CHECK_COUNT; - this.mode = settings.GC_MODE; - } - - /** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ - postrender() - { - this.count++; - - if (this.mode === GC_MODES.MANUAL) - { - return; - } - - this.checkCount++; - - if (this.checkCount > this.checkCountMax) - { - this.checkCount = 0; - - this.run(); - } - } - - /** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ - run() - { - const tm = this.renderer.texture; - const managedTextures = tm.managedTextures; - let wasRemoved = false; - - for (let i = 0; i < managedTextures.length; i++) - { - const texture = managedTextures[i]; - - // only supports non generated textures at the moment! - if (!texture.frameBuffer && this.count - texture.touched > this.maxIdle) - { - tm.destroyTexture(texture, true); - managedTextures[i] = null; - wasRemoved = true; - } - } - - if (wasRemoved) - { - let j = 0; - - for (let i = 0; i < managedTextures.length; i++) - { - if (managedTextures[i] !== null) - { - managedTextures[j++] = managedTextures[i]; - } - } - - managedTextures.length = j; - } - } - - /** - * Removes all the textures within the specified displayObject and its children from the GPU - * - * @param {PIXI.DisplayObject} displayObject - the displayObject to remove the textures from. - */ - unload(displayObject) - { - const tm = this.renderer.textureSystem; - - // only destroy non generated textures - if (displayObject._texture && displayObject._texture._glRenderTargets) - { - tm.destroyTexture(displayObject._texture); - } - - for (let i = displayObject.children.length - 1; i >= 0; i--) - { - this.unload(displayObject.children[i]); - } - } -} diff --git a/packages/core/src/renderers/systems/textures/TextureSystem.js b/packages/core/src/renderers/systems/textures/TextureSystem.js deleted file mode 100644 index ccc5de9..0000000 --- a/packages/core/src/renderers/systems/textures/TextureSystem.js +++ /dev/null @@ -1,281 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; -import GLTexture from './GLTexture'; -import { removeItems } from '@pixi/utils'; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class TextureSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - // TODO set to max textures... - this.boundTextures = [ - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - ]; - - this.currentLocation = -1; - - this.managedTextures = []; - } - - /** - * Sets up the renderer context and necessary buffers. - * - * @private - */ - contextChange() - { - const gl = this.gl = this.renderer.gl; - - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - - // TODO move this.. to a nice make empty textures class.. - this.emptyTextures = {}; - - const emptyTexture2D = new GLTexture(gl.createTexture()); - - gl.bindTexture(gl.TEXTURE_2D, emptyTexture2D.texture); - gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array(4)); - - this.emptyTextures[gl.TEXTURE_2D] = emptyTexture2D; - this.emptyTextures[gl.TEXTURE_CUBE_MAP] = new GLTexture(gl.createTexture()); - - gl.bindTexture(gl.TEXTURE_CUBE_MAP, this.emptyTextures[gl.TEXTURE_CUBE_MAP].texture); - - let i; - - for (i = 0; i < 6; i++) - { - gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, null); - } - - gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_MAG_FILTER, gl.LINEAR); - gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_MIN_FILTER, gl.LINEAR); - - for (i = 0; i < this.boundTextures.length; i++) - { - this.bind(null, i); - } - } - - bind(texture, location) - { - const gl = this.gl; - - location = location || 0; - - if (this.currentLocation !== location) - { - this.currentLocation = location; - gl.activeTexture(gl.TEXTURE0 + location); - } - - if (texture) - { - texture = texture.baseTexture || texture; - - if (texture.valid) - { - texture.touched = this.renderer.textureGC.count; - - const glTexture = texture._glTextures[this.CONTEXT_UID] || this.initTexture(texture); - - gl.bindTexture(texture.target, glTexture.texture); - - if (glTexture.dirtyId !== texture.dirtyId) - { - this.updateTexture(texture); - } - - this.boundTextures[location] = texture; - } - } - else - { - gl.bindTexture(gl.TEXTURE_2D, this.emptyTextures[gl.TEXTURE_2D].texture); - this.boundTextures[location] = null; - } - } - - unbind(texture) - { - const gl = this.gl; - - for (let i = 0; i < this.boundTextures.length; i++) - { - if (this.boundTextures[i] === texture) - { - if (this.currentLocation !== i) - { - gl.activeTexture(gl.TEXTURE0 + i); - this.currentLocation = i; - } - - gl.bindTexture(gl.TEXTURE_2D, this.emptyTextures[texture.target].texture); - this.boundTextures[i] = null; - } - } - } - - initTexture(texture) - { - const glTexture = new GLTexture(this.gl.createTexture()); - - // guarentee an update.. - glTexture.dirtyId = -1; - - texture._glTextures[this.CONTEXT_UID] = glTexture; - - this.managedTextures.push(texture); - texture.on('dispose', this.destroyTexture, this); - - return glTexture; - } - - updateTexture(texture) - { - const glTexture = texture._glTextures[this.CONTEXT_UID]; - const renderer = this.renderer; - - if (texture.resource && texture.resource.upload(renderer, texture, glTexture)) - { - // texture is uploaded, dont do anything! - } - else - { - // default, renderTexture-like logic - const width = texture.realWidth; - const height = texture.realHeight; - const gl = renderer.gl; - - if (glTexture.width !== width - || glTexture.height !== height - || glTexture.dirtyId < 0) - { - glTexture.width = width; - glTexture.height = height; - - gl.texImage2D(texture.target, 0, - texture.format, - width, - height, - 0, - texture.format, - texture.type, - null); - } - } - - // lets only update what changes.. - if (texture.dirtyStyleId !== glTexture.dirtyStyleId) - { - this.updateTextureStyle(texture); - } - glTexture.dirtyId = texture.dirtyId; - } - - /** - * Deletes the texture from WebGL - * - * @param {PIXI.BaseTexture|PIXI.Texture} texture - the texture to destroy - * @param {boolean} [skipRemove=false] - Whether to skip removing the texture from the TextureManager. - */ - destroyTexture(texture, skipRemove) - { - const gl = this.gl; - - texture = texture.baseTexture || texture; - - if (texture._glTextures[this.renderer.CONTEXT_UID]) - { - this.unbind(texture); - - gl.deleteTexture(texture._glTextures[this.renderer.CONTEXT_UID].texture); - texture.off('dispose', this.destroyTexture, this); - - delete texture._glTextures[this.renderer.CONTEXT_UID]; - - if (!skipRemove) - { - const i = this.managedTextures.indexOf(texture); - - if (i !== -1) - { - removeItems(this.managedTextures, i, 1); - } - } - } - } - - updateTextureStyle(texture) - { - const glTexture = texture._glTextures[this.CONTEXT_UID]; - - glTexture.mipmap = texture.mipmap && texture.isPowerOfTwo; - if (!glTexture) - { - return; - } - - if (texture.resource && texture.resource.style(this.renderer, texture, glTexture)) - { - // style is set, dont do anything! - } - else - { - this.setStyle(texture, glTexture); - } - - glTexture.dirtyStyleId = texture.dirtyStyleId; - } - - setStyle(texture, glTexture) - { - const gl = this.gl; - - if (glTexture.mipmap) - { - gl.generateMipmap(texture.target); - } - - gl.texParameteri(texture.target, gl.TEXTURE_WRAP_S, texture.wrapMode); - gl.texParameteri(texture.target, gl.TEXTURE_WRAP_T, texture.wrapMode); - - if (glTexture.mipmap) - { - /* eslint-disable max-len */ - gl.texParameteri(texture.target, gl.TEXTURE_MIN_FILTER, texture.scaleMode ? gl.LINEAR_MIPMAP_LINEAR : gl.NEAREST_MIPMAP_NEAREST); - /* eslint-disable max-len */ - } - else - { - gl.texParameteri(texture.target, gl.TEXTURE_MIN_FILTER, texture.scaleMode ? gl.LINEAR : gl.NEAREST); - } - - gl.texParameteri(texture.target, gl.TEXTURE_MAG_FILTER, texture.scaleMode ? gl.LINEAR : gl.NEAREST); - } -} diff --git a/packages/core/src/renderers/utils/ObjectRenderer.js b/packages/core/src/renderers/utils/ObjectRenderer.js deleted file mode 100644 index f67a273..0000000 --- a/packages/core/src/renderers/utils/ObjectRenderer.js +++ /dev/null @@ -1,48 +0,0 @@ -import WebGLSystem from '../systems/WebGLSystem'; - -/** - * Base for a common object renderer that can be used as a system renderer plugin. - * - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class ObjectRenderer extends WebGLSystem -{ - /** - * Starts the renderer and sets the shader - * - */ - start() - { - // set the shader.. - } - - /** - * Stops the renderer - * - */ - stop() - { - this.flush(); - } - - /** - * Stub method for rendering content and emptying the current batch. - * - */ - flush() - { - // flush! - } - - /** - * Renders an object - * - * @param {PIXI.DisplayObject} object - The object to render. - */ - render(object) // eslint-disable-line no-unused-vars - { - // render the object - } -} diff --git a/packages/core/src/renderers/utils/Quad.js b/packages/core/src/renderers/utils/Quad.js deleted file mode 100644 index 5c1517b..0000000 --- a/packages/core/src/renderers/utils/Quad.js +++ /dev/null @@ -1,26 +0,0 @@ -import Geometry from '../../geometry/Geometry'; - -/** - * Helper class to create a quad - * - * @class - * @memberof PIXI - */ -export default class Quad extends Geometry -{ - /** - * just a quad - */ - constructor() - { - super(); - - this.addAttribute('aVertexPosition', [ - 0, 0, - 1, 0, - 1, 1, - 0, 1, - ]) - .addIndex([0, 1, 3, 2]); - } -} diff --git a/packages/core/src/renderers/utils/QuadUv.js b/packages/core/src/renderers/utils/QuadUv.js deleted file mode 100644 index 97bda89..0000000 --- a/packages/core/src/renderers/utils/QuadUv.js +++ /dev/null @@ -1,103 +0,0 @@ -import Geometry from '../../geometry/Geometry'; -import Buffer from '../../geometry/Buffer'; - -/** - * Helper class to create a quad with uvs like in v4 - * - * @class - * @memberof PIXI - */ -export default class QuadUv extends Geometry -{ - constructor() - { - super(); - - /** - * An array of vertices - * - * @member {Float32Array} - */ - this.vertices = new Float32Array([ - -1, -1, - 1, -1, - 1, 1, - -1, 1, - ]); - - /** - * The Uvs of the quad - * - * @member {Float32Array} - */ - this.uvs = new Float32Array([ - 0, 0, - 1, 0, - 1, 1, - 0, 1, - ]); - - this.vertexBuffer = new Buffer(this.vertices); - this.uvBuffer = new Buffer(this.uvs); - - this.addAttribute('aVertexPosition', this.vertexBuffer) - .addAttribute('aTextureCoord', this.uvBuffer) - .addIndex([0, 1, 2, 0, 2, 3]); - } - - /** - * Maps two Rectangle to the quad. - * - * @param {PIXI.Rectangle} targetTextureFrame - the first rectangle - * @param {PIXI.Rectangle} destinationFrame - the second rectangle - * @return {PIXI.Quad} Returns itself. - */ - map(targetTextureFrame, destinationFrame) - { - let x = 0; // destinationFrame.x / targetTextureFrame.width; - let y = 0; // destinationFrame.y / targetTextureFrame.height; - - this.uvs[0] = x; - this.uvs[1] = y; - - this.uvs[2] = x + (destinationFrame.width / targetTextureFrame.width); - this.uvs[3] = y; - - this.uvs[4] = x + (destinationFrame.width / targetTextureFrame.width); - this.uvs[5] = y + (destinationFrame.height / targetTextureFrame.height); - - this.uvs[6] = x; - this.uvs[7] = y + (destinationFrame.height / targetTextureFrame.height); - - x = destinationFrame.x; - y = destinationFrame.y; - - this.vertices[0] = x; - this.vertices[1] = y; - - this.vertices[2] = x + destinationFrame.width; - this.vertices[3] = y; - - this.vertices[4] = x + destinationFrame.width; - this.vertices[5] = y + destinationFrame.height; - - this.vertices[6] = x; - this.vertices[7] = y + destinationFrame.height; - - this.invalidate(); - - return this; - } - - /** - * legacy upload method, just marks buffers dirty - * @returns {PIXI.QuadUv} Returns itself. - */ - invalidate() - { - this.vertexBuffer._updateID++; - this.uvBuffer._updateID++; - - return this; - } -} diff --git a/packages/core/src/renderers/utils/checkMaxIfStatmentsInShader.js b/packages/core/src/renderers/utils/checkMaxIfStatmentsInShader.js deleted file mode 100644 index 8047c96..0000000 --- a/packages/core/src/renderers/utils/checkMaxIfStatmentsInShader.js +++ /dev/null @@ -1,58 +0,0 @@ -const fragTemplate = [ - 'precision mediump float;', - 'void main(void){', - 'float test = 0.1;', - '%forloop%', - 'gl_FragColor = vec4(0.0);', - '}', -].join('\n'); - -export default function checkMaxIfStatmentsInShader(maxIfs, gl) -{ - if (maxIfs === 0) - { - throw new Error('Invalid value of `0` passed to `checkMaxIfStatementsInShader`'); - } - - const shader = gl.createShader(gl.FRAGMENT_SHADER); - - while (true) // eslint-disable-line no-constant-condition - { - const fragmentSrc = fragTemplate.replace(/%forloop%/gi, generateIfTestSrc(maxIfs)); - - gl.shaderSource(shader, fragmentSrc); - gl.compileShader(shader); - - if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) - { - maxIfs = (maxIfs / 2) | 0; - } - else - { - // valid! - break; - } - } - - return maxIfs; -} - -function generateIfTestSrc(maxIfs) -{ - let src = ''; - - for (let i = 0; i < maxIfs; ++i) - { - if (i > 0) - { - src += '\nelse '; - } - - if (i < maxIfs - 1) - { - src += `if(test == ${i}.0){}`; - } - } - - return src; -} diff --git a/packages/core/src/renderers/utils/mapWebGLBlendModesToPixi.js b/packages/core/src/renderers/utils/mapWebGLBlendModesToPixi.js deleted file mode 100644 index 10ffe4d..0000000 --- a/packages/core/src/renderers/utils/mapWebGLBlendModesToPixi.js +++ /dev/null @@ -1,47 +0,0 @@ -import { BLEND_MODES } from '@pixi/constants'; - -/** - * Maps gl blend combinations to WebGL. - * - * @memberof PIXI - * @function mapWebGLBlendModesToPixi - * @private - * @param {WebGLRenderingContext} gl - The rendering context. - * @param {string[]} [array=[]] - The array to output into. - * @return {string[]} Mapped modes. - */ -export default function mapWebGLBlendModesToPixi(gl, array = []) -{ - // TODO - premultiply alpha would be different. - // add a boolean for that! - array[BLEND_MODES.NORMAL] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.ADD] = [gl.ONE, gl.DST_ALPHA]; - array[BLEND_MODES.MULTIPLY] = [gl.DST_COLOR, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.SCREEN] = [gl.ONE, gl.ONE_MINUS_SRC_COLOR]; - array[BLEND_MODES.OVERLAY] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.DARKEN] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.LIGHTEN] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.COLOR_DODGE] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.COLOR_BURN] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.HARD_LIGHT] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.SOFT_LIGHT] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.DIFFERENCE] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.EXCLUSION] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.HUE] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.SATURATION] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.COLOR] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.LUMINOSITY] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.NONE] = [0, 0]; - - // not-premultiplied blend modes - array[BLEND_MODES.NORMAL_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.ADD_NPM] = [gl.SRC_ALPHA, gl.DST_ALPHA, gl.ONE, gl.DST_ALPHA]; - array[BLEND_MODES.SCREEN_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_COLOR, gl.ONE, gl.ONE_MINUS_SRC_COLOR]; - - // not-premultiplied blend modes - array[BLEND_MODES.NORMAL_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.ADD_NPM] = [gl.SRC_ALPHA, gl.DST_ALPHA, gl.ONE, gl.DST_ALPHA]; - array[BLEND_MODES.SCREEN_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_COLOR, gl.ONE, gl.ONE_MINUS_SRC_COLOR]; - - return array; -} diff --git a/packages/core/src/shader/GLProgram.js b/packages/core/src/shader/GLProgram.js new file mode 100644 index 0000000..92980ca --- /dev/null +++ b/packages/core/src/shader/GLProgram.js @@ -0,0 +1,43 @@ +/** + * Helper class to create a webGL Program + * + * @class + * @private + */ +export default class GLProgram +{ + constructor(program, uniformData) + { + /** + * The shader program + * + * @member {WebGLProgram} + */ + this.program = program; + + /** + * holds the uniform data which contains uniform locations + * and current uniform values used for caching and preventing undeed GPU commands + * @type {Object} + */ + this.uniformData = uniformData; + + /** + * uniformGroups holds the various upload functions for the shader. Each uniform group + * and program have a unique upload function generated. + * @type {Object} + */ + this.uniformGroups = {}; + } + + /** + * Destroys this program + * TODO + */ + destroy() + { + this.uniformData = null; + this.uniformGroups = null; + this.program = null; + } +} diff --git a/packages/core/src/shader/Program.js b/packages/core/src/shader/Program.js index 4f9971e..4b4007a 100644 --- a/packages/core/src/shader/Program.js +++ b/packages/core/src/shader/Program.js @@ -1,6 +1,11 @@ -import * as shaderUtils from '../renderers/systems/shader/shader'; +// import * as from '../systems/shader/shader'; +import { setPrecision, + defaultValue, + compileProgram, + mapSize, + mapType, + getTestContext } from './utils'; import { ProgramCache } from '@pixi/utils'; -import getTestContext from './getTestContext'; import defaultFragment from './defaultProgram.frag'; import defaultVertex from './defaultProgram.vert'; import { settings } from '@pixi/settings'; @@ -34,8 +39,8 @@ */ this.fragmentSrc = fragmentSrc || Program.defaultFragmentSrc; - this.vertexSrc = shaderUtils.setPrecision(this.vertexSrc, settings.PRECISION_VERTEX); - this.fragmentSrc = shaderUtils.setPrecision(this.fragmentSrc, settings.PRECISION_FRAGMENT); + this.vertexSrc = setPrecision(this.vertexSrc, settings.PRECISION_VERTEX); + this.fragmentSrc = setPrecision(this.fragmentSrc, settings.PRECISION_FRAGMENT); // currently this does not extract structs only default types this.extractData(this.vertexSrc, this.fragmentSrc); @@ -62,7 +67,7 @@ if (gl) { - const program = shaderUtils.compileProgram(gl, vertexSrc, fragmentSrc); + const program = compileProgram(gl, vertexSrc, fragmentSrc); this.attributeData = this.getAttributeData(program, gl); this.uniformData = this.getUniformData(program, gl); @@ -95,13 +100,13 @@ for (let i = 0; i < totalAttributes; i++) { const attribData = gl.getActiveAttrib(program, i); - const type = shaderUtils.mapType(gl, attribData.type); + const type = mapType(gl, attribData.type); /*eslint-disable */ const data = { type: type, name: attribData.name, - size: shaderUtils.mapSize(type), + size: mapSize(type), location: 0, }; /* eslint-enable */ @@ -145,14 +150,14 @@ const name = uniformData.name.replace(/\[.*?\]/, ''); const isArray = uniformData.name.match(/\[.*?\]/, ''); - const type = shaderUtils.mapType(gl, uniformData.type); + const type = mapType(gl, uniformData.type); /*eslint-disable */ uniforms[name] = { type: type, size: uniformData.size, isArray:isArray, - value: shaderUtils.defaultValue(type, uniformData.size), + value: defaultValue(type, uniformData.size), }; /* eslint-enable */ } diff --git a/packages/core/src/shader/ShaderSystem.js b/packages/core/src/shader/ShaderSystem.js new file mode 100644 index 0000000..40b8af8 --- /dev/null +++ b/packages/core/src/shader/ShaderSystem.js @@ -0,0 +1,171 @@ +import System from '../System'; +import GLProgram from './GLProgram'; +import { generateUniformsSync, + defaultValue, + compileProgram } from './utils'; + +let UID = 0; + +/** + * Helper class to create a webGL Texture + * + * @class + * @memberof PIXI.systems + * @extends PIXI.System + */ +export default class ShaderSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - A reference to the current renderer + */ + constructor(renderer) + { + super(renderer); + + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = null; + + this.shader = null; + this.program = null; + + this.id = UID++; + } + + contextChange(gl) + { + this.gl = gl; + } + + /** + * Changes the current shader to the one given in parameter + * + * @param {PIXI.Shader} shader - the new shader + * @param {boolean} dontSync - false if the shader should automatically sync its uniforms. + * @returns {PIXI.glCore.glProgram} the glProgram that belongs to the shader. + */ + bind(shader, dontSync) + { + shader.uniforms.globals = this.renderer.globalUniforms; + + const program = shader.program; + const glProgram = program.glPrograms[this.renderer.CONTEXT_UID] || this.generateShader(shader); + + this.shader = shader; + + // TODO - some current pixi plugins bypass this.. so it not safe to use yet.. + if (this.program !== program) + { + this.program = program; + this.gl.useProgram(glProgram.program); + } + + if (!dontSync) + { + this.syncUniformGroup(shader.uniformGroup); + } + + return glProgram; + } + + /** + * Uploads the uniforms values to the currently bound shader. + * + * @param {object} uniforms - the uniforms valiues that be applied to the current shader + */ + setUniforms(uniforms) + { + const shader = this.shader.program; + const glProgram = shader.glPrograms[this.renderer.CONTEXT_UID]; + + shader.syncUniforms(glProgram.uniformData, uniforms, this.renderer); + } + + syncUniformGroup(group) + { + const glProgram = this.getglProgram(); + + if (!group.static || group.dirtyId !== glProgram.uniformGroups[group.id]) + { + glProgram.uniformGroups[group.id] = group.dirtyId; + const syncFunc = group.syncUniforms[this.shader.program.id] || this.createSyncGroups(group); + + syncFunc(glProgram.uniformData, group.uniforms, this.renderer); + } + } + + createSyncGroups(group) + { + group.syncUniforms[this.shader.program.id] = generateUniformsSync(group, this.shader.program.uniformData); + + return group.syncUniforms[this.shader.program.id]; + } + + /** + * Returns the underlying GLShade rof the currently bound shader. + * This can be handy for when you to have a little more control over the setting of your uniforms. + * + * @return {PIXI.glCore.Shader} the glProgram for the currently bound Shader for this context + */ + getglProgram() + { + if (this.shader) + { + return this.shader.program.glPrograms[this.renderer.CONTEXT_UID]; + } + + return null; + } + + /** + * Generates a glProgram verion of the Shader provided. + * + * @private + * @param {PIXI.Shader} shader the shader that the glProgram will be based on. + * @return {PIXI.glCore.glProgram} A shiney new glProgram + */ + generateShader(shader) + { + const gl = this.gl; + + const program = shader.program; + + const attribMap = {}; + + for (const i in program.attributeData) + { + attribMap[i] = program.attributeData[i].location; + } + + const shaderProgram = compileProgram(gl, program.vertexSrc, program.fragmentSrc, attribMap); + const uniformData = {}; + + for (const i in program.uniformData) + { + const data = program.uniformData[i]; + + uniformData[i] = { + location: gl.getUniformLocation(shaderProgram, i), + value: defaultValue(data.type, data.size), + }; + } + + const glProgram = new GLProgram(shaderProgram, uniformData); + + program.glPrograms[this.renderer.CONTEXT_UID] = glProgram; + + return glProgram; + } + + /** + * Destroys this System and removes all its textures + */ + destroy() + { + // TODO implement destroy method for ShaderSystem + this.destroyed = true; + } +} diff --git a/packages/core/src/shader/defaultValue.js b/packages/core/src/shader/defaultValue.js deleted file mode 100644 index 847a069..0000000 --- a/packages/core/src/shader/defaultValue.js +++ /dev/null @@ -1,78 +0,0 @@ -function booleanArray(size) -{ - const array = new Array(size); - - for (let i = 0; i < array.length; i++) - { - array[i] = false; - } - - return array; -} - -/** - * @class - * @memberof PIXI.glCore.shader - * @param type {String} Type of value - * @param size {Number} - */ -export default function defaultValue(type, size) -{ - switch (type) - { - case 'float': - return 0; - - case 'vec2': - return new Float32Array(2 * size); - - case 'vec3': - return new Float32Array(3 * size); - - case 'vec4': - return new Float32Array(4 * size); - - case 'int': - case 'sampler2D': - return 0; - - case 'ivec2': - return new Int32Array(2 * size); - - case 'ivec3': - return new Int32Array(3 * size); - - case 'ivec4': - return new Int32Array(4 * size); - - case 'bool': - return false; - - case 'bvec2': - - return booleanArray(2 * size); - - case 'bvec3': - return booleanArray(3 * size); - - case 'bvec4': - return booleanArray(4 * size); - - case 'mat2': - return new Float32Array([1, 0, - 0, 1]); - - case 'mat3': - return new Float32Array([1, 0, 0, - 0, 1, 0, - 0, 0, 1]); - - case 'mat4': - return new Float32Array([1, 0, 0, 0, - 0, 1, 0, 0, - 0, 0, 1, 0, - 0, 0, 0, 1]); - } - - return 0; -} diff --git a/packages/core/src/shader/extractUniformsFromSrc.js b/packages/core/src/shader/extractUniformsFromSrc.js deleted file mode 100644 index 3d02c0a..0000000 --- a/packages/core/src/shader/extractUniformsFromSrc.js +++ /dev/null @@ -1,57 +0,0 @@ -import defaultValue from './defaultValue'; - -export default function extractUniformsFromSrc(vertexSrc, fragmentSrc, mask) -{ - const vertUniforms = extractUniformsFromString(vertexSrc, mask); - const fragUniforms = extractUniformsFromString(fragmentSrc, mask); - - return Object.assign(vertUniforms, fragUniforms); -} - -function extractUniformsFromString(string) -{ - const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); - - const uniforms = {}; - let nameSplit; - - // clean the lines a little - remove extra spaces / teabs etc - // then split along ';' - const lines = string.replace(/\s+/g, ' ') - .split(/\s*;\s*/); - - // loop through.. - for (let i = 0; i < lines.length; i++) - { - const line = lines[i].trim(); - - if (line.indexOf('uniform') > -1) - { - const splitLine = line.split(' '); - const type = splitLine[1]; - - let name = splitLine[2]; - let size = 1; - - if (name.indexOf('[') > -1) - { - // array! - nameSplit = name.split(/\[|]/); - name = nameSplit[0]; - size *= Number(nameSplit[1]); - } - - if (!name.match(maskRegex)) - { - uniforms[name] = { - value: defaultValue(type, size), - dirtyId: 0, - name, - type, - }; - } - } - } - - return uniforms; -} diff --git a/packages/core/src/shader/generateUniformsSync.js b/packages/core/src/shader/generateUniformsSync.js deleted file mode 100644 index 98a357f..0000000 --- a/packages/core/src/shader/generateUniformsSync.js +++ /dev/null @@ -1,231 +0,0 @@ -// cv = CachedValue -// v = value -// ud = uniformData -// uv = uniformValue -// l = loaction -const GLSL_TO_SINGLE_SETTERS_CACHED = { - - float: ` - if(cv !== v) - { - cv.v = v; - gl.uniform1f(location, v) - }`, - - vec2: ` - if(cv[0] !== v[0] || cv[1] !== v[1]) - { - cv[0] = v[0]; - cv[1] = v[1]; - gl.uniform2f(location, v[0], v[1]) - }`, - - vec3: ` - if(cv[0] !== v[0] || cv[1] !== v[1] || cv[2] !== v[2]) - { - cv[0] = v[0]; - cv[1] = v[1]; - cv[2] = v[2]; - - gl.uniform3f(location, v[0], v[1], v[2]) - }`, - - vec4: 'gl.uniform4f(location, v[0], v[1], v[2], v[3])', - - int: 'gl.uniform1i(location, v)', - ivec2: 'gl.uniform2i(location, v[0], v[1])', - ivec3: 'gl.uniform3i(location, v[0], v[1], v[2])', - ivec4: 'gl.uniform4i(location, v[0], v[1], v[2], v[3])', - - bool: 'gl.uniform1i(location, v)', - bvec2: 'gl.uniform2i(location, v[0], v[1])', - bvec3: 'gl.uniform3i(location, v[0], v[1], v[2])', - bvec4: 'gl.uniform4i(location, v[0], v[1], v[2], v[3])', - - mat2: 'gl.uniformMatrix2fv(location, false, v)', - mat3: 'gl.uniformMatrix3fv(location, false, v)', - mat4: 'gl.uniformMatrix4fv(location, false, v)', - - sampler2D: 'gl.uniform1i(location, v)', - samplerCube: 'gl.uniform1i(location, v)', - sampler2DArray: 'gl.uniform1i(location, v)', -}; - -const GLSL_TO_ARRAY_SETTERS = { - - float: `gl.uniform1fv(location, v)`, - - vec2: `gl.uniform2fv(location, v)`, - vec3: `gl.uniform3fv(location, v)`, - vec4: 'gl.uniform4fv(location, v)', - - mat4: 'gl.uniformMatrix4fv(location, false, v)', - mat3: 'gl.uniformMatrix3fv(location, false, v)', - mat2: 'gl.uniformMatrix2fv(location, false, v)', - - int: 'gl.uniform1iv(location, v)', - ivec2: 'gl.uniform2iv(location, v)', - ivec3: 'gl.uniform3iv(location, v)', - ivec4: 'gl.uniform4iv(location, v)', - - bool: 'gl.uniform1iv(location, v)', - bvec2: 'gl.uniform2iv(location, v)', - bvec3: 'gl.uniform3iv(location, v)', - bvec4: 'gl.uniform4iv(location, v)', - - sampler2D: 'gl.uniform1iv(location, v)', - samplerCube: 'gl.uniform1iv(location, v)', - sampler2DArray: 'gl.uniform1iv(location, v)', -}; - -export default function generateUniformsSync(group, uniformData) -{ - let textureCount = 0; - let func = `var v = null; - var cv = null - var gl = renderer.gl`; - - for (const i in group.uniforms) - { - const data = uniformData[i]; - - if (!data) - { - if (group.uniforms[i].group) - { - func += ` - renderer.shader.syncUniformGroup(uv.${i}); - `; - } - - continue; - } - - // TODO && uniformData[i].value !== 0 <-- do we still need this? - if (data.type === 'float' && data.size === 1) - { - func += ` - if(uv.${i} !== ud.${i}.value) - { - ud.${i}.value = uv.${i} - gl.uniform1f(ud.${i}.location, uv.${i}) - }\n`; - } - /* eslint-disable max-len */ - else if ((data.type === 'sampler2D' || data.type === 'samplerCube' || data.type === 'sampler2DArray') && data.size === 1 && !data.isArray) - /* eslint-disable max-len */ - { - func += ` - renderer.texture.bind(uv.${i}, ${textureCount}); - - if(ud.${i}.value !== ${textureCount}) - { - ud.${i}.value = ${textureCount}; - gl.uniform1i(ud.${i}.location, ${textureCount});\n; // eslint-disable-line max-len - }\n`; - - textureCount++; - } - else if (data.type === 'mat3' && data.size === 1) - { - if (group.uniforms[i].a !== undefined) - { - // TODO and some smart caching dirty ids here! - func += ` - gl.uniformMatrix3fv(ud.${i}.location, false, uv.${i}.toArray(true)); - \n`; - } - else - { - func += ` - gl.uniformMatrix3fv(ud.${i}.location, false, uv.${i}); - \n`; - } - } - else if (data.type === 'vec2' && data.size === 1) - { - // TODO - do we need both here? - // maybe we can get away with only using points? - if (group.uniforms[i].x !== undefined) - { - func += ` - cv = ud.${i}.value; - v = uv.${i}; - - if(cv[0] !== v.x || cv[1] !== v.y) - { - cv[0] = v.x; - cv[1] = v.y; - gl.uniform2f(ud.${i}.location, v.x, v.y); - }\n`; - } - else - { - func += ` - cv = ud.${i}.value; - v = uv.${i}; - - if(cv[0] !== v[0] || cv[1] !== v[1]) - { - cv[0] = v[0]; - cv[1] = v[1]; - gl.uniform2f(ud.${i}.location, v[0], v[1]); - } - \n`; - } - } - else if (data.type === 'vec4' && data.size === 1) - { - // TODO - do we need both here? - // maybe we can get away with only using points? - if (group.uniforms[i].width !== undefined) - { - func += ` - cv = ud.${i}.value; - v = uv.${i}; - - if(cv[0] !== v.x || cv[1] !== v.y || cv[2] !== v.width || cv[3] !== v.height) - { - cv[0] = v.x; - cv[1] = v.y; - cv[2] = v.width; - cv[3] = v.height; - gl.uniform4f(ud.${i}.location, v.x, v.y, v.width, v.height) - }\n`; - } - else - { - func += ` - cv = ud.${i}.value; - v = uv.${i}; - - if(cv[0] !== v[0] || cv[1] !== v[1] || cv[2] !== v[2] || cv[3] !== v[3]) - { - cv[0] = v[0]; - cv[1] = v[1]; - cv[2] = v[2]; - cv[3] = v[3]; - - gl.uniform4f(ud.${i}.location, v[0], v[1], v[2], v[3]) - } - \n`; - } - } - else - { - const templateType = (data.size === 1) ? GLSL_TO_SINGLE_SETTERS_CACHED : GLSL_TO_ARRAY_SETTERS; - - const template = templateType[data.type].replace('location', `ud.${i}.location`); - - func += ` - cv = ud.${i}.value; - v = uv.${i}; - ${template};\n`; - } - } - - // console.log(' --------------- ') - // console.log(func); - - return new Function('ud', 'uv', 'renderer', func); // eslint-disable-line no-new-func -} diff --git a/packages/core/src/shader/getTestContext.js b/packages/core/src/shader/getTestContext.js deleted file mode 100644 index ea174cc..0000000 --- a/packages/core/src/shader/getTestContext.js +++ /dev/null @@ -1,48 +0,0 @@ -import { settings } from '@pixi/settings'; - -let context = null; - -/** - * returns a little webGL context to use for program inspection. - * - * @static - * @private - * @returns {webGL-context} a gl context to test with - */ -export default function getTestContext() -{ - if (!context) - { - const canvas = document.createElement('canvas'); - - let gl; - - if (settings.PREFER_WEBGL_2) - { - gl = canvas.getContext('webgl2', {}); - } - - if (!gl) - { - gl = canvas.getContext('webgl', {}) - || canvas.getContext('experimental-webgl', {}); - - if (!gl) - { - // fail, not able to get a context - throw new Error('This browser does not support webGL. Try using the canvas renderer'); - } - else - { - // for shader testing.. - gl.getExtension('WEBGL_draw_buffers'); - } - } - - context = gl; - - return gl; - } - - return context; -} diff --git a/packages/core/src/shader/utils/checkMaxIfStatmentsInShader.js b/packages/core/src/shader/utils/checkMaxIfStatmentsInShader.js new file mode 100644 index 0000000..8047c96 --- /dev/null +++ b/packages/core/src/shader/utils/checkMaxIfStatmentsInShader.js @@ -0,0 +1,58 @@ +const fragTemplate = [ + 'precision mediump float;', + 'void main(void){', + 'float test = 0.1;', + '%forloop%', + 'gl_FragColor = vec4(0.0);', + '}', +].join('\n'); + +export default function checkMaxIfStatmentsInShader(maxIfs, gl) +{ + if (maxIfs === 0) + { + throw new Error('Invalid value of `0` passed to `checkMaxIfStatementsInShader`'); + } + + const shader = gl.createShader(gl.FRAGMENT_SHADER); + + while (true) // eslint-disable-line no-constant-condition + { + const fragmentSrc = fragTemplate.replace(/%forloop%/gi, generateIfTestSrc(maxIfs)); + + gl.shaderSource(shader, fragmentSrc); + gl.compileShader(shader); + + if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) + { + maxIfs = (maxIfs / 2) | 0; + } + else + { + // valid! + break; + } + } + + return maxIfs; +} + +function generateIfTestSrc(maxIfs) +{ + let src = ''; + + for (let i = 0; i < maxIfs; ++i) + { + if (i > 0) + { + src += '\nelse '; + } + + if (i < maxIfs - 1) + { + src += `if(test == ${i}.0){}`; + } + } + + return src; +} diff --git a/packages/core/src/shader/utils/compileProgram.js b/packages/core/src/shader/utils/compileProgram.js new file mode 100644 index 0000000..8c6ccb9 --- /dev/null +++ b/packages/core/src/shader/utils/compileProgram.js @@ -0,0 +1,78 @@ +/** + * @method compileProgram + * @memberof PIXI.glCore.shader + * @param gl {WebGLRenderingContext} The current WebGL context {WebGLProgram} + * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. + * @param fragmentSrc {string|string[]} The fragment shader source as an array of strings. + * @param attributeLocations {Object} An attribute location map that lets you manually set the attribute locations + * @return {WebGLProgram} the shader program + */ +export default function compileProgram(gl, vertexSrc, fragmentSrc, attributeLocations) +{ + const glVertShader = compileShader(gl, gl.VERTEX_SHADER, vertexSrc); + const glFragShader = compileShader(gl, gl.FRAGMENT_SHADER, fragmentSrc); + + let program = gl.createProgram(); + + gl.attachShader(program, glVertShader); + gl.attachShader(program, glFragShader); + + // optionally, set the attributes manually for the program rather than letting WebGL decide.. + if (attributeLocations) + { + for (const i in attributeLocations) + { + gl.bindAttribLocation(program, attributeLocations[i], i); + } + } + + gl.linkProgram(program); + + // if linking fails, then log and cleanup + if (!gl.getProgramParameter(program, gl.LINK_STATUS)) + { + console.error('Pixi.js Error: Could not initialize shader.'); + console.error('gl.VALIDATE_STATUS', gl.getProgramParameter(program, gl.VALIDATE_STATUS)); + console.error('gl.getError()', gl.getError()); + + // if there is a program info log, log it + if (gl.getProgramInfoLog(program) !== '') + { + console.warn('Pixi.js Warning: gl.getProgramInfoLog()', gl.getProgramInfoLog(program)); + } + + gl.deleteProgram(program); + program = null; + } + + // clean up some shaders + gl.deleteShader(glVertShader); + gl.deleteShader(glFragShader); + + return program; +} + +/** + * @private + * @param gl {WebGLRenderingContext} The current WebGL context {WebGLProgram} + * @param type {Number} the type, can be either VERTEX_SHADER or FRAGMENT_SHADER + * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. + * @return {WebGLShader} the shader + */ +function compileShader(gl, type, src) +{ + const shader = gl.createShader(type); + + gl.shaderSource(shader, src); + gl.compileShader(shader); + + if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) + { + console.warn(src); + console.error(gl.getShaderInfoLog(shader)); + + return null; + } + + return shader; +} diff --git a/packages/core/src/shader/utils/defaultValue.js b/packages/core/src/shader/utils/defaultValue.js new file mode 100644 index 0000000..97290bd --- /dev/null +++ b/packages/core/src/shader/utils/defaultValue.js @@ -0,0 +1,79 @@ +/** + * @method defaultValue + * @memberof PIXI.glCore.shader + * @param type {String} Type of value + * @param size {Number} + */ +export default function defaultValue(type, size) +{ + switch (type) + { + case 'float': + return 0; + + case 'vec2': + return new Float32Array(2 * size); + + case 'vec3': + return new Float32Array(3 * size); + + case 'vec4': + return new Float32Array(4 * size); + + case 'int': + case 'sampler2D': + case 'sampler2DArray': + return 0; + + case 'ivec2': + return new Int32Array(2 * size); + + case 'ivec3': + return new Int32Array(3 * size); + + case 'ivec4': + return new Int32Array(4 * size); + + case 'bool': + return false; + + case 'bvec2': + + return booleanArray(2 * size); + + case 'bvec3': + return booleanArray(3 * size); + + case 'bvec4': + return booleanArray(4 * size); + + case 'mat2': + return new Float32Array([1, 0, + 0, 1]); + + case 'mat3': + return new Float32Array([1, 0, 0, + 0, 1, 0, + 0, 0, 1]); + + case 'mat4': + return new Float32Array([1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1]); + } + + return null; +} + +function booleanArray(size) +{ + const array = new Array(size); + + for (let i = 0; i < array.length; i++) + { + array[i] = false; + } + + return array; +} diff --git a/packages/core/src/shader/utils/extractUniformsFromSrc.js b/packages/core/src/shader/utils/extractUniformsFromSrc.js new file mode 100644 index 0000000..3d02c0a --- /dev/null +++ b/packages/core/src/shader/utils/extractUniformsFromSrc.js @@ -0,0 +1,57 @@ +import defaultValue from './defaultValue'; + +export default function extractUniformsFromSrc(vertexSrc, fragmentSrc, mask) +{ + const vertUniforms = extractUniformsFromString(vertexSrc, mask); + const fragUniforms = extractUniformsFromString(fragmentSrc, mask); + + return Object.assign(vertUniforms, fragUniforms); +} + +function extractUniformsFromString(string) +{ + const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); + + const uniforms = {}; + let nameSplit; + + // clean the lines a little - remove extra spaces / teabs etc + // then split along ';' + const lines = string.replace(/\s+/g, ' ') + .split(/\s*;\s*/); + + // loop through.. + for (let i = 0; i < lines.length; i++) + { + const line = lines[i].trim(); + + if (line.indexOf('uniform') > -1) + { + const splitLine = line.split(' '); + const type = splitLine[1]; + + let name = splitLine[2]; + let size = 1; + + if (name.indexOf('[') > -1) + { + // array! + nameSplit = name.split(/\[|]/); + name = nameSplit[0]; + size *= Number(nameSplit[1]); + } + + if (!name.match(maskRegex)) + { + uniforms[name] = { + value: defaultValue(type, size), + dirtyId: 0, + name, + type, + }; + } + } + } + + return uniforms; +} diff --git a/packages/core/src/AbstractRenderer.js b/packages/core/src/AbstractRenderer.js new file mode 100644 index 0000000..64b2eb1 --- /dev/null +++ b/packages/core/src/AbstractRenderer.js @@ -0,0 +1,353 @@ +import { hex2string, hex2rgb } from '@pixi/utils'; +import { Matrix, Rectangle } from '@pixi/math'; +import { RENDERER_TYPE } from '@pixi/constants'; +import { settings } from '@pixi/settings'; +import { Container } from '@pixi/display'; +import RenderTexture from './renderTexture/RenderTexture'; +import EventEmitter from 'eventemitter3'; + +const tempMatrix = new Matrix(); + +/** + * 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 EventEmitter + * @memberof PIXI + */ +export default class AbstractRenderer extends EventEmitter +{ + // eslint-disable-next-line valid-jsdoc + /** + * @param {string} system - The name of the system this renderer is for. + * @param {object} [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.transparent=false] - If the render view is transparent, default false + * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false + * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. The + * resolution of the renderer retina would be 2. + * @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 {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when rendering, + * stopping pixel interpolation. + */ + constructor(system, options, arg2, arg3) + { + super(); + + // Support for constructor(system, screenWidth, screenHeight, options) + if (typeof options === 'number') + { + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + }, arg3); + } + + // 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 = RENDERER_TYPE.UNKNOWN; + + /** + * Measurements of the screen. (0, 0, screenWidth, screenHeight) + * + * Its safe to use as filterArea or hitArea for 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 1 + */ + this.resolution = options.resolution || settings.RESOLUTION; + + /** + * Whether the render view is transparent + * + * @member {boolean} + */ + this.transparent = options.transparent; + + /** + * Whether css dimensions of canvas view should be resized to screen dimensions automatically + * + * @member {boolean} + */ + this.autoResize = options.autoResize || false; + + /** + * Tracks the blend modes useful for this renderer. + * + * @member {object} + */ + this.blendModes = null; + + /** + * 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; + + /** + * If true PixiJS will Math.floor() x/y values when rendering, stopping pixel interpolation. + * Handy for crisp pixel art and speed on legacy devices. + * + * @member {boolean} + */ + this.roundPixels = options.roundPixels; + + /** + * The background color as a number. + * + * @member {number} + * @private + */ + this._backgroundColor = 0x000000; + + /** + * The background color as an [R, G, B] array. + * + * @member {number[]} + * @private + */ + this._backgroundColorRgba = [0, 0, 0, 0]; + + /** + * The background color as a string. + * + * @member {string} + * @private + */ + this._backgroundColorString = '#000000'; + + this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter + + /** + * This temporary display object used as the parent of the currently being rendered item + * + * @member {PIXI.DisplayObject} + * @private + */ + this._tempDisplayObjectParent = new Container(); + + /** + * The last root object that the renderer tried to render. + * + * @member {PIXI.DisplayObject} + * @private + */ + this._lastObjectRendered = this._tempDisplayObjectParent; + + /** + * Collection of plugins. + * @readonly + * @member {object} + */ + this.plugins = {}; + } + + /** + * Initialize the plugins. + * + * @protected + * @param {object} staticMap - The dictionary of staticly saved plugins. + */ + initPlugins(staticMap) + { + 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() + { + return this.view.width; + } + + /** + * Same as view.height, actual number of pixels in the canvas by vertical + * + * @member {number} + * @readonly + * @default 600 + */ + get height() + { + return this.view.height; + } + + /** + * Resizes the screen and canvas to the specified width and height + * Canvas dimensions are multiplied by resolution + * + * @param {number} screenWidth - the new width of the screen + * @param {number} screenHeight - the new height of the screen + */ + resize(screenWidth, screenHeight) + { + this.screen.width = screenWidth; + this.screen.height = screenHeight; + + this.view.width = screenWidth * this.resolution; + this.view.height = screenHeight * this.resolution; + + if (this.autoResize) + { + this.view.style.width = `${screenWidth}px`; + this.view.style.height = `${screenHeight}px`; + } + } + + /** + * 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 {PIXI.DisplayObject} displayObject - The displayObject the object will be generated from + * @param {number} scaleMode - Should be one of the scaleMode consts + * @param {number} resolution - The resolution / device pixel ratio of the texture being generated + * @param {PIXI.Rectangle} [region] - The region of the displayObject, that shall be rendered, + * if no region is specified, defaults to the local bounds of the displayObject. + * @return {PIXI.Texture} a texture of the graphics object + */ + generateTexture(displayObject, scaleMode, resolution, region) + { + region = region || displayObject.getLocalBounds(); + + // 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(region.width | 0, region.height | 0, scaleMode, resolution); + + tempMatrix.tx = -region.x; + tempMatrix.ty = -region.y; + + this.render(displayObject, renderTexture, false, tempMatrix, true); + + return renderTexture; + } + + /** + * Removes everything from the renderer and optionally removes the Canvas DOM element. + * + * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. + */ + destroy(removeView) + { + 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); + } + + this.plugins = null; + + this.type = RENDERER_TYPE.UNKNOWN; + + this.view = null; + + this.screen = null; + + this.resolution = 0; + + this.transparent = false; + + this.autoResize = false; + + this.blendModes = null; + + this.options = null; + + this.preserveDrawingBuffer = false; + this.clearBeforeRender = false; + + this.roundPixels = false; + + this._backgroundColor = 0; + this._backgroundColorRgba = null; + this._backgroundColorString = null; + + this._tempDisplayObjectParent = null; + this._lastObjectRendered = null; + } + + /** + * The background color to fill if not transparent + * + * @member {number} + */ + get backgroundColor() + { + return this._backgroundColor; + } + + set backgroundColor(value) // eslint-disable-line require-jsdoc + { + this._backgroundColor = value; + this._backgroundColorString = hex2string(value); + hex2rgb(value, this._backgroundColorRgba); + } +} diff --git a/packages/core/src/Renderer.js b/packages/core/src/Renderer.js new file mode 100644 index 0000000..2227e78 --- /dev/null +++ b/packages/core/src/Renderer.js @@ -0,0 +1,338 @@ +import AbstractRenderer from './AbstractRenderer'; +import { sayHello } from '@pixi/utils'; +import MaskSystem from './mask/MaskSystem'; +import StencilSystem from './mask/StencilSystem'; +import FilterSystem from './filters/FilterSystem'; +import FramebufferSystem from './framebuffer/FramebufferSystem'; +import RenderTextureSystem from './renderTexture/RenderTextureSystem'; +import TextureSystem from './textures/TextureSystem'; +import ProjectionSystem from './projection/ProjectionSystem'; +import StateSystem from './state/StateSystem'; +import GeometrySystem from './geometry/GeometrySystem'; +import ShaderSystem from './shader/ShaderSystem'; +import ContextSystem from './context/ContextSystem'; +import BatchSystem from './batch/BatchSystem'; +import TextureGCSystem from './textures/TextureGCSystem'; +import { RENDERER_TYPE } from '@pixi/constants'; +import UniformGroup from './shader/UniformGroup'; +import { Matrix } from '@pixi/math'; +import Runner from 'mini-runner'; + +/** + * 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 Render works by automatically managing webGLBatchs. + * 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 :) + * + * @class + * @memberof PIXI + * @extends PIXI.AbstractRenderer + */ +export default class Renderer extends AbstractRenderer +{ + // eslint-disable-next-line valid-jsdoc + /** + * + * @param {object} [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.transparent=false] - If the render view is transparent, default false + * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false + * @param {boolean} [options.antialias=false] - sets antialias. If not available natively then FXAA + * antialiasing is used + * @param {boolean} [options.forceFXAA=false] - forces FXAA antialiasing to be used over native. + * FXAA is faster, but may not always look as great + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. + * The resolution of the renderer retina would be 2. + * @param {boolean} [options.clearBeforeRender=true] - This sets if the renderer will clear + * the canvas or not before the new render pass. If you wish to set this to false, you *must* set + * preserveDrawingBuffer to `true`. + * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, + * enable this if you need to call toDataUrl on the webgl context. + * @param {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when + * rendering, stopping pixel interpolation. + * @param {number} [options.backgroundColor=0x000000] - The background color of the rendered area + * (shown if not transparent). + * @param {boolean} [options.legacy=false] - If true PixiJS will aim to ensure compatibility + * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. + * @param {string} [options.powerPreference] - Parameter passed to webgl context, set to "high-performance" + * for devices with dual graphics card + */ + constructor(options, arg2, arg3) + { + super('WebGL', options, arg2, arg3); + + /** + * The type of this renderer as a standardised const + * + * @member {number} + * @see PIXI.RENDERER_TYPE + */ + this.type = RENDERER_TYPE.WEBGL; + + // this will be set by the contextSystem (this.context) + this.gl = null; + this.CONTEXT_UID = 0; + this.legacy = !!options.legacy; + + // TODO legacy! + + // runners! + this.runners = { + destroy: new Runner('destroy'), + contextChange: new Runner('contextChange', 1), + reset: new Runner('reset'), + update: new Runner('update'), + postrender: new Runner('postrender'), + prerender: new Runner('prerender'), + resize: new Runner('resize', 2), + }; + + 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(GeometrySystem, 'geometry') + .addSystem(FramebufferSystem, 'framebuffer') + .addSystem(StencilSystem, 'stencil') + .addSystem(ProjectionSystem, 'projection') + .addSystem(TextureGCSystem, 'textureGC') + .addSystem(FilterSystem, 'filter') + .addSystem(RenderTextureSystem, 'renderTexture') + .addSystem(BatchSystem, 'batch'); + + this.initPlugins(Renderer.__plugins); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ + if (options.context) + { + this.context.initFromContext(options.context); + } + else + { + this.context.initFromOptions({ + alpha: this.transparent, + antialias: options.antialias, + premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', + stencil: true, + 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); + } + + /** + * Add a new system to the renderer. + * @param {class} ClassRef - Class reference + * @param {string} [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. + * @return {PIXI.Renderer} Return instance of renderer + */ + addSystem(ClassRef, name) + { + if (!name) + { + name = ClassRef.name; + } + + // TODO - read name from class.name.. + + /* + if(name.includes('System')) + { + name = name.replace('System', ''); + name = name.charAt(0).toLowerCase() + name.slice(1); + } + */ + + const system = new ClassRef(this); + + if (this[name]) + { + throw new Error(`Whoops! ${name} is already a manger`); + } + + this[name] = system; + + for (const i in this.runners) + { + this.runners[i].add(system); + } + + return this; + + /** + * 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. + */ + } + + /** + * Renders the object to its webGL view + * + * @param {PIXI.DisplayObject} displayObject - the object to be rendered + * @param {PIXI.RenderTexture} renderTexture - The render texture to render to. + * @param {boolean} [clear] - Should the canvas be cleared before the new render + * @param {PIXI.Transform} [transform] - A transform to apply to the render texture before rendering. + * @param {boolean} [skipUpdateTransform] - Should we skip the update transform pass? + */ + render(displayObject, renderTexture, clear, transform, skipUpdateTransform) + { + // can be handy to know! + this.renderingToScreen = !renderTexture; + + this.runners.prerender.run(); + this.emit('prerender'); + + // 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.parent; + + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = 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.run(); + + this.emit('postrender'); + } + + /** + * Resizes the webGL view to the specified width and height. + * + * @param {number} screenWidth - the new width of the screen + * @param {number} screenHeight - the new height of the screen + */ + resize(screenWidth, screenHeight) + { + AbstractRenderer.prototype.resize.call(this, screenWidth, screenHeight); + + this.runners.resize.run(screenWidth, screenHeight); + } + + /** + * Resets the WebGL state so you can render things however you fancy! + * + * @return {PIXI.Renderer} Returns itself. + */ + reset() + { + this.runners.reset.run(); + + return this; + } + + clear() + { + this.framebuffer.bind(); + this.framebuffer.clear(); + } + + /** + * Removes everything from the renderer (event listeners, spritebatch, etc...) + * + * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. + * See: https://github.com/pixijs/pixi.js/issues/2233 + */ + destroy(removeView) + { + this.runners.destroy.run(); + + // call base destroy + super.destroy(removeView); + + // TODO nullify all the managers.. + this.gl = null; + } + + /** + * 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 + * builds and excluding plugins. + * @name PIXI.Renderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. + */ + + /** + * Adds a plugin to the renderer. + * + * @method + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + static registerPlugin(pluginName, ctor) + { + Renderer.__plugins = Renderer.__plugins || {}; + Renderer.__plugins[pluginName] = ctor; + } +} diff --git a/packages/core/src/System.js b/packages/core/src/System.js new file mode 100644 index 0000000..05a10c8 --- /dev/null +++ b/packages/core/src/System.js @@ -0,0 +1,42 @@ +/** + * System is a base class used for extending systems used by the {@link PIXI.Renderer} + * @see PIXI.Renderer#addSystem + * @class + * @memberof PIXI + */ +export default class System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + /** + * The renderer this manager works for. + * + * @member {PIXI.Renderer} + */ + this.renderer = renderer; + + this.renderer.runners.contextChange.add(this); + } + + /** + * Generic method called when there is a WebGL context change. + * + */ + contextChange() + { + // do some codes init! + } + + /** + * Generic destroy methods to be overridden by the subclass + * + */ + destroy() + { + this.renderer.runners.contextChange.remove(this); + this.renderer = null; + } +} diff --git a/packages/core/src/batch/BatchSystem.js b/packages/core/src/batch/BatchSystem.js new file mode 100644 index 0000000..b04c249 --- /dev/null +++ b/packages/core/src/batch/BatchSystem.js @@ -0,0 +1,68 @@ +import System from '../System'; +import ObjectRenderer from './ObjectRenderer'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class BatchSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + super(renderer); + + /** + * An empty renderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.emptyRenderer = new ObjectRenderer(renderer); + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.currentRenderer = this.emptyRenderer; + } + + /** + * Changes the current renderer to the one given in parameter + * + * @param {PIXI.ObjectRenderer} objectRenderer - The object renderer to use. + */ + setObjectRenderer(objectRenderer) + { + if (this.currentRenderer === objectRenderer) + { + return; + } + + this.currentRenderer.stop(); + this.currentRenderer = objectRenderer; + + this.renderer.state.setState(objectRenderer.state); + + this.currentRenderer.start(); + } + + /** + * This should be called if you wish to do some custom rendering + * It will basically render anything that may be batched up such as sprites + * + */ + flush() + { + this.setObjectRenderer(this.emptyRenderer); + } + + reset() + { + this.setObjectRenderer(this.emptyRenderer); + } +} diff --git a/packages/core/src/batch/ObjectRenderer.js b/packages/core/src/batch/ObjectRenderer.js new file mode 100644 index 0000000..0c851a9 --- /dev/null +++ b/packages/core/src/batch/ObjectRenderer.js @@ -0,0 +1,48 @@ +import System from '../System'; + +/** + * Base for a common object renderer that can be used as a system renderer plugin. + * + * @class + * @extends PIXI.System + * @memberof PIXI + */ +export default class ObjectRenderer extends System +{ + /** + * Starts the renderer and sets the shader + * + */ + start() + { + // set the shader.. + } + + /** + * Stops the renderer + * + */ + stop() + { + this.flush(); + } + + /** + * Stub method for rendering content and emptying the current batch. + * + */ + flush() + { + // flush! + } + + /** + * Renders an object + * + * @param {PIXI.DisplayObject} object - The object to render. + */ + render(object) // eslint-disable-line no-unused-vars + { + // render the object + } +} diff --git a/packages/core/src/context/ContextSystem.js b/packages/core/src/context/ContextSystem.js new file mode 100644 index 0000000..301aab4 --- /dev/null +++ b/packages/core/src/context/ContextSystem.js @@ -0,0 +1,186 @@ +import System from '../System'; +import { settings } from '@pixi/settings'; + +let CONTEXT_UID = 0; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class ContextSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.webGLVersion = 1; + + this.handleContextLost = this.handleContextLost.bind(this); + this.handleContextRestored = this.handleContextRestored.bind(this); + + this.extensions = {}; + + renderer.view.addEventListener('webglcontextlost', this.handleContextLost, false); + renderer.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + } + + get isLost() + { + return (!this.gl || this.gl.isContextLost()); + } + + contextChange(gl) + { + this.gl = gl; + + // restore a context if it was previously lost + if (gl.isContextLost() && gl.getExtension('WEBGL_lose_context')) + { + gl.getExtension('WEBGL_lose_context').restoreContext(); + } + } + + initFromContext(gl) + { + this.gl = gl; + this.validateContext(gl); + this.renderer.gl = gl; + this.renderer.CONTEXT_UID = CONTEXT_UID++; + this.renderer.runners.contextChange.run(gl); + } + + initFromOptions(options) + { + const gl = this.createContext(this.renderer.view, options); + + this.initFromContext(gl); + } + + /** + * Helper class to create a webGL Context + * + * @class + * @memberof PIXI.glCore + * @param canvas {HTMLCanvasElement} the canvas element that we will get the context from + * @param options {Object} An options object that gets passed in to the canvas element containing the context attributes, + * see https://developer.mozilla.org/en/docs/Web/API/HTMLCanvasElement/getContext for the options available + * @return {WebGLRenderingContext} the WebGL context + */ + createContext(canvas, options) + { + let gl; + + if (settings.PREFER_WEBGL_2) + { + gl = canvas.getContext('webgl2', options); + } + + if (gl) + { + this.webGLVersion = 2; + } + else + { + this.webGLVersion = 1; + + gl = canvas.getContext('webgl', options) + || canvas.getContext('experimental-webgl', options); + + if (!gl) + { + // fail, not able to get a context + throw new Error('This browser does not support webGL. Try using the canvas renderer'); + } + } + + this.gl = gl; + + this.getExtensions(); + + return gl; + } + + getExtensions() + { + // time to set up default etensions that pixi uses.. + const gl = this.gl; + const extensions = this.extensions; + + if (this.webGLVersion === 1) + { + extensions.drawBuffers = gl.getExtension('WEBGL_draw_buffers'); + extensions.depthTexture = gl.getExtension('WEBKIT_WEBGL_depth_texture'); + extensions.floatTexture = gl.getExtension('OES_texture_float'); + extensions.loseContext = gl.getExtension('WEBGL_lose_context'); + + extensions.vertexArrayObject = gl.getExtension('OES_vertex_array_object') + || gl.getExtension('MOZ_OES_vertex_array_object') + || gl.getExtension('WEBKIT_OES_vertex_array_object'); + } + + // we don't use any specific WebGL 2 ones yet! + } + + /** + * Handles a lost webgl context + * + * @private + * @param {WebGLContextEvent} event - The context lost event. + */ + handleContextLost(event) + { + event.preventDefault(); + } + + /** + * Handles a restored webgl context + * + * @private + */ + handleContextRestored() + { + this.renderer.runners.contextChange.run(this.gl); + } + + destroy() + { + const view = this.renderer.view; + + // remove listeners + view.removeEventListener('webglcontextlost', this.handleContextLost); + view.removeEventListener('webglcontextrestored', this.handleContextRestored); + + this.gl.useProgram(null); + + if (this.extensions.loseContext) + { + this.extensions.loseContext.loseContext(); + } + } + + postrender() + { + this.gl.flush(); + } + + validateContext(gl) + { + const attributes = gl.getContextAttributes(); + + // this is going to be fairly simple for now.. but at least we have room to grow! + if (!attributes.stencil) + { + /* eslint-disable max-len */ + + /* eslint-disable no-console */ + console.warn('Provided WebGL context does not have a stencil buffer, masks may not render correctly'); + /* eslint-enable no-console */ + + /* eslint-enable max-len */ + } + } +} diff --git a/packages/core/src/filters/Filter.js b/packages/core/src/filters/Filter.js new file mode 100644 index 0000000..9d8cad8 --- /dev/null +++ b/packages/core/src/filters/Filter.js @@ -0,0 +1,140 @@ +import Shader from '../shader/Shader'; +import Program from '../shader/Program'; +import State from '../state/State'; +import { settings } from '@pixi/settings'; +// import extractUniformsFromSrc from './extractUniformsFromSrc'; +import defaultVertex from './defaultFilter.vert'; +import defaultFragment from './defaultFilter.frag'; + +// let math = require('../../math'); +/** + * @class + * @memberof PIXI + * @extends PIXI.Shader + */ +export default class Filter extends Shader +{ + /** + * @param {string} [vertexSrc] - The source of the vertex shader. + * @param {string} [fragmentSrc] - The source of the fragment shader. + * @param {object} [uniforms] - Custom uniforms to use to augment the built-in ones. + */ + constructor(vertexSrc, fragmentSrc, uniforms) + { + const program = Program.from(vertexSrc, fragmentSrc); + + super(program, uniforms); + + /** + * The padding of the filter. Some filters require extra space to breath such as a blur. + * Increasing this will add extra width and height to the bounds of the object that the + * filter is applied to. + * + * @member {number} + */ + this.padding = 0; + + /** + * The resolution of the filter. Setting this to be lower will lower the quality but + * increase the performance of the filter. + * + * @member {number} + */ + this.resolution = settings.RESOLUTION; + + /** + * If enabled is true the filter is applied, if false it will not. + * + * @member {boolean} + */ + this.enabled = true; + + /** + * If enabled, PixiJS will fit the filter area into boundaries for better performance. + * Switch it off if it does not work for specific shader. + * + * @member {boolean} + */ + this.autoFit = true; + + /** + * Legacy filters use position and uvs from attributes + * @member {boolean} + * @readonly + */ + this.legacy = !!this.program.attributeData.aTextureCoord; + + /** + * the webGL state the filter requires to render + * @member {PIXI.State} + */ + this.state = new State(); + } + + /** + * Applies the filter + * + * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + * @param {boolean} clear - Should the output be cleared before rendering to it + * @param {object} [currentState] - It's current state of filter. + * There are some useful properties in the currentState : + * target, filters, sourceFrame, destinationFrame, renderTarget, resolution + */ + apply(filterManager, input, output, clear, currentState, derp) // eslint-disable-line no-unused-vars + { + // do as you please! + + filterManager.applyFilter(this, input, output, clear, currentState, derp); + + // or just do a regular render.. + } + + /** + * Sets the blendmode of the filter + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL + */ + get blendMode() + { + return this.state.blendMode; + } + + set blendMode(value) // eslint-disable-line require-jsdoc + { + this.state.blendMode = value; + } + + /** + * The default vertex shader source + * + * @static + * @constant + */ + static get defaultVertexSrc() + { + return defaultVertex; + } + + /** + * The default fragment shader source + * + * @static + * @constant + */ + static get defaultFragmentSrc() + { + return defaultFragment; + } +} + +/** + * Used for caching shader IDs + * + * @static + * @private + */ +Filter.SOURCE_KEY_MAP = {}; + diff --git a/packages/core/src/filters/FilterSystem.js b/packages/core/src/filters/FilterSystem.js new file mode 100644 index 0000000..3174318 --- /dev/null +++ b/packages/core/src/filters/FilterSystem.js @@ -0,0 +1,431 @@ +import System from '../System'; + +import RenderTexture from '../renderTexture/RenderTexture'; +import Quad from '../utils/Quad'; +import QuadUv from '../utils/QuadUv'; +import { Rectangle } from '@pixi/math'; +import * as filterTransforms from './filterTransforms'; +import bitTwiddle from 'bit-twiddle'; +import UniformGroup from '../shader/UniformGroup'; +import { DRAW_MODES } from '@pixi/constants'; + +// +/** + * @ignore + * @class + */ +class FilterState +{ + /** + * + */ + constructor() + { + this.renderTexture = null; + this.sourceFrame = new Rectangle(); + this.destinationFrame = new Rectangle(); + this.filters = []; + this.target = null; + this.legacy = false; + this.resolution = 1; + } +} + +/** + * @class + * @memberof PIXI.systems + * @extends PIXI.System + */ +export default class FilterSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + /** + * stores a bunch of PO2 textures used for filtering + * @type {Object} + */ + this.texturePool = {}; + + /** + * a pool for storing filter states, save us creating new ones each tick + * @type {Array} + */ + this.statePool = []; + + /** + * A very simple geometry used when drawing a filter effect to the screen + * @type {Quad} + */ + this.quad = new Quad(); + + this.quadUv = new QuadUv(); + + /** + * Temporary rect for maths + * @type {PIXI.Rectangle} + */ + this.tempRect = new Rectangle(); + + this.activeState = {}; + + /** + * this uniform group is attached to filter uniforms when used + * @type {UniformGroup} + */ + this.globalUniforms = new UniformGroup({ + sourceFrame: this.tempRect, + destinationFrame: this.tempRect, + + // legacy variables + filterArea: new Float32Array(4), + filterClamp: new Float32Array(4), + }, true); + } + + /** + * Adds a new filter to the System. + * + * @param {PIXI.DisplayObject} target - The target of the filter to render. + * @param {PIXI.Filter[]} filters - The filters to apply. + */ + push(target, filters) + { + const renderer = this.renderer; + const filterStack = this.renderer.renderTexture.defaultFilterStack; + const state = this.statePool.pop() || new FilterState(); + + let resolution = filters[0].resolution; + let padding = filters[0].padding; + let autoFit = filters[0].autoFit; + let legacy = filters[0].legacy; + + for (let i = 1; i < filters.length; i++) + { + const filter = filters[i]; + + // lets use the lowest resolution.. + resolution = Math.min(resolution, filter.resolution); + // and the largest amount of padding! + padding = Math.max(padding, filter.padding); + // only auto fit if all filters are autofit + autoFit = autoFit || filter.autoFit; + + legacy = legacy || filter.legacy; + } + + filterStack.push(state); + + state.resolution = resolution; + + state.legacy = legacy; + + // round to whole number based on resolution + // TODO move that to the shader too? + state.sourceFrame = target.filterArea || target.getBounds(true); + + state.sourceFrame.pad(padding); + + if (autoFit) + { + state.sourceFrame.fit(this.renderer.renderTexture.destinationFrame); + } + + state.sourceFrame.round(resolution); + + state.renderTexture = this.getPotFilterTexture(state.sourceFrame.width, state.sourceFrame.height, resolution); + state.filters = filters; + + state.destinationFrame.width = state.renderTexture.width; + state.destinationFrame.height = state.renderTexture.height; + + state.renderTexture.filterFrame = state.sourceFrame; + renderer.renderTexture.bind(state.renderTexture, state.sourceFrame);// /, state.destinationFrame); + renderer.renderTexture.clear(); + } + + /** + * Pops off the filter and applies it. + * + */ + pop() + { + const renderer = this.renderer; + const filterStack = renderer.renderTexture.defaultFilterStack; + const state = filterStack.pop(); + const filters = state.filters; + + this.activeState = state; + + const globalUniforms = this.globalUniforms.uniforms; + + globalUniforms.sourceFrame = state.sourceFrame; + globalUniforms.destinationFrame = state.destinationFrame; + globalUniforms.resolution = state.resolution; + + // only update the rect if its legacy.. + if (state.legacy) + { + const filterArea = globalUniforms.filterArea; + const filterClamp = globalUniforms.filterClamp; + + filterArea[0] = state.destinationFrame.width; + filterArea[1] = state.destinationFrame.height; + filterArea[2] = state.sourceFrame.x; + filterArea[3] = state.sourceFrame.y; + + filterClamp[0] = 0.5 / state.resolution / state.destinationFrame.width; + filterClamp[1] = 0.5 / state.resolution / state.destinationFrame.height; + filterClamp[2] = (state.sourceFrame.width - 0.5) / state.resolution / state.destinationFrame.width; + filterClamp[3] = (state.sourceFrame.height - 0.5) / state.resolution / state.destinationFrame.height; + } + + this.globalUniforms.update(); + + const lastState = filterStack[filterStack.length - 1]; + + if (filters.length === 1) + { + filters[0].apply(this, state.renderTexture, lastState.renderTexture, false, state); + + this.returnFilterTexture(state.renderTexture); + } + else + { + let flip = state.renderTexture; + let flop = this.getPotFilterTexture( + flip.width, + flip.height, + state.resolution + ); + + flop.filterFrame = flip.filterFrame; + + let i = 0; + + for (i = 0; i < filters.length - 1; ++i) + { + filters[i].apply(this, flip, flop, true, state); + + const t = flip; + + flip = flop; + flop = t; + } + + filters[i].apply(this, flip, lastState.renderTexture, false, state); + + this.returnFilterTexture(flip); + this.returnFilterTexture(flop); + } + + this.statePool.push(state); + } + + /** + * Draws a filter. + * + * @param {PIXI.Filter} filter - The filter to draw. + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + * @param {boolean} clear - Should the output be cleared before rendering to it + */ + applyFilter(filter, input, output, clear) + { + const renderer = this.renderer; + + renderer.renderTexture.bind(output, output ? output.filterFrame : null); + + if (clear) + { + // gl.disable(gl.SCISSOR_TEST); + renderer.renderTexture.clear(); + // gl.enable(gl.SCISSOR_TEST); + } + + // set the uniforms.. + filter.uniforms.uSampler = input; + filter.uniforms.filterGlobals = this.globalUniforms; + + // TODO make it so that the order of this does not matter.. + // because it does at the moment cos of global uniforms. + // they need to get resynced + + renderer.state.setState(filter.state); + renderer.shader.bind(filter); + + if (filter.legacy) + { + this.quadUv.map(input._frame, input.filterFrame); + + renderer.geometry.bind(this.quadUv); + renderer.geometry.draw(DRAW_MODES.TRIANGLES); + } + else + { + renderer.geometry.bind(this.quad); + renderer.geometry.draw(DRAW_MODES.TRIANGLE_STRIP); + } + } + + /** + * Calculates the mapped matrix. + * + * TODO playing around here.. this is temporary - (will end up in the shader) + * this returns a matrix that will normalise map filter cords in the filter to screen space + * + * @param {PIXI.Matrix} outputMatrix - the matrix to output to. + * @return {PIXI.Matrix} The mapped matrix. + */ + calculateScreenSpaceMatrix(outputMatrix) + { + const currentState = this.activeState; + + return filterTransforms.calculateScreenSpaceMatrix( + outputMatrix, + currentState.sourceFrame, + currentState.destinationFrame + ); + } + + /** + * This will map the filter coord so that a texture can be used based on the transform of a sprite + * + * @param {PIXI.Matrix} outputMatrix - The matrix to output to. + * @param {PIXI.Sprite} sprite - The sprite to map to. + * @return {PIXI.Matrix} The mapped matrix. + */ + calculateSpriteMatrix(outputMatrix, sprite) + { + const currentState = this.activeState; + + return filterTransforms.calculateSpriteMatrix( + outputMatrix, + currentState.sourceFrame, + currentState.destinationFrame, + sprite + ); + } + + /** + * Destroys this Filter System. + * + * @param {boolean} [contextLost=false] context was lost, do not free shaders + * + */ + destroy(contextLost = false) + { + if (!contextLost) + { + this.emptyPool(); + } + else + { + this.texturePool = {}; + } + } + + /** + * Gets a Power-of-Two render texture. + * + * TODO move to a seperate class could be on renderer? + * also - could cause issue with multiple contexts? + * + * @private + * @param {WebGLRenderingContext} gl - The webgl rendering context + * @param {number} minWidth - The minimum width of the render target. + * @param {number} minHeight - The minimum height of the render target. + * @param {number} resolution - The resolution of the render target. + * @return {PIXI.RenderTarget} The new render target. + */ + getPotFilterTexture(minWidth, minHeight, resolution) + { + minWidth = bitTwiddle.nextPow2(minWidth); + minHeight = bitTwiddle.nextPow2(minHeight); + resolution = resolution || 1; + + const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + + if (!this.texturePool[key]) + { + this.texturePool[key] = []; + } + + let renderTexture = this.texturePool[key].pop(); + + if (!renderTexture) + { + // temporary bypass cache.. + // internally - this will cause a texture to be bound.. + renderTexture = RenderTexture.create({ + width: minWidth, + height: minHeight, + resolution, + }); + } + + return renderTexture; + } + + /** + * Gets extra render texture to use inside current filter + * + * @param {number} resolution resolution of the renderTexture + * @returns {PIXI.RenderTexture} + */ + getFilterTexture(resolution) + { + const rt = this.activeState.renderTexture; + + const filterTexture = this.getPotFilterTexture(rt.width, rt.height, resolution || rt.baseTexture.resolution); + + filterTexture.filterFrame = rt.filterFrame; + + return filterTexture; + } + + /** + * Frees a render texture back into the pool. + * + * @param {PIXI.RenderTarget} renderTexture - The renderTarget to free + */ + returnFilterTexture(renderTexture) + { + renderTexture.filterFrame = null; + + const base = renderTexture.baseTexture; + + const minWidth = base.width; + const minHeight = base.height; + + const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + + this.texturePool[key].push(renderTexture); + } + + /** + * Empties the texture pool. + * + */ + emptyPool() + { + for (const i in this.texturePool) + { + const textures = this.texturePool[i]; + + if (textures) + { + for (let j = 0; j < textures.length; j++) + { + textures[j].destroy(true); + } + } + } + + this.texturePool = {}; + } +} diff --git a/packages/core/src/filters/defaultFilter.frag b/packages/core/src/filters/defaultFilter.frag new file mode 100644 index 0000000..a14c1be --- /dev/null +++ b/packages/core/src/filters/defaultFilter.frag @@ -0,0 +1,22 @@ +varying vec2 vTextureCoord; +varying vec2 vFilterCoord; + +uniform sampler2D uSampler; +uniform sampler2D filterSampler; + +void main(void){ + vec4 masky = texture2D(filterSampler, vFilterCoord); + vec4 sample = texture2D(uSampler, vTextureCoord); + vec4 color; + if(mod(vFilterCoord.x, 1.0) > 0.5) + { + color = vec4(1.0, 0.0, 0.0, 1.0); + } + else + { + color = vec4(0.0, 1.0, 0.0, 1.0); + } + // gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0); + gl_FragColor = mix(sample, masky, 0.5); + gl_FragColor *= sample.a; +} diff --git a/packages/core/src/filters/defaultFilter.vert b/packages/core/src/filters/defaultFilter.vert new file mode 100644 index 0000000..e1febca --- /dev/null +++ b/packages/core/src/filters/defaultFilter.vert @@ -0,0 +1,14 @@ +attribute vec2 aVertexPosition; +attribute vec2 aTextureCoord; + +uniform mat3 projectionMatrix; +uniform mat3 filterMatrix; + +varying vec2 vTextureCoord; +varying vec2 vFilterCoord; + +void main(void){ + gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); + vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy; + vTextureCoord = aTextureCoord ; +} diff --git a/packages/core/src/filters/filterTransforms.js b/packages/core/src/filters/filterTransforms.js new file mode 100644 index 0000000..87a3cd6 --- /dev/null +++ b/packages/core/src/filters/filterTransforms.js @@ -0,0 +1,50 @@ +import { Matrix } from '@pixi/math'; + +/** + * Calculates the mapped matrix + * @param filterArea {Rectangle} The filter area + * @param sprite {Sprite} the target sprite + * @param outputMatrix {Matrix} @alvin + * @private + */ +// this returns a matrix that will normalise map filter cords in the filter to screen space +export function calculateScreenSpaceMatrix(outputMatrix, filterArea, textureSize) +{ + // TODO unwrap? + const mappedMatrix = outputMatrix.identity(); + + mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); + + mappedMatrix.scale(textureSize.width, textureSize.height); + + return mappedMatrix; +} + +export function calculateNormalizedScreenSpaceMatrix(outputMatrix, filterArea, textureSize) +{ + const mappedMatrix = outputMatrix.identity(); + + mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); + + const translateScaleX = (textureSize.width / filterArea.width); + const translateScaleY = (textureSize.height / filterArea.height); + + mappedMatrix.scale(translateScaleX, translateScaleY); + + return mappedMatrix; +} + +// this will map the filter coord so that a texture can be used based on the transform of a sprite +export function calculateSpriteMatrix(outputMatrix, filterArea, textureSize, sprite) +{ + const orig = sprite._texture.orig; + const mappedMatrix = outputMatrix.set(textureSize.width, 0, 0, textureSize.height, filterArea.x, filterArea.y); + const worldTransform = sprite.worldTransform.copyTo(Matrix.TEMP_MATRIX); + + worldTransform.invert(); + mappedMatrix.prepend(worldTransform); + mappedMatrix.scale(1.0 / orig.width, 1.0 / orig.height); + mappedMatrix.translate(sprite.anchor.x, sprite.anchor.y); + + return mappedMatrix; +} diff --git a/packages/core/src/filters/spriteMask/SpriteMaskFilter.js b/packages/core/src/filters/spriteMask/SpriteMaskFilter.js new file mode 100644 index 0000000..91697b7 --- /dev/null +++ b/packages/core/src/filters/spriteMask/SpriteMaskFilter.js @@ -0,0 +1,63 @@ +import Filter from '../Filter'; +import { Matrix } from '@pixi/math'; +import vertex from './spriteMaskFilter.vert'; +import fragment from './spriteMaskFilter.frag'; +import { default as TextureMatrix } from '../../textures/TextureMatrix'; + +/** + * The SpriteMaskFilter class + * + * @class + * @extends PIXI.Filter + * @memberof PIXI + */ +export default class SpriteMaskFilter extends Filter +{ + /** + * @param {PIXI.Sprite} sprite - the target sprite + */ + constructor(sprite) + { + const maskMatrix = new Matrix(); + + super(vertex, fragment); + + sprite.renderable = false; + + this.maskSprite = sprite; + this.maskMatrix = maskMatrix; + } + + /** + * Applies the filter + * + * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + */ + apply(filterManager, input, output) + { + const maskSprite = this.maskSprite; + const tex = this.maskSprite.texture; + + if (!tex.valid) + { + return; + } + if (!tex.transform) + { + // margin = 0.0, let it bleed a bit, shader code becomes easier + // assuming that atlas textures were made with 1-pixel padding + tex.transform = new TextureMatrix(tex, 0.0); + } + tex.transform.update(); + + this.uniforms.mask = tex; + this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite) + .prepend(tex.transform.mapCoord); + this.uniforms.alpha = maskSprite.worldAlpha; + this.uniforms.maskClamp = tex.transform.uClampFrame; + + filterManager.applyFilter(this, input, output); + } +} diff --git a/packages/core/src/filters/spriteMask/spriteMaskFilter.frag b/packages/core/src/filters/spriteMask/spriteMaskFilter.frag new file mode 100644 index 0000000..0e4aef8 --- /dev/null +++ b/packages/core/src/filters/spriteMask/spriteMaskFilter.frag @@ -0,0 +1,23 @@ +varying vec2 vMaskCoord; +varying vec2 vTextureCoord; + +uniform sampler2D uSampler; +uniform sampler2D mask; +uniform float alpha; +uniform vec4 maskClamp; + +void main(void) +{ + float clip = step(3.5, + step(maskClamp.x, vMaskCoord.x) + + step(maskClamp.y, vMaskCoord.y) + + step(vMaskCoord.x, maskClamp.z) + + step(vMaskCoord.y, maskClamp.w)); + + vec4 original = texture2D(uSampler, vTextureCoord); + vec4 masky = texture2D(mask, vMaskCoord); + + original *= (masky.r * masky.a * alpha * clip); + + gl_FragColor = original; +} diff --git a/packages/core/src/filters/spriteMask/spriteMaskFilter.vert b/packages/core/src/filters/spriteMask/spriteMaskFilter.vert new file mode 100644 index 0000000..d23a30c --- /dev/null +++ b/packages/core/src/filters/spriteMask/spriteMaskFilter.vert @@ -0,0 +1,16 @@ +attribute vec2 aVertexPosition; +attribute vec2 aTextureCoord; + +uniform mat3 projectionMatrix; +uniform mat3 otherMatrix; + +varying vec2 vMaskCoord; +varying vec2 vTextureCoord; + +void main(void) +{ + gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); + + vTextureCoord = aTextureCoord; + vMaskCoord = ( otherMatrix * vec3( aTextureCoord, 1.0) ).xy; +} diff --git a/packages/core/src/framebuffer/FrameBuffer.js b/packages/core/src/framebuffer/FrameBuffer.js new file mode 100644 index 0000000..84e9fcc --- /dev/null +++ b/packages/core/src/framebuffer/FrameBuffer.js @@ -0,0 +1,106 @@ +import Texture from '../textures/BaseTexture'; +import { FORMATS, TYPES } from '@pixi/constants'; + +/** + * Frame buffer + * @class + * @memberof PIXI + */ +export default class FrameBuffer +{ + constructor(width, height) + { + this.width = width || 100; + this.height = height || 100; + + this.stencil = false; + this.depth = false; + + this.dirtyId = 0; + this.dirtyFormat = 0; + this.dirtySize = 0; + + this.depthTexture = null; + this.colorTextures = []; + + this.glFrameBuffers = {}; + } + + get colorTexture() + { + return this.colorTextures[0]; + } + + addColorTexture(index, texture) + { + // TODO add some validation to the texture - same width / height etc? + this.colorTextures[index || 0] = texture || new Texture(null, { scaleMode: 0, + resolution: 1, + mipmap: false, + width: this.width, + height: this.height });// || new Texture(); + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + addDepthTexture(texture) + { + /* eslint-disable max-len */ + this.depthTexture = texture || new Texture(null, { scaleMode: 0, + resolution: 1, + width: this.width, + height: this.height, + mipmap: false, + format: FORMATS.DEPTH_COMPONENT, + type: TYPES.UNSIGNED_SHORT });// UNSIGNED_SHORT; + /* eslint-disable max-len */ + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + enableDepth() + { + this.depth = true; + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + enableStencil() + { + this.stencil = true; + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + resize(width, height) + { + if (width === this.width && height === this.height) return; + + this.width = width; + this.height = height; + + this.dirtyId++; + this.dirtySize++; + + for (let i = 0; i < this.colorTextures.length; i++) + { + this.colorTextures[i].setSize(width, height); + } + + if (this.depthTexture) + { + this.depthTexture.setSize(width, height); + } + } +} diff --git a/packages/core/src/framebuffer/FramebufferSystem.js b/packages/core/src/framebuffer/FramebufferSystem.js new file mode 100644 index 0000000..2cc2437 --- /dev/null +++ b/packages/core/src/framebuffer/FramebufferSystem.js @@ -0,0 +1,246 @@ +import System from '../System'; +import { Rectangle } from '@pixi/math'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class FramebufferSystem extends System +{ + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + contextChange() + { + this.gl = this.renderer.gl; + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + this.current = null; + this.viewport = new Rectangle(); + + this.drawBufferExtension = this.renderer.context.extensions.drawBuffers; + } + + bind(framebuffer, frame) + { + const gl = this.gl; + + this.current = framebuffer; + + if (framebuffer) + { + // TODO cacheing layer! + + const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID] || this.initFramebuffer(framebuffer); + + gl.bindFramebuffer(gl.FRAMEBUFFER, fbo.framebuffer); + // makesure all textures are unbound.. + + // now check for updates... + if (fbo.dirtyId !== framebuffer.dirtyId) + { + fbo.dirtyId = framebuffer.dirtyId; + + if (fbo.dirtyFormat !== framebuffer.dirtyFormat) + { + fbo.dirtyFormat = framebuffer.dirtyFormat; + this.updateFramebuffer(framebuffer); + } + else if (fbo.dirtySize !== framebuffer.dirtySize) + { + fbo.dirtySize = framebuffer.dirtySize; + this.resizeFramebuffer(framebuffer); + } + } + + for (let i = 0; i < framebuffer.colorTextures.length; i++) + { + if (framebuffer.colorTextures[i].texturePart) + { + this.renderer.texture.unbind(framebuffer.colorTextures[i].texture); + } + else + { + this.renderer.texture.unbind(framebuffer.colorTextures[i]); + } + } + + if (framebuffer.depthTexture) + { + this.renderer.texture.unbind(framebuffer.depthTexture); + } + + if (frame) + { + this.setViewport(frame.x, frame.y, frame.width, frame.height); + } + else + { + this.setViewport(0, 0, framebuffer.width, framebuffer.height); + } + } + else + { + gl.bindFramebuffer(gl.FRAMEBUFFER, null); + + if (frame) + { + this.setViewport(frame.x, frame.y, frame.width, frame.height); + } + else + { + this.setViewport(0, 0, this.renderer.width, this.renderer.height); + } + } + } + + setViewport(x, y, width, height) + { + const v = this.viewport; + + if (v.width !== width || v.height !== height || v.x !== x || v.y !== y) + { + v.x = x; + v.y = y; + v.width = width; + v.height = height; + + this.gl.viewport(x, y, width, height); + } + } + + get size() + { + if (this.current) + { + // TODO store temp + return { x: 0, y: 0, width: this.current.width, height: this.current.height }; + } + + return { x: 0, y: 0, width: this.renderer.width, height: this.renderer.height }; + } + + clear(r, g, b, a) + { + const gl = this.gl; + + // TODO clear color can be set only one right? + gl.clearColor(r, g, b, a); + gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); + } + + // private functions... + + initFramebuffer(framebuffer) + { + const gl = this.gl; + + // TODO - make this a class? + const fbo = { + framebuffer: gl.createFramebuffer(), + stencil: null, + dirtyId: 0, + dirtyFormat: 0, + dirtySize: 0, + }; + + framebuffer.glFrameBuffers[this.CONTEXT_UID] = fbo; + + return fbo; + } + + resizeFramebuffer(framebuffer) + { + const gl = this.gl; + + if (framebuffer.stencil || framebuffer.depth) + { + gl.bindRenderbuffer(gl.RENDERBUFFER, this.stencil); + gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); + } + } + + updateFramebuffer(framebuffer) + { + const gl = this.gl; + + const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID]; + + // bind the color texture + const colorTextures = framebuffer.colorTextures; + + let count = colorTextures.length; + + if (!this.drawBufferExtension) + { + count = Math.min(count, 1); + } + + const activeTextures = []; + + for (let i = 0; i < count; i++) + { + const texture = framebuffer.colorTextures[i]; + + if (texture.texturePart) + { + this.renderer.texture.bind(texture.texture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.COLOR_ATTACHMENT0 + i, + gl.TEXTURE_CUBE_MAP_NEGATIVE_X + texture.side, + texture.texture._glTextures[this.CONTEXT_UID].texture, + 0); + } + else + { + this.renderer.texture.bind(texture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.COLOR_ATTACHMENT0 + i, + gl.TEXTURE_2D, + texture._glTextures[this.CONTEXT_UID].texture, + 0); + } + + activeTextures.push(gl.COLOR_ATTACHMENT0 + i); + } + + if (this.drawBufferExtension && activeTextures.length > 1) + { + this.drawBufferExtension.drawBuffersWEBGL(activeTextures); + } + + if (framebuffer.depthTexture) + { + const depthTextureExt = this.renderer.context.extensions.depthTexture; + + if (depthTextureExt) + { + const depthTexture = framebuffer.depthTexture; + + this.renderer.texture.bind(depthTexture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.DEPTH_ATTACHMENT, + gl.TEXTURE_2D, + depthTexture._glTextures[this.CONTEXT_UID].texture, + 0); + } + } + + if (framebuffer.stencil || framebuffer.depth) + { + fbo.stencil = gl.createRenderbuffer(); + + gl.bindRenderbuffer(gl.RENDERBUFFER, fbo.stencil); + + // TODO.. this is depth AND stencil? + gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, fbo.stencil); + gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); + // fbo.enableStencil(); + } + } +} diff --git a/packages/core/src/geometry/GLBuffer.js b/packages/core/src/geometry/GLBuffer.js new file mode 100644 index 0000000..0477e8a --- /dev/null +++ b/packages/core/src/geometry/GLBuffer.js @@ -0,0 +1,9 @@ +export default class GLBuffer +{ + constructor(buffer) + { + this.buffer = buffer; + this.updateID = -1; + this.byteLength = -1; + } +} diff --git a/packages/core/src/geometry/GeometrySystem.js b/packages/core/src/geometry/GeometrySystem.js new file mode 100644 index 0000000..959f827 --- /dev/null +++ b/packages/core/src/geometry/GeometrySystem.js @@ -0,0 +1,388 @@ +import System from '../System'; +import GLBuffer from './GLBuffer'; + +const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class GeometrySystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this._activeGeometry = null; + this._activeVao = null; + + this.hasVao = true; + this.hasInstance = true; + } + + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + contextChange() + { + const gl = this.gl = this.renderer.gl; + + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + + // webgl2 + if (!gl.createVertexArray) + { + // webgl 1! + let nativeVaoExtension = this.renderer.context.extensions.vertexArrayObject; + + if (this.renderer.legacy) + { + nativeVaoExtension = null; + } + + if (nativeVaoExtension) + { + gl.createVertexArray = () => + nativeVaoExtension.createVertexArrayOES(); + + gl.bindVertexArray = (vao) => + nativeVaoExtension.bindVertexArrayOES(vao); + + gl.deleteVertexArray = (vao) => + nativeVaoExtension.deleteVertexArrayOES(vao); + } + else + { + this.hasVao = false; + gl.createVertexArray = () => + { + // empty + }; + + gl.bindVertexArray = () => + { + // empty + }; + + gl.deleteVertexArray = () => + { + // empty + }; + } + } + + if (!gl.vertexAttribDivisor) + { + const instanceExt = gl.getExtension('ANGLE_instanced_arrays'); + + if (instanceExt) + { + gl.vertexAttribDivisor = (a, b) => + instanceExt.vertexAttribDivisorANGLE(a, b); + + gl.drawElementsInstanced = (a, b, c, d, e) => + instanceExt.drawElementsInstancedANGLE(a, b, c, d, e); + + gl.drawArraysInstanced = (a, b, c, d) => + instanceExt.drawArraysInstancedANGLE(a, b, c, d); + } + else + { + this.hasInstance = false; + } + } + } + + /** + * Binds geometry so that is can be drawn. Creating a Vao if required + * @private + * @param {PIXI.Geometry} geometry instance of geometry to bind + */ + bind(geometry, shader) + { + shader = shader || this.renderer.shader.shader; + + const gl = this.gl; + + // not sure the best way to address this.. + // currently different shaders require different VAOs for the same geometry + // Still mulling over the best way to solve this one.. + // will likely need to modify the shader attribute locations at run time! + let vaos = geometry.glVertexArrayObjects[this.CONTEXT_UID]; + + if (!vaos) + { + geometry.glVertexArrayObjects[this.CONTEXT_UID] = vaos = {}; + } + + const vao = vaos[shader.program.id] || this.initGeometryVao(geometry, shader.program); + + this._activeGeometry = geometry; + + if (this._activeVao !== vao) + { + this._activeVao = vao; + + if (this.hasVao) + { + gl.bindVertexArray(vao); + } + else + { + this.activateVao(geometry, shader.program); + } + } + + // TODO - optimise later! + // don't need to loop through if nothing changed! + // maybe look to add an 'autoupdate' to geometry? + this.updateBuffers(); + } + + reset() + { + this.unbind(); + } + + updateBuffers() + { + const geometry = this._activeGeometry; + const gl = this.gl; + + for (let i = 0; i < geometry.buffers.length; i++) + { + const buffer = geometry.buffers[i]; + + const glBuffer = buffer._glBuffers[this.CONTEXT_UID]; + + if (buffer._updateID !== glBuffer.updateID) + { + glBuffer.updateID = buffer._updateID; + + // TODO can cache this on buffer! maybe added a getter / setter? + const type = buffer.index ? gl.ELEMENT_ARRAY_BUFFER : gl.ARRAY_BUFFER; + const drawType = buffer.static ? gl.STATIC_DRAW : gl.DYNAMIC_DRAW; + + gl.bindBuffer(type, glBuffer.buffer); + + if (glBuffer.byteLength >= buffer.data.byteLength) + { + // offset is always zero for now! + gl.bufferSubData(type, 0, buffer.data); + } + else + { + gl.bufferData(type, buffer.data, drawType); + } + } + } + } + + checkCompatability(geometry, program) + { + // geometry must have at least all the attributes that the shader requires. + const geometryAttributes = geometry.attributes; + const shaderAttributes = program.attributeData; + + for (const j in shaderAttributes) + { + if (!geometryAttributes[j]) + { + throw new Error(`shader and geometry incompatible, geometry missing the "${j}" attribute`); + } + } + } + + /** + * Creates a Vao with the same structure as the geometry and stores it on the geometry. + * @private + * @param {PIXI.Geometry} geometry instance of geometry to to generate Vao for + */ + initGeometryVao(geometry, program) + { + this.checkCompatability(geometry, program); + + const gl = this.gl; + const CONTEXT_UID = this.CONTEXT_UID; + const buffers = geometry.buffers; + const attributes = geometry.attributes; + + const tempStride = {}; + const tempStart = {}; + + for (const j in buffers) + { + tempStride[j] = 0; + tempStart[j] = 0; + } + + for (const j in attributes) + { + if (!attributes[j].size && program.attributeData[j]) + { + attributes[j].size = program.attributeData[j].size; + } + + tempStride[attributes[j].buffer] += attributes[j].size * byteSizeMap[attributes[j].type]; + } + + for (const j in attributes) + { + const attribute = attributes[j]; + const attribSize = attribute.size; + + if (attribute.stride === undefined) + { + if (tempStride[attribute.buffer] === attribSize * byteSizeMap[attribute.type]) + { + attribute.stride = 0; + } + else + { + attribute.stride = tempStride[attribute.buffer]; + } + } + + if (attribute.start === undefined) + { + attribute.start = tempStart[attribute.buffer]; + + tempStart[attribute.buffer] += attribSize * byteSizeMap[attribute.type]; + } + } + + // first update - and create the buffers! + // only create a gl buffer if it actually gets + for (let i = 0; i < buffers.length; i++) + { + const buffer = buffers[i]; + + if (!buffer._glBuffers[CONTEXT_UID]) + { + buffer._glBuffers[CONTEXT_UID] = new GLBuffer(gl.createBuffer()); + } + } + + // TODO - maybe make this a data object? + // lets wait to see if we need to first! + const vao = gl.createVertexArray(); + + gl.bindVertexArray(vao); + + this.activateVao(geometry, program); + + gl.bindVertexArray(null); + + // add it to the cache! + geometry.glVertexArrayObjects[this.CONTEXT_UID][program.id] = vao; + + return vao; + } + + activateVao(geometry, program) + { + const gl = this.gl; + const CONTEXT_UID = this.CONTEXT_UID; + const buffers = geometry.buffers; + const attributes = geometry.attributes; + + if (geometry.indexBuffer) + { + // first update the index buffer if we have one.. + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, geometry.indexBuffer._glBuffers[CONTEXT_UID].buffer); + } + + let lastBuffer = null; + + // add a new one! + for (const j in attributes) + { + const attribute = attributes[j]; + const buffer = buffers[attribute.buffer]; + const glBuffer = buffer._glBuffers[CONTEXT_UID]; + + if (program.attributeData[j]) + { + if (lastBuffer !== glBuffer) + { + gl.bindBuffer(gl.ARRAY_BUFFER, glBuffer.buffer); + + lastBuffer = glBuffer; + } + + const location = program.attributeData[j].location; + + // TODO introduce state again + // we can optimise this for older devices that have no VAOs + gl.enableVertexAttribArray(location); + + gl.vertexAttribPointer(location, + attribute.size, + attribute.type || gl.FLOAT, + attribute.normalized, + attribute.stride, + attribute.start); + + if (attribute.instance) + { + // TODO calculate instance count based of this... + if (this.hasInstance) + { + gl.vertexAttribDivisor(location, 1); + } + else + { + throw new Error('geometry error, GPU Instancing is not supported on this device'); + } + } + } + } + } + + draw(type, size, start, instanceCount) + { + const gl = this.gl; + const geometry = this._activeGeometry; + + // TODO.. this should not change so maybe cache the function? + + if (geometry.indexBuffer) + { + if (geometry.instanced) + { + /* eslint-disable max-len */ + gl.drawElementsInstanced(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2, instanceCount || 1); + /* eslint-enable max-len */ + } + else + { + gl.drawElements(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2); + } + } + else + if (geometry.instanced) + { + // TODO need a better way to calculate size.. + gl.drawArraysInstanced(type, start, size || geometry.getSize(), instanceCount || 1); + } + else + { + gl.drawArrays(type, start, size || geometry.getSize()); + } + + return this; + } + + unbind() + { + this.gl.bindVertexArray(null); + this._activeVao = null; + this._activeGeometry = null; + } +} diff --git a/packages/core/src/geometry/utils/setVertexAttribArrays.js b/packages/core/src/geometry/utils/setVertexAttribArrays.js new file mode 100644 index 0000000..23b6959 --- /dev/null +++ b/packages/core/src/geometry/utils/setVertexAttribArrays.js @@ -0,0 +1,54 @@ +// var GL_MAP = {}; + +/** + * @param gl {WebGLRenderingContext} The current WebGL context + * @param attribs {*} + * @param state {*} + */ +export default function setVertexAttribArrays(gl, attribs, state) +{ + let i; + + if (state) + { + const tempAttribState = state.tempAttribState; + const attribState = state.attribState; + + for (i = 0; i < tempAttribState.length; i++) + { + tempAttribState[i] = false; + } + + // set the new attribs + for (i = 0; i < attribs.length; i++) + { + tempAttribState[attribs[i].attribute.location] = true; + } + + for (i = 0; i < attribState.length; i++) + { + if (attribState[i] !== tempAttribState[i]) + { + attribState[i] = tempAttribState[i]; + + if (state.attribState[i]) + { + gl.enableVertexAttribArray(i); + } + else + { + gl.disableVertexAttribArray(i); + } + } + } + } + else + { + for (i = 0; i < attribs.length; i++) + { + const attrib = attribs[i]; + + gl.enableVertexAttribArray(attrib.attribute.location); + } + } +} diff --git a/packages/core/src/index.js b/packages/core/src/index.js index 58c456a..fd1f547 100644 --- a/packages/core/src/index.js +++ b/packages/core/src/index.js @@ -1,30 +1,30 @@ import * as resources from './textures/resources'; -import * as systems from './renderers/systems'; +import * as systems from './systems'; -export { resources }; export { systems }; +export { resources }; -export { default as Renderer } from './renderers/Renderer'; -export { default as AbstractRenderer } from './renderers/AbstractRenderer'; -export { default as FrameBuffer } from './textures/FrameBuffer'; +export { default as System } from './System'; +export { default as Renderer } from './Renderer'; +export { default as AbstractRenderer } from './AbstractRenderer'; +export { default as FrameBuffer } from './framebuffer/FrameBuffer'; export { default as CubeTexture } from './textures/CubeTexture'; export { default as BaseTexture } from './textures/BaseTexture'; export { default as Texture } from './textures/Texture'; export { default as TextureMatrix } from './textures/TextureMatrix'; -export { default as RenderTexture } from './textures/RenderTexture'; -export { default as BaseRenderTexture } from './textures/BaseRenderTexture'; +export { default as RenderTexture } from './renderTexture/RenderTexture'; +export { default as BaseRenderTexture } from './renderTexture/BaseRenderTexture'; export { default as TextureUvs } from './textures/TextureUvs'; -export { default as State } from './renderers/State'; -export { default as ObjectRenderer } from './renderers/utils/ObjectRenderer'; -export { default as Quad } from './renderers/utils/Quad'; -export { default as QuadUv } from './renderers/utils/QuadUv'; -export { default as checkMaxIfStatmentsInShader } from './renderers/utils/checkMaxIfStatmentsInShader'; +export { default as State } from './state/State'; +export { default as ObjectRenderer } from './batch/ObjectRenderer'; +export { default as Quad } from './utils/Quad'; +export { default as QuadUv } from './utils/QuadUv'; +export { default as checkMaxIfStatmentsInShader } from './shader/utils/checkMaxIfStatmentsInShader'; export { default as Shader } from './shader/Shader'; export { default as Program } from './shader/Program'; export { default as UniformGroup } from './shader/UniformGroup'; -export { default as SpriteMaskFilter } from './renderers/filters/spriteMask/SpriteMaskFilter'; -export { default as Filter } from './renderers/filters/Filter'; +export { default as SpriteMaskFilter } from './filters/spriteMask/SpriteMaskFilter'; +export { default as Filter } from './filters/Filter'; export { default as Attribute } from './geometry/Attribute'; export { default as Buffer } from './geometry/Buffer'; export { default as Geometry } from './geometry/Geometry'; - diff --git a/packages/core/src/mask/MaskSystem.js b/packages/core/src/mask/MaskSystem.js new file mode 100644 index 0000000..99174c2 --- /dev/null +++ b/packages/core/src/mask/MaskSystem.js @@ -0,0 +1,199 @@ +import System from '../System'; +import AlphaMaskFilter from '../filters/spriteMask/SpriteMaskFilter'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class MaskSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + // TODO - we don't need both! + this.scissor = false; + this.scissorData = null; + this.scissorRenderTarget = null; + + this.enableScissor = false; + + this.alphaMaskPool = []; + this.alphaMaskIndex = 0; + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.DisplayObject} target - Display Object to push the mask to + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + push(target, maskData) + { + // TODO the root check means scissor rect will not + // be used on render textures more info here: + // https://github.com/pixijs/pixi.js/pull/3545 + + if (maskData.texture) + { + this.pushSpriteMask(target, maskData); + } + else if (this.enableScissor + && !this.scissor + && this.renderer._activeRenderTarget.root + && !this.renderer.stencil.stencilMaskStack.length + && maskData.isFastRect()) + { + const matrix = maskData.worldTransform; + + let rot = Math.atan2(matrix.b, matrix.a); + + // use the nearest degree! + rot = Math.round(rot * (180 / Math.PI)); + + if (rot % 90) + { + this.pushStencilMask(maskData); + } + else + { + this.pushScissorMask(target, maskData); + } + } + else + { + this.pushStencilMask(maskData); + } + } + + /** + * Removes the last mask from the mask stack and doesn't return it. + * + * @param {PIXI.DisplayObject} target - Display Object to pop the mask from + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + pop(target, maskData) + { + if (maskData.texture) + { + this.popSpriteMask(target, maskData); + } + else if (this.enableScissor && !this.renderer.stencil.stencilMaskStack.length) + { + this.popScissorMask(target, maskData); + } + else + { + this.popStencilMask(target, maskData); + } + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.RenderTarget} target - Display Object to push the sprite mask to + * @param {PIXI.Sprite} maskData - Sprite to be used as the mask + */ + pushSpriteMask(target, maskData) + { + let alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; + + if (!alphaMaskFilter) + { + alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; + } + + alphaMaskFilter[0].resolution = this.renderer.resolution; + alphaMaskFilter[0].maskSprite = maskData; + + // TODO - may cause issues! + target.filterArea = maskData.getBounds(true); + + this.renderer.filterSystem.pushFilter(target, alphaMaskFilter); + + this.alphaMaskIndex++; + } + + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popSpriteMask() + { + this.renderer.filterSystem.popFilter(); + this.alphaMaskIndex--; + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + pushStencilMask(maskData) + { + this.renderer.batch.flush(); + this.renderer.stencil.pushStencil(maskData); + } + + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popStencilMask() + { + // this.renderer.currentRenderer.stop(); + this.renderer.stencil.popStencil(); + } + + /** + * + * @param {PIXI.DisplayObject} target - Display Object to push the mask to + * @param {PIXI.Graphics} maskData - The masking data. + */ + pushScissorMask(target, maskData) + { + maskData.renderable = true; + + const renderTarget = this.renderer._activeRenderTarget; + + const bounds = maskData.getBounds(); + + bounds.fit(renderTarget.size); + maskData.renderable = false; + + this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); + + const resolution = this.renderer.resolution; + + this.renderer.gl.scissor( + bounds.x * resolution, + (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, + bounds.width * resolution, + bounds.height * resolution + ); + + this.scissorRenderTarget = renderTarget; + this.scissorData = maskData; + this.scissor = true; + } + + /** + * + * + */ + popScissorMask() + { + this.scissorRenderTarget = null; + this.scissorData = null; + this.scissor = false; + + // must be scissor! + const gl = this.renderer.gl; + + gl.disable(gl.SCISSOR_TEST); + } +} diff --git a/packages/core/src/mask/StencilSystem.js b/packages/core/src/mask/StencilSystem.js new file mode 100644 index 0000000..62796a5 --- /dev/null +++ b/packages/core/src/mask/StencilSystem.js @@ -0,0 +1,133 @@ +import System from '../System'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class StencilSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + this.stencilMaskStack = []; + } + + /** + * Changes the mask stack that is used by this System. + * + * @param {PIXI.Graphics[]} stencilMaskStack - The mask stack + */ + setMaskStack(stencilMaskStack) + { + const gl = this.renderer.gl; + + if (stencilMaskStack.length !== this.stencilMaskStack.length) + { + if (stencilMaskStack.length === 0) + { + gl.disable(gl.STENCIL_TEST); + } + else + { + gl.enable(gl.STENCIL_TEST); + } + } + + this.stencilMaskStack = stencilMaskStack; + } + + /** + * Applies the Mask and adds it to the current stencil stack. @alvin + * + * @param {PIXI.Graphics} graphics - The mask + */ + pushStencil(graphics) + { + this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); + + // this.renderer._activeRenderTarget.attachStencilBuffer(); + + const gl = this.renderer.gl; + const prevMaskCount = this.stencilMaskStack.length; + + if (prevMaskCount === 0) + { + gl.enable(gl.STENCIL_TEST); + } + + this.stencilMaskStack.push(graphics); + + // Increment the refference stencil value where the new mask overlaps with the old ones. + gl.colorMask(false, false, false, false); + gl.stencilFunc(gl.EQUAL, prevMaskCount, this._getBitwiseMask()); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.INCR); + this.renderer.plugins.graphics.render(graphics); + + this._useCurrent(); + } + + /** + * Removes the last mask from the stencil stack. @alvin + */ + popStencil() + { + this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); + + const gl = this.renderer.gl; + const graphics = this.stencilMaskStack.pop(); + + if (this.stencilMaskStack.length === 0) + { + // the stack is empty! + gl.disable(gl.STENCIL_TEST); + gl.clear(gl.STENCIL_BUFFER_BIT); + gl.clearStencil(0); + } + else + { + // Decrement the refference stencil value where the popped mask overlaps with the other ones + gl.colorMask(false, false, false, false); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.DECR); + this.renderer.plugins.graphics.render(graphics); + + this._useCurrent(); + } + } + + /** + * Setup renderer to use the current stencil data. + */ + _useCurrent() + { + const gl = this.renderer.gl; + + gl.colorMask(true, true, true, true); + gl.stencilFunc(gl.EQUAL, this.stencilMaskStack.length, this._getBitwiseMask()); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP); + } + + /** + * Fill 1s equal to the number of acitve stencil masks. + * + * @return {number} The bitwise mask. + */ + _getBitwiseMask() + { + return (1 << this.stencilMaskStack.length) - 1; + } + + /** + * Destroys the mask stack. + * + */ + destroy() + { + super.destroy(this); + + this.stencilMaskStack = null; + } +} diff --git a/packages/core/src/projection/ProjectionSystem.js b/packages/core/src/projection/ProjectionSystem.js new file mode 100644 index 0000000..6e63b73 --- /dev/null +++ b/packages/core/src/projection/ProjectionSystem.js @@ -0,0 +1,73 @@ +import System from '../System'; +import { Matrix } from '@pixi/math'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class ProjectionSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.projectionMatrix = new Matrix(); + } + + update(destinationFrame, sourceFrame, resolution, root) + { + this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; + this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; + + this.calculateProjection(this.destinationFrame, this.sourceFrame, resolution, root); + + this.renderer.globalUniforms.uniforms.projectionMatrix = this.projectionMatrix; + this.renderer.globalUniforms.update(); + } + + /** + * Updates the projection matrix based on a projection frame (which is a rectangle) + * + * @param {Rectangle} destinationFrame - The destination frame. + * @param {Rectangle} sourceFrame - The source frame. + */ + calculateProjection(destinationFrame, sourceFrame, resolution, root) + { + const pm = this.projectionMatrix; + + // I don't think we will need this line.. + // pm.identity(); + + if (!root) + { + pm.a = (1 / destinationFrame.width * 2) * resolution; + pm.d = (1 / destinationFrame.height * 2) * resolution; + + pm.tx = -1 - (sourceFrame.x * pm.a); + pm.ty = -1 - (sourceFrame.y * pm.d); + } + else + { + pm.a = (1 / destinationFrame.width * 2) * resolution; + pm.d = (-1 / destinationFrame.height * 2) * resolution; + + pm.tx = -1 - (sourceFrame.x * pm.a); + pm.ty = 1 - (sourceFrame.y * pm.d); + } + } + + /** + * Sets the transform of the active render target to the given matrix + * + * @param {PIXI.Matrix} matrix - The transformation matrix + */ + setTransform()// matrix) + { + // this._activeRenderTarget.transform = matrix; + } +} diff --git a/packages/core/src/renderTexture/BaseRenderTexture.js b/packages/core/src/renderTexture/BaseRenderTexture.js new file mode 100644 index 0000000..88eb83b --- /dev/null +++ b/packages/core/src/renderTexture/BaseRenderTexture.js @@ -0,0 +1,137 @@ +import BaseTexture from '../textures/BaseTexture'; +import FrameBuffer from '../framebuffer/FrameBuffer'; + +/** + * A BaseRenderTexture is a special texture that allows any PixiJS display object to be rendered to it. + * + * __Hint__: All DisplayObjects (i.e. Sprites) that render to a BaseRenderTexture should be preloaded + * otherwise black rectangles will be drawn instead. + * + * A BaseRenderTexture takes a snapshot of any Display Object given to its render method. The position + * and rotation of the given Display Objects is ignored. For example: + * + * ```js + * let renderer = PIXI.autoDetectRenderer(1024, 1024, { view: canvas, ratio: 1 }); + * let baseRenderTexture = new PIXI.BaseRenderTexture(renderer, 800, 600); + * let sprite = PIXI.Sprite.fromImage("spinObj_01.png"); + * + * sprite.position.x = 800/2; + * sprite.position.y = 600/2; + * sprite.anchor.x = 0.5; + * sprite.anchor.y = 0.5; + * + * baseRenderTexture.render(sprite); + * ``` + * + * The Sprite in this case will be rendered using its local transform. To render this sprite at 0,0 + * you can clear the transform + * + * ```js + * + * sprite.setTransform() + * + * let baseRenderTexture = new PIXI.BaseRenderTexture(100, 100); + * let renderTexture = new PIXI.RenderTexture(baseRenderTexture); + * + * renderer.render(sprite, renderTexture); // Renders to center of RenderTexture + * ``` + * + * @class + * @extends PIXI.BaseTexture + * @memberof PIXI + */ +export default class BaseRenderTexture extends BaseTexture +{ + /** + * @param {object} [options] + * @param {number} [options.width=100] - The width of the base render texture + * @param {number} [options.height=100] - The height of the base render texture + * @param {PIXI.SCALE_MODES} [options.scaleMode] - See {@link PIXI.SCALE_MODES} for possible values + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the texture being generated + */ + constructor(options) + { + if (typeof options === 'number') + { + /* eslint-disable prefer-rest-params */ + // Backward compatibility of signature + const width = arguments[0]; + const height = arguments[1]; + const scaleMode = arguments[2]; + const resolution = arguments[3]; + + options = { width, height, scaleMode, resolution }; + /* eslint-enable prefer-rest-params */ + } + + super(null, options); + + const { width, height } = options || {}; + + // Set defaults + this.mipmap = false; + this.width = Math.ceil(width) || 100; + this.height = Math.ceil(height) || 100; + this.valid = true; + + /** + * A map of renderer IDs to webgl renderTargets + * + * @private + * @member {object} + */ + // this._glRenderTargets = {}; + + /** + * A reference to the canvas render target (we only need one as this can be shared across renderers) + * + * @private + * @member {object} + */ + this._canvasRenderTarget = null; + + this.clearColor = [0, 0, 0, 0]; + + this.frameBuffer = new FrameBuffer(width * this.resolution, height * this.resolution) + .addColorTexture(0, this); + + // TODO - could this be added the systems? + + /** + * The data structure for the stencil masks + * + * @member {PIXI.Graphics[]} + */ + this.stencilMaskStack = []; + + /** + * The data structure for the filters + * + * @member {PIXI.Graphics[]} + */ + this.filterStack = [{}]; + } + + /** + * Resizes the BaseRenderTexture. + * + * @param {number} width - The width to resize to. + * @param {number} height - The height to resize to. + */ + resize(width, height) + { + width = Math.ceil(width); + height = Math.ceil(height); + this.frameBuffer.resize(width * this.resolution, height * this.resolution); + } + + /** + * Destroys this texture + * + */ + destroy() + { + super.destroy(true); + this.renderer = null; + } +} diff --git a/packages/core/src/renderTexture/RenderTexture.js b/packages/core/src/renderTexture/RenderTexture.js new file mode 100644 index 0000000..f1ecf1f --- /dev/null +++ b/packages/core/src/renderTexture/RenderTexture.js @@ -0,0 +1,153 @@ +import BaseRenderTexture from './BaseRenderTexture'; +import Texture from '../textures/Texture'; + +/** + * A RenderTexture is a special texture that allows any PixiJS display object to be rendered to it. + * + * __Hint__: All DisplayObjects (i.e. Sprites) that render to a RenderTexture should be preloaded + * otherwise black rectangles will be drawn instead. + * + * A RenderTexture takes a snapshot of any Display Object given to its render method. For example: + * + * ```js + * let renderer = PIXI.autoDetectRenderer(1024, 1024, { view: canvas, ratio: 1 }); + * let renderTexture = PIXI.RenderTexture.create(800, 600); + * let sprite = PIXI.Sprite.fromImage("spinObj_01.png"); + * + * sprite.position.x = 800/2; + * sprite.position.y = 600/2; + * sprite.anchor.x = 0.5; + * sprite.anchor.y = 0.5; + * + * renderer.render(sprite, renderTexture); + * ``` + * + * The Sprite in this case will be rendered using its local transform. To render this sprite at 0,0 + * you can clear the transform + * + * ```js + * + * sprite.setTransform() + * + * let renderTexture = new PIXI.RenderTexture.create(100, 100); + * + * renderer.render(sprite, renderTexture); // Renders to center of RenderTexture + * ``` + * + * @class + * @extends PIXI.Texture + * @memberof PIXI + */ +export default class RenderTexture extends Texture +{ + /** + * @param {PIXI.BaseRenderTexture} baseRenderTexture - The renderer used for this RenderTexture + * @param {PIXI.Rectangle} [frame] - The rectangle frame of the texture to show + */ + constructor(baseRenderTexture, frame) + { + // support for legacy.. + let _legacyRenderer = null; + + if (!(baseRenderTexture instanceof BaseRenderTexture)) + { + /* eslint-disable prefer-rest-params, no-console */ + const width = arguments[1]; + const height = arguments[2]; + const scaleMode = arguments[3]; + const resolution = arguments[4]; + + // we have an old render texture.. + console.warn(`Please use RenderTexture.create(${width}, ${height}) instead of the ctor directly.`); + _legacyRenderer = arguments[0]; + /* eslint-enable prefer-rest-params, no-console */ + + frame = null; + baseRenderTexture = new BaseRenderTexture({ + width, + height, + scaleMode, + resolution, + }); + } + + /** + * The base texture object that this texture uses + * + * @member {BaseTexture} + */ + super(baseRenderTexture, frame); + + this.legacyRenderer = _legacyRenderer; + + /** + * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. + * + * @member {boolean} + */ + this.valid = true; + + /** + * FilterSystem temporary storage + * @private + * @member {PIXI.Rectangle} + */ + this.filterFrame = null; + + this._updateUvs(); + } + + /** + * Resizes the RenderTexture. + * + * @param {number} width - The width to resize to. + * @param {number} height - The height to resize to. + * @param {boolean} [resizeBaseTexture=true] - Should the baseTexture.width and height values be resized as well? + */ + resize(width, height, resizeBaseTexture = true) + { + width = Math.ceil(width); + height = Math.ceil(height); + + // TODO - could be not required.. + this.valid = (width > 0 && height > 0); + + this._frame.width = this.orig.width = width; + this._frame.height = this.orig.height = height; + + if (resizeBaseTexture) + { + this.baseTexture.resize(width, height); + } + + this._updateUvs(); + } + + /** + * A short hand way of creating a render texture. + * + * @param {object} [options] - Options + * @param {number} [options.width=100] - The width of the render texture + * @param {number} [options.height=100] - The height of the render texture + * @param {number} [options.scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the texture being generated + * @return {PIXI.RenderTexture} The new render texture + */ + static create(options) + { + // fallback, old-style: create(width, height, scaleMode, resolution) + if (typeof options === 'number') + { + /* eslint-disable prefer-rest-params */ + options = { + width: options, + height: arguments[1], + scaleMode: arguments[2], + resolution: arguments[3], + }; + /* eslint-enable prefer-rest-params */ + } + + return new RenderTexture(new BaseRenderTexture(options)); + } +} diff --git a/packages/core/src/renderTexture/RenderTextureSystem.js b/packages/core/src/renderTexture/RenderTextureSystem.js new file mode 100644 index 0000000..8d0ca61 --- /dev/null +++ b/packages/core/src/renderTexture/RenderTextureSystem.js @@ -0,0 +1,115 @@ +import System from '../System'; +import { Rectangle } from '@pixi/math'; + +const tempRect = new Rectangle(); + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class RenderTextureSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.clearColor = renderer._backgroundColorRgba; + + // TODO moe this property somewhere else! + this.defaultMaskStack = []; + this.defaultFilterStack = [{}]; + + // empty render texture? + this.renderTexture = null; + + this.destinationFrame = new Rectangle(); + } + + bind(renderTexture, sourceFrame, destinationFrame) + { + // TODO - do we want this?? + if (this.renderTexture === renderTexture) return; + this.renderTexture = renderTexture; + + const renderer = this.renderer; + + if (renderTexture) + { + const baseTexture = renderTexture.baseTexture; + + if (!destinationFrame) + { + tempRect.width = baseTexture.realWidth; + tempRect.height = baseTexture.realHeight; + + destinationFrame = tempRect; + } + + if (!sourceFrame) + { + sourceFrame = destinationFrame; + } + + this.renderer.framebuffer.bind(baseTexture.frameBuffer, destinationFrame); + + this.renderer.projection.update(destinationFrame, sourceFrame, baseTexture.resolution, false); + this.renderer.stencil.setMaskStack(baseTexture.stencilMaskStack); + } + else + { + // TODO these validation checks happen deeper down.. + // thing they can be avoided.. + if (!destinationFrame) + { + tempRect.width = renderer.width; + tempRect.height = renderer.height; + + destinationFrame = tempRect; + } + + if (!sourceFrame) + { + sourceFrame = destinationFrame; + } + + renderer.framebuffer.bind(null, destinationFrame); + + // TODO store this.. + this.renderer.projection.update(destinationFrame, sourceFrame, this.renderer.resolution, true); + this.renderer.stencil.setMaskStack(this.defaultMaskStack); + } + + this.destinationFrame.copyFrom(destinationFrame); + } + + /** + * Erases the render texture and fills the drawing area with a colour + * + * @param {number} [clearColor] - The colour + * @return {PIXI.Renderer} Returns itself. + */ + clear(clearColor) + { + if (this.renderTexture) + { + clearColor = clearColor || this.renderTexture.baseTexture.clearColor; + } + else + { + clearColor = clearColor || this.clearColor; + } + + this.renderer.framebuffer.clear(clearColor[0], clearColor[1], clearColor[2], clearColor[3]); + } + + resize()// screenWidth, screenHeight) + { + // resize the root only! + this.bind(null); + } +} diff --git a/packages/core/src/renderers/AbstractRenderer.js b/packages/core/src/renderers/AbstractRenderer.js deleted file mode 100644 index 928670f..0000000 --- a/packages/core/src/renderers/AbstractRenderer.js +++ /dev/null @@ -1,353 +0,0 @@ -import { hex2string, hex2rgb } from '@pixi/utils'; -import { Matrix, Rectangle } from '@pixi/math'; -import { RENDERER_TYPE } from '@pixi/constants'; -import { settings } from '@pixi/settings'; -import { Container } from '@pixi/display'; -import RenderTexture from '../textures/RenderTexture'; -import EventEmitter from 'eventemitter3'; - -const tempMatrix = new Matrix(); - -/** - * 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 EventEmitter - * @memberof PIXI - */ -export default class AbstractRenderer extends EventEmitter -{ - // eslint-disable-next-line valid-jsdoc - /** - * @param {string} system - The name of the system this renderer is for. - * @param {object} [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.transparent=false] - If the render view is transparent, default false - * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false - * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) - * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. The - * resolution of the renderer retina would be 2. - * @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 {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when rendering, - * stopping pixel interpolation. - */ - constructor(system, options, arg2, arg3) - { - super(); - - // Support for constructor(system, screenWidth, screenHeight, options) - if (typeof options === 'number') - { - options = Object.assign({ - width: options, - height: arg2 || settings.RENDER_OPTIONS.height, - }, arg3); - } - - // 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 = RENDERER_TYPE.UNKNOWN; - - /** - * Measurements of the screen. (0, 0, screenWidth, screenHeight) - * - * Its safe to use as filterArea or hitArea for 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 1 - */ - this.resolution = options.resolution || settings.RESOLUTION; - - /** - * Whether the render view is transparent - * - * @member {boolean} - */ - this.transparent = options.transparent; - - /** - * Whether css dimensions of canvas view should be resized to screen dimensions automatically - * - * @member {boolean} - */ - this.autoResize = options.autoResize || false; - - /** - * Tracks the blend modes useful for this renderer. - * - * @member {object} - */ - this.blendModes = null; - - /** - * 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; - - /** - * If true PixiJS will Math.floor() x/y values when rendering, stopping pixel interpolation. - * Handy for crisp pixel art and speed on legacy devices. - * - * @member {boolean} - */ - this.roundPixels = options.roundPixels; - - /** - * The background color as a number. - * - * @member {number} - * @private - */ - this._backgroundColor = 0x000000; - - /** - * The background color as an [R, G, B] array. - * - * @member {number[]} - * @private - */ - this._backgroundColorRgba = [0, 0, 0, 0]; - - /** - * The background color as a string. - * - * @member {string} - * @private - */ - this._backgroundColorString = '#000000'; - - this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter - - /** - * This temporary display object used as the parent of the currently being rendered item - * - * @member {PIXI.DisplayObject} - * @private - */ - this._tempDisplayObjectParent = new Container(); - - /** - * The last root object that the renderer tried to render. - * - * @member {PIXI.DisplayObject} - * @private - */ - this._lastObjectRendered = this._tempDisplayObjectParent; - - /** - * Collection of plugins. - * @readonly - * @member {object} - */ - this.plugins = {}; - } - - /** - * Initialize the plugins. - * - * @protected - * @param {object} staticMap - The dictionary of staticly saved plugins. - */ - initPlugins(staticMap) - { - 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() - { - return this.view.width; - } - - /** - * Same as view.height, actual number of pixels in the canvas by vertical - * - * @member {number} - * @readonly - * @default 600 - */ - get height() - { - return this.view.height; - } - - /** - * Resizes the screen and canvas to the specified width and height - * Canvas dimensions are multiplied by resolution - * - * @param {number} screenWidth - the new width of the screen - * @param {number} screenHeight - the new height of the screen - */ - resize(screenWidth, screenHeight) - { - this.screen.width = screenWidth; - this.screen.height = screenHeight; - - this.view.width = screenWidth * this.resolution; - this.view.height = screenHeight * this.resolution; - - if (this.autoResize) - { - this.view.style.width = `${screenWidth}px`; - this.view.style.height = `${screenHeight}px`; - } - } - - /** - * 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 {PIXI.DisplayObject} displayObject - The displayObject the object will be generated from - * @param {number} scaleMode - Should be one of the scaleMode consts - * @param {number} resolution - The resolution / device pixel ratio of the texture being generated - * @param {PIXI.Rectangle} [region] - The region of the displayObject, that shall be rendered, - * if no region is specified, defaults to the local bounds of the displayObject. - * @return {PIXI.Texture} a texture of the graphics object - */ - generateTexture(displayObject, scaleMode, resolution, region) - { - region = region || displayObject.getLocalBounds(); - - // 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(region.width | 0, region.height | 0, scaleMode, resolution); - - tempMatrix.tx = -region.x; - tempMatrix.ty = -region.y; - - this.render(displayObject, renderTexture, false, tempMatrix, true); - - return renderTexture; - } - - /** - * Removes everything from the renderer and optionally removes the Canvas DOM element. - * - * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. - */ - destroy(removeView) - { - 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); - } - - this.plugins = null; - - this.type = RENDERER_TYPE.UNKNOWN; - - this.view = null; - - this.screen = null; - - this.resolution = 0; - - this.transparent = false; - - this.autoResize = false; - - this.blendModes = null; - - this.options = null; - - this.preserveDrawingBuffer = false; - this.clearBeforeRender = false; - - this.roundPixels = false; - - this._backgroundColor = 0; - this._backgroundColorRgba = null; - this._backgroundColorString = null; - - this._tempDisplayObjectParent = null; - this._lastObjectRendered = null; - } - - /** - * The background color to fill if not transparent - * - * @member {number} - */ - get backgroundColor() - { - return this._backgroundColor; - } - - set backgroundColor(value) // eslint-disable-line require-jsdoc - { - this._backgroundColor = value; - this._backgroundColorString = hex2string(value); - hex2rgb(value, this._backgroundColorRgba); - } -} diff --git a/packages/core/src/renderers/Renderer.js b/packages/core/src/renderers/Renderer.js deleted file mode 100644 index a2020b1..0000000 --- a/packages/core/src/renderers/Renderer.js +++ /dev/null @@ -1,329 +0,0 @@ -import AbstractRenderer from './AbstractRenderer'; -import { sayHello } from '@pixi/utils'; -import MaskSystem from './systems/MaskSystem'; -import StencilSystem from './systems/StencilSystem'; -import FilterSystem from './systems/filter/FilterSystem'; -import FramebufferSystem from './systems/FramebufferSystem'; -import RenderTextureSystem from './systems/RenderTextureSystem'; -import TextureSystem from './systems/textures/TextureSystem'; -import ProjectionSystem from './systems/ProjectionSystem'; -import StateSystem from './systems/StateSystem'; -import GeometrySystem from './systems/geometry/GeometrySystem'; -import ShaderSystem from './systems/shader/ShaderSystem'; -import ContextSystem from './systems/ContextSystem'; -import BatchSystem from './systems/BatchSystem'; -import TextureGCSystem from './systems/textures/TextureGCSystem'; -import { RENDERER_TYPE } from '@pixi/constants'; -import UniformGroup from '../shader/UniformGroup'; -import { Matrix } from '@pixi/math'; -import Runner from 'mini-runner'; - -/** - * 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 Render works by automatically managing webGLBatchs. - * 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 :) - * - * @class - * @memberof PIXI - * @extends PIXI.AbstractRenderer - */ -export default class Renderer extends AbstractRenderer -{ - // eslint-disable-next-line valid-jsdoc - /** - * - * @param {object} [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.transparent=false] - If the render view is transparent, default false - * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false - * @param {boolean} [options.antialias=false] - sets antialias. If not available natively then FXAA - * antialiasing is used - * @param {boolean} [options.forceFXAA=false] - forces FXAA antialiasing to be used over native. - * FXAA is faster, but may not always look as great - * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. - * The resolution of the renderer retina would be 2. - * @param {boolean} [options.clearBeforeRender=true] - This sets if the renderer will clear - * the canvas or not before the new render pass. If you wish to set this to false, you *must* set - * preserveDrawingBuffer to `true`. - * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, - * enable this if you need to call toDataUrl on the webgl context. - * @param {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when - * rendering, stopping pixel interpolation. - * @param {number} [options.backgroundColor=0x000000] - The background color of the rendered area - * (shown if not transparent). - * @param {boolean} [options.legacy=false] - If true PixiJS will aim to ensure compatibility - * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. - * @param {string} [options.powerPreference] - Parameter passed to webgl context, set to "high-performance" - * for devices with dual graphics card - */ - constructor(options, arg2, arg3) - { - super('WebGL', options, arg2, arg3); - - /** - * The type of this renderer as a standardised const - * - * @member {number} - * @see PIXI.RENDERER_TYPE - */ - this.type = RENDERER_TYPE.WEBGL; - - // this will be set by the contextSystem (this.context) - this.gl = null; - this.CONTEXT_UID = 0; - this.legacy = !!options.legacy; - - // TODO legacy! - - // runners! - this.runners = { - destroy: new Runner('destroy'), - contextChange: new Runner('contextChange', 1), - reset: new Runner('reset'), - update: new Runner('update'), - postrender: new Runner('postrender'), - prerender: new Runner('prerender'), - resize: new Runner('resize', 2), - }; - - 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(GeometrySystem, 'geometry') - .addSystem(FramebufferSystem, 'framebuffer') - .addSystem(StencilSystem, 'stencil') - .addSystem(ProjectionSystem, 'projection') - .addSystem(TextureGCSystem, 'textureGC') - .addSystem(FilterSystem, 'filter') - .addSystem(RenderTextureSystem, 'renderTexture') - .addSystem(BatchSystem, 'batch'); - - this.initPlugins(Renderer.__plugins); - - /** - * The options passed in to create a new webgl context. - * - * @member {object} - * @private - */ - if (options.context) - { - this.context.initFromContext(options.context); - } - else - { - this.context.initFromOptions({ - alpha: this.transparent, - antialias: options.antialias, - premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', - stencil: true, - 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); - } - - addSystem(_class, name) - { - if (!name) - { - name = _class.name; - } - - // TODO - read name from class.name.. - - /* - if(name.includes('System')) - { - name = name.replace('System', ''); - name = name.charAt(0).toLowerCase() + name.slice(1); - } - */ - - const system = new _class(this); - - if (this[name]) - { - throw new Error(`Whoops! ${name} is already a manger`); - } - - this[name] = system; - - for (const i in this.runners) - { - this.runners[i].add(system); - } - - return this; - - /** - * 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. - */ - } - - /** - * Renders the object to its webGL view - * - * @param {PIXI.DisplayObject} displayObject - the object to be rendered - * @param {PIXI.RenderTexture} renderTexture - The render texture to render to. - * @param {boolean} [clear] - Should the canvas be cleared before the new render - * @param {PIXI.Transform} [transform] - A transform to apply to the render texture before rendering. - * @param {boolean} [skipUpdateTransform] - Should we skip the update transform pass? - */ - render(displayObject, renderTexture, clear, transform, skipUpdateTransform) - { - // can be handy to know! - this.renderingToScreen = !renderTexture; - - this.runners.prerender.run(); - this.emit('prerender'); - - // 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.parent; - - displayObject.parent = this._tempDisplayObjectParent; - displayObject.updateTransform(); - displayObject.parent = 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.run(); - - this.emit('postrender'); - } - - /** - * Resizes the webGL view to the specified width and height. - * - * @param {number} screenWidth - the new width of the screen - * @param {number} screenHeight - the new height of the screen - */ - resize(screenWidth, screenHeight) - { - AbstractRenderer.prototype.resize.call(this, screenWidth, screenHeight); - - this.runners.resize.run(screenWidth, screenHeight); - } - - /** - * Resets the WebGL state so you can render things however you fancy! - * - * @return {PIXI.Renderer} Returns itself. - */ - reset() - { - this.runners.reset.run(); - - return this; - } - - clear() - { - this.framebuffer.bind(); - this.framebuffer.clear(); - } - - /** - * Removes everything from the renderer (event listeners, spritebatch, etc...) - * - * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. - * See: https://github.com/pixijs/pixi.js/issues/2233 - */ - destroy(removeView) - { - this.runners.destroy.run(); - - // call base destroy - super.destroy(removeView); - - // TODO nullify all the managers.. - this.gl = null; - } - - /** - * 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 - * builds and excluding plugins. - * @name PIXI.Renderer#plugins - * @type {object} - * @readonly - * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. - * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. - * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. - * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. - */ - - /** - * Adds a plugin to the renderer. - * - * @method - * @param {string} pluginName - The name of the plugin. - * @param {Function} ctor - The constructor function or class for the plugin. - */ - static registerPlugin(pluginName, ctor) - { - Renderer.__plugins = Renderer.__plugins || {}; - Renderer.__plugins[pluginName] = ctor; - } -} diff --git a/packages/core/src/renderers/State.js b/packages/core/src/renderers/State.js deleted file mode 100644 index fa3f221..0000000 --- a/packages/core/src/renderers/State.js +++ /dev/null @@ -1,163 +0,0 @@ -/* eslint-disable max-len */ - -const BLEND = 0; -const OFFSET = 1; -const CULLING = 2; -const DEPTH_TEST = 3; -const WINDING = 4; - -/** - * This is a webGL state. It is passed The WebGL StateManager. - * Each mesh renderered may require webGL to be in a different state. - * For example you may want different blend mode or to enable polygon offsets - * - * @class - * @memberof PIXI - */ -export default class State -{ - /** - * - */ - constructor() - { - this.data = 0; - - this.blendMode = 0; - this.polygonOffset = 0; - - this.blend = true; - // this.depthTest = true; - } - - /** - * Activates blending of the computed fragment color values - * - * @member {boolean} - */ - get blend() - { - return !!(this.data & (1 << BLEND)); - } - - set blend(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << BLEND)) !== value) - { - this.data ^= (1 << BLEND); - } - } - - /** - * Activates adding an offset to depth values of polygon's fragments - * - * @member {boolean} - * @default false - */ - get offsets() - { - return !!(this.data & (1 << OFFSET)); - } - - set offsets(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << OFFSET)) !== value) - { - this.data ^= (1 << OFFSET); - } - } - - /** - * Activates culling of polygons. - * - * @member {boolean} - * @default false - */ - get culling() - { - return !!(this.data & (1 << CULLING)); - } - - set culling(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << CULLING)) !== value) - { - this.data ^= (1 << CULLING); - } - } - - /** - * Activates depth comparisons and updates to the depth buffer. - * - * @member {boolean} - * @default false - */ - get depthTest() - { - return !!(this.data & (1 << DEPTH_TEST)); - } - - set depthTest(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << DEPTH_TEST)) !== value) - { - this.data ^= (1 << DEPTH_TEST); - } - } - - /** - * Specifies whether or not front or back-facing polygons can be culled. - * @member {boolean} - * @default false - */ - get clockwiseFrontFace() - { - return !!(this.data & (1 << WINDING)); - } - - set clockwiseFrontFace(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << WINDING)) !== value) - { - this.data ^= (1 << WINDING); - } - } - - /** - * The blend mode to be applied when this state is set. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. - * Setting this mode to anything other than NO_BLEND will automatically switch blending on. - * - * @member {boolean} - * @default PIXI.BLEND_MODES.NORMAL - * @see PIXI.BLEND_MODES - */ - get blendMode() - { - return this._blendMode; - } - - set blendMode(value) // eslint-disable-line require-jsdoc - { - // 17 is NO BLEND - this.blend = (value !== 17); - this._blendMode = value; - } - - /** - * The polygon offset. Setting this property to anything other than 0 will automatically enable poygon offset fill. - * - * @member {number} - * @default 0 - */ - get polygonOffset() - { - return this._polygonOffset; - } - - set polygonOffset(value) // eslint-disable-line require-jsdoc - { - this.offsets = !!value; - this._polygonOffset = value; - } -} - diff --git a/packages/core/src/renderers/filters/Filter.js b/packages/core/src/renderers/filters/Filter.js deleted file mode 100644 index 2073b8d..0000000 --- a/packages/core/src/renderers/filters/Filter.js +++ /dev/null @@ -1,140 +0,0 @@ -import Shader from '../../shader/Shader'; -import Program from '../../shader/Program'; -import State from '../State'; -import { settings } from '@pixi/settings'; -// import extractUniformsFromSrc from './extractUniformsFromSrc'; -import defaultVertex from './defaultFilter.vert'; -import defaultFragment from './defaultFilter.frag'; - -// let math = require('../../math'); -/** - * @class - * @memberof PIXI - * @extends PIXI.Shader - */ -export default class Filter extends Shader -{ - /** - * @param {string} [vertexSrc] - The source of the vertex shader. - * @param {string} [fragmentSrc] - The source of the fragment shader. - * @param {object} [uniforms] - Custom uniforms to use to augment the built-in ones. - */ - constructor(vertexSrc, fragmentSrc, uniforms) - { - const program = Program.from(vertexSrc, fragmentSrc); - - super(program, uniforms); - - /** - * The padding of the filter. Some filters require extra space to breath such as a blur. - * Increasing this will add extra width and height to the bounds of the object that the - * filter is applied to. - * - * @member {number} - */ - this.padding = 0; - - /** - * The resolution of the filter. Setting this to be lower will lower the quality but - * increase the performance of the filter. - * - * @member {number} - */ - this.resolution = settings.RESOLUTION; - - /** - * If enabled is true the filter is applied, if false it will not. - * - * @member {boolean} - */ - this.enabled = true; - - /** - * If enabled, PixiJS will fit the filter area into boundaries for better performance. - * Switch it off if it does not work for specific shader. - * - * @member {boolean} - */ - this.autoFit = true; - - /** - * Legacy filters use position and uvs from attributes - * @member {boolean} - * @readonly - */ - this.legacy = !!this.program.attributeData.aTextureCoord; - - /** - * the webGL state the filter requires to render - * @member {PIXI.State} - */ - this.state = new State(); - } - - /** - * Applies the filter - * - * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from - * @param {PIXI.RenderTarget} input - The input render target. - * @param {PIXI.RenderTarget} output - The target to output to. - * @param {boolean} clear - Should the output be cleared before rendering to it - * @param {object} [currentState] - It's current state of filter. - * There are some useful properties in the currentState : - * target, filters, sourceFrame, destinationFrame, renderTarget, resolution - */ - apply(filterManager, input, output, clear, currentState, derp) // eslint-disable-line no-unused-vars - { - // do as you please! - - filterManager.applyFilter(this, input, output, clear, currentState, derp); - - // or just do a regular render.. - } - - /** - * Sets the blendmode of the filter - * - * @member {number} - * @default PIXI.BLEND_MODES.NORMAL - */ - get blendMode() - { - return this.state.blendMode; - } - - set blendMode(value) // eslint-disable-line require-jsdoc - { - this.state.blendMode = value; - } - - /** - * The default vertex shader source - * - * @static - * @constant - */ - static get defaultVertexSrc() - { - return defaultVertex; - } - - /** - * The default fragment shader source - * - * @static - * @constant - */ - static get defaultFragmentSrc() - { - return defaultFragment; - } -} - -/** - * Used for caching shader IDs - * - * @static - * @private - */ -Filter.SOURCE_KEY_MAP = {}; - diff --git a/packages/core/src/renderers/filters/defaultFilter.frag b/packages/core/src/renderers/filters/defaultFilter.frag deleted file mode 100644 index a14c1be..0000000 --- a/packages/core/src/renderers/filters/defaultFilter.frag +++ /dev/null @@ -1,22 +0,0 @@ -varying vec2 vTextureCoord; -varying vec2 vFilterCoord; - -uniform sampler2D uSampler; -uniform sampler2D filterSampler; - -void main(void){ - vec4 masky = texture2D(filterSampler, vFilterCoord); - vec4 sample = texture2D(uSampler, vTextureCoord); - vec4 color; - if(mod(vFilterCoord.x, 1.0) > 0.5) - { - color = vec4(1.0, 0.0, 0.0, 1.0); - } - else - { - color = vec4(0.0, 1.0, 0.0, 1.0); - } - // gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0); - gl_FragColor = mix(sample, masky, 0.5); - gl_FragColor *= sample.a; -} diff --git a/packages/core/src/renderers/filters/defaultFilter.vert b/packages/core/src/renderers/filters/defaultFilter.vert deleted file mode 100644 index e1febca..0000000 --- a/packages/core/src/renderers/filters/defaultFilter.vert +++ /dev/null @@ -1,14 +0,0 @@ -attribute vec2 aVertexPosition; -attribute vec2 aTextureCoord; - -uniform mat3 projectionMatrix; -uniform mat3 filterMatrix; - -varying vec2 vTextureCoord; -varying vec2 vFilterCoord; - -void main(void){ - gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); - vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy; - vTextureCoord = aTextureCoord ; -} diff --git a/packages/core/src/renderers/filters/filterTransforms.js b/packages/core/src/renderers/filters/filterTransforms.js deleted file mode 100644 index 87a3cd6..0000000 --- a/packages/core/src/renderers/filters/filterTransforms.js +++ /dev/null @@ -1,50 +0,0 @@ -import { Matrix } from '@pixi/math'; - -/** - * Calculates the mapped matrix - * @param filterArea {Rectangle} The filter area - * @param sprite {Sprite} the target sprite - * @param outputMatrix {Matrix} @alvin - * @private - */ -// this returns a matrix that will normalise map filter cords in the filter to screen space -export function calculateScreenSpaceMatrix(outputMatrix, filterArea, textureSize) -{ - // TODO unwrap? - const mappedMatrix = outputMatrix.identity(); - - mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); - - mappedMatrix.scale(textureSize.width, textureSize.height); - - return mappedMatrix; -} - -export function calculateNormalizedScreenSpaceMatrix(outputMatrix, filterArea, textureSize) -{ - const mappedMatrix = outputMatrix.identity(); - - mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); - - const translateScaleX = (textureSize.width / filterArea.width); - const translateScaleY = (textureSize.height / filterArea.height); - - mappedMatrix.scale(translateScaleX, translateScaleY); - - return mappedMatrix; -} - -// this will map the filter coord so that a texture can be used based on the transform of a sprite -export function calculateSpriteMatrix(outputMatrix, filterArea, textureSize, sprite) -{ - const orig = sprite._texture.orig; - const mappedMatrix = outputMatrix.set(textureSize.width, 0, 0, textureSize.height, filterArea.x, filterArea.y); - const worldTransform = sprite.worldTransform.copyTo(Matrix.TEMP_MATRIX); - - worldTransform.invert(); - mappedMatrix.prepend(worldTransform); - mappedMatrix.scale(1.0 / orig.width, 1.0 / orig.height); - mappedMatrix.translate(sprite.anchor.x, sprite.anchor.y); - - return mappedMatrix; -} diff --git a/packages/core/src/renderers/filters/spriteMask/SpriteMaskFilter.js b/packages/core/src/renderers/filters/spriteMask/SpriteMaskFilter.js deleted file mode 100644 index d5a91a4..0000000 --- a/packages/core/src/renderers/filters/spriteMask/SpriteMaskFilter.js +++ /dev/null @@ -1,63 +0,0 @@ -import Filter from '../Filter'; -import { Matrix } from '@pixi/math'; -import vertex from './spriteMaskFilter.vert'; -import fragment from './spriteMaskFilter.frag'; -import { default as TextureMatrix } from '../../../textures/TextureMatrix'; - -/** - * The SpriteMaskFilter class - * - * @class - * @extends PIXI.Filter - * @memberof PIXI - */ -export default class SpriteMaskFilter extends Filter -{ - /** - * @param {PIXI.Sprite} sprite - the target sprite - */ - constructor(sprite) - { - const maskMatrix = new Matrix(); - - super(vertex, fragment); - - sprite.renderable = false; - - this.maskSprite = sprite; - this.maskMatrix = maskMatrix; - } - - /** - * Applies the filter - * - * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from - * @param {PIXI.RenderTarget} input - The input render target. - * @param {PIXI.RenderTarget} output - The target to output to. - */ - apply(filterManager, input, output) - { - const maskSprite = this.maskSprite; - const tex = this.maskSprite.texture; - - if (!tex.valid) - { - return; - } - if (!tex.transform) - { - // margin = 0.0, let it bleed a bit, shader code becomes easier - // assuming that atlas textures were made with 1-pixel padding - tex.transform = new TextureMatrix(tex, 0.0); - } - tex.transform.update(); - - this.uniforms.mask = tex; - this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite) - .prepend(tex.transform.mapCoord); - this.uniforms.alpha = maskSprite.worldAlpha; - this.uniforms.maskClamp = tex.transform.uClampFrame; - - filterManager.applyFilter(this, input, output); - } -} diff --git a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.frag b/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.frag deleted file mode 100644 index 0e4aef8..0000000 --- a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.frag +++ /dev/null @@ -1,23 +0,0 @@ -varying vec2 vMaskCoord; -varying vec2 vTextureCoord; - -uniform sampler2D uSampler; -uniform sampler2D mask; -uniform float alpha; -uniform vec4 maskClamp; - -void main(void) -{ - float clip = step(3.5, - step(maskClamp.x, vMaskCoord.x) + - step(maskClamp.y, vMaskCoord.y) + - step(vMaskCoord.x, maskClamp.z) + - step(vMaskCoord.y, maskClamp.w)); - - vec4 original = texture2D(uSampler, vTextureCoord); - vec4 masky = texture2D(mask, vMaskCoord); - - original *= (masky.r * masky.a * alpha * clip); - - gl_FragColor = original; -} diff --git a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.vert b/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.vert deleted file mode 100644 index d23a30c..0000000 --- a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.vert +++ /dev/null @@ -1,16 +0,0 @@ -attribute vec2 aVertexPosition; -attribute vec2 aTextureCoord; - -uniform mat3 projectionMatrix; -uniform mat3 otherMatrix; - -varying vec2 vMaskCoord; -varying vec2 vTextureCoord; - -void main(void) -{ - gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); - - vTextureCoord = aTextureCoord; - vMaskCoord = ( otherMatrix * vec3( aTextureCoord, 1.0) ).xy; -} diff --git a/packages/core/src/renderers/systems/BatchSystem.js b/packages/core/src/renderers/systems/BatchSystem.js deleted file mode 100644 index 70e9518..0000000 --- a/packages/core/src/renderers/systems/BatchSystem.js +++ /dev/null @@ -1,68 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import ObjectRenderer from '../utils/ObjectRenderer'; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ - -export default class BatchSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this manager works for. - */ - constructor(renderer) - { - super(renderer); - - /** - * An empty renderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.emptyRenderer = new ObjectRenderer(renderer); - - /** - * The currently active ObjectRenderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.currentRenderer = this.emptyRenderer; - } - - /** - * Changes the current renderer to the one given in parameter - * - * @param {PIXI.ObjectRenderer} objectRenderer - The object renderer to use. - */ - setObjectRenderer(objectRenderer) - { - if (this.currentRenderer === objectRenderer) - { - return; - } - - this.currentRenderer.stop(); - this.currentRenderer = objectRenderer; - - this.renderer.state.setState(objectRenderer.state); - - this.currentRenderer.start(); - } - - /** - * This should be called if you wish to do some custom rendering - * It will basically render anything that may be batched up such as sprites - * - */ - flush() - { - this.setObjectRenderer(this.emptyRenderer); - } - - reset() - { - this.setObjectRenderer(this.emptyRenderer); - } -} diff --git a/packages/core/src/renderers/systems/ContextSystem.js b/packages/core/src/renderers/systems/ContextSystem.js deleted file mode 100644 index 43bf827..0000000 --- a/packages/core/src/renderers/systems/ContextSystem.js +++ /dev/null @@ -1,186 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { settings } from '@pixi/settings'; - -let CONTEXT_UID = 0; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class ContextSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this.webGLVersion = 1; - - this.handleContextLost = this.handleContextLost.bind(this); - this.handleContextRestored = this.handleContextRestored.bind(this); - - this.extensions = {}; - - renderer.view.addEventListener('webglcontextlost', this.handleContextLost, false); - renderer.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); - } - - get isLost() - { - return (!this.gl || this.gl.isContextLost()); - } - - contextChange(gl) - { - this.gl = gl; - - // restore a context if it was previously lost - if (gl.isContextLost() && gl.getExtension('WEBGL_lose_context')) - { - gl.getExtension('WEBGL_lose_context').restoreContext(); - } - } - - initFromContext(gl) - { - this.gl = gl; - this.validateContext(gl); - this.renderer.gl = gl; - this.renderer.CONTEXT_UID = CONTEXT_UID++; - this.renderer.runners.contextChange.run(gl); - } - - initFromOptions(options) - { - const gl = this.createContext(this.renderer.view, options); - - this.initFromContext(gl); - } - - /** - * Helper class to create a webGL Context - * - * @class - * @memberof PIXI.glCore - * @param canvas {HTMLCanvasElement} the canvas element that we will get the context from - * @param options {Object} An options object that gets passed in to the canvas element containing the context attributes, - * see https://developer.mozilla.org/en/docs/Web/API/HTMLCanvasElement/getContext for the options available - * @return {WebGLRenderingContext} the WebGL context - */ - createContext(canvas, options) - { - let gl; - - if (settings.PREFER_WEBGL_2) - { - gl = canvas.getContext('webgl2', options); - } - - if (gl) - { - this.webGLVersion = 2; - } - else - { - this.webGLVersion = 1; - - gl = canvas.getContext('webgl', options) - || canvas.getContext('experimental-webgl', options); - - if (!gl) - { - // fail, not able to get a context - throw new Error('This browser does not support webGL. Try using the canvas renderer'); - } - } - - this.gl = gl; - - this.getExtensions(); - - return gl; - } - - getExtensions() - { - // time to set up default etensions that pixi uses.. - const gl = this.gl; - const extensions = this.extensions; - - if (this.webGLVersion === 1) - { - extensions.drawBuffers = gl.getExtension('WEBGL_draw_buffers'); - extensions.depthTexture = gl.getExtension('WEBKIT_WEBGL_depth_texture'); - extensions.floatTexture = gl.getExtension('OES_texture_float'); - extensions.loseContext = gl.getExtension('WEBGL_lose_context'); - - extensions.vertexArrayObject = gl.getExtension('OES_vertex_array_object') - || gl.getExtension('MOZ_OES_vertex_array_object') - || gl.getExtension('WEBKIT_OES_vertex_array_object'); - } - - // we don't use any specific WebGL 2 ones yet! - } - - /** - * Handles a lost webgl context - * - * @private - * @param {WebGLContextEvent} event - The context lost event. - */ - handleContextLost(event) - { - event.preventDefault(); - } - - /** - * Handles a restored webgl context - * - * @private - */ - handleContextRestored() - { - this.renderer.runners.contextChange.run(this.gl); - } - - destroy() - { - const view = this.renderer.view; - - // remove listeners - view.removeEventListener('webglcontextlost', this.handleContextLost); - view.removeEventListener('webglcontextrestored', this.handleContextRestored); - - this.gl.useProgram(null); - - if (this.extensions.loseContext) - { - this.extensions.loseContext.loseContext(); - } - } - - postrender() - { - this.gl.flush(); - } - - validateContext(gl) - { - const attributes = gl.getContextAttributes(); - - // this is going to be fairly simple for now.. but at least we have room to grow! - if (!attributes.stencil) - { - /* eslint-disable max-len */ - - /* eslint-disable no-console */ - console.warn('Provided WebGL context does not have a stencil buffer, masks may not render correctly'); - /* eslint-enable no-console */ - - /* eslint-enable max-len */ - } - } -} diff --git a/packages/core/src/renderers/systems/FramebufferSystem.js b/packages/core/src/renderers/systems/FramebufferSystem.js deleted file mode 100644 index df7902b..0000000 --- a/packages/core/src/renderers/systems/FramebufferSystem.js +++ /dev/null @@ -1,246 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { Rectangle } from '@pixi/math'; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class FramebufferSystem extends WebGLSystem -{ - /** - * Sets up the renderer context and necessary buffers. - * - * @private - */ - contextChange() - { - this.gl = this.renderer.gl; - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - this.current = null; - this.viewport = new Rectangle(); - - this.drawBufferExtension = this.renderer.context.extensions.drawBuffers; - } - - bind(framebuffer, frame) - { - const gl = this.gl; - - this.current = framebuffer; - - if (framebuffer) - { - // TODO cacheing layer! - - const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID] || this.initFramebuffer(framebuffer); - - gl.bindFramebuffer(gl.FRAMEBUFFER, fbo.framebuffer); - // makesure all textures are unbound.. - - // now check for updates... - if (fbo.dirtyId !== framebuffer.dirtyId) - { - fbo.dirtyId = framebuffer.dirtyId; - - if (fbo.dirtyFormat !== framebuffer.dirtyFormat) - { - fbo.dirtyFormat = framebuffer.dirtyFormat; - this.updateFramebuffer(framebuffer); - } - else if (fbo.dirtySize !== framebuffer.dirtySize) - { - fbo.dirtySize = framebuffer.dirtySize; - this.resizeFramebuffer(framebuffer); - } - } - - for (let i = 0; i < framebuffer.colorTextures.length; i++) - { - if (framebuffer.colorTextures[i].texturePart) - { - this.renderer.texture.unbind(framebuffer.colorTextures[i].texture); - } - else - { - this.renderer.texture.unbind(framebuffer.colorTextures[i]); - } - } - - if (framebuffer.depthTexture) - { - this.renderer.texture.unbind(framebuffer.depthTexture); - } - - if (frame) - { - this.setViewport(frame.x, frame.y, frame.width, frame.height); - } - else - { - this.setViewport(0, 0, framebuffer.width, framebuffer.height); - } - } - else - { - gl.bindFramebuffer(gl.FRAMEBUFFER, null); - - if (frame) - { - this.setViewport(frame.x, frame.y, frame.width, frame.height); - } - else - { - this.setViewport(0, 0, this.renderer.width, this.renderer.height); - } - } - } - - setViewport(x, y, width, height) - { - const v = this.viewport; - - if (v.width !== width || v.height !== height || v.x !== x || v.y !== y) - { - v.x = x; - v.y = y; - v.width = width; - v.height = height; - - this.gl.viewport(x, y, width, height); - } - } - - get size() - { - if (this.current) - { - // TODO store temp - return { x: 0, y: 0, width: this.current.width, height: this.current.height }; - } - - return { x: 0, y: 0, width: this.renderer.width, height: this.renderer.height }; - } - - clear(r, g, b, a) - { - const gl = this.gl; - - // TODO clear color can be set only one right? - gl.clearColor(r, g, b, a); - gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); - } - - // private functions... - - initFramebuffer(framebuffer) - { - const gl = this.gl; - - // TODO - make this a class? - const fbo = { - framebuffer: gl.createFramebuffer(), - stencil: null, - dirtyId: 0, - dirtyFormat: 0, - dirtySize: 0, - }; - - framebuffer.glFrameBuffers[this.CONTEXT_UID] = fbo; - - return fbo; - } - - resizeFramebuffer(framebuffer) - { - const gl = this.gl; - - if (framebuffer.stencil || framebuffer.depth) - { - gl.bindRenderbuffer(gl.RENDERBUFFER, this.stencil); - gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); - } - } - - updateFramebuffer(framebuffer) - { - const gl = this.gl; - - const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID]; - - // bind the color texture - const colorTextures = framebuffer.colorTextures; - - let count = colorTextures.length; - - if (!this.drawBufferExtension) - { - count = Math.min(count, 1); - } - - const activeTextures = []; - - for (let i = 0; i < count; i++) - { - const texture = framebuffer.colorTextures[i]; - - if (texture.texturePart) - { - this.renderer.texture.bind(texture.texture, 0); - - gl.framebufferTexture2D(gl.FRAMEBUFFER, - gl.COLOR_ATTACHMENT0 + i, - gl.TEXTURE_CUBE_MAP_NEGATIVE_X + texture.side, - texture.texture._glTextures[this.CONTEXT_UID].texture, - 0); - } - else - { - this.renderer.texture.bind(texture, 0); - - gl.framebufferTexture2D(gl.FRAMEBUFFER, - gl.COLOR_ATTACHMENT0 + i, - gl.TEXTURE_2D, - texture._glTextures[this.CONTEXT_UID].texture, - 0); - } - - activeTextures.push(gl.COLOR_ATTACHMENT0 + i); - } - - if (this.drawBufferExtension && activeTextures.length > 1) - { - this.drawBufferExtension.drawBuffersWEBGL(activeTextures); - } - - if (framebuffer.depthTexture) - { - const depthTextureExt = this.renderer.context.extensions.depthTexture; - - if (depthTextureExt) - { - const depthTexture = framebuffer.depthTexture; - - this.renderer.texture.bind(depthTexture, 0); - - gl.framebufferTexture2D(gl.FRAMEBUFFER, - gl.DEPTH_ATTACHMENT, - gl.TEXTURE_2D, - depthTexture._glTextures[this.CONTEXT_UID].texture, - 0); - } - } - - if (framebuffer.stencil || framebuffer.depth) - { - fbo.stencil = gl.createRenderbuffer(); - - gl.bindRenderbuffer(gl.RENDERBUFFER, fbo.stencil); - - // TODO.. this is depth AND stencil? - gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, fbo.stencil); - gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); - // fbo.enableStencil(); - } - } -} diff --git a/packages/core/src/renderers/systems/MaskSystem.js b/packages/core/src/renderers/systems/MaskSystem.js deleted file mode 100644 index d9c4b1a..0000000 --- a/packages/core/src/renderers/systems/MaskSystem.js +++ /dev/null @@ -1,199 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import AlphaMaskFilter from '../filters/spriteMask/SpriteMaskFilter'; - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class MaskSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - // TODO - we don't need both! - this.scissor = false; - this.scissorData = null; - this.scissorRenderTarget = null; - - this.enableScissor = false; - - this.alphaMaskPool = []; - this.alphaMaskIndex = 0; - } - - /** - * Applies the Mask and adds it to the current filter stack. - * - * @param {PIXI.DisplayObject} target - Display Object to push the mask to - * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. - */ - push(target, maskData) - { - // TODO the root check means scissor rect will not - // be used on render textures more info here: - // https://github.com/pixijs/pixi.js/pull/3545 - - if (maskData.texture) - { - this.pushSpriteMask(target, maskData); - } - else if (this.enableScissor - && !this.scissor - && this.renderer._activeRenderTarget.root - && !this.renderer.stencil.stencilMaskStack.length - && maskData.isFastRect()) - { - const matrix = maskData.worldTransform; - - let rot = Math.atan2(matrix.b, matrix.a); - - // use the nearest degree! - rot = Math.round(rot * (180 / Math.PI)); - - if (rot % 90) - { - this.pushStencilMask(maskData); - } - else - { - this.pushScissorMask(target, maskData); - } - } - else - { - this.pushStencilMask(maskData); - } - } - - /** - * Removes the last mask from the mask stack and doesn't return it. - * - * @param {PIXI.DisplayObject} target - Display Object to pop the mask from - * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. - */ - pop(target, maskData) - { - if (maskData.texture) - { - this.popSpriteMask(target, maskData); - } - else if (this.enableScissor && !this.renderer.stencil.stencilMaskStack.length) - { - this.popScissorMask(target, maskData); - } - else - { - this.popStencilMask(target, maskData); - } - } - - /** - * Applies the Mask and adds it to the current filter stack. - * - * @param {PIXI.RenderTarget} target - Display Object to push the sprite mask to - * @param {PIXI.Sprite} maskData - Sprite to be used as the mask - */ - pushSpriteMask(target, maskData) - { - let alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; - - if (!alphaMaskFilter) - { - alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; - } - - alphaMaskFilter[0].resolution = this.renderer.resolution; - alphaMaskFilter[0].maskSprite = maskData; - - // TODO - may cause issues! - target.filterArea = maskData.getBounds(true); - - this.renderer.filterSystem.pushFilter(target, alphaMaskFilter); - - this.alphaMaskIndex++; - } - - /** - * Removes the last filter from the filter stack and doesn't return it. - * - */ - popSpriteMask() - { - this.renderer.filterSystem.popFilter(); - this.alphaMaskIndex--; - } - - /** - * Applies the Mask and adds it to the current filter stack. - * - * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. - */ - pushStencilMask(maskData) - { - this.renderer.batch.flush(); - this.renderer.stencil.pushStencil(maskData); - } - - /** - * Removes the last filter from the filter stack and doesn't return it. - * - */ - popStencilMask() - { - // this.renderer.currentRenderer.stop(); - this.renderer.stencil.popStencil(); - } - - /** - * - * @param {PIXI.DisplayObject} target - Display Object to push the mask to - * @param {PIXI.Graphics} maskData - The masking data. - */ - pushScissorMask(target, maskData) - { - maskData.renderable = true; - - const renderTarget = this.renderer._activeRenderTarget; - - const bounds = maskData.getBounds(); - - bounds.fit(renderTarget.size); - maskData.renderable = false; - - this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); - - const resolution = this.renderer.resolution; - - this.renderer.gl.scissor( - bounds.x * resolution, - (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, - bounds.width * resolution, - bounds.height * resolution - ); - - this.scissorRenderTarget = renderTarget; - this.scissorData = maskData; - this.scissor = true; - } - - /** - * - * - */ - popScissorMask() - { - this.scissorRenderTarget = null; - this.scissorData = null; - this.scissor = false; - - // must be scissor! - const gl = this.renderer.gl; - - gl.disable(gl.SCISSOR_TEST); - } -} diff --git a/packages/core/src/renderers/systems/ProjectionSystem.js b/packages/core/src/renderers/systems/ProjectionSystem.js deleted file mode 100644 index ec5efdb..0000000 --- a/packages/core/src/renderers/systems/ProjectionSystem.js +++ /dev/null @@ -1,73 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { Matrix } from '@pixi/math'; - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ - -export default class ProjectionSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this.projectionMatrix = new Matrix(); - } - - update(destinationFrame, sourceFrame, resolution, root) - { - this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; - this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; - - this.calculateProjection(this.destinationFrame, this.sourceFrame, resolution, root); - - this.renderer.globalUniforms.uniforms.projectionMatrix = this.projectionMatrix; - this.renderer.globalUniforms.update(); - } - - /** - * Updates the projection matrix based on a projection frame (which is a rectangle) - * - * @param {Rectangle} destinationFrame - The destination frame. - * @param {Rectangle} sourceFrame - The source frame. - */ - calculateProjection(destinationFrame, sourceFrame, resolution, root) - { - const pm = this.projectionMatrix; - - // I don't think we will need this line.. - // pm.identity(); - - if (!root) - { - pm.a = (1 / destinationFrame.width * 2) * resolution; - pm.d = (1 / destinationFrame.height * 2) * resolution; - - pm.tx = -1 - (sourceFrame.x * pm.a); - pm.ty = -1 - (sourceFrame.y * pm.d); - } - else - { - pm.a = (1 / destinationFrame.width * 2) * resolution; - pm.d = (-1 / destinationFrame.height * 2) * resolution; - - pm.tx = -1 - (sourceFrame.x * pm.a); - pm.ty = 1 - (sourceFrame.y * pm.d); - } - } - - /** - * Sets the transform of the active render target to the given matrix - * - * @param {PIXI.Matrix} matrix - The transformation matrix - */ - setTransform()// matrix) - { - // this._activeRenderTarget.transform = matrix; - } -} diff --git a/packages/core/src/renderers/systems/RenderTextureSystem.js b/packages/core/src/renderers/systems/RenderTextureSystem.js deleted file mode 100644 index a133ef1..0000000 --- a/packages/core/src/renderers/systems/RenderTextureSystem.js +++ /dev/null @@ -1,115 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { Rectangle } from '@pixi/math'; - -const tempRect = new Rectangle(); - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ - -export default class RenderTextureSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this.clearColor = renderer._backgroundColorRgba; - - // TODO moe this property somewhere else! - this.defaultMaskStack = []; - this.defaultFilterStack = [{}]; - - // empty render texture? - this.renderTexture = null; - - this.destinationFrame = new Rectangle(); - } - - bind(renderTexture, sourceFrame, destinationFrame) - { - // TODO - do we want this?? - if (this.renderTexture === renderTexture) return; - this.renderTexture = renderTexture; - - const renderer = this.renderer; - - if (renderTexture) - { - const baseTexture = renderTexture.baseTexture; - - if (!destinationFrame) - { - tempRect.width = baseTexture.realWidth; - tempRect.height = baseTexture.realHeight; - - destinationFrame = tempRect; - } - - if (!sourceFrame) - { - sourceFrame = destinationFrame; - } - - this.renderer.framebuffer.bind(baseTexture.frameBuffer, destinationFrame); - - this.renderer.projection.update(destinationFrame, sourceFrame, baseTexture.resolution, false); - this.renderer.stencil.setMaskStack(baseTexture.stencilMaskStack); - } - else - { - // TODO these validation checks happen deeper down.. - // thing they can be avoided.. - if (!destinationFrame) - { - tempRect.width = renderer.width; - tempRect.height = renderer.height; - - destinationFrame = tempRect; - } - - if (!sourceFrame) - { - sourceFrame = destinationFrame; - } - - renderer.framebuffer.bind(null, destinationFrame); - - // TODO store this.. - this.renderer.projection.update(destinationFrame, sourceFrame, this.renderer.resolution, true); - this.renderer.stencil.setMaskStack(this.defaultMaskStack); - } - - this.destinationFrame.copyFrom(destinationFrame); - } - - /** - * Erases the render texture and fills the drawing area with a colour - * - * @param {number} [clearColor] - The colour - * @return {PIXI.Renderer} Returns itself. - */ - clear(clearColor) - { - if (this.renderTexture) - { - clearColor = clearColor || this.renderTexture.baseTexture.clearColor; - } - else - { - clearColor = clearColor || this.clearColor; - } - - this.renderer.framebuffer.clear(clearColor[0], clearColor[1], clearColor[2], clearColor[3]); - } - - resize()// screenWidth, screenHeight) - { - // resize the root only! - this.bind(null); - } -} diff --git a/packages/core/src/renderers/systems/StateSystem.js b/packages/core/src/renderers/systems/StateSystem.js deleted file mode 100755 index d7f4caf..0000000 --- a/packages/core/src/renderers/systems/StateSystem.js +++ /dev/null @@ -1,288 +0,0 @@ -import mapWebGLBlendModesToPixi from '../utils/mapWebGLBlendModesToPixi'; -import WebGLSystem from './WebGLSystem'; -import WebGLState from '../State'; - -const BLEND = 0; -const OFFSET = 1; -const CULLING = 2; -const DEPTH_TEST = 3; -const WINDING = 4; - -/** - * A WebGL state machines - * - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class StateSystem extends WebGLSystem -{ - /** - * @param {WebGLRenderingContext} gl - The current WebGL rendering context - */ - constructor(renderer) - { - super(renderer); - - this.gl = null; - - this.maxAttribs = null; - - // check we have vao.. - this.nativeVaoExtension = null; - - this.attribState = null; - - this.stateId = 0; - this.polygonOffset = 0; - this.blendMode = 17; - - this.map = []; - - // map functions for when we set state.. - this.map[BLEND] = this.setBlend; - this.map[OFFSET] = this.setOffset; - this.map[CULLING] = this.setCullFace; - this.map[DEPTH_TEST] = this.setDepthTest; - this.map[WINDING] = this.setFrontFace; - - this.checks = []; - - this.defaultState = new WebGLState(); - this.defaultState.blend = true; - this.defaultState.depth = true; - } - - contextChange(gl) - { - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; - - this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); - - // check we have vao.. - this.nativeVaoExtension = ( - gl.getExtension('OES_vertex_array_object') - || gl.getExtension('MOZ_OES_vertex_array_object') - || gl.getExtension('WEBKIT_OES_vertex_array_object') - ); - - this.attribState = { - tempAttribState: new Array(this.maxAttribs), - attribState: new Array(this.maxAttribs), - }; - - this.blendModes = mapWebGLBlendModesToPixi(gl); - - this.setState(this.defaultState); - - this.reset(); - } - - /** - * Sets the current state - * - * @param {*} state - The state to set. - */ - setState(state) - { - state = state || this.defaultState; - - // TODO maybe to an object check? ( this.state === state )? - if (this.stateId !== state.data) - { - let diff = this.stateId ^ state.data; - let i = 0; - - // order from least to most common - while (diff) - { - if (diff & 1) - { - // state change! - this.map[i].call(this, !!(state.data & (1 << i))); - } - - diff = diff >> 1; - i++; - } - - this.stateId = state.data; - } - - // based on the above settings we check for specific modes.. - // for example if blend is active we check and set the blend modes - // or of polygon offset is active we check the poly depth. - for (let i = 0; i < this.checks.length; i++) - { - this.checks[i](this, state); - } - } - - /** - * Enables or disabled blending. - * - * @param {boolean} value - Turn on or off webgl blending. - */ - setBlend(value) - { - this.updateCheck(StateSystem.checkBlendMode, value); - - this.gl[value ? 'enable' : 'disable'](this.gl.BLEND); - } - - /** - * Enables or disable polygon offset fill - * - * @param {boolean} value - Turn on or off webgl polygon offset testing. - */ - setOffset(value) - { - this.gl[value ? 'enable' : 'disable'](this.gl.POLYGON_OFFSET_FILL); - } - - /** - * Sets whether to enable or disable depth test. - * - * @param {boolean} value - Turn on or off webgl depth testing. - */ - setDepthTest(value) - { - this.gl[value ? 'enable' : 'disable'](this.gl.DEPTH_TEST); - } - - /** - * Sets whether to enable or disable cull face. - * - * @param {boolean} value - Turn on or off webgl cull face. - */ - setCullFace(value) - { - this.gl[value ? 'enable' : 'disable'](this.gl.CULL_FACE); - } - - /** - * Sets the gl front face. - * - * @param {boolean} value - true is clockwise and false is counter-clockwise - */ - setFrontFace(value) - { - this.gl.frontFace(this.gl[value ? 'CW' : 'CCW']); - } - - /** - * Sets the blend mode. - * - * @param {number} value - The blend mode to set to. - */ - setBlendMode(value) - { - if (value === this.blendMode) - { - return; - } - - this.blendMode = value; - this.gl.blendFunc(this.blendModes[value][0], this.blendModes[value][1]); - } - - /** - * Sets the polygon offset. - * - * @param {number} value - the polygon offset - * @param {number} scale - the polygon offset scale - */ - setPolygonOffset(value, scale) - { - this.gl.polygonOffset(value, scale); - } - - /** - * Disables all the vaos in use - * - */ - resetAttributes() - { - for (let i = 0; i < this.attribState.tempAttribState.length; i++) - { - this.attribState.tempAttribState[i] = 0; - } - - for (let i = 0; i < this.attribState.attribState.length; i++) - { - this.attribState.attribState[i] = 0; - } - - // im going to assume one is always active for performance reasons. - for (let i = 1; i < this.maxAttribs; i++) - { - this.gl.disableVertexAttribArray(i); - } - } - - // used - /** - * Resets all the logic and disables the vaos - */ - reset() - { - // unbind any VAO if they exist.. - if (this.nativeVaoExtension) - { - this.nativeVaoExtension.bindVertexArrayOES(null); - } - - // reset all attributes.. - this.resetAttributes(); - - this.gl.pixelStorei(this.gl.UNPACK_FLIP_Y_WEBGL, false); - - this.setBlendMode(0); - - // TODO? - // this.setState(this.defaultState); - } - - /** - * checks to see which updates should be checked based on which settings have been activated - * for example if blend is enabled then we shold check the blend modes each time the state is changed - * or if poygon fill is activated then we need to check if the polygone offset changes. - * The idea is that we only check what we have too. - * - * @param {Function} func the checking function to add or remove - * @param {boolean} value should the check function be added or removed. - */ - updateCheck(func, value) - { - const index = this.checks.indexOf(func); - - if (value && index === -1) - { - this.checks.push(func); - } - else if (!value && index !== -1) - { - this.checks.splice(index, 1); - } - } - - /** - * A private little wrapper function that we call to check the blend mode. - * - * @static - * @private - * @param {PIXI.StateSystem} System the System to perform the state check on - * @param {PIXI.State} state the state that the blendMode will pulled from - */ - static checkBlendMode(system, state) - { - system.setBlendMode(state.blendMode); - } - - // TODO - add polygon offset? -} diff --git a/packages/core/src/renderers/systems/StencilSystem.js b/packages/core/src/renderers/systems/StencilSystem.js deleted file mode 100644 index 5d538cb..0000000 --- a/packages/core/src/renderers/systems/StencilSystem.js +++ /dev/null @@ -1,133 +0,0 @@ -import WebGLSystem from './WebGLSystem'; - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class StencilSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - this.stencilMaskStack = []; - } - - /** - * Changes the mask stack that is used by this System. - * - * @param {PIXI.Graphics[]} stencilMaskStack - The mask stack - */ - setMaskStack(stencilMaskStack) - { - const gl = this.renderer.gl; - - if (stencilMaskStack.length !== this.stencilMaskStack.length) - { - if (stencilMaskStack.length === 0) - { - gl.disable(gl.STENCIL_TEST); - } - else - { - gl.enable(gl.STENCIL_TEST); - } - } - - this.stencilMaskStack = stencilMaskStack; - } - - /** - * Applies the Mask and adds it to the current stencil stack. @alvin - * - * @param {PIXI.Graphics} graphics - The mask - */ - pushStencil(graphics) - { - this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); - - // this.renderer._activeRenderTarget.attachStencilBuffer(); - - const gl = this.renderer.gl; - const prevMaskCount = this.stencilMaskStack.length; - - if (prevMaskCount === 0) - { - gl.enable(gl.STENCIL_TEST); - } - - this.stencilMaskStack.push(graphics); - - // Increment the refference stencil value where the new mask overlaps with the old ones. - gl.colorMask(false, false, false, false); - gl.stencilFunc(gl.EQUAL, prevMaskCount, this._getBitwiseMask()); - gl.stencilOp(gl.KEEP, gl.KEEP, gl.INCR); - this.renderer.plugins.graphics.render(graphics); - - this._useCurrent(); - } - - /** - * Removes the last mask from the stencil stack. @alvin - */ - popStencil() - { - this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); - - const gl = this.renderer.gl; - const graphics = this.stencilMaskStack.pop(); - - if (this.stencilMaskStack.length === 0) - { - // the stack is empty! - gl.disable(gl.STENCIL_TEST); - gl.clear(gl.STENCIL_BUFFER_BIT); - gl.clearStencil(0); - } - else - { - // Decrement the refference stencil value where the popped mask overlaps with the other ones - gl.colorMask(false, false, false, false); - gl.stencilOp(gl.KEEP, gl.KEEP, gl.DECR); - this.renderer.plugins.graphics.render(graphics); - - this._useCurrent(); - } - } - - /** - * Setup renderer to use the current stencil data. - */ - _useCurrent() - { - const gl = this.renderer.gl; - - gl.colorMask(true, true, true, true); - gl.stencilFunc(gl.EQUAL, this.stencilMaskStack.length, this._getBitwiseMask()); - gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP); - } - - /** - * Fill 1s equal to the number of acitve stencil masks. - * - * @return {number} The bitwise mask. - */ - _getBitwiseMask() - { - return (1 << this.stencilMaskStack.length) - 1; - } - - /** - * Destroys the mask stack. - * - */ - destroy() - { - super.destroy(this); - - this.stencilMaskStack = null; - } -} diff --git a/packages/core/src/renderers/systems/WebGLSystem.js b/packages/core/src/renderers/systems/WebGLSystem.js deleted file mode 100644 index d3d5b31..0000000 --- a/packages/core/src/renderers/systems/WebGLSystem.js +++ /dev/null @@ -1,41 +0,0 @@ -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this manager works for. - */ - constructor(renderer) - { - /** - * The renderer this manager works for. - * - * @member {PIXI.Renderer} - */ - this.renderer = renderer; - - this.renderer.runners.contextChange.add(this); - } - - /** - * Generic method called when there is a WebGL context change. - * - */ - contextChange() - { - // do some codes init! - } - - /** - * Generic destroy methods to be overridden by the subclass - * - */ - destroy() - { - this.renderer.runners.contextChange.remove(this); - this.renderer = null; - } -} diff --git a/packages/core/src/renderers/systems/filter/FilterSystem.js b/packages/core/src/renderers/systems/filter/FilterSystem.js deleted file mode 100644 index e615037..0000000 --- a/packages/core/src/renderers/systems/filter/FilterSystem.js +++ /dev/null @@ -1,431 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; - -import RenderTexture from '../../../textures/RenderTexture'; -import Quad from '../../utils/Quad'; -import QuadUv from '../../utils/QuadUv'; -import { Rectangle } from '@pixi/math'; -import * as filterTransforms from '../../filters/filterTransforms'; -import bitTwiddle from 'bit-twiddle'; -import UniformGroup from '../../../shader/UniformGroup'; -import { DRAW_MODES } from '../../../../../constants'; - -// -/** - * @ignore - * @class - */ -class FilterState -{ - /** - * - */ - constructor() - { - this.renderTexture = null; - this.sourceFrame = new Rectangle(); - this.destinationFrame = new Rectangle(); - this.filters = []; - this.target = null; - this.legacy = false; - this.resolution = 1; - } -} - -/** - * @class - * @memberof PIXI.systems - * @extends PIXI.systems.WebGLSystem - */ -export default class FilterSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - /** - * stores a bunch of PO2 textures used for filtering - * @type {Object} - */ - this.texturePool = {}; - - /** - * a pool for storing filter states, save us creating new ones each tick - * @type {Array} - */ - this.statePool = []; - - /** - * A very simple geometry used when drawing a filter effect to the screen - * @type {Quad} - */ - this.quad = new Quad(); - - this.quadUv = new QuadUv(); - - /** - * Temporary rect for maths - * @type {PIXI.Rectangle} - */ - this.tempRect = new Rectangle(); - - this.activeState = {}; - - /** - * this uniform group is attached to filter uniforms when used - * @type {UniformGroup} - */ - this.globalUniforms = new UniformGroup({ - sourceFrame: this.tempRect, - destinationFrame: this.tempRect, - - // legacy variables - filterArea: new Float32Array(4), - filterClamp: new Float32Array(4), - }, true); - } - - /** - * Adds a new filter to the System. - * - * @param {PIXI.DisplayObject} target - The target of the filter to render. - * @param {PIXI.Filter[]} filters - The filters to apply. - */ - push(target, filters) - { - const renderer = this.renderer; - const filterStack = this.renderer.renderTexture.defaultFilterStack; - const state = this.statePool.pop() || new FilterState(); - - let resolution = filters[0].resolution; - let padding = filters[0].padding; - let autoFit = filters[0].autoFit; - let legacy = filters[0].legacy; - - for (let i = 1; i < filters.length; i++) - { - const filter = filters[i]; - - // lets use the lowest resolution.. - resolution = Math.min(resolution, filter.resolution); - // and the largest amount of padding! - padding = Math.max(padding, filter.padding); - // only auto fit if all filters are autofit - autoFit = autoFit || filter.autoFit; - - legacy = legacy || filter.legacy; - } - - filterStack.push(state); - - state.resolution = resolution; - - state.legacy = legacy; - - // round to whole number based on resolution - // TODO move that to the shader too? - state.sourceFrame = target.filterArea || target.getBounds(true); - - state.sourceFrame.pad(padding); - - if (autoFit) - { - state.sourceFrame.fit(this.renderer.renderTexture.destinationFrame); - } - - state.sourceFrame.round(resolution); - - state.renderTexture = this.getPotFilterTexture(state.sourceFrame.width, state.sourceFrame.height, resolution); - state.filters = filters; - - state.destinationFrame.width = state.renderTexture.width; - state.destinationFrame.height = state.renderTexture.height; - - state.renderTexture.filterFrame = state.sourceFrame; - renderer.renderTexture.bind(state.renderTexture, state.sourceFrame);// /, state.destinationFrame); - renderer.renderTexture.clear(); - } - - /** - * Pops off the filter and applies it. - * - */ - pop() - { - const renderer = this.renderer; - const filterStack = renderer.renderTexture.defaultFilterStack; - const state = filterStack.pop(); - const filters = state.filters; - - this.activeState = state; - - const globalUniforms = this.globalUniforms.uniforms; - - globalUniforms.sourceFrame = state.sourceFrame; - globalUniforms.destinationFrame = state.destinationFrame; - globalUniforms.resolution = state.resolution; - - // only update the rect if its legacy.. - if (state.legacy) - { - const filterArea = globalUniforms.filterArea; - const filterClamp = globalUniforms.filterClamp; - - filterArea[0] = state.destinationFrame.width; - filterArea[1] = state.destinationFrame.height; - filterArea[2] = state.sourceFrame.x; - filterArea[3] = state.sourceFrame.y; - - filterClamp[0] = 0.5 / state.resolution / state.destinationFrame.width; - filterClamp[1] = 0.5 / state.resolution / state.destinationFrame.height; - filterClamp[2] = (state.sourceFrame.width - 0.5) / state.resolution / state.destinationFrame.width; - filterClamp[3] = (state.sourceFrame.height - 0.5) / state.resolution / state.destinationFrame.height; - } - - this.globalUniforms.update(); - - const lastState = filterStack[filterStack.length - 1]; - - if (filters.length === 1) - { - filters[0].apply(this, state.renderTexture, lastState.renderTexture, false, state); - - this.returnFilterTexture(state.renderTexture); - } - else - { - let flip = state.renderTexture; - let flop = this.getPotFilterTexture( - flip.width, - flip.height, - state.resolution - ); - - flop.filterFrame = flip.filterFrame; - - let i = 0; - - for (i = 0; i < filters.length - 1; ++i) - { - filters[i].apply(this, flip, flop, true, state); - - const t = flip; - - flip = flop; - flop = t; - } - - filters[i].apply(this, flip, lastState.renderTexture, false, state); - - this.returnFilterTexture(flip); - this.returnFilterTexture(flop); - } - - this.statePool.push(state); - } - - /** - * Draws a filter. - * - * @param {PIXI.Filter} filter - The filter to draw. - * @param {PIXI.RenderTarget} input - The input render target. - * @param {PIXI.RenderTarget} output - The target to output to. - * @param {boolean} clear - Should the output be cleared before rendering to it - */ - applyFilter(filter, input, output, clear) - { - const renderer = this.renderer; - - renderer.renderTexture.bind(output, output ? output.filterFrame : null); - - if (clear) - { - // gl.disable(gl.SCISSOR_TEST); - renderer.renderTexture.clear(); - // gl.enable(gl.SCISSOR_TEST); - } - - // set the uniforms.. - filter.uniforms.uSampler = input; - filter.uniforms.filterGlobals = this.globalUniforms; - - // TODO make it so that the order of this does not matter.. - // because it does at the moment cos of global uniforms. - // they need to get resynced - - renderer.state.setState(filter.state); - renderer.shader.bind(filter); - - if (filter.legacy) - { - this.quadUv.map(input._frame, input.filterFrame); - - renderer.geometry.bind(this.quadUv); - renderer.geometry.draw(DRAW_MODES.TRIANGLES); - } - else - { - renderer.geometry.bind(this.quad); - renderer.geometry.draw(DRAW_MODES.TRIANGLE_STRIP); - } - } - - /** - * Calculates the mapped matrix. - * - * TODO playing around here.. this is temporary - (will end up in the shader) - * this returns a matrix that will normalise map filter cords in the filter to screen space - * - * @param {PIXI.Matrix} outputMatrix - the matrix to output to. - * @return {PIXI.Matrix} The mapped matrix. - */ - calculateScreenSpaceMatrix(outputMatrix) - { - const currentState = this.activeState; - - return filterTransforms.calculateScreenSpaceMatrix( - outputMatrix, - currentState.sourceFrame, - currentState.destinationFrame - ); - } - - /** - * This will map the filter coord so that a texture can be used based on the transform of a sprite - * - * @param {PIXI.Matrix} outputMatrix - The matrix to output to. - * @param {PIXI.Sprite} sprite - The sprite to map to. - * @return {PIXI.Matrix} The mapped matrix. - */ - calculateSpriteMatrix(outputMatrix, sprite) - { - const currentState = this.activeState; - - return filterTransforms.calculateSpriteMatrix( - outputMatrix, - currentState.sourceFrame, - currentState.destinationFrame, - sprite - ); - } - - /** - * Destroys this Filter System. - * - * @param {boolean} [contextLost=false] context was lost, do not free shaders - * - */ - destroy(contextLost = false) - { - if (!contextLost) - { - this.emptyPool(); - } - else - { - this.texturePool = {}; - } - } - - /** - * Gets a Power-of-Two render texture. - * - * TODO move to a seperate class could be on renderer? - * also - could cause issue with multiple contexts? - * - * @private - * @param {WebGLRenderingContext} gl - The webgl rendering context - * @param {number} minWidth - The minimum width of the render target. - * @param {number} minHeight - The minimum height of the render target. - * @param {number} resolution - The resolution of the render target. - * @return {PIXI.RenderTarget} The new render target. - */ - getPotFilterTexture(minWidth, minHeight, resolution) - { - minWidth = bitTwiddle.nextPow2(minWidth); - minHeight = bitTwiddle.nextPow2(minHeight); - resolution = resolution || 1; - - const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); - - if (!this.texturePool[key]) - { - this.texturePool[key] = []; - } - - let renderTexture = this.texturePool[key].pop(); - - if (!renderTexture) - { - // temporary bypass cache.. - // internally - this will cause a texture to be bound.. - renderTexture = RenderTexture.create({ - width: minWidth, - height: minHeight, - resolution, - }); - } - - return renderTexture; - } - - /** - * Gets extra render texture to use inside current filter - * - * @param {number} resolution resolution of the renderTexture - * @returns {PIXI.RenderTexture} - */ - getFilterTexture(resolution) - { - const rt = this.activeState.renderTexture; - - const filterTexture = this.getPotFilterTexture(rt.width, rt.height, resolution || rt.baseTexture.resolution); - - filterTexture.filterFrame = rt.filterFrame; - - return filterTexture; - } - - /** - * Frees a render texture back into the pool. - * - * @param {PIXI.RenderTarget} renderTexture - The renderTarget to free - */ - returnFilterTexture(renderTexture) - { - renderTexture.filterFrame = null; - - const base = renderTexture.baseTexture; - - const minWidth = base.width; - const minHeight = base.height; - - const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); - - this.texturePool[key].push(renderTexture); - } - - /** - * Empties the texture pool. - * - */ - emptyPool() - { - for (const i in this.texturePool) - { - const textures = this.texturePool[i]; - - if (textures) - { - for (let j = 0; j < textures.length; j++) - { - textures[j].destroy(true); - } - } - } - - this.texturePool = {}; - } -} diff --git a/packages/core/src/renderers/systems/geometry/GLBuffer.js b/packages/core/src/renderers/systems/geometry/GLBuffer.js deleted file mode 100644 index 0477e8a..0000000 --- a/packages/core/src/renderers/systems/geometry/GLBuffer.js +++ /dev/null @@ -1,9 +0,0 @@ -export default class GLBuffer -{ - constructor(buffer) - { - this.buffer = buffer; - this.updateID = -1; - this.byteLength = -1; - } -} diff --git a/packages/core/src/renderers/systems/geometry/GeometrySystem.js b/packages/core/src/renderers/systems/geometry/GeometrySystem.js deleted file mode 100644 index 26c8e9e..0000000 --- a/packages/core/src/renderers/systems/geometry/GeometrySystem.js +++ /dev/null @@ -1,388 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; -import GLBuffer from './GLBuffer'; - -const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class GeometrySystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this._activeGeometry = null; - this._activeVao = null; - - this.hasVao = true; - this.hasInstance = true; - } - - /** - * Sets up the renderer context and necessary buffers. - * - * @private - */ - contextChange() - { - const gl = this.gl = this.renderer.gl; - - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - - // webgl2 - if (!gl.createVertexArray) - { - // webgl 1! - let nativeVaoExtension = this.renderer.context.extensions.vertexArrayObject; - - if (this.renderer.legacy) - { - nativeVaoExtension = null; - } - - if (nativeVaoExtension) - { - gl.createVertexArray = () => - nativeVaoExtension.createVertexArrayOES(); - - gl.bindVertexArray = (vao) => - nativeVaoExtension.bindVertexArrayOES(vao); - - gl.deleteVertexArray = (vao) => - nativeVaoExtension.deleteVertexArrayOES(vao); - } - else - { - this.hasVao = false; - gl.createVertexArray = () => - { - // empty - }; - - gl.bindVertexArray = () => - { - // empty - }; - - gl.deleteVertexArray = () => - { - // empty - }; - } - } - - if (!gl.vertexAttribDivisor) - { - const instanceExt = gl.getExtension('ANGLE_instanced_arrays'); - - if (instanceExt) - { - gl.vertexAttribDivisor = (a, b) => - instanceExt.vertexAttribDivisorANGLE(a, b); - - gl.drawElementsInstanced = (a, b, c, d, e) => - instanceExt.drawElementsInstancedANGLE(a, b, c, d, e); - - gl.drawArraysInstanced = (a, b, c, d) => - instanceExt.drawArraysInstancedANGLE(a, b, c, d); - } - else - { - this.hasInstance = false; - } - } - } - - /** - * Binds geometry so that is can be drawn. Creating a Vao if required - * @private - * @param {PIXI.Geometry} geometry instance of geometry to bind - */ - bind(geometry, shader) - { - shader = shader || this.renderer.shader.shader; - - const gl = this.gl; - - // not sure the best way to address this.. - // currently different shaders require different VAOs for the same geometry - // Still mulling over the best way to solve this one.. - // will likely need to modify the shader attribute locations at run time! - let vaos = geometry.glVertexArrayObjects[this.CONTEXT_UID]; - - if (!vaos) - { - geometry.glVertexArrayObjects[this.CONTEXT_UID] = vaos = {}; - } - - const vao = vaos[shader.program.id] || this.initGeometryVao(geometry, shader.program); - - this._activeGeometry = geometry; - - if (this._activeVao !== vao) - { - this._activeVao = vao; - - if (this.hasVao) - { - gl.bindVertexArray(vao); - } - else - { - this.activateVao(geometry, shader.program); - } - } - - // TODO - optimise later! - // don't need to loop through if nothing changed! - // maybe look to add an 'autoupdate' to geometry? - this.updateBuffers(); - } - - reset() - { - this.unbind(); - } - - updateBuffers() - { - const geometry = this._activeGeometry; - const gl = this.gl; - - for (let i = 0; i < geometry.buffers.length; i++) - { - const buffer = geometry.buffers[i]; - - const glBuffer = buffer._glBuffers[this.CONTEXT_UID]; - - if (buffer._updateID !== glBuffer.updateID) - { - glBuffer.updateID = buffer._updateID; - - // TODO can cache this on buffer! maybe added a getter / setter? - const type = buffer.index ? gl.ELEMENT_ARRAY_BUFFER : gl.ARRAY_BUFFER; - const drawType = buffer.static ? gl.STATIC_DRAW : gl.DYNAMIC_DRAW; - - gl.bindBuffer(type, glBuffer.buffer); - - if (glBuffer.byteLength >= buffer.data.byteLength) - { - // offset is always zero for now! - gl.bufferSubData(type, 0, buffer.data); - } - else - { - gl.bufferData(type, buffer.data, drawType); - } - } - } - } - - checkCompatability(geometry, program) - { - // geometry must have at least all the attributes that the shader requires. - const geometryAttributes = geometry.attributes; - const shaderAttributes = program.attributeData; - - for (const j in shaderAttributes) - { - if (!geometryAttributes[j]) - { - throw new Error(`shader and geometry incompatible, geometry missing the "${j}" attribute`); - } - } - } - - /** - * Creates a Vao with the same structure as the geometry and stores it on the geometry. - * @private - * @param {PIXI.Geometry} geometry instance of geometry to to generate Vao for - */ - initGeometryVao(geometry, program) - { - this.checkCompatability(geometry, program); - - const gl = this.gl; - const CONTEXT_UID = this.CONTEXT_UID; - const buffers = geometry.buffers; - const attributes = geometry.attributes; - - const tempStride = {}; - const tempStart = {}; - - for (const j in buffers) - { - tempStride[j] = 0; - tempStart[j] = 0; - } - - for (const j in attributes) - { - if (!attributes[j].size && program.attributeData[j]) - { - attributes[j].size = program.attributeData[j].size; - } - - tempStride[attributes[j].buffer] += attributes[j].size * byteSizeMap[attributes[j].type]; - } - - for (const j in attributes) - { - const attribute = attributes[j]; - const attribSize = attribute.size; - - if (attribute.stride === undefined) - { - if (tempStride[attribute.buffer] === attribSize * byteSizeMap[attribute.type]) - { - attribute.stride = 0; - } - else - { - attribute.stride = tempStride[attribute.buffer]; - } - } - - if (attribute.start === undefined) - { - attribute.start = tempStart[attribute.buffer]; - - tempStart[attribute.buffer] += attribSize * byteSizeMap[attribute.type]; - } - } - - // first update - and create the buffers! - // only create a gl buffer if it actually gets - for (let i = 0; i < buffers.length; i++) - { - const buffer = buffers[i]; - - if (!buffer._glBuffers[CONTEXT_UID]) - { - buffer._glBuffers[CONTEXT_UID] = new GLBuffer(gl.createBuffer()); - } - } - - // TODO - maybe make this a data object? - // lets wait to see if we need to first! - const vao = gl.createVertexArray(); - - gl.bindVertexArray(vao); - - this.activateVao(geometry, program); - - gl.bindVertexArray(null); - - // add it to the cache! - geometry.glVertexArrayObjects[this.CONTEXT_UID][program.id] = vao; - - return vao; - } - - activateVao(geometry, program) - { - const gl = this.gl; - const CONTEXT_UID = this.CONTEXT_UID; - const buffers = geometry.buffers; - const attributes = geometry.attributes; - - if (geometry.indexBuffer) - { - // first update the index buffer if we have one.. - gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, geometry.indexBuffer._glBuffers[CONTEXT_UID].buffer); - } - - let lastBuffer = null; - - // add a new one! - for (const j in attributes) - { - const attribute = attributes[j]; - const buffer = buffers[attribute.buffer]; - const glBuffer = buffer._glBuffers[CONTEXT_UID]; - - if (program.attributeData[j]) - { - if (lastBuffer !== glBuffer) - { - gl.bindBuffer(gl.ARRAY_BUFFER, glBuffer.buffer); - - lastBuffer = glBuffer; - } - - const location = program.attributeData[j].location; - - // TODO introduce state again - // we can optimise this for older devices that have no VAOs - gl.enableVertexAttribArray(location); - - gl.vertexAttribPointer(location, - attribute.size, - attribute.type || gl.FLOAT, - attribute.normalized, - attribute.stride, - attribute.start); - - if (attribute.instance) - { - // TODO calculate instance count based of this... - if (this.hasInstance) - { - gl.vertexAttribDivisor(location, 1); - } - else - { - throw new Error('geometry error, GPU Instancing is not supported on this device'); - } - } - } - } - } - - draw(type, size, start, instanceCount) - { - const gl = this.gl; - const geometry = this._activeGeometry; - - // TODO.. this should not change so maybe cache the function? - - if (geometry.indexBuffer) - { - if (geometry.instanced) - { - /* eslint-disable max-len */ - gl.drawElementsInstanced(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2, instanceCount || 1); - /* eslint-enable max-len */ - } - else - { - gl.drawElements(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2); - } - } - else - if (geometry.instanced) - { - // TODO need a better way to calculate size.. - gl.drawArraysInstanced(type, start, size || geometry.getSize(), instanceCount || 1); - } - else - { - gl.drawArrays(type, start, size || geometry.getSize()); - } - - return this; - } - - unbind() - { - this.gl.bindVertexArray(null); - this._activeVao = null; - this._activeGeometry = null; - } -} diff --git a/packages/core/src/renderers/systems/geometry/setVertexAttribArrays.js b/packages/core/src/renderers/systems/geometry/setVertexAttribArrays.js deleted file mode 100644 index 23b6959..0000000 --- a/packages/core/src/renderers/systems/geometry/setVertexAttribArrays.js +++ /dev/null @@ -1,54 +0,0 @@ -// var GL_MAP = {}; - -/** - * @param gl {WebGLRenderingContext} The current WebGL context - * @param attribs {*} - * @param state {*} - */ -export default function setVertexAttribArrays(gl, attribs, state) -{ - let i; - - if (state) - { - const tempAttribState = state.tempAttribState; - const attribState = state.attribState; - - for (i = 0; i < tempAttribState.length; i++) - { - tempAttribState[i] = false; - } - - // set the new attribs - for (i = 0; i < attribs.length; i++) - { - tempAttribState[attribs[i].attribute.location] = true; - } - - for (i = 0; i < attribState.length; i++) - { - if (attribState[i] !== tempAttribState[i]) - { - attribState[i] = tempAttribState[i]; - - if (state.attribState[i]) - { - gl.enableVertexAttribArray(i); - } - else - { - gl.disableVertexAttribArray(i); - } - } - } - } - else - { - for (i = 0; i < attribs.length; i++) - { - const attrib = attribs[i]; - - gl.enableVertexAttribArray(attrib.attribute.location); - } - } -} diff --git a/packages/core/src/renderers/systems/index.js b/packages/core/src/renderers/systems/index.js deleted file mode 100644 index c843f03..0000000 --- a/packages/core/src/renderers/systems/index.js +++ /dev/null @@ -1,6 +0,0 @@ -/** - * Systems are individual components to the Renderer pipeline. - * @namespace PIXI.systems - */ -export { default as WebGLSystem } from './WebGLSystem'; -export { default as FilterSystem } from './filter/FilterSystem'; diff --git a/packages/core/src/renderers/systems/shader/GLProgram.js b/packages/core/src/renderers/systems/shader/GLProgram.js deleted file mode 100644 index 92980ca..0000000 --- a/packages/core/src/renderers/systems/shader/GLProgram.js +++ /dev/null @@ -1,43 +0,0 @@ -/** - * Helper class to create a webGL Program - * - * @class - * @private - */ -export default class GLProgram -{ - constructor(program, uniformData) - { - /** - * The shader program - * - * @member {WebGLProgram} - */ - this.program = program; - - /** - * holds the uniform data which contains uniform locations - * and current uniform values used for caching and preventing undeed GPU commands - * @type {Object} - */ - this.uniformData = uniformData; - - /** - * uniformGroups holds the various upload functions for the shader. Each uniform group - * and program have a unique upload function generated. - * @type {Object} - */ - this.uniformGroups = {}; - } - - /** - * Destroys this program - * TODO - */ - destroy() - { - this.uniformData = null; - this.uniformGroups = null; - this.program = null; - } -} diff --git a/packages/core/src/renderers/systems/shader/ShaderSystem.js b/packages/core/src/renderers/systems/shader/ShaderSystem.js deleted file mode 100644 index 6cf57b6..0000000 --- a/packages/core/src/renderers/systems/shader/ShaderSystem.js +++ /dev/null @@ -1,170 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; -import GLProgram from './GLProgram'; -import generateUniformsSync from '../../../shader/generateUniformsSync'; -import defaultValue from '../../../shader/defaultValue'; -import compileProgram from './shader/compileProgram'; - -let UID = 0; - -/** - * Helper class to create a webGL Texture - * - * @class - * @memberof PIXI - */ -export default class ShaderSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - A reference to the current renderer - */ - constructor(renderer) - { - super(renderer); - - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = null; - - this.shader = null; - this.program = null; - - this.id = UID++; - } - - contextChange(gl) - { - this.gl = gl; - } - - /** - * Changes the current shader to the one given in parameter - * - * @param {PIXI.Shader} shader - the new shader - * @param {boolean} dontSync - false if the shader should automatically sync its uniforms. - * @returns {PIXI.glCore.glProgram} the glProgram that belongs to the shader. - */ - bind(shader, dontSync) - { - shader.uniforms.globals = this.renderer.globalUniforms; - - const program = shader.program; - const glProgram = program.glPrograms[this.renderer.CONTEXT_UID] || this.generateShader(shader); - - this.shader = shader; - - // TODO - some current pixi plugins bypass this.. so it not safe to use yet.. - if (this.program !== program) - { - this.program = program; - this.gl.useProgram(glProgram.program); - } - - if (!dontSync) - { - this.syncUniformGroup(shader.uniformGroup); - } - - return glProgram; - } - - /** - * Uploads the uniforms values to the currently bound shader. - * - * @param {object} uniforms - the uniforms valiues that be applied to the current shader - */ - setUniforms(uniforms) - { - const shader = this.shader.program; - const glProgram = shader.glPrograms[this.renderer.CONTEXT_UID]; - - shader.syncUniforms(glProgram.uniformData, uniforms, this.renderer); - } - - syncUniformGroup(group) - { - const glProgram = this.getglProgram(); - - if (!group.static || group.dirtyId !== glProgram.uniformGroups[group.id]) - { - glProgram.uniformGroups[group.id] = group.dirtyId; - const syncFunc = group.syncUniforms[this.shader.program.id] || this.createSyncGroups(group); - - syncFunc(glProgram.uniformData, group.uniforms, this.renderer); - } - } - - createSyncGroups(group) - { - group.syncUniforms[this.shader.program.id] = generateUniformsSync(group, this.shader.program.uniformData); - - return group.syncUniforms[this.shader.program.id]; - } - - /** - * Returns the underlying GLShade rof the currently bound shader. - * This can be handy for when you to have a little more control over the setting of your uniforms. - * - * @return {PIXI.glCore.Shader} the glProgram for the currently bound Shader for this context - */ - getglProgram() - { - if (this.shader) - { - return this.shader.program.glPrograms[this.renderer.CONTEXT_UID]; - } - - return null; - } - - /** - * Generates a glProgram verion of the Shader provided. - * - * @private - * @param {PIXI.Shader} shader the shader that the glProgram will be based on. - * @return {PIXI.glCore.glProgram} A shiney new glProgram - */ - generateShader(shader) - { - const gl = this.gl; - - const program = shader.program; - - const attribMap = {}; - - for (const i in program.attributeData) - { - attribMap[i] = program.attributeData[i].location; - } - - const shaderProgram = compileProgram(gl, program.vertexSrc, program.fragmentSrc, attribMap); - const uniformData = {}; - - for (const i in program.uniformData) - { - const data = program.uniformData[i]; - - uniformData[i] = { - location: gl.getUniformLocation(shaderProgram, i), - value: defaultValue(data.type, data.size), - }; - } - - const glProgram = new GLProgram(shaderProgram, uniformData); - - program.glPrograms[this.renderer.CONTEXT_UID] = glProgram; - - return glProgram; - } - - /** - * Destroys this System and removes all its textures - */ - destroy() - { - // TODO implement destroy method for ShaderSystem - this.destroyed = true; - } -} diff --git a/packages/core/src/renderers/systems/shader/shader/compileProgram.js b/packages/core/src/renderers/systems/shader/shader/compileProgram.js deleted file mode 100644 index 8c6ccb9..0000000 --- a/packages/core/src/renderers/systems/shader/shader/compileProgram.js +++ /dev/null @@ -1,78 +0,0 @@ -/** - * @method compileProgram - * @memberof PIXI.glCore.shader - * @param gl {WebGLRenderingContext} The current WebGL context {WebGLProgram} - * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. - * @param fragmentSrc {string|string[]} The fragment shader source as an array of strings. - * @param attributeLocations {Object} An attribute location map that lets you manually set the attribute locations - * @return {WebGLProgram} the shader program - */ -export default function compileProgram(gl, vertexSrc, fragmentSrc, attributeLocations) -{ - const glVertShader = compileShader(gl, gl.VERTEX_SHADER, vertexSrc); - const glFragShader = compileShader(gl, gl.FRAGMENT_SHADER, fragmentSrc); - - let program = gl.createProgram(); - - gl.attachShader(program, glVertShader); - gl.attachShader(program, glFragShader); - - // optionally, set the attributes manually for the program rather than letting WebGL decide.. - if (attributeLocations) - { - for (const i in attributeLocations) - { - gl.bindAttribLocation(program, attributeLocations[i], i); - } - } - - gl.linkProgram(program); - - // if linking fails, then log and cleanup - if (!gl.getProgramParameter(program, gl.LINK_STATUS)) - { - console.error('Pixi.js Error: Could not initialize shader.'); - console.error('gl.VALIDATE_STATUS', gl.getProgramParameter(program, gl.VALIDATE_STATUS)); - console.error('gl.getError()', gl.getError()); - - // if there is a program info log, log it - if (gl.getProgramInfoLog(program) !== '') - { - console.warn('Pixi.js Warning: gl.getProgramInfoLog()', gl.getProgramInfoLog(program)); - } - - gl.deleteProgram(program); - program = null; - } - - // clean up some shaders - gl.deleteShader(glVertShader); - gl.deleteShader(glFragShader); - - return program; -} - -/** - * @private - * @param gl {WebGLRenderingContext} The current WebGL context {WebGLProgram} - * @param type {Number} the type, can be either VERTEX_SHADER or FRAGMENT_SHADER - * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. - * @return {WebGLShader} the shader - */ -function compileShader(gl, type, src) -{ - const shader = gl.createShader(type); - - gl.shaderSource(shader, src); - gl.compileShader(shader); - - if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) - { - console.warn(src); - console.error(gl.getShaderInfoLog(shader)); - - return null; - } - - return shader; -} diff --git a/packages/core/src/renderers/systems/shader/shader/defaultValue.js b/packages/core/src/renderers/systems/shader/shader/defaultValue.js deleted file mode 100644 index 97290bd..0000000 --- a/packages/core/src/renderers/systems/shader/shader/defaultValue.js +++ /dev/null @@ -1,79 +0,0 @@ -/** - * @method defaultValue - * @memberof PIXI.glCore.shader - * @param type {String} Type of value - * @param size {Number} - */ -export default function defaultValue(type, size) -{ - switch (type) - { - case 'float': - return 0; - - case 'vec2': - return new Float32Array(2 * size); - - case 'vec3': - return new Float32Array(3 * size); - - case 'vec4': - return new Float32Array(4 * size); - - case 'int': - case 'sampler2D': - case 'sampler2DArray': - return 0; - - case 'ivec2': - return new Int32Array(2 * size); - - case 'ivec3': - return new Int32Array(3 * size); - - case 'ivec4': - return new Int32Array(4 * size); - - case 'bool': - return false; - - case 'bvec2': - - return booleanArray(2 * size); - - case 'bvec3': - return booleanArray(3 * size); - - case 'bvec4': - return booleanArray(4 * size); - - case 'mat2': - return new Float32Array([1, 0, - 0, 1]); - - case 'mat3': - return new Float32Array([1, 0, 0, - 0, 1, 0, - 0, 0, 1]); - - case 'mat4': - return new Float32Array([1, 0, 0, 0, - 0, 1, 0, 0, - 0, 0, 1, 0, - 0, 0, 0, 1]); - } - - return null; -} - -function booleanArray(size) -{ - const array = new Array(size); - - for (let i = 0; i < array.length; i++) - { - array[i] = false; - } - - return array; -} diff --git a/packages/core/src/renderers/systems/shader/shader/index.js b/packages/core/src/renderers/systems/shader/shader/index.js deleted file mode 100644 index ba7a317..0000000 --- a/packages/core/src/renderers/systems/shader/shader/index.js +++ /dev/null @@ -1,8 +0,0 @@ -/** - * @namespace PIXI.glCore.shader - */ -export { default as compileProgram } from './compileProgram'; -export { default as defaultValue } from './defaultValue'; -export { default as setPrecision } from './setPrecision'; -export { default as mapSize } from './mapSize'; -export { default as mapType } from './mapType'; diff --git a/packages/core/src/renderers/systems/shader/shader/mapSize.js b/packages/core/src/renderers/systems/shader/shader/mapSize.js deleted file mode 100644 index b681554..0000000 --- a/packages/core/src/renderers/systems/shader/shader/mapSize.js +++ /dev/null @@ -1,33 +0,0 @@ -const GLSL_TO_SIZE = { - float: 1, - vec2: 2, - vec3: 3, - vec4: 4, - - int: 1, - ivec2: 2, - ivec3: 3, - ivec4: 4, - - bool: 1, - bvec2: 2, - bvec3: 3, - bvec4: 4, - - mat2: 4, - mat3: 9, - mat4: 16, - - sampler2D: 1, -}; - -/** - * @method mapSize - * @memberof PIXI.glCore.shader - * @param type {String} - * @return {Number} - */ -export default function mapSize(type) -{ - return GLSL_TO_SIZE[type]; -} diff --git a/packages/core/src/renderers/systems/shader/shader/mapType.js b/packages/core/src/renderers/systems/shader/shader/mapType.js deleted file mode 100644 index d288b7c..0000000 --- a/packages/core/src/renderers/systems/shader/shader/mapType.js +++ /dev/null @@ -1,45 +0,0 @@ -let GL_TABLE = null; - -const GL_TO_GLSL_TYPES = { - FLOAT: 'float', - FLOAT_VEC2: 'vec2', - FLOAT_VEC3: 'vec3', - FLOAT_VEC4: 'vec4', - - INT: 'int', - INT_VEC2: 'ivec2', - INT_VEC3: 'ivec3', - INT_VEC4: 'ivec4', - - BOOL: 'bool', - BOOL_VEC2: 'bvec2', - BOOL_VEC3: 'bvec3', - BOOL_VEC4: 'bvec4', - - FLOAT_MAT2: 'mat2', - FLOAT_MAT3: 'mat3', - FLOAT_MAT4: 'mat4', - - SAMPLER_2D: 'sampler2D', - SAMPLER_CUBE: 'samplerCube', - SAMPLER_2D_ARRAY: 'sampler2DArray', -}; - -export default function mapType(gl, type) -{ - if (!GL_TABLE) - { - const typeNames = Object.keys(GL_TO_GLSL_TYPES); - - GL_TABLE = {}; - - for (let i = 0; i < typeNames.length; ++i) - { - const tn = typeNames[i]; - - GL_TABLE[gl[tn]] = GL_TO_GLSL_TYPES[tn]; - } - } - - return GL_TABLE[type]; -} diff --git a/packages/core/src/renderers/systems/shader/shader/setPrecision.js b/packages/core/src/renderers/systems/shader/shader/setPrecision.js deleted file mode 100644 index e2c1f34..0000000 --- a/packages/core/src/renderers/systems/shader/shader/setPrecision.js +++ /dev/null @@ -1,18 +0,0 @@ -/** - * Sets the float precision on the shader. If the precision is already present this function will do nothing - * @param {string} src the shader source - * @param {string} precision The float precision of the shader. Options are 'lowp', 'mediump' or 'highp'. - * - * @return {string} modified shader source - */ -export default function setPrecision(src, precision) -{ - src = src.trim(); - - if (src.substring(0, 9) !== 'precision' && src.substring(0, 1) !== '#') - { - return `precision ${precision} float;\n${src}`; - } - - return src; -} diff --git a/packages/core/src/renderers/systems/textures/GLTexture.js b/packages/core/src/renderers/systems/textures/GLTexture.js deleted file mode 100644 index c3fb8dc..0000000 --- a/packages/core/src/renderers/systems/textures/GLTexture.js +++ /dev/null @@ -1,33 +0,0 @@ -export default class GLTexture -{ - constructor(texture) - { - /** - * The WebGL texture - * - * @member {WebGLTexture} - */ - this.texture = texture; - - this.width = -1; - this.height = -1; - - /** - * Texture contents dirty flag - * @member {number} - */ - this.dirtyId = -1; - - /** - * Texture style dirty flag - * @type {number} - */ - this.dirtyStyleId = -1; - - /** - * Whether mip levels has to be generated - * @type {boolean} - */ - this.mipmap = false; - } -} diff --git a/packages/core/src/renderers/systems/textures/TextureGCSystem.js b/packages/core/src/renderers/systems/textures/TextureGCSystem.js deleted file mode 100644 index b089938..0000000 --- a/packages/core/src/renderers/systems/textures/TextureGCSystem.js +++ /dev/null @@ -1,110 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; -import { GC_MODES } from '@pixi/constants'; -import { settings } from '@pixi/settings'; - -/** - * TextureGarbageCollector. This class manages the GPU and ensures that it does not get clogged - * up with textures that are no longer being used. - * - * @class - * @memberof PIXI - */ -export default class TextureGCSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this.count = 0; - this.checkCount = 0; - this.maxIdle = settings.GC_MAX_IDLE; - this.checkCountMax = settings.GC_MAX_CHECK_COUNT; - this.mode = settings.GC_MODE; - } - - /** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ - postrender() - { - this.count++; - - if (this.mode === GC_MODES.MANUAL) - { - return; - } - - this.checkCount++; - - if (this.checkCount > this.checkCountMax) - { - this.checkCount = 0; - - this.run(); - } - } - - /** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ - run() - { - const tm = this.renderer.texture; - const managedTextures = tm.managedTextures; - let wasRemoved = false; - - for (let i = 0; i < managedTextures.length; i++) - { - const texture = managedTextures[i]; - - // only supports non generated textures at the moment! - if (!texture.frameBuffer && this.count - texture.touched > this.maxIdle) - { - tm.destroyTexture(texture, true); - managedTextures[i] = null; - wasRemoved = true; - } - } - - if (wasRemoved) - { - let j = 0; - - for (let i = 0; i < managedTextures.length; i++) - { - if (managedTextures[i] !== null) - { - managedTextures[j++] = managedTextures[i]; - } - } - - managedTextures.length = j; - } - } - - /** - * Removes all the textures within the specified displayObject and its children from the GPU - * - * @param {PIXI.DisplayObject} displayObject - the displayObject to remove the textures from. - */ - unload(displayObject) - { - const tm = this.renderer.textureSystem; - - // only destroy non generated textures - if (displayObject._texture && displayObject._texture._glRenderTargets) - { - tm.destroyTexture(displayObject._texture); - } - - for (let i = displayObject.children.length - 1; i >= 0; i--) - { - this.unload(displayObject.children[i]); - } - } -} diff --git a/packages/core/src/renderers/systems/textures/TextureSystem.js b/packages/core/src/renderers/systems/textures/TextureSystem.js deleted file mode 100644 index ccc5de9..0000000 --- a/packages/core/src/renderers/systems/textures/TextureSystem.js +++ /dev/null @@ -1,281 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; -import GLTexture from './GLTexture'; -import { removeItems } from '@pixi/utils'; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class TextureSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - // TODO set to max textures... - this.boundTextures = [ - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - ]; - - this.currentLocation = -1; - - this.managedTextures = []; - } - - /** - * Sets up the renderer context and necessary buffers. - * - * @private - */ - contextChange() - { - const gl = this.gl = this.renderer.gl; - - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - - // TODO move this.. to a nice make empty textures class.. - this.emptyTextures = {}; - - const emptyTexture2D = new GLTexture(gl.createTexture()); - - gl.bindTexture(gl.TEXTURE_2D, emptyTexture2D.texture); - gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array(4)); - - this.emptyTextures[gl.TEXTURE_2D] = emptyTexture2D; - this.emptyTextures[gl.TEXTURE_CUBE_MAP] = new GLTexture(gl.createTexture()); - - gl.bindTexture(gl.TEXTURE_CUBE_MAP, this.emptyTextures[gl.TEXTURE_CUBE_MAP].texture); - - let i; - - for (i = 0; i < 6; i++) - { - gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, null); - } - - gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_MAG_FILTER, gl.LINEAR); - gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_MIN_FILTER, gl.LINEAR); - - for (i = 0; i < this.boundTextures.length; i++) - { - this.bind(null, i); - } - } - - bind(texture, location) - { - const gl = this.gl; - - location = location || 0; - - if (this.currentLocation !== location) - { - this.currentLocation = location; - gl.activeTexture(gl.TEXTURE0 + location); - } - - if (texture) - { - texture = texture.baseTexture || texture; - - if (texture.valid) - { - texture.touched = this.renderer.textureGC.count; - - const glTexture = texture._glTextures[this.CONTEXT_UID] || this.initTexture(texture); - - gl.bindTexture(texture.target, glTexture.texture); - - if (glTexture.dirtyId !== texture.dirtyId) - { - this.updateTexture(texture); - } - - this.boundTextures[location] = texture; - } - } - else - { - gl.bindTexture(gl.TEXTURE_2D, this.emptyTextures[gl.TEXTURE_2D].texture); - this.boundTextures[location] = null; - } - } - - unbind(texture) - { - const gl = this.gl; - - for (let i = 0; i < this.boundTextures.length; i++) - { - if (this.boundTextures[i] === texture) - { - if (this.currentLocation !== i) - { - gl.activeTexture(gl.TEXTURE0 + i); - this.currentLocation = i; - } - - gl.bindTexture(gl.TEXTURE_2D, this.emptyTextures[texture.target].texture); - this.boundTextures[i] = null; - } - } - } - - initTexture(texture) - { - const glTexture = new GLTexture(this.gl.createTexture()); - - // guarentee an update.. - glTexture.dirtyId = -1; - - texture._glTextures[this.CONTEXT_UID] = glTexture; - - this.managedTextures.push(texture); - texture.on('dispose', this.destroyTexture, this); - - return glTexture; - } - - updateTexture(texture) - { - const glTexture = texture._glTextures[this.CONTEXT_UID]; - const renderer = this.renderer; - - if (texture.resource && texture.resource.upload(renderer, texture, glTexture)) - { - // texture is uploaded, dont do anything! - } - else - { - // default, renderTexture-like logic - const width = texture.realWidth; - const height = texture.realHeight; - const gl = renderer.gl; - - if (glTexture.width !== width - || glTexture.height !== height - || glTexture.dirtyId < 0) - { - glTexture.width = width; - glTexture.height = height; - - gl.texImage2D(texture.target, 0, - texture.format, - width, - height, - 0, - texture.format, - texture.type, - null); - } - } - - // lets only update what changes.. - if (texture.dirtyStyleId !== glTexture.dirtyStyleId) - { - this.updateTextureStyle(texture); - } - glTexture.dirtyId = texture.dirtyId; - } - - /** - * Deletes the texture from WebGL - * - * @param {PIXI.BaseTexture|PIXI.Texture} texture - the texture to destroy - * @param {boolean} [skipRemove=false] - Whether to skip removing the texture from the TextureManager. - */ - destroyTexture(texture, skipRemove) - { - const gl = this.gl; - - texture = texture.baseTexture || texture; - - if (texture._glTextures[this.renderer.CONTEXT_UID]) - { - this.unbind(texture); - - gl.deleteTexture(texture._glTextures[this.renderer.CONTEXT_UID].texture); - texture.off('dispose', this.destroyTexture, this); - - delete texture._glTextures[this.renderer.CONTEXT_UID]; - - if (!skipRemove) - { - const i = this.managedTextures.indexOf(texture); - - if (i !== -1) - { - removeItems(this.managedTextures, i, 1); - } - } - } - } - - updateTextureStyle(texture) - { - const glTexture = texture._glTextures[this.CONTEXT_UID]; - - glTexture.mipmap = texture.mipmap && texture.isPowerOfTwo; - if (!glTexture) - { - return; - } - - if (texture.resource && texture.resource.style(this.renderer, texture, glTexture)) - { - // style is set, dont do anything! - } - else - { - this.setStyle(texture, glTexture); - } - - glTexture.dirtyStyleId = texture.dirtyStyleId; - } - - setStyle(texture, glTexture) - { - const gl = this.gl; - - if (glTexture.mipmap) - { - gl.generateMipmap(texture.target); - } - - gl.texParameteri(texture.target, gl.TEXTURE_WRAP_S, texture.wrapMode); - gl.texParameteri(texture.target, gl.TEXTURE_WRAP_T, texture.wrapMode); - - if (glTexture.mipmap) - { - /* eslint-disable max-len */ - gl.texParameteri(texture.target, gl.TEXTURE_MIN_FILTER, texture.scaleMode ? gl.LINEAR_MIPMAP_LINEAR : gl.NEAREST_MIPMAP_NEAREST); - /* eslint-disable max-len */ - } - else - { - gl.texParameteri(texture.target, gl.TEXTURE_MIN_FILTER, texture.scaleMode ? gl.LINEAR : gl.NEAREST); - } - - gl.texParameteri(texture.target, gl.TEXTURE_MAG_FILTER, texture.scaleMode ? gl.LINEAR : gl.NEAREST); - } -} diff --git a/packages/core/src/renderers/utils/ObjectRenderer.js b/packages/core/src/renderers/utils/ObjectRenderer.js deleted file mode 100644 index f67a273..0000000 --- a/packages/core/src/renderers/utils/ObjectRenderer.js +++ /dev/null @@ -1,48 +0,0 @@ -import WebGLSystem from '../systems/WebGLSystem'; - -/** - * Base for a common object renderer that can be used as a system renderer plugin. - * - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class ObjectRenderer extends WebGLSystem -{ - /** - * Starts the renderer and sets the shader - * - */ - start() - { - // set the shader.. - } - - /** - * Stops the renderer - * - */ - stop() - { - this.flush(); - } - - /** - * Stub method for rendering content and emptying the current batch. - * - */ - flush() - { - // flush! - } - - /** - * Renders an object - * - * @param {PIXI.DisplayObject} object - The object to render. - */ - render(object) // eslint-disable-line no-unused-vars - { - // render the object - } -} diff --git a/packages/core/src/renderers/utils/Quad.js b/packages/core/src/renderers/utils/Quad.js deleted file mode 100644 index 5c1517b..0000000 --- a/packages/core/src/renderers/utils/Quad.js +++ /dev/null @@ -1,26 +0,0 @@ -import Geometry from '../../geometry/Geometry'; - -/** - * Helper class to create a quad - * - * @class - * @memberof PIXI - */ -export default class Quad extends Geometry -{ - /** - * just a quad - */ - constructor() - { - super(); - - this.addAttribute('aVertexPosition', [ - 0, 0, - 1, 0, - 1, 1, - 0, 1, - ]) - .addIndex([0, 1, 3, 2]); - } -} diff --git a/packages/core/src/renderers/utils/QuadUv.js b/packages/core/src/renderers/utils/QuadUv.js deleted file mode 100644 index 97bda89..0000000 --- a/packages/core/src/renderers/utils/QuadUv.js +++ /dev/null @@ -1,103 +0,0 @@ -import Geometry from '../../geometry/Geometry'; -import Buffer from '../../geometry/Buffer'; - -/** - * Helper class to create a quad with uvs like in v4 - * - * @class - * @memberof PIXI - */ -export default class QuadUv extends Geometry -{ - constructor() - { - super(); - - /** - * An array of vertices - * - * @member {Float32Array} - */ - this.vertices = new Float32Array([ - -1, -1, - 1, -1, - 1, 1, - -1, 1, - ]); - - /** - * The Uvs of the quad - * - * @member {Float32Array} - */ - this.uvs = new Float32Array([ - 0, 0, - 1, 0, - 1, 1, - 0, 1, - ]); - - this.vertexBuffer = new Buffer(this.vertices); - this.uvBuffer = new Buffer(this.uvs); - - this.addAttribute('aVertexPosition', this.vertexBuffer) - .addAttribute('aTextureCoord', this.uvBuffer) - .addIndex([0, 1, 2, 0, 2, 3]); - } - - /** - * Maps two Rectangle to the quad. - * - * @param {PIXI.Rectangle} targetTextureFrame - the first rectangle - * @param {PIXI.Rectangle} destinationFrame - the second rectangle - * @return {PIXI.Quad} Returns itself. - */ - map(targetTextureFrame, destinationFrame) - { - let x = 0; // destinationFrame.x / targetTextureFrame.width; - let y = 0; // destinationFrame.y / targetTextureFrame.height; - - this.uvs[0] = x; - this.uvs[1] = y; - - this.uvs[2] = x + (destinationFrame.width / targetTextureFrame.width); - this.uvs[3] = y; - - this.uvs[4] = x + (destinationFrame.width / targetTextureFrame.width); - this.uvs[5] = y + (destinationFrame.height / targetTextureFrame.height); - - this.uvs[6] = x; - this.uvs[7] = y + (destinationFrame.height / targetTextureFrame.height); - - x = destinationFrame.x; - y = destinationFrame.y; - - this.vertices[0] = x; - this.vertices[1] = y; - - this.vertices[2] = x + destinationFrame.width; - this.vertices[3] = y; - - this.vertices[4] = x + destinationFrame.width; - this.vertices[5] = y + destinationFrame.height; - - this.vertices[6] = x; - this.vertices[7] = y + destinationFrame.height; - - this.invalidate(); - - return this; - } - - /** - * legacy upload method, just marks buffers dirty - * @returns {PIXI.QuadUv} Returns itself. - */ - invalidate() - { - this.vertexBuffer._updateID++; - this.uvBuffer._updateID++; - - return this; - } -} diff --git a/packages/core/src/renderers/utils/checkMaxIfStatmentsInShader.js b/packages/core/src/renderers/utils/checkMaxIfStatmentsInShader.js deleted file mode 100644 index 8047c96..0000000 --- a/packages/core/src/renderers/utils/checkMaxIfStatmentsInShader.js +++ /dev/null @@ -1,58 +0,0 @@ -const fragTemplate = [ - 'precision mediump float;', - 'void main(void){', - 'float test = 0.1;', - '%forloop%', - 'gl_FragColor = vec4(0.0);', - '}', -].join('\n'); - -export default function checkMaxIfStatmentsInShader(maxIfs, gl) -{ - if (maxIfs === 0) - { - throw new Error('Invalid value of `0` passed to `checkMaxIfStatementsInShader`'); - } - - const shader = gl.createShader(gl.FRAGMENT_SHADER); - - while (true) // eslint-disable-line no-constant-condition - { - const fragmentSrc = fragTemplate.replace(/%forloop%/gi, generateIfTestSrc(maxIfs)); - - gl.shaderSource(shader, fragmentSrc); - gl.compileShader(shader); - - if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) - { - maxIfs = (maxIfs / 2) | 0; - } - else - { - // valid! - break; - } - } - - return maxIfs; -} - -function generateIfTestSrc(maxIfs) -{ - let src = ''; - - for (let i = 0; i < maxIfs; ++i) - { - if (i > 0) - { - src += '\nelse '; - } - - if (i < maxIfs - 1) - { - src += `if(test == ${i}.0){}`; - } - } - - return src; -} diff --git a/packages/core/src/renderers/utils/mapWebGLBlendModesToPixi.js b/packages/core/src/renderers/utils/mapWebGLBlendModesToPixi.js deleted file mode 100644 index 10ffe4d..0000000 --- a/packages/core/src/renderers/utils/mapWebGLBlendModesToPixi.js +++ /dev/null @@ -1,47 +0,0 @@ -import { BLEND_MODES } from '@pixi/constants'; - -/** - * Maps gl blend combinations to WebGL. - * - * @memberof PIXI - * @function mapWebGLBlendModesToPixi - * @private - * @param {WebGLRenderingContext} gl - The rendering context. - * @param {string[]} [array=[]] - The array to output into. - * @return {string[]} Mapped modes. - */ -export default function mapWebGLBlendModesToPixi(gl, array = []) -{ - // TODO - premultiply alpha would be different. - // add a boolean for that! - array[BLEND_MODES.NORMAL] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.ADD] = [gl.ONE, gl.DST_ALPHA]; - array[BLEND_MODES.MULTIPLY] = [gl.DST_COLOR, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.SCREEN] = [gl.ONE, gl.ONE_MINUS_SRC_COLOR]; - array[BLEND_MODES.OVERLAY] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.DARKEN] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.LIGHTEN] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.COLOR_DODGE] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.COLOR_BURN] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.HARD_LIGHT] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.SOFT_LIGHT] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.DIFFERENCE] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.EXCLUSION] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.HUE] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.SATURATION] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.COLOR] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.LUMINOSITY] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.NONE] = [0, 0]; - - // not-premultiplied blend modes - array[BLEND_MODES.NORMAL_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.ADD_NPM] = [gl.SRC_ALPHA, gl.DST_ALPHA, gl.ONE, gl.DST_ALPHA]; - array[BLEND_MODES.SCREEN_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_COLOR, gl.ONE, gl.ONE_MINUS_SRC_COLOR]; - - // not-premultiplied blend modes - array[BLEND_MODES.NORMAL_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.ADD_NPM] = [gl.SRC_ALPHA, gl.DST_ALPHA, gl.ONE, gl.DST_ALPHA]; - array[BLEND_MODES.SCREEN_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_COLOR, gl.ONE, gl.ONE_MINUS_SRC_COLOR]; - - return array; -} diff --git a/packages/core/src/shader/GLProgram.js b/packages/core/src/shader/GLProgram.js new file mode 100644 index 0000000..92980ca --- /dev/null +++ b/packages/core/src/shader/GLProgram.js @@ -0,0 +1,43 @@ +/** + * Helper class to create a webGL Program + * + * @class + * @private + */ +export default class GLProgram +{ + constructor(program, uniformData) + { + /** + * The shader program + * + * @member {WebGLProgram} + */ + this.program = program; + + /** + * holds the uniform data which contains uniform locations + * and current uniform values used for caching and preventing undeed GPU commands + * @type {Object} + */ + this.uniformData = uniformData; + + /** + * uniformGroups holds the various upload functions for the shader. Each uniform group + * and program have a unique upload function generated. + * @type {Object} + */ + this.uniformGroups = {}; + } + + /** + * Destroys this program + * TODO + */ + destroy() + { + this.uniformData = null; + this.uniformGroups = null; + this.program = null; + } +} diff --git a/packages/core/src/shader/Program.js b/packages/core/src/shader/Program.js index 4f9971e..4b4007a 100644 --- a/packages/core/src/shader/Program.js +++ b/packages/core/src/shader/Program.js @@ -1,6 +1,11 @@ -import * as shaderUtils from '../renderers/systems/shader/shader'; +// import * as from '../systems/shader/shader'; +import { setPrecision, + defaultValue, + compileProgram, + mapSize, + mapType, + getTestContext } from './utils'; import { ProgramCache } from '@pixi/utils'; -import getTestContext from './getTestContext'; import defaultFragment from './defaultProgram.frag'; import defaultVertex from './defaultProgram.vert'; import { settings } from '@pixi/settings'; @@ -34,8 +39,8 @@ */ this.fragmentSrc = fragmentSrc || Program.defaultFragmentSrc; - this.vertexSrc = shaderUtils.setPrecision(this.vertexSrc, settings.PRECISION_VERTEX); - this.fragmentSrc = shaderUtils.setPrecision(this.fragmentSrc, settings.PRECISION_FRAGMENT); + this.vertexSrc = setPrecision(this.vertexSrc, settings.PRECISION_VERTEX); + this.fragmentSrc = setPrecision(this.fragmentSrc, settings.PRECISION_FRAGMENT); // currently this does not extract structs only default types this.extractData(this.vertexSrc, this.fragmentSrc); @@ -62,7 +67,7 @@ if (gl) { - const program = shaderUtils.compileProgram(gl, vertexSrc, fragmentSrc); + const program = compileProgram(gl, vertexSrc, fragmentSrc); this.attributeData = this.getAttributeData(program, gl); this.uniformData = this.getUniformData(program, gl); @@ -95,13 +100,13 @@ for (let i = 0; i < totalAttributes; i++) { const attribData = gl.getActiveAttrib(program, i); - const type = shaderUtils.mapType(gl, attribData.type); + const type = mapType(gl, attribData.type); /*eslint-disable */ const data = { type: type, name: attribData.name, - size: shaderUtils.mapSize(type), + size: mapSize(type), location: 0, }; /* eslint-enable */ @@ -145,14 +150,14 @@ const name = uniformData.name.replace(/\[.*?\]/, ''); const isArray = uniformData.name.match(/\[.*?\]/, ''); - const type = shaderUtils.mapType(gl, uniformData.type); + const type = mapType(gl, uniformData.type); /*eslint-disable */ uniforms[name] = { type: type, size: uniformData.size, isArray:isArray, - value: shaderUtils.defaultValue(type, uniformData.size), + value: defaultValue(type, uniformData.size), }; /* eslint-enable */ } diff --git a/packages/core/src/shader/ShaderSystem.js b/packages/core/src/shader/ShaderSystem.js new file mode 100644 index 0000000..40b8af8 --- /dev/null +++ b/packages/core/src/shader/ShaderSystem.js @@ -0,0 +1,171 @@ +import System from '../System'; +import GLProgram from './GLProgram'; +import { generateUniformsSync, + defaultValue, + compileProgram } from './utils'; + +let UID = 0; + +/** + * Helper class to create a webGL Texture + * + * @class + * @memberof PIXI.systems + * @extends PIXI.System + */ +export default class ShaderSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - A reference to the current renderer + */ + constructor(renderer) + { + super(renderer); + + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = null; + + this.shader = null; + this.program = null; + + this.id = UID++; + } + + contextChange(gl) + { + this.gl = gl; + } + + /** + * Changes the current shader to the one given in parameter + * + * @param {PIXI.Shader} shader - the new shader + * @param {boolean} dontSync - false if the shader should automatically sync its uniforms. + * @returns {PIXI.glCore.glProgram} the glProgram that belongs to the shader. + */ + bind(shader, dontSync) + { + shader.uniforms.globals = this.renderer.globalUniforms; + + const program = shader.program; + const glProgram = program.glPrograms[this.renderer.CONTEXT_UID] || this.generateShader(shader); + + this.shader = shader; + + // TODO - some current pixi plugins bypass this.. so it not safe to use yet.. + if (this.program !== program) + { + this.program = program; + this.gl.useProgram(glProgram.program); + } + + if (!dontSync) + { + this.syncUniformGroup(shader.uniformGroup); + } + + return glProgram; + } + + /** + * Uploads the uniforms values to the currently bound shader. + * + * @param {object} uniforms - the uniforms valiues that be applied to the current shader + */ + setUniforms(uniforms) + { + const shader = this.shader.program; + const glProgram = shader.glPrograms[this.renderer.CONTEXT_UID]; + + shader.syncUniforms(glProgram.uniformData, uniforms, this.renderer); + } + + syncUniformGroup(group) + { + const glProgram = this.getglProgram(); + + if (!group.static || group.dirtyId !== glProgram.uniformGroups[group.id]) + { + glProgram.uniformGroups[group.id] = group.dirtyId; + const syncFunc = group.syncUniforms[this.shader.program.id] || this.createSyncGroups(group); + + syncFunc(glProgram.uniformData, group.uniforms, this.renderer); + } + } + + createSyncGroups(group) + { + group.syncUniforms[this.shader.program.id] = generateUniformsSync(group, this.shader.program.uniformData); + + return group.syncUniforms[this.shader.program.id]; + } + + /** + * Returns the underlying GLShade rof the currently bound shader. + * This can be handy for when you to have a little more control over the setting of your uniforms. + * + * @return {PIXI.glCore.Shader} the glProgram for the currently bound Shader for this context + */ + getglProgram() + { + if (this.shader) + { + return this.shader.program.glPrograms[this.renderer.CONTEXT_UID]; + } + + return null; + } + + /** + * Generates a glProgram verion of the Shader provided. + * + * @private + * @param {PIXI.Shader} shader the shader that the glProgram will be based on. + * @return {PIXI.glCore.glProgram} A shiney new glProgram + */ + generateShader(shader) + { + const gl = this.gl; + + const program = shader.program; + + const attribMap = {}; + + for (const i in program.attributeData) + { + attribMap[i] = program.attributeData[i].location; + } + + const shaderProgram = compileProgram(gl, program.vertexSrc, program.fragmentSrc, attribMap); + const uniformData = {}; + + for (const i in program.uniformData) + { + const data = program.uniformData[i]; + + uniformData[i] = { + location: gl.getUniformLocation(shaderProgram, i), + value: defaultValue(data.type, data.size), + }; + } + + const glProgram = new GLProgram(shaderProgram, uniformData); + + program.glPrograms[this.renderer.CONTEXT_UID] = glProgram; + + return glProgram; + } + + /** + * Destroys this System and removes all its textures + */ + destroy() + { + // TODO implement destroy method for ShaderSystem + this.destroyed = true; + } +} diff --git a/packages/core/src/shader/defaultValue.js b/packages/core/src/shader/defaultValue.js deleted file mode 100644 index 847a069..0000000 --- a/packages/core/src/shader/defaultValue.js +++ /dev/null @@ -1,78 +0,0 @@ -function booleanArray(size) -{ - const array = new Array(size); - - for (let i = 0; i < array.length; i++) - { - array[i] = false; - } - - return array; -} - -/** - * @class - * @memberof PIXI.glCore.shader - * @param type {String} Type of value - * @param size {Number} - */ -export default function defaultValue(type, size) -{ - switch (type) - { - case 'float': - return 0; - - case 'vec2': - return new Float32Array(2 * size); - - case 'vec3': - return new Float32Array(3 * size); - - case 'vec4': - return new Float32Array(4 * size); - - case 'int': - case 'sampler2D': - return 0; - - case 'ivec2': - return new Int32Array(2 * size); - - case 'ivec3': - return new Int32Array(3 * size); - - case 'ivec4': - return new Int32Array(4 * size); - - case 'bool': - return false; - - case 'bvec2': - - return booleanArray(2 * size); - - case 'bvec3': - return booleanArray(3 * size); - - case 'bvec4': - return booleanArray(4 * size); - - case 'mat2': - return new Float32Array([1, 0, - 0, 1]); - - case 'mat3': - return new Float32Array([1, 0, 0, - 0, 1, 0, - 0, 0, 1]); - - case 'mat4': - return new Float32Array([1, 0, 0, 0, - 0, 1, 0, 0, - 0, 0, 1, 0, - 0, 0, 0, 1]); - } - - return 0; -} diff --git a/packages/core/src/shader/extractUniformsFromSrc.js b/packages/core/src/shader/extractUniformsFromSrc.js deleted file mode 100644 index 3d02c0a..0000000 --- a/packages/core/src/shader/extractUniformsFromSrc.js +++ /dev/null @@ -1,57 +0,0 @@ -import defaultValue from './defaultValue'; - -export default function extractUniformsFromSrc(vertexSrc, fragmentSrc, mask) -{ - const vertUniforms = extractUniformsFromString(vertexSrc, mask); - const fragUniforms = extractUniformsFromString(fragmentSrc, mask); - - return Object.assign(vertUniforms, fragUniforms); -} - -function extractUniformsFromString(string) -{ - const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); - - const uniforms = {}; - let nameSplit; - - // clean the lines a little - remove extra spaces / teabs etc - // then split along ';' - const lines = string.replace(/\s+/g, ' ') - .split(/\s*;\s*/); - - // loop through.. - for (let i = 0; i < lines.length; i++) - { - const line = lines[i].trim(); - - if (line.indexOf('uniform') > -1) - { - const splitLine = line.split(' '); - const type = splitLine[1]; - - let name = splitLine[2]; - let size = 1; - - if (name.indexOf('[') > -1) - { - // array! - nameSplit = name.split(/\[|]/); - name = nameSplit[0]; - size *= Number(nameSplit[1]); - } - - if (!name.match(maskRegex)) - { - uniforms[name] = { - value: defaultValue(type, size), - dirtyId: 0, - name, - type, - }; - } - } - } - - return uniforms; -} diff --git a/packages/core/src/shader/generateUniformsSync.js b/packages/core/src/shader/generateUniformsSync.js deleted file mode 100644 index 98a357f..0000000 --- a/packages/core/src/shader/generateUniformsSync.js +++ /dev/null @@ -1,231 +0,0 @@ -// cv = CachedValue -// v = value -// ud = uniformData -// uv = uniformValue -// l = loaction -const GLSL_TO_SINGLE_SETTERS_CACHED = { - - float: ` - if(cv !== v) - { - cv.v = v; - gl.uniform1f(location, v) - }`, - - vec2: ` - if(cv[0] !== v[0] || cv[1] !== v[1]) - { - cv[0] = v[0]; - cv[1] = v[1]; - gl.uniform2f(location, v[0], v[1]) - }`, - - vec3: ` - if(cv[0] !== v[0] || cv[1] !== v[1] || cv[2] !== v[2]) - { - cv[0] = v[0]; - cv[1] = v[1]; - cv[2] = v[2]; - - gl.uniform3f(location, v[0], v[1], v[2]) - }`, - - vec4: 'gl.uniform4f(location, v[0], v[1], v[2], v[3])', - - int: 'gl.uniform1i(location, v)', - ivec2: 'gl.uniform2i(location, v[0], v[1])', - ivec3: 'gl.uniform3i(location, v[0], v[1], v[2])', - ivec4: 'gl.uniform4i(location, v[0], v[1], v[2], v[3])', - - bool: 'gl.uniform1i(location, v)', - bvec2: 'gl.uniform2i(location, v[0], v[1])', - bvec3: 'gl.uniform3i(location, v[0], v[1], v[2])', - bvec4: 'gl.uniform4i(location, v[0], v[1], v[2], v[3])', - - mat2: 'gl.uniformMatrix2fv(location, false, v)', - mat3: 'gl.uniformMatrix3fv(location, false, v)', - mat4: 'gl.uniformMatrix4fv(location, false, v)', - - sampler2D: 'gl.uniform1i(location, v)', - samplerCube: 'gl.uniform1i(location, v)', - sampler2DArray: 'gl.uniform1i(location, v)', -}; - -const GLSL_TO_ARRAY_SETTERS = { - - float: `gl.uniform1fv(location, v)`, - - vec2: `gl.uniform2fv(location, v)`, - vec3: `gl.uniform3fv(location, v)`, - vec4: 'gl.uniform4fv(location, v)', - - mat4: 'gl.uniformMatrix4fv(location, false, v)', - mat3: 'gl.uniformMatrix3fv(location, false, v)', - mat2: 'gl.uniformMatrix2fv(location, false, v)', - - int: 'gl.uniform1iv(location, v)', - ivec2: 'gl.uniform2iv(location, v)', - ivec3: 'gl.uniform3iv(location, v)', - ivec4: 'gl.uniform4iv(location, v)', - - bool: 'gl.uniform1iv(location, v)', - bvec2: 'gl.uniform2iv(location, v)', - bvec3: 'gl.uniform3iv(location, v)', - bvec4: 'gl.uniform4iv(location, v)', - - sampler2D: 'gl.uniform1iv(location, v)', - samplerCube: 'gl.uniform1iv(location, v)', - sampler2DArray: 'gl.uniform1iv(location, v)', -}; - -export default function generateUniformsSync(group, uniformData) -{ - let textureCount = 0; - let func = `var v = null; - var cv = null - var gl = renderer.gl`; - - for (const i in group.uniforms) - { - const data = uniformData[i]; - - if (!data) - { - if (group.uniforms[i].group) - { - func += ` - renderer.shader.syncUniformGroup(uv.${i}); - `; - } - - continue; - } - - // TODO && uniformData[i].value !== 0 <-- do we still need this? - if (data.type === 'float' && data.size === 1) - { - func += ` - if(uv.${i} !== ud.${i}.value) - { - ud.${i}.value = uv.${i} - gl.uniform1f(ud.${i}.location, uv.${i}) - }\n`; - } - /* eslint-disable max-len */ - else if ((data.type === 'sampler2D' || data.type === 'samplerCube' || data.type === 'sampler2DArray') && data.size === 1 && !data.isArray) - /* eslint-disable max-len */ - { - func += ` - renderer.texture.bind(uv.${i}, ${textureCount}); - - if(ud.${i}.value !== ${textureCount}) - { - ud.${i}.value = ${textureCount}; - gl.uniform1i(ud.${i}.location, ${textureCount});\n; // eslint-disable-line max-len - }\n`; - - textureCount++; - } - else if (data.type === 'mat3' && data.size === 1) - { - if (group.uniforms[i].a !== undefined) - { - // TODO and some smart caching dirty ids here! - func += ` - gl.uniformMatrix3fv(ud.${i}.location, false, uv.${i}.toArray(true)); - \n`; - } - else - { - func += ` - gl.uniformMatrix3fv(ud.${i}.location, false, uv.${i}); - \n`; - } - } - else if (data.type === 'vec2' && data.size === 1) - { - // TODO - do we need both here? - // maybe we can get away with only using points? - if (group.uniforms[i].x !== undefined) - { - func += ` - cv = ud.${i}.value; - v = uv.${i}; - - if(cv[0] !== v.x || cv[1] !== v.y) - { - cv[0] = v.x; - cv[1] = v.y; - gl.uniform2f(ud.${i}.location, v.x, v.y); - }\n`; - } - else - { - func += ` - cv = ud.${i}.value; - v = uv.${i}; - - if(cv[0] !== v[0] || cv[1] !== v[1]) - { - cv[0] = v[0]; - cv[1] = v[1]; - gl.uniform2f(ud.${i}.location, v[0], v[1]); - } - \n`; - } - } - else if (data.type === 'vec4' && data.size === 1) - { - // TODO - do we need both here? - // maybe we can get away with only using points? - if (group.uniforms[i].width !== undefined) - { - func += ` - cv = ud.${i}.value; - v = uv.${i}; - - if(cv[0] !== v.x || cv[1] !== v.y || cv[2] !== v.width || cv[3] !== v.height) - { - cv[0] = v.x; - cv[1] = v.y; - cv[2] = v.width; - cv[3] = v.height; - gl.uniform4f(ud.${i}.location, v.x, v.y, v.width, v.height) - }\n`; - } - else - { - func += ` - cv = ud.${i}.value; - v = uv.${i}; - - if(cv[0] !== v[0] || cv[1] !== v[1] || cv[2] !== v[2] || cv[3] !== v[3]) - { - cv[0] = v[0]; - cv[1] = v[1]; - cv[2] = v[2]; - cv[3] = v[3]; - - gl.uniform4f(ud.${i}.location, v[0], v[1], v[2], v[3]) - } - \n`; - } - } - else - { - const templateType = (data.size === 1) ? GLSL_TO_SINGLE_SETTERS_CACHED : GLSL_TO_ARRAY_SETTERS; - - const template = templateType[data.type].replace('location', `ud.${i}.location`); - - func += ` - cv = ud.${i}.value; - v = uv.${i}; - ${template};\n`; - } - } - - // console.log(' --------------- ') - // console.log(func); - - return new Function('ud', 'uv', 'renderer', func); // eslint-disable-line no-new-func -} diff --git a/packages/core/src/shader/getTestContext.js b/packages/core/src/shader/getTestContext.js deleted file mode 100644 index ea174cc..0000000 --- a/packages/core/src/shader/getTestContext.js +++ /dev/null @@ -1,48 +0,0 @@ -import { settings } from '@pixi/settings'; - -let context = null; - -/** - * returns a little webGL context to use for program inspection. - * - * @static - * @private - * @returns {webGL-context} a gl context to test with - */ -export default function getTestContext() -{ - if (!context) - { - const canvas = document.createElement('canvas'); - - let gl; - - if (settings.PREFER_WEBGL_2) - { - gl = canvas.getContext('webgl2', {}); - } - - if (!gl) - { - gl = canvas.getContext('webgl', {}) - || canvas.getContext('experimental-webgl', {}); - - if (!gl) - { - // fail, not able to get a context - throw new Error('This browser does not support webGL. Try using the canvas renderer'); - } - else - { - // for shader testing.. - gl.getExtension('WEBGL_draw_buffers'); - } - } - - context = gl; - - return gl; - } - - return context; -} diff --git a/packages/core/src/shader/utils/checkMaxIfStatmentsInShader.js b/packages/core/src/shader/utils/checkMaxIfStatmentsInShader.js new file mode 100644 index 0000000..8047c96 --- /dev/null +++ b/packages/core/src/shader/utils/checkMaxIfStatmentsInShader.js @@ -0,0 +1,58 @@ +const fragTemplate = [ + 'precision mediump float;', + 'void main(void){', + 'float test = 0.1;', + '%forloop%', + 'gl_FragColor = vec4(0.0);', + '}', +].join('\n'); + +export default function checkMaxIfStatmentsInShader(maxIfs, gl) +{ + if (maxIfs === 0) + { + throw new Error('Invalid value of `0` passed to `checkMaxIfStatementsInShader`'); + } + + const shader = gl.createShader(gl.FRAGMENT_SHADER); + + while (true) // eslint-disable-line no-constant-condition + { + const fragmentSrc = fragTemplate.replace(/%forloop%/gi, generateIfTestSrc(maxIfs)); + + gl.shaderSource(shader, fragmentSrc); + gl.compileShader(shader); + + if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) + { + maxIfs = (maxIfs / 2) | 0; + } + else + { + // valid! + break; + } + } + + return maxIfs; +} + +function generateIfTestSrc(maxIfs) +{ + let src = ''; + + for (let i = 0; i < maxIfs; ++i) + { + if (i > 0) + { + src += '\nelse '; + } + + if (i < maxIfs - 1) + { + src += `if(test == ${i}.0){}`; + } + } + + return src; +} diff --git a/packages/core/src/shader/utils/compileProgram.js b/packages/core/src/shader/utils/compileProgram.js new file mode 100644 index 0000000..8c6ccb9 --- /dev/null +++ b/packages/core/src/shader/utils/compileProgram.js @@ -0,0 +1,78 @@ +/** + * @method compileProgram + * @memberof PIXI.glCore.shader + * @param gl {WebGLRenderingContext} The current WebGL context {WebGLProgram} + * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. + * @param fragmentSrc {string|string[]} The fragment shader source as an array of strings. + * @param attributeLocations {Object} An attribute location map that lets you manually set the attribute locations + * @return {WebGLProgram} the shader program + */ +export default function compileProgram(gl, vertexSrc, fragmentSrc, attributeLocations) +{ + const glVertShader = compileShader(gl, gl.VERTEX_SHADER, vertexSrc); + const glFragShader = compileShader(gl, gl.FRAGMENT_SHADER, fragmentSrc); + + let program = gl.createProgram(); + + gl.attachShader(program, glVertShader); + gl.attachShader(program, glFragShader); + + // optionally, set the attributes manually for the program rather than letting WebGL decide.. + if (attributeLocations) + { + for (const i in attributeLocations) + { + gl.bindAttribLocation(program, attributeLocations[i], i); + } + } + + gl.linkProgram(program); + + // if linking fails, then log and cleanup + if (!gl.getProgramParameter(program, gl.LINK_STATUS)) + { + console.error('Pixi.js Error: Could not initialize shader.'); + console.error('gl.VALIDATE_STATUS', gl.getProgramParameter(program, gl.VALIDATE_STATUS)); + console.error('gl.getError()', gl.getError()); + + // if there is a program info log, log it + if (gl.getProgramInfoLog(program) !== '') + { + console.warn('Pixi.js Warning: gl.getProgramInfoLog()', gl.getProgramInfoLog(program)); + } + + gl.deleteProgram(program); + program = null; + } + + // clean up some shaders + gl.deleteShader(glVertShader); + gl.deleteShader(glFragShader); + + return program; +} + +/** + * @private + * @param gl {WebGLRenderingContext} The current WebGL context {WebGLProgram} + * @param type {Number} the type, can be either VERTEX_SHADER or FRAGMENT_SHADER + * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. + * @return {WebGLShader} the shader + */ +function compileShader(gl, type, src) +{ + const shader = gl.createShader(type); + + gl.shaderSource(shader, src); + gl.compileShader(shader); + + if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) + { + console.warn(src); + console.error(gl.getShaderInfoLog(shader)); + + return null; + } + + return shader; +} diff --git a/packages/core/src/shader/utils/defaultValue.js b/packages/core/src/shader/utils/defaultValue.js new file mode 100644 index 0000000..97290bd --- /dev/null +++ b/packages/core/src/shader/utils/defaultValue.js @@ -0,0 +1,79 @@ +/** + * @method defaultValue + * @memberof PIXI.glCore.shader + * @param type {String} Type of value + * @param size {Number} + */ +export default function defaultValue(type, size) +{ + switch (type) + { + case 'float': + return 0; + + case 'vec2': + return new Float32Array(2 * size); + + case 'vec3': + return new Float32Array(3 * size); + + case 'vec4': + return new Float32Array(4 * size); + + case 'int': + case 'sampler2D': + case 'sampler2DArray': + return 0; + + case 'ivec2': + return new Int32Array(2 * size); + + case 'ivec3': + return new Int32Array(3 * size); + + case 'ivec4': + return new Int32Array(4 * size); + + case 'bool': + return false; + + case 'bvec2': + + return booleanArray(2 * size); + + case 'bvec3': + return booleanArray(3 * size); + + case 'bvec4': + return booleanArray(4 * size); + + case 'mat2': + return new Float32Array([1, 0, + 0, 1]); + + case 'mat3': + return new Float32Array([1, 0, 0, + 0, 1, 0, + 0, 0, 1]); + + case 'mat4': + return new Float32Array([1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1]); + } + + return null; +} + +function booleanArray(size) +{ + const array = new Array(size); + + for (let i = 0; i < array.length; i++) + { + array[i] = false; + } + + return array; +} diff --git a/packages/core/src/shader/utils/extractUniformsFromSrc.js b/packages/core/src/shader/utils/extractUniformsFromSrc.js new file mode 100644 index 0000000..3d02c0a --- /dev/null +++ b/packages/core/src/shader/utils/extractUniformsFromSrc.js @@ -0,0 +1,57 @@ +import defaultValue from './defaultValue'; + +export default function extractUniformsFromSrc(vertexSrc, fragmentSrc, mask) +{ + const vertUniforms = extractUniformsFromString(vertexSrc, mask); + const fragUniforms = extractUniformsFromString(fragmentSrc, mask); + + return Object.assign(vertUniforms, fragUniforms); +} + +function extractUniformsFromString(string) +{ + const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); + + const uniforms = {}; + let nameSplit; + + // clean the lines a little - remove extra spaces / teabs etc + // then split along ';' + const lines = string.replace(/\s+/g, ' ') + .split(/\s*;\s*/); + + // loop through.. + for (let i = 0; i < lines.length; i++) + { + const line = lines[i].trim(); + + if (line.indexOf('uniform') > -1) + { + const splitLine = line.split(' '); + const type = splitLine[1]; + + let name = splitLine[2]; + let size = 1; + + if (name.indexOf('[') > -1) + { + // array! + nameSplit = name.split(/\[|]/); + name = nameSplit[0]; + size *= Number(nameSplit[1]); + } + + if (!name.match(maskRegex)) + { + uniforms[name] = { + value: defaultValue(type, size), + dirtyId: 0, + name, + type, + }; + } + } + } + + return uniforms; +} diff --git a/packages/core/src/shader/utils/generateUniformsSync.js b/packages/core/src/shader/utils/generateUniformsSync.js new file mode 100644 index 0000000..98a357f --- /dev/null +++ b/packages/core/src/shader/utils/generateUniformsSync.js @@ -0,0 +1,231 @@ +// cv = CachedValue +// v = value +// ud = uniformData +// uv = uniformValue +// l = loaction +const GLSL_TO_SINGLE_SETTERS_CACHED = { + + float: ` + if(cv !== v) + { + cv.v = v; + gl.uniform1f(location, v) + }`, + + vec2: ` + if(cv[0] !== v[0] || cv[1] !== v[1]) + { + cv[0] = v[0]; + cv[1] = v[1]; + gl.uniform2f(location, v[0], v[1]) + }`, + + vec3: ` + if(cv[0] !== v[0] || cv[1] !== v[1] || cv[2] !== v[2]) + { + cv[0] = v[0]; + cv[1] = v[1]; + cv[2] = v[2]; + + gl.uniform3f(location, v[0], v[1], v[2]) + }`, + + vec4: 'gl.uniform4f(location, v[0], v[1], v[2], v[3])', + + int: 'gl.uniform1i(location, v)', + ivec2: 'gl.uniform2i(location, v[0], v[1])', + ivec3: 'gl.uniform3i(location, v[0], v[1], v[2])', + ivec4: 'gl.uniform4i(location, v[0], v[1], v[2], v[3])', + + bool: 'gl.uniform1i(location, v)', + bvec2: 'gl.uniform2i(location, v[0], v[1])', + bvec3: 'gl.uniform3i(location, v[0], v[1], v[2])', + bvec4: 'gl.uniform4i(location, v[0], v[1], v[2], v[3])', + + mat2: 'gl.uniformMatrix2fv(location, false, v)', + mat3: 'gl.uniformMatrix3fv(location, false, v)', + mat4: 'gl.uniformMatrix4fv(location, false, v)', + + sampler2D: 'gl.uniform1i(location, v)', + samplerCube: 'gl.uniform1i(location, v)', + sampler2DArray: 'gl.uniform1i(location, v)', +}; + +const GLSL_TO_ARRAY_SETTERS = { + + float: `gl.uniform1fv(location, v)`, + + vec2: `gl.uniform2fv(location, v)`, + vec3: `gl.uniform3fv(location, v)`, + vec4: 'gl.uniform4fv(location, v)', + + mat4: 'gl.uniformMatrix4fv(location, false, v)', + mat3: 'gl.uniformMatrix3fv(location, false, v)', + mat2: 'gl.uniformMatrix2fv(location, false, v)', + + int: 'gl.uniform1iv(location, v)', + ivec2: 'gl.uniform2iv(location, v)', + ivec3: 'gl.uniform3iv(location, v)', + ivec4: 'gl.uniform4iv(location, v)', + + bool: 'gl.uniform1iv(location, v)', + bvec2: 'gl.uniform2iv(location, v)', + bvec3: 'gl.uniform3iv(location, v)', + bvec4: 'gl.uniform4iv(location, v)', + + sampler2D: 'gl.uniform1iv(location, v)', + samplerCube: 'gl.uniform1iv(location, v)', + sampler2DArray: 'gl.uniform1iv(location, v)', +}; + +export default function generateUniformsSync(group, uniformData) +{ + let textureCount = 0; + let func = `var v = null; + var cv = null + var gl = renderer.gl`; + + for (const i in group.uniforms) + { + const data = uniformData[i]; + + if (!data) + { + if (group.uniforms[i].group) + { + func += ` + renderer.shader.syncUniformGroup(uv.${i}); + `; + } + + continue; + } + + // TODO && uniformData[i].value !== 0 <-- do we still need this? + if (data.type === 'float' && data.size === 1) + { + func += ` + if(uv.${i} !== ud.${i}.value) + { + ud.${i}.value = uv.${i} + gl.uniform1f(ud.${i}.location, uv.${i}) + }\n`; + } + /* eslint-disable max-len */ + else if ((data.type === 'sampler2D' || data.type === 'samplerCube' || data.type === 'sampler2DArray') && data.size === 1 && !data.isArray) + /* eslint-disable max-len */ + { + func += ` + renderer.texture.bind(uv.${i}, ${textureCount}); + + if(ud.${i}.value !== ${textureCount}) + { + ud.${i}.value = ${textureCount}; + gl.uniform1i(ud.${i}.location, ${textureCount});\n; // eslint-disable-line max-len + }\n`; + + textureCount++; + } + else if (data.type === 'mat3' && data.size === 1) + { + if (group.uniforms[i].a !== undefined) + { + // TODO and some smart caching dirty ids here! + func += ` + gl.uniformMatrix3fv(ud.${i}.location, false, uv.${i}.toArray(true)); + \n`; + } + else + { + func += ` + gl.uniformMatrix3fv(ud.${i}.location, false, uv.${i}); + \n`; + } + } + else if (data.type === 'vec2' && data.size === 1) + { + // TODO - do we need both here? + // maybe we can get away with only using points? + if (group.uniforms[i].x !== undefined) + { + func += ` + cv = ud.${i}.value; + v = uv.${i}; + + if(cv[0] !== v.x || cv[1] !== v.y) + { + cv[0] = v.x; + cv[1] = v.y; + gl.uniform2f(ud.${i}.location, v.x, v.y); + }\n`; + } + else + { + func += ` + cv = ud.${i}.value; + v = uv.${i}; + + if(cv[0] !== v[0] || cv[1] !== v[1]) + { + cv[0] = v[0]; + cv[1] = v[1]; + gl.uniform2f(ud.${i}.location, v[0], v[1]); + } + \n`; + } + } + else if (data.type === 'vec4' && data.size === 1) + { + // TODO - do we need both here? + // maybe we can get away with only using points? + if (group.uniforms[i].width !== undefined) + { + func += ` + cv = ud.${i}.value; + v = uv.${i}; + + if(cv[0] !== v.x || cv[1] !== v.y || cv[2] !== v.width || cv[3] !== v.height) + { + cv[0] = v.x; + cv[1] = v.y; + cv[2] = v.width; + cv[3] = v.height; + gl.uniform4f(ud.${i}.location, v.x, v.y, v.width, v.height) + }\n`; + } + else + { + func += ` + cv = ud.${i}.value; + v = uv.${i}; + + if(cv[0] !== v[0] || cv[1] !== v[1] || cv[2] !== v[2] || cv[3] !== v[3]) + { + cv[0] = v[0]; + cv[1] = v[1]; + cv[2] = v[2]; + cv[3] = v[3]; + + gl.uniform4f(ud.${i}.location, v[0], v[1], v[2], v[3]) + } + \n`; + } + } + else + { + const templateType = (data.size === 1) ? GLSL_TO_SINGLE_SETTERS_CACHED : GLSL_TO_ARRAY_SETTERS; + + const template = templateType[data.type].replace('location', `ud.${i}.location`); + + func += ` + cv = ud.${i}.value; + v = uv.${i}; + ${template};\n`; + } + } + + // console.log(' --------------- ') + // console.log(func); + + return new Function('ud', 'uv', 'renderer', func); // eslint-disable-line no-new-func +} diff --git a/packages/core/src/AbstractRenderer.js b/packages/core/src/AbstractRenderer.js new file mode 100644 index 0000000..64b2eb1 --- /dev/null +++ b/packages/core/src/AbstractRenderer.js @@ -0,0 +1,353 @@ +import { hex2string, hex2rgb } from '@pixi/utils'; +import { Matrix, Rectangle } from '@pixi/math'; +import { RENDERER_TYPE } from '@pixi/constants'; +import { settings } from '@pixi/settings'; +import { Container } from '@pixi/display'; +import RenderTexture from './renderTexture/RenderTexture'; +import EventEmitter from 'eventemitter3'; + +const tempMatrix = new Matrix(); + +/** + * 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 EventEmitter + * @memberof PIXI + */ +export default class AbstractRenderer extends EventEmitter +{ + // eslint-disable-next-line valid-jsdoc + /** + * @param {string} system - The name of the system this renderer is for. + * @param {object} [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.transparent=false] - If the render view is transparent, default false + * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false + * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. The + * resolution of the renderer retina would be 2. + * @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 {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when rendering, + * stopping pixel interpolation. + */ + constructor(system, options, arg2, arg3) + { + super(); + + // Support for constructor(system, screenWidth, screenHeight, options) + if (typeof options === 'number') + { + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + }, arg3); + } + + // 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 = RENDERER_TYPE.UNKNOWN; + + /** + * Measurements of the screen. (0, 0, screenWidth, screenHeight) + * + * Its safe to use as filterArea or hitArea for 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 1 + */ + this.resolution = options.resolution || settings.RESOLUTION; + + /** + * Whether the render view is transparent + * + * @member {boolean} + */ + this.transparent = options.transparent; + + /** + * Whether css dimensions of canvas view should be resized to screen dimensions automatically + * + * @member {boolean} + */ + this.autoResize = options.autoResize || false; + + /** + * Tracks the blend modes useful for this renderer. + * + * @member {object} + */ + this.blendModes = null; + + /** + * 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; + + /** + * If true PixiJS will Math.floor() x/y values when rendering, stopping pixel interpolation. + * Handy for crisp pixel art and speed on legacy devices. + * + * @member {boolean} + */ + this.roundPixels = options.roundPixels; + + /** + * The background color as a number. + * + * @member {number} + * @private + */ + this._backgroundColor = 0x000000; + + /** + * The background color as an [R, G, B] array. + * + * @member {number[]} + * @private + */ + this._backgroundColorRgba = [0, 0, 0, 0]; + + /** + * The background color as a string. + * + * @member {string} + * @private + */ + this._backgroundColorString = '#000000'; + + this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter + + /** + * This temporary display object used as the parent of the currently being rendered item + * + * @member {PIXI.DisplayObject} + * @private + */ + this._tempDisplayObjectParent = new Container(); + + /** + * The last root object that the renderer tried to render. + * + * @member {PIXI.DisplayObject} + * @private + */ + this._lastObjectRendered = this._tempDisplayObjectParent; + + /** + * Collection of plugins. + * @readonly + * @member {object} + */ + this.plugins = {}; + } + + /** + * Initialize the plugins. + * + * @protected + * @param {object} staticMap - The dictionary of staticly saved plugins. + */ + initPlugins(staticMap) + { + 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() + { + return this.view.width; + } + + /** + * Same as view.height, actual number of pixels in the canvas by vertical + * + * @member {number} + * @readonly + * @default 600 + */ + get height() + { + return this.view.height; + } + + /** + * Resizes the screen and canvas to the specified width and height + * Canvas dimensions are multiplied by resolution + * + * @param {number} screenWidth - the new width of the screen + * @param {number} screenHeight - the new height of the screen + */ + resize(screenWidth, screenHeight) + { + this.screen.width = screenWidth; + this.screen.height = screenHeight; + + this.view.width = screenWidth * this.resolution; + this.view.height = screenHeight * this.resolution; + + if (this.autoResize) + { + this.view.style.width = `${screenWidth}px`; + this.view.style.height = `${screenHeight}px`; + } + } + + /** + * 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 {PIXI.DisplayObject} displayObject - The displayObject the object will be generated from + * @param {number} scaleMode - Should be one of the scaleMode consts + * @param {number} resolution - The resolution / device pixel ratio of the texture being generated + * @param {PIXI.Rectangle} [region] - The region of the displayObject, that shall be rendered, + * if no region is specified, defaults to the local bounds of the displayObject. + * @return {PIXI.Texture} a texture of the graphics object + */ + generateTexture(displayObject, scaleMode, resolution, region) + { + region = region || displayObject.getLocalBounds(); + + // 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(region.width | 0, region.height | 0, scaleMode, resolution); + + tempMatrix.tx = -region.x; + tempMatrix.ty = -region.y; + + this.render(displayObject, renderTexture, false, tempMatrix, true); + + return renderTexture; + } + + /** + * Removes everything from the renderer and optionally removes the Canvas DOM element. + * + * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. + */ + destroy(removeView) + { + 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); + } + + this.plugins = null; + + this.type = RENDERER_TYPE.UNKNOWN; + + this.view = null; + + this.screen = null; + + this.resolution = 0; + + this.transparent = false; + + this.autoResize = false; + + this.blendModes = null; + + this.options = null; + + this.preserveDrawingBuffer = false; + this.clearBeforeRender = false; + + this.roundPixels = false; + + this._backgroundColor = 0; + this._backgroundColorRgba = null; + this._backgroundColorString = null; + + this._tempDisplayObjectParent = null; + this._lastObjectRendered = null; + } + + /** + * The background color to fill if not transparent + * + * @member {number} + */ + get backgroundColor() + { + return this._backgroundColor; + } + + set backgroundColor(value) // eslint-disable-line require-jsdoc + { + this._backgroundColor = value; + this._backgroundColorString = hex2string(value); + hex2rgb(value, this._backgroundColorRgba); + } +} diff --git a/packages/core/src/Renderer.js b/packages/core/src/Renderer.js new file mode 100644 index 0000000..2227e78 --- /dev/null +++ b/packages/core/src/Renderer.js @@ -0,0 +1,338 @@ +import AbstractRenderer from './AbstractRenderer'; +import { sayHello } from '@pixi/utils'; +import MaskSystem from './mask/MaskSystem'; +import StencilSystem from './mask/StencilSystem'; +import FilterSystem from './filters/FilterSystem'; +import FramebufferSystem from './framebuffer/FramebufferSystem'; +import RenderTextureSystem from './renderTexture/RenderTextureSystem'; +import TextureSystem from './textures/TextureSystem'; +import ProjectionSystem from './projection/ProjectionSystem'; +import StateSystem from './state/StateSystem'; +import GeometrySystem from './geometry/GeometrySystem'; +import ShaderSystem from './shader/ShaderSystem'; +import ContextSystem from './context/ContextSystem'; +import BatchSystem from './batch/BatchSystem'; +import TextureGCSystem from './textures/TextureGCSystem'; +import { RENDERER_TYPE } from '@pixi/constants'; +import UniformGroup from './shader/UniformGroup'; +import { Matrix } from '@pixi/math'; +import Runner from 'mini-runner'; + +/** + * 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 Render works by automatically managing webGLBatchs. + * 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 :) + * + * @class + * @memberof PIXI + * @extends PIXI.AbstractRenderer + */ +export default class Renderer extends AbstractRenderer +{ + // eslint-disable-next-line valid-jsdoc + /** + * + * @param {object} [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.transparent=false] - If the render view is transparent, default false + * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false + * @param {boolean} [options.antialias=false] - sets antialias. If not available natively then FXAA + * antialiasing is used + * @param {boolean} [options.forceFXAA=false] - forces FXAA antialiasing to be used over native. + * FXAA is faster, but may not always look as great + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. + * The resolution of the renderer retina would be 2. + * @param {boolean} [options.clearBeforeRender=true] - This sets if the renderer will clear + * the canvas or not before the new render pass. If you wish to set this to false, you *must* set + * preserveDrawingBuffer to `true`. + * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, + * enable this if you need to call toDataUrl on the webgl context. + * @param {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when + * rendering, stopping pixel interpolation. + * @param {number} [options.backgroundColor=0x000000] - The background color of the rendered area + * (shown if not transparent). + * @param {boolean} [options.legacy=false] - If true PixiJS will aim to ensure compatibility + * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. + * @param {string} [options.powerPreference] - Parameter passed to webgl context, set to "high-performance" + * for devices with dual graphics card + */ + constructor(options, arg2, arg3) + { + super('WebGL', options, arg2, arg3); + + /** + * The type of this renderer as a standardised const + * + * @member {number} + * @see PIXI.RENDERER_TYPE + */ + this.type = RENDERER_TYPE.WEBGL; + + // this will be set by the contextSystem (this.context) + this.gl = null; + this.CONTEXT_UID = 0; + this.legacy = !!options.legacy; + + // TODO legacy! + + // runners! + this.runners = { + destroy: new Runner('destroy'), + contextChange: new Runner('contextChange', 1), + reset: new Runner('reset'), + update: new Runner('update'), + postrender: new Runner('postrender'), + prerender: new Runner('prerender'), + resize: new Runner('resize', 2), + }; + + 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(GeometrySystem, 'geometry') + .addSystem(FramebufferSystem, 'framebuffer') + .addSystem(StencilSystem, 'stencil') + .addSystem(ProjectionSystem, 'projection') + .addSystem(TextureGCSystem, 'textureGC') + .addSystem(FilterSystem, 'filter') + .addSystem(RenderTextureSystem, 'renderTexture') + .addSystem(BatchSystem, 'batch'); + + this.initPlugins(Renderer.__plugins); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ + if (options.context) + { + this.context.initFromContext(options.context); + } + else + { + this.context.initFromOptions({ + alpha: this.transparent, + antialias: options.antialias, + premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', + stencil: true, + 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); + } + + /** + * Add a new system to the renderer. + * @param {class} ClassRef - Class reference + * @param {string} [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. + * @return {PIXI.Renderer} Return instance of renderer + */ + addSystem(ClassRef, name) + { + if (!name) + { + name = ClassRef.name; + } + + // TODO - read name from class.name.. + + /* + if(name.includes('System')) + { + name = name.replace('System', ''); + name = name.charAt(0).toLowerCase() + name.slice(1); + } + */ + + const system = new ClassRef(this); + + if (this[name]) + { + throw new Error(`Whoops! ${name} is already a manger`); + } + + this[name] = system; + + for (const i in this.runners) + { + this.runners[i].add(system); + } + + return this; + + /** + * 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. + */ + } + + /** + * Renders the object to its webGL view + * + * @param {PIXI.DisplayObject} displayObject - the object to be rendered + * @param {PIXI.RenderTexture} renderTexture - The render texture to render to. + * @param {boolean} [clear] - Should the canvas be cleared before the new render + * @param {PIXI.Transform} [transform] - A transform to apply to the render texture before rendering. + * @param {boolean} [skipUpdateTransform] - Should we skip the update transform pass? + */ + render(displayObject, renderTexture, clear, transform, skipUpdateTransform) + { + // can be handy to know! + this.renderingToScreen = !renderTexture; + + this.runners.prerender.run(); + this.emit('prerender'); + + // 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.parent; + + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = 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.run(); + + this.emit('postrender'); + } + + /** + * Resizes the webGL view to the specified width and height. + * + * @param {number} screenWidth - the new width of the screen + * @param {number} screenHeight - the new height of the screen + */ + resize(screenWidth, screenHeight) + { + AbstractRenderer.prototype.resize.call(this, screenWidth, screenHeight); + + this.runners.resize.run(screenWidth, screenHeight); + } + + /** + * Resets the WebGL state so you can render things however you fancy! + * + * @return {PIXI.Renderer} Returns itself. + */ + reset() + { + this.runners.reset.run(); + + return this; + } + + clear() + { + this.framebuffer.bind(); + this.framebuffer.clear(); + } + + /** + * Removes everything from the renderer (event listeners, spritebatch, etc...) + * + * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. + * See: https://github.com/pixijs/pixi.js/issues/2233 + */ + destroy(removeView) + { + this.runners.destroy.run(); + + // call base destroy + super.destroy(removeView); + + // TODO nullify all the managers.. + this.gl = null; + } + + /** + * 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 + * builds and excluding plugins. + * @name PIXI.Renderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. + */ + + /** + * Adds a plugin to the renderer. + * + * @method + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + static registerPlugin(pluginName, ctor) + { + Renderer.__plugins = Renderer.__plugins || {}; + Renderer.__plugins[pluginName] = ctor; + } +} diff --git a/packages/core/src/System.js b/packages/core/src/System.js new file mode 100644 index 0000000..05a10c8 --- /dev/null +++ b/packages/core/src/System.js @@ -0,0 +1,42 @@ +/** + * System is a base class used for extending systems used by the {@link PIXI.Renderer} + * @see PIXI.Renderer#addSystem + * @class + * @memberof PIXI + */ +export default class System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + /** + * The renderer this manager works for. + * + * @member {PIXI.Renderer} + */ + this.renderer = renderer; + + this.renderer.runners.contextChange.add(this); + } + + /** + * Generic method called when there is a WebGL context change. + * + */ + contextChange() + { + // do some codes init! + } + + /** + * Generic destroy methods to be overridden by the subclass + * + */ + destroy() + { + this.renderer.runners.contextChange.remove(this); + this.renderer = null; + } +} diff --git a/packages/core/src/batch/BatchSystem.js b/packages/core/src/batch/BatchSystem.js new file mode 100644 index 0000000..b04c249 --- /dev/null +++ b/packages/core/src/batch/BatchSystem.js @@ -0,0 +1,68 @@ +import System from '../System'; +import ObjectRenderer from './ObjectRenderer'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class BatchSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + super(renderer); + + /** + * An empty renderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.emptyRenderer = new ObjectRenderer(renderer); + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.currentRenderer = this.emptyRenderer; + } + + /** + * Changes the current renderer to the one given in parameter + * + * @param {PIXI.ObjectRenderer} objectRenderer - The object renderer to use. + */ + setObjectRenderer(objectRenderer) + { + if (this.currentRenderer === objectRenderer) + { + return; + } + + this.currentRenderer.stop(); + this.currentRenderer = objectRenderer; + + this.renderer.state.setState(objectRenderer.state); + + this.currentRenderer.start(); + } + + /** + * This should be called if you wish to do some custom rendering + * It will basically render anything that may be batched up such as sprites + * + */ + flush() + { + this.setObjectRenderer(this.emptyRenderer); + } + + reset() + { + this.setObjectRenderer(this.emptyRenderer); + } +} diff --git a/packages/core/src/batch/ObjectRenderer.js b/packages/core/src/batch/ObjectRenderer.js new file mode 100644 index 0000000..0c851a9 --- /dev/null +++ b/packages/core/src/batch/ObjectRenderer.js @@ -0,0 +1,48 @@ +import System from '../System'; + +/** + * Base for a common object renderer that can be used as a system renderer plugin. + * + * @class + * @extends PIXI.System + * @memberof PIXI + */ +export default class ObjectRenderer extends System +{ + /** + * Starts the renderer and sets the shader + * + */ + start() + { + // set the shader.. + } + + /** + * Stops the renderer + * + */ + stop() + { + this.flush(); + } + + /** + * Stub method for rendering content and emptying the current batch. + * + */ + flush() + { + // flush! + } + + /** + * Renders an object + * + * @param {PIXI.DisplayObject} object - The object to render. + */ + render(object) // eslint-disable-line no-unused-vars + { + // render the object + } +} diff --git a/packages/core/src/context/ContextSystem.js b/packages/core/src/context/ContextSystem.js new file mode 100644 index 0000000..301aab4 --- /dev/null +++ b/packages/core/src/context/ContextSystem.js @@ -0,0 +1,186 @@ +import System from '../System'; +import { settings } from '@pixi/settings'; + +let CONTEXT_UID = 0; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class ContextSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.webGLVersion = 1; + + this.handleContextLost = this.handleContextLost.bind(this); + this.handleContextRestored = this.handleContextRestored.bind(this); + + this.extensions = {}; + + renderer.view.addEventListener('webglcontextlost', this.handleContextLost, false); + renderer.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + } + + get isLost() + { + return (!this.gl || this.gl.isContextLost()); + } + + contextChange(gl) + { + this.gl = gl; + + // restore a context if it was previously lost + if (gl.isContextLost() && gl.getExtension('WEBGL_lose_context')) + { + gl.getExtension('WEBGL_lose_context').restoreContext(); + } + } + + initFromContext(gl) + { + this.gl = gl; + this.validateContext(gl); + this.renderer.gl = gl; + this.renderer.CONTEXT_UID = CONTEXT_UID++; + this.renderer.runners.contextChange.run(gl); + } + + initFromOptions(options) + { + const gl = this.createContext(this.renderer.view, options); + + this.initFromContext(gl); + } + + /** + * Helper class to create a webGL Context + * + * @class + * @memberof PIXI.glCore + * @param canvas {HTMLCanvasElement} the canvas element that we will get the context from + * @param options {Object} An options object that gets passed in to the canvas element containing the context attributes, + * see https://developer.mozilla.org/en/docs/Web/API/HTMLCanvasElement/getContext for the options available + * @return {WebGLRenderingContext} the WebGL context + */ + createContext(canvas, options) + { + let gl; + + if (settings.PREFER_WEBGL_2) + { + gl = canvas.getContext('webgl2', options); + } + + if (gl) + { + this.webGLVersion = 2; + } + else + { + this.webGLVersion = 1; + + gl = canvas.getContext('webgl', options) + || canvas.getContext('experimental-webgl', options); + + if (!gl) + { + // fail, not able to get a context + throw new Error('This browser does not support webGL. Try using the canvas renderer'); + } + } + + this.gl = gl; + + this.getExtensions(); + + return gl; + } + + getExtensions() + { + // time to set up default etensions that pixi uses.. + const gl = this.gl; + const extensions = this.extensions; + + if (this.webGLVersion === 1) + { + extensions.drawBuffers = gl.getExtension('WEBGL_draw_buffers'); + extensions.depthTexture = gl.getExtension('WEBKIT_WEBGL_depth_texture'); + extensions.floatTexture = gl.getExtension('OES_texture_float'); + extensions.loseContext = gl.getExtension('WEBGL_lose_context'); + + extensions.vertexArrayObject = gl.getExtension('OES_vertex_array_object') + || gl.getExtension('MOZ_OES_vertex_array_object') + || gl.getExtension('WEBKIT_OES_vertex_array_object'); + } + + // we don't use any specific WebGL 2 ones yet! + } + + /** + * Handles a lost webgl context + * + * @private + * @param {WebGLContextEvent} event - The context lost event. + */ + handleContextLost(event) + { + event.preventDefault(); + } + + /** + * Handles a restored webgl context + * + * @private + */ + handleContextRestored() + { + this.renderer.runners.contextChange.run(this.gl); + } + + destroy() + { + const view = this.renderer.view; + + // remove listeners + view.removeEventListener('webglcontextlost', this.handleContextLost); + view.removeEventListener('webglcontextrestored', this.handleContextRestored); + + this.gl.useProgram(null); + + if (this.extensions.loseContext) + { + this.extensions.loseContext.loseContext(); + } + } + + postrender() + { + this.gl.flush(); + } + + validateContext(gl) + { + const attributes = gl.getContextAttributes(); + + // this is going to be fairly simple for now.. but at least we have room to grow! + if (!attributes.stencil) + { + /* eslint-disable max-len */ + + /* eslint-disable no-console */ + console.warn('Provided WebGL context does not have a stencil buffer, masks may not render correctly'); + /* eslint-enable no-console */ + + /* eslint-enable max-len */ + } + } +} diff --git a/packages/core/src/filters/Filter.js b/packages/core/src/filters/Filter.js new file mode 100644 index 0000000..9d8cad8 --- /dev/null +++ b/packages/core/src/filters/Filter.js @@ -0,0 +1,140 @@ +import Shader from '../shader/Shader'; +import Program from '../shader/Program'; +import State from '../state/State'; +import { settings } from '@pixi/settings'; +// import extractUniformsFromSrc from './extractUniformsFromSrc'; +import defaultVertex from './defaultFilter.vert'; +import defaultFragment from './defaultFilter.frag'; + +// let math = require('../../math'); +/** + * @class + * @memberof PIXI + * @extends PIXI.Shader + */ +export default class Filter extends Shader +{ + /** + * @param {string} [vertexSrc] - The source of the vertex shader. + * @param {string} [fragmentSrc] - The source of the fragment shader. + * @param {object} [uniforms] - Custom uniforms to use to augment the built-in ones. + */ + constructor(vertexSrc, fragmentSrc, uniforms) + { + const program = Program.from(vertexSrc, fragmentSrc); + + super(program, uniforms); + + /** + * The padding of the filter. Some filters require extra space to breath such as a blur. + * Increasing this will add extra width and height to the bounds of the object that the + * filter is applied to. + * + * @member {number} + */ + this.padding = 0; + + /** + * The resolution of the filter. Setting this to be lower will lower the quality but + * increase the performance of the filter. + * + * @member {number} + */ + this.resolution = settings.RESOLUTION; + + /** + * If enabled is true the filter is applied, if false it will not. + * + * @member {boolean} + */ + this.enabled = true; + + /** + * If enabled, PixiJS will fit the filter area into boundaries for better performance. + * Switch it off if it does not work for specific shader. + * + * @member {boolean} + */ + this.autoFit = true; + + /** + * Legacy filters use position and uvs from attributes + * @member {boolean} + * @readonly + */ + this.legacy = !!this.program.attributeData.aTextureCoord; + + /** + * the webGL state the filter requires to render + * @member {PIXI.State} + */ + this.state = new State(); + } + + /** + * Applies the filter + * + * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + * @param {boolean} clear - Should the output be cleared before rendering to it + * @param {object} [currentState] - It's current state of filter. + * There are some useful properties in the currentState : + * target, filters, sourceFrame, destinationFrame, renderTarget, resolution + */ + apply(filterManager, input, output, clear, currentState, derp) // eslint-disable-line no-unused-vars + { + // do as you please! + + filterManager.applyFilter(this, input, output, clear, currentState, derp); + + // or just do a regular render.. + } + + /** + * Sets the blendmode of the filter + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL + */ + get blendMode() + { + return this.state.blendMode; + } + + set blendMode(value) // eslint-disable-line require-jsdoc + { + this.state.blendMode = value; + } + + /** + * The default vertex shader source + * + * @static + * @constant + */ + static get defaultVertexSrc() + { + return defaultVertex; + } + + /** + * The default fragment shader source + * + * @static + * @constant + */ + static get defaultFragmentSrc() + { + return defaultFragment; + } +} + +/** + * Used for caching shader IDs + * + * @static + * @private + */ +Filter.SOURCE_KEY_MAP = {}; + diff --git a/packages/core/src/filters/FilterSystem.js b/packages/core/src/filters/FilterSystem.js new file mode 100644 index 0000000..3174318 --- /dev/null +++ b/packages/core/src/filters/FilterSystem.js @@ -0,0 +1,431 @@ +import System from '../System'; + +import RenderTexture from '../renderTexture/RenderTexture'; +import Quad from '../utils/Quad'; +import QuadUv from '../utils/QuadUv'; +import { Rectangle } from '@pixi/math'; +import * as filterTransforms from './filterTransforms'; +import bitTwiddle from 'bit-twiddle'; +import UniformGroup from '../shader/UniformGroup'; +import { DRAW_MODES } from '@pixi/constants'; + +// +/** + * @ignore + * @class + */ +class FilterState +{ + /** + * + */ + constructor() + { + this.renderTexture = null; + this.sourceFrame = new Rectangle(); + this.destinationFrame = new Rectangle(); + this.filters = []; + this.target = null; + this.legacy = false; + this.resolution = 1; + } +} + +/** + * @class + * @memberof PIXI.systems + * @extends PIXI.System + */ +export default class FilterSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + /** + * stores a bunch of PO2 textures used for filtering + * @type {Object} + */ + this.texturePool = {}; + + /** + * a pool for storing filter states, save us creating new ones each tick + * @type {Array} + */ + this.statePool = []; + + /** + * A very simple geometry used when drawing a filter effect to the screen + * @type {Quad} + */ + this.quad = new Quad(); + + this.quadUv = new QuadUv(); + + /** + * Temporary rect for maths + * @type {PIXI.Rectangle} + */ + this.tempRect = new Rectangle(); + + this.activeState = {}; + + /** + * this uniform group is attached to filter uniforms when used + * @type {UniformGroup} + */ + this.globalUniforms = new UniformGroup({ + sourceFrame: this.tempRect, + destinationFrame: this.tempRect, + + // legacy variables + filterArea: new Float32Array(4), + filterClamp: new Float32Array(4), + }, true); + } + + /** + * Adds a new filter to the System. + * + * @param {PIXI.DisplayObject} target - The target of the filter to render. + * @param {PIXI.Filter[]} filters - The filters to apply. + */ + push(target, filters) + { + const renderer = this.renderer; + const filterStack = this.renderer.renderTexture.defaultFilterStack; + const state = this.statePool.pop() || new FilterState(); + + let resolution = filters[0].resolution; + let padding = filters[0].padding; + let autoFit = filters[0].autoFit; + let legacy = filters[0].legacy; + + for (let i = 1; i < filters.length; i++) + { + const filter = filters[i]; + + // lets use the lowest resolution.. + resolution = Math.min(resolution, filter.resolution); + // and the largest amount of padding! + padding = Math.max(padding, filter.padding); + // only auto fit if all filters are autofit + autoFit = autoFit || filter.autoFit; + + legacy = legacy || filter.legacy; + } + + filterStack.push(state); + + state.resolution = resolution; + + state.legacy = legacy; + + // round to whole number based on resolution + // TODO move that to the shader too? + state.sourceFrame = target.filterArea || target.getBounds(true); + + state.sourceFrame.pad(padding); + + if (autoFit) + { + state.sourceFrame.fit(this.renderer.renderTexture.destinationFrame); + } + + state.sourceFrame.round(resolution); + + state.renderTexture = this.getPotFilterTexture(state.sourceFrame.width, state.sourceFrame.height, resolution); + state.filters = filters; + + state.destinationFrame.width = state.renderTexture.width; + state.destinationFrame.height = state.renderTexture.height; + + state.renderTexture.filterFrame = state.sourceFrame; + renderer.renderTexture.bind(state.renderTexture, state.sourceFrame);// /, state.destinationFrame); + renderer.renderTexture.clear(); + } + + /** + * Pops off the filter and applies it. + * + */ + pop() + { + const renderer = this.renderer; + const filterStack = renderer.renderTexture.defaultFilterStack; + const state = filterStack.pop(); + const filters = state.filters; + + this.activeState = state; + + const globalUniforms = this.globalUniforms.uniforms; + + globalUniforms.sourceFrame = state.sourceFrame; + globalUniforms.destinationFrame = state.destinationFrame; + globalUniforms.resolution = state.resolution; + + // only update the rect if its legacy.. + if (state.legacy) + { + const filterArea = globalUniforms.filterArea; + const filterClamp = globalUniforms.filterClamp; + + filterArea[0] = state.destinationFrame.width; + filterArea[1] = state.destinationFrame.height; + filterArea[2] = state.sourceFrame.x; + filterArea[3] = state.sourceFrame.y; + + filterClamp[0] = 0.5 / state.resolution / state.destinationFrame.width; + filterClamp[1] = 0.5 / state.resolution / state.destinationFrame.height; + filterClamp[2] = (state.sourceFrame.width - 0.5) / state.resolution / state.destinationFrame.width; + filterClamp[3] = (state.sourceFrame.height - 0.5) / state.resolution / state.destinationFrame.height; + } + + this.globalUniforms.update(); + + const lastState = filterStack[filterStack.length - 1]; + + if (filters.length === 1) + { + filters[0].apply(this, state.renderTexture, lastState.renderTexture, false, state); + + this.returnFilterTexture(state.renderTexture); + } + else + { + let flip = state.renderTexture; + let flop = this.getPotFilterTexture( + flip.width, + flip.height, + state.resolution + ); + + flop.filterFrame = flip.filterFrame; + + let i = 0; + + for (i = 0; i < filters.length - 1; ++i) + { + filters[i].apply(this, flip, flop, true, state); + + const t = flip; + + flip = flop; + flop = t; + } + + filters[i].apply(this, flip, lastState.renderTexture, false, state); + + this.returnFilterTexture(flip); + this.returnFilterTexture(flop); + } + + this.statePool.push(state); + } + + /** + * Draws a filter. + * + * @param {PIXI.Filter} filter - The filter to draw. + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + * @param {boolean} clear - Should the output be cleared before rendering to it + */ + applyFilter(filter, input, output, clear) + { + const renderer = this.renderer; + + renderer.renderTexture.bind(output, output ? output.filterFrame : null); + + if (clear) + { + // gl.disable(gl.SCISSOR_TEST); + renderer.renderTexture.clear(); + // gl.enable(gl.SCISSOR_TEST); + } + + // set the uniforms.. + filter.uniforms.uSampler = input; + filter.uniforms.filterGlobals = this.globalUniforms; + + // TODO make it so that the order of this does not matter.. + // because it does at the moment cos of global uniforms. + // they need to get resynced + + renderer.state.setState(filter.state); + renderer.shader.bind(filter); + + if (filter.legacy) + { + this.quadUv.map(input._frame, input.filterFrame); + + renderer.geometry.bind(this.quadUv); + renderer.geometry.draw(DRAW_MODES.TRIANGLES); + } + else + { + renderer.geometry.bind(this.quad); + renderer.geometry.draw(DRAW_MODES.TRIANGLE_STRIP); + } + } + + /** + * Calculates the mapped matrix. + * + * TODO playing around here.. this is temporary - (will end up in the shader) + * this returns a matrix that will normalise map filter cords in the filter to screen space + * + * @param {PIXI.Matrix} outputMatrix - the matrix to output to. + * @return {PIXI.Matrix} The mapped matrix. + */ + calculateScreenSpaceMatrix(outputMatrix) + { + const currentState = this.activeState; + + return filterTransforms.calculateScreenSpaceMatrix( + outputMatrix, + currentState.sourceFrame, + currentState.destinationFrame + ); + } + + /** + * This will map the filter coord so that a texture can be used based on the transform of a sprite + * + * @param {PIXI.Matrix} outputMatrix - The matrix to output to. + * @param {PIXI.Sprite} sprite - The sprite to map to. + * @return {PIXI.Matrix} The mapped matrix. + */ + calculateSpriteMatrix(outputMatrix, sprite) + { + const currentState = this.activeState; + + return filterTransforms.calculateSpriteMatrix( + outputMatrix, + currentState.sourceFrame, + currentState.destinationFrame, + sprite + ); + } + + /** + * Destroys this Filter System. + * + * @param {boolean} [contextLost=false] context was lost, do not free shaders + * + */ + destroy(contextLost = false) + { + if (!contextLost) + { + this.emptyPool(); + } + else + { + this.texturePool = {}; + } + } + + /** + * Gets a Power-of-Two render texture. + * + * TODO move to a seperate class could be on renderer? + * also - could cause issue with multiple contexts? + * + * @private + * @param {WebGLRenderingContext} gl - The webgl rendering context + * @param {number} minWidth - The minimum width of the render target. + * @param {number} minHeight - The minimum height of the render target. + * @param {number} resolution - The resolution of the render target. + * @return {PIXI.RenderTarget} The new render target. + */ + getPotFilterTexture(minWidth, minHeight, resolution) + { + minWidth = bitTwiddle.nextPow2(minWidth); + minHeight = bitTwiddle.nextPow2(minHeight); + resolution = resolution || 1; + + const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + + if (!this.texturePool[key]) + { + this.texturePool[key] = []; + } + + let renderTexture = this.texturePool[key].pop(); + + if (!renderTexture) + { + // temporary bypass cache.. + // internally - this will cause a texture to be bound.. + renderTexture = RenderTexture.create({ + width: minWidth, + height: minHeight, + resolution, + }); + } + + return renderTexture; + } + + /** + * Gets extra render texture to use inside current filter + * + * @param {number} resolution resolution of the renderTexture + * @returns {PIXI.RenderTexture} + */ + getFilterTexture(resolution) + { + const rt = this.activeState.renderTexture; + + const filterTexture = this.getPotFilterTexture(rt.width, rt.height, resolution || rt.baseTexture.resolution); + + filterTexture.filterFrame = rt.filterFrame; + + return filterTexture; + } + + /** + * Frees a render texture back into the pool. + * + * @param {PIXI.RenderTarget} renderTexture - The renderTarget to free + */ + returnFilterTexture(renderTexture) + { + renderTexture.filterFrame = null; + + const base = renderTexture.baseTexture; + + const minWidth = base.width; + const minHeight = base.height; + + const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + + this.texturePool[key].push(renderTexture); + } + + /** + * Empties the texture pool. + * + */ + emptyPool() + { + for (const i in this.texturePool) + { + const textures = this.texturePool[i]; + + if (textures) + { + for (let j = 0; j < textures.length; j++) + { + textures[j].destroy(true); + } + } + } + + this.texturePool = {}; + } +} diff --git a/packages/core/src/filters/defaultFilter.frag b/packages/core/src/filters/defaultFilter.frag new file mode 100644 index 0000000..a14c1be --- /dev/null +++ b/packages/core/src/filters/defaultFilter.frag @@ -0,0 +1,22 @@ +varying vec2 vTextureCoord; +varying vec2 vFilterCoord; + +uniform sampler2D uSampler; +uniform sampler2D filterSampler; + +void main(void){ + vec4 masky = texture2D(filterSampler, vFilterCoord); + vec4 sample = texture2D(uSampler, vTextureCoord); + vec4 color; + if(mod(vFilterCoord.x, 1.0) > 0.5) + { + color = vec4(1.0, 0.0, 0.0, 1.0); + } + else + { + color = vec4(0.0, 1.0, 0.0, 1.0); + } + // gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0); + gl_FragColor = mix(sample, masky, 0.5); + gl_FragColor *= sample.a; +} diff --git a/packages/core/src/filters/defaultFilter.vert b/packages/core/src/filters/defaultFilter.vert new file mode 100644 index 0000000..e1febca --- /dev/null +++ b/packages/core/src/filters/defaultFilter.vert @@ -0,0 +1,14 @@ +attribute vec2 aVertexPosition; +attribute vec2 aTextureCoord; + +uniform mat3 projectionMatrix; +uniform mat3 filterMatrix; + +varying vec2 vTextureCoord; +varying vec2 vFilterCoord; + +void main(void){ + gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); + vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy; + vTextureCoord = aTextureCoord ; +} diff --git a/packages/core/src/filters/filterTransforms.js b/packages/core/src/filters/filterTransforms.js new file mode 100644 index 0000000..87a3cd6 --- /dev/null +++ b/packages/core/src/filters/filterTransforms.js @@ -0,0 +1,50 @@ +import { Matrix } from '@pixi/math'; + +/** + * Calculates the mapped matrix + * @param filterArea {Rectangle} The filter area + * @param sprite {Sprite} the target sprite + * @param outputMatrix {Matrix} @alvin + * @private + */ +// this returns a matrix that will normalise map filter cords in the filter to screen space +export function calculateScreenSpaceMatrix(outputMatrix, filterArea, textureSize) +{ + // TODO unwrap? + const mappedMatrix = outputMatrix.identity(); + + mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); + + mappedMatrix.scale(textureSize.width, textureSize.height); + + return mappedMatrix; +} + +export function calculateNormalizedScreenSpaceMatrix(outputMatrix, filterArea, textureSize) +{ + const mappedMatrix = outputMatrix.identity(); + + mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); + + const translateScaleX = (textureSize.width / filterArea.width); + const translateScaleY = (textureSize.height / filterArea.height); + + mappedMatrix.scale(translateScaleX, translateScaleY); + + return mappedMatrix; +} + +// this will map the filter coord so that a texture can be used based on the transform of a sprite +export function calculateSpriteMatrix(outputMatrix, filterArea, textureSize, sprite) +{ + const orig = sprite._texture.orig; + const mappedMatrix = outputMatrix.set(textureSize.width, 0, 0, textureSize.height, filterArea.x, filterArea.y); + const worldTransform = sprite.worldTransform.copyTo(Matrix.TEMP_MATRIX); + + worldTransform.invert(); + mappedMatrix.prepend(worldTransform); + mappedMatrix.scale(1.0 / orig.width, 1.0 / orig.height); + mappedMatrix.translate(sprite.anchor.x, sprite.anchor.y); + + return mappedMatrix; +} diff --git a/packages/core/src/filters/spriteMask/SpriteMaskFilter.js b/packages/core/src/filters/spriteMask/SpriteMaskFilter.js new file mode 100644 index 0000000..91697b7 --- /dev/null +++ b/packages/core/src/filters/spriteMask/SpriteMaskFilter.js @@ -0,0 +1,63 @@ +import Filter from '../Filter'; +import { Matrix } from '@pixi/math'; +import vertex from './spriteMaskFilter.vert'; +import fragment from './spriteMaskFilter.frag'; +import { default as TextureMatrix } from '../../textures/TextureMatrix'; + +/** + * The SpriteMaskFilter class + * + * @class + * @extends PIXI.Filter + * @memberof PIXI + */ +export default class SpriteMaskFilter extends Filter +{ + /** + * @param {PIXI.Sprite} sprite - the target sprite + */ + constructor(sprite) + { + const maskMatrix = new Matrix(); + + super(vertex, fragment); + + sprite.renderable = false; + + this.maskSprite = sprite; + this.maskMatrix = maskMatrix; + } + + /** + * Applies the filter + * + * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + */ + apply(filterManager, input, output) + { + const maskSprite = this.maskSprite; + const tex = this.maskSprite.texture; + + if (!tex.valid) + { + return; + } + if (!tex.transform) + { + // margin = 0.0, let it bleed a bit, shader code becomes easier + // assuming that atlas textures were made with 1-pixel padding + tex.transform = new TextureMatrix(tex, 0.0); + } + tex.transform.update(); + + this.uniforms.mask = tex; + this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite) + .prepend(tex.transform.mapCoord); + this.uniforms.alpha = maskSprite.worldAlpha; + this.uniforms.maskClamp = tex.transform.uClampFrame; + + filterManager.applyFilter(this, input, output); + } +} diff --git a/packages/core/src/filters/spriteMask/spriteMaskFilter.frag b/packages/core/src/filters/spriteMask/spriteMaskFilter.frag new file mode 100644 index 0000000..0e4aef8 --- /dev/null +++ b/packages/core/src/filters/spriteMask/spriteMaskFilter.frag @@ -0,0 +1,23 @@ +varying vec2 vMaskCoord; +varying vec2 vTextureCoord; + +uniform sampler2D uSampler; +uniform sampler2D mask; +uniform float alpha; +uniform vec4 maskClamp; + +void main(void) +{ + float clip = step(3.5, + step(maskClamp.x, vMaskCoord.x) + + step(maskClamp.y, vMaskCoord.y) + + step(vMaskCoord.x, maskClamp.z) + + step(vMaskCoord.y, maskClamp.w)); + + vec4 original = texture2D(uSampler, vTextureCoord); + vec4 masky = texture2D(mask, vMaskCoord); + + original *= (masky.r * masky.a * alpha * clip); + + gl_FragColor = original; +} diff --git a/packages/core/src/filters/spriteMask/spriteMaskFilter.vert b/packages/core/src/filters/spriteMask/spriteMaskFilter.vert new file mode 100644 index 0000000..d23a30c --- /dev/null +++ b/packages/core/src/filters/spriteMask/spriteMaskFilter.vert @@ -0,0 +1,16 @@ +attribute vec2 aVertexPosition; +attribute vec2 aTextureCoord; + +uniform mat3 projectionMatrix; +uniform mat3 otherMatrix; + +varying vec2 vMaskCoord; +varying vec2 vTextureCoord; + +void main(void) +{ + gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); + + vTextureCoord = aTextureCoord; + vMaskCoord = ( otherMatrix * vec3( aTextureCoord, 1.0) ).xy; +} diff --git a/packages/core/src/framebuffer/FrameBuffer.js b/packages/core/src/framebuffer/FrameBuffer.js new file mode 100644 index 0000000..84e9fcc --- /dev/null +++ b/packages/core/src/framebuffer/FrameBuffer.js @@ -0,0 +1,106 @@ +import Texture from '../textures/BaseTexture'; +import { FORMATS, TYPES } from '@pixi/constants'; + +/** + * Frame buffer + * @class + * @memberof PIXI + */ +export default class FrameBuffer +{ + constructor(width, height) + { + this.width = width || 100; + this.height = height || 100; + + this.stencil = false; + this.depth = false; + + this.dirtyId = 0; + this.dirtyFormat = 0; + this.dirtySize = 0; + + this.depthTexture = null; + this.colorTextures = []; + + this.glFrameBuffers = {}; + } + + get colorTexture() + { + return this.colorTextures[0]; + } + + addColorTexture(index, texture) + { + // TODO add some validation to the texture - same width / height etc? + this.colorTextures[index || 0] = texture || new Texture(null, { scaleMode: 0, + resolution: 1, + mipmap: false, + width: this.width, + height: this.height });// || new Texture(); + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + addDepthTexture(texture) + { + /* eslint-disable max-len */ + this.depthTexture = texture || new Texture(null, { scaleMode: 0, + resolution: 1, + width: this.width, + height: this.height, + mipmap: false, + format: FORMATS.DEPTH_COMPONENT, + type: TYPES.UNSIGNED_SHORT });// UNSIGNED_SHORT; + /* eslint-disable max-len */ + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + enableDepth() + { + this.depth = true; + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + enableStencil() + { + this.stencil = true; + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + resize(width, height) + { + if (width === this.width && height === this.height) return; + + this.width = width; + this.height = height; + + this.dirtyId++; + this.dirtySize++; + + for (let i = 0; i < this.colorTextures.length; i++) + { + this.colorTextures[i].setSize(width, height); + } + + if (this.depthTexture) + { + this.depthTexture.setSize(width, height); + } + } +} diff --git a/packages/core/src/framebuffer/FramebufferSystem.js b/packages/core/src/framebuffer/FramebufferSystem.js new file mode 100644 index 0000000..2cc2437 --- /dev/null +++ b/packages/core/src/framebuffer/FramebufferSystem.js @@ -0,0 +1,246 @@ +import System from '../System'; +import { Rectangle } from '@pixi/math'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class FramebufferSystem extends System +{ + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + contextChange() + { + this.gl = this.renderer.gl; + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + this.current = null; + this.viewport = new Rectangle(); + + this.drawBufferExtension = this.renderer.context.extensions.drawBuffers; + } + + bind(framebuffer, frame) + { + const gl = this.gl; + + this.current = framebuffer; + + if (framebuffer) + { + // TODO cacheing layer! + + const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID] || this.initFramebuffer(framebuffer); + + gl.bindFramebuffer(gl.FRAMEBUFFER, fbo.framebuffer); + // makesure all textures are unbound.. + + // now check for updates... + if (fbo.dirtyId !== framebuffer.dirtyId) + { + fbo.dirtyId = framebuffer.dirtyId; + + if (fbo.dirtyFormat !== framebuffer.dirtyFormat) + { + fbo.dirtyFormat = framebuffer.dirtyFormat; + this.updateFramebuffer(framebuffer); + } + else if (fbo.dirtySize !== framebuffer.dirtySize) + { + fbo.dirtySize = framebuffer.dirtySize; + this.resizeFramebuffer(framebuffer); + } + } + + for (let i = 0; i < framebuffer.colorTextures.length; i++) + { + if (framebuffer.colorTextures[i].texturePart) + { + this.renderer.texture.unbind(framebuffer.colorTextures[i].texture); + } + else + { + this.renderer.texture.unbind(framebuffer.colorTextures[i]); + } + } + + if (framebuffer.depthTexture) + { + this.renderer.texture.unbind(framebuffer.depthTexture); + } + + if (frame) + { + this.setViewport(frame.x, frame.y, frame.width, frame.height); + } + else + { + this.setViewport(0, 0, framebuffer.width, framebuffer.height); + } + } + else + { + gl.bindFramebuffer(gl.FRAMEBUFFER, null); + + if (frame) + { + this.setViewport(frame.x, frame.y, frame.width, frame.height); + } + else + { + this.setViewport(0, 0, this.renderer.width, this.renderer.height); + } + } + } + + setViewport(x, y, width, height) + { + const v = this.viewport; + + if (v.width !== width || v.height !== height || v.x !== x || v.y !== y) + { + v.x = x; + v.y = y; + v.width = width; + v.height = height; + + this.gl.viewport(x, y, width, height); + } + } + + get size() + { + if (this.current) + { + // TODO store temp + return { x: 0, y: 0, width: this.current.width, height: this.current.height }; + } + + return { x: 0, y: 0, width: this.renderer.width, height: this.renderer.height }; + } + + clear(r, g, b, a) + { + const gl = this.gl; + + // TODO clear color can be set only one right? + gl.clearColor(r, g, b, a); + gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); + } + + // private functions... + + initFramebuffer(framebuffer) + { + const gl = this.gl; + + // TODO - make this a class? + const fbo = { + framebuffer: gl.createFramebuffer(), + stencil: null, + dirtyId: 0, + dirtyFormat: 0, + dirtySize: 0, + }; + + framebuffer.glFrameBuffers[this.CONTEXT_UID] = fbo; + + return fbo; + } + + resizeFramebuffer(framebuffer) + { + const gl = this.gl; + + if (framebuffer.stencil || framebuffer.depth) + { + gl.bindRenderbuffer(gl.RENDERBUFFER, this.stencil); + gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); + } + } + + updateFramebuffer(framebuffer) + { + const gl = this.gl; + + const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID]; + + // bind the color texture + const colorTextures = framebuffer.colorTextures; + + let count = colorTextures.length; + + if (!this.drawBufferExtension) + { + count = Math.min(count, 1); + } + + const activeTextures = []; + + for (let i = 0; i < count; i++) + { + const texture = framebuffer.colorTextures[i]; + + if (texture.texturePart) + { + this.renderer.texture.bind(texture.texture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.COLOR_ATTACHMENT0 + i, + gl.TEXTURE_CUBE_MAP_NEGATIVE_X + texture.side, + texture.texture._glTextures[this.CONTEXT_UID].texture, + 0); + } + else + { + this.renderer.texture.bind(texture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.COLOR_ATTACHMENT0 + i, + gl.TEXTURE_2D, + texture._glTextures[this.CONTEXT_UID].texture, + 0); + } + + activeTextures.push(gl.COLOR_ATTACHMENT0 + i); + } + + if (this.drawBufferExtension && activeTextures.length > 1) + { + this.drawBufferExtension.drawBuffersWEBGL(activeTextures); + } + + if (framebuffer.depthTexture) + { + const depthTextureExt = this.renderer.context.extensions.depthTexture; + + if (depthTextureExt) + { + const depthTexture = framebuffer.depthTexture; + + this.renderer.texture.bind(depthTexture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.DEPTH_ATTACHMENT, + gl.TEXTURE_2D, + depthTexture._glTextures[this.CONTEXT_UID].texture, + 0); + } + } + + if (framebuffer.stencil || framebuffer.depth) + { + fbo.stencil = gl.createRenderbuffer(); + + gl.bindRenderbuffer(gl.RENDERBUFFER, fbo.stencil); + + // TODO.. this is depth AND stencil? + gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, fbo.stencil); + gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); + // fbo.enableStencil(); + } + } +} diff --git a/packages/core/src/geometry/GLBuffer.js b/packages/core/src/geometry/GLBuffer.js new file mode 100644 index 0000000..0477e8a --- /dev/null +++ b/packages/core/src/geometry/GLBuffer.js @@ -0,0 +1,9 @@ +export default class GLBuffer +{ + constructor(buffer) + { + this.buffer = buffer; + this.updateID = -1; + this.byteLength = -1; + } +} diff --git a/packages/core/src/geometry/GeometrySystem.js b/packages/core/src/geometry/GeometrySystem.js new file mode 100644 index 0000000..959f827 --- /dev/null +++ b/packages/core/src/geometry/GeometrySystem.js @@ -0,0 +1,388 @@ +import System from '../System'; +import GLBuffer from './GLBuffer'; + +const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class GeometrySystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this._activeGeometry = null; + this._activeVao = null; + + this.hasVao = true; + this.hasInstance = true; + } + + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + contextChange() + { + const gl = this.gl = this.renderer.gl; + + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + + // webgl2 + if (!gl.createVertexArray) + { + // webgl 1! + let nativeVaoExtension = this.renderer.context.extensions.vertexArrayObject; + + if (this.renderer.legacy) + { + nativeVaoExtension = null; + } + + if (nativeVaoExtension) + { + gl.createVertexArray = () => + nativeVaoExtension.createVertexArrayOES(); + + gl.bindVertexArray = (vao) => + nativeVaoExtension.bindVertexArrayOES(vao); + + gl.deleteVertexArray = (vao) => + nativeVaoExtension.deleteVertexArrayOES(vao); + } + else + { + this.hasVao = false; + gl.createVertexArray = () => + { + // empty + }; + + gl.bindVertexArray = () => + { + // empty + }; + + gl.deleteVertexArray = () => + { + // empty + }; + } + } + + if (!gl.vertexAttribDivisor) + { + const instanceExt = gl.getExtension('ANGLE_instanced_arrays'); + + if (instanceExt) + { + gl.vertexAttribDivisor = (a, b) => + instanceExt.vertexAttribDivisorANGLE(a, b); + + gl.drawElementsInstanced = (a, b, c, d, e) => + instanceExt.drawElementsInstancedANGLE(a, b, c, d, e); + + gl.drawArraysInstanced = (a, b, c, d) => + instanceExt.drawArraysInstancedANGLE(a, b, c, d); + } + else + { + this.hasInstance = false; + } + } + } + + /** + * Binds geometry so that is can be drawn. Creating a Vao if required + * @private + * @param {PIXI.Geometry} geometry instance of geometry to bind + */ + bind(geometry, shader) + { + shader = shader || this.renderer.shader.shader; + + const gl = this.gl; + + // not sure the best way to address this.. + // currently different shaders require different VAOs for the same geometry + // Still mulling over the best way to solve this one.. + // will likely need to modify the shader attribute locations at run time! + let vaos = geometry.glVertexArrayObjects[this.CONTEXT_UID]; + + if (!vaos) + { + geometry.glVertexArrayObjects[this.CONTEXT_UID] = vaos = {}; + } + + const vao = vaos[shader.program.id] || this.initGeometryVao(geometry, shader.program); + + this._activeGeometry = geometry; + + if (this._activeVao !== vao) + { + this._activeVao = vao; + + if (this.hasVao) + { + gl.bindVertexArray(vao); + } + else + { + this.activateVao(geometry, shader.program); + } + } + + // TODO - optimise later! + // don't need to loop through if nothing changed! + // maybe look to add an 'autoupdate' to geometry? + this.updateBuffers(); + } + + reset() + { + this.unbind(); + } + + updateBuffers() + { + const geometry = this._activeGeometry; + const gl = this.gl; + + for (let i = 0; i < geometry.buffers.length; i++) + { + const buffer = geometry.buffers[i]; + + const glBuffer = buffer._glBuffers[this.CONTEXT_UID]; + + if (buffer._updateID !== glBuffer.updateID) + { + glBuffer.updateID = buffer._updateID; + + // TODO can cache this on buffer! maybe added a getter / setter? + const type = buffer.index ? gl.ELEMENT_ARRAY_BUFFER : gl.ARRAY_BUFFER; + const drawType = buffer.static ? gl.STATIC_DRAW : gl.DYNAMIC_DRAW; + + gl.bindBuffer(type, glBuffer.buffer); + + if (glBuffer.byteLength >= buffer.data.byteLength) + { + // offset is always zero for now! + gl.bufferSubData(type, 0, buffer.data); + } + else + { + gl.bufferData(type, buffer.data, drawType); + } + } + } + } + + checkCompatability(geometry, program) + { + // geometry must have at least all the attributes that the shader requires. + const geometryAttributes = geometry.attributes; + const shaderAttributes = program.attributeData; + + for (const j in shaderAttributes) + { + if (!geometryAttributes[j]) + { + throw new Error(`shader and geometry incompatible, geometry missing the "${j}" attribute`); + } + } + } + + /** + * Creates a Vao with the same structure as the geometry and stores it on the geometry. + * @private + * @param {PIXI.Geometry} geometry instance of geometry to to generate Vao for + */ + initGeometryVao(geometry, program) + { + this.checkCompatability(geometry, program); + + const gl = this.gl; + const CONTEXT_UID = this.CONTEXT_UID; + const buffers = geometry.buffers; + const attributes = geometry.attributes; + + const tempStride = {}; + const tempStart = {}; + + for (const j in buffers) + { + tempStride[j] = 0; + tempStart[j] = 0; + } + + for (const j in attributes) + { + if (!attributes[j].size && program.attributeData[j]) + { + attributes[j].size = program.attributeData[j].size; + } + + tempStride[attributes[j].buffer] += attributes[j].size * byteSizeMap[attributes[j].type]; + } + + for (const j in attributes) + { + const attribute = attributes[j]; + const attribSize = attribute.size; + + if (attribute.stride === undefined) + { + if (tempStride[attribute.buffer] === attribSize * byteSizeMap[attribute.type]) + { + attribute.stride = 0; + } + else + { + attribute.stride = tempStride[attribute.buffer]; + } + } + + if (attribute.start === undefined) + { + attribute.start = tempStart[attribute.buffer]; + + tempStart[attribute.buffer] += attribSize * byteSizeMap[attribute.type]; + } + } + + // first update - and create the buffers! + // only create a gl buffer if it actually gets + for (let i = 0; i < buffers.length; i++) + { + const buffer = buffers[i]; + + if (!buffer._glBuffers[CONTEXT_UID]) + { + buffer._glBuffers[CONTEXT_UID] = new GLBuffer(gl.createBuffer()); + } + } + + // TODO - maybe make this a data object? + // lets wait to see if we need to first! + const vao = gl.createVertexArray(); + + gl.bindVertexArray(vao); + + this.activateVao(geometry, program); + + gl.bindVertexArray(null); + + // add it to the cache! + geometry.glVertexArrayObjects[this.CONTEXT_UID][program.id] = vao; + + return vao; + } + + activateVao(geometry, program) + { + const gl = this.gl; + const CONTEXT_UID = this.CONTEXT_UID; + const buffers = geometry.buffers; + const attributes = geometry.attributes; + + if (geometry.indexBuffer) + { + // first update the index buffer if we have one.. + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, geometry.indexBuffer._glBuffers[CONTEXT_UID].buffer); + } + + let lastBuffer = null; + + // add a new one! + for (const j in attributes) + { + const attribute = attributes[j]; + const buffer = buffers[attribute.buffer]; + const glBuffer = buffer._glBuffers[CONTEXT_UID]; + + if (program.attributeData[j]) + { + if (lastBuffer !== glBuffer) + { + gl.bindBuffer(gl.ARRAY_BUFFER, glBuffer.buffer); + + lastBuffer = glBuffer; + } + + const location = program.attributeData[j].location; + + // TODO introduce state again + // we can optimise this for older devices that have no VAOs + gl.enableVertexAttribArray(location); + + gl.vertexAttribPointer(location, + attribute.size, + attribute.type || gl.FLOAT, + attribute.normalized, + attribute.stride, + attribute.start); + + if (attribute.instance) + { + // TODO calculate instance count based of this... + if (this.hasInstance) + { + gl.vertexAttribDivisor(location, 1); + } + else + { + throw new Error('geometry error, GPU Instancing is not supported on this device'); + } + } + } + } + } + + draw(type, size, start, instanceCount) + { + const gl = this.gl; + const geometry = this._activeGeometry; + + // TODO.. this should not change so maybe cache the function? + + if (geometry.indexBuffer) + { + if (geometry.instanced) + { + /* eslint-disable max-len */ + gl.drawElementsInstanced(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2, instanceCount || 1); + /* eslint-enable max-len */ + } + else + { + gl.drawElements(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2); + } + } + else + if (geometry.instanced) + { + // TODO need a better way to calculate size.. + gl.drawArraysInstanced(type, start, size || geometry.getSize(), instanceCount || 1); + } + else + { + gl.drawArrays(type, start, size || geometry.getSize()); + } + + return this; + } + + unbind() + { + this.gl.bindVertexArray(null); + this._activeVao = null; + this._activeGeometry = null; + } +} diff --git a/packages/core/src/geometry/utils/setVertexAttribArrays.js b/packages/core/src/geometry/utils/setVertexAttribArrays.js new file mode 100644 index 0000000..23b6959 --- /dev/null +++ b/packages/core/src/geometry/utils/setVertexAttribArrays.js @@ -0,0 +1,54 @@ +// var GL_MAP = {}; + +/** + * @param gl {WebGLRenderingContext} The current WebGL context + * @param attribs {*} + * @param state {*} + */ +export default function setVertexAttribArrays(gl, attribs, state) +{ + let i; + + if (state) + { + const tempAttribState = state.tempAttribState; + const attribState = state.attribState; + + for (i = 0; i < tempAttribState.length; i++) + { + tempAttribState[i] = false; + } + + // set the new attribs + for (i = 0; i < attribs.length; i++) + { + tempAttribState[attribs[i].attribute.location] = true; + } + + for (i = 0; i < attribState.length; i++) + { + if (attribState[i] !== tempAttribState[i]) + { + attribState[i] = tempAttribState[i]; + + if (state.attribState[i]) + { + gl.enableVertexAttribArray(i); + } + else + { + gl.disableVertexAttribArray(i); + } + } + } + } + else + { + for (i = 0; i < attribs.length; i++) + { + const attrib = attribs[i]; + + gl.enableVertexAttribArray(attrib.attribute.location); + } + } +} diff --git a/packages/core/src/index.js b/packages/core/src/index.js index 58c456a..fd1f547 100644 --- a/packages/core/src/index.js +++ b/packages/core/src/index.js @@ -1,30 +1,30 @@ import * as resources from './textures/resources'; -import * as systems from './renderers/systems'; +import * as systems from './systems'; -export { resources }; export { systems }; +export { resources }; -export { default as Renderer } from './renderers/Renderer'; -export { default as AbstractRenderer } from './renderers/AbstractRenderer'; -export { default as FrameBuffer } from './textures/FrameBuffer'; +export { default as System } from './System'; +export { default as Renderer } from './Renderer'; +export { default as AbstractRenderer } from './AbstractRenderer'; +export { default as FrameBuffer } from './framebuffer/FrameBuffer'; export { default as CubeTexture } from './textures/CubeTexture'; export { default as BaseTexture } from './textures/BaseTexture'; export { default as Texture } from './textures/Texture'; export { default as TextureMatrix } from './textures/TextureMatrix'; -export { default as RenderTexture } from './textures/RenderTexture'; -export { default as BaseRenderTexture } from './textures/BaseRenderTexture'; +export { default as RenderTexture } from './renderTexture/RenderTexture'; +export { default as BaseRenderTexture } from './renderTexture/BaseRenderTexture'; export { default as TextureUvs } from './textures/TextureUvs'; -export { default as State } from './renderers/State'; -export { default as ObjectRenderer } from './renderers/utils/ObjectRenderer'; -export { default as Quad } from './renderers/utils/Quad'; -export { default as QuadUv } from './renderers/utils/QuadUv'; -export { default as checkMaxIfStatmentsInShader } from './renderers/utils/checkMaxIfStatmentsInShader'; +export { default as State } from './state/State'; +export { default as ObjectRenderer } from './batch/ObjectRenderer'; +export { default as Quad } from './utils/Quad'; +export { default as QuadUv } from './utils/QuadUv'; +export { default as checkMaxIfStatmentsInShader } from './shader/utils/checkMaxIfStatmentsInShader'; export { default as Shader } from './shader/Shader'; export { default as Program } from './shader/Program'; export { default as UniformGroup } from './shader/UniformGroup'; -export { default as SpriteMaskFilter } from './renderers/filters/spriteMask/SpriteMaskFilter'; -export { default as Filter } from './renderers/filters/Filter'; +export { default as SpriteMaskFilter } from './filters/spriteMask/SpriteMaskFilter'; +export { default as Filter } from './filters/Filter'; export { default as Attribute } from './geometry/Attribute'; export { default as Buffer } from './geometry/Buffer'; export { default as Geometry } from './geometry/Geometry'; - diff --git a/packages/core/src/mask/MaskSystem.js b/packages/core/src/mask/MaskSystem.js new file mode 100644 index 0000000..99174c2 --- /dev/null +++ b/packages/core/src/mask/MaskSystem.js @@ -0,0 +1,199 @@ +import System from '../System'; +import AlphaMaskFilter from '../filters/spriteMask/SpriteMaskFilter'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class MaskSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + // TODO - we don't need both! + this.scissor = false; + this.scissorData = null; + this.scissorRenderTarget = null; + + this.enableScissor = false; + + this.alphaMaskPool = []; + this.alphaMaskIndex = 0; + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.DisplayObject} target - Display Object to push the mask to + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + push(target, maskData) + { + // TODO the root check means scissor rect will not + // be used on render textures more info here: + // https://github.com/pixijs/pixi.js/pull/3545 + + if (maskData.texture) + { + this.pushSpriteMask(target, maskData); + } + else if (this.enableScissor + && !this.scissor + && this.renderer._activeRenderTarget.root + && !this.renderer.stencil.stencilMaskStack.length + && maskData.isFastRect()) + { + const matrix = maskData.worldTransform; + + let rot = Math.atan2(matrix.b, matrix.a); + + // use the nearest degree! + rot = Math.round(rot * (180 / Math.PI)); + + if (rot % 90) + { + this.pushStencilMask(maskData); + } + else + { + this.pushScissorMask(target, maskData); + } + } + else + { + this.pushStencilMask(maskData); + } + } + + /** + * Removes the last mask from the mask stack and doesn't return it. + * + * @param {PIXI.DisplayObject} target - Display Object to pop the mask from + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + pop(target, maskData) + { + if (maskData.texture) + { + this.popSpriteMask(target, maskData); + } + else if (this.enableScissor && !this.renderer.stencil.stencilMaskStack.length) + { + this.popScissorMask(target, maskData); + } + else + { + this.popStencilMask(target, maskData); + } + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.RenderTarget} target - Display Object to push the sprite mask to + * @param {PIXI.Sprite} maskData - Sprite to be used as the mask + */ + pushSpriteMask(target, maskData) + { + let alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; + + if (!alphaMaskFilter) + { + alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; + } + + alphaMaskFilter[0].resolution = this.renderer.resolution; + alphaMaskFilter[0].maskSprite = maskData; + + // TODO - may cause issues! + target.filterArea = maskData.getBounds(true); + + this.renderer.filterSystem.pushFilter(target, alphaMaskFilter); + + this.alphaMaskIndex++; + } + + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popSpriteMask() + { + this.renderer.filterSystem.popFilter(); + this.alphaMaskIndex--; + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + pushStencilMask(maskData) + { + this.renderer.batch.flush(); + this.renderer.stencil.pushStencil(maskData); + } + + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popStencilMask() + { + // this.renderer.currentRenderer.stop(); + this.renderer.stencil.popStencil(); + } + + /** + * + * @param {PIXI.DisplayObject} target - Display Object to push the mask to + * @param {PIXI.Graphics} maskData - The masking data. + */ + pushScissorMask(target, maskData) + { + maskData.renderable = true; + + const renderTarget = this.renderer._activeRenderTarget; + + const bounds = maskData.getBounds(); + + bounds.fit(renderTarget.size); + maskData.renderable = false; + + this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); + + const resolution = this.renderer.resolution; + + this.renderer.gl.scissor( + bounds.x * resolution, + (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, + bounds.width * resolution, + bounds.height * resolution + ); + + this.scissorRenderTarget = renderTarget; + this.scissorData = maskData; + this.scissor = true; + } + + /** + * + * + */ + popScissorMask() + { + this.scissorRenderTarget = null; + this.scissorData = null; + this.scissor = false; + + // must be scissor! + const gl = this.renderer.gl; + + gl.disable(gl.SCISSOR_TEST); + } +} diff --git a/packages/core/src/mask/StencilSystem.js b/packages/core/src/mask/StencilSystem.js new file mode 100644 index 0000000..62796a5 --- /dev/null +++ b/packages/core/src/mask/StencilSystem.js @@ -0,0 +1,133 @@ +import System from '../System'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class StencilSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + this.stencilMaskStack = []; + } + + /** + * Changes the mask stack that is used by this System. + * + * @param {PIXI.Graphics[]} stencilMaskStack - The mask stack + */ + setMaskStack(stencilMaskStack) + { + const gl = this.renderer.gl; + + if (stencilMaskStack.length !== this.stencilMaskStack.length) + { + if (stencilMaskStack.length === 0) + { + gl.disable(gl.STENCIL_TEST); + } + else + { + gl.enable(gl.STENCIL_TEST); + } + } + + this.stencilMaskStack = stencilMaskStack; + } + + /** + * Applies the Mask and adds it to the current stencil stack. @alvin + * + * @param {PIXI.Graphics} graphics - The mask + */ + pushStencil(graphics) + { + this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); + + // this.renderer._activeRenderTarget.attachStencilBuffer(); + + const gl = this.renderer.gl; + const prevMaskCount = this.stencilMaskStack.length; + + if (prevMaskCount === 0) + { + gl.enable(gl.STENCIL_TEST); + } + + this.stencilMaskStack.push(graphics); + + // Increment the refference stencil value where the new mask overlaps with the old ones. + gl.colorMask(false, false, false, false); + gl.stencilFunc(gl.EQUAL, prevMaskCount, this._getBitwiseMask()); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.INCR); + this.renderer.plugins.graphics.render(graphics); + + this._useCurrent(); + } + + /** + * Removes the last mask from the stencil stack. @alvin + */ + popStencil() + { + this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); + + const gl = this.renderer.gl; + const graphics = this.stencilMaskStack.pop(); + + if (this.stencilMaskStack.length === 0) + { + // the stack is empty! + gl.disable(gl.STENCIL_TEST); + gl.clear(gl.STENCIL_BUFFER_BIT); + gl.clearStencil(0); + } + else + { + // Decrement the refference stencil value where the popped mask overlaps with the other ones + gl.colorMask(false, false, false, false); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.DECR); + this.renderer.plugins.graphics.render(graphics); + + this._useCurrent(); + } + } + + /** + * Setup renderer to use the current stencil data. + */ + _useCurrent() + { + const gl = this.renderer.gl; + + gl.colorMask(true, true, true, true); + gl.stencilFunc(gl.EQUAL, this.stencilMaskStack.length, this._getBitwiseMask()); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP); + } + + /** + * Fill 1s equal to the number of acitve stencil masks. + * + * @return {number} The bitwise mask. + */ + _getBitwiseMask() + { + return (1 << this.stencilMaskStack.length) - 1; + } + + /** + * Destroys the mask stack. + * + */ + destroy() + { + super.destroy(this); + + this.stencilMaskStack = null; + } +} diff --git a/packages/core/src/projection/ProjectionSystem.js b/packages/core/src/projection/ProjectionSystem.js new file mode 100644 index 0000000..6e63b73 --- /dev/null +++ b/packages/core/src/projection/ProjectionSystem.js @@ -0,0 +1,73 @@ +import System from '../System'; +import { Matrix } from '@pixi/math'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class ProjectionSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.projectionMatrix = new Matrix(); + } + + update(destinationFrame, sourceFrame, resolution, root) + { + this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; + this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; + + this.calculateProjection(this.destinationFrame, this.sourceFrame, resolution, root); + + this.renderer.globalUniforms.uniforms.projectionMatrix = this.projectionMatrix; + this.renderer.globalUniforms.update(); + } + + /** + * Updates the projection matrix based on a projection frame (which is a rectangle) + * + * @param {Rectangle} destinationFrame - The destination frame. + * @param {Rectangle} sourceFrame - The source frame. + */ + calculateProjection(destinationFrame, sourceFrame, resolution, root) + { + const pm = this.projectionMatrix; + + // I don't think we will need this line.. + // pm.identity(); + + if (!root) + { + pm.a = (1 / destinationFrame.width * 2) * resolution; + pm.d = (1 / destinationFrame.height * 2) * resolution; + + pm.tx = -1 - (sourceFrame.x * pm.a); + pm.ty = -1 - (sourceFrame.y * pm.d); + } + else + { + pm.a = (1 / destinationFrame.width * 2) * resolution; + pm.d = (-1 / destinationFrame.height * 2) * resolution; + + pm.tx = -1 - (sourceFrame.x * pm.a); + pm.ty = 1 - (sourceFrame.y * pm.d); + } + } + + /** + * Sets the transform of the active render target to the given matrix + * + * @param {PIXI.Matrix} matrix - The transformation matrix + */ + setTransform()// matrix) + { + // this._activeRenderTarget.transform = matrix; + } +} diff --git a/packages/core/src/renderTexture/BaseRenderTexture.js b/packages/core/src/renderTexture/BaseRenderTexture.js new file mode 100644 index 0000000..88eb83b --- /dev/null +++ b/packages/core/src/renderTexture/BaseRenderTexture.js @@ -0,0 +1,137 @@ +import BaseTexture from '../textures/BaseTexture'; +import FrameBuffer from '../framebuffer/FrameBuffer'; + +/** + * A BaseRenderTexture is a special texture that allows any PixiJS display object to be rendered to it. + * + * __Hint__: All DisplayObjects (i.e. Sprites) that render to a BaseRenderTexture should be preloaded + * otherwise black rectangles will be drawn instead. + * + * A BaseRenderTexture takes a snapshot of any Display Object given to its render method. The position + * and rotation of the given Display Objects is ignored. For example: + * + * ```js + * let renderer = PIXI.autoDetectRenderer(1024, 1024, { view: canvas, ratio: 1 }); + * let baseRenderTexture = new PIXI.BaseRenderTexture(renderer, 800, 600); + * let sprite = PIXI.Sprite.fromImage("spinObj_01.png"); + * + * sprite.position.x = 800/2; + * sprite.position.y = 600/2; + * sprite.anchor.x = 0.5; + * sprite.anchor.y = 0.5; + * + * baseRenderTexture.render(sprite); + * ``` + * + * The Sprite in this case will be rendered using its local transform. To render this sprite at 0,0 + * you can clear the transform + * + * ```js + * + * sprite.setTransform() + * + * let baseRenderTexture = new PIXI.BaseRenderTexture(100, 100); + * let renderTexture = new PIXI.RenderTexture(baseRenderTexture); + * + * renderer.render(sprite, renderTexture); // Renders to center of RenderTexture + * ``` + * + * @class + * @extends PIXI.BaseTexture + * @memberof PIXI + */ +export default class BaseRenderTexture extends BaseTexture +{ + /** + * @param {object} [options] + * @param {number} [options.width=100] - The width of the base render texture + * @param {number} [options.height=100] - The height of the base render texture + * @param {PIXI.SCALE_MODES} [options.scaleMode] - See {@link PIXI.SCALE_MODES} for possible values + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the texture being generated + */ + constructor(options) + { + if (typeof options === 'number') + { + /* eslint-disable prefer-rest-params */ + // Backward compatibility of signature + const width = arguments[0]; + const height = arguments[1]; + const scaleMode = arguments[2]; + const resolution = arguments[3]; + + options = { width, height, scaleMode, resolution }; + /* eslint-enable prefer-rest-params */ + } + + super(null, options); + + const { width, height } = options || {}; + + // Set defaults + this.mipmap = false; + this.width = Math.ceil(width) || 100; + this.height = Math.ceil(height) || 100; + this.valid = true; + + /** + * A map of renderer IDs to webgl renderTargets + * + * @private + * @member {object} + */ + // this._glRenderTargets = {}; + + /** + * A reference to the canvas render target (we only need one as this can be shared across renderers) + * + * @private + * @member {object} + */ + this._canvasRenderTarget = null; + + this.clearColor = [0, 0, 0, 0]; + + this.frameBuffer = new FrameBuffer(width * this.resolution, height * this.resolution) + .addColorTexture(0, this); + + // TODO - could this be added the systems? + + /** + * The data structure for the stencil masks + * + * @member {PIXI.Graphics[]} + */ + this.stencilMaskStack = []; + + /** + * The data structure for the filters + * + * @member {PIXI.Graphics[]} + */ + this.filterStack = [{}]; + } + + /** + * Resizes the BaseRenderTexture. + * + * @param {number} width - The width to resize to. + * @param {number} height - The height to resize to. + */ + resize(width, height) + { + width = Math.ceil(width); + height = Math.ceil(height); + this.frameBuffer.resize(width * this.resolution, height * this.resolution); + } + + /** + * Destroys this texture + * + */ + destroy() + { + super.destroy(true); + this.renderer = null; + } +} diff --git a/packages/core/src/renderTexture/RenderTexture.js b/packages/core/src/renderTexture/RenderTexture.js new file mode 100644 index 0000000..f1ecf1f --- /dev/null +++ b/packages/core/src/renderTexture/RenderTexture.js @@ -0,0 +1,153 @@ +import BaseRenderTexture from './BaseRenderTexture'; +import Texture from '../textures/Texture'; + +/** + * A RenderTexture is a special texture that allows any PixiJS display object to be rendered to it. + * + * __Hint__: All DisplayObjects (i.e. Sprites) that render to a RenderTexture should be preloaded + * otherwise black rectangles will be drawn instead. + * + * A RenderTexture takes a snapshot of any Display Object given to its render method. For example: + * + * ```js + * let renderer = PIXI.autoDetectRenderer(1024, 1024, { view: canvas, ratio: 1 }); + * let renderTexture = PIXI.RenderTexture.create(800, 600); + * let sprite = PIXI.Sprite.fromImage("spinObj_01.png"); + * + * sprite.position.x = 800/2; + * sprite.position.y = 600/2; + * sprite.anchor.x = 0.5; + * sprite.anchor.y = 0.5; + * + * renderer.render(sprite, renderTexture); + * ``` + * + * The Sprite in this case will be rendered using its local transform. To render this sprite at 0,0 + * you can clear the transform + * + * ```js + * + * sprite.setTransform() + * + * let renderTexture = new PIXI.RenderTexture.create(100, 100); + * + * renderer.render(sprite, renderTexture); // Renders to center of RenderTexture + * ``` + * + * @class + * @extends PIXI.Texture + * @memberof PIXI + */ +export default class RenderTexture extends Texture +{ + /** + * @param {PIXI.BaseRenderTexture} baseRenderTexture - The renderer used for this RenderTexture + * @param {PIXI.Rectangle} [frame] - The rectangle frame of the texture to show + */ + constructor(baseRenderTexture, frame) + { + // support for legacy.. + let _legacyRenderer = null; + + if (!(baseRenderTexture instanceof BaseRenderTexture)) + { + /* eslint-disable prefer-rest-params, no-console */ + const width = arguments[1]; + const height = arguments[2]; + const scaleMode = arguments[3]; + const resolution = arguments[4]; + + // we have an old render texture.. + console.warn(`Please use RenderTexture.create(${width}, ${height}) instead of the ctor directly.`); + _legacyRenderer = arguments[0]; + /* eslint-enable prefer-rest-params, no-console */ + + frame = null; + baseRenderTexture = new BaseRenderTexture({ + width, + height, + scaleMode, + resolution, + }); + } + + /** + * The base texture object that this texture uses + * + * @member {BaseTexture} + */ + super(baseRenderTexture, frame); + + this.legacyRenderer = _legacyRenderer; + + /** + * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. + * + * @member {boolean} + */ + this.valid = true; + + /** + * FilterSystem temporary storage + * @private + * @member {PIXI.Rectangle} + */ + this.filterFrame = null; + + this._updateUvs(); + } + + /** + * Resizes the RenderTexture. + * + * @param {number} width - The width to resize to. + * @param {number} height - The height to resize to. + * @param {boolean} [resizeBaseTexture=true] - Should the baseTexture.width and height values be resized as well? + */ + resize(width, height, resizeBaseTexture = true) + { + width = Math.ceil(width); + height = Math.ceil(height); + + // TODO - could be not required.. + this.valid = (width > 0 && height > 0); + + this._frame.width = this.orig.width = width; + this._frame.height = this.orig.height = height; + + if (resizeBaseTexture) + { + this.baseTexture.resize(width, height); + } + + this._updateUvs(); + } + + /** + * A short hand way of creating a render texture. + * + * @param {object} [options] - Options + * @param {number} [options.width=100] - The width of the render texture + * @param {number} [options.height=100] - The height of the render texture + * @param {number} [options.scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the texture being generated + * @return {PIXI.RenderTexture} The new render texture + */ + static create(options) + { + // fallback, old-style: create(width, height, scaleMode, resolution) + if (typeof options === 'number') + { + /* eslint-disable prefer-rest-params */ + options = { + width: options, + height: arguments[1], + scaleMode: arguments[2], + resolution: arguments[3], + }; + /* eslint-enable prefer-rest-params */ + } + + return new RenderTexture(new BaseRenderTexture(options)); + } +} diff --git a/packages/core/src/renderTexture/RenderTextureSystem.js b/packages/core/src/renderTexture/RenderTextureSystem.js new file mode 100644 index 0000000..8d0ca61 --- /dev/null +++ b/packages/core/src/renderTexture/RenderTextureSystem.js @@ -0,0 +1,115 @@ +import System from '../System'; +import { Rectangle } from '@pixi/math'; + +const tempRect = new Rectangle(); + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class RenderTextureSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.clearColor = renderer._backgroundColorRgba; + + // TODO moe this property somewhere else! + this.defaultMaskStack = []; + this.defaultFilterStack = [{}]; + + // empty render texture? + this.renderTexture = null; + + this.destinationFrame = new Rectangle(); + } + + bind(renderTexture, sourceFrame, destinationFrame) + { + // TODO - do we want this?? + if (this.renderTexture === renderTexture) return; + this.renderTexture = renderTexture; + + const renderer = this.renderer; + + if (renderTexture) + { + const baseTexture = renderTexture.baseTexture; + + if (!destinationFrame) + { + tempRect.width = baseTexture.realWidth; + tempRect.height = baseTexture.realHeight; + + destinationFrame = tempRect; + } + + if (!sourceFrame) + { + sourceFrame = destinationFrame; + } + + this.renderer.framebuffer.bind(baseTexture.frameBuffer, destinationFrame); + + this.renderer.projection.update(destinationFrame, sourceFrame, baseTexture.resolution, false); + this.renderer.stencil.setMaskStack(baseTexture.stencilMaskStack); + } + else + { + // TODO these validation checks happen deeper down.. + // thing they can be avoided.. + if (!destinationFrame) + { + tempRect.width = renderer.width; + tempRect.height = renderer.height; + + destinationFrame = tempRect; + } + + if (!sourceFrame) + { + sourceFrame = destinationFrame; + } + + renderer.framebuffer.bind(null, destinationFrame); + + // TODO store this.. + this.renderer.projection.update(destinationFrame, sourceFrame, this.renderer.resolution, true); + this.renderer.stencil.setMaskStack(this.defaultMaskStack); + } + + this.destinationFrame.copyFrom(destinationFrame); + } + + /** + * Erases the render texture and fills the drawing area with a colour + * + * @param {number} [clearColor] - The colour + * @return {PIXI.Renderer} Returns itself. + */ + clear(clearColor) + { + if (this.renderTexture) + { + clearColor = clearColor || this.renderTexture.baseTexture.clearColor; + } + else + { + clearColor = clearColor || this.clearColor; + } + + this.renderer.framebuffer.clear(clearColor[0], clearColor[1], clearColor[2], clearColor[3]); + } + + resize()// screenWidth, screenHeight) + { + // resize the root only! + this.bind(null); + } +} diff --git a/packages/core/src/renderers/AbstractRenderer.js b/packages/core/src/renderers/AbstractRenderer.js deleted file mode 100644 index 928670f..0000000 --- a/packages/core/src/renderers/AbstractRenderer.js +++ /dev/null @@ -1,353 +0,0 @@ -import { hex2string, hex2rgb } from '@pixi/utils'; -import { Matrix, Rectangle } from '@pixi/math'; -import { RENDERER_TYPE } from '@pixi/constants'; -import { settings } from '@pixi/settings'; -import { Container } from '@pixi/display'; -import RenderTexture from '../textures/RenderTexture'; -import EventEmitter from 'eventemitter3'; - -const tempMatrix = new Matrix(); - -/** - * 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 EventEmitter - * @memberof PIXI - */ -export default class AbstractRenderer extends EventEmitter -{ - // eslint-disable-next-line valid-jsdoc - /** - * @param {string} system - The name of the system this renderer is for. - * @param {object} [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.transparent=false] - If the render view is transparent, default false - * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false - * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) - * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. The - * resolution of the renderer retina would be 2. - * @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 {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when rendering, - * stopping pixel interpolation. - */ - constructor(system, options, arg2, arg3) - { - super(); - - // Support for constructor(system, screenWidth, screenHeight, options) - if (typeof options === 'number') - { - options = Object.assign({ - width: options, - height: arg2 || settings.RENDER_OPTIONS.height, - }, arg3); - } - - // 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 = RENDERER_TYPE.UNKNOWN; - - /** - * Measurements of the screen. (0, 0, screenWidth, screenHeight) - * - * Its safe to use as filterArea or hitArea for 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 1 - */ - this.resolution = options.resolution || settings.RESOLUTION; - - /** - * Whether the render view is transparent - * - * @member {boolean} - */ - this.transparent = options.transparent; - - /** - * Whether css dimensions of canvas view should be resized to screen dimensions automatically - * - * @member {boolean} - */ - this.autoResize = options.autoResize || false; - - /** - * Tracks the blend modes useful for this renderer. - * - * @member {object} - */ - this.blendModes = null; - - /** - * 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; - - /** - * If true PixiJS will Math.floor() x/y values when rendering, stopping pixel interpolation. - * Handy for crisp pixel art and speed on legacy devices. - * - * @member {boolean} - */ - this.roundPixels = options.roundPixels; - - /** - * The background color as a number. - * - * @member {number} - * @private - */ - this._backgroundColor = 0x000000; - - /** - * The background color as an [R, G, B] array. - * - * @member {number[]} - * @private - */ - this._backgroundColorRgba = [0, 0, 0, 0]; - - /** - * The background color as a string. - * - * @member {string} - * @private - */ - this._backgroundColorString = '#000000'; - - this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter - - /** - * This temporary display object used as the parent of the currently being rendered item - * - * @member {PIXI.DisplayObject} - * @private - */ - this._tempDisplayObjectParent = new Container(); - - /** - * The last root object that the renderer tried to render. - * - * @member {PIXI.DisplayObject} - * @private - */ - this._lastObjectRendered = this._tempDisplayObjectParent; - - /** - * Collection of plugins. - * @readonly - * @member {object} - */ - this.plugins = {}; - } - - /** - * Initialize the plugins. - * - * @protected - * @param {object} staticMap - The dictionary of staticly saved plugins. - */ - initPlugins(staticMap) - { - 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() - { - return this.view.width; - } - - /** - * Same as view.height, actual number of pixels in the canvas by vertical - * - * @member {number} - * @readonly - * @default 600 - */ - get height() - { - return this.view.height; - } - - /** - * Resizes the screen and canvas to the specified width and height - * Canvas dimensions are multiplied by resolution - * - * @param {number} screenWidth - the new width of the screen - * @param {number} screenHeight - the new height of the screen - */ - resize(screenWidth, screenHeight) - { - this.screen.width = screenWidth; - this.screen.height = screenHeight; - - this.view.width = screenWidth * this.resolution; - this.view.height = screenHeight * this.resolution; - - if (this.autoResize) - { - this.view.style.width = `${screenWidth}px`; - this.view.style.height = `${screenHeight}px`; - } - } - - /** - * 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 {PIXI.DisplayObject} displayObject - The displayObject the object will be generated from - * @param {number} scaleMode - Should be one of the scaleMode consts - * @param {number} resolution - The resolution / device pixel ratio of the texture being generated - * @param {PIXI.Rectangle} [region] - The region of the displayObject, that shall be rendered, - * if no region is specified, defaults to the local bounds of the displayObject. - * @return {PIXI.Texture} a texture of the graphics object - */ - generateTexture(displayObject, scaleMode, resolution, region) - { - region = region || displayObject.getLocalBounds(); - - // 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(region.width | 0, region.height | 0, scaleMode, resolution); - - tempMatrix.tx = -region.x; - tempMatrix.ty = -region.y; - - this.render(displayObject, renderTexture, false, tempMatrix, true); - - return renderTexture; - } - - /** - * Removes everything from the renderer and optionally removes the Canvas DOM element. - * - * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. - */ - destroy(removeView) - { - 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); - } - - this.plugins = null; - - this.type = RENDERER_TYPE.UNKNOWN; - - this.view = null; - - this.screen = null; - - this.resolution = 0; - - this.transparent = false; - - this.autoResize = false; - - this.blendModes = null; - - this.options = null; - - this.preserveDrawingBuffer = false; - this.clearBeforeRender = false; - - this.roundPixels = false; - - this._backgroundColor = 0; - this._backgroundColorRgba = null; - this._backgroundColorString = null; - - this._tempDisplayObjectParent = null; - this._lastObjectRendered = null; - } - - /** - * The background color to fill if not transparent - * - * @member {number} - */ - get backgroundColor() - { - return this._backgroundColor; - } - - set backgroundColor(value) // eslint-disable-line require-jsdoc - { - this._backgroundColor = value; - this._backgroundColorString = hex2string(value); - hex2rgb(value, this._backgroundColorRgba); - } -} diff --git a/packages/core/src/renderers/Renderer.js b/packages/core/src/renderers/Renderer.js deleted file mode 100644 index a2020b1..0000000 --- a/packages/core/src/renderers/Renderer.js +++ /dev/null @@ -1,329 +0,0 @@ -import AbstractRenderer from './AbstractRenderer'; -import { sayHello } from '@pixi/utils'; -import MaskSystem from './systems/MaskSystem'; -import StencilSystem from './systems/StencilSystem'; -import FilterSystem from './systems/filter/FilterSystem'; -import FramebufferSystem from './systems/FramebufferSystem'; -import RenderTextureSystem from './systems/RenderTextureSystem'; -import TextureSystem from './systems/textures/TextureSystem'; -import ProjectionSystem from './systems/ProjectionSystem'; -import StateSystem from './systems/StateSystem'; -import GeometrySystem from './systems/geometry/GeometrySystem'; -import ShaderSystem from './systems/shader/ShaderSystem'; -import ContextSystem from './systems/ContextSystem'; -import BatchSystem from './systems/BatchSystem'; -import TextureGCSystem from './systems/textures/TextureGCSystem'; -import { RENDERER_TYPE } from '@pixi/constants'; -import UniformGroup from '../shader/UniformGroup'; -import { Matrix } from '@pixi/math'; -import Runner from 'mini-runner'; - -/** - * 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 Render works by automatically managing webGLBatchs. - * 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 :) - * - * @class - * @memberof PIXI - * @extends PIXI.AbstractRenderer - */ -export default class Renderer extends AbstractRenderer -{ - // eslint-disable-next-line valid-jsdoc - /** - * - * @param {object} [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.transparent=false] - If the render view is transparent, default false - * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false - * @param {boolean} [options.antialias=false] - sets antialias. If not available natively then FXAA - * antialiasing is used - * @param {boolean} [options.forceFXAA=false] - forces FXAA antialiasing to be used over native. - * FXAA is faster, but may not always look as great - * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. - * The resolution of the renderer retina would be 2. - * @param {boolean} [options.clearBeforeRender=true] - This sets if the renderer will clear - * the canvas or not before the new render pass. If you wish to set this to false, you *must* set - * preserveDrawingBuffer to `true`. - * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, - * enable this if you need to call toDataUrl on the webgl context. - * @param {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when - * rendering, stopping pixel interpolation. - * @param {number} [options.backgroundColor=0x000000] - The background color of the rendered area - * (shown if not transparent). - * @param {boolean} [options.legacy=false] - If true PixiJS will aim to ensure compatibility - * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. - * @param {string} [options.powerPreference] - Parameter passed to webgl context, set to "high-performance" - * for devices with dual graphics card - */ - constructor(options, arg2, arg3) - { - super('WebGL', options, arg2, arg3); - - /** - * The type of this renderer as a standardised const - * - * @member {number} - * @see PIXI.RENDERER_TYPE - */ - this.type = RENDERER_TYPE.WEBGL; - - // this will be set by the contextSystem (this.context) - this.gl = null; - this.CONTEXT_UID = 0; - this.legacy = !!options.legacy; - - // TODO legacy! - - // runners! - this.runners = { - destroy: new Runner('destroy'), - contextChange: new Runner('contextChange', 1), - reset: new Runner('reset'), - update: new Runner('update'), - postrender: new Runner('postrender'), - prerender: new Runner('prerender'), - resize: new Runner('resize', 2), - }; - - 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(GeometrySystem, 'geometry') - .addSystem(FramebufferSystem, 'framebuffer') - .addSystem(StencilSystem, 'stencil') - .addSystem(ProjectionSystem, 'projection') - .addSystem(TextureGCSystem, 'textureGC') - .addSystem(FilterSystem, 'filter') - .addSystem(RenderTextureSystem, 'renderTexture') - .addSystem(BatchSystem, 'batch'); - - this.initPlugins(Renderer.__plugins); - - /** - * The options passed in to create a new webgl context. - * - * @member {object} - * @private - */ - if (options.context) - { - this.context.initFromContext(options.context); - } - else - { - this.context.initFromOptions({ - alpha: this.transparent, - antialias: options.antialias, - premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', - stencil: true, - 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); - } - - addSystem(_class, name) - { - if (!name) - { - name = _class.name; - } - - // TODO - read name from class.name.. - - /* - if(name.includes('System')) - { - name = name.replace('System', ''); - name = name.charAt(0).toLowerCase() + name.slice(1); - } - */ - - const system = new _class(this); - - if (this[name]) - { - throw new Error(`Whoops! ${name} is already a manger`); - } - - this[name] = system; - - for (const i in this.runners) - { - this.runners[i].add(system); - } - - return this; - - /** - * 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. - */ - } - - /** - * Renders the object to its webGL view - * - * @param {PIXI.DisplayObject} displayObject - the object to be rendered - * @param {PIXI.RenderTexture} renderTexture - The render texture to render to. - * @param {boolean} [clear] - Should the canvas be cleared before the new render - * @param {PIXI.Transform} [transform] - A transform to apply to the render texture before rendering. - * @param {boolean} [skipUpdateTransform] - Should we skip the update transform pass? - */ - render(displayObject, renderTexture, clear, transform, skipUpdateTransform) - { - // can be handy to know! - this.renderingToScreen = !renderTexture; - - this.runners.prerender.run(); - this.emit('prerender'); - - // 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.parent; - - displayObject.parent = this._tempDisplayObjectParent; - displayObject.updateTransform(); - displayObject.parent = 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.run(); - - this.emit('postrender'); - } - - /** - * Resizes the webGL view to the specified width and height. - * - * @param {number} screenWidth - the new width of the screen - * @param {number} screenHeight - the new height of the screen - */ - resize(screenWidth, screenHeight) - { - AbstractRenderer.prototype.resize.call(this, screenWidth, screenHeight); - - this.runners.resize.run(screenWidth, screenHeight); - } - - /** - * Resets the WebGL state so you can render things however you fancy! - * - * @return {PIXI.Renderer} Returns itself. - */ - reset() - { - this.runners.reset.run(); - - return this; - } - - clear() - { - this.framebuffer.bind(); - this.framebuffer.clear(); - } - - /** - * Removes everything from the renderer (event listeners, spritebatch, etc...) - * - * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. - * See: https://github.com/pixijs/pixi.js/issues/2233 - */ - destroy(removeView) - { - this.runners.destroy.run(); - - // call base destroy - super.destroy(removeView); - - // TODO nullify all the managers.. - this.gl = null; - } - - /** - * 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 - * builds and excluding plugins. - * @name PIXI.Renderer#plugins - * @type {object} - * @readonly - * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. - * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. - * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. - * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. - */ - - /** - * Adds a plugin to the renderer. - * - * @method - * @param {string} pluginName - The name of the plugin. - * @param {Function} ctor - The constructor function or class for the plugin. - */ - static registerPlugin(pluginName, ctor) - { - Renderer.__plugins = Renderer.__plugins || {}; - Renderer.__plugins[pluginName] = ctor; - } -} diff --git a/packages/core/src/renderers/State.js b/packages/core/src/renderers/State.js deleted file mode 100644 index fa3f221..0000000 --- a/packages/core/src/renderers/State.js +++ /dev/null @@ -1,163 +0,0 @@ -/* eslint-disable max-len */ - -const BLEND = 0; -const OFFSET = 1; -const CULLING = 2; -const DEPTH_TEST = 3; -const WINDING = 4; - -/** - * This is a webGL state. It is passed The WebGL StateManager. - * Each mesh renderered may require webGL to be in a different state. - * For example you may want different blend mode or to enable polygon offsets - * - * @class - * @memberof PIXI - */ -export default class State -{ - /** - * - */ - constructor() - { - this.data = 0; - - this.blendMode = 0; - this.polygonOffset = 0; - - this.blend = true; - // this.depthTest = true; - } - - /** - * Activates blending of the computed fragment color values - * - * @member {boolean} - */ - get blend() - { - return !!(this.data & (1 << BLEND)); - } - - set blend(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << BLEND)) !== value) - { - this.data ^= (1 << BLEND); - } - } - - /** - * Activates adding an offset to depth values of polygon's fragments - * - * @member {boolean} - * @default false - */ - get offsets() - { - return !!(this.data & (1 << OFFSET)); - } - - set offsets(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << OFFSET)) !== value) - { - this.data ^= (1 << OFFSET); - } - } - - /** - * Activates culling of polygons. - * - * @member {boolean} - * @default false - */ - get culling() - { - return !!(this.data & (1 << CULLING)); - } - - set culling(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << CULLING)) !== value) - { - this.data ^= (1 << CULLING); - } - } - - /** - * Activates depth comparisons and updates to the depth buffer. - * - * @member {boolean} - * @default false - */ - get depthTest() - { - return !!(this.data & (1 << DEPTH_TEST)); - } - - set depthTest(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << DEPTH_TEST)) !== value) - { - this.data ^= (1 << DEPTH_TEST); - } - } - - /** - * Specifies whether or not front or back-facing polygons can be culled. - * @member {boolean} - * @default false - */ - get clockwiseFrontFace() - { - return !!(this.data & (1 << WINDING)); - } - - set clockwiseFrontFace(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << WINDING)) !== value) - { - this.data ^= (1 << WINDING); - } - } - - /** - * The blend mode to be applied when this state is set. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. - * Setting this mode to anything other than NO_BLEND will automatically switch blending on. - * - * @member {boolean} - * @default PIXI.BLEND_MODES.NORMAL - * @see PIXI.BLEND_MODES - */ - get blendMode() - { - return this._blendMode; - } - - set blendMode(value) // eslint-disable-line require-jsdoc - { - // 17 is NO BLEND - this.blend = (value !== 17); - this._blendMode = value; - } - - /** - * The polygon offset. Setting this property to anything other than 0 will automatically enable poygon offset fill. - * - * @member {number} - * @default 0 - */ - get polygonOffset() - { - return this._polygonOffset; - } - - set polygonOffset(value) // eslint-disable-line require-jsdoc - { - this.offsets = !!value; - this._polygonOffset = value; - } -} - diff --git a/packages/core/src/renderers/filters/Filter.js b/packages/core/src/renderers/filters/Filter.js deleted file mode 100644 index 2073b8d..0000000 --- a/packages/core/src/renderers/filters/Filter.js +++ /dev/null @@ -1,140 +0,0 @@ -import Shader from '../../shader/Shader'; -import Program from '../../shader/Program'; -import State from '../State'; -import { settings } from '@pixi/settings'; -// import extractUniformsFromSrc from './extractUniformsFromSrc'; -import defaultVertex from './defaultFilter.vert'; -import defaultFragment from './defaultFilter.frag'; - -// let math = require('../../math'); -/** - * @class - * @memberof PIXI - * @extends PIXI.Shader - */ -export default class Filter extends Shader -{ - /** - * @param {string} [vertexSrc] - The source of the vertex shader. - * @param {string} [fragmentSrc] - The source of the fragment shader. - * @param {object} [uniforms] - Custom uniforms to use to augment the built-in ones. - */ - constructor(vertexSrc, fragmentSrc, uniforms) - { - const program = Program.from(vertexSrc, fragmentSrc); - - super(program, uniforms); - - /** - * The padding of the filter. Some filters require extra space to breath such as a blur. - * Increasing this will add extra width and height to the bounds of the object that the - * filter is applied to. - * - * @member {number} - */ - this.padding = 0; - - /** - * The resolution of the filter. Setting this to be lower will lower the quality but - * increase the performance of the filter. - * - * @member {number} - */ - this.resolution = settings.RESOLUTION; - - /** - * If enabled is true the filter is applied, if false it will not. - * - * @member {boolean} - */ - this.enabled = true; - - /** - * If enabled, PixiJS will fit the filter area into boundaries for better performance. - * Switch it off if it does not work for specific shader. - * - * @member {boolean} - */ - this.autoFit = true; - - /** - * Legacy filters use position and uvs from attributes - * @member {boolean} - * @readonly - */ - this.legacy = !!this.program.attributeData.aTextureCoord; - - /** - * the webGL state the filter requires to render - * @member {PIXI.State} - */ - this.state = new State(); - } - - /** - * Applies the filter - * - * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from - * @param {PIXI.RenderTarget} input - The input render target. - * @param {PIXI.RenderTarget} output - The target to output to. - * @param {boolean} clear - Should the output be cleared before rendering to it - * @param {object} [currentState] - It's current state of filter. - * There are some useful properties in the currentState : - * target, filters, sourceFrame, destinationFrame, renderTarget, resolution - */ - apply(filterManager, input, output, clear, currentState, derp) // eslint-disable-line no-unused-vars - { - // do as you please! - - filterManager.applyFilter(this, input, output, clear, currentState, derp); - - // or just do a regular render.. - } - - /** - * Sets the blendmode of the filter - * - * @member {number} - * @default PIXI.BLEND_MODES.NORMAL - */ - get blendMode() - { - return this.state.blendMode; - } - - set blendMode(value) // eslint-disable-line require-jsdoc - { - this.state.blendMode = value; - } - - /** - * The default vertex shader source - * - * @static - * @constant - */ - static get defaultVertexSrc() - { - return defaultVertex; - } - - /** - * The default fragment shader source - * - * @static - * @constant - */ - static get defaultFragmentSrc() - { - return defaultFragment; - } -} - -/** - * Used for caching shader IDs - * - * @static - * @private - */ -Filter.SOURCE_KEY_MAP = {}; - diff --git a/packages/core/src/renderers/filters/defaultFilter.frag b/packages/core/src/renderers/filters/defaultFilter.frag deleted file mode 100644 index a14c1be..0000000 --- a/packages/core/src/renderers/filters/defaultFilter.frag +++ /dev/null @@ -1,22 +0,0 @@ -varying vec2 vTextureCoord; -varying vec2 vFilterCoord; - -uniform sampler2D uSampler; -uniform sampler2D filterSampler; - -void main(void){ - vec4 masky = texture2D(filterSampler, vFilterCoord); - vec4 sample = texture2D(uSampler, vTextureCoord); - vec4 color; - if(mod(vFilterCoord.x, 1.0) > 0.5) - { - color = vec4(1.0, 0.0, 0.0, 1.0); - } - else - { - color = vec4(0.0, 1.0, 0.0, 1.0); - } - // gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0); - gl_FragColor = mix(sample, masky, 0.5); - gl_FragColor *= sample.a; -} diff --git a/packages/core/src/renderers/filters/defaultFilter.vert b/packages/core/src/renderers/filters/defaultFilter.vert deleted file mode 100644 index e1febca..0000000 --- a/packages/core/src/renderers/filters/defaultFilter.vert +++ /dev/null @@ -1,14 +0,0 @@ -attribute vec2 aVertexPosition; -attribute vec2 aTextureCoord; - -uniform mat3 projectionMatrix; -uniform mat3 filterMatrix; - -varying vec2 vTextureCoord; -varying vec2 vFilterCoord; - -void main(void){ - gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); - vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy; - vTextureCoord = aTextureCoord ; -} diff --git a/packages/core/src/renderers/filters/filterTransforms.js b/packages/core/src/renderers/filters/filterTransforms.js deleted file mode 100644 index 87a3cd6..0000000 --- a/packages/core/src/renderers/filters/filterTransforms.js +++ /dev/null @@ -1,50 +0,0 @@ -import { Matrix } from '@pixi/math'; - -/** - * Calculates the mapped matrix - * @param filterArea {Rectangle} The filter area - * @param sprite {Sprite} the target sprite - * @param outputMatrix {Matrix} @alvin - * @private - */ -// this returns a matrix that will normalise map filter cords in the filter to screen space -export function calculateScreenSpaceMatrix(outputMatrix, filterArea, textureSize) -{ - // TODO unwrap? - const mappedMatrix = outputMatrix.identity(); - - mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); - - mappedMatrix.scale(textureSize.width, textureSize.height); - - return mappedMatrix; -} - -export function calculateNormalizedScreenSpaceMatrix(outputMatrix, filterArea, textureSize) -{ - const mappedMatrix = outputMatrix.identity(); - - mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); - - const translateScaleX = (textureSize.width / filterArea.width); - const translateScaleY = (textureSize.height / filterArea.height); - - mappedMatrix.scale(translateScaleX, translateScaleY); - - return mappedMatrix; -} - -// this will map the filter coord so that a texture can be used based on the transform of a sprite -export function calculateSpriteMatrix(outputMatrix, filterArea, textureSize, sprite) -{ - const orig = sprite._texture.orig; - const mappedMatrix = outputMatrix.set(textureSize.width, 0, 0, textureSize.height, filterArea.x, filterArea.y); - const worldTransform = sprite.worldTransform.copyTo(Matrix.TEMP_MATRIX); - - worldTransform.invert(); - mappedMatrix.prepend(worldTransform); - mappedMatrix.scale(1.0 / orig.width, 1.0 / orig.height); - mappedMatrix.translate(sprite.anchor.x, sprite.anchor.y); - - return mappedMatrix; -} diff --git a/packages/core/src/renderers/filters/spriteMask/SpriteMaskFilter.js b/packages/core/src/renderers/filters/spriteMask/SpriteMaskFilter.js deleted file mode 100644 index d5a91a4..0000000 --- a/packages/core/src/renderers/filters/spriteMask/SpriteMaskFilter.js +++ /dev/null @@ -1,63 +0,0 @@ -import Filter from '../Filter'; -import { Matrix } from '@pixi/math'; -import vertex from './spriteMaskFilter.vert'; -import fragment from './spriteMaskFilter.frag'; -import { default as TextureMatrix } from '../../../textures/TextureMatrix'; - -/** - * The SpriteMaskFilter class - * - * @class - * @extends PIXI.Filter - * @memberof PIXI - */ -export default class SpriteMaskFilter extends Filter -{ - /** - * @param {PIXI.Sprite} sprite - the target sprite - */ - constructor(sprite) - { - const maskMatrix = new Matrix(); - - super(vertex, fragment); - - sprite.renderable = false; - - this.maskSprite = sprite; - this.maskMatrix = maskMatrix; - } - - /** - * Applies the filter - * - * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from - * @param {PIXI.RenderTarget} input - The input render target. - * @param {PIXI.RenderTarget} output - The target to output to. - */ - apply(filterManager, input, output) - { - const maskSprite = this.maskSprite; - const tex = this.maskSprite.texture; - - if (!tex.valid) - { - return; - } - if (!tex.transform) - { - // margin = 0.0, let it bleed a bit, shader code becomes easier - // assuming that atlas textures were made with 1-pixel padding - tex.transform = new TextureMatrix(tex, 0.0); - } - tex.transform.update(); - - this.uniforms.mask = tex; - this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite) - .prepend(tex.transform.mapCoord); - this.uniforms.alpha = maskSprite.worldAlpha; - this.uniforms.maskClamp = tex.transform.uClampFrame; - - filterManager.applyFilter(this, input, output); - } -} diff --git a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.frag b/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.frag deleted file mode 100644 index 0e4aef8..0000000 --- a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.frag +++ /dev/null @@ -1,23 +0,0 @@ -varying vec2 vMaskCoord; -varying vec2 vTextureCoord; - -uniform sampler2D uSampler; -uniform sampler2D mask; -uniform float alpha; -uniform vec4 maskClamp; - -void main(void) -{ - float clip = step(3.5, - step(maskClamp.x, vMaskCoord.x) + - step(maskClamp.y, vMaskCoord.y) + - step(vMaskCoord.x, maskClamp.z) + - step(vMaskCoord.y, maskClamp.w)); - - vec4 original = texture2D(uSampler, vTextureCoord); - vec4 masky = texture2D(mask, vMaskCoord); - - original *= (masky.r * masky.a * alpha * clip); - - gl_FragColor = original; -} diff --git a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.vert b/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.vert deleted file mode 100644 index d23a30c..0000000 --- a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.vert +++ /dev/null @@ -1,16 +0,0 @@ -attribute vec2 aVertexPosition; -attribute vec2 aTextureCoord; - -uniform mat3 projectionMatrix; -uniform mat3 otherMatrix; - -varying vec2 vMaskCoord; -varying vec2 vTextureCoord; - -void main(void) -{ - gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); - - vTextureCoord = aTextureCoord; - vMaskCoord = ( otherMatrix * vec3( aTextureCoord, 1.0) ).xy; -} diff --git a/packages/core/src/renderers/systems/BatchSystem.js b/packages/core/src/renderers/systems/BatchSystem.js deleted file mode 100644 index 70e9518..0000000 --- a/packages/core/src/renderers/systems/BatchSystem.js +++ /dev/null @@ -1,68 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import ObjectRenderer from '../utils/ObjectRenderer'; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ - -export default class BatchSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this manager works for. - */ - constructor(renderer) - { - super(renderer); - - /** - * An empty renderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.emptyRenderer = new ObjectRenderer(renderer); - - /** - * The currently active ObjectRenderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.currentRenderer = this.emptyRenderer; - } - - /** - * Changes the current renderer to the one given in parameter - * - * @param {PIXI.ObjectRenderer} objectRenderer - The object renderer to use. - */ - setObjectRenderer(objectRenderer) - { - if (this.currentRenderer === objectRenderer) - { - return; - } - - this.currentRenderer.stop(); - this.currentRenderer = objectRenderer; - - this.renderer.state.setState(objectRenderer.state); - - this.currentRenderer.start(); - } - - /** - * This should be called if you wish to do some custom rendering - * It will basically render anything that may be batched up such as sprites - * - */ - flush() - { - this.setObjectRenderer(this.emptyRenderer); - } - - reset() - { - this.setObjectRenderer(this.emptyRenderer); - } -} diff --git a/packages/core/src/renderers/systems/ContextSystem.js b/packages/core/src/renderers/systems/ContextSystem.js deleted file mode 100644 index 43bf827..0000000 --- a/packages/core/src/renderers/systems/ContextSystem.js +++ /dev/null @@ -1,186 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { settings } from '@pixi/settings'; - -let CONTEXT_UID = 0; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class ContextSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this.webGLVersion = 1; - - this.handleContextLost = this.handleContextLost.bind(this); - this.handleContextRestored = this.handleContextRestored.bind(this); - - this.extensions = {}; - - renderer.view.addEventListener('webglcontextlost', this.handleContextLost, false); - renderer.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); - } - - get isLost() - { - return (!this.gl || this.gl.isContextLost()); - } - - contextChange(gl) - { - this.gl = gl; - - // restore a context if it was previously lost - if (gl.isContextLost() && gl.getExtension('WEBGL_lose_context')) - { - gl.getExtension('WEBGL_lose_context').restoreContext(); - } - } - - initFromContext(gl) - { - this.gl = gl; - this.validateContext(gl); - this.renderer.gl = gl; - this.renderer.CONTEXT_UID = CONTEXT_UID++; - this.renderer.runners.contextChange.run(gl); - } - - initFromOptions(options) - { - const gl = this.createContext(this.renderer.view, options); - - this.initFromContext(gl); - } - - /** - * Helper class to create a webGL Context - * - * @class - * @memberof PIXI.glCore - * @param canvas {HTMLCanvasElement} the canvas element that we will get the context from - * @param options {Object} An options object that gets passed in to the canvas element containing the context attributes, - * see https://developer.mozilla.org/en/docs/Web/API/HTMLCanvasElement/getContext for the options available - * @return {WebGLRenderingContext} the WebGL context - */ - createContext(canvas, options) - { - let gl; - - if (settings.PREFER_WEBGL_2) - { - gl = canvas.getContext('webgl2', options); - } - - if (gl) - { - this.webGLVersion = 2; - } - else - { - this.webGLVersion = 1; - - gl = canvas.getContext('webgl', options) - || canvas.getContext('experimental-webgl', options); - - if (!gl) - { - // fail, not able to get a context - throw new Error('This browser does not support webGL. Try using the canvas renderer'); - } - } - - this.gl = gl; - - this.getExtensions(); - - return gl; - } - - getExtensions() - { - // time to set up default etensions that pixi uses.. - const gl = this.gl; - const extensions = this.extensions; - - if (this.webGLVersion === 1) - { - extensions.drawBuffers = gl.getExtension('WEBGL_draw_buffers'); - extensions.depthTexture = gl.getExtension('WEBKIT_WEBGL_depth_texture'); - extensions.floatTexture = gl.getExtension('OES_texture_float'); - extensions.loseContext = gl.getExtension('WEBGL_lose_context'); - - extensions.vertexArrayObject = gl.getExtension('OES_vertex_array_object') - || gl.getExtension('MOZ_OES_vertex_array_object') - || gl.getExtension('WEBKIT_OES_vertex_array_object'); - } - - // we don't use any specific WebGL 2 ones yet! - } - - /** - * Handles a lost webgl context - * - * @private - * @param {WebGLContextEvent} event - The context lost event. - */ - handleContextLost(event) - { - event.preventDefault(); - } - - /** - * Handles a restored webgl context - * - * @private - */ - handleContextRestored() - { - this.renderer.runners.contextChange.run(this.gl); - } - - destroy() - { - const view = this.renderer.view; - - // remove listeners - view.removeEventListener('webglcontextlost', this.handleContextLost); - view.removeEventListener('webglcontextrestored', this.handleContextRestored); - - this.gl.useProgram(null); - - if (this.extensions.loseContext) - { - this.extensions.loseContext.loseContext(); - } - } - - postrender() - { - this.gl.flush(); - } - - validateContext(gl) - { - const attributes = gl.getContextAttributes(); - - // this is going to be fairly simple for now.. but at least we have room to grow! - if (!attributes.stencil) - { - /* eslint-disable max-len */ - - /* eslint-disable no-console */ - console.warn('Provided WebGL context does not have a stencil buffer, masks may not render correctly'); - /* eslint-enable no-console */ - - /* eslint-enable max-len */ - } - } -} diff --git a/packages/core/src/renderers/systems/FramebufferSystem.js b/packages/core/src/renderers/systems/FramebufferSystem.js deleted file mode 100644 index df7902b..0000000 --- a/packages/core/src/renderers/systems/FramebufferSystem.js +++ /dev/null @@ -1,246 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { Rectangle } from '@pixi/math'; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class FramebufferSystem extends WebGLSystem -{ - /** - * Sets up the renderer context and necessary buffers. - * - * @private - */ - contextChange() - { - this.gl = this.renderer.gl; - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - this.current = null; - this.viewport = new Rectangle(); - - this.drawBufferExtension = this.renderer.context.extensions.drawBuffers; - } - - bind(framebuffer, frame) - { - const gl = this.gl; - - this.current = framebuffer; - - if (framebuffer) - { - // TODO cacheing layer! - - const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID] || this.initFramebuffer(framebuffer); - - gl.bindFramebuffer(gl.FRAMEBUFFER, fbo.framebuffer); - // makesure all textures are unbound.. - - // now check for updates... - if (fbo.dirtyId !== framebuffer.dirtyId) - { - fbo.dirtyId = framebuffer.dirtyId; - - if (fbo.dirtyFormat !== framebuffer.dirtyFormat) - { - fbo.dirtyFormat = framebuffer.dirtyFormat; - this.updateFramebuffer(framebuffer); - } - else if (fbo.dirtySize !== framebuffer.dirtySize) - { - fbo.dirtySize = framebuffer.dirtySize; - this.resizeFramebuffer(framebuffer); - } - } - - for (let i = 0; i < framebuffer.colorTextures.length; i++) - { - if (framebuffer.colorTextures[i].texturePart) - { - this.renderer.texture.unbind(framebuffer.colorTextures[i].texture); - } - else - { - this.renderer.texture.unbind(framebuffer.colorTextures[i]); - } - } - - if (framebuffer.depthTexture) - { - this.renderer.texture.unbind(framebuffer.depthTexture); - } - - if (frame) - { - this.setViewport(frame.x, frame.y, frame.width, frame.height); - } - else - { - this.setViewport(0, 0, framebuffer.width, framebuffer.height); - } - } - else - { - gl.bindFramebuffer(gl.FRAMEBUFFER, null); - - if (frame) - { - this.setViewport(frame.x, frame.y, frame.width, frame.height); - } - else - { - this.setViewport(0, 0, this.renderer.width, this.renderer.height); - } - } - } - - setViewport(x, y, width, height) - { - const v = this.viewport; - - if (v.width !== width || v.height !== height || v.x !== x || v.y !== y) - { - v.x = x; - v.y = y; - v.width = width; - v.height = height; - - this.gl.viewport(x, y, width, height); - } - } - - get size() - { - if (this.current) - { - // TODO store temp - return { x: 0, y: 0, width: this.current.width, height: this.current.height }; - } - - return { x: 0, y: 0, width: this.renderer.width, height: this.renderer.height }; - } - - clear(r, g, b, a) - { - const gl = this.gl; - - // TODO clear color can be set only one right? - gl.clearColor(r, g, b, a); - gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); - } - - // private functions... - - initFramebuffer(framebuffer) - { - const gl = this.gl; - - // TODO - make this a class? - const fbo = { - framebuffer: gl.createFramebuffer(), - stencil: null, - dirtyId: 0, - dirtyFormat: 0, - dirtySize: 0, - }; - - framebuffer.glFrameBuffers[this.CONTEXT_UID] = fbo; - - return fbo; - } - - resizeFramebuffer(framebuffer) - { - const gl = this.gl; - - if (framebuffer.stencil || framebuffer.depth) - { - gl.bindRenderbuffer(gl.RENDERBUFFER, this.stencil); - gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); - } - } - - updateFramebuffer(framebuffer) - { - const gl = this.gl; - - const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID]; - - // bind the color texture - const colorTextures = framebuffer.colorTextures; - - let count = colorTextures.length; - - if (!this.drawBufferExtension) - { - count = Math.min(count, 1); - } - - const activeTextures = []; - - for (let i = 0; i < count; i++) - { - const texture = framebuffer.colorTextures[i]; - - if (texture.texturePart) - { - this.renderer.texture.bind(texture.texture, 0); - - gl.framebufferTexture2D(gl.FRAMEBUFFER, - gl.COLOR_ATTACHMENT0 + i, - gl.TEXTURE_CUBE_MAP_NEGATIVE_X + texture.side, - texture.texture._glTextures[this.CONTEXT_UID].texture, - 0); - } - else - { - this.renderer.texture.bind(texture, 0); - - gl.framebufferTexture2D(gl.FRAMEBUFFER, - gl.COLOR_ATTACHMENT0 + i, - gl.TEXTURE_2D, - texture._glTextures[this.CONTEXT_UID].texture, - 0); - } - - activeTextures.push(gl.COLOR_ATTACHMENT0 + i); - } - - if (this.drawBufferExtension && activeTextures.length > 1) - { - this.drawBufferExtension.drawBuffersWEBGL(activeTextures); - } - - if (framebuffer.depthTexture) - { - const depthTextureExt = this.renderer.context.extensions.depthTexture; - - if (depthTextureExt) - { - const depthTexture = framebuffer.depthTexture; - - this.renderer.texture.bind(depthTexture, 0); - - gl.framebufferTexture2D(gl.FRAMEBUFFER, - gl.DEPTH_ATTACHMENT, - gl.TEXTURE_2D, - depthTexture._glTextures[this.CONTEXT_UID].texture, - 0); - } - } - - if (framebuffer.stencil || framebuffer.depth) - { - fbo.stencil = gl.createRenderbuffer(); - - gl.bindRenderbuffer(gl.RENDERBUFFER, fbo.stencil); - - // TODO.. this is depth AND stencil? - gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, fbo.stencil); - gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); - // fbo.enableStencil(); - } - } -} diff --git a/packages/core/src/renderers/systems/MaskSystem.js b/packages/core/src/renderers/systems/MaskSystem.js deleted file mode 100644 index d9c4b1a..0000000 --- a/packages/core/src/renderers/systems/MaskSystem.js +++ /dev/null @@ -1,199 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import AlphaMaskFilter from '../filters/spriteMask/SpriteMaskFilter'; - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class MaskSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - // TODO - we don't need both! - this.scissor = false; - this.scissorData = null; - this.scissorRenderTarget = null; - - this.enableScissor = false; - - this.alphaMaskPool = []; - this.alphaMaskIndex = 0; - } - - /** - * Applies the Mask and adds it to the current filter stack. - * - * @param {PIXI.DisplayObject} target - Display Object to push the mask to - * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. - */ - push(target, maskData) - { - // TODO the root check means scissor rect will not - // be used on render textures more info here: - // https://github.com/pixijs/pixi.js/pull/3545 - - if (maskData.texture) - { - this.pushSpriteMask(target, maskData); - } - else if (this.enableScissor - && !this.scissor - && this.renderer._activeRenderTarget.root - && !this.renderer.stencil.stencilMaskStack.length - && maskData.isFastRect()) - { - const matrix = maskData.worldTransform; - - let rot = Math.atan2(matrix.b, matrix.a); - - // use the nearest degree! - rot = Math.round(rot * (180 / Math.PI)); - - if (rot % 90) - { - this.pushStencilMask(maskData); - } - else - { - this.pushScissorMask(target, maskData); - } - } - else - { - this.pushStencilMask(maskData); - } - } - - /** - * Removes the last mask from the mask stack and doesn't return it. - * - * @param {PIXI.DisplayObject} target - Display Object to pop the mask from - * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. - */ - pop(target, maskData) - { - if (maskData.texture) - { - this.popSpriteMask(target, maskData); - } - else if (this.enableScissor && !this.renderer.stencil.stencilMaskStack.length) - { - this.popScissorMask(target, maskData); - } - else - { - this.popStencilMask(target, maskData); - } - } - - /** - * Applies the Mask and adds it to the current filter stack. - * - * @param {PIXI.RenderTarget} target - Display Object to push the sprite mask to - * @param {PIXI.Sprite} maskData - Sprite to be used as the mask - */ - pushSpriteMask(target, maskData) - { - let alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; - - if (!alphaMaskFilter) - { - alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; - } - - alphaMaskFilter[0].resolution = this.renderer.resolution; - alphaMaskFilter[0].maskSprite = maskData; - - // TODO - may cause issues! - target.filterArea = maskData.getBounds(true); - - this.renderer.filterSystem.pushFilter(target, alphaMaskFilter); - - this.alphaMaskIndex++; - } - - /** - * Removes the last filter from the filter stack and doesn't return it. - * - */ - popSpriteMask() - { - this.renderer.filterSystem.popFilter(); - this.alphaMaskIndex--; - } - - /** - * Applies the Mask and adds it to the current filter stack. - * - * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. - */ - pushStencilMask(maskData) - { - this.renderer.batch.flush(); - this.renderer.stencil.pushStencil(maskData); - } - - /** - * Removes the last filter from the filter stack and doesn't return it. - * - */ - popStencilMask() - { - // this.renderer.currentRenderer.stop(); - this.renderer.stencil.popStencil(); - } - - /** - * - * @param {PIXI.DisplayObject} target - Display Object to push the mask to - * @param {PIXI.Graphics} maskData - The masking data. - */ - pushScissorMask(target, maskData) - { - maskData.renderable = true; - - const renderTarget = this.renderer._activeRenderTarget; - - const bounds = maskData.getBounds(); - - bounds.fit(renderTarget.size); - maskData.renderable = false; - - this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); - - const resolution = this.renderer.resolution; - - this.renderer.gl.scissor( - bounds.x * resolution, - (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, - bounds.width * resolution, - bounds.height * resolution - ); - - this.scissorRenderTarget = renderTarget; - this.scissorData = maskData; - this.scissor = true; - } - - /** - * - * - */ - popScissorMask() - { - this.scissorRenderTarget = null; - this.scissorData = null; - this.scissor = false; - - // must be scissor! - const gl = this.renderer.gl; - - gl.disable(gl.SCISSOR_TEST); - } -} diff --git a/packages/core/src/renderers/systems/ProjectionSystem.js b/packages/core/src/renderers/systems/ProjectionSystem.js deleted file mode 100644 index ec5efdb..0000000 --- a/packages/core/src/renderers/systems/ProjectionSystem.js +++ /dev/null @@ -1,73 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { Matrix } from '@pixi/math'; - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ - -export default class ProjectionSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this.projectionMatrix = new Matrix(); - } - - update(destinationFrame, sourceFrame, resolution, root) - { - this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; - this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; - - this.calculateProjection(this.destinationFrame, this.sourceFrame, resolution, root); - - this.renderer.globalUniforms.uniforms.projectionMatrix = this.projectionMatrix; - this.renderer.globalUniforms.update(); - } - - /** - * Updates the projection matrix based on a projection frame (which is a rectangle) - * - * @param {Rectangle} destinationFrame - The destination frame. - * @param {Rectangle} sourceFrame - The source frame. - */ - calculateProjection(destinationFrame, sourceFrame, resolution, root) - { - const pm = this.projectionMatrix; - - // I don't think we will need this line.. - // pm.identity(); - - if (!root) - { - pm.a = (1 / destinationFrame.width * 2) * resolution; - pm.d = (1 / destinationFrame.height * 2) * resolution; - - pm.tx = -1 - (sourceFrame.x * pm.a); - pm.ty = -1 - (sourceFrame.y * pm.d); - } - else - { - pm.a = (1 / destinationFrame.width * 2) * resolution; - pm.d = (-1 / destinationFrame.height * 2) * resolution; - - pm.tx = -1 - (sourceFrame.x * pm.a); - pm.ty = 1 - (sourceFrame.y * pm.d); - } - } - - /** - * Sets the transform of the active render target to the given matrix - * - * @param {PIXI.Matrix} matrix - The transformation matrix - */ - setTransform()// matrix) - { - // this._activeRenderTarget.transform = matrix; - } -} diff --git a/packages/core/src/renderers/systems/RenderTextureSystem.js b/packages/core/src/renderers/systems/RenderTextureSystem.js deleted file mode 100644 index a133ef1..0000000 --- a/packages/core/src/renderers/systems/RenderTextureSystem.js +++ /dev/null @@ -1,115 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { Rectangle } from '@pixi/math'; - -const tempRect = new Rectangle(); - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ - -export default class RenderTextureSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this.clearColor = renderer._backgroundColorRgba; - - // TODO moe this property somewhere else! - this.defaultMaskStack = []; - this.defaultFilterStack = [{}]; - - // empty render texture? - this.renderTexture = null; - - this.destinationFrame = new Rectangle(); - } - - bind(renderTexture, sourceFrame, destinationFrame) - { - // TODO - do we want this?? - if (this.renderTexture === renderTexture) return; - this.renderTexture = renderTexture; - - const renderer = this.renderer; - - if (renderTexture) - { - const baseTexture = renderTexture.baseTexture; - - if (!destinationFrame) - { - tempRect.width = baseTexture.realWidth; - tempRect.height = baseTexture.realHeight; - - destinationFrame = tempRect; - } - - if (!sourceFrame) - { - sourceFrame = destinationFrame; - } - - this.renderer.framebuffer.bind(baseTexture.frameBuffer, destinationFrame); - - this.renderer.projection.update(destinationFrame, sourceFrame, baseTexture.resolution, false); - this.renderer.stencil.setMaskStack(baseTexture.stencilMaskStack); - } - else - { - // TODO these validation checks happen deeper down.. - // thing they can be avoided.. - if (!destinationFrame) - { - tempRect.width = renderer.width; - tempRect.height = renderer.height; - - destinationFrame = tempRect; - } - - if (!sourceFrame) - { - sourceFrame = destinationFrame; - } - - renderer.framebuffer.bind(null, destinationFrame); - - // TODO store this.. - this.renderer.projection.update(destinationFrame, sourceFrame, this.renderer.resolution, true); - this.renderer.stencil.setMaskStack(this.defaultMaskStack); - } - - this.destinationFrame.copyFrom(destinationFrame); - } - - /** - * Erases the render texture and fills the drawing area with a colour - * - * @param {number} [clearColor] - The colour - * @return {PIXI.Renderer} Returns itself. - */ - clear(clearColor) - { - if (this.renderTexture) - { - clearColor = clearColor || this.renderTexture.baseTexture.clearColor; - } - else - { - clearColor = clearColor || this.clearColor; - } - - this.renderer.framebuffer.clear(clearColor[0], clearColor[1], clearColor[2], clearColor[3]); - } - - resize()// screenWidth, screenHeight) - { - // resize the root only! - this.bind(null); - } -} diff --git a/packages/core/src/renderers/systems/StateSystem.js b/packages/core/src/renderers/systems/StateSystem.js deleted file mode 100755 index d7f4caf..0000000 --- a/packages/core/src/renderers/systems/StateSystem.js +++ /dev/null @@ -1,288 +0,0 @@ -import mapWebGLBlendModesToPixi from '../utils/mapWebGLBlendModesToPixi'; -import WebGLSystem from './WebGLSystem'; -import WebGLState from '../State'; - -const BLEND = 0; -const OFFSET = 1; -const CULLING = 2; -const DEPTH_TEST = 3; -const WINDING = 4; - -/** - * A WebGL state machines - * - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class StateSystem extends WebGLSystem -{ - /** - * @param {WebGLRenderingContext} gl - The current WebGL rendering context - */ - constructor(renderer) - { - super(renderer); - - this.gl = null; - - this.maxAttribs = null; - - // check we have vao.. - this.nativeVaoExtension = null; - - this.attribState = null; - - this.stateId = 0; - this.polygonOffset = 0; - this.blendMode = 17; - - this.map = []; - - // map functions for when we set state.. - this.map[BLEND] = this.setBlend; - this.map[OFFSET] = this.setOffset; - this.map[CULLING] = this.setCullFace; - this.map[DEPTH_TEST] = this.setDepthTest; - this.map[WINDING] = this.setFrontFace; - - this.checks = []; - - this.defaultState = new WebGLState(); - this.defaultState.blend = true; - this.defaultState.depth = true; - } - - contextChange(gl) - { - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; - - this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); - - // check we have vao.. - this.nativeVaoExtension = ( - gl.getExtension('OES_vertex_array_object') - || gl.getExtension('MOZ_OES_vertex_array_object') - || gl.getExtension('WEBKIT_OES_vertex_array_object') - ); - - this.attribState = { - tempAttribState: new Array(this.maxAttribs), - attribState: new Array(this.maxAttribs), - }; - - this.blendModes = mapWebGLBlendModesToPixi(gl); - - this.setState(this.defaultState); - - this.reset(); - } - - /** - * Sets the current state - * - * @param {*} state - The state to set. - */ - setState(state) - { - state = state || this.defaultState; - - // TODO maybe to an object check? ( this.state === state )? - if (this.stateId !== state.data) - { - let diff = this.stateId ^ state.data; - let i = 0; - - // order from least to most common - while (diff) - { - if (diff & 1) - { - // state change! - this.map[i].call(this, !!(state.data & (1 << i))); - } - - diff = diff >> 1; - i++; - } - - this.stateId = state.data; - } - - // based on the above settings we check for specific modes.. - // for example if blend is active we check and set the blend modes - // or of polygon offset is active we check the poly depth. - for (let i = 0; i < this.checks.length; i++) - { - this.checks[i](this, state); - } - } - - /** - * Enables or disabled blending. - * - * @param {boolean} value - Turn on or off webgl blending. - */ - setBlend(value) - { - this.updateCheck(StateSystem.checkBlendMode, value); - - this.gl[value ? 'enable' : 'disable'](this.gl.BLEND); - } - - /** - * Enables or disable polygon offset fill - * - * @param {boolean} value - Turn on or off webgl polygon offset testing. - */ - setOffset(value) - { - this.gl[value ? 'enable' : 'disable'](this.gl.POLYGON_OFFSET_FILL); - } - - /** - * Sets whether to enable or disable depth test. - * - * @param {boolean} value - Turn on or off webgl depth testing. - */ - setDepthTest(value) - { - this.gl[value ? 'enable' : 'disable'](this.gl.DEPTH_TEST); - } - - /** - * Sets whether to enable or disable cull face. - * - * @param {boolean} value - Turn on or off webgl cull face. - */ - setCullFace(value) - { - this.gl[value ? 'enable' : 'disable'](this.gl.CULL_FACE); - } - - /** - * Sets the gl front face. - * - * @param {boolean} value - true is clockwise and false is counter-clockwise - */ - setFrontFace(value) - { - this.gl.frontFace(this.gl[value ? 'CW' : 'CCW']); - } - - /** - * Sets the blend mode. - * - * @param {number} value - The blend mode to set to. - */ - setBlendMode(value) - { - if (value === this.blendMode) - { - return; - } - - this.blendMode = value; - this.gl.blendFunc(this.blendModes[value][0], this.blendModes[value][1]); - } - - /** - * Sets the polygon offset. - * - * @param {number} value - the polygon offset - * @param {number} scale - the polygon offset scale - */ - setPolygonOffset(value, scale) - { - this.gl.polygonOffset(value, scale); - } - - /** - * Disables all the vaos in use - * - */ - resetAttributes() - { - for (let i = 0; i < this.attribState.tempAttribState.length; i++) - { - this.attribState.tempAttribState[i] = 0; - } - - for (let i = 0; i < this.attribState.attribState.length; i++) - { - this.attribState.attribState[i] = 0; - } - - // im going to assume one is always active for performance reasons. - for (let i = 1; i < this.maxAttribs; i++) - { - this.gl.disableVertexAttribArray(i); - } - } - - // used - /** - * Resets all the logic and disables the vaos - */ - reset() - { - // unbind any VAO if they exist.. - if (this.nativeVaoExtension) - { - this.nativeVaoExtension.bindVertexArrayOES(null); - } - - // reset all attributes.. - this.resetAttributes(); - - this.gl.pixelStorei(this.gl.UNPACK_FLIP_Y_WEBGL, false); - - this.setBlendMode(0); - - // TODO? - // this.setState(this.defaultState); - } - - /** - * checks to see which updates should be checked based on which settings have been activated - * for example if blend is enabled then we shold check the blend modes each time the state is changed - * or if poygon fill is activated then we need to check if the polygone offset changes. - * The idea is that we only check what we have too. - * - * @param {Function} func the checking function to add or remove - * @param {boolean} value should the check function be added or removed. - */ - updateCheck(func, value) - { - const index = this.checks.indexOf(func); - - if (value && index === -1) - { - this.checks.push(func); - } - else if (!value && index !== -1) - { - this.checks.splice(index, 1); - } - } - - /** - * A private little wrapper function that we call to check the blend mode. - * - * @static - * @private - * @param {PIXI.StateSystem} System the System to perform the state check on - * @param {PIXI.State} state the state that the blendMode will pulled from - */ - static checkBlendMode(system, state) - { - system.setBlendMode(state.blendMode); - } - - // TODO - add polygon offset? -} diff --git a/packages/core/src/renderers/systems/StencilSystem.js b/packages/core/src/renderers/systems/StencilSystem.js deleted file mode 100644 index 5d538cb..0000000 --- a/packages/core/src/renderers/systems/StencilSystem.js +++ /dev/null @@ -1,133 +0,0 @@ -import WebGLSystem from './WebGLSystem'; - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class StencilSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - this.stencilMaskStack = []; - } - - /** - * Changes the mask stack that is used by this System. - * - * @param {PIXI.Graphics[]} stencilMaskStack - The mask stack - */ - setMaskStack(stencilMaskStack) - { - const gl = this.renderer.gl; - - if (stencilMaskStack.length !== this.stencilMaskStack.length) - { - if (stencilMaskStack.length === 0) - { - gl.disable(gl.STENCIL_TEST); - } - else - { - gl.enable(gl.STENCIL_TEST); - } - } - - this.stencilMaskStack = stencilMaskStack; - } - - /** - * Applies the Mask and adds it to the current stencil stack. @alvin - * - * @param {PIXI.Graphics} graphics - The mask - */ - pushStencil(graphics) - { - this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); - - // this.renderer._activeRenderTarget.attachStencilBuffer(); - - const gl = this.renderer.gl; - const prevMaskCount = this.stencilMaskStack.length; - - if (prevMaskCount === 0) - { - gl.enable(gl.STENCIL_TEST); - } - - this.stencilMaskStack.push(graphics); - - // Increment the refference stencil value where the new mask overlaps with the old ones. - gl.colorMask(false, false, false, false); - gl.stencilFunc(gl.EQUAL, prevMaskCount, this._getBitwiseMask()); - gl.stencilOp(gl.KEEP, gl.KEEP, gl.INCR); - this.renderer.plugins.graphics.render(graphics); - - this._useCurrent(); - } - - /** - * Removes the last mask from the stencil stack. @alvin - */ - popStencil() - { - this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); - - const gl = this.renderer.gl; - const graphics = this.stencilMaskStack.pop(); - - if (this.stencilMaskStack.length === 0) - { - // the stack is empty! - gl.disable(gl.STENCIL_TEST); - gl.clear(gl.STENCIL_BUFFER_BIT); - gl.clearStencil(0); - } - else - { - // Decrement the refference stencil value where the popped mask overlaps with the other ones - gl.colorMask(false, false, false, false); - gl.stencilOp(gl.KEEP, gl.KEEP, gl.DECR); - this.renderer.plugins.graphics.render(graphics); - - this._useCurrent(); - } - } - - /** - * Setup renderer to use the current stencil data. - */ - _useCurrent() - { - const gl = this.renderer.gl; - - gl.colorMask(true, true, true, true); - gl.stencilFunc(gl.EQUAL, this.stencilMaskStack.length, this._getBitwiseMask()); - gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP); - } - - /** - * Fill 1s equal to the number of acitve stencil masks. - * - * @return {number} The bitwise mask. - */ - _getBitwiseMask() - { - return (1 << this.stencilMaskStack.length) - 1; - } - - /** - * Destroys the mask stack. - * - */ - destroy() - { - super.destroy(this); - - this.stencilMaskStack = null; - } -} diff --git a/packages/core/src/renderers/systems/WebGLSystem.js b/packages/core/src/renderers/systems/WebGLSystem.js deleted file mode 100644 index d3d5b31..0000000 --- a/packages/core/src/renderers/systems/WebGLSystem.js +++ /dev/null @@ -1,41 +0,0 @@ -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this manager works for. - */ - constructor(renderer) - { - /** - * The renderer this manager works for. - * - * @member {PIXI.Renderer} - */ - this.renderer = renderer; - - this.renderer.runners.contextChange.add(this); - } - - /** - * Generic method called when there is a WebGL context change. - * - */ - contextChange() - { - // do some codes init! - } - - /** - * Generic destroy methods to be overridden by the subclass - * - */ - destroy() - { - this.renderer.runners.contextChange.remove(this); - this.renderer = null; - } -} diff --git a/packages/core/src/renderers/systems/filter/FilterSystem.js b/packages/core/src/renderers/systems/filter/FilterSystem.js deleted file mode 100644 index e615037..0000000 --- a/packages/core/src/renderers/systems/filter/FilterSystem.js +++ /dev/null @@ -1,431 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; - -import RenderTexture from '../../../textures/RenderTexture'; -import Quad from '../../utils/Quad'; -import QuadUv from '../../utils/QuadUv'; -import { Rectangle } from '@pixi/math'; -import * as filterTransforms from '../../filters/filterTransforms'; -import bitTwiddle from 'bit-twiddle'; -import UniformGroup from '../../../shader/UniformGroup'; -import { DRAW_MODES } from '../../../../../constants'; - -// -/** - * @ignore - * @class - */ -class FilterState -{ - /** - * - */ - constructor() - { - this.renderTexture = null; - this.sourceFrame = new Rectangle(); - this.destinationFrame = new Rectangle(); - this.filters = []; - this.target = null; - this.legacy = false; - this.resolution = 1; - } -} - -/** - * @class - * @memberof PIXI.systems - * @extends PIXI.systems.WebGLSystem - */ -export default class FilterSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - /** - * stores a bunch of PO2 textures used for filtering - * @type {Object} - */ - this.texturePool = {}; - - /** - * a pool for storing filter states, save us creating new ones each tick - * @type {Array} - */ - this.statePool = []; - - /** - * A very simple geometry used when drawing a filter effect to the screen - * @type {Quad} - */ - this.quad = new Quad(); - - this.quadUv = new QuadUv(); - - /** - * Temporary rect for maths - * @type {PIXI.Rectangle} - */ - this.tempRect = new Rectangle(); - - this.activeState = {}; - - /** - * this uniform group is attached to filter uniforms when used - * @type {UniformGroup} - */ - this.globalUniforms = new UniformGroup({ - sourceFrame: this.tempRect, - destinationFrame: this.tempRect, - - // legacy variables - filterArea: new Float32Array(4), - filterClamp: new Float32Array(4), - }, true); - } - - /** - * Adds a new filter to the System. - * - * @param {PIXI.DisplayObject} target - The target of the filter to render. - * @param {PIXI.Filter[]} filters - The filters to apply. - */ - push(target, filters) - { - const renderer = this.renderer; - const filterStack = this.renderer.renderTexture.defaultFilterStack; - const state = this.statePool.pop() || new FilterState(); - - let resolution = filters[0].resolution; - let padding = filters[0].padding; - let autoFit = filters[0].autoFit; - let legacy = filters[0].legacy; - - for (let i = 1; i < filters.length; i++) - { - const filter = filters[i]; - - // lets use the lowest resolution.. - resolution = Math.min(resolution, filter.resolution); - // and the largest amount of padding! - padding = Math.max(padding, filter.padding); - // only auto fit if all filters are autofit - autoFit = autoFit || filter.autoFit; - - legacy = legacy || filter.legacy; - } - - filterStack.push(state); - - state.resolution = resolution; - - state.legacy = legacy; - - // round to whole number based on resolution - // TODO move that to the shader too? - state.sourceFrame = target.filterArea || target.getBounds(true); - - state.sourceFrame.pad(padding); - - if (autoFit) - { - state.sourceFrame.fit(this.renderer.renderTexture.destinationFrame); - } - - state.sourceFrame.round(resolution); - - state.renderTexture = this.getPotFilterTexture(state.sourceFrame.width, state.sourceFrame.height, resolution); - state.filters = filters; - - state.destinationFrame.width = state.renderTexture.width; - state.destinationFrame.height = state.renderTexture.height; - - state.renderTexture.filterFrame = state.sourceFrame; - renderer.renderTexture.bind(state.renderTexture, state.sourceFrame);// /, state.destinationFrame); - renderer.renderTexture.clear(); - } - - /** - * Pops off the filter and applies it. - * - */ - pop() - { - const renderer = this.renderer; - const filterStack = renderer.renderTexture.defaultFilterStack; - const state = filterStack.pop(); - const filters = state.filters; - - this.activeState = state; - - const globalUniforms = this.globalUniforms.uniforms; - - globalUniforms.sourceFrame = state.sourceFrame; - globalUniforms.destinationFrame = state.destinationFrame; - globalUniforms.resolution = state.resolution; - - // only update the rect if its legacy.. - if (state.legacy) - { - const filterArea = globalUniforms.filterArea; - const filterClamp = globalUniforms.filterClamp; - - filterArea[0] = state.destinationFrame.width; - filterArea[1] = state.destinationFrame.height; - filterArea[2] = state.sourceFrame.x; - filterArea[3] = state.sourceFrame.y; - - filterClamp[0] = 0.5 / state.resolution / state.destinationFrame.width; - filterClamp[1] = 0.5 / state.resolution / state.destinationFrame.height; - filterClamp[2] = (state.sourceFrame.width - 0.5) / state.resolution / state.destinationFrame.width; - filterClamp[3] = (state.sourceFrame.height - 0.5) / state.resolution / state.destinationFrame.height; - } - - this.globalUniforms.update(); - - const lastState = filterStack[filterStack.length - 1]; - - if (filters.length === 1) - { - filters[0].apply(this, state.renderTexture, lastState.renderTexture, false, state); - - this.returnFilterTexture(state.renderTexture); - } - else - { - let flip = state.renderTexture; - let flop = this.getPotFilterTexture( - flip.width, - flip.height, - state.resolution - ); - - flop.filterFrame = flip.filterFrame; - - let i = 0; - - for (i = 0; i < filters.length - 1; ++i) - { - filters[i].apply(this, flip, flop, true, state); - - const t = flip; - - flip = flop; - flop = t; - } - - filters[i].apply(this, flip, lastState.renderTexture, false, state); - - this.returnFilterTexture(flip); - this.returnFilterTexture(flop); - } - - this.statePool.push(state); - } - - /** - * Draws a filter. - * - * @param {PIXI.Filter} filter - The filter to draw. - * @param {PIXI.RenderTarget} input - The input render target. - * @param {PIXI.RenderTarget} output - The target to output to. - * @param {boolean} clear - Should the output be cleared before rendering to it - */ - applyFilter(filter, input, output, clear) - { - const renderer = this.renderer; - - renderer.renderTexture.bind(output, output ? output.filterFrame : null); - - if (clear) - { - // gl.disable(gl.SCISSOR_TEST); - renderer.renderTexture.clear(); - // gl.enable(gl.SCISSOR_TEST); - } - - // set the uniforms.. - filter.uniforms.uSampler = input; - filter.uniforms.filterGlobals = this.globalUniforms; - - // TODO make it so that the order of this does not matter.. - // because it does at the moment cos of global uniforms. - // they need to get resynced - - renderer.state.setState(filter.state); - renderer.shader.bind(filter); - - if (filter.legacy) - { - this.quadUv.map(input._frame, input.filterFrame); - - renderer.geometry.bind(this.quadUv); - renderer.geometry.draw(DRAW_MODES.TRIANGLES); - } - else - { - renderer.geometry.bind(this.quad); - renderer.geometry.draw(DRAW_MODES.TRIANGLE_STRIP); - } - } - - /** - * Calculates the mapped matrix. - * - * TODO playing around here.. this is temporary - (will end up in the shader) - * this returns a matrix that will normalise map filter cords in the filter to screen space - * - * @param {PIXI.Matrix} outputMatrix - the matrix to output to. - * @return {PIXI.Matrix} The mapped matrix. - */ - calculateScreenSpaceMatrix(outputMatrix) - { - const currentState = this.activeState; - - return filterTransforms.calculateScreenSpaceMatrix( - outputMatrix, - currentState.sourceFrame, - currentState.destinationFrame - ); - } - - /** - * This will map the filter coord so that a texture can be used based on the transform of a sprite - * - * @param {PIXI.Matrix} outputMatrix - The matrix to output to. - * @param {PIXI.Sprite} sprite - The sprite to map to. - * @return {PIXI.Matrix} The mapped matrix. - */ - calculateSpriteMatrix(outputMatrix, sprite) - { - const currentState = this.activeState; - - return filterTransforms.calculateSpriteMatrix( - outputMatrix, - currentState.sourceFrame, - currentState.destinationFrame, - sprite - ); - } - - /** - * Destroys this Filter System. - * - * @param {boolean} [contextLost=false] context was lost, do not free shaders - * - */ - destroy(contextLost = false) - { - if (!contextLost) - { - this.emptyPool(); - } - else - { - this.texturePool = {}; - } - } - - /** - * Gets a Power-of-Two render texture. - * - * TODO move to a seperate class could be on renderer? - * also - could cause issue with multiple contexts? - * - * @private - * @param {WebGLRenderingContext} gl - The webgl rendering context - * @param {number} minWidth - The minimum width of the render target. - * @param {number} minHeight - The minimum height of the render target. - * @param {number} resolution - The resolution of the render target. - * @return {PIXI.RenderTarget} The new render target. - */ - getPotFilterTexture(minWidth, minHeight, resolution) - { - minWidth = bitTwiddle.nextPow2(minWidth); - minHeight = bitTwiddle.nextPow2(minHeight); - resolution = resolution || 1; - - const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); - - if (!this.texturePool[key]) - { - this.texturePool[key] = []; - } - - let renderTexture = this.texturePool[key].pop(); - - if (!renderTexture) - { - // temporary bypass cache.. - // internally - this will cause a texture to be bound.. - renderTexture = RenderTexture.create({ - width: minWidth, - height: minHeight, - resolution, - }); - } - - return renderTexture; - } - - /** - * Gets extra render texture to use inside current filter - * - * @param {number} resolution resolution of the renderTexture - * @returns {PIXI.RenderTexture} - */ - getFilterTexture(resolution) - { - const rt = this.activeState.renderTexture; - - const filterTexture = this.getPotFilterTexture(rt.width, rt.height, resolution || rt.baseTexture.resolution); - - filterTexture.filterFrame = rt.filterFrame; - - return filterTexture; - } - - /** - * Frees a render texture back into the pool. - * - * @param {PIXI.RenderTarget} renderTexture - The renderTarget to free - */ - returnFilterTexture(renderTexture) - { - renderTexture.filterFrame = null; - - const base = renderTexture.baseTexture; - - const minWidth = base.width; - const minHeight = base.height; - - const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); - - this.texturePool[key].push(renderTexture); - } - - /** - * Empties the texture pool. - * - */ - emptyPool() - { - for (const i in this.texturePool) - { - const textures = this.texturePool[i]; - - if (textures) - { - for (let j = 0; j < textures.length; j++) - { - textures[j].destroy(true); - } - } - } - - this.texturePool = {}; - } -} diff --git a/packages/core/src/renderers/systems/geometry/GLBuffer.js b/packages/core/src/renderers/systems/geometry/GLBuffer.js deleted file mode 100644 index 0477e8a..0000000 --- a/packages/core/src/renderers/systems/geometry/GLBuffer.js +++ /dev/null @@ -1,9 +0,0 @@ -export default class GLBuffer -{ - constructor(buffer) - { - this.buffer = buffer; - this.updateID = -1; - this.byteLength = -1; - } -} diff --git a/packages/core/src/renderers/systems/geometry/GeometrySystem.js b/packages/core/src/renderers/systems/geometry/GeometrySystem.js deleted file mode 100644 index 26c8e9e..0000000 --- a/packages/core/src/renderers/systems/geometry/GeometrySystem.js +++ /dev/null @@ -1,388 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; -import GLBuffer from './GLBuffer'; - -const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class GeometrySystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this._activeGeometry = null; - this._activeVao = null; - - this.hasVao = true; - this.hasInstance = true; - } - - /** - * Sets up the renderer context and necessary buffers. - * - * @private - */ - contextChange() - { - const gl = this.gl = this.renderer.gl; - - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - - // webgl2 - if (!gl.createVertexArray) - { - // webgl 1! - let nativeVaoExtension = this.renderer.context.extensions.vertexArrayObject; - - if (this.renderer.legacy) - { - nativeVaoExtension = null; - } - - if (nativeVaoExtension) - { - gl.createVertexArray = () => - nativeVaoExtension.createVertexArrayOES(); - - gl.bindVertexArray = (vao) => - nativeVaoExtension.bindVertexArrayOES(vao); - - gl.deleteVertexArray = (vao) => - nativeVaoExtension.deleteVertexArrayOES(vao); - } - else - { - this.hasVao = false; - gl.createVertexArray = () => - { - // empty - }; - - gl.bindVertexArray = () => - { - // empty - }; - - gl.deleteVertexArray = () => - { - // empty - }; - } - } - - if (!gl.vertexAttribDivisor) - { - const instanceExt = gl.getExtension('ANGLE_instanced_arrays'); - - if (instanceExt) - { - gl.vertexAttribDivisor = (a, b) => - instanceExt.vertexAttribDivisorANGLE(a, b); - - gl.drawElementsInstanced = (a, b, c, d, e) => - instanceExt.drawElementsInstancedANGLE(a, b, c, d, e); - - gl.drawArraysInstanced = (a, b, c, d) => - instanceExt.drawArraysInstancedANGLE(a, b, c, d); - } - else - { - this.hasInstance = false; - } - } - } - - /** - * Binds geometry so that is can be drawn. Creating a Vao if required - * @private - * @param {PIXI.Geometry} geometry instance of geometry to bind - */ - bind(geometry, shader) - { - shader = shader || this.renderer.shader.shader; - - const gl = this.gl; - - // not sure the best way to address this.. - // currently different shaders require different VAOs for the same geometry - // Still mulling over the best way to solve this one.. - // will likely need to modify the shader attribute locations at run time! - let vaos = geometry.glVertexArrayObjects[this.CONTEXT_UID]; - - if (!vaos) - { - geometry.glVertexArrayObjects[this.CONTEXT_UID] = vaos = {}; - } - - const vao = vaos[shader.program.id] || this.initGeometryVao(geometry, shader.program); - - this._activeGeometry = geometry; - - if (this._activeVao !== vao) - { - this._activeVao = vao; - - if (this.hasVao) - { - gl.bindVertexArray(vao); - } - else - { - this.activateVao(geometry, shader.program); - } - } - - // TODO - optimise later! - // don't need to loop through if nothing changed! - // maybe look to add an 'autoupdate' to geometry? - this.updateBuffers(); - } - - reset() - { - this.unbind(); - } - - updateBuffers() - { - const geometry = this._activeGeometry; - const gl = this.gl; - - for (let i = 0; i < geometry.buffers.length; i++) - { - const buffer = geometry.buffers[i]; - - const glBuffer = buffer._glBuffers[this.CONTEXT_UID]; - - if (buffer._updateID !== glBuffer.updateID) - { - glBuffer.updateID = buffer._updateID; - - // TODO can cache this on buffer! maybe added a getter / setter? - const type = buffer.index ? gl.ELEMENT_ARRAY_BUFFER : gl.ARRAY_BUFFER; - const drawType = buffer.static ? gl.STATIC_DRAW : gl.DYNAMIC_DRAW; - - gl.bindBuffer(type, glBuffer.buffer); - - if (glBuffer.byteLength >= buffer.data.byteLength) - { - // offset is always zero for now! - gl.bufferSubData(type, 0, buffer.data); - } - else - { - gl.bufferData(type, buffer.data, drawType); - } - } - } - } - - checkCompatability(geometry, program) - { - // geometry must have at least all the attributes that the shader requires. - const geometryAttributes = geometry.attributes; - const shaderAttributes = program.attributeData; - - for (const j in shaderAttributes) - { - if (!geometryAttributes[j]) - { - throw new Error(`shader and geometry incompatible, geometry missing the "${j}" attribute`); - } - } - } - - /** - * Creates a Vao with the same structure as the geometry and stores it on the geometry. - * @private - * @param {PIXI.Geometry} geometry instance of geometry to to generate Vao for - */ - initGeometryVao(geometry, program) - { - this.checkCompatability(geometry, program); - - const gl = this.gl; - const CONTEXT_UID = this.CONTEXT_UID; - const buffers = geometry.buffers; - const attributes = geometry.attributes; - - const tempStride = {}; - const tempStart = {}; - - for (const j in buffers) - { - tempStride[j] = 0; - tempStart[j] = 0; - } - - for (const j in attributes) - { - if (!attributes[j].size && program.attributeData[j]) - { - attributes[j].size = program.attributeData[j].size; - } - - tempStride[attributes[j].buffer] += attributes[j].size * byteSizeMap[attributes[j].type]; - } - - for (const j in attributes) - { - const attribute = attributes[j]; - const attribSize = attribute.size; - - if (attribute.stride === undefined) - { - if (tempStride[attribute.buffer] === attribSize * byteSizeMap[attribute.type]) - { - attribute.stride = 0; - } - else - { - attribute.stride = tempStride[attribute.buffer]; - } - } - - if (attribute.start === undefined) - { - attribute.start = tempStart[attribute.buffer]; - - tempStart[attribute.buffer] += attribSize * byteSizeMap[attribute.type]; - } - } - - // first update - and create the buffers! - // only create a gl buffer if it actually gets - for (let i = 0; i < buffers.length; i++) - { - const buffer = buffers[i]; - - if (!buffer._glBuffers[CONTEXT_UID]) - { - buffer._glBuffers[CONTEXT_UID] = new GLBuffer(gl.createBuffer()); - } - } - - // TODO - maybe make this a data object? - // lets wait to see if we need to first! - const vao = gl.createVertexArray(); - - gl.bindVertexArray(vao); - - this.activateVao(geometry, program); - - gl.bindVertexArray(null); - - // add it to the cache! - geometry.glVertexArrayObjects[this.CONTEXT_UID][program.id] = vao; - - return vao; - } - - activateVao(geometry, program) - { - const gl = this.gl; - const CONTEXT_UID = this.CONTEXT_UID; - const buffers = geometry.buffers; - const attributes = geometry.attributes; - - if (geometry.indexBuffer) - { - // first update the index buffer if we have one.. - gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, geometry.indexBuffer._glBuffers[CONTEXT_UID].buffer); - } - - let lastBuffer = null; - - // add a new one! - for (const j in attributes) - { - const attribute = attributes[j]; - const buffer = buffers[attribute.buffer]; - const glBuffer = buffer._glBuffers[CONTEXT_UID]; - - if (program.attributeData[j]) - { - if (lastBuffer !== glBuffer) - { - gl.bindBuffer(gl.ARRAY_BUFFER, glBuffer.buffer); - - lastBuffer = glBuffer; - } - - const location = program.attributeData[j].location; - - // TODO introduce state again - // we can optimise this for older devices that have no VAOs - gl.enableVertexAttribArray(location); - - gl.vertexAttribPointer(location, - attribute.size, - attribute.type || gl.FLOAT, - attribute.normalized, - attribute.stride, - attribute.start); - - if (attribute.instance) - { - // TODO calculate instance count based of this... - if (this.hasInstance) - { - gl.vertexAttribDivisor(location, 1); - } - else - { - throw new Error('geometry error, GPU Instancing is not supported on this device'); - } - } - } - } - } - - draw(type, size, start, instanceCount) - { - const gl = this.gl; - const geometry = this._activeGeometry; - - // TODO.. this should not change so maybe cache the function? - - if (geometry.indexBuffer) - { - if (geometry.instanced) - { - /* eslint-disable max-len */ - gl.drawElementsInstanced(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2, instanceCount || 1); - /* eslint-enable max-len */ - } - else - { - gl.drawElements(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2); - } - } - else - if (geometry.instanced) - { - // TODO need a better way to calculate size.. - gl.drawArraysInstanced(type, start, size || geometry.getSize(), instanceCount || 1); - } - else - { - gl.drawArrays(type, start, size || geometry.getSize()); - } - - return this; - } - - unbind() - { - this.gl.bindVertexArray(null); - this._activeVao = null; - this._activeGeometry = null; - } -} diff --git a/packages/core/src/renderers/systems/geometry/setVertexAttribArrays.js b/packages/core/src/renderers/systems/geometry/setVertexAttribArrays.js deleted file mode 100644 index 23b6959..0000000 --- a/packages/core/src/renderers/systems/geometry/setVertexAttribArrays.js +++ /dev/null @@ -1,54 +0,0 @@ -// var GL_MAP = {}; - -/** - * @param gl {WebGLRenderingContext} The current WebGL context - * @param attribs {*} - * @param state {*} - */ -export default function setVertexAttribArrays(gl, attribs, state) -{ - let i; - - if (state) - { - const tempAttribState = state.tempAttribState; - const attribState = state.attribState; - - for (i = 0; i < tempAttribState.length; i++) - { - tempAttribState[i] = false; - } - - // set the new attribs - for (i = 0; i < attribs.length; i++) - { - tempAttribState[attribs[i].attribute.location] = true; - } - - for (i = 0; i < attribState.length; i++) - { - if (attribState[i] !== tempAttribState[i]) - { - attribState[i] = tempAttribState[i]; - - if (state.attribState[i]) - { - gl.enableVertexAttribArray(i); - } - else - { - gl.disableVertexAttribArray(i); - } - } - } - } - else - { - for (i = 0; i < attribs.length; i++) - { - const attrib = attribs[i]; - - gl.enableVertexAttribArray(attrib.attribute.location); - } - } -} diff --git a/packages/core/src/renderers/systems/index.js b/packages/core/src/renderers/systems/index.js deleted file mode 100644 index c843f03..0000000 --- a/packages/core/src/renderers/systems/index.js +++ /dev/null @@ -1,6 +0,0 @@ -/** - * Systems are individual components to the Renderer pipeline. - * @namespace PIXI.systems - */ -export { default as WebGLSystem } from './WebGLSystem'; -export { default as FilterSystem } from './filter/FilterSystem'; diff --git a/packages/core/src/renderers/systems/shader/GLProgram.js b/packages/core/src/renderers/systems/shader/GLProgram.js deleted file mode 100644 index 92980ca..0000000 --- a/packages/core/src/renderers/systems/shader/GLProgram.js +++ /dev/null @@ -1,43 +0,0 @@ -/** - * Helper class to create a webGL Program - * - * @class - * @private - */ -export default class GLProgram -{ - constructor(program, uniformData) - { - /** - * The shader program - * - * @member {WebGLProgram} - */ - this.program = program; - - /** - * holds the uniform data which contains uniform locations - * and current uniform values used for caching and preventing undeed GPU commands - * @type {Object} - */ - this.uniformData = uniformData; - - /** - * uniformGroups holds the various upload functions for the shader. Each uniform group - * and program have a unique upload function generated. - * @type {Object} - */ - this.uniformGroups = {}; - } - - /** - * Destroys this program - * TODO - */ - destroy() - { - this.uniformData = null; - this.uniformGroups = null; - this.program = null; - } -} diff --git a/packages/core/src/renderers/systems/shader/ShaderSystem.js b/packages/core/src/renderers/systems/shader/ShaderSystem.js deleted file mode 100644 index 6cf57b6..0000000 --- a/packages/core/src/renderers/systems/shader/ShaderSystem.js +++ /dev/null @@ -1,170 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; -import GLProgram from './GLProgram'; -import generateUniformsSync from '../../../shader/generateUniformsSync'; -import defaultValue from '../../../shader/defaultValue'; -import compileProgram from './shader/compileProgram'; - -let UID = 0; - -/** - * Helper class to create a webGL Texture - * - * @class - * @memberof PIXI - */ -export default class ShaderSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - A reference to the current renderer - */ - constructor(renderer) - { - super(renderer); - - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = null; - - this.shader = null; - this.program = null; - - this.id = UID++; - } - - contextChange(gl) - { - this.gl = gl; - } - - /** - * Changes the current shader to the one given in parameter - * - * @param {PIXI.Shader} shader - the new shader - * @param {boolean} dontSync - false if the shader should automatically sync its uniforms. - * @returns {PIXI.glCore.glProgram} the glProgram that belongs to the shader. - */ - bind(shader, dontSync) - { - shader.uniforms.globals = this.renderer.globalUniforms; - - const program = shader.program; - const glProgram = program.glPrograms[this.renderer.CONTEXT_UID] || this.generateShader(shader); - - this.shader = shader; - - // TODO - some current pixi plugins bypass this.. so it not safe to use yet.. - if (this.program !== program) - { - this.program = program; - this.gl.useProgram(glProgram.program); - } - - if (!dontSync) - { - this.syncUniformGroup(shader.uniformGroup); - } - - return glProgram; - } - - /** - * Uploads the uniforms values to the currently bound shader. - * - * @param {object} uniforms - the uniforms valiues that be applied to the current shader - */ - setUniforms(uniforms) - { - const shader = this.shader.program; - const glProgram = shader.glPrograms[this.renderer.CONTEXT_UID]; - - shader.syncUniforms(glProgram.uniformData, uniforms, this.renderer); - } - - syncUniformGroup(group) - { - const glProgram = this.getglProgram(); - - if (!group.static || group.dirtyId !== glProgram.uniformGroups[group.id]) - { - glProgram.uniformGroups[group.id] = group.dirtyId; - const syncFunc = group.syncUniforms[this.shader.program.id] || this.createSyncGroups(group); - - syncFunc(glProgram.uniformData, group.uniforms, this.renderer); - } - } - - createSyncGroups(group) - { - group.syncUniforms[this.shader.program.id] = generateUniformsSync(group, this.shader.program.uniformData); - - return group.syncUniforms[this.shader.program.id]; - } - - /** - * Returns the underlying GLShade rof the currently bound shader. - * This can be handy for when you to have a little more control over the setting of your uniforms. - * - * @return {PIXI.glCore.Shader} the glProgram for the currently bound Shader for this context - */ - getglProgram() - { - if (this.shader) - { - return this.shader.program.glPrograms[this.renderer.CONTEXT_UID]; - } - - return null; - } - - /** - * Generates a glProgram verion of the Shader provided. - * - * @private - * @param {PIXI.Shader} shader the shader that the glProgram will be based on. - * @return {PIXI.glCore.glProgram} A shiney new glProgram - */ - generateShader(shader) - { - const gl = this.gl; - - const program = shader.program; - - const attribMap = {}; - - for (const i in program.attributeData) - { - attribMap[i] = program.attributeData[i].location; - } - - const shaderProgram = compileProgram(gl, program.vertexSrc, program.fragmentSrc, attribMap); - const uniformData = {}; - - for (const i in program.uniformData) - { - const data = program.uniformData[i]; - - uniformData[i] = { - location: gl.getUniformLocation(shaderProgram, i), - value: defaultValue(data.type, data.size), - }; - } - - const glProgram = new GLProgram(shaderProgram, uniformData); - - program.glPrograms[this.renderer.CONTEXT_UID] = glProgram; - - return glProgram; - } - - /** - * Destroys this System and removes all its textures - */ - destroy() - { - // TODO implement destroy method for ShaderSystem - this.destroyed = true; - } -} diff --git a/packages/core/src/renderers/systems/shader/shader/compileProgram.js b/packages/core/src/renderers/systems/shader/shader/compileProgram.js deleted file mode 100644 index 8c6ccb9..0000000 --- a/packages/core/src/renderers/systems/shader/shader/compileProgram.js +++ /dev/null @@ -1,78 +0,0 @@ -/** - * @method compileProgram - * @memberof PIXI.glCore.shader - * @param gl {WebGLRenderingContext} The current WebGL context {WebGLProgram} - * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. - * @param fragmentSrc {string|string[]} The fragment shader source as an array of strings. - * @param attributeLocations {Object} An attribute location map that lets you manually set the attribute locations - * @return {WebGLProgram} the shader program - */ -export default function compileProgram(gl, vertexSrc, fragmentSrc, attributeLocations) -{ - const glVertShader = compileShader(gl, gl.VERTEX_SHADER, vertexSrc); - const glFragShader = compileShader(gl, gl.FRAGMENT_SHADER, fragmentSrc); - - let program = gl.createProgram(); - - gl.attachShader(program, glVertShader); - gl.attachShader(program, glFragShader); - - // optionally, set the attributes manually for the program rather than letting WebGL decide.. - if (attributeLocations) - { - for (const i in attributeLocations) - { - gl.bindAttribLocation(program, attributeLocations[i], i); - } - } - - gl.linkProgram(program); - - // if linking fails, then log and cleanup - if (!gl.getProgramParameter(program, gl.LINK_STATUS)) - { - console.error('Pixi.js Error: Could not initialize shader.'); - console.error('gl.VALIDATE_STATUS', gl.getProgramParameter(program, gl.VALIDATE_STATUS)); - console.error('gl.getError()', gl.getError()); - - // if there is a program info log, log it - if (gl.getProgramInfoLog(program) !== '') - { - console.warn('Pixi.js Warning: gl.getProgramInfoLog()', gl.getProgramInfoLog(program)); - } - - gl.deleteProgram(program); - program = null; - } - - // clean up some shaders - gl.deleteShader(glVertShader); - gl.deleteShader(glFragShader); - - return program; -} - -/** - * @private - * @param gl {WebGLRenderingContext} The current WebGL context {WebGLProgram} - * @param type {Number} the type, can be either VERTEX_SHADER or FRAGMENT_SHADER - * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. - * @return {WebGLShader} the shader - */ -function compileShader(gl, type, src) -{ - const shader = gl.createShader(type); - - gl.shaderSource(shader, src); - gl.compileShader(shader); - - if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) - { - console.warn(src); - console.error(gl.getShaderInfoLog(shader)); - - return null; - } - - return shader; -} diff --git a/packages/core/src/renderers/systems/shader/shader/defaultValue.js b/packages/core/src/renderers/systems/shader/shader/defaultValue.js deleted file mode 100644 index 97290bd..0000000 --- a/packages/core/src/renderers/systems/shader/shader/defaultValue.js +++ /dev/null @@ -1,79 +0,0 @@ -/** - * @method defaultValue - * @memberof PIXI.glCore.shader - * @param type {String} Type of value - * @param size {Number} - */ -export default function defaultValue(type, size) -{ - switch (type) - { - case 'float': - return 0; - - case 'vec2': - return new Float32Array(2 * size); - - case 'vec3': - return new Float32Array(3 * size); - - case 'vec4': - return new Float32Array(4 * size); - - case 'int': - case 'sampler2D': - case 'sampler2DArray': - return 0; - - case 'ivec2': - return new Int32Array(2 * size); - - case 'ivec3': - return new Int32Array(3 * size); - - case 'ivec4': - return new Int32Array(4 * size); - - case 'bool': - return false; - - case 'bvec2': - - return booleanArray(2 * size); - - case 'bvec3': - return booleanArray(3 * size); - - case 'bvec4': - return booleanArray(4 * size); - - case 'mat2': - return new Float32Array([1, 0, - 0, 1]); - - case 'mat3': - return new Float32Array([1, 0, 0, - 0, 1, 0, - 0, 0, 1]); - - case 'mat4': - return new Float32Array([1, 0, 0, 0, - 0, 1, 0, 0, - 0, 0, 1, 0, - 0, 0, 0, 1]); - } - - return null; -} - -function booleanArray(size) -{ - const array = new Array(size); - - for (let i = 0; i < array.length; i++) - { - array[i] = false; - } - - return array; -} diff --git a/packages/core/src/renderers/systems/shader/shader/index.js b/packages/core/src/renderers/systems/shader/shader/index.js deleted file mode 100644 index ba7a317..0000000 --- a/packages/core/src/renderers/systems/shader/shader/index.js +++ /dev/null @@ -1,8 +0,0 @@ -/** - * @namespace PIXI.glCore.shader - */ -export { default as compileProgram } from './compileProgram'; -export { default as defaultValue } from './defaultValue'; -export { default as setPrecision } from './setPrecision'; -export { default as mapSize } from './mapSize'; -export { default as mapType } from './mapType'; diff --git a/packages/core/src/renderers/systems/shader/shader/mapSize.js b/packages/core/src/renderers/systems/shader/shader/mapSize.js deleted file mode 100644 index b681554..0000000 --- a/packages/core/src/renderers/systems/shader/shader/mapSize.js +++ /dev/null @@ -1,33 +0,0 @@ -const GLSL_TO_SIZE = { - float: 1, - vec2: 2, - vec3: 3, - vec4: 4, - - int: 1, - ivec2: 2, - ivec3: 3, - ivec4: 4, - - bool: 1, - bvec2: 2, - bvec3: 3, - bvec4: 4, - - mat2: 4, - mat3: 9, - mat4: 16, - - sampler2D: 1, -}; - -/** - * @method mapSize - * @memberof PIXI.glCore.shader - * @param type {String} - * @return {Number} - */ -export default function mapSize(type) -{ - return GLSL_TO_SIZE[type]; -} diff --git a/packages/core/src/renderers/systems/shader/shader/mapType.js b/packages/core/src/renderers/systems/shader/shader/mapType.js deleted file mode 100644 index d288b7c..0000000 --- a/packages/core/src/renderers/systems/shader/shader/mapType.js +++ /dev/null @@ -1,45 +0,0 @@ -let GL_TABLE = null; - -const GL_TO_GLSL_TYPES = { - FLOAT: 'float', - FLOAT_VEC2: 'vec2', - FLOAT_VEC3: 'vec3', - FLOAT_VEC4: 'vec4', - - INT: 'int', - INT_VEC2: 'ivec2', - INT_VEC3: 'ivec3', - INT_VEC4: 'ivec4', - - BOOL: 'bool', - BOOL_VEC2: 'bvec2', - BOOL_VEC3: 'bvec3', - BOOL_VEC4: 'bvec4', - - FLOAT_MAT2: 'mat2', - FLOAT_MAT3: 'mat3', - FLOAT_MAT4: 'mat4', - - SAMPLER_2D: 'sampler2D', - SAMPLER_CUBE: 'samplerCube', - SAMPLER_2D_ARRAY: 'sampler2DArray', -}; - -export default function mapType(gl, type) -{ - if (!GL_TABLE) - { - const typeNames = Object.keys(GL_TO_GLSL_TYPES); - - GL_TABLE = {}; - - for (let i = 0; i < typeNames.length; ++i) - { - const tn = typeNames[i]; - - GL_TABLE[gl[tn]] = GL_TO_GLSL_TYPES[tn]; - } - } - - return GL_TABLE[type]; -} diff --git a/packages/core/src/renderers/systems/shader/shader/setPrecision.js b/packages/core/src/renderers/systems/shader/shader/setPrecision.js deleted file mode 100644 index e2c1f34..0000000 --- a/packages/core/src/renderers/systems/shader/shader/setPrecision.js +++ /dev/null @@ -1,18 +0,0 @@ -/** - * Sets the float precision on the shader. If the precision is already present this function will do nothing - * @param {string} src the shader source - * @param {string} precision The float precision of the shader. Options are 'lowp', 'mediump' or 'highp'. - * - * @return {string} modified shader source - */ -export default function setPrecision(src, precision) -{ - src = src.trim(); - - if (src.substring(0, 9) !== 'precision' && src.substring(0, 1) !== '#') - { - return `precision ${precision} float;\n${src}`; - } - - return src; -} diff --git a/packages/core/src/renderers/systems/textures/GLTexture.js b/packages/core/src/renderers/systems/textures/GLTexture.js deleted file mode 100644 index c3fb8dc..0000000 --- a/packages/core/src/renderers/systems/textures/GLTexture.js +++ /dev/null @@ -1,33 +0,0 @@ -export default class GLTexture -{ - constructor(texture) - { - /** - * The WebGL texture - * - * @member {WebGLTexture} - */ - this.texture = texture; - - this.width = -1; - this.height = -1; - - /** - * Texture contents dirty flag - * @member {number} - */ - this.dirtyId = -1; - - /** - * Texture style dirty flag - * @type {number} - */ - this.dirtyStyleId = -1; - - /** - * Whether mip levels has to be generated - * @type {boolean} - */ - this.mipmap = false; - } -} diff --git a/packages/core/src/renderers/systems/textures/TextureGCSystem.js b/packages/core/src/renderers/systems/textures/TextureGCSystem.js deleted file mode 100644 index b089938..0000000 --- a/packages/core/src/renderers/systems/textures/TextureGCSystem.js +++ /dev/null @@ -1,110 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; -import { GC_MODES } from '@pixi/constants'; -import { settings } from '@pixi/settings'; - -/** - * TextureGarbageCollector. This class manages the GPU and ensures that it does not get clogged - * up with textures that are no longer being used. - * - * @class - * @memberof PIXI - */ -export default class TextureGCSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this.count = 0; - this.checkCount = 0; - this.maxIdle = settings.GC_MAX_IDLE; - this.checkCountMax = settings.GC_MAX_CHECK_COUNT; - this.mode = settings.GC_MODE; - } - - /** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ - postrender() - { - this.count++; - - if (this.mode === GC_MODES.MANUAL) - { - return; - } - - this.checkCount++; - - if (this.checkCount > this.checkCountMax) - { - this.checkCount = 0; - - this.run(); - } - } - - /** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ - run() - { - const tm = this.renderer.texture; - const managedTextures = tm.managedTextures; - let wasRemoved = false; - - for (let i = 0; i < managedTextures.length; i++) - { - const texture = managedTextures[i]; - - // only supports non generated textures at the moment! - if (!texture.frameBuffer && this.count - texture.touched > this.maxIdle) - { - tm.destroyTexture(texture, true); - managedTextures[i] = null; - wasRemoved = true; - } - } - - if (wasRemoved) - { - let j = 0; - - for (let i = 0; i < managedTextures.length; i++) - { - if (managedTextures[i] !== null) - { - managedTextures[j++] = managedTextures[i]; - } - } - - managedTextures.length = j; - } - } - - /** - * Removes all the textures within the specified displayObject and its children from the GPU - * - * @param {PIXI.DisplayObject} displayObject - the displayObject to remove the textures from. - */ - unload(displayObject) - { - const tm = this.renderer.textureSystem; - - // only destroy non generated textures - if (displayObject._texture && displayObject._texture._glRenderTargets) - { - tm.destroyTexture(displayObject._texture); - } - - for (let i = displayObject.children.length - 1; i >= 0; i--) - { - this.unload(displayObject.children[i]); - } - } -} diff --git a/packages/core/src/renderers/systems/textures/TextureSystem.js b/packages/core/src/renderers/systems/textures/TextureSystem.js deleted file mode 100644 index ccc5de9..0000000 --- a/packages/core/src/renderers/systems/textures/TextureSystem.js +++ /dev/null @@ -1,281 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; -import GLTexture from './GLTexture'; -import { removeItems } from '@pixi/utils'; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class TextureSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - // TODO set to max textures... - this.boundTextures = [ - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - ]; - - this.currentLocation = -1; - - this.managedTextures = []; - } - - /** - * Sets up the renderer context and necessary buffers. - * - * @private - */ - contextChange() - { - const gl = this.gl = this.renderer.gl; - - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - - // TODO move this.. to a nice make empty textures class.. - this.emptyTextures = {}; - - const emptyTexture2D = new GLTexture(gl.createTexture()); - - gl.bindTexture(gl.TEXTURE_2D, emptyTexture2D.texture); - gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array(4)); - - this.emptyTextures[gl.TEXTURE_2D] = emptyTexture2D; - this.emptyTextures[gl.TEXTURE_CUBE_MAP] = new GLTexture(gl.createTexture()); - - gl.bindTexture(gl.TEXTURE_CUBE_MAP, this.emptyTextures[gl.TEXTURE_CUBE_MAP].texture); - - let i; - - for (i = 0; i < 6; i++) - { - gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, null); - } - - gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_MAG_FILTER, gl.LINEAR); - gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_MIN_FILTER, gl.LINEAR); - - for (i = 0; i < this.boundTextures.length; i++) - { - this.bind(null, i); - } - } - - bind(texture, location) - { - const gl = this.gl; - - location = location || 0; - - if (this.currentLocation !== location) - { - this.currentLocation = location; - gl.activeTexture(gl.TEXTURE0 + location); - } - - if (texture) - { - texture = texture.baseTexture || texture; - - if (texture.valid) - { - texture.touched = this.renderer.textureGC.count; - - const glTexture = texture._glTextures[this.CONTEXT_UID] || this.initTexture(texture); - - gl.bindTexture(texture.target, glTexture.texture); - - if (glTexture.dirtyId !== texture.dirtyId) - { - this.updateTexture(texture); - } - - this.boundTextures[location] = texture; - } - } - else - { - gl.bindTexture(gl.TEXTURE_2D, this.emptyTextures[gl.TEXTURE_2D].texture); - this.boundTextures[location] = null; - } - } - - unbind(texture) - { - const gl = this.gl; - - for (let i = 0; i < this.boundTextures.length; i++) - { - if (this.boundTextures[i] === texture) - { - if (this.currentLocation !== i) - { - gl.activeTexture(gl.TEXTURE0 + i); - this.currentLocation = i; - } - - gl.bindTexture(gl.TEXTURE_2D, this.emptyTextures[texture.target].texture); - this.boundTextures[i] = null; - } - } - } - - initTexture(texture) - { - const glTexture = new GLTexture(this.gl.createTexture()); - - // guarentee an update.. - glTexture.dirtyId = -1; - - texture._glTextures[this.CONTEXT_UID] = glTexture; - - this.managedTextures.push(texture); - texture.on('dispose', this.destroyTexture, this); - - return glTexture; - } - - updateTexture(texture) - { - const glTexture = texture._glTextures[this.CONTEXT_UID]; - const renderer = this.renderer; - - if (texture.resource && texture.resource.upload(renderer, texture, glTexture)) - { - // texture is uploaded, dont do anything! - } - else - { - // default, renderTexture-like logic - const width = texture.realWidth; - const height = texture.realHeight; - const gl = renderer.gl; - - if (glTexture.width !== width - || glTexture.height !== height - || glTexture.dirtyId < 0) - { - glTexture.width = width; - glTexture.height = height; - - gl.texImage2D(texture.target, 0, - texture.format, - width, - height, - 0, - texture.format, - texture.type, - null); - } - } - - // lets only update what changes.. - if (texture.dirtyStyleId !== glTexture.dirtyStyleId) - { - this.updateTextureStyle(texture); - } - glTexture.dirtyId = texture.dirtyId; - } - - /** - * Deletes the texture from WebGL - * - * @param {PIXI.BaseTexture|PIXI.Texture} texture - the texture to destroy - * @param {boolean} [skipRemove=false] - Whether to skip removing the texture from the TextureManager. - */ - destroyTexture(texture, skipRemove) - { - const gl = this.gl; - - texture = texture.baseTexture || texture; - - if (texture._glTextures[this.renderer.CONTEXT_UID]) - { - this.unbind(texture); - - gl.deleteTexture(texture._glTextures[this.renderer.CONTEXT_UID].texture); - texture.off('dispose', this.destroyTexture, this); - - delete texture._glTextures[this.renderer.CONTEXT_UID]; - - if (!skipRemove) - { - const i = this.managedTextures.indexOf(texture); - - if (i !== -1) - { - removeItems(this.managedTextures, i, 1); - } - } - } - } - - updateTextureStyle(texture) - { - const glTexture = texture._glTextures[this.CONTEXT_UID]; - - glTexture.mipmap = texture.mipmap && texture.isPowerOfTwo; - if (!glTexture) - { - return; - } - - if (texture.resource && texture.resource.style(this.renderer, texture, glTexture)) - { - // style is set, dont do anything! - } - else - { - this.setStyle(texture, glTexture); - } - - glTexture.dirtyStyleId = texture.dirtyStyleId; - } - - setStyle(texture, glTexture) - { - const gl = this.gl; - - if (glTexture.mipmap) - { - gl.generateMipmap(texture.target); - } - - gl.texParameteri(texture.target, gl.TEXTURE_WRAP_S, texture.wrapMode); - gl.texParameteri(texture.target, gl.TEXTURE_WRAP_T, texture.wrapMode); - - if (glTexture.mipmap) - { - /* eslint-disable max-len */ - gl.texParameteri(texture.target, gl.TEXTURE_MIN_FILTER, texture.scaleMode ? gl.LINEAR_MIPMAP_LINEAR : gl.NEAREST_MIPMAP_NEAREST); - /* eslint-disable max-len */ - } - else - { - gl.texParameteri(texture.target, gl.TEXTURE_MIN_FILTER, texture.scaleMode ? gl.LINEAR : gl.NEAREST); - } - - gl.texParameteri(texture.target, gl.TEXTURE_MAG_FILTER, texture.scaleMode ? gl.LINEAR : gl.NEAREST); - } -} diff --git a/packages/core/src/renderers/utils/ObjectRenderer.js b/packages/core/src/renderers/utils/ObjectRenderer.js deleted file mode 100644 index f67a273..0000000 --- a/packages/core/src/renderers/utils/ObjectRenderer.js +++ /dev/null @@ -1,48 +0,0 @@ -import WebGLSystem from '../systems/WebGLSystem'; - -/** - * Base for a common object renderer that can be used as a system renderer plugin. - * - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class ObjectRenderer extends WebGLSystem -{ - /** - * Starts the renderer and sets the shader - * - */ - start() - { - // set the shader.. - } - - /** - * Stops the renderer - * - */ - stop() - { - this.flush(); - } - - /** - * Stub method for rendering content and emptying the current batch. - * - */ - flush() - { - // flush! - } - - /** - * Renders an object - * - * @param {PIXI.DisplayObject} object - The object to render. - */ - render(object) // eslint-disable-line no-unused-vars - { - // render the object - } -} diff --git a/packages/core/src/renderers/utils/Quad.js b/packages/core/src/renderers/utils/Quad.js deleted file mode 100644 index 5c1517b..0000000 --- a/packages/core/src/renderers/utils/Quad.js +++ /dev/null @@ -1,26 +0,0 @@ -import Geometry from '../../geometry/Geometry'; - -/** - * Helper class to create a quad - * - * @class - * @memberof PIXI - */ -export default class Quad extends Geometry -{ - /** - * just a quad - */ - constructor() - { - super(); - - this.addAttribute('aVertexPosition', [ - 0, 0, - 1, 0, - 1, 1, - 0, 1, - ]) - .addIndex([0, 1, 3, 2]); - } -} diff --git a/packages/core/src/renderers/utils/QuadUv.js b/packages/core/src/renderers/utils/QuadUv.js deleted file mode 100644 index 97bda89..0000000 --- a/packages/core/src/renderers/utils/QuadUv.js +++ /dev/null @@ -1,103 +0,0 @@ -import Geometry from '../../geometry/Geometry'; -import Buffer from '../../geometry/Buffer'; - -/** - * Helper class to create a quad with uvs like in v4 - * - * @class - * @memberof PIXI - */ -export default class QuadUv extends Geometry -{ - constructor() - { - super(); - - /** - * An array of vertices - * - * @member {Float32Array} - */ - this.vertices = new Float32Array([ - -1, -1, - 1, -1, - 1, 1, - -1, 1, - ]); - - /** - * The Uvs of the quad - * - * @member {Float32Array} - */ - this.uvs = new Float32Array([ - 0, 0, - 1, 0, - 1, 1, - 0, 1, - ]); - - this.vertexBuffer = new Buffer(this.vertices); - this.uvBuffer = new Buffer(this.uvs); - - this.addAttribute('aVertexPosition', this.vertexBuffer) - .addAttribute('aTextureCoord', this.uvBuffer) - .addIndex([0, 1, 2, 0, 2, 3]); - } - - /** - * Maps two Rectangle to the quad. - * - * @param {PIXI.Rectangle} targetTextureFrame - the first rectangle - * @param {PIXI.Rectangle} destinationFrame - the second rectangle - * @return {PIXI.Quad} Returns itself. - */ - map(targetTextureFrame, destinationFrame) - { - let x = 0; // destinationFrame.x / targetTextureFrame.width; - let y = 0; // destinationFrame.y / targetTextureFrame.height; - - this.uvs[0] = x; - this.uvs[1] = y; - - this.uvs[2] = x + (destinationFrame.width / targetTextureFrame.width); - this.uvs[3] = y; - - this.uvs[4] = x + (destinationFrame.width / targetTextureFrame.width); - this.uvs[5] = y + (destinationFrame.height / targetTextureFrame.height); - - this.uvs[6] = x; - this.uvs[7] = y + (destinationFrame.height / targetTextureFrame.height); - - x = destinationFrame.x; - y = destinationFrame.y; - - this.vertices[0] = x; - this.vertices[1] = y; - - this.vertices[2] = x + destinationFrame.width; - this.vertices[3] = y; - - this.vertices[4] = x + destinationFrame.width; - this.vertices[5] = y + destinationFrame.height; - - this.vertices[6] = x; - this.vertices[7] = y + destinationFrame.height; - - this.invalidate(); - - return this; - } - - /** - * legacy upload method, just marks buffers dirty - * @returns {PIXI.QuadUv} Returns itself. - */ - invalidate() - { - this.vertexBuffer._updateID++; - this.uvBuffer._updateID++; - - return this; - } -} diff --git a/packages/core/src/renderers/utils/checkMaxIfStatmentsInShader.js b/packages/core/src/renderers/utils/checkMaxIfStatmentsInShader.js deleted file mode 100644 index 8047c96..0000000 --- a/packages/core/src/renderers/utils/checkMaxIfStatmentsInShader.js +++ /dev/null @@ -1,58 +0,0 @@ -const fragTemplate = [ - 'precision mediump float;', - 'void main(void){', - 'float test = 0.1;', - '%forloop%', - 'gl_FragColor = vec4(0.0);', - '}', -].join('\n'); - -export default function checkMaxIfStatmentsInShader(maxIfs, gl) -{ - if (maxIfs === 0) - { - throw new Error('Invalid value of `0` passed to `checkMaxIfStatementsInShader`'); - } - - const shader = gl.createShader(gl.FRAGMENT_SHADER); - - while (true) // eslint-disable-line no-constant-condition - { - const fragmentSrc = fragTemplate.replace(/%forloop%/gi, generateIfTestSrc(maxIfs)); - - gl.shaderSource(shader, fragmentSrc); - gl.compileShader(shader); - - if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) - { - maxIfs = (maxIfs / 2) | 0; - } - else - { - // valid! - break; - } - } - - return maxIfs; -} - -function generateIfTestSrc(maxIfs) -{ - let src = ''; - - for (let i = 0; i < maxIfs; ++i) - { - if (i > 0) - { - src += '\nelse '; - } - - if (i < maxIfs - 1) - { - src += `if(test == ${i}.0){}`; - } - } - - return src; -} diff --git a/packages/core/src/renderers/utils/mapWebGLBlendModesToPixi.js b/packages/core/src/renderers/utils/mapWebGLBlendModesToPixi.js deleted file mode 100644 index 10ffe4d..0000000 --- a/packages/core/src/renderers/utils/mapWebGLBlendModesToPixi.js +++ /dev/null @@ -1,47 +0,0 @@ -import { BLEND_MODES } from '@pixi/constants'; - -/** - * Maps gl blend combinations to WebGL. - * - * @memberof PIXI - * @function mapWebGLBlendModesToPixi - * @private - * @param {WebGLRenderingContext} gl - The rendering context. - * @param {string[]} [array=[]] - The array to output into. - * @return {string[]} Mapped modes. - */ -export default function mapWebGLBlendModesToPixi(gl, array = []) -{ - // TODO - premultiply alpha would be different. - // add a boolean for that! - array[BLEND_MODES.NORMAL] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.ADD] = [gl.ONE, gl.DST_ALPHA]; - array[BLEND_MODES.MULTIPLY] = [gl.DST_COLOR, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.SCREEN] = [gl.ONE, gl.ONE_MINUS_SRC_COLOR]; - array[BLEND_MODES.OVERLAY] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.DARKEN] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.LIGHTEN] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.COLOR_DODGE] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.COLOR_BURN] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.HARD_LIGHT] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.SOFT_LIGHT] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.DIFFERENCE] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.EXCLUSION] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.HUE] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.SATURATION] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.COLOR] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.LUMINOSITY] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.NONE] = [0, 0]; - - // not-premultiplied blend modes - array[BLEND_MODES.NORMAL_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.ADD_NPM] = [gl.SRC_ALPHA, gl.DST_ALPHA, gl.ONE, gl.DST_ALPHA]; - array[BLEND_MODES.SCREEN_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_COLOR, gl.ONE, gl.ONE_MINUS_SRC_COLOR]; - - // not-premultiplied blend modes - array[BLEND_MODES.NORMAL_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.ADD_NPM] = [gl.SRC_ALPHA, gl.DST_ALPHA, gl.ONE, gl.DST_ALPHA]; - array[BLEND_MODES.SCREEN_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_COLOR, gl.ONE, gl.ONE_MINUS_SRC_COLOR]; - - return array; -} diff --git a/packages/core/src/shader/GLProgram.js b/packages/core/src/shader/GLProgram.js new file mode 100644 index 0000000..92980ca --- /dev/null +++ b/packages/core/src/shader/GLProgram.js @@ -0,0 +1,43 @@ +/** + * Helper class to create a webGL Program + * + * @class + * @private + */ +export default class GLProgram +{ + constructor(program, uniformData) + { + /** + * The shader program + * + * @member {WebGLProgram} + */ + this.program = program; + + /** + * holds the uniform data which contains uniform locations + * and current uniform values used for caching and preventing undeed GPU commands + * @type {Object} + */ + this.uniformData = uniformData; + + /** + * uniformGroups holds the various upload functions for the shader. Each uniform group + * and program have a unique upload function generated. + * @type {Object} + */ + this.uniformGroups = {}; + } + + /** + * Destroys this program + * TODO + */ + destroy() + { + this.uniformData = null; + this.uniformGroups = null; + this.program = null; + } +} diff --git a/packages/core/src/shader/Program.js b/packages/core/src/shader/Program.js index 4f9971e..4b4007a 100644 --- a/packages/core/src/shader/Program.js +++ b/packages/core/src/shader/Program.js @@ -1,6 +1,11 @@ -import * as shaderUtils from '../renderers/systems/shader/shader'; +// import * as from '../systems/shader/shader'; +import { setPrecision, + defaultValue, + compileProgram, + mapSize, + mapType, + getTestContext } from './utils'; import { ProgramCache } from '@pixi/utils'; -import getTestContext from './getTestContext'; import defaultFragment from './defaultProgram.frag'; import defaultVertex from './defaultProgram.vert'; import { settings } from '@pixi/settings'; @@ -34,8 +39,8 @@ */ this.fragmentSrc = fragmentSrc || Program.defaultFragmentSrc; - this.vertexSrc = shaderUtils.setPrecision(this.vertexSrc, settings.PRECISION_VERTEX); - this.fragmentSrc = shaderUtils.setPrecision(this.fragmentSrc, settings.PRECISION_FRAGMENT); + this.vertexSrc = setPrecision(this.vertexSrc, settings.PRECISION_VERTEX); + this.fragmentSrc = setPrecision(this.fragmentSrc, settings.PRECISION_FRAGMENT); // currently this does not extract structs only default types this.extractData(this.vertexSrc, this.fragmentSrc); @@ -62,7 +67,7 @@ if (gl) { - const program = shaderUtils.compileProgram(gl, vertexSrc, fragmentSrc); + const program = compileProgram(gl, vertexSrc, fragmentSrc); this.attributeData = this.getAttributeData(program, gl); this.uniformData = this.getUniformData(program, gl); @@ -95,13 +100,13 @@ for (let i = 0; i < totalAttributes; i++) { const attribData = gl.getActiveAttrib(program, i); - const type = shaderUtils.mapType(gl, attribData.type); + const type = mapType(gl, attribData.type); /*eslint-disable */ const data = { type: type, name: attribData.name, - size: shaderUtils.mapSize(type), + size: mapSize(type), location: 0, }; /* eslint-enable */ @@ -145,14 +150,14 @@ const name = uniformData.name.replace(/\[.*?\]/, ''); const isArray = uniformData.name.match(/\[.*?\]/, ''); - const type = shaderUtils.mapType(gl, uniformData.type); + const type = mapType(gl, uniformData.type); /*eslint-disable */ uniforms[name] = { type: type, size: uniformData.size, isArray:isArray, - value: shaderUtils.defaultValue(type, uniformData.size), + value: defaultValue(type, uniformData.size), }; /* eslint-enable */ } diff --git a/packages/core/src/shader/ShaderSystem.js b/packages/core/src/shader/ShaderSystem.js new file mode 100644 index 0000000..40b8af8 --- /dev/null +++ b/packages/core/src/shader/ShaderSystem.js @@ -0,0 +1,171 @@ +import System from '../System'; +import GLProgram from './GLProgram'; +import { generateUniformsSync, + defaultValue, + compileProgram } from './utils'; + +let UID = 0; + +/** + * Helper class to create a webGL Texture + * + * @class + * @memberof PIXI.systems + * @extends PIXI.System + */ +export default class ShaderSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - A reference to the current renderer + */ + constructor(renderer) + { + super(renderer); + + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = null; + + this.shader = null; + this.program = null; + + this.id = UID++; + } + + contextChange(gl) + { + this.gl = gl; + } + + /** + * Changes the current shader to the one given in parameter + * + * @param {PIXI.Shader} shader - the new shader + * @param {boolean} dontSync - false if the shader should automatically sync its uniforms. + * @returns {PIXI.glCore.glProgram} the glProgram that belongs to the shader. + */ + bind(shader, dontSync) + { + shader.uniforms.globals = this.renderer.globalUniforms; + + const program = shader.program; + const glProgram = program.glPrograms[this.renderer.CONTEXT_UID] || this.generateShader(shader); + + this.shader = shader; + + // TODO - some current pixi plugins bypass this.. so it not safe to use yet.. + if (this.program !== program) + { + this.program = program; + this.gl.useProgram(glProgram.program); + } + + if (!dontSync) + { + this.syncUniformGroup(shader.uniformGroup); + } + + return glProgram; + } + + /** + * Uploads the uniforms values to the currently bound shader. + * + * @param {object} uniforms - the uniforms valiues that be applied to the current shader + */ + setUniforms(uniforms) + { + const shader = this.shader.program; + const glProgram = shader.glPrograms[this.renderer.CONTEXT_UID]; + + shader.syncUniforms(glProgram.uniformData, uniforms, this.renderer); + } + + syncUniformGroup(group) + { + const glProgram = this.getglProgram(); + + if (!group.static || group.dirtyId !== glProgram.uniformGroups[group.id]) + { + glProgram.uniformGroups[group.id] = group.dirtyId; + const syncFunc = group.syncUniforms[this.shader.program.id] || this.createSyncGroups(group); + + syncFunc(glProgram.uniformData, group.uniforms, this.renderer); + } + } + + createSyncGroups(group) + { + group.syncUniforms[this.shader.program.id] = generateUniformsSync(group, this.shader.program.uniformData); + + return group.syncUniforms[this.shader.program.id]; + } + + /** + * Returns the underlying GLShade rof the currently bound shader. + * This can be handy for when you to have a little more control over the setting of your uniforms. + * + * @return {PIXI.glCore.Shader} the glProgram for the currently bound Shader for this context + */ + getglProgram() + { + if (this.shader) + { + return this.shader.program.glPrograms[this.renderer.CONTEXT_UID]; + } + + return null; + } + + /** + * Generates a glProgram verion of the Shader provided. + * + * @private + * @param {PIXI.Shader} shader the shader that the glProgram will be based on. + * @return {PIXI.glCore.glProgram} A shiney new glProgram + */ + generateShader(shader) + { + const gl = this.gl; + + const program = shader.program; + + const attribMap = {}; + + for (const i in program.attributeData) + { + attribMap[i] = program.attributeData[i].location; + } + + const shaderProgram = compileProgram(gl, program.vertexSrc, program.fragmentSrc, attribMap); + const uniformData = {}; + + for (const i in program.uniformData) + { + const data = program.uniformData[i]; + + uniformData[i] = { + location: gl.getUniformLocation(shaderProgram, i), + value: defaultValue(data.type, data.size), + }; + } + + const glProgram = new GLProgram(shaderProgram, uniformData); + + program.glPrograms[this.renderer.CONTEXT_UID] = glProgram; + + return glProgram; + } + + /** + * Destroys this System and removes all its textures + */ + destroy() + { + // TODO implement destroy method for ShaderSystem + this.destroyed = true; + } +} diff --git a/packages/core/src/shader/defaultValue.js b/packages/core/src/shader/defaultValue.js deleted file mode 100644 index 847a069..0000000 --- a/packages/core/src/shader/defaultValue.js +++ /dev/null @@ -1,78 +0,0 @@ -function booleanArray(size) -{ - const array = new Array(size); - - for (let i = 0; i < array.length; i++) - { - array[i] = false; - } - - return array; -} - -/** - * @class - * @memberof PIXI.glCore.shader - * @param type {String} Type of value - * @param size {Number} - */ -export default function defaultValue(type, size) -{ - switch (type) - { - case 'float': - return 0; - - case 'vec2': - return new Float32Array(2 * size); - - case 'vec3': - return new Float32Array(3 * size); - - case 'vec4': - return new Float32Array(4 * size); - - case 'int': - case 'sampler2D': - return 0; - - case 'ivec2': - return new Int32Array(2 * size); - - case 'ivec3': - return new Int32Array(3 * size); - - case 'ivec4': - return new Int32Array(4 * size); - - case 'bool': - return false; - - case 'bvec2': - - return booleanArray(2 * size); - - case 'bvec3': - return booleanArray(3 * size); - - case 'bvec4': - return booleanArray(4 * size); - - case 'mat2': - return new Float32Array([1, 0, - 0, 1]); - - case 'mat3': - return new Float32Array([1, 0, 0, - 0, 1, 0, - 0, 0, 1]); - - case 'mat4': - return new Float32Array([1, 0, 0, 0, - 0, 1, 0, 0, - 0, 0, 1, 0, - 0, 0, 0, 1]); - } - - return 0; -} diff --git a/packages/core/src/shader/extractUniformsFromSrc.js b/packages/core/src/shader/extractUniformsFromSrc.js deleted file mode 100644 index 3d02c0a..0000000 --- a/packages/core/src/shader/extractUniformsFromSrc.js +++ /dev/null @@ -1,57 +0,0 @@ -import defaultValue from './defaultValue'; - -export default function extractUniformsFromSrc(vertexSrc, fragmentSrc, mask) -{ - const vertUniforms = extractUniformsFromString(vertexSrc, mask); - const fragUniforms = extractUniformsFromString(fragmentSrc, mask); - - return Object.assign(vertUniforms, fragUniforms); -} - -function extractUniformsFromString(string) -{ - const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); - - const uniforms = {}; - let nameSplit; - - // clean the lines a little - remove extra spaces / teabs etc - // then split along ';' - const lines = string.replace(/\s+/g, ' ') - .split(/\s*;\s*/); - - // loop through.. - for (let i = 0; i < lines.length; i++) - { - const line = lines[i].trim(); - - if (line.indexOf('uniform') > -1) - { - const splitLine = line.split(' '); - const type = splitLine[1]; - - let name = splitLine[2]; - let size = 1; - - if (name.indexOf('[') > -1) - { - // array! - nameSplit = name.split(/\[|]/); - name = nameSplit[0]; - size *= Number(nameSplit[1]); - } - - if (!name.match(maskRegex)) - { - uniforms[name] = { - value: defaultValue(type, size), - dirtyId: 0, - name, - type, - }; - } - } - } - - return uniforms; -} diff --git a/packages/core/src/shader/generateUniformsSync.js b/packages/core/src/shader/generateUniformsSync.js deleted file mode 100644 index 98a357f..0000000 --- a/packages/core/src/shader/generateUniformsSync.js +++ /dev/null @@ -1,231 +0,0 @@ -// cv = CachedValue -// v = value -// ud = uniformData -// uv = uniformValue -// l = loaction -const GLSL_TO_SINGLE_SETTERS_CACHED = { - - float: ` - if(cv !== v) - { - cv.v = v; - gl.uniform1f(location, v) - }`, - - vec2: ` - if(cv[0] !== v[0] || cv[1] !== v[1]) - { - cv[0] = v[0]; - cv[1] = v[1]; - gl.uniform2f(location, v[0], v[1]) - }`, - - vec3: ` - if(cv[0] !== v[0] || cv[1] !== v[1] || cv[2] !== v[2]) - { - cv[0] = v[0]; - cv[1] = v[1]; - cv[2] = v[2]; - - gl.uniform3f(location, v[0], v[1], v[2]) - }`, - - vec4: 'gl.uniform4f(location, v[0], v[1], v[2], v[3])', - - int: 'gl.uniform1i(location, v)', - ivec2: 'gl.uniform2i(location, v[0], v[1])', - ivec3: 'gl.uniform3i(location, v[0], v[1], v[2])', - ivec4: 'gl.uniform4i(location, v[0], v[1], v[2], v[3])', - - bool: 'gl.uniform1i(location, v)', - bvec2: 'gl.uniform2i(location, v[0], v[1])', - bvec3: 'gl.uniform3i(location, v[0], v[1], v[2])', - bvec4: 'gl.uniform4i(location, v[0], v[1], v[2], v[3])', - - mat2: 'gl.uniformMatrix2fv(location, false, v)', - mat3: 'gl.uniformMatrix3fv(location, false, v)', - mat4: 'gl.uniformMatrix4fv(location, false, v)', - - sampler2D: 'gl.uniform1i(location, v)', - samplerCube: 'gl.uniform1i(location, v)', - sampler2DArray: 'gl.uniform1i(location, v)', -}; - -const GLSL_TO_ARRAY_SETTERS = { - - float: `gl.uniform1fv(location, v)`, - - vec2: `gl.uniform2fv(location, v)`, - vec3: `gl.uniform3fv(location, v)`, - vec4: 'gl.uniform4fv(location, v)', - - mat4: 'gl.uniformMatrix4fv(location, false, v)', - mat3: 'gl.uniformMatrix3fv(location, false, v)', - mat2: 'gl.uniformMatrix2fv(location, false, v)', - - int: 'gl.uniform1iv(location, v)', - ivec2: 'gl.uniform2iv(location, v)', - ivec3: 'gl.uniform3iv(location, v)', - ivec4: 'gl.uniform4iv(location, v)', - - bool: 'gl.uniform1iv(location, v)', - bvec2: 'gl.uniform2iv(location, v)', - bvec3: 'gl.uniform3iv(location, v)', - bvec4: 'gl.uniform4iv(location, v)', - - sampler2D: 'gl.uniform1iv(location, v)', - samplerCube: 'gl.uniform1iv(location, v)', - sampler2DArray: 'gl.uniform1iv(location, v)', -}; - -export default function generateUniformsSync(group, uniformData) -{ - let textureCount = 0; - let func = `var v = null; - var cv = null - var gl = renderer.gl`; - - for (const i in group.uniforms) - { - const data = uniformData[i]; - - if (!data) - { - if (group.uniforms[i].group) - { - func += ` - renderer.shader.syncUniformGroup(uv.${i}); - `; - } - - continue; - } - - // TODO && uniformData[i].value !== 0 <-- do we still need this? - if (data.type === 'float' && data.size === 1) - { - func += ` - if(uv.${i} !== ud.${i}.value) - { - ud.${i}.value = uv.${i} - gl.uniform1f(ud.${i}.location, uv.${i}) - }\n`; - } - /* eslint-disable max-len */ - else if ((data.type === 'sampler2D' || data.type === 'samplerCube' || data.type === 'sampler2DArray') && data.size === 1 && !data.isArray) - /* eslint-disable max-len */ - { - func += ` - renderer.texture.bind(uv.${i}, ${textureCount}); - - if(ud.${i}.value !== ${textureCount}) - { - ud.${i}.value = ${textureCount}; - gl.uniform1i(ud.${i}.location, ${textureCount});\n; // eslint-disable-line max-len - }\n`; - - textureCount++; - } - else if (data.type === 'mat3' && data.size === 1) - { - if (group.uniforms[i].a !== undefined) - { - // TODO and some smart caching dirty ids here! - func += ` - gl.uniformMatrix3fv(ud.${i}.location, false, uv.${i}.toArray(true)); - \n`; - } - else - { - func += ` - gl.uniformMatrix3fv(ud.${i}.location, false, uv.${i}); - \n`; - } - } - else if (data.type === 'vec2' && data.size === 1) - { - // TODO - do we need both here? - // maybe we can get away with only using points? - if (group.uniforms[i].x !== undefined) - { - func += ` - cv = ud.${i}.value; - v = uv.${i}; - - if(cv[0] !== v.x || cv[1] !== v.y) - { - cv[0] = v.x; - cv[1] = v.y; - gl.uniform2f(ud.${i}.location, v.x, v.y); - }\n`; - } - else - { - func += ` - cv = ud.${i}.value; - v = uv.${i}; - - if(cv[0] !== v[0] || cv[1] !== v[1]) - { - cv[0] = v[0]; - cv[1] = v[1]; - gl.uniform2f(ud.${i}.location, v[0], v[1]); - } - \n`; - } - } - else if (data.type === 'vec4' && data.size === 1) - { - // TODO - do we need both here? - // maybe we can get away with only using points? - if (group.uniforms[i].width !== undefined) - { - func += ` - cv = ud.${i}.value; - v = uv.${i}; - - if(cv[0] !== v.x || cv[1] !== v.y || cv[2] !== v.width || cv[3] !== v.height) - { - cv[0] = v.x; - cv[1] = v.y; - cv[2] = v.width; - cv[3] = v.height; - gl.uniform4f(ud.${i}.location, v.x, v.y, v.width, v.height) - }\n`; - } - else - { - func += ` - cv = ud.${i}.value; - v = uv.${i}; - - if(cv[0] !== v[0] || cv[1] !== v[1] || cv[2] !== v[2] || cv[3] !== v[3]) - { - cv[0] = v[0]; - cv[1] = v[1]; - cv[2] = v[2]; - cv[3] = v[3]; - - gl.uniform4f(ud.${i}.location, v[0], v[1], v[2], v[3]) - } - \n`; - } - } - else - { - const templateType = (data.size === 1) ? GLSL_TO_SINGLE_SETTERS_CACHED : GLSL_TO_ARRAY_SETTERS; - - const template = templateType[data.type].replace('location', `ud.${i}.location`); - - func += ` - cv = ud.${i}.value; - v = uv.${i}; - ${template};\n`; - } - } - - // console.log(' --------------- ') - // console.log(func); - - return new Function('ud', 'uv', 'renderer', func); // eslint-disable-line no-new-func -} diff --git a/packages/core/src/shader/getTestContext.js b/packages/core/src/shader/getTestContext.js deleted file mode 100644 index ea174cc..0000000 --- a/packages/core/src/shader/getTestContext.js +++ /dev/null @@ -1,48 +0,0 @@ -import { settings } from '@pixi/settings'; - -let context = null; - -/** - * returns a little webGL context to use for program inspection. - * - * @static - * @private - * @returns {webGL-context} a gl context to test with - */ -export default function getTestContext() -{ - if (!context) - { - const canvas = document.createElement('canvas'); - - let gl; - - if (settings.PREFER_WEBGL_2) - { - gl = canvas.getContext('webgl2', {}); - } - - if (!gl) - { - gl = canvas.getContext('webgl', {}) - || canvas.getContext('experimental-webgl', {}); - - if (!gl) - { - // fail, not able to get a context - throw new Error('This browser does not support webGL. Try using the canvas renderer'); - } - else - { - // for shader testing.. - gl.getExtension('WEBGL_draw_buffers'); - } - } - - context = gl; - - return gl; - } - - return context; -} diff --git a/packages/core/src/shader/utils/checkMaxIfStatmentsInShader.js b/packages/core/src/shader/utils/checkMaxIfStatmentsInShader.js new file mode 100644 index 0000000..8047c96 --- /dev/null +++ b/packages/core/src/shader/utils/checkMaxIfStatmentsInShader.js @@ -0,0 +1,58 @@ +const fragTemplate = [ + 'precision mediump float;', + 'void main(void){', + 'float test = 0.1;', + '%forloop%', + 'gl_FragColor = vec4(0.0);', + '}', +].join('\n'); + +export default function checkMaxIfStatmentsInShader(maxIfs, gl) +{ + if (maxIfs === 0) + { + throw new Error('Invalid value of `0` passed to `checkMaxIfStatementsInShader`'); + } + + const shader = gl.createShader(gl.FRAGMENT_SHADER); + + while (true) // eslint-disable-line no-constant-condition + { + const fragmentSrc = fragTemplate.replace(/%forloop%/gi, generateIfTestSrc(maxIfs)); + + gl.shaderSource(shader, fragmentSrc); + gl.compileShader(shader); + + if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) + { + maxIfs = (maxIfs / 2) | 0; + } + else + { + // valid! + break; + } + } + + return maxIfs; +} + +function generateIfTestSrc(maxIfs) +{ + let src = ''; + + for (let i = 0; i < maxIfs; ++i) + { + if (i > 0) + { + src += '\nelse '; + } + + if (i < maxIfs - 1) + { + src += `if(test == ${i}.0){}`; + } + } + + return src; +} diff --git a/packages/core/src/shader/utils/compileProgram.js b/packages/core/src/shader/utils/compileProgram.js new file mode 100644 index 0000000..8c6ccb9 --- /dev/null +++ b/packages/core/src/shader/utils/compileProgram.js @@ -0,0 +1,78 @@ +/** + * @method compileProgram + * @memberof PIXI.glCore.shader + * @param gl {WebGLRenderingContext} The current WebGL context {WebGLProgram} + * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. + * @param fragmentSrc {string|string[]} The fragment shader source as an array of strings. + * @param attributeLocations {Object} An attribute location map that lets you manually set the attribute locations + * @return {WebGLProgram} the shader program + */ +export default function compileProgram(gl, vertexSrc, fragmentSrc, attributeLocations) +{ + const glVertShader = compileShader(gl, gl.VERTEX_SHADER, vertexSrc); + const glFragShader = compileShader(gl, gl.FRAGMENT_SHADER, fragmentSrc); + + let program = gl.createProgram(); + + gl.attachShader(program, glVertShader); + gl.attachShader(program, glFragShader); + + // optionally, set the attributes manually for the program rather than letting WebGL decide.. + if (attributeLocations) + { + for (const i in attributeLocations) + { + gl.bindAttribLocation(program, attributeLocations[i], i); + } + } + + gl.linkProgram(program); + + // if linking fails, then log and cleanup + if (!gl.getProgramParameter(program, gl.LINK_STATUS)) + { + console.error('Pixi.js Error: Could not initialize shader.'); + console.error('gl.VALIDATE_STATUS', gl.getProgramParameter(program, gl.VALIDATE_STATUS)); + console.error('gl.getError()', gl.getError()); + + // if there is a program info log, log it + if (gl.getProgramInfoLog(program) !== '') + { + console.warn('Pixi.js Warning: gl.getProgramInfoLog()', gl.getProgramInfoLog(program)); + } + + gl.deleteProgram(program); + program = null; + } + + // clean up some shaders + gl.deleteShader(glVertShader); + gl.deleteShader(glFragShader); + + return program; +} + +/** + * @private + * @param gl {WebGLRenderingContext} The current WebGL context {WebGLProgram} + * @param type {Number} the type, can be either VERTEX_SHADER or FRAGMENT_SHADER + * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. + * @return {WebGLShader} the shader + */ +function compileShader(gl, type, src) +{ + const shader = gl.createShader(type); + + gl.shaderSource(shader, src); + gl.compileShader(shader); + + if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) + { + console.warn(src); + console.error(gl.getShaderInfoLog(shader)); + + return null; + } + + return shader; +} diff --git a/packages/core/src/shader/utils/defaultValue.js b/packages/core/src/shader/utils/defaultValue.js new file mode 100644 index 0000000..97290bd --- /dev/null +++ b/packages/core/src/shader/utils/defaultValue.js @@ -0,0 +1,79 @@ +/** + * @method defaultValue + * @memberof PIXI.glCore.shader + * @param type {String} Type of value + * @param size {Number} + */ +export default function defaultValue(type, size) +{ + switch (type) + { + case 'float': + return 0; + + case 'vec2': + return new Float32Array(2 * size); + + case 'vec3': + return new Float32Array(3 * size); + + case 'vec4': + return new Float32Array(4 * size); + + case 'int': + case 'sampler2D': + case 'sampler2DArray': + return 0; + + case 'ivec2': + return new Int32Array(2 * size); + + case 'ivec3': + return new Int32Array(3 * size); + + case 'ivec4': + return new Int32Array(4 * size); + + case 'bool': + return false; + + case 'bvec2': + + return booleanArray(2 * size); + + case 'bvec3': + return booleanArray(3 * size); + + case 'bvec4': + return booleanArray(4 * size); + + case 'mat2': + return new Float32Array([1, 0, + 0, 1]); + + case 'mat3': + return new Float32Array([1, 0, 0, + 0, 1, 0, + 0, 0, 1]); + + case 'mat4': + return new Float32Array([1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1]); + } + + return null; +} + +function booleanArray(size) +{ + const array = new Array(size); + + for (let i = 0; i < array.length; i++) + { + array[i] = false; + } + + return array; +} diff --git a/packages/core/src/shader/utils/extractUniformsFromSrc.js b/packages/core/src/shader/utils/extractUniformsFromSrc.js new file mode 100644 index 0000000..3d02c0a --- /dev/null +++ b/packages/core/src/shader/utils/extractUniformsFromSrc.js @@ -0,0 +1,57 @@ +import defaultValue from './defaultValue'; + +export default function extractUniformsFromSrc(vertexSrc, fragmentSrc, mask) +{ + const vertUniforms = extractUniformsFromString(vertexSrc, mask); + const fragUniforms = extractUniformsFromString(fragmentSrc, mask); + + return Object.assign(vertUniforms, fragUniforms); +} + +function extractUniformsFromString(string) +{ + const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); + + const uniforms = {}; + let nameSplit; + + // clean the lines a little - remove extra spaces / teabs etc + // then split along ';' + const lines = string.replace(/\s+/g, ' ') + .split(/\s*;\s*/); + + // loop through.. + for (let i = 0; i < lines.length; i++) + { + const line = lines[i].trim(); + + if (line.indexOf('uniform') > -1) + { + const splitLine = line.split(' '); + const type = splitLine[1]; + + let name = splitLine[2]; + let size = 1; + + if (name.indexOf('[') > -1) + { + // array! + nameSplit = name.split(/\[|]/); + name = nameSplit[0]; + size *= Number(nameSplit[1]); + } + + if (!name.match(maskRegex)) + { + uniforms[name] = { + value: defaultValue(type, size), + dirtyId: 0, + name, + type, + }; + } + } + } + + return uniforms; +} diff --git a/packages/core/src/shader/utils/generateUniformsSync.js b/packages/core/src/shader/utils/generateUniformsSync.js new file mode 100644 index 0000000..98a357f --- /dev/null +++ b/packages/core/src/shader/utils/generateUniformsSync.js @@ -0,0 +1,231 @@ +// cv = CachedValue +// v = value +// ud = uniformData +// uv = uniformValue +// l = loaction +const GLSL_TO_SINGLE_SETTERS_CACHED = { + + float: ` + if(cv !== v) + { + cv.v = v; + gl.uniform1f(location, v) + }`, + + vec2: ` + if(cv[0] !== v[0] || cv[1] !== v[1]) + { + cv[0] = v[0]; + cv[1] = v[1]; + gl.uniform2f(location, v[0], v[1]) + }`, + + vec3: ` + if(cv[0] !== v[0] || cv[1] !== v[1] || cv[2] !== v[2]) + { + cv[0] = v[0]; + cv[1] = v[1]; + cv[2] = v[2]; + + gl.uniform3f(location, v[0], v[1], v[2]) + }`, + + vec4: 'gl.uniform4f(location, v[0], v[1], v[2], v[3])', + + int: 'gl.uniform1i(location, v)', + ivec2: 'gl.uniform2i(location, v[0], v[1])', + ivec3: 'gl.uniform3i(location, v[0], v[1], v[2])', + ivec4: 'gl.uniform4i(location, v[0], v[1], v[2], v[3])', + + bool: 'gl.uniform1i(location, v)', + bvec2: 'gl.uniform2i(location, v[0], v[1])', + bvec3: 'gl.uniform3i(location, v[0], v[1], v[2])', + bvec4: 'gl.uniform4i(location, v[0], v[1], v[2], v[3])', + + mat2: 'gl.uniformMatrix2fv(location, false, v)', + mat3: 'gl.uniformMatrix3fv(location, false, v)', + mat4: 'gl.uniformMatrix4fv(location, false, v)', + + sampler2D: 'gl.uniform1i(location, v)', + samplerCube: 'gl.uniform1i(location, v)', + sampler2DArray: 'gl.uniform1i(location, v)', +}; + +const GLSL_TO_ARRAY_SETTERS = { + + float: `gl.uniform1fv(location, v)`, + + vec2: `gl.uniform2fv(location, v)`, + vec3: `gl.uniform3fv(location, v)`, + vec4: 'gl.uniform4fv(location, v)', + + mat4: 'gl.uniformMatrix4fv(location, false, v)', + mat3: 'gl.uniformMatrix3fv(location, false, v)', + mat2: 'gl.uniformMatrix2fv(location, false, v)', + + int: 'gl.uniform1iv(location, v)', + ivec2: 'gl.uniform2iv(location, v)', + ivec3: 'gl.uniform3iv(location, v)', + ivec4: 'gl.uniform4iv(location, v)', + + bool: 'gl.uniform1iv(location, v)', + bvec2: 'gl.uniform2iv(location, v)', + bvec3: 'gl.uniform3iv(location, v)', + bvec4: 'gl.uniform4iv(location, v)', + + sampler2D: 'gl.uniform1iv(location, v)', + samplerCube: 'gl.uniform1iv(location, v)', + sampler2DArray: 'gl.uniform1iv(location, v)', +}; + +export default function generateUniformsSync(group, uniformData) +{ + let textureCount = 0; + let func = `var v = null; + var cv = null + var gl = renderer.gl`; + + for (const i in group.uniforms) + { + const data = uniformData[i]; + + if (!data) + { + if (group.uniforms[i].group) + { + func += ` + renderer.shader.syncUniformGroup(uv.${i}); + `; + } + + continue; + } + + // TODO && uniformData[i].value !== 0 <-- do we still need this? + if (data.type === 'float' && data.size === 1) + { + func += ` + if(uv.${i} !== ud.${i}.value) + { + ud.${i}.value = uv.${i} + gl.uniform1f(ud.${i}.location, uv.${i}) + }\n`; + } + /* eslint-disable max-len */ + else if ((data.type === 'sampler2D' || data.type === 'samplerCube' || data.type === 'sampler2DArray') && data.size === 1 && !data.isArray) + /* eslint-disable max-len */ + { + func += ` + renderer.texture.bind(uv.${i}, ${textureCount}); + + if(ud.${i}.value !== ${textureCount}) + { + ud.${i}.value = ${textureCount}; + gl.uniform1i(ud.${i}.location, ${textureCount});\n; // eslint-disable-line max-len + }\n`; + + textureCount++; + } + else if (data.type === 'mat3' && data.size === 1) + { + if (group.uniforms[i].a !== undefined) + { + // TODO and some smart caching dirty ids here! + func += ` + gl.uniformMatrix3fv(ud.${i}.location, false, uv.${i}.toArray(true)); + \n`; + } + else + { + func += ` + gl.uniformMatrix3fv(ud.${i}.location, false, uv.${i}); + \n`; + } + } + else if (data.type === 'vec2' && data.size === 1) + { + // TODO - do we need both here? + // maybe we can get away with only using points? + if (group.uniforms[i].x !== undefined) + { + func += ` + cv = ud.${i}.value; + v = uv.${i}; + + if(cv[0] !== v.x || cv[1] !== v.y) + { + cv[0] = v.x; + cv[1] = v.y; + gl.uniform2f(ud.${i}.location, v.x, v.y); + }\n`; + } + else + { + func += ` + cv = ud.${i}.value; + v = uv.${i}; + + if(cv[0] !== v[0] || cv[1] !== v[1]) + { + cv[0] = v[0]; + cv[1] = v[1]; + gl.uniform2f(ud.${i}.location, v[0], v[1]); + } + \n`; + } + } + else if (data.type === 'vec4' && data.size === 1) + { + // TODO - do we need both here? + // maybe we can get away with only using points? + if (group.uniforms[i].width !== undefined) + { + func += ` + cv = ud.${i}.value; + v = uv.${i}; + + if(cv[0] !== v.x || cv[1] !== v.y || cv[2] !== v.width || cv[3] !== v.height) + { + cv[0] = v.x; + cv[1] = v.y; + cv[2] = v.width; + cv[3] = v.height; + gl.uniform4f(ud.${i}.location, v.x, v.y, v.width, v.height) + }\n`; + } + else + { + func += ` + cv = ud.${i}.value; + v = uv.${i}; + + if(cv[0] !== v[0] || cv[1] !== v[1] || cv[2] !== v[2] || cv[3] !== v[3]) + { + cv[0] = v[0]; + cv[1] = v[1]; + cv[2] = v[2]; + cv[3] = v[3]; + + gl.uniform4f(ud.${i}.location, v[0], v[1], v[2], v[3]) + } + \n`; + } + } + else + { + const templateType = (data.size === 1) ? GLSL_TO_SINGLE_SETTERS_CACHED : GLSL_TO_ARRAY_SETTERS; + + const template = templateType[data.type].replace('location', `ud.${i}.location`); + + func += ` + cv = ud.${i}.value; + v = uv.${i}; + ${template};\n`; + } + } + + // console.log(' --------------- ') + // console.log(func); + + return new Function('ud', 'uv', 'renderer', func); // eslint-disable-line no-new-func +} diff --git a/packages/core/src/shader/utils/getTestContext.js b/packages/core/src/shader/utils/getTestContext.js new file mode 100644 index 0000000..ea174cc --- /dev/null +++ b/packages/core/src/shader/utils/getTestContext.js @@ -0,0 +1,48 @@ +import { settings } from '@pixi/settings'; + +let context = null; + +/** + * returns a little webGL context to use for program inspection. + * + * @static + * @private + * @returns {webGL-context} a gl context to test with + */ +export default function getTestContext() +{ + if (!context) + { + const canvas = document.createElement('canvas'); + + let gl; + + if (settings.PREFER_WEBGL_2) + { + gl = canvas.getContext('webgl2', {}); + } + + if (!gl) + { + gl = canvas.getContext('webgl', {}) + || canvas.getContext('experimental-webgl', {}); + + if (!gl) + { + // fail, not able to get a context + throw new Error('This browser does not support webGL. Try using the canvas renderer'); + } + else + { + // for shader testing.. + gl.getExtension('WEBGL_draw_buffers'); + } + } + + context = gl; + + return gl; + } + + return context; +} diff --git a/packages/core/src/AbstractRenderer.js b/packages/core/src/AbstractRenderer.js new file mode 100644 index 0000000..64b2eb1 --- /dev/null +++ b/packages/core/src/AbstractRenderer.js @@ -0,0 +1,353 @@ +import { hex2string, hex2rgb } from '@pixi/utils'; +import { Matrix, Rectangle } from '@pixi/math'; +import { RENDERER_TYPE } from '@pixi/constants'; +import { settings } from '@pixi/settings'; +import { Container } from '@pixi/display'; +import RenderTexture from './renderTexture/RenderTexture'; +import EventEmitter from 'eventemitter3'; + +const tempMatrix = new Matrix(); + +/** + * 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 EventEmitter + * @memberof PIXI + */ +export default class AbstractRenderer extends EventEmitter +{ + // eslint-disable-next-line valid-jsdoc + /** + * @param {string} system - The name of the system this renderer is for. + * @param {object} [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.transparent=false] - If the render view is transparent, default false + * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false + * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. The + * resolution of the renderer retina would be 2. + * @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 {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when rendering, + * stopping pixel interpolation. + */ + constructor(system, options, arg2, arg3) + { + super(); + + // Support for constructor(system, screenWidth, screenHeight, options) + if (typeof options === 'number') + { + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + }, arg3); + } + + // 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 = RENDERER_TYPE.UNKNOWN; + + /** + * Measurements of the screen. (0, 0, screenWidth, screenHeight) + * + * Its safe to use as filterArea or hitArea for 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 1 + */ + this.resolution = options.resolution || settings.RESOLUTION; + + /** + * Whether the render view is transparent + * + * @member {boolean} + */ + this.transparent = options.transparent; + + /** + * Whether css dimensions of canvas view should be resized to screen dimensions automatically + * + * @member {boolean} + */ + this.autoResize = options.autoResize || false; + + /** + * Tracks the blend modes useful for this renderer. + * + * @member {object} + */ + this.blendModes = null; + + /** + * 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; + + /** + * If true PixiJS will Math.floor() x/y values when rendering, stopping pixel interpolation. + * Handy for crisp pixel art and speed on legacy devices. + * + * @member {boolean} + */ + this.roundPixels = options.roundPixels; + + /** + * The background color as a number. + * + * @member {number} + * @private + */ + this._backgroundColor = 0x000000; + + /** + * The background color as an [R, G, B] array. + * + * @member {number[]} + * @private + */ + this._backgroundColorRgba = [0, 0, 0, 0]; + + /** + * The background color as a string. + * + * @member {string} + * @private + */ + this._backgroundColorString = '#000000'; + + this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter + + /** + * This temporary display object used as the parent of the currently being rendered item + * + * @member {PIXI.DisplayObject} + * @private + */ + this._tempDisplayObjectParent = new Container(); + + /** + * The last root object that the renderer tried to render. + * + * @member {PIXI.DisplayObject} + * @private + */ + this._lastObjectRendered = this._tempDisplayObjectParent; + + /** + * Collection of plugins. + * @readonly + * @member {object} + */ + this.plugins = {}; + } + + /** + * Initialize the plugins. + * + * @protected + * @param {object} staticMap - The dictionary of staticly saved plugins. + */ + initPlugins(staticMap) + { + 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() + { + return this.view.width; + } + + /** + * Same as view.height, actual number of pixels in the canvas by vertical + * + * @member {number} + * @readonly + * @default 600 + */ + get height() + { + return this.view.height; + } + + /** + * Resizes the screen and canvas to the specified width and height + * Canvas dimensions are multiplied by resolution + * + * @param {number} screenWidth - the new width of the screen + * @param {number} screenHeight - the new height of the screen + */ + resize(screenWidth, screenHeight) + { + this.screen.width = screenWidth; + this.screen.height = screenHeight; + + this.view.width = screenWidth * this.resolution; + this.view.height = screenHeight * this.resolution; + + if (this.autoResize) + { + this.view.style.width = `${screenWidth}px`; + this.view.style.height = `${screenHeight}px`; + } + } + + /** + * 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 {PIXI.DisplayObject} displayObject - The displayObject the object will be generated from + * @param {number} scaleMode - Should be one of the scaleMode consts + * @param {number} resolution - The resolution / device pixel ratio of the texture being generated + * @param {PIXI.Rectangle} [region] - The region of the displayObject, that shall be rendered, + * if no region is specified, defaults to the local bounds of the displayObject. + * @return {PIXI.Texture} a texture of the graphics object + */ + generateTexture(displayObject, scaleMode, resolution, region) + { + region = region || displayObject.getLocalBounds(); + + // 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(region.width | 0, region.height | 0, scaleMode, resolution); + + tempMatrix.tx = -region.x; + tempMatrix.ty = -region.y; + + this.render(displayObject, renderTexture, false, tempMatrix, true); + + return renderTexture; + } + + /** + * Removes everything from the renderer and optionally removes the Canvas DOM element. + * + * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. + */ + destroy(removeView) + { + 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); + } + + this.plugins = null; + + this.type = RENDERER_TYPE.UNKNOWN; + + this.view = null; + + this.screen = null; + + this.resolution = 0; + + this.transparent = false; + + this.autoResize = false; + + this.blendModes = null; + + this.options = null; + + this.preserveDrawingBuffer = false; + this.clearBeforeRender = false; + + this.roundPixels = false; + + this._backgroundColor = 0; + this._backgroundColorRgba = null; + this._backgroundColorString = null; + + this._tempDisplayObjectParent = null; + this._lastObjectRendered = null; + } + + /** + * The background color to fill if not transparent + * + * @member {number} + */ + get backgroundColor() + { + return this._backgroundColor; + } + + set backgroundColor(value) // eslint-disable-line require-jsdoc + { + this._backgroundColor = value; + this._backgroundColorString = hex2string(value); + hex2rgb(value, this._backgroundColorRgba); + } +} diff --git a/packages/core/src/Renderer.js b/packages/core/src/Renderer.js new file mode 100644 index 0000000..2227e78 --- /dev/null +++ b/packages/core/src/Renderer.js @@ -0,0 +1,338 @@ +import AbstractRenderer from './AbstractRenderer'; +import { sayHello } from '@pixi/utils'; +import MaskSystem from './mask/MaskSystem'; +import StencilSystem from './mask/StencilSystem'; +import FilterSystem from './filters/FilterSystem'; +import FramebufferSystem from './framebuffer/FramebufferSystem'; +import RenderTextureSystem from './renderTexture/RenderTextureSystem'; +import TextureSystem from './textures/TextureSystem'; +import ProjectionSystem from './projection/ProjectionSystem'; +import StateSystem from './state/StateSystem'; +import GeometrySystem from './geometry/GeometrySystem'; +import ShaderSystem from './shader/ShaderSystem'; +import ContextSystem from './context/ContextSystem'; +import BatchSystem from './batch/BatchSystem'; +import TextureGCSystem from './textures/TextureGCSystem'; +import { RENDERER_TYPE } from '@pixi/constants'; +import UniformGroup from './shader/UniformGroup'; +import { Matrix } from '@pixi/math'; +import Runner from 'mini-runner'; + +/** + * 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 Render works by automatically managing webGLBatchs. + * 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 :) + * + * @class + * @memberof PIXI + * @extends PIXI.AbstractRenderer + */ +export default class Renderer extends AbstractRenderer +{ + // eslint-disable-next-line valid-jsdoc + /** + * + * @param {object} [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.transparent=false] - If the render view is transparent, default false + * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false + * @param {boolean} [options.antialias=false] - sets antialias. If not available natively then FXAA + * antialiasing is used + * @param {boolean} [options.forceFXAA=false] - forces FXAA antialiasing to be used over native. + * FXAA is faster, but may not always look as great + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. + * The resolution of the renderer retina would be 2. + * @param {boolean} [options.clearBeforeRender=true] - This sets if the renderer will clear + * the canvas or not before the new render pass. If you wish to set this to false, you *must* set + * preserveDrawingBuffer to `true`. + * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, + * enable this if you need to call toDataUrl on the webgl context. + * @param {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when + * rendering, stopping pixel interpolation. + * @param {number} [options.backgroundColor=0x000000] - The background color of the rendered area + * (shown if not transparent). + * @param {boolean} [options.legacy=false] - If true PixiJS will aim to ensure compatibility + * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. + * @param {string} [options.powerPreference] - Parameter passed to webgl context, set to "high-performance" + * for devices with dual graphics card + */ + constructor(options, arg2, arg3) + { + super('WebGL', options, arg2, arg3); + + /** + * The type of this renderer as a standardised const + * + * @member {number} + * @see PIXI.RENDERER_TYPE + */ + this.type = RENDERER_TYPE.WEBGL; + + // this will be set by the contextSystem (this.context) + this.gl = null; + this.CONTEXT_UID = 0; + this.legacy = !!options.legacy; + + // TODO legacy! + + // runners! + this.runners = { + destroy: new Runner('destroy'), + contextChange: new Runner('contextChange', 1), + reset: new Runner('reset'), + update: new Runner('update'), + postrender: new Runner('postrender'), + prerender: new Runner('prerender'), + resize: new Runner('resize', 2), + }; + + 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(GeometrySystem, 'geometry') + .addSystem(FramebufferSystem, 'framebuffer') + .addSystem(StencilSystem, 'stencil') + .addSystem(ProjectionSystem, 'projection') + .addSystem(TextureGCSystem, 'textureGC') + .addSystem(FilterSystem, 'filter') + .addSystem(RenderTextureSystem, 'renderTexture') + .addSystem(BatchSystem, 'batch'); + + this.initPlugins(Renderer.__plugins); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ + if (options.context) + { + this.context.initFromContext(options.context); + } + else + { + this.context.initFromOptions({ + alpha: this.transparent, + antialias: options.antialias, + premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', + stencil: true, + 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); + } + + /** + * Add a new system to the renderer. + * @param {class} ClassRef - Class reference + * @param {string} [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. + * @return {PIXI.Renderer} Return instance of renderer + */ + addSystem(ClassRef, name) + { + if (!name) + { + name = ClassRef.name; + } + + // TODO - read name from class.name.. + + /* + if(name.includes('System')) + { + name = name.replace('System', ''); + name = name.charAt(0).toLowerCase() + name.slice(1); + } + */ + + const system = new ClassRef(this); + + if (this[name]) + { + throw new Error(`Whoops! ${name} is already a manger`); + } + + this[name] = system; + + for (const i in this.runners) + { + this.runners[i].add(system); + } + + return this; + + /** + * 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. + */ + } + + /** + * Renders the object to its webGL view + * + * @param {PIXI.DisplayObject} displayObject - the object to be rendered + * @param {PIXI.RenderTexture} renderTexture - The render texture to render to. + * @param {boolean} [clear] - Should the canvas be cleared before the new render + * @param {PIXI.Transform} [transform] - A transform to apply to the render texture before rendering. + * @param {boolean} [skipUpdateTransform] - Should we skip the update transform pass? + */ + render(displayObject, renderTexture, clear, transform, skipUpdateTransform) + { + // can be handy to know! + this.renderingToScreen = !renderTexture; + + this.runners.prerender.run(); + this.emit('prerender'); + + // 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.parent; + + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = 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.run(); + + this.emit('postrender'); + } + + /** + * Resizes the webGL view to the specified width and height. + * + * @param {number} screenWidth - the new width of the screen + * @param {number} screenHeight - the new height of the screen + */ + resize(screenWidth, screenHeight) + { + AbstractRenderer.prototype.resize.call(this, screenWidth, screenHeight); + + this.runners.resize.run(screenWidth, screenHeight); + } + + /** + * Resets the WebGL state so you can render things however you fancy! + * + * @return {PIXI.Renderer} Returns itself. + */ + reset() + { + this.runners.reset.run(); + + return this; + } + + clear() + { + this.framebuffer.bind(); + this.framebuffer.clear(); + } + + /** + * Removes everything from the renderer (event listeners, spritebatch, etc...) + * + * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. + * See: https://github.com/pixijs/pixi.js/issues/2233 + */ + destroy(removeView) + { + this.runners.destroy.run(); + + // call base destroy + super.destroy(removeView); + + // TODO nullify all the managers.. + this.gl = null; + } + + /** + * 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 + * builds and excluding plugins. + * @name PIXI.Renderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. + */ + + /** + * Adds a plugin to the renderer. + * + * @method + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + static registerPlugin(pluginName, ctor) + { + Renderer.__plugins = Renderer.__plugins || {}; + Renderer.__plugins[pluginName] = ctor; + } +} diff --git a/packages/core/src/System.js b/packages/core/src/System.js new file mode 100644 index 0000000..05a10c8 --- /dev/null +++ b/packages/core/src/System.js @@ -0,0 +1,42 @@ +/** + * System is a base class used for extending systems used by the {@link PIXI.Renderer} + * @see PIXI.Renderer#addSystem + * @class + * @memberof PIXI + */ +export default class System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + /** + * The renderer this manager works for. + * + * @member {PIXI.Renderer} + */ + this.renderer = renderer; + + this.renderer.runners.contextChange.add(this); + } + + /** + * Generic method called when there is a WebGL context change. + * + */ + contextChange() + { + // do some codes init! + } + + /** + * Generic destroy methods to be overridden by the subclass + * + */ + destroy() + { + this.renderer.runners.contextChange.remove(this); + this.renderer = null; + } +} diff --git a/packages/core/src/batch/BatchSystem.js b/packages/core/src/batch/BatchSystem.js new file mode 100644 index 0000000..b04c249 --- /dev/null +++ b/packages/core/src/batch/BatchSystem.js @@ -0,0 +1,68 @@ +import System from '../System'; +import ObjectRenderer from './ObjectRenderer'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class BatchSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + super(renderer); + + /** + * An empty renderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.emptyRenderer = new ObjectRenderer(renderer); + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.currentRenderer = this.emptyRenderer; + } + + /** + * Changes the current renderer to the one given in parameter + * + * @param {PIXI.ObjectRenderer} objectRenderer - The object renderer to use. + */ + setObjectRenderer(objectRenderer) + { + if (this.currentRenderer === objectRenderer) + { + return; + } + + this.currentRenderer.stop(); + this.currentRenderer = objectRenderer; + + this.renderer.state.setState(objectRenderer.state); + + this.currentRenderer.start(); + } + + /** + * This should be called if you wish to do some custom rendering + * It will basically render anything that may be batched up such as sprites + * + */ + flush() + { + this.setObjectRenderer(this.emptyRenderer); + } + + reset() + { + this.setObjectRenderer(this.emptyRenderer); + } +} diff --git a/packages/core/src/batch/ObjectRenderer.js b/packages/core/src/batch/ObjectRenderer.js new file mode 100644 index 0000000..0c851a9 --- /dev/null +++ b/packages/core/src/batch/ObjectRenderer.js @@ -0,0 +1,48 @@ +import System from '../System'; + +/** + * Base for a common object renderer that can be used as a system renderer plugin. + * + * @class + * @extends PIXI.System + * @memberof PIXI + */ +export default class ObjectRenderer extends System +{ + /** + * Starts the renderer and sets the shader + * + */ + start() + { + // set the shader.. + } + + /** + * Stops the renderer + * + */ + stop() + { + this.flush(); + } + + /** + * Stub method for rendering content and emptying the current batch. + * + */ + flush() + { + // flush! + } + + /** + * Renders an object + * + * @param {PIXI.DisplayObject} object - The object to render. + */ + render(object) // eslint-disable-line no-unused-vars + { + // render the object + } +} diff --git a/packages/core/src/context/ContextSystem.js b/packages/core/src/context/ContextSystem.js new file mode 100644 index 0000000..301aab4 --- /dev/null +++ b/packages/core/src/context/ContextSystem.js @@ -0,0 +1,186 @@ +import System from '../System'; +import { settings } from '@pixi/settings'; + +let CONTEXT_UID = 0; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class ContextSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.webGLVersion = 1; + + this.handleContextLost = this.handleContextLost.bind(this); + this.handleContextRestored = this.handleContextRestored.bind(this); + + this.extensions = {}; + + renderer.view.addEventListener('webglcontextlost', this.handleContextLost, false); + renderer.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + } + + get isLost() + { + return (!this.gl || this.gl.isContextLost()); + } + + contextChange(gl) + { + this.gl = gl; + + // restore a context if it was previously lost + if (gl.isContextLost() && gl.getExtension('WEBGL_lose_context')) + { + gl.getExtension('WEBGL_lose_context').restoreContext(); + } + } + + initFromContext(gl) + { + this.gl = gl; + this.validateContext(gl); + this.renderer.gl = gl; + this.renderer.CONTEXT_UID = CONTEXT_UID++; + this.renderer.runners.contextChange.run(gl); + } + + initFromOptions(options) + { + const gl = this.createContext(this.renderer.view, options); + + this.initFromContext(gl); + } + + /** + * Helper class to create a webGL Context + * + * @class + * @memberof PIXI.glCore + * @param canvas {HTMLCanvasElement} the canvas element that we will get the context from + * @param options {Object} An options object that gets passed in to the canvas element containing the context attributes, + * see https://developer.mozilla.org/en/docs/Web/API/HTMLCanvasElement/getContext for the options available + * @return {WebGLRenderingContext} the WebGL context + */ + createContext(canvas, options) + { + let gl; + + if (settings.PREFER_WEBGL_2) + { + gl = canvas.getContext('webgl2', options); + } + + if (gl) + { + this.webGLVersion = 2; + } + else + { + this.webGLVersion = 1; + + gl = canvas.getContext('webgl', options) + || canvas.getContext('experimental-webgl', options); + + if (!gl) + { + // fail, not able to get a context + throw new Error('This browser does not support webGL. Try using the canvas renderer'); + } + } + + this.gl = gl; + + this.getExtensions(); + + return gl; + } + + getExtensions() + { + // time to set up default etensions that pixi uses.. + const gl = this.gl; + const extensions = this.extensions; + + if (this.webGLVersion === 1) + { + extensions.drawBuffers = gl.getExtension('WEBGL_draw_buffers'); + extensions.depthTexture = gl.getExtension('WEBKIT_WEBGL_depth_texture'); + extensions.floatTexture = gl.getExtension('OES_texture_float'); + extensions.loseContext = gl.getExtension('WEBGL_lose_context'); + + extensions.vertexArrayObject = gl.getExtension('OES_vertex_array_object') + || gl.getExtension('MOZ_OES_vertex_array_object') + || gl.getExtension('WEBKIT_OES_vertex_array_object'); + } + + // we don't use any specific WebGL 2 ones yet! + } + + /** + * Handles a lost webgl context + * + * @private + * @param {WebGLContextEvent} event - The context lost event. + */ + handleContextLost(event) + { + event.preventDefault(); + } + + /** + * Handles a restored webgl context + * + * @private + */ + handleContextRestored() + { + this.renderer.runners.contextChange.run(this.gl); + } + + destroy() + { + const view = this.renderer.view; + + // remove listeners + view.removeEventListener('webglcontextlost', this.handleContextLost); + view.removeEventListener('webglcontextrestored', this.handleContextRestored); + + this.gl.useProgram(null); + + if (this.extensions.loseContext) + { + this.extensions.loseContext.loseContext(); + } + } + + postrender() + { + this.gl.flush(); + } + + validateContext(gl) + { + const attributes = gl.getContextAttributes(); + + // this is going to be fairly simple for now.. but at least we have room to grow! + if (!attributes.stencil) + { + /* eslint-disable max-len */ + + /* eslint-disable no-console */ + console.warn('Provided WebGL context does not have a stencil buffer, masks may not render correctly'); + /* eslint-enable no-console */ + + /* eslint-enable max-len */ + } + } +} diff --git a/packages/core/src/filters/Filter.js b/packages/core/src/filters/Filter.js new file mode 100644 index 0000000..9d8cad8 --- /dev/null +++ b/packages/core/src/filters/Filter.js @@ -0,0 +1,140 @@ +import Shader from '../shader/Shader'; +import Program from '../shader/Program'; +import State from '../state/State'; +import { settings } from '@pixi/settings'; +// import extractUniformsFromSrc from './extractUniformsFromSrc'; +import defaultVertex from './defaultFilter.vert'; +import defaultFragment from './defaultFilter.frag'; + +// let math = require('../../math'); +/** + * @class + * @memberof PIXI + * @extends PIXI.Shader + */ +export default class Filter extends Shader +{ + /** + * @param {string} [vertexSrc] - The source of the vertex shader. + * @param {string} [fragmentSrc] - The source of the fragment shader. + * @param {object} [uniforms] - Custom uniforms to use to augment the built-in ones. + */ + constructor(vertexSrc, fragmentSrc, uniforms) + { + const program = Program.from(vertexSrc, fragmentSrc); + + super(program, uniforms); + + /** + * The padding of the filter. Some filters require extra space to breath such as a blur. + * Increasing this will add extra width and height to the bounds of the object that the + * filter is applied to. + * + * @member {number} + */ + this.padding = 0; + + /** + * The resolution of the filter. Setting this to be lower will lower the quality but + * increase the performance of the filter. + * + * @member {number} + */ + this.resolution = settings.RESOLUTION; + + /** + * If enabled is true the filter is applied, if false it will not. + * + * @member {boolean} + */ + this.enabled = true; + + /** + * If enabled, PixiJS will fit the filter area into boundaries for better performance. + * Switch it off if it does not work for specific shader. + * + * @member {boolean} + */ + this.autoFit = true; + + /** + * Legacy filters use position and uvs from attributes + * @member {boolean} + * @readonly + */ + this.legacy = !!this.program.attributeData.aTextureCoord; + + /** + * the webGL state the filter requires to render + * @member {PIXI.State} + */ + this.state = new State(); + } + + /** + * Applies the filter + * + * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + * @param {boolean} clear - Should the output be cleared before rendering to it + * @param {object} [currentState] - It's current state of filter. + * There are some useful properties in the currentState : + * target, filters, sourceFrame, destinationFrame, renderTarget, resolution + */ + apply(filterManager, input, output, clear, currentState, derp) // eslint-disable-line no-unused-vars + { + // do as you please! + + filterManager.applyFilter(this, input, output, clear, currentState, derp); + + // or just do a regular render.. + } + + /** + * Sets the blendmode of the filter + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL + */ + get blendMode() + { + return this.state.blendMode; + } + + set blendMode(value) // eslint-disable-line require-jsdoc + { + this.state.blendMode = value; + } + + /** + * The default vertex shader source + * + * @static + * @constant + */ + static get defaultVertexSrc() + { + return defaultVertex; + } + + /** + * The default fragment shader source + * + * @static + * @constant + */ + static get defaultFragmentSrc() + { + return defaultFragment; + } +} + +/** + * Used for caching shader IDs + * + * @static + * @private + */ +Filter.SOURCE_KEY_MAP = {}; + diff --git a/packages/core/src/filters/FilterSystem.js b/packages/core/src/filters/FilterSystem.js new file mode 100644 index 0000000..3174318 --- /dev/null +++ b/packages/core/src/filters/FilterSystem.js @@ -0,0 +1,431 @@ +import System from '../System'; + +import RenderTexture from '../renderTexture/RenderTexture'; +import Quad from '../utils/Quad'; +import QuadUv from '../utils/QuadUv'; +import { Rectangle } from '@pixi/math'; +import * as filterTransforms from './filterTransforms'; +import bitTwiddle from 'bit-twiddle'; +import UniformGroup from '../shader/UniformGroup'; +import { DRAW_MODES } from '@pixi/constants'; + +// +/** + * @ignore + * @class + */ +class FilterState +{ + /** + * + */ + constructor() + { + this.renderTexture = null; + this.sourceFrame = new Rectangle(); + this.destinationFrame = new Rectangle(); + this.filters = []; + this.target = null; + this.legacy = false; + this.resolution = 1; + } +} + +/** + * @class + * @memberof PIXI.systems + * @extends PIXI.System + */ +export default class FilterSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + /** + * stores a bunch of PO2 textures used for filtering + * @type {Object} + */ + this.texturePool = {}; + + /** + * a pool for storing filter states, save us creating new ones each tick + * @type {Array} + */ + this.statePool = []; + + /** + * A very simple geometry used when drawing a filter effect to the screen + * @type {Quad} + */ + this.quad = new Quad(); + + this.quadUv = new QuadUv(); + + /** + * Temporary rect for maths + * @type {PIXI.Rectangle} + */ + this.tempRect = new Rectangle(); + + this.activeState = {}; + + /** + * this uniform group is attached to filter uniforms when used + * @type {UniformGroup} + */ + this.globalUniforms = new UniformGroup({ + sourceFrame: this.tempRect, + destinationFrame: this.tempRect, + + // legacy variables + filterArea: new Float32Array(4), + filterClamp: new Float32Array(4), + }, true); + } + + /** + * Adds a new filter to the System. + * + * @param {PIXI.DisplayObject} target - The target of the filter to render. + * @param {PIXI.Filter[]} filters - The filters to apply. + */ + push(target, filters) + { + const renderer = this.renderer; + const filterStack = this.renderer.renderTexture.defaultFilterStack; + const state = this.statePool.pop() || new FilterState(); + + let resolution = filters[0].resolution; + let padding = filters[0].padding; + let autoFit = filters[0].autoFit; + let legacy = filters[0].legacy; + + for (let i = 1; i < filters.length; i++) + { + const filter = filters[i]; + + // lets use the lowest resolution.. + resolution = Math.min(resolution, filter.resolution); + // and the largest amount of padding! + padding = Math.max(padding, filter.padding); + // only auto fit if all filters are autofit + autoFit = autoFit || filter.autoFit; + + legacy = legacy || filter.legacy; + } + + filterStack.push(state); + + state.resolution = resolution; + + state.legacy = legacy; + + // round to whole number based on resolution + // TODO move that to the shader too? + state.sourceFrame = target.filterArea || target.getBounds(true); + + state.sourceFrame.pad(padding); + + if (autoFit) + { + state.sourceFrame.fit(this.renderer.renderTexture.destinationFrame); + } + + state.sourceFrame.round(resolution); + + state.renderTexture = this.getPotFilterTexture(state.sourceFrame.width, state.sourceFrame.height, resolution); + state.filters = filters; + + state.destinationFrame.width = state.renderTexture.width; + state.destinationFrame.height = state.renderTexture.height; + + state.renderTexture.filterFrame = state.sourceFrame; + renderer.renderTexture.bind(state.renderTexture, state.sourceFrame);// /, state.destinationFrame); + renderer.renderTexture.clear(); + } + + /** + * Pops off the filter and applies it. + * + */ + pop() + { + const renderer = this.renderer; + const filterStack = renderer.renderTexture.defaultFilterStack; + const state = filterStack.pop(); + const filters = state.filters; + + this.activeState = state; + + const globalUniforms = this.globalUniforms.uniforms; + + globalUniforms.sourceFrame = state.sourceFrame; + globalUniforms.destinationFrame = state.destinationFrame; + globalUniforms.resolution = state.resolution; + + // only update the rect if its legacy.. + if (state.legacy) + { + const filterArea = globalUniforms.filterArea; + const filterClamp = globalUniforms.filterClamp; + + filterArea[0] = state.destinationFrame.width; + filterArea[1] = state.destinationFrame.height; + filterArea[2] = state.sourceFrame.x; + filterArea[3] = state.sourceFrame.y; + + filterClamp[0] = 0.5 / state.resolution / state.destinationFrame.width; + filterClamp[1] = 0.5 / state.resolution / state.destinationFrame.height; + filterClamp[2] = (state.sourceFrame.width - 0.5) / state.resolution / state.destinationFrame.width; + filterClamp[3] = (state.sourceFrame.height - 0.5) / state.resolution / state.destinationFrame.height; + } + + this.globalUniforms.update(); + + const lastState = filterStack[filterStack.length - 1]; + + if (filters.length === 1) + { + filters[0].apply(this, state.renderTexture, lastState.renderTexture, false, state); + + this.returnFilterTexture(state.renderTexture); + } + else + { + let flip = state.renderTexture; + let flop = this.getPotFilterTexture( + flip.width, + flip.height, + state.resolution + ); + + flop.filterFrame = flip.filterFrame; + + let i = 0; + + for (i = 0; i < filters.length - 1; ++i) + { + filters[i].apply(this, flip, flop, true, state); + + const t = flip; + + flip = flop; + flop = t; + } + + filters[i].apply(this, flip, lastState.renderTexture, false, state); + + this.returnFilterTexture(flip); + this.returnFilterTexture(flop); + } + + this.statePool.push(state); + } + + /** + * Draws a filter. + * + * @param {PIXI.Filter} filter - The filter to draw. + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + * @param {boolean} clear - Should the output be cleared before rendering to it + */ + applyFilter(filter, input, output, clear) + { + const renderer = this.renderer; + + renderer.renderTexture.bind(output, output ? output.filterFrame : null); + + if (clear) + { + // gl.disable(gl.SCISSOR_TEST); + renderer.renderTexture.clear(); + // gl.enable(gl.SCISSOR_TEST); + } + + // set the uniforms.. + filter.uniforms.uSampler = input; + filter.uniforms.filterGlobals = this.globalUniforms; + + // TODO make it so that the order of this does not matter.. + // because it does at the moment cos of global uniforms. + // they need to get resynced + + renderer.state.setState(filter.state); + renderer.shader.bind(filter); + + if (filter.legacy) + { + this.quadUv.map(input._frame, input.filterFrame); + + renderer.geometry.bind(this.quadUv); + renderer.geometry.draw(DRAW_MODES.TRIANGLES); + } + else + { + renderer.geometry.bind(this.quad); + renderer.geometry.draw(DRAW_MODES.TRIANGLE_STRIP); + } + } + + /** + * Calculates the mapped matrix. + * + * TODO playing around here.. this is temporary - (will end up in the shader) + * this returns a matrix that will normalise map filter cords in the filter to screen space + * + * @param {PIXI.Matrix} outputMatrix - the matrix to output to. + * @return {PIXI.Matrix} The mapped matrix. + */ + calculateScreenSpaceMatrix(outputMatrix) + { + const currentState = this.activeState; + + return filterTransforms.calculateScreenSpaceMatrix( + outputMatrix, + currentState.sourceFrame, + currentState.destinationFrame + ); + } + + /** + * This will map the filter coord so that a texture can be used based on the transform of a sprite + * + * @param {PIXI.Matrix} outputMatrix - The matrix to output to. + * @param {PIXI.Sprite} sprite - The sprite to map to. + * @return {PIXI.Matrix} The mapped matrix. + */ + calculateSpriteMatrix(outputMatrix, sprite) + { + const currentState = this.activeState; + + return filterTransforms.calculateSpriteMatrix( + outputMatrix, + currentState.sourceFrame, + currentState.destinationFrame, + sprite + ); + } + + /** + * Destroys this Filter System. + * + * @param {boolean} [contextLost=false] context was lost, do not free shaders + * + */ + destroy(contextLost = false) + { + if (!contextLost) + { + this.emptyPool(); + } + else + { + this.texturePool = {}; + } + } + + /** + * Gets a Power-of-Two render texture. + * + * TODO move to a seperate class could be on renderer? + * also - could cause issue with multiple contexts? + * + * @private + * @param {WebGLRenderingContext} gl - The webgl rendering context + * @param {number} minWidth - The minimum width of the render target. + * @param {number} minHeight - The minimum height of the render target. + * @param {number} resolution - The resolution of the render target. + * @return {PIXI.RenderTarget} The new render target. + */ + getPotFilterTexture(minWidth, minHeight, resolution) + { + minWidth = bitTwiddle.nextPow2(minWidth); + minHeight = bitTwiddle.nextPow2(minHeight); + resolution = resolution || 1; + + const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + + if (!this.texturePool[key]) + { + this.texturePool[key] = []; + } + + let renderTexture = this.texturePool[key].pop(); + + if (!renderTexture) + { + // temporary bypass cache.. + // internally - this will cause a texture to be bound.. + renderTexture = RenderTexture.create({ + width: minWidth, + height: minHeight, + resolution, + }); + } + + return renderTexture; + } + + /** + * Gets extra render texture to use inside current filter + * + * @param {number} resolution resolution of the renderTexture + * @returns {PIXI.RenderTexture} + */ + getFilterTexture(resolution) + { + const rt = this.activeState.renderTexture; + + const filterTexture = this.getPotFilterTexture(rt.width, rt.height, resolution || rt.baseTexture.resolution); + + filterTexture.filterFrame = rt.filterFrame; + + return filterTexture; + } + + /** + * Frees a render texture back into the pool. + * + * @param {PIXI.RenderTarget} renderTexture - The renderTarget to free + */ + returnFilterTexture(renderTexture) + { + renderTexture.filterFrame = null; + + const base = renderTexture.baseTexture; + + const minWidth = base.width; + const minHeight = base.height; + + const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + + this.texturePool[key].push(renderTexture); + } + + /** + * Empties the texture pool. + * + */ + emptyPool() + { + for (const i in this.texturePool) + { + const textures = this.texturePool[i]; + + if (textures) + { + for (let j = 0; j < textures.length; j++) + { + textures[j].destroy(true); + } + } + } + + this.texturePool = {}; + } +} diff --git a/packages/core/src/filters/defaultFilter.frag b/packages/core/src/filters/defaultFilter.frag new file mode 100644 index 0000000..a14c1be --- /dev/null +++ b/packages/core/src/filters/defaultFilter.frag @@ -0,0 +1,22 @@ +varying vec2 vTextureCoord; +varying vec2 vFilterCoord; + +uniform sampler2D uSampler; +uniform sampler2D filterSampler; + +void main(void){ + vec4 masky = texture2D(filterSampler, vFilterCoord); + vec4 sample = texture2D(uSampler, vTextureCoord); + vec4 color; + if(mod(vFilterCoord.x, 1.0) > 0.5) + { + color = vec4(1.0, 0.0, 0.0, 1.0); + } + else + { + color = vec4(0.0, 1.0, 0.0, 1.0); + } + // gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0); + gl_FragColor = mix(sample, masky, 0.5); + gl_FragColor *= sample.a; +} diff --git a/packages/core/src/filters/defaultFilter.vert b/packages/core/src/filters/defaultFilter.vert new file mode 100644 index 0000000..e1febca --- /dev/null +++ b/packages/core/src/filters/defaultFilter.vert @@ -0,0 +1,14 @@ +attribute vec2 aVertexPosition; +attribute vec2 aTextureCoord; + +uniform mat3 projectionMatrix; +uniform mat3 filterMatrix; + +varying vec2 vTextureCoord; +varying vec2 vFilterCoord; + +void main(void){ + gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); + vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy; + vTextureCoord = aTextureCoord ; +} diff --git a/packages/core/src/filters/filterTransforms.js b/packages/core/src/filters/filterTransforms.js new file mode 100644 index 0000000..87a3cd6 --- /dev/null +++ b/packages/core/src/filters/filterTransforms.js @@ -0,0 +1,50 @@ +import { Matrix } from '@pixi/math'; + +/** + * Calculates the mapped matrix + * @param filterArea {Rectangle} The filter area + * @param sprite {Sprite} the target sprite + * @param outputMatrix {Matrix} @alvin + * @private + */ +// this returns a matrix that will normalise map filter cords in the filter to screen space +export function calculateScreenSpaceMatrix(outputMatrix, filterArea, textureSize) +{ + // TODO unwrap? + const mappedMatrix = outputMatrix.identity(); + + mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); + + mappedMatrix.scale(textureSize.width, textureSize.height); + + return mappedMatrix; +} + +export function calculateNormalizedScreenSpaceMatrix(outputMatrix, filterArea, textureSize) +{ + const mappedMatrix = outputMatrix.identity(); + + mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); + + const translateScaleX = (textureSize.width / filterArea.width); + const translateScaleY = (textureSize.height / filterArea.height); + + mappedMatrix.scale(translateScaleX, translateScaleY); + + return mappedMatrix; +} + +// this will map the filter coord so that a texture can be used based on the transform of a sprite +export function calculateSpriteMatrix(outputMatrix, filterArea, textureSize, sprite) +{ + const orig = sprite._texture.orig; + const mappedMatrix = outputMatrix.set(textureSize.width, 0, 0, textureSize.height, filterArea.x, filterArea.y); + const worldTransform = sprite.worldTransform.copyTo(Matrix.TEMP_MATRIX); + + worldTransform.invert(); + mappedMatrix.prepend(worldTransform); + mappedMatrix.scale(1.0 / orig.width, 1.0 / orig.height); + mappedMatrix.translate(sprite.anchor.x, sprite.anchor.y); + + return mappedMatrix; +} diff --git a/packages/core/src/filters/spriteMask/SpriteMaskFilter.js b/packages/core/src/filters/spriteMask/SpriteMaskFilter.js new file mode 100644 index 0000000..91697b7 --- /dev/null +++ b/packages/core/src/filters/spriteMask/SpriteMaskFilter.js @@ -0,0 +1,63 @@ +import Filter from '../Filter'; +import { Matrix } from '@pixi/math'; +import vertex from './spriteMaskFilter.vert'; +import fragment from './spriteMaskFilter.frag'; +import { default as TextureMatrix } from '../../textures/TextureMatrix'; + +/** + * The SpriteMaskFilter class + * + * @class + * @extends PIXI.Filter + * @memberof PIXI + */ +export default class SpriteMaskFilter extends Filter +{ + /** + * @param {PIXI.Sprite} sprite - the target sprite + */ + constructor(sprite) + { + const maskMatrix = new Matrix(); + + super(vertex, fragment); + + sprite.renderable = false; + + this.maskSprite = sprite; + this.maskMatrix = maskMatrix; + } + + /** + * Applies the filter + * + * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + */ + apply(filterManager, input, output) + { + const maskSprite = this.maskSprite; + const tex = this.maskSprite.texture; + + if (!tex.valid) + { + return; + } + if (!tex.transform) + { + // margin = 0.0, let it bleed a bit, shader code becomes easier + // assuming that atlas textures were made with 1-pixel padding + tex.transform = new TextureMatrix(tex, 0.0); + } + tex.transform.update(); + + this.uniforms.mask = tex; + this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite) + .prepend(tex.transform.mapCoord); + this.uniforms.alpha = maskSprite.worldAlpha; + this.uniforms.maskClamp = tex.transform.uClampFrame; + + filterManager.applyFilter(this, input, output); + } +} diff --git a/packages/core/src/filters/spriteMask/spriteMaskFilter.frag b/packages/core/src/filters/spriteMask/spriteMaskFilter.frag new file mode 100644 index 0000000..0e4aef8 --- /dev/null +++ b/packages/core/src/filters/spriteMask/spriteMaskFilter.frag @@ -0,0 +1,23 @@ +varying vec2 vMaskCoord; +varying vec2 vTextureCoord; + +uniform sampler2D uSampler; +uniform sampler2D mask; +uniform float alpha; +uniform vec4 maskClamp; + +void main(void) +{ + float clip = step(3.5, + step(maskClamp.x, vMaskCoord.x) + + step(maskClamp.y, vMaskCoord.y) + + step(vMaskCoord.x, maskClamp.z) + + step(vMaskCoord.y, maskClamp.w)); + + vec4 original = texture2D(uSampler, vTextureCoord); + vec4 masky = texture2D(mask, vMaskCoord); + + original *= (masky.r * masky.a * alpha * clip); + + gl_FragColor = original; +} diff --git a/packages/core/src/filters/spriteMask/spriteMaskFilter.vert b/packages/core/src/filters/spriteMask/spriteMaskFilter.vert new file mode 100644 index 0000000..d23a30c --- /dev/null +++ b/packages/core/src/filters/spriteMask/spriteMaskFilter.vert @@ -0,0 +1,16 @@ +attribute vec2 aVertexPosition; +attribute vec2 aTextureCoord; + +uniform mat3 projectionMatrix; +uniform mat3 otherMatrix; + +varying vec2 vMaskCoord; +varying vec2 vTextureCoord; + +void main(void) +{ + gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); + + vTextureCoord = aTextureCoord; + vMaskCoord = ( otherMatrix * vec3( aTextureCoord, 1.0) ).xy; +} diff --git a/packages/core/src/framebuffer/FrameBuffer.js b/packages/core/src/framebuffer/FrameBuffer.js new file mode 100644 index 0000000..84e9fcc --- /dev/null +++ b/packages/core/src/framebuffer/FrameBuffer.js @@ -0,0 +1,106 @@ +import Texture from '../textures/BaseTexture'; +import { FORMATS, TYPES } from '@pixi/constants'; + +/** + * Frame buffer + * @class + * @memberof PIXI + */ +export default class FrameBuffer +{ + constructor(width, height) + { + this.width = width || 100; + this.height = height || 100; + + this.stencil = false; + this.depth = false; + + this.dirtyId = 0; + this.dirtyFormat = 0; + this.dirtySize = 0; + + this.depthTexture = null; + this.colorTextures = []; + + this.glFrameBuffers = {}; + } + + get colorTexture() + { + return this.colorTextures[0]; + } + + addColorTexture(index, texture) + { + // TODO add some validation to the texture - same width / height etc? + this.colorTextures[index || 0] = texture || new Texture(null, { scaleMode: 0, + resolution: 1, + mipmap: false, + width: this.width, + height: this.height });// || new Texture(); + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + addDepthTexture(texture) + { + /* eslint-disable max-len */ + this.depthTexture = texture || new Texture(null, { scaleMode: 0, + resolution: 1, + width: this.width, + height: this.height, + mipmap: false, + format: FORMATS.DEPTH_COMPONENT, + type: TYPES.UNSIGNED_SHORT });// UNSIGNED_SHORT; + /* eslint-disable max-len */ + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + enableDepth() + { + this.depth = true; + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + enableStencil() + { + this.stencil = true; + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + resize(width, height) + { + if (width === this.width && height === this.height) return; + + this.width = width; + this.height = height; + + this.dirtyId++; + this.dirtySize++; + + for (let i = 0; i < this.colorTextures.length; i++) + { + this.colorTextures[i].setSize(width, height); + } + + if (this.depthTexture) + { + this.depthTexture.setSize(width, height); + } + } +} diff --git a/packages/core/src/framebuffer/FramebufferSystem.js b/packages/core/src/framebuffer/FramebufferSystem.js new file mode 100644 index 0000000..2cc2437 --- /dev/null +++ b/packages/core/src/framebuffer/FramebufferSystem.js @@ -0,0 +1,246 @@ +import System from '../System'; +import { Rectangle } from '@pixi/math'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class FramebufferSystem extends System +{ + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + contextChange() + { + this.gl = this.renderer.gl; + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + this.current = null; + this.viewport = new Rectangle(); + + this.drawBufferExtension = this.renderer.context.extensions.drawBuffers; + } + + bind(framebuffer, frame) + { + const gl = this.gl; + + this.current = framebuffer; + + if (framebuffer) + { + // TODO cacheing layer! + + const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID] || this.initFramebuffer(framebuffer); + + gl.bindFramebuffer(gl.FRAMEBUFFER, fbo.framebuffer); + // makesure all textures are unbound.. + + // now check for updates... + if (fbo.dirtyId !== framebuffer.dirtyId) + { + fbo.dirtyId = framebuffer.dirtyId; + + if (fbo.dirtyFormat !== framebuffer.dirtyFormat) + { + fbo.dirtyFormat = framebuffer.dirtyFormat; + this.updateFramebuffer(framebuffer); + } + else if (fbo.dirtySize !== framebuffer.dirtySize) + { + fbo.dirtySize = framebuffer.dirtySize; + this.resizeFramebuffer(framebuffer); + } + } + + for (let i = 0; i < framebuffer.colorTextures.length; i++) + { + if (framebuffer.colorTextures[i].texturePart) + { + this.renderer.texture.unbind(framebuffer.colorTextures[i].texture); + } + else + { + this.renderer.texture.unbind(framebuffer.colorTextures[i]); + } + } + + if (framebuffer.depthTexture) + { + this.renderer.texture.unbind(framebuffer.depthTexture); + } + + if (frame) + { + this.setViewport(frame.x, frame.y, frame.width, frame.height); + } + else + { + this.setViewport(0, 0, framebuffer.width, framebuffer.height); + } + } + else + { + gl.bindFramebuffer(gl.FRAMEBUFFER, null); + + if (frame) + { + this.setViewport(frame.x, frame.y, frame.width, frame.height); + } + else + { + this.setViewport(0, 0, this.renderer.width, this.renderer.height); + } + } + } + + setViewport(x, y, width, height) + { + const v = this.viewport; + + if (v.width !== width || v.height !== height || v.x !== x || v.y !== y) + { + v.x = x; + v.y = y; + v.width = width; + v.height = height; + + this.gl.viewport(x, y, width, height); + } + } + + get size() + { + if (this.current) + { + // TODO store temp + return { x: 0, y: 0, width: this.current.width, height: this.current.height }; + } + + return { x: 0, y: 0, width: this.renderer.width, height: this.renderer.height }; + } + + clear(r, g, b, a) + { + const gl = this.gl; + + // TODO clear color can be set only one right? + gl.clearColor(r, g, b, a); + gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); + } + + // private functions... + + initFramebuffer(framebuffer) + { + const gl = this.gl; + + // TODO - make this a class? + const fbo = { + framebuffer: gl.createFramebuffer(), + stencil: null, + dirtyId: 0, + dirtyFormat: 0, + dirtySize: 0, + }; + + framebuffer.glFrameBuffers[this.CONTEXT_UID] = fbo; + + return fbo; + } + + resizeFramebuffer(framebuffer) + { + const gl = this.gl; + + if (framebuffer.stencil || framebuffer.depth) + { + gl.bindRenderbuffer(gl.RENDERBUFFER, this.stencil); + gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); + } + } + + updateFramebuffer(framebuffer) + { + const gl = this.gl; + + const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID]; + + // bind the color texture + const colorTextures = framebuffer.colorTextures; + + let count = colorTextures.length; + + if (!this.drawBufferExtension) + { + count = Math.min(count, 1); + } + + const activeTextures = []; + + for (let i = 0; i < count; i++) + { + const texture = framebuffer.colorTextures[i]; + + if (texture.texturePart) + { + this.renderer.texture.bind(texture.texture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.COLOR_ATTACHMENT0 + i, + gl.TEXTURE_CUBE_MAP_NEGATIVE_X + texture.side, + texture.texture._glTextures[this.CONTEXT_UID].texture, + 0); + } + else + { + this.renderer.texture.bind(texture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.COLOR_ATTACHMENT0 + i, + gl.TEXTURE_2D, + texture._glTextures[this.CONTEXT_UID].texture, + 0); + } + + activeTextures.push(gl.COLOR_ATTACHMENT0 + i); + } + + if (this.drawBufferExtension && activeTextures.length > 1) + { + this.drawBufferExtension.drawBuffersWEBGL(activeTextures); + } + + if (framebuffer.depthTexture) + { + const depthTextureExt = this.renderer.context.extensions.depthTexture; + + if (depthTextureExt) + { + const depthTexture = framebuffer.depthTexture; + + this.renderer.texture.bind(depthTexture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.DEPTH_ATTACHMENT, + gl.TEXTURE_2D, + depthTexture._glTextures[this.CONTEXT_UID].texture, + 0); + } + } + + if (framebuffer.stencil || framebuffer.depth) + { + fbo.stencil = gl.createRenderbuffer(); + + gl.bindRenderbuffer(gl.RENDERBUFFER, fbo.stencil); + + // TODO.. this is depth AND stencil? + gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, fbo.stencil); + gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); + // fbo.enableStencil(); + } + } +} diff --git a/packages/core/src/geometry/GLBuffer.js b/packages/core/src/geometry/GLBuffer.js new file mode 100644 index 0000000..0477e8a --- /dev/null +++ b/packages/core/src/geometry/GLBuffer.js @@ -0,0 +1,9 @@ +export default class GLBuffer +{ + constructor(buffer) + { + this.buffer = buffer; + this.updateID = -1; + this.byteLength = -1; + } +} diff --git a/packages/core/src/geometry/GeometrySystem.js b/packages/core/src/geometry/GeometrySystem.js new file mode 100644 index 0000000..959f827 --- /dev/null +++ b/packages/core/src/geometry/GeometrySystem.js @@ -0,0 +1,388 @@ +import System from '../System'; +import GLBuffer from './GLBuffer'; + +const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class GeometrySystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this._activeGeometry = null; + this._activeVao = null; + + this.hasVao = true; + this.hasInstance = true; + } + + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + contextChange() + { + const gl = this.gl = this.renderer.gl; + + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + + // webgl2 + if (!gl.createVertexArray) + { + // webgl 1! + let nativeVaoExtension = this.renderer.context.extensions.vertexArrayObject; + + if (this.renderer.legacy) + { + nativeVaoExtension = null; + } + + if (nativeVaoExtension) + { + gl.createVertexArray = () => + nativeVaoExtension.createVertexArrayOES(); + + gl.bindVertexArray = (vao) => + nativeVaoExtension.bindVertexArrayOES(vao); + + gl.deleteVertexArray = (vao) => + nativeVaoExtension.deleteVertexArrayOES(vao); + } + else + { + this.hasVao = false; + gl.createVertexArray = () => + { + // empty + }; + + gl.bindVertexArray = () => + { + // empty + }; + + gl.deleteVertexArray = () => + { + // empty + }; + } + } + + if (!gl.vertexAttribDivisor) + { + const instanceExt = gl.getExtension('ANGLE_instanced_arrays'); + + if (instanceExt) + { + gl.vertexAttribDivisor = (a, b) => + instanceExt.vertexAttribDivisorANGLE(a, b); + + gl.drawElementsInstanced = (a, b, c, d, e) => + instanceExt.drawElementsInstancedANGLE(a, b, c, d, e); + + gl.drawArraysInstanced = (a, b, c, d) => + instanceExt.drawArraysInstancedANGLE(a, b, c, d); + } + else + { + this.hasInstance = false; + } + } + } + + /** + * Binds geometry so that is can be drawn. Creating a Vao if required + * @private + * @param {PIXI.Geometry} geometry instance of geometry to bind + */ + bind(geometry, shader) + { + shader = shader || this.renderer.shader.shader; + + const gl = this.gl; + + // not sure the best way to address this.. + // currently different shaders require different VAOs for the same geometry + // Still mulling over the best way to solve this one.. + // will likely need to modify the shader attribute locations at run time! + let vaos = geometry.glVertexArrayObjects[this.CONTEXT_UID]; + + if (!vaos) + { + geometry.glVertexArrayObjects[this.CONTEXT_UID] = vaos = {}; + } + + const vao = vaos[shader.program.id] || this.initGeometryVao(geometry, shader.program); + + this._activeGeometry = geometry; + + if (this._activeVao !== vao) + { + this._activeVao = vao; + + if (this.hasVao) + { + gl.bindVertexArray(vao); + } + else + { + this.activateVao(geometry, shader.program); + } + } + + // TODO - optimise later! + // don't need to loop through if nothing changed! + // maybe look to add an 'autoupdate' to geometry? + this.updateBuffers(); + } + + reset() + { + this.unbind(); + } + + updateBuffers() + { + const geometry = this._activeGeometry; + const gl = this.gl; + + for (let i = 0; i < geometry.buffers.length; i++) + { + const buffer = geometry.buffers[i]; + + const glBuffer = buffer._glBuffers[this.CONTEXT_UID]; + + if (buffer._updateID !== glBuffer.updateID) + { + glBuffer.updateID = buffer._updateID; + + // TODO can cache this on buffer! maybe added a getter / setter? + const type = buffer.index ? gl.ELEMENT_ARRAY_BUFFER : gl.ARRAY_BUFFER; + const drawType = buffer.static ? gl.STATIC_DRAW : gl.DYNAMIC_DRAW; + + gl.bindBuffer(type, glBuffer.buffer); + + if (glBuffer.byteLength >= buffer.data.byteLength) + { + // offset is always zero for now! + gl.bufferSubData(type, 0, buffer.data); + } + else + { + gl.bufferData(type, buffer.data, drawType); + } + } + } + } + + checkCompatability(geometry, program) + { + // geometry must have at least all the attributes that the shader requires. + const geometryAttributes = geometry.attributes; + const shaderAttributes = program.attributeData; + + for (const j in shaderAttributes) + { + if (!geometryAttributes[j]) + { + throw new Error(`shader and geometry incompatible, geometry missing the "${j}" attribute`); + } + } + } + + /** + * Creates a Vao with the same structure as the geometry and stores it on the geometry. + * @private + * @param {PIXI.Geometry} geometry instance of geometry to to generate Vao for + */ + initGeometryVao(geometry, program) + { + this.checkCompatability(geometry, program); + + const gl = this.gl; + const CONTEXT_UID = this.CONTEXT_UID; + const buffers = geometry.buffers; + const attributes = geometry.attributes; + + const tempStride = {}; + const tempStart = {}; + + for (const j in buffers) + { + tempStride[j] = 0; + tempStart[j] = 0; + } + + for (const j in attributes) + { + if (!attributes[j].size && program.attributeData[j]) + { + attributes[j].size = program.attributeData[j].size; + } + + tempStride[attributes[j].buffer] += attributes[j].size * byteSizeMap[attributes[j].type]; + } + + for (const j in attributes) + { + const attribute = attributes[j]; + const attribSize = attribute.size; + + if (attribute.stride === undefined) + { + if (tempStride[attribute.buffer] === attribSize * byteSizeMap[attribute.type]) + { + attribute.stride = 0; + } + else + { + attribute.stride = tempStride[attribute.buffer]; + } + } + + if (attribute.start === undefined) + { + attribute.start = tempStart[attribute.buffer]; + + tempStart[attribute.buffer] += attribSize * byteSizeMap[attribute.type]; + } + } + + // first update - and create the buffers! + // only create a gl buffer if it actually gets + for (let i = 0; i < buffers.length; i++) + { + const buffer = buffers[i]; + + if (!buffer._glBuffers[CONTEXT_UID]) + { + buffer._glBuffers[CONTEXT_UID] = new GLBuffer(gl.createBuffer()); + } + } + + // TODO - maybe make this a data object? + // lets wait to see if we need to first! + const vao = gl.createVertexArray(); + + gl.bindVertexArray(vao); + + this.activateVao(geometry, program); + + gl.bindVertexArray(null); + + // add it to the cache! + geometry.glVertexArrayObjects[this.CONTEXT_UID][program.id] = vao; + + return vao; + } + + activateVao(geometry, program) + { + const gl = this.gl; + const CONTEXT_UID = this.CONTEXT_UID; + const buffers = geometry.buffers; + const attributes = geometry.attributes; + + if (geometry.indexBuffer) + { + // first update the index buffer if we have one.. + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, geometry.indexBuffer._glBuffers[CONTEXT_UID].buffer); + } + + let lastBuffer = null; + + // add a new one! + for (const j in attributes) + { + const attribute = attributes[j]; + const buffer = buffers[attribute.buffer]; + const glBuffer = buffer._glBuffers[CONTEXT_UID]; + + if (program.attributeData[j]) + { + if (lastBuffer !== glBuffer) + { + gl.bindBuffer(gl.ARRAY_BUFFER, glBuffer.buffer); + + lastBuffer = glBuffer; + } + + const location = program.attributeData[j].location; + + // TODO introduce state again + // we can optimise this for older devices that have no VAOs + gl.enableVertexAttribArray(location); + + gl.vertexAttribPointer(location, + attribute.size, + attribute.type || gl.FLOAT, + attribute.normalized, + attribute.stride, + attribute.start); + + if (attribute.instance) + { + // TODO calculate instance count based of this... + if (this.hasInstance) + { + gl.vertexAttribDivisor(location, 1); + } + else + { + throw new Error('geometry error, GPU Instancing is not supported on this device'); + } + } + } + } + } + + draw(type, size, start, instanceCount) + { + const gl = this.gl; + const geometry = this._activeGeometry; + + // TODO.. this should not change so maybe cache the function? + + if (geometry.indexBuffer) + { + if (geometry.instanced) + { + /* eslint-disable max-len */ + gl.drawElementsInstanced(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2, instanceCount || 1); + /* eslint-enable max-len */ + } + else + { + gl.drawElements(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2); + } + } + else + if (geometry.instanced) + { + // TODO need a better way to calculate size.. + gl.drawArraysInstanced(type, start, size || geometry.getSize(), instanceCount || 1); + } + else + { + gl.drawArrays(type, start, size || geometry.getSize()); + } + + return this; + } + + unbind() + { + this.gl.bindVertexArray(null); + this._activeVao = null; + this._activeGeometry = null; + } +} diff --git a/packages/core/src/geometry/utils/setVertexAttribArrays.js b/packages/core/src/geometry/utils/setVertexAttribArrays.js new file mode 100644 index 0000000..23b6959 --- /dev/null +++ b/packages/core/src/geometry/utils/setVertexAttribArrays.js @@ -0,0 +1,54 @@ +// var GL_MAP = {}; + +/** + * @param gl {WebGLRenderingContext} The current WebGL context + * @param attribs {*} + * @param state {*} + */ +export default function setVertexAttribArrays(gl, attribs, state) +{ + let i; + + if (state) + { + const tempAttribState = state.tempAttribState; + const attribState = state.attribState; + + for (i = 0; i < tempAttribState.length; i++) + { + tempAttribState[i] = false; + } + + // set the new attribs + for (i = 0; i < attribs.length; i++) + { + tempAttribState[attribs[i].attribute.location] = true; + } + + for (i = 0; i < attribState.length; i++) + { + if (attribState[i] !== tempAttribState[i]) + { + attribState[i] = tempAttribState[i]; + + if (state.attribState[i]) + { + gl.enableVertexAttribArray(i); + } + else + { + gl.disableVertexAttribArray(i); + } + } + } + } + else + { + for (i = 0; i < attribs.length; i++) + { + const attrib = attribs[i]; + + gl.enableVertexAttribArray(attrib.attribute.location); + } + } +} diff --git a/packages/core/src/index.js b/packages/core/src/index.js index 58c456a..fd1f547 100644 --- a/packages/core/src/index.js +++ b/packages/core/src/index.js @@ -1,30 +1,30 @@ import * as resources from './textures/resources'; -import * as systems from './renderers/systems'; +import * as systems from './systems'; -export { resources }; export { systems }; +export { resources }; -export { default as Renderer } from './renderers/Renderer'; -export { default as AbstractRenderer } from './renderers/AbstractRenderer'; -export { default as FrameBuffer } from './textures/FrameBuffer'; +export { default as System } from './System'; +export { default as Renderer } from './Renderer'; +export { default as AbstractRenderer } from './AbstractRenderer'; +export { default as FrameBuffer } from './framebuffer/FrameBuffer'; export { default as CubeTexture } from './textures/CubeTexture'; export { default as BaseTexture } from './textures/BaseTexture'; export { default as Texture } from './textures/Texture'; export { default as TextureMatrix } from './textures/TextureMatrix'; -export { default as RenderTexture } from './textures/RenderTexture'; -export { default as BaseRenderTexture } from './textures/BaseRenderTexture'; +export { default as RenderTexture } from './renderTexture/RenderTexture'; +export { default as BaseRenderTexture } from './renderTexture/BaseRenderTexture'; export { default as TextureUvs } from './textures/TextureUvs'; -export { default as State } from './renderers/State'; -export { default as ObjectRenderer } from './renderers/utils/ObjectRenderer'; -export { default as Quad } from './renderers/utils/Quad'; -export { default as QuadUv } from './renderers/utils/QuadUv'; -export { default as checkMaxIfStatmentsInShader } from './renderers/utils/checkMaxIfStatmentsInShader'; +export { default as State } from './state/State'; +export { default as ObjectRenderer } from './batch/ObjectRenderer'; +export { default as Quad } from './utils/Quad'; +export { default as QuadUv } from './utils/QuadUv'; +export { default as checkMaxIfStatmentsInShader } from './shader/utils/checkMaxIfStatmentsInShader'; export { default as Shader } from './shader/Shader'; export { default as Program } from './shader/Program'; export { default as UniformGroup } from './shader/UniformGroup'; -export { default as SpriteMaskFilter } from './renderers/filters/spriteMask/SpriteMaskFilter'; -export { default as Filter } from './renderers/filters/Filter'; +export { default as SpriteMaskFilter } from './filters/spriteMask/SpriteMaskFilter'; +export { default as Filter } from './filters/Filter'; export { default as Attribute } from './geometry/Attribute'; export { default as Buffer } from './geometry/Buffer'; export { default as Geometry } from './geometry/Geometry'; - diff --git a/packages/core/src/mask/MaskSystem.js b/packages/core/src/mask/MaskSystem.js new file mode 100644 index 0000000..99174c2 --- /dev/null +++ b/packages/core/src/mask/MaskSystem.js @@ -0,0 +1,199 @@ +import System from '../System'; +import AlphaMaskFilter from '../filters/spriteMask/SpriteMaskFilter'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class MaskSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + // TODO - we don't need both! + this.scissor = false; + this.scissorData = null; + this.scissorRenderTarget = null; + + this.enableScissor = false; + + this.alphaMaskPool = []; + this.alphaMaskIndex = 0; + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.DisplayObject} target - Display Object to push the mask to + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + push(target, maskData) + { + // TODO the root check means scissor rect will not + // be used on render textures more info here: + // https://github.com/pixijs/pixi.js/pull/3545 + + if (maskData.texture) + { + this.pushSpriteMask(target, maskData); + } + else if (this.enableScissor + && !this.scissor + && this.renderer._activeRenderTarget.root + && !this.renderer.stencil.stencilMaskStack.length + && maskData.isFastRect()) + { + const matrix = maskData.worldTransform; + + let rot = Math.atan2(matrix.b, matrix.a); + + // use the nearest degree! + rot = Math.round(rot * (180 / Math.PI)); + + if (rot % 90) + { + this.pushStencilMask(maskData); + } + else + { + this.pushScissorMask(target, maskData); + } + } + else + { + this.pushStencilMask(maskData); + } + } + + /** + * Removes the last mask from the mask stack and doesn't return it. + * + * @param {PIXI.DisplayObject} target - Display Object to pop the mask from + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + pop(target, maskData) + { + if (maskData.texture) + { + this.popSpriteMask(target, maskData); + } + else if (this.enableScissor && !this.renderer.stencil.stencilMaskStack.length) + { + this.popScissorMask(target, maskData); + } + else + { + this.popStencilMask(target, maskData); + } + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.RenderTarget} target - Display Object to push the sprite mask to + * @param {PIXI.Sprite} maskData - Sprite to be used as the mask + */ + pushSpriteMask(target, maskData) + { + let alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; + + if (!alphaMaskFilter) + { + alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; + } + + alphaMaskFilter[0].resolution = this.renderer.resolution; + alphaMaskFilter[0].maskSprite = maskData; + + // TODO - may cause issues! + target.filterArea = maskData.getBounds(true); + + this.renderer.filterSystem.pushFilter(target, alphaMaskFilter); + + this.alphaMaskIndex++; + } + + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popSpriteMask() + { + this.renderer.filterSystem.popFilter(); + this.alphaMaskIndex--; + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + pushStencilMask(maskData) + { + this.renderer.batch.flush(); + this.renderer.stencil.pushStencil(maskData); + } + + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popStencilMask() + { + // this.renderer.currentRenderer.stop(); + this.renderer.stencil.popStencil(); + } + + /** + * + * @param {PIXI.DisplayObject} target - Display Object to push the mask to + * @param {PIXI.Graphics} maskData - The masking data. + */ + pushScissorMask(target, maskData) + { + maskData.renderable = true; + + const renderTarget = this.renderer._activeRenderTarget; + + const bounds = maskData.getBounds(); + + bounds.fit(renderTarget.size); + maskData.renderable = false; + + this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); + + const resolution = this.renderer.resolution; + + this.renderer.gl.scissor( + bounds.x * resolution, + (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, + bounds.width * resolution, + bounds.height * resolution + ); + + this.scissorRenderTarget = renderTarget; + this.scissorData = maskData; + this.scissor = true; + } + + /** + * + * + */ + popScissorMask() + { + this.scissorRenderTarget = null; + this.scissorData = null; + this.scissor = false; + + // must be scissor! + const gl = this.renderer.gl; + + gl.disable(gl.SCISSOR_TEST); + } +} diff --git a/packages/core/src/mask/StencilSystem.js b/packages/core/src/mask/StencilSystem.js new file mode 100644 index 0000000..62796a5 --- /dev/null +++ b/packages/core/src/mask/StencilSystem.js @@ -0,0 +1,133 @@ +import System from '../System'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class StencilSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + this.stencilMaskStack = []; + } + + /** + * Changes the mask stack that is used by this System. + * + * @param {PIXI.Graphics[]} stencilMaskStack - The mask stack + */ + setMaskStack(stencilMaskStack) + { + const gl = this.renderer.gl; + + if (stencilMaskStack.length !== this.stencilMaskStack.length) + { + if (stencilMaskStack.length === 0) + { + gl.disable(gl.STENCIL_TEST); + } + else + { + gl.enable(gl.STENCIL_TEST); + } + } + + this.stencilMaskStack = stencilMaskStack; + } + + /** + * Applies the Mask and adds it to the current stencil stack. @alvin + * + * @param {PIXI.Graphics} graphics - The mask + */ + pushStencil(graphics) + { + this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); + + // this.renderer._activeRenderTarget.attachStencilBuffer(); + + const gl = this.renderer.gl; + const prevMaskCount = this.stencilMaskStack.length; + + if (prevMaskCount === 0) + { + gl.enable(gl.STENCIL_TEST); + } + + this.stencilMaskStack.push(graphics); + + // Increment the refference stencil value where the new mask overlaps with the old ones. + gl.colorMask(false, false, false, false); + gl.stencilFunc(gl.EQUAL, prevMaskCount, this._getBitwiseMask()); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.INCR); + this.renderer.plugins.graphics.render(graphics); + + this._useCurrent(); + } + + /** + * Removes the last mask from the stencil stack. @alvin + */ + popStencil() + { + this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); + + const gl = this.renderer.gl; + const graphics = this.stencilMaskStack.pop(); + + if (this.stencilMaskStack.length === 0) + { + // the stack is empty! + gl.disable(gl.STENCIL_TEST); + gl.clear(gl.STENCIL_BUFFER_BIT); + gl.clearStencil(0); + } + else + { + // Decrement the refference stencil value where the popped mask overlaps with the other ones + gl.colorMask(false, false, false, false); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.DECR); + this.renderer.plugins.graphics.render(graphics); + + this._useCurrent(); + } + } + + /** + * Setup renderer to use the current stencil data. + */ + _useCurrent() + { + const gl = this.renderer.gl; + + gl.colorMask(true, true, true, true); + gl.stencilFunc(gl.EQUAL, this.stencilMaskStack.length, this._getBitwiseMask()); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP); + } + + /** + * Fill 1s equal to the number of acitve stencil masks. + * + * @return {number} The bitwise mask. + */ + _getBitwiseMask() + { + return (1 << this.stencilMaskStack.length) - 1; + } + + /** + * Destroys the mask stack. + * + */ + destroy() + { + super.destroy(this); + + this.stencilMaskStack = null; + } +} diff --git a/packages/core/src/projection/ProjectionSystem.js b/packages/core/src/projection/ProjectionSystem.js new file mode 100644 index 0000000..6e63b73 --- /dev/null +++ b/packages/core/src/projection/ProjectionSystem.js @@ -0,0 +1,73 @@ +import System from '../System'; +import { Matrix } from '@pixi/math'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class ProjectionSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.projectionMatrix = new Matrix(); + } + + update(destinationFrame, sourceFrame, resolution, root) + { + this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; + this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; + + this.calculateProjection(this.destinationFrame, this.sourceFrame, resolution, root); + + this.renderer.globalUniforms.uniforms.projectionMatrix = this.projectionMatrix; + this.renderer.globalUniforms.update(); + } + + /** + * Updates the projection matrix based on a projection frame (which is a rectangle) + * + * @param {Rectangle} destinationFrame - The destination frame. + * @param {Rectangle} sourceFrame - The source frame. + */ + calculateProjection(destinationFrame, sourceFrame, resolution, root) + { + const pm = this.projectionMatrix; + + // I don't think we will need this line.. + // pm.identity(); + + if (!root) + { + pm.a = (1 / destinationFrame.width * 2) * resolution; + pm.d = (1 / destinationFrame.height * 2) * resolution; + + pm.tx = -1 - (sourceFrame.x * pm.a); + pm.ty = -1 - (sourceFrame.y * pm.d); + } + else + { + pm.a = (1 / destinationFrame.width * 2) * resolution; + pm.d = (-1 / destinationFrame.height * 2) * resolution; + + pm.tx = -1 - (sourceFrame.x * pm.a); + pm.ty = 1 - (sourceFrame.y * pm.d); + } + } + + /** + * Sets the transform of the active render target to the given matrix + * + * @param {PIXI.Matrix} matrix - The transformation matrix + */ + setTransform()// matrix) + { + // this._activeRenderTarget.transform = matrix; + } +} diff --git a/packages/core/src/renderTexture/BaseRenderTexture.js b/packages/core/src/renderTexture/BaseRenderTexture.js new file mode 100644 index 0000000..88eb83b --- /dev/null +++ b/packages/core/src/renderTexture/BaseRenderTexture.js @@ -0,0 +1,137 @@ +import BaseTexture from '../textures/BaseTexture'; +import FrameBuffer from '../framebuffer/FrameBuffer'; + +/** + * A BaseRenderTexture is a special texture that allows any PixiJS display object to be rendered to it. + * + * __Hint__: All DisplayObjects (i.e. Sprites) that render to a BaseRenderTexture should be preloaded + * otherwise black rectangles will be drawn instead. + * + * A BaseRenderTexture takes a snapshot of any Display Object given to its render method. The position + * and rotation of the given Display Objects is ignored. For example: + * + * ```js + * let renderer = PIXI.autoDetectRenderer(1024, 1024, { view: canvas, ratio: 1 }); + * let baseRenderTexture = new PIXI.BaseRenderTexture(renderer, 800, 600); + * let sprite = PIXI.Sprite.fromImage("spinObj_01.png"); + * + * sprite.position.x = 800/2; + * sprite.position.y = 600/2; + * sprite.anchor.x = 0.5; + * sprite.anchor.y = 0.5; + * + * baseRenderTexture.render(sprite); + * ``` + * + * The Sprite in this case will be rendered using its local transform. To render this sprite at 0,0 + * you can clear the transform + * + * ```js + * + * sprite.setTransform() + * + * let baseRenderTexture = new PIXI.BaseRenderTexture(100, 100); + * let renderTexture = new PIXI.RenderTexture(baseRenderTexture); + * + * renderer.render(sprite, renderTexture); // Renders to center of RenderTexture + * ``` + * + * @class + * @extends PIXI.BaseTexture + * @memberof PIXI + */ +export default class BaseRenderTexture extends BaseTexture +{ + /** + * @param {object} [options] + * @param {number} [options.width=100] - The width of the base render texture + * @param {number} [options.height=100] - The height of the base render texture + * @param {PIXI.SCALE_MODES} [options.scaleMode] - See {@link PIXI.SCALE_MODES} for possible values + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the texture being generated + */ + constructor(options) + { + if (typeof options === 'number') + { + /* eslint-disable prefer-rest-params */ + // Backward compatibility of signature + const width = arguments[0]; + const height = arguments[1]; + const scaleMode = arguments[2]; + const resolution = arguments[3]; + + options = { width, height, scaleMode, resolution }; + /* eslint-enable prefer-rest-params */ + } + + super(null, options); + + const { width, height } = options || {}; + + // Set defaults + this.mipmap = false; + this.width = Math.ceil(width) || 100; + this.height = Math.ceil(height) || 100; + this.valid = true; + + /** + * A map of renderer IDs to webgl renderTargets + * + * @private + * @member {object} + */ + // this._glRenderTargets = {}; + + /** + * A reference to the canvas render target (we only need one as this can be shared across renderers) + * + * @private + * @member {object} + */ + this._canvasRenderTarget = null; + + this.clearColor = [0, 0, 0, 0]; + + this.frameBuffer = new FrameBuffer(width * this.resolution, height * this.resolution) + .addColorTexture(0, this); + + // TODO - could this be added the systems? + + /** + * The data structure for the stencil masks + * + * @member {PIXI.Graphics[]} + */ + this.stencilMaskStack = []; + + /** + * The data structure for the filters + * + * @member {PIXI.Graphics[]} + */ + this.filterStack = [{}]; + } + + /** + * Resizes the BaseRenderTexture. + * + * @param {number} width - The width to resize to. + * @param {number} height - The height to resize to. + */ + resize(width, height) + { + width = Math.ceil(width); + height = Math.ceil(height); + this.frameBuffer.resize(width * this.resolution, height * this.resolution); + } + + /** + * Destroys this texture + * + */ + destroy() + { + super.destroy(true); + this.renderer = null; + } +} diff --git a/packages/core/src/renderTexture/RenderTexture.js b/packages/core/src/renderTexture/RenderTexture.js new file mode 100644 index 0000000..f1ecf1f --- /dev/null +++ b/packages/core/src/renderTexture/RenderTexture.js @@ -0,0 +1,153 @@ +import BaseRenderTexture from './BaseRenderTexture'; +import Texture from '../textures/Texture'; + +/** + * A RenderTexture is a special texture that allows any PixiJS display object to be rendered to it. + * + * __Hint__: All DisplayObjects (i.e. Sprites) that render to a RenderTexture should be preloaded + * otherwise black rectangles will be drawn instead. + * + * A RenderTexture takes a snapshot of any Display Object given to its render method. For example: + * + * ```js + * let renderer = PIXI.autoDetectRenderer(1024, 1024, { view: canvas, ratio: 1 }); + * let renderTexture = PIXI.RenderTexture.create(800, 600); + * let sprite = PIXI.Sprite.fromImage("spinObj_01.png"); + * + * sprite.position.x = 800/2; + * sprite.position.y = 600/2; + * sprite.anchor.x = 0.5; + * sprite.anchor.y = 0.5; + * + * renderer.render(sprite, renderTexture); + * ``` + * + * The Sprite in this case will be rendered using its local transform. To render this sprite at 0,0 + * you can clear the transform + * + * ```js + * + * sprite.setTransform() + * + * let renderTexture = new PIXI.RenderTexture.create(100, 100); + * + * renderer.render(sprite, renderTexture); // Renders to center of RenderTexture + * ``` + * + * @class + * @extends PIXI.Texture + * @memberof PIXI + */ +export default class RenderTexture extends Texture +{ + /** + * @param {PIXI.BaseRenderTexture} baseRenderTexture - The renderer used for this RenderTexture + * @param {PIXI.Rectangle} [frame] - The rectangle frame of the texture to show + */ + constructor(baseRenderTexture, frame) + { + // support for legacy.. + let _legacyRenderer = null; + + if (!(baseRenderTexture instanceof BaseRenderTexture)) + { + /* eslint-disable prefer-rest-params, no-console */ + const width = arguments[1]; + const height = arguments[2]; + const scaleMode = arguments[3]; + const resolution = arguments[4]; + + // we have an old render texture.. + console.warn(`Please use RenderTexture.create(${width}, ${height}) instead of the ctor directly.`); + _legacyRenderer = arguments[0]; + /* eslint-enable prefer-rest-params, no-console */ + + frame = null; + baseRenderTexture = new BaseRenderTexture({ + width, + height, + scaleMode, + resolution, + }); + } + + /** + * The base texture object that this texture uses + * + * @member {BaseTexture} + */ + super(baseRenderTexture, frame); + + this.legacyRenderer = _legacyRenderer; + + /** + * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. + * + * @member {boolean} + */ + this.valid = true; + + /** + * FilterSystem temporary storage + * @private + * @member {PIXI.Rectangle} + */ + this.filterFrame = null; + + this._updateUvs(); + } + + /** + * Resizes the RenderTexture. + * + * @param {number} width - The width to resize to. + * @param {number} height - The height to resize to. + * @param {boolean} [resizeBaseTexture=true] - Should the baseTexture.width and height values be resized as well? + */ + resize(width, height, resizeBaseTexture = true) + { + width = Math.ceil(width); + height = Math.ceil(height); + + // TODO - could be not required.. + this.valid = (width > 0 && height > 0); + + this._frame.width = this.orig.width = width; + this._frame.height = this.orig.height = height; + + if (resizeBaseTexture) + { + this.baseTexture.resize(width, height); + } + + this._updateUvs(); + } + + /** + * A short hand way of creating a render texture. + * + * @param {object} [options] - Options + * @param {number} [options.width=100] - The width of the render texture + * @param {number} [options.height=100] - The height of the render texture + * @param {number} [options.scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the texture being generated + * @return {PIXI.RenderTexture} The new render texture + */ + static create(options) + { + // fallback, old-style: create(width, height, scaleMode, resolution) + if (typeof options === 'number') + { + /* eslint-disable prefer-rest-params */ + options = { + width: options, + height: arguments[1], + scaleMode: arguments[2], + resolution: arguments[3], + }; + /* eslint-enable prefer-rest-params */ + } + + return new RenderTexture(new BaseRenderTexture(options)); + } +} diff --git a/packages/core/src/renderTexture/RenderTextureSystem.js b/packages/core/src/renderTexture/RenderTextureSystem.js new file mode 100644 index 0000000..8d0ca61 --- /dev/null +++ b/packages/core/src/renderTexture/RenderTextureSystem.js @@ -0,0 +1,115 @@ +import System from '../System'; +import { Rectangle } from '@pixi/math'; + +const tempRect = new Rectangle(); + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class RenderTextureSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.clearColor = renderer._backgroundColorRgba; + + // TODO moe this property somewhere else! + this.defaultMaskStack = []; + this.defaultFilterStack = [{}]; + + // empty render texture? + this.renderTexture = null; + + this.destinationFrame = new Rectangle(); + } + + bind(renderTexture, sourceFrame, destinationFrame) + { + // TODO - do we want this?? + if (this.renderTexture === renderTexture) return; + this.renderTexture = renderTexture; + + const renderer = this.renderer; + + if (renderTexture) + { + const baseTexture = renderTexture.baseTexture; + + if (!destinationFrame) + { + tempRect.width = baseTexture.realWidth; + tempRect.height = baseTexture.realHeight; + + destinationFrame = tempRect; + } + + if (!sourceFrame) + { + sourceFrame = destinationFrame; + } + + this.renderer.framebuffer.bind(baseTexture.frameBuffer, destinationFrame); + + this.renderer.projection.update(destinationFrame, sourceFrame, baseTexture.resolution, false); + this.renderer.stencil.setMaskStack(baseTexture.stencilMaskStack); + } + else + { + // TODO these validation checks happen deeper down.. + // thing they can be avoided.. + if (!destinationFrame) + { + tempRect.width = renderer.width; + tempRect.height = renderer.height; + + destinationFrame = tempRect; + } + + if (!sourceFrame) + { + sourceFrame = destinationFrame; + } + + renderer.framebuffer.bind(null, destinationFrame); + + // TODO store this.. + this.renderer.projection.update(destinationFrame, sourceFrame, this.renderer.resolution, true); + this.renderer.stencil.setMaskStack(this.defaultMaskStack); + } + + this.destinationFrame.copyFrom(destinationFrame); + } + + /** + * Erases the render texture and fills the drawing area with a colour + * + * @param {number} [clearColor] - The colour + * @return {PIXI.Renderer} Returns itself. + */ + clear(clearColor) + { + if (this.renderTexture) + { + clearColor = clearColor || this.renderTexture.baseTexture.clearColor; + } + else + { + clearColor = clearColor || this.clearColor; + } + + this.renderer.framebuffer.clear(clearColor[0], clearColor[1], clearColor[2], clearColor[3]); + } + + resize()// screenWidth, screenHeight) + { + // resize the root only! + this.bind(null); + } +} diff --git a/packages/core/src/renderers/AbstractRenderer.js b/packages/core/src/renderers/AbstractRenderer.js deleted file mode 100644 index 928670f..0000000 --- a/packages/core/src/renderers/AbstractRenderer.js +++ /dev/null @@ -1,353 +0,0 @@ -import { hex2string, hex2rgb } from '@pixi/utils'; -import { Matrix, Rectangle } from '@pixi/math'; -import { RENDERER_TYPE } from '@pixi/constants'; -import { settings } from '@pixi/settings'; -import { Container } from '@pixi/display'; -import RenderTexture from '../textures/RenderTexture'; -import EventEmitter from 'eventemitter3'; - -const tempMatrix = new Matrix(); - -/** - * 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 EventEmitter - * @memberof PIXI - */ -export default class AbstractRenderer extends EventEmitter -{ - // eslint-disable-next-line valid-jsdoc - /** - * @param {string} system - The name of the system this renderer is for. - * @param {object} [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.transparent=false] - If the render view is transparent, default false - * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false - * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) - * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. The - * resolution of the renderer retina would be 2. - * @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 {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when rendering, - * stopping pixel interpolation. - */ - constructor(system, options, arg2, arg3) - { - super(); - - // Support for constructor(system, screenWidth, screenHeight, options) - if (typeof options === 'number') - { - options = Object.assign({ - width: options, - height: arg2 || settings.RENDER_OPTIONS.height, - }, arg3); - } - - // 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 = RENDERER_TYPE.UNKNOWN; - - /** - * Measurements of the screen. (0, 0, screenWidth, screenHeight) - * - * Its safe to use as filterArea or hitArea for 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 1 - */ - this.resolution = options.resolution || settings.RESOLUTION; - - /** - * Whether the render view is transparent - * - * @member {boolean} - */ - this.transparent = options.transparent; - - /** - * Whether css dimensions of canvas view should be resized to screen dimensions automatically - * - * @member {boolean} - */ - this.autoResize = options.autoResize || false; - - /** - * Tracks the blend modes useful for this renderer. - * - * @member {object} - */ - this.blendModes = null; - - /** - * 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; - - /** - * If true PixiJS will Math.floor() x/y values when rendering, stopping pixel interpolation. - * Handy for crisp pixel art and speed on legacy devices. - * - * @member {boolean} - */ - this.roundPixels = options.roundPixels; - - /** - * The background color as a number. - * - * @member {number} - * @private - */ - this._backgroundColor = 0x000000; - - /** - * The background color as an [R, G, B] array. - * - * @member {number[]} - * @private - */ - this._backgroundColorRgba = [0, 0, 0, 0]; - - /** - * The background color as a string. - * - * @member {string} - * @private - */ - this._backgroundColorString = '#000000'; - - this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter - - /** - * This temporary display object used as the parent of the currently being rendered item - * - * @member {PIXI.DisplayObject} - * @private - */ - this._tempDisplayObjectParent = new Container(); - - /** - * The last root object that the renderer tried to render. - * - * @member {PIXI.DisplayObject} - * @private - */ - this._lastObjectRendered = this._tempDisplayObjectParent; - - /** - * Collection of plugins. - * @readonly - * @member {object} - */ - this.plugins = {}; - } - - /** - * Initialize the plugins. - * - * @protected - * @param {object} staticMap - The dictionary of staticly saved plugins. - */ - initPlugins(staticMap) - { - 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() - { - return this.view.width; - } - - /** - * Same as view.height, actual number of pixels in the canvas by vertical - * - * @member {number} - * @readonly - * @default 600 - */ - get height() - { - return this.view.height; - } - - /** - * Resizes the screen and canvas to the specified width and height - * Canvas dimensions are multiplied by resolution - * - * @param {number} screenWidth - the new width of the screen - * @param {number} screenHeight - the new height of the screen - */ - resize(screenWidth, screenHeight) - { - this.screen.width = screenWidth; - this.screen.height = screenHeight; - - this.view.width = screenWidth * this.resolution; - this.view.height = screenHeight * this.resolution; - - if (this.autoResize) - { - this.view.style.width = `${screenWidth}px`; - this.view.style.height = `${screenHeight}px`; - } - } - - /** - * 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 {PIXI.DisplayObject} displayObject - The displayObject the object will be generated from - * @param {number} scaleMode - Should be one of the scaleMode consts - * @param {number} resolution - The resolution / device pixel ratio of the texture being generated - * @param {PIXI.Rectangle} [region] - The region of the displayObject, that shall be rendered, - * if no region is specified, defaults to the local bounds of the displayObject. - * @return {PIXI.Texture} a texture of the graphics object - */ - generateTexture(displayObject, scaleMode, resolution, region) - { - region = region || displayObject.getLocalBounds(); - - // 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(region.width | 0, region.height | 0, scaleMode, resolution); - - tempMatrix.tx = -region.x; - tempMatrix.ty = -region.y; - - this.render(displayObject, renderTexture, false, tempMatrix, true); - - return renderTexture; - } - - /** - * Removes everything from the renderer and optionally removes the Canvas DOM element. - * - * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. - */ - destroy(removeView) - { - 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); - } - - this.plugins = null; - - this.type = RENDERER_TYPE.UNKNOWN; - - this.view = null; - - this.screen = null; - - this.resolution = 0; - - this.transparent = false; - - this.autoResize = false; - - this.blendModes = null; - - this.options = null; - - this.preserveDrawingBuffer = false; - this.clearBeforeRender = false; - - this.roundPixels = false; - - this._backgroundColor = 0; - this._backgroundColorRgba = null; - this._backgroundColorString = null; - - this._tempDisplayObjectParent = null; - this._lastObjectRendered = null; - } - - /** - * The background color to fill if not transparent - * - * @member {number} - */ - get backgroundColor() - { - return this._backgroundColor; - } - - set backgroundColor(value) // eslint-disable-line require-jsdoc - { - this._backgroundColor = value; - this._backgroundColorString = hex2string(value); - hex2rgb(value, this._backgroundColorRgba); - } -} diff --git a/packages/core/src/renderers/Renderer.js b/packages/core/src/renderers/Renderer.js deleted file mode 100644 index a2020b1..0000000 --- a/packages/core/src/renderers/Renderer.js +++ /dev/null @@ -1,329 +0,0 @@ -import AbstractRenderer from './AbstractRenderer'; -import { sayHello } from '@pixi/utils'; -import MaskSystem from './systems/MaskSystem'; -import StencilSystem from './systems/StencilSystem'; -import FilterSystem from './systems/filter/FilterSystem'; -import FramebufferSystem from './systems/FramebufferSystem'; -import RenderTextureSystem from './systems/RenderTextureSystem'; -import TextureSystem from './systems/textures/TextureSystem'; -import ProjectionSystem from './systems/ProjectionSystem'; -import StateSystem from './systems/StateSystem'; -import GeometrySystem from './systems/geometry/GeometrySystem'; -import ShaderSystem from './systems/shader/ShaderSystem'; -import ContextSystem from './systems/ContextSystem'; -import BatchSystem from './systems/BatchSystem'; -import TextureGCSystem from './systems/textures/TextureGCSystem'; -import { RENDERER_TYPE } from '@pixi/constants'; -import UniformGroup from '../shader/UniformGroup'; -import { Matrix } from '@pixi/math'; -import Runner from 'mini-runner'; - -/** - * 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 Render works by automatically managing webGLBatchs. - * 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 :) - * - * @class - * @memberof PIXI - * @extends PIXI.AbstractRenderer - */ -export default class Renderer extends AbstractRenderer -{ - // eslint-disable-next-line valid-jsdoc - /** - * - * @param {object} [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.transparent=false] - If the render view is transparent, default false - * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false - * @param {boolean} [options.antialias=false] - sets antialias. If not available natively then FXAA - * antialiasing is used - * @param {boolean} [options.forceFXAA=false] - forces FXAA antialiasing to be used over native. - * FXAA is faster, but may not always look as great - * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. - * The resolution of the renderer retina would be 2. - * @param {boolean} [options.clearBeforeRender=true] - This sets if the renderer will clear - * the canvas or not before the new render pass. If you wish to set this to false, you *must* set - * preserveDrawingBuffer to `true`. - * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, - * enable this if you need to call toDataUrl on the webgl context. - * @param {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when - * rendering, stopping pixel interpolation. - * @param {number} [options.backgroundColor=0x000000] - The background color of the rendered area - * (shown if not transparent). - * @param {boolean} [options.legacy=false] - If true PixiJS will aim to ensure compatibility - * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. - * @param {string} [options.powerPreference] - Parameter passed to webgl context, set to "high-performance" - * for devices with dual graphics card - */ - constructor(options, arg2, arg3) - { - super('WebGL', options, arg2, arg3); - - /** - * The type of this renderer as a standardised const - * - * @member {number} - * @see PIXI.RENDERER_TYPE - */ - this.type = RENDERER_TYPE.WEBGL; - - // this will be set by the contextSystem (this.context) - this.gl = null; - this.CONTEXT_UID = 0; - this.legacy = !!options.legacy; - - // TODO legacy! - - // runners! - this.runners = { - destroy: new Runner('destroy'), - contextChange: new Runner('contextChange', 1), - reset: new Runner('reset'), - update: new Runner('update'), - postrender: new Runner('postrender'), - prerender: new Runner('prerender'), - resize: new Runner('resize', 2), - }; - - 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(GeometrySystem, 'geometry') - .addSystem(FramebufferSystem, 'framebuffer') - .addSystem(StencilSystem, 'stencil') - .addSystem(ProjectionSystem, 'projection') - .addSystem(TextureGCSystem, 'textureGC') - .addSystem(FilterSystem, 'filter') - .addSystem(RenderTextureSystem, 'renderTexture') - .addSystem(BatchSystem, 'batch'); - - this.initPlugins(Renderer.__plugins); - - /** - * The options passed in to create a new webgl context. - * - * @member {object} - * @private - */ - if (options.context) - { - this.context.initFromContext(options.context); - } - else - { - this.context.initFromOptions({ - alpha: this.transparent, - antialias: options.antialias, - premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', - stencil: true, - 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); - } - - addSystem(_class, name) - { - if (!name) - { - name = _class.name; - } - - // TODO - read name from class.name.. - - /* - if(name.includes('System')) - { - name = name.replace('System', ''); - name = name.charAt(0).toLowerCase() + name.slice(1); - } - */ - - const system = new _class(this); - - if (this[name]) - { - throw new Error(`Whoops! ${name} is already a manger`); - } - - this[name] = system; - - for (const i in this.runners) - { - this.runners[i].add(system); - } - - return this; - - /** - * 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. - */ - } - - /** - * Renders the object to its webGL view - * - * @param {PIXI.DisplayObject} displayObject - the object to be rendered - * @param {PIXI.RenderTexture} renderTexture - The render texture to render to. - * @param {boolean} [clear] - Should the canvas be cleared before the new render - * @param {PIXI.Transform} [transform] - A transform to apply to the render texture before rendering. - * @param {boolean} [skipUpdateTransform] - Should we skip the update transform pass? - */ - render(displayObject, renderTexture, clear, transform, skipUpdateTransform) - { - // can be handy to know! - this.renderingToScreen = !renderTexture; - - this.runners.prerender.run(); - this.emit('prerender'); - - // 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.parent; - - displayObject.parent = this._tempDisplayObjectParent; - displayObject.updateTransform(); - displayObject.parent = 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.run(); - - this.emit('postrender'); - } - - /** - * Resizes the webGL view to the specified width and height. - * - * @param {number} screenWidth - the new width of the screen - * @param {number} screenHeight - the new height of the screen - */ - resize(screenWidth, screenHeight) - { - AbstractRenderer.prototype.resize.call(this, screenWidth, screenHeight); - - this.runners.resize.run(screenWidth, screenHeight); - } - - /** - * Resets the WebGL state so you can render things however you fancy! - * - * @return {PIXI.Renderer} Returns itself. - */ - reset() - { - this.runners.reset.run(); - - return this; - } - - clear() - { - this.framebuffer.bind(); - this.framebuffer.clear(); - } - - /** - * Removes everything from the renderer (event listeners, spritebatch, etc...) - * - * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. - * See: https://github.com/pixijs/pixi.js/issues/2233 - */ - destroy(removeView) - { - this.runners.destroy.run(); - - // call base destroy - super.destroy(removeView); - - // TODO nullify all the managers.. - this.gl = null; - } - - /** - * 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 - * builds and excluding plugins. - * @name PIXI.Renderer#plugins - * @type {object} - * @readonly - * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. - * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. - * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. - * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. - */ - - /** - * Adds a plugin to the renderer. - * - * @method - * @param {string} pluginName - The name of the plugin. - * @param {Function} ctor - The constructor function or class for the plugin. - */ - static registerPlugin(pluginName, ctor) - { - Renderer.__plugins = Renderer.__plugins || {}; - Renderer.__plugins[pluginName] = ctor; - } -} diff --git a/packages/core/src/renderers/State.js b/packages/core/src/renderers/State.js deleted file mode 100644 index fa3f221..0000000 --- a/packages/core/src/renderers/State.js +++ /dev/null @@ -1,163 +0,0 @@ -/* eslint-disable max-len */ - -const BLEND = 0; -const OFFSET = 1; -const CULLING = 2; -const DEPTH_TEST = 3; -const WINDING = 4; - -/** - * This is a webGL state. It is passed The WebGL StateManager. - * Each mesh renderered may require webGL to be in a different state. - * For example you may want different blend mode or to enable polygon offsets - * - * @class - * @memberof PIXI - */ -export default class State -{ - /** - * - */ - constructor() - { - this.data = 0; - - this.blendMode = 0; - this.polygonOffset = 0; - - this.blend = true; - // this.depthTest = true; - } - - /** - * Activates blending of the computed fragment color values - * - * @member {boolean} - */ - get blend() - { - return !!(this.data & (1 << BLEND)); - } - - set blend(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << BLEND)) !== value) - { - this.data ^= (1 << BLEND); - } - } - - /** - * Activates adding an offset to depth values of polygon's fragments - * - * @member {boolean} - * @default false - */ - get offsets() - { - return !!(this.data & (1 << OFFSET)); - } - - set offsets(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << OFFSET)) !== value) - { - this.data ^= (1 << OFFSET); - } - } - - /** - * Activates culling of polygons. - * - * @member {boolean} - * @default false - */ - get culling() - { - return !!(this.data & (1 << CULLING)); - } - - set culling(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << CULLING)) !== value) - { - this.data ^= (1 << CULLING); - } - } - - /** - * Activates depth comparisons and updates to the depth buffer. - * - * @member {boolean} - * @default false - */ - get depthTest() - { - return !!(this.data & (1 << DEPTH_TEST)); - } - - set depthTest(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << DEPTH_TEST)) !== value) - { - this.data ^= (1 << DEPTH_TEST); - } - } - - /** - * Specifies whether or not front or back-facing polygons can be culled. - * @member {boolean} - * @default false - */ - get clockwiseFrontFace() - { - return !!(this.data & (1 << WINDING)); - } - - set clockwiseFrontFace(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << WINDING)) !== value) - { - this.data ^= (1 << WINDING); - } - } - - /** - * The blend mode to be applied when this state is set. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. - * Setting this mode to anything other than NO_BLEND will automatically switch blending on. - * - * @member {boolean} - * @default PIXI.BLEND_MODES.NORMAL - * @see PIXI.BLEND_MODES - */ - get blendMode() - { - return this._blendMode; - } - - set blendMode(value) // eslint-disable-line require-jsdoc - { - // 17 is NO BLEND - this.blend = (value !== 17); - this._blendMode = value; - } - - /** - * The polygon offset. Setting this property to anything other than 0 will automatically enable poygon offset fill. - * - * @member {number} - * @default 0 - */ - get polygonOffset() - { - return this._polygonOffset; - } - - set polygonOffset(value) // eslint-disable-line require-jsdoc - { - this.offsets = !!value; - this._polygonOffset = value; - } -} - diff --git a/packages/core/src/renderers/filters/Filter.js b/packages/core/src/renderers/filters/Filter.js deleted file mode 100644 index 2073b8d..0000000 --- a/packages/core/src/renderers/filters/Filter.js +++ /dev/null @@ -1,140 +0,0 @@ -import Shader from '../../shader/Shader'; -import Program from '../../shader/Program'; -import State from '../State'; -import { settings } from '@pixi/settings'; -// import extractUniformsFromSrc from './extractUniformsFromSrc'; -import defaultVertex from './defaultFilter.vert'; -import defaultFragment from './defaultFilter.frag'; - -// let math = require('../../math'); -/** - * @class - * @memberof PIXI - * @extends PIXI.Shader - */ -export default class Filter extends Shader -{ - /** - * @param {string} [vertexSrc] - The source of the vertex shader. - * @param {string} [fragmentSrc] - The source of the fragment shader. - * @param {object} [uniforms] - Custom uniforms to use to augment the built-in ones. - */ - constructor(vertexSrc, fragmentSrc, uniforms) - { - const program = Program.from(vertexSrc, fragmentSrc); - - super(program, uniforms); - - /** - * The padding of the filter. Some filters require extra space to breath such as a blur. - * Increasing this will add extra width and height to the bounds of the object that the - * filter is applied to. - * - * @member {number} - */ - this.padding = 0; - - /** - * The resolution of the filter. Setting this to be lower will lower the quality but - * increase the performance of the filter. - * - * @member {number} - */ - this.resolution = settings.RESOLUTION; - - /** - * If enabled is true the filter is applied, if false it will not. - * - * @member {boolean} - */ - this.enabled = true; - - /** - * If enabled, PixiJS will fit the filter area into boundaries for better performance. - * Switch it off if it does not work for specific shader. - * - * @member {boolean} - */ - this.autoFit = true; - - /** - * Legacy filters use position and uvs from attributes - * @member {boolean} - * @readonly - */ - this.legacy = !!this.program.attributeData.aTextureCoord; - - /** - * the webGL state the filter requires to render - * @member {PIXI.State} - */ - this.state = new State(); - } - - /** - * Applies the filter - * - * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from - * @param {PIXI.RenderTarget} input - The input render target. - * @param {PIXI.RenderTarget} output - The target to output to. - * @param {boolean} clear - Should the output be cleared before rendering to it - * @param {object} [currentState] - It's current state of filter. - * There are some useful properties in the currentState : - * target, filters, sourceFrame, destinationFrame, renderTarget, resolution - */ - apply(filterManager, input, output, clear, currentState, derp) // eslint-disable-line no-unused-vars - { - // do as you please! - - filterManager.applyFilter(this, input, output, clear, currentState, derp); - - // or just do a regular render.. - } - - /** - * Sets the blendmode of the filter - * - * @member {number} - * @default PIXI.BLEND_MODES.NORMAL - */ - get blendMode() - { - return this.state.blendMode; - } - - set blendMode(value) // eslint-disable-line require-jsdoc - { - this.state.blendMode = value; - } - - /** - * The default vertex shader source - * - * @static - * @constant - */ - static get defaultVertexSrc() - { - return defaultVertex; - } - - /** - * The default fragment shader source - * - * @static - * @constant - */ - static get defaultFragmentSrc() - { - return defaultFragment; - } -} - -/** - * Used for caching shader IDs - * - * @static - * @private - */ -Filter.SOURCE_KEY_MAP = {}; - diff --git a/packages/core/src/renderers/filters/defaultFilter.frag b/packages/core/src/renderers/filters/defaultFilter.frag deleted file mode 100644 index a14c1be..0000000 --- a/packages/core/src/renderers/filters/defaultFilter.frag +++ /dev/null @@ -1,22 +0,0 @@ -varying vec2 vTextureCoord; -varying vec2 vFilterCoord; - -uniform sampler2D uSampler; -uniform sampler2D filterSampler; - -void main(void){ - vec4 masky = texture2D(filterSampler, vFilterCoord); - vec4 sample = texture2D(uSampler, vTextureCoord); - vec4 color; - if(mod(vFilterCoord.x, 1.0) > 0.5) - { - color = vec4(1.0, 0.0, 0.0, 1.0); - } - else - { - color = vec4(0.0, 1.0, 0.0, 1.0); - } - // gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0); - gl_FragColor = mix(sample, masky, 0.5); - gl_FragColor *= sample.a; -} diff --git a/packages/core/src/renderers/filters/defaultFilter.vert b/packages/core/src/renderers/filters/defaultFilter.vert deleted file mode 100644 index e1febca..0000000 --- a/packages/core/src/renderers/filters/defaultFilter.vert +++ /dev/null @@ -1,14 +0,0 @@ -attribute vec2 aVertexPosition; -attribute vec2 aTextureCoord; - -uniform mat3 projectionMatrix; -uniform mat3 filterMatrix; - -varying vec2 vTextureCoord; -varying vec2 vFilterCoord; - -void main(void){ - gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); - vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy; - vTextureCoord = aTextureCoord ; -} diff --git a/packages/core/src/renderers/filters/filterTransforms.js b/packages/core/src/renderers/filters/filterTransforms.js deleted file mode 100644 index 87a3cd6..0000000 --- a/packages/core/src/renderers/filters/filterTransforms.js +++ /dev/null @@ -1,50 +0,0 @@ -import { Matrix } from '@pixi/math'; - -/** - * Calculates the mapped matrix - * @param filterArea {Rectangle} The filter area - * @param sprite {Sprite} the target sprite - * @param outputMatrix {Matrix} @alvin - * @private - */ -// this returns a matrix that will normalise map filter cords in the filter to screen space -export function calculateScreenSpaceMatrix(outputMatrix, filterArea, textureSize) -{ - // TODO unwrap? - const mappedMatrix = outputMatrix.identity(); - - mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); - - mappedMatrix.scale(textureSize.width, textureSize.height); - - return mappedMatrix; -} - -export function calculateNormalizedScreenSpaceMatrix(outputMatrix, filterArea, textureSize) -{ - const mappedMatrix = outputMatrix.identity(); - - mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); - - const translateScaleX = (textureSize.width / filterArea.width); - const translateScaleY = (textureSize.height / filterArea.height); - - mappedMatrix.scale(translateScaleX, translateScaleY); - - return mappedMatrix; -} - -// this will map the filter coord so that a texture can be used based on the transform of a sprite -export function calculateSpriteMatrix(outputMatrix, filterArea, textureSize, sprite) -{ - const orig = sprite._texture.orig; - const mappedMatrix = outputMatrix.set(textureSize.width, 0, 0, textureSize.height, filterArea.x, filterArea.y); - const worldTransform = sprite.worldTransform.copyTo(Matrix.TEMP_MATRIX); - - worldTransform.invert(); - mappedMatrix.prepend(worldTransform); - mappedMatrix.scale(1.0 / orig.width, 1.0 / orig.height); - mappedMatrix.translate(sprite.anchor.x, sprite.anchor.y); - - return mappedMatrix; -} diff --git a/packages/core/src/renderers/filters/spriteMask/SpriteMaskFilter.js b/packages/core/src/renderers/filters/spriteMask/SpriteMaskFilter.js deleted file mode 100644 index d5a91a4..0000000 --- a/packages/core/src/renderers/filters/spriteMask/SpriteMaskFilter.js +++ /dev/null @@ -1,63 +0,0 @@ -import Filter from '../Filter'; -import { Matrix } from '@pixi/math'; -import vertex from './spriteMaskFilter.vert'; -import fragment from './spriteMaskFilter.frag'; -import { default as TextureMatrix } from '../../../textures/TextureMatrix'; - -/** - * The SpriteMaskFilter class - * - * @class - * @extends PIXI.Filter - * @memberof PIXI - */ -export default class SpriteMaskFilter extends Filter -{ - /** - * @param {PIXI.Sprite} sprite - the target sprite - */ - constructor(sprite) - { - const maskMatrix = new Matrix(); - - super(vertex, fragment); - - sprite.renderable = false; - - this.maskSprite = sprite; - this.maskMatrix = maskMatrix; - } - - /** - * Applies the filter - * - * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from - * @param {PIXI.RenderTarget} input - The input render target. - * @param {PIXI.RenderTarget} output - The target to output to. - */ - apply(filterManager, input, output) - { - const maskSprite = this.maskSprite; - const tex = this.maskSprite.texture; - - if (!tex.valid) - { - return; - } - if (!tex.transform) - { - // margin = 0.0, let it bleed a bit, shader code becomes easier - // assuming that atlas textures were made with 1-pixel padding - tex.transform = new TextureMatrix(tex, 0.0); - } - tex.transform.update(); - - this.uniforms.mask = tex; - this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite) - .prepend(tex.transform.mapCoord); - this.uniforms.alpha = maskSprite.worldAlpha; - this.uniforms.maskClamp = tex.transform.uClampFrame; - - filterManager.applyFilter(this, input, output); - } -} diff --git a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.frag b/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.frag deleted file mode 100644 index 0e4aef8..0000000 --- a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.frag +++ /dev/null @@ -1,23 +0,0 @@ -varying vec2 vMaskCoord; -varying vec2 vTextureCoord; - -uniform sampler2D uSampler; -uniform sampler2D mask; -uniform float alpha; -uniform vec4 maskClamp; - -void main(void) -{ - float clip = step(3.5, - step(maskClamp.x, vMaskCoord.x) + - step(maskClamp.y, vMaskCoord.y) + - step(vMaskCoord.x, maskClamp.z) + - step(vMaskCoord.y, maskClamp.w)); - - vec4 original = texture2D(uSampler, vTextureCoord); - vec4 masky = texture2D(mask, vMaskCoord); - - original *= (masky.r * masky.a * alpha * clip); - - gl_FragColor = original; -} diff --git a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.vert b/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.vert deleted file mode 100644 index d23a30c..0000000 --- a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.vert +++ /dev/null @@ -1,16 +0,0 @@ -attribute vec2 aVertexPosition; -attribute vec2 aTextureCoord; - -uniform mat3 projectionMatrix; -uniform mat3 otherMatrix; - -varying vec2 vMaskCoord; -varying vec2 vTextureCoord; - -void main(void) -{ - gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); - - vTextureCoord = aTextureCoord; - vMaskCoord = ( otherMatrix * vec3( aTextureCoord, 1.0) ).xy; -} diff --git a/packages/core/src/renderers/systems/BatchSystem.js b/packages/core/src/renderers/systems/BatchSystem.js deleted file mode 100644 index 70e9518..0000000 --- a/packages/core/src/renderers/systems/BatchSystem.js +++ /dev/null @@ -1,68 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import ObjectRenderer from '../utils/ObjectRenderer'; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ - -export default class BatchSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this manager works for. - */ - constructor(renderer) - { - super(renderer); - - /** - * An empty renderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.emptyRenderer = new ObjectRenderer(renderer); - - /** - * The currently active ObjectRenderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.currentRenderer = this.emptyRenderer; - } - - /** - * Changes the current renderer to the one given in parameter - * - * @param {PIXI.ObjectRenderer} objectRenderer - The object renderer to use. - */ - setObjectRenderer(objectRenderer) - { - if (this.currentRenderer === objectRenderer) - { - return; - } - - this.currentRenderer.stop(); - this.currentRenderer = objectRenderer; - - this.renderer.state.setState(objectRenderer.state); - - this.currentRenderer.start(); - } - - /** - * This should be called if you wish to do some custom rendering - * It will basically render anything that may be batched up such as sprites - * - */ - flush() - { - this.setObjectRenderer(this.emptyRenderer); - } - - reset() - { - this.setObjectRenderer(this.emptyRenderer); - } -} diff --git a/packages/core/src/renderers/systems/ContextSystem.js b/packages/core/src/renderers/systems/ContextSystem.js deleted file mode 100644 index 43bf827..0000000 --- a/packages/core/src/renderers/systems/ContextSystem.js +++ /dev/null @@ -1,186 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { settings } from '@pixi/settings'; - -let CONTEXT_UID = 0; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class ContextSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this.webGLVersion = 1; - - this.handleContextLost = this.handleContextLost.bind(this); - this.handleContextRestored = this.handleContextRestored.bind(this); - - this.extensions = {}; - - renderer.view.addEventListener('webglcontextlost', this.handleContextLost, false); - renderer.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); - } - - get isLost() - { - return (!this.gl || this.gl.isContextLost()); - } - - contextChange(gl) - { - this.gl = gl; - - // restore a context if it was previously lost - if (gl.isContextLost() && gl.getExtension('WEBGL_lose_context')) - { - gl.getExtension('WEBGL_lose_context').restoreContext(); - } - } - - initFromContext(gl) - { - this.gl = gl; - this.validateContext(gl); - this.renderer.gl = gl; - this.renderer.CONTEXT_UID = CONTEXT_UID++; - this.renderer.runners.contextChange.run(gl); - } - - initFromOptions(options) - { - const gl = this.createContext(this.renderer.view, options); - - this.initFromContext(gl); - } - - /** - * Helper class to create a webGL Context - * - * @class - * @memberof PIXI.glCore - * @param canvas {HTMLCanvasElement} the canvas element that we will get the context from - * @param options {Object} An options object that gets passed in to the canvas element containing the context attributes, - * see https://developer.mozilla.org/en/docs/Web/API/HTMLCanvasElement/getContext for the options available - * @return {WebGLRenderingContext} the WebGL context - */ - createContext(canvas, options) - { - let gl; - - if (settings.PREFER_WEBGL_2) - { - gl = canvas.getContext('webgl2', options); - } - - if (gl) - { - this.webGLVersion = 2; - } - else - { - this.webGLVersion = 1; - - gl = canvas.getContext('webgl', options) - || canvas.getContext('experimental-webgl', options); - - if (!gl) - { - // fail, not able to get a context - throw new Error('This browser does not support webGL. Try using the canvas renderer'); - } - } - - this.gl = gl; - - this.getExtensions(); - - return gl; - } - - getExtensions() - { - // time to set up default etensions that pixi uses.. - const gl = this.gl; - const extensions = this.extensions; - - if (this.webGLVersion === 1) - { - extensions.drawBuffers = gl.getExtension('WEBGL_draw_buffers'); - extensions.depthTexture = gl.getExtension('WEBKIT_WEBGL_depth_texture'); - extensions.floatTexture = gl.getExtension('OES_texture_float'); - extensions.loseContext = gl.getExtension('WEBGL_lose_context'); - - extensions.vertexArrayObject = gl.getExtension('OES_vertex_array_object') - || gl.getExtension('MOZ_OES_vertex_array_object') - || gl.getExtension('WEBKIT_OES_vertex_array_object'); - } - - // we don't use any specific WebGL 2 ones yet! - } - - /** - * Handles a lost webgl context - * - * @private - * @param {WebGLContextEvent} event - The context lost event. - */ - handleContextLost(event) - { - event.preventDefault(); - } - - /** - * Handles a restored webgl context - * - * @private - */ - handleContextRestored() - { - this.renderer.runners.contextChange.run(this.gl); - } - - destroy() - { - const view = this.renderer.view; - - // remove listeners - view.removeEventListener('webglcontextlost', this.handleContextLost); - view.removeEventListener('webglcontextrestored', this.handleContextRestored); - - this.gl.useProgram(null); - - if (this.extensions.loseContext) - { - this.extensions.loseContext.loseContext(); - } - } - - postrender() - { - this.gl.flush(); - } - - validateContext(gl) - { - const attributes = gl.getContextAttributes(); - - // this is going to be fairly simple for now.. but at least we have room to grow! - if (!attributes.stencil) - { - /* eslint-disable max-len */ - - /* eslint-disable no-console */ - console.warn('Provided WebGL context does not have a stencil buffer, masks may not render correctly'); - /* eslint-enable no-console */ - - /* eslint-enable max-len */ - } - } -} diff --git a/packages/core/src/renderers/systems/FramebufferSystem.js b/packages/core/src/renderers/systems/FramebufferSystem.js deleted file mode 100644 index df7902b..0000000 --- a/packages/core/src/renderers/systems/FramebufferSystem.js +++ /dev/null @@ -1,246 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { Rectangle } from '@pixi/math'; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class FramebufferSystem extends WebGLSystem -{ - /** - * Sets up the renderer context and necessary buffers. - * - * @private - */ - contextChange() - { - this.gl = this.renderer.gl; - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - this.current = null; - this.viewport = new Rectangle(); - - this.drawBufferExtension = this.renderer.context.extensions.drawBuffers; - } - - bind(framebuffer, frame) - { - const gl = this.gl; - - this.current = framebuffer; - - if (framebuffer) - { - // TODO cacheing layer! - - const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID] || this.initFramebuffer(framebuffer); - - gl.bindFramebuffer(gl.FRAMEBUFFER, fbo.framebuffer); - // makesure all textures are unbound.. - - // now check for updates... - if (fbo.dirtyId !== framebuffer.dirtyId) - { - fbo.dirtyId = framebuffer.dirtyId; - - if (fbo.dirtyFormat !== framebuffer.dirtyFormat) - { - fbo.dirtyFormat = framebuffer.dirtyFormat; - this.updateFramebuffer(framebuffer); - } - else if (fbo.dirtySize !== framebuffer.dirtySize) - { - fbo.dirtySize = framebuffer.dirtySize; - this.resizeFramebuffer(framebuffer); - } - } - - for (let i = 0; i < framebuffer.colorTextures.length; i++) - { - if (framebuffer.colorTextures[i].texturePart) - { - this.renderer.texture.unbind(framebuffer.colorTextures[i].texture); - } - else - { - this.renderer.texture.unbind(framebuffer.colorTextures[i]); - } - } - - if (framebuffer.depthTexture) - { - this.renderer.texture.unbind(framebuffer.depthTexture); - } - - if (frame) - { - this.setViewport(frame.x, frame.y, frame.width, frame.height); - } - else - { - this.setViewport(0, 0, framebuffer.width, framebuffer.height); - } - } - else - { - gl.bindFramebuffer(gl.FRAMEBUFFER, null); - - if (frame) - { - this.setViewport(frame.x, frame.y, frame.width, frame.height); - } - else - { - this.setViewport(0, 0, this.renderer.width, this.renderer.height); - } - } - } - - setViewport(x, y, width, height) - { - const v = this.viewport; - - if (v.width !== width || v.height !== height || v.x !== x || v.y !== y) - { - v.x = x; - v.y = y; - v.width = width; - v.height = height; - - this.gl.viewport(x, y, width, height); - } - } - - get size() - { - if (this.current) - { - // TODO store temp - return { x: 0, y: 0, width: this.current.width, height: this.current.height }; - } - - return { x: 0, y: 0, width: this.renderer.width, height: this.renderer.height }; - } - - clear(r, g, b, a) - { - const gl = this.gl; - - // TODO clear color can be set only one right? - gl.clearColor(r, g, b, a); - gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); - } - - // private functions... - - initFramebuffer(framebuffer) - { - const gl = this.gl; - - // TODO - make this a class? - const fbo = { - framebuffer: gl.createFramebuffer(), - stencil: null, - dirtyId: 0, - dirtyFormat: 0, - dirtySize: 0, - }; - - framebuffer.glFrameBuffers[this.CONTEXT_UID] = fbo; - - return fbo; - } - - resizeFramebuffer(framebuffer) - { - const gl = this.gl; - - if (framebuffer.stencil || framebuffer.depth) - { - gl.bindRenderbuffer(gl.RENDERBUFFER, this.stencil); - gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); - } - } - - updateFramebuffer(framebuffer) - { - const gl = this.gl; - - const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID]; - - // bind the color texture - const colorTextures = framebuffer.colorTextures; - - let count = colorTextures.length; - - if (!this.drawBufferExtension) - { - count = Math.min(count, 1); - } - - const activeTextures = []; - - for (let i = 0; i < count; i++) - { - const texture = framebuffer.colorTextures[i]; - - if (texture.texturePart) - { - this.renderer.texture.bind(texture.texture, 0); - - gl.framebufferTexture2D(gl.FRAMEBUFFER, - gl.COLOR_ATTACHMENT0 + i, - gl.TEXTURE_CUBE_MAP_NEGATIVE_X + texture.side, - texture.texture._glTextures[this.CONTEXT_UID].texture, - 0); - } - else - { - this.renderer.texture.bind(texture, 0); - - gl.framebufferTexture2D(gl.FRAMEBUFFER, - gl.COLOR_ATTACHMENT0 + i, - gl.TEXTURE_2D, - texture._glTextures[this.CONTEXT_UID].texture, - 0); - } - - activeTextures.push(gl.COLOR_ATTACHMENT0 + i); - } - - if (this.drawBufferExtension && activeTextures.length > 1) - { - this.drawBufferExtension.drawBuffersWEBGL(activeTextures); - } - - if (framebuffer.depthTexture) - { - const depthTextureExt = this.renderer.context.extensions.depthTexture; - - if (depthTextureExt) - { - const depthTexture = framebuffer.depthTexture; - - this.renderer.texture.bind(depthTexture, 0); - - gl.framebufferTexture2D(gl.FRAMEBUFFER, - gl.DEPTH_ATTACHMENT, - gl.TEXTURE_2D, - depthTexture._glTextures[this.CONTEXT_UID].texture, - 0); - } - } - - if (framebuffer.stencil || framebuffer.depth) - { - fbo.stencil = gl.createRenderbuffer(); - - gl.bindRenderbuffer(gl.RENDERBUFFER, fbo.stencil); - - // TODO.. this is depth AND stencil? - gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, fbo.stencil); - gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); - // fbo.enableStencil(); - } - } -} diff --git a/packages/core/src/renderers/systems/MaskSystem.js b/packages/core/src/renderers/systems/MaskSystem.js deleted file mode 100644 index d9c4b1a..0000000 --- a/packages/core/src/renderers/systems/MaskSystem.js +++ /dev/null @@ -1,199 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import AlphaMaskFilter from '../filters/spriteMask/SpriteMaskFilter'; - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class MaskSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - // TODO - we don't need both! - this.scissor = false; - this.scissorData = null; - this.scissorRenderTarget = null; - - this.enableScissor = false; - - this.alphaMaskPool = []; - this.alphaMaskIndex = 0; - } - - /** - * Applies the Mask and adds it to the current filter stack. - * - * @param {PIXI.DisplayObject} target - Display Object to push the mask to - * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. - */ - push(target, maskData) - { - // TODO the root check means scissor rect will not - // be used on render textures more info here: - // https://github.com/pixijs/pixi.js/pull/3545 - - if (maskData.texture) - { - this.pushSpriteMask(target, maskData); - } - else if (this.enableScissor - && !this.scissor - && this.renderer._activeRenderTarget.root - && !this.renderer.stencil.stencilMaskStack.length - && maskData.isFastRect()) - { - const matrix = maskData.worldTransform; - - let rot = Math.atan2(matrix.b, matrix.a); - - // use the nearest degree! - rot = Math.round(rot * (180 / Math.PI)); - - if (rot % 90) - { - this.pushStencilMask(maskData); - } - else - { - this.pushScissorMask(target, maskData); - } - } - else - { - this.pushStencilMask(maskData); - } - } - - /** - * Removes the last mask from the mask stack and doesn't return it. - * - * @param {PIXI.DisplayObject} target - Display Object to pop the mask from - * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. - */ - pop(target, maskData) - { - if (maskData.texture) - { - this.popSpriteMask(target, maskData); - } - else if (this.enableScissor && !this.renderer.stencil.stencilMaskStack.length) - { - this.popScissorMask(target, maskData); - } - else - { - this.popStencilMask(target, maskData); - } - } - - /** - * Applies the Mask and adds it to the current filter stack. - * - * @param {PIXI.RenderTarget} target - Display Object to push the sprite mask to - * @param {PIXI.Sprite} maskData - Sprite to be used as the mask - */ - pushSpriteMask(target, maskData) - { - let alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; - - if (!alphaMaskFilter) - { - alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; - } - - alphaMaskFilter[0].resolution = this.renderer.resolution; - alphaMaskFilter[0].maskSprite = maskData; - - // TODO - may cause issues! - target.filterArea = maskData.getBounds(true); - - this.renderer.filterSystem.pushFilter(target, alphaMaskFilter); - - this.alphaMaskIndex++; - } - - /** - * Removes the last filter from the filter stack and doesn't return it. - * - */ - popSpriteMask() - { - this.renderer.filterSystem.popFilter(); - this.alphaMaskIndex--; - } - - /** - * Applies the Mask and adds it to the current filter stack. - * - * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. - */ - pushStencilMask(maskData) - { - this.renderer.batch.flush(); - this.renderer.stencil.pushStencil(maskData); - } - - /** - * Removes the last filter from the filter stack and doesn't return it. - * - */ - popStencilMask() - { - // this.renderer.currentRenderer.stop(); - this.renderer.stencil.popStencil(); - } - - /** - * - * @param {PIXI.DisplayObject} target - Display Object to push the mask to - * @param {PIXI.Graphics} maskData - The masking data. - */ - pushScissorMask(target, maskData) - { - maskData.renderable = true; - - const renderTarget = this.renderer._activeRenderTarget; - - const bounds = maskData.getBounds(); - - bounds.fit(renderTarget.size); - maskData.renderable = false; - - this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); - - const resolution = this.renderer.resolution; - - this.renderer.gl.scissor( - bounds.x * resolution, - (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, - bounds.width * resolution, - bounds.height * resolution - ); - - this.scissorRenderTarget = renderTarget; - this.scissorData = maskData; - this.scissor = true; - } - - /** - * - * - */ - popScissorMask() - { - this.scissorRenderTarget = null; - this.scissorData = null; - this.scissor = false; - - // must be scissor! - const gl = this.renderer.gl; - - gl.disable(gl.SCISSOR_TEST); - } -} diff --git a/packages/core/src/renderers/systems/ProjectionSystem.js b/packages/core/src/renderers/systems/ProjectionSystem.js deleted file mode 100644 index ec5efdb..0000000 --- a/packages/core/src/renderers/systems/ProjectionSystem.js +++ /dev/null @@ -1,73 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { Matrix } from '@pixi/math'; - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ - -export default class ProjectionSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this.projectionMatrix = new Matrix(); - } - - update(destinationFrame, sourceFrame, resolution, root) - { - this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; - this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; - - this.calculateProjection(this.destinationFrame, this.sourceFrame, resolution, root); - - this.renderer.globalUniforms.uniforms.projectionMatrix = this.projectionMatrix; - this.renderer.globalUniforms.update(); - } - - /** - * Updates the projection matrix based on a projection frame (which is a rectangle) - * - * @param {Rectangle} destinationFrame - The destination frame. - * @param {Rectangle} sourceFrame - The source frame. - */ - calculateProjection(destinationFrame, sourceFrame, resolution, root) - { - const pm = this.projectionMatrix; - - // I don't think we will need this line.. - // pm.identity(); - - if (!root) - { - pm.a = (1 / destinationFrame.width * 2) * resolution; - pm.d = (1 / destinationFrame.height * 2) * resolution; - - pm.tx = -1 - (sourceFrame.x * pm.a); - pm.ty = -1 - (sourceFrame.y * pm.d); - } - else - { - pm.a = (1 / destinationFrame.width * 2) * resolution; - pm.d = (-1 / destinationFrame.height * 2) * resolution; - - pm.tx = -1 - (sourceFrame.x * pm.a); - pm.ty = 1 - (sourceFrame.y * pm.d); - } - } - - /** - * Sets the transform of the active render target to the given matrix - * - * @param {PIXI.Matrix} matrix - The transformation matrix - */ - setTransform()// matrix) - { - // this._activeRenderTarget.transform = matrix; - } -} diff --git a/packages/core/src/renderers/systems/RenderTextureSystem.js b/packages/core/src/renderers/systems/RenderTextureSystem.js deleted file mode 100644 index a133ef1..0000000 --- a/packages/core/src/renderers/systems/RenderTextureSystem.js +++ /dev/null @@ -1,115 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { Rectangle } from '@pixi/math'; - -const tempRect = new Rectangle(); - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ - -export default class RenderTextureSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this.clearColor = renderer._backgroundColorRgba; - - // TODO moe this property somewhere else! - this.defaultMaskStack = []; - this.defaultFilterStack = [{}]; - - // empty render texture? - this.renderTexture = null; - - this.destinationFrame = new Rectangle(); - } - - bind(renderTexture, sourceFrame, destinationFrame) - { - // TODO - do we want this?? - if (this.renderTexture === renderTexture) return; - this.renderTexture = renderTexture; - - const renderer = this.renderer; - - if (renderTexture) - { - const baseTexture = renderTexture.baseTexture; - - if (!destinationFrame) - { - tempRect.width = baseTexture.realWidth; - tempRect.height = baseTexture.realHeight; - - destinationFrame = tempRect; - } - - if (!sourceFrame) - { - sourceFrame = destinationFrame; - } - - this.renderer.framebuffer.bind(baseTexture.frameBuffer, destinationFrame); - - this.renderer.projection.update(destinationFrame, sourceFrame, baseTexture.resolution, false); - this.renderer.stencil.setMaskStack(baseTexture.stencilMaskStack); - } - else - { - // TODO these validation checks happen deeper down.. - // thing they can be avoided.. - if (!destinationFrame) - { - tempRect.width = renderer.width; - tempRect.height = renderer.height; - - destinationFrame = tempRect; - } - - if (!sourceFrame) - { - sourceFrame = destinationFrame; - } - - renderer.framebuffer.bind(null, destinationFrame); - - // TODO store this.. - this.renderer.projection.update(destinationFrame, sourceFrame, this.renderer.resolution, true); - this.renderer.stencil.setMaskStack(this.defaultMaskStack); - } - - this.destinationFrame.copyFrom(destinationFrame); - } - - /** - * Erases the render texture and fills the drawing area with a colour - * - * @param {number} [clearColor] - The colour - * @return {PIXI.Renderer} Returns itself. - */ - clear(clearColor) - { - if (this.renderTexture) - { - clearColor = clearColor || this.renderTexture.baseTexture.clearColor; - } - else - { - clearColor = clearColor || this.clearColor; - } - - this.renderer.framebuffer.clear(clearColor[0], clearColor[1], clearColor[2], clearColor[3]); - } - - resize()// screenWidth, screenHeight) - { - // resize the root only! - this.bind(null); - } -} diff --git a/packages/core/src/renderers/systems/StateSystem.js b/packages/core/src/renderers/systems/StateSystem.js deleted file mode 100755 index d7f4caf..0000000 --- a/packages/core/src/renderers/systems/StateSystem.js +++ /dev/null @@ -1,288 +0,0 @@ -import mapWebGLBlendModesToPixi from '../utils/mapWebGLBlendModesToPixi'; -import WebGLSystem from './WebGLSystem'; -import WebGLState from '../State'; - -const BLEND = 0; -const OFFSET = 1; -const CULLING = 2; -const DEPTH_TEST = 3; -const WINDING = 4; - -/** - * A WebGL state machines - * - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class StateSystem extends WebGLSystem -{ - /** - * @param {WebGLRenderingContext} gl - The current WebGL rendering context - */ - constructor(renderer) - { - super(renderer); - - this.gl = null; - - this.maxAttribs = null; - - // check we have vao.. - this.nativeVaoExtension = null; - - this.attribState = null; - - this.stateId = 0; - this.polygonOffset = 0; - this.blendMode = 17; - - this.map = []; - - // map functions for when we set state.. - this.map[BLEND] = this.setBlend; - this.map[OFFSET] = this.setOffset; - this.map[CULLING] = this.setCullFace; - this.map[DEPTH_TEST] = this.setDepthTest; - this.map[WINDING] = this.setFrontFace; - - this.checks = []; - - this.defaultState = new WebGLState(); - this.defaultState.blend = true; - this.defaultState.depth = true; - } - - contextChange(gl) - { - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; - - this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); - - // check we have vao.. - this.nativeVaoExtension = ( - gl.getExtension('OES_vertex_array_object') - || gl.getExtension('MOZ_OES_vertex_array_object') - || gl.getExtension('WEBKIT_OES_vertex_array_object') - ); - - this.attribState = { - tempAttribState: new Array(this.maxAttribs), - attribState: new Array(this.maxAttribs), - }; - - this.blendModes = mapWebGLBlendModesToPixi(gl); - - this.setState(this.defaultState); - - this.reset(); - } - - /** - * Sets the current state - * - * @param {*} state - The state to set. - */ - setState(state) - { - state = state || this.defaultState; - - // TODO maybe to an object check? ( this.state === state )? - if (this.stateId !== state.data) - { - let diff = this.stateId ^ state.data; - let i = 0; - - // order from least to most common - while (diff) - { - if (diff & 1) - { - // state change! - this.map[i].call(this, !!(state.data & (1 << i))); - } - - diff = diff >> 1; - i++; - } - - this.stateId = state.data; - } - - // based on the above settings we check for specific modes.. - // for example if blend is active we check and set the blend modes - // or of polygon offset is active we check the poly depth. - for (let i = 0; i < this.checks.length; i++) - { - this.checks[i](this, state); - } - } - - /** - * Enables or disabled blending. - * - * @param {boolean} value - Turn on or off webgl blending. - */ - setBlend(value) - { - this.updateCheck(StateSystem.checkBlendMode, value); - - this.gl[value ? 'enable' : 'disable'](this.gl.BLEND); - } - - /** - * Enables or disable polygon offset fill - * - * @param {boolean} value - Turn on or off webgl polygon offset testing. - */ - setOffset(value) - { - this.gl[value ? 'enable' : 'disable'](this.gl.POLYGON_OFFSET_FILL); - } - - /** - * Sets whether to enable or disable depth test. - * - * @param {boolean} value - Turn on or off webgl depth testing. - */ - setDepthTest(value) - { - this.gl[value ? 'enable' : 'disable'](this.gl.DEPTH_TEST); - } - - /** - * Sets whether to enable or disable cull face. - * - * @param {boolean} value - Turn on or off webgl cull face. - */ - setCullFace(value) - { - this.gl[value ? 'enable' : 'disable'](this.gl.CULL_FACE); - } - - /** - * Sets the gl front face. - * - * @param {boolean} value - true is clockwise and false is counter-clockwise - */ - setFrontFace(value) - { - this.gl.frontFace(this.gl[value ? 'CW' : 'CCW']); - } - - /** - * Sets the blend mode. - * - * @param {number} value - The blend mode to set to. - */ - setBlendMode(value) - { - if (value === this.blendMode) - { - return; - } - - this.blendMode = value; - this.gl.blendFunc(this.blendModes[value][0], this.blendModes[value][1]); - } - - /** - * Sets the polygon offset. - * - * @param {number} value - the polygon offset - * @param {number} scale - the polygon offset scale - */ - setPolygonOffset(value, scale) - { - this.gl.polygonOffset(value, scale); - } - - /** - * Disables all the vaos in use - * - */ - resetAttributes() - { - for (let i = 0; i < this.attribState.tempAttribState.length; i++) - { - this.attribState.tempAttribState[i] = 0; - } - - for (let i = 0; i < this.attribState.attribState.length; i++) - { - this.attribState.attribState[i] = 0; - } - - // im going to assume one is always active for performance reasons. - for (let i = 1; i < this.maxAttribs; i++) - { - this.gl.disableVertexAttribArray(i); - } - } - - // used - /** - * Resets all the logic and disables the vaos - */ - reset() - { - // unbind any VAO if they exist.. - if (this.nativeVaoExtension) - { - this.nativeVaoExtension.bindVertexArrayOES(null); - } - - // reset all attributes.. - this.resetAttributes(); - - this.gl.pixelStorei(this.gl.UNPACK_FLIP_Y_WEBGL, false); - - this.setBlendMode(0); - - // TODO? - // this.setState(this.defaultState); - } - - /** - * checks to see which updates should be checked based on which settings have been activated - * for example if blend is enabled then we shold check the blend modes each time the state is changed - * or if poygon fill is activated then we need to check if the polygone offset changes. - * The idea is that we only check what we have too. - * - * @param {Function} func the checking function to add or remove - * @param {boolean} value should the check function be added or removed. - */ - updateCheck(func, value) - { - const index = this.checks.indexOf(func); - - if (value && index === -1) - { - this.checks.push(func); - } - else if (!value && index !== -1) - { - this.checks.splice(index, 1); - } - } - - /** - * A private little wrapper function that we call to check the blend mode. - * - * @static - * @private - * @param {PIXI.StateSystem} System the System to perform the state check on - * @param {PIXI.State} state the state that the blendMode will pulled from - */ - static checkBlendMode(system, state) - { - system.setBlendMode(state.blendMode); - } - - // TODO - add polygon offset? -} diff --git a/packages/core/src/renderers/systems/StencilSystem.js b/packages/core/src/renderers/systems/StencilSystem.js deleted file mode 100644 index 5d538cb..0000000 --- a/packages/core/src/renderers/systems/StencilSystem.js +++ /dev/null @@ -1,133 +0,0 @@ -import WebGLSystem from './WebGLSystem'; - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class StencilSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - this.stencilMaskStack = []; - } - - /** - * Changes the mask stack that is used by this System. - * - * @param {PIXI.Graphics[]} stencilMaskStack - The mask stack - */ - setMaskStack(stencilMaskStack) - { - const gl = this.renderer.gl; - - if (stencilMaskStack.length !== this.stencilMaskStack.length) - { - if (stencilMaskStack.length === 0) - { - gl.disable(gl.STENCIL_TEST); - } - else - { - gl.enable(gl.STENCIL_TEST); - } - } - - this.stencilMaskStack = stencilMaskStack; - } - - /** - * Applies the Mask and adds it to the current stencil stack. @alvin - * - * @param {PIXI.Graphics} graphics - The mask - */ - pushStencil(graphics) - { - this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); - - // this.renderer._activeRenderTarget.attachStencilBuffer(); - - const gl = this.renderer.gl; - const prevMaskCount = this.stencilMaskStack.length; - - if (prevMaskCount === 0) - { - gl.enable(gl.STENCIL_TEST); - } - - this.stencilMaskStack.push(graphics); - - // Increment the refference stencil value where the new mask overlaps with the old ones. - gl.colorMask(false, false, false, false); - gl.stencilFunc(gl.EQUAL, prevMaskCount, this._getBitwiseMask()); - gl.stencilOp(gl.KEEP, gl.KEEP, gl.INCR); - this.renderer.plugins.graphics.render(graphics); - - this._useCurrent(); - } - - /** - * Removes the last mask from the stencil stack. @alvin - */ - popStencil() - { - this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); - - const gl = this.renderer.gl; - const graphics = this.stencilMaskStack.pop(); - - if (this.stencilMaskStack.length === 0) - { - // the stack is empty! - gl.disable(gl.STENCIL_TEST); - gl.clear(gl.STENCIL_BUFFER_BIT); - gl.clearStencil(0); - } - else - { - // Decrement the refference stencil value where the popped mask overlaps with the other ones - gl.colorMask(false, false, false, false); - gl.stencilOp(gl.KEEP, gl.KEEP, gl.DECR); - this.renderer.plugins.graphics.render(graphics); - - this._useCurrent(); - } - } - - /** - * Setup renderer to use the current stencil data. - */ - _useCurrent() - { - const gl = this.renderer.gl; - - gl.colorMask(true, true, true, true); - gl.stencilFunc(gl.EQUAL, this.stencilMaskStack.length, this._getBitwiseMask()); - gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP); - } - - /** - * Fill 1s equal to the number of acitve stencil masks. - * - * @return {number} The bitwise mask. - */ - _getBitwiseMask() - { - return (1 << this.stencilMaskStack.length) - 1; - } - - /** - * Destroys the mask stack. - * - */ - destroy() - { - super.destroy(this); - - this.stencilMaskStack = null; - } -} diff --git a/packages/core/src/renderers/systems/WebGLSystem.js b/packages/core/src/renderers/systems/WebGLSystem.js deleted file mode 100644 index d3d5b31..0000000 --- a/packages/core/src/renderers/systems/WebGLSystem.js +++ /dev/null @@ -1,41 +0,0 @@ -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this manager works for. - */ - constructor(renderer) - { - /** - * The renderer this manager works for. - * - * @member {PIXI.Renderer} - */ - this.renderer = renderer; - - this.renderer.runners.contextChange.add(this); - } - - /** - * Generic method called when there is a WebGL context change. - * - */ - contextChange() - { - // do some codes init! - } - - /** - * Generic destroy methods to be overridden by the subclass - * - */ - destroy() - { - this.renderer.runners.contextChange.remove(this); - this.renderer = null; - } -} diff --git a/packages/core/src/renderers/systems/filter/FilterSystem.js b/packages/core/src/renderers/systems/filter/FilterSystem.js deleted file mode 100644 index e615037..0000000 --- a/packages/core/src/renderers/systems/filter/FilterSystem.js +++ /dev/null @@ -1,431 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; - -import RenderTexture from '../../../textures/RenderTexture'; -import Quad from '../../utils/Quad'; -import QuadUv from '../../utils/QuadUv'; -import { Rectangle } from '@pixi/math'; -import * as filterTransforms from '../../filters/filterTransforms'; -import bitTwiddle from 'bit-twiddle'; -import UniformGroup from '../../../shader/UniformGroup'; -import { DRAW_MODES } from '../../../../../constants'; - -// -/** - * @ignore - * @class - */ -class FilterState -{ - /** - * - */ - constructor() - { - this.renderTexture = null; - this.sourceFrame = new Rectangle(); - this.destinationFrame = new Rectangle(); - this.filters = []; - this.target = null; - this.legacy = false; - this.resolution = 1; - } -} - -/** - * @class - * @memberof PIXI.systems - * @extends PIXI.systems.WebGLSystem - */ -export default class FilterSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - /** - * stores a bunch of PO2 textures used for filtering - * @type {Object} - */ - this.texturePool = {}; - - /** - * a pool for storing filter states, save us creating new ones each tick - * @type {Array} - */ - this.statePool = []; - - /** - * A very simple geometry used when drawing a filter effect to the screen - * @type {Quad} - */ - this.quad = new Quad(); - - this.quadUv = new QuadUv(); - - /** - * Temporary rect for maths - * @type {PIXI.Rectangle} - */ - this.tempRect = new Rectangle(); - - this.activeState = {}; - - /** - * this uniform group is attached to filter uniforms when used - * @type {UniformGroup} - */ - this.globalUniforms = new UniformGroup({ - sourceFrame: this.tempRect, - destinationFrame: this.tempRect, - - // legacy variables - filterArea: new Float32Array(4), - filterClamp: new Float32Array(4), - }, true); - } - - /** - * Adds a new filter to the System. - * - * @param {PIXI.DisplayObject} target - The target of the filter to render. - * @param {PIXI.Filter[]} filters - The filters to apply. - */ - push(target, filters) - { - const renderer = this.renderer; - const filterStack = this.renderer.renderTexture.defaultFilterStack; - const state = this.statePool.pop() || new FilterState(); - - let resolution = filters[0].resolution; - let padding = filters[0].padding; - let autoFit = filters[0].autoFit; - let legacy = filters[0].legacy; - - for (let i = 1; i < filters.length; i++) - { - const filter = filters[i]; - - // lets use the lowest resolution.. - resolution = Math.min(resolution, filter.resolution); - // and the largest amount of padding! - padding = Math.max(padding, filter.padding); - // only auto fit if all filters are autofit - autoFit = autoFit || filter.autoFit; - - legacy = legacy || filter.legacy; - } - - filterStack.push(state); - - state.resolution = resolution; - - state.legacy = legacy; - - // round to whole number based on resolution - // TODO move that to the shader too? - state.sourceFrame = target.filterArea || target.getBounds(true); - - state.sourceFrame.pad(padding); - - if (autoFit) - { - state.sourceFrame.fit(this.renderer.renderTexture.destinationFrame); - } - - state.sourceFrame.round(resolution); - - state.renderTexture = this.getPotFilterTexture(state.sourceFrame.width, state.sourceFrame.height, resolution); - state.filters = filters; - - state.destinationFrame.width = state.renderTexture.width; - state.destinationFrame.height = state.renderTexture.height; - - state.renderTexture.filterFrame = state.sourceFrame; - renderer.renderTexture.bind(state.renderTexture, state.sourceFrame);// /, state.destinationFrame); - renderer.renderTexture.clear(); - } - - /** - * Pops off the filter and applies it. - * - */ - pop() - { - const renderer = this.renderer; - const filterStack = renderer.renderTexture.defaultFilterStack; - const state = filterStack.pop(); - const filters = state.filters; - - this.activeState = state; - - const globalUniforms = this.globalUniforms.uniforms; - - globalUniforms.sourceFrame = state.sourceFrame; - globalUniforms.destinationFrame = state.destinationFrame; - globalUniforms.resolution = state.resolution; - - // only update the rect if its legacy.. - if (state.legacy) - { - const filterArea = globalUniforms.filterArea; - const filterClamp = globalUniforms.filterClamp; - - filterArea[0] = state.destinationFrame.width; - filterArea[1] = state.destinationFrame.height; - filterArea[2] = state.sourceFrame.x; - filterArea[3] = state.sourceFrame.y; - - filterClamp[0] = 0.5 / state.resolution / state.destinationFrame.width; - filterClamp[1] = 0.5 / state.resolution / state.destinationFrame.height; - filterClamp[2] = (state.sourceFrame.width - 0.5) / state.resolution / state.destinationFrame.width; - filterClamp[3] = (state.sourceFrame.height - 0.5) / state.resolution / state.destinationFrame.height; - } - - this.globalUniforms.update(); - - const lastState = filterStack[filterStack.length - 1]; - - if (filters.length === 1) - { - filters[0].apply(this, state.renderTexture, lastState.renderTexture, false, state); - - this.returnFilterTexture(state.renderTexture); - } - else - { - let flip = state.renderTexture; - let flop = this.getPotFilterTexture( - flip.width, - flip.height, - state.resolution - ); - - flop.filterFrame = flip.filterFrame; - - let i = 0; - - for (i = 0; i < filters.length - 1; ++i) - { - filters[i].apply(this, flip, flop, true, state); - - const t = flip; - - flip = flop; - flop = t; - } - - filters[i].apply(this, flip, lastState.renderTexture, false, state); - - this.returnFilterTexture(flip); - this.returnFilterTexture(flop); - } - - this.statePool.push(state); - } - - /** - * Draws a filter. - * - * @param {PIXI.Filter} filter - The filter to draw. - * @param {PIXI.RenderTarget} input - The input render target. - * @param {PIXI.RenderTarget} output - The target to output to. - * @param {boolean} clear - Should the output be cleared before rendering to it - */ - applyFilter(filter, input, output, clear) - { - const renderer = this.renderer; - - renderer.renderTexture.bind(output, output ? output.filterFrame : null); - - if (clear) - { - // gl.disable(gl.SCISSOR_TEST); - renderer.renderTexture.clear(); - // gl.enable(gl.SCISSOR_TEST); - } - - // set the uniforms.. - filter.uniforms.uSampler = input; - filter.uniforms.filterGlobals = this.globalUniforms; - - // TODO make it so that the order of this does not matter.. - // because it does at the moment cos of global uniforms. - // they need to get resynced - - renderer.state.setState(filter.state); - renderer.shader.bind(filter); - - if (filter.legacy) - { - this.quadUv.map(input._frame, input.filterFrame); - - renderer.geometry.bind(this.quadUv); - renderer.geometry.draw(DRAW_MODES.TRIANGLES); - } - else - { - renderer.geometry.bind(this.quad); - renderer.geometry.draw(DRAW_MODES.TRIANGLE_STRIP); - } - } - - /** - * Calculates the mapped matrix. - * - * TODO playing around here.. this is temporary - (will end up in the shader) - * this returns a matrix that will normalise map filter cords in the filter to screen space - * - * @param {PIXI.Matrix} outputMatrix - the matrix to output to. - * @return {PIXI.Matrix} The mapped matrix. - */ - calculateScreenSpaceMatrix(outputMatrix) - { - const currentState = this.activeState; - - return filterTransforms.calculateScreenSpaceMatrix( - outputMatrix, - currentState.sourceFrame, - currentState.destinationFrame - ); - } - - /** - * This will map the filter coord so that a texture can be used based on the transform of a sprite - * - * @param {PIXI.Matrix} outputMatrix - The matrix to output to. - * @param {PIXI.Sprite} sprite - The sprite to map to. - * @return {PIXI.Matrix} The mapped matrix. - */ - calculateSpriteMatrix(outputMatrix, sprite) - { - const currentState = this.activeState; - - return filterTransforms.calculateSpriteMatrix( - outputMatrix, - currentState.sourceFrame, - currentState.destinationFrame, - sprite - ); - } - - /** - * Destroys this Filter System. - * - * @param {boolean} [contextLost=false] context was lost, do not free shaders - * - */ - destroy(contextLost = false) - { - if (!contextLost) - { - this.emptyPool(); - } - else - { - this.texturePool = {}; - } - } - - /** - * Gets a Power-of-Two render texture. - * - * TODO move to a seperate class could be on renderer? - * also - could cause issue with multiple contexts? - * - * @private - * @param {WebGLRenderingContext} gl - The webgl rendering context - * @param {number} minWidth - The minimum width of the render target. - * @param {number} minHeight - The minimum height of the render target. - * @param {number} resolution - The resolution of the render target. - * @return {PIXI.RenderTarget} The new render target. - */ - getPotFilterTexture(minWidth, minHeight, resolution) - { - minWidth = bitTwiddle.nextPow2(minWidth); - minHeight = bitTwiddle.nextPow2(minHeight); - resolution = resolution || 1; - - const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); - - if (!this.texturePool[key]) - { - this.texturePool[key] = []; - } - - let renderTexture = this.texturePool[key].pop(); - - if (!renderTexture) - { - // temporary bypass cache.. - // internally - this will cause a texture to be bound.. - renderTexture = RenderTexture.create({ - width: minWidth, - height: minHeight, - resolution, - }); - } - - return renderTexture; - } - - /** - * Gets extra render texture to use inside current filter - * - * @param {number} resolution resolution of the renderTexture - * @returns {PIXI.RenderTexture} - */ - getFilterTexture(resolution) - { - const rt = this.activeState.renderTexture; - - const filterTexture = this.getPotFilterTexture(rt.width, rt.height, resolution || rt.baseTexture.resolution); - - filterTexture.filterFrame = rt.filterFrame; - - return filterTexture; - } - - /** - * Frees a render texture back into the pool. - * - * @param {PIXI.RenderTarget} renderTexture - The renderTarget to free - */ - returnFilterTexture(renderTexture) - { - renderTexture.filterFrame = null; - - const base = renderTexture.baseTexture; - - const minWidth = base.width; - const minHeight = base.height; - - const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); - - this.texturePool[key].push(renderTexture); - } - - /** - * Empties the texture pool. - * - */ - emptyPool() - { - for (const i in this.texturePool) - { - const textures = this.texturePool[i]; - - if (textures) - { - for (let j = 0; j < textures.length; j++) - { - textures[j].destroy(true); - } - } - } - - this.texturePool = {}; - } -} diff --git a/packages/core/src/renderers/systems/geometry/GLBuffer.js b/packages/core/src/renderers/systems/geometry/GLBuffer.js deleted file mode 100644 index 0477e8a..0000000 --- a/packages/core/src/renderers/systems/geometry/GLBuffer.js +++ /dev/null @@ -1,9 +0,0 @@ -export default class GLBuffer -{ - constructor(buffer) - { - this.buffer = buffer; - this.updateID = -1; - this.byteLength = -1; - } -} diff --git a/packages/core/src/renderers/systems/geometry/GeometrySystem.js b/packages/core/src/renderers/systems/geometry/GeometrySystem.js deleted file mode 100644 index 26c8e9e..0000000 --- a/packages/core/src/renderers/systems/geometry/GeometrySystem.js +++ /dev/null @@ -1,388 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; -import GLBuffer from './GLBuffer'; - -const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class GeometrySystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this._activeGeometry = null; - this._activeVao = null; - - this.hasVao = true; - this.hasInstance = true; - } - - /** - * Sets up the renderer context and necessary buffers. - * - * @private - */ - contextChange() - { - const gl = this.gl = this.renderer.gl; - - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - - // webgl2 - if (!gl.createVertexArray) - { - // webgl 1! - let nativeVaoExtension = this.renderer.context.extensions.vertexArrayObject; - - if (this.renderer.legacy) - { - nativeVaoExtension = null; - } - - if (nativeVaoExtension) - { - gl.createVertexArray = () => - nativeVaoExtension.createVertexArrayOES(); - - gl.bindVertexArray = (vao) => - nativeVaoExtension.bindVertexArrayOES(vao); - - gl.deleteVertexArray = (vao) => - nativeVaoExtension.deleteVertexArrayOES(vao); - } - else - { - this.hasVao = false; - gl.createVertexArray = () => - { - // empty - }; - - gl.bindVertexArray = () => - { - // empty - }; - - gl.deleteVertexArray = () => - { - // empty - }; - } - } - - if (!gl.vertexAttribDivisor) - { - const instanceExt = gl.getExtension('ANGLE_instanced_arrays'); - - if (instanceExt) - { - gl.vertexAttribDivisor = (a, b) => - instanceExt.vertexAttribDivisorANGLE(a, b); - - gl.drawElementsInstanced = (a, b, c, d, e) => - instanceExt.drawElementsInstancedANGLE(a, b, c, d, e); - - gl.drawArraysInstanced = (a, b, c, d) => - instanceExt.drawArraysInstancedANGLE(a, b, c, d); - } - else - { - this.hasInstance = false; - } - } - } - - /** - * Binds geometry so that is can be drawn. Creating a Vao if required - * @private - * @param {PIXI.Geometry} geometry instance of geometry to bind - */ - bind(geometry, shader) - { - shader = shader || this.renderer.shader.shader; - - const gl = this.gl; - - // not sure the best way to address this.. - // currently different shaders require different VAOs for the same geometry - // Still mulling over the best way to solve this one.. - // will likely need to modify the shader attribute locations at run time! - let vaos = geometry.glVertexArrayObjects[this.CONTEXT_UID]; - - if (!vaos) - { - geometry.glVertexArrayObjects[this.CONTEXT_UID] = vaos = {}; - } - - const vao = vaos[shader.program.id] || this.initGeometryVao(geometry, shader.program); - - this._activeGeometry = geometry; - - if (this._activeVao !== vao) - { - this._activeVao = vao; - - if (this.hasVao) - { - gl.bindVertexArray(vao); - } - else - { - this.activateVao(geometry, shader.program); - } - } - - // TODO - optimise later! - // don't need to loop through if nothing changed! - // maybe look to add an 'autoupdate' to geometry? - this.updateBuffers(); - } - - reset() - { - this.unbind(); - } - - updateBuffers() - { - const geometry = this._activeGeometry; - const gl = this.gl; - - for (let i = 0; i < geometry.buffers.length; i++) - { - const buffer = geometry.buffers[i]; - - const glBuffer = buffer._glBuffers[this.CONTEXT_UID]; - - if (buffer._updateID !== glBuffer.updateID) - { - glBuffer.updateID = buffer._updateID; - - // TODO can cache this on buffer! maybe added a getter / setter? - const type = buffer.index ? gl.ELEMENT_ARRAY_BUFFER : gl.ARRAY_BUFFER; - const drawType = buffer.static ? gl.STATIC_DRAW : gl.DYNAMIC_DRAW; - - gl.bindBuffer(type, glBuffer.buffer); - - if (glBuffer.byteLength >= buffer.data.byteLength) - { - // offset is always zero for now! - gl.bufferSubData(type, 0, buffer.data); - } - else - { - gl.bufferData(type, buffer.data, drawType); - } - } - } - } - - checkCompatability(geometry, program) - { - // geometry must have at least all the attributes that the shader requires. - const geometryAttributes = geometry.attributes; - const shaderAttributes = program.attributeData; - - for (const j in shaderAttributes) - { - if (!geometryAttributes[j]) - { - throw new Error(`shader and geometry incompatible, geometry missing the "${j}" attribute`); - } - } - } - - /** - * Creates a Vao with the same structure as the geometry and stores it on the geometry. - * @private - * @param {PIXI.Geometry} geometry instance of geometry to to generate Vao for - */ - initGeometryVao(geometry, program) - { - this.checkCompatability(geometry, program); - - const gl = this.gl; - const CONTEXT_UID = this.CONTEXT_UID; - const buffers = geometry.buffers; - const attributes = geometry.attributes; - - const tempStride = {}; - const tempStart = {}; - - for (const j in buffers) - { - tempStride[j] = 0; - tempStart[j] = 0; - } - - for (const j in attributes) - { - if (!attributes[j].size && program.attributeData[j]) - { - attributes[j].size = program.attributeData[j].size; - } - - tempStride[attributes[j].buffer] += attributes[j].size * byteSizeMap[attributes[j].type]; - } - - for (const j in attributes) - { - const attribute = attributes[j]; - const attribSize = attribute.size; - - if (attribute.stride === undefined) - { - if (tempStride[attribute.buffer] === attribSize * byteSizeMap[attribute.type]) - { - attribute.stride = 0; - } - else - { - attribute.stride = tempStride[attribute.buffer]; - } - } - - if (attribute.start === undefined) - { - attribute.start = tempStart[attribute.buffer]; - - tempStart[attribute.buffer] += attribSize * byteSizeMap[attribute.type]; - } - } - - // first update - and create the buffers! - // only create a gl buffer if it actually gets - for (let i = 0; i < buffers.length; i++) - { - const buffer = buffers[i]; - - if (!buffer._glBuffers[CONTEXT_UID]) - { - buffer._glBuffers[CONTEXT_UID] = new GLBuffer(gl.createBuffer()); - } - } - - // TODO - maybe make this a data object? - // lets wait to see if we need to first! - const vao = gl.createVertexArray(); - - gl.bindVertexArray(vao); - - this.activateVao(geometry, program); - - gl.bindVertexArray(null); - - // add it to the cache! - geometry.glVertexArrayObjects[this.CONTEXT_UID][program.id] = vao; - - return vao; - } - - activateVao(geometry, program) - { - const gl = this.gl; - const CONTEXT_UID = this.CONTEXT_UID; - const buffers = geometry.buffers; - const attributes = geometry.attributes; - - if (geometry.indexBuffer) - { - // first update the index buffer if we have one.. - gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, geometry.indexBuffer._glBuffers[CONTEXT_UID].buffer); - } - - let lastBuffer = null; - - // add a new one! - for (const j in attributes) - { - const attribute = attributes[j]; - const buffer = buffers[attribute.buffer]; - const glBuffer = buffer._glBuffers[CONTEXT_UID]; - - if (program.attributeData[j]) - { - if (lastBuffer !== glBuffer) - { - gl.bindBuffer(gl.ARRAY_BUFFER, glBuffer.buffer); - - lastBuffer = glBuffer; - } - - const location = program.attributeData[j].location; - - // TODO introduce state again - // we can optimise this for older devices that have no VAOs - gl.enableVertexAttribArray(location); - - gl.vertexAttribPointer(location, - attribute.size, - attribute.type || gl.FLOAT, - attribute.normalized, - attribute.stride, - attribute.start); - - if (attribute.instance) - { - // TODO calculate instance count based of this... - if (this.hasInstance) - { - gl.vertexAttribDivisor(location, 1); - } - else - { - throw new Error('geometry error, GPU Instancing is not supported on this device'); - } - } - } - } - } - - draw(type, size, start, instanceCount) - { - const gl = this.gl; - const geometry = this._activeGeometry; - - // TODO.. this should not change so maybe cache the function? - - if (geometry.indexBuffer) - { - if (geometry.instanced) - { - /* eslint-disable max-len */ - gl.drawElementsInstanced(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2, instanceCount || 1); - /* eslint-enable max-len */ - } - else - { - gl.drawElements(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2); - } - } - else - if (geometry.instanced) - { - // TODO need a better way to calculate size.. - gl.drawArraysInstanced(type, start, size || geometry.getSize(), instanceCount || 1); - } - else - { - gl.drawArrays(type, start, size || geometry.getSize()); - } - - return this; - } - - unbind() - { - this.gl.bindVertexArray(null); - this._activeVao = null; - this._activeGeometry = null; - } -} diff --git a/packages/core/src/renderers/systems/geometry/setVertexAttribArrays.js b/packages/core/src/renderers/systems/geometry/setVertexAttribArrays.js deleted file mode 100644 index 23b6959..0000000 --- a/packages/core/src/renderers/systems/geometry/setVertexAttribArrays.js +++ /dev/null @@ -1,54 +0,0 @@ -// var GL_MAP = {}; - -/** - * @param gl {WebGLRenderingContext} The current WebGL context - * @param attribs {*} - * @param state {*} - */ -export default function setVertexAttribArrays(gl, attribs, state) -{ - let i; - - if (state) - { - const tempAttribState = state.tempAttribState; - const attribState = state.attribState; - - for (i = 0; i < tempAttribState.length; i++) - { - tempAttribState[i] = false; - } - - // set the new attribs - for (i = 0; i < attribs.length; i++) - { - tempAttribState[attribs[i].attribute.location] = true; - } - - for (i = 0; i < attribState.length; i++) - { - if (attribState[i] !== tempAttribState[i]) - { - attribState[i] = tempAttribState[i]; - - if (state.attribState[i]) - { - gl.enableVertexAttribArray(i); - } - else - { - gl.disableVertexAttribArray(i); - } - } - } - } - else - { - for (i = 0; i < attribs.length; i++) - { - const attrib = attribs[i]; - - gl.enableVertexAttribArray(attrib.attribute.location); - } - } -} diff --git a/packages/core/src/renderers/systems/index.js b/packages/core/src/renderers/systems/index.js deleted file mode 100644 index c843f03..0000000 --- a/packages/core/src/renderers/systems/index.js +++ /dev/null @@ -1,6 +0,0 @@ -/** - * Systems are individual components to the Renderer pipeline. - * @namespace PIXI.systems - */ -export { default as WebGLSystem } from './WebGLSystem'; -export { default as FilterSystem } from './filter/FilterSystem'; diff --git a/packages/core/src/renderers/systems/shader/GLProgram.js b/packages/core/src/renderers/systems/shader/GLProgram.js deleted file mode 100644 index 92980ca..0000000 --- a/packages/core/src/renderers/systems/shader/GLProgram.js +++ /dev/null @@ -1,43 +0,0 @@ -/** - * Helper class to create a webGL Program - * - * @class - * @private - */ -export default class GLProgram -{ - constructor(program, uniformData) - { - /** - * The shader program - * - * @member {WebGLProgram} - */ - this.program = program; - - /** - * holds the uniform data which contains uniform locations - * and current uniform values used for caching and preventing undeed GPU commands - * @type {Object} - */ - this.uniformData = uniformData; - - /** - * uniformGroups holds the various upload functions for the shader. Each uniform group - * and program have a unique upload function generated. - * @type {Object} - */ - this.uniformGroups = {}; - } - - /** - * Destroys this program - * TODO - */ - destroy() - { - this.uniformData = null; - this.uniformGroups = null; - this.program = null; - } -} diff --git a/packages/core/src/renderers/systems/shader/ShaderSystem.js b/packages/core/src/renderers/systems/shader/ShaderSystem.js deleted file mode 100644 index 6cf57b6..0000000 --- a/packages/core/src/renderers/systems/shader/ShaderSystem.js +++ /dev/null @@ -1,170 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; -import GLProgram from './GLProgram'; -import generateUniformsSync from '../../../shader/generateUniformsSync'; -import defaultValue from '../../../shader/defaultValue'; -import compileProgram from './shader/compileProgram'; - -let UID = 0; - -/** - * Helper class to create a webGL Texture - * - * @class - * @memberof PIXI - */ -export default class ShaderSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - A reference to the current renderer - */ - constructor(renderer) - { - super(renderer); - - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = null; - - this.shader = null; - this.program = null; - - this.id = UID++; - } - - contextChange(gl) - { - this.gl = gl; - } - - /** - * Changes the current shader to the one given in parameter - * - * @param {PIXI.Shader} shader - the new shader - * @param {boolean} dontSync - false if the shader should automatically sync its uniforms. - * @returns {PIXI.glCore.glProgram} the glProgram that belongs to the shader. - */ - bind(shader, dontSync) - { - shader.uniforms.globals = this.renderer.globalUniforms; - - const program = shader.program; - const glProgram = program.glPrograms[this.renderer.CONTEXT_UID] || this.generateShader(shader); - - this.shader = shader; - - // TODO - some current pixi plugins bypass this.. so it not safe to use yet.. - if (this.program !== program) - { - this.program = program; - this.gl.useProgram(glProgram.program); - } - - if (!dontSync) - { - this.syncUniformGroup(shader.uniformGroup); - } - - return glProgram; - } - - /** - * Uploads the uniforms values to the currently bound shader. - * - * @param {object} uniforms - the uniforms valiues that be applied to the current shader - */ - setUniforms(uniforms) - { - const shader = this.shader.program; - const glProgram = shader.glPrograms[this.renderer.CONTEXT_UID]; - - shader.syncUniforms(glProgram.uniformData, uniforms, this.renderer); - } - - syncUniformGroup(group) - { - const glProgram = this.getglProgram(); - - if (!group.static || group.dirtyId !== glProgram.uniformGroups[group.id]) - { - glProgram.uniformGroups[group.id] = group.dirtyId; - const syncFunc = group.syncUniforms[this.shader.program.id] || this.createSyncGroups(group); - - syncFunc(glProgram.uniformData, group.uniforms, this.renderer); - } - } - - createSyncGroups(group) - { - group.syncUniforms[this.shader.program.id] = generateUniformsSync(group, this.shader.program.uniformData); - - return group.syncUniforms[this.shader.program.id]; - } - - /** - * Returns the underlying GLShade rof the currently bound shader. - * This can be handy for when you to have a little more control over the setting of your uniforms. - * - * @return {PIXI.glCore.Shader} the glProgram for the currently bound Shader for this context - */ - getglProgram() - { - if (this.shader) - { - return this.shader.program.glPrograms[this.renderer.CONTEXT_UID]; - } - - return null; - } - - /** - * Generates a glProgram verion of the Shader provided. - * - * @private - * @param {PIXI.Shader} shader the shader that the glProgram will be based on. - * @return {PIXI.glCore.glProgram} A shiney new glProgram - */ - generateShader(shader) - { - const gl = this.gl; - - const program = shader.program; - - const attribMap = {}; - - for (const i in program.attributeData) - { - attribMap[i] = program.attributeData[i].location; - } - - const shaderProgram = compileProgram(gl, program.vertexSrc, program.fragmentSrc, attribMap); - const uniformData = {}; - - for (const i in program.uniformData) - { - const data = program.uniformData[i]; - - uniformData[i] = { - location: gl.getUniformLocation(shaderProgram, i), - value: defaultValue(data.type, data.size), - }; - } - - const glProgram = new GLProgram(shaderProgram, uniformData); - - program.glPrograms[this.renderer.CONTEXT_UID] = glProgram; - - return glProgram; - } - - /** - * Destroys this System and removes all its textures - */ - destroy() - { - // TODO implement destroy method for ShaderSystem - this.destroyed = true; - } -} diff --git a/packages/core/src/renderers/systems/shader/shader/compileProgram.js b/packages/core/src/renderers/systems/shader/shader/compileProgram.js deleted file mode 100644 index 8c6ccb9..0000000 --- a/packages/core/src/renderers/systems/shader/shader/compileProgram.js +++ /dev/null @@ -1,78 +0,0 @@ -/** - * @method compileProgram - * @memberof PIXI.glCore.shader - * @param gl {WebGLRenderingContext} The current WebGL context {WebGLProgram} - * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. - * @param fragmentSrc {string|string[]} The fragment shader source as an array of strings. - * @param attributeLocations {Object} An attribute location map that lets you manually set the attribute locations - * @return {WebGLProgram} the shader program - */ -export default function compileProgram(gl, vertexSrc, fragmentSrc, attributeLocations) -{ - const glVertShader = compileShader(gl, gl.VERTEX_SHADER, vertexSrc); - const glFragShader = compileShader(gl, gl.FRAGMENT_SHADER, fragmentSrc); - - let program = gl.createProgram(); - - gl.attachShader(program, glVertShader); - gl.attachShader(program, glFragShader); - - // optionally, set the attributes manually for the program rather than letting WebGL decide.. - if (attributeLocations) - { - for (const i in attributeLocations) - { - gl.bindAttribLocation(program, attributeLocations[i], i); - } - } - - gl.linkProgram(program); - - // if linking fails, then log and cleanup - if (!gl.getProgramParameter(program, gl.LINK_STATUS)) - { - console.error('Pixi.js Error: Could not initialize shader.'); - console.error('gl.VALIDATE_STATUS', gl.getProgramParameter(program, gl.VALIDATE_STATUS)); - console.error('gl.getError()', gl.getError()); - - // if there is a program info log, log it - if (gl.getProgramInfoLog(program) !== '') - { - console.warn('Pixi.js Warning: gl.getProgramInfoLog()', gl.getProgramInfoLog(program)); - } - - gl.deleteProgram(program); - program = null; - } - - // clean up some shaders - gl.deleteShader(glVertShader); - gl.deleteShader(glFragShader); - - return program; -} - -/** - * @private - * @param gl {WebGLRenderingContext} The current WebGL context {WebGLProgram} - * @param type {Number} the type, can be either VERTEX_SHADER or FRAGMENT_SHADER - * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. - * @return {WebGLShader} the shader - */ -function compileShader(gl, type, src) -{ - const shader = gl.createShader(type); - - gl.shaderSource(shader, src); - gl.compileShader(shader); - - if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) - { - console.warn(src); - console.error(gl.getShaderInfoLog(shader)); - - return null; - } - - return shader; -} diff --git a/packages/core/src/renderers/systems/shader/shader/defaultValue.js b/packages/core/src/renderers/systems/shader/shader/defaultValue.js deleted file mode 100644 index 97290bd..0000000 --- a/packages/core/src/renderers/systems/shader/shader/defaultValue.js +++ /dev/null @@ -1,79 +0,0 @@ -/** - * @method defaultValue - * @memberof PIXI.glCore.shader - * @param type {String} Type of value - * @param size {Number} - */ -export default function defaultValue(type, size) -{ - switch (type) - { - case 'float': - return 0; - - case 'vec2': - return new Float32Array(2 * size); - - case 'vec3': - return new Float32Array(3 * size); - - case 'vec4': - return new Float32Array(4 * size); - - case 'int': - case 'sampler2D': - case 'sampler2DArray': - return 0; - - case 'ivec2': - return new Int32Array(2 * size); - - case 'ivec3': - return new Int32Array(3 * size); - - case 'ivec4': - return new Int32Array(4 * size); - - case 'bool': - return false; - - case 'bvec2': - - return booleanArray(2 * size); - - case 'bvec3': - return booleanArray(3 * size); - - case 'bvec4': - return booleanArray(4 * size); - - case 'mat2': - return new Float32Array([1, 0, - 0, 1]); - - case 'mat3': - return new Float32Array([1, 0, 0, - 0, 1, 0, - 0, 0, 1]); - - case 'mat4': - return new Float32Array([1, 0, 0, 0, - 0, 1, 0, 0, - 0, 0, 1, 0, - 0, 0, 0, 1]); - } - - return null; -} - -function booleanArray(size) -{ - const array = new Array(size); - - for (let i = 0; i < array.length; i++) - { - array[i] = false; - } - - return array; -} diff --git a/packages/core/src/renderers/systems/shader/shader/index.js b/packages/core/src/renderers/systems/shader/shader/index.js deleted file mode 100644 index ba7a317..0000000 --- a/packages/core/src/renderers/systems/shader/shader/index.js +++ /dev/null @@ -1,8 +0,0 @@ -/** - * @namespace PIXI.glCore.shader - */ -export { default as compileProgram } from './compileProgram'; -export { default as defaultValue } from './defaultValue'; -export { default as setPrecision } from './setPrecision'; -export { default as mapSize } from './mapSize'; -export { default as mapType } from './mapType'; diff --git a/packages/core/src/renderers/systems/shader/shader/mapSize.js b/packages/core/src/renderers/systems/shader/shader/mapSize.js deleted file mode 100644 index b681554..0000000 --- a/packages/core/src/renderers/systems/shader/shader/mapSize.js +++ /dev/null @@ -1,33 +0,0 @@ -const GLSL_TO_SIZE = { - float: 1, - vec2: 2, - vec3: 3, - vec4: 4, - - int: 1, - ivec2: 2, - ivec3: 3, - ivec4: 4, - - bool: 1, - bvec2: 2, - bvec3: 3, - bvec4: 4, - - mat2: 4, - mat3: 9, - mat4: 16, - - sampler2D: 1, -}; - -/** - * @method mapSize - * @memberof PIXI.glCore.shader - * @param type {String} - * @return {Number} - */ -export default function mapSize(type) -{ - return GLSL_TO_SIZE[type]; -} diff --git a/packages/core/src/renderers/systems/shader/shader/mapType.js b/packages/core/src/renderers/systems/shader/shader/mapType.js deleted file mode 100644 index d288b7c..0000000 --- a/packages/core/src/renderers/systems/shader/shader/mapType.js +++ /dev/null @@ -1,45 +0,0 @@ -let GL_TABLE = null; - -const GL_TO_GLSL_TYPES = { - FLOAT: 'float', - FLOAT_VEC2: 'vec2', - FLOAT_VEC3: 'vec3', - FLOAT_VEC4: 'vec4', - - INT: 'int', - INT_VEC2: 'ivec2', - INT_VEC3: 'ivec3', - INT_VEC4: 'ivec4', - - BOOL: 'bool', - BOOL_VEC2: 'bvec2', - BOOL_VEC3: 'bvec3', - BOOL_VEC4: 'bvec4', - - FLOAT_MAT2: 'mat2', - FLOAT_MAT3: 'mat3', - FLOAT_MAT4: 'mat4', - - SAMPLER_2D: 'sampler2D', - SAMPLER_CUBE: 'samplerCube', - SAMPLER_2D_ARRAY: 'sampler2DArray', -}; - -export default function mapType(gl, type) -{ - if (!GL_TABLE) - { - const typeNames = Object.keys(GL_TO_GLSL_TYPES); - - GL_TABLE = {}; - - for (let i = 0; i < typeNames.length; ++i) - { - const tn = typeNames[i]; - - GL_TABLE[gl[tn]] = GL_TO_GLSL_TYPES[tn]; - } - } - - return GL_TABLE[type]; -} diff --git a/packages/core/src/renderers/systems/shader/shader/setPrecision.js b/packages/core/src/renderers/systems/shader/shader/setPrecision.js deleted file mode 100644 index e2c1f34..0000000 --- a/packages/core/src/renderers/systems/shader/shader/setPrecision.js +++ /dev/null @@ -1,18 +0,0 @@ -/** - * Sets the float precision on the shader. If the precision is already present this function will do nothing - * @param {string} src the shader source - * @param {string} precision The float precision of the shader. Options are 'lowp', 'mediump' or 'highp'. - * - * @return {string} modified shader source - */ -export default function setPrecision(src, precision) -{ - src = src.trim(); - - if (src.substring(0, 9) !== 'precision' && src.substring(0, 1) !== '#') - { - return `precision ${precision} float;\n${src}`; - } - - return src; -} diff --git a/packages/core/src/renderers/systems/textures/GLTexture.js b/packages/core/src/renderers/systems/textures/GLTexture.js deleted file mode 100644 index c3fb8dc..0000000 --- a/packages/core/src/renderers/systems/textures/GLTexture.js +++ /dev/null @@ -1,33 +0,0 @@ -export default class GLTexture -{ - constructor(texture) - { - /** - * The WebGL texture - * - * @member {WebGLTexture} - */ - this.texture = texture; - - this.width = -1; - this.height = -1; - - /** - * Texture contents dirty flag - * @member {number} - */ - this.dirtyId = -1; - - /** - * Texture style dirty flag - * @type {number} - */ - this.dirtyStyleId = -1; - - /** - * Whether mip levels has to be generated - * @type {boolean} - */ - this.mipmap = false; - } -} diff --git a/packages/core/src/renderers/systems/textures/TextureGCSystem.js b/packages/core/src/renderers/systems/textures/TextureGCSystem.js deleted file mode 100644 index b089938..0000000 --- a/packages/core/src/renderers/systems/textures/TextureGCSystem.js +++ /dev/null @@ -1,110 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; -import { GC_MODES } from '@pixi/constants'; -import { settings } from '@pixi/settings'; - -/** - * TextureGarbageCollector. This class manages the GPU and ensures that it does not get clogged - * up with textures that are no longer being used. - * - * @class - * @memberof PIXI - */ -export default class TextureGCSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this.count = 0; - this.checkCount = 0; - this.maxIdle = settings.GC_MAX_IDLE; - this.checkCountMax = settings.GC_MAX_CHECK_COUNT; - this.mode = settings.GC_MODE; - } - - /** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ - postrender() - { - this.count++; - - if (this.mode === GC_MODES.MANUAL) - { - return; - } - - this.checkCount++; - - if (this.checkCount > this.checkCountMax) - { - this.checkCount = 0; - - this.run(); - } - } - - /** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ - run() - { - const tm = this.renderer.texture; - const managedTextures = tm.managedTextures; - let wasRemoved = false; - - for (let i = 0; i < managedTextures.length; i++) - { - const texture = managedTextures[i]; - - // only supports non generated textures at the moment! - if (!texture.frameBuffer && this.count - texture.touched > this.maxIdle) - { - tm.destroyTexture(texture, true); - managedTextures[i] = null; - wasRemoved = true; - } - } - - if (wasRemoved) - { - let j = 0; - - for (let i = 0; i < managedTextures.length; i++) - { - if (managedTextures[i] !== null) - { - managedTextures[j++] = managedTextures[i]; - } - } - - managedTextures.length = j; - } - } - - /** - * Removes all the textures within the specified displayObject and its children from the GPU - * - * @param {PIXI.DisplayObject} displayObject - the displayObject to remove the textures from. - */ - unload(displayObject) - { - const tm = this.renderer.textureSystem; - - // only destroy non generated textures - if (displayObject._texture && displayObject._texture._glRenderTargets) - { - tm.destroyTexture(displayObject._texture); - } - - for (let i = displayObject.children.length - 1; i >= 0; i--) - { - this.unload(displayObject.children[i]); - } - } -} diff --git a/packages/core/src/renderers/systems/textures/TextureSystem.js b/packages/core/src/renderers/systems/textures/TextureSystem.js deleted file mode 100644 index ccc5de9..0000000 --- a/packages/core/src/renderers/systems/textures/TextureSystem.js +++ /dev/null @@ -1,281 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; -import GLTexture from './GLTexture'; -import { removeItems } from '@pixi/utils'; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class TextureSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - // TODO set to max textures... - this.boundTextures = [ - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - ]; - - this.currentLocation = -1; - - this.managedTextures = []; - } - - /** - * Sets up the renderer context and necessary buffers. - * - * @private - */ - contextChange() - { - const gl = this.gl = this.renderer.gl; - - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - - // TODO move this.. to a nice make empty textures class.. - this.emptyTextures = {}; - - const emptyTexture2D = new GLTexture(gl.createTexture()); - - gl.bindTexture(gl.TEXTURE_2D, emptyTexture2D.texture); - gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array(4)); - - this.emptyTextures[gl.TEXTURE_2D] = emptyTexture2D; - this.emptyTextures[gl.TEXTURE_CUBE_MAP] = new GLTexture(gl.createTexture()); - - gl.bindTexture(gl.TEXTURE_CUBE_MAP, this.emptyTextures[gl.TEXTURE_CUBE_MAP].texture); - - let i; - - for (i = 0; i < 6; i++) - { - gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, null); - } - - gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_MAG_FILTER, gl.LINEAR); - gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_MIN_FILTER, gl.LINEAR); - - for (i = 0; i < this.boundTextures.length; i++) - { - this.bind(null, i); - } - } - - bind(texture, location) - { - const gl = this.gl; - - location = location || 0; - - if (this.currentLocation !== location) - { - this.currentLocation = location; - gl.activeTexture(gl.TEXTURE0 + location); - } - - if (texture) - { - texture = texture.baseTexture || texture; - - if (texture.valid) - { - texture.touched = this.renderer.textureGC.count; - - const glTexture = texture._glTextures[this.CONTEXT_UID] || this.initTexture(texture); - - gl.bindTexture(texture.target, glTexture.texture); - - if (glTexture.dirtyId !== texture.dirtyId) - { - this.updateTexture(texture); - } - - this.boundTextures[location] = texture; - } - } - else - { - gl.bindTexture(gl.TEXTURE_2D, this.emptyTextures[gl.TEXTURE_2D].texture); - this.boundTextures[location] = null; - } - } - - unbind(texture) - { - const gl = this.gl; - - for (let i = 0; i < this.boundTextures.length; i++) - { - if (this.boundTextures[i] === texture) - { - if (this.currentLocation !== i) - { - gl.activeTexture(gl.TEXTURE0 + i); - this.currentLocation = i; - } - - gl.bindTexture(gl.TEXTURE_2D, this.emptyTextures[texture.target].texture); - this.boundTextures[i] = null; - } - } - } - - initTexture(texture) - { - const glTexture = new GLTexture(this.gl.createTexture()); - - // guarentee an update.. - glTexture.dirtyId = -1; - - texture._glTextures[this.CONTEXT_UID] = glTexture; - - this.managedTextures.push(texture); - texture.on('dispose', this.destroyTexture, this); - - return glTexture; - } - - updateTexture(texture) - { - const glTexture = texture._glTextures[this.CONTEXT_UID]; - const renderer = this.renderer; - - if (texture.resource && texture.resource.upload(renderer, texture, glTexture)) - { - // texture is uploaded, dont do anything! - } - else - { - // default, renderTexture-like logic - const width = texture.realWidth; - const height = texture.realHeight; - const gl = renderer.gl; - - if (glTexture.width !== width - || glTexture.height !== height - || glTexture.dirtyId < 0) - { - glTexture.width = width; - glTexture.height = height; - - gl.texImage2D(texture.target, 0, - texture.format, - width, - height, - 0, - texture.format, - texture.type, - null); - } - } - - // lets only update what changes.. - if (texture.dirtyStyleId !== glTexture.dirtyStyleId) - { - this.updateTextureStyle(texture); - } - glTexture.dirtyId = texture.dirtyId; - } - - /** - * Deletes the texture from WebGL - * - * @param {PIXI.BaseTexture|PIXI.Texture} texture - the texture to destroy - * @param {boolean} [skipRemove=false] - Whether to skip removing the texture from the TextureManager. - */ - destroyTexture(texture, skipRemove) - { - const gl = this.gl; - - texture = texture.baseTexture || texture; - - if (texture._glTextures[this.renderer.CONTEXT_UID]) - { - this.unbind(texture); - - gl.deleteTexture(texture._glTextures[this.renderer.CONTEXT_UID].texture); - texture.off('dispose', this.destroyTexture, this); - - delete texture._glTextures[this.renderer.CONTEXT_UID]; - - if (!skipRemove) - { - const i = this.managedTextures.indexOf(texture); - - if (i !== -1) - { - removeItems(this.managedTextures, i, 1); - } - } - } - } - - updateTextureStyle(texture) - { - const glTexture = texture._glTextures[this.CONTEXT_UID]; - - glTexture.mipmap = texture.mipmap && texture.isPowerOfTwo; - if (!glTexture) - { - return; - } - - if (texture.resource && texture.resource.style(this.renderer, texture, glTexture)) - { - // style is set, dont do anything! - } - else - { - this.setStyle(texture, glTexture); - } - - glTexture.dirtyStyleId = texture.dirtyStyleId; - } - - setStyle(texture, glTexture) - { - const gl = this.gl; - - if (glTexture.mipmap) - { - gl.generateMipmap(texture.target); - } - - gl.texParameteri(texture.target, gl.TEXTURE_WRAP_S, texture.wrapMode); - gl.texParameteri(texture.target, gl.TEXTURE_WRAP_T, texture.wrapMode); - - if (glTexture.mipmap) - { - /* eslint-disable max-len */ - gl.texParameteri(texture.target, gl.TEXTURE_MIN_FILTER, texture.scaleMode ? gl.LINEAR_MIPMAP_LINEAR : gl.NEAREST_MIPMAP_NEAREST); - /* eslint-disable max-len */ - } - else - { - gl.texParameteri(texture.target, gl.TEXTURE_MIN_FILTER, texture.scaleMode ? gl.LINEAR : gl.NEAREST); - } - - gl.texParameteri(texture.target, gl.TEXTURE_MAG_FILTER, texture.scaleMode ? gl.LINEAR : gl.NEAREST); - } -} diff --git a/packages/core/src/renderers/utils/ObjectRenderer.js b/packages/core/src/renderers/utils/ObjectRenderer.js deleted file mode 100644 index f67a273..0000000 --- a/packages/core/src/renderers/utils/ObjectRenderer.js +++ /dev/null @@ -1,48 +0,0 @@ -import WebGLSystem from '../systems/WebGLSystem'; - -/** - * Base for a common object renderer that can be used as a system renderer plugin. - * - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class ObjectRenderer extends WebGLSystem -{ - /** - * Starts the renderer and sets the shader - * - */ - start() - { - // set the shader.. - } - - /** - * Stops the renderer - * - */ - stop() - { - this.flush(); - } - - /** - * Stub method for rendering content and emptying the current batch. - * - */ - flush() - { - // flush! - } - - /** - * Renders an object - * - * @param {PIXI.DisplayObject} object - The object to render. - */ - render(object) // eslint-disable-line no-unused-vars - { - // render the object - } -} diff --git a/packages/core/src/renderers/utils/Quad.js b/packages/core/src/renderers/utils/Quad.js deleted file mode 100644 index 5c1517b..0000000 --- a/packages/core/src/renderers/utils/Quad.js +++ /dev/null @@ -1,26 +0,0 @@ -import Geometry from '../../geometry/Geometry'; - -/** - * Helper class to create a quad - * - * @class - * @memberof PIXI - */ -export default class Quad extends Geometry -{ - /** - * just a quad - */ - constructor() - { - super(); - - this.addAttribute('aVertexPosition', [ - 0, 0, - 1, 0, - 1, 1, - 0, 1, - ]) - .addIndex([0, 1, 3, 2]); - } -} diff --git a/packages/core/src/renderers/utils/QuadUv.js b/packages/core/src/renderers/utils/QuadUv.js deleted file mode 100644 index 97bda89..0000000 --- a/packages/core/src/renderers/utils/QuadUv.js +++ /dev/null @@ -1,103 +0,0 @@ -import Geometry from '../../geometry/Geometry'; -import Buffer from '../../geometry/Buffer'; - -/** - * Helper class to create a quad with uvs like in v4 - * - * @class - * @memberof PIXI - */ -export default class QuadUv extends Geometry -{ - constructor() - { - super(); - - /** - * An array of vertices - * - * @member {Float32Array} - */ - this.vertices = new Float32Array([ - -1, -1, - 1, -1, - 1, 1, - -1, 1, - ]); - - /** - * The Uvs of the quad - * - * @member {Float32Array} - */ - this.uvs = new Float32Array([ - 0, 0, - 1, 0, - 1, 1, - 0, 1, - ]); - - this.vertexBuffer = new Buffer(this.vertices); - this.uvBuffer = new Buffer(this.uvs); - - this.addAttribute('aVertexPosition', this.vertexBuffer) - .addAttribute('aTextureCoord', this.uvBuffer) - .addIndex([0, 1, 2, 0, 2, 3]); - } - - /** - * Maps two Rectangle to the quad. - * - * @param {PIXI.Rectangle} targetTextureFrame - the first rectangle - * @param {PIXI.Rectangle} destinationFrame - the second rectangle - * @return {PIXI.Quad} Returns itself. - */ - map(targetTextureFrame, destinationFrame) - { - let x = 0; // destinationFrame.x / targetTextureFrame.width; - let y = 0; // destinationFrame.y / targetTextureFrame.height; - - this.uvs[0] = x; - this.uvs[1] = y; - - this.uvs[2] = x + (destinationFrame.width / targetTextureFrame.width); - this.uvs[3] = y; - - this.uvs[4] = x + (destinationFrame.width / targetTextureFrame.width); - this.uvs[5] = y + (destinationFrame.height / targetTextureFrame.height); - - this.uvs[6] = x; - this.uvs[7] = y + (destinationFrame.height / targetTextureFrame.height); - - x = destinationFrame.x; - y = destinationFrame.y; - - this.vertices[0] = x; - this.vertices[1] = y; - - this.vertices[2] = x + destinationFrame.width; - this.vertices[3] = y; - - this.vertices[4] = x + destinationFrame.width; - this.vertices[5] = y + destinationFrame.height; - - this.vertices[6] = x; - this.vertices[7] = y + destinationFrame.height; - - this.invalidate(); - - return this; - } - - /** - * legacy upload method, just marks buffers dirty - * @returns {PIXI.QuadUv} Returns itself. - */ - invalidate() - { - this.vertexBuffer._updateID++; - this.uvBuffer._updateID++; - - return this; - } -} diff --git a/packages/core/src/renderers/utils/checkMaxIfStatmentsInShader.js b/packages/core/src/renderers/utils/checkMaxIfStatmentsInShader.js deleted file mode 100644 index 8047c96..0000000 --- a/packages/core/src/renderers/utils/checkMaxIfStatmentsInShader.js +++ /dev/null @@ -1,58 +0,0 @@ -const fragTemplate = [ - 'precision mediump float;', - 'void main(void){', - 'float test = 0.1;', - '%forloop%', - 'gl_FragColor = vec4(0.0);', - '}', -].join('\n'); - -export default function checkMaxIfStatmentsInShader(maxIfs, gl) -{ - if (maxIfs === 0) - { - throw new Error('Invalid value of `0` passed to `checkMaxIfStatementsInShader`'); - } - - const shader = gl.createShader(gl.FRAGMENT_SHADER); - - while (true) // eslint-disable-line no-constant-condition - { - const fragmentSrc = fragTemplate.replace(/%forloop%/gi, generateIfTestSrc(maxIfs)); - - gl.shaderSource(shader, fragmentSrc); - gl.compileShader(shader); - - if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) - { - maxIfs = (maxIfs / 2) | 0; - } - else - { - // valid! - break; - } - } - - return maxIfs; -} - -function generateIfTestSrc(maxIfs) -{ - let src = ''; - - for (let i = 0; i < maxIfs; ++i) - { - if (i > 0) - { - src += '\nelse '; - } - - if (i < maxIfs - 1) - { - src += `if(test == ${i}.0){}`; - } - } - - return src; -} diff --git a/packages/core/src/renderers/utils/mapWebGLBlendModesToPixi.js b/packages/core/src/renderers/utils/mapWebGLBlendModesToPixi.js deleted file mode 100644 index 10ffe4d..0000000 --- a/packages/core/src/renderers/utils/mapWebGLBlendModesToPixi.js +++ /dev/null @@ -1,47 +0,0 @@ -import { BLEND_MODES } from '@pixi/constants'; - -/** - * Maps gl blend combinations to WebGL. - * - * @memberof PIXI - * @function mapWebGLBlendModesToPixi - * @private - * @param {WebGLRenderingContext} gl - The rendering context. - * @param {string[]} [array=[]] - The array to output into. - * @return {string[]} Mapped modes. - */ -export default function mapWebGLBlendModesToPixi(gl, array = []) -{ - // TODO - premultiply alpha would be different. - // add a boolean for that! - array[BLEND_MODES.NORMAL] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.ADD] = [gl.ONE, gl.DST_ALPHA]; - array[BLEND_MODES.MULTIPLY] = [gl.DST_COLOR, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.SCREEN] = [gl.ONE, gl.ONE_MINUS_SRC_COLOR]; - array[BLEND_MODES.OVERLAY] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.DARKEN] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.LIGHTEN] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.COLOR_DODGE] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.COLOR_BURN] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.HARD_LIGHT] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.SOFT_LIGHT] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.DIFFERENCE] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.EXCLUSION] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.HUE] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.SATURATION] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.COLOR] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.LUMINOSITY] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.NONE] = [0, 0]; - - // not-premultiplied blend modes - array[BLEND_MODES.NORMAL_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.ADD_NPM] = [gl.SRC_ALPHA, gl.DST_ALPHA, gl.ONE, gl.DST_ALPHA]; - array[BLEND_MODES.SCREEN_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_COLOR, gl.ONE, gl.ONE_MINUS_SRC_COLOR]; - - // not-premultiplied blend modes - array[BLEND_MODES.NORMAL_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.ADD_NPM] = [gl.SRC_ALPHA, gl.DST_ALPHA, gl.ONE, gl.DST_ALPHA]; - array[BLEND_MODES.SCREEN_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_COLOR, gl.ONE, gl.ONE_MINUS_SRC_COLOR]; - - return array; -} diff --git a/packages/core/src/shader/GLProgram.js b/packages/core/src/shader/GLProgram.js new file mode 100644 index 0000000..92980ca --- /dev/null +++ b/packages/core/src/shader/GLProgram.js @@ -0,0 +1,43 @@ +/** + * Helper class to create a webGL Program + * + * @class + * @private + */ +export default class GLProgram +{ + constructor(program, uniformData) + { + /** + * The shader program + * + * @member {WebGLProgram} + */ + this.program = program; + + /** + * holds the uniform data which contains uniform locations + * and current uniform values used for caching and preventing undeed GPU commands + * @type {Object} + */ + this.uniformData = uniformData; + + /** + * uniformGroups holds the various upload functions for the shader. Each uniform group + * and program have a unique upload function generated. + * @type {Object} + */ + this.uniformGroups = {}; + } + + /** + * Destroys this program + * TODO + */ + destroy() + { + this.uniformData = null; + this.uniformGroups = null; + this.program = null; + } +} diff --git a/packages/core/src/shader/Program.js b/packages/core/src/shader/Program.js index 4f9971e..4b4007a 100644 --- a/packages/core/src/shader/Program.js +++ b/packages/core/src/shader/Program.js @@ -1,6 +1,11 @@ -import * as shaderUtils from '../renderers/systems/shader/shader'; +// import * as from '../systems/shader/shader'; +import { setPrecision, + defaultValue, + compileProgram, + mapSize, + mapType, + getTestContext } from './utils'; import { ProgramCache } from '@pixi/utils'; -import getTestContext from './getTestContext'; import defaultFragment from './defaultProgram.frag'; import defaultVertex from './defaultProgram.vert'; import { settings } from '@pixi/settings'; @@ -34,8 +39,8 @@ */ this.fragmentSrc = fragmentSrc || Program.defaultFragmentSrc; - this.vertexSrc = shaderUtils.setPrecision(this.vertexSrc, settings.PRECISION_VERTEX); - this.fragmentSrc = shaderUtils.setPrecision(this.fragmentSrc, settings.PRECISION_FRAGMENT); + this.vertexSrc = setPrecision(this.vertexSrc, settings.PRECISION_VERTEX); + this.fragmentSrc = setPrecision(this.fragmentSrc, settings.PRECISION_FRAGMENT); // currently this does not extract structs only default types this.extractData(this.vertexSrc, this.fragmentSrc); @@ -62,7 +67,7 @@ if (gl) { - const program = shaderUtils.compileProgram(gl, vertexSrc, fragmentSrc); + const program = compileProgram(gl, vertexSrc, fragmentSrc); this.attributeData = this.getAttributeData(program, gl); this.uniformData = this.getUniformData(program, gl); @@ -95,13 +100,13 @@ for (let i = 0; i < totalAttributes; i++) { const attribData = gl.getActiveAttrib(program, i); - const type = shaderUtils.mapType(gl, attribData.type); + const type = mapType(gl, attribData.type); /*eslint-disable */ const data = { type: type, name: attribData.name, - size: shaderUtils.mapSize(type), + size: mapSize(type), location: 0, }; /* eslint-enable */ @@ -145,14 +150,14 @@ const name = uniformData.name.replace(/\[.*?\]/, ''); const isArray = uniformData.name.match(/\[.*?\]/, ''); - const type = shaderUtils.mapType(gl, uniformData.type); + const type = mapType(gl, uniformData.type); /*eslint-disable */ uniforms[name] = { type: type, size: uniformData.size, isArray:isArray, - value: shaderUtils.defaultValue(type, uniformData.size), + value: defaultValue(type, uniformData.size), }; /* eslint-enable */ } diff --git a/packages/core/src/shader/ShaderSystem.js b/packages/core/src/shader/ShaderSystem.js new file mode 100644 index 0000000..40b8af8 --- /dev/null +++ b/packages/core/src/shader/ShaderSystem.js @@ -0,0 +1,171 @@ +import System from '../System'; +import GLProgram from './GLProgram'; +import { generateUniformsSync, + defaultValue, + compileProgram } from './utils'; + +let UID = 0; + +/** + * Helper class to create a webGL Texture + * + * @class + * @memberof PIXI.systems + * @extends PIXI.System + */ +export default class ShaderSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - A reference to the current renderer + */ + constructor(renderer) + { + super(renderer); + + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = null; + + this.shader = null; + this.program = null; + + this.id = UID++; + } + + contextChange(gl) + { + this.gl = gl; + } + + /** + * Changes the current shader to the one given in parameter + * + * @param {PIXI.Shader} shader - the new shader + * @param {boolean} dontSync - false if the shader should automatically sync its uniforms. + * @returns {PIXI.glCore.glProgram} the glProgram that belongs to the shader. + */ + bind(shader, dontSync) + { + shader.uniforms.globals = this.renderer.globalUniforms; + + const program = shader.program; + const glProgram = program.glPrograms[this.renderer.CONTEXT_UID] || this.generateShader(shader); + + this.shader = shader; + + // TODO - some current pixi plugins bypass this.. so it not safe to use yet.. + if (this.program !== program) + { + this.program = program; + this.gl.useProgram(glProgram.program); + } + + if (!dontSync) + { + this.syncUniformGroup(shader.uniformGroup); + } + + return glProgram; + } + + /** + * Uploads the uniforms values to the currently bound shader. + * + * @param {object} uniforms - the uniforms valiues that be applied to the current shader + */ + setUniforms(uniforms) + { + const shader = this.shader.program; + const glProgram = shader.glPrograms[this.renderer.CONTEXT_UID]; + + shader.syncUniforms(glProgram.uniformData, uniforms, this.renderer); + } + + syncUniformGroup(group) + { + const glProgram = this.getglProgram(); + + if (!group.static || group.dirtyId !== glProgram.uniformGroups[group.id]) + { + glProgram.uniformGroups[group.id] = group.dirtyId; + const syncFunc = group.syncUniforms[this.shader.program.id] || this.createSyncGroups(group); + + syncFunc(glProgram.uniformData, group.uniforms, this.renderer); + } + } + + createSyncGroups(group) + { + group.syncUniforms[this.shader.program.id] = generateUniformsSync(group, this.shader.program.uniformData); + + return group.syncUniforms[this.shader.program.id]; + } + + /** + * Returns the underlying GLShade rof the currently bound shader. + * This can be handy for when you to have a little more control over the setting of your uniforms. + * + * @return {PIXI.glCore.Shader} the glProgram for the currently bound Shader for this context + */ + getglProgram() + { + if (this.shader) + { + return this.shader.program.glPrograms[this.renderer.CONTEXT_UID]; + } + + return null; + } + + /** + * Generates a glProgram verion of the Shader provided. + * + * @private + * @param {PIXI.Shader} shader the shader that the glProgram will be based on. + * @return {PIXI.glCore.glProgram} A shiney new glProgram + */ + generateShader(shader) + { + const gl = this.gl; + + const program = shader.program; + + const attribMap = {}; + + for (const i in program.attributeData) + { + attribMap[i] = program.attributeData[i].location; + } + + const shaderProgram = compileProgram(gl, program.vertexSrc, program.fragmentSrc, attribMap); + const uniformData = {}; + + for (const i in program.uniformData) + { + const data = program.uniformData[i]; + + uniformData[i] = { + location: gl.getUniformLocation(shaderProgram, i), + value: defaultValue(data.type, data.size), + }; + } + + const glProgram = new GLProgram(shaderProgram, uniformData); + + program.glPrograms[this.renderer.CONTEXT_UID] = glProgram; + + return glProgram; + } + + /** + * Destroys this System and removes all its textures + */ + destroy() + { + // TODO implement destroy method for ShaderSystem + this.destroyed = true; + } +} diff --git a/packages/core/src/shader/defaultValue.js b/packages/core/src/shader/defaultValue.js deleted file mode 100644 index 847a069..0000000 --- a/packages/core/src/shader/defaultValue.js +++ /dev/null @@ -1,78 +0,0 @@ -function booleanArray(size) -{ - const array = new Array(size); - - for (let i = 0; i < array.length; i++) - { - array[i] = false; - } - - return array; -} - -/** - * @class - * @memberof PIXI.glCore.shader - * @param type {String} Type of value - * @param size {Number} - */ -export default function defaultValue(type, size) -{ - switch (type) - { - case 'float': - return 0; - - case 'vec2': - return new Float32Array(2 * size); - - case 'vec3': - return new Float32Array(3 * size); - - case 'vec4': - return new Float32Array(4 * size); - - case 'int': - case 'sampler2D': - return 0; - - case 'ivec2': - return new Int32Array(2 * size); - - case 'ivec3': - return new Int32Array(3 * size); - - case 'ivec4': - return new Int32Array(4 * size); - - case 'bool': - return false; - - case 'bvec2': - - return booleanArray(2 * size); - - case 'bvec3': - return booleanArray(3 * size); - - case 'bvec4': - return booleanArray(4 * size); - - case 'mat2': - return new Float32Array([1, 0, - 0, 1]); - - case 'mat3': - return new Float32Array([1, 0, 0, - 0, 1, 0, - 0, 0, 1]); - - case 'mat4': - return new Float32Array([1, 0, 0, 0, - 0, 1, 0, 0, - 0, 0, 1, 0, - 0, 0, 0, 1]); - } - - return 0; -} diff --git a/packages/core/src/shader/extractUniformsFromSrc.js b/packages/core/src/shader/extractUniformsFromSrc.js deleted file mode 100644 index 3d02c0a..0000000 --- a/packages/core/src/shader/extractUniformsFromSrc.js +++ /dev/null @@ -1,57 +0,0 @@ -import defaultValue from './defaultValue'; - -export default function extractUniformsFromSrc(vertexSrc, fragmentSrc, mask) -{ - const vertUniforms = extractUniformsFromString(vertexSrc, mask); - const fragUniforms = extractUniformsFromString(fragmentSrc, mask); - - return Object.assign(vertUniforms, fragUniforms); -} - -function extractUniformsFromString(string) -{ - const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); - - const uniforms = {}; - let nameSplit; - - // clean the lines a little - remove extra spaces / teabs etc - // then split along ';' - const lines = string.replace(/\s+/g, ' ') - .split(/\s*;\s*/); - - // loop through.. - for (let i = 0; i < lines.length; i++) - { - const line = lines[i].trim(); - - if (line.indexOf('uniform') > -1) - { - const splitLine = line.split(' '); - const type = splitLine[1]; - - let name = splitLine[2]; - let size = 1; - - if (name.indexOf('[') > -1) - { - // array! - nameSplit = name.split(/\[|]/); - name = nameSplit[0]; - size *= Number(nameSplit[1]); - } - - if (!name.match(maskRegex)) - { - uniforms[name] = { - value: defaultValue(type, size), - dirtyId: 0, - name, - type, - }; - } - } - } - - return uniforms; -} diff --git a/packages/core/src/shader/generateUniformsSync.js b/packages/core/src/shader/generateUniformsSync.js deleted file mode 100644 index 98a357f..0000000 --- a/packages/core/src/shader/generateUniformsSync.js +++ /dev/null @@ -1,231 +0,0 @@ -// cv = CachedValue -// v = value -// ud = uniformData -// uv = uniformValue -// l = loaction -const GLSL_TO_SINGLE_SETTERS_CACHED = { - - float: ` - if(cv !== v) - { - cv.v = v; - gl.uniform1f(location, v) - }`, - - vec2: ` - if(cv[0] !== v[0] || cv[1] !== v[1]) - { - cv[0] = v[0]; - cv[1] = v[1]; - gl.uniform2f(location, v[0], v[1]) - }`, - - vec3: ` - if(cv[0] !== v[0] || cv[1] !== v[1] || cv[2] !== v[2]) - { - cv[0] = v[0]; - cv[1] = v[1]; - cv[2] = v[2]; - - gl.uniform3f(location, v[0], v[1], v[2]) - }`, - - vec4: 'gl.uniform4f(location, v[0], v[1], v[2], v[3])', - - int: 'gl.uniform1i(location, v)', - ivec2: 'gl.uniform2i(location, v[0], v[1])', - ivec3: 'gl.uniform3i(location, v[0], v[1], v[2])', - ivec4: 'gl.uniform4i(location, v[0], v[1], v[2], v[3])', - - bool: 'gl.uniform1i(location, v)', - bvec2: 'gl.uniform2i(location, v[0], v[1])', - bvec3: 'gl.uniform3i(location, v[0], v[1], v[2])', - bvec4: 'gl.uniform4i(location, v[0], v[1], v[2], v[3])', - - mat2: 'gl.uniformMatrix2fv(location, false, v)', - mat3: 'gl.uniformMatrix3fv(location, false, v)', - mat4: 'gl.uniformMatrix4fv(location, false, v)', - - sampler2D: 'gl.uniform1i(location, v)', - samplerCube: 'gl.uniform1i(location, v)', - sampler2DArray: 'gl.uniform1i(location, v)', -}; - -const GLSL_TO_ARRAY_SETTERS = { - - float: `gl.uniform1fv(location, v)`, - - vec2: `gl.uniform2fv(location, v)`, - vec3: `gl.uniform3fv(location, v)`, - vec4: 'gl.uniform4fv(location, v)', - - mat4: 'gl.uniformMatrix4fv(location, false, v)', - mat3: 'gl.uniformMatrix3fv(location, false, v)', - mat2: 'gl.uniformMatrix2fv(location, false, v)', - - int: 'gl.uniform1iv(location, v)', - ivec2: 'gl.uniform2iv(location, v)', - ivec3: 'gl.uniform3iv(location, v)', - ivec4: 'gl.uniform4iv(location, v)', - - bool: 'gl.uniform1iv(location, v)', - bvec2: 'gl.uniform2iv(location, v)', - bvec3: 'gl.uniform3iv(location, v)', - bvec4: 'gl.uniform4iv(location, v)', - - sampler2D: 'gl.uniform1iv(location, v)', - samplerCube: 'gl.uniform1iv(location, v)', - sampler2DArray: 'gl.uniform1iv(location, v)', -}; - -export default function generateUniformsSync(group, uniformData) -{ - let textureCount = 0; - let func = `var v = null; - var cv = null - var gl = renderer.gl`; - - for (const i in group.uniforms) - { - const data = uniformData[i]; - - if (!data) - { - if (group.uniforms[i].group) - { - func += ` - renderer.shader.syncUniformGroup(uv.${i}); - `; - } - - continue; - } - - // TODO && uniformData[i].value !== 0 <-- do we still need this? - if (data.type === 'float' && data.size === 1) - { - func += ` - if(uv.${i} !== ud.${i}.value) - { - ud.${i}.value = uv.${i} - gl.uniform1f(ud.${i}.location, uv.${i}) - }\n`; - } - /* eslint-disable max-len */ - else if ((data.type === 'sampler2D' || data.type === 'samplerCube' || data.type === 'sampler2DArray') && data.size === 1 && !data.isArray) - /* eslint-disable max-len */ - { - func += ` - renderer.texture.bind(uv.${i}, ${textureCount}); - - if(ud.${i}.value !== ${textureCount}) - { - ud.${i}.value = ${textureCount}; - gl.uniform1i(ud.${i}.location, ${textureCount});\n; // eslint-disable-line max-len - }\n`; - - textureCount++; - } - else if (data.type === 'mat3' && data.size === 1) - { - if (group.uniforms[i].a !== undefined) - { - // TODO and some smart caching dirty ids here! - func += ` - gl.uniformMatrix3fv(ud.${i}.location, false, uv.${i}.toArray(true)); - \n`; - } - else - { - func += ` - gl.uniformMatrix3fv(ud.${i}.location, false, uv.${i}); - \n`; - } - } - else if (data.type === 'vec2' && data.size === 1) - { - // TODO - do we need both here? - // maybe we can get away with only using points? - if (group.uniforms[i].x !== undefined) - { - func += ` - cv = ud.${i}.value; - v = uv.${i}; - - if(cv[0] !== v.x || cv[1] !== v.y) - { - cv[0] = v.x; - cv[1] = v.y; - gl.uniform2f(ud.${i}.location, v.x, v.y); - }\n`; - } - else - { - func += ` - cv = ud.${i}.value; - v = uv.${i}; - - if(cv[0] !== v[0] || cv[1] !== v[1]) - { - cv[0] = v[0]; - cv[1] = v[1]; - gl.uniform2f(ud.${i}.location, v[0], v[1]); - } - \n`; - } - } - else if (data.type === 'vec4' && data.size === 1) - { - // TODO - do we need both here? - // maybe we can get away with only using points? - if (group.uniforms[i].width !== undefined) - { - func += ` - cv = ud.${i}.value; - v = uv.${i}; - - if(cv[0] !== v.x || cv[1] !== v.y || cv[2] !== v.width || cv[3] !== v.height) - { - cv[0] = v.x; - cv[1] = v.y; - cv[2] = v.width; - cv[3] = v.height; - gl.uniform4f(ud.${i}.location, v.x, v.y, v.width, v.height) - }\n`; - } - else - { - func += ` - cv = ud.${i}.value; - v = uv.${i}; - - if(cv[0] !== v[0] || cv[1] !== v[1] || cv[2] !== v[2] || cv[3] !== v[3]) - { - cv[0] = v[0]; - cv[1] = v[1]; - cv[2] = v[2]; - cv[3] = v[3]; - - gl.uniform4f(ud.${i}.location, v[0], v[1], v[2], v[3]) - } - \n`; - } - } - else - { - const templateType = (data.size === 1) ? GLSL_TO_SINGLE_SETTERS_CACHED : GLSL_TO_ARRAY_SETTERS; - - const template = templateType[data.type].replace('location', `ud.${i}.location`); - - func += ` - cv = ud.${i}.value; - v = uv.${i}; - ${template};\n`; - } - } - - // console.log(' --------------- ') - // console.log(func); - - return new Function('ud', 'uv', 'renderer', func); // eslint-disable-line no-new-func -} diff --git a/packages/core/src/shader/getTestContext.js b/packages/core/src/shader/getTestContext.js deleted file mode 100644 index ea174cc..0000000 --- a/packages/core/src/shader/getTestContext.js +++ /dev/null @@ -1,48 +0,0 @@ -import { settings } from '@pixi/settings'; - -let context = null; - -/** - * returns a little webGL context to use for program inspection. - * - * @static - * @private - * @returns {webGL-context} a gl context to test with - */ -export default function getTestContext() -{ - if (!context) - { - const canvas = document.createElement('canvas'); - - let gl; - - if (settings.PREFER_WEBGL_2) - { - gl = canvas.getContext('webgl2', {}); - } - - if (!gl) - { - gl = canvas.getContext('webgl', {}) - || canvas.getContext('experimental-webgl', {}); - - if (!gl) - { - // fail, not able to get a context - throw new Error('This browser does not support webGL. Try using the canvas renderer'); - } - else - { - // for shader testing.. - gl.getExtension('WEBGL_draw_buffers'); - } - } - - context = gl; - - return gl; - } - - return context; -} diff --git a/packages/core/src/shader/utils/checkMaxIfStatmentsInShader.js b/packages/core/src/shader/utils/checkMaxIfStatmentsInShader.js new file mode 100644 index 0000000..8047c96 --- /dev/null +++ b/packages/core/src/shader/utils/checkMaxIfStatmentsInShader.js @@ -0,0 +1,58 @@ +const fragTemplate = [ + 'precision mediump float;', + 'void main(void){', + 'float test = 0.1;', + '%forloop%', + 'gl_FragColor = vec4(0.0);', + '}', +].join('\n'); + +export default function checkMaxIfStatmentsInShader(maxIfs, gl) +{ + if (maxIfs === 0) + { + throw new Error('Invalid value of `0` passed to `checkMaxIfStatementsInShader`'); + } + + const shader = gl.createShader(gl.FRAGMENT_SHADER); + + while (true) // eslint-disable-line no-constant-condition + { + const fragmentSrc = fragTemplate.replace(/%forloop%/gi, generateIfTestSrc(maxIfs)); + + gl.shaderSource(shader, fragmentSrc); + gl.compileShader(shader); + + if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) + { + maxIfs = (maxIfs / 2) | 0; + } + else + { + // valid! + break; + } + } + + return maxIfs; +} + +function generateIfTestSrc(maxIfs) +{ + let src = ''; + + for (let i = 0; i < maxIfs; ++i) + { + if (i > 0) + { + src += '\nelse '; + } + + if (i < maxIfs - 1) + { + src += `if(test == ${i}.0){}`; + } + } + + return src; +} diff --git a/packages/core/src/shader/utils/compileProgram.js b/packages/core/src/shader/utils/compileProgram.js new file mode 100644 index 0000000..8c6ccb9 --- /dev/null +++ b/packages/core/src/shader/utils/compileProgram.js @@ -0,0 +1,78 @@ +/** + * @method compileProgram + * @memberof PIXI.glCore.shader + * @param gl {WebGLRenderingContext} The current WebGL context {WebGLProgram} + * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. + * @param fragmentSrc {string|string[]} The fragment shader source as an array of strings. + * @param attributeLocations {Object} An attribute location map that lets you manually set the attribute locations + * @return {WebGLProgram} the shader program + */ +export default function compileProgram(gl, vertexSrc, fragmentSrc, attributeLocations) +{ + const glVertShader = compileShader(gl, gl.VERTEX_SHADER, vertexSrc); + const glFragShader = compileShader(gl, gl.FRAGMENT_SHADER, fragmentSrc); + + let program = gl.createProgram(); + + gl.attachShader(program, glVertShader); + gl.attachShader(program, glFragShader); + + // optionally, set the attributes manually for the program rather than letting WebGL decide.. + if (attributeLocations) + { + for (const i in attributeLocations) + { + gl.bindAttribLocation(program, attributeLocations[i], i); + } + } + + gl.linkProgram(program); + + // if linking fails, then log and cleanup + if (!gl.getProgramParameter(program, gl.LINK_STATUS)) + { + console.error('Pixi.js Error: Could not initialize shader.'); + console.error('gl.VALIDATE_STATUS', gl.getProgramParameter(program, gl.VALIDATE_STATUS)); + console.error('gl.getError()', gl.getError()); + + // if there is a program info log, log it + if (gl.getProgramInfoLog(program) !== '') + { + console.warn('Pixi.js Warning: gl.getProgramInfoLog()', gl.getProgramInfoLog(program)); + } + + gl.deleteProgram(program); + program = null; + } + + // clean up some shaders + gl.deleteShader(glVertShader); + gl.deleteShader(glFragShader); + + return program; +} + +/** + * @private + * @param gl {WebGLRenderingContext} The current WebGL context {WebGLProgram} + * @param type {Number} the type, can be either VERTEX_SHADER or FRAGMENT_SHADER + * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. + * @return {WebGLShader} the shader + */ +function compileShader(gl, type, src) +{ + const shader = gl.createShader(type); + + gl.shaderSource(shader, src); + gl.compileShader(shader); + + if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) + { + console.warn(src); + console.error(gl.getShaderInfoLog(shader)); + + return null; + } + + return shader; +} diff --git a/packages/core/src/shader/utils/defaultValue.js b/packages/core/src/shader/utils/defaultValue.js new file mode 100644 index 0000000..97290bd --- /dev/null +++ b/packages/core/src/shader/utils/defaultValue.js @@ -0,0 +1,79 @@ +/** + * @method defaultValue + * @memberof PIXI.glCore.shader + * @param type {String} Type of value + * @param size {Number} + */ +export default function defaultValue(type, size) +{ + switch (type) + { + case 'float': + return 0; + + case 'vec2': + return new Float32Array(2 * size); + + case 'vec3': + return new Float32Array(3 * size); + + case 'vec4': + return new Float32Array(4 * size); + + case 'int': + case 'sampler2D': + case 'sampler2DArray': + return 0; + + case 'ivec2': + return new Int32Array(2 * size); + + case 'ivec3': + return new Int32Array(3 * size); + + case 'ivec4': + return new Int32Array(4 * size); + + case 'bool': + return false; + + case 'bvec2': + + return booleanArray(2 * size); + + case 'bvec3': + return booleanArray(3 * size); + + case 'bvec4': + return booleanArray(4 * size); + + case 'mat2': + return new Float32Array([1, 0, + 0, 1]); + + case 'mat3': + return new Float32Array([1, 0, 0, + 0, 1, 0, + 0, 0, 1]); + + case 'mat4': + return new Float32Array([1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1]); + } + + return null; +} + +function booleanArray(size) +{ + const array = new Array(size); + + for (let i = 0; i < array.length; i++) + { + array[i] = false; + } + + return array; +} diff --git a/packages/core/src/shader/utils/extractUniformsFromSrc.js b/packages/core/src/shader/utils/extractUniformsFromSrc.js new file mode 100644 index 0000000..3d02c0a --- /dev/null +++ b/packages/core/src/shader/utils/extractUniformsFromSrc.js @@ -0,0 +1,57 @@ +import defaultValue from './defaultValue'; + +export default function extractUniformsFromSrc(vertexSrc, fragmentSrc, mask) +{ + const vertUniforms = extractUniformsFromString(vertexSrc, mask); + const fragUniforms = extractUniformsFromString(fragmentSrc, mask); + + return Object.assign(vertUniforms, fragUniforms); +} + +function extractUniformsFromString(string) +{ + const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); + + const uniforms = {}; + let nameSplit; + + // clean the lines a little - remove extra spaces / teabs etc + // then split along ';' + const lines = string.replace(/\s+/g, ' ') + .split(/\s*;\s*/); + + // loop through.. + for (let i = 0; i < lines.length; i++) + { + const line = lines[i].trim(); + + if (line.indexOf('uniform') > -1) + { + const splitLine = line.split(' '); + const type = splitLine[1]; + + let name = splitLine[2]; + let size = 1; + + if (name.indexOf('[') > -1) + { + // array! + nameSplit = name.split(/\[|]/); + name = nameSplit[0]; + size *= Number(nameSplit[1]); + } + + if (!name.match(maskRegex)) + { + uniforms[name] = { + value: defaultValue(type, size), + dirtyId: 0, + name, + type, + }; + } + } + } + + return uniforms; +} diff --git a/packages/core/src/shader/utils/generateUniformsSync.js b/packages/core/src/shader/utils/generateUniformsSync.js new file mode 100644 index 0000000..98a357f --- /dev/null +++ b/packages/core/src/shader/utils/generateUniformsSync.js @@ -0,0 +1,231 @@ +// cv = CachedValue +// v = value +// ud = uniformData +// uv = uniformValue +// l = loaction +const GLSL_TO_SINGLE_SETTERS_CACHED = { + + float: ` + if(cv !== v) + { + cv.v = v; + gl.uniform1f(location, v) + }`, + + vec2: ` + if(cv[0] !== v[0] || cv[1] !== v[1]) + { + cv[0] = v[0]; + cv[1] = v[1]; + gl.uniform2f(location, v[0], v[1]) + }`, + + vec3: ` + if(cv[0] !== v[0] || cv[1] !== v[1] || cv[2] !== v[2]) + { + cv[0] = v[0]; + cv[1] = v[1]; + cv[2] = v[2]; + + gl.uniform3f(location, v[0], v[1], v[2]) + }`, + + vec4: 'gl.uniform4f(location, v[0], v[1], v[2], v[3])', + + int: 'gl.uniform1i(location, v)', + ivec2: 'gl.uniform2i(location, v[0], v[1])', + ivec3: 'gl.uniform3i(location, v[0], v[1], v[2])', + ivec4: 'gl.uniform4i(location, v[0], v[1], v[2], v[3])', + + bool: 'gl.uniform1i(location, v)', + bvec2: 'gl.uniform2i(location, v[0], v[1])', + bvec3: 'gl.uniform3i(location, v[0], v[1], v[2])', + bvec4: 'gl.uniform4i(location, v[0], v[1], v[2], v[3])', + + mat2: 'gl.uniformMatrix2fv(location, false, v)', + mat3: 'gl.uniformMatrix3fv(location, false, v)', + mat4: 'gl.uniformMatrix4fv(location, false, v)', + + sampler2D: 'gl.uniform1i(location, v)', + samplerCube: 'gl.uniform1i(location, v)', + sampler2DArray: 'gl.uniform1i(location, v)', +}; + +const GLSL_TO_ARRAY_SETTERS = { + + float: `gl.uniform1fv(location, v)`, + + vec2: `gl.uniform2fv(location, v)`, + vec3: `gl.uniform3fv(location, v)`, + vec4: 'gl.uniform4fv(location, v)', + + mat4: 'gl.uniformMatrix4fv(location, false, v)', + mat3: 'gl.uniformMatrix3fv(location, false, v)', + mat2: 'gl.uniformMatrix2fv(location, false, v)', + + int: 'gl.uniform1iv(location, v)', + ivec2: 'gl.uniform2iv(location, v)', + ivec3: 'gl.uniform3iv(location, v)', + ivec4: 'gl.uniform4iv(location, v)', + + bool: 'gl.uniform1iv(location, v)', + bvec2: 'gl.uniform2iv(location, v)', + bvec3: 'gl.uniform3iv(location, v)', + bvec4: 'gl.uniform4iv(location, v)', + + sampler2D: 'gl.uniform1iv(location, v)', + samplerCube: 'gl.uniform1iv(location, v)', + sampler2DArray: 'gl.uniform1iv(location, v)', +}; + +export default function generateUniformsSync(group, uniformData) +{ + let textureCount = 0; + let func = `var v = null; + var cv = null + var gl = renderer.gl`; + + for (const i in group.uniforms) + { + const data = uniformData[i]; + + if (!data) + { + if (group.uniforms[i].group) + { + func += ` + renderer.shader.syncUniformGroup(uv.${i}); + `; + } + + continue; + } + + // TODO && uniformData[i].value !== 0 <-- do we still need this? + if (data.type === 'float' && data.size === 1) + { + func += ` + if(uv.${i} !== ud.${i}.value) + { + ud.${i}.value = uv.${i} + gl.uniform1f(ud.${i}.location, uv.${i}) + }\n`; + } + /* eslint-disable max-len */ + else if ((data.type === 'sampler2D' || data.type === 'samplerCube' || data.type === 'sampler2DArray') && data.size === 1 && !data.isArray) + /* eslint-disable max-len */ + { + func += ` + renderer.texture.bind(uv.${i}, ${textureCount}); + + if(ud.${i}.value !== ${textureCount}) + { + ud.${i}.value = ${textureCount}; + gl.uniform1i(ud.${i}.location, ${textureCount});\n; // eslint-disable-line max-len + }\n`; + + textureCount++; + } + else if (data.type === 'mat3' && data.size === 1) + { + if (group.uniforms[i].a !== undefined) + { + // TODO and some smart caching dirty ids here! + func += ` + gl.uniformMatrix3fv(ud.${i}.location, false, uv.${i}.toArray(true)); + \n`; + } + else + { + func += ` + gl.uniformMatrix3fv(ud.${i}.location, false, uv.${i}); + \n`; + } + } + else if (data.type === 'vec2' && data.size === 1) + { + // TODO - do we need both here? + // maybe we can get away with only using points? + if (group.uniforms[i].x !== undefined) + { + func += ` + cv = ud.${i}.value; + v = uv.${i}; + + if(cv[0] !== v.x || cv[1] !== v.y) + { + cv[0] = v.x; + cv[1] = v.y; + gl.uniform2f(ud.${i}.location, v.x, v.y); + }\n`; + } + else + { + func += ` + cv = ud.${i}.value; + v = uv.${i}; + + if(cv[0] !== v[0] || cv[1] !== v[1]) + { + cv[0] = v[0]; + cv[1] = v[1]; + gl.uniform2f(ud.${i}.location, v[0], v[1]); + } + \n`; + } + } + else if (data.type === 'vec4' && data.size === 1) + { + // TODO - do we need both here? + // maybe we can get away with only using points? + if (group.uniforms[i].width !== undefined) + { + func += ` + cv = ud.${i}.value; + v = uv.${i}; + + if(cv[0] !== v.x || cv[1] !== v.y || cv[2] !== v.width || cv[3] !== v.height) + { + cv[0] = v.x; + cv[1] = v.y; + cv[2] = v.width; + cv[3] = v.height; + gl.uniform4f(ud.${i}.location, v.x, v.y, v.width, v.height) + }\n`; + } + else + { + func += ` + cv = ud.${i}.value; + v = uv.${i}; + + if(cv[0] !== v[0] || cv[1] !== v[1] || cv[2] !== v[2] || cv[3] !== v[3]) + { + cv[0] = v[0]; + cv[1] = v[1]; + cv[2] = v[2]; + cv[3] = v[3]; + + gl.uniform4f(ud.${i}.location, v[0], v[1], v[2], v[3]) + } + \n`; + } + } + else + { + const templateType = (data.size === 1) ? GLSL_TO_SINGLE_SETTERS_CACHED : GLSL_TO_ARRAY_SETTERS; + + const template = templateType[data.type].replace('location', `ud.${i}.location`); + + func += ` + cv = ud.${i}.value; + v = uv.${i}; + ${template};\n`; + } + } + + // console.log(' --------------- ') + // console.log(func); + + return new Function('ud', 'uv', 'renderer', func); // eslint-disable-line no-new-func +} diff --git a/packages/core/src/shader/utils/getTestContext.js b/packages/core/src/shader/utils/getTestContext.js new file mode 100644 index 0000000..ea174cc --- /dev/null +++ b/packages/core/src/shader/utils/getTestContext.js @@ -0,0 +1,48 @@ +import { settings } from '@pixi/settings'; + +let context = null; + +/** + * returns a little webGL context to use for program inspection. + * + * @static + * @private + * @returns {webGL-context} a gl context to test with + */ +export default function getTestContext() +{ + if (!context) + { + const canvas = document.createElement('canvas'); + + let gl; + + if (settings.PREFER_WEBGL_2) + { + gl = canvas.getContext('webgl2', {}); + } + + if (!gl) + { + gl = canvas.getContext('webgl', {}) + || canvas.getContext('experimental-webgl', {}); + + if (!gl) + { + // fail, not able to get a context + throw new Error('This browser does not support webGL. Try using the canvas renderer'); + } + else + { + // for shader testing.. + gl.getExtension('WEBGL_draw_buffers'); + } + } + + context = gl; + + return gl; + } + + return context; +} diff --git a/packages/core/src/shader/utils/index.js b/packages/core/src/shader/utils/index.js new file mode 100644 index 0000000..4bf8dd4 --- /dev/null +++ b/packages/core/src/shader/utils/index.js @@ -0,0 +1,8 @@ +export { default as compileProgram } from './compileProgram'; +export { default as defaultValue } from './defaultValue'; +export { default as setPrecision } from './setPrecision'; +export { default as mapSize } from './mapSize'; +export { default as mapType } from './mapType'; +export { default as generateUniformsSync } from './generateUniformsSync'; +export { default as getTestContext } from './getTestContext'; +export { default as checkMaxIfStatmentsInShader } from './checkMaxIfStatmentsInShader'; diff --git a/packages/core/src/AbstractRenderer.js b/packages/core/src/AbstractRenderer.js new file mode 100644 index 0000000..64b2eb1 --- /dev/null +++ b/packages/core/src/AbstractRenderer.js @@ -0,0 +1,353 @@ +import { hex2string, hex2rgb } from '@pixi/utils'; +import { Matrix, Rectangle } from '@pixi/math'; +import { RENDERER_TYPE } from '@pixi/constants'; +import { settings } from '@pixi/settings'; +import { Container } from '@pixi/display'; +import RenderTexture from './renderTexture/RenderTexture'; +import EventEmitter from 'eventemitter3'; + +const tempMatrix = new Matrix(); + +/** + * 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 EventEmitter + * @memberof PIXI + */ +export default class AbstractRenderer extends EventEmitter +{ + // eslint-disable-next-line valid-jsdoc + /** + * @param {string} system - The name of the system this renderer is for. + * @param {object} [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.transparent=false] - If the render view is transparent, default false + * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false + * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. The + * resolution of the renderer retina would be 2. + * @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 {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when rendering, + * stopping pixel interpolation. + */ + constructor(system, options, arg2, arg3) + { + super(); + + // Support for constructor(system, screenWidth, screenHeight, options) + if (typeof options === 'number') + { + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + }, arg3); + } + + // 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 = RENDERER_TYPE.UNKNOWN; + + /** + * Measurements of the screen. (0, 0, screenWidth, screenHeight) + * + * Its safe to use as filterArea or hitArea for 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 1 + */ + this.resolution = options.resolution || settings.RESOLUTION; + + /** + * Whether the render view is transparent + * + * @member {boolean} + */ + this.transparent = options.transparent; + + /** + * Whether css dimensions of canvas view should be resized to screen dimensions automatically + * + * @member {boolean} + */ + this.autoResize = options.autoResize || false; + + /** + * Tracks the blend modes useful for this renderer. + * + * @member {object} + */ + this.blendModes = null; + + /** + * 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; + + /** + * If true PixiJS will Math.floor() x/y values when rendering, stopping pixel interpolation. + * Handy for crisp pixel art and speed on legacy devices. + * + * @member {boolean} + */ + this.roundPixels = options.roundPixels; + + /** + * The background color as a number. + * + * @member {number} + * @private + */ + this._backgroundColor = 0x000000; + + /** + * The background color as an [R, G, B] array. + * + * @member {number[]} + * @private + */ + this._backgroundColorRgba = [0, 0, 0, 0]; + + /** + * The background color as a string. + * + * @member {string} + * @private + */ + this._backgroundColorString = '#000000'; + + this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter + + /** + * This temporary display object used as the parent of the currently being rendered item + * + * @member {PIXI.DisplayObject} + * @private + */ + this._tempDisplayObjectParent = new Container(); + + /** + * The last root object that the renderer tried to render. + * + * @member {PIXI.DisplayObject} + * @private + */ + this._lastObjectRendered = this._tempDisplayObjectParent; + + /** + * Collection of plugins. + * @readonly + * @member {object} + */ + this.plugins = {}; + } + + /** + * Initialize the plugins. + * + * @protected + * @param {object} staticMap - The dictionary of staticly saved plugins. + */ + initPlugins(staticMap) + { + 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() + { + return this.view.width; + } + + /** + * Same as view.height, actual number of pixels in the canvas by vertical + * + * @member {number} + * @readonly + * @default 600 + */ + get height() + { + return this.view.height; + } + + /** + * Resizes the screen and canvas to the specified width and height + * Canvas dimensions are multiplied by resolution + * + * @param {number} screenWidth - the new width of the screen + * @param {number} screenHeight - the new height of the screen + */ + resize(screenWidth, screenHeight) + { + this.screen.width = screenWidth; + this.screen.height = screenHeight; + + this.view.width = screenWidth * this.resolution; + this.view.height = screenHeight * this.resolution; + + if (this.autoResize) + { + this.view.style.width = `${screenWidth}px`; + this.view.style.height = `${screenHeight}px`; + } + } + + /** + * 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 {PIXI.DisplayObject} displayObject - The displayObject the object will be generated from + * @param {number} scaleMode - Should be one of the scaleMode consts + * @param {number} resolution - The resolution / device pixel ratio of the texture being generated + * @param {PIXI.Rectangle} [region] - The region of the displayObject, that shall be rendered, + * if no region is specified, defaults to the local bounds of the displayObject. + * @return {PIXI.Texture} a texture of the graphics object + */ + generateTexture(displayObject, scaleMode, resolution, region) + { + region = region || displayObject.getLocalBounds(); + + // 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(region.width | 0, region.height | 0, scaleMode, resolution); + + tempMatrix.tx = -region.x; + tempMatrix.ty = -region.y; + + this.render(displayObject, renderTexture, false, tempMatrix, true); + + return renderTexture; + } + + /** + * Removes everything from the renderer and optionally removes the Canvas DOM element. + * + * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. + */ + destroy(removeView) + { + 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); + } + + this.plugins = null; + + this.type = RENDERER_TYPE.UNKNOWN; + + this.view = null; + + this.screen = null; + + this.resolution = 0; + + this.transparent = false; + + this.autoResize = false; + + this.blendModes = null; + + this.options = null; + + this.preserveDrawingBuffer = false; + this.clearBeforeRender = false; + + this.roundPixels = false; + + this._backgroundColor = 0; + this._backgroundColorRgba = null; + this._backgroundColorString = null; + + this._tempDisplayObjectParent = null; + this._lastObjectRendered = null; + } + + /** + * The background color to fill if not transparent + * + * @member {number} + */ + get backgroundColor() + { + return this._backgroundColor; + } + + set backgroundColor(value) // eslint-disable-line require-jsdoc + { + this._backgroundColor = value; + this._backgroundColorString = hex2string(value); + hex2rgb(value, this._backgroundColorRgba); + } +} diff --git a/packages/core/src/Renderer.js b/packages/core/src/Renderer.js new file mode 100644 index 0000000..2227e78 --- /dev/null +++ b/packages/core/src/Renderer.js @@ -0,0 +1,338 @@ +import AbstractRenderer from './AbstractRenderer'; +import { sayHello } from '@pixi/utils'; +import MaskSystem from './mask/MaskSystem'; +import StencilSystem from './mask/StencilSystem'; +import FilterSystem from './filters/FilterSystem'; +import FramebufferSystem from './framebuffer/FramebufferSystem'; +import RenderTextureSystem from './renderTexture/RenderTextureSystem'; +import TextureSystem from './textures/TextureSystem'; +import ProjectionSystem from './projection/ProjectionSystem'; +import StateSystem from './state/StateSystem'; +import GeometrySystem from './geometry/GeometrySystem'; +import ShaderSystem from './shader/ShaderSystem'; +import ContextSystem from './context/ContextSystem'; +import BatchSystem from './batch/BatchSystem'; +import TextureGCSystem from './textures/TextureGCSystem'; +import { RENDERER_TYPE } from '@pixi/constants'; +import UniformGroup from './shader/UniformGroup'; +import { Matrix } from '@pixi/math'; +import Runner from 'mini-runner'; + +/** + * 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 Render works by automatically managing webGLBatchs. + * 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 :) + * + * @class + * @memberof PIXI + * @extends PIXI.AbstractRenderer + */ +export default class Renderer extends AbstractRenderer +{ + // eslint-disable-next-line valid-jsdoc + /** + * + * @param {object} [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.transparent=false] - If the render view is transparent, default false + * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false + * @param {boolean} [options.antialias=false] - sets antialias. If not available natively then FXAA + * antialiasing is used + * @param {boolean} [options.forceFXAA=false] - forces FXAA antialiasing to be used over native. + * FXAA is faster, but may not always look as great + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. + * The resolution of the renderer retina would be 2. + * @param {boolean} [options.clearBeforeRender=true] - This sets if the renderer will clear + * the canvas or not before the new render pass. If you wish to set this to false, you *must* set + * preserveDrawingBuffer to `true`. + * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, + * enable this if you need to call toDataUrl on the webgl context. + * @param {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when + * rendering, stopping pixel interpolation. + * @param {number} [options.backgroundColor=0x000000] - The background color of the rendered area + * (shown if not transparent). + * @param {boolean} [options.legacy=false] - If true PixiJS will aim to ensure compatibility + * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. + * @param {string} [options.powerPreference] - Parameter passed to webgl context, set to "high-performance" + * for devices with dual graphics card + */ + constructor(options, arg2, arg3) + { + super('WebGL', options, arg2, arg3); + + /** + * The type of this renderer as a standardised const + * + * @member {number} + * @see PIXI.RENDERER_TYPE + */ + this.type = RENDERER_TYPE.WEBGL; + + // this will be set by the contextSystem (this.context) + this.gl = null; + this.CONTEXT_UID = 0; + this.legacy = !!options.legacy; + + // TODO legacy! + + // runners! + this.runners = { + destroy: new Runner('destroy'), + contextChange: new Runner('contextChange', 1), + reset: new Runner('reset'), + update: new Runner('update'), + postrender: new Runner('postrender'), + prerender: new Runner('prerender'), + resize: new Runner('resize', 2), + }; + + 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(GeometrySystem, 'geometry') + .addSystem(FramebufferSystem, 'framebuffer') + .addSystem(StencilSystem, 'stencil') + .addSystem(ProjectionSystem, 'projection') + .addSystem(TextureGCSystem, 'textureGC') + .addSystem(FilterSystem, 'filter') + .addSystem(RenderTextureSystem, 'renderTexture') + .addSystem(BatchSystem, 'batch'); + + this.initPlugins(Renderer.__plugins); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ + if (options.context) + { + this.context.initFromContext(options.context); + } + else + { + this.context.initFromOptions({ + alpha: this.transparent, + antialias: options.antialias, + premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', + stencil: true, + 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); + } + + /** + * Add a new system to the renderer. + * @param {class} ClassRef - Class reference + * @param {string} [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. + * @return {PIXI.Renderer} Return instance of renderer + */ + addSystem(ClassRef, name) + { + if (!name) + { + name = ClassRef.name; + } + + // TODO - read name from class.name.. + + /* + if(name.includes('System')) + { + name = name.replace('System', ''); + name = name.charAt(0).toLowerCase() + name.slice(1); + } + */ + + const system = new ClassRef(this); + + if (this[name]) + { + throw new Error(`Whoops! ${name} is already a manger`); + } + + this[name] = system; + + for (const i in this.runners) + { + this.runners[i].add(system); + } + + return this; + + /** + * 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. + */ + } + + /** + * Renders the object to its webGL view + * + * @param {PIXI.DisplayObject} displayObject - the object to be rendered + * @param {PIXI.RenderTexture} renderTexture - The render texture to render to. + * @param {boolean} [clear] - Should the canvas be cleared before the new render + * @param {PIXI.Transform} [transform] - A transform to apply to the render texture before rendering. + * @param {boolean} [skipUpdateTransform] - Should we skip the update transform pass? + */ + render(displayObject, renderTexture, clear, transform, skipUpdateTransform) + { + // can be handy to know! + this.renderingToScreen = !renderTexture; + + this.runners.prerender.run(); + this.emit('prerender'); + + // 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.parent; + + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = 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.run(); + + this.emit('postrender'); + } + + /** + * Resizes the webGL view to the specified width and height. + * + * @param {number} screenWidth - the new width of the screen + * @param {number} screenHeight - the new height of the screen + */ + resize(screenWidth, screenHeight) + { + AbstractRenderer.prototype.resize.call(this, screenWidth, screenHeight); + + this.runners.resize.run(screenWidth, screenHeight); + } + + /** + * Resets the WebGL state so you can render things however you fancy! + * + * @return {PIXI.Renderer} Returns itself. + */ + reset() + { + this.runners.reset.run(); + + return this; + } + + clear() + { + this.framebuffer.bind(); + this.framebuffer.clear(); + } + + /** + * Removes everything from the renderer (event listeners, spritebatch, etc...) + * + * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. + * See: https://github.com/pixijs/pixi.js/issues/2233 + */ + destroy(removeView) + { + this.runners.destroy.run(); + + // call base destroy + super.destroy(removeView); + + // TODO nullify all the managers.. + this.gl = null; + } + + /** + * 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 + * builds and excluding plugins. + * @name PIXI.Renderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. + */ + + /** + * Adds a plugin to the renderer. + * + * @method + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + static registerPlugin(pluginName, ctor) + { + Renderer.__plugins = Renderer.__plugins || {}; + Renderer.__plugins[pluginName] = ctor; + } +} diff --git a/packages/core/src/System.js b/packages/core/src/System.js new file mode 100644 index 0000000..05a10c8 --- /dev/null +++ b/packages/core/src/System.js @@ -0,0 +1,42 @@ +/** + * System is a base class used for extending systems used by the {@link PIXI.Renderer} + * @see PIXI.Renderer#addSystem + * @class + * @memberof PIXI + */ +export default class System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + /** + * The renderer this manager works for. + * + * @member {PIXI.Renderer} + */ + this.renderer = renderer; + + this.renderer.runners.contextChange.add(this); + } + + /** + * Generic method called when there is a WebGL context change. + * + */ + contextChange() + { + // do some codes init! + } + + /** + * Generic destroy methods to be overridden by the subclass + * + */ + destroy() + { + this.renderer.runners.contextChange.remove(this); + this.renderer = null; + } +} diff --git a/packages/core/src/batch/BatchSystem.js b/packages/core/src/batch/BatchSystem.js new file mode 100644 index 0000000..b04c249 --- /dev/null +++ b/packages/core/src/batch/BatchSystem.js @@ -0,0 +1,68 @@ +import System from '../System'; +import ObjectRenderer from './ObjectRenderer'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class BatchSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + super(renderer); + + /** + * An empty renderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.emptyRenderer = new ObjectRenderer(renderer); + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.currentRenderer = this.emptyRenderer; + } + + /** + * Changes the current renderer to the one given in parameter + * + * @param {PIXI.ObjectRenderer} objectRenderer - The object renderer to use. + */ + setObjectRenderer(objectRenderer) + { + if (this.currentRenderer === objectRenderer) + { + return; + } + + this.currentRenderer.stop(); + this.currentRenderer = objectRenderer; + + this.renderer.state.setState(objectRenderer.state); + + this.currentRenderer.start(); + } + + /** + * This should be called if you wish to do some custom rendering + * It will basically render anything that may be batched up such as sprites + * + */ + flush() + { + this.setObjectRenderer(this.emptyRenderer); + } + + reset() + { + this.setObjectRenderer(this.emptyRenderer); + } +} diff --git a/packages/core/src/batch/ObjectRenderer.js b/packages/core/src/batch/ObjectRenderer.js new file mode 100644 index 0000000..0c851a9 --- /dev/null +++ b/packages/core/src/batch/ObjectRenderer.js @@ -0,0 +1,48 @@ +import System from '../System'; + +/** + * Base for a common object renderer that can be used as a system renderer plugin. + * + * @class + * @extends PIXI.System + * @memberof PIXI + */ +export default class ObjectRenderer extends System +{ + /** + * Starts the renderer and sets the shader + * + */ + start() + { + // set the shader.. + } + + /** + * Stops the renderer + * + */ + stop() + { + this.flush(); + } + + /** + * Stub method for rendering content and emptying the current batch. + * + */ + flush() + { + // flush! + } + + /** + * Renders an object + * + * @param {PIXI.DisplayObject} object - The object to render. + */ + render(object) // eslint-disable-line no-unused-vars + { + // render the object + } +} diff --git a/packages/core/src/context/ContextSystem.js b/packages/core/src/context/ContextSystem.js new file mode 100644 index 0000000..301aab4 --- /dev/null +++ b/packages/core/src/context/ContextSystem.js @@ -0,0 +1,186 @@ +import System from '../System'; +import { settings } from '@pixi/settings'; + +let CONTEXT_UID = 0; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class ContextSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.webGLVersion = 1; + + this.handleContextLost = this.handleContextLost.bind(this); + this.handleContextRestored = this.handleContextRestored.bind(this); + + this.extensions = {}; + + renderer.view.addEventListener('webglcontextlost', this.handleContextLost, false); + renderer.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + } + + get isLost() + { + return (!this.gl || this.gl.isContextLost()); + } + + contextChange(gl) + { + this.gl = gl; + + // restore a context if it was previously lost + if (gl.isContextLost() && gl.getExtension('WEBGL_lose_context')) + { + gl.getExtension('WEBGL_lose_context').restoreContext(); + } + } + + initFromContext(gl) + { + this.gl = gl; + this.validateContext(gl); + this.renderer.gl = gl; + this.renderer.CONTEXT_UID = CONTEXT_UID++; + this.renderer.runners.contextChange.run(gl); + } + + initFromOptions(options) + { + const gl = this.createContext(this.renderer.view, options); + + this.initFromContext(gl); + } + + /** + * Helper class to create a webGL Context + * + * @class + * @memberof PIXI.glCore + * @param canvas {HTMLCanvasElement} the canvas element that we will get the context from + * @param options {Object} An options object that gets passed in to the canvas element containing the context attributes, + * see https://developer.mozilla.org/en/docs/Web/API/HTMLCanvasElement/getContext for the options available + * @return {WebGLRenderingContext} the WebGL context + */ + createContext(canvas, options) + { + let gl; + + if (settings.PREFER_WEBGL_2) + { + gl = canvas.getContext('webgl2', options); + } + + if (gl) + { + this.webGLVersion = 2; + } + else + { + this.webGLVersion = 1; + + gl = canvas.getContext('webgl', options) + || canvas.getContext('experimental-webgl', options); + + if (!gl) + { + // fail, not able to get a context + throw new Error('This browser does not support webGL. Try using the canvas renderer'); + } + } + + this.gl = gl; + + this.getExtensions(); + + return gl; + } + + getExtensions() + { + // time to set up default etensions that pixi uses.. + const gl = this.gl; + const extensions = this.extensions; + + if (this.webGLVersion === 1) + { + extensions.drawBuffers = gl.getExtension('WEBGL_draw_buffers'); + extensions.depthTexture = gl.getExtension('WEBKIT_WEBGL_depth_texture'); + extensions.floatTexture = gl.getExtension('OES_texture_float'); + extensions.loseContext = gl.getExtension('WEBGL_lose_context'); + + extensions.vertexArrayObject = gl.getExtension('OES_vertex_array_object') + || gl.getExtension('MOZ_OES_vertex_array_object') + || gl.getExtension('WEBKIT_OES_vertex_array_object'); + } + + // we don't use any specific WebGL 2 ones yet! + } + + /** + * Handles a lost webgl context + * + * @private + * @param {WebGLContextEvent} event - The context lost event. + */ + handleContextLost(event) + { + event.preventDefault(); + } + + /** + * Handles a restored webgl context + * + * @private + */ + handleContextRestored() + { + this.renderer.runners.contextChange.run(this.gl); + } + + destroy() + { + const view = this.renderer.view; + + // remove listeners + view.removeEventListener('webglcontextlost', this.handleContextLost); + view.removeEventListener('webglcontextrestored', this.handleContextRestored); + + this.gl.useProgram(null); + + if (this.extensions.loseContext) + { + this.extensions.loseContext.loseContext(); + } + } + + postrender() + { + this.gl.flush(); + } + + validateContext(gl) + { + const attributes = gl.getContextAttributes(); + + // this is going to be fairly simple for now.. but at least we have room to grow! + if (!attributes.stencil) + { + /* eslint-disable max-len */ + + /* eslint-disable no-console */ + console.warn('Provided WebGL context does not have a stencil buffer, masks may not render correctly'); + /* eslint-enable no-console */ + + /* eslint-enable max-len */ + } + } +} diff --git a/packages/core/src/filters/Filter.js b/packages/core/src/filters/Filter.js new file mode 100644 index 0000000..9d8cad8 --- /dev/null +++ b/packages/core/src/filters/Filter.js @@ -0,0 +1,140 @@ +import Shader from '../shader/Shader'; +import Program from '../shader/Program'; +import State from '../state/State'; +import { settings } from '@pixi/settings'; +// import extractUniformsFromSrc from './extractUniformsFromSrc'; +import defaultVertex from './defaultFilter.vert'; +import defaultFragment from './defaultFilter.frag'; + +// let math = require('../../math'); +/** + * @class + * @memberof PIXI + * @extends PIXI.Shader + */ +export default class Filter extends Shader +{ + /** + * @param {string} [vertexSrc] - The source of the vertex shader. + * @param {string} [fragmentSrc] - The source of the fragment shader. + * @param {object} [uniforms] - Custom uniforms to use to augment the built-in ones. + */ + constructor(vertexSrc, fragmentSrc, uniforms) + { + const program = Program.from(vertexSrc, fragmentSrc); + + super(program, uniforms); + + /** + * The padding of the filter. Some filters require extra space to breath such as a blur. + * Increasing this will add extra width and height to the bounds of the object that the + * filter is applied to. + * + * @member {number} + */ + this.padding = 0; + + /** + * The resolution of the filter. Setting this to be lower will lower the quality but + * increase the performance of the filter. + * + * @member {number} + */ + this.resolution = settings.RESOLUTION; + + /** + * If enabled is true the filter is applied, if false it will not. + * + * @member {boolean} + */ + this.enabled = true; + + /** + * If enabled, PixiJS will fit the filter area into boundaries for better performance. + * Switch it off if it does not work for specific shader. + * + * @member {boolean} + */ + this.autoFit = true; + + /** + * Legacy filters use position and uvs from attributes + * @member {boolean} + * @readonly + */ + this.legacy = !!this.program.attributeData.aTextureCoord; + + /** + * the webGL state the filter requires to render + * @member {PIXI.State} + */ + this.state = new State(); + } + + /** + * Applies the filter + * + * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + * @param {boolean} clear - Should the output be cleared before rendering to it + * @param {object} [currentState] - It's current state of filter. + * There are some useful properties in the currentState : + * target, filters, sourceFrame, destinationFrame, renderTarget, resolution + */ + apply(filterManager, input, output, clear, currentState, derp) // eslint-disable-line no-unused-vars + { + // do as you please! + + filterManager.applyFilter(this, input, output, clear, currentState, derp); + + // or just do a regular render.. + } + + /** + * Sets the blendmode of the filter + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL + */ + get blendMode() + { + return this.state.blendMode; + } + + set blendMode(value) // eslint-disable-line require-jsdoc + { + this.state.blendMode = value; + } + + /** + * The default vertex shader source + * + * @static + * @constant + */ + static get defaultVertexSrc() + { + return defaultVertex; + } + + /** + * The default fragment shader source + * + * @static + * @constant + */ + static get defaultFragmentSrc() + { + return defaultFragment; + } +} + +/** + * Used for caching shader IDs + * + * @static + * @private + */ +Filter.SOURCE_KEY_MAP = {}; + diff --git a/packages/core/src/filters/FilterSystem.js b/packages/core/src/filters/FilterSystem.js new file mode 100644 index 0000000..3174318 --- /dev/null +++ b/packages/core/src/filters/FilterSystem.js @@ -0,0 +1,431 @@ +import System from '../System'; + +import RenderTexture from '../renderTexture/RenderTexture'; +import Quad from '../utils/Quad'; +import QuadUv from '../utils/QuadUv'; +import { Rectangle } from '@pixi/math'; +import * as filterTransforms from './filterTransforms'; +import bitTwiddle from 'bit-twiddle'; +import UniformGroup from '../shader/UniformGroup'; +import { DRAW_MODES } from '@pixi/constants'; + +// +/** + * @ignore + * @class + */ +class FilterState +{ + /** + * + */ + constructor() + { + this.renderTexture = null; + this.sourceFrame = new Rectangle(); + this.destinationFrame = new Rectangle(); + this.filters = []; + this.target = null; + this.legacy = false; + this.resolution = 1; + } +} + +/** + * @class + * @memberof PIXI.systems + * @extends PIXI.System + */ +export default class FilterSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + /** + * stores a bunch of PO2 textures used for filtering + * @type {Object} + */ + this.texturePool = {}; + + /** + * a pool for storing filter states, save us creating new ones each tick + * @type {Array} + */ + this.statePool = []; + + /** + * A very simple geometry used when drawing a filter effect to the screen + * @type {Quad} + */ + this.quad = new Quad(); + + this.quadUv = new QuadUv(); + + /** + * Temporary rect for maths + * @type {PIXI.Rectangle} + */ + this.tempRect = new Rectangle(); + + this.activeState = {}; + + /** + * this uniform group is attached to filter uniforms when used + * @type {UniformGroup} + */ + this.globalUniforms = new UniformGroup({ + sourceFrame: this.tempRect, + destinationFrame: this.tempRect, + + // legacy variables + filterArea: new Float32Array(4), + filterClamp: new Float32Array(4), + }, true); + } + + /** + * Adds a new filter to the System. + * + * @param {PIXI.DisplayObject} target - The target of the filter to render. + * @param {PIXI.Filter[]} filters - The filters to apply. + */ + push(target, filters) + { + const renderer = this.renderer; + const filterStack = this.renderer.renderTexture.defaultFilterStack; + const state = this.statePool.pop() || new FilterState(); + + let resolution = filters[0].resolution; + let padding = filters[0].padding; + let autoFit = filters[0].autoFit; + let legacy = filters[0].legacy; + + for (let i = 1; i < filters.length; i++) + { + const filter = filters[i]; + + // lets use the lowest resolution.. + resolution = Math.min(resolution, filter.resolution); + // and the largest amount of padding! + padding = Math.max(padding, filter.padding); + // only auto fit if all filters are autofit + autoFit = autoFit || filter.autoFit; + + legacy = legacy || filter.legacy; + } + + filterStack.push(state); + + state.resolution = resolution; + + state.legacy = legacy; + + // round to whole number based on resolution + // TODO move that to the shader too? + state.sourceFrame = target.filterArea || target.getBounds(true); + + state.sourceFrame.pad(padding); + + if (autoFit) + { + state.sourceFrame.fit(this.renderer.renderTexture.destinationFrame); + } + + state.sourceFrame.round(resolution); + + state.renderTexture = this.getPotFilterTexture(state.sourceFrame.width, state.sourceFrame.height, resolution); + state.filters = filters; + + state.destinationFrame.width = state.renderTexture.width; + state.destinationFrame.height = state.renderTexture.height; + + state.renderTexture.filterFrame = state.sourceFrame; + renderer.renderTexture.bind(state.renderTexture, state.sourceFrame);// /, state.destinationFrame); + renderer.renderTexture.clear(); + } + + /** + * Pops off the filter and applies it. + * + */ + pop() + { + const renderer = this.renderer; + const filterStack = renderer.renderTexture.defaultFilterStack; + const state = filterStack.pop(); + const filters = state.filters; + + this.activeState = state; + + const globalUniforms = this.globalUniforms.uniforms; + + globalUniforms.sourceFrame = state.sourceFrame; + globalUniforms.destinationFrame = state.destinationFrame; + globalUniforms.resolution = state.resolution; + + // only update the rect if its legacy.. + if (state.legacy) + { + const filterArea = globalUniforms.filterArea; + const filterClamp = globalUniforms.filterClamp; + + filterArea[0] = state.destinationFrame.width; + filterArea[1] = state.destinationFrame.height; + filterArea[2] = state.sourceFrame.x; + filterArea[3] = state.sourceFrame.y; + + filterClamp[0] = 0.5 / state.resolution / state.destinationFrame.width; + filterClamp[1] = 0.5 / state.resolution / state.destinationFrame.height; + filterClamp[2] = (state.sourceFrame.width - 0.5) / state.resolution / state.destinationFrame.width; + filterClamp[3] = (state.sourceFrame.height - 0.5) / state.resolution / state.destinationFrame.height; + } + + this.globalUniforms.update(); + + const lastState = filterStack[filterStack.length - 1]; + + if (filters.length === 1) + { + filters[0].apply(this, state.renderTexture, lastState.renderTexture, false, state); + + this.returnFilterTexture(state.renderTexture); + } + else + { + let flip = state.renderTexture; + let flop = this.getPotFilterTexture( + flip.width, + flip.height, + state.resolution + ); + + flop.filterFrame = flip.filterFrame; + + let i = 0; + + for (i = 0; i < filters.length - 1; ++i) + { + filters[i].apply(this, flip, flop, true, state); + + const t = flip; + + flip = flop; + flop = t; + } + + filters[i].apply(this, flip, lastState.renderTexture, false, state); + + this.returnFilterTexture(flip); + this.returnFilterTexture(flop); + } + + this.statePool.push(state); + } + + /** + * Draws a filter. + * + * @param {PIXI.Filter} filter - The filter to draw. + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + * @param {boolean} clear - Should the output be cleared before rendering to it + */ + applyFilter(filter, input, output, clear) + { + const renderer = this.renderer; + + renderer.renderTexture.bind(output, output ? output.filterFrame : null); + + if (clear) + { + // gl.disable(gl.SCISSOR_TEST); + renderer.renderTexture.clear(); + // gl.enable(gl.SCISSOR_TEST); + } + + // set the uniforms.. + filter.uniforms.uSampler = input; + filter.uniforms.filterGlobals = this.globalUniforms; + + // TODO make it so that the order of this does not matter.. + // because it does at the moment cos of global uniforms. + // they need to get resynced + + renderer.state.setState(filter.state); + renderer.shader.bind(filter); + + if (filter.legacy) + { + this.quadUv.map(input._frame, input.filterFrame); + + renderer.geometry.bind(this.quadUv); + renderer.geometry.draw(DRAW_MODES.TRIANGLES); + } + else + { + renderer.geometry.bind(this.quad); + renderer.geometry.draw(DRAW_MODES.TRIANGLE_STRIP); + } + } + + /** + * Calculates the mapped matrix. + * + * TODO playing around here.. this is temporary - (will end up in the shader) + * this returns a matrix that will normalise map filter cords in the filter to screen space + * + * @param {PIXI.Matrix} outputMatrix - the matrix to output to. + * @return {PIXI.Matrix} The mapped matrix. + */ + calculateScreenSpaceMatrix(outputMatrix) + { + const currentState = this.activeState; + + return filterTransforms.calculateScreenSpaceMatrix( + outputMatrix, + currentState.sourceFrame, + currentState.destinationFrame + ); + } + + /** + * This will map the filter coord so that a texture can be used based on the transform of a sprite + * + * @param {PIXI.Matrix} outputMatrix - The matrix to output to. + * @param {PIXI.Sprite} sprite - The sprite to map to. + * @return {PIXI.Matrix} The mapped matrix. + */ + calculateSpriteMatrix(outputMatrix, sprite) + { + const currentState = this.activeState; + + return filterTransforms.calculateSpriteMatrix( + outputMatrix, + currentState.sourceFrame, + currentState.destinationFrame, + sprite + ); + } + + /** + * Destroys this Filter System. + * + * @param {boolean} [contextLost=false] context was lost, do not free shaders + * + */ + destroy(contextLost = false) + { + if (!contextLost) + { + this.emptyPool(); + } + else + { + this.texturePool = {}; + } + } + + /** + * Gets a Power-of-Two render texture. + * + * TODO move to a seperate class could be on renderer? + * also - could cause issue with multiple contexts? + * + * @private + * @param {WebGLRenderingContext} gl - The webgl rendering context + * @param {number} minWidth - The minimum width of the render target. + * @param {number} minHeight - The minimum height of the render target. + * @param {number} resolution - The resolution of the render target. + * @return {PIXI.RenderTarget} The new render target. + */ + getPotFilterTexture(minWidth, minHeight, resolution) + { + minWidth = bitTwiddle.nextPow2(minWidth); + minHeight = bitTwiddle.nextPow2(minHeight); + resolution = resolution || 1; + + const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + + if (!this.texturePool[key]) + { + this.texturePool[key] = []; + } + + let renderTexture = this.texturePool[key].pop(); + + if (!renderTexture) + { + // temporary bypass cache.. + // internally - this will cause a texture to be bound.. + renderTexture = RenderTexture.create({ + width: minWidth, + height: minHeight, + resolution, + }); + } + + return renderTexture; + } + + /** + * Gets extra render texture to use inside current filter + * + * @param {number} resolution resolution of the renderTexture + * @returns {PIXI.RenderTexture} + */ + getFilterTexture(resolution) + { + const rt = this.activeState.renderTexture; + + const filterTexture = this.getPotFilterTexture(rt.width, rt.height, resolution || rt.baseTexture.resolution); + + filterTexture.filterFrame = rt.filterFrame; + + return filterTexture; + } + + /** + * Frees a render texture back into the pool. + * + * @param {PIXI.RenderTarget} renderTexture - The renderTarget to free + */ + returnFilterTexture(renderTexture) + { + renderTexture.filterFrame = null; + + const base = renderTexture.baseTexture; + + const minWidth = base.width; + const minHeight = base.height; + + const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + + this.texturePool[key].push(renderTexture); + } + + /** + * Empties the texture pool. + * + */ + emptyPool() + { + for (const i in this.texturePool) + { + const textures = this.texturePool[i]; + + if (textures) + { + for (let j = 0; j < textures.length; j++) + { + textures[j].destroy(true); + } + } + } + + this.texturePool = {}; + } +} diff --git a/packages/core/src/filters/defaultFilter.frag b/packages/core/src/filters/defaultFilter.frag new file mode 100644 index 0000000..a14c1be --- /dev/null +++ b/packages/core/src/filters/defaultFilter.frag @@ -0,0 +1,22 @@ +varying vec2 vTextureCoord; +varying vec2 vFilterCoord; + +uniform sampler2D uSampler; +uniform sampler2D filterSampler; + +void main(void){ + vec4 masky = texture2D(filterSampler, vFilterCoord); + vec4 sample = texture2D(uSampler, vTextureCoord); + vec4 color; + if(mod(vFilterCoord.x, 1.0) > 0.5) + { + color = vec4(1.0, 0.0, 0.0, 1.0); + } + else + { + color = vec4(0.0, 1.0, 0.0, 1.0); + } + // gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0); + gl_FragColor = mix(sample, masky, 0.5); + gl_FragColor *= sample.a; +} diff --git a/packages/core/src/filters/defaultFilter.vert b/packages/core/src/filters/defaultFilter.vert new file mode 100644 index 0000000..e1febca --- /dev/null +++ b/packages/core/src/filters/defaultFilter.vert @@ -0,0 +1,14 @@ +attribute vec2 aVertexPosition; +attribute vec2 aTextureCoord; + +uniform mat3 projectionMatrix; +uniform mat3 filterMatrix; + +varying vec2 vTextureCoord; +varying vec2 vFilterCoord; + +void main(void){ + gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); + vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy; + vTextureCoord = aTextureCoord ; +} diff --git a/packages/core/src/filters/filterTransforms.js b/packages/core/src/filters/filterTransforms.js new file mode 100644 index 0000000..87a3cd6 --- /dev/null +++ b/packages/core/src/filters/filterTransforms.js @@ -0,0 +1,50 @@ +import { Matrix } from '@pixi/math'; + +/** + * Calculates the mapped matrix + * @param filterArea {Rectangle} The filter area + * @param sprite {Sprite} the target sprite + * @param outputMatrix {Matrix} @alvin + * @private + */ +// this returns a matrix that will normalise map filter cords in the filter to screen space +export function calculateScreenSpaceMatrix(outputMatrix, filterArea, textureSize) +{ + // TODO unwrap? + const mappedMatrix = outputMatrix.identity(); + + mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); + + mappedMatrix.scale(textureSize.width, textureSize.height); + + return mappedMatrix; +} + +export function calculateNormalizedScreenSpaceMatrix(outputMatrix, filterArea, textureSize) +{ + const mappedMatrix = outputMatrix.identity(); + + mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); + + const translateScaleX = (textureSize.width / filterArea.width); + const translateScaleY = (textureSize.height / filterArea.height); + + mappedMatrix.scale(translateScaleX, translateScaleY); + + return mappedMatrix; +} + +// this will map the filter coord so that a texture can be used based on the transform of a sprite +export function calculateSpriteMatrix(outputMatrix, filterArea, textureSize, sprite) +{ + const orig = sprite._texture.orig; + const mappedMatrix = outputMatrix.set(textureSize.width, 0, 0, textureSize.height, filterArea.x, filterArea.y); + const worldTransform = sprite.worldTransform.copyTo(Matrix.TEMP_MATRIX); + + worldTransform.invert(); + mappedMatrix.prepend(worldTransform); + mappedMatrix.scale(1.0 / orig.width, 1.0 / orig.height); + mappedMatrix.translate(sprite.anchor.x, sprite.anchor.y); + + return mappedMatrix; +} diff --git a/packages/core/src/filters/spriteMask/SpriteMaskFilter.js b/packages/core/src/filters/spriteMask/SpriteMaskFilter.js new file mode 100644 index 0000000..91697b7 --- /dev/null +++ b/packages/core/src/filters/spriteMask/SpriteMaskFilter.js @@ -0,0 +1,63 @@ +import Filter from '../Filter'; +import { Matrix } from '@pixi/math'; +import vertex from './spriteMaskFilter.vert'; +import fragment from './spriteMaskFilter.frag'; +import { default as TextureMatrix } from '../../textures/TextureMatrix'; + +/** + * The SpriteMaskFilter class + * + * @class + * @extends PIXI.Filter + * @memberof PIXI + */ +export default class SpriteMaskFilter extends Filter +{ + /** + * @param {PIXI.Sprite} sprite - the target sprite + */ + constructor(sprite) + { + const maskMatrix = new Matrix(); + + super(vertex, fragment); + + sprite.renderable = false; + + this.maskSprite = sprite; + this.maskMatrix = maskMatrix; + } + + /** + * Applies the filter + * + * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + */ + apply(filterManager, input, output) + { + const maskSprite = this.maskSprite; + const tex = this.maskSprite.texture; + + if (!tex.valid) + { + return; + } + if (!tex.transform) + { + // margin = 0.0, let it bleed a bit, shader code becomes easier + // assuming that atlas textures were made with 1-pixel padding + tex.transform = new TextureMatrix(tex, 0.0); + } + tex.transform.update(); + + this.uniforms.mask = tex; + this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite) + .prepend(tex.transform.mapCoord); + this.uniforms.alpha = maskSprite.worldAlpha; + this.uniforms.maskClamp = tex.transform.uClampFrame; + + filterManager.applyFilter(this, input, output); + } +} diff --git a/packages/core/src/filters/spriteMask/spriteMaskFilter.frag b/packages/core/src/filters/spriteMask/spriteMaskFilter.frag new file mode 100644 index 0000000..0e4aef8 --- /dev/null +++ b/packages/core/src/filters/spriteMask/spriteMaskFilter.frag @@ -0,0 +1,23 @@ +varying vec2 vMaskCoord; +varying vec2 vTextureCoord; + +uniform sampler2D uSampler; +uniform sampler2D mask; +uniform float alpha; +uniform vec4 maskClamp; + +void main(void) +{ + float clip = step(3.5, + step(maskClamp.x, vMaskCoord.x) + + step(maskClamp.y, vMaskCoord.y) + + step(vMaskCoord.x, maskClamp.z) + + step(vMaskCoord.y, maskClamp.w)); + + vec4 original = texture2D(uSampler, vTextureCoord); + vec4 masky = texture2D(mask, vMaskCoord); + + original *= (masky.r * masky.a * alpha * clip); + + gl_FragColor = original; +} diff --git a/packages/core/src/filters/spriteMask/spriteMaskFilter.vert b/packages/core/src/filters/spriteMask/spriteMaskFilter.vert new file mode 100644 index 0000000..d23a30c --- /dev/null +++ b/packages/core/src/filters/spriteMask/spriteMaskFilter.vert @@ -0,0 +1,16 @@ +attribute vec2 aVertexPosition; +attribute vec2 aTextureCoord; + +uniform mat3 projectionMatrix; +uniform mat3 otherMatrix; + +varying vec2 vMaskCoord; +varying vec2 vTextureCoord; + +void main(void) +{ + gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); + + vTextureCoord = aTextureCoord; + vMaskCoord = ( otherMatrix * vec3( aTextureCoord, 1.0) ).xy; +} diff --git a/packages/core/src/framebuffer/FrameBuffer.js b/packages/core/src/framebuffer/FrameBuffer.js new file mode 100644 index 0000000..84e9fcc --- /dev/null +++ b/packages/core/src/framebuffer/FrameBuffer.js @@ -0,0 +1,106 @@ +import Texture from '../textures/BaseTexture'; +import { FORMATS, TYPES } from '@pixi/constants'; + +/** + * Frame buffer + * @class + * @memberof PIXI + */ +export default class FrameBuffer +{ + constructor(width, height) + { + this.width = width || 100; + this.height = height || 100; + + this.stencil = false; + this.depth = false; + + this.dirtyId = 0; + this.dirtyFormat = 0; + this.dirtySize = 0; + + this.depthTexture = null; + this.colorTextures = []; + + this.glFrameBuffers = {}; + } + + get colorTexture() + { + return this.colorTextures[0]; + } + + addColorTexture(index, texture) + { + // TODO add some validation to the texture - same width / height etc? + this.colorTextures[index || 0] = texture || new Texture(null, { scaleMode: 0, + resolution: 1, + mipmap: false, + width: this.width, + height: this.height });// || new Texture(); + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + addDepthTexture(texture) + { + /* eslint-disable max-len */ + this.depthTexture = texture || new Texture(null, { scaleMode: 0, + resolution: 1, + width: this.width, + height: this.height, + mipmap: false, + format: FORMATS.DEPTH_COMPONENT, + type: TYPES.UNSIGNED_SHORT });// UNSIGNED_SHORT; + /* eslint-disable max-len */ + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + enableDepth() + { + this.depth = true; + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + enableStencil() + { + this.stencil = true; + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + resize(width, height) + { + if (width === this.width && height === this.height) return; + + this.width = width; + this.height = height; + + this.dirtyId++; + this.dirtySize++; + + for (let i = 0; i < this.colorTextures.length; i++) + { + this.colorTextures[i].setSize(width, height); + } + + if (this.depthTexture) + { + this.depthTexture.setSize(width, height); + } + } +} diff --git a/packages/core/src/framebuffer/FramebufferSystem.js b/packages/core/src/framebuffer/FramebufferSystem.js new file mode 100644 index 0000000..2cc2437 --- /dev/null +++ b/packages/core/src/framebuffer/FramebufferSystem.js @@ -0,0 +1,246 @@ +import System from '../System'; +import { Rectangle } from '@pixi/math'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class FramebufferSystem extends System +{ + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + contextChange() + { + this.gl = this.renderer.gl; + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + this.current = null; + this.viewport = new Rectangle(); + + this.drawBufferExtension = this.renderer.context.extensions.drawBuffers; + } + + bind(framebuffer, frame) + { + const gl = this.gl; + + this.current = framebuffer; + + if (framebuffer) + { + // TODO cacheing layer! + + const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID] || this.initFramebuffer(framebuffer); + + gl.bindFramebuffer(gl.FRAMEBUFFER, fbo.framebuffer); + // makesure all textures are unbound.. + + // now check for updates... + if (fbo.dirtyId !== framebuffer.dirtyId) + { + fbo.dirtyId = framebuffer.dirtyId; + + if (fbo.dirtyFormat !== framebuffer.dirtyFormat) + { + fbo.dirtyFormat = framebuffer.dirtyFormat; + this.updateFramebuffer(framebuffer); + } + else if (fbo.dirtySize !== framebuffer.dirtySize) + { + fbo.dirtySize = framebuffer.dirtySize; + this.resizeFramebuffer(framebuffer); + } + } + + for (let i = 0; i < framebuffer.colorTextures.length; i++) + { + if (framebuffer.colorTextures[i].texturePart) + { + this.renderer.texture.unbind(framebuffer.colorTextures[i].texture); + } + else + { + this.renderer.texture.unbind(framebuffer.colorTextures[i]); + } + } + + if (framebuffer.depthTexture) + { + this.renderer.texture.unbind(framebuffer.depthTexture); + } + + if (frame) + { + this.setViewport(frame.x, frame.y, frame.width, frame.height); + } + else + { + this.setViewport(0, 0, framebuffer.width, framebuffer.height); + } + } + else + { + gl.bindFramebuffer(gl.FRAMEBUFFER, null); + + if (frame) + { + this.setViewport(frame.x, frame.y, frame.width, frame.height); + } + else + { + this.setViewport(0, 0, this.renderer.width, this.renderer.height); + } + } + } + + setViewport(x, y, width, height) + { + const v = this.viewport; + + if (v.width !== width || v.height !== height || v.x !== x || v.y !== y) + { + v.x = x; + v.y = y; + v.width = width; + v.height = height; + + this.gl.viewport(x, y, width, height); + } + } + + get size() + { + if (this.current) + { + // TODO store temp + return { x: 0, y: 0, width: this.current.width, height: this.current.height }; + } + + return { x: 0, y: 0, width: this.renderer.width, height: this.renderer.height }; + } + + clear(r, g, b, a) + { + const gl = this.gl; + + // TODO clear color can be set only one right? + gl.clearColor(r, g, b, a); + gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); + } + + // private functions... + + initFramebuffer(framebuffer) + { + const gl = this.gl; + + // TODO - make this a class? + const fbo = { + framebuffer: gl.createFramebuffer(), + stencil: null, + dirtyId: 0, + dirtyFormat: 0, + dirtySize: 0, + }; + + framebuffer.glFrameBuffers[this.CONTEXT_UID] = fbo; + + return fbo; + } + + resizeFramebuffer(framebuffer) + { + const gl = this.gl; + + if (framebuffer.stencil || framebuffer.depth) + { + gl.bindRenderbuffer(gl.RENDERBUFFER, this.stencil); + gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); + } + } + + updateFramebuffer(framebuffer) + { + const gl = this.gl; + + const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID]; + + // bind the color texture + const colorTextures = framebuffer.colorTextures; + + let count = colorTextures.length; + + if (!this.drawBufferExtension) + { + count = Math.min(count, 1); + } + + const activeTextures = []; + + for (let i = 0; i < count; i++) + { + const texture = framebuffer.colorTextures[i]; + + if (texture.texturePart) + { + this.renderer.texture.bind(texture.texture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.COLOR_ATTACHMENT0 + i, + gl.TEXTURE_CUBE_MAP_NEGATIVE_X + texture.side, + texture.texture._glTextures[this.CONTEXT_UID].texture, + 0); + } + else + { + this.renderer.texture.bind(texture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.COLOR_ATTACHMENT0 + i, + gl.TEXTURE_2D, + texture._glTextures[this.CONTEXT_UID].texture, + 0); + } + + activeTextures.push(gl.COLOR_ATTACHMENT0 + i); + } + + if (this.drawBufferExtension && activeTextures.length > 1) + { + this.drawBufferExtension.drawBuffersWEBGL(activeTextures); + } + + if (framebuffer.depthTexture) + { + const depthTextureExt = this.renderer.context.extensions.depthTexture; + + if (depthTextureExt) + { + const depthTexture = framebuffer.depthTexture; + + this.renderer.texture.bind(depthTexture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.DEPTH_ATTACHMENT, + gl.TEXTURE_2D, + depthTexture._glTextures[this.CONTEXT_UID].texture, + 0); + } + } + + if (framebuffer.stencil || framebuffer.depth) + { + fbo.stencil = gl.createRenderbuffer(); + + gl.bindRenderbuffer(gl.RENDERBUFFER, fbo.stencil); + + // TODO.. this is depth AND stencil? + gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, fbo.stencil); + gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); + // fbo.enableStencil(); + } + } +} diff --git a/packages/core/src/geometry/GLBuffer.js b/packages/core/src/geometry/GLBuffer.js new file mode 100644 index 0000000..0477e8a --- /dev/null +++ b/packages/core/src/geometry/GLBuffer.js @@ -0,0 +1,9 @@ +export default class GLBuffer +{ + constructor(buffer) + { + this.buffer = buffer; + this.updateID = -1; + this.byteLength = -1; + } +} diff --git a/packages/core/src/geometry/GeometrySystem.js b/packages/core/src/geometry/GeometrySystem.js new file mode 100644 index 0000000..959f827 --- /dev/null +++ b/packages/core/src/geometry/GeometrySystem.js @@ -0,0 +1,388 @@ +import System from '../System'; +import GLBuffer from './GLBuffer'; + +const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class GeometrySystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this._activeGeometry = null; + this._activeVao = null; + + this.hasVao = true; + this.hasInstance = true; + } + + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + contextChange() + { + const gl = this.gl = this.renderer.gl; + + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + + // webgl2 + if (!gl.createVertexArray) + { + // webgl 1! + let nativeVaoExtension = this.renderer.context.extensions.vertexArrayObject; + + if (this.renderer.legacy) + { + nativeVaoExtension = null; + } + + if (nativeVaoExtension) + { + gl.createVertexArray = () => + nativeVaoExtension.createVertexArrayOES(); + + gl.bindVertexArray = (vao) => + nativeVaoExtension.bindVertexArrayOES(vao); + + gl.deleteVertexArray = (vao) => + nativeVaoExtension.deleteVertexArrayOES(vao); + } + else + { + this.hasVao = false; + gl.createVertexArray = () => + { + // empty + }; + + gl.bindVertexArray = () => + { + // empty + }; + + gl.deleteVertexArray = () => + { + // empty + }; + } + } + + if (!gl.vertexAttribDivisor) + { + const instanceExt = gl.getExtension('ANGLE_instanced_arrays'); + + if (instanceExt) + { + gl.vertexAttribDivisor = (a, b) => + instanceExt.vertexAttribDivisorANGLE(a, b); + + gl.drawElementsInstanced = (a, b, c, d, e) => + instanceExt.drawElementsInstancedANGLE(a, b, c, d, e); + + gl.drawArraysInstanced = (a, b, c, d) => + instanceExt.drawArraysInstancedANGLE(a, b, c, d); + } + else + { + this.hasInstance = false; + } + } + } + + /** + * Binds geometry so that is can be drawn. Creating a Vao if required + * @private + * @param {PIXI.Geometry} geometry instance of geometry to bind + */ + bind(geometry, shader) + { + shader = shader || this.renderer.shader.shader; + + const gl = this.gl; + + // not sure the best way to address this.. + // currently different shaders require different VAOs for the same geometry + // Still mulling over the best way to solve this one.. + // will likely need to modify the shader attribute locations at run time! + let vaos = geometry.glVertexArrayObjects[this.CONTEXT_UID]; + + if (!vaos) + { + geometry.glVertexArrayObjects[this.CONTEXT_UID] = vaos = {}; + } + + const vao = vaos[shader.program.id] || this.initGeometryVao(geometry, shader.program); + + this._activeGeometry = geometry; + + if (this._activeVao !== vao) + { + this._activeVao = vao; + + if (this.hasVao) + { + gl.bindVertexArray(vao); + } + else + { + this.activateVao(geometry, shader.program); + } + } + + // TODO - optimise later! + // don't need to loop through if nothing changed! + // maybe look to add an 'autoupdate' to geometry? + this.updateBuffers(); + } + + reset() + { + this.unbind(); + } + + updateBuffers() + { + const geometry = this._activeGeometry; + const gl = this.gl; + + for (let i = 0; i < geometry.buffers.length; i++) + { + const buffer = geometry.buffers[i]; + + const glBuffer = buffer._glBuffers[this.CONTEXT_UID]; + + if (buffer._updateID !== glBuffer.updateID) + { + glBuffer.updateID = buffer._updateID; + + // TODO can cache this on buffer! maybe added a getter / setter? + const type = buffer.index ? gl.ELEMENT_ARRAY_BUFFER : gl.ARRAY_BUFFER; + const drawType = buffer.static ? gl.STATIC_DRAW : gl.DYNAMIC_DRAW; + + gl.bindBuffer(type, glBuffer.buffer); + + if (glBuffer.byteLength >= buffer.data.byteLength) + { + // offset is always zero for now! + gl.bufferSubData(type, 0, buffer.data); + } + else + { + gl.bufferData(type, buffer.data, drawType); + } + } + } + } + + checkCompatability(geometry, program) + { + // geometry must have at least all the attributes that the shader requires. + const geometryAttributes = geometry.attributes; + const shaderAttributes = program.attributeData; + + for (const j in shaderAttributes) + { + if (!geometryAttributes[j]) + { + throw new Error(`shader and geometry incompatible, geometry missing the "${j}" attribute`); + } + } + } + + /** + * Creates a Vao with the same structure as the geometry and stores it on the geometry. + * @private + * @param {PIXI.Geometry} geometry instance of geometry to to generate Vao for + */ + initGeometryVao(geometry, program) + { + this.checkCompatability(geometry, program); + + const gl = this.gl; + const CONTEXT_UID = this.CONTEXT_UID; + const buffers = geometry.buffers; + const attributes = geometry.attributes; + + const tempStride = {}; + const tempStart = {}; + + for (const j in buffers) + { + tempStride[j] = 0; + tempStart[j] = 0; + } + + for (const j in attributes) + { + if (!attributes[j].size && program.attributeData[j]) + { + attributes[j].size = program.attributeData[j].size; + } + + tempStride[attributes[j].buffer] += attributes[j].size * byteSizeMap[attributes[j].type]; + } + + for (const j in attributes) + { + const attribute = attributes[j]; + const attribSize = attribute.size; + + if (attribute.stride === undefined) + { + if (tempStride[attribute.buffer] === attribSize * byteSizeMap[attribute.type]) + { + attribute.stride = 0; + } + else + { + attribute.stride = tempStride[attribute.buffer]; + } + } + + if (attribute.start === undefined) + { + attribute.start = tempStart[attribute.buffer]; + + tempStart[attribute.buffer] += attribSize * byteSizeMap[attribute.type]; + } + } + + // first update - and create the buffers! + // only create a gl buffer if it actually gets + for (let i = 0; i < buffers.length; i++) + { + const buffer = buffers[i]; + + if (!buffer._glBuffers[CONTEXT_UID]) + { + buffer._glBuffers[CONTEXT_UID] = new GLBuffer(gl.createBuffer()); + } + } + + // TODO - maybe make this a data object? + // lets wait to see if we need to first! + const vao = gl.createVertexArray(); + + gl.bindVertexArray(vao); + + this.activateVao(geometry, program); + + gl.bindVertexArray(null); + + // add it to the cache! + geometry.glVertexArrayObjects[this.CONTEXT_UID][program.id] = vao; + + return vao; + } + + activateVao(geometry, program) + { + const gl = this.gl; + const CONTEXT_UID = this.CONTEXT_UID; + const buffers = geometry.buffers; + const attributes = geometry.attributes; + + if (geometry.indexBuffer) + { + // first update the index buffer if we have one.. + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, geometry.indexBuffer._glBuffers[CONTEXT_UID].buffer); + } + + let lastBuffer = null; + + // add a new one! + for (const j in attributes) + { + const attribute = attributes[j]; + const buffer = buffers[attribute.buffer]; + const glBuffer = buffer._glBuffers[CONTEXT_UID]; + + if (program.attributeData[j]) + { + if (lastBuffer !== glBuffer) + { + gl.bindBuffer(gl.ARRAY_BUFFER, glBuffer.buffer); + + lastBuffer = glBuffer; + } + + const location = program.attributeData[j].location; + + // TODO introduce state again + // we can optimise this for older devices that have no VAOs + gl.enableVertexAttribArray(location); + + gl.vertexAttribPointer(location, + attribute.size, + attribute.type || gl.FLOAT, + attribute.normalized, + attribute.stride, + attribute.start); + + if (attribute.instance) + { + // TODO calculate instance count based of this... + if (this.hasInstance) + { + gl.vertexAttribDivisor(location, 1); + } + else + { + throw new Error('geometry error, GPU Instancing is not supported on this device'); + } + } + } + } + } + + draw(type, size, start, instanceCount) + { + const gl = this.gl; + const geometry = this._activeGeometry; + + // TODO.. this should not change so maybe cache the function? + + if (geometry.indexBuffer) + { + if (geometry.instanced) + { + /* eslint-disable max-len */ + gl.drawElementsInstanced(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2, instanceCount || 1); + /* eslint-enable max-len */ + } + else + { + gl.drawElements(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2); + } + } + else + if (geometry.instanced) + { + // TODO need a better way to calculate size.. + gl.drawArraysInstanced(type, start, size || geometry.getSize(), instanceCount || 1); + } + else + { + gl.drawArrays(type, start, size || geometry.getSize()); + } + + return this; + } + + unbind() + { + this.gl.bindVertexArray(null); + this._activeVao = null; + this._activeGeometry = null; + } +} diff --git a/packages/core/src/geometry/utils/setVertexAttribArrays.js b/packages/core/src/geometry/utils/setVertexAttribArrays.js new file mode 100644 index 0000000..23b6959 --- /dev/null +++ b/packages/core/src/geometry/utils/setVertexAttribArrays.js @@ -0,0 +1,54 @@ +// var GL_MAP = {}; + +/** + * @param gl {WebGLRenderingContext} The current WebGL context + * @param attribs {*} + * @param state {*} + */ +export default function setVertexAttribArrays(gl, attribs, state) +{ + let i; + + if (state) + { + const tempAttribState = state.tempAttribState; + const attribState = state.attribState; + + for (i = 0; i < tempAttribState.length; i++) + { + tempAttribState[i] = false; + } + + // set the new attribs + for (i = 0; i < attribs.length; i++) + { + tempAttribState[attribs[i].attribute.location] = true; + } + + for (i = 0; i < attribState.length; i++) + { + if (attribState[i] !== tempAttribState[i]) + { + attribState[i] = tempAttribState[i]; + + if (state.attribState[i]) + { + gl.enableVertexAttribArray(i); + } + else + { + gl.disableVertexAttribArray(i); + } + } + } + } + else + { + for (i = 0; i < attribs.length; i++) + { + const attrib = attribs[i]; + + gl.enableVertexAttribArray(attrib.attribute.location); + } + } +} diff --git a/packages/core/src/index.js b/packages/core/src/index.js index 58c456a..fd1f547 100644 --- a/packages/core/src/index.js +++ b/packages/core/src/index.js @@ -1,30 +1,30 @@ import * as resources from './textures/resources'; -import * as systems from './renderers/systems'; +import * as systems from './systems'; -export { resources }; export { systems }; +export { resources }; -export { default as Renderer } from './renderers/Renderer'; -export { default as AbstractRenderer } from './renderers/AbstractRenderer'; -export { default as FrameBuffer } from './textures/FrameBuffer'; +export { default as System } from './System'; +export { default as Renderer } from './Renderer'; +export { default as AbstractRenderer } from './AbstractRenderer'; +export { default as FrameBuffer } from './framebuffer/FrameBuffer'; export { default as CubeTexture } from './textures/CubeTexture'; export { default as BaseTexture } from './textures/BaseTexture'; export { default as Texture } from './textures/Texture'; export { default as TextureMatrix } from './textures/TextureMatrix'; -export { default as RenderTexture } from './textures/RenderTexture'; -export { default as BaseRenderTexture } from './textures/BaseRenderTexture'; +export { default as RenderTexture } from './renderTexture/RenderTexture'; +export { default as BaseRenderTexture } from './renderTexture/BaseRenderTexture'; export { default as TextureUvs } from './textures/TextureUvs'; -export { default as State } from './renderers/State'; -export { default as ObjectRenderer } from './renderers/utils/ObjectRenderer'; -export { default as Quad } from './renderers/utils/Quad'; -export { default as QuadUv } from './renderers/utils/QuadUv'; -export { default as checkMaxIfStatmentsInShader } from './renderers/utils/checkMaxIfStatmentsInShader'; +export { default as State } from './state/State'; +export { default as ObjectRenderer } from './batch/ObjectRenderer'; +export { default as Quad } from './utils/Quad'; +export { default as QuadUv } from './utils/QuadUv'; +export { default as checkMaxIfStatmentsInShader } from './shader/utils/checkMaxIfStatmentsInShader'; export { default as Shader } from './shader/Shader'; export { default as Program } from './shader/Program'; export { default as UniformGroup } from './shader/UniformGroup'; -export { default as SpriteMaskFilter } from './renderers/filters/spriteMask/SpriteMaskFilter'; -export { default as Filter } from './renderers/filters/Filter'; +export { default as SpriteMaskFilter } from './filters/spriteMask/SpriteMaskFilter'; +export { default as Filter } from './filters/Filter'; export { default as Attribute } from './geometry/Attribute'; export { default as Buffer } from './geometry/Buffer'; export { default as Geometry } from './geometry/Geometry'; - diff --git a/packages/core/src/mask/MaskSystem.js b/packages/core/src/mask/MaskSystem.js new file mode 100644 index 0000000..99174c2 --- /dev/null +++ b/packages/core/src/mask/MaskSystem.js @@ -0,0 +1,199 @@ +import System from '../System'; +import AlphaMaskFilter from '../filters/spriteMask/SpriteMaskFilter'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class MaskSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + // TODO - we don't need both! + this.scissor = false; + this.scissorData = null; + this.scissorRenderTarget = null; + + this.enableScissor = false; + + this.alphaMaskPool = []; + this.alphaMaskIndex = 0; + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.DisplayObject} target - Display Object to push the mask to + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + push(target, maskData) + { + // TODO the root check means scissor rect will not + // be used on render textures more info here: + // https://github.com/pixijs/pixi.js/pull/3545 + + if (maskData.texture) + { + this.pushSpriteMask(target, maskData); + } + else if (this.enableScissor + && !this.scissor + && this.renderer._activeRenderTarget.root + && !this.renderer.stencil.stencilMaskStack.length + && maskData.isFastRect()) + { + const matrix = maskData.worldTransform; + + let rot = Math.atan2(matrix.b, matrix.a); + + // use the nearest degree! + rot = Math.round(rot * (180 / Math.PI)); + + if (rot % 90) + { + this.pushStencilMask(maskData); + } + else + { + this.pushScissorMask(target, maskData); + } + } + else + { + this.pushStencilMask(maskData); + } + } + + /** + * Removes the last mask from the mask stack and doesn't return it. + * + * @param {PIXI.DisplayObject} target - Display Object to pop the mask from + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + pop(target, maskData) + { + if (maskData.texture) + { + this.popSpriteMask(target, maskData); + } + else if (this.enableScissor && !this.renderer.stencil.stencilMaskStack.length) + { + this.popScissorMask(target, maskData); + } + else + { + this.popStencilMask(target, maskData); + } + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.RenderTarget} target - Display Object to push the sprite mask to + * @param {PIXI.Sprite} maskData - Sprite to be used as the mask + */ + pushSpriteMask(target, maskData) + { + let alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; + + if (!alphaMaskFilter) + { + alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; + } + + alphaMaskFilter[0].resolution = this.renderer.resolution; + alphaMaskFilter[0].maskSprite = maskData; + + // TODO - may cause issues! + target.filterArea = maskData.getBounds(true); + + this.renderer.filterSystem.pushFilter(target, alphaMaskFilter); + + this.alphaMaskIndex++; + } + + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popSpriteMask() + { + this.renderer.filterSystem.popFilter(); + this.alphaMaskIndex--; + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + pushStencilMask(maskData) + { + this.renderer.batch.flush(); + this.renderer.stencil.pushStencil(maskData); + } + + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popStencilMask() + { + // this.renderer.currentRenderer.stop(); + this.renderer.stencil.popStencil(); + } + + /** + * + * @param {PIXI.DisplayObject} target - Display Object to push the mask to + * @param {PIXI.Graphics} maskData - The masking data. + */ + pushScissorMask(target, maskData) + { + maskData.renderable = true; + + const renderTarget = this.renderer._activeRenderTarget; + + const bounds = maskData.getBounds(); + + bounds.fit(renderTarget.size); + maskData.renderable = false; + + this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); + + const resolution = this.renderer.resolution; + + this.renderer.gl.scissor( + bounds.x * resolution, + (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, + bounds.width * resolution, + bounds.height * resolution + ); + + this.scissorRenderTarget = renderTarget; + this.scissorData = maskData; + this.scissor = true; + } + + /** + * + * + */ + popScissorMask() + { + this.scissorRenderTarget = null; + this.scissorData = null; + this.scissor = false; + + // must be scissor! + const gl = this.renderer.gl; + + gl.disable(gl.SCISSOR_TEST); + } +} diff --git a/packages/core/src/mask/StencilSystem.js b/packages/core/src/mask/StencilSystem.js new file mode 100644 index 0000000..62796a5 --- /dev/null +++ b/packages/core/src/mask/StencilSystem.js @@ -0,0 +1,133 @@ +import System from '../System'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class StencilSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + this.stencilMaskStack = []; + } + + /** + * Changes the mask stack that is used by this System. + * + * @param {PIXI.Graphics[]} stencilMaskStack - The mask stack + */ + setMaskStack(stencilMaskStack) + { + const gl = this.renderer.gl; + + if (stencilMaskStack.length !== this.stencilMaskStack.length) + { + if (stencilMaskStack.length === 0) + { + gl.disable(gl.STENCIL_TEST); + } + else + { + gl.enable(gl.STENCIL_TEST); + } + } + + this.stencilMaskStack = stencilMaskStack; + } + + /** + * Applies the Mask and adds it to the current stencil stack. @alvin + * + * @param {PIXI.Graphics} graphics - The mask + */ + pushStencil(graphics) + { + this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); + + // this.renderer._activeRenderTarget.attachStencilBuffer(); + + const gl = this.renderer.gl; + const prevMaskCount = this.stencilMaskStack.length; + + if (prevMaskCount === 0) + { + gl.enable(gl.STENCIL_TEST); + } + + this.stencilMaskStack.push(graphics); + + // Increment the refference stencil value where the new mask overlaps with the old ones. + gl.colorMask(false, false, false, false); + gl.stencilFunc(gl.EQUAL, prevMaskCount, this._getBitwiseMask()); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.INCR); + this.renderer.plugins.graphics.render(graphics); + + this._useCurrent(); + } + + /** + * Removes the last mask from the stencil stack. @alvin + */ + popStencil() + { + this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); + + const gl = this.renderer.gl; + const graphics = this.stencilMaskStack.pop(); + + if (this.stencilMaskStack.length === 0) + { + // the stack is empty! + gl.disable(gl.STENCIL_TEST); + gl.clear(gl.STENCIL_BUFFER_BIT); + gl.clearStencil(0); + } + else + { + // Decrement the refference stencil value where the popped mask overlaps with the other ones + gl.colorMask(false, false, false, false); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.DECR); + this.renderer.plugins.graphics.render(graphics); + + this._useCurrent(); + } + } + + /** + * Setup renderer to use the current stencil data. + */ + _useCurrent() + { + const gl = this.renderer.gl; + + gl.colorMask(true, true, true, true); + gl.stencilFunc(gl.EQUAL, this.stencilMaskStack.length, this._getBitwiseMask()); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP); + } + + /** + * Fill 1s equal to the number of acitve stencil masks. + * + * @return {number} The bitwise mask. + */ + _getBitwiseMask() + { + return (1 << this.stencilMaskStack.length) - 1; + } + + /** + * Destroys the mask stack. + * + */ + destroy() + { + super.destroy(this); + + this.stencilMaskStack = null; + } +} diff --git a/packages/core/src/projection/ProjectionSystem.js b/packages/core/src/projection/ProjectionSystem.js new file mode 100644 index 0000000..6e63b73 --- /dev/null +++ b/packages/core/src/projection/ProjectionSystem.js @@ -0,0 +1,73 @@ +import System from '../System'; +import { Matrix } from '@pixi/math'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class ProjectionSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.projectionMatrix = new Matrix(); + } + + update(destinationFrame, sourceFrame, resolution, root) + { + this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; + this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; + + this.calculateProjection(this.destinationFrame, this.sourceFrame, resolution, root); + + this.renderer.globalUniforms.uniforms.projectionMatrix = this.projectionMatrix; + this.renderer.globalUniforms.update(); + } + + /** + * Updates the projection matrix based on a projection frame (which is a rectangle) + * + * @param {Rectangle} destinationFrame - The destination frame. + * @param {Rectangle} sourceFrame - The source frame. + */ + calculateProjection(destinationFrame, sourceFrame, resolution, root) + { + const pm = this.projectionMatrix; + + // I don't think we will need this line.. + // pm.identity(); + + if (!root) + { + pm.a = (1 / destinationFrame.width * 2) * resolution; + pm.d = (1 / destinationFrame.height * 2) * resolution; + + pm.tx = -1 - (sourceFrame.x * pm.a); + pm.ty = -1 - (sourceFrame.y * pm.d); + } + else + { + pm.a = (1 / destinationFrame.width * 2) * resolution; + pm.d = (-1 / destinationFrame.height * 2) * resolution; + + pm.tx = -1 - (sourceFrame.x * pm.a); + pm.ty = 1 - (sourceFrame.y * pm.d); + } + } + + /** + * Sets the transform of the active render target to the given matrix + * + * @param {PIXI.Matrix} matrix - The transformation matrix + */ + setTransform()// matrix) + { + // this._activeRenderTarget.transform = matrix; + } +} diff --git a/packages/core/src/renderTexture/BaseRenderTexture.js b/packages/core/src/renderTexture/BaseRenderTexture.js new file mode 100644 index 0000000..88eb83b --- /dev/null +++ b/packages/core/src/renderTexture/BaseRenderTexture.js @@ -0,0 +1,137 @@ +import BaseTexture from '../textures/BaseTexture'; +import FrameBuffer from '../framebuffer/FrameBuffer'; + +/** + * A BaseRenderTexture is a special texture that allows any PixiJS display object to be rendered to it. + * + * __Hint__: All DisplayObjects (i.e. Sprites) that render to a BaseRenderTexture should be preloaded + * otherwise black rectangles will be drawn instead. + * + * A BaseRenderTexture takes a snapshot of any Display Object given to its render method. The position + * and rotation of the given Display Objects is ignored. For example: + * + * ```js + * let renderer = PIXI.autoDetectRenderer(1024, 1024, { view: canvas, ratio: 1 }); + * let baseRenderTexture = new PIXI.BaseRenderTexture(renderer, 800, 600); + * let sprite = PIXI.Sprite.fromImage("spinObj_01.png"); + * + * sprite.position.x = 800/2; + * sprite.position.y = 600/2; + * sprite.anchor.x = 0.5; + * sprite.anchor.y = 0.5; + * + * baseRenderTexture.render(sprite); + * ``` + * + * The Sprite in this case will be rendered using its local transform. To render this sprite at 0,0 + * you can clear the transform + * + * ```js + * + * sprite.setTransform() + * + * let baseRenderTexture = new PIXI.BaseRenderTexture(100, 100); + * let renderTexture = new PIXI.RenderTexture(baseRenderTexture); + * + * renderer.render(sprite, renderTexture); // Renders to center of RenderTexture + * ``` + * + * @class + * @extends PIXI.BaseTexture + * @memberof PIXI + */ +export default class BaseRenderTexture extends BaseTexture +{ + /** + * @param {object} [options] + * @param {number} [options.width=100] - The width of the base render texture + * @param {number} [options.height=100] - The height of the base render texture + * @param {PIXI.SCALE_MODES} [options.scaleMode] - See {@link PIXI.SCALE_MODES} for possible values + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the texture being generated + */ + constructor(options) + { + if (typeof options === 'number') + { + /* eslint-disable prefer-rest-params */ + // Backward compatibility of signature + const width = arguments[0]; + const height = arguments[1]; + const scaleMode = arguments[2]; + const resolution = arguments[3]; + + options = { width, height, scaleMode, resolution }; + /* eslint-enable prefer-rest-params */ + } + + super(null, options); + + const { width, height } = options || {}; + + // Set defaults + this.mipmap = false; + this.width = Math.ceil(width) || 100; + this.height = Math.ceil(height) || 100; + this.valid = true; + + /** + * A map of renderer IDs to webgl renderTargets + * + * @private + * @member {object} + */ + // this._glRenderTargets = {}; + + /** + * A reference to the canvas render target (we only need one as this can be shared across renderers) + * + * @private + * @member {object} + */ + this._canvasRenderTarget = null; + + this.clearColor = [0, 0, 0, 0]; + + this.frameBuffer = new FrameBuffer(width * this.resolution, height * this.resolution) + .addColorTexture(0, this); + + // TODO - could this be added the systems? + + /** + * The data structure for the stencil masks + * + * @member {PIXI.Graphics[]} + */ + this.stencilMaskStack = []; + + /** + * The data structure for the filters + * + * @member {PIXI.Graphics[]} + */ + this.filterStack = [{}]; + } + + /** + * Resizes the BaseRenderTexture. + * + * @param {number} width - The width to resize to. + * @param {number} height - The height to resize to. + */ + resize(width, height) + { + width = Math.ceil(width); + height = Math.ceil(height); + this.frameBuffer.resize(width * this.resolution, height * this.resolution); + } + + /** + * Destroys this texture + * + */ + destroy() + { + super.destroy(true); + this.renderer = null; + } +} diff --git a/packages/core/src/renderTexture/RenderTexture.js b/packages/core/src/renderTexture/RenderTexture.js new file mode 100644 index 0000000..f1ecf1f --- /dev/null +++ b/packages/core/src/renderTexture/RenderTexture.js @@ -0,0 +1,153 @@ +import BaseRenderTexture from './BaseRenderTexture'; +import Texture from '../textures/Texture'; + +/** + * A RenderTexture is a special texture that allows any PixiJS display object to be rendered to it. + * + * __Hint__: All DisplayObjects (i.e. Sprites) that render to a RenderTexture should be preloaded + * otherwise black rectangles will be drawn instead. + * + * A RenderTexture takes a snapshot of any Display Object given to its render method. For example: + * + * ```js + * let renderer = PIXI.autoDetectRenderer(1024, 1024, { view: canvas, ratio: 1 }); + * let renderTexture = PIXI.RenderTexture.create(800, 600); + * let sprite = PIXI.Sprite.fromImage("spinObj_01.png"); + * + * sprite.position.x = 800/2; + * sprite.position.y = 600/2; + * sprite.anchor.x = 0.5; + * sprite.anchor.y = 0.5; + * + * renderer.render(sprite, renderTexture); + * ``` + * + * The Sprite in this case will be rendered using its local transform. To render this sprite at 0,0 + * you can clear the transform + * + * ```js + * + * sprite.setTransform() + * + * let renderTexture = new PIXI.RenderTexture.create(100, 100); + * + * renderer.render(sprite, renderTexture); // Renders to center of RenderTexture + * ``` + * + * @class + * @extends PIXI.Texture + * @memberof PIXI + */ +export default class RenderTexture extends Texture +{ + /** + * @param {PIXI.BaseRenderTexture} baseRenderTexture - The renderer used for this RenderTexture + * @param {PIXI.Rectangle} [frame] - The rectangle frame of the texture to show + */ + constructor(baseRenderTexture, frame) + { + // support for legacy.. + let _legacyRenderer = null; + + if (!(baseRenderTexture instanceof BaseRenderTexture)) + { + /* eslint-disable prefer-rest-params, no-console */ + const width = arguments[1]; + const height = arguments[2]; + const scaleMode = arguments[3]; + const resolution = arguments[4]; + + // we have an old render texture.. + console.warn(`Please use RenderTexture.create(${width}, ${height}) instead of the ctor directly.`); + _legacyRenderer = arguments[0]; + /* eslint-enable prefer-rest-params, no-console */ + + frame = null; + baseRenderTexture = new BaseRenderTexture({ + width, + height, + scaleMode, + resolution, + }); + } + + /** + * The base texture object that this texture uses + * + * @member {BaseTexture} + */ + super(baseRenderTexture, frame); + + this.legacyRenderer = _legacyRenderer; + + /** + * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. + * + * @member {boolean} + */ + this.valid = true; + + /** + * FilterSystem temporary storage + * @private + * @member {PIXI.Rectangle} + */ + this.filterFrame = null; + + this._updateUvs(); + } + + /** + * Resizes the RenderTexture. + * + * @param {number} width - The width to resize to. + * @param {number} height - The height to resize to. + * @param {boolean} [resizeBaseTexture=true] - Should the baseTexture.width and height values be resized as well? + */ + resize(width, height, resizeBaseTexture = true) + { + width = Math.ceil(width); + height = Math.ceil(height); + + // TODO - could be not required.. + this.valid = (width > 0 && height > 0); + + this._frame.width = this.orig.width = width; + this._frame.height = this.orig.height = height; + + if (resizeBaseTexture) + { + this.baseTexture.resize(width, height); + } + + this._updateUvs(); + } + + /** + * A short hand way of creating a render texture. + * + * @param {object} [options] - Options + * @param {number} [options.width=100] - The width of the render texture + * @param {number} [options.height=100] - The height of the render texture + * @param {number} [options.scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the texture being generated + * @return {PIXI.RenderTexture} The new render texture + */ + static create(options) + { + // fallback, old-style: create(width, height, scaleMode, resolution) + if (typeof options === 'number') + { + /* eslint-disable prefer-rest-params */ + options = { + width: options, + height: arguments[1], + scaleMode: arguments[2], + resolution: arguments[3], + }; + /* eslint-enable prefer-rest-params */ + } + + return new RenderTexture(new BaseRenderTexture(options)); + } +} diff --git a/packages/core/src/renderTexture/RenderTextureSystem.js b/packages/core/src/renderTexture/RenderTextureSystem.js new file mode 100644 index 0000000..8d0ca61 --- /dev/null +++ b/packages/core/src/renderTexture/RenderTextureSystem.js @@ -0,0 +1,115 @@ +import System from '../System'; +import { Rectangle } from '@pixi/math'; + +const tempRect = new Rectangle(); + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class RenderTextureSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.clearColor = renderer._backgroundColorRgba; + + // TODO moe this property somewhere else! + this.defaultMaskStack = []; + this.defaultFilterStack = [{}]; + + // empty render texture? + this.renderTexture = null; + + this.destinationFrame = new Rectangle(); + } + + bind(renderTexture, sourceFrame, destinationFrame) + { + // TODO - do we want this?? + if (this.renderTexture === renderTexture) return; + this.renderTexture = renderTexture; + + const renderer = this.renderer; + + if (renderTexture) + { + const baseTexture = renderTexture.baseTexture; + + if (!destinationFrame) + { + tempRect.width = baseTexture.realWidth; + tempRect.height = baseTexture.realHeight; + + destinationFrame = tempRect; + } + + if (!sourceFrame) + { + sourceFrame = destinationFrame; + } + + this.renderer.framebuffer.bind(baseTexture.frameBuffer, destinationFrame); + + this.renderer.projection.update(destinationFrame, sourceFrame, baseTexture.resolution, false); + this.renderer.stencil.setMaskStack(baseTexture.stencilMaskStack); + } + else + { + // TODO these validation checks happen deeper down.. + // thing they can be avoided.. + if (!destinationFrame) + { + tempRect.width = renderer.width; + tempRect.height = renderer.height; + + destinationFrame = tempRect; + } + + if (!sourceFrame) + { + sourceFrame = destinationFrame; + } + + renderer.framebuffer.bind(null, destinationFrame); + + // TODO store this.. + this.renderer.projection.update(destinationFrame, sourceFrame, this.renderer.resolution, true); + this.renderer.stencil.setMaskStack(this.defaultMaskStack); + } + + this.destinationFrame.copyFrom(destinationFrame); + } + + /** + * Erases the render texture and fills the drawing area with a colour + * + * @param {number} [clearColor] - The colour + * @return {PIXI.Renderer} Returns itself. + */ + clear(clearColor) + { + if (this.renderTexture) + { + clearColor = clearColor || this.renderTexture.baseTexture.clearColor; + } + else + { + clearColor = clearColor || this.clearColor; + } + + this.renderer.framebuffer.clear(clearColor[0], clearColor[1], clearColor[2], clearColor[3]); + } + + resize()// screenWidth, screenHeight) + { + // resize the root only! + this.bind(null); + } +} diff --git a/packages/core/src/renderers/AbstractRenderer.js b/packages/core/src/renderers/AbstractRenderer.js deleted file mode 100644 index 928670f..0000000 --- a/packages/core/src/renderers/AbstractRenderer.js +++ /dev/null @@ -1,353 +0,0 @@ -import { hex2string, hex2rgb } from '@pixi/utils'; -import { Matrix, Rectangle } from '@pixi/math'; -import { RENDERER_TYPE } from '@pixi/constants'; -import { settings } from '@pixi/settings'; -import { Container } from '@pixi/display'; -import RenderTexture from '../textures/RenderTexture'; -import EventEmitter from 'eventemitter3'; - -const tempMatrix = new Matrix(); - -/** - * 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 EventEmitter - * @memberof PIXI - */ -export default class AbstractRenderer extends EventEmitter -{ - // eslint-disable-next-line valid-jsdoc - /** - * @param {string} system - The name of the system this renderer is for. - * @param {object} [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.transparent=false] - If the render view is transparent, default false - * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false - * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) - * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. The - * resolution of the renderer retina would be 2. - * @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 {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when rendering, - * stopping pixel interpolation. - */ - constructor(system, options, arg2, arg3) - { - super(); - - // Support for constructor(system, screenWidth, screenHeight, options) - if (typeof options === 'number') - { - options = Object.assign({ - width: options, - height: arg2 || settings.RENDER_OPTIONS.height, - }, arg3); - } - - // 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 = RENDERER_TYPE.UNKNOWN; - - /** - * Measurements of the screen. (0, 0, screenWidth, screenHeight) - * - * Its safe to use as filterArea or hitArea for 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 1 - */ - this.resolution = options.resolution || settings.RESOLUTION; - - /** - * Whether the render view is transparent - * - * @member {boolean} - */ - this.transparent = options.transparent; - - /** - * Whether css dimensions of canvas view should be resized to screen dimensions automatically - * - * @member {boolean} - */ - this.autoResize = options.autoResize || false; - - /** - * Tracks the blend modes useful for this renderer. - * - * @member {object} - */ - this.blendModes = null; - - /** - * 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; - - /** - * If true PixiJS will Math.floor() x/y values when rendering, stopping pixel interpolation. - * Handy for crisp pixel art and speed on legacy devices. - * - * @member {boolean} - */ - this.roundPixels = options.roundPixels; - - /** - * The background color as a number. - * - * @member {number} - * @private - */ - this._backgroundColor = 0x000000; - - /** - * The background color as an [R, G, B] array. - * - * @member {number[]} - * @private - */ - this._backgroundColorRgba = [0, 0, 0, 0]; - - /** - * The background color as a string. - * - * @member {string} - * @private - */ - this._backgroundColorString = '#000000'; - - this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter - - /** - * This temporary display object used as the parent of the currently being rendered item - * - * @member {PIXI.DisplayObject} - * @private - */ - this._tempDisplayObjectParent = new Container(); - - /** - * The last root object that the renderer tried to render. - * - * @member {PIXI.DisplayObject} - * @private - */ - this._lastObjectRendered = this._tempDisplayObjectParent; - - /** - * Collection of plugins. - * @readonly - * @member {object} - */ - this.plugins = {}; - } - - /** - * Initialize the plugins. - * - * @protected - * @param {object} staticMap - The dictionary of staticly saved plugins. - */ - initPlugins(staticMap) - { - 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() - { - return this.view.width; - } - - /** - * Same as view.height, actual number of pixels in the canvas by vertical - * - * @member {number} - * @readonly - * @default 600 - */ - get height() - { - return this.view.height; - } - - /** - * Resizes the screen and canvas to the specified width and height - * Canvas dimensions are multiplied by resolution - * - * @param {number} screenWidth - the new width of the screen - * @param {number} screenHeight - the new height of the screen - */ - resize(screenWidth, screenHeight) - { - this.screen.width = screenWidth; - this.screen.height = screenHeight; - - this.view.width = screenWidth * this.resolution; - this.view.height = screenHeight * this.resolution; - - if (this.autoResize) - { - this.view.style.width = `${screenWidth}px`; - this.view.style.height = `${screenHeight}px`; - } - } - - /** - * 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 {PIXI.DisplayObject} displayObject - The displayObject the object will be generated from - * @param {number} scaleMode - Should be one of the scaleMode consts - * @param {number} resolution - The resolution / device pixel ratio of the texture being generated - * @param {PIXI.Rectangle} [region] - The region of the displayObject, that shall be rendered, - * if no region is specified, defaults to the local bounds of the displayObject. - * @return {PIXI.Texture} a texture of the graphics object - */ - generateTexture(displayObject, scaleMode, resolution, region) - { - region = region || displayObject.getLocalBounds(); - - // 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(region.width | 0, region.height | 0, scaleMode, resolution); - - tempMatrix.tx = -region.x; - tempMatrix.ty = -region.y; - - this.render(displayObject, renderTexture, false, tempMatrix, true); - - return renderTexture; - } - - /** - * Removes everything from the renderer and optionally removes the Canvas DOM element. - * - * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. - */ - destroy(removeView) - { - 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); - } - - this.plugins = null; - - this.type = RENDERER_TYPE.UNKNOWN; - - this.view = null; - - this.screen = null; - - this.resolution = 0; - - this.transparent = false; - - this.autoResize = false; - - this.blendModes = null; - - this.options = null; - - this.preserveDrawingBuffer = false; - this.clearBeforeRender = false; - - this.roundPixels = false; - - this._backgroundColor = 0; - this._backgroundColorRgba = null; - this._backgroundColorString = null; - - this._tempDisplayObjectParent = null; - this._lastObjectRendered = null; - } - - /** - * The background color to fill if not transparent - * - * @member {number} - */ - get backgroundColor() - { - return this._backgroundColor; - } - - set backgroundColor(value) // eslint-disable-line require-jsdoc - { - this._backgroundColor = value; - this._backgroundColorString = hex2string(value); - hex2rgb(value, this._backgroundColorRgba); - } -} diff --git a/packages/core/src/renderers/Renderer.js b/packages/core/src/renderers/Renderer.js deleted file mode 100644 index a2020b1..0000000 --- a/packages/core/src/renderers/Renderer.js +++ /dev/null @@ -1,329 +0,0 @@ -import AbstractRenderer from './AbstractRenderer'; -import { sayHello } from '@pixi/utils'; -import MaskSystem from './systems/MaskSystem'; -import StencilSystem from './systems/StencilSystem'; -import FilterSystem from './systems/filter/FilterSystem'; -import FramebufferSystem from './systems/FramebufferSystem'; -import RenderTextureSystem from './systems/RenderTextureSystem'; -import TextureSystem from './systems/textures/TextureSystem'; -import ProjectionSystem from './systems/ProjectionSystem'; -import StateSystem from './systems/StateSystem'; -import GeometrySystem from './systems/geometry/GeometrySystem'; -import ShaderSystem from './systems/shader/ShaderSystem'; -import ContextSystem from './systems/ContextSystem'; -import BatchSystem from './systems/BatchSystem'; -import TextureGCSystem from './systems/textures/TextureGCSystem'; -import { RENDERER_TYPE } from '@pixi/constants'; -import UniformGroup from '../shader/UniformGroup'; -import { Matrix } from '@pixi/math'; -import Runner from 'mini-runner'; - -/** - * 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 Render works by automatically managing webGLBatchs. - * 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 :) - * - * @class - * @memberof PIXI - * @extends PIXI.AbstractRenderer - */ -export default class Renderer extends AbstractRenderer -{ - // eslint-disable-next-line valid-jsdoc - /** - * - * @param {object} [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.transparent=false] - If the render view is transparent, default false - * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false - * @param {boolean} [options.antialias=false] - sets antialias. If not available natively then FXAA - * antialiasing is used - * @param {boolean} [options.forceFXAA=false] - forces FXAA antialiasing to be used over native. - * FXAA is faster, but may not always look as great - * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. - * The resolution of the renderer retina would be 2. - * @param {boolean} [options.clearBeforeRender=true] - This sets if the renderer will clear - * the canvas or not before the new render pass. If you wish to set this to false, you *must* set - * preserveDrawingBuffer to `true`. - * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, - * enable this if you need to call toDataUrl on the webgl context. - * @param {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when - * rendering, stopping pixel interpolation. - * @param {number} [options.backgroundColor=0x000000] - The background color of the rendered area - * (shown if not transparent). - * @param {boolean} [options.legacy=false] - If true PixiJS will aim to ensure compatibility - * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. - * @param {string} [options.powerPreference] - Parameter passed to webgl context, set to "high-performance" - * for devices with dual graphics card - */ - constructor(options, arg2, arg3) - { - super('WebGL', options, arg2, arg3); - - /** - * The type of this renderer as a standardised const - * - * @member {number} - * @see PIXI.RENDERER_TYPE - */ - this.type = RENDERER_TYPE.WEBGL; - - // this will be set by the contextSystem (this.context) - this.gl = null; - this.CONTEXT_UID = 0; - this.legacy = !!options.legacy; - - // TODO legacy! - - // runners! - this.runners = { - destroy: new Runner('destroy'), - contextChange: new Runner('contextChange', 1), - reset: new Runner('reset'), - update: new Runner('update'), - postrender: new Runner('postrender'), - prerender: new Runner('prerender'), - resize: new Runner('resize', 2), - }; - - 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(GeometrySystem, 'geometry') - .addSystem(FramebufferSystem, 'framebuffer') - .addSystem(StencilSystem, 'stencil') - .addSystem(ProjectionSystem, 'projection') - .addSystem(TextureGCSystem, 'textureGC') - .addSystem(FilterSystem, 'filter') - .addSystem(RenderTextureSystem, 'renderTexture') - .addSystem(BatchSystem, 'batch'); - - this.initPlugins(Renderer.__plugins); - - /** - * The options passed in to create a new webgl context. - * - * @member {object} - * @private - */ - if (options.context) - { - this.context.initFromContext(options.context); - } - else - { - this.context.initFromOptions({ - alpha: this.transparent, - antialias: options.antialias, - premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', - stencil: true, - 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); - } - - addSystem(_class, name) - { - if (!name) - { - name = _class.name; - } - - // TODO - read name from class.name.. - - /* - if(name.includes('System')) - { - name = name.replace('System', ''); - name = name.charAt(0).toLowerCase() + name.slice(1); - } - */ - - const system = new _class(this); - - if (this[name]) - { - throw new Error(`Whoops! ${name} is already a manger`); - } - - this[name] = system; - - for (const i in this.runners) - { - this.runners[i].add(system); - } - - return this; - - /** - * 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. - */ - } - - /** - * Renders the object to its webGL view - * - * @param {PIXI.DisplayObject} displayObject - the object to be rendered - * @param {PIXI.RenderTexture} renderTexture - The render texture to render to. - * @param {boolean} [clear] - Should the canvas be cleared before the new render - * @param {PIXI.Transform} [transform] - A transform to apply to the render texture before rendering. - * @param {boolean} [skipUpdateTransform] - Should we skip the update transform pass? - */ - render(displayObject, renderTexture, clear, transform, skipUpdateTransform) - { - // can be handy to know! - this.renderingToScreen = !renderTexture; - - this.runners.prerender.run(); - this.emit('prerender'); - - // 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.parent; - - displayObject.parent = this._tempDisplayObjectParent; - displayObject.updateTransform(); - displayObject.parent = 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.run(); - - this.emit('postrender'); - } - - /** - * Resizes the webGL view to the specified width and height. - * - * @param {number} screenWidth - the new width of the screen - * @param {number} screenHeight - the new height of the screen - */ - resize(screenWidth, screenHeight) - { - AbstractRenderer.prototype.resize.call(this, screenWidth, screenHeight); - - this.runners.resize.run(screenWidth, screenHeight); - } - - /** - * Resets the WebGL state so you can render things however you fancy! - * - * @return {PIXI.Renderer} Returns itself. - */ - reset() - { - this.runners.reset.run(); - - return this; - } - - clear() - { - this.framebuffer.bind(); - this.framebuffer.clear(); - } - - /** - * Removes everything from the renderer (event listeners, spritebatch, etc...) - * - * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. - * See: https://github.com/pixijs/pixi.js/issues/2233 - */ - destroy(removeView) - { - this.runners.destroy.run(); - - // call base destroy - super.destroy(removeView); - - // TODO nullify all the managers.. - this.gl = null; - } - - /** - * 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 - * builds and excluding plugins. - * @name PIXI.Renderer#plugins - * @type {object} - * @readonly - * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. - * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. - * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. - * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. - */ - - /** - * Adds a plugin to the renderer. - * - * @method - * @param {string} pluginName - The name of the plugin. - * @param {Function} ctor - The constructor function or class for the plugin. - */ - static registerPlugin(pluginName, ctor) - { - Renderer.__plugins = Renderer.__plugins || {}; - Renderer.__plugins[pluginName] = ctor; - } -} diff --git a/packages/core/src/renderers/State.js b/packages/core/src/renderers/State.js deleted file mode 100644 index fa3f221..0000000 --- a/packages/core/src/renderers/State.js +++ /dev/null @@ -1,163 +0,0 @@ -/* eslint-disable max-len */ - -const BLEND = 0; -const OFFSET = 1; -const CULLING = 2; -const DEPTH_TEST = 3; -const WINDING = 4; - -/** - * This is a webGL state. It is passed The WebGL StateManager. - * Each mesh renderered may require webGL to be in a different state. - * For example you may want different blend mode or to enable polygon offsets - * - * @class - * @memberof PIXI - */ -export default class State -{ - /** - * - */ - constructor() - { - this.data = 0; - - this.blendMode = 0; - this.polygonOffset = 0; - - this.blend = true; - // this.depthTest = true; - } - - /** - * Activates blending of the computed fragment color values - * - * @member {boolean} - */ - get blend() - { - return !!(this.data & (1 << BLEND)); - } - - set blend(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << BLEND)) !== value) - { - this.data ^= (1 << BLEND); - } - } - - /** - * Activates adding an offset to depth values of polygon's fragments - * - * @member {boolean} - * @default false - */ - get offsets() - { - return !!(this.data & (1 << OFFSET)); - } - - set offsets(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << OFFSET)) !== value) - { - this.data ^= (1 << OFFSET); - } - } - - /** - * Activates culling of polygons. - * - * @member {boolean} - * @default false - */ - get culling() - { - return !!(this.data & (1 << CULLING)); - } - - set culling(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << CULLING)) !== value) - { - this.data ^= (1 << CULLING); - } - } - - /** - * Activates depth comparisons and updates to the depth buffer. - * - * @member {boolean} - * @default false - */ - get depthTest() - { - return !!(this.data & (1 << DEPTH_TEST)); - } - - set depthTest(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << DEPTH_TEST)) !== value) - { - this.data ^= (1 << DEPTH_TEST); - } - } - - /** - * Specifies whether or not front or back-facing polygons can be culled. - * @member {boolean} - * @default false - */ - get clockwiseFrontFace() - { - return !!(this.data & (1 << WINDING)); - } - - set clockwiseFrontFace(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << WINDING)) !== value) - { - this.data ^= (1 << WINDING); - } - } - - /** - * The blend mode to be applied when this state is set. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. - * Setting this mode to anything other than NO_BLEND will automatically switch blending on. - * - * @member {boolean} - * @default PIXI.BLEND_MODES.NORMAL - * @see PIXI.BLEND_MODES - */ - get blendMode() - { - return this._blendMode; - } - - set blendMode(value) // eslint-disable-line require-jsdoc - { - // 17 is NO BLEND - this.blend = (value !== 17); - this._blendMode = value; - } - - /** - * The polygon offset. Setting this property to anything other than 0 will automatically enable poygon offset fill. - * - * @member {number} - * @default 0 - */ - get polygonOffset() - { - return this._polygonOffset; - } - - set polygonOffset(value) // eslint-disable-line require-jsdoc - { - this.offsets = !!value; - this._polygonOffset = value; - } -} - diff --git a/packages/core/src/renderers/filters/Filter.js b/packages/core/src/renderers/filters/Filter.js deleted file mode 100644 index 2073b8d..0000000 --- a/packages/core/src/renderers/filters/Filter.js +++ /dev/null @@ -1,140 +0,0 @@ -import Shader from '../../shader/Shader'; -import Program from '../../shader/Program'; -import State from '../State'; -import { settings } from '@pixi/settings'; -// import extractUniformsFromSrc from './extractUniformsFromSrc'; -import defaultVertex from './defaultFilter.vert'; -import defaultFragment from './defaultFilter.frag'; - -// let math = require('../../math'); -/** - * @class - * @memberof PIXI - * @extends PIXI.Shader - */ -export default class Filter extends Shader -{ - /** - * @param {string} [vertexSrc] - The source of the vertex shader. - * @param {string} [fragmentSrc] - The source of the fragment shader. - * @param {object} [uniforms] - Custom uniforms to use to augment the built-in ones. - */ - constructor(vertexSrc, fragmentSrc, uniforms) - { - const program = Program.from(vertexSrc, fragmentSrc); - - super(program, uniforms); - - /** - * The padding of the filter. Some filters require extra space to breath such as a blur. - * Increasing this will add extra width and height to the bounds of the object that the - * filter is applied to. - * - * @member {number} - */ - this.padding = 0; - - /** - * The resolution of the filter. Setting this to be lower will lower the quality but - * increase the performance of the filter. - * - * @member {number} - */ - this.resolution = settings.RESOLUTION; - - /** - * If enabled is true the filter is applied, if false it will not. - * - * @member {boolean} - */ - this.enabled = true; - - /** - * If enabled, PixiJS will fit the filter area into boundaries for better performance. - * Switch it off if it does not work for specific shader. - * - * @member {boolean} - */ - this.autoFit = true; - - /** - * Legacy filters use position and uvs from attributes - * @member {boolean} - * @readonly - */ - this.legacy = !!this.program.attributeData.aTextureCoord; - - /** - * the webGL state the filter requires to render - * @member {PIXI.State} - */ - this.state = new State(); - } - - /** - * Applies the filter - * - * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from - * @param {PIXI.RenderTarget} input - The input render target. - * @param {PIXI.RenderTarget} output - The target to output to. - * @param {boolean} clear - Should the output be cleared before rendering to it - * @param {object} [currentState] - It's current state of filter. - * There are some useful properties in the currentState : - * target, filters, sourceFrame, destinationFrame, renderTarget, resolution - */ - apply(filterManager, input, output, clear, currentState, derp) // eslint-disable-line no-unused-vars - { - // do as you please! - - filterManager.applyFilter(this, input, output, clear, currentState, derp); - - // or just do a regular render.. - } - - /** - * Sets the blendmode of the filter - * - * @member {number} - * @default PIXI.BLEND_MODES.NORMAL - */ - get blendMode() - { - return this.state.blendMode; - } - - set blendMode(value) // eslint-disable-line require-jsdoc - { - this.state.blendMode = value; - } - - /** - * The default vertex shader source - * - * @static - * @constant - */ - static get defaultVertexSrc() - { - return defaultVertex; - } - - /** - * The default fragment shader source - * - * @static - * @constant - */ - static get defaultFragmentSrc() - { - return defaultFragment; - } -} - -/** - * Used for caching shader IDs - * - * @static - * @private - */ -Filter.SOURCE_KEY_MAP = {}; - diff --git a/packages/core/src/renderers/filters/defaultFilter.frag b/packages/core/src/renderers/filters/defaultFilter.frag deleted file mode 100644 index a14c1be..0000000 --- a/packages/core/src/renderers/filters/defaultFilter.frag +++ /dev/null @@ -1,22 +0,0 @@ -varying vec2 vTextureCoord; -varying vec2 vFilterCoord; - -uniform sampler2D uSampler; -uniform sampler2D filterSampler; - -void main(void){ - vec4 masky = texture2D(filterSampler, vFilterCoord); - vec4 sample = texture2D(uSampler, vTextureCoord); - vec4 color; - if(mod(vFilterCoord.x, 1.0) > 0.5) - { - color = vec4(1.0, 0.0, 0.0, 1.0); - } - else - { - color = vec4(0.0, 1.0, 0.0, 1.0); - } - // gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0); - gl_FragColor = mix(sample, masky, 0.5); - gl_FragColor *= sample.a; -} diff --git a/packages/core/src/renderers/filters/defaultFilter.vert b/packages/core/src/renderers/filters/defaultFilter.vert deleted file mode 100644 index e1febca..0000000 --- a/packages/core/src/renderers/filters/defaultFilter.vert +++ /dev/null @@ -1,14 +0,0 @@ -attribute vec2 aVertexPosition; -attribute vec2 aTextureCoord; - -uniform mat3 projectionMatrix; -uniform mat3 filterMatrix; - -varying vec2 vTextureCoord; -varying vec2 vFilterCoord; - -void main(void){ - gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); - vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy; - vTextureCoord = aTextureCoord ; -} diff --git a/packages/core/src/renderers/filters/filterTransforms.js b/packages/core/src/renderers/filters/filterTransforms.js deleted file mode 100644 index 87a3cd6..0000000 --- a/packages/core/src/renderers/filters/filterTransforms.js +++ /dev/null @@ -1,50 +0,0 @@ -import { Matrix } from '@pixi/math'; - -/** - * Calculates the mapped matrix - * @param filterArea {Rectangle} The filter area - * @param sprite {Sprite} the target sprite - * @param outputMatrix {Matrix} @alvin - * @private - */ -// this returns a matrix that will normalise map filter cords in the filter to screen space -export function calculateScreenSpaceMatrix(outputMatrix, filterArea, textureSize) -{ - // TODO unwrap? - const mappedMatrix = outputMatrix.identity(); - - mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); - - mappedMatrix.scale(textureSize.width, textureSize.height); - - return mappedMatrix; -} - -export function calculateNormalizedScreenSpaceMatrix(outputMatrix, filterArea, textureSize) -{ - const mappedMatrix = outputMatrix.identity(); - - mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); - - const translateScaleX = (textureSize.width / filterArea.width); - const translateScaleY = (textureSize.height / filterArea.height); - - mappedMatrix.scale(translateScaleX, translateScaleY); - - return mappedMatrix; -} - -// this will map the filter coord so that a texture can be used based on the transform of a sprite -export function calculateSpriteMatrix(outputMatrix, filterArea, textureSize, sprite) -{ - const orig = sprite._texture.orig; - const mappedMatrix = outputMatrix.set(textureSize.width, 0, 0, textureSize.height, filterArea.x, filterArea.y); - const worldTransform = sprite.worldTransform.copyTo(Matrix.TEMP_MATRIX); - - worldTransform.invert(); - mappedMatrix.prepend(worldTransform); - mappedMatrix.scale(1.0 / orig.width, 1.0 / orig.height); - mappedMatrix.translate(sprite.anchor.x, sprite.anchor.y); - - return mappedMatrix; -} diff --git a/packages/core/src/renderers/filters/spriteMask/SpriteMaskFilter.js b/packages/core/src/renderers/filters/spriteMask/SpriteMaskFilter.js deleted file mode 100644 index d5a91a4..0000000 --- a/packages/core/src/renderers/filters/spriteMask/SpriteMaskFilter.js +++ /dev/null @@ -1,63 +0,0 @@ -import Filter from '../Filter'; -import { Matrix } from '@pixi/math'; -import vertex from './spriteMaskFilter.vert'; -import fragment from './spriteMaskFilter.frag'; -import { default as TextureMatrix } from '../../../textures/TextureMatrix'; - -/** - * The SpriteMaskFilter class - * - * @class - * @extends PIXI.Filter - * @memberof PIXI - */ -export default class SpriteMaskFilter extends Filter -{ - /** - * @param {PIXI.Sprite} sprite - the target sprite - */ - constructor(sprite) - { - const maskMatrix = new Matrix(); - - super(vertex, fragment); - - sprite.renderable = false; - - this.maskSprite = sprite; - this.maskMatrix = maskMatrix; - } - - /** - * Applies the filter - * - * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from - * @param {PIXI.RenderTarget} input - The input render target. - * @param {PIXI.RenderTarget} output - The target to output to. - */ - apply(filterManager, input, output) - { - const maskSprite = this.maskSprite; - const tex = this.maskSprite.texture; - - if (!tex.valid) - { - return; - } - if (!tex.transform) - { - // margin = 0.0, let it bleed a bit, shader code becomes easier - // assuming that atlas textures were made with 1-pixel padding - tex.transform = new TextureMatrix(tex, 0.0); - } - tex.transform.update(); - - this.uniforms.mask = tex; - this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite) - .prepend(tex.transform.mapCoord); - this.uniforms.alpha = maskSprite.worldAlpha; - this.uniforms.maskClamp = tex.transform.uClampFrame; - - filterManager.applyFilter(this, input, output); - } -} diff --git a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.frag b/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.frag deleted file mode 100644 index 0e4aef8..0000000 --- a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.frag +++ /dev/null @@ -1,23 +0,0 @@ -varying vec2 vMaskCoord; -varying vec2 vTextureCoord; - -uniform sampler2D uSampler; -uniform sampler2D mask; -uniform float alpha; -uniform vec4 maskClamp; - -void main(void) -{ - float clip = step(3.5, - step(maskClamp.x, vMaskCoord.x) + - step(maskClamp.y, vMaskCoord.y) + - step(vMaskCoord.x, maskClamp.z) + - step(vMaskCoord.y, maskClamp.w)); - - vec4 original = texture2D(uSampler, vTextureCoord); - vec4 masky = texture2D(mask, vMaskCoord); - - original *= (masky.r * masky.a * alpha * clip); - - gl_FragColor = original; -} diff --git a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.vert b/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.vert deleted file mode 100644 index d23a30c..0000000 --- a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.vert +++ /dev/null @@ -1,16 +0,0 @@ -attribute vec2 aVertexPosition; -attribute vec2 aTextureCoord; - -uniform mat3 projectionMatrix; -uniform mat3 otherMatrix; - -varying vec2 vMaskCoord; -varying vec2 vTextureCoord; - -void main(void) -{ - gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); - - vTextureCoord = aTextureCoord; - vMaskCoord = ( otherMatrix * vec3( aTextureCoord, 1.0) ).xy; -} diff --git a/packages/core/src/renderers/systems/BatchSystem.js b/packages/core/src/renderers/systems/BatchSystem.js deleted file mode 100644 index 70e9518..0000000 --- a/packages/core/src/renderers/systems/BatchSystem.js +++ /dev/null @@ -1,68 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import ObjectRenderer from '../utils/ObjectRenderer'; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ - -export default class BatchSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this manager works for. - */ - constructor(renderer) - { - super(renderer); - - /** - * An empty renderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.emptyRenderer = new ObjectRenderer(renderer); - - /** - * The currently active ObjectRenderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.currentRenderer = this.emptyRenderer; - } - - /** - * Changes the current renderer to the one given in parameter - * - * @param {PIXI.ObjectRenderer} objectRenderer - The object renderer to use. - */ - setObjectRenderer(objectRenderer) - { - if (this.currentRenderer === objectRenderer) - { - return; - } - - this.currentRenderer.stop(); - this.currentRenderer = objectRenderer; - - this.renderer.state.setState(objectRenderer.state); - - this.currentRenderer.start(); - } - - /** - * This should be called if you wish to do some custom rendering - * It will basically render anything that may be batched up such as sprites - * - */ - flush() - { - this.setObjectRenderer(this.emptyRenderer); - } - - reset() - { - this.setObjectRenderer(this.emptyRenderer); - } -} diff --git a/packages/core/src/renderers/systems/ContextSystem.js b/packages/core/src/renderers/systems/ContextSystem.js deleted file mode 100644 index 43bf827..0000000 --- a/packages/core/src/renderers/systems/ContextSystem.js +++ /dev/null @@ -1,186 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { settings } from '@pixi/settings'; - -let CONTEXT_UID = 0; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class ContextSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this.webGLVersion = 1; - - this.handleContextLost = this.handleContextLost.bind(this); - this.handleContextRestored = this.handleContextRestored.bind(this); - - this.extensions = {}; - - renderer.view.addEventListener('webglcontextlost', this.handleContextLost, false); - renderer.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); - } - - get isLost() - { - return (!this.gl || this.gl.isContextLost()); - } - - contextChange(gl) - { - this.gl = gl; - - // restore a context if it was previously lost - if (gl.isContextLost() && gl.getExtension('WEBGL_lose_context')) - { - gl.getExtension('WEBGL_lose_context').restoreContext(); - } - } - - initFromContext(gl) - { - this.gl = gl; - this.validateContext(gl); - this.renderer.gl = gl; - this.renderer.CONTEXT_UID = CONTEXT_UID++; - this.renderer.runners.contextChange.run(gl); - } - - initFromOptions(options) - { - const gl = this.createContext(this.renderer.view, options); - - this.initFromContext(gl); - } - - /** - * Helper class to create a webGL Context - * - * @class - * @memberof PIXI.glCore - * @param canvas {HTMLCanvasElement} the canvas element that we will get the context from - * @param options {Object} An options object that gets passed in to the canvas element containing the context attributes, - * see https://developer.mozilla.org/en/docs/Web/API/HTMLCanvasElement/getContext for the options available - * @return {WebGLRenderingContext} the WebGL context - */ - createContext(canvas, options) - { - let gl; - - if (settings.PREFER_WEBGL_2) - { - gl = canvas.getContext('webgl2', options); - } - - if (gl) - { - this.webGLVersion = 2; - } - else - { - this.webGLVersion = 1; - - gl = canvas.getContext('webgl', options) - || canvas.getContext('experimental-webgl', options); - - if (!gl) - { - // fail, not able to get a context - throw new Error('This browser does not support webGL. Try using the canvas renderer'); - } - } - - this.gl = gl; - - this.getExtensions(); - - return gl; - } - - getExtensions() - { - // time to set up default etensions that pixi uses.. - const gl = this.gl; - const extensions = this.extensions; - - if (this.webGLVersion === 1) - { - extensions.drawBuffers = gl.getExtension('WEBGL_draw_buffers'); - extensions.depthTexture = gl.getExtension('WEBKIT_WEBGL_depth_texture'); - extensions.floatTexture = gl.getExtension('OES_texture_float'); - extensions.loseContext = gl.getExtension('WEBGL_lose_context'); - - extensions.vertexArrayObject = gl.getExtension('OES_vertex_array_object') - || gl.getExtension('MOZ_OES_vertex_array_object') - || gl.getExtension('WEBKIT_OES_vertex_array_object'); - } - - // we don't use any specific WebGL 2 ones yet! - } - - /** - * Handles a lost webgl context - * - * @private - * @param {WebGLContextEvent} event - The context lost event. - */ - handleContextLost(event) - { - event.preventDefault(); - } - - /** - * Handles a restored webgl context - * - * @private - */ - handleContextRestored() - { - this.renderer.runners.contextChange.run(this.gl); - } - - destroy() - { - const view = this.renderer.view; - - // remove listeners - view.removeEventListener('webglcontextlost', this.handleContextLost); - view.removeEventListener('webglcontextrestored', this.handleContextRestored); - - this.gl.useProgram(null); - - if (this.extensions.loseContext) - { - this.extensions.loseContext.loseContext(); - } - } - - postrender() - { - this.gl.flush(); - } - - validateContext(gl) - { - const attributes = gl.getContextAttributes(); - - // this is going to be fairly simple for now.. but at least we have room to grow! - if (!attributes.stencil) - { - /* eslint-disable max-len */ - - /* eslint-disable no-console */ - console.warn('Provided WebGL context does not have a stencil buffer, masks may not render correctly'); - /* eslint-enable no-console */ - - /* eslint-enable max-len */ - } - } -} diff --git a/packages/core/src/renderers/systems/FramebufferSystem.js b/packages/core/src/renderers/systems/FramebufferSystem.js deleted file mode 100644 index df7902b..0000000 --- a/packages/core/src/renderers/systems/FramebufferSystem.js +++ /dev/null @@ -1,246 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { Rectangle } from '@pixi/math'; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class FramebufferSystem extends WebGLSystem -{ - /** - * Sets up the renderer context and necessary buffers. - * - * @private - */ - contextChange() - { - this.gl = this.renderer.gl; - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - this.current = null; - this.viewport = new Rectangle(); - - this.drawBufferExtension = this.renderer.context.extensions.drawBuffers; - } - - bind(framebuffer, frame) - { - const gl = this.gl; - - this.current = framebuffer; - - if (framebuffer) - { - // TODO cacheing layer! - - const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID] || this.initFramebuffer(framebuffer); - - gl.bindFramebuffer(gl.FRAMEBUFFER, fbo.framebuffer); - // makesure all textures are unbound.. - - // now check for updates... - if (fbo.dirtyId !== framebuffer.dirtyId) - { - fbo.dirtyId = framebuffer.dirtyId; - - if (fbo.dirtyFormat !== framebuffer.dirtyFormat) - { - fbo.dirtyFormat = framebuffer.dirtyFormat; - this.updateFramebuffer(framebuffer); - } - else if (fbo.dirtySize !== framebuffer.dirtySize) - { - fbo.dirtySize = framebuffer.dirtySize; - this.resizeFramebuffer(framebuffer); - } - } - - for (let i = 0; i < framebuffer.colorTextures.length; i++) - { - if (framebuffer.colorTextures[i].texturePart) - { - this.renderer.texture.unbind(framebuffer.colorTextures[i].texture); - } - else - { - this.renderer.texture.unbind(framebuffer.colorTextures[i]); - } - } - - if (framebuffer.depthTexture) - { - this.renderer.texture.unbind(framebuffer.depthTexture); - } - - if (frame) - { - this.setViewport(frame.x, frame.y, frame.width, frame.height); - } - else - { - this.setViewport(0, 0, framebuffer.width, framebuffer.height); - } - } - else - { - gl.bindFramebuffer(gl.FRAMEBUFFER, null); - - if (frame) - { - this.setViewport(frame.x, frame.y, frame.width, frame.height); - } - else - { - this.setViewport(0, 0, this.renderer.width, this.renderer.height); - } - } - } - - setViewport(x, y, width, height) - { - const v = this.viewport; - - if (v.width !== width || v.height !== height || v.x !== x || v.y !== y) - { - v.x = x; - v.y = y; - v.width = width; - v.height = height; - - this.gl.viewport(x, y, width, height); - } - } - - get size() - { - if (this.current) - { - // TODO store temp - return { x: 0, y: 0, width: this.current.width, height: this.current.height }; - } - - return { x: 0, y: 0, width: this.renderer.width, height: this.renderer.height }; - } - - clear(r, g, b, a) - { - const gl = this.gl; - - // TODO clear color can be set only one right? - gl.clearColor(r, g, b, a); - gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); - } - - // private functions... - - initFramebuffer(framebuffer) - { - const gl = this.gl; - - // TODO - make this a class? - const fbo = { - framebuffer: gl.createFramebuffer(), - stencil: null, - dirtyId: 0, - dirtyFormat: 0, - dirtySize: 0, - }; - - framebuffer.glFrameBuffers[this.CONTEXT_UID] = fbo; - - return fbo; - } - - resizeFramebuffer(framebuffer) - { - const gl = this.gl; - - if (framebuffer.stencil || framebuffer.depth) - { - gl.bindRenderbuffer(gl.RENDERBUFFER, this.stencil); - gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); - } - } - - updateFramebuffer(framebuffer) - { - const gl = this.gl; - - const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID]; - - // bind the color texture - const colorTextures = framebuffer.colorTextures; - - let count = colorTextures.length; - - if (!this.drawBufferExtension) - { - count = Math.min(count, 1); - } - - const activeTextures = []; - - for (let i = 0; i < count; i++) - { - const texture = framebuffer.colorTextures[i]; - - if (texture.texturePart) - { - this.renderer.texture.bind(texture.texture, 0); - - gl.framebufferTexture2D(gl.FRAMEBUFFER, - gl.COLOR_ATTACHMENT0 + i, - gl.TEXTURE_CUBE_MAP_NEGATIVE_X + texture.side, - texture.texture._glTextures[this.CONTEXT_UID].texture, - 0); - } - else - { - this.renderer.texture.bind(texture, 0); - - gl.framebufferTexture2D(gl.FRAMEBUFFER, - gl.COLOR_ATTACHMENT0 + i, - gl.TEXTURE_2D, - texture._glTextures[this.CONTEXT_UID].texture, - 0); - } - - activeTextures.push(gl.COLOR_ATTACHMENT0 + i); - } - - if (this.drawBufferExtension && activeTextures.length > 1) - { - this.drawBufferExtension.drawBuffersWEBGL(activeTextures); - } - - if (framebuffer.depthTexture) - { - const depthTextureExt = this.renderer.context.extensions.depthTexture; - - if (depthTextureExt) - { - const depthTexture = framebuffer.depthTexture; - - this.renderer.texture.bind(depthTexture, 0); - - gl.framebufferTexture2D(gl.FRAMEBUFFER, - gl.DEPTH_ATTACHMENT, - gl.TEXTURE_2D, - depthTexture._glTextures[this.CONTEXT_UID].texture, - 0); - } - } - - if (framebuffer.stencil || framebuffer.depth) - { - fbo.stencil = gl.createRenderbuffer(); - - gl.bindRenderbuffer(gl.RENDERBUFFER, fbo.stencil); - - // TODO.. this is depth AND stencil? - gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, fbo.stencil); - gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); - // fbo.enableStencil(); - } - } -} diff --git a/packages/core/src/renderers/systems/MaskSystem.js b/packages/core/src/renderers/systems/MaskSystem.js deleted file mode 100644 index d9c4b1a..0000000 --- a/packages/core/src/renderers/systems/MaskSystem.js +++ /dev/null @@ -1,199 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import AlphaMaskFilter from '../filters/spriteMask/SpriteMaskFilter'; - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class MaskSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - // TODO - we don't need both! - this.scissor = false; - this.scissorData = null; - this.scissorRenderTarget = null; - - this.enableScissor = false; - - this.alphaMaskPool = []; - this.alphaMaskIndex = 0; - } - - /** - * Applies the Mask and adds it to the current filter stack. - * - * @param {PIXI.DisplayObject} target - Display Object to push the mask to - * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. - */ - push(target, maskData) - { - // TODO the root check means scissor rect will not - // be used on render textures more info here: - // https://github.com/pixijs/pixi.js/pull/3545 - - if (maskData.texture) - { - this.pushSpriteMask(target, maskData); - } - else if (this.enableScissor - && !this.scissor - && this.renderer._activeRenderTarget.root - && !this.renderer.stencil.stencilMaskStack.length - && maskData.isFastRect()) - { - const matrix = maskData.worldTransform; - - let rot = Math.atan2(matrix.b, matrix.a); - - // use the nearest degree! - rot = Math.round(rot * (180 / Math.PI)); - - if (rot % 90) - { - this.pushStencilMask(maskData); - } - else - { - this.pushScissorMask(target, maskData); - } - } - else - { - this.pushStencilMask(maskData); - } - } - - /** - * Removes the last mask from the mask stack and doesn't return it. - * - * @param {PIXI.DisplayObject} target - Display Object to pop the mask from - * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. - */ - pop(target, maskData) - { - if (maskData.texture) - { - this.popSpriteMask(target, maskData); - } - else if (this.enableScissor && !this.renderer.stencil.stencilMaskStack.length) - { - this.popScissorMask(target, maskData); - } - else - { - this.popStencilMask(target, maskData); - } - } - - /** - * Applies the Mask and adds it to the current filter stack. - * - * @param {PIXI.RenderTarget} target - Display Object to push the sprite mask to - * @param {PIXI.Sprite} maskData - Sprite to be used as the mask - */ - pushSpriteMask(target, maskData) - { - let alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; - - if (!alphaMaskFilter) - { - alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; - } - - alphaMaskFilter[0].resolution = this.renderer.resolution; - alphaMaskFilter[0].maskSprite = maskData; - - // TODO - may cause issues! - target.filterArea = maskData.getBounds(true); - - this.renderer.filterSystem.pushFilter(target, alphaMaskFilter); - - this.alphaMaskIndex++; - } - - /** - * Removes the last filter from the filter stack and doesn't return it. - * - */ - popSpriteMask() - { - this.renderer.filterSystem.popFilter(); - this.alphaMaskIndex--; - } - - /** - * Applies the Mask and adds it to the current filter stack. - * - * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. - */ - pushStencilMask(maskData) - { - this.renderer.batch.flush(); - this.renderer.stencil.pushStencil(maskData); - } - - /** - * Removes the last filter from the filter stack and doesn't return it. - * - */ - popStencilMask() - { - // this.renderer.currentRenderer.stop(); - this.renderer.stencil.popStencil(); - } - - /** - * - * @param {PIXI.DisplayObject} target - Display Object to push the mask to - * @param {PIXI.Graphics} maskData - The masking data. - */ - pushScissorMask(target, maskData) - { - maskData.renderable = true; - - const renderTarget = this.renderer._activeRenderTarget; - - const bounds = maskData.getBounds(); - - bounds.fit(renderTarget.size); - maskData.renderable = false; - - this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); - - const resolution = this.renderer.resolution; - - this.renderer.gl.scissor( - bounds.x * resolution, - (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, - bounds.width * resolution, - bounds.height * resolution - ); - - this.scissorRenderTarget = renderTarget; - this.scissorData = maskData; - this.scissor = true; - } - - /** - * - * - */ - popScissorMask() - { - this.scissorRenderTarget = null; - this.scissorData = null; - this.scissor = false; - - // must be scissor! - const gl = this.renderer.gl; - - gl.disable(gl.SCISSOR_TEST); - } -} diff --git a/packages/core/src/renderers/systems/ProjectionSystem.js b/packages/core/src/renderers/systems/ProjectionSystem.js deleted file mode 100644 index ec5efdb..0000000 --- a/packages/core/src/renderers/systems/ProjectionSystem.js +++ /dev/null @@ -1,73 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { Matrix } from '@pixi/math'; - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ - -export default class ProjectionSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this.projectionMatrix = new Matrix(); - } - - update(destinationFrame, sourceFrame, resolution, root) - { - this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; - this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; - - this.calculateProjection(this.destinationFrame, this.sourceFrame, resolution, root); - - this.renderer.globalUniforms.uniforms.projectionMatrix = this.projectionMatrix; - this.renderer.globalUniforms.update(); - } - - /** - * Updates the projection matrix based on a projection frame (which is a rectangle) - * - * @param {Rectangle} destinationFrame - The destination frame. - * @param {Rectangle} sourceFrame - The source frame. - */ - calculateProjection(destinationFrame, sourceFrame, resolution, root) - { - const pm = this.projectionMatrix; - - // I don't think we will need this line.. - // pm.identity(); - - if (!root) - { - pm.a = (1 / destinationFrame.width * 2) * resolution; - pm.d = (1 / destinationFrame.height * 2) * resolution; - - pm.tx = -1 - (sourceFrame.x * pm.a); - pm.ty = -1 - (sourceFrame.y * pm.d); - } - else - { - pm.a = (1 / destinationFrame.width * 2) * resolution; - pm.d = (-1 / destinationFrame.height * 2) * resolution; - - pm.tx = -1 - (sourceFrame.x * pm.a); - pm.ty = 1 - (sourceFrame.y * pm.d); - } - } - - /** - * Sets the transform of the active render target to the given matrix - * - * @param {PIXI.Matrix} matrix - The transformation matrix - */ - setTransform()// matrix) - { - // this._activeRenderTarget.transform = matrix; - } -} diff --git a/packages/core/src/renderers/systems/RenderTextureSystem.js b/packages/core/src/renderers/systems/RenderTextureSystem.js deleted file mode 100644 index a133ef1..0000000 --- a/packages/core/src/renderers/systems/RenderTextureSystem.js +++ /dev/null @@ -1,115 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { Rectangle } from '@pixi/math'; - -const tempRect = new Rectangle(); - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ - -export default class RenderTextureSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this.clearColor = renderer._backgroundColorRgba; - - // TODO moe this property somewhere else! - this.defaultMaskStack = []; - this.defaultFilterStack = [{}]; - - // empty render texture? - this.renderTexture = null; - - this.destinationFrame = new Rectangle(); - } - - bind(renderTexture, sourceFrame, destinationFrame) - { - // TODO - do we want this?? - if (this.renderTexture === renderTexture) return; - this.renderTexture = renderTexture; - - const renderer = this.renderer; - - if (renderTexture) - { - const baseTexture = renderTexture.baseTexture; - - if (!destinationFrame) - { - tempRect.width = baseTexture.realWidth; - tempRect.height = baseTexture.realHeight; - - destinationFrame = tempRect; - } - - if (!sourceFrame) - { - sourceFrame = destinationFrame; - } - - this.renderer.framebuffer.bind(baseTexture.frameBuffer, destinationFrame); - - this.renderer.projection.update(destinationFrame, sourceFrame, baseTexture.resolution, false); - this.renderer.stencil.setMaskStack(baseTexture.stencilMaskStack); - } - else - { - // TODO these validation checks happen deeper down.. - // thing they can be avoided.. - if (!destinationFrame) - { - tempRect.width = renderer.width; - tempRect.height = renderer.height; - - destinationFrame = tempRect; - } - - if (!sourceFrame) - { - sourceFrame = destinationFrame; - } - - renderer.framebuffer.bind(null, destinationFrame); - - // TODO store this.. - this.renderer.projection.update(destinationFrame, sourceFrame, this.renderer.resolution, true); - this.renderer.stencil.setMaskStack(this.defaultMaskStack); - } - - this.destinationFrame.copyFrom(destinationFrame); - } - - /** - * Erases the render texture and fills the drawing area with a colour - * - * @param {number} [clearColor] - The colour - * @return {PIXI.Renderer} Returns itself. - */ - clear(clearColor) - { - if (this.renderTexture) - { - clearColor = clearColor || this.renderTexture.baseTexture.clearColor; - } - else - { - clearColor = clearColor || this.clearColor; - } - - this.renderer.framebuffer.clear(clearColor[0], clearColor[1], clearColor[2], clearColor[3]); - } - - resize()// screenWidth, screenHeight) - { - // resize the root only! - this.bind(null); - } -} diff --git a/packages/core/src/renderers/systems/StateSystem.js b/packages/core/src/renderers/systems/StateSystem.js deleted file mode 100755 index d7f4caf..0000000 --- a/packages/core/src/renderers/systems/StateSystem.js +++ /dev/null @@ -1,288 +0,0 @@ -import mapWebGLBlendModesToPixi from '../utils/mapWebGLBlendModesToPixi'; -import WebGLSystem from './WebGLSystem'; -import WebGLState from '../State'; - -const BLEND = 0; -const OFFSET = 1; -const CULLING = 2; -const DEPTH_TEST = 3; -const WINDING = 4; - -/** - * A WebGL state machines - * - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class StateSystem extends WebGLSystem -{ - /** - * @param {WebGLRenderingContext} gl - The current WebGL rendering context - */ - constructor(renderer) - { - super(renderer); - - this.gl = null; - - this.maxAttribs = null; - - // check we have vao.. - this.nativeVaoExtension = null; - - this.attribState = null; - - this.stateId = 0; - this.polygonOffset = 0; - this.blendMode = 17; - - this.map = []; - - // map functions for when we set state.. - this.map[BLEND] = this.setBlend; - this.map[OFFSET] = this.setOffset; - this.map[CULLING] = this.setCullFace; - this.map[DEPTH_TEST] = this.setDepthTest; - this.map[WINDING] = this.setFrontFace; - - this.checks = []; - - this.defaultState = new WebGLState(); - this.defaultState.blend = true; - this.defaultState.depth = true; - } - - contextChange(gl) - { - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; - - this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); - - // check we have vao.. - this.nativeVaoExtension = ( - gl.getExtension('OES_vertex_array_object') - || gl.getExtension('MOZ_OES_vertex_array_object') - || gl.getExtension('WEBKIT_OES_vertex_array_object') - ); - - this.attribState = { - tempAttribState: new Array(this.maxAttribs), - attribState: new Array(this.maxAttribs), - }; - - this.blendModes = mapWebGLBlendModesToPixi(gl); - - this.setState(this.defaultState); - - this.reset(); - } - - /** - * Sets the current state - * - * @param {*} state - The state to set. - */ - setState(state) - { - state = state || this.defaultState; - - // TODO maybe to an object check? ( this.state === state )? - if (this.stateId !== state.data) - { - let diff = this.stateId ^ state.data; - let i = 0; - - // order from least to most common - while (diff) - { - if (diff & 1) - { - // state change! - this.map[i].call(this, !!(state.data & (1 << i))); - } - - diff = diff >> 1; - i++; - } - - this.stateId = state.data; - } - - // based on the above settings we check for specific modes.. - // for example if blend is active we check and set the blend modes - // or of polygon offset is active we check the poly depth. - for (let i = 0; i < this.checks.length; i++) - { - this.checks[i](this, state); - } - } - - /** - * Enables or disabled blending. - * - * @param {boolean} value - Turn on or off webgl blending. - */ - setBlend(value) - { - this.updateCheck(StateSystem.checkBlendMode, value); - - this.gl[value ? 'enable' : 'disable'](this.gl.BLEND); - } - - /** - * Enables or disable polygon offset fill - * - * @param {boolean} value - Turn on or off webgl polygon offset testing. - */ - setOffset(value) - { - this.gl[value ? 'enable' : 'disable'](this.gl.POLYGON_OFFSET_FILL); - } - - /** - * Sets whether to enable or disable depth test. - * - * @param {boolean} value - Turn on or off webgl depth testing. - */ - setDepthTest(value) - { - this.gl[value ? 'enable' : 'disable'](this.gl.DEPTH_TEST); - } - - /** - * Sets whether to enable or disable cull face. - * - * @param {boolean} value - Turn on or off webgl cull face. - */ - setCullFace(value) - { - this.gl[value ? 'enable' : 'disable'](this.gl.CULL_FACE); - } - - /** - * Sets the gl front face. - * - * @param {boolean} value - true is clockwise and false is counter-clockwise - */ - setFrontFace(value) - { - this.gl.frontFace(this.gl[value ? 'CW' : 'CCW']); - } - - /** - * Sets the blend mode. - * - * @param {number} value - The blend mode to set to. - */ - setBlendMode(value) - { - if (value === this.blendMode) - { - return; - } - - this.blendMode = value; - this.gl.blendFunc(this.blendModes[value][0], this.blendModes[value][1]); - } - - /** - * Sets the polygon offset. - * - * @param {number} value - the polygon offset - * @param {number} scale - the polygon offset scale - */ - setPolygonOffset(value, scale) - { - this.gl.polygonOffset(value, scale); - } - - /** - * Disables all the vaos in use - * - */ - resetAttributes() - { - for (let i = 0; i < this.attribState.tempAttribState.length; i++) - { - this.attribState.tempAttribState[i] = 0; - } - - for (let i = 0; i < this.attribState.attribState.length; i++) - { - this.attribState.attribState[i] = 0; - } - - // im going to assume one is always active for performance reasons. - for (let i = 1; i < this.maxAttribs; i++) - { - this.gl.disableVertexAttribArray(i); - } - } - - // used - /** - * Resets all the logic and disables the vaos - */ - reset() - { - // unbind any VAO if they exist.. - if (this.nativeVaoExtension) - { - this.nativeVaoExtension.bindVertexArrayOES(null); - } - - // reset all attributes.. - this.resetAttributes(); - - this.gl.pixelStorei(this.gl.UNPACK_FLIP_Y_WEBGL, false); - - this.setBlendMode(0); - - // TODO? - // this.setState(this.defaultState); - } - - /** - * checks to see which updates should be checked based on which settings have been activated - * for example if blend is enabled then we shold check the blend modes each time the state is changed - * or if poygon fill is activated then we need to check if the polygone offset changes. - * The idea is that we only check what we have too. - * - * @param {Function} func the checking function to add or remove - * @param {boolean} value should the check function be added or removed. - */ - updateCheck(func, value) - { - const index = this.checks.indexOf(func); - - if (value && index === -1) - { - this.checks.push(func); - } - else if (!value && index !== -1) - { - this.checks.splice(index, 1); - } - } - - /** - * A private little wrapper function that we call to check the blend mode. - * - * @static - * @private - * @param {PIXI.StateSystem} System the System to perform the state check on - * @param {PIXI.State} state the state that the blendMode will pulled from - */ - static checkBlendMode(system, state) - { - system.setBlendMode(state.blendMode); - } - - // TODO - add polygon offset? -} diff --git a/packages/core/src/renderers/systems/StencilSystem.js b/packages/core/src/renderers/systems/StencilSystem.js deleted file mode 100644 index 5d538cb..0000000 --- a/packages/core/src/renderers/systems/StencilSystem.js +++ /dev/null @@ -1,133 +0,0 @@ -import WebGLSystem from './WebGLSystem'; - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class StencilSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - this.stencilMaskStack = []; - } - - /** - * Changes the mask stack that is used by this System. - * - * @param {PIXI.Graphics[]} stencilMaskStack - The mask stack - */ - setMaskStack(stencilMaskStack) - { - const gl = this.renderer.gl; - - if (stencilMaskStack.length !== this.stencilMaskStack.length) - { - if (stencilMaskStack.length === 0) - { - gl.disable(gl.STENCIL_TEST); - } - else - { - gl.enable(gl.STENCIL_TEST); - } - } - - this.stencilMaskStack = stencilMaskStack; - } - - /** - * Applies the Mask and adds it to the current stencil stack. @alvin - * - * @param {PIXI.Graphics} graphics - The mask - */ - pushStencil(graphics) - { - this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); - - // this.renderer._activeRenderTarget.attachStencilBuffer(); - - const gl = this.renderer.gl; - const prevMaskCount = this.stencilMaskStack.length; - - if (prevMaskCount === 0) - { - gl.enable(gl.STENCIL_TEST); - } - - this.stencilMaskStack.push(graphics); - - // Increment the refference stencil value where the new mask overlaps with the old ones. - gl.colorMask(false, false, false, false); - gl.stencilFunc(gl.EQUAL, prevMaskCount, this._getBitwiseMask()); - gl.stencilOp(gl.KEEP, gl.KEEP, gl.INCR); - this.renderer.plugins.graphics.render(graphics); - - this._useCurrent(); - } - - /** - * Removes the last mask from the stencil stack. @alvin - */ - popStencil() - { - this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); - - const gl = this.renderer.gl; - const graphics = this.stencilMaskStack.pop(); - - if (this.stencilMaskStack.length === 0) - { - // the stack is empty! - gl.disable(gl.STENCIL_TEST); - gl.clear(gl.STENCIL_BUFFER_BIT); - gl.clearStencil(0); - } - else - { - // Decrement the refference stencil value where the popped mask overlaps with the other ones - gl.colorMask(false, false, false, false); - gl.stencilOp(gl.KEEP, gl.KEEP, gl.DECR); - this.renderer.plugins.graphics.render(graphics); - - this._useCurrent(); - } - } - - /** - * Setup renderer to use the current stencil data. - */ - _useCurrent() - { - const gl = this.renderer.gl; - - gl.colorMask(true, true, true, true); - gl.stencilFunc(gl.EQUAL, this.stencilMaskStack.length, this._getBitwiseMask()); - gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP); - } - - /** - * Fill 1s equal to the number of acitve stencil masks. - * - * @return {number} The bitwise mask. - */ - _getBitwiseMask() - { - return (1 << this.stencilMaskStack.length) - 1; - } - - /** - * Destroys the mask stack. - * - */ - destroy() - { - super.destroy(this); - - this.stencilMaskStack = null; - } -} diff --git a/packages/core/src/renderers/systems/WebGLSystem.js b/packages/core/src/renderers/systems/WebGLSystem.js deleted file mode 100644 index d3d5b31..0000000 --- a/packages/core/src/renderers/systems/WebGLSystem.js +++ /dev/null @@ -1,41 +0,0 @@ -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this manager works for. - */ - constructor(renderer) - { - /** - * The renderer this manager works for. - * - * @member {PIXI.Renderer} - */ - this.renderer = renderer; - - this.renderer.runners.contextChange.add(this); - } - - /** - * Generic method called when there is a WebGL context change. - * - */ - contextChange() - { - // do some codes init! - } - - /** - * Generic destroy methods to be overridden by the subclass - * - */ - destroy() - { - this.renderer.runners.contextChange.remove(this); - this.renderer = null; - } -} diff --git a/packages/core/src/renderers/systems/filter/FilterSystem.js b/packages/core/src/renderers/systems/filter/FilterSystem.js deleted file mode 100644 index e615037..0000000 --- a/packages/core/src/renderers/systems/filter/FilterSystem.js +++ /dev/null @@ -1,431 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; - -import RenderTexture from '../../../textures/RenderTexture'; -import Quad from '../../utils/Quad'; -import QuadUv from '../../utils/QuadUv'; -import { Rectangle } from '@pixi/math'; -import * as filterTransforms from '../../filters/filterTransforms'; -import bitTwiddle from 'bit-twiddle'; -import UniformGroup from '../../../shader/UniformGroup'; -import { DRAW_MODES } from '../../../../../constants'; - -// -/** - * @ignore - * @class - */ -class FilterState -{ - /** - * - */ - constructor() - { - this.renderTexture = null; - this.sourceFrame = new Rectangle(); - this.destinationFrame = new Rectangle(); - this.filters = []; - this.target = null; - this.legacy = false; - this.resolution = 1; - } -} - -/** - * @class - * @memberof PIXI.systems - * @extends PIXI.systems.WebGLSystem - */ -export default class FilterSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - /** - * stores a bunch of PO2 textures used for filtering - * @type {Object} - */ - this.texturePool = {}; - - /** - * a pool for storing filter states, save us creating new ones each tick - * @type {Array} - */ - this.statePool = []; - - /** - * A very simple geometry used when drawing a filter effect to the screen - * @type {Quad} - */ - this.quad = new Quad(); - - this.quadUv = new QuadUv(); - - /** - * Temporary rect for maths - * @type {PIXI.Rectangle} - */ - this.tempRect = new Rectangle(); - - this.activeState = {}; - - /** - * this uniform group is attached to filter uniforms when used - * @type {UniformGroup} - */ - this.globalUniforms = new UniformGroup({ - sourceFrame: this.tempRect, - destinationFrame: this.tempRect, - - // legacy variables - filterArea: new Float32Array(4), - filterClamp: new Float32Array(4), - }, true); - } - - /** - * Adds a new filter to the System. - * - * @param {PIXI.DisplayObject} target - The target of the filter to render. - * @param {PIXI.Filter[]} filters - The filters to apply. - */ - push(target, filters) - { - const renderer = this.renderer; - const filterStack = this.renderer.renderTexture.defaultFilterStack; - const state = this.statePool.pop() || new FilterState(); - - let resolution = filters[0].resolution; - let padding = filters[0].padding; - let autoFit = filters[0].autoFit; - let legacy = filters[0].legacy; - - for (let i = 1; i < filters.length; i++) - { - const filter = filters[i]; - - // lets use the lowest resolution.. - resolution = Math.min(resolution, filter.resolution); - // and the largest amount of padding! - padding = Math.max(padding, filter.padding); - // only auto fit if all filters are autofit - autoFit = autoFit || filter.autoFit; - - legacy = legacy || filter.legacy; - } - - filterStack.push(state); - - state.resolution = resolution; - - state.legacy = legacy; - - // round to whole number based on resolution - // TODO move that to the shader too? - state.sourceFrame = target.filterArea || target.getBounds(true); - - state.sourceFrame.pad(padding); - - if (autoFit) - { - state.sourceFrame.fit(this.renderer.renderTexture.destinationFrame); - } - - state.sourceFrame.round(resolution); - - state.renderTexture = this.getPotFilterTexture(state.sourceFrame.width, state.sourceFrame.height, resolution); - state.filters = filters; - - state.destinationFrame.width = state.renderTexture.width; - state.destinationFrame.height = state.renderTexture.height; - - state.renderTexture.filterFrame = state.sourceFrame; - renderer.renderTexture.bind(state.renderTexture, state.sourceFrame);// /, state.destinationFrame); - renderer.renderTexture.clear(); - } - - /** - * Pops off the filter and applies it. - * - */ - pop() - { - const renderer = this.renderer; - const filterStack = renderer.renderTexture.defaultFilterStack; - const state = filterStack.pop(); - const filters = state.filters; - - this.activeState = state; - - const globalUniforms = this.globalUniforms.uniforms; - - globalUniforms.sourceFrame = state.sourceFrame; - globalUniforms.destinationFrame = state.destinationFrame; - globalUniforms.resolution = state.resolution; - - // only update the rect if its legacy.. - if (state.legacy) - { - const filterArea = globalUniforms.filterArea; - const filterClamp = globalUniforms.filterClamp; - - filterArea[0] = state.destinationFrame.width; - filterArea[1] = state.destinationFrame.height; - filterArea[2] = state.sourceFrame.x; - filterArea[3] = state.sourceFrame.y; - - filterClamp[0] = 0.5 / state.resolution / state.destinationFrame.width; - filterClamp[1] = 0.5 / state.resolution / state.destinationFrame.height; - filterClamp[2] = (state.sourceFrame.width - 0.5) / state.resolution / state.destinationFrame.width; - filterClamp[3] = (state.sourceFrame.height - 0.5) / state.resolution / state.destinationFrame.height; - } - - this.globalUniforms.update(); - - const lastState = filterStack[filterStack.length - 1]; - - if (filters.length === 1) - { - filters[0].apply(this, state.renderTexture, lastState.renderTexture, false, state); - - this.returnFilterTexture(state.renderTexture); - } - else - { - let flip = state.renderTexture; - let flop = this.getPotFilterTexture( - flip.width, - flip.height, - state.resolution - ); - - flop.filterFrame = flip.filterFrame; - - let i = 0; - - for (i = 0; i < filters.length - 1; ++i) - { - filters[i].apply(this, flip, flop, true, state); - - const t = flip; - - flip = flop; - flop = t; - } - - filters[i].apply(this, flip, lastState.renderTexture, false, state); - - this.returnFilterTexture(flip); - this.returnFilterTexture(flop); - } - - this.statePool.push(state); - } - - /** - * Draws a filter. - * - * @param {PIXI.Filter} filter - The filter to draw. - * @param {PIXI.RenderTarget} input - The input render target. - * @param {PIXI.RenderTarget} output - The target to output to. - * @param {boolean} clear - Should the output be cleared before rendering to it - */ - applyFilter(filter, input, output, clear) - { - const renderer = this.renderer; - - renderer.renderTexture.bind(output, output ? output.filterFrame : null); - - if (clear) - { - // gl.disable(gl.SCISSOR_TEST); - renderer.renderTexture.clear(); - // gl.enable(gl.SCISSOR_TEST); - } - - // set the uniforms.. - filter.uniforms.uSampler = input; - filter.uniforms.filterGlobals = this.globalUniforms; - - // TODO make it so that the order of this does not matter.. - // because it does at the moment cos of global uniforms. - // they need to get resynced - - renderer.state.setState(filter.state); - renderer.shader.bind(filter); - - if (filter.legacy) - { - this.quadUv.map(input._frame, input.filterFrame); - - renderer.geometry.bind(this.quadUv); - renderer.geometry.draw(DRAW_MODES.TRIANGLES); - } - else - { - renderer.geometry.bind(this.quad); - renderer.geometry.draw(DRAW_MODES.TRIANGLE_STRIP); - } - } - - /** - * Calculates the mapped matrix. - * - * TODO playing around here.. this is temporary - (will end up in the shader) - * this returns a matrix that will normalise map filter cords in the filter to screen space - * - * @param {PIXI.Matrix} outputMatrix - the matrix to output to. - * @return {PIXI.Matrix} The mapped matrix. - */ - calculateScreenSpaceMatrix(outputMatrix) - { - const currentState = this.activeState; - - return filterTransforms.calculateScreenSpaceMatrix( - outputMatrix, - currentState.sourceFrame, - currentState.destinationFrame - ); - } - - /** - * This will map the filter coord so that a texture can be used based on the transform of a sprite - * - * @param {PIXI.Matrix} outputMatrix - The matrix to output to. - * @param {PIXI.Sprite} sprite - The sprite to map to. - * @return {PIXI.Matrix} The mapped matrix. - */ - calculateSpriteMatrix(outputMatrix, sprite) - { - const currentState = this.activeState; - - return filterTransforms.calculateSpriteMatrix( - outputMatrix, - currentState.sourceFrame, - currentState.destinationFrame, - sprite - ); - } - - /** - * Destroys this Filter System. - * - * @param {boolean} [contextLost=false] context was lost, do not free shaders - * - */ - destroy(contextLost = false) - { - if (!contextLost) - { - this.emptyPool(); - } - else - { - this.texturePool = {}; - } - } - - /** - * Gets a Power-of-Two render texture. - * - * TODO move to a seperate class could be on renderer? - * also - could cause issue with multiple contexts? - * - * @private - * @param {WebGLRenderingContext} gl - The webgl rendering context - * @param {number} minWidth - The minimum width of the render target. - * @param {number} minHeight - The minimum height of the render target. - * @param {number} resolution - The resolution of the render target. - * @return {PIXI.RenderTarget} The new render target. - */ - getPotFilterTexture(minWidth, minHeight, resolution) - { - minWidth = bitTwiddle.nextPow2(minWidth); - minHeight = bitTwiddle.nextPow2(minHeight); - resolution = resolution || 1; - - const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); - - if (!this.texturePool[key]) - { - this.texturePool[key] = []; - } - - let renderTexture = this.texturePool[key].pop(); - - if (!renderTexture) - { - // temporary bypass cache.. - // internally - this will cause a texture to be bound.. - renderTexture = RenderTexture.create({ - width: minWidth, - height: minHeight, - resolution, - }); - } - - return renderTexture; - } - - /** - * Gets extra render texture to use inside current filter - * - * @param {number} resolution resolution of the renderTexture - * @returns {PIXI.RenderTexture} - */ - getFilterTexture(resolution) - { - const rt = this.activeState.renderTexture; - - const filterTexture = this.getPotFilterTexture(rt.width, rt.height, resolution || rt.baseTexture.resolution); - - filterTexture.filterFrame = rt.filterFrame; - - return filterTexture; - } - - /** - * Frees a render texture back into the pool. - * - * @param {PIXI.RenderTarget} renderTexture - The renderTarget to free - */ - returnFilterTexture(renderTexture) - { - renderTexture.filterFrame = null; - - const base = renderTexture.baseTexture; - - const minWidth = base.width; - const minHeight = base.height; - - const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); - - this.texturePool[key].push(renderTexture); - } - - /** - * Empties the texture pool. - * - */ - emptyPool() - { - for (const i in this.texturePool) - { - const textures = this.texturePool[i]; - - if (textures) - { - for (let j = 0; j < textures.length; j++) - { - textures[j].destroy(true); - } - } - } - - this.texturePool = {}; - } -} diff --git a/packages/core/src/renderers/systems/geometry/GLBuffer.js b/packages/core/src/renderers/systems/geometry/GLBuffer.js deleted file mode 100644 index 0477e8a..0000000 --- a/packages/core/src/renderers/systems/geometry/GLBuffer.js +++ /dev/null @@ -1,9 +0,0 @@ -export default class GLBuffer -{ - constructor(buffer) - { - this.buffer = buffer; - this.updateID = -1; - this.byteLength = -1; - } -} diff --git a/packages/core/src/renderers/systems/geometry/GeometrySystem.js b/packages/core/src/renderers/systems/geometry/GeometrySystem.js deleted file mode 100644 index 26c8e9e..0000000 --- a/packages/core/src/renderers/systems/geometry/GeometrySystem.js +++ /dev/null @@ -1,388 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; -import GLBuffer from './GLBuffer'; - -const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class GeometrySystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this._activeGeometry = null; - this._activeVao = null; - - this.hasVao = true; - this.hasInstance = true; - } - - /** - * Sets up the renderer context and necessary buffers. - * - * @private - */ - contextChange() - { - const gl = this.gl = this.renderer.gl; - - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - - // webgl2 - if (!gl.createVertexArray) - { - // webgl 1! - let nativeVaoExtension = this.renderer.context.extensions.vertexArrayObject; - - if (this.renderer.legacy) - { - nativeVaoExtension = null; - } - - if (nativeVaoExtension) - { - gl.createVertexArray = () => - nativeVaoExtension.createVertexArrayOES(); - - gl.bindVertexArray = (vao) => - nativeVaoExtension.bindVertexArrayOES(vao); - - gl.deleteVertexArray = (vao) => - nativeVaoExtension.deleteVertexArrayOES(vao); - } - else - { - this.hasVao = false; - gl.createVertexArray = () => - { - // empty - }; - - gl.bindVertexArray = () => - { - // empty - }; - - gl.deleteVertexArray = () => - { - // empty - }; - } - } - - if (!gl.vertexAttribDivisor) - { - const instanceExt = gl.getExtension('ANGLE_instanced_arrays'); - - if (instanceExt) - { - gl.vertexAttribDivisor = (a, b) => - instanceExt.vertexAttribDivisorANGLE(a, b); - - gl.drawElementsInstanced = (a, b, c, d, e) => - instanceExt.drawElementsInstancedANGLE(a, b, c, d, e); - - gl.drawArraysInstanced = (a, b, c, d) => - instanceExt.drawArraysInstancedANGLE(a, b, c, d); - } - else - { - this.hasInstance = false; - } - } - } - - /** - * Binds geometry so that is can be drawn. Creating a Vao if required - * @private - * @param {PIXI.Geometry} geometry instance of geometry to bind - */ - bind(geometry, shader) - { - shader = shader || this.renderer.shader.shader; - - const gl = this.gl; - - // not sure the best way to address this.. - // currently different shaders require different VAOs for the same geometry - // Still mulling over the best way to solve this one.. - // will likely need to modify the shader attribute locations at run time! - let vaos = geometry.glVertexArrayObjects[this.CONTEXT_UID]; - - if (!vaos) - { - geometry.glVertexArrayObjects[this.CONTEXT_UID] = vaos = {}; - } - - const vao = vaos[shader.program.id] || this.initGeometryVao(geometry, shader.program); - - this._activeGeometry = geometry; - - if (this._activeVao !== vao) - { - this._activeVao = vao; - - if (this.hasVao) - { - gl.bindVertexArray(vao); - } - else - { - this.activateVao(geometry, shader.program); - } - } - - // TODO - optimise later! - // don't need to loop through if nothing changed! - // maybe look to add an 'autoupdate' to geometry? - this.updateBuffers(); - } - - reset() - { - this.unbind(); - } - - updateBuffers() - { - const geometry = this._activeGeometry; - const gl = this.gl; - - for (let i = 0; i < geometry.buffers.length; i++) - { - const buffer = geometry.buffers[i]; - - const glBuffer = buffer._glBuffers[this.CONTEXT_UID]; - - if (buffer._updateID !== glBuffer.updateID) - { - glBuffer.updateID = buffer._updateID; - - // TODO can cache this on buffer! maybe added a getter / setter? - const type = buffer.index ? gl.ELEMENT_ARRAY_BUFFER : gl.ARRAY_BUFFER; - const drawType = buffer.static ? gl.STATIC_DRAW : gl.DYNAMIC_DRAW; - - gl.bindBuffer(type, glBuffer.buffer); - - if (glBuffer.byteLength >= buffer.data.byteLength) - { - // offset is always zero for now! - gl.bufferSubData(type, 0, buffer.data); - } - else - { - gl.bufferData(type, buffer.data, drawType); - } - } - } - } - - checkCompatability(geometry, program) - { - // geometry must have at least all the attributes that the shader requires. - const geometryAttributes = geometry.attributes; - const shaderAttributes = program.attributeData; - - for (const j in shaderAttributes) - { - if (!geometryAttributes[j]) - { - throw new Error(`shader and geometry incompatible, geometry missing the "${j}" attribute`); - } - } - } - - /** - * Creates a Vao with the same structure as the geometry and stores it on the geometry. - * @private - * @param {PIXI.Geometry} geometry instance of geometry to to generate Vao for - */ - initGeometryVao(geometry, program) - { - this.checkCompatability(geometry, program); - - const gl = this.gl; - const CONTEXT_UID = this.CONTEXT_UID; - const buffers = geometry.buffers; - const attributes = geometry.attributes; - - const tempStride = {}; - const tempStart = {}; - - for (const j in buffers) - { - tempStride[j] = 0; - tempStart[j] = 0; - } - - for (const j in attributes) - { - if (!attributes[j].size && program.attributeData[j]) - { - attributes[j].size = program.attributeData[j].size; - } - - tempStride[attributes[j].buffer] += attributes[j].size * byteSizeMap[attributes[j].type]; - } - - for (const j in attributes) - { - const attribute = attributes[j]; - const attribSize = attribute.size; - - if (attribute.stride === undefined) - { - if (tempStride[attribute.buffer] === attribSize * byteSizeMap[attribute.type]) - { - attribute.stride = 0; - } - else - { - attribute.stride = tempStride[attribute.buffer]; - } - } - - if (attribute.start === undefined) - { - attribute.start = tempStart[attribute.buffer]; - - tempStart[attribute.buffer] += attribSize * byteSizeMap[attribute.type]; - } - } - - // first update - and create the buffers! - // only create a gl buffer if it actually gets - for (let i = 0; i < buffers.length; i++) - { - const buffer = buffers[i]; - - if (!buffer._glBuffers[CONTEXT_UID]) - { - buffer._glBuffers[CONTEXT_UID] = new GLBuffer(gl.createBuffer()); - } - } - - // TODO - maybe make this a data object? - // lets wait to see if we need to first! - const vao = gl.createVertexArray(); - - gl.bindVertexArray(vao); - - this.activateVao(geometry, program); - - gl.bindVertexArray(null); - - // add it to the cache! - geometry.glVertexArrayObjects[this.CONTEXT_UID][program.id] = vao; - - return vao; - } - - activateVao(geometry, program) - { - const gl = this.gl; - const CONTEXT_UID = this.CONTEXT_UID; - const buffers = geometry.buffers; - const attributes = geometry.attributes; - - if (geometry.indexBuffer) - { - // first update the index buffer if we have one.. - gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, geometry.indexBuffer._glBuffers[CONTEXT_UID].buffer); - } - - let lastBuffer = null; - - // add a new one! - for (const j in attributes) - { - const attribute = attributes[j]; - const buffer = buffers[attribute.buffer]; - const glBuffer = buffer._glBuffers[CONTEXT_UID]; - - if (program.attributeData[j]) - { - if (lastBuffer !== glBuffer) - { - gl.bindBuffer(gl.ARRAY_BUFFER, glBuffer.buffer); - - lastBuffer = glBuffer; - } - - const location = program.attributeData[j].location; - - // TODO introduce state again - // we can optimise this for older devices that have no VAOs - gl.enableVertexAttribArray(location); - - gl.vertexAttribPointer(location, - attribute.size, - attribute.type || gl.FLOAT, - attribute.normalized, - attribute.stride, - attribute.start); - - if (attribute.instance) - { - // TODO calculate instance count based of this... - if (this.hasInstance) - { - gl.vertexAttribDivisor(location, 1); - } - else - { - throw new Error('geometry error, GPU Instancing is not supported on this device'); - } - } - } - } - } - - draw(type, size, start, instanceCount) - { - const gl = this.gl; - const geometry = this._activeGeometry; - - // TODO.. this should not change so maybe cache the function? - - if (geometry.indexBuffer) - { - if (geometry.instanced) - { - /* eslint-disable max-len */ - gl.drawElementsInstanced(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2, instanceCount || 1); - /* eslint-enable max-len */ - } - else - { - gl.drawElements(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2); - } - } - else - if (geometry.instanced) - { - // TODO need a better way to calculate size.. - gl.drawArraysInstanced(type, start, size || geometry.getSize(), instanceCount || 1); - } - else - { - gl.drawArrays(type, start, size || geometry.getSize()); - } - - return this; - } - - unbind() - { - this.gl.bindVertexArray(null); - this._activeVao = null; - this._activeGeometry = null; - } -} diff --git a/packages/core/src/renderers/systems/geometry/setVertexAttribArrays.js b/packages/core/src/renderers/systems/geometry/setVertexAttribArrays.js deleted file mode 100644 index 23b6959..0000000 --- a/packages/core/src/renderers/systems/geometry/setVertexAttribArrays.js +++ /dev/null @@ -1,54 +0,0 @@ -// var GL_MAP = {}; - -/** - * @param gl {WebGLRenderingContext} The current WebGL context - * @param attribs {*} - * @param state {*} - */ -export default function setVertexAttribArrays(gl, attribs, state) -{ - let i; - - if (state) - { - const tempAttribState = state.tempAttribState; - const attribState = state.attribState; - - for (i = 0; i < tempAttribState.length; i++) - { - tempAttribState[i] = false; - } - - // set the new attribs - for (i = 0; i < attribs.length; i++) - { - tempAttribState[attribs[i].attribute.location] = true; - } - - for (i = 0; i < attribState.length; i++) - { - if (attribState[i] !== tempAttribState[i]) - { - attribState[i] = tempAttribState[i]; - - if (state.attribState[i]) - { - gl.enableVertexAttribArray(i); - } - else - { - gl.disableVertexAttribArray(i); - } - } - } - } - else - { - for (i = 0; i < attribs.length; i++) - { - const attrib = attribs[i]; - - gl.enableVertexAttribArray(attrib.attribute.location); - } - } -} diff --git a/packages/core/src/renderers/systems/index.js b/packages/core/src/renderers/systems/index.js deleted file mode 100644 index c843f03..0000000 --- a/packages/core/src/renderers/systems/index.js +++ /dev/null @@ -1,6 +0,0 @@ -/** - * Systems are individual components to the Renderer pipeline. - * @namespace PIXI.systems - */ -export { default as WebGLSystem } from './WebGLSystem'; -export { default as FilterSystem } from './filter/FilterSystem'; diff --git a/packages/core/src/renderers/systems/shader/GLProgram.js b/packages/core/src/renderers/systems/shader/GLProgram.js deleted file mode 100644 index 92980ca..0000000 --- a/packages/core/src/renderers/systems/shader/GLProgram.js +++ /dev/null @@ -1,43 +0,0 @@ -/** - * Helper class to create a webGL Program - * - * @class - * @private - */ -export default class GLProgram -{ - constructor(program, uniformData) - { - /** - * The shader program - * - * @member {WebGLProgram} - */ - this.program = program; - - /** - * holds the uniform data which contains uniform locations - * and current uniform values used for caching and preventing undeed GPU commands - * @type {Object} - */ - this.uniformData = uniformData; - - /** - * uniformGroups holds the various upload functions for the shader. Each uniform group - * and program have a unique upload function generated. - * @type {Object} - */ - this.uniformGroups = {}; - } - - /** - * Destroys this program - * TODO - */ - destroy() - { - this.uniformData = null; - this.uniformGroups = null; - this.program = null; - } -} diff --git a/packages/core/src/renderers/systems/shader/ShaderSystem.js b/packages/core/src/renderers/systems/shader/ShaderSystem.js deleted file mode 100644 index 6cf57b6..0000000 --- a/packages/core/src/renderers/systems/shader/ShaderSystem.js +++ /dev/null @@ -1,170 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; -import GLProgram from './GLProgram'; -import generateUniformsSync from '../../../shader/generateUniformsSync'; -import defaultValue from '../../../shader/defaultValue'; -import compileProgram from './shader/compileProgram'; - -let UID = 0; - -/** - * Helper class to create a webGL Texture - * - * @class - * @memberof PIXI - */ -export default class ShaderSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - A reference to the current renderer - */ - constructor(renderer) - { - super(renderer); - - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = null; - - this.shader = null; - this.program = null; - - this.id = UID++; - } - - contextChange(gl) - { - this.gl = gl; - } - - /** - * Changes the current shader to the one given in parameter - * - * @param {PIXI.Shader} shader - the new shader - * @param {boolean} dontSync - false if the shader should automatically sync its uniforms. - * @returns {PIXI.glCore.glProgram} the glProgram that belongs to the shader. - */ - bind(shader, dontSync) - { - shader.uniforms.globals = this.renderer.globalUniforms; - - const program = shader.program; - const glProgram = program.glPrograms[this.renderer.CONTEXT_UID] || this.generateShader(shader); - - this.shader = shader; - - // TODO - some current pixi plugins bypass this.. so it not safe to use yet.. - if (this.program !== program) - { - this.program = program; - this.gl.useProgram(glProgram.program); - } - - if (!dontSync) - { - this.syncUniformGroup(shader.uniformGroup); - } - - return glProgram; - } - - /** - * Uploads the uniforms values to the currently bound shader. - * - * @param {object} uniforms - the uniforms valiues that be applied to the current shader - */ - setUniforms(uniforms) - { - const shader = this.shader.program; - const glProgram = shader.glPrograms[this.renderer.CONTEXT_UID]; - - shader.syncUniforms(glProgram.uniformData, uniforms, this.renderer); - } - - syncUniformGroup(group) - { - const glProgram = this.getglProgram(); - - if (!group.static || group.dirtyId !== glProgram.uniformGroups[group.id]) - { - glProgram.uniformGroups[group.id] = group.dirtyId; - const syncFunc = group.syncUniforms[this.shader.program.id] || this.createSyncGroups(group); - - syncFunc(glProgram.uniformData, group.uniforms, this.renderer); - } - } - - createSyncGroups(group) - { - group.syncUniforms[this.shader.program.id] = generateUniformsSync(group, this.shader.program.uniformData); - - return group.syncUniforms[this.shader.program.id]; - } - - /** - * Returns the underlying GLShade rof the currently bound shader. - * This can be handy for when you to have a little more control over the setting of your uniforms. - * - * @return {PIXI.glCore.Shader} the glProgram for the currently bound Shader for this context - */ - getglProgram() - { - if (this.shader) - { - return this.shader.program.glPrograms[this.renderer.CONTEXT_UID]; - } - - return null; - } - - /** - * Generates a glProgram verion of the Shader provided. - * - * @private - * @param {PIXI.Shader} shader the shader that the glProgram will be based on. - * @return {PIXI.glCore.glProgram} A shiney new glProgram - */ - generateShader(shader) - { - const gl = this.gl; - - const program = shader.program; - - const attribMap = {}; - - for (const i in program.attributeData) - { - attribMap[i] = program.attributeData[i].location; - } - - const shaderProgram = compileProgram(gl, program.vertexSrc, program.fragmentSrc, attribMap); - const uniformData = {}; - - for (const i in program.uniformData) - { - const data = program.uniformData[i]; - - uniformData[i] = { - location: gl.getUniformLocation(shaderProgram, i), - value: defaultValue(data.type, data.size), - }; - } - - const glProgram = new GLProgram(shaderProgram, uniformData); - - program.glPrograms[this.renderer.CONTEXT_UID] = glProgram; - - return glProgram; - } - - /** - * Destroys this System and removes all its textures - */ - destroy() - { - // TODO implement destroy method for ShaderSystem - this.destroyed = true; - } -} diff --git a/packages/core/src/renderers/systems/shader/shader/compileProgram.js b/packages/core/src/renderers/systems/shader/shader/compileProgram.js deleted file mode 100644 index 8c6ccb9..0000000 --- a/packages/core/src/renderers/systems/shader/shader/compileProgram.js +++ /dev/null @@ -1,78 +0,0 @@ -/** - * @method compileProgram - * @memberof PIXI.glCore.shader - * @param gl {WebGLRenderingContext} The current WebGL context {WebGLProgram} - * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. - * @param fragmentSrc {string|string[]} The fragment shader source as an array of strings. - * @param attributeLocations {Object} An attribute location map that lets you manually set the attribute locations - * @return {WebGLProgram} the shader program - */ -export default function compileProgram(gl, vertexSrc, fragmentSrc, attributeLocations) -{ - const glVertShader = compileShader(gl, gl.VERTEX_SHADER, vertexSrc); - const glFragShader = compileShader(gl, gl.FRAGMENT_SHADER, fragmentSrc); - - let program = gl.createProgram(); - - gl.attachShader(program, glVertShader); - gl.attachShader(program, glFragShader); - - // optionally, set the attributes manually for the program rather than letting WebGL decide.. - if (attributeLocations) - { - for (const i in attributeLocations) - { - gl.bindAttribLocation(program, attributeLocations[i], i); - } - } - - gl.linkProgram(program); - - // if linking fails, then log and cleanup - if (!gl.getProgramParameter(program, gl.LINK_STATUS)) - { - console.error('Pixi.js Error: Could not initialize shader.'); - console.error('gl.VALIDATE_STATUS', gl.getProgramParameter(program, gl.VALIDATE_STATUS)); - console.error('gl.getError()', gl.getError()); - - // if there is a program info log, log it - if (gl.getProgramInfoLog(program) !== '') - { - console.warn('Pixi.js Warning: gl.getProgramInfoLog()', gl.getProgramInfoLog(program)); - } - - gl.deleteProgram(program); - program = null; - } - - // clean up some shaders - gl.deleteShader(glVertShader); - gl.deleteShader(glFragShader); - - return program; -} - -/** - * @private - * @param gl {WebGLRenderingContext} The current WebGL context {WebGLProgram} - * @param type {Number} the type, can be either VERTEX_SHADER or FRAGMENT_SHADER - * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. - * @return {WebGLShader} the shader - */ -function compileShader(gl, type, src) -{ - const shader = gl.createShader(type); - - gl.shaderSource(shader, src); - gl.compileShader(shader); - - if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) - { - console.warn(src); - console.error(gl.getShaderInfoLog(shader)); - - return null; - } - - return shader; -} diff --git a/packages/core/src/renderers/systems/shader/shader/defaultValue.js b/packages/core/src/renderers/systems/shader/shader/defaultValue.js deleted file mode 100644 index 97290bd..0000000 --- a/packages/core/src/renderers/systems/shader/shader/defaultValue.js +++ /dev/null @@ -1,79 +0,0 @@ -/** - * @method defaultValue - * @memberof PIXI.glCore.shader - * @param type {String} Type of value - * @param size {Number} - */ -export default function defaultValue(type, size) -{ - switch (type) - { - case 'float': - return 0; - - case 'vec2': - return new Float32Array(2 * size); - - case 'vec3': - return new Float32Array(3 * size); - - case 'vec4': - return new Float32Array(4 * size); - - case 'int': - case 'sampler2D': - case 'sampler2DArray': - return 0; - - case 'ivec2': - return new Int32Array(2 * size); - - case 'ivec3': - return new Int32Array(3 * size); - - case 'ivec4': - return new Int32Array(4 * size); - - case 'bool': - return false; - - case 'bvec2': - - return booleanArray(2 * size); - - case 'bvec3': - return booleanArray(3 * size); - - case 'bvec4': - return booleanArray(4 * size); - - case 'mat2': - return new Float32Array([1, 0, - 0, 1]); - - case 'mat3': - return new Float32Array([1, 0, 0, - 0, 1, 0, - 0, 0, 1]); - - case 'mat4': - return new Float32Array([1, 0, 0, 0, - 0, 1, 0, 0, - 0, 0, 1, 0, - 0, 0, 0, 1]); - } - - return null; -} - -function booleanArray(size) -{ - const array = new Array(size); - - for (let i = 0; i < array.length; i++) - { - array[i] = false; - } - - return array; -} diff --git a/packages/core/src/renderers/systems/shader/shader/index.js b/packages/core/src/renderers/systems/shader/shader/index.js deleted file mode 100644 index ba7a317..0000000 --- a/packages/core/src/renderers/systems/shader/shader/index.js +++ /dev/null @@ -1,8 +0,0 @@ -/** - * @namespace PIXI.glCore.shader - */ -export { default as compileProgram } from './compileProgram'; -export { default as defaultValue } from './defaultValue'; -export { default as setPrecision } from './setPrecision'; -export { default as mapSize } from './mapSize'; -export { default as mapType } from './mapType'; diff --git a/packages/core/src/renderers/systems/shader/shader/mapSize.js b/packages/core/src/renderers/systems/shader/shader/mapSize.js deleted file mode 100644 index b681554..0000000 --- a/packages/core/src/renderers/systems/shader/shader/mapSize.js +++ /dev/null @@ -1,33 +0,0 @@ -const GLSL_TO_SIZE = { - float: 1, - vec2: 2, - vec3: 3, - vec4: 4, - - int: 1, - ivec2: 2, - ivec3: 3, - ivec4: 4, - - bool: 1, - bvec2: 2, - bvec3: 3, - bvec4: 4, - - mat2: 4, - mat3: 9, - mat4: 16, - - sampler2D: 1, -}; - -/** - * @method mapSize - * @memberof PIXI.glCore.shader - * @param type {String} - * @return {Number} - */ -export default function mapSize(type) -{ - return GLSL_TO_SIZE[type]; -} diff --git a/packages/core/src/renderers/systems/shader/shader/mapType.js b/packages/core/src/renderers/systems/shader/shader/mapType.js deleted file mode 100644 index d288b7c..0000000 --- a/packages/core/src/renderers/systems/shader/shader/mapType.js +++ /dev/null @@ -1,45 +0,0 @@ -let GL_TABLE = null; - -const GL_TO_GLSL_TYPES = { - FLOAT: 'float', - FLOAT_VEC2: 'vec2', - FLOAT_VEC3: 'vec3', - FLOAT_VEC4: 'vec4', - - INT: 'int', - INT_VEC2: 'ivec2', - INT_VEC3: 'ivec3', - INT_VEC4: 'ivec4', - - BOOL: 'bool', - BOOL_VEC2: 'bvec2', - BOOL_VEC3: 'bvec3', - BOOL_VEC4: 'bvec4', - - FLOAT_MAT2: 'mat2', - FLOAT_MAT3: 'mat3', - FLOAT_MAT4: 'mat4', - - SAMPLER_2D: 'sampler2D', - SAMPLER_CUBE: 'samplerCube', - SAMPLER_2D_ARRAY: 'sampler2DArray', -}; - -export default function mapType(gl, type) -{ - if (!GL_TABLE) - { - const typeNames = Object.keys(GL_TO_GLSL_TYPES); - - GL_TABLE = {}; - - for (let i = 0; i < typeNames.length; ++i) - { - const tn = typeNames[i]; - - GL_TABLE[gl[tn]] = GL_TO_GLSL_TYPES[tn]; - } - } - - return GL_TABLE[type]; -} diff --git a/packages/core/src/renderers/systems/shader/shader/setPrecision.js b/packages/core/src/renderers/systems/shader/shader/setPrecision.js deleted file mode 100644 index e2c1f34..0000000 --- a/packages/core/src/renderers/systems/shader/shader/setPrecision.js +++ /dev/null @@ -1,18 +0,0 @@ -/** - * Sets the float precision on the shader. If the precision is already present this function will do nothing - * @param {string} src the shader source - * @param {string} precision The float precision of the shader. Options are 'lowp', 'mediump' or 'highp'. - * - * @return {string} modified shader source - */ -export default function setPrecision(src, precision) -{ - src = src.trim(); - - if (src.substring(0, 9) !== 'precision' && src.substring(0, 1) !== '#') - { - return `precision ${precision} float;\n${src}`; - } - - return src; -} diff --git a/packages/core/src/renderers/systems/textures/GLTexture.js b/packages/core/src/renderers/systems/textures/GLTexture.js deleted file mode 100644 index c3fb8dc..0000000 --- a/packages/core/src/renderers/systems/textures/GLTexture.js +++ /dev/null @@ -1,33 +0,0 @@ -export default class GLTexture -{ - constructor(texture) - { - /** - * The WebGL texture - * - * @member {WebGLTexture} - */ - this.texture = texture; - - this.width = -1; - this.height = -1; - - /** - * Texture contents dirty flag - * @member {number} - */ - this.dirtyId = -1; - - /** - * Texture style dirty flag - * @type {number} - */ - this.dirtyStyleId = -1; - - /** - * Whether mip levels has to be generated - * @type {boolean} - */ - this.mipmap = false; - } -} diff --git a/packages/core/src/renderers/systems/textures/TextureGCSystem.js b/packages/core/src/renderers/systems/textures/TextureGCSystem.js deleted file mode 100644 index b089938..0000000 --- a/packages/core/src/renderers/systems/textures/TextureGCSystem.js +++ /dev/null @@ -1,110 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; -import { GC_MODES } from '@pixi/constants'; -import { settings } from '@pixi/settings'; - -/** - * TextureGarbageCollector. This class manages the GPU and ensures that it does not get clogged - * up with textures that are no longer being used. - * - * @class - * @memberof PIXI - */ -export default class TextureGCSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this.count = 0; - this.checkCount = 0; - this.maxIdle = settings.GC_MAX_IDLE; - this.checkCountMax = settings.GC_MAX_CHECK_COUNT; - this.mode = settings.GC_MODE; - } - - /** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ - postrender() - { - this.count++; - - if (this.mode === GC_MODES.MANUAL) - { - return; - } - - this.checkCount++; - - if (this.checkCount > this.checkCountMax) - { - this.checkCount = 0; - - this.run(); - } - } - - /** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ - run() - { - const tm = this.renderer.texture; - const managedTextures = tm.managedTextures; - let wasRemoved = false; - - for (let i = 0; i < managedTextures.length; i++) - { - const texture = managedTextures[i]; - - // only supports non generated textures at the moment! - if (!texture.frameBuffer && this.count - texture.touched > this.maxIdle) - { - tm.destroyTexture(texture, true); - managedTextures[i] = null; - wasRemoved = true; - } - } - - if (wasRemoved) - { - let j = 0; - - for (let i = 0; i < managedTextures.length; i++) - { - if (managedTextures[i] !== null) - { - managedTextures[j++] = managedTextures[i]; - } - } - - managedTextures.length = j; - } - } - - /** - * Removes all the textures within the specified displayObject and its children from the GPU - * - * @param {PIXI.DisplayObject} displayObject - the displayObject to remove the textures from. - */ - unload(displayObject) - { - const tm = this.renderer.textureSystem; - - // only destroy non generated textures - if (displayObject._texture && displayObject._texture._glRenderTargets) - { - tm.destroyTexture(displayObject._texture); - } - - for (let i = displayObject.children.length - 1; i >= 0; i--) - { - this.unload(displayObject.children[i]); - } - } -} diff --git a/packages/core/src/renderers/systems/textures/TextureSystem.js b/packages/core/src/renderers/systems/textures/TextureSystem.js deleted file mode 100644 index ccc5de9..0000000 --- a/packages/core/src/renderers/systems/textures/TextureSystem.js +++ /dev/null @@ -1,281 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; -import GLTexture from './GLTexture'; -import { removeItems } from '@pixi/utils'; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class TextureSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - // TODO set to max textures... - this.boundTextures = [ - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - ]; - - this.currentLocation = -1; - - this.managedTextures = []; - } - - /** - * Sets up the renderer context and necessary buffers. - * - * @private - */ - contextChange() - { - const gl = this.gl = this.renderer.gl; - - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - - // TODO move this.. to a nice make empty textures class.. - this.emptyTextures = {}; - - const emptyTexture2D = new GLTexture(gl.createTexture()); - - gl.bindTexture(gl.TEXTURE_2D, emptyTexture2D.texture); - gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array(4)); - - this.emptyTextures[gl.TEXTURE_2D] = emptyTexture2D; - this.emptyTextures[gl.TEXTURE_CUBE_MAP] = new GLTexture(gl.createTexture()); - - gl.bindTexture(gl.TEXTURE_CUBE_MAP, this.emptyTextures[gl.TEXTURE_CUBE_MAP].texture); - - let i; - - for (i = 0; i < 6; i++) - { - gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, null); - } - - gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_MAG_FILTER, gl.LINEAR); - gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_MIN_FILTER, gl.LINEAR); - - for (i = 0; i < this.boundTextures.length; i++) - { - this.bind(null, i); - } - } - - bind(texture, location) - { - const gl = this.gl; - - location = location || 0; - - if (this.currentLocation !== location) - { - this.currentLocation = location; - gl.activeTexture(gl.TEXTURE0 + location); - } - - if (texture) - { - texture = texture.baseTexture || texture; - - if (texture.valid) - { - texture.touched = this.renderer.textureGC.count; - - const glTexture = texture._glTextures[this.CONTEXT_UID] || this.initTexture(texture); - - gl.bindTexture(texture.target, glTexture.texture); - - if (glTexture.dirtyId !== texture.dirtyId) - { - this.updateTexture(texture); - } - - this.boundTextures[location] = texture; - } - } - else - { - gl.bindTexture(gl.TEXTURE_2D, this.emptyTextures[gl.TEXTURE_2D].texture); - this.boundTextures[location] = null; - } - } - - unbind(texture) - { - const gl = this.gl; - - for (let i = 0; i < this.boundTextures.length; i++) - { - if (this.boundTextures[i] === texture) - { - if (this.currentLocation !== i) - { - gl.activeTexture(gl.TEXTURE0 + i); - this.currentLocation = i; - } - - gl.bindTexture(gl.TEXTURE_2D, this.emptyTextures[texture.target].texture); - this.boundTextures[i] = null; - } - } - } - - initTexture(texture) - { - const glTexture = new GLTexture(this.gl.createTexture()); - - // guarentee an update.. - glTexture.dirtyId = -1; - - texture._glTextures[this.CONTEXT_UID] = glTexture; - - this.managedTextures.push(texture); - texture.on('dispose', this.destroyTexture, this); - - return glTexture; - } - - updateTexture(texture) - { - const glTexture = texture._glTextures[this.CONTEXT_UID]; - const renderer = this.renderer; - - if (texture.resource && texture.resource.upload(renderer, texture, glTexture)) - { - // texture is uploaded, dont do anything! - } - else - { - // default, renderTexture-like logic - const width = texture.realWidth; - const height = texture.realHeight; - const gl = renderer.gl; - - if (glTexture.width !== width - || glTexture.height !== height - || glTexture.dirtyId < 0) - { - glTexture.width = width; - glTexture.height = height; - - gl.texImage2D(texture.target, 0, - texture.format, - width, - height, - 0, - texture.format, - texture.type, - null); - } - } - - // lets only update what changes.. - if (texture.dirtyStyleId !== glTexture.dirtyStyleId) - { - this.updateTextureStyle(texture); - } - glTexture.dirtyId = texture.dirtyId; - } - - /** - * Deletes the texture from WebGL - * - * @param {PIXI.BaseTexture|PIXI.Texture} texture - the texture to destroy - * @param {boolean} [skipRemove=false] - Whether to skip removing the texture from the TextureManager. - */ - destroyTexture(texture, skipRemove) - { - const gl = this.gl; - - texture = texture.baseTexture || texture; - - if (texture._glTextures[this.renderer.CONTEXT_UID]) - { - this.unbind(texture); - - gl.deleteTexture(texture._glTextures[this.renderer.CONTEXT_UID].texture); - texture.off('dispose', this.destroyTexture, this); - - delete texture._glTextures[this.renderer.CONTEXT_UID]; - - if (!skipRemove) - { - const i = this.managedTextures.indexOf(texture); - - if (i !== -1) - { - removeItems(this.managedTextures, i, 1); - } - } - } - } - - updateTextureStyle(texture) - { - const glTexture = texture._glTextures[this.CONTEXT_UID]; - - glTexture.mipmap = texture.mipmap && texture.isPowerOfTwo; - if (!glTexture) - { - return; - } - - if (texture.resource && texture.resource.style(this.renderer, texture, glTexture)) - { - // style is set, dont do anything! - } - else - { - this.setStyle(texture, glTexture); - } - - glTexture.dirtyStyleId = texture.dirtyStyleId; - } - - setStyle(texture, glTexture) - { - const gl = this.gl; - - if (glTexture.mipmap) - { - gl.generateMipmap(texture.target); - } - - gl.texParameteri(texture.target, gl.TEXTURE_WRAP_S, texture.wrapMode); - gl.texParameteri(texture.target, gl.TEXTURE_WRAP_T, texture.wrapMode); - - if (glTexture.mipmap) - { - /* eslint-disable max-len */ - gl.texParameteri(texture.target, gl.TEXTURE_MIN_FILTER, texture.scaleMode ? gl.LINEAR_MIPMAP_LINEAR : gl.NEAREST_MIPMAP_NEAREST); - /* eslint-disable max-len */ - } - else - { - gl.texParameteri(texture.target, gl.TEXTURE_MIN_FILTER, texture.scaleMode ? gl.LINEAR : gl.NEAREST); - } - - gl.texParameteri(texture.target, gl.TEXTURE_MAG_FILTER, texture.scaleMode ? gl.LINEAR : gl.NEAREST); - } -} diff --git a/packages/core/src/renderers/utils/ObjectRenderer.js b/packages/core/src/renderers/utils/ObjectRenderer.js deleted file mode 100644 index f67a273..0000000 --- a/packages/core/src/renderers/utils/ObjectRenderer.js +++ /dev/null @@ -1,48 +0,0 @@ -import WebGLSystem from '../systems/WebGLSystem'; - -/** - * Base for a common object renderer that can be used as a system renderer plugin. - * - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class ObjectRenderer extends WebGLSystem -{ - /** - * Starts the renderer and sets the shader - * - */ - start() - { - // set the shader.. - } - - /** - * Stops the renderer - * - */ - stop() - { - this.flush(); - } - - /** - * Stub method for rendering content and emptying the current batch. - * - */ - flush() - { - // flush! - } - - /** - * Renders an object - * - * @param {PIXI.DisplayObject} object - The object to render. - */ - render(object) // eslint-disable-line no-unused-vars - { - // render the object - } -} diff --git a/packages/core/src/renderers/utils/Quad.js b/packages/core/src/renderers/utils/Quad.js deleted file mode 100644 index 5c1517b..0000000 --- a/packages/core/src/renderers/utils/Quad.js +++ /dev/null @@ -1,26 +0,0 @@ -import Geometry from '../../geometry/Geometry'; - -/** - * Helper class to create a quad - * - * @class - * @memberof PIXI - */ -export default class Quad extends Geometry -{ - /** - * just a quad - */ - constructor() - { - super(); - - this.addAttribute('aVertexPosition', [ - 0, 0, - 1, 0, - 1, 1, - 0, 1, - ]) - .addIndex([0, 1, 3, 2]); - } -} diff --git a/packages/core/src/renderers/utils/QuadUv.js b/packages/core/src/renderers/utils/QuadUv.js deleted file mode 100644 index 97bda89..0000000 --- a/packages/core/src/renderers/utils/QuadUv.js +++ /dev/null @@ -1,103 +0,0 @@ -import Geometry from '../../geometry/Geometry'; -import Buffer from '../../geometry/Buffer'; - -/** - * Helper class to create a quad with uvs like in v4 - * - * @class - * @memberof PIXI - */ -export default class QuadUv extends Geometry -{ - constructor() - { - super(); - - /** - * An array of vertices - * - * @member {Float32Array} - */ - this.vertices = new Float32Array([ - -1, -1, - 1, -1, - 1, 1, - -1, 1, - ]); - - /** - * The Uvs of the quad - * - * @member {Float32Array} - */ - this.uvs = new Float32Array([ - 0, 0, - 1, 0, - 1, 1, - 0, 1, - ]); - - this.vertexBuffer = new Buffer(this.vertices); - this.uvBuffer = new Buffer(this.uvs); - - this.addAttribute('aVertexPosition', this.vertexBuffer) - .addAttribute('aTextureCoord', this.uvBuffer) - .addIndex([0, 1, 2, 0, 2, 3]); - } - - /** - * Maps two Rectangle to the quad. - * - * @param {PIXI.Rectangle} targetTextureFrame - the first rectangle - * @param {PIXI.Rectangle} destinationFrame - the second rectangle - * @return {PIXI.Quad} Returns itself. - */ - map(targetTextureFrame, destinationFrame) - { - let x = 0; // destinationFrame.x / targetTextureFrame.width; - let y = 0; // destinationFrame.y / targetTextureFrame.height; - - this.uvs[0] = x; - this.uvs[1] = y; - - this.uvs[2] = x + (destinationFrame.width / targetTextureFrame.width); - this.uvs[3] = y; - - this.uvs[4] = x + (destinationFrame.width / targetTextureFrame.width); - this.uvs[5] = y + (destinationFrame.height / targetTextureFrame.height); - - this.uvs[6] = x; - this.uvs[7] = y + (destinationFrame.height / targetTextureFrame.height); - - x = destinationFrame.x; - y = destinationFrame.y; - - this.vertices[0] = x; - this.vertices[1] = y; - - this.vertices[2] = x + destinationFrame.width; - this.vertices[3] = y; - - this.vertices[4] = x + destinationFrame.width; - this.vertices[5] = y + destinationFrame.height; - - this.vertices[6] = x; - this.vertices[7] = y + destinationFrame.height; - - this.invalidate(); - - return this; - } - - /** - * legacy upload method, just marks buffers dirty - * @returns {PIXI.QuadUv} Returns itself. - */ - invalidate() - { - this.vertexBuffer._updateID++; - this.uvBuffer._updateID++; - - return this; - } -} diff --git a/packages/core/src/renderers/utils/checkMaxIfStatmentsInShader.js b/packages/core/src/renderers/utils/checkMaxIfStatmentsInShader.js deleted file mode 100644 index 8047c96..0000000 --- a/packages/core/src/renderers/utils/checkMaxIfStatmentsInShader.js +++ /dev/null @@ -1,58 +0,0 @@ -const fragTemplate = [ - 'precision mediump float;', - 'void main(void){', - 'float test = 0.1;', - '%forloop%', - 'gl_FragColor = vec4(0.0);', - '}', -].join('\n'); - -export default function checkMaxIfStatmentsInShader(maxIfs, gl) -{ - if (maxIfs === 0) - { - throw new Error('Invalid value of `0` passed to `checkMaxIfStatementsInShader`'); - } - - const shader = gl.createShader(gl.FRAGMENT_SHADER); - - while (true) // eslint-disable-line no-constant-condition - { - const fragmentSrc = fragTemplate.replace(/%forloop%/gi, generateIfTestSrc(maxIfs)); - - gl.shaderSource(shader, fragmentSrc); - gl.compileShader(shader); - - if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) - { - maxIfs = (maxIfs / 2) | 0; - } - else - { - // valid! - break; - } - } - - return maxIfs; -} - -function generateIfTestSrc(maxIfs) -{ - let src = ''; - - for (let i = 0; i < maxIfs; ++i) - { - if (i > 0) - { - src += '\nelse '; - } - - if (i < maxIfs - 1) - { - src += `if(test == ${i}.0){}`; - } - } - - return src; -} diff --git a/packages/core/src/renderers/utils/mapWebGLBlendModesToPixi.js b/packages/core/src/renderers/utils/mapWebGLBlendModesToPixi.js deleted file mode 100644 index 10ffe4d..0000000 --- a/packages/core/src/renderers/utils/mapWebGLBlendModesToPixi.js +++ /dev/null @@ -1,47 +0,0 @@ -import { BLEND_MODES } from '@pixi/constants'; - -/** - * Maps gl blend combinations to WebGL. - * - * @memberof PIXI - * @function mapWebGLBlendModesToPixi - * @private - * @param {WebGLRenderingContext} gl - The rendering context. - * @param {string[]} [array=[]] - The array to output into. - * @return {string[]} Mapped modes. - */ -export default function mapWebGLBlendModesToPixi(gl, array = []) -{ - // TODO - premultiply alpha would be different. - // add a boolean for that! - array[BLEND_MODES.NORMAL] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.ADD] = [gl.ONE, gl.DST_ALPHA]; - array[BLEND_MODES.MULTIPLY] = [gl.DST_COLOR, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.SCREEN] = [gl.ONE, gl.ONE_MINUS_SRC_COLOR]; - array[BLEND_MODES.OVERLAY] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.DARKEN] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.LIGHTEN] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.COLOR_DODGE] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.COLOR_BURN] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.HARD_LIGHT] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.SOFT_LIGHT] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.DIFFERENCE] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.EXCLUSION] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.HUE] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.SATURATION] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.COLOR] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.LUMINOSITY] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.NONE] = [0, 0]; - - // not-premultiplied blend modes - array[BLEND_MODES.NORMAL_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.ADD_NPM] = [gl.SRC_ALPHA, gl.DST_ALPHA, gl.ONE, gl.DST_ALPHA]; - array[BLEND_MODES.SCREEN_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_COLOR, gl.ONE, gl.ONE_MINUS_SRC_COLOR]; - - // not-premultiplied blend modes - array[BLEND_MODES.NORMAL_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.ADD_NPM] = [gl.SRC_ALPHA, gl.DST_ALPHA, gl.ONE, gl.DST_ALPHA]; - array[BLEND_MODES.SCREEN_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_COLOR, gl.ONE, gl.ONE_MINUS_SRC_COLOR]; - - return array; -} diff --git a/packages/core/src/shader/GLProgram.js b/packages/core/src/shader/GLProgram.js new file mode 100644 index 0000000..92980ca --- /dev/null +++ b/packages/core/src/shader/GLProgram.js @@ -0,0 +1,43 @@ +/** + * Helper class to create a webGL Program + * + * @class + * @private + */ +export default class GLProgram +{ + constructor(program, uniformData) + { + /** + * The shader program + * + * @member {WebGLProgram} + */ + this.program = program; + + /** + * holds the uniform data which contains uniform locations + * and current uniform values used for caching and preventing undeed GPU commands + * @type {Object} + */ + this.uniformData = uniformData; + + /** + * uniformGroups holds the various upload functions for the shader. Each uniform group + * and program have a unique upload function generated. + * @type {Object} + */ + this.uniformGroups = {}; + } + + /** + * Destroys this program + * TODO + */ + destroy() + { + this.uniformData = null; + this.uniformGroups = null; + this.program = null; + } +} diff --git a/packages/core/src/shader/Program.js b/packages/core/src/shader/Program.js index 4f9971e..4b4007a 100644 --- a/packages/core/src/shader/Program.js +++ b/packages/core/src/shader/Program.js @@ -1,6 +1,11 @@ -import * as shaderUtils from '../renderers/systems/shader/shader'; +// import * as from '../systems/shader/shader'; +import { setPrecision, + defaultValue, + compileProgram, + mapSize, + mapType, + getTestContext } from './utils'; import { ProgramCache } from '@pixi/utils'; -import getTestContext from './getTestContext'; import defaultFragment from './defaultProgram.frag'; import defaultVertex from './defaultProgram.vert'; import { settings } from '@pixi/settings'; @@ -34,8 +39,8 @@ */ this.fragmentSrc = fragmentSrc || Program.defaultFragmentSrc; - this.vertexSrc = shaderUtils.setPrecision(this.vertexSrc, settings.PRECISION_VERTEX); - this.fragmentSrc = shaderUtils.setPrecision(this.fragmentSrc, settings.PRECISION_FRAGMENT); + this.vertexSrc = setPrecision(this.vertexSrc, settings.PRECISION_VERTEX); + this.fragmentSrc = setPrecision(this.fragmentSrc, settings.PRECISION_FRAGMENT); // currently this does not extract structs only default types this.extractData(this.vertexSrc, this.fragmentSrc); @@ -62,7 +67,7 @@ if (gl) { - const program = shaderUtils.compileProgram(gl, vertexSrc, fragmentSrc); + const program = compileProgram(gl, vertexSrc, fragmentSrc); this.attributeData = this.getAttributeData(program, gl); this.uniformData = this.getUniformData(program, gl); @@ -95,13 +100,13 @@ for (let i = 0; i < totalAttributes; i++) { const attribData = gl.getActiveAttrib(program, i); - const type = shaderUtils.mapType(gl, attribData.type); + const type = mapType(gl, attribData.type); /*eslint-disable */ const data = { type: type, name: attribData.name, - size: shaderUtils.mapSize(type), + size: mapSize(type), location: 0, }; /* eslint-enable */ @@ -145,14 +150,14 @@ const name = uniformData.name.replace(/\[.*?\]/, ''); const isArray = uniformData.name.match(/\[.*?\]/, ''); - const type = shaderUtils.mapType(gl, uniformData.type); + const type = mapType(gl, uniformData.type); /*eslint-disable */ uniforms[name] = { type: type, size: uniformData.size, isArray:isArray, - value: shaderUtils.defaultValue(type, uniformData.size), + value: defaultValue(type, uniformData.size), }; /* eslint-enable */ } diff --git a/packages/core/src/shader/ShaderSystem.js b/packages/core/src/shader/ShaderSystem.js new file mode 100644 index 0000000..40b8af8 --- /dev/null +++ b/packages/core/src/shader/ShaderSystem.js @@ -0,0 +1,171 @@ +import System from '../System'; +import GLProgram from './GLProgram'; +import { generateUniformsSync, + defaultValue, + compileProgram } from './utils'; + +let UID = 0; + +/** + * Helper class to create a webGL Texture + * + * @class + * @memberof PIXI.systems + * @extends PIXI.System + */ +export default class ShaderSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - A reference to the current renderer + */ + constructor(renderer) + { + super(renderer); + + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = null; + + this.shader = null; + this.program = null; + + this.id = UID++; + } + + contextChange(gl) + { + this.gl = gl; + } + + /** + * Changes the current shader to the one given in parameter + * + * @param {PIXI.Shader} shader - the new shader + * @param {boolean} dontSync - false if the shader should automatically sync its uniforms. + * @returns {PIXI.glCore.glProgram} the glProgram that belongs to the shader. + */ + bind(shader, dontSync) + { + shader.uniforms.globals = this.renderer.globalUniforms; + + const program = shader.program; + const glProgram = program.glPrograms[this.renderer.CONTEXT_UID] || this.generateShader(shader); + + this.shader = shader; + + // TODO - some current pixi plugins bypass this.. so it not safe to use yet.. + if (this.program !== program) + { + this.program = program; + this.gl.useProgram(glProgram.program); + } + + if (!dontSync) + { + this.syncUniformGroup(shader.uniformGroup); + } + + return glProgram; + } + + /** + * Uploads the uniforms values to the currently bound shader. + * + * @param {object} uniforms - the uniforms valiues that be applied to the current shader + */ + setUniforms(uniforms) + { + const shader = this.shader.program; + const glProgram = shader.glPrograms[this.renderer.CONTEXT_UID]; + + shader.syncUniforms(glProgram.uniformData, uniforms, this.renderer); + } + + syncUniformGroup(group) + { + const glProgram = this.getglProgram(); + + if (!group.static || group.dirtyId !== glProgram.uniformGroups[group.id]) + { + glProgram.uniformGroups[group.id] = group.dirtyId; + const syncFunc = group.syncUniforms[this.shader.program.id] || this.createSyncGroups(group); + + syncFunc(glProgram.uniformData, group.uniforms, this.renderer); + } + } + + createSyncGroups(group) + { + group.syncUniforms[this.shader.program.id] = generateUniformsSync(group, this.shader.program.uniformData); + + return group.syncUniforms[this.shader.program.id]; + } + + /** + * Returns the underlying GLShade rof the currently bound shader. + * This can be handy for when you to have a little more control over the setting of your uniforms. + * + * @return {PIXI.glCore.Shader} the glProgram for the currently bound Shader for this context + */ + getglProgram() + { + if (this.shader) + { + return this.shader.program.glPrograms[this.renderer.CONTEXT_UID]; + } + + return null; + } + + /** + * Generates a glProgram verion of the Shader provided. + * + * @private + * @param {PIXI.Shader} shader the shader that the glProgram will be based on. + * @return {PIXI.glCore.glProgram} A shiney new glProgram + */ + generateShader(shader) + { + const gl = this.gl; + + const program = shader.program; + + const attribMap = {}; + + for (const i in program.attributeData) + { + attribMap[i] = program.attributeData[i].location; + } + + const shaderProgram = compileProgram(gl, program.vertexSrc, program.fragmentSrc, attribMap); + const uniformData = {}; + + for (const i in program.uniformData) + { + const data = program.uniformData[i]; + + uniformData[i] = { + location: gl.getUniformLocation(shaderProgram, i), + value: defaultValue(data.type, data.size), + }; + } + + const glProgram = new GLProgram(shaderProgram, uniformData); + + program.glPrograms[this.renderer.CONTEXT_UID] = glProgram; + + return glProgram; + } + + /** + * Destroys this System and removes all its textures + */ + destroy() + { + // TODO implement destroy method for ShaderSystem + this.destroyed = true; + } +} diff --git a/packages/core/src/shader/defaultValue.js b/packages/core/src/shader/defaultValue.js deleted file mode 100644 index 847a069..0000000 --- a/packages/core/src/shader/defaultValue.js +++ /dev/null @@ -1,78 +0,0 @@ -function booleanArray(size) -{ - const array = new Array(size); - - for (let i = 0; i < array.length; i++) - { - array[i] = false; - } - - return array; -} - -/** - * @class - * @memberof PIXI.glCore.shader - * @param type {String} Type of value - * @param size {Number} - */ -export default function defaultValue(type, size) -{ - switch (type) - { - case 'float': - return 0; - - case 'vec2': - return new Float32Array(2 * size); - - case 'vec3': - return new Float32Array(3 * size); - - case 'vec4': - return new Float32Array(4 * size); - - case 'int': - case 'sampler2D': - return 0; - - case 'ivec2': - return new Int32Array(2 * size); - - case 'ivec3': - return new Int32Array(3 * size); - - case 'ivec4': - return new Int32Array(4 * size); - - case 'bool': - return false; - - case 'bvec2': - - return booleanArray(2 * size); - - case 'bvec3': - return booleanArray(3 * size); - - case 'bvec4': - return booleanArray(4 * size); - - case 'mat2': - return new Float32Array([1, 0, - 0, 1]); - - case 'mat3': - return new Float32Array([1, 0, 0, - 0, 1, 0, - 0, 0, 1]); - - case 'mat4': - return new Float32Array([1, 0, 0, 0, - 0, 1, 0, 0, - 0, 0, 1, 0, - 0, 0, 0, 1]); - } - - return 0; -} diff --git a/packages/core/src/shader/extractUniformsFromSrc.js b/packages/core/src/shader/extractUniformsFromSrc.js deleted file mode 100644 index 3d02c0a..0000000 --- a/packages/core/src/shader/extractUniformsFromSrc.js +++ /dev/null @@ -1,57 +0,0 @@ -import defaultValue from './defaultValue'; - -export default function extractUniformsFromSrc(vertexSrc, fragmentSrc, mask) -{ - const vertUniforms = extractUniformsFromString(vertexSrc, mask); - const fragUniforms = extractUniformsFromString(fragmentSrc, mask); - - return Object.assign(vertUniforms, fragUniforms); -} - -function extractUniformsFromString(string) -{ - const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); - - const uniforms = {}; - let nameSplit; - - // clean the lines a little - remove extra spaces / teabs etc - // then split along ';' - const lines = string.replace(/\s+/g, ' ') - .split(/\s*;\s*/); - - // loop through.. - for (let i = 0; i < lines.length; i++) - { - const line = lines[i].trim(); - - if (line.indexOf('uniform') > -1) - { - const splitLine = line.split(' '); - const type = splitLine[1]; - - let name = splitLine[2]; - let size = 1; - - if (name.indexOf('[') > -1) - { - // array! - nameSplit = name.split(/\[|]/); - name = nameSplit[0]; - size *= Number(nameSplit[1]); - } - - if (!name.match(maskRegex)) - { - uniforms[name] = { - value: defaultValue(type, size), - dirtyId: 0, - name, - type, - }; - } - } - } - - return uniforms; -} diff --git a/packages/core/src/shader/generateUniformsSync.js b/packages/core/src/shader/generateUniformsSync.js deleted file mode 100644 index 98a357f..0000000 --- a/packages/core/src/shader/generateUniformsSync.js +++ /dev/null @@ -1,231 +0,0 @@ -// cv = CachedValue -// v = value -// ud = uniformData -// uv = uniformValue -// l = loaction -const GLSL_TO_SINGLE_SETTERS_CACHED = { - - float: ` - if(cv !== v) - { - cv.v = v; - gl.uniform1f(location, v) - }`, - - vec2: ` - if(cv[0] !== v[0] || cv[1] !== v[1]) - { - cv[0] = v[0]; - cv[1] = v[1]; - gl.uniform2f(location, v[0], v[1]) - }`, - - vec3: ` - if(cv[0] !== v[0] || cv[1] !== v[1] || cv[2] !== v[2]) - { - cv[0] = v[0]; - cv[1] = v[1]; - cv[2] = v[2]; - - gl.uniform3f(location, v[0], v[1], v[2]) - }`, - - vec4: 'gl.uniform4f(location, v[0], v[1], v[2], v[3])', - - int: 'gl.uniform1i(location, v)', - ivec2: 'gl.uniform2i(location, v[0], v[1])', - ivec3: 'gl.uniform3i(location, v[0], v[1], v[2])', - ivec4: 'gl.uniform4i(location, v[0], v[1], v[2], v[3])', - - bool: 'gl.uniform1i(location, v)', - bvec2: 'gl.uniform2i(location, v[0], v[1])', - bvec3: 'gl.uniform3i(location, v[0], v[1], v[2])', - bvec4: 'gl.uniform4i(location, v[0], v[1], v[2], v[3])', - - mat2: 'gl.uniformMatrix2fv(location, false, v)', - mat3: 'gl.uniformMatrix3fv(location, false, v)', - mat4: 'gl.uniformMatrix4fv(location, false, v)', - - sampler2D: 'gl.uniform1i(location, v)', - samplerCube: 'gl.uniform1i(location, v)', - sampler2DArray: 'gl.uniform1i(location, v)', -}; - -const GLSL_TO_ARRAY_SETTERS = { - - float: `gl.uniform1fv(location, v)`, - - vec2: `gl.uniform2fv(location, v)`, - vec3: `gl.uniform3fv(location, v)`, - vec4: 'gl.uniform4fv(location, v)', - - mat4: 'gl.uniformMatrix4fv(location, false, v)', - mat3: 'gl.uniformMatrix3fv(location, false, v)', - mat2: 'gl.uniformMatrix2fv(location, false, v)', - - int: 'gl.uniform1iv(location, v)', - ivec2: 'gl.uniform2iv(location, v)', - ivec3: 'gl.uniform3iv(location, v)', - ivec4: 'gl.uniform4iv(location, v)', - - bool: 'gl.uniform1iv(location, v)', - bvec2: 'gl.uniform2iv(location, v)', - bvec3: 'gl.uniform3iv(location, v)', - bvec4: 'gl.uniform4iv(location, v)', - - sampler2D: 'gl.uniform1iv(location, v)', - samplerCube: 'gl.uniform1iv(location, v)', - sampler2DArray: 'gl.uniform1iv(location, v)', -}; - -export default function generateUniformsSync(group, uniformData) -{ - let textureCount = 0; - let func = `var v = null; - var cv = null - var gl = renderer.gl`; - - for (const i in group.uniforms) - { - const data = uniformData[i]; - - if (!data) - { - if (group.uniforms[i].group) - { - func += ` - renderer.shader.syncUniformGroup(uv.${i}); - `; - } - - continue; - } - - // TODO && uniformData[i].value !== 0 <-- do we still need this? - if (data.type === 'float' && data.size === 1) - { - func += ` - if(uv.${i} !== ud.${i}.value) - { - ud.${i}.value = uv.${i} - gl.uniform1f(ud.${i}.location, uv.${i}) - }\n`; - } - /* eslint-disable max-len */ - else if ((data.type === 'sampler2D' || data.type === 'samplerCube' || data.type === 'sampler2DArray') && data.size === 1 && !data.isArray) - /* eslint-disable max-len */ - { - func += ` - renderer.texture.bind(uv.${i}, ${textureCount}); - - if(ud.${i}.value !== ${textureCount}) - { - ud.${i}.value = ${textureCount}; - gl.uniform1i(ud.${i}.location, ${textureCount});\n; // eslint-disable-line max-len - }\n`; - - textureCount++; - } - else if (data.type === 'mat3' && data.size === 1) - { - if (group.uniforms[i].a !== undefined) - { - // TODO and some smart caching dirty ids here! - func += ` - gl.uniformMatrix3fv(ud.${i}.location, false, uv.${i}.toArray(true)); - \n`; - } - else - { - func += ` - gl.uniformMatrix3fv(ud.${i}.location, false, uv.${i}); - \n`; - } - } - else if (data.type === 'vec2' && data.size === 1) - { - // TODO - do we need both here? - // maybe we can get away with only using points? - if (group.uniforms[i].x !== undefined) - { - func += ` - cv = ud.${i}.value; - v = uv.${i}; - - if(cv[0] !== v.x || cv[1] !== v.y) - { - cv[0] = v.x; - cv[1] = v.y; - gl.uniform2f(ud.${i}.location, v.x, v.y); - }\n`; - } - else - { - func += ` - cv = ud.${i}.value; - v = uv.${i}; - - if(cv[0] !== v[0] || cv[1] !== v[1]) - { - cv[0] = v[0]; - cv[1] = v[1]; - gl.uniform2f(ud.${i}.location, v[0], v[1]); - } - \n`; - } - } - else if (data.type === 'vec4' && data.size === 1) - { - // TODO - do we need both here? - // maybe we can get away with only using points? - if (group.uniforms[i].width !== undefined) - { - func += ` - cv = ud.${i}.value; - v = uv.${i}; - - if(cv[0] !== v.x || cv[1] !== v.y || cv[2] !== v.width || cv[3] !== v.height) - { - cv[0] = v.x; - cv[1] = v.y; - cv[2] = v.width; - cv[3] = v.height; - gl.uniform4f(ud.${i}.location, v.x, v.y, v.width, v.height) - }\n`; - } - else - { - func += ` - cv = ud.${i}.value; - v = uv.${i}; - - if(cv[0] !== v[0] || cv[1] !== v[1] || cv[2] !== v[2] || cv[3] !== v[3]) - { - cv[0] = v[0]; - cv[1] = v[1]; - cv[2] = v[2]; - cv[3] = v[3]; - - gl.uniform4f(ud.${i}.location, v[0], v[1], v[2], v[3]) - } - \n`; - } - } - else - { - const templateType = (data.size === 1) ? GLSL_TO_SINGLE_SETTERS_CACHED : GLSL_TO_ARRAY_SETTERS; - - const template = templateType[data.type].replace('location', `ud.${i}.location`); - - func += ` - cv = ud.${i}.value; - v = uv.${i}; - ${template};\n`; - } - } - - // console.log(' --------------- ') - // console.log(func); - - return new Function('ud', 'uv', 'renderer', func); // eslint-disable-line no-new-func -} diff --git a/packages/core/src/shader/getTestContext.js b/packages/core/src/shader/getTestContext.js deleted file mode 100644 index ea174cc..0000000 --- a/packages/core/src/shader/getTestContext.js +++ /dev/null @@ -1,48 +0,0 @@ -import { settings } from '@pixi/settings'; - -let context = null; - -/** - * returns a little webGL context to use for program inspection. - * - * @static - * @private - * @returns {webGL-context} a gl context to test with - */ -export default function getTestContext() -{ - if (!context) - { - const canvas = document.createElement('canvas'); - - let gl; - - if (settings.PREFER_WEBGL_2) - { - gl = canvas.getContext('webgl2', {}); - } - - if (!gl) - { - gl = canvas.getContext('webgl', {}) - || canvas.getContext('experimental-webgl', {}); - - if (!gl) - { - // fail, not able to get a context - throw new Error('This browser does not support webGL. Try using the canvas renderer'); - } - else - { - // for shader testing.. - gl.getExtension('WEBGL_draw_buffers'); - } - } - - context = gl; - - return gl; - } - - return context; -} diff --git a/packages/core/src/shader/utils/checkMaxIfStatmentsInShader.js b/packages/core/src/shader/utils/checkMaxIfStatmentsInShader.js new file mode 100644 index 0000000..8047c96 --- /dev/null +++ b/packages/core/src/shader/utils/checkMaxIfStatmentsInShader.js @@ -0,0 +1,58 @@ +const fragTemplate = [ + 'precision mediump float;', + 'void main(void){', + 'float test = 0.1;', + '%forloop%', + 'gl_FragColor = vec4(0.0);', + '}', +].join('\n'); + +export default function checkMaxIfStatmentsInShader(maxIfs, gl) +{ + if (maxIfs === 0) + { + throw new Error('Invalid value of `0` passed to `checkMaxIfStatementsInShader`'); + } + + const shader = gl.createShader(gl.FRAGMENT_SHADER); + + while (true) // eslint-disable-line no-constant-condition + { + const fragmentSrc = fragTemplate.replace(/%forloop%/gi, generateIfTestSrc(maxIfs)); + + gl.shaderSource(shader, fragmentSrc); + gl.compileShader(shader); + + if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) + { + maxIfs = (maxIfs / 2) | 0; + } + else + { + // valid! + break; + } + } + + return maxIfs; +} + +function generateIfTestSrc(maxIfs) +{ + let src = ''; + + for (let i = 0; i < maxIfs; ++i) + { + if (i > 0) + { + src += '\nelse '; + } + + if (i < maxIfs - 1) + { + src += `if(test == ${i}.0){}`; + } + } + + return src; +} diff --git a/packages/core/src/shader/utils/compileProgram.js b/packages/core/src/shader/utils/compileProgram.js new file mode 100644 index 0000000..8c6ccb9 --- /dev/null +++ b/packages/core/src/shader/utils/compileProgram.js @@ -0,0 +1,78 @@ +/** + * @method compileProgram + * @memberof PIXI.glCore.shader + * @param gl {WebGLRenderingContext} The current WebGL context {WebGLProgram} + * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. + * @param fragmentSrc {string|string[]} The fragment shader source as an array of strings. + * @param attributeLocations {Object} An attribute location map that lets you manually set the attribute locations + * @return {WebGLProgram} the shader program + */ +export default function compileProgram(gl, vertexSrc, fragmentSrc, attributeLocations) +{ + const glVertShader = compileShader(gl, gl.VERTEX_SHADER, vertexSrc); + const glFragShader = compileShader(gl, gl.FRAGMENT_SHADER, fragmentSrc); + + let program = gl.createProgram(); + + gl.attachShader(program, glVertShader); + gl.attachShader(program, glFragShader); + + // optionally, set the attributes manually for the program rather than letting WebGL decide.. + if (attributeLocations) + { + for (const i in attributeLocations) + { + gl.bindAttribLocation(program, attributeLocations[i], i); + } + } + + gl.linkProgram(program); + + // if linking fails, then log and cleanup + if (!gl.getProgramParameter(program, gl.LINK_STATUS)) + { + console.error('Pixi.js Error: Could not initialize shader.'); + console.error('gl.VALIDATE_STATUS', gl.getProgramParameter(program, gl.VALIDATE_STATUS)); + console.error('gl.getError()', gl.getError()); + + // if there is a program info log, log it + if (gl.getProgramInfoLog(program) !== '') + { + console.warn('Pixi.js Warning: gl.getProgramInfoLog()', gl.getProgramInfoLog(program)); + } + + gl.deleteProgram(program); + program = null; + } + + // clean up some shaders + gl.deleteShader(glVertShader); + gl.deleteShader(glFragShader); + + return program; +} + +/** + * @private + * @param gl {WebGLRenderingContext} The current WebGL context {WebGLProgram} + * @param type {Number} the type, can be either VERTEX_SHADER or FRAGMENT_SHADER + * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. + * @return {WebGLShader} the shader + */ +function compileShader(gl, type, src) +{ + const shader = gl.createShader(type); + + gl.shaderSource(shader, src); + gl.compileShader(shader); + + if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) + { + console.warn(src); + console.error(gl.getShaderInfoLog(shader)); + + return null; + } + + return shader; +} diff --git a/packages/core/src/shader/utils/defaultValue.js b/packages/core/src/shader/utils/defaultValue.js new file mode 100644 index 0000000..97290bd --- /dev/null +++ b/packages/core/src/shader/utils/defaultValue.js @@ -0,0 +1,79 @@ +/** + * @method defaultValue + * @memberof PIXI.glCore.shader + * @param type {String} Type of value + * @param size {Number} + */ +export default function defaultValue(type, size) +{ + switch (type) + { + case 'float': + return 0; + + case 'vec2': + return new Float32Array(2 * size); + + case 'vec3': + return new Float32Array(3 * size); + + case 'vec4': + return new Float32Array(4 * size); + + case 'int': + case 'sampler2D': + case 'sampler2DArray': + return 0; + + case 'ivec2': + return new Int32Array(2 * size); + + case 'ivec3': + return new Int32Array(3 * size); + + case 'ivec4': + return new Int32Array(4 * size); + + case 'bool': + return false; + + case 'bvec2': + + return booleanArray(2 * size); + + case 'bvec3': + return booleanArray(3 * size); + + case 'bvec4': + return booleanArray(4 * size); + + case 'mat2': + return new Float32Array([1, 0, + 0, 1]); + + case 'mat3': + return new Float32Array([1, 0, 0, + 0, 1, 0, + 0, 0, 1]); + + case 'mat4': + return new Float32Array([1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1]); + } + + return null; +} + +function booleanArray(size) +{ + const array = new Array(size); + + for (let i = 0; i < array.length; i++) + { + array[i] = false; + } + + return array; +} diff --git a/packages/core/src/shader/utils/extractUniformsFromSrc.js b/packages/core/src/shader/utils/extractUniformsFromSrc.js new file mode 100644 index 0000000..3d02c0a --- /dev/null +++ b/packages/core/src/shader/utils/extractUniformsFromSrc.js @@ -0,0 +1,57 @@ +import defaultValue from './defaultValue'; + +export default function extractUniformsFromSrc(vertexSrc, fragmentSrc, mask) +{ + const vertUniforms = extractUniformsFromString(vertexSrc, mask); + const fragUniforms = extractUniformsFromString(fragmentSrc, mask); + + return Object.assign(vertUniforms, fragUniforms); +} + +function extractUniformsFromString(string) +{ + const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); + + const uniforms = {}; + let nameSplit; + + // clean the lines a little - remove extra spaces / teabs etc + // then split along ';' + const lines = string.replace(/\s+/g, ' ') + .split(/\s*;\s*/); + + // loop through.. + for (let i = 0; i < lines.length; i++) + { + const line = lines[i].trim(); + + if (line.indexOf('uniform') > -1) + { + const splitLine = line.split(' '); + const type = splitLine[1]; + + let name = splitLine[2]; + let size = 1; + + if (name.indexOf('[') > -1) + { + // array! + nameSplit = name.split(/\[|]/); + name = nameSplit[0]; + size *= Number(nameSplit[1]); + } + + if (!name.match(maskRegex)) + { + uniforms[name] = { + value: defaultValue(type, size), + dirtyId: 0, + name, + type, + }; + } + } + } + + return uniforms; +} diff --git a/packages/core/src/shader/utils/generateUniformsSync.js b/packages/core/src/shader/utils/generateUniformsSync.js new file mode 100644 index 0000000..98a357f --- /dev/null +++ b/packages/core/src/shader/utils/generateUniformsSync.js @@ -0,0 +1,231 @@ +// cv = CachedValue +// v = value +// ud = uniformData +// uv = uniformValue +// l = loaction +const GLSL_TO_SINGLE_SETTERS_CACHED = { + + float: ` + if(cv !== v) + { + cv.v = v; + gl.uniform1f(location, v) + }`, + + vec2: ` + if(cv[0] !== v[0] || cv[1] !== v[1]) + { + cv[0] = v[0]; + cv[1] = v[1]; + gl.uniform2f(location, v[0], v[1]) + }`, + + vec3: ` + if(cv[0] !== v[0] || cv[1] !== v[1] || cv[2] !== v[2]) + { + cv[0] = v[0]; + cv[1] = v[1]; + cv[2] = v[2]; + + gl.uniform3f(location, v[0], v[1], v[2]) + }`, + + vec4: 'gl.uniform4f(location, v[0], v[1], v[2], v[3])', + + int: 'gl.uniform1i(location, v)', + ivec2: 'gl.uniform2i(location, v[0], v[1])', + ivec3: 'gl.uniform3i(location, v[0], v[1], v[2])', + ivec4: 'gl.uniform4i(location, v[0], v[1], v[2], v[3])', + + bool: 'gl.uniform1i(location, v)', + bvec2: 'gl.uniform2i(location, v[0], v[1])', + bvec3: 'gl.uniform3i(location, v[0], v[1], v[2])', + bvec4: 'gl.uniform4i(location, v[0], v[1], v[2], v[3])', + + mat2: 'gl.uniformMatrix2fv(location, false, v)', + mat3: 'gl.uniformMatrix3fv(location, false, v)', + mat4: 'gl.uniformMatrix4fv(location, false, v)', + + sampler2D: 'gl.uniform1i(location, v)', + samplerCube: 'gl.uniform1i(location, v)', + sampler2DArray: 'gl.uniform1i(location, v)', +}; + +const GLSL_TO_ARRAY_SETTERS = { + + float: `gl.uniform1fv(location, v)`, + + vec2: `gl.uniform2fv(location, v)`, + vec3: `gl.uniform3fv(location, v)`, + vec4: 'gl.uniform4fv(location, v)', + + mat4: 'gl.uniformMatrix4fv(location, false, v)', + mat3: 'gl.uniformMatrix3fv(location, false, v)', + mat2: 'gl.uniformMatrix2fv(location, false, v)', + + int: 'gl.uniform1iv(location, v)', + ivec2: 'gl.uniform2iv(location, v)', + ivec3: 'gl.uniform3iv(location, v)', + ivec4: 'gl.uniform4iv(location, v)', + + bool: 'gl.uniform1iv(location, v)', + bvec2: 'gl.uniform2iv(location, v)', + bvec3: 'gl.uniform3iv(location, v)', + bvec4: 'gl.uniform4iv(location, v)', + + sampler2D: 'gl.uniform1iv(location, v)', + samplerCube: 'gl.uniform1iv(location, v)', + sampler2DArray: 'gl.uniform1iv(location, v)', +}; + +export default function generateUniformsSync(group, uniformData) +{ + let textureCount = 0; + let func = `var v = null; + var cv = null + var gl = renderer.gl`; + + for (const i in group.uniforms) + { + const data = uniformData[i]; + + if (!data) + { + if (group.uniforms[i].group) + { + func += ` + renderer.shader.syncUniformGroup(uv.${i}); + `; + } + + continue; + } + + // TODO && uniformData[i].value !== 0 <-- do we still need this? + if (data.type === 'float' && data.size === 1) + { + func += ` + if(uv.${i} !== ud.${i}.value) + { + ud.${i}.value = uv.${i} + gl.uniform1f(ud.${i}.location, uv.${i}) + }\n`; + } + /* eslint-disable max-len */ + else if ((data.type === 'sampler2D' || data.type === 'samplerCube' || data.type === 'sampler2DArray') && data.size === 1 && !data.isArray) + /* eslint-disable max-len */ + { + func += ` + renderer.texture.bind(uv.${i}, ${textureCount}); + + if(ud.${i}.value !== ${textureCount}) + { + ud.${i}.value = ${textureCount}; + gl.uniform1i(ud.${i}.location, ${textureCount});\n; // eslint-disable-line max-len + }\n`; + + textureCount++; + } + else if (data.type === 'mat3' && data.size === 1) + { + if (group.uniforms[i].a !== undefined) + { + // TODO and some smart caching dirty ids here! + func += ` + gl.uniformMatrix3fv(ud.${i}.location, false, uv.${i}.toArray(true)); + \n`; + } + else + { + func += ` + gl.uniformMatrix3fv(ud.${i}.location, false, uv.${i}); + \n`; + } + } + else if (data.type === 'vec2' && data.size === 1) + { + // TODO - do we need both here? + // maybe we can get away with only using points? + if (group.uniforms[i].x !== undefined) + { + func += ` + cv = ud.${i}.value; + v = uv.${i}; + + if(cv[0] !== v.x || cv[1] !== v.y) + { + cv[0] = v.x; + cv[1] = v.y; + gl.uniform2f(ud.${i}.location, v.x, v.y); + }\n`; + } + else + { + func += ` + cv = ud.${i}.value; + v = uv.${i}; + + if(cv[0] !== v[0] || cv[1] !== v[1]) + { + cv[0] = v[0]; + cv[1] = v[1]; + gl.uniform2f(ud.${i}.location, v[0], v[1]); + } + \n`; + } + } + else if (data.type === 'vec4' && data.size === 1) + { + // TODO - do we need both here? + // maybe we can get away with only using points? + if (group.uniforms[i].width !== undefined) + { + func += ` + cv = ud.${i}.value; + v = uv.${i}; + + if(cv[0] !== v.x || cv[1] !== v.y || cv[2] !== v.width || cv[3] !== v.height) + { + cv[0] = v.x; + cv[1] = v.y; + cv[2] = v.width; + cv[3] = v.height; + gl.uniform4f(ud.${i}.location, v.x, v.y, v.width, v.height) + }\n`; + } + else + { + func += ` + cv = ud.${i}.value; + v = uv.${i}; + + if(cv[0] !== v[0] || cv[1] !== v[1] || cv[2] !== v[2] || cv[3] !== v[3]) + { + cv[0] = v[0]; + cv[1] = v[1]; + cv[2] = v[2]; + cv[3] = v[3]; + + gl.uniform4f(ud.${i}.location, v[0], v[1], v[2], v[3]) + } + \n`; + } + } + else + { + const templateType = (data.size === 1) ? GLSL_TO_SINGLE_SETTERS_CACHED : GLSL_TO_ARRAY_SETTERS; + + const template = templateType[data.type].replace('location', `ud.${i}.location`); + + func += ` + cv = ud.${i}.value; + v = uv.${i}; + ${template};\n`; + } + } + + // console.log(' --------------- ') + // console.log(func); + + return new Function('ud', 'uv', 'renderer', func); // eslint-disable-line no-new-func +} diff --git a/packages/core/src/shader/utils/getTestContext.js b/packages/core/src/shader/utils/getTestContext.js new file mode 100644 index 0000000..ea174cc --- /dev/null +++ b/packages/core/src/shader/utils/getTestContext.js @@ -0,0 +1,48 @@ +import { settings } from '@pixi/settings'; + +let context = null; + +/** + * returns a little webGL context to use for program inspection. + * + * @static + * @private + * @returns {webGL-context} a gl context to test with + */ +export default function getTestContext() +{ + if (!context) + { + const canvas = document.createElement('canvas'); + + let gl; + + if (settings.PREFER_WEBGL_2) + { + gl = canvas.getContext('webgl2', {}); + } + + if (!gl) + { + gl = canvas.getContext('webgl', {}) + || canvas.getContext('experimental-webgl', {}); + + if (!gl) + { + // fail, not able to get a context + throw new Error('This browser does not support webGL. Try using the canvas renderer'); + } + else + { + // for shader testing.. + gl.getExtension('WEBGL_draw_buffers'); + } + } + + context = gl; + + return gl; + } + + return context; +} diff --git a/packages/core/src/shader/utils/index.js b/packages/core/src/shader/utils/index.js new file mode 100644 index 0000000..4bf8dd4 --- /dev/null +++ b/packages/core/src/shader/utils/index.js @@ -0,0 +1,8 @@ +export { default as compileProgram } from './compileProgram'; +export { default as defaultValue } from './defaultValue'; +export { default as setPrecision } from './setPrecision'; +export { default as mapSize } from './mapSize'; +export { default as mapType } from './mapType'; +export { default as generateUniformsSync } from './generateUniformsSync'; +export { default as getTestContext } from './getTestContext'; +export { default as checkMaxIfStatmentsInShader } from './checkMaxIfStatmentsInShader'; diff --git a/packages/core/src/shader/utils/mapSize.js b/packages/core/src/shader/utils/mapSize.js new file mode 100644 index 0000000..b681554 --- /dev/null +++ b/packages/core/src/shader/utils/mapSize.js @@ -0,0 +1,33 @@ +const GLSL_TO_SIZE = { + float: 1, + vec2: 2, + vec3: 3, + vec4: 4, + + int: 1, + ivec2: 2, + ivec3: 3, + ivec4: 4, + + bool: 1, + bvec2: 2, + bvec3: 3, + bvec4: 4, + + mat2: 4, + mat3: 9, + mat4: 16, + + sampler2D: 1, +}; + +/** + * @method mapSize + * @memberof PIXI.glCore.shader + * @param type {String} + * @return {Number} + */ +export default function mapSize(type) +{ + return GLSL_TO_SIZE[type]; +} diff --git a/packages/core/src/AbstractRenderer.js b/packages/core/src/AbstractRenderer.js new file mode 100644 index 0000000..64b2eb1 --- /dev/null +++ b/packages/core/src/AbstractRenderer.js @@ -0,0 +1,353 @@ +import { hex2string, hex2rgb } from '@pixi/utils'; +import { Matrix, Rectangle } from '@pixi/math'; +import { RENDERER_TYPE } from '@pixi/constants'; +import { settings } from '@pixi/settings'; +import { Container } from '@pixi/display'; +import RenderTexture from './renderTexture/RenderTexture'; +import EventEmitter from 'eventemitter3'; + +const tempMatrix = new Matrix(); + +/** + * 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 EventEmitter + * @memberof PIXI + */ +export default class AbstractRenderer extends EventEmitter +{ + // eslint-disable-next-line valid-jsdoc + /** + * @param {string} system - The name of the system this renderer is for. + * @param {object} [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.transparent=false] - If the render view is transparent, default false + * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false + * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. The + * resolution of the renderer retina would be 2. + * @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 {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when rendering, + * stopping pixel interpolation. + */ + constructor(system, options, arg2, arg3) + { + super(); + + // Support for constructor(system, screenWidth, screenHeight, options) + if (typeof options === 'number') + { + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + }, arg3); + } + + // 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 = RENDERER_TYPE.UNKNOWN; + + /** + * Measurements of the screen. (0, 0, screenWidth, screenHeight) + * + * Its safe to use as filterArea or hitArea for 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 1 + */ + this.resolution = options.resolution || settings.RESOLUTION; + + /** + * Whether the render view is transparent + * + * @member {boolean} + */ + this.transparent = options.transparent; + + /** + * Whether css dimensions of canvas view should be resized to screen dimensions automatically + * + * @member {boolean} + */ + this.autoResize = options.autoResize || false; + + /** + * Tracks the blend modes useful for this renderer. + * + * @member {object} + */ + this.blendModes = null; + + /** + * 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; + + /** + * If true PixiJS will Math.floor() x/y values when rendering, stopping pixel interpolation. + * Handy for crisp pixel art and speed on legacy devices. + * + * @member {boolean} + */ + this.roundPixels = options.roundPixels; + + /** + * The background color as a number. + * + * @member {number} + * @private + */ + this._backgroundColor = 0x000000; + + /** + * The background color as an [R, G, B] array. + * + * @member {number[]} + * @private + */ + this._backgroundColorRgba = [0, 0, 0, 0]; + + /** + * The background color as a string. + * + * @member {string} + * @private + */ + this._backgroundColorString = '#000000'; + + this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter + + /** + * This temporary display object used as the parent of the currently being rendered item + * + * @member {PIXI.DisplayObject} + * @private + */ + this._tempDisplayObjectParent = new Container(); + + /** + * The last root object that the renderer tried to render. + * + * @member {PIXI.DisplayObject} + * @private + */ + this._lastObjectRendered = this._tempDisplayObjectParent; + + /** + * Collection of plugins. + * @readonly + * @member {object} + */ + this.plugins = {}; + } + + /** + * Initialize the plugins. + * + * @protected + * @param {object} staticMap - The dictionary of staticly saved plugins. + */ + initPlugins(staticMap) + { + 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() + { + return this.view.width; + } + + /** + * Same as view.height, actual number of pixels in the canvas by vertical + * + * @member {number} + * @readonly + * @default 600 + */ + get height() + { + return this.view.height; + } + + /** + * Resizes the screen and canvas to the specified width and height + * Canvas dimensions are multiplied by resolution + * + * @param {number} screenWidth - the new width of the screen + * @param {number} screenHeight - the new height of the screen + */ + resize(screenWidth, screenHeight) + { + this.screen.width = screenWidth; + this.screen.height = screenHeight; + + this.view.width = screenWidth * this.resolution; + this.view.height = screenHeight * this.resolution; + + if (this.autoResize) + { + this.view.style.width = `${screenWidth}px`; + this.view.style.height = `${screenHeight}px`; + } + } + + /** + * 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 {PIXI.DisplayObject} displayObject - The displayObject the object will be generated from + * @param {number} scaleMode - Should be one of the scaleMode consts + * @param {number} resolution - The resolution / device pixel ratio of the texture being generated + * @param {PIXI.Rectangle} [region] - The region of the displayObject, that shall be rendered, + * if no region is specified, defaults to the local bounds of the displayObject. + * @return {PIXI.Texture} a texture of the graphics object + */ + generateTexture(displayObject, scaleMode, resolution, region) + { + region = region || displayObject.getLocalBounds(); + + // 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(region.width | 0, region.height | 0, scaleMode, resolution); + + tempMatrix.tx = -region.x; + tempMatrix.ty = -region.y; + + this.render(displayObject, renderTexture, false, tempMatrix, true); + + return renderTexture; + } + + /** + * Removes everything from the renderer and optionally removes the Canvas DOM element. + * + * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. + */ + destroy(removeView) + { + 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); + } + + this.plugins = null; + + this.type = RENDERER_TYPE.UNKNOWN; + + this.view = null; + + this.screen = null; + + this.resolution = 0; + + this.transparent = false; + + this.autoResize = false; + + this.blendModes = null; + + this.options = null; + + this.preserveDrawingBuffer = false; + this.clearBeforeRender = false; + + this.roundPixels = false; + + this._backgroundColor = 0; + this._backgroundColorRgba = null; + this._backgroundColorString = null; + + this._tempDisplayObjectParent = null; + this._lastObjectRendered = null; + } + + /** + * The background color to fill if not transparent + * + * @member {number} + */ + get backgroundColor() + { + return this._backgroundColor; + } + + set backgroundColor(value) // eslint-disable-line require-jsdoc + { + this._backgroundColor = value; + this._backgroundColorString = hex2string(value); + hex2rgb(value, this._backgroundColorRgba); + } +} diff --git a/packages/core/src/Renderer.js b/packages/core/src/Renderer.js new file mode 100644 index 0000000..2227e78 --- /dev/null +++ b/packages/core/src/Renderer.js @@ -0,0 +1,338 @@ +import AbstractRenderer from './AbstractRenderer'; +import { sayHello } from '@pixi/utils'; +import MaskSystem from './mask/MaskSystem'; +import StencilSystem from './mask/StencilSystem'; +import FilterSystem from './filters/FilterSystem'; +import FramebufferSystem from './framebuffer/FramebufferSystem'; +import RenderTextureSystem from './renderTexture/RenderTextureSystem'; +import TextureSystem from './textures/TextureSystem'; +import ProjectionSystem from './projection/ProjectionSystem'; +import StateSystem from './state/StateSystem'; +import GeometrySystem from './geometry/GeometrySystem'; +import ShaderSystem from './shader/ShaderSystem'; +import ContextSystem from './context/ContextSystem'; +import BatchSystem from './batch/BatchSystem'; +import TextureGCSystem from './textures/TextureGCSystem'; +import { RENDERER_TYPE } from '@pixi/constants'; +import UniformGroup from './shader/UniformGroup'; +import { Matrix } from '@pixi/math'; +import Runner from 'mini-runner'; + +/** + * 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 Render works by automatically managing webGLBatchs. + * 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 :) + * + * @class + * @memberof PIXI + * @extends PIXI.AbstractRenderer + */ +export default class Renderer extends AbstractRenderer +{ + // eslint-disable-next-line valid-jsdoc + /** + * + * @param {object} [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.transparent=false] - If the render view is transparent, default false + * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false + * @param {boolean} [options.antialias=false] - sets antialias. If not available natively then FXAA + * antialiasing is used + * @param {boolean} [options.forceFXAA=false] - forces FXAA antialiasing to be used over native. + * FXAA is faster, but may not always look as great + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. + * The resolution of the renderer retina would be 2. + * @param {boolean} [options.clearBeforeRender=true] - This sets if the renderer will clear + * the canvas or not before the new render pass. If you wish to set this to false, you *must* set + * preserveDrawingBuffer to `true`. + * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, + * enable this if you need to call toDataUrl on the webgl context. + * @param {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when + * rendering, stopping pixel interpolation. + * @param {number} [options.backgroundColor=0x000000] - The background color of the rendered area + * (shown if not transparent). + * @param {boolean} [options.legacy=false] - If true PixiJS will aim to ensure compatibility + * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. + * @param {string} [options.powerPreference] - Parameter passed to webgl context, set to "high-performance" + * for devices with dual graphics card + */ + constructor(options, arg2, arg3) + { + super('WebGL', options, arg2, arg3); + + /** + * The type of this renderer as a standardised const + * + * @member {number} + * @see PIXI.RENDERER_TYPE + */ + this.type = RENDERER_TYPE.WEBGL; + + // this will be set by the contextSystem (this.context) + this.gl = null; + this.CONTEXT_UID = 0; + this.legacy = !!options.legacy; + + // TODO legacy! + + // runners! + this.runners = { + destroy: new Runner('destroy'), + contextChange: new Runner('contextChange', 1), + reset: new Runner('reset'), + update: new Runner('update'), + postrender: new Runner('postrender'), + prerender: new Runner('prerender'), + resize: new Runner('resize', 2), + }; + + 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(GeometrySystem, 'geometry') + .addSystem(FramebufferSystem, 'framebuffer') + .addSystem(StencilSystem, 'stencil') + .addSystem(ProjectionSystem, 'projection') + .addSystem(TextureGCSystem, 'textureGC') + .addSystem(FilterSystem, 'filter') + .addSystem(RenderTextureSystem, 'renderTexture') + .addSystem(BatchSystem, 'batch'); + + this.initPlugins(Renderer.__plugins); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ + if (options.context) + { + this.context.initFromContext(options.context); + } + else + { + this.context.initFromOptions({ + alpha: this.transparent, + antialias: options.antialias, + premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', + stencil: true, + 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); + } + + /** + * Add a new system to the renderer. + * @param {class} ClassRef - Class reference + * @param {string} [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. + * @return {PIXI.Renderer} Return instance of renderer + */ + addSystem(ClassRef, name) + { + if (!name) + { + name = ClassRef.name; + } + + // TODO - read name from class.name.. + + /* + if(name.includes('System')) + { + name = name.replace('System', ''); + name = name.charAt(0).toLowerCase() + name.slice(1); + } + */ + + const system = new ClassRef(this); + + if (this[name]) + { + throw new Error(`Whoops! ${name} is already a manger`); + } + + this[name] = system; + + for (const i in this.runners) + { + this.runners[i].add(system); + } + + return this; + + /** + * 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. + */ + } + + /** + * Renders the object to its webGL view + * + * @param {PIXI.DisplayObject} displayObject - the object to be rendered + * @param {PIXI.RenderTexture} renderTexture - The render texture to render to. + * @param {boolean} [clear] - Should the canvas be cleared before the new render + * @param {PIXI.Transform} [transform] - A transform to apply to the render texture before rendering. + * @param {boolean} [skipUpdateTransform] - Should we skip the update transform pass? + */ + render(displayObject, renderTexture, clear, transform, skipUpdateTransform) + { + // can be handy to know! + this.renderingToScreen = !renderTexture; + + this.runners.prerender.run(); + this.emit('prerender'); + + // 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.parent; + + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = 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.run(); + + this.emit('postrender'); + } + + /** + * Resizes the webGL view to the specified width and height. + * + * @param {number} screenWidth - the new width of the screen + * @param {number} screenHeight - the new height of the screen + */ + resize(screenWidth, screenHeight) + { + AbstractRenderer.prototype.resize.call(this, screenWidth, screenHeight); + + this.runners.resize.run(screenWidth, screenHeight); + } + + /** + * Resets the WebGL state so you can render things however you fancy! + * + * @return {PIXI.Renderer} Returns itself. + */ + reset() + { + this.runners.reset.run(); + + return this; + } + + clear() + { + this.framebuffer.bind(); + this.framebuffer.clear(); + } + + /** + * Removes everything from the renderer (event listeners, spritebatch, etc...) + * + * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. + * See: https://github.com/pixijs/pixi.js/issues/2233 + */ + destroy(removeView) + { + this.runners.destroy.run(); + + // call base destroy + super.destroy(removeView); + + // TODO nullify all the managers.. + this.gl = null; + } + + /** + * 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 + * builds and excluding plugins. + * @name PIXI.Renderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. + */ + + /** + * Adds a plugin to the renderer. + * + * @method + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + static registerPlugin(pluginName, ctor) + { + Renderer.__plugins = Renderer.__plugins || {}; + Renderer.__plugins[pluginName] = ctor; + } +} diff --git a/packages/core/src/System.js b/packages/core/src/System.js new file mode 100644 index 0000000..05a10c8 --- /dev/null +++ b/packages/core/src/System.js @@ -0,0 +1,42 @@ +/** + * System is a base class used for extending systems used by the {@link PIXI.Renderer} + * @see PIXI.Renderer#addSystem + * @class + * @memberof PIXI + */ +export default class System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + /** + * The renderer this manager works for. + * + * @member {PIXI.Renderer} + */ + this.renderer = renderer; + + this.renderer.runners.contextChange.add(this); + } + + /** + * Generic method called when there is a WebGL context change. + * + */ + contextChange() + { + // do some codes init! + } + + /** + * Generic destroy methods to be overridden by the subclass + * + */ + destroy() + { + this.renderer.runners.contextChange.remove(this); + this.renderer = null; + } +} diff --git a/packages/core/src/batch/BatchSystem.js b/packages/core/src/batch/BatchSystem.js new file mode 100644 index 0000000..b04c249 --- /dev/null +++ b/packages/core/src/batch/BatchSystem.js @@ -0,0 +1,68 @@ +import System from '../System'; +import ObjectRenderer from './ObjectRenderer'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class BatchSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + super(renderer); + + /** + * An empty renderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.emptyRenderer = new ObjectRenderer(renderer); + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.currentRenderer = this.emptyRenderer; + } + + /** + * Changes the current renderer to the one given in parameter + * + * @param {PIXI.ObjectRenderer} objectRenderer - The object renderer to use. + */ + setObjectRenderer(objectRenderer) + { + if (this.currentRenderer === objectRenderer) + { + return; + } + + this.currentRenderer.stop(); + this.currentRenderer = objectRenderer; + + this.renderer.state.setState(objectRenderer.state); + + this.currentRenderer.start(); + } + + /** + * This should be called if you wish to do some custom rendering + * It will basically render anything that may be batched up such as sprites + * + */ + flush() + { + this.setObjectRenderer(this.emptyRenderer); + } + + reset() + { + this.setObjectRenderer(this.emptyRenderer); + } +} diff --git a/packages/core/src/batch/ObjectRenderer.js b/packages/core/src/batch/ObjectRenderer.js new file mode 100644 index 0000000..0c851a9 --- /dev/null +++ b/packages/core/src/batch/ObjectRenderer.js @@ -0,0 +1,48 @@ +import System from '../System'; + +/** + * Base for a common object renderer that can be used as a system renderer plugin. + * + * @class + * @extends PIXI.System + * @memberof PIXI + */ +export default class ObjectRenderer extends System +{ + /** + * Starts the renderer and sets the shader + * + */ + start() + { + // set the shader.. + } + + /** + * Stops the renderer + * + */ + stop() + { + this.flush(); + } + + /** + * Stub method for rendering content and emptying the current batch. + * + */ + flush() + { + // flush! + } + + /** + * Renders an object + * + * @param {PIXI.DisplayObject} object - The object to render. + */ + render(object) // eslint-disable-line no-unused-vars + { + // render the object + } +} diff --git a/packages/core/src/context/ContextSystem.js b/packages/core/src/context/ContextSystem.js new file mode 100644 index 0000000..301aab4 --- /dev/null +++ b/packages/core/src/context/ContextSystem.js @@ -0,0 +1,186 @@ +import System from '../System'; +import { settings } from '@pixi/settings'; + +let CONTEXT_UID = 0; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class ContextSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.webGLVersion = 1; + + this.handleContextLost = this.handleContextLost.bind(this); + this.handleContextRestored = this.handleContextRestored.bind(this); + + this.extensions = {}; + + renderer.view.addEventListener('webglcontextlost', this.handleContextLost, false); + renderer.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + } + + get isLost() + { + return (!this.gl || this.gl.isContextLost()); + } + + contextChange(gl) + { + this.gl = gl; + + // restore a context if it was previously lost + if (gl.isContextLost() && gl.getExtension('WEBGL_lose_context')) + { + gl.getExtension('WEBGL_lose_context').restoreContext(); + } + } + + initFromContext(gl) + { + this.gl = gl; + this.validateContext(gl); + this.renderer.gl = gl; + this.renderer.CONTEXT_UID = CONTEXT_UID++; + this.renderer.runners.contextChange.run(gl); + } + + initFromOptions(options) + { + const gl = this.createContext(this.renderer.view, options); + + this.initFromContext(gl); + } + + /** + * Helper class to create a webGL Context + * + * @class + * @memberof PIXI.glCore + * @param canvas {HTMLCanvasElement} the canvas element that we will get the context from + * @param options {Object} An options object that gets passed in to the canvas element containing the context attributes, + * see https://developer.mozilla.org/en/docs/Web/API/HTMLCanvasElement/getContext for the options available + * @return {WebGLRenderingContext} the WebGL context + */ + createContext(canvas, options) + { + let gl; + + if (settings.PREFER_WEBGL_2) + { + gl = canvas.getContext('webgl2', options); + } + + if (gl) + { + this.webGLVersion = 2; + } + else + { + this.webGLVersion = 1; + + gl = canvas.getContext('webgl', options) + || canvas.getContext('experimental-webgl', options); + + if (!gl) + { + // fail, not able to get a context + throw new Error('This browser does not support webGL. Try using the canvas renderer'); + } + } + + this.gl = gl; + + this.getExtensions(); + + return gl; + } + + getExtensions() + { + // time to set up default etensions that pixi uses.. + const gl = this.gl; + const extensions = this.extensions; + + if (this.webGLVersion === 1) + { + extensions.drawBuffers = gl.getExtension('WEBGL_draw_buffers'); + extensions.depthTexture = gl.getExtension('WEBKIT_WEBGL_depth_texture'); + extensions.floatTexture = gl.getExtension('OES_texture_float'); + extensions.loseContext = gl.getExtension('WEBGL_lose_context'); + + extensions.vertexArrayObject = gl.getExtension('OES_vertex_array_object') + || gl.getExtension('MOZ_OES_vertex_array_object') + || gl.getExtension('WEBKIT_OES_vertex_array_object'); + } + + // we don't use any specific WebGL 2 ones yet! + } + + /** + * Handles a lost webgl context + * + * @private + * @param {WebGLContextEvent} event - The context lost event. + */ + handleContextLost(event) + { + event.preventDefault(); + } + + /** + * Handles a restored webgl context + * + * @private + */ + handleContextRestored() + { + this.renderer.runners.contextChange.run(this.gl); + } + + destroy() + { + const view = this.renderer.view; + + // remove listeners + view.removeEventListener('webglcontextlost', this.handleContextLost); + view.removeEventListener('webglcontextrestored', this.handleContextRestored); + + this.gl.useProgram(null); + + if (this.extensions.loseContext) + { + this.extensions.loseContext.loseContext(); + } + } + + postrender() + { + this.gl.flush(); + } + + validateContext(gl) + { + const attributes = gl.getContextAttributes(); + + // this is going to be fairly simple for now.. but at least we have room to grow! + if (!attributes.stencil) + { + /* eslint-disable max-len */ + + /* eslint-disable no-console */ + console.warn('Provided WebGL context does not have a stencil buffer, masks may not render correctly'); + /* eslint-enable no-console */ + + /* eslint-enable max-len */ + } + } +} diff --git a/packages/core/src/filters/Filter.js b/packages/core/src/filters/Filter.js new file mode 100644 index 0000000..9d8cad8 --- /dev/null +++ b/packages/core/src/filters/Filter.js @@ -0,0 +1,140 @@ +import Shader from '../shader/Shader'; +import Program from '../shader/Program'; +import State from '../state/State'; +import { settings } from '@pixi/settings'; +// import extractUniformsFromSrc from './extractUniformsFromSrc'; +import defaultVertex from './defaultFilter.vert'; +import defaultFragment from './defaultFilter.frag'; + +// let math = require('../../math'); +/** + * @class + * @memberof PIXI + * @extends PIXI.Shader + */ +export default class Filter extends Shader +{ + /** + * @param {string} [vertexSrc] - The source of the vertex shader. + * @param {string} [fragmentSrc] - The source of the fragment shader. + * @param {object} [uniforms] - Custom uniforms to use to augment the built-in ones. + */ + constructor(vertexSrc, fragmentSrc, uniforms) + { + const program = Program.from(vertexSrc, fragmentSrc); + + super(program, uniforms); + + /** + * The padding of the filter. Some filters require extra space to breath such as a blur. + * Increasing this will add extra width and height to the bounds of the object that the + * filter is applied to. + * + * @member {number} + */ + this.padding = 0; + + /** + * The resolution of the filter. Setting this to be lower will lower the quality but + * increase the performance of the filter. + * + * @member {number} + */ + this.resolution = settings.RESOLUTION; + + /** + * If enabled is true the filter is applied, if false it will not. + * + * @member {boolean} + */ + this.enabled = true; + + /** + * If enabled, PixiJS will fit the filter area into boundaries for better performance. + * Switch it off if it does not work for specific shader. + * + * @member {boolean} + */ + this.autoFit = true; + + /** + * Legacy filters use position and uvs from attributes + * @member {boolean} + * @readonly + */ + this.legacy = !!this.program.attributeData.aTextureCoord; + + /** + * the webGL state the filter requires to render + * @member {PIXI.State} + */ + this.state = new State(); + } + + /** + * Applies the filter + * + * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + * @param {boolean} clear - Should the output be cleared before rendering to it + * @param {object} [currentState] - It's current state of filter. + * There are some useful properties in the currentState : + * target, filters, sourceFrame, destinationFrame, renderTarget, resolution + */ + apply(filterManager, input, output, clear, currentState, derp) // eslint-disable-line no-unused-vars + { + // do as you please! + + filterManager.applyFilter(this, input, output, clear, currentState, derp); + + // or just do a regular render.. + } + + /** + * Sets the blendmode of the filter + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL + */ + get blendMode() + { + return this.state.blendMode; + } + + set blendMode(value) // eslint-disable-line require-jsdoc + { + this.state.blendMode = value; + } + + /** + * The default vertex shader source + * + * @static + * @constant + */ + static get defaultVertexSrc() + { + return defaultVertex; + } + + /** + * The default fragment shader source + * + * @static + * @constant + */ + static get defaultFragmentSrc() + { + return defaultFragment; + } +} + +/** + * Used for caching shader IDs + * + * @static + * @private + */ +Filter.SOURCE_KEY_MAP = {}; + diff --git a/packages/core/src/filters/FilterSystem.js b/packages/core/src/filters/FilterSystem.js new file mode 100644 index 0000000..3174318 --- /dev/null +++ b/packages/core/src/filters/FilterSystem.js @@ -0,0 +1,431 @@ +import System from '../System'; + +import RenderTexture from '../renderTexture/RenderTexture'; +import Quad from '../utils/Quad'; +import QuadUv from '../utils/QuadUv'; +import { Rectangle } from '@pixi/math'; +import * as filterTransforms from './filterTransforms'; +import bitTwiddle from 'bit-twiddle'; +import UniformGroup from '../shader/UniformGroup'; +import { DRAW_MODES } from '@pixi/constants'; + +// +/** + * @ignore + * @class + */ +class FilterState +{ + /** + * + */ + constructor() + { + this.renderTexture = null; + this.sourceFrame = new Rectangle(); + this.destinationFrame = new Rectangle(); + this.filters = []; + this.target = null; + this.legacy = false; + this.resolution = 1; + } +} + +/** + * @class + * @memberof PIXI.systems + * @extends PIXI.System + */ +export default class FilterSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + /** + * stores a bunch of PO2 textures used for filtering + * @type {Object} + */ + this.texturePool = {}; + + /** + * a pool for storing filter states, save us creating new ones each tick + * @type {Array} + */ + this.statePool = []; + + /** + * A very simple geometry used when drawing a filter effect to the screen + * @type {Quad} + */ + this.quad = new Quad(); + + this.quadUv = new QuadUv(); + + /** + * Temporary rect for maths + * @type {PIXI.Rectangle} + */ + this.tempRect = new Rectangle(); + + this.activeState = {}; + + /** + * this uniform group is attached to filter uniforms when used + * @type {UniformGroup} + */ + this.globalUniforms = new UniformGroup({ + sourceFrame: this.tempRect, + destinationFrame: this.tempRect, + + // legacy variables + filterArea: new Float32Array(4), + filterClamp: new Float32Array(4), + }, true); + } + + /** + * Adds a new filter to the System. + * + * @param {PIXI.DisplayObject} target - The target of the filter to render. + * @param {PIXI.Filter[]} filters - The filters to apply. + */ + push(target, filters) + { + const renderer = this.renderer; + const filterStack = this.renderer.renderTexture.defaultFilterStack; + const state = this.statePool.pop() || new FilterState(); + + let resolution = filters[0].resolution; + let padding = filters[0].padding; + let autoFit = filters[0].autoFit; + let legacy = filters[0].legacy; + + for (let i = 1; i < filters.length; i++) + { + const filter = filters[i]; + + // lets use the lowest resolution.. + resolution = Math.min(resolution, filter.resolution); + // and the largest amount of padding! + padding = Math.max(padding, filter.padding); + // only auto fit if all filters are autofit + autoFit = autoFit || filter.autoFit; + + legacy = legacy || filter.legacy; + } + + filterStack.push(state); + + state.resolution = resolution; + + state.legacy = legacy; + + // round to whole number based on resolution + // TODO move that to the shader too? + state.sourceFrame = target.filterArea || target.getBounds(true); + + state.sourceFrame.pad(padding); + + if (autoFit) + { + state.sourceFrame.fit(this.renderer.renderTexture.destinationFrame); + } + + state.sourceFrame.round(resolution); + + state.renderTexture = this.getPotFilterTexture(state.sourceFrame.width, state.sourceFrame.height, resolution); + state.filters = filters; + + state.destinationFrame.width = state.renderTexture.width; + state.destinationFrame.height = state.renderTexture.height; + + state.renderTexture.filterFrame = state.sourceFrame; + renderer.renderTexture.bind(state.renderTexture, state.sourceFrame);// /, state.destinationFrame); + renderer.renderTexture.clear(); + } + + /** + * Pops off the filter and applies it. + * + */ + pop() + { + const renderer = this.renderer; + const filterStack = renderer.renderTexture.defaultFilterStack; + const state = filterStack.pop(); + const filters = state.filters; + + this.activeState = state; + + const globalUniforms = this.globalUniforms.uniforms; + + globalUniforms.sourceFrame = state.sourceFrame; + globalUniforms.destinationFrame = state.destinationFrame; + globalUniforms.resolution = state.resolution; + + // only update the rect if its legacy.. + if (state.legacy) + { + const filterArea = globalUniforms.filterArea; + const filterClamp = globalUniforms.filterClamp; + + filterArea[0] = state.destinationFrame.width; + filterArea[1] = state.destinationFrame.height; + filterArea[2] = state.sourceFrame.x; + filterArea[3] = state.sourceFrame.y; + + filterClamp[0] = 0.5 / state.resolution / state.destinationFrame.width; + filterClamp[1] = 0.5 / state.resolution / state.destinationFrame.height; + filterClamp[2] = (state.sourceFrame.width - 0.5) / state.resolution / state.destinationFrame.width; + filterClamp[3] = (state.sourceFrame.height - 0.5) / state.resolution / state.destinationFrame.height; + } + + this.globalUniforms.update(); + + const lastState = filterStack[filterStack.length - 1]; + + if (filters.length === 1) + { + filters[0].apply(this, state.renderTexture, lastState.renderTexture, false, state); + + this.returnFilterTexture(state.renderTexture); + } + else + { + let flip = state.renderTexture; + let flop = this.getPotFilterTexture( + flip.width, + flip.height, + state.resolution + ); + + flop.filterFrame = flip.filterFrame; + + let i = 0; + + for (i = 0; i < filters.length - 1; ++i) + { + filters[i].apply(this, flip, flop, true, state); + + const t = flip; + + flip = flop; + flop = t; + } + + filters[i].apply(this, flip, lastState.renderTexture, false, state); + + this.returnFilterTexture(flip); + this.returnFilterTexture(flop); + } + + this.statePool.push(state); + } + + /** + * Draws a filter. + * + * @param {PIXI.Filter} filter - The filter to draw. + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + * @param {boolean} clear - Should the output be cleared before rendering to it + */ + applyFilter(filter, input, output, clear) + { + const renderer = this.renderer; + + renderer.renderTexture.bind(output, output ? output.filterFrame : null); + + if (clear) + { + // gl.disable(gl.SCISSOR_TEST); + renderer.renderTexture.clear(); + // gl.enable(gl.SCISSOR_TEST); + } + + // set the uniforms.. + filter.uniforms.uSampler = input; + filter.uniforms.filterGlobals = this.globalUniforms; + + // TODO make it so that the order of this does not matter.. + // because it does at the moment cos of global uniforms. + // they need to get resynced + + renderer.state.setState(filter.state); + renderer.shader.bind(filter); + + if (filter.legacy) + { + this.quadUv.map(input._frame, input.filterFrame); + + renderer.geometry.bind(this.quadUv); + renderer.geometry.draw(DRAW_MODES.TRIANGLES); + } + else + { + renderer.geometry.bind(this.quad); + renderer.geometry.draw(DRAW_MODES.TRIANGLE_STRIP); + } + } + + /** + * Calculates the mapped matrix. + * + * TODO playing around here.. this is temporary - (will end up in the shader) + * this returns a matrix that will normalise map filter cords in the filter to screen space + * + * @param {PIXI.Matrix} outputMatrix - the matrix to output to. + * @return {PIXI.Matrix} The mapped matrix. + */ + calculateScreenSpaceMatrix(outputMatrix) + { + const currentState = this.activeState; + + return filterTransforms.calculateScreenSpaceMatrix( + outputMatrix, + currentState.sourceFrame, + currentState.destinationFrame + ); + } + + /** + * This will map the filter coord so that a texture can be used based on the transform of a sprite + * + * @param {PIXI.Matrix} outputMatrix - The matrix to output to. + * @param {PIXI.Sprite} sprite - The sprite to map to. + * @return {PIXI.Matrix} The mapped matrix. + */ + calculateSpriteMatrix(outputMatrix, sprite) + { + const currentState = this.activeState; + + return filterTransforms.calculateSpriteMatrix( + outputMatrix, + currentState.sourceFrame, + currentState.destinationFrame, + sprite + ); + } + + /** + * Destroys this Filter System. + * + * @param {boolean} [contextLost=false] context was lost, do not free shaders + * + */ + destroy(contextLost = false) + { + if (!contextLost) + { + this.emptyPool(); + } + else + { + this.texturePool = {}; + } + } + + /** + * Gets a Power-of-Two render texture. + * + * TODO move to a seperate class could be on renderer? + * also - could cause issue with multiple contexts? + * + * @private + * @param {WebGLRenderingContext} gl - The webgl rendering context + * @param {number} minWidth - The minimum width of the render target. + * @param {number} minHeight - The minimum height of the render target. + * @param {number} resolution - The resolution of the render target. + * @return {PIXI.RenderTarget} The new render target. + */ + getPotFilterTexture(minWidth, minHeight, resolution) + { + minWidth = bitTwiddle.nextPow2(minWidth); + minHeight = bitTwiddle.nextPow2(minHeight); + resolution = resolution || 1; + + const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + + if (!this.texturePool[key]) + { + this.texturePool[key] = []; + } + + let renderTexture = this.texturePool[key].pop(); + + if (!renderTexture) + { + // temporary bypass cache.. + // internally - this will cause a texture to be bound.. + renderTexture = RenderTexture.create({ + width: minWidth, + height: minHeight, + resolution, + }); + } + + return renderTexture; + } + + /** + * Gets extra render texture to use inside current filter + * + * @param {number} resolution resolution of the renderTexture + * @returns {PIXI.RenderTexture} + */ + getFilterTexture(resolution) + { + const rt = this.activeState.renderTexture; + + const filterTexture = this.getPotFilterTexture(rt.width, rt.height, resolution || rt.baseTexture.resolution); + + filterTexture.filterFrame = rt.filterFrame; + + return filterTexture; + } + + /** + * Frees a render texture back into the pool. + * + * @param {PIXI.RenderTarget} renderTexture - The renderTarget to free + */ + returnFilterTexture(renderTexture) + { + renderTexture.filterFrame = null; + + const base = renderTexture.baseTexture; + + const minWidth = base.width; + const minHeight = base.height; + + const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + + this.texturePool[key].push(renderTexture); + } + + /** + * Empties the texture pool. + * + */ + emptyPool() + { + for (const i in this.texturePool) + { + const textures = this.texturePool[i]; + + if (textures) + { + for (let j = 0; j < textures.length; j++) + { + textures[j].destroy(true); + } + } + } + + this.texturePool = {}; + } +} diff --git a/packages/core/src/filters/defaultFilter.frag b/packages/core/src/filters/defaultFilter.frag new file mode 100644 index 0000000..a14c1be --- /dev/null +++ b/packages/core/src/filters/defaultFilter.frag @@ -0,0 +1,22 @@ +varying vec2 vTextureCoord; +varying vec2 vFilterCoord; + +uniform sampler2D uSampler; +uniform sampler2D filterSampler; + +void main(void){ + vec4 masky = texture2D(filterSampler, vFilterCoord); + vec4 sample = texture2D(uSampler, vTextureCoord); + vec4 color; + if(mod(vFilterCoord.x, 1.0) > 0.5) + { + color = vec4(1.0, 0.0, 0.0, 1.0); + } + else + { + color = vec4(0.0, 1.0, 0.0, 1.0); + } + // gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0); + gl_FragColor = mix(sample, masky, 0.5); + gl_FragColor *= sample.a; +} diff --git a/packages/core/src/filters/defaultFilter.vert b/packages/core/src/filters/defaultFilter.vert new file mode 100644 index 0000000..e1febca --- /dev/null +++ b/packages/core/src/filters/defaultFilter.vert @@ -0,0 +1,14 @@ +attribute vec2 aVertexPosition; +attribute vec2 aTextureCoord; + +uniform mat3 projectionMatrix; +uniform mat3 filterMatrix; + +varying vec2 vTextureCoord; +varying vec2 vFilterCoord; + +void main(void){ + gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); + vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy; + vTextureCoord = aTextureCoord ; +} diff --git a/packages/core/src/filters/filterTransforms.js b/packages/core/src/filters/filterTransforms.js new file mode 100644 index 0000000..87a3cd6 --- /dev/null +++ b/packages/core/src/filters/filterTransforms.js @@ -0,0 +1,50 @@ +import { Matrix } from '@pixi/math'; + +/** + * Calculates the mapped matrix + * @param filterArea {Rectangle} The filter area + * @param sprite {Sprite} the target sprite + * @param outputMatrix {Matrix} @alvin + * @private + */ +// this returns a matrix that will normalise map filter cords in the filter to screen space +export function calculateScreenSpaceMatrix(outputMatrix, filterArea, textureSize) +{ + // TODO unwrap? + const mappedMatrix = outputMatrix.identity(); + + mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); + + mappedMatrix.scale(textureSize.width, textureSize.height); + + return mappedMatrix; +} + +export function calculateNormalizedScreenSpaceMatrix(outputMatrix, filterArea, textureSize) +{ + const mappedMatrix = outputMatrix.identity(); + + mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); + + const translateScaleX = (textureSize.width / filterArea.width); + const translateScaleY = (textureSize.height / filterArea.height); + + mappedMatrix.scale(translateScaleX, translateScaleY); + + return mappedMatrix; +} + +// this will map the filter coord so that a texture can be used based on the transform of a sprite +export function calculateSpriteMatrix(outputMatrix, filterArea, textureSize, sprite) +{ + const orig = sprite._texture.orig; + const mappedMatrix = outputMatrix.set(textureSize.width, 0, 0, textureSize.height, filterArea.x, filterArea.y); + const worldTransform = sprite.worldTransform.copyTo(Matrix.TEMP_MATRIX); + + worldTransform.invert(); + mappedMatrix.prepend(worldTransform); + mappedMatrix.scale(1.0 / orig.width, 1.0 / orig.height); + mappedMatrix.translate(sprite.anchor.x, sprite.anchor.y); + + return mappedMatrix; +} diff --git a/packages/core/src/filters/spriteMask/SpriteMaskFilter.js b/packages/core/src/filters/spriteMask/SpriteMaskFilter.js new file mode 100644 index 0000000..91697b7 --- /dev/null +++ b/packages/core/src/filters/spriteMask/SpriteMaskFilter.js @@ -0,0 +1,63 @@ +import Filter from '../Filter'; +import { Matrix } from '@pixi/math'; +import vertex from './spriteMaskFilter.vert'; +import fragment from './spriteMaskFilter.frag'; +import { default as TextureMatrix } from '../../textures/TextureMatrix'; + +/** + * The SpriteMaskFilter class + * + * @class + * @extends PIXI.Filter + * @memberof PIXI + */ +export default class SpriteMaskFilter extends Filter +{ + /** + * @param {PIXI.Sprite} sprite - the target sprite + */ + constructor(sprite) + { + const maskMatrix = new Matrix(); + + super(vertex, fragment); + + sprite.renderable = false; + + this.maskSprite = sprite; + this.maskMatrix = maskMatrix; + } + + /** + * Applies the filter + * + * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + */ + apply(filterManager, input, output) + { + const maskSprite = this.maskSprite; + const tex = this.maskSprite.texture; + + if (!tex.valid) + { + return; + } + if (!tex.transform) + { + // margin = 0.0, let it bleed a bit, shader code becomes easier + // assuming that atlas textures were made with 1-pixel padding + tex.transform = new TextureMatrix(tex, 0.0); + } + tex.transform.update(); + + this.uniforms.mask = tex; + this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite) + .prepend(tex.transform.mapCoord); + this.uniforms.alpha = maskSprite.worldAlpha; + this.uniforms.maskClamp = tex.transform.uClampFrame; + + filterManager.applyFilter(this, input, output); + } +} diff --git a/packages/core/src/filters/spriteMask/spriteMaskFilter.frag b/packages/core/src/filters/spriteMask/spriteMaskFilter.frag new file mode 100644 index 0000000..0e4aef8 --- /dev/null +++ b/packages/core/src/filters/spriteMask/spriteMaskFilter.frag @@ -0,0 +1,23 @@ +varying vec2 vMaskCoord; +varying vec2 vTextureCoord; + +uniform sampler2D uSampler; +uniform sampler2D mask; +uniform float alpha; +uniform vec4 maskClamp; + +void main(void) +{ + float clip = step(3.5, + step(maskClamp.x, vMaskCoord.x) + + step(maskClamp.y, vMaskCoord.y) + + step(vMaskCoord.x, maskClamp.z) + + step(vMaskCoord.y, maskClamp.w)); + + vec4 original = texture2D(uSampler, vTextureCoord); + vec4 masky = texture2D(mask, vMaskCoord); + + original *= (masky.r * masky.a * alpha * clip); + + gl_FragColor = original; +} diff --git a/packages/core/src/filters/spriteMask/spriteMaskFilter.vert b/packages/core/src/filters/spriteMask/spriteMaskFilter.vert new file mode 100644 index 0000000..d23a30c --- /dev/null +++ b/packages/core/src/filters/spriteMask/spriteMaskFilter.vert @@ -0,0 +1,16 @@ +attribute vec2 aVertexPosition; +attribute vec2 aTextureCoord; + +uniform mat3 projectionMatrix; +uniform mat3 otherMatrix; + +varying vec2 vMaskCoord; +varying vec2 vTextureCoord; + +void main(void) +{ + gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); + + vTextureCoord = aTextureCoord; + vMaskCoord = ( otherMatrix * vec3( aTextureCoord, 1.0) ).xy; +} diff --git a/packages/core/src/framebuffer/FrameBuffer.js b/packages/core/src/framebuffer/FrameBuffer.js new file mode 100644 index 0000000..84e9fcc --- /dev/null +++ b/packages/core/src/framebuffer/FrameBuffer.js @@ -0,0 +1,106 @@ +import Texture from '../textures/BaseTexture'; +import { FORMATS, TYPES } from '@pixi/constants'; + +/** + * Frame buffer + * @class + * @memberof PIXI + */ +export default class FrameBuffer +{ + constructor(width, height) + { + this.width = width || 100; + this.height = height || 100; + + this.stencil = false; + this.depth = false; + + this.dirtyId = 0; + this.dirtyFormat = 0; + this.dirtySize = 0; + + this.depthTexture = null; + this.colorTextures = []; + + this.glFrameBuffers = {}; + } + + get colorTexture() + { + return this.colorTextures[0]; + } + + addColorTexture(index, texture) + { + // TODO add some validation to the texture - same width / height etc? + this.colorTextures[index || 0] = texture || new Texture(null, { scaleMode: 0, + resolution: 1, + mipmap: false, + width: this.width, + height: this.height });// || new Texture(); + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + addDepthTexture(texture) + { + /* eslint-disable max-len */ + this.depthTexture = texture || new Texture(null, { scaleMode: 0, + resolution: 1, + width: this.width, + height: this.height, + mipmap: false, + format: FORMATS.DEPTH_COMPONENT, + type: TYPES.UNSIGNED_SHORT });// UNSIGNED_SHORT; + /* eslint-disable max-len */ + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + enableDepth() + { + this.depth = true; + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + enableStencil() + { + this.stencil = true; + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + resize(width, height) + { + if (width === this.width && height === this.height) return; + + this.width = width; + this.height = height; + + this.dirtyId++; + this.dirtySize++; + + for (let i = 0; i < this.colorTextures.length; i++) + { + this.colorTextures[i].setSize(width, height); + } + + if (this.depthTexture) + { + this.depthTexture.setSize(width, height); + } + } +} diff --git a/packages/core/src/framebuffer/FramebufferSystem.js b/packages/core/src/framebuffer/FramebufferSystem.js new file mode 100644 index 0000000..2cc2437 --- /dev/null +++ b/packages/core/src/framebuffer/FramebufferSystem.js @@ -0,0 +1,246 @@ +import System from '../System'; +import { Rectangle } from '@pixi/math'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class FramebufferSystem extends System +{ + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + contextChange() + { + this.gl = this.renderer.gl; + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + this.current = null; + this.viewport = new Rectangle(); + + this.drawBufferExtension = this.renderer.context.extensions.drawBuffers; + } + + bind(framebuffer, frame) + { + const gl = this.gl; + + this.current = framebuffer; + + if (framebuffer) + { + // TODO cacheing layer! + + const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID] || this.initFramebuffer(framebuffer); + + gl.bindFramebuffer(gl.FRAMEBUFFER, fbo.framebuffer); + // makesure all textures are unbound.. + + // now check for updates... + if (fbo.dirtyId !== framebuffer.dirtyId) + { + fbo.dirtyId = framebuffer.dirtyId; + + if (fbo.dirtyFormat !== framebuffer.dirtyFormat) + { + fbo.dirtyFormat = framebuffer.dirtyFormat; + this.updateFramebuffer(framebuffer); + } + else if (fbo.dirtySize !== framebuffer.dirtySize) + { + fbo.dirtySize = framebuffer.dirtySize; + this.resizeFramebuffer(framebuffer); + } + } + + for (let i = 0; i < framebuffer.colorTextures.length; i++) + { + if (framebuffer.colorTextures[i].texturePart) + { + this.renderer.texture.unbind(framebuffer.colorTextures[i].texture); + } + else + { + this.renderer.texture.unbind(framebuffer.colorTextures[i]); + } + } + + if (framebuffer.depthTexture) + { + this.renderer.texture.unbind(framebuffer.depthTexture); + } + + if (frame) + { + this.setViewport(frame.x, frame.y, frame.width, frame.height); + } + else + { + this.setViewport(0, 0, framebuffer.width, framebuffer.height); + } + } + else + { + gl.bindFramebuffer(gl.FRAMEBUFFER, null); + + if (frame) + { + this.setViewport(frame.x, frame.y, frame.width, frame.height); + } + else + { + this.setViewport(0, 0, this.renderer.width, this.renderer.height); + } + } + } + + setViewport(x, y, width, height) + { + const v = this.viewport; + + if (v.width !== width || v.height !== height || v.x !== x || v.y !== y) + { + v.x = x; + v.y = y; + v.width = width; + v.height = height; + + this.gl.viewport(x, y, width, height); + } + } + + get size() + { + if (this.current) + { + // TODO store temp + return { x: 0, y: 0, width: this.current.width, height: this.current.height }; + } + + return { x: 0, y: 0, width: this.renderer.width, height: this.renderer.height }; + } + + clear(r, g, b, a) + { + const gl = this.gl; + + // TODO clear color can be set only one right? + gl.clearColor(r, g, b, a); + gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); + } + + // private functions... + + initFramebuffer(framebuffer) + { + const gl = this.gl; + + // TODO - make this a class? + const fbo = { + framebuffer: gl.createFramebuffer(), + stencil: null, + dirtyId: 0, + dirtyFormat: 0, + dirtySize: 0, + }; + + framebuffer.glFrameBuffers[this.CONTEXT_UID] = fbo; + + return fbo; + } + + resizeFramebuffer(framebuffer) + { + const gl = this.gl; + + if (framebuffer.stencil || framebuffer.depth) + { + gl.bindRenderbuffer(gl.RENDERBUFFER, this.stencil); + gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); + } + } + + updateFramebuffer(framebuffer) + { + const gl = this.gl; + + const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID]; + + // bind the color texture + const colorTextures = framebuffer.colorTextures; + + let count = colorTextures.length; + + if (!this.drawBufferExtension) + { + count = Math.min(count, 1); + } + + const activeTextures = []; + + for (let i = 0; i < count; i++) + { + const texture = framebuffer.colorTextures[i]; + + if (texture.texturePart) + { + this.renderer.texture.bind(texture.texture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.COLOR_ATTACHMENT0 + i, + gl.TEXTURE_CUBE_MAP_NEGATIVE_X + texture.side, + texture.texture._glTextures[this.CONTEXT_UID].texture, + 0); + } + else + { + this.renderer.texture.bind(texture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.COLOR_ATTACHMENT0 + i, + gl.TEXTURE_2D, + texture._glTextures[this.CONTEXT_UID].texture, + 0); + } + + activeTextures.push(gl.COLOR_ATTACHMENT0 + i); + } + + if (this.drawBufferExtension && activeTextures.length > 1) + { + this.drawBufferExtension.drawBuffersWEBGL(activeTextures); + } + + if (framebuffer.depthTexture) + { + const depthTextureExt = this.renderer.context.extensions.depthTexture; + + if (depthTextureExt) + { + const depthTexture = framebuffer.depthTexture; + + this.renderer.texture.bind(depthTexture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.DEPTH_ATTACHMENT, + gl.TEXTURE_2D, + depthTexture._glTextures[this.CONTEXT_UID].texture, + 0); + } + } + + if (framebuffer.stencil || framebuffer.depth) + { + fbo.stencil = gl.createRenderbuffer(); + + gl.bindRenderbuffer(gl.RENDERBUFFER, fbo.stencil); + + // TODO.. this is depth AND stencil? + gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, fbo.stencil); + gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); + // fbo.enableStencil(); + } + } +} diff --git a/packages/core/src/geometry/GLBuffer.js b/packages/core/src/geometry/GLBuffer.js new file mode 100644 index 0000000..0477e8a --- /dev/null +++ b/packages/core/src/geometry/GLBuffer.js @@ -0,0 +1,9 @@ +export default class GLBuffer +{ + constructor(buffer) + { + this.buffer = buffer; + this.updateID = -1; + this.byteLength = -1; + } +} diff --git a/packages/core/src/geometry/GeometrySystem.js b/packages/core/src/geometry/GeometrySystem.js new file mode 100644 index 0000000..959f827 --- /dev/null +++ b/packages/core/src/geometry/GeometrySystem.js @@ -0,0 +1,388 @@ +import System from '../System'; +import GLBuffer from './GLBuffer'; + +const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class GeometrySystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this._activeGeometry = null; + this._activeVao = null; + + this.hasVao = true; + this.hasInstance = true; + } + + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + contextChange() + { + const gl = this.gl = this.renderer.gl; + + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + + // webgl2 + if (!gl.createVertexArray) + { + // webgl 1! + let nativeVaoExtension = this.renderer.context.extensions.vertexArrayObject; + + if (this.renderer.legacy) + { + nativeVaoExtension = null; + } + + if (nativeVaoExtension) + { + gl.createVertexArray = () => + nativeVaoExtension.createVertexArrayOES(); + + gl.bindVertexArray = (vao) => + nativeVaoExtension.bindVertexArrayOES(vao); + + gl.deleteVertexArray = (vao) => + nativeVaoExtension.deleteVertexArrayOES(vao); + } + else + { + this.hasVao = false; + gl.createVertexArray = () => + { + // empty + }; + + gl.bindVertexArray = () => + { + // empty + }; + + gl.deleteVertexArray = () => + { + // empty + }; + } + } + + if (!gl.vertexAttribDivisor) + { + const instanceExt = gl.getExtension('ANGLE_instanced_arrays'); + + if (instanceExt) + { + gl.vertexAttribDivisor = (a, b) => + instanceExt.vertexAttribDivisorANGLE(a, b); + + gl.drawElementsInstanced = (a, b, c, d, e) => + instanceExt.drawElementsInstancedANGLE(a, b, c, d, e); + + gl.drawArraysInstanced = (a, b, c, d) => + instanceExt.drawArraysInstancedANGLE(a, b, c, d); + } + else + { + this.hasInstance = false; + } + } + } + + /** + * Binds geometry so that is can be drawn. Creating a Vao if required + * @private + * @param {PIXI.Geometry} geometry instance of geometry to bind + */ + bind(geometry, shader) + { + shader = shader || this.renderer.shader.shader; + + const gl = this.gl; + + // not sure the best way to address this.. + // currently different shaders require different VAOs for the same geometry + // Still mulling over the best way to solve this one.. + // will likely need to modify the shader attribute locations at run time! + let vaos = geometry.glVertexArrayObjects[this.CONTEXT_UID]; + + if (!vaos) + { + geometry.glVertexArrayObjects[this.CONTEXT_UID] = vaos = {}; + } + + const vao = vaos[shader.program.id] || this.initGeometryVao(geometry, shader.program); + + this._activeGeometry = geometry; + + if (this._activeVao !== vao) + { + this._activeVao = vao; + + if (this.hasVao) + { + gl.bindVertexArray(vao); + } + else + { + this.activateVao(geometry, shader.program); + } + } + + // TODO - optimise later! + // don't need to loop through if nothing changed! + // maybe look to add an 'autoupdate' to geometry? + this.updateBuffers(); + } + + reset() + { + this.unbind(); + } + + updateBuffers() + { + const geometry = this._activeGeometry; + const gl = this.gl; + + for (let i = 0; i < geometry.buffers.length; i++) + { + const buffer = geometry.buffers[i]; + + const glBuffer = buffer._glBuffers[this.CONTEXT_UID]; + + if (buffer._updateID !== glBuffer.updateID) + { + glBuffer.updateID = buffer._updateID; + + // TODO can cache this on buffer! maybe added a getter / setter? + const type = buffer.index ? gl.ELEMENT_ARRAY_BUFFER : gl.ARRAY_BUFFER; + const drawType = buffer.static ? gl.STATIC_DRAW : gl.DYNAMIC_DRAW; + + gl.bindBuffer(type, glBuffer.buffer); + + if (glBuffer.byteLength >= buffer.data.byteLength) + { + // offset is always zero for now! + gl.bufferSubData(type, 0, buffer.data); + } + else + { + gl.bufferData(type, buffer.data, drawType); + } + } + } + } + + checkCompatability(geometry, program) + { + // geometry must have at least all the attributes that the shader requires. + const geometryAttributes = geometry.attributes; + const shaderAttributes = program.attributeData; + + for (const j in shaderAttributes) + { + if (!geometryAttributes[j]) + { + throw new Error(`shader and geometry incompatible, geometry missing the "${j}" attribute`); + } + } + } + + /** + * Creates a Vao with the same structure as the geometry and stores it on the geometry. + * @private + * @param {PIXI.Geometry} geometry instance of geometry to to generate Vao for + */ + initGeometryVao(geometry, program) + { + this.checkCompatability(geometry, program); + + const gl = this.gl; + const CONTEXT_UID = this.CONTEXT_UID; + const buffers = geometry.buffers; + const attributes = geometry.attributes; + + const tempStride = {}; + const tempStart = {}; + + for (const j in buffers) + { + tempStride[j] = 0; + tempStart[j] = 0; + } + + for (const j in attributes) + { + if (!attributes[j].size && program.attributeData[j]) + { + attributes[j].size = program.attributeData[j].size; + } + + tempStride[attributes[j].buffer] += attributes[j].size * byteSizeMap[attributes[j].type]; + } + + for (const j in attributes) + { + const attribute = attributes[j]; + const attribSize = attribute.size; + + if (attribute.stride === undefined) + { + if (tempStride[attribute.buffer] === attribSize * byteSizeMap[attribute.type]) + { + attribute.stride = 0; + } + else + { + attribute.stride = tempStride[attribute.buffer]; + } + } + + if (attribute.start === undefined) + { + attribute.start = tempStart[attribute.buffer]; + + tempStart[attribute.buffer] += attribSize * byteSizeMap[attribute.type]; + } + } + + // first update - and create the buffers! + // only create a gl buffer if it actually gets + for (let i = 0; i < buffers.length; i++) + { + const buffer = buffers[i]; + + if (!buffer._glBuffers[CONTEXT_UID]) + { + buffer._glBuffers[CONTEXT_UID] = new GLBuffer(gl.createBuffer()); + } + } + + // TODO - maybe make this a data object? + // lets wait to see if we need to first! + const vao = gl.createVertexArray(); + + gl.bindVertexArray(vao); + + this.activateVao(geometry, program); + + gl.bindVertexArray(null); + + // add it to the cache! + geometry.glVertexArrayObjects[this.CONTEXT_UID][program.id] = vao; + + return vao; + } + + activateVao(geometry, program) + { + const gl = this.gl; + const CONTEXT_UID = this.CONTEXT_UID; + const buffers = geometry.buffers; + const attributes = geometry.attributes; + + if (geometry.indexBuffer) + { + // first update the index buffer if we have one.. + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, geometry.indexBuffer._glBuffers[CONTEXT_UID].buffer); + } + + let lastBuffer = null; + + // add a new one! + for (const j in attributes) + { + const attribute = attributes[j]; + const buffer = buffers[attribute.buffer]; + const glBuffer = buffer._glBuffers[CONTEXT_UID]; + + if (program.attributeData[j]) + { + if (lastBuffer !== glBuffer) + { + gl.bindBuffer(gl.ARRAY_BUFFER, glBuffer.buffer); + + lastBuffer = glBuffer; + } + + const location = program.attributeData[j].location; + + // TODO introduce state again + // we can optimise this for older devices that have no VAOs + gl.enableVertexAttribArray(location); + + gl.vertexAttribPointer(location, + attribute.size, + attribute.type || gl.FLOAT, + attribute.normalized, + attribute.stride, + attribute.start); + + if (attribute.instance) + { + // TODO calculate instance count based of this... + if (this.hasInstance) + { + gl.vertexAttribDivisor(location, 1); + } + else + { + throw new Error('geometry error, GPU Instancing is not supported on this device'); + } + } + } + } + } + + draw(type, size, start, instanceCount) + { + const gl = this.gl; + const geometry = this._activeGeometry; + + // TODO.. this should not change so maybe cache the function? + + if (geometry.indexBuffer) + { + if (geometry.instanced) + { + /* eslint-disable max-len */ + gl.drawElementsInstanced(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2, instanceCount || 1); + /* eslint-enable max-len */ + } + else + { + gl.drawElements(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2); + } + } + else + if (geometry.instanced) + { + // TODO need a better way to calculate size.. + gl.drawArraysInstanced(type, start, size || geometry.getSize(), instanceCount || 1); + } + else + { + gl.drawArrays(type, start, size || geometry.getSize()); + } + + return this; + } + + unbind() + { + this.gl.bindVertexArray(null); + this._activeVao = null; + this._activeGeometry = null; + } +} diff --git a/packages/core/src/geometry/utils/setVertexAttribArrays.js b/packages/core/src/geometry/utils/setVertexAttribArrays.js new file mode 100644 index 0000000..23b6959 --- /dev/null +++ b/packages/core/src/geometry/utils/setVertexAttribArrays.js @@ -0,0 +1,54 @@ +// var GL_MAP = {}; + +/** + * @param gl {WebGLRenderingContext} The current WebGL context + * @param attribs {*} + * @param state {*} + */ +export default function setVertexAttribArrays(gl, attribs, state) +{ + let i; + + if (state) + { + const tempAttribState = state.tempAttribState; + const attribState = state.attribState; + + for (i = 0; i < tempAttribState.length; i++) + { + tempAttribState[i] = false; + } + + // set the new attribs + for (i = 0; i < attribs.length; i++) + { + tempAttribState[attribs[i].attribute.location] = true; + } + + for (i = 0; i < attribState.length; i++) + { + if (attribState[i] !== tempAttribState[i]) + { + attribState[i] = tempAttribState[i]; + + if (state.attribState[i]) + { + gl.enableVertexAttribArray(i); + } + else + { + gl.disableVertexAttribArray(i); + } + } + } + } + else + { + for (i = 0; i < attribs.length; i++) + { + const attrib = attribs[i]; + + gl.enableVertexAttribArray(attrib.attribute.location); + } + } +} diff --git a/packages/core/src/index.js b/packages/core/src/index.js index 58c456a..fd1f547 100644 --- a/packages/core/src/index.js +++ b/packages/core/src/index.js @@ -1,30 +1,30 @@ import * as resources from './textures/resources'; -import * as systems from './renderers/systems'; +import * as systems from './systems'; -export { resources }; export { systems }; +export { resources }; -export { default as Renderer } from './renderers/Renderer'; -export { default as AbstractRenderer } from './renderers/AbstractRenderer'; -export { default as FrameBuffer } from './textures/FrameBuffer'; +export { default as System } from './System'; +export { default as Renderer } from './Renderer'; +export { default as AbstractRenderer } from './AbstractRenderer'; +export { default as FrameBuffer } from './framebuffer/FrameBuffer'; export { default as CubeTexture } from './textures/CubeTexture'; export { default as BaseTexture } from './textures/BaseTexture'; export { default as Texture } from './textures/Texture'; export { default as TextureMatrix } from './textures/TextureMatrix'; -export { default as RenderTexture } from './textures/RenderTexture'; -export { default as BaseRenderTexture } from './textures/BaseRenderTexture'; +export { default as RenderTexture } from './renderTexture/RenderTexture'; +export { default as BaseRenderTexture } from './renderTexture/BaseRenderTexture'; export { default as TextureUvs } from './textures/TextureUvs'; -export { default as State } from './renderers/State'; -export { default as ObjectRenderer } from './renderers/utils/ObjectRenderer'; -export { default as Quad } from './renderers/utils/Quad'; -export { default as QuadUv } from './renderers/utils/QuadUv'; -export { default as checkMaxIfStatmentsInShader } from './renderers/utils/checkMaxIfStatmentsInShader'; +export { default as State } from './state/State'; +export { default as ObjectRenderer } from './batch/ObjectRenderer'; +export { default as Quad } from './utils/Quad'; +export { default as QuadUv } from './utils/QuadUv'; +export { default as checkMaxIfStatmentsInShader } from './shader/utils/checkMaxIfStatmentsInShader'; export { default as Shader } from './shader/Shader'; export { default as Program } from './shader/Program'; export { default as UniformGroup } from './shader/UniformGroup'; -export { default as SpriteMaskFilter } from './renderers/filters/spriteMask/SpriteMaskFilter'; -export { default as Filter } from './renderers/filters/Filter'; +export { default as SpriteMaskFilter } from './filters/spriteMask/SpriteMaskFilter'; +export { default as Filter } from './filters/Filter'; export { default as Attribute } from './geometry/Attribute'; export { default as Buffer } from './geometry/Buffer'; export { default as Geometry } from './geometry/Geometry'; - diff --git a/packages/core/src/mask/MaskSystem.js b/packages/core/src/mask/MaskSystem.js new file mode 100644 index 0000000..99174c2 --- /dev/null +++ b/packages/core/src/mask/MaskSystem.js @@ -0,0 +1,199 @@ +import System from '../System'; +import AlphaMaskFilter from '../filters/spriteMask/SpriteMaskFilter'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class MaskSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + // TODO - we don't need both! + this.scissor = false; + this.scissorData = null; + this.scissorRenderTarget = null; + + this.enableScissor = false; + + this.alphaMaskPool = []; + this.alphaMaskIndex = 0; + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.DisplayObject} target - Display Object to push the mask to + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + push(target, maskData) + { + // TODO the root check means scissor rect will not + // be used on render textures more info here: + // https://github.com/pixijs/pixi.js/pull/3545 + + if (maskData.texture) + { + this.pushSpriteMask(target, maskData); + } + else if (this.enableScissor + && !this.scissor + && this.renderer._activeRenderTarget.root + && !this.renderer.stencil.stencilMaskStack.length + && maskData.isFastRect()) + { + const matrix = maskData.worldTransform; + + let rot = Math.atan2(matrix.b, matrix.a); + + // use the nearest degree! + rot = Math.round(rot * (180 / Math.PI)); + + if (rot % 90) + { + this.pushStencilMask(maskData); + } + else + { + this.pushScissorMask(target, maskData); + } + } + else + { + this.pushStencilMask(maskData); + } + } + + /** + * Removes the last mask from the mask stack and doesn't return it. + * + * @param {PIXI.DisplayObject} target - Display Object to pop the mask from + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + pop(target, maskData) + { + if (maskData.texture) + { + this.popSpriteMask(target, maskData); + } + else if (this.enableScissor && !this.renderer.stencil.stencilMaskStack.length) + { + this.popScissorMask(target, maskData); + } + else + { + this.popStencilMask(target, maskData); + } + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.RenderTarget} target - Display Object to push the sprite mask to + * @param {PIXI.Sprite} maskData - Sprite to be used as the mask + */ + pushSpriteMask(target, maskData) + { + let alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; + + if (!alphaMaskFilter) + { + alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; + } + + alphaMaskFilter[0].resolution = this.renderer.resolution; + alphaMaskFilter[0].maskSprite = maskData; + + // TODO - may cause issues! + target.filterArea = maskData.getBounds(true); + + this.renderer.filterSystem.pushFilter(target, alphaMaskFilter); + + this.alphaMaskIndex++; + } + + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popSpriteMask() + { + this.renderer.filterSystem.popFilter(); + this.alphaMaskIndex--; + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + pushStencilMask(maskData) + { + this.renderer.batch.flush(); + this.renderer.stencil.pushStencil(maskData); + } + + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popStencilMask() + { + // this.renderer.currentRenderer.stop(); + this.renderer.stencil.popStencil(); + } + + /** + * + * @param {PIXI.DisplayObject} target - Display Object to push the mask to + * @param {PIXI.Graphics} maskData - The masking data. + */ + pushScissorMask(target, maskData) + { + maskData.renderable = true; + + const renderTarget = this.renderer._activeRenderTarget; + + const bounds = maskData.getBounds(); + + bounds.fit(renderTarget.size); + maskData.renderable = false; + + this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); + + const resolution = this.renderer.resolution; + + this.renderer.gl.scissor( + bounds.x * resolution, + (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, + bounds.width * resolution, + bounds.height * resolution + ); + + this.scissorRenderTarget = renderTarget; + this.scissorData = maskData; + this.scissor = true; + } + + /** + * + * + */ + popScissorMask() + { + this.scissorRenderTarget = null; + this.scissorData = null; + this.scissor = false; + + // must be scissor! + const gl = this.renderer.gl; + + gl.disable(gl.SCISSOR_TEST); + } +} diff --git a/packages/core/src/mask/StencilSystem.js b/packages/core/src/mask/StencilSystem.js new file mode 100644 index 0000000..62796a5 --- /dev/null +++ b/packages/core/src/mask/StencilSystem.js @@ -0,0 +1,133 @@ +import System from '../System'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class StencilSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + this.stencilMaskStack = []; + } + + /** + * Changes the mask stack that is used by this System. + * + * @param {PIXI.Graphics[]} stencilMaskStack - The mask stack + */ + setMaskStack(stencilMaskStack) + { + const gl = this.renderer.gl; + + if (stencilMaskStack.length !== this.stencilMaskStack.length) + { + if (stencilMaskStack.length === 0) + { + gl.disable(gl.STENCIL_TEST); + } + else + { + gl.enable(gl.STENCIL_TEST); + } + } + + this.stencilMaskStack = stencilMaskStack; + } + + /** + * Applies the Mask and adds it to the current stencil stack. @alvin + * + * @param {PIXI.Graphics} graphics - The mask + */ + pushStencil(graphics) + { + this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); + + // this.renderer._activeRenderTarget.attachStencilBuffer(); + + const gl = this.renderer.gl; + const prevMaskCount = this.stencilMaskStack.length; + + if (prevMaskCount === 0) + { + gl.enable(gl.STENCIL_TEST); + } + + this.stencilMaskStack.push(graphics); + + // Increment the refference stencil value where the new mask overlaps with the old ones. + gl.colorMask(false, false, false, false); + gl.stencilFunc(gl.EQUAL, prevMaskCount, this._getBitwiseMask()); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.INCR); + this.renderer.plugins.graphics.render(graphics); + + this._useCurrent(); + } + + /** + * Removes the last mask from the stencil stack. @alvin + */ + popStencil() + { + this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); + + const gl = this.renderer.gl; + const graphics = this.stencilMaskStack.pop(); + + if (this.stencilMaskStack.length === 0) + { + // the stack is empty! + gl.disable(gl.STENCIL_TEST); + gl.clear(gl.STENCIL_BUFFER_BIT); + gl.clearStencil(0); + } + else + { + // Decrement the refference stencil value where the popped mask overlaps with the other ones + gl.colorMask(false, false, false, false); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.DECR); + this.renderer.plugins.graphics.render(graphics); + + this._useCurrent(); + } + } + + /** + * Setup renderer to use the current stencil data. + */ + _useCurrent() + { + const gl = this.renderer.gl; + + gl.colorMask(true, true, true, true); + gl.stencilFunc(gl.EQUAL, this.stencilMaskStack.length, this._getBitwiseMask()); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP); + } + + /** + * Fill 1s equal to the number of acitve stencil masks. + * + * @return {number} The bitwise mask. + */ + _getBitwiseMask() + { + return (1 << this.stencilMaskStack.length) - 1; + } + + /** + * Destroys the mask stack. + * + */ + destroy() + { + super.destroy(this); + + this.stencilMaskStack = null; + } +} diff --git a/packages/core/src/projection/ProjectionSystem.js b/packages/core/src/projection/ProjectionSystem.js new file mode 100644 index 0000000..6e63b73 --- /dev/null +++ b/packages/core/src/projection/ProjectionSystem.js @@ -0,0 +1,73 @@ +import System from '../System'; +import { Matrix } from '@pixi/math'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class ProjectionSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.projectionMatrix = new Matrix(); + } + + update(destinationFrame, sourceFrame, resolution, root) + { + this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; + this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; + + this.calculateProjection(this.destinationFrame, this.sourceFrame, resolution, root); + + this.renderer.globalUniforms.uniforms.projectionMatrix = this.projectionMatrix; + this.renderer.globalUniforms.update(); + } + + /** + * Updates the projection matrix based on a projection frame (which is a rectangle) + * + * @param {Rectangle} destinationFrame - The destination frame. + * @param {Rectangle} sourceFrame - The source frame. + */ + calculateProjection(destinationFrame, sourceFrame, resolution, root) + { + const pm = this.projectionMatrix; + + // I don't think we will need this line.. + // pm.identity(); + + if (!root) + { + pm.a = (1 / destinationFrame.width * 2) * resolution; + pm.d = (1 / destinationFrame.height * 2) * resolution; + + pm.tx = -1 - (sourceFrame.x * pm.a); + pm.ty = -1 - (sourceFrame.y * pm.d); + } + else + { + pm.a = (1 / destinationFrame.width * 2) * resolution; + pm.d = (-1 / destinationFrame.height * 2) * resolution; + + pm.tx = -1 - (sourceFrame.x * pm.a); + pm.ty = 1 - (sourceFrame.y * pm.d); + } + } + + /** + * Sets the transform of the active render target to the given matrix + * + * @param {PIXI.Matrix} matrix - The transformation matrix + */ + setTransform()// matrix) + { + // this._activeRenderTarget.transform = matrix; + } +} diff --git a/packages/core/src/renderTexture/BaseRenderTexture.js b/packages/core/src/renderTexture/BaseRenderTexture.js new file mode 100644 index 0000000..88eb83b --- /dev/null +++ b/packages/core/src/renderTexture/BaseRenderTexture.js @@ -0,0 +1,137 @@ +import BaseTexture from '../textures/BaseTexture'; +import FrameBuffer from '../framebuffer/FrameBuffer'; + +/** + * A BaseRenderTexture is a special texture that allows any PixiJS display object to be rendered to it. + * + * __Hint__: All DisplayObjects (i.e. Sprites) that render to a BaseRenderTexture should be preloaded + * otherwise black rectangles will be drawn instead. + * + * A BaseRenderTexture takes a snapshot of any Display Object given to its render method. The position + * and rotation of the given Display Objects is ignored. For example: + * + * ```js + * let renderer = PIXI.autoDetectRenderer(1024, 1024, { view: canvas, ratio: 1 }); + * let baseRenderTexture = new PIXI.BaseRenderTexture(renderer, 800, 600); + * let sprite = PIXI.Sprite.fromImage("spinObj_01.png"); + * + * sprite.position.x = 800/2; + * sprite.position.y = 600/2; + * sprite.anchor.x = 0.5; + * sprite.anchor.y = 0.5; + * + * baseRenderTexture.render(sprite); + * ``` + * + * The Sprite in this case will be rendered using its local transform. To render this sprite at 0,0 + * you can clear the transform + * + * ```js + * + * sprite.setTransform() + * + * let baseRenderTexture = new PIXI.BaseRenderTexture(100, 100); + * let renderTexture = new PIXI.RenderTexture(baseRenderTexture); + * + * renderer.render(sprite, renderTexture); // Renders to center of RenderTexture + * ``` + * + * @class + * @extends PIXI.BaseTexture + * @memberof PIXI + */ +export default class BaseRenderTexture extends BaseTexture +{ + /** + * @param {object} [options] + * @param {number} [options.width=100] - The width of the base render texture + * @param {number} [options.height=100] - The height of the base render texture + * @param {PIXI.SCALE_MODES} [options.scaleMode] - See {@link PIXI.SCALE_MODES} for possible values + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the texture being generated + */ + constructor(options) + { + if (typeof options === 'number') + { + /* eslint-disable prefer-rest-params */ + // Backward compatibility of signature + const width = arguments[0]; + const height = arguments[1]; + const scaleMode = arguments[2]; + const resolution = arguments[3]; + + options = { width, height, scaleMode, resolution }; + /* eslint-enable prefer-rest-params */ + } + + super(null, options); + + const { width, height } = options || {}; + + // Set defaults + this.mipmap = false; + this.width = Math.ceil(width) || 100; + this.height = Math.ceil(height) || 100; + this.valid = true; + + /** + * A map of renderer IDs to webgl renderTargets + * + * @private + * @member {object} + */ + // this._glRenderTargets = {}; + + /** + * A reference to the canvas render target (we only need one as this can be shared across renderers) + * + * @private + * @member {object} + */ + this._canvasRenderTarget = null; + + this.clearColor = [0, 0, 0, 0]; + + this.frameBuffer = new FrameBuffer(width * this.resolution, height * this.resolution) + .addColorTexture(0, this); + + // TODO - could this be added the systems? + + /** + * The data structure for the stencil masks + * + * @member {PIXI.Graphics[]} + */ + this.stencilMaskStack = []; + + /** + * The data structure for the filters + * + * @member {PIXI.Graphics[]} + */ + this.filterStack = [{}]; + } + + /** + * Resizes the BaseRenderTexture. + * + * @param {number} width - The width to resize to. + * @param {number} height - The height to resize to. + */ + resize(width, height) + { + width = Math.ceil(width); + height = Math.ceil(height); + this.frameBuffer.resize(width * this.resolution, height * this.resolution); + } + + /** + * Destroys this texture + * + */ + destroy() + { + super.destroy(true); + this.renderer = null; + } +} diff --git a/packages/core/src/renderTexture/RenderTexture.js b/packages/core/src/renderTexture/RenderTexture.js new file mode 100644 index 0000000..f1ecf1f --- /dev/null +++ b/packages/core/src/renderTexture/RenderTexture.js @@ -0,0 +1,153 @@ +import BaseRenderTexture from './BaseRenderTexture'; +import Texture from '../textures/Texture'; + +/** + * A RenderTexture is a special texture that allows any PixiJS display object to be rendered to it. + * + * __Hint__: All DisplayObjects (i.e. Sprites) that render to a RenderTexture should be preloaded + * otherwise black rectangles will be drawn instead. + * + * A RenderTexture takes a snapshot of any Display Object given to its render method. For example: + * + * ```js + * let renderer = PIXI.autoDetectRenderer(1024, 1024, { view: canvas, ratio: 1 }); + * let renderTexture = PIXI.RenderTexture.create(800, 600); + * let sprite = PIXI.Sprite.fromImage("spinObj_01.png"); + * + * sprite.position.x = 800/2; + * sprite.position.y = 600/2; + * sprite.anchor.x = 0.5; + * sprite.anchor.y = 0.5; + * + * renderer.render(sprite, renderTexture); + * ``` + * + * The Sprite in this case will be rendered using its local transform. To render this sprite at 0,0 + * you can clear the transform + * + * ```js + * + * sprite.setTransform() + * + * let renderTexture = new PIXI.RenderTexture.create(100, 100); + * + * renderer.render(sprite, renderTexture); // Renders to center of RenderTexture + * ``` + * + * @class + * @extends PIXI.Texture + * @memberof PIXI + */ +export default class RenderTexture extends Texture +{ + /** + * @param {PIXI.BaseRenderTexture} baseRenderTexture - The renderer used for this RenderTexture + * @param {PIXI.Rectangle} [frame] - The rectangle frame of the texture to show + */ + constructor(baseRenderTexture, frame) + { + // support for legacy.. + let _legacyRenderer = null; + + if (!(baseRenderTexture instanceof BaseRenderTexture)) + { + /* eslint-disable prefer-rest-params, no-console */ + const width = arguments[1]; + const height = arguments[2]; + const scaleMode = arguments[3]; + const resolution = arguments[4]; + + // we have an old render texture.. + console.warn(`Please use RenderTexture.create(${width}, ${height}) instead of the ctor directly.`); + _legacyRenderer = arguments[0]; + /* eslint-enable prefer-rest-params, no-console */ + + frame = null; + baseRenderTexture = new BaseRenderTexture({ + width, + height, + scaleMode, + resolution, + }); + } + + /** + * The base texture object that this texture uses + * + * @member {BaseTexture} + */ + super(baseRenderTexture, frame); + + this.legacyRenderer = _legacyRenderer; + + /** + * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. + * + * @member {boolean} + */ + this.valid = true; + + /** + * FilterSystem temporary storage + * @private + * @member {PIXI.Rectangle} + */ + this.filterFrame = null; + + this._updateUvs(); + } + + /** + * Resizes the RenderTexture. + * + * @param {number} width - The width to resize to. + * @param {number} height - The height to resize to. + * @param {boolean} [resizeBaseTexture=true] - Should the baseTexture.width and height values be resized as well? + */ + resize(width, height, resizeBaseTexture = true) + { + width = Math.ceil(width); + height = Math.ceil(height); + + // TODO - could be not required.. + this.valid = (width > 0 && height > 0); + + this._frame.width = this.orig.width = width; + this._frame.height = this.orig.height = height; + + if (resizeBaseTexture) + { + this.baseTexture.resize(width, height); + } + + this._updateUvs(); + } + + /** + * A short hand way of creating a render texture. + * + * @param {object} [options] - Options + * @param {number} [options.width=100] - The width of the render texture + * @param {number} [options.height=100] - The height of the render texture + * @param {number} [options.scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the texture being generated + * @return {PIXI.RenderTexture} The new render texture + */ + static create(options) + { + // fallback, old-style: create(width, height, scaleMode, resolution) + if (typeof options === 'number') + { + /* eslint-disable prefer-rest-params */ + options = { + width: options, + height: arguments[1], + scaleMode: arguments[2], + resolution: arguments[3], + }; + /* eslint-enable prefer-rest-params */ + } + + return new RenderTexture(new BaseRenderTexture(options)); + } +} diff --git a/packages/core/src/renderTexture/RenderTextureSystem.js b/packages/core/src/renderTexture/RenderTextureSystem.js new file mode 100644 index 0000000..8d0ca61 --- /dev/null +++ b/packages/core/src/renderTexture/RenderTextureSystem.js @@ -0,0 +1,115 @@ +import System from '../System'; +import { Rectangle } from '@pixi/math'; + +const tempRect = new Rectangle(); + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class RenderTextureSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.clearColor = renderer._backgroundColorRgba; + + // TODO moe this property somewhere else! + this.defaultMaskStack = []; + this.defaultFilterStack = [{}]; + + // empty render texture? + this.renderTexture = null; + + this.destinationFrame = new Rectangle(); + } + + bind(renderTexture, sourceFrame, destinationFrame) + { + // TODO - do we want this?? + if (this.renderTexture === renderTexture) return; + this.renderTexture = renderTexture; + + const renderer = this.renderer; + + if (renderTexture) + { + const baseTexture = renderTexture.baseTexture; + + if (!destinationFrame) + { + tempRect.width = baseTexture.realWidth; + tempRect.height = baseTexture.realHeight; + + destinationFrame = tempRect; + } + + if (!sourceFrame) + { + sourceFrame = destinationFrame; + } + + this.renderer.framebuffer.bind(baseTexture.frameBuffer, destinationFrame); + + this.renderer.projection.update(destinationFrame, sourceFrame, baseTexture.resolution, false); + this.renderer.stencil.setMaskStack(baseTexture.stencilMaskStack); + } + else + { + // TODO these validation checks happen deeper down.. + // thing they can be avoided.. + if (!destinationFrame) + { + tempRect.width = renderer.width; + tempRect.height = renderer.height; + + destinationFrame = tempRect; + } + + if (!sourceFrame) + { + sourceFrame = destinationFrame; + } + + renderer.framebuffer.bind(null, destinationFrame); + + // TODO store this.. + this.renderer.projection.update(destinationFrame, sourceFrame, this.renderer.resolution, true); + this.renderer.stencil.setMaskStack(this.defaultMaskStack); + } + + this.destinationFrame.copyFrom(destinationFrame); + } + + /** + * Erases the render texture and fills the drawing area with a colour + * + * @param {number} [clearColor] - The colour + * @return {PIXI.Renderer} Returns itself. + */ + clear(clearColor) + { + if (this.renderTexture) + { + clearColor = clearColor || this.renderTexture.baseTexture.clearColor; + } + else + { + clearColor = clearColor || this.clearColor; + } + + this.renderer.framebuffer.clear(clearColor[0], clearColor[1], clearColor[2], clearColor[3]); + } + + resize()// screenWidth, screenHeight) + { + // resize the root only! + this.bind(null); + } +} diff --git a/packages/core/src/renderers/AbstractRenderer.js b/packages/core/src/renderers/AbstractRenderer.js deleted file mode 100644 index 928670f..0000000 --- a/packages/core/src/renderers/AbstractRenderer.js +++ /dev/null @@ -1,353 +0,0 @@ -import { hex2string, hex2rgb } from '@pixi/utils'; -import { Matrix, Rectangle } from '@pixi/math'; -import { RENDERER_TYPE } from '@pixi/constants'; -import { settings } from '@pixi/settings'; -import { Container } from '@pixi/display'; -import RenderTexture from '../textures/RenderTexture'; -import EventEmitter from 'eventemitter3'; - -const tempMatrix = new Matrix(); - -/** - * 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 EventEmitter - * @memberof PIXI - */ -export default class AbstractRenderer extends EventEmitter -{ - // eslint-disable-next-line valid-jsdoc - /** - * @param {string} system - The name of the system this renderer is for. - * @param {object} [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.transparent=false] - If the render view is transparent, default false - * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false - * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) - * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. The - * resolution of the renderer retina would be 2. - * @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 {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when rendering, - * stopping pixel interpolation. - */ - constructor(system, options, arg2, arg3) - { - super(); - - // Support for constructor(system, screenWidth, screenHeight, options) - if (typeof options === 'number') - { - options = Object.assign({ - width: options, - height: arg2 || settings.RENDER_OPTIONS.height, - }, arg3); - } - - // 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 = RENDERER_TYPE.UNKNOWN; - - /** - * Measurements of the screen. (0, 0, screenWidth, screenHeight) - * - * Its safe to use as filterArea or hitArea for 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 1 - */ - this.resolution = options.resolution || settings.RESOLUTION; - - /** - * Whether the render view is transparent - * - * @member {boolean} - */ - this.transparent = options.transparent; - - /** - * Whether css dimensions of canvas view should be resized to screen dimensions automatically - * - * @member {boolean} - */ - this.autoResize = options.autoResize || false; - - /** - * Tracks the blend modes useful for this renderer. - * - * @member {object} - */ - this.blendModes = null; - - /** - * 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; - - /** - * If true PixiJS will Math.floor() x/y values when rendering, stopping pixel interpolation. - * Handy for crisp pixel art and speed on legacy devices. - * - * @member {boolean} - */ - this.roundPixels = options.roundPixels; - - /** - * The background color as a number. - * - * @member {number} - * @private - */ - this._backgroundColor = 0x000000; - - /** - * The background color as an [R, G, B] array. - * - * @member {number[]} - * @private - */ - this._backgroundColorRgba = [0, 0, 0, 0]; - - /** - * The background color as a string. - * - * @member {string} - * @private - */ - this._backgroundColorString = '#000000'; - - this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter - - /** - * This temporary display object used as the parent of the currently being rendered item - * - * @member {PIXI.DisplayObject} - * @private - */ - this._tempDisplayObjectParent = new Container(); - - /** - * The last root object that the renderer tried to render. - * - * @member {PIXI.DisplayObject} - * @private - */ - this._lastObjectRendered = this._tempDisplayObjectParent; - - /** - * Collection of plugins. - * @readonly - * @member {object} - */ - this.plugins = {}; - } - - /** - * Initialize the plugins. - * - * @protected - * @param {object} staticMap - The dictionary of staticly saved plugins. - */ - initPlugins(staticMap) - { - 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() - { - return this.view.width; - } - - /** - * Same as view.height, actual number of pixels in the canvas by vertical - * - * @member {number} - * @readonly - * @default 600 - */ - get height() - { - return this.view.height; - } - - /** - * Resizes the screen and canvas to the specified width and height - * Canvas dimensions are multiplied by resolution - * - * @param {number} screenWidth - the new width of the screen - * @param {number} screenHeight - the new height of the screen - */ - resize(screenWidth, screenHeight) - { - this.screen.width = screenWidth; - this.screen.height = screenHeight; - - this.view.width = screenWidth * this.resolution; - this.view.height = screenHeight * this.resolution; - - if (this.autoResize) - { - this.view.style.width = `${screenWidth}px`; - this.view.style.height = `${screenHeight}px`; - } - } - - /** - * 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 {PIXI.DisplayObject} displayObject - The displayObject the object will be generated from - * @param {number} scaleMode - Should be one of the scaleMode consts - * @param {number} resolution - The resolution / device pixel ratio of the texture being generated - * @param {PIXI.Rectangle} [region] - The region of the displayObject, that shall be rendered, - * if no region is specified, defaults to the local bounds of the displayObject. - * @return {PIXI.Texture} a texture of the graphics object - */ - generateTexture(displayObject, scaleMode, resolution, region) - { - region = region || displayObject.getLocalBounds(); - - // 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(region.width | 0, region.height | 0, scaleMode, resolution); - - tempMatrix.tx = -region.x; - tempMatrix.ty = -region.y; - - this.render(displayObject, renderTexture, false, tempMatrix, true); - - return renderTexture; - } - - /** - * Removes everything from the renderer and optionally removes the Canvas DOM element. - * - * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. - */ - destroy(removeView) - { - 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); - } - - this.plugins = null; - - this.type = RENDERER_TYPE.UNKNOWN; - - this.view = null; - - this.screen = null; - - this.resolution = 0; - - this.transparent = false; - - this.autoResize = false; - - this.blendModes = null; - - this.options = null; - - this.preserveDrawingBuffer = false; - this.clearBeforeRender = false; - - this.roundPixels = false; - - this._backgroundColor = 0; - this._backgroundColorRgba = null; - this._backgroundColorString = null; - - this._tempDisplayObjectParent = null; - this._lastObjectRendered = null; - } - - /** - * The background color to fill if not transparent - * - * @member {number} - */ - get backgroundColor() - { - return this._backgroundColor; - } - - set backgroundColor(value) // eslint-disable-line require-jsdoc - { - this._backgroundColor = value; - this._backgroundColorString = hex2string(value); - hex2rgb(value, this._backgroundColorRgba); - } -} diff --git a/packages/core/src/renderers/Renderer.js b/packages/core/src/renderers/Renderer.js deleted file mode 100644 index a2020b1..0000000 --- a/packages/core/src/renderers/Renderer.js +++ /dev/null @@ -1,329 +0,0 @@ -import AbstractRenderer from './AbstractRenderer'; -import { sayHello } from '@pixi/utils'; -import MaskSystem from './systems/MaskSystem'; -import StencilSystem from './systems/StencilSystem'; -import FilterSystem from './systems/filter/FilterSystem'; -import FramebufferSystem from './systems/FramebufferSystem'; -import RenderTextureSystem from './systems/RenderTextureSystem'; -import TextureSystem from './systems/textures/TextureSystem'; -import ProjectionSystem from './systems/ProjectionSystem'; -import StateSystem from './systems/StateSystem'; -import GeometrySystem from './systems/geometry/GeometrySystem'; -import ShaderSystem from './systems/shader/ShaderSystem'; -import ContextSystem from './systems/ContextSystem'; -import BatchSystem from './systems/BatchSystem'; -import TextureGCSystem from './systems/textures/TextureGCSystem'; -import { RENDERER_TYPE } from '@pixi/constants'; -import UniformGroup from '../shader/UniformGroup'; -import { Matrix } from '@pixi/math'; -import Runner from 'mini-runner'; - -/** - * 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 Render works by automatically managing webGLBatchs. - * 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 :) - * - * @class - * @memberof PIXI - * @extends PIXI.AbstractRenderer - */ -export default class Renderer extends AbstractRenderer -{ - // eslint-disable-next-line valid-jsdoc - /** - * - * @param {object} [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.transparent=false] - If the render view is transparent, default false - * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false - * @param {boolean} [options.antialias=false] - sets antialias. If not available natively then FXAA - * antialiasing is used - * @param {boolean} [options.forceFXAA=false] - forces FXAA antialiasing to be used over native. - * FXAA is faster, but may not always look as great - * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. - * The resolution of the renderer retina would be 2. - * @param {boolean} [options.clearBeforeRender=true] - This sets if the renderer will clear - * the canvas or not before the new render pass. If you wish to set this to false, you *must* set - * preserveDrawingBuffer to `true`. - * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, - * enable this if you need to call toDataUrl on the webgl context. - * @param {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when - * rendering, stopping pixel interpolation. - * @param {number} [options.backgroundColor=0x000000] - The background color of the rendered area - * (shown if not transparent). - * @param {boolean} [options.legacy=false] - If true PixiJS will aim to ensure compatibility - * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. - * @param {string} [options.powerPreference] - Parameter passed to webgl context, set to "high-performance" - * for devices with dual graphics card - */ - constructor(options, arg2, arg3) - { - super('WebGL', options, arg2, arg3); - - /** - * The type of this renderer as a standardised const - * - * @member {number} - * @see PIXI.RENDERER_TYPE - */ - this.type = RENDERER_TYPE.WEBGL; - - // this will be set by the contextSystem (this.context) - this.gl = null; - this.CONTEXT_UID = 0; - this.legacy = !!options.legacy; - - // TODO legacy! - - // runners! - this.runners = { - destroy: new Runner('destroy'), - contextChange: new Runner('contextChange', 1), - reset: new Runner('reset'), - update: new Runner('update'), - postrender: new Runner('postrender'), - prerender: new Runner('prerender'), - resize: new Runner('resize', 2), - }; - - 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(GeometrySystem, 'geometry') - .addSystem(FramebufferSystem, 'framebuffer') - .addSystem(StencilSystem, 'stencil') - .addSystem(ProjectionSystem, 'projection') - .addSystem(TextureGCSystem, 'textureGC') - .addSystem(FilterSystem, 'filter') - .addSystem(RenderTextureSystem, 'renderTexture') - .addSystem(BatchSystem, 'batch'); - - this.initPlugins(Renderer.__plugins); - - /** - * The options passed in to create a new webgl context. - * - * @member {object} - * @private - */ - if (options.context) - { - this.context.initFromContext(options.context); - } - else - { - this.context.initFromOptions({ - alpha: this.transparent, - antialias: options.antialias, - premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', - stencil: true, - 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); - } - - addSystem(_class, name) - { - if (!name) - { - name = _class.name; - } - - // TODO - read name from class.name.. - - /* - if(name.includes('System')) - { - name = name.replace('System', ''); - name = name.charAt(0).toLowerCase() + name.slice(1); - } - */ - - const system = new _class(this); - - if (this[name]) - { - throw new Error(`Whoops! ${name} is already a manger`); - } - - this[name] = system; - - for (const i in this.runners) - { - this.runners[i].add(system); - } - - return this; - - /** - * 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. - */ - } - - /** - * Renders the object to its webGL view - * - * @param {PIXI.DisplayObject} displayObject - the object to be rendered - * @param {PIXI.RenderTexture} renderTexture - The render texture to render to. - * @param {boolean} [clear] - Should the canvas be cleared before the new render - * @param {PIXI.Transform} [transform] - A transform to apply to the render texture before rendering. - * @param {boolean} [skipUpdateTransform] - Should we skip the update transform pass? - */ - render(displayObject, renderTexture, clear, transform, skipUpdateTransform) - { - // can be handy to know! - this.renderingToScreen = !renderTexture; - - this.runners.prerender.run(); - this.emit('prerender'); - - // 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.parent; - - displayObject.parent = this._tempDisplayObjectParent; - displayObject.updateTransform(); - displayObject.parent = 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.run(); - - this.emit('postrender'); - } - - /** - * Resizes the webGL view to the specified width and height. - * - * @param {number} screenWidth - the new width of the screen - * @param {number} screenHeight - the new height of the screen - */ - resize(screenWidth, screenHeight) - { - AbstractRenderer.prototype.resize.call(this, screenWidth, screenHeight); - - this.runners.resize.run(screenWidth, screenHeight); - } - - /** - * Resets the WebGL state so you can render things however you fancy! - * - * @return {PIXI.Renderer} Returns itself. - */ - reset() - { - this.runners.reset.run(); - - return this; - } - - clear() - { - this.framebuffer.bind(); - this.framebuffer.clear(); - } - - /** - * Removes everything from the renderer (event listeners, spritebatch, etc...) - * - * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. - * See: https://github.com/pixijs/pixi.js/issues/2233 - */ - destroy(removeView) - { - this.runners.destroy.run(); - - // call base destroy - super.destroy(removeView); - - // TODO nullify all the managers.. - this.gl = null; - } - - /** - * 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 - * builds and excluding plugins. - * @name PIXI.Renderer#plugins - * @type {object} - * @readonly - * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. - * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. - * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. - * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. - */ - - /** - * Adds a plugin to the renderer. - * - * @method - * @param {string} pluginName - The name of the plugin. - * @param {Function} ctor - The constructor function or class for the plugin. - */ - static registerPlugin(pluginName, ctor) - { - Renderer.__plugins = Renderer.__plugins || {}; - Renderer.__plugins[pluginName] = ctor; - } -} diff --git a/packages/core/src/renderers/State.js b/packages/core/src/renderers/State.js deleted file mode 100644 index fa3f221..0000000 --- a/packages/core/src/renderers/State.js +++ /dev/null @@ -1,163 +0,0 @@ -/* eslint-disable max-len */ - -const BLEND = 0; -const OFFSET = 1; -const CULLING = 2; -const DEPTH_TEST = 3; -const WINDING = 4; - -/** - * This is a webGL state. It is passed The WebGL StateManager. - * Each mesh renderered may require webGL to be in a different state. - * For example you may want different blend mode or to enable polygon offsets - * - * @class - * @memberof PIXI - */ -export default class State -{ - /** - * - */ - constructor() - { - this.data = 0; - - this.blendMode = 0; - this.polygonOffset = 0; - - this.blend = true; - // this.depthTest = true; - } - - /** - * Activates blending of the computed fragment color values - * - * @member {boolean} - */ - get blend() - { - return !!(this.data & (1 << BLEND)); - } - - set blend(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << BLEND)) !== value) - { - this.data ^= (1 << BLEND); - } - } - - /** - * Activates adding an offset to depth values of polygon's fragments - * - * @member {boolean} - * @default false - */ - get offsets() - { - return !!(this.data & (1 << OFFSET)); - } - - set offsets(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << OFFSET)) !== value) - { - this.data ^= (1 << OFFSET); - } - } - - /** - * Activates culling of polygons. - * - * @member {boolean} - * @default false - */ - get culling() - { - return !!(this.data & (1 << CULLING)); - } - - set culling(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << CULLING)) !== value) - { - this.data ^= (1 << CULLING); - } - } - - /** - * Activates depth comparisons and updates to the depth buffer. - * - * @member {boolean} - * @default false - */ - get depthTest() - { - return !!(this.data & (1 << DEPTH_TEST)); - } - - set depthTest(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << DEPTH_TEST)) !== value) - { - this.data ^= (1 << DEPTH_TEST); - } - } - - /** - * Specifies whether or not front or back-facing polygons can be culled. - * @member {boolean} - * @default false - */ - get clockwiseFrontFace() - { - return !!(this.data & (1 << WINDING)); - } - - set clockwiseFrontFace(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << WINDING)) !== value) - { - this.data ^= (1 << WINDING); - } - } - - /** - * The blend mode to be applied when this state is set. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. - * Setting this mode to anything other than NO_BLEND will automatically switch blending on. - * - * @member {boolean} - * @default PIXI.BLEND_MODES.NORMAL - * @see PIXI.BLEND_MODES - */ - get blendMode() - { - return this._blendMode; - } - - set blendMode(value) // eslint-disable-line require-jsdoc - { - // 17 is NO BLEND - this.blend = (value !== 17); - this._blendMode = value; - } - - /** - * The polygon offset. Setting this property to anything other than 0 will automatically enable poygon offset fill. - * - * @member {number} - * @default 0 - */ - get polygonOffset() - { - return this._polygonOffset; - } - - set polygonOffset(value) // eslint-disable-line require-jsdoc - { - this.offsets = !!value; - this._polygonOffset = value; - } -} - diff --git a/packages/core/src/renderers/filters/Filter.js b/packages/core/src/renderers/filters/Filter.js deleted file mode 100644 index 2073b8d..0000000 --- a/packages/core/src/renderers/filters/Filter.js +++ /dev/null @@ -1,140 +0,0 @@ -import Shader from '../../shader/Shader'; -import Program from '../../shader/Program'; -import State from '../State'; -import { settings } from '@pixi/settings'; -// import extractUniformsFromSrc from './extractUniformsFromSrc'; -import defaultVertex from './defaultFilter.vert'; -import defaultFragment from './defaultFilter.frag'; - -// let math = require('../../math'); -/** - * @class - * @memberof PIXI - * @extends PIXI.Shader - */ -export default class Filter extends Shader -{ - /** - * @param {string} [vertexSrc] - The source of the vertex shader. - * @param {string} [fragmentSrc] - The source of the fragment shader. - * @param {object} [uniforms] - Custom uniforms to use to augment the built-in ones. - */ - constructor(vertexSrc, fragmentSrc, uniforms) - { - const program = Program.from(vertexSrc, fragmentSrc); - - super(program, uniforms); - - /** - * The padding of the filter. Some filters require extra space to breath such as a blur. - * Increasing this will add extra width and height to the bounds of the object that the - * filter is applied to. - * - * @member {number} - */ - this.padding = 0; - - /** - * The resolution of the filter. Setting this to be lower will lower the quality but - * increase the performance of the filter. - * - * @member {number} - */ - this.resolution = settings.RESOLUTION; - - /** - * If enabled is true the filter is applied, if false it will not. - * - * @member {boolean} - */ - this.enabled = true; - - /** - * If enabled, PixiJS will fit the filter area into boundaries for better performance. - * Switch it off if it does not work for specific shader. - * - * @member {boolean} - */ - this.autoFit = true; - - /** - * Legacy filters use position and uvs from attributes - * @member {boolean} - * @readonly - */ - this.legacy = !!this.program.attributeData.aTextureCoord; - - /** - * the webGL state the filter requires to render - * @member {PIXI.State} - */ - this.state = new State(); - } - - /** - * Applies the filter - * - * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from - * @param {PIXI.RenderTarget} input - The input render target. - * @param {PIXI.RenderTarget} output - The target to output to. - * @param {boolean} clear - Should the output be cleared before rendering to it - * @param {object} [currentState] - It's current state of filter. - * There are some useful properties in the currentState : - * target, filters, sourceFrame, destinationFrame, renderTarget, resolution - */ - apply(filterManager, input, output, clear, currentState, derp) // eslint-disable-line no-unused-vars - { - // do as you please! - - filterManager.applyFilter(this, input, output, clear, currentState, derp); - - // or just do a regular render.. - } - - /** - * Sets the blendmode of the filter - * - * @member {number} - * @default PIXI.BLEND_MODES.NORMAL - */ - get blendMode() - { - return this.state.blendMode; - } - - set blendMode(value) // eslint-disable-line require-jsdoc - { - this.state.blendMode = value; - } - - /** - * The default vertex shader source - * - * @static - * @constant - */ - static get defaultVertexSrc() - { - return defaultVertex; - } - - /** - * The default fragment shader source - * - * @static - * @constant - */ - static get defaultFragmentSrc() - { - return defaultFragment; - } -} - -/** - * Used for caching shader IDs - * - * @static - * @private - */ -Filter.SOURCE_KEY_MAP = {}; - diff --git a/packages/core/src/renderers/filters/defaultFilter.frag b/packages/core/src/renderers/filters/defaultFilter.frag deleted file mode 100644 index a14c1be..0000000 --- a/packages/core/src/renderers/filters/defaultFilter.frag +++ /dev/null @@ -1,22 +0,0 @@ -varying vec2 vTextureCoord; -varying vec2 vFilterCoord; - -uniform sampler2D uSampler; -uniform sampler2D filterSampler; - -void main(void){ - vec4 masky = texture2D(filterSampler, vFilterCoord); - vec4 sample = texture2D(uSampler, vTextureCoord); - vec4 color; - if(mod(vFilterCoord.x, 1.0) > 0.5) - { - color = vec4(1.0, 0.0, 0.0, 1.0); - } - else - { - color = vec4(0.0, 1.0, 0.0, 1.0); - } - // gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0); - gl_FragColor = mix(sample, masky, 0.5); - gl_FragColor *= sample.a; -} diff --git a/packages/core/src/renderers/filters/defaultFilter.vert b/packages/core/src/renderers/filters/defaultFilter.vert deleted file mode 100644 index e1febca..0000000 --- a/packages/core/src/renderers/filters/defaultFilter.vert +++ /dev/null @@ -1,14 +0,0 @@ -attribute vec2 aVertexPosition; -attribute vec2 aTextureCoord; - -uniform mat3 projectionMatrix; -uniform mat3 filterMatrix; - -varying vec2 vTextureCoord; -varying vec2 vFilterCoord; - -void main(void){ - gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); - vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy; - vTextureCoord = aTextureCoord ; -} diff --git a/packages/core/src/renderers/filters/filterTransforms.js b/packages/core/src/renderers/filters/filterTransforms.js deleted file mode 100644 index 87a3cd6..0000000 --- a/packages/core/src/renderers/filters/filterTransforms.js +++ /dev/null @@ -1,50 +0,0 @@ -import { Matrix } from '@pixi/math'; - -/** - * Calculates the mapped matrix - * @param filterArea {Rectangle} The filter area - * @param sprite {Sprite} the target sprite - * @param outputMatrix {Matrix} @alvin - * @private - */ -// this returns a matrix that will normalise map filter cords in the filter to screen space -export function calculateScreenSpaceMatrix(outputMatrix, filterArea, textureSize) -{ - // TODO unwrap? - const mappedMatrix = outputMatrix.identity(); - - mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); - - mappedMatrix.scale(textureSize.width, textureSize.height); - - return mappedMatrix; -} - -export function calculateNormalizedScreenSpaceMatrix(outputMatrix, filterArea, textureSize) -{ - const mappedMatrix = outputMatrix.identity(); - - mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); - - const translateScaleX = (textureSize.width / filterArea.width); - const translateScaleY = (textureSize.height / filterArea.height); - - mappedMatrix.scale(translateScaleX, translateScaleY); - - return mappedMatrix; -} - -// this will map the filter coord so that a texture can be used based on the transform of a sprite -export function calculateSpriteMatrix(outputMatrix, filterArea, textureSize, sprite) -{ - const orig = sprite._texture.orig; - const mappedMatrix = outputMatrix.set(textureSize.width, 0, 0, textureSize.height, filterArea.x, filterArea.y); - const worldTransform = sprite.worldTransform.copyTo(Matrix.TEMP_MATRIX); - - worldTransform.invert(); - mappedMatrix.prepend(worldTransform); - mappedMatrix.scale(1.0 / orig.width, 1.0 / orig.height); - mappedMatrix.translate(sprite.anchor.x, sprite.anchor.y); - - return mappedMatrix; -} diff --git a/packages/core/src/renderers/filters/spriteMask/SpriteMaskFilter.js b/packages/core/src/renderers/filters/spriteMask/SpriteMaskFilter.js deleted file mode 100644 index d5a91a4..0000000 --- a/packages/core/src/renderers/filters/spriteMask/SpriteMaskFilter.js +++ /dev/null @@ -1,63 +0,0 @@ -import Filter from '../Filter'; -import { Matrix } from '@pixi/math'; -import vertex from './spriteMaskFilter.vert'; -import fragment from './spriteMaskFilter.frag'; -import { default as TextureMatrix } from '../../../textures/TextureMatrix'; - -/** - * The SpriteMaskFilter class - * - * @class - * @extends PIXI.Filter - * @memberof PIXI - */ -export default class SpriteMaskFilter extends Filter -{ - /** - * @param {PIXI.Sprite} sprite - the target sprite - */ - constructor(sprite) - { - const maskMatrix = new Matrix(); - - super(vertex, fragment); - - sprite.renderable = false; - - this.maskSprite = sprite; - this.maskMatrix = maskMatrix; - } - - /** - * Applies the filter - * - * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from - * @param {PIXI.RenderTarget} input - The input render target. - * @param {PIXI.RenderTarget} output - The target to output to. - */ - apply(filterManager, input, output) - { - const maskSprite = this.maskSprite; - const tex = this.maskSprite.texture; - - if (!tex.valid) - { - return; - } - if (!tex.transform) - { - // margin = 0.0, let it bleed a bit, shader code becomes easier - // assuming that atlas textures were made with 1-pixel padding - tex.transform = new TextureMatrix(tex, 0.0); - } - tex.transform.update(); - - this.uniforms.mask = tex; - this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite) - .prepend(tex.transform.mapCoord); - this.uniforms.alpha = maskSprite.worldAlpha; - this.uniforms.maskClamp = tex.transform.uClampFrame; - - filterManager.applyFilter(this, input, output); - } -} diff --git a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.frag b/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.frag deleted file mode 100644 index 0e4aef8..0000000 --- a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.frag +++ /dev/null @@ -1,23 +0,0 @@ -varying vec2 vMaskCoord; -varying vec2 vTextureCoord; - -uniform sampler2D uSampler; -uniform sampler2D mask; -uniform float alpha; -uniform vec4 maskClamp; - -void main(void) -{ - float clip = step(3.5, - step(maskClamp.x, vMaskCoord.x) + - step(maskClamp.y, vMaskCoord.y) + - step(vMaskCoord.x, maskClamp.z) + - step(vMaskCoord.y, maskClamp.w)); - - vec4 original = texture2D(uSampler, vTextureCoord); - vec4 masky = texture2D(mask, vMaskCoord); - - original *= (masky.r * masky.a * alpha * clip); - - gl_FragColor = original; -} diff --git a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.vert b/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.vert deleted file mode 100644 index d23a30c..0000000 --- a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.vert +++ /dev/null @@ -1,16 +0,0 @@ -attribute vec2 aVertexPosition; -attribute vec2 aTextureCoord; - -uniform mat3 projectionMatrix; -uniform mat3 otherMatrix; - -varying vec2 vMaskCoord; -varying vec2 vTextureCoord; - -void main(void) -{ - gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); - - vTextureCoord = aTextureCoord; - vMaskCoord = ( otherMatrix * vec3( aTextureCoord, 1.0) ).xy; -} diff --git a/packages/core/src/renderers/systems/BatchSystem.js b/packages/core/src/renderers/systems/BatchSystem.js deleted file mode 100644 index 70e9518..0000000 --- a/packages/core/src/renderers/systems/BatchSystem.js +++ /dev/null @@ -1,68 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import ObjectRenderer from '../utils/ObjectRenderer'; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ - -export default class BatchSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this manager works for. - */ - constructor(renderer) - { - super(renderer); - - /** - * An empty renderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.emptyRenderer = new ObjectRenderer(renderer); - - /** - * The currently active ObjectRenderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.currentRenderer = this.emptyRenderer; - } - - /** - * Changes the current renderer to the one given in parameter - * - * @param {PIXI.ObjectRenderer} objectRenderer - The object renderer to use. - */ - setObjectRenderer(objectRenderer) - { - if (this.currentRenderer === objectRenderer) - { - return; - } - - this.currentRenderer.stop(); - this.currentRenderer = objectRenderer; - - this.renderer.state.setState(objectRenderer.state); - - this.currentRenderer.start(); - } - - /** - * This should be called if you wish to do some custom rendering - * It will basically render anything that may be batched up such as sprites - * - */ - flush() - { - this.setObjectRenderer(this.emptyRenderer); - } - - reset() - { - this.setObjectRenderer(this.emptyRenderer); - } -} diff --git a/packages/core/src/renderers/systems/ContextSystem.js b/packages/core/src/renderers/systems/ContextSystem.js deleted file mode 100644 index 43bf827..0000000 --- a/packages/core/src/renderers/systems/ContextSystem.js +++ /dev/null @@ -1,186 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { settings } from '@pixi/settings'; - -let CONTEXT_UID = 0; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class ContextSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this.webGLVersion = 1; - - this.handleContextLost = this.handleContextLost.bind(this); - this.handleContextRestored = this.handleContextRestored.bind(this); - - this.extensions = {}; - - renderer.view.addEventListener('webglcontextlost', this.handleContextLost, false); - renderer.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); - } - - get isLost() - { - return (!this.gl || this.gl.isContextLost()); - } - - contextChange(gl) - { - this.gl = gl; - - // restore a context if it was previously lost - if (gl.isContextLost() && gl.getExtension('WEBGL_lose_context')) - { - gl.getExtension('WEBGL_lose_context').restoreContext(); - } - } - - initFromContext(gl) - { - this.gl = gl; - this.validateContext(gl); - this.renderer.gl = gl; - this.renderer.CONTEXT_UID = CONTEXT_UID++; - this.renderer.runners.contextChange.run(gl); - } - - initFromOptions(options) - { - const gl = this.createContext(this.renderer.view, options); - - this.initFromContext(gl); - } - - /** - * Helper class to create a webGL Context - * - * @class - * @memberof PIXI.glCore - * @param canvas {HTMLCanvasElement} the canvas element that we will get the context from - * @param options {Object} An options object that gets passed in to the canvas element containing the context attributes, - * see https://developer.mozilla.org/en/docs/Web/API/HTMLCanvasElement/getContext for the options available - * @return {WebGLRenderingContext} the WebGL context - */ - createContext(canvas, options) - { - let gl; - - if (settings.PREFER_WEBGL_2) - { - gl = canvas.getContext('webgl2', options); - } - - if (gl) - { - this.webGLVersion = 2; - } - else - { - this.webGLVersion = 1; - - gl = canvas.getContext('webgl', options) - || canvas.getContext('experimental-webgl', options); - - if (!gl) - { - // fail, not able to get a context - throw new Error('This browser does not support webGL. Try using the canvas renderer'); - } - } - - this.gl = gl; - - this.getExtensions(); - - return gl; - } - - getExtensions() - { - // time to set up default etensions that pixi uses.. - const gl = this.gl; - const extensions = this.extensions; - - if (this.webGLVersion === 1) - { - extensions.drawBuffers = gl.getExtension('WEBGL_draw_buffers'); - extensions.depthTexture = gl.getExtension('WEBKIT_WEBGL_depth_texture'); - extensions.floatTexture = gl.getExtension('OES_texture_float'); - extensions.loseContext = gl.getExtension('WEBGL_lose_context'); - - extensions.vertexArrayObject = gl.getExtension('OES_vertex_array_object') - || gl.getExtension('MOZ_OES_vertex_array_object') - || gl.getExtension('WEBKIT_OES_vertex_array_object'); - } - - // we don't use any specific WebGL 2 ones yet! - } - - /** - * Handles a lost webgl context - * - * @private - * @param {WebGLContextEvent} event - The context lost event. - */ - handleContextLost(event) - { - event.preventDefault(); - } - - /** - * Handles a restored webgl context - * - * @private - */ - handleContextRestored() - { - this.renderer.runners.contextChange.run(this.gl); - } - - destroy() - { - const view = this.renderer.view; - - // remove listeners - view.removeEventListener('webglcontextlost', this.handleContextLost); - view.removeEventListener('webglcontextrestored', this.handleContextRestored); - - this.gl.useProgram(null); - - if (this.extensions.loseContext) - { - this.extensions.loseContext.loseContext(); - } - } - - postrender() - { - this.gl.flush(); - } - - validateContext(gl) - { - const attributes = gl.getContextAttributes(); - - // this is going to be fairly simple for now.. but at least we have room to grow! - if (!attributes.stencil) - { - /* eslint-disable max-len */ - - /* eslint-disable no-console */ - console.warn('Provided WebGL context does not have a stencil buffer, masks may not render correctly'); - /* eslint-enable no-console */ - - /* eslint-enable max-len */ - } - } -} diff --git a/packages/core/src/renderers/systems/FramebufferSystem.js b/packages/core/src/renderers/systems/FramebufferSystem.js deleted file mode 100644 index df7902b..0000000 --- a/packages/core/src/renderers/systems/FramebufferSystem.js +++ /dev/null @@ -1,246 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { Rectangle } from '@pixi/math'; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class FramebufferSystem extends WebGLSystem -{ - /** - * Sets up the renderer context and necessary buffers. - * - * @private - */ - contextChange() - { - this.gl = this.renderer.gl; - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - this.current = null; - this.viewport = new Rectangle(); - - this.drawBufferExtension = this.renderer.context.extensions.drawBuffers; - } - - bind(framebuffer, frame) - { - const gl = this.gl; - - this.current = framebuffer; - - if (framebuffer) - { - // TODO cacheing layer! - - const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID] || this.initFramebuffer(framebuffer); - - gl.bindFramebuffer(gl.FRAMEBUFFER, fbo.framebuffer); - // makesure all textures are unbound.. - - // now check for updates... - if (fbo.dirtyId !== framebuffer.dirtyId) - { - fbo.dirtyId = framebuffer.dirtyId; - - if (fbo.dirtyFormat !== framebuffer.dirtyFormat) - { - fbo.dirtyFormat = framebuffer.dirtyFormat; - this.updateFramebuffer(framebuffer); - } - else if (fbo.dirtySize !== framebuffer.dirtySize) - { - fbo.dirtySize = framebuffer.dirtySize; - this.resizeFramebuffer(framebuffer); - } - } - - for (let i = 0; i < framebuffer.colorTextures.length; i++) - { - if (framebuffer.colorTextures[i].texturePart) - { - this.renderer.texture.unbind(framebuffer.colorTextures[i].texture); - } - else - { - this.renderer.texture.unbind(framebuffer.colorTextures[i]); - } - } - - if (framebuffer.depthTexture) - { - this.renderer.texture.unbind(framebuffer.depthTexture); - } - - if (frame) - { - this.setViewport(frame.x, frame.y, frame.width, frame.height); - } - else - { - this.setViewport(0, 0, framebuffer.width, framebuffer.height); - } - } - else - { - gl.bindFramebuffer(gl.FRAMEBUFFER, null); - - if (frame) - { - this.setViewport(frame.x, frame.y, frame.width, frame.height); - } - else - { - this.setViewport(0, 0, this.renderer.width, this.renderer.height); - } - } - } - - setViewport(x, y, width, height) - { - const v = this.viewport; - - if (v.width !== width || v.height !== height || v.x !== x || v.y !== y) - { - v.x = x; - v.y = y; - v.width = width; - v.height = height; - - this.gl.viewport(x, y, width, height); - } - } - - get size() - { - if (this.current) - { - // TODO store temp - return { x: 0, y: 0, width: this.current.width, height: this.current.height }; - } - - return { x: 0, y: 0, width: this.renderer.width, height: this.renderer.height }; - } - - clear(r, g, b, a) - { - const gl = this.gl; - - // TODO clear color can be set only one right? - gl.clearColor(r, g, b, a); - gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); - } - - // private functions... - - initFramebuffer(framebuffer) - { - const gl = this.gl; - - // TODO - make this a class? - const fbo = { - framebuffer: gl.createFramebuffer(), - stencil: null, - dirtyId: 0, - dirtyFormat: 0, - dirtySize: 0, - }; - - framebuffer.glFrameBuffers[this.CONTEXT_UID] = fbo; - - return fbo; - } - - resizeFramebuffer(framebuffer) - { - const gl = this.gl; - - if (framebuffer.stencil || framebuffer.depth) - { - gl.bindRenderbuffer(gl.RENDERBUFFER, this.stencil); - gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); - } - } - - updateFramebuffer(framebuffer) - { - const gl = this.gl; - - const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID]; - - // bind the color texture - const colorTextures = framebuffer.colorTextures; - - let count = colorTextures.length; - - if (!this.drawBufferExtension) - { - count = Math.min(count, 1); - } - - const activeTextures = []; - - for (let i = 0; i < count; i++) - { - const texture = framebuffer.colorTextures[i]; - - if (texture.texturePart) - { - this.renderer.texture.bind(texture.texture, 0); - - gl.framebufferTexture2D(gl.FRAMEBUFFER, - gl.COLOR_ATTACHMENT0 + i, - gl.TEXTURE_CUBE_MAP_NEGATIVE_X + texture.side, - texture.texture._glTextures[this.CONTEXT_UID].texture, - 0); - } - else - { - this.renderer.texture.bind(texture, 0); - - gl.framebufferTexture2D(gl.FRAMEBUFFER, - gl.COLOR_ATTACHMENT0 + i, - gl.TEXTURE_2D, - texture._glTextures[this.CONTEXT_UID].texture, - 0); - } - - activeTextures.push(gl.COLOR_ATTACHMENT0 + i); - } - - if (this.drawBufferExtension && activeTextures.length > 1) - { - this.drawBufferExtension.drawBuffersWEBGL(activeTextures); - } - - if (framebuffer.depthTexture) - { - const depthTextureExt = this.renderer.context.extensions.depthTexture; - - if (depthTextureExt) - { - const depthTexture = framebuffer.depthTexture; - - this.renderer.texture.bind(depthTexture, 0); - - gl.framebufferTexture2D(gl.FRAMEBUFFER, - gl.DEPTH_ATTACHMENT, - gl.TEXTURE_2D, - depthTexture._glTextures[this.CONTEXT_UID].texture, - 0); - } - } - - if (framebuffer.stencil || framebuffer.depth) - { - fbo.stencil = gl.createRenderbuffer(); - - gl.bindRenderbuffer(gl.RENDERBUFFER, fbo.stencil); - - // TODO.. this is depth AND stencil? - gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, fbo.stencil); - gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); - // fbo.enableStencil(); - } - } -} diff --git a/packages/core/src/renderers/systems/MaskSystem.js b/packages/core/src/renderers/systems/MaskSystem.js deleted file mode 100644 index d9c4b1a..0000000 --- a/packages/core/src/renderers/systems/MaskSystem.js +++ /dev/null @@ -1,199 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import AlphaMaskFilter from '../filters/spriteMask/SpriteMaskFilter'; - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class MaskSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - // TODO - we don't need both! - this.scissor = false; - this.scissorData = null; - this.scissorRenderTarget = null; - - this.enableScissor = false; - - this.alphaMaskPool = []; - this.alphaMaskIndex = 0; - } - - /** - * Applies the Mask and adds it to the current filter stack. - * - * @param {PIXI.DisplayObject} target - Display Object to push the mask to - * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. - */ - push(target, maskData) - { - // TODO the root check means scissor rect will not - // be used on render textures more info here: - // https://github.com/pixijs/pixi.js/pull/3545 - - if (maskData.texture) - { - this.pushSpriteMask(target, maskData); - } - else if (this.enableScissor - && !this.scissor - && this.renderer._activeRenderTarget.root - && !this.renderer.stencil.stencilMaskStack.length - && maskData.isFastRect()) - { - const matrix = maskData.worldTransform; - - let rot = Math.atan2(matrix.b, matrix.a); - - // use the nearest degree! - rot = Math.round(rot * (180 / Math.PI)); - - if (rot % 90) - { - this.pushStencilMask(maskData); - } - else - { - this.pushScissorMask(target, maskData); - } - } - else - { - this.pushStencilMask(maskData); - } - } - - /** - * Removes the last mask from the mask stack and doesn't return it. - * - * @param {PIXI.DisplayObject} target - Display Object to pop the mask from - * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. - */ - pop(target, maskData) - { - if (maskData.texture) - { - this.popSpriteMask(target, maskData); - } - else if (this.enableScissor && !this.renderer.stencil.stencilMaskStack.length) - { - this.popScissorMask(target, maskData); - } - else - { - this.popStencilMask(target, maskData); - } - } - - /** - * Applies the Mask and adds it to the current filter stack. - * - * @param {PIXI.RenderTarget} target - Display Object to push the sprite mask to - * @param {PIXI.Sprite} maskData - Sprite to be used as the mask - */ - pushSpriteMask(target, maskData) - { - let alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; - - if (!alphaMaskFilter) - { - alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; - } - - alphaMaskFilter[0].resolution = this.renderer.resolution; - alphaMaskFilter[0].maskSprite = maskData; - - // TODO - may cause issues! - target.filterArea = maskData.getBounds(true); - - this.renderer.filterSystem.pushFilter(target, alphaMaskFilter); - - this.alphaMaskIndex++; - } - - /** - * Removes the last filter from the filter stack and doesn't return it. - * - */ - popSpriteMask() - { - this.renderer.filterSystem.popFilter(); - this.alphaMaskIndex--; - } - - /** - * Applies the Mask and adds it to the current filter stack. - * - * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. - */ - pushStencilMask(maskData) - { - this.renderer.batch.flush(); - this.renderer.stencil.pushStencil(maskData); - } - - /** - * Removes the last filter from the filter stack and doesn't return it. - * - */ - popStencilMask() - { - // this.renderer.currentRenderer.stop(); - this.renderer.stencil.popStencil(); - } - - /** - * - * @param {PIXI.DisplayObject} target - Display Object to push the mask to - * @param {PIXI.Graphics} maskData - The masking data. - */ - pushScissorMask(target, maskData) - { - maskData.renderable = true; - - const renderTarget = this.renderer._activeRenderTarget; - - const bounds = maskData.getBounds(); - - bounds.fit(renderTarget.size); - maskData.renderable = false; - - this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); - - const resolution = this.renderer.resolution; - - this.renderer.gl.scissor( - bounds.x * resolution, - (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, - bounds.width * resolution, - bounds.height * resolution - ); - - this.scissorRenderTarget = renderTarget; - this.scissorData = maskData; - this.scissor = true; - } - - /** - * - * - */ - popScissorMask() - { - this.scissorRenderTarget = null; - this.scissorData = null; - this.scissor = false; - - // must be scissor! - const gl = this.renderer.gl; - - gl.disable(gl.SCISSOR_TEST); - } -} diff --git a/packages/core/src/renderers/systems/ProjectionSystem.js b/packages/core/src/renderers/systems/ProjectionSystem.js deleted file mode 100644 index ec5efdb..0000000 --- a/packages/core/src/renderers/systems/ProjectionSystem.js +++ /dev/null @@ -1,73 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { Matrix } from '@pixi/math'; - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ - -export default class ProjectionSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this.projectionMatrix = new Matrix(); - } - - update(destinationFrame, sourceFrame, resolution, root) - { - this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; - this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; - - this.calculateProjection(this.destinationFrame, this.sourceFrame, resolution, root); - - this.renderer.globalUniforms.uniforms.projectionMatrix = this.projectionMatrix; - this.renderer.globalUniforms.update(); - } - - /** - * Updates the projection matrix based on a projection frame (which is a rectangle) - * - * @param {Rectangle} destinationFrame - The destination frame. - * @param {Rectangle} sourceFrame - The source frame. - */ - calculateProjection(destinationFrame, sourceFrame, resolution, root) - { - const pm = this.projectionMatrix; - - // I don't think we will need this line.. - // pm.identity(); - - if (!root) - { - pm.a = (1 / destinationFrame.width * 2) * resolution; - pm.d = (1 / destinationFrame.height * 2) * resolution; - - pm.tx = -1 - (sourceFrame.x * pm.a); - pm.ty = -1 - (sourceFrame.y * pm.d); - } - else - { - pm.a = (1 / destinationFrame.width * 2) * resolution; - pm.d = (-1 / destinationFrame.height * 2) * resolution; - - pm.tx = -1 - (sourceFrame.x * pm.a); - pm.ty = 1 - (sourceFrame.y * pm.d); - } - } - - /** - * Sets the transform of the active render target to the given matrix - * - * @param {PIXI.Matrix} matrix - The transformation matrix - */ - setTransform()// matrix) - { - // this._activeRenderTarget.transform = matrix; - } -} diff --git a/packages/core/src/renderers/systems/RenderTextureSystem.js b/packages/core/src/renderers/systems/RenderTextureSystem.js deleted file mode 100644 index a133ef1..0000000 --- a/packages/core/src/renderers/systems/RenderTextureSystem.js +++ /dev/null @@ -1,115 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { Rectangle } from '@pixi/math'; - -const tempRect = new Rectangle(); - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ - -export default class RenderTextureSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this.clearColor = renderer._backgroundColorRgba; - - // TODO moe this property somewhere else! - this.defaultMaskStack = []; - this.defaultFilterStack = [{}]; - - // empty render texture? - this.renderTexture = null; - - this.destinationFrame = new Rectangle(); - } - - bind(renderTexture, sourceFrame, destinationFrame) - { - // TODO - do we want this?? - if (this.renderTexture === renderTexture) return; - this.renderTexture = renderTexture; - - const renderer = this.renderer; - - if (renderTexture) - { - const baseTexture = renderTexture.baseTexture; - - if (!destinationFrame) - { - tempRect.width = baseTexture.realWidth; - tempRect.height = baseTexture.realHeight; - - destinationFrame = tempRect; - } - - if (!sourceFrame) - { - sourceFrame = destinationFrame; - } - - this.renderer.framebuffer.bind(baseTexture.frameBuffer, destinationFrame); - - this.renderer.projection.update(destinationFrame, sourceFrame, baseTexture.resolution, false); - this.renderer.stencil.setMaskStack(baseTexture.stencilMaskStack); - } - else - { - // TODO these validation checks happen deeper down.. - // thing they can be avoided.. - if (!destinationFrame) - { - tempRect.width = renderer.width; - tempRect.height = renderer.height; - - destinationFrame = tempRect; - } - - if (!sourceFrame) - { - sourceFrame = destinationFrame; - } - - renderer.framebuffer.bind(null, destinationFrame); - - // TODO store this.. - this.renderer.projection.update(destinationFrame, sourceFrame, this.renderer.resolution, true); - this.renderer.stencil.setMaskStack(this.defaultMaskStack); - } - - this.destinationFrame.copyFrom(destinationFrame); - } - - /** - * Erases the render texture and fills the drawing area with a colour - * - * @param {number} [clearColor] - The colour - * @return {PIXI.Renderer} Returns itself. - */ - clear(clearColor) - { - if (this.renderTexture) - { - clearColor = clearColor || this.renderTexture.baseTexture.clearColor; - } - else - { - clearColor = clearColor || this.clearColor; - } - - this.renderer.framebuffer.clear(clearColor[0], clearColor[1], clearColor[2], clearColor[3]); - } - - resize()// screenWidth, screenHeight) - { - // resize the root only! - this.bind(null); - } -} diff --git a/packages/core/src/renderers/systems/StateSystem.js b/packages/core/src/renderers/systems/StateSystem.js deleted file mode 100755 index d7f4caf..0000000 --- a/packages/core/src/renderers/systems/StateSystem.js +++ /dev/null @@ -1,288 +0,0 @@ -import mapWebGLBlendModesToPixi from '../utils/mapWebGLBlendModesToPixi'; -import WebGLSystem from './WebGLSystem'; -import WebGLState from '../State'; - -const BLEND = 0; -const OFFSET = 1; -const CULLING = 2; -const DEPTH_TEST = 3; -const WINDING = 4; - -/** - * A WebGL state machines - * - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class StateSystem extends WebGLSystem -{ - /** - * @param {WebGLRenderingContext} gl - The current WebGL rendering context - */ - constructor(renderer) - { - super(renderer); - - this.gl = null; - - this.maxAttribs = null; - - // check we have vao.. - this.nativeVaoExtension = null; - - this.attribState = null; - - this.stateId = 0; - this.polygonOffset = 0; - this.blendMode = 17; - - this.map = []; - - // map functions for when we set state.. - this.map[BLEND] = this.setBlend; - this.map[OFFSET] = this.setOffset; - this.map[CULLING] = this.setCullFace; - this.map[DEPTH_TEST] = this.setDepthTest; - this.map[WINDING] = this.setFrontFace; - - this.checks = []; - - this.defaultState = new WebGLState(); - this.defaultState.blend = true; - this.defaultState.depth = true; - } - - contextChange(gl) - { - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; - - this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); - - // check we have vao.. - this.nativeVaoExtension = ( - gl.getExtension('OES_vertex_array_object') - || gl.getExtension('MOZ_OES_vertex_array_object') - || gl.getExtension('WEBKIT_OES_vertex_array_object') - ); - - this.attribState = { - tempAttribState: new Array(this.maxAttribs), - attribState: new Array(this.maxAttribs), - }; - - this.blendModes = mapWebGLBlendModesToPixi(gl); - - this.setState(this.defaultState); - - this.reset(); - } - - /** - * Sets the current state - * - * @param {*} state - The state to set. - */ - setState(state) - { - state = state || this.defaultState; - - // TODO maybe to an object check? ( this.state === state )? - if (this.stateId !== state.data) - { - let diff = this.stateId ^ state.data; - let i = 0; - - // order from least to most common - while (diff) - { - if (diff & 1) - { - // state change! - this.map[i].call(this, !!(state.data & (1 << i))); - } - - diff = diff >> 1; - i++; - } - - this.stateId = state.data; - } - - // based on the above settings we check for specific modes.. - // for example if blend is active we check and set the blend modes - // or of polygon offset is active we check the poly depth. - for (let i = 0; i < this.checks.length; i++) - { - this.checks[i](this, state); - } - } - - /** - * Enables or disabled blending. - * - * @param {boolean} value - Turn on or off webgl blending. - */ - setBlend(value) - { - this.updateCheck(StateSystem.checkBlendMode, value); - - this.gl[value ? 'enable' : 'disable'](this.gl.BLEND); - } - - /** - * Enables or disable polygon offset fill - * - * @param {boolean} value - Turn on or off webgl polygon offset testing. - */ - setOffset(value) - { - this.gl[value ? 'enable' : 'disable'](this.gl.POLYGON_OFFSET_FILL); - } - - /** - * Sets whether to enable or disable depth test. - * - * @param {boolean} value - Turn on or off webgl depth testing. - */ - setDepthTest(value) - { - this.gl[value ? 'enable' : 'disable'](this.gl.DEPTH_TEST); - } - - /** - * Sets whether to enable or disable cull face. - * - * @param {boolean} value - Turn on or off webgl cull face. - */ - setCullFace(value) - { - this.gl[value ? 'enable' : 'disable'](this.gl.CULL_FACE); - } - - /** - * Sets the gl front face. - * - * @param {boolean} value - true is clockwise and false is counter-clockwise - */ - setFrontFace(value) - { - this.gl.frontFace(this.gl[value ? 'CW' : 'CCW']); - } - - /** - * Sets the blend mode. - * - * @param {number} value - The blend mode to set to. - */ - setBlendMode(value) - { - if (value === this.blendMode) - { - return; - } - - this.blendMode = value; - this.gl.blendFunc(this.blendModes[value][0], this.blendModes[value][1]); - } - - /** - * Sets the polygon offset. - * - * @param {number} value - the polygon offset - * @param {number} scale - the polygon offset scale - */ - setPolygonOffset(value, scale) - { - this.gl.polygonOffset(value, scale); - } - - /** - * Disables all the vaos in use - * - */ - resetAttributes() - { - for (let i = 0; i < this.attribState.tempAttribState.length; i++) - { - this.attribState.tempAttribState[i] = 0; - } - - for (let i = 0; i < this.attribState.attribState.length; i++) - { - this.attribState.attribState[i] = 0; - } - - // im going to assume one is always active for performance reasons. - for (let i = 1; i < this.maxAttribs; i++) - { - this.gl.disableVertexAttribArray(i); - } - } - - // used - /** - * Resets all the logic and disables the vaos - */ - reset() - { - // unbind any VAO if they exist.. - if (this.nativeVaoExtension) - { - this.nativeVaoExtension.bindVertexArrayOES(null); - } - - // reset all attributes.. - this.resetAttributes(); - - this.gl.pixelStorei(this.gl.UNPACK_FLIP_Y_WEBGL, false); - - this.setBlendMode(0); - - // TODO? - // this.setState(this.defaultState); - } - - /** - * checks to see which updates should be checked based on which settings have been activated - * for example if blend is enabled then we shold check the blend modes each time the state is changed - * or if poygon fill is activated then we need to check if the polygone offset changes. - * The idea is that we only check what we have too. - * - * @param {Function} func the checking function to add or remove - * @param {boolean} value should the check function be added or removed. - */ - updateCheck(func, value) - { - const index = this.checks.indexOf(func); - - if (value && index === -1) - { - this.checks.push(func); - } - else if (!value && index !== -1) - { - this.checks.splice(index, 1); - } - } - - /** - * A private little wrapper function that we call to check the blend mode. - * - * @static - * @private - * @param {PIXI.StateSystem} System the System to perform the state check on - * @param {PIXI.State} state the state that the blendMode will pulled from - */ - static checkBlendMode(system, state) - { - system.setBlendMode(state.blendMode); - } - - // TODO - add polygon offset? -} diff --git a/packages/core/src/renderers/systems/StencilSystem.js b/packages/core/src/renderers/systems/StencilSystem.js deleted file mode 100644 index 5d538cb..0000000 --- a/packages/core/src/renderers/systems/StencilSystem.js +++ /dev/null @@ -1,133 +0,0 @@ -import WebGLSystem from './WebGLSystem'; - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class StencilSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - this.stencilMaskStack = []; - } - - /** - * Changes the mask stack that is used by this System. - * - * @param {PIXI.Graphics[]} stencilMaskStack - The mask stack - */ - setMaskStack(stencilMaskStack) - { - const gl = this.renderer.gl; - - if (stencilMaskStack.length !== this.stencilMaskStack.length) - { - if (stencilMaskStack.length === 0) - { - gl.disable(gl.STENCIL_TEST); - } - else - { - gl.enable(gl.STENCIL_TEST); - } - } - - this.stencilMaskStack = stencilMaskStack; - } - - /** - * Applies the Mask and adds it to the current stencil stack. @alvin - * - * @param {PIXI.Graphics} graphics - The mask - */ - pushStencil(graphics) - { - this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); - - // this.renderer._activeRenderTarget.attachStencilBuffer(); - - const gl = this.renderer.gl; - const prevMaskCount = this.stencilMaskStack.length; - - if (prevMaskCount === 0) - { - gl.enable(gl.STENCIL_TEST); - } - - this.stencilMaskStack.push(graphics); - - // Increment the refference stencil value where the new mask overlaps with the old ones. - gl.colorMask(false, false, false, false); - gl.stencilFunc(gl.EQUAL, prevMaskCount, this._getBitwiseMask()); - gl.stencilOp(gl.KEEP, gl.KEEP, gl.INCR); - this.renderer.plugins.graphics.render(graphics); - - this._useCurrent(); - } - - /** - * Removes the last mask from the stencil stack. @alvin - */ - popStencil() - { - this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); - - const gl = this.renderer.gl; - const graphics = this.stencilMaskStack.pop(); - - if (this.stencilMaskStack.length === 0) - { - // the stack is empty! - gl.disable(gl.STENCIL_TEST); - gl.clear(gl.STENCIL_BUFFER_BIT); - gl.clearStencil(0); - } - else - { - // Decrement the refference stencil value where the popped mask overlaps with the other ones - gl.colorMask(false, false, false, false); - gl.stencilOp(gl.KEEP, gl.KEEP, gl.DECR); - this.renderer.plugins.graphics.render(graphics); - - this._useCurrent(); - } - } - - /** - * Setup renderer to use the current stencil data. - */ - _useCurrent() - { - const gl = this.renderer.gl; - - gl.colorMask(true, true, true, true); - gl.stencilFunc(gl.EQUAL, this.stencilMaskStack.length, this._getBitwiseMask()); - gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP); - } - - /** - * Fill 1s equal to the number of acitve stencil masks. - * - * @return {number} The bitwise mask. - */ - _getBitwiseMask() - { - return (1 << this.stencilMaskStack.length) - 1; - } - - /** - * Destroys the mask stack. - * - */ - destroy() - { - super.destroy(this); - - this.stencilMaskStack = null; - } -} diff --git a/packages/core/src/renderers/systems/WebGLSystem.js b/packages/core/src/renderers/systems/WebGLSystem.js deleted file mode 100644 index d3d5b31..0000000 --- a/packages/core/src/renderers/systems/WebGLSystem.js +++ /dev/null @@ -1,41 +0,0 @@ -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this manager works for. - */ - constructor(renderer) - { - /** - * The renderer this manager works for. - * - * @member {PIXI.Renderer} - */ - this.renderer = renderer; - - this.renderer.runners.contextChange.add(this); - } - - /** - * Generic method called when there is a WebGL context change. - * - */ - contextChange() - { - // do some codes init! - } - - /** - * Generic destroy methods to be overridden by the subclass - * - */ - destroy() - { - this.renderer.runners.contextChange.remove(this); - this.renderer = null; - } -} diff --git a/packages/core/src/renderers/systems/filter/FilterSystem.js b/packages/core/src/renderers/systems/filter/FilterSystem.js deleted file mode 100644 index e615037..0000000 --- a/packages/core/src/renderers/systems/filter/FilterSystem.js +++ /dev/null @@ -1,431 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; - -import RenderTexture from '../../../textures/RenderTexture'; -import Quad from '../../utils/Quad'; -import QuadUv from '../../utils/QuadUv'; -import { Rectangle } from '@pixi/math'; -import * as filterTransforms from '../../filters/filterTransforms'; -import bitTwiddle from 'bit-twiddle'; -import UniformGroup from '../../../shader/UniformGroup'; -import { DRAW_MODES } from '../../../../../constants'; - -// -/** - * @ignore - * @class - */ -class FilterState -{ - /** - * - */ - constructor() - { - this.renderTexture = null; - this.sourceFrame = new Rectangle(); - this.destinationFrame = new Rectangle(); - this.filters = []; - this.target = null; - this.legacy = false; - this.resolution = 1; - } -} - -/** - * @class - * @memberof PIXI.systems - * @extends PIXI.systems.WebGLSystem - */ -export default class FilterSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - /** - * stores a bunch of PO2 textures used for filtering - * @type {Object} - */ - this.texturePool = {}; - - /** - * a pool for storing filter states, save us creating new ones each tick - * @type {Array} - */ - this.statePool = []; - - /** - * A very simple geometry used when drawing a filter effect to the screen - * @type {Quad} - */ - this.quad = new Quad(); - - this.quadUv = new QuadUv(); - - /** - * Temporary rect for maths - * @type {PIXI.Rectangle} - */ - this.tempRect = new Rectangle(); - - this.activeState = {}; - - /** - * this uniform group is attached to filter uniforms when used - * @type {UniformGroup} - */ - this.globalUniforms = new UniformGroup({ - sourceFrame: this.tempRect, - destinationFrame: this.tempRect, - - // legacy variables - filterArea: new Float32Array(4), - filterClamp: new Float32Array(4), - }, true); - } - - /** - * Adds a new filter to the System. - * - * @param {PIXI.DisplayObject} target - The target of the filter to render. - * @param {PIXI.Filter[]} filters - The filters to apply. - */ - push(target, filters) - { - const renderer = this.renderer; - const filterStack = this.renderer.renderTexture.defaultFilterStack; - const state = this.statePool.pop() || new FilterState(); - - let resolution = filters[0].resolution; - let padding = filters[0].padding; - let autoFit = filters[0].autoFit; - let legacy = filters[0].legacy; - - for (let i = 1; i < filters.length; i++) - { - const filter = filters[i]; - - // lets use the lowest resolution.. - resolution = Math.min(resolution, filter.resolution); - // and the largest amount of padding! - padding = Math.max(padding, filter.padding); - // only auto fit if all filters are autofit - autoFit = autoFit || filter.autoFit; - - legacy = legacy || filter.legacy; - } - - filterStack.push(state); - - state.resolution = resolution; - - state.legacy = legacy; - - // round to whole number based on resolution - // TODO move that to the shader too? - state.sourceFrame = target.filterArea || target.getBounds(true); - - state.sourceFrame.pad(padding); - - if (autoFit) - { - state.sourceFrame.fit(this.renderer.renderTexture.destinationFrame); - } - - state.sourceFrame.round(resolution); - - state.renderTexture = this.getPotFilterTexture(state.sourceFrame.width, state.sourceFrame.height, resolution); - state.filters = filters; - - state.destinationFrame.width = state.renderTexture.width; - state.destinationFrame.height = state.renderTexture.height; - - state.renderTexture.filterFrame = state.sourceFrame; - renderer.renderTexture.bind(state.renderTexture, state.sourceFrame);// /, state.destinationFrame); - renderer.renderTexture.clear(); - } - - /** - * Pops off the filter and applies it. - * - */ - pop() - { - const renderer = this.renderer; - const filterStack = renderer.renderTexture.defaultFilterStack; - const state = filterStack.pop(); - const filters = state.filters; - - this.activeState = state; - - const globalUniforms = this.globalUniforms.uniforms; - - globalUniforms.sourceFrame = state.sourceFrame; - globalUniforms.destinationFrame = state.destinationFrame; - globalUniforms.resolution = state.resolution; - - // only update the rect if its legacy.. - if (state.legacy) - { - const filterArea = globalUniforms.filterArea; - const filterClamp = globalUniforms.filterClamp; - - filterArea[0] = state.destinationFrame.width; - filterArea[1] = state.destinationFrame.height; - filterArea[2] = state.sourceFrame.x; - filterArea[3] = state.sourceFrame.y; - - filterClamp[0] = 0.5 / state.resolution / state.destinationFrame.width; - filterClamp[1] = 0.5 / state.resolution / state.destinationFrame.height; - filterClamp[2] = (state.sourceFrame.width - 0.5) / state.resolution / state.destinationFrame.width; - filterClamp[3] = (state.sourceFrame.height - 0.5) / state.resolution / state.destinationFrame.height; - } - - this.globalUniforms.update(); - - const lastState = filterStack[filterStack.length - 1]; - - if (filters.length === 1) - { - filters[0].apply(this, state.renderTexture, lastState.renderTexture, false, state); - - this.returnFilterTexture(state.renderTexture); - } - else - { - let flip = state.renderTexture; - let flop = this.getPotFilterTexture( - flip.width, - flip.height, - state.resolution - ); - - flop.filterFrame = flip.filterFrame; - - let i = 0; - - for (i = 0; i < filters.length - 1; ++i) - { - filters[i].apply(this, flip, flop, true, state); - - const t = flip; - - flip = flop; - flop = t; - } - - filters[i].apply(this, flip, lastState.renderTexture, false, state); - - this.returnFilterTexture(flip); - this.returnFilterTexture(flop); - } - - this.statePool.push(state); - } - - /** - * Draws a filter. - * - * @param {PIXI.Filter} filter - The filter to draw. - * @param {PIXI.RenderTarget} input - The input render target. - * @param {PIXI.RenderTarget} output - The target to output to. - * @param {boolean} clear - Should the output be cleared before rendering to it - */ - applyFilter(filter, input, output, clear) - { - const renderer = this.renderer; - - renderer.renderTexture.bind(output, output ? output.filterFrame : null); - - if (clear) - { - // gl.disable(gl.SCISSOR_TEST); - renderer.renderTexture.clear(); - // gl.enable(gl.SCISSOR_TEST); - } - - // set the uniforms.. - filter.uniforms.uSampler = input; - filter.uniforms.filterGlobals = this.globalUniforms; - - // TODO make it so that the order of this does not matter.. - // because it does at the moment cos of global uniforms. - // they need to get resynced - - renderer.state.setState(filter.state); - renderer.shader.bind(filter); - - if (filter.legacy) - { - this.quadUv.map(input._frame, input.filterFrame); - - renderer.geometry.bind(this.quadUv); - renderer.geometry.draw(DRAW_MODES.TRIANGLES); - } - else - { - renderer.geometry.bind(this.quad); - renderer.geometry.draw(DRAW_MODES.TRIANGLE_STRIP); - } - } - - /** - * Calculates the mapped matrix. - * - * TODO playing around here.. this is temporary - (will end up in the shader) - * this returns a matrix that will normalise map filter cords in the filter to screen space - * - * @param {PIXI.Matrix} outputMatrix - the matrix to output to. - * @return {PIXI.Matrix} The mapped matrix. - */ - calculateScreenSpaceMatrix(outputMatrix) - { - const currentState = this.activeState; - - return filterTransforms.calculateScreenSpaceMatrix( - outputMatrix, - currentState.sourceFrame, - currentState.destinationFrame - ); - } - - /** - * This will map the filter coord so that a texture can be used based on the transform of a sprite - * - * @param {PIXI.Matrix} outputMatrix - The matrix to output to. - * @param {PIXI.Sprite} sprite - The sprite to map to. - * @return {PIXI.Matrix} The mapped matrix. - */ - calculateSpriteMatrix(outputMatrix, sprite) - { - const currentState = this.activeState; - - return filterTransforms.calculateSpriteMatrix( - outputMatrix, - currentState.sourceFrame, - currentState.destinationFrame, - sprite - ); - } - - /** - * Destroys this Filter System. - * - * @param {boolean} [contextLost=false] context was lost, do not free shaders - * - */ - destroy(contextLost = false) - { - if (!contextLost) - { - this.emptyPool(); - } - else - { - this.texturePool = {}; - } - } - - /** - * Gets a Power-of-Two render texture. - * - * TODO move to a seperate class could be on renderer? - * also - could cause issue with multiple contexts? - * - * @private - * @param {WebGLRenderingContext} gl - The webgl rendering context - * @param {number} minWidth - The minimum width of the render target. - * @param {number} minHeight - The minimum height of the render target. - * @param {number} resolution - The resolution of the render target. - * @return {PIXI.RenderTarget} The new render target. - */ - getPotFilterTexture(minWidth, minHeight, resolution) - { - minWidth = bitTwiddle.nextPow2(minWidth); - minHeight = bitTwiddle.nextPow2(minHeight); - resolution = resolution || 1; - - const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); - - if (!this.texturePool[key]) - { - this.texturePool[key] = []; - } - - let renderTexture = this.texturePool[key].pop(); - - if (!renderTexture) - { - // temporary bypass cache.. - // internally - this will cause a texture to be bound.. - renderTexture = RenderTexture.create({ - width: minWidth, - height: minHeight, - resolution, - }); - } - - return renderTexture; - } - - /** - * Gets extra render texture to use inside current filter - * - * @param {number} resolution resolution of the renderTexture - * @returns {PIXI.RenderTexture} - */ - getFilterTexture(resolution) - { - const rt = this.activeState.renderTexture; - - const filterTexture = this.getPotFilterTexture(rt.width, rt.height, resolution || rt.baseTexture.resolution); - - filterTexture.filterFrame = rt.filterFrame; - - return filterTexture; - } - - /** - * Frees a render texture back into the pool. - * - * @param {PIXI.RenderTarget} renderTexture - The renderTarget to free - */ - returnFilterTexture(renderTexture) - { - renderTexture.filterFrame = null; - - const base = renderTexture.baseTexture; - - const minWidth = base.width; - const minHeight = base.height; - - const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); - - this.texturePool[key].push(renderTexture); - } - - /** - * Empties the texture pool. - * - */ - emptyPool() - { - for (const i in this.texturePool) - { - const textures = this.texturePool[i]; - - if (textures) - { - for (let j = 0; j < textures.length; j++) - { - textures[j].destroy(true); - } - } - } - - this.texturePool = {}; - } -} diff --git a/packages/core/src/renderers/systems/geometry/GLBuffer.js b/packages/core/src/renderers/systems/geometry/GLBuffer.js deleted file mode 100644 index 0477e8a..0000000 --- a/packages/core/src/renderers/systems/geometry/GLBuffer.js +++ /dev/null @@ -1,9 +0,0 @@ -export default class GLBuffer -{ - constructor(buffer) - { - this.buffer = buffer; - this.updateID = -1; - this.byteLength = -1; - } -} diff --git a/packages/core/src/renderers/systems/geometry/GeometrySystem.js b/packages/core/src/renderers/systems/geometry/GeometrySystem.js deleted file mode 100644 index 26c8e9e..0000000 --- a/packages/core/src/renderers/systems/geometry/GeometrySystem.js +++ /dev/null @@ -1,388 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; -import GLBuffer from './GLBuffer'; - -const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class GeometrySystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this._activeGeometry = null; - this._activeVao = null; - - this.hasVao = true; - this.hasInstance = true; - } - - /** - * Sets up the renderer context and necessary buffers. - * - * @private - */ - contextChange() - { - const gl = this.gl = this.renderer.gl; - - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - - // webgl2 - if (!gl.createVertexArray) - { - // webgl 1! - let nativeVaoExtension = this.renderer.context.extensions.vertexArrayObject; - - if (this.renderer.legacy) - { - nativeVaoExtension = null; - } - - if (nativeVaoExtension) - { - gl.createVertexArray = () => - nativeVaoExtension.createVertexArrayOES(); - - gl.bindVertexArray = (vao) => - nativeVaoExtension.bindVertexArrayOES(vao); - - gl.deleteVertexArray = (vao) => - nativeVaoExtension.deleteVertexArrayOES(vao); - } - else - { - this.hasVao = false; - gl.createVertexArray = () => - { - // empty - }; - - gl.bindVertexArray = () => - { - // empty - }; - - gl.deleteVertexArray = () => - { - // empty - }; - } - } - - if (!gl.vertexAttribDivisor) - { - const instanceExt = gl.getExtension('ANGLE_instanced_arrays'); - - if (instanceExt) - { - gl.vertexAttribDivisor = (a, b) => - instanceExt.vertexAttribDivisorANGLE(a, b); - - gl.drawElementsInstanced = (a, b, c, d, e) => - instanceExt.drawElementsInstancedANGLE(a, b, c, d, e); - - gl.drawArraysInstanced = (a, b, c, d) => - instanceExt.drawArraysInstancedANGLE(a, b, c, d); - } - else - { - this.hasInstance = false; - } - } - } - - /** - * Binds geometry so that is can be drawn. Creating a Vao if required - * @private - * @param {PIXI.Geometry} geometry instance of geometry to bind - */ - bind(geometry, shader) - { - shader = shader || this.renderer.shader.shader; - - const gl = this.gl; - - // not sure the best way to address this.. - // currently different shaders require different VAOs for the same geometry - // Still mulling over the best way to solve this one.. - // will likely need to modify the shader attribute locations at run time! - let vaos = geometry.glVertexArrayObjects[this.CONTEXT_UID]; - - if (!vaos) - { - geometry.glVertexArrayObjects[this.CONTEXT_UID] = vaos = {}; - } - - const vao = vaos[shader.program.id] || this.initGeometryVao(geometry, shader.program); - - this._activeGeometry = geometry; - - if (this._activeVao !== vao) - { - this._activeVao = vao; - - if (this.hasVao) - { - gl.bindVertexArray(vao); - } - else - { - this.activateVao(geometry, shader.program); - } - } - - // TODO - optimise later! - // don't need to loop through if nothing changed! - // maybe look to add an 'autoupdate' to geometry? - this.updateBuffers(); - } - - reset() - { - this.unbind(); - } - - updateBuffers() - { - const geometry = this._activeGeometry; - const gl = this.gl; - - for (let i = 0; i < geometry.buffers.length; i++) - { - const buffer = geometry.buffers[i]; - - const glBuffer = buffer._glBuffers[this.CONTEXT_UID]; - - if (buffer._updateID !== glBuffer.updateID) - { - glBuffer.updateID = buffer._updateID; - - // TODO can cache this on buffer! maybe added a getter / setter? - const type = buffer.index ? gl.ELEMENT_ARRAY_BUFFER : gl.ARRAY_BUFFER; - const drawType = buffer.static ? gl.STATIC_DRAW : gl.DYNAMIC_DRAW; - - gl.bindBuffer(type, glBuffer.buffer); - - if (glBuffer.byteLength >= buffer.data.byteLength) - { - // offset is always zero for now! - gl.bufferSubData(type, 0, buffer.data); - } - else - { - gl.bufferData(type, buffer.data, drawType); - } - } - } - } - - checkCompatability(geometry, program) - { - // geometry must have at least all the attributes that the shader requires. - const geometryAttributes = geometry.attributes; - const shaderAttributes = program.attributeData; - - for (const j in shaderAttributes) - { - if (!geometryAttributes[j]) - { - throw new Error(`shader and geometry incompatible, geometry missing the "${j}" attribute`); - } - } - } - - /** - * Creates a Vao with the same structure as the geometry and stores it on the geometry. - * @private - * @param {PIXI.Geometry} geometry instance of geometry to to generate Vao for - */ - initGeometryVao(geometry, program) - { - this.checkCompatability(geometry, program); - - const gl = this.gl; - const CONTEXT_UID = this.CONTEXT_UID; - const buffers = geometry.buffers; - const attributes = geometry.attributes; - - const tempStride = {}; - const tempStart = {}; - - for (const j in buffers) - { - tempStride[j] = 0; - tempStart[j] = 0; - } - - for (const j in attributes) - { - if (!attributes[j].size && program.attributeData[j]) - { - attributes[j].size = program.attributeData[j].size; - } - - tempStride[attributes[j].buffer] += attributes[j].size * byteSizeMap[attributes[j].type]; - } - - for (const j in attributes) - { - const attribute = attributes[j]; - const attribSize = attribute.size; - - if (attribute.stride === undefined) - { - if (tempStride[attribute.buffer] === attribSize * byteSizeMap[attribute.type]) - { - attribute.stride = 0; - } - else - { - attribute.stride = tempStride[attribute.buffer]; - } - } - - if (attribute.start === undefined) - { - attribute.start = tempStart[attribute.buffer]; - - tempStart[attribute.buffer] += attribSize * byteSizeMap[attribute.type]; - } - } - - // first update - and create the buffers! - // only create a gl buffer if it actually gets - for (let i = 0; i < buffers.length; i++) - { - const buffer = buffers[i]; - - if (!buffer._glBuffers[CONTEXT_UID]) - { - buffer._glBuffers[CONTEXT_UID] = new GLBuffer(gl.createBuffer()); - } - } - - // TODO - maybe make this a data object? - // lets wait to see if we need to first! - const vao = gl.createVertexArray(); - - gl.bindVertexArray(vao); - - this.activateVao(geometry, program); - - gl.bindVertexArray(null); - - // add it to the cache! - geometry.glVertexArrayObjects[this.CONTEXT_UID][program.id] = vao; - - return vao; - } - - activateVao(geometry, program) - { - const gl = this.gl; - const CONTEXT_UID = this.CONTEXT_UID; - const buffers = geometry.buffers; - const attributes = geometry.attributes; - - if (geometry.indexBuffer) - { - // first update the index buffer if we have one.. - gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, geometry.indexBuffer._glBuffers[CONTEXT_UID].buffer); - } - - let lastBuffer = null; - - // add a new one! - for (const j in attributes) - { - const attribute = attributes[j]; - const buffer = buffers[attribute.buffer]; - const glBuffer = buffer._glBuffers[CONTEXT_UID]; - - if (program.attributeData[j]) - { - if (lastBuffer !== glBuffer) - { - gl.bindBuffer(gl.ARRAY_BUFFER, glBuffer.buffer); - - lastBuffer = glBuffer; - } - - const location = program.attributeData[j].location; - - // TODO introduce state again - // we can optimise this for older devices that have no VAOs - gl.enableVertexAttribArray(location); - - gl.vertexAttribPointer(location, - attribute.size, - attribute.type || gl.FLOAT, - attribute.normalized, - attribute.stride, - attribute.start); - - if (attribute.instance) - { - // TODO calculate instance count based of this... - if (this.hasInstance) - { - gl.vertexAttribDivisor(location, 1); - } - else - { - throw new Error('geometry error, GPU Instancing is not supported on this device'); - } - } - } - } - } - - draw(type, size, start, instanceCount) - { - const gl = this.gl; - const geometry = this._activeGeometry; - - // TODO.. this should not change so maybe cache the function? - - if (geometry.indexBuffer) - { - if (geometry.instanced) - { - /* eslint-disable max-len */ - gl.drawElementsInstanced(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2, instanceCount || 1); - /* eslint-enable max-len */ - } - else - { - gl.drawElements(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2); - } - } - else - if (geometry.instanced) - { - // TODO need a better way to calculate size.. - gl.drawArraysInstanced(type, start, size || geometry.getSize(), instanceCount || 1); - } - else - { - gl.drawArrays(type, start, size || geometry.getSize()); - } - - return this; - } - - unbind() - { - this.gl.bindVertexArray(null); - this._activeVao = null; - this._activeGeometry = null; - } -} diff --git a/packages/core/src/renderers/systems/geometry/setVertexAttribArrays.js b/packages/core/src/renderers/systems/geometry/setVertexAttribArrays.js deleted file mode 100644 index 23b6959..0000000 --- a/packages/core/src/renderers/systems/geometry/setVertexAttribArrays.js +++ /dev/null @@ -1,54 +0,0 @@ -// var GL_MAP = {}; - -/** - * @param gl {WebGLRenderingContext} The current WebGL context - * @param attribs {*} - * @param state {*} - */ -export default function setVertexAttribArrays(gl, attribs, state) -{ - let i; - - if (state) - { - const tempAttribState = state.tempAttribState; - const attribState = state.attribState; - - for (i = 0; i < tempAttribState.length; i++) - { - tempAttribState[i] = false; - } - - // set the new attribs - for (i = 0; i < attribs.length; i++) - { - tempAttribState[attribs[i].attribute.location] = true; - } - - for (i = 0; i < attribState.length; i++) - { - if (attribState[i] !== tempAttribState[i]) - { - attribState[i] = tempAttribState[i]; - - if (state.attribState[i]) - { - gl.enableVertexAttribArray(i); - } - else - { - gl.disableVertexAttribArray(i); - } - } - } - } - else - { - for (i = 0; i < attribs.length; i++) - { - const attrib = attribs[i]; - - gl.enableVertexAttribArray(attrib.attribute.location); - } - } -} diff --git a/packages/core/src/renderers/systems/index.js b/packages/core/src/renderers/systems/index.js deleted file mode 100644 index c843f03..0000000 --- a/packages/core/src/renderers/systems/index.js +++ /dev/null @@ -1,6 +0,0 @@ -/** - * Systems are individual components to the Renderer pipeline. - * @namespace PIXI.systems - */ -export { default as WebGLSystem } from './WebGLSystem'; -export { default as FilterSystem } from './filter/FilterSystem'; diff --git a/packages/core/src/renderers/systems/shader/GLProgram.js b/packages/core/src/renderers/systems/shader/GLProgram.js deleted file mode 100644 index 92980ca..0000000 --- a/packages/core/src/renderers/systems/shader/GLProgram.js +++ /dev/null @@ -1,43 +0,0 @@ -/** - * Helper class to create a webGL Program - * - * @class - * @private - */ -export default class GLProgram -{ - constructor(program, uniformData) - { - /** - * The shader program - * - * @member {WebGLProgram} - */ - this.program = program; - - /** - * holds the uniform data which contains uniform locations - * and current uniform values used for caching and preventing undeed GPU commands - * @type {Object} - */ - this.uniformData = uniformData; - - /** - * uniformGroups holds the various upload functions for the shader. Each uniform group - * and program have a unique upload function generated. - * @type {Object} - */ - this.uniformGroups = {}; - } - - /** - * Destroys this program - * TODO - */ - destroy() - { - this.uniformData = null; - this.uniformGroups = null; - this.program = null; - } -} diff --git a/packages/core/src/renderers/systems/shader/ShaderSystem.js b/packages/core/src/renderers/systems/shader/ShaderSystem.js deleted file mode 100644 index 6cf57b6..0000000 --- a/packages/core/src/renderers/systems/shader/ShaderSystem.js +++ /dev/null @@ -1,170 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; -import GLProgram from './GLProgram'; -import generateUniformsSync from '../../../shader/generateUniformsSync'; -import defaultValue from '../../../shader/defaultValue'; -import compileProgram from './shader/compileProgram'; - -let UID = 0; - -/** - * Helper class to create a webGL Texture - * - * @class - * @memberof PIXI - */ -export default class ShaderSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - A reference to the current renderer - */ - constructor(renderer) - { - super(renderer); - - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = null; - - this.shader = null; - this.program = null; - - this.id = UID++; - } - - contextChange(gl) - { - this.gl = gl; - } - - /** - * Changes the current shader to the one given in parameter - * - * @param {PIXI.Shader} shader - the new shader - * @param {boolean} dontSync - false if the shader should automatically sync its uniforms. - * @returns {PIXI.glCore.glProgram} the glProgram that belongs to the shader. - */ - bind(shader, dontSync) - { - shader.uniforms.globals = this.renderer.globalUniforms; - - const program = shader.program; - const glProgram = program.glPrograms[this.renderer.CONTEXT_UID] || this.generateShader(shader); - - this.shader = shader; - - // TODO - some current pixi plugins bypass this.. so it not safe to use yet.. - if (this.program !== program) - { - this.program = program; - this.gl.useProgram(glProgram.program); - } - - if (!dontSync) - { - this.syncUniformGroup(shader.uniformGroup); - } - - return glProgram; - } - - /** - * Uploads the uniforms values to the currently bound shader. - * - * @param {object} uniforms - the uniforms valiues that be applied to the current shader - */ - setUniforms(uniforms) - { - const shader = this.shader.program; - const glProgram = shader.glPrograms[this.renderer.CONTEXT_UID]; - - shader.syncUniforms(glProgram.uniformData, uniforms, this.renderer); - } - - syncUniformGroup(group) - { - const glProgram = this.getglProgram(); - - if (!group.static || group.dirtyId !== glProgram.uniformGroups[group.id]) - { - glProgram.uniformGroups[group.id] = group.dirtyId; - const syncFunc = group.syncUniforms[this.shader.program.id] || this.createSyncGroups(group); - - syncFunc(glProgram.uniformData, group.uniforms, this.renderer); - } - } - - createSyncGroups(group) - { - group.syncUniforms[this.shader.program.id] = generateUniformsSync(group, this.shader.program.uniformData); - - return group.syncUniforms[this.shader.program.id]; - } - - /** - * Returns the underlying GLShade rof the currently bound shader. - * This can be handy for when you to have a little more control over the setting of your uniforms. - * - * @return {PIXI.glCore.Shader} the glProgram for the currently bound Shader for this context - */ - getglProgram() - { - if (this.shader) - { - return this.shader.program.glPrograms[this.renderer.CONTEXT_UID]; - } - - return null; - } - - /** - * Generates a glProgram verion of the Shader provided. - * - * @private - * @param {PIXI.Shader} shader the shader that the glProgram will be based on. - * @return {PIXI.glCore.glProgram} A shiney new glProgram - */ - generateShader(shader) - { - const gl = this.gl; - - const program = shader.program; - - const attribMap = {}; - - for (const i in program.attributeData) - { - attribMap[i] = program.attributeData[i].location; - } - - const shaderProgram = compileProgram(gl, program.vertexSrc, program.fragmentSrc, attribMap); - const uniformData = {}; - - for (const i in program.uniformData) - { - const data = program.uniformData[i]; - - uniformData[i] = { - location: gl.getUniformLocation(shaderProgram, i), - value: defaultValue(data.type, data.size), - }; - } - - const glProgram = new GLProgram(shaderProgram, uniformData); - - program.glPrograms[this.renderer.CONTEXT_UID] = glProgram; - - return glProgram; - } - - /** - * Destroys this System and removes all its textures - */ - destroy() - { - // TODO implement destroy method for ShaderSystem - this.destroyed = true; - } -} diff --git a/packages/core/src/renderers/systems/shader/shader/compileProgram.js b/packages/core/src/renderers/systems/shader/shader/compileProgram.js deleted file mode 100644 index 8c6ccb9..0000000 --- a/packages/core/src/renderers/systems/shader/shader/compileProgram.js +++ /dev/null @@ -1,78 +0,0 @@ -/** - * @method compileProgram - * @memberof PIXI.glCore.shader - * @param gl {WebGLRenderingContext} The current WebGL context {WebGLProgram} - * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. - * @param fragmentSrc {string|string[]} The fragment shader source as an array of strings. - * @param attributeLocations {Object} An attribute location map that lets you manually set the attribute locations - * @return {WebGLProgram} the shader program - */ -export default function compileProgram(gl, vertexSrc, fragmentSrc, attributeLocations) -{ - const glVertShader = compileShader(gl, gl.VERTEX_SHADER, vertexSrc); - const glFragShader = compileShader(gl, gl.FRAGMENT_SHADER, fragmentSrc); - - let program = gl.createProgram(); - - gl.attachShader(program, glVertShader); - gl.attachShader(program, glFragShader); - - // optionally, set the attributes manually for the program rather than letting WebGL decide.. - if (attributeLocations) - { - for (const i in attributeLocations) - { - gl.bindAttribLocation(program, attributeLocations[i], i); - } - } - - gl.linkProgram(program); - - // if linking fails, then log and cleanup - if (!gl.getProgramParameter(program, gl.LINK_STATUS)) - { - console.error('Pixi.js Error: Could not initialize shader.'); - console.error('gl.VALIDATE_STATUS', gl.getProgramParameter(program, gl.VALIDATE_STATUS)); - console.error('gl.getError()', gl.getError()); - - // if there is a program info log, log it - if (gl.getProgramInfoLog(program) !== '') - { - console.warn('Pixi.js Warning: gl.getProgramInfoLog()', gl.getProgramInfoLog(program)); - } - - gl.deleteProgram(program); - program = null; - } - - // clean up some shaders - gl.deleteShader(glVertShader); - gl.deleteShader(glFragShader); - - return program; -} - -/** - * @private - * @param gl {WebGLRenderingContext} The current WebGL context {WebGLProgram} - * @param type {Number} the type, can be either VERTEX_SHADER or FRAGMENT_SHADER - * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. - * @return {WebGLShader} the shader - */ -function compileShader(gl, type, src) -{ - const shader = gl.createShader(type); - - gl.shaderSource(shader, src); - gl.compileShader(shader); - - if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) - { - console.warn(src); - console.error(gl.getShaderInfoLog(shader)); - - return null; - } - - return shader; -} diff --git a/packages/core/src/renderers/systems/shader/shader/defaultValue.js b/packages/core/src/renderers/systems/shader/shader/defaultValue.js deleted file mode 100644 index 97290bd..0000000 --- a/packages/core/src/renderers/systems/shader/shader/defaultValue.js +++ /dev/null @@ -1,79 +0,0 @@ -/** - * @method defaultValue - * @memberof PIXI.glCore.shader - * @param type {String} Type of value - * @param size {Number} - */ -export default function defaultValue(type, size) -{ - switch (type) - { - case 'float': - return 0; - - case 'vec2': - return new Float32Array(2 * size); - - case 'vec3': - return new Float32Array(3 * size); - - case 'vec4': - return new Float32Array(4 * size); - - case 'int': - case 'sampler2D': - case 'sampler2DArray': - return 0; - - case 'ivec2': - return new Int32Array(2 * size); - - case 'ivec3': - return new Int32Array(3 * size); - - case 'ivec4': - return new Int32Array(4 * size); - - case 'bool': - return false; - - case 'bvec2': - - return booleanArray(2 * size); - - case 'bvec3': - return booleanArray(3 * size); - - case 'bvec4': - return booleanArray(4 * size); - - case 'mat2': - return new Float32Array([1, 0, - 0, 1]); - - case 'mat3': - return new Float32Array([1, 0, 0, - 0, 1, 0, - 0, 0, 1]); - - case 'mat4': - return new Float32Array([1, 0, 0, 0, - 0, 1, 0, 0, - 0, 0, 1, 0, - 0, 0, 0, 1]); - } - - return null; -} - -function booleanArray(size) -{ - const array = new Array(size); - - for (let i = 0; i < array.length; i++) - { - array[i] = false; - } - - return array; -} diff --git a/packages/core/src/renderers/systems/shader/shader/index.js b/packages/core/src/renderers/systems/shader/shader/index.js deleted file mode 100644 index ba7a317..0000000 --- a/packages/core/src/renderers/systems/shader/shader/index.js +++ /dev/null @@ -1,8 +0,0 @@ -/** - * @namespace PIXI.glCore.shader - */ -export { default as compileProgram } from './compileProgram'; -export { default as defaultValue } from './defaultValue'; -export { default as setPrecision } from './setPrecision'; -export { default as mapSize } from './mapSize'; -export { default as mapType } from './mapType'; diff --git a/packages/core/src/renderers/systems/shader/shader/mapSize.js b/packages/core/src/renderers/systems/shader/shader/mapSize.js deleted file mode 100644 index b681554..0000000 --- a/packages/core/src/renderers/systems/shader/shader/mapSize.js +++ /dev/null @@ -1,33 +0,0 @@ -const GLSL_TO_SIZE = { - float: 1, - vec2: 2, - vec3: 3, - vec4: 4, - - int: 1, - ivec2: 2, - ivec3: 3, - ivec4: 4, - - bool: 1, - bvec2: 2, - bvec3: 3, - bvec4: 4, - - mat2: 4, - mat3: 9, - mat4: 16, - - sampler2D: 1, -}; - -/** - * @method mapSize - * @memberof PIXI.glCore.shader - * @param type {String} - * @return {Number} - */ -export default function mapSize(type) -{ - return GLSL_TO_SIZE[type]; -} diff --git a/packages/core/src/renderers/systems/shader/shader/mapType.js b/packages/core/src/renderers/systems/shader/shader/mapType.js deleted file mode 100644 index d288b7c..0000000 --- a/packages/core/src/renderers/systems/shader/shader/mapType.js +++ /dev/null @@ -1,45 +0,0 @@ -let GL_TABLE = null; - -const GL_TO_GLSL_TYPES = { - FLOAT: 'float', - FLOAT_VEC2: 'vec2', - FLOAT_VEC3: 'vec3', - FLOAT_VEC4: 'vec4', - - INT: 'int', - INT_VEC2: 'ivec2', - INT_VEC3: 'ivec3', - INT_VEC4: 'ivec4', - - BOOL: 'bool', - BOOL_VEC2: 'bvec2', - BOOL_VEC3: 'bvec3', - BOOL_VEC4: 'bvec4', - - FLOAT_MAT2: 'mat2', - FLOAT_MAT3: 'mat3', - FLOAT_MAT4: 'mat4', - - SAMPLER_2D: 'sampler2D', - SAMPLER_CUBE: 'samplerCube', - SAMPLER_2D_ARRAY: 'sampler2DArray', -}; - -export default function mapType(gl, type) -{ - if (!GL_TABLE) - { - const typeNames = Object.keys(GL_TO_GLSL_TYPES); - - GL_TABLE = {}; - - for (let i = 0; i < typeNames.length; ++i) - { - const tn = typeNames[i]; - - GL_TABLE[gl[tn]] = GL_TO_GLSL_TYPES[tn]; - } - } - - return GL_TABLE[type]; -} diff --git a/packages/core/src/renderers/systems/shader/shader/setPrecision.js b/packages/core/src/renderers/systems/shader/shader/setPrecision.js deleted file mode 100644 index e2c1f34..0000000 --- a/packages/core/src/renderers/systems/shader/shader/setPrecision.js +++ /dev/null @@ -1,18 +0,0 @@ -/** - * Sets the float precision on the shader. If the precision is already present this function will do nothing - * @param {string} src the shader source - * @param {string} precision The float precision of the shader. Options are 'lowp', 'mediump' or 'highp'. - * - * @return {string} modified shader source - */ -export default function setPrecision(src, precision) -{ - src = src.trim(); - - if (src.substring(0, 9) !== 'precision' && src.substring(0, 1) !== '#') - { - return `precision ${precision} float;\n${src}`; - } - - return src; -} diff --git a/packages/core/src/renderers/systems/textures/GLTexture.js b/packages/core/src/renderers/systems/textures/GLTexture.js deleted file mode 100644 index c3fb8dc..0000000 --- a/packages/core/src/renderers/systems/textures/GLTexture.js +++ /dev/null @@ -1,33 +0,0 @@ -export default class GLTexture -{ - constructor(texture) - { - /** - * The WebGL texture - * - * @member {WebGLTexture} - */ - this.texture = texture; - - this.width = -1; - this.height = -1; - - /** - * Texture contents dirty flag - * @member {number} - */ - this.dirtyId = -1; - - /** - * Texture style dirty flag - * @type {number} - */ - this.dirtyStyleId = -1; - - /** - * Whether mip levels has to be generated - * @type {boolean} - */ - this.mipmap = false; - } -} diff --git a/packages/core/src/renderers/systems/textures/TextureGCSystem.js b/packages/core/src/renderers/systems/textures/TextureGCSystem.js deleted file mode 100644 index b089938..0000000 --- a/packages/core/src/renderers/systems/textures/TextureGCSystem.js +++ /dev/null @@ -1,110 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; -import { GC_MODES } from '@pixi/constants'; -import { settings } from '@pixi/settings'; - -/** - * TextureGarbageCollector. This class manages the GPU and ensures that it does not get clogged - * up with textures that are no longer being used. - * - * @class - * @memberof PIXI - */ -export default class TextureGCSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this.count = 0; - this.checkCount = 0; - this.maxIdle = settings.GC_MAX_IDLE; - this.checkCountMax = settings.GC_MAX_CHECK_COUNT; - this.mode = settings.GC_MODE; - } - - /** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ - postrender() - { - this.count++; - - if (this.mode === GC_MODES.MANUAL) - { - return; - } - - this.checkCount++; - - if (this.checkCount > this.checkCountMax) - { - this.checkCount = 0; - - this.run(); - } - } - - /** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ - run() - { - const tm = this.renderer.texture; - const managedTextures = tm.managedTextures; - let wasRemoved = false; - - for (let i = 0; i < managedTextures.length; i++) - { - const texture = managedTextures[i]; - - // only supports non generated textures at the moment! - if (!texture.frameBuffer && this.count - texture.touched > this.maxIdle) - { - tm.destroyTexture(texture, true); - managedTextures[i] = null; - wasRemoved = true; - } - } - - if (wasRemoved) - { - let j = 0; - - for (let i = 0; i < managedTextures.length; i++) - { - if (managedTextures[i] !== null) - { - managedTextures[j++] = managedTextures[i]; - } - } - - managedTextures.length = j; - } - } - - /** - * Removes all the textures within the specified displayObject and its children from the GPU - * - * @param {PIXI.DisplayObject} displayObject - the displayObject to remove the textures from. - */ - unload(displayObject) - { - const tm = this.renderer.textureSystem; - - // only destroy non generated textures - if (displayObject._texture && displayObject._texture._glRenderTargets) - { - tm.destroyTexture(displayObject._texture); - } - - for (let i = displayObject.children.length - 1; i >= 0; i--) - { - this.unload(displayObject.children[i]); - } - } -} diff --git a/packages/core/src/renderers/systems/textures/TextureSystem.js b/packages/core/src/renderers/systems/textures/TextureSystem.js deleted file mode 100644 index ccc5de9..0000000 --- a/packages/core/src/renderers/systems/textures/TextureSystem.js +++ /dev/null @@ -1,281 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; -import GLTexture from './GLTexture'; -import { removeItems } from '@pixi/utils'; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class TextureSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - // TODO set to max textures... - this.boundTextures = [ - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - ]; - - this.currentLocation = -1; - - this.managedTextures = []; - } - - /** - * Sets up the renderer context and necessary buffers. - * - * @private - */ - contextChange() - { - const gl = this.gl = this.renderer.gl; - - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - - // TODO move this.. to a nice make empty textures class.. - this.emptyTextures = {}; - - const emptyTexture2D = new GLTexture(gl.createTexture()); - - gl.bindTexture(gl.TEXTURE_2D, emptyTexture2D.texture); - gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array(4)); - - this.emptyTextures[gl.TEXTURE_2D] = emptyTexture2D; - this.emptyTextures[gl.TEXTURE_CUBE_MAP] = new GLTexture(gl.createTexture()); - - gl.bindTexture(gl.TEXTURE_CUBE_MAP, this.emptyTextures[gl.TEXTURE_CUBE_MAP].texture); - - let i; - - for (i = 0; i < 6; i++) - { - gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, null); - } - - gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_MAG_FILTER, gl.LINEAR); - gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_MIN_FILTER, gl.LINEAR); - - for (i = 0; i < this.boundTextures.length; i++) - { - this.bind(null, i); - } - } - - bind(texture, location) - { - const gl = this.gl; - - location = location || 0; - - if (this.currentLocation !== location) - { - this.currentLocation = location; - gl.activeTexture(gl.TEXTURE0 + location); - } - - if (texture) - { - texture = texture.baseTexture || texture; - - if (texture.valid) - { - texture.touched = this.renderer.textureGC.count; - - const glTexture = texture._glTextures[this.CONTEXT_UID] || this.initTexture(texture); - - gl.bindTexture(texture.target, glTexture.texture); - - if (glTexture.dirtyId !== texture.dirtyId) - { - this.updateTexture(texture); - } - - this.boundTextures[location] = texture; - } - } - else - { - gl.bindTexture(gl.TEXTURE_2D, this.emptyTextures[gl.TEXTURE_2D].texture); - this.boundTextures[location] = null; - } - } - - unbind(texture) - { - const gl = this.gl; - - for (let i = 0; i < this.boundTextures.length; i++) - { - if (this.boundTextures[i] === texture) - { - if (this.currentLocation !== i) - { - gl.activeTexture(gl.TEXTURE0 + i); - this.currentLocation = i; - } - - gl.bindTexture(gl.TEXTURE_2D, this.emptyTextures[texture.target].texture); - this.boundTextures[i] = null; - } - } - } - - initTexture(texture) - { - const glTexture = new GLTexture(this.gl.createTexture()); - - // guarentee an update.. - glTexture.dirtyId = -1; - - texture._glTextures[this.CONTEXT_UID] = glTexture; - - this.managedTextures.push(texture); - texture.on('dispose', this.destroyTexture, this); - - return glTexture; - } - - updateTexture(texture) - { - const glTexture = texture._glTextures[this.CONTEXT_UID]; - const renderer = this.renderer; - - if (texture.resource && texture.resource.upload(renderer, texture, glTexture)) - { - // texture is uploaded, dont do anything! - } - else - { - // default, renderTexture-like logic - const width = texture.realWidth; - const height = texture.realHeight; - const gl = renderer.gl; - - if (glTexture.width !== width - || glTexture.height !== height - || glTexture.dirtyId < 0) - { - glTexture.width = width; - glTexture.height = height; - - gl.texImage2D(texture.target, 0, - texture.format, - width, - height, - 0, - texture.format, - texture.type, - null); - } - } - - // lets only update what changes.. - if (texture.dirtyStyleId !== glTexture.dirtyStyleId) - { - this.updateTextureStyle(texture); - } - glTexture.dirtyId = texture.dirtyId; - } - - /** - * Deletes the texture from WebGL - * - * @param {PIXI.BaseTexture|PIXI.Texture} texture - the texture to destroy - * @param {boolean} [skipRemove=false] - Whether to skip removing the texture from the TextureManager. - */ - destroyTexture(texture, skipRemove) - { - const gl = this.gl; - - texture = texture.baseTexture || texture; - - if (texture._glTextures[this.renderer.CONTEXT_UID]) - { - this.unbind(texture); - - gl.deleteTexture(texture._glTextures[this.renderer.CONTEXT_UID].texture); - texture.off('dispose', this.destroyTexture, this); - - delete texture._glTextures[this.renderer.CONTEXT_UID]; - - if (!skipRemove) - { - const i = this.managedTextures.indexOf(texture); - - if (i !== -1) - { - removeItems(this.managedTextures, i, 1); - } - } - } - } - - updateTextureStyle(texture) - { - const glTexture = texture._glTextures[this.CONTEXT_UID]; - - glTexture.mipmap = texture.mipmap && texture.isPowerOfTwo; - if (!glTexture) - { - return; - } - - if (texture.resource && texture.resource.style(this.renderer, texture, glTexture)) - { - // style is set, dont do anything! - } - else - { - this.setStyle(texture, glTexture); - } - - glTexture.dirtyStyleId = texture.dirtyStyleId; - } - - setStyle(texture, glTexture) - { - const gl = this.gl; - - if (glTexture.mipmap) - { - gl.generateMipmap(texture.target); - } - - gl.texParameteri(texture.target, gl.TEXTURE_WRAP_S, texture.wrapMode); - gl.texParameteri(texture.target, gl.TEXTURE_WRAP_T, texture.wrapMode); - - if (glTexture.mipmap) - { - /* eslint-disable max-len */ - gl.texParameteri(texture.target, gl.TEXTURE_MIN_FILTER, texture.scaleMode ? gl.LINEAR_MIPMAP_LINEAR : gl.NEAREST_MIPMAP_NEAREST); - /* eslint-disable max-len */ - } - else - { - gl.texParameteri(texture.target, gl.TEXTURE_MIN_FILTER, texture.scaleMode ? gl.LINEAR : gl.NEAREST); - } - - gl.texParameteri(texture.target, gl.TEXTURE_MAG_FILTER, texture.scaleMode ? gl.LINEAR : gl.NEAREST); - } -} diff --git a/packages/core/src/renderers/utils/ObjectRenderer.js b/packages/core/src/renderers/utils/ObjectRenderer.js deleted file mode 100644 index f67a273..0000000 --- a/packages/core/src/renderers/utils/ObjectRenderer.js +++ /dev/null @@ -1,48 +0,0 @@ -import WebGLSystem from '../systems/WebGLSystem'; - -/** - * Base for a common object renderer that can be used as a system renderer plugin. - * - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class ObjectRenderer extends WebGLSystem -{ - /** - * Starts the renderer and sets the shader - * - */ - start() - { - // set the shader.. - } - - /** - * Stops the renderer - * - */ - stop() - { - this.flush(); - } - - /** - * Stub method for rendering content and emptying the current batch. - * - */ - flush() - { - // flush! - } - - /** - * Renders an object - * - * @param {PIXI.DisplayObject} object - The object to render. - */ - render(object) // eslint-disable-line no-unused-vars - { - // render the object - } -} diff --git a/packages/core/src/renderers/utils/Quad.js b/packages/core/src/renderers/utils/Quad.js deleted file mode 100644 index 5c1517b..0000000 --- a/packages/core/src/renderers/utils/Quad.js +++ /dev/null @@ -1,26 +0,0 @@ -import Geometry from '../../geometry/Geometry'; - -/** - * Helper class to create a quad - * - * @class - * @memberof PIXI - */ -export default class Quad extends Geometry -{ - /** - * just a quad - */ - constructor() - { - super(); - - this.addAttribute('aVertexPosition', [ - 0, 0, - 1, 0, - 1, 1, - 0, 1, - ]) - .addIndex([0, 1, 3, 2]); - } -} diff --git a/packages/core/src/renderers/utils/QuadUv.js b/packages/core/src/renderers/utils/QuadUv.js deleted file mode 100644 index 97bda89..0000000 --- a/packages/core/src/renderers/utils/QuadUv.js +++ /dev/null @@ -1,103 +0,0 @@ -import Geometry from '../../geometry/Geometry'; -import Buffer from '../../geometry/Buffer'; - -/** - * Helper class to create a quad with uvs like in v4 - * - * @class - * @memberof PIXI - */ -export default class QuadUv extends Geometry -{ - constructor() - { - super(); - - /** - * An array of vertices - * - * @member {Float32Array} - */ - this.vertices = new Float32Array([ - -1, -1, - 1, -1, - 1, 1, - -1, 1, - ]); - - /** - * The Uvs of the quad - * - * @member {Float32Array} - */ - this.uvs = new Float32Array([ - 0, 0, - 1, 0, - 1, 1, - 0, 1, - ]); - - this.vertexBuffer = new Buffer(this.vertices); - this.uvBuffer = new Buffer(this.uvs); - - this.addAttribute('aVertexPosition', this.vertexBuffer) - .addAttribute('aTextureCoord', this.uvBuffer) - .addIndex([0, 1, 2, 0, 2, 3]); - } - - /** - * Maps two Rectangle to the quad. - * - * @param {PIXI.Rectangle} targetTextureFrame - the first rectangle - * @param {PIXI.Rectangle} destinationFrame - the second rectangle - * @return {PIXI.Quad} Returns itself. - */ - map(targetTextureFrame, destinationFrame) - { - let x = 0; // destinationFrame.x / targetTextureFrame.width; - let y = 0; // destinationFrame.y / targetTextureFrame.height; - - this.uvs[0] = x; - this.uvs[1] = y; - - this.uvs[2] = x + (destinationFrame.width / targetTextureFrame.width); - this.uvs[3] = y; - - this.uvs[4] = x + (destinationFrame.width / targetTextureFrame.width); - this.uvs[5] = y + (destinationFrame.height / targetTextureFrame.height); - - this.uvs[6] = x; - this.uvs[7] = y + (destinationFrame.height / targetTextureFrame.height); - - x = destinationFrame.x; - y = destinationFrame.y; - - this.vertices[0] = x; - this.vertices[1] = y; - - this.vertices[2] = x + destinationFrame.width; - this.vertices[3] = y; - - this.vertices[4] = x + destinationFrame.width; - this.vertices[5] = y + destinationFrame.height; - - this.vertices[6] = x; - this.vertices[7] = y + destinationFrame.height; - - this.invalidate(); - - return this; - } - - /** - * legacy upload method, just marks buffers dirty - * @returns {PIXI.QuadUv} Returns itself. - */ - invalidate() - { - this.vertexBuffer._updateID++; - this.uvBuffer._updateID++; - - return this; - } -} diff --git a/packages/core/src/renderers/utils/checkMaxIfStatmentsInShader.js b/packages/core/src/renderers/utils/checkMaxIfStatmentsInShader.js deleted file mode 100644 index 8047c96..0000000 --- a/packages/core/src/renderers/utils/checkMaxIfStatmentsInShader.js +++ /dev/null @@ -1,58 +0,0 @@ -const fragTemplate = [ - 'precision mediump float;', - 'void main(void){', - 'float test = 0.1;', - '%forloop%', - 'gl_FragColor = vec4(0.0);', - '}', -].join('\n'); - -export default function checkMaxIfStatmentsInShader(maxIfs, gl) -{ - if (maxIfs === 0) - { - throw new Error('Invalid value of `0` passed to `checkMaxIfStatementsInShader`'); - } - - const shader = gl.createShader(gl.FRAGMENT_SHADER); - - while (true) // eslint-disable-line no-constant-condition - { - const fragmentSrc = fragTemplate.replace(/%forloop%/gi, generateIfTestSrc(maxIfs)); - - gl.shaderSource(shader, fragmentSrc); - gl.compileShader(shader); - - if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) - { - maxIfs = (maxIfs / 2) | 0; - } - else - { - // valid! - break; - } - } - - return maxIfs; -} - -function generateIfTestSrc(maxIfs) -{ - let src = ''; - - for (let i = 0; i < maxIfs; ++i) - { - if (i > 0) - { - src += '\nelse '; - } - - if (i < maxIfs - 1) - { - src += `if(test == ${i}.0){}`; - } - } - - return src; -} diff --git a/packages/core/src/renderers/utils/mapWebGLBlendModesToPixi.js b/packages/core/src/renderers/utils/mapWebGLBlendModesToPixi.js deleted file mode 100644 index 10ffe4d..0000000 --- a/packages/core/src/renderers/utils/mapWebGLBlendModesToPixi.js +++ /dev/null @@ -1,47 +0,0 @@ -import { BLEND_MODES } from '@pixi/constants'; - -/** - * Maps gl blend combinations to WebGL. - * - * @memberof PIXI - * @function mapWebGLBlendModesToPixi - * @private - * @param {WebGLRenderingContext} gl - The rendering context. - * @param {string[]} [array=[]] - The array to output into. - * @return {string[]} Mapped modes. - */ -export default function mapWebGLBlendModesToPixi(gl, array = []) -{ - // TODO - premultiply alpha would be different. - // add a boolean for that! - array[BLEND_MODES.NORMAL] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.ADD] = [gl.ONE, gl.DST_ALPHA]; - array[BLEND_MODES.MULTIPLY] = [gl.DST_COLOR, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.SCREEN] = [gl.ONE, gl.ONE_MINUS_SRC_COLOR]; - array[BLEND_MODES.OVERLAY] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.DARKEN] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.LIGHTEN] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.COLOR_DODGE] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.COLOR_BURN] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.HARD_LIGHT] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.SOFT_LIGHT] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.DIFFERENCE] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.EXCLUSION] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.HUE] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.SATURATION] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.COLOR] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.LUMINOSITY] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.NONE] = [0, 0]; - - // not-premultiplied blend modes - array[BLEND_MODES.NORMAL_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.ADD_NPM] = [gl.SRC_ALPHA, gl.DST_ALPHA, gl.ONE, gl.DST_ALPHA]; - array[BLEND_MODES.SCREEN_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_COLOR, gl.ONE, gl.ONE_MINUS_SRC_COLOR]; - - // not-premultiplied blend modes - array[BLEND_MODES.NORMAL_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.ADD_NPM] = [gl.SRC_ALPHA, gl.DST_ALPHA, gl.ONE, gl.DST_ALPHA]; - array[BLEND_MODES.SCREEN_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_COLOR, gl.ONE, gl.ONE_MINUS_SRC_COLOR]; - - return array; -} diff --git a/packages/core/src/shader/GLProgram.js b/packages/core/src/shader/GLProgram.js new file mode 100644 index 0000000..92980ca --- /dev/null +++ b/packages/core/src/shader/GLProgram.js @@ -0,0 +1,43 @@ +/** + * Helper class to create a webGL Program + * + * @class + * @private + */ +export default class GLProgram +{ + constructor(program, uniformData) + { + /** + * The shader program + * + * @member {WebGLProgram} + */ + this.program = program; + + /** + * holds the uniform data which contains uniform locations + * and current uniform values used for caching and preventing undeed GPU commands + * @type {Object} + */ + this.uniformData = uniformData; + + /** + * uniformGroups holds the various upload functions for the shader. Each uniform group + * and program have a unique upload function generated. + * @type {Object} + */ + this.uniformGroups = {}; + } + + /** + * Destroys this program + * TODO + */ + destroy() + { + this.uniformData = null; + this.uniformGroups = null; + this.program = null; + } +} diff --git a/packages/core/src/shader/Program.js b/packages/core/src/shader/Program.js index 4f9971e..4b4007a 100644 --- a/packages/core/src/shader/Program.js +++ b/packages/core/src/shader/Program.js @@ -1,6 +1,11 @@ -import * as shaderUtils from '../renderers/systems/shader/shader'; +// import * as from '../systems/shader/shader'; +import { setPrecision, + defaultValue, + compileProgram, + mapSize, + mapType, + getTestContext } from './utils'; import { ProgramCache } from '@pixi/utils'; -import getTestContext from './getTestContext'; import defaultFragment from './defaultProgram.frag'; import defaultVertex from './defaultProgram.vert'; import { settings } from '@pixi/settings'; @@ -34,8 +39,8 @@ */ this.fragmentSrc = fragmentSrc || Program.defaultFragmentSrc; - this.vertexSrc = shaderUtils.setPrecision(this.vertexSrc, settings.PRECISION_VERTEX); - this.fragmentSrc = shaderUtils.setPrecision(this.fragmentSrc, settings.PRECISION_FRAGMENT); + this.vertexSrc = setPrecision(this.vertexSrc, settings.PRECISION_VERTEX); + this.fragmentSrc = setPrecision(this.fragmentSrc, settings.PRECISION_FRAGMENT); // currently this does not extract structs only default types this.extractData(this.vertexSrc, this.fragmentSrc); @@ -62,7 +67,7 @@ if (gl) { - const program = shaderUtils.compileProgram(gl, vertexSrc, fragmentSrc); + const program = compileProgram(gl, vertexSrc, fragmentSrc); this.attributeData = this.getAttributeData(program, gl); this.uniformData = this.getUniformData(program, gl); @@ -95,13 +100,13 @@ for (let i = 0; i < totalAttributes; i++) { const attribData = gl.getActiveAttrib(program, i); - const type = shaderUtils.mapType(gl, attribData.type); + const type = mapType(gl, attribData.type); /*eslint-disable */ const data = { type: type, name: attribData.name, - size: shaderUtils.mapSize(type), + size: mapSize(type), location: 0, }; /* eslint-enable */ @@ -145,14 +150,14 @@ const name = uniformData.name.replace(/\[.*?\]/, ''); const isArray = uniformData.name.match(/\[.*?\]/, ''); - const type = shaderUtils.mapType(gl, uniformData.type); + const type = mapType(gl, uniformData.type); /*eslint-disable */ uniforms[name] = { type: type, size: uniformData.size, isArray:isArray, - value: shaderUtils.defaultValue(type, uniformData.size), + value: defaultValue(type, uniformData.size), }; /* eslint-enable */ } diff --git a/packages/core/src/shader/ShaderSystem.js b/packages/core/src/shader/ShaderSystem.js new file mode 100644 index 0000000..40b8af8 --- /dev/null +++ b/packages/core/src/shader/ShaderSystem.js @@ -0,0 +1,171 @@ +import System from '../System'; +import GLProgram from './GLProgram'; +import { generateUniformsSync, + defaultValue, + compileProgram } from './utils'; + +let UID = 0; + +/** + * Helper class to create a webGL Texture + * + * @class + * @memberof PIXI.systems + * @extends PIXI.System + */ +export default class ShaderSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - A reference to the current renderer + */ + constructor(renderer) + { + super(renderer); + + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = null; + + this.shader = null; + this.program = null; + + this.id = UID++; + } + + contextChange(gl) + { + this.gl = gl; + } + + /** + * Changes the current shader to the one given in parameter + * + * @param {PIXI.Shader} shader - the new shader + * @param {boolean} dontSync - false if the shader should automatically sync its uniforms. + * @returns {PIXI.glCore.glProgram} the glProgram that belongs to the shader. + */ + bind(shader, dontSync) + { + shader.uniforms.globals = this.renderer.globalUniforms; + + const program = shader.program; + const glProgram = program.glPrograms[this.renderer.CONTEXT_UID] || this.generateShader(shader); + + this.shader = shader; + + // TODO - some current pixi plugins bypass this.. so it not safe to use yet.. + if (this.program !== program) + { + this.program = program; + this.gl.useProgram(glProgram.program); + } + + if (!dontSync) + { + this.syncUniformGroup(shader.uniformGroup); + } + + return glProgram; + } + + /** + * Uploads the uniforms values to the currently bound shader. + * + * @param {object} uniforms - the uniforms valiues that be applied to the current shader + */ + setUniforms(uniforms) + { + const shader = this.shader.program; + const glProgram = shader.glPrograms[this.renderer.CONTEXT_UID]; + + shader.syncUniforms(glProgram.uniformData, uniforms, this.renderer); + } + + syncUniformGroup(group) + { + const glProgram = this.getglProgram(); + + if (!group.static || group.dirtyId !== glProgram.uniformGroups[group.id]) + { + glProgram.uniformGroups[group.id] = group.dirtyId; + const syncFunc = group.syncUniforms[this.shader.program.id] || this.createSyncGroups(group); + + syncFunc(glProgram.uniformData, group.uniforms, this.renderer); + } + } + + createSyncGroups(group) + { + group.syncUniforms[this.shader.program.id] = generateUniformsSync(group, this.shader.program.uniformData); + + return group.syncUniforms[this.shader.program.id]; + } + + /** + * Returns the underlying GLShade rof the currently bound shader. + * This can be handy for when you to have a little more control over the setting of your uniforms. + * + * @return {PIXI.glCore.Shader} the glProgram for the currently bound Shader for this context + */ + getglProgram() + { + if (this.shader) + { + return this.shader.program.glPrograms[this.renderer.CONTEXT_UID]; + } + + return null; + } + + /** + * Generates a glProgram verion of the Shader provided. + * + * @private + * @param {PIXI.Shader} shader the shader that the glProgram will be based on. + * @return {PIXI.glCore.glProgram} A shiney new glProgram + */ + generateShader(shader) + { + const gl = this.gl; + + const program = shader.program; + + const attribMap = {}; + + for (const i in program.attributeData) + { + attribMap[i] = program.attributeData[i].location; + } + + const shaderProgram = compileProgram(gl, program.vertexSrc, program.fragmentSrc, attribMap); + const uniformData = {}; + + for (const i in program.uniformData) + { + const data = program.uniformData[i]; + + uniformData[i] = { + location: gl.getUniformLocation(shaderProgram, i), + value: defaultValue(data.type, data.size), + }; + } + + const glProgram = new GLProgram(shaderProgram, uniformData); + + program.glPrograms[this.renderer.CONTEXT_UID] = glProgram; + + return glProgram; + } + + /** + * Destroys this System and removes all its textures + */ + destroy() + { + // TODO implement destroy method for ShaderSystem + this.destroyed = true; + } +} diff --git a/packages/core/src/shader/defaultValue.js b/packages/core/src/shader/defaultValue.js deleted file mode 100644 index 847a069..0000000 --- a/packages/core/src/shader/defaultValue.js +++ /dev/null @@ -1,78 +0,0 @@ -function booleanArray(size) -{ - const array = new Array(size); - - for (let i = 0; i < array.length; i++) - { - array[i] = false; - } - - return array; -} - -/** - * @class - * @memberof PIXI.glCore.shader - * @param type {String} Type of value - * @param size {Number} - */ -export default function defaultValue(type, size) -{ - switch (type) - { - case 'float': - return 0; - - case 'vec2': - return new Float32Array(2 * size); - - case 'vec3': - return new Float32Array(3 * size); - - case 'vec4': - return new Float32Array(4 * size); - - case 'int': - case 'sampler2D': - return 0; - - case 'ivec2': - return new Int32Array(2 * size); - - case 'ivec3': - return new Int32Array(3 * size); - - case 'ivec4': - return new Int32Array(4 * size); - - case 'bool': - return false; - - case 'bvec2': - - return booleanArray(2 * size); - - case 'bvec3': - return booleanArray(3 * size); - - case 'bvec4': - return booleanArray(4 * size); - - case 'mat2': - return new Float32Array([1, 0, - 0, 1]); - - case 'mat3': - return new Float32Array([1, 0, 0, - 0, 1, 0, - 0, 0, 1]); - - case 'mat4': - return new Float32Array([1, 0, 0, 0, - 0, 1, 0, 0, - 0, 0, 1, 0, - 0, 0, 0, 1]); - } - - return 0; -} diff --git a/packages/core/src/shader/extractUniformsFromSrc.js b/packages/core/src/shader/extractUniformsFromSrc.js deleted file mode 100644 index 3d02c0a..0000000 --- a/packages/core/src/shader/extractUniformsFromSrc.js +++ /dev/null @@ -1,57 +0,0 @@ -import defaultValue from './defaultValue'; - -export default function extractUniformsFromSrc(vertexSrc, fragmentSrc, mask) -{ - const vertUniforms = extractUniformsFromString(vertexSrc, mask); - const fragUniforms = extractUniformsFromString(fragmentSrc, mask); - - return Object.assign(vertUniforms, fragUniforms); -} - -function extractUniformsFromString(string) -{ - const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); - - const uniforms = {}; - let nameSplit; - - // clean the lines a little - remove extra spaces / teabs etc - // then split along ';' - const lines = string.replace(/\s+/g, ' ') - .split(/\s*;\s*/); - - // loop through.. - for (let i = 0; i < lines.length; i++) - { - const line = lines[i].trim(); - - if (line.indexOf('uniform') > -1) - { - const splitLine = line.split(' '); - const type = splitLine[1]; - - let name = splitLine[2]; - let size = 1; - - if (name.indexOf('[') > -1) - { - // array! - nameSplit = name.split(/\[|]/); - name = nameSplit[0]; - size *= Number(nameSplit[1]); - } - - if (!name.match(maskRegex)) - { - uniforms[name] = { - value: defaultValue(type, size), - dirtyId: 0, - name, - type, - }; - } - } - } - - return uniforms; -} diff --git a/packages/core/src/shader/generateUniformsSync.js b/packages/core/src/shader/generateUniformsSync.js deleted file mode 100644 index 98a357f..0000000 --- a/packages/core/src/shader/generateUniformsSync.js +++ /dev/null @@ -1,231 +0,0 @@ -// cv = CachedValue -// v = value -// ud = uniformData -// uv = uniformValue -// l = loaction -const GLSL_TO_SINGLE_SETTERS_CACHED = { - - float: ` - if(cv !== v) - { - cv.v = v; - gl.uniform1f(location, v) - }`, - - vec2: ` - if(cv[0] !== v[0] || cv[1] !== v[1]) - { - cv[0] = v[0]; - cv[1] = v[1]; - gl.uniform2f(location, v[0], v[1]) - }`, - - vec3: ` - if(cv[0] !== v[0] || cv[1] !== v[1] || cv[2] !== v[2]) - { - cv[0] = v[0]; - cv[1] = v[1]; - cv[2] = v[2]; - - gl.uniform3f(location, v[0], v[1], v[2]) - }`, - - vec4: 'gl.uniform4f(location, v[0], v[1], v[2], v[3])', - - int: 'gl.uniform1i(location, v)', - ivec2: 'gl.uniform2i(location, v[0], v[1])', - ivec3: 'gl.uniform3i(location, v[0], v[1], v[2])', - ivec4: 'gl.uniform4i(location, v[0], v[1], v[2], v[3])', - - bool: 'gl.uniform1i(location, v)', - bvec2: 'gl.uniform2i(location, v[0], v[1])', - bvec3: 'gl.uniform3i(location, v[0], v[1], v[2])', - bvec4: 'gl.uniform4i(location, v[0], v[1], v[2], v[3])', - - mat2: 'gl.uniformMatrix2fv(location, false, v)', - mat3: 'gl.uniformMatrix3fv(location, false, v)', - mat4: 'gl.uniformMatrix4fv(location, false, v)', - - sampler2D: 'gl.uniform1i(location, v)', - samplerCube: 'gl.uniform1i(location, v)', - sampler2DArray: 'gl.uniform1i(location, v)', -}; - -const GLSL_TO_ARRAY_SETTERS = { - - float: `gl.uniform1fv(location, v)`, - - vec2: `gl.uniform2fv(location, v)`, - vec3: `gl.uniform3fv(location, v)`, - vec4: 'gl.uniform4fv(location, v)', - - mat4: 'gl.uniformMatrix4fv(location, false, v)', - mat3: 'gl.uniformMatrix3fv(location, false, v)', - mat2: 'gl.uniformMatrix2fv(location, false, v)', - - int: 'gl.uniform1iv(location, v)', - ivec2: 'gl.uniform2iv(location, v)', - ivec3: 'gl.uniform3iv(location, v)', - ivec4: 'gl.uniform4iv(location, v)', - - bool: 'gl.uniform1iv(location, v)', - bvec2: 'gl.uniform2iv(location, v)', - bvec3: 'gl.uniform3iv(location, v)', - bvec4: 'gl.uniform4iv(location, v)', - - sampler2D: 'gl.uniform1iv(location, v)', - samplerCube: 'gl.uniform1iv(location, v)', - sampler2DArray: 'gl.uniform1iv(location, v)', -}; - -export default function generateUniformsSync(group, uniformData) -{ - let textureCount = 0; - let func = `var v = null; - var cv = null - var gl = renderer.gl`; - - for (const i in group.uniforms) - { - const data = uniformData[i]; - - if (!data) - { - if (group.uniforms[i].group) - { - func += ` - renderer.shader.syncUniformGroup(uv.${i}); - `; - } - - continue; - } - - // TODO && uniformData[i].value !== 0 <-- do we still need this? - if (data.type === 'float' && data.size === 1) - { - func += ` - if(uv.${i} !== ud.${i}.value) - { - ud.${i}.value = uv.${i} - gl.uniform1f(ud.${i}.location, uv.${i}) - }\n`; - } - /* eslint-disable max-len */ - else if ((data.type === 'sampler2D' || data.type === 'samplerCube' || data.type === 'sampler2DArray') && data.size === 1 && !data.isArray) - /* eslint-disable max-len */ - { - func += ` - renderer.texture.bind(uv.${i}, ${textureCount}); - - if(ud.${i}.value !== ${textureCount}) - { - ud.${i}.value = ${textureCount}; - gl.uniform1i(ud.${i}.location, ${textureCount});\n; // eslint-disable-line max-len - }\n`; - - textureCount++; - } - else if (data.type === 'mat3' && data.size === 1) - { - if (group.uniforms[i].a !== undefined) - { - // TODO and some smart caching dirty ids here! - func += ` - gl.uniformMatrix3fv(ud.${i}.location, false, uv.${i}.toArray(true)); - \n`; - } - else - { - func += ` - gl.uniformMatrix3fv(ud.${i}.location, false, uv.${i}); - \n`; - } - } - else if (data.type === 'vec2' && data.size === 1) - { - // TODO - do we need both here? - // maybe we can get away with only using points? - if (group.uniforms[i].x !== undefined) - { - func += ` - cv = ud.${i}.value; - v = uv.${i}; - - if(cv[0] !== v.x || cv[1] !== v.y) - { - cv[0] = v.x; - cv[1] = v.y; - gl.uniform2f(ud.${i}.location, v.x, v.y); - }\n`; - } - else - { - func += ` - cv = ud.${i}.value; - v = uv.${i}; - - if(cv[0] !== v[0] || cv[1] !== v[1]) - { - cv[0] = v[0]; - cv[1] = v[1]; - gl.uniform2f(ud.${i}.location, v[0], v[1]); - } - \n`; - } - } - else if (data.type === 'vec4' && data.size === 1) - { - // TODO - do we need both here? - // maybe we can get away with only using points? - if (group.uniforms[i].width !== undefined) - { - func += ` - cv = ud.${i}.value; - v = uv.${i}; - - if(cv[0] !== v.x || cv[1] !== v.y || cv[2] !== v.width || cv[3] !== v.height) - { - cv[0] = v.x; - cv[1] = v.y; - cv[2] = v.width; - cv[3] = v.height; - gl.uniform4f(ud.${i}.location, v.x, v.y, v.width, v.height) - }\n`; - } - else - { - func += ` - cv = ud.${i}.value; - v = uv.${i}; - - if(cv[0] !== v[0] || cv[1] !== v[1] || cv[2] !== v[2] || cv[3] !== v[3]) - { - cv[0] = v[0]; - cv[1] = v[1]; - cv[2] = v[2]; - cv[3] = v[3]; - - gl.uniform4f(ud.${i}.location, v[0], v[1], v[2], v[3]) - } - \n`; - } - } - else - { - const templateType = (data.size === 1) ? GLSL_TO_SINGLE_SETTERS_CACHED : GLSL_TO_ARRAY_SETTERS; - - const template = templateType[data.type].replace('location', `ud.${i}.location`); - - func += ` - cv = ud.${i}.value; - v = uv.${i}; - ${template};\n`; - } - } - - // console.log(' --------------- ') - // console.log(func); - - return new Function('ud', 'uv', 'renderer', func); // eslint-disable-line no-new-func -} diff --git a/packages/core/src/shader/getTestContext.js b/packages/core/src/shader/getTestContext.js deleted file mode 100644 index ea174cc..0000000 --- a/packages/core/src/shader/getTestContext.js +++ /dev/null @@ -1,48 +0,0 @@ -import { settings } from '@pixi/settings'; - -let context = null; - -/** - * returns a little webGL context to use for program inspection. - * - * @static - * @private - * @returns {webGL-context} a gl context to test with - */ -export default function getTestContext() -{ - if (!context) - { - const canvas = document.createElement('canvas'); - - let gl; - - if (settings.PREFER_WEBGL_2) - { - gl = canvas.getContext('webgl2', {}); - } - - if (!gl) - { - gl = canvas.getContext('webgl', {}) - || canvas.getContext('experimental-webgl', {}); - - if (!gl) - { - // fail, not able to get a context - throw new Error('This browser does not support webGL. Try using the canvas renderer'); - } - else - { - // for shader testing.. - gl.getExtension('WEBGL_draw_buffers'); - } - } - - context = gl; - - return gl; - } - - return context; -} diff --git a/packages/core/src/shader/utils/checkMaxIfStatmentsInShader.js b/packages/core/src/shader/utils/checkMaxIfStatmentsInShader.js new file mode 100644 index 0000000..8047c96 --- /dev/null +++ b/packages/core/src/shader/utils/checkMaxIfStatmentsInShader.js @@ -0,0 +1,58 @@ +const fragTemplate = [ + 'precision mediump float;', + 'void main(void){', + 'float test = 0.1;', + '%forloop%', + 'gl_FragColor = vec4(0.0);', + '}', +].join('\n'); + +export default function checkMaxIfStatmentsInShader(maxIfs, gl) +{ + if (maxIfs === 0) + { + throw new Error('Invalid value of `0` passed to `checkMaxIfStatementsInShader`'); + } + + const shader = gl.createShader(gl.FRAGMENT_SHADER); + + while (true) // eslint-disable-line no-constant-condition + { + const fragmentSrc = fragTemplate.replace(/%forloop%/gi, generateIfTestSrc(maxIfs)); + + gl.shaderSource(shader, fragmentSrc); + gl.compileShader(shader); + + if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) + { + maxIfs = (maxIfs / 2) | 0; + } + else + { + // valid! + break; + } + } + + return maxIfs; +} + +function generateIfTestSrc(maxIfs) +{ + let src = ''; + + for (let i = 0; i < maxIfs; ++i) + { + if (i > 0) + { + src += '\nelse '; + } + + if (i < maxIfs - 1) + { + src += `if(test == ${i}.0){}`; + } + } + + return src; +} diff --git a/packages/core/src/shader/utils/compileProgram.js b/packages/core/src/shader/utils/compileProgram.js new file mode 100644 index 0000000..8c6ccb9 --- /dev/null +++ b/packages/core/src/shader/utils/compileProgram.js @@ -0,0 +1,78 @@ +/** + * @method compileProgram + * @memberof PIXI.glCore.shader + * @param gl {WebGLRenderingContext} The current WebGL context {WebGLProgram} + * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. + * @param fragmentSrc {string|string[]} The fragment shader source as an array of strings. + * @param attributeLocations {Object} An attribute location map that lets you manually set the attribute locations + * @return {WebGLProgram} the shader program + */ +export default function compileProgram(gl, vertexSrc, fragmentSrc, attributeLocations) +{ + const glVertShader = compileShader(gl, gl.VERTEX_SHADER, vertexSrc); + const glFragShader = compileShader(gl, gl.FRAGMENT_SHADER, fragmentSrc); + + let program = gl.createProgram(); + + gl.attachShader(program, glVertShader); + gl.attachShader(program, glFragShader); + + // optionally, set the attributes manually for the program rather than letting WebGL decide.. + if (attributeLocations) + { + for (const i in attributeLocations) + { + gl.bindAttribLocation(program, attributeLocations[i], i); + } + } + + gl.linkProgram(program); + + // if linking fails, then log and cleanup + if (!gl.getProgramParameter(program, gl.LINK_STATUS)) + { + console.error('Pixi.js Error: Could not initialize shader.'); + console.error('gl.VALIDATE_STATUS', gl.getProgramParameter(program, gl.VALIDATE_STATUS)); + console.error('gl.getError()', gl.getError()); + + // if there is a program info log, log it + if (gl.getProgramInfoLog(program) !== '') + { + console.warn('Pixi.js Warning: gl.getProgramInfoLog()', gl.getProgramInfoLog(program)); + } + + gl.deleteProgram(program); + program = null; + } + + // clean up some shaders + gl.deleteShader(glVertShader); + gl.deleteShader(glFragShader); + + return program; +} + +/** + * @private + * @param gl {WebGLRenderingContext} The current WebGL context {WebGLProgram} + * @param type {Number} the type, can be either VERTEX_SHADER or FRAGMENT_SHADER + * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. + * @return {WebGLShader} the shader + */ +function compileShader(gl, type, src) +{ + const shader = gl.createShader(type); + + gl.shaderSource(shader, src); + gl.compileShader(shader); + + if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) + { + console.warn(src); + console.error(gl.getShaderInfoLog(shader)); + + return null; + } + + return shader; +} diff --git a/packages/core/src/shader/utils/defaultValue.js b/packages/core/src/shader/utils/defaultValue.js new file mode 100644 index 0000000..97290bd --- /dev/null +++ b/packages/core/src/shader/utils/defaultValue.js @@ -0,0 +1,79 @@ +/** + * @method defaultValue + * @memberof PIXI.glCore.shader + * @param type {String} Type of value + * @param size {Number} + */ +export default function defaultValue(type, size) +{ + switch (type) + { + case 'float': + return 0; + + case 'vec2': + return new Float32Array(2 * size); + + case 'vec3': + return new Float32Array(3 * size); + + case 'vec4': + return new Float32Array(4 * size); + + case 'int': + case 'sampler2D': + case 'sampler2DArray': + return 0; + + case 'ivec2': + return new Int32Array(2 * size); + + case 'ivec3': + return new Int32Array(3 * size); + + case 'ivec4': + return new Int32Array(4 * size); + + case 'bool': + return false; + + case 'bvec2': + + return booleanArray(2 * size); + + case 'bvec3': + return booleanArray(3 * size); + + case 'bvec4': + return booleanArray(4 * size); + + case 'mat2': + return new Float32Array([1, 0, + 0, 1]); + + case 'mat3': + return new Float32Array([1, 0, 0, + 0, 1, 0, + 0, 0, 1]); + + case 'mat4': + return new Float32Array([1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1]); + } + + return null; +} + +function booleanArray(size) +{ + const array = new Array(size); + + for (let i = 0; i < array.length; i++) + { + array[i] = false; + } + + return array; +} diff --git a/packages/core/src/shader/utils/extractUniformsFromSrc.js b/packages/core/src/shader/utils/extractUniformsFromSrc.js new file mode 100644 index 0000000..3d02c0a --- /dev/null +++ b/packages/core/src/shader/utils/extractUniformsFromSrc.js @@ -0,0 +1,57 @@ +import defaultValue from './defaultValue'; + +export default function extractUniformsFromSrc(vertexSrc, fragmentSrc, mask) +{ + const vertUniforms = extractUniformsFromString(vertexSrc, mask); + const fragUniforms = extractUniformsFromString(fragmentSrc, mask); + + return Object.assign(vertUniforms, fragUniforms); +} + +function extractUniformsFromString(string) +{ + const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); + + const uniforms = {}; + let nameSplit; + + // clean the lines a little - remove extra spaces / teabs etc + // then split along ';' + const lines = string.replace(/\s+/g, ' ') + .split(/\s*;\s*/); + + // loop through.. + for (let i = 0; i < lines.length; i++) + { + const line = lines[i].trim(); + + if (line.indexOf('uniform') > -1) + { + const splitLine = line.split(' '); + const type = splitLine[1]; + + let name = splitLine[2]; + let size = 1; + + if (name.indexOf('[') > -1) + { + // array! + nameSplit = name.split(/\[|]/); + name = nameSplit[0]; + size *= Number(nameSplit[1]); + } + + if (!name.match(maskRegex)) + { + uniforms[name] = { + value: defaultValue(type, size), + dirtyId: 0, + name, + type, + }; + } + } + } + + return uniforms; +} diff --git a/packages/core/src/shader/utils/generateUniformsSync.js b/packages/core/src/shader/utils/generateUniformsSync.js new file mode 100644 index 0000000..98a357f --- /dev/null +++ b/packages/core/src/shader/utils/generateUniformsSync.js @@ -0,0 +1,231 @@ +// cv = CachedValue +// v = value +// ud = uniformData +// uv = uniformValue +// l = loaction +const GLSL_TO_SINGLE_SETTERS_CACHED = { + + float: ` + if(cv !== v) + { + cv.v = v; + gl.uniform1f(location, v) + }`, + + vec2: ` + if(cv[0] !== v[0] || cv[1] !== v[1]) + { + cv[0] = v[0]; + cv[1] = v[1]; + gl.uniform2f(location, v[0], v[1]) + }`, + + vec3: ` + if(cv[0] !== v[0] || cv[1] !== v[1] || cv[2] !== v[2]) + { + cv[0] = v[0]; + cv[1] = v[1]; + cv[2] = v[2]; + + gl.uniform3f(location, v[0], v[1], v[2]) + }`, + + vec4: 'gl.uniform4f(location, v[0], v[1], v[2], v[3])', + + int: 'gl.uniform1i(location, v)', + ivec2: 'gl.uniform2i(location, v[0], v[1])', + ivec3: 'gl.uniform3i(location, v[0], v[1], v[2])', + ivec4: 'gl.uniform4i(location, v[0], v[1], v[2], v[3])', + + bool: 'gl.uniform1i(location, v)', + bvec2: 'gl.uniform2i(location, v[0], v[1])', + bvec3: 'gl.uniform3i(location, v[0], v[1], v[2])', + bvec4: 'gl.uniform4i(location, v[0], v[1], v[2], v[3])', + + mat2: 'gl.uniformMatrix2fv(location, false, v)', + mat3: 'gl.uniformMatrix3fv(location, false, v)', + mat4: 'gl.uniformMatrix4fv(location, false, v)', + + sampler2D: 'gl.uniform1i(location, v)', + samplerCube: 'gl.uniform1i(location, v)', + sampler2DArray: 'gl.uniform1i(location, v)', +}; + +const GLSL_TO_ARRAY_SETTERS = { + + float: `gl.uniform1fv(location, v)`, + + vec2: `gl.uniform2fv(location, v)`, + vec3: `gl.uniform3fv(location, v)`, + vec4: 'gl.uniform4fv(location, v)', + + mat4: 'gl.uniformMatrix4fv(location, false, v)', + mat3: 'gl.uniformMatrix3fv(location, false, v)', + mat2: 'gl.uniformMatrix2fv(location, false, v)', + + int: 'gl.uniform1iv(location, v)', + ivec2: 'gl.uniform2iv(location, v)', + ivec3: 'gl.uniform3iv(location, v)', + ivec4: 'gl.uniform4iv(location, v)', + + bool: 'gl.uniform1iv(location, v)', + bvec2: 'gl.uniform2iv(location, v)', + bvec3: 'gl.uniform3iv(location, v)', + bvec4: 'gl.uniform4iv(location, v)', + + sampler2D: 'gl.uniform1iv(location, v)', + samplerCube: 'gl.uniform1iv(location, v)', + sampler2DArray: 'gl.uniform1iv(location, v)', +}; + +export default function generateUniformsSync(group, uniformData) +{ + let textureCount = 0; + let func = `var v = null; + var cv = null + var gl = renderer.gl`; + + for (const i in group.uniforms) + { + const data = uniformData[i]; + + if (!data) + { + if (group.uniforms[i].group) + { + func += ` + renderer.shader.syncUniformGroup(uv.${i}); + `; + } + + continue; + } + + // TODO && uniformData[i].value !== 0 <-- do we still need this? + if (data.type === 'float' && data.size === 1) + { + func += ` + if(uv.${i} !== ud.${i}.value) + { + ud.${i}.value = uv.${i} + gl.uniform1f(ud.${i}.location, uv.${i}) + }\n`; + } + /* eslint-disable max-len */ + else if ((data.type === 'sampler2D' || data.type === 'samplerCube' || data.type === 'sampler2DArray') && data.size === 1 && !data.isArray) + /* eslint-disable max-len */ + { + func += ` + renderer.texture.bind(uv.${i}, ${textureCount}); + + if(ud.${i}.value !== ${textureCount}) + { + ud.${i}.value = ${textureCount}; + gl.uniform1i(ud.${i}.location, ${textureCount});\n; // eslint-disable-line max-len + }\n`; + + textureCount++; + } + else if (data.type === 'mat3' && data.size === 1) + { + if (group.uniforms[i].a !== undefined) + { + // TODO and some smart caching dirty ids here! + func += ` + gl.uniformMatrix3fv(ud.${i}.location, false, uv.${i}.toArray(true)); + \n`; + } + else + { + func += ` + gl.uniformMatrix3fv(ud.${i}.location, false, uv.${i}); + \n`; + } + } + else if (data.type === 'vec2' && data.size === 1) + { + // TODO - do we need both here? + // maybe we can get away with only using points? + if (group.uniforms[i].x !== undefined) + { + func += ` + cv = ud.${i}.value; + v = uv.${i}; + + if(cv[0] !== v.x || cv[1] !== v.y) + { + cv[0] = v.x; + cv[1] = v.y; + gl.uniform2f(ud.${i}.location, v.x, v.y); + }\n`; + } + else + { + func += ` + cv = ud.${i}.value; + v = uv.${i}; + + if(cv[0] !== v[0] || cv[1] !== v[1]) + { + cv[0] = v[0]; + cv[1] = v[1]; + gl.uniform2f(ud.${i}.location, v[0], v[1]); + } + \n`; + } + } + else if (data.type === 'vec4' && data.size === 1) + { + // TODO - do we need both here? + // maybe we can get away with only using points? + if (group.uniforms[i].width !== undefined) + { + func += ` + cv = ud.${i}.value; + v = uv.${i}; + + if(cv[0] !== v.x || cv[1] !== v.y || cv[2] !== v.width || cv[3] !== v.height) + { + cv[0] = v.x; + cv[1] = v.y; + cv[2] = v.width; + cv[3] = v.height; + gl.uniform4f(ud.${i}.location, v.x, v.y, v.width, v.height) + }\n`; + } + else + { + func += ` + cv = ud.${i}.value; + v = uv.${i}; + + if(cv[0] !== v[0] || cv[1] !== v[1] || cv[2] !== v[2] || cv[3] !== v[3]) + { + cv[0] = v[0]; + cv[1] = v[1]; + cv[2] = v[2]; + cv[3] = v[3]; + + gl.uniform4f(ud.${i}.location, v[0], v[1], v[2], v[3]) + } + \n`; + } + } + else + { + const templateType = (data.size === 1) ? GLSL_TO_SINGLE_SETTERS_CACHED : GLSL_TO_ARRAY_SETTERS; + + const template = templateType[data.type].replace('location', `ud.${i}.location`); + + func += ` + cv = ud.${i}.value; + v = uv.${i}; + ${template};\n`; + } + } + + // console.log(' --------------- ') + // console.log(func); + + return new Function('ud', 'uv', 'renderer', func); // eslint-disable-line no-new-func +} diff --git a/packages/core/src/shader/utils/getTestContext.js b/packages/core/src/shader/utils/getTestContext.js new file mode 100644 index 0000000..ea174cc --- /dev/null +++ b/packages/core/src/shader/utils/getTestContext.js @@ -0,0 +1,48 @@ +import { settings } from '@pixi/settings'; + +let context = null; + +/** + * returns a little webGL context to use for program inspection. + * + * @static + * @private + * @returns {webGL-context} a gl context to test with + */ +export default function getTestContext() +{ + if (!context) + { + const canvas = document.createElement('canvas'); + + let gl; + + if (settings.PREFER_WEBGL_2) + { + gl = canvas.getContext('webgl2', {}); + } + + if (!gl) + { + gl = canvas.getContext('webgl', {}) + || canvas.getContext('experimental-webgl', {}); + + if (!gl) + { + // fail, not able to get a context + throw new Error('This browser does not support webGL. Try using the canvas renderer'); + } + else + { + // for shader testing.. + gl.getExtension('WEBGL_draw_buffers'); + } + } + + context = gl; + + return gl; + } + + return context; +} diff --git a/packages/core/src/shader/utils/index.js b/packages/core/src/shader/utils/index.js new file mode 100644 index 0000000..4bf8dd4 --- /dev/null +++ b/packages/core/src/shader/utils/index.js @@ -0,0 +1,8 @@ +export { default as compileProgram } from './compileProgram'; +export { default as defaultValue } from './defaultValue'; +export { default as setPrecision } from './setPrecision'; +export { default as mapSize } from './mapSize'; +export { default as mapType } from './mapType'; +export { default as generateUniformsSync } from './generateUniformsSync'; +export { default as getTestContext } from './getTestContext'; +export { default as checkMaxIfStatmentsInShader } from './checkMaxIfStatmentsInShader'; diff --git a/packages/core/src/shader/utils/mapSize.js b/packages/core/src/shader/utils/mapSize.js new file mode 100644 index 0000000..b681554 --- /dev/null +++ b/packages/core/src/shader/utils/mapSize.js @@ -0,0 +1,33 @@ +const GLSL_TO_SIZE = { + float: 1, + vec2: 2, + vec3: 3, + vec4: 4, + + int: 1, + ivec2: 2, + ivec3: 3, + ivec4: 4, + + bool: 1, + bvec2: 2, + bvec3: 3, + bvec4: 4, + + mat2: 4, + mat3: 9, + mat4: 16, + + sampler2D: 1, +}; + +/** + * @method mapSize + * @memberof PIXI.glCore.shader + * @param type {String} + * @return {Number} + */ +export default function mapSize(type) +{ + return GLSL_TO_SIZE[type]; +} diff --git a/packages/core/src/shader/utils/mapType.js b/packages/core/src/shader/utils/mapType.js new file mode 100644 index 0000000..d288b7c --- /dev/null +++ b/packages/core/src/shader/utils/mapType.js @@ -0,0 +1,45 @@ +let GL_TABLE = null; + +const GL_TO_GLSL_TYPES = { + FLOAT: 'float', + FLOAT_VEC2: 'vec2', + FLOAT_VEC3: 'vec3', + FLOAT_VEC4: 'vec4', + + INT: 'int', + INT_VEC2: 'ivec2', + INT_VEC3: 'ivec3', + INT_VEC4: 'ivec4', + + BOOL: 'bool', + BOOL_VEC2: 'bvec2', + BOOL_VEC3: 'bvec3', + BOOL_VEC4: 'bvec4', + + FLOAT_MAT2: 'mat2', + FLOAT_MAT3: 'mat3', + FLOAT_MAT4: 'mat4', + + SAMPLER_2D: 'sampler2D', + SAMPLER_CUBE: 'samplerCube', + SAMPLER_2D_ARRAY: 'sampler2DArray', +}; + +export default function mapType(gl, type) +{ + if (!GL_TABLE) + { + const typeNames = Object.keys(GL_TO_GLSL_TYPES); + + GL_TABLE = {}; + + for (let i = 0; i < typeNames.length; ++i) + { + const tn = typeNames[i]; + + GL_TABLE[gl[tn]] = GL_TO_GLSL_TYPES[tn]; + } + } + + return GL_TABLE[type]; +} diff --git a/packages/core/src/AbstractRenderer.js b/packages/core/src/AbstractRenderer.js new file mode 100644 index 0000000..64b2eb1 --- /dev/null +++ b/packages/core/src/AbstractRenderer.js @@ -0,0 +1,353 @@ +import { hex2string, hex2rgb } from '@pixi/utils'; +import { Matrix, Rectangle } from '@pixi/math'; +import { RENDERER_TYPE } from '@pixi/constants'; +import { settings } from '@pixi/settings'; +import { Container } from '@pixi/display'; +import RenderTexture from './renderTexture/RenderTexture'; +import EventEmitter from 'eventemitter3'; + +const tempMatrix = new Matrix(); + +/** + * 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 EventEmitter + * @memberof PIXI + */ +export default class AbstractRenderer extends EventEmitter +{ + // eslint-disable-next-line valid-jsdoc + /** + * @param {string} system - The name of the system this renderer is for. + * @param {object} [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.transparent=false] - If the render view is transparent, default false + * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false + * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. The + * resolution of the renderer retina would be 2. + * @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 {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when rendering, + * stopping pixel interpolation. + */ + constructor(system, options, arg2, arg3) + { + super(); + + // Support for constructor(system, screenWidth, screenHeight, options) + if (typeof options === 'number') + { + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + }, arg3); + } + + // 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 = RENDERER_TYPE.UNKNOWN; + + /** + * Measurements of the screen. (0, 0, screenWidth, screenHeight) + * + * Its safe to use as filterArea or hitArea for 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 1 + */ + this.resolution = options.resolution || settings.RESOLUTION; + + /** + * Whether the render view is transparent + * + * @member {boolean} + */ + this.transparent = options.transparent; + + /** + * Whether css dimensions of canvas view should be resized to screen dimensions automatically + * + * @member {boolean} + */ + this.autoResize = options.autoResize || false; + + /** + * Tracks the blend modes useful for this renderer. + * + * @member {object} + */ + this.blendModes = null; + + /** + * 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; + + /** + * If true PixiJS will Math.floor() x/y values when rendering, stopping pixel interpolation. + * Handy for crisp pixel art and speed on legacy devices. + * + * @member {boolean} + */ + this.roundPixels = options.roundPixels; + + /** + * The background color as a number. + * + * @member {number} + * @private + */ + this._backgroundColor = 0x000000; + + /** + * The background color as an [R, G, B] array. + * + * @member {number[]} + * @private + */ + this._backgroundColorRgba = [0, 0, 0, 0]; + + /** + * The background color as a string. + * + * @member {string} + * @private + */ + this._backgroundColorString = '#000000'; + + this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter + + /** + * This temporary display object used as the parent of the currently being rendered item + * + * @member {PIXI.DisplayObject} + * @private + */ + this._tempDisplayObjectParent = new Container(); + + /** + * The last root object that the renderer tried to render. + * + * @member {PIXI.DisplayObject} + * @private + */ + this._lastObjectRendered = this._tempDisplayObjectParent; + + /** + * Collection of plugins. + * @readonly + * @member {object} + */ + this.plugins = {}; + } + + /** + * Initialize the plugins. + * + * @protected + * @param {object} staticMap - The dictionary of staticly saved plugins. + */ + initPlugins(staticMap) + { + 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() + { + return this.view.width; + } + + /** + * Same as view.height, actual number of pixels in the canvas by vertical + * + * @member {number} + * @readonly + * @default 600 + */ + get height() + { + return this.view.height; + } + + /** + * Resizes the screen and canvas to the specified width and height + * Canvas dimensions are multiplied by resolution + * + * @param {number} screenWidth - the new width of the screen + * @param {number} screenHeight - the new height of the screen + */ + resize(screenWidth, screenHeight) + { + this.screen.width = screenWidth; + this.screen.height = screenHeight; + + this.view.width = screenWidth * this.resolution; + this.view.height = screenHeight * this.resolution; + + if (this.autoResize) + { + this.view.style.width = `${screenWidth}px`; + this.view.style.height = `${screenHeight}px`; + } + } + + /** + * 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 {PIXI.DisplayObject} displayObject - The displayObject the object will be generated from + * @param {number} scaleMode - Should be one of the scaleMode consts + * @param {number} resolution - The resolution / device pixel ratio of the texture being generated + * @param {PIXI.Rectangle} [region] - The region of the displayObject, that shall be rendered, + * if no region is specified, defaults to the local bounds of the displayObject. + * @return {PIXI.Texture} a texture of the graphics object + */ + generateTexture(displayObject, scaleMode, resolution, region) + { + region = region || displayObject.getLocalBounds(); + + // 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(region.width | 0, region.height | 0, scaleMode, resolution); + + tempMatrix.tx = -region.x; + tempMatrix.ty = -region.y; + + this.render(displayObject, renderTexture, false, tempMatrix, true); + + return renderTexture; + } + + /** + * Removes everything from the renderer and optionally removes the Canvas DOM element. + * + * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. + */ + destroy(removeView) + { + 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); + } + + this.plugins = null; + + this.type = RENDERER_TYPE.UNKNOWN; + + this.view = null; + + this.screen = null; + + this.resolution = 0; + + this.transparent = false; + + this.autoResize = false; + + this.blendModes = null; + + this.options = null; + + this.preserveDrawingBuffer = false; + this.clearBeforeRender = false; + + this.roundPixels = false; + + this._backgroundColor = 0; + this._backgroundColorRgba = null; + this._backgroundColorString = null; + + this._tempDisplayObjectParent = null; + this._lastObjectRendered = null; + } + + /** + * The background color to fill if not transparent + * + * @member {number} + */ + get backgroundColor() + { + return this._backgroundColor; + } + + set backgroundColor(value) // eslint-disable-line require-jsdoc + { + this._backgroundColor = value; + this._backgroundColorString = hex2string(value); + hex2rgb(value, this._backgroundColorRgba); + } +} diff --git a/packages/core/src/Renderer.js b/packages/core/src/Renderer.js new file mode 100644 index 0000000..2227e78 --- /dev/null +++ b/packages/core/src/Renderer.js @@ -0,0 +1,338 @@ +import AbstractRenderer from './AbstractRenderer'; +import { sayHello } from '@pixi/utils'; +import MaskSystem from './mask/MaskSystem'; +import StencilSystem from './mask/StencilSystem'; +import FilterSystem from './filters/FilterSystem'; +import FramebufferSystem from './framebuffer/FramebufferSystem'; +import RenderTextureSystem from './renderTexture/RenderTextureSystem'; +import TextureSystem from './textures/TextureSystem'; +import ProjectionSystem from './projection/ProjectionSystem'; +import StateSystem from './state/StateSystem'; +import GeometrySystem from './geometry/GeometrySystem'; +import ShaderSystem from './shader/ShaderSystem'; +import ContextSystem from './context/ContextSystem'; +import BatchSystem from './batch/BatchSystem'; +import TextureGCSystem from './textures/TextureGCSystem'; +import { RENDERER_TYPE } from '@pixi/constants'; +import UniformGroup from './shader/UniformGroup'; +import { Matrix } from '@pixi/math'; +import Runner from 'mini-runner'; + +/** + * 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 Render works by automatically managing webGLBatchs. + * 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 :) + * + * @class + * @memberof PIXI + * @extends PIXI.AbstractRenderer + */ +export default class Renderer extends AbstractRenderer +{ + // eslint-disable-next-line valid-jsdoc + /** + * + * @param {object} [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.transparent=false] - If the render view is transparent, default false + * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false + * @param {boolean} [options.antialias=false] - sets antialias. If not available natively then FXAA + * antialiasing is used + * @param {boolean} [options.forceFXAA=false] - forces FXAA antialiasing to be used over native. + * FXAA is faster, but may not always look as great + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. + * The resolution of the renderer retina would be 2. + * @param {boolean} [options.clearBeforeRender=true] - This sets if the renderer will clear + * the canvas or not before the new render pass. If you wish to set this to false, you *must* set + * preserveDrawingBuffer to `true`. + * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, + * enable this if you need to call toDataUrl on the webgl context. + * @param {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when + * rendering, stopping pixel interpolation. + * @param {number} [options.backgroundColor=0x000000] - The background color of the rendered area + * (shown if not transparent). + * @param {boolean} [options.legacy=false] - If true PixiJS will aim to ensure compatibility + * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. + * @param {string} [options.powerPreference] - Parameter passed to webgl context, set to "high-performance" + * for devices with dual graphics card + */ + constructor(options, arg2, arg3) + { + super('WebGL', options, arg2, arg3); + + /** + * The type of this renderer as a standardised const + * + * @member {number} + * @see PIXI.RENDERER_TYPE + */ + this.type = RENDERER_TYPE.WEBGL; + + // this will be set by the contextSystem (this.context) + this.gl = null; + this.CONTEXT_UID = 0; + this.legacy = !!options.legacy; + + // TODO legacy! + + // runners! + this.runners = { + destroy: new Runner('destroy'), + contextChange: new Runner('contextChange', 1), + reset: new Runner('reset'), + update: new Runner('update'), + postrender: new Runner('postrender'), + prerender: new Runner('prerender'), + resize: new Runner('resize', 2), + }; + + 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(GeometrySystem, 'geometry') + .addSystem(FramebufferSystem, 'framebuffer') + .addSystem(StencilSystem, 'stencil') + .addSystem(ProjectionSystem, 'projection') + .addSystem(TextureGCSystem, 'textureGC') + .addSystem(FilterSystem, 'filter') + .addSystem(RenderTextureSystem, 'renderTexture') + .addSystem(BatchSystem, 'batch'); + + this.initPlugins(Renderer.__plugins); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ + if (options.context) + { + this.context.initFromContext(options.context); + } + else + { + this.context.initFromOptions({ + alpha: this.transparent, + antialias: options.antialias, + premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', + stencil: true, + 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); + } + + /** + * Add a new system to the renderer. + * @param {class} ClassRef - Class reference + * @param {string} [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. + * @return {PIXI.Renderer} Return instance of renderer + */ + addSystem(ClassRef, name) + { + if (!name) + { + name = ClassRef.name; + } + + // TODO - read name from class.name.. + + /* + if(name.includes('System')) + { + name = name.replace('System', ''); + name = name.charAt(0).toLowerCase() + name.slice(1); + } + */ + + const system = new ClassRef(this); + + if (this[name]) + { + throw new Error(`Whoops! ${name} is already a manger`); + } + + this[name] = system; + + for (const i in this.runners) + { + this.runners[i].add(system); + } + + return this; + + /** + * 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. + */ + } + + /** + * Renders the object to its webGL view + * + * @param {PIXI.DisplayObject} displayObject - the object to be rendered + * @param {PIXI.RenderTexture} renderTexture - The render texture to render to. + * @param {boolean} [clear] - Should the canvas be cleared before the new render + * @param {PIXI.Transform} [transform] - A transform to apply to the render texture before rendering. + * @param {boolean} [skipUpdateTransform] - Should we skip the update transform pass? + */ + render(displayObject, renderTexture, clear, transform, skipUpdateTransform) + { + // can be handy to know! + this.renderingToScreen = !renderTexture; + + this.runners.prerender.run(); + this.emit('prerender'); + + // 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.parent; + + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = 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.run(); + + this.emit('postrender'); + } + + /** + * Resizes the webGL view to the specified width and height. + * + * @param {number} screenWidth - the new width of the screen + * @param {number} screenHeight - the new height of the screen + */ + resize(screenWidth, screenHeight) + { + AbstractRenderer.prototype.resize.call(this, screenWidth, screenHeight); + + this.runners.resize.run(screenWidth, screenHeight); + } + + /** + * Resets the WebGL state so you can render things however you fancy! + * + * @return {PIXI.Renderer} Returns itself. + */ + reset() + { + this.runners.reset.run(); + + return this; + } + + clear() + { + this.framebuffer.bind(); + this.framebuffer.clear(); + } + + /** + * Removes everything from the renderer (event listeners, spritebatch, etc...) + * + * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. + * See: https://github.com/pixijs/pixi.js/issues/2233 + */ + destroy(removeView) + { + this.runners.destroy.run(); + + // call base destroy + super.destroy(removeView); + + // TODO nullify all the managers.. + this.gl = null; + } + + /** + * 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 + * builds and excluding plugins. + * @name PIXI.Renderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. + */ + + /** + * Adds a plugin to the renderer. + * + * @method + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + static registerPlugin(pluginName, ctor) + { + Renderer.__plugins = Renderer.__plugins || {}; + Renderer.__plugins[pluginName] = ctor; + } +} diff --git a/packages/core/src/System.js b/packages/core/src/System.js new file mode 100644 index 0000000..05a10c8 --- /dev/null +++ b/packages/core/src/System.js @@ -0,0 +1,42 @@ +/** + * System is a base class used for extending systems used by the {@link PIXI.Renderer} + * @see PIXI.Renderer#addSystem + * @class + * @memberof PIXI + */ +export default class System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + /** + * The renderer this manager works for. + * + * @member {PIXI.Renderer} + */ + this.renderer = renderer; + + this.renderer.runners.contextChange.add(this); + } + + /** + * Generic method called when there is a WebGL context change. + * + */ + contextChange() + { + // do some codes init! + } + + /** + * Generic destroy methods to be overridden by the subclass + * + */ + destroy() + { + this.renderer.runners.contextChange.remove(this); + this.renderer = null; + } +} diff --git a/packages/core/src/batch/BatchSystem.js b/packages/core/src/batch/BatchSystem.js new file mode 100644 index 0000000..b04c249 --- /dev/null +++ b/packages/core/src/batch/BatchSystem.js @@ -0,0 +1,68 @@ +import System from '../System'; +import ObjectRenderer from './ObjectRenderer'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class BatchSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + super(renderer); + + /** + * An empty renderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.emptyRenderer = new ObjectRenderer(renderer); + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.currentRenderer = this.emptyRenderer; + } + + /** + * Changes the current renderer to the one given in parameter + * + * @param {PIXI.ObjectRenderer} objectRenderer - The object renderer to use. + */ + setObjectRenderer(objectRenderer) + { + if (this.currentRenderer === objectRenderer) + { + return; + } + + this.currentRenderer.stop(); + this.currentRenderer = objectRenderer; + + this.renderer.state.setState(objectRenderer.state); + + this.currentRenderer.start(); + } + + /** + * This should be called if you wish to do some custom rendering + * It will basically render anything that may be batched up such as sprites + * + */ + flush() + { + this.setObjectRenderer(this.emptyRenderer); + } + + reset() + { + this.setObjectRenderer(this.emptyRenderer); + } +} diff --git a/packages/core/src/batch/ObjectRenderer.js b/packages/core/src/batch/ObjectRenderer.js new file mode 100644 index 0000000..0c851a9 --- /dev/null +++ b/packages/core/src/batch/ObjectRenderer.js @@ -0,0 +1,48 @@ +import System from '../System'; + +/** + * Base for a common object renderer that can be used as a system renderer plugin. + * + * @class + * @extends PIXI.System + * @memberof PIXI + */ +export default class ObjectRenderer extends System +{ + /** + * Starts the renderer and sets the shader + * + */ + start() + { + // set the shader.. + } + + /** + * Stops the renderer + * + */ + stop() + { + this.flush(); + } + + /** + * Stub method for rendering content and emptying the current batch. + * + */ + flush() + { + // flush! + } + + /** + * Renders an object + * + * @param {PIXI.DisplayObject} object - The object to render. + */ + render(object) // eslint-disable-line no-unused-vars + { + // render the object + } +} diff --git a/packages/core/src/context/ContextSystem.js b/packages/core/src/context/ContextSystem.js new file mode 100644 index 0000000..301aab4 --- /dev/null +++ b/packages/core/src/context/ContextSystem.js @@ -0,0 +1,186 @@ +import System from '../System'; +import { settings } from '@pixi/settings'; + +let CONTEXT_UID = 0; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class ContextSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.webGLVersion = 1; + + this.handleContextLost = this.handleContextLost.bind(this); + this.handleContextRestored = this.handleContextRestored.bind(this); + + this.extensions = {}; + + renderer.view.addEventListener('webglcontextlost', this.handleContextLost, false); + renderer.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + } + + get isLost() + { + return (!this.gl || this.gl.isContextLost()); + } + + contextChange(gl) + { + this.gl = gl; + + // restore a context if it was previously lost + if (gl.isContextLost() && gl.getExtension('WEBGL_lose_context')) + { + gl.getExtension('WEBGL_lose_context').restoreContext(); + } + } + + initFromContext(gl) + { + this.gl = gl; + this.validateContext(gl); + this.renderer.gl = gl; + this.renderer.CONTEXT_UID = CONTEXT_UID++; + this.renderer.runners.contextChange.run(gl); + } + + initFromOptions(options) + { + const gl = this.createContext(this.renderer.view, options); + + this.initFromContext(gl); + } + + /** + * Helper class to create a webGL Context + * + * @class + * @memberof PIXI.glCore + * @param canvas {HTMLCanvasElement} the canvas element that we will get the context from + * @param options {Object} An options object that gets passed in to the canvas element containing the context attributes, + * see https://developer.mozilla.org/en/docs/Web/API/HTMLCanvasElement/getContext for the options available + * @return {WebGLRenderingContext} the WebGL context + */ + createContext(canvas, options) + { + let gl; + + if (settings.PREFER_WEBGL_2) + { + gl = canvas.getContext('webgl2', options); + } + + if (gl) + { + this.webGLVersion = 2; + } + else + { + this.webGLVersion = 1; + + gl = canvas.getContext('webgl', options) + || canvas.getContext('experimental-webgl', options); + + if (!gl) + { + // fail, not able to get a context + throw new Error('This browser does not support webGL. Try using the canvas renderer'); + } + } + + this.gl = gl; + + this.getExtensions(); + + return gl; + } + + getExtensions() + { + // time to set up default etensions that pixi uses.. + const gl = this.gl; + const extensions = this.extensions; + + if (this.webGLVersion === 1) + { + extensions.drawBuffers = gl.getExtension('WEBGL_draw_buffers'); + extensions.depthTexture = gl.getExtension('WEBKIT_WEBGL_depth_texture'); + extensions.floatTexture = gl.getExtension('OES_texture_float'); + extensions.loseContext = gl.getExtension('WEBGL_lose_context'); + + extensions.vertexArrayObject = gl.getExtension('OES_vertex_array_object') + || gl.getExtension('MOZ_OES_vertex_array_object') + || gl.getExtension('WEBKIT_OES_vertex_array_object'); + } + + // we don't use any specific WebGL 2 ones yet! + } + + /** + * Handles a lost webgl context + * + * @private + * @param {WebGLContextEvent} event - The context lost event. + */ + handleContextLost(event) + { + event.preventDefault(); + } + + /** + * Handles a restored webgl context + * + * @private + */ + handleContextRestored() + { + this.renderer.runners.contextChange.run(this.gl); + } + + destroy() + { + const view = this.renderer.view; + + // remove listeners + view.removeEventListener('webglcontextlost', this.handleContextLost); + view.removeEventListener('webglcontextrestored', this.handleContextRestored); + + this.gl.useProgram(null); + + if (this.extensions.loseContext) + { + this.extensions.loseContext.loseContext(); + } + } + + postrender() + { + this.gl.flush(); + } + + validateContext(gl) + { + const attributes = gl.getContextAttributes(); + + // this is going to be fairly simple for now.. but at least we have room to grow! + if (!attributes.stencil) + { + /* eslint-disable max-len */ + + /* eslint-disable no-console */ + console.warn('Provided WebGL context does not have a stencil buffer, masks may not render correctly'); + /* eslint-enable no-console */ + + /* eslint-enable max-len */ + } + } +} diff --git a/packages/core/src/filters/Filter.js b/packages/core/src/filters/Filter.js new file mode 100644 index 0000000..9d8cad8 --- /dev/null +++ b/packages/core/src/filters/Filter.js @@ -0,0 +1,140 @@ +import Shader from '../shader/Shader'; +import Program from '../shader/Program'; +import State from '../state/State'; +import { settings } from '@pixi/settings'; +// import extractUniformsFromSrc from './extractUniformsFromSrc'; +import defaultVertex from './defaultFilter.vert'; +import defaultFragment from './defaultFilter.frag'; + +// let math = require('../../math'); +/** + * @class + * @memberof PIXI + * @extends PIXI.Shader + */ +export default class Filter extends Shader +{ + /** + * @param {string} [vertexSrc] - The source of the vertex shader. + * @param {string} [fragmentSrc] - The source of the fragment shader. + * @param {object} [uniforms] - Custom uniforms to use to augment the built-in ones. + */ + constructor(vertexSrc, fragmentSrc, uniforms) + { + const program = Program.from(vertexSrc, fragmentSrc); + + super(program, uniforms); + + /** + * The padding of the filter. Some filters require extra space to breath such as a blur. + * Increasing this will add extra width and height to the bounds of the object that the + * filter is applied to. + * + * @member {number} + */ + this.padding = 0; + + /** + * The resolution of the filter. Setting this to be lower will lower the quality but + * increase the performance of the filter. + * + * @member {number} + */ + this.resolution = settings.RESOLUTION; + + /** + * If enabled is true the filter is applied, if false it will not. + * + * @member {boolean} + */ + this.enabled = true; + + /** + * If enabled, PixiJS will fit the filter area into boundaries for better performance. + * Switch it off if it does not work for specific shader. + * + * @member {boolean} + */ + this.autoFit = true; + + /** + * Legacy filters use position and uvs from attributes + * @member {boolean} + * @readonly + */ + this.legacy = !!this.program.attributeData.aTextureCoord; + + /** + * the webGL state the filter requires to render + * @member {PIXI.State} + */ + this.state = new State(); + } + + /** + * Applies the filter + * + * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + * @param {boolean} clear - Should the output be cleared before rendering to it + * @param {object} [currentState] - It's current state of filter. + * There are some useful properties in the currentState : + * target, filters, sourceFrame, destinationFrame, renderTarget, resolution + */ + apply(filterManager, input, output, clear, currentState, derp) // eslint-disable-line no-unused-vars + { + // do as you please! + + filterManager.applyFilter(this, input, output, clear, currentState, derp); + + // or just do a regular render.. + } + + /** + * Sets the blendmode of the filter + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL + */ + get blendMode() + { + return this.state.blendMode; + } + + set blendMode(value) // eslint-disable-line require-jsdoc + { + this.state.blendMode = value; + } + + /** + * The default vertex shader source + * + * @static + * @constant + */ + static get defaultVertexSrc() + { + return defaultVertex; + } + + /** + * The default fragment shader source + * + * @static + * @constant + */ + static get defaultFragmentSrc() + { + return defaultFragment; + } +} + +/** + * Used for caching shader IDs + * + * @static + * @private + */ +Filter.SOURCE_KEY_MAP = {}; + diff --git a/packages/core/src/filters/FilterSystem.js b/packages/core/src/filters/FilterSystem.js new file mode 100644 index 0000000..3174318 --- /dev/null +++ b/packages/core/src/filters/FilterSystem.js @@ -0,0 +1,431 @@ +import System from '../System'; + +import RenderTexture from '../renderTexture/RenderTexture'; +import Quad from '../utils/Quad'; +import QuadUv from '../utils/QuadUv'; +import { Rectangle } from '@pixi/math'; +import * as filterTransforms from './filterTransforms'; +import bitTwiddle from 'bit-twiddle'; +import UniformGroup from '../shader/UniformGroup'; +import { DRAW_MODES } from '@pixi/constants'; + +// +/** + * @ignore + * @class + */ +class FilterState +{ + /** + * + */ + constructor() + { + this.renderTexture = null; + this.sourceFrame = new Rectangle(); + this.destinationFrame = new Rectangle(); + this.filters = []; + this.target = null; + this.legacy = false; + this.resolution = 1; + } +} + +/** + * @class + * @memberof PIXI.systems + * @extends PIXI.System + */ +export default class FilterSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + /** + * stores a bunch of PO2 textures used for filtering + * @type {Object} + */ + this.texturePool = {}; + + /** + * a pool for storing filter states, save us creating new ones each tick + * @type {Array} + */ + this.statePool = []; + + /** + * A very simple geometry used when drawing a filter effect to the screen + * @type {Quad} + */ + this.quad = new Quad(); + + this.quadUv = new QuadUv(); + + /** + * Temporary rect for maths + * @type {PIXI.Rectangle} + */ + this.tempRect = new Rectangle(); + + this.activeState = {}; + + /** + * this uniform group is attached to filter uniforms when used + * @type {UniformGroup} + */ + this.globalUniforms = new UniformGroup({ + sourceFrame: this.tempRect, + destinationFrame: this.tempRect, + + // legacy variables + filterArea: new Float32Array(4), + filterClamp: new Float32Array(4), + }, true); + } + + /** + * Adds a new filter to the System. + * + * @param {PIXI.DisplayObject} target - The target of the filter to render. + * @param {PIXI.Filter[]} filters - The filters to apply. + */ + push(target, filters) + { + const renderer = this.renderer; + const filterStack = this.renderer.renderTexture.defaultFilterStack; + const state = this.statePool.pop() || new FilterState(); + + let resolution = filters[0].resolution; + let padding = filters[0].padding; + let autoFit = filters[0].autoFit; + let legacy = filters[0].legacy; + + for (let i = 1; i < filters.length; i++) + { + const filter = filters[i]; + + // lets use the lowest resolution.. + resolution = Math.min(resolution, filter.resolution); + // and the largest amount of padding! + padding = Math.max(padding, filter.padding); + // only auto fit if all filters are autofit + autoFit = autoFit || filter.autoFit; + + legacy = legacy || filter.legacy; + } + + filterStack.push(state); + + state.resolution = resolution; + + state.legacy = legacy; + + // round to whole number based on resolution + // TODO move that to the shader too? + state.sourceFrame = target.filterArea || target.getBounds(true); + + state.sourceFrame.pad(padding); + + if (autoFit) + { + state.sourceFrame.fit(this.renderer.renderTexture.destinationFrame); + } + + state.sourceFrame.round(resolution); + + state.renderTexture = this.getPotFilterTexture(state.sourceFrame.width, state.sourceFrame.height, resolution); + state.filters = filters; + + state.destinationFrame.width = state.renderTexture.width; + state.destinationFrame.height = state.renderTexture.height; + + state.renderTexture.filterFrame = state.sourceFrame; + renderer.renderTexture.bind(state.renderTexture, state.sourceFrame);// /, state.destinationFrame); + renderer.renderTexture.clear(); + } + + /** + * Pops off the filter and applies it. + * + */ + pop() + { + const renderer = this.renderer; + const filterStack = renderer.renderTexture.defaultFilterStack; + const state = filterStack.pop(); + const filters = state.filters; + + this.activeState = state; + + const globalUniforms = this.globalUniforms.uniforms; + + globalUniforms.sourceFrame = state.sourceFrame; + globalUniforms.destinationFrame = state.destinationFrame; + globalUniforms.resolution = state.resolution; + + // only update the rect if its legacy.. + if (state.legacy) + { + const filterArea = globalUniforms.filterArea; + const filterClamp = globalUniforms.filterClamp; + + filterArea[0] = state.destinationFrame.width; + filterArea[1] = state.destinationFrame.height; + filterArea[2] = state.sourceFrame.x; + filterArea[3] = state.sourceFrame.y; + + filterClamp[0] = 0.5 / state.resolution / state.destinationFrame.width; + filterClamp[1] = 0.5 / state.resolution / state.destinationFrame.height; + filterClamp[2] = (state.sourceFrame.width - 0.5) / state.resolution / state.destinationFrame.width; + filterClamp[3] = (state.sourceFrame.height - 0.5) / state.resolution / state.destinationFrame.height; + } + + this.globalUniforms.update(); + + const lastState = filterStack[filterStack.length - 1]; + + if (filters.length === 1) + { + filters[0].apply(this, state.renderTexture, lastState.renderTexture, false, state); + + this.returnFilterTexture(state.renderTexture); + } + else + { + let flip = state.renderTexture; + let flop = this.getPotFilterTexture( + flip.width, + flip.height, + state.resolution + ); + + flop.filterFrame = flip.filterFrame; + + let i = 0; + + for (i = 0; i < filters.length - 1; ++i) + { + filters[i].apply(this, flip, flop, true, state); + + const t = flip; + + flip = flop; + flop = t; + } + + filters[i].apply(this, flip, lastState.renderTexture, false, state); + + this.returnFilterTexture(flip); + this.returnFilterTexture(flop); + } + + this.statePool.push(state); + } + + /** + * Draws a filter. + * + * @param {PIXI.Filter} filter - The filter to draw. + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + * @param {boolean} clear - Should the output be cleared before rendering to it + */ + applyFilter(filter, input, output, clear) + { + const renderer = this.renderer; + + renderer.renderTexture.bind(output, output ? output.filterFrame : null); + + if (clear) + { + // gl.disable(gl.SCISSOR_TEST); + renderer.renderTexture.clear(); + // gl.enable(gl.SCISSOR_TEST); + } + + // set the uniforms.. + filter.uniforms.uSampler = input; + filter.uniforms.filterGlobals = this.globalUniforms; + + // TODO make it so that the order of this does not matter.. + // because it does at the moment cos of global uniforms. + // they need to get resynced + + renderer.state.setState(filter.state); + renderer.shader.bind(filter); + + if (filter.legacy) + { + this.quadUv.map(input._frame, input.filterFrame); + + renderer.geometry.bind(this.quadUv); + renderer.geometry.draw(DRAW_MODES.TRIANGLES); + } + else + { + renderer.geometry.bind(this.quad); + renderer.geometry.draw(DRAW_MODES.TRIANGLE_STRIP); + } + } + + /** + * Calculates the mapped matrix. + * + * TODO playing around here.. this is temporary - (will end up in the shader) + * this returns a matrix that will normalise map filter cords in the filter to screen space + * + * @param {PIXI.Matrix} outputMatrix - the matrix to output to. + * @return {PIXI.Matrix} The mapped matrix. + */ + calculateScreenSpaceMatrix(outputMatrix) + { + const currentState = this.activeState; + + return filterTransforms.calculateScreenSpaceMatrix( + outputMatrix, + currentState.sourceFrame, + currentState.destinationFrame + ); + } + + /** + * This will map the filter coord so that a texture can be used based on the transform of a sprite + * + * @param {PIXI.Matrix} outputMatrix - The matrix to output to. + * @param {PIXI.Sprite} sprite - The sprite to map to. + * @return {PIXI.Matrix} The mapped matrix. + */ + calculateSpriteMatrix(outputMatrix, sprite) + { + const currentState = this.activeState; + + return filterTransforms.calculateSpriteMatrix( + outputMatrix, + currentState.sourceFrame, + currentState.destinationFrame, + sprite + ); + } + + /** + * Destroys this Filter System. + * + * @param {boolean} [contextLost=false] context was lost, do not free shaders + * + */ + destroy(contextLost = false) + { + if (!contextLost) + { + this.emptyPool(); + } + else + { + this.texturePool = {}; + } + } + + /** + * Gets a Power-of-Two render texture. + * + * TODO move to a seperate class could be on renderer? + * also - could cause issue with multiple contexts? + * + * @private + * @param {WebGLRenderingContext} gl - The webgl rendering context + * @param {number} minWidth - The minimum width of the render target. + * @param {number} minHeight - The minimum height of the render target. + * @param {number} resolution - The resolution of the render target. + * @return {PIXI.RenderTarget} The new render target. + */ + getPotFilterTexture(minWidth, minHeight, resolution) + { + minWidth = bitTwiddle.nextPow2(minWidth); + minHeight = bitTwiddle.nextPow2(minHeight); + resolution = resolution || 1; + + const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + + if (!this.texturePool[key]) + { + this.texturePool[key] = []; + } + + let renderTexture = this.texturePool[key].pop(); + + if (!renderTexture) + { + // temporary bypass cache.. + // internally - this will cause a texture to be bound.. + renderTexture = RenderTexture.create({ + width: minWidth, + height: minHeight, + resolution, + }); + } + + return renderTexture; + } + + /** + * Gets extra render texture to use inside current filter + * + * @param {number} resolution resolution of the renderTexture + * @returns {PIXI.RenderTexture} + */ + getFilterTexture(resolution) + { + const rt = this.activeState.renderTexture; + + const filterTexture = this.getPotFilterTexture(rt.width, rt.height, resolution || rt.baseTexture.resolution); + + filterTexture.filterFrame = rt.filterFrame; + + return filterTexture; + } + + /** + * Frees a render texture back into the pool. + * + * @param {PIXI.RenderTarget} renderTexture - The renderTarget to free + */ + returnFilterTexture(renderTexture) + { + renderTexture.filterFrame = null; + + const base = renderTexture.baseTexture; + + const minWidth = base.width; + const minHeight = base.height; + + const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + + this.texturePool[key].push(renderTexture); + } + + /** + * Empties the texture pool. + * + */ + emptyPool() + { + for (const i in this.texturePool) + { + const textures = this.texturePool[i]; + + if (textures) + { + for (let j = 0; j < textures.length; j++) + { + textures[j].destroy(true); + } + } + } + + this.texturePool = {}; + } +} diff --git a/packages/core/src/filters/defaultFilter.frag b/packages/core/src/filters/defaultFilter.frag new file mode 100644 index 0000000..a14c1be --- /dev/null +++ b/packages/core/src/filters/defaultFilter.frag @@ -0,0 +1,22 @@ +varying vec2 vTextureCoord; +varying vec2 vFilterCoord; + +uniform sampler2D uSampler; +uniform sampler2D filterSampler; + +void main(void){ + vec4 masky = texture2D(filterSampler, vFilterCoord); + vec4 sample = texture2D(uSampler, vTextureCoord); + vec4 color; + if(mod(vFilterCoord.x, 1.0) > 0.5) + { + color = vec4(1.0, 0.0, 0.0, 1.0); + } + else + { + color = vec4(0.0, 1.0, 0.0, 1.0); + } + // gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0); + gl_FragColor = mix(sample, masky, 0.5); + gl_FragColor *= sample.a; +} diff --git a/packages/core/src/filters/defaultFilter.vert b/packages/core/src/filters/defaultFilter.vert new file mode 100644 index 0000000..e1febca --- /dev/null +++ b/packages/core/src/filters/defaultFilter.vert @@ -0,0 +1,14 @@ +attribute vec2 aVertexPosition; +attribute vec2 aTextureCoord; + +uniform mat3 projectionMatrix; +uniform mat3 filterMatrix; + +varying vec2 vTextureCoord; +varying vec2 vFilterCoord; + +void main(void){ + gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); + vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy; + vTextureCoord = aTextureCoord ; +} diff --git a/packages/core/src/filters/filterTransforms.js b/packages/core/src/filters/filterTransforms.js new file mode 100644 index 0000000..87a3cd6 --- /dev/null +++ b/packages/core/src/filters/filterTransforms.js @@ -0,0 +1,50 @@ +import { Matrix } from '@pixi/math'; + +/** + * Calculates the mapped matrix + * @param filterArea {Rectangle} The filter area + * @param sprite {Sprite} the target sprite + * @param outputMatrix {Matrix} @alvin + * @private + */ +// this returns a matrix that will normalise map filter cords in the filter to screen space +export function calculateScreenSpaceMatrix(outputMatrix, filterArea, textureSize) +{ + // TODO unwrap? + const mappedMatrix = outputMatrix.identity(); + + mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); + + mappedMatrix.scale(textureSize.width, textureSize.height); + + return mappedMatrix; +} + +export function calculateNormalizedScreenSpaceMatrix(outputMatrix, filterArea, textureSize) +{ + const mappedMatrix = outputMatrix.identity(); + + mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); + + const translateScaleX = (textureSize.width / filterArea.width); + const translateScaleY = (textureSize.height / filterArea.height); + + mappedMatrix.scale(translateScaleX, translateScaleY); + + return mappedMatrix; +} + +// this will map the filter coord so that a texture can be used based on the transform of a sprite +export function calculateSpriteMatrix(outputMatrix, filterArea, textureSize, sprite) +{ + const orig = sprite._texture.orig; + const mappedMatrix = outputMatrix.set(textureSize.width, 0, 0, textureSize.height, filterArea.x, filterArea.y); + const worldTransform = sprite.worldTransform.copyTo(Matrix.TEMP_MATRIX); + + worldTransform.invert(); + mappedMatrix.prepend(worldTransform); + mappedMatrix.scale(1.0 / orig.width, 1.0 / orig.height); + mappedMatrix.translate(sprite.anchor.x, sprite.anchor.y); + + return mappedMatrix; +} diff --git a/packages/core/src/filters/spriteMask/SpriteMaskFilter.js b/packages/core/src/filters/spriteMask/SpriteMaskFilter.js new file mode 100644 index 0000000..91697b7 --- /dev/null +++ b/packages/core/src/filters/spriteMask/SpriteMaskFilter.js @@ -0,0 +1,63 @@ +import Filter from '../Filter'; +import { Matrix } from '@pixi/math'; +import vertex from './spriteMaskFilter.vert'; +import fragment from './spriteMaskFilter.frag'; +import { default as TextureMatrix } from '../../textures/TextureMatrix'; + +/** + * The SpriteMaskFilter class + * + * @class + * @extends PIXI.Filter + * @memberof PIXI + */ +export default class SpriteMaskFilter extends Filter +{ + /** + * @param {PIXI.Sprite} sprite - the target sprite + */ + constructor(sprite) + { + const maskMatrix = new Matrix(); + + super(vertex, fragment); + + sprite.renderable = false; + + this.maskSprite = sprite; + this.maskMatrix = maskMatrix; + } + + /** + * Applies the filter + * + * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + */ + apply(filterManager, input, output) + { + const maskSprite = this.maskSprite; + const tex = this.maskSprite.texture; + + if (!tex.valid) + { + return; + } + if (!tex.transform) + { + // margin = 0.0, let it bleed a bit, shader code becomes easier + // assuming that atlas textures were made with 1-pixel padding + tex.transform = new TextureMatrix(tex, 0.0); + } + tex.transform.update(); + + this.uniforms.mask = tex; + this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite) + .prepend(tex.transform.mapCoord); + this.uniforms.alpha = maskSprite.worldAlpha; + this.uniforms.maskClamp = tex.transform.uClampFrame; + + filterManager.applyFilter(this, input, output); + } +} diff --git a/packages/core/src/filters/spriteMask/spriteMaskFilter.frag b/packages/core/src/filters/spriteMask/spriteMaskFilter.frag new file mode 100644 index 0000000..0e4aef8 --- /dev/null +++ b/packages/core/src/filters/spriteMask/spriteMaskFilter.frag @@ -0,0 +1,23 @@ +varying vec2 vMaskCoord; +varying vec2 vTextureCoord; + +uniform sampler2D uSampler; +uniform sampler2D mask; +uniform float alpha; +uniform vec4 maskClamp; + +void main(void) +{ + float clip = step(3.5, + step(maskClamp.x, vMaskCoord.x) + + step(maskClamp.y, vMaskCoord.y) + + step(vMaskCoord.x, maskClamp.z) + + step(vMaskCoord.y, maskClamp.w)); + + vec4 original = texture2D(uSampler, vTextureCoord); + vec4 masky = texture2D(mask, vMaskCoord); + + original *= (masky.r * masky.a * alpha * clip); + + gl_FragColor = original; +} diff --git a/packages/core/src/filters/spriteMask/spriteMaskFilter.vert b/packages/core/src/filters/spriteMask/spriteMaskFilter.vert new file mode 100644 index 0000000..d23a30c --- /dev/null +++ b/packages/core/src/filters/spriteMask/spriteMaskFilter.vert @@ -0,0 +1,16 @@ +attribute vec2 aVertexPosition; +attribute vec2 aTextureCoord; + +uniform mat3 projectionMatrix; +uniform mat3 otherMatrix; + +varying vec2 vMaskCoord; +varying vec2 vTextureCoord; + +void main(void) +{ + gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); + + vTextureCoord = aTextureCoord; + vMaskCoord = ( otherMatrix * vec3( aTextureCoord, 1.0) ).xy; +} diff --git a/packages/core/src/framebuffer/FrameBuffer.js b/packages/core/src/framebuffer/FrameBuffer.js new file mode 100644 index 0000000..84e9fcc --- /dev/null +++ b/packages/core/src/framebuffer/FrameBuffer.js @@ -0,0 +1,106 @@ +import Texture from '../textures/BaseTexture'; +import { FORMATS, TYPES } from '@pixi/constants'; + +/** + * Frame buffer + * @class + * @memberof PIXI + */ +export default class FrameBuffer +{ + constructor(width, height) + { + this.width = width || 100; + this.height = height || 100; + + this.stencil = false; + this.depth = false; + + this.dirtyId = 0; + this.dirtyFormat = 0; + this.dirtySize = 0; + + this.depthTexture = null; + this.colorTextures = []; + + this.glFrameBuffers = {}; + } + + get colorTexture() + { + return this.colorTextures[0]; + } + + addColorTexture(index, texture) + { + // TODO add some validation to the texture - same width / height etc? + this.colorTextures[index || 0] = texture || new Texture(null, { scaleMode: 0, + resolution: 1, + mipmap: false, + width: this.width, + height: this.height });// || new Texture(); + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + addDepthTexture(texture) + { + /* eslint-disable max-len */ + this.depthTexture = texture || new Texture(null, { scaleMode: 0, + resolution: 1, + width: this.width, + height: this.height, + mipmap: false, + format: FORMATS.DEPTH_COMPONENT, + type: TYPES.UNSIGNED_SHORT });// UNSIGNED_SHORT; + /* eslint-disable max-len */ + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + enableDepth() + { + this.depth = true; + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + enableStencil() + { + this.stencil = true; + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + resize(width, height) + { + if (width === this.width && height === this.height) return; + + this.width = width; + this.height = height; + + this.dirtyId++; + this.dirtySize++; + + for (let i = 0; i < this.colorTextures.length; i++) + { + this.colorTextures[i].setSize(width, height); + } + + if (this.depthTexture) + { + this.depthTexture.setSize(width, height); + } + } +} diff --git a/packages/core/src/framebuffer/FramebufferSystem.js b/packages/core/src/framebuffer/FramebufferSystem.js new file mode 100644 index 0000000..2cc2437 --- /dev/null +++ b/packages/core/src/framebuffer/FramebufferSystem.js @@ -0,0 +1,246 @@ +import System from '../System'; +import { Rectangle } from '@pixi/math'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class FramebufferSystem extends System +{ + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + contextChange() + { + this.gl = this.renderer.gl; + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + this.current = null; + this.viewport = new Rectangle(); + + this.drawBufferExtension = this.renderer.context.extensions.drawBuffers; + } + + bind(framebuffer, frame) + { + const gl = this.gl; + + this.current = framebuffer; + + if (framebuffer) + { + // TODO cacheing layer! + + const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID] || this.initFramebuffer(framebuffer); + + gl.bindFramebuffer(gl.FRAMEBUFFER, fbo.framebuffer); + // makesure all textures are unbound.. + + // now check for updates... + if (fbo.dirtyId !== framebuffer.dirtyId) + { + fbo.dirtyId = framebuffer.dirtyId; + + if (fbo.dirtyFormat !== framebuffer.dirtyFormat) + { + fbo.dirtyFormat = framebuffer.dirtyFormat; + this.updateFramebuffer(framebuffer); + } + else if (fbo.dirtySize !== framebuffer.dirtySize) + { + fbo.dirtySize = framebuffer.dirtySize; + this.resizeFramebuffer(framebuffer); + } + } + + for (let i = 0; i < framebuffer.colorTextures.length; i++) + { + if (framebuffer.colorTextures[i].texturePart) + { + this.renderer.texture.unbind(framebuffer.colorTextures[i].texture); + } + else + { + this.renderer.texture.unbind(framebuffer.colorTextures[i]); + } + } + + if (framebuffer.depthTexture) + { + this.renderer.texture.unbind(framebuffer.depthTexture); + } + + if (frame) + { + this.setViewport(frame.x, frame.y, frame.width, frame.height); + } + else + { + this.setViewport(0, 0, framebuffer.width, framebuffer.height); + } + } + else + { + gl.bindFramebuffer(gl.FRAMEBUFFER, null); + + if (frame) + { + this.setViewport(frame.x, frame.y, frame.width, frame.height); + } + else + { + this.setViewport(0, 0, this.renderer.width, this.renderer.height); + } + } + } + + setViewport(x, y, width, height) + { + const v = this.viewport; + + if (v.width !== width || v.height !== height || v.x !== x || v.y !== y) + { + v.x = x; + v.y = y; + v.width = width; + v.height = height; + + this.gl.viewport(x, y, width, height); + } + } + + get size() + { + if (this.current) + { + // TODO store temp + return { x: 0, y: 0, width: this.current.width, height: this.current.height }; + } + + return { x: 0, y: 0, width: this.renderer.width, height: this.renderer.height }; + } + + clear(r, g, b, a) + { + const gl = this.gl; + + // TODO clear color can be set only one right? + gl.clearColor(r, g, b, a); + gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); + } + + // private functions... + + initFramebuffer(framebuffer) + { + const gl = this.gl; + + // TODO - make this a class? + const fbo = { + framebuffer: gl.createFramebuffer(), + stencil: null, + dirtyId: 0, + dirtyFormat: 0, + dirtySize: 0, + }; + + framebuffer.glFrameBuffers[this.CONTEXT_UID] = fbo; + + return fbo; + } + + resizeFramebuffer(framebuffer) + { + const gl = this.gl; + + if (framebuffer.stencil || framebuffer.depth) + { + gl.bindRenderbuffer(gl.RENDERBUFFER, this.stencil); + gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); + } + } + + updateFramebuffer(framebuffer) + { + const gl = this.gl; + + const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID]; + + // bind the color texture + const colorTextures = framebuffer.colorTextures; + + let count = colorTextures.length; + + if (!this.drawBufferExtension) + { + count = Math.min(count, 1); + } + + const activeTextures = []; + + for (let i = 0; i < count; i++) + { + const texture = framebuffer.colorTextures[i]; + + if (texture.texturePart) + { + this.renderer.texture.bind(texture.texture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.COLOR_ATTACHMENT0 + i, + gl.TEXTURE_CUBE_MAP_NEGATIVE_X + texture.side, + texture.texture._glTextures[this.CONTEXT_UID].texture, + 0); + } + else + { + this.renderer.texture.bind(texture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.COLOR_ATTACHMENT0 + i, + gl.TEXTURE_2D, + texture._glTextures[this.CONTEXT_UID].texture, + 0); + } + + activeTextures.push(gl.COLOR_ATTACHMENT0 + i); + } + + if (this.drawBufferExtension && activeTextures.length > 1) + { + this.drawBufferExtension.drawBuffersWEBGL(activeTextures); + } + + if (framebuffer.depthTexture) + { + const depthTextureExt = this.renderer.context.extensions.depthTexture; + + if (depthTextureExt) + { + const depthTexture = framebuffer.depthTexture; + + this.renderer.texture.bind(depthTexture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.DEPTH_ATTACHMENT, + gl.TEXTURE_2D, + depthTexture._glTextures[this.CONTEXT_UID].texture, + 0); + } + } + + if (framebuffer.stencil || framebuffer.depth) + { + fbo.stencil = gl.createRenderbuffer(); + + gl.bindRenderbuffer(gl.RENDERBUFFER, fbo.stencil); + + // TODO.. this is depth AND stencil? + gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, fbo.stencil); + gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); + // fbo.enableStencil(); + } + } +} diff --git a/packages/core/src/geometry/GLBuffer.js b/packages/core/src/geometry/GLBuffer.js new file mode 100644 index 0000000..0477e8a --- /dev/null +++ b/packages/core/src/geometry/GLBuffer.js @@ -0,0 +1,9 @@ +export default class GLBuffer +{ + constructor(buffer) + { + this.buffer = buffer; + this.updateID = -1; + this.byteLength = -1; + } +} diff --git a/packages/core/src/geometry/GeometrySystem.js b/packages/core/src/geometry/GeometrySystem.js new file mode 100644 index 0000000..959f827 --- /dev/null +++ b/packages/core/src/geometry/GeometrySystem.js @@ -0,0 +1,388 @@ +import System from '../System'; +import GLBuffer from './GLBuffer'; + +const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class GeometrySystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this._activeGeometry = null; + this._activeVao = null; + + this.hasVao = true; + this.hasInstance = true; + } + + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + contextChange() + { + const gl = this.gl = this.renderer.gl; + + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + + // webgl2 + if (!gl.createVertexArray) + { + // webgl 1! + let nativeVaoExtension = this.renderer.context.extensions.vertexArrayObject; + + if (this.renderer.legacy) + { + nativeVaoExtension = null; + } + + if (nativeVaoExtension) + { + gl.createVertexArray = () => + nativeVaoExtension.createVertexArrayOES(); + + gl.bindVertexArray = (vao) => + nativeVaoExtension.bindVertexArrayOES(vao); + + gl.deleteVertexArray = (vao) => + nativeVaoExtension.deleteVertexArrayOES(vao); + } + else + { + this.hasVao = false; + gl.createVertexArray = () => + { + // empty + }; + + gl.bindVertexArray = () => + { + // empty + }; + + gl.deleteVertexArray = () => + { + // empty + }; + } + } + + if (!gl.vertexAttribDivisor) + { + const instanceExt = gl.getExtension('ANGLE_instanced_arrays'); + + if (instanceExt) + { + gl.vertexAttribDivisor = (a, b) => + instanceExt.vertexAttribDivisorANGLE(a, b); + + gl.drawElementsInstanced = (a, b, c, d, e) => + instanceExt.drawElementsInstancedANGLE(a, b, c, d, e); + + gl.drawArraysInstanced = (a, b, c, d) => + instanceExt.drawArraysInstancedANGLE(a, b, c, d); + } + else + { + this.hasInstance = false; + } + } + } + + /** + * Binds geometry so that is can be drawn. Creating a Vao if required + * @private + * @param {PIXI.Geometry} geometry instance of geometry to bind + */ + bind(geometry, shader) + { + shader = shader || this.renderer.shader.shader; + + const gl = this.gl; + + // not sure the best way to address this.. + // currently different shaders require different VAOs for the same geometry + // Still mulling over the best way to solve this one.. + // will likely need to modify the shader attribute locations at run time! + let vaos = geometry.glVertexArrayObjects[this.CONTEXT_UID]; + + if (!vaos) + { + geometry.glVertexArrayObjects[this.CONTEXT_UID] = vaos = {}; + } + + const vao = vaos[shader.program.id] || this.initGeometryVao(geometry, shader.program); + + this._activeGeometry = geometry; + + if (this._activeVao !== vao) + { + this._activeVao = vao; + + if (this.hasVao) + { + gl.bindVertexArray(vao); + } + else + { + this.activateVao(geometry, shader.program); + } + } + + // TODO - optimise later! + // don't need to loop through if nothing changed! + // maybe look to add an 'autoupdate' to geometry? + this.updateBuffers(); + } + + reset() + { + this.unbind(); + } + + updateBuffers() + { + const geometry = this._activeGeometry; + const gl = this.gl; + + for (let i = 0; i < geometry.buffers.length; i++) + { + const buffer = geometry.buffers[i]; + + const glBuffer = buffer._glBuffers[this.CONTEXT_UID]; + + if (buffer._updateID !== glBuffer.updateID) + { + glBuffer.updateID = buffer._updateID; + + // TODO can cache this on buffer! maybe added a getter / setter? + const type = buffer.index ? gl.ELEMENT_ARRAY_BUFFER : gl.ARRAY_BUFFER; + const drawType = buffer.static ? gl.STATIC_DRAW : gl.DYNAMIC_DRAW; + + gl.bindBuffer(type, glBuffer.buffer); + + if (glBuffer.byteLength >= buffer.data.byteLength) + { + // offset is always zero for now! + gl.bufferSubData(type, 0, buffer.data); + } + else + { + gl.bufferData(type, buffer.data, drawType); + } + } + } + } + + checkCompatability(geometry, program) + { + // geometry must have at least all the attributes that the shader requires. + const geometryAttributes = geometry.attributes; + const shaderAttributes = program.attributeData; + + for (const j in shaderAttributes) + { + if (!geometryAttributes[j]) + { + throw new Error(`shader and geometry incompatible, geometry missing the "${j}" attribute`); + } + } + } + + /** + * Creates a Vao with the same structure as the geometry and stores it on the geometry. + * @private + * @param {PIXI.Geometry} geometry instance of geometry to to generate Vao for + */ + initGeometryVao(geometry, program) + { + this.checkCompatability(geometry, program); + + const gl = this.gl; + const CONTEXT_UID = this.CONTEXT_UID; + const buffers = geometry.buffers; + const attributes = geometry.attributes; + + const tempStride = {}; + const tempStart = {}; + + for (const j in buffers) + { + tempStride[j] = 0; + tempStart[j] = 0; + } + + for (const j in attributes) + { + if (!attributes[j].size && program.attributeData[j]) + { + attributes[j].size = program.attributeData[j].size; + } + + tempStride[attributes[j].buffer] += attributes[j].size * byteSizeMap[attributes[j].type]; + } + + for (const j in attributes) + { + const attribute = attributes[j]; + const attribSize = attribute.size; + + if (attribute.stride === undefined) + { + if (tempStride[attribute.buffer] === attribSize * byteSizeMap[attribute.type]) + { + attribute.stride = 0; + } + else + { + attribute.stride = tempStride[attribute.buffer]; + } + } + + if (attribute.start === undefined) + { + attribute.start = tempStart[attribute.buffer]; + + tempStart[attribute.buffer] += attribSize * byteSizeMap[attribute.type]; + } + } + + // first update - and create the buffers! + // only create a gl buffer if it actually gets + for (let i = 0; i < buffers.length; i++) + { + const buffer = buffers[i]; + + if (!buffer._glBuffers[CONTEXT_UID]) + { + buffer._glBuffers[CONTEXT_UID] = new GLBuffer(gl.createBuffer()); + } + } + + // TODO - maybe make this a data object? + // lets wait to see if we need to first! + const vao = gl.createVertexArray(); + + gl.bindVertexArray(vao); + + this.activateVao(geometry, program); + + gl.bindVertexArray(null); + + // add it to the cache! + geometry.glVertexArrayObjects[this.CONTEXT_UID][program.id] = vao; + + return vao; + } + + activateVao(geometry, program) + { + const gl = this.gl; + const CONTEXT_UID = this.CONTEXT_UID; + const buffers = geometry.buffers; + const attributes = geometry.attributes; + + if (geometry.indexBuffer) + { + // first update the index buffer if we have one.. + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, geometry.indexBuffer._glBuffers[CONTEXT_UID].buffer); + } + + let lastBuffer = null; + + // add a new one! + for (const j in attributes) + { + const attribute = attributes[j]; + const buffer = buffers[attribute.buffer]; + const glBuffer = buffer._glBuffers[CONTEXT_UID]; + + if (program.attributeData[j]) + { + if (lastBuffer !== glBuffer) + { + gl.bindBuffer(gl.ARRAY_BUFFER, glBuffer.buffer); + + lastBuffer = glBuffer; + } + + const location = program.attributeData[j].location; + + // TODO introduce state again + // we can optimise this for older devices that have no VAOs + gl.enableVertexAttribArray(location); + + gl.vertexAttribPointer(location, + attribute.size, + attribute.type || gl.FLOAT, + attribute.normalized, + attribute.stride, + attribute.start); + + if (attribute.instance) + { + // TODO calculate instance count based of this... + if (this.hasInstance) + { + gl.vertexAttribDivisor(location, 1); + } + else + { + throw new Error('geometry error, GPU Instancing is not supported on this device'); + } + } + } + } + } + + draw(type, size, start, instanceCount) + { + const gl = this.gl; + const geometry = this._activeGeometry; + + // TODO.. this should not change so maybe cache the function? + + if (geometry.indexBuffer) + { + if (geometry.instanced) + { + /* eslint-disable max-len */ + gl.drawElementsInstanced(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2, instanceCount || 1); + /* eslint-enable max-len */ + } + else + { + gl.drawElements(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2); + } + } + else + if (geometry.instanced) + { + // TODO need a better way to calculate size.. + gl.drawArraysInstanced(type, start, size || geometry.getSize(), instanceCount || 1); + } + else + { + gl.drawArrays(type, start, size || geometry.getSize()); + } + + return this; + } + + unbind() + { + this.gl.bindVertexArray(null); + this._activeVao = null; + this._activeGeometry = null; + } +} diff --git a/packages/core/src/geometry/utils/setVertexAttribArrays.js b/packages/core/src/geometry/utils/setVertexAttribArrays.js new file mode 100644 index 0000000..23b6959 --- /dev/null +++ b/packages/core/src/geometry/utils/setVertexAttribArrays.js @@ -0,0 +1,54 @@ +// var GL_MAP = {}; + +/** + * @param gl {WebGLRenderingContext} The current WebGL context + * @param attribs {*} + * @param state {*} + */ +export default function setVertexAttribArrays(gl, attribs, state) +{ + let i; + + if (state) + { + const tempAttribState = state.tempAttribState; + const attribState = state.attribState; + + for (i = 0; i < tempAttribState.length; i++) + { + tempAttribState[i] = false; + } + + // set the new attribs + for (i = 0; i < attribs.length; i++) + { + tempAttribState[attribs[i].attribute.location] = true; + } + + for (i = 0; i < attribState.length; i++) + { + if (attribState[i] !== tempAttribState[i]) + { + attribState[i] = tempAttribState[i]; + + if (state.attribState[i]) + { + gl.enableVertexAttribArray(i); + } + else + { + gl.disableVertexAttribArray(i); + } + } + } + } + else + { + for (i = 0; i < attribs.length; i++) + { + const attrib = attribs[i]; + + gl.enableVertexAttribArray(attrib.attribute.location); + } + } +} diff --git a/packages/core/src/index.js b/packages/core/src/index.js index 58c456a..fd1f547 100644 --- a/packages/core/src/index.js +++ b/packages/core/src/index.js @@ -1,30 +1,30 @@ import * as resources from './textures/resources'; -import * as systems from './renderers/systems'; +import * as systems from './systems'; -export { resources }; export { systems }; +export { resources }; -export { default as Renderer } from './renderers/Renderer'; -export { default as AbstractRenderer } from './renderers/AbstractRenderer'; -export { default as FrameBuffer } from './textures/FrameBuffer'; +export { default as System } from './System'; +export { default as Renderer } from './Renderer'; +export { default as AbstractRenderer } from './AbstractRenderer'; +export { default as FrameBuffer } from './framebuffer/FrameBuffer'; export { default as CubeTexture } from './textures/CubeTexture'; export { default as BaseTexture } from './textures/BaseTexture'; export { default as Texture } from './textures/Texture'; export { default as TextureMatrix } from './textures/TextureMatrix'; -export { default as RenderTexture } from './textures/RenderTexture'; -export { default as BaseRenderTexture } from './textures/BaseRenderTexture'; +export { default as RenderTexture } from './renderTexture/RenderTexture'; +export { default as BaseRenderTexture } from './renderTexture/BaseRenderTexture'; export { default as TextureUvs } from './textures/TextureUvs'; -export { default as State } from './renderers/State'; -export { default as ObjectRenderer } from './renderers/utils/ObjectRenderer'; -export { default as Quad } from './renderers/utils/Quad'; -export { default as QuadUv } from './renderers/utils/QuadUv'; -export { default as checkMaxIfStatmentsInShader } from './renderers/utils/checkMaxIfStatmentsInShader'; +export { default as State } from './state/State'; +export { default as ObjectRenderer } from './batch/ObjectRenderer'; +export { default as Quad } from './utils/Quad'; +export { default as QuadUv } from './utils/QuadUv'; +export { default as checkMaxIfStatmentsInShader } from './shader/utils/checkMaxIfStatmentsInShader'; export { default as Shader } from './shader/Shader'; export { default as Program } from './shader/Program'; export { default as UniformGroup } from './shader/UniformGroup'; -export { default as SpriteMaskFilter } from './renderers/filters/spriteMask/SpriteMaskFilter'; -export { default as Filter } from './renderers/filters/Filter'; +export { default as SpriteMaskFilter } from './filters/spriteMask/SpriteMaskFilter'; +export { default as Filter } from './filters/Filter'; export { default as Attribute } from './geometry/Attribute'; export { default as Buffer } from './geometry/Buffer'; export { default as Geometry } from './geometry/Geometry'; - diff --git a/packages/core/src/mask/MaskSystem.js b/packages/core/src/mask/MaskSystem.js new file mode 100644 index 0000000..99174c2 --- /dev/null +++ b/packages/core/src/mask/MaskSystem.js @@ -0,0 +1,199 @@ +import System from '../System'; +import AlphaMaskFilter from '../filters/spriteMask/SpriteMaskFilter'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class MaskSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + // TODO - we don't need both! + this.scissor = false; + this.scissorData = null; + this.scissorRenderTarget = null; + + this.enableScissor = false; + + this.alphaMaskPool = []; + this.alphaMaskIndex = 0; + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.DisplayObject} target - Display Object to push the mask to + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + push(target, maskData) + { + // TODO the root check means scissor rect will not + // be used on render textures more info here: + // https://github.com/pixijs/pixi.js/pull/3545 + + if (maskData.texture) + { + this.pushSpriteMask(target, maskData); + } + else if (this.enableScissor + && !this.scissor + && this.renderer._activeRenderTarget.root + && !this.renderer.stencil.stencilMaskStack.length + && maskData.isFastRect()) + { + const matrix = maskData.worldTransform; + + let rot = Math.atan2(matrix.b, matrix.a); + + // use the nearest degree! + rot = Math.round(rot * (180 / Math.PI)); + + if (rot % 90) + { + this.pushStencilMask(maskData); + } + else + { + this.pushScissorMask(target, maskData); + } + } + else + { + this.pushStencilMask(maskData); + } + } + + /** + * Removes the last mask from the mask stack and doesn't return it. + * + * @param {PIXI.DisplayObject} target - Display Object to pop the mask from + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + pop(target, maskData) + { + if (maskData.texture) + { + this.popSpriteMask(target, maskData); + } + else if (this.enableScissor && !this.renderer.stencil.stencilMaskStack.length) + { + this.popScissorMask(target, maskData); + } + else + { + this.popStencilMask(target, maskData); + } + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.RenderTarget} target - Display Object to push the sprite mask to + * @param {PIXI.Sprite} maskData - Sprite to be used as the mask + */ + pushSpriteMask(target, maskData) + { + let alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; + + if (!alphaMaskFilter) + { + alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; + } + + alphaMaskFilter[0].resolution = this.renderer.resolution; + alphaMaskFilter[0].maskSprite = maskData; + + // TODO - may cause issues! + target.filterArea = maskData.getBounds(true); + + this.renderer.filterSystem.pushFilter(target, alphaMaskFilter); + + this.alphaMaskIndex++; + } + + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popSpriteMask() + { + this.renderer.filterSystem.popFilter(); + this.alphaMaskIndex--; + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + pushStencilMask(maskData) + { + this.renderer.batch.flush(); + this.renderer.stencil.pushStencil(maskData); + } + + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popStencilMask() + { + // this.renderer.currentRenderer.stop(); + this.renderer.stencil.popStencil(); + } + + /** + * + * @param {PIXI.DisplayObject} target - Display Object to push the mask to + * @param {PIXI.Graphics} maskData - The masking data. + */ + pushScissorMask(target, maskData) + { + maskData.renderable = true; + + const renderTarget = this.renderer._activeRenderTarget; + + const bounds = maskData.getBounds(); + + bounds.fit(renderTarget.size); + maskData.renderable = false; + + this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); + + const resolution = this.renderer.resolution; + + this.renderer.gl.scissor( + bounds.x * resolution, + (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, + bounds.width * resolution, + bounds.height * resolution + ); + + this.scissorRenderTarget = renderTarget; + this.scissorData = maskData; + this.scissor = true; + } + + /** + * + * + */ + popScissorMask() + { + this.scissorRenderTarget = null; + this.scissorData = null; + this.scissor = false; + + // must be scissor! + const gl = this.renderer.gl; + + gl.disable(gl.SCISSOR_TEST); + } +} diff --git a/packages/core/src/mask/StencilSystem.js b/packages/core/src/mask/StencilSystem.js new file mode 100644 index 0000000..62796a5 --- /dev/null +++ b/packages/core/src/mask/StencilSystem.js @@ -0,0 +1,133 @@ +import System from '../System'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class StencilSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + this.stencilMaskStack = []; + } + + /** + * Changes the mask stack that is used by this System. + * + * @param {PIXI.Graphics[]} stencilMaskStack - The mask stack + */ + setMaskStack(stencilMaskStack) + { + const gl = this.renderer.gl; + + if (stencilMaskStack.length !== this.stencilMaskStack.length) + { + if (stencilMaskStack.length === 0) + { + gl.disable(gl.STENCIL_TEST); + } + else + { + gl.enable(gl.STENCIL_TEST); + } + } + + this.stencilMaskStack = stencilMaskStack; + } + + /** + * Applies the Mask and adds it to the current stencil stack. @alvin + * + * @param {PIXI.Graphics} graphics - The mask + */ + pushStencil(graphics) + { + this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); + + // this.renderer._activeRenderTarget.attachStencilBuffer(); + + const gl = this.renderer.gl; + const prevMaskCount = this.stencilMaskStack.length; + + if (prevMaskCount === 0) + { + gl.enable(gl.STENCIL_TEST); + } + + this.stencilMaskStack.push(graphics); + + // Increment the refference stencil value where the new mask overlaps with the old ones. + gl.colorMask(false, false, false, false); + gl.stencilFunc(gl.EQUAL, prevMaskCount, this._getBitwiseMask()); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.INCR); + this.renderer.plugins.graphics.render(graphics); + + this._useCurrent(); + } + + /** + * Removes the last mask from the stencil stack. @alvin + */ + popStencil() + { + this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); + + const gl = this.renderer.gl; + const graphics = this.stencilMaskStack.pop(); + + if (this.stencilMaskStack.length === 0) + { + // the stack is empty! + gl.disable(gl.STENCIL_TEST); + gl.clear(gl.STENCIL_BUFFER_BIT); + gl.clearStencil(0); + } + else + { + // Decrement the refference stencil value where the popped mask overlaps with the other ones + gl.colorMask(false, false, false, false); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.DECR); + this.renderer.plugins.graphics.render(graphics); + + this._useCurrent(); + } + } + + /** + * Setup renderer to use the current stencil data. + */ + _useCurrent() + { + const gl = this.renderer.gl; + + gl.colorMask(true, true, true, true); + gl.stencilFunc(gl.EQUAL, this.stencilMaskStack.length, this._getBitwiseMask()); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP); + } + + /** + * Fill 1s equal to the number of acitve stencil masks. + * + * @return {number} The bitwise mask. + */ + _getBitwiseMask() + { + return (1 << this.stencilMaskStack.length) - 1; + } + + /** + * Destroys the mask stack. + * + */ + destroy() + { + super.destroy(this); + + this.stencilMaskStack = null; + } +} diff --git a/packages/core/src/projection/ProjectionSystem.js b/packages/core/src/projection/ProjectionSystem.js new file mode 100644 index 0000000..6e63b73 --- /dev/null +++ b/packages/core/src/projection/ProjectionSystem.js @@ -0,0 +1,73 @@ +import System from '../System'; +import { Matrix } from '@pixi/math'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class ProjectionSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.projectionMatrix = new Matrix(); + } + + update(destinationFrame, sourceFrame, resolution, root) + { + this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; + this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; + + this.calculateProjection(this.destinationFrame, this.sourceFrame, resolution, root); + + this.renderer.globalUniforms.uniforms.projectionMatrix = this.projectionMatrix; + this.renderer.globalUniforms.update(); + } + + /** + * Updates the projection matrix based on a projection frame (which is a rectangle) + * + * @param {Rectangle} destinationFrame - The destination frame. + * @param {Rectangle} sourceFrame - The source frame. + */ + calculateProjection(destinationFrame, sourceFrame, resolution, root) + { + const pm = this.projectionMatrix; + + // I don't think we will need this line.. + // pm.identity(); + + if (!root) + { + pm.a = (1 / destinationFrame.width * 2) * resolution; + pm.d = (1 / destinationFrame.height * 2) * resolution; + + pm.tx = -1 - (sourceFrame.x * pm.a); + pm.ty = -1 - (sourceFrame.y * pm.d); + } + else + { + pm.a = (1 / destinationFrame.width * 2) * resolution; + pm.d = (-1 / destinationFrame.height * 2) * resolution; + + pm.tx = -1 - (sourceFrame.x * pm.a); + pm.ty = 1 - (sourceFrame.y * pm.d); + } + } + + /** + * Sets the transform of the active render target to the given matrix + * + * @param {PIXI.Matrix} matrix - The transformation matrix + */ + setTransform()// matrix) + { + // this._activeRenderTarget.transform = matrix; + } +} diff --git a/packages/core/src/renderTexture/BaseRenderTexture.js b/packages/core/src/renderTexture/BaseRenderTexture.js new file mode 100644 index 0000000..88eb83b --- /dev/null +++ b/packages/core/src/renderTexture/BaseRenderTexture.js @@ -0,0 +1,137 @@ +import BaseTexture from '../textures/BaseTexture'; +import FrameBuffer from '../framebuffer/FrameBuffer'; + +/** + * A BaseRenderTexture is a special texture that allows any PixiJS display object to be rendered to it. + * + * __Hint__: All DisplayObjects (i.e. Sprites) that render to a BaseRenderTexture should be preloaded + * otherwise black rectangles will be drawn instead. + * + * A BaseRenderTexture takes a snapshot of any Display Object given to its render method. The position + * and rotation of the given Display Objects is ignored. For example: + * + * ```js + * let renderer = PIXI.autoDetectRenderer(1024, 1024, { view: canvas, ratio: 1 }); + * let baseRenderTexture = new PIXI.BaseRenderTexture(renderer, 800, 600); + * let sprite = PIXI.Sprite.fromImage("spinObj_01.png"); + * + * sprite.position.x = 800/2; + * sprite.position.y = 600/2; + * sprite.anchor.x = 0.5; + * sprite.anchor.y = 0.5; + * + * baseRenderTexture.render(sprite); + * ``` + * + * The Sprite in this case will be rendered using its local transform. To render this sprite at 0,0 + * you can clear the transform + * + * ```js + * + * sprite.setTransform() + * + * let baseRenderTexture = new PIXI.BaseRenderTexture(100, 100); + * let renderTexture = new PIXI.RenderTexture(baseRenderTexture); + * + * renderer.render(sprite, renderTexture); // Renders to center of RenderTexture + * ``` + * + * @class + * @extends PIXI.BaseTexture + * @memberof PIXI + */ +export default class BaseRenderTexture extends BaseTexture +{ + /** + * @param {object} [options] + * @param {number} [options.width=100] - The width of the base render texture + * @param {number} [options.height=100] - The height of the base render texture + * @param {PIXI.SCALE_MODES} [options.scaleMode] - See {@link PIXI.SCALE_MODES} for possible values + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the texture being generated + */ + constructor(options) + { + if (typeof options === 'number') + { + /* eslint-disable prefer-rest-params */ + // Backward compatibility of signature + const width = arguments[0]; + const height = arguments[1]; + const scaleMode = arguments[2]; + const resolution = arguments[3]; + + options = { width, height, scaleMode, resolution }; + /* eslint-enable prefer-rest-params */ + } + + super(null, options); + + const { width, height } = options || {}; + + // Set defaults + this.mipmap = false; + this.width = Math.ceil(width) || 100; + this.height = Math.ceil(height) || 100; + this.valid = true; + + /** + * A map of renderer IDs to webgl renderTargets + * + * @private + * @member {object} + */ + // this._glRenderTargets = {}; + + /** + * A reference to the canvas render target (we only need one as this can be shared across renderers) + * + * @private + * @member {object} + */ + this._canvasRenderTarget = null; + + this.clearColor = [0, 0, 0, 0]; + + this.frameBuffer = new FrameBuffer(width * this.resolution, height * this.resolution) + .addColorTexture(0, this); + + // TODO - could this be added the systems? + + /** + * The data structure for the stencil masks + * + * @member {PIXI.Graphics[]} + */ + this.stencilMaskStack = []; + + /** + * The data structure for the filters + * + * @member {PIXI.Graphics[]} + */ + this.filterStack = [{}]; + } + + /** + * Resizes the BaseRenderTexture. + * + * @param {number} width - The width to resize to. + * @param {number} height - The height to resize to. + */ + resize(width, height) + { + width = Math.ceil(width); + height = Math.ceil(height); + this.frameBuffer.resize(width * this.resolution, height * this.resolution); + } + + /** + * Destroys this texture + * + */ + destroy() + { + super.destroy(true); + this.renderer = null; + } +} diff --git a/packages/core/src/renderTexture/RenderTexture.js b/packages/core/src/renderTexture/RenderTexture.js new file mode 100644 index 0000000..f1ecf1f --- /dev/null +++ b/packages/core/src/renderTexture/RenderTexture.js @@ -0,0 +1,153 @@ +import BaseRenderTexture from './BaseRenderTexture'; +import Texture from '../textures/Texture'; + +/** + * A RenderTexture is a special texture that allows any PixiJS display object to be rendered to it. + * + * __Hint__: All DisplayObjects (i.e. Sprites) that render to a RenderTexture should be preloaded + * otherwise black rectangles will be drawn instead. + * + * A RenderTexture takes a snapshot of any Display Object given to its render method. For example: + * + * ```js + * let renderer = PIXI.autoDetectRenderer(1024, 1024, { view: canvas, ratio: 1 }); + * let renderTexture = PIXI.RenderTexture.create(800, 600); + * let sprite = PIXI.Sprite.fromImage("spinObj_01.png"); + * + * sprite.position.x = 800/2; + * sprite.position.y = 600/2; + * sprite.anchor.x = 0.5; + * sprite.anchor.y = 0.5; + * + * renderer.render(sprite, renderTexture); + * ``` + * + * The Sprite in this case will be rendered using its local transform. To render this sprite at 0,0 + * you can clear the transform + * + * ```js + * + * sprite.setTransform() + * + * let renderTexture = new PIXI.RenderTexture.create(100, 100); + * + * renderer.render(sprite, renderTexture); // Renders to center of RenderTexture + * ``` + * + * @class + * @extends PIXI.Texture + * @memberof PIXI + */ +export default class RenderTexture extends Texture +{ + /** + * @param {PIXI.BaseRenderTexture} baseRenderTexture - The renderer used for this RenderTexture + * @param {PIXI.Rectangle} [frame] - The rectangle frame of the texture to show + */ + constructor(baseRenderTexture, frame) + { + // support for legacy.. + let _legacyRenderer = null; + + if (!(baseRenderTexture instanceof BaseRenderTexture)) + { + /* eslint-disable prefer-rest-params, no-console */ + const width = arguments[1]; + const height = arguments[2]; + const scaleMode = arguments[3]; + const resolution = arguments[4]; + + // we have an old render texture.. + console.warn(`Please use RenderTexture.create(${width}, ${height}) instead of the ctor directly.`); + _legacyRenderer = arguments[0]; + /* eslint-enable prefer-rest-params, no-console */ + + frame = null; + baseRenderTexture = new BaseRenderTexture({ + width, + height, + scaleMode, + resolution, + }); + } + + /** + * The base texture object that this texture uses + * + * @member {BaseTexture} + */ + super(baseRenderTexture, frame); + + this.legacyRenderer = _legacyRenderer; + + /** + * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. + * + * @member {boolean} + */ + this.valid = true; + + /** + * FilterSystem temporary storage + * @private + * @member {PIXI.Rectangle} + */ + this.filterFrame = null; + + this._updateUvs(); + } + + /** + * Resizes the RenderTexture. + * + * @param {number} width - The width to resize to. + * @param {number} height - The height to resize to. + * @param {boolean} [resizeBaseTexture=true] - Should the baseTexture.width and height values be resized as well? + */ + resize(width, height, resizeBaseTexture = true) + { + width = Math.ceil(width); + height = Math.ceil(height); + + // TODO - could be not required.. + this.valid = (width > 0 && height > 0); + + this._frame.width = this.orig.width = width; + this._frame.height = this.orig.height = height; + + if (resizeBaseTexture) + { + this.baseTexture.resize(width, height); + } + + this._updateUvs(); + } + + /** + * A short hand way of creating a render texture. + * + * @param {object} [options] - Options + * @param {number} [options.width=100] - The width of the render texture + * @param {number} [options.height=100] - The height of the render texture + * @param {number} [options.scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the texture being generated + * @return {PIXI.RenderTexture} The new render texture + */ + static create(options) + { + // fallback, old-style: create(width, height, scaleMode, resolution) + if (typeof options === 'number') + { + /* eslint-disable prefer-rest-params */ + options = { + width: options, + height: arguments[1], + scaleMode: arguments[2], + resolution: arguments[3], + }; + /* eslint-enable prefer-rest-params */ + } + + return new RenderTexture(new BaseRenderTexture(options)); + } +} diff --git a/packages/core/src/renderTexture/RenderTextureSystem.js b/packages/core/src/renderTexture/RenderTextureSystem.js new file mode 100644 index 0000000..8d0ca61 --- /dev/null +++ b/packages/core/src/renderTexture/RenderTextureSystem.js @@ -0,0 +1,115 @@ +import System from '../System'; +import { Rectangle } from '@pixi/math'; + +const tempRect = new Rectangle(); + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class RenderTextureSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.clearColor = renderer._backgroundColorRgba; + + // TODO moe this property somewhere else! + this.defaultMaskStack = []; + this.defaultFilterStack = [{}]; + + // empty render texture? + this.renderTexture = null; + + this.destinationFrame = new Rectangle(); + } + + bind(renderTexture, sourceFrame, destinationFrame) + { + // TODO - do we want this?? + if (this.renderTexture === renderTexture) return; + this.renderTexture = renderTexture; + + const renderer = this.renderer; + + if (renderTexture) + { + const baseTexture = renderTexture.baseTexture; + + if (!destinationFrame) + { + tempRect.width = baseTexture.realWidth; + tempRect.height = baseTexture.realHeight; + + destinationFrame = tempRect; + } + + if (!sourceFrame) + { + sourceFrame = destinationFrame; + } + + this.renderer.framebuffer.bind(baseTexture.frameBuffer, destinationFrame); + + this.renderer.projection.update(destinationFrame, sourceFrame, baseTexture.resolution, false); + this.renderer.stencil.setMaskStack(baseTexture.stencilMaskStack); + } + else + { + // TODO these validation checks happen deeper down.. + // thing they can be avoided.. + if (!destinationFrame) + { + tempRect.width = renderer.width; + tempRect.height = renderer.height; + + destinationFrame = tempRect; + } + + if (!sourceFrame) + { + sourceFrame = destinationFrame; + } + + renderer.framebuffer.bind(null, destinationFrame); + + // TODO store this.. + this.renderer.projection.update(destinationFrame, sourceFrame, this.renderer.resolution, true); + this.renderer.stencil.setMaskStack(this.defaultMaskStack); + } + + this.destinationFrame.copyFrom(destinationFrame); + } + + /** + * Erases the render texture and fills the drawing area with a colour + * + * @param {number} [clearColor] - The colour + * @return {PIXI.Renderer} Returns itself. + */ + clear(clearColor) + { + if (this.renderTexture) + { + clearColor = clearColor || this.renderTexture.baseTexture.clearColor; + } + else + { + clearColor = clearColor || this.clearColor; + } + + this.renderer.framebuffer.clear(clearColor[0], clearColor[1], clearColor[2], clearColor[3]); + } + + resize()// screenWidth, screenHeight) + { + // resize the root only! + this.bind(null); + } +} diff --git a/packages/core/src/renderers/AbstractRenderer.js b/packages/core/src/renderers/AbstractRenderer.js deleted file mode 100644 index 928670f..0000000 --- a/packages/core/src/renderers/AbstractRenderer.js +++ /dev/null @@ -1,353 +0,0 @@ -import { hex2string, hex2rgb } from '@pixi/utils'; -import { Matrix, Rectangle } from '@pixi/math'; -import { RENDERER_TYPE } from '@pixi/constants'; -import { settings } from '@pixi/settings'; -import { Container } from '@pixi/display'; -import RenderTexture from '../textures/RenderTexture'; -import EventEmitter from 'eventemitter3'; - -const tempMatrix = new Matrix(); - -/** - * 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 EventEmitter - * @memberof PIXI - */ -export default class AbstractRenderer extends EventEmitter -{ - // eslint-disable-next-line valid-jsdoc - /** - * @param {string} system - The name of the system this renderer is for. - * @param {object} [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.transparent=false] - If the render view is transparent, default false - * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false - * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) - * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. The - * resolution of the renderer retina would be 2. - * @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 {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when rendering, - * stopping pixel interpolation. - */ - constructor(system, options, arg2, arg3) - { - super(); - - // Support for constructor(system, screenWidth, screenHeight, options) - if (typeof options === 'number') - { - options = Object.assign({ - width: options, - height: arg2 || settings.RENDER_OPTIONS.height, - }, arg3); - } - - // 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 = RENDERER_TYPE.UNKNOWN; - - /** - * Measurements of the screen. (0, 0, screenWidth, screenHeight) - * - * Its safe to use as filterArea or hitArea for 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 1 - */ - this.resolution = options.resolution || settings.RESOLUTION; - - /** - * Whether the render view is transparent - * - * @member {boolean} - */ - this.transparent = options.transparent; - - /** - * Whether css dimensions of canvas view should be resized to screen dimensions automatically - * - * @member {boolean} - */ - this.autoResize = options.autoResize || false; - - /** - * Tracks the blend modes useful for this renderer. - * - * @member {object} - */ - this.blendModes = null; - - /** - * 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; - - /** - * If true PixiJS will Math.floor() x/y values when rendering, stopping pixel interpolation. - * Handy for crisp pixel art and speed on legacy devices. - * - * @member {boolean} - */ - this.roundPixels = options.roundPixels; - - /** - * The background color as a number. - * - * @member {number} - * @private - */ - this._backgroundColor = 0x000000; - - /** - * The background color as an [R, G, B] array. - * - * @member {number[]} - * @private - */ - this._backgroundColorRgba = [0, 0, 0, 0]; - - /** - * The background color as a string. - * - * @member {string} - * @private - */ - this._backgroundColorString = '#000000'; - - this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter - - /** - * This temporary display object used as the parent of the currently being rendered item - * - * @member {PIXI.DisplayObject} - * @private - */ - this._tempDisplayObjectParent = new Container(); - - /** - * The last root object that the renderer tried to render. - * - * @member {PIXI.DisplayObject} - * @private - */ - this._lastObjectRendered = this._tempDisplayObjectParent; - - /** - * Collection of plugins. - * @readonly - * @member {object} - */ - this.plugins = {}; - } - - /** - * Initialize the plugins. - * - * @protected - * @param {object} staticMap - The dictionary of staticly saved plugins. - */ - initPlugins(staticMap) - { - 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() - { - return this.view.width; - } - - /** - * Same as view.height, actual number of pixels in the canvas by vertical - * - * @member {number} - * @readonly - * @default 600 - */ - get height() - { - return this.view.height; - } - - /** - * Resizes the screen and canvas to the specified width and height - * Canvas dimensions are multiplied by resolution - * - * @param {number} screenWidth - the new width of the screen - * @param {number} screenHeight - the new height of the screen - */ - resize(screenWidth, screenHeight) - { - this.screen.width = screenWidth; - this.screen.height = screenHeight; - - this.view.width = screenWidth * this.resolution; - this.view.height = screenHeight * this.resolution; - - if (this.autoResize) - { - this.view.style.width = `${screenWidth}px`; - this.view.style.height = `${screenHeight}px`; - } - } - - /** - * 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 {PIXI.DisplayObject} displayObject - The displayObject the object will be generated from - * @param {number} scaleMode - Should be one of the scaleMode consts - * @param {number} resolution - The resolution / device pixel ratio of the texture being generated - * @param {PIXI.Rectangle} [region] - The region of the displayObject, that shall be rendered, - * if no region is specified, defaults to the local bounds of the displayObject. - * @return {PIXI.Texture} a texture of the graphics object - */ - generateTexture(displayObject, scaleMode, resolution, region) - { - region = region || displayObject.getLocalBounds(); - - // 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(region.width | 0, region.height | 0, scaleMode, resolution); - - tempMatrix.tx = -region.x; - tempMatrix.ty = -region.y; - - this.render(displayObject, renderTexture, false, tempMatrix, true); - - return renderTexture; - } - - /** - * Removes everything from the renderer and optionally removes the Canvas DOM element. - * - * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. - */ - destroy(removeView) - { - 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); - } - - this.plugins = null; - - this.type = RENDERER_TYPE.UNKNOWN; - - this.view = null; - - this.screen = null; - - this.resolution = 0; - - this.transparent = false; - - this.autoResize = false; - - this.blendModes = null; - - this.options = null; - - this.preserveDrawingBuffer = false; - this.clearBeforeRender = false; - - this.roundPixels = false; - - this._backgroundColor = 0; - this._backgroundColorRgba = null; - this._backgroundColorString = null; - - this._tempDisplayObjectParent = null; - this._lastObjectRendered = null; - } - - /** - * The background color to fill if not transparent - * - * @member {number} - */ - get backgroundColor() - { - return this._backgroundColor; - } - - set backgroundColor(value) // eslint-disable-line require-jsdoc - { - this._backgroundColor = value; - this._backgroundColorString = hex2string(value); - hex2rgb(value, this._backgroundColorRgba); - } -} diff --git a/packages/core/src/renderers/Renderer.js b/packages/core/src/renderers/Renderer.js deleted file mode 100644 index a2020b1..0000000 --- a/packages/core/src/renderers/Renderer.js +++ /dev/null @@ -1,329 +0,0 @@ -import AbstractRenderer from './AbstractRenderer'; -import { sayHello } from '@pixi/utils'; -import MaskSystem from './systems/MaskSystem'; -import StencilSystem from './systems/StencilSystem'; -import FilterSystem from './systems/filter/FilterSystem'; -import FramebufferSystem from './systems/FramebufferSystem'; -import RenderTextureSystem from './systems/RenderTextureSystem'; -import TextureSystem from './systems/textures/TextureSystem'; -import ProjectionSystem from './systems/ProjectionSystem'; -import StateSystem from './systems/StateSystem'; -import GeometrySystem from './systems/geometry/GeometrySystem'; -import ShaderSystem from './systems/shader/ShaderSystem'; -import ContextSystem from './systems/ContextSystem'; -import BatchSystem from './systems/BatchSystem'; -import TextureGCSystem from './systems/textures/TextureGCSystem'; -import { RENDERER_TYPE } from '@pixi/constants'; -import UniformGroup from '../shader/UniformGroup'; -import { Matrix } from '@pixi/math'; -import Runner from 'mini-runner'; - -/** - * 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 Render works by automatically managing webGLBatchs. - * 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 :) - * - * @class - * @memberof PIXI - * @extends PIXI.AbstractRenderer - */ -export default class Renderer extends AbstractRenderer -{ - // eslint-disable-next-line valid-jsdoc - /** - * - * @param {object} [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.transparent=false] - If the render view is transparent, default false - * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false - * @param {boolean} [options.antialias=false] - sets antialias. If not available natively then FXAA - * antialiasing is used - * @param {boolean} [options.forceFXAA=false] - forces FXAA antialiasing to be used over native. - * FXAA is faster, but may not always look as great - * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. - * The resolution of the renderer retina would be 2. - * @param {boolean} [options.clearBeforeRender=true] - This sets if the renderer will clear - * the canvas or not before the new render pass. If you wish to set this to false, you *must* set - * preserveDrawingBuffer to `true`. - * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, - * enable this if you need to call toDataUrl on the webgl context. - * @param {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when - * rendering, stopping pixel interpolation. - * @param {number} [options.backgroundColor=0x000000] - The background color of the rendered area - * (shown if not transparent). - * @param {boolean} [options.legacy=false] - If true PixiJS will aim to ensure compatibility - * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. - * @param {string} [options.powerPreference] - Parameter passed to webgl context, set to "high-performance" - * for devices with dual graphics card - */ - constructor(options, arg2, arg3) - { - super('WebGL', options, arg2, arg3); - - /** - * The type of this renderer as a standardised const - * - * @member {number} - * @see PIXI.RENDERER_TYPE - */ - this.type = RENDERER_TYPE.WEBGL; - - // this will be set by the contextSystem (this.context) - this.gl = null; - this.CONTEXT_UID = 0; - this.legacy = !!options.legacy; - - // TODO legacy! - - // runners! - this.runners = { - destroy: new Runner('destroy'), - contextChange: new Runner('contextChange', 1), - reset: new Runner('reset'), - update: new Runner('update'), - postrender: new Runner('postrender'), - prerender: new Runner('prerender'), - resize: new Runner('resize', 2), - }; - - 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(GeometrySystem, 'geometry') - .addSystem(FramebufferSystem, 'framebuffer') - .addSystem(StencilSystem, 'stencil') - .addSystem(ProjectionSystem, 'projection') - .addSystem(TextureGCSystem, 'textureGC') - .addSystem(FilterSystem, 'filter') - .addSystem(RenderTextureSystem, 'renderTexture') - .addSystem(BatchSystem, 'batch'); - - this.initPlugins(Renderer.__plugins); - - /** - * The options passed in to create a new webgl context. - * - * @member {object} - * @private - */ - if (options.context) - { - this.context.initFromContext(options.context); - } - else - { - this.context.initFromOptions({ - alpha: this.transparent, - antialias: options.antialias, - premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', - stencil: true, - 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); - } - - addSystem(_class, name) - { - if (!name) - { - name = _class.name; - } - - // TODO - read name from class.name.. - - /* - if(name.includes('System')) - { - name = name.replace('System', ''); - name = name.charAt(0).toLowerCase() + name.slice(1); - } - */ - - const system = new _class(this); - - if (this[name]) - { - throw new Error(`Whoops! ${name} is already a manger`); - } - - this[name] = system; - - for (const i in this.runners) - { - this.runners[i].add(system); - } - - return this; - - /** - * 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. - */ - } - - /** - * Renders the object to its webGL view - * - * @param {PIXI.DisplayObject} displayObject - the object to be rendered - * @param {PIXI.RenderTexture} renderTexture - The render texture to render to. - * @param {boolean} [clear] - Should the canvas be cleared before the new render - * @param {PIXI.Transform} [transform] - A transform to apply to the render texture before rendering. - * @param {boolean} [skipUpdateTransform] - Should we skip the update transform pass? - */ - render(displayObject, renderTexture, clear, transform, skipUpdateTransform) - { - // can be handy to know! - this.renderingToScreen = !renderTexture; - - this.runners.prerender.run(); - this.emit('prerender'); - - // 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.parent; - - displayObject.parent = this._tempDisplayObjectParent; - displayObject.updateTransform(); - displayObject.parent = 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.run(); - - this.emit('postrender'); - } - - /** - * Resizes the webGL view to the specified width and height. - * - * @param {number} screenWidth - the new width of the screen - * @param {number} screenHeight - the new height of the screen - */ - resize(screenWidth, screenHeight) - { - AbstractRenderer.prototype.resize.call(this, screenWidth, screenHeight); - - this.runners.resize.run(screenWidth, screenHeight); - } - - /** - * Resets the WebGL state so you can render things however you fancy! - * - * @return {PIXI.Renderer} Returns itself. - */ - reset() - { - this.runners.reset.run(); - - return this; - } - - clear() - { - this.framebuffer.bind(); - this.framebuffer.clear(); - } - - /** - * Removes everything from the renderer (event listeners, spritebatch, etc...) - * - * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. - * See: https://github.com/pixijs/pixi.js/issues/2233 - */ - destroy(removeView) - { - this.runners.destroy.run(); - - // call base destroy - super.destroy(removeView); - - // TODO nullify all the managers.. - this.gl = null; - } - - /** - * 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 - * builds and excluding plugins. - * @name PIXI.Renderer#plugins - * @type {object} - * @readonly - * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. - * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. - * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. - * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. - */ - - /** - * Adds a plugin to the renderer. - * - * @method - * @param {string} pluginName - The name of the plugin. - * @param {Function} ctor - The constructor function or class for the plugin. - */ - static registerPlugin(pluginName, ctor) - { - Renderer.__plugins = Renderer.__plugins || {}; - Renderer.__plugins[pluginName] = ctor; - } -} diff --git a/packages/core/src/renderers/State.js b/packages/core/src/renderers/State.js deleted file mode 100644 index fa3f221..0000000 --- a/packages/core/src/renderers/State.js +++ /dev/null @@ -1,163 +0,0 @@ -/* eslint-disable max-len */ - -const BLEND = 0; -const OFFSET = 1; -const CULLING = 2; -const DEPTH_TEST = 3; -const WINDING = 4; - -/** - * This is a webGL state. It is passed The WebGL StateManager. - * Each mesh renderered may require webGL to be in a different state. - * For example you may want different blend mode or to enable polygon offsets - * - * @class - * @memberof PIXI - */ -export default class State -{ - /** - * - */ - constructor() - { - this.data = 0; - - this.blendMode = 0; - this.polygonOffset = 0; - - this.blend = true; - // this.depthTest = true; - } - - /** - * Activates blending of the computed fragment color values - * - * @member {boolean} - */ - get blend() - { - return !!(this.data & (1 << BLEND)); - } - - set blend(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << BLEND)) !== value) - { - this.data ^= (1 << BLEND); - } - } - - /** - * Activates adding an offset to depth values of polygon's fragments - * - * @member {boolean} - * @default false - */ - get offsets() - { - return !!(this.data & (1 << OFFSET)); - } - - set offsets(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << OFFSET)) !== value) - { - this.data ^= (1 << OFFSET); - } - } - - /** - * Activates culling of polygons. - * - * @member {boolean} - * @default false - */ - get culling() - { - return !!(this.data & (1 << CULLING)); - } - - set culling(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << CULLING)) !== value) - { - this.data ^= (1 << CULLING); - } - } - - /** - * Activates depth comparisons and updates to the depth buffer. - * - * @member {boolean} - * @default false - */ - get depthTest() - { - return !!(this.data & (1 << DEPTH_TEST)); - } - - set depthTest(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << DEPTH_TEST)) !== value) - { - this.data ^= (1 << DEPTH_TEST); - } - } - - /** - * Specifies whether or not front or back-facing polygons can be culled. - * @member {boolean} - * @default false - */ - get clockwiseFrontFace() - { - return !!(this.data & (1 << WINDING)); - } - - set clockwiseFrontFace(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << WINDING)) !== value) - { - this.data ^= (1 << WINDING); - } - } - - /** - * The blend mode to be applied when this state is set. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. - * Setting this mode to anything other than NO_BLEND will automatically switch blending on. - * - * @member {boolean} - * @default PIXI.BLEND_MODES.NORMAL - * @see PIXI.BLEND_MODES - */ - get blendMode() - { - return this._blendMode; - } - - set blendMode(value) // eslint-disable-line require-jsdoc - { - // 17 is NO BLEND - this.blend = (value !== 17); - this._blendMode = value; - } - - /** - * The polygon offset. Setting this property to anything other than 0 will automatically enable poygon offset fill. - * - * @member {number} - * @default 0 - */ - get polygonOffset() - { - return this._polygonOffset; - } - - set polygonOffset(value) // eslint-disable-line require-jsdoc - { - this.offsets = !!value; - this._polygonOffset = value; - } -} - diff --git a/packages/core/src/renderers/filters/Filter.js b/packages/core/src/renderers/filters/Filter.js deleted file mode 100644 index 2073b8d..0000000 --- a/packages/core/src/renderers/filters/Filter.js +++ /dev/null @@ -1,140 +0,0 @@ -import Shader from '../../shader/Shader'; -import Program from '../../shader/Program'; -import State from '../State'; -import { settings } from '@pixi/settings'; -// import extractUniformsFromSrc from './extractUniformsFromSrc'; -import defaultVertex from './defaultFilter.vert'; -import defaultFragment from './defaultFilter.frag'; - -// let math = require('../../math'); -/** - * @class - * @memberof PIXI - * @extends PIXI.Shader - */ -export default class Filter extends Shader -{ - /** - * @param {string} [vertexSrc] - The source of the vertex shader. - * @param {string} [fragmentSrc] - The source of the fragment shader. - * @param {object} [uniforms] - Custom uniforms to use to augment the built-in ones. - */ - constructor(vertexSrc, fragmentSrc, uniforms) - { - const program = Program.from(vertexSrc, fragmentSrc); - - super(program, uniforms); - - /** - * The padding of the filter. Some filters require extra space to breath such as a blur. - * Increasing this will add extra width and height to the bounds of the object that the - * filter is applied to. - * - * @member {number} - */ - this.padding = 0; - - /** - * The resolution of the filter. Setting this to be lower will lower the quality but - * increase the performance of the filter. - * - * @member {number} - */ - this.resolution = settings.RESOLUTION; - - /** - * If enabled is true the filter is applied, if false it will not. - * - * @member {boolean} - */ - this.enabled = true; - - /** - * If enabled, PixiJS will fit the filter area into boundaries for better performance. - * Switch it off if it does not work for specific shader. - * - * @member {boolean} - */ - this.autoFit = true; - - /** - * Legacy filters use position and uvs from attributes - * @member {boolean} - * @readonly - */ - this.legacy = !!this.program.attributeData.aTextureCoord; - - /** - * the webGL state the filter requires to render - * @member {PIXI.State} - */ - this.state = new State(); - } - - /** - * Applies the filter - * - * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from - * @param {PIXI.RenderTarget} input - The input render target. - * @param {PIXI.RenderTarget} output - The target to output to. - * @param {boolean} clear - Should the output be cleared before rendering to it - * @param {object} [currentState] - It's current state of filter. - * There are some useful properties in the currentState : - * target, filters, sourceFrame, destinationFrame, renderTarget, resolution - */ - apply(filterManager, input, output, clear, currentState, derp) // eslint-disable-line no-unused-vars - { - // do as you please! - - filterManager.applyFilter(this, input, output, clear, currentState, derp); - - // or just do a regular render.. - } - - /** - * Sets the blendmode of the filter - * - * @member {number} - * @default PIXI.BLEND_MODES.NORMAL - */ - get blendMode() - { - return this.state.blendMode; - } - - set blendMode(value) // eslint-disable-line require-jsdoc - { - this.state.blendMode = value; - } - - /** - * The default vertex shader source - * - * @static - * @constant - */ - static get defaultVertexSrc() - { - return defaultVertex; - } - - /** - * The default fragment shader source - * - * @static - * @constant - */ - static get defaultFragmentSrc() - { - return defaultFragment; - } -} - -/** - * Used for caching shader IDs - * - * @static - * @private - */ -Filter.SOURCE_KEY_MAP = {}; - diff --git a/packages/core/src/renderers/filters/defaultFilter.frag b/packages/core/src/renderers/filters/defaultFilter.frag deleted file mode 100644 index a14c1be..0000000 --- a/packages/core/src/renderers/filters/defaultFilter.frag +++ /dev/null @@ -1,22 +0,0 @@ -varying vec2 vTextureCoord; -varying vec2 vFilterCoord; - -uniform sampler2D uSampler; -uniform sampler2D filterSampler; - -void main(void){ - vec4 masky = texture2D(filterSampler, vFilterCoord); - vec4 sample = texture2D(uSampler, vTextureCoord); - vec4 color; - if(mod(vFilterCoord.x, 1.0) > 0.5) - { - color = vec4(1.0, 0.0, 0.0, 1.0); - } - else - { - color = vec4(0.0, 1.0, 0.0, 1.0); - } - // gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0); - gl_FragColor = mix(sample, masky, 0.5); - gl_FragColor *= sample.a; -} diff --git a/packages/core/src/renderers/filters/defaultFilter.vert b/packages/core/src/renderers/filters/defaultFilter.vert deleted file mode 100644 index e1febca..0000000 --- a/packages/core/src/renderers/filters/defaultFilter.vert +++ /dev/null @@ -1,14 +0,0 @@ -attribute vec2 aVertexPosition; -attribute vec2 aTextureCoord; - -uniform mat3 projectionMatrix; -uniform mat3 filterMatrix; - -varying vec2 vTextureCoord; -varying vec2 vFilterCoord; - -void main(void){ - gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); - vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy; - vTextureCoord = aTextureCoord ; -} diff --git a/packages/core/src/renderers/filters/filterTransforms.js b/packages/core/src/renderers/filters/filterTransforms.js deleted file mode 100644 index 87a3cd6..0000000 --- a/packages/core/src/renderers/filters/filterTransforms.js +++ /dev/null @@ -1,50 +0,0 @@ -import { Matrix } from '@pixi/math'; - -/** - * Calculates the mapped matrix - * @param filterArea {Rectangle} The filter area - * @param sprite {Sprite} the target sprite - * @param outputMatrix {Matrix} @alvin - * @private - */ -// this returns a matrix that will normalise map filter cords in the filter to screen space -export function calculateScreenSpaceMatrix(outputMatrix, filterArea, textureSize) -{ - // TODO unwrap? - const mappedMatrix = outputMatrix.identity(); - - mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); - - mappedMatrix.scale(textureSize.width, textureSize.height); - - return mappedMatrix; -} - -export function calculateNormalizedScreenSpaceMatrix(outputMatrix, filterArea, textureSize) -{ - const mappedMatrix = outputMatrix.identity(); - - mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); - - const translateScaleX = (textureSize.width / filterArea.width); - const translateScaleY = (textureSize.height / filterArea.height); - - mappedMatrix.scale(translateScaleX, translateScaleY); - - return mappedMatrix; -} - -// this will map the filter coord so that a texture can be used based on the transform of a sprite -export function calculateSpriteMatrix(outputMatrix, filterArea, textureSize, sprite) -{ - const orig = sprite._texture.orig; - const mappedMatrix = outputMatrix.set(textureSize.width, 0, 0, textureSize.height, filterArea.x, filterArea.y); - const worldTransform = sprite.worldTransform.copyTo(Matrix.TEMP_MATRIX); - - worldTransform.invert(); - mappedMatrix.prepend(worldTransform); - mappedMatrix.scale(1.0 / orig.width, 1.0 / orig.height); - mappedMatrix.translate(sprite.anchor.x, sprite.anchor.y); - - return mappedMatrix; -} diff --git a/packages/core/src/renderers/filters/spriteMask/SpriteMaskFilter.js b/packages/core/src/renderers/filters/spriteMask/SpriteMaskFilter.js deleted file mode 100644 index d5a91a4..0000000 --- a/packages/core/src/renderers/filters/spriteMask/SpriteMaskFilter.js +++ /dev/null @@ -1,63 +0,0 @@ -import Filter from '../Filter'; -import { Matrix } from '@pixi/math'; -import vertex from './spriteMaskFilter.vert'; -import fragment from './spriteMaskFilter.frag'; -import { default as TextureMatrix } from '../../../textures/TextureMatrix'; - -/** - * The SpriteMaskFilter class - * - * @class - * @extends PIXI.Filter - * @memberof PIXI - */ -export default class SpriteMaskFilter extends Filter -{ - /** - * @param {PIXI.Sprite} sprite - the target sprite - */ - constructor(sprite) - { - const maskMatrix = new Matrix(); - - super(vertex, fragment); - - sprite.renderable = false; - - this.maskSprite = sprite; - this.maskMatrix = maskMatrix; - } - - /** - * Applies the filter - * - * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from - * @param {PIXI.RenderTarget} input - The input render target. - * @param {PIXI.RenderTarget} output - The target to output to. - */ - apply(filterManager, input, output) - { - const maskSprite = this.maskSprite; - const tex = this.maskSprite.texture; - - if (!tex.valid) - { - return; - } - if (!tex.transform) - { - // margin = 0.0, let it bleed a bit, shader code becomes easier - // assuming that atlas textures were made with 1-pixel padding - tex.transform = new TextureMatrix(tex, 0.0); - } - tex.transform.update(); - - this.uniforms.mask = tex; - this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite) - .prepend(tex.transform.mapCoord); - this.uniforms.alpha = maskSprite.worldAlpha; - this.uniforms.maskClamp = tex.transform.uClampFrame; - - filterManager.applyFilter(this, input, output); - } -} diff --git a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.frag b/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.frag deleted file mode 100644 index 0e4aef8..0000000 --- a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.frag +++ /dev/null @@ -1,23 +0,0 @@ -varying vec2 vMaskCoord; -varying vec2 vTextureCoord; - -uniform sampler2D uSampler; -uniform sampler2D mask; -uniform float alpha; -uniform vec4 maskClamp; - -void main(void) -{ - float clip = step(3.5, - step(maskClamp.x, vMaskCoord.x) + - step(maskClamp.y, vMaskCoord.y) + - step(vMaskCoord.x, maskClamp.z) + - step(vMaskCoord.y, maskClamp.w)); - - vec4 original = texture2D(uSampler, vTextureCoord); - vec4 masky = texture2D(mask, vMaskCoord); - - original *= (masky.r * masky.a * alpha * clip); - - gl_FragColor = original; -} diff --git a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.vert b/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.vert deleted file mode 100644 index d23a30c..0000000 --- a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.vert +++ /dev/null @@ -1,16 +0,0 @@ -attribute vec2 aVertexPosition; -attribute vec2 aTextureCoord; - -uniform mat3 projectionMatrix; -uniform mat3 otherMatrix; - -varying vec2 vMaskCoord; -varying vec2 vTextureCoord; - -void main(void) -{ - gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); - - vTextureCoord = aTextureCoord; - vMaskCoord = ( otherMatrix * vec3( aTextureCoord, 1.0) ).xy; -} diff --git a/packages/core/src/renderers/systems/BatchSystem.js b/packages/core/src/renderers/systems/BatchSystem.js deleted file mode 100644 index 70e9518..0000000 --- a/packages/core/src/renderers/systems/BatchSystem.js +++ /dev/null @@ -1,68 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import ObjectRenderer from '../utils/ObjectRenderer'; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ - -export default class BatchSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this manager works for. - */ - constructor(renderer) - { - super(renderer); - - /** - * An empty renderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.emptyRenderer = new ObjectRenderer(renderer); - - /** - * The currently active ObjectRenderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.currentRenderer = this.emptyRenderer; - } - - /** - * Changes the current renderer to the one given in parameter - * - * @param {PIXI.ObjectRenderer} objectRenderer - The object renderer to use. - */ - setObjectRenderer(objectRenderer) - { - if (this.currentRenderer === objectRenderer) - { - return; - } - - this.currentRenderer.stop(); - this.currentRenderer = objectRenderer; - - this.renderer.state.setState(objectRenderer.state); - - this.currentRenderer.start(); - } - - /** - * This should be called if you wish to do some custom rendering - * It will basically render anything that may be batched up such as sprites - * - */ - flush() - { - this.setObjectRenderer(this.emptyRenderer); - } - - reset() - { - this.setObjectRenderer(this.emptyRenderer); - } -} diff --git a/packages/core/src/renderers/systems/ContextSystem.js b/packages/core/src/renderers/systems/ContextSystem.js deleted file mode 100644 index 43bf827..0000000 --- a/packages/core/src/renderers/systems/ContextSystem.js +++ /dev/null @@ -1,186 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { settings } from '@pixi/settings'; - -let CONTEXT_UID = 0; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class ContextSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this.webGLVersion = 1; - - this.handleContextLost = this.handleContextLost.bind(this); - this.handleContextRestored = this.handleContextRestored.bind(this); - - this.extensions = {}; - - renderer.view.addEventListener('webglcontextlost', this.handleContextLost, false); - renderer.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); - } - - get isLost() - { - return (!this.gl || this.gl.isContextLost()); - } - - contextChange(gl) - { - this.gl = gl; - - // restore a context if it was previously lost - if (gl.isContextLost() && gl.getExtension('WEBGL_lose_context')) - { - gl.getExtension('WEBGL_lose_context').restoreContext(); - } - } - - initFromContext(gl) - { - this.gl = gl; - this.validateContext(gl); - this.renderer.gl = gl; - this.renderer.CONTEXT_UID = CONTEXT_UID++; - this.renderer.runners.contextChange.run(gl); - } - - initFromOptions(options) - { - const gl = this.createContext(this.renderer.view, options); - - this.initFromContext(gl); - } - - /** - * Helper class to create a webGL Context - * - * @class - * @memberof PIXI.glCore - * @param canvas {HTMLCanvasElement} the canvas element that we will get the context from - * @param options {Object} An options object that gets passed in to the canvas element containing the context attributes, - * see https://developer.mozilla.org/en/docs/Web/API/HTMLCanvasElement/getContext for the options available - * @return {WebGLRenderingContext} the WebGL context - */ - createContext(canvas, options) - { - let gl; - - if (settings.PREFER_WEBGL_2) - { - gl = canvas.getContext('webgl2', options); - } - - if (gl) - { - this.webGLVersion = 2; - } - else - { - this.webGLVersion = 1; - - gl = canvas.getContext('webgl', options) - || canvas.getContext('experimental-webgl', options); - - if (!gl) - { - // fail, not able to get a context - throw new Error('This browser does not support webGL. Try using the canvas renderer'); - } - } - - this.gl = gl; - - this.getExtensions(); - - return gl; - } - - getExtensions() - { - // time to set up default etensions that pixi uses.. - const gl = this.gl; - const extensions = this.extensions; - - if (this.webGLVersion === 1) - { - extensions.drawBuffers = gl.getExtension('WEBGL_draw_buffers'); - extensions.depthTexture = gl.getExtension('WEBKIT_WEBGL_depth_texture'); - extensions.floatTexture = gl.getExtension('OES_texture_float'); - extensions.loseContext = gl.getExtension('WEBGL_lose_context'); - - extensions.vertexArrayObject = gl.getExtension('OES_vertex_array_object') - || gl.getExtension('MOZ_OES_vertex_array_object') - || gl.getExtension('WEBKIT_OES_vertex_array_object'); - } - - // we don't use any specific WebGL 2 ones yet! - } - - /** - * Handles a lost webgl context - * - * @private - * @param {WebGLContextEvent} event - The context lost event. - */ - handleContextLost(event) - { - event.preventDefault(); - } - - /** - * Handles a restored webgl context - * - * @private - */ - handleContextRestored() - { - this.renderer.runners.contextChange.run(this.gl); - } - - destroy() - { - const view = this.renderer.view; - - // remove listeners - view.removeEventListener('webglcontextlost', this.handleContextLost); - view.removeEventListener('webglcontextrestored', this.handleContextRestored); - - this.gl.useProgram(null); - - if (this.extensions.loseContext) - { - this.extensions.loseContext.loseContext(); - } - } - - postrender() - { - this.gl.flush(); - } - - validateContext(gl) - { - const attributes = gl.getContextAttributes(); - - // this is going to be fairly simple for now.. but at least we have room to grow! - if (!attributes.stencil) - { - /* eslint-disable max-len */ - - /* eslint-disable no-console */ - console.warn('Provided WebGL context does not have a stencil buffer, masks may not render correctly'); - /* eslint-enable no-console */ - - /* eslint-enable max-len */ - } - } -} diff --git a/packages/core/src/renderers/systems/FramebufferSystem.js b/packages/core/src/renderers/systems/FramebufferSystem.js deleted file mode 100644 index df7902b..0000000 --- a/packages/core/src/renderers/systems/FramebufferSystem.js +++ /dev/null @@ -1,246 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { Rectangle } from '@pixi/math'; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class FramebufferSystem extends WebGLSystem -{ - /** - * Sets up the renderer context and necessary buffers. - * - * @private - */ - contextChange() - { - this.gl = this.renderer.gl; - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - this.current = null; - this.viewport = new Rectangle(); - - this.drawBufferExtension = this.renderer.context.extensions.drawBuffers; - } - - bind(framebuffer, frame) - { - const gl = this.gl; - - this.current = framebuffer; - - if (framebuffer) - { - // TODO cacheing layer! - - const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID] || this.initFramebuffer(framebuffer); - - gl.bindFramebuffer(gl.FRAMEBUFFER, fbo.framebuffer); - // makesure all textures are unbound.. - - // now check for updates... - if (fbo.dirtyId !== framebuffer.dirtyId) - { - fbo.dirtyId = framebuffer.dirtyId; - - if (fbo.dirtyFormat !== framebuffer.dirtyFormat) - { - fbo.dirtyFormat = framebuffer.dirtyFormat; - this.updateFramebuffer(framebuffer); - } - else if (fbo.dirtySize !== framebuffer.dirtySize) - { - fbo.dirtySize = framebuffer.dirtySize; - this.resizeFramebuffer(framebuffer); - } - } - - for (let i = 0; i < framebuffer.colorTextures.length; i++) - { - if (framebuffer.colorTextures[i].texturePart) - { - this.renderer.texture.unbind(framebuffer.colorTextures[i].texture); - } - else - { - this.renderer.texture.unbind(framebuffer.colorTextures[i]); - } - } - - if (framebuffer.depthTexture) - { - this.renderer.texture.unbind(framebuffer.depthTexture); - } - - if (frame) - { - this.setViewport(frame.x, frame.y, frame.width, frame.height); - } - else - { - this.setViewport(0, 0, framebuffer.width, framebuffer.height); - } - } - else - { - gl.bindFramebuffer(gl.FRAMEBUFFER, null); - - if (frame) - { - this.setViewport(frame.x, frame.y, frame.width, frame.height); - } - else - { - this.setViewport(0, 0, this.renderer.width, this.renderer.height); - } - } - } - - setViewport(x, y, width, height) - { - const v = this.viewport; - - if (v.width !== width || v.height !== height || v.x !== x || v.y !== y) - { - v.x = x; - v.y = y; - v.width = width; - v.height = height; - - this.gl.viewport(x, y, width, height); - } - } - - get size() - { - if (this.current) - { - // TODO store temp - return { x: 0, y: 0, width: this.current.width, height: this.current.height }; - } - - return { x: 0, y: 0, width: this.renderer.width, height: this.renderer.height }; - } - - clear(r, g, b, a) - { - const gl = this.gl; - - // TODO clear color can be set only one right? - gl.clearColor(r, g, b, a); - gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); - } - - // private functions... - - initFramebuffer(framebuffer) - { - const gl = this.gl; - - // TODO - make this a class? - const fbo = { - framebuffer: gl.createFramebuffer(), - stencil: null, - dirtyId: 0, - dirtyFormat: 0, - dirtySize: 0, - }; - - framebuffer.glFrameBuffers[this.CONTEXT_UID] = fbo; - - return fbo; - } - - resizeFramebuffer(framebuffer) - { - const gl = this.gl; - - if (framebuffer.stencil || framebuffer.depth) - { - gl.bindRenderbuffer(gl.RENDERBUFFER, this.stencil); - gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); - } - } - - updateFramebuffer(framebuffer) - { - const gl = this.gl; - - const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID]; - - // bind the color texture - const colorTextures = framebuffer.colorTextures; - - let count = colorTextures.length; - - if (!this.drawBufferExtension) - { - count = Math.min(count, 1); - } - - const activeTextures = []; - - for (let i = 0; i < count; i++) - { - const texture = framebuffer.colorTextures[i]; - - if (texture.texturePart) - { - this.renderer.texture.bind(texture.texture, 0); - - gl.framebufferTexture2D(gl.FRAMEBUFFER, - gl.COLOR_ATTACHMENT0 + i, - gl.TEXTURE_CUBE_MAP_NEGATIVE_X + texture.side, - texture.texture._glTextures[this.CONTEXT_UID].texture, - 0); - } - else - { - this.renderer.texture.bind(texture, 0); - - gl.framebufferTexture2D(gl.FRAMEBUFFER, - gl.COLOR_ATTACHMENT0 + i, - gl.TEXTURE_2D, - texture._glTextures[this.CONTEXT_UID].texture, - 0); - } - - activeTextures.push(gl.COLOR_ATTACHMENT0 + i); - } - - if (this.drawBufferExtension && activeTextures.length > 1) - { - this.drawBufferExtension.drawBuffersWEBGL(activeTextures); - } - - if (framebuffer.depthTexture) - { - const depthTextureExt = this.renderer.context.extensions.depthTexture; - - if (depthTextureExt) - { - const depthTexture = framebuffer.depthTexture; - - this.renderer.texture.bind(depthTexture, 0); - - gl.framebufferTexture2D(gl.FRAMEBUFFER, - gl.DEPTH_ATTACHMENT, - gl.TEXTURE_2D, - depthTexture._glTextures[this.CONTEXT_UID].texture, - 0); - } - } - - if (framebuffer.stencil || framebuffer.depth) - { - fbo.stencil = gl.createRenderbuffer(); - - gl.bindRenderbuffer(gl.RENDERBUFFER, fbo.stencil); - - // TODO.. this is depth AND stencil? - gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, fbo.stencil); - gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); - // fbo.enableStencil(); - } - } -} diff --git a/packages/core/src/renderers/systems/MaskSystem.js b/packages/core/src/renderers/systems/MaskSystem.js deleted file mode 100644 index d9c4b1a..0000000 --- a/packages/core/src/renderers/systems/MaskSystem.js +++ /dev/null @@ -1,199 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import AlphaMaskFilter from '../filters/spriteMask/SpriteMaskFilter'; - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class MaskSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - // TODO - we don't need both! - this.scissor = false; - this.scissorData = null; - this.scissorRenderTarget = null; - - this.enableScissor = false; - - this.alphaMaskPool = []; - this.alphaMaskIndex = 0; - } - - /** - * Applies the Mask and adds it to the current filter stack. - * - * @param {PIXI.DisplayObject} target - Display Object to push the mask to - * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. - */ - push(target, maskData) - { - // TODO the root check means scissor rect will not - // be used on render textures more info here: - // https://github.com/pixijs/pixi.js/pull/3545 - - if (maskData.texture) - { - this.pushSpriteMask(target, maskData); - } - else if (this.enableScissor - && !this.scissor - && this.renderer._activeRenderTarget.root - && !this.renderer.stencil.stencilMaskStack.length - && maskData.isFastRect()) - { - const matrix = maskData.worldTransform; - - let rot = Math.atan2(matrix.b, matrix.a); - - // use the nearest degree! - rot = Math.round(rot * (180 / Math.PI)); - - if (rot % 90) - { - this.pushStencilMask(maskData); - } - else - { - this.pushScissorMask(target, maskData); - } - } - else - { - this.pushStencilMask(maskData); - } - } - - /** - * Removes the last mask from the mask stack and doesn't return it. - * - * @param {PIXI.DisplayObject} target - Display Object to pop the mask from - * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. - */ - pop(target, maskData) - { - if (maskData.texture) - { - this.popSpriteMask(target, maskData); - } - else if (this.enableScissor && !this.renderer.stencil.stencilMaskStack.length) - { - this.popScissorMask(target, maskData); - } - else - { - this.popStencilMask(target, maskData); - } - } - - /** - * Applies the Mask and adds it to the current filter stack. - * - * @param {PIXI.RenderTarget} target - Display Object to push the sprite mask to - * @param {PIXI.Sprite} maskData - Sprite to be used as the mask - */ - pushSpriteMask(target, maskData) - { - let alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; - - if (!alphaMaskFilter) - { - alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; - } - - alphaMaskFilter[0].resolution = this.renderer.resolution; - alphaMaskFilter[0].maskSprite = maskData; - - // TODO - may cause issues! - target.filterArea = maskData.getBounds(true); - - this.renderer.filterSystem.pushFilter(target, alphaMaskFilter); - - this.alphaMaskIndex++; - } - - /** - * Removes the last filter from the filter stack and doesn't return it. - * - */ - popSpriteMask() - { - this.renderer.filterSystem.popFilter(); - this.alphaMaskIndex--; - } - - /** - * Applies the Mask and adds it to the current filter stack. - * - * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. - */ - pushStencilMask(maskData) - { - this.renderer.batch.flush(); - this.renderer.stencil.pushStencil(maskData); - } - - /** - * Removes the last filter from the filter stack and doesn't return it. - * - */ - popStencilMask() - { - // this.renderer.currentRenderer.stop(); - this.renderer.stencil.popStencil(); - } - - /** - * - * @param {PIXI.DisplayObject} target - Display Object to push the mask to - * @param {PIXI.Graphics} maskData - The masking data. - */ - pushScissorMask(target, maskData) - { - maskData.renderable = true; - - const renderTarget = this.renderer._activeRenderTarget; - - const bounds = maskData.getBounds(); - - bounds.fit(renderTarget.size); - maskData.renderable = false; - - this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); - - const resolution = this.renderer.resolution; - - this.renderer.gl.scissor( - bounds.x * resolution, - (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, - bounds.width * resolution, - bounds.height * resolution - ); - - this.scissorRenderTarget = renderTarget; - this.scissorData = maskData; - this.scissor = true; - } - - /** - * - * - */ - popScissorMask() - { - this.scissorRenderTarget = null; - this.scissorData = null; - this.scissor = false; - - // must be scissor! - const gl = this.renderer.gl; - - gl.disable(gl.SCISSOR_TEST); - } -} diff --git a/packages/core/src/renderers/systems/ProjectionSystem.js b/packages/core/src/renderers/systems/ProjectionSystem.js deleted file mode 100644 index ec5efdb..0000000 --- a/packages/core/src/renderers/systems/ProjectionSystem.js +++ /dev/null @@ -1,73 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { Matrix } from '@pixi/math'; - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ - -export default class ProjectionSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this.projectionMatrix = new Matrix(); - } - - update(destinationFrame, sourceFrame, resolution, root) - { - this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; - this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; - - this.calculateProjection(this.destinationFrame, this.sourceFrame, resolution, root); - - this.renderer.globalUniforms.uniforms.projectionMatrix = this.projectionMatrix; - this.renderer.globalUniforms.update(); - } - - /** - * Updates the projection matrix based on a projection frame (which is a rectangle) - * - * @param {Rectangle} destinationFrame - The destination frame. - * @param {Rectangle} sourceFrame - The source frame. - */ - calculateProjection(destinationFrame, sourceFrame, resolution, root) - { - const pm = this.projectionMatrix; - - // I don't think we will need this line.. - // pm.identity(); - - if (!root) - { - pm.a = (1 / destinationFrame.width * 2) * resolution; - pm.d = (1 / destinationFrame.height * 2) * resolution; - - pm.tx = -1 - (sourceFrame.x * pm.a); - pm.ty = -1 - (sourceFrame.y * pm.d); - } - else - { - pm.a = (1 / destinationFrame.width * 2) * resolution; - pm.d = (-1 / destinationFrame.height * 2) * resolution; - - pm.tx = -1 - (sourceFrame.x * pm.a); - pm.ty = 1 - (sourceFrame.y * pm.d); - } - } - - /** - * Sets the transform of the active render target to the given matrix - * - * @param {PIXI.Matrix} matrix - The transformation matrix - */ - setTransform()// matrix) - { - // this._activeRenderTarget.transform = matrix; - } -} diff --git a/packages/core/src/renderers/systems/RenderTextureSystem.js b/packages/core/src/renderers/systems/RenderTextureSystem.js deleted file mode 100644 index a133ef1..0000000 --- a/packages/core/src/renderers/systems/RenderTextureSystem.js +++ /dev/null @@ -1,115 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { Rectangle } from '@pixi/math'; - -const tempRect = new Rectangle(); - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ - -export default class RenderTextureSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this.clearColor = renderer._backgroundColorRgba; - - // TODO moe this property somewhere else! - this.defaultMaskStack = []; - this.defaultFilterStack = [{}]; - - // empty render texture? - this.renderTexture = null; - - this.destinationFrame = new Rectangle(); - } - - bind(renderTexture, sourceFrame, destinationFrame) - { - // TODO - do we want this?? - if (this.renderTexture === renderTexture) return; - this.renderTexture = renderTexture; - - const renderer = this.renderer; - - if (renderTexture) - { - const baseTexture = renderTexture.baseTexture; - - if (!destinationFrame) - { - tempRect.width = baseTexture.realWidth; - tempRect.height = baseTexture.realHeight; - - destinationFrame = tempRect; - } - - if (!sourceFrame) - { - sourceFrame = destinationFrame; - } - - this.renderer.framebuffer.bind(baseTexture.frameBuffer, destinationFrame); - - this.renderer.projection.update(destinationFrame, sourceFrame, baseTexture.resolution, false); - this.renderer.stencil.setMaskStack(baseTexture.stencilMaskStack); - } - else - { - // TODO these validation checks happen deeper down.. - // thing they can be avoided.. - if (!destinationFrame) - { - tempRect.width = renderer.width; - tempRect.height = renderer.height; - - destinationFrame = tempRect; - } - - if (!sourceFrame) - { - sourceFrame = destinationFrame; - } - - renderer.framebuffer.bind(null, destinationFrame); - - // TODO store this.. - this.renderer.projection.update(destinationFrame, sourceFrame, this.renderer.resolution, true); - this.renderer.stencil.setMaskStack(this.defaultMaskStack); - } - - this.destinationFrame.copyFrom(destinationFrame); - } - - /** - * Erases the render texture and fills the drawing area with a colour - * - * @param {number} [clearColor] - The colour - * @return {PIXI.Renderer} Returns itself. - */ - clear(clearColor) - { - if (this.renderTexture) - { - clearColor = clearColor || this.renderTexture.baseTexture.clearColor; - } - else - { - clearColor = clearColor || this.clearColor; - } - - this.renderer.framebuffer.clear(clearColor[0], clearColor[1], clearColor[2], clearColor[3]); - } - - resize()// screenWidth, screenHeight) - { - // resize the root only! - this.bind(null); - } -} diff --git a/packages/core/src/renderers/systems/StateSystem.js b/packages/core/src/renderers/systems/StateSystem.js deleted file mode 100755 index d7f4caf..0000000 --- a/packages/core/src/renderers/systems/StateSystem.js +++ /dev/null @@ -1,288 +0,0 @@ -import mapWebGLBlendModesToPixi from '../utils/mapWebGLBlendModesToPixi'; -import WebGLSystem from './WebGLSystem'; -import WebGLState from '../State'; - -const BLEND = 0; -const OFFSET = 1; -const CULLING = 2; -const DEPTH_TEST = 3; -const WINDING = 4; - -/** - * A WebGL state machines - * - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class StateSystem extends WebGLSystem -{ - /** - * @param {WebGLRenderingContext} gl - The current WebGL rendering context - */ - constructor(renderer) - { - super(renderer); - - this.gl = null; - - this.maxAttribs = null; - - // check we have vao.. - this.nativeVaoExtension = null; - - this.attribState = null; - - this.stateId = 0; - this.polygonOffset = 0; - this.blendMode = 17; - - this.map = []; - - // map functions for when we set state.. - this.map[BLEND] = this.setBlend; - this.map[OFFSET] = this.setOffset; - this.map[CULLING] = this.setCullFace; - this.map[DEPTH_TEST] = this.setDepthTest; - this.map[WINDING] = this.setFrontFace; - - this.checks = []; - - this.defaultState = new WebGLState(); - this.defaultState.blend = true; - this.defaultState.depth = true; - } - - contextChange(gl) - { - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; - - this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); - - // check we have vao.. - this.nativeVaoExtension = ( - gl.getExtension('OES_vertex_array_object') - || gl.getExtension('MOZ_OES_vertex_array_object') - || gl.getExtension('WEBKIT_OES_vertex_array_object') - ); - - this.attribState = { - tempAttribState: new Array(this.maxAttribs), - attribState: new Array(this.maxAttribs), - }; - - this.blendModes = mapWebGLBlendModesToPixi(gl); - - this.setState(this.defaultState); - - this.reset(); - } - - /** - * Sets the current state - * - * @param {*} state - The state to set. - */ - setState(state) - { - state = state || this.defaultState; - - // TODO maybe to an object check? ( this.state === state )? - if (this.stateId !== state.data) - { - let diff = this.stateId ^ state.data; - let i = 0; - - // order from least to most common - while (diff) - { - if (diff & 1) - { - // state change! - this.map[i].call(this, !!(state.data & (1 << i))); - } - - diff = diff >> 1; - i++; - } - - this.stateId = state.data; - } - - // based on the above settings we check for specific modes.. - // for example if blend is active we check and set the blend modes - // or of polygon offset is active we check the poly depth. - for (let i = 0; i < this.checks.length; i++) - { - this.checks[i](this, state); - } - } - - /** - * Enables or disabled blending. - * - * @param {boolean} value - Turn on or off webgl blending. - */ - setBlend(value) - { - this.updateCheck(StateSystem.checkBlendMode, value); - - this.gl[value ? 'enable' : 'disable'](this.gl.BLEND); - } - - /** - * Enables or disable polygon offset fill - * - * @param {boolean} value - Turn on or off webgl polygon offset testing. - */ - setOffset(value) - { - this.gl[value ? 'enable' : 'disable'](this.gl.POLYGON_OFFSET_FILL); - } - - /** - * Sets whether to enable or disable depth test. - * - * @param {boolean} value - Turn on or off webgl depth testing. - */ - setDepthTest(value) - { - this.gl[value ? 'enable' : 'disable'](this.gl.DEPTH_TEST); - } - - /** - * Sets whether to enable or disable cull face. - * - * @param {boolean} value - Turn on or off webgl cull face. - */ - setCullFace(value) - { - this.gl[value ? 'enable' : 'disable'](this.gl.CULL_FACE); - } - - /** - * Sets the gl front face. - * - * @param {boolean} value - true is clockwise and false is counter-clockwise - */ - setFrontFace(value) - { - this.gl.frontFace(this.gl[value ? 'CW' : 'CCW']); - } - - /** - * Sets the blend mode. - * - * @param {number} value - The blend mode to set to. - */ - setBlendMode(value) - { - if (value === this.blendMode) - { - return; - } - - this.blendMode = value; - this.gl.blendFunc(this.blendModes[value][0], this.blendModes[value][1]); - } - - /** - * Sets the polygon offset. - * - * @param {number} value - the polygon offset - * @param {number} scale - the polygon offset scale - */ - setPolygonOffset(value, scale) - { - this.gl.polygonOffset(value, scale); - } - - /** - * Disables all the vaos in use - * - */ - resetAttributes() - { - for (let i = 0; i < this.attribState.tempAttribState.length; i++) - { - this.attribState.tempAttribState[i] = 0; - } - - for (let i = 0; i < this.attribState.attribState.length; i++) - { - this.attribState.attribState[i] = 0; - } - - // im going to assume one is always active for performance reasons. - for (let i = 1; i < this.maxAttribs; i++) - { - this.gl.disableVertexAttribArray(i); - } - } - - // used - /** - * Resets all the logic and disables the vaos - */ - reset() - { - // unbind any VAO if they exist.. - if (this.nativeVaoExtension) - { - this.nativeVaoExtension.bindVertexArrayOES(null); - } - - // reset all attributes.. - this.resetAttributes(); - - this.gl.pixelStorei(this.gl.UNPACK_FLIP_Y_WEBGL, false); - - this.setBlendMode(0); - - // TODO? - // this.setState(this.defaultState); - } - - /** - * checks to see which updates should be checked based on which settings have been activated - * for example if blend is enabled then we shold check the blend modes each time the state is changed - * or if poygon fill is activated then we need to check if the polygone offset changes. - * The idea is that we only check what we have too. - * - * @param {Function} func the checking function to add or remove - * @param {boolean} value should the check function be added or removed. - */ - updateCheck(func, value) - { - const index = this.checks.indexOf(func); - - if (value && index === -1) - { - this.checks.push(func); - } - else if (!value && index !== -1) - { - this.checks.splice(index, 1); - } - } - - /** - * A private little wrapper function that we call to check the blend mode. - * - * @static - * @private - * @param {PIXI.StateSystem} System the System to perform the state check on - * @param {PIXI.State} state the state that the blendMode will pulled from - */ - static checkBlendMode(system, state) - { - system.setBlendMode(state.blendMode); - } - - // TODO - add polygon offset? -} diff --git a/packages/core/src/renderers/systems/StencilSystem.js b/packages/core/src/renderers/systems/StencilSystem.js deleted file mode 100644 index 5d538cb..0000000 --- a/packages/core/src/renderers/systems/StencilSystem.js +++ /dev/null @@ -1,133 +0,0 @@ -import WebGLSystem from './WebGLSystem'; - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class StencilSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - this.stencilMaskStack = []; - } - - /** - * Changes the mask stack that is used by this System. - * - * @param {PIXI.Graphics[]} stencilMaskStack - The mask stack - */ - setMaskStack(stencilMaskStack) - { - const gl = this.renderer.gl; - - if (stencilMaskStack.length !== this.stencilMaskStack.length) - { - if (stencilMaskStack.length === 0) - { - gl.disable(gl.STENCIL_TEST); - } - else - { - gl.enable(gl.STENCIL_TEST); - } - } - - this.stencilMaskStack = stencilMaskStack; - } - - /** - * Applies the Mask and adds it to the current stencil stack. @alvin - * - * @param {PIXI.Graphics} graphics - The mask - */ - pushStencil(graphics) - { - this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); - - // this.renderer._activeRenderTarget.attachStencilBuffer(); - - const gl = this.renderer.gl; - const prevMaskCount = this.stencilMaskStack.length; - - if (prevMaskCount === 0) - { - gl.enable(gl.STENCIL_TEST); - } - - this.stencilMaskStack.push(graphics); - - // Increment the refference stencil value where the new mask overlaps with the old ones. - gl.colorMask(false, false, false, false); - gl.stencilFunc(gl.EQUAL, prevMaskCount, this._getBitwiseMask()); - gl.stencilOp(gl.KEEP, gl.KEEP, gl.INCR); - this.renderer.plugins.graphics.render(graphics); - - this._useCurrent(); - } - - /** - * Removes the last mask from the stencil stack. @alvin - */ - popStencil() - { - this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); - - const gl = this.renderer.gl; - const graphics = this.stencilMaskStack.pop(); - - if (this.stencilMaskStack.length === 0) - { - // the stack is empty! - gl.disable(gl.STENCIL_TEST); - gl.clear(gl.STENCIL_BUFFER_BIT); - gl.clearStencil(0); - } - else - { - // Decrement the refference stencil value where the popped mask overlaps with the other ones - gl.colorMask(false, false, false, false); - gl.stencilOp(gl.KEEP, gl.KEEP, gl.DECR); - this.renderer.plugins.graphics.render(graphics); - - this._useCurrent(); - } - } - - /** - * Setup renderer to use the current stencil data. - */ - _useCurrent() - { - const gl = this.renderer.gl; - - gl.colorMask(true, true, true, true); - gl.stencilFunc(gl.EQUAL, this.stencilMaskStack.length, this._getBitwiseMask()); - gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP); - } - - /** - * Fill 1s equal to the number of acitve stencil masks. - * - * @return {number} The bitwise mask. - */ - _getBitwiseMask() - { - return (1 << this.stencilMaskStack.length) - 1; - } - - /** - * Destroys the mask stack. - * - */ - destroy() - { - super.destroy(this); - - this.stencilMaskStack = null; - } -} diff --git a/packages/core/src/renderers/systems/WebGLSystem.js b/packages/core/src/renderers/systems/WebGLSystem.js deleted file mode 100644 index d3d5b31..0000000 --- a/packages/core/src/renderers/systems/WebGLSystem.js +++ /dev/null @@ -1,41 +0,0 @@ -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this manager works for. - */ - constructor(renderer) - { - /** - * The renderer this manager works for. - * - * @member {PIXI.Renderer} - */ - this.renderer = renderer; - - this.renderer.runners.contextChange.add(this); - } - - /** - * Generic method called when there is a WebGL context change. - * - */ - contextChange() - { - // do some codes init! - } - - /** - * Generic destroy methods to be overridden by the subclass - * - */ - destroy() - { - this.renderer.runners.contextChange.remove(this); - this.renderer = null; - } -} diff --git a/packages/core/src/renderers/systems/filter/FilterSystem.js b/packages/core/src/renderers/systems/filter/FilterSystem.js deleted file mode 100644 index e615037..0000000 --- a/packages/core/src/renderers/systems/filter/FilterSystem.js +++ /dev/null @@ -1,431 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; - -import RenderTexture from '../../../textures/RenderTexture'; -import Quad from '../../utils/Quad'; -import QuadUv from '../../utils/QuadUv'; -import { Rectangle } from '@pixi/math'; -import * as filterTransforms from '../../filters/filterTransforms'; -import bitTwiddle from 'bit-twiddle'; -import UniformGroup from '../../../shader/UniformGroup'; -import { DRAW_MODES } from '../../../../../constants'; - -// -/** - * @ignore - * @class - */ -class FilterState -{ - /** - * - */ - constructor() - { - this.renderTexture = null; - this.sourceFrame = new Rectangle(); - this.destinationFrame = new Rectangle(); - this.filters = []; - this.target = null; - this.legacy = false; - this.resolution = 1; - } -} - -/** - * @class - * @memberof PIXI.systems - * @extends PIXI.systems.WebGLSystem - */ -export default class FilterSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - /** - * stores a bunch of PO2 textures used for filtering - * @type {Object} - */ - this.texturePool = {}; - - /** - * a pool for storing filter states, save us creating new ones each tick - * @type {Array} - */ - this.statePool = []; - - /** - * A very simple geometry used when drawing a filter effect to the screen - * @type {Quad} - */ - this.quad = new Quad(); - - this.quadUv = new QuadUv(); - - /** - * Temporary rect for maths - * @type {PIXI.Rectangle} - */ - this.tempRect = new Rectangle(); - - this.activeState = {}; - - /** - * this uniform group is attached to filter uniforms when used - * @type {UniformGroup} - */ - this.globalUniforms = new UniformGroup({ - sourceFrame: this.tempRect, - destinationFrame: this.tempRect, - - // legacy variables - filterArea: new Float32Array(4), - filterClamp: new Float32Array(4), - }, true); - } - - /** - * Adds a new filter to the System. - * - * @param {PIXI.DisplayObject} target - The target of the filter to render. - * @param {PIXI.Filter[]} filters - The filters to apply. - */ - push(target, filters) - { - const renderer = this.renderer; - const filterStack = this.renderer.renderTexture.defaultFilterStack; - const state = this.statePool.pop() || new FilterState(); - - let resolution = filters[0].resolution; - let padding = filters[0].padding; - let autoFit = filters[0].autoFit; - let legacy = filters[0].legacy; - - for (let i = 1; i < filters.length; i++) - { - const filter = filters[i]; - - // lets use the lowest resolution.. - resolution = Math.min(resolution, filter.resolution); - // and the largest amount of padding! - padding = Math.max(padding, filter.padding); - // only auto fit if all filters are autofit - autoFit = autoFit || filter.autoFit; - - legacy = legacy || filter.legacy; - } - - filterStack.push(state); - - state.resolution = resolution; - - state.legacy = legacy; - - // round to whole number based on resolution - // TODO move that to the shader too? - state.sourceFrame = target.filterArea || target.getBounds(true); - - state.sourceFrame.pad(padding); - - if (autoFit) - { - state.sourceFrame.fit(this.renderer.renderTexture.destinationFrame); - } - - state.sourceFrame.round(resolution); - - state.renderTexture = this.getPotFilterTexture(state.sourceFrame.width, state.sourceFrame.height, resolution); - state.filters = filters; - - state.destinationFrame.width = state.renderTexture.width; - state.destinationFrame.height = state.renderTexture.height; - - state.renderTexture.filterFrame = state.sourceFrame; - renderer.renderTexture.bind(state.renderTexture, state.sourceFrame);// /, state.destinationFrame); - renderer.renderTexture.clear(); - } - - /** - * Pops off the filter and applies it. - * - */ - pop() - { - const renderer = this.renderer; - const filterStack = renderer.renderTexture.defaultFilterStack; - const state = filterStack.pop(); - const filters = state.filters; - - this.activeState = state; - - const globalUniforms = this.globalUniforms.uniforms; - - globalUniforms.sourceFrame = state.sourceFrame; - globalUniforms.destinationFrame = state.destinationFrame; - globalUniforms.resolution = state.resolution; - - // only update the rect if its legacy.. - if (state.legacy) - { - const filterArea = globalUniforms.filterArea; - const filterClamp = globalUniforms.filterClamp; - - filterArea[0] = state.destinationFrame.width; - filterArea[1] = state.destinationFrame.height; - filterArea[2] = state.sourceFrame.x; - filterArea[3] = state.sourceFrame.y; - - filterClamp[0] = 0.5 / state.resolution / state.destinationFrame.width; - filterClamp[1] = 0.5 / state.resolution / state.destinationFrame.height; - filterClamp[2] = (state.sourceFrame.width - 0.5) / state.resolution / state.destinationFrame.width; - filterClamp[3] = (state.sourceFrame.height - 0.5) / state.resolution / state.destinationFrame.height; - } - - this.globalUniforms.update(); - - const lastState = filterStack[filterStack.length - 1]; - - if (filters.length === 1) - { - filters[0].apply(this, state.renderTexture, lastState.renderTexture, false, state); - - this.returnFilterTexture(state.renderTexture); - } - else - { - let flip = state.renderTexture; - let flop = this.getPotFilterTexture( - flip.width, - flip.height, - state.resolution - ); - - flop.filterFrame = flip.filterFrame; - - let i = 0; - - for (i = 0; i < filters.length - 1; ++i) - { - filters[i].apply(this, flip, flop, true, state); - - const t = flip; - - flip = flop; - flop = t; - } - - filters[i].apply(this, flip, lastState.renderTexture, false, state); - - this.returnFilterTexture(flip); - this.returnFilterTexture(flop); - } - - this.statePool.push(state); - } - - /** - * Draws a filter. - * - * @param {PIXI.Filter} filter - The filter to draw. - * @param {PIXI.RenderTarget} input - The input render target. - * @param {PIXI.RenderTarget} output - The target to output to. - * @param {boolean} clear - Should the output be cleared before rendering to it - */ - applyFilter(filter, input, output, clear) - { - const renderer = this.renderer; - - renderer.renderTexture.bind(output, output ? output.filterFrame : null); - - if (clear) - { - // gl.disable(gl.SCISSOR_TEST); - renderer.renderTexture.clear(); - // gl.enable(gl.SCISSOR_TEST); - } - - // set the uniforms.. - filter.uniforms.uSampler = input; - filter.uniforms.filterGlobals = this.globalUniforms; - - // TODO make it so that the order of this does not matter.. - // because it does at the moment cos of global uniforms. - // they need to get resynced - - renderer.state.setState(filter.state); - renderer.shader.bind(filter); - - if (filter.legacy) - { - this.quadUv.map(input._frame, input.filterFrame); - - renderer.geometry.bind(this.quadUv); - renderer.geometry.draw(DRAW_MODES.TRIANGLES); - } - else - { - renderer.geometry.bind(this.quad); - renderer.geometry.draw(DRAW_MODES.TRIANGLE_STRIP); - } - } - - /** - * Calculates the mapped matrix. - * - * TODO playing around here.. this is temporary - (will end up in the shader) - * this returns a matrix that will normalise map filter cords in the filter to screen space - * - * @param {PIXI.Matrix} outputMatrix - the matrix to output to. - * @return {PIXI.Matrix} The mapped matrix. - */ - calculateScreenSpaceMatrix(outputMatrix) - { - const currentState = this.activeState; - - return filterTransforms.calculateScreenSpaceMatrix( - outputMatrix, - currentState.sourceFrame, - currentState.destinationFrame - ); - } - - /** - * This will map the filter coord so that a texture can be used based on the transform of a sprite - * - * @param {PIXI.Matrix} outputMatrix - The matrix to output to. - * @param {PIXI.Sprite} sprite - The sprite to map to. - * @return {PIXI.Matrix} The mapped matrix. - */ - calculateSpriteMatrix(outputMatrix, sprite) - { - const currentState = this.activeState; - - return filterTransforms.calculateSpriteMatrix( - outputMatrix, - currentState.sourceFrame, - currentState.destinationFrame, - sprite - ); - } - - /** - * Destroys this Filter System. - * - * @param {boolean} [contextLost=false] context was lost, do not free shaders - * - */ - destroy(contextLost = false) - { - if (!contextLost) - { - this.emptyPool(); - } - else - { - this.texturePool = {}; - } - } - - /** - * Gets a Power-of-Two render texture. - * - * TODO move to a seperate class could be on renderer? - * also - could cause issue with multiple contexts? - * - * @private - * @param {WebGLRenderingContext} gl - The webgl rendering context - * @param {number} minWidth - The minimum width of the render target. - * @param {number} minHeight - The minimum height of the render target. - * @param {number} resolution - The resolution of the render target. - * @return {PIXI.RenderTarget} The new render target. - */ - getPotFilterTexture(minWidth, minHeight, resolution) - { - minWidth = bitTwiddle.nextPow2(minWidth); - minHeight = bitTwiddle.nextPow2(minHeight); - resolution = resolution || 1; - - const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); - - if (!this.texturePool[key]) - { - this.texturePool[key] = []; - } - - let renderTexture = this.texturePool[key].pop(); - - if (!renderTexture) - { - // temporary bypass cache.. - // internally - this will cause a texture to be bound.. - renderTexture = RenderTexture.create({ - width: minWidth, - height: minHeight, - resolution, - }); - } - - return renderTexture; - } - - /** - * Gets extra render texture to use inside current filter - * - * @param {number} resolution resolution of the renderTexture - * @returns {PIXI.RenderTexture} - */ - getFilterTexture(resolution) - { - const rt = this.activeState.renderTexture; - - const filterTexture = this.getPotFilterTexture(rt.width, rt.height, resolution || rt.baseTexture.resolution); - - filterTexture.filterFrame = rt.filterFrame; - - return filterTexture; - } - - /** - * Frees a render texture back into the pool. - * - * @param {PIXI.RenderTarget} renderTexture - The renderTarget to free - */ - returnFilterTexture(renderTexture) - { - renderTexture.filterFrame = null; - - const base = renderTexture.baseTexture; - - const minWidth = base.width; - const minHeight = base.height; - - const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); - - this.texturePool[key].push(renderTexture); - } - - /** - * Empties the texture pool. - * - */ - emptyPool() - { - for (const i in this.texturePool) - { - const textures = this.texturePool[i]; - - if (textures) - { - for (let j = 0; j < textures.length; j++) - { - textures[j].destroy(true); - } - } - } - - this.texturePool = {}; - } -} diff --git a/packages/core/src/renderers/systems/geometry/GLBuffer.js b/packages/core/src/renderers/systems/geometry/GLBuffer.js deleted file mode 100644 index 0477e8a..0000000 --- a/packages/core/src/renderers/systems/geometry/GLBuffer.js +++ /dev/null @@ -1,9 +0,0 @@ -export default class GLBuffer -{ - constructor(buffer) - { - this.buffer = buffer; - this.updateID = -1; - this.byteLength = -1; - } -} diff --git a/packages/core/src/renderers/systems/geometry/GeometrySystem.js b/packages/core/src/renderers/systems/geometry/GeometrySystem.js deleted file mode 100644 index 26c8e9e..0000000 --- a/packages/core/src/renderers/systems/geometry/GeometrySystem.js +++ /dev/null @@ -1,388 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; -import GLBuffer from './GLBuffer'; - -const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class GeometrySystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this._activeGeometry = null; - this._activeVao = null; - - this.hasVao = true; - this.hasInstance = true; - } - - /** - * Sets up the renderer context and necessary buffers. - * - * @private - */ - contextChange() - { - const gl = this.gl = this.renderer.gl; - - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - - // webgl2 - if (!gl.createVertexArray) - { - // webgl 1! - let nativeVaoExtension = this.renderer.context.extensions.vertexArrayObject; - - if (this.renderer.legacy) - { - nativeVaoExtension = null; - } - - if (nativeVaoExtension) - { - gl.createVertexArray = () => - nativeVaoExtension.createVertexArrayOES(); - - gl.bindVertexArray = (vao) => - nativeVaoExtension.bindVertexArrayOES(vao); - - gl.deleteVertexArray = (vao) => - nativeVaoExtension.deleteVertexArrayOES(vao); - } - else - { - this.hasVao = false; - gl.createVertexArray = () => - { - // empty - }; - - gl.bindVertexArray = () => - { - // empty - }; - - gl.deleteVertexArray = () => - { - // empty - }; - } - } - - if (!gl.vertexAttribDivisor) - { - const instanceExt = gl.getExtension('ANGLE_instanced_arrays'); - - if (instanceExt) - { - gl.vertexAttribDivisor = (a, b) => - instanceExt.vertexAttribDivisorANGLE(a, b); - - gl.drawElementsInstanced = (a, b, c, d, e) => - instanceExt.drawElementsInstancedANGLE(a, b, c, d, e); - - gl.drawArraysInstanced = (a, b, c, d) => - instanceExt.drawArraysInstancedANGLE(a, b, c, d); - } - else - { - this.hasInstance = false; - } - } - } - - /** - * Binds geometry so that is can be drawn. Creating a Vao if required - * @private - * @param {PIXI.Geometry} geometry instance of geometry to bind - */ - bind(geometry, shader) - { - shader = shader || this.renderer.shader.shader; - - const gl = this.gl; - - // not sure the best way to address this.. - // currently different shaders require different VAOs for the same geometry - // Still mulling over the best way to solve this one.. - // will likely need to modify the shader attribute locations at run time! - let vaos = geometry.glVertexArrayObjects[this.CONTEXT_UID]; - - if (!vaos) - { - geometry.glVertexArrayObjects[this.CONTEXT_UID] = vaos = {}; - } - - const vao = vaos[shader.program.id] || this.initGeometryVao(geometry, shader.program); - - this._activeGeometry = geometry; - - if (this._activeVao !== vao) - { - this._activeVao = vao; - - if (this.hasVao) - { - gl.bindVertexArray(vao); - } - else - { - this.activateVao(geometry, shader.program); - } - } - - // TODO - optimise later! - // don't need to loop through if nothing changed! - // maybe look to add an 'autoupdate' to geometry? - this.updateBuffers(); - } - - reset() - { - this.unbind(); - } - - updateBuffers() - { - const geometry = this._activeGeometry; - const gl = this.gl; - - for (let i = 0; i < geometry.buffers.length; i++) - { - const buffer = geometry.buffers[i]; - - const glBuffer = buffer._glBuffers[this.CONTEXT_UID]; - - if (buffer._updateID !== glBuffer.updateID) - { - glBuffer.updateID = buffer._updateID; - - // TODO can cache this on buffer! maybe added a getter / setter? - const type = buffer.index ? gl.ELEMENT_ARRAY_BUFFER : gl.ARRAY_BUFFER; - const drawType = buffer.static ? gl.STATIC_DRAW : gl.DYNAMIC_DRAW; - - gl.bindBuffer(type, glBuffer.buffer); - - if (glBuffer.byteLength >= buffer.data.byteLength) - { - // offset is always zero for now! - gl.bufferSubData(type, 0, buffer.data); - } - else - { - gl.bufferData(type, buffer.data, drawType); - } - } - } - } - - checkCompatability(geometry, program) - { - // geometry must have at least all the attributes that the shader requires. - const geometryAttributes = geometry.attributes; - const shaderAttributes = program.attributeData; - - for (const j in shaderAttributes) - { - if (!geometryAttributes[j]) - { - throw new Error(`shader and geometry incompatible, geometry missing the "${j}" attribute`); - } - } - } - - /** - * Creates a Vao with the same structure as the geometry and stores it on the geometry. - * @private - * @param {PIXI.Geometry} geometry instance of geometry to to generate Vao for - */ - initGeometryVao(geometry, program) - { - this.checkCompatability(geometry, program); - - const gl = this.gl; - const CONTEXT_UID = this.CONTEXT_UID; - const buffers = geometry.buffers; - const attributes = geometry.attributes; - - const tempStride = {}; - const tempStart = {}; - - for (const j in buffers) - { - tempStride[j] = 0; - tempStart[j] = 0; - } - - for (const j in attributes) - { - if (!attributes[j].size && program.attributeData[j]) - { - attributes[j].size = program.attributeData[j].size; - } - - tempStride[attributes[j].buffer] += attributes[j].size * byteSizeMap[attributes[j].type]; - } - - for (const j in attributes) - { - const attribute = attributes[j]; - const attribSize = attribute.size; - - if (attribute.stride === undefined) - { - if (tempStride[attribute.buffer] === attribSize * byteSizeMap[attribute.type]) - { - attribute.stride = 0; - } - else - { - attribute.stride = tempStride[attribute.buffer]; - } - } - - if (attribute.start === undefined) - { - attribute.start = tempStart[attribute.buffer]; - - tempStart[attribute.buffer] += attribSize * byteSizeMap[attribute.type]; - } - } - - // first update - and create the buffers! - // only create a gl buffer if it actually gets - for (let i = 0; i < buffers.length; i++) - { - const buffer = buffers[i]; - - if (!buffer._glBuffers[CONTEXT_UID]) - { - buffer._glBuffers[CONTEXT_UID] = new GLBuffer(gl.createBuffer()); - } - } - - // TODO - maybe make this a data object? - // lets wait to see if we need to first! - const vao = gl.createVertexArray(); - - gl.bindVertexArray(vao); - - this.activateVao(geometry, program); - - gl.bindVertexArray(null); - - // add it to the cache! - geometry.glVertexArrayObjects[this.CONTEXT_UID][program.id] = vao; - - return vao; - } - - activateVao(geometry, program) - { - const gl = this.gl; - const CONTEXT_UID = this.CONTEXT_UID; - const buffers = geometry.buffers; - const attributes = geometry.attributes; - - if (geometry.indexBuffer) - { - // first update the index buffer if we have one.. - gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, geometry.indexBuffer._glBuffers[CONTEXT_UID].buffer); - } - - let lastBuffer = null; - - // add a new one! - for (const j in attributes) - { - const attribute = attributes[j]; - const buffer = buffers[attribute.buffer]; - const glBuffer = buffer._glBuffers[CONTEXT_UID]; - - if (program.attributeData[j]) - { - if (lastBuffer !== glBuffer) - { - gl.bindBuffer(gl.ARRAY_BUFFER, glBuffer.buffer); - - lastBuffer = glBuffer; - } - - const location = program.attributeData[j].location; - - // TODO introduce state again - // we can optimise this for older devices that have no VAOs - gl.enableVertexAttribArray(location); - - gl.vertexAttribPointer(location, - attribute.size, - attribute.type || gl.FLOAT, - attribute.normalized, - attribute.stride, - attribute.start); - - if (attribute.instance) - { - // TODO calculate instance count based of this... - if (this.hasInstance) - { - gl.vertexAttribDivisor(location, 1); - } - else - { - throw new Error('geometry error, GPU Instancing is not supported on this device'); - } - } - } - } - } - - draw(type, size, start, instanceCount) - { - const gl = this.gl; - const geometry = this._activeGeometry; - - // TODO.. this should not change so maybe cache the function? - - if (geometry.indexBuffer) - { - if (geometry.instanced) - { - /* eslint-disable max-len */ - gl.drawElementsInstanced(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2, instanceCount || 1); - /* eslint-enable max-len */ - } - else - { - gl.drawElements(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2); - } - } - else - if (geometry.instanced) - { - // TODO need a better way to calculate size.. - gl.drawArraysInstanced(type, start, size || geometry.getSize(), instanceCount || 1); - } - else - { - gl.drawArrays(type, start, size || geometry.getSize()); - } - - return this; - } - - unbind() - { - this.gl.bindVertexArray(null); - this._activeVao = null; - this._activeGeometry = null; - } -} diff --git a/packages/core/src/renderers/systems/geometry/setVertexAttribArrays.js b/packages/core/src/renderers/systems/geometry/setVertexAttribArrays.js deleted file mode 100644 index 23b6959..0000000 --- a/packages/core/src/renderers/systems/geometry/setVertexAttribArrays.js +++ /dev/null @@ -1,54 +0,0 @@ -// var GL_MAP = {}; - -/** - * @param gl {WebGLRenderingContext} The current WebGL context - * @param attribs {*} - * @param state {*} - */ -export default function setVertexAttribArrays(gl, attribs, state) -{ - let i; - - if (state) - { - const tempAttribState = state.tempAttribState; - const attribState = state.attribState; - - for (i = 0; i < tempAttribState.length; i++) - { - tempAttribState[i] = false; - } - - // set the new attribs - for (i = 0; i < attribs.length; i++) - { - tempAttribState[attribs[i].attribute.location] = true; - } - - for (i = 0; i < attribState.length; i++) - { - if (attribState[i] !== tempAttribState[i]) - { - attribState[i] = tempAttribState[i]; - - if (state.attribState[i]) - { - gl.enableVertexAttribArray(i); - } - else - { - gl.disableVertexAttribArray(i); - } - } - } - } - else - { - for (i = 0; i < attribs.length; i++) - { - const attrib = attribs[i]; - - gl.enableVertexAttribArray(attrib.attribute.location); - } - } -} diff --git a/packages/core/src/renderers/systems/index.js b/packages/core/src/renderers/systems/index.js deleted file mode 100644 index c843f03..0000000 --- a/packages/core/src/renderers/systems/index.js +++ /dev/null @@ -1,6 +0,0 @@ -/** - * Systems are individual components to the Renderer pipeline. - * @namespace PIXI.systems - */ -export { default as WebGLSystem } from './WebGLSystem'; -export { default as FilterSystem } from './filter/FilterSystem'; diff --git a/packages/core/src/renderers/systems/shader/GLProgram.js b/packages/core/src/renderers/systems/shader/GLProgram.js deleted file mode 100644 index 92980ca..0000000 --- a/packages/core/src/renderers/systems/shader/GLProgram.js +++ /dev/null @@ -1,43 +0,0 @@ -/** - * Helper class to create a webGL Program - * - * @class - * @private - */ -export default class GLProgram -{ - constructor(program, uniformData) - { - /** - * The shader program - * - * @member {WebGLProgram} - */ - this.program = program; - - /** - * holds the uniform data which contains uniform locations - * and current uniform values used for caching and preventing undeed GPU commands - * @type {Object} - */ - this.uniformData = uniformData; - - /** - * uniformGroups holds the various upload functions for the shader. Each uniform group - * and program have a unique upload function generated. - * @type {Object} - */ - this.uniformGroups = {}; - } - - /** - * Destroys this program - * TODO - */ - destroy() - { - this.uniformData = null; - this.uniformGroups = null; - this.program = null; - } -} diff --git a/packages/core/src/renderers/systems/shader/ShaderSystem.js b/packages/core/src/renderers/systems/shader/ShaderSystem.js deleted file mode 100644 index 6cf57b6..0000000 --- a/packages/core/src/renderers/systems/shader/ShaderSystem.js +++ /dev/null @@ -1,170 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; -import GLProgram from './GLProgram'; -import generateUniformsSync from '../../../shader/generateUniformsSync'; -import defaultValue from '../../../shader/defaultValue'; -import compileProgram from './shader/compileProgram'; - -let UID = 0; - -/** - * Helper class to create a webGL Texture - * - * @class - * @memberof PIXI - */ -export default class ShaderSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - A reference to the current renderer - */ - constructor(renderer) - { - super(renderer); - - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = null; - - this.shader = null; - this.program = null; - - this.id = UID++; - } - - contextChange(gl) - { - this.gl = gl; - } - - /** - * Changes the current shader to the one given in parameter - * - * @param {PIXI.Shader} shader - the new shader - * @param {boolean} dontSync - false if the shader should automatically sync its uniforms. - * @returns {PIXI.glCore.glProgram} the glProgram that belongs to the shader. - */ - bind(shader, dontSync) - { - shader.uniforms.globals = this.renderer.globalUniforms; - - const program = shader.program; - const glProgram = program.glPrograms[this.renderer.CONTEXT_UID] || this.generateShader(shader); - - this.shader = shader; - - // TODO - some current pixi plugins bypass this.. so it not safe to use yet.. - if (this.program !== program) - { - this.program = program; - this.gl.useProgram(glProgram.program); - } - - if (!dontSync) - { - this.syncUniformGroup(shader.uniformGroup); - } - - return glProgram; - } - - /** - * Uploads the uniforms values to the currently bound shader. - * - * @param {object} uniforms - the uniforms valiues that be applied to the current shader - */ - setUniforms(uniforms) - { - const shader = this.shader.program; - const glProgram = shader.glPrograms[this.renderer.CONTEXT_UID]; - - shader.syncUniforms(glProgram.uniformData, uniforms, this.renderer); - } - - syncUniformGroup(group) - { - const glProgram = this.getglProgram(); - - if (!group.static || group.dirtyId !== glProgram.uniformGroups[group.id]) - { - glProgram.uniformGroups[group.id] = group.dirtyId; - const syncFunc = group.syncUniforms[this.shader.program.id] || this.createSyncGroups(group); - - syncFunc(glProgram.uniformData, group.uniforms, this.renderer); - } - } - - createSyncGroups(group) - { - group.syncUniforms[this.shader.program.id] = generateUniformsSync(group, this.shader.program.uniformData); - - return group.syncUniforms[this.shader.program.id]; - } - - /** - * Returns the underlying GLShade rof the currently bound shader. - * This can be handy for when you to have a little more control over the setting of your uniforms. - * - * @return {PIXI.glCore.Shader} the glProgram for the currently bound Shader for this context - */ - getglProgram() - { - if (this.shader) - { - return this.shader.program.glPrograms[this.renderer.CONTEXT_UID]; - } - - return null; - } - - /** - * Generates a glProgram verion of the Shader provided. - * - * @private - * @param {PIXI.Shader} shader the shader that the glProgram will be based on. - * @return {PIXI.glCore.glProgram} A shiney new glProgram - */ - generateShader(shader) - { - const gl = this.gl; - - const program = shader.program; - - const attribMap = {}; - - for (const i in program.attributeData) - { - attribMap[i] = program.attributeData[i].location; - } - - const shaderProgram = compileProgram(gl, program.vertexSrc, program.fragmentSrc, attribMap); - const uniformData = {}; - - for (const i in program.uniformData) - { - const data = program.uniformData[i]; - - uniformData[i] = { - location: gl.getUniformLocation(shaderProgram, i), - value: defaultValue(data.type, data.size), - }; - } - - const glProgram = new GLProgram(shaderProgram, uniformData); - - program.glPrograms[this.renderer.CONTEXT_UID] = glProgram; - - return glProgram; - } - - /** - * Destroys this System and removes all its textures - */ - destroy() - { - // TODO implement destroy method for ShaderSystem - this.destroyed = true; - } -} diff --git a/packages/core/src/renderers/systems/shader/shader/compileProgram.js b/packages/core/src/renderers/systems/shader/shader/compileProgram.js deleted file mode 100644 index 8c6ccb9..0000000 --- a/packages/core/src/renderers/systems/shader/shader/compileProgram.js +++ /dev/null @@ -1,78 +0,0 @@ -/** - * @method compileProgram - * @memberof PIXI.glCore.shader - * @param gl {WebGLRenderingContext} The current WebGL context {WebGLProgram} - * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. - * @param fragmentSrc {string|string[]} The fragment shader source as an array of strings. - * @param attributeLocations {Object} An attribute location map that lets you manually set the attribute locations - * @return {WebGLProgram} the shader program - */ -export default function compileProgram(gl, vertexSrc, fragmentSrc, attributeLocations) -{ - const glVertShader = compileShader(gl, gl.VERTEX_SHADER, vertexSrc); - const glFragShader = compileShader(gl, gl.FRAGMENT_SHADER, fragmentSrc); - - let program = gl.createProgram(); - - gl.attachShader(program, glVertShader); - gl.attachShader(program, glFragShader); - - // optionally, set the attributes manually for the program rather than letting WebGL decide.. - if (attributeLocations) - { - for (const i in attributeLocations) - { - gl.bindAttribLocation(program, attributeLocations[i], i); - } - } - - gl.linkProgram(program); - - // if linking fails, then log and cleanup - if (!gl.getProgramParameter(program, gl.LINK_STATUS)) - { - console.error('Pixi.js Error: Could not initialize shader.'); - console.error('gl.VALIDATE_STATUS', gl.getProgramParameter(program, gl.VALIDATE_STATUS)); - console.error('gl.getError()', gl.getError()); - - // if there is a program info log, log it - if (gl.getProgramInfoLog(program) !== '') - { - console.warn('Pixi.js Warning: gl.getProgramInfoLog()', gl.getProgramInfoLog(program)); - } - - gl.deleteProgram(program); - program = null; - } - - // clean up some shaders - gl.deleteShader(glVertShader); - gl.deleteShader(glFragShader); - - return program; -} - -/** - * @private - * @param gl {WebGLRenderingContext} The current WebGL context {WebGLProgram} - * @param type {Number} the type, can be either VERTEX_SHADER or FRAGMENT_SHADER - * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. - * @return {WebGLShader} the shader - */ -function compileShader(gl, type, src) -{ - const shader = gl.createShader(type); - - gl.shaderSource(shader, src); - gl.compileShader(shader); - - if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) - { - console.warn(src); - console.error(gl.getShaderInfoLog(shader)); - - return null; - } - - return shader; -} diff --git a/packages/core/src/renderers/systems/shader/shader/defaultValue.js b/packages/core/src/renderers/systems/shader/shader/defaultValue.js deleted file mode 100644 index 97290bd..0000000 --- a/packages/core/src/renderers/systems/shader/shader/defaultValue.js +++ /dev/null @@ -1,79 +0,0 @@ -/** - * @method defaultValue - * @memberof PIXI.glCore.shader - * @param type {String} Type of value - * @param size {Number} - */ -export default function defaultValue(type, size) -{ - switch (type) - { - case 'float': - return 0; - - case 'vec2': - return new Float32Array(2 * size); - - case 'vec3': - return new Float32Array(3 * size); - - case 'vec4': - return new Float32Array(4 * size); - - case 'int': - case 'sampler2D': - case 'sampler2DArray': - return 0; - - case 'ivec2': - return new Int32Array(2 * size); - - case 'ivec3': - return new Int32Array(3 * size); - - case 'ivec4': - return new Int32Array(4 * size); - - case 'bool': - return false; - - case 'bvec2': - - return booleanArray(2 * size); - - case 'bvec3': - return booleanArray(3 * size); - - case 'bvec4': - return booleanArray(4 * size); - - case 'mat2': - return new Float32Array([1, 0, - 0, 1]); - - case 'mat3': - return new Float32Array([1, 0, 0, - 0, 1, 0, - 0, 0, 1]); - - case 'mat4': - return new Float32Array([1, 0, 0, 0, - 0, 1, 0, 0, - 0, 0, 1, 0, - 0, 0, 0, 1]); - } - - return null; -} - -function booleanArray(size) -{ - const array = new Array(size); - - for (let i = 0; i < array.length; i++) - { - array[i] = false; - } - - return array; -} diff --git a/packages/core/src/renderers/systems/shader/shader/index.js b/packages/core/src/renderers/systems/shader/shader/index.js deleted file mode 100644 index ba7a317..0000000 --- a/packages/core/src/renderers/systems/shader/shader/index.js +++ /dev/null @@ -1,8 +0,0 @@ -/** - * @namespace PIXI.glCore.shader - */ -export { default as compileProgram } from './compileProgram'; -export { default as defaultValue } from './defaultValue'; -export { default as setPrecision } from './setPrecision'; -export { default as mapSize } from './mapSize'; -export { default as mapType } from './mapType'; diff --git a/packages/core/src/renderers/systems/shader/shader/mapSize.js b/packages/core/src/renderers/systems/shader/shader/mapSize.js deleted file mode 100644 index b681554..0000000 --- a/packages/core/src/renderers/systems/shader/shader/mapSize.js +++ /dev/null @@ -1,33 +0,0 @@ -const GLSL_TO_SIZE = { - float: 1, - vec2: 2, - vec3: 3, - vec4: 4, - - int: 1, - ivec2: 2, - ivec3: 3, - ivec4: 4, - - bool: 1, - bvec2: 2, - bvec3: 3, - bvec4: 4, - - mat2: 4, - mat3: 9, - mat4: 16, - - sampler2D: 1, -}; - -/** - * @method mapSize - * @memberof PIXI.glCore.shader - * @param type {String} - * @return {Number} - */ -export default function mapSize(type) -{ - return GLSL_TO_SIZE[type]; -} diff --git a/packages/core/src/renderers/systems/shader/shader/mapType.js b/packages/core/src/renderers/systems/shader/shader/mapType.js deleted file mode 100644 index d288b7c..0000000 --- a/packages/core/src/renderers/systems/shader/shader/mapType.js +++ /dev/null @@ -1,45 +0,0 @@ -let GL_TABLE = null; - -const GL_TO_GLSL_TYPES = { - FLOAT: 'float', - FLOAT_VEC2: 'vec2', - FLOAT_VEC3: 'vec3', - FLOAT_VEC4: 'vec4', - - INT: 'int', - INT_VEC2: 'ivec2', - INT_VEC3: 'ivec3', - INT_VEC4: 'ivec4', - - BOOL: 'bool', - BOOL_VEC2: 'bvec2', - BOOL_VEC3: 'bvec3', - BOOL_VEC4: 'bvec4', - - FLOAT_MAT2: 'mat2', - FLOAT_MAT3: 'mat3', - FLOAT_MAT4: 'mat4', - - SAMPLER_2D: 'sampler2D', - SAMPLER_CUBE: 'samplerCube', - SAMPLER_2D_ARRAY: 'sampler2DArray', -}; - -export default function mapType(gl, type) -{ - if (!GL_TABLE) - { - const typeNames = Object.keys(GL_TO_GLSL_TYPES); - - GL_TABLE = {}; - - for (let i = 0; i < typeNames.length; ++i) - { - const tn = typeNames[i]; - - GL_TABLE[gl[tn]] = GL_TO_GLSL_TYPES[tn]; - } - } - - return GL_TABLE[type]; -} diff --git a/packages/core/src/renderers/systems/shader/shader/setPrecision.js b/packages/core/src/renderers/systems/shader/shader/setPrecision.js deleted file mode 100644 index e2c1f34..0000000 --- a/packages/core/src/renderers/systems/shader/shader/setPrecision.js +++ /dev/null @@ -1,18 +0,0 @@ -/** - * Sets the float precision on the shader. If the precision is already present this function will do nothing - * @param {string} src the shader source - * @param {string} precision The float precision of the shader. Options are 'lowp', 'mediump' or 'highp'. - * - * @return {string} modified shader source - */ -export default function setPrecision(src, precision) -{ - src = src.trim(); - - if (src.substring(0, 9) !== 'precision' && src.substring(0, 1) !== '#') - { - return `precision ${precision} float;\n${src}`; - } - - return src; -} diff --git a/packages/core/src/renderers/systems/textures/GLTexture.js b/packages/core/src/renderers/systems/textures/GLTexture.js deleted file mode 100644 index c3fb8dc..0000000 --- a/packages/core/src/renderers/systems/textures/GLTexture.js +++ /dev/null @@ -1,33 +0,0 @@ -export default class GLTexture -{ - constructor(texture) - { - /** - * The WebGL texture - * - * @member {WebGLTexture} - */ - this.texture = texture; - - this.width = -1; - this.height = -1; - - /** - * Texture contents dirty flag - * @member {number} - */ - this.dirtyId = -1; - - /** - * Texture style dirty flag - * @type {number} - */ - this.dirtyStyleId = -1; - - /** - * Whether mip levels has to be generated - * @type {boolean} - */ - this.mipmap = false; - } -} diff --git a/packages/core/src/renderers/systems/textures/TextureGCSystem.js b/packages/core/src/renderers/systems/textures/TextureGCSystem.js deleted file mode 100644 index b089938..0000000 --- a/packages/core/src/renderers/systems/textures/TextureGCSystem.js +++ /dev/null @@ -1,110 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; -import { GC_MODES } from '@pixi/constants'; -import { settings } from '@pixi/settings'; - -/** - * TextureGarbageCollector. This class manages the GPU and ensures that it does not get clogged - * up with textures that are no longer being used. - * - * @class - * @memberof PIXI - */ -export default class TextureGCSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this.count = 0; - this.checkCount = 0; - this.maxIdle = settings.GC_MAX_IDLE; - this.checkCountMax = settings.GC_MAX_CHECK_COUNT; - this.mode = settings.GC_MODE; - } - - /** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ - postrender() - { - this.count++; - - if (this.mode === GC_MODES.MANUAL) - { - return; - } - - this.checkCount++; - - if (this.checkCount > this.checkCountMax) - { - this.checkCount = 0; - - this.run(); - } - } - - /** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ - run() - { - const tm = this.renderer.texture; - const managedTextures = tm.managedTextures; - let wasRemoved = false; - - for (let i = 0; i < managedTextures.length; i++) - { - const texture = managedTextures[i]; - - // only supports non generated textures at the moment! - if (!texture.frameBuffer && this.count - texture.touched > this.maxIdle) - { - tm.destroyTexture(texture, true); - managedTextures[i] = null; - wasRemoved = true; - } - } - - if (wasRemoved) - { - let j = 0; - - for (let i = 0; i < managedTextures.length; i++) - { - if (managedTextures[i] !== null) - { - managedTextures[j++] = managedTextures[i]; - } - } - - managedTextures.length = j; - } - } - - /** - * Removes all the textures within the specified displayObject and its children from the GPU - * - * @param {PIXI.DisplayObject} displayObject - the displayObject to remove the textures from. - */ - unload(displayObject) - { - const tm = this.renderer.textureSystem; - - // only destroy non generated textures - if (displayObject._texture && displayObject._texture._glRenderTargets) - { - tm.destroyTexture(displayObject._texture); - } - - for (let i = displayObject.children.length - 1; i >= 0; i--) - { - this.unload(displayObject.children[i]); - } - } -} diff --git a/packages/core/src/renderers/systems/textures/TextureSystem.js b/packages/core/src/renderers/systems/textures/TextureSystem.js deleted file mode 100644 index ccc5de9..0000000 --- a/packages/core/src/renderers/systems/textures/TextureSystem.js +++ /dev/null @@ -1,281 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; -import GLTexture from './GLTexture'; -import { removeItems } from '@pixi/utils'; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class TextureSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - // TODO set to max textures... - this.boundTextures = [ - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - ]; - - this.currentLocation = -1; - - this.managedTextures = []; - } - - /** - * Sets up the renderer context and necessary buffers. - * - * @private - */ - contextChange() - { - const gl = this.gl = this.renderer.gl; - - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - - // TODO move this.. to a nice make empty textures class.. - this.emptyTextures = {}; - - const emptyTexture2D = new GLTexture(gl.createTexture()); - - gl.bindTexture(gl.TEXTURE_2D, emptyTexture2D.texture); - gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array(4)); - - this.emptyTextures[gl.TEXTURE_2D] = emptyTexture2D; - this.emptyTextures[gl.TEXTURE_CUBE_MAP] = new GLTexture(gl.createTexture()); - - gl.bindTexture(gl.TEXTURE_CUBE_MAP, this.emptyTextures[gl.TEXTURE_CUBE_MAP].texture); - - let i; - - for (i = 0; i < 6; i++) - { - gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, null); - } - - gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_MAG_FILTER, gl.LINEAR); - gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_MIN_FILTER, gl.LINEAR); - - for (i = 0; i < this.boundTextures.length; i++) - { - this.bind(null, i); - } - } - - bind(texture, location) - { - const gl = this.gl; - - location = location || 0; - - if (this.currentLocation !== location) - { - this.currentLocation = location; - gl.activeTexture(gl.TEXTURE0 + location); - } - - if (texture) - { - texture = texture.baseTexture || texture; - - if (texture.valid) - { - texture.touched = this.renderer.textureGC.count; - - const glTexture = texture._glTextures[this.CONTEXT_UID] || this.initTexture(texture); - - gl.bindTexture(texture.target, glTexture.texture); - - if (glTexture.dirtyId !== texture.dirtyId) - { - this.updateTexture(texture); - } - - this.boundTextures[location] = texture; - } - } - else - { - gl.bindTexture(gl.TEXTURE_2D, this.emptyTextures[gl.TEXTURE_2D].texture); - this.boundTextures[location] = null; - } - } - - unbind(texture) - { - const gl = this.gl; - - for (let i = 0; i < this.boundTextures.length; i++) - { - if (this.boundTextures[i] === texture) - { - if (this.currentLocation !== i) - { - gl.activeTexture(gl.TEXTURE0 + i); - this.currentLocation = i; - } - - gl.bindTexture(gl.TEXTURE_2D, this.emptyTextures[texture.target].texture); - this.boundTextures[i] = null; - } - } - } - - initTexture(texture) - { - const glTexture = new GLTexture(this.gl.createTexture()); - - // guarentee an update.. - glTexture.dirtyId = -1; - - texture._glTextures[this.CONTEXT_UID] = glTexture; - - this.managedTextures.push(texture); - texture.on('dispose', this.destroyTexture, this); - - return glTexture; - } - - updateTexture(texture) - { - const glTexture = texture._glTextures[this.CONTEXT_UID]; - const renderer = this.renderer; - - if (texture.resource && texture.resource.upload(renderer, texture, glTexture)) - { - // texture is uploaded, dont do anything! - } - else - { - // default, renderTexture-like logic - const width = texture.realWidth; - const height = texture.realHeight; - const gl = renderer.gl; - - if (glTexture.width !== width - || glTexture.height !== height - || glTexture.dirtyId < 0) - { - glTexture.width = width; - glTexture.height = height; - - gl.texImage2D(texture.target, 0, - texture.format, - width, - height, - 0, - texture.format, - texture.type, - null); - } - } - - // lets only update what changes.. - if (texture.dirtyStyleId !== glTexture.dirtyStyleId) - { - this.updateTextureStyle(texture); - } - glTexture.dirtyId = texture.dirtyId; - } - - /** - * Deletes the texture from WebGL - * - * @param {PIXI.BaseTexture|PIXI.Texture} texture - the texture to destroy - * @param {boolean} [skipRemove=false] - Whether to skip removing the texture from the TextureManager. - */ - destroyTexture(texture, skipRemove) - { - const gl = this.gl; - - texture = texture.baseTexture || texture; - - if (texture._glTextures[this.renderer.CONTEXT_UID]) - { - this.unbind(texture); - - gl.deleteTexture(texture._glTextures[this.renderer.CONTEXT_UID].texture); - texture.off('dispose', this.destroyTexture, this); - - delete texture._glTextures[this.renderer.CONTEXT_UID]; - - if (!skipRemove) - { - const i = this.managedTextures.indexOf(texture); - - if (i !== -1) - { - removeItems(this.managedTextures, i, 1); - } - } - } - } - - updateTextureStyle(texture) - { - const glTexture = texture._glTextures[this.CONTEXT_UID]; - - glTexture.mipmap = texture.mipmap && texture.isPowerOfTwo; - if (!glTexture) - { - return; - } - - if (texture.resource && texture.resource.style(this.renderer, texture, glTexture)) - { - // style is set, dont do anything! - } - else - { - this.setStyle(texture, glTexture); - } - - glTexture.dirtyStyleId = texture.dirtyStyleId; - } - - setStyle(texture, glTexture) - { - const gl = this.gl; - - if (glTexture.mipmap) - { - gl.generateMipmap(texture.target); - } - - gl.texParameteri(texture.target, gl.TEXTURE_WRAP_S, texture.wrapMode); - gl.texParameteri(texture.target, gl.TEXTURE_WRAP_T, texture.wrapMode); - - if (glTexture.mipmap) - { - /* eslint-disable max-len */ - gl.texParameteri(texture.target, gl.TEXTURE_MIN_FILTER, texture.scaleMode ? gl.LINEAR_MIPMAP_LINEAR : gl.NEAREST_MIPMAP_NEAREST); - /* eslint-disable max-len */ - } - else - { - gl.texParameteri(texture.target, gl.TEXTURE_MIN_FILTER, texture.scaleMode ? gl.LINEAR : gl.NEAREST); - } - - gl.texParameteri(texture.target, gl.TEXTURE_MAG_FILTER, texture.scaleMode ? gl.LINEAR : gl.NEAREST); - } -} diff --git a/packages/core/src/renderers/utils/ObjectRenderer.js b/packages/core/src/renderers/utils/ObjectRenderer.js deleted file mode 100644 index f67a273..0000000 --- a/packages/core/src/renderers/utils/ObjectRenderer.js +++ /dev/null @@ -1,48 +0,0 @@ -import WebGLSystem from '../systems/WebGLSystem'; - -/** - * Base for a common object renderer that can be used as a system renderer plugin. - * - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class ObjectRenderer extends WebGLSystem -{ - /** - * Starts the renderer and sets the shader - * - */ - start() - { - // set the shader.. - } - - /** - * Stops the renderer - * - */ - stop() - { - this.flush(); - } - - /** - * Stub method for rendering content and emptying the current batch. - * - */ - flush() - { - // flush! - } - - /** - * Renders an object - * - * @param {PIXI.DisplayObject} object - The object to render. - */ - render(object) // eslint-disable-line no-unused-vars - { - // render the object - } -} diff --git a/packages/core/src/renderers/utils/Quad.js b/packages/core/src/renderers/utils/Quad.js deleted file mode 100644 index 5c1517b..0000000 --- a/packages/core/src/renderers/utils/Quad.js +++ /dev/null @@ -1,26 +0,0 @@ -import Geometry from '../../geometry/Geometry'; - -/** - * Helper class to create a quad - * - * @class - * @memberof PIXI - */ -export default class Quad extends Geometry -{ - /** - * just a quad - */ - constructor() - { - super(); - - this.addAttribute('aVertexPosition', [ - 0, 0, - 1, 0, - 1, 1, - 0, 1, - ]) - .addIndex([0, 1, 3, 2]); - } -} diff --git a/packages/core/src/renderers/utils/QuadUv.js b/packages/core/src/renderers/utils/QuadUv.js deleted file mode 100644 index 97bda89..0000000 --- a/packages/core/src/renderers/utils/QuadUv.js +++ /dev/null @@ -1,103 +0,0 @@ -import Geometry from '../../geometry/Geometry'; -import Buffer from '../../geometry/Buffer'; - -/** - * Helper class to create a quad with uvs like in v4 - * - * @class - * @memberof PIXI - */ -export default class QuadUv extends Geometry -{ - constructor() - { - super(); - - /** - * An array of vertices - * - * @member {Float32Array} - */ - this.vertices = new Float32Array([ - -1, -1, - 1, -1, - 1, 1, - -1, 1, - ]); - - /** - * The Uvs of the quad - * - * @member {Float32Array} - */ - this.uvs = new Float32Array([ - 0, 0, - 1, 0, - 1, 1, - 0, 1, - ]); - - this.vertexBuffer = new Buffer(this.vertices); - this.uvBuffer = new Buffer(this.uvs); - - this.addAttribute('aVertexPosition', this.vertexBuffer) - .addAttribute('aTextureCoord', this.uvBuffer) - .addIndex([0, 1, 2, 0, 2, 3]); - } - - /** - * Maps two Rectangle to the quad. - * - * @param {PIXI.Rectangle} targetTextureFrame - the first rectangle - * @param {PIXI.Rectangle} destinationFrame - the second rectangle - * @return {PIXI.Quad} Returns itself. - */ - map(targetTextureFrame, destinationFrame) - { - let x = 0; // destinationFrame.x / targetTextureFrame.width; - let y = 0; // destinationFrame.y / targetTextureFrame.height; - - this.uvs[0] = x; - this.uvs[1] = y; - - this.uvs[2] = x + (destinationFrame.width / targetTextureFrame.width); - this.uvs[3] = y; - - this.uvs[4] = x + (destinationFrame.width / targetTextureFrame.width); - this.uvs[5] = y + (destinationFrame.height / targetTextureFrame.height); - - this.uvs[6] = x; - this.uvs[7] = y + (destinationFrame.height / targetTextureFrame.height); - - x = destinationFrame.x; - y = destinationFrame.y; - - this.vertices[0] = x; - this.vertices[1] = y; - - this.vertices[2] = x + destinationFrame.width; - this.vertices[3] = y; - - this.vertices[4] = x + destinationFrame.width; - this.vertices[5] = y + destinationFrame.height; - - this.vertices[6] = x; - this.vertices[7] = y + destinationFrame.height; - - this.invalidate(); - - return this; - } - - /** - * legacy upload method, just marks buffers dirty - * @returns {PIXI.QuadUv} Returns itself. - */ - invalidate() - { - this.vertexBuffer._updateID++; - this.uvBuffer._updateID++; - - return this; - } -} diff --git a/packages/core/src/renderers/utils/checkMaxIfStatmentsInShader.js b/packages/core/src/renderers/utils/checkMaxIfStatmentsInShader.js deleted file mode 100644 index 8047c96..0000000 --- a/packages/core/src/renderers/utils/checkMaxIfStatmentsInShader.js +++ /dev/null @@ -1,58 +0,0 @@ -const fragTemplate = [ - 'precision mediump float;', - 'void main(void){', - 'float test = 0.1;', - '%forloop%', - 'gl_FragColor = vec4(0.0);', - '}', -].join('\n'); - -export default function checkMaxIfStatmentsInShader(maxIfs, gl) -{ - if (maxIfs === 0) - { - throw new Error('Invalid value of `0` passed to `checkMaxIfStatementsInShader`'); - } - - const shader = gl.createShader(gl.FRAGMENT_SHADER); - - while (true) // eslint-disable-line no-constant-condition - { - const fragmentSrc = fragTemplate.replace(/%forloop%/gi, generateIfTestSrc(maxIfs)); - - gl.shaderSource(shader, fragmentSrc); - gl.compileShader(shader); - - if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) - { - maxIfs = (maxIfs / 2) | 0; - } - else - { - // valid! - break; - } - } - - return maxIfs; -} - -function generateIfTestSrc(maxIfs) -{ - let src = ''; - - for (let i = 0; i < maxIfs; ++i) - { - if (i > 0) - { - src += '\nelse '; - } - - if (i < maxIfs - 1) - { - src += `if(test == ${i}.0){}`; - } - } - - return src; -} diff --git a/packages/core/src/renderers/utils/mapWebGLBlendModesToPixi.js b/packages/core/src/renderers/utils/mapWebGLBlendModesToPixi.js deleted file mode 100644 index 10ffe4d..0000000 --- a/packages/core/src/renderers/utils/mapWebGLBlendModesToPixi.js +++ /dev/null @@ -1,47 +0,0 @@ -import { BLEND_MODES } from '@pixi/constants'; - -/** - * Maps gl blend combinations to WebGL. - * - * @memberof PIXI - * @function mapWebGLBlendModesToPixi - * @private - * @param {WebGLRenderingContext} gl - The rendering context. - * @param {string[]} [array=[]] - The array to output into. - * @return {string[]} Mapped modes. - */ -export default function mapWebGLBlendModesToPixi(gl, array = []) -{ - // TODO - premultiply alpha would be different. - // add a boolean for that! - array[BLEND_MODES.NORMAL] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.ADD] = [gl.ONE, gl.DST_ALPHA]; - array[BLEND_MODES.MULTIPLY] = [gl.DST_COLOR, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.SCREEN] = [gl.ONE, gl.ONE_MINUS_SRC_COLOR]; - array[BLEND_MODES.OVERLAY] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.DARKEN] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.LIGHTEN] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.COLOR_DODGE] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.COLOR_BURN] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.HARD_LIGHT] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.SOFT_LIGHT] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.DIFFERENCE] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.EXCLUSION] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.HUE] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.SATURATION] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.COLOR] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.LUMINOSITY] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.NONE] = [0, 0]; - - // not-premultiplied blend modes - array[BLEND_MODES.NORMAL_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.ADD_NPM] = [gl.SRC_ALPHA, gl.DST_ALPHA, gl.ONE, gl.DST_ALPHA]; - array[BLEND_MODES.SCREEN_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_COLOR, gl.ONE, gl.ONE_MINUS_SRC_COLOR]; - - // not-premultiplied blend modes - array[BLEND_MODES.NORMAL_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.ADD_NPM] = [gl.SRC_ALPHA, gl.DST_ALPHA, gl.ONE, gl.DST_ALPHA]; - array[BLEND_MODES.SCREEN_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_COLOR, gl.ONE, gl.ONE_MINUS_SRC_COLOR]; - - return array; -} diff --git a/packages/core/src/shader/GLProgram.js b/packages/core/src/shader/GLProgram.js new file mode 100644 index 0000000..92980ca --- /dev/null +++ b/packages/core/src/shader/GLProgram.js @@ -0,0 +1,43 @@ +/** + * Helper class to create a webGL Program + * + * @class + * @private + */ +export default class GLProgram +{ + constructor(program, uniformData) + { + /** + * The shader program + * + * @member {WebGLProgram} + */ + this.program = program; + + /** + * holds the uniform data which contains uniform locations + * and current uniform values used for caching and preventing undeed GPU commands + * @type {Object} + */ + this.uniformData = uniformData; + + /** + * uniformGroups holds the various upload functions for the shader. Each uniform group + * and program have a unique upload function generated. + * @type {Object} + */ + this.uniformGroups = {}; + } + + /** + * Destroys this program + * TODO + */ + destroy() + { + this.uniformData = null; + this.uniformGroups = null; + this.program = null; + } +} diff --git a/packages/core/src/shader/Program.js b/packages/core/src/shader/Program.js index 4f9971e..4b4007a 100644 --- a/packages/core/src/shader/Program.js +++ b/packages/core/src/shader/Program.js @@ -1,6 +1,11 @@ -import * as shaderUtils from '../renderers/systems/shader/shader'; +// import * as from '../systems/shader/shader'; +import { setPrecision, + defaultValue, + compileProgram, + mapSize, + mapType, + getTestContext } from './utils'; import { ProgramCache } from '@pixi/utils'; -import getTestContext from './getTestContext'; import defaultFragment from './defaultProgram.frag'; import defaultVertex from './defaultProgram.vert'; import { settings } from '@pixi/settings'; @@ -34,8 +39,8 @@ */ this.fragmentSrc = fragmentSrc || Program.defaultFragmentSrc; - this.vertexSrc = shaderUtils.setPrecision(this.vertexSrc, settings.PRECISION_VERTEX); - this.fragmentSrc = shaderUtils.setPrecision(this.fragmentSrc, settings.PRECISION_FRAGMENT); + this.vertexSrc = setPrecision(this.vertexSrc, settings.PRECISION_VERTEX); + this.fragmentSrc = setPrecision(this.fragmentSrc, settings.PRECISION_FRAGMENT); // currently this does not extract structs only default types this.extractData(this.vertexSrc, this.fragmentSrc); @@ -62,7 +67,7 @@ if (gl) { - const program = shaderUtils.compileProgram(gl, vertexSrc, fragmentSrc); + const program = compileProgram(gl, vertexSrc, fragmentSrc); this.attributeData = this.getAttributeData(program, gl); this.uniformData = this.getUniformData(program, gl); @@ -95,13 +100,13 @@ for (let i = 0; i < totalAttributes; i++) { const attribData = gl.getActiveAttrib(program, i); - const type = shaderUtils.mapType(gl, attribData.type); + const type = mapType(gl, attribData.type); /*eslint-disable */ const data = { type: type, name: attribData.name, - size: shaderUtils.mapSize(type), + size: mapSize(type), location: 0, }; /* eslint-enable */ @@ -145,14 +150,14 @@ const name = uniformData.name.replace(/\[.*?\]/, ''); const isArray = uniformData.name.match(/\[.*?\]/, ''); - const type = shaderUtils.mapType(gl, uniformData.type); + const type = mapType(gl, uniformData.type); /*eslint-disable */ uniforms[name] = { type: type, size: uniformData.size, isArray:isArray, - value: shaderUtils.defaultValue(type, uniformData.size), + value: defaultValue(type, uniformData.size), }; /* eslint-enable */ } diff --git a/packages/core/src/shader/ShaderSystem.js b/packages/core/src/shader/ShaderSystem.js new file mode 100644 index 0000000..40b8af8 --- /dev/null +++ b/packages/core/src/shader/ShaderSystem.js @@ -0,0 +1,171 @@ +import System from '../System'; +import GLProgram from './GLProgram'; +import { generateUniformsSync, + defaultValue, + compileProgram } from './utils'; + +let UID = 0; + +/** + * Helper class to create a webGL Texture + * + * @class + * @memberof PIXI.systems + * @extends PIXI.System + */ +export default class ShaderSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - A reference to the current renderer + */ + constructor(renderer) + { + super(renderer); + + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = null; + + this.shader = null; + this.program = null; + + this.id = UID++; + } + + contextChange(gl) + { + this.gl = gl; + } + + /** + * Changes the current shader to the one given in parameter + * + * @param {PIXI.Shader} shader - the new shader + * @param {boolean} dontSync - false if the shader should automatically sync its uniforms. + * @returns {PIXI.glCore.glProgram} the glProgram that belongs to the shader. + */ + bind(shader, dontSync) + { + shader.uniforms.globals = this.renderer.globalUniforms; + + const program = shader.program; + const glProgram = program.glPrograms[this.renderer.CONTEXT_UID] || this.generateShader(shader); + + this.shader = shader; + + // TODO - some current pixi plugins bypass this.. so it not safe to use yet.. + if (this.program !== program) + { + this.program = program; + this.gl.useProgram(glProgram.program); + } + + if (!dontSync) + { + this.syncUniformGroup(shader.uniformGroup); + } + + return glProgram; + } + + /** + * Uploads the uniforms values to the currently bound shader. + * + * @param {object} uniforms - the uniforms valiues that be applied to the current shader + */ + setUniforms(uniforms) + { + const shader = this.shader.program; + const glProgram = shader.glPrograms[this.renderer.CONTEXT_UID]; + + shader.syncUniforms(glProgram.uniformData, uniforms, this.renderer); + } + + syncUniformGroup(group) + { + const glProgram = this.getglProgram(); + + if (!group.static || group.dirtyId !== glProgram.uniformGroups[group.id]) + { + glProgram.uniformGroups[group.id] = group.dirtyId; + const syncFunc = group.syncUniforms[this.shader.program.id] || this.createSyncGroups(group); + + syncFunc(glProgram.uniformData, group.uniforms, this.renderer); + } + } + + createSyncGroups(group) + { + group.syncUniforms[this.shader.program.id] = generateUniformsSync(group, this.shader.program.uniformData); + + return group.syncUniforms[this.shader.program.id]; + } + + /** + * Returns the underlying GLShade rof the currently bound shader. + * This can be handy for when you to have a little more control over the setting of your uniforms. + * + * @return {PIXI.glCore.Shader} the glProgram for the currently bound Shader for this context + */ + getglProgram() + { + if (this.shader) + { + return this.shader.program.glPrograms[this.renderer.CONTEXT_UID]; + } + + return null; + } + + /** + * Generates a glProgram verion of the Shader provided. + * + * @private + * @param {PIXI.Shader} shader the shader that the glProgram will be based on. + * @return {PIXI.glCore.glProgram} A shiney new glProgram + */ + generateShader(shader) + { + const gl = this.gl; + + const program = shader.program; + + const attribMap = {}; + + for (const i in program.attributeData) + { + attribMap[i] = program.attributeData[i].location; + } + + const shaderProgram = compileProgram(gl, program.vertexSrc, program.fragmentSrc, attribMap); + const uniformData = {}; + + for (const i in program.uniformData) + { + const data = program.uniformData[i]; + + uniformData[i] = { + location: gl.getUniformLocation(shaderProgram, i), + value: defaultValue(data.type, data.size), + }; + } + + const glProgram = new GLProgram(shaderProgram, uniformData); + + program.glPrograms[this.renderer.CONTEXT_UID] = glProgram; + + return glProgram; + } + + /** + * Destroys this System and removes all its textures + */ + destroy() + { + // TODO implement destroy method for ShaderSystem + this.destroyed = true; + } +} diff --git a/packages/core/src/shader/defaultValue.js b/packages/core/src/shader/defaultValue.js deleted file mode 100644 index 847a069..0000000 --- a/packages/core/src/shader/defaultValue.js +++ /dev/null @@ -1,78 +0,0 @@ -function booleanArray(size) -{ - const array = new Array(size); - - for (let i = 0; i < array.length; i++) - { - array[i] = false; - } - - return array; -} - -/** - * @class - * @memberof PIXI.glCore.shader - * @param type {String} Type of value - * @param size {Number} - */ -export default function defaultValue(type, size) -{ - switch (type) - { - case 'float': - return 0; - - case 'vec2': - return new Float32Array(2 * size); - - case 'vec3': - return new Float32Array(3 * size); - - case 'vec4': - return new Float32Array(4 * size); - - case 'int': - case 'sampler2D': - return 0; - - case 'ivec2': - return new Int32Array(2 * size); - - case 'ivec3': - return new Int32Array(3 * size); - - case 'ivec4': - return new Int32Array(4 * size); - - case 'bool': - return false; - - case 'bvec2': - - return booleanArray(2 * size); - - case 'bvec3': - return booleanArray(3 * size); - - case 'bvec4': - return booleanArray(4 * size); - - case 'mat2': - return new Float32Array([1, 0, - 0, 1]); - - case 'mat3': - return new Float32Array([1, 0, 0, - 0, 1, 0, - 0, 0, 1]); - - case 'mat4': - return new Float32Array([1, 0, 0, 0, - 0, 1, 0, 0, - 0, 0, 1, 0, - 0, 0, 0, 1]); - } - - return 0; -} diff --git a/packages/core/src/shader/extractUniformsFromSrc.js b/packages/core/src/shader/extractUniformsFromSrc.js deleted file mode 100644 index 3d02c0a..0000000 --- a/packages/core/src/shader/extractUniformsFromSrc.js +++ /dev/null @@ -1,57 +0,0 @@ -import defaultValue from './defaultValue'; - -export default function extractUniformsFromSrc(vertexSrc, fragmentSrc, mask) -{ - const vertUniforms = extractUniformsFromString(vertexSrc, mask); - const fragUniforms = extractUniformsFromString(fragmentSrc, mask); - - return Object.assign(vertUniforms, fragUniforms); -} - -function extractUniformsFromString(string) -{ - const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); - - const uniforms = {}; - let nameSplit; - - // clean the lines a little - remove extra spaces / teabs etc - // then split along ';' - const lines = string.replace(/\s+/g, ' ') - .split(/\s*;\s*/); - - // loop through.. - for (let i = 0; i < lines.length; i++) - { - const line = lines[i].trim(); - - if (line.indexOf('uniform') > -1) - { - const splitLine = line.split(' '); - const type = splitLine[1]; - - let name = splitLine[2]; - let size = 1; - - if (name.indexOf('[') > -1) - { - // array! - nameSplit = name.split(/\[|]/); - name = nameSplit[0]; - size *= Number(nameSplit[1]); - } - - if (!name.match(maskRegex)) - { - uniforms[name] = { - value: defaultValue(type, size), - dirtyId: 0, - name, - type, - }; - } - } - } - - return uniforms; -} diff --git a/packages/core/src/shader/generateUniformsSync.js b/packages/core/src/shader/generateUniformsSync.js deleted file mode 100644 index 98a357f..0000000 --- a/packages/core/src/shader/generateUniformsSync.js +++ /dev/null @@ -1,231 +0,0 @@ -// cv = CachedValue -// v = value -// ud = uniformData -// uv = uniformValue -// l = loaction -const GLSL_TO_SINGLE_SETTERS_CACHED = { - - float: ` - if(cv !== v) - { - cv.v = v; - gl.uniform1f(location, v) - }`, - - vec2: ` - if(cv[0] !== v[0] || cv[1] !== v[1]) - { - cv[0] = v[0]; - cv[1] = v[1]; - gl.uniform2f(location, v[0], v[1]) - }`, - - vec3: ` - if(cv[0] !== v[0] || cv[1] !== v[1] || cv[2] !== v[2]) - { - cv[0] = v[0]; - cv[1] = v[1]; - cv[2] = v[2]; - - gl.uniform3f(location, v[0], v[1], v[2]) - }`, - - vec4: 'gl.uniform4f(location, v[0], v[1], v[2], v[3])', - - int: 'gl.uniform1i(location, v)', - ivec2: 'gl.uniform2i(location, v[0], v[1])', - ivec3: 'gl.uniform3i(location, v[0], v[1], v[2])', - ivec4: 'gl.uniform4i(location, v[0], v[1], v[2], v[3])', - - bool: 'gl.uniform1i(location, v)', - bvec2: 'gl.uniform2i(location, v[0], v[1])', - bvec3: 'gl.uniform3i(location, v[0], v[1], v[2])', - bvec4: 'gl.uniform4i(location, v[0], v[1], v[2], v[3])', - - mat2: 'gl.uniformMatrix2fv(location, false, v)', - mat3: 'gl.uniformMatrix3fv(location, false, v)', - mat4: 'gl.uniformMatrix4fv(location, false, v)', - - sampler2D: 'gl.uniform1i(location, v)', - samplerCube: 'gl.uniform1i(location, v)', - sampler2DArray: 'gl.uniform1i(location, v)', -}; - -const GLSL_TO_ARRAY_SETTERS = { - - float: `gl.uniform1fv(location, v)`, - - vec2: `gl.uniform2fv(location, v)`, - vec3: `gl.uniform3fv(location, v)`, - vec4: 'gl.uniform4fv(location, v)', - - mat4: 'gl.uniformMatrix4fv(location, false, v)', - mat3: 'gl.uniformMatrix3fv(location, false, v)', - mat2: 'gl.uniformMatrix2fv(location, false, v)', - - int: 'gl.uniform1iv(location, v)', - ivec2: 'gl.uniform2iv(location, v)', - ivec3: 'gl.uniform3iv(location, v)', - ivec4: 'gl.uniform4iv(location, v)', - - bool: 'gl.uniform1iv(location, v)', - bvec2: 'gl.uniform2iv(location, v)', - bvec3: 'gl.uniform3iv(location, v)', - bvec4: 'gl.uniform4iv(location, v)', - - sampler2D: 'gl.uniform1iv(location, v)', - samplerCube: 'gl.uniform1iv(location, v)', - sampler2DArray: 'gl.uniform1iv(location, v)', -}; - -export default function generateUniformsSync(group, uniformData) -{ - let textureCount = 0; - let func = `var v = null; - var cv = null - var gl = renderer.gl`; - - for (const i in group.uniforms) - { - const data = uniformData[i]; - - if (!data) - { - if (group.uniforms[i].group) - { - func += ` - renderer.shader.syncUniformGroup(uv.${i}); - `; - } - - continue; - } - - // TODO && uniformData[i].value !== 0 <-- do we still need this? - if (data.type === 'float' && data.size === 1) - { - func += ` - if(uv.${i} !== ud.${i}.value) - { - ud.${i}.value = uv.${i} - gl.uniform1f(ud.${i}.location, uv.${i}) - }\n`; - } - /* eslint-disable max-len */ - else if ((data.type === 'sampler2D' || data.type === 'samplerCube' || data.type === 'sampler2DArray') && data.size === 1 && !data.isArray) - /* eslint-disable max-len */ - { - func += ` - renderer.texture.bind(uv.${i}, ${textureCount}); - - if(ud.${i}.value !== ${textureCount}) - { - ud.${i}.value = ${textureCount}; - gl.uniform1i(ud.${i}.location, ${textureCount});\n; // eslint-disable-line max-len - }\n`; - - textureCount++; - } - else if (data.type === 'mat3' && data.size === 1) - { - if (group.uniforms[i].a !== undefined) - { - // TODO and some smart caching dirty ids here! - func += ` - gl.uniformMatrix3fv(ud.${i}.location, false, uv.${i}.toArray(true)); - \n`; - } - else - { - func += ` - gl.uniformMatrix3fv(ud.${i}.location, false, uv.${i}); - \n`; - } - } - else if (data.type === 'vec2' && data.size === 1) - { - // TODO - do we need both here? - // maybe we can get away with only using points? - if (group.uniforms[i].x !== undefined) - { - func += ` - cv = ud.${i}.value; - v = uv.${i}; - - if(cv[0] !== v.x || cv[1] !== v.y) - { - cv[0] = v.x; - cv[1] = v.y; - gl.uniform2f(ud.${i}.location, v.x, v.y); - }\n`; - } - else - { - func += ` - cv = ud.${i}.value; - v = uv.${i}; - - if(cv[0] !== v[0] || cv[1] !== v[1]) - { - cv[0] = v[0]; - cv[1] = v[1]; - gl.uniform2f(ud.${i}.location, v[0], v[1]); - } - \n`; - } - } - else if (data.type === 'vec4' && data.size === 1) - { - // TODO - do we need both here? - // maybe we can get away with only using points? - if (group.uniforms[i].width !== undefined) - { - func += ` - cv = ud.${i}.value; - v = uv.${i}; - - if(cv[0] !== v.x || cv[1] !== v.y || cv[2] !== v.width || cv[3] !== v.height) - { - cv[0] = v.x; - cv[1] = v.y; - cv[2] = v.width; - cv[3] = v.height; - gl.uniform4f(ud.${i}.location, v.x, v.y, v.width, v.height) - }\n`; - } - else - { - func += ` - cv = ud.${i}.value; - v = uv.${i}; - - if(cv[0] !== v[0] || cv[1] !== v[1] || cv[2] !== v[2] || cv[3] !== v[3]) - { - cv[0] = v[0]; - cv[1] = v[1]; - cv[2] = v[2]; - cv[3] = v[3]; - - gl.uniform4f(ud.${i}.location, v[0], v[1], v[2], v[3]) - } - \n`; - } - } - else - { - const templateType = (data.size === 1) ? GLSL_TO_SINGLE_SETTERS_CACHED : GLSL_TO_ARRAY_SETTERS; - - const template = templateType[data.type].replace('location', `ud.${i}.location`); - - func += ` - cv = ud.${i}.value; - v = uv.${i}; - ${template};\n`; - } - } - - // console.log(' --------------- ') - // console.log(func); - - return new Function('ud', 'uv', 'renderer', func); // eslint-disable-line no-new-func -} diff --git a/packages/core/src/shader/getTestContext.js b/packages/core/src/shader/getTestContext.js deleted file mode 100644 index ea174cc..0000000 --- a/packages/core/src/shader/getTestContext.js +++ /dev/null @@ -1,48 +0,0 @@ -import { settings } from '@pixi/settings'; - -let context = null; - -/** - * returns a little webGL context to use for program inspection. - * - * @static - * @private - * @returns {webGL-context} a gl context to test with - */ -export default function getTestContext() -{ - if (!context) - { - const canvas = document.createElement('canvas'); - - let gl; - - if (settings.PREFER_WEBGL_2) - { - gl = canvas.getContext('webgl2', {}); - } - - if (!gl) - { - gl = canvas.getContext('webgl', {}) - || canvas.getContext('experimental-webgl', {}); - - if (!gl) - { - // fail, not able to get a context - throw new Error('This browser does not support webGL. Try using the canvas renderer'); - } - else - { - // for shader testing.. - gl.getExtension('WEBGL_draw_buffers'); - } - } - - context = gl; - - return gl; - } - - return context; -} diff --git a/packages/core/src/shader/utils/checkMaxIfStatmentsInShader.js b/packages/core/src/shader/utils/checkMaxIfStatmentsInShader.js new file mode 100644 index 0000000..8047c96 --- /dev/null +++ b/packages/core/src/shader/utils/checkMaxIfStatmentsInShader.js @@ -0,0 +1,58 @@ +const fragTemplate = [ + 'precision mediump float;', + 'void main(void){', + 'float test = 0.1;', + '%forloop%', + 'gl_FragColor = vec4(0.0);', + '}', +].join('\n'); + +export default function checkMaxIfStatmentsInShader(maxIfs, gl) +{ + if (maxIfs === 0) + { + throw new Error('Invalid value of `0` passed to `checkMaxIfStatementsInShader`'); + } + + const shader = gl.createShader(gl.FRAGMENT_SHADER); + + while (true) // eslint-disable-line no-constant-condition + { + const fragmentSrc = fragTemplate.replace(/%forloop%/gi, generateIfTestSrc(maxIfs)); + + gl.shaderSource(shader, fragmentSrc); + gl.compileShader(shader); + + if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) + { + maxIfs = (maxIfs / 2) | 0; + } + else + { + // valid! + break; + } + } + + return maxIfs; +} + +function generateIfTestSrc(maxIfs) +{ + let src = ''; + + for (let i = 0; i < maxIfs; ++i) + { + if (i > 0) + { + src += '\nelse '; + } + + if (i < maxIfs - 1) + { + src += `if(test == ${i}.0){}`; + } + } + + return src; +} diff --git a/packages/core/src/shader/utils/compileProgram.js b/packages/core/src/shader/utils/compileProgram.js new file mode 100644 index 0000000..8c6ccb9 --- /dev/null +++ b/packages/core/src/shader/utils/compileProgram.js @@ -0,0 +1,78 @@ +/** + * @method compileProgram + * @memberof PIXI.glCore.shader + * @param gl {WebGLRenderingContext} The current WebGL context {WebGLProgram} + * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. + * @param fragmentSrc {string|string[]} The fragment shader source as an array of strings. + * @param attributeLocations {Object} An attribute location map that lets you manually set the attribute locations + * @return {WebGLProgram} the shader program + */ +export default function compileProgram(gl, vertexSrc, fragmentSrc, attributeLocations) +{ + const glVertShader = compileShader(gl, gl.VERTEX_SHADER, vertexSrc); + const glFragShader = compileShader(gl, gl.FRAGMENT_SHADER, fragmentSrc); + + let program = gl.createProgram(); + + gl.attachShader(program, glVertShader); + gl.attachShader(program, glFragShader); + + // optionally, set the attributes manually for the program rather than letting WebGL decide.. + if (attributeLocations) + { + for (const i in attributeLocations) + { + gl.bindAttribLocation(program, attributeLocations[i], i); + } + } + + gl.linkProgram(program); + + // if linking fails, then log and cleanup + if (!gl.getProgramParameter(program, gl.LINK_STATUS)) + { + console.error('Pixi.js Error: Could not initialize shader.'); + console.error('gl.VALIDATE_STATUS', gl.getProgramParameter(program, gl.VALIDATE_STATUS)); + console.error('gl.getError()', gl.getError()); + + // if there is a program info log, log it + if (gl.getProgramInfoLog(program) !== '') + { + console.warn('Pixi.js Warning: gl.getProgramInfoLog()', gl.getProgramInfoLog(program)); + } + + gl.deleteProgram(program); + program = null; + } + + // clean up some shaders + gl.deleteShader(glVertShader); + gl.deleteShader(glFragShader); + + return program; +} + +/** + * @private + * @param gl {WebGLRenderingContext} The current WebGL context {WebGLProgram} + * @param type {Number} the type, can be either VERTEX_SHADER or FRAGMENT_SHADER + * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. + * @return {WebGLShader} the shader + */ +function compileShader(gl, type, src) +{ + const shader = gl.createShader(type); + + gl.shaderSource(shader, src); + gl.compileShader(shader); + + if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) + { + console.warn(src); + console.error(gl.getShaderInfoLog(shader)); + + return null; + } + + return shader; +} diff --git a/packages/core/src/shader/utils/defaultValue.js b/packages/core/src/shader/utils/defaultValue.js new file mode 100644 index 0000000..97290bd --- /dev/null +++ b/packages/core/src/shader/utils/defaultValue.js @@ -0,0 +1,79 @@ +/** + * @method defaultValue + * @memberof PIXI.glCore.shader + * @param type {String} Type of value + * @param size {Number} + */ +export default function defaultValue(type, size) +{ + switch (type) + { + case 'float': + return 0; + + case 'vec2': + return new Float32Array(2 * size); + + case 'vec3': + return new Float32Array(3 * size); + + case 'vec4': + return new Float32Array(4 * size); + + case 'int': + case 'sampler2D': + case 'sampler2DArray': + return 0; + + case 'ivec2': + return new Int32Array(2 * size); + + case 'ivec3': + return new Int32Array(3 * size); + + case 'ivec4': + return new Int32Array(4 * size); + + case 'bool': + return false; + + case 'bvec2': + + return booleanArray(2 * size); + + case 'bvec3': + return booleanArray(3 * size); + + case 'bvec4': + return booleanArray(4 * size); + + case 'mat2': + return new Float32Array([1, 0, + 0, 1]); + + case 'mat3': + return new Float32Array([1, 0, 0, + 0, 1, 0, + 0, 0, 1]); + + case 'mat4': + return new Float32Array([1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1]); + } + + return null; +} + +function booleanArray(size) +{ + const array = new Array(size); + + for (let i = 0; i < array.length; i++) + { + array[i] = false; + } + + return array; +} diff --git a/packages/core/src/shader/utils/extractUniformsFromSrc.js b/packages/core/src/shader/utils/extractUniformsFromSrc.js new file mode 100644 index 0000000..3d02c0a --- /dev/null +++ b/packages/core/src/shader/utils/extractUniformsFromSrc.js @@ -0,0 +1,57 @@ +import defaultValue from './defaultValue'; + +export default function extractUniformsFromSrc(vertexSrc, fragmentSrc, mask) +{ + const vertUniforms = extractUniformsFromString(vertexSrc, mask); + const fragUniforms = extractUniformsFromString(fragmentSrc, mask); + + return Object.assign(vertUniforms, fragUniforms); +} + +function extractUniformsFromString(string) +{ + const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); + + const uniforms = {}; + let nameSplit; + + // clean the lines a little - remove extra spaces / teabs etc + // then split along ';' + const lines = string.replace(/\s+/g, ' ') + .split(/\s*;\s*/); + + // loop through.. + for (let i = 0; i < lines.length; i++) + { + const line = lines[i].trim(); + + if (line.indexOf('uniform') > -1) + { + const splitLine = line.split(' '); + const type = splitLine[1]; + + let name = splitLine[2]; + let size = 1; + + if (name.indexOf('[') > -1) + { + // array! + nameSplit = name.split(/\[|]/); + name = nameSplit[0]; + size *= Number(nameSplit[1]); + } + + if (!name.match(maskRegex)) + { + uniforms[name] = { + value: defaultValue(type, size), + dirtyId: 0, + name, + type, + }; + } + } + } + + return uniforms; +} diff --git a/packages/core/src/shader/utils/generateUniformsSync.js b/packages/core/src/shader/utils/generateUniformsSync.js new file mode 100644 index 0000000..98a357f --- /dev/null +++ b/packages/core/src/shader/utils/generateUniformsSync.js @@ -0,0 +1,231 @@ +// cv = CachedValue +// v = value +// ud = uniformData +// uv = uniformValue +// l = loaction +const GLSL_TO_SINGLE_SETTERS_CACHED = { + + float: ` + if(cv !== v) + { + cv.v = v; + gl.uniform1f(location, v) + }`, + + vec2: ` + if(cv[0] !== v[0] || cv[1] !== v[1]) + { + cv[0] = v[0]; + cv[1] = v[1]; + gl.uniform2f(location, v[0], v[1]) + }`, + + vec3: ` + if(cv[0] !== v[0] || cv[1] !== v[1] || cv[2] !== v[2]) + { + cv[0] = v[0]; + cv[1] = v[1]; + cv[2] = v[2]; + + gl.uniform3f(location, v[0], v[1], v[2]) + }`, + + vec4: 'gl.uniform4f(location, v[0], v[1], v[2], v[3])', + + int: 'gl.uniform1i(location, v)', + ivec2: 'gl.uniform2i(location, v[0], v[1])', + ivec3: 'gl.uniform3i(location, v[0], v[1], v[2])', + ivec4: 'gl.uniform4i(location, v[0], v[1], v[2], v[3])', + + bool: 'gl.uniform1i(location, v)', + bvec2: 'gl.uniform2i(location, v[0], v[1])', + bvec3: 'gl.uniform3i(location, v[0], v[1], v[2])', + bvec4: 'gl.uniform4i(location, v[0], v[1], v[2], v[3])', + + mat2: 'gl.uniformMatrix2fv(location, false, v)', + mat3: 'gl.uniformMatrix3fv(location, false, v)', + mat4: 'gl.uniformMatrix4fv(location, false, v)', + + sampler2D: 'gl.uniform1i(location, v)', + samplerCube: 'gl.uniform1i(location, v)', + sampler2DArray: 'gl.uniform1i(location, v)', +}; + +const GLSL_TO_ARRAY_SETTERS = { + + float: `gl.uniform1fv(location, v)`, + + vec2: `gl.uniform2fv(location, v)`, + vec3: `gl.uniform3fv(location, v)`, + vec4: 'gl.uniform4fv(location, v)', + + mat4: 'gl.uniformMatrix4fv(location, false, v)', + mat3: 'gl.uniformMatrix3fv(location, false, v)', + mat2: 'gl.uniformMatrix2fv(location, false, v)', + + int: 'gl.uniform1iv(location, v)', + ivec2: 'gl.uniform2iv(location, v)', + ivec3: 'gl.uniform3iv(location, v)', + ivec4: 'gl.uniform4iv(location, v)', + + bool: 'gl.uniform1iv(location, v)', + bvec2: 'gl.uniform2iv(location, v)', + bvec3: 'gl.uniform3iv(location, v)', + bvec4: 'gl.uniform4iv(location, v)', + + sampler2D: 'gl.uniform1iv(location, v)', + samplerCube: 'gl.uniform1iv(location, v)', + sampler2DArray: 'gl.uniform1iv(location, v)', +}; + +export default function generateUniformsSync(group, uniformData) +{ + let textureCount = 0; + let func = `var v = null; + var cv = null + var gl = renderer.gl`; + + for (const i in group.uniforms) + { + const data = uniformData[i]; + + if (!data) + { + if (group.uniforms[i].group) + { + func += ` + renderer.shader.syncUniformGroup(uv.${i}); + `; + } + + continue; + } + + // TODO && uniformData[i].value !== 0 <-- do we still need this? + if (data.type === 'float' && data.size === 1) + { + func += ` + if(uv.${i} !== ud.${i}.value) + { + ud.${i}.value = uv.${i} + gl.uniform1f(ud.${i}.location, uv.${i}) + }\n`; + } + /* eslint-disable max-len */ + else if ((data.type === 'sampler2D' || data.type === 'samplerCube' || data.type === 'sampler2DArray') && data.size === 1 && !data.isArray) + /* eslint-disable max-len */ + { + func += ` + renderer.texture.bind(uv.${i}, ${textureCount}); + + if(ud.${i}.value !== ${textureCount}) + { + ud.${i}.value = ${textureCount}; + gl.uniform1i(ud.${i}.location, ${textureCount});\n; // eslint-disable-line max-len + }\n`; + + textureCount++; + } + else if (data.type === 'mat3' && data.size === 1) + { + if (group.uniforms[i].a !== undefined) + { + // TODO and some smart caching dirty ids here! + func += ` + gl.uniformMatrix3fv(ud.${i}.location, false, uv.${i}.toArray(true)); + \n`; + } + else + { + func += ` + gl.uniformMatrix3fv(ud.${i}.location, false, uv.${i}); + \n`; + } + } + else if (data.type === 'vec2' && data.size === 1) + { + // TODO - do we need both here? + // maybe we can get away with only using points? + if (group.uniforms[i].x !== undefined) + { + func += ` + cv = ud.${i}.value; + v = uv.${i}; + + if(cv[0] !== v.x || cv[1] !== v.y) + { + cv[0] = v.x; + cv[1] = v.y; + gl.uniform2f(ud.${i}.location, v.x, v.y); + }\n`; + } + else + { + func += ` + cv = ud.${i}.value; + v = uv.${i}; + + if(cv[0] !== v[0] || cv[1] !== v[1]) + { + cv[0] = v[0]; + cv[1] = v[1]; + gl.uniform2f(ud.${i}.location, v[0], v[1]); + } + \n`; + } + } + else if (data.type === 'vec4' && data.size === 1) + { + // TODO - do we need both here? + // maybe we can get away with only using points? + if (group.uniforms[i].width !== undefined) + { + func += ` + cv = ud.${i}.value; + v = uv.${i}; + + if(cv[0] !== v.x || cv[1] !== v.y || cv[2] !== v.width || cv[3] !== v.height) + { + cv[0] = v.x; + cv[1] = v.y; + cv[2] = v.width; + cv[3] = v.height; + gl.uniform4f(ud.${i}.location, v.x, v.y, v.width, v.height) + }\n`; + } + else + { + func += ` + cv = ud.${i}.value; + v = uv.${i}; + + if(cv[0] !== v[0] || cv[1] !== v[1] || cv[2] !== v[2] || cv[3] !== v[3]) + { + cv[0] = v[0]; + cv[1] = v[1]; + cv[2] = v[2]; + cv[3] = v[3]; + + gl.uniform4f(ud.${i}.location, v[0], v[1], v[2], v[3]) + } + \n`; + } + } + else + { + const templateType = (data.size === 1) ? GLSL_TO_SINGLE_SETTERS_CACHED : GLSL_TO_ARRAY_SETTERS; + + const template = templateType[data.type].replace('location', `ud.${i}.location`); + + func += ` + cv = ud.${i}.value; + v = uv.${i}; + ${template};\n`; + } + } + + // console.log(' --------------- ') + // console.log(func); + + return new Function('ud', 'uv', 'renderer', func); // eslint-disable-line no-new-func +} diff --git a/packages/core/src/shader/utils/getTestContext.js b/packages/core/src/shader/utils/getTestContext.js new file mode 100644 index 0000000..ea174cc --- /dev/null +++ b/packages/core/src/shader/utils/getTestContext.js @@ -0,0 +1,48 @@ +import { settings } from '@pixi/settings'; + +let context = null; + +/** + * returns a little webGL context to use for program inspection. + * + * @static + * @private + * @returns {webGL-context} a gl context to test with + */ +export default function getTestContext() +{ + if (!context) + { + const canvas = document.createElement('canvas'); + + let gl; + + if (settings.PREFER_WEBGL_2) + { + gl = canvas.getContext('webgl2', {}); + } + + if (!gl) + { + gl = canvas.getContext('webgl', {}) + || canvas.getContext('experimental-webgl', {}); + + if (!gl) + { + // fail, not able to get a context + throw new Error('This browser does not support webGL. Try using the canvas renderer'); + } + else + { + // for shader testing.. + gl.getExtension('WEBGL_draw_buffers'); + } + } + + context = gl; + + return gl; + } + + return context; +} diff --git a/packages/core/src/shader/utils/index.js b/packages/core/src/shader/utils/index.js new file mode 100644 index 0000000..4bf8dd4 --- /dev/null +++ b/packages/core/src/shader/utils/index.js @@ -0,0 +1,8 @@ +export { default as compileProgram } from './compileProgram'; +export { default as defaultValue } from './defaultValue'; +export { default as setPrecision } from './setPrecision'; +export { default as mapSize } from './mapSize'; +export { default as mapType } from './mapType'; +export { default as generateUniformsSync } from './generateUniformsSync'; +export { default as getTestContext } from './getTestContext'; +export { default as checkMaxIfStatmentsInShader } from './checkMaxIfStatmentsInShader'; diff --git a/packages/core/src/shader/utils/mapSize.js b/packages/core/src/shader/utils/mapSize.js new file mode 100644 index 0000000..b681554 --- /dev/null +++ b/packages/core/src/shader/utils/mapSize.js @@ -0,0 +1,33 @@ +const GLSL_TO_SIZE = { + float: 1, + vec2: 2, + vec3: 3, + vec4: 4, + + int: 1, + ivec2: 2, + ivec3: 3, + ivec4: 4, + + bool: 1, + bvec2: 2, + bvec3: 3, + bvec4: 4, + + mat2: 4, + mat3: 9, + mat4: 16, + + sampler2D: 1, +}; + +/** + * @method mapSize + * @memberof PIXI.glCore.shader + * @param type {String} + * @return {Number} + */ +export default function mapSize(type) +{ + return GLSL_TO_SIZE[type]; +} diff --git a/packages/core/src/shader/utils/mapType.js b/packages/core/src/shader/utils/mapType.js new file mode 100644 index 0000000..d288b7c --- /dev/null +++ b/packages/core/src/shader/utils/mapType.js @@ -0,0 +1,45 @@ +let GL_TABLE = null; + +const GL_TO_GLSL_TYPES = { + FLOAT: 'float', + FLOAT_VEC2: 'vec2', + FLOAT_VEC3: 'vec3', + FLOAT_VEC4: 'vec4', + + INT: 'int', + INT_VEC2: 'ivec2', + INT_VEC3: 'ivec3', + INT_VEC4: 'ivec4', + + BOOL: 'bool', + BOOL_VEC2: 'bvec2', + BOOL_VEC3: 'bvec3', + BOOL_VEC4: 'bvec4', + + FLOAT_MAT2: 'mat2', + FLOAT_MAT3: 'mat3', + FLOAT_MAT4: 'mat4', + + SAMPLER_2D: 'sampler2D', + SAMPLER_CUBE: 'samplerCube', + SAMPLER_2D_ARRAY: 'sampler2DArray', +}; + +export default function mapType(gl, type) +{ + if (!GL_TABLE) + { + const typeNames = Object.keys(GL_TO_GLSL_TYPES); + + GL_TABLE = {}; + + for (let i = 0; i < typeNames.length; ++i) + { + const tn = typeNames[i]; + + GL_TABLE[gl[tn]] = GL_TO_GLSL_TYPES[tn]; + } + } + + return GL_TABLE[type]; +} diff --git a/packages/core/src/shader/utils/setPrecision.js b/packages/core/src/shader/utils/setPrecision.js new file mode 100644 index 0000000..e2c1f34 --- /dev/null +++ b/packages/core/src/shader/utils/setPrecision.js @@ -0,0 +1,18 @@ +/** + * Sets the float precision on the shader. If the precision is already present this function will do nothing + * @param {string} src the shader source + * @param {string} precision The float precision of the shader. Options are 'lowp', 'mediump' or 'highp'. + * + * @return {string} modified shader source + */ +export default function setPrecision(src, precision) +{ + src = src.trim(); + + if (src.substring(0, 9) !== 'precision' && src.substring(0, 1) !== '#') + { + return `precision ${precision} float;\n${src}`; + } + + return src; +} diff --git a/packages/core/src/AbstractRenderer.js b/packages/core/src/AbstractRenderer.js new file mode 100644 index 0000000..64b2eb1 --- /dev/null +++ b/packages/core/src/AbstractRenderer.js @@ -0,0 +1,353 @@ +import { hex2string, hex2rgb } from '@pixi/utils'; +import { Matrix, Rectangle } from '@pixi/math'; +import { RENDERER_TYPE } from '@pixi/constants'; +import { settings } from '@pixi/settings'; +import { Container } from '@pixi/display'; +import RenderTexture from './renderTexture/RenderTexture'; +import EventEmitter from 'eventemitter3'; + +const tempMatrix = new Matrix(); + +/** + * 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 EventEmitter + * @memberof PIXI + */ +export default class AbstractRenderer extends EventEmitter +{ + // eslint-disable-next-line valid-jsdoc + /** + * @param {string} system - The name of the system this renderer is for. + * @param {object} [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.transparent=false] - If the render view is transparent, default false + * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false + * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. The + * resolution of the renderer retina would be 2. + * @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 {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when rendering, + * stopping pixel interpolation. + */ + constructor(system, options, arg2, arg3) + { + super(); + + // Support for constructor(system, screenWidth, screenHeight, options) + if (typeof options === 'number') + { + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + }, arg3); + } + + // 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 = RENDERER_TYPE.UNKNOWN; + + /** + * Measurements of the screen. (0, 0, screenWidth, screenHeight) + * + * Its safe to use as filterArea or hitArea for 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 1 + */ + this.resolution = options.resolution || settings.RESOLUTION; + + /** + * Whether the render view is transparent + * + * @member {boolean} + */ + this.transparent = options.transparent; + + /** + * Whether css dimensions of canvas view should be resized to screen dimensions automatically + * + * @member {boolean} + */ + this.autoResize = options.autoResize || false; + + /** + * Tracks the blend modes useful for this renderer. + * + * @member {object} + */ + this.blendModes = null; + + /** + * 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; + + /** + * If true PixiJS will Math.floor() x/y values when rendering, stopping pixel interpolation. + * Handy for crisp pixel art and speed on legacy devices. + * + * @member {boolean} + */ + this.roundPixels = options.roundPixels; + + /** + * The background color as a number. + * + * @member {number} + * @private + */ + this._backgroundColor = 0x000000; + + /** + * The background color as an [R, G, B] array. + * + * @member {number[]} + * @private + */ + this._backgroundColorRgba = [0, 0, 0, 0]; + + /** + * The background color as a string. + * + * @member {string} + * @private + */ + this._backgroundColorString = '#000000'; + + this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter + + /** + * This temporary display object used as the parent of the currently being rendered item + * + * @member {PIXI.DisplayObject} + * @private + */ + this._tempDisplayObjectParent = new Container(); + + /** + * The last root object that the renderer tried to render. + * + * @member {PIXI.DisplayObject} + * @private + */ + this._lastObjectRendered = this._tempDisplayObjectParent; + + /** + * Collection of plugins. + * @readonly + * @member {object} + */ + this.plugins = {}; + } + + /** + * Initialize the plugins. + * + * @protected + * @param {object} staticMap - The dictionary of staticly saved plugins. + */ + initPlugins(staticMap) + { + 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() + { + return this.view.width; + } + + /** + * Same as view.height, actual number of pixels in the canvas by vertical + * + * @member {number} + * @readonly + * @default 600 + */ + get height() + { + return this.view.height; + } + + /** + * Resizes the screen and canvas to the specified width and height + * Canvas dimensions are multiplied by resolution + * + * @param {number} screenWidth - the new width of the screen + * @param {number} screenHeight - the new height of the screen + */ + resize(screenWidth, screenHeight) + { + this.screen.width = screenWidth; + this.screen.height = screenHeight; + + this.view.width = screenWidth * this.resolution; + this.view.height = screenHeight * this.resolution; + + if (this.autoResize) + { + this.view.style.width = `${screenWidth}px`; + this.view.style.height = `${screenHeight}px`; + } + } + + /** + * 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 {PIXI.DisplayObject} displayObject - The displayObject the object will be generated from + * @param {number} scaleMode - Should be one of the scaleMode consts + * @param {number} resolution - The resolution / device pixel ratio of the texture being generated + * @param {PIXI.Rectangle} [region] - The region of the displayObject, that shall be rendered, + * if no region is specified, defaults to the local bounds of the displayObject. + * @return {PIXI.Texture} a texture of the graphics object + */ + generateTexture(displayObject, scaleMode, resolution, region) + { + region = region || displayObject.getLocalBounds(); + + // 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(region.width | 0, region.height | 0, scaleMode, resolution); + + tempMatrix.tx = -region.x; + tempMatrix.ty = -region.y; + + this.render(displayObject, renderTexture, false, tempMatrix, true); + + return renderTexture; + } + + /** + * Removes everything from the renderer and optionally removes the Canvas DOM element. + * + * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. + */ + destroy(removeView) + { + 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); + } + + this.plugins = null; + + this.type = RENDERER_TYPE.UNKNOWN; + + this.view = null; + + this.screen = null; + + this.resolution = 0; + + this.transparent = false; + + this.autoResize = false; + + this.blendModes = null; + + this.options = null; + + this.preserveDrawingBuffer = false; + this.clearBeforeRender = false; + + this.roundPixels = false; + + this._backgroundColor = 0; + this._backgroundColorRgba = null; + this._backgroundColorString = null; + + this._tempDisplayObjectParent = null; + this._lastObjectRendered = null; + } + + /** + * The background color to fill if not transparent + * + * @member {number} + */ + get backgroundColor() + { + return this._backgroundColor; + } + + set backgroundColor(value) // eslint-disable-line require-jsdoc + { + this._backgroundColor = value; + this._backgroundColorString = hex2string(value); + hex2rgb(value, this._backgroundColorRgba); + } +} diff --git a/packages/core/src/Renderer.js b/packages/core/src/Renderer.js new file mode 100644 index 0000000..2227e78 --- /dev/null +++ b/packages/core/src/Renderer.js @@ -0,0 +1,338 @@ +import AbstractRenderer from './AbstractRenderer'; +import { sayHello } from '@pixi/utils'; +import MaskSystem from './mask/MaskSystem'; +import StencilSystem from './mask/StencilSystem'; +import FilterSystem from './filters/FilterSystem'; +import FramebufferSystem from './framebuffer/FramebufferSystem'; +import RenderTextureSystem from './renderTexture/RenderTextureSystem'; +import TextureSystem from './textures/TextureSystem'; +import ProjectionSystem from './projection/ProjectionSystem'; +import StateSystem from './state/StateSystem'; +import GeometrySystem from './geometry/GeometrySystem'; +import ShaderSystem from './shader/ShaderSystem'; +import ContextSystem from './context/ContextSystem'; +import BatchSystem from './batch/BatchSystem'; +import TextureGCSystem from './textures/TextureGCSystem'; +import { RENDERER_TYPE } from '@pixi/constants'; +import UniformGroup from './shader/UniformGroup'; +import { Matrix } from '@pixi/math'; +import Runner from 'mini-runner'; + +/** + * 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 Render works by automatically managing webGLBatchs. + * 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 :) + * + * @class + * @memberof PIXI + * @extends PIXI.AbstractRenderer + */ +export default class Renderer extends AbstractRenderer +{ + // eslint-disable-next-line valid-jsdoc + /** + * + * @param {object} [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.transparent=false] - If the render view is transparent, default false + * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false + * @param {boolean} [options.antialias=false] - sets antialias. If not available natively then FXAA + * antialiasing is used + * @param {boolean} [options.forceFXAA=false] - forces FXAA antialiasing to be used over native. + * FXAA is faster, but may not always look as great + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. + * The resolution of the renderer retina would be 2. + * @param {boolean} [options.clearBeforeRender=true] - This sets if the renderer will clear + * the canvas or not before the new render pass. If you wish to set this to false, you *must* set + * preserveDrawingBuffer to `true`. + * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, + * enable this if you need to call toDataUrl on the webgl context. + * @param {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when + * rendering, stopping pixel interpolation. + * @param {number} [options.backgroundColor=0x000000] - The background color of the rendered area + * (shown if not transparent). + * @param {boolean} [options.legacy=false] - If true PixiJS will aim to ensure compatibility + * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. + * @param {string} [options.powerPreference] - Parameter passed to webgl context, set to "high-performance" + * for devices with dual graphics card + */ + constructor(options, arg2, arg3) + { + super('WebGL', options, arg2, arg3); + + /** + * The type of this renderer as a standardised const + * + * @member {number} + * @see PIXI.RENDERER_TYPE + */ + this.type = RENDERER_TYPE.WEBGL; + + // this will be set by the contextSystem (this.context) + this.gl = null; + this.CONTEXT_UID = 0; + this.legacy = !!options.legacy; + + // TODO legacy! + + // runners! + this.runners = { + destroy: new Runner('destroy'), + contextChange: new Runner('contextChange', 1), + reset: new Runner('reset'), + update: new Runner('update'), + postrender: new Runner('postrender'), + prerender: new Runner('prerender'), + resize: new Runner('resize', 2), + }; + + 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(GeometrySystem, 'geometry') + .addSystem(FramebufferSystem, 'framebuffer') + .addSystem(StencilSystem, 'stencil') + .addSystem(ProjectionSystem, 'projection') + .addSystem(TextureGCSystem, 'textureGC') + .addSystem(FilterSystem, 'filter') + .addSystem(RenderTextureSystem, 'renderTexture') + .addSystem(BatchSystem, 'batch'); + + this.initPlugins(Renderer.__plugins); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ + if (options.context) + { + this.context.initFromContext(options.context); + } + else + { + this.context.initFromOptions({ + alpha: this.transparent, + antialias: options.antialias, + premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', + stencil: true, + 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); + } + + /** + * Add a new system to the renderer. + * @param {class} ClassRef - Class reference + * @param {string} [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. + * @return {PIXI.Renderer} Return instance of renderer + */ + addSystem(ClassRef, name) + { + if (!name) + { + name = ClassRef.name; + } + + // TODO - read name from class.name.. + + /* + if(name.includes('System')) + { + name = name.replace('System', ''); + name = name.charAt(0).toLowerCase() + name.slice(1); + } + */ + + const system = new ClassRef(this); + + if (this[name]) + { + throw new Error(`Whoops! ${name} is already a manger`); + } + + this[name] = system; + + for (const i in this.runners) + { + this.runners[i].add(system); + } + + return this; + + /** + * 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. + */ + } + + /** + * Renders the object to its webGL view + * + * @param {PIXI.DisplayObject} displayObject - the object to be rendered + * @param {PIXI.RenderTexture} renderTexture - The render texture to render to. + * @param {boolean} [clear] - Should the canvas be cleared before the new render + * @param {PIXI.Transform} [transform] - A transform to apply to the render texture before rendering. + * @param {boolean} [skipUpdateTransform] - Should we skip the update transform pass? + */ + render(displayObject, renderTexture, clear, transform, skipUpdateTransform) + { + // can be handy to know! + this.renderingToScreen = !renderTexture; + + this.runners.prerender.run(); + this.emit('prerender'); + + // 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.parent; + + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = 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.run(); + + this.emit('postrender'); + } + + /** + * Resizes the webGL view to the specified width and height. + * + * @param {number} screenWidth - the new width of the screen + * @param {number} screenHeight - the new height of the screen + */ + resize(screenWidth, screenHeight) + { + AbstractRenderer.prototype.resize.call(this, screenWidth, screenHeight); + + this.runners.resize.run(screenWidth, screenHeight); + } + + /** + * Resets the WebGL state so you can render things however you fancy! + * + * @return {PIXI.Renderer} Returns itself. + */ + reset() + { + this.runners.reset.run(); + + return this; + } + + clear() + { + this.framebuffer.bind(); + this.framebuffer.clear(); + } + + /** + * Removes everything from the renderer (event listeners, spritebatch, etc...) + * + * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. + * See: https://github.com/pixijs/pixi.js/issues/2233 + */ + destroy(removeView) + { + this.runners.destroy.run(); + + // call base destroy + super.destroy(removeView); + + // TODO nullify all the managers.. + this.gl = null; + } + + /** + * 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 + * builds and excluding plugins. + * @name PIXI.Renderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. + */ + + /** + * Adds a plugin to the renderer. + * + * @method + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + static registerPlugin(pluginName, ctor) + { + Renderer.__plugins = Renderer.__plugins || {}; + Renderer.__plugins[pluginName] = ctor; + } +} diff --git a/packages/core/src/System.js b/packages/core/src/System.js new file mode 100644 index 0000000..05a10c8 --- /dev/null +++ b/packages/core/src/System.js @@ -0,0 +1,42 @@ +/** + * System is a base class used for extending systems used by the {@link PIXI.Renderer} + * @see PIXI.Renderer#addSystem + * @class + * @memberof PIXI + */ +export default class System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + /** + * The renderer this manager works for. + * + * @member {PIXI.Renderer} + */ + this.renderer = renderer; + + this.renderer.runners.contextChange.add(this); + } + + /** + * Generic method called when there is a WebGL context change. + * + */ + contextChange() + { + // do some codes init! + } + + /** + * Generic destroy methods to be overridden by the subclass + * + */ + destroy() + { + this.renderer.runners.contextChange.remove(this); + this.renderer = null; + } +} diff --git a/packages/core/src/batch/BatchSystem.js b/packages/core/src/batch/BatchSystem.js new file mode 100644 index 0000000..b04c249 --- /dev/null +++ b/packages/core/src/batch/BatchSystem.js @@ -0,0 +1,68 @@ +import System from '../System'; +import ObjectRenderer from './ObjectRenderer'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class BatchSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + super(renderer); + + /** + * An empty renderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.emptyRenderer = new ObjectRenderer(renderer); + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.currentRenderer = this.emptyRenderer; + } + + /** + * Changes the current renderer to the one given in parameter + * + * @param {PIXI.ObjectRenderer} objectRenderer - The object renderer to use. + */ + setObjectRenderer(objectRenderer) + { + if (this.currentRenderer === objectRenderer) + { + return; + } + + this.currentRenderer.stop(); + this.currentRenderer = objectRenderer; + + this.renderer.state.setState(objectRenderer.state); + + this.currentRenderer.start(); + } + + /** + * This should be called if you wish to do some custom rendering + * It will basically render anything that may be batched up such as sprites + * + */ + flush() + { + this.setObjectRenderer(this.emptyRenderer); + } + + reset() + { + this.setObjectRenderer(this.emptyRenderer); + } +} diff --git a/packages/core/src/batch/ObjectRenderer.js b/packages/core/src/batch/ObjectRenderer.js new file mode 100644 index 0000000..0c851a9 --- /dev/null +++ b/packages/core/src/batch/ObjectRenderer.js @@ -0,0 +1,48 @@ +import System from '../System'; + +/** + * Base for a common object renderer that can be used as a system renderer plugin. + * + * @class + * @extends PIXI.System + * @memberof PIXI + */ +export default class ObjectRenderer extends System +{ + /** + * Starts the renderer and sets the shader + * + */ + start() + { + // set the shader.. + } + + /** + * Stops the renderer + * + */ + stop() + { + this.flush(); + } + + /** + * Stub method for rendering content and emptying the current batch. + * + */ + flush() + { + // flush! + } + + /** + * Renders an object + * + * @param {PIXI.DisplayObject} object - The object to render. + */ + render(object) // eslint-disable-line no-unused-vars + { + // render the object + } +} diff --git a/packages/core/src/context/ContextSystem.js b/packages/core/src/context/ContextSystem.js new file mode 100644 index 0000000..301aab4 --- /dev/null +++ b/packages/core/src/context/ContextSystem.js @@ -0,0 +1,186 @@ +import System from '../System'; +import { settings } from '@pixi/settings'; + +let CONTEXT_UID = 0; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class ContextSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.webGLVersion = 1; + + this.handleContextLost = this.handleContextLost.bind(this); + this.handleContextRestored = this.handleContextRestored.bind(this); + + this.extensions = {}; + + renderer.view.addEventListener('webglcontextlost', this.handleContextLost, false); + renderer.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + } + + get isLost() + { + return (!this.gl || this.gl.isContextLost()); + } + + contextChange(gl) + { + this.gl = gl; + + // restore a context if it was previously lost + if (gl.isContextLost() && gl.getExtension('WEBGL_lose_context')) + { + gl.getExtension('WEBGL_lose_context').restoreContext(); + } + } + + initFromContext(gl) + { + this.gl = gl; + this.validateContext(gl); + this.renderer.gl = gl; + this.renderer.CONTEXT_UID = CONTEXT_UID++; + this.renderer.runners.contextChange.run(gl); + } + + initFromOptions(options) + { + const gl = this.createContext(this.renderer.view, options); + + this.initFromContext(gl); + } + + /** + * Helper class to create a webGL Context + * + * @class + * @memberof PIXI.glCore + * @param canvas {HTMLCanvasElement} the canvas element that we will get the context from + * @param options {Object} An options object that gets passed in to the canvas element containing the context attributes, + * see https://developer.mozilla.org/en/docs/Web/API/HTMLCanvasElement/getContext for the options available + * @return {WebGLRenderingContext} the WebGL context + */ + createContext(canvas, options) + { + let gl; + + if (settings.PREFER_WEBGL_2) + { + gl = canvas.getContext('webgl2', options); + } + + if (gl) + { + this.webGLVersion = 2; + } + else + { + this.webGLVersion = 1; + + gl = canvas.getContext('webgl', options) + || canvas.getContext('experimental-webgl', options); + + if (!gl) + { + // fail, not able to get a context + throw new Error('This browser does not support webGL. Try using the canvas renderer'); + } + } + + this.gl = gl; + + this.getExtensions(); + + return gl; + } + + getExtensions() + { + // time to set up default etensions that pixi uses.. + const gl = this.gl; + const extensions = this.extensions; + + if (this.webGLVersion === 1) + { + extensions.drawBuffers = gl.getExtension('WEBGL_draw_buffers'); + extensions.depthTexture = gl.getExtension('WEBKIT_WEBGL_depth_texture'); + extensions.floatTexture = gl.getExtension('OES_texture_float'); + extensions.loseContext = gl.getExtension('WEBGL_lose_context'); + + extensions.vertexArrayObject = gl.getExtension('OES_vertex_array_object') + || gl.getExtension('MOZ_OES_vertex_array_object') + || gl.getExtension('WEBKIT_OES_vertex_array_object'); + } + + // we don't use any specific WebGL 2 ones yet! + } + + /** + * Handles a lost webgl context + * + * @private + * @param {WebGLContextEvent} event - The context lost event. + */ + handleContextLost(event) + { + event.preventDefault(); + } + + /** + * Handles a restored webgl context + * + * @private + */ + handleContextRestored() + { + this.renderer.runners.contextChange.run(this.gl); + } + + destroy() + { + const view = this.renderer.view; + + // remove listeners + view.removeEventListener('webglcontextlost', this.handleContextLost); + view.removeEventListener('webglcontextrestored', this.handleContextRestored); + + this.gl.useProgram(null); + + if (this.extensions.loseContext) + { + this.extensions.loseContext.loseContext(); + } + } + + postrender() + { + this.gl.flush(); + } + + validateContext(gl) + { + const attributes = gl.getContextAttributes(); + + // this is going to be fairly simple for now.. but at least we have room to grow! + if (!attributes.stencil) + { + /* eslint-disable max-len */ + + /* eslint-disable no-console */ + console.warn('Provided WebGL context does not have a stencil buffer, masks may not render correctly'); + /* eslint-enable no-console */ + + /* eslint-enable max-len */ + } + } +} diff --git a/packages/core/src/filters/Filter.js b/packages/core/src/filters/Filter.js new file mode 100644 index 0000000..9d8cad8 --- /dev/null +++ b/packages/core/src/filters/Filter.js @@ -0,0 +1,140 @@ +import Shader from '../shader/Shader'; +import Program from '../shader/Program'; +import State from '../state/State'; +import { settings } from '@pixi/settings'; +// import extractUniformsFromSrc from './extractUniformsFromSrc'; +import defaultVertex from './defaultFilter.vert'; +import defaultFragment from './defaultFilter.frag'; + +// let math = require('../../math'); +/** + * @class + * @memberof PIXI + * @extends PIXI.Shader + */ +export default class Filter extends Shader +{ + /** + * @param {string} [vertexSrc] - The source of the vertex shader. + * @param {string} [fragmentSrc] - The source of the fragment shader. + * @param {object} [uniforms] - Custom uniforms to use to augment the built-in ones. + */ + constructor(vertexSrc, fragmentSrc, uniforms) + { + const program = Program.from(vertexSrc, fragmentSrc); + + super(program, uniforms); + + /** + * The padding of the filter. Some filters require extra space to breath such as a blur. + * Increasing this will add extra width and height to the bounds of the object that the + * filter is applied to. + * + * @member {number} + */ + this.padding = 0; + + /** + * The resolution of the filter. Setting this to be lower will lower the quality but + * increase the performance of the filter. + * + * @member {number} + */ + this.resolution = settings.RESOLUTION; + + /** + * If enabled is true the filter is applied, if false it will not. + * + * @member {boolean} + */ + this.enabled = true; + + /** + * If enabled, PixiJS will fit the filter area into boundaries for better performance. + * Switch it off if it does not work for specific shader. + * + * @member {boolean} + */ + this.autoFit = true; + + /** + * Legacy filters use position and uvs from attributes + * @member {boolean} + * @readonly + */ + this.legacy = !!this.program.attributeData.aTextureCoord; + + /** + * the webGL state the filter requires to render + * @member {PIXI.State} + */ + this.state = new State(); + } + + /** + * Applies the filter + * + * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + * @param {boolean} clear - Should the output be cleared before rendering to it + * @param {object} [currentState] - It's current state of filter. + * There are some useful properties in the currentState : + * target, filters, sourceFrame, destinationFrame, renderTarget, resolution + */ + apply(filterManager, input, output, clear, currentState, derp) // eslint-disable-line no-unused-vars + { + // do as you please! + + filterManager.applyFilter(this, input, output, clear, currentState, derp); + + // or just do a regular render.. + } + + /** + * Sets the blendmode of the filter + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL + */ + get blendMode() + { + return this.state.blendMode; + } + + set blendMode(value) // eslint-disable-line require-jsdoc + { + this.state.blendMode = value; + } + + /** + * The default vertex shader source + * + * @static + * @constant + */ + static get defaultVertexSrc() + { + return defaultVertex; + } + + /** + * The default fragment shader source + * + * @static + * @constant + */ + static get defaultFragmentSrc() + { + return defaultFragment; + } +} + +/** + * Used for caching shader IDs + * + * @static + * @private + */ +Filter.SOURCE_KEY_MAP = {}; + diff --git a/packages/core/src/filters/FilterSystem.js b/packages/core/src/filters/FilterSystem.js new file mode 100644 index 0000000..3174318 --- /dev/null +++ b/packages/core/src/filters/FilterSystem.js @@ -0,0 +1,431 @@ +import System from '../System'; + +import RenderTexture from '../renderTexture/RenderTexture'; +import Quad from '../utils/Quad'; +import QuadUv from '../utils/QuadUv'; +import { Rectangle } from '@pixi/math'; +import * as filterTransforms from './filterTransforms'; +import bitTwiddle from 'bit-twiddle'; +import UniformGroup from '../shader/UniformGroup'; +import { DRAW_MODES } from '@pixi/constants'; + +// +/** + * @ignore + * @class + */ +class FilterState +{ + /** + * + */ + constructor() + { + this.renderTexture = null; + this.sourceFrame = new Rectangle(); + this.destinationFrame = new Rectangle(); + this.filters = []; + this.target = null; + this.legacy = false; + this.resolution = 1; + } +} + +/** + * @class + * @memberof PIXI.systems + * @extends PIXI.System + */ +export default class FilterSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + /** + * stores a bunch of PO2 textures used for filtering + * @type {Object} + */ + this.texturePool = {}; + + /** + * a pool for storing filter states, save us creating new ones each tick + * @type {Array} + */ + this.statePool = []; + + /** + * A very simple geometry used when drawing a filter effect to the screen + * @type {Quad} + */ + this.quad = new Quad(); + + this.quadUv = new QuadUv(); + + /** + * Temporary rect for maths + * @type {PIXI.Rectangle} + */ + this.tempRect = new Rectangle(); + + this.activeState = {}; + + /** + * this uniform group is attached to filter uniforms when used + * @type {UniformGroup} + */ + this.globalUniforms = new UniformGroup({ + sourceFrame: this.tempRect, + destinationFrame: this.tempRect, + + // legacy variables + filterArea: new Float32Array(4), + filterClamp: new Float32Array(4), + }, true); + } + + /** + * Adds a new filter to the System. + * + * @param {PIXI.DisplayObject} target - The target of the filter to render. + * @param {PIXI.Filter[]} filters - The filters to apply. + */ + push(target, filters) + { + const renderer = this.renderer; + const filterStack = this.renderer.renderTexture.defaultFilterStack; + const state = this.statePool.pop() || new FilterState(); + + let resolution = filters[0].resolution; + let padding = filters[0].padding; + let autoFit = filters[0].autoFit; + let legacy = filters[0].legacy; + + for (let i = 1; i < filters.length; i++) + { + const filter = filters[i]; + + // lets use the lowest resolution.. + resolution = Math.min(resolution, filter.resolution); + // and the largest amount of padding! + padding = Math.max(padding, filter.padding); + // only auto fit if all filters are autofit + autoFit = autoFit || filter.autoFit; + + legacy = legacy || filter.legacy; + } + + filterStack.push(state); + + state.resolution = resolution; + + state.legacy = legacy; + + // round to whole number based on resolution + // TODO move that to the shader too? + state.sourceFrame = target.filterArea || target.getBounds(true); + + state.sourceFrame.pad(padding); + + if (autoFit) + { + state.sourceFrame.fit(this.renderer.renderTexture.destinationFrame); + } + + state.sourceFrame.round(resolution); + + state.renderTexture = this.getPotFilterTexture(state.sourceFrame.width, state.sourceFrame.height, resolution); + state.filters = filters; + + state.destinationFrame.width = state.renderTexture.width; + state.destinationFrame.height = state.renderTexture.height; + + state.renderTexture.filterFrame = state.sourceFrame; + renderer.renderTexture.bind(state.renderTexture, state.sourceFrame);// /, state.destinationFrame); + renderer.renderTexture.clear(); + } + + /** + * Pops off the filter and applies it. + * + */ + pop() + { + const renderer = this.renderer; + const filterStack = renderer.renderTexture.defaultFilterStack; + const state = filterStack.pop(); + const filters = state.filters; + + this.activeState = state; + + const globalUniforms = this.globalUniforms.uniforms; + + globalUniforms.sourceFrame = state.sourceFrame; + globalUniforms.destinationFrame = state.destinationFrame; + globalUniforms.resolution = state.resolution; + + // only update the rect if its legacy.. + if (state.legacy) + { + const filterArea = globalUniforms.filterArea; + const filterClamp = globalUniforms.filterClamp; + + filterArea[0] = state.destinationFrame.width; + filterArea[1] = state.destinationFrame.height; + filterArea[2] = state.sourceFrame.x; + filterArea[3] = state.sourceFrame.y; + + filterClamp[0] = 0.5 / state.resolution / state.destinationFrame.width; + filterClamp[1] = 0.5 / state.resolution / state.destinationFrame.height; + filterClamp[2] = (state.sourceFrame.width - 0.5) / state.resolution / state.destinationFrame.width; + filterClamp[3] = (state.sourceFrame.height - 0.5) / state.resolution / state.destinationFrame.height; + } + + this.globalUniforms.update(); + + const lastState = filterStack[filterStack.length - 1]; + + if (filters.length === 1) + { + filters[0].apply(this, state.renderTexture, lastState.renderTexture, false, state); + + this.returnFilterTexture(state.renderTexture); + } + else + { + let flip = state.renderTexture; + let flop = this.getPotFilterTexture( + flip.width, + flip.height, + state.resolution + ); + + flop.filterFrame = flip.filterFrame; + + let i = 0; + + for (i = 0; i < filters.length - 1; ++i) + { + filters[i].apply(this, flip, flop, true, state); + + const t = flip; + + flip = flop; + flop = t; + } + + filters[i].apply(this, flip, lastState.renderTexture, false, state); + + this.returnFilterTexture(flip); + this.returnFilterTexture(flop); + } + + this.statePool.push(state); + } + + /** + * Draws a filter. + * + * @param {PIXI.Filter} filter - The filter to draw. + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + * @param {boolean} clear - Should the output be cleared before rendering to it + */ + applyFilter(filter, input, output, clear) + { + const renderer = this.renderer; + + renderer.renderTexture.bind(output, output ? output.filterFrame : null); + + if (clear) + { + // gl.disable(gl.SCISSOR_TEST); + renderer.renderTexture.clear(); + // gl.enable(gl.SCISSOR_TEST); + } + + // set the uniforms.. + filter.uniforms.uSampler = input; + filter.uniforms.filterGlobals = this.globalUniforms; + + // TODO make it so that the order of this does not matter.. + // because it does at the moment cos of global uniforms. + // they need to get resynced + + renderer.state.setState(filter.state); + renderer.shader.bind(filter); + + if (filter.legacy) + { + this.quadUv.map(input._frame, input.filterFrame); + + renderer.geometry.bind(this.quadUv); + renderer.geometry.draw(DRAW_MODES.TRIANGLES); + } + else + { + renderer.geometry.bind(this.quad); + renderer.geometry.draw(DRAW_MODES.TRIANGLE_STRIP); + } + } + + /** + * Calculates the mapped matrix. + * + * TODO playing around here.. this is temporary - (will end up in the shader) + * this returns a matrix that will normalise map filter cords in the filter to screen space + * + * @param {PIXI.Matrix} outputMatrix - the matrix to output to. + * @return {PIXI.Matrix} The mapped matrix. + */ + calculateScreenSpaceMatrix(outputMatrix) + { + const currentState = this.activeState; + + return filterTransforms.calculateScreenSpaceMatrix( + outputMatrix, + currentState.sourceFrame, + currentState.destinationFrame + ); + } + + /** + * This will map the filter coord so that a texture can be used based on the transform of a sprite + * + * @param {PIXI.Matrix} outputMatrix - The matrix to output to. + * @param {PIXI.Sprite} sprite - The sprite to map to. + * @return {PIXI.Matrix} The mapped matrix. + */ + calculateSpriteMatrix(outputMatrix, sprite) + { + const currentState = this.activeState; + + return filterTransforms.calculateSpriteMatrix( + outputMatrix, + currentState.sourceFrame, + currentState.destinationFrame, + sprite + ); + } + + /** + * Destroys this Filter System. + * + * @param {boolean} [contextLost=false] context was lost, do not free shaders + * + */ + destroy(contextLost = false) + { + if (!contextLost) + { + this.emptyPool(); + } + else + { + this.texturePool = {}; + } + } + + /** + * Gets a Power-of-Two render texture. + * + * TODO move to a seperate class could be on renderer? + * also - could cause issue with multiple contexts? + * + * @private + * @param {WebGLRenderingContext} gl - The webgl rendering context + * @param {number} minWidth - The minimum width of the render target. + * @param {number} minHeight - The minimum height of the render target. + * @param {number} resolution - The resolution of the render target. + * @return {PIXI.RenderTarget} The new render target. + */ + getPotFilterTexture(minWidth, minHeight, resolution) + { + minWidth = bitTwiddle.nextPow2(minWidth); + minHeight = bitTwiddle.nextPow2(minHeight); + resolution = resolution || 1; + + const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + + if (!this.texturePool[key]) + { + this.texturePool[key] = []; + } + + let renderTexture = this.texturePool[key].pop(); + + if (!renderTexture) + { + // temporary bypass cache.. + // internally - this will cause a texture to be bound.. + renderTexture = RenderTexture.create({ + width: minWidth, + height: minHeight, + resolution, + }); + } + + return renderTexture; + } + + /** + * Gets extra render texture to use inside current filter + * + * @param {number} resolution resolution of the renderTexture + * @returns {PIXI.RenderTexture} + */ + getFilterTexture(resolution) + { + const rt = this.activeState.renderTexture; + + const filterTexture = this.getPotFilterTexture(rt.width, rt.height, resolution || rt.baseTexture.resolution); + + filterTexture.filterFrame = rt.filterFrame; + + return filterTexture; + } + + /** + * Frees a render texture back into the pool. + * + * @param {PIXI.RenderTarget} renderTexture - The renderTarget to free + */ + returnFilterTexture(renderTexture) + { + renderTexture.filterFrame = null; + + const base = renderTexture.baseTexture; + + const minWidth = base.width; + const minHeight = base.height; + + const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + + this.texturePool[key].push(renderTexture); + } + + /** + * Empties the texture pool. + * + */ + emptyPool() + { + for (const i in this.texturePool) + { + const textures = this.texturePool[i]; + + if (textures) + { + for (let j = 0; j < textures.length; j++) + { + textures[j].destroy(true); + } + } + } + + this.texturePool = {}; + } +} diff --git a/packages/core/src/filters/defaultFilter.frag b/packages/core/src/filters/defaultFilter.frag new file mode 100644 index 0000000..a14c1be --- /dev/null +++ b/packages/core/src/filters/defaultFilter.frag @@ -0,0 +1,22 @@ +varying vec2 vTextureCoord; +varying vec2 vFilterCoord; + +uniform sampler2D uSampler; +uniform sampler2D filterSampler; + +void main(void){ + vec4 masky = texture2D(filterSampler, vFilterCoord); + vec4 sample = texture2D(uSampler, vTextureCoord); + vec4 color; + if(mod(vFilterCoord.x, 1.0) > 0.5) + { + color = vec4(1.0, 0.0, 0.0, 1.0); + } + else + { + color = vec4(0.0, 1.0, 0.0, 1.0); + } + // gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0); + gl_FragColor = mix(sample, masky, 0.5); + gl_FragColor *= sample.a; +} diff --git a/packages/core/src/filters/defaultFilter.vert b/packages/core/src/filters/defaultFilter.vert new file mode 100644 index 0000000..e1febca --- /dev/null +++ b/packages/core/src/filters/defaultFilter.vert @@ -0,0 +1,14 @@ +attribute vec2 aVertexPosition; +attribute vec2 aTextureCoord; + +uniform mat3 projectionMatrix; +uniform mat3 filterMatrix; + +varying vec2 vTextureCoord; +varying vec2 vFilterCoord; + +void main(void){ + gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); + vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy; + vTextureCoord = aTextureCoord ; +} diff --git a/packages/core/src/filters/filterTransforms.js b/packages/core/src/filters/filterTransforms.js new file mode 100644 index 0000000..87a3cd6 --- /dev/null +++ b/packages/core/src/filters/filterTransforms.js @@ -0,0 +1,50 @@ +import { Matrix } from '@pixi/math'; + +/** + * Calculates the mapped matrix + * @param filterArea {Rectangle} The filter area + * @param sprite {Sprite} the target sprite + * @param outputMatrix {Matrix} @alvin + * @private + */ +// this returns a matrix that will normalise map filter cords in the filter to screen space +export function calculateScreenSpaceMatrix(outputMatrix, filterArea, textureSize) +{ + // TODO unwrap? + const mappedMatrix = outputMatrix.identity(); + + mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); + + mappedMatrix.scale(textureSize.width, textureSize.height); + + return mappedMatrix; +} + +export function calculateNormalizedScreenSpaceMatrix(outputMatrix, filterArea, textureSize) +{ + const mappedMatrix = outputMatrix.identity(); + + mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); + + const translateScaleX = (textureSize.width / filterArea.width); + const translateScaleY = (textureSize.height / filterArea.height); + + mappedMatrix.scale(translateScaleX, translateScaleY); + + return mappedMatrix; +} + +// this will map the filter coord so that a texture can be used based on the transform of a sprite +export function calculateSpriteMatrix(outputMatrix, filterArea, textureSize, sprite) +{ + const orig = sprite._texture.orig; + const mappedMatrix = outputMatrix.set(textureSize.width, 0, 0, textureSize.height, filterArea.x, filterArea.y); + const worldTransform = sprite.worldTransform.copyTo(Matrix.TEMP_MATRIX); + + worldTransform.invert(); + mappedMatrix.prepend(worldTransform); + mappedMatrix.scale(1.0 / orig.width, 1.0 / orig.height); + mappedMatrix.translate(sprite.anchor.x, sprite.anchor.y); + + return mappedMatrix; +} diff --git a/packages/core/src/filters/spriteMask/SpriteMaskFilter.js b/packages/core/src/filters/spriteMask/SpriteMaskFilter.js new file mode 100644 index 0000000..91697b7 --- /dev/null +++ b/packages/core/src/filters/spriteMask/SpriteMaskFilter.js @@ -0,0 +1,63 @@ +import Filter from '../Filter'; +import { Matrix } from '@pixi/math'; +import vertex from './spriteMaskFilter.vert'; +import fragment from './spriteMaskFilter.frag'; +import { default as TextureMatrix } from '../../textures/TextureMatrix'; + +/** + * The SpriteMaskFilter class + * + * @class + * @extends PIXI.Filter + * @memberof PIXI + */ +export default class SpriteMaskFilter extends Filter +{ + /** + * @param {PIXI.Sprite} sprite - the target sprite + */ + constructor(sprite) + { + const maskMatrix = new Matrix(); + + super(vertex, fragment); + + sprite.renderable = false; + + this.maskSprite = sprite; + this.maskMatrix = maskMatrix; + } + + /** + * Applies the filter + * + * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + */ + apply(filterManager, input, output) + { + const maskSprite = this.maskSprite; + const tex = this.maskSprite.texture; + + if (!tex.valid) + { + return; + } + if (!tex.transform) + { + // margin = 0.0, let it bleed a bit, shader code becomes easier + // assuming that atlas textures were made with 1-pixel padding + tex.transform = new TextureMatrix(tex, 0.0); + } + tex.transform.update(); + + this.uniforms.mask = tex; + this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite) + .prepend(tex.transform.mapCoord); + this.uniforms.alpha = maskSprite.worldAlpha; + this.uniforms.maskClamp = tex.transform.uClampFrame; + + filterManager.applyFilter(this, input, output); + } +} diff --git a/packages/core/src/filters/spriteMask/spriteMaskFilter.frag b/packages/core/src/filters/spriteMask/spriteMaskFilter.frag new file mode 100644 index 0000000..0e4aef8 --- /dev/null +++ b/packages/core/src/filters/spriteMask/spriteMaskFilter.frag @@ -0,0 +1,23 @@ +varying vec2 vMaskCoord; +varying vec2 vTextureCoord; + +uniform sampler2D uSampler; +uniform sampler2D mask; +uniform float alpha; +uniform vec4 maskClamp; + +void main(void) +{ + float clip = step(3.5, + step(maskClamp.x, vMaskCoord.x) + + step(maskClamp.y, vMaskCoord.y) + + step(vMaskCoord.x, maskClamp.z) + + step(vMaskCoord.y, maskClamp.w)); + + vec4 original = texture2D(uSampler, vTextureCoord); + vec4 masky = texture2D(mask, vMaskCoord); + + original *= (masky.r * masky.a * alpha * clip); + + gl_FragColor = original; +} diff --git a/packages/core/src/filters/spriteMask/spriteMaskFilter.vert b/packages/core/src/filters/spriteMask/spriteMaskFilter.vert new file mode 100644 index 0000000..d23a30c --- /dev/null +++ b/packages/core/src/filters/spriteMask/spriteMaskFilter.vert @@ -0,0 +1,16 @@ +attribute vec2 aVertexPosition; +attribute vec2 aTextureCoord; + +uniform mat3 projectionMatrix; +uniform mat3 otherMatrix; + +varying vec2 vMaskCoord; +varying vec2 vTextureCoord; + +void main(void) +{ + gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); + + vTextureCoord = aTextureCoord; + vMaskCoord = ( otherMatrix * vec3( aTextureCoord, 1.0) ).xy; +} diff --git a/packages/core/src/framebuffer/FrameBuffer.js b/packages/core/src/framebuffer/FrameBuffer.js new file mode 100644 index 0000000..84e9fcc --- /dev/null +++ b/packages/core/src/framebuffer/FrameBuffer.js @@ -0,0 +1,106 @@ +import Texture from '../textures/BaseTexture'; +import { FORMATS, TYPES } from '@pixi/constants'; + +/** + * Frame buffer + * @class + * @memberof PIXI + */ +export default class FrameBuffer +{ + constructor(width, height) + { + this.width = width || 100; + this.height = height || 100; + + this.stencil = false; + this.depth = false; + + this.dirtyId = 0; + this.dirtyFormat = 0; + this.dirtySize = 0; + + this.depthTexture = null; + this.colorTextures = []; + + this.glFrameBuffers = {}; + } + + get colorTexture() + { + return this.colorTextures[0]; + } + + addColorTexture(index, texture) + { + // TODO add some validation to the texture - same width / height etc? + this.colorTextures[index || 0] = texture || new Texture(null, { scaleMode: 0, + resolution: 1, + mipmap: false, + width: this.width, + height: this.height });// || new Texture(); + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + addDepthTexture(texture) + { + /* eslint-disable max-len */ + this.depthTexture = texture || new Texture(null, { scaleMode: 0, + resolution: 1, + width: this.width, + height: this.height, + mipmap: false, + format: FORMATS.DEPTH_COMPONENT, + type: TYPES.UNSIGNED_SHORT });// UNSIGNED_SHORT; + /* eslint-disable max-len */ + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + enableDepth() + { + this.depth = true; + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + enableStencil() + { + this.stencil = true; + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + resize(width, height) + { + if (width === this.width && height === this.height) return; + + this.width = width; + this.height = height; + + this.dirtyId++; + this.dirtySize++; + + for (let i = 0; i < this.colorTextures.length; i++) + { + this.colorTextures[i].setSize(width, height); + } + + if (this.depthTexture) + { + this.depthTexture.setSize(width, height); + } + } +} diff --git a/packages/core/src/framebuffer/FramebufferSystem.js b/packages/core/src/framebuffer/FramebufferSystem.js new file mode 100644 index 0000000..2cc2437 --- /dev/null +++ b/packages/core/src/framebuffer/FramebufferSystem.js @@ -0,0 +1,246 @@ +import System from '../System'; +import { Rectangle } from '@pixi/math'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class FramebufferSystem extends System +{ + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + contextChange() + { + this.gl = this.renderer.gl; + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + this.current = null; + this.viewport = new Rectangle(); + + this.drawBufferExtension = this.renderer.context.extensions.drawBuffers; + } + + bind(framebuffer, frame) + { + const gl = this.gl; + + this.current = framebuffer; + + if (framebuffer) + { + // TODO cacheing layer! + + const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID] || this.initFramebuffer(framebuffer); + + gl.bindFramebuffer(gl.FRAMEBUFFER, fbo.framebuffer); + // makesure all textures are unbound.. + + // now check for updates... + if (fbo.dirtyId !== framebuffer.dirtyId) + { + fbo.dirtyId = framebuffer.dirtyId; + + if (fbo.dirtyFormat !== framebuffer.dirtyFormat) + { + fbo.dirtyFormat = framebuffer.dirtyFormat; + this.updateFramebuffer(framebuffer); + } + else if (fbo.dirtySize !== framebuffer.dirtySize) + { + fbo.dirtySize = framebuffer.dirtySize; + this.resizeFramebuffer(framebuffer); + } + } + + for (let i = 0; i < framebuffer.colorTextures.length; i++) + { + if (framebuffer.colorTextures[i].texturePart) + { + this.renderer.texture.unbind(framebuffer.colorTextures[i].texture); + } + else + { + this.renderer.texture.unbind(framebuffer.colorTextures[i]); + } + } + + if (framebuffer.depthTexture) + { + this.renderer.texture.unbind(framebuffer.depthTexture); + } + + if (frame) + { + this.setViewport(frame.x, frame.y, frame.width, frame.height); + } + else + { + this.setViewport(0, 0, framebuffer.width, framebuffer.height); + } + } + else + { + gl.bindFramebuffer(gl.FRAMEBUFFER, null); + + if (frame) + { + this.setViewport(frame.x, frame.y, frame.width, frame.height); + } + else + { + this.setViewport(0, 0, this.renderer.width, this.renderer.height); + } + } + } + + setViewport(x, y, width, height) + { + const v = this.viewport; + + if (v.width !== width || v.height !== height || v.x !== x || v.y !== y) + { + v.x = x; + v.y = y; + v.width = width; + v.height = height; + + this.gl.viewport(x, y, width, height); + } + } + + get size() + { + if (this.current) + { + // TODO store temp + return { x: 0, y: 0, width: this.current.width, height: this.current.height }; + } + + return { x: 0, y: 0, width: this.renderer.width, height: this.renderer.height }; + } + + clear(r, g, b, a) + { + const gl = this.gl; + + // TODO clear color can be set only one right? + gl.clearColor(r, g, b, a); + gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); + } + + // private functions... + + initFramebuffer(framebuffer) + { + const gl = this.gl; + + // TODO - make this a class? + const fbo = { + framebuffer: gl.createFramebuffer(), + stencil: null, + dirtyId: 0, + dirtyFormat: 0, + dirtySize: 0, + }; + + framebuffer.glFrameBuffers[this.CONTEXT_UID] = fbo; + + return fbo; + } + + resizeFramebuffer(framebuffer) + { + const gl = this.gl; + + if (framebuffer.stencil || framebuffer.depth) + { + gl.bindRenderbuffer(gl.RENDERBUFFER, this.stencil); + gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); + } + } + + updateFramebuffer(framebuffer) + { + const gl = this.gl; + + const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID]; + + // bind the color texture + const colorTextures = framebuffer.colorTextures; + + let count = colorTextures.length; + + if (!this.drawBufferExtension) + { + count = Math.min(count, 1); + } + + const activeTextures = []; + + for (let i = 0; i < count; i++) + { + const texture = framebuffer.colorTextures[i]; + + if (texture.texturePart) + { + this.renderer.texture.bind(texture.texture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.COLOR_ATTACHMENT0 + i, + gl.TEXTURE_CUBE_MAP_NEGATIVE_X + texture.side, + texture.texture._glTextures[this.CONTEXT_UID].texture, + 0); + } + else + { + this.renderer.texture.bind(texture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.COLOR_ATTACHMENT0 + i, + gl.TEXTURE_2D, + texture._glTextures[this.CONTEXT_UID].texture, + 0); + } + + activeTextures.push(gl.COLOR_ATTACHMENT0 + i); + } + + if (this.drawBufferExtension && activeTextures.length > 1) + { + this.drawBufferExtension.drawBuffersWEBGL(activeTextures); + } + + if (framebuffer.depthTexture) + { + const depthTextureExt = this.renderer.context.extensions.depthTexture; + + if (depthTextureExt) + { + const depthTexture = framebuffer.depthTexture; + + this.renderer.texture.bind(depthTexture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.DEPTH_ATTACHMENT, + gl.TEXTURE_2D, + depthTexture._glTextures[this.CONTEXT_UID].texture, + 0); + } + } + + if (framebuffer.stencil || framebuffer.depth) + { + fbo.stencil = gl.createRenderbuffer(); + + gl.bindRenderbuffer(gl.RENDERBUFFER, fbo.stencil); + + // TODO.. this is depth AND stencil? + gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, fbo.stencil); + gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); + // fbo.enableStencil(); + } + } +} diff --git a/packages/core/src/geometry/GLBuffer.js b/packages/core/src/geometry/GLBuffer.js new file mode 100644 index 0000000..0477e8a --- /dev/null +++ b/packages/core/src/geometry/GLBuffer.js @@ -0,0 +1,9 @@ +export default class GLBuffer +{ + constructor(buffer) + { + this.buffer = buffer; + this.updateID = -1; + this.byteLength = -1; + } +} diff --git a/packages/core/src/geometry/GeometrySystem.js b/packages/core/src/geometry/GeometrySystem.js new file mode 100644 index 0000000..959f827 --- /dev/null +++ b/packages/core/src/geometry/GeometrySystem.js @@ -0,0 +1,388 @@ +import System from '../System'; +import GLBuffer from './GLBuffer'; + +const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class GeometrySystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this._activeGeometry = null; + this._activeVao = null; + + this.hasVao = true; + this.hasInstance = true; + } + + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + contextChange() + { + const gl = this.gl = this.renderer.gl; + + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + + // webgl2 + if (!gl.createVertexArray) + { + // webgl 1! + let nativeVaoExtension = this.renderer.context.extensions.vertexArrayObject; + + if (this.renderer.legacy) + { + nativeVaoExtension = null; + } + + if (nativeVaoExtension) + { + gl.createVertexArray = () => + nativeVaoExtension.createVertexArrayOES(); + + gl.bindVertexArray = (vao) => + nativeVaoExtension.bindVertexArrayOES(vao); + + gl.deleteVertexArray = (vao) => + nativeVaoExtension.deleteVertexArrayOES(vao); + } + else + { + this.hasVao = false; + gl.createVertexArray = () => + { + // empty + }; + + gl.bindVertexArray = () => + { + // empty + }; + + gl.deleteVertexArray = () => + { + // empty + }; + } + } + + if (!gl.vertexAttribDivisor) + { + const instanceExt = gl.getExtension('ANGLE_instanced_arrays'); + + if (instanceExt) + { + gl.vertexAttribDivisor = (a, b) => + instanceExt.vertexAttribDivisorANGLE(a, b); + + gl.drawElementsInstanced = (a, b, c, d, e) => + instanceExt.drawElementsInstancedANGLE(a, b, c, d, e); + + gl.drawArraysInstanced = (a, b, c, d) => + instanceExt.drawArraysInstancedANGLE(a, b, c, d); + } + else + { + this.hasInstance = false; + } + } + } + + /** + * Binds geometry so that is can be drawn. Creating a Vao if required + * @private + * @param {PIXI.Geometry} geometry instance of geometry to bind + */ + bind(geometry, shader) + { + shader = shader || this.renderer.shader.shader; + + const gl = this.gl; + + // not sure the best way to address this.. + // currently different shaders require different VAOs for the same geometry + // Still mulling over the best way to solve this one.. + // will likely need to modify the shader attribute locations at run time! + let vaos = geometry.glVertexArrayObjects[this.CONTEXT_UID]; + + if (!vaos) + { + geometry.glVertexArrayObjects[this.CONTEXT_UID] = vaos = {}; + } + + const vao = vaos[shader.program.id] || this.initGeometryVao(geometry, shader.program); + + this._activeGeometry = geometry; + + if (this._activeVao !== vao) + { + this._activeVao = vao; + + if (this.hasVao) + { + gl.bindVertexArray(vao); + } + else + { + this.activateVao(geometry, shader.program); + } + } + + // TODO - optimise later! + // don't need to loop through if nothing changed! + // maybe look to add an 'autoupdate' to geometry? + this.updateBuffers(); + } + + reset() + { + this.unbind(); + } + + updateBuffers() + { + const geometry = this._activeGeometry; + const gl = this.gl; + + for (let i = 0; i < geometry.buffers.length; i++) + { + const buffer = geometry.buffers[i]; + + const glBuffer = buffer._glBuffers[this.CONTEXT_UID]; + + if (buffer._updateID !== glBuffer.updateID) + { + glBuffer.updateID = buffer._updateID; + + // TODO can cache this on buffer! maybe added a getter / setter? + const type = buffer.index ? gl.ELEMENT_ARRAY_BUFFER : gl.ARRAY_BUFFER; + const drawType = buffer.static ? gl.STATIC_DRAW : gl.DYNAMIC_DRAW; + + gl.bindBuffer(type, glBuffer.buffer); + + if (glBuffer.byteLength >= buffer.data.byteLength) + { + // offset is always zero for now! + gl.bufferSubData(type, 0, buffer.data); + } + else + { + gl.bufferData(type, buffer.data, drawType); + } + } + } + } + + checkCompatability(geometry, program) + { + // geometry must have at least all the attributes that the shader requires. + const geometryAttributes = geometry.attributes; + const shaderAttributes = program.attributeData; + + for (const j in shaderAttributes) + { + if (!geometryAttributes[j]) + { + throw new Error(`shader and geometry incompatible, geometry missing the "${j}" attribute`); + } + } + } + + /** + * Creates a Vao with the same structure as the geometry and stores it on the geometry. + * @private + * @param {PIXI.Geometry} geometry instance of geometry to to generate Vao for + */ + initGeometryVao(geometry, program) + { + this.checkCompatability(geometry, program); + + const gl = this.gl; + const CONTEXT_UID = this.CONTEXT_UID; + const buffers = geometry.buffers; + const attributes = geometry.attributes; + + const tempStride = {}; + const tempStart = {}; + + for (const j in buffers) + { + tempStride[j] = 0; + tempStart[j] = 0; + } + + for (const j in attributes) + { + if (!attributes[j].size && program.attributeData[j]) + { + attributes[j].size = program.attributeData[j].size; + } + + tempStride[attributes[j].buffer] += attributes[j].size * byteSizeMap[attributes[j].type]; + } + + for (const j in attributes) + { + const attribute = attributes[j]; + const attribSize = attribute.size; + + if (attribute.stride === undefined) + { + if (tempStride[attribute.buffer] === attribSize * byteSizeMap[attribute.type]) + { + attribute.stride = 0; + } + else + { + attribute.stride = tempStride[attribute.buffer]; + } + } + + if (attribute.start === undefined) + { + attribute.start = tempStart[attribute.buffer]; + + tempStart[attribute.buffer] += attribSize * byteSizeMap[attribute.type]; + } + } + + // first update - and create the buffers! + // only create a gl buffer if it actually gets + for (let i = 0; i < buffers.length; i++) + { + const buffer = buffers[i]; + + if (!buffer._glBuffers[CONTEXT_UID]) + { + buffer._glBuffers[CONTEXT_UID] = new GLBuffer(gl.createBuffer()); + } + } + + // TODO - maybe make this a data object? + // lets wait to see if we need to first! + const vao = gl.createVertexArray(); + + gl.bindVertexArray(vao); + + this.activateVao(geometry, program); + + gl.bindVertexArray(null); + + // add it to the cache! + geometry.glVertexArrayObjects[this.CONTEXT_UID][program.id] = vao; + + return vao; + } + + activateVao(geometry, program) + { + const gl = this.gl; + const CONTEXT_UID = this.CONTEXT_UID; + const buffers = geometry.buffers; + const attributes = geometry.attributes; + + if (geometry.indexBuffer) + { + // first update the index buffer if we have one.. + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, geometry.indexBuffer._glBuffers[CONTEXT_UID].buffer); + } + + let lastBuffer = null; + + // add a new one! + for (const j in attributes) + { + const attribute = attributes[j]; + const buffer = buffers[attribute.buffer]; + const glBuffer = buffer._glBuffers[CONTEXT_UID]; + + if (program.attributeData[j]) + { + if (lastBuffer !== glBuffer) + { + gl.bindBuffer(gl.ARRAY_BUFFER, glBuffer.buffer); + + lastBuffer = glBuffer; + } + + const location = program.attributeData[j].location; + + // TODO introduce state again + // we can optimise this for older devices that have no VAOs + gl.enableVertexAttribArray(location); + + gl.vertexAttribPointer(location, + attribute.size, + attribute.type || gl.FLOAT, + attribute.normalized, + attribute.stride, + attribute.start); + + if (attribute.instance) + { + // TODO calculate instance count based of this... + if (this.hasInstance) + { + gl.vertexAttribDivisor(location, 1); + } + else + { + throw new Error('geometry error, GPU Instancing is not supported on this device'); + } + } + } + } + } + + draw(type, size, start, instanceCount) + { + const gl = this.gl; + const geometry = this._activeGeometry; + + // TODO.. this should not change so maybe cache the function? + + if (geometry.indexBuffer) + { + if (geometry.instanced) + { + /* eslint-disable max-len */ + gl.drawElementsInstanced(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2, instanceCount || 1); + /* eslint-enable max-len */ + } + else + { + gl.drawElements(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2); + } + } + else + if (geometry.instanced) + { + // TODO need a better way to calculate size.. + gl.drawArraysInstanced(type, start, size || geometry.getSize(), instanceCount || 1); + } + else + { + gl.drawArrays(type, start, size || geometry.getSize()); + } + + return this; + } + + unbind() + { + this.gl.bindVertexArray(null); + this._activeVao = null; + this._activeGeometry = null; + } +} diff --git a/packages/core/src/geometry/utils/setVertexAttribArrays.js b/packages/core/src/geometry/utils/setVertexAttribArrays.js new file mode 100644 index 0000000..23b6959 --- /dev/null +++ b/packages/core/src/geometry/utils/setVertexAttribArrays.js @@ -0,0 +1,54 @@ +// var GL_MAP = {}; + +/** + * @param gl {WebGLRenderingContext} The current WebGL context + * @param attribs {*} + * @param state {*} + */ +export default function setVertexAttribArrays(gl, attribs, state) +{ + let i; + + if (state) + { + const tempAttribState = state.tempAttribState; + const attribState = state.attribState; + + for (i = 0; i < tempAttribState.length; i++) + { + tempAttribState[i] = false; + } + + // set the new attribs + for (i = 0; i < attribs.length; i++) + { + tempAttribState[attribs[i].attribute.location] = true; + } + + for (i = 0; i < attribState.length; i++) + { + if (attribState[i] !== tempAttribState[i]) + { + attribState[i] = tempAttribState[i]; + + if (state.attribState[i]) + { + gl.enableVertexAttribArray(i); + } + else + { + gl.disableVertexAttribArray(i); + } + } + } + } + else + { + for (i = 0; i < attribs.length; i++) + { + const attrib = attribs[i]; + + gl.enableVertexAttribArray(attrib.attribute.location); + } + } +} diff --git a/packages/core/src/index.js b/packages/core/src/index.js index 58c456a..fd1f547 100644 --- a/packages/core/src/index.js +++ b/packages/core/src/index.js @@ -1,30 +1,30 @@ import * as resources from './textures/resources'; -import * as systems from './renderers/systems'; +import * as systems from './systems'; -export { resources }; export { systems }; +export { resources }; -export { default as Renderer } from './renderers/Renderer'; -export { default as AbstractRenderer } from './renderers/AbstractRenderer'; -export { default as FrameBuffer } from './textures/FrameBuffer'; +export { default as System } from './System'; +export { default as Renderer } from './Renderer'; +export { default as AbstractRenderer } from './AbstractRenderer'; +export { default as FrameBuffer } from './framebuffer/FrameBuffer'; export { default as CubeTexture } from './textures/CubeTexture'; export { default as BaseTexture } from './textures/BaseTexture'; export { default as Texture } from './textures/Texture'; export { default as TextureMatrix } from './textures/TextureMatrix'; -export { default as RenderTexture } from './textures/RenderTexture'; -export { default as BaseRenderTexture } from './textures/BaseRenderTexture'; +export { default as RenderTexture } from './renderTexture/RenderTexture'; +export { default as BaseRenderTexture } from './renderTexture/BaseRenderTexture'; export { default as TextureUvs } from './textures/TextureUvs'; -export { default as State } from './renderers/State'; -export { default as ObjectRenderer } from './renderers/utils/ObjectRenderer'; -export { default as Quad } from './renderers/utils/Quad'; -export { default as QuadUv } from './renderers/utils/QuadUv'; -export { default as checkMaxIfStatmentsInShader } from './renderers/utils/checkMaxIfStatmentsInShader'; +export { default as State } from './state/State'; +export { default as ObjectRenderer } from './batch/ObjectRenderer'; +export { default as Quad } from './utils/Quad'; +export { default as QuadUv } from './utils/QuadUv'; +export { default as checkMaxIfStatmentsInShader } from './shader/utils/checkMaxIfStatmentsInShader'; export { default as Shader } from './shader/Shader'; export { default as Program } from './shader/Program'; export { default as UniformGroup } from './shader/UniformGroup'; -export { default as SpriteMaskFilter } from './renderers/filters/spriteMask/SpriteMaskFilter'; -export { default as Filter } from './renderers/filters/Filter'; +export { default as SpriteMaskFilter } from './filters/spriteMask/SpriteMaskFilter'; +export { default as Filter } from './filters/Filter'; export { default as Attribute } from './geometry/Attribute'; export { default as Buffer } from './geometry/Buffer'; export { default as Geometry } from './geometry/Geometry'; - diff --git a/packages/core/src/mask/MaskSystem.js b/packages/core/src/mask/MaskSystem.js new file mode 100644 index 0000000..99174c2 --- /dev/null +++ b/packages/core/src/mask/MaskSystem.js @@ -0,0 +1,199 @@ +import System from '../System'; +import AlphaMaskFilter from '../filters/spriteMask/SpriteMaskFilter'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class MaskSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + // TODO - we don't need both! + this.scissor = false; + this.scissorData = null; + this.scissorRenderTarget = null; + + this.enableScissor = false; + + this.alphaMaskPool = []; + this.alphaMaskIndex = 0; + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.DisplayObject} target - Display Object to push the mask to + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + push(target, maskData) + { + // TODO the root check means scissor rect will not + // be used on render textures more info here: + // https://github.com/pixijs/pixi.js/pull/3545 + + if (maskData.texture) + { + this.pushSpriteMask(target, maskData); + } + else if (this.enableScissor + && !this.scissor + && this.renderer._activeRenderTarget.root + && !this.renderer.stencil.stencilMaskStack.length + && maskData.isFastRect()) + { + const matrix = maskData.worldTransform; + + let rot = Math.atan2(matrix.b, matrix.a); + + // use the nearest degree! + rot = Math.round(rot * (180 / Math.PI)); + + if (rot % 90) + { + this.pushStencilMask(maskData); + } + else + { + this.pushScissorMask(target, maskData); + } + } + else + { + this.pushStencilMask(maskData); + } + } + + /** + * Removes the last mask from the mask stack and doesn't return it. + * + * @param {PIXI.DisplayObject} target - Display Object to pop the mask from + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + pop(target, maskData) + { + if (maskData.texture) + { + this.popSpriteMask(target, maskData); + } + else if (this.enableScissor && !this.renderer.stencil.stencilMaskStack.length) + { + this.popScissorMask(target, maskData); + } + else + { + this.popStencilMask(target, maskData); + } + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.RenderTarget} target - Display Object to push the sprite mask to + * @param {PIXI.Sprite} maskData - Sprite to be used as the mask + */ + pushSpriteMask(target, maskData) + { + let alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; + + if (!alphaMaskFilter) + { + alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; + } + + alphaMaskFilter[0].resolution = this.renderer.resolution; + alphaMaskFilter[0].maskSprite = maskData; + + // TODO - may cause issues! + target.filterArea = maskData.getBounds(true); + + this.renderer.filterSystem.pushFilter(target, alphaMaskFilter); + + this.alphaMaskIndex++; + } + + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popSpriteMask() + { + this.renderer.filterSystem.popFilter(); + this.alphaMaskIndex--; + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + pushStencilMask(maskData) + { + this.renderer.batch.flush(); + this.renderer.stencil.pushStencil(maskData); + } + + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popStencilMask() + { + // this.renderer.currentRenderer.stop(); + this.renderer.stencil.popStencil(); + } + + /** + * + * @param {PIXI.DisplayObject} target - Display Object to push the mask to + * @param {PIXI.Graphics} maskData - The masking data. + */ + pushScissorMask(target, maskData) + { + maskData.renderable = true; + + const renderTarget = this.renderer._activeRenderTarget; + + const bounds = maskData.getBounds(); + + bounds.fit(renderTarget.size); + maskData.renderable = false; + + this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); + + const resolution = this.renderer.resolution; + + this.renderer.gl.scissor( + bounds.x * resolution, + (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, + bounds.width * resolution, + bounds.height * resolution + ); + + this.scissorRenderTarget = renderTarget; + this.scissorData = maskData; + this.scissor = true; + } + + /** + * + * + */ + popScissorMask() + { + this.scissorRenderTarget = null; + this.scissorData = null; + this.scissor = false; + + // must be scissor! + const gl = this.renderer.gl; + + gl.disable(gl.SCISSOR_TEST); + } +} diff --git a/packages/core/src/mask/StencilSystem.js b/packages/core/src/mask/StencilSystem.js new file mode 100644 index 0000000..62796a5 --- /dev/null +++ b/packages/core/src/mask/StencilSystem.js @@ -0,0 +1,133 @@ +import System from '../System'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class StencilSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + this.stencilMaskStack = []; + } + + /** + * Changes the mask stack that is used by this System. + * + * @param {PIXI.Graphics[]} stencilMaskStack - The mask stack + */ + setMaskStack(stencilMaskStack) + { + const gl = this.renderer.gl; + + if (stencilMaskStack.length !== this.stencilMaskStack.length) + { + if (stencilMaskStack.length === 0) + { + gl.disable(gl.STENCIL_TEST); + } + else + { + gl.enable(gl.STENCIL_TEST); + } + } + + this.stencilMaskStack = stencilMaskStack; + } + + /** + * Applies the Mask and adds it to the current stencil stack. @alvin + * + * @param {PIXI.Graphics} graphics - The mask + */ + pushStencil(graphics) + { + this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); + + // this.renderer._activeRenderTarget.attachStencilBuffer(); + + const gl = this.renderer.gl; + const prevMaskCount = this.stencilMaskStack.length; + + if (prevMaskCount === 0) + { + gl.enable(gl.STENCIL_TEST); + } + + this.stencilMaskStack.push(graphics); + + // Increment the refference stencil value where the new mask overlaps with the old ones. + gl.colorMask(false, false, false, false); + gl.stencilFunc(gl.EQUAL, prevMaskCount, this._getBitwiseMask()); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.INCR); + this.renderer.plugins.graphics.render(graphics); + + this._useCurrent(); + } + + /** + * Removes the last mask from the stencil stack. @alvin + */ + popStencil() + { + this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); + + const gl = this.renderer.gl; + const graphics = this.stencilMaskStack.pop(); + + if (this.stencilMaskStack.length === 0) + { + // the stack is empty! + gl.disable(gl.STENCIL_TEST); + gl.clear(gl.STENCIL_BUFFER_BIT); + gl.clearStencil(0); + } + else + { + // Decrement the refference stencil value where the popped mask overlaps with the other ones + gl.colorMask(false, false, false, false); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.DECR); + this.renderer.plugins.graphics.render(graphics); + + this._useCurrent(); + } + } + + /** + * Setup renderer to use the current stencil data. + */ + _useCurrent() + { + const gl = this.renderer.gl; + + gl.colorMask(true, true, true, true); + gl.stencilFunc(gl.EQUAL, this.stencilMaskStack.length, this._getBitwiseMask()); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP); + } + + /** + * Fill 1s equal to the number of acitve stencil masks. + * + * @return {number} The bitwise mask. + */ + _getBitwiseMask() + { + return (1 << this.stencilMaskStack.length) - 1; + } + + /** + * Destroys the mask stack. + * + */ + destroy() + { + super.destroy(this); + + this.stencilMaskStack = null; + } +} diff --git a/packages/core/src/projection/ProjectionSystem.js b/packages/core/src/projection/ProjectionSystem.js new file mode 100644 index 0000000..6e63b73 --- /dev/null +++ b/packages/core/src/projection/ProjectionSystem.js @@ -0,0 +1,73 @@ +import System from '../System'; +import { Matrix } from '@pixi/math'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class ProjectionSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.projectionMatrix = new Matrix(); + } + + update(destinationFrame, sourceFrame, resolution, root) + { + this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; + this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; + + this.calculateProjection(this.destinationFrame, this.sourceFrame, resolution, root); + + this.renderer.globalUniforms.uniforms.projectionMatrix = this.projectionMatrix; + this.renderer.globalUniforms.update(); + } + + /** + * Updates the projection matrix based on a projection frame (which is a rectangle) + * + * @param {Rectangle} destinationFrame - The destination frame. + * @param {Rectangle} sourceFrame - The source frame. + */ + calculateProjection(destinationFrame, sourceFrame, resolution, root) + { + const pm = this.projectionMatrix; + + // I don't think we will need this line.. + // pm.identity(); + + if (!root) + { + pm.a = (1 / destinationFrame.width * 2) * resolution; + pm.d = (1 / destinationFrame.height * 2) * resolution; + + pm.tx = -1 - (sourceFrame.x * pm.a); + pm.ty = -1 - (sourceFrame.y * pm.d); + } + else + { + pm.a = (1 / destinationFrame.width * 2) * resolution; + pm.d = (-1 / destinationFrame.height * 2) * resolution; + + pm.tx = -1 - (sourceFrame.x * pm.a); + pm.ty = 1 - (sourceFrame.y * pm.d); + } + } + + /** + * Sets the transform of the active render target to the given matrix + * + * @param {PIXI.Matrix} matrix - The transformation matrix + */ + setTransform()// matrix) + { + // this._activeRenderTarget.transform = matrix; + } +} diff --git a/packages/core/src/renderTexture/BaseRenderTexture.js b/packages/core/src/renderTexture/BaseRenderTexture.js new file mode 100644 index 0000000..88eb83b --- /dev/null +++ b/packages/core/src/renderTexture/BaseRenderTexture.js @@ -0,0 +1,137 @@ +import BaseTexture from '../textures/BaseTexture'; +import FrameBuffer from '../framebuffer/FrameBuffer'; + +/** + * A BaseRenderTexture is a special texture that allows any PixiJS display object to be rendered to it. + * + * __Hint__: All DisplayObjects (i.e. Sprites) that render to a BaseRenderTexture should be preloaded + * otherwise black rectangles will be drawn instead. + * + * A BaseRenderTexture takes a snapshot of any Display Object given to its render method. The position + * and rotation of the given Display Objects is ignored. For example: + * + * ```js + * let renderer = PIXI.autoDetectRenderer(1024, 1024, { view: canvas, ratio: 1 }); + * let baseRenderTexture = new PIXI.BaseRenderTexture(renderer, 800, 600); + * let sprite = PIXI.Sprite.fromImage("spinObj_01.png"); + * + * sprite.position.x = 800/2; + * sprite.position.y = 600/2; + * sprite.anchor.x = 0.5; + * sprite.anchor.y = 0.5; + * + * baseRenderTexture.render(sprite); + * ``` + * + * The Sprite in this case will be rendered using its local transform. To render this sprite at 0,0 + * you can clear the transform + * + * ```js + * + * sprite.setTransform() + * + * let baseRenderTexture = new PIXI.BaseRenderTexture(100, 100); + * let renderTexture = new PIXI.RenderTexture(baseRenderTexture); + * + * renderer.render(sprite, renderTexture); // Renders to center of RenderTexture + * ``` + * + * @class + * @extends PIXI.BaseTexture + * @memberof PIXI + */ +export default class BaseRenderTexture extends BaseTexture +{ + /** + * @param {object} [options] + * @param {number} [options.width=100] - The width of the base render texture + * @param {number} [options.height=100] - The height of the base render texture + * @param {PIXI.SCALE_MODES} [options.scaleMode] - See {@link PIXI.SCALE_MODES} for possible values + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the texture being generated + */ + constructor(options) + { + if (typeof options === 'number') + { + /* eslint-disable prefer-rest-params */ + // Backward compatibility of signature + const width = arguments[0]; + const height = arguments[1]; + const scaleMode = arguments[2]; + const resolution = arguments[3]; + + options = { width, height, scaleMode, resolution }; + /* eslint-enable prefer-rest-params */ + } + + super(null, options); + + const { width, height } = options || {}; + + // Set defaults + this.mipmap = false; + this.width = Math.ceil(width) || 100; + this.height = Math.ceil(height) || 100; + this.valid = true; + + /** + * A map of renderer IDs to webgl renderTargets + * + * @private + * @member {object} + */ + // this._glRenderTargets = {}; + + /** + * A reference to the canvas render target (we only need one as this can be shared across renderers) + * + * @private + * @member {object} + */ + this._canvasRenderTarget = null; + + this.clearColor = [0, 0, 0, 0]; + + this.frameBuffer = new FrameBuffer(width * this.resolution, height * this.resolution) + .addColorTexture(0, this); + + // TODO - could this be added the systems? + + /** + * The data structure for the stencil masks + * + * @member {PIXI.Graphics[]} + */ + this.stencilMaskStack = []; + + /** + * The data structure for the filters + * + * @member {PIXI.Graphics[]} + */ + this.filterStack = [{}]; + } + + /** + * Resizes the BaseRenderTexture. + * + * @param {number} width - The width to resize to. + * @param {number} height - The height to resize to. + */ + resize(width, height) + { + width = Math.ceil(width); + height = Math.ceil(height); + this.frameBuffer.resize(width * this.resolution, height * this.resolution); + } + + /** + * Destroys this texture + * + */ + destroy() + { + super.destroy(true); + this.renderer = null; + } +} diff --git a/packages/core/src/renderTexture/RenderTexture.js b/packages/core/src/renderTexture/RenderTexture.js new file mode 100644 index 0000000..f1ecf1f --- /dev/null +++ b/packages/core/src/renderTexture/RenderTexture.js @@ -0,0 +1,153 @@ +import BaseRenderTexture from './BaseRenderTexture'; +import Texture from '../textures/Texture'; + +/** + * A RenderTexture is a special texture that allows any PixiJS display object to be rendered to it. + * + * __Hint__: All DisplayObjects (i.e. Sprites) that render to a RenderTexture should be preloaded + * otherwise black rectangles will be drawn instead. + * + * A RenderTexture takes a snapshot of any Display Object given to its render method. For example: + * + * ```js + * let renderer = PIXI.autoDetectRenderer(1024, 1024, { view: canvas, ratio: 1 }); + * let renderTexture = PIXI.RenderTexture.create(800, 600); + * let sprite = PIXI.Sprite.fromImage("spinObj_01.png"); + * + * sprite.position.x = 800/2; + * sprite.position.y = 600/2; + * sprite.anchor.x = 0.5; + * sprite.anchor.y = 0.5; + * + * renderer.render(sprite, renderTexture); + * ``` + * + * The Sprite in this case will be rendered using its local transform. To render this sprite at 0,0 + * you can clear the transform + * + * ```js + * + * sprite.setTransform() + * + * let renderTexture = new PIXI.RenderTexture.create(100, 100); + * + * renderer.render(sprite, renderTexture); // Renders to center of RenderTexture + * ``` + * + * @class + * @extends PIXI.Texture + * @memberof PIXI + */ +export default class RenderTexture extends Texture +{ + /** + * @param {PIXI.BaseRenderTexture} baseRenderTexture - The renderer used for this RenderTexture + * @param {PIXI.Rectangle} [frame] - The rectangle frame of the texture to show + */ + constructor(baseRenderTexture, frame) + { + // support for legacy.. + let _legacyRenderer = null; + + if (!(baseRenderTexture instanceof BaseRenderTexture)) + { + /* eslint-disable prefer-rest-params, no-console */ + const width = arguments[1]; + const height = arguments[2]; + const scaleMode = arguments[3]; + const resolution = arguments[4]; + + // we have an old render texture.. + console.warn(`Please use RenderTexture.create(${width}, ${height}) instead of the ctor directly.`); + _legacyRenderer = arguments[0]; + /* eslint-enable prefer-rest-params, no-console */ + + frame = null; + baseRenderTexture = new BaseRenderTexture({ + width, + height, + scaleMode, + resolution, + }); + } + + /** + * The base texture object that this texture uses + * + * @member {BaseTexture} + */ + super(baseRenderTexture, frame); + + this.legacyRenderer = _legacyRenderer; + + /** + * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. + * + * @member {boolean} + */ + this.valid = true; + + /** + * FilterSystem temporary storage + * @private + * @member {PIXI.Rectangle} + */ + this.filterFrame = null; + + this._updateUvs(); + } + + /** + * Resizes the RenderTexture. + * + * @param {number} width - The width to resize to. + * @param {number} height - The height to resize to. + * @param {boolean} [resizeBaseTexture=true] - Should the baseTexture.width and height values be resized as well? + */ + resize(width, height, resizeBaseTexture = true) + { + width = Math.ceil(width); + height = Math.ceil(height); + + // TODO - could be not required.. + this.valid = (width > 0 && height > 0); + + this._frame.width = this.orig.width = width; + this._frame.height = this.orig.height = height; + + if (resizeBaseTexture) + { + this.baseTexture.resize(width, height); + } + + this._updateUvs(); + } + + /** + * A short hand way of creating a render texture. + * + * @param {object} [options] - Options + * @param {number} [options.width=100] - The width of the render texture + * @param {number} [options.height=100] - The height of the render texture + * @param {number} [options.scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the texture being generated + * @return {PIXI.RenderTexture} The new render texture + */ + static create(options) + { + // fallback, old-style: create(width, height, scaleMode, resolution) + if (typeof options === 'number') + { + /* eslint-disable prefer-rest-params */ + options = { + width: options, + height: arguments[1], + scaleMode: arguments[2], + resolution: arguments[3], + }; + /* eslint-enable prefer-rest-params */ + } + + return new RenderTexture(new BaseRenderTexture(options)); + } +} diff --git a/packages/core/src/renderTexture/RenderTextureSystem.js b/packages/core/src/renderTexture/RenderTextureSystem.js new file mode 100644 index 0000000..8d0ca61 --- /dev/null +++ b/packages/core/src/renderTexture/RenderTextureSystem.js @@ -0,0 +1,115 @@ +import System from '../System'; +import { Rectangle } from '@pixi/math'; + +const tempRect = new Rectangle(); + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class RenderTextureSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.clearColor = renderer._backgroundColorRgba; + + // TODO moe this property somewhere else! + this.defaultMaskStack = []; + this.defaultFilterStack = [{}]; + + // empty render texture? + this.renderTexture = null; + + this.destinationFrame = new Rectangle(); + } + + bind(renderTexture, sourceFrame, destinationFrame) + { + // TODO - do we want this?? + if (this.renderTexture === renderTexture) return; + this.renderTexture = renderTexture; + + const renderer = this.renderer; + + if (renderTexture) + { + const baseTexture = renderTexture.baseTexture; + + if (!destinationFrame) + { + tempRect.width = baseTexture.realWidth; + tempRect.height = baseTexture.realHeight; + + destinationFrame = tempRect; + } + + if (!sourceFrame) + { + sourceFrame = destinationFrame; + } + + this.renderer.framebuffer.bind(baseTexture.frameBuffer, destinationFrame); + + this.renderer.projection.update(destinationFrame, sourceFrame, baseTexture.resolution, false); + this.renderer.stencil.setMaskStack(baseTexture.stencilMaskStack); + } + else + { + // TODO these validation checks happen deeper down.. + // thing they can be avoided.. + if (!destinationFrame) + { + tempRect.width = renderer.width; + tempRect.height = renderer.height; + + destinationFrame = tempRect; + } + + if (!sourceFrame) + { + sourceFrame = destinationFrame; + } + + renderer.framebuffer.bind(null, destinationFrame); + + // TODO store this.. + this.renderer.projection.update(destinationFrame, sourceFrame, this.renderer.resolution, true); + this.renderer.stencil.setMaskStack(this.defaultMaskStack); + } + + this.destinationFrame.copyFrom(destinationFrame); + } + + /** + * Erases the render texture and fills the drawing area with a colour + * + * @param {number} [clearColor] - The colour + * @return {PIXI.Renderer} Returns itself. + */ + clear(clearColor) + { + if (this.renderTexture) + { + clearColor = clearColor || this.renderTexture.baseTexture.clearColor; + } + else + { + clearColor = clearColor || this.clearColor; + } + + this.renderer.framebuffer.clear(clearColor[0], clearColor[1], clearColor[2], clearColor[3]); + } + + resize()// screenWidth, screenHeight) + { + // resize the root only! + this.bind(null); + } +} diff --git a/packages/core/src/renderers/AbstractRenderer.js b/packages/core/src/renderers/AbstractRenderer.js deleted file mode 100644 index 928670f..0000000 --- a/packages/core/src/renderers/AbstractRenderer.js +++ /dev/null @@ -1,353 +0,0 @@ -import { hex2string, hex2rgb } from '@pixi/utils'; -import { Matrix, Rectangle } from '@pixi/math'; -import { RENDERER_TYPE } from '@pixi/constants'; -import { settings } from '@pixi/settings'; -import { Container } from '@pixi/display'; -import RenderTexture from '../textures/RenderTexture'; -import EventEmitter from 'eventemitter3'; - -const tempMatrix = new Matrix(); - -/** - * 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 EventEmitter - * @memberof PIXI - */ -export default class AbstractRenderer extends EventEmitter -{ - // eslint-disable-next-line valid-jsdoc - /** - * @param {string} system - The name of the system this renderer is for. - * @param {object} [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.transparent=false] - If the render view is transparent, default false - * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false - * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) - * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. The - * resolution of the renderer retina would be 2. - * @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 {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when rendering, - * stopping pixel interpolation. - */ - constructor(system, options, arg2, arg3) - { - super(); - - // Support for constructor(system, screenWidth, screenHeight, options) - if (typeof options === 'number') - { - options = Object.assign({ - width: options, - height: arg2 || settings.RENDER_OPTIONS.height, - }, arg3); - } - - // 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 = RENDERER_TYPE.UNKNOWN; - - /** - * Measurements of the screen. (0, 0, screenWidth, screenHeight) - * - * Its safe to use as filterArea or hitArea for 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 1 - */ - this.resolution = options.resolution || settings.RESOLUTION; - - /** - * Whether the render view is transparent - * - * @member {boolean} - */ - this.transparent = options.transparent; - - /** - * Whether css dimensions of canvas view should be resized to screen dimensions automatically - * - * @member {boolean} - */ - this.autoResize = options.autoResize || false; - - /** - * Tracks the blend modes useful for this renderer. - * - * @member {object} - */ - this.blendModes = null; - - /** - * 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; - - /** - * If true PixiJS will Math.floor() x/y values when rendering, stopping pixel interpolation. - * Handy for crisp pixel art and speed on legacy devices. - * - * @member {boolean} - */ - this.roundPixels = options.roundPixels; - - /** - * The background color as a number. - * - * @member {number} - * @private - */ - this._backgroundColor = 0x000000; - - /** - * The background color as an [R, G, B] array. - * - * @member {number[]} - * @private - */ - this._backgroundColorRgba = [0, 0, 0, 0]; - - /** - * The background color as a string. - * - * @member {string} - * @private - */ - this._backgroundColorString = '#000000'; - - this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter - - /** - * This temporary display object used as the parent of the currently being rendered item - * - * @member {PIXI.DisplayObject} - * @private - */ - this._tempDisplayObjectParent = new Container(); - - /** - * The last root object that the renderer tried to render. - * - * @member {PIXI.DisplayObject} - * @private - */ - this._lastObjectRendered = this._tempDisplayObjectParent; - - /** - * Collection of plugins. - * @readonly - * @member {object} - */ - this.plugins = {}; - } - - /** - * Initialize the plugins. - * - * @protected - * @param {object} staticMap - The dictionary of staticly saved plugins. - */ - initPlugins(staticMap) - { - 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() - { - return this.view.width; - } - - /** - * Same as view.height, actual number of pixels in the canvas by vertical - * - * @member {number} - * @readonly - * @default 600 - */ - get height() - { - return this.view.height; - } - - /** - * Resizes the screen and canvas to the specified width and height - * Canvas dimensions are multiplied by resolution - * - * @param {number} screenWidth - the new width of the screen - * @param {number} screenHeight - the new height of the screen - */ - resize(screenWidth, screenHeight) - { - this.screen.width = screenWidth; - this.screen.height = screenHeight; - - this.view.width = screenWidth * this.resolution; - this.view.height = screenHeight * this.resolution; - - if (this.autoResize) - { - this.view.style.width = `${screenWidth}px`; - this.view.style.height = `${screenHeight}px`; - } - } - - /** - * 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 {PIXI.DisplayObject} displayObject - The displayObject the object will be generated from - * @param {number} scaleMode - Should be one of the scaleMode consts - * @param {number} resolution - The resolution / device pixel ratio of the texture being generated - * @param {PIXI.Rectangle} [region] - The region of the displayObject, that shall be rendered, - * if no region is specified, defaults to the local bounds of the displayObject. - * @return {PIXI.Texture} a texture of the graphics object - */ - generateTexture(displayObject, scaleMode, resolution, region) - { - region = region || displayObject.getLocalBounds(); - - // 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(region.width | 0, region.height | 0, scaleMode, resolution); - - tempMatrix.tx = -region.x; - tempMatrix.ty = -region.y; - - this.render(displayObject, renderTexture, false, tempMatrix, true); - - return renderTexture; - } - - /** - * Removes everything from the renderer and optionally removes the Canvas DOM element. - * - * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. - */ - destroy(removeView) - { - 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); - } - - this.plugins = null; - - this.type = RENDERER_TYPE.UNKNOWN; - - this.view = null; - - this.screen = null; - - this.resolution = 0; - - this.transparent = false; - - this.autoResize = false; - - this.blendModes = null; - - this.options = null; - - this.preserveDrawingBuffer = false; - this.clearBeforeRender = false; - - this.roundPixels = false; - - this._backgroundColor = 0; - this._backgroundColorRgba = null; - this._backgroundColorString = null; - - this._tempDisplayObjectParent = null; - this._lastObjectRendered = null; - } - - /** - * The background color to fill if not transparent - * - * @member {number} - */ - get backgroundColor() - { - return this._backgroundColor; - } - - set backgroundColor(value) // eslint-disable-line require-jsdoc - { - this._backgroundColor = value; - this._backgroundColorString = hex2string(value); - hex2rgb(value, this._backgroundColorRgba); - } -} diff --git a/packages/core/src/renderers/Renderer.js b/packages/core/src/renderers/Renderer.js deleted file mode 100644 index a2020b1..0000000 --- a/packages/core/src/renderers/Renderer.js +++ /dev/null @@ -1,329 +0,0 @@ -import AbstractRenderer from './AbstractRenderer'; -import { sayHello } from '@pixi/utils'; -import MaskSystem from './systems/MaskSystem'; -import StencilSystem from './systems/StencilSystem'; -import FilterSystem from './systems/filter/FilterSystem'; -import FramebufferSystem from './systems/FramebufferSystem'; -import RenderTextureSystem from './systems/RenderTextureSystem'; -import TextureSystem from './systems/textures/TextureSystem'; -import ProjectionSystem from './systems/ProjectionSystem'; -import StateSystem from './systems/StateSystem'; -import GeometrySystem from './systems/geometry/GeometrySystem'; -import ShaderSystem from './systems/shader/ShaderSystem'; -import ContextSystem from './systems/ContextSystem'; -import BatchSystem from './systems/BatchSystem'; -import TextureGCSystem from './systems/textures/TextureGCSystem'; -import { RENDERER_TYPE } from '@pixi/constants'; -import UniformGroup from '../shader/UniformGroup'; -import { Matrix } from '@pixi/math'; -import Runner from 'mini-runner'; - -/** - * 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 Render works by automatically managing webGLBatchs. - * 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 :) - * - * @class - * @memberof PIXI - * @extends PIXI.AbstractRenderer - */ -export default class Renderer extends AbstractRenderer -{ - // eslint-disable-next-line valid-jsdoc - /** - * - * @param {object} [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.transparent=false] - If the render view is transparent, default false - * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false - * @param {boolean} [options.antialias=false] - sets antialias. If not available natively then FXAA - * antialiasing is used - * @param {boolean} [options.forceFXAA=false] - forces FXAA antialiasing to be used over native. - * FXAA is faster, but may not always look as great - * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. - * The resolution of the renderer retina would be 2. - * @param {boolean} [options.clearBeforeRender=true] - This sets if the renderer will clear - * the canvas or not before the new render pass. If you wish to set this to false, you *must* set - * preserveDrawingBuffer to `true`. - * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, - * enable this if you need to call toDataUrl on the webgl context. - * @param {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when - * rendering, stopping pixel interpolation. - * @param {number} [options.backgroundColor=0x000000] - The background color of the rendered area - * (shown if not transparent). - * @param {boolean} [options.legacy=false] - If true PixiJS will aim to ensure compatibility - * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. - * @param {string} [options.powerPreference] - Parameter passed to webgl context, set to "high-performance" - * for devices with dual graphics card - */ - constructor(options, arg2, arg3) - { - super('WebGL', options, arg2, arg3); - - /** - * The type of this renderer as a standardised const - * - * @member {number} - * @see PIXI.RENDERER_TYPE - */ - this.type = RENDERER_TYPE.WEBGL; - - // this will be set by the contextSystem (this.context) - this.gl = null; - this.CONTEXT_UID = 0; - this.legacy = !!options.legacy; - - // TODO legacy! - - // runners! - this.runners = { - destroy: new Runner('destroy'), - contextChange: new Runner('contextChange', 1), - reset: new Runner('reset'), - update: new Runner('update'), - postrender: new Runner('postrender'), - prerender: new Runner('prerender'), - resize: new Runner('resize', 2), - }; - - 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(GeometrySystem, 'geometry') - .addSystem(FramebufferSystem, 'framebuffer') - .addSystem(StencilSystem, 'stencil') - .addSystem(ProjectionSystem, 'projection') - .addSystem(TextureGCSystem, 'textureGC') - .addSystem(FilterSystem, 'filter') - .addSystem(RenderTextureSystem, 'renderTexture') - .addSystem(BatchSystem, 'batch'); - - this.initPlugins(Renderer.__plugins); - - /** - * The options passed in to create a new webgl context. - * - * @member {object} - * @private - */ - if (options.context) - { - this.context.initFromContext(options.context); - } - else - { - this.context.initFromOptions({ - alpha: this.transparent, - antialias: options.antialias, - premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', - stencil: true, - 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); - } - - addSystem(_class, name) - { - if (!name) - { - name = _class.name; - } - - // TODO - read name from class.name.. - - /* - if(name.includes('System')) - { - name = name.replace('System', ''); - name = name.charAt(0).toLowerCase() + name.slice(1); - } - */ - - const system = new _class(this); - - if (this[name]) - { - throw new Error(`Whoops! ${name} is already a manger`); - } - - this[name] = system; - - for (const i in this.runners) - { - this.runners[i].add(system); - } - - return this; - - /** - * 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. - */ - } - - /** - * Renders the object to its webGL view - * - * @param {PIXI.DisplayObject} displayObject - the object to be rendered - * @param {PIXI.RenderTexture} renderTexture - The render texture to render to. - * @param {boolean} [clear] - Should the canvas be cleared before the new render - * @param {PIXI.Transform} [transform] - A transform to apply to the render texture before rendering. - * @param {boolean} [skipUpdateTransform] - Should we skip the update transform pass? - */ - render(displayObject, renderTexture, clear, transform, skipUpdateTransform) - { - // can be handy to know! - this.renderingToScreen = !renderTexture; - - this.runners.prerender.run(); - this.emit('prerender'); - - // 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.parent; - - displayObject.parent = this._tempDisplayObjectParent; - displayObject.updateTransform(); - displayObject.parent = 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.run(); - - this.emit('postrender'); - } - - /** - * Resizes the webGL view to the specified width and height. - * - * @param {number} screenWidth - the new width of the screen - * @param {number} screenHeight - the new height of the screen - */ - resize(screenWidth, screenHeight) - { - AbstractRenderer.prototype.resize.call(this, screenWidth, screenHeight); - - this.runners.resize.run(screenWidth, screenHeight); - } - - /** - * Resets the WebGL state so you can render things however you fancy! - * - * @return {PIXI.Renderer} Returns itself. - */ - reset() - { - this.runners.reset.run(); - - return this; - } - - clear() - { - this.framebuffer.bind(); - this.framebuffer.clear(); - } - - /** - * Removes everything from the renderer (event listeners, spritebatch, etc...) - * - * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. - * See: https://github.com/pixijs/pixi.js/issues/2233 - */ - destroy(removeView) - { - this.runners.destroy.run(); - - // call base destroy - super.destroy(removeView); - - // TODO nullify all the managers.. - this.gl = null; - } - - /** - * 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 - * builds and excluding plugins. - * @name PIXI.Renderer#plugins - * @type {object} - * @readonly - * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. - * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. - * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. - * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. - */ - - /** - * Adds a plugin to the renderer. - * - * @method - * @param {string} pluginName - The name of the plugin. - * @param {Function} ctor - The constructor function or class for the plugin. - */ - static registerPlugin(pluginName, ctor) - { - Renderer.__plugins = Renderer.__plugins || {}; - Renderer.__plugins[pluginName] = ctor; - } -} diff --git a/packages/core/src/renderers/State.js b/packages/core/src/renderers/State.js deleted file mode 100644 index fa3f221..0000000 --- a/packages/core/src/renderers/State.js +++ /dev/null @@ -1,163 +0,0 @@ -/* eslint-disable max-len */ - -const BLEND = 0; -const OFFSET = 1; -const CULLING = 2; -const DEPTH_TEST = 3; -const WINDING = 4; - -/** - * This is a webGL state. It is passed The WebGL StateManager. - * Each mesh renderered may require webGL to be in a different state. - * For example you may want different blend mode or to enable polygon offsets - * - * @class - * @memberof PIXI - */ -export default class State -{ - /** - * - */ - constructor() - { - this.data = 0; - - this.blendMode = 0; - this.polygonOffset = 0; - - this.blend = true; - // this.depthTest = true; - } - - /** - * Activates blending of the computed fragment color values - * - * @member {boolean} - */ - get blend() - { - return !!(this.data & (1 << BLEND)); - } - - set blend(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << BLEND)) !== value) - { - this.data ^= (1 << BLEND); - } - } - - /** - * Activates adding an offset to depth values of polygon's fragments - * - * @member {boolean} - * @default false - */ - get offsets() - { - return !!(this.data & (1 << OFFSET)); - } - - set offsets(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << OFFSET)) !== value) - { - this.data ^= (1 << OFFSET); - } - } - - /** - * Activates culling of polygons. - * - * @member {boolean} - * @default false - */ - get culling() - { - return !!(this.data & (1 << CULLING)); - } - - set culling(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << CULLING)) !== value) - { - this.data ^= (1 << CULLING); - } - } - - /** - * Activates depth comparisons and updates to the depth buffer. - * - * @member {boolean} - * @default false - */ - get depthTest() - { - return !!(this.data & (1 << DEPTH_TEST)); - } - - set depthTest(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << DEPTH_TEST)) !== value) - { - this.data ^= (1 << DEPTH_TEST); - } - } - - /** - * Specifies whether or not front or back-facing polygons can be culled. - * @member {boolean} - * @default false - */ - get clockwiseFrontFace() - { - return !!(this.data & (1 << WINDING)); - } - - set clockwiseFrontFace(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << WINDING)) !== value) - { - this.data ^= (1 << WINDING); - } - } - - /** - * The blend mode to be applied when this state is set. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. - * Setting this mode to anything other than NO_BLEND will automatically switch blending on. - * - * @member {boolean} - * @default PIXI.BLEND_MODES.NORMAL - * @see PIXI.BLEND_MODES - */ - get blendMode() - { - return this._blendMode; - } - - set blendMode(value) // eslint-disable-line require-jsdoc - { - // 17 is NO BLEND - this.blend = (value !== 17); - this._blendMode = value; - } - - /** - * The polygon offset. Setting this property to anything other than 0 will automatically enable poygon offset fill. - * - * @member {number} - * @default 0 - */ - get polygonOffset() - { - return this._polygonOffset; - } - - set polygonOffset(value) // eslint-disable-line require-jsdoc - { - this.offsets = !!value; - this._polygonOffset = value; - } -} - diff --git a/packages/core/src/renderers/filters/Filter.js b/packages/core/src/renderers/filters/Filter.js deleted file mode 100644 index 2073b8d..0000000 --- a/packages/core/src/renderers/filters/Filter.js +++ /dev/null @@ -1,140 +0,0 @@ -import Shader from '../../shader/Shader'; -import Program from '../../shader/Program'; -import State from '../State'; -import { settings } from '@pixi/settings'; -// import extractUniformsFromSrc from './extractUniformsFromSrc'; -import defaultVertex from './defaultFilter.vert'; -import defaultFragment from './defaultFilter.frag'; - -// let math = require('../../math'); -/** - * @class - * @memberof PIXI - * @extends PIXI.Shader - */ -export default class Filter extends Shader -{ - /** - * @param {string} [vertexSrc] - The source of the vertex shader. - * @param {string} [fragmentSrc] - The source of the fragment shader. - * @param {object} [uniforms] - Custom uniforms to use to augment the built-in ones. - */ - constructor(vertexSrc, fragmentSrc, uniforms) - { - const program = Program.from(vertexSrc, fragmentSrc); - - super(program, uniforms); - - /** - * The padding of the filter. Some filters require extra space to breath such as a blur. - * Increasing this will add extra width and height to the bounds of the object that the - * filter is applied to. - * - * @member {number} - */ - this.padding = 0; - - /** - * The resolution of the filter. Setting this to be lower will lower the quality but - * increase the performance of the filter. - * - * @member {number} - */ - this.resolution = settings.RESOLUTION; - - /** - * If enabled is true the filter is applied, if false it will not. - * - * @member {boolean} - */ - this.enabled = true; - - /** - * If enabled, PixiJS will fit the filter area into boundaries for better performance. - * Switch it off if it does not work for specific shader. - * - * @member {boolean} - */ - this.autoFit = true; - - /** - * Legacy filters use position and uvs from attributes - * @member {boolean} - * @readonly - */ - this.legacy = !!this.program.attributeData.aTextureCoord; - - /** - * the webGL state the filter requires to render - * @member {PIXI.State} - */ - this.state = new State(); - } - - /** - * Applies the filter - * - * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from - * @param {PIXI.RenderTarget} input - The input render target. - * @param {PIXI.RenderTarget} output - The target to output to. - * @param {boolean} clear - Should the output be cleared before rendering to it - * @param {object} [currentState] - It's current state of filter. - * There are some useful properties in the currentState : - * target, filters, sourceFrame, destinationFrame, renderTarget, resolution - */ - apply(filterManager, input, output, clear, currentState, derp) // eslint-disable-line no-unused-vars - { - // do as you please! - - filterManager.applyFilter(this, input, output, clear, currentState, derp); - - // or just do a regular render.. - } - - /** - * Sets the blendmode of the filter - * - * @member {number} - * @default PIXI.BLEND_MODES.NORMAL - */ - get blendMode() - { - return this.state.blendMode; - } - - set blendMode(value) // eslint-disable-line require-jsdoc - { - this.state.blendMode = value; - } - - /** - * The default vertex shader source - * - * @static - * @constant - */ - static get defaultVertexSrc() - { - return defaultVertex; - } - - /** - * The default fragment shader source - * - * @static - * @constant - */ - static get defaultFragmentSrc() - { - return defaultFragment; - } -} - -/** - * Used for caching shader IDs - * - * @static - * @private - */ -Filter.SOURCE_KEY_MAP = {}; - diff --git a/packages/core/src/renderers/filters/defaultFilter.frag b/packages/core/src/renderers/filters/defaultFilter.frag deleted file mode 100644 index a14c1be..0000000 --- a/packages/core/src/renderers/filters/defaultFilter.frag +++ /dev/null @@ -1,22 +0,0 @@ -varying vec2 vTextureCoord; -varying vec2 vFilterCoord; - -uniform sampler2D uSampler; -uniform sampler2D filterSampler; - -void main(void){ - vec4 masky = texture2D(filterSampler, vFilterCoord); - vec4 sample = texture2D(uSampler, vTextureCoord); - vec4 color; - if(mod(vFilterCoord.x, 1.0) > 0.5) - { - color = vec4(1.0, 0.0, 0.0, 1.0); - } - else - { - color = vec4(0.0, 1.0, 0.0, 1.0); - } - // gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0); - gl_FragColor = mix(sample, masky, 0.5); - gl_FragColor *= sample.a; -} diff --git a/packages/core/src/renderers/filters/defaultFilter.vert b/packages/core/src/renderers/filters/defaultFilter.vert deleted file mode 100644 index e1febca..0000000 --- a/packages/core/src/renderers/filters/defaultFilter.vert +++ /dev/null @@ -1,14 +0,0 @@ -attribute vec2 aVertexPosition; -attribute vec2 aTextureCoord; - -uniform mat3 projectionMatrix; -uniform mat3 filterMatrix; - -varying vec2 vTextureCoord; -varying vec2 vFilterCoord; - -void main(void){ - gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); - vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy; - vTextureCoord = aTextureCoord ; -} diff --git a/packages/core/src/renderers/filters/filterTransforms.js b/packages/core/src/renderers/filters/filterTransforms.js deleted file mode 100644 index 87a3cd6..0000000 --- a/packages/core/src/renderers/filters/filterTransforms.js +++ /dev/null @@ -1,50 +0,0 @@ -import { Matrix } from '@pixi/math'; - -/** - * Calculates the mapped matrix - * @param filterArea {Rectangle} The filter area - * @param sprite {Sprite} the target sprite - * @param outputMatrix {Matrix} @alvin - * @private - */ -// this returns a matrix that will normalise map filter cords in the filter to screen space -export function calculateScreenSpaceMatrix(outputMatrix, filterArea, textureSize) -{ - // TODO unwrap? - const mappedMatrix = outputMatrix.identity(); - - mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); - - mappedMatrix.scale(textureSize.width, textureSize.height); - - return mappedMatrix; -} - -export function calculateNormalizedScreenSpaceMatrix(outputMatrix, filterArea, textureSize) -{ - const mappedMatrix = outputMatrix.identity(); - - mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); - - const translateScaleX = (textureSize.width / filterArea.width); - const translateScaleY = (textureSize.height / filterArea.height); - - mappedMatrix.scale(translateScaleX, translateScaleY); - - return mappedMatrix; -} - -// this will map the filter coord so that a texture can be used based on the transform of a sprite -export function calculateSpriteMatrix(outputMatrix, filterArea, textureSize, sprite) -{ - const orig = sprite._texture.orig; - const mappedMatrix = outputMatrix.set(textureSize.width, 0, 0, textureSize.height, filterArea.x, filterArea.y); - const worldTransform = sprite.worldTransform.copyTo(Matrix.TEMP_MATRIX); - - worldTransform.invert(); - mappedMatrix.prepend(worldTransform); - mappedMatrix.scale(1.0 / orig.width, 1.0 / orig.height); - mappedMatrix.translate(sprite.anchor.x, sprite.anchor.y); - - return mappedMatrix; -} diff --git a/packages/core/src/renderers/filters/spriteMask/SpriteMaskFilter.js b/packages/core/src/renderers/filters/spriteMask/SpriteMaskFilter.js deleted file mode 100644 index d5a91a4..0000000 --- a/packages/core/src/renderers/filters/spriteMask/SpriteMaskFilter.js +++ /dev/null @@ -1,63 +0,0 @@ -import Filter from '../Filter'; -import { Matrix } from '@pixi/math'; -import vertex from './spriteMaskFilter.vert'; -import fragment from './spriteMaskFilter.frag'; -import { default as TextureMatrix } from '../../../textures/TextureMatrix'; - -/** - * The SpriteMaskFilter class - * - * @class - * @extends PIXI.Filter - * @memberof PIXI - */ -export default class SpriteMaskFilter extends Filter -{ - /** - * @param {PIXI.Sprite} sprite - the target sprite - */ - constructor(sprite) - { - const maskMatrix = new Matrix(); - - super(vertex, fragment); - - sprite.renderable = false; - - this.maskSprite = sprite; - this.maskMatrix = maskMatrix; - } - - /** - * Applies the filter - * - * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from - * @param {PIXI.RenderTarget} input - The input render target. - * @param {PIXI.RenderTarget} output - The target to output to. - */ - apply(filterManager, input, output) - { - const maskSprite = this.maskSprite; - const tex = this.maskSprite.texture; - - if (!tex.valid) - { - return; - } - if (!tex.transform) - { - // margin = 0.0, let it bleed a bit, shader code becomes easier - // assuming that atlas textures were made with 1-pixel padding - tex.transform = new TextureMatrix(tex, 0.0); - } - tex.transform.update(); - - this.uniforms.mask = tex; - this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite) - .prepend(tex.transform.mapCoord); - this.uniforms.alpha = maskSprite.worldAlpha; - this.uniforms.maskClamp = tex.transform.uClampFrame; - - filterManager.applyFilter(this, input, output); - } -} diff --git a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.frag b/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.frag deleted file mode 100644 index 0e4aef8..0000000 --- a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.frag +++ /dev/null @@ -1,23 +0,0 @@ -varying vec2 vMaskCoord; -varying vec2 vTextureCoord; - -uniform sampler2D uSampler; -uniform sampler2D mask; -uniform float alpha; -uniform vec4 maskClamp; - -void main(void) -{ - float clip = step(3.5, - step(maskClamp.x, vMaskCoord.x) + - step(maskClamp.y, vMaskCoord.y) + - step(vMaskCoord.x, maskClamp.z) + - step(vMaskCoord.y, maskClamp.w)); - - vec4 original = texture2D(uSampler, vTextureCoord); - vec4 masky = texture2D(mask, vMaskCoord); - - original *= (masky.r * masky.a * alpha * clip); - - gl_FragColor = original; -} diff --git a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.vert b/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.vert deleted file mode 100644 index d23a30c..0000000 --- a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.vert +++ /dev/null @@ -1,16 +0,0 @@ -attribute vec2 aVertexPosition; -attribute vec2 aTextureCoord; - -uniform mat3 projectionMatrix; -uniform mat3 otherMatrix; - -varying vec2 vMaskCoord; -varying vec2 vTextureCoord; - -void main(void) -{ - gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); - - vTextureCoord = aTextureCoord; - vMaskCoord = ( otherMatrix * vec3( aTextureCoord, 1.0) ).xy; -} diff --git a/packages/core/src/renderers/systems/BatchSystem.js b/packages/core/src/renderers/systems/BatchSystem.js deleted file mode 100644 index 70e9518..0000000 --- a/packages/core/src/renderers/systems/BatchSystem.js +++ /dev/null @@ -1,68 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import ObjectRenderer from '../utils/ObjectRenderer'; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ - -export default class BatchSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this manager works for. - */ - constructor(renderer) - { - super(renderer); - - /** - * An empty renderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.emptyRenderer = new ObjectRenderer(renderer); - - /** - * The currently active ObjectRenderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.currentRenderer = this.emptyRenderer; - } - - /** - * Changes the current renderer to the one given in parameter - * - * @param {PIXI.ObjectRenderer} objectRenderer - The object renderer to use. - */ - setObjectRenderer(objectRenderer) - { - if (this.currentRenderer === objectRenderer) - { - return; - } - - this.currentRenderer.stop(); - this.currentRenderer = objectRenderer; - - this.renderer.state.setState(objectRenderer.state); - - this.currentRenderer.start(); - } - - /** - * This should be called if you wish to do some custom rendering - * It will basically render anything that may be batched up such as sprites - * - */ - flush() - { - this.setObjectRenderer(this.emptyRenderer); - } - - reset() - { - this.setObjectRenderer(this.emptyRenderer); - } -} diff --git a/packages/core/src/renderers/systems/ContextSystem.js b/packages/core/src/renderers/systems/ContextSystem.js deleted file mode 100644 index 43bf827..0000000 --- a/packages/core/src/renderers/systems/ContextSystem.js +++ /dev/null @@ -1,186 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { settings } from '@pixi/settings'; - -let CONTEXT_UID = 0; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class ContextSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this.webGLVersion = 1; - - this.handleContextLost = this.handleContextLost.bind(this); - this.handleContextRestored = this.handleContextRestored.bind(this); - - this.extensions = {}; - - renderer.view.addEventListener('webglcontextlost', this.handleContextLost, false); - renderer.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); - } - - get isLost() - { - return (!this.gl || this.gl.isContextLost()); - } - - contextChange(gl) - { - this.gl = gl; - - // restore a context if it was previously lost - if (gl.isContextLost() && gl.getExtension('WEBGL_lose_context')) - { - gl.getExtension('WEBGL_lose_context').restoreContext(); - } - } - - initFromContext(gl) - { - this.gl = gl; - this.validateContext(gl); - this.renderer.gl = gl; - this.renderer.CONTEXT_UID = CONTEXT_UID++; - this.renderer.runners.contextChange.run(gl); - } - - initFromOptions(options) - { - const gl = this.createContext(this.renderer.view, options); - - this.initFromContext(gl); - } - - /** - * Helper class to create a webGL Context - * - * @class - * @memberof PIXI.glCore - * @param canvas {HTMLCanvasElement} the canvas element that we will get the context from - * @param options {Object} An options object that gets passed in to the canvas element containing the context attributes, - * see https://developer.mozilla.org/en/docs/Web/API/HTMLCanvasElement/getContext for the options available - * @return {WebGLRenderingContext} the WebGL context - */ - createContext(canvas, options) - { - let gl; - - if (settings.PREFER_WEBGL_2) - { - gl = canvas.getContext('webgl2', options); - } - - if (gl) - { - this.webGLVersion = 2; - } - else - { - this.webGLVersion = 1; - - gl = canvas.getContext('webgl', options) - || canvas.getContext('experimental-webgl', options); - - if (!gl) - { - // fail, not able to get a context - throw new Error('This browser does not support webGL. Try using the canvas renderer'); - } - } - - this.gl = gl; - - this.getExtensions(); - - return gl; - } - - getExtensions() - { - // time to set up default etensions that pixi uses.. - const gl = this.gl; - const extensions = this.extensions; - - if (this.webGLVersion === 1) - { - extensions.drawBuffers = gl.getExtension('WEBGL_draw_buffers'); - extensions.depthTexture = gl.getExtension('WEBKIT_WEBGL_depth_texture'); - extensions.floatTexture = gl.getExtension('OES_texture_float'); - extensions.loseContext = gl.getExtension('WEBGL_lose_context'); - - extensions.vertexArrayObject = gl.getExtension('OES_vertex_array_object') - || gl.getExtension('MOZ_OES_vertex_array_object') - || gl.getExtension('WEBKIT_OES_vertex_array_object'); - } - - // we don't use any specific WebGL 2 ones yet! - } - - /** - * Handles a lost webgl context - * - * @private - * @param {WebGLContextEvent} event - The context lost event. - */ - handleContextLost(event) - { - event.preventDefault(); - } - - /** - * Handles a restored webgl context - * - * @private - */ - handleContextRestored() - { - this.renderer.runners.contextChange.run(this.gl); - } - - destroy() - { - const view = this.renderer.view; - - // remove listeners - view.removeEventListener('webglcontextlost', this.handleContextLost); - view.removeEventListener('webglcontextrestored', this.handleContextRestored); - - this.gl.useProgram(null); - - if (this.extensions.loseContext) - { - this.extensions.loseContext.loseContext(); - } - } - - postrender() - { - this.gl.flush(); - } - - validateContext(gl) - { - const attributes = gl.getContextAttributes(); - - // this is going to be fairly simple for now.. but at least we have room to grow! - if (!attributes.stencil) - { - /* eslint-disable max-len */ - - /* eslint-disable no-console */ - console.warn('Provided WebGL context does not have a stencil buffer, masks may not render correctly'); - /* eslint-enable no-console */ - - /* eslint-enable max-len */ - } - } -} diff --git a/packages/core/src/renderers/systems/FramebufferSystem.js b/packages/core/src/renderers/systems/FramebufferSystem.js deleted file mode 100644 index df7902b..0000000 --- a/packages/core/src/renderers/systems/FramebufferSystem.js +++ /dev/null @@ -1,246 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { Rectangle } from '@pixi/math'; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class FramebufferSystem extends WebGLSystem -{ - /** - * Sets up the renderer context and necessary buffers. - * - * @private - */ - contextChange() - { - this.gl = this.renderer.gl; - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - this.current = null; - this.viewport = new Rectangle(); - - this.drawBufferExtension = this.renderer.context.extensions.drawBuffers; - } - - bind(framebuffer, frame) - { - const gl = this.gl; - - this.current = framebuffer; - - if (framebuffer) - { - // TODO cacheing layer! - - const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID] || this.initFramebuffer(framebuffer); - - gl.bindFramebuffer(gl.FRAMEBUFFER, fbo.framebuffer); - // makesure all textures are unbound.. - - // now check for updates... - if (fbo.dirtyId !== framebuffer.dirtyId) - { - fbo.dirtyId = framebuffer.dirtyId; - - if (fbo.dirtyFormat !== framebuffer.dirtyFormat) - { - fbo.dirtyFormat = framebuffer.dirtyFormat; - this.updateFramebuffer(framebuffer); - } - else if (fbo.dirtySize !== framebuffer.dirtySize) - { - fbo.dirtySize = framebuffer.dirtySize; - this.resizeFramebuffer(framebuffer); - } - } - - for (let i = 0; i < framebuffer.colorTextures.length; i++) - { - if (framebuffer.colorTextures[i].texturePart) - { - this.renderer.texture.unbind(framebuffer.colorTextures[i].texture); - } - else - { - this.renderer.texture.unbind(framebuffer.colorTextures[i]); - } - } - - if (framebuffer.depthTexture) - { - this.renderer.texture.unbind(framebuffer.depthTexture); - } - - if (frame) - { - this.setViewport(frame.x, frame.y, frame.width, frame.height); - } - else - { - this.setViewport(0, 0, framebuffer.width, framebuffer.height); - } - } - else - { - gl.bindFramebuffer(gl.FRAMEBUFFER, null); - - if (frame) - { - this.setViewport(frame.x, frame.y, frame.width, frame.height); - } - else - { - this.setViewport(0, 0, this.renderer.width, this.renderer.height); - } - } - } - - setViewport(x, y, width, height) - { - const v = this.viewport; - - if (v.width !== width || v.height !== height || v.x !== x || v.y !== y) - { - v.x = x; - v.y = y; - v.width = width; - v.height = height; - - this.gl.viewport(x, y, width, height); - } - } - - get size() - { - if (this.current) - { - // TODO store temp - return { x: 0, y: 0, width: this.current.width, height: this.current.height }; - } - - return { x: 0, y: 0, width: this.renderer.width, height: this.renderer.height }; - } - - clear(r, g, b, a) - { - const gl = this.gl; - - // TODO clear color can be set only one right? - gl.clearColor(r, g, b, a); - gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); - } - - // private functions... - - initFramebuffer(framebuffer) - { - const gl = this.gl; - - // TODO - make this a class? - const fbo = { - framebuffer: gl.createFramebuffer(), - stencil: null, - dirtyId: 0, - dirtyFormat: 0, - dirtySize: 0, - }; - - framebuffer.glFrameBuffers[this.CONTEXT_UID] = fbo; - - return fbo; - } - - resizeFramebuffer(framebuffer) - { - const gl = this.gl; - - if (framebuffer.stencil || framebuffer.depth) - { - gl.bindRenderbuffer(gl.RENDERBUFFER, this.stencil); - gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); - } - } - - updateFramebuffer(framebuffer) - { - const gl = this.gl; - - const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID]; - - // bind the color texture - const colorTextures = framebuffer.colorTextures; - - let count = colorTextures.length; - - if (!this.drawBufferExtension) - { - count = Math.min(count, 1); - } - - const activeTextures = []; - - for (let i = 0; i < count; i++) - { - const texture = framebuffer.colorTextures[i]; - - if (texture.texturePart) - { - this.renderer.texture.bind(texture.texture, 0); - - gl.framebufferTexture2D(gl.FRAMEBUFFER, - gl.COLOR_ATTACHMENT0 + i, - gl.TEXTURE_CUBE_MAP_NEGATIVE_X + texture.side, - texture.texture._glTextures[this.CONTEXT_UID].texture, - 0); - } - else - { - this.renderer.texture.bind(texture, 0); - - gl.framebufferTexture2D(gl.FRAMEBUFFER, - gl.COLOR_ATTACHMENT0 + i, - gl.TEXTURE_2D, - texture._glTextures[this.CONTEXT_UID].texture, - 0); - } - - activeTextures.push(gl.COLOR_ATTACHMENT0 + i); - } - - if (this.drawBufferExtension && activeTextures.length > 1) - { - this.drawBufferExtension.drawBuffersWEBGL(activeTextures); - } - - if (framebuffer.depthTexture) - { - const depthTextureExt = this.renderer.context.extensions.depthTexture; - - if (depthTextureExt) - { - const depthTexture = framebuffer.depthTexture; - - this.renderer.texture.bind(depthTexture, 0); - - gl.framebufferTexture2D(gl.FRAMEBUFFER, - gl.DEPTH_ATTACHMENT, - gl.TEXTURE_2D, - depthTexture._glTextures[this.CONTEXT_UID].texture, - 0); - } - } - - if (framebuffer.stencil || framebuffer.depth) - { - fbo.stencil = gl.createRenderbuffer(); - - gl.bindRenderbuffer(gl.RENDERBUFFER, fbo.stencil); - - // TODO.. this is depth AND stencil? - gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, fbo.stencil); - gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); - // fbo.enableStencil(); - } - } -} diff --git a/packages/core/src/renderers/systems/MaskSystem.js b/packages/core/src/renderers/systems/MaskSystem.js deleted file mode 100644 index d9c4b1a..0000000 --- a/packages/core/src/renderers/systems/MaskSystem.js +++ /dev/null @@ -1,199 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import AlphaMaskFilter from '../filters/spriteMask/SpriteMaskFilter'; - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class MaskSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - // TODO - we don't need both! - this.scissor = false; - this.scissorData = null; - this.scissorRenderTarget = null; - - this.enableScissor = false; - - this.alphaMaskPool = []; - this.alphaMaskIndex = 0; - } - - /** - * Applies the Mask and adds it to the current filter stack. - * - * @param {PIXI.DisplayObject} target - Display Object to push the mask to - * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. - */ - push(target, maskData) - { - // TODO the root check means scissor rect will not - // be used on render textures more info here: - // https://github.com/pixijs/pixi.js/pull/3545 - - if (maskData.texture) - { - this.pushSpriteMask(target, maskData); - } - else if (this.enableScissor - && !this.scissor - && this.renderer._activeRenderTarget.root - && !this.renderer.stencil.stencilMaskStack.length - && maskData.isFastRect()) - { - const matrix = maskData.worldTransform; - - let rot = Math.atan2(matrix.b, matrix.a); - - // use the nearest degree! - rot = Math.round(rot * (180 / Math.PI)); - - if (rot % 90) - { - this.pushStencilMask(maskData); - } - else - { - this.pushScissorMask(target, maskData); - } - } - else - { - this.pushStencilMask(maskData); - } - } - - /** - * Removes the last mask from the mask stack and doesn't return it. - * - * @param {PIXI.DisplayObject} target - Display Object to pop the mask from - * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. - */ - pop(target, maskData) - { - if (maskData.texture) - { - this.popSpriteMask(target, maskData); - } - else if (this.enableScissor && !this.renderer.stencil.stencilMaskStack.length) - { - this.popScissorMask(target, maskData); - } - else - { - this.popStencilMask(target, maskData); - } - } - - /** - * Applies the Mask and adds it to the current filter stack. - * - * @param {PIXI.RenderTarget} target - Display Object to push the sprite mask to - * @param {PIXI.Sprite} maskData - Sprite to be used as the mask - */ - pushSpriteMask(target, maskData) - { - let alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; - - if (!alphaMaskFilter) - { - alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; - } - - alphaMaskFilter[0].resolution = this.renderer.resolution; - alphaMaskFilter[0].maskSprite = maskData; - - // TODO - may cause issues! - target.filterArea = maskData.getBounds(true); - - this.renderer.filterSystem.pushFilter(target, alphaMaskFilter); - - this.alphaMaskIndex++; - } - - /** - * Removes the last filter from the filter stack and doesn't return it. - * - */ - popSpriteMask() - { - this.renderer.filterSystem.popFilter(); - this.alphaMaskIndex--; - } - - /** - * Applies the Mask and adds it to the current filter stack. - * - * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. - */ - pushStencilMask(maskData) - { - this.renderer.batch.flush(); - this.renderer.stencil.pushStencil(maskData); - } - - /** - * Removes the last filter from the filter stack and doesn't return it. - * - */ - popStencilMask() - { - // this.renderer.currentRenderer.stop(); - this.renderer.stencil.popStencil(); - } - - /** - * - * @param {PIXI.DisplayObject} target - Display Object to push the mask to - * @param {PIXI.Graphics} maskData - The masking data. - */ - pushScissorMask(target, maskData) - { - maskData.renderable = true; - - const renderTarget = this.renderer._activeRenderTarget; - - const bounds = maskData.getBounds(); - - bounds.fit(renderTarget.size); - maskData.renderable = false; - - this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); - - const resolution = this.renderer.resolution; - - this.renderer.gl.scissor( - bounds.x * resolution, - (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, - bounds.width * resolution, - bounds.height * resolution - ); - - this.scissorRenderTarget = renderTarget; - this.scissorData = maskData; - this.scissor = true; - } - - /** - * - * - */ - popScissorMask() - { - this.scissorRenderTarget = null; - this.scissorData = null; - this.scissor = false; - - // must be scissor! - const gl = this.renderer.gl; - - gl.disable(gl.SCISSOR_TEST); - } -} diff --git a/packages/core/src/renderers/systems/ProjectionSystem.js b/packages/core/src/renderers/systems/ProjectionSystem.js deleted file mode 100644 index ec5efdb..0000000 --- a/packages/core/src/renderers/systems/ProjectionSystem.js +++ /dev/null @@ -1,73 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { Matrix } from '@pixi/math'; - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ - -export default class ProjectionSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this.projectionMatrix = new Matrix(); - } - - update(destinationFrame, sourceFrame, resolution, root) - { - this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; - this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; - - this.calculateProjection(this.destinationFrame, this.sourceFrame, resolution, root); - - this.renderer.globalUniforms.uniforms.projectionMatrix = this.projectionMatrix; - this.renderer.globalUniforms.update(); - } - - /** - * Updates the projection matrix based on a projection frame (which is a rectangle) - * - * @param {Rectangle} destinationFrame - The destination frame. - * @param {Rectangle} sourceFrame - The source frame. - */ - calculateProjection(destinationFrame, sourceFrame, resolution, root) - { - const pm = this.projectionMatrix; - - // I don't think we will need this line.. - // pm.identity(); - - if (!root) - { - pm.a = (1 / destinationFrame.width * 2) * resolution; - pm.d = (1 / destinationFrame.height * 2) * resolution; - - pm.tx = -1 - (sourceFrame.x * pm.a); - pm.ty = -1 - (sourceFrame.y * pm.d); - } - else - { - pm.a = (1 / destinationFrame.width * 2) * resolution; - pm.d = (-1 / destinationFrame.height * 2) * resolution; - - pm.tx = -1 - (sourceFrame.x * pm.a); - pm.ty = 1 - (sourceFrame.y * pm.d); - } - } - - /** - * Sets the transform of the active render target to the given matrix - * - * @param {PIXI.Matrix} matrix - The transformation matrix - */ - setTransform()// matrix) - { - // this._activeRenderTarget.transform = matrix; - } -} diff --git a/packages/core/src/renderers/systems/RenderTextureSystem.js b/packages/core/src/renderers/systems/RenderTextureSystem.js deleted file mode 100644 index a133ef1..0000000 --- a/packages/core/src/renderers/systems/RenderTextureSystem.js +++ /dev/null @@ -1,115 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { Rectangle } from '@pixi/math'; - -const tempRect = new Rectangle(); - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ - -export default class RenderTextureSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this.clearColor = renderer._backgroundColorRgba; - - // TODO moe this property somewhere else! - this.defaultMaskStack = []; - this.defaultFilterStack = [{}]; - - // empty render texture? - this.renderTexture = null; - - this.destinationFrame = new Rectangle(); - } - - bind(renderTexture, sourceFrame, destinationFrame) - { - // TODO - do we want this?? - if (this.renderTexture === renderTexture) return; - this.renderTexture = renderTexture; - - const renderer = this.renderer; - - if (renderTexture) - { - const baseTexture = renderTexture.baseTexture; - - if (!destinationFrame) - { - tempRect.width = baseTexture.realWidth; - tempRect.height = baseTexture.realHeight; - - destinationFrame = tempRect; - } - - if (!sourceFrame) - { - sourceFrame = destinationFrame; - } - - this.renderer.framebuffer.bind(baseTexture.frameBuffer, destinationFrame); - - this.renderer.projection.update(destinationFrame, sourceFrame, baseTexture.resolution, false); - this.renderer.stencil.setMaskStack(baseTexture.stencilMaskStack); - } - else - { - // TODO these validation checks happen deeper down.. - // thing they can be avoided.. - if (!destinationFrame) - { - tempRect.width = renderer.width; - tempRect.height = renderer.height; - - destinationFrame = tempRect; - } - - if (!sourceFrame) - { - sourceFrame = destinationFrame; - } - - renderer.framebuffer.bind(null, destinationFrame); - - // TODO store this.. - this.renderer.projection.update(destinationFrame, sourceFrame, this.renderer.resolution, true); - this.renderer.stencil.setMaskStack(this.defaultMaskStack); - } - - this.destinationFrame.copyFrom(destinationFrame); - } - - /** - * Erases the render texture and fills the drawing area with a colour - * - * @param {number} [clearColor] - The colour - * @return {PIXI.Renderer} Returns itself. - */ - clear(clearColor) - { - if (this.renderTexture) - { - clearColor = clearColor || this.renderTexture.baseTexture.clearColor; - } - else - { - clearColor = clearColor || this.clearColor; - } - - this.renderer.framebuffer.clear(clearColor[0], clearColor[1], clearColor[2], clearColor[3]); - } - - resize()// screenWidth, screenHeight) - { - // resize the root only! - this.bind(null); - } -} diff --git a/packages/core/src/renderers/systems/StateSystem.js b/packages/core/src/renderers/systems/StateSystem.js deleted file mode 100755 index d7f4caf..0000000 --- a/packages/core/src/renderers/systems/StateSystem.js +++ /dev/null @@ -1,288 +0,0 @@ -import mapWebGLBlendModesToPixi from '../utils/mapWebGLBlendModesToPixi'; -import WebGLSystem from './WebGLSystem'; -import WebGLState from '../State'; - -const BLEND = 0; -const OFFSET = 1; -const CULLING = 2; -const DEPTH_TEST = 3; -const WINDING = 4; - -/** - * A WebGL state machines - * - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class StateSystem extends WebGLSystem -{ - /** - * @param {WebGLRenderingContext} gl - The current WebGL rendering context - */ - constructor(renderer) - { - super(renderer); - - this.gl = null; - - this.maxAttribs = null; - - // check we have vao.. - this.nativeVaoExtension = null; - - this.attribState = null; - - this.stateId = 0; - this.polygonOffset = 0; - this.blendMode = 17; - - this.map = []; - - // map functions for when we set state.. - this.map[BLEND] = this.setBlend; - this.map[OFFSET] = this.setOffset; - this.map[CULLING] = this.setCullFace; - this.map[DEPTH_TEST] = this.setDepthTest; - this.map[WINDING] = this.setFrontFace; - - this.checks = []; - - this.defaultState = new WebGLState(); - this.defaultState.blend = true; - this.defaultState.depth = true; - } - - contextChange(gl) - { - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; - - this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); - - // check we have vao.. - this.nativeVaoExtension = ( - gl.getExtension('OES_vertex_array_object') - || gl.getExtension('MOZ_OES_vertex_array_object') - || gl.getExtension('WEBKIT_OES_vertex_array_object') - ); - - this.attribState = { - tempAttribState: new Array(this.maxAttribs), - attribState: new Array(this.maxAttribs), - }; - - this.blendModes = mapWebGLBlendModesToPixi(gl); - - this.setState(this.defaultState); - - this.reset(); - } - - /** - * Sets the current state - * - * @param {*} state - The state to set. - */ - setState(state) - { - state = state || this.defaultState; - - // TODO maybe to an object check? ( this.state === state )? - if (this.stateId !== state.data) - { - let diff = this.stateId ^ state.data; - let i = 0; - - // order from least to most common - while (diff) - { - if (diff & 1) - { - // state change! - this.map[i].call(this, !!(state.data & (1 << i))); - } - - diff = diff >> 1; - i++; - } - - this.stateId = state.data; - } - - // based on the above settings we check for specific modes.. - // for example if blend is active we check and set the blend modes - // or of polygon offset is active we check the poly depth. - for (let i = 0; i < this.checks.length; i++) - { - this.checks[i](this, state); - } - } - - /** - * Enables or disabled blending. - * - * @param {boolean} value - Turn on or off webgl blending. - */ - setBlend(value) - { - this.updateCheck(StateSystem.checkBlendMode, value); - - this.gl[value ? 'enable' : 'disable'](this.gl.BLEND); - } - - /** - * Enables or disable polygon offset fill - * - * @param {boolean} value - Turn on or off webgl polygon offset testing. - */ - setOffset(value) - { - this.gl[value ? 'enable' : 'disable'](this.gl.POLYGON_OFFSET_FILL); - } - - /** - * Sets whether to enable or disable depth test. - * - * @param {boolean} value - Turn on or off webgl depth testing. - */ - setDepthTest(value) - { - this.gl[value ? 'enable' : 'disable'](this.gl.DEPTH_TEST); - } - - /** - * Sets whether to enable or disable cull face. - * - * @param {boolean} value - Turn on or off webgl cull face. - */ - setCullFace(value) - { - this.gl[value ? 'enable' : 'disable'](this.gl.CULL_FACE); - } - - /** - * Sets the gl front face. - * - * @param {boolean} value - true is clockwise and false is counter-clockwise - */ - setFrontFace(value) - { - this.gl.frontFace(this.gl[value ? 'CW' : 'CCW']); - } - - /** - * Sets the blend mode. - * - * @param {number} value - The blend mode to set to. - */ - setBlendMode(value) - { - if (value === this.blendMode) - { - return; - } - - this.blendMode = value; - this.gl.blendFunc(this.blendModes[value][0], this.blendModes[value][1]); - } - - /** - * Sets the polygon offset. - * - * @param {number} value - the polygon offset - * @param {number} scale - the polygon offset scale - */ - setPolygonOffset(value, scale) - { - this.gl.polygonOffset(value, scale); - } - - /** - * Disables all the vaos in use - * - */ - resetAttributes() - { - for (let i = 0; i < this.attribState.tempAttribState.length; i++) - { - this.attribState.tempAttribState[i] = 0; - } - - for (let i = 0; i < this.attribState.attribState.length; i++) - { - this.attribState.attribState[i] = 0; - } - - // im going to assume one is always active for performance reasons. - for (let i = 1; i < this.maxAttribs; i++) - { - this.gl.disableVertexAttribArray(i); - } - } - - // used - /** - * Resets all the logic and disables the vaos - */ - reset() - { - // unbind any VAO if they exist.. - if (this.nativeVaoExtension) - { - this.nativeVaoExtension.bindVertexArrayOES(null); - } - - // reset all attributes.. - this.resetAttributes(); - - this.gl.pixelStorei(this.gl.UNPACK_FLIP_Y_WEBGL, false); - - this.setBlendMode(0); - - // TODO? - // this.setState(this.defaultState); - } - - /** - * checks to see which updates should be checked based on which settings have been activated - * for example if blend is enabled then we shold check the blend modes each time the state is changed - * or if poygon fill is activated then we need to check if the polygone offset changes. - * The idea is that we only check what we have too. - * - * @param {Function} func the checking function to add or remove - * @param {boolean} value should the check function be added or removed. - */ - updateCheck(func, value) - { - const index = this.checks.indexOf(func); - - if (value && index === -1) - { - this.checks.push(func); - } - else if (!value && index !== -1) - { - this.checks.splice(index, 1); - } - } - - /** - * A private little wrapper function that we call to check the blend mode. - * - * @static - * @private - * @param {PIXI.StateSystem} System the System to perform the state check on - * @param {PIXI.State} state the state that the blendMode will pulled from - */ - static checkBlendMode(system, state) - { - system.setBlendMode(state.blendMode); - } - - // TODO - add polygon offset? -} diff --git a/packages/core/src/renderers/systems/StencilSystem.js b/packages/core/src/renderers/systems/StencilSystem.js deleted file mode 100644 index 5d538cb..0000000 --- a/packages/core/src/renderers/systems/StencilSystem.js +++ /dev/null @@ -1,133 +0,0 @@ -import WebGLSystem from './WebGLSystem'; - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class StencilSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - this.stencilMaskStack = []; - } - - /** - * Changes the mask stack that is used by this System. - * - * @param {PIXI.Graphics[]} stencilMaskStack - The mask stack - */ - setMaskStack(stencilMaskStack) - { - const gl = this.renderer.gl; - - if (stencilMaskStack.length !== this.stencilMaskStack.length) - { - if (stencilMaskStack.length === 0) - { - gl.disable(gl.STENCIL_TEST); - } - else - { - gl.enable(gl.STENCIL_TEST); - } - } - - this.stencilMaskStack = stencilMaskStack; - } - - /** - * Applies the Mask and adds it to the current stencil stack. @alvin - * - * @param {PIXI.Graphics} graphics - The mask - */ - pushStencil(graphics) - { - this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); - - // this.renderer._activeRenderTarget.attachStencilBuffer(); - - const gl = this.renderer.gl; - const prevMaskCount = this.stencilMaskStack.length; - - if (prevMaskCount === 0) - { - gl.enable(gl.STENCIL_TEST); - } - - this.stencilMaskStack.push(graphics); - - // Increment the refference stencil value where the new mask overlaps with the old ones. - gl.colorMask(false, false, false, false); - gl.stencilFunc(gl.EQUAL, prevMaskCount, this._getBitwiseMask()); - gl.stencilOp(gl.KEEP, gl.KEEP, gl.INCR); - this.renderer.plugins.graphics.render(graphics); - - this._useCurrent(); - } - - /** - * Removes the last mask from the stencil stack. @alvin - */ - popStencil() - { - this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); - - const gl = this.renderer.gl; - const graphics = this.stencilMaskStack.pop(); - - if (this.stencilMaskStack.length === 0) - { - // the stack is empty! - gl.disable(gl.STENCIL_TEST); - gl.clear(gl.STENCIL_BUFFER_BIT); - gl.clearStencil(0); - } - else - { - // Decrement the refference stencil value where the popped mask overlaps with the other ones - gl.colorMask(false, false, false, false); - gl.stencilOp(gl.KEEP, gl.KEEP, gl.DECR); - this.renderer.plugins.graphics.render(graphics); - - this._useCurrent(); - } - } - - /** - * Setup renderer to use the current stencil data. - */ - _useCurrent() - { - const gl = this.renderer.gl; - - gl.colorMask(true, true, true, true); - gl.stencilFunc(gl.EQUAL, this.stencilMaskStack.length, this._getBitwiseMask()); - gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP); - } - - /** - * Fill 1s equal to the number of acitve stencil masks. - * - * @return {number} The bitwise mask. - */ - _getBitwiseMask() - { - return (1 << this.stencilMaskStack.length) - 1; - } - - /** - * Destroys the mask stack. - * - */ - destroy() - { - super.destroy(this); - - this.stencilMaskStack = null; - } -} diff --git a/packages/core/src/renderers/systems/WebGLSystem.js b/packages/core/src/renderers/systems/WebGLSystem.js deleted file mode 100644 index d3d5b31..0000000 --- a/packages/core/src/renderers/systems/WebGLSystem.js +++ /dev/null @@ -1,41 +0,0 @@ -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this manager works for. - */ - constructor(renderer) - { - /** - * The renderer this manager works for. - * - * @member {PIXI.Renderer} - */ - this.renderer = renderer; - - this.renderer.runners.contextChange.add(this); - } - - /** - * Generic method called when there is a WebGL context change. - * - */ - contextChange() - { - // do some codes init! - } - - /** - * Generic destroy methods to be overridden by the subclass - * - */ - destroy() - { - this.renderer.runners.contextChange.remove(this); - this.renderer = null; - } -} diff --git a/packages/core/src/renderers/systems/filter/FilterSystem.js b/packages/core/src/renderers/systems/filter/FilterSystem.js deleted file mode 100644 index e615037..0000000 --- a/packages/core/src/renderers/systems/filter/FilterSystem.js +++ /dev/null @@ -1,431 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; - -import RenderTexture from '../../../textures/RenderTexture'; -import Quad from '../../utils/Quad'; -import QuadUv from '../../utils/QuadUv'; -import { Rectangle } from '@pixi/math'; -import * as filterTransforms from '../../filters/filterTransforms'; -import bitTwiddle from 'bit-twiddle'; -import UniformGroup from '../../../shader/UniformGroup'; -import { DRAW_MODES } from '../../../../../constants'; - -// -/** - * @ignore - * @class - */ -class FilterState -{ - /** - * - */ - constructor() - { - this.renderTexture = null; - this.sourceFrame = new Rectangle(); - this.destinationFrame = new Rectangle(); - this.filters = []; - this.target = null; - this.legacy = false; - this.resolution = 1; - } -} - -/** - * @class - * @memberof PIXI.systems - * @extends PIXI.systems.WebGLSystem - */ -export default class FilterSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - /** - * stores a bunch of PO2 textures used for filtering - * @type {Object} - */ - this.texturePool = {}; - - /** - * a pool for storing filter states, save us creating new ones each tick - * @type {Array} - */ - this.statePool = []; - - /** - * A very simple geometry used when drawing a filter effect to the screen - * @type {Quad} - */ - this.quad = new Quad(); - - this.quadUv = new QuadUv(); - - /** - * Temporary rect for maths - * @type {PIXI.Rectangle} - */ - this.tempRect = new Rectangle(); - - this.activeState = {}; - - /** - * this uniform group is attached to filter uniforms when used - * @type {UniformGroup} - */ - this.globalUniforms = new UniformGroup({ - sourceFrame: this.tempRect, - destinationFrame: this.tempRect, - - // legacy variables - filterArea: new Float32Array(4), - filterClamp: new Float32Array(4), - }, true); - } - - /** - * Adds a new filter to the System. - * - * @param {PIXI.DisplayObject} target - The target of the filter to render. - * @param {PIXI.Filter[]} filters - The filters to apply. - */ - push(target, filters) - { - const renderer = this.renderer; - const filterStack = this.renderer.renderTexture.defaultFilterStack; - const state = this.statePool.pop() || new FilterState(); - - let resolution = filters[0].resolution; - let padding = filters[0].padding; - let autoFit = filters[0].autoFit; - let legacy = filters[0].legacy; - - for (let i = 1; i < filters.length; i++) - { - const filter = filters[i]; - - // lets use the lowest resolution.. - resolution = Math.min(resolution, filter.resolution); - // and the largest amount of padding! - padding = Math.max(padding, filter.padding); - // only auto fit if all filters are autofit - autoFit = autoFit || filter.autoFit; - - legacy = legacy || filter.legacy; - } - - filterStack.push(state); - - state.resolution = resolution; - - state.legacy = legacy; - - // round to whole number based on resolution - // TODO move that to the shader too? - state.sourceFrame = target.filterArea || target.getBounds(true); - - state.sourceFrame.pad(padding); - - if (autoFit) - { - state.sourceFrame.fit(this.renderer.renderTexture.destinationFrame); - } - - state.sourceFrame.round(resolution); - - state.renderTexture = this.getPotFilterTexture(state.sourceFrame.width, state.sourceFrame.height, resolution); - state.filters = filters; - - state.destinationFrame.width = state.renderTexture.width; - state.destinationFrame.height = state.renderTexture.height; - - state.renderTexture.filterFrame = state.sourceFrame; - renderer.renderTexture.bind(state.renderTexture, state.sourceFrame);// /, state.destinationFrame); - renderer.renderTexture.clear(); - } - - /** - * Pops off the filter and applies it. - * - */ - pop() - { - const renderer = this.renderer; - const filterStack = renderer.renderTexture.defaultFilterStack; - const state = filterStack.pop(); - const filters = state.filters; - - this.activeState = state; - - const globalUniforms = this.globalUniforms.uniforms; - - globalUniforms.sourceFrame = state.sourceFrame; - globalUniforms.destinationFrame = state.destinationFrame; - globalUniforms.resolution = state.resolution; - - // only update the rect if its legacy.. - if (state.legacy) - { - const filterArea = globalUniforms.filterArea; - const filterClamp = globalUniforms.filterClamp; - - filterArea[0] = state.destinationFrame.width; - filterArea[1] = state.destinationFrame.height; - filterArea[2] = state.sourceFrame.x; - filterArea[3] = state.sourceFrame.y; - - filterClamp[0] = 0.5 / state.resolution / state.destinationFrame.width; - filterClamp[1] = 0.5 / state.resolution / state.destinationFrame.height; - filterClamp[2] = (state.sourceFrame.width - 0.5) / state.resolution / state.destinationFrame.width; - filterClamp[3] = (state.sourceFrame.height - 0.5) / state.resolution / state.destinationFrame.height; - } - - this.globalUniforms.update(); - - const lastState = filterStack[filterStack.length - 1]; - - if (filters.length === 1) - { - filters[0].apply(this, state.renderTexture, lastState.renderTexture, false, state); - - this.returnFilterTexture(state.renderTexture); - } - else - { - let flip = state.renderTexture; - let flop = this.getPotFilterTexture( - flip.width, - flip.height, - state.resolution - ); - - flop.filterFrame = flip.filterFrame; - - let i = 0; - - for (i = 0; i < filters.length - 1; ++i) - { - filters[i].apply(this, flip, flop, true, state); - - const t = flip; - - flip = flop; - flop = t; - } - - filters[i].apply(this, flip, lastState.renderTexture, false, state); - - this.returnFilterTexture(flip); - this.returnFilterTexture(flop); - } - - this.statePool.push(state); - } - - /** - * Draws a filter. - * - * @param {PIXI.Filter} filter - The filter to draw. - * @param {PIXI.RenderTarget} input - The input render target. - * @param {PIXI.RenderTarget} output - The target to output to. - * @param {boolean} clear - Should the output be cleared before rendering to it - */ - applyFilter(filter, input, output, clear) - { - const renderer = this.renderer; - - renderer.renderTexture.bind(output, output ? output.filterFrame : null); - - if (clear) - { - // gl.disable(gl.SCISSOR_TEST); - renderer.renderTexture.clear(); - // gl.enable(gl.SCISSOR_TEST); - } - - // set the uniforms.. - filter.uniforms.uSampler = input; - filter.uniforms.filterGlobals = this.globalUniforms; - - // TODO make it so that the order of this does not matter.. - // because it does at the moment cos of global uniforms. - // they need to get resynced - - renderer.state.setState(filter.state); - renderer.shader.bind(filter); - - if (filter.legacy) - { - this.quadUv.map(input._frame, input.filterFrame); - - renderer.geometry.bind(this.quadUv); - renderer.geometry.draw(DRAW_MODES.TRIANGLES); - } - else - { - renderer.geometry.bind(this.quad); - renderer.geometry.draw(DRAW_MODES.TRIANGLE_STRIP); - } - } - - /** - * Calculates the mapped matrix. - * - * TODO playing around here.. this is temporary - (will end up in the shader) - * this returns a matrix that will normalise map filter cords in the filter to screen space - * - * @param {PIXI.Matrix} outputMatrix - the matrix to output to. - * @return {PIXI.Matrix} The mapped matrix. - */ - calculateScreenSpaceMatrix(outputMatrix) - { - const currentState = this.activeState; - - return filterTransforms.calculateScreenSpaceMatrix( - outputMatrix, - currentState.sourceFrame, - currentState.destinationFrame - ); - } - - /** - * This will map the filter coord so that a texture can be used based on the transform of a sprite - * - * @param {PIXI.Matrix} outputMatrix - The matrix to output to. - * @param {PIXI.Sprite} sprite - The sprite to map to. - * @return {PIXI.Matrix} The mapped matrix. - */ - calculateSpriteMatrix(outputMatrix, sprite) - { - const currentState = this.activeState; - - return filterTransforms.calculateSpriteMatrix( - outputMatrix, - currentState.sourceFrame, - currentState.destinationFrame, - sprite - ); - } - - /** - * Destroys this Filter System. - * - * @param {boolean} [contextLost=false] context was lost, do not free shaders - * - */ - destroy(contextLost = false) - { - if (!contextLost) - { - this.emptyPool(); - } - else - { - this.texturePool = {}; - } - } - - /** - * Gets a Power-of-Two render texture. - * - * TODO move to a seperate class could be on renderer? - * also - could cause issue with multiple contexts? - * - * @private - * @param {WebGLRenderingContext} gl - The webgl rendering context - * @param {number} minWidth - The minimum width of the render target. - * @param {number} minHeight - The minimum height of the render target. - * @param {number} resolution - The resolution of the render target. - * @return {PIXI.RenderTarget} The new render target. - */ - getPotFilterTexture(minWidth, minHeight, resolution) - { - minWidth = bitTwiddle.nextPow2(minWidth); - minHeight = bitTwiddle.nextPow2(minHeight); - resolution = resolution || 1; - - const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); - - if (!this.texturePool[key]) - { - this.texturePool[key] = []; - } - - let renderTexture = this.texturePool[key].pop(); - - if (!renderTexture) - { - // temporary bypass cache.. - // internally - this will cause a texture to be bound.. - renderTexture = RenderTexture.create({ - width: minWidth, - height: minHeight, - resolution, - }); - } - - return renderTexture; - } - - /** - * Gets extra render texture to use inside current filter - * - * @param {number} resolution resolution of the renderTexture - * @returns {PIXI.RenderTexture} - */ - getFilterTexture(resolution) - { - const rt = this.activeState.renderTexture; - - const filterTexture = this.getPotFilterTexture(rt.width, rt.height, resolution || rt.baseTexture.resolution); - - filterTexture.filterFrame = rt.filterFrame; - - return filterTexture; - } - - /** - * Frees a render texture back into the pool. - * - * @param {PIXI.RenderTarget} renderTexture - The renderTarget to free - */ - returnFilterTexture(renderTexture) - { - renderTexture.filterFrame = null; - - const base = renderTexture.baseTexture; - - const minWidth = base.width; - const minHeight = base.height; - - const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); - - this.texturePool[key].push(renderTexture); - } - - /** - * Empties the texture pool. - * - */ - emptyPool() - { - for (const i in this.texturePool) - { - const textures = this.texturePool[i]; - - if (textures) - { - for (let j = 0; j < textures.length; j++) - { - textures[j].destroy(true); - } - } - } - - this.texturePool = {}; - } -} diff --git a/packages/core/src/renderers/systems/geometry/GLBuffer.js b/packages/core/src/renderers/systems/geometry/GLBuffer.js deleted file mode 100644 index 0477e8a..0000000 --- a/packages/core/src/renderers/systems/geometry/GLBuffer.js +++ /dev/null @@ -1,9 +0,0 @@ -export default class GLBuffer -{ - constructor(buffer) - { - this.buffer = buffer; - this.updateID = -1; - this.byteLength = -1; - } -} diff --git a/packages/core/src/renderers/systems/geometry/GeometrySystem.js b/packages/core/src/renderers/systems/geometry/GeometrySystem.js deleted file mode 100644 index 26c8e9e..0000000 --- a/packages/core/src/renderers/systems/geometry/GeometrySystem.js +++ /dev/null @@ -1,388 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; -import GLBuffer from './GLBuffer'; - -const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class GeometrySystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this._activeGeometry = null; - this._activeVao = null; - - this.hasVao = true; - this.hasInstance = true; - } - - /** - * Sets up the renderer context and necessary buffers. - * - * @private - */ - contextChange() - { - const gl = this.gl = this.renderer.gl; - - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - - // webgl2 - if (!gl.createVertexArray) - { - // webgl 1! - let nativeVaoExtension = this.renderer.context.extensions.vertexArrayObject; - - if (this.renderer.legacy) - { - nativeVaoExtension = null; - } - - if (nativeVaoExtension) - { - gl.createVertexArray = () => - nativeVaoExtension.createVertexArrayOES(); - - gl.bindVertexArray = (vao) => - nativeVaoExtension.bindVertexArrayOES(vao); - - gl.deleteVertexArray = (vao) => - nativeVaoExtension.deleteVertexArrayOES(vao); - } - else - { - this.hasVao = false; - gl.createVertexArray = () => - { - // empty - }; - - gl.bindVertexArray = () => - { - // empty - }; - - gl.deleteVertexArray = () => - { - // empty - }; - } - } - - if (!gl.vertexAttribDivisor) - { - const instanceExt = gl.getExtension('ANGLE_instanced_arrays'); - - if (instanceExt) - { - gl.vertexAttribDivisor = (a, b) => - instanceExt.vertexAttribDivisorANGLE(a, b); - - gl.drawElementsInstanced = (a, b, c, d, e) => - instanceExt.drawElementsInstancedANGLE(a, b, c, d, e); - - gl.drawArraysInstanced = (a, b, c, d) => - instanceExt.drawArraysInstancedANGLE(a, b, c, d); - } - else - { - this.hasInstance = false; - } - } - } - - /** - * Binds geometry so that is can be drawn. Creating a Vao if required - * @private - * @param {PIXI.Geometry} geometry instance of geometry to bind - */ - bind(geometry, shader) - { - shader = shader || this.renderer.shader.shader; - - const gl = this.gl; - - // not sure the best way to address this.. - // currently different shaders require different VAOs for the same geometry - // Still mulling over the best way to solve this one.. - // will likely need to modify the shader attribute locations at run time! - let vaos = geometry.glVertexArrayObjects[this.CONTEXT_UID]; - - if (!vaos) - { - geometry.glVertexArrayObjects[this.CONTEXT_UID] = vaos = {}; - } - - const vao = vaos[shader.program.id] || this.initGeometryVao(geometry, shader.program); - - this._activeGeometry = geometry; - - if (this._activeVao !== vao) - { - this._activeVao = vao; - - if (this.hasVao) - { - gl.bindVertexArray(vao); - } - else - { - this.activateVao(geometry, shader.program); - } - } - - // TODO - optimise later! - // don't need to loop through if nothing changed! - // maybe look to add an 'autoupdate' to geometry? - this.updateBuffers(); - } - - reset() - { - this.unbind(); - } - - updateBuffers() - { - const geometry = this._activeGeometry; - const gl = this.gl; - - for (let i = 0; i < geometry.buffers.length; i++) - { - const buffer = geometry.buffers[i]; - - const glBuffer = buffer._glBuffers[this.CONTEXT_UID]; - - if (buffer._updateID !== glBuffer.updateID) - { - glBuffer.updateID = buffer._updateID; - - // TODO can cache this on buffer! maybe added a getter / setter? - const type = buffer.index ? gl.ELEMENT_ARRAY_BUFFER : gl.ARRAY_BUFFER; - const drawType = buffer.static ? gl.STATIC_DRAW : gl.DYNAMIC_DRAW; - - gl.bindBuffer(type, glBuffer.buffer); - - if (glBuffer.byteLength >= buffer.data.byteLength) - { - // offset is always zero for now! - gl.bufferSubData(type, 0, buffer.data); - } - else - { - gl.bufferData(type, buffer.data, drawType); - } - } - } - } - - checkCompatability(geometry, program) - { - // geometry must have at least all the attributes that the shader requires. - const geometryAttributes = geometry.attributes; - const shaderAttributes = program.attributeData; - - for (const j in shaderAttributes) - { - if (!geometryAttributes[j]) - { - throw new Error(`shader and geometry incompatible, geometry missing the "${j}" attribute`); - } - } - } - - /** - * Creates a Vao with the same structure as the geometry and stores it on the geometry. - * @private - * @param {PIXI.Geometry} geometry instance of geometry to to generate Vao for - */ - initGeometryVao(geometry, program) - { - this.checkCompatability(geometry, program); - - const gl = this.gl; - const CONTEXT_UID = this.CONTEXT_UID; - const buffers = geometry.buffers; - const attributes = geometry.attributes; - - const tempStride = {}; - const tempStart = {}; - - for (const j in buffers) - { - tempStride[j] = 0; - tempStart[j] = 0; - } - - for (const j in attributes) - { - if (!attributes[j].size && program.attributeData[j]) - { - attributes[j].size = program.attributeData[j].size; - } - - tempStride[attributes[j].buffer] += attributes[j].size * byteSizeMap[attributes[j].type]; - } - - for (const j in attributes) - { - const attribute = attributes[j]; - const attribSize = attribute.size; - - if (attribute.stride === undefined) - { - if (tempStride[attribute.buffer] === attribSize * byteSizeMap[attribute.type]) - { - attribute.stride = 0; - } - else - { - attribute.stride = tempStride[attribute.buffer]; - } - } - - if (attribute.start === undefined) - { - attribute.start = tempStart[attribute.buffer]; - - tempStart[attribute.buffer] += attribSize * byteSizeMap[attribute.type]; - } - } - - // first update - and create the buffers! - // only create a gl buffer if it actually gets - for (let i = 0; i < buffers.length; i++) - { - const buffer = buffers[i]; - - if (!buffer._glBuffers[CONTEXT_UID]) - { - buffer._glBuffers[CONTEXT_UID] = new GLBuffer(gl.createBuffer()); - } - } - - // TODO - maybe make this a data object? - // lets wait to see if we need to first! - const vao = gl.createVertexArray(); - - gl.bindVertexArray(vao); - - this.activateVao(geometry, program); - - gl.bindVertexArray(null); - - // add it to the cache! - geometry.glVertexArrayObjects[this.CONTEXT_UID][program.id] = vao; - - return vao; - } - - activateVao(geometry, program) - { - const gl = this.gl; - const CONTEXT_UID = this.CONTEXT_UID; - const buffers = geometry.buffers; - const attributes = geometry.attributes; - - if (geometry.indexBuffer) - { - // first update the index buffer if we have one.. - gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, geometry.indexBuffer._glBuffers[CONTEXT_UID].buffer); - } - - let lastBuffer = null; - - // add a new one! - for (const j in attributes) - { - const attribute = attributes[j]; - const buffer = buffers[attribute.buffer]; - const glBuffer = buffer._glBuffers[CONTEXT_UID]; - - if (program.attributeData[j]) - { - if (lastBuffer !== glBuffer) - { - gl.bindBuffer(gl.ARRAY_BUFFER, glBuffer.buffer); - - lastBuffer = glBuffer; - } - - const location = program.attributeData[j].location; - - // TODO introduce state again - // we can optimise this for older devices that have no VAOs - gl.enableVertexAttribArray(location); - - gl.vertexAttribPointer(location, - attribute.size, - attribute.type || gl.FLOAT, - attribute.normalized, - attribute.stride, - attribute.start); - - if (attribute.instance) - { - // TODO calculate instance count based of this... - if (this.hasInstance) - { - gl.vertexAttribDivisor(location, 1); - } - else - { - throw new Error('geometry error, GPU Instancing is not supported on this device'); - } - } - } - } - } - - draw(type, size, start, instanceCount) - { - const gl = this.gl; - const geometry = this._activeGeometry; - - // TODO.. this should not change so maybe cache the function? - - if (geometry.indexBuffer) - { - if (geometry.instanced) - { - /* eslint-disable max-len */ - gl.drawElementsInstanced(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2, instanceCount || 1); - /* eslint-enable max-len */ - } - else - { - gl.drawElements(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2); - } - } - else - if (geometry.instanced) - { - // TODO need a better way to calculate size.. - gl.drawArraysInstanced(type, start, size || geometry.getSize(), instanceCount || 1); - } - else - { - gl.drawArrays(type, start, size || geometry.getSize()); - } - - return this; - } - - unbind() - { - this.gl.bindVertexArray(null); - this._activeVao = null; - this._activeGeometry = null; - } -} diff --git a/packages/core/src/renderers/systems/geometry/setVertexAttribArrays.js b/packages/core/src/renderers/systems/geometry/setVertexAttribArrays.js deleted file mode 100644 index 23b6959..0000000 --- a/packages/core/src/renderers/systems/geometry/setVertexAttribArrays.js +++ /dev/null @@ -1,54 +0,0 @@ -// var GL_MAP = {}; - -/** - * @param gl {WebGLRenderingContext} The current WebGL context - * @param attribs {*} - * @param state {*} - */ -export default function setVertexAttribArrays(gl, attribs, state) -{ - let i; - - if (state) - { - const tempAttribState = state.tempAttribState; - const attribState = state.attribState; - - for (i = 0; i < tempAttribState.length; i++) - { - tempAttribState[i] = false; - } - - // set the new attribs - for (i = 0; i < attribs.length; i++) - { - tempAttribState[attribs[i].attribute.location] = true; - } - - for (i = 0; i < attribState.length; i++) - { - if (attribState[i] !== tempAttribState[i]) - { - attribState[i] = tempAttribState[i]; - - if (state.attribState[i]) - { - gl.enableVertexAttribArray(i); - } - else - { - gl.disableVertexAttribArray(i); - } - } - } - } - else - { - for (i = 0; i < attribs.length; i++) - { - const attrib = attribs[i]; - - gl.enableVertexAttribArray(attrib.attribute.location); - } - } -} diff --git a/packages/core/src/renderers/systems/index.js b/packages/core/src/renderers/systems/index.js deleted file mode 100644 index c843f03..0000000 --- a/packages/core/src/renderers/systems/index.js +++ /dev/null @@ -1,6 +0,0 @@ -/** - * Systems are individual components to the Renderer pipeline. - * @namespace PIXI.systems - */ -export { default as WebGLSystem } from './WebGLSystem'; -export { default as FilterSystem } from './filter/FilterSystem'; diff --git a/packages/core/src/renderers/systems/shader/GLProgram.js b/packages/core/src/renderers/systems/shader/GLProgram.js deleted file mode 100644 index 92980ca..0000000 --- a/packages/core/src/renderers/systems/shader/GLProgram.js +++ /dev/null @@ -1,43 +0,0 @@ -/** - * Helper class to create a webGL Program - * - * @class - * @private - */ -export default class GLProgram -{ - constructor(program, uniformData) - { - /** - * The shader program - * - * @member {WebGLProgram} - */ - this.program = program; - - /** - * holds the uniform data which contains uniform locations - * and current uniform values used for caching and preventing undeed GPU commands - * @type {Object} - */ - this.uniformData = uniformData; - - /** - * uniformGroups holds the various upload functions for the shader. Each uniform group - * and program have a unique upload function generated. - * @type {Object} - */ - this.uniformGroups = {}; - } - - /** - * Destroys this program - * TODO - */ - destroy() - { - this.uniformData = null; - this.uniformGroups = null; - this.program = null; - } -} diff --git a/packages/core/src/renderers/systems/shader/ShaderSystem.js b/packages/core/src/renderers/systems/shader/ShaderSystem.js deleted file mode 100644 index 6cf57b6..0000000 --- a/packages/core/src/renderers/systems/shader/ShaderSystem.js +++ /dev/null @@ -1,170 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; -import GLProgram from './GLProgram'; -import generateUniformsSync from '../../../shader/generateUniformsSync'; -import defaultValue from '../../../shader/defaultValue'; -import compileProgram from './shader/compileProgram'; - -let UID = 0; - -/** - * Helper class to create a webGL Texture - * - * @class - * @memberof PIXI - */ -export default class ShaderSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - A reference to the current renderer - */ - constructor(renderer) - { - super(renderer); - - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = null; - - this.shader = null; - this.program = null; - - this.id = UID++; - } - - contextChange(gl) - { - this.gl = gl; - } - - /** - * Changes the current shader to the one given in parameter - * - * @param {PIXI.Shader} shader - the new shader - * @param {boolean} dontSync - false if the shader should automatically sync its uniforms. - * @returns {PIXI.glCore.glProgram} the glProgram that belongs to the shader. - */ - bind(shader, dontSync) - { - shader.uniforms.globals = this.renderer.globalUniforms; - - const program = shader.program; - const glProgram = program.glPrograms[this.renderer.CONTEXT_UID] || this.generateShader(shader); - - this.shader = shader; - - // TODO - some current pixi plugins bypass this.. so it not safe to use yet.. - if (this.program !== program) - { - this.program = program; - this.gl.useProgram(glProgram.program); - } - - if (!dontSync) - { - this.syncUniformGroup(shader.uniformGroup); - } - - return glProgram; - } - - /** - * Uploads the uniforms values to the currently bound shader. - * - * @param {object} uniforms - the uniforms valiues that be applied to the current shader - */ - setUniforms(uniforms) - { - const shader = this.shader.program; - const glProgram = shader.glPrograms[this.renderer.CONTEXT_UID]; - - shader.syncUniforms(glProgram.uniformData, uniforms, this.renderer); - } - - syncUniformGroup(group) - { - const glProgram = this.getglProgram(); - - if (!group.static || group.dirtyId !== glProgram.uniformGroups[group.id]) - { - glProgram.uniformGroups[group.id] = group.dirtyId; - const syncFunc = group.syncUniforms[this.shader.program.id] || this.createSyncGroups(group); - - syncFunc(glProgram.uniformData, group.uniforms, this.renderer); - } - } - - createSyncGroups(group) - { - group.syncUniforms[this.shader.program.id] = generateUniformsSync(group, this.shader.program.uniformData); - - return group.syncUniforms[this.shader.program.id]; - } - - /** - * Returns the underlying GLShade rof the currently bound shader. - * This can be handy for when you to have a little more control over the setting of your uniforms. - * - * @return {PIXI.glCore.Shader} the glProgram for the currently bound Shader for this context - */ - getglProgram() - { - if (this.shader) - { - return this.shader.program.glPrograms[this.renderer.CONTEXT_UID]; - } - - return null; - } - - /** - * Generates a glProgram verion of the Shader provided. - * - * @private - * @param {PIXI.Shader} shader the shader that the glProgram will be based on. - * @return {PIXI.glCore.glProgram} A shiney new glProgram - */ - generateShader(shader) - { - const gl = this.gl; - - const program = shader.program; - - const attribMap = {}; - - for (const i in program.attributeData) - { - attribMap[i] = program.attributeData[i].location; - } - - const shaderProgram = compileProgram(gl, program.vertexSrc, program.fragmentSrc, attribMap); - const uniformData = {}; - - for (const i in program.uniformData) - { - const data = program.uniformData[i]; - - uniformData[i] = { - location: gl.getUniformLocation(shaderProgram, i), - value: defaultValue(data.type, data.size), - }; - } - - const glProgram = new GLProgram(shaderProgram, uniformData); - - program.glPrograms[this.renderer.CONTEXT_UID] = glProgram; - - return glProgram; - } - - /** - * Destroys this System and removes all its textures - */ - destroy() - { - // TODO implement destroy method for ShaderSystem - this.destroyed = true; - } -} diff --git a/packages/core/src/renderers/systems/shader/shader/compileProgram.js b/packages/core/src/renderers/systems/shader/shader/compileProgram.js deleted file mode 100644 index 8c6ccb9..0000000 --- a/packages/core/src/renderers/systems/shader/shader/compileProgram.js +++ /dev/null @@ -1,78 +0,0 @@ -/** - * @method compileProgram - * @memberof PIXI.glCore.shader - * @param gl {WebGLRenderingContext} The current WebGL context {WebGLProgram} - * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. - * @param fragmentSrc {string|string[]} The fragment shader source as an array of strings. - * @param attributeLocations {Object} An attribute location map that lets you manually set the attribute locations - * @return {WebGLProgram} the shader program - */ -export default function compileProgram(gl, vertexSrc, fragmentSrc, attributeLocations) -{ - const glVertShader = compileShader(gl, gl.VERTEX_SHADER, vertexSrc); - const glFragShader = compileShader(gl, gl.FRAGMENT_SHADER, fragmentSrc); - - let program = gl.createProgram(); - - gl.attachShader(program, glVertShader); - gl.attachShader(program, glFragShader); - - // optionally, set the attributes manually for the program rather than letting WebGL decide.. - if (attributeLocations) - { - for (const i in attributeLocations) - { - gl.bindAttribLocation(program, attributeLocations[i], i); - } - } - - gl.linkProgram(program); - - // if linking fails, then log and cleanup - if (!gl.getProgramParameter(program, gl.LINK_STATUS)) - { - console.error('Pixi.js Error: Could not initialize shader.'); - console.error('gl.VALIDATE_STATUS', gl.getProgramParameter(program, gl.VALIDATE_STATUS)); - console.error('gl.getError()', gl.getError()); - - // if there is a program info log, log it - if (gl.getProgramInfoLog(program) !== '') - { - console.warn('Pixi.js Warning: gl.getProgramInfoLog()', gl.getProgramInfoLog(program)); - } - - gl.deleteProgram(program); - program = null; - } - - // clean up some shaders - gl.deleteShader(glVertShader); - gl.deleteShader(glFragShader); - - return program; -} - -/** - * @private - * @param gl {WebGLRenderingContext} The current WebGL context {WebGLProgram} - * @param type {Number} the type, can be either VERTEX_SHADER or FRAGMENT_SHADER - * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. - * @return {WebGLShader} the shader - */ -function compileShader(gl, type, src) -{ - const shader = gl.createShader(type); - - gl.shaderSource(shader, src); - gl.compileShader(shader); - - if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) - { - console.warn(src); - console.error(gl.getShaderInfoLog(shader)); - - return null; - } - - return shader; -} diff --git a/packages/core/src/renderers/systems/shader/shader/defaultValue.js b/packages/core/src/renderers/systems/shader/shader/defaultValue.js deleted file mode 100644 index 97290bd..0000000 --- a/packages/core/src/renderers/systems/shader/shader/defaultValue.js +++ /dev/null @@ -1,79 +0,0 @@ -/** - * @method defaultValue - * @memberof PIXI.glCore.shader - * @param type {String} Type of value - * @param size {Number} - */ -export default function defaultValue(type, size) -{ - switch (type) - { - case 'float': - return 0; - - case 'vec2': - return new Float32Array(2 * size); - - case 'vec3': - return new Float32Array(3 * size); - - case 'vec4': - return new Float32Array(4 * size); - - case 'int': - case 'sampler2D': - case 'sampler2DArray': - return 0; - - case 'ivec2': - return new Int32Array(2 * size); - - case 'ivec3': - return new Int32Array(3 * size); - - case 'ivec4': - return new Int32Array(4 * size); - - case 'bool': - return false; - - case 'bvec2': - - return booleanArray(2 * size); - - case 'bvec3': - return booleanArray(3 * size); - - case 'bvec4': - return booleanArray(4 * size); - - case 'mat2': - return new Float32Array([1, 0, - 0, 1]); - - case 'mat3': - return new Float32Array([1, 0, 0, - 0, 1, 0, - 0, 0, 1]); - - case 'mat4': - return new Float32Array([1, 0, 0, 0, - 0, 1, 0, 0, - 0, 0, 1, 0, - 0, 0, 0, 1]); - } - - return null; -} - -function booleanArray(size) -{ - const array = new Array(size); - - for (let i = 0; i < array.length; i++) - { - array[i] = false; - } - - return array; -} diff --git a/packages/core/src/renderers/systems/shader/shader/index.js b/packages/core/src/renderers/systems/shader/shader/index.js deleted file mode 100644 index ba7a317..0000000 --- a/packages/core/src/renderers/systems/shader/shader/index.js +++ /dev/null @@ -1,8 +0,0 @@ -/** - * @namespace PIXI.glCore.shader - */ -export { default as compileProgram } from './compileProgram'; -export { default as defaultValue } from './defaultValue'; -export { default as setPrecision } from './setPrecision'; -export { default as mapSize } from './mapSize'; -export { default as mapType } from './mapType'; diff --git a/packages/core/src/renderers/systems/shader/shader/mapSize.js b/packages/core/src/renderers/systems/shader/shader/mapSize.js deleted file mode 100644 index b681554..0000000 --- a/packages/core/src/renderers/systems/shader/shader/mapSize.js +++ /dev/null @@ -1,33 +0,0 @@ -const GLSL_TO_SIZE = { - float: 1, - vec2: 2, - vec3: 3, - vec4: 4, - - int: 1, - ivec2: 2, - ivec3: 3, - ivec4: 4, - - bool: 1, - bvec2: 2, - bvec3: 3, - bvec4: 4, - - mat2: 4, - mat3: 9, - mat4: 16, - - sampler2D: 1, -}; - -/** - * @method mapSize - * @memberof PIXI.glCore.shader - * @param type {String} - * @return {Number} - */ -export default function mapSize(type) -{ - return GLSL_TO_SIZE[type]; -} diff --git a/packages/core/src/renderers/systems/shader/shader/mapType.js b/packages/core/src/renderers/systems/shader/shader/mapType.js deleted file mode 100644 index d288b7c..0000000 --- a/packages/core/src/renderers/systems/shader/shader/mapType.js +++ /dev/null @@ -1,45 +0,0 @@ -let GL_TABLE = null; - -const GL_TO_GLSL_TYPES = { - FLOAT: 'float', - FLOAT_VEC2: 'vec2', - FLOAT_VEC3: 'vec3', - FLOAT_VEC4: 'vec4', - - INT: 'int', - INT_VEC2: 'ivec2', - INT_VEC3: 'ivec3', - INT_VEC4: 'ivec4', - - BOOL: 'bool', - BOOL_VEC2: 'bvec2', - BOOL_VEC3: 'bvec3', - BOOL_VEC4: 'bvec4', - - FLOAT_MAT2: 'mat2', - FLOAT_MAT3: 'mat3', - FLOAT_MAT4: 'mat4', - - SAMPLER_2D: 'sampler2D', - SAMPLER_CUBE: 'samplerCube', - SAMPLER_2D_ARRAY: 'sampler2DArray', -}; - -export default function mapType(gl, type) -{ - if (!GL_TABLE) - { - const typeNames = Object.keys(GL_TO_GLSL_TYPES); - - GL_TABLE = {}; - - for (let i = 0; i < typeNames.length; ++i) - { - const tn = typeNames[i]; - - GL_TABLE[gl[tn]] = GL_TO_GLSL_TYPES[tn]; - } - } - - return GL_TABLE[type]; -} diff --git a/packages/core/src/renderers/systems/shader/shader/setPrecision.js b/packages/core/src/renderers/systems/shader/shader/setPrecision.js deleted file mode 100644 index e2c1f34..0000000 --- a/packages/core/src/renderers/systems/shader/shader/setPrecision.js +++ /dev/null @@ -1,18 +0,0 @@ -/** - * Sets the float precision on the shader. If the precision is already present this function will do nothing - * @param {string} src the shader source - * @param {string} precision The float precision of the shader. Options are 'lowp', 'mediump' or 'highp'. - * - * @return {string} modified shader source - */ -export default function setPrecision(src, precision) -{ - src = src.trim(); - - if (src.substring(0, 9) !== 'precision' && src.substring(0, 1) !== '#') - { - return `precision ${precision} float;\n${src}`; - } - - return src; -} diff --git a/packages/core/src/renderers/systems/textures/GLTexture.js b/packages/core/src/renderers/systems/textures/GLTexture.js deleted file mode 100644 index c3fb8dc..0000000 --- a/packages/core/src/renderers/systems/textures/GLTexture.js +++ /dev/null @@ -1,33 +0,0 @@ -export default class GLTexture -{ - constructor(texture) - { - /** - * The WebGL texture - * - * @member {WebGLTexture} - */ - this.texture = texture; - - this.width = -1; - this.height = -1; - - /** - * Texture contents dirty flag - * @member {number} - */ - this.dirtyId = -1; - - /** - * Texture style dirty flag - * @type {number} - */ - this.dirtyStyleId = -1; - - /** - * Whether mip levels has to be generated - * @type {boolean} - */ - this.mipmap = false; - } -} diff --git a/packages/core/src/renderers/systems/textures/TextureGCSystem.js b/packages/core/src/renderers/systems/textures/TextureGCSystem.js deleted file mode 100644 index b089938..0000000 --- a/packages/core/src/renderers/systems/textures/TextureGCSystem.js +++ /dev/null @@ -1,110 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; -import { GC_MODES } from '@pixi/constants'; -import { settings } from '@pixi/settings'; - -/** - * TextureGarbageCollector. This class manages the GPU and ensures that it does not get clogged - * up with textures that are no longer being used. - * - * @class - * @memberof PIXI - */ -export default class TextureGCSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this.count = 0; - this.checkCount = 0; - this.maxIdle = settings.GC_MAX_IDLE; - this.checkCountMax = settings.GC_MAX_CHECK_COUNT; - this.mode = settings.GC_MODE; - } - - /** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ - postrender() - { - this.count++; - - if (this.mode === GC_MODES.MANUAL) - { - return; - } - - this.checkCount++; - - if (this.checkCount > this.checkCountMax) - { - this.checkCount = 0; - - this.run(); - } - } - - /** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ - run() - { - const tm = this.renderer.texture; - const managedTextures = tm.managedTextures; - let wasRemoved = false; - - for (let i = 0; i < managedTextures.length; i++) - { - const texture = managedTextures[i]; - - // only supports non generated textures at the moment! - if (!texture.frameBuffer && this.count - texture.touched > this.maxIdle) - { - tm.destroyTexture(texture, true); - managedTextures[i] = null; - wasRemoved = true; - } - } - - if (wasRemoved) - { - let j = 0; - - for (let i = 0; i < managedTextures.length; i++) - { - if (managedTextures[i] !== null) - { - managedTextures[j++] = managedTextures[i]; - } - } - - managedTextures.length = j; - } - } - - /** - * Removes all the textures within the specified displayObject and its children from the GPU - * - * @param {PIXI.DisplayObject} displayObject - the displayObject to remove the textures from. - */ - unload(displayObject) - { - const tm = this.renderer.textureSystem; - - // only destroy non generated textures - if (displayObject._texture && displayObject._texture._glRenderTargets) - { - tm.destroyTexture(displayObject._texture); - } - - for (let i = displayObject.children.length - 1; i >= 0; i--) - { - this.unload(displayObject.children[i]); - } - } -} diff --git a/packages/core/src/renderers/systems/textures/TextureSystem.js b/packages/core/src/renderers/systems/textures/TextureSystem.js deleted file mode 100644 index ccc5de9..0000000 --- a/packages/core/src/renderers/systems/textures/TextureSystem.js +++ /dev/null @@ -1,281 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; -import GLTexture from './GLTexture'; -import { removeItems } from '@pixi/utils'; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class TextureSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - // TODO set to max textures... - this.boundTextures = [ - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - ]; - - this.currentLocation = -1; - - this.managedTextures = []; - } - - /** - * Sets up the renderer context and necessary buffers. - * - * @private - */ - contextChange() - { - const gl = this.gl = this.renderer.gl; - - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - - // TODO move this.. to a nice make empty textures class.. - this.emptyTextures = {}; - - const emptyTexture2D = new GLTexture(gl.createTexture()); - - gl.bindTexture(gl.TEXTURE_2D, emptyTexture2D.texture); - gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array(4)); - - this.emptyTextures[gl.TEXTURE_2D] = emptyTexture2D; - this.emptyTextures[gl.TEXTURE_CUBE_MAP] = new GLTexture(gl.createTexture()); - - gl.bindTexture(gl.TEXTURE_CUBE_MAP, this.emptyTextures[gl.TEXTURE_CUBE_MAP].texture); - - let i; - - for (i = 0; i < 6; i++) - { - gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, null); - } - - gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_MAG_FILTER, gl.LINEAR); - gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_MIN_FILTER, gl.LINEAR); - - for (i = 0; i < this.boundTextures.length; i++) - { - this.bind(null, i); - } - } - - bind(texture, location) - { - const gl = this.gl; - - location = location || 0; - - if (this.currentLocation !== location) - { - this.currentLocation = location; - gl.activeTexture(gl.TEXTURE0 + location); - } - - if (texture) - { - texture = texture.baseTexture || texture; - - if (texture.valid) - { - texture.touched = this.renderer.textureGC.count; - - const glTexture = texture._glTextures[this.CONTEXT_UID] || this.initTexture(texture); - - gl.bindTexture(texture.target, glTexture.texture); - - if (glTexture.dirtyId !== texture.dirtyId) - { - this.updateTexture(texture); - } - - this.boundTextures[location] = texture; - } - } - else - { - gl.bindTexture(gl.TEXTURE_2D, this.emptyTextures[gl.TEXTURE_2D].texture); - this.boundTextures[location] = null; - } - } - - unbind(texture) - { - const gl = this.gl; - - for (let i = 0; i < this.boundTextures.length; i++) - { - if (this.boundTextures[i] === texture) - { - if (this.currentLocation !== i) - { - gl.activeTexture(gl.TEXTURE0 + i); - this.currentLocation = i; - } - - gl.bindTexture(gl.TEXTURE_2D, this.emptyTextures[texture.target].texture); - this.boundTextures[i] = null; - } - } - } - - initTexture(texture) - { - const glTexture = new GLTexture(this.gl.createTexture()); - - // guarentee an update.. - glTexture.dirtyId = -1; - - texture._glTextures[this.CONTEXT_UID] = glTexture; - - this.managedTextures.push(texture); - texture.on('dispose', this.destroyTexture, this); - - return glTexture; - } - - updateTexture(texture) - { - const glTexture = texture._glTextures[this.CONTEXT_UID]; - const renderer = this.renderer; - - if (texture.resource && texture.resource.upload(renderer, texture, glTexture)) - { - // texture is uploaded, dont do anything! - } - else - { - // default, renderTexture-like logic - const width = texture.realWidth; - const height = texture.realHeight; - const gl = renderer.gl; - - if (glTexture.width !== width - || glTexture.height !== height - || glTexture.dirtyId < 0) - { - glTexture.width = width; - glTexture.height = height; - - gl.texImage2D(texture.target, 0, - texture.format, - width, - height, - 0, - texture.format, - texture.type, - null); - } - } - - // lets only update what changes.. - if (texture.dirtyStyleId !== glTexture.dirtyStyleId) - { - this.updateTextureStyle(texture); - } - glTexture.dirtyId = texture.dirtyId; - } - - /** - * Deletes the texture from WebGL - * - * @param {PIXI.BaseTexture|PIXI.Texture} texture - the texture to destroy - * @param {boolean} [skipRemove=false] - Whether to skip removing the texture from the TextureManager. - */ - destroyTexture(texture, skipRemove) - { - const gl = this.gl; - - texture = texture.baseTexture || texture; - - if (texture._glTextures[this.renderer.CONTEXT_UID]) - { - this.unbind(texture); - - gl.deleteTexture(texture._glTextures[this.renderer.CONTEXT_UID].texture); - texture.off('dispose', this.destroyTexture, this); - - delete texture._glTextures[this.renderer.CONTEXT_UID]; - - if (!skipRemove) - { - const i = this.managedTextures.indexOf(texture); - - if (i !== -1) - { - removeItems(this.managedTextures, i, 1); - } - } - } - } - - updateTextureStyle(texture) - { - const glTexture = texture._glTextures[this.CONTEXT_UID]; - - glTexture.mipmap = texture.mipmap && texture.isPowerOfTwo; - if (!glTexture) - { - return; - } - - if (texture.resource && texture.resource.style(this.renderer, texture, glTexture)) - { - // style is set, dont do anything! - } - else - { - this.setStyle(texture, glTexture); - } - - glTexture.dirtyStyleId = texture.dirtyStyleId; - } - - setStyle(texture, glTexture) - { - const gl = this.gl; - - if (glTexture.mipmap) - { - gl.generateMipmap(texture.target); - } - - gl.texParameteri(texture.target, gl.TEXTURE_WRAP_S, texture.wrapMode); - gl.texParameteri(texture.target, gl.TEXTURE_WRAP_T, texture.wrapMode); - - if (glTexture.mipmap) - { - /* eslint-disable max-len */ - gl.texParameteri(texture.target, gl.TEXTURE_MIN_FILTER, texture.scaleMode ? gl.LINEAR_MIPMAP_LINEAR : gl.NEAREST_MIPMAP_NEAREST); - /* eslint-disable max-len */ - } - else - { - gl.texParameteri(texture.target, gl.TEXTURE_MIN_FILTER, texture.scaleMode ? gl.LINEAR : gl.NEAREST); - } - - gl.texParameteri(texture.target, gl.TEXTURE_MAG_FILTER, texture.scaleMode ? gl.LINEAR : gl.NEAREST); - } -} diff --git a/packages/core/src/renderers/utils/ObjectRenderer.js b/packages/core/src/renderers/utils/ObjectRenderer.js deleted file mode 100644 index f67a273..0000000 --- a/packages/core/src/renderers/utils/ObjectRenderer.js +++ /dev/null @@ -1,48 +0,0 @@ -import WebGLSystem from '../systems/WebGLSystem'; - -/** - * Base for a common object renderer that can be used as a system renderer plugin. - * - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class ObjectRenderer extends WebGLSystem -{ - /** - * Starts the renderer and sets the shader - * - */ - start() - { - // set the shader.. - } - - /** - * Stops the renderer - * - */ - stop() - { - this.flush(); - } - - /** - * Stub method for rendering content and emptying the current batch. - * - */ - flush() - { - // flush! - } - - /** - * Renders an object - * - * @param {PIXI.DisplayObject} object - The object to render. - */ - render(object) // eslint-disable-line no-unused-vars - { - // render the object - } -} diff --git a/packages/core/src/renderers/utils/Quad.js b/packages/core/src/renderers/utils/Quad.js deleted file mode 100644 index 5c1517b..0000000 --- a/packages/core/src/renderers/utils/Quad.js +++ /dev/null @@ -1,26 +0,0 @@ -import Geometry from '../../geometry/Geometry'; - -/** - * Helper class to create a quad - * - * @class - * @memberof PIXI - */ -export default class Quad extends Geometry -{ - /** - * just a quad - */ - constructor() - { - super(); - - this.addAttribute('aVertexPosition', [ - 0, 0, - 1, 0, - 1, 1, - 0, 1, - ]) - .addIndex([0, 1, 3, 2]); - } -} diff --git a/packages/core/src/renderers/utils/QuadUv.js b/packages/core/src/renderers/utils/QuadUv.js deleted file mode 100644 index 97bda89..0000000 --- a/packages/core/src/renderers/utils/QuadUv.js +++ /dev/null @@ -1,103 +0,0 @@ -import Geometry from '../../geometry/Geometry'; -import Buffer from '../../geometry/Buffer'; - -/** - * Helper class to create a quad with uvs like in v4 - * - * @class - * @memberof PIXI - */ -export default class QuadUv extends Geometry -{ - constructor() - { - super(); - - /** - * An array of vertices - * - * @member {Float32Array} - */ - this.vertices = new Float32Array([ - -1, -1, - 1, -1, - 1, 1, - -1, 1, - ]); - - /** - * The Uvs of the quad - * - * @member {Float32Array} - */ - this.uvs = new Float32Array([ - 0, 0, - 1, 0, - 1, 1, - 0, 1, - ]); - - this.vertexBuffer = new Buffer(this.vertices); - this.uvBuffer = new Buffer(this.uvs); - - this.addAttribute('aVertexPosition', this.vertexBuffer) - .addAttribute('aTextureCoord', this.uvBuffer) - .addIndex([0, 1, 2, 0, 2, 3]); - } - - /** - * Maps two Rectangle to the quad. - * - * @param {PIXI.Rectangle} targetTextureFrame - the first rectangle - * @param {PIXI.Rectangle} destinationFrame - the second rectangle - * @return {PIXI.Quad} Returns itself. - */ - map(targetTextureFrame, destinationFrame) - { - let x = 0; // destinationFrame.x / targetTextureFrame.width; - let y = 0; // destinationFrame.y / targetTextureFrame.height; - - this.uvs[0] = x; - this.uvs[1] = y; - - this.uvs[2] = x + (destinationFrame.width / targetTextureFrame.width); - this.uvs[3] = y; - - this.uvs[4] = x + (destinationFrame.width / targetTextureFrame.width); - this.uvs[5] = y + (destinationFrame.height / targetTextureFrame.height); - - this.uvs[6] = x; - this.uvs[7] = y + (destinationFrame.height / targetTextureFrame.height); - - x = destinationFrame.x; - y = destinationFrame.y; - - this.vertices[0] = x; - this.vertices[1] = y; - - this.vertices[2] = x + destinationFrame.width; - this.vertices[3] = y; - - this.vertices[4] = x + destinationFrame.width; - this.vertices[5] = y + destinationFrame.height; - - this.vertices[6] = x; - this.vertices[7] = y + destinationFrame.height; - - this.invalidate(); - - return this; - } - - /** - * legacy upload method, just marks buffers dirty - * @returns {PIXI.QuadUv} Returns itself. - */ - invalidate() - { - this.vertexBuffer._updateID++; - this.uvBuffer._updateID++; - - return this; - } -} diff --git a/packages/core/src/renderers/utils/checkMaxIfStatmentsInShader.js b/packages/core/src/renderers/utils/checkMaxIfStatmentsInShader.js deleted file mode 100644 index 8047c96..0000000 --- a/packages/core/src/renderers/utils/checkMaxIfStatmentsInShader.js +++ /dev/null @@ -1,58 +0,0 @@ -const fragTemplate = [ - 'precision mediump float;', - 'void main(void){', - 'float test = 0.1;', - '%forloop%', - 'gl_FragColor = vec4(0.0);', - '}', -].join('\n'); - -export default function checkMaxIfStatmentsInShader(maxIfs, gl) -{ - if (maxIfs === 0) - { - throw new Error('Invalid value of `0` passed to `checkMaxIfStatementsInShader`'); - } - - const shader = gl.createShader(gl.FRAGMENT_SHADER); - - while (true) // eslint-disable-line no-constant-condition - { - const fragmentSrc = fragTemplate.replace(/%forloop%/gi, generateIfTestSrc(maxIfs)); - - gl.shaderSource(shader, fragmentSrc); - gl.compileShader(shader); - - if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) - { - maxIfs = (maxIfs / 2) | 0; - } - else - { - // valid! - break; - } - } - - return maxIfs; -} - -function generateIfTestSrc(maxIfs) -{ - let src = ''; - - for (let i = 0; i < maxIfs; ++i) - { - if (i > 0) - { - src += '\nelse '; - } - - if (i < maxIfs - 1) - { - src += `if(test == ${i}.0){}`; - } - } - - return src; -} diff --git a/packages/core/src/renderers/utils/mapWebGLBlendModesToPixi.js b/packages/core/src/renderers/utils/mapWebGLBlendModesToPixi.js deleted file mode 100644 index 10ffe4d..0000000 --- a/packages/core/src/renderers/utils/mapWebGLBlendModesToPixi.js +++ /dev/null @@ -1,47 +0,0 @@ -import { BLEND_MODES } from '@pixi/constants'; - -/** - * Maps gl blend combinations to WebGL. - * - * @memberof PIXI - * @function mapWebGLBlendModesToPixi - * @private - * @param {WebGLRenderingContext} gl - The rendering context. - * @param {string[]} [array=[]] - The array to output into. - * @return {string[]} Mapped modes. - */ -export default function mapWebGLBlendModesToPixi(gl, array = []) -{ - // TODO - premultiply alpha would be different. - // add a boolean for that! - array[BLEND_MODES.NORMAL] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.ADD] = [gl.ONE, gl.DST_ALPHA]; - array[BLEND_MODES.MULTIPLY] = [gl.DST_COLOR, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.SCREEN] = [gl.ONE, gl.ONE_MINUS_SRC_COLOR]; - array[BLEND_MODES.OVERLAY] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.DARKEN] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.LIGHTEN] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.COLOR_DODGE] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.COLOR_BURN] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.HARD_LIGHT] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.SOFT_LIGHT] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.DIFFERENCE] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.EXCLUSION] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.HUE] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.SATURATION] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.COLOR] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.LUMINOSITY] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.NONE] = [0, 0]; - - // not-premultiplied blend modes - array[BLEND_MODES.NORMAL_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.ADD_NPM] = [gl.SRC_ALPHA, gl.DST_ALPHA, gl.ONE, gl.DST_ALPHA]; - array[BLEND_MODES.SCREEN_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_COLOR, gl.ONE, gl.ONE_MINUS_SRC_COLOR]; - - // not-premultiplied blend modes - array[BLEND_MODES.NORMAL_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.ADD_NPM] = [gl.SRC_ALPHA, gl.DST_ALPHA, gl.ONE, gl.DST_ALPHA]; - array[BLEND_MODES.SCREEN_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_COLOR, gl.ONE, gl.ONE_MINUS_SRC_COLOR]; - - return array; -} diff --git a/packages/core/src/shader/GLProgram.js b/packages/core/src/shader/GLProgram.js new file mode 100644 index 0000000..92980ca --- /dev/null +++ b/packages/core/src/shader/GLProgram.js @@ -0,0 +1,43 @@ +/** + * Helper class to create a webGL Program + * + * @class + * @private + */ +export default class GLProgram +{ + constructor(program, uniformData) + { + /** + * The shader program + * + * @member {WebGLProgram} + */ + this.program = program; + + /** + * holds the uniform data which contains uniform locations + * and current uniform values used for caching and preventing undeed GPU commands + * @type {Object} + */ + this.uniformData = uniformData; + + /** + * uniformGroups holds the various upload functions for the shader. Each uniform group + * and program have a unique upload function generated. + * @type {Object} + */ + this.uniformGroups = {}; + } + + /** + * Destroys this program + * TODO + */ + destroy() + { + this.uniformData = null; + this.uniformGroups = null; + this.program = null; + } +} diff --git a/packages/core/src/shader/Program.js b/packages/core/src/shader/Program.js index 4f9971e..4b4007a 100644 --- a/packages/core/src/shader/Program.js +++ b/packages/core/src/shader/Program.js @@ -1,6 +1,11 @@ -import * as shaderUtils from '../renderers/systems/shader/shader'; +// import * as from '../systems/shader/shader'; +import { setPrecision, + defaultValue, + compileProgram, + mapSize, + mapType, + getTestContext } from './utils'; import { ProgramCache } from '@pixi/utils'; -import getTestContext from './getTestContext'; import defaultFragment from './defaultProgram.frag'; import defaultVertex from './defaultProgram.vert'; import { settings } from '@pixi/settings'; @@ -34,8 +39,8 @@ */ this.fragmentSrc = fragmentSrc || Program.defaultFragmentSrc; - this.vertexSrc = shaderUtils.setPrecision(this.vertexSrc, settings.PRECISION_VERTEX); - this.fragmentSrc = shaderUtils.setPrecision(this.fragmentSrc, settings.PRECISION_FRAGMENT); + this.vertexSrc = setPrecision(this.vertexSrc, settings.PRECISION_VERTEX); + this.fragmentSrc = setPrecision(this.fragmentSrc, settings.PRECISION_FRAGMENT); // currently this does not extract structs only default types this.extractData(this.vertexSrc, this.fragmentSrc); @@ -62,7 +67,7 @@ if (gl) { - const program = shaderUtils.compileProgram(gl, vertexSrc, fragmentSrc); + const program = compileProgram(gl, vertexSrc, fragmentSrc); this.attributeData = this.getAttributeData(program, gl); this.uniformData = this.getUniformData(program, gl); @@ -95,13 +100,13 @@ for (let i = 0; i < totalAttributes; i++) { const attribData = gl.getActiveAttrib(program, i); - const type = shaderUtils.mapType(gl, attribData.type); + const type = mapType(gl, attribData.type); /*eslint-disable */ const data = { type: type, name: attribData.name, - size: shaderUtils.mapSize(type), + size: mapSize(type), location: 0, }; /* eslint-enable */ @@ -145,14 +150,14 @@ const name = uniformData.name.replace(/\[.*?\]/, ''); const isArray = uniformData.name.match(/\[.*?\]/, ''); - const type = shaderUtils.mapType(gl, uniformData.type); + const type = mapType(gl, uniformData.type); /*eslint-disable */ uniforms[name] = { type: type, size: uniformData.size, isArray:isArray, - value: shaderUtils.defaultValue(type, uniformData.size), + value: defaultValue(type, uniformData.size), }; /* eslint-enable */ } diff --git a/packages/core/src/shader/ShaderSystem.js b/packages/core/src/shader/ShaderSystem.js new file mode 100644 index 0000000..40b8af8 --- /dev/null +++ b/packages/core/src/shader/ShaderSystem.js @@ -0,0 +1,171 @@ +import System from '../System'; +import GLProgram from './GLProgram'; +import { generateUniformsSync, + defaultValue, + compileProgram } from './utils'; + +let UID = 0; + +/** + * Helper class to create a webGL Texture + * + * @class + * @memberof PIXI.systems + * @extends PIXI.System + */ +export default class ShaderSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - A reference to the current renderer + */ + constructor(renderer) + { + super(renderer); + + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = null; + + this.shader = null; + this.program = null; + + this.id = UID++; + } + + contextChange(gl) + { + this.gl = gl; + } + + /** + * Changes the current shader to the one given in parameter + * + * @param {PIXI.Shader} shader - the new shader + * @param {boolean} dontSync - false if the shader should automatically sync its uniforms. + * @returns {PIXI.glCore.glProgram} the glProgram that belongs to the shader. + */ + bind(shader, dontSync) + { + shader.uniforms.globals = this.renderer.globalUniforms; + + const program = shader.program; + const glProgram = program.glPrograms[this.renderer.CONTEXT_UID] || this.generateShader(shader); + + this.shader = shader; + + // TODO - some current pixi plugins bypass this.. so it not safe to use yet.. + if (this.program !== program) + { + this.program = program; + this.gl.useProgram(glProgram.program); + } + + if (!dontSync) + { + this.syncUniformGroup(shader.uniformGroup); + } + + return glProgram; + } + + /** + * Uploads the uniforms values to the currently bound shader. + * + * @param {object} uniforms - the uniforms valiues that be applied to the current shader + */ + setUniforms(uniforms) + { + const shader = this.shader.program; + const glProgram = shader.glPrograms[this.renderer.CONTEXT_UID]; + + shader.syncUniforms(glProgram.uniformData, uniforms, this.renderer); + } + + syncUniformGroup(group) + { + const glProgram = this.getglProgram(); + + if (!group.static || group.dirtyId !== glProgram.uniformGroups[group.id]) + { + glProgram.uniformGroups[group.id] = group.dirtyId; + const syncFunc = group.syncUniforms[this.shader.program.id] || this.createSyncGroups(group); + + syncFunc(glProgram.uniformData, group.uniforms, this.renderer); + } + } + + createSyncGroups(group) + { + group.syncUniforms[this.shader.program.id] = generateUniformsSync(group, this.shader.program.uniformData); + + return group.syncUniforms[this.shader.program.id]; + } + + /** + * Returns the underlying GLShade rof the currently bound shader. + * This can be handy for when you to have a little more control over the setting of your uniforms. + * + * @return {PIXI.glCore.Shader} the glProgram for the currently bound Shader for this context + */ + getglProgram() + { + if (this.shader) + { + return this.shader.program.glPrograms[this.renderer.CONTEXT_UID]; + } + + return null; + } + + /** + * Generates a glProgram verion of the Shader provided. + * + * @private + * @param {PIXI.Shader} shader the shader that the glProgram will be based on. + * @return {PIXI.glCore.glProgram} A shiney new glProgram + */ + generateShader(shader) + { + const gl = this.gl; + + const program = shader.program; + + const attribMap = {}; + + for (const i in program.attributeData) + { + attribMap[i] = program.attributeData[i].location; + } + + const shaderProgram = compileProgram(gl, program.vertexSrc, program.fragmentSrc, attribMap); + const uniformData = {}; + + for (const i in program.uniformData) + { + const data = program.uniformData[i]; + + uniformData[i] = { + location: gl.getUniformLocation(shaderProgram, i), + value: defaultValue(data.type, data.size), + }; + } + + const glProgram = new GLProgram(shaderProgram, uniformData); + + program.glPrograms[this.renderer.CONTEXT_UID] = glProgram; + + return glProgram; + } + + /** + * Destroys this System and removes all its textures + */ + destroy() + { + // TODO implement destroy method for ShaderSystem + this.destroyed = true; + } +} diff --git a/packages/core/src/shader/defaultValue.js b/packages/core/src/shader/defaultValue.js deleted file mode 100644 index 847a069..0000000 --- a/packages/core/src/shader/defaultValue.js +++ /dev/null @@ -1,78 +0,0 @@ -function booleanArray(size) -{ - const array = new Array(size); - - for (let i = 0; i < array.length; i++) - { - array[i] = false; - } - - return array; -} - -/** - * @class - * @memberof PIXI.glCore.shader - * @param type {String} Type of value - * @param size {Number} - */ -export default function defaultValue(type, size) -{ - switch (type) - { - case 'float': - return 0; - - case 'vec2': - return new Float32Array(2 * size); - - case 'vec3': - return new Float32Array(3 * size); - - case 'vec4': - return new Float32Array(4 * size); - - case 'int': - case 'sampler2D': - return 0; - - case 'ivec2': - return new Int32Array(2 * size); - - case 'ivec3': - return new Int32Array(3 * size); - - case 'ivec4': - return new Int32Array(4 * size); - - case 'bool': - return false; - - case 'bvec2': - - return booleanArray(2 * size); - - case 'bvec3': - return booleanArray(3 * size); - - case 'bvec4': - return booleanArray(4 * size); - - case 'mat2': - return new Float32Array([1, 0, - 0, 1]); - - case 'mat3': - return new Float32Array([1, 0, 0, - 0, 1, 0, - 0, 0, 1]); - - case 'mat4': - return new Float32Array([1, 0, 0, 0, - 0, 1, 0, 0, - 0, 0, 1, 0, - 0, 0, 0, 1]); - } - - return 0; -} diff --git a/packages/core/src/shader/extractUniformsFromSrc.js b/packages/core/src/shader/extractUniformsFromSrc.js deleted file mode 100644 index 3d02c0a..0000000 --- a/packages/core/src/shader/extractUniformsFromSrc.js +++ /dev/null @@ -1,57 +0,0 @@ -import defaultValue from './defaultValue'; - -export default function extractUniformsFromSrc(vertexSrc, fragmentSrc, mask) -{ - const vertUniforms = extractUniformsFromString(vertexSrc, mask); - const fragUniforms = extractUniformsFromString(fragmentSrc, mask); - - return Object.assign(vertUniforms, fragUniforms); -} - -function extractUniformsFromString(string) -{ - const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); - - const uniforms = {}; - let nameSplit; - - // clean the lines a little - remove extra spaces / teabs etc - // then split along ';' - const lines = string.replace(/\s+/g, ' ') - .split(/\s*;\s*/); - - // loop through.. - for (let i = 0; i < lines.length; i++) - { - const line = lines[i].trim(); - - if (line.indexOf('uniform') > -1) - { - const splitLine = line.split(' '); - const type = splitLine[1]; - - let name = splitLine[2]; - let size = 1; - - if (name.indexOf('[') > -1) - { - // array! - nameSplit = name.split(/\[|]/); - name = nameSplit[0]; - size *= Number(nameSplit[1]); - } - - if (!name.match(maskRegex)) - { - uniforms[name] = { - value: defaultValue(type, size), - dirtyId: 0, - name, - type, - }; - } - } - } - - return uniforms; -} diff --git a/packages/core/src/shader/generateUniformsSync.js b/packages/core/src/shader/generateUniformsSync.js deleted file mode 100644 index 98a357f..0000000 --- a/packages/core/src/shader/generateUniformsSync.js +++ /dev/null @@ -1,231 +0,0 @@ -// cv = CachedValue -// v = value -// ud = uniformData -// uv = uniformValue -// l = loaction -const GLSL_TO_SINGLE_SETTERS_CACHED = { - - float: ` - if(cv !== v) - { - cv.v = v; - gl.uniform1f(location, v) - }`, - - vec2: ` - if(cv[0] !== v[0] || cv[1] !== v[1]) - { - cv[0] = v[0]; - cv[1] = v[1]; - gl.uniform2f(location, v[0], v[1]) - }`, - - vec3: ` - if(cv[0] !== v[0] || cv[1] !== v[1] || cv[2] !== v[2]) - { - cv[0] = v[0]; - cv[1] = v[1]; - cv[2] = v[2]; - - gl.uniform3f(location, v[0], v[1], v[2]) - }`, - - vec4: 'gl.uniform4f(location, v[0], v[1], v[2], v[3])', - - int: 'gl.uniform1i(location, v)', - ivec2: 'gl.uniform2i(location, v[0], v[1])', - ivec3: 'gl.uniform3i(location, v[0], v[1], v[2])', - ivec4: 'gl.uniform4i(location, v[0], v[1], v[2], v[3])', - - bool: 'gl.uniform1i(location, v)', - bvec2: 'gl.uniform2i(location, v[0], v[1])', - bvec3: 'gl.uniform3i(location, v[0], v[1], v[2])', - bvec4: 'gl.uniform4i(location, v[0], v[1], v[2], v[3])', - - mat2: 'gl.uniformMatrix2fv(location, false, v)', - mat3: 'gl.uniformMatrix3fv(location, false, v)', - mat4: 'gl.uniformMatrix4fv(location, false, v)', - - sampler2D: 'gl.uniform1i(location, v)', - samplerCube: 'gl.uniform1i(location, v)', - sampler2DArray: 'gl.uniform1i(location, v)', -}; - -const GLSL_TO_ARRAY_SETTERS = { - - float: `gl.uniform1fv(location, v)`, - - vec2: `gl.uniform2fv(location, v)`, - vec3: `gl.uniform3fv(location, v)`, - vec4: 'gl.uniform4fv(location, v)', - - mat4: 'gl.uniformMatrix4fv(location, false, v)', - mat3: 'gl.uniformMatrix3fv(location, false, v)', - mat2: 'gl.uniformMatrix2fv(location, false, v)', - - int: 'gl.uniform1iv(location, v)', - ivec2: 'gl.uniform2iv(location, v)', - ivec3: 'gl.uniform3iv(location, v)', - ivec4: 'gl.uniform4iv(location, v)', - - bool: 'gl.uniform1iv(location, v)', - bvec2: 'gl.uniform2iv(location, v)', - bvec3: 'gl.uniform3iv(location, v)', - bvec4: 'gl.uniform4iv(location, v)', - - sampler2D: 'gl.uniform1iv(location, v)', - samplerCube: 'gl.uniform1iv(location, v)', - sampler2DArray: 'gl.uniform1iv(location, v)', -}; - -export default function generateUniformsSync(group, uniformData) -{ - let textureCount = 0; - let func = `var v = null; - var cv = null - var gl = renderer.gl`; - - for (const i in group.uniforms) - { - const data = uniformData[i]; - - if (!data) - { - if (group.uniforms[i].group) - { - func += ` - renderer.shader.syncUniformGroup(uv.${i}); - `; - } - - continue; - } - - // TODO && uniformData[i].value !== 0 <-- do we still need this? - if (data.type === 'float' && data.size === 1) - { - func += ` - if(uv.${i} !== ud.${i}.value) - { - ud.${i}.value = uv.${i} - gl.uniform1f(ud.${i}.location, uv.${i}) - }\n`; - } - /* eslint-disable max-len */ - else if ((data.type === 'sampler2D' || data.type === 'samplerCube' || data.type === 'sampler2DArray') && data.size === 1 && !data.isArray) - /* eslint-disable max-len */ - { - func += ` - renderer.texture.bind(uv.${i}, ${textureCount}); - - if(ud.${i}.value !== ${textureCount}) - { - ud.${i}.value = ${textureCount}; - gl.uniform1i(ud.${i}.location, ${textureCount});\n; // eslint-disable-line max-len - }\n`; - - textureCount++; - } - else if (data.type === 'mat3' && data.size === 1) - { - if (group.uniforms[i].a !== undefined) - { - // TODO and some smart caching dirty ids here! - func += ` - gl.uniformMatrix3fv(ud.${i}.location, false, uv.${i}.toArray(true)); - \n`; - } - else - { - func += ` - gl.uniformMatrix3fv(ud.${i}.location, false, uv.${i}); - \n`; - } - } - else if (data.type === 'vec2' && data.size === 1) - { - // TODO - do we need both here? - // maybe we can get away with only using points? - if (group.uniforms[i].x !== undefined) - { - func += ` - cv = ud.${i}.value; - v = uv.${i}; - - if(cv[0] !== v.x || cv[1] !== v.y) - { - cv[0] = v.x; - cv[1] = v.y; - gl.uniform2f(ud.${i}.location, v.x, v.y); - }\n`; - } - else - { - func += ` - cv = ud.${i}.value; - v = uv.${i}; - - if(cv[0] !== v[0] || cv[1] !== v[1]) - { - cv[0] = v[0]; - cv[1] = v[1]; - gl.uniform2f(ud.${i}.location, v[0], v[1]); - } - \n`; - } - } - else if (data.type === 'vec4' && data.size === 1) - { - // TODO - do we need both here? - // maybe we can get away with only using points? - if (group.uniforms[i].width !== undefined) - { - func += ` - cv = ud.${i}.value; - v = uv.${i}; - - if(cv[0] !== v.x || cv[1] !== v.y || cv[2] !== v.width || cv[3] !== v.height) - { - cv[0] = v.x; - cv[1] = v.y; - cv[2] = v.width; - cv[3] = v.height; - gl.uniform4f(ud.${i}.location, v.x, v.y, v.width, v.height) - }\n`; - } - else - { - func += ` - cv = ud.${i}.value; - v = uv.${i}; - - if(cv[0] !== v[0] || cv[1] !== v[1] || cv[2] !== v[2] || cv[3] !== v[3]) - { - cv[0] = v[0]; - cv[1] = v[1]; - cv[2] = v[2]; - cv[3] = v[3]; - - gl.uniform4f(ud.${i}.location, v[0], v[1], v[2], v[3]) - } - \n`; - } - } - else - { - const templateType = (data.size === 1) ? GLSL_TO_SINGLE_SETTERS_CACHED : GLSL_TO_ARRAY_SETTERS; - - const template = templateType[data.type].replace('location', `ud.${i}.location`); - - func += ` - cv = ud.${i}.value; - v = uv.${i}; - ${template};\n`; - } - } - - // console.log(' --------------- ') - // console.log(func); - - return new Function('ud', 'uv', 'renderer', func); // eslint-disable-line no-new-func -} diff --git a/packages/core/src/shader/getTestContext.js b/packages/core/src/shader/getTestContext.js deleted file mode 100644 index ea174cc..0000000 --- a/packages/core/src/shader/getTestContext.js +++ /dev/null @@ -1,48 +0,0 @@ -import { settings } from '@pixi/settings'; - -let context = null; - -/** - * returns a little webGL context to use for program inspection. - * - * @static - * @private - * @returns {webGL-context} a gl context to test with - */ -export default function getTestContext() -{ - if (!context) - { - const canvas = document.createElement('canvas'); - - let gl; - - if (settings.PREFER_WEBGL_2) - { - gl = canvas.getContext('webgl2', {}); - } - - if (!gl) - { - gl = canvas.getContext('webgl', {}) - || canvas.getContext('experimental-webgl', {}); - - if (!gl) - { - // fail, not able to get a context - throw new Error('This browser does not support webGL. Try using the canvas renderer'); - } - else - { - // for shader testing.. - gl.getExtension('WEBGL_draw_buffers'); - } - } - - context = gl; - - return gl; - } - - return context; -} diff --git a/packages/core/src/shader/utils/checkMaxIfStatmentsInShader.js b/packages/core/src/shader/utils/checkMaxIfStatmentsInShader.js new file mode 100644 index 0000000..8047c96 --- /dev/null +++ b/packages/core/src/shader/utils/checkMaxIfStatmentsInShader.js @@ -0,0 +1,58 @@ +const fragTemplate = [ + 'precision mediump float;', + 'void main(void){', + 'float test = 0.1;', + '%forloop%', + 'gl_FragColor = vec4(0.0);', + '}', +].join('\n'); + +export default function checkMaxIfStatmentsInShader(maxIfs, gl) +{ + if (maxIfs === 0) + { + throw new Error('Invalid value of `0` passed to `checkMaxIfStatementsInShader`'); + } + + const shader = gl.createShader(gl.FRAGMENT_SHADER); + + while (true) // eslint-disable-line no-constant-condition + { + const fragmentSrc = fragTemplate.replace(/%forloop%/gi, generateIfTestSrc(maxIfs)); + + gl.shaderSource(shader, fragmentSrc); + gl.compileShader(shader); + + if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) + { + maxIfs = (maxIfs / 2) | 0; + } + else + { + // valid! + break; + } + } + + return maxIfs; +} + +function generateIfTestSrc(maxIfs) +{ + let src = ''; + + for (let i = 0; i < maxIfs; ++i) + { + if (i > 0) + { + src += '\nelse '; + } + + if (i < maxIfs - 1) + { + src += `if(test == ${i}.0){}`; + } + } + + return src; +} diff --git a/packages/core/src/shader/utils/compileProgram.js b/packages/core/src/shader/utils/compileProgram.js new file mode 100644 index 0000000..8c6ccb9 --- /dev/null +++ b/packages/core/src/shader/utils/compileProgram.js @@ -0,0 +1,78 @@ +/** + * @method compileProgram + * @memberof PIXI.glCore.shader + * @param gl {WebGLRenderingContext} The current WebGL context {WebGLProgram} + * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. + * @param fragmentSrc {string|string[]} The fragment shader source as an array of strings. + * @param attributeLocations {Object} An attribute location map that lets you manually set the attribute locations + * @return {WebGLProgram} the shader program + */ +export default function compileProgram(gl, vertexSrc, fragmentSrc, attributeLocations) +{ + const glVertShader = compileShader(gl, gl.VERTEX_SHADER, vertexSrc); + const glFragShader = compileShader(gl, gl.FRAGMENT_SHADER, fragmentSrc); + + let program = gl.createProgram(); + + gl.attachShader(program, glVertShader); + gl.attachShader(program, glFragShader); + + // optionally, set the attributes manually for the program rather than letting WebGL decide.. + if (attributeLocations) + { + for (const i in attributeLocations) + { + gl.bindAttribLocation(program, attributeLocations[i], i); + } + } + + gl.linkProgram(program); + + // if linking fails, then log and cleanup + if (!gl.getProgramParameter(program, gl.LINK_STATUS)) + { + console.error('Pixi.js Error: Could not initialize shader.'); + console.error('gl.VALIDATE_STATUS', gl.getProgramParameter(program, gl.VALIDATE_STATUS)); + console.error('gl.getError()', gl.getError()); + + // if there is a program info log, log it + if (gl.getProgramInfoLog(program) !== '') + { + console.warn('Pixi.js Warning: gl.getProgramInfoLog()', gl.getProgramInfoLog(program)); + } + + gl.deleteProgram(program); + program = null; + } + + // clean up some shaders + gl.deleteShader(glVertShader); + gl.deleteShader(glFragShader); + + return program; +} + +/** + * @private + * @param gl {WebGLRenderingContext} The current WebGL context {WebGLProgram} + * @param type {Number} the type, can be either VERTEX_SHADER or FRAGMENT_SHADER + * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. + * @return {WebGLShader} the shader + */ +function compileShader(gl, type, src) +{ + const shader = gl.createShader(type); + + gl.shaderSource(shader, src); + gl.compileShader(shader); + + if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) + { + console.warn(src); + console.error(gl.getShaderInfoLog(shader)); + + return null; + } + + return shader; +} diff --git a/packages/core/src/shader/utils/defaultValue.js b/packages/core/src/shader/utils/defaultValue.js new file mode 100644 index 0000000..97290bd --- /dev/null +++ b/packages/core/src/shader/utils/defaultValue.js @@ -0,0 +1,79 @@ +/** + * @method defaultValue + * @memberof PIXI.glCore.shader + * @param type {String} Type of value + * @param size {Number} + */ +export default function defaultValue(type, size) +{ + switch (type) + { + case 'float': + return 0; + + case 'vec2': + return new Float32Array(2 * size); + + case 'vec3': + return new Float32Array(3 * size); + + case 'vec4': + return new Float32Array(4 * size); + + case 'int': + case 'sampler2D': + case 'sampler2DArray': + return 0; + + case 'ivec2': + return new Int32Array(2 * size); + + case 'ivec3': + return new Int32Array(3 * size); + + case 'ivec4': + return new Int32Array(4 * size); + + case 'bool': + return false; + + case 'bvec2': + + return booleanArray(2 * size); + + case 'bvec3': + return booleanArray(3 * size); + + case 'bvec4': + return booleanArray(4 * size); + + case 'mat2': + return new Float32Array([1, 0, + 0, 1]); + + case 'mat3': + return new Float32Array([1, 0, 0, + 0, 1, 0, + 0, 0, 1]); + + case 'mat4': + return new Float32Array([1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1]); + } + + return null; +} + +function booleanArray(size) +{ + const array = new Array(size); + + for (let i = 0; i < array.length; i++) + { + array[i] = false; + } + + return array; +} diff --git a/packages/core/src/shader/utils/extractUniformsFromSrc.js b/packages/core/src/shader/utils/extractUniformsFromSrc.js new file mode 100644 index 0000000..3d02c0a --- /dev/null +++ b/packages/core/src/shader/utils/extractUniformsFromSrc.js @@ -0,0 +1,57 @@ +import defaultValue from './defaultValue'; + +export default function extractUniformsFromSrc(vertexSrc, fragmentSrc, mask) +{ + const vertUniforms = extractUniformsFromString(vertexSrc, mask); + const fragUniforms = extractUniformsFromString(fragmentSrc, mask); + + return Object.assign(vertUniforms, fragUniforms); +} + +function extractUniformsFromString(string) +{ + const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); + + const uniforms = {}; + let nameSplit; + + // clean the lines a little - remove extra spaces / teabs etc + // then split along ';' + const lines = string.replace(/\s+/g, ' ') + .split(/\s*;\s*/); + + // loop through.. + for (let i = 0; i < lines.length; i++) + { + const line = lines[i].trim(); + + if (line.indexOf('uniform') > -1) + { + const splitLine = line.split(' '); + const type = splitLine[1]; + + let name = splitLine[2]; + let size = 1; + + if (name.indexOf('[') > -1) + { + // array! + nameSplit = name.split(/\[|]/); + name = nameSplit[0]; + size *= Number(nameSplit[1]); + } + + if (!name.match(maskRegex)) + { + uniforms[name] = { + value: defaultValue(type, size), + dirtyId: 0, + name, + type, + }; + } + } + } + + return uniforms; +} diff --git a/packages/core/src/shader/utils/generateUniformsSync.js b/packages/core/src/shader/utils/generateUniformsSync.js new file mode 100644 index 0000000..98a357f --- /dev/null +++ b/packages/core/src/shader/utils/generateUniformsSync.js @@ -0,0 +1,231 @@ +// cv = CachedValue +// v = value +// ud = uniformData +// uv = uniformValue +// l = loaction +const GLSL_TO_SINGLE_SETTERS_CACHED = { + + float: ` + if(cv !== v) + { + cv.v = v; + gl.uniform1f(location, v) + }`, + + vec2: ` + if(cv[0] !== v[0] || cv[1] !== v[1]) + { + cv[0] = v[0]; + cv[1] = v[1]; + gl.uniform2f(location, v[0], v[1]) + }`, + + vec3: ` + if(cv[0] !== v[0] || cv[1] !== v[1] || cv[2] !== v[2]) + { + cv[0] = v[0]; + cv[1] = v[1]; + cv[2] = v[2]; + + gl.uniform3f(location, v[0], v[1], v[2]) + }`, + + vec4: 'gl.uniform4f(location, v[0], v[1], v[2], v[3])', + + int: 'gl.uniform1i(location, v)', + ivec2: 'gl.uniform2i(location, v[0], v[1])', + ivec3: 'gl.uniform3i(location, v[0], v[1], v[2])', + ivec4: 'gl.uniform4i(location, v[0], v[1], v[2], v[3])', + + bool: 'gl.uniform1i(location, v)', + bvec2: 'gl.uniform2i(location, v[0], v[1])', + bvec3: 'gl.uniform3i(location, v[0], v[1], v[2])', + bvec4: 'gl.uniform4i(location, v[0], v[1], v[2], v[3])', + + mat2: 'gl.uniformMatrix2fv(location, false, v)', + mat3: 'gl.uniformMatrix3fv(location, false, v)', + mat4: 'gl.uniformMatrix4fv(location, false, v)', + + sampler2D: 'gl.uniform1i(location, v)', + samplerCube: 'gl.uniform1i(location, v)', + sampler2DArray: 'gl.uniform1i(location, v)', +}; + +const GLSL_TO_ARRAY_SETTERS = { + + float: `gl.uniform1fv(location, v)`, + + vec2: `gl.uniform2fv(location, v)`, + vec3: `gl.uniform3fv(location, v)`, + vec4: 'gl.uniform4fv(location, v)', + + mat4: 'gl.uniformMatrix4fv(location, false, v)', + mat3: 'gl.uniformMatrix3fv(location, false, v)', + mat2: 'gl.uniformMatrix2fv(location, false, v)', + + int: 'gl.uniform1iv(location, v)', + ivec2: 'gl.uniform2iv(location, v)', + ivec3: 'gl.uniform3iv(location, v)', + ivec4: 'gl.uniform4iv(location, v)', + + bool: 'gl.uniform1iv(location, v)', + bvec2: 'gl.uniform2iv(location, v)', + bvec3: 'gl.uniform3iv(location, v)', + bvec4: 'gl.uniform4iv(location, v)', + + sampler2D: 'gl.uniform1iv(location, v)', + samplerCube: 'gl.uniform1iv(location, v)', + sampler2DArray: 'gl.uniform1iv(location, v)', +}; + +export default function generateUniformsSync(group, uniformData) +{ + let textureCount = 0; + let func = `var v = null; + var cv = null + var gl = renderer.gl`; + + for (const i in group.uniforms) + { + const data = uniformData[i]; + + if (!data) + { + if (group.uniforms[i].group) + { + func += ` + renderer.shader.syncUniformGroup(uv.${i}); + `; + } + + continue; + } + + // TODO && uniformData[i].value !== 0 <-- do we still need this? + if (data.type === 'float' && data.size === 1) + { + func += ` + if(uv.${i} !== ud.${i}.value) + { + ud.${i}.value = uv.${i} + gl.uniform1f(ud.${i}.location, uv.${i}) + }\n`; + } + /* eslint-disable max-len */ + else if ((data.type === 'sampler2D' || data.type === 'samplerCube' || data.type === 'sampler2DArray') && data.size === 1 && !data.isArray) + /* eslint-disable max-len */ + { + func += ` + renderer.texture.bind(uv.${i}, ${textureCount}); + + if(ud.${i}.value !== ${textureCount}) + { + ud.${i}.value = ${textureCount}; + gl.uniform1i(ud.${i}.location, ${textureCount});\n; // eslint-disable-line max-len + }\n`; + + textureCount++; + } + else if (data.type === 'mat3' && data.size === 1) + { + if (group.uniforms[i].a !== undefined) + { + // TODO and some smart caching dirty ids here! + func += ` + gl.uniformMatrix3fv(ud.${i}.location, false, uv.${i}.toArray(true)); + \n`; + } + else + { + func += ` + gl.uniformMatrix3fv(ud.${i}.location, false, uv.${i}); + \n`; + } + } + else if (data.type === 'vec2' && data.size === 1) + { + // TODO - do we need both here? + // maybe we can get away with only using points? + if (group.uniforms[i].x !== undefined) + { + func += ` + cv = ud.${i}.value; + v = uv.${i}; + + if(cv[0] !== v.x || cv[1] !== v.y) + { + cv[0] = v.x; + cv[1] = v.y; + gl.uniform2f(ud.${i}.location, v.x, v.y); + }\n`; + } + else + { + func += ` + cv = ud.${i}.value; + v = uv.${i}; + + if(cv[0] !== v[0] || cv[1] !== v[1]) + { + cv[0] = v[0]; + cv[1] = v[1]; + gl.uniform2f(ud.${i}.location, v[0], v[1]); + } + \n`; + } + } + else if (data.type === 'vec4' && data.size === 1) + { + // TODO - do we need both here? + // maybe we can get away with only using points? + if (group.uniforms[i].width !== undefined) + { + func += ` + cv = ud.${i}.value; + v = uv.${i}; + + if(cv[0] !== v.x || cv[1] !== v.y || cv[2] !== v.width || cv[3] !== v.height) + { + cv[0] = v.x; + cv[1] = v.y; + cv[2] = v.width; + cv[3] = v.height; + gl.uniform4f(ud.${i}.location, v.x, v.y, v.width, v.height) + }\n`; + } + else + { + func += ` + cv = ud.${i}.value; + v = uv.${i}; + + if(cv[0] !== v[0] || cv[1] !== v[1] || cv[2] !== v[2] || cv[3] !== v[3]) + { + cv[0] = v[0]; + cv[1] = v[1]; + cv[2] = v[2]; + cv[3] = v[3]; + + gl.uniform4f(ud.${i}.location, v[0], v[1], v[2], v[3]) + } + \n`; + } + } + else + { + const templateType = (data.size === 1) ? GLSL_TO_SINGLE_SETTERS_CACHED : GLSL_TO_ARRAY_SETTERS; + + const template = templateType[data.type].replace('location', `ud.${i}.location`); + + func += ` + cv = ud.${i}.value; + v = uv.${i}; + ${template};\n`; + } + } + + // console.log(' --------------- ') + // console.log(func); + + return new Function('ud', 'uv', 'renderer', func); // eslint-disable-line no-new-func +} diff --git a/packages/core/src/shader/utils/getTestContext.js b/packages/core/src/shader/utils/getTestContext.js new file mode 100644 index 0000000..ea174cc --- /dev/null +++ b/packages/core/src/shader/utils/getTestContext.js @@ -0,0 +1,48 @@ +import { settings } from '@pixi/settings'; + +let context = null; + +/** + * returns a little webGL context to use for program inspection. + * + * @static + * @private + * @returns {webGL-context} a gl context to test with + */ +export default function getTestContext() +{ + if (!context) + { + const canvas = document.createElement('canvas'); + + let gl; + + if (settings.PREFER_WEBGL_2) + { + gl = canvas.getContext('webgl2', {}); + } + + if (!gl) + { + gl = canvas.getContext('webgl', {}) + || canvas.getContext('experimental-webgl', {}); + + if (!gl) + { + // fail, not able to get a context + throw new Error('This browser does not support webGL. Try using the canvas renderer'); + } + else + { + // for shader testing.. + gl.getExtension('WEBGL_draw_buffers'); + } + } + + context = gl; + + return gl; + } + + return context; +} diff --git a/packages/core/src/shader/utils/index.js b/packages/core/src/shader/utils/index.js new file mode 100644 index 0000000..4bf8dd4 --- /dev/null +++ b/packages/core/src/shader/utils/index.js @@ -0,0 +1,8 @@ +export { default as compileProgram } from './compileProgram'; +export { default as defaultValue } from './defaultValue'; +export { default as setPrecision } from './setPrecision'; +export { default as mapSize } from './mapSize'; +export { default as mapType } from './mapType'; +export { default as generateUniformsSync } from './generateUniformsSync'; +export { default as getTestContext } from './getTestContext'; +export { default as checkMaxIfStatmentsInShader } from './checkMaxIfStatmentsInShader'; diff --git a/packages/core/src/shader/utils/mapSize.js b/packages/core/src/shader/utils/mapSize.js new file mode 100644 index 0000000..b681554 --- /dev/null +++ b/packages/core/src/shader/utils/mapSize.js @@ -0,0 +1,33 @@ +const GLSL_TO_SIZE = { + float: 1, + vec2: 2, + vec3: 3, + vec4: 4, + + int: 1, + ivec2: 2, + ivec3: 3, + ivec4: 4, + + bool: 1, + bvec2: 2, + bvec3: 3, + bvec4: 4, + + mat2: 4, + mat3: 9, + mat4: 16, + + sampler2D: 1, +}; + +/** + * @method mapSize + * @memberof PIXI.glCore.shader + * @param type {String} + * @return {Number} + */ +export default function mapSize(type) +{ + return GLSL_TO_SIZE[type]; +} diff --git a/packages/core/src/shader/utils/mapType.js b/packages/core/src/shader/utils/mapType.js new file mode 100644 index 0000000..d288b7c --- /dev/null +++ b/packages/core/src/shader/utils/mapType.js @@ -0,0 +1,45 @@ +let GL_TABLE = null; + +const GL_TO_GLSL_TYPES = { + FLOAT: 'float', + FLOAT_VEC2: 'vec2', + FLOAT_VEC3: 'vec3', + FLOAT_VEC4: 'vec4', + + INT: 'int', + INT_VEC2: 'ivec2', + INT_VEC3: 'ivec3', + INT_VEC4: 'ivec4', + + BOOL: 'bool', + BOOL_VEC2: 'bvec2', + BOOL_VEC3: 'bvec3', + BOOL_VEC4: 'bvec4', + + FLOAT_MAT2: 'mat2', + FLOAT_MAT3: 'mat3', + FLOAT_MAT4: 'mat4', + + SAMPLER_2D: 'sampler2D', + SAMPLER_CUBE: 'samplerCube', + SAMPLER_2D_ARRAY: 'sampler2DArray', +}; + +export default function mapType(gl, type) +{ + if (!GL_TABLE) + { + const typeNames = Object.keys(GL_TO_GLSL_TYPES); + + GL_TABLE = {}; + + for (let i = 0; i < typeNames.length; ++i) + { + const tn = typeNames[i]; + + GL_TABLE[gl[tn]] = GL_TO_GLSL_TYPES[tn]; + } + } + + return GL_TABLE[type]; +} diff --git a/packages/core/src/shader/utils/setPrecision.js b/packages/core/src/shader/utils/setPrecision.js new file mode 100644 index 0000000..e2c1f34 --- /dev/null +++ b/packages/core/src/shader/utils/setPrecision.js @@ -0,0 +1,18 @@ +/** + * Sets the float precision on the shader. If the precision is already present this function will do nothing + * @param {string} src the shader source + * @param {string} precision The float precision of the shader. Options are 'lowp', 'mediump' or 'highp'. + * + * @return {string} modified shader source + */ +export default function setPrecision(src, precision) +{ + src = src.trim(); + + if (src.substring(0, 9) !== 'precision' && src.substring(0, 1) !== '#') + { + return `precision ${precision} float;\n${src}`; + } + + return src; +} diff --git a/packages/core/src/state/State.js b/packages/core/src/state/State.js new file mode 100644 index 0000000..fa3f221 --- /dev/null +++ b/packages/core/src/state/State.js @@ -0,0 +1,163 @@ +/* eslint-disable max-len */ + +const BLEND = 0; +const OFFSET = 1; +const CULLING = 2; +const DEPTH_TEST = 3; +const WINDING = 4; + +/** + * This is a webGL state. It is passed The WebGL StateManager. + * Each mesh renderered may require webGL to be in a different state. + * For example you may want different blend mode or to enable polygon offsets + * + * @class + * @memberof PIXI + */ +export default class State +{ + /** + * + */ + constructor() + { + this.data = 0; + + this.blendMode = 0; + this.polygonOffset = 0; + + this.blend = true; + // this.depthTest = true; + } + + /** + * Activates blending of the computed fragment color values + * + * @member {boolean} + */ + get blend() + { + return !!(this.data & (1 << BLEND)); + } + + set blend(value) // eslint-disable-line require-jsdoc + { + if (!!(this.data & (1 << BLEND)) !== value) + { + this.data ^= (1 << BLEND); + } + } + + /** + * Activates adding an offset to depth values of polygon's fragments + * + * @member {boolean} + * @default false + */ + get offsets() + { + return !!(this.data & (1 << OFFSET)); + } + + set offsets(value) // eslint-disable-line require-jsdoc + { + if (!!(this.data & (1 << OFFSET)) !== value) + { + this.data ^= (1 << OFFSET); + } + } + + /** + * Activates culling of polygons. + * + * @member {boolean} + * @default false + */ + get culling() + { + return !!(this.data & (1 << CULLING)); + } + + set culling(value) // eslint-disable-line require-jsdoc + { + if (!!(this.data & (1 << CULLING)) !== value) + { + this.data ^= (1 << CULLING); + } + } + + /** + * Activates depth comparisons and updates to the depth buffer. + * + * @member {boolean} + * @default false + */ + get depthTest() + { + return !!(this.data & (1 << DEPTH_TEST)); + } + + set depthTest(value) // eslint-disable-line require-jsdoc + { + if (!!(this.data & (1 << DEPTH_TEST)) !== value) + { + this.data ^= (1 << DEPTH_TEST); + } + } + + /** + * Specifies whether or not front or back-facing polygons can be culled. + * @member {boolean} + * @default false + */ + get clockwiseFrontFace() + { + return !!(this.data & (1 << WINDING)); + } + + set clockwiseFrontFace(value) // eslint-disable-line require-jsdoc + { + if (!!(this.data & (1 << WINDING)) !== value) + { + this.data ^= (1 << WINDING); + } + } + + /** + * The blend mode to be applied when this state is set. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. + * Setting this mode to anything other than NO_BLEND will automatically switch blending on. + * + * @member {boolean} + * @default PIXI.BLEND_MODES.NORMAL + * @see PIXI.BLEND_MODES + */ + get blendMode() + { + return this._blendMode; + } + + set blendMode(value) // eslint-disable-line require-jsdoc + { + // 17 is NO BLEND + this.blend = (value !== 17); + this._blendMode = value; + } + + /** + * The polygon offset. Setting this property to anything other than 0 will automatically enable poygon offset fill. + * + * @member {number} + * @default 0 + */ + get polygonOffset() + { + return this._polygonOffset; + } + + set polygonOffset(value) // eslint-disable-line require-jsdoc + { + this.offsets = !!value; + this._polygonOffset = value; + } +} + diff --git a/packages/core/src/AbstractRenderer.js b/packages/core/src/AbstractRenderer.js new file mode 100644 index 0000000..64b2eb1 --- /dev/null +++ b/packages/core/src/AbstractRenderer.js @@ -0,0 +1,353 @@ +import { hex2string, hex2rgb } from '@pixi/utils'; +import { Matrix, Rectangle } from '@pixi/math'; +import { RENDERER_TYPE } from '@pixi/constants'; +import { settings } from '@pixi/settings'; +import { Container } from '@pixi/display'; +import RenderTexture from './renderTexture/RenderTexture'; +import EventEmitter from 'eventemitter3'; + +const tempMatrix = new Matrix(); + +/** + * 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 EventEmitter + * @memberof PIXI + */ +export default class AbstractRenderer extends EventEmitter +{ + // eslint-disable-next-line valid-jsdoc + /** + * @param {string} system - The name of the system this renderer is for. + * @param {object} [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.transparent=false] - If the render view is transparent, default false + * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false + * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. The + * resolution of the renderer retina would be 2. + * @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 {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when rendering, + * stopping pixel interpolation. + */ + constructor(system, options, arg2, arg3) + { + super(); + + // Support for constructor(system, screenWidth, screenHeight, options) + if (typeof options === 'number') + { + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + }, arg3); + } + + // 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 = RENDERER_TYPE.UNKNOWN; + + /** + * Measurements of the screen. (0, 0, screenWidth, screenHeight) + * + * Its safe to use as filterArea or hitArea for 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 1 + */ + this.resolution = options.resolution || settings.RESOLUTION; + + /** + * Whether the render view is transparent + * + * @member {boolean} + */ + this.transparent = options.transparent; + + /** + * Whether css dimensions of canvas view should be resized to screen dimensions automatically + * + * @member {boolean} + */ + this.autoResize = options.autoResize || false; + + /** + * Tracks the blend modes useful for this renderer. + * + * @member {object} + */ + this.blendModes = null; + + /** + * 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; + + /** + * If true PixiJS will Math.floor() x/y values when rendering, stopping pixel interpolation. + * Handy for crisp pixel art and speed on legacy devices. + * + * @member {boolean} + */ + this.roundPixels = options.roundPixels; + + /** + * The background color as a number. + * + * @member {number} + * @private + */ + this._backgroundColor = 0x000000; + + /** + * The background color as an [R, G, B] array. + * + * @member {number[]} + * @private + */ + this._backgroundColorRgba = [0, 0, 0, 0]; + + /** + * The background color as a string. + * + * @member {string} + * @private + */ + this._backgroundColorString = '#000000'; + + this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter + + /** + * This temporary display object used as the parent of the currently being rendered item + * + * @member {PIXI.DisplayObject} + * @private + */ + this._tempDisplayObjectParent = new Container(); + + /** + * The last root object that the renderer tried to render. + * + * @member {PIXI.DisplayObject} + * @private + */ + this._lastObjectRendered = this._tempDisplayObjectParent; + + /** + * Collection of plugins. + * @readonly + * @member {object} + */ + this.plugins = {}; + } + + /** + * Initialize the plugins. + * + * @protected + * @param {object} staticMap - The dictionary of staticly saved plugins. + */ + initPlugins(staticMap) + { + 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() + { + return this.view.width; + } + + /** + * Same as view.height, actual number of pixels in the canvas by vertical + * + * @member {number} + * @readonly + * @default 600 + */ + get height() + { + return this.view.height; + } + + /** + * Resizes the screen and canvas to the specified width and height + * Canvas dimensions are multiplied by resolution + * + * @param {number} screenWidth - the new width of the screen + * @param {number} screenHeight - the new height of the screen + */ + resize(screenWidth, screenHeight) + { + this.screen.width = screenWidth; + this.screen.height = screenHeight; + + this.view.width = screenWidth * this.resolution; + this.view.height = screenHeight * this.resolution; + + if (this.autoResize) + { + this.view.style.width = `${screenWidth}px`; + this.view.style.height = `${screenHeight}px`; + } + } + + /** + * 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 {PIXI.DisplayObject} displayObject - The displayObject the object will be generated from + * @param {number} scaleMode - Should be one of the scaleMode consts + * @param {number} resolution - The resolution / device pixel ratio of the texture being generated + * @param {PIXI.Rectangle} [region] - The region of the displayObject, that shall be rendered, + * if no region is specified, defaults to the local bounds of the displayObject. + * @return {PIXI.Texture} a texture of the graphics object + */ + generateTexture(displayObject, scaleMode, resolution, region) + { + region = region || displayObject.getLocalBounds(); + + // 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(region.width | 0, region.height | 0, scaleMode, resolution); + + tempMatrix.tx = -region.x; + tempMatrix.ty = -region.y; + + this.render(displayObject, renderTexture, false, tempMatrix, true); + + return renderTexture; + } + + /** + * Removes everything from the renderer and optionally removes the Canvas DOM element. + * + * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. + */ + destroy(removeView) + { + 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); + } + + this.plugins = null; + + this.type = RENDERER_TYPE.UNKNOWN; + + this.view = null; + + this.screen = null; + + this.resolution = 0; + + this.transparent = false; + + this.autoResize = false; + + this.blendModes = null; + + this.options = null; + + this.preserveDrawingBuffer = false; + this.clearBeforeRender = false; + + this.roundPixels = false; + + this._backgroundColor = 0; + this._backgroundColorRgba = null; + this._backgroundColorString = null; + + this._tempDisplayObjectParent = null; + this._lastObjectRendered = null; + } + + /** + * The background color to fill if not transparent + * + * @member {number} + */ + get backgroundColor() + { + return this._backgroundColor; + } + + set backgroundColor(value) // eslint-disable-line require-jsdoc + { + this._backgroundColor = value; + this._backgroundColorString = hex2string(value); + hex2rgb(value, this._backgroundColorRgba); + } +} diff --git a/packages/core/src/Renderer.js b/packages/core/src/Renderer.js new file mode 100644 index 0000000..2227e78 --- /dev/null +++ b/packages/core/src/Renderer.js @@ -0,0 +1,338 @@ +import AbstractRenderer from './AbstractRenderer'; +import { sayHello } from '@pixi/utils'; +import MaskSystem from './mask/MaskSystem'; +import StencilSystem from './mask/StencilSystem'; +import FilterSystem from './filters/FilterSystem'; +import FramebufferSystem from './framebuffer/FramebufferSystem'; +import RenderTextureSystem from './renderTexture/RenderTextureSystem'; +import TextureSystem from './textures/TextureSystem'; +import ProjectionSystem from './projection/ProjectionSystem'; +import StateSystem from './state/StateSystem'; +import GeometrySystem from './geometry/GeometrySystem'; +import ShaderSystem from './shader/ShaderSystem'; +import ContextSystem from './context/ContextSystem'; +import BatchSystem from './batch/BatchSystem'; +import TextureGCSystem from './textures/TextureGCSystem'; +import { RENDERER_TYPE } from '@pixi/constants'; +import UniformGroup from './shader/UniformGroup'; +import { Matrix } from '@pixi/math'; +import Runner from 'mini-runner'; + +/** + * 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 Render works by automatically managing webGLBatchs. + * 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 :) + * + * @class + * @memberof PIXI + * @extends PIXI.AbstractRenderer + */ +export default class Renderer extends AbstractRenderer +{ + // eslint-disable-next-line valid-jsdoc + /** + * + * @param {object} [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.transparent=false] - If the render view is transparent, default false + * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false + * @param {boolean} [options.antialias=false] - sets antialias. If not available natively then FXAA + * antialiasing is used + * @param {boolean} [options.forceFXAA=false] - forces FXAA antialiasing to be used over native. + * FXAA is faster, but may not always look as great + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. + * The resolution of the renderer retina would be 2. + * @param {boolean} [options.clearBeforeRender=true] - This sets if the renderer will clear + * the canvas or not before the new render pass. If you wish to set this to false, you *must* set + * preserveDrawingBuffer to `true`. + * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, + * enable this if you need to call toDataUrl on the webgl context. + * @param {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when + * rendering, stopping pixel interpolation. + * @param {number} [options.backgroundColor=0x000000] - The background color of the rendered area + * (shown if not transparent). + * @param {boolean} [options.legacy=false] - If true PixiJS will aim to ensure compatibility + * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. + * @param {string} [options.powerPreference] - Parameter passed to webgl context, set to "high-performance" + * for devices with dual graphics card + */ + constructor(options, arg2, arg3) + { + super('WebGL', options, arg2, arg3); + + /** + * The type of this renderer as a standardised const + * + * @member {number} + * @see PIXI.RENDERER_TYPE + */ + this.type = RENDERER_TYPE.WEBGL; + + // this will be set by the contextSystem (this.context) + this.gl = null; + this.CONTEXT_UID = 0; + this.legacy = !!options.legacy; + + // TODO legacy! + + // runners! + this.runners = { + destroy: new Runner('destroy'), + contextChange: new Runner('contextChange', 1), + reset: new Runner('reset'), + update: new Runner('update'), + postrender: new Runner('postrender'), + prerender: new Runner('prerender'), + resize: new Runner('resize', 2), + }; + + 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(GeometrySystem, 'geometry') + .addSystem(FramebufferSystem, 'framebuffer') + .addSystem(StencilSystem, 'stencil') + .addSystem(ProjectionSystem, 'projection') + .addSystem(TextureGCSystem, 'textureGC') + .addSystem(FilterSystem, 'filter') + .addSystem(RenderTextureSystem, 'renderTexture') + .addSystem(BatchSystem, 'batch'); + + this.initPlugins(Renderer.__plugins); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ + if (options.context) + { + this.context.initFromContext(options.context); + } + else + { + this.context.initFromOptions({ + alpha: this.transparent, + antialias: options.antialias, + premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', + stencil: true, + 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); + } + + /** + * Add a new system to the renderer. + * @param {class} ClassRef - Class reference + * @param {string} [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. + * @return {PIXI.Renderer} Return instance of renderer + */ + addSystem(ClassRef, name) + { + if (!name) + { + name = ClassRef.name; + } + + // TODO - read name from class.name.. + + /* + if(name.includes('System')) + { + name = name.replace('System', ''); + name = name.charAt(0).toLowerCase() + name.slice(1); + } + */ + + const system = new ClassRef(this); + + if (this[name]) + { + throw new Error(`Whoops! ${name} is already a manger`); + } + + this[name] = system; + + for (const i in this.runners) + { + this.runners[i].add(system); + } + + return this; + + /** + * 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. + */ + } + + /** + * Renders the object to its webGL view + * + * @param {PIXI.DisplayObject} displayObject - the object to be rendered + * @param {PIXI.RenderTexture} renderTexture - The render texture to render to. + * @param {boolean} [clear] - Should the canvas be cleared before the new render + * @param {PIXI.Transform} [transform] - A transform to apply to the render texture before rendering. + * @param {boolean} [skipUpdateTransform] - Should we skip the update transform pass? + */ + render(displayObject, renderTexture, clear, transform, skipUpdateTransform) + { + // can be handy to know! + this.renderingToScreen = !renderTexture; + + this.runners.prerender.run(); + this.emit('prerender'); + + // 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.parent; + + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = 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.run(); + + this.emit('postrender'); + } + + /** + * Resizes the webGL view to the specified width and height. + * + * @param {number} screenWidth - the new width of the screen + * @param {number} screenHeight - the new height of the screen + */ + resize(screenWidth, screenHeight) + { + AbstractRenderer.prototype.resize.call(this, screenWidth, screenHeight); + + this.runners.resize.run(screenWidth, screenHeight); + } + + /** + * Resets the WebGL state so you can render things however you fancy! + * + * @return {PIXI.Renderer} Returns itself. + */ + reset() + { + this.runners.reset.run(); + + return this; + } + + clear() + { + this.framebuffer.bind(); + this.framebuffer.clear(); + } + + /** + * Removes everything from the renderer (event listeners, spritebatch, etc...) + * + * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. + * See: https://github.com/pixijs/pixi.js/issues/2233 + */ + destroy(removeView) + { + this.runners.destroy.run(); + + // call base destroy + super.destroy(removeView); + + // TODO nullify all the managers.. + this.gl = null; + } + + /** + * 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 + * builds and excluding plugins. + * @name PIXI.Renderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. + */ + + /** + * Adds a plugin to the renderer. + * + * @method + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + static registerPlugin(pluginName, ctor) + { + Renderer.__plugins = Renderer.__plugins || {}; + Renderer.__plugins[pluginName] = ctor; + } +} diff --git a/packages/core/src/System.js b/packages/core/src/System.js new file mode 100644 index 0000000..05a10c8 --- /dev/null +++ b/packages/core/src/System.js @@ -0,0 +1,42 @@ +/** + * System is a base class used for extending systems used by the {@link PIXI.Renderer} + * @see PIXI.Renderer#addSystem + * @class + * @memberof PIXI + */ +export default class System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + /** + * The renderer this manager works for. + * + * @member {PIXI.Renderer} + */ + this.renderer = renderer; + + this.renderer.runners.contextChange.add(this); + } + + /** + * Generic method called when there is a WebGL context change. + * + */ + contextChange() + { + // do some codes init! + } + + /** + * Generic destroy methods to be overridden by the subclass + * + */ + destroy() + { + this.renderer.runners.contextChange.remove(this); + this.renderer = null; + } +} diff --git a/packages/core/src/batch/BatchSystem.js b/packages/core/src/batch/BatchSystem.js new file mode 100644 index 0000000..b04c249 --- /dev/null +++ b/packages/core/src/batch/BatchSystem.js @@ -0,0 +1,68 @@ +import System from '../System'; +import ObjectRenderer from './ObjectRenderer'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class BatchSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + super(renderer); + + /** + * An empty renderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.emptyRenderer = new ObjectRenderer(renderer); + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.currentRenderer = this.emptyRenderer; + } + + /** + * Changes the current renderer to the one given in parameter + * + * @param {PIXI.ObjectRenderer} objectRenderer - The object renderer to use. + */ + setObjectRenderer(objectRenderer) + { + if (this.currentRenderer === objectRenderer) + { + return; + } + + this.currentRenderer.stop(); + this.currentRenderer = objectRenderer; + + this.renderer.state.setState(objectRenderer.state); + + this.currentRenderer.start(); + } + + /** + * This should be called if you wish to do some custom rendering + * It will basically render anything that may be batched up such as sprites + * + */ + flush() + { + this.setObjectRenderer(this.emptyRenderer); + } + + reset() + { + this.setObjectRenderer(this.emptyRenderer); + } +} diff --git a/packages/core/src/batch/ObjectRenderer.js b/packages/core/src/batch/ObjectRenderer.js new file mode 100644 index 0000000..0c851a9 --- /dev/null +++ b/packages/core/src/batch/ObjectRenderer.js @@ -0,0 +1,48 @@ +import System from '../System'; + +/** + * Base for a common object renderer that can be used as a system renderer plugin. + * + * @class + * @extends PIXI.System + * @memberof PIXI + */ +export default class ObjectRenderer extends System +{ + /** + * Starts the renderer and sets the shader + * + */ + start() + { + // set the shader.. + } + + /** + * Stops the renderer + * + */ + stop() + { + this.flush(); + } + + /** + * Stub method for rendering content and emptying the current batch. + * + */ + flush() + { + // flush! + } + + /** + * Renders an object + * + * @param {PIXI.DisplayObject} object - The object to render. + */ + render(object) // eslint-disable-line no-unused-vars + { + // render the object + } +} diff --git a/packages/core/src/context/ContextSystem.js b/packages/core/src/context/ContextSystem.js new file mode 100644 index 0000000..301aab4 --- /dev/null +++ b/packages/core/src/context/ContextSystem.js @@ -0,0 +1,186 @@ +import System from '../System'; +import { settings } from '@pixi/settings'; + +let CONTEXT_UID = 0; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class ContextSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.webGLVersion = 1; + + this.handleContextLost = this.handleContextLost.bind(this); + this.handleContextRestored = this.handleContextRestored.bind(this); + + this.extensions = {}; + + renderer.view.addEventListener('webglcontextlost', this.handleContextLost, false); + renderer.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + } + + get isLost() + { + return (!this.gl || this.gl.isContextLost()); + } + + contextChange(gl) + { + this.gl = gl; + + // restore a context if it was previously lost + if (gl.isContextLost() && gl.getExtension('WEBGL_lose_context')) + { + gl.getExtension('WEBGL_lose_context').restoreContext(); + } + } + + initFromContext(gl) + { + this.gl = gl; + this.validateContext(gl); + this.renderer.gl = gl; + this.renderer.CONTEXT_UID = CONTEXT_UID++; + this.renderer.runners.contextChange.run(gl); + } + + initFromOptions(options) + { + const gl = this.createContext(this.renderer.view, options); + + this.initFromContext(gl); + } + + /** + * Helper class to create a webGL Context + * + * @class + * @memberof PIXI.glCore + * @param canvas {HTMLCanvasElement} the canvas element that we will get the context from + * @param options {Object} An options object that gets passed in to the canvas element containing the context attributes, + * see https://developer.mozilla.org/en/docs/Web/API/HTMLCanvasElement/getContext for the options available + * @return {WebGLRenderingContext} the WebGL context + */ + createContext(canvas, options) + { + let gl; + + if (settings.PREFER_WEBGL_2) + { + gl = canvas.getContext('webgl2', options); + } + + if (gl) + { + this.webGLVersion = 2; + } + else + { + this.webGLVersion = 1; + + gl = canvas.getContext('webgl', options) + || canvas.getContext('experimental-webgl', options); + + if (!gl) + { + // fail, not able to get a context + throw new Error('This browser does not support webGL. Try using the canvas renderer'); + } + } + + this.gl = gl; + + this.getExtensions(); + + return gl; + } + + getExtensions() + { + // time to set up default etensions that pixi uses.. + const gl = this.gl; + const extensions = this.extensions; + + if (this.webGLVersion === 1) + { + extensions.drawBuffers = gl.getExtension('WEBGL_draw_buffers'); + extensions.depthTexture = gl.getExtension('WEBKIT_WEBGL_depth_texture'); + extensions.floatTexture = gl.getExtension('OES_texture_float'); + extensions.loseContext = gl.getExtension('WEBGL_lose_context'); + + extensions.vertexArrayObject = gl.getExtension('OES_vertex_array_object') + || gl.getExtension('MOZ_OES_vertex_array_object') + || gl.getExtension('WEBKIT_OES_vertex_array_object'); + } + + // we don't use any specific WebGL 2 ones yet! + } + + /** + * Handles a lost webgl context + * + * @private + * @param {WebGLContextEvent} event - The context lost event. + */ + handleContextLost(event) + { + event.preventDefault(); + } + + /** + * Handles a restored webgl context + * + * @private + */ + handleContextRestored() + { + this.renderer.runners.contextChange.run(this.gl); + } + + destroy() + { + const view = this.renderer.view; + + // remove listeners + view.removeEventListener('webglcontextlost', this.handleContextLost); + view.removeEventListener('webglcontextrestored', this.handleContextRestored); + + this.gl.useProgram(null); + + if (this.extensions.loseContext) + { + this.extensions.loseContext.loseContext(); + } + } + + postrender() + { + this.gl.flush(); + } + + validateContext(gl) + { + const attributes = gl.getContextAttributes(); + + // this is going to be fairly simple for now.. but at least we have room to grow! + if (!attributes.stencil) + { + /* eslint-disable max-len */ + + /* eslint-disable no-console */ + console.warn('Provided WebGL context does not have a stencil buffer, masks may not render correctly'); + /* eslint-enable no-console */ + + /* eslint-enable max-len */ + } + } +} diff --git a/packages/core/src/filters/Filter.js b/packages/core/src/filters/Filter.js new file mode 100644 index 0000000..9d8cad8 --- /dev/null +++ b/packages/core/src/filters/Filter.js @@ -0,0 +1,140 @@ +import Shader from '../shader/Shader'; +import Program from '../shader/Program'; +import State from '../state/State'; +import { settings } from '@pixi/settings'; +// import extractUniformsFromSrc from './extractUniformsFromSrc'; +import defaultVertex from './defaultFilter.vert'; +import defaultFragment from './defaultFilter.frag'; + +// let math = require('../../math'); +/** + * @class + * @memberof PIXI + * @extends PIXI.Shader + */ +export default class Filter extends Shader +{ + /** + * @param {string} [vertexSrc] - The source of the vertex shader. + * @param {string} [fragmentSrc] - The source of the fragment shader. + * @param {object} [uniforms] - Custom uniforms to use to augment the built-in ones. + */ + constructor(vertexSrc, fragmentSrc, uniforms) + { + const program = Program.from(vertexSrc, fragmentSrc); + + super(program, uniforms); + + /** + * The padding of the filter. Some filters require extra space to breath such as a blur. + * Increasing this will add extra width and height to the bounds of the object that the + * filter is applied to. + * + * @member {number} + */ + this.padding = 0; + + /** + * The resolution of the filter. Setting this to be lower will lower the quality but + * increase the performance of the filter. + * + * @member {number} + */ + this.resolution = settings.RESOLUTION; + + /** + * If enabled is true the filter is applied, if false it will not. + * + * @member {boolean} + */ + this.enabled = true; + + /** + * If enabled, PixiJS will fit the filter area into boundaries for better performance. + * Switch it off if it does not work for specific shader. + * + * @member {boolean} + */ + this.autoFit = true; + + /** + * Legacy filters use position and uvs from attributes + * @member {boolean} + * @readonly + */ + this.legacy = !!this.program.attributeData.aTextureCoord; + + /** + * the webGL state the filter requires to render + * @member {PIXI.State} + */ + this.state = new State(); + } + + /** + * Applies the filter + * + * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + * @param {boolean} clear - Should the output be cleared before rendering to it + * @param {object} [currentState] - It's current state of filter. + * There are some useful properties in the currentState : + * target, filters, sourceFrame, destinationFrame, renderTarget, resolution + */ + apply(filterManager, input, output, clear, currentState, derp) // eslint-disable-line no-unused-vars + { + // do as you please! + + filterManager.applyFilter(this, input, output, clear, currentState, derp); + + // or just do a regular render.. + } + + /** + * Sets the blendmode of the filter + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL + */ + get blendMode() + { + return this.state.blendMode; + } + + set blendMode(value) // eslint-disable-line require-jsdoc + { + this.state.blendMode = value; + } + + /** + * The default vertex shader source + * + * @static + * @constant + */ + static get defaultVertexSrc() + { + return defaultVertex; + } + + /** + * The default fragment shader source + * + * @static + * @constant + */ + static get defaultFragmentSrc() + { + return defaultFragment; + } +} + +/** + * Used for caching shader IDs + * + * @static + * @private + */ +Filter.SOURCE_KEY_MAP = {}; + diff --git a/packages/core/src/filters/FilterSystem.js b/packages/core/src/filters/FilterSystem.js new file mode 100644 index 0000000..3174318 --- /dev/null +++ b/packages/core/src/filters/FilterSystem.js @@ -0,0 +1,431 @@ +import System from '../System'; + +import RenderTexture from '../renderTexture/RenderTexture'; +import Quad from '../utils/Quad'; +import QuadUv from '../utils/QuadUv'; +import { Rectangle } from '@pixi/math'; +import * as filterTransforms from './filterTransforms'; +import bitTwiddle from 'bit-twiddle'; +import UniformGroup from '../shader/UniformGroup'; +import { DRAW_MODES } from '@pixi/constants'; + +// +/** + * @ignore + * @class + */ +class FilterState +{ + /** + * + */ + constructor() + { + this.renderTexture = null; + this.sourceFrame = new Rectangle(); + this.destinationFrame = new Rectangle(); + this.filters = []; + this.target = null; + this.legacy = false; + this.resolution = 1; + } +} + +/** + * @class + * @memberof PIXI.systems + * @extends PIXI.System + */ +export default class FilterSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + /** + * stores a bunch of PO2 textures used for filtering + * @type {Object} + */ + this.texturePool = {}; + + /** + * a pool for storing filter states, save us creating new ones each tick + * @type {Array} + */ + this.statePool = []; + + /** + * A very simple geometry used when drawing a filter effect to the screen + * @type {Quad} + */ + this.quad = new Quad(); + + this.quadUv = new QuadUv(); + + /** + * Temporary rect for maths + * @type {PIXI.Rectangle} + */ + this.tempRect = new Rectangle(); + + this.activeState = {}; + + /** + * this uniform group is attached to filter uniforms when used + * @type {UniformGroup} + */ + this.globalUniforms = new UniformGroup({ + sourceFrame: this.tempRect, + destinationFrame: this.tempRect, + + // legacy variables + filterArea: new Float32Array(4), + filterClamp: new Float32Array(4), + }, true); + } + + /** + * Adds a new filter to the System. + * + * @param {PIXI.DisplayObject} target - The target of the filter to render. + * @param {PIXI.Filter[]} filters - The filters to apply. + */ + push(target, filters) + { + const renderer = this.renderer; + const filterStack = this.renderer.renderTexture.defaultFilterStack; + const state = this.statePool.pop() || new FilterState(); + + let resolution = filters[0].resolution; + let padding = filters[0].padding; + let autoFit = filters[0].autoFit; + let legacy = filters[0].legacy; + + for (let i = 1; i < filters.length; i++) + { + const filter = filters[i]; + + // lets use the lowest resolution.. + resolution = Math.min(resolution, filter.resolution); + // and the largest amount of padding! + padding = Math.max(padding, filter.padding); + // only auto fit if all filters are autofit + autoFit = autoFit || filter.autoFit; + + legacy = legacy || filter.legacy; + } + + filterStack.push(state); + + state.resolution = resolution; + + state.legacy = legacy; + + // round to whole number based on resolution + // TODO move that to the shader too? + state.sourceFrame = target.filterArea || target.getBounds(true); + + state.sourceFrame.pad(padding); + + if (autoFit) + { + state.sourceFrame.fit(this.renderer.renderTexture.destinationFrame); + } + + state.sourceFrame.round(resolution); + + state.renderTexture = this.getPotFilterTexture(state.sourceFrame.width, state.sourceFrame.height, resolution); + state.filters = filters; + + state.destinationFrame.width = state.renderTexture.width; + state.destinationFrame.height = state.renderTexture.height; + + state.renderTexture.filterFrame = state.sourceFrame; + renderer.renderTexture.bind(state.renderTexture, state.sourceFrame);// /, state.destinationFrame); + renderer.renderTexture.clear(); + } + + /** + * Pops off the filter and applies it. + * + */ + pop() + { + const renderer = this.renderer; + const filterStack = renderer.renderTexture.defaultFilterStack; + const state = filterStack.pop(); + const filters = state.filters; + + this.activeState = state; + + const globalUniforms = this.globalUniforms.uniforms; + + globalUniforms.sourceFrame = state.sourceFrame; + globalUniforms.destinationFrame = state.destinationFrame; + globalUniforms.resolution = state.resolution; + + // only update the rect if its legacy.. + if (state.legacy) + { + const filterArea = globalUniforms.filterArea; + const filterClamp = globalUniforms.filterClamp; + + filterArea[0] = state.destinationFrame.width; + filterArea[1] = state.destinationFrame.height; + filterArea[2] = state.sourceFrame.x; + filterArea[3] = state.sourceFrame.y; + + filterClamp[0] = 0.5 / state.resolution / state.destinationFrame.width; + filterClamp[1] = 0.5 / state.resolution / state.destinationFrame.height; + filterClamp[2] = (state.sourceFrame.width - 0.5) / state.resolution / state.destinationFrame.width; + filterClamp[3] = (state.sourceFrame.height - 0.5) / state.resolution / state.destinationFrame.height; + } + + this.globalUniforms.update(); + + const lastState = filterStack[filterStack.length - 1]; + + if (filters.length === 1) + { + filters[0].apply(this, state.renderTexture, lastState.renderTexture, false, state); + + this.returnFilterTexture(state.renderTexture); + } + else + { + let flip = state.renderTexture; + let flop = this.getPotFilterTexture( + flip.width, + flip.height, + state.resolution + ); + + flop.filterFrame = flip.filterFrame; + + let i = 0; + + for (i = 0; i < filters.length - 1; ++i) + { + filters[i].apply(this, flip, flop, true, state); + + const t = flip; + + flip = flop; + flop = t; + } + + filters[i].apply(this, flip, lastState.renderTexture, false, state); + + this.returnFilterTexture(flip); + this.returnFilterTexture(flop); + } + + this.statePool.push(state); + } + + /** + * Draws a filter. + * + * @param {PIXI.Filter} filter - The filter to draw. + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + * @param {boolean} clear - Should the output be cleared before rendering to it + */ + applyFilter(filter, input, output, clear) + { + const renderer = this.renderer; + + renderer.renderTexture.bind(output, output ? output.filterFrame : null); + + if (clear) + { + // gl.disable(gl.SCISSOR_TEST); + renderer.renderTexture.clear(); + // gl.enable(gl.SCISSOR_TEST); + } + + // set the uniforms.. + filter.uniforms.uSampler = input; + filter.uniforms.filterGlobals = this.globalUniforms; + + // TODO make it so that the order of this does not matter.. + // because it does at the moment cos of global uniforms. + // they need to get resynced + + renderer.state.setState(filter.state); + renderer.shader.bind(filter); + + if (filter.legacy) + { + this.quadUv.map(input._frame, input.filterFrame); + + renderer.geometry.bind(this.quadUv); + renderer.geometry.draw(DRAW_MODES.TRIANGLES); + } + else + { + renderer.geometry.bind(this.quad); + renderer.geometry.draw(DRAW_MODES.TRIANGLE_STRIP); + } + } + + /** + * Calculates the mapped matrix. + * + * TODO playing around here.. this is temporary - (will end up in the shader) + * this returns a matrix that will normalise map filter cords in the filter to screen space + * + * @param {PIXI.Matrix} outputMatrix - the matrix to output to. + * @return {PIXI.Matrix} The mapped matrix. + */ + calculateScreenSpaceMatrix(outputMatrix) + { + const currentState = this.activeState; + + return filterTransforms.calculateScreenSpaceMatrix( + outputMatrix, + currentState.sourceFrame, + currentState.destinationFrame + ); + } + + /** + * This will map the filter coord so that a texture can be used based on the transform of a sprite + * + * @param {PIXI.Matrix} outputMatrix - The matrix to output to. + * @param {PIXI.Sprite} sprite - The sprite to map to. + * @return {PIXI.Matrix} The mapped matrix. + */ + calculateSpriteMatrix(outputMatrix, sprite) + { + const currentState = this.activeState; + + return filterTransforms.calculateSpriteMatrix( + outputMatrix, + currentState.sourceFrame, + currentState.destinationFrame, + sprite + ); + } + + /** + * Destroys this Filter System. + * + * @param {boolean} [contextLost=false] context was lost, do not free shaders + * + */ + destroy(contextLost = false) + { + if (!contextLost) + { + this.emptyPool(); + } + else + { + this.texturePool = {}; + } + } + + /** + * Gets a Power-of-Two render texture. + * + * TODO move to a seperate class could be on renderer? + * also - could cause issue with multiple contexts? + * + * @private + * @param {WebGLRenderingContext} gl - The webgl rendering context + * @param {number} minWidth - The minimum width of the render target. + * @param {number} minHeight - The minimum height of the render target. + * @param {number} resolution - The resolution of the render target. + * @return {PIXI.RenderTarget} The new render target. + */ + getPotFilterTexture(minWidth, minHeight, resolution) + { + minWidth = bitTwiddle.nextPow2(minWidth); + minHeight = bitTwiddle.nextPow2(minHeight); + resolution = resolution || 1; + + const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + + if (!this.texturePool[key]) + { + this.texturePool[key] = []; + } + + let renderTexture = this.texturePool[key].pop(); + + if (!renderTexture) + { + // temporary bypass cache.. + // internally - this will cause a texture to be bound.. + renderTexture = RenderTexture.create({ + width: minWidth, + height: minHeight, + resolution, + }); + } + + return renderTexture; + } + + /** + * Gets extra render texture to use inside current filter + * + * @param {number} resolution resolution of the renderTexture + * @returns {PIXI.RenderTexture} + */ + getFilterTexture(resolution) + { + const rt = this.activeState.renderTexture; + + const filterTexture = this.getPotFilterTexture(rt.width, rt.height, resolution || rt.baseTexture.resolution); + + filterTexture.filterFrame = rt.filterFrame; + + return filterTexture; + } + + /** + * Frees a render texture back into the pool. + * + * @param {PIXI.RenderTarget} renderTexture - The renderTarget to free + */ + returnFilterTexture(renderTexture) + { + renderTexture.filterFrame = null; + + const base = renderTexture.baseTexture; + + const minWidth = base.width; + const minHeight = base.height; + + const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + + this.texturePool[key].push(renderTexture); + } + + /** + * Empties the texture pool. + * + */ + emptyPool() + { + for (const i in this.texturePool) + { + const textures = this.texturePool[i]; + + if (textures) + { + for (let j = 0; j < textures.length; j++) + { + textures[j].destroy(true); + } + } + } + + this.texturePool = {}; + } +} diff --git a/packages/core/src/filters/defaultFilter.frag b/packages/core/src/filters/defaultFilter.frag new file mode 100644 index 0000000..a14c1be --- /dev/null +++ b/packages/core/src/filters/defaultFilter.frag @@ -0,0 +1,22 @@ +varying vec2 vTextureCoord; +varying vec2 vFilterCoord; + +uniform sampler2D uSampler; +uniform sampler2D filterSampler; + +void main(void){ + vec4 masky = texture2D(filterSampler, vFilterCoord); + vec4 sample = texture2D(uSampler, vTextureCoord); + vec4 color; + if(mod(vFilterCoord.x, 1.0) > 0.5) + { + color = vec4(1.0, 0.0, 0.0, 1.0); + } + else + { + color = vec4(0.0, 1.0, 0.0, 1.0); + } + // gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0); + gl_FragColor = mix(sample, masky, 0.5); + gl_FragColor *= sample.a; +} diff --git a/packages/core/src/filters/defaultFilter.vert b/packages/core/src/filters/defaultFilter.vert new file mode 100644 index 0000000..e1febca --- /dev/null +++ b/packages/core/src/filters/defaultFilter.vert @@ -0,0 +1,14 @@ +attribute vec2 aVertexPosition; +attribute vec2 aTextureCoord; + +uniform mat3 projectionMatrix; +uniform mat3 filterMatrix; + +varying vec2 vTextureCoord; +varying vec2 vFilterCoord; + +void main(void){ + gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); + vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy; + vTextureCoord = aTextureCoord ; +} diff --git a/packages/core/src/filters/filterTransforms.js b/packages/core/src/filters/filterTransforms.js new file mode 100644 index 0000000..87a3cd6 --- /dev/null +++ b/packages/core/src/filters/filterTransforms.js @@ -0,0 +1,50 @@ +import { Matrix } from '@pixi/math'; + +/** + * Calculates the mapped matrix + * @param filterArea {Rectangle} The filter area + * @param sprite {Sprite} the target sprite + * @param outputMatrix {Matrix} @alvin + * @private + */ +// this returns a matrix that will normalise map filter cords in the filter to screen space +export function calculateScreenSpaceMatrix(outputMatrix, filterArea, textureSize) +{ + // TODO unwrap? + const mappedMatrix = outputMatrix.identity(); + + mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); + + mappedMatrix.scale(textureSize.width, textureSize.height); + + return mappedMatrix; +} + +export function calculateNormalizedScreenSpaceMatrix(outputMatrix, filterArea, textureSize) +{ + const mappedMatrix = outputMatrix.identity(); + + mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); + + const translateScaleX = (textureSize.width / filterArea.width); + const translateScaleY = (textureSize.height / filterArea.height); + + mappedMatrix.scale(translateScaleX, translateScaleY); + + return mappedMatrix; +} + +// this will map the filter coord so that a texture can be used based on the transform of a sprite +export function calculateSpriteMatrix(outputMatrix, filterArea, textureSize, sprite) +{ + const orig = sprite._texture.orig; + const mappedMatrix = outputMatrix.set(textureSize.width, 0, 0, textureSize.height, filterArea.x, filterArea.y); + const worldTransform = sprite.worldTransform.copyTo(Matrix.TEMP_MATRIX); + + worldTransform.invert(); + mappedMatrix.prepend(worldTransform); + mappedMatrix.scale(1.0 / orig.width, 1.0 / orig.height); + mappedMatrix.translate(sprite.anchor.x, sprite.anchor.y); + + return mappedMatrix; +} diff --git a/packages/core/src/filters/spriteMask/SpriteMaskFilter.js b/packages/core/src/filters/spriteMask/SpriteMaskFilter.js new file mode 100644 index 0000000..91697b7 --- /dev/null +++ b/packages/core/src/filters/spriteMask/SpriteMaskFilter.js @@ -0,0 +1,63 @@ +import Filter from '../Filter'; +import { Matrix } from '@pixi/math'; +import vertex from './spriteMaskFilter.vert'; +import fragment from './spriteMaskFilter.frag'; +import { default as TextureMatrix } from '../../textures/TextureMatrix'; + +/** + * The SpriteMaskFilter class + * + * @class + * @extends PIXI.Filter + * @memberof PIXI + */ +export default class SpriteMaskFilter extends Filter +{ + /** + * @param {PIXI.Sprite} sprite - the target sprite + */ + constructor(sprite) + { + const maskMatrix = new Matrix(); + + super(vertex, fragment); + + sprite.renderable = false; + + this.maskSprite = sprite; + this.maskMatrix = maskMatrix; + } + + /** + * Applies the filter + * + * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + */ + apply(filterManager, input, output) + { + const maskSprite = this.maskSprite; + const tex = this.maskSprite.texture; + + if (!tex.valid) + { + return; + } + if (!tex.transform) + { + // margin = 0.0, let it bleed a bit, shader code becomes easier + // assuming that atlas textures were made with 1-pixel padding + tex.transform = new TextureMatrix(tex, 0.0); + } + tex.transform.update(); + + this.uniforms.mask = tex; + this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite) + .prepend(tex.transform.mapCoord); + this.uniforms.alpha = maskSprite.worldAlpha; + this.uniforms.maskClamp = tex.transform.uClampFrame; + + filterManager.applyFilter(this, input, output); + } +} diff --git a/packages/core/src/filters/spriteMask/spriteMaskFilter.frag b/packages/core/src/filters/spriteMask/spriteMaskFilter.frag new file mode 100644 index 0000000..0e4aef8 --- /dev/null +++ b/packages/core/src/filters/spriteMask/spriteMaskFilter.frag @@ -0,0 +1,23 @@ +varying vec2 vMaskCoord; +varying vec2 vTextureCoord; + +uniform sampler2D uSampler; +uniform sampler2D mask; +uniform float alpha; +uniform vec4 maskClamp; + +void main(void) +{ + float clip = step(3.5, + step(maskClamp.x, vMaskCoord.x) + + step(maskClamp.y, vMaskCoord.y) + + step(vMaskCoord.x, maskClamp.z) + + step(vMaskCoord.y, maskClamp.w)); + + vec4 original = texture2D(uSampler, vTextureCoord); + vec4 masky = texture2D(mask, vMaskCoord); + + original *= (masky.r * masky.a * alpha * clip); + + gl_FragColor = original; +} diff --git a/packages/core/src/filters/spriteMask/spriteMaskFilter.vert b/packages/core/src/filters/spriteMask/spriteMaskFilter.vert new file mode 100644 index 0000000..d23a30c --- /dev/null +++ b/packages/core/src/filters/spriteMask/spriteMaskFilter.vert @@ -0,0 +1,16 @@ +attribute vec2 aVertexPosition; +attribute vec2 aTextureCoord; + +uniform mat3 projectionMatrix; +uniform mat3 otherMatrix; + +varying vec2 vMaskCoord; +varying vec2 vTextureCoord; + +void main(void) +{ + gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); + + vTextureCoord = aTextureCoord; + vMaskCoord = ( otherMatrix * vec3( aTextureCoord, 1.0) ).xy; +} diff --git a/packages/core/src/framebuffer/FrameBuffer.js b/packages/core/src/framebuffer/FrameBuffer.js new file mode 100644 index 0000000..84e9fcc --- /dev/null +++ b/packages/core/src/framebuffer/FrameBuffer.js @@ -0,0 +1,106 @@ +import Texture from '../textures/BaseTexture'; +import { FORMATS, TYPES } from '@pixi/constants'; + +/** + * Frame buffer + * @class + * @memberof PIXI + */ +export default class FrameBuffer +{ + constructor(width, height) + { + this.width = width || 100; + this.height = height || 100; + + this.stencil = false; + this.depth = false; + + this.dirtyId = 0; + this.dirtyFormat = 0; + this.dirtySize = 0; + + this.depthTexture = null; + this.colorTextures = []; + + this.glFrameBuffers = {}; + } + + get colorTexture() + { + return this.colorTextures[0]; + } + + addColorTexture(index, texture) + { + // TODO add some validation to the texture - same width / height etc? + this.colorTextures[index || 0] = texture || new Texture(null, { scaleMode: 0, + resolution: 1, + mipmap: false, + width: this.width, + height: this.height });// || new Texture(); + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + addDepthTexture(texture) + { + /* eslint-disable max-len */ + this.depthTexture = texture || new Texture(null, { scaleMode: 0, + resolution: 1, + width: this.width, + height: this.height, + mipmap: false, + format: FORMATS.DEPTH_COMPONENT, + type: TYPES.UNSIGNED_SHORT });// UNSIGNED_SHORT; + /* eslint-disable max-len */ + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + enableDepth() + { + this.depth = true; + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + enableStencil() + { + this.stencil = true; + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + resize(width, height) + { + if (width === this.width && height === this.height) return; + + this.width = width; + this.height = height; + + this.dirtyId++; + this.dirtySize++; + + for (let i = 0; i < this.colorTextures.length; i++) + { + this.colorTextures[i].setSize(width, height); + } + + if (this.depthTexture) + { + this.depthTexture.setSize(width, height); + } + } +} diff --git a/packages/core/src/framebuffer/FramebufferSystem.js b/packages/core/src/framebuffer/FramebufferSystem.js new file mode 100644 index 0000000..2cc2437 --- /dev/null +++ b/packages/core/src/framebuffer/FramebufferSystem.js @@ -0,0 +1,246 @@ +import System from '../System'; +import { Rectangle } from '@pixi/math'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class FramebufferSystem extends System +{ + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + contextChange() + { + this.gl = this.renderer.gl; + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + this.current = null; + this.viewport = new Rectangle(); + + this.drawBufferExtension = this.renderer.context.extensions.drawBuffers; + } + + bind(framebuffer, frame) + { + const gl = this.gl; + + this.current = framebuffer; + + if (framebuffer) + { + // TODO cacheing layer! + + const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID] || this.initFramebuffer(framebuffer); + + gl.bindFramebuffer(gl.FRAMEBUFFER, fbo.framebuffer); + // makesure all textures are unbound.. + + // now check for updates... + if (fbo.dirtyId !== framebuffer.dirtyId) + { + fbo.dirtyId = framebuffer.dirtyId; + + if (fbo.dirtyFormat !== framebuffer.dirtyFormat) + { + fbo.dirtyFormat = framebuffer.dirtyFormat; + this.updateFramebuffer(framebuffer); + } + else if (fbo.dirtySize !== framebuffer.dirtySize) + { + fbo.dirtySize = framebuffer.dirtySize; + this.resizeFramebuffer(framebuffer); + } + } + + for (let i = 0; i < framebuffer.colorTextures.length; i++) + { + if (framebuffer.colorTextures[i].texturePart) + { + this.renderer.texture.unbind(framebuffer.colorTextures[i].texture); + } + else + { + this.renderer.texture.unbind(framebuffer.colorTextures[i]); + } + } + + if (framebuffer.depthTexture) + { + this.renderer.texture.unbind(framebuffer.depthTexture); + } + + if (frame) + { + this.setViewport(frame.x, frame.y, frame.width, frame.height); + } + else + { + this.setViewport(0, 0, framebuffer.width, framebuffer.height); + } + } + else + { + gl.bindFramebuffer(gl.FRAMEBUFFER, null); + + if (frame) + { + this.setViewport(frame.x, frame.y, frame.width, frame.height); + } + else + { + this.setViewport(0, 0, this.renderer.width, this.renderer.height); + } + } + } + + setViewport(x, y, width, height) + { + const v = this.viewport; + + if (v.width !== width || v.height !== height || v.x !== x || v.y !== y) + { + v.x = x; + v.y = y; + v.width = width; + v.height = height; + + this.gl.viewport(x, y, width, height); + } + } + + get size() + { + if (this.current) + { + // TODO store temp + return { x: 0, y: 0, width: this.current.width, height: this.current.height }; + } + + return { x: 0, y: 0, width: this.renderer.width, height: this.renderer.height }; + } + + clear(r, g, b, a) + { + const gl = this.gl; + + // TODO clear color can be set only one right? + gl.clearColor(r, g, b, a); + gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); + } + + // private functions... + + initFramebuffer(framebuffer) + { + const gl = this.gl; + + // TODO - make this a class? + const fbo = { + framebuffer: gl.createFramebuffer(), + stencil: null, + dirtyId: 0, + dirtyFormat: 0, + dirtySize: 0, + }; + + framebuffer.glFrameBuffers[this.CONTEXT_UID] = fbo; + + return fbo; + } + + resizeFramebuffer(framebuffer) + { + const gl = this.gl; + + if (framebuffer.stencil || framebuffer.depth) + { + gl.bindRenderbuffer(gl.RENDERBUFFER, this.stencil); + gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); + } + } + + updateFramebuffer(framebuffer) + { + const gl = this.gl; + + const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID]; + + // bind the color texture + const colorTextures = framebuffer.colorTextures; + + let count = colorTextures.length; + + if (!this.drawBufferExtension) + { + count = Math.min(count, 1); + } + + const activeTextures = []; + + for (let i = 0; i < count; i++) + { + const texture = framebuffer.colorTextures[i]; + + if (texture.texturePart) + { + this.renderer.texture.bind(texture.texture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.COLOR_ATTACHMENT0 + i, + gl.TEXTURE_CUBE_MAP_NEGATIVE_X + texture.side, + texture.texture._glTextures[this.CONTEXT_UID].texture, + 0); + } + else + { + this.renderer.texture.bind(texture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.COLOR_ATTACHMENT0 + i, + gl.TEXTURE_2D, + texture._glTextures[this.CONTEXT_UID].texture, + 0); + } + + activeTextures.push(gl.COLOR_ATTACHMENT0 + i); + } + + if (this.drawBufferExtension && activeTextures.length > 1) + { + this.drawBufferExtension.drawBuffersWEBGL(activeTextures); + } + + if (framebuffer.depthTexture) + { + const depthTextureExt = this.renderer.context.extensions.depthTexture; + + if (depthTextureExt) + { + const depthTexture = framebuffer.depthTexture; + + this.renderer.texture.bind(depthTexture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.DEPTH_ATTACHMENT, + gl.TEXTURE_2D, + depthTexture._glTextures[this.CONTEXT_UID].texture, + 0); + } + } + + if (framebuffer.stencil || framebuffer.depth) + { + fbo.stencil = gl.createRenderbuffer(); + + gl.bindRenderbuffer(gl.RENDERBUFFER, fbo.stencil); + + // TODO.. this is depth AND stencil? + gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, fbo.stencil); + gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); + // fbo.enableStencil(); + } + } +} diff --git a/packages/core/src/geometry/GLBuffer.js b/packages/core/src/geometry/GLBuffer.js new file mode 100644 index 0000000..0477e8a --- /dev/null +++ b/packages/core/src/geometry/GLBuffer.js @@ -0,0 +1,9 @@ +export default class GLBuffer +{ + constructor(buffer) + { + this.buffer = buffer; + this.updateID = -1; + this.byteLength = -1; + } +} diff --git a/packages/core/src/geometry/GeometrySystem.js b/packages/core/src/geometry/GeometrySystem.js new file mode 100644 index 0000000..959f827 --- /dev/null +++ b/packages/core/src/geometry/GeometrySystem.js @@ -0,0 +1,388 @@ +import System from '../System'; +import GLBuffer from './GLBuffer'; + +const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class GeometrySystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this._activeGeometry = null; + this._activeVao = null; + + this.hasVao = true; + this.hasInstance = true; + } + + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + contextChange() + { + const gl = this.gl = this.renderer.gl; + + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + + // webgl2 + if (!gl.createVertexArray) + { + // webgl 1! + let nativeVaoExtension = this.renderer.context.extensions.vertexArrayObject; + + if (this.renderer.legacy) + { + nativeVaoExtension = null; + } + + if (nativeVaoExtension) + { + gl.createVertexArray = () => + nativeVaoExtension.createVertexArrayOES(); + + gl.bindVertexArray = (vao) => + nativeVaoExtension.bindVertexArrayOES(vao); + + gl.deleteVertexArray = (vao) => + nativeVaoExtension.deleteVertexArrayOES(vao); + } + else + { + this.hasVao = false; + gl.createVertexArray = () => + { + // empty + }; + + gl.bindVertexArray = () => + { + // empty + }; + + gl.deleteVertexArray = () => + { + // empty + }; + } + } + + if (!gl.vertexAttribDivisor) + { + const instanceExt = gl.getExtension('ANGLE_instanced_arrays'); + + if (instanceExt) + { + gl.vertexAttribDivisor = (a, b) => + instanceExt.vertexAttribDivisorANGLE(a, b); + + gl.drawElementsInstanced = (a, b, c, d, e) => + instanceExt.drawElementsInstancedANGLE(a, b, c, d, e); + + gl.drawArraysInstanced = (a, b, c, d) => + instanceExt.drawArraysInstancedANGLE(a, b, c, d); + } + else + { + this.hasInstance = false; + } + } + } + + /** + * Binds geometry so that is can be drawn. Creating a Vao if required + * @private + * @param {PIXI.Geometry} geometry instance of geometry to bind + */ + bind(geometry, shader) + { + shader = shader || this.renderer.shader.shader; + + const gl = this.gl; + + // not sure the best way to address this.. + // currently different shaders require different VAOs for the same geometry + // Still mulling over the best way to solve this one.. + // will likely need to modify the shader attribute locations at run time! + let vaos = geometry.glVertexArrayObjects[this.CONTEXT_UID]; + + if (!vaos) + { + geometry.glVertexArrayObjects[this.CONTEXT_UID] = vaos = {}; + } + + const vao = vaos[shader.program.id] || this.initGeometryVao(geometry, shader.program); + + this._activeGeometry = geometry; + + if (this._activeVao !== vao) + { + this._activeVao = vao; + + if (this.hasVao) + { + gl.bindVertexArray(vao); + } + else + { + this.activateVao(geometry, shader.program); + } + } + + // TODO - optimise later! + // don't need to loop through if nothing changed! + // maybe look to add an 'autoupdate' to geometry? + this.updateBuffers(); + } + + reset() + { + this.unbind(); + } + + updateBuffers() + { + const geometry = this._activeGeometry; + const gl = this.gl; + + for (let i = 0; i < geometry.buffers.length; i++) + { + const buffer = geometry.buffers[i]; + + const glBuffer = buffer._glBuffers[this.CONTEXT_UID]; + + if (buffer._updateID !== glBuffer.updateID) + { + glBuffer.updateID = buffer._updateID; + + // TODO can cache this on buffer! maybe added a getter / setter? + const type = buffer.index ? gl.ELEMENT_ARRAY_BUFFER : gl.ARRAY_BUFFER; + const drawType = buffer.static ? gl.STATIC_DRAW : gl.DYNAMIC_DRAW; + + gl.bindBuffer(type, glBuffer.buffer); + + if (glBuffer.byteLength >= buffer.data.byteLength) + { + // offset is always zero for now! + gl.bufferSubData(type, 0, buffer.data); + } + else + { + gl.bufferData(type, buffer.data, drawType); + } + } + } + } + + checkCompatability(geometry, program) + { + // geometry must have at least all the attributes that the shader requires. + const geometryAttributes = geometry.attributes; + const shaderAttributes = program.attributeData; + + for (const j in shaderAttributes) + { + if (!geometryAttributes[j]) + { + throw new Error(`shader and geometry incompatible, geometry missing the "${j}" attribute`); + } + } + } + + /** + * Creates a Vao with the same structure as the geometry and stores it on the geometry. + * @private + * @param {PIXI.Geometry} geometry instance of geometry to to generate Vao for + */ + initGeometryVao(geometry, program) + { + this.checkCompatability(geometry, program); + + const gl = this.gl; + const CONTEXT_UID = this.CONTEXT_UID; + const buffers = geometry.buffers; + const attributes = geometry.attributes; + + const tempStride = {}; + const tempStart = {}; + + for (const j in buffers) + { + tempStride[j] = 0; + tempStart[j] = 0; + } + + for (const j in attributes) + { + if (!attributes[j].size && program.attributeData[j]) + { + attributes[j].size = program.attributeData[j].size; + } + + tempStride[attributes[j].buffer] += attributes[j].size * byteSizeMap[attributes[j].type]; + } + + for (const j in attributes) + { + const attribute = attributes[j]; + const attribSize = attribute.size; + + if (attribute.stride === undefined) + { + if (tempStride[attribute.buffer] === attribSize * byteSizeMap[attribute.type]) + { + attribute.stride = 0; + } + else + { + attribute.stride = tempStride[attribute.buffer]; + } + } + + if (attribute.start === undefined) + { + attribute.start = tempStart[attribute.buffer]; + + tempStart[attribute.buffer] += attribSize * byteSizeMap[attribute.type]; + } + } + + // first update - and create the buffers! + // only create a gl buffer if it actually gets + for (let i = 0; i < buffers.length; i++) + { + const buffer = buffers[i]; + + if (!buffer._glBuffers[CONTEXT_UID]) + { + buffer._glBuffers[CONTEXT_UID] = new GLBuffer(gl.createBuffer()); + } + } + + // TODO - maybe make this a data object? + // lets wait to see if we need to first! + const vao = gl.createVertexArray(); + + gl.bindVertexArray(vao); + + this.activateVao(geometry, program); + + gl.bindVertexArray(null); + + // add it to the cache! + geometry.glVertexArrayObjects[this.CONTEXT_UID][program.id] = vao; + + return vao; + } + + activateVao(geometry, program) + { + const gl = this.gl; + const CONTEXT_UID = this.CONTEXT_UID; + const buffers = geometry.buffers; + const attributes = geometry.attributes; + + if (geometry.indexBuffer) + { + // first update the index buffer if we have one.. + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, geometry.indexBuffer._glBuffers[CONTEXT_UID].buffer); + } + + let lastBuffer = null; + + // add a new one! + for (const j in attributes) + { + const attribute = attributes[j]; + const buffer = buffers[attribute.buffer]; + const glBuffer = buffer._glBuffers[CONTEXT_UID]; + + if (program.attributeData[j]) + { + if (lastBuffer !== glBuffer) + { + gl.bindBuffer(gl.ARRAY_BUFFER, glBuffer.buffer); + + lastBuffer = glBuffer; + } + + const location = program.attributeData[j].location; + + // TODO introduce state again + // we can optimise this for older devices that have no VAOs + gl.enableVertexAttribArray(location); + + gl.vertexAttribPointer(location, + attribute.size, + attribute.type || gl.FLOAT, + attribute.normalized, + attribute.stride, + attribute.start); + + if (attribute.instance) + { + // TODO calculate instance count based of this... + if (this.hasInstance) + { + gl.vertexAttribDivisor(location, 1); + } + else + { + throw new Error('geometry error, GPU Instancing is not supported on this device'); + } + } + } + } + } + + draw(type, size, start, instanceCount) + { + const gl = this.gl; + const geometry = this._activeGeometry; + + // TODO.. this should not change so maybe cache the function? + + if (geometry.indexBuffer) + { + if (geometry.instanced) + { + /* eslint-disable max-len */ + gl.drawElementsInstanced(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2, instanceCount || 1); + /* eslint-enable max-len */ + } + else + { + gl.drawElements(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2); + } + } + else + if (geometry.instanced) + { + // TODO need a better way to calculate size.. + gl.drawArraysInstanced(type, start, size || geometry.getSize(), instanceCount || 1); + } + else + { + gl.drawArrays(type, start, size || geometry.getSize()); + } + + return this; + } + + unbind() + { + this.gl.bindVertexArray(null); + this._activeVao = null; + this._activeGeometry = null; + } +} diff --git a/packages/core/src/geometry/utils/setVertexAttribArrays.js b/packages/core/src/geometry/utils/setVertexAttribArrays.js new file mode 100644 index 0000000..23b6959 --- /dev/null +++ b/packages/core/src/geometry/utils/setVertexAttribArrays.js @@ -0,0 +1,54 @@ +// var GL_MAP = {}; + +/** + * @param gl {WebGLRenderingContext} The current WebGL context + * @param attribs {*} + * @param state {*} + */ +export default function setVertexAttribArrays(gl, attribs, state) +{ + let i; + + if (state) + { + const tempAttribState = state.tempAttribState; + const attribState = state.attribState; + + for (i = 0; i < tempAttribState.length; i++) + { + tempAttribState[i] = false; + } + + // set the new attribs + for (i = 0; i < attribs.length; i++) + { + tempAttribState[attribs[i].attribute.location] = true; + } + + for (i = 0; i < attribState.length; i++) + { + if (attribState[i] !== tempAttribState[i]) + { + attribState[i] = tempAttribState[i]; + + if (state.attribState[i]) + { + gl.enableVertexAttribArray(i); + } + else + { + gl.disableVertexAttribArray(i); + } + } + } + } + else + { + for (i = 0; i < attribs.length; i++) + { + const attrib = attribs[i]; + + gl.enableVertexAttribArray(attrib.attribute.location); + } + } +} diff --git a/packages/core/src/index.js b/packages/core/src/index.js index 58c456a..fd1f547 100644 --- a/packages/core/src/index.js +++ b/packages/core/src/index.js @@ -1,30 +1,30 @@ import * as resources from './textures/resources'; -import * as systems from './renderers/systems'; +import * as systems from './systems'; -export { resources }; export { systems }; +export { resources }; -export { default as Renderer } from './renderers/Renderer'; -export { default as AbstractRenderer } from './renderers/AbstractRenderer'; -export { default as FrameBuffer } from './textures/FrameBuffer'; +export { default as System } from './System'; +export { default as Renderer } from './Renderer'; +export { default as AbstractRenderer } from './AbstractRenderer'; +export { default as FrameBuffer } from './framebuffer/FrameBuffer'; export { default as CubeTexture } from './textures/CubeTexture'; export { default as BaseTexture } from './textures/BaseTexture'; export { default as Texture } from './textures/Texture'; export { default as TextureMatrix } from './textures/TextureMatrix'; -export { default as RenderTexture } from './textures/RenderTexture'; -export { default as BaseRenderTexture } from './textures/BaseRenderTexture'; +export { default as RenderTexture } from './renderTexture/RenderTexture'; +export { default as BaseRenderTexture } from './renderTexture/BaseRenderTexture'; export { default as TextureUvs } from './textures/TextureUvs'; -export { default as State } from './renderers/State'; -export { default as ObjectRenderer } from './renderers/utils/ObjectRenderer'; -export { default as Quad } from './renderers/utils/Quad'; -export { default as QuadUv } from './renderers/utils/QuadUv'; -export { default as checkMaxIfStatmentsInShader } from './renderers/utils/checkMaxIfStatmentsInShader'; +export { default as State } from './state/State'; +export { default as ObjectRenderer } from './batch/ObjectRenderer'; +export { default as Quad } from './utils/Quad'; +export { default as QuadUv } from './utils/QuadUv'; +export { default as checkMaxIfStatmentsInShader } from './shader/utils/checkMaxIfStatmentsInShader'; export { default as Shader } from './shader/Shader'; export { default as Program } from './shader/Program'; export { default as UniformGroup } from './shader/UniformGroup'; -export { default as SpriteMaskFilter } from './renderers/filters/spriteMask/SpriteMaskFilter'; -export { default as Filter } from './renderers/filters/Filter'; +export { default as SpriteMaskFilter } from './filters/spriteMask/SpriteMaskFilter'; +export { default as Filter } from './filters/Filter'; export { default as Attribute } from './geometry/Attribute'; export { default as Buffer } from './geometry/Buffer'; export { default as Geometry } from './geometry/Geometry'; - diff --git a/packages/core/src/mask/MaskSystem.js b/packages/core/src/mask/MaskSystem.js new file mode 100644 index 0000000..99174c2 --- /dev/null +++ b/packages/core/src/mask/MaskSystem.js @@ -0,0 +1,199 @@ +import System from '../System'; +import AlphaMaskFilter from '../filters/spriteMask/SpriteMaskFilter'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class MaskSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + // TODO - we don't need both! + this.scissor = false; + this.scissorData = null; + this.scissorRenderTarget = null; + + this.enableScissor = false; + + this.alphaMaskPool = []; + this.alphaMaskIndex = 0; + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.DisplayObject} target - Display Object to push the mask to + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + push(target, maskData) + { + // TODO the root check means scissor rect will not + // be used on render textures more info here: + // https://github.com/pixijs/pixi.js/pull/3545 + + if (maskData.texture) + { + this.pushSpriteMask(target, maskData); + } + else if (this.enableScissor + && !this.scissor + && this.renderer._activeRenderTarget.root + && !this.renderer.stencil.stencilMaskStack.length + && maskData.isFastRect()) + { + const matrix = maskData.worldTransform; + + let rot = Math.atan2(matrix.b, matrix.a); + + // use the nearest degree! + rot = Math.round(rot * (180 / Math.PI)); + + if (rot % 90) + { + this.pushStencilMask(maskData); + } + else + { + this.pushScissorMask(target, maskData); + } + } + else + { + this.pushStencilMask(maskData); + } + } + + /** + * Removes the last mask from the mask stack and doesn't return it. + * + * @param {PIXI.DisplayObject} target - Display Object to pop the mask from + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + pop(target, maskData) + { + if (maskData.texture) + { + this.popSpriteMask(target, maskData); + } + else if (this.enableScissor && !this.renderer.stencil.stencilMaskStack.length) + { + this.popScissorMask(target, maskData); + } + else + { + this.popStencilMask(target, maskData); + } + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.RenderTarget} target - Display Object to push the sprite mask to + * @param {PIXI.Sprite} maskData - Sprite to be used as the mask + */ + pushSpriteMask(target, maskData) + { + let alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; + + if (!alphaMaskFilter) + { + alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; + } + + alphaMaskFilter[0].resolution = this.renderer.resolution; + alphaMaskFilter[0].maskSprite = maskData; + + // TODO - may cause issues! + target.filterArea = maskData.getBounds(true); + + this.renderer.filterSystem.pushFilter(target, alphaMaskFilter); + + this.alphaMaskIndex++; + } + + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popSpriteMask() + { + this.renderer.filterSystem.popFilter(); + this.alphaMaskIndex--; + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + pushStencilMask(maskData) + { + this.renderer.batch.flush(); + this.renderer.stencil.pushStencil(maskData); + } + + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popStencilMask() + { + // this.renderer.currentRenderer.stop(); + this.renderer.stencil.popStencil(); + } + + /** + * + * @param {PIXI.DisplayObject} target - Display Object to push the mask to + * @param {PIXI.Graphics} maskData - The masking data. + */ + pushScissorMask(target, maskData) + { + maskData.renderable = true; + + const renderTarget = this.renderer._activeRenderTarget; + + const bounds = maskData.getBounds(); + + bounds.fit(renderTarget.size); + maskData.renderable = false; + + this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); + + const resolution = this.renderer.resolution; + + this.renderer.gl.scissor( + bounds.x * resolution, + (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, + bounds.width * resolution, + bounds.height * resolution + ); + + this.scissorRenderTarget = renderTarget; + this.scissorData = maskData; + this.scissor = true; + } + + /** + * + * + */ + popScissorMask() + { + this.scissorRenderTarget = null; + this.scissorData = null; + this.scissor = false; + + // must be scissor! + const gl = this.renderer.gl; + + gl.disable(gl.SCISSOR_TEST); + } +} diff --git a/packages/core/src/mask/StencilSystem.js b/packages/core/src/mask/StencilSystem.js new file mode 100644 index 0000000..62796a5 --- /dev/null +++ b/packages/core/src/mask/StencilSystem.js @@ -0,0 +1,133 @@ +import System from '../System'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class StencilSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + this.stencilMaskStack = []; + } + + /** + * Changes the mask stack that is used by this System. + * + * @param {PIXI.Graphics[]} stencilMaskStack - The mask stack + */ + setMaskStack(stencilMaskStack) + { + const gl = this.renderer.gl; + + if (stencilMaskStack.length !== this.stencilMaskStack.length) + { + if (stencilMaskStack.length === 0) + { + gl.disable(gl.STENCIL_TEST); + } + else + { + gl.enable(gl.STENCIL_TEST); + } + } + + this.stencilMaskStack = stencilMaskStack; + } + + /** + * Applies the Mask and adds it to the current stencil stack. @alvin + * + * @param {PIXI.Graphics} graphics - The mask + */ + pushStencil(graphics) + { + this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); + + // this.renderer._activeRenderTarget.attachStencilBuffer(); + + const gl = this.renderer.gl; + const prevMaskCount = this.stencilMaskStack.length; + + if (prevMaskCount === 0) + { + gl.enable(gl.STENCIL_TEST); + } + + this.stencilMaskStack.push(graphics); + + // Increment the refference stencil value where the new mask overlaps with the old ones. + gl.colorMask(false, false, false, false); + gl.stencilFunc(gl.EQUAL, prevMaskCount, this._getBitwiseMask()); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.INCR); + this.renderer.plugins.graphics.render(graphics); + + this._useCurrent(); + } + + /** + * Removes the last mask from the stencil stack. @alvin + */ + popStencil() + { + this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); + + const gl = this.renderer.gl; + const graphics = this.stencilMaskStack.pop(); + + if (this.stencilMaskStack.length === 0) + { + // the stack is empty! + gl.disable(gl.STENCIL_TEST); + gl.clear(gl.STENCIL_BUFFER_BIT); + gl.clearStencil(0); + } + else + { + // Decrement the refference stencil value where the popped mask overlaps with the other ones + gl.colorMask(false, false, false, false); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.DECR); + this.renderer.plugins.graphics.render(graphics); + + this._useCurrent(); + } + } + + /** + * Setup renderer to use the current stencil data. + */ + _useCurrent() + { + const gl = this.renderer.gl; + + gl.colorMask(true, true, true, true); + gl.stencilFunc(gl.EQUAL, this.stencilMaskStack.length, this._getBitwiseMask()); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP); + } + + /** + * Fill 1s equal to the number of acitve stencil masks. + * + * @return {number} The bitwise mask. + */ + _getBitwiseMask() + { + return (1 << this.stencilMaskStack.length) - 1; + } + + /** + * Destroys the mask stack. + * + */ + destroy() + { + super.destroy(this); + + this.stencilMaskStack = null; + } +} diff --git a/packages/core/src/projection/ProjectionSystem.js b/packages/core/src/projection/ProjectionSystem.js new file mode 100644 index 0000000..6e63b73 --- /dev/null +++ b/packages/core/src/projection/ProjectionSystem.js @@ -0,0 +1,73 @@ +import System from '../System'; +import { Matrix } from '@pixi/math'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class ProjectionSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.projectionMatrix = new Matrix(); + } + + update(destinationFrame, sourceFrame, resolution, root) + { + this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; + this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; + + this.calculateProjection(this.destinationFrame, this.sourceFrame, resolution, root); + + this.renderer.globalUniforms.uniforms.projectionMatrix = this.projectionMatrix; + this.renderer.globalUniforms.update(); + } + + /** + * Updates the projection matrix based on a projection frame (which is a rectangle) + * + * @param {Rectangle} destinationFrame - The destination frame. + * @param {Rectangle} sourceFrame - The source frame. + */ + calculateProjection(destinationFrame, sourceFrame, resolution, root) + { + const pm = this.projectionMatrix; + + // I don't think we will need this line.. + // pm.identity(); + + if (!root) + { + pm.a = (1 / destinationFrame.width * 2) * resolution; + pm.d = (1 / destinationFrame.height * 2) * resolution; + + pm.tx = -1 - (sourceFrame.x * pm.a); + pm.ty = -1 - (sourceFrame.y * pm.d); + } + else + { + pm.a = (1 / destinationFrame.width * 2) * resolution; + pm.d = (-1 / destinationFrame.height * 2) * resolution; + + pm.tx = -1 - (sourceFrame.x * pm.a); + pm.ty = 1 - (sourceFrame.y * pm.d); + } + } + + /** + * Sets the transform of the active render target to the given matrix + * + * @param {PIXI.Matrix} matrix - The transformation matrix + */ + setTransform()// matrix) + { + // this._activeRenderTarget.transform = matrix; + } +} diff --git a/packages/core/src/renderTexture/BaseRenderTexture.js b/packages/core/src/renderTexture/BaseRenderTexture.js new file mode 100644 index 0000000..88eb83b --- /dev/null +++ b/packages/core/src/renderTexture/BaseRenderTexture.js @@ -0,0 +1,137 @@ +import BaseTexture from '../textures/BaseTexture'; +import FrameBuffer from '../framebuffer/FrameBuffer'; + +/** + * A BaseRenderTexture is a special texture that allows any PixiJS display object to be rendered to it. + * + * __Hint__: All DisplayObjects (i.e. Sprites) that render to a BaseRenderTexture should be preloaded + * otherwise black rectangles will be drawn instead. + * + * A BaseRenderTexture takes a snapshot of any Display Object given to its render method. The position + * and rotation of the given Display Objects is ignored. For example: + * + * ```js + * let renderer = PIXI.autoDetectRenderer(1024, 1024, { view: canvas, ratio: 1 }); + * let baseRenderTexture = new PIXI.BaseRenderTexture(renderer, 800, 600); + * let sprite = PIXI.Sprite.fromImage("spinObj_01.png"); + * + * sprite.position.x = 800/2; + * sprite.position.y = 600/2; + * sprite.anchor.x = 0.5; + * sprite.anchor.y = 0.5; + * + * baseRenderTexture.render(sprite); + * ``` + * + * The Sprite in this case will be rendered using its local transform. To render this sprite at 0,0 + * you can clear the transform + * + * ```js + * + * sprite.setTransform() + * + * let baseRenderTexture = new PIXI.BaseRenderTexture(100, 100); + * let renderTexture = new PIXI.RenderTexture(baseRenderTexture); + * + * renderer.render(sprite, renderTexture); // Renders to center of RenderTexture + * ``` + * + * @class + * @extends PIXI.BaseTexture + * @memberof PIXI + */ +export default class BaseRenderTexture extends BaseTexture +{ + /** + * @param {object} [options] + * @param {number} [options.width=100] - The width of the base render texture + * @param {number} [options.height=100] - The height of the base render texture + * @param {PIXI.SCALE_MODES} [options.scaleMode] - See {@link PIXI.SCALE_MODES} for possible values + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the texture being generated + */ + constructor(options) + { + if (typeof options === 'number') + { + /* eslint-disable prefer-rest-params */ + // Backward compatibility of signature + const width = arguments[0]; + const height = arguments[1]; + const scaleMode = arguments[2]; + const resolution = arguments[3]; + + options = { width, height, scaleMode, resolution }; + /* eslint-enable prefer-rest-params */ + } + + super(null, options); + + const { width, height } = options || {}; + + // Set defaults + this.mipmap = false; + this.width = Math.ceil(width) || 100; + this.height = Math.ceil(height) || 100; + this.valid = true; + + /** + * A map of renderer IDs to webgl renderTargets + * + * @private + * @member {object} + */ + // this._glRenderTargets = {}; + + /** + * A reference to the canvas render target (we only need one as this can be shared across renderers) + * + * @private + * @member {object} + */ + this._canvasRenderTarget = null; + + this.clearColor = [0, 0, 0, 0]; + + this.frameBuffer = new FrameBuffer(width * this.resolution, height * this.resolution) + .addColorTexture(0, this); + + // TODO - could this be added the systems? + + /** + * The data structure for the stencil masks + * + * @member {PIXI.Graphics[]} + */ + this.stencilMaskStack = []; + + /** + * The data structure for the filters + * + * @member {PIXI.Graphics[]} + */ + this.filterStack = [{}]; + } + + /** + * Resizes the BaseRenderTexture. + * + * @param {number} width - The width to resize to. + * @param {number} height - The height to resize to. + */ + resize(width, height) + { + width = Math.ceil(width); + height = Math.ceil(height); + this.frameBuffer.resize(width * this.resolution, height * this.resolution); + } + + /** + * Destroys this texture + * + */ + destroy() + { + super.destroy(true); + this.renderer = null; + } +} diff --git a/packages/core/src/renderTexture/RenderTexture.js b/packages/core/src/renderTexture/RenderTexture.js new file mode 100644 index 0000000..f1ecf1f --- /dev/null +++ b/packages/core/src/renderTexture/RenderTexture.js @@ -0,0 +1,153 @@ +import BaseRenderTexture from './BaseRenderTexture'; +import Texture from '../textures/Texture'; + +/** + * A RenderTexture is a special texture that allows any PixiJS display object to be rendered to it. + * + * __Hint__: All DisplayObjects (i.e. Sprites) that render to a RenderTexture should be preloaded + * otherwise black rectangles will be drawn instead. + * + * A RenderTexture takes a snapshot of any Display Object given to its render method. For example: + * + * ```js + * let renderer = PIXI.autoDetectRenderer(1024, 1024, { view: canvas, ratio: 1 }); + * let renderTexture = PIXI.RenderTexture.create(800, 600); + * let sprite = PIXI.Sprite.fromImage("spinObj_01.png"); + * + * sprite.position.x = 800/2; + * sprite.position.y = 600/2; + * sprite.anchor.x = 0.5; + * sprite.anchor.y = 0.5; + * + * renderer.render(sprite, renderTexture); + * ``` + * + * The Sprite in this case will be rendered using its local transform. To render this sprite at 0,0 + * you can clear the transform + * + * ```js + * + * sprite.setTransform() + * + * let renderTexture = new PIXI.RenderTexture.create(100, 100); + * + * renderer.render(sprite, renderTexture); // Renders to center of RenderTexture + * ``` + * + * @class + * @extends PIXI.Texture + * @memberof PIXI + */ +export default class RenderTexture extends Texture +{ + /** + * @param {PIXI.BaseRenderTexture} baseRenderTexture - The renderer used for this RenderTexture + * @param {PIXI.Rectangle} [frame] - The rectangle frame of the texture to show + */ + constructor(baseRenderTexture, frame) + { + // support for legacy.. + let _legacyRenderer = null; + + if (!(baseRenderTexture instanceof BaseRenderTexture)) + { + /* eslint-disable prefer-rest-params, no-console */ + const width = arguments[1]; + const height = arguments[2]; + const scaleMode = arguments[3]; + const resolution = arguments[4]; + + // we have an old render texture.. + console.warn(`Please use RenderTexture.create(${width}, ${height}) instead of the ctor directly.`); + _legacyRenderer = arguments[0]; + /* eslint-enable prefer-rest-params, no-console */ + + frame = null; + baseRenderTexture = new BaseRenderTexture({ + width, + height, + scaleMode, + resolution, + }); + } + + /** + * The base texture object that this texture uses + * + * @member {BaseTexture} + */ + super(baseRenderTexture, frame); + + this.legacyRenderer = _legacyRenderer; + + /** + * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. + * + * @member {boolean} + */ + this.valid = true; + + /** + * FilterSystem temporary storage + * @private + * @member {PIXI.Rectangle} + */ + this.filterFrame = null; + + this._updateUvs(); + } + + /** + * Resizes the RenderTexture. + * + * @param {number} width - The width to resize to. + * @param {number} height - The height to resize to. + * @param {boolean} [resizeBaseTexture=true] - Should the baseTexture.width and height values be resized as well? + */ + resize(width, height, resizeBaseTexture = true) + { + width = Math.ceil(width); + height = Math.ceil(height); + + // TODO - could be not required.. + this.valid = (width > 0 && height > 0); + + this._frame.width = this.orig.width = width; + this._frame.height = this.orig.height = height; + + if (resizeBaseTexture) + { + this.baseTexture.resize(width, height); + } + + this._updateUvs(); + } + + /** + * A short hand way of creating a render texture. + * + * @param {object} [options] - Options + * @param {number} [options.width=100] - The width of the render texture + * @param {number} [options.height=100] - The height of the render texture + * @param {number} [options.scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the texture being generated + * @return {PIXI.RenderTexture} The new render texture + */ + static create(options) + { + // fallback, old-style: create(width, height, scaleMode, resolution) + if (typeof options === 'number') + { + /* eslint-disable prefer-rest-params */ + options = { + width: options, + height: arguments[1], + scaleMode: arguments[2], + resolution: arguments[3], + }; + /* eslint-enable prefer-rest-params */ + } + + return new RenderTexture(new BaseRenderTexture(options)); + } +} diff --git a/packages/core/src/renderTexture/RenderTextureSystem.js b/packages/core/src/renderTexture/RenderTextureSystem.js new file mode 100644 index 0000000..8d0ca61 --- /dev/null +++ b/packages/core/src/renderTexture/RenderTextureSystem.js @@ -0,0 +1,115 @@ +import System from '../System'; +import { Rectangle } from '@pixi/math'; + +const tempRect = new Rectangle(); + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class RenderTextureSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.clearColor = renderer._backgroundColorRgba; + + // TODO moe this property somewhere else! + this.defaultMaskStack = []; + this.defaultFilterStack = [{}]; + + // empty render texture? + this.renderTexture = null; + + this.destinationFrame = new Rectangle(); + } + + bind(renderTexture, sourceFrame, destinationFrame) + { + // TODO - do we want this?? + if (this.renderTexture === renderTexture) return; + this.renderTexture = renderTexture; + + const renderer = this.renderer; + + if (renderTexture) + { + const baseTexture = renderTexture.baseTexture; + + if (!destinationFrame) + { + tempRect.width = baseTexture.realWidth; + tempRect.height = baseTexture.realHeight; + + destinationFrame = tempRect; + } + + if (!sourceFrame) + { + sourceFrame = destinationFrame; + } + + this.renderer.framebuffer.bind(baseTexture.frameBuffer, destinationFrame); + + this.renderer.projection.update(destinationFrame, sourceFrame, baseTexture.resolution, false); + this.renderer.stencil.setMaskStack(baseTexture.stencilMaskStack); + } + else + { + // TODO these validation checks happen deeper down.. + // thing they can be avoided.. + if (!destinationFrame) + { + tempRect.width = renderer.width; + tempRect.height = renderer.height; + + destinationFrame = tempRect; + } + + if (!sourceFrame) + { + sourceFrame = destinationFrame; + } + + renderer.framebuffer.bind(null, destinationFrame); + + // TODO store this.. + this.renderer.projection.update(destinationFrame, sourceFrame, this.renderer.resolution, true); + this.renderer.stencil.setMaskStack(this.defaultMaskStack); + } + + this.destinationFrame.copyFrom(destinationFrame); + } + + /** + * Erases the render texture and fills the drawing area with a colour + * + * @param {number} [clearColor] - The colour + * @return {PIXI.Renderer} Returns itself. + */ + clear(clearColor) + { + if (this.renderTexture) + { + clearColor = clearColor || this.renderTexture.baseTexture.clearColor; + } + else + { + clearColor = clearColor || this.clearColor; + } + + this.renderer.framebuffer.clear(clearColor[0], clearColor[1], clearColor[2], clearColor[3]); + } + + resize()// screenWidth, screenHeight) + { + // resize the root only! + this.bind(null); + } +} diff --git a/packages/core/src/renderers/AbstractRenderer.js b/packages/core/src/renderers/AbstractRenderer.js deleted file mode 100644 index 928670f..0000000 --- a/packages/core/src/renderers/AbstractRenderer.js +++ /dev/null @@ -1,353 +0,0 @@ -import { hex2string, hex2rgb } from '@pixi/utils'; -import { Matrix, Rectangle } from '@pixi/math'; -import { RENDERER_TYPE } from '@pixi/constants'; -import { settings } from '@pixi/settings'; -import { Container } from '@pixi/display'; -import RenderTexture from '../textures/RenderTexture'; -import EventEmitter from 'eventemitter3'; - -const tempMatrix = new Matrix(); - -/** - * 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 EventEmitter - * @memberof PIXI - */ -export default class AbstractRenderer extends EventEmitter -{ - // eslint-disable-next-line valid-jsdoc - /** - * @param {string} system - The name of the system this renderer is for. - * @param {object} [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.transparent=false] - If the render view is transparent, default false - * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false - * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) - * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. The - * resolution of the renderer retina would be 2. - * @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 {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when rendering, - * stopping pixel interpolation. - */ - constructor(system, options, arg2, arg3) - { - super(); - - // Support for constructor(system, screenWidth, screenHeight, options) - if (typeof options === 'number') - { - options = Object.assign({ - width: options, - height: arg2 || settings.RENDER_OPTIONS.height, - }, arg3); - } - - // 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 = RENDERER_TYPE.UNKNOWN; - - /** - * Measurements of the screen. (0, 0, screenWidth, screenHeight) - * - * Its safe to use as filterArea or hitArea for 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 1 - */ - this.resolution = options.resolution || settings.RESOLUTION; - - /** - * Whether the render view is transparent - * - * @member {boolean} - */ - this.transparent = options.transparent; - - /** - * Whether css dimensions of canvas view should be resized to screen dimensions automatically - * - * @member {boolean} - */ - this.autoResize = options.autoResize || false; - - /** - * Tracks the blend modes useful for this renderer. - * - * @member {object} - */ - this.blendModes = null; - - /** - * 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; - - /** - * If true PixiJS will Math.floor() x/y values when rendering, stopping pixel interpolation. - * Handy for crisp pixel art and speed on legacy devices. - * - * @member {boolean} - */ - this.roundPixels = options.roundPixels; - - /** - * The background color as a number. - * - * @member {number} - * @private - */ - this._backgroundColor = 0x000000; - - /** - * The background color as an [R, G, B] array. - * - * @member {number[]} - * @private - */ - this._backgroundColorRgba = [0, 0, 0, 0]; - - /** - * The background color as a string. - * - * @member {string} - * @private - */ - this._backgroundColorString = '#000000'; - - this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter - - /** - * This temporary display object used as the parent of the currently being rendered item - * - * @member {PIXI.DisplayObject} - * @private - */ - this._tempDisplayObjectParent = new Container(); - - /** - * The last root object that the renderer tried to render. - * - * @member {PIXI.DisplayObject} - * @private - */ - this._lastObjectRendered = this._tempDisplayObjectParent; - - /** - * Collection of plugins. - * @readonly - * @member {object} - */ - this.plugins = {}; - } - - /** - * Initialize the plugins. - * - * @protected - * @param {object} staticMap - The dictionary of staticly saved plugins. - */ - initPlugins(staticMap) - { - 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() - { - return this.view.width; - } - - /** - * Same as view.height, actual number of pixels in the canvas by vertical - * - * @member {number} - * @readonly - * @default 600 - */ - get height() - { - return this.view.height; - } - - /** - * Resizes the screen and canvas to the specified width and height - * Canvas dimensions are multiplied by resolution - * - * @param {number} screenWidth - the new width of the screen - * @param {number} screenHeight - the new height of the screen - */ - resize(screenWidth, screenHeight) - { - this.screen.width = screenWidth; - this.screen.height = screenHeight; - - this.view.width = screenWidth * this.resolution; - this.view.height = screenHeight * this.resolution; - - if (this.autoResize) - { - this.view.style.width = `${screenWidth}px`; - this.view.style.height = `${screenHeight}px`; - } - } - - /** - * 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 {PIXI.DisplayObject} displayObject - The displayObject the object will be generated from - * @param {number} scaleMode - Should be one of the scaleMode consts - * @param {number} resolution - The resolution / device pixel ratio of the texture being generated - * @param {PIXI.Rectangle} [region] - The region of the displayObject, that shall be rendered, - * if no region is specified, defaults to the local bounds of the displayObject. - * @return {PIXI.Texture} a texture of the graphics object - */ - generateTexture(displayObject, scaleMode, resolution, region) - { - region = region || displayObject.getLocalBounds(); - - // 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(region.width | 0, region.height | 0, scaleMode, resolution); - - tempMatrix.tx = -region.x; - tempMatrix.ty = -region.y; - - this.render(displayObject, renderTexture, false, tempMatrix, true); - - return renderTexture; - } - - /** - * Removes everything from the renderer and optionally removes the Canvas DOM element. - * - * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. - */ - destroy(removeView) - { - 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); - } - - this.plugins = null; - - this.type = RENDERER_TYPE.UNKNOWN; - - this.view = null; - - this.screen = null; - - this.resolution = 0; - - this.transparent = false; - - this.autoResize = false; - - this.blendModes = null; - - this.options = null; - - this.preserveDrawingBuffer = false; - this.clearBeforeRender = false; - - this.roundPixels = false; - - this._backgroundColor = 0; - this._backgroundColorRgba = null; - this._backgroundColorString = null; - - this._tempDisplayObjectParent = null; - this._lastObjectRendered = null; - } - - /** - * The background color to fill if not transparent - * - * @member {number} - */ - get backgroundColor() - { - return this._backgroundColor; - } - - set backgroundColor(value) // eslint-disable-line require-jsdoc - { - this._backgroundColor = value; - this._backgroundColorString = hex2string(value); - hex2rgb(value, this._backgroundColorRgba); - } -} diff --git a/packages/core/src/renderers/Renderer.js b/packages/core/src/renderers/Renderer.js deleted file mode 100644 index a2020b1..0000000 --- a/packages/core/src/renderers/Renderer.js +++ /dev/null @@ -1,329 +0,0 @@ -import AbstractRenderer from './AbstractRenderer'; -import { sayHello } from '@pixi/utils'; -import MaskSystem from './systems/MaskSystem'; -import StencilSystem from './systems/StencilSystem'; -import FilterSystem from './systems/filter/FilterSystem'; -import FramebufferSystem from './systems/FramebufferSystem'; -import RenderTextureSystem from './systems/RenderTextureSystem'; -import TextureSystem from './systems/textures/TextureSystem'; -import ProjectionSystem from './systems/ProjectionSystem'; -import StateSystem from './systems/StateSystem'; -import GeometrySystem from './systems/geometry/GeometrySystem'; -import ShaderSystem from './systems/shader/ShaderSystem'; -import ContextSystem from './systems/ContextSystem'; -import BatchSystem from './systems/BatchSystem'; -import TextureGCSystem from './systems/textures/TextureGCSystem'; -import { RENDERER_TYPE } from '@pixi/constants'; -import UniformGroup from '../shader/UniformGroup'; -import { Matrix } from '@pixi/math'; -import Runner from 'mini-runner'; - -/** - * 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 Render works by automatically managing webGLBatchs. - * 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 :) - * - * @class - * @memberof PIXI - * @extends PIXI.AbstractRenderer - */ -export default class Renderer extends AbstractRenderer -{ - // eslint-disable-next-line valid-jsdoc - /** - * - * @param {object} [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.transparent=false] - If the render view is transparent, default false - * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false - * @param {boolean} [options.antialias=false] - sets antialias. If not available natively then FXAA - * antialiasing is used - * @param {boolean} [options.forceFXAA=false] - forces FXAA antialiasing to be used over native. - * FXAA is faster, but may not always look as great - * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. - * The resolution of the renderer retina would be 2. - * @param {boolean} [options.clearBeforeRender=true] - This sets if the renderer will clear - * the canvas or not before the new render pass. If you wish to set this to false, you *must* set - * preserveDrawingBuffer to `true`. - * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, - * enable this if you need to call toDataUrl on the webgl context. - * @param {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when - * rendering, stopping pixel interpolation. - * @param {number} [options.backgroundColor=0x000000] - The background color of the rendered area - * (shown if not transparent). - * @param {boolean} [options.legacy=false] - If true PixiJS will aim to ensure compatibility - * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. - * @param {string} [options.powerPreference] - Parameter passed to webgl context, set to "high-performance" - * for devices with dual graphics card - */ - constructor(options, arg2, arg3) - { - super('WebGL', options, arg2, arg3); - - /** - * The type of this renderer as a standardised const - * - * @member {number} - * @see PIXI.RENDERER_TYPE - */ - this.type = RENDERER_TYPE.WEBGL; - - // this will be set by the contextSystem (this.context) - this.gl = null; - this.CONTEXT_UID = 0; - this.legacy = !!options.legacy; - - // TODO legacy! - - // runners! - this.runners = { - destroy: new Runner('destroy'), - contextChange: new Runner('contextChange', 1), - reset: new Runner('reset'), - update: new Runner('update'), - postrender: new Runner('postrender'), - prerender: new Runner('prerender'), - resize: new Runner('resize', 2), - }; - - 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(GeometrySystem, 'geometry') - .addSystem(FramebufferSystem, 'framebuffer') - .addSystem(StencilSystem, 'stencil') - .addSystem(ProjectionSystem, 'projection') - .addSystem(TextureGCSystem, 'textureGC') - .addSystem(FilterSystem, 'filter') - .addSystem(RenderTextureSystem, 'renderTexture') - .addSystem(BatchSystem, 'batch'); - - this.initPlugins(Renderer.__plugins); - - /** - * The options passed in to create a new webgl context. - * - * @member {object} - * @private - */ - if (options.context) - { - this.context.initFromContext(options.context); - } - else - { - this.context.initFromOptions({ - alpha: this.transparent, - antialias: options.antialias, - premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', - stencil: true, - 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); - } - - addSystem(_class, name) - { - if (!name) - { - name = _class.name; - } - - // TODO - read name from class.name.. - - /* - if(name.includes('System')) - { - name = name.replace('System', ''); - name = name.charAt(0).toLowerCase() + name.slice(1); - } - */ - - const system = new _class(this); - - if (this[name]) - { - throw new Error(`Whoops! ${name} is already a manger`); - } - - this[name] = system; - - for (const i in this.runners) - { - this.runners[i].add(system); - } - - return this; - - /** - * 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. - */ - } - - /** - * Renders the object to its webGL view - * - * @param {PIXI.DisplayObject} displayObject - the object to be rendered - * @param {PIXI.RenderTexture} renderTexture - The render texture to render to. - * @param {boolean} [clear] - Should the canvas be cleared before the new render - * @param {PIXI.Transform} [transform] - A transform to apply to the render texture before rendering. - * @param {boolean} [skipUpdateTransform] - Should we skip the update transform pass? - */ - render(displayObject, renderTexture, clear, transform, skipUpdateTransform) - { - // can be handy to know! - this.renderingToScreen = !renderTexture; - - this.runners.prerender.run(); - this.emit('prerender'); - - // 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.parent; - - displayObject.parent = this._tempDisplayObjectParent; - displayObject.updateTransform(); - displayObject.parent = 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.run(); - - this.emit('postrender'); - } - - /** - * Resizes the webGL view to the specified width and height. - * - * @param {number} screenWidth - the new width of the screen - * @param {number} screenHeight - the new height of the screen - */ - resize(screenWidth, screenHeight) - { - AbstractRenderer.prototype.resize.call(this, screenWidth, screenHeight); - - this.runners.resize.run(screenWidth, screenHeight); - } - - /** - * Resets the WebGL state so you can render things however you fancy! - * - * @return {PIXI.Renderer} Returns itself. - */ - reset() - { - this.runners.reset.run(); - - return this; - } - - clear() - { - this.framebuffer.bind(); - this.framebuffer.clear(); - } - - /** - * Removes everything from the renderer (event listeners, spritebatch, etc...) - * - * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. - * See: https://github.com/pixijs/pixi.js/issues/2233 - */ - destroy(removeView) - { - this.runners.destroy.run(); - - // call base destroy - super.destroy(removeView); - - // TODO nullify all the managers.. - this.gl = null; - } - - /** - * 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 - * builds and excluding plugins. - * @name PIXI.Renderer#plugins - * @type {object} - * @readonly - * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. - * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. - * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. - * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. - */ - - /** - * Adds a plugin to the renderer. - * - * @method - * @param {string} pluginName - The name of the plugin. - * @param {Function} ctor - The constructor function or class for the plugin. - */ - static registerPlugin(pluginName, ctor) - { - Renderer.__plugins = Renderer.__plugins || {}; - Renderer.__plugins[pluginName] = ctor; - } -} diff --git a/packages/core/src/renderers/State.js b/packages/core/src/renderers/State.js deleted file mode 100644 index fa3f221..0000000 --- a/packages/core/src/renderers/State.js +++ /dev/null @@ -1,163 +0,0 @@ -/* eslint-disable max-len */ - -const BLEND = 0; -const OFFSET = 1; -const CULLING = 2; -const DEPTH_TEST = 3; -const WINDING = 4; - -/** - * This is a webGL state. It is passed The WebGL StateManager. - * Each mesh renderered may require webGL to be in a different state. - * For example you may want different blend mode or to enable polygon offsets - * - * @class - * @memberof PIXI - */ -export default class State -{ - /** - * - */ - constructor() - { - this.data = 0; - - this.blendMode = 0; - this.polygonOffset = 0; - - this.blend = true; - // this.depthTest = true; - } - - /** - * Activates blending of the computed fragment color values - * - * @member {boolean} - */ - get blend() - { - return !!(this.data & (1 << BLEND)); - } - - set blend(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << BLEND)) !== value) - { - this.data ^= (1 << BLEND); - } - } - - /** - * Activates adding an offset to depth values of polygon's fragments - * - * @member {boolean} - * @default false - */ - get offsets() - { - return !!(this.data & (1 << OFFSET)); - } - - set offsets(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << OFFSET)) !== value) - { - this.data ^= (1 << OFFSET); - } - } - - /** - * Activates culling of polygons. - * - * @member {boolean} - * @default false - */ - get culling() - { - return !!(this.data & (1 << CULLING)); - } - - set culling(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << CULLING)) !== value) - { - this.data ^= (1 << CULLING); - } - } - - /** - * Activates depth comparisons and updates to the depth buffer. - * - * @member {boolean} - * @default false - */ - get depthTest() - { - return !!(this.data & (1 << DEPTH_TEST)); - } - - set depthTest(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << DEPTH_TEST)) !== value) - { - this.data ^= (1 << DEPTH_TEST); - } - } - - /** - * Specifies whether or not front or back-facing polygons can be culled. - * @member {boolean} - * @default false - */ - get clockwiseFrontFace() - { - return !!(this.data & (1 << WINDING)); - } - - set clockwiseFrontFace(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << WINDING)) !== value) - { - this.data ^= (1 << WINDING); - } - } - - /** - * The blend mode to be applied when this state is set. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. - * Setting this mode to anything other than NO_BLEND will automatically switch blending on. - * - * @member {boolean} - * @default PIXI.BLEND_MODES.NORMAL - * @see PIXI.BLEND_MODES - */ - get blendMode() - { - return this._blendMode; - } - - set blendMode(value) // eslint-disable-line require-jsdoc - { - // 17 is NO BLEND - this.blend = (value !== 17); - this._blendMode = value; - } - - /** - * The polygon offset. Setting this property to anything other than 0 will automatically enable poygon offset fill. - * - * @member {number} - * @default 0 - */ - get polygonOffset() - { - return this._polygonOffset; - } - - set polygonOffset(value) // eslint-disable-line require-jsdoc - { - this.offsets = !!value; - this._polygonOffset = value; - } -} - diff --git a/packages/core/src/renderers/filters/Filter.js b/packages/core/src/renderers/filters/Filter.js deleted file mode 100644 index 2073b8d..0000000 --- a/packages/core/src/renderers/filters/Filter.js +++ /dev/null @@ -1,140 +0,0 @@ -import Shader from '../../shader/Shader'; -import Program from '../../shader/Program'; -import State from '../State'; -import { settings } from '@pixi/settings'; -// import extractUniformsFromSrc from './extractUniformsFromSrc'; -import defaultVertex from './defaultFilter.vert'; -import defaultFragment from './defaultFilter.frag'; - -// let math = require('../../math'); -/** - * @class - * @memberof PIXI - * @extends PIXI.Shader - */ -export default class Filter extends Shader -{ - /** - * @param {string} [vertexSrc] - The source of the vertex shader. - * @param {string} [fragmentSrc] - The source of the fragment shader. - * @param {object} [uniforms] - Custom uniforms to use to augment the built-in ones. - */ - constructor(vertexSrc, fragmentSrc, uniforms) - { - const program = Program.from(vertexSrc, fragmentSrc); - - super(program, uniforms); - - /** - * The padding of the filter. Some filters require extra space to breath such as a blur. - * Increasing this will add extra width and height to the bounds of the object that the - * filter is applied to. - * - * @member {number} - */ - this.padding = 0; - - /** - * The resolution of the filter. Setting this to be lower will lower the quality but - * increase the performance of the filter. - * - * @member {number} - */ - this.resolution = settings.RESOLUTION; - - /** - * If enabled is true the filter is applied, if false it will not. - * - * @member {boolean} - */ - this.enabled = true; - - /** - * If enabled, PixiJS will fit the filter area into boundaries for better performance. - * Switch it off if it does not work for specific shader. - * - * @member {boolean} - */ - this.autoFit = true; - - /** - * Legacy filters use position and uvs from attributes - * @member {boolean} - * @readonly - */ - this.legacy = !!this.program.attributeData.aTextureCoord; - - /** - * the webGL state the filter requires to render - * @member {PIXI.State} - */ - this.state = new State(); - } - - /** - * Applies the filter - * - * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from - * @param {PIXI.RenderTarget} input - The input render target. - * @param {PIXI.RenderTarget} output - The target to output to. - * @param {boolean} clear - Should the output be cleared before rendering to it - * @param {object} [currentState] - It's current state of filter. - * There are some useful properties in the currentState : - * target, filters, sourceFrame, destinationFrame, renderTarget, resolution - */ - apply(filterManager, input, output, clear, currentState, derp) // eslint-disable-line no-unused-vars - { - // do as you please! - - filterManager.applyFilter(this, input, output, clear, currentState, derp); - - // or just do a regular render.. - } - - /** - * Sets the blendmode of the filter - * - * @member {number} - * @default PIXI.BLEND_MODES.NORMAL - */ - get blendMode() - { - return this.state.blendMode; - } - - set blendMode(value) // eslint-disable-line require-jsdoc - { - this.state.blendMode = value; - } - - /** - * The default vertex shader source - * - * @static - * @constant - */ - static get defaultVertexSrc() - { - return defaultVertex; - } - - /** - * The default fragment shader source - * - * @static - * @constant - */ - static get defaultFragmentSrc() - { - return defaultFragment; - } -} - -/** - * Used for caching shader IDs - * - * @static - * @private - */ -Filter.SOURCE_KEY_MAP = {}; - diff --git a/packages/core/src/renderers/filters/defaultFilter.frag b/packages/core/src/renderers/filters/defaultFilter.frag deleted file mode 100644 index a14c1be..0000000 --- a/packages/core/src/renderers/filters/defaultFilter.frag +++ /dev/null @@ -1,22 +0,0 @@ -varying vec2 vTextureCoord; -varying vec2 vFilterCoord; - -uniform sampler2D uSampler; -uniform sampler2D filterSampler; - -void main(void){ - vec4 masky = texture2D(filterSampler, vFilterCoord); - vec4 sample = texture2D(uSampler, vTextureCoord); - vec4 color; - if(mod(vFilterCoord.x, 1.0) > 0.5) - { - color = vec4(1.0, 0.0, 0.0, 1.0); - } - else - { - color = vec4(0.0, 1.0, 0.0, 1.0); - } - // gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0); - gl_FragColor = mix(sample, masky, 0.5); - gl_FragColor *= sample.a; -} diff --git a/packages/core/src/renderers/filters/defaultFilter.vert b/packages/core/src/renderers/filters/defaultFilter.vert deleted file mode 100644 index e1febca..0000000 --- a/packages/core/src/renderers/filters/defaultFilter.vert +++ /dev/null @@ -1,14 +0,0 @@ -attribute vec2 aVertexPosition; -attribute vec2 aTextureCoord; - -uniform mat3 projectionMatrix; -uniform mat3 filterMatrix; - -varying vec2 vTextureCoord; -varying vec2 vFilterCoord; - -void main(void){ - gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); - vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy; - vTextureCoord = aTextureCoord ; -} diff --git a/packages/core/src/renderers/filters/filterTransforms.js b/packages/core/src/renderers/filters/filterTransforms.js deleted file mode 100644 index 87a3cd6..0000000 --- a/packages/core/src/renderers/filters/filterTransforms.js +++ /dev/null @@ -1,50 +0,0 @@ -import { Matrix } from '@pixi/math'; - -/** - * Calculates the mapped matrix - * @param filterArea {Rectangle} The filter area - * @param sprite {Sprite} the target sprite - * @param outputMatrix {Matrix} @alvin - * @private - */ -// this returns a matrix that will normalise map filter cords in the filter to screen space -export function calculateScreenSpaceMatrix(outputMatrix, filterArea, textureSize) -{ - // TODO unwrap? - const mappedMatrix = outputMatrix.identity(); - - mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); - - mappedMatrix.scale(textureSize.width, textureSize.height); - - return mappedMatrix; -} - -export function calculateNormalizedScreenSpaceMatrix(outputMatrix, filterArea, textureSize) -{ - const mappedMatrix = outputMatrix.identity(); - - mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); - - const translateScaleX = (textureSize.width / filterArea.width); - const translateScaleY = (textureSize.height / filterArea.height); - - mappedMatrix.scale(translateScaleX, translateScaleY); - - return mappedMatrix; -} - -// this will map the filter coord so that a texture can be used based on the transform of a sprite -export function calculateSpriteMatrix(outputMatrix, filterArea, textureSize, sprite) -{ - const orig = sprite._texture.orig; - const mappedMatrix = outputMatrix.set(textureSize.width, 0, 0, textureSize.height, filterArea.x, filterArea.y); - const worldTransform = sprite.worldTransform.copyTo(Matrix.TEMP_MATRIX); - - worldTransform.invert(); - mappedMatrix.prepend(worldTransform); - mappedMatrix.scale(1.0 / orig.width, 1.0 / orig.height); - mappedMatrix.translate(sprite.anchor.x, sprite.anchor.y); - - return mappedMatrix; -} diff --git a/packages/core/src/renderers/filters/spriteMask/SpriteMaskFilter.js b/packages/core/src/renderers/filters/spriteMask/SpriteMaskFilter.js deleted file mode 100644 index d5a91a4..0000000 --- a/packages/core/src/renderers/filters/spriteMask/SpriteMaskFilter.js +++ /dev/null @@ -1,63 +0,0 @@ -import Filter from '../Filter'; -import { Matrix } from '@pixi/math'; -import vertex from './spriteMaskFilter.vert'; -import fragment from './spriteMaskFilter.frag'; -import { default as TextureMatrix } from '../../../textures/TextureMatrix'; - -/** - * The SpriteMaskFilter class - * - * @class - * @extends PIXI.Filter - * @memberof PIXI - */ -export default class SpriteMaskFilter extends Filter -{ - /** - * @param {PIXI.Sprite} sprite - the target sprite - */ - constructor(sprite) - { - const maskMatrix = new Matrix(); - - super(vertex, fragment); - - sprite.renderable = false; - - this.maskSprite = sprite; - this.maskMatrix = maskMatrix; - } - - /** - * Applies the filter - * - * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from - * @param {PIXI.RenderTarget} input - The input render target. - * @param {PIXI.RenderTarget} output - The target to output to. - */ - apply(filterManager, input, output) - { - const maskSprite = this.maskSprite; - const tex = this.maskSprite.texture; - - if (!tex.valid) - { - return; - } - if (!tex.transform) - { - // margin = 0.0, let it bleed a bit, shader code becomes easier - // assuming that atlas textures were made with 1-pixel padding - tex.transform = new TextureMatrix(tex, 0.0); - } - tex.transform.update(); - - this.uniforms.mask = tex; - this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite) - .prepend(tex.transform.mapCoord); - this.uniforms.alpha = maskSprite.worldAlpha; - this.uniforms.maskClamp = tex.transform.uClampFrame; - - filterManager.applyFilter(this, input, output); - } -} diff --git a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.frag b/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.frag deleted file mode 100644 index 0e4aef8..0000000 --- a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.frag +++ /dev/null @@ -1,23 +0,0 @@ -varying vec2 vMaskCoord; -varying vec2 vTextureCoord; - -uniform sampler2D uSampler; -uniform sampler2D mask; -uniform float alpha; -uniform vec4 maskClamp; - -void main(void) -{ - float clip = step(3.5, - step(maskClamp.x, vMaskCoord.x) + - step(maskClamp.y, vMaskCoord.y) + - step(vMaskCoord.x, maskClamp.z) + - step(vMaskCoord.y, maskClamp.w)); - - vec4 original = texture2D(uSampler, vTextureCoord); - vec4 masky = texture2D(mask, vMaskCoord); - - original *= (masky.r * masky.a * alpha * clip); - - gl_FragColor = original; -} diff --git a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.vert b/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.vert deleted file mode 100644 index d23a30c..0000000 --- a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.vert +++ /dev/null @@ -1,16 +0,0 @@ -attribute vec2 aVertexPosition; -attribute vec2 aTextureCoord; - -uniform mat3 projectionMatrix; -uniform mat3 otherMatrix; - -varying vec2 vMaskCoord; -varying vec2 vTextureCoord; - -void main(void) -{ - gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); - - vTextureCoord = aTextureCoord; - vMaskCoord = ( otherMatrix * vec3( aTextureCoord, 1.0) ).xy; -} diff --git a/packages/core/src/renderers/systems/BatchSystem.js b/packages/core/src/renderers/systems/BatchSystem.js deleted file mode 100644 index 70e9518..0000000 --- a/packages/core/src/renderers/systems/BatchSystem.js +++ /dev/null @@ -1,68 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import ObjectRenderer from '../utils/ObjectRenderer'; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ - -export default class BatchSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this manager works for. - */ - constructor(renderer) - { - super(renderer); - - /** - * An empty renderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.emptyRenderer = new ObjectRenderer(renderer); - - /** - * The currently active ObjectRenderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.currentRenderer = this.emptyRenderer; - } - - /** - * Changes the current renderer to the one given in parameter - * - * @param {PIXI.ObjectRenderer} objectRenderer - The object renderer to use. - */ - setObjectRenderer(objectRenderer) - { - if (this.currentRenderer === objectRenderer) - { - return; - } - - this.currentRenderer.stop(); - this.currentRenderer = objectRenderer; - - this.renderer.state.setState(objectRenderer.state); - - this.currentRenderer.start(); - } - - /** - * This should be called if you wish to do some custom rendering - * It will basically render anything that may be batched up such as sprites - * - */ - flush() - { - this.setObjectRenderer(this.emptyRenderer); - } - - reset() - { - this.setObjectRenderer(this.emptyRenderer); - } -} diff --git a/packages/core/src/renderers/systems/ContextSystem.js b/packages/core/src/renderers/systems/ContextSystem.js deleted file mode 100644 index 43bf827..0000000 --- a/packages/core/src/renderers/systems/ContextSystem.js +++ /dev/null @@ -1,186 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { settings } from '@pixi/settings'; - -let CONTEXT_UID = 0; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class ContextSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this.webGLVersion = 1; - - this.handleContextLost = this.handleContextLost.bind(this); - this.handleContextRestored = this.handleContextRestored.bind(this); - - this.extensions = {}; - - renderer.view.addEventListener('webglcontextlost', this.handleContextLost, false); - renderer.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); - } - - get isLost() - { - return (!this.gl || this.gl.isContextLost()); - } - - contextChange(gl) - { - this.gl = gl; - - // restore a context if it was previously lost - if (gl.isContextLost() && gl.getExtension('WEBGL_lose_context')) - { - gl.getExtension('WEBGL_lose_context').restoreContext(); - } - } - - initFromContext(gl) - { - this.gl = gl; - this.validateContext(gl); - this.renderer.gl = gl; - this.renderer.CONTEXT_UID = CONTEXT_UID++; - this.renderer.runners.contextChange.run(gl); - } - - initFromOptions(options) - { - const gl = this.createContext(this.renderer.view, options); - - this.initFromContext(gl); - } - - /** - * Helper class to create a webGL Context - * - * @class - * @memberof PIXI.glCore - * @param canvas {HTMLCanvasElement} the canvas element that we will get the context from - * @param options {Object} An options object that gets passed in to the canvas element containing the context attributes, - * see https://developer.mozilla.org/en/docs/Web/API/HTMLCanvasElement/getContext for the options available - * @return {WebGLRenderingContext} the WebGL context - */ - createContext(canvas, options) - { - let gl; - - if (settings.PREFER_WEBGL_2) - { - gl = canvas.getContext('webgl2', options); - } - - if (gl) - { - this.webGLVersion = 2; - } - else - { - this.webGLVersion = 1; - - gl = canvas.getContext('webgl', options) - || canvas.getContext('experimental-webgl', options); - - if (!gl) - { - // fail, not able to get a context - throw new Error('This browser does not support webGL. Try using the canvas renderer'); - } - } - - this.gl = gl; - - this.getExtensions(); - - return gl; - } - - getExtensions() - { - // time to set up default etensions that pixi uses.. - const gl = this.gl; - const extensions = this.extensions; - - if (this.webGLVersion === 1) - { - extensions.drawBuffers = gl.getExtension('WEBGL_draw_buffers'); - extensions.depthTexture = gl.getExtension('WEBKIT_WEBGL_depth_texture'); - extensions.floatTexture = gl.getExtension('OES_texture_float'); - extensions.loseContext = gl.getExtension('WEBGL_lose_context'); - - extensions.vertexArrayObject = gl.getExtension('OES_vertex_array_object') - || gl.getExtension('MOZ_OES_vertex_array_object') - || gl.getExtension('WEBKIT_OES_vertex_array_object'); - } - - // we don't use any specific WebGL 2 ones yet! - } - - /** - * Handles a lost webgl context - * - * @private - * @param {WebGLContextEvent} event - The context lost event. - */ - handleContextLost(event) - { - event.preventDefault(); - } - - /** - * Handles a restored webgl context - * - * @private - */ - handleContextRestored() - { - this.renderer.runners.contextChange.run(this.gl); - } - - destroy() - { - const view = this.renderer.view; - - // remove listeners - view.removeEventListener('webglcontextlost', this.handleContextLost); - view.removeEventListener('webglcontextrestored', this.handleContextRestored); - - this.gl.useProgram(null); - - if (this.extensions.loseContext) - { - this.extensions.loseContext.loseContext(); - } - } - - postrender() - { - this.gl.flush(); - } - - validateContext(gl) - { - const attributes = gl.getContextAttributes(); - - // this is going to be fairly simple for now.. but at least we have room to grow! - if (!attributes.stencil) - { - /* eslint-disable max-len */ - - /* eslint-disable no-console */ - console.warn('Provided WebGL context does not have a stencil buffer, masks may not render correctly'); - /* eslint-enable no-console */ - - /* eslint-enable max-len */ - } - } -} diff --git a/packages/core/src/renderers/systems/FramebufferSystem.js b/packages/core/src/renderers/systems/FramebufferSystem.js deleted file mode 100644 index df7902b..0000000 --- a/packages/core/src/renderers/systems/FramebufferSystem.js +++ /dev/null @@ -1,246 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { Rectangle } from '@pixi/math'; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class FramebufferSystem extends WebGLSystem -{ - /** - * Sets up the renderer context and necessary buffers. - * - * @private - */ - contextChange() - { - this.gl = this.renderer.gl; - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - this.current = null; - this.viewport = new Rectangle(); - - this.drawBufferExtension = this.renderer.context.extensions.drawBuffers; - } - - bind(framebuffer, frame) - { - const gl = this.gl; - - this.current = framebuffer; - - if (framebuffer) - { - // TODO cacheing layer! - - const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID] || this.initFramebuffer(framebuffer); - - gl.bindFramebuffer(gl.FRAMEBUFFER, fbo.framebuffer); - // makesure all textures are unbound.. - - // now check for updates... - if (fbo.dirtyId !== framebuffer.dirtyId) - { - fbo.dirtyId = framebuffer.dirtyId; - - if (fbo.dirtyFormat !== framebuffer.dirtyFormat) - { - fbo.dirtyFormat = framebuffer.dirtyFormat; - this.updateFramebuffer(framebuffer); - } - else if (fbo.dirtySize !== framebuffer.dirtySize) - { - fbo.dirtySize = framebuffer.dirtySize; - this.resizeFramebuffer(framebuffer); - } - } - - for (let i = 0; i < framebuffer.colorTextures.length; i++) - { - if (framebuffer.colorTextures[i].texturePart) - { - this.renderer.texture.unbind(framebuffer.colorTextures[i].texture); - } - else - { - this.renderer.texture.unbind(framebuffer.colorTextures[i]); - } - } - - if (framebuffer.depthTexture) - { - this.renderer.texture.unbind(framebuffer.depthTexture); - } - - if (frame) - { - this.setViewport(frame.x, frame.y, frame.width, frame.height); - } - else - { - this.setViewport(0, 0, framebuffer.width, framebuffer.height); - } - } - else - { - gl.bindFramebuffer(gl.FRAMEBUFFER, null); - - if (frame) - { - this.setViewport(frame.x, frame.y, frame.width, frame.height); - } - else - { - this.setViewport(0, 0, this.renderer.width, this.renderer.height); - } - } - } - - setViewport(x, y, width, height) - { - const v = this.viewport; - - if (v.width !== width || v.height !== height || v.x !== x || v.y !== y) - { - v.x = x; - v.y = y; - v.width = width; - v.height = height; - - this.gl.viewport(x, y, width, height); - } - } - - get size() - { - if (this.current) - { - // TODO store temp - return { x: 0, y: 0, width: this.current.width, height: this.current.height }; - } - - return { x: 0, y: 0, width: this.renderer.width, height: this.renderer.height }; - } - - clear(r, g, b, a) - { - const gl = this.gl; - - // TODO clear color can be set only one right? - gl.clearColor(r, g, b, a); - gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); - } - - // private functions... - - initFramebuffer(framebuffer) - { - const gl = this.gl; - - // TODO - make this a class? - const fbo = { - framebuffer: gl.createFramebuffer(), - stencil: null, - dirtyId: 0, - dirtyFormat: 0, - dirtySize: 0, - }; - - framebuffer.glFrameBuffers[this.CONTEXT_UID] = fbo; - - return fbo; - } - - resizeFramebuffer(framebuffer) - { - const gl = this.gl; - - if (framebuffer.stencil || framebuffer.depth) - { - gl.bindRenderbuffer(gl.RENDERBUFFER, this.stencil); - gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); - } - } - - updateFramebuffer(framebuffer) - { - const gl = this.gl; - - const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID]; - - // bind the color texture - const colorTextures = framebuffer.colorTextures; - - let count = colorTextures.length; - - if (!this.drawBufferExtension) - { - count = Math.min(count, 1); - } - - const activeTextures = []; - - for (let i = 0; i < count; i++) - { - const texture = framebuffer.colorTextures[i]; - - if (texture.texturePart) - { - this.renderer.texture.bind(texture.texture, 0); - - gl.framebufferTexture2D(gl.FRAMEBUFFER, - gl.COLOR_ATTACHMENT0 + i, - gl.TEXTURE_CUBE_MAP_NEGATIVE_X + texture.side, - texture.texture._glTextures[this.CONTEXT_UID].texture, - 0); - } - else - { - this.renderer.texture.bind(texture, 0); - - gl.framebufferTexture2D(gl.FRAMEBUFFER, - gl.COLOR_ATTACHMENT0 + i, - gl.TEXTURE_2D, - texture._glTextures[this.CONTEXT_UID].texture, - 0); - } - - activeTextures.push(gl.COLOR_ATTACHMENT0 + i); - } - - if (this.drawBufferExtension && activeTextures.length > 1) - { - this.drawBufferExtension.drawBuffersWEBGL(activeTextures); - } - - if (framebuffer.depthTexture) - { - const depthTextureExt = this.renderer.context.extensions.depthTexture; - - if (depthTextureExt) - { - const depthTexture = framebuffer.depthTexture; - - this.renderer.texture.bind(depthTexture, 0); - - gl.framebufferTexture2D(gl.FRAMEBUFFER, - gl.DEPTH_ATTACHMENT, - gl.TEXTURE_2D, - depthTexture._glTextures[this.CONTEXT_UID].texture, - 0); - } - } - - if (framebuffer.stencil || framebuffer.depth) - { - fbo.stencil = gl.createRenderbuffer(); - - gl.bindRenderbuffer(gl.RENDERBUFFER, fbo.stencil); - - // TODO.. this is depth AND stencil? - gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, fbo.stencil); - gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); - // fbo.enableStencil(); - } - } -} diff --git a/packages/core/src/renderers/systems/MaskSystem.js b/packages/core/src/renderers/systems/MaskSystem.js deleted file mode 100644 index d9c4b1a..0000000 --- a/packages/core/src/renderers/systems/MaskSystem.js +++ /dev/null @@ -1,199 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import AlphaMaskFilter from '../filters/spriteMask/SpriteMaskFilter'; - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class MaskSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - // TODO - we don't need both! - this.scissor = false; - this.scissorData = null; - this.scissorRenderTarget = null; - - this.enableScissor = false; - - this.alphaMaskPool = []; - this.alphaMaskIndex = 0; - } - - /** - * Applies the Mask and adds it to the current filter stack. - * - * @param {PIXI.DisplayObject} target - Display Object to push the mask to - * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. - */ - push(target, maskData) - { - // TODO the root check means scissor rect will not - // be used on render textures more info here: - // https://github.com/pixijs/pixi.js/pull/3545 - - if (maskData.texture) - { - this.pushSpriteMask(target, maskData); - } - else if (this.enableScissor - && !this.scissor - && this.renderer._activeRenderTarget.root - && !this.renderer.stencil.stencilMaskStack.length - && maskData.isFastRect()) - { - const matrix = maskData.worldTransform; - - let rot = Math.atan2(matrix.b, matrix.a); - - // use the nearest degree! - rot = Math.round(rot * (180 / Math.PI)); - - if (rot % 90) - { - this.pushStencilMask(maskData); - } - else - { - this.pushScissorMask(target, maskData); - } - } - else - { - this.pushStencilMask(maskData); - } - } - - /** - * Removes the last mask from the mask stack and doesn't return it. - * - * @param {PIXI.DisplayObject} target - Display Object to pop the mask from - * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. - */ - pop(target, maskData) - { - if (maskData.texture) - { - this.popSpriteMask(target, maskData); - } - else if (this.enableScissor && !this.renderer.stencil.stencilMaskStack.length) - { - this.popScissorMask(target, maskData); - } - else - { - this.popStencilMask(target, maskData); - } - } - - /** - * Applies the Mask and adds it to the current filter stack. - * - * @param {PIXI.RenderTarget} target - Display Object to push the sprite mask to - * @param {PIXI.Sprite} maskData - Sprite to be used as the mask - */ - pushSpriteMask(target, maskData) - { - let alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; - - if (!alphaMaskFilter) - { - alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; - } - - alphaMaskFilter[0].resolution = this.renderer.resolution; - alphaMaskFilter[0].maskSprite = maskData; - - // TODO - may cause issues! - target.filterArea = maskData.getBounds(true); - - this.renderer.filterSystem.pushFilter(target, alphaMaskFilter); - - this.alphaMaskIndex++; - } - - /** - * Removes the last filter from the filter stack and doesn't return it. - * - */ - popSpriteMask() - { - this.renderer.filterSystem.popFilter(); - this.alphaMaskIndex--; - } - - /** - * Applies the Mask and adds it to the current filter stack. - * - * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. - */ - pushStencilMask(maskData) - { - this.renderer.batch.flush(); - this.renderer.stencil.pushStencil(maskData); - } - - /** - * Removes the last filter from the filter stack and doesn't return it. - * - */ - popStencilMask() - { - // this.renderer.currentRenderer.stop(); - this.renderer.stencil.popStencil(); - } - - /** - * - * @param {PIXI.DisplayObject} target - Display Object to push the mask to - * @param {PIXI.Graphics} maskData - The masking data. - */ - pushScissorMask(target, maskData) - { - maskData.renderable = true; - - const renderTarget = this.renderer._activeRenderTarget; - - const bounds = maskData.getBounds(); - - bounds.fit(renderTarget.size); - maskData.renderable = false; - - this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); - - const resolution = this.renderer.resolution; - - this.renderer.gl.scissor( - bounds.x * resolution, - (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, - bounds.width * resolution, - bounds.height * resolution - ); - - this.scissorRenderTarget = renderTarget; - this.scissorData = maskData; - this.scissor = true; - } - - /** - * - * - */ - popScissorMask() - { - this.scissorRenderTarget = null; - this.scissorData = null; - this.scissor = false; - - // must be scissor! - const gl = this.renderer.gl; - - gl.disable(gl.SCISSOR_TEST); - } -} diff --git a/packages/core/src/renderers/systems/ProjectionSystem.js b/packages/core/src/renderers/systems/ProjectionSystem.js deleted file mode 100644 index ec5efdb..0000000 --- a/packages/core/src/renderers/systems/ProjectionSystem.js +++ /dev/null @@ -1,73 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { Matrix } from '@pixi/math'; - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ - -export default class ProjectionSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this.projectionMatrix = new Matrix(); - } - - update(destinationFrame, sourceFrame, resolution, root) - { - this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; - this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; - - this.calculateProjection(this.destinationFrame, this.sourceFrame, resolution, root); - - this.renderer.globalUniforms.uniforms.projectionMatrix = this.projectionMatrix; - this.renderer.globalUniforms.update(); - } - - /** - * Updates the projection matrix based on a projection frame (which is a rectangle) - * - * @param {Rectangle} destinationFrame - The destination frame. - * @param {Rectangle} sourceFrame - The source frame. - */ - calculateProjection(destinationFrame, sourceFrame, resolution, root) - { - const pm = this.projectionMatrix; - - // I don't think we will need this line.. - // pm.identity(); - - if (!root) - { - pm.a = (1 / destinationFrame.width * 2) * resolution; - pm.d = (1 / destinationFrame.height * 2) * resolution; - - pm.tx = -1 - (sourceFrame.x * pm.a); - pm.ty = -1 - (sourceFrame.y * pm.d); - } - else - { - pm.a = (1 / destinationFrame.width * 2) * resolution; - pm.d = (-1 / destinationFrame.height * 2) * resolution; - - pm.tx = -1 - (sourceFrame.x * pm.a); - pm.ty = 1 - (sourceFrame.y * pm.d); - } - } - - /** - * Sets the transform of the active render target to the given matrix - * - * @param {PIXI.Matrix} matrix - The transformation matrix - */ - setTransform()// matrix) - { - // this._activeRenderTarget.transform = matrix; - } -} diff --git a/packages/core/src/renderers/systems/RenderTextureSystem.js b/packages/core/src/renderers/systems/RenderTextureSystem.js deleted file mode 100644 index a133ef1..0000000 --- a/packages/core/src/renderers/systems/RenderTextureSystem.js +++ /dev/null @@ -1,115 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { Rectangle } from '@pixi/math'; - -const tempRect = new Rectangle(); - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ - -export default class RenderTextureSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this.clearColor = renderer._backgroundColorRgba; - - // TODO moe this property somewhere else! - this.defaultMaskStack = []; - this.defaultFilterStack = [{}]; - - // empty render texture? - this.renderTexture = null; - - this.destinationFrame = new Rectangle(); - } - - bind(renderTexture, sourceFrame, destinationFrame) - { - // TODO - do we want this?? - if (this.renderTexture === renderTexture) return; - this.renderTexture = renderTexture; - - const renderer = this.renderer; - - if (renderTexture) - { - const baseTexture = renderTexture.baseTexture; - - if (!destinationFrame) - { - tempRect.width = baseTexture.realWidth; - tempRect.height = baseTexture.realHeight; - - destinationFrame = tempRect; - } - - if (!sourceFrame) - { - sourceFrame = destinationFrame; - } - - this.renderer.framebuffer.bind(baseTexture.frameBuffer, destinationFrame); - - this.renderer.projection.update(destinationFrame, sourceFrame, baseTexture.resolution, false); - this.renderer.stencil.setMaskStack(baseTexture.stencilMaskStack); - } - else - { - // TODO these validation checks happen deeper down.. - // thing they can be avoided.. - if (!destinationFrame) - { - tempRect.width = renderer.width; - tempRect.height = renderer.height; - - destinationFrame = tempRect; - } - - if (!sourceFrame) - { - sourceFrame = destinationFrame; - } - - renderer.framebuffer.bind(null, destinationFrame); - - // TODO store this.. - this.renderer.projection.update(destinationFrame, sourceFrame, this.renderer.resolution, true); - this.renderer.stencil.setMaskStack(this.defaultMaskStack); - } - - this.destinationFrame.copyFrom(destinationFrame); - } - - /** - * Erases the render texture and fills the drawing area with a colour - * - * @param {number} [clearColor] - The colour - * @return {PIXI.Renderer} Returns itself. - */ - clear(clearColor) - { - if (this.renderTexture) - { - clearColor = clearColor || this.renderTexture.baseTexture.clearColor; - } - else - { - clearColor = clearColor || this.clearColor; - } - - this.renderer.framebuffer.clear(clearColor[0], clearColor[1], clearColor[2], clearColor[3]); - } - - resize()// screenWidth, screenHeight) - { - // resize the root only! - this.bind(null); - } -} diff --git a/packages/core/src/renderers/systems/StateSystem.js b/packages/core/src/renderers/systems/StateSystem.js deleted file mode 100755 index d7f4caf..0000000 --- a/packages/core/src/renderers/systems/StateSystem.js +++ /dev/null @@ -1,288 +0,0 @@ -import mapWebGLBlendModesToPixi from '../utils/mapWebGLBlendModesToPixi'; -import WebGLSystem from './WebGLSystem'; -import WebGLState from '../State'; - -const BLEND = 0; -const OFFSET = 1; -const CULLING = 2; -const DEPTH_TEST = 3; -const WINDING = 4; - -/** - * A WebGL state machines - * - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class StateSystem extends WebGLSystem -{ - /** - * @param {WebGLRenderingContext} gl - The current WebGL rendering context - */ - constructor(renderer) - { - super(renderer); - - this.gl = null; - - this.maxAttribs = null; - - // check we have vao.. - this.nativeVaoExtension = null; - - this.attribState = null; - - this.stateId = 0; - this.polygonOffset = 0; - this.blendMode = 17; - - this.map = []; - - // map functions for when we set state.. - this.map[BLEND] = this.setBlend; - this.map[OFFSET] = this.setOffset; - this.map[CULLING] = this.setCullFace; - this.map[DEPTH_TEST] = this.setDepthTest; - this.map[WINDING] = this.setFrontFace; - - this.checks = []; - - this.defaultState = new WebGLState(); - this.defaultState.blend = true; - this.defaultState.depth = true; - } - - contextChange(gl) - { - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; - - this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); - - // check we have vao.. - this.nativeVaoExtension = ( - gl.getExtension('OES_vertex_array_object') - || gl.getExtension('MOZ_OES_vertex_array_object') - || gl.getExtension('WEBKIT_OES_vertex_array_object') - ); - - this.attribState = { - tempAttribState: new Array(this.maxAttribs), - attribState: new Array(this.maxAttribs), - }; - - this.blendModes = mapWebGLBlendModesToPixi(gl); - - this.setState(this.defaultState); - - this.reset(); - } - - /** - * Sets the current state - * - * @param {*} state - The state to set. - */ - setState(state) - { - state = state || this.defaultState; - - // TODO maybe to an object check? ( this.state === state )? - if (this.stateId !== state.data) - { - let diff = this.stateId ^ state.data; - let i = 0; - - // order from least to most common - while (diff) - { - if (diff & 1) - { - // state change! - this.map[i].call(this, !!(state.data & (1 << i))); - } - - diff = diff >> 1; - i++; - } - - this.stateId = state.data; - } - - // based on the above settings we check for specific modes.. - // for example if blend is active we check and set the blend modes - // or of polygon offset is active we check the poly depth. - for (let i = 0; i < this.checks.length; i++) - { - this.checks[i](this, state); - } - } - - /** - * Enables or disabled blending. - * - * @param {boolean} value - Turn on or off webgl blending. - */ - setBlend(value) - { - this.updateCheck(StateSystem.checkBlendMode, value); - - this.gl[value ? 'enable' : 'disable'](this.gl.BLEND); - } - - /** - * Enables or disable polygon offset fill - * - * @param {boolean} value - Turn on or off webgl polygon offset testing. - */ - setOffset(value) - { - this.gl[value ? 'enable' : 'disable'](this.gl.POLYGON_OFFSET_FILL); - } - - /** - * Sets whether to enable or disable depth test. - * - * @param {boolean} value - Turn on or off webgl depth testing. - */ - setDepthTest(value) - { - this.gl[value ? 'enable' : 'disable'](this.gl.DEPTH_TEST); - } - - /** - * Sets whether to enable or disable cull face. - * - * @param {boolean} value - Turn on or off webgl cull face. - */ - setCullFace(value) - { - this.gl[value ? 'enable' : 'disable'](this.gl.CULL_FACE); - } - - /** - * Sets the gl front face. - * - * @param {boolean} value - true is clockwise and false is counter-clockwise - */ - setFrontFace(value) - { - this.gl.frontFace(this.gl[value ? 'CW' : 'CCW']); - } - - /** - * Sets the blend mode. - * - * @param {number} value - The blend mode to set to. - */ - setBlendMode(value) - { - if (value === this.blendMode) - { - return; - } - - this.blendMode = value; - this.gl.blendFunc(this.blendModes[value][0], this.blendModes[value][1]); - } - - /** - * Sets the polygon offset. - * - * @param {number} value - the polygon offset - * @param {number} scale - the polygon offset scale - */ - setPolygonOffset(value, scale) - { - this.gl.polygonOffset(value, scale); - } - - /** - * Disables all the vaos in use - * - */ - resetAttributes() - { - for (let i = 0; i < this.attribState.tempAttribState.length; i++) - { - this.attribState.tempAttribState[i] = 0; - } - - for (let i = 0; i < this.attribState.attribState.length; i++) - { - this.attribState.attribState[i] = 0; - } - - // im going to assume one is always active for performance reasons. - for (let i = 1; i < this.maxAttribs; i++) - { - this.gl.disableVertexAttribArray(i); - } - } - - // used - /** - * Resets all the logic and disables the vaos - */ - reset() - { - // unbind any VAO if they exist.. - if (this.nativeVaoExtension) - { - this.nativeVaoExtension.bindVertexArrayOES(null); - } - - // reset all attributes.. - this.resetAttributes(); - - this.gl.pixelStorei(this.gl.UNPACK_FLIP_Y_WEBGL, false); - - this.setBlendMode(0); - - // TODO? - // this.setState(this.defaultState); - } - - /** - * checks to see which updates should be checked based on which settings have been activated - * for example if blend is enabled then we shold check the blend modes each time the state is changed - * or if poygon fill is activated then we need to check if the polygone offset changes. - * The idea is that we only check what we have too. - * - * @param {Function} func the checking function to add or remove - * @param {boolean} value should the check function be added or removed. - */ - updateCheck(func, value) - { - const index = this.checks.indexOf(func); - - if (value && index === -1) - { - this.checks.push(func); - } - else if (!value && index !== -1) - { - this.checks.splice(index, 1); - } - } - - /** - * A private little wrapper function that we call to check the blend mode. - * - * @static - * @private - * @param {PIXI.StateSystem} System the System to perform the state check on - * @param {PIXI.State} state the state that the blendMode will pulled from - */ - static checkBlendMode(system, state) - { - system.setBlendMode(state.blendMode); - } - - // TODO - add polygon offset? -} diff --git a/packages/core/src/renderers/systems/StencilSystem.js b/packages/core/src/renderers/systems/StencilSystem.js deleted file mode 100644 index 5d538cb..0000000 --- a/packages/core/src/renderers/systems/StencilSystem.js +++ /dev/null @@ -1,133 +0,0 @@ -import WebGLSystem from './WebGLSystem'; - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class StencilSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - this.stencilMaskStack = []; - } - - /** - * Changes the mask stack that is used by this System. - * - * @param {PIXI.Graphics[]} stencilMaskStack - The mask stack - */ - setMaskStack(stencilMaskStack) - { - const gl = this.renderer.gl; - - if (stencilMaskStack.length !== this.stencilMaskStack.length) - { - if (stencilMaskStack.length === 0) - { - gl.disable(gl.STENCIL_TEST); - } - else - { - gl.enable(gl.STENCIL_TEST); - } - } - - this.stencilMaskStack = stencilMaskStack; - } - - /** - * Applies the Mask and adds it to the current stencil stack. @alvin - * - * @param {PIXI.Graphics} graphics - The mask - */ - pushStencil(graphics) - { - this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); - - // this.renderer._activeRenderTarget.attachStencilBuffer(); - - const gl = this.renderer.gl; - const prevMaskCount = this.stencilMaskStack.length; - - if (prevMaskCount === 0) - { - gl.enable(gl.STENCIL_TEST); - } - - this.stencilMaskStack.push(graphics); - - // Increment the refference stencil value where the new mask overlaps with the old ones. - gl.colorMask(false, false, false, false); - gl.stencilFunc(gl.EQUAL, prevMaskCount, this._getBitwiseMask()); - gl.stencilOp(gl.KEEP, gl.KEEP, gl.INCR); - this.renderer.plugins.graphics.render(graphics); - - this._useCurrent(); - } - - /** - * Removes the last mask from the stencil stack. @alvin - */ - popStencil() - { - this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); - - const gl = this.renderer.gl; - const graphics = this.stencilMaskStack.pop(); - - if (this.stencilMaskStack.length === 0) - { - // the stack is empty! - gl.disable(gl.STENCIL_TEST); - gl.clear(gl.STENCIL_BUFFER_BIT); - gl.clearStencil(0); - } - else - { - // Decrement the refference stencil value where the popped mask overlaps with the other ones - gl.colorMask(false, false, false, false); - gl.stencilOp(gl.KEEP, gl.KEEP, gl.DECR); - this.renderer.plugins.graphics.render(graphics); - - this._useCurrent(); - } - } - - /** - * Setup renderer to use the current stencil data. - */ - _useCurrent() - { - const gl = this.renderer.gl; - - gl.colorMask(true, true, true, true); - gl.stencilFunc(gl.EQUAL, this.stencilMaskStack.length, this._getBitwiseMask()); - gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP); - } - - /** - * Fill 1s equal to the number of acitve stencil masks. - * - * @return {number} The bitwise mask. - */ - _getBitwiseMask() - { - return (1 << this.stencilMaskStack.length) - 1; - } - - /** - * Destroys the mask stack. - * - */ - destroy() - { - super.destroy(this); - - this.stencilMaskStack = null; - } -} diff --git a/packages/core/src/renderers/systems/WebGLSystem.js b/packages/core/src/renderers/systems/WebGLSystem.js deleted file mode 100644 index d3d5b31..0000000 --- a/packages/core/src/renderers/systems/WebGLSystem.js +++ /dev/null @@ -1,41 +0,0 @@ -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this manager works for. - */ - constructor(renderer) - { - /** - * The renderer this manager works for. - * - * @member {PIXI.Renderer} - */ - this.renderer = renderer; - - this.renderer.runners.contextChange.add(this); - } - - /** - * Generic method called when there is a WebGL context change. - * - */ - contextChange() - { - // do some codes init! - } - - /** - * Generic destroy methods to be overridden by the subclass - * - */ - destroy() - { - this.renderer.runners.contextChange.remove(this); - this.renderer = null; - } -} diff --git a/packages/core/src/renderers/systems/filter/FilterSystem.js b/packages/core/src/renderers/systems/filter/FilterSystem.js deleted file mode 100644 index e615037..0000000 --- a/packages/core/src/renderers/systems/filter/FilterSystem.js +++ /dev/null @@ -1,431 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; - -import RenderTexture from '../../../textures/RenderTexture'; -import Quad from '../../utils/Quad'; -import QuadUv from '../../utils/QuadUv'; -import { Rectangle } from '@pixi/math'; -import * as filterTransforms from '../../filters/filterTransforms'; -import bitTwiddle from 'bit-twiddle'; -import UniformGroup from '../../../shader/UniformGroup'; -import { DRAW_MODES } from '../../../../../constants'; - -// -/** - * @ignore - * @class - */ -class FilterState -{ - /** - * - */ - constructor() - { - this.renderTexture = null; - this.sourceFrame = new Rectangle(); - this.destinationFrame = new Rectangle(); - this.filters = []; - this.target = null; - this.legacy = false; - this.resolution = 1; - } -} - -/** - * @class - * @memberof PIXI.systems - * @extends PIXI.systems.WebGLSystem - */ -export default class FilterSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - /** - * stores a bunch of PO2 textures used for filtering - * @type {Object} - */ - this.texturePool = {}; - - /** - * a pool for storing filter states, save us creating new ones each tick - * @type {Array} - */ - this.statePool = []; - - /** - * A very simple geometry used when drawing a filter effect to the screen - * @type {Quad} - */ - this.quad = new Quad(); - - this.quadUv = new QuadUv(); - - /** - * Temporary rect for maths - * @type {PIXI.Rectangle} - */ - this.tempRect = new Rectangle(); - - this.activeState = {}; - - /** - * this uniform group is attached to filter uniforms when used - * @type {UniformGroup} - */ - this.globalUniforms = new UniformGroup({ - sourceFrame: this.tempRect, - destinationFrame: this.tempRect, - - // legacy variables - filterArea: new Float32Array(4), - filterClamp: new Float32Array(4), - }, true); - } - - /** - * Adds a new filter to the System. - * - * @param {PIXI.DisplayObject} target - The target of the filter to render. - * @param {PIXI.Filter[]} filters - The filters to apply. - */ - push(target, filters) - { - const renderer = this.renderer; - const filterStack = this.renderer.renderTexture.defaultFilterStack; - const state = this.statePool.pop() || new FilterState(); - - let resolution = filters[0].resolution; - let padding = filters[0].padding; - let autoFit = filters[0].autoFit; - let legacy = filters[0].legacy; - - for (let i = 1; i < filters.length; i++) - { - const filter = filters[i]; - - // lets use the lowest resolution.. - resolution = Math.min(resolution, filter.resolution); - // and the largest amount of padding! - padding = Math.max(padding, filter.padding); - // only auto fit if all filters are autofit - autoFit = autoFit || filter.autoFit; - - legacy = legacy || filter.legacy; - } - - filterStack.push(state); - - state.resolution = resolution; - - state.legacy = legacy; - - // round to whole number based on resolution - // TODO move that to the shader too? - state.sourceFrame = target.filterArea || target.getBounds(true); - - state.sourceFrame.pad(padding); - - if (autoFit) - { - state.sourceFrame.fit(this.renderer.renderTexture.destinationFrame); - } - - state.sourceFrame.round(resolution); - - state.renderTexture = this.getPotFilterTexture(state.sourceFrame.width, state.sourceFrame.height, resolution); - state.filters = filters; - - state.destinationFrame.width = state.renderTexture.width; - state.destinationFrame.height = state.renderTexture.height; - - state.renderTexture.filterFrame = state.sourceFrame; - renderer.renderTexture.bind(state.renderTexture, state.sourceFrame);// /, state.destinationFrame); - renderer.renderTexture.clear(); - } - - /** - * Pops off the filter and applies it. - * - */ - pop() - { - const renderer = this.renderer; - const filterStack = renderer.renderTexture.defaultFilterStack; - const state = filterStack.pop(); - const filters = state.filters; - - this.activeState = state; - - const globalUniforms = this.globalUniforms.uniforms; - - globalUniforms.sourceFrame = state.sourceFrame; - globalUniforms.destinationFrame = state.destinationFrame; - globalUniforms.resolution = state.resolution; - - // only update the rect if its legacy.. - if (state.legacy) - { - const filterArea = globalUniforms.filterArea; - const filterClamp = globalUniforms.filterClamp; - - filterArea[0] = state.destinationFrame.width; - filterArea[1] = state.destinationFrame.height; - filterArea[2] = state.sourceFrame.x; - filterArea[3] = state.sourceFrame.y; - - filterClamp[0] = 0.5 / state.resolution / state.destinationFrame.width; - filterClamp[1] = 0.5 / state.resolution / state.destinationFrame.height; - filterClamp[2] = (state.sourceFrame.width - 0.5) / state.resolution / state.destinationFrame.width; - filterClamp[3] = (state.sourceFrame.height - 0.5) / state.resolution / state.destinationFrame.height; - } - - this.globalUniforms.update(); - - const lastState = filterStack[filterStack.length - 1]; - - if (filters.length === 1) - { - filters[0].apply(this, state.renderTexture, lastState.renderTexture, false, state); - - this.returnFilterTexture(state.renderTexture); - } - else - { - let flip = state.renderTexture; - let flop = this.getPotFilterTexture( - flip.width, - flip.height, - state.resolution - ); - - flop.filterFrame = flip.filterFrame; - - let i = 0; - - for (i = 0; i < filters.length - 1; ++i) - { - filters[i].apply(this, flip, flop, true, state); - - const t = flip; - - flip = flop; - flop = t; - } - - filters[i].apply(this, flip, lastState.renderTexture, false, state); - - this.returnFilterTexture(flip); - this.returnFilterTexture(flop); - } - - this.statePool.push(state); - } - - /** - * Draws a filter. - * - * @param {PIXI.Filter} filter - The filter to draw. - * @param {PIXI.RenderTarget} input - The input render target. - * @param {PIXI.RenderTarget} output - The target to output to. - * @param {boolean} clear - Should the output be cleared before rendering to it - */ - applyFilter(filter, input, output, clear) - { - const renderer = this.renderer; - - renderer.renderTexture.bind(output, output ? output.filterFrame : null); - - if (clear) - { - // gl.disable(gl.SCISSOR_TEST); - renderer.renderTexture.clear(); - // gl.enable(gl.SCISSOR_TEST); - } - - // set the uniforms.. - filter.uniforms.uSampler = input; - filter.uniforms.filterGlobals = this.globalUniforms; - - // TODO make it so that the order of this does not matter.. - // because it does at the moment cos of global uniforms. - // they need to get resynced - - renderer.state.setState(filter.state); - renderer.shader.bind(filter); - - if (filter.legacy) - { - this.quadUv.map(input._frame, input.filterFrame); - - renderer.geometry.bind(this.quadUv); - renderer.geometry.draw(DRAW_MODES.TRIANGLES); - } - else - { - renderer.geometry.bind(this.quad); - renderer.geometry.draw(DRAW_MODES.TRIANGLE_STRIP); - } - } - - /** - * Calculates the mapped matrix. - * - * TODO playing around here.. this is temporary - (will end up in the shader) - * this returns a matrix that will normalise map filter cords in the filter to screen space - * - * @param {PIXI.Matrix} outputMatrix - the matrix to output to. - * @return {PIXI.Matrix} The mapped matrix. - */ - calculateScreenSpaceMatrix(outputMatrix) - { - const currentState = this.activeState; - - return filterTransforms.calculateScreenSpaceMatrix( - outputMatrix, - currentState.sourceFrame, - currentState.destinationFrame - ); - } - - /** - * This will map the filter coord so that a texture can be used based on the transform of a sprite - * - * @param {PIXI.Matrix} outputMatrix - The matrix to output to. - * @param {PIXI.Sprite} sprite - The sprite to map to. - * @return {PIXI.Matrix} The mapped matrix. - */ - calculateSpriteMatrix(outputMatrix, sprite) - { - const currentState = this.activeState; - - return filterTransforms.calculateSpriteMatrix( - outputMatrix, - currentState.sourceFrame, - currentState.destinationFrame, - sprite - ); - } - - /** - * Destroys this Filter System. - * - * @param {boolean} [contextLost=false] context was lost, do not free shaders - * - */ - destroy(contextLost = false) - { - if (!contextLost) - { - this.emptyPool(); - } - else - { - this.texturePool = {}; - } - } - - /** - * Gets a Power-of-Two render texture. - * - * TODO move to a seperate class could be on renderer? - * also - could cause issue with multiple contexts? - * - * @private - * @param {WebGLRenderingContext} gl - The webgl rendering context - * @param {number} minWidth - The minimum width of the render target. - * @param {number} minHeight - The minimum height of the render target. - * @param {number} resolution - The resolution of the render target. - * @return {PIXI.RenderTarget} The new render target. - */ - getPotFilterTexture(minWidth, minHeight, resolution) - { - minWidth = bitTwiddle.nextPow2(minWidth); - minHeight = bitTwiddle.nextPow2(minHeight); - resolution = resolution || 1; - - const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); - - if (!this.texturePool[key]) - { - this.texturePool[key] = []; - } - - let renderTexture = this.texturePool[key].pop(); - - if (!renderTexture) - { - // temporary bypass cache.. - // internally - this will cause a texture to be bound.. - renderTexture = RenderTexture.create({ - width: minWidth, - height: minHeight, - resolution, - }); - } - - return renderTexture; - } - - /** - * Gets extra render texture to use inside current filter - * - * @param {number} resolution resolution of the renderTexture - * @returns {PIXI.RenderTexture} - */ - getFilterTexture(resolution) - { - const rt = this.activeState.renderTexture; - - const filterTexture = this.getPotFilterTexture(rt.width, rt.height, resolution || rt.baseTexture.resolution); - - filterTexture.filterFrame = rt.filterFrame; - - return filterTexture; - } - - /** - * Frees a render texture back into the pool. - * - * @param {PIXI.RenderTarget} renderTexture - The renderTarget to free - */ - returnFilterTexture(renderTexture) - { - renderTexture.filterFrame = null; - - const base = renderTexture.baseTexture; - - const minWidth = base.width; - const minHeight = base.height; - - const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); - - this.texturePool[key].push(renderTexture); - } - - /** - * Empties the texture pool. - * - */ - emptyPool() - { - for (const i in this.texturePool) - { - const textures = this.texturePool[i]; - - if (textures) - { - for (let j = 0; j < textures.length; j++) - { - textures[j].destroy(true); - } - } - } - - this.texturePool = {}; - } -} diff --git a/packages/core/src/renderers/systems/geometry/GLBuffer.js b/packages/core/src/renderers/systems/geometry/GLBuffer.js deleted file mode 100644 index 0477e8a..0000000 --- a/packages/core/src/renderers/systems/geometry/GLBuffer.js +++ /dev/null @@ -1,9 +0,0 @@ -export default class GLBuffer -{ - constructor(buffer) - { - this.buffer = buffer; - this.updateID = -1; - this.byteLength = -1; - } -} diff --git a/packages/core/src/renderers/systems/geometry/GeometrySystem.js b/packages/core/src/renderers/systems/geometry/GeometrySystem.js deleted file mode 100644 index 26c8e9e..0000000 --- a/packages/core/src/renderers/systems/geometry/GeometrySystem.js +++ /dev/null @@ -1,388 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; -import GLBuffer from './GLBuffer'; - -const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class GeometrySystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this._activeGeometry = null; - this._activeVao = null; - - this.hasVao = true; - this.hasInstance = true; - } - - /** - * Sets up the renderer context and necessary buffers. - * - * @private - */ - contextChange() - { - const gl = this.gl = this.renderer.gl; - - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - - // webgl2 - if (!gl.createVertexArray) - { - // webgl 1! - let nativeVaoExtension = this.renderer.context.extensions.vertexArrayObject; - - if (this.renderer.legacy) - { - nativeVaoExtension = null; - } - - if (nativeVaoExtension) - { - gl.createVertexArray = () => - nativeVaoExtension.createVertexArrayOES(); - - gl.bindVertexArray = (vao) => - nativeVaoExtension.bindVertexArrayOES(vao); - - gl.deleteVertexArray = (vao) => - nativeVaoExtension.deleteVertexArrayOES(vao); - } - else - { - this.hasVao = false; - gl.createVertexArray = () => - { - // empty - }; - - gl.bindVertexArray = () => - { - // empty - }; - - gl.deleteVertexArray = () => - { - // empty - }; - } - } - - if (!gl.vertexAttribDivisor) - { - const instanceExt = gl.getExtension('ANGLE_instanced_arrays'); - - if (instanceExt) - { - gl.vertexAttribDivisor = (a, b) => - instanceExt.vertexAttribDivisorANGLE(a, b); - - gl.drawElementsInstanced = (a, b, c, d, e) => - instanceExt.drawElementsInstancedANGLE(a, b, c, d, e); - - gl.drawArraysInstanced = (a, b, c, d) => - instanceExt.drawArraysInstancedANGLE(a, b, c, d); - } - else - { - this.hasInstance = false; - } - } - } - - /** - * Binds geometry so that is can be drawn. Creating a Vao if required - * @private - * @param {PIXI.Geometry} geometry instance of geometry to bind - */ - bind(geometry, shader) - { - shader = shader || this.renderer.shader.shader; - - const gl = this.gl; - - // not sure the best way to address this.. - // currently different shaders require different VAOs for the same geometry - // Still mulling over the best way to solve this one.. - // will likely need to modify the shader attribute locations at run time! - let vaos = geometry.glVertexArrayObjects[this.CONTEXT_UID]; - - if (!vaos) - { - geometry.glVertexArrayObjects[this.CONTEXT_UID] = vaos = {}; - } - - const vao = vaos[shader.program.id] || this.initGeometryVao(geometry, shader.program); - - this._activeGeometry = geometry; - - if (this._activeVao !== vao) - { - this._activeVao = vao; - - if (this.hasVao) - { - gl.bindVertexArray(vao); - } - else - { - this.activateVao(geometry, shader.program); - } - } - - // TODO - optimise later! - // don't need to loop through if nothing changed! - // maybe look to add an 'autoupdate' to geometry? - this.updateBuffers(); - } - - reset() - { - this.unbind(); - } - - updateBuffers() - { - const geometry = this._activeGeometry; - const gl = this.gl; - - for (let i = 0; i < geometry.buffers.length; i++) - { - const buffer = geometry.buffers[i]; - - const glBuffer = buffer._glBuffers[this.CONTEXT_UID]; - - if (buffer._updateID !== glBuffer.updateID) - { - glBuffer.updateID = buffer._updateID; - - // TODO can cache this on buffer! maybe added a getter / setter? - const type = buffer.index ? gl.ELEMENT_ARRAY_BUFFER : gl.ARRAY_BUFFER; - const drawType = buffer.static ? gl.STATIC_DRAW : gl.DYNAMIC_DRAW; - - gl.bindBuffer(type, glBuffer.buffer); - - if (glBuffer.byteLength >= buffer.data.byteLength) - { - // offset is always zero for now! - gl.bufferSubData(type, 0, buffer.data); - } - else - { - gl.bufferData(type, buffer.data, drawType); - } - } - } - } - - checkCompatability(geometry, program) - { - // geometry must have at least all the attributes that the shader requires. - const geometryAttributes = geometry.attributes; - const shaderAttributes = program.attributeData; - - for (const j in shaderAttributes) - { - if (!geometryAttributes[j]) - { - throw new Error(`shader and geometry incompatible, geometry missing the "${j}" attribute`); - } - } - } - - /** - * Creates a Vao with the same structure as the geometry and stores it on the geometry. - * @private - * @param {PIXI.Geometry} geometry instance of geometry to to generate Vao for - */ - initGeometryVao(geometry, program) - { - this.checkCompatability(geometry, program); - - const gl = this.gl; - const CONTEXT_UID = this.CONTEXT_UID; - const buffers = geometry.buffers; - const attributes = geometry.attributes; - - const tempStride = {}; - const tempStart = {}; - - for (const j in buffers) - { - tempStride[j] = 0; - tempStart[j] = 0; - } - - for (const j in attributes) - { - if (!attributes[j].size && program.attributeData[j]) - { - attributes[j].size = program.attributeData[j].size; - } - - tempStride[attributes[j].buffer] += attributes[j].size * byteSizeMap[attributes[j].type]; - } - - for (const j in attributes) - { - const attribute = attributes[j]; - const attribSize = attribute.size; - - if (attribute.stride === undefined) - { - if (tempStride[attribute.buffer] === attribSize * byteSizeMap[attribute.type]) - { - attribute.stride = 0; - } - else - { - attribute.stride = tempStride[attribute.buffer]; - } - } - - if (attribute.start === undefined) - { - attribute.start = tempStart[attribute.buffer]; - - tempStart[attribute.buffer] += attribSize * byteSizeMap[attribute.type]; - } - } - - // first update - and create the buffers! - // only create a gl buffer if it actually gets - for (let i = 0; i < buffers.length; i++) - { - const buffer = buffers[i]; - - if (!buffer._glBuffers[CONTEXT_UID]) - { - buffer._glBuffers[CONTEXT_UID] = new GLBuffer(gl.createBuffer()); - } - } - - // TODO - maybe make this a data object? - // lets wait to see if we need to first! - const vao = gl.createVertexArray(); - - gl.bindVertexArray(vao); - - this.activateVao(geometry, program); - - gl.bindVertexArray(null); - - // add it to the cache! - geometry.glVertexArrayObjects[this.CONTEXT_UID][program.id] = vao; - - return vao; - } - - activateVao(geometry, program) - { - const gl = this.gl; - const CONTEXT_UID = this.CONTEXT_UID; - const buffers = geometry.buffers; - const attributes = geometry.attributes; - - if (geometry.indexBuffer) - { - // first update the index buffer if we have one.. - gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, geometry.indexBuffer._glBuffers[CONTEXT_UID].buffer); - } - - let lastBuffer = null; - - // add a new one! - for (const j in attributes) - { - const attribute = attributes[j]; - const buffer = buffers[attribute.buffer]; - const glBuffer = buffer._glBuffers[CONTEXT_UID]; - - if (program.attributeData[j]) - { - if (lastBuffer !== glBuffer) - { - gl.bindBuffer(gl.ARRAY_BUFFER, glBuffer.buffer); - - lastBuffer = glBuffer; - } - - const location = program.attributeData[j].location; - - // TODO introduce state again - // we can optimise this for older devices that have no VAOs - gl.enableVertexAttribArray(location); - - gl.vertexAttribPointer(location, - attribute.size, - attribute.type || gl.FLOAT, - attribute.normalized, - attribute.stride, - attribute.start); - - if (attribute.instance) - { - // TODO calculate instance count based of this... - if (this.hasInstance) - { - gl.vertexAttribDivisor(location, 1); - } - else - { - throw new Error('geometry error, GPU Instancing is not supported on this device'); - } - } - } - } - } - - draw(type, size, start, instanceCount) - { - const gl = this.gl; - const geometry = this._activeGeometry; - - // TODO.. this should not change so maybe cache the function? - - if (geometry.indexBuffer) - { - if (geometry.instanced) - { - /* eslint-disable max-len */ - gl.drawElementsInstanced(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2, instanceCount || 1); - /* eslint-enable max-len */ - } - else - { - gl.drawElements(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2); - } - } - else - if (geometry.instanced) - { - // TODO need a better way to calculate size.. - gl.drawArraysInstanced(type, start, size || geometry.getSize(), instanceCount || 1); - } - else - { - gl.drawArrays(type, start, size || geometry.getSize()); - } - - return this; - } - - unbind() - { - this.gl.bindVertexArray(null); - this._activeVao = null; - this._activeGeometry = null; - } -} diff --git a/packages/core/src/renderers/systems/geometry/setVertexAttribArrays.js b/packages/core/src/renderers/systems/geometry/setVertexAttribArrays.js deleted file mode 100644 index 23b6959..0000000 --- a/packages/core/src/renderers/systems/geometry/setVertexAttribArrays.js +++ /dev/null @@ -1,54 +0,0 @@ -// var GL_MAP = {}; - -/** - * @param gl {WebGLRenderingContext} The current WebGL context - * @param attribs {*} - * @param state {*} - */ -export default function setVertexAttribArrays(gl, attribs, state) -{ - let i; - - if (state) - { - const tempAttribState = state.tempAttribState; - const attribState = state.attribState; - - for (i = 0; i < tempAttribState.length; i++) - { - tempAttribState[i] = false; - } - - // set the new attribs - for (i = 0; i < attribs.length; i++) - { - tempAttribState[attribs[i].attribute.location] = true; - } - - for (i = 0; i < attribState.length; i++) - { - if (attribState[i] !== tempAttribState[i]) - { - attribState[i] = tempAttribState[i]; - - if (state.attribState[i]) - { - gl.enableVertexAttribArray(i); - } - else - { - gl.disableVertexAttribArray(i); - } - } - } - } - else - { - for (i = 0; i < attribs.length; i++) - { - const attrib = attribs[i]; - - gl.enableVertexAttribArray(attrib.attribute.location); - } - } -} diff --git a/packages/core/src/renderers/systems/index.js b/packages/core/src/renderers/systems/index.js deleted file mode 100644 index c843f03..0000000 --- a/packages/core/src/renderers/systems/index.js +++ /dev/null @@ -1,6 +0,0 @@ -/** - * Systems are individual components to the Renderer pipeline. - * @namespace PIXI.systems - */ -export { default as WebGLSystem } from './WebGLSystem'; -export { default as FilterSystem } from './filter/FilterSystem'; diff --git a/packages/core/src/renderers/systems/shader/GLProgram.js b/packages/core/src/renderers/systems/shader/GLProgram.js deleted file mode 100644 index 92980ca..0000000 --- a/packages/core/src/renderers/systems/shader/GLProgram.js +++ /dev/null @@ -1,43 +0,0 @@ -/** - * Helper class to create a webGL Program - * - * @class - * @private - */ -export default class GLProgram -{ - constructor(program, uniformData) - { - /** - * The shader program - * - * @member {WebGLProgram} - */ - this.program = program; - - /** - * holds the uniform data which contains uniform locations - * and current uniform values used for caching and preventing undeed GPU commands - * @type {Object} - */ - this.uniformData = uniformData; - - /** - * uniformGroups holds the various upload functions for the shader. Each uniform group - * and program have a unique upload function generated. - * @type {Object} - */ - this.uniformGroups = {}; - } - - /** - * Destroys this program - * TODO - */ - destroy() - { - this.uniformData = null; - this.uniformGroups = null; - this.program = null; - } -} diff --git a/packages/core/src/renderers/systems/shader/ShaderSystem.js b/packages/core/src/renderers/systems/shader/ShaderSystem.js deleted file mode 100644 index 6cf57b6..0000000 --- a/packages/core/src/renderers/systems/shader/ShaderSystem.js +++ /dev/null @@ -1,170 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; -import GLProgram from './GLProgram'; -import generateUniformsSync from '../../../shader/generateUniformsSync'; -import defaultValue from '../../../shader/defaultValue'; -import compileProgram from './shader/compileProgram'; - -let UID = 0; - -/** - * Helper class to create a webGL Texture - * - * @class - * @memberof PIXI - */ -export default class ShaderSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - A reference to the current renderer - */ - constructor(renderer) - { - super(renderer); - - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = null; - - this.shader = null; - this.program = null; - - this.id = UID++; - } - - contextChange(gl) - { - this.gl = gl; - } - - /** - * Changes the current shader to the one given in parameter - * - * @param {PIXI.Shader} shader - the new shader - * @param {boolean} dontSync - false if the shader should automatically sync its uniforms. - * @returns {PIXI.glCore.glProgram} the glProgram that belongs to the shader. - */ - bind(shader, dontSync) - { - shader.uniforms.globals = this.renderer.globalUniforms; - - const program = shader.program; - const glProgram = program.glPrograms[this.renderer.CONTEXT_UID] || this.generateShader(shader); - - this.shader = shader; - - // TODO - some current pixi plugins bypass this.. so it not safe to use yet.. - if (this.program !== program) - { - this.program = program; - this.gl.useProgram(glProgram.program); - } - - if (!dontSync) - { - this.syncUniformGroup(shader.uniformGroup); - } - - return glProgram; - } - - /** - * Uploads the uniforms values to the currently bound shader. - * - * @param {object} uniforms - the uniforms valiues that be applied to the current shader - */ - setUniforms(uniforms) - { - const shader = this.shader.program; - const glProgram = shader.glPrograms[this.renderer.CONTEXT_UID]; - - shader.syncUniforms(glProgram.uniformData, uniforms, this.renderer); - } - - syncUniformGroup(group) - { - const glProgram = this.getglProgram(); - - if (!group.static || group.dirtyId !== glProgram.uniformGroups[group.id]) - { - glProgram.uniformGroups[group.id] = group.dirtyId; - const syncFunc = group.syncUniforms[this.shader.program.id] || this.createSyncGroups(group); - - syncFunc(glProgram.uniformData, group.uniforms, this.renderer); - } - } - - createSyncGroups(group) - { - group.syncUniforms[this.shader.program.id] = generateUniformsSync(group, this.shader.program.uniformData); - - return group.syncUniforms[this.shader.program.id]; - } - - /** - * Returns the underlying GLShade rof the currently bound shader. - * This can be handy for when you to have a little more control over the setting of your uniforms. - * - * @return {PIXI.glCore.Shader} the glProgram for the currently bound Shader for this context - */ - getglProgram() - { - if (this.shader) - { - return this.shader.program.glPrograms[this.renderer.CONTEXT_UID]; - } - - return null; - } - - /** - * Generates a glProgram verion of the Shader provided. - * - * @private - * @param {PIXI.Shader} shader the shader that the glProgram will be based on. - * @return {PIXI.glCore.glProgram} A shiney new glProgram - */ - generateShader(shader) - { - const gl = this.gl; - - const program = shader.program; - - const attribMap = {}; - - for (const i in program.attributeData) - { - attribMap[i] = program.attributeData[i].location; - } - - const shaderProgram = compileProgram(gl, program.vertexSrc, program.fragmentSrc, attribMap); - const uniformData = {}; - - for (const i in program.uniformData) - { - const data = program.uniformData[i]; - - uniformData[i] = { - location: gl.getUniformLocation(shaderProgram, i), - value: defaultValue(data.type, data.size), - }; - } - - const glProgram = new GLProgram(shaderProgram, uniformData); - - program.glPrograms[this.renderer.CONTEXT_UID] = glProgram; - - return glProgram; - } - - /** - * Destroys this System and removes all its textures - */ - destroy() - { - // TODO implement destroy method for ShaderSystem - this.destroyed = true; - } -} diff --git a/packages/core/src/renderers/systems/shader/shader/compileProgram.js b/packages/core/src/renderers/systems/shader/shader/compileProgram.js deleted file mode 100644 index 8c6ccb9..0000000 --- a/packages/core/src/renderers/systems/shader/shader/compileProgram.js +++ /dev/null @@ -1,78 +0,0 @@ -/** - * @method compileProgram - * @memberof PIXI.glCore.shader - * @param gl {WebGLRenderingContext} The current WebGL context {WebGLProgram} - * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. - * @param fragmentSrc {string|string[]} The fragment shader source as an array of strings. - * @param attributeLocations {Object} An attribute location map that lets you manually set the attribute locations - * @return {WebGLProgram} the shader program - */ -export default function compileProgram(gl, vertexSrc, fragmentSrc, attributeLocations) -{ - const glVertShader = compileShader(gl, gl.VERTEX_SHADER, vertexSrc); - const glFragShader = compileShader(gl, gl.FRAGMENT_SHADER, fragmentSrc); - - let program = gl.createProgram(); - - gl.attachShader(program, glVertShader); - gl.attachShader(program, glFragShader); - - // optionally, set the attributes manually for the program rather than letting WebGL decide.. - if (attributeLocations) - { - for (const i in attributeLocations) - { - gl.bindAttribLocation(program, attributeLocations[i], i); - } - } - - gl.linkProgram(program); - - // if linking fails, then log and cleanup - if (!gl.getProgramParameter(program, gl.LINK_STATUS)) - { - console.error('Pixi.js Error: Could not initialize shader.'); - console.error('gl.VALIDATE_STATUS', gl.getProgramParameter(program, gl.VALIDATE_STATUS)); - console.error('gl.getError()', gl.getError()); - - // if there is a program info log, log it - if (gl.getProgramInfoLog(program) !== '') - { - console.warn('Pixi.js Warning: gl.getProgramInfoLog()', gl.getProgramInfoLog(program)); - } - - gl.deleteProgram(program); - program = null; - } - - // clean up some shaders - gl.deleteShader(glVertShader); - gl.deleteShader(glFragShader); - - return program; -} - -/** - * @private - * @param gl {WebGLRenderingContext} The current WebGL context {WebGLProgram} - * @param type {Number} the type, can be either VERTEX_SHADER or FRAGMENT_SHADER - * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. - * @return {WebGLShader} the shader - */ -function compileShader(gl, type, src) -{ - const shader = gl.createShader(type); - - gl.shaderSource(shader, src); - gl.compileShader(shader); - - if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) - { - console.warn(src); - console.error(gl.getShaderInfoLog(shader)); - - return null; - } - - return shader; -} diff --git a/packages/core/src/renderers/systems/shader/shader/defaultValue.js b/packages/core/src/renderers/systems/shader/shader/defaultValue.js deleted file mode 100644 index 97290bd..0000000 --- a/packages/core/src/renderers/systems/shader/shader/defaultValue.js +++ /dev/null @@ -1,79 +0,0 @@ -/** - * @method defaultValue - * @memberof PIXI.glCore.shader - * @param type {String} Type of value - * @param size {Number} - */ -export default function defaultValue(type, size) -{ - switch (type) - { - case 'float': - return 0; - - case 'vec2': - return new Float32Array(2 * size); - - case 'vec3': - return new Float32Array(3 * size); - - case 'vec4': - return new Float32Array(4 * size); - - case 'int': - case 'sampler2D': - case 'sampler2DArray': - return 0; - - case 'ivec2': - return new Int32Array(2 * size); - - case 'ivec3': - return new Int32Array(3 * size); - - case 'ivec4': - return new Int32Array(4 * size); - - case 'bool': - return false; - - case 'bvec2': - - return booleanArray(2 * size); - - case 'bvec3': - return booleanArray(3 * size); - - case 'bvec4': - return booleanArray(4 * size); - - case 'mat2': - return new Float32Array([1, 0, - 0, 1]); - - case 'mat3': - return new Float32Array([1, 0, 0, - 0, 1, 0, - 0, 0, 1]); - - case 'mat4': - return new Float32Array([1, 0, 0, 0, - 0, 1, 0, 0, - 0, 0, 1, 0, - 0, 0, 0, 1]); - } - - return null; -} - -function booleanArray(size) -{ - const array = new Array(size); - - for (let i = 0; i < array.length; i++) - { - array[i] = false; - } - - return array; -} diff --git a/packages/core/src/renderers/systems/shader/shader/index.js b/packages/core/src/renderers/systems/shader/shader/index.js deleted file mode 100644 index ba7a317..0000000 --- a/packages/core/src/renderers/systems/shader/shader/index.js +++ /dev/null @@ -1,8 +0,0 @@ -/** - * @namespace PIXI.glCore.shader - */ -export { default as compileProgram } from './compileProgram'; -export { default as defaultValue } from './defaultValue'; -export { default as setPrecision } from './setPrecision'; -export { default as mapSize } from './mapSize'; -export { default as mapType } from './mapType'; diff --git a/packages/core/src/renderers/systems/shader/shader/mapSize.js b/packages/core/src/renderers/systems/shader/shader/mapSize.js deleted file mode 100644 index b681554..0000000 --- a/packages/core/src/renderers/systems/shader/shader/mapSize.js +++ /dev/null @@ -1,33 +0,0 @@ -const GLSL_TO_SIZE = { - float: 1, - vec2: 2, - vec3: 3, - vec4: 4, - - int: 1, - ivec2: 2, - ivec3: 3, - ivec4: 4, - - bool: 1, - bvec2: 2, - bvec3: 3, - bvec4: 4, - - mat2: 4, - mat3: 9, - mat4: 16, - - sampler2D: 1, -}; - -/** - * @method mapSize - * @memberof PIXI.glCore.shader - * @param type {String} - * @return {Number} - */ -export default function mapSize(type) -{ - return GLSL_TO_SIZE[type]; -} diff --git a/packages/core/src/renderers/systems/shader/shader/mapType.js b/packages/core/src/renderers/systems/shader/shader/mapType.js deleted file mode 100644 index d288b7c..0000000 --- a/packages/core/src/renderers/systems/shader/shader/mapType.js +++ /dev/null @@ -1,45 +0,0 @@ -let GL_TABLE = null; - -const GL_TO_GLSL_TYPES = { - FLOAT: 'float', - FLOAT_VEC2: 'vec2', - FLOAT_VEC3: 'vec3', - FLOAT_VEC4: 'vec4', - - INT: 'int', - INT_VEC2: 'ivec2', - INT_VEC3: 'ivec3', - INT_VEC4: 'ivec4', - - BOOL: 'bool', - BOOL_VEC2: 'bvec2', - BOOL_VEC3: 'bvec3', - BOOL_VEC4: 'bvec4', - - FLOAT_MAT2: 'mat2', - FLOAT_MAT3: 'mat3', - FLOAT_MAT4: 'mat4', - - SAMPLER_2D: 'sampler2D', - SAMPLER_CUBE: 'samplerCube', - SAMPLER_2D_ARRAY: 'sampler2DArray', -}; - -export default function mapType(gl, type) -{ - if (!GL_TABLE) - { - const typeNames = Object.keys(GL_TO_GLSL_TYPES); - - GL_TABLE = {}; - - for (let i = 0; i < typeNames.length; ++i) - { - const tn = typeNames[i]; - - GL_TABLE[gl[tn]] = GL_TO_GLSL_TYPES[tn]; - } - } - - return GL_TABLE[type]; -} diff --git a/packages/core/src/renderers/systems/shader/shader/setPrecision.js b/packages/core/src/renderers/systems/shader/shader/setPrecision.js deleted file mode 100644 index e2c1f34..0000000 --- a/packages/core/src/renderers/systems/shader/shader/setPrecision.js +++ /dev/null @@ -1,18 +0,0 @@ -/** - * Sets the float precision on the shader. If the precision is already present this function will do nothing - * @param {string} src the shader source - * @param {string} precision The float precision of the shader. Options are 'lowp', 'mediump' or 'highp'. - * - * @return {string} modified shader source - */ -export default function setPrecision(src, precision) -{ - src = src.trim(); - - if (src.substring(0, 9) !== 'precision' && src.substring(0, 1) !== '#') - { - return `precision ${precision} float;\n${src}`; - } - - return src; -} diff --git a/packages/core/src/renderers/systems/textures/GLTexture.js b/packages/core/src/renderers/systems/textures/GLTexture.js deleted file mode 100644 index c3fb8dc..0000000 --- a/packages/core/src/renderers/systems/textures/GLTexture.js +++ /dev/null @@ -1,33 +0,0 @@ -export default class GLTexture -{ - constructor(texture) - { - /** - * The WebGL texture - * - * @member {WebGLTexture} - */ - this.texture = texture; - - this.width = -1; - this.height = -1; - - /** - * Texture contents dirty flag - * @member {number} - */ - this.dirtyId = -1; - - /** - * Texture style dirty flag - * @type {number} - */ - this.dirtyStyleId = -1; - - /** - * Whether mip levels has to be generated - * @type {boolean} - */ - this.mipmap = false; - } -} diff --git a/packages/core/src/renderers/systems/textures/TextureGCSystem.js b/packages/core/src/renderers/systems/textures/TextureGCSystem.js deleted file mode 100644 index b089938..0000000 --- a/packages/core/src/renderers/systems/textures/TextureGCSystem.js +++ /dev/null @@ -1,110 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; -import { GC_MODES } from '@pixi/constants'; -import { settings } from '@pixi/settings'; - -/** - * TextureGarbageCollector. This class manages the GPU and ensures that it does not get clogged - * up with textures that are no longer being used. - * - * @class - * @memberof PIXI - */ -export default class TextureGCSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this.count = 0; - this.checkCount = 0; - this.maxIdle = settings.GC_MAX_IDLE; - this.checkCountMax = settings.GC_MAX_CHECK_COUNT; - this.mode = settings.GC_MODE; - } - - /** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ - postrender() - { - this.count++; - - if (this.mode === GC_MODES.MANUAL) - { - return; - } - - this.checkCount++; - - if (this.checkCount > this.checkCountMax) - { - this.checkCount = 0; - - this.run(); - } - } - - /** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ - run() - { - const tm = this.renderer.texture; - const managedTextures = tm.managedTextures; - let wasRemoved = false; - - for (let i = 0; i < managedTextures.length; i++) - { - const texture = managedTextures[i]; - - // only supports non generated textures at the moment! - if (!texture.frameBuffer && this.count - texture.touched > this.maxIdle) - { - tm.destroyTexture(texture, true); - managedTextures[i] = null; - wasRemoved = true; - } - } - - if (wasRemoved) - { - let j = 0; - - for (let i = 0; i < managedTextures.length; i++) - { - if (managedTextures[i] !== null) - { - managedTextures[j++] = managedTextures[i]; - } - } - - managedTextures.length = j; - } - } - - /** - * Removes all the textures within the specified displayObject and its children from the GPU - * - * @param {PIXI.DisplayObject} displayObject - the displayObject to remove the textures from. - */ - unload(displayObject) - { - const tm = this.renderer.textureSystem; - - // only destroy non generated textures - if (displayObject._texture && displayObject._texture._glRenderTargets) - { - tm.destroyTexture(displayObject._texture); - } - - for (let i = displayObject.children.length - 1; i >= 0; i--) - { - this.unload(displayObject.children[i]); - } - } -} diff --git a/packages/core/src/renderers/systems/textures/TextureSystem.js b/packages/core/src/renderers/systems/textures/TextureSystem.js deleted file mode 100644 index ccc5de9..0000000 --- a/packages/core/src/renderers/systems/textures/TextureSystem.js +++ /dev/null @@ -1,281 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; -import GLTexture from './GLTexture'; -import { removeItems } from '@pixi/utils'; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class TextureSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - // TODO set to max textures... - this.boundTextures = [ - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - ]; - - this.currentLocation = -1; - - this.managedTextures = []; - } - - /** - * Sets up the renderer context and necessary buffers. - * - * @private - */ - contextChange() - { - const gl = this.gl = this.renderer.gl; - - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - - // TODO move this.. to a nice make empty textures class.. - this.emptyTextures = {}; - - const emptyTexture2D = new GLTexture(gl.createTexture()); - - gl.bindTexture(gl.TEXTURE_2D, emptyTexture2D.texture); - gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array(4)); - - this.emptyTextures[gl.TEXTURE_2D] = emptyTexture2D; - this.emptyTextures[gl.TEXTURE_CUBE_MAP] = new GLTexture(gl.createTexture()); - - gl.bindTexture(gl.TEXTURE_CUBE_MAP, this.emptyTextures[gl.TEXTURE_CUBE_MAP].texture); - - let i; - - for (i = 0; i < 6; i++) - { - gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, null); - } - - gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_MAG_FILTER, gl.LINEAR); - gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_MIN_FILTER, gl.LINEAR); - - for (i = 0; i < this.boundTextures.length; i++) - { - this.bind(null, i); - } - } - - bind(texture, location) - { - const gl = this.gl; - - location = location || 0; - - if (this.currentLocation !== location) - { - this.currentLocation = location; - gl.activeTexture(gl.TEXTURE0 + location); - } - - if (texture) - { - texture = texture.baseTexture || texture; - - if (texture.valid) - { - texture.touched = this.renderer.textureGC.count; - - const glTexture = texture._glTextures[this.CONTEXT_UID] || this.initTexture(texture); - - gl.bindTexture(texture.target, glTexture.texture); - - if (glTexture.dirtyId !== texture.dirtyId) - { - this.updateTexture(texture); - } - - this.boundTextures[location] = texture; - } - } - else - { - gl.bindTexture(gl.TEXTURE_2D, this.emptyTextures[gl.TEXTURE_2D].texture); - this.boundTextures[location] = null; - } - } - - unbind(texture) - { - const gl = this.gl; - - for (let i = 0; i < this.boundTextures.length; i++) - { - if (this.boundTextures[i] === texture) - { - if (this.currentLocation !== i) - { - gl.activeTexture(gl.TEXTURE0 + i); - this.currentLocation = i; - } - - gl.bindTexture(gl.TEXTURE_2D, this.emptyTextures[texture.target].texture); - this.boundTextures[i] = null; - } - } - } - - initTexture(texture) - { - const glTexture = new GLTexture(this.gl.createTexture()); - - // guarentee an update.. - glTexture.dirtyId = -1; - - texture._glTextures[this.CONTEXT_UID] = glTexture; - - this.managedTextures.push(texture); - texture.on('dispose', this.destroyTexture, this); - - return glTexture; - } - - updateTexture(texture) - { - const glTexture = texture._glTextures[this.CONTEXT_UID]; - const renderer = this.renderer; - - if (texture.resource && texture.resource.upload(renderer, texture, glTexture)) - { - // texture is uploaded, dont do anything! - } - else - { - // default, renderTexture-like logic - const width = texture.realWidth; - const height = texture.realHeight; - const gl = renderer.gl; - - if (glTexture.width !== width - || glTexture.height !== height - || glTexture.dirtyId < 0) - { - glTexture.width = width; - glTexture.height = height; - - gl.texImage2D(texture.target, 0, - texture.format, - width, - height, - 0, - texture.format, - texture.type, - null); - } - } - - // lets only update what changes.. - if (texture.dirtyStyleId !== glTexture.dirtyStyleId) - { - this.updateTextureStyle(texture); - } - glTexture.dirtyId = texture.dirtyId; - } - - /** - * Deletes the texture from WebGL - * - * @param {PIXI.BaseTexture|PIXI.Texture} texture - the texture to destroy - * @param {boolean} [skipRemove=false] - Whether to skip removing the texture from the TextureManager. - */ - destroyTexture(texture, skipRemove) - { - const gl = this.gl; - - texture = texture.baseTexture || texture; - - if (texture._glTextures[this.renderer.CONTEXT_UID]) - { - this.unbind(texture); - - gl.deleteTexture(texture._glTextures[this.renderer.CONTEXT_UID].texture); - texture.off('dispose', this.destroyTexture, this); - - delete texture._glTextures[this.renderer.CONTEXT_UID]; - - if (!skipRemove) - { - const i = this.managedTextures.indexOf(texture); - - if (i !== -1) - { - removeItems(this.managedTextures, i, 1); - } - } - } - } - - updateTextureStyle(texture) - { - const glTexture = texture._glTextures[this.CONTEXT_UID]; - - glTexture.mipmap = texture.mipmap && texture.isPowerOfTwo; - if (!glTexture) - { - return; - } - - if (texture.resource && texture.resource.style(this.renderer, texture, glTexture)) - { - // style is set, dont do anything! - } - else - { - this.setStyle(texture, glTexture); - } - - glTexture.dirtyStyleId = texture.dirtyStyleId; - } - - setStyle(texture, glTexture) - { - const gl = this.gl; - - if (glTexture.mipmap) - { - gl.generateMipmap(texture.target); - } - - gl.texParameteri(texture.target, gl.TEXTURE_WRAP_S, texture.wrapMode); - gl.texParameteri(texture.target, gl.TEXTURE_WRAP_T, texture.wrapMode); - - if (glTexture.mipmap) - { - /* eslint-disable max-len */ - gl.texParameteri(texture.target, gl.TEXTURE_MIN_FILTER, texture.scaleMode ? gl.LINEAR_MIPMAP_LINEAR : gl.NEAREST_MIPMAP_NEAREST); - /* eslint-disable max-len */ - } - else - { - gl.texParameteri(texture.target, gl.TEXTURE_MIN_FILTER, texture.scaleMode ? gl.LINEAR : gl.NEAREST); - } - - gl.texParameteri(texture.target, gl.TEXTURE_MAG_FILTER, texture.scaleMode ? gl.LINEAR : gl.NEAREST); - } -} diff --git a/packages/core/src/renderers/utils/ObjectRenderer.js b/packages/core/src/renderers/utils/ObjectRenderer.js deleted file mode 100644 index f67a273..0000000 --- a/packages/core/src/renderers/utils/ObjectRenderer.js +++ /dev/null @@ -1,48 +0,0 @@ -import WebGLSystem from '../systems/WebGLSystem'; - -/** - * Base for a common object renderer that can be used as a system renderer plugin. - * - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class ObjectRenderer extends WebGLSystem -{ - /** - * Starts the renderer and sets the shader - * - */ - start() - { - // set the shader.. - } - - /** - * Stops the renderer - * - */ - stop() - { - this.flush(); - } - - /** - * Stub method for rendering content and emptying the current batch. - * - */ - flush() - { - // flush! - } - - /** - * Renders an object - * - * @param {PIXI.DisplayObject} object - The object to render. - */ - render(object) // eslint-disable-line no-unused-vars - { - // render the object - } -} diff --git a/packages/core/src/renderers/utils/Quad.js b/packages/core/src/renderers/utils/Quad.js deleted file mode 100644 index 5c1517b..0000000 --- a/packages/core/src/renderers/utils/Quad.js +++ /dev/null @@ -1,26 +0,0 @@ -import Geometry from '../../geometry/Geometry'; - -/** - * Helper class to create a quad - * - * @class - * @memberof PIXI - */ -export default class Quad extends Geometry -{ - /** - * just a quad - */ - constructor() - { - super(); - - this.addAttribute('aVertexPosition', [ - 0, 0, - 1, 0, - 1, 1, - 0, 1, - ]) - .addIndex([0, 1, 3, 2]); - } -} diff --git a/packages/core/src/renderers/utils/QuadUv.js b/packages/core/src/renderers/utils/QuadUv.js deleted file mode 100644 index 97bda89..0000000 --- a/packages/core/src/renderers/utils/QuadUv.js +++ /dev/null @@ -1,103 +0,0 @@ -import Geometry from '../../geometry/Geometry'; -import Buffer from '../../geometry/Buffer'; - -/** - * Helper class to create a quad with uvs like in v4 - * - * @class - * @memberof PIXI - */ -export default class QuadUv extends Geometry -{ - constructor() - { - super(); - - /** - * An array of vertices - * - * @member {Float32Array} - */ - this.vertices = new Float32Array([ - -1, -1, - 1, -1, - 1, 1, - -1, 1, - ]); - - /** - * The Uvs of the quad - * - * @member {Float32Array} - */ - this.uvs = new Float32Array([ - 0, 0, - 1, 0, - 1, 1, - 0, 1, - ]); - - this.vertexBuffer = new Buffer(this.vertices); - this.uvBuffer = new Buffer(this.uvs); - - this.addAttribute('aVertexPosition', this.vertexBuffer) - .addAttribute('aTextureCoord', this.uvBuffer) - .addIndex([0, 1, 2, 0, 2, 3]); - } - - /** - * Maps two Rectangle to the quad. - * - * @param {PIXI.Rectangle} targetTextureFrame - the first rectangle - * @param {PIXI.Rectangle} destinationFrame - the second rectangle - * @return {PIXI.Quad} Returns itself. - */ - map(targetTextureFrame, destinationFrame) - { - let x = 0; // destinationFrame.x / targetTextureFrame.width; - let y = 0; // destinationFrame.y / targetTextureFrame.height; - - this.uvs[0] = x; - this.uvs[1] = y; - - this.uvs[2] = x + (destinationFrame.width / targetTextureFrame.width); - this.uvs[3] = y; - - this.uvs[4] = x + (destinationFrame.width / targetTextureFrame.width); - this.uvs[5] = y + (destinationFrame.height / targetTextureFrame.height); - - this.uvs[6] = x; - this.uvs[7] = y + (destinationFrame.height / targetTextureFrame.height); - - x = destinationFrame.x; - y = destinationFrame.y; - - this.vertices[0] = x; - this.vertices[1] = y; - - this.vertices[2] = x + destinationFrame.width; - this.vertices[3] = y; - - this.vertices[4] = x + destinationFrame.width; - this.vertices[5] = y + destinationFrame.height; - - this.vertices[6] = x; - this.vertices[7] = y + destinationFrame.height; - - this.invalidate(); - - return this; - } - - /** - * legacy upload method, just marks buffers dirty - * @returns {PIXI.QuadUv} Returns itself. - */ - invalidate() - { - this.vertexBuffer._updateID++; - this.uvBuffer._updateID++; - - return this; - } -} diff --git a/packages/core/src/renderers/utils/checkMaxIfStatmentsInShader.js b/packages/core/src/renderers/utils/checkMaxIfStatmentsInShader.js deleted file mode 100644 index 8047c96..0000000 --- a/packages/core/src/renderers/utils/checkMaxIfStatmentsInShader.js +++ /dev/null @@ -1,58 +0,0 @@ -const fragTemplate = [ - 'precision mediump float;', - 'void main(void){', - 'float test = 0.1;', - '%forloop%', - 'gl_FragColor = vec4(0.0);', - '}', -].join('\n'); - -export default function checkMaxIfStatmentsInShader(maxIfs, gl) -{ - if (maxIfs === 0) - { - throw new Error('Invalid value of `0` passed to `checkMaxIfStatementsInShader`'); - } - - const shader = gl.createShader(gl.FRAGMENT_SHADER); - - while (true) // eslint-disable-line no-constant-condition - { - const fragmentSrc = fragTemplate.replace(/%forloop%/gi, generateIfTestSrc(maxIfs)); - - gl.shaderSource(shader, fragmentSrc); - gl.compileShader(shader); - - if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) - { - maxIfs = (maxIfs / 2) | 0; - } - else - { - // valid! - break; - } - } - - return maxIfs; -} - -function generateIfTestSrc(maxIfs) -{ - let src = ''; - - for (let i = 0; i < maxIfs; ++i) - { - if (i > 0) - { - src += '\nelse '; - } - - if (i < maxIfs - 1) - { - src += `if(test == ${i}.0){}`; - } - } - - return src; -} diff --git a/packages/core/src/renderers/utils/mapWebGLBlendModesToPixi.js b/packages/core/src/renderers/utils/mapWebGLBlendModesToPixi.js deleted file mode 100644 index 10ffe4d..0000000 --- a/packages/core/src/renderers/utils/mapWebGLBlendModesToPixi.js +++ /dev/null @@ -1,47 +0,0 @@ -import { BLEND_MODES } from '@pixi/constants'; - -/** - * Maps gl blend combinations to WebGL. - * - * @memberof PIXI - * @function mapWebGLBlendModesToPixi - * @private - * @param {WebGLRenderingContext} gl - The rendering context. - * @param {string[]} [array=[]] - The array to output into. - * @return {string[]} Mapped modes. - */ -export default function mapWebGLBlendModesToPixi(gl, array = []) -{ - // TODO - premultiply alpha would be different. - // add a boolean for that! - array[BLEND_MODES.NORMAL] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.ADD] = [gl.ONE, gl.DST_ALPHA]; - array[BLEND_MODES.MULTIPLY] = [gl.DST_COLOR, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.SCREEN] = [gl.ONE, gl.ONE_MINUS_SRC_COLOR]; - array[BLEND_MODES.OVERLAY] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.DARKEN] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.LIGHTEN] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.COLOR_DODGE] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.COLOR_BURN] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.HARD_LIGHT] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.SOFT_LIGHT] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.DIFFERENCE] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.EXCLUSION] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.HUE] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.SATURATION] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.COLOR] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.LUMINOSITY] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.NONE] = [0, 0]; - - // not-premultiplied blend modes - array[BLEND_MODES.NORMAL_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.ADD_NPM] = [gl.SRC_ALPHA, gl.DST_ALPHA, gl.ONE, gl.DST_ALPHA]; - array[BLEND_MODES.SCREEN_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_COLOR, gl.ONE, gl.ONE_MINUS_SRC_COLOR]; - - // not-premultiplied blend modes - array[BLEND_MODES.NORMAL_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.ADD_NPM] = [gl.SRC_ALPHA, gl.DST_ALPHA, gl.ONE, gl.DST_ALPHA]; - array[BLEND_MODES.SCREEN_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_COLOR, gl.ONE, gl.ONE_MINUS_SRC_COLOR]; - - return array; -} diff --git a/packages/core/src/shader/GLProgram.js b/packages/core/src/shader/GLProgram.js new file mode 100644 index 0000000..92980ca --- /dev/null +++ b/packages/core/src/shader/GLProgram.js @@ -0,0 +1,43 @@ +/** + * Helper class to create a webGL Program + * + * @class + * @private + */ +export default class GLProgram +{ + constructor(program, uniformData) + { + /** + * The shader program + * + * @member {WebGLProgram} + */ + this.program = program; + + /** + * holds the uniform data which contains uniform locations + * and current uniform values used for caching and preventing undeed GPU commands + * @type {Object} + */ + this.uniformData = uniformData; + + /** + * uniformGroups holds the various upload functions for the shader. Each uniform group + * and program have a unique upload function generated. + * @type {Object} + */ + this.uniformGroups = {}; + } + + /** + * Destroys this program + * TODO + */ + destroy() + { + this.uniformData = null; + this.uniformGroups = null; + this.program = null; + } +} diff --git a/packages/core/src/shader/Program.js b/packages/core/src/shader/Program.js index 4f9971e..4b4007a 100644 --- a/packages/core/src/shader/Program.js +++ b/packages/core/src/shader/Program.js @@ -1,6 +1,11 @@ -import * as shaderUtils from '../renderers/systems/shader/shader'; +// import * as from '../systems/shader/shader'; +import { setPrecision, + defaultValue, + compileProgram, + mapSize, + mapType, + getTestContext } from './utils'; import { ProgramCache } from '@pixi/utils'; -import getTestContext from './getTestContext'; import defaultFragment from './defaultProgram.frag'; import defaultVertex from './defaultProgram.vert'; import { settings } from '@pixi/settings'; @@ -34,8 +39,8 @@ */ this.fragmentSrc = fragmentSrc || Program.defaultFragmentSrc; - this.vertexSrc = shaderUtils.setPrecision(this.vertexSrc, settings.PRECISION_VERTEX); - this.fragmentSrc = shaderUtils.setPrecision(this.fragmentSrc, settings.PRECISION_FRAGMENT); + this.vertexSrc = setPrecision(this.vertexSrc, settings.PRECISION_VERTEX); + this.fragmentSrc = setPrecision(this.fragmentSrc, settings.PRECISION_FRAGMENT); // currently this does not extract structs only default types this.extractData(this.vertexSrc, this.fragmentSrc); @@ -62,7 +67,7 @@ if (gl) { - const program = shaderUtils.compileProgram(gl, vertexSrc, fragmentSrc); + const program = compileProgram(gl, vertexSrc, fragmentSrc); this.attributeData = this.getAttributeData(program, gl); this.uniformData = this.getUniformData(program, gl); @@ -95,13 +100,13 @@ for (let i = 0; i < totalAttributes; i++) { const attribData = gl.getActiveAttrib(program, i); - const type = shaderUtils.mapType(gl, attribData.type); + const type = mapType(gl, attribData.type); /*eslint-disable */ const data = { type: type, name: attribData.name, - size: shaderUtils.mapSize(type), + size: mapSize(type), location: 0, }; /* eslint-enable */ @@ -145,14 +150,14 @@ const name = uniformData.name.replace(/\[.*?\]/, ''); const isArray = uniformData.name.match(/\[.*?\]/, ''); - const type = shaderUtils.mapType(gl, uniformData.type); + const type = mapType(gl, uniformData.type); /*eslint-disable */ uniforms[name] = { type: type, size: uniformData.size, isArray:isArray, - value: shaderUtils.defaultValue(type, uniformData.size), + value: defaultValue(type, uniformData.size), }; /* eslint-enable */ } diff --git a/packages/core/src/shader/ShaderSystem.js b/packages/core/src/shader/ShaderSystem.js new file mode 100644 index 0000000..40b8af8 --- /dev/null +++ b/packages/core/src/shader/ShaderSystem.js @@ -0,0 +1,171 @@ +import System from '../System'; +import GLProgram from './GLProgram'; +import { generateUniformsSync, + defaultValue, + compileProgram } from './utils'; + +let UID = 0; + +/** + * Helper class to create a webGL Texture + * + * @class + * @memberof PIXI.systems + * @extends PIXI.System + */ +export default class ShaderSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - A reference to the current renderer + */ + constructor(renderer) + { + super(renderer); + + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = null; + + this.shader = null; + this.program = null; + + this.id = UID++; + } + + contextChange(gl) + { + this.gl = gl; + } + + /** + * Changes the current shader to the one given in parameter + * + * @param {PIXI.Shader} shader - the new shader + * @param {boolean} dontSync - false if the shader should automatically sync its uniforms. + * @returns {PIXI.glCore.glProgram} the glProgram that belongs to the shader. + */ + bind(shader, dontSync) + { + shader.uniforms.globals = this.renderer.globalUniforms; + + const program = shader.program; + const glProgram = program.glPrograms[this.renderer.CONTEXT_UID] || this.generateShader(shader); + + this.shader = shader; + + // TODO - some current pixi plugins bypass this.. so it not safe to use yet.. + if (this.program !== program) + { + this.program = program; + this.gl.useProgram(glProgram.program); + } + + if (!dontSync) + { + this.syncUniformGroup(shader.uniformGroup); + } + + return glProgram; + } + + /** + * Uploads the uniforms values to the currently bound shader. + * + * @param {object} uniforms - the uniforms valiues that be applied to the current shader + */ + setUniforms(uniforms) + { + const shader = this.shader.program; + const glProgram = shader.glPrograms[this.renderer.CONTEXT_UID]; + + shader.syncUniforms(glProgram.uniformData, uniforms, this.renderer); + } + + syncUniformGroup(group) + { + const glProgram = this.getglProgram(); + + if (!group.static || group.dirtyId !== glProgram.uniformGroups[group.id]) + { + glProgram.uniformGroups[group.id] = group.dirtyId; + const syncFunc = group.syncUniforms[this.shader.program.id] || this.createSyncGroups(group); + + syncFunc(glProgram.uniformData, group.uniforms, this.renderer); + } + } + + createSyncGroups(group) + { + group.syncUniforms[this.shader.program.id] = generateUniformsSync(group, this.shader.program.uniformData); + + return group.syncUniforms[this.shader.program.id]; + } + + /** + * Returns the underlying GLShade rof the currently bound shader. + * This can be handy for when you to have a little more control over the setting of your uniforms. + * + * @return {PIXI.glCore.Shader} the glProgram for the currently bound Shader for this context + */ + getglProgram() + { + if (this.shader) + { + return this.shader.program.glPrograms[this.renderer.CONTEXT_UID]; + } + + return null; + } + + /** + * Generates a glProgram verion of the Shader provided. + * + * @private + * @param {PIXI.Shader} shader the shader that the glProgram will be based on. + * @return {PIXI.glCore.glProgram} A shiney new glProgram + */ + generateShader(shader) + { + const gl = this.gl; + + const program = shader.program; + + const attribMap = {}; + + for (const i in program.attributeData) + { + attribMap[i] = program.attributeData[i].location; + } + + const shaderProgram = compileProgram(gl, program.vertexSrc, program.fragmentSrc, attribMap); + const uniformData = {}; + + for (const i in program.uniformData) + { + const data = program.uniformData[i]; + + uniformData[i] = { + location: gl.getUniformLocation(shaderProgram, i), + value: defaultValue(data.type, data.size), + }; + } + + const glProgram = new GLProgram(shaderProgram, uniformData); + + program.glPrograms[this.renderer.CONTEXT_UID] = glProgram; + + return glProgram; + } + + /** + * Destroys this System and removes all its textures + */ + destroy() + { + // TODO implement destroy method for ShaderSystem + this.destroyed = true; + } +} diff --git a/packages/core/src/shader/defaultValue.js b/packages/core/src/shader/defaultValue.js deleted file mode 100644 index 847a069..0000000 --- a/packages/core/src/shader/defaultValue.js +++ /dev/null @@ -1,78 +0,0 @@ -function booleanArray(size) -{ - const array = new Array(size); - - for (let i = 0; i < array.length; i++) - { - array[i] = false; - } - - return array; -} - -/** - * @class - * @memberof PIXI.glCore.shader - * @param type {String} Type of value - * @param size {Number} - */ -export default function defaultValue(type, size) -{ - switch (type) - { - case 'float': - return 0; - - case 'vec2': - return new Float32Array(2 * size); - - case 'vec3': - return new Float32Array(3 * size); - - case 'vec4': - return new Float32Array(4 * size); - - case 'int': - case 'sampler2D': - return 0; - - case 'ivec2': - return new Int32Array(2 * size); - - case 'ivec3': - return new Int32Array(3 * size); - - case 'ivec4': - return new Int32Array(4 * size); - - case 'bool': - return false; - - case 'bvec2': - - return booleanArray(2 * size); - - case 'bvec3': - return booleanArray(3 * size); - - case 'bvec4': - return booleanArray(4 * size); - - case 'mat2': - return new Float32Array([1, 0, - 0, 1]); - - case 'mat3': - return new Float32Array([1, 0, 0, - 0, 1, 0, - 0, 0, 1]); - - case 'mat4': - return new Float32Array([1, 0, 0, 0, - 0, 1, 0, 0, - 0, 0, 1, 0, - 0, 0, 0, 1]); - } - - return 0; -} diff --git a/packages/core/src/shader/extractUniformsFromSrc.js b/packages/core/src/shader/extractUniformsFromSrc.js deleted file mode 100644 index 3d02c0a..0000000 --- a/packages/core/src/shader/extractUniformsFromSrc.js +++ /dev/null @@ -1,57 +0,0 @@ -import defaultValue from './defaultValue'; - -export default function extractUniformsFromSrc(vertexSrc, fragmentSrc, mask) -{ - const vertUniforms = extractUniformsFromString(vertexSrc, mask); - const fragUniforms = extractUniformsFromString(fragmentSrc, mask); - - return Object.assign(vertUniforms, fragUniforms); -} - -function extractUniformsFromString(string) -{ - const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); - - const uniforms = {}; - let nameSplit; - - // clean the lines a little - remove extra spaces / teabs etc - // then split along ';' - const lines = string.replace(/\s+/g, ' ') - .split(/\s*;\s*/); - - // loop through.. - for (let i = 0; i < lines.length; i++) - { - const line = lines[i].trim(); - - if (line.indexOf('uniform') > -1) - { - const splitLine = line.split(' '); - const type = splitLine[1]; - - let name = splitLine[2]; - let size = 1; - - if (name.indexOf('[') > -1) - { - // array! - nameSplit = name.split(/\[|]/); - name = nameSplit[0]; - size *= Number(nameSplit[1]); - } - - if (!name.match(maskRegex)) - { - uniforms[name] = { - value: defaultValue(type, size), - dirtyId: 0, - name, - type, - }; - } - } - } - - return uniforms; -} diff --git a/packages/core/src/shader/generateUniformsSync.js b/packages/core/src/shader/generateUniformsSync.js deleted file mode 100644 index 98a357f..0000000 --- a/packages/core/src/shader/generateUniformsSync.js +++ /dev/null @@ -1,231 +0,0 @@ -// cv = CachedValue -// v = value -// ud = uniformData -// uv = uniformValue -// l = loaction -const GLSL_TO_SINGLE_SETTERS_CACHED = { - - float: ` - if(cv !== v) - { - cv.v = v; - gl.uniform1f(location, v) - }`, - - vec2: ` - if(cv[0] !== v[0] || cv[1] !== v[1]) - { - cv[0] = v[0]; - cv[1] = v[1]; - gl.uniform2f(location, v[0], v[1]) - }`, - - vec3: ` - if(cv[0] !== v[0] || cv[1] !== v[1] || cv[2] !== v[2]) - { - cv[0] = v[0]; - cv[1] = v[1]; - cv[2] = v[2]; - - gl.uniform3f(location, v[0], v[1], v[2]) - }`, - - vec4: 'gl.uniform4f(location, v[0], v[1], v[2], v[3])', - - int: 'gl.uniform1i(location, v)', - ivec2: 'gl.uniform2i(location, v[0], v[1])', - ivec3: 'gl.uniform3i(location, v[0], v[1], v[2])', - ivec4: 'gl.uniform4i(location, v[0], v[1], v[2], v[3])', - - bool: 'gl.uniform1i(location, v)', - bvec2: 'gl.uniform2i(location, v[0], v[1])', - bvec3: 'gl.uniform3i(location, v[0], v[1], v[2])', - bvec4: 'gl.uniform4i(location, v[0], v[1], v[2], v[3])', - - mat2: 'gl.uniformMatrix2fv(location, false, v)', - mat3: 'gl.uniformMatrix3fv(location, false, v)', - mat4: 'gl.uniformMatrix4fv(location, false, v)', - - sampler2D: 'gl.uniform1i(location, v)', - samplerCube: 'gl.uniform1i(location, v)', - sampler2DArray: 'gl.uniform1i(location, v)', -}; - -const GLSL_TO_ARRAY_SETTERS = { - - float: `gl.uniform1fv(location, v)`, - - vec2: `gl.uniform2fv(location, v)`, - vec3: `gl.uniform3fv(location, v)`, - vec4: 'gl.uniform4fv(location, v)', - - mat4: 'gl.uniformMatrix4fv(location, false, v)', - mat3: 'gl.uniformMatrix3fv(location, false, v)', - mat2: 'gl.uniformMatrix2fv(location, false, v)', - - int: 'gl.uniform1iv(location, v)', - ivec2: 'gl.uniform2iv(location, v)', - ivec3: 'gl.uniform3iv(location, v)', - ivec4: 'gl.uniform4iv(location, v)', - - bool: 'gl.uniform1iv(location, v)', - bvec2: 'gl.uniform2iv(location, v)', - bvec3: 'gl.uniform3iv(location, v)', - bvec4: 'gl.uniform4iv(location, v)', - - sampler2D: 'gl.uniform1iv(location, v)', - samplerCube: 'gl.uniform1iv(location, v)', - sampler2DArray: 'gl.uniform1iv(location, v)', -}; - -export default function generateUniformsSync(group, uniformData) -{ - let textureCount = 0; - let func = `var v = null; - var cv = null - var gl = renderer.gl`; - - for (const i in group.uniforms) - { - const data = uniformData[i]; - - if (!data) - { - if (group.uniforms[i].group) - { - func += ` - renderer.shader.syncUniformGroup(uv.${i}); - `; - } - - continue; - } - - // TODO && uniformData[i].value !== 0 <-- do we still need this? - if (data.type === 'float' && data.size === 1) - { - func += ` - if(uv.${i} !== ud.${i}.value) - { - ud.${i}.value = uv.${i} - gl.uniform1f(ud.${i}.location, uv.${i}) - }\n`; - } - /* eslint-disable max-len */ - else if ((data.type === 'sampler2D' || data.type === 'samplerCube' || data.type === 'sampler2DArray') && data.size === 1 && !data.isArray) - /* eslint-disable max-len */ - { - func += ` - renderer.texture.bind(uv.${i}, ${textureCount}); - - if(ud.${i}.value !== ${textureCount}) - { - ud.${i}.value = ${textureCount}; - gl.uniform1i(ud.${i}.location, ${textureCount});\n; // eslint-disable-line max-len - }\n`; - - textureCount++; - } - else if (data.type === 'mat3' && data.size === 1) - { - if (group.uniforms[i].a !== undefined) - { - // TODO and some smart caching dirty ids here! - func += ` - gl.uniformMatrix3fv(ud.${i}.location, false, uv.${i}.toArray(true)); - \n`; - } - else - { - func += ` - gl.uniformMatrix3fv(ud.${i}.location, false, uv.${i}); - \n`; - } - } - else if (data.type === 'vec2' && data.size === 1) - { - // TODO - do we need both here? - // maybe we can get away with only using points? - if (group.uniforms[i].x !== undefined) - { - func += ` - cv = ud.${i}.value; - v = uv.${i}; - - if(cv[0] !== v.x || cv[1] !== v.y) - { - cv[0] = v.x; - cv[1] = v.y; - gl.uniform2f(ud.${i}.location, v.x, v.y); - }\n`; - } - else - { - func += ` - cv = ud.${i}.value; - v = uv.${i}; - - if(cv[0] !== v[0] || cv[1] !== v[1]) - { - cv[0] = v[0]; - cv[1] = v[1]; - gl.uniform2f(ud.${i}.location, v[0], v[1]); - } - \n`; - } - } - else if (data.type === 'vec4' && data.size === 1) - { - // TODO - do we need both here? - // maybe we can get away with only using points? - if (group.uniforms[i].width !== undefined) - { - func += ` - cv = ud.${i}.value; - v = uv.${i}; - - if(cv[0] !== v.x || cv[1] !== v.y || cv[2] !== v.width || cv[3] !== v.height) - { - cv[0] = v.x; - cv[1] = v.y; - cv[2] = v.width; - cv[3] = v.height; - gl.uniform4f(ud.${i}.location, v.x, v.y, v.width, v.height) - }\n`; - } - else - { - func += ` - cv = ud.${i}.value; - v = uv.${i}; - - if(cv[0] !== v[0] || cv[1] !== v[1] || cv[2] !== v[2] || cv[3] !== v[3]) - { - cv[0] = v[0]; - cv[1] = v[1]; - cv[2] = v[2]; - cv[3] = v[3]; - - gl.uniform4f(ud.${i}.location, v[0], v[1], v[2], v[3]) - } - \n`; - } - } - else - { - const templateType = (data.size === 1) ? GLSL_TO_SINGLE_SETTERS_CACHED : GLSL_TO_ARRAY_SETTERS; - - const template = templateType[data.type].replace('location', `ud.${i}.location`); - - func += ` - cv = ud.${i}.value; - v = uv.${i}; - ${template};\n`; - } - } - - // console.log(' --------------- ') - // console.log(func); - - return new Function('ud', 'uv', 'renderer', func); // eslint-disable-line no-new-func -} diff --git a/packages/core/src/shader/getTestContext.js b/packages/core/src/shader/getTestContext.js deleted file mode 100644 index ea174cc..0000000 --- a/packages/core/src/shader/getTestContext.js +++ /dev/null @@ -1,48 +0,0 @@ -import { settings } from '@pixi/settings'; - -let context = null; - -/** - * returns a little webGL context to use for program inspection. - * - * @static - * @private - * @returns {webGL-context} a gl context to test with - */ -export default function getTestContext() -{ - if (!context) - { - const canvas = document.createElement('canvas'); - - let gl; - - if (settings.PREFER_WEBGL_2) - { - gl = canvas.getContext('webgl2', {}); - } - - if (!gl) - { - gl = canvas.getContext('webgl', {}) - || canvas.getContext('experimental-webgl', {}); - - if (!gl) - { - // fail, not able to get a context - throw new Error('This browser does not support webGL. Try using the canvas renderer'); - } - else - { - // for shader testing.. - gl.getExtension('WEBGL_draw_buffers'); - } - } - - context = gl; - - return gl; - } - - return context; -} diff --git a/packages/core/src/shader/utils/checkMaxIfStatmentsInShader.js b/packages/core/src/shader/utils/checkMaxIfStatmentsInShader.js new file mode 100644 index 0000000..8047c96 --- /dev/null +++ b/packages/core/src/shader/utils/checkMaxIfStatmentsInShader.js @@ -0,0 +1,58 @@ +const fragTemplate = [ + 'precision mediump float;', + 'void main(void){', + 'float test = 0.1;', + '%forloop%', + 'gl_FragColor = vec4(0.0);', + '}', +].join('\n'); + +export default function checkMaxIfStatmentsInShader(maxIfs, gl) +{ + if (maxIfs === 0) + { + throw new Error('Invalid value of `0` passed to `checkMaxIfStatementsInShader`'); + } + + const shader = gl.createShader(gl.FRAGMENT_SHADER); + + while (true) // eslint-disable-line no-constant-condition + { + const fragmentSrc = fragTemplate.replace(/%forloop%/gi, generateIfTestSrc(maxIfs)); + + gl.shaderSource(shader, fragmentSrc); + gl.compileShader(shader); + + if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) + { + maxIfs = (maxIfs / 2) | 0; + } + else + { + // valid! + break; + } + } + + return maxIfs; +} + +function generateIfTestSrc(maxIfs) +{ + let src = ''; + + for (let i = 0; i < maxIfs; ++i) + { + if (i > 0) + { + src += '\nelse '; + } + + if (i < maxIfs - 1) + { + src += `if(test == ${i}.0){}`; + } + } + + return src; +} diff --git a/packages/core/src/shader/utils/compileProgram.js b/packages/core/src/shader/utils/compileProgram.js new file mode 100644 index 0000000..8c6ccb9 --- /dev/null +++ b/packages/core/src/shader/utils/compileProgram.js @@ -0,0 +1,78 @@ +/** + * @method compileProgram + * @memberof PIXI.glCore.shader + * @param gl {WebGLRenderingContext} The current WebGL context {WebGLProgram} + * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. + * @param fragmentSrc {string|string[]} The fragment shader source as an array of strings. + * @param attributeLocations {Object} An attribute location map that lets you manually set the attribute locations + * @return {WebGLProgram} the shader program + */ +export default function compileProgram(gl, vertexSrc, fragmentSrc, attributeLocations) +{ + const glVertShader = compileShader(gl, gl.VERTEX_SHADER, vertexSrc); + const glFragShader = compileShader(gl, gl.FRAGMENT_SHADER, fragmentSrc); + + let program = gl.createProgram(); + + gl.attachShader(program, glVertShader); + gl.attachShader(program, glFragShader); + + // optionally, set the attributes manually for the program rather than letting WebGL decide.. + if (attributeLocations) + { + for (const i in attributeLocations) + { + gl.bindAttribLocation(program, attributeLocations[i], i); + } + } + + gl.linkProgram(program); + + // if linking fails, then log and cleanup + if (!gl.getProgramParameter(program, gl.LINK_STATUS)) + { + console.error('Pixi.js Error: Could not initialize shader.'); + console.error('gl.VALIDATE_STATUS', gl.getProgramParameter(program, gl.VALIDATE_STATUS)); + console.error('gl.getError()', gl.getError()); + + // if there is a program info log, log it + if (gl.getProgramInfoLog(program) !== '') + { + console.warn('Pixi.js Warning: gl.getProgramInfoLog()', gl.getProgramInfoLog(program)); + } + + gl.deleteProgram(program); + program = null; + } + + // clean up some shaders + gl.deleteShader(glVertShader); + gl.deleteShader(glFragShader); + + return program; +} + +/** + * @private + * @param gl {WebGLRenderingContext} The current WebGL context {WebGLProgram} + * @param type {Number} the type, can be either VERTEX_SHADER or FRAGMENT_SHADER + * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. + * @return {WebGLShader} the shader + */ +function compileShader(gl, type, src) +{ + const shader = gl.createShader(type); + + gl.shaderSource(shader, src); + gl.compileShader(shader); + + if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) + { + console.warn(src); + console.error(gl.getShaderInfoLog(shader)); + + return null; + } + + return shader; +} diff --git a/packages/core/src/shader/utils/defaultValue.js b/packages/core/src/shader/utils/defaultValue.js new file mode 100644 index 0000000..97290bd --- /dev/null +++ b/packages/core/src/shader/utils/defaultValue.js @@ -0,0 +1,79 @@ +/** + * @method defaultValue + * @memberof PIXI.glCore.shader + * @param type {String} Type of value + * @param size {Number} + */ +export default function defaultValue(type, size) +{ + switch (type) + { + case 'float': + return 0; + + case 'vec2': + return new Float32Array(2 * size); + + case 'vec3': + return new Float32Array(3 * size); + + case 'vec4': + return new Float32Array(4 * size); + + case 'int': + case 'sampler2D': + case 'sampler2DArray': + return 0; + + case 'ivec2': + return new Int32Array(2 * size); + + case 'ivec3': + return new Int32Array(3 * size); + + case 'ivec4': + return new Int32Array(4 * size); + + case 'bool': + return false; + + case 'bvec2': + + return booleanArray(2 * size); + + case 'bvec3': + return booleanArray(3 * size); + + case 'bvec4': + return booleanArray(4 * size); + + case 'mat2': + return new Float32Array([1, 0, + 0, 1]); + + case 'mat3': + return new Float32Array([1, 0, 0, + 0, 1, 0, + 0, 0, 1]); + + case 'mat4': + return new Float32Array([1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1]); + } + + return null; +} + +function booleanArray(size) +{ + const array = new Array(size); + + for (let i = 0; i < array.length; i++) + { + array[i] = false; + } + + return array; +} diff --git a/packages/core/src/shader/utils/extractUniformsFromSrc.js b/packages/core/src/shader/utils/extractUniformsFromSrc.js new file mode 100644 index 0000000..3d02c0a --- /dev/null +++ b/packages/core/src/shader/utils/extractUniformsFromSrc.js @@ -0,0 +1,57 @@ +import defaultValue from './defaultValue'; + +export default function extractUniformsFromSrc(vertexSrc, fragmentSrc, mask) +{ + const vertUniforms = extractUniformsFromString(vertexSrc, mask); + const fragUniforms = extractUniformsFromString(fragmentSrc, mask); + + return Object.assign(vertUniforms, fragUniforms); +} + +function extractUniformsFromString(string) +{ + const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); + + const uniforms = {}; + let nameSplit; + + // clean the lines a little - remove extra spaces / teabs etc + // then split along ';' + const lines = string.replace(/\s+/g, ' ') + .split(/\s*;\s*/); + + // loop through.. + for (let i = 0; i < lines.length; i++) + { + const line = lines[i].trim(); + + if (line.indexOf('uniform') > -1) + { + const splitLine = line.split(' '); + const type = splitLine[1]; + + let name = splitLine[2]; + let size = 1; + + if (name.indexOf('[') > -1) + { + // array! + nameSplit = name.split(/\[|]/); + name = nameSplit[0]; + size *= Number(nameSplit[1]); + } + + if (!name.match(maskRegex)) + { + uniforms[name] = { + value: defaultValue(type, size), + dirtyId: 0, + name, + type, + }; + } + } + } + + return uniforms; +} diff --git a/packages/core/src/shader/utils/generateUniformsSync.js b/packages/core/src/shader/utils/generateUniformsSync.js new file mode 100644 index 0000000..98a357f --- /dev/null +++ b/packages/core/src/shader/utils/generateUniformsSync.js @@ -0,0 +1,231 @@ +// cv = CachedValue +// v = value +// ud = uniformData +// uv = uniformValue +// l = loaction +const GLSL_TO_SINGLE_SETTERS_CACHED = { + + float: ` + if(cv !== v) + { + cv.v = v; + gl.uniform1f(location, v) + }`, + + vec2: ` + if(cv[0] !== v[0] || cv[1] !== v[1]) + { + cv[0] = v[0]; + cv[1] = v[1]; + gl.uniform2f(location, v[0], v[1]) + }`, + + vec3: ` + if(cv[0] !== v[0] || cv[1] !== v[1] || cv[2] !== v[2]) + { + cv[0] = v[0]; + cv[1] = v[1]; + cv[2] = v[2]; + + gl.uniform3f(location, v[0], v[1], v[2]) + }`, + + vec4: 'gl.uniform4f(location, v[0], v[1], v[2], v[3])', + + int: 'gl.uniform1i(location, v)', + ivec2: 'gl.uniform2i(location, v[0], v[1])', + ivec3: 'gl.uniform3i(location, v[0], v[1], v[2])', + ivec4: 'gl.uniform4i(location, v[0], v[1], v[2], v[3])', + + bool: 'gl.uniform1i(location, v)', + bvec2: 'gl.uniform2i(location, v[0], v[1])', + bvec3: 'gl.uniform3i(location, v[0], v[1], v[2])', + bvec4: 'gl.uniform4i(location, v[0], v[1], v[2], v[3])', + + mat2: 'gl.uniformMatrix2fv(location, false, v)', + mat3: 'gl.uniformMatrix3fv(location, false, v)', + mat4: 'gl.uniformMatrix4fv(location, false, v)', + + sampler2D: 'gl.uniform1i(location, v)', + samplerCube: 'gl.uniform1i(location, v)', + sampler2DArray: 'gl.uniform1i(location, v)', +}; + +const GLSL_TO_ARRAY_SETTERS = { + + float: `gl.uniform1fv(location, v)`, + + vec2: `gl.uniform2fv(location, v)`, + vec3: `gl.uniform3fv(location, v)`, + vec4: 'gl.uniform4fv(location, v)', + + mat4: 'gl.uniformMatrix4fv(location, false, v)', + mat3: 'gl.uniformMatrix3fv(location, false, v)', + mat2: 'gl.uniformMatrix2fv(location, false, v)', + + int: 'gl.uniform1iv(location, v)', + ivec2: 'gl.uniform2iv(location, v)', + ivec3: 'gl.uniform3iv(location, v)', + ivec4: 'gl.uniform4iv(location, v)', + + bool: 'gl.uniform1iv(location, v)', + bvec2: 'gl.uniform2iv(location, v)', + bvec3: 'gl.uniform3iv(location, v)', + bvec4: 'gl.uniform4iv(location, v)', + + sampler2D: 'gl.uniform1iv(location, v)', + samplerCube: 'gl.uniform1iv(location, v)', + sampler2DArray: 'gl.uniform1iv(location, v)', +}; + +export default function generateUniformsSync(group, uniformData) +{ + let textureCount = 0; + let func = `var v = null; + var cv = null + var gl = renderer.gl`; + + for (const i in group.uniforms) + { + const data = uniformData[i]; + + if (!data) + { + if (group.uniforms[i].group) + { + func += ` + renderer.shader.syncUniformGroup(uv.${i}); + `; + } + + continue; + } + + // TODO && uniformData[i].value !== 0 <-- do we still need this? + if (data.type === 'float' && data.size === 1) + { + func += ` + if(uv.${i} !== ud.${i}.value) + { + ud.${i}.value = uv.${i} + gl.uniform1f(ud.${i}.location, uv.${i}) + }\n`; + } + /* eslint-disable max-len */ + else if ((data.type === 'sampler2D' || data.type === 'samplerCube' || data.type === 'sampler2DArray') && data.size === 1 && !data.isArray) + /* eslint-disable max-len */ + { + func += ` + renderer.texture.bind(uv.${i}, ${textureCount}); + + if(ud.${i}.value !== ${textureCount}) + { + ud.${i}.value = ${textureCount}; + gl.uniform1i(ud.${i}.location, ${textureCount});\n; // eslint-disable-line max-len + }\n`; + + textureCount++; + } + else if (data.type === 'mat3' && data.size === 1) + { + if (group.uniforms[i].a !== undefined) + { + // TODO and some smart caching dirty ids here! + func += ` + gl.uniformMatrix3fv(ud.${i}.location, false, uv.${i}.toArray(true)); + \n`; + } + else + { + func += ` + gl.uniformMatrix3fv(ud.${i}.location, false, uv.${i}); + \n`; + } + } + else if (data.type === 'vec2' && data.size === 1) + { + // TODO - do we need both here? + // maybe we can get away with only using points? + if (group.uniforms[i].x !== undefined) + { + func += ` + cv = ud.${i}.value; + v = uv.${i}; + + if(cv[0] !== v.x || cv[1] !== v.y) + { + cv[0] = v.x; + cv[1] = v.y; + gl.uniform2f(ud.${i}.location, v.x, v.y); + }\n`; + } + else + { + func += ` + cv = ud.${i}.value; + v = uv.${i}; + + if(cv[0] !== v[0] || cv[1] !== v[1]) + { + cv[0] = v[0]; + cv[1] = v[1]; + gl.uniform2f(ud.${i}.location, v[0], v[1]); + } + \n`; + } + } + else if (data.type === 'vec4' && data.size === 1) + { + // TODO - do we need both here? + // maybe we can get away with only using points? + if (group.uniforms[i].width !== undefined) + { + func += ` + cv = ud.${i}.value; + v = uv.${i}; + + if(cv[0] !== v.x || cv[1] !== v.y || cv[2] !== v.width || cv[3] !== v.height) + { + cv[0] = v.x; + cv[1] = v.y; + cv[2] = v.width; + cv[3] = v.height; + gl.uniform4f(ud.${i}.location, v.x, v.y, v.width, v.height) + }\n`; + } + else + { + func += ` + cv = ud.${i}.value; + v = uv.${i}; + + if(cv[0] !== v[0] || cv[1] !== v[1] || cv[2] !== v[2] || cv[3] !== v[3]) + { + cv[0] = v[0]; + cv[1] = v[1]; + cv[2] = v[2]; + cv[3] = v[3]; + + gl.uniform4f(ud.${i}.location, v[0], v[1], v[2], v[3]) + } + \n`; + } + } + else + { + const templateType = (data.size === 1) ? GLSL_TO_SINGLE_SETTERS_CACHED : GLSL_TO_ARRAY_SETTERS; + + const template = templateType[data.type].replace('location', `ud.${i}.location`); + + func += ` + cv = ud.${i}.value; + v = uv.${i}; + ${template};\n`; + } + } + + // console.log(' --------------- ') + // console.log(func); + + return new Function('ud', 'uv', 'renderer', func); // eslint-disable-line no-new-func +} diff --git a/packages/core/src/shader/utils/getTestContext.js b/packages/core/src/shader/utils/getTestContext.js new file mode 100644 index 0000000..ea174cc --- /dev/null +++ b/packages/core/src/shader/utils/getTestContext.js @@ -0,0 +1,48 @@ +import { settings } from '@pixi/settings'; + +let context = null; + +/** + * returns a little webGL context to use for program inspection. + * + * @static + * @private + * @returns {webGL-context} a gl context to test with + */ +export default function getTestContext() +{ + if (!context) + { + const canvas = document.createElement('canvas'); + + let gl; + + if (settings.PREFER_WEBGL_2) + { + gl = canvas.getContext('webgl2', {}); + } + + if (!gl) + { + gl = canvas.getContext('webgl', {}) + || canvas.getContext('experimental-webgl', {}); + + if (!gl) + { + // fail, not able to get a context + throw new Error('This browser does not support webGL. Try using the canvas renderer'); + } + else + { + // for shader testing.. + gl.getExtension('WEBGL_draw_buffers'); + } + } + + context = gl; + + return gl; + } + + return context; +} diff --git a/packages/core/src/shader/utils/index.js b/packages/core/src/shader/utils/index.js new file mode 100644 index 0000000..4bf8dd4 --- /dev/null +++ b/packages/core/src/shader/utils/index.js @@ -0,0 +1,8 @@ +export { default as compileProgram } from './compileProgram'; +export { default as defaultValue } from './defaultValue'; +export { default as setPrecision } from './setPrecision'; +export { default as mapSize } from './mapSize'; +export { default as mapType } from './mapType'; +export { default as generateUniformsSync } from './generateUniformsSync'; +export { default as getTestContext } from './getTestContext'; +export { default as checkMaxIfStatmentsInShader } from './checkMaxIfStatmentsInShader'; diff --git a/packages/core/src/shader/utils/mapSize.js b/packages/core/src/shader/utils/mapSize.js new file mode 100644 index 0000000..b681554 --- /dev/null +++ b/packages/core/src/shader/utils/mapSize.js @@ -0,0 +1,33 @@ +const GLSL_TO_SIZE = { + float: 1, + vec2: 2, + vec3: 3, + vec4: 4, + + int: 1, + ivec2: 2, + ivec3: 3, + ivec4: 4, + + bool: 1, + bvec2: 2, + bvec3: 3, + bvec4: 4, + + mat2: 4, + mat3: 9, + mat4: 16, + + sampler2D: 1, +}; + +/** + * @method mapSize + * @memberof PIXI.glCore.shader + * @param type {String} + * @return {Number} + */ +export default function mapSize(type) +{ + return GLSL_TO_SIZE[type]; +} diff --git a/packages/core/src/shader/utils/mapType.js b/packages/core/src/shader/utils/mapType.js new file mode 100644 index 0000000..d288b7c --- /dev/null +++ b/packages/core/src/shader/utils/mapType.js @@ -0,0 +1,45 @@ +let GL_TABLE = null; + +const GL_TO_GLSL_TYPES = { + FLOAT: 'float', + FLOAT_VEC2: 'vec2', + FLOAT_VEC3: 'vec3', + FLOAT_VEC4: 'vec4', + + INT: 'int', + INT_VEC2: 'ivec2', + INT_VEC3: 'ivec3', + INT_VEC4: 'ivec4', + + BOOL: 'bool', + BOOL_VEC2: 'bvec2', + BOOL_VEC3: 'bvec3', + BOOL_VEC4: 'bvec4', + + FLOAT_MAT2: 'mat2', + FLOAT_MAT3: 'mat3', + FLOAT_MAT4: 'mat4', + + SAMPLER_2D: 'sampler2D', + SAMPLER_CUBE: 'samplerCube', + SAMPLER_2D_ARRAY: 'sampler2DArray', +}; + +export default function mapType(gl, type) +{ + if (!GL_TABLE) + { + const typeNames = Object.keys(GL_TO_GLSL_TYPES); + + GL_TABLE = {}; + + for (let i = 0; i < typeNames.length; ++i) + { + const tn = typeNames[i]; + + GL_TABLE[gl[tn]] = GL_TO_GLSL_TYPES[tn]; + } + } + + return GL_TABLE[type]; +} diff --git a/packages/core/src/shader/utils/setPrecision.js b/packages/core/src/shader/utils/setPrecision.js new file mode 100644 index 0000000..e2c1f34 --- /dev/null +++ b/packages/core/src/shader/utils/setPrecision.js @@ -0,0 +1,18 @@ +/** + * Sets the float precision on the shader. If the precision is already present this function will do nothing + * @param {string} src the shader source + * @param {string} precision The float precision of the shader. Options are 'lowp', 'mediump' or 'highp'. + * + * @return {string} modified shader source + */ +export default function setPrecision(src, precision) +{ + src = src.trim(); + + if (src.substring(0, 9) !== 'precision' && src.substring(0, 1) !== '#') + { + return `precision ${precision} float;\n${src}`; + } + + return src; +} diff --git a/packages/core/src/state/State.js b/packages/core/src/state/State.js new file mode 100644 index 0000000..fa3f221 --- /dev/null +++ b/packages/core/src/state/State.js @@ -0,0 +1,163 @@ +/* eslint-disable max-len */ + +const BLEND = 0; +const OFFSET = 1; +const CULLING = 2; +const DEPTH_TEST = 3; +const WINDING = 4; + +/** + * This is a webGL state. It is passed The WebGL StateManager. + * Each mesh renderered may require webGL to be in a different state. + * For example you may want different blend mode or to enable polygon offsets + * + * @class + * @memberof PIXI + */ +export default class State +{ + /** + * + */ + constructor() + { + this.data = 0; + + this.blendMode = 0; + this.polygonOffset = 0; + + this.blend = true; + // this.depthTest = true; + } + + /** + * Activates blending of the computed fragment color values + * + * @member {boolean} + */ + get blend() + { + return !!(this.data & (1 << BLEND)); + } + + set blend(value) // eslint-disable-line require-jsdoc + { + if (!!(this.data & (1 << BLEND)) !== value) + { + this.data ^= (1 << BLEND); + } + } + + /** + * Activates adding an offset to depth values of polygon's fragments + * + * @member {boolean} + * @default false + */ + get offsets() + { + return !!(this.data & (1 << OFFSET)); + } + + set offsets(value) // eslint-disable-line require-jsdoc + { + if (!!(this.data & (1 << OFFSET)) !== value) + { + this.data ^= (1 << OFFSET); + } + } + + /** + * Activates culling of polygons. + * + * @member {boolean} + * @default false + */ + get culling() + { + return !!(this.data & (1 << CULLING)); + } + + set culling(value) // eslint-disable-line require-jsdoc + { + if (!!(this.data & (1 << CULLING)) !== value) + { + this.data ^= (1 << CULLING); + } + } + + /** + * Activates depth comparisons and updates to the depth buffer. + * + * @member {boolean} + * @default false + */ + get depthTest() + { + return !!(this.data & (1 << DEPTH_TEST)); + } + + set depthTest(value) // eslint-disable-line require-jsdoc + { + if (!!(this.data & (1 << DEPTH_TEST)) !== value) + { + this.data ^= (1 << DEPTH_TEST); + } + } + + /** + * Specifies whether or not front or back-facing polygons can be culled. + * @member {boolean} + * @default false + */ + get clockwiseFrontFace() + { + return !!(this.data & (1 << WINDING)); + } + + set clockwiseFrontFace(value) // eslint-disable-line require-jsdoc + { + if (!!(this.data & (1 << WINDING)) !== value) + { + this.data ^= (1 << WINDING); + } + } + + /** + * The blend mode to be applied when this state is set. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. + * Setting this mode to anything other than NO_BLEND will automatically switch blending on. + * + * @member {boolean} + * @default PIXI.BLEND_MODES.NORMAL + * @see PIXI.BLEND_MODES + */ + get blendMode() + { + return this._blendMode; + } + + set blendMode(value) // eslint-disable-line require-jsdoc + { + // 17 is NO BLEND + this.blend = (value !== 17); + this._blendMode = value; + } + + /** + * The polygon offset. Setting this property to anything other than 0 will automatically enable poygon offset fill. + * + * @member {number} + * @default 0 + */ + get polygonOffset() + { + return this._polygonOffset; + } + + set polygonOffset(value) // eslint-disable-line require-jsdoc + { + this.offsets = !!value; + this._polygonOffset = value; + } +} + diff --git a/packages/core/src/state/StateSystem.js b/packages/core/src/state/StateSystem.js new file mode 100755 index 0000000..39c35bb --- /dev/null +++ b/packages/core/src/state/StateSystem.js @@ -0,0 +1,288 @@ +import mapWebGLBlendModesToPixi from './utils/mapWebGLBlendModesToPixi'; +import System from '../System'; +import WebGLState from './State'; + +const BLEND = 0; +const OFFSET = 1; +const CULLING = 2; +const DEPTH_TEST = 3; +const WINDING = 4; + +/** + * A WebGL state machines + * + * @class + * @extends PIXI.systems.System + * @memberof PIXI.systems + */ +export default class StateSystem extends System +{ + /** + * @param {WebGLRenderingContext} gl - The current WebGL rendering context + */ + constructor(renderer) + { + super(renderer); + + this.gl = null; + + this.maxAttribs = null; + + // check we have vao.. + this.nativeVaoExtension = null; + + this.attribState = null; + + this.stateId = 0; + this.polygonOffset = 0; + this.blendMode = 17; + + this.map = []; + + // map functions for when we set state.. + this.map[BLEND] = this.setBlend; + this.map[OFFSET] = this.setOffset; + this.map[CULLING] = this.setCullFace; + this.map[DEPTH_TEST] = this.setDepthTest; + this.map[WINDING] = this.setFrontFace; + + this.checks = []; + + this.defaultState = new WebGLState(); + this.defaultState.blend = true; + this.defaultState.depth = true; + } + + contextChange(gl) + { + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); + + // check we have vao.. + this.nativeVaoExtension = ( + gl.getExtension('OES_vertex_array_object') + || gl.getExtension('MOZ_OES_vertex_array_object') + || gl.getExtension('WEBKIT_OES_vertex_array_object') + ); + + this.attribState = { + tempAttribState: new Array(this.maxAttribs), + attribState: new Array(this.maxAttribs), + }; + + this.blendModes = mapWebGLBlendModesToPixi(gl); + + this.setState(this.defaultState); + + this.reset(); + } + + /** + * Sets the current state + * + * @param {*} state - The state to set. + */ + setState(state) + { + state = state || this.defaultState; + + // TODO maybe to an object check? ( this.state === state )? + if (this.stateId !== state.data) + { + let diff = this.stateId ^ state.data; + let i = 0; + + // order from least to most common + while (diff) + { + if (diff & 1) + { + // state change! + this.map[i].call(this, !!(state.data & (1 << i))); + } + + diff = diff >> 1; + i++; + } + + this.stateId = state.data; + } + + // based on the above settings we check for specific modes.. + // for example if blend is active we check and set the blend modes + // or of polygon offset is active we check the poly depth. + for (let i = 0; i < this.checks.length; i++) + { + this.checks[i](this, state); + } + } + + /** + * Enables or disabled blending. + * + * @param {boolean} value - Turn on or off webgl blending. + */ + setBlend(value) + { + this.updateCheck(StateSystem.checkBlendMode, value); + + this.gl[value ? 'enable' : 'disable'](this.gl.BLEND); + } + + /** + * Enables or disable polygon offset fill + * + * @param {boolean} value - Turn on or off webgl polygon offset testing. + */ + setOffset(value) + { + this.gl[value ? 'enable' : 'disable'](this.gl.POLYGON_OFFSET_FILL); + } + + /** + * Sets whether to enable or disable depth test. + * + * @param {boolean} value - Turn on or off webgl depth testing. + */ + setDepthTest(value) + { + this.gl[value ? 'enable' : 'disable'](this.gl.DEPTH_TEST); + } + + /** + * Sets whether to enable or disable cull face. + * + * @param {boolean} value - Turn on or off webgl cull face. + */ + setCullFace(value) + { + this.gl[value ? 'enable' : 'disable'](this.gl.CULL_FACE); + } + + /** + * Sets the gl front face. + * + * @param {boolean} value - true is clockwise and false is counter-clockwise + */ + setFrontFace(value) + { + this.gl.frontFace(this.gl[value ? 'CW' : 'CCW']); + } + + /** + * Sets the blend mode. + * + * @param {number} value - The blend mode to set to. + */ + setBlendMode(value) + { + if (value === this.blendMode) + { + return; + } + + this.blendMode = value; + this.gl.blendFunc(this.blendModes[value][0], this.blendModes[value][1]); + } + + /** + * Sets the polygon offset. + * + * @param {number} value - the polygon offset + * @param {number} scale - the polygon offset scale + */ + setPolygonOffset(value, scale) + { + this.gl.polygonOffset(value, scale); + } + + /** + * Disables all the vaos in use + * + */ + resetAttributes() + { + for (let i = 0; i < this.attribState.tempAttribState.length; i++) + { + this.attribState.tempAttribState[i] = 0; + } + + for (let i = 0; i < this.attribState.attribState.length; i++) + { + this.attribState.attribState[i] = 0; + } + + // im going to assume one is always active for performance reasons. + for (let i = 1; i < this.maxAttribs; i++) + { + this.gl.disableVertexAttribArray(i); + } + } + + // used + /** + * Resets all the logic and disables the vaos + */ + reset() + { + // unbind any VAO if they exist.. + if (this.nativeVaoExtension) + { + this.nativeVaoExtension.bindVertexArrayOES(null); + } + + // reset all attributes.. + this.resetAttributes(); + + this.gl.pixelStorei(this.gl.UNPACK_FLIP_Y_WEBGL, false); + + this.setBlendMode(0); + + // TODO? + // this.setState(this.defaultState); + } + + /** + * checks to see which updates should be checked based on which settings have been activated + * for example if blend is enabled then we shold check the blend modes each time the state is changed + * or if poygon fill is activated then we need to check if the polygone offset changes. + * The idea is that we only check what we have too. + * + * @param {Function} func the checking function to add or remove + * @param {boolean} value should the check function be added or removed. + */ + updateCheck(func, value) + { + const index = this.checks.indexOf(func); + + if (value && index === -1) + { + this.checks.push(func); + } + else if (!value && index !== -1) + { + this.checks.splice(index, 1); + } + } + + /** + * A private little wrapper function that we call to check the blend mode. + * + * @static + * @private + * @param {PIXI.StateSystem} System the System to perform the state check on + * @param {PIXI.State} state the state that the blendMode will pulled from + */ + static checkBlendMode(system, state) + { + system.setBlendMode(state.blendMode); + } + + // TODO - add polygon offset? +} diff --git a/packages/core/src/AbstractRenderer.js b/packages/core/src/AbstractRenderer.js new file mode 100644 index 0000000..64b2eb1 --- /dev/null +++ b/packages/core/src/AbstractRenderer.js @@ -0,0 +1,353 @@ +import { hex2string, hex2rgb } from '@pixi/utils'; +import { Matrix, Rectangle } from '@pixi/math'; +import { RENDERER_TYPE } from '@pixi/constants'; +import { settings } from '@pixi/settings'; +import { Container } from '@pixi/display'; +import RenderTexture from './renderTexture/RenderTexture'; +import EventEmitter from 'eventemitter3'; + +const tempMatrix = new Matrix(); + +/** + * 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 EventEmitter + * @memberof PIXI + */ +export default class AbstractRenderer extends EventEmitter +{ + // eslint-disable-next-line valid-jsdoc + /** + * @param {string} system - The name of the system this renderer is for. + * @param {object} [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.transparent=false] - If the render view is transparent, default false + * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false + * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. The + * resolution of the renderer retina would be 2. + * @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 {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when rendering, + * stopping pixel interpolation. + */ + constructor(system, options, arg2, arg3) + { + super(); + + // Support for constructor(system, screenWidth, screenHeight, options) + if (typeof options === 'number') + { + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + }, arg3); + } + + // 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 = RENDERER_TYPE.UNKNOWN; + + /** + * Measurements of the screen. (0, 0, screenWidth, screenHeight) + * + * Its safe to use as filterArea or hitArea for 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 1 + */ + this.resolution = options.resolution || settings.RESOLUTION; + + /** + * Whether the render view is transparent + * + * @member {boolean} + */ + this.transparent = options.transparent; + + /** + * Whether css dimensions of canvas view should be resized to screen dimensions automatically + * + * @member {boolean} + */ + this.autoResize = options.autoResize || false; + + /** + * Tracks the blend modes useful for this renderer. + * + * @member {object} + */ + this.blendModes = null; + + /** + * 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; + + /** + * If true PixiJS will Math.floor() x/y values when rendering, stopping pixel interpolation. + * Handy for crisp pixel art and speed on legacy devices. + * + * @member {boolean} + */ + this.roundPixels = options.roundPixels; + + /** + * The background color as a number. + * + * @member {number} + * @private + */ + this._backgroundColor = 0x000000; + + /** + * The background color as an [R, G, B] array. + * + * @member {number[]} + * @private + */ + this._backgroundColorRgba = [0, 0, 0, 0]; + + /** + * The background color as a string. + * + * @member {string} + * @private + */ + this._backgroundColorString = '#000000'; + + this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter + + /** + * This temporary display object used as the parent of the currently being rendered item + * + * @member {PIXI.DisplayObject} + * @private + */ + this._tempDisplayObjectParent = new Container(); + + /** + * The last root object that the renderer tried to render. + * + * @member {PIXI.DisplayObject} + * @private + */ + this._lastObjectRendered = this._tempDisplayObjectParent; + + /** + * Collection of plugins. + * @readonly + * @member {object} + */ + this.plugins = {}; + } + + /** + * Initialize the plugins. + * + * @protected + * @param {object} staticMap - The dictionary of staticly saved plugins. + */ + initPlugins(staticMap) + { + 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() + { + return this.view.width; + } + + /** + * Same as view.height, actual number of pixels in the canvas by vertical + * + * @member {number} + * @readonly + * @default 600 + */ + get height() + { + return this.view.height; + } + + /** + * Resizes the screen and canvas to the specified width and height + * Canvas dimensions are multiplied by resolution + * + * @param {number} screenWidth - the new width of the screen + * @param {number} screenHeight - the new height of the screen + */ + resize(screenWidth, screenHeight) + { + this.screen.width = screenWidth; + this.screen.height = screenHeight; + + this.view.width = screenWidth * this.resolution; + this.view.height = screenHeight * this.resolution; + + if (this.autoResize) + { + this.view.style.width = `${screenWidth}px`; + this.view.style.height = `${screenHeight}px`; + } + } + + /** + * 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 {PIXI.DisplayObject} displayObject - The displayObject the object will be generated from + * @param {number} scaleMode - Should be one of the scaleMode consts + * @param {number} resolution - The resolution / device pixel ratio of the texture being generated + * @param {PIXI.Rectangle} [region] - The region of the displayObject, that shall be rendered, + * if no region is specified, defaults to the local bounds of the displayObject. + * @return {PIXI.Texture} a texture of the graphics object + */ + generateTexture(displayObject, scaleMode, resolution, region) + { + region = region || displayObject.getLocalBounds(); + + // 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(region.width | 0, region.height | 0, scaleMode, resolution); + + tempMatrix.tx = -region.x; + tempMatrix.ty = -region.y; + + this.render(displayObject, renderTexture, false, tempMatrix, true); + + return renderTexture; + } + + /** + * Removes everything from the renderer and optionally removes the Canvas DOM element. + * + * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. + */ + destroy(removeView) + { + 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); + } + + this.plugins = null; + + this.type = RENDERER_TYPE.UNKNOWN; + + this.view = null; + + this.screen = null; + + this.resolution = 0; + + this.transparent = false; + + this.autoResize = false; + + this.blendModes = null; + + this.options = null; + + this.preserveDrawingBuffer = false; + this.clearBeforeRender = false; + + this.roundPixels = false; + + this._backgroundColor = 0; + this._backgroundColorRgba = null; + this._backgroundColorString = null; + + this._tempDisplayObjectParent = null; + this._lastObjectRendered = null; + } + + /** + * The background color to fill if not transparent + * + * @member {number} + */ + get backgroundColor() + { + return this._backgroundColor; + } + + set backgroundColor(value) // eslint-disable-line require-jsdoc + { + this._backgroundColor = value; + this._backgroundColorString = hex2string(value); + hex2rgb(value, this._backgroundColorRgba); + } +} diff --git a/packages/core/src/Renderer.js b/packages/core/src/Renderer.js new file mode 100644 index 0000000..2227e78 --- /dev/null +++ b/packages/core/src/Renderer.js @@ -0,0 +1,338 @@ +import AbstractRenderer from './AbstractRenderer'; +import { sayHello } from '@pixi/utils'; +import MaskSystem from './mask/MaskSystem'; +import StencilSystem from './mask/StencilSystem'; +import FilterSystem from './filters/FilterSystem'; +import FramebufferSystem from './framebuffer/FramebufferSystem'; +import RenderTextureSystem from './renderTexture/RenderTextureSystem'; +import TextureSystem from './textures/TextureSystem'; +import ProjectionSystem from './projection/ProjectionSystem'; +import StateSystem from './state/StateSystem'; +import GeometrySystem from './geometry/GeometrySystem'; +import ShaderSystem from './shader/ShaderSystem'; +import ContextSystem from './context/ContextSystem'; +import BatchSystem from './batch/BatchSystem'; +import TextureGCSystem from './textures/TextureGCSystem'; +import { RENDERER_TYPE } from '@pixi/constants'; +import UniformGroup from './shader/UniformGroup'; +import { Matrix } from '@pixi/math'; +import Runner from 'mini-runner'; + +/** + * 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 Render works by automatically managing webGLBatchs. + * 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 :) + * + * @class + * @memberof PIXI + * @extends PIXI.AbstractRenderer + */ +export default class Renderer extends AbstractRenderer +{ + // eslint-disable-next-line valid-jsdoc + /** + * + * @param {object} [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.transparent=false] - If the render view is transparent, default false + * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false + * @param {boolean} [options.antialias=false] - sets antialias. If not available natively then FXAA + * antialiasing is used + * @param {boolean} [options.forceFXAA=false] - forces FXAA antialiasing to be used over native. + * FXAA is faster, but may not always look as great + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. + * The resolution of the renderer retina would be 2. + * @param {boolean} [options.clearBeforeRender=true] - This sets if the renderer will clear + * the canvas or not before the new render pass. If you wish to set this to false, you *must* set + * preserveDrawingBuffer to `true`. + * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, + * enable this if you need to call toDataUrl on the webgl context. + * @param {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when + * rendering, stopping pixel interpolation. + * @param {number} [options.backgroundColor=0x000000] - The background color of the rendered area + * (shown if not transparent). + * @param {boolean} [options.legacy=false] - If true PixiJS will aim to ensure compatibility + * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. + * @param {string} [options.powerPreference] - Parameter passed to webgl context, set to "high-performance" + * for devices with dual graphics card + */ + constructor(options, arg2, arg3) + { + super('WebGL', options, arg2, arg3); + + /** + * The type of this renderer as a standardised const + * + * @member {number} + * @see PIXI.RENDERER_TYPE + */ + this.type = RENDERER_TYPE.WEBGL; + + // this will be set by the contextSystem (this.context) + this.gl = null; + this.CONTEXT_UID = 0; + this.legacy = !!options.legacy; + + // TODO legacy! + + // runners! + this.runners = { + destroy: new Runner('destroy'), + contextChange: new Runner('contextChange', 1), + reset: new Runner('reset'), + update: new Runner('update'), + postrender: new Runner('postrender'), + prerender: new Runner('prerender'), + resize: new Runner('resize', 2), + }; + + 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(GeometrySystem, 'geometry') + .addSystem(FramebufferSystem, 'framebuffer') + .addSystem(StencilSystem, 'stencil') + .addSystem(ProjectionSystem, 'projection') + .addSystem(TextureGCSystem, 'textureGC') + .addSystem(FilterSystem, 'filter') + .addSystem(RenderTextureSystem, 'renderTexture') + .addSystem(BatchSystem, 'batch'); + + this.initPlugins(Renderer.__plugins); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ + if (options.context) + { + this.context.initFromContext(options.context); + } + else + { + this.context.initFromOptions({ + alpha: this.transparent, + antialias: options.antialias, + premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', + stencil: true, + 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); + } + + /** + * Add a new system to the renderer. + * @param {class} ClassRef - Class reference + * @param {string} [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. + * @return {PIXI.Renderer} Return instance of renderer + */ + addSystem(ClassRef, name) + { + if (!name) + { + name = ClassRef.name; + } + + // TODO - read name from class.name.. + + /* + if(name.includes('System')) + { + name = name.replace('System', ''); + name = name.charAt(0).toLowerCase() + name.slice(1); + } + */ + + const system = new ClassRef(this); + + if (this[name]) + { + throw new Error(`Whoops! ${name} is already a manger`); + } + + this[name] = system; + + for (const i in this.runners) + { + this.runners[i].add(system); + } + + return this; + + /** + * 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. + */ + } + + /** + * Renders the object to its webGL view + * + * @param {PIXI.DisplayObject} displayObject - the object to be rendered + * @param {PIXI.RenderTexture} renderTexture - The render texture to render to. + * @param {boolean} [clear] - Should the canvas be cleared before the new render + * @param {PIXI.Transform} [transform] - A transform to apply to the render texture before rendering. + * @param {boolean} [skipUpdateTransform] - Should we skip the update transform pass? + */ + render(displayObject, renderTexture, clear, transform, skipUpdateTransform) + { + // can be handy to know! + this.renderingToScreen = !renderTexture; + + this.runners.prerender.run(); + this.emit('prerender'); + + // 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.parent; + + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = 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.run(); + + this.emit('postrender'); + } + + /** + * Resizes the webGL view to the specified width and height. + * + * @param {number} screenWidth - the new width of the screen + * @param {number} screenHeight - the new height of the screen + */ + resize(screenWidth, screenHeight) + { + AbstractRenderer.prototype.resize.call(this, screenWidth, screenHeight); + + this.runners.resize.run(screenWidth, screenHeight); + } + + /** + * Resets the WebGL state so you can render things however you fancy! + * + * @return {PIXI.Renderer} Returns itself. + */ + reset() + { + this.runners.reset.run(); + + return this; + } + + clear() + { + this.framebuffer.bind(); + this.framebuffer.clear(); + } + + /** + * Removes everything from the renderer (event listeners, spritebatch, etc...) + * + * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. + * See: https://github.com/pixijs/pixi.js/issues/2233 + */ + destroy(removeView) + { + this.runners.destroy.run(); + + // call base destroy + super.destroy(removeView); + + // TODO nullify all the managers.. + this.gl = null; + } + + /** + * 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 + * builds and excluding plugins. + * @name PIXI.Renderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. + */ + + /** + * Adds a plugin to the renderer. + * + * @method + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + static registerPlugin(pluginName, ctor) + { + Renderer.__plugins = Renderer.__plugins || {}; + Renderer.__plugins[pluginName] = ctor; + } +} diff --git a/packages/core/src/System.js b/packages/core/src/System.js new file mode 100644 index 0000000..05a10c8 --- /dev/null +++ b/packages/core/src/System.js @@ -0,0 +1,42 @@ +/** + * System is a base class used for extending systems used by the {@link PIXI.Renderer} + * @see PIXI.Renderer#addSystem + * @class + * @memberof PIXI + */ +export default class System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + /** + * The renderer this manager works for. + * + * @member {PIXI.Renderer} + */ + this.renderer = renderer; + + this.renderer.runners.contextChange.add(this); + } + + /** + * Generic method called when there is a WebGL context change. + * + */ + contextChange() + { + // do some codes init! + } + + /** + * Generic destroy methods to be overridden by the subclass + * + */ + destroy() + { + this.renderer.runners.contextChange.remove(this); + this.renderer = null; + } +} diff --git a/packages/core/src/batch/BatchSystem.js b/packages/core/src/batch/BatchSystem.js new file mode 100644 index 0000000..b04c249 --- /dev/null +++ b/packages/core/src/batch/BatchSystem.js @@ -0,0 +1,68 @@ +import System from '../System'; +import ObjectRenderer from './ObjectRenderer'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class BatchSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + super(renderer); + + /** + * An empty renderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.emptyRenderer = new ObjectRenderer(renderer); + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.currentRenderer = this.emptyRenderer; + } + + /** + * Changes the current renderer to the one given in parameter + * + * @param {PIXI.ObjectRenderer} objectRenderer - The object renderer to use. + */ + setObjectRenderer(objectRenderer) + { + if (this.currentRenderer === objectRenderer) + { + return; + } + + this.currentRenderer.stop(); + this.currentRenderer = objectRenderer; + + this.renderer.state.setState(objectRenderer.state); + + this.currentRenderer.start(); + } + + /** + * This should be called if you wish to do some custom rendering + * It will basically render anything that may be batched up such as sprites + * + */ + flush() + { + this.setObjectRenderer(this.emptyRenderer); + } + + reset() + { + this.setObjectRenderer(this.emptyRenderer); + } +} diff --git a/packages/core/src/batch/ObjectRenderer.js b/packages/core/src/batch/ObjectRenderer.js new file mode 100644 index 0000000..0c851a9 --- /dev/null +++ b/packages/core/src/batch/ObjectRenderer.js @@ -0,0 +1,48 @@ +import System from '../System'; + +/** + * Base for a common object renderer that can be used as a system renderer plugin. + * + * @class + * @extends PIXI.System + * @memberof PIXI + */ +export default class ObjectRenderer extends System +{ + /** + * Starts the renderer and sets the shader + * + */ + start() + { + // set the shader.. + } + + /** + * Stops the renderer + * + */ + stop() + { + this.flush(); + } + + /** + * Stub method for rendering content and emptying the current batch. + * + */ + flush() + { + // flush! + } + + /** + * Renders an object + * + * @param {PIXI.DisplayObject} object - The object to render. + */ + render(object) // eslint-disable-line no-unused-vars + { + // render the object + } +} diff --git a/packages/core/src/context/ContextSystem.js b/packages/core/src/context/ContextSystem.js new file mode 100644 index 0000000..301aab4 --- /dev/null +++ b/packages/core/src/context/ContextSystem.js @@ -0,0 +1,186 @@ +import System from '../System'; +import { settings } from '@pixi/settings'; + +let CONTEXT_UID = 0; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class ContextSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.webGLVersion = 1; + + this.handleContextLost = this.handleContextLost.bind(this); + this.handleContextRestored = this.handleContextRestored.bind(this); + + this.extensions = {}; + + renderer.view.addEventListener('webglcontextlost', this.handleContextLost, false); + renderer.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + } + + get isLost() + { + return (!this.gl || this.gl.isContextLost()); + } + + contextChange(gl) + { + this.gl = gl; + + // restore a context if it was previously lost + if (gl.isContextLost() && gl.getExtension('WEBGL_lose_context')) + { + gl.getExtension('WEBGL_lose_context').restoreContext(); + } + } + + initFromContext(gl) + { + this.gl = gl; + this.validateContext(gl); + this.renderer.gl = gl; + this.renderer.CONTEXT_UID = CONTEXT_UID++; + this.renderer.runners.contextChange.run(gl); + } + + initFromOptions(options) + { + const gl = this.createContext(this.renderer.view, options); + + this.initFromContext(gl); + } + + /** + * Helper class to create a webGL Context + * + * @class + * @memberof PIXI.glCore + * @param canvas {HTMLCanvasElement} the canvas element that we will get the context from + * @param options {Object} An options object that gets passed in to the canvas element containing the context attributes, + * see https://developer.mozilla.org/en/docs/Web/API/HTMLCanvasElement/getContext for the options available + * @return {WebGLRenderingContext} the WebGL context + */ + createContext(canvas, options) + { + let gl; + + if (settings.PREFER_WEBGL_2) + { + gl = canvas.getContext('webgl2', options); + } + + if (gl) + { + this.webGLVersion = 2; + } + else + { + this.webGLVersion = 1; + + gl = canvas.getContext('webgl', options) + || canvas.getContext('experimental-webgl', options); + + if (!gl) + { + // fail, not able to get a context + throw new Error('This browser does not support webGL. Try using the canvas renderer'); + } + } + + this.gl = gl; + + this.getExtensions(); + + return gl; + } + + getExtensions() + { + // time to set up default etensions that pixi uses.. + const gl = this.gl; + const extensions = this.extensions; + + if (this.webGLVersion === 1) + { + extensions.drawBuffers = gl.getExtension('WEBGL_draw_buffers'); + extensions.depthTexture = gl.getExtension('WEBKIT_WEBGL_depth_texture'); + extensions.floatTexture = gl.getExtension('OES_texture_float'); + extensions.loseContext = gl.getExtension('WEBGL_lose_context'); + + extensions.vertexArrayObject = gl.getExtension('OES_vertex_array_object') + || gl.getExtension('MOZ_OES_vertex_array_object') + || gl.getExtension('WEBKIT_OES_vertex_array_object'); + } + + // we don't use any specific WebGL 2 ones yet! + } + + /** + * Handles a lost webgl context + * + * @private + * @param {WebGLContextEvent} event - The context lost event. + */ + handleContextLost(event) + { + event.preventDefault(); + } + + /** + * Handles a restored webgl context + * + * @private + */ + handleContextRestored() + { + this.renderer.runners.contextChange.run(this.gl); + } + + destroy() + { + const view = this.renderer.view; + + // remove listeners + view.removeEventListener('webglcontextlost', this.handleContextLost); + view.removeEventListener('webglcontextrestored', this.handleContextRestored); + + this.gl.useProgram(null); + + if (this.extensions.loseContext) + { + this.extensions.loseContext.loseContext(); + } + } + + postrender() + { + this.gl.flush(); + } + + validateContext(gl) + { + const attributes = gl.getContextAttributes(); + + // this is going to be fairly simple for now.. but at least we have room to grow! + if (!attributes.stencil) + { + /* eslint-disable max-len */ + + /* eslint-disable no-console */ + console.warn('Provided WebGL context does not have a stencil buffer, masks may not render correctly'); + /* eslint-enable no-console */ + + /* eslint-enable max-len */ + } + } +} diff --git a/packages/core/src/filters/Filter.js b/packages/core/src/filters/Filter.js new file mode 100644 index 0000000..9d8cad8 --- /dev/null +++ b/packages/core/src/filters/Filter.js @@ -0,0 +1,140 @@ +import Shader from '../shader/Shader'; +import Program from '../shader/Program'; +import State from '../state/State'; +import { settings } from '@pixi/settings'; +// import extractUniformsFromSrc from './extractUniformsFromSrc'; +import defaultVertex from './defaultFilter.vert'; +import defaultFragment from './defaultFilter.frag'; + +// let math = require('../../math'); +/** + * @class + * @memberof PIXI + * @extends PIXI.Shader + */ +export default class Filter extends Shader +{ + /** + * @param {string} [vertexSrc] - The source of the vertex shader. + * @param {string} [fragmentSrc] - The source of the fragment shader. + * @param {object} [uniforms] - Custom uniforms to use to augment the built-in ones. + */ + constructor(vertexSrc, fragmentSrc, uniforms) + { + const program = Program.from(vertexSrc, fragmentSrc); + + super(program, uniforms); + + /** + * The padding of the filter. Some filters require extra space to breath such as a blur. + * Increasing this will add extra width and height to the bounds of the object that the + * filter is applied to. + * + * @member {number} + */ + this.padding = 0; + + /** + * The resolution of the filter. Setting this to be lower will lower the quality but + * increase the performance of the filter. + * + * @member {number} + */ + this.resolution = settings.RESOLUTION; + + /** + * If enabled is true the filter is applied, if false it will not. + * + * @member {boolean} + */ + this.enabled = true; + + /** + * If enabled, PixiJS will fit the filter area into boundaries for better performance. + * Switch it off if it does not work for specific shader. + * + * @member {boolean} + */ + this.autoFit = true; + + /** + * Legacy filters use position and uvs from attributes + * @member {boolean} + * @readonly + */ + this.legacy = !!this.program.attributeData.aTextureCoord; + + /** + * the webGL state the filter requires to render + * @member {PIXI.State} + */ + this.state = new State(); + } + + /** + * Applies the filter + * + * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + * @param {boolean} clear - Should the output be cleared before rendering to it + * @param {object} [currentState] - It's current state of filter. + * There are some useful properties in the currentState : + * target, filters, sourceFrame, destinationFrame, renderTarget, resolution + */ + apply(filterManager, input, output, clear, currentState, derp) // eslint-disable-line no-unused-vars + { + // do as you please! + + filterManager.applyFilter(this, input, output, clear, currentState, derp); + + // or just do a regular render.. + } + + /** + * Sets the blendmode of the filter + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL + */ + get blendMode() + { + return this.state.blendMode; + } + + set blendMode(value) // eslint-disable-line require-jsdoc + { + this.state.blendMode = value; + } + + /** + * The default vertex shader source + * + * @static + * @constant + */ + static get defaultVertexSrc() + { + return defaultVertex; + } + + /** + * The default fragment shader source + * + * @static + * @constant + */ + static get defaultFragmentSrc() + { + return defaultFragment; + } +} + +/** + * Used for caching shader IDs + * + * @static + * @private + */ +Filter.SOURCE_KEY_MAP = {}; + diff --git a/packages/core/src/filters/FilterSystem.js b/packages/core/src/filters/FilterSystem.js new file mode 100644 index 0000000..3174318 --- /dev/null +++ b/packages/core/src/filters/FilterSystem.js @@ -0,0 +1,431 @@ +import System from '../System'; + +import RenderTexture from '../renderTexture/RenderTexture'; +import Quad from '../utils/Quad'; +import QuadUv from '../utils/QuadUv'; +import { Rectangle } from '@pixi/math'; +import * as filterTransforms from './filterTransforms'; +import bitTwiddle from 'bit-twiddle'; +import UniformGroup from '../shader/UniformGroup'; +import { DRAW_MODES } from '@pixi/constants'; + +// +/** + * @ignore + * @class + */ +class FilterState +{ + /** + * + */ + constructor() + { + this.renderTexture = null; + this.sourceFrame = new Rectangle(); + this.destinationFrame = new Rectangle(); + this.filters = []; + this.target = null; + this.legacy = false; + this.resolution = 1; + } +} + +/** + * @class + * @memberof PIXI.systems + * @extends PIXI.System + */ +export default class FilterSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + /** + * stores a bunch of PO2 textures used for filtering + * @type {Object} + */ + this.texturePool = {}; + + /** + * a pool for storing filter states, save us creating new ones each tick + * @type {Array} + */ + this.statePool = []; + + /** + * A very simple geometry used when drawing a filter effect to the screen + * @type {Quad} + */ + this.quad = new Quad(); + + this.quadUv = new QuadUv(); + + /** + * Temporary rect for maths + * @type {PIXI.Rectangle} + */ + this.tempRect = new Rectangle(); + + this.activeState = {}; + + /** + * this uniform group is attached to filter uniforms when used + * @type {UniformGroup} + */ + this.globalUniforms = new UniformGroup({ + sourceFrame: this.tempRect, + destinationFrame: this.tempRect, + + // legacy variables + filterArea: new Float32Array(4), + filterClamp: new Float32Array(4), + }, true); + } + + /** + * Adds a new filter to the System. + * + * @param {PIXI.DisplayObject} target - The target of the filter to render. + * @param {PIXI.Filter[]} filters - The filters to apply. + */ + push(target, filters) + { + const renderer = this.renderer; + const filterStack = this.renderer.renderTexture.defaultFilterStack; + const state = this.statePool.pop() || new FilterState(); + + let resolution = filters[0].resolution; + let padding = filters[0].padding; + let autoFit = filters[0].autoFit; + let legacy = filters[0].legacy; + + for (let i = 1; i < filters.length; i++) + { + const filter = filters[i]; + + // lets use the lowest resolution.. + resolution = Math.min(resolution, filter.resolution); + // and the largest amount of padding! + padding = Math.max(padding, filter.padding); + // only auto fit if all filters are autofit + autoFit = autoFit || filter.autoFit; + + legacy = legacy || filter.legacy; + } + + filterStack.push(state); + + state.resolution = resolution; + + state.legacy = legacy; + + // round to whole number based on resolution + // TODO move that to the shader too? + state.sourceFrame = target.filterArea || target.getBounds(true); + + state.sourceFrame.pad(padding); + + if (autoFit) + { + state.sourceFrame.fit(this.renderer.renderTexture.destinationFrame); + } + + state.sourceFrame.round(resolution); + + state.renderTexture = this.getPotFilterTexture(state.sourceFrame.width, state.sourceFrame.height, resolution); + state.filters = filters; + + state.destinationFrame.width = state.renderTexture.width; + state.destinationFrame.height = state.renderTexture.height; + + state.renderTexture.filterFrame = state.sourceFrame; + renderer.renderTexture.bind(state.renderTexture, state.sourceFrame);// /, state.destinationFrame); + renderer.renderTexture.clear(); + } + + /** + * Pops off the filter and applies it. + * + */ + pop() + { + const renderer = this.renderer; + const filterStack = renderer.renderTexture.defaultFilterStack; + const state = filterStack.pop(); + const filters = state.filters; + + this.activeState = state; + + const globalUniforms = this.globalUniforms.uniforms; + + globalUniforms.sourceFrame = state.sourceFrame; + globalUniforms.destinationFrame = state.destinationFrame; + globalUniforms.resolution = state.resolution; + + // only update the rect if its legacy.. + if (state.legacy) + { + const filterArea = globalUniforms.filterArea; + const filterClamp = globalUniforms.filterClamp; + + filterArea[0] = state.destinationFrame.width; + filterArea[1] = state.destinationFrame.height; + filterArea[2] = state.sourceFrame.x; + filterArea[3] = state.sourceFrame.y; + + filterClamp[0] = 0.5 / state.resolution / state.destinationFrame.width; + filterClamp[1] = 0.5 / state.resolution / state.destinationFrame.height; + filterClamp[2] = (state.sourceFrame.width - 0.5) / state.resolution / state.destinationFrame.width; + filterClamp[3] = (state.sourceFrame.height - 0.5) / state.resolution / state.destinationFrame.height; + } + + this.globalUniforms.update(); + + const lastState = filterStack[filterStack.length - 1]; + + if (filters.length === 1) + { + filters[0].apply(this, state.renderTexture, lastState.renderTexture, false, state); + + this.returnFilterTexture(state.renderTexture); + } + else + { + let flip = state.renderTexture; + let flop = this.getPotFilterTexture( + flip.width, + flip.height, + state.resolution + ); + + flop.filterFrame = flip.filterFrame; + + let i = 0; + + for (i = 0; i < filters.length - 1; ++i) + { + filters[i].apply(this, flip, flop, true, state); + + const t = flip; + + flip = flop; + flop = t; + } + + filters[i].apply(this, flip, lastState.renderTexture, false, state); + + this.returnFilterTexture(flip); + this.returnFilterTexture(flop); + } + + this.statePool.push(state); + } + + /** + * Draws a filter. + * + * @param {PIXI.Filter} filter - The filter to draw. + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + * @param {boolean} clear - Should the output be cleared before rendering to it + */ + applyFilter(filter, input, output, clear) + { + const renderer = this.renderer; + + renderer.renderTexture.bind(output, output ? output.filterFrame : null); + + if (clear) + { + // gl.disable(gl.SCISSOR_TEST); + renderer.renderTexture.clear(); + // gl.enable(gl.SCISSOR_TEST); + } + + // set the uniforms.. + filter.uniforms.uSampler = input; + filter.uniforms.filterGlobals = this.globalUniforms; + + // TODO make it so that the order of this does not matter.. + // because it does at the moment cos of global uniforms. + // they need to get resynced + + renderer.state.setState(filter.state); + renderer.shader.bind(filter); + + if (filter.legacy) + { + this.quadUv.map(input._frame, input.filterFrame); + + renderer.geometry.bind(this.quadUv); + renderer.geometry.draw(DRAW_MODES.TRIANGLES); + } + else + { + renderer.geometry.bind(this.quad); + renderer.geometry.draw(DRAW_MODES.TRIANGLE_STRIP); + } + } + + /** + * Calculates the mapped matrix. + * + * TODO playing around here.. this is temporary - (will end up in the shader) + * this returns a matrix that will normalise map filter cords in the filter to screen space + * + * @param {PIXI.Matrix} outputMatrix - the matrix to output to. + * @return {PIXI.Matrix} The mapped matrix. + */ + calculateScreenSpaceMatrix(outputMatrix) + { + const currentState = this.activeState; + + return filterTransforms.calculateScreenSpaceMatrix( + outputMatrix, + currentState.sourceFrame, + currentState.destinationFrame + ); + } + + /** + * This will map the filter coord so that a texture can be used based on the transform of a sprite + * + * @param {PIXI.Matrix} outputMatrix - The matrix to output to. + * @param {PIXI.Sprite} sprite - The sprite to map to. + * @return {PIXI.Matrix} The mapped matrix. + */ + calculateSpriteMatrix(outputMatrix, sprite) + { + const currentState = this.activeState; + + return filterTransforms.calculateSpriteMatrix( + outputMatrix, + currentState.sourceFrame, + currentState.destinationFrame, + sprite + ); + } + + /** + * Destroys this Filter System. + * + * @param {boolean} [contextLost=false] context was lost, do not free shaders + * + */ + destroy(contextLost = false) + { + if (!contextLost) + { + this.emptyPool(); + } + else + { + this.texturePool = {}; + } + } + + /** + * Gets a Power-of-Two render texture. + * + * TODO move to a seperate class could be on renderer? + * also - could cause issue with multiple contexts? + * + * @private + * @param {WebGLRenderingContext} gl - The webgl rendering context + * @param {number} minWidth - The minimum width of the render target. + * @param {number} minHeight - The minimum height of the render target. + * @param {number} resolution - The resolution of the render target. + * @return {PIXI.RenderTarget} The new render target. + */ + getPotFilterTexture(minWidth, minHeight, resolution) + { + minWidth = bitTwiddle.nextPow2(minWidth); + minHeight = bitTwiddle.nextPow2(minHeight); + resolution = resolution || 1; + + const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + + if (!this.texturePool[key]) + { + this.texturePool[key] = []; + } + + let renderTexture = this.texturePool[key].pop(); + + if (!renderTexture) + { + // temporary bypass cache.. + // internally - this will cause a texture to be bound.. + renderTexture = RenderTexture.create({ + width: minWidth, + height: minHeight, + resolution, + }); + } + + return renderTexture; + } + + /** + * Gets extra render texture to use inside current filter + * + * @param {number} resolution resolution of the renderTexture + * @returns {PIXI.RenderTexture} + */ + getFilterTexture(resolution) + { + const rt = this.activeState.renderTexture; + + const filterTexture = this.getPotFilterTexture(rt.width, rt.height, resolution || rt.baseTexture.resolution); + + filterTexture.filterFrame = rt.filterFrame; + + return filterTexture; + } + + /** + * Frees a render texture back into the pool. + * + * @param {PIXI.RenderTarget} renderTexture - The renderTarget to free + */ + returnFilterTexture(renderTexture) + { + renderTexture.filterFrame = null; + + const base = renderTexture.baseTexture; + + const minWidth = base.width; + const minHeight = base.height; + + const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + + this.texturePool[key].push(renderTexture); + } + + /** + * Empties the texture pool. + * + */ + emptyPool() + { + for (const i in this.texturePool) + { + const textures = this.texturePool[i]; + + if (textures) + { + for (let j = 0; j < textures.length; j++) + { + textures[j].destroy(true); + } + } + } + + this.texturePool = {}; + } +} diff --git a/packages/core/src/filters/defaultFilter.frag b/packages/core/src/filters/defaultFilter.frag new file mode 100644 index 0000000..a14c1be --- /dev/null +++ b/packages/core/src/filters/defaultFilter.frag @@ -0,0 +1,22 @@ +varying vec2 vTextureCoord; +varying vec2 vFilterCoord; + +uniform sampler2D uSampler; +uniform sampler2D filterSampler; + +void main(void){ + vec4 masky = texture2D(filterSampler, vFilterCoord); + vec4 sample = texture2D(uSampler, vTextureCoord); + vec4 color; + if(mod(vFilterCoord.x, 1.0) > 0.5) + { + color = vec4(1.0, 0.0, 0.0, 1.0); + } + else + { + color = vec4(0.0, 1.0, 0.0, 1.0); + } + // gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0); + gl_FragColor = mix(sample, masky, 0.5); + gl_FragColor *= sample.a; +} diff --git a/packages/core/src/filters/defaultFilter.vert b/packages/core/src/filters/defaultFilter.vert new file mode 100644 index 0000000..e1febca --- /dev/null +++ b/packages/core/src/filters/defaultFilter.vert @@ -0,0 +1,14 @@ +attribute vec2 aVertexPosition; +attribute vec2 aTextureCoord; + +uniform mat3 projectionMatrix; +uniform mat3 filterMatrix; + +varying vec2 vTextureCoord; +varying vec2 vFilterCoord; + +void main(void){ + gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); + vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy; + vTextureCoord = aTextureCoord ; +} diff --git a/packages/core/src/filters/filterTransforms.js b/packages/core/src/filters/filterTransforms.js new file mode 100644 index 0000000..87a3cd6 --- /dev/null +++ b/packages/core/src/filters/filterTransforms.js @@ -0,0 +1,50 @@ +import { Matrix } from '@pixi/math'; + +/** + * Calculates the mapped matrix + * @param filterArea {Rectangle} The filter area + * @param sprite {Sprite} the target sprite + * @param outputMatrix {Matrix} @alvin + * @private + */ +// this returns a matrix that will normalise map filter cords in the filter to screen space +export function calculateScreenSpaceMatrix(outputMatrix, filterArea, textureSize) +{ + // TODO unwrap? + const mappedMatrix = outputMatrix.identity(); + + mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); + + mappedMatrix.scale(textureSize.width, textureSize.height); + + return mappedMatrix; +} + +export function calculateNormalizedScreenSpaceMatrix(outputMatrix, filterArea, textureSize) +{ + const mappedMatrix = outputMatrix.identity(); + + mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); + + const translateScaleX = (textureSize.width / filterArea.width); + const translateScaleY = (textureSize.height / filterArea.height); + + mappedMatrix.scale(translateScaleX, translateScaleY); + + return mappedMatrix; +} + +// this will map the filter coord so that a texture can be used based on the transform of a sprite +export function calculateSpriteMatrix(outputMatrix, filterArea, textureSize, sprite) +{ + const orig = sprite._texture.orig; + const mappedMatrix = outputMatrix.set(textureSize.width, 0, 0, textureSize.height, filterArea.x, filterArea.y); + const worldTransform = sprite.worldTransform.copyTo(Matrix.TEMP_MATRIX); + + worldTransform.invert(); + mappedMatrix.prepend(worldTransform); + mappedMatrix.scale(1.0 / orig.width, 1.0 / orig.height); + mappedMatrix.translate(sprite.anchor.x, sprite.anchor.y); + + return mappedMatrix; +} diff --git a/packages/core/src/filters/spriteMask/SpriteMaskFilter.js b/packages/core/src/filters/spriteMask/SpriteMaskFilter.js new file mode 100644 index 0000000..91697b7 --- /dev/null +++ b/packages/core/src/filters/spriteMask/SpriteMaskFilter.js @@ -0,0 +1,63 @@ +import Filter from '../Filter'; +import { Matrix } from '@pixi/math'; +import vertex from './spriteMaskFilter.vert'; +import fragment from './spriteMaskFilter.frag'; +import { default as TextureMatrix } from '../../textures/TextureMatrix'; + +/** + * The SpriteMaskFilter class + * + * @class + * @extends PIXI.Filter + * @memberof PIXI + */ +export default class SpriteMaskFilter extends Filter +{ + /** + * @param {PIXI.Sprite} sprite - the target sprite + */ + constructor(sprite) + { + const maskMatrix = new Matrix(); + + super(vertex, fragment); + + sprite.renderable = false; + + this.maskSprite = sprite; + this.maskMatrix = maskMatrix; + } + + /** + * Applies the filter + * + * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + */ + apply(filterManager, input, output) + { + const maskSprite = this.maskSprite; + const tex = this.maskSprite.texture; + + if (!tex.valid) + { + return; + } + if (!tex.transform) + { + // margin = 0.0, let it bleed a bit, shader code becomes easier + // assuming that atlas textures were made with 1-pixel padding + tex.transform = new TextureMatrix(tex, 0.0); + } + tex.transform.update(); + + this.uniforms.mask = tex; + this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite) + .prepend(tex.transform.mapCoord); + this.uniforms.alpha = maskSprite.worldAlpha; + this.uniforms.maskClamp = tex.transform.uClampFrame; + + filterManager.applyFilter(this, input, output); + } +} diff --git a/packages/core/src/filters/spriteMask/spriteMaskFilter.frag b/packages/core/src/filters/spriteMask/spriteMaskFilter.frag new file mode 100644 index 0000000..0e4aef8 --- /dev/null +++ b/packages/core/src/filters/spriteMask/spriteMaskFilter.frag @@ -0,0 +1,23 @@ +varying vec2 vMaskCoord; +varying vec2 vTextureCoord; + +uniform sampler2D uSampler; +uniform sampler2D mask; +uniform float alpha; +uniform vec4 maskClamp; + +void main(void) +{ + float clip = step(3.5, + step(maskClamp.x, vMaskCoord.x) + + step(maskClamp.y, vMaskCoord.y) + + step(vMaskCoord.x, maskClamp.z) + + step(vMaskCoord.y, maskClamp.w)); + + vec4 original = texture2D(uSampler, vTextureCoord); + vec4 masky = texture2D(mask, vMaskCoord); + + original *= (masky.r * masky.a * alpha * clip); + + gl_FragColor = original; +} diff --git a/packages/core/src/filters/spriteMask/spriteMaskFilter.vert b/packages/core/src/filters/spriteMask/spriteMaskFilter.vert new file mode 100644 index 0000000..d23a30c --- /dev/null +++ b/packages/core/src/filters/spriteMask/spriteMaskFilter.vert @@ -0,0 +1,16 @@ +attribute vec2 aVertexPosition; +attribute vec2 aTextureCoord; + +uniform mat3 projectionMatrix; +uniform mat3 otherMatrix; + +varying vec2 vMaskCoord; +varying vec2 vTextureCoord; + +void main(void) +{ + gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); + + vTextureCoord = aTextureCoord; + vMaskCoord = ( otherMatrix * vec3( aTextureCoord, 1.0) ).xy; +} diff --git a/packages/core/src/framebuffer/FrameBuffer.js b/packages/core/src/framebuffer/FrameBuffer.js new file mode 100644 index 0000000..84e9fcc --- /dev/null +++ b/packages/core/src/framebuffer/FrameBuffer.js @@ -0,0 +1,106 @@ +import Texture from '../textures/BaseTexture'; +import { FORMATS, TYPES } from '@pixi/constants'; + +/** + * Frame buffer + * @class + * @memberof PIXI + */ +export default class FrameBuffer +{ + constructor(width, height) + { + this.width = width || 100; + this.height = height || 100; + + this.stencil = false; + this.depth = false; + + this.dirtyId = 0; + this.dirtyFormat = 0; + this.dirtySize = 0; + + this.depthTexture = null; + this.colorTextures = []; + + this.glFrameBuffers = {}; + } + + get colorTexture() + { + return this.colorTextures[0]; + } + + addColorTexture(index, texture) + { + // TODO add some validation to the texture - same width / height etc? + this.colorTextures[index || 0] = texture || new Texture(null, { scaleMode: 0, + resolution: 1, + mipmap: false, + width: this.width, + height: this.height });// || new Texture(); + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + addDepthTexture(texture) + { + /* eslint-disable max-len */ + this.depthTexture = texture || new Texture(null, { scaleMode: 0, + resolution: 1, + width: this.width, + height: this.height, + mipmap: false, + format: FORMATS.DEPTH_COMPONENT, + type: TYPES.UNSIGNED_SHORT });// UNSIGNED_SHORT; + /* eslint-disable max-len */ + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + enableDepth() + { + this.depth = true; + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + enableStencil() + { + this.stencil = true; + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + resize(width, height) + { + if (width === this.width && height === this.height) return; + + this.width = width; + this.height = height; + + this.dirtyId++; + this.dirtySize++; + + for (let i = 0; i < this.colorTextures.length; i++) + { + this.colorTextures[i].setSize(width, height); + } + + if (this.depthTexture) + { + this.depthTexture.setSize(width, height); + } + } +} diff --git a/packages/core/src/framebuffer/FramebufferSystem.js b/packages/core/src/framebuffer/FramebufferSystem.js new file mode 100644 index 0000000..2cc2437 --- /dev/null +++ b/packages/core/src/framebuffer/FramebufferSystem.js @@ -0,0 +1,246 @@ +import System from '../System'; +import { Rectangle } from '@pixi/math'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class FramebufferSystem extends System +{ + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + contextChange() + { + this.gl = this.renderer.gl; + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + this.current = null; + this.viewport = new Rectangle(); + + this.drawBufferExtension = this.renderer.context.extensions.drawBuffers; + } + + bind(framebuffer, frame) + { + const gl = this.gl; + + this.current = framebuffer; + + if (framebuffer) + { + // TODO cacheing layer! + + const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID] || this.initFramebuffer(framebuffer); + + gl.bindFramebuffer(gl.FRAMEBUFFER, fbo.framebuffer); + // makesure all textures are unbound.. + + // now check for updates... + if (fbo.dirtyId !== framebuffer.dirtyId) + { + fbo.dirtyId = framebuffer.dirtyId; + + if (fbo.dirtyFormat !== framebuffer.dirtyFormat) + { + fbo.dirtyFormat = framebuffer.dirtyFormat; + this.updateFramebuffer(framebuffer); + } + else if (fbo.dirtySize !== framebuffer.dirtySize) + { + fbo.dirtySize = framebuffer.dirtySize; + this.resizeFramebuffer(framebuffer); + } + } + + for (let i = 0; i < framebuffer.colorTextures.length; i++) + { + if (framebuffer.colorTextures[i].texturePart) + { + this.renderer.texture.unbind(framebuffer.colorTextures[i].texture); + } + else + { + this.renderer.texture.unbind(framebuffer.colorTextures[i]); + } + } + + if (framebuffer.depthTexture) + { + this.renderer.texture.unbind(framebuffer.depthTexture); + } + + if (frame) + { + this.setViewport(frame.x, frame.y, frame.width, frame.height); + } + else + { + this.setViewport(0, 0, framebuffer.width, framebuffer.height); + } + } + else + { + gl.bindFramebuffer(gl.FRAMEBUFFER, null); + + if (frame) + { + this.setViewport(frame.x, frame.y, frame.width, frame.height); + } + else + { + this.setViewport(0, 0, this.renderer.width, this.renderer.height); + } + } + } + + setViewport(x, y, width, height) + { + const v = this.viewport; + + if (v.width !== width || v.height !== height || v.x !== x || v.y !== y) + { + v.x = x; + v.y = y; + v.width = width; + v.height = height; + + this.gl.viewport(x, y, width, height); + } + } + + get size() + { + if (this.current) + { + // TODO store temp + return { x: 0, y: 0, width: this.current.width, height: this.current.height }; + } + + return { x: 0, y: 0, width: this.renderer.width, height: this.renderer.height }; + } + + clear(r, g, b, a) + { + const gl = this.gl; + + // TODO clear color can be set only one right? + gl.clearColor(r, g, b, a); + gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); + } + + // private functions... + + initFramebuffer(framebuffer) + { + const gl = this.gl; + + // TODO - make this a class? + const fbo = { + framebuffer: gl.createFramebuffer(), + stencil: null, + dirtyId: 0, + dirtyFormat: 0, + dirtySize: 0, + }; + + framebuffer.glFrameBuffers[this.CONTEXT_UID] = fbo; + + return fbo; + } + + resizeFramebuffer(framebuffer) + { + const gl = this.gl; + + if (framebuffer.stencil || framebuffer.depth) + { + gl.bindRenderbuffer(gl.RENDERBUFFER, this.stencil); + gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); + } + } + + updateFramebuffer(framebuffer) + { + const gl = this.gl; + + const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID]; + + // bind the color texture + const colorTextures = framebuffer.colorTextures; + + let count = colorTextures.length; + + if (!this.drawBufferExtension) + { + count = Math.min(count, 1); + } + + const activeTextures = []; + + for (let i = 0; i < count; i++) + { + const texture = framebuffer.colorTextures[i]; + + if (texture.texturePart) + { + this.renderer.texture.bind(texture.texture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.COLOR_ATTACHMENT0 + i, + gl.TEXTURE_CUBE_MAP_NEGATIVE_X + texture.side, + texture.texture._glTextures[this.CONTEXT_UID].texture, + 0); + } + else + { + this.renderer.texture.bind(texture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.COLOR_ATTACHMENT0 + i, + gl.TEXTURE_2D, + texture._glTextures[this.CONTEXT_UID].texture, + 0); + } + + activeTextures.push(gl.COLOR_ATTACHMENT0 + i); + } + + if (this.drawBufferExtension && activeTextures.length > 1) + { + this.drawBufferExtension.drawBuffersWEBGL(activeTextures); + } + + if (framebuffer.depthTexture) + { + const depthTextureExt = this.renderer.context.extensions.depthTexture; + + if (depthTextureExt) + { + const depthTexture = framebuffer.depthTexture; + + this.renderer.texture.bind(depthTexture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.DEPTH_ATTACHMENT, + gl.TEXTURE_2D, + depthTexture._glTextures[this.CONTEXT_UID].texture, + 0); + } + } + + if (framebuffer.stencil || framebuffer.depth) + { + fbo.stencil = gl.createRenderbuffer(); + + gl.bindRenderbuffer(gl.RENDERBUFFER, fbo.stencil); + + // TODO.. this is depth AND stencil? + gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, fbo.stencil); + gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); + // fbo.enableStencil(); + } + } +} diff --git a/packages/core/src/geometry/GLBuffer.js b/packages/core/src/geometry/GLBuffer.js new file mode 100644 index 0000000..0477e8a --- /dev/null +++ b/packages/core/src/geometry/GLBuffer.js @@ -0,0 +1,9 @@ +export default class GLBuffer +{ + constructor(buffer) + { + this.buffer = buffer; + this.updateID = -1; + this.byteLength = -1; + } +} diff --git a/packages/core/src/geometry/GeometrySystem.js b/packages/core/src/geometry/GeometrySystem.js new file mode 100644 index 0000000..959f827 --- /dev/null +++ b/packages/core/src/geometry/GeometrySystem.js @@ -0,0 +1,388 @@ +import System from '../System'; +import GLBuffer from './GLBuffer'; + +const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class GeometrySystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this._activeGeometry = null; + this._activeVao = null; + + this.hasVao = true; + this.hasInstance = true; + } + + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + contextChange() + { + const gl = this.gl = this.renderer.gl; + + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + + // webgl2 + if (!gl.createVertexArray) + { + // webgl 1! + let nativeVaoExtension = this.renderer.context.extensions.vertexArrayObject; + + if (this.renderer.legacy) + { + nativeVaoExtension = null; + } + + if (nativeVaoExtension) + { + gl.createVertexArray = () => + nativeVaoExtension.createVertexArrayOES(); + + gl.bindVertexArray = (vao) => + nativeVaoExtension.bindVertexArrayOES(vao); + + gl.deleteVertexArray = (vao) => + nativeVaoExtension.deleteVertexArrayOES(vao); + } + else + { + this.hasVao = false; + gl.createVertexArray = () => + { + // empty + }; + + gl.bindVertexArray = () => + { + // empty + }; + + gl.deleteVertexArray = () => + { + // empty + }; + } + } + + if (!gl.vertexAttribDivisor) + { + const instanceExt = gl.getExtension('ANGLE_instanced_arrays'); + + if (instanceExt) + { + gl.vertexAttribDivisor = (a, b) => + instanceExt.vertexAttribDivisorANGLE(a, b); + + gl.drawElementsInstanced = (a, b, c, d, e) => + instanceExt.drawElementsInstancedANGLE(a, b, c, d, e); + + gl.drawArraysInstanced = (a, b, c, d) => + instanceExt.drawArraysInstancedANGLE(a, b, c, d); + } + else + { + this.hasInstance = false; + } + } + } + + /** + * Binds geometry so that is can be drawn. Creating a Vao if required + * @private + * @param {PIXI.Geometry} geometry instance of geometry to bind + */ + bind(geometry, shader) + { + shader = shader || this.renderer.shader.shader; + + const gl = this.gl; + + // not sure the best way to address this.. + // currently different shaders require different VAOs for the same geometry + // Still mulling over the best way to solve this one.. + // will likely need to modify the shader attribute locations at run time! + let vaos = geometry.glVertexArrayObjects[this.CONTEXT_UID]; + + if (!vaos) + { + geometry.glVertexArrayObjects[this.CONTEXT_UID] = vaos = {}; + } + + const vao = vaos[shader.program.id] || this.initGeometryVao(geometry, shader.program); + + this._activeGeometry = geometry; + + if (this._activeVao !== vao) + { + this._activeVao = vao; + + if (this.hasVao) + { + gl.bindVertexArray(vao); + } + else + { + this.activateVao(geometry, shader.program); + } + } + + // TODO - optimise later! + // don't need to loop through if nothing changed! + // maybe look to add an 'autoupdate' to geometry? + this.updateBuffers(); + } + + reset() + { + this.unbind(); + } + + updateBuffers() + { + const geometry = this._activeGeometry; + const gl = this.gl; + + for (let i = 0; i < geometry.buffers.length; i++) + { + const buffer = geometry.buffers[i]; + + const glBuffer = buffer._glBuffers[this.CONTEXT_UID]; + + if (buffer._updateID !== glBuffer.updateID) + { + glBuffer.updateID = buffer._updateID; + + // TODO can cache this on buffer! maybe added a getter / setter? + const type = buffer.index ? gl.ELEMENT_ARRAY_BUFFER : gl.ARRAY_BUFFER; + const drawType = buffer.static ? gl.STATIC_DRAW : gl.DYNAMIC_DRAW; + + gl.bindBuffer(type, glBuffer.buffer); + + if (glBuffer.byteLength >= buffer.data.byteLength) + { + // offset is always zero for now! + gl.bufferSubData(type, 0, buffer.data); + } + else + { + gl.bufferData(type, buffer.data, drawType); + } + } + } + } + + checkCompatability(geometry, program) + { + // geometry must have at least all the attributes that the shader requires. + const geometryAttributes = geometry.attributes; + const shaderAttributes = program.attributeData; + + for (const j in shaderAttributes) + { + if (!geometryAttributes[j]) + { + throw new Error(`shader and geometry incompatible, geometry missing the "${j}" attribute`); + } + } + } + + /** + * Creates a Vao with the same structure as the geometry and stores it on the geometry. + * @private + * @param {PIXI.Geometry} geometry instance of geometry to to generate Vao for + */ + initGeometryVao(geometry, program) + { + this.checkCompatability(geometry, program); + + const gl = this.gl; + const CONTEXT_UID = this.CONTEXT_UID; + const buffers = geometry.buffers; + const attributes = geometry.attributes; + + const tempStride = {}; + const tempStart = {}; + + for (const j in buffers) + { + tempStride[j] = 0; + tempStart[j] = 0; + } + + for (const j in attributes) + { + if (!attributes[j].size && program.attributeData[j]) + { + attributes[j].size = program.attributeData[j].size; + } + + tempStride[attributes[j].buffer] += attributes[j].size * byteSizeMap[attributes[j].type]; + } + + for (const j in attributes) + { + const attribute = attributes[j]; + const attribSize = attribute.size; + + if (attribute.stride === undefined) + { + if (tempStride[attribute.buffer] === attribSize * byteSizeMap[attribute.type]) + { + attribute.stride = 0; + } + else + { + attribute.stride = tempStride[attribute.buffer]; + } + } + + if (attribute.start === undefined) + { + attribute.start = tempStart[attribute.buffer]; + + tempStart[attribute.buffer] += attribSize * byteSizeMap[attribute.type]; + } + } + + // first update - and create the buffers! + // only create a gl buffer if it actually gets + for (let i = 0; i < buffers.length; i++) + { + const buffer = buffers[i]; + + if (!buffer._glBuffers[CONTEXT_UID]) + { + buffer._glBuffers[CONTEXT_UID] = new GLBuffer(gl.createBuffer()); + } + } + + // TODO - maybe make this a data object? + // lets wait to see if we need to first! + const vao = gl.createVertexArray(); + + gl.bindVertexArray(vao); + + this.activateVao(geometry, program); + + gl.bindVertexArray(null); + + // add it to the cache! + geometry.glVertexArrayObjects[this.CONTEXT_UID][program.id] = vao; + + return vao; + } + + activateVao(geometry, program) + { + const gl = this.gl; + const CONTEXT_UID = this.CONTEXT_UID; + const buffers = geometry.buffers; + const attributes = geometry.attributes; + + if (geometry.indexBuffer) + { + // first update the index buffer if we have one.. + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, geometry.indexBuffer._glBuffers[CONTEXT_UID].buffer); + } + + let lastBuffer = null; + + // add a new one! + for (const j in attributes) + { + const attribute = attributes[j]; + const buffer = buffers[attribute.buffer]; + const glBuffer = buffer._glBuffers[CONTEXT_UID]; + + if (program.attributeData[j]) + { + if (lastBuffer !== glBuffer) + { + gl.bindBuffer(gl.ARRAY_BUFFER, glBuffer.buffer); + + lastBuffer = glBuffer; + } + + const location = program.attributeData[j].location; + + // TODO introduce state again + // we can optimise this for older devices that have no VAOs + gl.enableVertexAttribArray(location); + + gl.vertexAttribPointer(location, + attribute.size, + attribute.type || gl.FLOAT, + attribute.normalized, + attribute.stride, + attribute.start); + + if (attribute.instance) + { + // TODO calculate instance count based of this... + if (this.hasInstance) + { + gl.vertexAttribDivisor(location, 1); + } + else + { + throw new Error('geometry error, GPU Instancing is not supported on this device'); + } + } + } + } + } + + draw(type, size, start, instanceCount) + { + const gl = this.gl; + const geometry = this._activeGeometry; + + // TODO.. this should not change so maybe cache the function? + + if (geometry.indexBuffer) + { + if (geometry.instanced) + { + /* eslint-disable max-len */ + gl.drawElementsInstanced(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2, instanceCount || 1); + /* eslint-enable max-len */ + } + else + { + gl.drawElements(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2); + } + } + else + if (geometry.instanced) + { + // TODO need a better way to calculate size.. + gl.drawArraysInstanced(type, start, size || geometry.getSize(), instanceCount || 1); + } + else + { + gl.drawArrays(type, start, size || geometry.getSize()); + } + + return this; + } + + unbind() + { + this.gl.bindVertexArray(null); + this._activeVao = null; + this._activeGeometry = null; + } +} diff --git a/packages/core/src/geometry/utils/setVertexAttribArrays.js b/packages/core/src/geometry/utils/setVertexAttribArrays.js new file mode 100644 index 0000000..23b6959 --- /dev/null +++ b/packages/core/src/geometry/utils/setVertexAttribArrays.js @@ -0,0 +1,54 @@ +// var GL_MAP = {}; + +/** + * @param gl {WebGLRenderingContext} The current WebGL context + * @param attribs {*} + * @param state {*} + */ +export default function setVertexAttribArrays(gl, attribs, state) +{ + let i; + + if (state) + { + const tempAttribState = state.tempAttribState; + const attribState = state.attribState; + + for (i = 0; i < tempAttribState.length; i++) + { + tempAttribState[i] = false; + } + + // set the new attribs + for (i = 0; i < attribs.length; i++) + { + tempAttribState[attribs[i].attribute.location] = true; + } + + for (i = 0; i < attribState.length; i++) + { + if (attribState[i] !== tempAttribState[i]) + { + attribState[i] = tempAttribState[i]; + + if (state.attribState[i]) + { + gl.enableVertexAttribArray(i); + } + else + { + gl.disableVertexAttribArray(i); + } + } + } + } + else + { + for (i = 0; i < attribs.length; i++) + { + const attrib = attribs[i]; + + gl.enableVertexAttribArray(attrib.attribute.location); + } + } +} diff --git a/packages/core/src/index.js b/packages/core/src/index.js index 58c456a..fd1f547 100644 --- a/packages/core/src/index.js +++ b/packages/core/src/index.js @@ -1,30 +1,30 @@ import * as resources from './textures/resources'; -import * as systems from './renderers/systems'; +import * as systems from './systems'; -export { resources }; export { systems }; +export { resources }; -export { default as Renderer } from './renderers/Renderer'; -export { default as AbstractRenderer } from './renderers/AbstractRenderer'; -export { default as FrameBuffer } from './textures/FrameBuffer'; +export { default as System } from './System'; +export { default as Renderer } from './Renderer'; +export { default as AbstractRenderer } from './AbstractRenderer'; +export { default as FrameBuffer } from './framebuffer/FrameBuffer'; export { default as CubeTexture } from './textures/CubeTexture'; export { default as BaseTexture } from './textures/BaseTexture'; export { default as Texture } from './textures/Texture'; export { default as TextureMatrix } from './textures/TextureMatrix'; -export { default as RenderTexture } from './textures/RenderTexture'; -export { default as BaseRenderTexture } from './textures/BaseRenderTexture'; +export { default as RenderTexture } from './renderTexture/RenderTexture'; +export { default as BaseRenderTexture } from './renderTexture/BaseRenderTexture'; export { default as TextureUvs } from './textures/TextureUvs'; -export { default as State } from './renderers/State'; -export { default as ObjectRenderer } from './renderers/utils/ObjectRenderer'; -export { default as Quad } from './renderers/utils/Quad'; -export { default as QuadUv } from './renderers/utils/QuadUv'; -export { default as checkMaxIfStatmentsInShader } from './renderers/utils/checkMaxIfStatmentsInShader'; +export { default as State } from './state/State'; +export { default as ObjectRenderer } from './batch/ObjectRenderer'; +export { default as Quad } from './utils/Quad'; +export { default as QuadUv } from './utils/QuadUv'; +export { default as checkMaxIfStatmentsInShader } from './shader/utils/checkMaxIfStatmentsInShader'; export { default as Shader } from './shader/Shader'; export { default as Program } from './shader/Program'; export { default as UniformGroup } from './shader/UniformGroup'; -export { default as SpriteMaskFilter } from './renderers/filters/spriteMask/SpriteMaskFilter'; -export { default as Filter } from './renderers/filters/Filter'; +export { default as SpriteMaskFilter } from './filters/spriteMask/SpriteMaskFilter'; +export { default as Filter } from './filters/Filter'; export { default as Attribute } from './geometry/Attribute'; export { default as Buffer } from './geometry/Buffer'; export { default as Geometry } from './geometry/Geometry'; - diff --git a/packages/core/src/mask/MaskSystem.js b/packages/core/src/mask/MaskSystem.js new file mode 100644 index 0000000..99174c2 --- /dev/null +++ b/packages/core/src/mask/MaskSystem.js @@ -0,0 +1,199 @@ +import System from '../System'; +import AlphaMaskFilter from '../filters/spriteMask/SpriteMaskFilter'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class MaskSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + // TODO - we don't need both! + this.scissor = false; + this.scissorData = null; + this.scissorRenderTarget = null; + + this.enableScissor = false; + + this.alphaMaskPool = []; + this.alphaMaskIndex = 0; + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.DisplayObject} target - Display Object to push the mask to + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + push(target, maskData) + { + // TODO the root check means scissor rect will not + // be used on render textures more info here: + // https://github.com/pixijs/pixi.js/pull/3545 + + if (maskData.texture) + { + this.pushSpriteMask(target, maskData); + } + else if (this.enableScissor + && !this.scissor + && this.renderer._activeRenderTarget.root + && !this.renderer.stencil.stencilMaskStack.length + && maskData.isFastRect()) + { + const matrix = maskData.worldTransform; + + let rot = Math.atan2(matrix.b, matrix.a); + + // use the nearest degree! + rot = Math.round(rot * (180 / Math.PI)); + + if (rot % 90) + { + this.pushStencilMask(maskData); + } + else + { + this.pushScissorMask(target, maskData); + } + } + else + { + this.pushStencilMask(maskData); + } + } + + /** + * Removes the last mask from the mask stack and doesn't return it. + * + * @param {PIXI.DisplayObject} target - Display Object to pop the mask from + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + pop(target, maskData) + { + if (maskData.texture) + { + this.popSpriteMask(target, maskData); + } + else if (this.enableScissor && !this.renderer.stencil.stencilMaskStack.length) + { + this.popScissorMask(target, maskData); + } + else + { + this.popStencilMask(target, maskData); + } + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.RenderTarget} target - Display Object to push the sprite mask to + * @param {PIXI.Sprite} maskData - Sprite to be used as the mask + */ + pushSpriteMask(target, maskData) + { + let alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; + + if (!alphaMaskFilter) + { + alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; + } + + alphaMaskFilter[0].resolution = this.renderer.resolution; + alphaMaskFilter[0].maskSprite = maskData; + + // TODO - may cause issues! + target.filterArea = maskData.getBounds(true); + + this.renderer.filterSystem.pushFilter(target, alphaMaskFilter); + + this.alphaMaskIndex++; + } + + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popSpriteMask() + { + this.renderer.filterSystem.popFilter(); + this.alphaMaskIndex--; + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + pushStencilMask(maskData) + { + this.renderer.batch.flush(); + this.renderer.stencil.pushStencil(maskData); + } + + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popStencilMask() + { + // this.renderer.currentRenderer.stop(); + this.renderer.stencil.popStencil(); + } + + /** + * + * @param {PIXI.DisplayObject} target - Display Object to push the mask to + * @param {PIXI.Graphics} maskData - The masking data. + */ + pushScissorMask(target, maskData) + { + maskData.renderable = true; + + const renderTarget = this.renderer._activeRenderTarget; + + const bounds = maskData.getBounds(); + + bounds.fit(renderTarget.size); + maskData.renderable = false; + + this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); + + const resolution = this.renderer.resolution; + + this.renderer.gl.scissor( + bounds.x * resolution, + (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, + bounds.width * resolution, + bounds.height * resolution + ); + + this.scissorRenderTarget = renderTarget; + this.scissorData = maskData; + this.scissor = true; + } + + /** + * + * + */ + popScissorMask() + { + this.scissorRenderTarget = null; + this.scissorData = null; + this.scissor = false; + + // must be scissor! + const gl = this.renderer.gl; + + gl.disable(gl.SCISSOR_TEST); + } +} diff --git a/packages/core/src/mask/StencilSystem.js b/packages/core/src/mask/StencilSystem.js new file mode 100644 index 0000000..62796a5 --- /dev/null +++ b/packages/core/src/mask/StencilSystem.js @@ -0,0 +1,133 @@ +import System from '../System'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class StencilSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + this.stencilMaskStack = []; + } + + /** + * Changes the mask stack that is used by this System. + * + * @param {PIXI.Graphics[]} stencilMaskStack - The mask stack + */ + setMaskStack(stencilMaskStack) + { + const gl = this.renderer.gl; + + if (stencilMaskStack.length !== this.stencilMaskStack.length) + { + if (stencilMaskStack.length === 0) + { + gl.disable(gl.STENCIL_TEST); + } + else + { + gl.enable(gl.STENCIL_TEST); + } + } + + this.stencilMaskStack = stencilMaskStack; + } + + /** + * Applies the Mask and adds it to the current stencil stack. @alvin + * + * @param {PIXI.Graphics} graphics - The mask + */ + pushStencil(graphics) + { + this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); + + // this.renderer._activeRenderTarget.attachStencilBuffer(); + + const gl = this.renderer.gl; + const prevMaskCount = this.stencilMaskStack.length; + + if (prevMaskCount === 0) + { + gl.enable(gl.STENCIL_TEST); + } + + this.stencilMaskStack.push(graphics); + + // Increment the refference stencil value where the new mask overlaps with the old ones. + gl.colorMask(false, false, false, false); + gl.stencilFunc(gl.EQUAL, prevMaskCount, this._getBitwiseMask()); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.INCR); + this.renderer.plugins.graphics.render(graphics); + + this._useCurrent(); + } + + /** + * Removes the last mask from the stencil stack. @alvin + */ + popStencil() + { + this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); + + const gl = this.renderer.gl; + const graphics = this.stencilMaskStack.pop(); + + if (this.stencilMaskStack.length === 0) + { + // the stack is empty! + gl.disable(gl.STENCIL_TEST); + gl.clear(gl.STENCIL_BUFFER_BIT); + gl.clearStencil(0); + } + else + { + // Decrement the refference stencil value where the popped mask overlaps with the other ones + gl.colorMask(false, false, false, false); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.DECR); + this.renderer.plugins.graphics.render(graphics); + + this._useCurrent(); + } + } + + /** + * Setup renderer to use the current stencil data. + */ + _useCurrent() + { + const gl = this.renderer.gl; + + gl.colorMask(true, true, true, true); + gl.stencilFunc(gl.EQUAL, this.stencilMaskStack.length, this._getBitwiseMask()); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP); + } + + /** + * Fill 1s equal to the number of acitve stencil masks. + * + * @return {number} The bitwise mask. + */ + _getBitwiseMask() + { + return (1 << this.stencilMaskStack.length) - 1; + } + + /** + * Destroys the mask stack. + * + */ + destroy() + { + super.destroy(this); + + this.stencilMaskStack = null; + } +} diff --git a/packages/core/src/projection/ProjectionSystem.js b/packages/core/src/projection/ProjectionSystem.js new file mode 100644 index 0000000..6e63b73 --- /dev/null +++ b/packages/core/src/projection/ProjectionSystem.js @@ -0,0 +1,73 @@ +import System from '../System'; +import { Matrix } from '@pixi/math'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class ProjectionSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.projectionMatrix = new Matrix(); + } + + update(destinationFrame, sourceFrame, resolution, root) + { + this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; + this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; + + this.calculateProjection(this.destinationFrame, this.sourceFrame, resolution, root); + + this.renderer.globalUniforms.uniforms.projectionMatrix = this.projectionMatrix; + this.renderer.globalUniforms.update(); + } + + /** + * Updates the projection matrix based on a projection frame (which is a rectangle) + * + * @param {Rectangle} destinationFrame - The destination frame. + * @param {Rectangle} sourceFrame - The source frame. + */ + calculateProjection(destinationFrame, sourceFrame, resolution, root) + { + const pm = this.projectionMatrix; + + // I don't think we will need this line.. + // pm.identity(); + + if (!root) + { + pm.a = (1 / destinationFrame.width * 2) * resolution; + pm.d = (1 / destinationFrame.height * 2) * resolution; + + pm.tx = -1 - (sourceFrame.x * pm.a); + pm.ty = -1 - (sourceFrame.y * pm.d); + } + else + { + pm.a = (1 / destinationFrame.width * 2) * resolution; + pm.d = (-1 / destinationFrame.height * 2) * resolution; + + pm.tx = -1 - (sourceFrame.x * pm.a); + pm.ty = 1 - (sourceFrame.y * pm.d); + } + } + + /** + * Sets the transform of the active render target to the given matrix + * + * @param {PIXI.Matrix} matrix - The transformation matrix + */ + setTransform()// matrix) + { + // this._activeRenderTarget.transform = matrix; + } +} diff --git a/packages/core/src/renderTexture/BaseRenderTexture.js b/packages/core/src/renderTexture/BaseRenderTexture.js new file mode 100644 index 0000000..88eb83b --- /dev/null +++ b/packages/core/src/renderTexture/BaseRenderTexture.js @@ -0,0 +1,137 @@ +import BaseTexture from '../textures/BaseTexture'; +import FrameBuffer from '../framebuffer/FrameBuffer'; + +/** + * A BaseRenderTexture is a special texture that allows any PixiJS display object to be rendered to it. + * + * __Hint__: All DisplayObjects (i.e. Sprites) that render to a BaseRenderTexture should be preloaded + * otherwise black rectangles will be drawn instead. + * + * A BaseRenderTexture takes a snapshot of any Display Object given to its render method. The position + * and rotation of the given Display Objects is ignored. For example: + * + * ```js + * let renderer = PIXI.autoDetectRenderer(1024, 1024, { view: canvas, ratio: 1 }); + * let baseRenderTexture = new PIXI.BaseRenderTexture(renderer, 800, 600); + * let sprite = PIXI.Sprite.fromImage("spinObj_01.png"); + * + * sprite.position.x = 800/2; + * sprite.position.y = 600/2; + * sprite.anchor.x = 0.5; + * sprite.anchor.y = 0.5; + * + * baseRenderTexture.render(sprite); + * ``` + * + * The Sprite in this case will be rendered using its local transform. To render this sprite at 0,0 + * you can clear the transform + * + * ```js + * + * sprite.setTransform() + * + * let baseRenderTexture = new PIXI.BaseRenderTexture(100, 100); + * let renderTexture = new PIXI.RenderTexture(baseRenderTexture); + * + * renderer.render(sprite, renderTexture); // Renders to center of RenderTexture + * ``` + * + * @class + * @extends PIXI.BaseTexture + * @memberof PIXI + */ +export default class BaseRenderTexture extends BaseTexture +{ + /** + * @param {object} [options] + * @param {number} [options.width=100] - The width of the base render texture + * @param {number} [options.height=100] - The height of the base render texture + * @param {PIXI.SCALE_MODES} [options.scaleMode] - See {@link PIXI.SCALE_MODES} for possible values + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the texture being generated + */ + constructor(options) + { + if (typeof options === 'number') + { + /* eslint-disable prefer-rest-params */ + // Backward compatibility of signature + const width = arguments[0]; + const height = arguments[1]; + const scaleMode = arguments[2]; + const resolution = arguments[3]; + + options = { width, height, scaleMode, resolution }; + /* eslint-enable prefer-rest-params */ + } + + super(null, options); + + const { width, height } = options || {}; + + // Set defaults + this.mipmap = false; + this.width = Math.ceil(width) || 100; + this.height = Math.ceil(height) || 100; + this.valid = true; + + /** + * A map of renderer IDs to webgl renderTargets + * + * @private + * @member {object} + */ + // this._glRenderTargets = {}; + + /** + * A reference to the canvas render target (we only need one as this can be shared across renderers) + * + * @private + * @member {object} + */ + this._canvasRenderTarget = null; + + this.clearColor = [0, 0, 0, 0]; + + this.frameBuffer = new FrameBuffer(width * this.resolution, height * this.resolution) + .addColorTexture(0, this); + + // TODO - could this be added the systems? + + /** + * The data structure for the stencil masks + * + * @member {PIXI.Graphics[]} + */ + this.stencilMaskStack = []; + + /** + * The data structure for the filters + * + * @member {PIXI.Graphics[]} + */ + this.filterStack = [{}]; + } + + /** + * Resizes the BaseRenderTexture. + * + * @param {number} width - The width to resize to. + * @param {number} height - The height to resize to. + */ + resize(width, height) + { + width = Math.ceil(width); + height = Math.ceil(height); + this.frameBuffer.resize(width * this.resolution, height * this.resolution); + } + + /** + * Destroys this texture + * + */ + destroy() + { + super.destroy(true); + this.renderer = null; + } +} diff --git a/packages/core/src/renderTexture/RenderTexture.js b/packages/core/src/renderTexture/RenderTexture.js new file mode 100644 index 0000000..f1ecf1f --- /dev/null +++ b/packages/core/src/renderTexture/RenderTexture.js @@ -0,0 +1,153 @@ +import BaseRenderTexture from './BaseRenderTexture'; +import Texture from '../textures/Texture'; + +/** + * A RenderTexture is a special texture that allows any PixiJS display object to be rendered to it. + * + * __Hint__: All DisplayObjects (i.e. Sprites) that render to a RenderTexture should be preloaded + * otherwise black rectangles will be drawn instead. + * + * A RenderTexture takes a snapshot of any Display Object given to its render method. For example: + * + * ```js + * let renderer = PIXI.autoDetectRenderer(1024, 1024, { view: canvas, ratio: 1 }); + * let renderTexture = PIXI.RenderTexture.create(800, 600); + * let sprite = PIXI.Sprite.fromImage("spinObj_01.png"); + * + * sprite.position.x = 800/2; + * sprite.position.y = 600/2; + * sprite.anchor.x = 0.5; + * sprite.anchor.y = 0.5; + * + * renderer.render(sprite, renderTexture); + * ``` + * + * The Sprite in this case will be rendered using its local transform. To render this sprite at 0,0 + * you can clear the transform + * + * ```js + * + * sprite.setTransform() + * + * let renderTexture = new PIXI.RenderTexture.create(100, 100); + * + * renderer.render(sprite, renderTexture); // Renders to center of RenderTexture + * ``` + * + * @class + * @extends PIXI.Texture + * @memberof PIXI + */ +export default class RenderTexture extends Texture +{ + /** + * @param {PIXI.BaseRenderTexture} baseRenderTexture - The renderer used for this RenderTexture + * @param {PIXI.Rectangle} [frame] - The rectangle frame of the texture to show + */ + constructor(baseRenderTexture, frame) + { + // support for legacy.. + let _legacyRenderer = null; + + if (!(baseRenderTexture instanceof BaseRenderTexture)) + { + /* eslint-disable prefer-rest-params, no-console */ + const width = arguments[1]; + const height = arguments[2]; + const scaleMode = arguments[3]; + const resolution = arguments[4]; + + // we have an old render texture.. + console.warn(`Please use RenderTexture.create(${width}, ${height}) instead of the ctor directly.`); + _legacyRenderer = arguments[0]; + /* eslint-enable prefer-rest-params, no-console */ + + frame = null; + baseRenderTexture = new BaseRenderTexture({ + width, + height, + scaleMode, + resolution, + }); + } + + /** + * The base texture object that this texture uses + * + * @member {BaseTexture} + */ + super(baseRenderTexture, frame); + + this.legacyRenderer = _legacyRenderer; + + /** + * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. + * + * @member {boolean} + */ + this.valid = true; + + /** + * FilterSystem temporary storage + * @private + * @member {PIXI.Rectangle} + */ + this.filterFrame = null; + + this._updateUvs(); + } + + /** + * Resizes the RenderTexture. + * + * @param {number} width - The width to resize to. + * @param {number} height - The height to resize to. + * @param {boolean} [resizeBaseTexture=true] - Should the baseTexture.width and height values be resized as well? + */ + resize(width, height, resizeBaseTexture = true) + { + width = Math.ceil(width); + height = Math.ceil(height); + + // TODO - could be not required.. + this.valid = (width > 0 && height > 0); + + this._frame.width = this.orig.width = width; + this._frame.height = this.orig.height = height; + + if (resizeBaseTexture) + { + this.baseTexture.resize(width, height); + } + + this._updateUvs(); + } + + /** + * A short hand way of creating a render texture. + * + * @param {object} [options] - Options + * @param {number} [options.width=100] - The width of the render texture + * @param {number} [options.height=100] - The height of the render texture + * @param {number} [options.scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the texture being generated + * @return {PIXI.RenderTexture} The new render texture + */ + static create(options) + { + // fallback, old-style: create(width, height, scaleMode, resolution) + if (typeof options === 'number') + { + /* eslint-disable prefer-rest-params */ + options = { + width: options, + height: arguments[1], + scaleMode: arguments[2], + resolution: arguments[3], + }; + /* eslint-enable prefer-rest-params */ + } + + return new RenderTexture(new BaseRenderTexture(options)); + } +} diff --git a/packages/core/src/renderTexture/RenderTextureSystem.js b/packages/core/src/renderTexture/RenderTextureSystem.js new file mode 100644 index 0000000..8d0ca61 --- /dev/null +++ b/packages/core/src/renderTexture/RenderTextureSystem.js @@ -0,0 +1,115 @@ +import System from '../System'; +import { Rectangle } from '@pixi/math'; + +const tempRect = new Rectangle(); + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class RenderTextureSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.clearColor = renderer._backgroundColorRgba; + + // TODO moe this property somewhere else! + this.defaultMaskStack = []; + this.defaultFilterStack = [{}]; + + // empty render texture? + this.renderTexture = null; + + this.destinationFrame = new Rectangle(); + } + + bind(renderTexture, sourceFrame, destinationFrame) + { + // TODO - do we want this?? + if (this.renderTexture === renderTexture) return; + this.renderTexture = renderTexture; + + const renderer = this.renderer; + + if (renderTexture) + { + const baseTexture = renderTexture.baseTexture; + + if (!destinationFrame) + { + tempRect.width = baseTexture.realWidth; + tempRect.height = baseTexture.realHeight; + + destinationFrame = tempRect; + } + + if (!sourceFrame) + { + sourceFrame = destinationFrame; + } + + this.renderer.framebuffer.bind(baseTexture.frameBuffer, destinationFrame); + + this.renderer.projection.update(destinationFrame, sourceFrame, baseTexture.resolution, false); + this.renderer.stencil.setMaskStack(baseTexture.stencilMaskStack); + } + else + { + // TODO these validation checks happen deeper down.. + // thing they can be avoided.. + if (!destinationFrame) + { + tempRect.width = renderer.width; + tempRect.height = renderer.height; + + destinationFrame = tempRect; + } + + if (!sourceFrame) + { + sourceFrame = destinationFrame; + } + + renderer.framebuffer.bind(null, destinationFrame); + + // TODO store this.. + this.renderer.projection.update(destinationFrame, sourceFrame, this.renderer.resolution, true); + this.renderer.stencil.setMaskStack(this.defaultMaskStack); + } + + this.destinationFrame.copyFrom(destinationFrame); + } + + /** + * Erases the render texture and fills the drawing area with a colour + * + * @param {number} [clearColor] - The colour + * @return {PIXI.Renderer} Returns itself. + */ + clear(clearColor) + { + if (this.renderTexture) + { + clearColor = clearColor || this.renderTexture.baseTexture.clearColor; + } + else + { + clearColor = clearColor || this.clearColor; + } + + this.renderer.framebuffer.clear(clearColor[0], clearColor[1], clearColor[2], clearColor[3]); + } + + resize()// screenWidth, screenHeight) + { + // resize the root only! + this.bind(null); + } +} diff --git a/packages/core/src/renderers/AbstractRenderer.js b/packages/core/src/renderers/AbstractRenderer.js deleted file mode 100644 index 928670f..0000000 --- a/packages/core/src/renderers/AbstractRenderer.js +++ /dev/null @@ -1,353 +0,0 @@ -import { hex2string, hex2rgb } from '@pixi/utils'; -import { Matrix, Rectangle } from '@pixi/math'; -import { RENDERER_TYPE } from '@pixi/constants'; -import { settings } from '@pixi/settings'; -import { Container } from '@pixi/display'; -import RenderTexture from '../textures/RenderTexture'; -import EventEmitter from 'eventemitter3'; - -const tempMatrix = new Matrix(); - -/** - * 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 EventEmitter - * @memberof PIXI - */ -export default class AbstractRenderer extends EventEmitter -{ - // eslint-disable-next-line valid-jsdoc - /** - * @param {string} system - The name of the system this renderer is for. - * @param {object} [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.transparent=false] - If the render view is transparent, default false - * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false - * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) - * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. The - * resolution of the renderer retina would be 2. - * @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 {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when rendering, - * stopping pixel interpolation. - */ - constructor(system, options, arg2, arg3) - { - super(); - - // Support for constructor(system, screenWidth, screenHeight, options) - if (typeof options === 'number') - { - options = Object.assign({ - width: options, - height: arg2 || settings.RENDER_OPTIONS.height, - }, arg3); - } - - // 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 = RENDERER_TYPE.UNKNOWN; - - /** - * Measurements of the screen. (0, 0, screenWidth, screenHeight) - * - * Its safe to use as filterArea or hitArea for 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 1 - */ - this.resolution = options.resolution || settings.RESOLUTION; - - /** - * Whether the render view is transparent - * - * @member {boolean} - */ - this.transparent = options.transparent; - - /** - * Whether css dimensions of canvas view should be resized to screen dimensions automatically - * - * @member {boolean} - */ - this.autoResize = options.autoResize || false; - - /** - * Tracks the blend modes useful for this renderer. - * - * @member {object} - */ - this.blendModes = null; - - /** - * 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; - - /** - * If true PixiJS will Math.floor() x/y values when rendering, stopping pixel interpolation. - * Handy for crisp pixel art and speed on legacy devices. - * - * @member {boolean} - */ - this.roundPixels = options.roundPixels; - - /** - * The background color as a number. - * - * @member {number} - * @private - */ - this._backgroundColor = 0x000000; - - /** - * The background color as an [R, G, B] array. - * - * @member {number[]} - * @private - */ - this._backgroundColorRgba = [0, 0, 0, 0]; - - /** - * The background color as a string. - * - * @member {string} - * @private - */ - this._backgroundColorString = '#000000'; - - this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter - - /** - * This temporary display object used as the parent of the currently being rendered item - * - * @member {PIXI.DisplayObject} - * @private - */ - this._tempDisplayObjectParent = new Container(); - - /** - * The last root object that the renderer tried to render. - * - * @member {PIXI.DisplayObject} - * @private - */ - this._lastObjectRendered = this._tempDisplayObjectParent; - - /** - * Collection of plugins. - * @readonly - * @member {object} - */ - this.plugins = {}; - } - - /** - * Initialize the plugins. - * - * @protected - * @param {object} staticMap - The dictionary of staticly saved plugins. - */ - initPlugins(staticMap) - { - 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() - { - return this.view.width; - } - - /** - * Same as view.height, actual number of pixels in the canvas by vertical - * - * @member {number} - * @readonly - * @default 600 - */ - get height() - { - return this.view.height; - } - - /** - * Resizes the screen and canvas to the specified width and height - * Canvas dimensions are multiplied by resolution - * - * @param {number} screenWidth - the new width of the screen - * @param {number} screenHeight - the new height of the screen - */ - resize(screenWidth, screenHeight) - { - this.screen.width = screenWidth; - this.screen.height = screenHeight; - - this.view.width = screenWidth * this.resolution; - this.view.height = screenHeight * this.resolution; - - if (this.autoResize) - { - this.view.style.width = `${screenWidth}px`; - this.view.style.height = `${screenHeight}px`; - } - } - - /** - * 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 {PIXI.DisplayObject} displayObject - The displayObject the object will be generated from - * @param {number} scaleMode - Should be one of the scaleMode consts - * @param {number} resolution - The resolution / device pixel ratio of the texture being generated - * @param {PIXI.Rectangle} [region] - The region of the displayObject, that shall be rendered, - * if no region is specified, defaults to the local bounds of the displayObject. - * @return {PIXI.Texture} a texture of the graphics object - */ - generateTexture(displayObject, scaleMode, resolution, region) - { - region = region || displayObject.getLocalBounds(); - - // 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(region.width | 0, region.height | 0, scaleMode, resolution); - - tempMatrix.tx = -region.x; - tempMatrix.ty = -region.y; - - this.render(displayObject, renderTexture, false, tempMatrix, true); - - return renderTexture; - } - - /** - * Removes everything from the renderer and optionally removes the Canvas DOM element. - * - * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. - */ - destroy(removeView) - { - 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); - } - - this.plugins = null; - - this.type = RENDERER_TYPE.UNKNOWN; - - this.view = null; - - this.screen = null; - - this.resolution = 0; - - this.transparent = false; - - this.autoResize = false; - - this.blendModes = null; - - this.options = null; - - this.preserveDrawingBuffer = false; - this.clearBeforeRender = false; - - this.roundPixels = false; - - this._backgroundColor = 0; - this._backgroundColorRgba = null; - this._backgroundColorString = null; - - this._tempDisplayObjectParent = null; - this._lastObjectRendered = null; - } - - /** - * The background color to fill if not transparent - * - * @member {number} - */ - get backgroundColor() - { - return this._backgroundColor; - } - - set backgroundColor(value) // eslint-disable-line require-jsdoc - { - this._backgroundColor = value; - this._backgroundColorString = hex2string(value); - hex2rgb(value, this._backgroundColorRgba); - } -} diff --git a/packages/core/src/renderers/Renderer.js b/packages/core/src/renderers/Renderer.js deleted file mode 100644 index a2020b1..0000000 --- a/packages/core/src/renderers/Renderer.js +++ /dev/null @@ -1,329 +0,0 @@ -import AbstractRenderer from './AbstractRenderer'; -import { sayHello } from '@pixi/utils'; -import MaskSystem from './systems/MaskSystem'; -import StencilSystem from './systems/StencilSystem'; -import FilterSystem from './systems/filter/FilterSystem'; -import FramebufferSystem from './systems/FramebufferSystem'; -import RenderTextureSystem from './systems/RenderTextureSystem'; -import TextureSystem from './systems/textures/TextureSystem'; -import ProjectionSystem from './systems/ProjectionSystem'; -import StateSystem from './systems/StateSystem'; -import GeometrySystem from './systems/geometry/GeometrySystem'; -import ShaderSystem from './systems/shader/ShaderSystem'; -import ContextSystem from './systems/ContextSystem'; -import BatchSystem from './systems/BatchSystem'; -import TextureGCSystem from './systems/textures/TextureGCSystem'; -import { RENDERER_TYPE } from '@pixi/constants'; -import UniformGroup from '../shader/UniformGroup'; -import { Matrix } from '@pixi/math'; -import Runner from 'mini-runner'; - -/** - * 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 Render works by automatically managing webGLBatchs. - * 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 :) - * - * @class - * @memberof PIXI - * @extends PIXI.AbstractRenderer - */ -export default class Renderer extends AbstractRenderer -{ - // eslint-disable-next-line valid-jsdoc - /** - * - * @param {object} [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.transparent=false] - If the render view is transparent, default false - * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false - * @param {boolean} [options.antialias=false] - sets antialias. If not available natively then FXAA - * antialiasing is used - * @param {boolean} [options.forceFXAA=false] - forces FXAA antialiasing to be used over native. - * FXAA is faster, but may not always look as great - * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. - * The resolution of the renderer retina would be 2. - * @param {boolean} [options.clearBeforeRender=true] - This sets if the renderer will clear - * the canvas or not before the new render pass. If you wish to set this to false, you *must* set - * preserveDrawingBuffer to `true`. - * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, - * enable this if you need to call toDataUrl on the webgl context. - * @param {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when - * rendering, stopping pixel interpolation. - * @param {number} [options.backgroundColor=0x000000] - The background color of the rendered area - * (shown if not transparent). - * @param {boolean} [options.legacy=false] - If true PixiJS will aim to ensure compatibility - * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. - * @param {string} [options.powerPreference] - Parameter passed to webgl context, set to "high-performance" - * for devices with dual graphics card - */ - constructor(options, arg2, arg3) - { - super('WebGL', options, arg2, arg3); - - /** - * The type of this renderer as a standardised const - * - * @member {number} - * @see PIXI.RENDERER_TYPE - */ - this.type = RENDERER_TYPE.WEBGL; - - // this will be set by the contextSystem (this.context) - this.gl = null; - this.CONTEXT_UID = 0; - this.legacy = !!options.legacy; - - // TODO legacy! - - // runners! - this.runners = { - destroy: new Runner('destroy'), - contextChange: new Runner('contextChange', 1), - reset: new Runner('reset'), - update: new Runner('update'), - postrender: new Runner('postrender'), - prerender: new Runner('prerender'), - resize: new Runner('resize', 2), - }; - - 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(GeometrySystem, 'geometry') - .addSystem(FramebufferSystem, 'framebuffer') - .addSystem(StencilSystem, 'stencil') - .addSystem(ProjectionSystem, 'projection') - .addSystem(TextureGCSystem, 'textureGC') - .addSystem(FilterSystem, 'filter') - .addSystem(RenderTextureSystem, 'renderTexture') - .addSystem(BatchSystem, 'batch'); - - this.initPlugins(Renderer.__plugins); - - /** - * The options passed in to create a new webgl context. - * - * @member {object} - * @private - */ - if (options.context) - { - this.context.initFromContext(options.context); - } - else - { - this.context.initFromOptions({ - alpha: this.transparent, - antialias: options.antialias, - premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', - stencil: true, - 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); - } - - addSystem(_class, name) - { - if (!name) - { - name = _class.name; - } - - // TODO - read name from class.name.. - - /* - if(name.includes('System')) - { - name = name.replace('System', ''); - name = name.charAt(0).toLowerCase() + name.slice(1); - } - */ - - const system = new _class(this); - - if (this[name]) - { - throw new Error(`Whoops! ${name} is already a manger`); - } - - this[name] = system; - - for (const i in this.runners) - { - this.runners[i].add(system); - } - - return this; - - /** - * 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. - */ - } - - /** - * Renders the object to its webGL view - * - * @param {PIXI.DisplayObject} displayObject - the object to be rendered - * @param {PIXI.RenderTexture} renderTexture - The render texture to render to. - * @param {boolean} [clear] - Should the canvas be cleared before the new render - * @param {PIXI.Transform} [transform] - A transform to apply to the render texture before rendering. - * @param {boolean} [skipUpdateTransform] - Should we skip the update transform pass? - */ - render(displayObject, renderTexture, clear, transform, skipUpdateTransform) - { - // can be handy to know! - this.renderingToScreen = !renderTexture; - - this.runners.prerender.run(); - this.emit('prerender'); - - // 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.parent; - - displayObject.parent = this._tempDisplayObjectParent; - displayObject.updateTransform(); - displayObject.parent = 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.run(); - - this.emit('postrender'); - } - - /** - * Resizes the webGL view to the specified width and height. - * - * @param {number} screenWidth - the new width of the screen - * @param {number} screenHeight - the new height of the screen - */ - resize(screenWidth, screenHeight) - { - AbstractRenderer.prototype.resize.call(this, screenWidth, screenHeight); - - this.runners.resize.run(screenWidth, screenHeight); - } - - /** - * Resets the WebGL state so you can render things however you fancy! - * - * @return {PIXI.Renderer} Returns itself. - */ - reset() - { - this.runners.reset.run(); - - return this; - } - - clear() - { - this.framebuffer.bind(); - this.framebuffer.clear(); - } - - /** - * Removes everything from the renderer (event listeners, spritebatch, etc...) - * - * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. - * See: https://github.com/pixijs/pixi.js/issues/2233 - */ - destroy(removeView) - { - this.runners.destroy.run(); - - // call base destroy - super.destroy(removeView); - - // TODO nullify all the managers.. - this.gl = null; - } - - /** - * 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 - * builds and excluding plugins. - * @name PIXI.Renderer#plugins - * @type {object} - * @readonly - * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. - * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. - * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. - * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. - */ - - /** - * Adds a plugin to the renderer. - * - * @method - * @param {string} pluginName - The name of the plugin. - * @param {Function} ctor - The constructor function or class for the plugin. - */ - static registerPlugin(pluginName, ctor) - { - Renderer.__plugins = Renderer.__plugins || {}; - Renderer.__plugins[pluginName] = ctor; - } -} diff --git a/packages/core/src/renderers/State.js b/packages/core/src/renderers/State.js deleted file mode 100644 index fa3f221..0000000 --- a/packages/core/src/renderers/State.js +++ /dev/null @@ -1,163 +0,0 @@ -/* eslint-disable max-len */ - -const BLEND = 0; -const OFFSET = 1; -const CULLING = 2; -const DEPTH_TEST = 3; -const WINDING = 4; - -/** - * This is a webGL state. It is passed The WebGL StateManager. - * Each mesh renderered may require webGL to be in a different state. - * For example you may want different blend mode or to enable polygon offsets - * - * @class - * @memberof PIXI - */ -export default class State -{ - /** - * - */ - constructor() - { - this.data = 0; - - this.blendMode = 0; - this.polygonOffset = 0; - - this.blend = true; - // this.depthTest = true; - } - - /** - * Activates blending of the computed fragment color values - * - * @member {boolean} - */ - get blend() - { - return !!(this.data & (1 << BLEND)); - } - - set blend(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << BLEND)) !== value) - { - this.data ^= (1 << BLEND); - } - } - - /** - * Activates adding an offset to depth values of polygon's fragments - * - * @member {boolean} - * @default false - */ - get offsets() - { - return !!(this.data & (1 << OFFSET)); - } - - set offsets(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << OFFSET)) !== value) - { - this.data ^= (1 << OFFSET); - } - } - - /** - * Activates culling of polygons. - * - * @member {boolean} - * @default false - */ - get culling() - { - return !!(this.data & (1 << CULLING)); - } - - set culling(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << CULLING)) !== value) - { - this.data ^= (1 << CULLING); - } - } - - /** - * Activates depth comparisons and updates to the depth buffer. - * - * @member {boolean} - * @default false - */ - get depthTest() - { - return !!(this.data & (1 << DEPTH_TEST)); - } - - set depthTest(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << DEPTH_TEST)) !== value) - { - this.data ^= (1 << DEPTH_TEST); - } - } - - /** - * Specifies whether or not front or back-facing polygons can be culled. - * @member {boolean} - * @default false - */ - get clockwiseFrontFace() - { - return !!(this.data & (1 << WINDING)); - } - - set clockwiseFrontFace(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << WINDING)) !== value) - { - this.data ^= (1 << WINDING); - } - } - - /** - * The blend mode to be applied when this state is set. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. - * Setting this mode to anything other than NO_BLEND will automatically switch blending on. - * - * @member {boolean} - * @default PIXI.BLEND_MODES.NORMAL - * @see PIXI.BLEND_MODES - */ - get blendMode() - { - return this._blendMode; - } - - set blendMode(value) // eslint-disable-line require-jsdoc - { - // 17 is NO BLEND - this.blend = (value !== 17); - this._blendMode = value; - } - - /** - * The polygon offset. Setting this property to anything other than 0 will automatically enable poygon offset fill. - * - * @member {number} - * @default 0 - */ - get polygonOffset() - { - return this._polygonOffset; - } - - set polygonOffset(value) // eslint-disable-line require-jsdoc - { - this.offsets = !!value; - this._polygonOffset = value; - } -} - diff --git a/packages/core/src/renderers/filters/Filter.js b/packages/core/src/renderers/filters/Filter.js deleted file mode 100644 index 2073b8d..0000000 --- a/packages/core/src/renderers/filters/Filter.js +++ /dev/null @@ -1,140 +0,0 @@ -import Shader from '../../shader/Shader'; -import Program from '../../shader/Program'; -import State from '../State'; -import { settings } from '@pixi/settings'; -// import extractUniformsFromSrc from './extractUniformsFromSrc'; -import defaultVertex from './defaultFilter.vert'; -import defaultFragment from './defaultFilter.frag'; - -// let math = require('../../math'); -/** - * @class - * @memberof PIXI - * @extends PIXI.Shader - */ -export default class Filter extends Shader -{ - /** - * @param {string} [vertexSrc] - The source of the vertex shader. - * @param {string} [fragmentSrc] - The source of the fragment shader. - * @param {object} [uniforms] - Custom uniforms to use to augment the built-in ones. - */ - constructor(vertexSrc, fragmentSrc, uniforms) - { - const program = Program.from(vertexSrc, fragmentSrc); - - super(program, uniforms); - - /** - * The padding of the filter. Some filters require extra space to breath such as a blur. - * Increasing this will add extra width and height to the bounds of the object that the - * filter is applied to. - * - * @member {number} - */ - this.padding = 0; - - /** - * The resolution of the filter. Setting this to be lower will lower the quality but - * increase the performance of the filter. - * - * @member {number} - */ - this.resolution = settings.RESOLUTION; - - /** - * If enabled is true the filter is applied, if false it will not. - * - * @member {boolean} - */ - this.enabled = true; - - /** - * If enabled, PixiJS will fit the filter area into boundaries for better performance. - * Switch it off if it does not work for specific shader. - * - * @member {boolean} - */ - this.autoFit = true; - - /** - * Legacy filters use position and uvs from attributes - * @member {boolean} - * @readonly - */ - this.legacy = !!this.program.attributeData.aTextureCoord; - - /** - * the webGL state the filter requires to render - * @member {PIXI.State} - */ - this.state = new State(); - } - - /** - * Applies the filter - * - * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from - * @param {PIXI.RenderTarget} input - The input render target. - * @param {PIXI.RenderTarget} output - The target to output to. - * @param {boolean} clear - Should the output be cleared before rendering to it - * @param {object} [currentState] - It's current state of filter. - * There are some useful properties in the currentState : - * target, filters, sourceFrame, destinationFrame, renderTarget, resolution - */ - apply(filterManager, input, output, clear, currentState, derp) // eslint-disable-line no-unused-vars - { - // do as you please! - - filterManager.applyFilter(this, input, output, clear, currentState, derp); - - // or just do a regular render.. - } - - /** - * Sets the blendmode of the filter - * - * @member {number} - * @default PIXI.BLEND_MODES.NORMAL - */ - get blendMode() - { - return this.state.blendMode; - } - - set blendMode(value) // eslint-disable-line require-jsdoc - { - this.state.blendMode = value; - } - - /** - * The default vertex shader source - * - * @static - * @constant - */ - static get defaultVertexSrc() - { - return defaultVertex; - } - - /** - * The default fragment shader source - * - * @static - * @constant - */ - static get defaultFragmentSrc() - { - return defaultFragment; - } -} - -/** - * Used for caching shader IDs - * - * @static - * @private - */ -Filter.SOURCE_KEY_MAP = {}; - diff --git a/packages/core/src/renderers/filters/defaultFilter.frag b/packages/core/src/renderers/filters/defaultFilter.frag deleted file mode 100644 index a14c1be..0000000 --- a/packages/core/src/renderers/filters/defaultFilter.frag +++ /dev/null @@ -1,22 +0,0 @@ -varying vec2 vTextureCoord; -varying vec2 vFilterCoord; - -uniform sampler2D uSampler; -uniform sampler2D filterSampler; - -void main(void){ - vec4 masky = texture2D(filterSampler, vFilterCoord); - vec4 sample = texture2D(uSampler, vTextureCoord); - vec4 color; - if(mod(vFilterCoord.x, 1.0) > 0.5) - { - color = vec4(1.0, 0.0, 0.0, 1.0); - } - else - { - color = vec4(0.0, 1.0, 0.0, 1.0); - } - // gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0); - gl_FragColor = mix(sample, masky, 0.5); - gl_FragColor *= sample.a; -} diff --git a/packages/core/src/renderers/filters/defaultFilter.vert b/packages/core/src/renderers/filters/defaultFilter.vert deleted file mode 100644 index e1febca..0000000 --- a/packages/core/src/renderers/filters/defaultFilter.vert +++ /dev/null @@ -1,14 +0,0 @@ -attribute vec2 aVertexPosition; -attribute vec2 aTextureCoord; - -uniform mat3 projectionMatrix; -uniform mat3 filterMatrix; - -varying vec2 vTextureCoord; -varying vec2 vFilterCoord; - -void main(void){ - gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); - vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy; - vTextureCoord = aTextureCoord ; -} diff --git a/packages/core/src/renderers/filters/filterTransforms.js b/packages/core/src/renderers/filters/filterTransforms.js deleted file mode 100644 index 87a3cd6..0000000 --- a/packages/core/src/renderers/filters/filterTransforms.js +++ /dev/null @@ -1,50 +0,0 @@ -import { Matrix } from '@pixi/math'; - -/** - * Calculates the mapped matrix - * @param filterArea {Rectangle} The filter area - * @param sprite {Sprite} the target sprite - * @param outputMatrix {Matrix} @alvin - * @private - */ -// this returns a matrix that will normalise map filter cords in the filter to screen space -export function calculateScreenSpaceMatrix(outputMatrix, filterArea, textureSize) -{ - // TODO unwrap? - const mappedMatrix = outputMatrix.identity(); - - mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); - - mappedMatrix.scale(textureSize.width, textureSize.height); - - return mappedMatrix; -} - -export function calculateNormalizedScreenSpaceMatrix(outputMatrix, filterArea, textureSize) -{ - const mappedMatrix = outputMatrix.identity(); - - mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); - - const translateScaleX = (textureSize.width / filterArea.width); - const translateScaleY = (textureSize.height / filterArea.height); - - mappedMatrix.scale(translateScaleX, translateScaleY); - - return mappedMatrix; -} - -// this will map the filter coord so that a texture can be used based on the transform of a sprite -export function calculateSpriteMatrix(outputMatrix, filterArea, textureSize, sprite) -{ - const orig = sprite._texture.orig; - const mappedMatrix = outputMatrix.set(textureSize.width, 0, 0, textureSize.height, filterArea.x, filterArea.y); - const worldTransform = sprite.worldTransform.copyTo(Matrix.TEMP_MATRIX); - - worldTransform.invert(); - mappedMatrix.prepend(worldTransform); - mappedMatrix.scale(1.0 / orig.width, 1.0 / orig.height); - mappedMatrix.translate(sprite.anchor.x, sprite.anchor.y); - - return mappedMatrix; -} diff --git a/packages/core/src/renderers/filters/spriteMask/SpriteMaskFilter.js b/packages/core/src/renderers/filters/spriteMask/SpriteMaskFilter.js deleted file mode 100644 index d5a91a4..0000000 --- a/packages/core/src/renderers/filters/spriteMask/SpriteMaskFilter.js +++ /dev/null @@ -1,63 +0,0 @@ -import Filter from '../Filter'; -import { Matrix } from '@pixi/math'; -import vertex from './spriteMaskFilter.vert'; -import fragment from './spriteMaskFilter.frag'; -import { default as TextureMatrix } from '../../../textures/TextureMatrix'; - -/** - * The SpriteMaskFilter class - * - * @class - * @extends PIXI.Filter - * @memberof PIXI - */ -export default class SpriteMaskFilter extends Filter -{ - /** - * @param {PIXI.Sprite} sprite - the target sprite - */ - constructor(sprite) - { - const maskMatrix = new Matrix(); - - super(vertex, fragment); - - sprite.renderable = false; - - this.maskSprite = sprite; - this.maskMatrix = maskMatrix; - } - - /** - * Applies the filter - * - * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from - * @param {PIXI.RenderTarget} input - The input render target. - * @param {PIXI.RenderTarget} output - The target to output to. - */ - apply(filterManager, input, output) - { - const maskSprite = this.maskSprite; - const tex = this.maskSprite.texture; - - if (!tex.valid) - { - return; - } - if (!tex.transform) - { - // margin = 0.0, let it bleed a bit, shader code becomes easier - // assuming that atlas textures were made with 1-pixel padding - tex.transform = new TextureMatrix(tex, 0.0); - } - tex.transform.update(); - - this.uniforms.mask = tex; - this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite) - .prepend(tex.transform.mapCoord); - this.uniforms.alpha = maskSprite.worldAlpha; - this.uniforms.maskClamp = tex.transform.uClampFrame; - - filterManager.applyFilter(this, input, output); - } -} diff --git a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.frag b/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.frag deleted file mode 100644 index 0e4aef8..0000000 --- a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.frag +++ /dev/null @@ -1,23 +0,0 @@ -varying vec2 vMaskCoord; -varying vec2 vTextureCoord; - -uniform sampler2D uSampler; -uniform sampler2D mask; -uniform float alpha; -uniform vec4 maskClamp; - -void main(void) -{ - float clip = step(3.5, - step(maskClamp.x, vMaskCoord.x) + - step(maskClamp.y, vMaskCoord.y) + - step(vMaskCoord.x, maskClamp.z) + - step(vMaskCoord.y, maskClamp.w)); - - vec4 original = texture2D(uSampler, vTextureCoord); - vec4 masky = texture2D(mask, vMaskCoord); - - original *= (masky.r * masky.a * alpha * clip); - - gl_FragColor = original; -} diff --git a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.vert b/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.vert deleted file mode 100644 index d23a30c..0000000 --- a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.vert +++ /dev/null @@ -1,16 +0,0 @@ -attribute vec2 aVertexPosition; -attribute vec2 aTextureCoord; - -uniform mat3 projectionMatrix; -uniform mat3 otherMatrix; - -varying vec2 vMaskCoord; -varying vec2 vTextureCoord; - -void main(void) -{ - gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); - - vTextureCoord = aTextureCoord; - vMaskCoord = ( otherMatrix * vec3( aTextureCoord, 1.0) ).xy; -} diff --git a/packages/core/src/renderers/systems/BatchSystem.js b/packages/core/src/renderers/systems/BatchSystem.js deleted file mode 100644 index 70e9518..0000000 --- a/packages/core/src/renderers/systems/BatchSystem.js +++ /dev/null @@ -1,68 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import ObjectRenderer from '../utils/ObjectRenderer'; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ - -export default class BatchSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this manager works for. - */ - constructor(renderer) - { - super(renderer); - - /** - * An empty renderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.emptyRenderer = new ObjectRenderer(renderer); - - /** - * The currently active ObjectRenderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.currentRenderer = this.emptyRenderer; - } - - /** - * Changes the current renderer to the one given in parameter - * - * @param {PIXI.ObjectRenderer} objectRenderer - The object renderer to use. - */ - setObjectRenderer(objectRenderer) - { - if (this.currentRenderer === objectRenderer) - { - return; - } - - this.currentRenderer.stop(); - this.currentRenderer = objectRenderer; - - this.renderer.state.setState(objectRenderer.state); - - this.currentRenderer.start(); - } - - /** - * This should be called if you wish to do some custom rendering - * It will basically render anything that may be batched up such as sprites - * - */ - flush() - { - this.setObjectRenderer(this.emptyRenderer); - } - - reset() - { - this.setObjectRenderer(this.emptyRenderer); - } -} diff --git a/packages/core/src/renderers/systems/ContextSystem.js b/packages/core/src/renderers/systems/ContextSystem.js deleted file mode 100644 index 43bf827..0000000 --- a/packages/core/src/renderers/systems/ContextSystem.js +++ /dev/null @@ -1,186 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { settings } from '@pixi/settings'; - -let CONTEXT_UID = 0; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class ContextSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this.webGLVersion = 1; - - this.handleContextLost = this.handleContextLost.bind(this); - this.handleContextRestored = this.handleContextRestored.bind(this); - - this.extensions = {}; - - renderer.view.addEventListener('webglcontextlost', this.handleContextLost, false); - renderer.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); - } - - get isLost() - { - return (!this.gl || this.gl.isContextLost()); - } - - contextChange(gl) - { - this.gl = gl; - - // restore a context if it was previously lost - if (gl.isContextLost() && gl.getExtension('WEBGL_lose_context')) - { - gl.getExtension('WEBGL_lose_context').restoreContext(); - } - } - - initFromContext(gl) - { - this.gl = gl; - this.validateContext(gl); - this.renderer.gl = gl; - this.renderer.CONTEXT_UID = CONTEXT_UID++; - this.renderer.runners.contextChange.run(gl); - } - - initFromOptions(options) - { - const gl = this.createContext(this.renderer.view, options); - - this.initFromContext(gl); - } - - /** - * Helper class to create a webGL Context - * - * @class - * @memberof PIXI.glCore - * @param canvas {HTMLCanvasElement} the canvas element that we will get the context from - * @param options {Object} An options object that gets passed in to the canvas element containing the context attributes, - * see https://developer.mozilla.org/en/docs/Web/API/HTMLCanvasElement/getContext for the options available - * @return {WebGLRenderingContext} the WebGL context - */ - createContext(canvas, options) - { - let gl; - - if (settings.PREFER_WEBGL_2) - { - gl = canvas.getContext('webgl2', options); - } - - if (gl) - { - this.webGLVersion = 2; - } - else - { - this.webGLVersion = 1; - - gl = canvas.getContext('webgl', options) - || canvas.getContext('experimental-webgl', options); - - if (!gl) - { - // fail, not able to get a context - throw new Error('This browser does not support webGL. Try using the canvas renderer'); - } - } - - this.gl = gl; - - this.getExtensions(); - - return gl; - } - - getExtensions() - { - // time to set up default etensions that pixi uses.. - const gl = this.gl; - const extensions = this.extensions; - - if (this.webGLVersion === 1) - { - extensions.drawBuffers = gl.getExtension('WEBGL_draw_buffers'); - extensions.depthTexture = gl.getExtension('WEBKIT_WEBGL_depth_texture'); - extensions.floatTexture = gl.getExtension('OES_texture_float'); - extensions.loseContext = gl.getExtension('WEBGL_lose_context'); - - extensions.vertexArrayObject = gl.getExtension('OES_vertex_array_object') - || gl.getExtension('MOZ_OES_vertex_array_object') - || gl.getExtension('WEBKIT_OES_vertex_array_object'); - } - - // we don't use any specific WebGL 2 ones yet! - } - - /** - * Handles a lost webgl context - * - * @private - * @param {WebGLContextEvent} event - The context lost event. - */ - handleContextLost(event) - { - event.preventDefault(); - } - - /** - * Handles a restored webgl context - * - * @private - */ - handleContextRestored() - { - this.renderer.runners.contextChange.run(this.gl); - } - - destroy() - { - const view = this.renderer.view; - - // remove listeners - view.removeEventListener('webglcontextlost', this.handleContextLost); - view.removeEventListener('webglcontextrestored', this.handleContextRestored); - - this.gl.useProgram(null); - - if (this.extensions.loseContext) - { - this.extensions.loseContext.loseContext(); - } - } - - postrender() - { - this.gl.flush(); - } - - validateContext(gl) - { - const attributes = gl.getContextAttributes(); - - // this is going to be fairly simple for now.. but at least we have room to grow! - if (!attributes.stencil) - { - /* eslint-disable max-len */ - - /* eslint-disable no-console */ - console.warn('Provided WebGL context does not have a stencil buffer, masks may not render correctly'); - /* eslint-enable no-console */ - - /* eslint-enable max-len */ - } - } -} diff --git a/packages/core/src/renderers/systems/FramebufferSystem.js b/packages/core/src/renderers/systems/FramebufferSystem.js deleted file mode 100644 index df7902b..0000000 --- a/packages/core/src/renderers/systems/FramebufferSystem.js +++ /dev/null @@ -1,246 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { Rectangle } from '@pixi/math'; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class FramebufferSystem extends WebGLSystem -{ - /** - * Sets up the renderer context and necessary buffers. - * - * @private - */ - contextChange() - { - this.gl = this.renderer.gl; - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - this.current = null; - this.viewport = new Rectangle(); - - this.drawBufferExtension = this.renderer.context.extensions.drawBuffers; - } - - bind(framebuffer, frame) - { - const gl = this.gl; - - this.current = framebuffer; - - if (framebuffer) - { - // TODO cacheing layer! - - const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID] || this.initFramebuffer(framebuffer); - - gl.bindFramebuffer(gl.FRAMEBUFFER, fbo.framebuffer); - // makesure all textures are unbound.. - - // now check for updates... - if (fbo.dirtyId !== framebuffer.dirtyId) - { - fbo.dirtyId = framebuffer.dirtyId; - - if (fbo.dirtyFormat !== framebuffer.dirtyFormat) - { - fbo.dirtyFormat = framebuffer.dirtyFormat; - this.updateFramebuffer(framebuffer); - } - else if (fbo.dirtySize !== framebuffer.dirtySize) - { - fbo.dirtySize = framebuffer.dirtySize; - this.resizeFramebuffer(framebuffer); - } - } - - for (let i = 0; i < framebuffer.colorTextures.length; i++) - { - if (framebuffer.colorTextures[i].texturePart) - { - this.renderer.texture.unbind(framebuffer.colorTextures[i].texture); - } - else - { - this.renderer.texture.unbind(framebuffer.colorTextures[i]); - } - } - - if (framebuffer.depthTexture) - { - this.renderer.texture.unbind(framebuffer.depthTexture); - } - - if (frame) - { - this.setViewport(frame.x, frame.y, frame.width, frame.height); - } - else - { - this.setViewport(0, 0, framebuffer.width, framebuffer.height); - } - } - else - { - gl.bindFramebuffer(gl.FRAMEBUFFER, null); - - if (frame) - { - this.setViewport(frame.x, frame.y, frame.width, frame.height); - } - else - { - this.setViewport(0, 0, this.renderer.width, this.renderer.height); - } - } - } - - setViewport(x, y, width, height) - { - const v = this.viewport; - - if (v.width !== width || v.height !== height || v.x !== x || v.y !== y) - { - v.x = x; - v.y = y; - v.width = width; - v.height = height; - - this.gl.viewport(x, y, width, height); - } - } - - get size() - { - if (this.current) - { - // TODO store temp - return { x: 0, y: 0, width: this.current.width, height: this.current.height }; - } - - return { x: 0, y: 0, width: this.renderer.width, height: this.renderer.height }; - } - - clear(r, g, b, a) - { - const gl = this.gl; - - // TODO clear color can be set only one right? - gl.clearColor(r, g, b, a); - gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); - } - - // private functions... - - initFramebuffer(framebuffer) - { - const gl = this.gl; - - // TODO - make this a class? - const fbo = { - framebuffer: gl.createFramebuffer(), - stencil: null, - dirtyId: 0, - dirtyFormat: 0, - dirtySize: 0, - }; - - framebuffer.glFrameBuffers[this.CONTEXT_UID] = fbo; - - return fbo; - } - - resizeFramebuffer(framebuffer) - { - const gl = this.gl; - - if (framebuffer.stencil || framebuffer.depth) - { - gl.bindRenderbuffer(gl.RENDERBUFFER, this.stencil); - gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); - } - } - - updateFramebuffer(framebuffer) - { - const gl = this.gl; - - const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID]; - - // bind the color texture - const colorTextures = framebuffer.colorTextures; - - let count = colorTextures.length; - - if (!this.drawBufferExtension) - { - count = Math.min(count, 1); - } - - const activeTextures = []; - - for (let i = 0; i < count; i++) - { - const texture = framebuffer.colorTextures[i]; - - if (texture.texturePart) - { - this.renderer.texture.bind(texture.texture, 0); - - gl.framebufferTexture2D(gl.FRAMEBUFFER, - gl.COLOR_ATTACHMENT0 + i, - gl.TEXTURE_CUBE_MAP_NEGATIVE_X + texture.side, - texture.texture._glTextures[this.CONTEXT_UID].texture, - 0); - } - else - { - this.renderer.texture.bind(texture, 0); - - gl.framebufferTexture2D(gl.FRAMEBUFFER, - gl.COLOR_ATTACHMENT0 + i, - gl.TEXTURE_2D, - texture._glTextures[this.CONTEXT_UID].texture, - 0); - } - - activeTextures.push(gl.COLOR_ATTACHMENT0 + i); - } - - if (this.drawBufferExtension && activeTextures.length > 1) - { - this.drawBufferExtension.drawBuffersWEBGL(activeTextures); - } - - if (framebuffer.depthTexture) - { - const depthTextureExt = this.renderer.context.extensions.depthTexture; - - if (depthTextureExt) - { - const depthTexture = framebuffer.depthTexture; - - this.renderer.texture.bind(depthTexture, 0); - - gl.framebufferTexture2D(gl.FRAMEBUFFER, - gl.DEPTH_ATTACHMENT, - gl.TEXTURE_2D, - depthTexture._glTextures[this.CONTEXT_UID].texture, - 0); - } - } - - if (framebuffer.stencil || framebuffer.depth) - { - fbo.stencil = gl.createRenderbuffer(); - - gl.bindRenderbuffer(gl.RENDERBUFFER, fbo.stencil); - - // TODO.. this is depth AND stencil? - gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, fbo.stencil); - gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); - // fbo.enableStencil(); - } - } -} diff --git a/packages/core/src/renderers/systems/MaskSystem.js b/packages/core/src/renderers/systems/MaskSystem.js deleted file mode 100644 index d9c4b1a..0000000 --- a/packages/core/src/renderers/systems/MaskSystem.js +++ /dev/null @@ -1,199 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import AlphaMaskFilter from '../filters/spriteMask/SpriteMaskFilter'; - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class MaskSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - // TODO - we don't need both! - this.scissor = false; - this.scissorData = null; - this.scissorRenderTarget = null; - - this.enableScissor = false; - - this.alphaMaskPool = []; - this.alphaMaskIndex = 0; - } - - /** - * Applies the Mask and adds it to the current filter stack. - * - * @param {PIXI.DisplayObject} target - Display Object to push the mask to - * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. - */ - push(target, maskData) - { - // TODO the root check means scissor rect will not - // be used on render textures more info here: - // https://github.com/pixijs/pixi.js/pull/3545 - - if (maskData.texture) - { - this.pushSpriteMask(target, maskData); - } - else if (this.enableScissor - && !this.scissor - && this.renderer._activeRenderTarget.root - && !this.renderer.stencil.stencilMaskStack.length - && maskData.isFastRect()) - { - const matrix = maskData.worldTransform; - - let rot = Math.atan2(matrix.b, matrix.a); - - // use the nearest degree! - rot = Math.round(rot * (180 / Math.PI)); - - if (rot % 90) - { - this.pushStencilMask(maskData); - } - else - { - this.pushScissorMask(target, maskData); - } - } - else - { - this.pushStencilMask(maskData); - } - } - - /** - * Removes the last mask from the mask stack and doesn't return it. - * - * @param {PIXI.DisplayObject} target - Display Object to pop the mask from - * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. - */ - pop(target, maskData) - { - if (maskData.texture) - { - this.popSpriteMask(target, maskData); - } - else if (this.enableScissor && !this.renderer.stencil.stencilMaskStack.length) - { - this.popScissorMask(target, maskData); - } - else - { - this.popStencilMask(target, maskData); - } - } - - /** - * Applies the Mask and adds it to the current filter stack. - * - * @param {PIXI.RenderTarget} target - Display Object to push the sprite mask to - * @param {PIXI.Sprite} maskData - Sprite to be used as the mask - */ - pushSpriteMask(target, maskData) - { - let alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; - - if (!alphaMaskFilter) - { - alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; - } - - alphaMaskFilter[0].resolution = this.renderer.resolution; - alphaMaskFilter[0].maskSprite = maskData; - - // TODO - may cause issues! - target.filterArea = maskData.getBounds(true); - - this.renderer.filterSystem.pushFilter(target, alphaMaskFilter); - - this.alphaMaskIndex++; - } - - /** - * Removes the last filter from the filter stack and doesn't return it. - * - */ - popSpriteMask() - { - this.renderer.filterSystem.popFilter(); - this.alphaMaskIndex--; - } - - /** - * Applies the Mask and adds it to the current filter stack. - * - * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. - */ - pushStencilMask(maskData) - { - this.renderer.batch.flush(); - this.renderer.stencil.pushStencil(maskData); - } - - /** - * Removes the last filter from the filter stack and doesn't return it. - * - */ - popStencilMask() - { - // this.renderer.currentRenderer.stop(); - this.renderer.stencil.popStencil(); - } - - /** - * - * @param {PIXI.DisplayObject} target - Display Object to push the mask to - * @param {PIXI.Graphics} maskData - The masking data. - */ - pushScissorMask(target, maskData) - { - maskData.renderable = true; - - const renderTarget = this.renderer._activeRenderTarget; - - const bounds = maskData.getBounds(); - - bounds.fit(renderTarget.size); - maskData.renderable = false; - - this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); - - const resolution = this.renderer.resolution; - - this.renderer.gl.scissor( - bounds.x * resolution, - (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, - bounds.width * resolution, - bounds.height * resolution - ); - - this.scissorRenderTarget = renderTarget; - this.scissorData = maskData; - this.scissor = true; - } - - /** - * - * - */ - popScissorMask() - { - this.scissorRenderTarget = null; - this.scissorData = null; - this.scissor = false; - - // must be scissor! - const gl = this.renderer.gl; - - gl.disable(gl.SCISSOR_TEST); - } -} diff --git a/packages/core/src/renderers/systems/ProjectionSystem.js b/packages/core/src/renderers/systems/ProjectionSystem.js deleted file mode 100644 index ec5efdb..0000000 --- a/packages/core/src/renderers/systems/ProjectionSystem.js +++ /dev/null @@ -1,73 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { Matrix } from '@pixi/math'; - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ - -export default class ProjectionSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this.projectionMatrix = new Matrix(); - } - - update(destinationFrame, sourceFrame, resolution, root) - { - this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; - this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; - - this.calculateProjection(this.destinationFrame, this.sourceFrame, resolution, root); - - this.renderer.globalUniforms.uniforms.projectionMatrix = this.projectionMatrix; - this.renderer.globalUniforms.update(); - } - - /** - * Updates the projection matrix based on a projection frame (which is a rectangle) - * - * @param {Rectangle} destinationFrame - The destination frame. - * @param {Rectangle} sourceFrame - The source frame. - */ - calculateProjection(destinationFrame, sourceFrame, resolution, root) - { - const pm = this.projectionMatrix; - - // I don't think we will need this line.. - // pm.identity(); - - if (!root) - { - pm.a = (1 / destinationFrame.width * 2) * resolution; - pm.d = (1 / destinationFrame.height * 2) * resolution; - - pm.tx = -1 - (sourceFrame.x * pm.a); - pm.ty = -1 - (sourceFrame.y * pm.d); - } - else - { - pm.a = (1 / destinationFrame.width * 2) * resolution; - pm.d = (-1 / destinationFrame.height * 2) * resolution; - - pm.tx = -1 - (sourceFrame.x * pm.a); - pm.ty = 1 - (sourceFrame.y * pm.d); - } - } - - /** - * Sets the transform of the active render target to the given matrix - * - * @param {PIXI.Matrix} matrix - The transformation matrix - */ - setTransform()// matrix) - { - // this._activeRenderTarget.transform = matrix; - } -} diff --git a/packages/core/src/renderers/systems/RenderTextureSystem.js b/packages/core/src/renderers/systems/RenderTextureSystem.js deleted file mode 100644 index a133ef1..0000000 --- a/packages/core/src/renderers/systems/RenderTextureSystem.js +++ /dev/null @@ -1,115 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { Rectangle } from '@pixi/math'; - -const tempRect = new Rectangle(); - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ - -export default class RenderTextureSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this.clearColor = renderer._backgroundColorRgba; - - // TODO moe this property somewhere else! - this.defaultMaskStack = []; - this.defaultFilterStack = [{}]; - - // empty render texture? - this.renderTexture = null; - - this.destinationFrame = new Rectangle(); - } - - bind(renderTexture, sourceFrame, destinationFrame) - { - // TODO - do we want this?? - if (this.renderTexture === renderTexture) return; - this.renderTexture = renderTexture; - - const renderer = this.renderer; - - if (renderTexture) - { - const baseTexture = renderTexture.baseTexture; - - if (!destinationFrame) - { - tempRect.width = baseTexture.realWidth; - tempRect.height = baseTexture.realHeight; - - destinationFrame = tempRect; - } - - if (!sourceFrame) - { - sourceFrame = destinationFrame; - } - - this.renderer.framebuffer.bind(baseTexture.frameBuffer, destinationFrame); - - this.renderer.projection.update(destinationFrame, sourceFrame, baseTexture.resolution, false); - this.renderer.stencil.setMaskStack(baseTexture.stencilMaskStack); - } - else - { - // TODO these validation checks happen deeper down.. - // thing they can be avoided.. - if (!destinationFrame) - { - tempRect.width = renderer.width; - tempRect.height = renderer.height; - - destinationFrame = tempRect; - } - - if (!sourceFrame) - { - sourceFrame = destinationFrame; - } - - renderer.framebuffer.bind(null, destinationFrame); - - // TODO store this.. - this.renderer.projection.update(destinationFrame, sourceFrame, this.renderer.resolution, true); - this.renderer.stencil.setMaskStack(this.defaultMaskStack); - } - - this.destinationFrame.copyFrom(destinationFrame); - } - - /** - * Erases the render texture and fills the drawing area with a colour - * - * @param {number} [clearColor] - The colour - * @return {PIXI.Renderer} Returns itself. - */ - clear(clearColor) - { - if (this.renderTexture) - { - clearColor = clearColor || this.renderTexture.baseTexture.clearColor; - } - else - { - clearColor = clearColor || this.clearColor; - } - - this.renderer.framebuffer.clear(clearColor[0], clearColor[1], clearColor[2], clearColor[3]); - } - - resize()// screenWidth, screenHeight) - { - // resize the root only! - this.bind(null); - } -} diff --git a/packages/core/src/renderers/systems/StateSystem.js b/packages/core/src/renderers/systems/StateSystem.js deleted file mode 100755 index d7f4caf..0000000 --- a/packages/core/src/renderers/systems/StateSystem.js +++ /dev/null @@ -1,288 +0,0 @@ -import mapWebGLBlendModesToPixi from '../utils/mapWebGLBlendModesToPixi'; -import WebGLSystem from './WebGLSystem'; -import WebGLState from '../State'; - -const BLEND = 0; -const OFFSET = 1; -const CULLING = 2; -const DEPTH_TEST = 3; -const WINDING = 4; - -/** - * A WebGL state machines - * - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class StateSystem extends WebGLSystem -{ - /** - * @param {WebGLRenderingContext} gl - The current WebGL rendering context - */ - constructor(renderer) - { - super(renderer); - - this.gl = null; - - this.maxAttribs = null; - - // check we have vao.. - this.nativeVaoExtension = null; - - this.attribState = null; - - this.stateId = 0; - this.polygonOffset = 0; - this.blendMode = 17; - - this.map = []; - - // map functions for when we set state.. - this.map[BLEND] = this.setBlend; - this.map[OFFSET] = this.setOffset; - this.map[CULLING] = this.setCullFace; - this.map[DEPTH_TEST] = this.setDepthTest; - this.map[WINDING] = this.setFrontFace; - - this.checks = []; - - this.defaultState = new WebGLState(); - this.defaultState.blend = true; - this.defaultState.depth = true; - } - - contextChange(gl) - { - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; - - this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); - - // check we have vao.. - this.nativeVaoExtension = ( - gl.getExtension('OES_vertex_array_object') - || gl.getExtension('MOZ_OES_vertex_array_object') - || gl.getExtension('WEBKIT_OES_vertex_array_object') - ); - - this.attribState = { - tempAttribState: new Array(this.maxAttribs), - attribState: new Array(this.maxAttribs), - }; - - this.blendModes = mapWebGLBlendModesToPixi(gl); - - this.setState(this.defaultState); - - this.reset(); - } - - /** - * Sets the current state - * - * @param {*} state - The state to set. - */ - setState(state) - { - state = state || this.defaultState; - - // TODO maybe to an object check? ( this.state === state )? - if (this.stateId !== state.data) - { - let diff = this.stateId ^ state.data; - let i = 0; - - // order from least to most common - while (diff) - { - if (diff & 1) - { - // state change! - this.map[i].call(this, !!(state.data & (1 << i))); - } - - diff = diff >> 1; - i++; - } - - this.stateId = state.data; - } - - // based on the above settings we check for specific modes.. - // for example if blend is active we check and set the blend modes - // or of polygon offset is active we check the poly depth. - for (let i = 0; i < this.checks.length; i++) - { - this.checks[i](this, state); - } - } - - /** - * Enables or disabled blending. - * - * @param {boolean} value - Turn on or off webgl blending. - */ - setBlend(value) - { - this.updateCheck(StateSystem.checkBlendMode, value); - - this.gl[value ? 'enable' : 'disable'](this.gl.BLEND); - } - - /** - * Enables or disable polygon offset fill - * - * @param {boolean} value - Turn on or off webgl polygon offset testing. - */ - setOffset(value) - { - this.gl[value ? 'enable' : 'disable'](this.gl.POLYGON_OFFSET_FILL); - } - - /** - * Sets whether to enable or disable depth test. - * - * @param {boolean} value - Turn on or off webgl depth testing. - */ - setDepthTest(value) - { - this.gl[value ? 'enable' : 'disable'](this.gl.DEPTH_TEST); - } - - /** - * Sets whether to enable or disable cull face. - * - * @param {boolean} value - Turn on or off webgl cull face. - */ - setCullFace(value) - { - this.gl[value ? 'enable' : 'disable'](this.gl.CULL_FACE); - } - - /** - * Sets the gl front face. - * - * @param {boolean} value - true is clockwise and false is counter-clockwise - */ - setFrontFace(value) - { - this.gl.frontFace(this.gl[value ? 'CW' : 'CCW']); - } - - /** - * Sets the blend mode. - * - * @param {number} value - The blend mode to set to. - */ - setBlendMode(value) - { - if (value === this.blendMode) - { - return; - } - - this.blendMode = value; - this.gl.blendFunc(this.blendModes[value][0], this.blendModes[value][1]); - } - - /** - * Sets the polygon offset. - * - * @param {number} value - the polygon offset - * @param {number} scale - the polygon offset scale - */ - setPolygonOffset(value, scale) - { - this.gl.polygonOffset(value, scale); - } - - /** - * Disables all the vaos in use - * - */ - resetAttributes() - { - for (let i = 0; i < this.attribState.tempAttribState.length; i++) - { - this.attribState.tempAttribState[i] = 0; - } - - for (let i = 0; i < this.attribState.attribState.length; i++) - { - this.attribState.attribState[i] = 0; - } - - // im going to assume one is always active for performance reasons. - for (let i = 1; i < this.maxAttribs; i++) - { - this.gl.disableVertexAttribArray(i); - } - } - - // used - /** - * Resets all the logic and disables the vaos - */ - reset() - { - // unbind any VAO if they exist.. - if (this.nativeVaoExtension) - { - this.nativeVaoExtension.bindVertexArrayOES(null); - } - - // reset all attributes.. - this.resetAttributes(); - - this.gl.pixelStorei(this.gl.UNPACK_FLIP_Y_WEBGL, false); - - this.setBlendMode(0); - - // TODO? - // this.setState(this.defaultState); - } - - /** - * checks to see which updates should be checked based on which settings have been activated - * for example if blend is enabled then we shold check the blend modes each time the state is changed - * or if poygon fill is activated then we need to check if the polygone offset changes. - * The idea is that we only check what we have too. - * - * @param {Function} func the checking function to add or remove - * @param {boolean} value should the check function be added or removed. - */ - updateCheck(func, value) - { - const index = this.checks.indexOf(func); - - if (value && index === -1) - { - this.checks.push(func); - } - else if (!value && index !== -1) - { - this.checks.splice(index, 1); - } - } - - /** - * A private little wrapper function that we call to check the blend mode. - * - * @static - * @private - * @param {PIXI.StateSystem} System the System to perform the state check on - * @param {PIXI.State} state the state that the blendMode will pulled from - */ - static checkBlendMode(system, state) - { - system.setBlendMode(state.blendMode); - } - - // TODO - add polygon offset? -} diff --git a/packages/core/src/renderers/systems/StencilSystem.js b/packages/core/src/renderers/systems/StencilSystem.js deleted file mode 100644 index 5d538cb..0000000 --- a/packages/core/src/renderers/systems/StencilSystem.js +++ /dev/null @@ -1,133 +0,0 @@ -import WebGLSystem from './WebGLSystem'; - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class StencilSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - this.stencilMaskStack = []; - } - - /** - * Changes the mask stack that is used by this System. - * - * @param {PIXI.Graphics[]} stencilMaskStack - The mask stack - */ - setMaskStack(stencilMaskStack) - { - const gl = this.renderer.gl; - - if (stencilMaskStack.length !== this.stencilMaskStack.length) - { - if (stencilMaskStack.length === 0) - { - gl.disable(gl.STENCIL_TEST); - } - else - { - gl.enable(gl.STENCIL_TEST); - } - } - - this.stencilMaskStack = stencilMaskStack; - } - - /** - * Applies the Mask and adds it to the current stencil stack. @alvin - * - * @param {PIXI.Graphics} graphics - The mask - */ - pushStencil(graphics) - { - this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); - - // this.renderer._activeRenderTarget.attachStencilBuffer(); - - const gl = this.renderer.gl; - const prevMaskCount = this.stencilMaskStack.length; - - if (prevMaskCount === 0) - { - gl.enable(gl.STENCIL_TEST); - } - - this.stencilMaskStack.push(graphics); - - // Increment the refference stencil value where the new mask overlaps with the old ones. - gl.colorMask(false, false, false, false); - gl.stencilFunc(gl.EQUAL, prevMaskCount, this._getBitwiseMask()); - gl.stencilOp(gl.KEEP, gl.KEEP, gl.INCR); - this.renderer.plugins.graphics.render(graphics); - - this._useCurrent(); - } - - /** - * Removes the last mask from the stencil stack. @alvin - */ - popStencil() - { - this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); - - const gl = this.renderer.gl; - const graphics = this.stencilMaskStack.pop(); - - if (this.stencilMaskStack.length === 0) - { - // the stack is empty! - gl.disable(gl.STENCIL_TEST); - gl.clear(gl.STENCIL_BUFFER_BIT); - gl.clearStencil(0); - } - else - { - // Decrement the refference stencil value where the popped mask overlaps with the other ones - gl.colorMask(false, false, false, false); - gl.stencilOp(gl.KEEP, gl.KEEP, gl.DECR); - this.renderer.plugins.graphics.render(graphics); - - this._useCurrent(); - } - } - - /** - * Setup renderer to use the current stencil data. - */ - _useCurrent() - { - const gl = this.renderer.gl; - - gl.colorMask(true, true, true, true); - gl.stencilFunc(gl.EQUAL, this.stencilMaskStack.length, this._getBitwiseMask()); - gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP); - } - - /** - * Fill 1s equal to the number of acitve stencil masks. - * - * @return {number} The bitwise mask. - */ - _getBitwiseMask() - { - return (1 << this.stencilMaskStack.length) - 1; - } - - /** - * Destroys the mask stack. - * - */ - destroy() - { - super.destroy(this); - - this.stencilMaskStack = null; - } -} diff --git a/packages/core/src/renderers/systems/WebGLSystem.js b/packages/core/src/renderers/systems/WebGLSystem.js deleted file mode 100644 index d3d5b31..0000000 --- a/packages/core/src/renderers/systems/WebGLSystem.js +++ /dev/null @@ -1,41 +0,0 @@ -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this manager works for. - */ - constructor(renderer) - { - /** - * The renderer this manager works for. - * - * @member {PIXI.Renderer} - */ - this.renderer = renderer; - - this.renderer.runners.contextChange.add(this); - } - - /** - * Generic method called when there is a WebGL context change. - * - */ - contextChange() - { - // do some codes init! - } - - /** - * Generic destroy methods to be overridden by the subclass - * - */ - destroy() - { - this.renderer.runners.contextChange.remove(this); - this.renderer = null; - } -} diff --git a/packages/core/src/renderers/systems/filter/FilterSystem.js b/packages/core/src/renderers/systems/filter/FilterSystem.js deleted file mode 100644 index e615037..0000000 --- a/packages/core/src/renderers/systems/filter/FilterSystem.js +++ /dev/null @@ -1,431 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; - -import RenderTexture from '../../../textures/RenderTexture'; -import Quad from '../../utils/Quad'; -import QuadUv from '../../utils/QuadUv'; -import { Rectangle } from '@pixi/math'; -import * as filterTransforms from '../../filters/filterTransforms'; -import bitTwiddle from 'bit-twiddle'; -import UniformGroup from '../../../shader/UniformGroup'; -import { DRAW_MODES } from '../../../../../constants'; - -// -/** - * @ignore - * @class - */ -class FilterState -{ - /** - * - */ - constructor() - { - this.renderTexture = null; - this.sourceFrame = new Rectangle(); - this.destinationFrame = new Rectangle(); - this.filters = []; - this.target = null; - this.legacy = false; - this.resolution = 1; - } -} - -/** - * @class - * @memberof PIXI.systems - * @extends PIXI.systems.WebGLSystem - */ -export default class FilterSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - /** - * stores a bunch of PO2 textures used for filtering - * @type {Object} - */ - this.texturePool = {}; - - /** - * a pool for storing filter states, save us creating new ones each tick - * @type {Array} - */ - this.statePool = []; - - /** - * A very simple geometry used when drawing a filter effect to the screen - * @type {Quad} - */ - this.quad = new Quad(); - - this.quadUv = new QuadUv(); - - /** - * Temporary rect for maths - * @type {PIXI.Rectangle} - */ - this.tempRect = new Rectangle(); - - this.activeState = {}; - - /** - * this uniform group is attached to filter uniforms when used - * @type {UniformGroup} - */ - this.globalUniforms = new UniformGroup({ - sourceFrame: this.tempRect, - destinationFrame: this.tempRect, - - // legacy variables - filterArea: new Float32Array(4), - filterClamp: new Float32Array(4), - }, true); - } - - /** - * Adds a new filter to the System. - * - * @param {PIXI.DisplayObject} target - The target of the filter to render. - * @param {PIXI.Filter[]} filters - The filters to apply. - */ - push(target, filters) - { - const renderer = this.renderer; - const filterStack = this.renderer.renderTexture.defaultFilterStack; - const state = this.statePool.pop() || new FilterState(); - - let resolution = filters[0].resolution; - let padding = filters[0].padding; - let autoFit = filters[0].autoFit; - let legacy = filters[0].legacy; - - for (let i = 1; i < filters.length; i++) - { - const filter = filters[i]; - - // lets use the lowest resolution.. - resolution = Math.min(resolution, filter.resolution); - // and the largest amount of padding! - padding = Math.max(padding, filter.padding); - // only auto fit if all filters are autofit - autoFit = autoFit || filter.autoFit; - - legacy = legacy || filter.legacy; - } - - filterStack.push(state); - - state.resolution = resolution; - - state.legacy = legacy; - - // round to whole number based on resolution - // TODO move that to the shader too? - state.sourceFrame = target.filterArea || target.getBounds(true); - - state.sourceFrame.pad(padding); - - if (autoFit) - { - state.sourceFrame.fit(this.renderer.renderTexture.destinationFrame); - } - - state.sourceFrame.round(resolution); - - state.renderTexture = this.getPotFilterTexture(state.sourceFrame.width, state.sourceFrame.height, resolution); - state.filters = filters; - - state.destinationFrame.width = state.renderTexture.width; - state.destinationFrame.height = state.renderTexture.height; - - state.renderTexture.filterFrame = state.sourceFrame; - renderer.renderTexture.bind(state.renderTexture, state.sourceFrame);// /, state.destinationFrame); - renderer.renderTexture.clear(); - } - - /** - * Pops off the filter and applies it. - * - */ - pop() - { - const renderer = this.renderer; - const filterStack = renderer.renderTexture.defaultFilterStack; - const state = filterStack.pop(); - const filters = state.filters; - - this.activeState = state; - - const globalUniforms = this.globalUniforms.uniforms; - - globalUniforms.sourceFrame = state.sourceFrame; - globalUniforms.destinationFrame = state.destinationFrame; - globalUniforms.resolution = state.resolution; - - // only update the rect if its legacy.. - if (state.legacy) - { - const filterArea = globalUniforms.filterArea; - const filterClamp = globalUniforms.filterClamp; - - filterArea[0] = state.destinationFrame.width; - filterArea[1] = state.destinationFrame.height; - filterArea[2] = state.sourceFrame.x; - filterArea[3] = state.sourceFrame.y; - - filterClamp[0] = 0.5 / state.resolution / state.destinationFrame.width; - filterClamp[1] = 0.5 / state.resolution / state.destinationFrame.height; - filterClamp[2] = (state.sourceFrame.width - 0.5) / state.resolution / state.destinationFrame.width; - filterClamp[3] = (state.sourceFrame.height - 0.5) / state.resolution / state.destinationFrame.height; - } - - this.globalUniforms.update(); - - const lastState = filterStack[filterStack.length - 1]; - - if (filters.length === 1) - { - filters[0].apply(this, state.renderTexture, lastState.renderTexture, false, state); - - this.returnFilterTexture(state.renderTexture); - } - else - { - let flip = state.renderTexture; - let flop = this.getPotFilterTexture( - flip.width, - flip.height, - state.resolution - ); - - flop.filterFrame = flip.filterFrame; - - let i = 0; - - for (i = 0; i < filters.length - 1; ++i) - { - filters[i].apply(this, flip, flop, true, state); - - const t = flip; - - flip = flop; - flop = t; - } - - filters[i].apply(this, flip, lastState.renderTexture, false, state); - - this.returnFilterTexture(flip); - this.returnFilterTexture(flop); - } - - this.statePool.push(state); - } - - /** - * Draws a filter. - * - * @param {PIXI.Filter} filter - The filter to draw. - * @param {PIXI.RenderTarget} input - The input render target. - * @param {PIXI.RenderTarget} output - The target to output to. - * @param {boolean} clear - Should the output be cleared before rendering to it - */ - applyFilter(filter, input, output, clear) - { - const renderer = this.renderer; - - renderer.renderTexture.bind(output, output ? output.filterFrame : null); - - if (clear) - { - // gl.disable(gl.SCISSOR_TEST); - renderer.renderTexture.clear(); - // gl.enable(gl.SCISSOR_TEST); - } - - // set the uniforms.. - filter.uniforms.uSampler = input; - filter.uniforms.filterGlobals = this.globalUniforms; - - // TODO make it so that the order of this does not matter.. - // because it does at the moment cos of global uniforms. - // they need to get resynced - - renderer.state.setState(filter.state); - renderer.shader.bind(filter); - - if (filter.legacy) - { - this.quadUv.map(input._frame, input.filterFrame); - - renderer.geometry.bind(this.quadUv); - renderer.geometry.draw(DRAW_MODES.TRIANGLES); - } - else - { - renderer.geometry.bind(this.quad); - renderer.geometry.draw(DRAW_MODES.TRIANGLE_STRIP); - } - } - - /** - * Calculates the mapped matrix. - * - * TODO playing around here.. this is temporary - (will end up in the shader) - * this returns a matrix that will normalise map filter cords in the filter to screen space - * - * @param {PIXI.Matrix} outputMatrix - the matrix to output to. - * @return {PIXI.Matrix} The mapped matrix. - */ - calculateScreenSpaceMatrix(outputMatrix) - { - const currentState = this.activeState; - - return filterTransforms.calculateScreenSpaceMatrix( - outputMatrix, - currentState.sourceFrame, - currentState.destinationFrame - ); - } - - /** - * This will map the filter coord so that a texture can be used based on the transform of a sprite - * - * @param {PIXI.Matrix} outputMatrix - The matrix to output to. - * @param {PIXI.Sprite} sprite - The sprite to map to. - * @return {PIXI.Matrix} The mapped matrix. - */ - calculateSpriteMatrix(outputMatrix, sprite) - { - const currentState = this.activeState; - - return filterTransforms.calculateSpriteMatrix( - outputMatrix, - currentState.sourceFrame, - currentState.destinationFrame, - sprite - ); - } - - /** - * Destroys this Filter System. - * - * @param {boolean} [contextLost=false] context was lost, do not free shaders - * - */ - destroy(contextLost = false) - { - if (!contextLost) - { - this.emptyPool(); - } - else - { - this.texturePool = {}; - } - } - - /** - * Gets a Power-of-Two render texture. - * - * TODO move to a seperate class could be on renderer? - * also - could cause issue with multiple contexts? - * - * @private - * @param {WebGLRenderingContext} gl - The webgl rendering context - * @param {number} minWidth - The minimum width of the render target. - * @param {number} minHeight - The minimum height of the render target. - * @param {number} resolution - The resolution of the render target. - * @return {PIXI.RenderTarget} The new render target. - */ - getPotFilterTexture(minWidth, minHeight, resolution) - { - minWidth = bitTwiddle.nextPow2(minWidth); - minHeight = bitTwiddle.nextPow2(minHeight); - resolution = resolution || 1; - - const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); - - if (!this.texturePool[key]) - { - this.texturePool[key] = []; - } - - let renderTexture = this.texturePool[key].pop(); - - if (!renderTexture) - { - // temporary bypass cache.. - // internally - this will cause a texture to be bound.. - renderTexture = RenderTexture.create({ - width: minWidth, - height: minHeight, - resolution, - }); - } - - return renderTexture; - } - - /** - * Gets extra render texture to use inside current filter - * - * @param {number} resolution resolution of the renderTexture - * @returns {PIXI.RenderTexture} - */ - getFilterTexture(resolution) - { - const rt = this.activeState.renderTexture; - - const filterTexture = this.getPotFilterTexture(rt.width, rt.height, resolution || rt.baseTexture.resolution); - - filterTexture.filterFrame = rt.filterFrame; - - return filterTexture; - } - - /** - * Frees a render texture back into the pool. - * - * @param {PIXI.RenderTarget} renderTexture - The renderTarget to free - */ - returnFilterTexture(renderTexture) - { - renderTexture.filterFrame = null; - - const base = renderTexture.baseTexture; - - const minWidth = base.width; - const minHeight = base.height; - - const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); - - this.texturePool[key].push(renderTexture); - } - - /** - * Empties the texture pool. - * - */ - emptyPool() - { - for (const i in this.texturePool) - { - const textures = this.texturePool[i]; - - if (textures) - { - for (let j = 0; j < textures.length; j++) - { - textures[j].destroy(true); - } - } - } - - this.texturePool = {}; - } -} diff --git a/packages/core/src/renderers/systems/geometry/GLBuffer.js b/packages/core/src/renderers/systems/geometry/GLBuffer.js deleted file mode 100644 index 0477e8a..0000000 --- a/packages/core/src/renderers/systems/geometry/GLBuffer.js +++ /dev/null @@ -1,9 +0,0 @@ -export default class GLBuffer -{ - constructor(buffer) - { - this.buffer = buffer; - this.updateID = -1; - this.byteLength = -1; - } -} diff --git a/packages/core/src/renderers/systems/geometry/GeometrySystem.js b/packages/core/src/renderers/systems/geometry/GeometrySystem.js deleted file mode 100644 index 26c8e9e..0000000 --- a/packages/core/src/renderers/systems/geometry/GeometrySystem.js +++ /dev/null @@ -1,388 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; -import GLBuffer from './GLBuffer'; - -const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class GeometrySystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this._activeGeometry = null; - this._activeVao = null; - - this.hasVao = true; - this.hasInstance = true; - } - - /** - * Sets up the renderer context and necessary buffers. - * - * @private - */ - contextChange() - { - const gl = this.gl = this.renderer.gl; - - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - - // webgl2 - if (!gl.createVertexArray) - { - // webgl 1! - let nativeVaoExtension = this.renderer.context.extensions.vertexArrayObject; - - if (this.renderer.legacy) - { - nativeVaoExtension = null; - } - - if (nativeVaoExtension) - { - gl.createVertexArray = () => - nativeVaoExtension.createVertexArrayOES(); - - gl.bindVertexArray = (vao) => - nativeVaoExtension.bindVertexArrayOES(vao); - - gl.deleteVertexArray = (vao) => - nativeVaoExtension.deleteVertexArrayOES(vao); - } - else - { - this.hasVao = false; - gl.createVertexArray = () => - { - // empty - }; - - gl.bindVertexArray = () => - { - // empty - }; - - gl.deleteVertexArray = () => - { - // empty - }; - } - } - - if (!gl.vertexAttribDivisor) - { - const instanceExt = gl.getExtension('ANGLE_instanced_arrays'); - - if (instanceExt) - { - gl.vertexAttribDivisor = (a, b) => - instanceExt.vertexAttribDivisorANGLE(a, b); - - gl.drawElementsInstanced = (a, b, c, d, e) => - instanceExt.drawElementsInstancedANGLE(a, b, c, d, e); - - gl.drawArraysInstanced = (a, b, c, d) => - instanceExt.drawArraysInstancedANGLE(a, b, c, d); - } - else - { - this.hasInstance = false; - } - } - } - - /** - * Binds geometry so that is can be drawn. Creating a Vao if required - * @private - * @param {PIXI.Geometry} geometry instance of geometry to bind - */ - bind(geometry, shader) - { - shader = shader || this.renderer.shader.shader; - - const gl = this.gl; - - // not sure the best way to address this.. - // currently different shaders require different VAOs for the same geometry - // Still mulling over the best way to solve this one.. - // will likely need to modify the shader attribute locations at run time! - let vaos = geometry.glVertexArrayObjects[this.CONTEXT_UID]; - - if (!vaos) - { - geometry.glVertexArrayObjects[this.CONTEXT_UID] = vaos = {}; - } - - const vao = vaos[shader.program.id] || this.initGeometryVao(geometry, shader.program); - - this._activeGeometry = geometry; - - if (this._activeVao !== vao) - { - this._activeVao = vao; - - if (this.hasVao) - { - gl.bindVertexArray(vao); - } - else - { - this.activateVao(geometry, shader.program); - } - } - - // TODO - optimise later! - // don't need to loop through if nothing changed! - // maybe look to add an 'autoupdate' to geometry? - this.updateBuffers(); - } - - reset() - { - this.unbind(); - } - - updateBuffers() - { - const geometry = this._activeGeometry; - const gl = this.gl; - - for (let i = 0; i < geometry.buffers.length; i++) - { - const buffer = geometry.buffers[i]; - - const glBuffer = buffer._glBuffers[this.CONTEXT_UID]; - - if (buffer._updateID !== glBuffer.updateID) - { - glBuffer.updateID = buffer._updateID; - - // TODO can cache this on buffer! maybe added a getter / setter? - const type = buffer.index ? gl.ELEMENT_ARRAY_BUFFER : gl.ARRAY_BUFFER; - const drawType = buffer.static ? gl.STATIC_DRAW : gl.DYNAMIC_DRAW; - - gl.bindBuffer(type, glBuffer.buffer); - - if (glBuffer.byteLength >= buffer.data.byteLength) - { - // offset is always zero for now! - gl.bufferSubData(type, 0, buffer.data); - } - else - { - gl.bufferData(type, buffer.data, drawType); - } - } - } - } - - checkCompatability(geometry, program) - { - // geometry must have at least all the attributes that the shader requires. - const geometryAttributes = geometry.attributes; - const shaderAttributes = program.attributeData; - - for (const j in shaderAttributes) - { - if (!geometryAttributes[j]) - { - throw new Error(`shader and geometry incompatible, geometry missing the "${j}" attribute`); - } - } - } - - /** - * Creates a Vao with the same structure as the geometry and stores it on the geometry. - * @private - * @param {PIXI.Geometry} geometry instance of geometry to to generate Vao for - */ - initGeometryVao(geometry, program) - { - this.checkCompatability(geometry, program); - - const gl = this.gl; - const CONTEXT_UID = this.CONTEXT_UID; - const buffers = geometry.buffers; - const attributes = geometry.attributes; - - const tempStride = {}; - const tempStart = {}; - - for (const j in buffers) - { - tempStride[j] = 0; - tempStart[j] = 0; - } - - for (const j in attributes) - { - if (!attributes[j].size && program.attributeData[j]) - { - attributes[j].size = program.attributeData[j].size; - } - - tempStride[attributes[j].buffer] += attributes[j].size * byteSizeMap[attributes[j].type]; - } - - for (const j in attributes) - { - const attribute = attributes[j]; - const attribSize = attribute.size; - - if (attribute.stride === undefined) - { - if (tempStride[attribute.buffer] === attribSize * byteSizeMap[attribute.type]) - { - attribute.stride = 0; - } - else - { - attribute.stride = tempStride[attribute.buffer]; - } - } - - if (attribute.start === undefined) - { - attribute.start = tempStart[attribute.buffer]; - - tempStart[attribute.buffer] += attribSize * byteSizeMap[attribute.type]; - } - } - - // first update - and create the buffers! - // only create a gl buffer if it actually gets - for (let i = 0; i < buffers.length; i++) - { - const buffer = buffers[i]; - - if (!buffer._glBuffers[CONTEXT_UID]) - { - buffer._glBuffers[CONTEXT_UID] = new GLBuffer(gl.createBuffer()); - } - } - - // TODO - maybe make this a data object? - // lets wait to see if we need to first! - const vao = gl.createVertexArray(); - - gl.bindVertexArray(vao); - - this.activateVao(geometry, program); - - gl.bindVertexArray(null); - - // add it to the cache! - geometry.glVertexArrayObjects[this.CONTEXT_UID][program.id] = vao; - - return vao; - } - - activateVao(geometry, program) - { - const gl = this.gl; - const CONTEXT_UID = this.CONTEXT_UID; - const buffers = geometry.buffers; - const attributes = geometry.attributes; - - if (geometry.indexBuffer) - { - // first update the index buffer if we have one.. - gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, geometry.indexBuffer._glBuffers[CONTEXT_UID].buffer); - } - - let lastBuffer = null; - - // add a new one! - for (const j in attributes) - { - const attribute = attributes[j]; - const buffer = buffers[attribute.buffer]; - const glBuffer = buffer._glBuffers[CONTEXT_UID]; - - if (program.attributeData[j]) - { - if (lastBuffer !== glBuffer) - { - gl.bindBuffer(gl.ARRAY_BUFFER, glBuffer.buffer); - - lastBuffer = glBuffer; - } - - const location = program.attributeData[j].location; - - // TODO introduce state again - // we can optimise this for older devices that have no VAOs - gl.enableVertexAttribArray(location); - - gl.vertexAttribPointer(location, - attribute.size, - attribute.type || gl.FLOAT, - attribute.normalized, - attribute.stride, - attribute.start); - - if (attribute.instance) - { - // TODO calculate instance count based of this... - if (this.hasInstance) - { - gl.vertexAttribDivisor(location, 1); - } - else - { - throw new Error('geometry error, GPU Instancing is not supported on this device'); - } - } - } - } - } - - draw(type, size, start, instanceCount) - { - const gl = this.gl; - const geometry = this._activeGeometry; - - // TODO.. this should not change so maybe cache the function? - - if (geometry.indexBuffer) - { - if (geometry.instanced) - { - /* eslint-disable max-len */ - gl.drawElementsInstanced(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2, instanceCount || 1); - /* eslint-enable max-len */ - } - else - { - gl.drawElements(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2); - } - } - else - if (geometry.instanced) - { - // TODO need a better way to calculate size.. - gl.drawArraysInstanced(type, start, size || geometry.getSize(), instanceCount || 1); - } - else - { - gl.drawArrays(type, start, size || geometry.getSize()); - } - - return this; - } - - unbind() - { - this.gl.bindVertexArray(null); - this._activeVao = null; - this._activeGeometry = null; - } -} diff --git a/packages/core/src/renderers/systems/geometry/setVertexAttribArrays.js b/packages/core/src/renderers/systems/geometry/setVertexAttribArrays.js deleted file mode 100644 index 23b6959..0000000 --- a/packages/core/src/renderers/systems/geometry/setVertexAttribArrays.js +++ /dev/null @@ -1,54 +0,0 @@ -// var GL_MAP = {}; - -/** - * @param gl {WebGLRenderingContext} The current WebGL context - * @param attribs {*} - * @param state {*} - */ -export default function setVertexAttribArrays(gl, attribs, state) -{ - let i; - - if (state) - { - const tempAttribState = state.tempAttribState; - const attribState = state.attribState; - - for (i = 0; i < tempAttribState.length; i++) - { - tempAttribState[i] = false; - } - - // set the new attribs - for (i = 0; i < attribs.length; i++) - { - tempAttribState[attribs[i].attribute.location] = true; - } - - for (i = 0; i < attribState.length; i++) - { - if (attribState[i] !== tempAttribState[i]) - { - attribState[i] = tempAttribState[i]; - - if (state.attribState[i]) - { - gl.enableVertexAttribArray(i); - } - else - { - gl.disableVertexAttribArray(i); - } - } - } - } - else - { - for (i = 0; i < attribs.length; i++) - { - const attrib = attribs[i]; - - gl.enableVertexAttribArray(attrib.attribute.location); - } - } -} diff --git a/packages/core/src/renderers/systems/index.js b/packages/core/src/renderers/systems/index.js deleted file mode 100644 index c843f03..0000000 --- a/packages/core/src/renderers/systems/index.js +++ /dev/null @@ -1,6 +0,0 @@ -/** - * Systems are individual components to the Renderer pipeline. - * @namespace PIXI.systems - */ -export { default as WebGLSystem } from './WebGLSystem'; -export { default as FilterSystem } from './filter/FilterSystem'; diff --git a/packages/core/src/renderers/systems/shader/GLProgram.js b/packages/core/src/renderers/systems/shader/GLProgram.js deleted file mode 100644 index 92980ca..0000000 --- a/packages/core/src/renderers/systems/shader/GLProgram.js +++ /dev/null @@ -1,43 +0,0 @@ -/** - * Helper class to create a webGL Program - * - * @class - * @private - */ -export default class GLProgram -{ - constructor(program, uniformData) - { - /** - * The shader program - * - * @member {WebGLProgram} - */ - this.program = program; - - /** - * holds the uniform data which contains uniform locations - * and current uniform values used for caching and preventing undeed GPU commands - * @type {Object} - */ - this.uniformData = uniformData; - - /** - * uniformGroups holds the various upload functions for the shader. Each uniform group - * and program have a unique upload function generated. - * @type {Object} - */ - this.uniformGroups = {}; - } - - /** - * Destroys this program - * TODO - */ - destroy() - { - this.uniformData = null; - this.uniformGroups = null; - this.program = null; - } -} diff --git a/packages/core/src/renderers/systems/shader/ShaderSystem.js b/packages/core/src/renderers/systems/shader/ShaderSystem.js deleted file mode 100644 index 6cf57b6..0000000 --- a/packages/core/src/renderers/systems/shader/ShaderSystem.js +++ /dev/null @@ -1,170 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; -import GLProgram from './GLProgram'; -import generateUniformsSync from '../../../shader/generateUniformsSync'; -import defaultValue from '../../../shader/defaultValue'; -import compileProgram from './shader/compileProgram'; - -let UID = 0; - -/** - * Helper class to create a webGL Texture - * - * @class - * @memberof PIXI - */ -export default class ShaderSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - A reference to the current renderer - */ - constructor(renderer) - { - super(renderer); - - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = null; - - this.shader = null; - this.program = null; - - this.id = UID++; - } - - contextChange(gl) - { - this.gl = gl; - } - - /** - * Changes the current shader to the one given in parameter - * - * @param {PIXI.Shader} shader - the new shader - * @param {boolean} dontSync - false if the shader should automatically sync its uniforms. - * @returns {PIXI.glCore.glProgram} the glProgram that belongs to the shader. - */ - bind(shader, dontSync) - { - shader.uniforms.globals = this.renderer.globalUniforms; - - const program = shader.program; - const glProgram = program.glPrograms[this.renderer.CONTEXT_UID] || this.generateShader(shader); - - this.shader = shader; - - // TODO - some current pixi plugins bypass this.. so it not safe to use yet.. - if (this.program !== program) - { - this.program = program; - this.gl.useProgram(glProgram.program); - } - - if (!dontSync) - { - this.syncUniformGroup(shader.uniformGroup); - } - - return glProgram; - } - - /** - * Uploads the uniforms values to the currently bound shader. - * - * @param {object} uniforms - the uniforms valiues that be applied to the current shader - */ - setUniforms(uniforms) - { - const shader = this.shader.program; - const glProgram = shader.glPrograms[this.renderer.CONTEXT_UID]; - - shader.syncUniforms(glProgram.uniformData, uniforms, this.renderer); - } - - syncUniformGroup(group) - { - const glProgram = this.getglProgram(); - - if (!group.static || group.dirtyId !== glProgram.uniformGroups[group.id]) - { - glProgram.uniformGroups[group.id] = group.dirtyId; - const syncFunc = group.syncUniforms[this.shader.program.id] || this.createSyncGroups(group); - - syncFunc(glProgram.uniformData, group.uniforms, this.renderer); - } - } - - createSyncGroups(group) - { - group.syncUniforms[this.shader.program.id] = generateUniformsSync(group, this.shader.program.uniformData); - - return group.syncUniforms[this.shader.program.id]; - } - - /** - * Returns the underlying GLShade rof the currently bound shader. - * This can be handy for when you to have a little more control over the setting of your uniforms. - * - * @return {PIXI.glCore.Shader} the glProgram for the currently bound Shader for this context - */ - getglProgram() - { - if (this.shader) - { - return this.shader.program.glPrograms[this.renderer.CONTEXT_UID]; - } - - return null; - } - - /** - * Generates a glProgram verion of the Shader provided. - * - * @private - * @param {PIXI.Shader} shader the shader that the glProgram will be based on. - * @return {PIXI.glCore.glProgram} A shiney new glProgram - */ - generateShader(shader) - { - const gl = this.gl; - - const program = shader.program; - - const attribMap = {}; - - for (const i in program.attributeData) - { - attribMap[i] = program.attributeData[i].location; - } - - const shaderProgram = compileProgram(gl, program.vertexSrc, program.fragmentSrc, attribMap); - const uniformData = {}; - - for (const i in program.uniformData) - { - const data = program.uniformData[i]; - - uniformData[i] = { - location: gl.getUniformLocation(shaderProgram, i), - value: defaultValue(data.type, data.size), - }; - } - - const glProgram = new GLProgram(shaderProgram, uniformData); - - program.glPrograms[this.renderer.CONTEXT_UID] = glProgram; - - return glProgram; - } - - /** - * Destroys this System and removes all its textures - */ - destroy() - { - // TODO implement destroy method for ShaderSystem - this.destroyed = true; - } -} diff --git a/packages/core/src/renderers/systems/shader/shader/compileProgram.js b/packages/core/src/renderers/systems/shader/shader/compileProgram.js deleted file mode 100644 index 8c6ccb9..0000000 --- a/packages/core/src/renderers/systems/shader/shader/compileProgram.js +++ /dev/null @@ -1,78 +0,0 @@ -/** - * @method compileProgram - * @memberof PIXI.glCore.shader - * @param gl {WebGLRenderingContext} The current WebGL context {WebGLProgram} - * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. - * @param fragmentSrc {string|string[]} The fragment shader source as an array of strings. - * @param attributeLocations {Object} An attribute location map that lets you manually set the attribute locations - * @return {WebGLProgram} the shader program - */ -export default function compileProgram(gl, vertexSrc, fragmentSrc, attributeLocations) -{ - const glVertShader = compileShader(gl, gl.VERTEX_SHADER, vertexSrc); - const glFragShader = compileShader(gl, gl.FRAGMENT_SHADER, fragmentSrc); - - let program = gl.createProgram(); - - gl.attachShader(program, glVertShader); - gl.attachShader(program, glFragShader); - - // optionally, set the attributes manually for the program rather than letting WebGL decide.. - if (attributeLocations) - { - for (const i in attributeLocations) - { - gl.bindAttribLocation(program, attributeLocations[i], i); - } - } - - gl.linkProgram(program); - - // if linking fails, then log and cleanup - if (!gl.getProgramParameter(program, gl.LINK_STATUS)) - { - console.error('Pixi.js Error: Could not initialize shader.'); - console.error('gl.VALIDATE_STATUS', gl.getProgramParameter(program, gl.VALIDATE_STATUS)); - console.error('gl.getError()', gl.getError()); - - // if there is a program info log, log it - if (gl.getProgramInfoLog(program) !== '') - { - console.warn('Pixi.js Warning: gl.getProgramInfoLog()', gl.getProgramInfoLog(program)); - } - - gl.deleteProgram(program); - program = null; - } - - // clean up some shaders - gl.deleteShader(glVertShader); - gl.deleteShader(glFragShader); - - return program; -} - -/** - * @private - * @param gl {WebGLRenderingContext} The current WebGL context {WebGLProgram} - * @param type {Number} the type, can be either VERTEX_SHADER or FRAGMENT_SHADER - * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. - * @return {WebGLShader} the shader - */ -function compileShader(gl, type, src) -{ - const shader = gl.createShader(type); - - gl.shaderSource(shader, src); - gl.compileShader(shader); - - if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) - { - console.warn(src); - console.error(gl.getShaderInfoLog(shader)); - - return null; - } - - return shader; -} diff --git a/packages/core/src/renderers/systems/shader/shader/defaultValue.js b/packages/core/src/renderers/systems/shader/shader/defaultValue.js deleted file mode 100644 index 97290bd..0000000 --- a/packages/core/src/renderers/systems/shader/shader/defaultValue.js +++ /dev/null @@ -1,79 +0,0 @@ -/** - * @method defaultValue - * @memberof PIXI.glCore.shader - * @param type {String} Type of value - * @param size {Number} - */ -export default function defaultValue(type, size) -{ - switch (type) - { - case 'float': - return 0; - - case 'vec2': - return new Float32Array(2 * size); - - case 'vec3': - return new Float32Array(3 * size); - - case 'vec4': - return new Float32Array(4 * size); - - case 'int': - case 'sampler2D': - case 'sampler2DArray': - return 0; - - case 'ivec2': - return new Int32Array(2 * size); - - case 'ivec3': - return new Int32Array(3 * size); - - case 'ivec4': - return new Int32Array(4 * size); - - case 'bool': - return false; - - case 'bvec2': - - return booleanArray(2 * size); - - case 'bvec3': - return booleanArray(3 * size); - - case 'bvec4': - return booleanArray(4 * size); - - case 'mat2': - return new Float32Array([1, 0, - 0, 1]); - - case 'mat3': - return new Float32Array([1, 0, 0, - 0, 1, 0, - 0, 0, 1]); - - case 'mat4': - return new Float32Array([1, 0, 0, 0, - 0, 1, 0, 0, - 0, 0, 1, 0, - 0, 0, 0, 1]); - } - - return null; -} - -function booleanArray(size) -{ - const array = new Array(size); - - for (let i = 0; i < array.length; i++) - { - array[i] = false; - } - - return array; -} diff --git a/packages/core/src/renderers/systems/shader/shader/index.js b/packages/core/src/renderers/systems/shader/shader/index.js deleted file mode 100644 index ba7a317..0000000 --- a/packages/core/src/renderers/systems/shader/shader/index.js +++ /dev/null @@ -1,8 +0,0 @@ -/** - * @namespace PIXI.glCore.shader - */ -export { default as compileProgram } from './compileProgram'; -export { default as defaultValue } from './defaultValue'; -export { default as setPrecision } from './setPrecision'; -export { default as mapSize } from './mapSize'; -export { default as mapType } from './mapType'; diff --git a/packages/core/src/renderers/systems/shader/shader/mapSize.js b/packages/core/src/renderers/systems/shader/shader/mapSize.js deleted file mode 100644 index b681554..0000000 --- a/packages/core/src/renderers/systems/shader/shader/mapSize.js +++ /dev/null @@ -1,33 +0,0 @@ -const GLSL_TO_SIZE = { - float: 1, - vec2: 2, - vec3: 3, - vec4: 4, - - int: 1, - ivec2: 2, - ivec3: 3, - ivec4: 4, - - bool: 1, - bvec2: 2, - bvec3: 3, - bvec4: 4, - - mat2: 4, - mat3: 9, - mat4: 16, - - sampler2D: 1, -}; - -/** - * @method mapSize - * @memberof PIXI.glCore.shader - * @param type {String} - * @return {Number} - */ -export default function mapSize(type) -{ - return GLSL_TO_SIZE[type]; -} diff --git a/packages/core/src/renderers/systems/shader/shader/mapType.js b/packages/core/src/renderers/systems/shader/shader/mapType.js deleted file mode 100644 index d288b7c..0000000 --- a/packages/core/src/renderers/systems/shader/shader/mapType.js +++ /dev/null @@ -1,45 +0,0 @@ -let GL_TABLE = null; - -const GL_TO_GLSL_TYPES = { - FLOAT: 'float', - FLOAT_VEC2: 'vec2', - FLOAT_VEC3: 'vec3', - FLOAT_VEC4: 'vec4', - - INT: 'int', - INT_VEC2: 'ivec2', - INT_VEC3: 'ivec3', - INT_VEC4: 'ivec4', - - BOOL: 'bool', - BOOL_VEC2: 'bvec2', - BOOL_VEC3: 'bvec3', - BOOL_VEC4: 'bvec4', - - FLOAT_MAT2: 'mat2', - FLOAT_MAT3: 'mat3', - FLOAT_MAT4: 'mat4', - - SAMPLER_2D: 'sampler2D', - SAMPLER_CUBE: 'samplerCube', - SAMPLER_2D_ARRAY: 'sampler2DArray', -}; - -export default function mapType(gl, type) -{ - if (!GL_TABLE) - { - const typeNames = Object.keys(GL_TO_GLSL_TYPES); - - GL_TABLE = {}; - - for (let i = 0; i < typeNames.length; ++i) - { - const tn = typeNames[i]; - - GL_TABLE[gl[tn]] = GL_TO_GLSL_TYPES[tn]; - } - } - - return GL_TABLE[type]; -} diff --git a/packages/core/src/renderers/systems/shader/shader/setPrecision.js b/packages/core/src/renderers/systems/shader/shader/setPrecision.js deleted file mode 100644 index e2c1f34..0000000 --- a/packages/core/src/renderers/systems/shader/shader/setPrecision.js +++ /dev/null @@ -1,18 +0,0 @@ -/** - * Sets the float precision on the shader. If the precision is already present this function will do nothing - * @param {string} src the shader source - * @param {string} precision The float precision of the shader. Options are 'lowp', 'mediump' or 'highp'. - * - * @return {string} modified shader source - */ -export default function setPrecision(src, precision) -{ - src = src.trim(); - - if (src.substring(0, 9) !== 'precision' && src.substring(0, 1) !== '#') - { - return `precision ${precision} float;\n${src}`; - } - - return src; -} diff --git a/packages/core/src/renderers/systems/textures/GLTexture.js b/packages/core/src/renderers/systems/textures/GLTexture.js deleted file mode 100644 index c3fb8dc..0000000 --- a/packages/core/src/renderers/systems/textures/GLTexture.js +++ /dev/null @@ -1,33 +0,0 @@ -export default class GLTexture -{ - constructor(texture) - { - /** - * The WebGL texture - * - * @member {WebGLTexture} - */ - this.texture = texture; - - this.width = -1; - this.height = -1; - - /** - * Texture contents dirty flag - * @member {number} - */ - this.dirtyId = -1; - - /** - * Texture style dirty flag - * @type {number} - */ - this.dirtyStyleId = -1; - - /** - * Whether mip levels has to be generated - * @type {boolean} - */ - this.mipmap = false; - } -} diff --git a/packages/core/src/renderers/systems/textures/TextureGCSystem.js b/packages/core/src/renderers/systems/textures/TextureGCSystem.js deleted file mode 100644 index b089938..0000000 --- a/packages/core/src/renderers/systems/textures/TextureGCSystem.js +++ /dev/null @@ -1,110 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; -import { GC_MODES } from '@pixi/constants'; -import { settings } from '@pixi/settings'; - -/** - * TextureGarbageCollector. This class manages the GPU and ensures that it does not get clogged - * up with textures that are no longer being used. - * - * @class - * @memberof PIXI - */ -export default class TextureGCSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this.count = 0; - this.checkCount = 0; - this.maxIdle = settings.GC_MAX_IDLE; - this.checkCountMax = settings.GC_MAX_CHECK_COUNT; - this.mode = settings.GC_MODE; - } - - /** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ - postrender() - { - this.count++; - - if (this.mode === GC_MODES.MANUAL) - { - return; - } - - this.checkCount++; - - if (this.checkCount > this.checkCountMax) - { - this.checkCount = 0; - - this.run(); - } - } - - /** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ - run() - { - const tm = this.renderer.texture; - const managedTextures = tm.managedTextures; - let wasRemoved = false; - - for (let i = 0; i < managedTextures.length; i++) - { - const texture = managedTextures[i]; - - // only supports non generated textures at the moment! - if (!texture.frameBuffer && this.count - texture.touched > this.maxIdle) - { - tm.destroyTexture(texture, true); - managedTextures[i] = null; - wasRemoved = true; - } - } - - if (wasRemoved) - { - let j = 0; - - for (let i = 0; i < managedTextures.length; i++) - { - if (managedTextures[i] !== null) - { - managedTextures[j++] = managedTextures[i]; - } - } - - managedTextures.length = j; - } - } - - /** - * Removes all the textures within the specified displayObject and its children from the GPU - * - * @param {PIXI.DisplayObject} displayObject - the displayObject to remove the textures from. - */ - unload(displayObject) - { - const tm = this.renderer.textureSystem; - - // only destroy non generated textures - if (displayObject._texture && displayObject._texture._glRenderTargets) - { - tm.destroyTexture(displayObject._texture); - } - - for (let i = displayObject.children.length - 1; i >= 0; i--) - { - this.unload(displayObject.children[i]); - } - } -} diff --git a/packages/core/src/renderers/systems/textures/TextureSystem.js b/packages/core/src/renderers/systems/textures/TextureSystem.js deleted file mode 100644 index ccc5de9..0000000 --- a/packages/core/src/renderers/systems/textures/TextureSystem.js +++ /dev/null @@ -1,281 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; -import GLTexture from './GLTexture'; -import { removeItems } from '@pixi/utils'; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class TextureSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - // TODO set to max textures... - this.boundTextures = [ - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - ]; - - this.currentLocation = -1; - - this.managedTextures = []; - } - - /** - * Sets up the renderer context and necessary buffers. - * - * @private - */ - contextChange() - { - const gl = this.gl = this.renderer.gl; - - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - - // TODO move this.. to a nice make empty textures class.. - this.emptyTextures = {}; - - const emptyTexture2D = new GLTexture(gl.createTexture()); - - gl.bindTexture(gl.TEXTURE_2D, emptyTexture2D.texture); - gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array(4)); - - this.emptyTextures[gl.TEXTURE_2D] = emptyTexture2D; - this.emptyTextures[gl.TEXTURE_CUBE_MAP] = new GLTexture(gl.createTexture()); - - gl.bindTexture(gl.TEXTURE_CUBE_MAP, this.emptyTextures[gl.TEXTURE_CUBE_MAP].texture); - - let i; - - for (i = 0; i < 6; i++) - { - gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, null); - } - - gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_MAG_FILTER, gl.LINEAR); - gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_MIN_FILTER, gl.LINEAR); - - for (i = 0; i < this.boundTextures.length; i++) - { - this.bind(null, i); - } - } - - bind(texture, location) - { - const gl = this.gl; - - location = location || 0; - - if (this.currentLocation !== location) - { - this.currentLocation = location; - gl.activeTexture(gl.TEXTURE0 + location); - } - - if (texture) - { - texture = texture.baseTexture || texture; - - if (texture.valid) - { - texture.touched = this.renderer.textureGC.count; - - const glTexture = texture._glTextures[this.CONTEXT_UID] || this.initTexture(texture); - - gl.bindTexture(texture.target, glTexture.texture); - - if (glTexture.dirtyId !== texture.dirtyId) - { - this.updateTexture(texture); - } - - this.boundTextures[location] = texture; - } - } - else - { - gl.bindTexture(gl.TEXTURE_2D, this.emptyTextures[gl.TEXTURE_2D].texture); - this.boundTextures[location] = null; - } - } - - unbind(texture) - { - const gl = this.gl; - - for (let i = 0; i < this.boundTextures.length; i++) - { - if (this.boundTextures[i] === texture) - { - if (this.currentLocation !== i) - { - gl.activeTexture(gl.TEXTURE0 + i); - this.currentLocation = i; - } - - gl.bindTexture(gl.TEXTURE_2D, this.emptyTextures[texture.target].texture); - this.boundTextures[i] = null; - } - } - } - - initTexture(texture) - { - const glTexture = new GLTexture(this.gl.createTexture()); - - // guarentee an update.. - glTexture.dirtyId = -1; - - texture._glTextures[this.CONTEXT_UID] = glTexture; - - this.managedTextures.push(texture); - texture.on('dispose', this.destroyTexture, this); - - return glTexture; - } - - updateTexture(texture) - { - const glTexture = texture._glTextures[this.CONTEXT_UID]; - const renderer = this.renderer; - - if (texture.resource && texture.resource.upload(renderer, texture, glTexture)) - { - // texture is uploaded, dont do anything! - } - else - { - // default, renderTexture-like logic - const width = texture.realWidth; - const height = texture.realHeight; - const gl = renderer.gl; - - if (glTexture.width !== width - || glTexture.height !== height - || glTexture.dirtyId < 0) - { - glTexture.width = width; - glTexture.height = height; - - gl.texImage2D(texture.target, 0, - texture.format, - width, - height, - 0, - texture.format, - texture.type, - null); - } - } - - // lets only update what changes.. - if (texture.dirtyStyleId !== glTexture.dirtyStyleId) - { - this.updateTextureStyle(texture); - } - glTexture.dirtyId = texture.dirtyId; - } - - /** - * Deletes the texture from WebGL - * - * @param {PIXI.BaseTexture|PIXI.Texture} texture - the texture to destroy - * @param {boolean} [skipRemove=false] - Whether to skip removing the texture from the TextureManager. - */ - destroyTexture(texture, skipRemove) - { - const gl = this.gl; - - texture = texture.baseTexture || texture; - - if (texture._glTextures[this.renderer.CONTEXT_UID]) - { - this.unbind(texture); - - gl.deleteTexture(texture._glTextures[this.renderer.CONTEXT_UID].texture); - texture.off('dispose', this.destroyTexture, this); - - delete texture._glTextures[this.renderer.CONTEXT_UID]; - - if (!skipRemove) - { - const i = this.managedTextures.indexOf(texture); - - if (i !== -1) - { - removeItems(this.managedTextures, i, 1); - } - } - } - } - - updateTextureStyle(texture) - { - const glTexture = texture._glTextures[this.CONTEXT_UID]; - - glTexture.mipmap = texture.mipmap && texture.isPowerOfTwo; - if (!glTexture) - { - return; - } - - if (texture.resource && texture.resource.style(this.renderer, texture, glTexture)) - { - // style is set, dont do anything! - } - else - { - this.setStyle(texture, glTexture); - } - - glTexture.dirtyStyleId = texture.dirtyStyleId; - } - - setStyle(texture, glTexture) - { - const gl = this.gl; - - if (glTexture.mipmap) - { - gl.generateMipmap(texture.target); - } - - gl.texParameteri(texture.target, gl.TEXTURE_WRAP_S, texture.wrapMode); - gl.texParameteri(texture.target, gl.TEXTURE_WRAP_T, texture.wrapMode); - - if (glTexture.mipmap) - { - /* eslint-disable max-len */ - gl.texParameteri(texture.target, gl.TEXTURE_MIN_FILTER, texture.scaleMode ? gl.LINEAR_MIPMAP_LINEAR : gl.NEAREST_MIPMAP_NEAREST); - /* eslint-disable max-len */ - } - else - { - gl.texParameteri(texture.target, gl.TEXTURE_MIN_FILTER, texture.scaleMode ? gl.LINEAR : gl.NEAREST); - } - - gl.texParameteri(texture.target, gl.TEXTURE_MAG_FILTER, texture.scaleMode ? gl.LINEAR : gl.NEAREST); - } -} diff --git a/packages/core/src/renderers/utils/ObjectRenderer.js b/packages/core/src/renderers/utils/ObjectRenderer.js deleted file mode 100644 index f67a273..0000000 --- a/packages/core/src/renderers/utils/ObjectRenderer.js +++ /dev/null @@ -1,48 +0,0 @@ -import WebGLSystem from '../systems/WebGLSystem'; - -/** - * Base for a common object renderer that can be used as a system renderer plugin. - * - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class ObjectRenderer extends WebGLSystem -{ - /** - * Starts the renderer and sets the shader - * - */ - start() - { - // set the shader.. - } - - /** - * Stops the renderer - * - */ - stop() - { - this.flush(); - } - - /** - * Stub method for rendering content and emptying the current batch. - * - */ - flush() - { - // flush! - } - - /** - * Renders an object - * - * @param {PIXI.DisplayObject} object - The object to render. - */ - render(object) // eslint-disable-line no-unused-vars - { - // render the object - } -} diff --git a/packages/core/src/renderers/utils/Quad.js b/packages/core/src/renderers/utils/Quad.js deleted file mode 100644 index 5c1517b..0000000 --- a/packages/core/src/renderers/utils/Quad.js +++ /dev/null @@ -1,26 +0,0 @@ -import Geometry from '../../geometry/Geometry'; - -/** - * Helper class to create a quad - * - * @class - * @memberof PIXI - */ -export default class Quad extends Geometry -{ - /** - * just a quad - */ - constructor() - { - super(); - - this.addAttribute('aVertexPosition', [ - 0, 0, - 1, 0, - 1, 1, - 0, 1, - ]) - .addIndex([0, 1, 3, 2]); - } -} diff --git a/packages/core/src/renderers/utils/QuadUv.js b/packages/core/src/renderers/utils/QuadUv.js deleted file mode 100644 index 97bda89..0000000 --- a/packages/core/src/renderers/utils/QuadUv.js +++ /dev/null @@ -1,103 +0,0 @@ -import Geometry from '../../geometry/Geometry'; -import Buffer from '../../geometry/Buffer'; - -/** - * Helper class to create a quad with uvs like in v4 - * - * @class - * @memberof PIXI - */ -export default class QuadUv extends Geometry -{ - constructor() - { - super(); - - /** - * An array of vertices - * - * @member {Float32Array} - */ - this.vertices = new Float32Array([ - -1, -1, - 1, -1, - 1, 1, - -1, 1, - ]); - - /** - * The Uvs of the quad - * - * @member {Float32Array} - */ - this.uvs = new Float32Array([ - 0, 0, - 1, 0, - 1, 1, - 0, 1, - ]); - - this.vertexBuffer = new Buffer(this.vertices); - this.uvBuffer = new Buffer(this.uvs); - - this.addAttribute('aVertexPosition', this.vertexBuffer) - .addAttribute('aTextureCoord', this.uvBuffer) - .addIndex([0, 1, 2, 0, 2, 3]); - } - - /** - * Maps two Rectangle to the quad. - * - * @param {PIXI.Rectangle} targetTextureFrame - the first rectangle - * @param {PIXI.Rectangle} destinationFrame - the second rectangle - * @return {PIXI.Quad} Returns itself. - */ - map(targetTextureFrame, destinationFrame) - { - let x = 0; // destinationFrame.x / targetTextureFrame.width; - let y = 0; // destinationFrame.y / targetTextureFrame.height; - - this.uvs[0] = x; - this.uvs[1] = y; - - this.uvs[2] = x + (destinationFrame.width / targetTextureFrame.width); - this.uvs[3] = y; - - this.uvs[4] = x + (destinationFrame.width / targetTextureFrame.width); - this.uvs[5] = y + (destinationFrame.height / targetTextureFrame.height); - - this.uvs[6] = x; - this.uvs[7] = y + (destinationFrame.height / targetTextureFrame.height); - - x = destinationFrame.x; - y = destinationFrame.y; - - this.vertices[0] = x; - this.vertices[1] = y; - - this.vertices[2] = x + destinationFrame.width; - this.vertices[3] = y; - - this.vertices[4] = x + destinationFrame.width; - this.vertices[5] = y + destinationFrame.height; - - this.vertices[6] = x; - this.vertices[7] = y + destinationFrame.height; - - this.invalidate(); - - return this; - } - - /** - * legacy upload method, just marks buffers dirty - * @returns {PIXI.QuadUv} Returns itself. - */ - invalidate() - { - this.vertexBuffer._updateID++; - this.uvBuffer._updateID++; - - return this; - } -} diff --git a/packages/core/src/renderers/utils/checkMaxIfStatmentsInShader.js b/packages/core/src/renderers/utils/checkMaxIfStatmentsInShader.js deleted file mode 100644 index 8047c96..0000000 --- a/packages/core/src/renderers/utils/checkMaxIfStatmentsInShader.js +++ /dev/null @@ -1,58 +0,0 @@ -const fragTemplate = [ - 'precision mediump float;', - 'void main(void){', - 'float test = 0.1;', - '%forloop%', - 'gl_FragColor = vec4(0.0);', - '}', -].join('\n'); - -export default function checkMaxIfStatmentsInShader(maxIfs, gl) -{ - if (maxIfs === 0) - { - throw new Error('Invalid value of `0` passed to `checkMaxIfStatementsInShader`'); - } - - const shader = gl.createShader(gl.FRAGMENT_SHADER); - - while (true) // eslint-disable-line no-constant-condition - { - const fragmentSrc = fragTemplate.replace(/%forloop%/gi, generateIfTestSrc(maxIfs)); - - gl.shaderSource(shader, fragmentSrc); - gl.compileShader(shader); - - if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) - { - maxIfs = (maxIfs / 2) | 0; - } - else - { - // valid! - break; - } - } - - return maxIfs; -} - -function generateIfTestSrc(maxIfs) -{ - let src = ''; - - for (let i = 0; i < maxIfs; ++i) - { - if (i > 0) - { - src += '\nelse '; - } - - if (i < maxIfs - 1) - { - src += `if(test == ${i}.0){}`; - } - } - - return src; -} diff --git a/packages/core/src/renderers/utils/mapWebGLBlendModesToPixi.js b/packages/core/src/renderers/utils/mapWebGLBlendModesToPixi.js deleted file mode 100644 index 10ffe4d..0000000 --- a/packages/core/src/renderers/utils/mapWebGLBlendModesToPixi.js +++ /dev/null @@ -1,47 +0,0 @@ -import { BLEND_MODES } from '@pixi/constants'; - -/** - * Maps gl blend combinations to WebGL. - * - * @memberof PIXI - * @function mapWebGLBlendModesToPixi - * @private - * @param {WebGLRenderingContext} gl - The rendering context. - * @param {string[]} [array=[]] - The array to output into. - * @return {string[]} Mapped modes. - */ -export default function mapWebGLBlendModesToPixi(gl, array = []) -{ - // TODO - premultiply alpha would be different. - // add a boolean for that! - array[BLEND_MODES.NORMAL] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.ADD] = [gl.ONE, gl.DST_ALPHA]; - array[BLEND_MODES.MULTIPLY] = [gl.DST_COLOR, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.SCREEN] = [gl.ONE, gl.ONE_MINUS_SRC_COLOR]; - array[BLEND_MODES.OVERLAY] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.DARKEN] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.LIGHTEN] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.COLOR_DODGE] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.COLOR_BURN] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.HARD_LIGHT] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.SOFT_LIGHT] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.DIFFERENCE] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.EXCLUSION] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.HUE] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.SATURATION] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.COLOR] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.LUMINOSITY] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.NONE] = [0, 0]; - - // not-premultiplied blend modes - array[BLEND_MODES.NORMAL_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.ADD_NPM] = [gl.SRC_ALPHA, gl.DST_ALPHA, gl.ONE, gl.DST_ALPHA]; - array[BLEND_MODES.SCREEN_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_COLOR, gl.ONE, gl.ONE_MINUS_SRC_COLOR]; - - // not-premultiplied blend modes - array[BLEND_MODES.NORMAL_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.ADD_NPM] = [gl.SRC_ALPHA, gl.DST_ALPHA, gl.ONE, gl.DST_ALPHA]; - array[BLEND_MODES.SCREEN_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_COLOR, gl.ONE, gl.ONE_MINUS_SRC_COLOR]; - - return array; -} diff --git a/packages/core/src/shader/GLProgram.js b/packages/core/src/shader/GLProgram.js new file mode 100644 index 0000000..92980ca --- /dev/null +++ b/packages/core/src/shader/GLProgram.js @@ -0,0 +1,43 @@ +/** + * Helper class to create a webGL Program + * + * @class + * @private + */ +export default class GLProgram +{ + constructor(program, uniformData) + { + /** + * The shader program + * + * @member {WebGLProgram} + */ + this.program = program; + + /** + * holds the uniform data which contains uniform locations + * and current uniform values used for caching and preventing undeed GPU commands + * @type {Object} + */ + this.uniformData = uniformData; + + /** + * uniformGroups holds the various upload functions for the shader. Each uniform group + * and program have a unique upload function generated. + * @type {Object} + */ + this.uniformGroups = {}; + } + + /** + * Destroys this program + * TODO + */ + destroy() + { + this.uniformData = null; + this.uniformGroups = null; + this.program = null; + } +} diff --git a/packages/core/src/shader/Program.js b/packages/core/src/shader/Program.js index 4f9971e..4b4007a 100644 --- a/packages/core/src/shader/Program.js +++ b/packages/core/src/shader/Program.js @@ -1,6 +1,11 @@ -import * as shaderUtils from '../renderers/systems/shader/shader'; +// import * as from '../systems/shader/shader'; +import { setPrecision, + defaultValue, + compileProgram, + mapSize, + mapType, + getTestContext } from './utils'; import { ProgramCache } from '@pixi/utils'; -import getTestContext from './getTestContext'; import defaultFragment from './defaultProgram.frag'; import defaultVertex from './defaultProgram.vert'; import { settings } from '@pixi/settings'; @@ -34,8 +39,8 @@ */ this.fragmentSrc = fragmentSrc || Program.defaultFragmentSrc; - this.vertexSrc = shaderUtils.setPrecision(this.vertexSrc, settings.PRECISION_VERTEX); - this.fragmentSrc = shaderUtils.setPrecision(this.fragmentSrc, settings.PRECISION_FRAGMENT); + this.vertexSrc = setPrecision(this.vertexSrc, settings.PRECISION_VERTEX); + this.fragmentSrc = setPrecision(this.fragmentSrc, settings.PRECISION_FRAGMENT); // currently this does not extract structs only default types this.extractData(this.vertexSrc, this.fragmentSrc); @@ -62,7 +67,7 @@ if (gl) { - const program = shaderUtils.compileProgram(gl, vertexSrc, fragmentSrc); + const program = compileProgram(gl, vertexSrc, fragmentSrc); this.attributeData = this.getAttributeData(program, gl); this.uniformData = this.getUniformData(program, gl); @@ -95,13 +100,13 @@ for (let i = 0; i < totalAttributes; i++) { const attribData = gl.getActiveAttrib(program, i); - const type = shaderUtils.mapType(gl, attribData.type); + const type = mapType(gl, attribData.type); /*eslint-disable */ const data = { type: type, name: attribData.name, - size: shaderUtils.mapSize(type), + size: mapSize(type), location: 0, }; /* eslint-enable */ @@ -145,14 +150,14 @@ const name = uniformData.name.replace(/\[.*?\]/, ''); const isArray = uniformData.name.match(/\[.*?\]/, ''); - const type = shaderUtils.mapType(gl, uniformData.type); + const type = mapType(gl, uniformData.type); /*eslint-disable */ uniforms[name] = { type: type, size: uniformData.size, isArray:isArray, - value: shaderUtils.defaultValue(type, uniformData.size), + value: defaultValue(type, uniformData.size), }; /* eslint-enable */ } diff --git a/packages/core/src/shader/ShaderSystem.js b/packages/core/src/shader/ShaderSystem.js new file mode 100644 index 0000000..40b8af8 --- /dev/null +++ b/packages/core/src/shader/ShaderSystem.js @@ -0,0 +1,171 @@ +import System from '../System'; +import GLProgram from './GLProgram'; +import { generateUniformsSync, + defaultValue, + compileProgram } from './utils'; + +let UID = 0; + +/** + * Helper class to create a webGL Texture + * + * @class + * @memberof PIXI.systems + * @extends PIXI.System + */ +export default class ShaderSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - A reference to the current renderer + */ + constructor(renderer) + { + super(renderer); + + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = null; + + this.shader = null; + this.program = null; + + this.id = UID++; + } + + contextChange(gl) + { + this.gl = gl; + } + + /** + * Changes the current shader to the one given in parameter + * + * @param {PIXI.Shader} shader - the new shader + * @param {boolean} dontSync - false if the shader should automatically sync its uniforms. + * @returns {PIXI.glCore.glProgram} the glProgram that belongs to the shader. + */ + bind(shader, dontSync) + { + shader.uniforms.globals = this.renderer.globalUniforms; + + const program = shader.program; + const glProgram = program.glPrograms[this.renderer.CONTEXT_UID] || this.generateShader(shader); + + this.shader = shader; + + // TODO - some current pixi plugins bypass this.. so it not safe to use yet.. + if (this.program !== program) + { + this.program = program; + this.gl.useProgram(glProgram.program); + } + + if (!dontSync) + { + this.syncUniformGroup(shader.uniformGroup); + } + + return glProgram; + } + + /** + * Uploads the uniforms values to the currently bound shader. + * + * @param {object} uniforms - the uniforms valiues that be applied to the current shader + */ + setUniforms(uniforms) + { + const shader = this.shader.program; + const glProgram = shader.glPrograms[this.renderer.CONTEXT_UID]; + + shader.syncUniforms(glProgram.uniformData, uniforms, this.renderer); + } + + syncUniformGroup(group) + { + const glProgram = this.getglProgram(); + + if (!group.static || group.dirtyId !== glProgram.uniformGroups[group.id]) + { + glProgram.uniformGroups[group.id] = group.dirtyId; + const syncFunc = group.syncUniforms[this.shader.program.id] || this.createSyncGroups(group); + + syncFunc(glProgram.uniformData, group.uniforms, this.renderer); + } + } + + createSyncGroups(group) + { + group.syncUniforms[this.shader.program.id] = generateUniformsSync(group, this.shader.program.uniformData); + + return group.syncUniforms[this.shader.program.id]; + } + + /** + * Returns the underlying GLShade rof the currently bound shader. + * This can be handy for when you to have a little more control over the setting of your uniforms. + * + * @return {PIXI.glCore.Shader} the glProgram for the currently bound Shader for this context + */ + getglProgram() + { + if (this.shader) + { + return this.shader.program.glPrograms[this.renderer.CONTEXT_UID]; + } + + return null; + } + + /** + * Generates a glProgram verion of the Shader provided. + * + * @private + * @param {PIXI.Shader} shader the shader that the glProgram will be based on. + * @return {PIXI.glCore.glProgram} A shiney new glProgram + */ + generateShader(shader) + { + const gl = this.gl; + + const program = shader.program; + + const attribMap = {}; + + for (const i in program.attributeData) + { + attribMap[i] = program.attributeData[i].location; + } + + const shaderProgram = compileProgram(gl, program.vertexSrc, program.fragmentSrc, attribMap); + const uniformData = {}; + + for (const i in program.uniformData) + { + const data = program.uniformData[i]; + + uniformData[i] = { + location: gl.getUniformLocation(shaderProgram, i), + value: defaultValue(data.type, data.size), + }; + } + + const glProgram = new GLProgram(shaderProgram, uniformData); + + program.glPrograms[this.renderer.CONTEXT_UID] = glProgram; + + return glProgram; + } + + /** + * Destroys this System and removes all its textures + */ + destroy() + { + // TODO implement destroy method for ShaderSystem + this.destroyed = true; + } +} diff --git a/packages/core/src/shader/defaultValue.js b/packages/core/src/shader/defaultValue.js deleted file mode 100644 index 847a069..0000000 --- a/packages/core/src/shader/defaultValue.js +++ /dev/null @@ -1,78 +0,0 @@ -function booleanArray(size) -{ - const array = new Array(size); - - for (let i = 0; i < array.length; i++) - { - array[i] = false; - } - - return array; -} - -/** - * @class - * @memberof PIXI.glCore.shader - * @param type {String} Type of value - * @param size {Number} - */ -export default function defaultValue(type, size) -{ - switch (type) - { - case 'float': - return 0; - - case 'vec2': - return new Float32Array(2 * size); - - case 'vec3': - return new Float32Array(3 * size); - - case 'vec4': - return new Float32Array(4 * size); - - case 'int': - case 'sampler2D': - return 0; - - case 'ivec2': - return new Int32Array(2 * size); - - case 'ivec3': - return new Int32Array(3 * size); - - case 'ivec4': - return new Int32Array(4 * size); - - case 'bool': - return false; - - case 'bvec2': - - return booleanArray(2 * size); - - case 'bvec3': - return booleanArray(3 * size); - - case 'bvec4': - return booleanArray(4 * size); - - case 'mat2': - return new Float32Array([1, 0, - 0, 1]); - - case 'mat3': - return new Float32Array([1, 0, 0, - 0, 1, 0, - 0, 0, 1]); - - case 'mat4': - return new Float32Array([1, 0, 0, 0, - 0, 1, 0, 0, - 0, 0, 1, 0, - 0, 0, 0, 1]); - } - - return 0; -} diff --git a/packages/core/src/shader/extractUniformsFromSrc.js b/packages/core/src/shader/extractUniformsFromSrc.js deleted file mode 100644 index 3d02c0a..0000000 --- a/packages/core/src/shader/extractUniformsFromSrc.js +++ /dev/null @@ -1,57 +0,0 @@ -import defaultValue from './defaultValue'; - -export default function extractUniformsFromSrc(vertexSrc, fragmentSrc, mask) -{ - const vertUniforms = extractUniformsFromString(vertexSrc, mask); - const fragUniforms = extractUniformsFromString(fragmentSrc, mask); - - return Object.assign(vertUniforms, fragUniforms); -} - -function extractUniformsFromString(string) -{ - const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); - - const uniforms = {}; - let nameSplit; - - // clean the lines a little - remove extra spaces / teabs etc - // then split along ';' - const lines = string.replace(/\s+/g, ' ') - .split(/\s*;\s*/); - - // loop through.. - for (let i = 0; i < lines.length; i++) - { - const line = lines[i].trim(); - - if (line.indexOf('uniform') > -1) - { - const splitLine = line.split(' '); - const type = splitLine[1]; - - let name = splitLine[2]; - let size = 1; - - if (name.indexOf('[') > -1) - { - // array! - nameSplit = name.split(/\[|]/); - name = nameSplit[0]; - size *= Number(nameSplit[1]); - } - - if (!name.match(maskRegex)) - { - uniforms[name] = { - value: defaultValue(type, size), - dirtyId: 0, - name, - type, - }; - } - } - } - - return uniforms; -} diff --git a/packages/core/src/shader/generateUniformsSync.js b/packages/core/src/shader/generateUniformsSync.js deleted file mode 100644 index 98a357f..0000000 --- a/packages/core/src/shader/generateUniformsSync.js +++ /dev/null @@ -1,231 +0,0 @@ -// cv = CachedValue -// v = value -// ud = uniformData -// uv = uniformValue -// l = loaction -const GLSL_TO_SINGLE_SETTERS_CACHED = { - - float: ` - if(cv !== v) - { - cv.v = v; - gl.uniform1f(location, v) - }`, - - vec2: ` - if(cv[0] !== v[0] || cv[1] !== v[1]) - { - cv[0] = v[0]; - cv[1] = v[1]; - gl.uniform2f(location, v[0], v[1]) - }`, - - vec3: ` - if(cv[0] !== v[0] || cv[1] !== v[1] || cv[2] !== v[2]) - { - cv[0] = v[0]; - cv[1] = v[1]; - cv[2] = v[2]; - - gl.uniform3f(location, v[0], v[1], v[2]) - }`, - - vec4: 'gl.uniform4f(location, v[0], v[1], v[2], v[3])', - - int: 'gl.uniform1i(location, v)', - ivec2: 'gl.uniform2i(location, v[0], v[1])', - ivec3: 'gl.uniform3i(location, v[0], v[1], v[2])', - ivec4: 'gl.uniform4i(location, v[0], v[1], v[2], v[3])', - - bool: 'gl.uniform1i(location, v)', - bvec2: 'gl.uniform2i(location, v[0], v[1])', - bvec3: 'gl.uniform3i(location, v[0], v[1], v[2])', - bvec4: 'gl.uniform4i(location, v[0], v[1], v[2], v[3])', - - mat2: 'gl.uniformMatrix2fv(location, false, v)', - mat3: 'gl.uniformMatrix3fv(location, false, v)', - mat4: 'gl.uniformMatrix4fv(location, false, v)', - - sampler2D: 'gl.uniform1i(location, v)', - samplerCube: 'gl.uniform1i(location, v)', - sampler2DArray: 'gl.uniform1i(location, v)', -}; - -const GLSL_TO_ARRAY_SETTERS = { - - float: `gl.uniform1fv(location, v)`, - - vec2: `gl.uniform2fv(location, v)`, - vec3: `gl.uniform3fv(location, v)`, - vec4: 'gl.uniform4fv(location, v)', - - mat4: 'gl.uniformMatrix4fv(location, false, v)', - mat3: 'gl.uniformMatrix3fv(location, false, v)', - mat2: 'gl.uniformMatrix2fv(location, false, v)', - - int: 'gl.uniform1iv(location, v)', - ivec2: 'gl.uniform2iv(location, v)', - ivec3: 'gl.uniform3iv(location, v)', - ivec4: 'gl.uniform4iv(location, v)', - - bool: 'gl.uniform1iv(location, v)', - bvec2: 'gl.uniform2iv(location, v)', - bvec3: 'gl.uniform3iv(location, v)', - bvec4: 'gl.uniform4iv(location, v)', - - sampler2D: 'gl.uniform1iv(location, v)', - samplerCube: 'gl.uniform1iv(location, v)', - sampler2DArray: 'gl.uniform1iv(location, v)', -}; - -export default function generateUniformsSync(group, uniformData) -{ - let textureCount = 0; - let func = `var v = null; - var cv = null - var gl = renderer.gl`; - - for (const i in group.uniforms) - { - const data = uniformData[i]; - - if (!data) - { - if (group.uniforms[i].group) - { - func += ` - renderer.shader.syncUniformGroup(uv.${i}); - `; - } - - continue; - } - - // TODO && uniformData[i].value !== 0 <-- do we still need this? - if (data.type === 'float' && data.size === 1) - { - func += ` - if(uv.${i} !== ud.${i}.value) - { - ud.${i}.value = uv.${i} - gl.uniform1f(ud.${i}.location, uv.${i}) - }\n`; - } - /* eslint-disable max-len */ - else if ((data.type === 'sampler2D' || data.type === 'samplerCube' || data.type === 'sampler2DArray') && data.size === 1 && !data.isArray) - /* eslint-disable max-len */ - { - func += ` - renderer.texture.bind(uv.${i}, ${textureCount}); - - if(ud.${i}.value !== ${textureCount}) - { - ud.${i}.value = ${textureCount}; - gl.uniform1i(ud.${i}.location, ${textureCount});\n; // eslint-disable-line max-len - }\n`; - - textureCount++; - } - else if (data.type === 'mat3' && data.size === 1) - { - if (group.uniforms[i].a !== undefined) - { - // TODO and some smart caching dirty ids here! - func += ` - gl.uniformMatrix3fv(ud.${i}.location, false, uv.${i}.toArray(true)); - \n`; - } - else - { - func += ` - gl.uniformMatrix3fv(ud.${i}.location, false, uv.${i}); - \n`; - } - } - else if (data.type === 'vec2' && data.size === 1) - { - // TODO - do we need both here? - // maybe we can get away with only using points? - if (group.uniforms[i].x !== undefined) - { - func += ` - cv = ud.${i}.value; - v = uv.${i}; - - if(cv[0] !== v.x || cv[1] !== v.y) - { - cv[0] = v.x; - cv[1] = v.y; - gl.uniform2f(ud.${i}.location, v.x, v.y); - }\n`; - } - else - { - func += ` - cv = ud.${i}.value; - v = uv.${i}; - - if(cv[0] !== v[0] || cv[1] !== v[1]) - { - cv[0] = v[0]; - cv[1] = v[1]; - gl.uniform2f(ud.${i}.location, v[0], v[1]); - } - \n`; - } - } - else if (data.type === 'vec4' && data.size === 1) - { - // TODO - do we need both here? - // maybe we can get away with only using points? - if (group.uniforms[i].width !== undefined) - { - func += ` - cv = ud.${i}.value; - v = uv.${i}; - - if(cv[0] !== v.x || cv[1] !== v.y || cv[2] !== v.width || cv[3] !== v.height) - { - cv[0] = v.x; - cv[1] = v.y; - cv[2] = v.width; - cv[3] = v.height; - gl.uniform4f(ud.${i}.location, v.x, v.y, v.width, v.height) - }\n`; - } - else - { - func += ` - cv = ud.${i}.value; - v = uv.${i}; - - if(cv[0] !== v[0] || cv[1] !== v[1] || cv[2] !== v[2] || cv[3] !== v[3]) - { - cv[0] = v[0]; - cv[1] = v[1]; - cv[2] = v[2]; - cv[3] = v[3]; - - gl.uniform4f(ud.${i}.location, v[0], v[1], v[2], v[3]) - } - \n`; - } - } - else - { - const templateType = (data.size === 1) ? GLSL_TO_SINGLE_SETTERS_CACHED : GLSL_TO_ARRAY_SETTERS; - - const template = templateType[data.type].replace('location', `ud.${i}.location`); - - func += ` - cv = ud.${i}.value; - v = uv.${i}; - ${template};\n`; - } - } - - // console.log(' --------------- ') - // console.log(func); - - return new Function('ud', 'uv', 'renderer', func); // eslint-disable-line no-new-func -} diff --git a/packages/core/src/shader/getTestContext.js b/packages/core/src/shader/getTestContext.js deleted file mode 100644 index ea174cc..0000000 --- a/packages/core/src/shader/getTestContext.js +++ /dev/null @@ -1,48 +0,0 @@ -import { settings } from '@pixi/settings'; - -let context = null; - -/** - * returns a little webGL context to use for program inspection. - * - * @static - * @private - * @returns {webGL-context} a gl context to test with - */ -export default function getTestContext() -{ - if (!context) - { - const canvas = document.createElement('canvas'); - - let gl; - - if (settings.PREFER_WEBGL_2) - { - gl = canvas.getContext('webgl2', {}); - } - - if (!gl) - { - gl = canvas.getContext('webgl', {}) - || canvas.getContext('experimental-webgl', {}); - - if (!gl) - { - // fail, not able to get a context - throw new Error('This browser does not support webGL. Try using the canvas renderer'); - } - else - { - // for shader testing.. - gl.getExtension('WEBGL_draw_buffers'); - } - } - - context = gl; - - return gl; - } - - return context; -} diff --git a/packages/core/src/shader/utils/checkMaxIfStatmentsInShader.js b/packages/core/src/shader/utils/checkMaxIfStatmentsInShader.js new file mode 100644 index 0000000..8047c96 --- /dev/null +++ b/packages/core/src/shader/utils/checkMaxIfStatmentsInShader.js @@ -0,0 +1,58 @@ +const fragTemplate = [ + 'precision mediump float;', + 'void main(void){', + 'float test = 0.1;', + '%forloop%', + 'gl_FragColor = vec4(0.0);', + '}', +].join('\n'); + +export default function checkMaxIfStatmentsInShader(maxIfs, gl) +{ + if (maxIfs === 0) + { + throw new Error('Invalid value of `0` passed to `checkMaxIfStatementsInShader`'); + } + + const shader = gl.createShader(gl.FRAGMENT_SHADER); + + while (true) // eslint-disable-line no-constant-condition + { + const fragmentSrc = fragTemplate.replace(/%forloop%/gi, generateIfTestSrc(maxIfs)); + + gl.shaderSource(shader, fragmentSrc); + gl.compileShader(shader); + + if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) + { + maxIfs = (maxIfs / 2) | 0; + } + else + { + // valid! + break; + } + } + + return maxIfs; +} + +function generateIfTestSrc(maxIfs) +{ + let src = ''; + + for (let i = 0; i < maxIfs; ++i) + { + if (i > 0) + { + src += '\nelse '; + } + + if (i < maxIfs - 1) + { + src += `if(test == ${i}.0){}`; + } + } + + return src; +} diff --git a/packages/core/src/shader/utils/compileProgram.js b/packages/core/src/shader/utils/compileProgram.js new file mode 100644 index 0000000..8c6ccb9 --- /dev/null +++ b/packages/core/src/shader/utils/compileProgram.js @@ -0,0 +1,78 @@ +/** + * @method compileProgram + * @memberof PIXI.glCore.shader + * @param gl {WebGLRenderingContext} The current WebGL context {WebGLProgram} + * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. + * @param fragmentSrc {string|string[]} The fragment shader source as an array of strings. + * @param attributeLocations {Object} An attribute location map that lets you manually set the attribute locations + * @return {WebGLProgram} the shader program + */ +export default function compileProgram(gl, vertexSrc, fragmentSrc, attributeLocations) +{ + const glVertShader = compileShader(gl, gl.VERTEX_SHADER, vertexSrc); + const glFragShader = compileShader(gl, gl.FRAGMENT_SHADER, fragmentSrc); + + let program = gl.createProgram(); + + gl.attachShader(program, glVertShader); + gl.attachShader(program, glFragShader); + + // optionally, set the attributes manually for the program rather than letting WebGL decide.. + if (attributeLocations) + { + for (const i in attributeLocations) + { + gl.bindAttribLocation(program, attributeLocations[i], i); + } + } + + gl.linkProgram(program); + + // if linking fails, then log and cleanup + if (!gl.getProgramParameter(program, gl.LINK_STATUS)) + { + console.error('Pixi.js Error: Could not initialize shader.'); + console.error('gl.VALIDATE_STATUS', gl.getProgramParameter(program, gl.VALIDATE_STATUS)); + console.error('gl.getError()', gl.getError()); + + // if there is a program info log, log it + if (gl.getProgramInfoLog(program) !== '') + { + console.warn('Pixi.js Warning: gl.getProgramInfoLog()', gl.getProgramInfoLog(program)); + } + + gl.deleteProgram(program); + program = null; + } + + // clean up some shaders + gl.deleteShader(glVertShader); + gl.deleteShader(glFragShader); + + return program; +} + +/** + * @private + * @param gl {WebGLRenderingContext} The current WebGL context {WebGLProgram} + * @param type {Number} the type, can be either VERTEX_SHADER or FRAGMENT_SHADER + * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. + * @return {WebGLShader} the shader + */ +function compileShader(gl, type, src) +{ + const shader = gl.createShader(type); + + gl.shaderSource(shader, src); + gl.compileShader(shader); + + if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) + { + console.warn(src); + console.error(gl.getShaderInfoLog(shader)); + + return null; + } + + return shader; +} diff --git a/packages/core/src/shader/utils/defaultValue.js b/packages/core/src/shader/utils/defaultValue.js new file mode 100644 index 0000000..97290bd --- /dev/null +++ b/packages/core/src/shader/utils/defaultValue.js @@ -0,0 +1,79 @@ +/** + * @method defaultValue + * @memberof PIXI.glCore.shader + * @param type {String} Type of value + * @param size {Number} + */ +export default function defaultValue(type, size) +{ + switch (type) + { + case 'float': + return 0; + + case 'vec2': + return new Float32Array(2 * size); + + case 'vec3': + return new Float32Array(3 * size); + + case 'vec4': + return new Float32Array(4 * size); + + case 'int': + case 'sampler2D': + case 'sampler2DArray': + return 0; + + case 'ivec2': + return new Int32Array(2 * size); + + case 'ivec3': + return new Int32Array(3 * size); + + case 'ivec4': + return new Int32Array(4 * size); + + case 'bool': + return false; + + case 'bvec2': + + return booleanArray(2 * size); + + case 'bvec3': + return booleanArray(3 * size); + + case 'bvec4': + return booleanArray(4 * size); + + case 'mat2': + return new Float32Array([1, 0, + 0, 1]); + + case 'mat3': + return new Float32Array([1, 0, 0, + 0, 1, 0, + 0, 0, 1]); + + case 'mat4': + return new Float32Array([1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1]); + } + + return null; +} + +function booleanArray(size) +{ + const array = new Array(size); + + for (let i = 0; i < array.length; i++) + { + array[i] = false; + } + + return array; +} diff --git a/packages/core/src/shader/utils/extractUniformsFromSrc.js b/packages/core/src/shader/utils/extractUniformsFromSrc.js new file mode 100644 index 0000000..3d02c0a --- /dev/null +++ b/packages/core/src/shader/utils/extractUniformsFromSrc.js @@ -0,0 +1,57 @@ +import defaultValue from './defaultValue'; + +export default function extractUniformsFromSrc(vertexSrc, fragmentSrc, mask) +{ + const vertUniforms = extractUniformsFromString(vertexSrc, mask); + const fragUniforms = extractUniformsFromString(fragmentSrc, mask); + + return Object.assign(vertUniforms, fragUniforms); +} + +function extractUniformsFromString(string) +{ + const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); + + const uniforms = {}; + let nameSplit; + + // clean the lines a little - remove extra spaces / teabs etc + // then split along ';' + const lines = string.replace(/\s+/g, ' ') + .split(/\s*;\s*/); + + // loop through.. + for (let i = 0; i < lines.length; i++) + { + const line = lines[i].trim(); + + if (line.indexOf('uniform') > -1) + { + const splitLine = line.split(' '); + const type = splitLine[1]; + + let name = splitLine[2]; + let size = 1; + + if (name.indexOf('[') > -1) + { + // array! + nameSplit = name.split(/\[|]/); + name = nameSplit[0]; + size *= Number(nameSplit[1]); + } + + if (!name.match(maskRegex)) + { + uniforms[name] = { + value: defaultValue(type, size), + dirtyId: 0, + name, + type, + }; + } + } + } + + return uniforms; +} diff --git a/packages/core/src/shader/utils/generateUniformsSync.js b/packages/core/src/shader/utils/generateUniformsSync.js new file mode 100644 index 0000000..98a357f --- /dev/null +++ b/packages/core/src/shader/utils/generateUniformsSync.js @@ -0,0 +1,231 @@ +// cv = CachedValue +// v = value +// ud = uniformData +// uv = uniformValue +// l = loaction +const GLSL_TO_SINGLE_SETTERS_CACHED = { + + float: ` + if(cv !== v) + { + cv.v = v; + gl.uniform1f(location, v) + }`, + + vec2: ` + if(cv[0] !== v[0] || cv[1] !== v[1]) + { + cv[0] = v[0]; + cv[1] = v[1]; + gl.uniform2f(location, v[0], v[1]) + }`, + + vec3: ` + if(cv[0] !== v[0] || cv[1] !== v[1] || cv[2] !== v[2]) + { + cv[0] = v[0]; + cv[1] = v[1]; + cv[2] = v[2]; + + gl.uniform3f(location, v[0], v[1], v[2]) + }`, + + vec4: 'gl.uniform4f(location, v[0], v[1], v[2], v[3])', + + int: 'gl.uniform1i(location, v)', + ivec2: 'gl.uniform2i(location, v[0], v[1])', + ivec3: 'gl.uniform3i(location, v[0], v[1], v[2])', + ivec4: 'gl.uniform4i(location, v[0], v[1], v[2], v[3])', + + bool: 'gl.uniform1i(location, v)', + bvec2: 'gl.uniform2i(location, v[0], v[1])', + bvec3: 'gl.uniform3i(location, v[0], v[1], v[2])', + bvec4: 'gl.uniform4i(location, v[0], v[1], v[2], v[3])', + + mat2: 'gl.uniformMatrix2fv(location, false, v)', + mat3: 'gl.uniformMatrix3fv(location, false, v)', + mat4: 'gl.uniformMatrix4fv(location, false, v)', + + sampler2D: 'gl.uniform1i(location, v)', + samplerCube: 'gl.uniform1i(location, v)', + sampler2DArray: 'gl.uniform1i(location, v)', +}; + +const GLSL_TO_ARRAY_SETTERS = { + + float: `gl.uniform1fv(location, v)`, + + vec2: `gl.uniform2fv(location, v)`, + vec3: `gl.uniform3fv(location, v)`, + vec4: 'gl.uniform4fv(location, v)', + + mat4: 'gl.uniformMatrix4fv(location, false, v)', + mat3: 'gl.uniformMatrix3fv(location, false, v)', + mat2: 'gl.uniformMatrix2fv(location, false, v)', + + int: 'gl.uniform1iv(location, v)', + ivec2: 'gl.uniform2iv(location, v)', + ivec3: 'gl.uniform3iv(location, v)', + ivec4: 'gl.uniform4iv(location, v)', + + bool: 'gl.uniform1iv(location, v)', + bvec2: 'gl.uniform2iv(location, v)', + bvec3: 'gl.uniform3iv(location, v)', + bvec4: 'gl.uniform4iv(location, v)', + + sampler2D: 'gl.uniform1iv(location, v)', + samplerCube: 'gl.uniform1iv(location, v)', + sampler2DArray: 'gl.uniform1iv(location, v)', +}; + +export default function generateUniformsSync(group, uniformData) +{ + let textureCount = 0; + let func = `var v = null; + var cv = null + var gl = renderer.gl`; + + for (const i in group.uniforms) + { + const data = uniformData[i]; + + if (!data) + { + if (group.uniforms[i].group) + { + func += ` + renderer.shader.syncUniformGroup(uv.${i}); + `; + } + + continue; + } + + // TODO && uniformData[i].value !== 0 <-- do we still need this? + if (data.type === 'float' && data.size === 1) + { + func += ` + if(uv.${i} !== ud.${i}.value) + { + ud.${i}.value = uv.${i} + gl.uniform1f(ud.${i}.location, uv.${i}) + }\n`; + } + /* eslint-disable max-len */ + else if ((data.type === 'sampler2D' || data.type === 'samplerCube' || data.type === 'sampler2DArray') && data.size === 1 && !data.isArray) + /* eslint-disable max-len */ + { + func += ` + renderer.texture.bind(uv.${i}, ${textureCount}); + + if(ud.${i}.value !== ${textureCount}) + { + ud.${i}.value = ${textureCount}; + gl.uniform1i(ud.${i}.location, ${textureCount});\n; // eslint-disable-line max-len + }\n`; + + textureCount++; + } + else if (data.type === 'mat3' && data.size === 1) + { + if (group.uniforms[i].a !== undefined) + { + // TODO and some smart caching dirty ids here! + func += ` + gl.uniformMatrix3fv(ud.${i}.location, false, uv.${i}.toArray(true)); + \n`; + } + else + { + func += ` + gl.uniformMatrix3fv(ud.${i}.location, false, uv.${i}); + \n`; + } + } + else if (data.type === 'vec2' && data.size === 1) + { + // TODO - do we need both here? + // maybe we can get away with only using points? + if (group.uniforms[i].x !== undefined) + { + func += ` + cv = ud.${i}.value; + v = uv.${i}; + + if(cv[0] !== v.x || cv[1] !== v.y) + { + cv[0] = v.x; + cv[1] = v.y; + gl.uniform2f(ud.${i}.location, v.x, v.y); + }\n`; + } + else + { + func += ` + cv = ud.${i}.value; + v = uv.${i}; + + if(cv[0] !== v[0] || cv[1] !== v[1]) + { + cv[0] = v[0]; + cv[1] = v[1]; + gl.uniform2f(ud.${i}.location, v[0], v[1]); + } + \n`; + } + } + else if (data.type === 'vec4' && data.size === 1) + { + // TODO - do we need both here? + // maybe we can get away with only using points? + if (group.uniforms[i].width !== undefined) + { + func += ` + cv = ud.${i}.value; + v = uv.${i}; + + if(cv[0] !== v.x || cv[1] !== v.y || cv[2] !== v.width || cv[3] !== v.height) + { + cv[0] = v.x; + cv[1] = v.y; + cv[2] = v.width; + cv[3] = v.height; + gl.uniform4f(ud.${i}.location, v.x, v.y, v.width, v.height) + }\n`; + } + else + { + func += ` + cv = ud.${i}.value; + v = uv.${i}; + + if(cv[0] !== v[0] || cv[1] !== v[1] || cv[2] !== v[2] || cv[3] !== v[3]) + { + cv[0] = v[0]; + cv[1] = v[1]; + cv[2] = v[2]; + cv[3] = v[3]; + + gl.uniform4f(ud.${i}.location, v[0], v[1], v[2], v[3]) + } + \n`; + } + } + else + { + const templateType = (data.size === 1) ? GLSL_TO_SINGLE_SETTERS_CACHED : GLSL_TO_ARRAY_SETTERS; + + const template = templateType[data.type].replace('location', `ud.${i}.location`); + + func += ` + cv = ud.${i}.value; + v = uv.${i}; + ${template};\n`; + } + } + + // console.log(' --------------- ') + // console.log(func); + + return new Function('ud', 'uv', 'renderer', func); // eslint-disable-line no-new-func +} diff --git a/packages/core/src/shader/utils/getTestContext.js b/packages/core/src/shader/utils/getTestContext.js new file mode 100644 index 0000000..ea174cc --- /dev/null +++ b/packages/core/src/shader/utils/getTestContext.js @@ -0,0 +1,48 @@ +import { settings } from '@pixi/settings'; + +let context = null; + +/** + * returns a little webGL context to use for program inspection. + * + * @static + * @private + * @returns {webGL-context} a gl context to test with + */ +export default function getTestContext() +{ + if (!context) + { + const canvas = document.createElement('canvas'); + + let gl; + + if (settings.PREFER_WEBGL_2) + { + gl = canvas.getContext('webgl2', {}); + } + + if (!gl) + { + gl = canvas.getContext('webgl', {}) + || canvas.getContext('experimental-webgl', {}); + + if (!gl) + { + // fail, not able to get a context + throw new Error('This browser does not support webGL. Try using the canvas renderer'); + } + else + { + // for shader testing.. + gl.getExtension('WEBGL_draw_buffers'); + } + } + + context = gl; + + return gl; + } + + return context; +} diff --git a/packages/core/src/shader/utils/index.js b/packages/core/src/shader/utils/index.js new file mode 100644 index 0000000..4bf8dd4 --- /dev/null +++ b/packages/core/src/shader/utils/index.js @@ -0,0 +1,8 @@ +export { default as compileProgram } from './compileProgram'; +export { default as defaultValue } from './defaultValue'; +export { default as setPrecision } from './setPrecision'; +export { default as mapSize } from './mapSize'; +export { default as mapType } from './mapType'; +export { default as generateUniformsSync } from './generateUniformsSync'; +export { default as getTestContext } from './getTestContext'; +export { default as checkMaxIfStatmentsInShader } from './checkMaxIfStatmentsInShader'; diff --git a/packages/core/src/shader/utils/mapSize.js b/packages/core/src/shader/utils/mapSize.js new file mode 100644 index 0000000..b681554 --- /dev/null +++ b/packages/core/src/shader/utils/mapSize.js @@ -0,0 +1,33 @@ +const GLSL_TO_SIZE = { + float: 1, + vec2: 2, + vec3: 3, + vec4: 4, + + int: 1, + ivec2: 2, + ivec3: 3, + ivec4: 4, + + bool: 1, + bvec2: 2, + bvec3: 3, + bvec4: 4, + + mat2: 4, + mat3: 9, + mat4: 16, + + sampler2D: 1, +}; + +/** + * @method mapSize + * @memberof PIXI.glCore.shader + * @param type {String} + * @return {Number} + */ +export default function mapSize(type) +{ + return GLSL_TO_SIZE[type]; +} diff --git a/packages/core/src/shader/utils/mapType.js b/packages/core/src/shader/utils/mapType.js new file mode 100644 index 0000000..d288b7c --- /dev/null +++ b/packages/core/src/shader/utils/mapType.js @@ -0,0 +1,45 @@ +let GL_TABLE = null; + +const GL_TO_GLSL_TYPES = { + FLOAT: 'float', + FLOAT_VEC2: 'vec2', + FLOAT_VEC3: 'vec3', + FLOAT_VEC4: 'vec4', + + INT: 'int', + INT_VEC2: 'ivec2', + INT_VEC3: 'ivec3', + INT_VEC4: 'ivec4', + + BOOL: 'bool', + BOOL_VEC2: 'bvec2', + BOOL_VEC3: 'bvec3', + BOOL_VEC4: 'bvec4', + + FLOAT_MAT2: 'mat2', + FLOAT_MAT3: 'mat3', + FLOAT_MAT4: 'mat4', + + SAMPLER_2D: 'sampler2D', + SAMPLER_CUBE: 'samplerCube', + SAMPLER_2D_ARRAY: 'sampler2DArray', +}; + +export default function mapType(gl, type) +{ + if (!GL_TABLE) + { + const typeNames = Object.keys(GL_TO_GLSL_TYPES); + + GL_TABLE = {}; + + for (let i = 0; i < typeNames.length; ++i) + { + const tn = typeNames[i]; + + GL_TABLE[gl[tn]] = GL_TO_GLSL_TYPES[tn]; + } + } + + return GL_TABLE[type]; +} diff --git a/packages/core/src/shader/utils/setPrecision.js b/packages/core/src/shader/utils/setPrecision.js new file mode 100644 index 0000000..e2c1f34 --- /dev/null +++ b/packages/core/src/shader/utils/setPrecision.js @@ -0,0 +1,18 @@ +/** + * Sets the float precision on the shader. If the precision is already present this function will do nothing + * @param {string} src the shader source + * @param {string} precision The float precision of the shader. Options are 'lowp', 'mediump' or 'highp'. + * + * @return {string} modified shader source + */ +export default function setPrecision(src, precision) +{ + src = src.trim(); + + if (src.substring(0, 9) !== 'precision' && src.substring(0, 1) !== '#') + { + return `precision ${precision} float;\n${src}`; + } + + return src; +} diff --git a/packages/core/src/state/State.js b/packages/core/src/state/State.js new file mode 100644 index 0000000..fa3f221 --- /dev/null +++ b/packages/core/src/state/State.js @@ -0,0 +1,163 @@ +/* eslint-disable max-len */ + +const BLEND = 0; +const OFFSET = 1; +const CULLING = 2; +const DEPTH_TEST = 3; +const WINDING = 4; + +/** + * This is a webGL state. It is passed The WebGL StateManager. + * Each mesh renderered may require webGL to be in a different state. + * For example you may want different blend mode or to enable polygon offsets + * + * @class + * @memberof PIXI + */ +export default class State +{ + /** + * + */ + constructor() + { + this.data = 0; + + this.blendMode = 0; + this.polygonOffset = 0; + + this.blend = true; + // this.depthTest = true; + } + + /** + * Activates blending of the computed fragment color values + * + * @member {boolean} + */ + get blend() + { + return !!(this.data & (1 << BLEND)); + } + + set blend(value) // eslint-disable-line require-jsdoc + { + if (!!(this.data & (1 << BLEND)) !== value) + { + this.data ^= (1 << BLEND); + } + } + + /** + * Activates adding an offset to depth values of polygon's fragments + * + * @member {boolean} + * @default false + */ + get offsets() + { + return !!(this.data & (1 << OFFSET)); + } + + set offsets(value) // eslint-disable-line require-jsdoc + { + if (!!(this.data & (1 << OFFSET)) !== value) + { + this.data ^= (1 << OFFSET); + } + } + + /** + * Activates culling of polygons. + * + * @member {boolean} + * @default false + */ + get culling() + { + return !!(this.data & (1 << CULLING)); + } + + set culling(value) // eslint-disable-line require-jsdoc + { + if (!!(this.data & (1 << CULLING)) !== value) + { + this.data ^= (1 << CULLING); + } + } + + /** + * Activates depth comparisons and updates to the depth buffer. + * + * @member {boolean} + * @default false + */ + get depthTest() + { + return !!(this.data & (1 << DEPTH_TEST)); + } + + set depthTest(value) // eslint-disable-line require-jsdoc + { + if (!!(this.data & (1 << DEPTH_TEST)) !== value) + { + this.data ^= (1 << DEPTH_TEST); + } + } + + /** + * Specifies whether or not front or back-facing polygons can be culled. + * @member {boolean} + * @default false + */ + get clockwiseFrontFace() + { + return !!(this.data & (1 << WINDING)); + } + + set clockwiseFrontFace(value) // eslint-disable-line require-jsdoc + { + if (!!(this.data & (1 << WINDING)) !== value) + { + this.data ^= (1 << WINDING); + } + } + + /** + * The blend mode to be applied when this state is set. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. + * Setting this mode to anything other than NO_BLEND will automatically switch blending on. + * + * @member {boolean} + * @default PIXI.BLEND_MODES.NORMAL + * @see PIXI.BLEND_MODES + */ + get blendMode() + { + return this._blendMode; + } + + set blendMode(value) // eslint-disable-line require-jsdoc + { + // 17 is NO BLEND + this.blend = (value !== 17); + this._blendMode = value; + } + + /** + * The polygon offset. Setting this property to anything other than 0 will automatically enable poygon offset fill. + * + * @member {number} + * @default 0 + */ + get polygonOffset() + { + return this._polygonOffset; + } + + set polygonOffset(value) // eslint-disable-line require-jsdoc + { + this.offsets = !!value; + this._polygonOffset = value; + } +} + diff --git a/packages/core/src/state/StateSystem.js b/packages/core/src/state/StateSystem.js new file mode 100755 index 0000000..39c35bb --- /dev/null +++ b/packages/core/src/state/StateSystem.js @@ -0,0 +1,288 @@ +import mapWebGLBlendModesToPixi from './utils/mapWebGLBlendModesToPixi'; +import System from '../System'; +import WebGLState from './State'; + +const BLEND = 0; +const OFFSET = 1; +const CULLING = 2; +const DEPTH_TEST = 3; +const WINDING = 4; + +/** + * A WebGL state machines + * + * @class + * @extends PIXI.systems.System + * @memberof PIXI.systems + */ +export default class StateSystem extends System +{ + /** + * @param {WebGLRenderingContext} gl - The current WebGL rendering context + */ + constructor(renderer) + { + super(renderer); + + this.gl = null; + + this.maxAttribs = null; + + // check we have vao.. + this.nativeVaoExtension = null; + + this.attribState = null; + + this.stateId = 0; + this.polygonOffset = 0; + this.blendMode = 17; + + this.map = []; + + // map functions for when we set state.. + this.map[BLEND] = this.setBlend; + this.map[OFFSET] = this.setOffset; + this.map[CULLING] = this.setCullFace; + this.map[DEPTH_TEST] = this.setDepthTest; + this.map[WINDING] = this.setFrontFace; + + this.checks = []; + + this.defaultState = new WebGLState(); + this.defaultState.blend = true; + this.defaultState.depth = true; + } + + contextChange(gl) + { + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); + + // check we have vao.. + this.nativeVaoExtension = ( + gl.getExtension('OES_vertex_array_object') + || gl.getExtension('MOZ_OES_vertex_array_object') + || gl.getExtension('WEBKIT_OES_vertex_array_object') + ); + + this.attribState = { + tempAttribState: new Array(this.maxAttribs), + attribState: new Array(this.maxAttribs), + }; + + this.blendModes = mapWebGLBlendModesToPixi(gl); + + this.setState(this.defaultState); + + this.reset(); + } + + /** + * Sets the current state + * + * @param {*} state - The state to set. + */ + setState(state) + { + state = state || this.defaultState; + + // TODO maybe to an object check? ( this.state === state )? + if (this.stateId !== state.data) + { + let diff = this.stateId ^ state.data; + let i = 0; + + // order from least to most common + while (diff) + { + if (diff & 1) + { + // state change! + this.map[i].call(this, !!(state.data & (1 << i))); + } + + diff = diff >> 1; + i++; + } + + this.stateId = state.data; + } + + // based on the above settings we check for specific modes.. + // for example if blend is active we check and set the blend modes + // or of polygon offset is active we check the poly depth. + for (let i = 0; i < this.checks.length; i++) + { + this.checks[i](this, state); + } + } + + /** + * Enables or disabled blending. + * + * @param {boolean} value - Turn on or off webgl blending. + */ + setBlend(value) + { + this.updateCheck(StateSystem.checkBlendMode, value); + + this.gl[value ? 'enable' : 'disable'](this.gl.BLEND); + } + + /** + * Enables or disable polygon offset fill + * + * @param {boolean} value - Turn on or off webgl polygon offset testing. + */ + setOffset(value) + { + this.gl[value ? 'enable' : 'disable'](this.gl.POLYGON_OFFSET_FILL); + } + + /** + * Sets whether to enable or disable depth test. + * + * @param {boolean} value - Turn on or off webgl depth testing. + */ + setDepthTest(value) + { + this.gl[value ? 'enable' : 'disable'](this.gl.DEPTH_TEST); + } + + /** + * Sets whether to enable or disable cull face. + * + * @param {boolean} value - Turn on or off webgl cull face. + */ + setCullFace(value) + { + this.gl[value ? 'enable' : 'disable'](this.gl.CULL_FACE); + } + + /** + * Sets the gl front face. + * + * @param {boolean} value - true is clockwise and false is counter-clockwise + */ + setFrontFace(value) + { + this.gl.frontFace(this.gl[value ? 'CW' : 'CCW']); + } + + /** + * Sets the blend mode. + * + * @param {number} value - The blend mode to set to. + */ + setBlendMode(value) + { + if (value === this.blendMode) + { + return; + } + + this.blendMode = value; + this.gl.blendFunc(this.blendModes[value][0], this.blendModes[value][1]); + } + + /** + * Sets the polygon offset. + * + * @param {number} value - the polygon offset + * @param {number} scale - the polygon offset scale + */ + setPolygonOffset(value, scale) + { + this.gl.polygonOffset(value, scale); + } + + /** + * Disables all the vaos in use + * + */ + resetAttributes() + { + for (let i = 0; i < this.attribState.tempAttribState.length; i++) + { + this.attribState.tempAttribState[i] = 0; + } + + for (let i = 0; i < this.attribState.attribState.length; i++) + { + this.attribState.attribState[i] = 0; + } + + // im going to assume one is always active for performance reasons. + for (let i = 1; i < this.maxAttribs; i++) + { + this.gl.disableVertexAttribArray(i); + } + } + + // used + /** + * Resets all the logic and disables the vaos + */ + reset() + { + // unbind any VAO if they exist.. + if (this.nativeVaoExtension) + { + this.nativeVaoExtension.bindVertexArrayOES(null); + } + + // reset all attributes.. + this.resetAttributes(); + + this.gl.pixelStorei(this.gl.UNPACK_FLIP_Y_WEBGL, false); + + this.setBlendMode(0); + + // TODO? + // this.setState(this.defaultState); + } + + /** + * checks to see which updates should be checked based on which settings have been activated + * for example if blend is enabled then we shold check the blend modes each time the state is changed + * or if poygon fill is activated then we need to check if the polygone offset changes. + * The idea is that we only check what we have too. + * + * @param {Function} func the checking function to add or remove + * @param {boolean} value should the check function be added or removed. + */ + updateCheck(func, value) + { + const index = this.checks.indexOf(func); + + if (value && index === -1) + { + this.checks.push(func); + } + else if (!value && index !== -1) + { + this.checks.splice(index, 1); + } + } + + /** + * A private little wrapper function that we call to check the blend mode. + * + * @static + * @private + * @param {PIXI.StateSystem} System the System to perform the state check on + * @param {PIXI.State} state the state that the blendMode will pulled from + */ + static checkBlendMode(system, state) + { + system.setBlendMode(state.blendMode); + } + + // TODO - add polygon offset? +} diff --git a/packages/core/src/state/utils/mapWebGLBlendModesToPixi.js b/packages/core/src/state/utils/mapWebGLBlendModesToPixi.js new file mode 100644 index 0000000..10ffe4d --- /dev/null +++ b/packages/core/src/state/utils/mapWebGLBlendModesToPixi.js @@ -0,0 +1,47 @@ +import { BLEND_MODES } from '@pixi/constants'; + +/** + * Maps gl blend combinations to WebGL. + * + * @memberof PIXI + * @function mapWebGLBlendModesToPixi + * @private + * @param {WebGLRenderingContext} gl - The rendering context. + * @param {string[]} [array=[]] - The array to output into. + * @return {string[]} Mapped modes. + */ +export default function mapWebGLBlendModesToPixi(gl, array = []) +{ + // TODO - premultiply alpha would be different. + // add a boolean for that! + array[BLEND_MODES.NORMAL] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; + array[BLEND_MODES.ADD] = [gl.ONE, gl.DST_ALPHA]; + array[BLEND_MODES.MULTIPLY] = [gl.DST_COLOR, gl.ONE_MINUS_SRC_ALPHA]; + array[BLEND_MODES.SCREEN] = [gl.ONE, gl.ONE_MINUS_SRC_COLOR]; + array[BLEND_MODES.OVERLAY] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; + array[BLEND_MODES.DARKEN] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; + array[BLEND_MODES.LIGHTEN] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; + array[BLEND_MODES.COLOR_DODGE] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; + array[BLEND_MODES.COLOR_BURN] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; + array[BLEND_MODES.HARD_LIGHT] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; + array[BLEND_MODES.SOFT_LIGHT] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; + array[BLEND_MODES.DIFFERENCE] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; + array[BLEND_MODES.EXCLUSION] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; + array[BLEND_MODES.HUE] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; + array[BLEND_MODES.SATURATION] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; + array[BLEND_MODES.COLOR] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; + array[BLEND_MODES.LUMINOSITY] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; + array[BLEND_MODES.NONE] = [0, 0]; + + // not-premultiplied blend modes + array[BLEND_MODES.NORMAL_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; + array[BLEND_MODES.ADD_NPM] = [gl.SRC_ALPHA, gl.DST_ALPHA, gl.ONE, gl.DST_ALPHA]; + array[BLEND_MODES.SCREEN_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_COLOR, gl.ONE, gl.ONE_MINUS_SRC_COLOR]; + + // not-premultiplied blend modes + array[BLEND_MODES.NORMAL_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; + array[BLEND_MODES.ADD_NPM] = [gl.SRC_ALPHA, gl.DST_ALPHA, gl.ONE, gl.DST_ALPHA]; + array[BLEND_MODES.SCREEN_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_COLOR, gl.ONE, gl.ONE_MINUS_SRC_COLOR]; + + return array; +} diff --git a/packages/core/src/AbstractRenderer.js b/packages/core/src/AbstractRenderer.js new file mode 100644 index 0000000..64b2eb1 --- /dev/null +++ b/packages/core/src/AbstractRenderer.js @@ -0,0 +1,353 @@ +import { hex2string, hex2rgb } from '@pixi/utils'; +import { Matrix, Rectangle } from '@pixi/math'; +import { RENDERER_TYPE } from '@pixi/constants'; +import { settings } from '@pixi/settings'; +import { Container } from '@pixi/display'; +import RenderTexture from './renderTexture/RenderTexture'; +import EventEmitter from 'eventemitter3'; + +const tempMatrix = new Matrix(); + +/** + * 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 EventEmitter + * @memberof PIXI + */ +export default class AbstractRenderer extends EventEmitter +{ + // eslint-disable-next-line valid-jsdoc + /** + * @param {string} system - The name of the system this renderer is for. + * @param {object} [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.transparent=false] - If the render view is transparent, default false + * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false + * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. The + * resolution of the renderer retina would be 2. + * @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 {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when rendering, + * stopping pixel interpolation. + */ + constructor(system, options, arg2, arg3) + { + super(); + + // Support for constructor(system, screenWidth, screenHeight, options) + if (typeof options === 'number') + { + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + }, arg3); + } + + // 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 = RENDERER_TYPE.UNKNOWN; + + /** + * Measurements of the screen. (0, 0, screenWidth, screenHeight) + * + * Its safe to use as filterArea or hitArea for 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 1 + */ + this.resolution = options.resolution || settings.RESOLUTION; + + /** + * Whether the render view is transparent + * + * @member {boolean} + */ + this.transparent = options.transparent; + + /** + * Whether css dimensions of canvas view should be resized to screen dimensions automatically + * + * @member {boolean} + */ + this.autoResize = options.autoResize || false; + + /** + * Tracks the blend modes useful for this renderer. + * + * @member {object} + */ + this.blendModes = null; + + /** + * 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; + + /** + * If true PixiJS will Math.floor() x/y values when rendering, stopping pixel interpolation. + * Handy for crisp pixel art and speed on legacy devices. + * + * @member {boolean} + */ + this.roundPixels = options.roundPixels; + + /** + * The background color as a number. + * + * @member {number} + * @private + */ + this._backgroundColor = 0x000000; + + /** + * The background color as an [R, G, B] array. + * + * @member {number[]} + * @private + */ + this._backgroundColorRgba = [0, 0, 0, 0]; + + /** + * The background color as a string. + * + * @member {string} + * @private + */ + this._backgroundColorString = '#000000'; + + this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter + + /** + * This temporary display object used as the parent of the currently being rendered item + * + * @member {PIXI.DisplayObject} + * @private + */ + this._tempDisplayObjectParent = new Container(); + + /** + * The last root object that the renderer tried to render. + * + * @member {PIXI.DisplayObject} + * @private + */ + this._lastObjectRendered = this._tempDisplayObjectParent; + + /** + * Collection of plugins. + * @readonly + * @member {object} + */ + this.plugins = {}; + } + + /** + * Initialize the plugins. + * + * @protected + * @param {object} staticMap - The dictionary of staticly saved plugins. + */ + initPlugins(staticMap) + { + 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() + { + return this.view.width; + } + + /** + * Same as view.height, actual number of pixels in the canvas by vertical + * + * @member {number} + * @readonly + * @default 600 + */ + get height() + { + return this.view.height; + } + + /** + * Resizes the screen and canvas to the specified width and height + * Canvas dimensions are multiplied by resolution + * + * @param {number} screenWidth - the new width of the screen + * @param {number} screenHeight - the new height of the screen + */ + resize(screenWidth, screenHeight) + { + this.screen.width = screenWidth; + this.screen.height = screenHeight; + + this.view.width = screenWidth * this.resolution; + this.view.height = screenHeight * this.resolution; + + if (this.autoResize) + { + this.view.style.width = `${screenWidth}px`; + this.view.style.height = `${screenHeight}px`; + } + } + + /** + * 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 {PIXI.DisplayObject} displayObject - The displayObject the object will be generated from + * @param {number} scaleMode - Should be one of the scaleMode consts + * @param {number} resolution - The resolution / device pixel ratio of the texture being generated + * @param {PIXI.Rectangle} [region] - The region of the displayObject, that shall be rendered, + * if no region is specified, defaults to the local bounds of the displayObject. + * @return {PIXI.Texture} a texture of the graphics object + */ + generateTexture(displayObject, scaleMode, resolution, region) + { + region = region || displayObject.getLocalBounds(); + + // 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(region.width | 0, region.height | 0, scaleMode, resolution); + + tempMatrix.tx = -region.x; + tempMatrix.ty = -region.y; + + this.render(displayObject, renderTexture, false, tempMatrix, true); + + return renderTexture; + } + + /** + * Removes everything from the renderer and optionally removes the Canvas DOM element. + * + * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. + */ + destroy(removeView) + { + 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); + } + + this.plugins = null; + + this.type = RENDERER_TYPE.UNKNOWN; + + this.view = null; + + this.screen = null; + + this.resolution = 0; + + this.transparent = false; + + this.autoResize = false; + + this.blendModes = null; + + this.options = null; + + this.preserveDrawingBuffer = false; + this.clearBeforeRender = false; + + this.roundPixels = false; + + this._backgroundColor = 0; + this._backgroundColorRgba = null; + this._backgroundColorString = null; + + this._tempDisplayObjectParent = null; + this._lastObjectRendered = null; + } + + /** + * The background color to fill if not transparent + * + * @member {number} + */ + get backgroundColor() + { + return this._backgroundColor; + } + + set backgroundColor(value) // eslint-disable-line require-jsdoc + { + this._backgroundColor = value; + this._backgroundColorString = hex2string(value); + hex2rgb(value, this._backgroundColorRgba); + } +} diff --git a/packages/core/src/Renderer.js b/packages/core/src/Renderer.js new file mode 100644 index 0000000..2227e78 --- /dev/null +++ b/packages/core/src/Renderer.js @@ -0,0 +1,338 @@ +import AbstractRenderer from './AbstractRenderer'; +import { sayHello } from '@pixi/utils'; +import MaskSystem from './mask/MaskSystem'; +import StencilSystem from './mask/StencilSystem'; +import FilterSystem from './filters/FilterSystem'; +import FramebufferSystem from './framebuffer/FramebufferSystem'; +import RenderTextureSystem from './renderTexture/RenderTextureSystem'; +import TextureSystem from './textures/TextureSystem'; +import ProjectionSystem from './projection/ProjectionSystem'; +import StateSystem from './state/StateSystem'; +import GeometrySystem from './geometry/GeometrySystem'; +import ShaderSystem from './shader/ShaderSystem'; +import ContextSystem from './context/ContextSystem'; +import BatchSystem from './batch/BatchSystem'; +import TextureGCSystem from './textures/TextureGCSystem'; +import { RENDERER_TYPE } from '@pixi/constants'; +import UniformGroup from './shader/UniformGroup'; +import { Matrix } from '@pixi/math'; +import Runner from 'mini-runner'; + +/** + * 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 Render works by automatically managing webGLBatchs. + * 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 :) + * + * @class + * @memberof PIXI + * @extends PIXI.AbstractRenderer + */ +export default class Renderer extends AbstractRenderer +{ + // eslint-disable-next-line valid-jsdoc + /** + * + * @param {object} [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.transparent=false] - If the render view is transparent, default false + * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false + * @param {boolean} [options.antialias=false] - sets antialias. If not available natively then FXAA + * antialiasing is used + * @param {boolean} [options.forceFXAA=false] - forces FXAA antialiasing to be used over native. + * FXAA is faster, but may not always look as great + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. + * The resolution of the renderer retina would be 2. + * @param {boolean} [options.clearBeforeRender=true] - This sets if the renderer will clear + * the canvas or not before the new render pass. If you wish to set this to false, you *must* set + * preserveDrawingBuffer to `true`. + * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, + * enable this if you need to call toDataUrl on the webgl context. + * @param {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when + * rendering, stopping pixel interpolation. + * @param {number} [options.backgroundColor=0x000000] - The background color of the rendered area + * (shown if not transparent). + * @param {boolean} [options.legacy=false] - If true PixiJS will aim to ensure compatibility + * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. + * @param {string} [options.powerPreference] - Parameter passed to webgl context, set to "high-performance" + * for devices with dual graphics card + */ + constructor(options, arg2, arg3) + { + super('WebGL', options, arg2, arg3); + + /** + * The type of this renderer as a standardised const + * + * @member {number} + * @see PIXI.RENDERER_TYPE + */ + this.type = RENDERER_TYPE.WEBGL; + + // this will be set by the contextSystem (this.context) + this.gl = null; + this.CONTEXT_UID = 0; + this.legacy = !!options.legacy; + + // TODO legacy! + + // runners! + this.runners = { + destroy: new Runner('destroy'), + contextChange: new Runner('contextChange', 1), + reset: new Runner('reset'), + update: new Runner('update'), + postrender: new Runner('postrender'), + prerender: new Runner('prerender'), + resize: new Runner('resize', 2), + }; + + 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(GeometrySystem, 'geometry') + .addSystem(FramebufferSystem, 'framebuffer') + .addSystem(StencilSystem, 'stencil') + .addSystem(ProjectionSystem, 'projection') + .addSystem(TextureGCSystem, 'textureGC') + .addSystem(FilterSystem, 'filter') + .addSystem(RenderTextureSystem, 'renderTexture') + .addSystem(BatchSystem, 'batch'); + + this.initPlugins(Renderer.__plugins); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ + if (options.context) + { + this.context.initFromContext(options.context); + } + else + { + this.context.initFromOptions({ + alpha: this.transparent, + antialias: options.antialias, + premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', + stencil: true, + 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); + } + + /** + * Add a new system to the renderer. + * @param {class} ClassRef - Class reference + * @param {string} [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. + * @return {PIXI.Renderer} Return instance of renderer + */ + addSystem(ClassRef, name) + { + if (!name) + { + name = ClassRef.name; + } + + // TODO - read name from class.name.. + + /* + if(name.includes('System')) + { + name = name.replace('System', ''); + name = name.charAt(0).toLowerCase() + name.slice(1); + } + */ + + const system = new ClassRef(this); + + if (this[name]) + { + throw new Error(`Whoops! ${name} is already a manger`); + } + + this[name] = system; + + for (const i in this.runners) + { + this.runners[i].add(system); + } + + return this; + + /** + * 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. + */ + } + + /** + * Renders the object to its webGL view + * + * @param {PIXI.DisplayObject} displayObject - the object to be rendered + * @param {PIXI.RenderTexture} renderTexture - The render texture to render to. + * @param {boolean} [clear] - Should the canvas be cleared before the new render + * @param {PIXI.Transform} [transform] - A transform to apply to the render texture before rendering. + * @param {boolean} [skipUpdateTransform] - Should we skip the update transform pass? + */ + render(displayObject, renderTexture, clear, transform, skipUpdateTransform) + { + // can be handy to know! + this.renderingToScreen = !renderTexture; + + this.runners.prerender.run(); + this.emit('prerender'); + + // 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.parent; + + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = 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.run(); + + this.emit('postrender'); + } + + /** + * Resizes the webGL view to the specified width and height. + * + * @param {number} screenWidth - the new width of the screen + * @param {number} screenHeight - the new height of the screen + */ + resize(screenWidth, screenHeight) + { + AbstractRenderer.prototype.resize.call(this, screenWidth, screenHeight); + + this.runners.resize.run(screenWidth, screenHeight); + } + + /** + * Resets the WebGL state so you can render things however you fancy! + * + * @return {PIXI.Renderer} Returns itself. + */ + reset() + { + this.runners.reset.run(); + + return this; + } + + clear() + { + this.framebuffer.bind(); + this.framebuffer.clear(); + } + + /** + * Removes everything from the renderer (event listeners, spritebatch, etc...) + * + * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. + * See: https://github.com/pixijs/pixi.js/issues/2233 + */ + destroy(removeView) + { + this.runners.destroy.run(); + + // call base destroy + super.destroy(removeView); + + // TODO nullify all the managers.. + this.gl = null; + } + + /** + * 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 + * builds and excluding plugins. + * @name PIXI.Renderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. + */ + + /** + * Adds a plugin to the renderer. + * + * @method + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + static registerPlugin(pluginName, ctor) + { + Renderer.__plugins = Renderer.__plugins || {}; + Renderer.__plugins[pluginName] = ctor; + } +} diff --git a/packages/core/src/System.js b/packages/core/src/System.js new file mode 100644 index 0000000..05a10c8 --- /dev/null +++ b/packages/core/src/System.js @@ -0,0 +1,42 @@ +/** + * System is a base class used for extending systems used by the {@link PIXI.Renderer} + * @see PIXI.Renderer#addSystem + * @class + * @memberof PIXI + */ +export default class System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + /** + * The renderer this manager works for. + * + * @member {PIXI.Renderer} + */ + this.renderer = renderer; + + this.renderer.runners.contextChange.add(this); + } + + /** + * Generic method called when there is a WebGL context change. + * + */ + contextChange() + { + // do some codes init! + } + + /** + * Generic destroy methods to be overridden by the subclass + * + */ + destroy() + { + this.renderer.runners.contextChange.remove(this); + this.renderer = null; + } +} diff --git a/packages/core/src/batch/BatchSystem.js b/packages/core/src/batch/BatchSystem.js new file mode 100644 index 0000000..b04c249 --- /dev/null +++ b/packages/core/src/batch/BatchSystem.js @@ -0,0 +1,68 @@ +import System from '../System'; +import ObjectRenderer from './ObjectRenderer'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class BatchSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + super(renderer); + + /** + * An empty renderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.emptyRenderer = new ObjectRenderer(renderer); + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.currentRenderer = this.emptyRenderer; + } + + /** + * Changes the current renderer to the one given in parameter + * + * @param {PIXI.ObjectRenderer} objectRenderer - The object renderer to use. + */ + setObjectRenderer(objectRenderer) + { + if (this.currentRenderer === objectRenderer) + { + return; + } + + this.currentRenderer.stop(); + this.currentRenderer = objectRenderer; + + this.renderer.state.setState(objectRenderer.state); + + this.currentRenderer.start(); + } + + /** + * This should be called if you wish to do some custom rendering + * It will basically render anything that may be batched up such as sprites + * + */ + flush() + { + this.setObjectRenderer(this.emptyRenderer); + } + + reset() + { + this.setObjectRenderer(this.emptyRenderer); + } +} diff --git a/packages/core/src/batch/ObjectRenderer.js b/packages/core/src/batch/ObjectRenderer.js new file mode 100644 index 0000000..0c851a9 --- /dev/null +++ b/packages/core/src/batch/ObjectRenderer.js @@ -0,0 +1,48 @@ +import System from '../System'; + +/** + * Base for a common object renderer that can be used as a system renderer plugin. + * + * @class + * @extends PIXI.System + * @memberof PIXI + */ +export default class ObjectRenderer extends System +{ + /** + * Starts the renderer and sets the shader + * + */ + start() + { + // set the shader.. + } + + /** + * Stops the renderer + * + */ + stop() + { + this.flush(); + } + + /** + * Stub method for rendering content and emptying the current batch. + * + */ + flush() + { + // flush! + } + + /** + * Renders an object + * + * @param {PIXI.DisplayObject} object - The object to render. + */ + render(object) // eslint-disable-line no-unused-vars + { + // render the object + } +} diff --git a/packages/core/src/context/ContextSystem.js b/packages/core/src/context/ContextSystem.js new file mode 100644 index 0000000..301aab4 --- /dev/null +++ b/packages/core/src/context/ContextSystem.js @@ -0,0 +1,186 @@ +import System from '../System'; +import { settings } from '@pixi/settings'; + +let CONTEXT_UID = 0; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class ContextSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.webGLVersion = 1; + + this.handleContextLost = this.handleContextLost.bind(this); + this.handleContextRestored = this.handleContextRestored.bind(this); + + this.extensions = {}; + + renderer.view.addEventListener('webglcontextlost', this.handleContextLost, false); + renderer.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + } + + get isLost() + { + return (!this.gl || this.gl.isContextLost()); + } + + contextChange(gl) + { + this.gl = gl; + + // restore a context if it was previously lost + if (gl.isContextLost() && gl.getExtension('WEBGL_lose_context')) + { + gl.getExtension('WEBGL_lose_context').restoreContext(); + } + } + + initFromContext(gl) + { + this.gl = gl; + this.validateContext(gl); + this.renderer.gl = gl; + this.renderer.CONTEXT_UID = CONTEXT_UID++; + this.renderer.runners.contextChange.run(gl); + } + + initFromOptions(options) + { + const gl = this.createContext(this.renderer.view, options); + + this.initFromContext(gl); + } + + /** + * Helper class to create a webGL Context + * + * @class + * @memberof PIXI.glCore + * @param canvas {HTMLCanvasElement} the canvas element that we will get the context from + * @param options {Object} An options object that gets passed in to the canvas element containing the context attributes, + * see https://developer.mozilla.org/en/docs/Web/API/HTMLCanvasElement/getContext for the options available + * @return {WebGLRenderingContext} the WebGL context + */ + createContext(canvas, options) + { + let gl; + + if (settings.PREFER_WEBGL_2) + { + gl = canvas.getContext('webgl2', options); + } + + if (gl) + { + this.webGLVersion = 2; + } + else + { + this.webGLVersion = 1; + + gl = canvas.getContext('webgl', options) + || canvas.getContext('experimental-webgl', options); + + if (!gl) + { + // fail, not able to get a context + throw new Error('This browser does not support webGL. Try using the canvas renderer'); + } + } + + this.gl = gl; + + this.getExtensions(); + + return gl; + } + + getExtensions() + { + // time to set up default etensions that pixi uses.. + const gl = this.gl; + const extensions = this.extensions; + + if (this.webGLVersion === 1) + { + extensions.drawBuffers = gl.getExtension('WEBGL_draw_buffers'); + extensions.depthTexture = gl.getExtension('WEBKIT_WEBGL_depth_texture'); + extensions.floatTexture = gl.getExtension('OES_texture_float'); + extensions.loseContext = gl.getExtension('WEBGL_lose_context'); + + extensions.vertexArrayObject = gl.getExtension('OES_vertex_array_object') + || gl.getExtension('MOZ_OES_vertex_array_object') + || gl.getExtension('WEBKIT_OES_vertex_array_object'); + } + + // we don't use any specific WebGL 2 ones yet! + } + + /** + * Handles a lost webgl context + * + * @private + * @param {WebGLContextEvent} event - The context lost event. + */ + handleContextLost(event) + { + event.preventDefault(); + } + + /** + * Handles a restored webgl context + * + * @private + */ + handleContextRestored() + { + this.renderer.runners.contextChange.run(this.gl); + } + + destroy() + { + const view = this.renderer.view; + + // remove listeners + view.removeEventListener('webglcontextlost', this.handleContextLost); + view.removeEventListener('webglcontextrestored', this.handleContextRestored); + + this.gl.useProgram(null); + + if (this.extensions.loseContext) + { + this.extensions.loseContext.loseContext(); + } + } + + postrender() + { + this.gl.flush(); + } + + validateContext(gl) + { + const attributes = gl.getContextAttributes(); + + // this is going to be fairly simple for now.. but at least we have room to grow! + if (!attributes.stencil) + { + /* eslint-disable max-len */ + + /* eslint-disable no-console */ + console.warn('Provided WebGL context does not have a stencil buffer, masks may not render correctly'); + /* eslint-enable no-console */ + + /* eslint-enable max-len */ + } + } +} diff --git a/packages/core/src/filters/Filter.js b/packages/core/src/filters/Filter.js new file mode 100644 index 0000000..9d8cad8 --- /dev/null +++ b/packages/core/src/filters/Filter.js @@ -0,0 +1,140 @@ +import Shader from '../shader/Shader'; +import Program from '../shader/Program'; +import State from '../state/State'; +import { settings } from '@pixi/settings'; +// import extractUniformsFromSrc from './extractUniformsFromSrc'; +import defaultVertex from './defaultFilter.vert'; +import defaultFragment from './defaultFilter.frag'; + +// let math = require('../../math'); +/** + * @class + * @memberof PIXI + * @extends PIXI.Shader + */ +export default class Filter extends Shader +{ + /** + * @param {string} [vertexSrc] - The source of the vertex shader. + * @param {string} [fragmentSrc] - The source of the fragment shader. + * @param {object} [uniforms] - Custom uniforms to use to augment the built-in ones. + */ + constructor(vertexSrc, fragmentSrc, uniforms) + { + const program = Program.from(vertexSrc, fragmentSrc); + + super(program, uniforms); + + /** + * The padding of the filter. Some filters require extra space to breath such as a blur. + * Increasing this will add extra width and height to the bounds of the object that the + * filter is applied to. + * + * @member {number} + */ + this.padding = 0; + + /** + * The resolution of the filter. Setting this to be lower will lower the quality but + * increase the performance of the filter. + * + * @member {number} + */ + this.resolution = settings.RESOLUTION; + + /** + * If enabled is true the filter is applied, if false it will not. + * + * @member {boolean} + */ + this.enabled = true; + + /** + * If enabled, PixiJS will fit the filter area into boundaries for better performance. + * Switch it off if it does not work for specific shader. + * + * @member {boolean} + */ + this.autoFit = true; + + /** + * Legacy filters use position and uvs from attributes + * @member {boolean} + * @readonly + */ + this.legacy = !!this.program.attributeData.aTextureCoord; + + /** + * the webGL state the filter requires to render + * @member {PIXI.State} + */ + this.state = new State(); + } + + /** + * Applies the filter + * + * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + * @param {boolean} clear - Should the output be cleared before rendering to it + * @param {object} [currentState] - It's current state of filter. + * There are some useful properties in the currentState : + * target, filters, sourceFrame, destinationFrame, renderTarget, resolution + */ + apply(filterManager, input, output, clear, currentState, derp) // eslint-disable-line no-unused-vars + { + // do as you please! + + filterManager.applyFilter(this, input, output, clear, currentState, derp); + + // or just do a regular render.. + } + + /** + * Sets the blendmode of the filter + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL + */ + get blendMode() + { + return this.state.blendMode; + } + + set blendMode(value) // eslint-disable-line require-jsdoc + { + this.state.blendMode = value; + } + + /** + * The default vertex shader source + * + * @static + * @constant + */ + static get defaultVertexSrc() + { + return defaultVertex; + } + + /** + * The default fragment shader source + * + * @static + * @constant + */ + static get defaultFragmentSrc() + { + return defaultFragment; + } +} + +/** + * Used for caching shader IDs + * + * @static + * @private + */ +Filter.SOURCE_KEY_MAP = {}; + diff --git a/packages/core/src/filters/FilterSystem.js b/packages/core/src/filters/FilterSystem.js new file mode 100644 index 0000000..3174318 --- /dev/null +++ b/packages/core/src/filters/FilterSystem.js @@ -0,0 +1,431 @@ +import System from '../System'; + +import RenderTexture from '../renderTexture/RenderTexture'; +import Quad from '../utils/Quad'; +import QuadUv from '../utils/QuadUv'; +import { Rectangle } from '@pixi/math'; +import * as filterTransforms from './filterTransforms'; +import bitTwiddle from 'bit-twiddle'; +import UniformGroup from '../shader/UniformGroup'; +import { DRAW_MODES } from '@pixi/constants'; + +// +/** + * @ignore + * @class + */ +class FilterState +{ + /** + * + */ + constructor() + { + this.renderTexture = null; + this.sourceFrame = new Rectangle(); + this.destinationFrame = new Rectangle(); + this.filters = []; + this.target = null; + this.legacy = false; + this.resolution = 1; + } +} + +/** + * @class + * @memberof PIXI.systems + * @extends PIXI.System + */ +export default class FilterSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + /** + * stores a bunch of PO2 textures used for filtering + * @type {Object} + */ + this.texturePool = {}; + + /** + * a pool for storing filter states, save us creating new ones each tick + * @type {Array} + */ + this.statePool = []; + + /** + * A very simple geometry used when drawing a filter effect to the screen + * @type {Quad} + */ + this.quad = new Quad(); + + this.quadUv = new QuadUv(); + + /** + * Temporary rect for maths + * @type {PIXI.Rectangle} + */ + this.tempRect = new Rectangle(); + + this.activeState = {}; + + /** + * this uniform group is attached to filter uniforms when used + * @type {UniformGroup} + */ + this.globalUniforms = new UniformGroup({ + sourceFrame: this.tempRect, + destinationFrame: this.tempRect, + + // legacy variables + filterArea: new Float32Array(4), + filterClamp: new Float32Array(4), + }, true); + } + + /** + * Adds a new filter to the System. + * + * @param {PIXI.DisplayObject} target - The target of the filter to render. + * @param {PIXI.Filter[]} filters - The filters to apply. + */ + push(target, filters) + { + const renderer = this.renderer; + const filterStack = this.renderer.renderTexture.defaultFilterStack; + const state = this.statePool.pop() || new FilterState(); + + let resolution = filters[0].resolution; + let padding = filters[0].padding; + let autoFit = filters[0].autoFit; + let legacy = filters[0].legacy; + + for (let i = 1; i < filters.length; i++) + { + const filter = filters[i]; + + // lets use the lowest resolution.. + resolution = Math.min(resolution, filter.resolution); + // and the largest amount of padding! + padding = Math.max(padding, filter.padding); + // only auto fit if all filters are autofit + autoFit = autoFit || filter.autoFit; + + legacy = legacy || filter.legacy; + } + + filterStack.push(state); + + state.resolution = resolution; + + state.legacy = legacy; + + // round to whole number based on resolution + // TODO move that to the shader too? + state.sourceFrame = target.filterArea || target.getBounds(true); + + state.sourceFrame.pad(padding); + + if (autoFit) + { + state.sourceFrame.fit(this.renderer.renderTexture.destinationFrame); + } + + state.sourceFrame.round(resolution); + + state.renderTexture = this.getPotFilterTexture(state.sourceFrame.width, state.sourceFrame.height, resolution); + state.filters = filters; + + state.destinationFrame.width = state.renderTexture.width; + state.destinationFrame.height = state.renderTexture.height; + + state.renderTexture.filterFrame = state.sourceFrame; + renderer.renderTexture.bind(state.renderTexture, state.sourceFrame);// /, state.destinationFrame); + renderer.renderTexture.clear(); + } + + /** + * Pops off the filter and applies it. + * + */ + pop() + { + const renderer = this.renderer; + const filterStack = renderer.renderTexture.defaultFilterStack; + const state = filterStack.pop(); + const filters = state.filters; + + this.activeState = state; + + const globalUniforms = this.globalUniforms.uniforms; + + globalUniforms.sourceFrame = state.sourceFrame; + globalUniforms.destinationFrame = state.destinationFrame; + globalUniforms.resolution = state.resolution; + + // only update the rect if its legacy.. + if (state.legacy) + { + const filterArea = globalUniforms.filterArea; + const filterClamp = globalUniforms.filterClamp; + + filterArea[0] = state.destinationFrame.width; + filterArea[1] = state.destinationFrame.height; + filterArea[2] = state.sourceFrame.x; + filterArea[3] = state.sourceFrame.y; + + filterClamp[0] = 0.5 / state.resolution / state.destinationFrame.width; + filterClamp[1] = 0.5 / state.resolution / state.destinationFrame.height; + filterClamp[2] = (state.sourceFrame.width - 0.5) / state.resolution / state.destinationFrame.width; + filterClamp[3] = (state.sourceFrame.height - 0.5) / state.resolution / state.destinationFrame.height; + } + + this.globalUniforms.update(); + + const lastState = filterStack[filterStack.length - 1]; + + if (filters.length === 1) + { + filters[0].apply(this, state.renderTexture, lastState.renderTexture, false, state); + + this.returnFilterTexture(state.renderTexture); + } + else + { + let flip = state.renderTexture; + let flop = this.getPotFilterTexture( + flip.width, + flip.height, + state.resolution + ); + + flop.filterFrame = flip.filterFrame; + + let i = 0; + + for (i = 0; i < filters.length - 1; ++i) + { + filters[i].apply(this, flip, flop, true, state); + + const t = flip; + + flip = flop; + flop = t; + } + + filters[i].apply(this, flip, lastState.renderTexture, false, state); + + this.returnFilterTexture(flip); + this.returnFilterTexture(flop); + } + + this.statePool.push(state); + } + + /** + * Draws a filter. + * + * @param {PIXI.Filter} filter - The filter to draw. + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + * @param {boolean} clear - Should the output be cleared before rendering to it + */ + applyFilter(filter, input, output, clear) + { + const renderer = this.renderer; + + renderer.renderTexture.bind(output, output ? output.filterFrame : null); + + if (clear) + { + // gl.disable(gl.SCISSOR_TEST); + renderer.renderTexture.clear(); + // gl.enable(gl.SCISSOR_TEST); + } + + // set the uniforms.. + filter.uniforms.uSampler = input; + filter.uniforms.filterGlobals = this.globalUniforms; + + // TODO make it so that the order of this does not matter.. + // because it does at the moment cos of global uniforms. + // they need to get resynced + + renderer.state.setState(filter.state); + renderer.shader.bind(filter); + + if (filter.legacy) + { + this.quadUv.map(input._frame, input.filterFrame); + + renderer.geometry.bind(this.quadUv); + renderer.geometry.draw(DRAW_MODES.TRIANGLES); + } + else + { + renderer.geometry.bind(this.quad); + renderer.geometry.draw(DRAW_MODES.TRIANGLE_STRIP); + } + } + + /** + * Calculates the mapped matrix. + * + * TODO playing around here.. this is temporary - (will end up in the shader) + * this returns a matrix that will normalise map filter cords in the filter to screen space + * + * @param {PIXI.Matrix} outputMatrix - the matrix to output to. + * @return {PIXI.Matrix} The mapped matrix. + */ + calculateScreenSpaceMatrix(outputMatrix) + { + const currentState = this.activeState; + + return filterTransforms.calculateScreenSpaceMatrix( + outputMatrix, + currentState.sourceFrame, + currentState.destinationFrame + ); + } + + /** + * This will map the filter coord so that a texture can be used based on the transform of a sprite + * + * @param {PIXI.Matrix} outputMatrix - The matrix to output to. + * @param {PIXI.Sprite} sprite - The sprite to map to. + * @return {PIXI.Matrix} The mapped matrix. + */ + calculateSpriteMatrix(outputMatrix, sprite) + { + const currentState = this.activeState; + + return filterTransforms.calculateSpriteMatrix( + outputMatrix, + currentState.sourceFrame, + currentState.destinationFrame, + sprite + ); + } + + /** + * Destroys this Filter System. + * + * @param {boolean} [contextLost=false] context was lost, do not free shaders + * + */ + destroy(contextLost = false) + { + if (!contextLost) + { + this.emptyPool(); + } + else + { + this.texturePool = {}; + } + } + + /** + * Gets a Power-of-Two render texture. + * + * TODO move to a seperate class could be on renderer? + * also - could cause issue with multiple contexts? + * + * @private + * @param {WebGLRenderingContext} gl - The webgl rendering context + * @param {number} minWidth - The minimum width of the render target. + * @param {number} minHeight - The minimum height of the render target. + * @param {number} resolution - The resolution of the render target. + * @return {PIXI.RenderTarget} The new render target. + */ + getPotFilterTexture(minWidth, minHeight, resolution) + { + minWidth = bitTwiddle.nextPow2(minWidth); + minHeight = bitTwiddle.nextPow2(minHeight); + resolution = resolution || 1; + + const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + + if (!this.texturePool[key]) + { + this.texturePool[key] = []; + } + + let renderTexture = this.texturePool[key].pop(); + + if (!renderTexture) + { + // temporary bypass cache.. + // internally - this will cause a texture to be bound.. + renderTexture = RenderTexture.create({ + width: minWidth, + height: minHeight, + resolution, + }); + } + + return renderTexture; + } + + /** + * Gets extra render texture to use inside current filter + * + * @param {number} resolution resolution of the renderTexture + * @returns {PIXI.RenderTexture} + */ + getFilterTexture(resolution) + { + const rt = this.activeState.renderTexture; + + const filterTexture = this.getPotFilterTexture(rt.width, rt.height, resolution || rt.baseTexture.resolution); + + filterTexture.filterFrame = rt.filterFrame; + + return filterTexture; + } + + /** + * Frees a render texture back into the pool. + * + * @param {PIXI.RenderTarget} renderTexture - The renderTarget to free + */ + returnFilterTexture(renderTexture) + { + renderTexture.filterFrame = null; + + const base = renderTexture.baseTexture; + + const minWidth = base.width; + const minHeight = base.height; + + const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + + this.texturePool[key].push(renderTexture); + } + + /** + * Empties the texture pool. + * + */ + emptyPool() + { + for (const i in this.texturePool) + { + const textures = this.texturePool[i]; + + if (textures) + { + for (let j = 0; j < textures.length; j++) + { + textures[j].destroy(true); + } + } + } + + this.texturePool = {}; + } +} diff --git a/packages/core/src/filters/defaultFilter.frag b/packages/core/src/filters/defaultFilter.frag new file mode 100644 index 0000000..a14c1be --- /dev/null +++ b/packages/core/src/filters/defaultFilter.frag @@ -0,0 +1,22 @@ +varying vec2 vTextureCoord; +varying vec2 vFilterCoord; + +uniform sampler2D uSampler; +uniform sampler2D filterSampler; + +void main(void){ + vec4 masky = texture2D(filterSampler, vFilterCoord); + vec4 sample = texture2D(uSampler, vTextureCoord); + vec4 color; + if(mod(vFilterCoord.x, 1.0) > 0.5) + { + color = vec4(1.0, 0.0, 0.0, 1.0); + } + else + { + color = vec4(0.0, 1.0, 0.0, 1.0); + } + // gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0); + gl_FragColor = mix(sample, masky, 0.5); + gl_FragColor *= sample.a; +} diff --git a/packages/core/src/filters/defaultFilter.vert b/packages/core/src/filters/defaultFilter.vert new file mode 100644 index 0000000..e1febca --- /dev/null +++ b/packages/core/src/filters/defaultFilter.vert @@ -0,0 +1,14 @@ +attribute vec2 aVertexPosition; +attribute vec2 aTextureCoord; + +uniform mat3 projectionMatrix; +uniform mat3 filterMatrix; + +varying vec2 vTextureCoord; +varying vec2 vFilterCoord; + +void main(void){ + gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); + vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy; + vTextureCoord = aTextureCoord ; +} diff --git a/packages/core/src/filters/filterTransforms.js b/packages/core/src/filters/filterTransforms.js new file mode 100644 index 0000000..87a3cd6 --- /dev/null +++ b/packages/core/src/filters/filterTransforms.js @@ -0,0 +1,50 @@ +import { Matrix } from '@pixi/math'; + +/** + * Calculates the mapped matrix + * @param filterArea {Rectangle} The filter area + * @param sprite {Sprite} the target sprite + * @param outputMatrix {Matrix} @alvin + * @private + */ +// this returns a matrix that will normalise map filter cords in the filter to screen space +export function calculateScreenSpaceMatrix(outputMatrix, filterArea, textureSize) +{ + // TODO unwrap? + const mappedMatrix = outputMatrix.identity(); + + mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); + + mappedMatrix.scale(textureSize.width, textureSize.height); + + return mappedMatrix; +} + +export function calculateNormalizedScreenSpaceMatrix(outputMatrix, filterArea, textureSize) +{ + const mappedMatrix = outputMatrix.identity(); + + mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); + + const translateScaleX = (textureSize.width / filterArea.width); + const translateScaleY = (textureSize.height / filterArea.height); + + mappedMatrix.scale(translateScaleX, translateScaleY); + + return mappedMatrix; +} + +// this will map the filter coord so that a texture can be used based on the transform of a sprite +export function calculateSpriteMatrix(outputMatrix, filterArea, textureSize, sprite) +{ + const orig = sprite._texture.orig; + const mappedMatrix = outputMatrix.set(textureSize.width, 0, 0, textureSize.height, filterArea.x, filterArea.y); + const worldTransform = sprite.worldTransform.copyTo(Matrix.TEMP_MATRIX); + + worldTransform.invert(); + mappedMatrix.prepend(worldTransform); + mappedMatrix.scale(1.0 / orig.width, 1.0 / orig.height); + mappedMatrix.translate(sprite.anchor.x, sprite.anchor.y); + + return mappedMatrix; +} diff --git a/packages/core/src/filters/spriteMask/SpriteMaskFilter.js b/packages/core/src/filters/spriteMask/SpriteMaskFilter.js new file mode 100644 index 0000000..91697b7 --- /dev/null +++ b/packages/core/src/filters/spriteMask/SpriteMaskFilter.js @@ -0,0 +1,63 @@ +import Filter from '../Filter'; +import { Matrix } from '@pixi/math'; +import vertex from './spriteMaskFilter.vert'; +import fragment from './spriteMaskFilter.frag'; +import { default as TextureMatrix } from '../../textures/TextureMatrix'; + +/** + * The SpriteMaskFilter class + * + * @class + * @extends PIXI.Filter + * @memberof PIXI + */ +export default class SpriteMaskFilter extends Filter +{ + /** + * @param {PIXI.Sprite} sprite - the target sprite + */ + constructor(sprite) + { + const maskMatrix = new Matrix(); + + super(vertex, fragment); + + sprite.renderable = false; + + this.maskSprite = sprite; + this.maskMatrix = maskMatrix; + } + + /** + * Applies the filter + * + * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + */ + apply(filterManager, input, output) + { + const maskSprite = this.maskSprite; + const tex = this.maskSprite.texture; + + if (!tex.valid) + { + return; + } + if (!tex.transform) + { + // margin = 0.0, let it bleed a bit, shader code becomes easier + // assuming that atlas textures were made with 1-pixel padding + tex.transform = new TextureMatrix(tex, 0.0); + } + tex.transform.update(); + + this.uniforms.mask = tex; + this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite) + .prepend(tex.transform.mapCoord); + this.uniforms.alpha = maskSprite.worldAlpha; + this.uniforms.maskClamp = tex.transform.uClampFrame; + + filterManager.applyFilter(this, input, output); + } +} diff --git a/packages/core/src/filters/spriteMask/spriteMaskFilter.frag b/packages/core/src/filters/spriteMask/spriteMaskFilter.frag new file mode 100644 index 0000000..0e4aef8 --- /dev/null +++ b/packages/core/src/filters/spriteMask/spriteMaskFilter.frag @@ -0,0 +1,23 @@ +varying vec2 vMaskCoord; +varying vec2 vTextureCoord; + +uniform sampler2D uSampler; +uniform sampler2D mask; +uniform float alpha; +uniform vec4 maskClamp; + +void main(void) +{ + float clip = step(3.5, + step(maskClamp.x, vMaskCoord.x) + + step(maskClamp.y, vMaskCoord.y) + + step(vMaskCoord.x, maskClamp.z) + + step(vMaskCoord.y, maskClamp.w)); + + vec4 original = texture2D(uSampler, vTextureCoord); + vec4 masky = texture2D(mask, vMaskCoord); + + original *= (masky.r * masky.a * alpha * clip); + + gl_FragColor = original; +} diff --git a/packages/core/src/filters/spriteMask/spriteMaskFilter.vert b/packages/core/src/filters/spriteMask/spriteMaskFilter.vert new file mode 100644 index 0000000..d23a30c --- /dev/null +++ b/packages/core/src/filters/spriteMask/spriteMaskFilter.vert @@ -0,0 +1,16 @@ +attribute vec2 aVertexPosition; +attribute vec2 aTextureCoord; + +uniform mat3 projectionMatrix; +uniform mat3 otherMatrix; + +varying vec2 vMaskCoord; +varying vec2 vTextureCoord; + +void main(void) +{ + gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); + + vTextureCoord = aTextureCoord; + vMaskCoord = ( otherMatrix * vec3( aTextureCoord, 1.0) ).xy; +} diff --git a/packages/core/src/framebuffer/FrameBuffer.js b/packages/core/src/framebuffer/FrameBuffer.js new file mode 100644 index 0000000..84e9fcc --- /dev/null +++ b/packages/core/src/framebuffer/FrameBuffer.js @@ -0,0 +1,106 @@ +import Texture from '../textures/BaseTexture'; +import { FORMATS, TYPES } from '@pixi/constants'; + +/** + * Frame buffer + * @class + * @memberof PIXI + */ +export default class FrameBuffer +{ + constructor(width, height) + { + this.width = width || 100; + this.height = height || 100; + + this.stencil = false; + this.depth = false; + + this.dirtyId = 0; + this.dirtyFormat = 0; + this.dirtySize = 0; + + this.depthTexture = null; + this.colorTextures = []; + + this.glFrameBuffers = {}; + } + + get colorTexture() + { + return this.colorTextures[0]; + } + + addColorTexture(index, texture) + { + // TODO add some validation to the texture - same width / height etc? + this.colorTextures[index || 0] = texture || new Texture(null, { scaleMode: 0, + resolution: 1, + mipmap: false, + width: this.width, + height: this.height });// || new Texture(); + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + addDepthTexture(texture) + { + /* eslint-disable max-len */ + this.depthTexture = texture || new Texture(null, { scaleMode: 0, + resolution: 1, + width: this.width, + height: this.height, + mipmap: false, + format: FORMATS.DEPTH_COMPONENT, + type: TYPES.UNSIGNED_SHORT });// UNSIGNED_SHORT; + /* eslint-disable max-len */ + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + enableDepth() + { + this.depth = true; + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + enableStencil() + { + this.stencil = true; + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + resize(width, height) + { + if (width === this.width && height === this.height) return; + + this.width = width; + this.height = height; + + this.dirtyId++; + this.dirtySize++; + + for (let i = 0; i < this.colorTextures.length; i++) + { + this.colorTextures[i].setSize(width, height); + } + + if (this.depthTexture) + { + this.depthTexture.setSize(width, height); + } + } +} diff --git a/packages/core/src/framebuffer/FramebufferSystem.js b/packages/core/src/framebuffer/FramebufferSystem.js new file mode 100644 index 0000000..2cc2437 --- /dev/null +++ b/packages/core/src/framebuffer/FramebufferSystem.js @@ -0,0 +1,246 @@ +import System from '../System'; +import { Rectangle } from '@pixi/math'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class FramebufferSystem extends System +{ + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + contextChange() + { + this.gl = this.renderer.gl; + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + this.current = null; + this.viewport = new Rectangle(); + + this.drawBufferExtension = this.renderer.context.extensions.drawBuffers; + } + + bind(framebuffer, frame) + { + const gl = this.gl; + + this.current = framebuffer; + + if (framebuffer) + { + // TODO cacheing layer! + + const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID] || this.initFramebuffer(framebuffer); + + gl.bindFramebuffer(gl.FRAMEBUFFER, fbo.framebuffer); + // makesure all textures are unbound.. + + // now check for updates... + if (fbo.dirtyId !== framebuffer.dirtyId) + { + fbo.dirtyId = framebuffer.dirtyId; + + if (fbo.dirtyFormat !== framebuffer.dirtyFormat) + { + fbo.dirtyFormat = framebuffer.dirtyFormat; + this.updateFramebuffer(framebuffer); + } + else if (fbo.dirtySize !== framebuffer.dirtySize) + { + fbo.dirtySize = framebuffer.dirtySize; + this.resizeFramebuffer(framebuffer); + } + } + + for (let i = 0; i < framebuffer.colorTextures.length; i++) + { + if (framebuffer.colorTextures[i].texturePart) + { + this.renderer.texture.unbind(framebuffer.colorTextures[i].texture); + } + else + { + this.renderer.texture.unbind(framebuffer.colorTextures[i]); + } + } + + if (framebuffer.depthTexture) + { + this.renderer.texture.unbind(framebuffer.depthTexture); + } + + if (frame) + { + this.setViewport(frame.x, frame.y, frame.width, frame.height); + } + else + { + this.setViewport(0, 0, framebuffer.width, framebuffer.height); + } + } + else + { + gl.bindFramebuffer(gl.FRAMEBUFFER, null); + + if (frame) + { + this.setViewport(frame.x, frame.y, frame.width, frame.height); + } + else + { + this.setViewport(0, 0, this.renderer.width, this.renderer.height); + } + } + } + + setViewport(x, y, width, height) + { + const v = this.viewport; + + if (v.width !== width || v.height !== height || v.x !== x || v.y !== y) + { + v.x = x; + v.y = y; + v.width = width; + v.height = height; + + this.gl.viewport(x, y, width, height); + } + } + + get size() + { + if (this.current) + { + // TODO store temp + return { x: 0, y: 0, width: this.current.width, height: this.current.height }; + } + + return { x: 0, y: 0, width: this.renderer.width, height: this.renderer.height }; + } + + clear(r, g, b, a) + { + const gl = this.gl; + + // TODO clear color can be set only one right? + gl.clearColor(r, g, b, a); + gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); + } + + // private functions... + + initFramebuffer(framebuffer) + { + const gl = this.gl; + + // TODO - make this a class? + const fbo = { + framebuffer: gl.createFramebuffer(), + stencil: null, + dirtyId: 0, + dirtyFormat: 0, + dirtySize: 0, + }; + + framebuffer.glFrameBuffers[this.CONTEXT_UID] = fbo; + + return fbo; + } + + resizeFramebuffer(framebuffer) + { + const gl = this.gl; + + if (framebuffer.stencil || framebuffer.depth) + { + gl.bindRenderbuffer(gl.RENDERBUFFER, this.stencil); + gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); + } + } + + updateFramebuffer(framebuffer) + { + const gl = this.gl; + + const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID]; + + // bind the color texture + const colorTextures = framebuffer.colorTextures; + + let count = colorTextures.length; + + if (!this.drawBufferExtension) + { + count = Math.min(count, 1); + } + + const activeTextures = []; + + for (let i = 0; i < count; i++) + { + const texture = framebuffer.colorTextures[i]; + + if (texture.texturePart) + { + this.renderer.texture.bind(texture.texture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.COLOR_ATTACHMENT0 + i, + gl.TEXTURE_CUBE_MAP_NEGATIVE_X + texture.side, + texture.texture._glTextures[this.CONTEXT_UID].texture, + 0); + } + else + { + this.renderer.texture.bind(texture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.COLOR_ATTACHMENT0 + i, + gl.TEXTURE_2D, + texture._glTextures[this.CONTEXT_UID].texture, + 0); + } + + activeTextures.push(gl.COLOR_ATTACHMENT0 + i); + } + + if (this.drawBufferExtension && activeTextures.length > 1) + { + this.drawBufferExtension.drawBuffersWEBGL(activeTextures); + } + + if (framebuffer.depthTexture) + { + const depthTextureExt = this.renderer.context.extensions.depthTexture; + + if (depthTextureExt) + { + const depthTexture = framebuffer.depthTexture; + + this.renderer.texture.bind(depthTexture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.DEPTH_ATTACHMENT, + gl.TEXTURE_2D, + depthTexture._glTextures[this.CONTEXT_UID].texture, + 0); + } + } + + if (framebuffer.stencil || framebuffer.depth) + { + fbo.stencil = gl.createRenderbuffer(); + + gl.bindRenderbuffer(gl.RENDERBUFFER, fbo.stencil); + + // TODO.. this is depth AND stencil? + gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, fbo.stencil); + gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); + // fbo.enableStencil(); + } + } +} diff --git a/packages/core/src/geometry/GLBuffer.js b/packages/core/src/geometry/GLBuffer.js new file mode 100644 index 0000000..0477e8a --- /dev/null +++ b/packages/core/src/geometry/GLBuffer.js @@ -0,0 +1,9 @@ +export default class GLBuffer +{ + constructor(buffer) + { + this.buffer = buffer; + this.updateID = -1; + this.byteLength = -1; + } +} diff --git a/packages/core/src/geometry/GeometrySystem.js b/packages/core/src/geometry/GeometrySystem.js new file mode 100644 index 0000000..959f827 --- /dev/null +++ b/packages/core/src/geometry/GeometrySystem.js @@ -0,0 +1,388 @@ +import System from '../System'; +import GLBuffer from './GLBuffer'; + +const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class GeometrySystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this._activeGeometry = null; + this._activeVao = null; + + this.hasVao = true; + this.hasInstance = true; + } + + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + contextChange() + { + const gl = this.gl = this.renderer.gl; + + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + + // webgl2 + if (!gl.createVertexArray) + { + // webgl 1! + let nativeVaoExtension = this.renderer.context.extensions.vertexArrayObject; + + if (this.renderer.legacy) + { + nativeVaoExtension = null; + } + + if (nativeVaoExtension) + { + gl.createVertexArray = () => + nativeVaoExtension.createVertexArrayOES(); + + gl.bindVertexArray = (vao) => + nativeVaoExtension.bindVertexArrayOES(vao); + + gl.deleteVertexArray = (vao) => + nativeVaoExtension.deleteVertexArrayOES(vao); + } + else + { + this.hasVao = false; + gl.createVertexArray = () => + { + // empty + }; + + gl.bindVertexArray = () => + { + // empty + }; + + gl.deleteVertexArray = () => + { + // empty + }; + } + } + + if (!gl.vertexAttribDivisor) + { + const instanceExt = gl.getExtension('ANGLE_instanced_arrays'); + + if (instanceExt) + { + gl.vertexAttribDivisor = (a, b) => + instanceExt.vertexAttribDivisorANGLE(a, b); + + gl.drawElementsInstanced = (a, b, c, d, e) => + instanceExt.drawElementsInstancedANGLE(a, b, c, d, e); + + gl.drawArraysInstanced = (a, b, c, d) => + instanceExt.drawArraysInstancedANGLE(a, b, c, d); + } + else + { + this.hasInstance = false; + } + } + } + + /** + * Binds geometry so that is can be drawn. Creating a Vao if required + * @private + * @param {PIXI.Geometry} geometry instance of geometry to bind + */ + bind(geometry, shader) + { + shader = shader || this.renderer.shader.shader; + + const gl = this.gl; + + // not sure the best way to address this.. + // currently different shaders require different VAOs for the same geometry + // Still mulling over the best way to solve this one.. + // will likely need to modify the shader attribute locations at run time! + let vaos = geometry.glVertexArrayObjects[this.CONTEXT_UID]; + + if (!vaos) + { + geometry.glVertexArrayObjects[this.CONTEXT_UID] = vaos = {}; + } + + const vao = vaos[shader.program.id] || this.initGeometryVao(geometry, shader.program); + + this._activeGeometry = geometry; + + if (this._activeVao !== vao) + { + this._activeVao = vao; + + if (this.hasVao) + { + gl.bindVertexArray(vao); + } + else + { + this.activateVao(geometry, shader.program); + } + } + + // TODO - optimise later! + // don't need to loop through if nothing changed! + // maybe look to add an 'autoupdate' to geometry? + this.updateBuffers(); + } + + reset() + { + this.unbind(); + } + + updateBuffers() + { + const geometry = this._activeGeometry; + const gl = this.gl; + + for (let i = 0; i < geometry.buffers.length; i++) + { + const buffer = geometry.buffers[i]; + + const glBuffer = buffer._glBuffers[this.CONTEXT_UID]; + + if (buffer._updateID !== glBuffer.updateID) + { + glBuffer.updateID = buffer._updateID; + + // TODO can cache this on buffer! maybe added a getter / setter? + const type = buffer.index ? gl.ELEMENT_ARRAY_BUFFER : gl.ARRAY_BUFFER; + const drawType = buffer.static ? gl.STATIC_DRAW : gl.DYNAMIC_DRAW; + + gl.bindBuffer(type, glBuffer.buffer); + + if (glBuffer.byteLength >= buffer.data.byteLength) + { + // offset is always zero for now! + gl.bufferSubData(type, 0, buffer.data); + } + else + { + gl.bufferData(type, buffer.data, drawType); + } + } + } + } + + checkCompatability(geometry, program) + { + // geometry must have at least all the attributes that the shader requires. + const geometryAttributes = geometry.attributes; + const shaderAttributes = program.attributeData; + + for (const j in shaderAttributes) + { + if (!geometryAttributes[j]) + { + throw new Error(`shader and geometry incompatible, geometry missing the "${j}" attribute`); + } + } + } + + /** + * Creates a Vao with the same structure as the geometry and stores it on the geometry. + * @private + * @param {PIXI.Geometry} geometry instance of geometry to to generate Vao for + */ + initGeometryVao(geometry, program) + { + this.checkCompatability(geometry, program); + + const gl = this.gl; + const CONTEXT_UID = this.CONTEXT_UID; + const buffers = geometry.buffers; + const attributes = geometry.attributes; + + const tempStride = {}; + const tempStart = {}; + + for (const j in buffers) + { + tempStride[j] = 0; + tempStart[j] = 0; + } + + for (const j in attributes) + { + if (!attributes[j].size && program.attributeData[j]) + { + attributes[j].size = program.attributeData[j].size; + } + + tempStride[attributes[j].buffer] += attributes[j].size * byteSizeMap[attributes[j].type]; + } + + for (const j in attributes) + { + const attribute = attributes[j]; + const attribSize = attribute.size; + + if (attribute.stride === undefined) + { + if (tempStride[attribute.buffer] === attribSize * byteSizeMap[attribute.type]) + { + attribute.stride = 0; + } + else + { + attribute.stride = tempStride[attribute.buffer]; + } + } + + if (attribute.start === undefined) + { + attribute.start = tempStart[attribute.buffer]; + + tempStart[attribute.buffer] += attribSize * byteSizeMap[attribute.type]; + } + } + + // first update - and create the buffers! + // only create a gl buffer if it actually gets + for (let i = 0; i < buffers.length; i++) + { + const buffer = buffers[i]; + + if (!buffer._glBuffers[CONTEXT_UID]) + { + buffer._glBuffers[CONTEXT_UID] = new GLBuffer(gl.createBuffer()); + } + } + + // TODO - maybe make this a data object? + // lets wait to see if we need to first! + const vao = gl.createVertexArray(); + + gl.bindVertexArray(vao); + + this.activateVao(geometry, program); + + gl.bindVertexArray(null); + + // add it to the cache! + geometry.glVertexArrayObjects[this.CONTEXT_UID][program.id] = vao; + + return vao; + } + + activateVao(geometry, program) + { + const gl = this.gl; + const CONTEXT_UID = this.CONTEXT_UID; + const buffers = geometry.buffers; + const attributes = geometry.attributes; + + if (geometry.indexBuffer) + { + // first update the index buffer if we have one.. + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, geometry.indexBuffer._glBuffers[CONTEXT_UID].buffer); + } + + let lastBuffer = null; + + // add a new one! + for (const j in attributes) + { + const attribute = attributes[j]; + const buffer = buffers[attribute.buffer]; + const glBuffer = buffer._glBuffers[CONTEXT_UID]; + + if (program.attributeData[j]) + { + if (lastBuffer !== glBuffer) + { + gl.bindBuffer(gl.ARRAY_BUFFER, glBuffer.buffer); + + lastBuffer = glBuffer; + } + + const location = program.attributeData[j].location; + + // TODO introduce state again + // we can optimise this for older devices that have no VAOs + gl.enableVertexAttribArray(location); + + gl.vertexAttribPointer(location, + attribute.size, + attribute.type || gl.FLOAT, + attribute.normalized, + attribute.stride, + attribute.start); + + if (attribute.instance) + { + // TODO calculate instance count based of this... + if (this.hasInstance) + { + gl.vertexAttribDivisor(location, 1); + } + else + { + throw new Error('geometry error, GPU Instancing is not supported on this device'); + } + } + } + } + } + + draw(type, size, start, instanceCount) + { + const gl = this.gl; + const geometry = this._activeGeometry; + + // TODO.. this should not change so maybe cache the function? + + if (geometry.indexBuffer) + { + if (geometry.instanced) + { + /* eslint-disable max-len */ + gl.drawElementsInstanced(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2, instanceCount || 1); + /* eslint-enable max-len */ + } + else + { + gl.drawElements(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2); + } + } + else + if (geometry.instanced) + { + // TODO need a better way to calculate size.. + gl.drawArraysInstanced(type, start, size || geometry.getSize(), instanceCount || 1); + } + else + { + gl.drawArrays(type, start, size || geometry.getSize()); + } + + return this; + } + + unbind() + { + this.gl.bindVertexArray(null); + this._activeVao = null; + this._activeGeometry = null; + } +} diff --git a/packages/core/src/geometry/utils/setVertexAttribArrays.js b/packages/core/src/geometry/utils/setVertexAttribArrays.js new file mode 100644 index 0000000..23b6959 --- /dev/null +++ b/packages/core/src/geometry/utils/setVertexAttribArrays.js @@ -0,0 +1,54 @@ +// var GL_MAP = {}; + +/** + * @param gl {WebGLRenderingContext} The current WebGL context + * @param attribs {*} + * @param state {*} + */ +export default function setVertexAttribArrays(gl, attribs, state) +{ + let i; + + if (state) + { + const tempAttribState = state.tempAttribState; + const attribState = state.attribState; + + for (i = 0; i < tempAttribState.length; i++) + { + tempAttribState[i] = false; + } + + // set the new attribs + for (i = 0; i < attribs.length; i++) + { + tempAttribState[attribs[i].attribute.location] = true; + } + + for (i = 0; i < attribState.length; i++) + { + if (attribState[i] !== tempAttribState[i]) + { + attribState[i] = tempAttribState[i]; + + if (state.attribState[i]) + { + gl.enableVertexAttribArray(i); + } + else + { + gl.disableVertexAttribArray(i); + } + } + } + } + else + { + for (i = 0; i < attribs.length; i++) + { + const attrib = attribs[i]; + + gl.enableVertexAttribArray(attrib.attribute.location); + } + } +} diff --git a/packages/core/src/index.js b/packages/core/src/index.js index 58c456a..fd1f547 100644 --- a/packages/core/src/index.js +++ b/packages/core/src/index.js @@ -1,30 +1,30 @@ import * as resources from './textures/resources'; -import * as systems from './renderers/systems'; +import * as systems from './systems'; -export { resources }; export { systems }; +export { resources }; -export { default as Renderer } from './renderers/Renderer'; -export { default as AbstractRenderer } from './renderers/AbstractRenderer'; -export { default as FrameBuffer } from './textures/FrameBuffer'; +export { default as System } from './System'; +export { default as Renderer } from './Renderer'; +export { default as AbstractRenderer } from './AbstractRenderer'; +export { default as FrameBuffer } from './framebuffer/FrameBuffer'; export { default as CubeTexture } from './textures/CubeTexture'; export { default as BaseTexture } from './textures/BaseTexture'; export { default as Texture } from './textures/Texture'; export { default as TextureMatrix } from './textures/TextureMatrix'; -export { default as RenderTexture } from './textures/RenderTexture'; -export { default as BaseRenderTexture } from './textures/BaseRenderTexture'; +export { default as RenderTexture } from './renderTexture/RenderTexture'; +export { default as BaseRenderTexture } from './renderTexture/BaseRenderTexture'; export { default as TextureUvs } from './textures/TextureUvs'; -export { default as State } from './renderers/State'; -export { default as ObjectRenderer } from './renderers/utils/ObjectRenderer'; -export { default as Quad } from './renderers/utils/Quad'; -export { default as QuadUv } from './renderers/utils/QuadUv'; -export { default as checkMaxIfStatmentsInShader } from './renderers/utils/checkMaxIfStatmentsInShader'; +export { default as State } from './state/State'; +export { default as ObjectRenderer } from './batch/ObjectRenderer'; +export { default as Quad } from './utils/Quad'; +export { default as QuadUv } from './utils/QuadUv'; +export { default as checkMaxIfStatmentsInShader } from './shader/utils/checkMaxIfStatmentsInShader'; export { default as Shader } from './shader/Shader'; export { default as Program } from './shader/Program'; export { default as UniformGroup } from './shader/UniformGroup'; -export { default as SpriteMaskFilter } from './renderers/filters/spriteMask/SpriteMaskFilter'; -export { default as Filter } from './renderers/filters/Filter'; +export { default as SpriteMaskFilter } from './filters/spriteMask/SpriteMaskFilter'; +export { default as Filter } from './filters/Filter'; export { default as Attribute } from './geometry/Attribute'; export { default as Buffer } from './geometry/Buffer'; export { default as Geometry } from './geometry/Geometry'; - diff --git a/packages/core/src/mask/MaskSystem.js b/packages/core/src/mask/MaskSystem.js new file mode 100644 index 0000000..99174c2 --- /dev/null +++ b/packages/core/src/mask/MaskSystem.js @@ -0,0 +1,199 @@ +import System from '../System'; +import AlphaMaskFilter from '../filters/spriteMask/SpriteMaskFilter'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class MaskSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + // TODO - we don't need both! + this.scissor = false; + this.scissorData = null; + this.scissorRenderTarget = null; + + this.enableScissor = false; + + this.alphaMaskPool = []; + this.alphaMaskIndex = 0; + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.DisplayObject} target - Display Object to push the mask to + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + push(target, maskData) + { + // TODO the root check means scissor rect will not + // be used on render textures more info here: + // https://github.com/pixijs/pixi.js/pull/3545 + + if (maskData.texture) + { + this.pushSpriteMask(target, maskData); + } + else if (this.enableScissor + && !this.scissor + && this.renderer._activeRenderTarget.root + && !this.renderer.stencil.stencilMaskStack.length + && maskData.isFastRect()) + { + const matrix = maskData.worldTransform; + + let rot = Math.atan2(matrix.b, matrix.a); + + // use the nearest degree! + rot = Math.round(rot * (180 / Math.PI)); + + if (rot % 90) + { + this.pushStencilMask(maskData); + } + else + { + this.pushScissorMask(target, maskData); + } + } + else + { + this.pushStencilMask(maskData); + } + } + + /** + * Removes the last mask from the mask stack and doesn't return it. + * + * @param {PIXI.DisplayObject} target - Display Object to pop the mask from + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + pop(target, maskData) + { + if (maskData.texture) + { + this.popSpriteMask(target, maskData); + } + else if (this.enableScissor && !this.renderer.stencil.stencilMaskStack.length) + { + this.popScissorMask(target, maskData); + } + else + { + this.popStencilMask(target, maskData); + } + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.RenderTarget} target - Display Object to push the sprite mask to + * @param {PIXI.Sprite} maskData - Sprite to be used as the mask + */ + pushSpriteMask(target, maskData) + { + let alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; + + if (!alphaMaskFilter) + { + alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; + } + + alphaMaskFilter[0].resolution = this.renderer.resolution; + alphaMaskFilter[0].maskSprite = maskData; + + // TODO - may cause issues! + target.filterArea = maskData.getBounds(true); + + this.renderer.filterSystem.pushFilter(target, alphaMaskFilter); + + this.alphaMaskIndex++; + } + + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popSpriteMask() + { + this.renderer.filterSystem.popFilter(); + this.alphaMaskIndex--; + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + pushStencilMask(maskData) + { + this.renderer.batch.flush(); + this.renderer.stencil.pushStencil(maskData); + } + + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popStencilMask() + { + // this.renderer.currentRenderer.stop(); + this.renderer.stencil.popStencil(); + } + + /** + * + * @param {PIXI.DisplayObject} target - Display Object to push the mask to + * @param {PIXI.Graphics} maskData - The masking data. + */ + pushScissorMask(target, maskData) + { + maskData.renderable = true; + + const renderTarget = this.renderer._activeRenderTarget; + + const bounds = maskData.getBounds(); + + bounds.fit(renderTarget.size); + maskData.renderable = false; + + this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); + + const resolution = this.renderer.resolution; + + this.renderer.gl.scissor( + bounds.x * resolution, + (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, + bounds.width * resolution, + bounds.height * resolution + ); + + this.scissorRenderTarget = renderTarget; + this.scissorData = maskData; + this.scissor = true; + } + + /** + * + * + */ + popScissorMask() + { + this.scissorRenderTarget = null; + this.scissorData = null; + this.scissor = false; + + // must be scissor! + const gl = this.renderer.gl; + + gl.disable(gl.SCISSOR_TEST); + } +} diff --git a/packages/core/src/mask/StencilSystem.js b/packages/core/src/mask/StencilSystem.js new file mode 100644 index 0000000..62796a5 --- /dev/null +++ b/packages/core/src/mask/StencilSystem.js @@ -0,0 +1,133 @@ +import System from '../System'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class StencilSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + this.stencilMaskStack = []; + } + + /** + * Changes the mask stack that is used by this System. + * + * @param {PIXI.Graphics[]} stencilMaskStack - The mask stack + */ + setMaskStack(stencilMaskStack) + { + const gl = this.renderer.gl; + + if (stencilMaskStack.length !== this.stencilMaskStack.length) + { + if (stencilMaskStack.length === 0) + { + gl.disable(gl.STENCIL_TEST); + } + else + { + gl.enable(gl.STENCIL_TEST); + } + } + + this.stencilMaskStack = stencilMaskStack; + } + + /** + * Applies the Mask and adds it to the current stencil stack. @alvin + * + * @param {PIXI.Graphics} graphics - The mask + */ + pushStencil(graphics) + { + this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); + + // this.renderer._activeRenderTarget.attachStencilBuffer(); + + const gl = this.renderer.gl; + const prevMaskCount = this.stencilMaskStack.length; + + if (prevMaskCount === 0) + { + gl.enable(gl.STENCIL_TEST); + } + + this.stencilMaskStack.push(graphics); + + // Increment the refference stencil value where the new mask overlaps with the old ones. + gl.colorMask(false, false, false, false); + gl.stencilFunc(gl.EQUAL, prevMaskCount, this._getBitwiseMask()); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.INCR); + this.renderer.plugins.graphics.render(graphics); + + this._useCurrent(); + } + + /** + * Removes the last mask from the stencil stack. @alvin + */ + popStencil() + { + this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); + + const gl = this.renderer.gl; + const graphics = this.stencilMaskStack.pop(); + + if (this.stencilMaskStack.length === 0) + { + // the stack is empty! + gl.disable(gl.STENCIL_TEST); + gl.clear(gl.STENCIL_BUFFER_BIT); + gl.clearStencil(0); + } + else + { + // Decrement the refference stencil value where the popped mask overlaps with the other ones + gl.colorMask(false, false, false, false); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.DECR); + this.renderer.plugins.graphics.render(graphics); + + this._useCurrent(); + } + } + + /** + * Setup renderer to use the current stencil data. + */ + _useCurrent() + { + const gl = this.renderer.gl; + + gl.colorMask(true, true, true, true); + gl.stencilFunc(gl.EQUAL, this.stencilMaskStack.length, this._getBitwiseMask()); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP); + } + + /** + * Fill 1s equal to the number of acitve stencil masks. + * + * @return {number} The bitwise mask. + */ + _getBitwiseMask() + { + return (1 << this.stencilMaskStack.length) - 1; + } + + /** + * Destroys the mask stack. + * + */ + destroy() + { + super.destroy(this); + + this.stencilMaskStack = null; + } +} diff --git a/packages/core/src/projection/ProjectionSystem.js b/packages/core/src/projection/ProjectionSystem.js new file mode 100644 index 0000000..6e63b73 --- /dev/null +++ b/packages/core/src/projection/ProjectionSystem.js @@ -0,0 +1,73 @@ +import System from '../System'; +import { Matrix } from '@pixi/math'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class ProjectionSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.projectionMatrix = new Matrix(); + } + + update(destinationFrame, sourceFrame, resolution, root) + { + this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; + this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; + + this.calculateProjection(this.destinationFrame, this.sourceFrame, resolution, root); + + this.renderer.globalUniforms.uniforms.projectionMatrix = this.projectionMatrix; + this.renderer.globalUniforms.update(); + } + + /** + * Updates the projection matrix based on a projection frame (which is a rectangle) + * + * @param {Rectangle} destinationFrame - The destination frame. + * @param {Rectangle} sourceFrame - The source frame. + */ + calculateProjection(destinationFrame, sourceFrame, resolution, root) + { + const pm = this.projectionMatrix; + + // I don't think we will need this line.. + // pm.identity(); + + if (!root) + { + pm.a = (1 / destinationFrame.width * 2) * resolution; + pm.d = (1 / destinationFrame.height * 2) * resolution; + + pm.tx = -1 - (sourceFrame.x * pm.a); + pm.ty = -1 - (sourceFrame.y * pm.d); + } + else + { + pm.a = (1 / destinationFrame.width * 2) * resolution; + pm.d = (-1 / destinationFrame.height * 2) * resolution; + + pm.tx = -1 - (sourceFrame.x * pm.a); + pm.ty = 1 - (sourceFrame.y * pm.d); + } + } + + /** + * Sets the transform of the active render target to the given matrix + * + * @param {PIXI.Matrix} matrix - The transformation matrix + */ + setTransform()// matrix) + { + // this._activeRenderTarget.transform = matrix; + } +} diff --git a/packages/core/src/renderTexture/BaseRenderTexture.js b/packages/core/src/renderTexture/BaseRenderTexture.js new file mode 100644 index 0000000..88eb83b --- /dev/null +++ b/packages/core/src/renderTexture/BaseRenderTexture.js @@ -0,0 +1,137 @@ +import BaseTexture from '../textures/BaseTexture'; +import FrameBuffer from '../framebuffer/FrameBuffer'; + +/** + * A BaseRenderTexture is a special texture that allows any PixiJS display object to be rendered to it. + * + * __Hint__: All DisplayObjects (i.e. Sprites) that render to a BaseRenderTexture should be preloaded + * otherwise black rectangles will be drawn instead. + * + * A BaseRenderTexture takes a snapshot of any Display Object given to its render method. The position + * and rotation of the given Display Objects is ignored. For example: + * + * ```js + * let renderer = PIXI.autoDetectRenderer(1024, 1024, { view: canvas, ratio: 1 }); + * let baseRenderTexture = new PIXI.BaseRenderTexture(renderer, 800, 600); + * let sprite = PIXI.Sprite.fromImage("spinObj_01.png"); + * + * sprite.position.x = 800/2; + * sprite.position.y = 600/2; + * sprite.anchor.x = 0.5; + * sprite.anchor.y = 0.5; + * + * baseRenderTexture.render(sprite); + * ``` + * + * The Sprite in this case will be rendered using its local transform. To render this sprite at 0,0 + * you can clear the transform + * + * ```js + * + * sprite.setTransform() + * + * let baseRenderTexture = new PIXI.BaseRenderTexture(100, 100); + * let renderTexture = new PIXI.RenderTexture(baseRenderTexture); + * + * renderer.render(sprite, renderTexture); // Renders to center of RenderTexture + * ``` + * + * @class + * @extends PIXI.BaseTexture + * @memberof PIXI + */ +export default class BaseRenderTexture extends BaseTexture +{ + /** + * @param {object} [options] + * @param {number} [options.width=100] - The width of the base render texture + * @param {number} [options.height=100] - The height of the base render texture + * @param {PIXI.SCALE_MODES} [options.scaleMode] - See {@link PIXI.SCALE_MODES} for possible values + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the texture being generated + */ + constructor(options) + { + if (typeof options === 'number') + { + /* eslint-disable prefer-rest-params */ + // Backward compatibility of signature + const width = arguments[0]; + const height = arguments[1]; + const scaleMode = arguments[2]; + const resolution = arguments[3]; + + options = { width, height, scaleMode, resolution }; + /* eslint-enable prefer-rest-params */ + } + + super(null, options); + + const { width, height } = options || {}; + + // Set defaults + this.mipmap = false; + this.width = Math.ceil(width) || 100; + this.height = Math.ceil(height) || 100; + this.valid = true; + + /** + * A map of renderer IDs to webgl renderTargets + * + * @private + * @member {object} + */ + // this._glRenderTargets = {}; + + /** + * A reference to the canvas render target (we only need one as this can be shared across renderers) + * + * @private + * @member {object} + */ + this._canvasRenderTarget = null; + + this.clearColor = [0, 0, 0, 0]; + + this.frameBuffer = new FrameBuffer(width * this.resolution, height * this.resolution) + .addColorTexture(0, this); + + // TODO - could this be added the systems? + + /** + * The data structure for the stencil masks + * + * @member {PIXI.Graphics[]} + */ + this.stencilMaskStack = []; + + /** + * The data structure for the filters + * + * @member {PIXI.Graphics[]} + */ + this.filterStack = [{}]; + } + + /** + * Resizes the BaseRenderTexture. + * + * @param {number} width - The width to resize to. + * @param {number} height - The height to resize to. + */ + resize(width, height) + { + width = Math.ceil(width); + height = Math.ceil(height); + this.frameBuffer.resize(width * this.resolution, height * this.resolution); + } + + /** + * Destroys this texture + * + */ + destroy() + { + super.destroy(true); + this.renderer = null; + } +} diff --git a/packages/core/src/renderTexture/RenderTexture.js b/packages/core/src/renderTexture/RenderTexture.js new file mode 100644 index 0000000..f1ecf1f --- /dev/null +++ b/packages/core/src/renderTexture/RenderTexture.js @@ -0,0 +1,153 @@ +import BaseRenderTexture from './BaseRenderTexture'; +import Texture from '../textures/Texture'; + +/** + * A RenderTexture is a special texture that allows any PixiJS display object to be rendered to it. + * + * __Hint__: All DisplayObjects (i.e. Sprites) that render to a RenderTexture should be preloaded + * otherwise black rectangles will be drawn instead. + * + * A RenderTexture takes a snapshot of any Display Object given to its render method. For example: + * + * ```js + * let renderer = PIXI.autoDetectRenderer(1024, 1024, { view: canvas, ratio: 1 }); + * let renderTexture = PIXI.RenderTexture.create(800, 600); + * let sprite = PIXI.Sprite.fromImage("spinObj_01.png"); + * + * sprite.position.x = 800/2; + * sprite.position.y = 600/2; + * sprite.anchor.x = 0.5; + * sprite.anchor.y = 0.5; + * + * renderer.render(sprite, renderTexture); + * ``` + * + * The Sprite in this case will be rendered using its local transform. To render this sprite at 0,0 + * you can clear the transform + * + * ```js + * + * sprite.setTransform() + * + * let renderTexture = new PIXI.RenderTexture.create(100, 100); + * + * renderer.render(sprite, renderTexture); // Renders to center of RenderTexture + * ``` + * + * @class + * @extends PIXI.Texture + * @memberof PIXI + */ +export default class RenderTexture extends Texture +{ + /** + * @param {PIXI.BaseRenderTexture} baseRenderTexture - The renderer used for this RenderTexture + * @param {PIXI.Rectangle} [frame] - The rectangle frame of the texture to show + */ + constructor(baseRenderTexture, frame) + { + // support for legacy.. + let _legacyRenderer = null; + + if (!(baseRenderTexture instanceof BaseRenderTexture)) + { + /* eslint-disable prefer-rest-params, no-console */ + const width = arguments[1]; + const height = arguments[2]; + const scaleMode = arguments[3]; + const resolution = arguments[4]; + + // we have an old render texture.. + console.warn(`Please use RenderTexture.create(${width}, ${height}) instead of the ctor directly.`); + _legacyRenderer = arguments[0]; + /* eslint-enable prefer-rest-params, no-console */ + + frame = null; + baseRenderTexture = new BaseRenderTexture({ + width, + height, + scaleMode, + resolution, + }); + } + + /** + * The base texture object that this texture uses + * + * @member {BaseTexture} + */ + super(baseRenderTexture, frame); + + this.legacyRenderer = _legacyRenderer; + + /** + * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. + * + * @member {boolean} + */ + this.valid = true; + + /** + * FilterSystem temporary storage + * @private + * @member {PIXI.Rectangle} + */ + this.filterFrame = null; + + this._updateUvs(); + } + + /** + * Resizes the RenderTexture. + * + * @param {number} width - The width to resize to. + * @param {number} height - The height to resize to. + * @param {boolean} [resizeBaseTexture=true] - Should the baseTexture.width and height values be resized as well? + */ + resize(width, height, resizeBaseTexture = true) + { + width = Math.ceil(width); + height = Math.ceil(height); + + // TODO - could be not required.. + this.valid = (width > 0 && height > 0); + + this._frame.width = this.orig.width = width; + this._frame.height = this.orig.height = height; + + if (resizeBaseTexture) + { + this.baseTexture.resize(width, height); + } + + this._updateUvs(); + } + + /** + * A short hand way of creating a render texture. + * + * @param {object} [options] - Options + * @param {number} [options.width=100] - The width of the render texture + * @param {number} [options.height=100] - The height of the render texture + * @param {number} [options.scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the texture being generated + * @return {PIXI.RenderTexture} The new render texture + */ + static create(options) + { + // fallback, old-style: create(width, height, scaleMode, resolution) + if (typeof options === 'number') + { + /* eslint-disable prefer-rest-params */ + options = { + width: options, + height: arguments[1], + scaleMode: arguments[2], + resolution: arguments[3], + }; + /* eslint-enable prefer-rest-params */ + } + + return new RenderTexture(new BaseRenderTexture(options)); + } +} diff --git a/packages/core/src/renderTexture/RenderTextureSystem.js b/packages/core/src/renderTexture/RenderTextureSystem.js new file mode 100644 index 0000000..8d0ca61 --- /dev/null +++ b/packages/core/src/renderTexture/RenderTextureSystem.js @@ -0,0 +1,115 @@ +import System from '../System'; +import { Rectangle } from '@pixi/math'; + +const tempRect = new Rectangle(); + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class RenderTextureSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.clearColor = renderer._backgroundColorRgba; + + // TODO moe this property somewhere else! + this.defaultMaskStack = []; + this.defaultFilterStack = [{}]; + + // empty render texture? + this.renderTexture = null; + + this.destinationFrame = new Rectangle(); + } + + bind(renderTexture, sourceFrame, destinationFrame) + { + // TODO - do we want this?? + if (this.renderTexture === renderTexture) return; + this.renderTexture = renderTexture; + + const renderer = this.renderer; + + if (renderTexture) + { + const baseTexture = renderTexture.baseTexture; + + if (!destinationFrame) + { + tempRect.width = baseTexture.realWidth; + tempRect.height = baseTexture.realHeight; + + destinationFrame = tempRect; + } + + if (!sourceFrame) + { + sourceFrame = destinationFrame; + } + + this.renderer.framebuffer.bind(baseTexture.frameBuffer, destinationFrame); + + this.renderer.projection.update(destinationFrame, sourceFrame, baseTexture.resolution, false); + this.renderer.stencil.setMaskStack(baseTexture.stencilMaskStack); + } + else + { + // TODO these validation checks happen deeper down.. + // thing they can be avoided.. + if (!destinationFrame) + { + tempRect.width = renderer.width; + tempRect.height = renderer.height; + + destinationFrame = tempRect; + } + + if (!sourceFrame) + { + sourceFrame = destinationFrame; + } + + renderer.framebuffer.bind(null, destinationFrame); + + // TODO store this.. + this.renderer.projection.update(destinationFrame, sourceFrame, this.renderer.resolution, true); + this.renderer.stencil.setMaskStack(this.defaultMaskStack); + } + + this.destinationFrame.copyFrom(destinationFrame); + } + + /** + * Erases the render texture and fills the drawing area with a colour + * + * @param {number} [clearColor] - The colour + * @return {PIXI.Renderer} Returns itself. + */ + clear(clearColor) + { + if (this.renderTexture) + { + clearColor = clearColor || this.renderTexture.baseTexture.clearColor; + } + else + { + clearColor = clearColor || this.clearColor; + } + + this.renderer.framebuffer.clear(clearColor[0], clearColor[1], clearColor[2], clearColor[3]); + } + + resize()// screenWidth, screenHeight) + { + // resize the root only! + this.bind(null); + } +} diff --git a/packages/core/src/renderers/AbstractRenderer.js b/packages/core/src/renderers/AbstractRenderer.js deleted file mode 100644 index 928670f..0000000 --- a/packages/core/src/renderers/AbstractRenderer.js +++ /dev/null @@ -1,353 +0,0 @@ -import { hex2string, hex2rgb } from '@pixi/utils'; -import { Matrix, Rectangle } from '@pixi/math'; -import { RENDERER_TYPE } from '@pixi/constants'; -import { settings } from '@pixi/settings'; -import { Container } from '@pixi/display'; -import RenderTexture from '../textures/RenderTexture'; -import EventEmitter from 'eventemitter3'; - -const tempMatrix = new Matrix(); - -/** - * 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 EventEmitter - * @memberof PIXI - */ -export default class AbstractRenderer extends EventEmitter -{ - // eslint-disable-next-line valid-jsdoc - /** - * @param {string} system - The name of the system this renderer is for. - * @param {object} [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.transparent=false] - If the render view is transparent, default false - * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false - * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) - * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. The - * resolution of the renderer retina would be 2. - * @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 {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when rendering, - * stopping pixel interpolation. - */ - constructor(system, options, arg2, arg3) - { - super(); - - // Support for constructor(system, screenWidth, screenHeight, options) - if (typeof options === 'number') - { - options = Object.assign({ - width: options, - height: arg2 || settings.RENDER_OPTIONS.height, - }, arg3); - } - - // 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 = RENDERER_TYPE.UNKNOWN; - - /** - * Measurements of the screen. (0, 0, screenWidth, screenHeight) - * - * Its safe to use as filterArea or hitArea for 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 1 - */ - this.resolution = options.resolution || settings.RESOLUTION; - - /** - * Whether the render view is transparent - * - * @member {boolean} - */ - this.transparent = options.transparent; - - /** - * Whether css dimensions of canvas view should be resized to screen dimensions automatically - * - * @member {boolean} - */ - this.autoResize = options.autoResize || false; - - /** - * Tracks the blend modes useful for this renderer. - * - * @member {object} - */ - this.blendModes = null; - - /** - * 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; - - /** - * If true PixiJS will Math.floor() x/y values when rendering, stopping pixel interpolation. - * Handy for crisp pixel art and speed on legacy devices. - * - * @member {boolean} - */ - this.roundPixels = options.roundPixels; - - /** - * The background color as a number. - * - * @member {number} - * @private - */ - this._backgroundColor = 0x000000; - - /** - * The background color as an [R, G, B] array. - * - * @member {number[]} - * @private - */ - this._backgroundColorRgba = [0, 0, 0, 0]; - - /** - * The background color as a string. - * - * @member {string} - * @private - */ - this._backgroundColorString = '#000000'; - - this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter - - /** - * This temporary display object used as the parent of the currently being rendered item - * - * @member {PIXI.DisplayObject} - * @private - */ - this._tempDisplayObjectParent = new Container(); - - /** - * The last root object that the renderer tried to render. - * - * @member {PIXI.DisplayObject} - * @private - */ - this._lastObjectRendered = this._tempDisplayObjectParent; - - /** - * Collection of plugins. - * @readonly - * @member {object} - */ - this.plugins = {}; - } - - /** - * Initialize the plugins. - * - * @protected - * @param {object} staticMap - The dictionary of staticly saved plugins. - */ - initPlugins(staticMap) - { - 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() - { - return this.view.width; - } - - /** - * Same as view.height, actual number of pixels in the canvas by vertical - * - * @member {number} - * @readonly - * @default 600 - */ - get height() - { - return this.view.height; - } - - /** - * Resizes the screen and canvas to the specified width and height - * Canvas dimensions are multiplied by resolution - * - * @param {number} screenWidth - the new width of the screen - * @param {number} screenHeight - the new height of the screen - */ - resize(screenWidth, screenHeight) - { - this.screen.width = screenWidth; - this.screen.height = screenHeight; - - this.view.width = screenWidth * this.resolution; - this.view.height = screenHeight * this.resolution; - - if (this.autoResize) - { - this.view.style.width = `${screenWidth}px`; - this.view.style.height = `${screenHeight}px`; - } - } - - /** - * 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 {PIXI.DisplayObject} displayObject - The displayObject the object will be generated from - * @param {number} scaleMode - Should be one of the scaleMode consts - * @param {number} resolution - The resolution / device pixel ratio of the texture being generated - * @param {PIXI.Rectangle} [region] - The region of the displayObject, that shall be rendered, - * if no region is specified, defaults to the local bounds of the displayObject. - * @return {PIXI.Texture} a texture of the graphics object - */ - generateTexture(displayObject, scaleMode, resolution, region) - { - region = region || displayObject.getLocalBounds(); - - // 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(region.width | 0, region.height | 0, scaleMode, resolution); - - tempMatrix.tx = -region.x; - tempMatrix.ty = -region.y; - - this.render(displayObject, renderTexture, false, tempMatrix, true); - - return renderTexture; - } - - /** - * Removes everything from the renderer and optionally removes the Canvas DOM element. - * - * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. - */ - destroy(removeView) - { - 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); - } - - this.plugins = null; - - this.type = RENDERER_TYPE.UNKNOWN; - - this.view = null; - - this.screen = null; - - this.resolution = 0; - - this.transparent = false; - - this.autoResize = false; - - this.blendModes = null; - - this.options = null; - - this.preserveDrawingBuffer = false; - this.clearBeforeRender = false; - - this.roundPixels = false; - - this._backgroundColor = 0; - this._backgroundColorRgba = null; - this._backgroundColorString = null; - - this._tempDisplayObjectParent = null; - this._lastObjectRendered = null; - } - - /** - * The background color to fill if not transparent - * - * @member {number} - */ - get backgroundColor() - { - return this._backgroundColor; - } - - set backgroundColor(value) // eslint-disable-line require-jsdoc - { - this._backgroundColor = value; - this._backgroundColorString = hex2string(value); - hex2rgb(value, this._backgroundColorRgba); - } -} diff --git a/packages/core/src/renderers/Renderer.js b/packages/core/src/renderers/Renderer.js deleted file mode 100644 index a2020b1..0000000 --- a/packages/core/src/renderers/Renderer.js +++ /dev/null @@ -1,329 +0,0 @@ -import AbstractRenderer from './AbstractRenderer'; -import { sayHello } from '@pixi/utils'; -import MaskSystem from './systems/MaskSystem'; -import StencilSystem from './systems/StencilSystem'; -import FilterSystem from './systems/filter/FilterSystem'; -import FramebufferSystem from './systems/FramebufferSystem'; -import RenderTextureSystem from './systems/RenderTextureSystem'; -import TextureSystem from './systems/textures/TextureSystem'; -import ProjectionSystem from './systems/ProjectionSystem'; -import StateSystem from './systems/StateSystem'; -import GeometrySystem from './systems/geometry/GeometrySystem'; -import ShaderSystem from './systems/shader/ShaderSystem'; -import ContextSystem from './systems/ContextSystem'; -import BatchSystem from './systems/BatchSystem'; -import TextureGCSystem from './systems/textures/TextureGCSystem'; -import { RENDERER_TYPE } from '@pixi/constants'; -import UniformGroup from '../shader/UniformGroup'; -import { Matrix } from '@pixi/math'; -import Runner from 'mini-runner'; - -/** - * 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 Render works by automatically managing webGLBatchs. - * 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 :) - * - * @class - * @memberof PIXI - * @extends PIXI.AbstractRenderer - */ -export default class Renderer extends AbstractRenderer -{ - // eslint-disable-next-line valid-jsdoc - /** - * - * @param {object} [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.transparent=false] - If the render view is transparent, default false - * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false - * @param {boolean} [options.antialias=false] - sets antialias. If not available natively then FXAA - * antialiasing is used - * @param {boolean} [options.forceFXAA=false] - forces FXAA antialiasing to be used over native. - * FXAA is faster, but may not always look as great - * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. - * The resolution of the renderer retina would be 2. - * @param {boolean} [options.clearBeforeRender=true] - This sets if the renderer will clear - * the canvas or not before the new render pass. If you wish to set this to false, you *must* set - * preserveDrawingBuffer to `true`. - * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, - * enable this if you need to call toDataUrl on the webgl context. - * @param {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when - * rendering, stopping pixel interpolation. - * @param {number} [options.backgroundColor=0x000000] - The background color of the rendered area - * (shown if not transparent). - * @param {boolean} [options.legacy=false] - If true PixiJS will aim to ensure compatibility - * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. - * @param {string} [options.powerPreference] - Parameter passed to webgl context, set to "high-performance" - * for devices with dual graphics card - */ - constructor(options, arg2, arg3) - { - super('WebGL', options, arg2, arg3); - - /** - * The type of this renderer as a standardised const - * - * @member {number} - * @see PIXI.RENDERER_TYPE - */ - this.type = RENDERER_TYPE.WEBGL; - - // this will be set by the contextSystem (this.context) - this.gl = null; - this.CONTEXT_UID = 0; - this.legacy = !!options.legacy; - - // TODO legacy! - - // runners! - this.runners = { - destroy: new Runner('destroy'), - contextChange: new Runner('contextChange', 1), - reset: new Runner('reset'), - update: new Runner('update'), - postrender: new Runner('postrender'), - prerender: new Runner('prerender'), - resize: new Runner('resize', 2), - }; - - 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(GeometrySystem, 'geometry') - .addSystem(FramebufferSystem, 'framebuffer') - .addSystem(StencilSystem, 'stencil') - .addSystem(ProjectionSystem, 'projection') - .addSystem(TextureGCSystem, 'textureGC') - .addSystem(FilterSystem, 'filter') - .addSystem(RenderTextureSystem, 'renderTexture') - .addSystem(BatchSystem, 'batch'); - - this.initPlugins(Renderer.__plugins); - - /** - * The options passed in to create a new webgl context. - * - * @member {object} - * @private - */ - if (options.context) - { - this.context.initFromContext(options.context); - } - else - { - this.context.initFromOptions({ - alpha: this.transparent, - antialias: options.antialias, - premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', - stencil: true, - 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); - } - - addSystem(_class, name) - { - if (!name) - { - name = _class.name; - } - - // TODO - read name from class.name.. - - /* - if(name.includes('System')) - { - name = name.replace('System', ''); - name = name.charAt(0).toLowerCase() + name.slice(1); - } - */ - - const system = new _class(this); - - if (this[name]) - { - throw new Error(`Whoops! ${name} is already a manger`); - } - - this[name] = system; - - for (const i in this.runners) - { - this.runners[i].add(system); - } - - return this; - - /** - * 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. - */ - } - - /** - * Renders the object to its webGL view - * - * @param {PIXI.DisplayObject} displayObject - the object to be rendered - * @param {PIXI.RenderTexture} renderTexture - The render texture to render to. - * @param {boolean} [clear] - Should the canvas be cleared before the new render - * @param {PIXI.Transform} [transform] - A transform to apply to the render texture before rendering. - * @param {boolean} [skipUpdateTransform] - Should we skip the update transform pass? - */ - render(displayObject, renderTexture, clear, transform, skipUpdateTransform) - { - // can be handy to know! - this.renderingToScreen = !renderTexture; - - this.runners.prerender.run(); - this.emit('prerender'); - - // 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.parent; - - displayObject.parent = this._tempDisplayObjectParent; - displayObject.updateTransform(); - displayObject.parent = 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.run(); - - this.emit('postrender'); - } - - /** - * Resizes the webGL view to the specified width and height. - * - * @param {number} screenWidth - the new width of the screen - * @param {number} screenHeight - the new height of the screen - */ - resize(screenWidth, screenHeight) - { - AbstractRenderer.prototype.resize.call(this, screenWidth, screenHeight); - - this.runners.resize.run(screenWidth, screenHeight); - } - - /** - * Resets the WebGL state so you can render things however you fancy! - * - * @return {PIXI.Renderer} Returns itself. - */ - reset() - { - this.runners.reset.run(); - - return this; - } - - clear() - { - this.framebuffer.bind(); - this.framebuffer.clear(); - } - - /** - * Removes everything from the renderer (event listeners, spritebatch, etc...) - * - * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. - * See: https://github.com/pixijs/pixi.js/issues/2233 - */ - destroy(removeView) - { - this.runners.destroy.run(); - - // call base destroy - super.destroy(removeView); - - // TODO nullify all the managers.. - this.gl = null; - } - - /** - * 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 - * builds and excluding plugins. - * @name PIXI.Renderer#plugins - * @type {object} - * @readonly - * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. - * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. - * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. - * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. - */ - - /** - * Adds a plugin to the renderer. - * - * @method - * @param {string} pluginName - The name of the plugin. - * @param {Function} ctor - The constructor function or class for the plugin. - */ - static registerPlugin(pluginName, ctor) - { - Renderer.__plugins = Renderer.__plugins || {}; - Renderer.__plugins[pluginName] = ctor; - } -} diff --git a/packages/core/src/renderers/State.js b/packages/core/src/renderers/State.js deleted file mode 100644 index fa3f221..0000000 --- a/packages/core/src/renderers/State.js +++ /dev/null @@ -1,163 +0,0 @@ -/* eslint-disable max-len */ - -const BLEND = 0; -const OFFSET = 1; -const CULLING = 2; -const DEPTH_TEST = 3; -const WINDING = 4; - -/** - * This is a webGL state. It is passed The WebGL StateManager. - * Each mesh renderered may require webGL to be in a different state. - * For example you may want different blend mode or to enable polygon offsets - * - * @class - * @memberof PIXI - */ -export default class State -{ - /** - * - */ - constructor() - { - this.data = 0; - - this.blendMode = 0; - this.polygonOffset = 0; - - this.blend = true; - // this.depthTest = true; - } - - /** - * Activates blending of the computed fragment color values - * - * @member {boolean} - */ - get blend() - { - return !!(this.data & (1 << BLEND)); - } - - set blend(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << BLEND)) !== value) - { - this.data ^= (1 << BLEND); - } - } - - /** - * Activates adding an offset to depth values of polygon's fragments - * - * @member {boolean} - * @default false - */ - get offsets() - { - return !!(this.data & (1 << OFFSET)); - } - - set offsets(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << OFFSET)) !== value) - { - this.data ^= (1 << OFFSET); - } - } - - /** - * Activates culling of polygons. - * - * @member {boolean} - * @default false - */ - get culling() - { - return !!(this.data & (1 << CULLING)); - } - - set culling(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << CULLING)) !== value) - { - this.data ^= (1 << CULLING); - } - } - - /** - * Activates depth comparisons and updates to the depth buffer. - * - * @member {boolean} - * @default false - */ - get depthTest() - { - return !!(this.data & (1 << DEPTH_TEST)); - } - - set depthTest(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << DEPTH_TEST)) !== value) - { - this.data ^= (1 << DEPTH_TEST); - } - } - - /** - * Specifies whether or not front or back-facing polygons can be culled. - * @member {boolean} - * @default false - */ - get clockwiseFrontFace() - { - return !!(this.data & (1 << WINDING)); - } - - set clockwiseFrontFace(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << WINDING)) !== value) - { - this.data ^= (1 << WINDING); - } - } - - /** - * The blend mode to be applied when this state is set. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. - * Setting this mode to anything other than NO_BLEND will automatically switch blending on. - * - * @member {boolean} - * @default PIXI.BLEND_MODES.NORMAL - * @see PIXI.BLEND_MODES - */ - get blendMode() - { - return this._blendMode; - } - - set blendMode(value) // eslint-disable-line require-jsdoc - { - // 17 is NO BLEND - this.blend = (value !== 17); - this._blendMode = value; - } - - /** - * The polygon offset. Setting this property to anything other than 0 will automatically enable poygon offset fill. - * - * @member {number} - * @default 0 - */ - get polygonOffset() - { - return this._polygonOffset; - } - - set polygonOffset(value) // eslint-disable-line require-jsdoc - { - this.offsets = !!value; - this._polygonOffset = value; - } -} - diff --git a/packages/core/src/renderers/filters/Filter.js b/packages/core/src/renderers/filters/Filter.js deleted file mode 100644 index 2073b8d..0000000 --- a/packages/core/src/renderers/filters/Filter.js +++ /dev/null @@ -1,140 +0,0 @@ -import Shader from '../../shader/Shader'; -import Program from '../../shader/Program'; -import State from '../State'; -import { settings } from '@pixi/settings'; -// import extractUniformsFromSrc from './extractUniformsFromSrc'; -import defaultVertex from './defaultFilter.vert'; -import defaultFragment from './defaultFilter.frag'; - -// let math = require('../../math'); -/** - * @class - * @memberof PIXI - * @extends PIXI.Shader - */ -export default class Filter extends Shader -{ - /** - * @param {string} [vertexSrc] - The source of the vertex shader. - * @param {string} [fragmentSrc] - The source of the fragment shader. - * @param {object} [uniforms] - Custom uniforms to use to augment the built-in ones. - */ - constructor(vertexSrc, fragmentSrc, uniforms) - { - const program = Program.from(vertexSrc, fragmentSrc); - - super(program, uniforms); - - /** - * The padding of the filter. Some filters require extra space to breath such as a blur. - * Increasing this will add extra width and height to the bounds of the object that the - * filter is applied to. - * - * @member {number} - */ - this.padding = 0; - - /** - * The resolution of the filter. Setting this to be lower will lower the quality but - * increase the performance of the filter. - * - * @member {number} - */ - this.resolution = settings.RESOLUTION; - - /** - * If enabled is true the filter is applied, if false it will not. - * - * @member {boolean} - */ - this.enabled = true; - - /** - * If enabled, PixiJS will fit the filter area into boundaries for better performance. - * Switch it off if it does not work for specific shader. - * - * @member {boolean} - */ - this.autoFit = true; - - /** - * Legacy filters use position and uvs from attributes - * @member {boolean} - * @readonly - */ - this.legacy = !!this.program.attributeData.aTextureCoord; - - /** - * the webGL state the filter requires to render - * @member {PIXI.State} - */ - this.state = new State(); - } - - /** - * Applies the filter - * - * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from - * @param {PIXI.RenderTarget} input - The input render target. - * @param {PIXI.RenderTarget} output - The target to output to. - * @param {boolean} clear - Should the output be cleared before rendering to it - * @param {object} [currentState] - It's current state of filter. - * There are some useful properties in the currentState : - * target, filters, sourceFrame, destinationFrame, renderTarget, resolution - */ - apply(filterManager, input, output, clear, currentState, derp) // eslint-disable-line no-unused-vars - { - // do as you please! - - filterManager.applyFilter(this, input, output, clear, currentState, derp); - - // or just do a regular render.. - } - - /** - * Sets the blendmode of the filter - * - * @member {number} - * @default PIXI.BLEND_MODES.NORMAL - */ - get blendMode() - { - return this.state.blendMode; - } - - set blendMode(value) // eslint-disable-line require-jsdoc - { - this.state.blendMode = value; - } - - /** - * The default vertex shader source - * - * @static - * @constant - */ - static get defaultVertexSrc() - { - return defaultVertex; - } - - /** - * The default fragment shader source - * - * @static - * @constant - */ - static get defaultFragmentSrc() - { - return defaultFragment; - } -} - -/** - * Used for caching shader IDs - * - * @static - * @private - */ -Filter.SOURCE_KEY_MAP = {}; - diff --git a/packages/core/src/renderers/filters/defaultFilter.frag b/packages/core/src/renderers/filters/defaultFilter.frag deleted file mode 100644 index a14c1be..0000000 --- a/packages/core/src/renderers/filters/defaultFilter.frag +++ /dev/null @@ -1,22 +0,0 @@ -varying vec2 vTextureCoord; -varying vec2 vFilterCoord; - -uniform sampler2D uSampler; -uniform sampler2D filterSampler; - -void main(void){ - vec4 masky = texture2D(filterSampler, vFilterCoord); - vec4 sample = texture2D(uSampler, vTextureCoord); - vec4 color; - if(mod(vFilterCoord.x, 1.0) > 0.5) - { - color = vec4(1.0, 0.0, 0.0, 1.0); - } - else - { - color = vec4(0.0, 1.0, 0.0, 1.0); - } - // gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0); - gl_FragColor = mix(sample, masky, 0.5); - gl_FragColor *= sample.a; -} diff --git a/packages/core/src/renderers/filters/defaultFilter.vert b/packages/core/src/renderers/filters/defaultFilter.vert deleted file mode 100644 index e1febca..0000000 --- a/packages/core/src/renderers/filters/defaultFilter.vert +++ /dev/null @@ -1,14 +0,0 @@ -attribute vec2 aVertexPosition; -attribute vec2 aTextureCoord; - -uniform mat3 projectionMatrix; -uniform mat3 filterMatrix; - -varying vec2 vTextureCoord; -varying vec2 vFilterCoord; - -void main(void){ - gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); - vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy; - vTextureCoord = aTextureCoord ; -} diff --git a/packages/core/src/renderers/filters/filterTransforms.js b/packages/core/src/renderers/filters/filterTransforms.js deleted file mode 100644 index 87a3cd6..0000000 --- a/packages/core/src/renderers/filters/filterTransforms.js +++ /dev/null @@ -1,50 +0,0 @@ -import { Matrix } from '@pixi/math'; - -/** - * Calculates the mapped matrix - * @param filterArea {Rectangle} The filter area - * @param sprite {Sprite} the target sprite - * @param outputMatrix {Matrix} @alvin - * @private - */ -// this returns a matrix that will normalise map filter cords in the filter to screen space -export function calculateScreenSpaceMatrix(outputMatrix, filterArea, textureSize) -{ - // TODO unwrap? - const mappedMatrix = outputMatrix.identity(); - - mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); - - mappedMatrix.scale(textureSize.width, textureSize.height); - - return mappedMatrix; -} - -export function calculateNormalizedScreenSpaceMatrix(outputMatrix, filterArea, textureSize) -{ - const mappedMatrix = outputMatrix.identity(); - - mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); - - const translateScaleX = (textureSize.width / filterArea.width); - const translateScaleY = (textureSize.height / filterArea.height); - - mappedMatrix.scale(translateScaleX, translateScaleY); - - return mappedMatrix; -} - -// this will map the filter coord so that a texture can be used based on the transform of a sprite -export function calculateSpriteMatrix(outputMatrix, filterArea, textureSize, sprite) -{ - const orig = sprite._texture.orig; - const mappedMatrix = outputMatrix.set(textureSize.width, 0, 0, textureSize.height, filterArea.x, filterArea.y); - const worldTransform = sprite.worldTransform.copyTo(Matrix.TEMP_MATRIX); - - worldTransform.invert(); - mappedMatrix.prepend(worldTransform); - mappedMatrix.scale(1.0 / orig.width, 1.0 / orig.height); - mappedMatrix.translate(sprite.anchor.x, sprite.anchor.y); - - return mappedMatrix; -} diff --git a/packages/core/src/renderers/filters/spriteMask/SpriteMaskFilter.js b/packages/core/src/renderers/filters/spriteMask/SpriteMaskFilter.js deleted file mode 100644 index d5a91a4..0000000 --- a/packages/core/src/renderers/filters/spriteMask/SpriteMaskFilter.js +++ /dev/null @@ -1,63 +0,0 @@ -import Filter from '../Filter'; -import { Matrix } from '@pixi/math'; -import vertex from './spriteMaskFilter.vert'; -import fragment from './spriteMaskFilter.frag'; -import { default as TextureMatrix } from '../../../textures/TextureMatrix'; - -/** - * The SpriteMaskFilter class - * - * @class - * @extends PIXI.Filter - * @memberof PIXI - */ -export default class SpriteMaskFilter extends Filter -{ - /** - * @param {PIXI.Sprite} sprite - the target sprite - */ - constructor(sprite) - { - const maskMatrix = new Matrix(); - - super(vertex, fragment); - - sprite.renderable = false; - - this.maskSprite = sprite; - this.maskMatrix = maskMatrix; - } - - /** - * Applies the filter - * - * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from - * @param {PIXI.RenderTarget} input - The input render target. - * @param {PIXI.RenderTarget} output - The target to output to. - */ - apply(filterManager, input, output) - { - const maskSprite = this.maskSprite; - const tex = this.maskSprite.texture; - - if (!tex.valid) - { - return; - } - if (!tex.transform) - { - // margin = 0.0, let it bleed a bit, shader code becomes easier - // assuming that atlas textures were made with 1-pixel padding - tex.transform = new TextureMatrix(tex, 0.0); - } - tex.transform.update(); - - this.uniforms.mask = tex; - this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite) - .prepend(tex.transform.mapCoord); - this.uniforms.alpha = maskSprite.worldAlpha; - this.uniforms.maskClamp = tex.transform.uClampFrame; - - filterManager.applyFilter(this, input, output); - } -} diff --git a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.frag b/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.frag deleted file mode 100644 index 0e4aef8..0000000 --- a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.frag +++ /dev/null @@ -1,23 +0,0 @@ -varying vec2 vMaskCoord; -varying vec2 vTextureCoord; - -uniform sampler2D uSampler; -uniform sampler2D mask; -uniform float alpha; -uniform vec4 maskClamp; - -void main(void) -{ - float clip = step(3.5, - step(maskClamp.x, vMaskCoord.x) + - step(maskClamp.y, vMaskCoord.y) + - step(vMaskCoord.x, maskClamp.z) + - step(vMaskCoord.y, maskClamp.w)); - - vec4 original = texture2D(uSampler, vTextureCoord); - vec4 masky = texture2D(mask, vMaskCoord); - - original *= (masky.r * masky.a * alpha * clip); - - gl_FragColor = original; -} diff --git a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.vert b/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.vert deleted file mode 100644 index d23a30c..0000000 --- a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.vert +++ /dev/null @@ -1,16 +0,0 @@ -attribute vec2 aVertexPosition; -attribute vec2 aTextureCoord; - -uniform mat3 projectionMatrix; -uniform mat3 otherMatrix; - -varying vec2 vMaskCoord; -varying vec2 vTextureCoord; - -void main(void) -{ - gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); - - vTextureCoord = aTextureCoord; - vMaskCoord = ( otherMatrix * vec3( aTextureCoord, 1.0) ).xy; -} diff --git a/packages/core/src/renderers/systems/BatchSystem.js b/packages/core/src/renderers/systems/BatchSystem.js deleted file mode 100644 index 70e9518..0000000 --- a/packages/core/src/renderers/systems/BatchSystem.js +++ /dev/null @@ -1,68 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import ObjectRenderer from '../utils/ObjectRenderer'; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ - -export default class BatchSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this manager works for. - */ - constructor(renderer) - { - super(renderer); - - /** - * An empty renderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.emptyRenderer = new ObjectRenderer(renderer); - - /** - * The currently active ObjectRenderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.currentRenderer = this.emptyRenderer; - } - - /** - * Changes the current renderer to the one given in parameter - * - * @param {PIXI.ObjectRenderer} objectRenderer - The object renderer to use. - */ - setObjectRenderer(objectRenderer) - { - if (this.currentRenderer === objectRenderer) - { - return; - } - - this.currentRenderer.stop(); - this.currentRenderer = objectRenderer; - - this.renderer.state.setState(objectRenderer.state); - - this.currentRenderer.start(); - } - - /** - * This should be called if you wish to do some custom rendering - * It will basically render anything that may be batched up such as sprites - * - */ - flush() - { - this.setObjectRenderer(this.emptyRenderer); - } - - reset() - { - this.setObjectRenderer(this.emptyRenderer); - } -} diff --git a/packages/core/src/renderers/systems/ContextSystem.js b/packages/core/src/renderers/systems/ContextSystem.js deleted file mode 100644 index 43bf827..0000000 --- a/packages/core/src/renderers/systems/ContextSystem.js +++ /dev/null @@ -1,186 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { settings } from '@pixi/settings'; - -let CONTEXT_UID = 0; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class ContextSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this.webGLVersion = 1; - - this.handleContextLost = this.handleContextLost.bind(this); - this.handleContextRestored = this.handleContextRestored.bind(this); - - this.extensions = {}; - - renderer.view.addEventListener('webglcontextlost', this.handleContextLost, false); - renderer.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); - } - - get isLost() - { - return (!this.gl || this.gl.isContextLost()); - } - - contextChange(gl) - { - this.gl = gl; - - // restore a context if it was previously lost - if (gl.isContextLost() && gl.getExtension('WEBGL_lose_context')) - { - gl.getExtension('WEBGL_lose_context').restoreContext(); - } - } - - initFromContext(gl) - { - this.gl = gl; - this.validateContext(gl); - this.renderer.gl = gl; - this.renderer.CONTEXT_UID = CONTEXT_UID++; - this.renderer.runners.contextChange.run(gl); - } - - initFromOptions(options) - { - const gl = this.createContext(this.renderer.view, options); - - this.initFromContext(gl); - } - - /** - * Helper class to create a webGL Context - * - * @class - * @memberof PIXI.glCore - * @param canvas {HTMLCanvasElement} the canvas element that we will get the context from - * @param options {Object} An options object that gets passed in to the canvas element containing the context attributes, - * see https://developer.mozilla.org/en/docs/Web/API/HTMLCanvasElement/getContext for the options available - * @return {WebGLRenderingContext} the WebGL context - */ - createContext(canvas, options) - { - let gl; - - if (settings.PREFER_WEBGL_2) - { - gl = canvas.getContext('webgl2', options); - } - - if (gl) - { - this.webGLVersion = 2; - } - else - { - this.webGLVersion = 1; - - gl = canvas.getContext('webgl', options) - || canvas.getContext('experimental-webgl', options); - - if (!gl) - { - // fail, not able to get a context - throw new Error('This browser does not support webGL. Try using the canvas renderer'); - } - } - - this.gl = gl; - - this.getExtensions(); - - return gl; - } - - getExtensions() - { - // time to set up default etensions that pixi uses.. - const gl = this.gl; - const extensions = this.extensions; - - if (this.webGLVersion === 1) - { - extensions.drawBuffers = gl.getExtension('WEBGL_draw_buffers'); - extensions.depthTexture = gl.getExtension('WEBKIT_WEBGL_depth_texture'); - extensions.floatTexture = gl.getExtension('OES_texture_float'); - extensions.loseContext = gl.getExtension('WEBGL_lose_context'); - - extensions.vertexArrayObject = gl.getExtension('OES_vertex_array_object') - || gl.getExtension('MOZ_OES_vertex_array_object') - || gl.getExtension('WEBKIT_OES_vertex_array_object'); - } - - // we don't use any specific WebGL 2 ones yet! - } - - /** - * Handles a lost webgl context - * - * @private - * @param {WebGLContextEvent} event - The context lost event. - */ - handleContextLost(event) - { - event.preventDefault(); - } - - /** - * Handles a restored webgl context - * - * @private - */ - handleContextRestored() - { - this.renderer.runners.contextChange.run(this.gl); - } - - destroy() - { - const view = this.renderer.view; - - // remove listeners - view.removeEventListener('webglcontextlost', this.handleContextLost); - view.removeEventListener('webglcontextrestored', this.handleContextRestored); - - this.gl.useProgram(null); - - if (this.extensions.loseContext) - { - this.extensions.loseContext.loseContext(); - } - } - - postrender() - { - this.gl.flush(); - } - - validateContext(gl) - { - const attributes = gl.getContextAttributes(); - - // this is going to be fairly simple for now.. but at least we have room to grow! - if (!attributes.stencil) - { - /* eslint-disable max-len */ - - /* eslint-disable no-console */ - console.warn('Provided WebGL context does not have a stencil buffer, masks may not render correctly'); - /* eslint-enable no-console */ - - /* eslint-enable max-len */ - } - } -} diff --git a/packages/core/src/renderers/systems/FramebufferSystem.js b/packages/core/src/renderers/systems/FramebufferSystem.js deleted file mode 100644 index df7902b..0000000 --- a/packages/core/src/renderers/systems/FramebufferSystem.js +++ /dev/null @@ -1,246 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { Rectangle } from '@pixi/math'; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class FramebufferSystem extends WebGLSystem -{ - /** - * Sets up the renderer context and necessary buffers. - * - * @private - */ - contextChange() - { - this.gl = this.renderer.gl; - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - this.current = null; - this.viewport = new Rectangle(); - - this.drawBufferExtension = this.renderer.context.extensions.drawBuffers; - } - - bind(framebuffer, frame) - { - const gl = this.gl; - - this.current = framebuffer; - - if (framebuffer) - { - // TODO cacheing layer! - - const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID] || this.initFramebuffer(framebuffer); - - gl.bindFramebuffer(gl.FRAMEBUFFER, fbo.framebuffer); - // makesure all textures are unbound.. - - // now check for updates... - if (fbo.dirtyId !== framebuffer.dirtyId) - { - fbo.dirtyId = framebuffer.dirtyId; - - if (fbo.dirtyFormat !== framebuffer.dirtyFormat) - { - fbo.dirtyFormat = framebuffer.dirtyFormat; - this.updateFramebuffer(framebuffer); - } - else if (fbo.dirtySize !== framebuffer.dirtySize) - { - fbo.dirtySize = framebuffer.dirtySize; - this.resizeFramebuffer(framebuffer); - } - } - - for (let i = 0; i < framebuffer.colorTextures.length; i++) - { - if (framebuffer.colorTextures[i].texturePart) - { - this.renderer.texture.unbind(framebuffer.colorTextures[i].texture); - } - else - { - this.renderer.texture.unbind(framebuffer.colorTextures[i]); - } - } - - if (framebuffer.depthTexture) - { - this.renderer.texture.unbind(framebuffer.depthTexture); - } - - if (frame) - { - this.setViewport(frame.x, frame.y, frame.width, frame.height); - } - else - { - this.setViewport(0, 0, framebuffer.width, framebuffer.height); - } - } - else - { - gl.bindFramebuffer(gl.FRAMEBUFFER, null); - - if (frame) - { - this.setViewport(frame.x, frame.y, frame.width, frame.height); - } - else - { - this.setViewport(0, 0, this.renderer.width, this.renderer.height); - } - } - } - - setViewport(x, y, width, height) - { - const v = this.viewport; - - if (v.width !== width || v.height !== height || v.x !== x || v.y !== y) - { - v.x = x; - v.y = y; - v.width = width; - v.height = height; - - this.gl.viewport(x, y, width, height); - } - } - - get size() - { - if (this.current) - { - // TODO store temp - return { x: 0, y: 0, width: this.current.width, height: this.current.height }; - } - - return { x: 0, y: 0, width: this.renderer.width, height: this.renderer.height }; - } - - clear(r, g, b, a) - { - const gl = this.gl; - - // TODO clear color can be set only one right? - gl.clearColor(r, g, b, a); - gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); - } - - // private functions... - - initFramebuffer(framebuffer) - { - const gl = this.gl; - - // TODO - make this a class? - const fbo = { - framebuffer: gl.createFramebuffer(), - stencil: null, - dirtyId: 0, - dirtyFormat: 0, - dirtySize: 0, - }; - - framebuffer.glFrameBuffers[this.CONTEXT_UID] = fbo; - - return fbo; - } - - resizeFramebuffer(framebuffer) - { - const gl = this.gl; - - if (framebuffer.stencil || framebuffer.depth) - { - gl.bindRenderbuffer(gl.RENDERBUFFER, this.stencil); - gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); - } - } - - updateFramebuffer(framebuffer) - { - const gl = this.gl; - - const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID]; - - // bind the color texture - const colorTextures = framebuffer.colorTextures; - - let count = colorTextures.length; - - if (!this.drawBufferExtension) - { - count = Math.min(count, 1); - } - - const activeTextures = []; - - for (let i = 0; i < count; i++) - { - const texture = framebuffer.colorTextures[i]; - - if (texture.texturePart) - { - this.renderer.texture.bind(texture.texture, 0); - - gl.framebufferTexture2D(gl.FRAMEBUFFER, - gl.COLOR_ATTACHMENT0 + i, - gl.TEXTURE_CUBE_MAP_NEGATIVE_X + texture.side, - texture.texture._glTextures[this.CONTEXT_UID].texture, - 0); - } - else - { - this.renderer.texture.bind(texture, 0); - - gl.framebufferTexture2D(gl.FRAMEBUFFER, - gl.COLOR_ATTACHMENT0 + i, - gl.TEXTURE_2D, - texture._glTextures[this.CONTEXT_UID].texture, - 0); - } - - activeTextures.push(gl.COLOR_ATTACHMENT0 + i); - } - - if (this.drawBufferExtension && activeTextures.length > 1) - { - this.drawBufferExtension.drawBuffersWEBGL(activeTextures); - } - - if (framebuffer.depthTexture) - { - const depthTextureExt = this.renderer.context.extensions.depthTexture; - - if (depthTextureExt) - { - const depthTexture = framebuffer.depthTexture; - - this.renderer.texture.bind(depthTexture, 0); - - gl.framebufferTexture2D(gl.FRAMEBUFFER, - gl.DEPTH_ATTACHMENT, - gl.TEXTURE_2D, - depthTexture._glTextures[this.CONTEXT_UID].texture, - 0); - } - } - - if (framebuffer.stencil || framebuffer.depth) - { - fbo.stencil = gl.createRenderbuffer(); - - gl.bindRenderbuffer(gl.RENDERBUFFER, fbo.stencil); - - // TODO.. this is depth AND stencil? - gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, fbo.stencil); - gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); - // fbo.enableStencil(); - } - } -} diff --git a/packages/core/src/renderers/systems/MaskSystem.js b/packages/core/src/renderers/systems/MaskSystem.js deleted file mode 100644 index d9c4b1a..0000000 --- a/packages/core/src/renderers/systems/MaskSystem.js +++ /dev/null @@ -1,199 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import AlphaMaskFilter from '../filters/spriteMask/SpriteMaskFilter'; - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class MaskSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - // TODO - we don't need both! - this.scissor = false; - this.scissorData = null; - this.scissorRenderTarget = null; - - this.enableScissor = false; - - this.alphaMaskPool = []; - this.alphaMaskIndex = 0; - } - - /** - * Applies the Mask and adds it to the current filter stack. - * - * @param {PIXI.DisplayObject} target - Display Object to push the mask to - * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. - */ - push(target, maskData) - { - // TODO the root check means scissor rect will not - // be used on render textures more info here: - // https://github.com/pixijs/pixi.js/pull/3545 - - if (maskData.texture) - { - this.pushSpriteMask(target, maskData); - } - else if (this.enableScissor - && !this.scissor - && this.renderer._activeRenderTarget.root - && !this.renderer.stencil.stencilMaskStack.length - && maskData.isFastRect()) - { - const matrix = maskData.worldTransform; - - let rot = Math.atan2(matrix.b, matrix.a); - - // use the nearest degree! - rot = Math.round(rot * (180 / Math.PI)); - - if (rot % 90) - { - this.pushStencilMask(maskData); - } - else - { - this.pushScissorMask(target, maskData); - } - } - else - { - this.pushStencilMask(maskData); - } - } - - /** - * Removes the last mask from the mask stack and doesn't return it. - * - * @param {PIXI.DisplayObject} target - Display Object to pop the mask from - * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. - */ - pop(target, maskData) - { - if (maskData.texture) - { - this.popSpriteMask(target, maskData); - } - else if (this.enableScissor && !this.renderer.stencil.stencilMaskStack.length) - { - this.popScissorMask(target, maskData); - } - else - { - this.popStencilMask(target, maskData); - } - } - - /** - * Applies the Mask and adds it to the current filter stack. - * - * @param {PIXI.RenderTarget} target - Display Object to push the sprite mask to - * @param {PIXI.Sprite} maskData - Sprite to be used as the mask - */ - pushSpriteMask(target, maskData) - { - let alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; - - if (!alphaMaskFilter) - { - alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; - } - - alphaMaskFilter[0].resolution = this.renderer.resolution; - alphaMaskFilter[0].maskSprite = maskData; - - // TODO - may cause issues! - target.filterArea = maskData.getBounds(true); - - this.renderer.filterSystem.pushFilter(target, alphaMaskFilter); - - this.alphaMaskIndex++; - } - - /** - * Removes the last filter from the filter stack and doesn't return it. - * - */ - popSpriteMask() - { - this.renderer.filterSystem.popFilter(); - this.alphaMaskIndex--; - } - - /** - * Applies the Mask and adds it to the current filter stack. - * - * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. - */ - pushStencilMask(maskData) - { - this.renderer.batch.flush(); - this.renderer.stencil.pushStencil(maskData); - } - - /** - * Removes the last filter from the filter stack and doesn't return it. - * - */ - popStencilMask() - { - // this.renderer.currentRenderer.stop(); - this.renderer.stencil.popStencil(); - } - - /** - * - * @param {PIXI.DisplayObject} target - Display Object to push the mask to - * @param {PIXI.Graphics} maskData - The masking data. - */ - pushScissorMask(target, maskData) - { - maskData.renderable = true; - - const renderTarget = this.renderer._activeRenderTarget; - - const bounds = maskData.getBounds(); - - bounds.fit(renderTarget.size); - maskData.renderable = false; - - this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); - - const resolution = this.renderer.resolution; - - this.renderer.gl.scissor( - bounds.x * resolution, - (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, - bounds.width * resolution, - bounds.height * resolution - ); - - this.scissorRenderTarget = renderTarget; - this.scissorData = maskData; - this.scissor = true; - } - - /** - * - * - */ - popScissorMask() - { - this.scissorRenderTarget = null; - this.scissorData = null; - this.scissor = false; - - // must be scissor! - const gl = this.renderer.gl; - - gl.disable(gl.SCISSOR_TEST); - } -} diff --git a/packages/core/src/renderers/systems/ProjectionSystem.js b/packages/core/src/renderers/systems/ProjectionSystem.js deleted file mode 100644 index ec5efdb..0000000 --- a/packages/core/src/renderers/systems/ProjectionSystem.js +++ /dev/null @@ -1,73 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { Matrix } from '@pixi/math'; - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ - -export default class ProjectionSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this.projectionMatrix = new Matrix(); - } - - update(destinationFrame, sourceFrame, resolution, root) - { - this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; - this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; - - this.calculateProjection(this.destinationFrame, this.sourceFrame, resolution, root); - - this.renderer.globalUniforms.uniforms.projectionMatrix = this.projectionMatrix; - this.renderer.globalUniforms.update(); - } - - /** - * Updates the projection matrix based on a projection frame (which is a rectangle) - * - * @param {Rectangle} destinationFrame - The destination frame. - * @param {Rectangle} sourceFrame - The source frame. - */ - calculateProjection(destinationFrame, sourceFrame, resolution, root) - { - const pm = this.projectionMatrix; - - // I don't think we will need this line.. - // pm.identity(); - - if (!root) - { - pm.a = (1 / destinationFrame.width * 2) * resolution; - pm.d = (1 / destinationFrame.height * 2) * resolution; - - pm.tx = -1 - (sourceFrame.x * pm.a); - pm.ty = -1 - (sourceFrame.y * pm.d); - } - else - { - pm.a = (1 / destinationFrame.width * 2) * resolution; - pm.d = (-1 / destinationFrame.height * 2) * resolution; - - pm.tx = -1 - (sourceFrame.x * pm.a); - pm.ty = 1 - (sourceFrame.y * pm.d); - } - } - - /** - * Sets the transform of the active render target to the given matrix - * - * @param {PIXI.Matrix} matrix - The transformation matrix - */ - setTransform()// matrix) - { - // this._activeRenderTarget.transform = matrix; - } -} diff --git a/packages/core/src/renderers/systems/RenderTextureSystem.js b/packages/core/src/renderers/systems/RenderTextureSystem.js deleted file mode 100644 index a133ef1..0000000 --- a/packages/core/src/renderers/systems/RenderTextureSystem.js +++ /dev/null @@ -1,115 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { Rectangle } from '@pixi/math'; - -const tempRect = new Rectangle(); - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ - -export default class RenderTextureSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this.clearColor = renderer._backgroundColorRgba; - - // TODO moe this property somewhere else! - this.defaultMaskStack = []; - this.defaultFilterStack = [{}]; - - // empty render texture? - this.renderTexture = null; - - this.destinationFrame = new Rectangle(); - } - - bind(renderTexture, sourceFrame, destinationFrame) - { - // TODO - do we want this?? - if (this.renderTexture === renderTexture) return; - this.renderTexture = renderTexture; - - const renderer = this.renderer; - - if (renderTexture) - { - const baseTexture = renderTexture.baseTexture; - - if (!destinationFrame) - { - tempRect.width = baseTexture.realWidth; - tempRect.height = baseTexture.realHeight; - - destinationFrame = tempRect; - } - - if (!sourceFrame) - { - sourceFrame = destinationFrame; - } - - this.renderer.framebuffer.bind(baseTexture.frameBuffer, destinationFrame); - - this.renderer.projection.update(destinationFrame, sourceFrame, baseTexture.resolution, false); - this.renderer.stencil.setMaskStack(baseTexture.stencilMaskStack); - } - else - { - // TODO these validation checks happen deeper down.. - // thing they can be avoided.. - if (!destinationFrame) - { - tempRect.width = renderer.width; - tempRect.height = renderer.height; - - destinationFrame = tempRect; - } - - if (!sourceFrame) - { - sourceFrame = destinationFrame; - } - - renderer.framebuffer.bind(null, destinationFrame); - - // TODO store this.. - this.renderer.projection.update(destinationFrame, sourceFrame, this.renderer.resolution, true); - this.renderer.stencil.setMaskStack(this.defaultMaskStack); - } - - this.destinationFrame.copyFrom(destinationFrame); - } - - /** - * Erases the render texture and fills the drawing area with a colour - * - * @param {number} [clearColor] - The colour - * @return {PIXI.Renderer} Returns itself. - */ - clear(clearColor) - { - if (this.renderTexture) - { - clearColor = clearColor || this.renderTexture.baseTexture.clearColor; - } - else - { - clearColor = clearColor || this.clearColor; - } - - this.renderer.framebuffer.clear(clearColor[0], clearColor[1], clearColor[2], clearColor[3]); - } - - resize()// screenWidth, screenHeight) - { - // resize the root only! - this.bind(null); - } -} diff --git a/packages/core/src/renderers/systems/StateSystem.js b/packages/core/src/renderers/systems/StateSystem.js deleted file mode 100755 index d7f4caf..0000000 --- a/packages/core/src/renderers/systems/StateSystem.js +++ /dev/null @@ -1,288 +0,0 @@ -import mapWebGLBlendModesToPixi from '../utils/mapWebGLBlendModesToPixi'; -import WebGLSystem from './WebGLSystem'; -import WebGLState from '../State'; - -const BLEND = 0; -const OFFSET = 1; -const CULLING = 2; -const DEPTH_TEST = 3; -const WINDING = 4; - -/** - * A WebGL state machines - * - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class StateSystem extends WebGLSystem -{ - /** - * @param {WebGLRenderingContext} gl - The current WebGL rendering context - */ - constructor(renderer) - { - super(renderer); - - this.gl = null; - - this.maxAttribs = null; - - // check we have vao.. - this.nativeVaoExtension = null; - - this.attribState = null; - - this.stateId = 0; - this.polygonOffset = 0; - this.blendMode = 17; - - this.map = []; - - // map functions for when we set state.. - this.map[BLEND] = this.setBlend; - this.map[OFFSET] = this.setOffset; - this.map[CULLING] = this.setCullFace; - this.map[DEPTH_TEST] = this.setDepthTest; - this.map[WINDING] = this.setFrontFace; - - this.checks = []; - - this.defaultState = new WebGLState(); - this.defaultState.blend = true; - this.defaultState.depth = true; - } - - contextChange(gl) - { - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; - - this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); - - // check we have vao.. - this.nativeVaoExtension = ( - gl.getExtension('OES_vertex_array_object') - || gl.getExtension('MOZ_OES_vertex_array_object') - || gl.getExtension('WEBKIT_OES_vertex_array_object') - ); - - this.attribState = { - tempAttribState: new Array(this.maxAttribs), - attribState: new Array(this.maxAttribs), - }; - - this.blendModes = mapWebGLBlendModesToPixi(gl); - - this.setState(this.defaultState); - - this.reset(); - } - - /** - * Sets the current state - * - * @param {*} state - The state to set. - */ - setState(state) - { - state = state || this.defaultState; - - // TODO maybe to an object check? ( this.state === state )? - if (this.stateId !== state.data) - { - let diff = this.stateId ^ state.data; - let i = 0; - - // order from least to most common - while (diff) - { - if (diff & 1) - { - // state change! - this.map[i].call(this, !!(state.data & (1 << i))); - } - - diff = diff >> 1; - i++; - } - - this.stateId = state.data; - } - - // based on the above settings we check for specific modes.. - // for example if blend is active we check and set the blend modes - // or of polygon offset is active we check the poly depth. - for (let i = 0; i < this.checks.length; i++) - { - this.checks[i](this, state); - } - } - - /** - * Enables or disabled blending. - * - * @param {boolean} value - Turn on or off webgl blending. - */ - setBlend(value) - { - this.updateCheck(StateSystem.checkBlendMode, value); - - this.gl[value ? 'enable' : 'disable'](this.gl.BLEND); - } - - /** - * Enables or disable polygon offset fill - * - * @param {boolean} value - Turn on or off webgl polygon offset testing. - */ - setOffset(value) - { - this.gl[value ? 'enable' : 'disable'](this.gl.POLYGON_OFFSET_FILL); - } - - /** - * Sets whether to enable or disable depth test. - * - * @param {boolean} value - Turn on or off webgl depth testing. - */ - setDepthTest(value) - { - this.gl[value ? 'enable' : 'disable'](this.gl.DEPTH_TEST); - } - - /** - * Sets whether to enable or disable cull face. - * - * @param {boolean} value - Turn on or off webgl cull face. - */ - setCullFace(value) - { - this.gl[value ? 'enable' : 'disable'](this.gl.CULL_FACE); - } - - /** - * Sets the gl front face. - * - * @param {boolean} value - true is clockwise and false is counter-clockwise - */ - setFrontFace(value) - { - this.gl.frontFace(this.gl[value ? 'CW' : 'CCW']); - } - - /** - * Sets the blend mode. - * - * @param {number} value - The blend mode to set to. - */ - setBlendMode(value) - { - if (value === this.blendMode) - { - return; - } - - this.blendMode = value; - this.gl.blendFunc(this.blendModes[value][0], this.blendModes[value][1]); - } - - /** - * Sets the polygon offset. - * - * @param {number} value - the polygon offset - * @param {number} scale - the polygon offset scale - */ - setPolygonOffset(value, scale) - { - this.gl.polygonOffset(value, scale); - } - - /** - * Disables all the vaos in use - * - */ - resetAttributes() - { - for (let i = 0; i < this.attribState.tempAttribState.length; i++) - { - this.attribState.tempAttribState[i] = 0; - } - - for (let i = 0; i < this.attribState.attribState.length; i++) - { - this.attribState.attribState[i] = 0; - } - - // im going to assume one is always active for performance reasons. - for (let i = 1; i < this.maxAttribs; i++) - { - this.gl.disableVertexAttribArray(i); - } - } - - // used - /** - * Resets all the logic and disables the vaos - */ - reset() - { - // unbind any VAO if they exist.. - if (this.nativeVaoExtension) - { - this.nativeVaoExtension.bindVertexArrayOES(null); - } - - // reset all attributes.. - this.resetAttributes(); - - this.gl.pixelStorei(this.gl.UNPACK_FLIP_Y_WEBGL, false); - - this.setBlendMode(0); - - // TODO? - // this.setState(this.defaultState); - } - - /** - * checks to see which updates should be checked based on which settings have been activated - * for example if blend is enabled then we shold check the blend modes each time the state is changed - * or if poygon fill is activated then we need to check if the polygone offset changes. - * The idea is that we only check what we have too. - * - * @param {Function} func the checking function to add or remove - * @param {boolean} value should the check function be added or removed. - */ - updateCheck(func, value) - { - const index = this.checks.indexOf(func); - - if (value && index === -1) - { - this.checks.push(func); - } - else if (!value && index !== -1) - { - this.checks.splice(index, 1); - } - } - - /** - * A private little wrapper function that we call to check the blend mode. - * - * @static - * @private - * @param {PIXI.StateSystem} System the System to perform the state check on - * @param {PIXI.State} state the state that the blendMode will pulled from - */ - static checkBlendMode(system, state) - { - system.setBlendMode(state.blendMode); - } - - // TODO - add polygon offset? -} diff --git a/packages/core/src/renderers/systems/StencilSystem.js b/packages/core/src/renderers/systems/StencilSystem.js deleted file mode 100644 index 5d538cb..0000000 --- a/packages/core/src/renderers/systems/StencilSystem.js +++ /dev/null @@ -1,133 +0,0 @@ -import WebGLSystem from './WebGLSystem'; - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class StencilSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - this.stencilMaskStack = []; - } - - /** - * Changes the mask stack that is used by this System. - * - * @param {PIXI.Graphics[]} stencilMaskStack - The mask stack - */ - setMaskStack(stencilMaskStack) - { - const gl = this.renderer.gl; - - if (stencilMaskStack.length !== this.stencilMaskStack.length) - { - if (stencilMaskStack.length === 0) - { - gl.disable(gl.STENCIL_TEST); - } - else - { - gl.enable(gl.STENCIL_TEST); - } - } - - this.stencilMaskStack = stencilMaskStack; - } - - /** - * Applies the Mask and adds it to the current stencil stack. @alvin - * - * @param {PIXI.Graphics} graphics - The mask - */ - pushStencil(graphics) - { - this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); - - // this.renderer._activeRenderTarget.attachStencilBuffer(); - - const gl = this.renderer.gl; - const prevMaskCount = this.stencilMaskStack.length; - - if (prevMaskCount === 0) - { - gl.enable(gl.STENCIL_TEST); - } - - this.stencilMaskStack.push(graphics); - - // Increment the refference stencil value where the new mask overlaps with the old ones. - gl.colorMask(false, false, false, false); - gl.stencilFunc(gl.EQUAL, prevMaskCount, this._getBitwiseMask()); - gl.stencilOp(gl.KEEP, gl.KEEP, gl.INCR); - this.renderer.plugins.graphics.render(graphics); - - this._useCurrent(); - } - - /** - * Removes the last mask from the stencil stack. @alvin - */ - popStencil() - { - this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); - - const gl = this.renderer.gl; - const graphics = this.stencilMaskStack.pop(); - - if (this.stencilMaskStack.length === 0) - { - // the stack is empty! - gl.disable(gl.STENCIL_TEST); - gl.clear(gl.STENCIL_BUFFER_BIT); - gl.clearStencil(0); - } - else - { - // Decrement the refference stencil value where the popped mask overlaps with the other ones - gl.colorMask(false, false, false, false); - gl.stencilOp(gl.KEEP, gl.KEEP, gl.DECR); - this.renderer.plugins.graphics.render(graphics); - - this._useCurrent(); - } - } - - /** - * Setup renderer to use the current stencil data. - */ - _useCurrent() - { - const gl = this.renderer.gl; - - gl.colorMask(true, true, true, true); - gl.stencilFunc(gl.EQUAL, this.stencilMaskStack.length, this._getBitwiseMask()); - gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP); - } - - /** - * Fill 1s equal to the number of acitve stencil masks. - * - * @return {number} The bitwise mask. - */ - _getBitwiseMask() - { - return (1 << this.stencilMaskStack.length) - 1; - } - - /** - * Destroys the mask stack. - * - */ - destroy() - { - super.destroy(this); - - this.stencilMaskStack = null; - } -} diff --git a/packages/core/src/renderers/systems/WebGLSystem.js b/packages/core/src/renderers/systems/WebGLSystem.js deleted file mode 100644 index d3d5b31..0000000 --- a/packages/core/src/renderers/systems/WebGLSystem.js +++ /dev/null @@ -1,41 +0,0 @@ -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this manager works for. - */ - constructor(renderer) - { - /** - * The renderer this manager works for. - * - * @member {PIXI.Renderer} - */ - this.renderer = renderer; - - this.renderer.runners.contextChange.add(this); - } - - /** - * Generic method called when there is a WebGL context change. - * - */ - contextChange() - { - // do some codes init! - } - - /** - * Generic destroy methods to be overridden by the subclass - * - */ - destroy() - { - this.renderer.runners.contextChange.remove(this); - this.renderer = null; - } -} diff --git a/packages/core/src/renderers/systems/filter/FilterSystem.js b/packages/core/src/renderers/systems/filter/FilterSystem.js deleted file mode 100644 index e615037..0000000 --- a/packages/core/src/renderers/systems/filter/FilterSystem.js +++ /dev/null @@ -1,431 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; - -import RenderTexture from '../../../textures/RenderTexture'; -import Quad from '../../utils/Quad'; -import QuadUv from '../../utils/QuadUv'; -import { Rectangle } from '@pixi/math'; -import * as filterTransforms from '../../filters/filterTransforms'; -import bitTwiddle from 'bit-twiddle'; -import UniformGroup from '../../../shader/UniformGroup'; -import { DRAW_MODES } from '../../../../../constants'; - -// -/** - * @ignore - * @class - */ -class FilterState -{ - /** - * - */ - constructor() - { - this.renderTexture = null; - this.sourceFrame = new Rectangle(); - this.destinationFrame = new Rectangle(); - this.filters = []; - this.target = null; - this.legacy = false; - this.resolution = 1; - } -} - -/** - * @class - * @memberof PIXI.systems - * @extends PIXI.systems.WebGLSystem - */ -export default class FilterSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - /** - * stores a bunch of PO2 textures used for filtering - * @type {Object} - */ - this.texturePool = {}; - - /** - * a pool for storing filter states, save us creating new ones each tick - * @type {Array} - */ - this.statePool = []; - - /** - * A very simple geometry used when drawing a filter effect to the screen - * @type {Quad} - */ - this.quad = new Quad(); - - this.quadUv = new QuadUv(); - - /** - * Temporary rect for maths - * @type {PIXI.Rectangle} - */ - this.tempRect = new Rectangle(); - - this.activeState = {}; - - /** - * this uniform group is attached to filter uniforms when used - * @type {UniformGroup} - */ - this.globalUniforms = new UniformGroup({ - sourceFrame: this.tempRect, - destinationFrame: this.tempRect, - - // legacy variables - filterArea: new Float32Array(4), - filterClamp: new Float32Array(4), - }, true); - } - - /** - * Adds a new filter to the System. - * - * @param {PIXI.DisplayObject} target - The target of the filter to render. - * @param {PIXI.Filter[]} filters - The filters to apply. - */ - push(target, filters) - { - const renderer = this.renderer; - const filterStack = this.renderer.renderTexture.defaultFilterStack; - const state = this.statePool.pop() || new FilterState(); - - let resolution = filters[0].resolution; - let padding = filters[0].padding; - let autoFit = filters[0].autoFit; - let legacy = filters[0].legacy; - - for (let i = 1; i < filters.length; i++) - { - const filter = filters[i]; - - // lets use the lowest resolution.. - resolution = Math.min(resolution, filter.resolution); - // and the largest amount of padding! - padding = Math.max(padding, filter.padding); - // only auto fit if all filters are autofit - autoFit = autoFit || filter.autoFit; - - legacy = legacy || filter.legacy; - } - - filterStack.push(state); - - state.resolution = resolution; - - state.legacy = legacy; - - // round to whole number based on resolution - // TODO move that to the shader too? - state.sourceFrame = target.filterArea || target.getBounds(true); - - state.sourceFrame.pad(padding); - - if (autoFit) - { - state.sourceFrame.fit(this.renderer.renderTexture.destinationFrame); - } - - state.sourceFrame.round(resolution); - - state.renderTexture = this.getPotFilterTexture(state.sourceFrame.width, state.sourceFrame.height, resolution); - state.filters = filters; - - state.destinationFrame.width = state.renderTexture.width; - state.destinationFrame.height = state.renderTexture.height; - - state.renderTexture.filterFrame = state.sourceFrame; - renderer.renderTexture.bind(state.renderTexture, state.sourceFrame);// /, state.destinationFrame); - renderer.renderTexture.clear(); - } - - /** - * Pops off the filter and applies it. - * - */ - pop() - { - const renderer = this.renderer; - const filterStack = renderer.renderTexture.defaultFilterStack; - const state = filterStack.pop(); - const filters = state.filters; - - this.activeState = state; - - const globalUniforms = this.globalUniforms.uniforms; - - globalUniforms.sourceFrame = state.sourceFrame; - globalUniforms.destinationFrame = state.destinationFrame; - globalUniforms.resolution = state.resolution; - - // only update the rect if its legacy.. - if (state.legacy) - { - const filterArea = globalUniforms.filterArea; - const filterClamp = globalUniforms.filterClamp; - - filterArea[0] = state.destinationFrame.width; - filterArea[1] = state.destinationFrame.height; - filterArea[2] = state.sourceFrame.x; - filterArea[3] = state.sourceFrame.y; - - filterClamp[0] = 0.5 / state.resolution / state.destinationFrame.width; - filterClamp[1] = 0.5 / state.resolution / state.destinationFrame.height; - filterClamp[2] = (state.sourceFrame.width - 0.5) / state.resolution / state.destinationFrame.width; - filterClamp[3] = (state.sourceFrame.height - 0.5) / state.resolution / state.destinationFrame.height; - } - - this.globalUniforms.update(); - - const lastState = filterStack[filterStack.length - 1]; - - if (filters.length === 1) - { - filters[0].apply(this, state.renderTexture, lastState.renderTexture, false, state); - - this.returnFilterTexture(state.renderTexture); - } - else - { - let flip = state.renderTexture; - let flop = this.getPotFilterTexture( - flip.width, - flip.height, - state.resolution - ); - - flop.filterFrame = flip.filterFrame; - - let i = 0; - - for (i = 0; i < filters.length - 1; ++i) - { - filters[i].apply(this, flip, flop, true, state); - - const t = flip; - - flip = flop; - flop = t; - } - - filters[i].apply(this, flip, lastState.renderTexture, false, state); - - this.returnFilterTexture(flip); - this.returnFilterTexture(flop); - } - - this.statePool.push(state); - } - - /** - * Draws a filter. - * - * @param {PIXI.Filter} filter - The filter to draw. - * @param {PIXI.RenderTarget} input - The input render target. - * @param {PIXI.RenderTarget} output - The target to output to. - * @param {boolean} clear - Should the output be cleared before rendering to it - */ - applyFilter(filter, input, output, clear) - { - const renderer = this.renderer; - - renderer.renderTexture.bind(output, output ? output.filterFrame : null); - - if (clear) - { - // gl.disable(gl.SCISSOR_TEST); - renderer.renderTexture.clear(); - // gl.enable(gl.SCISSOR_TEST); - } - - // set the uniforms.. - filter.uniforms.uSampler = input; - filter.uniforms.filterGlobals = this.globalUniforms; - - // TODO make it so that the order of this does not matter.. - // because it does at the moment cos of global uniforms. - // they need to get resynced - - renderer.state.setState(filter.state); - renderer.shader.bind(filter); - - if (filter.legacy) - { - this.quadUv.map(input._frame, input.filterFrame); - - renderer.geometry.bind(this.quadUv); - renderer.geometry.draw(DRAW_MODES.TRIANGLES); - } - else - { - renderer.geometry.bind(this.quad); - renderer.geometry.draw(DRAW_MODES.TRIANGLE_STRIP); - } - } - - /** - * Calculates the mapped matrix. - * - * TODO playing around here.. this is temporary - (will end up in the shader) - * this returns a matrix that will normalise map filter cords in the filter to screen space - * - * @param {PIXI.Matrix} outputMatrix - the matrix to output to. - * @return {PIXI.Matrix} The mapped matrix. - */ - calculateScreenSpaceMatrix(outputMatrix) - { - const currentState = this.activeState; - - return filterTransforms.calculateScreenSpaceMatrix( - outputMatrix, - currentState.sourceFrame, - currentState.destinationFrame - ); - } - - /** - * This will map the filter coord so that a texture can be used based on the transform of a sprite - * - * @param {PIXI.Matrix} outputMatrix - The matrix to output to. - * @param {PIXI.Sprite} sprite - The sprite to map to. - * @return {PIXI.Matrix} The mapped matrix. - */ - calculateSpriteMatrix(outputMatrix, sprite) - { - const currentState = this.activeState; - - return filterTransforms.calculateSpriteMatrix( - outputMatrix, - currentState.sourceFrame, - currentState.destinationFrame, - sprite - ); - } - - /** - * Destroys this Filter System. - * - * @param {boolean} [contextLost=false] context was lost, do not free shaders - * - */ - destroy(contextLost = false) - { - if (!contextLost) - { - this.emptyPool(); - } - else - { - this.texturePool = {}; - } - } - - /** - * Gets a Power-of-Two render texture. - * - * TODO move to a seperate class could be on renderer? - * also - could cause issue with multiple contexts? - * - * @private - * @param {WebGLRenderingContext} gl - The webgl rendering context - * @param {number} minWidth - The minimum width of the render target. - * @param {number} minHeight - The minimum height of the render target. - * @param {number} resolution - The resolution of the render target. - * @return {PIXI.RenderTarget} The new render target. - */ - getPotFilterTexture(minWidth, minHeight, resolution) - { - minWidth = bitTwiddle.nextPow2(minWidth); - minHeight = bitTwiddle.nextPow2(minHeight); - resolution = resolution || 1; - - const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); - - if (!this.texturePool[key]) - { - this.texturePool[key] = []; - } - - let renderTexture = this.texturePool[key].pop(); - - if (!renderTexture) - { - // temporary bypass cache.. - // internally - this will cause a texture to be bound.. - renderTexture = RenderTexture.create({ - width: minWidth, - height: minHeight, - resolution, - }); - } - - return renderTexture; - } - - /** - * Gets extra render texture to use inside current filter - * - * @param {number} resolution resolution of the renderTexture - * @returns {PIXI.RenderTexture} - */ - getFilterTexture(resolution) - { - const rt = this.activeState.renderTexture; - - const filterTexture = this.getPotFilterTexture(rt.width, rt.height, resolution || rt.baseTexture.resolution); - - filterTexture.filterFrame = rt.filterFrame; - - return filterTexture; - } - - /** - * Frees a render texture back into the pool. - * - * @param {PIXI.RenderTarget} renderTexture - The renderTarget to free - */ - returnFilterTexture(renderTexture) - { - renderTexture.filterFrame = null; - - const base = renderTexture.baseTexture; - - const minWidth = base.width; - const minHeight = base.height; - - const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); - - this.texturePool[key].push(renderTexture); - } - - /** - * Empties the texture pool. - * - */ - emptyPool() - { - for (const i in this.texturePool) - { - const textures = this.texturePool[i]; - - if (textures) - { - for (let j = 0; j < textures.length; j++) - { - textures[j].destroy(true); - } - } - } - - this.texturePool = {}; - } -} diff --git a/packages/core/src/renderers/systems/geometry/GLBuffer.js b/packages/core/src/renderers/systems/geometry/GLBuffer.js deleted file mode 100644 index 0477e8a..0000000 --- a/packages/core/src/renderers/systems/geometry/GLBuffer.js +++ /dev/null @@ -1,9 +0,0 @@ -export default class GLBuffer -{ - constructor(buffer) - { - this.buffer = buffer; - this.updateID = -1; - this.byteLength = -1; - } -} diff --git a/packages/core/src/renderers/systems/geometry/GeometrySystem.js b/packages/core/src/renderers/systems/geometry/GeometrySystem.js deleted file mode 100644 index 26c8e9e..0000000 --- a/packages/core/src/renderers/systems/geometry/GeometrySystem.js +++ /dev/null @@ -1,388 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; -import GLBuffer from './GLBuffer'; - -const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class GeometrySystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this._activeGeometry = null; - this._activeVao = null; - - this.hasVao = true; - this.hasInstance = true; - } - - /** - * Sets up the renderer context and necessary buffers. - * - * @private - */ - contextChange() - { - const gl = this.gl = this.renderer.gl; - - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - - // webgl2 - if (!gl.createVertexArray) - { - // webgl 1! - let nativeVaoExtension = this.renderer.context.extensions.vertexArrayObject; - - if (this.renderer.legacy) - { - nativeVaoExtension = null; - } - - if (nativeVaoExtension) - { - gl.createVertexArray = () => - nativeVaoExtension.createVertexArrayOES(); - - gl.bindVertexArray = (vao) => - nativeVaoExtension.bindVertexArrayOES(vao); - - gl.deleteVertexArray = (vao) => - nativeVaoExtension.deleteVertexArrayOES(vao); - } - else - { - this.hasVao = false; - gl.createVertexArray = () => - { - // empty - }; - - gl.bindVertexArray = () => - { - // empty - }; - - gl.deleteVertexArray = () => - { - // empty - }; - } - } - - if (!gl.vertexAttribDivisor) - { - const instanceExt = gl.getExtension('ANGLE_instanced_arrays'); - - if (instanceExt) - { - gl.vertexAttribDivisor = (a, b) => - instanceExt.vertexAttribDivisorANGLE(a, b); - - gl.drawElementsInstanced = (a, b, c, d, e) => - instanceExt.drawElementsInstancedANGLE(a, b, c, d, e); - - gl.drawArraysInstanced = (a, b, c, d) => - instanceExt.drawArraysInstancedANGLE(a, b, c, d); - } - else - { - this.hasInstance = false; - } - } - } - - /** - * Binds geometry so that is can be drawn. Creating a Vao if required - * @private - * @param {PIXI.Geometry} geometry instance of geometry to bind - */ - bind(geometry, shader) - { - shader = shader || this.renderer.shader.shader; - - const gl = this.gl; - - // not sure the best way to address this.. - // currently different shaders require different VAOs for the same geometry - // Still mulling over the best way to solve this one.. - // will likely need to modify the shader attribute locations at run time! - let vaos = geometry.glVertexArrayObjects[this.CONTEXT_UID]; - - if (!vaos) - { - geometry.glVertexArrayObjects[this.CONTEXT_UID] = vaos = {}; - } - - const vao = vaos[shader.program.id] || this.initGeometryVao(geometry, shader.program); - - this._activeGeometry = geometry; - - if (this._activeVao !== vao) - { - this._activeVao = vao; - - if (this.hasVao) - { - gl.bindVertexArray(vao); - } - else - { - this.activateVao(geometry, shader.program); - } - } - - // TODO - optimise later! - // don't need to loop through if nothing changed! - // maybe look to add an 'autoupdate' to geometry? - this.updateBuffers(); - } - - reset() - { - this.unbind(); - } - - updateBuffers() - { - const geometry = this._activeGeometry; - const gl = this.gl; - - for (let i = 0; i < geometry.buffers.length; i++) - { - const buffer = geometry.buffers[i]; - - const glBuffer = buffer._glBuffers[this.CONTEXT_UID]; - - if (buffer._updateID !== glBuffer.updateID) - { - glBuffer.updateID = buffer._updateID; - - // TODO can cache this on buffer! maybe added a getter / setter? - const type = buffer.index ? gl.ELEMENT_ARRAY_BUFFER : gl.ARRAY_BUFFER; - const drawType = buffer.static ? gl.STATIC_DRAW : gl.DYNAMIC_DRAW; - - gl.bindBuffer(type, glBuffer.buffer); - - if (glBuffer.byteLength >= buffer.data.byteLength) - { - // offset is always zero for now! - gl.bufferSubData(type, 0, buffer.data); - } - else - { - gl.bufferData(type, buffer.data, drawType); - } - } - } - } - - checkCompatability(geometry, program) - { - // geometry must have at least all the attributes that the shader requires. - const geometryAttributes = geometry.attributes; - const shaderAttributes = program.attributeData; - - for (const j in shaderAttributes) - { - if (!geometryAttributes[j]) - { - throw new Error(`shader and geometry incompatible, geometry missing the "${j}" attribute`); - } - } - } - - /** - * Creates a Vao with the same structure as the geometry and stores it on the geometry. - * @private - * @param {PIXI.Geometry} geometry instance of geometry to to generate Vao for - */ - initGeometryVao(geometry, program) - { - this.checkCompatability(geometry, program); - - const gl = this.gl; - const CONTEXT_UID = this.CONTEXT_UID; - const buffers = geometry.buffers; - const attributes = geometry.attributes; - - const tempStride = {}; - const tempStart = {}; - - for (const j in buffers) - { - tempStride[j] = 0; - tempStart[j] = 0; - } - - for (const j in attributes) - { - if (!attributes[j].size && program.attributeData[j]) - { - attributes[j].size = program.attributeData[j].size; - } - - tempStride[attributes[j].buffer] += attributes[j].size * byteSizeMap[attributes[j].type]; - } - - for (const j in attributes) - { - const attribute = attributes[j]; - const attribSize = attribute.size; - - if (attribute.stride === undefined) - { - if (tempStride[attribute.buffer] === attribSize * byteSizeMap[attribute.type]) - { - attribute.stride = 0; - } - else - { - attribute.stride = tempStride[attribute.buffer]; - } - } - - if (attribute.start === undefined) - { - attribute.start = tempStart[attribute.buffer]; - - tempStart[attribute.buffer] += attribSize * byteSizeMap[attribute.type]; - } - } - - // first update - and create the buffers! - // only create a gl buffer if it actually gets - for (let i = 0; i < buffers.length; i++) - { - const buffer = buffers[i]; - - if (!buffer._glBuffers[CONTEXT_UID]) - { - buffer._glBuffers[CONTEXT_UID] = new GLBuffer(gl.createBuffer()); - } - } - - // TODO - maybe make this a data object? - // lets wait to see if we need to first! - const vao = gl.createVertexArray(); - - gl.bindVertexArray(vao); - - this.activateVao(geometry, program); - - gl.bindVertexArray(null); - - // add it to the cache! - geometry.glVertexArrayObjects[this.CONTEXT_UID][program.id] = vao; - - return vao; - } - - activateVao(geometry, program) - { - const gl = this.gl; - const CONTEXT_UID = this.CONTEXT_UID; - const buffers = geometry.buffers; - const attributes = geometry.attributes; - - if (geometry.indexBuffer) - { - // first update the index buffer if we have one.. - gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, geometry.indexBuffer._glBuffers[CONTEXT_UID].buffer); - } - - let lastBuffer = null; - - // add a new one! - for (const j in attributes) - { - const attribute = attributes[j]; - const buffer = buffers[attribute.buffer]; - const glBuffer = buffer._glBuffers[CONTEXT_UID]; - - if (program.attributeData[j]) - { - if (lastBuffer !== glBuffer) - { - gl.bindBuffer(gl.ARRAY_BUFFER, glBuffer.buffer); - - lastBuffer = glBuffer; - } - - const location = program.attributeData[j].location; - - // TODO introduce state again - // we can optimise this for older devices that have no VAOs - gl.enableVertexAttribArray(location); - - gl.vertexAttribPointer(location, - attribute.size, - attribute.type || gl.FLOAT, - attribute.normalized, - attribute.stride, - attribute.start); - - if (attribute.instance) - { - // TODO calculate instance count based of this... - if (this.hasInstance) - { - gl.vertexAttribDivisor(location, 1); - } - else - { - throw new Error('geometry error, GPU Instancing is not supported on this device'); - } - } - } - } - } - - draw(type, size, start, instanceCount) - { - const gl = this.gl; - const geometry = this._activeGeometry; - - // TODO.. this should not change so maybe cache the function? - - if (geometry.indexBuffer) - { - if (geometry.instanced) - { - /* eslint-disable max-len */ - gl.drawElementsInstanced(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2, instanceCount || 1); - /* eslint-enable max-len */ - } - else - { - gl.drawElements(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2); - } - } - else - if (geometry.instanced) - { - // TODO need a better way to calculate size.. - gl.drawArraysInstanced(type, start, size || geometry.getSize(), instanceCount || 1); - } - else - { - gl.drawArrays(type, start, size || geometry.getSize()); - } - - return this; - } - - unbind() - { - this.gl.bindVertexArray(null); - this._activeVao = null; - this._activeGeometry = null; - } -} diff --git a/packages/core/src/renderers/systems/geometry/setVertexAttribArrays.js b/packages/core/src/renderers/systems/geometry/setVertexAttribArrays.js deleted file mode 100644 index 23b6959..0000000 --- a/packages/core/src/renderers/systems/geometry/setVertexAttribArrays.js +++ /dev/null @@ -1,54 +0,0 @@ -// var GL_MAP = {}; - -/** - * @param gl {WebGLRenderingContext} The current WebGL context - * @param attribs {*} - * @param state {*} - */ -export default function setVertexAttribArrays(gl, attribs, state) -{ - let i; - - if (state) - { - const tempAttribState = state.tempAttribState; - const attribState = state.attribState; - - for (i = 0; i < tempAttribState.length; i++) - { - tempAttribState[i] = false; - } - - // set the new attribs - for (i = 0; i < attribs.length; i++) - { - tempAttribState[attribs[i].attribute.location] = true; - } - - for (i = 0; i < attribState.length; i++) - { - if (attribState[i] !== tempAttribState[i]) - { - attribState[i] = tempAttribState[i]; - - if (state.attribState[i]) - { - gl.enableVertexAttribArray(i); - } - else - { - gl.disableVertexAttribArray(i); - } - } - } - } - else - { - for (i = 0; i < attribs.length; i++) - { - const attrib = attribs[i]; - - gl.enableVertexAttribArray(attrib.attribute.location); - } - } -} diff --git a/packages/core/src/renderers/systems/index.js b/packages/core/src/renderers/systems/index.js deleted file mode 100644 index c843f03..0000000 --- a/packages/core/src/renderers/systems/index.js +++ /dev/null @@ -1,6 +0,0 @@ -/** - * Systems are individual components to the Renderer pipeline. - * @namespace PIXI.systems - */ -export { default as WebGLSystem } from './WebGLSystem'; -export { default as FilterSystem } from './filter/FilterSystem'; diff --git a/packages/core/src/renderers/systems/shader/GLProgram.js b/packages/core/src/renderers/systems/shader/GLProgram.js deleted file mode 100644 index 92980ca..0000000 --- a/packages/core/src/renderers/systems/shader/GLProgram.js +++ /dev/null @@ -1,43 +0,0 @@ -/** - * Helper class to create a webGL Program - * - * @class - * @private - */ -export default class GLProgram -{ - constructor(program, uniformData) - { - /** - * The shader program - * - * @member {WebGLProgram} - */ - this.program = program; - - /** - * holds the uniform data which contains uniform locations - * and current uniform values used for caching and preventing undeed GPU commands - * @type {Object} - */ - this.uniformData = uniformData; - - /** - * uniformGroups holds the various upload functions for the shader. Each uniform group - * and program have a unique upload function generated. - * @type {Object} - */ - this.uniformGroups = {}; - } - - /** - * Destroys this program - * TODO - */ - destroy() - { - this.uniformData = null; - this.uniformGroups = null; - this.program = null; - } -} diff --git a/packages/core/src/renderers/systems/shader/ShaderSystem.js b/packages/core/src/renderers/systems/shader/ShaderSystem.js deleted file mode 100644 index 6cf57b6..0000000 --- a/packages/core/src/renderers/systems/shader/ShaderSystem.js +++ /dev/null @@ -1,170 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; -import GLProgram from './GLProgram'; -import generateUniformsSync from '../../../shader/generateUniformsSync'; -import defaultValue from '../../../shader/defaultValue'; -import compileProgram from './shader/compileProgram'; - -let UID = 0; - -/** - * Helper class to create a webGL Texture - * - * @class - * @memberof PIXI - */ -export default class ShaderSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - A reference to the current renderer - */ - constructor(renderer) - { - super(renderer); - - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = null; - - this.shader = null; - this.program = null; - - this.id = UID++; - } - - contextChange(gl) - { - this.gl = gl; - } - - /** - * Changes the current shader to the one given in parameter - * - * @param {PIXI.Shader} shader - the new shader - * @param {boolean} dontSync - false if the shader should automatically sync its uniforms. - * @returns {PIXI.glCore.glProgram} the glProgram that belongs to the shader. - */ - bind(shader, dontSync) - { - shader.uniforms.globals = this.renderer.globalUniforms; - - const program = shader.program; - const glProgram = program.glPrograms[this.renderer.CONTEXT_UID] || this.generateShader(shader); - - this.shader = shader; - - // TODO - some current pixi plugins bypass this.. so it not safe to use yet.. - if (this.program !== program) - { - this.program = program; - this.gl.useProgram(glProgram.program); - } - - if (!dontSync) - { - this.syncUniformGroup(shader.uniformGroup); - } - - return glProgram; - } - - /** - * Uploads the uniforms values to the currently bound shader. - * - * @param {object} uniforms - the uniforms valiues that be applied to the current shader - */ - setUniforms(uniforms) - { - const shader = this.shader.program; - const glProgram = shader.glPrograms[this.renderer.CONTEXT_UID]; - - shader.syncUniforms(glProgram.uniformData, uniforms, this.renderer); - } - - syncUniformGroup(group) - { - const glProgram = this.getglProgram(); - - if (!group.static || group.dirtyId !== glProgram.uniformGroups[group.id]) - { - glProgram.uniformGroups[group.id] = group.dirtyId; - const syncFunc = group.syncUniforms[this.shader.program.id] || this.createSyncGroups(group); - - syncFunc(glProgram.uniformData, group.uniforms, this.renderer); - } - } - - createSyncGroups(group) - { - group.syncUniforms[this.shader.program.id] = generateUniformsSync(group, this.shader.program.uniformData); - - return group.syncUniforms[this.shader.program.id]; - } - - /** - * Returns the underlying GLShade rof the currently bound shader. - * This can be handy for when you to have a little more control over the setting of your uniforms. - * - * @return {PIXI.glCore.Shader} the glProgram for the currently bound Shader for this context - */ - getglProgram() - { - if (this.shader) - { - return this.shader.program.glPrograms[this.renderer.CONTEXT_UID]; - } - - return null; - } - - /** - * Generates a glProgram verion of the Shader provided. - * - * @private - * @param {PIXI.Shader} shader the shader that the glProgram will be based on. - * @return {PIXI.glCore.glProgram} A shiney new glProgram - */ - generateShader(shader) - { - const gl = this.gl; - - const program = shader.program; - - const attribMap = {}; - - for (const i in program.attributeData) - { - attribMap[i] = program.attributeData[i].location; - } - - const shaderProgram = compileProgram(gl, program.vertexSrc, program.fragmentSrc, attribMap); - const uniformData = {}; - - for (const i in program.uniformData) - { - const data = program.uniformData[i]; - - uniformData[i] = { - location: gl.getUniformLocation(shaderProgram, i), - value: defaultValue(data.type, data.size), - }; - } - - const glProgram = new GLProgram(shaderProgram, uniformData); - - program.glPrograms[this.renderer.CONTEXT_UID] = glProgram; - - return glProgram; - } - - /** - * Destroys this System and removes all its textures - */ - destroy() - { - // TODO implement destroy method for ShaderSystem - this.destroyed = true; - } -} diff --git a/packages/core/src/renderers/systems/shader/shader/compileProgram.js b/packages/core/src/renderers/systems/shader/shader/compileProgram.js deleted file mode 100644 index 8c6ccb9..0000000 --- a/packages/core/src/renderers/systems/shader/shader/compileProgram.js +++ /dev/null @@ -1,78 +0,0 @@ -/** - * @method compileProgram - * @memberof PIXI.glCore.shader - * @param gl {WebGLRenderingContext} The current WebGL context {WebGLProgram} - * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. - * @param fragmentSrc {string|string[]} The fragment shader source as an array of strings. - * @param attributeLocations {Object} An attribute location map that lets you manually set the attribute locations - * @return {WebGLProgram} the shader program - */ -export default function compileProgram(gl, vertexSrc, fragmentSrc, attributeLocations) -{ - const glVertShader = compileShader(gl, gl.VERTEX_SHADER, vertexSrc); - const glFragShader = compileShader(gl, gl.FRAGMENT_SHADER, fragmentSrc); - - let program = gl.createProgram(); - - gl.attachShader(program, glVertShader); - gl.attachShader(program, glFragShader); - - // optionally, set the attributes manually for the program rather than letting WebGL decide.. - if (attributeLocations) - { - for (const i in attributeLocations) - { - gl.bindAttribLocation(program, attributeLocations[i], i); - } - } - - gl.linkProgram(program); - - // if linking fails, then log and cleanup - if (!gl.getProgramParameter(program, gl.LINK_STATUS)) - { - console.error('Pixi.js Error: Could not initialize shader.'); - console.error('gl.VALIDATE_STATUS', gl.getProgramParameter(program, gl.VALIDATE_STATUS)); - console.error('gl.getError()', gl.getError()); - - // if there is a program info log, log it - if (gl.getProgramInfoLog(program) !== '') - { - console.warn('Pixi.js Warning: gl.getProgramInfoLog()', gl.getProgramInfoLog(program)); - } - - gl.deleteProgram(program); - program = null; - } - - // clean up some shaders - gl.deleteShader(glVertShader); - gl.deleteShader(glFragShader); - - return program; -} - -/** - * @private - * @param gl {WebGLRenderingContext} The current WebGL context {WebGLProgram} - * @param type {Number} the type, can be either VERTEX_SHADER or FRAGMENT_SHADER - * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. - * @return {WebGLShader} the shader - */ -function compileShader(gl, type, src) -{ - const shader = gl.createShader(type); - - gl.shaderSource(shader, src); - gl.compileShader(shader); - - if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) - { - console.warn(src); - console.error(gl.getShaderInfoLog(shader)); - - return null; - } - - return shader; -} diff --git a/packages/core/src/renderers/systems/shader/shader/defaultValue.js b/packages/core/src/renderers/systems/shader/shader/defaultValue.js deleted file mode 100644 index 97290bd..0000000 --- a/packages/core/src/renderers/systems/shader/shader/defaultValue.js +++ /dev/null @@ -1,79 +0,0 @@ -/** - * @method defaultValue - * @memberof PIXI.glCore.shader - * @param type {String} Type of value - * @param size {Number} - */ -export default function defaultValue(type, size) -{ - switch (type) - { - case 'float': - return 0; - - case 'vec2': - return new Float32Array(2 * size); - - case 'vec3': - return new Float32Array(3 * size); - - case 'vec4': - return new Float32Array(4 * size); - - case 'int': - case 'sampler2D': - case 'sampler2DArray': - return 0; - - case 'ivec2': - return new Int32Array(2 * size); - - case 'ivec3': - return new Int32Array(3 * size); - - case 'ivec4': - return new Int32Array(4 * size); - - case 'bool': - return false; - - case 'bvec2': - - return booleanArray(2 * size); - - case 'bvec3': - return booleanArray(3 * size); - - case 'bvec4': - return booleanArray(4 * size); - - case 'mat2': - return new Float32Array([1, 0, - 0, 1]); - - case 'mat3': - return new Float32Array([1, 0, 0, - 0, 1, 0, - 0, 0, 1]); - - case 'mat4': - return new Float32Array([1, 0, 0, 0, - 0, 1, 0, 0, - 0, 0, 1, 0, - 0, 0, 0, 1]); - } - - return null; -} - -function booleanArray(size) -{ - const array = new Array(size); - - for (let i = 0; i < array.length; i++) - { - array[i] = false; - } - - return array; -} diff --git a/packages/core/src/renderers/systems/shader/shader/index.js b/packages/core/src/renderers/systems/shader/shader/index.js deleted file mode 100644 index ba7a317..0000000 --- a/packages/core/src/renderers/systems/shader/shader/index.js +++ /dev/null @@ -1,8 +0,0 @@ -/** - * @namespace PIXI.glCore.shader - */ -export { default as compileProgram } from './compileProgram'; -export { default as defaultValue } from './defaultValue'; -export { default as setPrecision } from './setPrecision'; -export { default as mapSize } from './mapSize'; -export { default as mapType } from './mapType'; diff --git a/packages/core/src/renderers/systems/shader/shader/mapSize.js b/packages/core/src/renderers/systems/shader/shader/mapSize.js deleted file mode 100644 index b681554..0000000 --- a/packages/core/src/renderers/systems/shader/shader/mapSize.js +++ /dev/null @@ -1,33 +0,0 @@ -const GLSL_TO_SIZE = { - float: 1, - vec2: 2, - vec3: 3, - vec4: 4, - - int: 1, - ivec2: 2, - ivec3: 3, - ivec4: 4, - - bool: 1, - bvec2: 2, - bvec3: 3, - bvec4: 4, - - mat2: 4, - mat3: 9, - mat4: 16, - - sampler2D: 1, -}; - -/** - * @method mapSize - * @memberof PIXI.glCore.shader - * @param type {String} - * @return {Number} - */ -export default function mapSize(type) -{ - return GLSL_TO_SIZE[type]; -} diff --git a/packages/core/src/renderers/systems/shader/shader/mapType.js b/packages/core/src/renderers/systems/shader/shader/mapType.js deleted file mode 100644 index d288b7c..0000000 --- a/packages/core/src/renderers/systems/shader/shader/mapType.js +++ /dev/null @@ -1,45 +0,0 @@ -let GL_TABLE = null; - -const GL_TO_GLSL_TYPES = { - FLOAT: 'float', - FLOAT_VEC2: 'vec2', - FLOAT_VEC3: 'vec3', - FLOAT_VEC4: 'vec4', - - INT: 'int', - INT_VEC2: 'ivec2', - INT_VEC3: 'ivec3', - INT_VEC4: 'ivec4', - - BOOL: 'bool', - BOOL_VEC2: 'bvec2', - BOOL_VEC3: 'bvec3', - BOOL_VEC4: 'bvec4', - - FLOAT_MAT2: 'mat2', - FLOAT_MAT3: 'mat3', - FLOAT_MAT4: 'mat4', - - SAMPLER_2D: 'sampler2D', - SAMPLER_CUBE: 'samplerCube', - SAMPLER_2D_ARRAY: 'sampler2DArray', -}; - -export default function mapType(gl, type) -{ - if (!GL_TABLE) - { - const typeNames = Object.keys(GL_TO_GLSL_TYPES); - - GL_TABLE = {}; - - for (let i = 0; i < typeNames.length; ++i) - { - const tn = typeNames[i]; - - GL_TABLE[gl[tn]] = GL_TO_GLSL_TYPES[tn]; - } - } - - return GL_TABLE[type]; -} diff --git a/packages/core/src/renderers/systems/shader/shader/setPrecision.js b/packages/core/src/renderers/systems/shader/shader/setPrecision.js deleted file mode 100644 index e2c1f34..0000000 --- a/packages/core/src/renderers/systems/shader/shader/setPrecision.js +++ /dev/null @@ -1,18 +0,0 @@ -/** - * Sets the float precision on the shader. If the precision is already present this function will do nothing - * @param {string} src the shader source - * @param {string} precision The float precision of the shader. Options are 'lowp', 'mediump' or 'highp'. - * - * @return {string} modified shader source - */ -export default function setPrecision(src, precision) -{ - src = src.trim(); - - if (src.substring(0, 9) !== 'precision' && src.substring(0, 1) !== '#') - { - return `precision ${precision} float;\n${src}`; - } - - return src; -} diff --git a/packages/core/src/renderers/systems/textures/GLTexture.js b/packages/core/src/renderers/systems/textures/GLTexture.js deleted file mode 100644 index c3fb8dc..0000000 --- a/packages/core/src/renderers/systems/textures/GLTexture.js +++ /dev/null @@ -1,33 +0,0 @@ -export default class GLTexture -{ - constructor(texture) - { - /** - * The WebGL texture - * - * @member {WebGLTexture} - */ - this.texture = texture; - - this.width = -1; - this.height = -1; - - /** - * Texture contents dirty flag - * @member {number} - */ - this.dirtyId = -1; - - /** - * Texture style dirty flag - * @type {number} - */ - this.dirtyStyleId = -1; - - /** - * Whether mip levels has to be generated - * @type {boolean} - */ - this.mipmap = false; - } -} diff --git a/packages/core/src/renderers/systems/textures/TextureGCSystem.js b/packages/core/src/renderers/systems/textures/TextureGCSystem.js deleted file mode 100644 index b089938..0000000 --- a/packages/core/src/renderers/systems/textures/TextureGCSystem.js +++ /dev/null @@ -1,110 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; -import { GC_MODES } from '@pixi/constants'; -import { settings } from '@pixi/settings'; - -/** - * TextureGarbageCollector. This class manages the GPU and ensures that it does not get clogged - * up with textures that are no longer being used. - * - * @class - * @memberof PIXI - */ -export default class TextureGCSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this.count = 0; - this.checkCount = 0; - this.maxIdle = settings.GC_MAX_IDLE; - this.checkCountMax = settings.GC_MAX_CHECK_COUNT; - this.mode = settings.GC_MODE; - } - - /** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ - postrender() - { - this.count++; - - if (this.mode === GC_MODES.MANUAL) - { - return; - } - - this.checkCount++; - - if (this.checkCount > this.checkCountMax) - { - this.checkCount = 0; - - this.run(); - } - } - - /** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ - run() - { - const tm = this.renderer.texture; - const managedTextures = tm.managedTextures; - let wasRemoved = false; - - for (let i = 0; i < managedTextures.length; i++) - { - const texture = managedTextures[i]; - - // only supports non generated textures at the moment! - if (!texture.frameBuffer && this.count - texture.touched > this.maxIdle) - { - tm.destroyTexture(texture, true); - managedTextures[i] = null; - wasRemoved = true; - } - } - - if (wasRemoved) - { - let j = 0; - - for (let i = 0; i < managedTextures.length; i++) - { - if (managedTextures[i] !== null) - { - managedTextures[j++] = managedTextures[i]; - } - } - - managedTextures.length = j; - } - } - - /** - * Removes all the textures within the specified displayObject and its children from the GPU - * - * @param {PIXI.DisplayObject} displayObject - the displayObject to remove the textures from. - */ - unload(displayObject) - { - const tm = this.renderer.textureSystem; - - // only destroy non generated textures - if (displayObject._texture && displayObject._texture._glRenderTargets) - { - tm.destroyTexture(displayObject._texture); - } - - for (let i = displayObject.children.length - 1; i >= 0; i--) - { - this.unload(displayObject.children[i]); - } - } -} diff --git a/packages/core/src/renderers/systems/textures/TextureSystem.js b/packages/core/src/renderers/systems/textures/TextureSystem.js deleted file mode 100644 index ccc5de9..0000000 --- a/packages/core/src/renderers/systems/textures/TextureSystem.js +++ /dev/null @@ -1,281 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; -import GLTexture from './GLTexture'; -import { removeItems } from '@pixi/utils'; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class TextureSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - // TODO set to max textures... - this.boundTextures = [ - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - ]; - - this.currentLocation = -1; - - this.managedTextures = []; - } - - /** - * Sets up the renderer context and necessary buffers. - * - * @private - */ - contextChange() - { - const gl = this.gl = this.renderer.gl; - - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - - // TODO move this.. to a nice make empty textures class.. - this.emptyTextures = {}; - - const emptyTexture2D = new GLTexture(gl.createTexture()); - - gl.bindTexture(gl.TEXTURE_2D, emptyTexture2D.texture); - gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array(4)); - - this.emptyTextures[gl.TEXTURE_2D] = emptyTexture2D; - this.emptyTextures[gl.TEXTURE_CUBE_MAP] = new GLTexture(gl.createTexture()); - - gl.bindTexture(gl.TEXTURE_CUBE_MAP, this.emptyTextures[gl.TEXTURE_CUBE_MAP].texture); - - let i; - - for (i = 0; i < 6; i++) - { - gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, null); - } - - gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_MAG_FILTER, gl.LINEAR); - gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_MIN_FILTER, gl.LINEAR); - - for (i = 0; i < this.boundTextures.length; i++) - { - this.bind(null, i); - } - } - - bind(texture, location) - { - const gl = this.gl; - - location = location || 0; - - if (this.currentLocation !== location) - { - this.currentLocation = location; - gl.activeTexture(gl.TEXTURE0 + location); - } - - if (texture) - { - texture = texture.baseTexture || texture; - - if (texture.valid) - { - texture.touched = this.renderer.textureGC.count; - - const glTexture = texture._glTextures[this.CONTEXT_UID] || this.initTexture(texture); - - gl.bindTexture(texture.target, glTexture.texture); - - if (glTexture.dirtyId !== texture.dirtyId) - { - this.updateTexture(texture); - } - - this.boundTextures[location] = texture; - } - } - else - { - gl.bindTexture(gl.TEXTURE_2D, this.emptyTextures[gl.TEXTURE_2D].texture); - this.boundTextures[location] = null; - } - } - - unbind(texture) - { - const gl = this.gl; - - for (let i = 0; i < this.boundTextures.length; i++) - { - if (this.boundTextures[i] === texture) - { - if (this.currentLocation !== i) - { - gl.activeTexture(gl.TEXTURE0 + i); - this.currentLocation = i; - } - - gl.bindTexture(gl.TEXTURE_2D, this.emptyTextures[texture.target].texture); - this.boundTextures[i] = null; - } - } - } - - initTexture(texture) - { - const glTexture = new GLTexture(this.gl.createTexture()); - - // guarentee an update.. - glTexture.dirtyId = -1; - - texture._glTextures[this.CONTEXT_UID] = glTexture; - - this.managedTextures.push(texture); - texture.on('dispose', this.destroyTexture, this); - - return glTexture; - } - - updateTexture(texture) - { - const glTexture = texture._glTextures[this.CONTEXT_UID]; - const renderer = this.renderer; - - if (texture.resource && texture.resource.upload(renderer, texture, glTexture)) - { - // texture is uploaded, dont do anything! - } - else - { - // default, renderTexture-like logic - const width = texture.realWidth; - const height = texture.realHeight; - const gl = renderer.gl; - - if (glTexture.width !== width - || glTexture.height !== height - || glTexture.dirtyId < 0) - { - glTexture.width = width; - glTexture.height = height; - - gl.texImage2D(texture.target, 0, - texture.format, - width, - height, - 0, - texture.format, - texture.type, - null); - } - } - - // lets only update what changes.. - if (texture.dirtyStyleId !== glTexture.dirtyStyleId) - { - this.updateTextureStyle(texture); - } - glTexture.dirtyId = texture.dirtyId; - } - - /** - * Deletes the texture from WebGL - * - * @param {PIXI.BaseTexture|PIXI.Texture} texture - the texture to destroy - * @param {boolean} [skipRemove=false] - Whether to skip removing the texture from the TextureManager. - */ - destroyTexture(texture, skipRemove) - { - const gl = this.gl; - - texture = texture.baseTexture || texture; - - if (texture._glTextures[this.renderer.CONTEXT_UID]) - { - this.unbind(texture); - - gl.deleteTexture(texture._glTextures[this.renderer.CONTEXT_UID].texture); - texture.off('dispose', this.destroyTexture, this); - - delete texture._glTextures[this.renderer.CONTEXT_UID]; - - if (!skipRemove) - { - const i = this.managedTextures.indexOf(texture); - - if (i !== -1) - { - removeItems(this.managedTextures, i, 1); - } - } - } - } - - updateTextureStyle(texture) - { - const glTexture = texture._glTextures[this.CONTEXT_UID]; - - glTexture.mipmap = texture.mipmap && texture.isPowerOfTwo; - if (!glTexture) - { - return; - } - - if (texture.resource && texture.resource.style(this.renderer, texture, glTexture)) - { - // style is set, dont do anything! - } - else - { - this.setStyle(texture, glTexture); - } - - glTexture.dirtyStyleId = texture.dirtyStyleId; - } - - setStyle(texture, glTexture) - { - const gl = this.gl; - - if (glTexture.mipmap) - { - gl.generateMipmap(texture.target); - } - - gl.texParameteri(texture.target, gl.TEXTURE_WRAP_S, texture.wrapMode); - gl.texParameteri(texture.target, gl.TEXTURE_WRAP_T, texture.wrapMode); - - if (glTexture.mipmap) - { - /* eslint-disable max-len */ - gl.texParameteri(texture.target, gl.TEXTURE_MIN_FILTER, texture.scaleMode ? gl.LINEAR_MIPMAP_LINEAR : gl.NEAREST_MIPMAP_NEAREST); - /* eslint-disable max-len */ - } - else - { - gl.texParameteri(texture.target, gl.TEXTURE_MIN_FILTER, texture.scaleMode ? gl.LINEAR : gl.NEAREST); - } - - gl.texParameteri(texture.target, gl.TEXTURE_MAG_FILTER, texture.scaleMode ? gl.LINEAR : gl.NEAREST); - } -} diff --git a/packages/core/src/renderers/utils/ObjectRenderer.js b/packages/core/src/renderers/utils/ObjectRenderer.js deleted file mode 100644 index f67a273..0000000 --- a/packages/core/src/renderers/utils/ObjectRenderer.js +++ /dev/null @@ -1,48 +0,0 @@ -import WebGLSystem from '../systems/WebGLSystem'; - -/** - * Base for a common object renderer that can be used as a system renderer plugin. - * - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class ObjectRenderer extends WebGLSystem -{ - /** - * Starts the renderer and sets the shader - * - */ - start() - { - // set the shader.. - } - - /** - * Stops the renderer - * - */ - stop() - { - this.flush(); - } - - /** - * Stub method for rendering content and emptying the current batch. - * - */ - flush() - { - // flush! - } - - /** - * Renders an object - * - * @param {PIXI.DisplayObject} object - The object to render. - */ - render(object) // eslint-disable-line no-unused-vars - { - // render the object - } -} diff --git a/packages/core/src/renderers/utils/Quad.js b/packages/core/src/renderers/utils/Quad.js deleted file mode 100644 index 5c1517b..0000000 --- a/packages/core/src/renderers/utils/Quad.js +++ /dev/null @@ -1,26 +0,0 @@ -import Geometry from '../../geometry/Geometry'; - -/** - * Helper class to create a quad - * - * @class - * @memberof PIXI - */ -export default class Quad extends Geometry -{ - /** - * just a quad - */ - constructor() - { - super(); - - this.addAttribute('aVertexPosition', [ - 0, 0, - 1, 0, - 1, 1, - 0, 1, - ]) - .addIndex([0, 1, 3, 2]); - } -} diff --git a/packages/core/src/renderers/utils/QuadUv.js b/packages/core/src/renderers/utils/QuadUv.js deleted file mode 100644 index 97bda89..0000000 --- a/packages/core/src/renderers/utils/QuadUv.js +++ /dev/null @@ -1,103 +0,0 @@ -import Geometry from '../../geometry/Geometry'; -import Buffer from '../../geometry/Buffer'; - -/** - * Helper class to create a quad with uvs like in v4 - * - * @class - * @memberof PIXI - */ -export default class QuadUv extends Geometry -{ - constructor() - { - super(); - - /** - * An array of vertices - * - * @member {Float32Array} - */ - this.vertices = new Float32Array([ - -1, -1, - 1, -1, - 1, 1, - -1, 1, - ]); - - /** - * The Uvs of the quad - * - * @member {Float32Array} - */ - this.uvs = new Float32Array([ - 0, 0, - 1, 0, - 1, 1, - 0, 1, - ]); - - this.vertexBuffer = new Buffer(this.vertices); - this.uvBuffer = new Buffer(this.uvs); - - this.addAttribute('aVertexPosition', this.vertexBuffer) - .addAttribute('aTextureCoord', this.uvBuffer) - .addIndex([0, 1, 2, 0, 2, 3]); - } - - /** - * Maps two Rectangle to the quad. - * - * @param {PIXI.Rectangle} targetTextureFrame - the first rectangle - * @param {PIXI.Rectangle} destinationFrame - the second rectangle - * @return {PIXI.Quad} Returns itself. - */ - map(targetTextureFrame, destinationFrame) - { - let x = 0; // destinationFrame.x / targetTextureFrame.width; - let y = 0; // destinationFrame.y / targetTextureFrame.height; - - this.uvs[0] = x; - this.uvs[1] = y; - - this.uvs[2] = x + (destinationFrame.width / targetTextureFrame.width); - this.uvs[3] = y; - - this.uvs[4] = x + (destinationFrame.width / targetTextureFrame.width); - this.uvs[5] = y + (destinationFrame.height / targetTextureFrame.height); - - this.uvs[6] = x; - this.uvs[7] = y + (destinationFrame.height / targetTextureFrame.height); - - x = destinationFrame.x; - y = destinationFrame.y; - - this.vertices[0] = x; - this.vertices[1] = y; - - this.vertices[2] = x + destinationFrame.width; - this.vertices[3] = y; - - this.vertices[4] = x + destinationFrame.width; - this.vertices[5] = y + destinationFrame.height; - - this.vertices[6] = x; - this.vertices[7] = y + destinationFrame.height; - - this.invalidate(); - - return this; - } - - /** - * legacy upload method, just marks buffers dirty - * @returns {PIXI.QuadUv} Returns itself. - */ - invalidate() - { - this.vertexBuffer._updateID++; - this.uvBuffer._updateID++; - - return this; - } -} diff --git a/packages/core/src/renderers/utils/checkMaxIfStatmentsInShader.js b/packages/core/src/renderers/utils/checkMaxIfStatmentsInShader.js deleted file mode 100644 index 8047c96..0000000 --- a/packages/core/src/renderers/utils/checkMaxIfStatmentsInShader.js +++ /dev/null @@ -1,58 +0,0 @@ -const fragTemplate = [ - 'precision mediump float;', - 'void main(void){', - 'float test = 0.1;', - '%forloop%', - 'gl_FragColor = vec4(0.0);', - '}', -].join('\n'); - -export default function checkMaxIfStatmentsInShader(maxIfs, gl) -{ - if (maxIfs === 0) - { - throw new Error('Invalid value of `0` passed to `checkMaxIfStatementsInShader`'); - } - - const shader = gl.createShader(gl.FRAGMENT_SHADER); - - while (true) // eslint-disable-line no-constant-condition - { - const fragmentSrc = fragTemplate.replace(/%forloop%/gi, generateIfTestSrc(maxIfs)); - - gl.shaderSource(shader, fragmentSrc); - gl.compileShader(shader); - - if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) - { - maxIfs = (maxIfs / 2) | 0; - } - else - { - // valid! - break; - } - } - - return maxIfs; -} - -function generateIfTestSrc(maxIfs) -{ - let src = ''; - - for (let i = 0; i < maxIfs; ++i) - { - if (i > 0) - { - src += '\nelse '; - } - - if (i < maxIfs - 1) - { - src += `if(test == ${i}.0){}`; - } - } - - return src; -} diff --git a/packages/core/src/renderers/utils/mapWebGLBlendModesToPixi.js b/packages/core/src/renderers/utils/mapWebGLBlendModesToPixi.js deleted file mode 100644 index 10ffe4d..0000000 --- a/packages/core/src/renderers/utils/mapWebGLBlendModesToPixi.js +++ /dev/null @@ -1,47 +0,0 @@ -import { BLEND_MODES } from '@pixi/constants'; - -/** - * Maps gl blend combinations to WebGL. - * - * @memberof PIXI - * @function mapWebGLBlendModesToPixi - * @private - * @param {WebGLRenderingContext} gl - The rendering context. - * @param {string[]} [array=[]] - The array to output into. - * @return {string[]} Mapped modes. - */ -export default function mapWebGLBlendModesToPixi(gl, array = []) -{ - // TODO - premultiply alpha would be different. - // add a boolean for that! - array[BLEND_MODES.NORMAL] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.ADD] = [gl.ONE, gl.DST_ALPHA]; - array[BLEND_MODES.MULTIPLY] = [gl.DST_COLOR, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.SCREEN] = [gl.ONE, gl.ONE_MINUS_SRC_COLOR]; - array[BLEND_MODES.OVERLAY] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.DARKEN] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.LIGHTEN] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.COLOR_DODGE] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.COLOR_BURN] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.HARD_LIGHT] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.SOFT_LIGHT] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.DIFFERENCE] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.EXCLUSION] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.HUE] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.SATURATION] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.COLOR] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.LUMINOSITY] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.NONE] = [0, 0]; - - // not-premultiplied blend modes - array[BLEND_MODES.NORMAL_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.ADD_NPM] = [gl.SRC_ALPHA, gl.DST_ALPHA, gl.ONE, gl.DST_ALPHA]; - array[BLEND_MODES.SCREEN_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_COLOR, gl.ONE, gl.ONE_MINUS_SRC_COLOR]; - - // not-premultiplied blend modes - array[BLEND_MODES.NORMAL_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.ADD_NPM] = [gl.SRC_ALPHA, gl.DST_ALPHA, gl.ONE, gl.DST_ALPHA]; - array[BLEND_MODES.SCREEN_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_COLOR, gl.ONE, gl.ONE_MINUS_SRC_COLOR]; - - return array; -} diff --git a/packages/core/src/shader/GLProgram.js b/packages/core/src/shader/GLProgram.js new file mode 100644 index 0000000..92980ca --- /dev/null +++ b/packages/core/src/shader/GLProgram.js @@ -0,0 +1,43 @@ +/** + * Helper class to create a webGL Program + * + * @class + * @private + */ +export default class GLProgram +{ + constructor(program, uniformData) + { + /** + * The shader program + * + * @member {WebGLProgram} + */ + this.program = program; + + /** + * holds the uniform data which contains uniform locations + * and current uniform values used for caching and preventing undeed GPU commands + * @type {Object} + */ + this.uniformData = uniformData; + + /** + * uniformGroups holds the various upload functions for the shader. Each uniform group + * and program have a unique upload function generated. + * @type {Object} + */ + this.uniformGroups = {}; + } + + /** + * Destroys this program + * TODO + */ + destroy() + { + this.uniformData = null; + this.uniformGroups = null; + this.program = null; + } +} diff --git a/packages/core/src/shader/Program.js b/packages/core/src/shader/Program.js index 4f9971e..4b4007a 100644 --- a/packages/core/src/shader/Program.js +++ b/packages/core/src/shader/Program.js @@ -1,6 +1,11 @@ -import * as shaderUtils from '../renderers/systems/shader/shader'; +// import * as from '../systems/shader/shader'; +import { setPrecision, + defaultValue, + compileProgram, + mapSize, + mapType, + getTestContext } from './utils'; import { ProgramCache } from '@pixi/utils'; -import getTestContext from './getTestContext'; import defaultFragment from './defaultProgram.frag'; import defaultVertex from './defaultProgram.vert'; import { settings } from '@pixi/settings'; @@ -34,8 +39,8 @@ */ this.fragmentSrc = fragmentSrc || Program.defaultFragmentSrc; - this.vertexSrc = shaderUtils.setPrecision(this.vertexSrc, settings.PRECISION_VERTEX); - this.fragmentSrc = shaderUtils.setPrecision(this.fragmentSrc, settings.PRECISION_FRAGMENT); + this.vertexSrc = setPrecision(this.vertexSrc, settings.PRECISION_VERTEX); + this.fragmentSrc = setPrecision(this.fragmentSrc, settings.PRECISION_FRAGMENT); // currently this does not extract structs only default types this.extractData(this.vertexSrc, this.fragmentSrc); @@ -62,7 +67,7 @@ if (gl) { - const program = shaderUtils.compileProgram(gl, vertexSrc, fragmentSrc); + const program = compileProgram(gl, vertexSrc, fragmentSrc); this.attributeData = this.getAttributeData(program, gl); this.uniformData = this.getUniformData(program, gl); @@ -95,13 +100,13 @@ for (let i = 0; i < totalAttributes; i++) { const attribData = gl.getActiveAttrib(program, i); - const type = shaderUtils.mapType(gl, attribData.type); + const type = mapType(gl, attribData.type); /*eslint-disable */ const data = { type: type, name: attribData.name, - size: shaderUtils.mapSize(type), + size: mapSize(type), location: 0, }; /* eslint-enable */ @@ -145,14 +150,14 @@ const name = uniformData.name.replace(/\[.*?\]/, ''); const isArray = uniformData.name.match(/\[.*?\]/, ''); - const type = shaderUtils.mapType(gl, uniformData.type); + const type = mapType(gl, uniformData.type); /*eslint-disable */ uniforms[name] = { type: type, size: uniformData.size, isArray:isArray, - value: shaderUtils.defaultValue(type, uniformData.size), + value: defaultValue(type, uniformData.size), }; /* eslint-enable */ } diff --git a/packages/core/src/shader/ShaderSystem.js b/packages/core/src/shader/ShaderSystem.js new file mode 100644 index 0000000..40b8af8 --- /dev/null +++ b/packages/core/src/shader/ShaderSystem.js @@ -0,0 +1,171 @@ +import System from '../System'; +import GLProgram from './GLProgram'; +import { generateUniformsSync, + defaultValue, + compileProgram } from './utils'; + +let UID = 0; + +/** + * Helper class to create a webGL Texture + * + * @class + * @memberof PIXI.systems + * @extends PIXI.System + */ +export default class ShaderSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - A reference to the current renderer + */ + constructor(renderer) + { + super(renderer); + + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = null; + + this.shader = null; + this.program = null; + + this.id = UID++; + } + + contextChange(gl) + { + this.gl = gl; + } + + /** + * Changes the current shader to the one given in parameter + * + * @param {PIXI.Shader} shader - the new shader + * @param {boolean} dontSync - false if the shader should automatically sync its uniforms. + * @returns {PIXI.glCore.glProgram} the glProgram that belongs to the shader. + */ + bind(shader, dontSync) + { + shader.uniforms.globals = this.renderer.globalUniforms; + + const program = shader.program; + const glProgram = program.glPrograms[this.renderer.CONTEXT_UID] || this.generateShader(shader); + + this.shader = shader; + + // TODO - some current pixi plugins bypass this.. so it not safe to use yet.. + if (this.program !== program) + { + this.program = program; + this.gl.useProgram(glProgram.program); + } + + if (!dontSync) + { + this.syncUniformGroup(shader.uniformGroup); + } + + return glProgram; + } + + /** + * Uploads the uniforms values to the currently bound shader. + * + * @param {object} uniforms - the uniforms valiues that be applied to the current shader + */ + setUniforms(uniforms) + { + const shader = this.shader.program; + const glProgram = shader.glPrograms[this.renderer.CONTEXT_UID]; + + shader.syncUniforms(glProgram.uniformData, uniforms, this.renderer); + } + + syncUniformGroup(group) + { + const glProgram = this.getglProgram(); + + if (!group.static || group.dirtyId !== glProgram.uniformGroups[group.id]) + { + glProgram.uniformGroups[group.id] = group.dirtyId; + const syncFunc = group.syncUniforms[this.shader.program.id] || this.createSyncGroups(group); + + syncFunc(glProgram.uniformData, group.uniforms, this.renderer); + } + } + + createSyncGroups(group) + { + group.syncUniforms[this.shader.program.id] = generateUniformsSync(group, this.shader.program.uniformData); + + return group.syncUniforms[this.shader.program.id]; + } + + /** + * Returns the underlying GLShade rof the currently bound shader. + * This can be handy for when you to have a little more control over the setting of your uniforms. + * + * @return {PIXI.glCore.Shader} the glProgram for the currently bound Shader for this context + */ + getglProgram() + { + if (this.shader) + { + return this.shader.program.glPrograms[this.renderer.CONTEXT_UID]; + } + + return null; + } + + /** + * Generates a glProgram verion of the Shader provided. + * + * @private + * @param {PIXI.Shader} shader the shader that the glProgram will be based on. + * @return {PIXI.glCore.glProgram} A shiney new glProgram + */ + generateShader(shader) + { + const gl = this.gl; + + const program = shader.program; + + const attribMap = {}; + + for (const i in program.attributeData) + { + attribMap[i] = program.attributeData[i].location; + } + + const shaderProgram = compileProgram(gl, program.vertexSrc, program.fragmentSrc, attribMap); + const uniformData = {}; + + for (const i in program.uniformData) + { + const data = program.uniformData[i]; + + uniformData[i] = { + location: gl.getUniformLocation(shaderProgram, i), + value: defaultValue(data.type, data.size), + }; + } + + const glProgram = new GLProgram(shaderProgram, uniformData); + + program.glPrograms[this.renderer.CONTEXT_UID] = glProgram; + + return glProgram; + } + + /** + * Destroys this System and removes all its textures + */ + destroy() + { + // TODO implement destroy method for ShaderSystem + this.destroyed = true; + } +} diff --git a/packages/core/src/shader/defaultValue.js b/packages/core/src/shader/defaultValue.js deleted file mode 100644 index 847a069..0000000 --- a/packages/core/src/shader/defaultValue.js +++ /dev/null @@ -1,78 +0,0 @@ -function booleanArray(size) -{ - const array = new Array(size); - - for (let i = 0; i < array.length; i++) - { - array[i] = false; - } - - return array; -} - -/** - * @class - * @memberof PIXI.glCore.shader - * @param type {String} Type of value - * @param size {Number} - */ -export default function defaultValue(type, size) -{ - switch (type) - { - case 'float': - return 0; - - case 'vec2': - return new Float32Array(2 * size); - - case 'vec3': - return new Float32Array(3 * size); - - case 'vec4': - return new Float32Array(4 * size); - - case 'int': - case 'sampler2D': - return 0; - - case 'ivec2': - return new Int32Array(2 * size); - - case 'ivec3': - return new Int32Array(3 * size); - - case 'ivec4': - return new Int32Array(4 * size); - - case 'bool': - return false; - - case 'bvec2': - - return booleanArray(2 * size); - - case 'bvec3': - return booleanArray(3 * size); - - case 'bvec4': - return booleanArray(4 * size); - - case 'mat2': - return new Float32Array([1, 0, - 0, 1]); - - case 'mat3': - return new Float32Array([1, 0, 0, - 0, 1, 0, - 0, 0, 1]); - - case 'mat4': - return new Float32Array([1, 0, 0, 0, - 0, 1, 0, 0, - 0, 0, 1, 0, - 0, 0, 0, 1]); - } - - return 0; -} diff --git a/packages/core/src/shader/extractUniformsFromSrc.js b/packages/core/src/shader/extractUniformsFromSrc.js deleted file mode 100644 index 3d02c0a..0000000 --- a/packages/core/src/shader/extractUniformsFromSrc.js +++ /dev/null @@ -1,57 +0,0 @@ -import defaultValue from './defaultValue'; - -export default function extractUniformsFromSrc(vertexSrc, fragmentSrc, mask) -{ - const vertUniforms = extractUniformsFromString(vertexSrc, mask); - const fragUniforms = extractUniformsFromString(fragmentSrc, mask); - - return Object.assign(vertUniforms, fragUniforms); -} - -function extractUniformsFromString(string) -{ - const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); - - const uniforms = {}; - let nameSplit; - - // clean the lines a little - remove extra spaces / teabs etc - // then split along ';' - const lines = string.replace(/\s+/g, ' ') - .split(/\s*;\s*/); - - // loop through.. - for (let i = 0; i < lines.length; i++) - { - const line = lines[i].trim(); - - if (line.indexOf('uniform') > -1) - { - const splitLine = line.split(' '); - const type = splitLine[1]; - - let name = splitLine[2]; - let size = 1; - - if (name.indexOf('[') > -1) - { - // array! - nameSplit = name.split(/\[|]/); - name = nameSplit[0]; - size *= Number(nameSplit[1]); - } - - if (!name.match(maskRegex)) - { - uniforms[name] = { - value: defaultValue(type, size), - dirtyId: 0, - name, - type, - }; - } - } - } - - return uniforms; -} diff --git a/packages/core/src/shader/generateUniformsSync.js b/packages/core/src/shader/generateUniformsSync.js deleted file mode 100644 index 98a357f..0000000 --- a/packages/core/src/shader/generateUniformsSync.js +++ /dev/null @@ -1,231 +0,0 @@ -// cv = CachedValue -// v = value -// ud = uniformData -// uv = uniformValue -// l = loaction -const GLSL_TO_SINGLE_SETTERS_CACHED = { - - float: ` - if(cv !== v) - { - cv.v = v; - gl.uniform1f(location, v) - }`, - - vec2: ` - if(cv[0] !== v[0] || cv[1] !== v[1]) - { - cv[0] = v[0]; - cv[1] = v[1]; - gl.uniform2f(location, v[0], v[1]) - }`, - - vec3: ` - if(cv[0] !== v[0] || cv[1] !== v[1] || cv[2] !== v[2]) - { - cv[0] = v[0]; - cv[1] = v[1]; - cv[2] = v[2]; - - gl.uniform3f(location, v[0], v[1], v[2]) - }`, - - vec4: 'gl.uniform4f(location, v[0], v[1], v[2], v[3])', - - int: 'gl.uniform1i(location, v)', - ivec2: 'gl.uniform2i(location, v[0], v[1])', - ivec3: 'gl.uniform3i(location, v[0], v[1], v[2])', - ivec4: 'gl.uniform4i(location, v[0], v[1], v[2], v[3])', - - bool: 'gl.uniform1i(location, v)', - bvec2: 'gl.uniform2i(location, v[0], v[1])', - bvec3: 'gl.uniform3i(location, v[0], v[1], v[2])', - bvec4: 'gl.uniform4i(location, v[0], v[1], v[2], v[3])', - - mat2: 'gl.uniformMatrix2fv(location, false, v)', - mat3: 'gl.uniformMatrix3fv(location, false, v)', - mat4: 'gl.uniformMatrix4fv(location, false, v)', - - sampler2D: 'gl.uniform1i(location, v)', - samplerCube: 'gl.uniform1i(location, v)', - sampler2DArray: 'gl.uniform1i(location, v)', -}; - -const GLSL_TO_ARRAY_SETTERS = { - - float: `gl.uniform1fv(location, v)`, - - vec2: `gl.uniform2fv(location, v)`, - vec3: `gl.uniform3fv(location, v)`, - vec4: 'gl.uniform4fv(location, v)', - - mat4: 'gl.uniformMatrix4fv(location, false, v)', - mat3: 'gl.uniformMatrix3fv(location, false, v)', - mat2: 'gl.uniformMatrix2fv(location, false, v)', - - int: 'gl.uniform1iv(location, v)', - ivec2: 'gl.uniform2iv(location, v)', - ivec3: 'gl.uniform3iv(location, v)', - ivec4: 'gl.uniform4iv(location, v)', - - bool: 'gl.uniform1iv(location, v)', - bvec2: 'gl.uniform2iv(location, v)', - bvec3: 'gl.uniform3iv(location, v)', - bvec4: 'gl.uniform4iv(location, v)', - - sampler2D: 'gl.uniform1iv(location, v)', - samplerCube: 'gl.uniform1iv(location, v)', - sampler2DArray: 'gl.uniform1iv(location, v)', -}; - -export default function generateUniformsSync(group, uniformData) -{ - let textureCount = 0; - let func = `var v = null; - var cv = null - var gl = renderer.gl`; - - for (const i in group.uniforms) - { - const data = uniformData[i]; - - if (!data) - { - if (group.uniforms[i].group) - { - func += ` - renderer.shader.syncUniformGroup(uv.${i}); - `; - } - - continue; - } - - // TODO && uniformData[i].value !== 0 <-- do we still need this? - if (data.type === 'float' && data.size === 1) - { - func += ` - if(uv.${i} !== ud.${i}.value) - { - ud.${i}.value = uv.${i} - gl.uniform1f(ud.${i}.location, uv.${i}) - }\n`; - } - /* eslint-disable max-len */ - else if ((data.type === 'sampler2D' || data.type === 'samplerCube' || data.type === 'sampler2DArray') && data.size === 1 && !data.isArray) - /* eslint-disable max-len */ - { - func += ` - renderer.texture.bind(uv.${i}, ${textureCount}); - - if(ud.${i}.value !== ${textureCount}) - { - ud.${i}.value = ${textureCount}; - gl.uniform1i(ud.${i}.location, ${textureCount});\n; // eslint-disable-line max-len - }\n`; - - textureCount++; - } - else if (data.type === 'mat3' && data.size === 1) - { - if (group.uniforms[i].a !== undefined) - { - // TODO and some smart caching dirty ids here! - func += ` - gl.uniformMatrix3fv(ud.${i}.location, false, uv.${i}.toArray(true)); - \n`; - } - else - { - func += ` - gl.uniformMatrix3fv(ud.${i}.location, false, uv.${i}); - \n`; - } - } - else if (data.type === 'vec2' && data.size === 1) - { - // TODO - do we need both here? - // maybe we can get away with only using points? - if (group.uniforms[i].x !== undefined) - { - func += ` - cv = ud.${i}.value; - v = uv.${i}; - - if(cv[0] !== v.x || cv[1] !== v.y) - { - cv[0] = v.x; - cv[1] = v.y; - gl.uniform2f(ud.${i}.location, v.x, v.y); - }\n`; - } - else - { - func += ` - cv = ud.${i}.value; - v = uv.${i}; - - if(cv[0] !== v[0] || cv[1] !== v[1]) - { - cv[0] = v[0]; - cv[1] = v[1]; - gl.uniform2f(ud.${i}.location, v[0], v[1]); - } - \n`; - } - } - else if (data.type === 'vec4' && data.size === 1) - { - // TODO - do we need both here? - // maybe we can get away with only using points? - if (group.uniforms[i].width !== undefined) - { - func += ` - cv = ud.${i}.value; - v = uv.${i}; - - if(cv[0] !== v.x || cv[1] !== v.y || cv[2] !== v.width || cv[3] !== v.height) - { - cv[0] = v.x; - cv[1] = v.y; - cv[2] = v.width; - cv[3] = v.height; - gl.uniform4f(ud.${i}.location, v.x, v.y, v.width, v.height) - }\n`; - } - else - { - func += ` - cv = ud.${i}.value; - v = uv.${i}; - - if(cv[0] !== v[0] || cv[1] !== v[1] || cv[2] !== v[2] || cv[3] !== v[3]) - { - cv[0] = v[0]; - cv[1] = v[1]; - cv[2] = v[2]; - cv[3] = v[3]; - - gl.uniform4f(ud.${i}.location, v[0], v[1], v[2], v[3]) - } - \n`; - } - } - else - { - const templateType = (data.size === 1) ? GLSL_TO_SINGLE_SETTERS_CACHED : GLSL_TO_ARRAY_SETTERS; - - const template = templateType[data.type].replace('location', `ud.${i}.location`); - - func += ` - cv = ud.${i}.value; - v = uv.${i}; - ${template};\n`; - } - } - - // console.log(' --------------- ') - // console.log(func); - - return new Function('ud', 'uv', 'renderer', func); // eslint-disable-line no-new-func -} diff --git a/packages/core/src/shader/getTestContext.js b/packages/core/src/shader/getTestContext.js deleted file mode 100644 index ea174cc..0000000 --- a/packages/core/src/shader/getTestContext.js +++ /dev/null @@ -1,48 +0,0 @@ -import { settings } from '@pixi/settings'; - -let context = null; - -/** - * returns a little webGL context to use for program inspection. - * - * @static - * @private - * @returns {webGL-context} a gl context to test with - */ -export default function getTestContext() -{ - if (!context) - { - const canvas = document.createElement('canvas'); - - let gl; - - if (settings.PREFER_WEBGL_2) - { - gl = canvas.getContext('webgl2', {}); - } - - if (!gl) - { - gl = canvas.getContext('webgl', {}) - || canvas.getContext('experimental-webgl', {}); - - if (!gl) - { - // fail, not able to get a context - throw new Error('This browser does not support webGL. Try using the canvas renderer'); - } - else - { - // for shader testing.. - gl.getExtension('WEBGL_draw_buffers'); - } - } - - context = gl; - - return gl; - } - - return context; -} diff --git a/packages/core/src/shader/utils/checkMaxIfStatmentsInShader.js b/packages/core/src/shader/utils/checkMaxIfStatmentsInShader.js new file mode 100644 index 0000000..8047c96 --- /dev/null +++ b/packages/core/src/shader/utils/checkMaxIfStatmentsInShader.js @@ -0,0 +1,58 @@ +const fragTemplate = [ + 'precision mediump float;', + 'void main(void){', + 'float test = 0.1;', + '%forloop%', + 'gl_FragColor = vec4(0.0);', + '}', +].join('\n'); + +export default function checkMaxIfStatmentsInShader(maxIfs, gl) +{ + if (maxIfs === 0) + { + throw new Error('Invalid value of `0` passed to `checkMaxIfStatementsInShader`'); + } + + const shader = gl.createShader(gl.FRAGMENT_SHADER); + + while (true) // eslint-disable-line no-constant-condition + { + const fragmentSrc = fragTemplate.replace(/%forloop%/gi, generateIfTestSrc(maxIfs)); + + gl.shaderSource(shader, fragmentSrc); + gl.compileShader(shader); + + if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) + { + maxIfs = (maxIfs / 2) | 0; + } + else + { + // valid! + break; + } + } + + return maxIfs; +} + +function generateIfTestSrc(maxIfs) +{ + let src = ''; + + for (let i = 0; i < maxIfs; ++i) + { + if (i > 0) + { + src += '\nelse '; + } + + if (i < maxIfs - 1) + { + src += `if(test == ${i}.0){}`; + } + } + + return src; +} diff --git a/packages/core/src/shader/utils/compileProgram.js b/packages/core/src/shader/utils/compileProgram.js new file mode 100644 index 0000000..8c6ccb9 --- /dev/null +++ b/packages/core/src/shader/utils/compileProgram.js @@ -0,0 +1,78 @@ +/** + * @method compileProgram + * @memberof PIXI.glCore.shader + * @param gl {WebGLRenderingContext} The current WebGL context {WebGLProgram} + * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. + * @param fragmentSrc {string|string[]} The fragment shader source as an array of strings. + * @param attributeLocations {Object} An attribute location map that lets you manually set the attribute locations + * @return {WebGLProgram} the shader program + */ +export default function compileProgram(gl, vertexSrc, fragmentSrc, attributeLocations) +{ + const glVertShader = compileShader(gl, gl.VERTEX_SHADER, vertexSrc); + const glFragShader = compileShader(gl, gl.FRAGMENT_SHADER, fragmentSrc); + + let program = gl.createProgram(); + + gl.attachShader(program, glVertShader); + gl.attachShader(program, glFragShader); + + // optionally, set the attributes manually for the program rather than letting WebGL decide.. + if (attributeLocations) + { + for (const i in attributeLocations) + { + gl.bindAttribLocation(program, attributeLocations[i], i); + } + } + + gl.linkProgram(program); + + // if linking fails, then log and cleanup + if (!gl.getProgramParameter(program, gl.LINK_STATUS)) + { + console.error('Pixi.js Error: Could not initialize shader.'); + console.error('gl.VALIDATE_STATUS', gl.getProgramParameter(program, gl.VALIDATE_STATUS)); + console.error('gl.getError()', gl.getError()); + + // if there is a program info log, log it + if (gl.getProgramInfoLog(program) !== '') + { + console.warn('Pixi.js Warning: gl.getProgramInfoLog()', gl.getProgramInfoLog(program)); + } + + gl.deleteProgram(program); + program = null; + } + + // clean up some shaders + gl.deleteShader(glVertShader); + gl.deleteShader(glFragShader); + + return program; +} + +/** + * @private + * @param gl {WebGLRenderingContext} The current WebGL context {WebGLProgram} + * @param type {Number} the type, can be either VERTEX_SHADER or FRAGMENT_SHADER + * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. + * @return {WebGLShader} the shader + */ +function compileShader(gl, type, src) +{ + const shader = gl.createShader(type); + + gl.shaderSource(shader, src); + gl.compileShader(shader); + + if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) + { + console.warn(src); + console.error(gl.getShaderInfoLog(shader)); + + return null; + } + + return shader; +} diff --git a/packages/core/src/shader/utils/defaultValue.js b/packages/core/src/shader/utils/defaultValue.js new file mode 100644 index 0000000..97290bd --- /dev/null +++ b/packages/core/src/shader/utils/defaultValue.js @@ -0,0 +1,79 @@ +/** + * @method defaultValue + * @memberof PIXI.glCore.shader + * @param type {String} Type of value + * @param size {Number} + */ +export default function defaultValue(type, size) +{ + switch (type) + { + case 'float': + return 0; + + case 'vec2': + return new Float32Array(2 * size); + + case 'vec3': + return new Float32Array(3 * size); + + case 'vec4': + return new Float32Array(4 * size); + + case 'int': + case 'sampler2D': + case 'sampler2DArray': + return 0; + + case 'ivec2': + return new Int32Array(2 * size); + + case 'ivec3': + return new Int32Array(3 * size); + + case 'ivec4': + return new Int32Array(4 * size); + + case 'bool': + return false; + + case 'bvec2': + + return booleanArray(2 * size); + + case 'bvec3': + return booleanArray(3 * size); + + case 'bvec4': + return booleanArray(4 * size); + + case 'mat2': + return new Float32Array([1, 0, + 0, 1]); + + case 'mat3': + return new Float32Array([1, 0, 0, + 0, 1, 0, + 0, 0, 1]); + + case 'mat4': + return new Float32Array([1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1]); + } + + return null; +} + +function booleanArray(size) +{ + const array = new Array(size); + + for (let i = 0; i < array.length; i++) + { + array[i] = false; + } + + return array; +} diff --git a/packages/core/src/shader/utils/extractUniformsFromSrc.js b/packages/core/src/shader/utils/extractUniformsFromSrc.js new file mode 100644 index 0000000..3d02c0a --- /dev/null +++ b/packages/core/src/shader/utils/extractUniformsFromSrc.js @@ -0,0 +1,57 @@ +import defaultValue from './defaultValue'; + +export default function extractUniformsFromSrc(vertexSrc, fragmentSrc, mask) +{ + const vertUniforms = extractUniformsFromString(vertexSrc, mask); + const fragUniforms = extractUniformsFromString(fragmentSrc, mask); + + return Object.assign(vertUniforms, fragUniforms); +} + +function extractUniformsFromString(string) +{ + const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); + + const uniforms = {}; + let nameSplit; + + // clean the lines a little - remove extra spaces / teabs etc + // then split along ';' + const lines = string.replace(/\s+/g, ' ') + .split(/\s*;\s*/); + + // loop through.. + for (let i = 0; i < lines.length; i++) + { + const line = lines[i].trim(); + + if (line.indexOf('uniform') > -1) + { + const splitLine = line.split(' '); + const type = splitLine[1]; + + let name = splitLine[2]; + let size = 1; + + if (name.indexOf('[') > -1) + { + // array! + nameSplit = name.split(/\[|]/); + name = nameSplit[0]; + size *= Number(nameSplit[1]); + } + + if (!name.match(maskRegex)) + { + uniforms[name] = { + value: defaultValue(type, size), + dirtyId: 0, + name, + type, + }; + } + } + } + + return uniforms; +} diff --git a/packages/core/src/shader/utils/generateUniformsSync.js b/packages/core/src/shader/utils/generateUniformsSync.js new file mode 100644 index 0000000..98a357f --- /dev/null +++ b/packages/core/src/shader/utils/generateUniformsSync.js @@ -0,0 +1,231 @@ +// cv = CachedValue +// v = value +// ud = uniformData +// uv = uniformValue +// l = loaction +const GLSL_TO_SINGLE_SETTERS_CACHED = { + + float: ` + if(cv !== v) + { + cv.v = v; + gl.uniform1f(location, v) + }`, + + vec2: ` + if(cv[0] !== v[0] || cv[1] !== v[1]) + { + cv[0] = v[0]; + cv[1] = v[1]; + gl.uniform2f(location, v[0], v[1]) + }`, + + vec3: ` + if(cv[0] !== v[0] || cv[1] !== v[1] || cv[2] !== v[2]) + { + cv[0] = v[0]; + cv[1] = v[1]; + cv[2] = v[2]; + + gl.uniform3f(location, v[0], v[1], v[2]) + }`, + + vec4: 'gl.uniform4f(location, v[0], v[1], v[2], v[3])', + + int: 'gl.uniform1i(location, v)', + ivec2: 'gl.uniform2i(location, v[0], v[1])', + ivec3: 'gl.uniform3i(location, v[0], v[1], v[2])', + ivec4: 'gl.uniform4i(location, v[0], v[1], v[2], v[3])', + + bool: 'gl.uniform1i(location, v)', + bvec2: 'gl.uniform2i(location, v[0], v[1])', + bvec3: 'gl.uniform3i(location, v[0], v[1], v[2])', + bvec4: 'gl.uniform4i(location, v[0], v[1], v[2], v[3])', + + mat2: 'gl.uniformMatrix2fv(location, false, v)', + mat3: 'gl.uniformMatrix3fv(location, false, v)', + mat4: 'gl.uniformMatrix4fv(location, false, v)', + + sampler2D: 'gl.uniform1i(location, v)', + samplerCube: 'gl.uniform1i(location, v)', + sampler2DArray: 'gl.uniform1i(location, v)', +}; + +const GLSL_TO_ARRAY_SETTERS = { + + float: `gl.uniform1fv(location, v)`, + + vec2: `gl.uniform2fv(location, v)`, + vec3: `gl.uniform3fv(location, v)`, + vec4: 'gl.uniform4fv(location, v)', + + mat4: 'gl.uniformMatrix4fv(location, false, v)', + mat3: 'gl.uniformMatrix3fv(location, false, v)', + mat2: 'gl.uniformMatrix2fv(location, false, v)', + + int: 'gl.uniform1iv(location, v)', + ivec2: 'gl.uniform2iv(location, v)', + ivec3: 'gl.uniform3iv(location, v)', + ivec4: 'gl.uniform4iv(location, v)', + + bool: 'gl.uniform1iv(location, v)', + bvec2: 'gl.uniform2iv(location, v)', + bvec3: 'gl.uniform3iv(location, v)', + bvec4: 'gl.uniform4iv(location, v)', + + sampler2D: 'gl.uniform1iv(location, v)', + samplerCube: 'gl.uniform1iv(location, v)', + sampler2DArray: 'gl.uniform1iv(location, v)', +}; + +export default function generateUniformsSync(group, uniformData) +{ + let textureCount = 0; + let func = `var v = null; + var cv = null + var gl = renderer.gl`; + + for (const i in group.uniforms) + { + const data = uniformData[i]; + + if (!data) + { + if (group.uniforms[i].group) + { + func += ` + renderer.shader.syncUniformGroup(uv.${i}); + `; + } + + continue; + } + + // TODO && uniformData[i].value !== 0 <-- do we still need this? + if (data.type === 'float' && data.size === 1) + { + func += ` + if(uv.${i} !== ud.${i}.value) + { + ud.${i}.value = uv.${i} + gl.uniform1f(ud.${i}.location, uv.${i}) + }\n`; + } + /* eslint-disable max-len */ + else if ((data.type === 'sampler2D' || data.type === 'samplerCube' || data.type === 'sampler2DArray') && data.size === 1 && !data.isArray) + /* eslint-disable max-len */ + { + func += ` + renderer.texture.bind(uv.${i}, ${textureCount}); + + if(ud.${i}.value !== ${textureCount}) + { + ud.${i}.value = ${textureCount}; + gl.uniform1i(ud.${i}.location, ${textureCount});\n; // eslint-disable-line max-len + }\n`; + + textureCount++; + } + else if (data.type === 'mat3' && data.size === 1) + { + if (group.uniforms[i].a !== undefined) + { + // TODO and some smart caching dirty ids here! + func += ` + gl.uniformMatrix3fv(ud.${i}.location, false, uv.${i}.toArray(true)); + \n`; + } + else + { + func += ` + gl.uniformMatrix3fv(ud.${i}.location, false, uv.${i}); + \n`; + } + } + else if (data.type === 'vec2' && data.size === 1) + { + // TODO - do we need both here? + // maybe we can get away with only using points? + if (group.uniforms[i].x !== undefined) + { + func += ` + cv = ud.${i}.value; + v = uv.${i}; + + if(cv[0] !== v.x || cv[1] !== v.y) + { + cv[0] = v.x; + cv[1] = v.y; + gl.uniform2f(ud.${i}.location, v.x, v.y); + }\n`; + } + else + { + func += ` + cv = ud.${i}.value; + v = uv.${i}; + + if(cv[0] !== v[0] || cv[1] !== v[1]) + { + cv[0] = v[0]; + cv[1] = v[1]; + gl.uniform2f(ud.${i}.location, v[0], v[1]); + } + \n`; + } + } + else if (data.type === 'vec4' && data.size === 1) + { + // TODO - do we need both here? + // maybe we can get away with only using points? + if (group.uniforms[i].width !== undefined) + { + func += ` + cv = ud.${i}.value; + v = uv.${i}; + + if(cv[0] !== v.x || cv[1] !== v.y || cv[2] !== v.width || cv[3] !== v.height) + { + cv[0] = v.x; + cv[1] = v.y; + cv[2] = v.width; + cv[3] = v.height; + gl.uniform4f(ud.${i}.location, v.x, v.y, v.width, v.height) + }\n`; + } + else + { + func += ` + cv = ud.${i}.value; + v = uv.${i}; + + if(cv[0] !== v[0] || cv[1] !== v[1] || cv[2] !== v[2] || cv[3] !== v[3]) + { + cv[0] = v[0]; + cv[1] = v[1]; + cv[2] = v[2]; + cv[3] = v[3]; + + gl.uniform4f(ud.${i}.location, v[0], v[1], v[2], v[3]) + } + \n`; + } + } + else + { + const templateType = (data.size === 1) ? GLSL_TO_SINGLE_SETTERS_CACHED : GLSL_TO_ARRAY_SETTERS; + + const template = templateType[data.type].replace('location', `ud.${i}.location`); + + func += ` + cv = ud.${i}.value; + v = uv.${i}; + ${template};\n`; + } + } + + // console.log(' --------------- ') + // console.log(func); + + return new Function('ud', 'uv', 'renderer', func); // eslint-disable-line no-new-func +} diff --git a/packages/core/src/shader/utils/getTestContext.js b/packages/core/src/shader/utils/getTestContext.js new file mode 100644 index 0000000..ea174cc --- /dev/null +++ b/packages/core/src/shader/utils/getTestContext.js @@ -0,0 +1,48 @@ +import { settings } from '@pixi/settings'; + +let context = null; + +/** + * returns a little webGL context to use for program inspection. + * + * @static + * @private + * @returns {webGL-context} a gl context to test with + */ +export default function getTestContext() +{ + if (!context) + { + const canvas = document.createElement('canvas'); + + let gl; + + if (settings.PREFER_WEBGL_2) + { + gl = canvas.getContext('webgl2', {}); + } + + if (!gl) + { + gl = canvas.getContext('webgl', {}) + || canvas.getContext('experimental-webgl', {}); + + if (!gl) + { + // fail, not able to get a context + throw new Error('This browser does not support webGL. Try using the canvas renderer'); + } + else + { + // for shader testing.. + gl.getExtension('WEBGL_draw_buffers'); + } + } + + context = gl; + + return gl; + } + + return context; +} diff --git a/packages/core/src/shader/utils/index.js b/packages/core/src/shader/utils/index.js new file mode 100644 index 0000000..4bf8dd4 --- /dev/null +++ b/packages/core/src/shader/utils/index.js @@ -0,0 +1,8 @@ +export { default as compileProgram } from './compileProgram'; +export { default as defaultValue } from './defaultValue'; +export { default as setPrecision } from './setPrecision'; +export { default as mapSize } from './mapSize'; +export { default as mapType } from './mapType'; +export { default as generateUniformsSync } from './generateUniformsSync'; +export { default as getTestContext } from './getTestContext'; +export { default as checkMaxIfStatmentsInShader } from './checkMaxIfStatmentsInShader'; diff --git a/packages/core/src/shader/utils/mapSize.js b/packages/core/src/shader/utils/mapSize.js new file mode 100644 index 0000000..b681554 --- /dev/null +++ b/packages/core/src/shader/utils/mapSize.js @@ -0,0 +1,33 @@ +const GLSL_TO_SIZE = { + float: 1, + vec2: 2, + vec3: 3, + vec4: 4, + + int: 1, + ivec2: 2, + ivec3: 3, + ivec4: 4, + + bool: 1, + bvec2: 2, + bvec3: 3, + bvec4: 4, + + mat2: 4, + mat3: 9, + mat4: 16, + + sampler2D: 1, +}; + +/** + * @method mapSize + * @memberof PIXI.glCore.shader + * @param type {String} + * @return {Number} + */ +export default function mapSize(type) +{ + return GLSL_TO_SIZE[type]; +} diff --git a/packages/core/src/shader/utils/mapType.js b/packages/core/src/shader/utils/mapType.js new file mode 100644 index 0000000..d288b7c --- /dev/null +++ b/packages/core/src/shader/utils/mapType.js @@ -0,0 +1,45 @@ +let GL_TABLE = null; + +const GL_TO_GLSL_TYPES = { + FLOAT: 'float', + FLOAT_VEC2: 'vec2', + FLOAT_VEC3: 'vec3', + FLOAT_VEC4: 'vec4', + + INT: 'int', + INT_VEC2: 'ivec2', + INT_VEC3: 'ivec3', + INT_VEC4: 'ivec4', + + BOOL: 'bool', + BOOL_VEC2: 'bvec2', + BOOL_VEC3: 'bvec3', + BOOL_VEC4: 'bvec4', + + FLOAT_MAT2: 'mat2', + FLOAT_MAT3: 'mat3', + FLOAT_MAT4: 'mat4', + + SAMPLER_2D: 'sampler2D', + SAMPLER_CUBE: 'samplerCube', + SAMPLER_2D_ARRAY: 'sampler2DArray', +}; + +export default function mapType(gl, type) +{ + if (!GL_TABLE) + { + const typeNames = Object.keys(GL_TO_GLSL_TYPES); + + GL_TABLE = {}; + + for (let i = 0; i < typeNames.length; ++i) + { + const tn = typeNames[i]; + + GL_TABLE[gl[tn]] = GL_TO_GLSL_TYPES[tn]; + } + } + + return GL_TABLE[type]; +} diff --git a/packages/core/src/shader/utils/setPrecision.js b/packages/core/src/shader/utils/setPrecision.js new file mode 100644 index 0000000..e2c1f34 --- /dev/null +++ b/packages/core/src/shader/utils/setPrecision.js @@ -0,0 +1,18 @@ +/** + * Sets the float precision on the shader. If the precision is already present this function will do nothing + * @param {string} src the shader source + * @param {string} precision The float precision of the shader. Options are 'lowp', 'mediump' or 'highp'. + * + * @return {string} modified shader source + */ +export default function setPrecision(src, precision) +{ + src = src.trim(); + + if (src.substring(0, 9) !== 'precision' && src.substring(0, 1) !== '#') + { + return `precision ${precision} float;\n${src}`; + } + + return src; +} diff --git a/packages/core/src/state/State.js b/packages/core/src/state/State.js new file mode 100644 index 0000000..fa3f221 --- /dev/null +++ b/packages/core/src/state/State.js @@ -0,0 +1,163 @@ +/* eslint-disable max-len */ + +const BLEND = 0; +const OFFSET = 1; +const CULLING = 2; +const DEPTH_TEST = 3; +const WINDING = 4; + +/** + * This is a webGL state. It is passed The WebGL StateManager. + * Each mesh renderered may require webGL to be in a different state. + * For example you may want different blend mode or to enable polygon offsets + * + * @class + * @memberof PIXI + */ +export default class State +{ + /** + * + */ + constructor() + { + this.data = 0; + + this.blendMode = 0; + this.polygonOffset = 0; + + this.blend = true; + // this.depthTest = true; + } + + /** + * Activates blending of the computed fragment color values + * + * @member {boolean} + */ + get blend() + { + return !!(this.data & (1 << BLEND)); + } + + set blend(value) // eslint-disable-line require-jsdoc + { + if (!!(this.data & (1 << BLEND)) !== value) + { + this.data ^= (1 << BLEND); + } + } + + /** + * Activates adding an offset to depth values of polygon's fragments + * + * @member {boolean} + * @default false + */ + get offsets() + { + return !!(this.data & (1 << OFFSET)); + } + + set offsets(value) // eslint-disable-line require-jsdoc + { + if (!!(this.data & (1 << OFFSET)) !== value) + { + this.data ^= (1 << OFFSET); + } + } + + /** + * Activates culling of polygons. + * + * @member {boolean} + * @default false + */ + get culling() + { + return !!(this.data & (1 << CULLING)); + } + + set culling(value) // eslint-disable-line require-jsdoc + { + if (!!(this.data & (1 << CULLING)) !== value) + { + this.data ^= (1 << CULLING); + } + } + + /** + * Activates depth comparisons and updates to the depth buffer. + * + * @member {boolean} + * @default false + */ + get depthTest() + { + return !!(this.data & (1 << DEPTH_TEST)); + } + + set depthTest(value) // eslint-disable-line require-jsdoc + { + if (!!(this.data & (1 << DEPTH_TEST)) !== value) + { + this.data ^= (1 << DEPTH_TEST); + } + } + + /** + * Specifies whether or not front or back-facing polygons can be culled. + * @member {boolean} + * @default false + */ + get clockwiseFrontFace() + { + return !!(this.data & (1 << WINDING)); + } + + set clockwiseFrontFace(value) // eslint-disable-line require-jsdoc + { + if (!!(this.data & (1 << WINDING)) !== value) + { + this.data ^= (1 << WINDING); + } + } + + /** + * The blend mode to be applied when this state is set. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. + * Setting this mode to anything other than NO_BLEND will automatically switch blending on. + * + * @member {boolean} + * @default PIXI.BLEND_MODES.NORMAL + * @see PIXI.BLEND_MODES + */ + get blendMode() + { + return this._blendMode; + } + + set blendMode(value) // eslint-disable-line require-jsdoc + { + // 17 is NO BLEND + this.blend = (value !== 17); + this._blendMode = value; + } + + /** + * The polygon offset. Setting this property to anything other than 0 will automatically enable poygon offset fill. + * + * @member {number} + * @default 0 + */ + get polygonOffset() + { + return this._polygonOffset; + } + + set polygonOffset(value) // eslint-disable-line require-jsdoc + { + this.offsets = !!value; + this._polygonOffset = value; + } +} + diff --git a/packages/core/src/state/StateSystem.js b/packages/core/src/state/StateSystem.js new file mode 100755 index 0000000..39c35bb --- /dev/null +++ b/packages/core/src/state/StateSystem.js @@ -0,0 +1,288 @@ +import mapWebGLBlendModesToPixi from './utils/mapWebGLBlendModesToPixi'; +import System from '../System'; +import WebGLState from './State'; + +const BLEND = 0; +const OFFSET = 1; +const CULLING = 2; +const DEPTH_TEST = 3; +const WINDING = 4; + +/** + * A WebGL state machines + * + * @class + * @extends PIXI.systems.System + * @memberof PIXI.systems + */ +export default class StateSystem extends System +{ + /** + * @param {WebGLRenderingContext} gl - The current WebGL rendering context + */ + constructor(renderer) + { + super(renderer); + + this.gl = null; + + this.maxAttribs = null; + + // check we have vao.. + this.nativeVaoExtension = null; + + this.attribState = null; + + this.stateId = 0; + this.polygonOffset = 0; + this.blendMode = 17; + + this.map = []; + + // map functions for when we set state.. + this.map[BLEND] = this.setBlend; + this.map[OFFSET] = this.setOffset; + this.map[CULLING] = this.setCullFace; + this.map[DEPTH_TEST] = this.setDepthTest; + this.map[WINDING] = this.setFrontFace; + + this.checks = []; + + this.defaultState = new WebGLState(); + this.defaultState.blend = true; + this.defaultState.depth = true; + } + + contextChange(gl) + { + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); + + // check we have vao.. + this.nativeVaoExtension = ( + gl.getExtension('OES_vertex_array_object') + || gl.getExtension('MOZ_OES_vertex_array_object') + || gl.getExtension('WEBKIT_OES_vertex_array_object') + ); + + this.attribState = { + tempAttribState: new Array(this.maxAttribs), + attribState: new Array(this.maxAttribs), + }; + + this.blendModes = mapWebGLBlendModesToPixi(gl); + + this.setState(this.defaultState); + + this.reset(); + } + + /** + * Sets the current state + * + * @param {*} state - The state to set. + */ + setState(state) + { + state = state || this.defaultState; + + // TODO maybe to an object check? ( this.state === state )? + if (this.stateId !== state.data) + { + let diff = this.stateId ^ state.data; + let i = 0; + + // order from least to most common + while (diff) + { + if (diff & 1) + { + // state change! + this.map[i].call(this, !!(state.data & (1 << i))); + } + + diff = diff >> 1; + i++; + } + + this.stateId = state.data; + } + + // based on the above settings we check for specific modes.. + // for example if blend is active we check and set the blend modes + // or of polygon offset is active we check the poly depth. + for (let i = 0; i < this.checks.length; i++) + { + this.checks[i](this, state); + } + } + + /** + * Enables or disabled blending. + * + * @param {boolean} value - Turn on or off webgl blending. + */ + setBlend(value) + { + this.updateCheck(StateSystem.checkBlendMode, value); + + this.gl[value ? 'enable' : 'disable'](this.gl.BLEND); + } + + /** + * Enables or disable polygon offset fill + * + * @param {boolean} value - Turn on or off webgl polygon offset testing. + */ + setOffset(value) + { + this.gl[value ? 'enable' : 'disable'](this.gl.POLYGON_OFFSET_FILL); + } + + /** + * Sets whether to enable or disable depth test. + * + * @param {boolean} value - Turn on or off webgl depth testing. + */ + setDepthTest(value) + { + this.gl[value ? 'enable' : 'disable'](this.gl.DEPTH_TEST); + } + + /** + * Sets whether to enable or disable cull face. + * + * @param {boolean} value - Turn on or off webgl cull face. + */ + setCullFace(value) + { + this.gl[value ? 'enable' : 'disable'](this.gl.CULL_FACE); + } + + /** + * Sets the gl front face. + * + * @param {boolean} value - true is clockwise and false is counter-clockwise + */ + setFrontFace(value) + { + this.gl.frontFace(this.gl[value ? 'CW' : 'CCW']); + } + + /** + * Sets the blend mode. + * + * @param {number} value - The blend mode to set to. + */ + setBlendMode(value) + { + if (value === this.blendMode) + { + return; + } + + this.blendMode = value; + this.gl.blendFunc(this.blendModes[value][0], this.blendModes[value][1]); + } + + /** + * Sets the polygon offset. + * + * @param {number} value - the polygon offset + * @param {number} scale - the polygon offset scale + */ + setPolygonOffset(value, scale) + { + this.gl.polygonOffset(value, scale); + } + + /** + * Disables all the vaos in use + * + */ + resetAttributes() + { + for (let i = 0; i < this.attribState.tempAttribState.length; i++) + { + this.attribState.tempAttribState[i] = 0; + } + + for (let i = 0; i < this.attribState.attribState.length; i++) + { + this.attribState.attribState[i] = 0; + } + + // im going to assume one is always active for performance reasons. + for (let i = 1; i < this.maxAttribs; i++) + { + this.gl.disableVertexAttribArray(i); + } + } + + // used + /** + * Resets all the logic and disables the vaos + */ + reset() + { + // unbind any VAO if they exist.. + if (this.nativeVaoExtension) + { + this.nativeVaoExtension.bindVertexArrayOES(null); + } + + // reset all attributes.. + this.resetAttributes(); + + this.gl.pixelStorei(this.gl.UNPACK_FLIP_Y_WEBGL, false); + + this.setBlendMode(0); + + // TODO? + // this.setState(this.defaultState); + } + + /** + * checks to see which updates should be checked based on which settings have been activated + * for example if blend is enabled then we shold check the blend modes each time the state is changed + * or if poygon fill is activated then we need to check if the polygone offset changes. + * The idea is that we only check what we have too. + * + * @param {Function} func the checking function to add or remove + * @param {boolean} value should the check function be added or removed. + */ + updateCheck(func, value) + { + const index = this.checks.indexOf(func); + + if (value && index === -1) + { + this.checks.push(func); + } + else if (!value && index !== -1) + { + this.checks.splice(index, 1); + } + } + + /** + * A private little wrapper function that we call to check the blend mode. + * + * @static + * @private + * @param {PIXI.StateSystem} System the System to perform the state check on + * @param {PIXI.State} state the state that the blendMode will pulled from + */ + static checkBlendMode(system, state) + { + system.setBlendMode(state.blendMode); + } + + // TODO - add polygon offset? +} diff --git a/packages/core/src/state/utils/mapWebGLBlendModesToPixi.js b/packages/core/src/state/utils/mapWebGLBlendModesToPixi.js new file mode 100644 index 0000000..10ffe4d --- /dev/null +++ b/packages/core/src/state/utils/mapWebGLBlendModesToPixi.js @@ -0,0 +1,47 @@ +import { BLEND_MODES } from '@pixi/constants'; + +/** + * Maps gl blend combinations to WebGL. + * + * @memberof PIXI + * @function mapWebGLBlendModesToPixi + * @private + * @param {WebGLRenderingContext} gl - The rendering context. + * @param {string[]} [array=[]] - The array to output into. + * @return {string[]} Mapped modes. + */ +export default function mapWebGLBlendModesToPixi(gl, array = []) +{ + // TODO - premultiply alpha would be different. + // add a boolean for that! + array[BLEND_MODES.NORMAL] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; + array[BLEND_MODES.ADD] = [gl.ONE, gl.DST_ALPHA]; + array[BLEND_MODES.MULTIPLY] = [gl.DST_COLOR, gl.ONE_MINUS_SRC_ALPHA]; + array[BLEND_MODES.SCREEN] = [gl.ONE, gl.ONE_MINUS_SRC_COLOR]; + array[BLEND_MODES.OVERLAY] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; + array[BLEND_MODES.DARKEN] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; + array[BLEND_MODES.LIGHTEN] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; + array[BLEND_MODES.COLOR_DODGE] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; + array[BLEND_MODES.COLOR_BURN] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; + array[BLEND_MODES.HARD_LIGHT] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; + array[BLEND_MODES.SOFT_LIGHT] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; + array[BLEND_MODES.DIFFERENCE] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; + array[BLEND_MODES.EXCLUSION] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; + array[BLEND_MODES.HUE] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; + array[BLEND_MODES.SATURATION] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; + array[BLEND_MODES.COLOR] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; + array[BLEND_MODES.LUMINOSITY] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; + array[BLEND_MODES.NONE] = [0, 0]; + + // not-premultiplied blend modes + array[BLEND_MODES.NORMAL_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; + array[BLEND_MODES.ADD_NPM] = [gl.SRC_ALPHA, gl.DST_ALPHA, gl.ONE, gl.DST_ALPHA]; + array[BLEND_MODES.SCREEN_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_COLOR, gl.ONE, gl.ONE_MINUS_SRC_COLOR]; + + // not-premultiplied blend modes + array[BLEND_MODES.NORMAL_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; + array[BLEND_MODES.ADD_NPM] = [gl.SRC_ALPHA, gl.DST_ALPHA, gl.ONE, gl.DST_ALPHA]; + array[BLEND_MODES.SCREEN_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_COLOR, gl.ONE, gl.ONE_MINUS_SRC_COLOR]; + + return array; +} diff --git a/packages/core/src/systems.js b/packages/core/src/systems.js new file mode 100644 index 0000000..8edd9d5 --- /dev/null +++ b/packages/core/src/systems.js @@ -0,0 +1,17 @@ +/** + * Systems are individual components to the Renderer pipeline. + * @namespace PIXI.systems + */ +export { default as FilterSystem } from './filters/FilterSystem'; +export { default as BatchSystem } from './batch/BatchSystem'; +export { default as ContextSystem } from './context/ContextSystem'; +export { default as FramebufferSystem } from './framebuffer/FramebufferSystem'; +export { default as GeometrySystem } from './geometry/GeometrySystem'; +export { default as MaskSystem } from './mask/MaskSystem'; +export { default as StencilSystem } from './mask/StencilSystem'; +export { default as ProjectionSystem } from './projection/ProjectionSystem'; +export { default as RenderTextureSystem } from './renderTexture/RenderTextureSystem'; +export { default as ShaderSystem } from './shader/ShaderSystem'; +export { default as StateSystem } from './state/StateSystem'; +export { default as TextureGCSystem } from './textures/TextureGCSystem'; +export { default as TextureSystem } from './textures/TextureSystem'; diff --git a/packages/core/src/AbstractRenderer.js b/packages/core/src/AbstractRenderer.js new file mode 100644 index 0000000..64b2eb1 --- /dev/null +++ b/packages/core/src/AbstractRenderer.js @@ -0,0 +1,353 @@ +import { hex2string, hex2rgb } from '@pixi/utils'; +import { Matrix, Rectangle } from '@pixi/math'; +import { RENDERER_TYPE } from '@pixi/constants'; +import { settings } from '@pixi/settings'; +import { Container } from '@pixi/display'; +import RenderTexture from './renderTexture/RenderTexture'; +import EventEmitter from 'eventemitter3'; + +const tempMatrix = new Matrix(); + +/** + * 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 EventEmitter + * @memberof PIXI + */ +export default class AbstractRenderer extends EventEmitter +{ + // eslint-disable-next-line valid-jsdoc + /** + * @param {string} system - The name of the system this renderer is for. + * @param {object} [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.transparent=false] - If the render view is transparent, default false + * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false + * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. The + * resolution of the renderer retina would be 2. + * @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 {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when rendering, + * stopping pixel interpolation. + */ + constructor(system, options, arg2, arg3) + { + super(); + + // Support for constructor(system, screenWidth, screenHeight, options) + if (typeof options === 'number') + { + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + }, arg3); + } + + // 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 = RENDERER_TYPE.UNKNOWN; + + /** + * Measurements of the screen. (0, 0, screenWidth, screenHeight) + * + * Its safe to use as filterArea or hitArea for 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 1 + */ + this.resolution = options.resolution || settings.RESOLUTION; + + /** + * Whether the render view is transparent + * + * @member {boolean} + */ + this.transparent = options.transparent; + + /** + * Whether css dimensions of canvas view should be resized to screen dimensions automatically + * + * @member {boolean} + */ + this.autoResize = options.autoResize || false; + + /** + * Tracks the blend modes useful for this renderer. + * + * @member {object} + */ + this.blendModes = null; + + /** + * 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; + + /** + * If true PixiJS will Math.floor() x/y values when rendering, stopping pixel interpolation. + * Handy for crisp pixel art and speed on legacy devices. + * + * @member {boolean} + */ + this.roundPixels = options.roundPixels; + + /** + * The background color as a number. + * + * @member {number} + * @private + */ + this._backgroundColor = 0x000000; + + /** + * The background color as an [R, G, B] array. + * + * @member {number[]} + * @private + */ + this._backgroundColorRgba = [0, 0, 0, 0]; + + /** + * The background color as a string. + * + * @member {string} + * @private + */ + this._backgroundColorString = '#000000'; + + this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter + + /** + * This temporary display object used as the parent of the currently being rendered item + * + * @member {PIXI.DisplayObject} + * @private + */ + this._tempDisplayObjectParent = new Container(); + + /** + * The last root object that the renderer tried to render. + * + * @member {PIXI.DisplayObject} + * @private + */ + this._lastObjectRendered = this._tempDisplayObjectParent; + + /** + * Collection of plugins. + * @readonly + * @member {object} + */ + this.plugins = {}; + } + + /** + * Initialize the plugins. + * + * @protected + * @param {object} staticMap - The dictionary of staticly saved plugins. + */ + initPlugins(staticMap) + { + 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() + { + return this.view.width; + } + + /** + * Same as view.height, actual number of pixels in the canvas by vertical + * + * @member {number} + * @readonly + * @default 600 + */ + get height() + { + return this.view.height; + } + + /** + * Resizes the screen and canvas to the specified width and height + * Canvas dimensions are multiplied by resolution + * + * @param {number} screenWidth - the new width of the screen + * @param {number} screenHeight - the new height of the screen + */ + resize(screenWidth, screenHeight) + { + this.screen.width = screenWidth; + this.screen.height = screenHeight; + + this.view.width = screenWidth * this.resolution; + this.view.height = screenHeight * this.resolution; + + if (this.autoResize) + { + this.view.style.width = `${screenWidth}px`; + this.view.style.height = `${screenHeight}px`; + } + } + + /** + * 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 {PIXI.DisplayObject} displayObject - The displayObject the object will be generated from + * @param {number} scaleMode - Should be one of the scaleMode consts + * @param {number} resolution - The resolution / device pixel ratio of the texture being generated + * @param {PIXI.Rectangle} [region] - The region of the displayObject, that shall be rendered, + * if no region is specified, defaults to the local bounds of the displayObject. + * @return {PIXI.Texture} a texture of the graphics object + */ + generateTexture(displayObject, scaleMode, resolution, region) + { + region = region || displayObject.getLocalBounds(); + + // 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(region.width | 0, region.height | 0, scaleMode, resolution); + + tempMatrix.tx = -region.x; + tempMatrix.ty = -region.y; + + this.render(displayObject, renderTexture, false, tempMatrix, true); + + return renderTexture; + } + + /** + * Removes everything from the renderer and optionally removes the Canvas DOM element. + * + * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. + */ + destroy(removeView) + { + 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); + } + + this.plugins = null; + + this.type = RENDERER_TYPE.UNKNOWN; + + this.view = null; + + this.screen = null; + + this.resolution = 0; + + this.transparent = false; + + this.autoResize = false; + + this.blendModes = null; + + this.options = null; + + this.preserveDrawingBuffer = false; + this.clearBeforeRender = false; + + this.roundPixels = false; + + this._backgroundColor = 0; + this._backgroundColorRgba = null; + this._backgroundColorString = null; + + this._tempDisplayObjectParent = null; + this._lastObjectRendered = null; + } + + /** + * The background color to fill if not transparent + * + * @member {number} + */ + get backgroundColor() + { + return this._backgroundColor; + } + + set backgroundColor(value) // eslint-disable-line require-jsdoc + { + this._backgroundColor = value; + this._backgroundColorString = hex2string(value); + hex2rgb(value, this._backgroundColorRgba); + } +} diff --git a/packages/core/src/Renderer.js b/packages/core/src/Renderer.js new file mode 100644 index 0000000..2227e78 --- /dev/null +++ b/packages/core/src/Renderer.js @@ -0,0 +1,338 @@ +import AbstractRenderer from './AbstractRenderer'; +import { sayHello } from '@pixi/utils'; +import MaskSystem from './mask/MaskSystem'; +import StencilSystem from './mask/StencilSystem'; +import FilterSystem from './filters/FilterSystem'; +import FramebufferSystem from './framebuffer/FramebufferSystem'; +import RenderTextureSystem from './renderTexture/RenderTextureSystem'; +import TextureSystem from './textures/TextureSystem'; +import ProjectionSystem from './projection/ProjectionSystem'; +import StateSystem from './state/StateSystem'; +import GeometrySystem from './geometry/GeometrySystem'; +import ShaderSystem from './shader/ShaderSystem'; +import ContextSystem from './context/ContextSystem'; +import BatchSystem from './batch/BatchSystem'; +import TextureGCSystem from './textures/TextureGCSystem'; +import { RENDERER_TYPE } from '@pixi/constants'; +import UniformGroup from './shader/UniformGroup'; +import { Matrix } from '@pixi/math'; +import Runner from 'mini-runner'; + +/** + * 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 Render works by automatically managing webGLBatchs. + * 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 :) + * + * @class + * @memberof PIXI + * @extends PIXI.AbstractRenderer + */ +export default class Renderer extends AbstractRenderer +{ + // eslint-disable-next-line valid-jsdoc + /** + * + * @param {object} [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.transparent=false] - If the render view is transparent, default false + * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false + * @param {boolean} [options.antialias=false] - sets antialias. If not available natively then FXAA + * antialiasing is used + * @param {boolean} [options.forceFXAA=false] - forces FXAA antialiasing to be used over native. + * FXAA is faster, but may not always look as great + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. + * The resolution of the renderer retina would be 2. + * @param {boolean} [options.clearBeforeRender=true] - This sets if the renderer will clear + * the canvas or not before the new render pass. If you wish to set this to false, you *must* set + * preserveDrawingBuffer to `true`. + * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, + * enable this if you need to call toDataUrl on the webgl context. + * @param {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when + * rendering, stopping pixel interpolation. + * @param {number} [options.backgroundColor=0x000000] - The background color of the rendered area + * (shown if not transparent). + * @param {boolean} [options.legacy=false] - If true PixiJS will aim to ensure compatibility + * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. + * @param {string} [options.powerPreference] - Parameter passed to webgl context, set to "high-performance" + * for devices with dual graphics card + */ + constructor(options, arg2, arg3) + { + super('WebGL', options, arg2, arg3); + + /** + * The type of this renderer as a standardised const + * + * @member {number} + * @see PIXI.RENDERER_TYPE + */ + this.type = RENDERER_TYPE.WEBGL; + + // this will be set by the contextSystem (this.context) + this.gl = null; + this.CONTEXT_UID = 0; + this.legacy = !!options.legacy; + + // TODO legacy! + + // runners! + this.runners = { + destroy: new Runner('destroy'), + contextChange: new Runner('contextChange', 1), + reset: new Runner('reset'), + update: new Runner('update'), + postrender: new Runner('postrender'), + prerender: new Runner('prerender'), + resize: new Runner('resize', 2), + }; + + 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(GeometrySystem, 'geometry') + .addSystem(FramebufferSystem, 'framebuffer') + .addSystem(StencilSystem, 'stencil') + .addSystem(ProjectionSystem, 'projection') + .addSystem(TextureGCSystem, 'textureGC') + .addSystem(FilterSystem, 'filter') + .addSystem(RenderTextureSystem, 'renderTexture') + .addSystem(BatchSystem, 'batch'); + + this.initPlugins(Renderer.__plugins); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ + if (options.context) + { + this.context.initFromContext(options.context); + } + else + { + this.context.initFromOptions({ + alpha: this.transparent, + antialias: options.antialias, + premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', + stencil: true, + 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); + } + + /** + * Add a new system to the renderer. + * @param {class} ClassRef - Class reference + * @param {string} [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. + * @return {PIXI.Renderer} Return instance of renderer + */ + addSystem(ClassRef, name) + { + if (!name) + { + name = ClassRef.name; + } + + // TODO - read name from class.name.. + + /* + if(name.includes('System')) + { + name = name.replace('System', ''); + name = name.charAt(0).toLowerCase() + name.slice(1); + } + */ + + const system = new ClassRef(this); + + if (this[name]) + { + throw new Error(`Whoops! ${name} is already a manger`); + } + + this[name] = system; + + for (const i in this.runners) + { + this.runners[i].add(system); + } + + return this; + + /** + * 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. + */ + } + + /** + * Renders the object to its webGL view + * + * @param {PIXI.DisplayObject} displayObject - the object to be rendered + * @param {PIXI.RenderTexture} renderTexture - The render texture to render to. + * @param {boolean} [clear] - Should the canvas be cleared before the new render + * @param {PIXI.Transform} [transform] - A transform to apply to the render texture before rendering. + * @param {boolean} [skipUpdateTransform] - Should we skip the update transform pass? + */ + render(displayObject, renderTexture, clear, transform, skipUpdateTransform) + { + // can be handy to know! + this.renderingToScreen = !renderTexture; + + this.runners.prerender.run(); + this.emit('prerender'); + + // 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.parent; + + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = 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.run(); + + this.emit('postrender'); + } + + /** + * Resizes the webGL view to the specified width and height. + * + * @param {number} screenWidth - the new width of the screen + * @param {number} screenHeight - the new height of the screen + */ + resize(screenWidth, screenHeight) + { + AbstractRenderer.prototype.resize.call(this, screenWidth, screenHeight); + + this.runners.resize.run(screenWidth, screenHeight); + } + + /** + * Resets the WebGL state so you can render things however you fancy! + * + * @return {PIXI.Renderer} Returns itself. + */ + reset() + { + this.runners.reset.run(); + + return this; + } + + clear() + { + this.framebuffer.bind(); + this.framebuffer.clear(); + } + + /** + * Removes everything from the renderer (event listeners, spritebatch, etc...) + * + * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. + * See: https://github.com/pixijs/pixi.js/issues/2233 + */ + destroy(removeView) + { + this.runners.destroy.run(); + + // call base destroy + super.destroy(removeView); + + // TODO nullify all the managers.. + this.gl = null; + } + + /** + * 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 + * builds and excluding plugins. + * @name PIXI.Renderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. + */ + + /** + * Adds a plugin to the renderer. + * + * @method + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + static registerPlugin(pluginName, ctor) + { + Renderer.__plugins = Renderer.__plugins || {}; + Renderer.__plugins[pluginName] = ctor; + } +} diff --git a/packages/core/src/System.js b/packages/core/src/System.js new file mode 100644 index 0000000..05a10c8 --- /dev/null +++ b/packages/core/src/System.js @@ -0,0 +1,42 @@ +/** + * System is a base class used for extending systems used by the {@link PIXI.Renderer} + * @see PIXI.Renderer#addSystem + * @class + * @memberof PIXI + */ +export default class System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + /** + * The renderer this manager works for. + * + * @member {PIXI.Renderer} + */ + this.renderer = renderer; + + this.renderer.runners.contextChange.add(this); + } + + /** + * Generic method called when there is a WebGL context change. + * + */ + contextChange() + { + // do some codes init! + } + + /** + * Generic destroy methods to be overridden by the subclass + * + */ + destroy() + { + this.renderer.runners.contextChange.remove(this); + this.renderer = null; + } +} diff --git a/packages/core/src/batch/BatchSystem.js b/packages/core/src/batch/BatchSystem.js new file mode 100644 index 0000000..b04c249 --- /dev/null +++ b/packages/core/src/batch/BatchSystem.js @@ -0,0 +1,68 @@ +import System from '../System'; +import ObjectRenderer from './ObjectRenderer'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class BatchSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + super(renderer); + + /** + * An empty renderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.emptyRenderer = new ObjectRenderer(renderer); + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.currentRenderer = this.emptyRenderer; + } + + /** + * Changes the current renderer to the one given in parameter + * + * @param {PIXI.ObjectRenderer} objectRenderer - The object renderer to use. + */ + setObjectRenderer(objectRenderer) + { + if (this.currentRenderer === objectRenderer) + { + return; + } + + this.currentRenderer.stop(); + this.currentRenderer = objectRenderer; + + this.renderer.state.setState(objectRenderer.state); + + this.currentRenderer.start(); + } + + /** + * This should be called if you wish to do some custom rendering + * It will basically render anything that may be batched up such as sprites + * + */ + flush() + { + this.setObjectRenderer(this.emptyRenderer); + } + + reset() + { + this.setObjectRenderer(this.emptyRenderer); + } +} diff --git a/packages/core/src/batch/ObjectRenderer.js b/packages/core/src/batch/ObjectRenderer.js new file mode 100644 index 0000000..0c851a9 --- /dev/null +++ b/packages/core/src/batch/ObjectRenderer.js @@ -0,0 +1,48 @@ +import System from '../System'; + +/** + * Base for a common object renderer that can be used as a system renderer plugin. + * + * @class + * @extends PIXI.System + * @memberof PIXI + */ +export default class ObjectRenderer extends System +{ + /** + * Starts the renderer and sets the shader + * + */ + start() + { + // set the shader.. + } + + /** + * Stops the renderer + * + */ + stop() + { + this.flush(); + } + + /** + * Stub method for rendering content and emptying the current batch. + * + */ + flush() + { + // flush! + } + + /** + * Renders an object + * + * @param {PIXI.DisplayObject} object - The object to render. + */ + render(object) // eslint-disable-line no-unused-vars + { + // render the object + } +} diff --git a/packages/core/src/context/ContextSystem.js b/packages/core/src/context/ContextSystem.js new file mode 100644 index 0000000..301aab4 --- /dev/null +++ b/packages/core/src/context/ContextSystem.js @@ -0,0 +1,186 @@ +import System from '../System'; +import { settings } from '@pixi/settings'; + +let CONTEXT_UID = 0; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class ContextSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.webGLVersion = 1; + + this.handleContextLost = this.handleContextLost.bind(this); + this.handleContextRestored = this.handleContextRestored.bind(this); + + this.extensions = {}; + + renderer.view.addEventListener('webglcontextlost', this.handleContextLost, false); + renderer.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + } + + get isLost() + { + return (!this.gl || this.gl.isContextLost()); + } + + contextChange(gl) + { + this.gl = gl; + + // restore a context if it was previously lost + if (gl.isContextLost() && gl.getExtension('WEBGL_lose_context')) + { + gl.getExtension('WEBGL_lose_context').restoreContext(); + } + } + + initFromContext(gl) + { + this.gl = gl; + this.validateContext(gl); + this.renderer.gl = gl; + this.renderer.CONTEXT_UID = CONTEXT_UID++; + this.renderer.runners.contextChange.run(gl); + } + + initFromOptions(options) + { + const gl = this.createContext(this.renderer.view, options); + + this.initFromContext(gl); + } + + /** + * Helper class to create a webGL Context + * + * @class + * @memberof PIXI.glCore + * @param canvas {HTMLCanvasElement} the canvas element that we will get the context from + * @param options {Object} An options object that gets passed in to the canvas element containing the context attributes, + * see https://developer.mozilla.org/en/docs/Web/API/HTMLCanvasElement/getContext for the options available + * @return {WebGLRenderingContext} the WebGL context + */ + createContext(canvas, options) + { + let gl; + + if (settings.PREFER_WEBGL_2) + { + gl = canvas.getContext('webgl2', options); + } + + if (gl) + { + this.webGLVersion = 2; + } + else + { + this.webGLVersion = 1; + + gl = canvas.getContext('webgl', options) + || canvas.getContext('experimental-webgl', options); + + if (!gl) + { + // fail, not able to get a context + throw new Error('This browser does not support webGL. Try using the canvas renderer'); + } + } + + this.gl = gl; + + this.getExtensions(); + + return gl; + } + + getExtensions() + { + // time to set up default etensions that pixi uses.. + const gl = this.gl; + const extensions = this.extensions; + + if (this.webGLVersion === 1) + { + extensions.drawBuffers = gl.getExtension('WEBGL_draw_buffers'); + extensions.depthTexture = gl.getExtension('WEBKIT_WEBGL_depth_texture'); + extensions.floatTexture = gl.getExtension('OES_texture_float'); + extensions.loseContext = gl.getExtension('WEBGL_lose_context'); + + extensions.vertexArrayObject = gl.getExtension('OES_vertex_array_object') + || gl.getExtension('MOZ_OES_vertex_array_object') + || gl.getExtension('WEBKIT_OES_vertex_array_object'); + } + + // we don't use any specific WebGL 2 ones yet! + } + + /** + * Handles a lost webgl context + * + * @private + * @param {WebGLContextEvent} event - The context lost event. + */ + handleContextLost(event) + { + event.preventDefault(); + } + + /** + * Handles a restored webgl context + * + * @private + */ + handleContextRestored() + { + this.renderer.runners.contextChange.run(this.gl); + } + + destroy() + { + const view = this.renderer.view; + + // remove listeners + view.removeEventListener('webglcontextlost', this.handleContextLost); + view.removeEventListener('webglcontextrestored', this.handleContextRestored); + + this.gl.useProgram(null); + + if (this.extensions.loseContext) + { + this.extensions.loseContext.loseContext(); + } + } + + postrender() + { + this.gl.flush(); + } + + validateContext(gl) + { + const attributes = gl.getContextAttributes(); + + // this is going to be fairly simple for now.. but at least we have room to grow! + if (!attributes.stencil) + { + /* eslint-disable max-len */ + + /* eslint-disable no-console */ + console.warn('Provided WebGL context does not have a stencil buffer, masks may not render correctly'); + /* eslint-enable no-console */ + + /* eslint-enable max-len */ + } + } +} diff --git a/packages/core/src/filters/Filter.js b/packages/core/src/filters/Filter.js new file mode 100644 index 0000000..9d8cad8 --- /dev/null +++ b/packages/core/src/filters/Filter.js @@ -0,0 +1,140 @@ +import Shader from '../shader/Shader'; +import Program from '../shader/Program'; +import State from '../state/State'; +import { settings } from '@pixi/settings'; +// import extractUniformsFromSrc from './extractUniformsFromSrc'; +import defaultVertex from './defaultFilter.vert'; +import defaultFragment from './defaultFilter.frag'; + +// let math = require('../../math'); +/** + * @class + * @memberof PIXI + * @extends PIXI.Shader + */ +export default class Filter extends Shader +{ + /** + * @param {string} [vertexSrc] - The source of the vertex shader. + * @param {string} [fragmentSrc] - The source of the fragment shader. + * @param {object} [uniforms] - Custom uniforms to use to augment the built-in ones. + */ + constructor(vertexSrc, fragmentSrc, uniforms) + { + const program = Program.from(vertexSrc, fragmentSrc); + + super(program, uniforms); + + /** + * The padding of the filter. Some filters require extra space to breath such as a blur. + * Increasing this will add extra width and height to the bounds of the object that the + * filter is applied to. + * + * @member {number} + */ + this.padding = 0; + + /** + * The resolution of the filter. Setting this to be lower will lower the quality but + * increase the performance of the filter. + * + * @member {number} + */ + this.resolution = settings.RESOLUTION; + + /** + * If enabled is true the filter is applied, if false it will not. + * + * @member {boolean} + */ + this.enabled = true; + + /** + * If enabled, PixiJS will fit the filter area into boundaries for better performance. + * Switch it off if it does not work for specific shader. + * + * @member {boolean} + */ + this.autoFit = true; + + /** + * Legacy filters use position and uvs from attributes + * @member {boolean} + * @readonly + */ + this.legacy = !!this.program.attributeData.aTextureCoord; + + /** + * the webGL state the filter requires to render + * @member {PIXI.State} + */ + this.state = new State(); + } + + /** + * Applies the filter + * + * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + * @param {boolean} clear - Should the output be cleared before rendering to it + * @param {object} [currentState] - It's current state of filter. + * There are some useful properties in the currentState : + * target, filters, sourceFrame, destinationFrame, renderTarget, resolution + */ + apply(filterManager, input, output, clear, currentState, derp) // eslint-disable-line no-unused-vars + { + // do as you please! + + filterManager.applyFilter(this, input, output, clear, currentState, derp); + + // or just do a regular render.. + } + + /** + * Sets the blendmode of the filter + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL + */ + get blendMode() + { + return this.state.blendMode; + } + + set blendMode(value) // eslint-disable-line require-jsdoc + { + this.state.blendMode = value; + } + + /** + * The default vertex shader source + * + * @static + * @constant + */ + static get defaultVertexSrc() + { + return defaultVertex; + } + + /** + * The default fragment shader source + * + * @static + * @constant + */ + static get defaultFragmentSrc() + { + return defaultFragment; + } +} + +/** + * Used for caching shader IDs + * + * @static + * @private + */ +Filter.SOURCE_KEY_MAP = {}; + diff --git a/packages/core/src/filters/FilterSystem.js b/packages/core/src/filters/FilterSystem.js new file mode 100644 index 0000000..3174318 --- /dev/null +++ b/packages/core/src/filters/FilterSystem.js @@ -0,0 +1,431 @@ +import System from '../System'; + +import RenderTexture from '../renderTexture/RenderTexture'; +import Quad from '../utils/Quad'; +import QuadUv from '../utils/QuadUv'; +import { Rectangle } from '@pixi/math'; +import * as filterTransforms from './filterTransforms'; +import bitTwiddle from 'bit-twiddle'; +import UniformGroup from '../shader/UniformGroup'; +import { DRAW_MODES } from '@pixi/constants'; + +// +/** + * @ignore + * @class + */ +class FilterState +{ + /** + * + */ + constructor() + { + this.renderTexture = null; + this.sourceFrame = new Rectangle(); + this.destinationFrame = new Rectangle(); + this.filters = []; + this.target = null; + this.legacy = false; + this.resolution = 1; + } +} + +/** + * @class + * @memberof PIXI.systems + * @extends PIXI.System + */ +export default class FilterSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + /** + * stores a bunch of PO2 textures used for filtering + * @type {Object} + */ + this.texturePool = {}; + + /** + * a pool for storing filter states, save us creating new ones each tick + * @type {Array} + */ + this.statePool = []; + + /** + * A very simple geometry used when drawing a filter effect to the screen + * @type {Quad} + */ + this.quad = new Quad(); + + this.quadUv = new QuadUv(); + + /** + * Temporary rect for maths + * @type {PIXI.Rectangle} + */ + this.tempRect = new Rectangle(); + + this.activeState = {}; + + /** + * this uniform group is attached to filter uniforms when used + * @type {UniformGroup} + */ + this.globalUniforms = new UniformGroup({ + sourceFrame: this.tempRect, + destinationFrame: this.tempRect, + + // legacy variables + filterArea: new Float32Array(4), + filterClamp: new Float32Array(4), + }, true); + } + + /** + * Adds a new filter to the System. + * + * @param {PIXI.DisplayObject} target - The target of the filter to render. + * @param {PIXI.Filter[]} filters - The filters to apply. + */ + push(target, filters) + { + const renderer = this.renderer; + const filterStack = this.renderer.renderTexture.defaultFilterStack; + const state = this.statePool.pop() || new FilterState(); + + let resolution = filters[0].resolution; + let padding = filters[0].padding; + let autoFit = filters[0].autoFit; + let legacy = filters[0].legacy; + + for (let i = 1; i < filters.length; i++) + { + const filter = filters[i]; + + // lets use the lowest resolution.. + resolution = Math.min(resolution, filter.resolution); + // and the largest amount of padding! + padding = Math.max(padding, filter.padding); + // only auto fit if all filters are autofit + autoFit = autoFit || filter.autoFit; + + legacy = legacy || filter.legacy; + } + + filterStack.push(state); + + state.resolution = resolution; + + state.legacy = legacy; + + // round to whole number based on resolution + // TODO move that to the shader too? + state.sourceFrame = target.filterArea || target.getBounds(true); + + state.sourceFrame.pad(padding); + + if (autoFit) + { + state.sourceFrame.fit(this.renderer.renderTexture.destinationFrame); + } + + state.sourceFrame.round(resolution); + + state.renderTexture = this.getPotFilterTexture(state.sourceFrame.width, state.sourceFrame.height, resolution); + state.filters = filters; + + state.destinationFrame.width = state.renderTexture.width; + state.destinationFrame.height = state.renderTexture.height; + + state.renderTexture.filterFrame = state.sourceFrame; + renderer.renderTexture.bind(state.renderTexture, state.sourceFrame);// /, state.destinationFrame); + renderer.renderTexture.clear(); + } + + /** + * Pops off the filter and applies it. + * + */ + pop() + { + const renderer = this.renderer; + const filterStack = renderer.renderTexture.defaultFilterStack; + const state = filterStack.pop(); + const filters = state.filters; + + this.activeState = state; + + const globalUniforms = this.globalUniforms.uniforms; + + globalUniforms.sourceFrame = state.sourceFrame; + globalUniforms.destinationFrame = state.destinationFrame; + globalUniforms.resolution = state.resolution; + + // only update the rect if its legacy.. + if (state.legacy) + { + const filterArea = globalUniforms.filterArea; + const filterClamp = globalUniforms.filterClamp; + + filterArea[0] = state.destinationFrame.width; + filterArea[1] = state.destinationFrame.height; + filterArea[2] = state.sourceFrame.x; + filterArea[3] = state.sourceFrame.y; + + filterClamp[0] = 0.5 / state.resolution / state.destinationFrame.width; + filterClamp[1] = 0.5 / state.resolution / state.destinationFrame.height; + filterClamp[2] = (state.sourceFrame.width - 0.5) / state.resolution / state.destinationFrame.width; + filterClamp[3] = (state.sourceFrame.height - 0.5) / state.resolution / state.destinationFrame.height; + } + + this.globalUniforms.update(); + + const lastState = filterStack[filterStack.length - 1]; + + if (filters.length === 1) + { + filters[0].apply(this, state.renderTexture, lastState.renderTexture, false, state); + + this.returnFilterTexture(state.renderTexture); + } + else + { + let flip = state.renderTexture; + let flop = this.getPotFilterTexture( + flip.width, + flip.height, + state.resolution + ); + + flop.filterFrame = flip.filterFrame; + + let i = 0; + + for (i = 0; i < filters.length - 1; ++i) + { + filters[i].apply(this, flip, flop, true, state); + + const t = flip; + + flip = flop; + flop = t; + } + + filters[i].apply(this, flip, lastState.renderTexture, false, state); + + this.returnFilterTexture(flip); + this.returnFilterTexture(flop); + } + + this.statePool.push(state); + } + + /** + * Draws a filter. + * + * @param {PIXI.Filter} filter - The filter to draw. + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + * @param {boolean} clear - Should the output be cleared before rendering to it + */ + applyFilter(filter, input, output, clear) + { + const renderer = this.renderer; + + renderer.renderTexture.bind(output, output ? output.filterFrame : null); + + if (clear) + { + // gl.disable(gl.SCISSOR_TEST); + renderer.renderTexture.clear(); + // gl.enable(gl.SCISSOR_TEST); + } + + // set the uniforms.. + filter.uniforms.uSampler = input; + filter.uniforms.filterGlobals = this.globalUniforms; + + // TODO make it so that the order of this does not matter.. + // because it does at the moment cos of global uniforms. + // they need to get resynced + + renderer.state.setState(filter.state); + renderer.shader.bind(filter); + + if (filter.legacy) + { + this.quadUv.map(input._frame, input.filterFrame); + + renderer.geometry.bind(this.quadUv); + renderer.geometry.draw(DRAW_MODES.TRIANGLES); + } + else + { + renderer.geometry.bind(this.quad); + renderer.geometry.draw(DRAW_MODES.TRIANGLE_STRIP); + } + } + + /** + * Calculates the mapped matrix. + * + * TODO playing around here.. this is temporary - (will end up in the shader) + * this returns a matrix that will normalise map filter cords in the filter to screen space + * + * @param {PIXI.Matrix} outputMatrix - the matrix to output to. + * @return {PIXI.Matrix} The mapped matrix. + */ + calculateScreenSpaceMatrix(outputMatrix) + { + const currentState = this.activeState; + + return filterTransforms.calculateScreenSpaceMatrix( + outputMatrix, + currentState.sourceFrame, + currentState.destinationFrame + ); + } + + /** + * This will map the filter coord so that a texture can be used based on the transform of a sprite + * + * @param {PIXI.Matrix} outputMatrix - The matrix to output to. + * @param {PIXI.Sprite} sprite - The sprite to map to. + * @return {PIXI.Matrix} The mapped matrix. + */ + calculateSpriteMatrix(outputMatrix, sprite) + { + const currentState = this.activeState; + + return filterTransforms.calculateSpriteMatrix( + outputMatrix, + currentState.sourceFrame, + currentState.destinationFrame, + sprite + ); + } + + /** + * Destroys this Filter System. + * + * @param {boolean} [contextLost=false] context was lost, do not free shaders + * + */ + destroy(contextLost = false) + { + if (!contextLost) + { + this.emptyPool(); + } + else + { + this.texturePool = {}; + } + } + + /** + * Gets a Power-of-Two render texture. + * + * TODO move to a seperate class could be on renderer? + * also - could cause issue with multiple contexts? + * + * @private + * @param {WebGLRenderingContext} gl - The webgl rendering context + * @param {number} minWidth - The minimum width of the render target. + * @param {number} minHeight - The minimum height of the render target. + * @param {number} resolution - The resolution of the render target. + * @return {PIXI.RenderTarget} The new render target. + */ + getPotFilterTexture(minWidth, minHeight, resolution) + { + minWidth = bitTwiddle.nextPow2(minWidth); + minHeight = bitTwiddle.nextPow2(minHeight); + resolution = resolution || 1; + + const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + + if (!this.texturePool[key]) + { + this.texturePool[key] = []; + } + + let renderTexture = this.texturePool[key].pop(); + + if (!renderTexture) + { + // temporary bypass cache.. + // internally - this will cause a texture to be bound.. + renderTexture = RenderTexture.create({ + width: minWidth, + height: minHeight, + resolution, + }); + } + + return renderTexture; + } + + /** + * Gets extra render texture to use inside current filter + * + * @param {number} resolution resolution of the renderTexture + * @returns {PIXI.RenderTexture} + */ + getFilterTexture(resolution) + { + const rt = this.activeState.renderTexture; + + const filterTexture = this.getPotFilterTexture(rt.width, rt.height, resolution || rt.baseTexture.resolution); + + filterTexture.filterFrame = rt.filterFrame; + + return filterTexture; + } + + /** + * Frees a render texture back into the pool. + * + * @param {PIXI.RenderTarget} renderTexture - The renderTarget to free + */ + returnFilterTexture(renderTexture) + { + renderTexture.filterFrame = null; + + const base = renderTexture.baseTexture; + + const minWidth = base.width; + const minHeight = base.height; + + const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + + this.texturePool[key].push(renderTexture); + } + + /** + * Empties the texture pool. + * + */ + emptyPool() + { + for (const i in this.texturePool) + { + const textures = this.texturePool[i]; + + if (textures) + { + for (let j = 0; j < textures.length; j++) + { + textures[j].destroy(true); + } + } + } + + this.texturePool = {}; + } +} diff --git a/packages/core/src/filters/defaultFilter.frag b/packages/core/src/filters/defaultFilter.frag new file mode 100644 index 0000000..a14c1be --- /dev/null +++ b/packages/core/src/filters/defaultFilter.frag @@ -0,0 +1,22 @@ +varying vec2 vTextureCoord; +varying vec2 vFilterCoord; + +uniform sampler2D uSampler; +uniform sampler2D filterSampler; + +void main(void){ + vec4 masky = texture2D(filterSampler, vFilterCoord); + vec4 sample = texture2D(uSampler, vTextureCoord); + vec4 color; + if(mod(vFilterCoord.x, 1.0) > 0.5) + { + color = vec4(1.0, 0.0, 0.0, 1.0); + } + else + { + color = vec4(0.0, 1.0, 0.0, 1.0); + } + // gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0); + gl_FragColor = mix(sample, masky, 0.5); + gl_FragColor *= sample.a; +} diff --git a/packages/core/src/filters/defaultFilter.vert b/packages/core/src/filters/defaultFilter.vert new file mode 100644 index 0000000..e1febca --- /dev/null +++ b/packages/core/src/filters/defaultFilter.vert @@ -0,0 +1,14 @@ +attribute vec2 aVertexPosition; +attribute vec2 aTextureCoord; + +uniform mat3 projectionMatrix; +uniform mat3 filterMatrix; + +varying vec2 vTextureCoord; +varying vec2 vFilterCoord; + +void main(void){ + gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); + vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy; + vTextureCoord = aTextureCoord ; +} diff --git a/packages/core/src/filters/filterTransforms.js b/packages/core/src/filters/filterTransforms.js new file mode 100644 index 0000000..87a3cd6 --- /dev/null +++ b/packages/core/src/filters/filterTransforms.js @@ -0,0 +1,50 @@ +import { Matrix } from '@pixi/math'; + +/** + * Calculates the mapped matrix + * @param filterArea {Rectangle} The filter area + * @param sprite {Sprite} the target sprite + * @param outputMatrix {Matrix} @alvin + * @private + */ +// this returns a matrix that will normalise map filter cords in the filter to screen space +export function calculateScreenSpaceMatrix(outputMatrix, filterArea, textureSize) +{ + // TODO unwrap? + const mappedMatrix = outputMatrix.identity(); + + mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); + + mappedMatrix.scale(textureSize.width, textureSize.height); + + return mappedMatrix; +} + +export function calculateNormalizedScreenSpaceMatrix(outputMatrix, filterArea, textureSize) +{ + const mappedMatrix = outputMatrix.identity(); + + mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); + + const translateScaleX = (textureSize.width / filterArea.width); + const translateScaleY = (textureSize.height / filterArea.height); + + mappedMatrix.scale(translateScaleX, translateScaleY); + + return mappedMatrix; +} + +// this will map the filter coord so that a texture can be used based on the transform of a sprite +export function calculateSpriteMatrix(outputMatrix, filterArea, textureSize, sprite) +{ + const orig = sprite._texture.orig; + const mappedMatrix = outputMatrix.set(textureSize.width, 0, 0, textureSize.height, filterArea.x, filterArea.y); + const worldTransform = sprite.worldTransform.copyTo(Matrix.TEMP_MATRIX); + + worldTransform.invert(); + mappedMatrix.prepend(worldTransform); + mappedMatrix.scale(1.0 / orig.width, 1.0 / orig.height); + mappedMatrix.translate(sprite.anchor.x, sprite.anchor.y); + + return mappedMatrix; +} diff --git a/packages/core/src/filters/spriteMask/SpriteMaskFilter.js b/packages/core/src/filters/spriteMask/SpriteMaskFilter.js new file mode 100644 index 0000000..91697b7 --- /dev/null +++ b/packages/core/src/filters/spriteMask/SpriteMaskFilter.js @@ -0,0 +1,63 @@ +import Filter from '../Filter'; +import { Matrix } from '@pixi/math'; +import vertex from './spriteMaskFilter.vert'; +import fragment from './spriteMaskFilter.frag'; +import { default as TextureMatrix } from '../../textures/TextureMatrix'; + +/** + * The SpriteMaskFilter class + * + * @class + * @extends PIXI.Filter + * @memberof PIXI + */ +export default class SpriteMaskFilter extends Filter +{ + /** + * @param {PIXI.Sprite} sprite - the target sprite + */ + constructor(sprite) + { + const maskMatrix = new Matrix(); + + super(vertex, fragment); + + sprite.renderable = false; + + this.maskSprite = sprite; + this.maskMatrix = maskMatrix; + } + + /** + * Applies the filter + * + * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + */ + apply(filterManager, input, output) + { + const maskSprite = this.maskSprite; + const tex = this.maskSprite.texture; + + if (!tex.valid) + { + return; + } + if (!tex.transform) + { + // margin = 0.0, let it bleed a bit, shader code becomes easier + // assuming that atlas textures were made with 1-pixel padding + tex.transform = new TextureMatrix(tex, 0.0); + } + tex.transform.update(); + + this.uniforms.mask = tex; + this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite) + .prepend(tex.transform.mapCoord); + this.uniforms.alpha = maskSprite.worldAlpha; + this.uniforms.maskClamp = tex.transform.uClampFrame; + + filterManager.applyFilter(this, input, output); + } +} diff --git a/packages/core/src/filters/spriteMask/spriteMaskFilter.frag b/packages/core/src/filters/spriteMask/spriteMaskFilter.frag new file mode 100644 index 0000000..0e4aef8 --- /dev/null +++ b/packages/core/src/filters/spriteMask/spriteMaskFilter.frag @@ -0,0 +1,23 @@ +varying vec2 vMaskCoord; +varying vec2 vTextureCoord; + +uniform sampler2D uSampler; +uniform sampler2D mask; +uniform float alpha; +uniform vec4 maskClamp; + +void main(void) +{ + float clip = step(3.5, + step(maskClamp.x, vMaskCoord.x) + + step(maskClamp.y, vMaskCoord.y) + + step(vMaskCoord.x, maskClamp.z) + + step(vMaskCoord.y, maskClamp.w)); + + vec4 original = texture2D(uSampler, vTextureCoord); + vec4 masky = texture2D(mask, vMaskCoord); + + original *= (masky.r * masky.a * alpha * clip); + + gl_FragColor = original; +} diff --git a/packages/core/src/filters/spriteMask/spriteMaskFilter.vert b/packages/core/src/filters/spriteMask/spriteMaskFilter.vert new file mode 100644 index 0000000..d23a30c --- /dev/null +++ b/packages/core/src/filters/spriteMask/spriteMaskFilter.vert @@ -0,0 +1,16 @@ +attribute vec2 aVertexPosition; +attribute vec2 aTextureCoord; + +uniform mat3 projectionMatrix; +uniform mat3 otherMatrix; + +varying vec2 vMaskCoord; +varying vec2 vTextureCoord; + +void main(void) +{ + gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); + + vTextureCoord = aTextureCoord; + vMaskCoord = ( otherMatrix * vec3( aTextureCoord, 1.0) ).xy; +} diff --git a/packages/core/src/framebuffer/FrameBuffer.js b/packages/core/src/framebuffer/FrameBuffer.js new file mode 100644 index 0000000..84e9fcc --- /dev/null +++ b/packages/core/src/framebuffer/FrameBuffer.js @@ -0,0 +1,106 @@ +import Texture from '../textures/BaseTexture'; +import { FORMATS, TYPES } from '@pixi/constants'; + +/** + * Frame buffer + * @class + * @memberof PIXI + */ +export default class FrameBuffer +{ + constructor(width, height) + { + this.width = width || 100; + this.height = height || 100; + + this.stencil = false; + this.depth = false; + + this.dirtyId = 0; + this.dirtyFormat = 0; + this.dirtySize = 0; + + this.depthTexture = null; + this.colorTextures = []; + + this.glFrameBuffers = {}; + } + + get colorTexture() + { + return this.colorTextures[0]; + } + + addColorTexture(index, texture) + { + // TODO add some validation to the texture - same width / height etc? + this.colorTextures[index || 0] = texture || new Texture(null, { scaleMode: 0, + resolution: 1, + mipmap: false, + width: this.width, + height: this.height });// || new Texture(); + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + addDepthTexture(texture) + { + /* eslint-disable max-len */ + this.depthTexture = texture || new Texture(null, { scaleMode: 0, + resolution: 1, + width: this.width, + height: this.height, + mipmap: false, + format: FORMATS.DEPTH_COMPONENT, + type: TYPES.UNSIGNED_SHORT });// UNSIGNED_SHORT; + /* eslint-disable max-len */ + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + enableDepth() + { + this.depth = true; + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + enableStencil() + { + this.stencil = true; + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + resize(width, height) + { + if (width === this.width && height === this.height) return; + + this.width = width; + this.height = height; + + this.dirtyId++; + this.dirtySize++; + + for (let i = 0; i < this.colorTextures.length; i++) + { + this.colorTextures[i].setSize(width, height); + } + + if (this.depthTexture) + { + this.depthTexture.setSize(width, height); + } + } +} diff --git a/packages/core/src/framebuffer/FramebufferSystem.js b/packages/core/src/framebuffer/FramebufferSystem.js new file mode 100644 index 0000000..2cc2437 --- /dev/null +++ b/packages/core/src/framebuffer/FramebufferSystem.js @@ -0,0 +1,246 @@ +import System from '../System'; +import { Rectangle } from '@pixi/math'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class FramebufferSystem extends System +{ + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + contextChange() + { + this.gl = this.renderer.gl; + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + this.current = null; + this.viewport = new Rectangle(); + + this.drawBufferExtension = this.renderer.context.extensions.drawBuffers; + } + + bind(framebuffer, frame) + { + const gl = this.gl; + + this.current = framebuffer; + + if (framebuffer) + { + // TODO cacheing layer! + + const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID] || this.initFramebuffer(framebuffer); + + gl.bindFramebuffer(gl.FRAMEBUFFER, fbo.framebuffer); + // makesure all textures are unbound.. + + // now check for updates... + if (fbo.dirtyId !== framebuffer.dirtyId) + { + fbo.dirtyId = framebuffer.dirtyId; + + if (fbo.dirtyFormat !== framebuffer.dirtyFormat) + { + fbo.dirtyFormat = framebuffer.dirtyFormat; + this.updateFramebuffer(framebuffer); + } + else if (fbo.dirtySize !== framebuffer.dirtySize) + { + fbo.dirtySize = framebuffer.dirtySize; + this.resizeFramebuffer(framebuffer); + } + } + + for (let i = 0; i < framebuffer.colorTextures.length; i++) + { + if (framebuffer.colorTextures[i].texturePart) + { + this.renderer.texture.unbind(framebuffer.colorTextures[i].texture); + } + else + { + this.renderer.texture.unbind(framebuffer.colorTextures[i]); + } + } + + if (framebuffer.depthTexture) + { + this.renderer.texture.unbind(framebuffer.depthTexture); + } + + if (frame) + { + this.setViewport(frame.x, frame.y, frame.width, frame.height); + } + else + { + this.setViewport(0, 0, framebuffer.width, framebuffer.height); + } + } + else + { + gl.bindFramebuffer(gl.FRAMEBUFFER, null); + + if (frame) + { + this.setViewport(frame.x, frame.y, frame.width, frame.height); + } + else + { + this.setViewport(0, 0, this.renderer.width, this.renderer.height); + } + } + } + + setViewport(x, y, width, height) + { + const v = this.viewport; + + if (v.width !== width || v.height !== height || v.x !== x || v.y !== y) + { + v.x = x; + v.y = y; + v.width = width; + v.height = height; + + this.gl.viewport(x, y, width, height); + } + } + + get size() + { + if (this.current) + { + // TODO store temp + return { x: 0, y: 0, width: this.current.width, height: this.current.height }; + } + + return { x: 0, y: 0, width: this.renderer.width, height: this.renderer.height }; + } + + clear(r, g, b, a) + { + const gl = this.gl; + + // TODO clear color can be set only one right? + gl.clearColor(r, g, b, a); + gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); + } + + // private functions... + + initFramebuffer(framebuffer) + { + const gl = this.gl; + + // TODO - make this a class? + const fbo = { + framebuffer: gl.createFramebuffer(), + stencil: null, + dirtyId: 0, + dirtyFormat: 0, + dirtySize: 0, + }; + + framebuffer.glFrameBuffers[this.CONTEXT_UID] = fbo; + + return fbo; + } + + resizeFramebuffer(framebuffer) + { + const gl = this.gl; + + if (framebuffer.stencil || framebuffer.depth) + { + gl.bindRenderbuffer(gl.RENDERBUFFER, this.stencil); + gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); + } + } + + updateFramebuffer(framebuffer) + { + const gl = this.gl; + + const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID]; + + // bind the color texture + const colorTextures = framebuffer.colorTextures; + + let count = colorTextures.length; + + if (!this.drawBufferExtension) + { + count = Math.min(count, 1); + } + + const activeTextures = []; + + for (let i = 0; i < count; i++) + { + const texture = framebuffer.colorTextures[i]; + + if (texture.texturePart) + { + this.renderer.texture.bind(texture.texture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.COLOR_ATTACHMENT0 + i, + gl.TEXTURE_CUBE_MAP_NEGATIVE_X + texture.side, + texture.texture._glTextures[this.CONTEXT_UID].texture, + 0); + } + else + { + this.renderer.texture.bind(texture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.COLOR_ATTACHMENT0 + i, + gl.TEXTURE_2D, + texture._glTextures[this.CONTEXT_UID].texture, + 0); + } + + activeTextures.push(gl.COLOR_ATTACHMENT0 + i); + } + + if (this.drawBufferExtension && activeTextures.length > 1) + { + this.drawBufferExtension.drawBuffersWEBGL(activeTextures); + } + + if (framebuffer.depthTexture) + { + const depthTextureExt = this.renderer.context.extensions.depthTexture; + + if (depthTextureExt) + { + const depthTexture = framebuffer.depthTexture; + + this.renderer.texture.bind(depthTexture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.DEPTH_ATTACHMENT, + gl.TEXTURE_2D, + depthTexture._glTextures[this.CONTEXT_UID].texture, + 0); + } + } + + if (framebuffer.stencil || framebuffer.depth) + { + fbo.stencil = gl.createRenderbuffer(); + + gl.bindRenderbuffer(gl.RENDERBUFFER, fbo.stencil); + + // TODO.. this is depth AND stencil? + gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, fbo.stencil); + gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); + // fbo.enableStencil(); + } + } +} diff --git a/packages/core/src/geometry/GLBuffer.js b/packages/core/src/geometry/GLBuffer.js new file mode 100644 index 0000000..0477e8a --- /dev/null +++ b/packages/core/src/geometry/GLBuffer.js @@ -0,0 +1,9 @@ +export default class GLBuffer +{ + constructor(buffer) + { + this.buffer = buffer; + this.updateID = -1; + this.byteLength = -1; + } +} diff --git a/packages/core/src/geometry/GeometrySystem.js b/packages/core/src/geometry/GeometrySystem.js new file mode 100644 index 0000000..959f827 --- /dev/null +++ b/packages/core/src/geometry/GeometrySystem.js @@ -0,0 +1,388 @@ +import System from '../System'; +import GLBuffer from './GLBuffer'; + +const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class GeometrySystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this._activeGeometry = null; + this._activeVao = null; + + this.hasVao = true; + this.hasInstance = true; + } + + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + contextChange() + { + const gl = this.gl = this.renderer.gl; + + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + + // webgl2 + if (!gl.createVertexArray) + { + // webgl 1! + let nativeVaoExtension = this.renderer.context.extensions.vertexArrayObject; + + if (this.renderer.legacy) + { + nativeVaoExtension = null; + } + + if (nativeVaoExtension) + { + gl.createVertexArray = () => + nativeVaoExtension.createVertexArrayOES(); + + gl.bindVertexArray = (vao) => + nativeVaoExtension.bindVertexArrayOES(vao); + + gl.deleteVertexArray = (vao) => + nativeVaoExtension.deleteVertexArrayOES(vao); + } + else + { + this.hasVao = false; + gl.createVertexArray = () => + { + // empty + }; + + gl.bindVertexArray = () => + { + // empty + }; + + gl.deleteVertexArray = () => + { + // empty + }; + } + } + + if (!gl.vertexAttribDivisor) + { + const instanceExt = gl.getExtension('ANGLE_instanced_arrays'); + + if (instanceExt) + { + gl.vertexAttribDivisor = (a, b) => + instanceExt.vertexAttribDivisorANGLE(a, b); + + gl.drawElementsInstanced = (a, b, c, d, e) => + instanceExt.drawElementsInstancedANGLE(a, b, c, d, e); + + gl.drawArraysInstanced = (a, b, c, d) => + instanceExt.drawArraysInstancedANGLE(a, b, c, d); + } + else + { + this.hasInstance = false; + } + } + } + + /** + * Binds geometry so that is can be drawn. Creating a Vao if required + * @private + * @param {PIXI.Geometry} geometry instance of geometry to bind + */ + bind(geometry, shader) + { + shader = shader || this.renderer.shader.shader; + + const gl = this.gl; + + // not sure the best way to address this.. + // currently different shaders require different VAOs for the same geometry + // Still mulling over the best way to solve this one.. + // will likely need to modify the shader attribute locations at run time! + let vaos = geometry.glVertexArrayObjects[this.CONTEXT_UID]; + + if (!vaos) + { + geometry.glVertexArrayObjects[this.CONTEXT_UID] = vaos = {}; + } + + const vao = vaos[shader.program.id] || this.initGeometryVao(geometry, shader.program); + + this._activeGeometry = geometry; + + if (this._activeVao !== vao) + { + this._activeVao = vao; + + if (this.hasVao) + { + gl.bindVertexArray(vao); + } + else + { + this.activateVao(geometry, shader.program); + } + } + + // TODO - optimise later! + // don't need to loop through if nothing changed! + // maybe look to add an 'autoupdate' to geometry? + this.updateBuffers(); + } + + reset() + { + this.unbind(); + } + + updateBuffers() + { + const geometry = this._activeGeometry; + const gl = this.gl; + + for (let i = 0; i < geometry.buffers.length; i++) + { + const buffer = geometry.buffers[i]; + + const glBuffer = buffer._glBuffers[this.CONTEXT_UID]; + + if (buffer._updateID !== glBuffer.updateID) + { + glBuffer.updateID = buffer._updateID; + + // TODO can cache this on buffer! maybe added a getter / setter? + const type = buffer.index ? gl.ELEMENT_ARRAY_BUFFER : gl.ARRAY_BUFFER; + const drawType = buffer.static ? gl.STATIC_DRAW : gl.DYNAMIC_DRAW; + + gl.bindBuffer(type, glBuffer.buffer); + + if (glBuffer.byteLength >= buffer.data.byteLength) + { + // offset is always zero for now! + gl.bufferSubData(type, 0, buffer.data); + } + else + { + gl.bufferData(type, buffer.data, drawType); + } + } + } + } + + checkCompatability(geometry, program) + { + // geometry must have at least all the attributes that the shader requires. + const geometryAttributes = geometry.attributes; + const shaderAttributes = program.attributeData; + + for (const j in shaderAttributes) + { + if (!geometryAttributes[j]) + { + throw new Error(`shader and geometry incompatible, geometry missing the "${j}" attribute`); + } + } + } + + /** + * Creates a Vao with the same structure as the geometry and stores it on the geometry. + * @private + * @param {PIXI.Geometry} geometry instance of geometry to to generate Vao for + */ + initGeometryVao(geometry, program) + { + this.checkCompatability(geometry, program); + + const gl = this.gl; + const CONTEXT_UID = this.CONTEXT_UID; + const buffers = geometry.buffers; + const attributes = geometry.attributes; + + const tempStride = {}; + const tempStart = {}; + + for (const j in buffers) + { + tempStride[j] = 0; + tempStart[j] = 0; + } + + for (const j in attributes) + { + if (!attributes[j].size && program.attributeData[j]) + { + attributes[j].size = program.attributeData[j].size; + } + + tempStride[attributes[j].buffer] += attributes[j].size * byteSizeMap[attributes[j].type]; + } + + for (const j in attributes) + { + const attribute = attributes[j]; + const attribSize = attribute.size; + + if (attribute.stride === undefined) + { + if (tempStride[attribute.buffer] === attribSize * byteSizeMap[attribute.type]) + { + attribute.stride = 0; + } + else + { + attribute.stride = tempStride[attribute.buffer]; + } + } + + if (attribute.start === undefined) + { + attribute.start = tempStart[attribute.buffer]; + + tempStart[attribute.buffer] += attribSize * byteSizeMap[attribute.type]; + } + } + + // first update - and create the buffers! + // only create a gl buffer if it actually gets + for (let i = 0; i < buffers.length; i++) + { + const buffer = buffers[i]; + + if (!buffer._glBuffers[CONTEXT_UID]) + { + buffer._glBuffers[CONTEXT_UID] = new GLBuffer(gl.createBuffer()); + } + } + + // TODO - maybe make this a data object? + // lets wait to see if we need to first! + const vao = gl.createVertexArray(); + + gl.bindVertexArray(vao); + + this.activateVao(geometry, program); + + gl.bindVertexArray(null); + + // add it to the cache! + geometry.glVertexArrayObjects[this.CONTEXT_UID][program.id] = vao; + + return vao; + } + + activateVao(geometry, program) + { + const gl = this.gl; + const CONTEXT_UID = this.CONTEXT_UID; + const buffers = geometry.buffers; + const attributes = geometry.attributes; + + if (geometry.indexBuffer) + { + // first update the index buffer if we have one.. + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, geometry.indexBuffer._glBuffers[CONTEXT_UID].buffer); + } + + let lastBuffer = null; + + // add a new one! + for (const j in attributes) + { + const attribute = attributes[j]; + const buffer = buffers[attribute.buffer]; + const glBuffer = buffer._glBuffers[CONTEXT_UID]; + + if (program.attributeData[j]) + { + if (lastBuffer !== glBuffer) + { + gl.bindBuffer(gl.ARRAY_BUFFER, glBuffer.buffer); + + lastBuffer = glBuffer; + } + + const location = program.attributeData[j].location; + + // TODO introduce state again + // we can optimise this for older devices that have no VAOs + gl.enableVertexAttribArray(location); + + gl.vertexAttribPointer(location, + attribute.size, + attribute.type || gl.FLOAT, + attribute.normalized, + attribute.stride, + attribute.start); + + if (attribute.instance) + { + // TODO calculate instance count based of this... + if (this.hasInstance) + { + gl.vertexAttribDivisor(location, 1); + } + else + { + throw new Error('geometry error, GPU Instancing is not supported on this device'); + } + } + } + } + } + + draw(type, size, start, instanceCount) + { + const gl = this.gl; + const geometry = this._activeGeometry; + + // TODO.. this should not change so maybe cache the function? + + if (geometry.indexBuffer) + { + if (geometry.instanced) + { + /* eslint-disable max-len */ + gl.drawElementsInstanced(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2, instanceCount || 1); + /* eslint-enable max-len */ + } + else + { + gl.drawElements(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2); + } + } + else + if (geometry.instanced) + { + // TODO need a better way to calculate size.. + gl.drawArraysInstanced(type, start, size || geometry.getSize(), instanceCount || 1); + } + else + { + gl.drawArrays(type, start, size || geometry.getSize()); + } + + return this; + } + + unbind() + { + this.gl.bindVertexArray(null); + this._activeVao = null; + this._activeGeometry = null; + } +} diff --git a/packages/core/src/geometry/utils/setVertexAttribArrays.js b/packages/core/src/geometry/utils/setVertexAttribArrays.js new file mode 100644 index 0000000..23b6959 --- /dev/null +++ b/packages/core/src/geometry/utils/setVertexAttribArrays.js @@ -0,0 +1,54 @@ +// var GL_MAP = {}; + +/** + * @param gl {WebGLRenderingContext} The current WebGL context + * @param attribs {*} + * @param state {*} + */ +export default function setVertexAttribArrays(gl, attribs, state) +{ + let i; + + if (state) + { + const tempAttribState = state.tempAttribState; + const attribState = state.attribState; + + for (i = 0; i < tempAttribState.length; i++) + { + tempAttribState[i] = false; + } + + // set the new attribs + for (i = 0; i < attribs.length; i++) + { + tempAttribState[attribs[i].attribute.location] = true; + } + + for (i = 0; i < attribState.length; i++) + { + if (attribState[i] !== tempAttribState[i]) + { + attribState[i] = tempAttribState[i]; + + if (state.attribState[i]) + { + gl.enableVertexAttribArray(i); + } + else + { + gl.disableVertexAttribArray(i); + } + } + } + } + else + { + for (i = 0; i < attribs.length; i++) + { + const attrib = attribs[i]; + + gl.enableVertexAttribArray(attrib.attribute.location); + } + } +} diff --git a/packages/core/src/index.js b/packages/core/src/index.js index 58c456a..fd1f547 100644 --- a/packages/core/src/index.js +++ b/packages/core/src/index.js @@ -1,30 +1,30 @@ import * as resources from './textures/resources'; -import * as systems from './renderers/systems'; +import * as systems from './systems'; -export { resources }; export { systems }; +export { resources }; -export { default as Renderer } from './renderers/Renderer'; -export { default as AbstractRenderer } from './renderers/AbstractRenderer'; -export { default as FrameBuffer } from './textures/FrameBuffer'; +export { default as System } from './System'; +export { default as Renderer } from './Renderer'; +export { default as AbstractRenderer } from './AbstractRenderer'; +export { default as FrameBuffer } from './framebuffer/FrameBuffer'; export { default as CubeTexture } from './textures/CubeTexture'; export { default as BaseTexture } from './textures/BaseTexture'; export { default as Texture } from './textures/Texture'; export { default as TextureMatrix } from './textures/TextureMatrix'; -export { default as RenderTexture } from './textures/RenderTexture'; -export { default as BaseRenderTexture } from './textures/BaseRenderTexture'; +export { default as RenderTexture } from './renderTexture/RenderTexture'; +export { default as BaseRenderTexture } from './renderTexture/BaseRenderTexture'; export { default as TextureUvs } from './textures/TextureUvs'; -export { default as State } from './renderers/State'; -export { default as ObjectRenderer } from './renderers/utils/ObjectRenderer'; -export { default as Quad } from './renderers/utils/Quad'; -export { default as QuadUv } from './renderers/utils/QuadUv'; -export { default as checkMaxIfStatmentsInShader } from './renderers/utils/checkMaxIfStatmentsInShader'; +export { default as State } from './state/State'; +export { default as ObjectRenderer } from './batch/ObjectRenderer'; +export { default as Quad } from './utils/Quad'; +export { default as QuadUv } from './utils/QuadUv'; +export { default as checkMaxIfStatmentsInShader } from './shader/utils/checkMaxIfStatmentsInShader'; export { default as Shader } from './shader/Shader'; export { default as Program } from './shader/Program'; export { default as UniformGroup } from './shader/UniformGroup'; -export { default as SpriteMaskFilter } from './renderers/filters/spriteMask/SpriteMaskFilter'; -export { default as Filter } from './renderers/filters/Filter'; +export { default as SpriteMaskFilter } from './filters/spriteMask/SpriteMaskFilter'; +export { default as Filter } from './filters/Filter'; export { default as Attribute } from './geometry/Attribute'; export { default as Buffer } from './geometry/Buffer'; export { default as Geometry } from './geometry/Geometry'; - diff --git a/packages/core/src/mask/MaskSystem.js b/packages/core/src/mask/MaskSystem.js new file mode 100644 index 0000000..99174c2 --- /dev/null +++ b/packages/core/src/mask/MaskSystem.js @@ -0,0 +1,199 @@ +import System from '../System'; +import AlphaMaskFilter from '../filters/spriteMask/SpriteMaskFilter'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class MaskSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + // TODO - we don't need both! + this.scissor = false; + this.scissorData = null; + this.scissorRenderTarget = null; + + this.enableScissor = false; + + this.alphaMaskPool = []; + this.alphaMaskIndex = 0; + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.DisplayObject} target - Display Object to push the mask to + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + push(target, maskData) + { + // TODO the root check means scissor rect will not + // be used on render textures more info here: + // https://github.com/pixijs/pixi.js/pull/3545 + + if (maskData.texture) + { + this.pushSpriteMask(target, maskData); + } + else if (this.enableScissor + && !this.scissor + && this.renderer._activeRenderTarget.root + && !this.renderer.stencil.stencilMaskStack.length + && maskData.isFastRect()) + { + const matrix = maskData.worldTransform; + + let rot = Math.atan2(matrix.b, matrix.a); + + // use the nearest degree! + rot = Math.round(rot * (180 / Math.PI)); + + if (rot % 90) + { + this.pushStencilMask(maskData); + } + else + { + this.pushScissorMask(target, maskData); + } + } + else + { + this.pushStencilMask(maskData); + } + } + + /** + * Removes the last mask from the mask stack and doesn't return it. + * + * @param {PIXI.DisplayObject} target - Display Object to pop the mask from + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + pop(target, maskData) + { + if (maskData.texture) + { + this.popSpriteMask(target, maskData); + } + else if (this.enableScissor && !this.renderer.stencil.stencilMaskStack.length) + { + this.popScissorMask(target, maskData); + } + else + { + this.popStencilMask(target, maskData); + } + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.RenderTarget} target - Display Object to push the sprite mask to + * @param {PIXI.Sprite} maskData - Sprite to be used as the mask + */ + pushSpriteMask(target, maskData) + { + let alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; + + if (!alphaMaskFilter) + { + alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; + } + + alphaMaskFilter[0].resolution = this.renderer.resolution; + alphaMaskFilter[0].maskSprite = maskData; + + // TODO - may cause issues! + target.filterArea = maskData.getBounds(true); + + this.renderer.filterSystem.pushFilter(target, alphaMaskFilter); + + this.alphaMaskIndex++; + } + + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popSpriteMask() + { + this.renderer.filterSystem.popFilter(); + this.alphaMaskIndex--; + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + pushStencilMask(maskData) + { + this.renderer.batch.flush(); + this.renderer.stencil.pushStencil(maskData); + } + + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popStencilMask() + { + // this.renderer.currentRenderer.stop(); + this.renderer.stencil.popStencil(); + } + + /** + * + * @param {PIXI.DisplayObject} target - Display Object to push the mask to + * @param {PIXI.Graphics} maskData - The masking data. + */ + pushScissorMask(target, maskData) + { + maskData.renderable = true; + + const renderTarget = this.renderer._activeRenderTarget; + + const bounds = maskData.getBounds(); + + bounds.fit(renderTarget.size); + maskData.renderable = false; + + this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); + + const resolution = this.renderer.resolution; + + this.renderer.gl.scissor( + bounds.x * resolution, + (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, + bounds.width * resolution, + bounds.height * resolution + ); + + this.scissorRenderTarget = renderTarget; + this.scissorData = maskData; + this.scissor = true; + } + + /** + * + * + */ + popScissorMask() + { + this.scissorRenderTarget = null; + this.scissorData = null; + this.scissor = false; + + // must be scissor! + const gl = this.renderer.gl; + + gl.disable(gl.SCISSOR_TEST); + } +} diff --git a/packages/core/src/mask/StencilSystem.js b/packages/core/src/mask/StencilSystem.js new file mode 100644 index 0000000..62796a5 --- /dev/null +++ b/packages/core/src/mask/StencilSystem.js @@ -0,0 +1,133 @@ +import System from '../System'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class StencilSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + this.stencilMaskStack = []; + } + + /** + * Changes the mask stack that is used by this System. + * + * @param {PIXI.Graphics[]} stencilMaskStack - The mask stack + */ + setMaskStack(stencilMaskStack) + { + const gl = this.renderer.gl; + + if (stencilMaskStack.length !== this.stencilMaskStack.length) + { + if (stencilMaskStack.length === 0) + { + gl.disable(gl.STENCIL_TEST); + } + else + { + gl.enable(gl.STENCIL_TEST); + } + } + + this.stencilMaskStack = stencilMaskStack; + } + + /** + * Applies the Mask and adds it to the current stencil stack. @alvin + * + * @param {PIXI.Graphics} graphics - The mask + */ + pushStencil(graphics) + { + this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); + + // this.renderer._activeRenderTarget.attachStencilBuffer(); + + const gl = this.renderer.gl; + const prevMaskCount = this.stencilMaskStack.length; + + if (prevMaskCount === 0) + { + gl.enable(gl.STENCIL_TEST); + } + + this.stencilMaskStack.push(graphics); + + // Increment the refference stencil value where the new mask overlaps with the old ones. + gl.colorMask(false, false, false, false); + gl.stencilFunc(gl.EQUAL, prevMaskCount, this._getBitwiseMask()); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.INCR); + this.renderer.plugins.graphics.render(graphics); + + this._useCurrent(); + } + + /** + * Removes the last mask from the stencil stack. @alvin + */ + popStencil() + { + this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); + + const gl = this.renderer.gl; + const graphics = this.stencilMaskStack.pop(); + + if (this.stencilMaskStack.length === 0) + { + // the stack is empty! + gl.disable(gl.STENCIL_TEST); + gl.clear(gl.STENCIL_BUFFER_BIT); + gl.clearStencil(0); + } + else + { + // Decrement the refference stencil value where the popped mask overlaps with the other ones + gl.colorMask(false, false, false, false); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.DECR); + this.renderer.plugins.graphics.render(graphics); + + this._useCurrent(); + } + } + + /** + * Setup renderer to use the current stencil data. + */ + _useCurrent() + { + const gl = this.renderer.gl; + + gl.colorMask(true, true, true, true); + gl.stencilFunc(gl.EQUAL, this.stencilMaskStack.length, this._getBitwiseMask()); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP); + } + + /** + * Fill 1s equal to the number of acitve stencil masks. + * + * @return {number} The bitwise mask. + */ + _getBitwiseMask() + { + return (1 << this.stencilMaskStack.length) - 1; + } + + /** + * Destroys the mask stack. + * + */ + destroy() + { + super.destroy(this); + + this.stencilMaskStack = null; + } +} diff --git a/packages/core/src/projection/ProjectionSystem.js b/packages/core/src/projection/ProjectionSystem.js new file mode 100644 index 0000000..6e63b73 --- /dev/null +++ b/packages/core/src/projection/ProjectionSystem.js @@ -0,0 +1,73 @@ +import System from '../System'; +import { Matrix } from '@pixi/math'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class ProjectionSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.projectionMatrix = new Matrix(); + } + + update(destinationFrame, sourceFrame, resolution, root) + { + this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; + this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; + + this.calculateProjection(this.destinationFrame, this.sourceFrame, resolution, root); + + this.renderer.globalUniforms.uniforms.projectionMatrix = this.projectionMatrix; + this.renderer.globalUniforms.update(); + } + + /** + * Updates the projection matrix based on a projection frame (which is a rectangle) + * + * @param {Rectangle} destinationFrame - The destination frame. + * @param {Rectangle} sourceFrame - The source frame. + */ + calculateProjection(destinationFrame, sourceFrame, resolution, root) + { + const pm = this.projectionMatrix; + + // I don't think we will need this line.. + // pm.identity(); + + if (!root) + { + pm.a = (1 / destinationFrame.width * 2) * resolution; + pm.d = (1 / destinationFrame.height * 2) * resolution; + + pm.tx = -1 - (sourceFrame.x * pm.a); + pm.ty = -1 - (sourceFrame.y * pm.d); + } + else + { + pm.a = (1 / destinationFrame.width * 2) * resolution; + pm.d = (-1 / destinationFrame.height * 2) * resolution; + + pm.tx = -1 - (sourceFrame.x * pm.a); + pm.ty = 1 - (sourceFrame.y * pm.d); + } + } + + /** + * Sets the transform of the active render target to the given matrix + * + * @param {PIXI.Matrix} matrix - The transformation matrix + */ + setTransform()// matrix) + { + // this._activeRenderTarget.transform = matrix; + } +} diff --git a/packages/core/src/renderTexture/BaseRenderTexture.js b/packages/core/src/renderTexture/BaseRenderTexture.js new file mode 100644 index 0000000..88eb83b --- /dev/null +++ b/packages/core/src/renderTexture/BaseRenderTexture.js @@ -0,0 +1,137 @@ +import BaseTexture from '../textures/BaseTexture'; +import FrameBuffer from '../framebuffer/FrameBuffer'; + +/** + * A BaseRenderTexture is a special texture that allows any PixiJS display object to be rendered to it. + * + * __Hint__: All DisplayObjects (i.e. Sprites) that render to a BaseRenderTexture should be preloaded + * otherwise black rectangles will be drawn instead. + * + * A BaseRenderTexture takes a snapshot of any Display Object given to its render method. The position + * and rotation of the given Display Objects is ignored. For example: + * + * ```js + * let renderer = PIXI.autoDetectRenderer(1024, 1024, { view: canvas, ratio: 1 }); + * let baseRenderTexture = new PIXI.BaseRenderTexture(renderer, 800, 600); + * let sprite = PIXI.Sprite.fromImage("spinObj_01.png"); + * + * sprite.position.x = 800/2; + * sprite.position.y = 600/2; + * sprite.anchor.x = 0.5; + * sprite.anchor.y = 0.5; + * + * baseRenderTexture.render(sprite); + * ``` + * + * The Sprite in this case will be rendered using its local transform. To render this sprite at 0,0 + * you can clear the transform + * + * ```js + * + * sprite.setTransform() + * + * let baseRenderTexture = new PIXI.BaseRenderTexture(100, 100); + * let renderTexture = new PIXI.RenderTexture(baseRenderTexture); + * + * renderer.render(sprite, renderTexture); // Renders to center of RenderTexture + * ``` + * + * @class + * @extends PIXI.BaseTexture + * @memberof PIXI + */ +export default class BaseRenderTexture extends BaseTexture +{ + /** + * @param {object} [options] + * @param {number} [options.width=100] - The width of the base render texture + * @param {number} [options.height=100] - The height of the base render texture + * @param {PIXI.SCALE_MODES} [options.scaleMode] - See {@link PIXI.SCALE_MODES} for possible values + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the texture being generated + */ + constructor(options) + { + if (typeof options === 'number') + { + /* eslint-disable prefer-rest-params */ + // Backward compatibility of signature + const width = arguments[0]; + const height = arguments[1]; + const scaleMode = arguments[2]; + const resolution = arguments[3]; + + options = { width, height, scaleMode, resolution }; + /* eslint-enable prefer-rest-params */ + } + + super(null, options); + + const { width, height } = options || {}; + + // Set defaults + this.mipmap = false; + this.width = Math.ceil(width) || 100; + this.height = Math.ceil(height) || 100; + this.valid = true; + + /** + * A map of renderer IDs to webgl renderTargets + * + * @private + * @member {object} + */ + // this._glRenderTargets = {}; + + /** + * A reference to the canvas render target (we only need one as this can be shared across renderers) + * + * @private + * @member {object} + */ + this._canvasRenderTarget = null; + + this.clearColor = [0, 0, 0, 0]; + + this.frameBuffer = new FrameBuffer(width * this.resolution, height * this.resolution) + .addColorTexture(0, this); + + // TODO - could this be added the systems? + + /** + * The data structure for the stencil masks + * + * @member {PIXI.Graphics[]} + */ + this.stencilMaskStack = []; + + /** + * The data structure for the filters + * + * @member {PIXI.Graphics[]} + */ + this.filterStack = [{}]; + } + + /** + * Resizes the BaseRenderTexture. + * + * @param {number} width - The width to resize to. + * @param {number} height - The height to resize to. + */ + resize(width, height) + { + width = Math.ceil(width); + height = Math.ceil(height); + this.frameBuffer.resize(width * this.resolution, height * this.resolution); + } + + /** + * Destroys this texture + * + */ + destroy() + { + super.destroy(true); + this.renderer = null; + } +} diff --git a/packages/core/src/renderTexture/RenderTexture.js b/packages/core/src/renderTexture/RenderTexture.js new file mode 100644 index 0000000..f1ecf1f --- /dev/null +++ b/packages/core/src/renderTexture/RenderTexture.js @@ -0,0 +1,153 @@ +import BaseRenderTexture from './BaseRenderTexture'; +import Texture from '../textures/Texture'; + +/** + * A RenderTexture is a special texture that allows any PixiJS display object to be rendered to it. + * + * __Hint__: All DisplayObjects (i.e. Sprites) that render to a RenderTexture should be preloaded + * otherwise black rectangles will be drawn instead. + * + * A RenderTexture takes a snapshot of any Display Object given to its render method. For example: + * + * ```js + * let renderer = PIXI.autoDetectRenderer(1024, 1024, { view: canvas, ratio: 1 }); + * let renderTexture = PIXI.RenderTexture.create(800, 600); + * let sprite = PIXI.Sprite.fromImage("spinObj_01.png"); + * + * sprite.position.x = 800/2; + * sprite.position.y = 600/2; + * sprite.anchor.x = 0.5; + * sprite.anchor.y = 0.5; + * + * renderer.render(sprite, renderTexture); + * ``` + * + * The Sprite in this case will be rendered using its local transform. To render this sprite at 0,0 + * you can clear the transform + * + * ```js + * + * sprite.setTransform() + * + * let renderTexture = new PIXI.RenderTexture.create(100, 100); + * + * renderer.render(sprite, renderTexture); // Renders to center of RenderTexture + * ``` + * + * @class + * @extends PIXI.Texture + * @memberof PIXI + */ +export default class RenderTexture extends Texture +{ + /** + * @param {PIXI.BaseRenderTexture} baseRenderTexture - The renderer used for this RenderTexture + * @param {PIXI.Rectangle} [frame] - The rectangle frame of the texture to show + */ + constructor(baseRenderTexture, frame) + { + // support for legacy.. + let _legacyRenderer = null; + + if (!(baseRenderTexture instanceof BaseRenderTexture)) + { + /* eslint-disable prefer-rest-params, no-console */ + const width = arguments[1]; + const height = arguments[2]; + const scaleMode = arguments[3]; + const resolution = arguments[4]; + + // we have an old render texture.. + console.warn(`Please use RenderTexture.create(${width}, ${height}) instead of the ctor directly.`); + _legacyRenderer = arguments[0]; + /* eslint-enable prefer-rest-params, no-console */ + + frame = null; + baseRenderTexture = new BaseRenderTexture({ + width, + height, + scaleMode, + resolution, + }); + } + + /** + * The base texture object that this texture uses + * + * @member {BaseTexture} + */ + super(baseRenderTexture, frame); + + this.legacyRenderer = _legacyRenderer; + + /** + * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. + * + * @member {boolean} + */ + this.valid = true; + + /** + * FilterSystem temporary storage + * @private + * @member {PIXI.Rectangle} + */ + this.filterFrame = null; + + this._updateUvs(); + } + + /** + * Resizes the RenderTexture. + * + * @param {number} width - The width to resize to. + * @param {number} height - The height to resize to. + * @param {boolean} [resizeBaseTexture=true] - Should the baseTexture.width and height values be resized as well? + */ + resize(width, height, resizeBaseTexture = true) + { + width = Math.ceil(width); + height = Math.ceil(height); + + // TODO - could be not required.. + this.valid = (width > 0 && height > 0); + + this._frame.width = this.orig.width = width; + this._frame.height = this.orig.height = height; + + if (resizeBaseTexture) + { + this.baseTexture.resize(width, height); + } + + this._updateUvs(); + } + + /** + * A short hand way of creating a render texture. + * + * @param {object} [options] - Options + * @param {number} [options.width=100] - The width of the render texture + * @param {number} [options.height=100] - The height of the render texture + * @param {number} [options.scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the texture being generated + * @return {PIXI.RenderTexture} The new render texture + */ + static create(options) + { + // fallback, old-style: create(width, height, scaleMode, resolution) + if (typeof options === 'number') + { + /* eslint-disable prefer-rest-params */ + options = { + width: options, + height: arguments[1], + scaleMode: arguments[2], + resolution: arguments[3], + }; + /* eslint-enable prefer-rest-params */ + } + + return new RenderTexture(new BaseRenderTexture(options)); + } +} diff --git a/packages/core/src/renderTexture/RenderTextureSystem.js b/packages/core/src/renderTexture/RenderTextureSystem.js new file mode 100644 index 0000000..8d0ca61 --- /dev/null +++ b/packages/core/src/renderTexture/RenderTextureSystem.js @@ -0,0 +1,115 @@ +import System from '../System'; +import { Rectangle } from '@pixi/math'; + +const tempRect = new Rectangle(); + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class RenderTextureSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.clearColor = renderer._backgroundColorRgba; + + // TODO moe this property somewhere else! + this.defaultMaskStack = []; + this.defaultFilterStack = [{}]; + + // empty render texture? + this.renderTexture = null; + + this.destinationFrame = new Rectangle(); + } + + bind(renderTexture, sourceFrame, destinationFrame) + { + // TODO - do we want this?? + if (this.renderTexture === renderTexture) return; + this.renderTexture = renderTexture; + + const renderer = this.renderer; + + if (renderTexture) + { + const baseTexture = renderTexture.baseTexture; + + if (!destinationFrame) + { + tempRect.width = baseTexture.realWidth; + tempRect.height = baseTexture.realHeight; + + destinationFrame = tempRect; + } + + if (!sourceFrame) + { + sourceFrame = destinationFrame; + } + + this.renderer.framebuffer.bind(baseTexture.frameBuffer, destinationFrame); + + this.renderer.projection.update(destinationFrame, sourceFrame, baseTexture.resolution, false); + this.renderer.stencil.setMaskStack(baseTexture.stencilMaskStack); + } + else + { + // TODO these validation checks happen deeper down.. + // thing they can be avoided.. + if (!destinationFrame) + { + tempRect.width = renderer.width; + tempRect.height = renderer.height; + + destinationFrame = tempRect; + } + + if (!sourceFrame) + { + sourceFrame = destinationFrame; + } + + renderer.framebuffer.bind(null, destinationFrame); + + // TODO store this.. + this.renderer.projection.update(destinationFrame, sourceFrame, this.renderer.resolution, true); + this.renderer.stencil.setMaskStack(this.defaultMaskStack); + } + + this.destinationFrame.copyFrom(destinationFrame); + } + + /** + * Erases the render texture and fills the drawing area with a colour + * + * @param {number} [clearColor] - The colour + * @return {PIXI.Renderer} Returns itself. + */ + clear(clearColor) + { + if (this.renderTexture) + { + clearColor = clearColor || this.renderTexture.baseTexture.clearColor; + } + else + { + clearColor = clearColor || this.clearColor; + } + + this.renderer.framebuffer.clear(clearColor[0], clearColor[1], clearColor[2], clearColor[3]); + } + + resize()// screenWidth, screenHeight) + { + // resize the root only! + this.bind(null); + } +} diff --git a/packages/core/src/renderers/AbstractRenderer.js b/packages/core/src/renderers/AbstractRenderer.js deleted file mode 100644 index 928670f..0000000 --- a/packages/core/src/renderers/AbstractRenderer.js +++ /dev/null @@ -1,353 +0,0 @@ -import { hex2string, hex2rgb } from '@pixi/utils'; -import { Matrix, Rectangle } from '@pixi/math'; -import { RENDERER_TYPE } from '@pixi/constants'; -import { settings } from '@pixi/settings'; -import { Container } from '@pixi/display'; -import RenderTexture from '../textures/RenderTexture'; -import EventEmitter from 'eventemitter3'; - -const tempMatrix = new Matrix(); - -/** - * 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 EventEmitter - * @memberof PIXI - */ -export default class AbstractRenderer extends EventEmitter -{ - // eslint-disable-next-line valid-jsdoc - /** - * @param {string} system - The name of the system this renderer is for. - * @param {object} [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.transparent=false] - If the render view is transparent, default false - * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false - * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) - * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. The - * resolution of the renderer retina would be 2. - * @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 {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when rendering, - * stopping pixel interpolation. - */ - constructor(system, options, arg2, arg3) - { - super(); - - // Support for constructor(system, screenWidth, screenHeight, options) - if (typeof options === 'number') - { - options = Object.assign({ - width: options, - height: arg2 || settings.RENDER_OPTIONS.height, - }, arg3); - } - - // 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 = RENDERER_TYPE.UNKNOWN; - - /** - * Measurements of the screen. (0, 0, screenWidth, screenHeight) - * - * Its safe to use as filterArea or hitArea for 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 1 - */ - this.resolution = options.resolution || settings.RESOLUTION; - - /** - * Whether the render view is transparent - * - * @member {boolean} - */ - this.transparent = options.transparent; - - /** - * Whether css dimensions of canvas view should be resized to screen dimensions automatically - * - * @member {boolean} - */ - this.autoResize = options.autoResize || false; - - /** - * Tracks the blend modes useful for this renderer. - * - * @member {object} - */ - this.blendModes = null; - - /** - * 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; - - /** - * If true PixiJS will Math.floor() x/y values when rendering, stopping pixel interpolation. - * Handy for crisp pixel art and speed on legacy devices. - * - * @member {boolean} - */ - this.roundPixels = options.roundPixels; - - /** - * The background color as a number. - * - * @member {number} - * @private - */ - this._backgroundColor = 0x000000; - - /** - * The background color as an [R, G, B] array. - * - * @member {number[]} - * @private - */ - this._backgroundColorRgba = [0, 0, 0, 0]; - - /** - * The background color as a string. - * - * @member {string} - * @private - */ - this._backgroundColorString = '#000000'; - - this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter - - /** - * This temporary display object used as the parent of the currently being rendered item - * - * @member {PIXI.DisplayObject} - * @private - */ - this._tempDisplayObjectParent = new Container(); - - /** - * The last root object that the renderer tried to render. - * - * @member {PIXI.DisplayObject} - * @private - */ - this._lastObjectRendered = this._tempDisplayObjectParent; - - /** - * Collection of plugins. - * @readonly - * @member {object} - */ - this.plugins = {}; - } - - /** - * Initialize the plugins. - * - * @protected - * @param {object} staticMap - The dictionary of staticly saved plugins. - */ - initPlugins(staticMap) - { - 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() - { - return this.view.width; - } - - /** - * Same as view.height, actual number of pixels in the canvas by vertical - * - * @member {number} - * @readonly - * @default 600 - */ - get height() - { - return this.view.height; - } - - /** - * Resizes the screen and canvas to the specified width and height - * Canvas dimensions are multiplied by resolution - * - * @param {number} screenWidth - the new width of the screen - * @param {number} screenHeight - the new height of the screen - */ - resize(screenWidth, screenHeight) - { - this.screen.width = screenWidth; - this.screen.height = screenHeight; - - this.view.width = screenWidth * this.resolution; - this.view.height = screenHeight * this.resolution; - - if (this.autoResize) - { - this.view.style.width = `${screenWidth}px`; - this.view.style.height = `${screenHeight}px`; - } - } - - /** - * 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 {PIXI.DisplayObject} displayObject - The displayObject the object will be generated from - * @param {number} scaleMode - Should be one of the scaleMode consts - * @param {number} resolution - The resolution / device pixel ratio of the texture being generated - * @param {PIXI.Rectangle} [region] - The region of the displayObject, that shall be rendered, - * if no region is specified, defaults to the local bounds of the displayObject. - * @return {PIXI.Texture} a texture of the graphics object - */ - generateTexture(displayObject, scaleMode, resolution, region) - { - region = region || displayObject.getLocalBounds(); - - // 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(region.width | 0, region.height | 0, scaleMode, resolution); - - tempMatrix.tx = -region.x; - tempMatrix.ty = -region.y; - - this.render(displayObject, renderTexture, false, tempMatrix, true); - - return renderTexture; - } - - /** - * Removes everything from the renderer and optionally removes the Canvas DOM element. - * - * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. - */ - destroy(removeView) - { - 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); - } - - this.plugins = null; - - this.type = RENDERER_TYPE.UNKNOWN; - - this.view = null; - - this.screen = null; - - this.resolution = 0; - - this.transparent = false; - - this.autoResize = false; - - this.blendModes = null; - - this.options = null; - - this.preserveDrawingBuffer = false; - this.clearBeforeRender = false; - - this.roundPixels = false; - - this._backgroundColor = 0; - this._backgroundColorRgba = null; - this._backgroundColorString = null; - - this._tempDisplayObjectParent = null; - this._lastObjectRendered = null; - } - - /** - * The background color to fill if not transparent - * - * @member {number} - */ - get backgroundColor() - { - return this._backgroundColor; - } - - set backgroundColor(value) // eslint-disable-line require-jsdoc - { - this._backgroundColor = value; - this._backgroundColorString = hex2string(value); - hex2rgb(value, this._backgroundColorRgba); - } -} diff --git a/packages/core/src/renderers/Renderer.js b/packages/core/src/renderers/Renderer.js deleted file mode 100644 index a2020b1..0000000 --- a/packages/core/src/renderers/Renderer.js +++ /dev/null @@ -1,329 +0,0 @@ -import AbstractRenderer from './AbstractRenderer'; -import { sayHello } from '@pixi/utils'; -import MaskSystem from './systems/MaskSystem'; -import StencilSystem from './systems/StencilSystem'; -import FilterSystem from './systems/filter/FilterSystem'; -import FramebufferSystem from './systems/FramebufferSystem'; -import RenderTextureSystem from './systems/RenderTextureSystem'; -import TextureSystem from './systems/textures/TextureSystem'; -import ProjectionSystem from './systems/ProjectionSystem'; -import StateSystem from './systems/StateSystem'; -import GeometrySystem from './systems/geometry/GeometrySystem'; -import ShaderSystem from './systems/shader/ShaderSystem'; -import ContextSystem from './systems/ContextSystem'; -import BatchSystem from './systems/BatchSystem'; -import TextureGCSystem from './systems/textures/TextureGCSystem'; -import { RENDERER_TYPE } from '@pixi/constants'; -import UniformGroup from '../shader/UniformGroup'; -import { Matrix } from '@pixi/math'; -import Runner from 'mini-runner'; - -/** - * 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 Render works by automatically managing webGLBatchs. - * 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 :) - * - * @class - * @memberof PIXI - * @extends PIXI.AbstractRenderer - */ -export default class Renderer extends AbstractRenderer -{ - // eslint-disable-next-line valid-jsdoc - /** - * - * @param {object} [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.transparent=false] - If the render view is transparent, default false - * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false - * @param {boolean} [options.antialias=false] - sets antialias. If not available natively then FXAA - * antialiasing is used - * @param {boolean} [options.forceFXAA=false] - forces FXAA antialiasing to be used over native. - * FXAA is faster, but may not always look as great - * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. - * The resolution of the renderer retina would be 2. - * @param {boolean} [options.clearBeforeRender=true] - This sets if the renderer will clear - * the canvas or not before the new render pass. If you wish to set this to false, you *must* set - * preserveDrawingBuffer to `true`. - * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, - * enable this if you need to call toDataUrl on the webgl context. - * @param {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when - * rendering, stopping pixel interpolation. - * @param {number} [options.backgroundColor=0x000000] - The background color of the rendered area - * (shown if not transparent). - * @param {boolean} [options.legacy=false] - If true PixiJS will aim to ensure compatibility - * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. - * @param {string} [options.powerPreference] - Parameter passed to webgl context, set to "high-performance" - * for devices with dual graphics card - */ - constructor(options, arg2, arg3) - { - super('WebGL', options, arg2, arg3); - - /** - * The type of this renderer as a standardised const - * - * @member {number} - * @see PIXI.RENDERER_TYPE - */ - this.type = RENDERER_TYPE.WEBGL; - - // this will be set by the contextSystem (this.context) - this.gl = null; - this.CONTEXT_UID = 0; - this.legacy = !!options.legacy; - - // TODO legacy! - - // runners! - this.runners = { - destroy: new Runner('destroy'), - contextChange: new Runner('contextChange', 1), - reset: new Runner('reset'), - update: new Runner('update'), - postrender: new Runner('postrender'), - prerender: new Runner('prerender'), - resize: new Runner('resize', 2), - }; - - 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(GeometrySystem, 'geometry') - .addSystem(FramebufferSystem, 'framebuffer') - .addSystem(StencilSystem, 'stencil') - .addSystem(ProjectionSystem, 'projection') - .addSystem(TextureGCSystem, 'textureGC') - .addSystem(FilterSystem, 'filter') - .addSystem(RenderTextureSystem, 'renderTexture') - .addSystem(BatchSystem, 'batch'); - - this.initPlugins(Renderer.__plugins); - - /** - * The options passed in to create a new webgl context. - * - * @member {object} - * @private - */ - if (options.context) - { - this.context.initFromContext(options.context); - } - else - { - this.context.initFromOptions({ - alpha: this.transparent, - antialias: options.antialias, - premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', - stencil: true, - 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); - } - - addSystem(_class, name) - { - if (!name) - { - name = _class.name; - } - - // TODO - read name from class.name.. - - /* - if(name.includes('System')) - { - name = name.replace('System', ''); - name = name.charAt(0).toLowerCase() + name.slice(1); - } - */ - - const system = new _class(this); - - if (this[name]) - { - throw new Error(`Whoops! ${name} is already a manger`); - } - - this[name] = system; - - for (const i in this.runners) - { - this.runners[i].add(system); - } - - return this; - - /** - * 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. - */ - } - - /** - * Renders the object to its webGL view - * - * @param {PIXI.DisplayObject} displayObject - the object to be rendered - * @param {PIXI.RenderTexture} renderTexture - The render texture to render to. - * @param {boolean} [clear] - Should the canvas be cleared before the new render - * @param {PIXI.Transform} [transform] - A transform to apply to the render texture before rendering. - * @param {boolean} [skipUpdateTransform] - Should we skip the update transform pass? - */ - render(displayObject, renderTexture, clear, transform, skipUpdateTransform) - { - // can be handy to know! - this.renderingToScreen = !renderTexture; - - this.runners.prerender.run(); - this.emit('prerender'); - - // 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.parent; - - displayObject.parent = this._tempDisplayObjectParent; - displayObject.updateTransform(); - displayObject.parent = 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.run(); - - this.emit('postrender'); - } - - /** - * Resizes the webGL view to the specified width and height. - * - * @param {number} screenWidth - the new width of the screen - * @param {number} screenHeight - the new height of the screen - */ - resize(screenWidth, screenHeight) - { - AbstractRenderer.prototype.resize.call(this, screenWidth, screenHeight); - - this.runners.resize.run(screenWidth, screenHeight); - } - - /** - * Resets the WebGL state so you can render things however you fancy! - * - * @return {PIXI.Renderer} Returns itself. - */ - reset() - { - this.runners.reset.run(); - - return this; - } - - clear() - { - this.framebuffer.bind(); - this.framebuffer.clear(); - } - - /** - * Removes everything from the renderer (event listeners, spritebatch, etc...) - * - * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. - * See: https://github.com/pixijs/pixi.js/issues/2233 - */ - destroy(removeView) - { - this.runners.destroy.run(); - - // call base destroy - super.destroy(removeView); - - // TODO nullify all the managers.. - this.gl = null; - } - - /** - * 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 - * builds and excluding plugins. - * @name PIXI.Renderer#plugins - * @type {object} - * @readonly - * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. - * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. - * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. - * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. - */ - - /** - * Adds a plugin to the renderer. - * - * @method - * @param {string} pluginName - The name of the plugin. - * @param {Function} ctor - The constructor function or class for the plugin. - */ - static registerPlugin(pluginName, ctor) - { - Renderer.__plugins = Renderer.__plugins || {}; - Renderer.__plugins[pluginName] = ctor; - } -} diff --git a/packages/core/src/renderers/State.js b/packages/core/src/renderers/State.js deleted file mode 100644 index fa3f221..0000000 --- a/packages/core/src/renderers/State.js +++ /dev/null @@ -1,163 +0,0 @@ -/* eslint-disable max-len */ - -const BLEND = 0; -const OFFSET = 1; -const CULLING = 2; -const DEPTH_TEST = 3; -const WINDING = 4; - -/** - * This is a webGL state. It is passed The WebGL StateManager. - * Each mesh renderered may require webGL to be in a different state. - * For example you may want different blend mode or to enable polygon offsets - * - * @class - * @memberof PIXI - */ -export default class State -{ - /** - * - */ - constructor() - { - this.data = 0; - - this.blendMode = 0; - this.polygonOffset = 0; - - this.blend = true; - // this.depthTest = true; - } - - /** - * Activates blending of the computed fragment color values - * - * @member {boolean} - */ - get blend() - { - return !!(this.data & (1 << BLEND)); - } - - set blend(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << BLEND)) !== value) - { - this.data ^= (1 << BLEND); - } - } - - /** - * Activates adding an offset to depth values of polygon's fragments - * - * @member {boolean} - * @default false - */ - get offsets() - { - return !!(this.data & (1 << OFFSET)); - } - - set offsets(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << OFFSET)) !== value) - { - this.data ^= (1 << OFFSET); - } - } - - /** - * Activates culling of polygons. - * - * @member {boolean} - * @default false - */ - get culling() - { - return !!(this.data & (1 << CULLING)); - } - - set culling(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << CULLING)) !== value) - { - this.data ^= (1 << CULLING); - } - } - - /** - * Activates depth comparisons and updates to the depth buffer. - * - * @member {boolean} - * @default false - */ - get depthTest() - { - return !!(this.data & (1 << DEPTH_TEST)); - } - - set depthTest(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << DEPTH_TEST)) !== value) - { - this.data ^= (1 << DEPTH_TEST); - } - } - - /** - * Specifies whether or not front or back-facing polygons can be culled. - * @member {boolean} - * @default false - */ - get clockwiseFrontFace() - { - return !!(this.data & (1 << WINDING)); - } - - set clockwiseFrontFace(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << WINDING)) !== value) - { - this.data ^= (1 << WINDING); - } - } - - /** - * The blend mode to be applied when this state is set. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. - * Setting this mode to anything other than NO_BLEND will automatically switch blending on. - * - * @member {boolean} - * @default PIXI.BLEND_MODES.NORMAL - * @see PIXI.BLEND_MODES - */ - get blendMode() - { - return this._blendMode; - } - - set blendMode(value) // eslint-disable-line require-jsdoc - { - // 17 is NO BLEND - this.blend = (value !== 17); - this._blendMode = value; - } - - /** - * The polygon offset. Setting this property to anything other than 0 will automatically enable poygon offset fill. - * - * @member {number} - * @default 0 - */ - get polygonOffset() - { - return this._polygonOffset; - } - - set polygonOffset(value) // eslint-disable-line require-jsdoc - { - this.offsets = !!value; - this._polygonOffset = value; - } -} - diff --git a/packages/core/src/renderers/filters/Filter.js b/packages/core/src/renderers/filters/Filter.js deleted file mode 100644 index 2073b8d..0000000 --- a/packages/core/src/renderers/filters/Filter.js +++ /dev/null @@ -1,140 +0,0 @@ -import Shader from '../../shader/Shader'; -import Program from '../../shader/Program'; -import State from '../State'; -import { settings } from '@pixi/settings'; -// import extractUniformsFromSrc from './extractUniformsFromSrc'; -import defaultVertex from './defaultFilter.vert'; -import defaultFragment from './defaultFilter.frag'; - -// let math = require('../../math'); -/** - * @class - * @memberof PIXI - * @extends PIXI.Shader - */ -export default class Filter extends Shader -{ - /** - * @param {string} [vertexSrc] - The source of the vertex shader. - * @param {string} [fragmentSrc] - The source of the fragment shader. - * @param {object} [uniforms] - Custom uniforms to use to augment the built-in ones. - */ - constructor(vertexSrc, fragmentSrc, uniforms) - { - const program = Program.from(vertexSrc, fragmentSrc); - - super(program, uniforms); - - /** - * The padding of the filter. Some filters require extra space to breath such as a blur. - * Increasing this will add extra width and height to the bounds of the object that the - * filter is applied to. - * - * @member {number} - */ - this.padding = 0; - - /** - * The resolution of the filter. Setting this to be lower will lower the quality but - * increase the performance of the filter. - * - * @member {number} - */ - this.resolution = settings.RESOLUTION; - - /** - * If enabled is true the filter is applied, if false it will not. - * - * @member {boolean} - */ - this.enabled = true; - - /** - * If enabled, PixiJS will fit the filter area into boundaries for better performance. - * Switch it off if it does not work for specific shader. - * - * @member {boolean} - */ - this.autoFit = true; - - /** - * Legacy filters use position and uvs from attributes - * @member {boolean} - * @readonly - */ - this.legacy = !!this.program.attributeData.aTextureCoord; - - /** - * the webGL state the filter requires to render - * @member {PIXI.State} - */ - this.state = new State(); - } - - /** - * Applies the filter - * - * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from - * @param {PIXI.RenderTarget} input - The input render target. - * @param {PIXI.RenderTarget} output - The target to output to. - * @param {boolean} clear - Should the output be cleared before rendering to it - * @param {object} [currentState] - It's current state of filter. - * There are some useful properties in the currentState : - * target, filters, sourceFrame, destinationFrame, renderTarget, resolution - */ - apply(filterManager, input, output, clear, currentState, derp) // eslint-disable-line no-unused-vars - { - // do as you please! - - filterManager.applyFilter(this, input, output, clear, currentState, derp); - - // or just do a regular render.. - } - - /** - * Sets the blendmode of the filter - * - * @member {number} - * @default PIXI.BLEND_MODES.NORMAL - */ - get blendMode() - { - return this.state.blendMode; - } - - set blendMode(value) // eslint-disable-line require-jsdoc - { - this.state.blendMode = value; - } - - /** - * The default vertex shader source - * - * @static - * @constant - */ - static get defaultVertexSrc() - { - return defaultVertex; - } - - /** - * The default fragment shader source - * - * @static - * @constant - */ - static get defaultFragmentSrc() - { - return defaultFragment; - } -} - -/** - * Used for caching shader IDs - * - * @static - * @private - */ -Filter.SOURCE_KEY_MAP = {}; - diff --git a/packages/core/src/renderers/filters/defaultFilter.frag b/packages/core/src/renderers/filters/defaultFilter.frag deleted file mode 100644 index a14c1be..0000000 --- a/packages/core/src/renderers/filters/defaultFilter.frag +++ /dev/null @@ -1,22 +0,0 @@ -varying vec2 vTextureCoord; -varying vec2 vFilterCoord; - -uniform sampler2D uSampler; -uniform sampler2D filterSampler; - -void main(void){ - vec4 masky = texture2D(filterSampler, vFilterCoord); - vec4 sample = texture2D(uSampler, vTextureCoord); - vec4 color; - if(mod(vFilterCoord.x, 1.0) > 0.5) - { - color = vec4(1.0, 0.0, 0.0, 1.0); - } - else - { - color = vec4(0.0, 1.0, 0.0, 1.0); - } - // gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0); - gl_FragColor = mix(sample, masky, 0.5); - gl_FragColor *= sample.a; -} diff --git a/packages/core/src/renderers/filters/defaultFilter.vert b/packages/core/src/renderers/filters/defaultFilter.vert deleted file mode 100644 index e1febca..0000000 --- a/packages/core/src/renderers/filters/defaultFilter.vert +++ /dev/null @@ -1,14 +0,0 @@ -attribute vec2 aVertexPosition; -attribute vec2 aTextureCoord; - -uniform mat3 projectionMatrix; -uniform mat3 filterMatrix; - -varying vec2 vTextureCoord; -varying vec2 vFilterCoord; - -void main(void){ - gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); - vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy; - vTextureCoord = aTextureCoord ; -} diff --git a/packages/core/src/renderers/filters/filterTransforms.js b/packages/core/src/renderers/filters/filterTransforms.js deleted file mode 100644 index 87a3cd6..0000000 --- a/packages/core/src/renderers/filters/filterTransforms.js +++ /dev/null @@ -1,50 +0,0 @@ -import { Matrix } from '@pixi/math'; - -/** - * Calculates the mapped matrix - * @param filterArea {Rectangle} The filter area - * @param sprite {Sprite} the target sprite - * @param outputMatrix {Matrix} @alvin - * @private - */ -// this returns a matrix that will normalise map filter cords in the filter to screen space -export function calculateScreenSpaceMatrix(outputMatrix, filterArea, textureSize) -{ - // TODO unwrap? - const mappedMatrix = outputMatrix.identity(); - - mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); - - mappedMatrix.scale(textureSize.width, textureSize.height); - - return mappedMatrix; -} - -export function calculateNormalizedScreenSpaceMatrix(outputMatrix, filterArea, textureSize) -{ - const mappedMatrix = outputMatrix.identity(); - - mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); - - const translateScaleX = (textureSize.width / filterArea.width); - const translateScaleY = (textureSize.height / filterArea.height); - - mappedMatrix.scale(translateScaleX, translateScaleY); - - return mappedMatrix; -} - -// this will map the filter coord so that a texture can be used based on the transform of a sprite -export function calculateSpriteMatrix(outputMatrix, filterArea, textureSize, sprite) -{ - const orig = sprite._texture.orig; - const mappedMatrix = outputMatrix.set(textureSize.width, 0, 0, textureSize.height, filterArea.x, filterArea.y); - const worldTransform = sprite.worldTransform.copyTo(Matrix.TEMP_MATRIX); - - worldTransform.invert(); - mappedMatrix.prepend(worldTransform); - mappedMatrix.scale(1.0 / orig.width, 1.0 / orig.height); - mappedMatrix.translate(sprite.anchor.x, sprite.anchor.y); - - return mappedMatrix; -} diff --git a/packages/core/src/renderers/filters/spriteMask/SpriteMaskFilter.js b/packages/core/src/renderers/filters/spriteMask/SpriteMaskFilter.js deleted file mode 100644 index d5a91a4..0000000 --- a/packages/core/src/renderers/filters/spriteMask/SpriteMaskFilter.js +++ /dev/null @@ -1,63 +0,0 @@ -import Filter from '../Filter'; -import { Matrix } from '@pixi/math'; -import vertex from './spriteMaskFilter.vert'; -import fragment from './spriteMaskFilter.frag'; -import { default as TextureMatrix } from '../../../textures/TextureMatrix'; - -/** - * The SpriteMaskFilter class - * - * @class - * @extends PIXI.Filter - * @memberof PIXI - */ -export default class SpriteMaskFilter extends Filter -{ - /** - * @param {PIXI.Sprite} sprite - the target sprite - */ - constructor(sprite) - { - const maskMatrix = new Matrix(); - - super(vertex, fragment); - - sprite.renderable = false; - - this.maskSprite = sprite; - this.maskMatrix = maskMatrix; - } - - /** - * Applies the filter - * - * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from - * @param {PIXI.RenderTarget} input - The input render target. - * @param {PIXI.RenderTarget} output - The target to output to. - */ - apply(filterManager, input, output) - { - const maskSprite = this.maskSprite; - const tex = this.maskSprite.texture; - - if (!tex.valid) - { - return; - } - if (!tex.transform) - { - // margin = 0.0, let it bleed a bit, shader code becomes easier - // assuming that atlas textures were made with 1-pixel padding - tex.transform = new TextureMatrix(tex, 0.0); - } - tex.transform.update(); - - this.uniforms.mask = tex; - this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite) - .prepend(tex.transform.mapCoord); - this.uniforms.alpha = maskSprite.worldAlpha; - this.uniforms.maskClamp = tex.transform.uClampFrame; - - filterManager.applyFilter(this, input, output); - } -} diff --git a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.frag b/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.frag deleted file mode 100644 index 0e4aef8..0000000 --- a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.frag +++ /dev/null @@ -1,23 +0,0 @@ -varying vec2 vMaskCoord; -varying vec2 vTextureCoord; - -uniform sampler2D uSampler; -uniform sampler2D mask; -uniform float alpha; -uniform vec4 maskClamp; - -void main(void) -{ - float clip = step(3.5, - step(maskClamp.x, vMaskCoord.x) + - step(maskClamp.y, vMaskCoord.y) + - step(vMaskCoord.x, maskClamp.z) + - step(vMaskCoord.y, maskClamp.w)); - - vec4 original = texture2D(uSampler, vTextureCoord); - vec4 masky = texture2D(mask, vMaskCoord); - - original *= (masky.r * masky.a * alpha * clip); - - gl_FragColor = original; -} diff --git a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.vert b/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.vert deleted file mode 100644 index d23a30c..0000000 --- a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.vert +++ /dev/null @@ -1,16 +0,0 @@ -attribute vec2 aVertexPosition; -attribute vec2 aTextureCoord; - -uniform mat3 projectionMatrix; -uniform mat3 otherMatrix; - -varying vec2 vMaskCoord; -varying vec2 vTextureCoord; - -void main(void) -{ - gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); - - vTextureCoord = aTextureCoord; - vMaskCoord = ( otherMatrix * vec3( aTextureCoord, 1.0) ).xy; -} diff --git a/packages/core/src/renderers/systems/BatchSystem.js b/packages/core/src/renderers/systems/BatchSystem.js deleted file mode 100644 index 70e9518..0000000 --- a/packages/core/src/renderers/systems/BatchSystem.js +++ /dev/null @@ -1,68 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import ObjectRenderer from '../utils/ObjectRenderer'; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ - -export default class BatchSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this manager works for. - */ - constructor(renderer) - { - super(renderer); - - /** - * An empty renderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.emptyRenderer = new ObjectRenderer(renderer); - - /** - * The currently active ObjectRenderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.currentRenderer = this.emptyRenderer; - } - - /** - * Changes the current renderer to the one given in parameter - * - * @param {PIXI.ObjectRenderer} objectRenderer - The object renderer to use. - */ - setObjectRenderer(objectRenderer) - { - if (this.currentRenderer === objectRenderer) - { - return; - } - - this.currentRenderer.stop(); - this.currentRenderer = objectRenderer; - - this.renderer.state.setState(objectRenderer.state); - - this.currentRenderer.start(); - } - - /** - * This should be called if you wish to do some custom rendering - * It will basically render anything that may be batched up such as sprites - * - */ - flush() - { - this.setObjectRenderer(this.emptyRenderer); - } - - reset() - { - this.setObjectRenderer(this.emptyRenderer); - } -} diff --git a/packages/core/src/renderers/systems/ContextSystem.js b/packages/core/src/renderers/systems/ContextSystem.js deleted file mode 100644 index 43bf827..0000000 --- a/packages/core/src/renderers/systems/ContextSystem.js +++ /dev/null @@ -1,186 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { settings } from '@pixi/settings'; - -let CONTEXT_UID = 0; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class ContextSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this.webGLVersion = 1; - - this.handleContextLost = this.handleContextLost.bind(this); - this.handleContextRestored = this.handleContextRestored.bind(this); - - this.extensions = {}; - - renderer.view.addEventListener('webglcontextlost', this.handleContextLost, false); - renderer.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); - } - - get isLost() - { - return (!this.gl || this.gl.isContextLost()); - } - - contextChange(gl) - { - this.gl = gl; - - // restore a context if it was previously lost - if (gl.isContextLost() && gl.getExtension('WEBGL_lose_context')) - { - gl.getExtension('WEBGL_lose_context').restoreContext(); - } - } - - initFromContext(gl) - { - this.gl = gl; - this.validateContext(gl); - this.renderer.gl = gl; - this.renderer.CONTEXT_UID = CONTEXT_UID++; - this.renderer.runners.contextChange.run(gl); - } - - initFromOptions(options) - { - const gl = this.createContext(this.renderer.view, options); - - this.initFromContext(gl); - } - - /** - * Helper class to create a webGL Context - * - * @class - * @memberof PIXI.glCore - * @param canvas {HTMLCanvasElement} the canvas element that we will get the context from - * @param options {Object} An options object that gets passed in to the canvas element containing the context attributes, - * see https://developer.mozilla.org/en/docs/Web/API/HTMLCanvasElement/getContext for the options available - * @return {WebGLRenderingContext} the WebGL context - */ - createContext(canvas, options) - { - let gl; - - if (settings.PREFER_WEBGL_2) - { - gl = canvas.getContext('webgl2', options); - } - - if (gl) - { - this.webGLVersion = 2; - } - else - { - this.webGLVersion = 1; - - gl = canvas.getContext('webgl', options) - || canvas.getContext('experimental-webgl', options); - - if (!gl) - { - // fail, not able to get a context - throw new Error('This browser does not support webGL. Try using the canvas renderer'); - } - } - - this.gl = gl; - - this.getExtensions(); - - return gl; - } - - getExtensions() - { - // time to set up default etensions that pixi uses.. - const gl = this.gl; - const extensions = this.extensions; - - if (this.webGLVersion === 1) - { - extensions.drawBuffers = gl.getExtension('WEBGL_draw_buffers'); - extensions.depthTexture = gl.getExtension('WEBKIT_WEBGL_depth_texture'); - extensions.floatTexture = gl.getExtension('OES_texture_float'); - extensions.loseContext = gl.getExtension('WEBGL_lose_context'); - - extensions.vertexArrayObject = gl.getExtension('OES_vertex_array_object') - || gl.getExtension('MOZ_OES_vertex_array_object') - || gl.getExtension('WEBKIT_OES_vertex_array_object'); - } - - // we don't use any specific WebGL 2 ones yet! - } - - /** - * Handles a lost webgl context - * - * @private - * @param {WebGLContextEvent} event - The context lost event. - */ - handleContextLost(event) - { - event.preventDefault(); - } - - /** - * Handles a restored webgl context - * - * @private - */ - handleContextRestored() - { - this.renderer.runners.contextChange.run(this.gl); - } - - destroy() - { - const view = this.renderer.view; - - // remove listeners - view.removeEventListener('webglcontextlost', this.handleContextLost); - view.removeEventListener('webglcontextrestored', this.handleContextRestored); - - this.gl.useProgram(null); - - if (this.extensions.loseContext) - { - this.extensions.loseContext.loseContext(); - } - } - - postrender() - { - this.gl.flush(); - } - - validateContext(gl) - { - const attributes = gl.getContextAttributes(); - - // this is going to be fairly simple for now.. but at least we have room to grow! - if (!attributes.stencil) - { - /* eslint-disable max-len */ - - /* eslint-disable no-console */ - console.warn('Provided WebGL context does not have a stencil buffer, masks may not render correctly'); - /* eslint-enable no-console */ - - /* eslint-enable max-len */ - } - } -} diff --git a/packages/core/src/renderers/systems/FramebufferSystem.js b/packages/core/src/renderers/systems/FramebufferSystem.js deleted file mode 100644 index df7902b..0000000 --- a/packages/core/src/renderers/systems/FramebufferSystem.js +++ /dev/null @@ -1,246 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { Rectangle } from '@pixi/math'; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class FramebufferSystem extends WebGLSystem -{ - /** - * Sets up the renderer context and necessary buffers. - * - * @private - */ - contextChange() - { - this.gl = this.renderer.gl; - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - this.current = null; - this.viewport = new Rectangle(); - - this.drawBufferExtension = this.renderer.context.extensions.drawBuffers; - } - - bind(framebuffer, frame) - { - const gl = this.gl; - - this.current = framebuffer; - - if (framebuffer) - { - // TODO cacheing layer! - - const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID] || this.initFramebuffer(framebuffer); - - gl.bindFramebuffer(gl.FRAMEBUFFER, fbo.framebuffer); - // makesure all textures are unbound.. - - // now check for updates... - if (fbo.dirtyId !== framebuffer.dirtyId) - { - fbo.dirtyId = framebuffer.dirtyId; - - if (fbo.dirtyFormat !== framebuffer.dirtyFormat) - { - fbo.dirtyFormat = framebuffer.dirtyFormat; - this.updateFramebuffer(framebuffer); - } - else if (fbo.dirtySize !== framebuffer.dirtySize) - { - fbo.dirtySize = framebuffer.dirtySize; - this.resizeFramebuffer(framebuffer); - } - } - - for (let i = 0; i < framebuffer.colorTextures.length; i++) - { - if (framebuffer.colorTextures[i].texturePart) - { - this.renderer.texture.unbind(framebuffer.colorTextures[i].texture); - } - else - { - this.renderer.texture.unbind(framebuffer.colorTextures[i]); - } - } - - if (framebuffer.depthTexture) - { - this.renderer.texture.unbind(framebuffer.depthTexture); - } - - if (frame) - { - this.setViewport(frame.x, frame.y, frame.width, frame.height); - } - else - { - this.setViewport(0, 0, framebuffer.width, framebuffer.height); - } - } - else - { - gl.bindFramebuffer(gl.FRAMEBUFFER, null); - - if (frame) - { - this.setViewport(frame.x, frame.y, frame.width, frame.height); - } - else - { - this.setViewport(0, 0, this.renderer.width, this.renderer.height); - } - } - } - - setViewport(x, y, width, height) - { - const v = this.viewport; - - if (v.width !== width || v.height !== height || v.x !== x || v.y !== y) - { - v.x = x; - v.y = y; - v.width = width; - v.height = height; - - this.gl.viewport(x, y, width, height); - } - } - - get size() - { - if (this.current) - { - // TODO store temp - return { x: 0, y: 0, width: this.current.width, height: this.current.height }; - } - - return { x: 0, y: 0, width: this.renderer.width, height: this.renderer.height }; - } - - clear(r, g, b, a) - { - const gl = this.gl; - - // TODO clear color can be set only one right? - gl.clearColor(r, g, b, a); - gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); - } - - // private functions... - - initFramebuffer(framebuffer) - { - const gl = this.gl; - - // TODO - make this a class? - const fbo = { - framebuffer: gl.createFramebuffer(), - stencil: null, - dirtyId: 0, - dirtyFormat: 0, - dirtySize: 0, - }; - - framebuffer.glFrameBuffers[this.CONTEXT_UID] = fbo; - - return fbo; - } - - resizeFramebuffer(framebuffer) - { - const gl = this.gl; - - if (framebuffer.stencil || framebuffer.depth) - { - gl.bindRenderbuffer(gl.RENDERBUFFER, this.stencil); - gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); - } - } - - updateFramebuffer(framebuffer) - { - const gl = this.gl; - - const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID]; - - // bind the color texture - const colorTextures = framebuffer.colorTextures; - - let count = colorTextures.length; - - if (!this.drawBufferExtension) - { - count = Math.min(count, 1); - } - - const activeTextures = []; - - for (let i = 0; i < count; i++) - { - const texture = framebuffer.colorTextures[i]; - - if (texture.texturePart) - { - this.renderer.texture.bind(texture.texture, 0); - - gl.framebufferTexture2D(gl.FRAMEBUFFER, - gl.COLOR_ATTACHMENT0 + i, - gl.TEXTURE_CUBE_MAP_NEGATIVE_X + texture.side, - texture.texture._glTextures[this.CONTEXT_UID].texture, - 0); - } - else - { - this.renderer.texture.bind(texture, 0); - - gl.framebufferTexture2D(gl.FRAMEBUFFER, - gl.COLOR_ATTACHMENT0 + i, - gl.TEXTURE_2D, - texture._glTextures[this.CONTEXT_UID].texture, - 0); - } - - activeTextures.push(gl.COLOR_ATTACHMENT0 + i); - } - - if (this.drawBufferExtension && activeTextures.length > 1) - { - this.drawBufferExtension.drawBuffersWEBGL(activeTextures); - } - - if (framebuffer.depthTexture) - { - const depthTextureExt = this.renderer.context.extensions.depthTexture; - - if (depthTextureExt) - { - const depthTexture = framebuffer.depthTexture; - - this.renderer.texture.bind(depthTexture, 0); - - gl.framebufferTexture2D(gl.FRAMEBUFFER, - gl.DEPTH_ATTACHMENT, - gl.TEXTURE_2D, - depthTexture._glTextures[this.CONTEXT_UID].texture, - 0); - } - } - - if (framebuffer.stencil || framebuffer.depth) - { - fbo.stencil = gl.createRenderbuffer(); - - gl.bindRenderbuffer(gl.RENDERBUFFER, fbo.stencil); - - // TODO.. this is depth AND stencil? - gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, fbo.stencil); - gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); - // fbo.enableStencil(); - } - } -} diff --git a/packages/core/src/renderers/systems/MaskSystem.js b/packages/core/src/renderers/systems/MaskSystem.js deleted file mode 100644 index d9c4b1a..0000000 --- a/packages/core/src/renderers/systems/MaskSystem.js +++ /dev/null @@ -1,199 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import AlphaMaskFilter from '../filters/spriteMask/SpriteMaskFilter'; - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class MaskSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - // TODO - we don't need both! - this.scissor = false; - this.scissorData = null; - this.scissorRenderTarget = null; - - this.enableScissor = false; - - this.alphaMaskPool = []; - this.alphaMaskIndex = 0; - } - - /** - * Applies the Mask and adds it to the current filter stack. - * - * @param {PIXI.DisplayObject} target - Display Object to push the mask to - * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. - */ - push(target, maskData) - { - // TODO the root check means scissor rect will not - // be used on render textures more info here: - // https://github.com/pixijs/pixi.js/pull/3545 - - if (maskData.texture) - { - this.pushSpriteMask(target, maskData); - } - else if (this.enableScissor - && !this.scissor - && this.renderer._activeRenderTarget.root - && !this.renderer.stencil.stencilMaskStack.length - && maskData.isFastRect()) - { - const matrix = maskData.worldTransform; - - let rot = Math.atan2(matrix.b, matrix.a); - - // use the nearest degree! - rot = Math.round(rot * (180 / Math.PI)); - - if (rot % 90) - { - this.pushStencilMask(maskData); - } - else - { - this.pushScissorMask(target, maskData); - } - } - else - { - this.pushStencilMask(maskData); - } - } - - /** - * Removes the last mask from the mask stack and doesn't return it. - * - * @param {PIXI.DisplayObject} target - Display Object to pop the mask from - * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. - */ - pop(target, maskData) - { - if (maskData.texture) - { - this.popSpriteMask(target, maskData); - } - else if (this.enableScissor && !this.renderer.stencil.stencilMaskStack.length) - { - this.popScissorMask(target, maskData); - } - else - { - this.popStencilMask(target, maskData); - } - } - - /** - * Applies the Mask and adds it to the current filter stack. - * - * @param {PIXI.RenderTarget} target - Display Object to push the sprite mask to - * @param {PIXI.Sprite} maskData - Sprite to be used as the mask - */ - pushSpriteMask(target, maskData) - { - let alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; - - if (!alphaMaskFilter) - { - alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; - } - - alphaMaskFilter[0].resolution = this.renderer.resolution; - alphaMaskFilter[0].maskSprite = maskData; - - // TODO - may cause issues! - target.filterArea = maskData.getBounds(true); - - this.renderer.filterSystem.pushFilter(target, alphaMaskFilter); - - this.alphaMaskIndex++; - } - - /** - * Removes the last filter from the filter stack and doesn't return it. - * - */ - popSpriteMask() - { - this.renderer.filterSystem.popFilter(); - this.alphaMaskIndex--; - } - - /** - * Applies the Mask and adds it to the current filter stack. - * - * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. - */ - pushStencilMask(maskData) - { - this.renderer.batch.flush(); - this.renderer.stencil.pushStencil(maskData); - } - - /** - * Removes the last filter from the filter stack and doesn't return it. - * - */ - popStencilMask() - { - // this.renderer.currentRenderer.stop(); - this.renderer.stencil.popStencil(); - } - - /** - * - * @param {PIXI.DisplayObject} target - Display Object to push the mask to - * @param {PIXI.Graphics} maskData - The masking data. - */ - pushScissorMask(target, maskData) - { - maskData.renderable = true; - - const renderTarget = this.renderer._activeRenderTarget; - - const bounds = maskData.getBounds(); - - bounds.fit(renderTarget.size); - maskData.renderable = false; - - this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); - - const resolution = this.renderer.resolution; - - this.renderer.gl.scissor( - bounds.x * resolution, - (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, - bounds.width * resolution, - bounds.height * resolution - ); - - this.scissorRenderTarget = renderTarget; - this.scissorData = maskData; - this.scissor = true; - } - - /** - * - * - */ - popScissorMask() - { - this.scissorRenderTarget = null; - this.scissorData = null; - this.scissor = false; - - // must be scissor! - const gl = this.renderer.gl; - - gl.disable(gl.SCISSOR_TEST); - } -} diff --git a/packages/core/src/renderers/systems/ProjectionSystem.js b/packages/core/src/renderers/systems/ProjectionSystem.js deleted file mode 100644 index ec5efdb..0000000 --- a/packages/core/src/renderers/systems/ProjectionSystem.js +++ /dev/null @@ -1,73 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { Matrix } from '@pixi/math'; - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ - -export default class ProjectionSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this.projectionMatrix = new Matrix(); - } - - update(destinationFrame, sourceFrame, resolution, root) - { - this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; - this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; - - this.calculateProjection(this.destinationFrame, this.sourceFrame, resolution, root); - - this.renderer.globalUniforms.uniforms.projectionMatrix = this.projectionMatrix; - this.renderer.globalUniforms.update(); - } - - /** - * Updates the projection matrix based on a projection frame (which is a rectangle) - * - * @param {Rectangle} destinationFrame - The destination frame. - * @param {Rectangle} sourceFrame - The source frame. - */ - calculateProjection(destinationFrame, sourceFrame, resolution, root) - { - const pm = this.projectionMatrix; - - // I don't think we will need this line.. - // pm.identity(); - - if (!root) - { - pm.a = (1 / destinationFrame.width * 2) * resolution; - pm.d = (1 / destinationFrame.height * 2) * resolution; - - pm.tx = -1 - (sourceFrame.x * pm.a); - pm.ty = -1 - (sourceFrame.y * pm.d); - } - else - { - pm.a = (1 / destinationFrame.width * 2) * resolution; - pm.d = (-1 / destinationFrame.height * 2) * resolution; - - pm.tx = -1 - (sourceFrame.x * pm.a); - pm.ty = 1 - (sourceFrame.y * pm.d); - } - } - - /** - * Sets the transform of the active render target to the given matrix - * - * @param {PIXI.Matrix} matrix - The transformation matrix - */ - setTransform()// matrix) - { - // this._activeRenderTarget.transform = matrix; - } -} diff --git a/packages/core/src/renderers/systems/RenderTextureSystem.js b/packages/core/src/renderers/systems/RenderTextureSystem.js deleted file mode 100644 index a133ef1..0000000 --- a/packages/core/src/renderers/systems/RenderTextureSystem.js +++ /dev/null @@ -1,115 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { Rectangle } from '@pixi/math'; - -const tempRect = new Rectangle(); - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ - -export default class RenderTextureSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this.clearColor = renderer._backgroundColorRgba; - - // TODO moe this property somewhere else! - this.defaultMaskStack = []; - this.defaultFilterStack = [{}]; - - // empty render texture? - this.renderTexture = null; - - this.destinationFrame = new Rectangle(); - } - - bind(renderTexture, sourceFrame, destinationFrame) - { - // TODO - do we want this?? - if (this.renderTexture === renderTexture) return; - this.renderTexture = renderTexture; - - const renderer = this.renderer; - - if (renderTexture) - { - const baseTexture = renderTexture.baseTexture; - - if (!destinationFrame) - { - tempRect.width = baseTexture.realWidth; - tempRect.height = baseTexture.realHeight; - - destinationFrame = tempRect; - } - - if (!sourceFrame) - { - sourceFrame = destinationFrame; - } - - this.renderer.framebuffer.bind(baseTexture.frameBuffer, destinationFrame); - - this.renderer.projection.update(destinationFrame, sourceFrame, baseTexture.resolution, false); - this.renderer.stencil.setMaskStack(baseTexture.stencilMaskStack); - } - else - { - // TODO these validation checks happen deeper down.. - // thing they can be avoided.. - if (!destinationFrame) - { - tempRect.width = renderer.width; - tempRect.height = renderer.height; - - destinationFrame = tempRect; - } - - if (!sourceFrame) - { - sourceFrame = destinationFrame; - } - - renderer.framebuffer.bind(null, destinationFrame); - - // TODO store this.. - this.renderer.projection.update(destinationFrame, sourceFrame, this.renderer.resolution, true); - this.renderer.stencil.setMaskStack(this.defaultMaskStack); - } - - this.destinationFrame.copyFrom(destinationFrame); - } - - /** - * Erases the render texture and fills the drawing area with a colour - * - * @param {number} [clearColor] - The colour - * @return {PIXI.Renderer} Returns itself. - */ - clear(clearColor) - { - if (this.renderTexture) - { - clearColor = clearColor || this.renderTexture.baseTexture.clearColor; - } - else - { - clearColor = clearColor || this.clearColor; - } - - this.renderer.framebuffer.clear(clearColor[0], clearColor[1], clearColor[2], clearColor[3]); - } - - resize()// screenWidth, screenHeight) - { - // resize the root only! - this.bind(null); - } -} diff --git a/packages/core/src/renderers/systems/StateSystem.js b/packages/core/src/renderers/systems/StateSystem.js deleted file mode 100755 index d7f4caf..0000000 --- a/packages/core/src/renderers/systems/StateSystem.js +++ /dev/null @@ -1,288 +0,0 @@ -import mapWebGLBlendModesToPixi from '../utils/mapWebGLBlendModesToPixi'; -import WebGLSystem from './WebGLSystem'; -import WebGLState from '../State'; - -const BLEND = 0; -const OFFSET = 1; -const CULLING = 2; -const DEPTH_TEST = 3; -const WINDING = 4; - -/** - * A WebGL state machines - * - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class StateSystem extends WebGLSystem -{ - /** - * @param {WebGLRenderingContext} gl - The current WebGL rendering context - */ - constructor(renderer) - { - super(renderer); - - this.gl = null; - - this.maxAttribs = null; - - // check we have vao.. - this.nativeVaoExtension = null; - - this.attribState = null; - - this.stateId = 0; - this.polygonOffset = 0; - this.blendMode = 17; - - this.map = []; - - // map functions for when we set state.. - this.map[BLEND] = this.setBlend; - this.map[OFFSET] = this.setOffset; - this.map[CULLING] = this.setCullFace; - this.map[DEPTH_TEST] = this.setDepthTest; - this.map[WINDING] = this.setFrontFace; - - this.checks = []; - - this.defaultState = new WebGLState(); - this.defaultState.blend = true; - this.defaultState.depth = true; - } - - contextChange(gl) - { - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; - - this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); - - // check we have vao.. - this.nativeVaoExtension = ( - gl.getExtension('OES_vertex_array_object') - || gl.getExtension('MOZ_OES_vertex_array_object') - || gl.getExtension('WEBKIT_OES_vertex_array_object') - ); - - this.attribState = { - tempAttribState: new Array(this.maxAttribs), - attribState: new Array(this.maxAttribs), - }; - - this.blendModes = mapWebGLBlendModesToPixi(gl); - - this.setState(this.defaultState); - - this.reset(); - } - - /** - * Sets the current state - * - * @param {*} state - The state to set. - */ - setState(state) - { - state = state || this.defaultState; - - // TODO maybe to an object check? ( this.state === state )? - if (this.stateId !== state.data) - { - let diff = this.stateId ^ state.data; - let i = 0; - - // order from least to most common - while (diff) - { - if (diff & 1) - { - // state change! - this.map[i].call(this, !!(state.data & (1 << i))); - } - - diff = diff >> 1; - i++; - } - - this.stateId = state.data; - } - - // based on the above settings we check for specific modes.. - // for example if blend is active we check and set the blend modes - // or of polygon offset is active we check the poly depth. - for (let i = 0; i < this.checks.length; i++) - { - this.checks[i](this, state); - } - } - - /** - * Enables or disabled blending. - * - * @param {boolean} value - Turn on or off webgl blending. - */ - setBlend(value) - { - this.updateCheck(StateSystem.checkBlendMode, value); - - this.gl[value ? 'enable' : 'disable'](this.gl.BLEND); - } - - /** - * Enables or disable polygon offset fill - * - * @param {boolean} value - Turn on or off webgl polygon offset testing. - */ - setOffset(value) - { - this.gl[value ? 'enable' : 'disable'](this.gl.POLYGON_OFFSET_FILL); - } - - /** - * Sets whether to enable or disable depth test. - * - * @param {boolean} value - Turn on or off webgl depth testing. - */ - setDepthTest(value) - { - this.gl[value ? 'enable' : 'disable'](this.gl.DEPTH_TEST); - } - - /** - * Sets whether to enable or disable cull face. - * - * @param {boolean} value - Turn on or off webgl cull face. - */ - setCullFace(value) - { - this.gl[value ? 'enable' : 'disable'](this.gl.CULL_FACE); - } - - /** - * Sets the gl front face. - * - * @param {boolean} value - true is clockwise and false is counter-clockwise - */ - setFrontFace(value) - { - this.gl.frontFace(this.gl[value ? 'CW' : 'CCW']); - } - - /** - * Sets the blend mode. - * - * @param {number} value - The blend mode to set to. - */ - setBlendMode(value) - { - if (value === this.blendMode) - { - return; - } - - this.blendMode = value; - this.gl.blendFunc(this.blendModes[value][0], this.blendModes[value][1]); - } - - /** - * Sets the polygon offset. - * - * @param {number} value - the polygon offset - * @param {number} scale - the polygon offset scale - */ - setPolygonOffset(value, scale) - { - this.gl.polygonOffset(value, scale); - } - - /** - * Disables all the vaos in use - * - */ - resetAttributes() - { - for (let i = 0; i < this.attribState.tempAttribState.length; i++) - { - this.attribState.tempAttribState[i] = 0; - } - - for (let i = 0; i < this.attribState.attribState.length; i++) - { - this.attribState.attribState[i] = 0; - } - - // im going to assume one is always active for performance reasons. - for (let i = 1; i < this.maxAttribs; i++) - { - this.gl.disableVertexAttribArray(i); - } - } - - // used - /** - * Resets all the logic and disables the vaos - */ - reset() - { - // unbind any VAO if they exist.. - if (this.nativeVaoExtension) - { - this.nativeVaoExtension.bindVertexArrayOES(null); - } - - // reset all attributes.. - this.resetAttributes(); - - this.gl.pixelStorei(this.gl.UNPACK_FLIP_Y_WEBGL, false); - - this.setBlendMode(0); - - // TODO? - // this.setState(this.defaultState); - } - - /** - * checks to see which updates should be checked based on which settings have been activated - * for example if blend is enabled then we shold check the blend modes each time the state is changed - * or if poygon fill is activated then we need to check if the polygone offset changes. - * The idea is that we only check what we have too. - * - * @param {Function} func the checking function to add or remove - * @param {boolean} value should the check function be added or removed. - */ - updateCheck(func, value) - { - const index = this.checks.indexOf(func); - - if (value && index === -1) - { - this.checks.push(func); - } - else if (!value && index !== -1) - { - this.checks.splice(index, 1); - } - } - - /** - * A private little wrapper function that we call to check the blend mode. - * - * @static - * @private - * @param {PIXI.StateSystem} System the System to perform the state check on - * @param {PIXI.State} state the state that the blendMode will pulled from - */ - static checkBlendMode(system, state) - { - system.setBlendMode(state.blendMode); - } - - // TODO - add polygon offset? -} diff --git a/packages/core/src/renderers/systems/StencilSystem.js b/packages/core/src/renderers/systems/StencilSystem.js deleted file mode 100644 index 5d538cb..0000000 --- a/packages/core/src/renderers/systems/StencilSystem.js +++ /dev/null @@ -1,133 +0,0 @@ -import WebGLSystem from './WebGLSystem'; - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class StencilSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - this.stencilMaskStack = []; - } - - /** - * Changes the mask stack that is used by this System. - * - * @param {PIXI.Graphics[]} stencilMaskStack - The mask stack - */ - setMaskStack(stencilMaskStack) - { - const gl = this.renderer.gl; - - if (stencilMaskStack.length !== this.stencilMaskStack.length) - { - if (stencilMaskStack.length === 0) - { - gl.disable(gl.STENCIL_TEST); - } - else - { - gl.enable(gl.STENCIL_TEST); - } - } - - this.stencilMaskStack = stencilMaskStack; - } - - /** - * Applies the Mask and adds it to the current stencil stack. @alvin - * - * @param {PIXI.Graphics} graphics - The mask - */ - pushStencil(graphics) - { - this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); - - // this.renderer._activeRenderTarget.attachStencilBuffer(); - - const gl = this.renderer.gl; - const prevMaskCount = this.stencilMaskStack.length; - - if (prevMaskCount === 0) - { - gl.enable(gl.STENCIL_TEST); - } - - this.stencilMaskStack.push(graphics); - - // Increment the refference stencil value where the new mask overlaps with the old ones. - gl.colorMask(false, false, false, false); - gl.stencilFunc(gl.EQUAL, prevMaskCount, this._getBitwiseMask()); - gl.stencilOp(gl.KEEP, gl.KEEP, gl.INCR); - this.renderer.plugins.graphics.render(graphics); - - this._useCurrent(); - } - - /** - * Removes the last mask from the stencil stack. @alvin - */ - popStencil() - { - this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); - - const gl = this.renderer.gl; - const graphics = this.stencilMaskStack.pop(); - - if (this.stencilMaskStack.length === 0) - { - // the stack is empty! - gl.disable(gl.STENCIL_TEST); - gl.clear(gl.STENCIL_BUFFER_BIT); - gl.clearStencil(0); - } - else - { - // Decrement the refference stencil value where the popped mask overlaps with the other ones - gl.colorMask(false, false, false, false); - gl.stencilOp(gl.KEEP, gl.KEEP, gl.DECR); - this.renderer.plugins.graphics.render(graphics); - - this._useCurrent(); - } - } - - /** - * Setup renderer to use the current stencil data. - */ - _useCurrent() - { - const gl = this.renderer.gl; - - gl.colorMask(true, true, true, true); - gl.stencilFunc(gl.EQUAL, this.stencilMaskStack.length, this._getBitwiseMask()); - gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP); - } - - /** - * Fill 1s equal to the number of acitve stencil masks. - * - * @return {number} The bitwise mask. - */ - _getBitwiseMask() - { - return (1 << this.stencilMaskStack.length) - 1; - } - - /** - * Destroys the mask stack. - * - */ - destroy() - { - super.destroy(this); - - this.stencilMaskStack = null; - } -} diff --git a/packages/core/src/renderers/systems/WebGLSystem.js b/packages/core/src/renderers/systems/WebGLSystem.js deleted file mode 100644 index d3d5b31..0000000 --- a/packages/core/src/renderers/systems/WebGLSystem.js +++ /dev/null @@ -1,41 +0,0 @@ -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this manager works for. - */ - constructor(renderer) - { - /** - * The renderer this manager works for. - * - * @member {PIXI.Renderer} - */ - this.renderer = renderer; - - this.renderer.runners.contextChange.add(this); - } - - /** - * Generic method called when there is a WebGL context change. - * - */ - contextChange() - { - // do some codes init! - } - - /** - * Generic destroy methods to be overridden by the subclass - * - */ - destroy() - { - this.renderer.runners.contextChange.remove(this); - this.renderer = null; - } -} diff --git a/packages/core/src/renderers/systems/filter/FilterSystem.js b/packages/core/src/renderers/systems/filter/FilterSystem.js deleted file mode 100644 index e615037..0000000 --- a/packages/core/src/renderers/systems/filter/FilterSystem.js +++ /dev/null @@ -1,431 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; - -import RenderTexture from '../../../textures/RenderTexture'; -import Quad from '../../utils/Quad'; -import QuadUv from '../../utils/QuadUv'; -import { Rectangle } from '@pixi/math'; -import * as filterTransforms from '../../filters/filterTransforms'; -import bitTwiddle from 'bit-twiddle'; -import UniformGroup from '../../../shader/UniformGroup'; -import { DRAW_MODES } from '../../../../../constants'; - -// -/** - * @ignore - * @class - */ -class FilterState -{ - /** - * - */ - constructor() - { - this.renderTexture = null; - this.sourceFrame = new Rectangle(); - this.destinationFrame = new Rectangle(); - this.filters = []; - this.target = null; - this.legacy = false; - this.resolution = 1; - } -} - -/** - * @class - * @memberof PIXI.systems - * @extends PIXI.systems.WebGLSystem - */ -export default class FilterSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - /** - * stores a bunch of PO2 textures used for filtering - * @type {Object} - */ - this.texturePool = {}; - - /** - * a pool for storing filter states, save us creating new ones each tick - * @type {Array} - */ - this.statePool = []; - - /** - * A very simple geometry used when drawing a filter effect to the screen - * @type {Quad} - */ - this.quad = new Quad(); - - this.quadUv = new QuadUv(); - - /** - * Temporary rect for maths - * @type {PIXI.Rectangle} - */ - this.tempRect = new Rectangle(); - - this.activeState = {}; - - /** - * this uniform group is attached to filter uniforms when used - * @type {UniformGroup} - */ - this.globalUniforms = new UniformGroup({ - sourceFrame: this.tempRect, - destinationFrame: this.tempRect, - - // legacy variables - filterArea: new Float32Array(4), - filterClamp: new Float32Array(4), - }, true); - } - - /** - * Adds a new filter to the System. - * - * @param {PIXI.DisplayObject} target - The target of the filter to render. - * @param {PIXI.Filter[]} filters - The filters to apply. - */ - push(target, filters) - { - const renderer = this.renderer; - const filterStack = this.renderer.renderTexture.defaultFilterStack; - const state = this.statePool.pop() || new FilterState(); - - let resolution = filters[0].resolution; - let padding = filters[0].padding; - let autoFit = filters[0].autoFit; - let legacy = filters[0].legacy; - - for (let i = 1; i < filters.length; i++) - { - const filter = filters[i]; - - // lets use the lowest resolution.. - resolution = Math.min(resolution, filter.resolution); - // and the largest amount of padding! - padding = Math.max(padding, filter.padding); - // only auto fit if all filters are autofit - autoFit = autoFit || filter.autoFit; - - legacy = legacy || filter.legacy; - } - - filterStack.push(state); - - state.resolution = resolution; - - state.legacy = legacy; - - // round to whole number based on resolution - // TODO move that to the shader too? - state.sourceFrame = target.filterArea || target.getBounds(true); - - state.sourceFrame.pad(padding); - - if (autoFit) - { - state.sourceFrame.fit(this.renderer.renderTexture.destinationFrame); - } - - state.sourceFrame.round(resolution); - - state.renderTexture = this.getPotFilterTexture(state.sourceFrame.width, state.sourceFrame.height, resolution); - state.filters = filters; - - state.destinationFrame.width = state.renderTexture.width; - state.destinationFrame.height = state.renderTexture.height; - - state.renderTexture.filterFrame = state.sourceFrame; - renderer.renderTexture.bind(state.renderTexture, state.sourceFrame);// /, state.destinationFrame); - renderer.renderTexture.clear(); - } - - /** - * Pops off the filter and applies it. - * - */ - pop() - { - const renderer = this.renderer; - const filterStack = renderer.renderTexture.defaultFilterStack; - const state = filterStack.pop(); - const filters = state.filters; - - this.activeState = state; - - const globalUniforms = this.globalUniforms.uniforms; - - globalUniforms.sourceFrame = state.sourceFrame; - globalUniforms.destinationFrame = state.destinationFrame; - globalUniforms.resolution = state.resolution; - - // only update the rect if its legacy.. - if (state.legacy) - { - const filterArea = globalUniforms.filterArea; - const filterClamp = globalUniforms.filterClamp; - - filterArea[0] = state.destinationFrame.width; - filterArea[1] = state.destinationFrame.height; - filterArea[2] = state.sourceFrame.x; - filterArea[3] = state.sourceFrame.y; - - filterClamp[0] = 0.5 / state.resolution / state.destinationFrame.width; - filterClamp[1] = 0.5 / state.resolution / state.destinationFrame.height; - filterClamp[2] = (state.sourceFrame.width - 0.5) / state.resolution / state.destinationFrame.width; - filterClamp[3] = (state.sourceFrame.height - 0.5) / state.resolution / state.destinationFrame.height; - } - - this.globalUniforms.update(); - - const lastState = filterStack[filterStack.length - 1]; - - if (filters.length === 1) - { - filters[0].apply(this, state.renderTexture, lastState.renderTexture, false, state); - - this.returnFilterTexture(state.renderTexture); - } - else - { - let flip = state.renderTexture; - let flop = this.getPotFilterTexture( - flip.width, - flip.height, - state.resolution - ); - - flop.filterFrame = flip.filterFrame; - - let i = 0; - - for (i = 0; i < filters.length - 1; ++i) - { - filters[i].apply(this, flip, flop, true, state); - - const t = flip; - - flip = flop; - flop = t; - } - - filters[i].apply(this, flip, lastState.renderTexture, false, state); - - this.returnFilterTexture(flip); - this.returnFilterTexture(flop); - } - - this.statePool.push(state); - } - - /** - * Draws a filter. - * - * @param {PIXI.Filter} filter - The filter to draw. - * @param {PIXI.RenderTarget} input - The input render target. - * @param {PIXI.RenderTarget} output - The target to output to. - * @param {boolean} clear - Should the output be cleared before rendering to it - */ - applyFilter(filter, input, output, clear) - { - const renderer = this.renderer; - - renderer.renderTexture.bind(output, output ? output.filterFrame : null); - - if (clear) - { - // gl.disable(gl.SCISSOR_TEST); - renderer.renderTexture.clear(); - // gl.enable(gl.SCISSOR_TEST); - } - - // set the uniforms.. - filter.uniforms.uSampler = input; - filter.uniforms.filterGlobals = this.globalUniforms; - - // TODO make it so that the order of this does not matter.. - // because it does at the moment cos of global uniforms. - // they need to get resynced - - renderer.state.setState(filter.state); - renderer.shader.bind(filter); - - if (filter.legacy) - { - this.quadUv.map(input._frame, input.filterFrame); - - renderer.geometry.bind(this.quadUv); - renderer.geometry.draw(DRAW_MODES.TRIANGLES); - } - else - { - renderer.geometry.bind(this.quad); - renderer.geometry.draw(DRAW_MODES.TRIANGLE_STRIP); - } - } - - /** - * Calculates the mapped matrix. - * - * TODO playing around here.. this is temporary - (will end up in the shader) - * this returns a matrix that will normalise map filter cords in the filter to screen space - * - * @param {PIXI.Matrix} outputMatrix - the matrix to output to. - * @return {PIXI.Matrix} The mapped matrix. - */ - calculateScreenSpaceMatrix(outputMatrix) - { - const currentState = this.activeState; - - return filterTransforms.calculateScreenSpaceMatrix( - outputMatrix, - currentState.sourceFrame, - currentState.destinationFrame - ); - } - - /** - * This will map the filter coord so that a texture can be used based on the transform of a sprite - * - * @param {PIXI.Matrix} outputMatrix - The matrix to output to. - * @param {PIXI.Sprite} sprite - The sprite to map to. - * @return {PIXI.Matrix} The mapped matrix. - */ - calculateSpriteMatrix(outputMatrix, sprite) - { - const currentState = this.activeState; - - return filterTransforms.calculateSpriteMatrix( - outputMatrix, - currentState.sourceFrame, - currentState.destinationFrame, - sprite - ); - } - - /** - * Destroys this Filter System. - * - * @param {boolean} [contextLost=false] context was lost, do not free shaders - * - */ - destroy(contextLost = false) - { - if (!contextLost) - { - this.emptyPool(); - } - else - { - this.texturePool = {}; - } - } - - /** - * Gets a Power-of-Two render texture. - * - * TODO move to a seperate class could be on renderer? - * also - could cause issue with multiple contexts? - * - * @private - * @param {WebGLRenderingContext} gl - The webgl rendering context - * @param {number} minWidth - The minimum width of the render target. - * @param {number} minHeight - The minimum height of the render target. - * @param {number} resolution - The resolution of the render target. - * @return {PIXI.RenderTarget} The new render target. - */ - getPotFilterTexture(minWidth, minHeight, resolution) - { - minWidth = bitTwiddle.nextPow2(minWidth); - minHeight = bitTwiddle.nextPow2(minHeight); - resolution = resolution || 1; - - const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); - - if (!this.texturePool[key]) - { - this.texturePool[key] = []; - } - - let renderTexture = this.texturePool[key].pop(); - - if (!renderTexture) - { - // temporary bypass cache.. - // internally - this will cause a texture to be bound.. - renderTexture = RenderTexture.create({ - width: minWidth, - height: minHeight, - resolution, - }); - } - - return renderTexture; - } - - /** - * Gets extra render texture to use inside current filter - * - * @param {number} resolution resolution of the renderTexture - * @returns {PIXI.RenderTexture} - */ - getFilterTexture(resolution) - { - const rt = this.activeState.renderTexture; - - const filterTexture = this.getPotFilterTexture(rt.width, rt.height, resolution || rt.baseTexture.resolution); - - filterTexture.filterFrame = rt.filterFrame; - - return filterTexture; - } - - /** - * Frees a render texture back into the pool. - * - * @param {PIXI.RenderTarget} renderTexture - The renderTarget to free - */ - returnFilterTexture(renderTexture) - { - renderTexture.filterFrame = null; - - const base = renderTexture.baseTexture; - - const minWidth = base.width; - const minHeight = base.height; - - const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); - - this.texturePool[key].push(renderTexture); - } - - /** - * Empties the texture pool. - * - */ - emptyPool() - { - for (const i in this.texturePool) - { - const textures = this.texturePool[i]; - - if (textures) - { - for (let j = 0; j < textures.length; j++) - { - textures[j].destroy(true); - } - } - } - - this.texturePool = {}; - } -} diff --git a/packages/core/src/renderers/systems/geometry/GLBuffer.js b/packages/core/src/renderers/systems/geometry/GLBuffer.js deleted file mode 100644 index 0477e8a..0000000 --- a/packages/core/src/renderers/systems/geometry/GLBuffer.js +++ /dev/null @@ -1,9 +0,0 @@ -export default class GLBuffer -{ - constructor(buffer) - { - this.buffer = buffer; - this.updateID = -1; - this.byteLength = -1; - } -} diff --git a/packages/core/src/renderers/systems/geometry/GeometrySystem.js b/packages/core/src/renderers/systems/geometry/GeometrySystem.js deleted file mode 100644 index 26c8e9e..0000000 --- a/packages/core/src/renderers/systems/geometry/GeometrySystem.js +++ /dev/null @@ -1,388 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; -import GLBuffer from './GLBuffer'; - -const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class GeometrySystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this._activeGeometry = null; - this._activeVao = null; - - this.hasVao = true; - this.hasInstance = true; - } - - /** - * Sets up the renderer context and necessary buffers. - * - * @private - */ - contextChange() - { - const gl = this.gl = this.renderer.gl; - - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - - // webgl2 - if (!gl.createVertexArray) - { - // webgl 1! - let nativeVaoExtension = this.renderer.context.extensions.vertexArrayObject; - - if (this.renderer.legacy) - { - nativeVaoExtension = null; - } - - if (nativeVaoExtension) - { - gl.createVertexArray = () => - nativeVaoExtension.createVertexArrayOES(); - - gl.bindVertexArray = (vao) => - nativeVaoExtension.bindVertexArrayOES(vao); - - gl.deleteVertexArray = (vao) => - nativeVaoExtension.deleteVertexArrayOES(vao); - } - else - { - this.hasVao = false; - gl.createVertexArray = () => - { - // empty - }; - - gl.bindVertexArray = () => - { - // empty - }; - - gl.deleteVertexArray = () => - { - // empty - }; - } - } - - if (!gl.vertexAttribDivisor) - { - const instanceExt = gl.getExtension('ANGLE_instanced_arrays'); - - if (instanceExt) - { - gl.vertexAttribDivisor = (a, b) => - instanceExt.vertexAttribDivisorANGLE(a, b); - - gl.drawElementsInstanced = (a, b, c, d, e) => - instanceExt.drawElementsInstancedANGLE(a, b, c, d, e); - - gl.drawArraysInstanced = (a, b, c, d) => - instanceExt.drawArraysInstancedANGLE(a, b, c, d); - } - else - { - this.hasInstance = false; - } - } - } - - /** - * Binds geometry so that is can be drawn. Creating a Vao if required - * @private - * @param {PIXI.Geometry} geometry instance of geometry to bind - */ - bind(geometry, shader) - { - shader = shader || this.renderer.shader.shader; - - const gl = this.gl; - - // not sure the best way to address this.. - // currently different shaders require different VAOs for the same geometry - // Still mulling over the best way to solve this one.. - // will likely need to modify the shader attribute locations at run time! - let vaos = geometry.glVertexArrayObjects[this.CONTEXT_UID]; - - if (!vaos) - { - geometry.glVertexArrayObjects[this.CONTEXT_UID] = vaos = {}; - } - - const vao = vaos[shader.program.id] || this.initGeometryVao(geometry, shader.program); - - this._activeGeometry = geometry; - - if (this._activeVao !== vao) - { - this._activeVao = vao; - - if (this.hasVao) - { - gl.bindVertexArray(vao); - } - else - { - this.activateVao(geometry, shader.program); - } - } - - // TODO - optimise later! - // don't need to loop through if nothing changed! - // maybe look to add an 'autoupdate' to geometry? - this.updateBuffers(); - } - - reset() - { - this.unbind(); - } - - updateBuffers() - { - const geometry = this._activeGeometry; - const gl = this.gl; - - for (let i = 0; i < geometry.buffers.length; i++) - { - const buffer = geometry.buffers[i]; - - const glBuffer = buffer._glBuffers[this.CONTEXT_UID]; - - if (buffer._updateID !== glBuffer.updateID) - { - glBuffer.updateID = buffer._updateID; - - // TODO can cache this on buffer! maybe added a getter / setter? - const type = buffer.index ? gl.ELEMENT_ARRAY_BUFFER : gl.ARRAY_BUFFER; - const drawType = buffer.static ? gl.STATIC_DRAW : gl.DYNAMIC_DRAW; - - gl.bindBuffer(type, glBuffer.buffer); - - if (glBuffer.byteLength >= buffer.data.byteLength) - { - // offset is always zero for now! - gl.bufferSubData(type, 0, buffer.data); - } - else - { - gl.bufferData(type, buffer.data, drawType); - } - } - } - } - - checkCompatability(geometry, program) - { - // geometry must have at least all the attributes that the shader requires. - const geometryAttributes = geometry.attributes; - const shaderAttributes = program.attributeData; - - for (const j in shaderAttributes) - { - if (!geometryAttributes[j]) - { - throw new Error(`shader and geometry incompatible, geometry missing the "${j}" attribute`); - } - } - } - - /** - * Creates a Vao with the same structure as the geometry and stores it on the geometry. - * @private - * @param {PIXI.Geometry} geometry instance of geometry to to generate Vao for - */ - initGeometryVao(geometry, program) - { - this.checkCompatability(geometry, program); - - const gl = this.gl; - const CONTEXT_UID = this.CONTEXT_UID; - const buffers = geometry.buffers; - const attributes = geometry.attributes; - - const tempStride = {}; - const tempStart = {}; - - for (const j in buffers) - { - tempStride[j] = 0; - tempStart[j] = 0; - } - - for (const j in attributes) - { - if (!attributes[j].size && program.attributeData[j]) - { - attributes[j].size = program.attributeData[j].size; - } - - tempStride[attributes[j].buffer] += attributes[j].size * byteSizeMap[attributes[j].type]; - } - - for (const j in attributes) - { - const attribute = attributes[j]; - const attribSize = attribute.size; - - if (attribute.stride === undefined) - { - if (tempStride[attribute.buffer] === attribSize * byteSizeMap[attribute.type]) - { - attribute.stride = 0; - } - else - { - attribute.stride = tempStride[attribute.buffer]; - } - } - - if (attribute.start === undefined) - { - attribute.start = tempStart[attribute.buffer]; - - tempStart[attribute.buffer] += attribSize * byteSizeMap[attribute.type]; - } - } - - // first update - and create the buffers! - // only create a gl buffer if it actually gets - for (let i = 0; i < buffers.length; i++) - { - const buffer = buffers[i]; - - if (!buffer._glBuffers[CONTEXT_UID]) - { - buffer._glBuffers[CONTEXT_UID] = new GLBuffer(gl.createBuffer()); - } - } - - // TODO - maybe make this a data object? - // lets wait to see if we need to first! - const vao = gl.createVertexArray(); - - gl.bindVertexArray(vao); - - this.activateVao(geometry, program); - - gl.bindVertexArray(null); - - // add it to the cache! - geometry.glVertexArrayObjects[this.CONTEXT_UID][program.id] = vao; - - return vao; - } - - activateVao(geometry, program) - { - const gl = this.gl; - const CONTEXT_UID = this.CONTEXT_UID; - const buffers = geometry.buffers; - const attributes = geometry.attributes; - - if (geometry.indexBuffer) - { - // first update the index buffer if we have one.. - gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, geometry.indexBuffer._glBuffers[CONTEXT_UID].buffer); - } - - let lastBuffer = null; - - // add a new one! - for (const j in attributes) - { - const attribute = attributes[j]; - const buffer = buffers[attribute.buffer]; - const glBuffer = buffer._glBuffers[CONTEXT_UID]; - - if (program.attributeData[j]) - { - if (lastBuffer !== glBuffer) - { - gl.bindBuffer(gl.ARRAY_BUFFER, glBuffer.buffer); - - lastBuffer = glBuffer; - } - - const location = program.attributeData[j].location; - - // TODO introduce state again - // we can optimise this for older devices that have no VAOs - gl.enableVertexAttribArray(location); - - gl.vertexAttribPointer(location, - attribute.size, - attribute.type || gl.FLOAT, - attribute.normalized, - attribute.stride, - attribute.start); - - if (attribute.instance) - { - // TODO calculate instance count based of this... - if (this.hasInstance) - { - gl.vertexAttribDivisor(location, 1); - } - else - { - throw new Error('geometry error, GPU Instancing is not supported on this device'); - } - } - } - } - } - - draw(type, size, start, instanceCount) - { - const gl = this.gl; - const geometry = this._activeGeometry; - - // TODO.. this should not change so maybe cache the function? - - if (geometry.indexBuffer) - { - if (geometry.instanced) - { - /* eslint-disable max-len */ - gl.drawElementsInstanced(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2, instanceCount || 1); - /* eslint-enable max-len */ - } - else - { - gl.drawElements(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2); - } - } - else - if (geometry.instanced) - { - // TODO need a better way to calculate size.. - gl.drawArraysInstanced(type, start, size || geometry.getSize(), instanceCount || 1); - } - else - { - gl.drawArrays(type, start, size || geometry.getSize()); - } - - return this; - } - - unbind() - { - this.gl.bindVertexArray(null); - this._activeVao = null; - this._activeGeometry = null; - } -} diff --git a/packages/core/src/renderers/systems/geometry/setVertexAttribArrays.js b/packages/core/src/renderers/systems/geometry/setVertexAttribArrays.js deleted file mode 100644 index 23b6959..0000000 --- a/packages/core/src/renderers/systems/geometry/setVertexAttribArrays.js +++ /dev/null @@ -1,54 +0,0 @@ -// var GL_MAP = {}; - -/** - * @param gl {WebGLRenderingContext} The current WebGL context - * @param attribs {*} - * @param state {*} - */ -export default function setVertexAttribArrays(gl, attribs, state) -{ - let i; - - if (state) - { - const tempAttribState = state.tempAttribState; - const attribState = state.attribState; - - for (i = 0; i < tempAttribState.length; i++) - { - tempAttribState[i] = false; - } - - // set the new attribs - for (i = 0; i < attribs.length; i++) - { - tempAttribState[attribs[i].attribute.location] = true; - } - - for (i = 0; i < attribState.length; i++) - { - if (attribState[i] !== tempAttribState[i]) - { - attribState[i] = tempAttribState[i]; - - if (state.attribState[i]) - { - gl.enableVertexAttribArray(i); - } - else - { - gl.disableVertexAttribArray(i); - } - } - } - } - else - { - for (i = 0; i < attribs.length; i++) - { - const attrib = attribs[i]; - - gl.enableVertexAttribArray(attrib.attribute.location); - } - } -} diff --git a/packages/core/src/renderers/systems/index.js b/packages/core/src/renderers/systems/index.js deleted file mode 100644 index c843f03..0000000 --- a/packages/core/src/renderers/systems/index.js +++ /dev/null @@ -1,6 +0,0 @@ -/** - * Systems are individual components to the Renderer pipeline. - * @namespace PIXI.systems - */ -export { default as WebGLSystem } from './WebGLSystem'; -export { default as FilterSystem } from './filter/FilterSystem'; diff --git a/packages/core/src/renderers/systems/shader/GLProgram.js b/packages/core/src/renderers/systems/shader/GLProgram.js deleted file mode 100644 index 92980ca..0000000 --- a/packages/core/src/renderers/systems/shader/GLProgram.js +++ /dev/null @@ -1,43 +0,0 @@ -/** - * Helper class to create a webGL Program - * - * @class - * @private - */ -export default class GLProgram -{ - constructor(program, uniformData) - { - /** - * The shader program - * - * @member {WebGLProgram} - */ - this.program = program; - - /** - * holds the uniform data which contains uniform locations - * and current uniform values used for caching and preventing undeed GPU commands - * @type {Object} - */ - this.uniformData = uniformData; - - /** - * uniformGroups holds the various upload functions for the shader. Each uniform group - * and program have a unique upload function generated. - * @type {Object} - */ - this.uniformGroups = {}; - } - - /** - * Destroys this program - * TODO - */ - destroy() - { - this.uniformData = null; - this.uniformGroups = null; - this.program = null; - } -} diff --git a/packages/core/src/renderers/systems/shader/ShaderSystem.js b/packages/core/src/renderers/systems/shader/ShaderSystem.js deleted file mode 100644 index 6cf57b6..0000000 --- a/packages/core/src/renderers/systems/shader/ShaderSystem.js +++ /dev/null @@ -1,170 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; -import GLProgram from './GLProgram'; -import generateUniformsSync from '../../../shader/generateUniformsSync'; -import defaultValue from '../../../shader/defaultValue'; -import compileProgram from './shader/compileProgram'; - -let UID = 0; - -/** - * Helper class to create a webGL Texture - * - * @class - * @memberof PIXI - */ -export default class ShaderSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - A reference to the current renderer - */ - constructor(renderer) - { - super(renderer); - - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = null; - - this.shader = null; - this.program = null; - - this.id = UID++; - } - - contextChange(gl) - { - this.gl = gl; - } - - /** - * Changes the current shader to the one given in parameter - * - * @param {PIXI.Shader} shader - the new shader - * @param {boolean} dontSync - false if the shader should automatically sync its uniforms. - * @returns {PIXI.glCore.glProgram} the glProgram that belongs to the shader. - */ - bind(shader, dontSync) - { - shader.uniforms.globals = this.renderer.globalUniforms; - - const program = shader.program; - const glProgram = program.glPrograms[this.renderer.CONTEXT_UID] || this.generateShader(shader); - - this.shader = shader; - - // TODO - some current pixi plugins bypass this.. so it not safe to use yet.. - if (this.program !== program) - { - this.program = program; - this.gl.useProgram(glProgram.program); - } - - if (!dontSync) - { - this.syncUniformGroup(shader.uniformGroup); - } - - return glProgram; - } - - /** - * Uploads the uniforms values to the currently bound shader. - * - * @param {object} uniforms - the uniforms valiues that be applied to the current shader - */ - setUniforms(uniforms) - { - const shader = this.shader.program; - const glProgram = shader.glPrograms[this.renderer.CONTEXT_UID]; - - shader.syncUniforms(glProgram.uniformData, uniforms, this.renderer); - } - - syncUniformGroup(group) - { - const glProgram = this.getglProgram(); - - if (!group.static || group.dirtyId !== glProgram.uniformGroups[group.id]) - { - glProgram.uniformGroups[group.id] = group.dirtyId; - const syncFunc = group.syncUniforms[this.shader.program.id] || this.createSyncGroups(group); - - syncFunc(glProgram.uniformData, group.uniforms, this.renderer); - } - } - - createSyncGroups(group) - { - group.syncUniforms[this.shader.program.id] = generateUniformsSync(group, this.shader.program.uniformData); - - return group.syncUniforms[this.shader.program.id]; - } - - /** - * Returns the underlying GLShade rof the currently bound shader. - * This can be handy for when you to have a little more control over the setting of your uniforms. - * - * @return {PIXI.glCore.Shader} the glProgram for the currently bound Shader for this context - */ - getglProgram() - { - if (this.shader) - { - return this.shader.program.glPrograms[this.renderer.CONTEXT_UID]; - } - - return null; - } - - /** - * Generates a glProgram verion of the Shader provided. - * - * @private - * @param {PIXI.Shader} shader the shader that the glProgram will be based on. - * @return {PIXI.glCore.glProgram} A shiney new glProgram - */ - generateShader(shader) - { - const gl = this.gl; - - const program = shader.program; - - const attribMap = {}; - - for (const i in program.attributeData) - { - attribMap[i] = program.attributeData[i].location; - } - - const shaderProgram = compileProgram(gl, program.vertexSrc, program.fragmentSrc, attribMap); - const uniformData = {}; - - for (const i in program.uniformData) - { - const data = program.uniformData[i]; - - uniformData[i] = { - location: gl.getUniformLocation(shaderProgram, i), - value: defaultValue(data.type, data.size), - }; - } - - const glProgram = new GLProgram(shaderProgram, uniformData); - - program.glPrograms[this.renderer.CONTEXT_UID] = glProgram; - - return glProgram; - } - - /** - * Destroys this System and removes all its textures - */ - destroy() - { - // TODO implement destroy method for ShaderSystem - this.destroyed = true; - } -} diff --git a/packages/core/src/renderers/systems/shader/shader/compileProgram.js b/packages/core/src/renderers/systems/shader/shader/compileProgram.js deleted file mode 100644 index 8c6ccb9..0000000 --- a/packages/core/src/renderers/systems/shader/shader/compileProgram.js +++ /dev/null @@ -1,78 +0,0 @@ -/** - * @method compileProgram - * @memberof PIXI.glCore.shader - * @param gl {WebGLRenderingContext} The current WebGL context {WebGLProgram} - * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. - * @param fragmentSrc {string|string[]} The fragment shader source as an array of strings. - * @param attributeLocations {Object} An attribute location map that lets you manually set the attribute locations - * @return {WebGLProgram} the shader program - */ -export default function compileProgram(gl, vertexSrc, fragmentSrc, attributeLocations) -{ - const glVertShader = compileShader(gl, gl.VERTEX_SHADER, vertexSrc); - const glFragShader = compileShader(gl, gl.FRAGMENT_SHADER, fragmentSrc); - - let program = gl.createProgram(); - - gl.attachShader(program, glVertShader); - gl.attachShader(program, glFragShader); - - // optionally, set the attributes manually for the program rather than letting WebGL decide.. - if (attributeLocations) - { - for (const i in attributeLocations) - { - gl.bindAttribLocation(program, attributeLocations[i], i); - } - } - - gl.linkProgram(program); - - // if linking fails, then log and cleanup - if (!gl.getProgramParameter(program, gl.LINK_STATUS)) - { - console.error('Pixi.js Error: Could not initialize shader.'); - console.error('gl.VALIDATE_STATUS', gl.getProgramParameter(program, gl.VALIDATE_STATUS)); - console.error('gl.getError()', gl.getError()); - - // if there is a program info log, log it - if (gl.getProgramInfoLog(program) !== '') - { - console.warn('Pixi.js Warning: gl.getProgramInfoLog()', gl.getProgramInfoLog(program)); - } - - gl.deleteProgram(program); - program = null; - } - - // clean up some shaders - gl.deleteShader(glVertShader); - gl.deleteShader(glFragShader); - - return program; -} - -/** - * @private - * @param gl {WebGLRenderingContext} The current WebGL context {WebGLProgram} - * @param type {Number} the type, can be either VERTEX_SHADER or FRAGMENT_SHADER - * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. - * @return {WebGLShader} the shader - */ -function compileShader(gl, type, src) -{ - const shader = gl.createShader(type); - - gl.shaderSource(shader, src); - gl.compileShader(shader); - - if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) - { - console.warn(src); - console.error(gl.getShaderInfoLog(shader)); - - return null; - } - - return shader; -} diff --git a/packages/core/src/renderers/systems/shader/shader/defaultValue.js b/packages/core/src/renderers/systems/shader/shader/defaultValue.js deleted file mode 100644 index 97290bd..0000000 --- a/packages/core/src/renderers/systems/shader/shader/defaultValue.js +++ /dev/null @@ -1,79 +0,0 @@ -/** - * @method defaultValue - * @memberof PIXI.glCore.shader - * @param type {String} Type of value - * @param size {Number} - */ -export default function defaultValue(type, size) -{ - switch (type) - { - case 'float': - return 0; - - case 'vec2': - return new Float32Array(2 * size); - - case 'vec3': - return new Float32Array(3 * size); - - case 'vec4': - return new Float32Array(4 * size); - - case 'int': - case 'sampler2D': - case 'sampler2DArray': - return 0; - - case 'ivec2': - return new Int32Array(2 * size); - - case 'ivec3': - return new Int32Array(3 * size); - - case 'ivec4': - return new Int32Array(4 * size); - - case 'bool': - return false; - - case 'bvec2': - - return booleanArray(2 * size); - - case 'bvec3': - return booleanArray(3 * size); - - case 'bvec4': - return booleanArray(4 * size); - - case 'mat2': - return new Float32Array([1, 0, - 0, 1]); - - case 'mat3': - return new Float32Array([1, 0, 0, - 0, 1, 0, - 0, 0, 1]); - - case 'mat4': - return new Float32Array([1, 0, 0, 0, - 0, 1, 0, 0, - 0, 0, 1, 0, - 0, 0, 0, 1]); - } - - return null; -} - -function booleanArray(size) -{ - const array = new Array(size); - - for (let i = 0; i < array.length; i++) - { - array[i] = false; - } - - return array; -} diff --git a/packages/core/src/renderers/systems/shader/shader/index.js b/packages/core/src/renderers/systems/shader/shader/index.js deleted file mode 100644 index ba7a317..0000000 --- a/packages/core/src/renderers/systems/shader/shader/index.js +++ /dev/null @@ -1,8 +0,0 @@ -/** - * @namespace PIXI.glCore.shader - */ -export { default as compileProgram } from './compileProgram'; -export { default as defaultValue } from './defaultValue'; -export { default as setPrecision } from './setPrecision'; -export { default as mapSize } from './mapSize'; -export { default as mapType } from './mapType'; diff --git a/packages/core/src/renderers/systems/shader/shader/mapSize.js b/packages/core/src/renderers/systems/shader/shader/mapSize.js deleted file mode 100644 index b681554..0000000 --- a/packages/core/src/renderers/systems/shader/shader/mapSize.js +++ /dev/null @@ -1,33 +0,0 @@ -const GLSL_TO_SIZE = { - float: 1, - vec2: 2, - vec3: 3, - vec4: 4, - - int: 1, - ivec2: 2, - ivec3: 3, - ivec4: 4, - - bool: 1, - bvec2: 2, - bvec3: 3, - bvec4: 4, - - mat2: 4, - mat3: 9, - mat4: 16, - - sampler2D: 1, -}; - -/** - * @method mapSize - * @memberof PIXI.glCore.shader - * @param type {String} - * @return {Number} - */ -export default function mapSize(type) -{ - return GLSL_TO_SIZE[type]; -} diff --git a/packages/core/src/renderers/systems/shader/shader/mapType.js b/packages/core/src/renderers/systems/shader/shader/mapType.js deleted file mode 100644 index d288b7c..0000000 --- a/packages/core/src/renderers/systems/shader/shader/mapType.js +++ /dev/null @@ -1,45 +0,0 @@ -let GL_TABLE = null; - -const GL_TO_GLSL_TYPES = { - FLOAT: 'float', - FLOAT_VEC2: 'vec2', - FLOAT_VEC3: 'vec3', - FLOAT_VEC4: 'vec4', - - INT: 'int', - INT_VEC2: 'ivec2', - INT_VEC3: 'ivec3', - INT_VEC4: 'ivec4', - - BOOL: 'bool', - BOOL_VEC2: 'bvec2', - BOOL_VEC3: 'bvec3', - BOOL_VEC4: 'bvec4', - - FLOAT_MAT2: 'mat2', - FLOAT_MAT3: 'mat3', - FLOAT_MAT4: 'mat4', - - SAMPLER_2D: 'sampler2D', - SAMPLER_CUBE: 'samplerCube', - SAMPLER_2D_ARRAY: 'sampler2DArray', -}; - -export default function mapType(gl, type) -{ - if (!GL_TABLE) - { - const typeNames = Object.keys(GL_TO_GLSL_TYPES); - - GL_TABLE = {}; - - for (let i = 0; i < typeNames.length; ++i) - { - const tn = typeNames[i]; - - GL_TABLE[gl[tn]] = GL_TO_GLSL_TYPES[tn]; - } - } - - return GL_TABLE[type]; -} diff --git a/packages/core/src/renderers/systems/shader/shader/setPrecision.js b/packages/core/src/renderers/systems/shader/shader/setPrecision.js deleted file mode 100644 index e2c1f34..0000000 --- a/packages/core/src/renderers/systems/shader/shader/setPrecision.js +++ /dev/null @@ -1,18 +0,0 @@ -/** - * Sets the float precision on the shader. If the precision is already present this function will do nothing - * @param {string} src the shader source - * @param {string} precision The float precision of the shader. Options are 'lowp', 'mediump' or 'highp'. - * - * @return {string} modified shader source - */ -export default function setPrecision(src, precision) -{ - src = src.trim(); - - if (src.substring(0, 9) !== 'precision' && src.substring(0, 1) !== '#') - { - return `precision ${precision} float;\n${src}`; - } - - return src; -} diff --git a/packages/core/src/renderers/systems/textures/GLTexture.js b/packages/core/src/renderers/systems/textures/GLTexture.js deleted file mode 100644 index c3fb8dc..0000000 --- a/packages/core/src/renderers/systems/textures/GLTexture.js +++ /dev/null @@ -1,33 +0,0 @@ -export default class GLTexture -{ - constructor(texture) - { - /** - * The WebGL texture - * - * @member {WebGLTexture} - */ - this.texture = texture; - - this.width = -1; - this.height = -1; - - /** - * Texture contents dirty flag - * @member {number} - */ - this.dirtyId = -1; - - /** - * Texture style dirty flag - * @type {number} - */ - this.dirtyStyleId = -1; - - /** - * Whether mip levels has to be generated - * @type {boolean} - */ - this.mipmap = false; - } -} diff --git a/packages/core/src/renderers/systems/textures/TextureGCSystem.js b/packages/core/src/renderers/systems/textures/TextureGCSystem.js deleted file mode 100644 index b089938..0000000 --- a/packages/core/src/renderers/systems/textures/TextureGCSystem.js +++ /dev/null @@ -1,110 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; -import { GC_MODES } from '@pixi/constants'; -import { settings } from '@pixi/settings'; - -/** - * TextureGarbageCollector. This class manages the GPU and ensures that it does not get clogged - * up with textures that are no longer being used. - * - * @class - * @memberof PIXI - */ -export default class TextureGCSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this.count = 0; - this.checkCount = 0; - this.maxIdle = settings.GC_MAX_IDLE; - this.checkCountMax = settings.GC_MAX_CHECK_COUNT; - this.mode = settings.GC_MODE; - } - - /** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ - postrender() - { - this.count++; - - if (this.mode === GC_MODES.MANUAL) - { - return; - } - - this.checkCount++; - - if (this.checkCount > this.checkCountMax) - { - this.checkCount = 0; - - this.run(); - } - } - - /** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ - run() - { - const tm = this.renderer.texture; - const managedTextures = tm.managedTextures; - let wasRemoved = false; - - for (let i = 0; i < managedTextures.length; i++) - { - const texture = managedTextures[i]; - - // only supports non generated textures at the moment! - if (!texture.frameBuffer && this.count - texture.touched > this.maxIdle) - { - tm.destroyTexture(texture, true); - managedTextures[i] = null; - wasRemoved = true; - } - } - - if (wasRemoved) - { - let j = 0; - - for (let i = 0; i < managedTextures.length; i++) - { - if (managedTextures[i] !== null) - { - managedTextures[j++] = managedTextures[i]; - } - } - - managedTextures.length = j; - } - } - - /** - * Removes all the textures within the specified displayObject and its children from the GPU - * - * @param {PIXI.DisplayObject} displayObject - the displayObject to remove the textures from. - */ - unload(displayObject) - { - const tm = this.renderer.textureSystem; - - // only destroy non generated textures - if (displayObject._texture && displayObject._texture._glRenderTargets) - { - tm.destroyTexture(displayObject._texture); - } - - for (let i = displayObject.children.length - 1; i >= 0; i--) - { - this.unload(displayObject.children[i]); - } - } -} diff --git a/packages/core/src/renderers/systems/textures/TextureSystem.js b/packages/core/src/renderers/systems/textures/TextureSystem.js deleted file mode 100644 index ccc5de9..0000000 --- a/packages/core/src/renderers/systems/textures/TextureSystem.js +++ /dev/null @@ -1,281 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; -import GLTexture from './GLTexture'; -import { removeItems } from '@pixi/utils'; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class TextureSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - // TODO set to max textures... - this.boundTextures = [ - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - ]; - - this.currentLocation = -1; - - this.managedTextures = []; - } - - /** - * Sets up the renderer context and necessary buffers. - * - * @private - */ - contextChange() - { - const gl = this.gl = this.renderer.gl; - - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - - // TODO move this.. to a nice make empty textures class.. - this.emptyTextures = {}; - - const emptyTexture2D = new GLTexture(gl.createTexture()); - - gl.bindTexture(gl.TEXTURE_2D, emptyTexture2D.texture); - gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array(4)); - - this.emptyTextures[gl.TEXTURE_2D] = emptyTexture2D; - this.emptyTextures[gl.TEXTURE_CUBE_MAP] = new GLTexture(gl.createTexture()); - - gl.bindTexture(gl.TEXTURE_CUBE_MAP, this.emptyTextures[gl.TEXTURE_CUBE_MAP].texture); - - let i; - - for (i = 0; i < 6; i++) - { - gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, null); - } - - gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_MAG_FILTER, gl.LINEAR); - gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_MIN_FILTER, gl.LINEAR); - - for (i = 0; i < this.boundTextures.length; i++) - { - this.bind(null, i); - } - } - - bind(texture, location) - { - const gl = this.gl; - - location = location || 0; - - if (this.currentLocation !== location) - { - this.currentLocation = location; - gl.activeTexture(gl.TEXTURE0 + location); - } - - if (texture) - { - texture = texture.baseTexture || texture; - - if (texture.valid) - { - texture.touched = this.renderer.textureGC.count; - - const glTexture = texture._glTextures[this.CONTEXT_UID] || this.initTexture(texture); - - gl.bindTexture(texture.target, glTexture.texture); - - if (glTexture.dirtyId !== texture.dirtyId) - { - this.updateTexture(texture); - } - - this.boundTextures[location] = texture; - } - } - else - { - gl.bindTexture(gl.TEXTURE_2D, this.emptyTextures[gl.TEXTURE_2D].texture); - this.boundTextures[location] = null; - } - } - - unbind(texture) - { - const gl = this.gl; - - for (let i = 0; i < this.boundTextures.length; i++) - { - if (this.boundTextures[i] === texture) - { - if (this.currentLocation !== i) - { - gl.activeTexture(gl.TEXTURE0 + i); - this.currentLocation = i; - } - - gl.bindTexture(gl.TEXTURE_2D, this.emptyTextures[texture.target].texture); - this.boundTextures[i] = null; - } - } - } - - initTexture(texture) - { - const glTexture = new GLTexture(this.gl.createTexture()); - - // guarentee an update.. - glTexture.dirtyId = -1; - - texture._glTextures[this.CONTEXT_UID] = glTexture; - - this.managedTextures.push(texture); - texture.on('dispose', this.destroyTexture, this); - - return glTexture; - } - - updateTexture(texture) - { - const glTexture = texture._glTextures[this.CONTEXT_UID]; - const renderer = this.renderer; - - if (texture.resource && texture.resource.upload(renderer, texture, glTexture)) - { - // texture is uploaded, dont do anything! - } - else - { - // default, renderTexture-like logic - const width = texture.realWidth; - const height = texture.realHeight; - const gl = renderer.gl; - - if (glTexture.width !== width - || glTexture.height !== height - || glTexture.dirtyId < 0) - { - glTexture.width = width; - glTexture.height = height; - - gl.texImage2D(texture.target, 0, - texture.format, - width, - height, - 0, - texture.format, - texture.type, - null); - } - } - - // lets only update what changes.. - if (texture.dirtyStyleId !== glTexture.dirtyStyleId) - { - this.updateTextureStyle(texture); - } - glTexture.dirtyId = texture.dirtyId; - } - - /** - * Deletes the texture from WebGL - * - * @param {PIXI.BaseTexture|PIXI.Texture} texture - the texture to destroy - * @param {boolean} [skipRemove=false] - Whether to skip removing the texture from the TextureManager. - */ - destroyTexture(texture, skipRemove) - { - const gl = this.gl; - - texture = texture.baseTexture || texture; - - if (texture._glTextures[this.renderer.CONTEXT_UID]) - { - this.unbind(texture); - - gl.deleteTexture(texture._glTextures[this.renderer.CONTEXT_UID].texture); - texture.off('dispose', this.destroyTexture, this); - - delete texture._glTextures[this.renderer.CONTEXT_UID]; - - if (!skipRemove) - { - const i = this.managedTextures.indexOf(texture); - - if (i !== -1) - { - removeItems(this.managedTextures, i, 1); - } - } - } - } - - updateTextureStyle(texture) - { - const glTexture = texture._glTextures[this.CONTEXT_UID]; - - glTexture.mipmap = texture.mipmap && texture.isPowerOfTwo; - if (!glTexture) - { - return; - } - - if (texture.resource && texture.resource.style(this.renderer, texture, glTexture)) - { - // style is set, dont do anything! - } - else - { - this.setStyle(texture, glTexture); - } - - glTexture.dirtyStyleId = texture.dirtyStyleId; - } - - setStyle(texture, glTexture) - { - const gl = this.gl; - - if (glTexture.mipmap) - { - gl.generateMipmap(texture.target); - } - - gl.texParameteri(texture.target, gl.TEXTURE_WRAP_S, texture.wrapMode); - gl.texParameteri(texture.target, gl.TEXTURE_WRAP_T, texture.wrapMode); - - if (glTexture.mipmap) - { - /* eslint-disable max-len */ - gl.texParameteri(texture.target, gl.TEXTURE_MIN_FILTER, texture.scaleMode ? gl.LINEAR_MIPMAP_LINEAR : gl.NEAREST_MIPMAP_NEAREST); - /* eslint-disable max-len */ - } - else - { - gl.texParameteri(texture.target, gl.TEXTURE_MIN_FILTER, texture.scaleMode ? gl.LINEAR : gl.NEAREST); - } - - gl.texParameteri(texture.target, gl.TEXTURE_MAG_FILTER, texture.scaleMode ? gl.LINEAR : gl.NEAREST); - } -} diff --git a/packages/core/src/renderers/utils/ObjectRenderer.js b/packages/core/src/renderers/utils/ObjectRenderer.js deleted file mode 100644 index f67a273..0000000 --- a/packages/core/src/renderers/utils/ObjectRenderer.js +++ /dev/null @@ -1,48 +0,0 @@ -import WebGLSystem from '../systems/WebGLSystem'; - -/** - * Base for a common object renderer that can be used as a system renderer plugin. - * - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class ObjectRenderer extends WebGLSystem -{ - /** - * Starts the renderer and sets the shader - * - */ - start() - { - // set the shader.. - } - - /** - * Stops the renderer - * - */ - stop() - { - this.flush(); - } - - /** - * Stub method for rendering content and emptying the current batch. - * - */ - flush() - { - // flush! - } - - /** - * Renders an object - * - * @param {PIXI.DisplayObject} object - The object to render. - */ - render(object) // eslint-disable-line no-unused-vars - { - // render the object - } -} diff --git a/packages/core/src/renderers/utils/Quad.js b/packages/core/src/renderers/utils/Quad.js deleted file mode 100644 index 5c1517b..0000000 --- a/packages/core/src/renderers/utils/Quad.js +++ /dev/null @@ -1,26 +0,0 @@ -import Geometry from '../../geometry/Geometry'; - -/** - * Helper class to create a quad - * - * @class - * @memberof PIXI - */ -export default class Quad extends Geometry -{ - /** - * just a quad - */ - constructor() - { - super(); - - this.addAttribute('aVertexPosition', [ - 0, 0, - 1, 0, - 1, 1, - 0, 1, - ]) - .addIndex([0, 1, 3, 2]); - } -} diff --git a/packages/core/src/renderers/utils/QuadUv.js b/packages/core/src/renderers/utils/QuadUv.js deleted file mode 100644 index 97bda89..0000000 --- a/packages/core/src/renderers/utils/QuadUv.js +++ /dev/null @@ -1,103 +0,0 @@ -import Geometry from '../../geometry/Geometry'; -import Buffer from '../../geometry/Buffer'; - -/** - * Helper class to create a quad with uvs like in v4 - * - * @class - * @memberof PIXI - */ -export default class QuadUv extends Geometry -{ - constructor() - { - super(); - - /** - * An array of vertices - * - * @member {Float32Array} - */ - this.vertices = new Float32Array([ - -1, -1, - 1, -1, - 1, 1, - -1, 1, - ]); - - /** - * The Uvs of the quad - * - * @member {Float32Array} - */ - this.uvs = new Float32Array([ - 0, 0, - 1, 0, - 1, 1, - 0, 1, - ]); - - this.vertexBuffer = new Buffer(this.vertices); - this.uvBuffer = new Buffer(this.uvs); - - this.addAttribute('aVertexPosition', this.vertexBuffer) - .addAttribute('aTextureCoord', this.uvBuffer) - .addIndex([0, 1, 2, 0, 2, 3]); - } - - /** - * Maps two Rectangle to the quad. - * - * @param {PIXI.Rectangle} targetTextureFrame - the first rectangle - * @param {PIXI.Rectangle} destinationFrame - the second rectangle - * @return {PIXI.Quad} Returns itself. - */ - map(targetTextureFrame, destinationFrame) - { - let x = 0; // destinationFrame.x / targetTextureFrame.width; - let y = 0; // destinationFrame.y / targetTextureFrame.height; - - this.uvs[0] = x; - this.uvs[1] = y; - - this.uvs[2] = x + (destinationFrame.width / targetTextureFrame.width); - this.uvs[3] = y; - - this.uvs[4] = x + (destinationFrame.width / targetTextureFrame.width); - this.uvs[5] = y + (destinationFrame.height / targetTextureFrame.height); - - this.uvs[6] = x; - this.uvs[7] = y + (destinationFrame.height / targetTextureFrame.height); - - x = destinationFrame.x; - y = destinationFrame.y; - - this.vertices[0] = x; - this.vertices[1] = y; - - this.vertices[2] = x + destinationFrame.width; - this.vertices[3] = y; - - this.vertices[4] = x + destinationFrame.width; - this.vertices[5] = y + destinationFrame.height; - - this.vertices[6] = x; - this.vertices[7] = y + destinationFrame.height; - - this.invalidate(); - - return this; - } - - /** - * legacy upload method, just marks buffers dirty - * @returns {PIXI.QuadUv} Returns itself. - */ - invalidate() - { - this.vertexBuffer._updateID++; - this.uvBuffer._updateID++; - - return this; - } -} diff --git a/packages/core/src/renderers/utils/checkMaxIfStatmentsInShader.js b/packages/core/src/renderers/utils/checkMaxIfStatmentsInShader.js deleted file mode 100644 index 8047c96..0000000 --- a/packages/core/src/renderers/utils/checkMaxIfStatmentsInShader.js +++ /dev/null @@ -1,58 +0,0 @@ -const fragTemplate = [ - 'precision mediump float;', - 'void main(void){', - 'float test = 0.1;', - '%forloop%', - 'gl_FragColor = vec4(0.0);', - '}', -].join('\n'); - -export default function checkMaxIfStatmentsInShader(maxIfs, gl) -{ - if (maxIfs === 0) - { - throw new Error('Invalid value of `0` passed to `checkMaxIfStatementsInShader`'); - } - - const shader = gl.createShader(gl.FRAGMENT_SHADER); - - while (true) // eslint-disable-line no-constant-condition - { - const fragmentSrc = fragTemplate.replace(/%forloop%/gi, generateIfTestSrc(maxIfs)); - - gl.shaderSource(shader, fragmentSrc); - gl.compileShader(shader); - - if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) - { - maxIfs = (maxIfs / 2) | 0; - } - else - { - // valid! - break; - } - } - - return maxIfs; -} - -function generateIfTestSrc(maxIfs) -{ - let src = ''; - - for (let i = 0; i < maxIfs; ++i) - { - if (i > 0) - { - src += '\nelse '; - } - - if (i < maxIfs - 1) - { - src += `if(test == ${i}.0){}`; - } - } - - return src; -} diff --git a/packages/core/src/renderers/utils/mapWebGLBlendModesToPixi.js b/packages/core/src/renderers/utils/mapWebGLBlendModesToPixi.js deleted file mode 100644 index 10ffe4d..0000000 --- a/packages/core/src/renderers/utils/mapWebGLBlendModesToPixi.js +++ /dev/null @@ -1,47 +0,0 @@ -import { BLEND_MODES } from '@pixi/constants'; - -/** - * Maps gl blend combinations to WebGL. - * - * @memberof PIXI - * @function mapWebGLBlendModesToPixi - * @private - * @param {WebGLRenderingContext} gl - The rendering context. - * @param {string[]} [array=[]] - The array to output into. - * @return {string[]} Mapped modes. - */ -export default function mapWebGLBlendModesToPixi(gl, array = []) -{ - // TODO - premultiply alpha would be different. - // add a boolean for that! - array[BLEND_MODES.NORMAL] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.ADD] = [gl.ONE, gl.DST_ALPHA]; - array[BLEND_MODES.MULTIPLY] = [gl.DST_COLOR, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.SCREEN] = [gl.ONE, gl.ONE_MINUS_SRC_COLOR]; - array[BLEND_MODES.OVERLAY] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.DARKEN] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.LIGHTEN] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.COLOR_DODGE] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.COLOR_BURN] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.HARD_LIGHT] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.SOFT_LIGHT] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.DIFFERENCE] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.EXCLUSION] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.HUE] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.SATURATION] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.COLOR] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.LUMINOSITY] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.NONE] = [0, 0]; - - // not-premultiplied blend modes - array[BLEND_MODES.NORMAL_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.ADD_NPM] = [gl.SRC_ALPHA, gl.DST_ALPHA, gl.ONE, gl.DST_ALPHA]; - array[BLEND_MODES.SCREEN_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_COLOR, gl.ONE, gl.ONE_MINUS_SRC_COLOR]; - - // not-premultiplied blend modes - array[BLEND_MODES.NORMAL_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.ADD_NPM] = [gl.SRC_ALPHA, gl.DST_ALPHA, gl.ONE, gl.DST_ALPHA]; - array[BLEND_MODES.SCREEN_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_COLOR, gl.ONE, gl.ONE_MINUS_SRC_COLOR]; - - return array; -} diff --git a/packages/core/src/shader/GLProgram.js b/packages/core/src/shader/GLProgram.js new file mode 100644 index 0000000..92980ca --- /dev/null +++ b/packages/core/src/shader/GLProgram.js @@ -0,0 +1,43 @@ +/** + * Helper class to create a webGL Program + * + * @class + * @private + */ +export default class GLProgram +{ + constructor(program, uniformData) + { + /** + * The shader program + * + * @member {WebGLProgram} + */ + this.program = program; + + /** + * holds the uniform data which contains uniform locations + * and current uniform values used for caching and preventing undeed GPU commands + * @type {Object} + */ + this.uniformData = uniformData; + + /** + * uniformGroups holds the various upload functions for the shader. Each uniform group + * and program have a unique upload function generated. + * @type {Object} + */ + this.uniformGroups = {}; + } + + /** + * Destroys this program + * TODO + */ + destroy() + { + this.uniformData = null; + this.uniformGroups = null; + this.program = null; + } +} diff --git a/packages/core/src/shader/Program.js b/packages/core/src/shader/Program.js index 4f9971e..4b4007a 100644 --- a/packages/core/src/shader/Program.js +++ b/packages/core/src/shader/Program.js @@ -1,6 +1,11 @@ -import * as shaderUtils from '../renderers/systems/shader/shader'; +// import * as from '../systems/shader/shader'; +import { setPrecision, + defaultValue, + compileProgram, + mapSize, + mapType, + getTestContext } from './utils'; import { ProgramCache } from '@pixi/utils'; -import getTestContext from './getTestContext'; import defaultFragment from './defaultProgram.frag'; import defaultVertex from './defaultProgram.vert'; import { settings } from '@pixi/settings'; @@ -34,8 +39,8 @@ */ this.fragmentSrc = fragmentSrc || Program.defaultFragmentSrc; - this.vertexSrc = shaderUtils.setPrecision(this.vertexSrc, settings.PRECISION_VERTEX); - this.fragmentSrc = shaderUtils.setPrecision(this.fragmentSrc, settings.PRECISION_FRAGMENT); + this.vertexSrc = setPrecision(this.vertexSrc, settings.PRECISION_VERTEX); + this.fragmentSrc = setPrecision(this.fragmentSrc, settings.PRECISION_FRAGMENT); // currently this does not extract structs only default types this.extractData(this.vertexSrc, this.fragmentSrc); @@ -62,7 +67,7 @@ if (gl) { - const program = shaderUtils.compileProgram(gl, vertexSrc, fragmentSrc); + const program = compileProgram(gl, vertexSrc, fragmentSrc); this.attributeData = this.getAttributeData(program, gl); this.uniformData = this.getUniformData(program, gl); @@ -95,13 +100,13 @@ for (let i = 0; i < totalAttributes; i++) { const attribData = gl.getActiveAttrib(program, i); - const type = shaderUtils.mapType(gl, attribData.type); + const type = mapType(gl, attribData.type); /*eslint-disable */ const data = { type: type, name: attribData.name, - size: shaderUtils.mapSize(type), + size: mapSize(type), location: 0, }; /* eslint-enable */ @@ -145,14 +150,14 @@ const name = uniformData.name.replace(/\[.*?\]/, ''); const isArray = uniformData.name.match(/\[.*?\]/, ''); - const type = shaderUtils.mapType(gl, uniformData.type); + const type = mapType(gl, uniformData.type); /*eslint-disable */ uniforms[name] = { type: type, size: uniformData.size, isArray:isArray, - value: shaderUtils.defaultValue(type, uniformData.size), + value: defaultValue(type, uniformData.size), }; /* eslint-enable */ } diff --git a/packages/core/src/shader/ShaderSystem.js b/packages/core/src/shader/ShaderSystem.js new file mode 100644 index 0000000..40b8af8 --- /dev/null +++ b/packages/core/src/shader/ShaderSystem.js @@ -0,0 +1,171 @@ +import System from '../System'; +import GLProgram from './GLProgram'; +import { generateUniformsSync, + defaultValue, + compileProgram } from './utils'; + +let UID = 0; + +/** + * Helper class to create a webGL Texture + * + * @class + * @memberof PIXI.systems + * @extends PIXI.System + */ +export default class ShaderSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - A reference to the current renderer + */ + constructor(renderer) + { + super(renderer); + + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = null; + + this.shader = null; + this.program = null; + + this.id = UID++; + } + + contextChange(gl) + { + this.gl = gl; + } + + /** + * Changes the current shader to the one given in parameter + * + * @param {PIXI.Shader} shader - the new shader + * @param {boolean} dontSync - false if the shader should automatically sync its uniforms. + * @returns {PIXI.glCore.glProgram} the glProgram that belongs to the shader. + */ + bind(shader, dontSync) + { + shader.uniforms.globals = this.renderer.globalUniforms; + + const program = shader.program; + const glProgram = program.glPrograms[this.renderer.CONTEXT_UID] || this.generateShader(shader); + + this.shader = shader; + + // TODO - some current pixi plugins bypass this.. so it not safe to use yet.. + if (this.program !== program) + { + this.program = program; + this.gl.useProgram(glProgram.program); + } + + if (!dontSync) + { + this.syncUniformGroup(shader.uniformGroup); + } + + return glProgram; + } + + /** + * Uploads the uniforms values to the currently bound shader. + * + * @param {object} uniforms - the uniforms valiues that be applied to the current shader + */ + setUniforms(uniforms) + { + const shader = this.shader.program; + const glProgram = shader.glPrograms[this.renderer.CONTEXT_UID]; + + shader.syncUniforms(glProgram.uniformData, uniforms, this.renderer); + } + + syncUniformGroup(group) + { + const glProgram = this.getglProgram(); + + if (!group.static || group.dirtyId !== glProgram.uniformGroups[group.id]) + { + glProgram.uniformGroups[group.id] = group.dirtyId; + const syncFunc = group.syncUniforms[this.shader.program.id] || this.createSyncGroups(group); + + syncFunc(glProgram.uniformData, group.uniforms, this.renderer); + } + } + + createSyncGroups(group) + { + group.syncUniforms[this.shader.program.id] = generateUniformsSync(group, this.shader.program.uniformData); + + return group.syncUniforms[this.shader.program.id]; + } + + /** + * Returns the underlying GLShade rof the currently bound shader. + * This can be handy for when you to have a little more control over the setting of your uniforms. + * + * @return {PIXI.glCore.Shader} the glProgram for the currently bound Shader for this context + */ + getglProgram() + { + if (this.shader) + { + return this.shader.program.glPrograms[this.renderer.CONTEXT_UID]; + } + + return null; + } + + /** + * Generates a glProgram verion of the Shader provided. + * + * @private + * @param {PIXI.Shader} shader the shader that the glProgram will be based on. + * @return {PIXI.glCore.glProgram} A shiney new glProgram + */ + generateShader(shader) + { + const gl = this.gl; + + const program = shader.program; + + const attribMap = {}; + + for (const i in program.attributeData) + { + attribMap[i] = program.attributeData[i].location; + } + + const shaderProgram = compileProgram(gl, program.vertexSrc, program.fragmentSrc, attribMap); + const uniformData = {}; + + for (const i in program.uniformData) + { + const data = program.uniformData[i]; + + uniformData[i] = { + location: gl.getUniformLocation(shaderProgram, i), + value: defaultValue(data.type, data.size), + }; + } + + const glProgram = new GLProgram(shaderProgram, uniformData); + + program.glPrograms[this.renderer.CONTEXT_UID] = glProgram; + + return glProgram; + } + + /** + * Destroys this System and removes all its textures + */ + destroy() + { + // TODO implement destroy method for ShaderSystem + this.destroyed = true; + } +} diff --git a/packages/core/src/shader/defaultValue.js b/packages/core/src/shader/defaultValue.js deleted file mode 100644 index 847a069..0000000 --- a/packages/core/src/shader/defaultValue.js +++ /dev/null @@ -1,78 +0,0 @@ -function booleanArray(size) -{ - const array = new Array(size); - - for (let i = 0; i < array.length; i++) - { - array[i] = false; - } - - return array; -} - -/** - * @class - * @memberof PIXI.glCore.shader - * @param type {String} Type of value - * @param size {Number} - */ -export default function defaultValue(type, size) -{ - switch (type) - { - case 'float': - return 0; - - case 'vec2': - return new Float32Array(2 * size); - - case 'vec3': - return new Float32Array(3 * size); - - case 'vec4': - return new Float32Array(4 * size); - - case 'int': - case 'sampler2D': - return 0; - - case 'ivec2': - return new Int32Array(2 * size); - - case 'ivec3': - return new Int32Array(3 * size); - - case 'ivec4': - return new Int32Array(4 * size); - - case 'bool': - return false; - - case 'bvec2': - - return booleanArray(2 * size); - - case 'bvec3': - return booleanArray(3 * size); - - case 'bvec4': - return booleanArray(4 * size); - - case 'mat2': - return new Float32Array([1, 0, - 0, 1]); - - case 'mat3': - return new Float32Array([1, 0, 0, - 0, 1, 0, - 0, 0, 1]); - - case 'mat4': - return new Float32Array([1, 0, 0, 0, - 0, 1, 0, 0, - 0, 0, 1, 0, - 0, 0, 0, 1]); - } - - return 0; -} diff --git a/packages/core/src/shader/extractUniformsFromSrc.js b/packages/core/src/shader/extractUniformsFromSrc.js deleted file mode 100644 index 3d02c0a..0000000 --- a/packages/core/src/shader/extractUniformsFromSrc.js +++ /dev/null @@ -1,57 +0,0 @@ -import defaultValue from './defaultValue'; - -export default function extractUniformsFromSrc(vertexSrc, fragmentSrc, mask) -{ - const vertUniforms = extractUniformsFromString(vertexSrc, mask); - const fragUniforms = extractUniformsFromString(fragmentSrc, mask); - - return Object.assign(vertUniforms, fragUniforms); -} - -function extractUniformsFromString(string) -{ - const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); - - const uniforms = {}; - let nameSplit; - - // clean the lines a little - remove extra spaces / teabs etc - // then split along ';' - const lines = string.replace(/\s+/g, ' ') - .split(/\s*;\s*/); - - // loop through.. - for (let i = 0; i < lines.length; i++) - { - const line = lines[i].trim(); - - if (line.indexOf('uniform') > -1) - { - const splitLine = line.split(' '); - const type = splitLine[1]; - - let name = splitLine[2]; - let size = 1; - - if (name.indexOf('[') > -1) - { - // array! - nameSplit = name.split(/\[|]/); - name = nameSplit[0]; - size *= Number(nameSplit[1]); - } - - if (!name.match(maskRegex)) - { - uniforms[name] = { - value: defaultValue(type, size), - dirtyId: 0, - name, - type, - }; - } - } - } - - return uniforms; -} diff --git a/packages/core/src/shader/generateUniformsSync.js b/packages/core/src/shader/generateUniformsSync.js deleted file mode 100644 index 98a357f..0000000 --- a/packages/core/src/shader/generateUniformsSync.js +++ /dev/null @@ -1,231 +0,0 @@ -// cv = CachedValue -// v = value -// ud = uniformData -// uv = uniformValue -// l = loaction -const GLSL_TO_SINGLE_SETTERS_CACHED = { - - float: ` - if(cv !== v) - { - cv.v = v; - gl.uniform1f(location, v) - }`, - - vec2: ` - if(cv[0] !== v[0] || cv[1] !== v[1]) - { - cv[0] = v[0]; - cv[1] = v[1]; - gl.uniform2f(location, v[0], v[1]) - }`, - - vec3: ` - if(cv[0] !== v[0] || cv[1] !== v[1] || cv[2] !== v[2]) - { - cv[0] = v[0]; - cv[1] = v[1]; - cv[2] = v[2]; - - gl.uniform3f(location, v[0], v[1], v[2]) - }`, - - vec4: 'gl.uniform4f(location, v[0], v[1], v[2], v[3])', - - int: 'gl.uniform1i(location, v)', - ivec2: 'gl.uniform2i(location, v[0], v[1])', - ivec3: 'gl.uniform3i(location, v[0], v[1], v[2])', - ivec4: 'gl.uniform4i(location, v[0], v[1], v[2], v[3])', - - bool: 'gl.uniform1i(location, v)', - bvec2: 'gl.uniform2i(location, v[0], v[1])', - bvec3: 'gl.uniform3i(location, v[0], v[1], v[2])', - bvec4: 'gl.uniform4i(location, v[0], v[1], v[2], v[3])', - - mat2: 'gl.uniformMatrix2fv(location, false, v)', - mat3: 'gl.uniformMatrix3fv(location, false, v)', - mat4: 'gl.uniformMatrix4fv(location, false, v)', - - sampler2D: 'gl.uniform1i(location, v)', - samplerCube: 'gl.uniform1i(location, v)', - sampler2DArray: 'gl.uniform1i(location, v)', -}; - -const GLSL_TO_ARRAY_SETTERS = { - - float: `gl.uniform1fv(location, v)`, - - vec2: `gl.uniform2fv(location, v)`, - vec3: `gl.uniform3fv(location, v)`, - vec4: 'gl.uniform4fv(location, v)', - - mat4: 'gl.uniformMatrix4fv(location, false, v)', - mat3: 'gl.uniformMatrix3fv(location, false, v)', - mat2: 'gl.uniformMatrix2fv(location, false, v)', - - int: 'gl.uniform1iv(location, v)', - ivec2: 'gl.uniform2iv(location, v)', - ivec3: 'gl.uniform3iv(location, v)', - ivec4: 'gl.uniform4iv(location, v)', - - bool: 'gl.uniform1iv(location, v)', - bvec2: 'gl.uniform2iv(location, v)', - bvec3: 'gl.uniform3iv(location, v)', - bvec4: 'gl.uniform4iv(location, v)', - - sampler2D: 'gl.uniform1iv(location, v)', - samplerCube: 'gl.uniform1iv(location, v)', - sampler2DArray: 'gl.uniform1iv(location, v)', -}; - -export default function generateUniformsSync(group, uniformData) -{ - let textureCount = 0; - let func = `var v = null; - var cv = null - var gl = renderer.gl`; - - for (const i in group.uniforms) - { - const data = uniformData[i]; - - if (!data) - { - if (group.uniforms[i].group) - { - func += ` - renderer.shader.syncUniformGroup(uv.${i}); - `; - } - - continue; - } - - // TODO && uniformData[i].value !== 0 <-- do we still need this? - if (data.type === 'float' && data.size === 1) - { - func += ` - if(uv.${i} !== ud.${i}.value) - { - ud.${i}.value = uv.${i} - gl.uniform1f(ud.${i}.location, uv.${i}) - }\n`; - } - /* eslint-disable max-len */ - else if ((data.type === 'sampler2D' || data.type === 'samplerCube' || data.type === 'sampler2DArray') && data.size === 1 && !data.isArray) - /* eslint-disable max-len */ - { - func += ` - renderer.texture.bind(uv.${i}, ${textureCount}); - - if(ud.${i}.value !== ${textureCount}) - { - ud.${i}.value = ${textureCount}; - gl.uniform1i(ud.${i}.location, ${textureCount});\n; // eslint-disable-line max-len - }\n`; - - textureCount++; - } - else if (data.type === 'mat3' && data.size === 1) - { - if (group.uniforms[i].a !== undefined) - { - // TODO and some smart caching dirty ids here! - func += ` - gl.uniformMatrix3fv(ud.${i}.location, false, uv.${i}.toArray(true)); - \n`; - } - else - { - func += ` - gl.uniformMatrix3fv(ud.${i}.location, false, uv.${i}); - \n`; - } - } - else if (data.type === 'vec2' && data.size === 1) - { - // TODO - do we need both here? - // maybe we can get away with only using points? - if (group.uniforms[i].x !== undefined) - { - func += ` - cv = ud.${i}.value; - v = uv.${i}; - - if(cv[0] !== v.x || cv[1] !== v.y) - { - cv[0] = v.x; - cv[1] = v.y; - gl.uniform2f(ud.${i}.location, v.x, v.y); - }\n`; - } - else - { - func += ` - cv = ud.${i}.value; - v = uv.${i}; - - if(cv[0] !== v[0] || cv[1] !== v[1]) - { - cv[0] = v[0]; - cv[1] = v[1]; - gl.uniform2f(ud.${i}.location, v[0], v[1]); - } - \n`; - } - } - else if (data.type === 'vec4' && data.size === 1) - { - // TODO - do we need both here? - // maybe we can get away with only using points? - if (group.uniforms[i].width !== undefined) - { - func += ` - cv = ud.${i}.value; - v = uv.${i}; - - if(cv[0] !== v.x || cv[1] !== v.y || cv[2] !== v.width || cv[3] !== v.height) - { - cv[0] = v.x; - cv[1] = v.y; - cv[2] = v.width; - cv[3] = v.height; - gl.uniform4f(ud.${i}.location, v.x, v.y, v.width, v.height) - }\n`; - } - else - { - func += ` - cv = ud.${i}.value; - v = uv.${i}; - - if(cv[0] !== v[0] || cv[1] !== v[1] || cv[2] !== v[2] || cv[3] !== v[3]) - { - cv[0] = v[0]; - cv[1] = v[1]; - cv[2] = v[2]; - cv[3] = v[3]; - - gl.uniform4f(ud.${i}.location, v[0], v[1], v[2], v[3]) - } - \n`; - } - } - else - { - const templateType = (data.size === 1) ? GLSL_TO_SINGLE_SETTERS_CACHED : GLSL_TO_ARRAY_SETTERS; - - const template = templateType[data.type].replace('location', `ud.${i}.location`); - - func += ` - cv = ud.${i}.value; - v = uv.${i}; - ${template};\n`; - } - } - - // console.log(' --------------- ') - // console.log(func); - - return new Function('ud', 'uv', 'renderer', func); // eslint-disable-line no-new-func -} diff --git a/packages/core/src/shader/getTestContext.js b/packages/core/src/shader/getTestContext.js deleted file mode 100644 index ea174cc..0000000 --- a/packages/core/src/shader/getTestContext.js +++ /dev/null @@ -1,48 +0,0 @@ -import { settings } from '@pixi/settings'; - -let context = null; - -/** - * returns a little webGL context to use for program inspection. - * - * @static - * @private - * @returns {webGL-context} a gl context to test with - */ -export default function getTestContext() -{ - if (!context) - { - const canvas = document.createElement('canvas'); - - let gl; - - if (settings.PREFER_WEBGL_2) - { - gl = canvas.getContext('webgl2', {}); - } - - if (!gl) - { - gl = canvas.getContext('webgl', {}) - || canvas.getContext('experimental-webgl', {}); - - if (!gl) - { - // fail, not able to get a context - throw new Error('This browser does not support webGL. Try using the canvas renderer'); - } - else - { - // for shader testing.. - gl.getExtension('WEBGL_draw_buffers'); - } - } - - context = gl; - - return gl; - } - - return context; -} diff --git a/packages/core/src/shader/utils/checkMaxIfStatmentsInShader.js b/packages/core/src/shader/utils/checkMaxIfStatmentsInShader.js new file mode 100644 index 0000000..8047c96 --- /dev/null +++ b/packages/core/src/shader/utils/checkMaxIfStatmentsInShader.js @@ -0,0 +1,58 @@ +const fragTemplate = [ + 'precision mediump float;', + 'void main(void){', + 'float test = 0.1;', + '%forloop%', + 'gl_FragColor = vec4(0.0);', + '}', +].join('\n'); + +export default function checkMaxIfStatmentsInShader(maxIfs, gl) +{ + if (maxIfs === 0) + { + throw new Error('Invalid value of `0` passed to `checkMaxIfStatementsInShader`'); + } + + const shader = gl.createShader(gl.FRAGMENT_SHADER); + + while (true) // eslint-disable-line no-constant-condition + { + const fragmentSrc = fragTemplate.replace(/%forloop%/gi, generateIfTestSrc(maxIfs)); + + gl.shaderSource(shader, fragmentSrc); + gl.compileShader(shader); + + if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) + { + maxIfs = (maxIfs / 2) | 0; + } + else + { + // valid! + break; + } + } + + return maxIfs; +} + +function generateIfTestSrc(maxIfs) +{ + let src = ''; + + for (let i = 0; i < maxIfs; ++i) + { + if (i > 0) + { + src += '\nelse '; + } + + if (i < maxIfs - 1) + { + src += `if(test == ${i}.0){}`; + } + } + + return src; +} diff --git a/packages/core/src/shader/utils/compileProgram.js b/packages/core/src/shader/utils/compileProgram.js new file mode 100644 index 0000000..8c6ccb9 --- /dev/null +++ b/packages/core/src/shader/utils/compileProgram.js @@ -0,0 +1,78 @@ +/** + * @method compileProgram + * @memberof PIXI.glCore.shader + * @param gl {WebGLRenderingContext} The current WebGL context {WebGLProgram} + * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. + * @param fragmentSrc {string|string[]} The fragment shader source as an array of strings. + * @param attributeLocations {Object} An attribute location map that lets you manually set the attribute locations + * @return {WebGLProgram} the shader program + */ +export default function compileProgram(gl, vertexSrc, fragmentSrc, attributeLocations) +{ + const glVertShader = compileShader(gl, gl.VERTEX_SHADER, vertexSrc); + const glFragShader = compileShader(gl, gl.FRAGMENT_SHADER, fragmentSrc); + + let program = gl.createProgram(); + + gl.attachShader(program, glVertShader); + gl.attachShader(program, glFragShader); + + // optionally, set the attributes manually for the program rather than letting WebGL decide.. + if (attributeLocations) + { + for (const i in attributeLocations) + { + gl.bindAttribLocation(program, attributeLocations[i], i); + } + } + + gl.linkProgram(program); + + // if linking fails, then log and cleanup + if (!gl.getProgramParameter(program, gl.LINK_STATUS)) + { + console.error('Pixi.js Error: Could not initialize shader.'); + console.error('gl.VALIDATE_STATUS', gl.getProgramParameter(program, gl.VALIDATE_STATUS)); + console.error('gl.getError()', gl.getError()); + + // if there is a program info log, log it + if (gl.getProgramInfoLog(program) !== '') + { + console.warn('Pixi.js Warning: gl.getProgramInfoLog()', gl.getProgramInfoLog(program)); + } + + gl.deleteProgram(program); + program = null; + } + + // clean up some shaders + gl.deleteShader(glVertShader); + gl.deleteShader(glFragShader); + + return program; +} + +/** + * @private + * @param gl {WebGLRenderingContext} The current WebGL context {WebGLProgram} + * @param type {Number} the type, can be either VERTEX_SHADER or FRAGMENT_SHADER + * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. + * @return {WebGLShader} the shader + */ +function compileShader(gl, type, src) +{ + const shader = gl.createShader(type); + + gl.shaderSource(shader, src); + gl.compileShader(shader); + + if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) + { + console.warn(src); + console.error(gl.getShaderInfoLog(shader)); + + return null; + } + + return shader; +} diff --git a/packages/core/src/shader/utils/defaultValue.js b/packages/core/src/shader/utils/defaultValue.js new file mode 100644 index 0000000..97290bd --- /dev/null +++ b/packages/core/src/shader/utils/defaultValue.js @@ -0,0 +1,79 @@ +/** + * @method defaultValue + * @memberof PIXI.glCore.shader + * @param type {String} Type of value + * @param size {Number} + */ +export default function defaultValue(type, size) +{ + switch (type) + { + case 'float': + return 0; + + case 'vec2': + return new Float32Array(2 * size); + + case 'vec3': + return new Float32Array(3 * size); + + case 'vec4': + return new Float32Array(4 * size); + + case 'int': + case 'sampler2D': + case 'sampler2DArray': + return 0; + + case 'ivec2': + return new Int32Array(2 * size); + + case 'ivec3': + return new Int32Array(3 * size); + + case 'ivec4': + return new Int32Array(4 * size); + + case 'bool': + return false; + + case 'bvec2': + + return booleanArray(2 * size); + + case 'bvec3': + return booleanArray(3 * size); + + case 'bvec4': + return booleanArray(4 * size); + + case 'mat2': + return new Float32Array([1, 0, + 0, 1]); + + case 'mat3': + return new Float32Array([1, 0, 0, + 0, 1, 0, + 0, 0, 1]); + + case 'mat4': + return new Float32Array([1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1]); + } + + return null; +} + +function booleanArray(size) +{ + const array = new Array(size); + + for (let i = 0; i < array.length; i++) + { + array[i] = false; + } + + return array; +} diff --git a/packages/core/src/shader/utils/extractUniformsFromSrc.js b/packages/core/src/shader/utils/extractUniformsFromSrc.js new file mode 100644 index 0000000..3d02c0a --- /dev/null +++ b/packages/core/src/shader/utils/extractUniformsFromSrc.js @@ -0,0 +1,57 @@ +import defaultValue from './defaultValue'; + +export default function extractUniformsFromSrc(vertexSrc, fragmentSrc, mask) +{ + const vertUniforms = extractUniformsFromString(vertexSrc, mask); + const fragUniforms = extractUniformsFromString(fragmentSrc, mask); + + return Object.assign(vertUniforms, fragUniforms); +} + +function extractUniformsFromString(string) +{ + const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); + + const uniforms = {}; + let nameSplit; + + // clean the lines a little - remove extra spaces / teabs etc + // then split along ';' + const lines = string.replace(/\s+/g, ' ') + .split(/\s*;\s*/); + + // loop through.. + for (let i = 0; i < lines.length; i++) + { + const line = lines[i].trim(); + + if (line.indexOf('uniform') > -1) + { + const splitLine = line.split(' '); + const type = splitLine[1]; + + let name = splitLine[2]; + let size = 1; + + if (name.indexOf('[') > -1) + { + // array! + nameSplit = name.split(/\[|]/); + name = nameSplit[0]; + size *= Number(nameSplit[1]); + } + + if (!name.match(maskRegex)) + { + uniforms[name] = { + value: defaultValue(type, size), + dirtyId: 0, + name, + type, + }; + } + } + } + + return uniforms; +} diff --git a/packages/core/src/shader/utils/generateUniformsSync.js b/packages/core/src/shader/utils/generateUniformsSync.js new file mode 100644 index 0000000..98a357f --- /dev/null +++ b/packages/core/src/shader/utils/generateUniformsSync.js @@ -0,0 +1,231 @@ +// cv = CachedValue +// v = value +// ud = uniformData +// uv = uniformValue +// l = loaction +const GLSL_TO_SINGLE_SETTERS_CACHED = { + + float: ` + if(cv !== v) + { + cv.v = v; + gl.uniform1f(location, v) + }`, + + vec2: ` + if(cv[0] !== v[0] || cv[1] !== v[1]) + { + cv[0] = v[0]; + cv[1] = v[1]; + gl.uniform2f(location, v[0], v[1]) + }`, + + vec3: ` + if(cv[0] !== v[0] || cv[1] !== v[1] || cv[2] !== v[2]) + { + cv[0] = v[0]; + cv[1] = v[1]; + cv[2] = v[2]; + + gl.uniform3f(location, v[0], v[1], v[2]) + }`, + + vec4: 'gl.uniform4f(location, v[0], v[1], v[2], v[3])', + + int: 'gl.uniform1i(location, v)', + ivec2: 'gl.uniform2i(location, v[0], v[1])', + ivec3: 'gl.uniform3i(location, v[0], v[1], v[2])', + ivec4: 'gl.uniform4i(location, v[0], v[1], v[2], v[3])', + + bool: 'gl.uniform1i(location, v)', + bvec2: 'gl.uniform2i(location, v[0], v[1])', + bvec3: 'gl.uniform3i(location, v[0], v[1], v[2])', + bvec4: 'gl.uniform4i(location, v[0], v[1], v[2], v[3])', + + mat2: 'gl.uniformMatrix2fv(location, false, v)', + mat3: 'gl.uniformMatrix3fv(location, false, v)', + mat4: 'gl.uniformMatrix4fv(location, false, v)', + + sampler2D: 'gl.uniform1i(location, v)', + samplerCube: 'gl.uniform1i(location, v)', + sampler2DArray: 'gl.uniform1i(location, v)', +}; + +const GLSL_TO_ARRAY_SETTERS = { + + float: `gl.uniform1fv(location, v)`, + + vec2: `gl.uniform2fv(location, v)`, + vec3: `gl.uniform3fv(location, v)`, + vec4: 'gl.uniform4fv(location, v)', + + mat4: 'gl.uniformMatrix4fv(location, false, v)', + mat3: 'gl.uniformMatrix3fv(location, false, v)', + mat2: 'gl.uniformMatrix2fv(location, false, v)', + + int: 'gl.uniform1iv(location, v)', + ivec2: 'gl.uniform2iv(location, v)', + ivec3: 'gl.uniform3iv(location, v)', + ivec4: 'gl.uniform4iv(location, v)', + + bool: 'gl.uniform1iv(location, v)', + bvec2: 'gl.uniform2iv(location, v)', + bvec3: 'gl.uniform3iv(location, v)', + bvec4: 'gl.uniform4iv(location, v)', + + sampler2D: 'gl.uniform1iv(location, v)', + samplerCube: 'gl.uniform1iv(location, v)', + sampler2DArray: 'gl.uniform1iv(location, v)', +}; + +export default function generateUniformsSync(group, uniformData) +{ + let textureCount = 0; + let func = `var v = null; + var cv = null + var gl = renderer.gl`; + + for (const i in group.uniforms) + { + const data = uniformData[i]; + + if (!data) + { + if (group.uniforms[i].group) + { + func += ` + renderer.shader.syncUniformGroup(uv.${i}); + `; + } + + continue; + } + + // TODO && uniformData[i].value !== 0 <-- do we still need this? + if (data.type === 'float' && data.size === 1) + { + func += ` + if(uv.${i} !== ud.${i}.value) + { + ud.${i}.value = uv.${i} + gl.uniform1f(ud.${i}.location, uv.${i}) + }\n`; + } + /* eslint-disable max-len */ + else if ((data.type === 'sampler2D' || data.type === 'samplerCube' || data.type === 'sampler2DArray') && data.size === 1 && !data.isArray) + /* eslint-disable max-len */ + { + func += ` + renderer.texture.bind(uv.${i}, ${textureCount}); + + if(ud.${i}.value !== ${textureCount}) + { + ud.${i}.value = ${textureCount}; + gl.uniform1i(ud.${i}.location, ${textureCount});\n; // eslint-disable-line max-len + }\n`; + + textureCount++; + } + else if (data.type === 'mat3' && data.size === 1) + { + if (group.uniforms[i].a !== undefined) + { + // TODO and some smart caching dirty ids here! + func += ` + gl.uniformMatrix3fv(ud.${i}.location, false, uv.${i}.toArray(true)); + \n`; + } + else + { + func += ` + gl.uniformMatrix3fv(ud.${i}.location, false, uv.${i}); + \n`; + } + } + else if (data.type === 'vec2' && data.size === 1) + { + // TODO - do we need both here? + // maybe we can get away with only using points? + if (group.uniforms[i].x !== undefined) + { + func += ` + cv = ud.${i}.value; + v = uv.${i}; + + if(cv[0] !== v.x || cv[1] !== v.y) + { + cv[0] = v.x; + cv[1] = v.y; + gl.uniform2f(ud.${i}.location, v.x, v.y); + }\n`; + } + else + { + func += ` + cv = ud.${i}.value; + v = uv.${i}; + + if(cv[0] !== v[0] || cv[1] !== v[1]) + { + cv[0] = v[0]; + cv[1] = v[1]; + gl.uniform2f(ud.${i}.location, v[0], v[1]); + } + \n`; + } + } + else if (data.type === 'vec4' && data.size === 1) + { + // TODO - do we need both here? + // maybe we can get away with only using points? + if (group.uniforms[i].width !== undefined) + { + func += ` + cv = ud.${i}.value; + v = uv.${i}; + + if(cv[0] !== v.x || cv[1] !== v.y || cv[2] !== v.width || cv[3] !== v.height) + { + cv[0] = v.x; + cv[1] = v.y; + cv[2] = v.width; + cv[3] = v.height; + gl.uniform4f(ud.${i}.location, v.x, v.y, v.width, v.height) + }\n`; + } + else + { + func += ` + cv = ud.${i}.value; + v = uv.${i}; + + if(cv[0] !== v[0] || cv[1] !== v[1] || cv[2] !== v[2] || cv[3] !== v[3]) + { + cv[0] = v[0]; + cv[1] = v[1]; + cv[2] = v[2]; + cv[3] = v[3]; + + gl.uniform4f(ud.${i}.location, v[0], v[1], v[2], v[3]) + } + \n`; + } + } + else + { + const templateType = (data.size === 1) ? GLSL_TO_SINGLE_SETTERS_CACHED : GLSL_TO_ARRAY_SETTERS; + + const template = templateType[data.type].replace('location', `ud.${i}.location`); + + func += ` + cv = ud.${i}.value; + v = uv.${i}; + ${template};\n`; + } + } + + // console.log(' --------------- ') + // console.log(func); + + return new Function('ud', 'uv', 'renderer', func); // eslint-disable-line no-new-func +} diff --git a/packages/core/src/shader/utils/getTestContext.js b/packages/core/src/shader/utils/getTestContext.js new file mode 100644 index 0000000..ea174cc --- /dev/null +++ b/packages/core/src/shader/utils/getTestContext.js @@ -0,0 +1,48 @@ +import { settings } from '@pixi/settings'; + +let context = null; + +/** + * returns a little webGL context to use for program inspection. + * + * @static + * @private + * @returns {webGL-context} a gl context to test with + */ +export default function getTestContext() +{ + if (!context) + { + const canvas = document.createElement('canvas'); + + let gl; + + if (settings.PREFER_WEBGL_2) + { + gl = canvas.getContext('webgl2', {}); + } + + if (!gl) + { + gl = canvas.getContext('webgl', {}) + || canvas.getContext('experimental-webgl', {}); + + if (!gl) + { + // fail, not able to get a context + throw new Error('This browser does not support webGL. Try using the canvas renderer'); + } + else + { + // for shader testing.. + gl.getExtension('WEBGL_draw_buffers'); + } + } + + context = gl; + + return gl; + } + + return context; +} diff --git a/packages/core/src/shader/utils/index.js b/packages/core/src/shader/utils/index.js new file mode 100644 index 0000000..4bf8dd4 --- /dev/null +++ b/packages/core/src/shader/utils/index.js @@ -0,0 +1,8 @@ +export { default as compileProgram } from './compileProgram'; +export { default as defaultValue } from './defaultValue'; +export { default as setPrecision } from './setPrecision'; +export { default as mapSize } from './mapSize'; +export { default as mapType } from './mapType'; +export { default as generateUniformsSync } from './generateUniformsSync'; +export { default as getTestContext } from './getTestContext'; +export { default as checkMaxIfStatmentsInShader } from './checkMaxIfStatmentsInShader'; diff --git a/packages/core/src/shader/utils/mapSize.js b/packages/core/src/shader/utils/mapSize.js new file mode 100644 index 0000000..b681554 --- /dev/null +++ b/packages/core/src/shader/utils/mapSize.js @@ -0,0 +1,33 @@ +const GLSL_TO_SIZE = { + float: 1, + vec2: 2, + vec3: 3, + vec4: 4, + + int: 1, + ivec2: 2, + ivec3: 3, + ivec4: 4, + + bool: 1, + bvec2: 2, + bvec3: 3, + bvec4: 4, + + mat2: 4, + mat3: 9, + mat4: 16, + + sampler2D: 1, +}; + +/** + * @method mapSize + * @memberof PIXI.glCore.shader + * @param type {String} + * @return {Number} + */ +export default function mapSize(type) +{ + return GLSL_TO_SIZE[type]; +} diff --git a/packages/core/src/shader/utils/mapType.js b/packages/core/src/shader/utils/mapType.js new file mode 100644 index 0000000..d288b7c --- /dev/null +++ b/packages/core/src/shader/utils/mapType.js @@ -0,0 +1,45 @@ +let GL_TABLE = null; + +const GL_TO_GLSL_TYPES = { + FLOAT: 'float', + FLOAT_VEC2: 'vec2', + FLOAT_VEC3: 'vec3', + FLOAT_VEC4: 'vec4', + + INT: 'int', + INT_VEC2: 'ivec2', + INT_VEC3: 'ivec3', + INT_VEC4: 'ivec4', + + BOOL: 'bool', + BOOL_VEC2: 'bvec2', + BOOL_VEC3: 'bvec3', + BOOL_VEC4: 'bvec4', + + FLOAT_MAT2: 'mat2', + FLOAT_MAT3: 'mat3', + FLOAT_MAT4: 'mat4', + + SAMPLER_2D: 'sampler2D', + SAMPLER_CUBE: 'samplerCube', + SAMPLER_2D_ARRAY: 'sampler2DArray', +}; + +export default function mapType(gl, type) +{ + if (!GL_TABLE) + { + const typeNames = Object.keys(GL_TO_GLSL_TYPES); + + GL_TABLE = {}; + + for (let i = 0; i < typeNames.length; ++i) + { + const tn = typeNames[i]; + + GL_TABLE[gl[tn]] = GL_TO_GLSL_TYPES[tn]; + } + } + + return GL_TABLE[type]; +} diff --git a/packages/core/src/shader/utils/setPrecision.js b/packages/core/src/shader/utils/setPrecision.js new file mode 100644 index 0000000..e2c1f34 --- /dev/null +++ b/packages/core/src/shader/utils/setPrecision.js @@ -0,0 +1,18 @@ +/** + * Sets the float precision on the shader. If the precision is already present this function will do nothing + * @param {string} src the shader source + * @param {string} precision The float precision of the shader. Options are 'lowp', 'mediump' or 'highp'. + * + * @return {string} modified shader source + */ +export default function setPrecision(src, precision) +{ + src = src.trim(); + + if (src.substring(0, 9) !== 'precision' && src.substring(0, 1) !== '#') + { + return `precision ${precision} float;\n${src}`; + } + + return src; +} diff --git a/packages/core/src/state/State.js b/packages/core/src/state/State.js new file mode 100644 index 0000000..fa3f221 --- /dev/null +++ b/packages/core/src/state/State.js @@ -0,0 +1,163 @@ +/* eslint-disable max-len */ + +const BLEND = 0; +const OFFSET = 1; +const CULLING = 2; +const DEPTH_TEST = 3; +const WINDING = 4; + +/** + * This is a webGL state. It is passed The WebGL StateManager. + * Each mesh renderered may require webGL to be in a different state. + * For example you may want different blend mode or to enable polygon offsets + * + * @class + * @memberof PIXI + */ +export default class State +{ + /** + * + */ + constructor() + { + this.data = 0; + + this.blendMode = 0; + this.polygonOffset = 0; + + this.blend = true; + // this.depthTest = true; + } + + /** + * Activates blending of the computed fragment color values + * + * @member {boolean} + */ + get blend() + { + return !!(this.data & (1 << BLEND)); + } + + set blend(value) // eslint-disable-line require-jsdoc + { + if (!!(this.data & (1 << BLEND)) !== value) + { + this.data ^= (1 << BLEND); + } + } + + /** + * Activates adding an offset to depth values of polygon's fragments + * + * @member {boolean} + * @default false + */ + get offsets() + { + return !!(this.data & (1 << OFFSET)); + } + + set offsets(value) // eslint-disable-line require-jsdoc + { + if (!!(this.data & (1 << OFFSET)) !== value) + { + this.data ^= (1 << OFFSET); + } + } + + /** + * Activates culling of polygons. + * + * @member {boolean} + * @default false + */ + get culling() + { + return !!(this.data & (1 << CULLING)); + } + + set culling(value) // eslint-disable-line require-jsdoc + { + if (!!(this.data & (1 << CULLING)) !== value) + { + this.data ^= (1 << CULLING); + } + } + + /** + * Activates depth comparisons and updates to the depth buffer. + * + * @member {boolean} + * @default false + */ + get depthTest() + { + return !!(this.data & (1 << DEPTH_TEST)); + } + + set depthTest(value) // eslint-disable-line require-jsdoc + { + if (!!(this.data & (1 << DEPTH_TEST)) !== value) + { + this.data ^= (1 << DEPTH_TEST); + } + } + + /** + * Specifies whether or not front or back-facing polygons can be culled. + * @member {boolean} + * @default false + */ + get clockwiseFrontFace() + { + return !!(this.data & (1 << WINDING)); + } + + set clockwiseFrontFace(value) // eslint-disable-line require-jsdoc + { + if (!!(this.data & (1 << WINDING)) !== value) + { + this.data ^= (1 << WINDING); + } + } + + /** + * The blend mode to be applied when this state is set. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. + * Setting this mode to anything other than NO_BLEND will automatically switch blending on. + * + * @member {boolean} + * @default PIXI.BLEND_MODES.NORMAL + * @see PIXI.BLEND_MODES + */ + get blendMode() + { + return this._blendMode; + } + + set blendMode(value) // eslint-disable-line require-jsdoc + { + // 17 is NO BLEND + this.blend = (value !== 17); + this._blendMode = value; + } + + /** + * The polygon offset. Setting this property to anything other than 0 will automatically enable poygon offset fill. + * + * @member {number} + * @default 0 + */ + get polygonOffset() + { + return this._polygonOffset; + } + + set polygonOffset(value) // eslint-disable-line require-jsdoc + { + this.offsets = !!value; + this._polygonOffset = value; + } +} + diff --git a/packages/core/src/state/StateSystem.js b/packages/core/src/state/StateSystem.js new file mode 100755 index 0000000..39c35bb --- /dev/null +++ b/packages/core/src/state/StateSystem.js @@ -0,0 +1,288 @@ +import mapWebGLBlendModesToPixi from './utils/mapWebGLBlendModesToPixi'; +import System from '../System'; +import WebGLState from './State'; + +const BLEND = 0; +const OFFSET = 1; +const CULLING = 2; +const DEPTH_TEST = 3; +const WINDING = 4; + +/** + * A WebGL state machines + * + * @class + * @extends PIXI.systems.System + * @memberof PIXI.systems + */ +export default class StateSystem extends System +{ + /** + * @param {WebGLRenderingContext} gl - The current WebGL rendering context + */ + constructor(renderer) + { + super(renderer); + + this.gl = null; + + this.maxAttribs = null; + + // check we have vao.. + this.nativeVaoExtension = null; + + this.attribState = null; + + this.stateId = 0; + this.polygonOffset = 0; + this.blendMode = 17; + + this.map = []; + + // map functions for when we set state.. + this.map[BLEND] = this.setBlend; + this.map[OFFSET] = this.setOffset; + this.map[CULLING] = this.setCullFace; + this.map[DEPTH_TEST] = this.setDepthTest; + this.map[WINDING] = this.setFrontFace; + + this.checks = []; + + this.defaultState = new WebGLState(); + this.defaultState.blend = true; + this.defaultState.depth = true; + } + + contextChange(gl) + { + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); + + // check we have vao.. + this.nativeVaoExtension = ( + gl.getExtension('OES_vertex_array_object') + || gl.getExtension('MOZ_OES_vertex_array_object') + || gl.getExtension('WEBKIT_OES_vertex_array_object') + ); + + this.attribState = { + tempAttribState: new Array(this.maxAttribs), + attribState: new Array(this.maxAttribs), + }; + + this.blendModes = mapWebGLBlendModesToPixi(gl); + + this.setState(this.defaultState); + + this.reset(); + } + + /** + * Sets the current state + * + * @param {*} state - The state to set. + */ + setState(state) + { + state = state || this.defaultState; + + // TODO maybe to an object check? ( this.state === state )? + if (this.stateId !== state.data) + { + let diff = this.stateId ^ state.data; + let i = 0; + + // order from least to most common + while (diff) + { + if (diff & 1) + { + // state change! + this.map[i].call(this, !!(state.data & (1 << i))); + } + + diff = diff >> 1; + i++; + } + + this.stateId = state.data; + } + + // based on the above settings we check for specific modes.. + // for example if blend is active we check and set the blend modes + // or of polygon offset is active we check the poly depth. + for (let i = 0; i < this.checks.length; i++) + { + this.checks[i](this, state); + } + } + + /** + * Enables or disabled blending. + * + * @param {boolean} value - Turn on or off webgl blending. + */ + setBlend(value) + { + this.updateCheck(StateSystem.checkBlendMode, value); + + this.gl[value ? 'enable' : 'disable'](this.gl.BLEND); + } + + /** + * Enables or disable polygon offset fill + * + * @param {boolean} value - Turn on or off webgl polygon offset testing. + */ + setOffset(value) + { + this.gl[value ? 'enable' : 'disable'](this.gl.POLYGON_OFFSET_FILL); + } + + /** + * Sets whether to enable or disable depth test. + * + * @param {boolean} value - Turn on or off webgl depth testing. + */ + setDepthTest(value) + { + this.gl[value ? 'enable' : 'disable'](this.gl.DEPTH_TEST); + } + + /** + * Sets whether to enable or disable cull face. + * + * @param {boolean} value - Turn on or off webgl cull face. + */ + setCullFace(value) + { + this.gl[value ? 'enable' : 'disable'](this.gl.CULL_FACE); + } + + /** + * Sets the gl front face. + * + * @param {boolean} value - true is clockwise and false is counter-clockwise + */ + setFrontFace(value) + { + this.gl.frontFace(this.gl[value ? 'CW' : 'CCW']); + } + + /** + * Sets the blend mode. + * + * @param {number} value - The blend mode to set to. + */ + setBlendMode(value) + { + if (value === this.blendMode) + { + return; + } + + this.blendMode = value; + this.gl.blendFunc(this.blendModes[value][0], this.blendModes[value][1]); + } + + /** + * Sets the polygon offset. + * + * @param {number} value - the polygon offset + * @param {number} scale - the polygon offset scale + */ + setPolygonOffset(value, scale) + { + this.gl.polygonOffset(value, scale); + } + + /** + * Disables all the vaos in use + * + */ + resetAttributes() + { + for (let i = 0; i < this.attribState.tempAttribState.length; i++) + { + this.attribState.tempAttribState[i] = 0; + } + + for (let i = 0; i < this.attribState.attribState.length; i++) + { + this.attribState.attribState[i] = 0; + } + + // im going to assume one is always active for performance reasons. + for (let i = 1; i < this.maxAttribs; i++) + { + this.gl.disableVertexAttribArray(i); + } + } + + // used + /** + * Resets all the logic and disables the vaos + */ + reset() + { + // unbind any VAO if they exist.. + if (this.nativeVaoExtension) + { + this.nativeVaoExtension.bindVertexArrayOES(null); + } + + // reset all attributes.. + this.resetAttributes(); + + this.gl.pixelStorei(this.gl.UNPACK_FLIP_Y_WEBGL, false); + + this.setBlendMode(0); + + // TODO? + // this.setState(this.defaultState); + } + + /** + * checks to see which updates should be checked based on which settings have been activated + * for example if blend is enabled then we shold check the blend modes each time the state is changed + * or if poygon fill is activated then we need to check if the polygone offset changes. + * The idea is that we only check what we have too. + * + * @param {Function} func the checking function to add or remove + * @param {boolean} value should the check function be added or removed. + */ + updateCheck(func, value) + { + const index = this.checks.indexOf(func); + + if (value && index === -1) + { + this.checks.push(func); + } + else if (!value && index !== -1) + { + this.checks.splice(index, 1); + } + } + + /** + * A private little wrapper function that we call to check the blend mode. + * + * @static + * @private + * @param {PIXI.StateSystem} System the System to perform the state check on + * @param {PIXI.State} state the state that the blendMode will pulled from + */ + static checkBlendMode(system, state) + { + system.setBlendMode(state.blendMode); + } + + // TODO - add polygon offset? +} diff --git a/packages/core/src/state/utils/mapWebGLBlendModesToPixi.js b/packages/core/src/state/utils/mapWebGLBlendModesToPixi.js new file mode 100644 index 0000000..10ffe4d --- /dev/null +++ b/packages/core/src/state/utils/mapWebGLBlendModesToPixi.js @@ -0,0 +1,47 @@ +import { BLEND_MODES } from '@pixi/constants'; + +/** + * Maps gl blend combinations to WebGL. + * + * @memberof PIXI + * @function mapWebGLBlendModesToPixi + * @private + * @param {WebGLRenderingContext} gl - The rendering context. + * @param {string[]} [array=[]] - The array to output into. + * @return {string[]} Mapped modes. + */ +export default function mapWebGLBlendModesToPixi(gl, array = []) +{ + // TODO - premultiply alpha would be different. + // add a boolean for that! + array[BLEND_MODES.NORMAL] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; + array[BLEND_MODES.ADD] = [gl.ONE, gl.DST_ALPHA]; + array[BLEND_MODES.MULTIPLY] = [gl.DST_COLOR, gl.ONE_MINUS_SRC_ALPHA]; + array[BLEND_MODES.SCREEN] = [gl.ONE, gl.ONE_MINUS_SRC_COLOR]; + array[BLEND_MODES.OVERLAY] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; + array[BLEND_MODES.DARKEN] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; + array[BLEND_MODES.LIGHTEN] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; + array[BLEND_MODES.COLOR_DODGE] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; + array[BLEND_MODES.COLOR_BURN] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; + array[BLEND_MODES.HARD_LIGHT] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; + array[BLEND_MODES.SOFT_LIGHT] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; + array[BLEND_MODES.DIFFERENCE] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; + array[BLEND_MODES.EXCLUSION] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; + array[BLEND_MODES.HUE] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; + array[BLEND_MODES.SATURATION] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; + array[BLEND_MODES.COLOR] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; + array[BLEND_MODES.LUMINOSITY] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; + array[BLEND_MODES.NONE] = [0, 0]; + + // not-premultiplied blend modes + array[BLEND_MODES.NORMAL_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; + array[BLEND_MODES.ADD_NPM] = [gl.SRC_ALPHA, gl.DST_ALPHA, gl.ONE, gl.DST_ALPHA]; + array[BLEND_MODES.SCREEN_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_COLOR, gl.ONE, gl.ONE_MINUS_SRC_COLOR]; + + // not-premultiplied blend modes + array[BLEND_MODES.NORMAL_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; + array[BLEND_MODES.ADD_NPM] = [gl.SRC_ALPHA, gl.DST_ALPHA, gl.ONE, gl.DST_ALPHA]; + array[BLEND_MODES.SCREEN_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_COLOR, gl.ONE, gl.ONE_MINUS_SRC_COLOR]; + + return array; +} diff --git a/packages/core/src/systems.js b/packages/core/src/systems.js new file mode 100644 index 0000000..8edd9d5 --- /dev/null +++ b/packages/core/src/systems.js @@ -0,0 +1,17 @@ +/** + * Systems are individual components to the Renderer pipeline. + * @namespace PIXI.systems + */ +export { default as FilterSystem } from './filters/FilterSystem'; +export { default as BatchSystem } from './batch/BatchSystem'; +export { default as ContextSystem } from './context/ContextSystem'; +export { default as FramebufferSystem } from './framebuffer/FramebufferSystem'; +export { default as GeometrySystem } from './geometry/GeometrySystem'; +export { default as MaskSystem } from './mask/MaskSystem'; +export { default as StencilSystem } from './mask/StencilSystem'; +export { default as ProjectionSystem } from './projection/ProjectionSystem'; +export { default as RenderTextureSystem } from './renderTexture/RenderTextureSystem'; +export { default as ShaderSystem } from './shader/ShaderSystem'; +export { default as StateSystem } from './state/StateSystem'; +export { default as TextureGCSystem } from './textures/TextureGCSystem'; +export { default as TextureSystem } from './textures/TextureSystem'; diff --git a/packages/core/src/textures/BaseRenderTexture.js b/packages/core/src/textures/BaseRenderTexture.js index 0d4611e..77c44f5 100644 --- a/packages/core/src/textures/BaseRenderTexture.js +++ b/packages/core/src/textures/BaseRenderTexture.js @@ -1,5 +1,5 @@ import BaseTexture from './BaseTexture'; -import FrameBuffer from './FrameBuffer'; +import FrameBuffer from '../framebuffer/FrameBuffer'; /** * A BaseRenderTexture is a special texture that allows any PixiJS display object to be rendered to it. diff --git a/packages/core/src/AbstractRenderer.js b/packages/core/src/AbstractRenderer.js new file mode 100644 index 0000000..64b2eb1 --- /dev/null +++ b/packages/core/src/AbstractRenderer.js @@ -0,0 +1,353 @@ +import { hex2string, hex2rgb } from '@pixi/utils'; +import { Matrix, Rectangle } from '@pixi/math'; +import { RENDERER_TYPE } from '@pixi/constants'; +import { settings } from '@pixi/settings'; +import { Container } from '@pixi/display'; +import RenderTexture from './renderTexture/RenderTexture'; +import EventEmitter from 'eventemitter3'; + +const tempMatrix = new Matrix(); + +/** + * 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 EventEmitter + * @memberof PIXI + */ +export default class AbstractRenderer extends EventEmitter +{ + // eslint-disable-next-line valid-jsdoc + /** + * @param {string} system - The name of the system this renderer is for. + * @param {object} [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.transparent=false] - If the render view is transparent, default false + * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false + * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. The + * resolution of the renderer retina would be 2. + * @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 {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when rendering, + * stopping pixel interpolation. + */ + constructor(system, options, arg2, arg3) + { + super(); + + // Support for constructor(system, screenWidth, screenHeight, options) + if (typeof options === 'number') + { + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + }, arg3); + } + + // 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 = RENDERER_TYPE.UNKNOWN; + + /** + * Measurements of the screen. (0, 0, screenWidth, screenHeight) + * + * Its safe to use as filterArea or hitArea for 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 1 + */ + this.resolution = options.resolution || settings.RESOLUTION; + + /** + * Whether the render view is transparent + * + * @member {boolean} + */ + this.transparent = options.transparent; + + /** + * Whether css dimensions of canvas view should be resized to screen dimensions automatically + * + * @member {boolean} + */ + this.autoResize = options.autoResize || false; + + /** + * Tracks the blend modes useful for this renderer. + * + * @member {object} + */ + this.blendModes = null; + + /** + * 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; + + /** + * If true PixiJS will Math.floor() x/y values when rendering, stopping pixel interpolation. + * Handy for crisp pixel art and speed on legacy devices. + * + * @member {boolean} + */ + this.roundPixels = options.roundPixels; + + /** + * The background color as a number. + * + * @member {number} + * @private + */ + this._backgroundColor = 0x000000; + + /** + * The background color as an [R, G, B] array. + * + * @member {number[]} + * @private + */ + this._backgroundColorRgba = [0, 0, 0, 0]; + + /** + * The background color as a string. + * + * @member {string} + * @private + */ + this._backgroundColorString = '#000000'; + + this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter + + /** + * This temporary display object used as the parent of the currently being rendered item + * + * @member {PIXI.DisplayObject} + * @private + */ + this._tempDisplayObjectParent = new Container(); + + /** + * The last root object that the renderer tried to render. + * + * @member {PIXI.DisplayObject} + * @private + */ + this._lastObjectRendered = this._tempDisplayObjectParent; + + /** + * Collection of plugins. + * @readonly + * @member {object} + */ + this.plugins = {}; + } + + /** + * Initialize the plugins. + * + * @protected + * @param {object} staticMap - The dictionary of staticly saved plugins. + */ + initPlugins(staticMap) + { + 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() + { + return this.view.width; + } + + /** + * Same as view.height, actual number of pixels in the canvas by vertical + * + * @member {number} + * @readonly + * @default 600 + */ + get height() + { + return this.view.height; + } + + /** + * Resizes the screen and canvas to the specified width and height + * Canvas dimensions are multiplied by resolution + * + * @param {number} screenWidth - the new width of the screen + * @param {number} screenHeight - the new height of the screen + */ + resize(screenWidth, screenHeight) + { + this.screen.width = screenWidth; + this.screen.height = screenHeight; + + this.view.width = screenWidth * this.resolution; + this.view.height = screenHeight * this.resolution; + + if (this.autoResize) + { + this.view.style.width = `${screenWidth}px`; + this.view.style.height = `${screenHeight}px`; + } + } + + /** + * 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 {PIXI.DisplayObject} displayObject - The displayObject the object will be generated from + * @param {number} scaleMode - Should be one of the scaleMode consts + * @param {number} resolution - The resolution / device pixel ratio of the texture being generated + * @param {PIXI.Rectangle} [region] - The region of the displayObject, that shall be rendered, + * if no region is specified, defaults to the local bounds of the displayObject. + * @return {PIXI.Texture} a texture of the graphics object + */ + generateTexture(displayObject, scaleMode, resolution, region) + { + region = region || displayObject.getLocalBounds(); + + // 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(region.width | 0, region.height | 0, scaleMode, resolution); + + tempMatrix.tx = -region.x; + tempMatrix.ty = -region.y; + + this.render(displayObject, renderTexture, false, tempMatrix, true); + + return renderTexture; + } + + /** + * Removes everything from the renderer and optionally removes the Canvas DOM element. + * + * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. + */ + destroy(removeView) + { + 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); + } + + this.plugins = null; + + this.type = RENDERER_TYPE.UNKNOWN; + + this.view = null; + + this.screen = null; + + this.resolution = 0; + + this.transparent = false; + + this.autoResize = false; + + this.blendModes = null; + + this.options = null; + + this.preserveDrawingBuffer = false; + this.clearBeforeRender = false; + + this.roundPixels = false; + + this._backgroundColor = 0; + this._backgroundColorRgba = null; + this._backgroundColorString = null; + + this._tempDisplayObjectParent = null; + this._lastObjectRendered = null; + } + + /** + * The background color to fill if not transparent + * + * @member {number} + */ + get backgroundColor() + { + return this._backgroundColor; + } + + set backgroundColor(value) // eslint-disable-line require-jsdoc + { + this._backgroundColor = value; + this._backgroundColorString = hex2string(value); + hex2rgb(value, this._backgroundColorRgba); + } +} diff --git a/packages/core/src/Renderer.js b/packages/core/src/Renderer.js new file mode 100644 index 0000000..2227e78 --- /dev/null +++ b/packages/core/src/Renderer.js @@ -0,0 +1,338 @@ +import AbstractRenderer from './AbstractRenderer'; +import { sayHello } from '@pixi/utils'; +import MaskSystem from './mask/MaskSystem'; +import StencilSystem from './mask/StencilSystem'; +import FilterSystem from './filters/FilterSystem'; +import FramebufferSystem from './framebuffer/FramebufferSystem'; +import RenderTextureSystem from './renderTexture/RenderTextureSystem'; +import TextureSystem from './textures/TextureSystem'; +import ProjectionSystem from './projection/ProjectionSystem'; +import StateSystem from './state/StateSystem'; +import GeometrySystem from './geometry/GeometrySystem'; +import ShaderSystem from './shader/ShaderSystem'; +import ContextSystem from './context/ContextSystem'; +import BatchSystem from './batch/BatchSystem'; +import TextureGCSystem from './textures/TextureGCSystem'; +import { RENDERER_TYPE } from '@pixi/constants'; +import UniformGroup from './shader/UniformGroup'; +import { Matrix } from '@pixi/math'; +import Runner from 'mini-runner'; + +/** + * 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 Render works by automatically managing webGLBatchs. + * 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 :) + * + * @class + * @memberof PIXI + * @extends PIXI.AbstractRenderer + */ +export default class Renderer extends AbstractRenderer +{ + // eslint-disable-next-line valid-jsdoc + /** + * + * @param {object} [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.transparent=false] - If the render view is transparent, default false + * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false + * @param {boolean} [options.antialias=false] - sets antialias. If not available natively then FXAA + * antialiasing is used + * @param {boolean} [options.forceFXAA=false] - forces FXAA antialiasing to be used over native. + * FXAA is faster, but may not always look as great + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. + * The resolution of the renderer retina would be 2. + * @param {boolean} [options.clearBeforeRender=true] - This sets if the renderer will clear + * the canvas or not before the new render pass. If you wish to set this to false, you *must* set + * preserveDrawingBuffer to `true`. + * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, + * enable this if you need to call toDataUrl on the webgl context. + * @param {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when + * rendering, stopping pixel interpolation. + * @param {number} [options.backgroundColor=0x000000] - The background color of the rendered area + * (shown if not transparent). + * @param {boolean} [options.legacy=false] - If true PixiJS will aim to ensure compatibility + * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. + * @param {string} [options.powerPreference] - Parameter passed to webgl context, set to "high-performance" + * for devices with dual graphics card + */ + constructor(options, arg2, arg3) + { + super('WebGL', options, arg2, arg3); + + /** + * The type of this renderer as a standardised const + * + * @member {number} + * @see PIXI.RENDERER_TYPE + */ + this.type = RENDERER_TYPE.WEBGL; + + // this will be set by the contextSystem (this.context) + this.gl = null; + this.CONTEXT_UID = 0; + this.legacy = !!options.legacy; + + // TODO legacy! + + // runners! + this.runners = { + destroy: new Runner('destroy'), + contextChange: new Runner('contextChange', 1), + reset: new Runner('reset'), + update: new Runner('update'), + postrender: new Runner('postrender'), + prerender: new Runner('prerender'), + resize: new Runner('resize', 2), + }; + + 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(GeometrySystem, 'geometry') + .addSystem(FramebufferSystem, 'framebuffer') + .addSystem(StencilSystem, 'stencil') + .addSystem(ProjectionSystem, 'projection') + .addSystem(TextureGCSystem, 'textureGC') + .addSystem(FilterSystem, 'filter') + .addSystem(RenderTextureSystem, 'renderTexture') + .addSystem(BatchSystem, 'batch'); + + this.initPlugins(Renderer.__plugins); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ + if (options.context) + { + this.context.initFromContext(options.context); + } + else + { + this.context.initFromOptions({ + alpha: this.transparent, + antialias: options.antialias, + premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', + stencil: true, + 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); + } + + /** + * Add a new system to the renderer. + * @param {class} ClassRef - Class reference + * @param {string} [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. + * @return {PIXI.Renderer} Return instance of renderer + */ + addSystem(ClassRef, name) + { + if (!name) + { + name = ClassRef.name; + } + + // TODO - read name from class.name.. + + /* + if(name.includes('System')) + { + name = name.replace('System', ''); + name = name.charAt(0).toLowerCase() + name.slice(1); + } + */ + + const system = new ClassRef(this); + + if (this[name]) + { + throw new Error(`Whoops! ${name} is already a manger`); + } + + this[name] = system; + + for (const i in this.runners) + { + this.runners[i].add(system); + } + + return this; + + /** + * 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. + */ + } + + /** + * Renders the object to its webGL view + * + * @param {PIXI.DisplayObject} displayObject - the object to be rendered + * @param {PIXI.RenderTexture} renderTexture - The render texture to render to. + * @param {boolean} [clear] - Should the canvas be cleared before the new render + * @param {PIXI.Transform} [transform] - A transform to apply to the render texture before rendering. + * @param {boolean} [skipUpdateTransform] - Should we skip the update transform pass? + */ + render(displayObject, renderTexture, clear, transform, skipUpdateTransform) + { + // can be handy to know! + this.renderingToScreen = !renderTexture; + + this.runners.prerender.run(); + this.emit('prerender'); + + // 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.parent; + + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = 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.run(); + + this.emit('postrender'); + } + + /** + * Resizes the webGL view to the specified width and height. + * + * @param {number} screenWidth - the new width of the screen + * @param {number} screenHeight - the new height of the screen + */ + resize(screenWidth, screenHeight) + { + AbstractRenderer.prototype.resize.call(this, screenWidth, screenHeight); + + this.runners.resize.run(screenWidth, screenHeight); + } + + /** + * Resets the WebGL state so you can render things however you fancy! + * + * @return {PIXI.Renderer} Returns itself. + */ + reset() + { + this.runners.reset.run(); + + return this; + } + + clear() + { + this.framebuffer.bind(); + this.framebuffer.clear(); + } + + /** + * Removes everything from the renderer (event listeners, spritebatch, etc...) + * + * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. + * See: https://github.com/pixijs/pixi.js/issues/2233 + */ + destroy(removeView) + { + this.runners.destroy.run(); + + // call base destroy + super.destroy(removeView); + + // TODO nullify all the managers.. + this.gl = null; + } + + /** + * 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 + * builds and excluding plugins. + * @name PIXI.Renderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. + */ + + /** + * Adds a plugin to the renderer. + * + * @method + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + static registerPlugin(pluginName, ctor) + { + Renderer.__plugins = Renderer.__plugins || {}; + Renderer.__plugins[pluginName] = ctor; + } +} diff --git a/packages/core/src/System.js b/packages/core/src/System.js new file mode 100644 index 0000000..05a10c8 --- /dev/null +++ b/packages/core/src/System.js @@ -0,0 +1,42 @@ +/** + * System is a base class used for extending systems used by the {@link PIXI.Renderer} + * @see PIXI.Renderer#addSystem + * @class + * @memberof PIXI + */ +export default class System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + /** + * The renderer this manager works for. + * + * @member {PIXI.Renderer} + */ + this.renderer = renderer; + + this.renderer.runners.contextChange.add(this); + } + + /** + * Generic method called when there is a WebGL context change. + * + */ + contextChange() + { + // do some codes init! + } + + /** + * Generic destroy methods to be overridden by the subclass + * + */ + destroy() + { + this.renderer.runners.contextChange.remove(this); + this.renderer = null; + } +} diff --git a/packages/core/src/batch/BatchSystem.js b/packages/core/src/batch/BatchSystem.js new file mode 100644 index 0000000..b04c249 --- /dev/null +++ b/packages/core/src/batch/BatchSystem.js @@ -0,0 +1,68 @@ +import System from '../System'; +import ObjectRenderer from './ObjectRenderer'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class BatchSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + super(renderer); + + /** + * An empty renderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.emptyRenderer = new ObjectRenderer(renderer); + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.currentRenderer = this.emptyRenderer; + } + + /** + * Changes the current renderer to the one given in parameter + * + * @param {PIXI.ObjectRenderer} objectRenderer - The object renderer to use. + */ + setObjectRenderer(objectRenderer) + { + if (this.currentRenderer === objectRenderer) + { + return; + } + + this.currentRenderer.stop(); + this.currentRenderer = objectRenderer; + + this.renderer.state.setState(objectRenderer.state); + + this.currentRenderer.start(); + } + + /** + * This should be called if you wish to do some custom rendering + * It will basically render anything that may be batched up such as sprites + * + */ + flush() + { + this.setObjectRenderer(this.emptyRenderer); + } + + reset() + { + this.setObjectRenderer(this.emptyRenderer); + } +} diff --git a/packages/core/src/batch/ObjectRenderer.js b/packages/core/src/batch/ObjectRenderer.js new file mode 100644 index 0000000..0c851a9 --- /dev/null +++ b/packages/core/src/batch/ObjectRenderer.js @@ -0,0 +1,48 @@ +import System from '../System'; + +/** + * Base for a common object renderer that can be used as a system renderer plugin. + * + * @class + * @extends PIXI.System + * @memberof PIXI + */ +export default class ObjectRenderer extends System +{ + /** + * Starts the renderer and sets the shader + * + */ + start() + { + // set the shader.. + } + + /** + * Stops the renderer + * + */ + stop() + { + this.flush(); + } + + /** + * Stub method for rendering content and emptying the current batch. + * + */ + flush() + { + // flush! + } + + /** + * Renders an object + * + * @param {PIXI.DisplayObject} object - The object to render. + */ + render(object) // eslint-disable-line no-unused-vars + { + // render the object + } +} diff --git a/packages/core/src/context/ContextSystem.js b/packages/core/src/context/ContextSystem.js new file mode 100644 index 0000000..301aab4 --- /dev/null +++ b/packages/core/src/context/ContextSystem.js @@ -0,0 +1,186 @@ +import System from '../System'; +import { settings } from '@pixi/settings'; + +let CONTEXT_UID = 0; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class ContextSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.webGLVersion = 1; + + this.handleContextLost = this.handleContextLost.bind(this); + this.handleContextRestored = this.handleContextRestored.bind(this); + + this.extensions = {}; + + renderer.view.addEventListener('webglcontextlost', this.handleContextLost, false); + renderer.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + } + + get isLost() + { + return (!this.gl || this.gl.isContextLost()); + } + + contextChange(gl) + { + this.gl = gl; + + // restore a context if it was previously lost + if (gl.isContextLost() && gl.getExtension('WEBGL_lose_context')) + { + gl.getExtension('WEBGL_lose_context').restoreContext(); + } + } + + initFromContext(gl) + { + this.gl = gl; + this.validateContext(gl); + this.renderer.gl = gl; + this.renderer.CONTEXT_UID = CONTEXT_UID++; + this.renderer.runners.contextChange.run(gl); + } + + initFromOptions(options) + { + const gl = this.createContext(this.renderer.view, options); + + this.initFromContext(gl); + } + + /** + * Helper class to create a webGL Context + * + * @class + * @memberof PIXI.glCore + * @param canvas {HTMLCanvasElement} the canvas element that we will get the context from + * @param options {Object} An options object that gets passed in to the canvas element containing the context attributes, + * see https://developer.mozilla.org/en/docs/Web/API/HTMLCanvasElement/getContext for the options available + * @return {WebGLRenderingContext} the WebGL context + */ + createContext(canvas, options) + { + let gl; + + if (settings.PREFER_WEBGL_2) + { + gl = canvas.getContext('webgl2', options); + } + + if (gl) + { + this.webGLVersion = 2; + } + else + { + this.webGLVersion = 1; + + gl = canvas.getContext('webgl', options) + || canvas.getContext('experimental-webgl', options); + + if (!gl) + { + // fail, not able to get a context + throw new Error('This browser does not support webGL. Try using the canvas renderer'); + } + } + + this.gl = gl; + + this.getExtensions(); + + return gl; + } + + getExtensions() + { + // time to set up default etensions that pixi uses.. + const gl = this.gl; + const extensions = this.extensions; + + if (this.webGLVersion === 1) + { + extensions.drawBuffers = gl.getExtension('WEBGL_draw_buffers'); + extensions.depthTexture = gl.getExtension('WEBKIT_WEBGL_depth_texture'); + extensions.floatTexture = gl.getExtension('OES_texture_float'); + extensions.loseContext = gl.getExtension('WEBGL_lose_context'); + + extensions.vertexArrayObject = gl.getExtension('OES_vertex_array_object') + || gl.getExtension('MOZ_OES_vertex_array_object') + || gl.getExtension('WEBKIT_OES_vertex_array_object'); + } + + // we don't use any specific WebGL 2 ones yet! + } + + /** + * Handles a lost webgl context + * + * @private + * @param {WebGLContextEvent} event - The context lost event. + */ + handleContextLost(event) + { + event.preventDefault(); + } + + /** + * Handles a restored webgl context + * + * @private + */ + handleContextRestored() + { + this.renderer.runners.contextChange.run(this.gl); + } + + destroy() + { + const view = this.renderer.view; + + // remove listeners + view.removeEventListener('webglcontextlost', this.handleContextLost); + view.removeEventListener('webglcontextrestored', this.handleContextRestored); + + this.gl.useProgram(null); + + if (this.extensions.loseContext) + { + this.extensions.loseContext.loseContext(); + } + } + + postrender() + { + this.gl.flush(); + } + + validateContext(gl) + { + const attributes = gl.getContextAttributes(); + + // this is going to be fairly simple for now.. but at least we have room to grow! + if (!attributes.stencil) + { + /* eslint-disable max-len */ + + /* eslint-disable no-console */ + console.warn('Provided WebGL context does not have a stencil buffer, masks may not render correctly'); + /* eslint-enable no-console */ + + /* eslint-enable max-len */ + } + } +} diff --git a/packages/core/src/filters/Filter.js b/packages/core/src/filters/Filter.js new file mode 100644 index 0000000..9d8cad8 --- /dev/null +++ b/packages/core/src/filters/Filter.js @@ -0,0 +1,140 @@ +import Shader from '../shader/Shader'; +import Program from '../shader/Program'; +import State from '../state/State'; +import { settings } from '@pixi/settings'; +// import extractUniformsFromSrc from './extractUniformsFromSrc'; +import defaultVertex from './defaultFilter.vert'; +import defaultFragment from './defaultFilter.frag'; + +// let math = require('../../math'); +/** + * @class + * @memberof PIXI + * @extends PIXI.Shader + */ +export default class Filter extends Shader +{ + /** + * @param {string} [vertexSrc] - The source of the vertex shader. + * @param {string} [fragmentSrc] - The source of the fragment shader. + * @param {object} [uniforms] - Custom uniforms to use to augment the built-in ones. + */ + constructor(vertexSrc, fragmentSrc, uniforms) + { + const program = Program.from(vertexSrc, fragmentSrc); + + super(program, uniforms); + + /** + * The padding of the filter. Some filters require extra space to breath such as a blur. + * Increasing this will add extra width and height to the bounds of the object that the + * filter is applied to. + * + * @member {number} + */ + this.padding = 0; + + /** + * The resolution of the filter. Setting this to be lower will lower the quality but + * increase the performance of the filter. + * + * @member {number} + */ + this.resolution = settings.RESOLUTION; + + /** + * If enabled is true the filter is applied, if false it will not. + * + * @member {boolean} + */ + this.enabled = true; + + /** + * If enabled, PixiJS will fit the filter area into boundaries for better performance. + * Switch it off if it does not work for specific shader. + * + * @member {boolean} + */ + this.autoFit = true; + + /** + * Legacy filters use position and uvs from attributes + * @member {boolean} + * @readonly + */ + this.legacy = !!this.program.attributeData.aTextureCoord; + + /** + * the webGL state the filter requires to render + * @member {PIXI.State} + */ + this.state = new State(); + } + + /** + * Applies the filter + * + * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + * @param {boolean} clear - Should the output be cleared before rendering to it + * @param {object} [currentState] - It's current state of filter. + * There are some useful properties in the currentState : + * target, filters, sourceFrame, destinationFrame, renderTarget, resolution + */ + apply(filterManager, input, output, clear, currentState, derp) // eslint-disable-line no-unused-vars + { + // do as you please! + + filterManager.applyFilter(this, input, output, clear, currentState, derp); + + // or just do a regular render.. + } + + /** + * Sets the blendmode of the filter + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL + */ + get blendMode() + { + return this.state.blendMode; + } + + set blendMode(value) // eslint-disable-line require-jsdoc + { + this.state.blendMode = value; + } + + /** + * The default vertex shader source + * + * @static + * @constant + */ + static get defaultVertexSrc() + { + return defaultVertex; + } + + /** + * The default fragment shader source + * + * @static + * @constant + */ + static get defaultFragmentSrc() + { + return defaultFragment; + } +} + +/** + * Used for caching shader IDs + * + * @static + * @private + */ +Filter.SOURCE_KEY_MAP = {}; + diff --git a/packages/core/src/filters/FilterSystem.js b/packages/core/src/filters/FilterSystem.js new file mode 100644 index 0000000..3174318 --- /dev/null +++ b/packages/core/src/filters/FilterSystem.js @@ -0,0 +1,431 @@ +import System from '../System'; + +import RenderTexture from '../renderTexture/RenderTexture'; +import Quad from '../utils/Quad'; +import QuadUv from '../utils/QuadUv'; +import { Rectangle } from '@pixi/math'; +import * as filterTransforms from './filterTransforms'; +import bitTwiddle from 'bit-twiddle'; +import UniformGroup from '../shader/UniformGroup'; +import { DRAW_MODES } from '@pixi/constants'; + +// +/** + * @ignore + * @class + */ +class FilterState +{ + /** + * + */ + constructor() + { + this.renderTexture = null; + this.sourceFrame = new Rectangle(); + this.destinationFrame = new Rectangle(); + this.filters = []; + this.target = null; + this.legacy = false; + this.resolution = 1; + } +} + +/** + * @class + * @memberof PIXI.systems + * @extends PIXI.System + */ +export default class FilterSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + /** + * stores a bunch of PO2 textures used for filtering + * @type {Object} + */ + this.texturePool = {}; + + /** + * a pool for storing filter states, save us creating new ones each tick + * @type {Array} + */ + this.statePool = []; + + /** + * A very simple geometry used when drawing a filter effect to the screen + * @type {Quad} + */ + this.quad = new Quad(); + + this.quadUv = new QuadUv(); + + /** + * Temporary rect for maths + * @type {PIXI.Rectangle} + */ + this.tempRect = new Rectangle(); + + this.activeState = {}; + + /** + * this uniform group is attached to filter uniforms when used + * @type {UniformGroup} + */ + this.globalUniforms = new UniformGroup({ + sourceFrame: this.tempRect, + destinationFrame: this.tempRect, + + // legacy variables + filterArea: new Float32Array(4), + filterClamp: new Float32Array(4), + }, true); + } + + /** + * Adds a new filter to the System. + * + * @param {PIXI.DisplayObject} target - The target of the filter to render. + * @param {PIXI.Filter[]} filters - The filters to apply. + */ + push(target, filters) + { + const renderer = this.renderer; + const filterStack = this.renderer.renderTexture.defaultFilterStack; + const state = this.statePool.pop() || new FilterState(); + + let resolution = filters[0].resolution; + let padding = filters[0].padding; + let autoFit = filters[0].autoFit; + let legacy = filters[0].legacy; + + for (let i = 1; i < filters.length; i++) + { + const filter = filters[i]; + + // lets use the lowest resolution.. + resolution = Math.min(resolution, filter.resolution); + // and the largest amount of padding! + padding = Math.max(padding, filter.padding); + // only auto fit if all filters are autofit + autoFit = autoFit || filter.autoFit; + + legacy = legacy || filter.legacy; + } + + filterStack.push(state); + + state.resolution = resolution; + + state.legacy = legacy; + + // round to whole number based on resolution + // TODO move that to the shader too? + state.sourceFrame = target.filterArea || target.getBounds(true); + + state.sourceFrame.pad(padding); + + if (autoFit) + { + state.sourceFrame.fit(this.renderer.renderTexture.destinationFrame); + } + + state.sourceFrame.round(resolution); + + state.renderTexture = this.getPotFilterTexture(state.sourceFrame.width, state.sourceFrame.height, resolution); + state.filters = filters; + + state.destinationFrame.width = state.renderTexture.width; + state.destinationFrame.height = state.renderTexture.height; + + state.renderTexture.filterFrame = state.sourceFrame; + renderer.renderTexture.bind(state.renderTexture, state.sourceFrame);// /, state.destinationFrame); + renderer.renderTexture.clear(); + } + + /** + * Pops off the filter and applies it. + * + */ + pop() + { + const renderer = this.renderer; + const filterStack = renderer.renderTexture.defaultFilterStack; + const state = filterStack.pop(); + const filters = state.filters; + + this.activeState = state; + + const globalUniforms = this.globalUniforms.uniforms; + + globalUniforms.sourceFrame = state.sourceFrame; + globalUniforms.destinationFrame = state.destinationFrame; + globalUniforms.resolution = state.resolution; + + // only update the rect if its legacy.. + if (state.legacy) + { + const filterArea = globalUniforms.filterArea; + const filterClamp = globalUniforms.filterClamp; + + filterArea[0] = state.destinationFrame.width; + filterArea[1] = state.destinationFrame.height; + filterArea[2] = state.sourceFrame.x; + filterArea[3] = state.sourceFrame.y; + + filterClamp[0] = 0.5 / state.resolution / state.destinationFrame.width; + filterClamp[1] = 0.5 / state.resolution / state.destinationFrame.height; + filterClamp[2] = (state.sourceFrame.width - 0.5) / state.resolution / state.destinationFrame.width; + filterClamp[3] = (state.sourceFrame.height - 0.5) / state.resolution / state.destinationFrame.height; + } + + this.globalUniforms.update(); + + const lastState = filterStack[filterStack.length - 1]; + + if (filters.length === 1) + { + filters[0].apply(this, state.renderTexture, lastState.renderTexture, false, state); + + this.returnFilterTexture(state.renderTexture); + } + else + { + let flip = state.renderTexture; + let flop = this.getPotFilterTexture( + flip.width, + flip.height, + state.resolution + ); + + flop.filterFrame = flip.filterFrame; + + let i = 0; + + for (i = 0; i < filters.length - 1; ++i) + { + filters[i].apply(this, flip, flop, true, state); + + const t = flip; + + flip = flop; + flop = t; + } + + filters[i].apply(this, flip, lastState.renderTexture, false, state); + + this.returnFilterTexture(flip); + this.returnFilterTexture(flop); + } + + this.statePool.push(state); + } + + /** + * Draws a filter. + * + * @param {PIXI.Filter} filter - The filter to draw. + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + * @param {boolean} clear - Should the output be cleared before rendering to it + */ + applyFilter(filter, input, output, clear) + { + const renderer = this.renderer; + + renderer.renderTexture.bind(output, output ? output.filterFrame : null); + + if (clear) + { + // gl.disable(gl.SCISSOR_TEST); + renderer.renderTexture.clear(); + // gl.enable(gl.SCISSOR_TEST); + } + + // set the uniforms.. + filter.uniforms.uSampler = input; + filter.uniforms.filterGlobals = this.globalUniforms; + + // TODO make it so that the order of this does not matter.. + // because it does at the moment cos of global uniforms. + // they need to get resynced + + renderer.state.setState(filter.state); + renderer.shader.bind(filter); + + if (filter.legacy) + { + this.quadUv.map(input._frame, input.filterFrame); + + renderer.geometry.bind(this.quadUv); + renderer.geometry.draw(DRAW_MODES.TRIANGLES); + } + else + { + renderer.geometry.bind(this.quad); + renderer.geometry.draw(DRAW_MODES.TRIANGLE_STRIP); + } + } + + /** + * Calculates the mapped matrix. + * + * TODO playing around here.. this is temporary - (will end up in the shader) + * this returns a matrix that will normalise map filter cords in the filter to screen space + * + * @param {PIXI.Matrix} outputMatrix - the matrix to output to. + * @return {PIXI.Matrix} The mapped matrix. + */ + calculateScreenSpaceMatrix(outputMatrix) + { + const currentState = this.activeState; + + return filterTransforms.calculateScreenSpaceMatrix( + outputMatrix, + currentState.sourceFrame, + currentState.destinationFrame + ); + } + + /** + * This will map the filter coord so that a texture can be used based on the transform of a sprite + * + * @param {PIXI.Matrix} outputMatrix - The matrix to output to. + * @param {PIXI.Sprite} sprite - The sprite to map to. + * @return {PIXI.Matrix} The mapped matrix. + */ + calculateSpriteMatrix(outputMatrix, sprite) + { + const currentState = this.activeState; + + return filterTransforms.calculateSpriteMatrix( + outputMatrix, + currentState.sourceFrame, + currentState.destinationFrame, + sprite + ); + } + + /** + * Destroys this Filter System. + * + * @param {boolean} [contextLost=false] context was lost, do not free shaders + * + */ + destroy(contextLost = false) + { + if (!contextLost) + { + this.emptyPool(); + } + else + { + this.texturePool = {}; + } + } + + /** + * Gets a Power-of-Two render texture. + * + * TODO move to a seperate class could be on renderer? + * also - could cause issue with multiple contexts? + * + * @private + * @param {WebGLRenderingContext} gl - The webgl rendering context + * @param {number} minWidth - The minimum width of the render target. + * @param {number} minHeight - The minimum height of the render target. + * @param {number} resolution - The resolution of the render target. + * @return {PIXI.RenderTarget} The new render target. + */ + getPotFilterTexture(minWidth, minHeight, resolution) + { + minWidth = bitTwiddle.nextPow2(minWidth); + minHeight = bitTwiddle.nextPow2(minHeight); + resolution = resolution || 1; + + const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + + if (!this.texturePool[key]) + { + this.texturePool[key] = []; + } + + let renderTexture = this.texturePool[key].pop(); + + if (!renderTexture) + { + // temporary bypass cache.. + // internally - this will cause a texture to be bound.. + renderTexture = RenderTexture.create({ + width: minWidth, + height: minHeight, + resolution, + }); + } + + return renderTexture; + } + + /** + * Gets extra render texture to use inside current filter + * + * @param {number} resolution resolution of the renderTexture + * @returns {PIXI.RenderTexture} + */ + getFilterTexture(resolution) + { + const rt = this.activeState.renderTexture; + + const filterTexture = this.getPotFilterTexture(rt.width, rt.height, resolution || rt.baseTexture.resolution); + + filterTexture.filterFrame = rt.filterFrame; + + return filterTexture; + } + + /** + * Frees a render texture back into the pool. + * + * @param {PIXI.RenderTarget} renderTexture - The renderTarget to free + */ + returnFilterTexture(renderTexture) + { + renderTexture.filterFrame = null; + + const base = renderTexture.baseTexture; + + const minWidth = base.width; + const minHeight = base.height; + + const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + + this.texturePool[key].push(renderTexture); + } + + /** + * Empties the texture pool. + * + */ + emptyPool() + { + for (const i in this.texturePool) + { + const textures = this.texturePool[i]; + + if (textures) + { + for (let j = 0; j < textures.length; j++) + { + textures[j].destroy(true); + } + } + } + + this.texturePool = {}; + } +} diff --git a/packages/core/src/filters/defaultFilter.frag b/packages/core/src/filters/defaultFilter.frag new file mode 100644 index 0000000..a14c1be --- /dev/null +++ b/packages/core/src/filters/defaultFilter.frag @@ -0,0 +1,22 @@ +varying vec2 vTextureCoord; +varying vec2 vFilterCoord; + +uniform sampler2D uSampler; +uniform sampler2D filterSampler; + +void main(void){ + vec4 masky = texture2D(filterSampler, vFilterCoord); + vec4 sample = texture2D(uSampler, vTextureCoord); + vec4 color; + if(mod(vFilterCoord.x, 1.0) > 0.5) + { + color = vec4(1.0, 0.0, 0.0, 1.0); + } + else + { + color = vec4(0.0, 1.0, 0.0, 1.0); + } + // gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0); + gl_FragColor = mix(sample, masky, 0.5); + gl_FragColor *= sample.a; +} diff --git a/packages/core/src/filters/defaultFilter.vert b/packages/core/src/filters/defaultFilter.vert new file mode 100644 index 0000000..e1febca --- /dev/null +++ b/packages/core/src/filters/defaultFilter.vert @@ -0,0 +1,14 @@ +attribute vec2 aVertexPosition; +attribute vec2 aTextureCoord; + +uniform mat3 projectionMatrix; +uniform mat3 filterMatrix; + +varying vec2 vTextureCoord; +varying vec2 vFilterCoord; + +void main(void){ + gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); + vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy; + vTextureCoord = aTextureCoord ; +} diff --git a/packages/core/src/filters/filterTransforms.js b/packages/core/src/filters/filterTransforms.js new file mode 100644 index 0000000..87a3cd6 --- /dev/null +++ b/packages/core/src/filters/filterTransforms.js @@ -0,0 +1,50 @@ +import { Matrix } from '@pixi/math'; + +/** + * Calculates the mapped matrix + * @param filterArea {Rectangle} The filter area + * @param sprite {Sprite} the target sprite + * @param outputMatrix {Matrix} @alvin + * @private + */ +// this returns a matrix that will normalise map filter cords in the filter to screen space +export function calculateScreenSpaceMatrix(outputMatrix, filterArea, textureSize) +{ + // TODO unwrap? + const mappedMatrix = outputMatrix.identity(); + + mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); + + mappedMatrix.scale(textureSize.width, textureSize.height); + + return mappedMatrix; +} + +export function calculateNormalizedScreenSpaceMatrix(outputMatrix, filterArea, textureSize) +{ + const mappedMatrix = outputMatrix.identity(); + + mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); + + const translateScaleX = (textureSize.width / filterArea.width); + const translateScaleY = (textureSize.height / filterArea.height); + + mappedMatrix.scale(translateScaleX, translateScaleY); + + return mappedMatrix; +} + +// this will map the filter coord so that a texture can be used based on the transform of a sprite +export function calculateSpriteMatrix(outputMatrix, filterArea, textureSize, sprite) +{ + const orig = sprite._texture.orig; + const mappedMatrix = outputMatrix.set(textureSize.width, 0, 0, textureSize.height, filterArea.x, filterArea.y); + const worldTransform = sprite.worldTransform.copyTo(Matrix.TEMP_MATRIX); + + worldTransform.invert(); + mappedMatrix.prepend(worldTransform); + mappedMatrix.scale(1.0 / orig.width, 1.0 / orig.height); + mappedMatrix.translate(sprite.anchor.x, sprite.anchor.y); + + return mappedMatrix; +} diff --git a/packages/core/src/filters/spriteMask/SpriteMaskFilter.js b/packages/core/src/filters/spriteMask/SpriteMaskFilter.js new file mode 100644 index 0000000..91697b7 --- /dev/null +++ b/packages/core/src/filters/spriteMask/SpriteMaskFilter.js @@ -0,0 +1,63 @@ +import Filter from '../Filter'; +import { Matrix } from '@pixi/math'; +import vertex from './spriteMaskFilter.vert'; +import fragment from './spriteMaskFilter.frag'; +import { default as TextureMatrix } from '../../textures/TextureMatrix'; + +/** + * The SpriteMaskFilter class + * + * @class + * @extends PIXI.Filter + * @memberof PIXI + */ +export default class SpriteMaskFilter extends Filter +{ + /** + * @param {PIXI.Sprite} sprite - the target sprite + */ + constructor(sprite) + { + const maskMatrix = new Matrix(); + + super(vertex, fragment); + + sprite.renderable = false; + + this.maskSprite = sprite; + this.maskMatrix = maskMatrix; + } + + /** + * Applies the filter + * + * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + */ + apply(filterManager, input, output) + { + const maskSprite = this.maskSprite; + const tex = this.maskSprite.texture; + + if (!tex.valid) + { + return; + } + if (!tex.transform) + { + // margin = 0.0, let it bleed a bit, shader code becomes easier + // assuming that atlas textures were made with 1-pixel padding + tex.transform = new TextureMatrix(tex, 0.0); + } + tex.transform.update(); + + this.uniforms.mask = tex; + this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite) + .prepend(tex.transform.mapCoord); + this.uniforms.alpha = maskSprite.worldAlpha; + this.uniforms.maskClamp = tex.transform.uClampFrame; + + filterManager.applyFilter(this, input, output); + } +} diff --git a/packages/core/src/filters/spriteMask/spriteMaskFilter.frag b/packages/core/src/filters/spriteMask/spriteMaskFilter.frag new file mode 100644 index 0000000..0e4aef8 --- /dev/null +++ b/packages/core/src/filters/spriteMask/spriteMaskFilter.frag @@ -0,0 +1,23 @@ +varying vec2 vMaskCoord; +varying vec2 vTextureCoord; + +uniform sampler2D uSampler; +uniform sampler2D mask; +uniform float alpha; +uniform vec4 maskClamp; + +void main(void) +{ + float clip = step(3.5, + step(maskClamp.x, vMaskCoord.x) + + step(maskClamp.y, vMaskCoord.y) + + step(vMaskCoord.x, maskClamp.z) + + step(vMaskCoord.y, maskClamp.w)); + + vec4 original = texture2D(uSampler, vTextureCoord); + vec4 masky = texture2D(mask, vMaskCoord); + + original *= (masky.r * masky.a * alpha * clip); + + gl_FragColor = original; +} diff --git a/packages/core/src/filters/spriteMask/spriteMaskFilter.vert b/packages/core/src/filters/spriteMask/spriteMaskFilter.vert new file mode 100644 index 0000000..d23a30c --- /dev/null +++ b/packages/core/src/filters/spriteMask/spriteMaskFilter.vert @@ -0,0 +1,16 @@ +attribute vec2 aVertexPosition; +attribute vec2 aTextureCoord; + +uniform mat3 projectionMatrix; +uniform mat3 otherMatrix; + +varying vec2 vMaskCoord; +varying vec2 vTextureCoord; + +void main(void) +{ + gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); + + vTextureCoord = aTextureCoord; + vMaskCoord = ( otherMatrix * vec3( aTextureCoord, 1.0) ).xy; +} diff --git a/packages/core/src/framebuffer/FrameBuffer.js b/packages/core/src/framebuffer/FrameBuffer.js new file mode 100644 index 0000000..84e9fcc --- /dev/null +++ b/packages/core/src/framebuffer/FrameBuffer.js @@ -0,0 +1,106 @@ +import Texture from '../textures/BaseTexture'; +import { FORMATS, TYPES } from '@pixi/constants'; + +/** + * Frame buffer + * @class + * @memberof PIXI + */ +export default class FrameBuffer +{ + constructor(width, height) + { + this.width = width || 100; + this.height = height || 100; + + this.stencil = false; + this.depth = false; + + this.dirtyId = 0; + this.dirtyFormat = 0; + this.dirtySize = 0; + + this.depthTexture = null; + this.colorTextures = []; + + this.glFrameBuffers = {}; + } + + get colorTexture() + { + return this.colorTextures[0]; + } + + addColorTexture(index, texture) + { + // TODO add some validation to the texture - same width / height etc? + this.colorTextures[index || 0] = texture || new Texture(null, { scaleMode: 0, + resolution: 1, + mipmap: false, + width: this.width, + height: this.height });// || new Texture(); + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + addDepthTexture(texture) + { + /* eslint-disable max-len */ + this.depthTexture = texture || new Texture(null, { scaleMode: 0, + resolution: 1, + width: this.width, + height: this.height, + mipmap: false, + format: FORMATS.DEPTH_COMPONENT, + type: TYPES.UNSIGNED_SHORT });// UNSIGNED_SHORT; + /* eslint-disable max-len */ + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + enableDepth() + { + this.depth = true; + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + enableStencil() + { + this.stencil = true; + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + resize(width, height) + { + if (width === this.width && height === this.height) return; + + this.width = width; + this.height = height; + + this.dirtyId++; + this.dirtySize++; + + for (let i = 0; i < this.colorTextures.length; i++) + { + this.colorTextures[i].setSize(width, height); + } + + if (this.depthTexture) + { + this.depthTexture.setSize(width, height); + } + } +} diff --git a/packages/core/src/framebuffer/FramebufferSystem.js b/packages/core/src/framebuffer/FramebufferSystem.js new file mode 100644 index 0000000..2cc2437 --- /dev/null +++ b/packages/core/src/framebuffer/FramebufferSystem.js @@ -0,0 +1,246 @@ +import System from '../System'; +import { Rectangle } from '@pixi/math'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class FramebufferSystem extends System +{ + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + contextChange() + { + this.gl = this.renderer.gl; + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + this.current = null; + this.viewport = new Rectangle(); + + this.drawBufferExtension = this.renderer.context.extensions.drawBuffers; + } + + bind(framebuffer, frame) + { + const gl = this.gl; + + this.current = framebuffer; + + if (framebuffer) + { + // TODO cacheing layer! + + const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID] || this.initFramebuffer(framebuffer); + + gl.bindFramebuffer(gl.FRAMEBUFFER, fbo.framebuffer); + // makesure all textures are unbound.. + + // now check for updates... + if (fbo.dirtyId !== framebuffer.dirtyId) + { + fbo.dirtyId = framebuffer.dirtyId; + + if (fbo.dirtyFormat !== framebuffer.dirtyFormat) + { + fbo.dirtyFormat = framebuffer.dirtyFormat; + this.updateFramebuffer(framebuffer); + } + else if (fbo.dirtySize !== framebuffer.dirtySize) + { + fbo.dirtySize = framebuffer.dirtySize; + this.resizeFramebuffer(framebuffer); + } + } + + for (let i = 0; i < framebuffer.colorTextures.length; i++) + { + if (framebuffer.colorTextures[i].texturePart) + { + this.renderer.texture.unbind(framebuffer.colorTextures[i].texture); + } + else + { + this.renderer.texture.unbind(framebuffer.colorTextures[i]); + } + } + + if (framebuffer.depthTexture) + { + this.renderer.texture.unbind(framebuffer.depthTexture); + } + + if (frame) + { + this.setViewport(frame.x, frame.y, frame.width, frame.height); + } + else + { + this.setViewport(0, 0, framebuffer.width, framebuffer.height); + } + } + else + { + gl.bindFramebuffer(gl.FRAMEBUFFER, null); + + if (frame) + { + this.setViewport(frame.x, frame.y, frame.width, frame.height); + } + else + { + this.setViewport(0, 0, this.renderer.width, this.renderer.height); + } + } + } + + setViewport(x, y, width, height) + { + const v = this.viewport; + + if (v.width !== width || v.height !== height || v.x !== x || v.y !== y) + { + v.x = x; + v.y = y; + v.width = width; + v.height = height; + + this.gl.viewport(x, y, width, height); + } + } + + get size() + { + if (this.current) + { + // TODO store temp + return { x: 0, y: 0, width: this.current.width, height: this.current.height }; + } + + return { x: 0, y: 0, width: this.renderer.width, height: this.renderer.height }; + } + + clear(r, g, b, a) + { + const gl = this.gl; + + // TODO clear color can be set only one right? + gl.clearColor(r, g, b, a); + gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); + } + + // private functions... + + initFramebuffer(framebuffer) + { + const gl = this.gl; + + // TODO - make this a class? + const fbo = { + framebuffer: gl.createFramebuffer(), + stencil: null, + dirtyId: 0, + dirtyFormat: 0, + dirtySize: 0, + }; + + framebuffer.glFrameBuffers[this.CONTEXT_UID] = fbo; + + return fbo; + } + + resizeFramebuffer(framebuffer) + { + const gl = this.gl; + + if (framebuffer.stencil || framebuffer.depth) + { + gl.bindRenderbuffer(gl.RENDERBUFFER, this.stencil); + gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); + } + } + + updateFramebuffer(framebuffer) + { + const gl = this.gl; + + const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID]; + + // bind the color texture + const colorTextures = framebuffer.colorTextures; + + let count = colorTextures.length; + + if (!this.drawBufferExtension) + { + count = Math.min(count, 1); + } + + const activeTextures = []; + + for (let i = 0; i < count; i++) + { + const texture = framebuffer.colorTextures[i]; + + if (texture.texturePart) + { + this.renderer.texture.bind(texture.texture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.COLOR_ATTACHMENT0 + i, + gl.TEXTURE_CUBE_MAP_NEGATIVE_X + texture.side, + texture.texture._glTextures[this.CONTEXT_UID].texture, + 0); + } + else + { + this.renderer.texture.bind(texture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.COLOR_ATTACHMENT0 + i, + gl.TEXTURE_2D, + texture._glTextures[this.CONTEXT_UID].texture, + 0); + } + + activeTextures.push(gl.COLOR_ATTACHMENT0 + i); + } + + if (this.drawBufferExtension && activeTextures.length > 1) + { + this.drawBufferExtension.drawBuffersWEBGL(activeTextures); + } + + if (framebuffer.depthTexture) + { + const depthTextureExt = this.renderer.context.extensions.depthTexture; + + if (depthTextureExt) + { + const depthTexture = framebuffer.depthTexture; + + this.renderer.texture.bind(depthTexture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.DEPTH_ATTACHMENT, + gl.TEXTURE_2D, + depthTexture._glTextures[this.CONTEXT_UID].texture, + 0); + } + } + + if (framebuffer.stencil || framebuffer.depth) + { + fbo.stencil = gl.createRenderbuffer(); + + gl.bindRenderbuffer(gl.RENDERBUFFER, fbo.stencil); + + // TODO.. this is depth AND stencil? + gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, fbo.stencil); + gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); + // fbo.enableStencil(); + } + } +} diff --git a/packages/core/src/geometry/GLBuffer.js b/packages/core/src/geometry/GLBuffer.js new file mode 100644 index 0000000..0477e8a --- /dev/null +++ b/packages/core/src/geometry/GLBuffer.js @@ -0,0 +1,9 @@ +export default class GLBuffer +{ + constructor(buffer) + { + this.buffer = buffer; + this.updateID = -1; + this.byteLength = -1; + } +} diff --git a/packages/core/src/geometry/GeometrySystem.js b/packages/core/src/geometry/GeometrySystem.js new file mode 100644 index 0000000..959f827 --- /dev/null +++ b/packages/core/src/geometry/GeometrySystem.js @@ -0,0 +1,388 @@ +import System from '../System'; +import GLBuffer from './GLBuffer'; + +const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class GeometrySystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this._activeGeometry = null; + this._activeVao = null; + + this.hasVao = true; + this.hasInstance = true; + } + + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + contextChange() + { + const gl = this.gl = this.renderer.gl; + + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + + // webgl2 + if (!gl.createVertexArray) + { + // webgl 1! + let nativeVaoExtension = this.renderer.context.extensions.vertexArrayObject; + + if (this.renderer.legacy) + { + nativeVaoExtension = null; + } + + if (nativeVaoExtension) + { + gl.createVertexArray = () => + nativeVaoExtension.createVertexArrayOES(); + + gl.bindVertexArray = (vao) => + nativeVaoExtension.bindVertexArrayOES(vao); + + gl.deleteVertexArray = (vao) => + nativeVaoExtension.deleteVertexArrayOES(vao); + } + else + { + this.hasVao = false; + gl.createVertexArray = () => + { + // empty + }; + + gl.bindVertexArray = () => + { + // empty + }; + + gl.deleteVertexArray = () => + { + // empty + }; + } + } + + if (!gl.vertexAttribDivisor) + { + const instanceExt = gl.getExtension('ANGLE_instanced_arrays'); + + if (instanceExt) + { + gl.vertexAttribDivisor = (a, b) => + instanceExt.vertexAttribDivisorANGLE(a, b); + + gl.drawElementsInstanced = (a, b, c, d, e) => + instanceExt.drawElementsInstancedANGLE(a, b, c, d, e); + + gl.drawArraysInstanced = (a, b, c, d) => + instanceExt.drawArraysInstancedANGLE(a, b, c, d); + } + else + { + this.hasInstance = false; + } + } + } + + /** + * Binds geometry so that is can be drawn. Creating a Vao if required + * @private + * @param {PIXI.Geometry} geometry instance of geometry to bind + */ + bind(geometry, shader) + { + shader = shader || this.renderer.shader.shader; + + const gl = this.gl; + + // not sure the best way to address this.. + // currently different shaders require different VAOs for the same geometry + // Still mulling over the best way to solve this one.. + // will likely need to modify the shader attribute locations at run time! + let vaos = geometry.glVertexArrayObjects[this.CONTEXT_UID]; + + if (!vaos) + { + geometry.glVertexArrayObjects[this.CONTEXT_UID] = vaos = {}; + } + + const vao = vaos[shader.program.id] || this.initGeometryVao(geometry, shader.program); + + this._activeGeometry = geometry; + + if (this._activeVao !== vao) + { + this._activeVao = vao; + + if (this.hasVao) + { + gl.bindVertexArray(vao); + } + else + { + this.activateVao(geometry, shader.program); + } + } + + // TODO - optimise later! + // don't need to loop through if nothing changed! + // maybe look to add an 'autoupdate' to geometry? + this.updateBuffers(); + } + + reset() + { + this.unbind(); + } + + updateBuffers() + { + const geometry = this._activeGeometry; + const gl = this.gl; + + for (let i = 0; i < geometry.buffers.length; i++) + { + const buffer = geometry.buffers[i]; + + const glBuffer = buffer._glBuffers[this.CONTEXT_UID]; + + if (buffer._updateID !== glBuffer.updateID) + { + glBuffer.updateID = buffer._updateID; + + // TODO can cache this on buffer! maybe added a getter / setter? + const type = buffer.index ? gl.ELEMENT_ARRAY_BUFFER : gl.ARRAY_BUFFER; + const drawType = buffer.static ? gl.STATIC_DRAW : gl.DYNAMIC_DRAW; + + gl.bindBuffer(type, glBuffer.buffer); + + if (glBuffer.byteLength >= buffer.data.byteLength) + { + // offset is always zero for now! + gl.bufferSubData(type, 0, buffer.data); + } + else + { + gl.bufferData(type, buffer.data, drawType); + } + } + } + } + + checkCompatability(geometry, program) + { + // geometry must have at least all the attributes that the shader requires. + const geometryAttributes = geometry.attributes; + const shaderAttributes = program.attributeData; + + for (const j in shaderAttributes) + { + if (!geometryAttributes[j]) + { + throw new Error(`shader and geometry incompatible, geometry missing the "${j}" attribute`); + } + } + } + + /** + * Creates a Vao with the same structure as the geometry and stores it on the geometry. + * @private + * @param {PIXI.Geometry} geometry instance of geometry to to generate Vao for + */ + initGeometryVao(geometry, program) + { + this.checkCompatability(geometry, program); + + const gl = this.gl; + const CONTEXT_UID = this.CONTEXT_UID; + const buffers = geometry.buffers; + const attributes = geometry.attributes; + + const tempStride = {}; + const tempStart = {}; + + for (const j in buffers) + { + tempStride[j] = 0; + tempStart[j] = 0; + } + + for (const j in attributes) + { + if (!attributes[j].size && program.attributeData[j]) + { + attributes[j].size = program.attributeData[j].size; + } + + tempStride[attributes[j].buffer] += attributes[j].size * byteSizeMap[attributes[j].type]; + } + + for (const j in attributes) + { + const attribute = attributes[j]; + const attribSize = attribute.size; + + if (attribute.stride === undefined) + { + if (tempStride[attribute.buffer] === attribSize * byteSizeMap[attribute.type]) + { + attribute.stride = 0; + } + else + { + attribute.stride = tempStride[attribute.buffer]; + } + } + + if (attribute.start === undefined) + { + attribute.start = tempStart[attribute.buffer]; + + tempStart[attribute.buffer] += attribSize * byteSizeMap[attribute.type]; + } + } + + // first update - and create the buffers! + // only create a gl buffer if it actually gets + for (let i = 0; i < buffers.length; i++) + { + const buffer = buffers[i]; + + if (!buffer._glBuffers[CONTEXT_UID]) + { + buffer._glBuffers[CONTEXT_UID] = new GLBuffer(gl.createBuffer()); + } + } + + // TODO - maybe make this a data object? + // lets wait to see if we need to first! + const vao = gl.createVertexArray(); + + gl.bindVertexArray(vao); + + this.activateVao(geometry, program); + + gl.bindVertexArray(null); + + // add it to the cache! + geometry.glVertexArrayObjects[this.CONTEXT_UID][program.id] = vao; + + return vao; + } + + activateVao(geometry, program) + { + const gl = this.gl; + const CONTEXT_UID = this.CONTEXT_UID; + const buffers = geometry.buffers; + const attributes = geometry.attributes; + + if (geometry.indexBuffer) + { + // first update the index buffer if we have one.. + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, geometry.indexBuffer._glBuffers[CONTEXT_UID].buffer); + } + + let lastBuffer = null; + + // add a new one! + for (const j in attributes) + { + const attribute = attributes[j]; + const buffer = buffers[attribute.buffer]; + const glBuffer = buffer._glBuffers[CONTEXT_UID]; + + if (program.attributeData[j]) + { + if (lastBuffer !== glBuffer) + { + gl.bindBuffer(gl.ARRAY_BUFFER, glBuffer.buffer); + + lastBuffer = glBuffer; + } + + const location = program.attributeData[j].location; + + // TODO introduce state again + // we can optimise this for older devices that have no VAOs + gl.enableVertexAttribArray(location); + + gl.vertexAttribPointer(location, + attribute.size, + attribute.type || gl.FLOAT, + attribute.normalized, + attribute.stride, + attribute.start); + + if (attribute.instance) + { + // TODO calculate instance count based of this... + if (this.hasInstance) + { + gl.vertexAttribDivisor(location, 1); + } + else + { + throw new Error('geometry error, GPU Instancing is not supported on this device'); + } + } + } + } + } + + draw(type, size, start, instanceCount) + { + const gl = this.gl; + const geometry = this._activeGeometry; + + // TODO.. this should not change so maybe cache the function? + + if (geometry.indexBuffer) + { + if (geometry.instanced) + { + /* eslint-disable max-len */ + gl.drawElementsInstanced(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2, instanceCount || 1); + /* eslint-enable max-len */ + } + else + { + gl.drawElements(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2); + } + } + else + if (geometry.instanced) + { + // TODO need a better way to calculate size.. + gl.drawArraysInstanced(type, start, size || geometry.getSize(), instanceCount || 1); + } + else + { + gl.drawArrays(type, start, size || geometry.getSize()); + } + + return this; + } + + unbind() + { + this.gl.bindVertexArray(null); + this._activeVao = null; + this._activeGeometry = null; + } +} diff --git a/packages/core/src/geometry/utils/setVertexAttribArrays.js b/packages/core/src/geometry/utils/setVertexAttribArrays.js new file mode 100644 index 0000000..23b6959 --- /dev/null +++ b/packages/core/src/geometry/utils/setVertexAttribArrays.js @@ -0,0 +1,54 @@ +// var GL_MAP = {}; + +/** + * @param gl {WebGLRenderingContext} The current WebGL context + * @param attribs {*} + * @param state {*} + */ +export default function setVertexAttribArrays(gl, attribs, state) +{ + let i; + + if (state) + { + const tempAttribState = state.tempAttribState; + const attribState = state.attribState; + + for (i = 0; i < tempAttribState.length; i++) + { + tempAttribState[i] = false; + } + + // set the new attribs + for (i = 0; i < attribs.length; i++) + { + tempAttribState[attribs[i].attribute.location] = true; + } + + for (i = 0; i < attribState.length; i++) + { + if (attribState[i] !== tempAttribState[i]) + { + attribState[i] = tempAttribState[i]; + + if (state.attribState[i]) + { + gl.enableVertexAttribArray(i); + } + else + { + gl.disableVertexAttribArray(i); + } + } + } + } + else + { + for (i = 0; i < attribs.length; i++) + { + const attrib = attribs[i]; + + gl.enableVertexAttribArray(attrib.attribute.location); + } + } +} diff --git a/packages/core/src/index.js b/packages/core/src/index.js index 58c456a..fd1f547 100644 --- a/packages/core/src/index.js +++ b/packages/core/src/index.js @@ -1,30 +1,30 @@ import * as resources from './textures/resources'; -import * as systems from './renderers/systems'; +import * as systems from './systems'; -export { resources }; export { systems }; +export { resources }; -export { default as Renderer } from './renderers/Renderer'; -export { default as AbstractRenderer } from './renderers/AbstractRenderer'; -export { default as FrameBuffer } from './textures/FrameBuffer'; +export { default as System } from './System'; +export { default as Renderer } from './Renderer'; +export { default as AbstractRenderer } from './AbstractRenderer'; +export { default as FrameBuffer } from './framebuffer/FrameBuffer'; export { default as CubeTexture } from './textures/CubeTexture'; export { default as BaseTexture } from './textures/BaseTexture'; export { default as Texture } from './textures/Texture'; export { default as TextureMatrix } from './textures/TextureMatrix'; -export { default as RenderTexture } from './textures/RenderTexture'; -export { default as BaseRenderTexture } from './textures/BaseRenderTexture'; +export { default as RenderTexture } from './renderTexture/RenderTexture'; +export { default as BaseRenderTexture } from './renderTexture/BaseRenderTexture'; export { default as TextureUvs } from './textures/TextureUvs'; -export { default as State } from './renderers/State'; -export { default as ObjectRenderer } from './renderers/utils/ObjectRenderer'; -export { default as Quad } from './renderers/utils/Quad'; -export { default as QuadUv } from './renderers/utils/QuadUv'; -export { default as checkMaxIfStatmentsInShader } from './renderers/utils/checkMaxIfStatmentsInShader'; +export { default as State } from './state/State'; +export { default as ObjectRenderer } from './batch/ObjectRenderer'; +export { default as Quad } from './utils/Quad'; +export { default as QuadUv } from './utils/QuadUv'; +export { default as checkMaxIfStatmentsInShader } from './shader/utils/checkMaxIfStatmentsInShader'; export { default as Shader } from './shader/Shader'; export { default as Program } from './shader/Program'; export { default as UniformGroup } from './shader/UniformGroup'; -export { default as SpriteMaskFilter } from './renderers/filters/spriteMask/SpriteMaskFilter'; -export { default as Filter } from './renderers/filters/Filter'; +export { default as SpriteMaskFilter } from './filters/spriteMask/SpriteMaskFilter'; +export { default as Filter } from './filters/Filter'; export { default as Attribute } from './geometry/Attribute'; export { default as Buffer } from './geometry/Buffer'; export { default as Geometry } from './geometry/Geometry'; - diff --git a/packages/core/src/mask/MaskSystem.js b/packages/core/src/mask/MaskSystem.js new file mode 100644 index 0000000..99174c2 --- /dev/null +++ b/packages/core/src/mask/MaskSystem.js @@ -0,0 +1,199 @@ +import System from '../System'; +import AlphaMaskFilter from '../filters/spriteMask/SpriteMaskFilter'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class MaskSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + // TODO - we don't need both! + this.scissor = false; + this.scissorData = null; + this.scissorRenderTarget = null; + + this.enableScissor = false; + + this.alphaMaskPool = []; + this.alphaMaskIndex = 0; + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.DisplayObject} target - Display Object to push the mask to + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + push(target, maskData) + { + // TODO the root check means scissor rect will not + // be used on render textures more info here: + // https://github.com/pixijs/pixi.js/pull/3545 + + if (maskData.texture) + { + this.pushSpriteMask(target, maskData); + } + else if (this.enableScissor + && !this.scissor + && this.renderer._activeRenderTarget.root + && !this.renderer.stencil.stencilMaskStack.length + && maskData.isFastRect()) + { + const matrix = maskData.worldTransform; + + let rot = Math.atan2(matrix.b, matrix.a); + + // use the nearest degree! + rot = Math.round(rot * (180 / Math.PI)); + + if (rot % 90) + { + this.pushStencilMask(maskData); + } + else + { + this.pushScissorMask(target, maskData); + } + } + else + { + this.pushStencilMask(maskData); + } + } + + /** + * Removes the last mask from the mask stack and doesn't return it. + * + * @param {PIXI.DisplayObject} target - Display Object to pop the mask from + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + pop(target, maskData) + { + if (maskData.texture) + { + this.popSpriteMask(target, maskData); + } + else if (this.enableScissor && !this.renderer.stencil.stencilMaskStack.length) + { + this.popScissorMask(target, maskData); + } + else + { + this.popStencilMask(target, maskData); + } + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.RenderTarget} target - Display Object to push the sprite mask to + * @param {PIXI.Sprite} maskData - Sprite to be used as the mask + */ + pushSpriteMask(target, maskData) + { + let alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; + + if (!alphaMaskFilter) + { + alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; + } + + alphaMaskFilter[0].resolution = this.renderer.resolution; + alphaMaskFilter[0].maskSprite = maskData; + + // TODO - may cause issues! + target.filterArea = maskData.getBounds(true); + + this.renderer.filterSystem.pushFilter(target, alphaMaskFilter); + + this.alphaMaskIndex++; + } + + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popSpriteMask() + { + this.renderer.filterSystem.popFilter(); + this.alphaMaskIndex--; + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + pushStencilMask(maskData) + { + this.renderer.batch.flush(); + this.renderer.stencil.pushStencil(maskData); + } + + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popStencilMask() + { + // this.renderer.currentRenderer.stop(); + this.renderer.stencil.popStencil(); + } + + /** + * + * @param {PIXI.DisplayObject} target - Display Object to push the mask to + * @param {PIXI.Graphics} maskData - The masking data. + */ + pushScissorMask(target, maskData) + { + maskData.renderable = true; + + const renderTarget = this.renderer._activeRenderTarget; + + const bounds = maskData.getBounds(); + + bounds.fit(renderTarget.size); + maskData.renderable = false; + + this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); + + const resolution = this.renderer.resolution; + + this.renderer.gl.scissor( + bounds.x * resolution, + (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, + bounds.width * resolution, + bounds.height * resolution + ); + + this.scissorRenderTarget = renderTarget; + this.scissorData = maskData; + this.scissor = true; + } + + /** + * + * + */ + popScissorMask() + { + this.scissorRenderTarget = null; + this.scissorData = null; + this.scissor = false; + + // must be scissor! + const gl = this.renderer.gl; + + gl.disable(gl.SCISSOR_TEST); + } +} diff --git a/packages/core/src/mask/StencilSystem.js b/packages/core/src/mask/StencilSystem.js new file mode 100644 index 0000000..62796a5 --- /dev/null +++ b/packages/core/src/mask/StencilSystem.js @@ -0,0 +1,133 @@ +import System from '../System'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class StencilSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + this.stencilMaskStack = []; + } + + /** + * Changes the mask stack that is used by this System. + * + * @param {PIXI.Graphics[]} stencilMaskStack - The mask stack + */ + setMaskStack(stencilMaskStack) + { + const gl = this.renderer.gl; + + if (stencilMaskStack.length !== this.stencilMaskStack.length) + { + if (stencilMaskStack.length === 0) + { + gl.disable(gl.STENCIL_TEST); + } + else + { + gl.enable(gl.STENCIL_TEST); + } + } + + this.stencilMaskStack = stencilMaskStack; + } + + /** + * Applies the Mask and adds it to the current stencil stack. @alvin + * + * @param {PIXI.Graphics} graphics - The mask + */ + pushStencil(graphics) + { + this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); + + // this.renderer._activeRenderTarget.attachStencilBuffer(); + + const gl = this.renderer.gl; + const prevMaskCount = this.stencilMaskStack.length; + + if (prevMaskCount === 0) + { + gl.enable(gl.STENCIL_TEST); + } + + this.stencilMaskStack.push(graphics); + + // Increment the refference stencil value where the new mask overlaps with the old ones. + gl.colorMask(false, false, false, false); + gl.stencilFunc(gl.EQUAL, prevMaskCount, this._getBitwiseMask()); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.INCR); + this.renderer.plugins.graphics.render(graphics); + + this._useCurrent(); + } + + /** + * Removes the last mask from the stencil stack. @alvin + */ + popStencil() + { + this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); + + const gl = this.renderer.gl; + const graphics = this.stencilMaskStack.pop(); + + if (this.stencilMaskStack.length === 0) + { + // the stack is empty! + gl.disable(gl.STENCIL_TEST); + gl.clear(gl.STENCIL_BUFFER_BIT); + gl.clearStencil(0); + } + else + { + // Decrement the refference stencil value where the popped mask overlaps with the other ones + gl.colorMask(false, false, false, false); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.DECR); + this.renderer.plugins.graphics.render(graphics); + + this._useCurrent(); + } + } + + /** + * Setup renderer to use the current stencil data. + */ + _useCurrent() + { + const gl = this.renderer.gl; + + gl.colorMask(true, true, true, true); + gl.stencilFunc(gl.EQUAL, this.stencilMaskStack.length, this._getBitwiseMask()); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP); + } + + /** + * Fill 1s equal to the number of acitve stencil masks. + * + * @return {number} The bitwise mask. + */ + _getBitwiseMask() + { + return (1 << this.stencilMaskStack.length) - 1; + } + + /** + * Destroys the mask stack. + * + */ + destroy() + { + super.destroy(this); + + this.stencilMaskStack = null; + } +} diff --git a/packages/core/src/projection/ProjectionSystem.js b/packages/core/src/projection/ProjectionSystem.js new file mode 100644 index 0000000..6e63b73 --- /dev/null +++ b/packages/core/src/projection/ProjectionSystem.js @@ -0,0 +1,73 @@ +import System from '../System'; +import { Matrix } from '@pixi/math'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class ProjectionSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.projectionMatrix = new Matrix(); + } + + update(destinationFrame, sourceFrame, resolution, root) + { + this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; + this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; + + this.calculateProjection(this.destinationFrame, this.sourceFrame, resolution, root); + + this.renderer.globalUniforms.uniforms.projectionMatrix = this.projectionMatrix; + this.renderer.globalUniforms.update(); + } + + /** + * Updates the projection matrix based on a projection frame (which is a rectangle) + * + * @param {Rectangle} destinationFrame - The destination frame. + * @param {Rectangle} sourceFrame - The source frame. + */ + calculateProjection(destinationFrame, sourceFrame, resolution, root) + { + const pm = this.projectionMatrix; + + // I don't think we will need this line.. + // pm.identity(); + + if (!root) + { + pm.a = (1 / destinationFrame.width * 2) * resolution; + pm.d = (1 / destinationFrame.height * 2) * resolution; + + pm.tx = -1 - (sourceFrame.x * pm.a); + pm.ty = -1 - (sourceFrame.y * pm.d); + } + else + { + pm.a = (1 / destinationFrame.width * 2) * resolution; + pm.d = (-1 / destinationFrame.height * 2) * resolution; + + pm.tx = -1 - (sourceFrame.x * pm.a); + pm.ty = 1 - (sourceFrame.y * pm.d); + } + } + + /** + * Sets the transform of the active render target to the given matrix + * + * @param {PIXI.Matrix} matrix - The transformation matrix + */ + setTransform()// matrix) + { + // this._activeRenderTarget.transform = matrix; + } +} diff --git a/packages/core/src/renderTexture/BaseRenderTexture.js b/packages/core/src/renderTexture/BaseRenderTexture.js new file mode 100644 index 0000000..88eb83b --- /dev/null +++ b/packages/core/src/renderTexture/BaseRenderTexture.js @@ -0,0 +1,137 @@ +import BaseTexture from '../textures/BaseTexture'; +import FrameBuffer from '../framebuffer/FrameBuffer'; + +/** + * A BaseRenderTexture is a special texture that allows any PixiJS display object to be rendered to it. + * + * __Hint__: All DisplayObjects (i.e. Sprites) that render to a BaseRenderTexture should be preloaded + * otherwise black rectangles will be drawn instead. + * + * A BaseRenderTexture takes a snapshot of any Display Object given to its render method. The position + * and rotation of the given Display Objects is ignored. For example: + * + * ```js + * let renderer = PIXI.autoDetectRenderer(1024, 1024, { view: canvas, ratio: 1 }); + * let baseRenderTexture = new PIXI.BaseRenderTexture(renderer, 800, 600); + * let sprite = PIXI.Sprite.fromImage("spinObj_01.png"); + * + * sprite.position.x = 800/2; + * sprite.position.y = 600/2; + * sprite.anchor.x = 0.5; + * sprite.anchor.y = 0.5; + * + * baseRenderTexture.render(sprite); + * ``` + * + * The Sprite in this case will be rendered using its local transform. To render this sprite at 0,0 + * you can clear the transform + * + * ```js + * + * sprite.setTransform() + * + * let baseRenderTexture = new PIXI.BaseRenderTexture(100, 100); + * let renderTexture = new PIXI.RenderTexture(baseRenderTexture); + * + * renderer.render(sprite, renderTexture); // Renders to center of RenderTexture + * ``` + * + * @class + * @extends PIXI.BaseTexture + * @memberof PIXI + */ +export default class BaseRenderTexture extends BaseTexture +{ + /** + * @param {object} [options] + * @param {number} [options.width=100] - The width of the base render texture + * @param {number} [options.height=100] - The height of the base render texture + * @param {PIXI.SCALE_MODES} [options.scaleMode] - See {@link PIXI.SCALE_MODES} for possible values + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the texture being generated + */ + constructor(options) + { + if (typeof options === 'number') + { + /* eslint-disable prefer-rest-params */ + // Backward compatibility of signature + const width = arguments[0]; + const height = arguments[1]; + const scaleMode = arguments[2]; + const resolution = arguments[3]; + + options = { width, height, scaleMode, resolution }; + /* eslint-enable prefer-rest-params */ + } + + super(null, options); + + const { width, height } = options || {}; + + // Set defaults + this.mipmap = false; + this.width = Math.ceil(width) || 100; + this.height = Math.ceil(height) || 100; + this.valid = true; + + /** + * A map of renderer IDs to webgl renderTargets + * + * @private + * @member {object} + */ + // this._glRenderTargets = {}; + + /** + * A reference to the canvas render target (we only need one as this can be shared across renderers) + * + * @private + * @member {object} + */ + this._canvasRenderTarget = null; + + this.clearColor = [0, 0, 0, 0]; + + this.frameBuffer = new FrameBuffer(width * this.resolution, height * this.resolution) + .addColorTexture(0, this); + + // TODO - could this be added the systems? + + /** + * The data structure for the stencil masks + * + * @member {PIXI.Graphics[]} + */ + this.stencilMaskStack = []; + + /** + * The data structure for the filters + * + * @member {PIXI.Graphics[]} + */ + this.filterStack = [{}]; + } + + /** + * Resizes the BaseRenderTexture. + * + * @param {number} width - The width to resize to. + * @param {number} height - The height to resize to. + */ + resize(width, height) + { + width = Math.ceil(width); + height = Math.ceil(height); + this.frameBuffer.resize(width * this.resolution, height * this.resolution); + } + + /** + * Destroys this texture + * + */ + destroy() + { + super.destroy(true); + this.renderer = null; + } +} diff --git a/packages/core/src/renderTexture/RenderTexture.js b/packages/core/src/renderTexture/RenderTexture.js new file mode 100644 index 0000000..f1ecf1f --- /dev/null +++ b/packages/core/src/renderTexture/RenderTexture.js @@ -0,0 +1,153 @@ +import BaseRenderTexture from './BaseRenderTexture'; +import Texture from '../textures/Texture'; + +/** + * A RenderTexture is a special texture that allows any PixiJS display object to be rendered to it. + * + * __Hint__: All DisplayObjects (i.e. Sprites) that render to a RenderTexture should be preloaded + * otherwise black rectangles will be drawn instead. + * + * A RenderTexture takes a snapshot of any Display Object given to its render method. For example: + * + * ```js + * let renderer = PIXI.autoDetectRenderer(1024, 1024, { view: canvas, ratio: 1 }); + * let renderTexture = PIXI.RenderTexture.create(800, 600); + * let sprite = PIXI.Sprite.fromImage("spinObj_01.png"); + * + * sprite.position.x = 800/2; + * sprite.position.y = 600/2; + * sprite.anchor.x = 0.5; + * sprite.anchor.y = 0.5; + * + * renderer.render(sprite, renderTexture); + * ``` + * + * The Sprite in this case will be rendered using its local transform. To render this sprite at 0,0 + * you can clear the transform + * + * ```js + * + * sprite.setTransform() + * + * let renderTexture = new PIXI.RenderTexture.create(100, 100); + * + * renderer.render(sprite, renderTexture); // Renders to center of RenderTexture + * ``` + * + * @class + * @extends PIXI.Texture + * @memberof PIXI + */ +export default class RenderTexture extends Texture +{ + /** + * @param {PIXI.BaseRenderTexture} baseRenderTexture - The renderer used for this RenderTexture + * @param {PIXI.Rectangle} [frame] - The rectangle frame of the texture to show + */ + constructor(baseRenderTexture, frame) + { + // support for legacy.. + let _legacyRenderer = null; + + if (!(baseRenderTexture instanceof BaseRenderTexture)) + { + /* eslint-disable prefer-rest-params, no-console */ + const width = arguments[1]; + const height = arguments[2]; + const scaleMode = arguments[3]; + const resolution = arguments[4]; + + // we have an old render texture.. + console.warn(`Please use RenderTexture.create(${width}, ${height}) instead of the ctor directly.`); + _legacyRenderer = arguments[0]; + /* eslint-enable prefer-rest-params, no-console */ + + frame = null; + baseRenderTexture = new BaseRenderTexture({ + width, + height, + scaleMode, + resolution, + }); + } + + /** + * The base texture object that this texture uses + * + * @member {BaseTexture} + */ + super(baseRenderTexture, frame); + + this.legacyRenderer = _legacyRenderer; + + /** + * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. + * + * @member {boolean} + */ + this.valid = true; + + /** + * FilterSystem temporary storage + * @private + * @member {PIXI.Rectangle} + */ + this.filterFrame = null; + + this._updateUvs(); + } + + /** + * Resizes the RenderTexture. + * + * @param {number} width - The width to resize to. + * @param {number} height - The height to resize to. + * @param {boolean} [resizeBaseTexture=true] - Should the baseTexture.width and height values be resized as well? + */ + resize(width, height, resizeBaseTexture = true) + { + width = Math.ceil(width); + height = Math.ceil(height); + + // TODO - could be not required.. + this.valid = (width > 0 && height > 0); + + this._frame.width = this.orig.width = width; + this._frame.height = this.orig.height = height; + + if (resizeBaseTexture) + { + this.baseTexture.resize(width, height); + } + + this._updateUvs(); + } + + /** + * A short hand way of creating a render texture. + * + * @param {object} [options] - Options + * @param {number} [options.width=100] - The width of the render texture + * @param {number} [options.height=100] - The height of the render texture + * @param {number} [options.scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the texture being generated + * @return {PIXI.RenderTexture} The new render texture + */ + static create(options) + { + // fallback, old-style: create(width, height, scaleMode, resolution) + if (typeof options === 'number') + { + /* eslint-disable prefer-rest-params */ + options = { + width: options, + height: arguments[1], + scaleMode: arguments[2], + resolution: arguments[3], + }; + /* eslint-enable prefer-rest-params */ + } + + return new RenderTexture(new BaseRenderTexture(options)); + } +} diff --git a/packages/core/src/renderTexture/RenderTextureSystem.js b/packages/core/src/renderTexture/RenderTextureSystem.js new file mode 100644 index 0000000..8d0ca61 --- /dev/null +++ b/packages/core/src/renderTexture/RenderTextureSystem.js @@ -0,0 +1,115 @@ +import System from '../System'; +import { Rectangle } from '@pixi/math'; + +const tempRect = new Rectangle(); + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class RenderTextureSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.clearColor = renderer._backgroundColorRgba; + + // TODO moe this property somewhere else! + this.defaultMaskStack = []; + this.defaultFilterStack = [{}]; + + // empty render texture? + this.renderTexture = null; + + this.destinationFrame = new Rectangle(); + } + + bind(renderTexture, sourceFrame, destinationFrame) + { + // TODO - do we want this?? + if (this.renderTexture === renderTexture) return; + this.renderTexture = renderTexture; + + const renderer = this.renderer; + + if (renderTexture) + { + const baseTexture = renderTexture.baseTexture; + + if (!destinationFrame) + { + tempRect.width = baseTexture.realWidth; + tempRect.height = baseTexture.realHeight; + + destinationFrame = tempRect; + } + + if (!sourceFrame) + { + sourceFrame = destinationFrame; + } + + this.renderer.framebuffer.bind(baseTexture.frameBuffer, destinationFrame); + + this.renderer.projection.update(destinationFrame, sourceFrame, baseTexture.resolution, false); + this.renderer.stencil.setMaskStack(baseTexture.stencilMaskStack); + } + else + { + // TODO these validation checks happen deeper down.. + // thing they can be avoided.. + if (!destinationFrame) + { + tempRect.width = renderer.width; + tempRect.height = renderer.height; + + destinationFrame = tempRect; + } + + if (!sourceFrame) + { + sourceFrame = destinationFrame; + } + + renderer.framebuffer.bind(null, destinationFrame); + + // TODO store this.. + this.renderer.projection.update(destinationFrame, sourceFrame, this.renderer.resolution, true); + this.renderer.stencil.setMaskStack(this.defaultMaskStack); + } + + this.destinationFrame.copyFrom(destinationFrame); + } + + /** + * Erases the render texture and fills the drawing area with a colour + * + * @param {number} [clearColor] - The colour + * @return {PIXI.Renderer} Returns itself. + */ + clear(clearColor) + { + if (this.renderTexture) + { + clearColor = clearColor || this.renderTexture.baseTexture.clearColor; + } + else + { + clearColor = clearColor || this.clearColor; + } + + this.renderer.framebuffer.clear(clearColor[0], clearColor[1], clearColor[2], clearColor[3]); + } + + resize()// screenWidth, screenHeight) + { + // resize the root only! + this.bind(null); + } +} diff --git a/packages/core/src/renderers/AbstractRenderer.js b/packages/core/src/renderers/AbstractRenderer.js deleted file mode 100644 index 928670f..0000000 --- a/packages/core/src/renderers/AbstractRenderer.js +++ /dev/null @@ -1,353 +0,0 @@ -import { hex2string, hex2rgb } from '@pixi/utils'; -import { Matrix, Rectangle } from '@pixi/math'; -import { RENDERER_TYPE } from '@pixi/constants'; -import { settings } from '@pixi/settings'; -import { Container } from '@pixi/display'; -import RenderTexture from '../textures/RenderTexture'; -import EventEmitter from 'eventemitter3'; - -const tempMatrix = new Matrix(); - -/** - * 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 EventEmitter - * @memberof PIXI - */ -export default class AbstractRenderer extends EventEmitter -{ - // eslint-disable-next-line valid-jsdoc - /** - * @param {string} system - The name of the system this renderer is for. - * @param {object} [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.transparent=false] - If the render view is transparent, default false - * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false - * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) - * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. The - * resolution of the renderer retina would be 2. - * @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 {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when rendering, - * stopping pixel interpolation. - */ - constructor(system, options, arg2, arg3) - { - super(); - - // Support for constructor(system, screenWidth, screenHeight, options) - if (typeof options === 'number') - { - options = Object.assign({ - width: options, - height: arg2 || settings.RENDER_OPTIONS.height, - }, arg3); - } - - // 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 = RENDERER_TYPE.UNKNOWN; - - /** - * Measurements of the screen. (0, 0, screenWidth, screenHeight) - * - * Its safe to use as filterArea or hitArea for 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 1 - */ - this.resolution = options.resolution || settings.RESOLUTION; - - /** - * Whether the render view is transparent - * - * @member {boolean} - */ - this.transparent = options.transparent; - - /** - * Whether css dimensions of canvas view should be resized to screen dimensions automatically - * - * @member {boolean} - */ - this.autoResize = options.autoResize || false; - - /** - * Tracks the blend modes useful for this renderer. - * - * @member {object} - */ - this.blendModes = null; - - /** - * 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; - - /** - * If true PixiJS will Math.floor() x/y values when rendering, stopping pixel interpolation. - * Handy for crisp pixel art and speed on legacy devices. - * - * @member {boolean} - */ - this.roundPixels = options.roundPixels; - - /** - * The background color as a number. - * - * @member {number} - * @private - */ - this._backgroundColor = 0x000000; - - /** - * The background color as an [R, G, B] array. - * - * @member {number[]} - * @private - */ - this._backgroundColorRgba = [0, 0, 0, 0]; - - /** - * The background color as a string. - * - * @member {string} - * @private - */ - this._backgroundColorString = '#000000'; - - this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter - - /** - * This temporary display object used as the parent of the currently being rendered item - * - * @member {PIXI.DisplayObject} - * @private - */ - this._tempDisplayObjectParent = new Container(); - - /** - * The last root object that the renderer tried to render. - * - * @member {PIXI.DisplayObject} - * @private - */ - this._lastObjectRendered = this._tempDisplayObjectParent; - - /** - * Collection of plugins. - * @readonly - * @member {object} - */ - this.plugins = {}; - } - - /** - * Initialize the plugins. - * - * @protected - * @param {object} staticMap - The dictionary of staticly saved plugins. - */ - initPlugins(staticMap) - { - 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() - { - return this.view.width; - } - - /** - * Same as view.height, actual number of pixels in the canvas by vertical - * - * @member {number} - * @readonly - * @default 600 - */ - get height() - { - return this.view.height; - } - - /** - * Resizes the screen and canvas to the specified width and height - * Canvas dimensions are multiplied by resolution - * - * @param {number} screenWidth - the new width of the screen - * @param {number} screenHeight - the new height of the screen - */ - resize(screenWidth, screenHeight) - { - this.screen.width = screenWidth; - this.screen.height = screenHeight; - - this.view.width = screenWidth * this.resolution; - this.view.height = screenHeight * this.resolution; - - if (this.autoResize) - { - this.view.style.width = `${screenWidth}px`; - this.view.style.height = `${screenHeight}px`; - } - } - - /** - * 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 {PIXI.DisplayObject} displayObject - The displayObject the object will be generated from - * @param {number} scaleMode - Should be one of the scaleMode consts - * @param {number} resolution - The resolution / device pixel ratio of the texture being generated - * @param {PIXI.Rectangle} [region] - The region of the displayObject, that shall be rendered, - * if no region is specified, defaults to the local bounds of the displayObject. - * @return {PIXI.Texture} a texture of the graphics object - */ - generateTexture(displayObject, scaleMode, resolution, region) - { - region = region || displayObject.getLocalBounds(); - - // 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(region.width | 0, region.height | 0, scaleMode, resolution); - - tempMatrix.tx = -region.x; - tempMatrix.ty = -region.y; - - this.render(displayObject, renderTexture, false, tempMatrix, true); - - return renderTexture; - } - - /** - * Removes everything from the renderer and optionally removes the Canvas DOM element. - * - * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. - */ - destroy(removeView) - { - 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); - } - - this.plugins = null; - - this.type = RENDERER_TYPE.UNKNOWN; - - this.view = null; - - this.screen = null; - - this.resolution = 0; - - this.transparent = false; - - this.autoResize = false; - - this.blendModes = null; - - this.options = null; - - this.preserveDrawingBuffer = false; - this.clearBeforeRender = false; - - this.roundPixels = false; - - this._backgroundColor = 0; - this._backgroundColorRgba = null; - this._backgroundColorString = null; - - this._tempDisplayObjectParent = null; - this._lastObjectRendered = null; - } - - /** - * The background color to fill if not transparent - * - * @member {number} - */ - get backgroundColor() - { - return this._backgroundColor; - } - - set backgroundColor(value) // eslint-disable-line require-jsdoc - { - this._backgroundColor = value; - this._backgroundColorString = hex2string(value); - hex2rgb(value, this._backgroundColorRgba); - } -} diff --git a/packages/core/src/renderers/Renderer.js b/packages/core/src/renderers/Renderer.js deleted file mode 100644 index a2020b1..0000000 --- a/packages/core/src/renderers/Renderer.js +++ /dev/null @@ -1,329 +0,0 @@ -import AbstractRenderer from './AbstractRenderer'; -import { sayHello } from '@pixi/utils'; -import MaskSystem from './systems/MaskSystem'; -import StencilSystem from './systems/StencilSystem'; -import FilterSystem from './systems/filter/FilterSystem'; -import FramebufferSystem from './systems/FramebufferSystem'; -import RenderTextureSystem from './systems/RenderTextureSystem'; -import TextureSystem from './systems/textures/TextureSystem'; -import ProjectionSystem from './systems/ProjectionSystem'; -import StateSystem from './systems/StateSystem'; -import GeometrySystem from './systems/geometry/GeometrySystem'; -import ShaderSystem from './systems/shader/ShaderSystem'; -import ContextSystem from './systems/ContextSystem'; -import BatchSystem from './systems/BatchSystem'; -import TextureGCSystem from './systems/textures/TextureGCSystem'; -import { RENDERER_TYPE } from '@pixi/constants'; -import UniformGroup from '../shader/UniformGroup'; -import { Matrix } from '@pixi/math'; -import Runner from 'mini-runner'; - -/** - * 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 Render works by automatically managing webGLBatchs. - * 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 :) - * - * @class - * @memberof PIXI - * @extends PIXI.AbstractRenderer - */ -export default class Renderer extends AbstractRenderer -{ - // eslint-disable-next-line valid-jsdoc - /** - * - * @param {object} [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.transparent=false] - If the render view is transparent, default false - * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false - * @param {boolean} [options.antialias=false] - sets antialias. If not available natively then FXAA - * antialiasing is used - * @param {boolean} [options.forceFXAA=false] - forces FXAA antialiasing to be used over native. - * FXAA is faster, but may not always look as great - * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. - * The resolution of the renderer retina would be 2. - * @param {boolean} [options.clearBeforeRender=true] - This sets if the renderer will clear - * the canvas or not before the new render pass. If you wish to set this to false, you *must* set - * preserveDrawingBuffer to `true`. - * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, - * enable this if you need to call toDataUrl on the webgl context. - * @param {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when - * rendering, stopping pixel interpolation. - * @param {number} [options.backgroundColor=0x000000] - The background color of the rendered area - * (shown if not transparent). - * @param {boolean} [options.legacy=false] - If true PixiJS will aim to ensure compatibility - * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. - * @param {string} [options.powerPreference] - Parameter passed to webgl context, set to "high-performance" - * for devices with dual graphics card - */ - constructor(options, arg2, arg3) - { - super('WebGL', options, arg2, arg3); - - /** - * The type of this renderer as a standardised const - * - * @member {number} - * @see PIXI.RENDERER_TYPE - */ - this.type = RENDERER_TYPE.WEBGL; - - // this will be set by the contextSystem (this.context) - this.gl = null; - this.CONTEXT_UID = 0; - this.legacy = !!options.legacy; - - // TODO legacy! - - // runners! - this.runners = { - destroy: new Runner('destroy'), - contextChange: new Runner('contextChange', 1), - reset: new Runner('reset'), - update: new Runner('update'), - postrender: new Runner('postrender'), - prerender: new Runner('prerender'), - resize: new Runner('resize', 2), - }; - - 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(GeometrySystem, 'geometry') - .addSystem(FramebufferSystem, 'framebuffer') - .addSystem(StencilSystem, 'stencil') - .addSystem(ProjectionSystem, 'projection') - .addSystem(TextureGCSystem, 'textureGC') - .addSystem(FilterSystem, 'filter') - .addSystem(RenderTextureSystem, 'renderTexture') - .addSystem(BatchSystem, 'batch'); - - this.initPlugins(Renderer.__plugins); - - /** - * The options passed in to create a new webgl context. - * - * @member {object} - * @private - */ - if (options.context) - { - this.context.initFromContext(options.context); - } - else - { - this.context.initFromOptions({ - alpha: this.transparent, - antialias: options.antialias, - premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', - stencil: true, - 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); - } - - addSystem(_class, name) - { - if (!name) - { - name = _class.name; - } - - // TODO - read name from class.name.. - - /* - if(name.includes('System')) - { - name = name.replace('System', ''); - name = name.charAt(0).toLowerCase() + name.slice(1); - } - */ - - const system = new _class(this); - - if (this[name]) - { - throw new Error(`Whoops! ${name} is already a manger`); - } - - this[name] = system; - - for (const i in this.runners) - { - this.runners[i].add(system); - } - - return this; - - /** - * 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. - */ - } - - /** - * Renders the object to its webGL view - * - * @param {PIXI.DisplayObject} displayObject - the object to be rendered - * @param {PIXI.RenderTexture} renderTexture - The render texture to render to. - * @param {boolean} [clear] - Should the canvas be cleared before the new render - * @param {PIXI.Transform} [transform] - A transform to apply to the render texture before rendering. - * @param {boolean} [skipUpdateTransform] - Should we skip the update transform pass? - */ - render(displayObject, renderTexture, clear, transform, skipUpdateTransform) - { - // can be handy to know! - this.renderingToScreen = !renderTexture; - - this.runners.prerender.run(); - this.emit('prerender'); - - // 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.parent; - - displayObject.parent = this._tempDisplayObjectParent; - displayObject.updateTransform(); - displayObject.parent = 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.run(); - - this.emit('postrender'); - } - - /** - * Resizes the webGL view to the specified width and height. - * - * @param {number} screenWidth - the new width of the screen - * @param {number} screenHeight - the new height of the screen - */ - resize(screenWidth, screenHeight) - { - AbstractRenderer.prototype.resize.call(this, screenWidth, screenHeight); - - this.runners.resize.run(screenWidth, screenHeight); - } - - /** - * Resets the WebGL state so you can render things however you fancy! - * - * @return {PIXI.Renderer} Returns itself. - */ - reset() - { - this.runners.reset.run(); - - return this; - } - - clear() - { - this.framebuffer.bind(); - this.framebuffer.clear(); - } - - /** - * Removes everything from the renderer (event listeners, spritebatch, etc...) - * - * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. - * See: https://github.com/pixijs/pixi.js/issues/2233 - */ - destroy(removeView) - { - this.runners.destroy.run(); - - // call base destroy - super.destroy(removeView); - - // TODO nullify all the managers.. - this.gl = null; - } - - /** - * 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 - * builds and excluding plugins. - * @name PIXI.Renderer#plugins - * @type {object} - * @readonly - * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. - * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. - * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. - * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. - */ - - /** - * Adds a plugin to the renderer. - * - * @method - * @param {string} pluginName - The name of the plugin. - * @param {Function} ctor - The constructor function or class for the plugin. - */ - static registerPlugin(pluginName, ctor) - { - Renderer.__plugins = Renderer.__plugins || {}; - Renderer.__plugins[pluginName] = ctor; - } -} diff --git a/packages/core/src/renderers/State.js b/packages/core/src/renderers/State.js deleted file mode 100644 index fa3f221..0000000 --- a/packages/core/src/renderers/State.js +++ /dev/null @@ -1,163 +0,0 @@ -/* eslint-disable max-len */ - -const BLEND = 0; -const OFFSET = 1; -const CULLING = 2; -const DEPTH_TEST = 3; -const WINDING = 4; - -/** - * This is a webGL state. It is passed The WebGL StateManager. - * Each mesh renderered may require webGL to be in a different state. - * For example you may want different blend mode or to enable polygon offsets - * - * @class - * @memberof PIXI - */ -export default class State -{ - /** - * - */ - constructor() - { - this.data = 0; - - this.blendMode = 0; - this.polygonOffset = 0; - - this.blend = true; - // this.depthTest = true; - } - - /** - * Activates blending of the computed fragment color values - * - * @member {boolean} - */ - get blend() - { - return !!(this.data & (1 << BLEND)); - } - - set blend(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << BLEND)) !== value) - { - this.data ^= (1 << BLEND); - } - } - - /** - * Activates adding an offset to depth values of polygon's fragments - * - * @member {boolean} - * @default false - */ - get offsets() - { - return !!(this.data & (1 << OFFSET)); - } - - set offsets(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << OFFSET)) !== value) - { - this.data ^= (1 << OFFSET); - } - } - - /** - * Activates culling of polygons. - * - * @member {boolean} - * @default false - */ - get culling() - { - return !!(this.data & (1 << CULLING)); - } - - set culling(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << CULLING)) !== value) - { - this.data ^= (1 << CULLING); - } - } - - /** - * Activates depth comparisons and updates to the depth buffer. - * - * @member {boolean} - * @default false - */ - get depthTest() - { - return !!(this.data & (1 << DEPTH_TEST)); - } - - set depthTest(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << DEPTH_TEST)) !== value) - { - this.data ^= (1 << DEPTH_TEST); - } - } - - /** - * Specifies whether or not front or back-facing polygons can be culled. - * @member {boolean} - * @default false - */ - get clockwiseFrontFace() - { - return !!(this.data & (1 << WINDING)); - } - - set clockwiseFrontFace(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << WINDING)) !== value) - { - this.data ^= (1 << WINDING); - } - } - - /** - * The blend mode to be applied when this state is set. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. - * Setting this mode to anything other than NO_BLEND will automatically switch blending on. - * - * @member {boolean} - * @default PIXI.BLEND_MODES.NORMAL - * @see PIXI.BLEND_MODES - */ - get blendMode() - { - return this._blendMode; - } - - set blendMode(value) // eslint-disable-line require-jsdoc - { - // 17 is NO BLEND - this.blend = (value !== 17); - this._blendMode = value; - } - - /** - * The polygon offset. Setting this property to anything other than 0 will automatically enable poygon offset fill. - * - * @member {number} - * @default 0 - */ - get polygonOffset() - { - return this._polygonOffset; - } - - set polygonOffset(value) // eslint-disable-line require-jsdoc - { - this.offsets = !!value; - this._polygonOffset = value; - } -} - diff --git a/packages/core/src/renderers/filters/Filter.js b/packages/core/src/renderers/filters/Filter.js deleted file mode 100644 index 2073b8d..0000000 --- a/packages/core/src/renderers/filters/Filter.js +++ /dev/null @@ -1,140 +0,0 @@ -import Shader from '../../shader/Shader'; -import Program from '../../shader/Program'; -import State from '../State'; -import { settings } from '@pixi/settings'; -// import extractUniformsFromSrc from './extractUniformsFromSrc'; -import defaultVertex from './defaultFilter.vert'; -import defaultFragment from './defaultFilter.frag'; - -// let math = require('../../math'); -/** - * @class - * @memberof PIXI - * @extends PIXI.Shader - */ -export default class Filter extends Shader -{ - /** - * @param {string} [vertexSrc] - The source of the vertex shader. - * @param {string} [fragmentSrc] - The source of the fragment shader. - * @param {object} [uniforms] - Custom uniforms to use to augment the built-in ones. - */ - constructor(vertexSrc, fragmentSrc, uniforms) - { - const program = Program.from(vertexSrc, fragmentSrc); - - super(program, uniforms); - - /** - * The padding of the filter. Some filters require extra space to breath such as a blur. - * Increasing this will add extra width and height to the bounds of the object that the - * filter is applied to. - * - * @member {number} - */ - this.padding = 0; - - /** - * The resolution of the filter. Setting this to be lower will lower the quality but - * increase the performance of the filter. - * - * @member {number} - */ - this.resolution = settings.RESOLUTION; - - /** - * If enabled is true the filter is applied, if false it will not. - * - * @member {boolean} - */ - this.enabled = true; - - /** - * If enabled, PixiJS will fit the filter area into boundaries for better performance. - * Switch it off if it does not work for specific shader. - * - * @member {boolean} - */ - this.autoFit = true; - - /** - * Legacy filters use position and uvs from attributes - * @member {boolean} - * @readonly - */ - this.legacy = !!this.program.attributeData.aTextureCoord; - - /** - * the webGL state the filter requires to render - * @member {PIXI.State} - */ - this.state = new State(); - } - - /** - * Applies the filter - * - * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from - * @param {PIXI.RenderTarget} input - The input render target. - * @param {PIXI.RenderTarget} output - The target to output to. - * @param {boolean} clear - Should the output be cleared before rendering to it - * @param {object} [currentState] - It's current state of filter. - * There are some useful properties in the currentState : - * target, filters, sourceFrame, destinationFrame, renderTarget, resolution - */ - apply(filterManager, input, output, clear, currentState, derp) // eslint-disable-line no-unused-vars - { - // do as you please! - - filterManager.applyFilter(this, input, output, clear, currentState, derp); - - // or just do a regular render.. - } - - /** - * Sets the blendmode of the filter - * - * @member {number} - * @default PIXI.BLEND_MODES.NORMAL - */ - get blendMode() - { - return this.state.blendMode; - } - - set blendMode(value) // eslint-disable-line require-jsdoc - { - this.state.blendMode = value; - } - - /** - * The default vertex shader source - * - * @static - * @constant - */ - static get defaultVertexSrc() - { - return defaultVertex; - } - - /** - * The default fragment shader source - * - * @static - * @constant - */ - static get defaultFragmentSrc() - { - return defaultFragment; - } -} - -/** - * Used for caching shader IDs - * - * @static - * @private - */ -Filter.SOURCE_KEY_MAP = {}; - diff --git a/packages/core/src/renderers/filters/defaultFilter.frag b/packages/core/src/renderers/filters/defaultFilter.frag deleted file mode 100644 index a14c1be..0000000 --- a/packages/core/src/renderers/filters/defaultFilter.frag +++ /dev/null @@ -1,22 +0,0 @@ -varying vec2 vTextureCoord; -varying vec2 vFilterCoord; - -uniform sampler2D uSampler; -uniform sampler2D filterSampler; - -void main(void){ - vec4 masky = texture2D(filterSampler, vFilterCoord); - vec4 sample = texture2D(uSampler, vTextureCoord); - vec4 color; - if(mod(vFilterCoord.x, 1.0) > 0.5) - { - color = vec4(1.0, 0.0, 0.0, 1.0); - } - else - { - color = vec4(0.0, 1.0, 0.0, 1.0); - } - // gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0); - gl_FragColor = mix(sample, masky, 0.5); - gl_FragColor *= sample.a; -} diff --git a/packages/core/src/renderers/filters/defaultFilter.vert b/packages/core/src/renderers/filters/defaultFilter.vert deleted file mode 100644 index e1febca..0000000 --- a/packages/core/src/renderers/filters/defaultFilter.vert +++ /dev/null @@ -1,14 +0,0 @@ -attribute vec2 aVertexPosition; -attribute vec2 aTextureCoord; - -uniform mat3 projectionMatrix; -uniform mat3 filterMatrix; - -varying vec2 vTextureCoord; -varying vec2 vFilterCoord; - -void main(void){ - gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); - vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy; - vTextureCoord = aTextureCoord ; -} diff --git a/packages/core/src/renderers/filters/filterTransforms.js b/packages/core/src/renderers/filters/filterTransforms.js deleted file mode 100644 index 87a3cd6..0000000 --- a/packages/core/src/renderers/filters/filterTransforms.js +++ /dev/null @@ -1,50 +0,0 @@ -import { Matrix } from '@pixi/math'; - -/** - * Calculates the mapped matrix - * @param filterArea {Rectangle} The filter area - * @param sprite {Sprite} the target sprite - * @param outputMatrix {Matrix} @alvin - * @private - */ -// this returns a matrix that will normalise map filter cords in the filter to screen space -export function calculateScreenSpaceMatrix(outputMatrix, filterArea, textureSize) -{ - // TODO unwrap? - const mappedMatrix = outputMatrix.identity(); - - mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); - - mappedMatrix.scale(textureSize.width, textureSize.height); - - return mappedMatrix; -} - -export function calculateNormalizedScreenSpaceMatrix(outputMatrix, filterArea, textureSize) -{ - const mappedMatrix = outputMatrix.identity(); - - mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); - - const translateScaleX = (textureSize.width / filterArea.width); - const translateScaleY = (textureSize.height / filterArea.height); - - mappedMatrix.scale(translateScaleX, translateScaleY); - - return mappedMatrix; -} - -// this will map the filter coord so that a texture can be used based on the transform of a sprite -export function calculateSpriteMatrix(outputMatrix, filterArea, textureSize, sprite) -{ - const orig = sprite._texture.orig; - const mappedMatrix = outputMatrix.set(textureSize.width, 0, 0, textureSize.height, filterArea.x, filterArea.y); - const worldTransform = sprite.worldTransform.copyTo(Matrix.TEMP_MATRIX); - - worldTransform.invert(); - mappedMatrix.prepend(worldTransform); - mappedMatrix.scale(1.0 / orig.width, 1.0 / orig.height); - mappedMatrix.translate(sprite.anchor.x, sprite.anchor.y); - - return mappedMatrix; -} diff --git a/packages/core/src/renderers/filters/spriteMask/SpriteMaskFilter.js b/packages/core/src/renderers/filters/spriteMask/SpriteMaskFilter.js deleted file mode 100644 index d5a91a4..0000000 --- a/packages/core/src/renderers/filters/spriteMask/SpriteMaskFilter.js +++ /dev/null @@ -1,63 +0,0 @@ -import Filter from '../Filter'; -import { Matrix } from '@pixi/math'; -import vertex from './spriteMaskFilter.vert'; -import fragment from './spriteMaskFilter.frag'; -import { default as TextureMatrix } from '../../../textures/TextureMatrix'; - -/** - * The SpriteMaskFilter class - * - * @class - * @extends PIXI.Filter - * @memberof PIXI - */ -export default class SpriteMaskFilter extends Filter -{ - /** - * @param {PIXI.Sprite} sprite - the target sprite - */ - constructor(sprite) - { - const maskMatrix = new Matrix(); - - super(vertex, fragment); - - sprite.renderable = false; - - this.maskSprite = sprite; - this.maskMatrix = maskMatrix; - } - - /** - * Applies the filter - * - * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from - * @param {PIXI.RenderTarget} input - The input render target. - * @param {PIXI.RenderTarget} output - The target to output to. - */ - apply(filterManager, input, output) - { - const maskSprite = this.maskSprite; - const tex = this.maskSprite.texture; - - if (!tex.valid) - { - return; - } - if (!tex.transform) - { - // margin = 0.0, let it bleed a bit, shader code becomes easier - // assuming that atlas textures were made with 1-pixel padding - tex.transform = new TextureMatrix(tex, 0.0); - } - tex.transform.update(); - - this.uniforms.mask = tex; - this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite) - .prepend(tex.transform.mapCoord); - this.uniforms.alpha = maskSprite.worldAlpha; - this.uniforms.maskClamp = tex.transform.uClampFrame; - - filterManager.applyFilter(this, input, output); - } -} diff --git a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.frag b/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.frag deleted file mode 100644 index 0e4aef8..0000000 --- a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.frag +++ /dev/null @@ -1,23 +0,0 @@ -varying vec2 vMaskCoord; -varying vec2 vTextureCoord; - -uniform sampler2D uSampler; -uniform sampler2D mask; -uniform float alpha; -uniform vec4 maskClamp; - -void main(void) -{ - float clip = step(3.5, - step(maskClamp.x, vMaskCoord.x) + - step(maskClamp.y, vMaskCoord.y) + - step(vMaskCoord.x, maskClamp.z) + - step(vMaskCoord.y, maskClamp.w)); - - vec4 original = texture2D(uSampler, vTextureCoord); - vec4 masky = texture2D(mask, vMaskCoord); - - original *= (masky.r * masky.a * alpha * clip); - - gl_FragColor = original; -} diff --git a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.vert b/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.vert deleted file mode 100644 index d23a30c..0000000 --- a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.vert +++ /dev/null @@ -1,16 +0,0 @@ -attribute vec2 aVertexPosition; -attribute vec2 aTextureCoord; - -uniform mat3 projectionMatrix; -uniform mat3 otherMatrix; - -varying vec2 vMaskCoord; -varying vec2 vTextureCoord; - -void main(void) -{ - gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); - - vTextureCoord = aTextureCoord; - vMaskCoord = ( otherMatrix * vec3( aTextureCoord, 1.0) ).xy; -} diff --git a/packages/core/src/renderers/systems/BatchSystem.js b/packages/core/src/renderers/systems/BatchSystem.js deleted file mode 100644 index 70e9518..0000000 --- a/packages/core/src/renderers/systems/BatchSystem.js +++ /dev/null @@ -1,68 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import ObjectRenderer from '../utils/ObjectRenderer'; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ - -export default class BatchSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this manager works for. - */ - constructor(renderer) - { - super(renderer); - - /** - * An empty renderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.emptyRenderer = new ObjectRenderer(renderer); - - /** - * The currently active ObjectRenderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.currentRenderer = this.emptyRenderer; - } - - /** - * Changes the current renderer to the one given in parameter - * - * @param {PIXI.ObjectRenderer} objectRenderer - The object renderer to use. - */ - setObjectRenderer(objectRenderer) - { - if (this.currentRenderer === objectRenderer) - { - return; - } - - this.currentRenderer.stop(); - this.currentRenderer = objectRenderer; - - this.renderer.state.setState(objectRenderer.state); - - this.currentRenderer.start(); - } - - /** - * This should be called if you wish to do some custom rendering - * It will basically render anything that may be batched up such as sprites - * - */ - flush() - { - this.setObjectRenderer(this.emptyRenderer); - } - - reset() - { - this.setObjectRenderer(this.emptyRenderer); - } -} diff --git a/packages/core/src/renderers/systems/ContextSystem.js b/packages/core/src/renderers/systems/ContextSystem.js deleted file mode 100644 index 43bf827..0000000 --- a/packages/core/src/renderers/systems/ContextSystem.js +++ /dev/null @@ -1,186 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { settings } from '@pixi/settings'; - -let CONTEXT_UID = 0; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class ContextSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this.webGLVersion = 1; - - this.handleContextLost = this.handleContextLost.bind(this); - this.handleContextRestored = this.handleContextRestored.bind(this); - - this.extensions = {}; - - renderer.view.addEventListener('webglcontextlost', this.handleContextLost, false); - renderer.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); - } - - get isLost() - { - return (!this.gl || this.gl.isContextLost()); - } - - contextChange(gl) - { - this.gl = gl; - - // restore a context if it was previously lost - if (gl.isContextLost() && gl.getExtension('WEBGL_lose_context')) - { - gl.getExtension('WEBGL_lose_context').restoreContext(); - } - } - - initFromContext(gl) - { - this.gl = gl; - this.validateContext(gl); - this.renderer.gl = gl; - this.renderer.CONTEXT_UID = CONTEXT_UID++; - this.renderer.runners.contextChange.run(gl); - } - - initFromOptions(options) - { - const gl = this.createContext(this.renderer.view, options); - - this.initFromContext(gl); - } - - /** - * Helper class to create a webGL Context - * - * @class - * @memberof PIXI.glCore - * @param canvas {HTMLCanvasElement} the canvas element that we will get the context from - * @param options {Object} An options object that gets passed in to the canvas element containing the context attributes, - * see https://developer.mozilla.org/en/docs/Web/API/HTMLCanvasElement/getContext for the options available - * @return {WebGLRenderingContext} the WebGL context - */ - createContext(canvas, options) - { - let gl; - - if (settings.PREFER_WEBGL_2) - { - gl = canvas.getContext('webgl2', options); - } - - if (gl) - { - this.webGLVersion = 2; - } - else - { - this.webGLVersion = 1; - - gl = canvas.getContext('webgl', options) - || canvas.getContext('experimental-webgl', options); - - if (!gl) - { - // fail, not able to get a context - throw new Error('This browser does not support webGL. Try using the canvas renderer'); - } - } - - this.gl = gl; - - this.getExtensions(); - - return gl; - } - - getExtensions() - { - // time to set up default etensions that pixi uses.. - const gl = this.gl; - const extensions = this.extensions; - - if (this.webGLVersion === 1) - { - extensions.drawBuffers = gl.getExtension('WEBGL_draw_buffers'); - extensions.depthTexture = gl.getExtension('WEBKIT_WEBGL_depth_texture'); - extensions.floatTexture = gl.getExtension('OES_texture_float'); - extensions.loseContext = gl.getExtension('WEBGL_lose_context'); - - extensions.vertexArrayObject = gl.getExtension('OES_vertex_array_object') - || gl.getExtension('MOZ_OES_vertex_array_object') - || gl.getExtension('WEBKIT_OES_vertex_array_object'); - } - - // we don't use any specific WebGL 2 ones yet! - } - - /** - * Handles a lost webgl context - * - * @private - * @param {WebGLContextEvent} event - The context lost event. - */ - handleContextLost(event) - { - event.preventDefault(); - } - - /** - * Handles a restored webgl context - * - * @private - */ - handleContextRestored() - { - this.renderer.runners.contextChange.run(this.gl); - } - - destroy() - { - const view = this.renderer.view; - - // remove listeners - view.removeEventListener('webglcontextlost', this.handleContextLost); - view.removeEventListener('webglcontextrestored', this.handleContextRestored); - - this.gl.useProgram(null); - - if (this.extensions.loseContext) - { - this.extensions.loseContext.loseContext(); - } - } - - postrender() - { - this.gl.flush(); - } - - validateContext(gl) - { - const attributes = gl.getContextAttributes(); - - // this is going to be fairly simple for now.. but at least we have room to grow! - if (!attributes.stencil) - { - /* eslint-disable max-len */ - - /* eslint-disable no-console */ - console.warn('Provided WebGL context does not have a stencil buffer, masks may not render correctly'); - /* eslint-enable no-console */ - - /* eslint-enable max-len */ - } - } -} diff --git a/packages/core/src/renderers/systems/FramebufferSystem.js b/packages/core/src/renderers/systems/FramebufferSystem.js deleted file mode 100644 index df7902b..0000000 --- a/packages/core/src/renderers/systems/FramebufferSystem.js +++ /dev/null @@ -1,246 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { Rectangle } from '@pixi/math'; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class FramebufferSystem extends WebGLSystem -{ - /** - * Sets up the renderer context and necessary buffers. - * - * @private - */ - contextChange() - { - this.gl = this.renderer.gl; - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - this.current = null; - this.viewport = new Rectangle(); - - this.drawBufferExtension = this.renderer.context.extensions.drawBuffers; - } - - bind(framebuffer, frame) - { - const gl = this.gl; - - this.current = framebuffer; - - if (framebuffer) - { - // TODO cacheing layer! - - const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID] || this.initFramebuffer(framebuffer); - - gl.bindFramebuffer(gl.FRAMEBUFFER, fbo.framebuffer); - // makesure all textures are unbound.. - - // now check for updates... - if (fbo.dirtyId !== framebuffer.dirtyId) - { - fbo.dirtyId = framebuffer.dirtyId; - - if (fbo.dirtyFormat !== framebuffer.dirtyFormat) - { - fbo.dirtyFormat = framebuffer.dirtyFormat; - this.updateFramebuffer(framebuffer); - } - else if (fbo.dirtySize !== framebuffer.dirtySize) - { - fbo.dirtySize = framebuffer.dirtySize; - this.resizeFramebuffer(framebuffer); - } - } - - for (let i = 0; i < framebuffer.colorTextures.length; i++) - { - if (framebuffer.colorTextures[i].texturePart) - { - this.renderer.texture.unbind(framebuffer.colorTextures[i].texture); - } - else - { - this.renderer.texture.unbind(framebuffer.colorTextures[i]); - } - } - - if (framebuffer.depthTexture) - { - this.renderer.texture.unbind(framebuffer.depthTexture); - } - - if (frame) - { - this.setViewport(frame.x, frame.y, frame.width, frame.height); - } - else - { - this.setViewport(0, 0, framebuffer.width, framebuffer.height); - } - } - else - { - gl.bindFramebuffer(gl.FRAMEBUFFER, null); - - if (frame) - { - this.setViewport(frame.x, frame.y, frame.width, frame.height); - } - else - { - this.setViewport(0, 0, this.renderer.width, this.renderer.height); - } - } - } - - setViewport(x, y, width, height) - { - const v = this.viewport; - - if (v.width !== width || v.height !== height || v.x !== x || v.y !== y) - { - v.x = x; - v.y = y; - v.width = width; - v.height = height; - - this.gl.viewport(x, y, width, height); - } - } - - get size() - { - if (this.current) - { - // TODO store temp - return { x: 0, y: 0, width: this.current.width, height: this.current.height }; - } - - return { x: 0, y: 0, width: this.renderer.width, height: this.renderer.height }; - } - - clear(r, g, b, a) - { - const gl = this.gl; - - // TODO clear color can be set only one right? - gl.clearColor(r, g, b, a); - gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); - } - - // private functions... - - initFramebuffer(framebuffer) - { - const gl = this.gl; - - // TODO - make this a class? - const fbo = { - framebuffer: gl.createFramebuffer(), - stencil: null, - dirtyId: 0, - dirtyFormat: 0, - dirtySize: 0, - }; - - framebuffer.glFrameBuffers[this.CONTEXT_UID] = fbo; - - return fbo; - } - - resizeFramebuffer(framebuffer) - { - const gl = this.gl; - - if (framebuffer.stencil || framebuffer.depth) - { - gl.bindRenderbuffer(gl.RENDERBUFFER, this.stencil); - gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); - } - } - - updateFramebuffer(framebuffer) - { - const gl = this.gl; - - const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID]; - - // bind the color texture - const colorTextures = framebuffer.colorTextures; - - let count = colorTextures.length; - - if (!this.drawBufferExtension) - { - count = Math.min(count, 1); - } - - const activeTextures = []; - - for (let i = 0; i < count; i++) - { - const texture = framebuffer.colorTextures[i]; - - if (texture.texturePart) - { - this.renderer.texture.bind(texture.texture, 0); - - gl.framebufferTexture2D(gl.FRAMEBUFFER, - gl.COLOR_ATTACHMENT0 + i, - gl.TEXTURE_CUBE_MAP_NEGATIVE_X + texture.side, - texture.texture._glTextures[this.CONTEXT_UID].texture, - 0); - } - else - { - this.renderer.texture.bind(texture, 0); - - gl.framebufferTexture2D(gl.FRAMEBUFFER, - gl.COLOR_ATTACHMENT0 + i, - gl.TEXTURE_2D, - texture._glTextures[this.CONTEXT_UID].texture, - 0); - } - - activeTextures.push(gl.COLOR_ATTACHMENT0 + i); - } - - if (this.drawBufferExtension && activeTextures.length > 1) - { - this.drawBufferExtension.drawBuffersWEBGL(activeTextures); - } - - if (framebuffer.depthTexture) - { - const depthTextureExt = this.renderer.context.extensions.depthTexture; - - if (depthTextureExt) - { - const depthTexture = framebuffer.depthTexture; - - this.renderer.texture.bind(depthTexture, 0); - - gl.framebufferTexture2D(gl.FRAMEBUFFER, - gl.DEPTH_ATTACHMENT, - gl.TEXTURE_2D, - depthTexture._glTextures[this.CONTEXT_UID].texture, - 0); - } - } - - if (framebuffer.stencil || framebuffer.depth) - { - fbo.stencil = gl.createRenderbuffer(); - - gl.bindRenderbuffer(gl.RENDERBUFFER, fbo.stencil); - - // TODO.. this is depth AND stencil? - gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, fbo.stencil); - gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); - // fbo.enableStencil(); - } - } -} diff --git a/packages/core/src/renderers/systems/MaskSystem.js b/packages/core/src/renderers/systems/MaskSystem.js deleted file mode 100644 index d9c4b1a..0000000 --- a/packages/core/src/renderers/systems/MaskSystem.js +++ /dev/null @@ -1,199 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import AlphaMaskFilter from '../filters/spriteMask/SpriteMaskFilter'; - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class MaskSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - // TODO - we don't need both! - this.scissor = false; - this.scissorData = null; - this.scissorRenderTarget = null; - - this.enableScissor = false; - - this.alphaMaskPool = []; - this.alphaMaskIndex = 0; - } - - /** - * Applies the Mask and adds it to the current filter stack. - * - * @param {PIXI.DisplayObject} target - Display Object to push the mask to - * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. - */ - push(target, maskData) - { - // TODO the root check means scissor rect will not - // be used on render textures more info here: - // https://github.com/pixijs/pixi.js/pull/3545 - - if (maskData.texture) - { - this.pushSpriteMask(target, maskData); - } - else if (this.enableScissor - && !this.scissor - && this.renderer._activeRenderTarget.root - && !this.renderer.stencil.stencilMaskStack.length - && maskData.isFastRect()) - { - const matrix = maskData.worldTransform; - - let rot = Math.atan2(matrix.b, matrix.a); - - // use the nearest degree! - rot = Math.round(rot * (180 / Math.PI)); - - if (rot % 90) - { - this.pushStencilMask(maskData); - } - else - { - this.pushScissorMask(target, maskData); - } - } - else - { - this.pushStencilMask(maskData); - } - } - - /** - * Removes the last mask from the mask stack and doesn't return it. - * - * @param {PIXI.DisplayObject} target - Display Object to pop the mask from - * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. - */ - pop(target, maskData) - { - if (maskData.texture) - { - this.popSpriteMask(target, maskData); - } - else if (this.enableScissor && !this.renderer.stencil.stencilMaskStack.length) - { - this.popScissorMask(target, maskData); - } - else - { - this.popStencilMask(target, maskData); - } - } - - /** - * Applies the Mask and adds it to the current filter stack. - * - * @param {PIXI.RenderTarget} target - Display Object to push the sprite mask to - * @param {PIXI.Sprite} maskData - Sprite to be used as the mask - */ - pushSpriteMask(target, maskData) - { - let alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; - - if (!alphaMaskFilter) - { - alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; - } - - alphaMaskFilter[0].resolution = this.renderer.resolution; - alphaMaskFilter[0].maskSprite = maskData; - - // TODO - may cause issues! - target.filterArea = maskData.getBounds(true); - - this.renderer.filterSystem.pushFilter(target, alphaMaskFilter); - - this.alphaMaskIndex++; - } - - /** - * Removes the last filter from the filter stack and doesn't return it. - * - */ - popSpriteMask() - { - this.renderer.filterSystem.popFilter(); - this.alphaMaskIndex--; - } - - /** - * Applies the Mask and adds it to the current filter stack. - * - * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. - */ - pushStencilMask(maskData) - { - this.renderer.batch.flush(); - this.renderer.stencil.pushStencil(maskData); - } - - /** - * Removes the last filter from the filter stack and doesn't return it. - * - */ - popStencilMask() - { - // this.renderer.currentRenderer.stop(); - this.renderer.stencil.popStencil(); - } - - /** - * - * @param {PIXI.DisplayObject} target - Display Object to push the mask to - * @param {PIXI.Graphics} maskData - The masking data. - */ - pushScissorMask(target, maskData) - { - maskData.renderable = true; - - const renderTarget = this.renderer._activeRenderTarget; - - const bounds = maskData.getBounds(); - - bounds.fit(renderTarget.size); - maskData.renderable = false; - - this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); - - const resolution = this.renderer.resolution; - - this.renderer.gl.scissor( - bounds.x * resolution, - (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, - bounds.width * resolution, - bounds.height * resolution - ); - - this.scissorRenderTarget = renderTarget; - this.scissorData = maskData; - this.scissor = true; - } - - /** - * - * - */ - popScissorMask() - { - this.scissorRenderTarget = null; - this.scissorData = null; - this.scissor = false; - - // must be scissor! - const gl = this.renderer.gl; - - gl.disable(gl.SCISSOR_TEST); - } -} diff --git a/packages/core/src/renderers/systems/ProjectionSystem.js b/packages/core/src/renderers/systems/ProjectionSystem.js deleted file mode 100644 index ec5efdb..0000000 --- a/packages/core/src/renderers/systems/ProjectionSystem.js +++ /dev/null @@ -1,73 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { Matrix } from '@pixi/math'; - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ - -export default class ProjectionSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this.projectionMatrix = new Matrix(); - } - - update(destinationFrame, sourceFrame, resolution, root) - { - this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; - this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; - - this.calculateProjection(this.destinationFrame, this.sourceFrame, resolution, root); - - this.renderer.globalUniforms.uniforms.projectionMatrix = this.projectionMatrix; - this.renderer.globalUniforms.update(); - } - - /** - * Updates the projection matrix based on a projection frame (which is a rectangle) - * - * @param {Rectangle} destinationFrame - The destination frame. - * @param {Rectangle} sourceFrame - The source frame. - */ - calculateProjection(destinationFrame, sourceFrame, resolution, root) - { - const pm = this.projectionMatrix; - - // I don't think we will need this line.. - // pm.identity(); - - if (!root) - { - pm.a = (1 / destinationFrame.width * 2) * resolution; - pm.d = (1 / destinationFrame.height * 2) * resolution; - - pm.tx = -1 - (sourceFrame.x * pm.a); - pm.ty = -1 - (sourceFrame.y * pm.d); - } - else - { - pm.a = (1 / destinationFrame.width * 2) * resolution; - pm.d = (-1 / destinationFrame.height * 2) * resolution; - - pm.tx = -1 - (sourceFrame.x * pm.a); - pm.ty = 1 - (sourceFrame.y * pm.d); - } - } - - /** - * Sets the transform of the active render target to the given matrix - * - * @param {PIXI.Matrix} matrix - The transformation matrix - */ - setTransform()// matrix) - { - // this._activeRenderTarget.transform = matrix; - } -} diff --git a/packages/core/src/renderers/systems/RenderTextureSystem.js b/packages/core/src/renderers/systems/RenderTextureSystem.js deleted file mode 100644 index a133ef1..0000000 --- a/packages/core/src/renderers/systems/RenderTextureSystem.js +++ /dev/null @@ -1,115 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { Rectangle } from '@pixi/math'; - -const tempRect = new Rectangle(); - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ - -export default class RenderTextureSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this.clearColor = renderer._backgroundColorRgba; - - // TODO moe this property somewhere else! - this.defaultMaskStack = []; - this.defaultFilterStack = [{}]; - - // empty render texture? - this.renderTexture = null; - - this.destinationFrame = new Rectangle(); - } - - bind(renderTexture, sourceFrame, destinationFrame) - { - // TODO - do we want this?? - if (this.renderTexture === renderTexture) return; - this.renderTexture = renderTexture; - - const renderer = this.renderer; - - if (renderTexture) - { - const baseTexture = renderTexture.baseTexture; - - if (!destinationFrame) - { - tempRect.width = baseTexture.realWidth; - tempRect.height = baseTexture.realHeight; - - destinationFrame = tempRect; - } - - if (!sourceFrame) - { - sourceFrame = destinationFrame; - } - - this.renderer.framebuffer.bind(baseTexture.frameBuffer, destinationFrame); - - this.renderer.projection.update(destinationFrame, sourceFrame, baseTexture.resolution, false); - this.renderer.stencil.setMaskStack(baseTexture.stencilMaskStack); - } - else - { - // TODO these validation checks happen deeper down.. - // thing they can be avoided.. - if (!destinationFrame) - { - tempRect.width = renderer.width; - tempRect.height = renderer.height; - - destinationFrame = tempRect; - } - - if (!sourceFrame) - { - sourceFrame = destinationFrame; - } - - renderer.framebuffer.bind(null, destinationFrame); - - // TODO store this.. - this.renderer.projection.update(destinationFrame, sourceFrame, this.renderer.resolution, true); - this.renderer.stencil.setMaskStack(this.defaultMaskStack); - } - - this.destinationFrame.copyFrom(destinationFrame); - } - - /** - * Erases the render texture and fills the drawing area with a colour - * - * @param {number} [clearColor] - The colour - * @return {PIXI.Renderer} Returns itself. - */ - clear(clearColor) - { - if (this.renderTexture) - { - clearColor = clearColor || this.renderTexture.baseTexture.clearColor; - } - else - { - clearColor = clearColor || this.clearColor; - } - - this.renderer.framebuffer.clear(clearColor[0], clearColor[1], clearColor[2], clearColor[3]); - } - - resize()// screenWidth, screenHeight) - { - // resize the root only! - this.bind(null); - } -} diff --git a/packages/core/src/renderers/systems/StateSystem.js b/packages/core/src/renderers/systems/StateSystem.js deleted file mode 100755 index d7f4caf..0000000 --- a/packages/core/src/renderers/systems/StateSystem.js +++ /dev/null @@ -1,288 +0,0 @@ -import mapWebGLBlendModesToPixi from '../utils/mapWebGLBlendModesToPixi'; -import WebGLSystem from './WebGLSystem'; -import WebGLState from '../State'; - -const BLEND = 0; -const OFFSET = 1; -const CULLING = 2; -const DEPTH_TEST = 3; -const WINDING = 4; - -/** - * A WebGL state machines - * - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class StateSystem extends WebGLSystem -{ - /** - * @param {WebGLRenderingContext} gl - The current WebGL rendering context - */ - constructor(renderer) - { - super(renderer); - - this.gl = null; - - this.maxAttribs = null; - - // check we have vao.. - this.nativeVaoExtension = null; - - this.attribState = null; - - this.stateId = 0; - this.polygonOffset = 0; - this.blendMode = 17; - - this.map = []; - - // map functions for when we set state.. - this.map[BLEND] = this.setBlend; - this.map[OFFSET] = this.setOffset; - this.map[CULLING] = this.setCullFace; - this.map[DEPTH_TEST] = this.setDepthTest; - this.map[WINDING] = this.setFrontFace; - - this.checks = []; - - this.defaultState = new WebGLState(); - this.defaultState.blend = true; - this.defaultState.depth = true; - } - - contextChange(gl) - { - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; - - this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); - - // check we have vao.. - this.nativeVaoExtension = ( - gl.getExtension('OES_vertex_array_object') - || gl.getExtension('MOZ_OES_vertex_array_object') - || gl.getExtension('WEBKIT_OES_vertex_array_object') - ); - - this.attribState = { - tempAttribState: new Array(this.maxAttribs), - attribState: new Array(this.maxAttribs), - }; - - this.blendModes = mapWebGLBlendModesToPixi(gl); - - this.setState(this.defaultState); - - this.reset(); - } - - /** - * Sets the current state - * - * @param {*} state - The state to set. - */ - setState(state) - { - state = state || this.defaultState; - - // TODO maybe to an object check? ( this.state === state )? - if (this.stateId !== state.data) - { - let diff = this.stateId ^ state.data; - let i = 0; - - // order from least to most common - while (diff) - { - if (diff & 1) - { - // state change! - this.map[i].call(this, !!(state.data & (1 << i))); - } - - diff = diff >> 1; - i++; - } - - this.stateId = state.data; - } - - // based on the above settings we check for specific modes.. - // for example if blend is active we check and set the blend modes - // or of polygon offset is active we check the poly depth. - for (let i = 0; i < this.checks.length; i++) - { - this.checks[i](this, state); - } - } - - /** - * Enables or disabled blending. - * - * @param {boolean} value - Turn on or off webgl blending. - */ - setBlend(value) - { - this.updateCheck(StateSystem.checkBlendMode, value); - - this.gl[value ? 'enable' : 'disable'](this.gl.BLEND); - } - - /** - * Enables or disable polygon offset fill - * - * @param {boolean} value - Turn on or off webgl polygon offset testing. - */ - setOffset(value) - { - this.gl[value ? 'enable' : 'disable'](this.gl.POLYGON_OFFSET_FILL); - } - - /** - * Sets whether to enable or disable depth test. - * - * @param {boolean} value - Turn on or off webgl depth testing. - */ - setDepthTest(value) - { - this.gl[value ? 'enable' : 'disable'](this.gl.DEPTH_TEST); - } - - /** - * Sets whether to enable or disable cull face. - * - * @param {boolean} value - Turn on or off webgl cull face. - */ - setCullFace(value) - { - this.gl[value ? 'enable' : 'disable'](this.gl.CULL_FACE); - } - - /** - * Sets the gl front face. - * - * @param {boolean} value - true is clockwise and false is counter-clockwise - */ - setFrontFace(value) - { - this.gl.frontFace(this.gl[value ? 'CW' : 'CCW']); - } - - /** - * Sets the blend mode. - * - * @param {number} value - The blend mode to set to. - */ - setBlendMode(value) - { - if (value === this.blendMode) - { - return; - } - - this.blendMode = value; - this.gl.blendFunc(this.blendModes[value][0], this.blendModes[value][1]); - } - - /** - * Sets the polygon offset. - * - * @param {number} value - the polygon offset - * @param {number} scale - the polygon offset scale - */ - setPolygonOffset(value, scale) - { - this.gl.polygonOffset(value, scale); - } - - /** - * Disables all the vaos in use - * - */ - resetAttributes() - { - for (let i = 0; i < this.attribState.tempAttribState.length; i++) - { - this.attribState.tempAttribState[i] = 0; - } - - for (let i = 0; i < this.attribState.attribState.length; i++) - { - this.attribState.attribState[i] = 0; - } - - // im going to assume one is always active for performance reasons. - for (let i = 1; i < this.maxAttribs; i++) - { - this.gl.disableVertexAttribArray(i); - } - } - - // used - /** - * Resets all the logic and disables the vaos - */ - reset() - { - // unbind any VAO if they exist.. - if (this.nativeVaoExtension) - { - this.nativeVaoExtension.bindVertexArrayOES(null); - } - - // reset all attributes.. - this.resetAttributes(); - - this.gl.pixelStorei(this.gl.UNPACK_FLIP_Y_WEBGL, false); - - this.setBlendMode(0); - - // TODO? - // this.setState(this.defaultState); - } - - /** - * checks to see which updates should be checked based on which settings have been activated - * for example if blend is enabled then we shold check the blend modes each time the state is changed - * or if poygon fill is activated then we need to check if the polygone offset changes. - * The idea is that we only check what we have too. - * - * @param {Function} func the checking function to add or remove - * @param {boolean} value should the check function be added or removed. - */ - updateCheck(func, value) - { - const index = this.checks.indexOf(func); - - if (value && index === -1) - { - this.checks.push(func); - } - else if (!value && index !== -1) - { - this.checks.splice(index, 1); - } - } - - /** - * A private little wrapper function that we call to check the blend mode. - * - * @static - * @private - * @param {PIXI.StateSystem} System the System to perform the state check on - * @param {PIXI.State} state the state that the blendMode will pulled from - */ - static checkBlendMode(system, state) - { - system.setBlendMode(state.blendMode); - } - - // TODO - add polygon offset? -} diff --git a/packages/core/src/renderers/systems/StencilSystem.js b/packages/core/src/renderers/systems/StencilSystem.js deleted file mode 100644 index 5d538cb..0000000 --- a/packages/core/src/renderers/systems/StencilSystem.js +++ /dev/null @@ -1,133 +0,0 @@ -import WebGLSystem from './WebGLSystem'; - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class StencilSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - this.stencilMaskStack = []; - } - - /** - * Changes the mask stack that is used by this System. - * - * @param {PIXI.Graphics[]} stencilMaskStack - The mask stack - */ - setMaskStack(stencilMaskStack) - { - const gl = this.renderer.gl; - - if (stencilMaskStack.length !== this.stencilMaskStack.length) - { - if (stencilMaskStack.length === 0) - { - gl.disable(gl.STENCIL_TEST); - } - else - { - gl.enable(gl.STENCIL_TEST); - } - } - - this.stencilMaskStack = stencilMaskStack; - } - - /** - * Applies the Mask and adds it to the current stencil stack. @alvin - * - * @param {PIXI.Graphics} graphics - The mask - */ - pushStencil(graphics) - { - this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); - - // this.renderer._activeRenderTarget.attachStencilBuffer(); - - const gl = this.renderer.gl; - const prevMaskCount = this.stencilMaskStack.length; - - if (prevMaskCount === 0) - { - gl.enable(gl.STENCIL_TEST); - } - - this.stencilMaskStack.push(graphics); - - // Increment the refference stencil value where the new mask overlaps with the old ones. - gl.colorMask(false, false, false, false); - gl.stencilFunc(gl.EQUAL, prevMaskCount, this._getBitwiseMask()); - gl.stencilOp(gl.KEEP, gl.KEEP, gl.INCR); - this.renderer.plugins.graphics.render(graphics); - - this._useCurrent(); - } - - /** - * Removes the last mask from the stencil stack. @alvin - */ - popStencil() - { - this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); - - const gl = this.renderer.gl; - const graphics = this.stencilMaskStack.pop(); - - if (this.stencilMaskStack.length === 0) - { - // the stack is empty! - gl.disable(gl.STENCIL_TEST); - gl.clear(gl.STENCIL_BUFFER_BIT); - gl.clearStencil(0); - } - else - { - // Decrement the refference stencil value where the popped mask overlaps with the other ones - gl.colorMask(false, false, false, false); - gl.stencilOp(gl.KEEP, gl.KEEP, gl.DECR); - this.renderer.plugins.graphics.render(graphics); - - this._useCurrent(); - } - } - - /** - * Setup renderer to use the current stencil data. - */ - _useCurrent() - { - const gl = this.renderer.gl; - - gl.colorMask(true, true, true, true); - gl.stencilFunc(gl.EQUAL, this.stencilMaskStack.length, this._getBitwiseMask()); - gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP); - } - - /** - * Fill 1s equal to the number of acitve stencil masks. - * - * @return {number} The bitwise mask. - */ - _getBitwiseMask() - { - return (1 << this.stencilMaskStack.length) - 1; - } - - /** - * Destroys the mask stack. - * - */ - destroy() - { - super.destroy(this); - - this.stencilMaskStack = null; - } -} diff --git a/packages/core/src/renderers/systems/WebGLSystem.js b/packages/core/src/renderers/systems/WebGLSystem.js deleted file mode 100644 index d3d5b31..0000000 --- a/packages/core/src/renderers/systems/WebGLSystem.js +++ /dev/null @@ -1,41 +0,0 @@ -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this manager works for. - */ - constructor(renderer) - { - /** - * The renderer this manager works for. - * - * @member {PIXI.Renderer} - */ - this.renderer = renderer; - - this.renderer.runners.contextChange.add(this); - } - - /** - * Generic method called when there is a WebGL context change. - * - */ - contextChange() - { - // do some codes init! - } - - /** - * Generic destroy methods to be overridden by the subclass - * - */ - destroy() - { - this.renderer.runners.contextChange.remove(this); - this.renderer = null; - } -} diff --git a/packages/core/src/renderers/systems/filter/FilterSystem.js b/packages/core/src/renderers/systems/filter/FilterSystem.js deleted file mode 100644 index e615037..0000000 --- a/packages/core/src/renderers/systems/filter/FilterSystem.js +++ /dev/null @@ -1,431 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; - -import RenderTexture from '../../../textures/RenderTexture'; -import Quad from '../../utils/Quad'; -import QuadUv from '../../utils/QuadUv'; -import { Rectangle } from '@pixi/math'; -import * as filterTransforms from '../../filters/filterTransforms'; -import bitTwiddle from 'bit-twiddle'; -import UniformGroup from '../../../shader/UniformGroup'; -import { DRAW_MODES } from '../../../../../constants'; - -// -/** - * @ignore - * @class - */ -class FilterState -{ - /** - * - */ - constructor() - { - this.renderTexture = null; - this.sourceFrame = new Rectangle(); - this.destinationFrame = new Rectangle(); - this.filters = []; - this.target = null; - this.legacy = false; - this.resolution = 1; - } -} - -/** - * @class - * @memberof PIXI.systems - * @extends PIXI.systems.WebGLSystem - */ -export default class FilterSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - /** - * stores a bunch of PO2 textures used for filtering - * @type {Object} - */ - this.texturePool = {}; - - /** - * a pool for storing filter states, save us creating new ones each tick - * @type {Array} - */ - this.statePool = []; - - /** - * A very simple geometry used when drawing a filter effect to the screen - * @type {Quad} - */ - this.quad = new Quad(); - - this.quadUv = new QuadUv(); - - /** - * Temporary rect for maths - * @type {PIXI.Rectangle} - */ - this.tempRect = new Rectangle(); - - this.activeState = {}; - - /** - * this uniform group is attached to filter uniforms when used - * @type {UniformGroup} - */ - this.globalUniforms = new UniformGroup({ - sourceFrame: this.tempRect, - destinationFrame: this.tempRect, - - // legacy variables - filterArea: new Float32Array(4), - filterClamp: new Float32Array(4), - }, true); - } - - /** - * Adds a new filter to the System. - * - * @param {PIXI.DisplayObject} target - The target of the filter to render. - * @param {PIXI.Filter[]} filters - The filters to apply. - */ - push(target, filters) - { - const renderer = this.renderer; - const filterStack = this.renderer.renderTexture.defaultFilterStack; - const state = this.statePool.pop() || new FilterState(); - - let resolution = filters[0].resolution; - let padding = filters[0].padding; - let autoFit = filters[0].autoFit; - let legacy = filters[0].legacy; - - for (let i = 1; i < filters.length; i++) - { - const filter = filters[i]; - - // lets use the lowest resolution.. - resolution = Math.min(resolution, filter.resolution); - // and the largest amount of padding! - padding = Math.max(padding, filter.padding); - // only auto fit if all filters are autofit - autoFit = autoFit || filter.autoFit; - - legacy = legacy || filter.legacy; - } - - filterStack.push(state); - - state.resolution = resolution; - - state.legacy = legacy; - - // round to whole number based on resolution - // TODO move that to the shader too? - state.sourceFrame = target.filterArea || target.getBounds(true); - - state.sourceFrame.pad(padding); - - if (autoFit) - { - state.sourceFrame.fit(this.renderer.renderTexture.destinationFrame); - } - - state.sourceFrame.round(resolution); - - state.renderTexture = this.getPotFilterTexture(state.sourceFrame.width, state.sourceFrame.height, resolution); - state.filters = filters; - - state.destinationFrame.width = state.renderTexture.width; - state.destinationFrame.height = state.renderTexture.height; - - state.renderTexture.filterFrame = state.sourceFrame; - renderer.renderTexture.bind(state.renderTexture, state.sourceFrame);// /, state.destinationFrame); - renderer.renderTexture.clear(); - } - - /** - * Pops off the filter and applies it. - * - */ - pop() - { - const renderer = this.renderer; - const filterStack = renderer.renderTexture.defaultFilterStack; - const state = filterStack.pop(); - const filters = state.filters; - - this.activeState = state; - - const globalUniforms = this.globalUniforms.uniforms; - - globalUniforms.sourceFrame = state.sourceFrame; - globalUniforms.destinationFrame = state.destinationFrame; - globalUniforms.resolution = state.resolution; - - // only update the rect if its legacy.. - if (state.legacy) - { - const filterArea = globalUniforms.filterArea; - const filterClamp = globalUniforms.filterClamp; - - filterArea[0] = state.destinationFrame.width; - filterArea[1] = state.destinationFrame.height; - filterArea[2] = state.sourceFrame.x; - filterArea[3] = state.sourceFrame.y; - - filterClamp[0] = 0.5 / state.resolution / state.destinationFrame.width; - filterClamp[1] = 0.5 / state.resolution / state.destinationFrame.height; - filterClamp[2] = (state.sourceFrame.width - 0.5) / state.resolution / state.destinationFrame.width; - filterClamp[3] = (state.sourceFrame.height - 0.5) / state.resolution / state.destinationFrame.height; - } - - this.globalUniforms.update(); - - const lastState = filterStack[filterStack.length - 1]; - - if (filters.length === 1) - { - filters[0].apply(this, state.renderTexture, lastState.renderTexture, false, state); - - this.returnFilterTexture(state.renderTexture); - } - else - { - let flip = state.renderTexture; - let flop = this.getPotFilterTexture( - flip.width, - flip.height, - state.resolution - ); - - flop.filterFrame = flip.filterFrame; - - let i = 0; - - for (i = 0; i < filters.length - 1; ++i) - { - filters[i].apply(this, flip, flop, true, state); - - const t = flip; - - flip = flop; - flop = t; - } - - filters[i].apply(this, flip, lastState.renderTexture, false, state); - - this.returnFilterTexture(flip); - this.returnFilterTexture(flop); - } - - this.statePool.push(state); - } - - /** - * Draws a filter. - * - * @param {PIXI.Filter} filter - The filter to draw. - * @param {PIXI.RenderTarget} input - The input render target. - * @param {PIXI.RenderTarget} output - The target to output to. - * @param {boolean} clear - Should the output be cleared before rendering to it - */ - applyFilter(filter, input, output, clear) - { - const renderer = this.renderer; - - renderer.renderTexture.bind(output, output ? output.filterFrame : null); - - if (clear) - { - // gl.disable(gl.SCISSOR_TEST); - renderer.renderTexture.clear(); - // gl.enable(gl.SCISSOR_TEST); - } - - // set the uniforms.. - filter.uniforms.uSampler = input; - filter.uniforms.filterGlobals = this.globalUniforms; - - // TODO make it so that the order of this does not matter.. - // because it does at the moment cos of global uniforms. - // they need to get resynced - - renderer.state.setState(filter.state); - renderer.shader.bind(filter); - - if (filter.legacy) - { - this.quadUv.map(input._frame, input.filterFrame); - - renderer.geometry.bind(this.quadUv); - renderer.geometry.draw(DRAW_MODES.TRIANGLES); - } - else - { - renderer.geometry.bind(this.quad); - renderer.geometry.draw(DRAW_MODES.TRIANGLE_STRIP); - } - } - - /** - * Calculates the mapped matrix. - * - * TODO playing around here.. this is temporary - (will end up in the shader) - * this returns a matrix that will normalise map filter cords in the filter to screen space - * - * @param {PIXI.Matrix} outputMatrix - the matrix to output to. - * @return {PIXI.Matrix} The mapped matrix. - */ - calculateScreenSpaceMatrix(outputMatrix) - { - const currentState = this.activeState; - - return filterTransforms.calculateScreenSpaceMatrix( - outputMatrix, - currentState.sourceFrame, - currentState.destinationFrame - ); - } - - /** - * This will map the filter coord so that a texture can be used based on the transform of a sprite - * - * @param {PIXI.Matrix} outputMatrix - The matrix to output to. - * @param {PIXI.Sprite} sprite - The sprite to map to. - * @return {PIXI.Matrix} The mapped matrix. - */ - calculateSpriteMatrix(outputMatrix, sprite) - { - const currentState = this.activeState; - - return filterTransforms.calculateSpriteMatrix( - outputMatrix, - currentState.sourceFrame, - currentState.destinationFrame, - sprite - ); - } - - /** - * Destroys this Filter System. - * - * @param {boolean} [contextLost=false] context was lost, do not free shaders - * - */ - destroy(contextLost = false) - { - if (!contextLost) - { - this.emptyPool(); - } - else - { - this.texturePool = {}; - } - } - - /** - * Gets a Power-of-Two render texture. - * - * TODO move to a seperate class could be on renderer? - * also - could cause issue with multiple contexts? - * - * @private - * @param {WebGLRenderingContext} gl - The webgl rendering context - * @param {number} minWidth - The minimum width of the render target. - * @param {number} minHeight - The minimum height of the render target. - * @param {number} resolution - The resolution of the render target. - * @return {PIXI.RenderTarget} The new render target. - */ - getPotFilterTexture(minWidth, minHeight, resolution) - { - minWidth = bitTwiddle.nextPow2(minWidth); - minHeight = bitTwiddle.nextPow2(minHeight); - resolution = resolution || 1; - - const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); - - if (!this.texturePool[key]) - { - this.texturePool[key] = []; - } - - let renderTexture = this.texturePool[key].pop(); - - if (!renderTexture) - { - // temporary bypass cache.. - // internally - this will cause a texture to be bound.. - renderTexture = RenderTexture.create({ - width: minWidth, - height: minHeight, - resolution, - }); - } - - return renderTexture; - } - - /** - * Gets extra render texture to use inside current filter - * - * @param {number} resolution resolution of the renderTexture - * @returns {PIXI.RenderTexture} - */ - getFilterTexture(resolution) - { - const rt = this.activeState.renderTexture; - - const filterTexture = this.getPotFilterTexture(rt.width, rt.height, resolution || rt.baseTexture.resolution); - - filterTexture.filterFrame = rt.filterFrame; - - return filterTexture; - } - - /** - * Frees a render texture back into the pool. - * - * @param {PIXI.RenderTarget} renderTexture - The renderTarget to free - */ - returnFilterTexture(renderTexture) - { - renderTexture.filterFrame = null; - - const base = renderTexture.baseTexture; - - const minWidth = base.width; - const minHeight = base.height; - - const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); - - this.texturePool[key].push(renderTexture); - } - - /** - * Empties the texture pool. - * - */ - emptyPool() - { - for (const i in this.texturePool) - { - const textures = this.texturePool[i]; - - if (textures) - { - for (let j = 0; j < textures.length; j++) - { - textures[j].destroy(true); - } - } - } - - this.texturePool = {}; - } -} diff --git a/packages/core/src/renderers/systems/geometry/GLBuffer.js b/packages/core/src/renderers/systems/geometry/GLBuffer.js deleted file mode 100644 index 0477e8a..0000000 --- a/packages/core/src/renderers/systems/geometry/GLBuffer.js +++ /dev/null @@ -1,9 +0,0 @@ -export default class GLBuffer -{ - constructor(buffer) - { - this.buffer = buffer; - this.updateID = -1; - this.byteLength = -1; - } -} diff --git a/packages/core/src/renderers/systems/geometry/GeometrySystem.js b/packages/core/src/renderers/systems/geometry/GeometrySystem.js deleted file mode 100644 index 26c8e9e..0000000 --- a/packages/core/src/renderers/systems/geometry/GeometrySystem.js +++ /dev/null @@ -1,388 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; -import GLBuffer from './GLBuffer'; - -const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class GeometrySystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this._activeGeometry = null; - this._activeVao = null; - - this.hasVao = true; - this.hasInstance = true; - } - - /** - * Sets up the renderer context and necessary buffers. - * - * @private - */ - contextChange() - { - const gl = this.gl = this.renderer.gl; - - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - - // webgl2 - if (!gl.createVertexArray) - { - // webgl 1! - let nativeVaoExtension = this.renderer.context.extensions.vertexArrayObject; - - if (this.renderer.legacy) - { - nativeVaoExtension = null; - } - - if (nativeVaoExtension) - { - gl.createVertexArray = () => - nativeVaoExtension.createVertexArrayOES(); - - gl.bindVertexArray = (vao) => - nativeVaoExtension.bindVertexArrayOES(vao); - - gl.deleteVertexArray = (vao) => - nativeVaoExtension.deleteVertexArrayOES(vao); - } - else - { - this.hasVao = false; - gl.createVertexArray = () => - { - // empty - }; - - gl.bindVertexArray = () => - { - // empty - }; - - gl.deleteVertexArray = () => - { - // empty - }; - } - } - - if (!gl.vertexAttribDivisor) - { - const instanceExt = gl.getExtension('ANGLE_instanced_arrays'); - - if (instanceExt) - { - gl.vertexAttribDivisor = (a, b) => - instanceExt.vertexAttribDivisorANGLE(a, b); - - gl.drawElementsInstanced = (a, b, c, d, e) => - instanceExt.drawElementsInstancedANGLE(a, b, c, d, e); - - gl.drawArraysInstanced = (a, b, c, d) => - instanceExt.drawArraysInstancedANGLE(a, b, c, d); - } - else - { - this.hasInstance = false; - } - } - } - - /** - * Binds geometry so that is can be drawn. Creating a Vao if required - * @private - * @param {PIXI.Geometry} geometry instance of geometry to bind - */ - bind(geometry, shader) - { - shader = shader || this.renderer.shader.shader; - - const gl = this.gl; - - // not sure the best way to address this.. - // currently different shaders require different VAOs for the same geometry - // Still mulling over the best way to solve this one.. - // will likely need to modify the shader attribute locations at run time! - let vaos = geometry.glVertexArrayObjects[this.CONTEXT_UID]; - - if (!vaos) - { - geometry.glVertexArrayObjects[this.CONTEXT_UID] = vaos = {}; - } - - const vao = vaos[shader.program.id] || this.initGeometryVao(geometry, shader.program); - - this._activeGeometry = geometry; - - if (this._activeVao !== vao) - { - this._activeVao = vao; - - if (this.hasVao) - { - gl.bindVertexArray(vao); - } - else - { - this.activateVao(geometry, shader.program); - } - } - - // TODO - optimise later! - // don't need to loop through if nothing changed! - // maybe look to add an 'autoupdate' to geometry? - this.updateBuffers(); - } - - reset() - { - this.unbind(); - } - - updateBuffers() - { - const geometry = this._activeGeometry; - const gl = this.gl; - - for (let i = 0; i < geometry.buffers.length; i++) - { - const buffer = geometry.buffers[i]; - - const glBuffer = buffer._glBuffers[this.CONTEXT_UID]; - - if (buffer._updateID !== glBuffer.updateID) - { - glBuffer.updateID = buffer._updateID; - - // TODO can cache this on buffer! maybe added a getter / setter? - const type = buffer.index ? gl.ELEMENT_ARRAY_BUFFER : gl.ARRAY_BUFFER; - const drawType = buffer.static ? gl.STATIC_DRAW : gl.DYNAMIC_DRAW; - - gl.bindBuffer(type, glBuffer.buffer); - - if (glBuffer.byteLength >= buffer.data.byteLength) - { - // offset is always zero for now! - gl.bufferSubData(type, 0, buffer.data); - } - else - { - gl.bufferData(type, buffer.data, drawType); - } - } - } - } - - checkCompatability(geometry, program) - { - // geometry must have at least all the attributes that the shader requires. - const geometryAttributes = geometry.attributes; - const shaderAttributes = program.attributeData; - - for (const j in shaderAttributes) - { - if (!geometryAttributes[j]) - { - throw new Error(`shader and geometry incompatible, geometry missing the "${j}" attribute`); - } - } - } - - /** - * Creates a Vao with the same structure as the geometry and stores it on the geometry. - * @private - * @param {PIXI.Geometry} geometry instance of geometry to to generate Vao for - */ - initGeometryVao(geometry, program) - { - this.checkCompatability(geometry, program); - - const gl = this.gl; - const CONTEXT_UID = this.CONTEXT_UID; - const buffers = geometry.buffers; - const attributes = geometry.attributes; - - const tempStride = {}; - const tempStart = {}; - - for (const j in buffers) - { - tempStride[j] = 0; - tempStart[j] = 0; - } - - for (const j in attributes) - { - if (!attributes[j].size && program.attributeData[j]) - { - attributes[j].size = program.attributeData[j].size; - } - - tempStride[attributes[j].buffer] += attributes[j].size * byteSizeMap[attributes[j].type]; - } - - for (const j in attributes) - { - const attribute = attributes[j]; - const attribSize = attribute.size; - - if (attribute.stride === undefined) - { - if (tempStride[attribute.buffer] === attribSize * byteSizeMap[attribute.type]) - { - attribute.stride = 0; - } - else - { - attribute.stride = tempStride[attribute.buffer]; - } - } - - if (attribute.start === undefined) - { - attribute.start = tempStart[attribute.buffer]; - - tempStart[attribute.buffer] += attribSize * byteSizeMap[attribute.type]; - } - } - - // first update - and create the buffers! - // only create a gl buffer if it actually gets - for (let i = 0; i < buffers.length; i++) - { - const buffer = buffers[i]; - - if (!buffer._glBuffers[CONTEXT_UID]) - { - buffer._glBuffers[CONTEXT_UID] = new GLBuffer(gl.createBuffer()); - } - } - - // TODO - maybe make this a data object? - // lets wait to see if we need to first! - const vao = gl.createVertexArray(); - - gl.bindVertexArray(vao); - - this.activateVao(geometry, program); - - gl.bindVertexArray(null); - - // add it to the cache! - geometry.glVertexArrayObjects[this.CONTEXT_UID][program.id] = vao; - - return vao; - } - - activateVao(geometry, program) - { - const gl = this.gl; - const CONTEXT_UID = this.CONTEXT_UID; - const buffers = geometry.buffers; - const attributes = geometry.attributes; - - if (geometry.indexBuffer) - { - // first update the index buffer if we have one.. - gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, geometry.indexBuffer._glBuffers[CONTEXT_UID].buffer); - } - - let lastBuffer = null; - - // add a new one! - for (const j in attributes) - { - const attribute = attributes[j]; - const buffer = buffers[attribute.buffer]; - const glBuffer = buffer._glBuffers[CONTEXT_UID]; - - if (program.attributeData[j]) - { - if (lastBuffer !== glBuffer) - { - gl.bindBuffer(gl.ARRAY_BUFFER, glBuffer.buffer); - - lastBuffer = glBuffer; - } - - const location = program.attributeData[j].location; - - // TODO introduce state again - // we can optimise this for older devices that have no VAOs - gl.enableVertexAttribArray(location); - - gl.vertexAttribPointer(location, - attribute.size, - attribute.type || gl.FLOAT, - attribute.normalized, - attribute.stride, - attribute.start); - - if (attribute.instance) - { - // TODO calculate instance count based of this... - if (this.hasInstance) - { - gl.vertexAttribDivisor(location, 1); - } - else - { - throw new Error('geometry error, GPU Instancing is not supported on this device'); - } - } - } - } - } - - draw(type, size, start, instanceCount) - { - const gl = this.gl; - const geometry = this._activeGeometry; - - // TODO.. this should not change so maybe cache the function? - - if (geometry.indexBuffer) - { - if (geometry.instanced) - { - /* eslint-disable max-len */ - gl.drawElementsInstanced(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2, instanceCount || 1); - /* eslint-enable max-len */ - } - else - { - gl.drawElements(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2); - } - } - else - if (geometry.instanced) - { - // TODO need a better way to calculate size.. - gl.drawArraysInstanced(type, start, size || geometry.getSize(), instanceCount || 1); - } - else - { - gl.drawArrays(type, start, size || geometry.getSize()); - } - - return this; - } - - unbind() - { - this.gl.bindVertexArray(null); - this._activeVao = null; - this._activeGeometry = null; - } -} diff --git a/packages/core/src/renderers/systems/geometry/setVertexAttribArrays.js b/packages/core/src/renderers/systems/geometry/setVertexAttribArrays.js deleted file mode 100644 index 23b6959..0000000 --- a/packages/core/src/renderers/systems/geometry/setVertexAttribArrays.js +++ /dev/null @@ -1,54 +0,0 @@ -// var GL_MAP = {}; - -/** - * @param gl {WebGLRenderingContext} The current WebGL context - * @param attribs {*} - * @param state {*} - */ -export default function setVertexAttribArrays(gl, attribs, state) -{ - let i; - - if (state) - { - const tempAttribState = state.tempAttribState; - const attribState = state.attribState; - - for (i = 0; i < tempAttribState.length; i++) - { - tempAttribState[i] = false; - } - - // set the new attribs - for (i = 0; i < attribs.length; i++) - { - tempAttribState[attribs[i].attribute.location] = true; - } - - for (i = 0; i < attribState.length; i++) - { - if (attribState[i] !== tempAttribState[i]) - { - attribState[i] = tempAttribState[i]; - - if (state.attribState[i]) - { - gl.enableVertexAttribArray(i); - } - else - { - gl.disableVertexAttribArray(i); - } - } - } - } - else - { - for (i = 0; i < attribs.length; i++) - { - const attrib = attribs[i]; - - gl.enableVertexAttribArray(attrib.attribute.location); - } - } -} diff --git a/packages/core/src/renderers/systems/index.js b/packages/core/src/renderers/systems/index.js deleted file mode 100644 index c843f03..0000000 --- a/packages/core/src/renderers/systems/index.js +++ /dev/null @@ -1,6 +0,0 @@ -/** - * Systems are individual components to the Renderer pipeline. - * @namespace PIXI.systems - */ -export { default as WebGLSystem } from './WebGLSystem'; -export { default as FilterSystem } from './filter/FilterSystem'; diff --git a/packages/core/src/renderers/systems/shader/GLProgram.js b/packages/core/src/renderers/systems/shader/GLProgram.js deleted file mode 100644 index 92980ca..0000000 --- a/packages/core/src/renderers/systems/shader/GLProgram.js +++ /dev/null @@ -1,43 +0,0 @@ -/** - * Helper class to create a webGL Program - * - * @class - * @private - */ -export default class GLProgram -{ - constructor(program, uniformData) - { - /** - * The shader program - * - * @member {WebGLProgram} - */ - this.program = program; - - /** - * holds the uniform data which contains uniform locations - * and current uniform values used for caching and preventing undeed GPU commands - * @type {Object} - */ - this.uniformData = uniformData; - - /** - * uniformGroups holds the various upload functions for the shader. Each uniform group - * and program have a unique upload function generated. - * @type {Object} - */ - this.uniformGroups = {}; - } - - /** - * Destroys this program - * TODO - */ - destroy() - { - this.uniformData = null; - this.uniformGroups = null; - this.program = null; - } -} diff --git a/packages/core/src/renderers/systems/shader/ShaderSystem.js b/packages/core/src/renderers/systems/shader/ShaderSystem.js deleted file mode 100644 index 6cf57b6..0000000 --- a/packages/core/src/renderers/systems/shader/ShaderSystem.js +++ /dev/null @@ -1,170 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; -import GLProgram from './GLProgram'; -import generateUniformsSync from '../../../shader/generateUniformsSync'; -import defaultValue from '../../../shader/defaultValue'; -import compileProgram from './shader/compileProgram'; - -let UID = 0; - -/** - * Helper class to create a webGL Texture - * - * @class - * @memberof PIXI - */ -export default class ShaderSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - A reference to the current renderer - */ - constructor(renderer) - { - super(renderer); - - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = null; - - this.shader = null; - this.program = null; - - this.id = UID++; - } - - contextChange(gl) - { - this.gl = gl; - } - - /** - * Changes the current shader to the one given in parameter - * - * @param {PIXI.Shader} shader - the new shader - * @param {boolean} dontSync - false if the shader should automatically sync its uniforms. - * @returns {PIXI.glCore.glProgram} the glProgram that belongs to the shader. - */ - bind(shader, dontSync) - { - shader.uniforms.globals = this.renderer.globalUniforms; - - const program = shader.program; - const glProgram = program.glPrograms[this.renderer.CONTEXT_UID] || this.generateShader(shader); - - this.shader = shader; - - // TODO - some current pixi plugins bypass this.. so it not safe to use yet.. - if (this.program !== program) - { - this.program = program; - this.gl.useProgram(glProgram.program); - } - - if (!dontSync) - { - this.syncUniformGroup(shader.uniformGroup); - } - - return glProgram; - } - - /** - * Uploads the uniforms values to the currently bound shader. - * - * @param {object} uniforms - the uniforms valiues that be applied to the current shader - */ - setUniforms(uniforms) - { - const shader = this.shader.program; - const glProgram = shader.glPrograms[this.renderer.CONTEXT_UID]; - - shader.syncUniforms(glProgram.uniformData, uniforms, this.renderer); - } - - syncUniformGroup(group) - { - const glProgram = this.getglProgram(); - - if (!group.static || group.dirtyId !== glProgram.uniformGroups[group.id]) - { - glProgram.uniformGroups[group.id] = group.dirtyId; - const syncFunc = group.syncUniforms[this.shader.program.id] || this.createSyncGroups(group); - - syncFunc(glProgram.uniformData, group.uniforms, this.renderer); - } - } - - createSyncGroups(group) - { - group.syncUniforms[this.shader.program.id] = generateUniformsSync(group, this.shader.program.uniformData); - - return group.syncUniforms[this.shader.program.id]; - } - - /** - * Returns the underlying GLShade rof the currently bound shader. - * This can be handy for when you to have a little more control over the setting of your uniforms. - * - * @return {PIXI.glCore.Shader} the glProgram for the currently bound Shader for this context - */ - getglProgram() - { - if (this.shader) - { - return this.shader.program.glPrograms[this.renderer.CONTEXT_UID]; - } - - return null; - } - - /** - * Generates a glProgram verion of the Shader provided. - * - * @private - * @param {PIXI.Shader} shader the shader that the glProgram will be based on. - * @return {PIXI.glCore.glProgram} A shiney new glProgram - */ - generateShader(shader) - { - const gl = this.gl; - - const program = shader.program; - - const attribMap = {}; - - for (const i in program.attributeData) - { - attribMap[i] = program.attributeData[i].location; - } - - const shaderProgram = compileProgram(gl, program.vertexSrc, program.fragmentSrc, attribMap); - const uniformData = {}; - - for (const i in program.uniformData) - { - const data = program.uniformData[i]; - - uniformData[i] = { - location: gl.getUniformLocation(shaderProgram, i), - value: defaultValue(data.type, data.size), - }; - } - - const glProgram = new GLProgram(shaderProgram, uniformData); - - program.glPrograms[this.renderer.CONTEXT_UID] = glProgram; - - return glProgram; - } - - /** - * Destroys this System and removes all its textures - */ - destroy() - { - // TODO implement destroy method for ShaderSystem - this.destroyed = true; - } -} diff --git a/packages/core/src/renderers/systems/shader/shader/compileProgram.js b/packages/core/src/renderers/systems/shader/shader/compileProgram.js deleted file mode 100644 index 8c6ccb9..0000000 --- a/packages/core/src/renderers/systems/shader/shader/compileProgram.js +++ /dev/null @@ -1,78 +0,0 @@ -/** - * @method compileProgram - * @memberof PIXI.glCore.shader - * @param gl {WebGLRenderingContext} The current WebGL context {WebGLProgram} - * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. - * @param fragmentSrc {string|string[]} The fragment shader source as an array of strings. - * @param attributeLocations {Object} An attribute location map that lets you manually set the attribute locations - * @return {WebGLProgram} the shader program - */ -export default function compileProgram(gl, vertexSrc, fragmentSrc, attributeLocations) -{ - const glVertShader = compileShader(gl, gl.VERTEX_SHADER, vertexSrc); - const glFragShader = compileShader(gl, gl.FRAGMENT_SHADER, fragmentSrc); - - let program = gl.createProgram(); - - gl.attachShader(program, glVertShader); - gl.attachShader(program, glFragShader); - - // optionally, set the attributes manually for the program rather than letting WebGL decide.. - if (attributeLocations) - { - for (const i in attributeLocations) - { - gl.bindAttribLocation(program, attributeLocations[i], i); - } - } - - gl.linkProgram(program); - - // if linking fails, then log and cleanup - if (!gl.getProgramParameter(program, gl.LINK_STATUS)) - { - console.error('Pixi.js Error: Could not initialize shader.'); - console.error('gl.VALIDATE_STATUS', gl.getProgramParameter(program, gl.VALIDATE_STATUS)); - console.error('gl.getError()', gl.getError()); - - // if there is a program info log, log it - if (gl.getProgramInfoLog(program) !== '') - { - console.warn('Pixi.js Warning: gl.getProgramInfoLog()', gl.getProgramInfoLog(program)); - } - - gl.deleteProgram(program); - program = null; - } - - // clean up some shaders - gl.deleteShader(glVertShader); - gl.deleteShader(glFragShader); - - return program; -} - -/** - * @private - * @param gl {WebGLRenderingContext} The current WebGL context {WebGLProgram} - * @param type {Number} the type, can be either VERTEX_SHADER or FRAGMENT_SHADER - * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. - * @return {WebGLShader} the shader - */ -function compileShader(gl, type, src) -{ - const shader = gl.createShader(type); - - gl.shaderSource(shader, src); - gl.compileShader(shader); - - if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) - { - console.warn(src); - console.error(gl.getShaderInfoLog(shader)); - - return null; - } - - return shader; -} diff --git a/packages/core/src/renderers/systems/shader/shader/defaultValue.js b/packages/core/src/renderers/systems/shader/shader/defaultValue.js deleted file mode 100644 index 97290bd..0000000 --- a/packages/core/src/renderers/systems/shader/shader/defaultValue.js +++ /dev/null @@ -1,79 +0,0 @@ -/** - * @method defaultValue - * @memberof PIXI.glCore.shader - * @param type {String} Type of value - * @param size {Number} - */ -export default function defaultValue(type, size) -{ - switch (type) - { - case 'float': - return 0; - - case 'vec2': - return new Float32Array(2 * size); - - case 'vec3': - return new Float32Array(3 * size); - - case 'vec4': - return new Float32Array(4 * size); - - case 'int': - case 'sampler2D': - case 'sampler2DArray': - return 0; - - case 'ivec2': - return new Int32Array(2 * size); - - case 'ivec3': - return new Int32Array(3 * size); - - case 'ivec4': - return new Int32Array(4 * size); - - case 'bool': - return false; - - case 'bvec2': - - return booleanArray(2 * size); - - case 'bvec3': - return booleanArray(3 * size); - - case 'bvec4': - return booleanArray(4 * size); - - case 'mat2': - return new Float32Array([1, 0, - 0, 1]); - - case 'mat3': - return new Float32Array([1, 0, 0, - 0, 1, 0, - 0, 0, 1]); - - case 'mat4': - return new Float32Array([1, 0, 0, 0, - 0, 1, 0, 0, - 0, 0, 1, 0, - 0, 0, 0, 1]); - } - - return null; -} - -function booleanArray(size) -{ - const array = new Array(size); - - for (let i = 0; i < array.length; i++) - { - array[i] = false; - } - - return array; -} diff --git a/packages/core/src/renderers/systems/shader/shader/index.js b/packages/core/src/renderers/systems/shader/shader/index.js deleted file mode 100644 index ba7a317..0000000 --- a/packages/core/src/renderers/systems/shader/shader/index.js +++ /dev/null @@ -1,8 +0,0 @@ -/** - * @namespace PIXI.glCore.shader - */ -export { default as compileProgram } from './compileProgram'; -export { default as defaultValue } from './defaultValue'; -export { default as setPrecision } from './setPrecision'; -export { default as mapSize } from './mapSize'; -export { default as mapType } from './mapType'; diff --git a/packages/core/src/renderers/systems/shader/shader/mapSize.js b/packages/core/src/renderers/systems/shader/shader/mapSize.js deleted file mode 100644 index b681554..0000000 --- a/packages/core/src/renderers/systems/shader/shader/mapSize.js +++ /dev/null @@ -1,33 +0,0 @@ -const GLSL_TO_SIZE = { - float: 1, - vec2: 2, - vec3: 3, - vec4: 4, - - int: 1, - ivec2: 2, - ivec3: 3, - ivec4: 4, - - bool: 1, - bvec2: 2, - bvec3: 3, - bvec4: 4, - - mat2: 4, - mat3: 9, - mat4: 16, - - sampler2D: 1, -}; - -/** - * @method mapSize - * @memberof PIXI.glCore.shader - * @param type {String} - * @return {Number} - */ -export default function mapSize(type) -{ - return GLSL_TO_SIZE[type]; -} diff --git a/packages/core/src/renderers/systems/shader/shader/mapType.js b/packages/core/src/renderers/systems/shader/shader/mapType.js deleted file mode 100644 index d288b7c..0000000 --- a/packages/core/src/renderers/systems/shader/shader/mapType.js +++ /dev/null @@ -1,45 +0,0 @@ -let GL_TABLE = null; - -const GL_TO_GLSL_TYPES = { - FLOAT: 'float', - FLOAT_VEC2: 'vec2', - FLOAT_VEC3: 'vec3', - FLOAT_VEC4: 'vec4', - - INT: 'int', - INT_VEC2: 'ivec2', - INT_VEC3: 'ivec3', - INT_VEC4: 'ivec4', - - BOOL: 'bool', - BOOL_VEC2: 'bvec2', - BOOL_VEC3: 'bvec3', - BOOL_VEC4: 'bvec4', - - FLOAT_MAT2: 'mat2', - FLOAT_MAT3: 'mat3', - FLOAT_MAT4: 'mat4', - - SAMPLER_2D: 'sampler2D', - SAMPLER_CUBE: 'samplerCube', - SAMPLER_2D_ARRAY: 'sampler2DArray', -}; - -export default function mapType(gl, type) -{ - if (!GL_TABLE) - { - const typeNames = Object.keys(GL_TO_GLSL_TYPES); - - GL_TABLE = {}; - - for (let i = 0; i < typeNames.length; ++i) - { - const tn = typeNames[i]; - - GL_TABLE[gl[tn]] = GL_TO_GLSL_TYPES[tn]; - } - } - - return GL_TABLE[type]; -} diff --git a/packages/core/src/renderers/systems/shader/shader/setPrecision.js b/packages/core/src/renderers/systems/shader/shader/setPrecision.js deleted file mode 100644 index e2c1f34..0000000 --- a/packages/core/src/renderers/systems/shader/shader/setPrecision.js +++ /dev/null @@ -1,18 +0,0 @@ -/** - * Sets the float precision on the shader. If the precision is already present this function will do nothing - * @param {string} src the shader source - * @param {string} precision The float precision of the shader. Options are 'lowp', 'mediump' or 'highp'. - * - * @return {string} modified shader source - */ -export default function setPrecision(src, precision) -{ - src = src.trim(); - - if (src.substring(0, 9) !== 'precision' && src.substring(0, 1) !== '#') - { - return `precision ${precision} float;\n${src}`; - } - - return src; -} diff --git a/packages/core/src/renderers/systems/textures/GLTexture.js b/packages/core/src/renderers/systems/textures/GLTexture.js deleted file mode 100644 index c3fb8dc..0000000 --- a/packages/core/src/renderers/systems/textures/GLTexture.js +++ /dev/null @@ -1,33 +0,0 @@ -export default class GLTexture -{ - constructor(texture) - { - /** - * The WebGL texture - * - * @member {WebGLTexture} - */ - this.texture = texture; - - this.width = -1; - this.height = -1; - - /** - * Texture contents dirty flag - * @member {number} - */ - this.dirtyId = -1; - - /** - * Texture style dirty flag - * @type {number} - */ - this.dirtyStyleId = -1; - - /** - * Whether mip levels has to be generated - * @type {boolean} - */ - this.mipmap = false; - } -} diff --git a/packages/core/src/renderers/systems/textures/TextureGCSystem.js b/packages/core/src/renderers/systems/textures/TextureGCSystem.js deleted file mode 100644 index b089938..0000000 --- a/packages/core/src/renderers/systems/textures/TextureGCSystem.js +++ /dev/null @@ -1,110 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; -import { GC_MODES } from '@pixi/constants'; -import { settings } from '@pixi/settings'; - -/** - * TextureGarbageCollector. This class manages the GPU and ensures that it does not get clogged - * up with textures that are no longer being used. - * - * @class - * @memberof PIXI - */ -export default class TextureGCSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this.count = 0; - this.checkCount = 0; - this.maxIdle = settings.GC_MAX_IDLE; - this.checkCountMax = settings.GC_MAX_CHECK_COUNT; - this.mode = settings.GC_MODE; - } - - /** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ - postrender() - { - this.count++; - - if (this.mode === GC_MODES.MANUAL) - { - return; - } - - this.checkCount++; - - if (this.checkCount > this.checkCountMax) - { - this.checkCount = 0; - - this.run(); - } - } - - /** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ - run() - { - const tm = this.renderer.texture; - const managedTextures = tm.managedTextures; - let wasRemoved = false; - - for (let i = 0; i < managedTextures.length; i++) - { - const texture = managedTextures[i]; - - // only supports non generated textures at the moment! - if (!texture.frameBuffer && this.count - texture.touched > this.maxIdle) - { - tm.destroyTexture(texture, true); - managedTextures[i] = null; - wasRemoved = true; - } - } - - if (wasRemoved) - { - let j = 0; - - for (let i = 0; i < managedTextures.length; i++) - { - if (managedTextures[i] !== null) - { - managedTextures[j++] = managedTextures[i]; - } - } - - managedTextures.length = j; - } - } - - /** - * Removes all the textures within the specified displayObject and its children from the GPU - * - * @param {PIXI.DisplayObject} displayObject - the displayObject to remove the textures from. - */ - unload(displayObject) - { - const tm = this.renderer.textureSystem; - - // only destroy non generated textures - if (displayObject._texture && displayObject._texture._glRenderTargets) - { - tm.destroyTexture(displayObject._texture); - } - - for (let i = displayObject.children.length - 1; i >= 0; i--) - { - this.unload(displayObject.children[i]); - } - } -} diff --git a/packages/core/src/renderers/systems/textures/TextureSystem.js b/packages/core/src/renderers/systems/textures/TextureSystem.js deleted file mode 100644 index ccc5de9..0000000 --- a/packages/core/src/renderers/systems/textures/TextureSystem.js +++ /dev/null @@ -1,281 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; -import GLTexture from './GLTexture'; -import { removeItems } from '@pixi/utils'; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class TextureSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - // TODO set to max textures... - this.boundTextures = [ - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - ]; - - this.currentLocation = -1; - - this.managedTextures = []; - } - - /** - * Sets up the renderer context and necessary buffers. - * - * @private - */ - contextChange() - { - const gl = this.gl = this.renderer.gl; - - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - - // TODO move this.. to a nice make empty textures class.. - this.emptyTextures = {}; - - const emptyTexture2D = new GLTexture(gl.createTexture()); - - gl.bindTexture(gl.TEXTURE_2D, emptyTexture2D.texture); - gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array(4)); - - this.emptyTextures[gl.TEXTURE_2D] = emptyTexture2D; - this.emptyTextures[gl.TEXTURE_CUBE_MAP] = new GLTexture(gl.createTexture()); - - gl.bindTexture(gl.TEXTURE_CUBE_MAP, this.emptyTextures[gl.TEXTURE_CUBE_MAP].texture); - - let i; - - for (i = 0; i < 6; i++) - { - gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, null); - } - - gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_MAG_FILTER, gl.LINEAR); - gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_MIN_FILTER, gl.LINEAR); - - for (i = 0; i < this.boundTextures.length; i++) - { - this.bind(null, i); - } - } - - bind(texture, location) - { - const gl = this.gl; - - location = location || 0; - - if (this.currentLocation !== location) - { - this.currentLocation = location; - gl.activeTexture(gl.TEXTURE0 + location); - } - - if (texture) - { - texture = texture.baseTexture || texture; - - if (texture.valid) - { - texture.touched = this.renderer.textureGC.count; - - const glTexture = texture._glTextures[this.CONTEXT_UID] || this.initTexture(texture); - - gl.bindTexture(texture.target, glTexture.texture); - - if (glTexture.dirtyId !== texture.dirtyId) - { - this.updateTexture(texture); - } - - this.boundTextures[location] = texture; - } - } - else - { - gl.bindTexture(gl.TEXTURE_2D, this.emptyTextures[gl.TEXTURE_2D].texture); - this.boundTextures[location] = null; - } - } - - unbind(texture) - { - const gl = this.gl; - - for (let i = 0; i < this.boundTextures.length; i++) - { - if (this.boundTextures[i] === texture) - { - if (this.currentLocation !== i) - { - gl.activeTexture(gl.TEXTURE0 + i); - this.currentLocation = i; - } - - gl.bindTexture(gl.TEXTURE_2D, this.emptyTextures[texture.target].texture); - this.boundTextures[i] = null; - } - } - } - - initTexture(texture) - { - const glTexture = new GLTexture(this.gl.createTexture()); - - // guarentee an update.. - glTexture.dirtyId = -1; - - texture._glTextures[this.CONTEXT_UID] = glTexture; - - this.managedTextures.push(texture); - texture.on('dispose', this.destroyTexture, this); - - return glTexture; - } - - updateTexture(texture) - { - const glTexture = texture._glTextures[this.CONTEXT_UID]; - const renderer = this.renderer; - - if (texture.resource && texture.resource.upload(renderer, texture, glTexture)) - { - // texture is uploaded, dont do anything! - } - else - { - // default, renderTexture-like logic - const width = texture.realWidth; - const height = texture.realHeight; - const gl = renderer.gl; - - if (glTexture.width !== width - || glTexture.height !== height - || glTexture.dirtyId < 0) - { - glTexture.width = width; - glTexture.height = height; - - gl.texImage2D(texture.target, 0, - texture.format, - width, - height, - 0, - texture.format, - texture.type, - null); - } - } - - // lets only update what changes.. - if (texture.dirtyStyleId !== glTexture.dirtyStyleId) - { - this.updateTextureStyle(texture); - } - glTexture.dirtyId = texture.dirtyId; - } - - /** - * Deletes the texture from WebGL - * - * @param {PIXI.BaseTexture|PIXI.Texture} texture - the texture to destroy - * @param {boolean} [skipRemove=false] - Whether to skip removing the texture from the TextureManager. - */ - destroyTexture(texture, skipRemove) - { - const gl = this.gl; - - texture = texture.baseTexture || texture; - - if (texture._glTextures[this.renderer.CONTEXT_UID]) - { - this.unbind(texture); - - gl.deleteTexture(texture._glTextures[this.renderer.CONTEXT_UID].texture); - texture.off('dispose', this.destroyTexture, this); - - delete texture._glTextures[this.renderer.CONTEXT_UID]; - - if (!skipRemove) - { - const i = this.managedTextures.indexOf(texture); - - if (i !== -1) - { - removeItems(this.managedTextures, i, 1); - } - } - } - } - - updateTextureStyle(texture) - { - const glTexture = texture._glTextures[this.CONTEXT_UID]; - - glTexture.mipmap = texture.mipmap && texture.isPowerOfTwo; - if (!glTexture) - { - return; - } - - if (texture.resource && texture.resource.style(this.renderer, texture, glTexture)) - { - // style is set, dont do anything! - } - else - { - this.setStyle(texture, glTexture); - } - - glTexture.dirtyStyleId = texture.dirtyStyleId; - } - - setStyle(texture, glTexture) - { - const gl = this.gl; - - if (glTexture.mipmap) - { - gl.generateMipmap(texture.target); - } - - gl.texParameteri(texture.target, gl.TEXTURE_WRAP_S, texture.wrapMode); - gl.texParameteri(texture.target, gl.TEXTURE_WRAP_T, texture.wrapMode); - - if (glTexture.mipmap) - { - /* eslint-disable max-len */ - gl.texParameteri(texture.target, gl.TEXTURE_MIN_FILTER, texture.scaleMode ? gl.LINEAR_MIPMAP_LINEAR : gl.NEAREST_MIPMAP_NEAREST); - /* eslint-disable max-len */ - } - else - { - gl.texParameteri(texture.target, gl.TEXTURE_MIN_FILTER, texture.scaleMode ? gl.LINEAR : gl.NEAREST); - } - - gl.texParameteri(texture.target, gl.TEXTURE_MAG_FILTER, texture.scaleMode ? gl.LINEAR : gl.NEAREST); - } -} diff --git a/packages/core/src/renderers/utils/ObjectRenderer.js b/packages/core/src/renderers/utils/ObjectRenderer.js deleted file mode 100644 index f67a273..0000000 --- a/packages/core/src/renderers/utils/ObjectRenderer.js +++ /dev/null @@ -1,48 +0,0 @@ -import WebGLSystem from '../systems/WebGLSystem'; - -/** - * Base for a common object renderer that can be used as a system renderer plugin. - * - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class ObjectRenderer extends WebGLSystem -{ - /** - * Starts the renderer and sets the shader - * - */ - start() - { - // set the shader.. - } - - /** - * Stops the renderer - * - */ - stop() - { - this.flush(); - } - - /** - * Stub method for rendering content and emptying the current batch. - * - */ - flush() - { - // flush! - } - - /** - * Renders an object - * - * @param {PIXI.DisplayObject} object - The object to render. - */ - render(object) // eslint-disable-line no-unused-vars - { - // render the object - } -} diff --git a/packages/core/src/renderers/utils/Quad.js b/packages/core/src/renderers/utils/Quad.js deleted file mode 100644 index 5c1517b..0000000 --- a/packages/core/src/renderers/utils/Quad.js +++ /dev/null @@ -1,26 +0,0 @@ -import Geometry from '../../geometry/Geometry'; - -/** - * Helper class to create a quad - * - * @class - * @memberof PIXI - */ -export default class Quad extends Geometry -{ - /** - * just a quad - */ - constructor() - { - super(); - - this.addAttribute('aVertexPosition', [ - 0, 0, - 1, 0, - 1, 1, - 0, 1, - ]) - .addIndex([0, 1, 3, 2]); - } -} diff --git a/packages/core/src/renderers/utils/QuadUv.js b/packages/core/src/renderers/utils/QuadUv.js deleted file mode 100644 index 97bda89..0000000 --- a/packages/core/src/renderers/utils/QuadUv.js +++ /dev/null @@ -1,103 +0,0 @@ -import Geometry from '../../geometry/Geometry'; -import Buffer from '../../geometry/Buffer'; - -/** - * Helper class to create a quad with uvs like in v4 - * - * @class - * @memberof PIXI - */ -export default class QuadUv extends Geometry -{ - constructor() - { - super(); - - /** - * An array of vertices - * - * @member {Float32Array} - */ - this.vertices = new Float32Array([ - -1, -1, - 1, -1, - 1, 1, - -1, 1, - ]); - - /** - * The Uvs of the quad - * - * @member {Float32Array} - */ - this.uvs = new Float32Array([ - 0, 0, - 1, 0, - 1, 1, - 0, 1, - ]); - - this.vertexBuffer = new Buffer(this.vertices); - this.uvBuffer = new Buffer(this.uvs); - - this.addAttribute('aVertexPosition', this.vertexBuffer) - .addAttribute('aTextureCoord', this.uvBuffer) - .addIndex([0, 1, 2, 0, 2, 3]); - } - - /** - * Maps two Rectangle to the quad. - * - * @param {PIXI.Rectangle} targetTextureFrame - the first rectangle - * @param {PIXI.Rectangle} destinationFrame - the second rectangle - * @return {PIXI.Quad} Returns itself. - */ - map(targetTextureFrame, destinationFrame) - { - let x = 0; // destinationFrame.x / targetTextureFrame.width; - let y = 0; // destinationFrame.y / targetTextureFrame.height; - - this.uvs[0] = x; - this.uvs[1] = y; - - this.uvs[2] = x + (destinationFrame.width / targetTextureFrame.width); - this.uvs[3] = y; - - this.uvs[4] = x + (destinationFrame.width / targetTextureFrame.width); - this.uvs[5] = y + (destinationFrame.height / targetTextureFrame.height); - - this.uvs[6] = x; - this.uvs[7] = y + (destinationFrame.height / targetTextureFrame.height); - - x = destinationFrame.x; - y = destinationFrame.y; - - this.vertices[0] = x; - this.vertices[1] = y; - - this.vertices[2] = x + destinationFrame.width; - this.vertices[3] = y; - - this.vertices[4] = x + destinationFrame.width; - this.vertices[5] = y + destinationFrame.height; - - this.vertices[6] = x; - this.vertices[7] = y + destinationFrame.height; - - this.invalidate(); - - return this; - } - - /** - * legacy upload method, just marks buffers dirty - * @returns {PIXI.QuadUv} Returns itself. - */ - invalidate() - { - this.vertexBuffer._updateID++; - this.uvBuffer._updateID++; - - return this; - } -} diff --git a/packages/core/src/renderers/utils/checkMaxIfStatmentsInShader.js b/packages/core/src/renderers/utils/checkMaxIfStatmentsInShader.js deleted file mode 100644 index 8047c96..0000000 --- a/packages/core/src/renderers/utils/checkMaxIfStatmentsInShader.js +++ /dev/null @@ -1,58 +0,0 @@ -const fragTemplate = [ - 'precision mediump float;', - 'void main(void){', - 'float test = 0.1;', - '%forloop%', - 'gl_FragColor = vec4(0.0);', - '}', -].join('\n'); - -export default function checkMaxIfStatmentsInShader(maxIfs, gl) -{ - if (maxIfs === 0) - { - throw new Error('Invalid value of `0` passed to `checkMaxIfStatementsInShader`'); - } - - const shader = gl.createShader(gl.FRAGMENT_SHADER); - - while (true) // eslint-disable-line no-constant-condition - { - const fragmentSrc = fragTemplate.replace(/%forloop%/gi, generateIfTestSrc(maxIfs)); - - gl.shaderSource(shader, fragmentSrc); - gl.compileShader(shader); - - if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) - { - maxIfs = (maxIfs / 2) | 0; - } - else - { - // valid! - break; - } - } - - return maxIfs; -} - -function generateIfTestSrc(maxIfs) -{ - let src = ''; - - for (let i = 0; i < maxIfs; ++i) - { - if (i > 0) - { - src += '\nelse '; - } - - if (i < maxIfs - 1) - { - src += `if(test == ${i}.0){}`; - } - } - - return src; -} diff --git a/packages/core/src/renderers/utils/mapWebGLBlendModesToPixi.js b/packages/core/src/renderers/utils/mapWebGLBlendModesToPixi.js deleted file mode 100644 index 10ffe4d..0000000 --- a/packages/core/src/renderers/utils/mapWebGLBlendModesToPixi.js +++ /dev/null @@ -1,47 +0,0 @@ -import { BLEND_MODES } from '@pixi/constants'; - -/** - * Maps gl blend combinations to WebGL. - * - * @memberof PIXI - * @function mapWebGLBlendModesToPixi - * @private - * @param {WebGLRenderingContext} gl - The rendering context. - * @param {string[]} [array=[]] - The array to output into. - * @return {string[]} Mapped modes. - */ -export default function mapWebGLBlendModesToPixi(gl, array = []) -{ - // TODO - premultiply alpha would be different. - // add a boolean for that! - array[BLEND_MODES.NORMAL] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.ADD] = [gl.ONE, gl.DST_ALPHA]; - array[BLEND_MODES.MULTIPLY] = [gl.DST_COLOR, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.SCREEN] = [gl.ONE, gl.ONE_MINUS_SRC_COLOR]; - array[BLEND_MODES.OVERLAY] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.DARKEN] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.LIGHTEN] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.COLOR_DODGE] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.COLOR_BURN] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.HARD_LIGHT] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.SOFT_LIGHT] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.DIFFERENCE] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.EXCLUSION] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.HUE] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.SATURATION] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.COLOR] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.LUMINOSITY] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.NONE] = [0, 0]; - - // not-premultiplied blend modes - array[BLEND_MODES.NORMAL_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.ADD_NPM] = [gl.SRC_ALPHA, gl.DST_ALPHA, gl.ONE, gl.DST_ALPHA]; - array[BLEND_MODES.SCREEN_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_COLOR, gl.ONE, gl.ONE_MINUS_SRC_COLOR]; - - // not-premultiplied blend modes - array[BLEND_MODES.NORMAL_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.ADD_NPM] = [gl.SRC_ALPHA, gl.DST_ALPHA, gl.ONE, gl.DST_ALPHA]; - array[BLEND_MODES.SCREEN_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_COLOR, gl.ONE, gl.ONE_MINUS_SRC_COLOR]; - - return array; -} diff --git a/packages/core/src/shader/GLProgram.js b/packages/core/src/shader/GLProgram.js new file mode 100644 index 0000000..92980ca --- /dev/null +++ b/packages/core/src/shader/GLProgram.js @@ -0,0 +1,43 @@ +/** + * Helper class to create a webGL Program + * + * @class + * @private + */ +export default class GLProgram +{ + constructor(program, uniformData) + { + /** + * The shader program + * + * @member {WebGLProgram} + */ + this.program = program; + + /** + * holds the uniform data which contains uniform locations + * and current uniform values used for caching and preventing undeed GPU commands + * @type {Object} + */ + this.uniformData = uniformData; + + /** + * uniformGroups holds the various upload functions for the shader. Each uniform group + * and program have a unique upload function generated. + * @type {Object} + */ + this.uniformGroups = {}; + } + + /** + * Destroys this program + * TODO + */ + destroy() + { + this.uniformData = null; + this.uniformGroups = null; + this.program = null; + } +} diff --git a/packages/core/src/shader/Program.js b/packages/core/src/shader/Program.js index 4f9971e..4b4007a 100644 --- a/packages/core/src/shader/Program.js +++ b/packages/core/src/shader/Program.js @@ -1,6 +1,11 @@ -import * as shaderUtils from '../renderers/systems/shader/shader'; +// import * as from '../systems/shader/shader'; +import { setPrecision, + defaultValue, + compileProgram, + mapSize, + mapType, + getTestContext } from './utils'; import { ProgramCache } from '@pixi/utils'; -import getTestContext from './getTestContext'; import defaultFragment from './defaultProgram.frag'; import defaultVertex from './defaultProgram.vert'; import { settings } from '@pixi/settings'; @@ -34,8 +39,8 @@ */ this.fragmentSrc = fragmentSrc || Program.defaultFragmentSrc; - this.vertexSrc = shaderUtils.setPrecision(this.vertexSrc, settings.PRECISION_VERTEX); - this.fragmentSrc = shaderUtils.setPrecision(this.fragmentSrc, settings.PRECISION_FRAGMENT); + this.vertexSrc = setPrecision(this.vertexSrc, settings.PRECISION_VERTEX); + this.fragmentSrc = setPrecision(this.fragmentSrc, settings.PRECISION_FRAGMENT); // currently this does not extract structs only default types this.extractData(this.vertexSrc, this.fragmentSrc); @@ -62,7 +67,7 @@ if (gl) { - const program = shaderUtils.compileProgram(gl, vertexSrc, fragmentSrc); + const program = compileProgram(gl, vertexSrc, fragmentSrc); this.attributeData = this.getAttributeData(program, gl); this.uniformData = this.getUniformData(program, gl); @@ -95,13 +100,13 @@ for (let i = 0; i < totalAttributes; i++) { const attribData = gl.getActiveAttrib(program, i); - const type = shaderUtils.mapType(gl, attribData.type); + const type = mapType(gl, attribData.type); /*eslint-disable */ const data = { type: type, name: attribData.name, - size: shaderUtils.mapSize(type), + size: mapSize(type), location: 0, }; /* eslint-enable */ @@ -145,14 +150,14 @@ const name = uniformData.name.replace(/\[.*?\]/, ''); const isArray = uniformData.name.match(/\[.*?\]/, ''); - const type = shaderUtils.mapType(gl, uniformData.type); + const type = mapType(gl, uniformData.type); /*eslint-disable */ uniforms[name] = { type: type, size: uniformData.size, isArray:isArray, - value: shaderUtils.defaultValue(type, uniformData.size), + value: defaultValue(type, uniformData.size), }; /* eslint-enable */ } diff --git a/packages/core/src/shader/ShaderSystem.js b/packages/core/src/shader/ShaderSystem.js new file mode 100644 index 0000000..40b8af8 --- /dev/null +++ b/packages/core/src/shader/ShaderSystem.js @@ -0,0 +1,171 @@ +import System from '../System'; +import GLProgram from './GLProgram'; +import { generateUniformsSync, + defaultValue, + compileProgram } from './utils'; + +let UID = 0; + +/** + * Helper class to create a webGL Texture + * + * @class + * @memberof PIXI.systems + * @extends PIXI.System + */ +export default class ShaderSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - A reference to the current renderer + */ + constructor(renderer) + { + super(renderer); + + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = null; + + this.shader = null; + this.program = null; + + this.id = UID++; + } + + contextChange(gl) + { + this.gl = gl; + } + + /** + * Changes the current shader to the one given in parameter + * + * @param {PIXI.Shader} shader - the new shader + * @param {boolean} dontSync - false if the shader should automatically sync its uniforms. + * @returns {PIXI.glCore.glProgram} the glProgram that belongs to the shader. + */ + bind(shader, dontSync) + { + shader.uniforms.globals = this.renderer.globalUniforms; + + const program = shader.program; + const glProgram = program.glPrograms[this.renderer.CONTEXT_UID] || this.generateShader(shader); + + this.shader = shader; + + // TODO - some current pixi plugins bypass this.. so it not safe to use yet.. + if (this.program !== program) + { + this.program = program; + this.gl.useProgram(glProgram.program); + } + + if (!dontSync) + { + this.syncUniformGroup(shader.uniformGroup); + } + + return glProgram; + } + + /** + * Uploads the uniforms values to the currently bound shader. + * + * @param {object} uniforms - the uniforms valiues that be applied to the current shader + */ + setUniforms(uniforms) + { + const shader = this.shader.program; + const glProgram = shader.glPrograms[this.renderer.CONTEXT_UID]; + + shader.syncUniforms(glProgram.uniformData, uniforms, this.renderer); + } + + syncUniformGroup(group) + { + const glProgram = this.getglProgram(); + + if (!group.static || group.dirtyId !== glProgram.uniformGroups[group.id]) + { + glProgram.uniformGroups[group.id] = group.dirtyId; + const syncFunc = group.syncUniforms[this.shader.program.id] || this.createSyncGroups(group); + + syncFunc(glProgram.uniformData, group.uniforms, this.renderer); + } + } + + createSyncGroups(group) + { + group.syncUniforms[this.shader.program.id] = generateUniformsSync(group, this.shader.program.uniformData); + + return group.syncUniforms[this.shader.program.id]; + } + + /** + * Returns the underlying GLShade rof the currently bound shader. + * This can be handy for when you to have a little more control over the setting of your uniforms. + * + * @return {PIXI.glCore.Shader} the glProgram for the currently bound Shader for this context + */ + getglProgram() + { + if (this.shader) + { + return this.shader.program.glPrograms[this.renderer.CONTEXT_UID]; + } + + return null; + } + + /** + * Generates a glProgram verion of the Shader provided. + * + * @private + * @param {PIXI.Shader} shader the shader that the glProgram will be based on. + * @return {PIXI.glCore.glProgram} A shiney new glProgram + */ + generateShader(shader) + { + const gl = this.gl; + + const program = shader.program; + + const attribMap = {}; + + for (const i in program.attributeData) + { + attribMap[i] = program.attributeData[i].location; + } + + const shaderProgram = compileProgram(gl, program.vertexSrc, program.fragmentSrc, attribMap); + const uniformData = {}; + + for (const i in program.uniformData) + { + const data = program.uniformData[i]; + + uniformData[i] = { + location: gl.getUniformLocation(shaderProgram, i), + value: defaultValue(data.type, data.size), + }; + } + + const glProgram = new GLProgram(shaderProgram, uniformData); + + program.glPrograms[this.renderer.CONTEXT_UID] = glProgram; + + return glProgram; + } + + /** + * Destroys this System and removes all its textures + */ + destroy() + { + // TODO implement destroy method for ShaderSystem + this.destroyed = true; + } +} diff --git a/packages/core/src/shader/defaultValue.js b/packages/core/src/shader/defaultValue.js deleted file mode 100644 index 847a069..0000000 --- a/packages/core/src/shader/defaultValue.js +++ /dev/null @@ -1,78 +0,0 @@ -function booleanArray(size) -{ - const array = new Array(size); - - for (let i = 0; i < array.length; i++) - { - array[i] = false; - } - - return array; -} - -/** - * @class - * @memberof PIXI.glCore.shader - * @param type {String} Type of value - * @param size {Number} - */ -export default function defaultValue(type, size) -{ - switch (type) - { - case 'float': - return 0; - - case 'vec2': - return new Float32Array(2 * size); - - case 'vec3': - return new Float32Array(3 * size); - - case 'vec4': - return new Float32Array(4 * size); - - case 'int': - case 'sampler2D': - return 0; - - case 'ivec2': - return new Int32Array(2 * size); - - case 'ivec3': - return new Int32Array(3 * size); - - case 'ivec4': - return new Int32Array(4 * size); - - case 'bool': - return false; - - case 'bvec2': - - return booleanArray(2 * size); - - case 'bvec3': - return booleanArray(3 * size); - - case 'bvec4': - return booleanArray(4 * size); - - case 'mat2': - return new Float32Array([1, 0, - 0, 1]); - - case 'mat3': - return new Float32Array([1, 0, 0, - 0, 1, 0, - 0, 0, 1]); - - case 'mat4': - return new Float32Array([1, 0, 0, 0, - 0, 1, 0, 0, - 0, 0, 1, 0, - 0, 0, 0, 1]); - } - - return 0; -} diff --git a/packages/core/src/shader/extractUniformsFromSrc.js b/packages/core/src/shader/extractUniformsFromSrc.js deleted file mode 100644 index 3d02c0a..0000000 --- a/packages/core/src/shader/extractUniformsFromSrc.js +++ /dev/null @@ -1,57 +0,0 @@ -import defaultValue from './defaultValue'; - -export default function extractUniformsFromSrc(vertexSrc, fragmentSrc, mask) -{ - const vertUniforms = extractUniformsFromString(vertexSrc, mask); - const fragUniforms = extractUniformsFromString(fragmentSrc, mask); - - return Object.assign(vertUniforms, fragUniforms); -} - -function extractUniformsFromString(string) -{ - const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); - - const uniforms = {}; - let nameSplit; - - // clean the lines a little - remove extra spaces / teabs etc - // then split along ';' - const lines = string.replace(/\s+/g, ' ') - .split(/\s*;\s*/); - - // loop through.. - for (let i = 0; i < lines.length; i++) - { - const line = lines[i].trim(); - - if (line.indexOf('uniform') > -1) - { - const splitLine = line.split(' '); - const type = splitLine[1]; - - let name = splitLine[2]; - let size = 1; - - if (name.indexOf('[') > -1) - { - // array! - nameSplit = name.split(/\[|]/); - name = nameSplit[0]; - size *= Number(nameSplit[1]); - } - - if (!name.match(maskRegex)) - { - uniforms[name] = { - value: defaultValue(type, size), - dirtyId: 0, - name, - type, - }; - } - } - } - - return uniforms; -} diff --git a/packages/core/src/shader/generateUniformsSync.js b/packages/core/src/shader/generateUniformsSync.js deleted file mode 100644 index 98a357f..0000000 --- a/packages/core/src/shader/generateUniformsSync.js +++ /dev/null @@ -1,231 +0,0 @@ -// cv = CachedValue -// v = value -// ud = uniformData -// uv = uniformValue -// l = loaction -const GLSL_TO_SINGLE_SETTERS_CACHED = { - - float: ` - if(cv !== v) - { - cv.v = v; - gl.uniform1f(location, v) - }`, - - vec2: ` - if(cv[0] !== v[0] || cv[1] !== v[1]) - { - cv[0] = v[0]; - cv[1] = v[1]; - gl.uniform2f(location, v[0], v[1]) - }`, - - vec3: ` - if(cv[0] !== v[0] || cv[1] !== v[1] || cv[2] !== v[2]) - { - cv[0] = v[0]; - cv[1] = v[1]; - cv[2] = v[2]; - - gl.uniform3f(location, v[0], v[1], v[2]) - }`, - - vec4: 'gl.uniform4f(location, v[0], v[1], v[2], v[3])', - - int: 'gl.uniform1i(location, v)', - ivec2: 'gl.uniform2i(location, v[0], v[1])', - ivec3: 'gl.uniform3i(location, v[0], v[1], v[2])', - ivec4: 'gl.uniform4i(location, v[0], v[1], v[2], v[3])', - - bool: 'gl.uniform1i(location, v)', - bvec2: 'gl.uniform2i(location, v[0], v[1])', - bvec3: 'gl.uniform3i(location, v[0], v[1], v[2])', - bvec4: 'gl.uniform4i(location, v[0], v[1], v[2], v[3])', - - mat2: 'gl.uniformMatrix2fv(location, false, v)', - mat3: 'gl.uniformMatrix3fv(location, false, v)', - mat4: 'gl.uniformMatrix4fv(location, false, v)', - - sampler2D: 'gl.uniform1i(location, v)', - samplerCube: 'gl.uniform1i(location, v)', - sampler2DArray: 'gl.uniform1i(location, v)', -}; - -const GLSL_TO_ARRAY_SETTERS = { - - float: `gl.uniform1fv(location, v)`, - - vec2: `gl.uniform2fv(location, v)`, - vec3: `gl.uniform3fv(location, v)`, - vec4: 'gl.uniform4fv(location, v)', - - mat4: 'gl.uniformMatrix4fv(location, false, v)', - mat3: 'gl.uniformMatrix3fv(location, false, v)', - mat2: 'gl.uniformMatrix2fv(location, false, v)', - - int: 'gl.uniform1iv(location, v)', - ivec2: 'gl.uniform2iv(location, v)', - ivec3: 'gl.uniform3iv(location, v)', - ivec4: 'gl.uniform4iv(location, v)', - - bool: 'gl.uniform1iv(location, v)', - bvec2: 'gl.uniform2iv(location, v)', - bvec3: 'gl.uniform3iv(location, v)', - bvec4: 'gl.uniform4iv(location, v)', - - sampler2D: 'gl.uniform1iv(location, v)', - samplerCube: 'gl.uniform1iv(location, v)', - sampler2DArray: 'gl.uniform1iv(location, v)', -}; - -export default function generateUniformsSync(group, uniformData) -{ - let textureCount = 0; - let func = `var v = null; - var cv = null - var gl = renderer.gl`; - - for (const i in group.uniforms) - { - const data = uniformData[i]; - - if (!data) - { - if (group.uniforms[i].group) - { - func += ` - renderer.shader.syncUniformGroup(uv.${i}); - `; - } - - continue; - } - - // TODO && uniformData[i].value !== 0 <-- do we still need this? - if (data.type === 'float' && data.size === 1) - { - func += ` - if(uv.${i} !== ud.${i}.value) - { - ud.${i}.value = uv.${i} - gl.uniform1f(ud.${i}.location, uv.${i}) - }\n`; - } - /* eslint-disable max-len */ - else if ((data.type === 'sampler2D' || data.type === 'samplerCube' || data.type === 'sampler2DArray') && data.size === 1 && !data.isArray) - /* eslint-disable max-len */ - { - func += ` - renderer.texture.bind(uv.${i}, ${textureCount}); - - if(ud.${i}.value !== ${textureCount}) - { - ud.${i}.value = ${textureCount}; - gl.uniform1i(ud.${i}.location, ${textureCount});\n; // eslint-disable-line max-len - }\n`; - - textureCount++; - } - else if (data.type === 'mat3' && data.size === 1) - { - if (group.uniforms[i].a !== undefined) - { - // TODO and some smart caching dirty ids here! - func += ` - gl.uniformMatrix3fv(ud.${i}.location, false, uv.${i}.toArray(true)); - \n`; - } - else - { - func += ` - gl.uniformMatrix3fv(ud.${i}.location, false, uv.${i}); - \n`; - } - } - else if (data.type === 'vec2' && data.size === 1) - { - // TODO - do we need both here? - // maybe we can get away with only using points? - if (group.uniforms[i].x !== undefined) - { - func += ` - cv = ud.${i}.value; - v = uv.${i}; - - if(cv[0] !== v.x || cv[1] !== v.y) - { - cv[0] = v.x; - cv[1] = v.y; - gl.uniform2f(ud.${i}.location, v.x, v.y); - }\n`; - } - else - { - func += ` - cv = ud.${i}.value; - v = uv.${i}; - - if(cv[0] !== v[0] || cv[1] !== v[1]) - { - cv[0] = v[0]; - cv[1] = v[1]; - gl.uniform2f(ud.${i}.location, v[0], v[1]); - } - \n`; - } - } - else if (data.type === 'vec4' && data.size === 1) - { - // TODO - do we need both here? - // maybe we can get away with only using points? - if (group.uniforms[i].width !== undefined) - { - func += ` - cv = ud.${i}.value; - v = uv.${i}; - - if(cv[0] !== v.x || cv[1] !== v.y || cv[2] !== v.width || cv[3] !== v.height) - { - cv[0] = v.x; - cv[1] = v.y; - cv[2] = v.width; - cv[3] = v.height; - gl.uniform4f(ud.${i}.location, v.x, v.y, v.width, v.height) - }\n`; - } - else - { - func += ` - cv = ud.${i}.value; - v = uv.${i}; - - if(cv[0] !== v[0] || cv[1] !== v[1] || cv[2] !== v[2] || cv[3] !== v[3]) - { - cv[0] = v[0]; - cv[1] = v[1]; - cv[2] = v[2]; - cv[3] = v[3]; - - gl.uniform4f(ud.${i}.location, v[0], v[1], v[2], v[3]) - } - \n`; - } - } - else - { - const templateType = (data.size === 1) ? GLSL_TO_SINGLE_SETTERS_CACHED : GLSL_TO_ARRAY_SETTERS; - - const template = templateType[data.type].replace('location', `ud.${i}.location`); - - func += ` - cv = ud.${i}.value; - v = uv.${i}; - ${template};\n`; - } - } - - // console.log(' --------------- ') - // console.log(func); - - return new Function('ud', 'uv', 'renderer', func); // eslint-disable-line no-new-func -} diff --git a/packages/core/src/shader/getTestContext.js b/packages/core/src/shader/getTestContext.js deleted file mode 100644 index ea174cc..0000000 --- a/packages/core/src/shader/getTestContext.js +++ /dev/null @@ -1,48 +0,0 @@ -import { settings } from '@pixi/settings'; - -let context = null; - -/** - * returns a little webGL context to use for program inspection. - * - * @static - * @private - * @returns {webGL-context} a gl context to test with - */ -export default function getTestContext() -{ - if (!context) - { - const canvas = document.createElement('canvas'); - - let gl; - - if (settings.PREFER_WEBGL_2) - { - gl = canvas.getContext('webgl2', {}); - } - - if (!gl) - { - gl = canvas.getContext('webgl', {}) - || canvas.getContext('experimental-webgl', {}); - - if (!gl) - { - // fail, not able to get a context - throw new Error('This browser does not support webGL. Try using the canvas renderer'); - } - else - { - // for shader testing.. - gl.getExtension('WEBGL_draw_buffers'); - } - } - - context = gl; - - return gl; - } - - return context; -} diff --git a/packages/core/src/shader/utils/checkMaxIfStatmentsInShader.js b/packages/core/src/shader/utils/checkMaxIfStatmentsInShader.js new file mode 100644 index 0000000..8047c96 --- /dev/null +++ b/packages/core/src/shader/utils/checkMaxIfStatmentsInShader.js @@ -0,0 +1,58 @@ +const fragTemplate = [ + 'precision mediump float;', + 'void main(void){', + 'float test = 0.1;', + '%forloop%', + 'gl_FragColor = vec4(0.0);', + '}', +].join('\n'); + +export default function checkMaxIfStatmentsInShader(maxIfs, gl) +{ + if (maxIfs === 0) + { + throw new Error('Invalid value of `0` passed to `checkMaxIfStatementsInShader`'); + } + + const shader = gl.createShader(gl.FRAGMENT_SHADER); + + while (true) // eslint-disable-line no-constant-condition + { + const fragmentSrc = fragTemplate.replace(/%forloop%/gi, generateIfTestSrc(maxIfs)); + + gl.shaderSource(shader, fragmentSrc); + gl.compileShader(shader); + + if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) + { + maxIfs = (maxIfs / 2) | 0; + } + else + { + // valid! + break; + } + } + + return maxIfs; +} + +function generateIfTestSrc(maxIfs) +{ + let src = ''; + + for (let i = 0; i < maxIfs; ++i) + { + if (i > 0) + { + src += '\nelse '; + } + + if (i < maxIfs - 1) + { + src += `if(test == ${i}.0){}`; + } + } + + return src; +} diff --git a/packages/core/src/shader/utils/compileProgram.js b/packages/core/src/shader/utils/compileProgram.js new file mode 100644 index 0000000..8c6ccb9 --- /dev/null +++ b/packages/core/src/shader/utils/compileProgram.js @@ -0,0 +1,78 @@ +/** + * @method compileProgram + * @memberof PIXI.glCore.shader + * @param gl {WebGLRenderingContext} The current WebGL context {WebGLProgram} + * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. + * @param fragmentSrc {string|string[]} The fragment shader source as an array of strings. + * @param attributeLocations {Object} An attribute location map that lets you manually set the attribute locations + * @return {WebGLProgram} the shader program + */ +export default function compileProgram(gl, vertexSrc, fragmentSrc, attributeLocations) +{ + const glVertShader = compileShader(gl, gl.VERTEX_SHADER, vertexSrc); + const glFragShader = compileShader(gl, gl.FRAGMENT_SHADER, fragmentSrc); + + let program = gl.createProgram(); + + gl.attachShader(program, glVertShader); + gl.attachShader(program, glFragShader); + + // optionally, set the attributes manually for the program rather than letting WebGL decide.. + if (attributeLocations) + { + for (const i in attributeLocations) + { + gl.bindAttribLocation(program, attributeLocations[i], i); + } + } + + gl.linkProgram(program); + + // if linking fails, then log and cleanup + if (!gl.getProgramParameter(program, gl.LINK_STATUS)) + { + console.error('Pixi.js Error: Could not initialize shader.'); + console.error('gl.VALIDATE_STATUS', gl.getProgramParameter(program, gl.VALIDATE_STATUS)); + console.error('gl.getError()', gl.getError()); + + // if there is a program info log, log it + if (gl.getProgramInfoLog(program) !== '') + { + console.warn('Pixi.js Warning: gl.getProgramInfoLog()', gl.getProgramInfoLog(program)); + } + + gl.deleteProgram(program); + program = null; + } + + // clean up some shaders + gl.deleteShader(glVertShader); + gl.deleteShader(glFragShader); + + return program; +} + +/** + * @private + * @param gl {WebGLRenderingContext} The current WebGL context {WebGLProgram} + * @param type {Number} the type, can be either VERTEX_SHADER or FRAGMENT_SHADER + * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. + * @return {WebGLShader} the shader + */ +function compileShader(gl, type, src) +{ + const shader = gl.createShader(type); + + gl.shaderSource(shader, src); + gl.compileShader(shader); + + if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) + { + console.warn(src); + console.error(gl.getShaderInfoLog(shader)); + + return null; + } + + return shader; +} diff --git a/packages/core/src/shader/utils/defaultValue.js b/packages/core/src/shader/utils/defaultValue.js new file mode 100644 index 0000000..97290bd --- /dev/null +++ b/packages/core/src/shader/utils/defaultValue.js @@ -0,0 +1,79 @@ +/** + * @method defaultValue + * @memberof PIXI.glCore.shader + * @param type {String} Type of value + * @param size {Number} + */ +export default function defaultValue(type, size) +{ + switch (type) + { + case 'float': + return 0; + + case 'vec2': + return new Float32Array(2 * size); + + case 'vec3': + return new Float32Array(3 * size); + + case 'vec4': + return new Float32Array(4 * size); + + case 'int': + case 'sampler2D': + case 'sampler2DArray': + return 0; + + case 'ivec2': + return new Int32Array(2 * size); + + case 'ivec3': + return new Int32Array(3 * size); + + case 'ivec4': + return new Int32Array(4 * size); + + case 'bool': + return false; + + case 'bvec2': + + return booleanArray(2 * size); + + case 'bvec3': + return booleanArray(3 * size); + + case 'bvec4': + return booleanArray(4 * size); + + case 'mat2': + return new Float32Array([1, 0, + 0, 1]); + + case 'mat3': + return new Float32Array([1, 0, 0, + 0, 1, 0, + 0, 0, 1]); + + case 'mat4': + return new Float32Array([1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1]); + } + + return null; +} + +function booleanArray(size) +{ + const array = new Array(size); + + for (let i = 0; i < array.length; i++) + { + array[i] = false; + } + + return array; +} diff --git a/packages/core/src/shader/utils/extractUniformsFromSrc.js b/packages/core/src/shader/utils/extractUniformsFromSrc.js new file mode 100644 index 0000000..3d02c0a --- /dev/null +++ b/packages/core/src/shader/utils/extractUniformsFromSrc.js @@ -0,0 +1,57 @@ +import defaultValue from './defaultValue'; + +export default function extractUniformsFromSrc(vertexSrc, fragmentSrc, mask) +{ + const vertUniforms = extractUniformsFromString(vertexSrc, mask); + const fragUniforms = extractUniformsFromString(fragmentSrc, mask); + + return Object.assign(vertUniforms, fragUniforms); +} + +function extractUniformsFromString(string) +{ + const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); + + const uniforms = {}; + let nameSplit; + + // clean the lines a little - remove extra spaces / teabs etc + // then split along ';' + const lines = string.replace(/\s+/g, ' ') + .split(/\s*;\s*/); + + // loop through.. + for (let i = 0; i < lines.length; i++) + { + const line = lines[i].trim(); + + if (line.indexOf('uniform') > -1) + { + const splitLine = line.split(' '); + const type = splitLine[1]; + + let name = splitLine[2]; + let size = 1; + + if (name.indexOf('[') > -1) + { + // array! + nameSplit = name.split(/\[|]/); + name = nameSplit[0]; + size *= Number(nameSplit[1]); + } + + if (!name.match(maskRegex)) + { + uniforms[name] = { + value: defaultValue(type, size), + dirtyId: 0, + name, + type, + }; + } + } + } + + return uniforms; +} diff --git a/packages/core/src/shader/utils/generateUniformsSync.js b/packages/core/src/shader/utils/generateUniformsSync.js new file mode 100644 index 0000000..98a357f --- /dev/null +++ b/packages/core/src/shader/utils/generateUniformsSync.js @@ -0,0 +1,231 @@ +// cv = CachedValue +// v = value +// ud = uniformData +// uv = uniformValue +// l = loaction +const GLSL_TO_SINGLE_SETTERS_CACHED = { + + float: ` + if(cv !== v) + { + cv.v = v; + gl.uniform1f(location, v) + }`, + + vec2: ` + if(cv[0] !== v[0] || cv[1] !== v[1]) + { + cv[0] = v[0]; + cv[1] = v[1]; + gl.uniform2f(location, v[0], v[1]) + }`, + + vec3: ` + if(cv[0] !== v[0] || cv[1] !== v[1] || cv[2] !== v[2]) + { + cv[0] = v[0]; + cv[1] = v[1]; + cv[2] = v[2]; + + gl.uniform3f(location, v[0], v[1], v[2]) + }`, + + vec4: 'gl.uniform4f(location, v[0], v[1], v[2], v[3])', + + int: 'gl.uniform1i(location, v)', + ivec2: 'gl.uniform2i(location, v[0], v[1])', + ivec3: 'gl.uniform3i(location, v[0], v[1], v[2])', + ivec4: 'gl.uniform4i(location, v[0], v[1], v[2], v[3])', + + bool: 'gl.uniform1i(location, v)', + bvec2: 'gl.uniform2i(location, v[0], v[1])', + bvec3: 'gl.uniform3i(location, v[0], v[1], v[2])', + bvec4: 'gl.uniform4i(location, v[0], v[1], v[2], v[3])', + + mat2: 'gl.uniformMatrix2fv(location, false, v)', + mat3: 'gl.uniformMatrix3fv(location, false, v)', + mat4: 'gl.uniformMatrix4fv(location, false, v)', + + sampler2D: 'gl.uniform1i(location, v)', + samplerCube: 'gl.uniform1i(location, v)', + sampler2DArray: 'gl.uniform1i(location, v)', +}; + +const GLSL_TO_ARRAY_SETTERS = { + + float: `gl.uniform1fv(location, v)`, + + vec2: `gl.uniform2fv(location, v)`, + vec3: `gl.uniform3fv(location, v)`, + vec4: 'gl.uniform4fv(location, v)', + + mat4: 'gl.uniformMatrix4fv(location, false, v)', + mat3: 'gl.uniformMatrix3fv(location, false, v)', + mat2: 'gl.uniformMatrix2fv(location, false, v)', + + int: 'gl.uniform1iv(location, v)', + ivec2: 'gl.uniform2iv(location, v)', + ivec3: 'gl.uniform3iv(location, v)', + ivec4: 'gl.uniform4iv(location, v)', + + bool: 'gl.uniform1iv(location, v)', + bvec2: 'gl.uniform2iv(location, v)', + bvec3: 'gl.uniform3iv(location, v)', + bvec4: 'gl.uniform4iv(location, v)', + + sampler2D: 'gl.uniform1iv(location, v)', + samplerCube: 'gl.uniform1iv(location, v)', + sampler2DArray: 'gl.uniform1iv(location, v)', +}; + +export default function generateUniformsSync(group, uniformData) +{ + let textureCount = 0; + let func = `var v = null; + var cv = null + var gl = renderer.gl`; + + for (const i in group.uniforms) + { + const data = uniformData[i]; + + if (!data) + { + if (group.uniforms[i].group) + { + func += ` + renderer.shader.syncUniformGroup(uv.${i}); + `; + } + + continue; + } + + // TODO && uniformData[i].value !== 0 <-- do we still need this? + if (data.type === 'float' && data.size === 1) + { + func += ` + if(uv.${i} !== ud.${i}.value) + { + ud.${i}.value = uv.${i} + gl.uniform1f(ud.${i}.location, uv.${i}) + }\n`; + } + /* eslint-disable max-len */ + else if ((data.type === 'sampler2D' || data.type === 'samplerCube' || data.type === 'sampler2DArray') && data.size === 1 && !data.isArray) + /* eslint-disable max-len */ + { + func += ` + renderer.texture.bind(uv.${i}, ${textureCount}); + + if(ud.${i}.value !== ${textureCount}) + { + ud.${i}.value = ${textureCount}; + gl.uniform1i(ud.${i}.location, ${textureCount});\n; // eslint-disable-line max-len + }\n`; + + textureCount++; + } + else if (data.type === 'mat3' && data.size === 1) + { + if (group.uniforms[i].a !== undefined) + { + // TODO and some smart caching dirty ids here! + func += ` + gl.uniformMatrix3fv(ud.${i}.location, false, uv.${i}.toArray(true)); + \n`; + } + else + { + func += ` + gl.uniformMatrix3fv(ud.${i}.location, false, uv.${i}); + \n`; + } + } + else if (data.type === 'vec2' && data.size === 1) + { + // TODO - do we need both here? + // maybe we can get away with only using points? + if (group.uniforms[i].x !== undefined) + { + func += ` + cv = ud.${i}.value; + v = uv.${i}; + + if(cv[0] !== v.x || cv[1] !== v.y) + { + cv[0] = v.x; + cv[1] = v.y; + gl.uniform2f(ud.${i}.location, v.x, v.y); + }\n`; + } + else + { + func += ` + cv = ud.${i}.value; + v = uv.${i}; + + if(cv[0] !== v[0] || cv[1] !== v[1]) + { + cv[0] = v[0]; + cv[1] = v[1]; + gl.uniform2f(ud.${i}.location, v[0], v[1]); + } + \n`; + } + } + else if (data.type === 'vec4' && data.size === 1) + { + // TODO - do we need both here? + // maybe we can get away with only using points? + if (group.uniforms[i].width !== undefined) + { + func += ` + cv = ud.${i}.value; + v = uv.${i}; + + if(cv[0] !== v.x || cv[1] !== v.y || cv[2] !== v.width || cv[3] !== v.height) + { + cv[0] = v.x; + cv[1] = v.y; + cv[2] = v.width; + cv[3] = v.height; + gl.uniform4f(ud.${i}.location, v.x, v.y, v.width, v.height) + }\n`; + } + else + { + func += ` + cv = ud.${i}.value; + v = uv.${i}; + + if(cv[0] !== v[0] || cv[1] !== v[1] || cv[2] !== v[2] || cv[3] !== v[3]) + { + cv[0] = v[0]; + cv[1] = v[1]; + cv[2] = v[2]; + cv[3] = v[3]; + + gl.uniform4f(ud.${i}.location, v[0], v[1], v[2], v[3]) + } + \n`; + } + } + else + { + const templateType = (data.size === 1) ? GLSL_TO_SINGLE_SETTERS_CACHED : GLSL_TO_ARRAY_SETTERS; + + const template = templateType[data.type].replace('location', `ud.${i}.location`); + + func += ` + cv = ud.${i}.value; + v = uv.${i}; + ${template};\n`; + } + } + + // console.log(' --------------- ') + // console.log(func); + + return new Function('ud', 'uv', 'renderer', func); // eslint-disable-line no-new-func +} diff --git a/packages/core/src/shader/utils/getTestContext.js b/packages/core/src/shader/utils/getTestContext.js new file mode 100644 index 0000000..ea174cc --- /dev/null +++ b/packages/core/src/shader/utils/getTestContext.js @@ -0,0 +1,48 @@ +import { settings } from '@pixi/settings'; + +let context = null; + +/** + * returns a little webGL context to use for program inspection. + * + * @static + * @private + * @returns {webGL-context} a gl context to test with + */ +export default function getTestContext() +{ + if (!context) + { + const canvas = document.createElement('canvas'); + + let gl; + + if (settings.PREFER_WEBGL_2) + { + gl = canvas.getContext('webgl2', {}); + } + + if (!gl) + { + gl = canvas.getContext('webgl', {}) + || canvas.getContext('experimental-webgl', {}); + + if (!gl) + { + // fail, not able to get a context + throw new Error('This browser does not support webGL. Try using the canvas renderer'); + } + else + { + // for shader testing.. + gl.getExtension('WEBGL_draw_buffers'); + } + } + + context = gl; + + return gl; + } + + return context; +} diff --git a/packages/core/src/shader/utils/index.js b/packages/core/src/shader/utils/index.js new file mode 100644 index 0000000..4bf8dd4 --- /dev/null +++ b/packages/core/src/shader/utils/index.js @@ -0,0 +1,8 @@ +export { default as compileProgram } from './compileProgram'; +export { default as defaultValue } from './defaultValue'; +export { default as setPrecision } from './setPrecision'; +export { default as mapSize } from './mapSize'; +export { default as mapType } from './mapType'; +export { default as generateUniformsSync } from './generateUniformsSync'; +export { default as getTestContext } from './getTestContext'; +export { default as checkMaxIfStatmentsInShader } from './checkMaxIfStatmentsInShader'; diff --git a/packages/core/src/shader/utils/mapSize.js b/packages/core/src/shader/utils/mapSize.js new file mode 100644 index 0000000..b681554 --- /dev/null +++ b/packages/core/src/shader/utils/mapSize.js @@ -0,0 +1,33 @@ +const GLSL_TO_SIZE = { + float: 1, + vec2: 2, + vec3: 3, + vec4: 4, + + int: 1, + ivec2: 2, + ivec3: 3, + ivec4: 4, + + bool: 1, + bvec2: 2, + bvec3: 3, + bvec4: 4, + + mat2: 4, + mat3: 9, + mat4: 16, + + sampler2D: 1, +}; + +/** + * @method mapSize + * @memberof PIXI.glCore.shader + * @param type {String} + * @return {Number} + */ +export default function mapSize(type) +{ + return GLSL_TO_SIZE[type]; +} diff --git a/packages/core/src/shader/utils/mapType.js b/packages/core/src/shader/utils/mapType.js new file mode 100644 index 0000000..d288b7c --- /dev/null +++ b/packages/core/src/shader/utils/mapType.js @@ -0,0 +1,45 @@ +let GL_TABLE = null; + +const GL_TO_GLSL_TYPES = { + FLOAT: 'float', + FLOAT_VEC2: 'vec2', + FLOAT_VEC3: 'vec3', + FLOAT_VEC4: 'vec4', + + INT: 'int', + INT_VEC2: 'ivec2', + INT_VEC3: 'ivec3', + INT_VEC4: 'ivec4', + + BOOL: 'bool', + BOOL_VEC2: 'bvec2', + BOOL_VEC3: 'bvec3', + BOOL_VEC4: 'bvec4', + + FLOAT_MAT2: 'mat2', + FLOAT_MAT3: 'mat3', + FLOAT_MAT4: 'mat4', + + SAMPLER_2D: 'sampler2D', + SAMPLER_CUBE: 'samplerCube', + SAMPLER_2D_ARRAY: 'sampler2DArray', +}; + +export default function mapType(gl, type) +{ + if (!GL_TABLE) + { + const typeNames = Object.keys(GL_TO_GLSL_TYPES); + + GL_TABLE = {}; + + for (let i = 0; i < typeNames.length; ++i) + { + const tn = typeNames[i]; + + GL_TABLE[gl[tn]] = GL_TO_GLSL_TYPES[tn]; + } + } + + return GL_TABLE[type]; +} diff --git a/packages/core/src/shader/utils/setPrecision.js b/packages/core/src/shader/utils/setPrecision.js new file mode 100644 index 0000000..e2c1f34 --- /dev/null +++ b/packages/core/src/shader/utils/setPrecision.js @@ -0,0 +1,18 @@ +/** + * Sets the float precision on the shader. If the precision is already present this function will do nothing + * @param {string} src the shader source + * @param {string} precision The float precision of the shader. Options are 'lowp', 'mediump' or 'highp'. + * + * @return {string} modified shader source + */ +export default function setPrecision(src, precision) +{ + src = src.trim(); + + if (src.substring(0, 9) !== 'precision' && src.substring(0, 1) !== '#') + { + return `precision ${precision} float;\n${src}`; + } + + return src; +} diff --git a/packages/core/src/state/State.js b/packages/core/src/state/State.js new file mode 100644 index 0000000..fa3f221 --- /dev/null +++ b/packages/core/src/state/State.js @@ -0,0 +1,163 @@ +/* eslint-disable max-len */ + +const BLEND = 0; +const OFFSET = 1; +const CULLING = 2; +const DEPTH_TEST = 3; +const WINDING = 4; + +/** + * This is a webGL state. It is passed The WebGL StateManager. + * Each mesh renderered may require webGL to be in a different state. + * For example you may want different blend mode or to enable polygon offsets + * + * @class + * @memberof PIXI + */ +export default class State +{ + /** + * + */ + constructor() + { + this.data = 0; + + this.blendMode = 0; + this.polygonOffset = 0; + + this.blend = true; + // this.depthTest = true; + } + + /** + * Activates blending of the computed fragment color values + * + * @member {boolean} + */ + get blend() + { + return !!(this.data & (1 << BLEND)); + } + + set blend(value) // eslint-disable-line require-jsdoc + { + if (!!(this.data & (1 << BLEND)) !== value) + { + this.data ^= (1 << BLEND); + } + } + + /** + * Activates adding an offset to depth values of polygon's fragments + * + * @member {boolean} + * @default false + */ + get offsets() + { + return !!(this.data & (1 << OFFSET)); + } + + set offsets(value) // eslint-disable-line require-jsdoc + { + if (!!(this.data & (1 << OFFSET)) !== value) + { + this.data ^= (1 << OFFSET); + } + } + + /** + * Activates culling of polygons. + * + * @member {boolean} + * @default false + */ + get culling() + { + return !!(this.data & (1 << CULLING)); + } + + set culling(value) // eslint-disable-line require-jsdoc + { + if (!!(this.data & (1 << CULLING)) !== value) + { + this.data ^= (1 << CULLING); + } + } + + /** + * Activates depth comparisons and updates to the depth buffer. + * + * @member {boolean} + * @default false + */ + get depthTest() + { + return !!(this.data & (1 << DEPTH_TEST)); + } + + set depthTest(value) // eslint-disable-line require-jsdoc + { + if (!!(this.data & (1 << DEPTH_TEST)) !== value) + { + this.data ^= (1 << DEPTH_TEST); + } + } + + /** + * Specifies whether or not front or back-facing polygons can be culled. + * @member {boolean} + * @default false + */ + get clockwiseFrontFace() + { + return !!(this.data & (1 << WINDING)); + } + + set clockwiseFrontFace(value) // eslint-disable-line require-jsdoc + { + if (!!(this.data & (1 << WINDING)) !== value) + { + this.data ^= (1 << WINDING); + } + } + + /** + * The blend mode to be applied when this state is set. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. + * Setting this mode to anything other than NO_BLEND will automatically switch blending on. + * + * @member {boolean} + * @default PIXI.BLEND_MODES.NORMAL + * @see PIXI.BLEND_MODES + */ + get blendMode() + { + return this._blendMode; + } + + set blendMode(value) // eslint-disable-line require-jsdoc + { + // 17 is NO BLEND + this.blend = (value !== 17); + this._blendMode = value; + } + + /** + * The polygon offset. Setting this property to anything other than 0 will automatically enable poygon offset fill. + * + * @member {number} + * @default 0 + */ + get polygonOffset() + { + return this._polygonOffset; + } + + set polygonOffset(value) // eslint-disable-line require-jsdoc + { + this.offsets = !!value; + this._polygonOffset = value; + } +} + diff --git a/packages/core/src/state/StateSystem.js b/packages/core/src/state/StateSystem.js new file mode 100755 index 0000000..39c35bb --- /dev/null +++ b/packages/core/src/state/StateSystem.js @@ -0,0 +1,288 @@ +import mapWebGLBlendModesToPixi from './utils/mapWebGLBlendModesToPixi'; +import System from '../System'; +import WebGLState from './State'; + +const BLEND = 0; +const OFFSET = 1; +const CULLING = 2; +const DEPTH_TEST = 3; +const WINDING = 4; + +/** + * A WebGL state machines + * + * @class + * @extends PIXI.systems.System + * @memberof PIXI.systems + */ +export default class StateSystem extends System +{ + /** + * @param {WebGLRenderingContext} gl - The current WebGL rendering context + */ + constructor(renderer) + { + super(renderer); + + this.gl = null; + + this.maxAttribs = null; + + // check we have vao.. + this.nativeVaoExtension = null; + + this.attribState = null; + + this.stateId = 0; + this.polygonOffset = 0; + this.blendMode = 17; + + this.map = []; + + // map functions for when we set state.. + this.map[BLEND] = this.setBlend; + this.map[OFFSET] = this.setOffset; + this.map[CULLING] = this.setCullFace; + this.map[DEPTH_TEST] = this.setDepthTest; + this.map[WINDING] = this.setFrontFace; + + this.checks = []; + + this.defaultState = new WebGLState(); + this.defaultState.blend = true; + this.defaultState.depth = true; + } + + contextChange(gl) + { + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); + + // check we have vao.. + this.nativeVaoExtension = ( + gl.getExtension('OES_vertex_array_object') + || gl.getExtension('MOZ_OES_vertex_array_object') + || gl.getExtension('WEBKIT_OES_vertex_array_object') + ); + + this.attribState = { + tempAttribState: new Array(this.maxAttribs), + attribState: new Array(this.maxAttribs), + }; + + this.blendModes = mapWebGLBlendModesToPixi(gl); + + this.setState(this.defaultState); + + this.reset(); + } + + /** + * Sets the current state + * + * @param {*} state - The state to set. + */ + setState(state) + { + state = state || this.defaultState; + + // TODO maybe to an object check? ( this.state === state )? + if (this.stateId !== state.data) + { + let diff = this.stateId ^ state.data; + let i = 0; + + // order from least to most common + while (diff) + { + if (diff & 1) + { + // state change! + this.map[i].call(this, !!(state.data & (1 << i))); + } + + diff = diff >> 1; + i++; + } + + this.stateId = state.data; + } + + // based on the above settings we check for specific modes.. + // for example if blend is active we check and set the blend modes + // or of polygon offset is active we check the poly depth. + for (let i = 0; i < this.checks.length; i++) + { + this.checks[i](this, state); + } + } + + /** + * Enables or disabled blending. + * + * @param {boolean} value - Turn on or off webgl blending. + */ + setBlend(value) + { + this.updateCheck(StateSystem.checkBlendMode, value); + + this.gl[value ? 'enable' : 'disable'](this.gl.BLEND); + } + + /** + * Enables or disable polygon offset fill + * + * @param {boolean} value - Turn on or off webgl polygon offset testing. + */ + setOffset(value) + { + this.gl[value ? 'enable' : 'disable'](this.gl.POLYGON_OFFSET_FILL); + } + + /** + * Sets whether to enable or disable depth test. + * + * @param {boolean} value - Turn on or off webgl depth testing. + */ + setDepthTest(value) + { + this.gl[value ? 'enable' : 'disable'](this.gl.DEPTH_TEST); + } + + /** + * Sets whether to enable or disable cull face. + * + * @param {boolean} value - Turn on or off webgl cull face. + */ + setCullFace(value) + { + this.gl[value ? 'enable' : 'disable'](this.gl.CULL_FACE); + } + + /** + * Sets the gl front face. + * + * @param {boolean} value - true is clockwise and false is counter-clockwise + */ + setFrontFace(value) + { + this.gl.frontFace(this.gl[value ? 'CW' : 'CCW']); + } + + /** + * Sets the blend mode. + * + * @param {number} value - The blend mode to set to. + */ + setBlendMode(value) + { + if (value === this.blendMode) + { + return; + } + + this.blendMode = value; + this.gl.blendFunc(this.blendModes[value][0], this.blendModes[value][1]); + } + + /** + * Sets the polygon offset. + * + * @param {number} value - the polygon offset + * @param {number} scale - the polygon offset scale + */ + setPolygonOffset(value, scale) + { + this.gl.polygonOffset(value, scale); + } + + /** + * Disables all the vaos in use + * + */ + resetAttributes() + { + for (let i = 0; i < this.attribState.tempAttribState.length; i++) + { + this.attribState.tempAttribState[i] = 0; + } + + for (let i = 0; i < this.attribState.attribState.length; i++) + { + this.attribState.attribState[i] = 0; + } + + // im going to assume one is always active for performance reasons. + for (let i = 1; i < this.maxAttribs; i++) + { + this.gl.disableVertexAttribArray(i); + } + } + + // used + /** + * Resets all the logic and disables the vaos + */ + reset() + { + // unbind any VAO if they exist.. + if (this.nativeVaoExtension) + { + this.nativeVaoExtension.bindVertexArrayOES(null); + } + + // reset all attributes.. + this.resetAttributes(); + + this.gl.pixelStorei(this.gl.UNPACK_FLIP_Y_WEBGL, false); + + this.setBlendMode(0); + + // TODO? + // this.setState(this.defaultState); + } + + /** + * checks to see which updates should be checked based on which settings have been activated + * for example if blend is enabled then we shold check the blend modes each time the state is changed + * or if poygon fill is activated then we need to check if the polygone offset changes. + * The idea is that we only check what we have too. + * + * @param {Function} func the checking function to add or remove + * @param {boolean} value should the check function be added or removed. + */ + updateCheck(func, value) + { + const index = this.checks.indexOf(func); + + if (value && index === -1) + { + this.checks.push(func); + } + else if (!value && index !== -1) + { + this.checks.splice(index, 1); + } + } + + /** + * A private little wrapper function that we call to check the blend mode. + * + * @static + * @private + * @param {PIXI.StateSystem} System the System to perform the state check on + * @param {PIXI.State} state the state that the blendMode will pulled from + */ + static checkBlendMode(system, state) + { + system.setBlendMode(state.blendMode); + } + + // TODO - add polygon offset? +} diff --git a/packages/core/src/state/utils/mapWebGLBlendModesToPixi.js b/packages/core/src/state/utils/mapWebGLBlendModesToPixi.js new file mode 100644 index 0000000..10ffe4d --- /dev/null +++ b/packages/core/src/state/utils/mapWebGLBlendModesToPixi.js @@ -0,0 +1,47 @@ +import { BLEND_MODES } from '@pixi/constants'; + +/** + * Maps gl blend combinations to WebGL. + * + * @memberof PIXI + * @function mapWebGLBlendModesToPixi + * @private + * @param {WebGLRenderingContext} gl - The rendering context. + * @param {string[]} [array=[]] - The array to output into. + * @return {string[]} Mapped modes. + */ +export default function mapWebGLBlendModesToPixi(gl, array = []) +{ + // TODO - premultiply alpha would be different. + // add a boolean for that! + array[BLEND_MODES.NORMAL] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; + array[BLEND_MODES.ADD] = [gl.ONE, gl.DST_ALPHA]; + array[BLEND_MODES.MULTIPLY] = [gl.DST_COLOR, gl.ONE_MINUS_SRC_ALPHA]; + array[BLEND_MODES.SCREEN] = [gl.ONE, gl.ONE_MINUS_SRC_COLOR]; + array[BLEND_MODES.OVERLAY] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; + array[BLEND_MODES.DARKEN] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; + array[BLEND_MODES.LIGHTEN] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; + array[BLEND_MODES.COLOR_DODGE] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; + array[BLEND_MODES.COLOR_BURN] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; + array[BLEND_MODES.HARD_LIGHT] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; + array[BLEND_MODES.SOFT_LIGHT] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; + array[BLEND_MODES.DIFFERENCE] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; + array[BLEND_MODES.EXCLUSION] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; + array[BLEND_MODES.HUE] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; + array[BLEND_MODES.SATURATION] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; + array[BLEND_MODES.COLOR] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; + array[BLEND_MODES.LUMINOSITY] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; + array[BLEND_MODES.NONE] = [0, 0]; + + // not-premultiplied blend modes + array[BLEND_MODES.NORMAL_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; + array[BLEND_MODES.ADD_NPM] = [gl.SRC_ALPHA, gl.DST_ALPHA, gl.ONE, gl.DST_ALPHA]; + array[BLEND_MODES.SCREEN_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_COLOR, gl.ONE, gl.ONE_MINUS_SRC_COLOR]; + + // not-premultiplied blend modes + array[BLEND_MODES.NORMAL_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; + array[BLEND_MODES.ADD_NPM] = [gl.SRC_ALPHA, gl.DST_ALPHA, gl.ONE, gl.DST_ALPHA]; + array[BLEND_MODES.SCREEN_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_COLOR, gl.ONE, gl.ONE_MINUS_SRC_COLOR]; + + return array; +} diff --git a/packages/core/src/systems.js b/packages/core/src/systems.js new file mode 100644 index 0000000..8edd9d5 --- /dev/null +++ b/packages/core/src/systems.js @@ -0,0 +1,17 @@ +/** + * Systems are individual components to the Renderer pipeline. + * @namespace PIXI.systems + */ +export { default as FilterSystem } from './filters/FilterSystem'; +export { default as BatchSystem } from './batch/BatchSystem'; +export { default as ContextSystem } from './context/ContextSystem'; +export { default as FramebufferSystem } from './framebuffer/FramebufferSystem'; +export { default as GeometrySystem } from './geometry/GeometrySystem'; +export { default as MaskSystem } from './mask/MaskSystem'; +export { default as StencilSystem } from './mask/StencilSystem'; +export { default as ProjectionSystem } from './projection/ProjectionSystem'; +export { default as RenderTextureSystem } from './renderTexture/RenderTextureSystem'; +export { default as ShaderSystem } from './shader/ShaderSystem'; +export { default as StateSystem } from './state/StateSystem'; +export { default as TextureGCSystem } from './textures/TextureGCSystem'; +export { default as TextureSystem } from './textures/TextureSystem'; diff --git a/packages/core/src/textures/BaseRenderTexture.js b/packages/core/src/textures/BaseRenderTexture.js index 0d4611e..77c44f5 100644 --- a/packages/core/src/textures/BaseRenderTexture.js +++ b/packages/core/src/textures/BaseRenderTexture.js @@ -1,5 +1,5 @@ import BaseTexture from './BaseTexture'; -import FrameBuffer from './FrameBuffer'; +import FrameBuffer from '../framebuffer/FrameBuffer'; /** * A BaseRenderTexture is a special texture that allows any PixiJS display object to be rendered to it. diff --git a/packages/core/src/textures/FrameBuffer.js b/packages/core/src/textures/FrameBuffer.js deleted file mode 100644 index 58c8f39..0000000 --- a/packages/core/src/textures/FrameBuffer.js +++ /dev/null @@ -1,106 +0,0 @@ -import Texture from './BaseTexture'; -import { FORMATS, TYPES } from '@pixi/constants'; - -/** - * Frame buffer - * @class - * @memberof PIXI - */ -export default class FrameBuffer -{ - constructor(width, height) - { - this.width = width || 100; - this.height = height || 100; - - this.stencil = false; - this.depth = false; - - this.dirtyId = 0; - this.dirtyFormat = 0; - this.dirtySize = 0; - - this.depthTexture = null; - this.colorTextures = []; - - this.glFrameBuffers = {}; - } - - get colorTexture() - { - return this.colorTextures[0]; - } - - addColorTexture(index, texture) - { - // TODO add some validation to the texture - same width / height etc? - this.colorTextures[index || 0] = texture || new Texture(null, { scaleMode: 0, - resolution: 1, - mipmap: false, - width: this.width, - height: this.height });// || new Texture(); - - this.dirtyId++; - this.dirtyFormat++; - - return this; - } - - addDepthTexture(texture) - { - /* eslint-disable max-len */ - this.depthTexture = texture || new Texture(null, { scaleMode: 0, - resolution: 1, - width: this.width, - height: this.height, - mipmap: false, - format: FORMATS.DEPTH_COMPONENT, - type: TYPES.UNSIGNED_SHORT });// UNSIGNED_SHORT; - /* eslint-disable max-len */ - this.dirtyId++; - this.dirtyFormat++; - - return this; - } - - enableDepth() - { - this.depth = true; - - this.dirtyId++; - this.dirtyFormat++; - - return this; - } - - enableStencil() - { - this.stencil = true; - - this.dirtyId++; - this.dirtyFormat++; - - return this; - } - - resize(width, height) - { - if (width === this.width && height === this.height) return; - - this.width = width; - this.height = height; - - this.dirtyId++; - this.dirtySize++; - - for (let i = 0; i < this.colorTextures.length; i++) - { - this.colorTextures[i].setSize(width, height); - } - - if (this.depthTexture) - { - this.depthTexture.setSize(width, height); - } - } -} diff --git a/packages/core/src/AbstractRenderer.js b/packages/core/src/AbstractRenderer.js new file mode 100644 index 0000000..64b2eb1 --- /dev/null +++ b/packages/core/src/AbstractRenderer.js @@ -0,0 +1,353 @@ +import { hex2string, hex2rgb } from '@pixi/utils'; +import { Matrix, Rectangle } from '@pixi/math'; +import { RENDERER_TYPE } from '@pixi/constants'; +import { settings } from '@pixi/settings'; +import { Container } from '@pixi/display'; +import RenderTexture from './renderTexture/RenderTexture'; +import EventEmitter from 'eventemitter3'; + +const tempMatrix = new Matrix(); + +/** + * 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 EventEmitter + * @memberof PIXI + */ +export default class AbstractRenderer extends EventEmitter +{ + // eslint-disable-next-line valid-jsdoc + /** + * @param {string} system - The name of the system this renderer is for. + * @param {object} [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.transparent=false] - If the render view is transparent, default false + * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false + * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. The + * resolution of the renderer retina would be 2. + * @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 {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when rendering, + * stopping pixel interpolation. + */ + constructor(system, options, arg2, arg3) + { + super(); + + // Support for constructor(system, screenWidth, screenHeight, options) + if (typeof options === 'number') + { + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + }, arg3); + } + + // 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 = RENDERER_TYPE.UNKNOWN; + + /** + * Measurements of the screen. (0, 0, screenWidth, screenHeight) + * + * Its safe to use as filterArea or hitArea for 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 1 + */ + this.resolution = options.resolution || settings.RESOLUTION; + + /** + * Whether the render view is transparent + * + * @member {boolean} + */ + this.transparent = options.transparent; + + /** + * Whether css dimensions of canvas view should be resized to screen dimensions automatically + * + * @member {boolean} + */ + this.autoResize = options.autoResize || false; + + /** + * Tracks the blend modes useful for this renderer. + * + * @member {object} + */ + this.blendModes = null; + + /** + * 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; + + /** + * If true PixiJS will Math.floor() x/y values when rendering, stopping pixel interpolation. + * Handy for crisp pixel art and speed on legacy devices. + * + * @member {boolean} + */ + this.roundPixels = options.roundPixels; + + /** + * The background color as a number. + * + * @member {number} + * @private + */ + this._backgroundColor = 0x000000; + + /** + * The background color as an [R, G, B] array. + * + * @member {number[]} + * @private + */ + this._backgroundColorRgba = [0, 0, 0, 0]; + + /** + * The background color as a string. + * + * @member {string} + * @private + */ + this._backgroundColorString = '#000000'; + + this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter + + /** + * This temporary display object used as the parent of the currently being rendered item + * + * @member {PIXI.DisplayObject} + * @private + */ + this._tempDisplayObjectParent = new Container(); + + /** + * The last root object that the renderer tried to render. + * + * @member {PIXI.DisplayObject} + * @private + */ + this._lastObjectRendered = this._tempDisplayObjectParent; + + /** + * Collection of plugins. + * @readonly + * @member {object} + */ + this.plugins = {}; + } + + /** + * Initialize the plugins. + * + * @protected + * @param {object} staticMap - The dictionary of staticly saved plugins. + */ + initPlugins(staticMap) + { + 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() + { + return this.view.width; + } + + /** + * Same as view.height, actual number of pixels in the canvas by vertical + * + * @member {number} + * @readonly + * @default 600 + */ + get height() + { + return this.view.height; + } + + /** + * Resizes the screen and canvas to the specified width and height + * Canvas dimensions are multiplied by resolution + * + * @param {number} screenWidth - the new width of the screen + * @param {number} screenHeight - the new height of the screen + */ + resize(screenWidth, screenHeight) + { + this.screen.width = screenWidth; + this.screen.height = screenHeight; + + this.view.width = screenWidth * this.resolution; + this.view.height = screenHeight * this.resolution; + + if (this.autoResize) + { + this.view.style.width = `${screenWidth}px`; + this.view.style.height = `${screenHeight}px`; + } + } + + /** + * 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 {PIXI.DisplayObject} displayObject - The displayObject the object will be generated from + * @param {number} scaleMode - Should be one of the scaleMode consts + * @param {number} resolution - The resolution / device pixel ratio of the texture being generated + * @param {PIXI.Rectangle} [region] - The region of the displayObject, that shall be rendered, + * if no region is specified, defaults to the local bounds of the displayObject. + * @return {PIXI.Texture} a texture of the graphics object + */ + generateTexture(displayObject, scaleMode, resolution, region) + { + region = region || displayObject.getLocalBounds(); + + // 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(region.width | 0, region.height | 0, scaleMode, resolution); + + tempMatrix.tx = -region.x; + tempMatrix.ty = -region.y; + + this.render(displayObject, renderTexture, false, tempMatrix, true); + + return renderTexture; + } + + /** + * Removes everything from the renderer and optionally removes the Canvas DOM element. + * + * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. + */ + destroy(removeView) + { + 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); + } + + this.plugins = null; + + this.type = RENDERER_TYPE.UNKNOWN; + + this.view = null; + + this.screen = null; + + this.resolution = 0; + + this.transparent = false; + + this.autoResize = false; + + this.blendModes = null; + + this.options = null; + + this.preserveDrawingBuffer = false; + this.clearBeforeRender = false; + + this.roundPixels = false; + + this._backgroundColor = 0; + this._backgroundColorRgba = null; + this._backgroundColorString = null; + + this._tempDisplayObjectParent = null; + this._lastObjectRendered = null; + } + + /** + * The background color to fill if not transparent + * + * @member {number} + */ + get backgroundColor() + { + return this._backgroundColor; + } + + set backgroundColor(value) // eslint-disable-line require-jsdoc + { + this._backgroundColor = value; + this._backgroundColorString = hex2string(value); + hex2rgb(value, this._backgroundColorRgba); + } +} diff --git a/packages/core/src/Renderer.js b/packages/core/src/Renderer.js new file mode 100644 index 0000000..2227e78 --- /dev/null +++ b/packages/core/src/Renderer.js @@ -0,0 +1,338 @@ +import AbstractRenderer from './AbstractRenderer'; +import { sayHello } from '@pixi/utils'; +import MaskSystem from './mask/MaskSystem'; +import StencilSystem from './mask/StencilSystem'; +import FilterSystem from './filters/FilterSystem'; +import FramebufferSystem from './framebuffer/FramebufferSystem'; +import RenderTextureSystem from './renderTexture/RenderTextureSystem'; +import TextureSystem from './textures/TextureSystem'; +import ProjectionSystem from './projection/ProjectionSystem'; +import StateSystem from './state/StateSystem'; +import GeometrySystem from './geometry/GeometrySystem'; +import ShaderSystem from './shader/ShaderSystem'; +import ContextSystem from './context/ContextSystem'; +import BatchSystem from './batch/BatchSystem'; +import TextureGCSystem from './textures/TextureGCSystem'; +import { RENDERER_TYPE } from '@pixi/constants'; +import UniformGroup from './shader/UniformGroup'; +import { Matrix } from '@pixi/math'; +import Runner from 'mini-runner'; + +/** + * 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 Render works by automatically managing webGLBatchs. + * 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 :) + * + * @class + * @memberof PIXI + * @extends PIXI.AbstractRenderer + */ +export default class Renderer extends AbstractRenderer +{ + // eslint-disable-next-line valid-jsdoc + /** + * + * @param {object} [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.transparent=false] - If the render view is transparent, default false + * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false + * @param {boolean} [options.antialias=false] - sets antialias. If not available natively then FXAA + * antialiasing is used + * @param {boolean} [options.forceFXAA=false] - forces FXAA antialiasing to be used over native. + * FXAA is faster, but may not always look as great + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. + * The resolution of the renderer retina would be 2. + * @param {boolean} [options.clearBeforeRender=true] - This sets if the renderer will clear + * the canvas or not before the new render pass. If you wish to set this to false, you *must* set + * preserveDrawingBuffer to `true`. + * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, + * enable this if you need to call toDataUrl on the webgl context. + * @param {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when + * rendering, stopping pixel interpolation. + * @param {number} [options.backgroundColor=0x000000] - The background color of the rendered area + * (shown if not transparent). + * @param {boolean} [options.legacy=false] - If true PixiJS will aim to ensure compatibility + * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. + * @param {string} [options.powerPreference] - Parameter passed to webgl context, set to "high-performance" + * for devices with dual graphics card + */ + constructor(options, arg2, arg3) + { + super('WebGL', options, arg2, arg3); + + /** + * The type of this renderer as a standardised const + * + * @member {number} + * @see PIXI.RENDERER_TYPE + */ + this.type = RENDERER_TYPE.WEBGL; + + // this will be set by the contextSystem (this.context) + this.gl = null; + this.CONTEXT_UID = 0; + this.legacy = !!options.legacy; + + // TODO legacy! + + // runners! + this.runners = { + destroy: new Runner('destroy'), + contextChange: new Runner('contextChange', 1), + reset: new Runner('reset'), + update: new Runner('update'), + postrender: new Runner('postrender'), + prerender: new Runner('prerender'), + resize: new Runner('resize', 2), + }; + + 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(GeometrySystem, 'geometry') + .addSystem(FramebufferSystem, 'framebuffer') + .addSystem(StencilSystem, 'stencil') + .addSystem(ProjectionSystem, 'projection') + .addSystem(TextureGCSystem, 'textureGC') + .addSystem(FilterSystem, 'filter') + .addSystem(RenderTextureSystem, 'renderTexture') + .addSystem(BatchSystem, 'batch'); + + this.initPlugins(Renderer.__plugins); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ + if (options.context) + { + this.context.initFromContext(options.context); + } + else + { + this.context.initFromOptions({ + alpha: this.transparent, + antialias: options.antialias, + premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', + stencil: true, + 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); + } + + /** + * Add a new system to the renderer. + * @param {class} ClassRef - Class reference + * @param {string} [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. + * @return {PIXI.Renderer} Return instance of renderer + */ + addSystem(ClassRef, name) + { + if (!name) + { + name = ClassRef.name; + } + + // TODO - read name from class.name.. + + /* + if(name.includes('System')) + { + name = name.replace('System', ''); + name = name.charAt(0).toLowerCase() + name.slice(1); + } + */ + + const system = new ClassRef(this); + + if (this[name]) + { + throw new Error(`Whoops! ${name} is already a manger`); + } + + this[name] = system; + + for (const i in this.runners) + { + this.runners[i].add(system); + } + + return this; + + /** + * 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. + */ + } + + /** + * Renders the object to its webGL view + * + * @param {PIXI.DisplayObject} displayObject - the object to be rendered + * @param {PIXI.RenderTexture} renderTexture - The render texture to render to. + * @param {boolean} [clear] - Should the canvas be cleared before the new render + * @param {PIXI.Transform} [transform] - A transform to apply to the render texture before rendering. + * @param {boolean} [skipUpdateTransform] - Should we skip the update transform pass? + */ + render(displayObject, renderTexture, clear, transform, skipUpdateTransform) + { + // can be handy to know! + this.renderingToScreen = !renderTexture; + + this.runners.prerender.run(); + this.emit('prerender'); + + // 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.parent; + + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = 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.run(); + + this.emit('postrender'); + } + + /** + * Resizes the webGL view to the specified width and height. + * + * @param {number} screenWidth - the new width of the screen + * @param {number} screenHeight - the new height of the screen + */ + resize(screenWidth, screenHeight) + { + AbstractRenderer.prototype.resize.call(this, screenWidth, screenHeight); + + this.runners.resize.run(screenWidth, screenHeight); + } + + /** + * Resets the WebGL state so you can render things however you fancy! + * + * @return {PIXI.Renderer} Returns itself. + */ + reset() + { + this.runners.reset.run(); + + return this; + } + + clear() + { + this.framebuffer.bind(); + this.framebuffer.clear(); + } + + /** + * Removes everything from the renderer (event listeners, spritebatch, etc...) + * + * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. + * See: https://github.com/pixijs/pixi.js/issues/2233 + */ + destroy(removeView) + { + this.runners.destroy.run(); + + // call base destroy + super.destroy(removeView); + + // TODO nullify all the managers.. + this.gl = null; + } + + /** + * 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 + * builds and excluding plugins. + * @name PIXI.Renderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. + */ + + /** + * Adds a plugin to the renderer. + * + * @method + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + static registerPlugin(pluginName, ctor) + { + Renderer.__plugins = Renderer.__plugins || {}; + Renderer.__plugins[pluginName] = ctor; + } +} diff --git a/packages/core/src/System.js b/packages/core/src/System.js new file mode 100644 index 0000000..05a10c8 --- /dev/null +++ b/packages/core/src/System.js @@ -0,0 +1,42 @@ +/** + * System is a base class used for extending systems used by the {@link PIXI.Renderer} + * @see PIXI.Renderer#addSystem + * @class + * @memberof PIXI + */ +export default class System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + /** + * The renderer this manager works for. + * + * @member {PIXI.Renderer} + */ + this.renderer = renderer; + + this.renderer.runners.contextChange.add(this); + } + + /** + * Generic method called when there is a WebGL context change. + * + */ + contextChange() + { + // do some codes init! + } + + /** + * Generic destroy methods to be overridden by the subclass + * + */ + destroy() + { + this.renderer.runners.contextChange.remove(this); + this.renderer = null; + } +} diff --git a/packages/core/src/batch/BatchSystem.js b/packages/core/src/batch/BatchSystem.js new file mode 100644 index 0000000..b04c249 --- /dev/null +++ b/packages/core/src/batch/BatchSystem.js @@ -0,0 +1,68 @@ +import System from '../System'; +import ObjectRenderer from './ObjectRenderer'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class BatchSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + super(renderer); + + /** + * An empty renderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.emptyRenderer = new ObjectRenderer(renderer); + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.currentRenderer = this.emptyRenderer; + } + + /** + * Changes the current renderer to the one given in parameter + * + * @param {PIXI.ObjectRenderer} objectRenderer - The object renderer to use. + */ + setObjectRenderer(objectRenderer) + { + if (this.currentRenderer === objectRenderer) + { + return; + } + + this.currentRenderer.stop(); + this.currentRenderer = objectRenderer; + + this.renderer.state.setState(objectRenderer.state); + + this.currentRenderer.start(); + } + + /** + * This should be called if you wish to do some custom rendering + * It will basically render anything that may be batched up such as sprites + * + */ + flush() + { + this.setObjectRenderer(this.emptyRenderer); + } + + reset() + { + this.setObjectRenderer(this.emptyRenderer); + } +} diff --git a/packages/core/src/batch/ObjectRenderer.js b/packages/core/src/batch/ObjectRenderer.js new file mode 100644 index 0000000..0c851a9 --- /dev/null +++ b/packages/core/src/batch/ObjectRenderer.js @@ -0,0 +1,48 @@ +import System from '../System'; + +/** + * Base for a common object renderer that can be used as a system renderer plugin. + * + * @class + * @extends PIXI.System + * @memberof PIXI + */ +export default class ObjectRenderer extends System +{ + /** + * Starts the renderer and sets the shader + * + */ + start() + { + // set the shader.. + } + + /** + * Stops the renderer + * + */ + stop() + { + this.flush(); + } + + /** + * Stub method for rendering content and emptying the current batch. + * + */ + flush() + { + // flush! + } + + /** + * Renders an object + * + * @param {PIXI.DisplayObject} object - The object to render. + */ + render(object) // eslint-disable-line no-unused-vars + { + // render the object + } +} diff --git a/packages/core/src/context/ContextSystem.js b/packages/core/src/context/ContextSystem.js new file mode 100644 index 0000000..301aab4 --- /dev/null +++ b/packages/core/src/context/ContextSystem.js @@ -0,0 +1,186 @@ +import System from '../System'; +import { settings } from '@pixi/settings'; + +let CONTEXT_UID = 0; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class ContextSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.webGLVersion = 1; + + this.handleContextLost = this.handleContextLost.bind(this); + this.handleContextRestored = this.handleContextRestored.bind(this); + + this.extensions = {}; + + renderer.view.addEventListener('webglcontextlost', this.handleContextLost, false); + renderer.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + } + + get isLost() + { + return (!this.gl || this.gl.isContextLost()); + } + + contextChange(gl) + { + this.gl = gl; + + // restore a context if it was previously lost + if (gl.isContextLost() && gl.getExtension('WEBGL_lose_context')) + { + gl.getExtension('WEBGL_lose_context').restoreContext(); + } + } + + initFromContext(gl) + { + this.gl = gl; + this.validateContext(gl); + this.renderer.gl = gl; + this.renderer.CONTEXT_UID = CONTEXT_UID++; + this.renderer.runners.contextChange.run(gl); + } + + initFromOptions(options) + { + const gl = this.createContext(this.renderer.view, options); + + this.initFromContext(gl); + } + + /** + * Helper class to create a webGL Context + * + * @class + * @memberof PIXI.glCore + * @param canvas {HTMLCanvasElement} the canvas element that we will get the context from + * @param options {Object} An options object that gets passed in to the canvas element containing the context attributes, + * see https://developer.mozilla.org/en/docs/Web/API/HTMLCanvasElement/getContext for the options available + * @return {WebGLRenderingContext} the WebGL context + */ + createContext(canvas, options) + { + let gl; + + if (settings.PREFER_WEBGL_2) + { + gl = canvas.getContext('webgl2', options); + } + + if (gl) + { + this.webGLVersion = 2; + } + else + { + this.webGLVersion = 1; + + gl = canvas.getContext('webgl', options) + || canvas.getContext('experimental-webgl', options); + + if (!gl) + { + // fail, not able to get a context + throw new Error('This browser does not support webGL. Try using the canvas renderer'); + } + } + + this.gl = gl; + + this.getExtensions(); + + return gl; + } + + getExtensions() + { + // time to set up default etensions that pixi uses.. + const gl = this.gl; + const extensions = this.extensions; + + if (this.webGLVersion === 1) + { + extensions.drawBuffers = gl.getExtension('WEBGL_draw_buffers'); + extensions.depthTexture = gl.getExtension('WEBKIT_WEBGL_depth_texture'); + extensions.floatTexture = gl.getExtension('OES_texture_float'); + extensions.loseContext = gl.getExtension('WEBGL_lose_context'); + + extensions.vertexArrayObject = gl.getExtension('OES_vertex_array_object') + || gl.getExtension('MOZ_OES_vertex_array_object') + || gl.getExtension('WEBKIT_OES_vertex_array_object'); + } + + // we don't use any specific WebGL 2 ones yet! + } + + /** + * Handles a lost webgl context + * + * @private + * @param {WebGLContextEvent} event - The context lost event. + */ + handleContextLost(event) + { + event.preventDefault(); + } + + /** + * Handles a restored webgl context + * + * @private + */ + handleContextRestored() + { + this.renderer.runners.contextChange.run(this.gl); + } + + destroy() + { + const view = this.renderer.view; + + // remove listeners + view.removeEventListener('webglcontextlost', this.handleContextLost); + view.removeEventListener('webglcontextrestored', this.handleContextRestored); + + this.gl.useProgram(null); + + if (this.extensions.loseContext) + { + this.extensions.loseContext.loseContext(); + } + } + + postrender() + { + this.gl.flush(); + } + + validateContext(gl) + { + const attributes = gl.getContextAttributes(); + + // this is going to be fairly simple for now.. but at least we have room to grow! + if (!attributes.stencil) + { + /* eslint-disable max-len */ + + /* eslint-disable no-console */ + console.warn('Provided WebGL context does not have a stencil buffer, masks may not render correctly'); + /* eslint-enable no-console */ + + /* eslint-enable max-len */ + } + } +} diff --git a/packages/core/src/filters/Filter.js b/packages/core/src/filters/Filter.js new file mode 100644 index 0000000..9d8cad8 --- /dev/null +++ b/packages/core/src/filters/Filter.js @@ -0,0 +1,140 @@ +import Shader from '../shader/Shader'; +import Program from '../shader/Program'; +import State from '../state/State'; +import { settings } from '@pixi/settings'; +// import extractUniformsFromSrc from './extractUniformsFromSrc'; +import defaultVertex from './defaultFilter.vert'; +import defaultFragment from './defaultFilter.frag'; + +// let math = require('../../math'); +/** + * @class + * @memberof PIXI + * @extends PIXI.Shader + */ +export default class Filter extends Shader +{ + /** + * @param {string} [vertexSrc] - The source of the vertex shader. + * @param {string} [fragmentSrc] - The source of the fragment shader. + * @param {object} [uniforms] - Custom uniforms to use to augment the built-in ones. + */ + constructor(vertexSrc, fragmentSrc, uniforms) + { + const program = Program.from(vertexSrc, fragmentSrc); + + super(program, uniforms); + + /** + * The padding of the filter. Some filters require extra space to breath such as a blur. + * Increasing this will add extra width and height to the bounds of the object that the + * filter is applied to. + * + * @member {number} + */ + this.padding = 0; + + /** + * The resolution of the filter. Setting this to be lower will lower the quality but + * increase the performance of the filter. + * + * @member {number} + */ + this.resolution = settings.RESOLUTION; + + /** + * If enabled is true the filter is applied, if false it will not. + * + * @member {boolean} + */ + this.enabled = true; + + /** + * If enabled, PixiJS will fit the filter area into boundaries for better performance. + * Switch it off if it does not work for specific shader. + * + * @member {boolean} + */ + this.autoFit = true; + + /** + * Legacy filters use position and uvs from attributes + * @member {boolean} + * @readonly + */ + this.legacy = !!this.program.attributeData.aTextureCoord; + + /** + * the webGL state the filter requires to render + * @member {PIXI.State} + */ + this.state = new State(); + } + + /** + * Applies the filter + * + * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + * @param {boolean} clear - Should the output be cleared before rendering to it + * @param {object} [currentState] - It's current state of filter. + * There are some useful properties in the currentState : + * target, filters, sourceFrame, destinationFrame, renderTarget, resolution + */ + apply(filterManager, input, output, clear, currentState, derp) // eslint-disable-line no-unused-vars + { + // do as you please! + + filterManager.applyFilter(this, input, output, clear, currentState, derp); + + // or just do a regular render.. + } + + /** + * Sets the blendmode of the filter + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL + */ + get blendMode() + { + return this.state.blendMode; + } + + set blendMode(value) // eslint-disable-line require-jsdoc + { + this.state.blendMode = value; + } + + /** + * The default vertex shader source + * + * @static + * @constant + */ + static get defaultVertexSrc() + { + return defaultVertex; + } + + /** + * The default fragment shader source + * + * @static + * @constant + */ + static get defaultFragmentSrc() + { + return defaultFragment; + } +} + +/** + * Used for caching shader IDs + * + * @static + * @private + */ +Filter.SOURCE_KEY_MAP = {}; + diff --git a/packages/core/src/filters/FilterSystem.js b/packages/core/src/filters/FilterSystem.js new file mode 100644 index 0000000..3174318 --- /dev/null +++ b/packages/core/src/filters/FilterSystem.js @@ -0,0 +1,431 @@ +import System from '../System'; + +import RenderTexture from '../renderTexture/RenderTexture'; +import Quad from '../utils/Quad'; +import QuadUv from '../utils/QuadUv'; +import { Rectangle } from '@pixi/math'; +import * as filterTransforms from './filterTransforms'; +import bitTwiddle from 'bit-twiddle'; +import UniformGroup from '../shader/UniformGroup'; +import { DRAW_MODES } from '@pixi/constants'; + +// +/** + * @ignore + * @class + */ +class FilterState +{ + /** + * + */ + constructor() + { + this.renderTexture = null; + this.sourceFrame = new Rectangle(); + this.destinationFrame = new Rectangle(); + this.filters = []; + this.target = null; + this.legacy = false; + this.resolution = 1; + } +} + +/** + * @class + * @memberof PIXI.systems + * @extends PIXI.System + */ +export default class FilterSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + /** + * stores a bunch of PO2 textures used for filtering + * @type {Object} + */ + this.texturePool = {}; + + /** + * a pool for storing filter states, save us creating new ones each tick + * @type {Array} + */ + this.statePool = []; + + /** + * A very simple geometry used when drawing a filter effect to the screen + * @type {Quad} + */ + this.quad = new Quad(); + + this.quadUv = new QuadUv(); + + /** + * Temporary rect for maths + * @type {PIXI.Rectangle} + */ + this.tempRect = new Rectangle(); + + this.activeState = {}; + + /** + * this uniform group is attached to filter uniforms when used + * @type {UniformGroup} + */ + this.globalUniforms = new UniformGroup({ + sourceFrame: this.tempRect, + destinationFrame: this.tempRect, + + // legacy variables + filterArea: new Float32Array(4), + filterClamp: new Float32Array(4), + }, true); + } + + /** + * Adds a new filter to the System. + * + * @param {PIXI.DisplayObject} target - The target of the filter to render. + * @param {PIXI.Filter[]} filters - The filters to apply. + */ + push(target, filters) + { + const renderer = this.renderer; + const filterStack = this.renderer.renderTexture.defaultFilterStack; + const state = this.statePool.pop() || new FilterState(); + + let resolution = filters[0].resolution; + let padding = filters[0].padding; + let autoFit = filters[0].autoFit; + let legacy = filters[0].legacy; + + for (let i = 1; i < filters.length; i++) + { + const filter = filters[i]; + + // lets use the lowest resolution.. + resolution = Math.min(resolution, filter.resolution); + // and the largest amount of padding! + padding = Math.max(padding, filter.padding); + // only auto fit if all filters are autofit + autoFit = autoFit || filter.autoFit; + + legacy = legacy || filter.legacy; + } + + filterStack.push(state); + + state.resolution = resolution; + + state.legacy = legacy; + + // round to whole number based on resolution + // TODO move that to the shader too? + state.sourceFrame = target.filterArea || target.getBounds(true); + + state.sourceFrame.pad(padding); + + if (autoFit) + { + state.sourceFrame.fit(this.renderer.renderTexture.destinationFrame); + } + + state.sourceFrame.round(resolution); + + state.renderTexture = this.getPotFilterTexture(state.sourceFrame.width, state.sourceFrame.height, resolution); + state.filters = filters; + + state.destinationFrame.width = state.renderTexture.width; + state.destinationFrame.height = state.renderTexture.height; + + state.renderTexture.filterFrame = state.sourceFrame; + renderer.renderTexture.bind(state.renderTexture, state.sourceFrame);// /, state.destinationFrame); + renderer.renderTexture.clear(); + } + + /** + * Pops off the filter and applies it. + * + */ + pop() + { + const renderer = this.renderer; + const filterStack = renderer.renderTexture.defaultFilterStack; + const state = filterStack.pop(); + const filters = state.filters; + + this.activeState = state; + + const globalUniforms = this.globalUniforms.uniforms; + + globalUniforms.sourceFrame = state.sourceFrame; + globalUniforms.destinationFrame = state.destinationFrame; + globalUniforms.resolution = state.resolution; + + // only update the rect if its legacy.. + if (state.legacy) + { + const filterArea = globalUniforms.filterArea; + const filterClamp = globalUniforms.filterClamp; + + filterArea[0] = state.destinationFrame.width; + filterArea[1] = state.destinationFrame.height; + filterArea[2] = state.sourceFrame.x; + filterArea[3] = state.sourceFrame.y; + + filterClamp[0] = 0.5 / state.resolution / state.destinationFrame.width; + filterClamp[1] = 0.5 / state.resolution / state.destinationFrame.height; + filterClamp[2] = (state.sourceFrame.width - 0.5) / state.resolution / state.destinationFrame.width; + filterClamp[3] = (state.sourceFrame.height - 0.5) / state.resolution / state.destinationFrame.height; + } + + this.globalUniforms.update(); + + const lastState = filterStack[filterStack.length - 1]; + + if (filters.length === 1) + { + filters[0].apply(this, state.renderTexture, lastState.renderTexture, false, state); + + this.returnFilterTexture(state.renderTexture); + } + else + { + let flip = state.renderTexture; + let flop = this.getPotFilterTexture( + flip.width, + flip.height, + state.resolution + ); + + flop.filterFrame = flip.filterFrame; + + let i = 0; + + for (i = 0; i < filters.length - 1; ++i) + { + filters[i].apply(this, flip, flop, true, state); + + const t = flip; + + flip = flop; + flop = t; + } + + filters[i].apply(this, flip, lastState.renderTexture, false, state); + + this.returnFilterTexture(flip); + this.returnFilterTexture(flop); + } + + this.statePool.push(state); + } + + /** + * Draws a filter. + * + * @param {PIXI.Filter} filter - The filter to draw. + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + * @param {boolean} clear - Should the output be cleared before rendering to it + */ + applyFilter(filter, input, output, clear) + { + const renderer = this.renderer; + + renderer.renderTexture.bind(output, output ? output.filterFrame : null); + + if (clear) + { + // gl.disable(gl.SCISSOR_TEST); + renderer.renderTexture.clear(); + // gl.enable(gl.SCISSOR_TEST); + } + + // set the uniforms.. + filter.uniforms.uSampler = input; + filter.uniforms.filterGlobals = this.globalUniforms; + + // TODO make it so that the order of this does not matter.. + // because it does at the moment cos of global uniforms. + // they need to get resynced + + renderer.state.setState(filter.state); + renderer.shader.bind(filter); + + if (filter.legacy) + { + this.quadUv.map(input._frame, input.filterFrame); + + renderer.geometry.bind(this.quadUv); + renderer.geometry.draw(DRAW_MODES.TRIANGLES); + } + else + { + renderer.geometry.bind(this.quad); + renderer.geometry.draw(DRAW_MODES.TRIANGLE_STRIP); + } + } + + /** + * Calculates the mapped matrix. + * + * TODO playing around here.. this is temporary - (will end up in the shader) + * this returns a matrix that will normalise map filter cords in the filter to screen space + * + * @param {PIXI.Matrix} outputMatrix - the matrix to output to. + * @return {PIXI.Matrix} The mapped matrix. + */ + calculateScreenSpaceMatrix(outputMatrix) + { + const currentState = this.activeState; + + return filterTransforms.calculateScreenSpaceMatrix( + outputMatrix, + currentState.sourceFrame, + currentState.destinationFrame + ); + } + + /** + * This will map the filter coord so that a texture can be used based on the transform of a sprite + * + * @param {PIXI.Matrix} outputMatrix - The matrix to output to. + * @param {PIXI.Sprite} sprite - The sprite to map to. + * @return {PIXI.Matrix} The mapped matrix. + */ + calculateSpriteMatrix(outputMatrix, sprite) + { + const currentState = this.activeState; + + return filterTransforms.calculateSpriteMatrix( + outputMatrix, + currentState.sourceFrame, + currentState.destinationFrame, + sprite + ); + } + + /** + * Destroys this Filter System. + * + * @param {boolean} [contextLost=false] context was lost, do not free shaders + * + */ + destroy(contextLost = false) + { + if (!contextLost) + { + this.emptyPool(); + } + else + { + this.texturePool = {}; + } + } + + /** + * Gets a Power-of-Two render texture. + * + * TODO move to a seperate class could be on renderer? + * also - could cause issue with multiple contexts? + * + * @private + * @param {WebGLRenderingContext} gl - The webgl rendering context + * @param {number} minWidth - The minimum width of the render target. + * @param {number} minHeight - The minimum height of the render target. + * @param {number} resolution - The resolution of the render target. + * @return {PIXI.RenderTarget} The new render target. + */ + getPotFilterTexture(minWidth, minHeight, resolution) + { + minWidth = bitTwiddle.nextPow2(minWidth); + minHeight = bitTwiddle.nextPow2(minHeight); + resolution = resolution || 1; + + const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + + if (!this.texturePool[key]) + { + this.texturePool[key] = []; + } + + let renderTexture = this.texturePool[key].pop(); + + if (!renderTexture) + { + // temporary bypass cache.. + // internally - this will cause a texture to be bound.. + renderTexture = RenderTexture.create({ + width: minWidth, + height: minHeight, + resolution, + }); + } + + return renderTexture; + } + + /** + * Gets extra render texture to use inside current filter + * + * @param {number} resolution resolution of the renderTexture + * @returns {PIXI.RenderTexture} + */ + getFilterTexture(resolution) + { + const rt = this.activeState.renderTexture; + + const filterTexture = this.getPotFilterTexture(rt.width, rt.height, resolution || rt.baseTexture.resolution); + + filterTexture.filterFrame = rt.filterFrame; + + return filterTexture; + } + + /** + * Frees a render texture back into the pool. + * + * @param {PIXI.RenderTarget} renderTexture - The renderTarget to free + */ + returnFilterTexture(renderTexture) + { + renderTexture.filterFrame = null; + + const base = renderTexture.baseTexture; + + const minWidth = base.width; + const minHeight = base.height; + + const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + + this.texturePool[key].push(renderTexture); + } + + /** + * Empties the texture pool. + * + */ + emptyPool() + { + for (const i in this.texturePool) + { + const textures = this.texturePool[i]; + + if (textures) + { + for (let j = 0; j < textures.length; j++) + { + textures[j].destroy(true); + } + } + } + + this.texturePool = {}; + } +} diff --git a/packages/core/src/filters/defaultFilter.frag b/packages/core/src/filters/defaultFilter.frag new file mode 100644 index 0000000..a14c1be --- /dev/null +++ b/packages/core/src/filters/defaultFilter.frag @@ -0,0 +1,22 @@ +varying vec2 vTextureCoord; +varying vec2 vFilterCoord; + +uniform sampler2D uSampler; +uniform sampler2D filterSampler; + +void main(void){ + vec4 masky = texture2D(filterSampler, vFilterCoord); + vec4 sample = texture2D(uSampler, vTextureCoord); + vec4 color; + if(mod(vFilterCoord.x, 1.0) > 0.5) + { + color = vec4(1.0, 0.0, 0.0, 1.0); + } + else + { + color = vec4(0.0, 1.0, 0.0, 1.0); + } + // gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0); + gl_FragColor = mix(sample, masky, 0.5); + gl_FragColor *= sample.a; +} diff --git a/packages/core/src/filters/defaultFilter.vert b/packages/core/src/filters/defaultFilter.vert new file mode 100644 index 0000000..e1febca --- /dev/null +++ b/packages/core/src/filters/defaultFilter.vert @@ -0,0 +1,14 @@ +attribute vec2 aVertexPosition; +attribute vec2 aTextureCoord; + +uniform mat3 projectionMatrix; +uniform mat3 filterMatrix; + +varying vec2 vTextureCoord; +varying vec2 vFilterCoord; + +void main(void){ + gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); + vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy; + vTextureCoord = aTextureCoord ; +} diff --git a/packages/core/src/filters/filterTransforms.js b/packages/core/src/filters/filterTransforms.js new file mode 100644 index 0000000..87a3cd6 --- /dev/null +++ b/packages/core/src/filters/filterTransforms.js @@ -0,0 +1,50 @@ +import { Matrix } from '@pixi/math'; + +/** + * Calculates the mapped matrix + * @param filterArea {Rectangle} The filter area + * @param sprite {Sprite} the target sprite + * @param outputMatrix {Matrix} @alvin + * @private + */ +// this returns a matrix that will normalise map filter cords in the filter to screen space +export function calculateScreenSpaceMatrix(outputMatrix, filterArea, textureSize) +{ + // TODO unwrap? + const mappedMatrix = outputMatrix.identity(); + + mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); + + mappedMatrix.scale(textureSize.width, textureSize.height); + + return mappedMatrix; +} + +export function calculateNormalizedScreenSpaceMatrix(outputMatrix, filterArea, textureSize) +{ + const mappedMatrix = outputMatrix.identity(); + + mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); + + const translateScaleX = (textureSize.width / filterArea.width); + const translateScaleY = (textureSize.height / filterArea.height); + + mappedMatrix.scale(translateScaleX, translateScaleY); + + return mappedMatrix; +} + +// this will map the filter coord so that a texture can be used based on the transform of a sprite +export function calculateSpriteMatrix(outputMatrix, filterArea, textureSize, sprite) +{ + const orig = sprite._texture.orig; + const mappedMatrix = outputMatrix.set(textureSize.width, 0, 0, textureSize.height, filterArea.x, filterArea.y); + const worldTransform = sprite.worldTransform.copyTo(Matrix.TEMP_MATRIX); + + worldTransform.invert(); + mappedMatrix.prepend(worldTransform); + mappedMatrix.scale(1.0 / orig.width, 1.0 / orig.height); + mappedMatrix.translate(sprite.anchor.x, sprite.anchor.y); + + return mappedMatrix; +} diff --git a/packages/core/src/filters/spriteMask/SpriteMaskFilter.js b/packages/core/src/filters/spriteMask/SpriteMaskFilter.js new file mode 100644 index 0000000..91697b7 --- /dev/null +++ b/packages/core/src/filters/spriteMask/SpriteMaskFilter.js @@ -0,0 +1,63 @@ +import Filter from '../Filter'; +import { Matrix } from '@pixi/math'; +import vertex from './spriteMaskFilter.vert'; +import fragment from './spriteMaskFilter.frag'; +import { default as TextureMatrix } from '../../textures/TextureMatrix'; + +/** + * The SpriteMaskFilter class + * + * @class + * @extends PIXI.Filter + * @memberof PIXI + */ +export default class SpriteMaskFilter extends Filter +{ + /** + * @param {PIXI.Sprite} sprite - the target sprite + */ + constructor(sprite) + { + const maskMatrix = new Matrix(); + + super(vertex, fragment); + + sprite.renderable = false; + + this.maskSprite = sprite; + this.maskMatrix = maskMatrix; + } + + /** + * Applies the filter + * + * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + */ + apply(filterManager, input, output) + { + const maskSprite = this.maskSprite; + const tex = this.maskSprite.texture; + + if (!tex.valid) + { + return; + } + if (!tex.transform) + { + // margin = 0.0, let it bleed a bit, shader code becomes easier + // assuming that atlas textures were made with 1-pixel padding + tex.transform = new TextureMatrix(tex, 0.0); + } + tex.transform.update(); + + this.uniforms.mask = tex; + this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite) + .prepend(tex.transform.mapCoord); + this.uniforms.alpha = maskSprite.worldAlpha; + this.uniforms.maskClamp = tex.transform.uClampFrame; + + filterManager.applyFilter(this, input, output); + } +} diff --git a/packages/core/src/filters/spriteMask/spriteMaskFilter.frag b/packages/core/src/filters/spriteMask/spriteMaskFilter.frag new file mode 100644 index 0000000..0e4aef8 --- /dev/null +++ b/packages/core/src/filters/spriteMask/spriteMaskFilter.frag @@ -0,0 +1,23 @@ +varying vec2 vMaskCoord; +varying vec2 vTextureCoord; + +uniform sampler2D uSampler; +uniform sampler2D mask; +uniform float alpha; +uniform vec4 maskClamp; + +void main(void) +{ + float clip = step(3.5, + step(maskClamp.x, vMaskCoord.x) + + step(maskClamp.y, vMaskCoord.y) + + step(vMaskCoord.x, maskClamp.z) + + step(vMaskCoord.y, maskClamp.w)); + + vec4 original = texture2D(uSampler, vTextureCoord); + vec4 masky = texture2D(mask, vMaskCoord); + + original *= (masky.r * masky.a * alpha * clip); + + gl_FragColor = original; +} diff --git a/packages/core/src/filters/spriteMask/spriteMaskFilter.vert b/packages/core/src/filters/spriteMask/spriteMaskFilter.vert new file mode 100644 index 0000000..d23a30c --- /dev/null +++ b/packages/core/src/filters/spriteMask/spriteMaskFilter.vert @@ -0,0 +1,16 @@ +attribute vec2 aVertexPosition; +attribute vec2 aTextureCoord; + +uniform mat3 projectionMatrix; +uniform mat3 otherMatrix; + +varying vec2 vMaskCoord; +varying vec2 vTextureCoord; + +void main(void) +{ + gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); + + vTextureCoord = aTextureCoord; + vMaskCoord = ( otherMatrix * vec3( aTextureCoord, 1.0) ).xy; +} diff --git a/packages/core/src/framebuffer/FrameBuffer.js b/packages/core/src/framebuffer/FrameBuffer.js new file mode 100644 index 0000000..84e9fcc --- /dev/null +++ b/packages/core/src/framebuffer/FrameBuffer.js @@ -0,0 +1,106 @@ +import Texture from '../textures/BaseTexture'; +import { FORMATS, TYPES } from '@pixi/constants'; + +/** + * Frame buffer + * @class + * @memberof PIXI + */ +export default class FrameBuffer +{ + constructor(width, height) + { + this.width = width || 100; + this.height = height || 100; + + this.stencil = false; + this.depth = false; + + this.dirtyId = 0; + this.dirtyFormat = 0; + this.dirtySize = 0; + + this.depthTexture = null; + this.colorTextures = []; + + this.glFrameBuffers = {}; + } + + get colorTexture() + { + return this.colorTextures[0]; + } + + addColorTexture(index, texture) + { + // TODO add some validation to the texture - same width / height etc? + this.colorTextures[index || 0] = texture || new Texture(null, { scaleMode: 0, + resolution: 1, + mipmap: false, + width: this.width, + height: this.height });// || new Texture(); + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + addDepthTexture(texture) + { + /* eslint-disable max-len */ + this.depthTexture = texture || new Texture(null, { scaleMode: 0, + resolution: 1, + width: this.width, + height: this.height, + mipmap: false, + format: FORMATS.DEPTH_COMPONENT, + type: TYPES.UNSIGNED_SHORT });// UNSIGNED_SHORT; + /* eslint-disable max-len */ + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + enableDepth() + { + this.depth = true; + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + enableStencil() + { + this.stencil = true; + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + resize(width, height) + { + if (width === this.width && height === this.height) return; + + this.width = width; + this.height = height; + + this.dirtyId++; + this.dirtySize++; + + for (let i = 0; i < this.colorTextures.length; i++) + { + this.colorTextures[i].setSize(width, height); + } + + if (this.depthTexture) + { + this.depthTexture.setSize(width, height); + } + } +} diff --git a/packages/core/src/framebuffer/FramebufferSystem.js b/packages/core/src/framebuffer/FramebufferSystem.js new file mode 100644 index 0000000..2cc2437 --- /dev/null +++ b/packages/core/src/framebuffer/FramebufferSystem.js @@ -0,0 +1,246 @@ +import System from '../System'; +import { Rectangle } from '@pixi/math'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class FramebufferSystem extends System +{ + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + contextChange() + { + this.gl = this.renderer.gl; + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + this.current = null; + this.viewport = new Rectangle(); + + this.drawBufferExtension = this.renderer.context.extensions.drawBuffers; + } + + bind(framebuffer, frame) + { + const gl = this.gl; + + this.current = framebuffer; + + if (framebuffer) + { + // TODO cacheing layer! + + const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID] || this.initFramebuffer(framebuffer); + + gl.bindFramebuffer(gl.FRAMEBUFFER, fbo.framebuffer); + // makesure all textures are unbound.. + + // now check for updates... + if (fbo.dirtyId !== framebuffer.dirtyId) + { + fbo.dirtyId = framebuffer.dirtyId; + + if (fbo.dirtyFormat !== framebuffer.dirtyFormat) + { + fbo.dirtyFormat = framebuffer.dirtyFormat; + this.updateFramebuffer(framebuffer); + } + else if (fbo.dirtySize !== framebuffer.dirtySize) + { + fbo.dirtySize = framebuffer.dirtySize; + this.resizeFramebuffer(framebuffer); + } + } + + for (let i = 0; i < framebuffer.colorTextures.length; i++) + { + if (framebuffer.colorTextures[i].texturePart) + { + this.renderer.texture.unbind(framebuffer.colorTextures[i].texture); + } + else + { + this.renderer.texture.unbind(framebuffer.colorTextures[i]); + } + } + + if (framebuffer.depthTexture) + { + this.renderer.texture.unbind(framebuffer.depthTexture); + } + + if (frame) + { + this.setViewport(frame.x, frame.y, frame.width, frame.height); + } + else + { + this.setViewport(0, 0, framebuffer.width, framebuffer.height); + } + } + else + { + gl.bindFramebuffer(gl.FRAMEBUFFER, null); + + if (frame) + { + this.setViewport(frame.x, frame.y, frame.width, frame.height); + } + else + { + this.setViewport(0, 0, this.renderer.width, this.renderer.height); + } + } + } + + setViewport(x, y, width, height) + { + const v = this.viewport; + + if (v.width !== width || v.height !== height || v.x !== x || v.y !== y) + { + v.x = x; + v.y = y; + v.width = width; + v.height = height; + + this.gl.viewport(x, y, width, height); + } + } + + get size() + { + if (this.current) + { + // TODO store temp + return { x: 0, y: 0, width: this.current.width, height: this.current.height }; + } + + return { x: 0, y: 0, width: this.renderer.width, height: this.renderer.height }; + } + + clear(r, g, b, a) + { + const gl = this.gl; + + // TODO clear color can be set only one right? + gl.clearColor(r, g, b, a); + gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); + } + + // private functions... + + initFramebuffer(framebuffer) + { + const gl = this.gl; + + // TODO - make this a class? + const fbo = { + framebuffer: gl.createFramebuffer(), + stencil: null, + dirtyId: 0, + dirtyFormat: 0, + dirtySize: 0, + }; + + framebuffer.glFrameBuffers[this.CONTEXT_UID] = fbo; + + return fbo; + } + + resizeFramebuffer(framebuffer) + { + const gl = this.gl; + + if (framebuffer.stencil || framebuffer.depth) + { + gl.bindRenderbuffer(gl.RENDERBUFFER, this.stencil); + gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); + } + } + + updateFramebuffer(framebuffer) + { + const gl = this.gl; + + const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID]; + + // bind the color texture + const colorTextures = framebuffer.colorTextures; + + let count = colorTextures.length; + + if (!this.drawBufferExtension) + { + count = Math.min(count, 1); + } + + const activeTextures = []; + + for (let i = 0; i < count; i++) + { + const texture = framebuffer.colorTextures[i]; + + if (texture.texturePart) + { + this.renderer.texture.bind(texture.texture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.COLOR_ATTACHMENT0 + i, + gl.TEXTURE_CUBE_MAP_NEGATIVE_X + texture.side, + texture.texture._glTextures[this.CONTEXT_UID].texture, + 0); + } + else + { + this.renderer.texture.bind(texture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.COLOR_ATTACHMENT0 + i, + gl.TEXTURE_2D, + texture._glTextures[this.CONTEXT_UID].texture, + 0); + } + + activeTextures.push(gl.COLOR_ATTACHMENT0 + i); + } + + if (this.drawBufferExtension && activeTextures.length > 1) + { + this.drawBufferExtension.drawBuffersWEBGL(activeTextures); + } + + if (framebuffer.depthTexture) + { + const depthTextureExt = this.renderer.context.extensions.depthTexture; + + if (depthTextureExt) + { + const depthTexture = framebuffer.depthTexture; + + this.renderer.texture.bind(depthTexture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.DEPTH_ATTACHMENT, + gl.TEXTURE_2D, + depthTexture._glTextures[this.CONTEXT_UID].texture, + 0); + } + } + + if (framebuffer.stencil || framebuffer.depth) + { + fbo.stencil = gl.createRenderbuffer(); + + gl.bindRenderbuffer(gl.RENDERBUFFER, fbo.stencil); + + // TODO.. this is depth AND stencil? + gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, fbo.stencil); + gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); + // fbo.enableStencil(); + } + } +} diff --git a/packages/core/src/geometry/GLBuffer.js b/packages/core/src/geometry/GLBuffer.js new file mode 100644 index 0000000..0477e8a --- /dev/null +++ b/packages/core/src/geometry/GLBuffer.js @@ -0,0 +1,9 @@ +export default class GLBuffer +{ + constructor(buffer) + { + this.buffer = buffer; + this.updateID = -1; + this.byteLength = -1; + } +} diff --git a/packages/core/src/geometry/GeometrySystem.js b/packages/core/src/geometry/GeometrySystem.js new file mode 100644 index 0000000..959f827 --- /dev/null +++ b/packages/core/src/geometry/GeometrySystem.js @@ -0,0 +1,388 @@ +import System from '../System'; +import GLBuffer from './GLBuffer'; + +const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class GeometrySystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this._activeGeometry = null; + this._activeVao = null; + + this.hasVao = true; + this.hasInstance = true; + } + + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + contextChange() + { + const gl = this.gl = this.renderer.gl; + + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + + // webgl2 + if (!gl.createVertexArray) + { + // webgl 1! + let nativeVaoExtension = this.renderer.context.extensions.vertexArrayObject; + + if (this.renderer.legacy) + { + nativeVaoExtension = null; + } + + if (nativeVaoExtension) + { + gl.createVertexArray = () => + nativeVaoExtension.createVertexArrayOES(); + + gl.bindVertexArray = (vao) => + nativeVaoExtension.bindVertexArrayOES(vao); + + gl.deleteVertexArray = (vao) => + nativeVaoExtension.deleteVertexArrayOES(vao); + } + else + { + this.hasVao = false; + gl.createVertexArray = () => + { + // empty + }; + + gl.bindVertexArray = () => + { + // empty + }; + + gl.deleteVertexArray = () => + { + // empty + }; + } + } + + if (!gl.vertexAttribDivisor) + { + const instanceExt = gl.getExtension('ANGLE_instanced_arrays'); + + if (instanceExt) + { + gl.vertexAttribDivisor = (a, b) => + instanceExt.vertexAttribDivisorANGLE(a, b); + + gl.drawElementsInstanced = (a, b, c, d, e) => + instanceExt.drawElementsInstancedANGLE(a, b, c, d, e); + + gl.drawArraysInstanced = (a, b, c, d) => + instanceExt.drawArraysInstancedANGLE(a, b, c, d); + } + else + { + this.hasInstance = false; + } + } + } + + /** + * Binds geometry so that is can be drawn. Creating a Vao if required + * @private + * @param {PIXI.Geometry} geometry instance of geometry to bind + */ + bind(geometry, shader) + { + shader = shader || this.renderer.shader.shader; + + const gl = this.gl; + + // not sure the best way to address this.. + // currently different shaders require different VAOs for the same geometry + // Still mulling over the best way to solve this one.. + // will likely need to modify the shader attribute locations at run time! + let vaos = geometry.glVertexArrayObjects[this.CONTEXT_UID]; + + if (!vaos) + { + geometry.glVertexArrayObjects[this.CONTEXT_UID] = vaos = {}; + } + + const vao = vaos[shader.program.id] || this.initGeometryVao(geometry, shader.program); + + this._activeGeometry = geometry; + + if (this._activeVao !== vao) + { + this._activeVao = vao; + + if (this.hasVao) + { + gl.bindVertexArray(vao); + } + else + { + this.activateVao(geometry, shader.program); + } + } + + // TODO - optimise later! + // don't need to loop through if nothing changed! + // maybe look to add an 'autoupdate' to geometry? + this.updateBuffers(); + } + + reset() + { + this.unbind(); + } + + updateBuffers() + { + const geometry = this._activeGeometry; + const gl = this.gl; + + for (let i = 0; i < geometry.buffers.length; i++) + { + const buffer = geometry.buffers[i]; + + const glBuffer = buffer._glBuffers[this.CONTEXT_UID]; + + if (buffer._updateID !== glBuffer.updateID) + { + glBuffer.updateID = buffer._updateID; + + // TODO can cache this on buffer! maybe added a getter / setter? + const type = buffer.index ? gl.ELEMENT_ARRAY_BUFFER : gl.ARRAY_BUFFER; + const drawType = buffer.static ? gl.STATIC_DRAW : gl.DYNAMIC_DRAW; + + gl.bindBuffer(type, glBuffer.buffer); + + if (glBuffer.byteLength >= buffer.data.byteLength) + { + // offset is always zero for now! + gl.bufferSubData(type, 0, buffer.data); + } + else + { + gl.bufferData(type, buffer.data, drawType); + } + } + } + } + + checkCompatability(geometry, program) + { + // geometry must have at least all the attributes that the shader requires. + const geometryAttributes = geometry.attributes; + const shaderAttributes = program.attributeData; + + for (const j in shaderAttributes) + { + if (!geometryAttributes[j]) + { + throw new Error(`shader and geometry incompatible, geometry missing the "${j}" attribute`); + } + } + } + + /** + * Creates a Vao with the same structure as the geometry and stores it on the geometry. + * @private + * @param {PIXI.Geometry} geometry instance of geometry to to generate Vao for + */ + initGeometryVao(geometry, program) + { + this.checkCompatability(geometry, program); + + const gl = this.gl; + const CONTEXT_UID = this.CONTEXT_UID; + const buffers = geometry.buffers; + const attributes = geometry.attributes; + + const tempStride = {}; + const tempStart = {}; + + for (const j in buffers) + { + tempStride[j] = 0; + tempStart[j] = 0; + } + + for (const j in attributes) + { + if (!attributes[j].size && program.attributeData[j]) + { + attributes[j].size = program.attributeData[j].size; + } + + tempStride[attributes[j].buffer] += attributes[j].size * byteSizeMap[attributes[j].type]; + } + + for (const j in attributes) + { + const attribute = attributes[j]; + const attribSize = attribute.size; + + if (attribute.stride === undefined) + { + if (tempStride[attribute.buffer] === attribSize * byteSizeMap[attribute.type]) + { + attribute.stride = 0; + } + else + { + attribute.stride = tempStride[attribute.buffer]; + } + } + + if (attribute.start === undefined) + { + attribute.start = tempStart[attribute.buffer]; + + tempStart[attribute.buffer] += attribSize * byteSizeMap[attribute.type]; + } + } + + // first update - and create the buffers! + // only create a gl buffer if it actually gets + for (let i = 0; i < buffers.length; i++) + { + const buffer = buffers[i]; + + if (!buffer._glBuffers[CONTEXT_UID]) + { + buffer._glBuffers[CONTEXT_UID] = new GLBuffer(gl.createBuffer()); + } + } + + // TODO - maybe make this a data object? + // lets wait to see if we need to first! + const vao = gl.createVertexArray(); + + gl.bindVertexArray(vao); + + this.activateVao(geometry, program); + + gl.bindVertexArray(null); + + // add it to the cache! + geometry.glVertexArrayObjects[this.CONTEXT_UID][program.id] = vao; + + return vao; + } + + activateVao(geometry, program) + { + const gl = this.gl; + const CONTEXT_UID = this.CONTEXT_UID; + const buffers = geometry.buffers; + const attributes = geometry.attributes; + + if (geometry.indexBuffer) + { + // first update the index buffer if we have one.. + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, geometry.indexBuffer._glBuffers[CONTEXT_UID].buffer); + } + + let lastBuffer = null; + + // add a new one! + for (const j in attributes) + { + const attribute = attributes[j]; + const buffer = buffers[attribute.buffer]; + const glBuffer = buffer._glBuffers[CONTEXT_UID]; + + if (program.attributeData[j]) + { + if (lastBuffer !== glBuffer) + { + gl.bindBuffer(gl.ARRAY_BUFFER, glBuffer.buffer); + + lastBuffer = glBuffer; + } + + const location = program.attributeData[j].location; + + // TODO introduce state again + // we can optimise this for older devices that have no VAOs + gl.enableVertexAttribArray(location); + + gl.vertexAttribPointer(location, + attribute.size, + attribute.type || gl.FLOAT, + attribute.normalized, + attribute.stride, + attribute.start); + + if (attribute.instance) + { + // TODO calculate instance count based of this... + if (this.hasInstance) + { + gl.vertexAttribDivisor(location, 1); + } + else + { + throw new Error('geometry error, GPU Instancing is not supported on this device'); + } + } + } + } + } + + draw(type, size, start, instanceCount) + { + const gl = this.gl; + const geometry = this._activeGeometry; + + // TODO.. this should not change so maybe cache the function? + + if (geometry.indexBuffer) + { + if (geometry.instanced) + { + /* eslint-disable max-len */ + gl.drawElementsInstanced(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2, instanceCount || 1); + /* eslint-enable max-len */ + } + else + { + gl.drawElements(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2); + } + } + else + if (geometry.instanced) + { + // TODO need a better way to calculate size.. + gl.drawArraysInstanced(type, start, size || geometry.getSize(), instanceCount || 1); + } + else + { + gl.drawArrays(type, start, size || geometry.getSize()); + } + + return this; + } + + unbind() + { + this.gl.bindVertexArray(null); + this._activeVao = null; + this._activeGeometry = null; + } +} diff --git a/packages/core/src/geometry/utils/setVertexAttribArrays.js b/packages/core/src/geometry/utils/setVertexAttribArrays.js new file mode 100644 index 0000000..23b6959 --- /dev/null +++ b/packages/core/src/geometry/utils/setVertexAttribArrays.js @@ -0,0 +1,54 @@ +// var GL_MAP = {}; + +/** + * @param gl {WebGLRenderingContext} The current WebGL context + * @param attribs {*} + * @param state {*} + */ +export default function setVertexAttribArrays(gl, attribs, state) +{ + let i; + + if (state) + { + const tempAttribState = state.tempAttribState; + const attribState = state.attribState; + + for (i = 0; i < tempAttribState.length; i++) + { + tempAttribState[i] = false; + } + + // set the new attribs + for (i = 0; i < attribs.length; i++) + { + tempAttribState[attribs[i].attribute.location] = true; + } + + for (i = 0; i < attribState.length; i++) + { + if (attribState[i] !== tempAttribState[i]) + { + attribState[i] = tempAttribState[i]; + + if (state.attribState[i]) + { + gl.enableVertexAttribArray(i); + } + else + { + gl.disableVertexAttribArray(i); + } + } + } + } + else + { + for (i = 0; i < attribs.length; i++) + { + const attrib = attribs[i]; + + gl.enableVertexAttribArray(attrib.attribute.location); + } + } +} diff --git a/packages/core/src/index.js b/packages/core/src/index.js index 58c456a..fd1f547 100644 --- a/packages/core/src/index.js +++ b/packages/core/src/index.js @@ -1,30 +1,30 @@ import * as resources from './textures/resources'; -import * as systems from './renderers/systems'; +import * as systems from './systems'; -export { resources }; export { systems }; +export { resources }; -export { default as Renderer } from './renderers/Renderer'; -export { default as AbstractRenderer } from './renderers/AbstractRenderer'; -export { default as FrameBuffer } from './textures/FrameBuffer'; +export { default as System } from './System'; +export { default as Renderer } from './Renderer'; +export { default as AbstractRenderer } from './AbstractRenderer'; +export { default as FrameBuffer } from './framebuffer/FrameBuffer'; export { default as CubeTexture } from './textures/CubeTexture'; export { default as BaseTexture } from './textures/BaseTexture'; export { default as Texture } from './textures/Texture'; export { default as TextureMatrix } from './textures/TextureMatrix'; -export { default as RenderTexture } from './textures/RenderTexture'; -export { default as BaseRenderTexture } from './textures/BaseRenderTexture'; +export { default as RenderTexture } from './renderTexture/RenderTexture'; +export { default as BaseRenderTexture } from './renderTexture/BaseRenderTexture'; export { default as TextureUvs } from './textures/TextureUvs'; -export { default as State } from './renderers/State'; -export { default as ObjectRenderer } from './renderers/utils/ObjectRenderer'; -export { default as Quad } from './renderers/utils/Quad'; -export { default as QuadUv } from './renderers/utils/QuadUv'; -export { default as checkMaxIfStatmentsInShader } from './renderers/utils/checkMaxIfStatmentsInShader'; +export { default as State } from './state/State'; +export { default as ObjectRenderer } from './batch/ObjectRenderer'; +export { default as Quad } from './utils/Quad'; +export { default as QuadUv } from './utils/QuadUv'; +export { default as checkMaxIfStatmentsInShader } from './shader/utils/checkMaxIfStatmentsInShader'; export { default as Shader } from './shader/Shader'; export { default as Program } from './shader/Program'; export { default as UniformGroup } from './shader/UniformGroup'; -export { default as SpriteMaskFilter } from './renderers/filters/spriteMask/SpriteMaskFilter'; -export { default as Filter } from './renderers/filters/Filter'; +export { default as SpriteMaskFilter } from './filters/spriteMask/SpriteMaskFilter'; +export { default as Filter } from './filters/Filter'; export { default as Attribute } from './geometry/Attribute'; export { default as Buffer } from './geometry/Buffer'; export { default as Geometry } from './geometry/Geometry'; - diff --git a/packages/core/src/mask/MaskSystem.js b/packages/core/src/mask/MaskSystem.js new file mode 100644 index 0000000..99174c2 --- /dev/null +++ b/packages/core/src/mask/MaskSystem.js @@ -0,0 +1,199 @@ +import System from '../System'; +import AlphaMaskFilter from '../filters/spriteMask/SpriteMaskFilter'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class MaskSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + // TODO - we don't need both! + this.scissor = false; + this.scissorData = null; + this.scissorRenderTarget = null; + + this.enableScissor = false; + + this.alphaMaskPool = []; + this.alphaMaskIndex = 0; + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.DisplayObject} target - Display Object to push the mask to + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + push(target, maskData) + { + // TODO the root check means scissor rect will not + // be used on render textures more info here: + // https://github.com/pixijs/pixi.js/pull/3545 + + if (maskData.texture) + { + this.pushSpriteMask(target, maskData); + } + else if (this.enableScissor + && !this.scissor + && this.renderer._activeRenderTarget.root + && !this.renderer.stencil.stencilMaskStack.length + && maskData.isFastRect()) + { + const matrix = maskData.worldTransform; + + let rot = Math.atan2(matrix.b, matrix.a); + + // use the nearest degree! + rot = Math.round(rot * (180 / Math.PI)); + + if (rot % 90) + { + this.pushStencilMask(maskData); + } + else + { + this.pushScissorMask(target, maskData); + } + } + else + { + this.pushStencilMask(maskData); + } + } + + /** + * Removes the last mask from the mask stack and doesn't return it. + * + * @param {PIXI.DisplayObject} target - Display Object to pop the mask from + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + pop(target, maskData) + { + if (maskData.texture) + { + this.popSpriteMask(target, maskData); + } + else if (this.enableScissor && !this.renderer.stencil.stencilMaskStack.length) + { + this.popScissorMask(target, maskData); + } + else + { + this.popStencilMask(target, maskData); + } + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.RenderTarget} target - Display Object to push the sprite mask to + * @param {PIXI.Sprite} maskData - Sprite to be used as the mask + */ + pushSpriteMask(target, maskData) + { + let alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; + + if (!alphaMaskFilter) + { + alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; + } + + alphaMaskFilter[0].resolution = this.renderer.resolution; + alphaMaskFilter[0].maskSprite = maskData; + + // TODO - may cause issues! + target.filterArea = maskData.getBounds(true); + + this.renderer.filterSystem.pushFilter(target, alphaMaskFilter); + + this.alphaMaskIndex++; + } + + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popSpriteMask() + { + this.renderer.filterSystem.popFilter(); + this.alphaMaskIndex--; + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + pushStencilMask(maskData) + { + this.renderer.batch.flush(); + this.renderer.stencil.pushStencil(maskData); + } + + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popStencilMask() + { + // this.renderer.currentRenderer.stop(); + this.renderer.stencil.popStencil(); + } + + /** + * + * @param {PIXI.DisplayObject} target - Display Object to push the mask to + * @param {PIXI.Graphics} maskData - The masking data. + */ + pushScissorMask(target, maskData) + { + maskData.renderable = true; + + const renderTarget = this.renderer._activeRenderTarget; + + const bounds = maskData.getBounds(); + + bounds.fit(renderTarget.size); + maskData.renderable = false; + + this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); + + const resolution = this.renderer.resolution; + + this.renderer.gl.scissor( + bounds.x * resolution, + (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, + bounds.width * resolution, + bounds.height * resolution + ); + + this.scissorRenderTarget = renderTarget; + this.scissorData = maskData; + this.scissor = true; + } + + /** + * + * + */ + popScissorMask() + { + this.scissorRenderTarget = null; + this.scissorData = null; + this.scissor = false; + + // must be scissor! + const gl = this.renderer.gl; + + gl.disable(gl.SCISSOR_TEST); + } +} diff --git a/packages/core/src/mask/StencilSystem.js b/packages/core/src/mask/StencilSystem.js new file mode 100644 index 0000000..62796a5 --- /dev/null +++ b/packages/core/src/mask/StencilSystem.js @@ -0,0 +1,133 @@ +import System from '../System'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class StencilSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + this.stencilMaskStack = []; + } + + /** + * Changes the mask stack that is used by this System. + * + * @param {PIXI.Graphics[]} stencilMaskStack - The mask stack + */ + setMaskStack(stencilMaskStack) + { + const gl = this.renderer.gl; + + if (stencilMaskStack.length !== this.stencilMaskStack.length) + { + if (stencilMaskStack.length === 0) + { + gl.disable(gl.STENCIL_TEST); + } + else + { + gl.enable(gl.STENCIL_TEST); + } + } + + this.stencilMaskStack = stencilMaskStack; + } + + /** + * Applies the Mask and adds it to the current stencil stack. @alvin + * + * @param {PIXI.Graphics} graphics - The mask + */ + pushStencil(graphics) + { + this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); + + // this.renderer._activeRenderTarget.attachStencilBuffer(); + + const gl = this.renderer.gl; + const prevMaskCount = this.stencilMaskStack.length; + + if (prevMaskCount === 0) + { + gl.enable(gl.STENCIL_TEST); + } + + this.stencilMaskStack.push(graphics); + + // Increment the refference stencil value where the new mask overlaps with the old ones. + gl.colorMask(false, false, false, false); + gl.stencilFunc(gl.EQUAL, prevMaskCount, this._getBitwiseMask()); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.INCR); + this.renderer.plugins.graphics.render(graphics); + + this._useCurrent(); + } + + /** + * Removes the last mask from the stencil stack. @alvin + */ + popStencil() + { + this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); + + const gl = this.renderer.gl; + const graphics = this.stencilMaskStack.pop(); + + if (this.stencilMaskStack.length === 0) + { + // the stack is empty! + gl.disable(gl.STENCIL_TEST); + gl.clear(gl.STENCIL_BUFFER_BIT); + gl.clearStencil(0); + } + else + { + // Decrement the refference stencil value where the popped mask overlaps with the other ones + gl.colorMask(false, false, false, false); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.DECR); + this.renderer.plugins.graphics.render(graphics); + + this._useCurrent(); + } + } + + /** + * Setup renderer to use the current stencil data. + */ + _useCurrent() + { + const gl = this.renderer.gl; + + gl.colorMask(true, true, true, true); + gl.stencilFunc(gl.EQUAL, this.stencilMaskStack.length, this._getBitwiseMask()); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP); + } + + /** + * Fill 1s equal to the number of acitve stencil masks. + * + * @return {number} The bitwise mask. + */ + _getBitwiseMask() + { + return (1 << this.stencilMaskStack.length) - 1; + } + + /** + * Destroys the mask stack. + * + */ + destroy() + { + super.destroy(this); + + this.stencilMaskStack = null; + } +} diff --git a/packages/core/src/projection/ProjectionSystem.js b/packages/core/src/projection/ProjectionSystem.js new file mode 100644 index 0000000..6e63b73 --- /dev/null +++ b/packages/core/src/projection/ProjectionSystem.js @@ -0,0 +1,73 @@ +import System from '../System'; +import { Matrix } from '@pixi/math'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class ProjectionSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.projectionMatrix = new Matrix(); + } + + update(destinationFrame, sourceFrame, resolution, root) + { + this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; + this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; + + this.calculateProjection(this.destinationFrame, this.sourceFrame, resolution, root); + + this.renderer.globalUniforms.uniforms.projectionMatrix = this.projectionMatrix; + this.renderer.globalUniforms.update(); + } + + /** + * Updates the projection matrix based on a projection frame (which is a rectangle) + * + * @param {Rectangle} destinationFrame - The destination frame. + * @param {Rectangle} sourceFrame - The source frame. + */ + calculateProjection(destinationFrame, sourceFrame, resolution, root) + { + const pm = this.projectionMatrix; + + // I don't think we will need this line.. + // pm.identity(); + + if (!root) + { + pm.a = (1 / destinationFrame.width * 2) * resolution; + pm.d = (1 / destinationFrame.height * 2) * resolution; + + pm.tx = -1 - (sourceFrame.x * pm.a); + pm.ty = -1 - (sourceFrame.y * pm.d); + } + else + { + pm.a = (1 / destinationFrame.width * 2) * resolution; + pm.d = (-1 / destinationFrame.height * 2) * resolution; + + pm.tx = -1 - (sourceFrame.x * pm.a); + pm.ty = 1 - (sourceFrame.y * pm.d); + } + } + + /** + * Sets the transform of the active render target to the given matrix + * + * @param {PIXI.Matrix} matrix - The transformation matrix + */ + setTransform()// matrix) + { + // this._activeRenderTarget.transform = matrix; + } +} diff --git a/packages/core/src/renderTexture/BaseRenderTexture.js b/packages/core/src/renderTexture/BaseRenderTexture.js new file mode 100644 index 0000000..88eb83b --- /dev/null +++ b/packages/core/src/renderTexture/BaseRenderTexture.js @@ -0,0 +1,137 @@ +import BaseTexture from '../textures/BaseTexture'; +import FrameBuffer from '../framebuffer/FrameBuffer'; + +/** + * A BaseRenderTexture is a special texture that allows any PixiJS display object to be rendered to it. + * + * __Hint__: All DisplayObjects (i.e. Sprites) that render to a BaseRenderTexture should be preloaded + * otherwise black rectangles will be drawn instead. + * + * A BaseRenderTexture takes a snapshot of any Display Object given to its render method. The position + * and rotation of the given Display Objects is ignored. For example: + * + * ```js + * let renderer = PIXI.autoDetectRenderer(1024, 1024, { view: canvas, ratio: 1 }); + * let baseRenderTexture = new PIXI.BaseRenderTexture(renderer, 800, 600); + * let sprite = PIXI.Sprite.fromImage("spinObj_01.png"); + * + * sprite.position.x = 800/2; + * sprite.position.y = 600/2; + * sprite.anchor.x = 0.5; + * sprite.anchor.y = 0.5; + * + * baseRenderTexture.render(sprite); + * ``` + * + * The Sprite in this case will be rendered using its local transform. To render this sprite at 0,0 + * you can clear the transform + * + * ```js + * + * sprite.setTransform() + * + * let baseRenderTexture = new PIXI.BaseRenderTexture(100, 100); + * let renderTexture = new PIXI.RenderTexture(baseRenderTexture); + * + * renderer.render(sprite, renderTexture); // Renders to center of RenderTexture + * ``` + * + * @class + * @extends PIXI.BaseTexture + * @memberof PIXI + */ +export default class BaseRenderTexture extends BaseTexture +{ + /** + * @param {object} [options] + * @param {number} [options.width=100] - The width of the base render texture + * @param {number} [options.height=100] - The height of the base render texture + * @param {PIXI.SCALE_MODES} [options.scaleMode] - See {@link PIXI.SCALE_MODES} for possible values + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the texture being generated + */ + constructor(options) + { + if (typeof options === 'number') + { + /* eslint-disable prefer-rest-params */ + // Backward compatibility of signature + const width = arguments[0]; + const height = arguments[1]; + const scaleMode = arguments[2]; + const resolution = arguments[3]; + + options = { width, height, scaleMode, resolution }; + /* eslint-enable prefer-rest-params */ + } + + super(null, options); + + const { width, height } = options || {}; + + // Set defaults + this.mipmap = false; + this.width = Math.ceil(width) || 100; + this.height = Math.ceil(height) || 100; + this.valid = true; + + /** + * A map of renderer IDs to webgl renderTargets + * + * @private + * @member {object} + */ + // this._glRenderTargets = {}; + + /** + * A reference to the canvas render target (we only need one as this can be shared across renderers) + * + * @private + * @member {object} + */ + this._canvasRenderTarget = null; + + this.clearColor = [0, 0, 0, 0]; + + this.frameBuffer = new FrameBuffer(width * this.resolution, height * this.resolution) + .addColorTexture(0, this); + + // TODO - could this be added the systems? + + /** + * The data structure for the stencil masks + * + * @member {PIXI.Graphics[]} + */ + this.stencilMaskStack = []; + + /** + * The data structure for the filters + * + * @member {PIXI.Graphics[]} + */ + this.filterStack = [{}]; + } + + /** + * Resizes the BaseRenderTexture. + * + * @param {number} width - The width to resize to. + * @param {number} height - The height to resize to. + */ + resize(width, height) + { + width = Math.ceil(width); + height = Math.ceil(height); + this.frameBuffer.resize(width * this.resolution, height * this.resolution); + } + + /** + * Destroys this texture + * + */ + destroy() + { + super.destroy(true); + this.renderer = null; + } +} diff --git a/packages/core/src/renderTexture/RenderTexture.js b/packages/core/src/renderTexture/RenderTexture.js new file mode 100644 index 0000000..f1ecf1f --- /dev/null +++ b/packages/core/src/renderTexture/RenderTexture.js @@ -0,0 +1,153 @@ +import BaseRenderTexture from './BaseRenderTexture'; +import Texture from '../textures/Texture'; + +/** + * A RenderTexture is a special texture that allows any PixiJS display object to be rendered to it. + * + * __Hint__: All DisplayObjects (i.e. Sprites) that render to a RenderTexture should be preloaded + * otherwise black rectangles will be drawn instead. + * + * A RenderTexture takes a snapshot of any Display Object given to its render method. For example: + * + * ```js + * let renderer = PIXI.autoDetectRenderer(1024, 1024, { view: canvas, ratio: 1 }); + * let renderTexture = PIXI.RenderTexture.create(800, 600); + * let sprite = PIXI.Sprite.fromImage("spinObj_01.png"); + * + * sprite.position.x = 800/2; + * sprite.position.y = 600/2; + * sprite.anchor.x = 0.5; + * sprite.anchor.y = 0.5; + * + * renderer.render(sprite, renderTexture); + * ``` + * + * The Sprite in this case will be rendered using its local transform. To render this sprite at 0,0 + * you can clear the transform + * + * ```js + * + * sprite.setTransform() + * + * let renderTexture = new PIXI.RenderTexture.create(100, 100); + * + * renderer.render(sprite, renderTexture); // Renders to center of RenderTexture + * ``` + * + * @class + * @extends PIXI.Texture + * @memberof PIXI + */ +export default class RenderTexture extends Texture +{ + /** + * @param {PIXI.BaseRenderTexture} baseRenderTexture - The renderer used for this RenderTexture + * @param {PIXI.Rectangle} [frame] - The rectangle frame of the texture to show + */ + constructor(baseRenderTexture, frame) + { + // support for legacy.. + let _legacyRenderer = null; + + if (!(baseRenderTexture instanceof BaseRenderTexture)) + { + /* eslint-disable prefer-rest-params, no-console */ + const width = arguments[1]; + const height = arguments[2]; + const scaleMode = arguments[3]; + const resolution = arguments[4]; + + // we have an old render texture.. + console.warn(`Please use RenderTexture.create(${width}, ${height}) instead of the ctor directly.`); + _legacyRenderer = arguments[0]; + /* eslint-enable prefer-rest-params, no-console */ + + frame = null; + baseRenderTexture = new BaseRenderTexture({ + width, + height, + scaleMode, + resolution, + }); + } + + /** + * The base texture object that this texture uses + * + * @member {BaseTexture} + */ + super(baseRenderTexture, frame); + + this.legacyRenderer = _legacyRenderer; + + /** + * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. + * + * @member {boolean} + */ + this.valid = true; + + /** + * FilterSystem temporary storage + * @private + * @member {PIXI.Rectangle} + */ + this.filterFrame = null; + + this._updateUvs(); + } + + /** + * Resizes the RenderTexture. + * + * @param {number} width - The width to resize to. + * @param {number} height - The height to resize to. + * @param {boolean} [resizeBaseTexture=true] - Should the baseTexture.width and height values be resized as well? + */ + resize(width, height, resizeBaseTexture = true) + { + width = Math.ceil(width); + height = Math.ceil(height); + + // TODO - could be not required.. + this.valid = (width > 0 && height > 0); + + this._frame.width = this.orig.width = width; + this._frame.height = this.orig.height = height; + + if (resizeBaseTexture) + { + this.baseTexture.resize(width, height); + } + + this._updateUvs(); + } + + /** + * A short hand way of creating a render texture. + * + * @param {object} [options] - Options + * @param {number} [options.width=100] - The width of the render texture + * @param {number} [options.height=100] - The height of the render texture + * @param {number} [options.scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the texture being generated + * @return {PIXI.RenderTexture} The new render texture + */ + static create(options) + { + // fallback, old-style: create(width, height, scaleMode, resolution) + if (typeof options === 'number') + { + /* eslint-disable prefer-rest-params */ + options = { + width: options, + height: arguments[1], + scaleMode: arguments[2], + resolution: arguments[3], + }; + /* eslint-enable prefer-rest-params */ + } + + return new RenderTexture(new BaseRenderTexture(options)); + } +} diff --git a/packages/core/src/renderTexture/RenderTextureSystem.js b/packages/core/src/renderTexture/RenderTextureSystem.js new file mode 100644 index 0000000..8d0ca61 --- /dev/null +++ b/packages/core/src/renderTexture/RenderTextureSystem.js @@ -0,0 +1,115 @@ +import System from '../System'; +import { Rectangle } from '@pixi/math'; + +const tempRect = new Rectangle(); + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class RenderTextureSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.clearColor = renderer._backgroundColorRgba; + + // TODO moe this property somewhere else! + this.defaultMaskStack = []; + this.defaultFilterStack = [{}]; + + // empty render texture? + this.renderTexture = null; + + this.destinationFrame = new Rectangle(); + } + + bind(renderTexture, sourceFrame, destinationFrame) + { + // TODO - do we want this?? + if (this.renderTexture === renderTexture) return; + this.renderTexture = renderTexture; + + const renderer = this.renderer; + + if (renderTexture) + { + const baseTexture = renderTexture.baseTexture; + + if (!destinationFrame) + { + tempRect.width = baseTexture.realWidth; + tempRect.height = baseTexture.realHeight; + + destinationFrame = tempRect; + } + + if (!sourceFrame) + { + sourceFrame = destinationFrame; + } + + this.renderer.framebuffer.bind(baseTexture.frameBuffer, destinationFrame); + + this.renderer.projection.update(destinationFrame, sourceFrame, baseTexture.resolution, false); + this.renderer.stencil.setMaskStack(baseTexture.stencilMaskStack); + } + else + { + // TODO these validation checks happen deeper down.. + // thing they can be avoided.. + if (!destinationFrame) + { + tempRect.width = renderer.width; + tempRect.height = renderer.height; + + destinationFrame = tempRect; + } + + if (!sourceFrame) + { + sourceFrame = destinationFrame; + } + + renderer.framebuffer.bind(null, destinationFrame); + + // TODO store this.. + this.renderer.projection.update(destinationFrame, sourceFrame, this.renderer.resolution, true); + this.renderer.stencil.setMaskStack(this.defaultMaskStack); + } + + this.destinationFrame.copyFrom(destinationFrame); + } + + /** + * Erases the render texture and fills the drawing area with a colour + * + * @param {number} [clearColor] - The colour + * @return {PIXI.Renderer} Returns itself. + */ + clear(clearColor) + { + if (this.renderTexture) + { + clearColor = clearColor || this.renderTexture.baseTexture.clearColor; + } + else + { + clearColor = clearColor || this.clearColor; + } + + this.renderer.framebuffer.clear(clearColor[0], clearColor[1], clearColor[2], clearColor[3]); + } + + resize()// screenWidth, screenHeight) + { + // resize the root only! + this.bind(null); + } +} diff --git a/packages/core/src/renderers/AbstractRenderer.js b/packages/core/src/renderers/AbstractRenderer.js deleted file mode 100644 index 928670f..0000000 --- a/packages/core/src/renderers/AbstractRenderer.js +++ /dev/null @@ -1,353 +0,0 @@ -import { hex2string, hex2rgb } from '@pixi/utils'; -import { Matrix, Rectangle } from '@pixi/math'; -import { RENDERER_TYPE } from '@pixi/constants'; -import { settings } from '@pixi/settings'; -import { Container } from '@pixi/display'; -import RenderTexture from '../textures/RenderTexture'; -import EventEmitter from 'eventemitter3'; - -const tempMatrix = new Matrix(); - -/** - * 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 EventEmitter - * @memberof PIXI - */ -export default class AbstractRenderer extends EventEmitter -{ - // eslint-disable-next-line valid-jsdoc - /** - * @param {string} system - The name of the system this renderer is for. - * @param {object} [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.transparent=false] - If the render view is transparent, default false - * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false - * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) - * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. The - * resolution of the renderer retina would be 2. - * @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 {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when rendering, - * stopping pixel interpolation. - */ - constructor(system, options, arg2, arg3) - { - super(); - - // Support for constructor(system, screenWidth, screenHeight, options) - if (typeof options === 'number') - { - options = Object.assign({ - width: options, - height: arg2 || settings.RENDER_OPTIONS.height, - }, arg3); - } - - // 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 = RENDERER_TYPE.UNKNOWN; - - /** - * Measurements of the screen. (0, 0, screenWidth, screenHeight) - * - * Its safe to use as filterArea or hitArea for 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 1 - */ - this.resolution = options.resolution || settings.RESOLUTION; - - /** - * Whether the render view is transparent - * - * @member {boolean} - */ - this.transparent = options.transparent; - - /** - * Whether css dimensions of canvas view should be resized to screen dimensions automatically - * - * @member {boolean} - */ - this.autoResize = options.autoResize || false; - - /** - * Tracks the blend modes useful for this renderer. - * - * @member {object} - */ - this.blendModes = null; - - /** - * 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; - - /** - * If true PixiJS will Math.floor() x/y values when rendering, stopping pixel interpolation. - * Handy for crisp pixel art and speed on legacy devices. - * - * @member {boolean} - */ - this.roundPixels = options.roundPixels; - - /** - * The background color as a number. - * - * @member {number} - * @private - */ - this._backgroundColor = 0x000000; - - /** - * The background color as an [R, G, B] array. - * - * @member {number[]} - * @private - */ - this._backgroundColorRgba = [0, 0, 0, 0]; - - /** - * The background color as a string. - * - * @member {string} - * @private - */ - this._backgroundColorString = '#000000'; - - this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter - - /** - * This temporary display object used as the parent of the currently being rendered item - * - * @member {PIXI.DisplayObject} - * @private - */ - this._tempDisplayObjectParent = new Container(); - - /** - * The last root object that the renderer tried to render. - * - * @member {PIXI.DisplayObject} - * @private - */ - this._lastObjectRendered = this._tempDisplayObjectParent; - - /** - * Collection of plugins. - * @readonly - * @member {object} - */ - this.plugins = {}; - } - - /** - * Initialize the plugins. - * - * @protected - * @param {object} staticMap - The dictionary of staticly saved plugins. - */ - initPlugins(staticMap) - { - 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() - { - return this.view.width; - } - - /** - * Same as view.height, actual number of pixels in the canvas by vertical - * - * @member {number} - * @readonly - * @default 600 - */ - get height() - { - return this.view.height; - } - - /** - * Resizes the screen and canvas to the specified width and height - * Canvas dimensions are multiplied by resolution - * - * @param {number} screenWidth - the new width of the screen - * @param {number} screenHeight - the new height of the screen - */ - resize(screenWidth, screenHeight) - { - this.screen.width = screenWidth; - this.screen.height = screenHeight; - - this.view.width = screenWidth * this.resolution; - this.view.height = screenHeight * this.resolution; - - if (this.autoResize) - { - this.view.style.width = `${screenWidth}px`; - this.view.style.height = `${screenHeight}px`; - } - } - - /** - * 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 {PIXI.DisplayObject} displayObject - The displayObject the object will be generated from - * @param {number} scaleMode - Should be one of the scaleMode consts - * @param {number} resolution - The resolution / device pixel ratio of the texture being generated - * @param {PIXI.Rectangle} [region] - The region of the displayObject, that shall be rendered, - * if no region is specified, defaults to the local bounds of the displayObject. - * @return {PIXI.Texture} a texture of the graphics object - */ - generateTexture(displayObject, scaleMode, resolution, region) - { - region = region || displayObject.getLocalBounds(); - - // 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(region.width | 0, region.height | 0, scaleMode, resolution); - - tempMatrix.tx = -region.x; - tempMatrix.ty = -region.y; - - this.render(displayObject, renderTexture, false, tempMatrix, true); - - return renderTexture; - } - - /** - * Removes everything from the renderer and optionally removes the Canvas DOM element. - * - * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. - */ - destroy(removeView) - { - 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); - } - - this.plugins = null; - - this.type = RENDERER_TYPE.UNKNOWN; - - this.view = null; - - this.screen = null; - - this.resolution = 0; - - this.transparent = false; - - this.autoResize = false; - - this.blendModes = null; - - this.options = null; - - this.preserveDrawingBuffer = false; - this.clearBeforeRender = false; - - this.roundPixels = false; - - this._backgroundColor = 0; - this._backgroundColorRgba = null; - this._backgroundColorString = null; - - this._tempDisplayObjectParent = null; - this._lastObjectRendered = null; - } - - /** - * The background color to fill if not transparent - * - * @member {number} - */ - get backgroundColor() - { - return this._backgroundColor; - } - - set backgroundColor(value) // eslint-disable-line require-jsdoc - { - this._backgroundColor = value; - this._backgroundColorString = hex2string(value); - hex2rgb(value, this._backgroundColorRgba); - } -} diff --git a/packages/core/src/renderers/Renderer.js b/packages/core/src/renderers/Renderer.js deleted file mode 100644 index a2020b1..0000000 --- a/packages/core/src/renderers/Renderer.js +++ /dev/null @@ -1,329 +0,0 @@ -import AbstractRenderer from './AbstractRenderer'; -import { sayHello } from '@pixi/utils'; -import MaskSystem from './systems/MaskSystem'; -import StencilSystem from './systems/StencilSystem'; -import FilterSystem from './systems/filter/FilterSystem'; -import FramebufferSystem from './systems/FramebufferSystem'; -import RenderTextureSystem from './systems/RenderTextureSystem'; -import TextureSystem from './systems/textures/TextureSystem'; -import ProjectionSystem from './systems/ProjectionSystem'; -import StateSystem from './systems/StateSystem'; -import GeometrySystem from './systems/geometry/GeometrySystem'; -import ShaderSystem from './systems/shader/ShaderSystem'; -import ContextSystem from './systems/ContextSystem'; -import BatchSystem from './systems/BatchSystem'; -import TextureGCSystem from './systems/textures/TextureGCSystem'; -import { RENDERER_TYPE } from '@pixi/constants'; -import UniformGroup from '../shader/UniformGroup'; -import { Matrix } from '@pixi/math'; -import Runner from 'mini-runner'; - -/** - * 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 Render works by automatically managing webGLBatchs. - * 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 :) - * - * @class - * @memberof PIXI - * @extends PIXI.AbstractRenderer - */ -export default class Renderer extends AbstractRenderer -{ - // eslint-disable-next-line valid-jsdoc - /** - * - * @param {object} [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.transparent=false] - If the render view is transparent, default false - * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false - * @param {boolean} [options.antialias=false] - sets antialias. If not available natively then FXAA - * antialiasing is used - * @param {boolean} [options.forceFXAA=false] - forces FXAA antialiasing to be used over native. - * FXAA is faster, but may not always look as great - * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. - * The resolution of the renderer retina would be 2. - * @param {boolean} [options.clearBeforeRender=true] - This sets if the renderer will clear - * the canvas or not before the new render pass. If you wish to set this to false, you *must* set - * preserveDrawingBuffer to `true`. - * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, - * enable this if you need to call toDataUrl on the webgl context. - * @param {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when - * rendering, stopping pixel interpolation. - * @param {number} [options.backgroundColor=0x000000] - The background color of the rendered area - * (shown if not transparent). - * @param {boolean} [options.legacy=false] - If true PixiJS will aim to ensure compatibility - * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. - * @param {string} [options.powerPreference] - Parameter passed to webgl context, set to "high-performance" - * for devices with dual graphics card - */ - constructor(options, arg2, arg3) - { - super('WebGL', options, arg2, arg3); - - /** - * The type of this renderer as a standardised const - * - * @member {number} - * @see PIXI.RENDERER_TYPE - */ - this.type = RENDERER_TYPE.WEBGL; - - // this will be set by the contextSystem (this.context) - this.gl = null; - this.CONTEXT_UID = 0; - this.legacy = !!options.legacy; - - // TODO legacy! - - // runners! - this.runners = { - destroy: new Runner('destroy'), - contextChange: new Runner('contextChange', 1), - reset: new Runner('reset'), - update: new Runner('update'), - postrender: new Runner('postrender'), - prerender: new Runner('prerender'), - resize: new Runner('resize', 2), - }; - - 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(GeometrySystem, 'geometry') - .addSystem(FramebufferSystem, 'framebuffer') - .addSystem(StencilSystem, 'stencil') - .addSystem(ProjectionSystem, 'projection') - .addSystem(TextureGCSystem, 'textureGC') - .addSystem(FilterSystem, 'filter') - .addSystem(RenderTextureSystem, 'renderTexture') - .addSystem(BatchSystem, 'batch'); - - this.initPlugins(Renderer.__plugins); - - /** - * The options passed in to create a new webgl context. - * - * @member {object} - * @private - */ - if (options.context) - { - this.context.initFromContext(options.context); - } - else - { - this.context.initFromOptions({ - alpha: this.transparent, - antialias: options.antialias, - premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', - stencil: true, - 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); - } - - addSystem(_class, name) - { - if (!name) - { - name = _class.name; - } - - // TODO - read name from class.name.. - - /* - if(name.includes('System')) - { - name = name.replace('System', ''); - name = name.charAt(0).toLowerCase() + name.slice(1); - } - */ - - const system = new _class(this); - - if (this[name]) - { - throw new Error(`Whoops! ${name} is already a manger`); - } - - this[name] = system; - - for (const i in this.runners) - { - this.runners[i].add(system); - } - - return this; - - /** - * 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. - */ - } - - /** - * Renders the object to its webGL view - * - * @param {PIXI.DisplayObject} displayObject - the object to be rendered - * @param {PIXI.RenderTexture} renderTexture - The render texture to render to. - * @param {boolean} [clear] - Should the canvas be cleared before the new render - * @param {PIXI.Transform} [transform] - A transform to apply to the render texture before rendering. - * @param {boolean} [skipUpdateTransform] - Should we skip the update transform pass? - */ - render(displayObject, renderTexture, clear, transform, skipUpdateTransform) - { - // can be handy to know! - this.renderingToScreen = !renderTexture; - - this.runners.prerender.run(); - this.emit('prerender'); - - // 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.parent; - - displayObject.parent = this._tempDisplayObjectParent; - displayObject.updateTransform(); - displayObject.parent = 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.run(); - - this.emit('postrender'); - } - - /** - * Resizes the webGL view to the specified width and height. - * - * @param {number} screenWidth - the new width of the screen - * @param {number} screenHeight - the new height of the screen - */ - resize(screenWidth, screenHeight) - { - AbstractRenderer.prototype.resize.call(this, screenWidth, screenHeight); - - this.runners.resize.run(screenWidth, screenHeight); - } - - /** - * Resets the WebGL state so you can render things however you fancy! - * - * @return {PIXI.Renderer} Returns itself. - */ - reset() - { - this.runners.reset.run(); - - return this; - } - - clear() - { - this.framebuffer.bind(); - this.framebuffer.clear(); - } - - /** - * Removes everything from the renderer (event listeners, spritebatch, etc...) - * - * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. - * See: https://github.com/pixijs/pixi.js/issues/2233 - */ - destroy(removeView) - { - this.runners.destroy.run(); - - // call base destroy - super.destroy(removeView); - - // TODO nullify all the managers.. - this.gl = null; - } - - /** - * 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 - * builds and excluding plugins. - * @name PIXI.Renderer#plugins - * @type {object} - * @readonly - * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. - * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. - * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. - * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. - */ - - /** - * Adds a plugin to the renderer. - * - * @method - * @param {string} pluginName - The name of the plugin. - * @param {Function} ctor - The constructor function or class for the plugin. - */ - static registerPlugin(pluginName, ctor) - { - Renderer.__plugins = Renderer.__plugins || {}; - Renderer.__plugins[pluginName] = ctor; - } -} diff --git a/packages/core/src/renderers/State.js b/packages/core/src/renderers/State.js deleted file mode 100644 index fa3f221..0000000 --- a/packages/core/src/renderers/State.js +++ /dev/null @@ -1,163 +0,0 @@ -/* eslint-disable max-len */ - -const BLEND = 0; -const OFFSET = 1; -const CULLING = 2; -const DEPTH_TEST = 3; -const WINDING = 4; - -/** - * This is a webGL state. It is passed The WebGL StateManager. - * Each mesh renderered may require webGL to be in a different state. - * For example you may want different blend mode or to enable polygon offsets - * - * @class - * @memberof PIXI - */ -export default class State -{ - /** - * - */ - constructor() - { - this.data = 0; - - this.blendMode = 0; - this.polygonOffset = 0; - - this.blend = true; - // this.depthTest = true; - } - - /** - * Activates blending of the computed fragment color values - * - * @member {boolean} - */ - get blend() - { - return !!(this.data & (1 << BLEND)); - } - - set blend(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << BLEND)) !== value) - { - this.data ^= (1 << BLEND); - } - } - - /** - * Activates adding an offset to depth values of polygon's fragments - * - * @member {boolean} - * @default false - */ - get offsets() - { - return !!(this.data & (1 << OFFSET)); - } - - set offsets(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << OFFSET)) !== value) - { - this.data ^= (1 << OFFSET); - } - } - - /** - * Activates culling of polygons. - * - * @member {boolean} - * @default false - */ - get culling() - { - return !!(this.data & (1 << CULLING)); - } - - set culling(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << CULLING)) !== value) - { - this.data ^= (1 << CULLING); - } - } - - /** - * Activates depth comparisons and updates to the depth buffer. - * - * @member {boolean} - * @default false - */ - get depthTest() - { - return !!(this.data & (1 << DEPTH_TEST)); - } - - set depthTest(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << DEPTH_TEST)) !== value) - { - this.data ^= (1 << DEPTH_TEST); - } - } - - /** - * Specifies whether or not front or back-facing polygons can be culled. - * @member {boolean} - * @default false - */ - get clockwiseFrontFace() - { - return !!(this.data & (1 << WINDING)); - } - - set clockwiseFrontFace(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << WINDING)) !== value) - { - this.data ^= (1 << WINDING); - } - } - - /** - * The blend mode to be applied when this state is set. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. - * Setting this mode to anything other than NO_BLEND will automatically switch blending on. - * - * @member {boolean} - * @default PIXI.BLEND_MODES.NORMAL - * @see PIXI.BLEND_MODES - */ - get blendMode() - { - return this._blendMode; - } - - set blendMode(value) // eslint-disable-line require-jsdoc - { - // 17 is NO BLEND - this.blend = (value !== 17); - this._blendMode = value; - } - - /** - * The polygon offset. Setting this property to anything other than 0 will automatically enable poygon offset fill. - * - * @member {number} - * @default 0 - */ - get polygonOffset() - { - return this._polygonOffset; - } - - set polygonOffset(value) // eslint-disable-line require-jsdoc - { - this.offsets = !!value; - this._polygonOffset = value; - } -} - diff --git a/packages/core/src/renderers/filters/Filter.js b/packages/core/src/renderers/filters/Filter.js deleted file mode 100644 index 2073b8d..0000000 --- a/packages/core/src/renderers/filters/Filter.js +++ /dev/null @@ -1,140 +0,0 @@ -import Shader from '../../shader/Shader'; -import Program from '../../shader/Program'; -import State from '../State'; -import { settings } from '@pixi/settings'; -// import extractUniformsFromSrc from './extractUniformsFromSrc'; -import defaultVertex from './defaultFilter.vert'; -import defaultFragment from './defaultFilter.frag'; - -// let math = require('../../math'); -/** - * @class - * @memberof PIXI - * @extends PIXI.Shader - */ -export default class Filter extends Shader -{ - /** - * @param {string} [vertexSrc] - The source of the vertex shader. - * @param {string} [fragmentSrc] - The source of the fragment shader. - * @param {object} [uniforms] - Custom uniforms to use to augment the built-in ones. - */ - constructor(vertexSrc, fragmentSrc, uniforms) - { - const program = Program.from(vertexSrc, fragmentSrc); - - super(program, uniforms); - - /** - * The padding of the filter. Some filters require extra space to breath such as a blur. - * Increasing this will add extra width and height to the bounds of the object that the - * filter is applied to. - * - * @member {number} - */ - this.padding = 0; - - /** - * The resolution of the filter. Setting this to be lower will lower the quality but - * increase the performance of the filter. - * - * @member {number} - */ - this.resolution = settings.RESOLUTION; - - /** - * If enabled is true the filter is applied, if false it will not. - * - * @member {boolean} - */ - this.enabled = true; - - /** - * If enabled, PixiJS will fit the filter area into boundaries for better performance. - * Switch it off if it does not work for specific shader. - * - * @member {boolean} - */ - this.autoFit = true; - - /** - * Legacy filters use position and uvs from attributes - * @member {boolean} - * @readonly - */ - this.legacy = !!this.program.attributeData.aTextureCoord; - - /** - * the webGL state the filter requires to render - * @member {PIXI.State} - */ - this.state = new State(); - } - - /** - * Applies the filter - * - * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from - * @param {PIXI.RenderTarget} input - The input render target. - * @param {PIXI.RenderTarget} output - The target to output to. - * @param {boolean} clear - Should the output be cleared before rendering to it - * @param {object} [currentState] - It's current state of filter. - * There are some useful properties in the currentState : - * target, filters, sourceFrame, destinationFrame, renderTarget, resolution - */ - apply(filterManager, input, output, clear, currentState, derp) // eslint-disable-line no-unused-vars - { - // do as you please! - - filterManager.applyFilter(this, input, output, clear, currentState, derp); - - // or just do a regular render.. - } - - /** - * Sets the blendmode of the filter - * - * @member {number} - * @default PIXI.BLEND_MODES.NORMAL - */ - get blendMode() - { - return this.state.blendMode; - } - - set blendMode(value) // eslint-disable-line require-jsdoc - { - this.state.blendMode = value; - } - - /** - * The default vertex shader source - * - * @static - * @constant - */ - static get defaultVertexSrc() - { - return defaultVertex; - } - - /** - * The default fragment shader source - * - * @static - * @constant - */ - static get defaultFragmentSrc() - { - return defaultFragment; - } -} - -/** - * Used for caching shader IDs - * - * @static - * @private - */ -Filter.SOURCE_KEY_MAP = {}; - diff --git a/packages/core/src/renderers/filters/defaultFilter.frag b/packages/core/src/renderers/filters/defaultFilter.frag deleted file mode 100644 index a14c1be..0000000 --- a/packages/core/src/renderers/filters/defaultFilter.frag +++ /dev/null @@ -1,22 +0,0 @@ -varying vec2 vTextureCoord; -varying vec2 vFilterCoord; - -uniform sampler2D uSampler; -uniform sampler2D filterSampler; - -void main(void){ - vec4 masky = texture2D(filterSampler, vFilterCoord); - vec4 sample = texture2D(uSampler, vTextureCoord); - vec4 color; - if(mod(vFilterCoord.x, 1.0) > 0.5) - { - color = vec4(1.0, 0.0, 0.0, 1.0); - } - else - { - color = vec4(0.0, 1.0, 0.0, 1.0); - } - // gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0); - gl_FragColor = mix(sample, masky, 0.5); - gl_FragColor *= sample.a; -} diff --git a/packages/core/src/renderers/filters/defaultFilter.vert b/packages/core/src/renderers/filters/defaultFilter.vert deleted file mode 100644 index e1febca..0000000 --- a/packages/core/src/renderers/filters/defaultFilter.vert +++ /dev/null @@ -1,14 +0,0 @@ -attribute vec2 aVertexPosition; -attribute vec2 aTextureCoord; - -uniform mat3 projectionMatrix; -uniform mat3 filterMatrix; - -varying vec2 vTextureCoord; -varying vec2 vFilterCoord; - -void main(void){ - gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); - vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy; - vTextureCoord = aTextureCoord ; -} diff --git a/packages/core/src/renderers/filters/filterTransforms.js b/packages/core/src/renderers/filters/filterTransforms.js deleted file mode 100644 index 87a3cd6..0000000 --- a/packages/core/src/renderers/filters/filterTransforms.js +++ /dev/null @@ -1,50 +0,0 @@ -import { Matrix } from '@pixi/math'; - -/** - * Calculates the mapped matrix - * @param filterArea {Rectangle} The filter area - * @param sprite {Sprite} the target sprite - * @param outputMatrix {Matrix} @alvin - * @private - */ -// this returns a matrix that will normalise map filter cords in the filter to screen space -export function calculateScreenSpaceMatrix(outputMatrix, filterArea, textureSize) -{ - // TODO unwrap? - const mappedMatrix = outputMatrix.identity(); - - mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); - - mappedMatrix.scale(textureSize.width, textureSize.height); - - return mappedMatrix; -} - -export function calculateNormalizedScreenSpaceMatrix(outputMatrix, filterArea, textureSize) -{ - const mappedMatrix = outputMatrix.identity(); - - mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); - - const translateScaleX = (textureSize.width / filterArea.width); - const translateScaleY = (textureSize.height / filterArea.height); - - mappedMatrix.scale(translateScaleX, translateScaleY); - - return mappedMatrix; -} - -// this will map the filter coord so that a texture can be used based on the transform of a sprite -export function calculateSpriteMatrix(outputMatrix, filterArea, textureSize, sprite) -{ - const orig = sprite._texture.orig; - const mappedMatrix = outputMatrix.set(textureSize.width, 0, 0, textureSize.height, filterArea.x, filterArea.y); - const worldTransform = sprite.worldTransform.copyTo(Matrix.TEMP_MATRIX); - - worldTransform.invert(); - mappedMatrix.prepend(worldTransform); - mappedMatrix.scale(1.0 / orig.width, 1.0 / orig.height); - mappedMatrix.translate(sprite.anchor.x, sprite.anchor.y); - - return mappedMatrix; -} diff --git a/packages/core/src/renderers/filters/spriteMask/SpriteMaskFilter.js b/packages/core/src/renderers/filters/spriteMask/SpriteMaskFilter.js deleted file mode 100644 index d5a91a4..0000000 --- a/packages/core/src/renderers/filters/spriteMask/SpriteMaskFilter.js +++ /dev/null @@ -1,63 +0,0 @@ -import Filter from '../Filter'; -import { Matrix } from '@pixi/math'; -import vertex from './spriteMaskFilter.vert'; -import fragment from './spriteMaskFilter.frag'; -import { default as TextureMatrix } from '../../../textures/TextureMatrix'; - -/** - * The SpriteMaskFilter class - * - * @class - * @extends PIXI.Filter - * @memberof PIXI - */ -export default class SpriteMaskFilter extends Filter -{ - /** - * @param {PIXI.Sprite} sprite - the target sprite - */ - constructor(sprite) - { - const maskMatrix = new Matrix(); - - super(vertex, fragment); - - sprite.renderable = false; - - this.maskSprite = sprite; - this.maskMatrix = maskMatrix; - } - - /** - * Applies the filter - * - * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from - * @param {PIXI.RenderTarget} input - The input render target. - * @param {PIXI.RenderTarget} output - The target to output to. - */ - apply(filterManager, input, output) - { - const maskSprite = this.maskSprite; - const tex = this.maskSprite.texture; - - if (!tex.valid) - { - return; - } - if (!tex.transform) - { - // margin = 0.0, let it bleed a bit, shader code becomes easier - // assuming that atlas textures were made with 1-pixel padding - tex.transform = new TextureMatrix(tex, 0.0); - } - tex.transform.update(); - - this.uniforms.mask = tex; - this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite) - .prepend(tex.transform.mapCoord); - this.uniforms.alpha = maskSprite.worldAlpha; - this.uniforms.maskClamp = tex.transform.uClampFrame; - - filterManager.applyFilter(this, input, output); - } -} diff --git a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.frag b/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.frag deleted file mode 100644 index 0e4aef8..0000000 --- a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.frag +++ /dev/null @@ -1,23 +0,0 @@ -varying vec2 vMaskCoord; -varying vec2 vTextureCoord; - -uniform sampler2D uSampler; -uniform sampler2D mask; -uniform float alpha; -uniform vec4 maskClamp; - -void main(void) -{ - float clip = step(3.5, - step(maskClamp.x, vMaskCoord.x) + - step(maskClamp.y, vMaskCoord.y) + - step(vMaskCoord.x, maskClamp.z) + - step(vMaskCoord.y, maskClamp.w)); - - vec4 original = texture2D(uSampler, vTextureCoord); - vec4 masky = texture2D(mask, vMaskCoord); - - original *= (masky.r * masky.a * alpha * clip); - - gl_FragColor = original; -} diff --git a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.vert b/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.vert deleted file mode 100644 index d23a30c..0000000 --- a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.vert +++ /dev/null @@ -1,16 +0,0 @@ -attribute vec2 aVertexPosition; -attribute vec2 aTextureCoord; - -uniform mat3 projectionMatrix; -uniform mat3 otherMatrix; - -varying vec2 vMaskCoord; -varying vec2 vTextureCoord; - -void main(void) -{ - gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); - - vTextureCoord = aTextureCoord; - vMaskCoord = ( otherMatrix * vec3( aTextureCoord, 1.0) ).xy; -} diff --git a/packages/core/src/renderers/systems/BatchSystem.js b/packages/core/src/renderers/systems/BatchSystem.js deleted file mode 100644 index 70e9518..0000000 --- a/packages/core/src/renderers/systems/BatchSystem.js +++ /dev/null @@ -1,68 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import ObjectRenderer from '../utils/ObjectRenderer'; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ - -export default class BatchSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this manager works for. - */ - constructor(renderer) - { - super(renderer); - - /** - * An empty renderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.emptyRenderer = new ObjectRenderer(renderer); - - /** - * The currently active ObjectRenderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.currentRenderer = this.emptyRenderer; - } - - /** - * Changes the current renderer to the one given in parameter - * - * @param {PIXI.ObjectRenderer} objectRenderer - The object renderer to use. - */ - setObjectRenderer(objectRenderer) - { - if (this.currentRenderer === objectRenderer) - { - return; - } - - this.currentRenderer.stop(); - this.currentRenderer = objectRenderer; - - this.renderer.state.setState(objectRenderer.state); - - this.currentRenderer.start(); - } - - /** - * This should be called if you wish to do some custom rendering - * It will basically render anything that may be batched up such as sprites - * - */ - flush() - { - this.setObjectRenderer(this.emptyRenderer); - } - - reset() - { - this.setObjectRenderer(this.emptyRenderer); - } -} diff --git a/packages/core/src/renderers/systems/ContextSystem.js b/packages/core/src/renderers/systems/ContextSystem.js deleted file mode 100644 index 43bf827..0000000 --- a/packages/core/src/renderers/systems/ContextSystem.js +++ /dev/null @@ -1,186 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { settings } from '@pixi/settings'; - -let CONTEXT_UID = 0; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class ContextSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this.webGLVersion = 1; - - this.handleContextLost = this.handleContextLost.bind(this); - this.handleContextRestored = this.handleContextRestored.bind(this); - - this.extensions = {}; - - renderer.view.addEventListener('webglcontextlost', this.handleContextLost, false); - renderer.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); - } - - get isLost() - { - return (!this.gl || this.gl.isContextLost()); - } - - contextChange(gl) - { - this.gl = gl; - - // restore a context if it was previously lost - if (gl.isContextLost() && gl.getExtension('WEBGL_lose_context')) - { - gl.getExtension('WEBGL_lose_context').restoreContext(); - } - } - - initFromContext(gl) - { - this.gl = gl; - this.validateContext(gl); - this.renderer.gl = gl; - this.renderer.CONTEXT_UID = CONTEXT_UID++; - this.renderer.runners.contextChange.run(gl); - } - - initFromOptions(options) - { - const gl = this.createContext(this.renderer.view, options); - - this.initFromContext(gl); - } - - /** - * Helper class to create a webGL Context - * - * @class - * @memberof PIXI.glCore - * @param canvas {HTMLCanvasElement} the canvas element that we will get the context from - * @param options {Object} An options object that gets passed in to the canvas element containing the context attributes, - * see https://developer.mozilla.org/en/docs/Web/API/HTMLCanvasElement/getContext for the options available - * @return {WebGLRenderingContext} the WebGL context - */ - createContext(canvas, options) - { - let gl; - - if (settings.PREFER_WEBGL_2) - { - gl = canvas.getContext('webgl2', options); - } - - if (gl) - { - this.webGLVersion = 2; - } - else - { - this.webGLVersion = 1; - - gl = canvas.getContext('webgl', options) - || canvas.getContext('experimental-webgl', options); - - if (!gl) - { - // fail, not able to get a context - throw new Error('This browser does not support webGL. Try using the canvas renderer'); - } - } - - this.gl = gl; - - this.getExtensions(); - - return gl; - } - - getExtensions() - { - // time to set up default etensions that pixi uses.. - const gl = this.gl; - const extensions = this.extensions; - - if (this.webGLVersion === 1) - { - extensions.drawBuffers = gl.getExtension('WEBGL_draw_buffers'); - extensions.depthTexture = gl.getExtension('WEBKIT_WEBGL_depth_texture'); - extensions.floatTexture = gl.getExtension('OES_texture_float'); - extensions.loseContext = gl.getExtension('WEBGL_lose_context'); - - extensions.vertexArrayObject = gl.getExtension('OES_vertex_array_object') - || gl.getExtension('MOZ_OES_vertex_array_object') - || gl.getExtension('WEBKIT_OES_vertex_array_object'); - } - - // we don't use any specific WebGL 2 ones yet! - } - - /** - * Handles a lost webgl context - * - * @private - * @param {WebGLContextEvent} event - The context lost event. - */ - handleContextLost(event) - { - event.preventDefault(); - } - - /** - * Handles a restored webgl context - * - * @private - */ - handleContextRestored() - { - this.renderer.runners.contextChange.run(this.gl); - } - - destroy() - { - const view = this.renderer.view; - - // remove listeners - view.removeEventListener('webglcontextlost', this.handleContextLost); - view.removeEventListener('webglcontextrestored', this.handleContextRestored); - - this.gl.useProgram(null); - - if (this.extensions.loseContext) - { - this.extensions.loseContext.loseContext(); - } - } - - postrender() - { - this.gl.flush(); - } - - validateContext(gl) - { - const attributes = gl.getContextAttributes(); - - // this is going to be fairly simple for now.. but at least we have room to grow! - if (!attributes.stencil) - { - /* eslint-disable max-len */ - - /* eslint-disable no-console */ - console.warn('Provided WebGL context does not have a stencil buffer, masks may not render correctly'); - /* eslint-enable no-console */ - - /* eslint-enable max-len */ - } - } -} diff --git a/packages/core/src/renderers/systems/FramebufferSystem.js b/packages/core/src/renderers/systems/FramebufferSystem.js deleted file mode 100644 index df7902b..0000000 --- a/packages/core/src/renderers/systems/FramebufferSystem.js +++ /dev/null @@ -1,246 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { Rectangle } from '@pixi/math'; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class FramebufferSystem extends WebGLSystem -{ - /** - * Sets up the renderer context and necessary buffers. - * - * @private - */ - contextChange() - { - this.gl = this.renderer.gl; - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - this.current = null; - this.viewport = new Rectangle(); - - this.drawBufferExtension = this.renderer.context.extensions.drawBuffers; - } - - bind(framebuffer, frame) - { - const gl = this.gl; - - this.current = framebuffer; - - if (framebuffer) - { - // TODO cacheing layer! - - const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID] || this.initFramebuffer(framebuffer); - - gl.bindFramebuffer(gl.FRAMEBUFFER, fbo.framebuffer); - // makesure all textures are unbound.. - - // now check for updates... - if (fbo.dirtyId !== framebuffer.dirtyId) - { - fbo.dirtyId = framebuffer.dirtyId; - - if (fbo.dirtyFormat !== framebuffer.dirtyFormat) - { - fbo.dirtyFormat = framebuffer.dirtyFormat; - this.updateFramebuffer(framebuffer); - } - else if (fbo.dirtySize !== framebuffer.dirtySize) - { - fbo.dirtySize = framebuffer.dirtySize; - this.resizeFramebuffer(framebuffer); - } - } - - for (let i = 0; i < framebuffer.colorTextures.length; i++) - { - if (framebuffer.colorTextures[i].texturePart) - { - this.renderer.texture.unbind(framebuffer.colorTextures[i].texture); - } - else - { - this.renderer.texture.unbind(framebuffer.colorTextures[i]); - } - } - - if (framebuffer.depthTexture) - { - this.renderer.texture.unbind(framebuffer.depthTexture); - } - - if (frame) - { - this.setViewport(frame.x, frame.y, frame.width, frame.height); - } - else - { - this.setViewport(0, 0, framebuffer.width, framebuffer.height); - } - } - else - { - gl.bindFramebuffer(gl.FRAMEBUFFER, null); - - if (frame) - { - this.setViewport(frame.x, frame.y, frame.width, frame.height); - } - else - { - this.setViewport(0, 0, this.renderer.width, this.renderer.height); - } - } - } - - setViewport(x, y, width, height) - { - const v = this.viewport; - - if (v.width !== width || v.height !== height || v.x !== x || v.y !== y) - { - v.x = x; - v.y = y; - v.width = width; - v.height = height; - - this.gl.viewport(x, y, width, height); - } - } - - get size() - { - if (this.current) - { - // TODO store temp - return { x: 0, y: 0, width: this.current.width, height: this.current.height }; - } - - return { x: 0, y: 0, width: this.renderer.width, height: this.renderer.height }; - } - - clear(r, g, b, a) - { - const gl = this.gl; - - // TODO clear color can be set only one right? - gl.clearColor(r, g, b, a); - gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); - } - - // private functions... - - initFramebuffer(framebuffer) - { - const gl = this.gl; - - // TODO - make this a class? - const fbo = { - framebuffer: gl.createFramebuffer(), - stencil: null, - dirtyId: 0, - dirtyFormat: 0, - dirtySize: 0, - }; - - framebuffer.glFrameBuffers[this.CONTEXT_UID] = fbo; - - return fbo; - } - - resizeFramebuffer(framebuffer) - { - const gl = this.gl; - - if (framebuffer.stencil || framebuffer.depth) - { - gl.bindRenderbuffer(gl.RENDERBUFFER, this.stencil); - gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); - } - } - - updateFramebuffer(framebuffer) - { - const gl = this.gl; - - const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID]; - - // bind the color texture - const colorTextures = framebuffer.colorTextures; - - let count = colorTextures.length; - - if (!this.drawBufferExtension) - { - count = Math.min(count, 1); - } - - const activeTextures = []; - - for (let i = 0; i < count; i++) - { - const texture = framebuffer.colorTextures[i]; - - if (texture.texturePart) - { - this.renderer.texture.bind(texture.texture, 0); - - gl.framebufferTexture2D(gl.FRAMEBUFFER, - gl.COLOR_ATTACHMENT0 + i, - gl.TEXTURE_CUBE_MAP_NEGATIVE_X + texture.side, - texture.texture._glTextures[this.CONTEXT_UID].texture, - 0); - } - else - { - this.renderer.texture.bind(texture, 0); - - gl.framebufferTexture2D(gl.FRAMEBUFFER, - gl.COLOR_ATTACHMENT0 + i, - gl.TEXTURE_2D, - texture._glTextures[this.CONTEXT_UID].texture, - 0); - } - - activeTextures.push(gl.COLOR_ATTACHMENT0 + i); - } - - if (this.drawBufferExtension && activeTextures.length > 1) - { - this.drawBufferExtension.drawBuffersWEBGL(activeTextures); - } - - if (framebuffer.depthTexture) - { - const depthTextureExt = this.renderer.context.extensions.depthTexture; - - if (depthTextureExt) - { - const depthTexture = framebuffer.depthTexture; - - this.renderer.texture.bind(depthTexture, 0); - - gl.framebufferTexture2D(gl.FRAMEBUFFER, - gl.DEPTH_ATTACHMENT, - gl.TEXTURE_2D, - depthTexture._glTextures[this.CONTEXT_UID].texture, - 0); - } - } - - if (framebuffer.stencil || framebuffer.depth) - { - fbo.stencil = gl.createRenderbuffer(); - - gl.bindRenderbuffer(gl.RENDERBUFFER, fbo.stencil); - - // TODO.. this is depth AND stencil? - gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, fbo.stencil); - gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); - // fbo.enableStencil(); - } - } -} diff --git a/packages/core/src/renderers/systems/MaskSystem.js b/packages/core/src/renderers/systems/MaskSystem.js deleted file mode 100644 index d9c4b1a..0000000 --- a/packages/core/src/renderers/systems/MaskSystem.js +++ /dev/null @@ -1,199 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import AlphaMaskFilter from '../filters/spriteMask/SpriteMaskFilter'; - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class MaskSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - // TODO - we don't need both! - this.scissor = false; - this.scissorData = null; - this.scissorRenderTarget = null; - - this.enableScissor = false; - - this.alphaMaskPool = []; - this.alphaMaskIndex = 0; - } - - /** - * Applies the Mask and adds it to the current filter stack. - * - * @param {PIXI.DisplayObject} target - Display Object to push the mask to - * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. - */ - push(target, maskData) - { - // TODO the root check means scissor rect will not - // be used on render textures more info here: - // https://github.com/pixijs/pixi.js/pull/3545 - - if (maskData.texture) - { - this.pushSpriteMask(target, maskData); - } - else if (this.enableScissor - && !this.scissor - && this.renderer._activeRenderTarget.root - && !this.renderer.stencil.stencilMaskStack.length - && maskData.isFastRect()) - { - const matrix = maskData.worldTransform; - - let rot = Math.atan2(matrix.b, matrix.a); - - // use the nearest degree! - rot = Math.round(rot * (180 / Math.PI)); - - if (rot % 90) - { - this.pushStencilMask(maskData); - } - else - { - this.pushScissorMask(target, maskData); - } - } - else - { - this.pushStencilMask(maskData); - } - } - - /** - * Removes the last mask from the mask stack and doesn't return it. - * - * @param {PIXI.DisplayObject} target - Display Object to pop the mask from - * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. - */ - pop(target, maskData) - { - if (maskData.texture) - { - this.popSpriteMask(target, maskData); - } - else if (this.enableScissor && !this.renderer.stencil.stencilMaskStack.length) - { - this.popScissorMask(target, maskData); - } - else - { - this.popStencilMask(target, maskData); - } - } - - /** - * Applies the Mask and adds it to the current filter stack. - * - * @param {PIXI.RenderTarget} target - Display Object to push the sprite mask to - * @param {PIXI.Sprite} maskData - Sprite to be used as the mask - */ - pushSpriteMask(target, maskData) - { - let alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; - - if (!alphaMaskFilter) - { - alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; - } - - alphaMaskFilter[0].resolution = this.renderer.resolution; - alphaMaskFilter[0].maskSprite = maskData; - - // TODO - may cause issues! - target.filterArea = maskData.getBounds(true); - - this.renderer.filterSystem.pushFilter(target, alphaMaskFilter); - - this.alphaMaskIndex++; - } - - /** - * Removes the last filter from the filter stack and doesn't return it. - * - */ - popSpriteMask() - { - this.renderer.filterSystem.popFilter(); - this.alphaMaskIndex--; - } - - /** - * Applies the Mask and adds it to the current filter stack. - * - * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. - */ - pushStencilMask(maskData) - { - this.renderer.batch.flush(); - this.renderer.stencil.pushStencil(maskData); - } - - /** - * Removes the last filter from the filter stack and doesn't return it. - * - */ - popStencilMask() - { - // this.renderer.currentRenderer.stop(); - this.renderer.stencil.popStencil(); - } - - /** - * - * @param {PIXI.DisplayObject} target - Display Object to push the mask to - * @param {PIXI.Graphics} maskData - The masking data. - */ - pushScissorMask(target, maskData) - { - maskData.renderable = true; - - const renderTarget = this.renderer._activeRenderTarget; - - const bounds = maskData.getBounds(); - - bounds.fit(renderTarget.size); - maskData.renderable = false; - - this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); - - const resolution = this.renderer.resolution; - - this.renderer.gl.scissor( - bounds.x * resolution, - (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, - bounds.width * resolution, - bounds.height * resolution - ); - - this.scissorRenderTarget = renderTarget; - this.scissorData = maskData; - this.scissor = true; - } - - /** - * - * - */ - popScissorMask() - { - this.scissorRenderTarget = null; - this.scissorData = null; - this.scissor = false; - - // must be scissor! - const gl = this.renderer.gl; - - gl.disable(gl.SCISSOR_TEST); - } -} diff --git a/packages/core/src/renderers/systems/ProjectionSystem.js b/packages/core/src/renderers/systems/ProjectionSystem.js deleted file mode 100644 index ec5efdb..0000000 --- a/packages/core/src/renderers/systems/ProjectionSystem.js +++ /dev/null @@ -1,73 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { Matrix } from '@pixi/math'; - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ - -export default class ProjectionSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this.projectionMatrix = new Matrix(); - } - - update(destinationFrame, sourceFrame, resolution, root) - { - this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; - this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; - - this.calculateProjection(this.destinationFrame, this.sourceFrame, resolution, root); - - this.renderer.globalUniforms.uniforms.projectionMatrix = this.projectionMatrix; - this.renderer.globalUniforms.update(); - } - - /** - * Updates the projection matrix based on a projection frame (which is a rectangle) - * - * @param {Rectangle} destinationFrame - The destination frame. - * @param {Rectangle} sourceFrame - The source frame. - */ - calculateProjection(destinationFrame, sourceFrame, resolution, root) - { - const pm = this.projectionMatrix; - - // I don't think we will need this line.. - // pm.identity(); - - if (!root) - { - pm.a = (1 / destinationFrame.width * 2) * resolution; - pm.d = (1 / destinationFrame.height * 2) * resolution; - - pm.tx = -1 - (sourceFrame.x * pm.a); - pm.ty = -1 - (sourceFrame.y * pm.d); - } - else - { - pm.a = (1 / destinationFrame.width * 2) * resolution; - pm.d = (-1 / destinationFrame.height * 2) * resolution; - - pm.tx = -1 - (sourceFrame.x * pm.a); - pm.ty = 1 - (sourceFrame.y * pm.d); - } - } - - /** - * Sets the transform of the active render target to the given matrix - * - * @param {PIXI.Matrix} matrix - The transformation matrix - */ - setTransform()// matrix) - { - // this._activeRenderTarget.transform = matrix; - } -} diff --git a/packages/core/src/renderers/systems/RenderTextureSystem.js b/packages/core/src/renderers/systems/RenderTextureSystem.js deleted file mode 100644 index a133ef1..0000000 --- a/packages/core/src/renderers/systems/RenderTextureSystem.js +++ /dev/null @@ -1,115 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { Rectangle } from '@pixi/math'; - -const tempRect = new Rectangle(); - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ - -export default class RenderTextureSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this.clearColor = renderer._backgroundColorRgba; - - // TODO moe this property somewhere else! - this.defaultMaskStack = []; - this.defaultFilterStack = [{}]; - - // empty render texture? - this.renderTexture = null; - - this.destinationFrame = new Rectangle(); - } - - bind(renderTexture, sourceFrame, destinationFrame) - { - // TODO - do we want this?? - if (this.renderTexture === renderTexture) return; - this.renderTexture = renderTexture; - - const renderer = this.renderer; - - if (renderTexture) - { - const baseTexture = renderTexture.baseTexture; - - if (!destinationFrame) - { - tempRect.width = baseTexture.realWidth; - tempRect.height = baseTexture.realHeight; - - destinationFrame = tempRect; - } - - if (!sourceFrame) - { - sourceFrame = destinationFrame; - } - - this.renderer.framebuffer.bind(baseTexture.frameBuffer, destinationFrame); - - this.renderer.projection.update(destinationFrame, sourceFrame, baseTexture.resolution, false); - this.renderer.stencil.setMaskStack(baseTexture.stencilMaskStack); - } - else - { - // TODO these validation checks happen deeper down.. - // thing they can be avoided.. - if (!destinationFrame) - { - tempRect.width = renderer.width; - tempRect.height = renderer.height; - - destinationFrame = tempRect; - } - - if (!sourceFrame) - { - sourceFrame = destinationFrame; - } - - renderer.framebuffer.bind(null, destinationFrame); - - // TODO store this.. - this.renderer.projection.update(destinationFrame, sourceFrame, this.renderer.resolution, true); - this.renderer.stencil.setMaskStack(this.defaultMaskStack); - } - - this.destinationFrame.copyFrom(destinationFrame); - } - - /** - * Erases the render texture and fills the drawing area with a colour - * - * @param {number} [clearColor] - The colour - * @return {PIXI.Renderer} Returns itself. - */ - clear(clearColor) - { - if (this.renderTexture) - { - clearColor = clearColor || this.renderTexture.baseTexture.clearColor; - } - else - { - clearColor = clearColor || this.clearColor; - } - - this.renderer.framebuffer.clear(clearColor[0], clearColor[1], clearColor[2], clearColor[3]); - } - - resize()// screenWidth, screenHeight) - { - // resize the root only! - this.bind(null); - } -} diff --git a/packages/core/src/renderers/systems/StateSystem.js b/packages/core/src/renderers/systems/StateSystem.js deleted file mode 100755 index d7f4caf..0000000 --- a/packages/core/src/renderers/systems/StateSystem.js +++ /dev/null @@ -1,288 +0,0 @@ -import mapWebGLBlendModesToPixi from '../utils/mapWebGLBlendModesToPixi'; -import WebGLSystem from './WebGLSystem'; -import WebGLState from '../State'; - -const BLEND = 0; -const OFFSET = 1; -const CULLING = 2; -const DEPTH_TEST = 3; -const WINDING = 4; - -/** - * A WebGL state machines - * - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class StateSystem extends WebGLSystem -{ - /** - * @param {WebGLRenderingContext} gl - The current WebGL rendering context - */ - constructor(renderer) - { - super(renderer); - - this.gl = null; - - this.maxAttribs = null; - - // check we have vao.. - this.nativeVaoExtension = null; - - this.attribState = null; - - this.stateId = 0; - this.polygonOffset = 0; - this.blendMode = 17; - - this.map = []; - - // map functions for when we set state.. - this.map[BLEND] = this.setBlend; - this.map[OFFSET] = this.setOffset; - this.map[CULLING] = this.setCullFace; - this.map[DEPTH_TEST] = this.setDepthTest; - this.map[WINDING] = this.setFrontFace; - - this.checks = []; - - this.defaultState = new WebGLState(); - this.defaultState.blend = true; - this.defaultState.depth = true; - } - - contextChange(gl) - { - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; - - this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); - - // check we have vao.. - this.nativeVaoExtension = ( - gl.getExtension('OES_vertex_array_object') - || gl.getExtension('MOZ_OES_vertex_array_object') - || gl.getExtension('WEBKIT_OES_vertex_array_object') - ); - - this.attribState = { - tempAttribState: new Array(this.maxAttribs), - attribState: new Array(this.maxAttribs), - }; - - this.blendModes = mapWebGLBlendModesToPixi(gl); - - this.setState(this.defaultState); - - this.reset(); - } - - /** - * Sets the current state - * - * @param {*} state - The state to set. - */ - setState(state) - { - state = state || this.defaultState; - - // TODO maybe to an object check? ( this.state === state )? - if (this.stateId !== state.data) - { - let diff = this.stateId ^ state.data; - let i = 0; - - // order from least to most common - while (diff) - { - if (diff & 1) - { - // state change! - this.map[i].call(this, !!(state.data & (1 << i))); - } - - diff = diff >> 1; - i++; - } - - this.stateId = state.data; - } - - // based on the above settings we check for specific modes.. - // for example if blend is active we check and set the blend modes - // or of polygon offset is active we check the poly depth. - for (let i = 0; i < this.checks.length; i++) - { - this.checks[i](this, state); - } - } - - /** - * Enables or disabled blending. - * - * @param {boolean} value - Turn on or off webgl blending. - */ - setBlend(value) - { - this.updateCheck(StateSystem.checkBlendMode, value); - - this.gl[value ? 'enable' : 'disable'](this.gl.BLEND); - } - - /** - * Enables or disable polygon offset fill - * - * @param {boolean} value - Turn on or off webgl polygon offset testing. - */ - setOffset(value) - { - this.gl[value ? 'enable' : 'disable'](this.gl.POLYGON_OFFSET_FILL); - } - - /** - * Sets whether to enable or disable depth test. - * - * @param {boolean} value - Turn on or off webgl depth testing. - */ - setDepthTest(value) - { - this.gl[value ? 'enable' : 'disable'](this.gl.DEPTH_TEST); - } - - /** - * Sets whether to enable or disable cull face. - * - * @param {boolean} value - Turn on or off webgl cull face. - */ - setCullFace(value) - { - this.gl[value ? 'enable' : 'disable'](this.gl.CULL_FACE); - } - - /** - * Sets the gl front face. - * - * @param {boolean} value - true is clockwise and false is counter-clockwise - */ - setFrontFace(value) - { - this.gl.frontFace(this.gl[value ? 'CW' : 'CCW']); - } - - /** - * Sets the blend mode. - * - * @param {number} value - The blend mode to set to. - */ - setBlendMode(value) - { - if (value === this.blendMode) - { - return; - } - - this.blendMode = value; - this.gl.blendFunc(this.blendModes[value][0], this.blendModes[value][1]); - } - - /** - * Sets the polygon offset. - * - * @param {number} value - the polygon offset - * @param {number} scale - the polygon offset scale - */ - setPolygonOffset(value, scale) - { - this.gl.polygonOffset(value, scale); - } - - /** - * Disables all the vaos in use - * - */ - resetAttributes() - { - for (let i = 0; i < this.attribState.tempAttribState.length; i++) - { - this.attribState.tempAttribState[i] = 0; - } - - for (let i = 0; i < this.attribState.attribState.length; i++) - { - this.attribState.attribState[i] = 0; - } - - // im going to assume one is always active for performance reasons. - for (let i = 1; i < this.maxAttribs; i++) - { - this.gl.disableVertexAttribArray(i); - } - } - - // used - /** - * Resets all the logic and disables the vaos - */ - reset() - { - // unbind any VAO if they exist.. - if (this.nativeVaoExtension) - { - this.nativeVaoExtension.bindVertexArrayOES(null); - } - - // reset all attributes.. - this.resetAttributes(); - - this.gl.pixelStorei(this.gl.UNPACK_FLIP_Y_WEBGL, false); - - this.setBlendMode(0); - - // TODO? - // this.setState(this.defaultState); - } - - /** - * checks to see which updates should be checked based on which settings have been activated - * for example if blend is enabled then we shold check the blend modes each time the state is changed - * or if poygon fill is activated then we need to check if the polygone offset changes. - * The idea is that we only check what we have too. - * - * @param {Function} func the checking function to add or remove - * @param {boolean} value should the check function be added or removed. - */ - updateCheck(func, value) - { - const index = this.checks.indexOf(func); - - if (value && index === -1) - { - this.checks.push(func); - } - else if (!value && index !== -1) - { - this.checks.splice(index, 1); - } - } - - /** - * A private little wrapper function that we call to check the blend mode. - * - * @static - * @private - * @param {PIXI.StateSystem} System the System to perform the state check on - * @param {PIXI.State} state the state that the blendMode will pulled from - */ - static checkBlendMode(system, state) - { - system.setBlendMode(state.blendMode); - } - - // TODO - add polygon offset? -} diff --git a/packages/core/src/renderers/systems/StencilSystem.js b/packages/core/src/renderers/systems/StencilSystem.js deleted file mode 100644 index 5d538cb..0000000 --- a/packages/core/src/renderers/systems/StencilSystem.js +++ /dev/null @@ -1,133 +0,0 @@ -import WebGLSystem from './WebGLSystem'; - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class StencilSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - this.stencilMaskStack = []; - } - - /** - * Changes the mask stack that is used by this System. - * - * @param {PIXI.Graphics[]} stencilMaskStack - The mask stack - */ - setMaskStack(stencilMaskStack) - { - const gl = this.renderer.gl; - - if (stencilMaskStack.length !== this.stencilMaskStack.length) - { - if (stencilMaskStack.length === 0) - { - gl.disable(gl.STENCIL_TEST); - } - else - { - gl.enable(gl.STENCIL_TEST); - } - } - - this.stencilMaskStack = stencilMaskStack; - } - - /** - * Applies the Mask and adds it to the current stencil stack. @alvin - * - * @param {PIXI.Graphics} graphics - The mask - */ - pushStencil(graphics) - { - this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); - - // this.renderer._activeRenderTarget.attachStencilBuffer(); - - const gl = this.renderer.gl; - const prevMaskCount = this.stencilMaskStack.length; - - if (prevMaskCount === 0) - { - gl.enable(gl.STENCIL_TEST); - } - - this.stencilMaskStack.push(graphics); - - // Increment the refference stencil value where the new mask overlaps with the old ones. - gl.colorMask(false, false, false, false); - gl.stencilFunc(gl.EQUAL, prevMaskCount, this._getBitwiseMask()); - gl.stencilOp(gl.KEEP, gl.KEEP, gl.INCR); - this.renderer.plugins.graphics.render(graphics); - - this._useCurrent(); - } - - /** - * Removes the last mask from the stencil stack. @alvin - */ - popStencil() - { - this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); - - const gl = this.renderer.gl; - const graphics = this.stencilMaskStack.pop(); - - if (this.stencilMaskStack.length === 0) - { - // the stack is empty! - gl.disable(gl.STENCIL_TEST); - gl.clear(gl.STENCIL_BUFFER_BIT); - gl.clearStencil(0); - } - else - { - // Decrement the refference stencil value where the popped mask overlaps with the other ones - gl.colorMask(false, false, false, false); - gl.stencilOp(gl.KEEP, gl.KEEP, gl.DECR); - this.renderer.plugins.graphics.render(graphics); - - this._useCurrent(); - } - } - - /** - * Setup renderer to use the current stencil data. - */ - _useCurrent() - { - const gl = this.renderer.gl; - - gl.colorMask(true, true, true, true); - gl.stencilFunc(gl.EQUAL, this.stencilMaskStack.length, this._getBitwiseMask()); - gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP); - } - - /** - * Fill 1s equal to the number of acitve stencil masks. - * - * @return {number} The bitwise mask. - */ - _getBitwiseMask() - { - return (1 << this.stencilMaskStack.length) - 1; - } - - /** - * Destroys the mask stack. - * - */ - destroy() - { - super.destroy(this); - - this.stencilMaskStack = null; - } -} diff --git a/packages/core/src/renderers/systems/WebGLSystem.js b/packages/core/src/renderers/systems/WebGLSystem.js deleted file mode 100644 index d3d5b31..0000000 --- a/packages/core/src/renderers/systems/WebGLSystem.js +++ /dev/null @@ -1,41 +0,0 @@ -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this manager works for. - */ - constructor(renderer) - { - /** - * The renderer this manager works for. - * - * @member {PIXI.Renderer} - */ - this.renderer = renderer; - - this.renderer.runners.contextChange.add(this); - } - - /** - * Generic method called when there is a WebGL context change. - * - */ - contextChange() - { - // do some codes init! - } - - /** - * Generic destroy methods to be overridden by the subclass - * - */ - destroy() - { - this.renderer.runners.contextChange.remove(this); - this.renderer = null; - } -} diff --git a/packages/core/src/renderers/systems/filter/FilterSystem.js b/packages/core/src/renderers/systems/filter/FilterSystem.js deleted file mode 100644 index e615037..0000000 --- a/packages/core/src/renderers/systems/filter/FilterSystem.js +++ /dev/null @@ -1,431 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; - -import RenderTexture from '../../../textures/RenderTexture'; -import Quad from '../../utils/Quad'; -import QuadUv from '../../utils/QuadUv'; -import { Rectangle } from '@pixi/math'; -import * as filterTransforms from '../../filters/filterTransforms'; -import bitTwiddle from 'bit-twiddle'; -import UniformGroup from '../../../shader/UniformGroup'; -import { DRAW_MODES } from '../../../../../constants'; - -// -/** - * @ignore - * @class - */ -class FilterState -{ - /** - * - */ - constructor() - { - this.renderTexture = null; - this.sourceFrame = new Rectangle(); - this.destinationFrame = new Rectangle(); - this.filters = []; - this.target = null; - this.legacy = false; - this.resolution = 1; - } -} - -/** - * @class - * @memberof PIXI.systems - * @extends PIXI.systems.WebGLSystem - */ -export default class FilterSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - /** - * stores a bunch of PO2 textures used for filtering - * @type {Object} - */ - this.texturePool = {}; - - /** - * a pool for storing filter states, save us creating new ones each tick - * @type {Array} - */ - this.statePool = []; - - /** - * A very simple geometry used when drawing a filter effect to the screen - * @type {Quad} - */ - this.quad = new Quad(); - - this.quadUv = new QuadUv(); - - /** - * Temporary rect for maths - * @type {PIXI.Rectangle} - */ - this.tempRect = new Rectangle(); - - this.activeState = {}; - - /** - * this uniform group is attached to filter uniforms when used - * @type {UniformGroup} - */ - this.globalUniforms = new UniformGroup({ - sourceFrame: this.tempRect, - destinationFrame: this.tempRect, - - // legacy variables - filterArea: new Float32Array(4), - filterClamp: new Float32Array(4), - }, true); - } - - /** - * Adds a new filter to the System. - * - * @param {PIXI.DisplayObject} target - The target of the filter to render. - * @param {PIXI.Filter[]} filters - The filters to apply. - */ - push(target, filters) - { - const renderer = this.renderer; - const filterStack = this.renderer.renderTexture.defaultFilterStack; - const state = this.statePool.pop() || new FilterState(); - - let resolution = filters[0].resolution; - let padding = filters[0].padding; - let autoFit = filters[0].autoFit; - let legacy = filters[0].legacy; - - for (let i = 1; i < filters.length; i++) - { - const filter = filters[i]; - - // lets use the lowest resolution.. - resolution = Math.min(resolution, filter.resolution); - // and the largest amount of padding! - padding = Math.max(padding, filter.padding); - // only auto fit if all filters are autofit - autoFit = autoFit || filter.autoFit; - - legacy = legacy || filter.legacy; - } - - filterStack.push(state); - - state.resolution = resolution; - - state.legacy = legacy; - - // round to whole number based on resolution - // TODO move that to the shader too? - state.sourceFrame = target.filterArea || target.getBounds(true); - - state.sourceFrame.pad(padding); - - if (autoFit) - { - state.sourceFrame.fit(this.renderer.renderTexture.destinationFrame); - } - - state.sourceFrame.round(resolution); - - state.renderTexture = this.getPotFilterTexture(state.sourceFrame.width, state.sourceFrame.height, resolution); - state.filters = filters; - - state.destinationFrame.width = state.renderTexture.width; - state.destinationFrame.height = state.renderTexture.height; - - state.renderTexture.filterFrame = state.sourceFrame; - renderer.renderTexture.bind(state.renderTexture, state.sourceFrame);// /, state.destinationFrame); - renderer.renderTexture.clear(); - } - - /** - * Pops off the filter and applies it. - * - */ - pop() - { - const renderer = this.renderer; - const filterStack = renderer.renderTexture.defaultFilterStack; - const state = filterStack.pop(); - const filters = state.filters; - - this.activeState = state; - - const globalUniforms = this.globalUniforms.uniforms; - - globalUniforms.sourceFrame = state.sourceFrame; - globalUniforms.destinationFrame = state.destinationFrame; - globalUniforms.resolution = state.resolution; - - // only update the rect if its legacy.. - if (state.legacy) - { - const filterArea = globalUniforms.filterArea; - const filterClamp = globalUniforms.filterClamp; - - filterArea[0] = state.destinationFrame.width; - filterArea[1] = state.destinationFrame.height; - filterArea[2] = state.sourceFrame.x; - filterArea[3] = state.sourceFrame.y; - - filterClamp[0] = 0.5 / state.resolution / state.destinationFrame.width; - filterClamp[1] = 0.5 / state.resolution / state.destinationFrame.height; - filterClamp[2] = (state.sourceFrame.width - 0.5) / state.resolution / state.destinationFrame.width; - filterClamp[3] = (state.sourceFrame.height - 0.5) / state.resolution / state.destinationFrame.height; - } - - this.globalUniforms.update(); - - const lastState = filterStack[filterStack.length - 1]; - - if (filters.length === 1) - { - filters[0].apply(this, state.renderTexture, lastState.renderTexture, false, state); - - this.returnFilterTexture(state.renderTexture); - } - else - { - let flip = state.renderTexture; - let flop = this.getPotFilterTexture( - flip.width, - flip.height, - state.resolution - ); - - flop.filterFrame = flip.filterFrame; - - let i = 0; - - for (i = 0; i < filters.length - 1; ++i) - { - filters[i].apply(this, flip, flop, true, state); - - const t = flip; - - flip = flop; - flop = t; - } - - filters[i].apply(this, flip, lastState.renderTexture, false, state); - - this.returnFilterTexture(flip); - this.returnFilterTexture(flop); - } - - this.statePool.push(state); - } - - /** - * Draws a filter. - * - * @param {PIXI.Filter} filter - The filter to draw. - * @param {PIXI.RenderTarget} input - The input render target. - * @param {PIXI.RenderTarget} output - The target to output to. - * @param {boolean} clear - Should the output be cleared before rendering to it - */ - applyFilter(filter, input, output, clear) - { - const renderer = this.renderer; - - renderer.renderTexture.bind(output, output ? output.filterFrame : null); - - if (clear) - { - // gl.disable(gl.SCISSOR_TEST); - renderer.renderTexture.clear(); - // gl.enable(gl.SCISSOR_TEST); - } - - // set the uniforms.. - filter.uniforms.uSampler = input; - filter.uniforms.filterGlobals = this.globalUniforms; - - // TODO make it so that the order of this does not matter.. - // because it does at the moment cos of global uniforms. - // they need to get resynced - - renderer.state.setState(filter.state); - renderer.shader.bind(filter); - - if (filter.legacy) - { - this.quadUv.map(input._frame, input.filterFrame); - - renderer.geometry.bind(this.quadUv); - renderer.geometry.draw(DRAW_MODES.TRIANGLES); - } - else - { - renderer.geometry.bind(this.quad); - renderer.geometry.draw(DRAW_MODES.TRIANGLE_STRIP); - } - } - - /** - * Calculates the mapped matrix. - * - * TODO playing around here.. this is temporary - (will end up in the shader) - * this returns a matrix that will normalise map filter cords in the filter to screen space - * - * @param {PIXI.Matrix} outputMatrix - the matrix to output to. - * @return {PIXI.Matrix} The mapped matrix. - */ - calculateScreenSpaceMatrix(outputMatrix) - { - const currentState = this.activeState; - - return filterTransforms.calculateScreenSpaceMatrix( - outputMatrix, - currentState.sourceFrame, - currentState.destinationFrame - ); - } - - /** - * This will map the filter coord so that a texture can be used based on the transform of a sprite - * - * @param {PIXI.Matrix} outputMatrix - The matrix to output to. - * @param {PIXI.Sprite} sprite - The sprite to map to. - * @return {PIXI.Matrix} The mapped matrix. - */ - calculateSpriteMatrix(outputMatrix, sprite) - { - const currentState = this.activeState; - - return filterTransforms.calculateSpriteMatrix( - outputMatrix, - currentState.sourceFrame, - currentState.destinationFrame, - sprite - ); - } - - /** - * Destroys this Filter System. - * - * @param {boolean} [contextLost=false] context was lost, do not free shaders - * - */ - destroy(contextLost = false) - { - if (!contextLost) - { - this.emptyPool(); - } - else - { - this.texturePool = {}; - } - } - - /** - * Gets a Power-of-Two render texture. - * - * TODO move to a seperate class could be on renderer? - * also - could cause issue with multiple contexts? - * - * @private - * @param {WebGLRenderingContext} gl - The webgl rendering context - * @param {number} minWidth - The minimum width of the render target. - * @param {number} minHeight - The minimum height of the render target. - * @param {number} resolution - The resolution of the render target. - * @return {PIXI.RenderTarget} The new render target. - */ - getPotFilterTexture(minWidth, minHeight, resolution) - { - minWidth = bitTwiddle.nextPow2(minWidth); - minHeight = bitTwiddle.nextPow2(minHeight); - resolution = resolution || 1; - - const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); - - if (!this.texturePool[key]) - { - this.texturePool[key] = []; - } - - let renderTexture = this.texturePool[key].pop(); - - if (!renderTexture) - { - // temporary bypass cache.. - // internally - this will cause a texture to be bound.. - renderTexture = RenderTexture.create({ - width: minWidth, - height: minHeight, - resolution, - }); - } - - return renderTexture; - } - - /** - * Gets extra render texture to use inside current filter - * - * @param {number} resolution resolution of the renderTexture - * @returns {PIXI.RenderTexture} - */ - getFilterTexture(resolution) - { - const rt = this.activeState.renderTexture; - - const filterTexture = this.getPotFilterTexture(rt.width, rt.height, resolution || rt.baseTexture.resolution); - - filterTexture.filterFrame = rt.filterFrame; - - return filterTexture; - } - - /** - * Frees a render texture back into the pool. - * - * @param {PIXI.RenderTarget} renderTexture - The renderTarget to free - */ - returnFilterTexture(renderTexture) - { - renderTexture.filterFrame = null; - - const base = renderTexture.baseTexture; - - const minWidth = base.width; - const minHeight = base.height; - - const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); - - this.texturePool[key].push(renderTexture); - } - - /** - * Empties the texture pool. - * - */ - emptyPool() - { - for (const i in this.texturePool) - { - const textures = this.texturePool[i]; - - if (textures) - { - for (let j = 0; j < textures.length; j++) - { - textures[j].destroy(true); - } - } - } - - this.texturePool = {}; - } -} diff --git a/packages/core/src/renderers/systems/geometry/GLBuffer.js b/packages/core/src/renderers/systems/geometry/GLBuffer.js deleted file mode 100644 index 0477e8a..0000000 --- a/packages/core/src/renderers/systems/geometry/GLBuffer.js +++ /dev/null @@ -1,9 +0,0 @@ -export default class GLBuffer -{ - constructor(buffer) - { - this.buffer = buffer; - this.updateID = -1; - this.byteLength = -1; - } -} diff --git a/packages/core/src/renderers/systems/geometry/GeometrySystem.js b/packages/core/src/renderers/systems/geometry/GeometrySystem.js deleted file mode 100644 index 26c8e9e..0000000 --- a/packages/core/src/renderers/systems/geometry/GeometrySystem.js +++ /dev/null @@ -1,388 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; -import GLBuffer from './GLBuffer'; - -const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class GeometrySystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this._activeGeometry = null; - this._activeVao = null; - - this.hasVao = true; - this.hasInstance = true; - } - - /** - * Sets up the renderer context and necessary buffers. - * - * @private - */ - contextChange() - { - const gl = this.gl = this.renderer.gl; - - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - - // webgl2 - if (!gl.createVertexArray) - { - // webgl 1! - let nativeVaoExtension = this.renderer.context.extensions.vertexArrayObject; - - if (this.renderer.legacy) - { - nativeVaoExtension = null; - } - - if (nativeVaoExtension) - { - gl.createVertexArray = () => - nativeVaoExtension.createVertexArrayOES(); - - gl.bindVertexArray = (vao) => - nativeVaoExtension.bindVertexArrayOES(vao); - - gl.deleteVertexArray = (vao) => - nativeVaoExtension.deleteVertexArrayOES(vao); - } - else - { - this.hasVao = false; - gl.createVertexArray = () => - { - // empty - }; - - gl.bindVertexArray = () => - { - // empty - }; - - gl.deleteVertexArray = () => - { - // empty - }; - } - } - - if (!gl.vertexAttribDivisor) - { - const instanceExt = gl.getExtension('ANGLE_instanced_arrays'); - - if (instanceExt) - { - gl.vertexAttribDivisor = (a, b) => - instanceExt.vertexAttribDivisorANGLE(a, b); - - gl.drawElementsInstanced = (a, b, c, d, e) => - instanceExt.drawElementsInstancedANGLE(a, b, c, d, e); - - gl.drawArraysInstanced = (a, b, c, d) => - instanceExt.drawArraysInstancedANGLE(a, b, c, d); - } - else - { - this.hasInstance = false; - } - } - } - - /** - * Binds geometry so that is can be drawn. Creating a Vao if required - * @private - * @param {PIXI.Geometry} geometry instance of geometry to bind - */ - bind(geometry, shader) - { - shader = shader || this.renderer.shader.shader; - - const gl = this.gl; - - // not sure the best way to address this.. - // currently different shaders require different VAOs for the same geometry - // Still mulling over the best way to solve this one.. - // will likely need to modify the shader attribute locations at run time! - let vaos = geometry.glVertexArrayObjects[this.CONTEXT_UID]; - - if (!vaos) - { - geometry.glVertexArrayObjects[this.CONTEXT_UID] = vaos = {}; - } - - const vao = vaos[shader.program.id] || this.initGeometryVao(geometry, shader.program); - - this._activeGeometry = geometry; - - if (this._activeVao !== vao) - { - this._activeVao = vao; - - if (this.hasVao) - { - gl.bindVertexArray(vao); - } - else - { - this.activateVao(geometry, shader.program); - } - } - - // TODO - optimise later! - // don't need to loop through if nothing changed! - // maybe look to add an 'autoupdate' to geometry? - this.updateBuffers(); - } - - reset() - { - this.unbind(); - } - - updateBuffers() - { - const geometry = this._activeGeometry; - const gl = this.gl; - - for (let i = 0; i < geometry.buffers.length; i++) - { - const buffer = geometry.buffers[i]; - - const glBuffer = buffer._glBuffers[this.CONTEXT_UID]; - - if (buffer._updateID !== glBuffer.updateID) - { - glBuffer.updateID = buffer._updateID; - - // TODO can cache this on buffer! maybe added a getter / setter? - const type = buffer.index ? gl.ELEMENT_ARRAY_BUFFER : gl.ARRAY_BUFFER; - const drawType = buffer.static ? gl.STATIC_DRAW : gl.DYNAMIC_DRAW; - - gl.bindBuffer(type, glBuffer.buffer); - - if (glBuffer.byteLength >= buffer.data.byteLength) - { - // offset is always zero for now! - gl.bufferSubData(type, 0, buffer.data); - } - else - { - gl.bufferData(type, buffer.data, drawType); - } - } - } - } - - checkCompatability(geometry, program) - { - // geometry must have at least all the attributes that the shader requires. - const geometryAttributes = geometry.attributes; - const shaderAttributes = program.attributeData; - - for (const j in shaderAttributes) - { - if (!geometryAttributes[j]) - { - throw new Error(`shader and geometry incompatible, geometry missing the "${j}" attribute`); - } - } - } - - /** - * Creates a Vao with the same structure as the geometry and stores it on the geometry. - * @private - * @param {PIXI.Geometry} geometry instance of geometry to to generate Vao for - */ - initGeometryVao(geometry, program) - { - this.checkCompatability(geometry, program); - - const gl = this.gl; - const CONTEXT_UID = this.CONTEXT_UID; - const buffers = geometry.buffers; - const attributes = geometry.attributes; - - const tempStride = {}; - const tempStart = {}; - - for (const j in buffers) - { - tempStride[j] = 0; - tempStart[j] = 0; - } - - for (const j in attributes) - { - if (!attributes[j].size && program.attributeData[j]) - { - attributes[j].size = program.attributeData[j].size; - } - - tempStride[attributes[j].buffer] += attributes[j].size * byteSizeMap[attributes[j].type]; - } - - for (const j in attributes) - { - const attribute = attributes[j]; - const attribSize = attribute.size; - - if (attribute.stride === undefined) - { - if (tempStride[attribute.buffer] === attribSize * byteSizeMap[attribute.type]) - { - attribute.stride = 0; - } - else - { - attribute.stride = tempStride[attribute.buffer]; - } - } - - if (attribute.start === undefined) - { - attribute.start = tempStart[attribute.buffer]; - - tempStart[attribute.buffer] += attribSize * byteSizeMap[attribute.type]; - } - } - - // first update - and create the buffers! - // only create a gl buffer if it actually gets - for (let i = 0; i < buffers.length; i++) - { - const buffer = buffers[i]; - - if (!buffer._glBuffers[CONTEXT_UID]) - { - buffer._glBuffers[CONTEXT_UID] = new GLBuffer(gl.createBuffer()); - } - } - - // TODO - maybe make this a data object? - // lets wait to see if we need to first! - const vao = gl.createVertexArray(); - - gl.bindVertexArray(vao); - - this.activateVao(geometry, program); - - gl.bindVertexArray(null); - - // add it to the cache! - geometry.glVertexArrayObjects[this.CONTEXT_UID][program.id] = vao; - - return vao; - } - - activateVao(geometry, program) - { - const gl = this.gl; - const CONTEXT_UID = this.CONTEXT_UID; - const buffers = geometry.buffers; - const attributes = geometry.attributes; - - if (geometry.indexBuffer) - { - // first update the index buffer if we have one.. - gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, geometry.indexBuffer._glBuffers[CONTEXT_UID].buffer); - } - - let lastBuffer = null; - - // add a new one! - for (const j in attributes) - { - const attribute = attributes[j]; - const buffer = buffers[attribute.buffer]; - const glBuffer = buffer._glBuffers[CONTEXT_UID]; - - if (program.attributeData[j]) - { - if (lastBuffer !== glBuffer) - { - gl.bindBuffer(gl.ARRAY_BUFFER, glBuffer.buffer); - - lastBuffer = glBuffer; - } - - const location = program.attributeData[j].location; - - // TODO introduce state again - // we can optimise this for older devices that have no VAOs - gl.enableVertexAttribArray(location); - - gl.vertexAttribPointer(location, - attribute.size, - attribute.type || gl.FLOAT, - attribute.normalized, - attribute.stride, - attribute.start); - - if (attribute.instance) - { - // TODO calculate instance count based of this... - if (this.hasInstance) - { - gl.vertexAttribDivisor(location, 1); - } - else - { - throw new Error('geometry error, GPU Instancing is not supported on this device'); - } - } - } - } - } - - draw(type, size, start, instanceCount) - { - const gl = this.gl; - const geometry = this._activeGeometry; - - // TODO.. this should not change so maybe cache the function? - - if (geometry.indexBuffer) - { - if (geometry.instanced) - { - /* eslint-disable max-len */ - gl.drawElementsInstanced(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2, instanceCount || 1); - /* eslint-enable max-len */ - } - else - { - gl.drawElements(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2); - } - } - else - if (geometry.instanced) - { - // TODO need a better way to calculate size.. - gl.drawArraysInstanced(type, start, size || geometry.getSize(), instanceCount || 1); - } - else - { - gl.drawArrays(type, start, size || geometry.getSize()); - } - - return this; - } - - unbind() - { - this.gl.bindVertexArray(null); - this._activeVao = null; - this._activeGeometry = null; - } -} diff --git a/packages/core/src/renderers/systems/geometry/setVertexAttribArrays.js b/packages/core/src/renderers/systems/geometry/setVertexAttribArrays.js deleted file mode 100644 index 23b6959..0000000 --- a/packages/core/src/renderers/systems/geometry/setVertexAttribArrays.js +++ /dev/null @@ -1,54 +0,0 @@ -// var GL_MAP = {}; - -/** - * @param gl {WebGLRenderingContext} The current WebGL context - * @param attribs {*} - * @param state {*} - */ -export default function setVertexAttribArrays(gl, attribs, state) -{ - let i; - - if (state) - { - const tempAttribState = state.tempAttribState; - const attribState = state.attribState; - - for (i = 0; i < tempAttribState.length; i++) - { - tempAttribState[i] = false; - } - - // set the new attribs - for (i = 0; i < attribs.length; i++) - { - tempAttribState[attribs[i].attribute.location] = true; - } - - for (i = 0; i < attribState.length; i++) - { - if (attribState[i] !== tempAttribState[i]) - { - attribState[i] = tempAttribState[i]; - - if (state.attribState[i]) - { - gl.enableVertexAttribArray(i); - } - else - { - gl.disableVertexAttribArray(i); - } - } - } - } - else - { - for (i = 0; i < attribs.length; i++) - { - const attrib = attribs[i]; - - gl.enableVertexAttribArray(attrib.attribute.location); - } - } -} diff --git a/packages/core/src/renderers/systems/index.js b/packages/core/src/renderers/systems/index.js deleted file mode 100644 index c843f03..0000000 --- a/packages/core/src/renderers/systems/index.js +++ /dev/null @@ -1,6 +0,0 @@ -/** - * Systems are individual components to the Renderer pipeline. - * @namespace PIXI.systems - */ -export { default as WebGLSystem } from './WebGLSystem'; -export { default as FilterSystem } from './filter/FilterSystem'; diff --git a/packages/core/src/renderers/systems/shader/GLProgram.js b/packages/core/src/renderers/systems/shader/GLProgram.js deleted file mode 100644 index 92980ca..0000000 --- a/packages/core/src/renderers/systems/shader/GLProgram.js +++ /dev/null @@ -1,43 +0,0 @@ -/** - * Helper class to create a webGL Program - * - * @class - * @private - */ -export default class GLProgram -{ - constructor(program, uniformData) - { - /** - * The shader program - * - * @member {WebGLProgram} - */ - this.program = program; - - /** - * holds the uniform data which contains uniform locations - * and current uniform values used for caching and preventing undeed GPU commands - * @type {Object} - */ - this.uniformData = uniformData; - - /** - * uniformGroups holds the various upload functions for the shader. Each uniform group - * and program have a unique upload function generated. - * @type {Object} - */ - this.uniformGroups = {}; - } - - /** - * Destroys this program - * TODO - */ - destroy() - { - this.uniformData = null; - this.uniformGroups = null; - this.program = null; - } -} diff --git a/packages/core/src/renderers/systems/shader/ShaderSystem.js b/packages/core/src/renderers/systems/shader/ShaderSystem.js deleted file mode 100644 index 6cf57b6..0000000 --- a/packages/core/src/renderers/systems/shader/ShaderSystem.js +++ /dev/null @@ -1,170 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; -import GLProgram from './GLProgram'; -import generateUniformsSync from '../../../shader/generateUniformsSync'; -import defaultValue from '../../../shader/defaultValue'; -import compileProgram from './shader/compileProgram'; - -let UID = 0; - -/** - * Helper class to create a webGL Texture - * - * @class - * @memberof PIXI - */ -export default class ShaderSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - A reference to the current renderer - */ - constructor(renderer) - { - super(renderer); - - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = null; - - this.shader = null; - this.program = null; - - this.id = UID++; - } - - contextChange(gl) - { - this.gl = gl; - } - - /** - * Changes the current shader to the one given in parameter - * - * @param {PIXI.Shader} shader - the new shader - * @param {boolean} dontSync - false if the shader should automatically sync its uniforms. - * @returns {PIXI.glCore.glProgram} the glProgram that belongs to the shader. - */ - bind(shader, dontSync) - { - shader.uniforms.globals = this.renderer.globalUniforms; - - const program = shader.program; - const glProgram = program.glPrograms[this.renderer.CONTEXT_UID] || this.generateShader(shader); - - this.shader = shader; - - // TODO - some current pixi plugins bypass this.. so it not safe to use yet.. - if (this.program !== program) - { - this.program = program; - this.gl.useProgram(glProgram.program); - } - - if (!dontSync) - { - this.syncUniformGroup(shader.uniformGroup); - } - - return glProgram; - } - - /** - * Uploads the uniforms values to the currently bound shader. - * - * @param {object} uniforms - the uniforms valiues that be applied to the current shader - */ - setUniforms(uniforms) - { - const shader = this.shader.program; - const glProgram = shader.glPrograms[this.renderer.CONTEXT_UID]; - - shader.syncUniforms(glProgram.uniformData, uniforms, this.renderer); - } - - syncUniformGroup(group) - { - const glProgram = this.getglProgram(); - - if (!group.static || group.dirtyId !== glProgram.uniformGroups[group.id]) - { - glProgram.uniformGroups[group.id] = group.dirtyId; - const syncFunc = group.syncUniforms[this.shader.program.id] || this.createSyncGroups(group); - - syncFunc(glProgram.uniformData, group.uniforms, this.renderer); - } - } - - createSyncGroups(group) - { - group.syncUniforms[this.shader.program.id] = generateUniformsSync(group, this.shader.program.uniformData); - - return group.syncUniforms[this.shader.program.id]; - } - - /** - * Returns the underlying GLShade rof the currently bound shader. - * This can be handy for when you to have a little more control over the setting of your uniforms. - * - * @return {PIXI.glCore.Shader} the glProgram for the currently bound Shader for this context - */ - getglProgram() - { - if (this.shader) - { - return this.shader.program.glPrograms[this.renderer.CONTEXT_UID]; - } - - return null; - } - - /** - * Generates a glProgram verion of the Shader provided. - * - * @private - * @param {PIXI.Shader} shader the shader that the glProgram will be based on. - * @return {PIXI.glCore.glProgram} A shiney new glProgram - */ - generateShader(shader) - { - const gl = this.gl; - - const program = shader.program; - - const attribMap = {}; - - for (const i in program.attributeData) - { - attribMap[i] = program.attributeData[i].location; - } - - const shaderProgram = compileProgram(gl, program.vertexSrc, program.fragmentSrc, attribMap); - const uniformData = {}; - - for (const i in program.uniformData) - { - const data = program.uniformData[i]; - - uniformData[i] = { - location: gl.getUniformLocation(shaderProgram, i), - value: defaultValue(data.type, data.size), - }; - } - - const glProgram = new GLProgram(shaderProgram, uniformData); - - program.glPrograms[this.renderer.CONTEXT_UID] = glProgram; - - return glProgram; - } - - /** - * Destroys this System and removes all its textures - */ - destroy() - { - // TODO implement destroy method for ShaderSystem - this.destroyed = true; - } -} diff --git a/packages/core/src/renderers/systems/shader/shader/compileProgram.js b/packages/core/src/renderers/systems/shader/shader/compileProgram.js deleted file mode 100644 index 8c6ccb9..0000000 --- a/packages/core/src/renderers/systems/shader/shader/compileProgram.js +++ /dev/null @@ -1,78 +0,0 @@ -/** - * @method compileProgram - * @memberof PIXI.glCore.shader - * @param gl {WebGLRenderingContext} The current WebGL context {WebGLProgram} - * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. - * @param fragmentSrc {string|string[]} The fragment shader source as an array of strings. - * @param attributeLocations {Object} An attribute location map that lets you manually set the attribute locations - * @return {WebGLProgram} the shader program - */ -export default function compileProgram(gl, vertexSrc, fragmentSrc, attributeLocations) -{ - const glVertShader = compileShader(gl, gl.VERTEX_SHADER, vertexSrc); - const glFragShader = compileShader(gl, gl.FRAGMENT_SHADER, fragmentSrc); - - let program = gl.createProgram(); - - gl.attachShader(program, glVertShader); - gl.attachShader(program, glFragShader); - - // optionally, set the attributes manually for the program rather than letting WebGL decide.. - if (attributeLocations) - { - for (const i in attributeLocations) - { - gl.bindAttribLocation(program, attributeLocations[i], i); - } - } - - gl.linkProgram(program); - - // if linking fails, then log and cleanup - if (!gl.getProgramParameter(program, gl.LINK_STATUS)) - { - console.error('Pixi.js Error: Could not initialize shader.'); - console.error('gl.VALIDATE_STATUS', gl.getProgramParameter(program, gl.VALIDATE_STATUS)); - console.error('gl.getError()', gl.getError()); - - // if there is a program info log, log it - if (gl.getProgramInfoLog(program) !== '') - { - console.warn('Pixi.js Warning: gl.getProgramInfoLog()', gl.getProgramInfoLog(program)); - } - - gl.deleteProgram(program); - program = null; - } - - // clean up some shaders - gl.deleteShader(glVertShader); - gl.deleteShader(glFragShader); - - return program; -} - -/** - * @private - * @param gl {WebGLRenderingContext} The current WebGL context {WebGLProgram} - * @param type {Number} the type, can be either VERTEX_SHADER or FRAGMENT_SHADER - * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. - * @return {WebGLShader} the shader - */ -function compileShader(gl, type, src) -{ - const shader = gl.createShader(type); - - gl.shaderSource(shader, src); - gl.compileShader(shader); - - if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) - { - console.warn(src); - console.error(gl.getShaderInfoLog(shader)); - - return null; - } - - return shader; -} diff --git a/packages/core/src/renderers/systems/shader/shader/defaultValue.js b/packages/core/src/renderers/systems/shader/shader/defaultValue.js deleted file mode 100644 index 97290bd..0000000 --- a/packages/core/src/renderers/systems/shader/shader/defaultValue.js +++ /dev/null @@ -1,79 +0,0 @@ -/** - * @method defaultValue - * @memberof PIXI.glCore.shader - * @param type {String} Type of value - * @param size {Number} - */ -export default function defaultValue(type, size) -{ - switch (type) - { - case 'float': - return 0; - - case 'vec2': - return new Float32Array(2 * size); - - case 'vec3': - return new Float32Array(3 * size); - - case 'vec4': - return new Float32Array(4 * size); - - case 'int': - case 'sampler2D': - case 'sampler2DArray': - return 0; - - case 'ivec2': - return new Int32Array(2 * size); - - case 'ivec3': - return new Int32Array(3 * size); - - case 'ivec4': - return new Int32Array(4 * size); - - case 'bool': - return false; - - case 'bvec2': - - return booleanArray(2 * size); - - case 'bvec3': - return booleanArray(3 * size); - - case 'bvec4': - return booleanArray(4 * size); - - case 'mat2': - return new Float32Array([1, 0, - 0, 1]); - - case 'mat3': - return new Float32Array([1, 0, 0, - 0, 1, 0, - 0, 0, 1]); - - case 'mat4': - return new Float32Array([1, 0, 0, 0, - 0, 1, 0, 0, - 0, 0, 1, 0, - 0, 0, 0, 1]); - } - - return null; -} - -function booleanArray(size) -{ - const array = new Array(size); - - for (let i = 0; i < array.length; i++) - { - array[i] = false; - } - - return array; -} diff --git a/packages/core/src/renderers/systems/shader/shader/index.js b/packages/core/src/renderers/systems/shader/shader/index.js deleted file mode 100644 index ba7a317..0000000 --- a/packages/core/src/renderers/systems/shader/shader/index.js +++ /dev/null @@ -1,8 +0,0 @@ -/** - * @namespace PIXI.glCore.shader - */ -export { default as compileProgram } from './compileProgram'; -export { default as defaultValue } from './defaultValue'; -export { default as setPrecision } from './setPrecision'; -export { default as mapSize } from './mapSize'; -export { default as mapType } from './mapType'; diff --git a/packages/core/src/renderers/systems/shader/shader/mapSize.js b/packages/core/src/renderers/systems/shader/shader/mapSize.js deleted file mode 100644 index b681554..0000000 --- a/packages/core/src/renderers/systems/shader/shader/mapSize.js +++ /dev/null @@ -1,33 +0,0 @@ -const GLSL_TO_SIZE = { - float: 1, - vec2: 2, - vec3: 3, - vec4: 4, - - int: 1, - ivec2: 2, - ivec3: 3, - ivec4: 4, - - bool: 1, - bvec2: 2, - bvec3: 3, - bvec4: 4, - - mat2: 4, - mat3: 9, - mat4: 16, - - sampler2D: 1, -}; - -/** - * @method mapSize - * @memberof PIXI.glCore.shader - * @param type {String} - * @return {Number} - */ -export default function mapSize(type) -{ - return GLSL_TO_SIZE[type]; -} diff --git a/packages/core/src/renderers/systems/shader/shader/mapType.js b/packages/core/src/renderers/systems/shader/shader/mapType.js deleted file mode 100644 index d288b7c..0000000 --- a/packages/core/src/renderers/systems/shader/shader/mapType.js +++ /dev/null @@ -1,45 +0,0 @@ -let GL_TABLE = null; - -const GL_TO_GLSL_TYPES = { - FLOAT: 'float', - FLOAT_VEC2: 'vec2', - FLOAT_VEC3: 'vec3', - FLOAT_VEC4: 'vec4', - - INT: 'int', - INT_VEC2: 'ivec2', - INT_VEC3: 'ivec3', - INT_VEC4: 'ivec4', - - BOOL: 'bool', - BOOL_VEC2: 'bvec2', - BOOL_VEC3: 'bvec3', - BOOL_VEC4: 'bvec4', - - FLOAT_MAT2: 'mat2', - FLOAT_MAT3: 'mat3', - FLOAT_MAT4: 'mat4', - - SAMPLER_2D: 'sampler2D', - SAMPLER_CUBE: 'samplerCube', - SAMPLER_2D_ARRAY: 'sampler2DArray', -}; - -export default function mapType(gl, type) -{ - if (!GL_TABLE) - { - const typeNames = Object.keys(GL_TO_GLSL_TYPES); - - GL_TABLE = {}; - - for (let i = 0; i < typeNames.length; ++i) - { - const tn = typeNames[i]; - - GL_TABLE[gl[tn]] = GL_TO_GLSL_TYPES[tn]; - } - } - - return GL_TABLE[type]; -} diff --git a/packages/core/src/renderers/systems/shader/shader/setPrecision.js b/packages/core/src/renderers/systems/shader/shader/setPrecision.js deleted file mode 100644 index e2c1f34..0000000 --- a/packages/core/src/renderers/systems/shader/shader/setPrecision.js +++ /dev/null @@ -1,18 +0,0 @@ -/** - * Sets the float precision on the shader. If the precision is already present this function will do nothing - * @param {string} src the shader source - * @param {string} precision The float precision of the shader. Options are 'lowp', 'mediump' or 'highp'. - * - * @return {string} modified shader source - */ -export default function setPrecision(src, precision) -{ - src = src.trim(); - - if (src.substring(0, 9) !== 'precision' && src.substring(0, 1) !== '#') - { - return `precision ${precision} float;\n${src}`; - } - - return src; -} diff --git a/packages/core/src/renderers/systems/textures/GLTexture.js b/packages/core/src/renderers/systems/textures/GLTexture.js deleted file mode 100644 index c3fb8dc..0000000 --- a/packages/core/src/renderers/systems/textures/GLTexture.js +++ /dev/null @@ -1,33 +0,0 @@ -export default class GLTexture -{ - constructor(texture) - { - /** - * The WebGL texture - * - * @member {WebGLTexture} - */ - this.texture = texture; - - this.width = -1; - this.height = -1; - - /** - * Texture contents dirty flag - * @member {number} - */ - this.dirtyId = -1; - - /** - * Texture style dirty flag - * @type {number} - */ - this.dirtyStyleId = -1; - - /** - * Whether mip levels has to be generated - * @type {boolean} - */ - this.mipmap = false; - } -} diff --git a/packages/core/src/renderers/systems/textures/TextureGCSystem.js b/packages/core/src/renderers/systems/textures/TextureGCSystem.js deleted file mode 100644 index b089938..0000000 --- a/packages/core/src/renderers/systems/textures/TextureGCSystem.js +++ /dev/null @@ -1,110 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; -import { GC_MODES } from '@pixi/constants'; -import { settings } from '@pixi/settings'; - -/** - * TextureGarbageCollector. This class manages the GPU and ensures that it does not get clogged - * up with textures that are no longer being used. - * - * @class - * @memberof PIXI - */ -export default class TextureGCSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this.count = 0; - this.checkCount = 0; - this.maxIdle = settings.GC_MAX_IDLE; - this.checkCountMax = settings.GC_MAX_CHECK_COUNT; - this.mode = settings.GC_MODE; - } - - /** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ - postrender() - { - this.count++; - - if (this.mode === GC_MODES.MANUAL) - { - return; - } - - this.checkCount++; - - if (this.checkCount > this.checkCountMax) - { - this.checkCount = 0; - - this.run(); - } - } - - /** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ - run() - { - const tm = this.renderer.texture; - const managedTextures = tm.managedTextures; - let wasRemoved = false; - - for (let i = 0; i < managedTextures.length; i++) - { - const texture = managedTextures[i]; - - // only supports non generated textures at the moment! - if (!texture.frameBuffer && this.count - texture.touched > this.maxIdle) - { - tm.destroyTexture(texture, true); - managedTextures[i] = null; - wasRemoved = true; - } - } - - if (wasRemoved) - { - let j = 0; - - for (let i = 0; i < managedTextures.length; i++) - { - if (managedTextures[i] !== null) - { - managedTextures[j++] = managedTextures[i]; - } - } - - managedTextures.length = j; - } - } - - /** - * Removes all the textures within the specified displayObject and its children from the GPU - * - * @param {PIXI.DisplayObject} displayObject - the displayObject to remove the textures from. - */ - unload(displayObject) - { - const tm = this.renderer.textureSystem; - - // only destroy non generated textures - if (displayObject._texture && displayObject._texture._glRenderTargets) - { - tm.destroyTexture(displayObject._texture); - } - - for (let i = displayObject.children.length - 1; i >= 0; i--) - { - this.unload(displayObject.children[i]); - } - } -} diff --git a/packages/core/src/renderers/systems/textures/TextureSystem.js b/packages/core/src/renderers/systems/textures/TextureSystem.js deleted file mode 100644 index ccc5de9..0000000 --- a/packages/core/src/renderers/systems/textures/TextureSystem.js +++ /dev/null @@ -1,281 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; -import GLTexture from './GLTexture'; -import { removeItems } from '@pixi/utils'; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class TextureSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - // TODO set to max textures... - this.boundTextures = [ - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - ]; - - this.currentLocation = -1; - - this.managedTextures = []; - } - - /** - * Sets up the renderer context and necessary buffers. - * - * @private - */ - contextChange() - { - const gl = this.gl = this.renderer.gl; - - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - - // TODO move this.. to a nice make empty textures class.. - this.emptyTextures = {}; - - const emptyTexture2D = new GLTexture(gl.createTexture()); - - gl.bindTexture(gl.TEXTURE_2D, emptyTexture2D.texture); - gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array(4)); - - this.emptyTextures[gl.TEXTURE_2D] = emptyTexture2D; - this.emptyTextures[gl.TEXTURE_CUBE_MAP] = new GLTexture(gl.createTexture()); - - gl.bindTexture(gl.TEXTURE_CUBE_MAP, this.emptyTextures[gl.TEXTURE_CUBE_MAP].texture); - - let i; - - for (i = 0; i < 6; i++) - { - gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, null); - } - - gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_MAG_FILTER, gl.LINEAR); - gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_MIN_FILTER, gl.LINEAR); - - for (i = 0; i < this.boundTextures.length; i++) - { - this.bind(null, i); - } - } - - bind(texture, location) - { - const gl = this.gl; - - location = location || 0; - - if (this.currentLocation !== location) - { - this.currentLocation = location; - gl.activeTexture(gl.TEXTURE0 + location); - } - - if (texture) - { - texture = texture.baseTexture || texture; - - if (texture.valid) - { - texture.touched = this.renderer.textureGC.count; - - const glTexture = texture._glTextures[this.CONTEXT_UID] || this.initTexture(texture); - - gl.bindTexture(texture.target, glTexture.texture); - - if (glTexture.dirtyId !== texture.dirtyId) - { - this.updateTexture(texture); - } - - this.boundTextures[location] = texture; - } - } - else - { - gl.bindTexture(gl.TEXTURE_2D, this.emptyTextures[gl.TEXTURE_2D].texture); - this.boundTextures[location] = null; - } - } - - unbind(texture) - { - const gl = this.gl; - - for (let i = 0; i < this.boundTextures.length; i++) - { - if (this.boundTextures[i] === texture) - { - if (this.currentLocation !== i) - { - gl.activeTexture(gl.TEXTURE0 + i); - this.currentLocation = i; - } - - gl.bindTexture(gl.TEXTURE_2D, this.emptyTextures[texture.target].texture); - this.boundTextures[i] = null; - } - } - } - - initTexture(texture) - { - const glTexture = new GLTexture(this.gl.createTexture()); - - // guarentee an update.. - glTexture.dirtyId = -1; - - texture._glTextures[this.CONTEXT_UID] = glTexture; - - this.managedTextures.push(texture); - texture.on('dispose', this.destroyTexture, this); - - return glTexture; - } - - updateTexture(texture) - { - const glTexture = texture._glTextures[this.CONTEXT_UID]; - const renderer = this.renderer; - - if (texture.resource && texture.resource.upload(renderer, texture, glTexture)) - { - // texture is uploaded, dont do anything! - } - else - { - // default, renderTexture-like logic - const width = texture.realWidth; - const height = texture.realHeight; - const gl = renderer.gl; - - if (glTexture.width !== width - || glTexture.height !== height - || glTexture.dirtyId < 0) - { - glTexture.width = width; - glTexture.height = height; - - gl.texImage2D(texture.target, 0, - texture.format, - width, - height, - 0, - texture.format, - texture.type, - null); - } - } - - // lets only update what changes.. - if (texture.dirtyStyleId !== glTexture.dirtyStyleId) - { - this.updateTextureStyle(texture); - } - glTexture.dirtyId = texture.dirtyId; - } - - /** - * Deletes the texture from WebGL - * - * @param {PIXI.BaseTexture|PIXI.Texture} texture - the texture to destroy - * @param {boolean} [skipRemove=false] - Whether to skip removing the texture from the TextureManager. - */ - destroyTexture(texture, skipRemove) - { - const gl = this.gl; - - texture = texture.baseTexture || texture; - - if (texture._glTextures[this.renderer.CONTEXT_UID]) - { - this.unbind(texture); - - gl.deleteTexture(texture._glTextures[this.renderer.CONTEXT_UID].texture); - texture.off('dispose', this.destroyTexture, this); - - delete texture._glTextures[this.renderer.CONTEXT_UID]; - - if (!skipRemove) - { - const i = this.managedTextures.indexOf(texture); - - if (i !== -1) - { - removeItems(this.managedTextures, i, 1); - } - } - } - } - - updateTextureStyle(texture) - { - const glTexture = texture._glTextures[this.CONTEXT_UID]; - - glTexture.mipmap = texture.mipmap && texture.isPowerOfTwo; - if (!glTexture) - { - return; - } - - if (texture.resource && texture.resource.style(this.renderer, texture, glTexture)) - { - // style is set, dont do anything! - } - else - { - this.setStyle(texture, glTexture); - } - - glTexture.dirtyStyleId = texture.dirtyStyleId; - } - - setStyle(texture, glTexture) - { - const gl = this.gl; - - if (glTexture.mipmap) - { - gl.generateMipmap(texture.target); - } - - gl.texParameteri(texture.target, gl.TEXTURE_WRAP_S, texture.wrapMode); - gl.texParameteri(texture.target, gl.TEXTURE_WRAP_T, texture.wrapMode); - - if (glTexture.mipmap) - { - /* eslint-disable max-len */ - gl.texParameteri(texture.target, gl.TEXTURE_MIN_FILTER, texture.scaleMode ? gl.LINEAR_MIPMAP_LINEAR : gl.NEAREST_MIPMAP_NEAREST); - /* eslint-disable max-len */ - } - else - { - gl.texParameteri(texture.target, gl.TEXTURE_MIN_FILTER, texture.scaleMode ? gl.LINEAR : gl.NEAREST); - } - - gl.texParameteri(texture.target, gl.TEXTURE_MAG_FILTER, texture.scaleMode ? gl.LINEAR : gl.NEAREST); - } -} diff --git a/packages/core/src/renderers/utils/ObjectRenderer.js b/packages/core/src/renderers/utils/ObjectRenderer.js deleted file mode 100644 index f67a273..0000000 --- a/packages/core/src/renderers/utils/ObjectRenderer.js +++ /dev/null @@ -1,48 +0,0 @@ -import WebGLSystem from '../systems/WebGLSystem'; - -/** - * Base for a common object renderer that can be used as a system renderer plugin. - * - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class ObjectRenderer extends WebGLSystem -{ - /** - * Starts the renderer and sets the shader - * - */ - start() - { - // set the shader.. - } - - /** - * Stops the renderer - * - */ - stop() - { - this.flush(); - } - - /** - * Stub method for rendering content and emptying the current batch. - * - */ - flush() - { - // flush! - } - - /** - * Renders an object - * - * @param {PIXI.DisplayObject} object - The object to render. - */ - render(object) // eslint-disable-line no-unused-vars - { - // render the object - } -} diff --git a/packages/core/src/renderers/utils/Quad.js b/packages/core/src/renderers/utils/Quad.js deleted file mode 100644 index 5c1517b..0000000 --- a/packages/core/src/renderers/utils/Quad.js +++ /dev/null @@ -1,26 +0,0 @@ -import Geometry from '../../geometry/Geometry'; - -/** - * Helper class to create a quad - * - * @class - * @memberof PIXI - */ -export default class Quad extends Geometry -{ - /** - * just a quad - */ - constructor() - { - super(); - - this.addAttribute('aVertexPosition', [ - 0, 0, - 1, 0, - 1, 1, - 0, 1, - ]) - .addIndex([0, 1, 3, 2]); - } -} diff --git a/packages/core/src/renderers/utils/QuadUv.js b/packages/core/src/renderers/utils/QuadUv.js deleted file mode 100644 index 97bda89..0000000 --- a/packages/core/src/renderers/utils/QuadUv.js +++ /dev/null @@ -1,103 +0,0 @@ -import Geometry from '../../geometry/Geometry'; -import Buffer from '../../geometry/Buffer'; - -/** - * Helper class to create a quad with uvs like in v4 - * - * @class - * @memberof PIXI - */ -export default class QuadUv extends Geometry -{ - constructor() - { - super(); - - /** - * An array of vertices - * - * @member {Float32Array} - */ - this.vertices = new Float32Array([ - -1, -1, - 1, -1, - 1, 1, - -1, 1, - ]); - - /** - * The Uvs of the quad - * - * @member {Float32Array} - */ - this.uvs = new Float32Array([ - 0, 0, - 1, 0, - 1, 1, - 0, 1, - ]); - - this.vertexBuffer = new Buffer(this.vertices); - this.uvBuffer = new Buffer(this.uvs); - - this.addAttribute('aVertexPosition', this.vertexBuffer) - .addAttribute('aTextureCoord', this.uvBuffer) - .addIndex([0, 1, 2, 0, 2, 3]); - } - - /** - * Maps two Rectangle to the quad. - * - * @param {PIXI.Rectangle} targetTextureFrame - the first rectangle - * @param {PIXI.Rectangle} destinationFrame - the second rectangle - * @return {PIXI.Quad} Returns itself. - */ - map(targetTextureFrame, destinationFrame) - { - let x = 0; // destinationFrame.x / targetTextureFrame.width; - let y = 0; // destinationFrame.y / targetTextureFrame.height; - - this.uvs[0] = x; - this.uvs[1] = y; - - this.uvs[2] = x + (destinationFrame.width / targetTextureFrame.width); - this.uvs[3] = y; - - this.uvs[4] = x + (destinationFrame.width / targetTextureFrame.width); - this.uvs[5] = y + (destinationFrame.height / targetTextureFrame.height); - - this.uvs[6] = x; - this.uvs[7] = y + (destinationFrame.height / targetTextureFrame.height); - - x = destinationFrame.x; - y = destinationFrame.y; - - this.vertices[0] = x; - this.vertices[1] = y; - - this.vertices[2] = x + destinationFrame.width; - this.vertices[3] = y; - - this.vertices[4] = x + destinationFrame.width; - this.vertices[5] = y + destinationFrame.height; - - this.vertices[6] = x; - this.vertices[7] = y + destinationFrame.height; - - this.invalidate(); - - return this; - } - - /** - * legacy upload method, just marks buffers dirty - * @returns {PIXI.QuadUv} Returns itself. - */ - invalidate() - { - this.vertexBuffer._updateID++; - this.uvBuffer._updateID++; - - return this; - } -} diff --git a/packages/core/src/renderers/utils/checkMaxIfStatmentsInShader.js b/packages/core/src/renderers/utils/checkMaxIfStatmentsInShader.js deleted file mode 100644 index 8047c96..0000000 --- a/packages/core/src/renderers/utils/checkMaxIfStatmentsInShader.js +++ /dev/null @@ -1,58 +0,0 @@ -const fragTemplate = [ - 'precision mediump float;', - 'void main(void){', - 'float test = 0.1;', - '%forloop%', - 'gl_FragColor = vec4(0.0);', - '}', -].join('\n'); - -export default function checkMaxIfStatmentsInShader(maxIfs, gl) -{ - if (maxIfs === 0) - { - throw new Error('Invalid value of `0` passed to `checkMaxIfStatementsInShader`'); - } - - const shader = gl.createShader(gl.FRAGMENT_SHADER); - - while (true) // eslint-disable-line no-constant-condition - { - const fragmentSrc = fragTemplate.replace(/%forloop%/gi, generateIfTestSrc(maxIfs)); - - gl.shaderSource(shader, fragmentSrc); - gl.compileShader(shader); - - if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) - { - maxIfs = (maxIfs / 2) | 0; - } - else - { - // valid! - break; - } - } - - return maxIfs; -} - -function generateIfTestSrc(maxIfs) -{ - let src = ''; - - for (let i = 0; i < maxIfs; ++i) - { - if (i > 0) - { - src += '\nelse '; - } - - if (i < maxIfs - 1) - { - src += `if(test == ${i}.0){}`; - } - } - - return src; -} diff --git a/packages/core/src/renderers/utils/mapWebGLBlendModesToPixi.js b/packages/core/src/renderers/utils/mapWebGLBlendModesToPixi.js deleted file mode 100644 index 10ffe4d..0000000 --- a/packages/core/src/renderers/utils/mapWebGLBlendModesToPixi.js +++ /dev/null @@ -1,47 +0,0 @@ -import { BLEND_MODES } from '@pixi/constants'; - -/** - * Maps gl blend combinations to WebGL. - * - * @memberof PIXI - * @function mapWebGLBlendModesToPixi - * @private - * @param {WebGLRenderingContext} gl - The rendering context. - * @param {string[]} [array=[]] - The array to output into. - * @return {string[]} Mapped modes. - */ -export default function mapWebGLBlendModesToPixi(gl, array = []) -{ - // TODO - premultiply alpha would be different. - // add a boolean for that! - array[BLEND_MODES.NORMAL] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.ADD] = [gl.ONE, gl.DST_ALPHA]; - array[BLEND_MODES.MULTIPLY] = [gl.DST_COLOR, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.SCREEN] = [gl.ONE, gl.ONE_MINUS_SRC_COLOR]; - array[BLEND_MODES.OVERLAY] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.DARKEN] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.LIGHTEN] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.COLOR_DODGE] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.COLOR_BURN] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.HARD_LIGHT] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.SOFT_LIGHT] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.DIFFERENCE] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.EXCLUSION] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.HUE] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.SATURATION] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.COLOR] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.LUMINOSITY] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.NONE] = [0, 0]; - - // not-premultiplied blend modes - array[BLEND_MODES.NORMAL_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.ADD_NPM] = [gl.SRC_ALPHA, gl.DST_ALPHA, gl.ONE, gl.DST_ALPHA]; - array[BLEND_MODES.SCREEN_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_COLOR, gl.ONE, gl.ONE_MINUS_SRC_COLOR]; - - // not-premultiplied blend modes - array[BLEND_MODES.NORMAL_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.ADD_NPM] = [gl.SRC_ALPHA, gl.DST_ALPHA, gl.ONE, gl.DST_ALPHA]; - array[BLEND_MODES.SCREEN_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_COLOR, gl.ONE, gl.ONE_MINUS_SRC_COLOR]; - - return array; -} diff --git a/packages/core/src/shader/GLProgram.js b/packages/core/src/shader/GLProgram.js new file mode 100644 index 0000000..92980ca --- /dev/null +++ b/packages/core/src/shader/GLProgram.js @@ -0,0 +1,43 @@ +/** + * Helper class to create a webGL Program + * + * @class + * @private + */ +export default class GLProgram +{ + constructor(program, uniformData) + { + /** + * The shader program + * + * @member {WebGLProgram} + */ + this.program = program; + + /** + * holds the uniform data which contains uniform locations + * and current uniform values used for caching and preventing undeed GPU commands + * @type {Object} + */ + this.uniformData = uniformData; + + /** + * uniformGroups holds the various upload functions for the shader. Each uniform group + * and program have a unique upload function generated. + * @type {Object} + */ + this.uniformGroups = {}; + } + + /** + * Destroys this program + * TODO + */ + destroy() + { + this.uniformData = null; + this.uniformGroups = null; + this.program = null; + } +} diff --git a/packages/core/src/shader/Program.js b/packages/core/src/shader/Program.js index 4f9971e..4b4007a 100644 --- a/packages/core/src/shader/Program.js +++ b/packages/core/src/shader/Program.js @@ -1,6 +1,11 @@ -import * as shaderUtils from '../renderers/systems/shader/shader'; +// import * as from '../systems/shader/shader'; +import { setPrecision, + defaultValue, + compileProgram, + mapSize, + mapType, + getTestContext } from './utils'; import { ProgramCache } from '@pixi/utils'; -import getTestContext from './getTestContext'; import defaultFragment from './defaultProgram.frag'; import defaultVertex from './defaultProgram.vert'; import { settings } from '@pixi/settings'; @@ -34,8 +39,8 @@ */ this.fragmentSrc = fragmentSrc || Program.defaultFragmentSrc; - this.vertexSrc = shaderUtils.setPrecision(this.vertexSrc, settings.PRECISION_VERTEX); - this.fragmentSrc = shaderUtils.setPrecision(this.fragmentSrc, settings.PRECISION_FRAGMENT); + this.vertexSrc = setPrecision(this.vertexSrc, settings.PRECISION_VERTEX); + this.fragmentSrc = setPrecision(this.fragmentSrc, settings.PRECISION_FRAGMENT); // currently this does not extract structs only default types this.extractData(this.vertexSrc, this.fragmentSrc); @@ -62,7 +67,7 @@ if (gl) { - const program = shaderUtils.compileProgram(gl, vertexSrc, fragmentSrc); + const program = compileProgram(gl, vertexSrc, fragmentSrc); this.attributeData = this.getAttributeData(program, gl); this.uniformData = this.getUniformData(program, gl); @@ -95,13 +100,13 @@ for (let i = 0; i < totalAttributes; i++) { const attribData = gl.getActiveAttrib(program, i); - const type = shaderUtils.mapType(gl, attribData.type); + const type = mapType(gl, attribData.type); /*eslint-disable */ const data = { type: type, name: attribData.name, - size: shaderUtils.mapSize(type), + size: mapSize(type), location: 0, }; /* eslint-enable */ @@ -145,14 +150,14 @@ const name = uniformData.name.replace(/\[.*?\]/, ''); const isArray = uniformData.name.match(/\[.*?\]/, ''); - const type = shaderUtils.mapType(gl, uniformData.type); + const type = mapType(gl, uniformData.type); /*eslint-disable */ uniforms[name] = { type: type, size: uniformData.size, isArray:isArray, - value: shaderUtils.defaultValue(type, uniformData.size), + value: defaultValue(type, uniformData.size), }; /* eslint-enable */ } diff --git a/packages/core/src/shader/ShaderSystem.js b/packages/core/src/shader/ShaderSystem.js new file mode 100644 index 0000000..40b8af8 --- /dev/null +++ b/packages/core/src/shader/ShaderSystem.js @@ -0,0 +1,171 @@ +import System from '../System'; +import GLProgram from './GLProgram'; +import { generateUniformsSync, + defaultValue, + compileProgram } from './utils'; + +let UID = 0; + +/** + * Helper class to create a webGL Texture + * + * @class + * @memberof PIXI.systems + * @extends PIXI.System + */ +export default class ShaderSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - A reference to the current renderer + */ + constructor(renderer) + { + super(renderer); + + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = null; + + this.shader = null; + this.program = null; + + this.id = UID++; + } + + contextChange(gl) + { + this.gl = gl; + } + + /** + * Changes the current shader to the one given in parameter + * + * @param {PIXI.Shader} shader - the new shader + * @param {boolean} dontSync - false if the shader should automatically sync its uniforms. + * @returns {PIXI.glCore.glProgram} the glProgram that belongs to the shader. + */ + bind(shader, dontSync) + { + shader.uniforms.globals = this.renderer.globalUniforms; + + const program = shader.program; + const glProgram = program.glPrograms[this.renderer.CONTEXT_UID] || this.generateShader(shader); + + this.shader = shader; + + // TODO - some current pixi plugins bypass this.. so it not safe to use yet.. + if (this.program !== program) + { + this.program = program; + this.gl.useProgram(glProgram.program); + } + + if (!dontSync) + { + this.syncUniformGroup(shader.uniformGroup); + } + + return glProgram; + } + + /** + * Uploads the uniforms values to the currently bound shader. + * + * @param {object} uniforms - the uniforms valiues that be applied to the current shader + */ + setUniforms(uniforms) + { + const shader = this.shader.program; + const glProgram = shader.glPrograms[this.renderer.CONTEXT_UID]; + + shader.syncUniforms(glProgram.uniformData, uniforms, this.renderer); + } + + syncUniformGroup(group) + { + const glProgram = this.getglProgram(); + + if (!group.static || group.dirtyId !== glProgram.uniformGroups[group.id]) + { + glProgram.uniformGroups[group.id] = group.dirtyId; + const syncFunc = group.syncUniforms[this.shader.program.id] || this.createSyncGroups(group); + + syncFunc(glProgram.uniformData, group.uniforms, this.renderer); + } + } + + createSyncGroups(group) + { + group.syncUniforms[this.shader.program.id] = generateUniformsSync(group, this.shader.program.uniformData); + + return group.syncUniforms[this.shader.program.id]; + } + + /** + * Returns the underlying GLShade rof the currently bound shader. + * This can be handy for when you to have a little more control over the setting of your uniforms. + * + * @return {PIXI.glCore.Shader} the glProgram for the currently bound Shader for this context + */ + getglProgram() + { + if (this.shader) + { + return this.shader.program.glPrograms[this.renderer.CONTEXT_UID]; + } + + return null; + } + + /** + * Generates a glProgram verion of the Shader provided. + * + * @private + * @param {PIXI.Shader} shader the shader that the glProgram will be based on. + * @return {PIXI.glCore.glProgram} A shiney new glProgram + */ + generateShader(shader) + { + const gl = this.gl; + + const program = shader.program; + + const attribMap = {}; + + for (const i in program.attributeData) + { + attribMap[i] = program.attributeData[i].location; + } + + const shaderProgram = compileProgram(gl, program.vertexSrc, program.fragmentSrc, attribMap); + const uniformData = {}; + + for (const i in program.uniformData) + { + const data = program.uniformData[i]; + + uniformData[i] = { + location: gl.getUniformLocation(shaderProgram, i), + value: defaultValue(data.type, data.size), + }; + } + + const glProgram = new GLProgram(shaderProgram, uniformData); + + program.glPrograms[this.renderer.CONTEXT_UID] = glProgram; + + return glProgram; + } + + /** + * Destroys this System and removes all its textures + */ + destroy() + { + // TODO implement destroy method for ShaderSystem + this.destroyed = true; + } +} diff --git a/packages/core/src/shader/defaultValue.js b/packages/core/src/shader/defaultValue.js deleted file mode 100644 index 847a069..0000000 --- a/packages/core/src/shader/defaultValue.js +++ /dev/null @@ -1,78 +0,0 @@ -function booleanArray(size) -{ - const array = new Array(size); - - for (let i = 0; i < array.length; i++) - { - array[i] = false; - } - - return array; -} - -/** - * @class - * @memberof PIXI.glCore.shader - * @param type {String} Type of value - * @param size {Number} - */ -export default function defaultValue(type, size) -{ - switch (type) - { - case 'float': - return 0; - - case 'vec2': - return new Float32Array(2 * size); - - case 'vec3': - return new Float32Array(3 * size); - - case 'vec4': - return new Float32Array(4 * size); - - case 'int': - case 'sampler2D': - return 0; - - case 'ivec2': - return new Int32Array(2 * size); - - case 'ivec3': - return new Int32Array(3 * size); - - case 'ivec4': - return new Int32Array(4 * size); - - case 'bool': - return false; - - case 'bvec2': - - return booleanArray(2 * size); - - case 'bvec3': - return booleanArray(3 * size); - - case 'bvec4': - return booleanArray(4 * size); - - case 'mat2': - return new Float32Array([1, 0, - 0, 1]); - - case 'mat3': - return new Float32Array([1, 0, 0, - 0, 1, 0, - 0, 0, 1]); - - case 'mat4': - return new Float32Array([1, 0, 0, 0, - 0, 1, 0, 0, - 0, 0, 1, 0, - 0, 0, 0, 1]); - } - - return 0; -} diff --git a/packages/core/src/shader/extractUniformsFromSrc.js b/packages/core/src/shader/extractUniformsFromSrc.js deleted file mode 100644 index 3d02c0a..0000000 --- a/packages/core/src/shader/extractUniformsFromSrc.js +++ /dev/null @@ -1,57 +0,0 @@ -import defaultValue from './defaultValue'; - -export default function extractUniformsFromSrc(vertexSrc, fragmentSrc, mask) -{ - const vertUniforms = extractUniformsFromString(vertexSrc, mask); - const fragUniforms = extractUniformsFromString(fragmentSrc, mask); - - return Object.assign(vertUniforms, fragUniforms); -} - -function extractUniformsFromString(string) -{ - const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); - - const uniforms = {}; - let nameSplit; - - // clean the lines a little - remove extra spaces / teabs etc - // then split along ';' - const lines = string.replace(/\s+/g, ' ') - .split(/\s*;\s*/); - - // loop through.. - for (let i = 0; i < lines.length; i++) - { - const line = lines[i].trim(); - - if (line.indexOf('uniform') > -1) - { - const splitLine = line.split(' '); - const type = splitLine[1]; - - let name = splitLine[2]; - let size = 1; - - if (name.indexOf('[') > -1) - { - // array! - nameSplit = name.split(/\[|]/); - name = nameSplit[0]; - size *= Number(nameSplit[1]); - } - - if (!name.match(maskRegex)) - { - uniforms[name] = { - value: defaultValue(type, size), - dirtyId: 0, - name, - type, - }; - } - } - } - - return uniforms; -} diff --git a/packages/core/src/shader/generateUniformsSync.js b/packages/core/src/shader/generateUniformsSync.js deleted file mode 100644 index 98a357f..0000000 --- a/packages/core/src/shader/generateUniformsSync.js +++ /dev/null @@ -1,231 +0,0 @@ -// cv = CachedValue -// v = value -// ud = uniformData -// uv = uniformValue -// l = loaction -const GLSL_TO_SINGLE_SETTERS_CACHED = { - - float: ` - if(cv !== v) - { - cv.v = v; - gl.uniform1f(location, v) - }`, - - vec2: ` - if(cv[0] !== v[0] || cv[1] !== v[1]) - { - cv[0] = v[0]; - cv[1] = v[1]; - gl.uniform2f(location, v[0], v[1]) - }`, - - vec3: ` - if(cv[0] !== v[0] || cv[1] !== v[1] || cv[2] !== v[2]) - { - cv[0] = v[0]; - cv[1] = v[1]; - cv[2] = v[2]; - - gl.uniform3f(location, v[0], v[1], v[2]) - }`, - - vec4: 'gl.uniform4f(location, v[0], v[1], v[2], v[3])', - - int: 'gl.uniform1i(location, v)', - ivec2: 'gl.uniform2i(location, v[0], v[1])', - ivec3: 'gl.uniform3i(location, v[0], v[1], v[2])', - ivec4: 'gl.uniform4i(location, v[0], v[1], v[2], v[3])', - - bool: 'gl.uniform1i(location, v)', - bvec2: 'gl.uniform2i(location, v[0], v[1])', - bvec3: 'gl.uniform3i(location, v[0], v[1], v[2])', - bvec4: 'gl.uniform4i(location, v[0], v[1], v[2], v[3])', - - mat2: 'gl.uniformMatrix2fv(location, false, v)', - mat3: 'gl.uniformMatrix3fv(location, false, v)', - mat4: 'gl.uniformMatrix4fv(location, false, v)', - - sampler2D: 'gl.uniform1i(location, v)', - samplerCube: 'gl.uniform1i(location, v)', - sampler2DArray: 'gl.uniform1i(location, v)', -}; - -const GLSL_TO_ARRAY_SETTERS = { - - float: `gl.uniform1fv(location, v)`, - - vec2: `gl.uniform2fv(location, v)`, - vec3: `gl.uniform3fv(location, v)`, - vec4: 'gl.uniform4fv(location, v)', - - mat4: 'gl.uniformMatrix4fv(location, false, v)', - mat3: 'gl.uniformMatrix3fv(location, false, v)', - mat2: 'gl.uniformMatrix2fv(location, false, v)', - - int: 'gl.uniform1iv(location, v)', - ivec2: 'gl.uniform2iv(location, v)', - ivec3: 'gl.uniform3iv(location, v)', - ivec4: 'gl.uniform4iv(location, v)', - - bool: 'gl.uniform1iv(location, v)', - bvec2: 'gl.uniform2iv(location, v)', - bvec3: 'gl.uniform3iv(location, v)', - bvec4: 'gl.uniform4iv(location, v)', - - sampler2D: 'gl.uniform1iv(location, v)', - samplerCube: 'gl.uniform1iv(location, v)', - sampler2DArray: 'gl.uniform1iv(location, v)', -}; - -export default function generateUniformsSync(group, uniformData) -{ - let textureCount = 0; - let func = `var v = null; - var cv = null - var gl = renderer.gl`; - - for (const i in group.uniforms) - { - const data = uniformData[i]; - - if (!data) - { - if (group.uniforms[i].group) - { - func += ` - renderer.shader.syncUniformGroup(uv.${i}); - `; - } - - continue; - } - - // TODO && uniformData[i].value !== 0 <-- do we still need this? - if (data.type === 'float' && data.size === 1) - { - func += ` - if(uv.${i} !== ud.${i}.value) - { - ud.${i}.value = uv.${i} - gl.uniform1f(ud.${i}.location, uv.${i}) - }\n`; - } - /* eslint-disable max-len */ - else if ((data.type === 'sampler2D' || data.type === 'samplerCube' || data.type === 'sampler2DArray') && data.size === 1 && !data.isArray) - /* eslint-disable max-len */ - { - func += ` - renderer.texture.bind(uv.${i}, ${textureCount}); - - if(ud.${i}.value !== ${textureCount}) - { - ud.${i}.value = ${textureCount}; - gl.uniform1i(ud.${i}.location, ${textureCount});\n; // eslint-disable-line max-len - }\n`; - - textureCount++; - } - else if (data.type === 'mat3' && data.size === 1) - { - if (group.uniforms[i].a !== undefined) - { - // TODO and some smart caching dirty ids here! - func += ` - gl.uniformMatrix3fv(ud.${i}.location, false, uv.${i}.toArray(true)); - \n`; - } - else - { - func += ` - gl.uniformMatrix3fv(ud.${i}.location, false, uv.${i}); - \n`; - } - } - else if (data.type === 'vec2' && data.size === 1) - { - // TODO - do we need both here? - // maybe we can get away with only using points? - if (group.uniforms[i].x !== undefined) - { - func += ` - cv = ud.${i}.value; - v = uv.${i}; - - if(cv[0] !== v.x || cv[1] !== v.y) - { - cv[0] = v.x; - cv[1] = v.y; - gl.uniform2f(ud.${i}.location, v.x, v.y); - }\n`; - } - else - { - func += ` - cv = ud.${i}.value; - v = uv.${i}; - - if(cv[0] !== v[0] || cv[1] !== v[1]) - { - cv[0] = v[0]; - cv[1] = v[1]; - gl.uniform2f(ud.${i}.location, v[0], v[1]); - } - \n`; - } - } - else if (data.type === 'vec4' && data.size === 1) - { - // TODO - do we need both here? - // maybe we can get away with only using points? - if (group.uniforms[i].width !== undefined) - { - func += ` - cv = ud.${i}.value; - v = uv.${i}; - - if(cv[0] !== v.x || cv[1] !== v.y || cv[2] !== v.width || cv[3] !== v.height) - { - cv[0] = v.x; - cv[1] = v.y; - cv[2] = v.width; - cv[3] = v.height; - gl.uniform4f(ud.${i}.location, v.x, v.y, v.width, v.height) - }\n`; - } - else - { - func += ` - cv = ud.${i}.value; - v = uv.${i}; - - if(cv[0] !== v[0] || cv[1] !== v[1] || cv[2] !== v[2] || cv[3] !== v[3]) - { - cv[0] = v[0]; - cv[1] = v[1]; - cv[2] = v[2]; - cv[3] = v[3]; - - gl.uniform4f(ud.${i}.location, v[0], v[1], v[2], v[3]) - } - \n`; - } - } - else - { - const templateType = (data.size === 1) ? GLSL_TO_SINGLE_SETTERS_CACHED : GLSL_TO_ARRAY_SETTERS; - - const template = templateType[data.type].replace('location', `ud.${i}.location`); - - func += ` - cv = ud.${i}.value; - v = uv.${i}; - ${template};\n`; - } - } - - // console.log(' --------------- ') - // console.log(func); - - return new Function('ud', 'uv', 'renderer', func); // eslint-disable-line no-new-func -} diff --git a/packages/core/src/shader/getTestContext.js b/packages/core/src/shader/getTestContext.js deleted file mode 100644 index ea174cc..0000000 --- a/packages/core/src/shader/getTestContext.js +++ /dev/null @@ -1,48 +0,0 @@ -import { settings } from '@pixi/settings'; - -let context = null; - -/** - * returns a little webGL context to use for program inspection. - * - * @static - * @private - * @returns {webGL-context} a gl context to test with - */ -export default function getTestContext() -{ - if (!context) - { - const canvas = document.createElement('canvas'); - - let gl; - - if (settings.PREFER_WEBGL_2) - { - gl = canvas.getContext('webgl2', {}); - } - - if (!gl) - { - gl = canvas.getContext('webgl', {}) - || canvas.getContext('experimental-webgl', {}); - - if (!gl) - { - // fail, not able to get a context - throw new Error('This browser does not support webGL. Try using the canvas renderer'); - } - else - { - // for shader testing.. - gl.getExtension('WEBGL_draw_buffers'); - } - } - - context = gl; - - return gl; - } - - return context; -} diff --git a/packages/core/src/shader/utils/checkMaxIfStatmentsInShader.js b/packages/core/src/shader/utils/checkMaxIfStatmentsInShader.js new file mode 100644 index 0000000..8047c96 --- /dev/null +++ b/packages/core/src/shader/utils/checkMaxIfStatmentsInShader.js @@ -0,0 +1,58 @@ +const fragTemplate = [ + 'precision mediump float;', + 'void main(void){', + 'float test = 0.1;', + '%forloop%', + 'gl_FragColor = vec4(0.0);', + '}', +].join('\n'); + +export default function checkMaxIfStatmentsInShader(maxIfs, gl) +{ + if (maxIfs === 0) + { + throw new Error('Invalid value of `0` passed to `checkMaxIfStatementsInShader`'); + } + + const shader = gl.createShader(gl.FRAGMENT_SHADER); + + while (true) // eslint-disable-line no-constant-condition + { + const fragmentSrc = fragTemplate.replace(/%forloop%/gi, generateIfTestSrc(maxIfs)); + + gl.shaderSource(shader, fragmentSrc); + gl.compileShader(shader); + + if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) + { + maxIfs = (maxIfs / 2) | 0; + } + else + { + // valid! + break; + } + } + + return maxIfs; +} + +function generateIfTestSrc(maxIfs) +{ + let src = ''; + + for (let i = 0; i < maxIfs; ++i) + { + if (i > 0) + { + src += '\nelse '; + } + + if (i < maxIfs - 1) + { + src += `if(test == ${i}.0){}`; + } + } + + return src; +} diff --git a/packages/core/src/shader/utils/compileProgram.js b/packages/core/src/shader/utils/compileProgram.js new file mode 100644 index 0000000..8c6ccb9 --- /dev/null +++ b/packages/core/src/shader/utils/compileProgram.js @@ -0,0 +1,78 @@ +/** + * @method compileProgram + * @memberof PIXI.glCore.shader + * @param gl {WebGLRenderingContext} The current WebGL context {WebGLProgram} + * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. + * @param fragmentSrc {string|string[]} The fragment shader source as an array of strings. + * @param attributeLocations {Object} An attribute location map that lets you manually set the attribute locations + * @return {WebGLProgram} the shader program + */ +export default function compileProgram(gl, vertexSrc, fragmentSrc, attributeLocations) +{ + const glVertShader = compileShader(gl, gl.VERTEX_SHADER, vertexSrc); + const glFragShader = compileShader(gl, gl.FRAGMENT_SHADER, fragmentSrc); + + let program = gl.createProgram(); + + gl.attachShader(program, glVertShader); + gl.attachShader(program, glFragShader); + + // optionally, set the attributes manually for the program rather than letting WebGL decide.. + if (attributeLocations) + { + for (const i in attributeLocations) + { + gl.bindAttribLocation(program, attributeLocations[i], i); + } + } + + gl.linkProgram(program); + + // if linking fails, then log and cleanup + if (!gl.getProgramParameter(program, gl.LINK_STATUS)) + { + console.error('Pixi.js Error: Could not initialize shader.'); + console.error('gl.VALIDATE_STATUS', gl.getProgramParameter(program, gl.VALIDATE_STATUS)); + console.error('gl.getError()', gl.getError()); + + // if there is a program info log, log it + if (gl.getProgramInfoLog(program) !== '') + { + console.warn('Pixi.js Warning: gl.getProgramInfoLog()', gl.getProgramInfoLog(program)); + } + + gl.deleteProgram(program); + program = null; + } + + // clean up some shaders + gl.deleteShader(glVertShader); + gl.deleteShader(glFragShader); + + return program; +} + +/** + * @private + * @param gl {WebGLRenderingContext} The current WebGL context {WebGLProgram} + * @param type {Number} the type, can be either VERTEX_SHADER or FRAGMENT_SHADER + * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. + * @return {WebGLShader} the shader + */ +function compileShader(gl, type, src) +{ + const shader = gl.createShader(type); + + gl.shaderSource(shader, src); + gl.compileShader(shader); + + if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) + { + console.warn(src); + console.error(gl.getShaderInfoLog(shader)); + + return null; + } + + return shader; +} diff --git a/packages/core/src/shader/utils/defaultValue.js b/packages/core/src/shader/utils/defaultValue.js new file mode 100644 index 0000000..97290bd --- /dev/null +++ b/packages/core/src/shader/utils/defaultValue.js @@ -0,0 +1,79 @@ +/** + * @method defaultValue + * @memberof PIXI.glCore.shader + * @param type {String} Type of value + * @param size {Number} + */ +export default function defaultValue(type, size) +{ + switch (type) + { + case 'float': + return 0; + + case 'vec2': + return new Float32Array(2 * size); + + case 'vec3': + return new Float32Array(3 * size); + + case 'vec4': + return new Float32Array(4 * size); + + case 'int': + case 'sampler2D': + case 'sampler2DArray': + return 0; + + case 'ivec2': + return new Int32Array(2 * size); + + case 'ivec3': + return new Int32Array(3 * size); + + case 'ivec4': + return new Int32Array(4 * size); + + case 'bool': + return false; + + case 'bvec2': + + return booleanArray(2 * size); + + case 'bvec3': + return booleanArray(3 * size); + + case 'bvec4': + return booleanArray(4 * size); + + case 'mat2': + return new Float32Array([1, 0, + 0, 1]); + + case 'mat3': + return new Float32Array([1, 0, 0, + 0, 1, 0, + 0, 0, 1]); + + case 'mat4': + return new Float32Array([1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1]); + } + + return null; +} + +function booleanArray(size) +{ + const array = new Array(size); + + for (let i = 0; i < array.length; i++) + { + array[i] = false; + } + + return array; +} diff --git a/packages/core/src/shader/utils/extractUniformsFromSrc.js b/packages/core/src/shader/utils/extractUniformsFromSrc.js new file mode 100644 index 0000000..3d02c0a --- /dev/null +++ b/packages/core/src/shader/utils/extractUniformsFromSrc.js @@ -0,0 +1,57 @@ +import defaultValue from './defaultValue'; + +export default function extractUniformsFromSrc(vertexSrc, fragmentSrc, mask) +{ + const vertUniforms = extractUniformsFromString(vertexSrc, mask); + const fragUniforms = extractUniformsFromString(fragmentSrc, mask); + + return Object.assign(vertUniforms, fragUniforms); +} + +function extractUniformsFromString(string) +{ + const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); + + const uniforms = {}; + let nameSplit; + + // clean the lines a little - remove extra spaces / teabs etc + // then split along ';' + const lines = string.replace(/\s+/g, ' ') + .split(/\s*;\s*/); + + // loop through.. + for (let i = 0; i < lines.length; i++) + { + const line = lines[i].trim(); + + if (line.indexOf('uniform') > -1) + { + const splitLine = line.split(' '); + const type = splitLine[1]; + + let name = splitLine[2]; + let size = 1; + + if (name.indexOf('[') > -1) + { + // array! + nameSplit = name.split(/\[|]/); + name = nameSplit[0]; + size *= Number(nameSplit[1]); + } + + if (!name.match(maskRegex)) + { + uniforms[name] = { + value: defaultValue(type, size), + dirtyId: 0, + name, + type, + }; + } + } + } + + return uniforms; +} diff --git a/packages/core/src/shader/utils/generateUniformsSync.js b/packages/core/src/shader/utils/generateUniformsSync.js new file mode 100644 index 0000000..98a357f --- /dev/null +++ b/packages/core/src/shader/utils/generateUniformsSync.js @@ -0,0 +1,231 @@ +// cv = CachedValue +// v = value +// ud = uniformData +// uv = uniformValue +// l = loaction +const GLSL_TO_SINGLE_SETTERS_CACHED = { + + float: ` + if(cv !== v) + { + cv.v = v; + gl.uniform1f(location, v) + }`, + + vec2: ` + if(cv[0] !== v[0] || cv[1] !== v[1]) + { + cv[0] = v[0]; + cv[1] = v[1]; + gl.uniform2f(location, v[0], v[1]) + }`, + + vec3: ` + if(cv[0] !== v[0] || cv[1] !== v[1] || cv[2] !== v[2]) + { + cv[0] = v[0]; + cv[1] = v[1]; + cv[2] = v[2]; + + gl.uniform3f(location, v[0], v[1], v[2]) + }`, + + vec4: 'gl.uniform4f(location, v[0], v[1], v[2], v[3])', + + int: 'gl.uniform1i(location, v)', + ivec2: 'gl.uniform2i(location, v[0], v[1])', + ivec3: 'gl.uniform3i(location, v[0], v[1], v[2])', + ivec4: 'gl.uniform4i(location, v[0], v[1], v[2], v[3])', + + bool: 'gl.uniform1i(location, v)', + bvec2: 'gl.uniform2i(location, v[0], v[1])', + bvec3: 'gl.uniform3i(location, v[0], v[1], v[2])', + bvec4: 'gl.uniform4i(location, v[0], v[1], v[2], v[3])', + + mat2: 'gl.uniformMatrix2fv(location, false, v)', + mat3: 'gl.uniformMatrix3fv(location, false, v)', + mat4: 'gl.uniformMatrix4fv(location, false, v)', + + sampler2D: 'gl.uniform1i(location, v)', + samplerCube: 'gl.uniform1i(location, v)', + sampler2DArray: 'gl.uniform1i(location, v)', +}; + +const GLSL_TO_ARRAY_SETTERS = { + + float: `gl.uniform1fv(location, v)`, + + vec2: `gl.uniform2fv(location, v)`, + vec3: `gl.uniform3fv(location, v)`, + vec4: 'gl.uniform4fv(location, v)', + + mat4: 'gl.uniformMatrix4fv(location, false, v)', + mat3: 'gl.uniformMatrix3fv(location, false, v)', + mat2: 'gl.uniformMatrix2fv(location, false, v)', + + int: 'gl.uniform1iv(location, v)', + ivec2: 'gl.uniform2iv(location, v)', + ivec3: 'gl.uniform3iv(location, v)', + ivec4: 'gl.uniform4iv(location, v)', + + bool: 'gl.uniform1iv(location, v)', + bvec2: 'gl.uniform2iv(location, v)', + bvec3: 'gl.uniform3iv(location, v)', + bvec4: 'gl.uniform4iv(location, v)', + + sampler2D: 'gl.uniform1iv(location, v)', + samplerCube: 'gl.uniform1iv(location, v)', + sampler2DArray: 'gl.uniform1iv(location, v)', +}; + +export default function generateUniformsSync(group, uniformData) +{ + let textureCount = 0; + let func = `var v = null; + var cv = null + var gl = renderer.gl`; + + for (const i in group.uniforms) + { + const data = uniformData[i]; + + if (!data) + { + if (group.uniforms[i].group) + { + func += ` + renderer.shader.syncUniformGroup(uv.${i}); + `; + } + + continue; + } + + // TODO && uniformData[i].value !== 0 <-- do we still need this? + if (data.type === 'float' && data.size === 1) + { + func += ` + if(uv.${i} !== ud.${i}.value) + { + ud.${i}.value = uv.${i} + gl.uniform1f(ud.${i}.location, uv.${i}) + }\n`; + } + /* eslint-disable max-len */ + else if ((data.type === 'sampler2D' || data.type === 'samplerCube' || data.type === 'sampler2DArray') && data.size === 1 && !data.isArray) + /* eslint-disable max-len */ + { + func += ` + renderer.texture.bind(uv.${i}, ${textureCount}); + + if(ud.${i}.value !== ${textureCount}) + { + ud.${i}.value = ${textureCount}; + gl.uniform1i(ud.${i}.location, ${textureCount});\n; // eslint-disable-line max-len + }\n`; + + textureCount++; + } + else if (data.type === 'mat3' && data.size === 1) + { + if (group.uniforms[i].a !== undefined) + { + // TODO and some smart caching dirty ids here! + func += ` + gl.uniformMatrix3fv(ud.${i}.location, false, uv.${i}.toArray(true)); + \n`; + } + else + { + func += ` + gl.uniformMatrix3fv(ud.${i}.location, false, uv.${i}); + \n`; + } + } + else if (data.type === 'vec2' && data.size === 1) + { + // TODO - do we need both here? + // maybe we can get away with only using points? + if (group.uniforms[i].x !== undefined) + { + func += ` + cv = ud.${i}.value; + v = uv.${i}; + + if(cv[0] !== v.x || cv[1] !== v.y) + { + cv[0] = v.x; + cv[1] = v.y; + gl.uniform2f(ud.${i}.location, v.x, v.y); + }\n`; + } + else + { + func += ` + cv = ud.${i}.value; + v = uv.${i}; + + if(cv[0] !== v[0] || cv[1] !== v[1]) + { + cv[0] = v[0]; + cv[1] = v[1]; + gl.uniform2f(ud.${i}.location, v[0], v[1]); + } + \n`; + } + } + else if (data.type === 'vec4' && data.size === 1) + { + // TODO - do we need both here? + // maybe we can get away with only using points? + if (group.uniforms[i].width !== undefined) + { + func += ` + cv = ud.${i}.value; + v = uv.${i}; + + if(cv[0] !== v.x || cv[1] !== v.y || cv[2] !== v.width || cv[3] !== v.height) + { + cv[0] = v.x; + cv[1] = v.y; + cv[2] = v.width; + cv[3] = v.height; + gl.uniform4f(ud.${i}.location, v.x, v.y, v.width, v.height) + }\n`; + } + else + { + func += ` + cv = ud.${i}.value; + v = uv.${i}; + + if(cv[0] !== v[0] || cv[1] !== v[1] || cv[2] !== v[2] || cv[3] !== v[3]) + { + cv[0] = v[0]; + cv[1] = v[1]; + cv[2] = v[2]; + cv[3] = v[3]; + + gl.uniform4f(ud.${i}.location, v[0], v[1], v[2], v[3]) + } + \n`; + } + } + else + { + const templateType = (data.size === 1) ? GLSL_TO_SINGLE_SETTERS_CACHED : GLSL_TO_ARRAY_SETTERS; + + const template = templateType[data.type].replace('location', `ud.${i}.location`); + + func += ` + cv = ud.${i}.value; + v = uv.${i}; + ${template};\n`; + } + } + + // console.log(' --------------- ') + // console.log(func); + + return new Function('ud', 'uv', 'renderer', func); // eslint-disable-line no-new-func +} diff --git a/packages/core/src/shader/utils/getTestContext.js b/packages/core/src/shader/utils/getTestContext.js new file mode 100644 index 0000000..ea174cc --- /dev/null +++ b/packages/core/src/shader/utils/getTestContext.js @@ -0,0 +1,48 @@ +import { settings } from '@pixi/settings'; + +let context = null; + +/** + * returns a little webGL context to use for program inspection. + * + * @static + * @private + * @returns {webGL-context} a gl context to test with + */ +export default function getTestContext() +{ + if (!context) + { + const canvas = document.createElement('canvas'); + + let gl; + + if (settings.PREFER_WEBGL_2) + { + gl = canvas.getContext('webgl2', {}); + } + + if (!gl) + { + gl = canvas.getContext('webgl', {}) + || canvas.getContext('experimental-webgl', {}); + + if (!gl) + { + // fail, not able to get a context + throw new Error('This browser does not support webGL. Try using the canvas renderer'); + } + else + { + // for shader testing.. + gl.getExtension('WEBGL_draw_buffers'); + } + } + + context = gl; + + return gl; + } + + return context; +} diff --git a/packages/core/src/shader/utils/index.js b/packages/core/src/shader/utils/index.js new file mode 100644 index 0000000..4bf8dd4 --- /dev/null +++ b/packages/core/src/shader/utils/index.js @@ -0,0 +1,8 @@ +export { default as compileProgram } from './compileProgram'; +export { default as defaultValue } from './defaultValue'; +export { default as setPrecision } from './setPrecision'; +export { default as mapSize } from './mapSize'; +export { default as mapType } from './mapType'; +export { default as generateUniformsSync } from './generateUniformsSync'; +export { default as getTestContext } from './getTestContext'; +export { default as checkMaxIfStatmentsInShader } from './checkMaxIfStatmentsInShader'; diff --git a/packages/core/src/shader/utils/mapSize.js b/packages/core/src/shader/utils/mapSize.js new file mode 100644 index 0000000..b681554 --- /dev/null +++ b/packages/core/src/shader/utils/mapSize.js @@ -0,0 +1,33 @@ +const GLSL_TO_SIZE = { + float: 1, + vec2: 2, + vec3: 3, + vec4: 4, + + int: 1, + ivec2: 2, + ivec3: 3, + ivec4: 4, + + bool: 1, + bvec2: 2, + bvec3: 3, + bvec4: 4, + + mat2: 4, + mat3: 9, + mat4: 16, + + sampler2D: 1, +}; + +/** + * @method mapSize + * @memberof PIXI.glCore.shader + * @param type {String} + * @return {Number} + */ +export default function mapSize(type) +{ + return GLSL_TO_SIZE[type]; +} diff --git a/packages/core/src/shader/utils/mapType.js b/packages/core/src/shader/utils/mapType.js new file mode 100644 index 0000000..d288b7c --- /dev/null +++ b/packages/core/src/shader/utils/mapType.js @@ -0,0 +1,45 @@ +let GL_TABLE = null; + +const GL_TO_GLSL_TYPES = { + FLOAT: 'float', + FLOAT_VEC2: 'vec2', + FLOAT_VEC3: 'vec3', + FLOAT_VEC4: 'vec4', + + INT: 'int', + INT_VEC2: 'ivec2', + INT_VEC3: 'ivec3', + INT_VEC4: 'ivec4', + + BOOL: 'bool', + BOOL_VEC2: 'bvec2', + BOOL_VEC3: 'bvec3', + BOOL_VEC4: 'bvec4', + + FLOAT_MAT2: 'mat2', + FLOAT_MAT3: 'mat3', + FLOAT_MAT4: 'mat4', + + SAMPLER_2D: 'sampler2D', + SAMPLER_CUBE: 'samplerCube', + SAMPLER_2D_ARRAY: 'sampler2DArray', +}; + +export default function mapType(gl, type) +{ + if (!GL_TABLE) + { + const typeNames = Object.keys(GL_TO_GLSL_TYPES); + + GL_TABLE = {}; + + for (let i = 0; i < typeNames.length; ++i) + { + const tn = typeNames[i]; + + GL_TABLE[gl[tn]] = GL_TO_GLSL_TYPES[tn]; + } + } + + return GL_TABLE[type]; +} diff --git a/packages/core/src/shader/utils/setPrecision.js b/packages/core/src/shader/utils/setPrecision.js new file mode 100644 index 0000000..e2c1f34 --- /dev/null +++ b/packages/core/src/shader/utils/setPrecision.js @@ -0,0 +1,18 @@ +/** + * Sets the float precision on the shader. If the precision is already present this function will do nothing + * @param {string} src the shader source + * @param {string} precision The float precision of the shader. Options are 'lowp', 'mediump' or 'highp'. + * + * @return {string} modified shader source + */ +export default function setPrecision(src, precision) +{ + src = src.trim(); + + if (src.substring(0, 9) !== 'precision' && src.substring(0, 1) !== '#') + { + return `precision ${precision} float;\n${src}`; + } + + return src; +} diff --git a/packages/core/src/state/State.js b/packages/core/src/state/State.js new file mode 100644 index 0000000..fa3f221 --- /dev/null +++ b/packages/core/src/state/State.js @@ -0,0 +1,163 @@ +/* eslint-disable max-len */ + +const BLEND = 0; +const OFFSET = 1; +const CULLING = 2; +const DEPTH_TEST = 3; +const WINDING = 4; + +/** + * This is a webGL state. It is passed The WebGL StateManager. + * Each mesh renderered may require webGL to be in a different state. + * For example you may want different blend mode or to enable polygon offsets + * + * @class + * @memberof PIXI + */ +export default class State +{ + /** + * + */ + constructor() + { + this.data = 0; + + this.blendMode = 0; + this.polygonOffset = 0; + + this.blend = true; + // this.depthTest = true; + } + + /** + * Activates blending of the computed fragment color values + * + * @member {boolean} + */ + get blend() + { + return !!(this.data & (1 << BLEND)); + } + + set blend(value) // eslint-disable-line require-jsdoc + { + if (!!(this.data & (1 << BLEND)) !== value) + { + this.data ^= (1 << BLEND); + } + } + + /** + * Activates adding an offset to depth values of polygon's fragments + * + * @member {boolean} + * @default false + */ + get offsets() + { + return !!(this.data & (1 << OFFSET)); + } + + set offsets(value) // eslint-disable-line require-jsdoc + { + if (!!(this.data & (1 << OFFSET)) !== value) + { + this.data ^= (1 << OFFSET); + } + } + + /** + * Activates culling of polygons. + * + * @member {boolean} + * @default false + */ + get culling() + { + return !!(this.data & (1 << CULLING)); + } + + set culling(value) // eslint-disable-line require-jsdoc + { + if (!!(this.data & (1 << CULLING)) !== value) + { + this.data ^= (1 << CULLING); + } + } + + /** + * Activates depth comparisons and updates to the depth buffer. + * + * @member {boolean} + * @default false + */ + get depthTest() + { + return !!(this.data & (1 << DEPTH_TEST)); + } + + set depthTest(value) // eslint-disable-line require-jsdoc + { + if (!!(this.data & (1 << DEPTH_TEST)) !== value) + { + this.data ^= (1 << DEPTH_TEST); + } + } + + /** + * Specifies whether or not front or back-facing polygons can be culled. + * @member {boolean} + * @default false + */ + get clockwiseFrontFace() + { + return !!(this.data & (1 << WINDING)); + } + + set clockwiseFrontFace(value) // eslint-disable-line require-jsdoc + { + if (!!(this.data & (1 << WINDING)) !== value) + { + this.data ^= (1 << WINDING); + } + } + + /** + * The blend mode to be applied when this state is set. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. + * Setting this mode to anything other than NO_BLEND will automatically switch blending on. + * + * @member {boolean} + * @default PIXI.BLEND_MODES.NORMAL + * @see PIXI.BLEND_MODES + */ + get blendMode() + { + return this._blendMode; + } + + set blendMode(value) // eslint-disable-line require-jsdoc + { + // 17 is NO BLEND + this.blend = (value !== 17); + this._blendMode = value; + } + + /** + * The polygon offset. Setting this property to anything other than 0 will automatically enable poygon offset fill. + * + * @member {number} + * @default 0 + */ + get polygonOffset() + { + return this._polygonOffset; + } + + set polygonOffset(value) // eslint-disable-line require-jsdoc + { + this.offsets = !!value; + this._polygonOffset = value; + } +} + diff --git a/packages/core/src/state/StateSystem.js b/packages/core/src/state/StateSystem.js new file mode 100755 index 0000000..39c35bb --- /dev/null +++ b/packages/core/src/state/StateSystem.js @@ -0,0 +1,288 @@ +import mapWebGLBlendModesToPixi from './utils/mapWebGLBlendModesToPixi'; +import System from '../System'; +import WebGLState from './State'; + +const BLEND = 0; +const OFFSET = 1; +const CULLING = 2; +const DEPTH_TEST = 3; +const WINDING = 4; + +/** + * A WebGL state machines + * + * @class + * @extends PIXI.systems.System + * @memberof PIXI.systems + */ +export default class StateSystem extends System +{ + /** + * @param {WebGLRenderingContext} gl - The current WebGL rendering context + */ + constructor(renderer) + { + super(renderer); + + this.gl = null; + + this.maxAttribs = null; + + // check we have vao.. + this.nativeVaoExtension = null; + + this.attribState = null; + + this.stateId = 0; + this.polygonOffset = 0; + this.blendMode = 17; + + this.map = []; + + // map functions for when we set state.. + this.map[BLEND] = this.setBlend; + this.map[OFFSET] = this.setOffset; + this.map[CULLING] = this.setCullFace; + this.map[DEPTH_TEST] = this.setDepthTest; + this.map[WINDING] = this.setFrontFace; + + this.checks = []; + + this.defaultState = new WebGLState(); + this.defaultState.blend = true; + this.defaultState.depth = true; + } + + contextChange(gl) + { + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); + + // check we have vao.. + this.nativeVaoExtension = ( + gl.getExtension('OES_vertex_array_object') + || gl.getExtension('MOZ_OES_vertex_array_object') + || gl.getExtension('WEBKIT_OES_vertex_array_object') + ); + + this.attribState = { + tempAttribState: new Array(this.maxAttribs), + attribState: new Array(this.maxAttribs), + }; + + this.blendModes = mapWebGLBlendModesToPixi(gl); + + this.setState(this.defaultState); + + this.reset(); + } + + /** + * Sets the current state + * + * @param {*} state - The state to set. + */ + setState(state) + { + state = state || this.defaultState; + + // TODO maybe to an object check? ( this.state === state )? + if (this.stateId !== state.data) + { + let diff = this.stateId ^ state.data; + let i = 0; + + // order from least to most common + while (diff) + { + if (diff & 1) + { + // state change! + this.map[i].call(this, !!(state.data & (1 << i))); + } + + diff = diff >> 1; + i++; + } + + this.stateId = state.data; + } + + // based on the above settings we check for specific modes.. + // for example if blend is active we check and set the blend modes + // or of polygon offset is active we check the poly depth. + for (let i = 0; i < this.checks.length; i++) + { + this.checks[i](this, state); + } + } + + /** + * Enables or disabled blending. + * + * @param {boolean} value - Turn on or off webgl blending. + */ + setBlend(value) + { + this.updateCheck(StateSystem.checkBlendMode, value); + + this.gl[value ? 'enable' : 'disable'](this.gl.BLEND); + } + + /** + * Enables or disable polygon offset fill + * + * @param {boolean} value - Turn on or off webgl polygon offset testing. + */ + setOffset(value) + { + this.gl[value ? 'enable' : 'disable'](this.gl.POLYGON_OFFSET_FILL); + } + + /** + * Sets whether to enable or disable depth test. + * + * @param {boolean} value - Turn on or off webgl depth testing. + */ + setDepthTest(value) + { + this.gl[value ? 'enable' : 'disable'](this.gl.DEPTH_TEST); + } + + /** + * Sets whether to enable or disable cull face. + * + * @param {boolean} value - Turn on or off webgl cull face. + */ + setCullFace(value) + { + this.gl[value ? 'enable' : 'disable'](this.gl.CULL_FACE); + } + + /** + * Sets the gl front face. + * + * @param {boolean} value - true is clockwise and false is counter-clockwise + */ + setFrontFace(value) + { + this.gl.frontFace(this.gl[value ? 'CW' : 'CCW']); + } + + /** + * Sets the blend mode. + * + * @param {number} value - The blend mode to set to. + */ + setBlendMode(value) + { + if (value === this.blendMode) + { + return; + } + + this.blendMode = value; + this.gl.blendFunc(this.blendModes[value][0], this.blendModes[value][1]); + } + + /** + * Sets the polygon offset. + * + * @param {number} value - the polygon offset + * @param {number} scale - the polygon offset scale + */ + setPolygonOffset(value, scale) + { + this.gl.polygonOffset(value, scale); + } + + /** + * Disables all the vaos in use + * + */ + resetAttributes() + { + for (let i = 0; i < this.attribState.tempAttribState.length; i++) + { + this.attribState.tempAttribState[i] = 0; + } + + for (let i = 0; i < this.attribState.attribState.length; i++) + { + this.attribState.attribState[i] = 0; + } + + // im going to assume one is always active for performance reasons. + for (let i = 1; i < this.maxAttribs; i++) + { + this.gl.disableVertexAttribArray(i); + } + } + + // used + /** + * Resets all the logic and disables the vaos + */ + reset() + { + // unbind any VAO if they exist.. + if (this.nativeVaoExtension) + { + this.nativeVaoExtension.bindVertexArrayOES(null); + } + + // reset all attributes.. + this.resetAttributes(); + + this.gl.pixelStorei(this.gl.UNPACK_FLIP_Y_WEBGL, false); + + this.setBlendMode(0); + + // TODO? + // this.setState(this.defaultState); + } + + /** + * checks to see which updates should be checked based on which settings have been activated + * for example if blend is enabled then we shold check the blend modes each time the state is changed + * or if poygon fill is activated then we need to check if the polygone offset changes. + * The idea is that we only check what we have too. + * + * @param {Function} func the checking function to add or remove + * @param {boolean} value should the check function be added or removed. + */ + updateCheck(func, value) + { + const index = this.checks.indexOf(func); + + if (value && index === -1) + { + this.checks.push(func); + } + else if (!value && index !== -1) + { + this.checks.splice(index, 1); + } + } + + /** + * A private little wrapper function that we call to check the blend mode. + * + * @static + * @private + * @param {PIXI.StateSystem} System the System to perform the state check on + * @param {PIXI.State} state the state that the blendMode will pulled from + */ + static checkBlendMode(system, state) + { + system.setBlendMode(state.blendMode); + } + + // TODO - add polygon offset? +} diff --git a/packages/core/src/state/utils/mapWebGLBlendModesToPixi.js b/packages/core/src/state/utils/mapWebGLBlendModesToPixi.js new file mode 100644 index 0000000..10ffe4d --- /dev/null +++ b/packages/core/src/state/utils/mapWebGLBlendModesToPixi.js @@ -0,0 +1,47 @@ +import { BLEND_MODES } from '@pixi/constants'; + +/** + * Maps gl blend combinations to WebGL. + * + * @memberof PIXI + * @function mapWebGLBlendModesToPixi + * @private + * @param {WebGLRenderingContext} gl - The rendering context. + * @param {string[]} [array=[]] - The array to output into. + * @return {string[]} Mapped modes. + */ +export default function mapWebGLBlendModesToPixi(gl, array = []) +{ + // TODO - premultiply alpha would be different. + // add a boolean for that! + array[BLEND_MODES.NORMAL] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; + array[BLEND_MODES.ADD] = [gl.ONE, gl.DST_ALPHA]; + array[BLEND_MODES.MULTIPLY] = [gl.DST_COLOR, gl.ONE_MINUS_SRC_ALPHA]; + array[BLEND_MODES.SCREEN] = [gl.ONE, gl.ONE_MINUS_SRC_COLOR]; + array[BLEND_MODES.OVERLAY] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; + array[BLEND_MODES.DARKEN] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; + array[BLEND_MODES.LIGHTEN] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; + array[BLEND_MODES.COLOR_DODGE] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; + array[BLEND_MODES.COLOR_BURN] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; + array[BLEND_MODES.HARD_LIGHT] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; + array[BLEND_MODES.SOFT_LIGHT] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; + array[BLEND_MODES.DIFFERENCE] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; + array[BLEND_MODES.EXCLUSION] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; + array[BLEND_MODES.HUE] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; + array[BLEND_MODES.SATURATION] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; + array[BLEND_MODES.COLOR] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; + array[BLEND_MODES.LUMINOSITY] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; + array[BLEND_MODES.NONE] = [0, 0]; + + // not-premultiplied blend modes + array[BLEND_MODES.NORMAL_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; + array[BLEND_MODES.ADD_NPM] = [gl.SRC_ALPHA, gl.DST_ALPHA, gl.ONE, gl.DST_ALPHA]; + array[BLEND_MODES.SCREEN_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_COLOR, gl.ONE, gl.ONE_MINUS_SRC_COLOR]; + + // not-premultiplied blend modes + array[BLEND_MODES.NORMAL_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; + array[BLEND_MODES.ADD_NPM] = [gl.SRC_ALPHA, gl.DST_ALPHA, gl.ONE, gl.DST_ALPHA]; + array[BLEND_MODES.SCREEN_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_COLOR, gl.ONE, gl.ONE_MINUS_SRC_COLOR]; + + return array; +} diff --git a/packages/core/src/systems.js b/packages/core/src/systems.js new file mode 100644 index 0000000..8edd9d5 --- /dev/null +++ b/packages/core/src/systems.js @@ -0,0 +1,17 @@ +/** + * Systems are individual components to the Renderer pipeline. + * @namespace PIXI.systems + */ +export { default as FilterSystem } from './filters/FilterSystem'; +export { default as BatchSystem } from './batch/BatchSystem'; +export { default as ContextSystem } from './context/ContextSystem'; +export { default as FramebufferSystem } from './framebuffer/FramebufferSystem'; +export { default as GeometrySystem } from './geometry/GeometrySystem'; +export { default as MaskSystem } from './mask/MaskSystem'; +export { default as StencilSystem } from './mask/StencilSystem'; +export { default as ProjectionSystem } from './projection/ProjectionSystem'; +export { default as RenderTextureSystem } from './renderTexture/RenderTextureSystem'; +export { default as ShaderSystem } from './shader/ShaderSystem'; +export { default as StateSystem } from './state/StateSystem'; +export { default as TextureGCSystem } from './textures/TextureGCSystem'; +export { default as TextureSystem } from './textures/TextureSystem'; diff --git a/packages/core/src/textures/BaseRenderTexture.js b/packages/core/src/textures/BaseRenderTexture.js index 0d4611e..77c44f5 100644 --- a/packages/core/src/textures/BaseRenderTexture.js +++ b/packages/core/src/textures/BaseRenderTexture.js @@ -1,5 +1,5 @@ import BaseTexture from './BaseTexture'; -import FrameBuffer from './FrameBuffer'; +import FrameBuffer from '../framebuffer/FrameBuffer'; /** * A BaseRenderTexture is a special texture that allows any PixiJS display object to be rendered to it. diff --git a/packages/core/src/textures/FrameBuffer.js b/packages/core/src/textures/FrameBuffer.js deleted file mode 100644 index 58c8f39..0000000 --- a/packages/core/src/textures/FrameBuffer.js +++ /dev/null @@ -1,106 +0,0 @@ -import Texture from './BaseTexture'; -import { FORMATS, TYPES } from '@pixi/constants'; - -/** - * Frame buffer - * @class - * @memberof PIXI - */ -export default class FrameBuffer -{ - constructor(width, height) - { - this.width = width || 100; - this.height = height || 100; - - this.stencil = false; - this.depth = false; - - this.dirtyId = 0; - this.dirtyFormat = 0; - this.dirtySize = 0; - - this.depthTexture = null; - this.colorTextures = []; - - this.glFrameBuffers = {}; - } - - get colorTexture() - { - return this.colorTextures[0]; - } - - addColorTexture(index, texture) - { - // TODO add some validation to the texture - same width / height etc? - this.colorTextures[index || 0] = texture || new Texture(null, { scaleMode: 0, - resolution: 1, - mipmap: false, - width: this.width, - height: this.height });// || new Texture(); - - this.dirtyId++; - this.dirtyFormat++; - - return this; - } - - addDepthTexture(texture) - { - /* eslint-disable max-len */ - this.depthTexture = texture || new Texture(null, { scaleMode: 0, - resolution: 1, - width: this.width, - height: this.height, - mipmap: false, - format: FORMATS.DEPTH_COMPONENT, - type: TYPES.UNSIGNED_SHORT });// UNSIGNED_SHORT; - /* eslint-disable max-len */ - this.dirtyId++; - this.dirtyFormat++; - - return this; - } - - enableDepth() - { - this.depth = true; - - this.dirtyId++; - this.dirtyFormat++; - - return this; - } - - enableStencil() - { - this.stencil = true; - - this.dirtyId++; - this.dirtyFormat++; - - return this; - } - - resize(width, height) - { - if (width === this.width && height === this.height) return; - - this.width = width; - this.height = height; - - this.dirtyId++; - this.dirtySize++; - - for (let i = 0; i < this.colorTextures.length; i++) - { - this.colorTextures[i].setSize(width, height); - } - - if (this.depthTexture) - { - this.depthTexture.setSize(width, height); - } - } -} diff --git a/packages/core/src/textures/GLTexture.js b/packages/core/src/textures/GLTexture.js new file mode 100644 index 0000000..c3fb8dc --- /dev/null +++ b/packages/core/src/textures/GLTexture.js @@ -0,0 +1,33 @@ +export default class GLTexture +{ + constructor(texture) + { + /** + * The WebGL texture + * + * @member {WebGLTexture} + */ + this.texture = texture; + + this.width = -1; + this.height = -1; + + /** + * Texture contents dirty flag + * @member {number} + */ + this.dirtyId = -1; + + /** + * Texture style dirty flag + * @type {number} + */ + this.dirtyStyleId = -1; + + /** + * Whether mip levels has to be generated + * @type {boolean} + */ + this.mipmap = false; + } +} diff --git a/packages/core/src/AbstractRenderer.js b/packages/core/src/AbstractRenderer.js new file mode 100644 index 0000000..64b2eb1 --- /dev/null +++ b/packages/core/src/AbstractRenderer.js @@ -0,0 +1,353 @@ +import { hex2string, hex2rgb } from '@pixi/utils'; +import { Matrix, Rectangle } from '@pixi/math'; +import { RENDERER_TYPE } from '@pixi/constants'; +import { settings } from '@pixi/settings'; +import { Container } from '@pixi/display'; +import RenderTexture from './renderTexture/RenderTexture'; +import EventEmitter from 'eventemitter3'; + +const tempMatrix = new Matrix(); + +/** + * 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 EventEmitter + * @memberof PIXI + */ +export default class AbstractRenderer extends EventEmitter +{ + // eslint-disable-next-line valid-jsdoc + /** + * @param {string} system - The name of the system this renderer is for. + * @param {object} [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.transparent=false] - If the render view is transparent, default false + * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false + * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. The + * resolution of the renderer retina would be 2. + * @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 {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when rendering, + * stopping pixel interpolation. + */ + constructor(system, options, arg2, arg3) + { + super(); + + // Support for constructor(system, screenWidth, screenHeight, options) + if (typeof options === 'number') + { + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + }, arg3); + } + + // 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 = RENDERER_TYPE.UNKNOWN; + + /** + * Measurements of the screen. (0, 0, screenWidth, screenHeight) + * + * Its safe to use as filterArea or hitArea for 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 1 + */ + this.resolution = options.resolution || settings.RESOLUTION; + + /** + * Whether the render view is transparent + * + * @member {boolean} + */ + this.transparent = options.transparent; + + /** + * Whether css dimensions of canvas view should be resized to screen dimensions automatically + * + * @member {boolean} + */ + this.autoResize = options.autoResize || false; + + /** + * Tracks the blend modes useful for this renderer. + * + * @member {object} + */ + this.blendModes = null; + + /** + * 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; + + /** + * If true PixiJS will Math.floor() x/y values when rendering, stopping pixel interpolation. + * Handy for crisp pixel art and speed on legacy devices. + * + * @member {boolean} + */ + this.roundPixels = options.roundPixels; + + /** + * The background color as a number. + * + * @member {number} + * @private + */ + this._backgroundColor = 0x000000; + + /** + * The background color as an [R, G, B] array. + * + * @member {number[]} + * @private + */ + this._backgroundColorRgba = [0, 0, 0, 0]; + + /** + * The background color as a string. + * + * @member {string} + * @private + */ + this._backgroundColorString = '#000000'; + + this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter + + /** + * This temporary display object used as the parent of the currently being rendered item + * + * @member {PIXI.DisplayObject} + * @private + */ + this._tempDisplayObjectParent = new Container(); + + /** + * The last root object that the renderer tried to render. + * + * @member {PIXI.DisplayObject} + * @private + */ + this._lastObjectRendered = this._tempDisplayObjectParent; + + /** + * Collection of plugins. + * @readonly + * @member {object} + */ + this.plugins = {}; + } + + /** + * Initialize the plugins. + * + * @protected + * @param {object} staticMap - The dictionary of staticly saved plugins. + */ + initPlugins(staticMap) + { + 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() + { + return this.view.width; + } + + /** + * Same as view.height, actual number of pixels in the canvas by vertical + * + * @member {number} + * @readonly + * @default 600 + */ + get height() + { + return this.view.height; + } + + /** + * Resizes the screen and canvas to the specified width and height + * Canvas dimensions are multiplied by resolution + * + * @param {number} screenWidth - the new width of the screen + * @param {number} screenHeight - the new height of the screen + */ + resize(screenWidth, screenHeight) + { + this.screen.width = screenWidth; + this.screen.height = screenHeight; + + this.view.width = screenWidth * this.resolution; + this.view.height = screenHeight * this.resolution; + + if (this.autoResize) + { + this.view.style.width = `${screenWidth}px`; + this.view.style.height = `${screenHeight}px`; + } + } + + /** + * 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 {PIXI.DisplayObject} displayObject - The displayObject the object will be generated from + * @param {number} scaleMode - Should be one of the scaleMode consts + * @param {number} resolution - The resolution / device pixel ratio of the texture being generated + * @param {PIXI.Rectangle} [region] - The region of the displayObject, that shall be rendered, + * if no region is specified, defaults to the local bounds of the displayObject. + * @return {PIXI.Texture} a texture of the graphics object + */ + generateTexture(displayObject, scaleMode, resolution, region) + { + region = region || displayObject.getLocalBounds(); + + // 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(region.width | 0, region.height | 0, scaleMode, resolution); + + tempMatrix.tx = -region.x; + tempMatrix.ty = -region.y; + + this.render(displayObject, renderTexture, false, tempMatrix, true); + + return renderTexture; + } + + /** + * Removes everything from the renderer and optionally removes the Canvas DOM element. + * + * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. + */ + destroy(removeView) + { + 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); + } + + this.plugins = null; + + this.type = RENDERER_TYPE.UNKNOWN; + + this.view = null; + + this.screen = null; + + this.resolution = 0; + + this.transparent = false; + + this.autoResize = false; + + this.blendModes = null; + + this.options = null; + + this.preserveDrawingBuffer = false; + this.clearBeforeRender = false; + + this.roundPixels = false; + + this._backgroundColor = 0; + this._backgroundColorRgba = null; + this._backgroundColorString = null; + + this._tempDisplayObjectParent = null; + this._lastObjectRendered = null; + } + + /** + * The background color to fill if not transparent + * + * @member {number} + */ + get backgroundColor() + { + return this._backgroundColor; + } + + set backgroundColor(value) // eslint-disable-line require-jsdoc + { + this._backgroundColor = value; + this._backgroundColorString = hex2string(value); + hex2rgb(value, this._backgroundColorRgba); + } +} diff --git a/packages/core/src/Renderer.js b/packages/core/src/Renderer.js new file mode 100644 index 0000000..2227e78 --- /dev/null +++ b/packages/core/src/Renderer.js @@ -0,0 +1,338 @@ +import AbstractRenderer from './AbstractRenderer'; +import { sayHello } from '@pixi/utils'; +import MaskSystem from './mask/MaskSystem'; +import StencilSystem from './mask/StencilSystem'; +import FilterSystem from './filters/FilterSystem'; +import FramebufferSystem from './framebuffer/FramebufferSystem'; +import RenderTextureSystem from './renderTexture/RenderTextureSystem'; +import TextureSystem from './textures/TextureSystem'; +import ProjectionSystem from './projection/ProjectionSystem'; +import StateSystem from './state/StateSystem'; +import GeometrySystem from './geometry/GeometrySystem'; +import ShaderSystem from './shader/ShaderSystem'; +import ContextSystem from './context/ContextSystem'; +import BatchSystem from './batch/BatchSystem'; +import TextureGCSystem from './textures/TextureGCSystem'; +import { RENDERER_TYPE } from '@pixi/constants'; +import UniformGroup from './shader/UniformGroup'; +import { Matrix } from '@pixi/math'; +import Runner from 'mini-runner'; + +/** + * 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 Render works by automatically managing webGLBatchs. + * 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 :) + * + * @class + * @memberof PIXI + * @extends PIXI.AbstractRenderer + */ +export default class Renderer extends AbstractRenderer +{ + // eslint-disable-next-line valid-jsdoc + /** + * + * @param {object} [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.transparent=false] - If the render view is transparent, default false + * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false + * @param {boolean} [options.antialias=false] - sets antialias. If not available natively then FXAA + * antialiasing is used + * @param {boolean} [options.forceFXAA=false] - forces FXAA antialiasing to be used over native. + * FXAA is faster, but may not always look as great + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. + * The resolution of the renderer retina would be 2. + * @param {boolean} [options.clearBeforeRender=true] - This sets if the renderer will clear + * the canvas or not before the new render pass. If you wish to set this to false, you *must* set + * preserveDrawingBuffer to `true`. + * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, + * enable this if you need to call toDataUrl on the webgl context. + * @param {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when + * rendering, stopping pixel interpolation. + * @param {number} [options.backgroundColor=0x000000] - The background color of the rendered area + * (shown if not transparent). + * @param {boolean} [options.legacy=false] - If true PixiJS will aim to ensure compatibility + * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. + * @param {string} [options.powerPreference] - Parameter passed to webgl context, set to "high-performance" + * for devices with dual graphics card + */ + constructor(options, arg2, arg3) + { + super('WebGL', options, arg2, arg3); + + /** + * The type of this renderer as a standardised const + * + * @member {number} + * @see PIXI.RENDERER_TYPE + */ + this.type = RENDERER_TYPE.WEBGL; + + // this will be set by the contextSystem (this.context) + this.gl = null; + this.CONTEXT_UID = 0; + this.legacy = !!options.legacy; + + // TODO legacy! + + // runners! + this.runners = { + destroy: new Runner('destroy'), + contextChange: new Runner('contextChange', 1), + reset: new Runner('reset'), + update: new Runner('update'), + postrender: new Runner('postrender'), + prerender: new Runner('prerender'), + resize: new Runner('resize', 2), + }; + + 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(GeometrySystem, 'geometry') + .addSystem(FramebufferSystem, 'framebuffer') + .addSystem(StencilSystem, 'stencil') + .addSystem(ProjectionSystem, 'projection') + .addSystem(TextureGCSystem, 'textureGC') + .addSystem(FilterSystem, 'filter') + .addSystem(RenderTextureSystem, 'renderTexture') + .addSystem(BatchSystem, 'batch'); + + this.initPlugins(Renderer.__plugins); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ + if (options.context) + { + this.context.initFromContext(options.context); + } + else + { + this.context.initFromOptions({ + alpha: this.transparent, + antialias: options.antialias, + premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', + stencil: true, + 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); + } + + /** + * Add a new system to the renderer. + * @param {class} ClassRef - Class reference + * @param {string} [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. + * @return {PIXI.Renderer} Return instance of renderer + */ + addSystem(ClassRef, name) + { + if (!name) + { + name = ClassRef.name; + } + + // TODO - read name from class.name.. + + /* + if(name.includes('System')) + { + name = name.replace('System', ''); + name = name.charAt(0).toLowerCase() + name.slice(1); + } + */ + + const system = new ClassRef(this); + + if (this[name]) + { + throw new Error(`Whoops! ${name} is already a manger`); + } + + this[name] = system; + + for (const i in this.runners) + { + this.runners[i].add(system); + } + + return this; + + /** + * 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. + */ + } + + /** + * Renders the object to its webGL view + * + * @param {PIXI.DisplayObject} displayObject - the object to be rendered + * @param {PIXI.RenderTexture} renderTexture - The render texture to render to. + * @param {boolean} [clear] - Should the canvas be cleared before the new render + * @param {PIXI.Transform} [transform] - A transform to apply to the render texture before rendering. + * @param {boolean} [skipUpdateTransform] - Should we skip the update transform pass? + */ + render(displayObject, renderTexture, clear, transform, skipUpdateTransform) + { + // can be handy to know! + this.renderingToScreen = !renderTexture; + + this.runners.prerender.run(); + this.emit('prerender'); + + // 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.parent; + + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = 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.run(); + + this.emit('postrender'); + } + + /** + * Resizes the webGL view to the specified width and height. + * + * @param {number} screenWidth - the new width of the screen + * @param {number} screenHeight - the new height of the screen + */ + resize(screenWidth, screenHeight) + { + AbstractRenderer.prototype.resize.call(this, screenWidth, screenHeight); + + this.runners.resize.run(screenWidth, screenHeight); + } + + /** + * Resets the WebGL state so you can render things however you fancy! + * + * @return {PIXI.Renderer} Returns itself. + */ + reset() + { + this.runners.reset.run(); + + return this; + } + + clear() + { + this.framebuffer.bind(); + this.framebuffer.clear(); + } + + /** + * Removes everything from the renderer (event listeners, spritebatch, etc...) + * + * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. + * See: https://github.com/pixijs/pixi.js/issues/2233 + */ + destroy(removeView) + { + this.runners.destroy.run(); + + // call base destroy + super.destroy(removeView); + + // TODO nullify all the managers.. + this.gl = null; + } + + /** + * 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 + * builds and excluding plugins. + * @name PIXI.Renderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. + */ + + /** + * Adds a plugin to the renderer. + * + * @method + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + static registerPlugin(pluginName, ctor) + { + Renderer.__plugins = Renderer.__plugins || {}; + Renderer.__plugins[pluginName] = ctor; + } +} diff --git a/packages/core/src/System.js b/packages/core/src/System.js new file mode 100644 index 0000000..05a10c8 --- /dev/null +++ b/packages/core/src/System.js @@ -0,0 +1,42 @@ +/** + * System is a base class used for extending systems used by the {@link PIXI.Renderer} + * @see PIXI.Renderer#addSystem + * @class + * @memberof PIXI + */ +export default class System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + /** + * The renderer this manager works for. + * + * @member {PIXI.Renderer} + */ + this.renderer = renderer; + + this.renderer.runners.contextChange.add(this); + } + + /** + * Generic method called when there is a WebGL context change. + * + */ + contextChange() + { + // do some codes init! + } + + /** + * Generic destroy methods to be overridden by the subclass + * + */ + destroy() + { + this.renderer.runners.contextChange.remove(this); + this.renderer = null; + } +} diff --git a/packages/core/src/batch/BatchSystem.js b/packages/core/src/batch/BatchSystem.js new file mode 100644 index 0000000..b04c249 --- /dev/null +++ b/packages/core/src/batch/BatchSystem.js @@ -0,0 +1,68 @@ +import System from '../System'; +import ObjectRenderer from './ObjectRenderer'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class BatchSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + super(renderer); + + /** + * An empty renderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.emptyRenderer = new ObjectRenderer(renderer); + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.currentRenderer = this.emptyRenderer; + } + + /** + * Changes the current renderer to the one given in parameter + * + * @param {PIXI.ObjectRenderer} objectRenderer - The object renderer to use. + */ + setObjectRenderer(objectRenderer) + { + if (this.currentRenderer === objectRenderer) + { + return; + } + + this.currentRenderer.stop(); + this.currentRenderer = objectRenderer; + + this.renderer.state.setState(objectRenderer.state); + + this.currentRenderer.start(); + } + + /** + * This should be called if you wish to do some custom rendering + * It will basically render anything that may be batched up such as sprites + * + */ + flush() + { + this.setObjectRenderer(this.emptyRenderer); + } + + reset() + { + this.setObjectRenderer(this.emptyRenderer); + } +} diff --git a/packages/core/src/batch/ObjectRenderer.js b/packages/core/src/batch/ObjectRenderer.js new file mode 100644 index 0000000..0c851a9 --- /dev/null +++ b/packages/core/src/batch/ObjectRenderer.js @@ -0,0 +1,48 @@ +import System from '../System'; + +/** + * Base for a common object renderer that can be used as a system renderer plugin. + * + * @class + * @extends PIXI.System + * @memberof PIXI + */ +export default class ObjectRenderer extends System +{ + /** + * Starts the renderer and sets the shader + * + */ + start() + { + // set the shader.. + } + + /** + * Stops the renderer + * + */ + stop() + { + this.flush(); + } + + /** + * Stub method for rendering content and emptying the current batch. + * + */ + flush() + { + // flush! + } + + /** + * Renders an object + * + * @param {PIXI.DisplayObject} object - The object to render. + */ + render(object) // eslint-disable-line no-unused-vars + { + // render the object + } +} diff --git a/packages/core/src/context/ContextSystem.js b/packages/core/src/context/ContextSystem.js new file mode 100644 index 0000000..301aab4 --- /dev/null +++ b/packages/core/src/context/ContextSystem.js @@ -0,0 +1,186 @@ +import System from '../System'; +import { settings } from '@pixi/settings'; + +let CONTEXT_UID = 0; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class ContextSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.webGLVersion = 1; + + this.handleContextLost = this.handleContextLost.bind(this); + this.handleContextRestored = this.handleContextRestored.bind(this); + + this.extensions = {}; + + renderer.view.addEventListener('webglcontextlost', this.handleContextLost, false); + renderer.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + } + + get isLost() + { + return (!this.gl || this.gl.isContextLost()); + } + + contextChange(gl) + { + this.gl = gl; + + // restore a context if it was previously lost + if (gl.isContextLost() && gl.getExtension('WEBGL_lose_context')) + { + gl.getExtension('WEBGL_lose_context').restoreContext(); + } + } + + initFromContext(gl) + { + this.gl = gl; + this.validateContext(gl); + this.renderer.gl = gl; + this.renderer.CONTEXT_UID = CONTEXT_UID++; + this.renderer.runners.contextChange.run(gl); + } + + initFromOptions(options) + { + const gl = this.createContext(this.renderer.view, options); + + this.initFromContext(gl); + } + + /** + * Helper class to create a webGL Context + * + * @class + * @memberof PIXI.glCore + * @param canvas {HTMLCanvasElement} the canvas element that we will get the context from + * @param options {Object} An options object that gets passed in to the canvas element containing the context attributes, + * see https://developer.mozilla.org/en/docs/Web/API/HTMLCanvasElement/getContext for the options available + * @return {WebGLRenderingContext} the WebGL context + */ + createContext(canvas, options) + { + let gl; + + if (settings.PREFER_WEBGL_2) + { + gl = canvas.getContext('webgl2', options); + } + + if (gl) + { + this.webGLVersion = 2; + } + else + { + this.webGLVersion = 1; + + gl = canvas.getContext('webgl', options) + || canvas.getContext('experimental-webgl', options); + + if (!gl) + { + // fail, not able to get a context + throw new Error('This browser does not support webGL. Try using the canvas renderer'); + } + } + + this.gl = gl; + + this.getExtensions(); + + return gl; + } + + getExtensions() + { + // time to set up default etensions that pixi uses.. + const gl = this.gl; + const extensions = this.extensions; + + if (this.webGLVersion === 1) + { + extensions.drawBuffers = gl.getExtension('WEBGL_draw_buffers'); + extensions.depthTexture = gl.getExtension('WEBKIT_WEBGL_depth_texture'); + extensions.floatTexture = gl.getExtension('OES_texture_float'); + extensions.loseContext = gl.getExtension('WEBGL_lose_context'); + + extensions.vertexArrayObject = gl.getExtension('OES_vertex_array_object') + || gl.getExtension('MOZ_OES_vertex_array_object') + || gl.getExtension('WEBKIT_OES_vertex_array_object'); + } + + // we don't use any specific WebGL 2 ones yet! + } + + /** + * Handles a lost webgl context + * + * @private + * @param {WebGLContextEvent} event - The context lost event. + */ + handleContextLost(event) + { + event.preventDefault(); + } + + /** + * Handles a restored webgl context + * + * @private + */ + handleContextRestored() + { + this.renderer.runners.contextChange.run(this.gl); + } + + destroy() + { + const view = this.renderer.view; + + // remove listeners + view.removeEventListener('webglcontextlost', this.handleContextLost); + view.removeEventListener('webglcontextrestored', this.handleContextRestored); + + this.gl.useProgram(null); + + if (this.extensions.loseContext) + { + this.extensions.loseContext.loseContext(); + } + } + + postrender() + { + this.gl.flush(); + } + + validateContext(gl) + { + const attributes = gl.getContextAttributes(); + + // this is going to be fairly simple for now.. but at least we have room to grow! + if (!attributes.stencil) + { + /* eslint-disable max-len */ + + /* eslint-disable no-console */ + console.warn('Provided WebGL context does not have a stencil buffer, masks may not render correctly'); + /* eslint-enable no-console */ + + /* eslint-enable max-len */ + } + } +} diff --git a/packages/core/src/filters/Filter.js b/packages/core/src/filters/Filter.js new file mode 100644 index 0000000..9d8cad8 --- /dev/null +++ b/packages/core/src/filters/Filter.js @@ -0,0 +1,140 @@ +import Shader from '../shader/Shader'; +import Program from '../shader/Program'; +import State from '../state/State'; +import { settings } from '@pixi/settings'; +// import extractUniformsFromSrc from './extractUniformsFromSrc'; +import defaultVertex from './defaultFilter.vert'; +import defaultFragment from './defaultFilter.frag'; + +// let math = require('../../math'); +/** + * @class + * @memberof PIXI + * @extends PIXI.Shader + */ +export default class Filter extends Shader +{ + /** + * @param {string} [vertexSrc] - The source of the vertex shader. + * @param {string} [fragmentSrc] - The source of the fragment shader. + * @param {object} [uniforms] - Custom uniforms to use to augment the built-in ones. + */ + constructor(vertexSrc, fragmentSrc, uniforms) + { + const program = Program.from(vertexSrc, fragmentSrc); + + super(program, uniforms); + + /** + * The padding of the filter. Some filters require extra space to breath such as a blur. + * Increasing this will add extra width and height to the bounds of the object that the + * filter is applied to. + * + * @member {number} + */ + this.padding = 0; + + /** + * The resolution of the filter. Setting this to be lower will lower the quality but + * increase the performance of the filter. + * + * @member {number} + */ + this.resolution = settings.RESOLUTION; + + /** + * If enabled is true the filter is applied, if false it will not. + * + * @member {boolean} + */ + this.enabled = true; + + /** + * If enabled, PixiJS will fit the filter area into boundaries for better performance. + * Switch it off if it does not work for specific shader. + * + * @member {boolean} + */ + this.autoFit = true; + + /** + * Legacy filters use position and uvs from attributes + * @member {boolean} + * @readonly + */ + this.legacy = !!this.program.attributeData.aTextureCoord; + + /** + * the webGL state the filter requires to render + * @member {PIXI.State} + */ + this.state = new State(); + } + + /** + * Applies the filter + * + * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + * @param {boolean} clear - Should the output be cleared before rendering to it + * @param {object} [currentState] - It's current state of filter. + * There are some useful properties in the currentState : + * target, filters, sourceFrame, destinationFrame, renderTarget, resolution + */ + apply(filterManager, input, output, clear, currentState, derp) // eslint-disable-line no-unused-vars + { + // do as you please! + + filterManager.applyFilter(this, input, output, clear, currentState, derp); + + // or just do a regular render.. + } + + /** + * Sets the blendmode of the filter + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL + */ + get blendMode() + { + return this.state.blendMode; + } + + set blendMode(value) // eslint-disable-line require-jsdoc + { + this.state.blendMode = value; + } + + /** + * The default vertex shader source + * + * @static + * @constant + */ + static get defaultVertexSrc() + { + return defaultVertex; + } + + /** + * The default fragment shader source + * + * @static + * @constant + */ + static get defaultFragmentSrc() + { + return defaultFragment; + } +} + +/** + * Used for caching shader IDs + * + * @static + * @private + */ +Filter.SOURCE_KEY_MAP = {}; + diff --git a/packages/core/src/filters/FilterSystem.js b/packages/core/src/filters/FilterSystem.js new file mode 100644 index 0000000..3174318 --- /dev/null +++ b/packages/core/src/filters/FilterSystem.js @@ -0,0 +1,431 @@ +import System from '../System'; + +import RenderTexture from '../renderTexture/RenderTexture'; +import Quad from '../utils/Quad'; +import QuadUv from '../utils/QuadUv'; +import { Rectangle } from '@pixi/math'; +import * as filterTransforms from './filterTransforms'; +import bitTwiddle from 'bit-twiddle'; +import UniformGroup from '../shader/UniformGroup'; +import { DRAW_MODES } from '@pixi/constants'; + +// +/** + * @ignore + * @class + */ +class FilterState +{ + /** + * + */ + constructor() + { + this.renderTexture = null; + this.sourceFrame = new Rectangle(); + this.destinationFrame = new Rectangle(); + this.filters = []; + this.target = null; + this.legacy = false; + this.resolution = 1; + } +} + +/** + * @class + * @memberof PIXI.systems + * @extends PIXI.System + */ +export default class FilterSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + /** + * stores a bunch of PO2 textures used for filtering + * @type {Object} + */ + this.texturePool = {}; + + /** + * a pool for storing filter states, save us creating new ones each tick + * @type {Array} + */ + this.statePool = []; + + /** + * A very simple geometry used when drawing a filter effect to the screen + * @type {Quad} + */ + this.quad = new Quad(); + + this.quadUv = new QuadUv(); + + /** + * Temporary rect for maths + * @type {PIXI.Rectangle} + */ + this.tempRect = new Rectangle(); + + this.activeState = {}; + + /** + * this uniform group is attached to filter uniforms when used + * @type {UniformGroup} + */ + this.globalUniforms = new UniformGroup({ + sourceFrame: this.tempRect, + destinationFrame: this.tempRect, + + // legacy variables + filterArea: new Float32Array(4), + filterClamp: new Float32Array(4), + }, true); + } + + /** + * Adds a new filter to the System. + * + * @param {PIXI.DisplayObject} target - The target of the filter to render. + * @param {PIXI.Filter[]} filters - The filters to apply. + */ + push(target, filters) + { + const renderer = this.renderer; + const filterStack = this.renderer.renderTexture.defaultFilterStack; + const state = this.statePool.pop() || new FilterState(); + + let resolution = filters[0].resolution; + let padding = filters[0].padding; + let autoFit = filters[0].autoFit; + let legacy = filters[0].legacy; + + for (let i = 1; i < filters.length; i++) + { + const filter = filters[i]; + + // lets use the lowest resolution.. + resolution = Math.min(resolution, filter.resolution); + // and the largest amount of padding! + padding = Math.max(padding, filter.padding); + // only auto fit if all filters are autofit + autoFit = autoFit || filter.autoFit; + + legacy = legacy || filter.legacy; + } + + filterStack.push(state); + + state.resolution = resolution; + + state.legacy = legacy; + + // round to whole number based on resolution + // TODO move that to the shader too? + state.sourceFrame = target.filterArea || target.getBounds(true); + + state.sourceFrame.pad(padding); + + if (autoFit) + { + state.sourceFrame.fit(this.renderer.renderTexture.destinationFrame); + } + + state.sourceFrame.round(resolution); + + state.renderTexture = this.getPotFilterTexture(state.sourceFrame.width, state.sourceFrame.height, resolution); + state.filters = filters; + + state.destinationFrame.width = state.renderTexture.width; + state.destinationFrame.height = state.renderTexture.height; + + state.renderTexture.filterFrame = state.sourceFrame; + renderer.renderTexture.bind(state.renderTexture, state.sourceFrame);// /, state.destinationFrame); + renderer.renderTexture.clear(); + } + + /** + * Pops off the filter and applies it. + * + */ + pop() + { + const renderer = this.renderer; + const filterStack = renderer.renderTexture.defaultFilterStack; + const state = filterStack.pop(); + const filters = state.filters; + + this.activeState = state; + + const globalUniforms = this.globalUniforms.uniforms; + + globalUniforms.sourceFrame = state.sourceFrame; + globalUniforms.destinationFrame = state.destinationFrame; + globalUniforms.resolution = state.resolution; + + // only update the rect if its legacy.. + if (state.legacy) + { + const filterArea = globalUniforms.filterArea; + const filterClamp = globalUniforms.filterClamp; + + filterArea[0] = state.destinationFrame.width; + filterArea[1] = state.destinationFrame.height; + filterArea[2] = state.sourceFrame.x; + filterArea[3] = state.sourceFrame.y; + + filterClamp[0] = 0.5 / state.resolution / state.destinationFrame.width; + filterClamp[1] = 0.5 / state.resolution / state.destinationFrame.height; + filterClamp[2] = (state.sourceFrame.width - 0.5) / state.resolution / state.destinationFrame.width; + filterClamp[3] = (state.sourceFrame.height - 0.5) / state.resolution / state.destinationFrame.height; + } + + this.globalUniforms.update(); + + const lastState = filterStack[filterStack.length - 1]; + + if (filters.length === 1) + { + filters[0].apply(this, state.renderTexture, lastState.renderTexture, false, state); + + this.returnFilterTexture(state.renderTexture); + } + else + { + let flip = state.renderTexture; + let flop = this.getPotFilterTexture( + flip.width, + flip.height, + state.resolution + ); + + flop.filterFrame = flip.filterFrame; + + let i = 0; + + for (i = 0; i < filters.length - 1; ++i) + { + filters[i].apply(this, flip, flop, true, state); + + const t = flip; + + flip = flop; + flop = t; + } + + filters[i].apply(this, flip, lastState.renderTexture, false, state); + + this.returnFilterTexture(flip); + this.returnFilterTexture(flop); + } + + this.statePool.push(state); + } + + /** + * Draws a filter. + * + * @param {PIXI.Filter} filter - The filter to draw. + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + * @param {boolean} clear - Should the output be cleared before rendering to it + */ + applyFilter(filter, input, output, clear) + { + const renderer = this.renderer; + + renderer.renderTexture.bind(output, output ? output.filterFrame : null); + + if (clear) + { + // gl.disable(gl.SCISSOR_TEST); + renderer.renderTexture.clear(); + // gl.enable(gl.SCISSOR_TEST); + } + + // set the uniforms.. + filter.uniforms.uSampler = input; + filter.uniforms.filterGlobals = this.globalUniforms; + + // TODO make it so that the order of this does not matter.. + // because it does at the moment cos of global uniforms. + // they need to get resynced + + renderer.state.setState(filter.state); + renderer.shader.bind(filter); + + if (filter.legacy) + { + this.quadUv.map(input._frame, input.filterFrame); + + renderer.geometry.bind(this.quadUv); + renderer.geometry.draw(DRAW_MODES.TRIANGLES); + } + else + { + renderer.geometry.bind(this.quad); + renderer.geometry.draw(DRAW_MODES.TRIANGLE_STRIP); + } + } + + /** + * Calculates the mapped matrix. + * + * TODO playing around here.. this is temporary - (will end up in the shader) + * this returns a matrix that will normalise map filter cords in the filter to screen space + * + * @param {PIXI.Matrix} outputMatrix - the matrix to output to. + * @return {PIXI.Matrix} The mapped matrix. + */ + calculateScreenSpaceMatrix(outputMatrix) + { + const currentState = this.activeState; + + return filterTransforms.calculateScreenSpaceMatrix( + outputMatrix, + currentState.sourceFrame, + currentState.destinationFrame + ); + } + + /** + * This will map the filter coord so that a texture can be used based on the transform of a sprite + * + * @param {PIXI.Matrix} outputMatrix - The matrix to output to. + * @param {PIXI.Sprite} sprite - The sprite to map to. + * @return {PIXI.Matrix} The mapped matrix. + */ + calculateSpriteMatrix(outputMatrix, sprite) + { + const currentState = this.activeState; + + return filterTransforms.calculateSpriteMatrix( + outputMatrix, + currentState.sourceFrame, + currentState.destinationFrame, + sprite + ); + } + + /** + * Destroys this Filter System. + * + * @param {boolean} [contextLost=false] context was lost, do not free shaders + * + */ + destroy(contextLost = false) + { + if (!contextLost) + { + this.emptyPool(); + } + else + { + this.texturePool = {}; + } + } + + /** + * Gets a Power-of-Two render texture. + * + * TODO move to a seperate class could be on renderer? + * also - could cause issue with multiple contexts? + * + * @private + * @param {WebGLRenderingContext} gl - The webgl rendering context + * @param {number} minWidth - The minimum width of the render target. + * @param {number} minHeight - The minimum height of the render target. + * @param {number} resolution - The resolution of the render target. + * @return {PIXI.RenderTarget} The new render target. + */ + getPotFilterTexture(minWidth, minHeight, resolution) + { + minWidth = bitTwiddle.nextPow2(minWidth); + minHeight = bitTwiddle.nextPow2(minHeight); + resolution = resolution || 1; + + const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + + if (!this.texturePool[key]) + { + this.texturePool[key] = []; + } + + let renderTexture = this.texturePool[key].pop(); + + if (!renderTexture) + { + // temporary bypass cache.. + // internally - this will cause a texture to be bound.. + renderTexture = RenderTexture.create({ + width: minWidth, + height: minHeight, + resolution, + }); + } + + return renderTexture; + } + + /** + * Gets extra render texture to use inside current filter + * + * @param {number} resolution resolution of the renderTexture + * @returns {PIXI.RenderTexture} + */ + getFilterTexture(resolution) + { + const rt = this.activeState.renderTexture; + + const filterTexture = this.getPotFilterTexture(rt.width, rt.height, resolution || rt.baseTexture.resolution); + + filterTexture.filterFrame = rt.filterFrame; + + return filterTexture; + } + + /** + * Frees a render texture back into the pool. + * + * @param {PIXI.RenderTarget} renderTexture - The renderTarget to free + */ + returnFilterTexture(renderTexture) + { + renderTexture.filterFrame = null; + + const base = renderTexture.baseTexture; + + const minWidth = base.width; + const minHeight = base.height; + + const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + + this.texturePool[key].push(renderTexture); + } + + /** + * Empties the texture pool. + * + */ + emptyPool() + { + for (const i in this.texturePool) + { + const textures = this.texturePool[i]; + + if (textures) + { + for (let j = 0; j < textures.length; j++) + { + textures[j].destroy(true); + } + } + } + + this.texturePool = {}; + } +} diff --git a/packages/core/src/filters/defaultFilter.frag b/packages/core/src/filters/defaultFilter.frag new file mode 100644 index 0000000..a14c1be --- /dev/null +++ b/packages/core/src/filters/defaultFilter.frag @@ -0,0 +1,22 @@ +varying vec2 vTextureCoord; +varying vec2 vFilterCoord; + +uniform sampler2D uSampler; +uniform sampler2D filterSampler; + +void main(void){ + vec4 masky = texture2D(filterSampler, vFilterCoord); + vec4 sample = texture2D(uSampler, vTextureCoord); + vec4 color; + if(mod(vFilterCoord.x, 1.0) > 0.5) + { + color = vec4(1.0, 0.0, 0.0, 1.0); + } + else + { + color = vec4(0.0, 1.0, 0.0, 1.0); + } + // gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0); + gl_FragColor = mix(sample, masky, 0.5); + gl_FragColor *= sample.a; +} diff --git a/packages/core/src/filters/defaultFilter.vert b/packages/core/src/filters/defaultFilter.vert new file mode 100644 index 0000000..e1febca --- /dev/null +++ b/packages/core/src/filters/defaultFilter.vert @@ -0,0 +1,14 @@ +attribute vec2 aVertexPosition; +attribute vec2 aTextureCoord; + +uniform mat3 projectionMatrix; +uniform mat3 filterMatrix; + +varying vec2 vTextureCoord; +varying vec2 vFilterCoord; + +void main(void){ + gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); + vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy; + vTextureCoord = aTextureCoord ; +} diff --git a/packages/core/src/filters/filterTransforms.js b/packages/core/src/filters/filterTransforms.js new file mode 100644 index 0000000..87a3cd6 --- /dev/null +++ b/packages/core/src/filters/filterTransforms.js @@ -0,0 +1,50 @@ +import { Matrix } from '@pixi/math'; + +/** + * Calculates the mapped matrix + * @param filterArea {Rectangle} The filter area + * @param sprite {Sprite} the target sprite + * @param outputMatrix {Matrix} @alvin + * @private + */ +// this returns a matrix that will normalise map filter cords in the filter to screen space +export function calculateScreenSpaceMatrix(outputMatrix, filterArea, textureSize) +{ + // TODO unwrap? + const mappedMatrix = outputMatrix.identity(); + + mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); + + mappedMatrix.scale(textureSize.width, textureSize.height); + + return mappedMatrix; +} + +export function calculateNormalizedScreenSpaceMatrix(outputMatrix, filterArea, textureSize) +{ + const mappedMatrix = outputMatrix.identity(); + + mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); + + const translateScaleX = (textureSize.width / filterArea.width); + const translateScaleY = (textureSize.height / filterArea.height); + + mappedMatrix.scale(translateScaleX, translateScaleY); + + return mappedMatrix; +} + +// this will map the filter coord so that a texture can be used based on the transform of a sprite +export function calculateSpriteMatrix(outputMatrix, filterArea, textureSize, sprite) +{ + const orig = sprite._texture.orig; + const mappedMatrix = outputMatrix.set(textureSize.width, 0, 0, textureSize.height, filterArea.x, filterArea.y); + const worldTransform = sprite.worldTransform.copyTo(Matrix.TEMP_MATRIX); + + worldTransform.invert(); + mappedMatrix.prepend(worldTransform); + mappedMatrix.scale(1.0 / orig.width, 1.0 / orig.height); + mappedMatrix.translate(sprite.anchor.x, sprite.anchor.y); + + return mappedMatrix; +} diff --git a/packages/core/src/filters/spriteMask/SpriteMaskFilter.js b/packages/core/src/filters/spriteMask/SpriteMaskFilter.js new file mode 100644 index 0000000..91697b7 --- /dev/null +++ b/packages/core/src/filters/spriteMask/SpriteMaskFilter.js @@ -0,0 +1,63 @@ +import Filter from '../Filter'; +import { Matrix } from '@pixi/math'; +import vertex from './spriteMaskFilter.vert'; +import fragment from './spriteMaskFilter.frag'; +import { default as TextureMatrix } from '../../textures/TextureMatrix'; + +/** + * The SpriteMaskFilter class + * + * @class + * @extends PIXI.Filter + * @memberof PIXI + */ +export default class SpriteMaskFilter extends Filter +{ + /** + * @param {PIXI.Sprite} sprite - the target sprite + */ + constructor(sprite) + { + const maskMatrix = new Matrix(); + + super(vertex, fragment); + + sprite.renderable = false; + + this.maskSprite = sprite; + this.maskMatrix = maskMatrix; + } + + /** + * Applies the filter + * + * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + */ + apply(filterManager, input, output) + { + const maskSprite = this.maskSprite; + const tex = this.maskSprite.texture; + + if (!tex.valid) + { + return; + } + if (!tex.transform) + { + // margin = 0.0, let it bleed a bit, shader code becomes easier + // assuming that atlas textures were made with 1-pixel padding + tex.transform = new TextureMatrix(tex, 0.0); + } + tex.transform.update(); + + this.uniforms.mask = tex; + this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite) + .prepend(tex.transform.mapCoord); + this.uniforms.alpha = maskSprite.worldAlpha; + this.uniforms.maskClamp = tex.transform.uClampFrame; + + filterManager.applyFilter(this, input, output); + } +} diff --git a/packages/core/src/filters/spriteMask/spriteMaskFilter.frag b/packages/core/src/filters/spriteMask/spriteMaskFilter.frag new file mode 100644 index 0000000..0e4aef8 --- /dev/null +++ b/packages/core/src/filters/spriteMask/spriteMaskFilter.frag @@ -0,0 +1,23 @@ +varying vec2 vMaskCoord; +varying vec2 vTextureCoord; + +uniform sampler2D uSampler; +uniform sampler2D mask; +uniform float alpha; +uniform vec4 maskClamp; + +void main(void) +{ + float clip = step(3.5, + step(maskClamp.x, vMaskCoord.x) + + step(maskClamp.y, vMaskCoord.y) + + step(vMaskCoord.x, maskClamp.z) + + step(vMaskCoord.y, maskClamp.w)); + + vec4 original = texture2D(uSampler, vTextureCoord); + vec4 masky = texture2D(mask, vMaskCoord); + + original *= (masky.r * masky.a * alpha * clip); + + gl_FragColor = original; +} diff --git a/packages/core/src/filters/spriteMask/spriteMaskFilter.vert b/packages/core/src/filters/spriteMask/spriteMaskFilter.vert new file mode 100644 index 0000000..d23a30c --- /dev/null +++ b/packages/core/src/filters/spriteMask/spriteMaskFilter.vert @@ -0,0 +1,16 @@ +attribute vec2 aVertexPosition; +attribute vec2 aTextureCoord; + +uniform mat3 projectionMatrix; +uniform mat3 otherMatrix; + +varying vec2 vMaskCoord; +varying vec2 vTextureCoord; + +void main(void) +{ + gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); + + vTextureCoord = aTextureCoord; + vMaskCoord = ( otherMatrix * vec3( aTextureCoord, 1.0) ).xy; +} diff --git a/packages/core/src/framebuffer/FrameBuffer.js b/packages/core/src/framebuffer/FrameBuffer.js new file mode 100644 index 0000000..84e9fcc --- /dev/null +++ b/packages/core/src/framebuffer/FrameBuffer.js @@ -0,0 +1,106 @@ +import Texture from '../textures/BaseTexture'; +import { FORMATS, TYPES } from '@pixi/constants'; + +/** + * Frame buffer + * @class + * @memberof PIXI + */ +export default class FrameBuffer +{ + constructor(width, height) + { + this.width = width || 100; + this.height = height || 100; + + this.stencil = false; + this.depth = false; + + this.dirtyId = 0; + this.dirtyFormat = 0; + this.dirtySize = 0; + + this.depthTexture = null; + this.colorTextures = []; + + this.glFrameBuffers = {}; + } + + get colorTexture() + { + return this.colorTextures[0]; + } + + addColorTexture(index, texture) + { + // TODO add some validation to the texture - same width / height etc? + this.colorTextures[index || 0] = texture || new Texture(null, { scaleMode: 0, + resolution: 1, + mipmap: false, + width: this.width, + height: this.height });// || new Texture(); + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + addDepthTexture(texture) + { + /* eslint-disable max-len */ + this.depthTexture = texture || new Texture(null, { scaleMode: 0, + resolution: 1, + width: this.width, + height: this.height, + mipmap: false, + format: FORMATS.DEPTH_COMPONENT, + type: TYPES.UNSIGNED_SHORT });// UNSIGNED_SHORT; + /* eslint-disable max-len */ + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + enableDepth() + { + this.depth = true; + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + enableStencil() + { + this.stencil = true; + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + resize(width, height) + { + if (width === this.width && height === this.height) return; + + this.width = width; + this.height = height; + + this.dirtyId++; + this.dirtySize++; + + for (let i = 0; i < this.colorTextures.length; i++) + { + this.colorTextures[i].setSize(width, height); + } + + if (this.depthTexture) + { + this.depthTexture.setSize(width, height); + } + } +} diff --git a/packages/core/src/framebuffer/FramebufferSystem.js b/packages/core/src/framebuffer/FramebufferSystem.js new file mode 100644 index 0000000..2cc2437 --- /dev/null +++ b/packages/core/src/framebuffer/FramebufferSystem.js @@ -0,0 +1,246 @@ +import System from '../System'; +import { Rectangle } from '@pixi/math'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class FramebufferSystem extends System +{ + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + contextChange() + { + this.gl = this.renderer.gl; + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + this.current = null; + this.viewport = new Rectangle(); + + this.drawBufferExtension = this.renderer.context.extensions.drawBuffers; + } + + bind(framebuffer, frame) + { + const gl = this.gl; + + this.current = framebuffer; + + if (framebuffer) + { + // TODO cacheing layer! + + const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID] || this.initFramebuffer(framebuffer); + + gl.bindFramebuffer(gl.FRAMEBUFFER, fbo.framebuffer); + // makesure all textures are unbound.. + + // now check for updates... + if (fbo.dirtyId !== framebuffer.dirtyId) + { + fbo.dirtyId = framebuffer.dirtyId; + + if (fbo.dirtyFormat !== framebuffer.dirtyFormat) + { + fbo.dirtyFormat = framebuffer.dirtyFormat; + this.updateFramebuffer(framebuffer); + } + else if (fbo.dirtySize !== framebuffer.dirtySize) + { + fbo.dirtySize = framebuffer.dirtySize; + this.resizeFramebuffer(framebuffer); + } + } + + for (let i = 0; i < framebuffer.colorTextures.length; i++) + { + if (framebuffer.colorTextures[i].texturePart) + { + this.renderer.texture.unbind(framebuffer.colorTextures[i].texture); + } + else + { + this.renderer.texture.unbind(framebuffer.colorTextures[i]); + } + } + + if (framebuffer.depthTexture) + { + this.renderer.texture.unbind(framebuffer.depthTexture); + } + + if (frame) + { + this.setViewport(frame.x, frame.y, frame.width, frame.height); + } + else + { + this.setViewport(0, 0, framebuffer.width, framebuffer.height); + } + } + else + { + gl.bindFramebuffer(gl.FRAMEBUFFER, null); + + if (frame) + { + this.setViewport(frame.x, frame.y, frame.width, frame.height); + } + else + { + this.setViewport(0, 0, this.renderer.width, this.renderer.height); + } + } + } + + setViewport(x, y, width, height) + { + const v = this.viewport; + + if (v.width !== width || v.height !== height || v.x !== x || v.y !== y) + { + v.x = x; + v.y = y; + v.width = width; + v.height = height; + + this.gl.viewport(x, y, width, height); + } + } + + get size() + { + if (this.current) + { + // TODO store temp + return { x: 0, y: 0, width: this.current.width, height: this.current.height }; + } + + return { x: 0, y: 0, width: this.renderer.width, height: this.renderer.height }; + } + + clear(r, g, b, a) + { + const gl = this.gl; + + // TODO clear color can be set only one right? + gl.clearColor(r, g, b, a); + gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); + } + + // private functions... + + initFramebuffer(framebuffer) + { + const gl = this.gl; + + // TODO - make this a class? + const fbo = { + framebuffer: gl.createFramebuffer(), + stencil: null, + dirtyId: 0, + dirtyFormat: 0, + dirtySize: 0, + }; + + framebuffer.glFrameBuffers[this.CONTEXT_UID] = fbo; + + return fbo; + } + + resizeFramebuffer(framebuffer) + { + const gl = this.gl; + + if (framebuffer.stencil || framebuffer.depth) + { + gl.bindRenderbuffer(gl.RENDERBUFFER, this.stencil); + gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); + } + } + + updateFramebuffer(framebuffer) + { + const gl = this.gl; + + const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID]; + + // bind the color texture + const colorTextures = framebuffer.colorTextures; + + let count = colorTextures.length; + + if (!this.drawBufferExtension) + { + count = Math.min(count, 1); + } + + const activeTextures = []; + + for (let i = 0; i < count; i++) + { + const texture = framebuffer.colorTextures[i]; + + if (texture.texturePart) + { + this.renderer.texture.bind(texture.texture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.COLOR_ATTACHMENT0 + i, + gl.TEXTURE_CUBE_MAP_NEGATIVE_X + texture.side, + texture.texture._glTextures[this.CONTEXT_UID].texture, + 0); + } + else + { + this.renderer.texture.bind(texture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.COLOR_ATTACHMENT0 + i, + gl.TEXTURE_2D, + texture._glTextures[this.CONTEXT_UID].texture, + 0); + } + + activeTextures.push(gl.COLOR_ATTACHMENT0 + i); + } + + if (this.drawBufferExtension && activeTextures.length > 1) + { + this.drawBufferExtension.drawBuffersWEBGL(activeTextures); + } + + if (framebuffer.depthTexture) + { + const depthTextureExt = this.renderer.context.extensions.depthTexture; + + if (depthTextureExt) + { + const depthTexture = framebuffer.depthTexture; + + this.renderer.texture.bind(depthTexture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.DEPTH_ATTACHMENT, + gl.TEXTURE_2D, + depthTexture._glTextures[this.CONTEXT_UID].texture, + 0); + } + } + + if (framebuffer.stencil || framebuffer.depth) + { + fbo.stencil = gl.createRenderbuffer(); + + gl.bindRenderbuffer(gl.RENDERBUFFER, fbo.stencil); + + // TODO.. this is depth AND stencil? + gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, fbo.stencil); + gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); + // fbo.enableStencil(); + } + } +} diff --git a/packages/core/src/geometry/GLBuffer.js b/packages/core/src/geometry/GLBuffer.js new file mode 100644 index 0000000..0477e8a --- /dev/null +++ b/packages/core/src/geometry/GLBuffer.js @@ -0,0 +1,9 @@ +export default class GLBuffer +{ + constructor(buffer) + { + this.buffer = buffer; + this.updateID = -1; + this.byteLength = -1; + } +} diff --git a/packages/core/src/geometry/GeometrySystem.js b/packages/core/src/geometry/GeometrySystem.js new file mode 100644 index 0000000..959f827 --- /dev/null +++ b/packages/core/src/geometry/GeometrySystem.js @@ -0,0 +1,388 @@ +import System from '../System'; +import GLBuffer from './GLBuffer'; + +const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class GeometrySystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this._activeGeometry = null; + this._activeVao = null; + + this.hasVao = true; + this.hasInstance = true; + } + + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + contextChange() + { + const gl = this.gl = this.renderer.gl; + + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + + // webgl2 + if (!gl.createVertexArray) + { + // webgl 1! + let nativeVaoExtension = this.renderer.context.extensions.vertexArrayObject; + + if (this.renderer.legacy) + { + nativeVaoExtension = null; + } + + if (nativeVaoExtension) + { + gl.createVertexArray = () => + nativeVaoExtension.createVertexArrayOES(); + + gl.bindVertexArray = (vao) => + nativeVaoExtension.bindVertexArrayOES(vao); + + gl.deleteVertexArray = (vao) => + nativeVaoExtension.deleteVertexArrayOES(vao); + } + else + { + this.hasVao = false; + gl.createVertexArray = () => + { + // empty + }; + + gl.bindVertexArray = () => + { + // empty + }; + + gl.deleteVertexArray = () => + { + // empty + }; + } + } + + if (!gl.vertexAttribDivisor) + { + const instanceExt = gl.getExtension('ANGLE_instanced_arrays'); + + if (instanceExt) + { + gl.vertexAttribDivisor = (a, b) => + instanceExt.vertexAttribDivisorANGLE(a, b); + + gl.drawElementsInstanced = (a, b, c, d, e) => + instanceExt.drawElementsInstancedANGLE(a, b, c, d, e); + + gl.drawArraysInstanced = (a, b, c, d) => + instanceExt.drawArraysInstancedANGLE(a, b, c, d); + } + else + { + this.hasInstance = false; + } + } + } + + /** + * Binds geometry so that is can be drawn. Creating a Vao if required + * @private + * @param {PIXI.Geometry} geometry instance of geometry to bind + */ + bind(geometry, shader) + { + shader = shader || this.renderer.shader.shader; + + const gl = this.gl; + + // not sure the best way to address this.. + // currently different shaders require different VAOs for the same geometry + // Still mulling over the best way to solve this one.. + // will likely need to modify the shader attribute locations at run time! + let vaos = geometry.glVertexArrayObjects[this.CONTEXT_UID]; + + if (!vaos) + { + geometry.glVertexArrayObjects[this.CONTEXT_UID] = vaos = {}; + } + + const vao = vaos[shader.program.id] || this.initGeometryVao(geometry, shader.program); + + this._activeGeometry = geometry; + + if (this._activeVao !== vao) + { + this._activeVao = vao; + + if (this.hasVao) + { + gl.bindVertexArray(vao); + } + else + { + this.activateVao(geometry, shader.program); + } + } + + // TODO - optimise later! + // don't need to loop through if nothing changed! + // maybe look to add an 'autoupdate' to geometry? + this.updateBuffers(); + } + + reset() + { + this.unbind(); + } + + updateBuffers() + { + const geometry = this._activeGeometry; + const gl = this.gl; + + for (let i = 0; i < geometry.buffers.length; i++) + { + const buffer = geometry.buffers[i]; + + const glBuffer = buffer._glBuffers[this.CONTEXT_UID]; + + if (buffer._updateID !== glBuffer.updateID) + { + glBuffer.updateID = buffer._updateID; + + // TODO can cache this on buffer! maybe added a getter / setter? + const type = buffer.index ? gl.ELEMENT_ARRAY_BUFFER : gl.ARRAY_BUFFER; + const drawType = buffer.static ? gl.STATIC_DRAW : gl.DYNAMIC_DRAW; + + gl.bindBuffer(type, glBuffer.buffer); + + if (glBuffer.byteLength >= buffer.data.byteLength) + { + // offset is always zero for now! + gl.bufferSubData(type, 0, buffer.data); + } + else + { + gl.bufferData(type, buffer.data, drawType); + } + } + } + } + + checkCompatability(geometry, program) + { + // geometry must have at least all the attributes that the shader requires. + const geometryAttributes = geometry.attributes; + const shaderAttributes = program.attributeData; + + for (const j in shaderAttributes) + { + if (!geometryAttributes[j]) + { + throw new Error(`shader and geometry incompatible, geometry missing the "${j}" attribute`); + } + } + } + + /** + * Creates a Vao with the same structure as the geometry and stores it on the geometry. + * @private + * @param {PIXI.Geometry} geometry instance of geometry to to generate Vao for + */ + initGeometryVao(geometry, program) + { + this.checkCompatability(geometry, program); + + const gl = this.gl; + const CONTEXT_UID = this.CONTEXT_UID; + const buffers = geometry.buffers; + const attributes = geometry.attributes; + + const tempStride = {}; + const tempStart = {}; + + for (const j in buffers) + { + tempStride[j] = 0; + tempStart[j] = 0; + } + + for (const j in attributes) + { + if (!attributes[j].size && program.attributeData[j]) + { + attributes[j].size = program.attributeData[j].size; + } + + tempStride[attributes[j].buffer] += attributes[j].size * byteSizeMap[attributes[j].type]; + } + + for (const j in attributes) + { + const attribute = attributes[j]; + const attribSize = attribute.size; + + if (attribute.stride === undefined) + { + if (tempStride[attribute.buffer] === attribSize * byteSizeMap[attribute.type]) + { + attribute.stride = 0; + } + else + { + attribute.stride = tempStride[attribute.buffer]; + } + } + + if (attribute.start === undefined) + { + attribute.start = tempStart[attribute.buffer]; + + tempStart[attribute.buffer] += attribSize * byteSizeMap[attribute.type]; + } + } + + // first update - and create the buffers! + // only create a gl buffer if it actually gets + for (let i = 0; i < buffers.length; i++) + { + const buffer = buffers[i]; + + if (!buffer._glBuffers[CONTEXT_UID]) + { + buffer._glBuffers[CONTEXT_UID] = new GLBuffer(gl.createBuffer()); + } + } + + // TODO - maybe make this a data object? + // lets wait to see if we need to first! + const vao = gl.createVertexArray(); + + gl.bindVertexArray(vao); + + this.activateVao(geometry, program); + + gl.bindVertexArray(null); + + // add it to the cache! + geometry.glVertexArrayObjects[this.CONTEXT_UID][program.id] = vao; + + return vao; + } + + activateVao(geometry, program) + { + const gl = this.gl; + const CONTEXT_UID = this.CONTEXT_UID; + const buffers = geometry.buffers; + const attributes = geometry.attributes; + + if (geometry.indexBuffer) + { + // first update the index buffer if we have one.. + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, geometry.indexBuffer._glBuffers[CONTEXT_UID].buffer); + } + + let lastBuffer = null; + + // add a new one! + for (const j in attributes) + { + const attribute = attributes[j]; + const buffer = buffers[attribute.buffer]; + const glBuffer = buffer._glBuffers[CONTEXT_UID]; + + if (program.attributeData[j]) + { + if (lastBuffer !== glBuffer) + { + gl.bindBuffer(gl.ARRAY_BUFFER, glBuffer.buffer); + + lastBuffer = glBuffer; + } + + const location = program.attributeData[j].location; + + // TODO introduce state again + // we can optimise this for older devices that have no VAOs + gl.enableVertexAttribArray(location); + + gl.vertexAttribPointer(location, + attribute.size, + attribute.type || gl.FLOAT, + attribute.normalized, + attribute.stride, + attribute.start); + + if (attribute.instance) + { + // TODO calculate instance count based of this... + if (this.hasInstance) + { + gl.vertexAttribDivisor(location, 1); + } + else + { + throw new Error('geometry error, GPU Instancing is not supported on this device'); + } + } + } + } + } + + draw(type, size, start, instanceCount) + { + const gl = this.gl; + const geometry = this._activeGeometry; + + // TODO.. this should not change so maybe cache the function? + + if (geometry.indexBuffer) + { + if (geometry.instanced) + { + /* eslint-disable max-len */ + gl.drawElementsInstanced(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2, instanceCount || 1); + /* eslint-enable max-len */ + } + else + { + gl.drawElements(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2); + } + } + else + if (geometry.instanced) + { + // TODO need a better way to calculate size.. + gl.drawArraysInstanced(type, start, size || geometry.getSize(), instanceCount || 1); + } + else + { + gl.drawArrays(type, start, size || geometry.getSize()); + } + + return this; + } + + unbind() + { + this.gl.bindVertexArray(null); + this._activeVao = null; + this._activeGeometry = null; + } +} diff --git a/packages/core/src/geometry/utils/setVertexAttribArrays.js b/packages/core/src/geometry/utils/setVertexAttribArrays.js new file mode 100644 index 0000000..23b6959 --- /dev/null +++ b/packages/core/src/geometry/utils/setVertexAttribArrays.js @@ -0,0 +1,54 @@ +// var GL_MAP = {}; + +/** + * @param gl {WebGLRenderingContext} The current WebGL context + * @param attribs {*} + * @param state {*} + */ +export default function setVertexAttribArrays(gl, attribs, state) +{ + let i; + + if (state) + { + const tempAttribState = state.tempAttribState; + const attribState = state.attribState; + + for (i = 0; i < tempAttribState.length; i++) + { + tempAttribState[i] = false; + } + + // set the new attribs + for (i = 0; i < attribs.length; i++) + { + tempAttribState[attribs[i].attribute.location] = true; + } + + for (i = 0; i < attribState.length; i++) + { + if (attribState[i] !== tempAttribState[i]) + { + attribState[i] = tempAttribState[i]; + + if (state.attribState[i]) + { + gl.enableVertexAttribArray(i); + } + else + { + gl.disableVertexAttribArray(i); + } + } + } + } + else + { + for (i = 0; i < attribs.length; i++) + { + const attrib = attribs[i]; + + gl.enableVertexAttribArray(attrib.attribute.location); + } + } +} diff --git a/packages/core/src/index.js b/packages/core/src/index.js index 58c456a..fd1f547 100644 --- a/packages/core/src/index.js +++ b/packages/core/src/index.js @@ -1,30 +1,30 @@ import * as resources from './textures/resources'; -import * as systems from './renderers/systems'; +import * as systems from './systems'; -export { resources }; export { systems }; +export { resources }; -export { default as Renderer } from './renderers/Renderer'; -export { default as AbstractRenderer } from './renderers/AbstractRenderer'; -export { default as FrameBuffer } from './textures/FrameBuffer'; +export { default as System } from './System'; +export { default as Renderer } from './Renderer'; +export { default as AbstractRenderer } from './AbstractRenderer'; +export { default as FrameBuffer } from './framebuffer/FrameBuffer'; export { default as CubeTexture } from './textures/CubeTexture'; export { default as BaseTexture } from './textures/BaseTexture'; export { default as Texture } from './textures/Texture'; export { default as TextureMatrix } from './textures/TextureMatrix'; -export { default as RenderTexture } from './textures/RenderTexture'; -export { default as BaseRenderTexture } from './textures/BaseRenderTexture'; +export { default as RenderTexture } from './renderTexture/RenderTexture'; +export { default as BaseRenderTexture } from './renderTexture/BaseRenderTexture'; export { default as TextureUvs } from './textures/TextureUvs'; -export { default as State } from './renderers/State'; -export { default as ObjectRenderer } from './renderers/utils/ObjectRenderer'; -export { default as Quad } from './renderers/utils/Quad'; -export { default as QuadUv } from './renderers/utils/QuadUv'; -export { default as checkMaxIfStatmentsInShader } from './renderers/utils/checkMaxIfStatmentsInShader'; +export { default as State } from './state/State'; +export { default as ObjectRenderer } from './batch/ObjectRenderer'; +export { default as Quad } from './utils/Quad'; +export { default as QuadUv } from './utils/QuadUv'; +export { default as checkMaxIfStatmentsInShader } from './shader/utils/checkMaxIfStatmentsInShader'; export { default as Shader } from './shader/Shader'; export { default as Program } from './shader/Program'; export { default as UniformGroup } from './shader/UniformGroup'; -export { default as SpriteMaskFilter } from './renderers/filters/spriteMask/SpriteMaskFilter'; -export { default as Filter } from './renderers/filters/Filter'; +export { default as SpriteMaskFilter } from './filters/spriteMask/SpriteMaskFilter'; +export { default as Filter } from './filters/Filter'; export { default as Attribute } from './geometry/Attribute'; export { default as Buffer } from './geometry/Buffer'; export { default as Geometry } from './geometry/Geometry'; - diff --git a/packages/core/src/mask/MaskSystem.js b/packages/core/src/mask/MaskSystem.js new file mode 100644 index 0000000..99174c2 --- /dev/null +++ b/packages/core/src/mask/MaskSystem.js @@ -0,0 +1,199 @@ +import System from '../System'; +import AlphaMaskFilter from '../filters/spriteMask/SpriteMaskFilter'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class MaskSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + // TODO - we don't need both! + this.scissor = false; + this.scissorData = null; + this.scissorRenderTarget = null; + + this.enableScissor = false; + + this.alphaMaskPool = []; + this.alphaMaskIndex = 0; + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.DisplayObject} target - Display Object to push the mask to + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + push(target, maskData) + { + // TODO the root check means scissor rect will not + // be used on render textures more info here: + // https://github.com/pixijs/pixi.js/pull/3545 + + if (maskData.texture) + { + this.pushSpriteMask(target, maskData); + } + else if (this.enableScissor + && !this.scissor + && this.renderer._activeRenderTarget.root + && !this.renderer.stencil.stencilMaskStack.length + && maskData.isFastRect()) + { + const matrix = maskData.worldTransform; + + let rot = Math.atan2(matrix.b, matrix.a); + + // use the nearest degree! + rot = Math.round(rot * (180 / Math.PI)); + + if (rot % 90) + { + this.pushStencilMask(maskData); + } + else + { + this.pushScissorMask(target, maskData); + } + } + else + { + this.pushStencilMask(maskData); + } + } + + /** + * Removes the last mask from the mask stack and doesn't return it. + * + * @param {PIXI.DisplayObject} target - Display Object to pop the mask from + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + pop(target, maskData) + { + if (maskData.texture) + { + this.popSpriteMask(target, maskData); + } + else if (this.enableScissor && !this.renderer.stencil.stencilMaskStack.length) + { + this.popScissorMask(target, maskData); + } + else + { + this.popStencilMask(target, maskData); + } + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.RenderTarget} target - Display Object to push the sprite mask to + * @param {PIXI.Sprite} maskData - Sprite to be used as the mask + */ + pushSpriteMask(target, maskData) + { + let alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; + + if (!alphaMaskFilter) + { + alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; + } + + alphaMaskFilter[0].resolution = this.renderer.resolution; + alphaMaskFilter[0].maskSprite = maskData; + + // TODO - may cause issues! + target.filterArea = maskData.getBounds(true); + + this.renderer.filterSystem.pushFilter(target, alphaMaskFilter); + + this.alphaMaskIndex++; + } + + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popSpriteMask() + { + this.renderer.filterSystem.popFilter(); + this.alphaMaskIndex--; + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + pushStencilMask(maskData) + { + this.renderer.batch.flush(); + this.renderer.stencil.pushStencil(maskData); + } + + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popStencilMask() + { + // this.renderer.currentRenderer.stop(); + this.renderer.stencil.popStencil(); + } + + /** + * + * @param {PIXI.DisplayObject} target - Display Object to push the mask to + * @param {PIXI.Graphics} maskData - The masking data. + */ + pushScissorMask(target, maskData) + { + maskData.renderable = true; + + const renderTarget = this.renderer._activeRenderTarget; + + const bounds = maskData.getBounds(); + + bounds.fit(renderTarget.size); + maskData.renderable = false; + + this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); + + const resolution = this.renderer.resolution; + + this.renderer.gl.scissor( + bounds.x * resolution, + (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, + bounds.width * resolution, + bounds.height * resolution + ); + + this.scissorRenderTarget = renderTarget; + this.scissorData = maskData; + this.scissor = true; + } + + /** + * + * + */ + popScissorMask() + { + this.scissorRenderTarget = null; + this.scissorData = null; + this.scissor = false; + + // must be scissor! + const gl = this.renderer.gl; + + gl.disable(gl.SCISSOR_TEST); + } +} diff --git a/packages/core/src/mask/StencilSystem.js b/packages/core/src/mask/StencilSystem.js new file mode 100644 index 0000000..62796a5 --- /dev/null +++ b/packages/core/src/mask/StencilSystem.js @@ -0,0 +1,133 @@ +import System from '../System'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class StencilSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + this.stencilMaskStack = []; + } + + /** + * Changes the mask stack that is used by this System. + * + * @param {PIXI.Graphics[]} stencilMaskStack - The mask stack + */ + setMaskStack(stencilMaskStack) + { + const gl = this.renderer.gl; + + if (stencilMaskStack.length !== this.stencilMaskStack.length) + { + if (stencilMaskStack.length === 0) + { + gl.disable(gl.STENCIL_TEST); + } + else + { + gl.enable(gl.STENCIL_TEST); + } + } + + this.stencilMaskStack = stencilMaskStack; + } + + /** + * Applies the Mask and adds it to the current stencil stack. @alvin + * + * @param {PIXI.Graphics} graphics - The mask + */ + pushStencil(graphics) + { + this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); + + // this.renderer._activeRenderTarget.attachStencilBuffer(); + + const gl = this.renderer.gl; + const prevMaskCount = this.stencilMaskStack.length; + + if (prevMaskCount === 0) + { + gl.enable(gl.STENCIL_TEST); + } + + this.stencilMaskStack.push(graphics); + + // Increment the refference stencil value where the new mask overlaps with the old ones. + gl.colorMask(false, false, false, false); + gl.stencilFunc(gl.EQUAL, prevMaskCount, this._getBitwiseMask()); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.INCR); + this.renderer.plugins.graphics.render(graphics); + + this._useCurrent(); + } + + /** + * Removes the last mask from the stencil stack. @alvin + */ + popStencil() + { + this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); + + const gl = this.renderer.gl; + const graphics = this.stencilMaskStack.pop(); + + if (this.stencilMaskStack.length === 0) + { + // the stack is empty! + gl.disable(gl.STENCIL_TEST); + gl.clear(gl.STENCIL_BUFFER_BIT); + gl.clearStencil(0); + } + else + { + // Decrement the refference stencil value where the popped mask overlaps with the other ones + gl.colorMask(false, false, false, false); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.DECR); + this.renderer.plugins.graphics.render(graphics); + + this._useCurrent(); + } + } + + /** + * Setup renderer to use the current stencil data. + */ + _useCurrent() + { + const gl = this.renderer.gl; + + gl.colorMask(true, true, true, true); + gl.stencilFunc(gl.EQUAL, this.stencilMaskStack.length, this._getBitwiseMask()); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP); + } + + /** + * Fill 1s equal to the number of acitve stencil masks. + * + * @return {number} The bitwise mask. + */ + _getBitwiseMask() + { + return (1 << this.stencilMaskStack.length) - 1; + } + + /** + * Destroys the mask stack. + * + */ + destroy() + { + super.destroy(this); + + this.stencilMaskStack = null; + } +} diff --git a/packages/core/src/projection/ProjectionSystem.js b/packages/core/src/projection/ProjectionSystem.js new file mode 100644 index 0000000..6e63b73 --- /dev/null +++ b/packages/core/src/projection/ProjectionSystem.js @@ -0,0 +1,73 @@ +import System from '../System'; +import { Matrix } from '@pixi/math'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class ProjectionSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.projectionMatrix = new Matrix(); + } + + update(destinationFrame, sourceFrame, resolution, root) + { + this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; + this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; + + this.calculateProjection(this.destinationFrame, this.sourceFrame, resolution, root); + + this.renderer.globalUniforms.uniforms.projectionMatrix = this.projectionMatrix; + this.renderer.globalUniforms.update(); + } + + /** + * Updates the projection matrix based on a projection frame (which is a rectangle) + * + * @param {Rectangle} destinationFrame - The destination frame. + * @param {Rectangle} sourceFrame - The source frame. + */ + calculateProjection(destinationFrame, sourceFrame, resolution, root) + { + const pm = this.projectionMatrix; + + // I don't think we will need this line.. + // pm.identity(); + + if (!root) + { + pm.a = (1 / destinationFrame.width * 2) * resolution; + pm.d = (1 / destinationFrame.height * 2) * resolution; + + pm.tx = -1 - (sourceFrame.x * pm.a); + pm.ty = -1 - (sourceFrame.y * pm.d); + } + else + { + pm.a = (1 / destinationFrame.width * 2) * resolution; + pm.d = (-1 / destinationFrame.height * 2) * resolution; + + pm.tx = -1 - (sourceFrame.x * pm.a); + pm.ty = 1 - (sourceFrame.y * pm.d); + } + } + + /** + * Sets the transform of the active render target to the given matrix + * + * @param {PIXI.Matrix} matrix - The transformation matrix + */ + setTransform()// matrix) + { + // this._activeRenderTarget.transform = matrix; + } +} diff --git a/packages/core/src/renderTexture/BaseRenderTexture.js b/packages/core/src/renderTexture/BaseRenderTexture.js new file mode 100644 index 0000000..88eb83b --- /dev/null +++ b/packages/core/src/renderTexture/BaseRenderTexture.js @@ -0,0 +1,137 @@ +import BaseTexture from '../textures/BaseTexture'; +import FrameBuffer from '../framebuffer/FrameBuffer'; + +/** + * A BaseRenderTexture is a special texture that allows any PixiJS display object to be rendered to it. + * + * __Hint__: All DisplayObjects (i.e. Sprites) that render to a BaseRenderTexture should be preloaded + * otherwise black rectangles will be drawn instead. + * + * A BaseRenderTexture takes a snapshot of any Display Object given to its render method. The position + * and rotation of the given Display Objects is ignored. For example: + * + * ```js + * let renderer = PIXI.autoDetectRenderer(1024, 1024, { view: canvas, ratio: 1 }); + * let baseRenderTexture = new PIXI.BaseRenderTexture(renderer, 800, 600); + * let sprite = PIXI.Sprite.fromImage("spinObj_01.png"); + * + * sprite.position.x = 800/2; + * sprite.position.y = 600/2; + * sprite.anchor.x = 0.5; + * sprite.anchor.y = 0.5; + * + * baseRenderTexture.render(sprite); + * ``` + * + * The Sprite in this case will be rendered using its local transform. To render this sprite at 0,0 + * you can clear the transform + * + * ```js + * + * sprite.setTransform() + * + * let baseRenderTexture = new PIXI.BaseRenderTexture(100, 100); + * let renderTexture = new PIXI.RenderTexture(baseRenderTexture); + * + * renderer.render(sprite, renderTexture); // Renders to center of RenderTexture + * ``` + * + * @class + * @extends PIXI.BaseTexture + * @memberof PIXI + */ +export default class BaseRenderTexture extends BaseTexture +{ + /** + * @param {object} [options] + * @param {number} [options.width=100] - The width of the base render texture + * @param {number} [options.height=100] - The height of the base render texture + * @param {PIXI.SCALE_MODES} [options.scaleMode] - See {@link PIXI.SCALE_MODES} for possible values + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the texture being generated + */ + constructor(options) + { + if (typeof options === 'number') + { + /* eslint-disable prefer-rest-params */ + // Backward compatibility of signature + const width = arguments[0]; + const height = arguments[1]; + const scaleMode = arguments[2]; + const resolution = arguments[3]; + + options = { width, height, scaleMode, resolution }; + /* eslint-enable prefer-rest-params */ + } + + super(null, options); + + const { width, height } = options || {}; + + // Set defaults + this.mipmap = false; + this.width = Math.ceil(width) || 100; + this.height = Math.ceil(height) || 100; + this.valid = true; + + /** + * A map of renderer IDs to webgl renderTargets + * + * @private + * @member {object} + */ + // this._glRenderTargets = {}; + + /** + * A reference to the canvas render target (we only need one as this can be shared across renderers) + * + * @private + * @member {object} + */ + this._canvasRenderTarget = null; + + this.clearColor = [0, 0, 0, 0]; + + this.frameBuffer = new FrameBuffer(width * this.resolution, height * this.resolution) + .addColorTexture(0, this); + + // TODO - could this be added the systems? + + /** + * The data structure for the stencil masks + * + * @member {PIXI.Graphics[]} + */ + this.stencilMaskStack = []; + + /** + * The data structure for the filters + * + * @member {PIXI.Graphics[]} + */ + this.filterStack = [{}]; + } + + /** + * Resizes the BaseRenderTexture. + * + * @param {number} width - The width to resize to. + * @param {number} height - The height to resize to. + */ + resize(width, height) + { + width = Math.ceil(width); + height = Math.ceil(height); + this.frameBuffer.resize(width * this.resolution, height * this.resolution); + } + + /** + * Destroys this texture + * + */ + destroy() + { + super.destroy(true); + this.renderer = null; + } +} diff --git a/packages/core/src/renderTexture/RenderTexture.js b/packages/core/src/renderTexture/RenderTexture.js new file mode 100644 index 0000000..f1ecf1f --- /dev/null +++ b/packages/core/src/renderTexture/RenderTexture.js @@ -0,0 +1,153 @@ +import BaseRenderTexture from './BaseRenderTexture'; +import Texture from '../textures/Texture'; + +/** + * A RenderTexture is a special texture that allows any PixiJS display object to be rendered to it. + * + * __Hint__: All DisplayObjects (i.e. Sprites) that render to a RenderTexture should be preloaded + * otherwise black rectangles will be drawn instead. + * + * A RenderTexture takes a snapshot of any Display Object given to its render method. For example: + * + * ```js + * let renderer = PIXI.autoDetectRenderer(1024, 1024, { view: canvas, ratio: 1 }); + * let renderTexture = PIXI.RenderTexture.create(800, 600); + * let sprite = PIXI.Sprite.fromImage("spinObj_01.png"); + * + * sprite.position.x = 800/2; + * sprite.position.y = 600/2; + * sprite.anchor.x = 0.5; + * sprite.anchor.y = 0.5; + * + * renderer.render(sprite, renderTexture); + * ``` + * + * The Sprite in this case will be rendered using its local transform. To render this sprite at 0,0 + * you can clear the transform + * + * ```js + * + * sprite.setTransform() + * + * let renderTexture = new PIXI.RenderTexture.create(100, 100); + * + * renderer.render(sprite, renderTexture); // Renders to center of RenderTexture + * ``` + * + * @class + * @extends PIXI.Texture + * @memberof PIXI + */ +export default class RenderTexture extends Texture +{ + /** + * @param {PIXI.BaseRenderTexture} baseRenderTexture - The renderer used for this RenderTexture + * @param {PIXI.Rectangle} [frame] - The rectangle frame of the texture to show + */ + constructor(baseRenderTexture, frame) + { + // support for legacy.. + let _legacyRenderer = null; + + if (!(baseRenderTexture instanceof BaseRenderTexture)) + { + /* eslint-disable prefer-rest-params, no-console */ + const width = arguments[1]; + const height = arguments[2]; + const scaleMode = arguments[3]; + const resolution = arguments[4]; + + // we have an old render texture.. + console.warn(`Please use RenderTexture.create(${width}, ${height}) instead of the ctor directly.`); + _legacyRenderer = arguments[0]; + /* eslint-enable prefer-rest-params, no-console */ + + frame = null; + baseRenderTexture = new BaseRenderTexture({ + width, + height, + scaleMode, + resolution, + }); + } + + /** + * The base texture object that this texture uses + * + * @member {BaseTexture} + */ + super(baseRenderTexture, frame); + + this.legacyRenderer = _legacyRenderer; + + /** + * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. + * + * @member {boolean} + */ + this.valid = true; + + /** + * FilterSystem temporary storage + * @private + * @member {PIXI.Rectangle} + */ + this.filterFrame = null; + + this._updateUvs(); + } + + /** + * Resizes the RenderTexture. + * + * @param {number} width - The width to resize to. + * @param {number} height - The height to resize to. + * @param {boolean} [resizeBaseTexture=true] - Should the baseTexture.width and height values be resized as well? + */ + resize(width, height, resizeBaseTexture = true) + { + width = Math.ceil(width); + height = Math.ceil(height); + + // TODO - could be not required.. + this.valid = (width > 0 && height > 0); + + this._frame.width = this.orig.width = width; + this._frame.height = this.orig.height = height; + + if (resizeBaseTexture) + { + this.baseTexture.resize(width, height); + } + + this._updateUvs(); + } + + /** + * A short hand way of creating a render texture. + * + * @param {object} [options] - Options + * @param {number} [options.width=100] - The width of the render texture + * @param {number} [options.height=100] - The height of the render texture + * @param {number} [options.scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the texture being generated + * @return {PIXI.RenderTexture} The new render texture + */ + static create(options) + { + // fallback, old-style: create(width, height, scaleMode, resolution) + if (typeof options === 'number') + { + /* eslint-disable prefer-rest-params */ + options = { + width: options, + height: arguments[1], + scaleMode: arguments[2], + resolution: arguments[3], + }; + /* eslint-enable prefer-rest-params */ + } + + return new RenderTexture(new BaseRenderTexture(options)); + } +} diff --git a/packages/core/src/renderTexture/RenderTextureSystem.js b/packages/core/src/renderTexture/RenderTextureSystem.js new file mode 100644 index 0000000..8d0ca61 --- /dev/null +++ b/packages/core/src/renderTexture/RenderTextureSystem.js @@ -0,0 +1,115 @@ +import System from '../System'; +import { Rectangle } from '@pixi/math'; + +const tempRect = new Rectangle(); + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class RenderTextureSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.clearColor = renderer._backgroundColorRgba; + + // TODO moe this property somewhere else! + this.defaultMaskStack = []; + this.defaultFilterStack = [{}]; + + // empty render texture? + this.renderTexture = null; + + this.destinationFrame = new Rectangle(); + } + + bind(renderTexture, sourceFrame, destinationFrame) + { + // TODO - do we want this?? + if (this.renderTexture === renderTexture) return; + this.renderTexture = renderTexture; + + const renderer = this.renderer; + + if (renderTexture) + { + const baseTexture = renderTexture.baseTexture; + + if (!destinationFrame) + { + tempRect.width = baseTexture.realWidth; + tempRect.height = baseTexture.realHeight; + + destinationFrame = tempRect; + } + + if (!sourceFrame) + { + sourceFrame = destinationFrame; + } + + this.renderer.framebuffer.bind(baseTexture.frameBuffer, destinationFrame); + + this.renderer.projection.update(destinationFrame, sourceFrame, baseTexture.resolution, false); + this.renderer.stencil.setMaskStack(baseTexture.stencilMaskStack); + } + else + { + // TODO these validation checks happen deeper down.. + // thing they can be avoided.. + if (!destinationFrame) + { + tempRect.width = renderer.width; + tempRect.height = renderer.height; + + destinationFrame = tempRect; + } + + if (!sourceFrame) + { + sourceFrame = destinationFrame; + } + + renderer.framebuffer.bind(null, destinationFrame); + + // TODO store this.. + this.renderer.projection.update(destinationFrame, sourceFrame, this.renderer.resolution, true); + this.renderer.stencil.setMaskStack(this.defaultMaskStack); + } + + this.destinationFrame.copyFrom(destinationFrame); + } + + /** + * Erases the render texture and fills the drawing area with a colour + * + * @param {number} [clearColor] - The colour + * @return {PIXI.Renderer} Returns itself. + */ + clear(clearColor) + { + if (this.renderTexture) + { + clearColor = clearColor || this.renderTexture.baseTexture.clearColor; + } + else + { + clearColor = clearColor || this.clearColor; + } + + this.renderer.framebuffer.clear(clearColor[0], clearColor[1], clearColor[2], clearColor[3]); + } + + resize()// screenWidth, screenHeight) + { + // resize the root only! + this.bind(null); + } +} diff --git a/packages/core/src/renderers/AbstractRenderer.js b/packages/core/src/renderers/AbstractRenderer.js deleted file mode 100644 index 928670f..0000000 --- a/packages/core/src/renderers/AbstractRenderer.js +++ /dev/null @@ -1,353 +0,0 @@ -import { hex2string, hex2rgb } from '@pixi/utils'; -import { Matrix, Rectangle } from '@pixi/math'; -import { RENDERER_TYPE } from '@pixi/constants'; -import { settings } from '@pixi/settings'; -import { Container } from '@pixi/display'; -import RenderTexture from '../textures/RenderTexture'; -import EventEmitter from 'eventemitter3'; - -const tempMatrix = new Matrix(); - -/** - * 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 EventEmitter - * @memberof PIXI - */ -export default class AbstractRenderer extends EventEmitter -{ - // eslint-disable-next-line valid-jsdoc - /** - * @param {string} system - The name of the system this renderer is for. - * @param {object} [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.transparent=false] - If the render view is transparent, default false - * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false - * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) - * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. The - * resolution of the renderer retina would be 2. - * @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 {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when rendering, - * stopping pixel interpolation. - */ - constructor(system, options, arg2, arg3) - { - super(); - - // Support for constructor(system, screenWidth, screenHeight, options) - if (typeof options === 'number') - { - options = Object.assign({ - width: options, - height: arg2 || settings.RENDER_OPTIONS.height, - }, arg3); - } - - // 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 = RENDERER_TYPE.UNKNOWN; - - /** - * Measurements of the screen. (0, 0, screenWidth, screenHeight) - * - * Its safe to use as filterArea or hitArea for 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 1 - */ - this.resolution = options.resolution || settings.RESOLUTION; - - /** - * Whether the render view is transparent - * - * @member {boolean} - */ - this.transparent = options.transparent; - - /** - * Whether css dimensions of canvas view should be resized to screen dimensions automatically - * - * @member {boolean} - */ - this.autoResize = options.autoResize || false; - - /** - * Tracks the blend modes useful for this renderer. - * - * @member {object} - */ - this.blendModes = null; - - /** - * 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; - - /** - * If true PixiJS will Math.floor() x/y values when rendering, stopping pixel interpolation. - * Handy for crisp pixel art and speed on legacy devices. - * - * @member {boolean} - */ - this.roundPixels = options.roundPixels; - - /** - * The background color as a number. - * - * @member {number} - * @private - */ - this._backgroundColor = 0x000000; - - /** - * The background color as an [R, G, B] array. - * - * @member {number[]} - * @private - */ - this._backgroundColorRgba = [0, 0, 0, 0]; - - /** - * The background color as a string. - * - * @member {string} - * @private - */ - this._backgroundColorString = '#000000'; - - this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter - - /** - * This temporary display object used as the parent of the currently being rendered item - * - * @member {PIXI.DisplayObject} - * @private - */ - this._tempDisplayObjectParent = new Container(); - - /** - * The last root object that the renderer tried to render. - * - * @member {PIXI.DisplayObject} - * @private - */ - this._lastObjectRendered = this._tempDisplayObjectParent; - - /** - * Collection of plugins. - * @readonly - * @member {object} - */ - this.plugins = {}; - } - - /** - * Initialize the plugins. - * - * @protected - * @param {object} staticMap - The dictionary of staticly saved plugins. - */ - initPlugins(staticMap) - { - 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() - { - return this.view.width; - } - - /** - * Same as view.height, actual number of pixels in the canvas by vertical - * - * @member {number} - * @readonly - * @default 600 - */ - get height() - { - return this.view.height; - } - - /** - * Resizes the screen and canvas to the specified width and height - * Canvas dimensions are multiplied by resolution - * - * @param {number} screenWidth - the new width of the screen - * @param {number} screenHeight - the new height of the screen - */ - resize(screenWidth, screenHeight) - { - this.screen.width = screenWidth; - this.screen.height = screenHeight; - - this.view.width = screenWidth * this.resolution; - this.view.height = screenHeight * this.resolution; - - if (this.autoResize) - { - this.view.style.width = `${screenWidth}px`; - this.view.style.height = `${screenHeight}px`; - } - } - - /** - * 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 {PIXI.DisplayObject} displayObject - The displayObject the object will be generated from - * @param {number} scaleMode - Should be one of the scaleMode consts - * @param {number} resolution - The resolution / device pixel ratio of the texture being generated - * @param {PIXI.Rectangle} [region] - The region of the displayObject, that shall be rendered, - * if no region is specified, defaults to the local bounds of the displayObject. - * @return {PIXI.Texture} a texture of the graphics object - */ - generateTexture(displayObject, scaleMode, resolution, region) - { - region = region || displayObject.getLocalBounds(); - - // 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(region.width | 0, region.height | 0, scaleMode, resolution); - - tempMatrix.tx = -region.x; - tempMatrix.ty = -region.y; - - this.render(displayObject, renderTexture, false, tempMatrix, true); - - return renderTexture; - } - - /** - * Removes everything from the renderer and optionally removes the Canvas DOM element. - * - * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. - */ - destroy(removeView) - { - 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); - } - - this.plugins = null; - - this.type = RENDERER_TYPE.UNKNOWN; - - this.view = null; - - this.screen = null; - - this.resolution = 0; - - this.transparent = false; - - this.autoResize = false; - - this.blendModes = null; - - this.options = null; - - this.preserveDrawingBuffer = false; - this.clearBeforeRender = false; - - this.roundPixels = false; - - this._backgroundColor = 0; - this._backgroundColorRgba = null; - this._backgroundColorString = null; - - this._tempDisplayObjectParent = null; - this._lastObjectRendered = null; - } - - /** - * The background color to fill if not transparent - * - * @member {number} - */ - get backgroundColor() - { - return this._backgroundColor; - } - - set backgroundColor(value) // eslint-disable-line require-jsdoc - { - this._backgroundColor = value; - this._backgroundColorString = hex2string(value); - hex2rgb(value, this._backgroundColorRgba); - } -} diff --git a/packages/core/src/renderers/Renderer.js b/packages/core/src/renderers/Renderer.js deleted file mode 100644 index a2020b1..0000000 --- a/packages/core/src/renderers/Renderer.js +++ /dev/null @@ -1,329 +0,0 @@ -import AbstractRenderer from './AbstractRenderer'; -import { sayHello } from '@pixi/utils'; -import MaskSystem from './systems/MaskSystem'; -import StencilSystem from './systems/StencilSystem'; -import FilterSystem from './systems/filter/FilterSystem'; -import FramebufferSystem from './systems/FramebufferSystem'; -import RenderTextureSystem from './systems/RenderTextureSystem'; -import TextureSystem from './systems/textures/TextureSystem'; -import ProjectionSystem from './systems/ProjectionSystem'; -import StateSystem from './systems/StateSystem'; -import GeometrySystem from './systems/geometry/GeometrySystem'; -import ShaderSystem from './systems/shader/ShaderSystem'; -import ContextSystem from './systems/ContextSystem'; -import BatchSystem from './systems/BatchSystem'; -import TextureGCSystem from './systems/textures/TextureGCSystem'; -import { RENDERER_TYPE } from '@pixi/constants'; -import UniformGroup from '../shader/UniformGroup'; -import { Matrix } from '@pixi/math'; -import Runner from 'mini-runner'; - -/** - * 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 Render works by automatically managing webGLBatchs. - * 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 :) - * - * @class - * @memberof PIXI - * @extends PIXI.AbstractRenderer - */ -export default class Renderer extends AbstractRenderer -{ - // eslint-disable-next-line valid-jsdoc - /** - * - * @param {object} [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.transparent=false] - If the render view is transparent, default false - * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false - * @param {boolean} [options.antialias=false] - sets antialias. If not available natively then FXAA - * antialiasing is used - * @param {boolean} [options.forceFXAA=false] - forces FXAA antialiasing to be used over native. - * FXAA is faster, but may not always look as great - * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. - * The resolution of the renderer retina would be 2. - * @param {boolean} [options.clearBeforeRender=true] - This sets if the renderer will clear - * the canvas or not before the new render pass. If you wish to set this to false, you *must* set - * preserveDrawingBuffer to `true`. - * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, - * enable this if you need to call toDataUrl on the webgl context. - * @param {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when - * rendering, stopping pixel interpolation. - * @param {number} [options.backgroundColor=0x000000] - The background color of the rendered area - * (shown if not transparent). - * @param {boolean} [options.legacy=false] - If true PixiJS will aim to ensure compatibility - * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. - * @param {string} [options.powerPreference] - Parameter passed to webgl context, set to "high-performance" - * for devices with dual graphics card - */ - constructor(options, arg2, arg3) - { - super('WebGL', options, arg2, arg3); - - /** - * The type of this renderer as a standardised const - * - * @member {number} - * @see PIXI.RENDERER_TYPE - */ - this.type = RENDERER_TYPE.WEBGL; - - // this will be set by the contextSystem (this.context) - this.gl = null; - this.CONTEXT_UID = 0; - this.legacy = !!options.legacy; - - // TODO legacy! - - // runners! - this.runners = { - destroy: new Runner('destroy'), - contextChange: new Runner('contextChange', 1), - reset: new Runner('reset'), - update: new Runner('update'), - postrender: new Runner('postrender'), - prerender: new Runner('prerender'), - resize: new Runner('resize', 2), - }; - - 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(GeometrySystem, 'geometry') - .addSystem(FramebufferSystem, 'framebuffer') - .addSystem(StencilSystem, 'stencil') - .addSystem(ProjectionSystem, 'projection') - .addSystem(TextureGCSystem, 'textureGC') - .addSystem(FilterSystem, 'filter') - .addSystem(RenderTextureSystem, 'renderTexture') - .addSystem(BatchSystem, 'batch'); - - this.initPlugins(Renderer.__plugins); - - /** - * The options passed in to create a new webgl context. - * - * @member {object} - * @private - */ - if (options.context) - { - this.context.initFromContext(options.context); - } - else - { - this.context.initFromOptions({ - alpha: this.transparent, - antialias: options.antialias, - premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', - stencil: true, - 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); - } - - addSystem(_class, name) - { - if (!name) - { - name = _class.name; - } - - // TODO - read name from class.name.. - - /* - if(name.includes('System')) - { - name = name.replace('System', ''); - name = name.charAt(0).toLowerCase() + name.slice(1); - } - */ - - const system = new _class(this); - - if (this[name]) - { - throw new Error(`Whoops! ${name} is already a manger`); - } - - this[name] = system; - - for (const i in this.runners) - { - this.runners[i].add(system); - } - - return this; - - /** - * 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. - */ - } - - /** - * Renders the object to its webGL view - * - * @param {PIXI.DisplayObject} displayObject - the object to be rendered - * @param {PIXI.RenderTexture} renderTexture - The render texture to render to. - * @param {boolean} [clear] - Should the canvas be cleared before the new render - * @param {PIXI.Transform} [transform] - A transform to apply to the render texture before rendering. - * @param {boolean} [skipUpdateTransform] - Should we skip the update transform pass? - */ - render(displayObject, renderTexture, clear, transform, skipUpdateTransform) - { - // can be handy to know! - this.renderingToScreen = !renderTexture; - - this.runners.prerender.run(); - this.emit('prerender'); - - // 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.parent; - - displayObject.parent = this._tempDisplayObjectParent; - displayObject.updateTransform(); - displayObject.parent = 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.run(); - - this.emit('postrender'); - } - - /** - * Resizes the webGL view to the specified width and height. - * - * @param {number} screenWidth - the new width of the screen - * @param {number} screenHeight - the new height of the screen - */ - resize(screenWidth, screenHeight) - { - AbstractRenderer.prototype.resize.call(this, screenWidth, screenHeight); - - this.runners.resize.run(screenWidth, screenHeight); - } - - /** - * Resets the WebGL state so you can render things however you fancy! - * - * @return {PIXI.Renderer} Returns itself. - */ - reset() - { - this.runners.reset.run(); - - return this; - } - - clear() - { - this.framebuffer.bind(); - this.framebuffer.clear(); - } - - /** - * Removes everything from the renderer (event listeners, spritebatch, etc...) - * - * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. - * See: https://github.com/pixijs/pixi.js/issues/2233 - */ - destroy(removeView) - { - this.runners.destroy.run(); - - // call base destroy - super.destroy(removeView); - - // TODO nullify all the managers.. - this.gl = null; - } - - /** - * 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 - * builds and excluding plugins. - * @name PIXI.Renderer#plugins - * @type {object} - * @readonly - * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. - * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. - * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. - * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. - */ - - /** - * Adds a plugin to the renderer. - * - * @method - * @param {string} pluginName - The name of the plugin. - * @param {Function} ctor - The constructor function or class for the plugin. - */ - static registerPlugin(pluginName, ctor) - { - Renderer.__plugins = Renderer.__plugins || {}; - Renderer.__plugins[pluginName] = ctor; - } -} diff --git a/packages/core/src/renderers/State.js b/packages/core/src/renderers/State.js deleted file mode 100644 index fa3f221..0000000 --- a/packages/core/src/renderers/State.js +++ /dev/null @@ -1,163 +0,0 @@ -/* eslint-disable max-len */ - -const BLEND = 0; -const OFFSET = 1; -const CULLING = 2; -const DEPTH_TEST = 3; -const WINDING = 4; - -/** - * This is a webGL state. It is passed The WebGL StateManager. - * Each mesh renderered may require webGL to be in a different state. - * For example you may want different blend mode or to enable polygon offsets - * - * @class - * @memberof PIXI - */ -export default class State -{ - /** - * - */ - constructor() - { - this.data = 0; - - this.blendMode = 0; - this.polygonOffset = 0; - - this.blend = true; - // this.depthTest = true; - } - - /** - * Activates blending of the computed fragment color values - * - * @member {boolean} - */ - get blend() - { - return !!(this.data & (1 << BLEND)); - } - - set blend(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << BLEND)) !== value) - { - this.data ^= (1 << BLEND); - } - } - - /** - * Activates adding an offset to depth values of polygon's fragments - * - * @member {boolean} - * @default false - */ - get offsets() - { - return !!(this.data & (1 << OFFSET)); - } - - set offsets(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << OFFSET)) !== value) - { - this.data ^= (1 << OFFSET); - } - } - - /** - * Activates culling of polygons. - * - * @member {boolean} - * @default false - */ - get culling() - { - return !!(this.data & (1 << CULLING)); - } - - set culling(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << CULLING)) !== value) - { - this.data ^= (1 << CULLING); - } - } - - /** - * Activates depth comparisons and updates to the depth buffer. - * - * @member {boolean} - * @default false - */ - get depthTest() - { - return !!(this.data & (1 << DEPTH_TEST)); - } - - set depthTest(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << DEPTH_TEST)) !== value) - { - this.data ^= (1 << DEPTH_TEST); - } - } - - /** - * Specifies whether or not front or back-facing polygons can be culled. - * @member {boolean} - * @default false - */ - get clockwiseFrontFace() - { - return !!(this.data & (1 << WINDING)); - } - - set clockwiseFrontFace(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << WINDING)) !== value) - { - this.data ^= (1 << WINDING); - } - } - - /** - * The blend mode to be applied when this state is set. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. - * Setting this mode to anything other than NO_BLEND will automatically switch blending on. - * - * @member {boolean} - * @default PIXI.BLEND_MODES.NORMAL - * @see PIXI.BLEND_MODES - */ - get blendMode() - { - return this._blendMode; - } - - set blendMode(value) // eslint-disable-line require-jsdoc - { - // 17 is NO BLEND - this.blend = (value !== 17); - this._blendMode = value; - } - - /** - * The polygon offset. Setting this property to anything other than 0 will automatically enable poygon offset fill. - * - * @member {number} - * @default 0 - */ - get polygonOffset() - { - return this._polygonOffset; - } - - set polygonOffset(value) // eslint-disable-line require-jsdoc - { - this.offsets = !!value; - this._polygonOffset = value; - } -} - diff --git a/packages/core/src/renderers/filters/Filter.js b/packages/core/src/renderers/filters/Filter.js deleted file mode 100644 index 2073b8d..0000000 --- a/packages/core/src/renderers/filters/Filter.js +++ /dev/null @@ -1,140 +0,0 @@ -import Shader from '../../shader/Shader'; -import Program from '../../shader/Program'; -import State from '../State'; -import { settings } from '@pixi/settings'; -// import extractUniformsFromSrc from './extractUniformsFromSrc'; -import defaultVertex from './defaultFilter.vert'; -import defaultFragment from './defaultFilter.frag'; - -// let math = require('../../math'); -/** - * @class - * @memberof PIXI - * @extends PIXI.Shader - */ -export default class Filter extends Shader -{ - /** - * @param {string} [vertexSrc] - The source of the vertex shader. - * @param {string} [fragmentSrc] - The source of the fragment shader. - * @param {object} [uniforms] - Custom uniforms to use to augment the built-in ones. - */ - constructor(vertexSrc, fragmentSrc, uniforms) - { - const program = Program.from(vertexSrc, fragmentSrc); - - super(program, uniforms); - - /** - * The padding of the filter. Some filters require extra space to breath such as a blur. - * Increasing this will add extra width and height to the bounds of the object that the - * filter is applied to. - * - * @member {number} - */ - this.padding = 0; - - /** - * The resolution of the filter. Setting this to be lower will lower the quality but - * increase the performance of the filter. - * - * @member {number} - */ - this.resolution = settings.RESOLUTION; - - /** - * If enabled is true the filter is applied, if false it will not. - * - * @member {boolean} - */ - this.enabled = true; - - /** - * If enabled, PixiJS will fit the filter area into boundaries for better performance. - * Switch it off if it does not work for specific shader. - * - * @member {boolean} - */ - this.autoFit = true; - - /** - * Legacy filters use position and uvs from attributes - * @member {boolean} - * @readonly - */ - this.legacy = !!this.program.attributeData.aTextureCoord; - - /** - * the webGL state the filter requires to render - * @member {PIXI.State} - */ - this.state = new State(); - } - - /** - * Applies the filter - * - * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from - * @param {PIXI.RenderTarget} input - The input render target. - * @param {PIXI.RenderTarget} output - The target to output to. - * @param {boolean} clear - Should the output be cleared before rendering to it - * @param {object} [currentState] - It's current state of filter. - * There are some useful properties in the currentState : - * target, filters, sourceFrame, destinationFrame, renderTarget, resolution - */ - apply(filterManager, input, output, clear, currentState, derp) // eslint-disable-line no-unused-vars - { - // do as you please! - - filterManager.applyFilter(this, input, output, clear, currentState, derp); - - // or just do a regular render.. - } - - /** - * Sets the blendmode of the filter - * - * @member {number} - * @default PIXI.BLEND_MODES.NORMAL - */ - get blendMode() - { - return this.state.blendMode; - } - - set blendMode(value) // eslint-disable-line require-jsdoc - { - this.state.blendMode = value; - } - - /** - * The default vertex shader source - * - * @static - * @constant - */ - static get defaultVertexSrc() - { - return defaultVertex; - } - - /** - * The default fragment shader source - * - * @static - * @constant - */ - static get defaultFragmentSrc() - { - return defaultFragment; - } -} - -/** - * Used for caching shader IDs - * - * @static - * @private - */ -Filter.SOURCE_KEY_MAP = {}; - diff --git a/packages/core/src/renderers/filters/defaultFilter.frag b/packages/core/src/renderers/filters/defaultFilter.frag deleted file mode 100644 index a14c1be..0000000 --- a/packages/core/src/renderers/filters/defaultFilter.frag +++ /dev/null @@ -1,22 +0,0 @@ -varying vec2 vTextureCoord; -varying vec2 vFilterCoord; - -uniform sampler2D uSampler; -uniform sampler2D filterSampler; - -void main(void){ - vec4 masky = texture2D(filterSampler, vFilterCoord); - vec4 sample = texture2D(uSampler, vTextureCoord); - vec4 color; - if(mod(vFilterCoord.x, 1.0) > 0.5) - { - color = vec4(1.0, 0.0, 0.0, 1.0); - } - else - { - color = vec4(0.0, 1.0, 0.0, 1.0); - } - // gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0); - gl_FragColor = mix(sample, masky, 0.5); - gl_FragColor *= sample.a; -} diff --git a/packages/core/src/renderers/filters/defaultFilter.vert b/packages/core/src/renderers/filters/defaultFilter.vert deleted file mode 100644 index e1febca..0000000 --- a/packages/core/src/renderers/filters/defaultFilter.vert +++ /dev/null @@ -1,14 +0,0 @@ -attribute vec2 aVertexPosition; -attribute vec2 aTextureCoord; - -uniform mat3 projectionMatrix; -uniform mat3 filterMatrix; - -varying vec2 vTextureCoord; -varying vec2 vFilterCoord; - -void main(void){ - gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); - vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy; - vTextureCoord = aTextureCoord ; -} diff --git a/packages/core/src/renderers/filters/filterTransforms.js b/packages/core/src/renderers/filters/filterTransforms.js deleted file mode 100644 index 87a3cd6..0000000 --- a/packages/core/src/renderers/filters/filterTransforms.js +++ /dev/null @@ -1,50 +0,0 @@ -import { Matrix } from '@pixi/math'; - -/** - * Calculates the mapped matrix - * @param filterArea {Rectangle} The filter area - * @param sprite {Sprite} the target sprite - * @param outputMatrix {Matrix} @alvin - * @private - */ -// this returns a matrix that will normalise map filter cords in the filter to screen space -export function calculateScreenSpaceMatrix(outputMatrix, filterArea, textureSize) -{ - // TODO unwrap? - const mappedMatrix = outputMatrix.identity(); - - mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); - - mappedMatrix.scale(textureSize.width, textureSize.height); - - return mappedMatrix; -} - -export function calculateNormalizedScreenSpaceMatrix(outputMatrix, filterArea, textureSize) -{ - const mappedMatrix = outputMatrix.identity(); - - mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); - - const translateScaleX = (textureSize.width / filterArea.width); - const translateScaleY = (textureSize.height / filterArea.height); - - mappedMatrix.scale(translateScaleX, translateScaleY); - - return mappedMatrix; -} - -// this will map the filter coord so that a texture can be used based on the transform of a sprite -export function calculateSpriteMatrix(outputMatrix, filterArea, textureSize, sprite) -{ - const orig = sprite._texture.orig; - const mappedMatrix = outputMatrix.set(textureSize.width, 0, 0, textureSize.height, filterArea.x, filterArea.y); - const worldTransform = sprite.worldTransform.copyTo(Matrix.TEMP_MATRIX); - - worldTransform.invert(); - mappedMatrix.prepend(worldTransform); - mappedMatrix.scale(1.0 / orig.width, 1.0 / orig.height); - mappedMatrix.translate(sprite.anchor.x, sprite.anchor.y); - - return mappedMatrix; -} diff --git a/packages/core/src/renderers/filters/spriteMask/SpriteMaskFilter.js b/packages/core/src/renderers/filters/spriteMask/SpriteMaskFilter.js deleted file mode 100644 index d5a91a4..0000000 --- a/packages/core/src/renderers/filters/spriteMask/SpriteMaskFilter.js +++ /dev/null @@ -1,63 +0,0 @@ -import Filter from '../Filter'; -import { Matrix } from '@pixi/math'; -import vertex from './spriteMaskFilter.vert'; -import fragment from './spriteMaskFilter.frag'; -import { default as TextureMatrix } from '../../../textures/TextureMatrix'; - -/** - * The SpriteMaskFilter class - * - * @class - * @extends PIXI.Filter - * @memberof PIXI - */ -export default class SpriteMaskFilter extends Filter -{ - /** - * @param {PIXI.Sprite} sprite - the target sprite - */ - constructor(sprite) - { - const maskMatrix = new Matrix(); - - super(vertex, fragment); - - sprite.renderable = false; - - this.maskSprite = sprite; - this.maskMatrix = maskMatrix; - } - - /** - * Applies the filter - * - * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from - * @param {PIXI.RenderTarget} input - The input render target. - * @param {PIXI.RenderTarget} output - The target to output to. - */ - apply(filterManager, input, output) - { - const maskSprite = this.maskSprite; - const tex = this.maskSprite.texture; - - if (!tex.valid) - { - return; - } - if (!tex.transform) - { - // margin = 0.0, let it bleed a bit, shader code becomes easier - // assuming that atlas textures were made with 1-pixel padding - tex.transform = new TextureMatrix(tex, 0.0); - } - tex.transform.update(); - - this.uniforms.mask = tex; - this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite) - .prepend(tex.transform.mapCoord); - this.uniforms.alpha = maskSprite.worldAlpha; - this.uniforms.maskClamp = tex.transform.uClampFrame; - - filterManager.applyFilter(this, input, output); - } -} diff --git a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.frag b/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.frag deleted file mode 100644 index 0e4aef8..0000000 --- a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.frag +++ /dev/null @@ -1,23 +0,0 @@ -varying vec2 vMaskCoord; -varying vec2 vTextureCoord; - -uniform sampler2D uSampler; -uniform sampler2D mask; -uniform float alpha; -uniform vec4 maskClamp; - -void main(void) -{ - float clip = step(3.5, - step(maskClamp.x, vMaskCoord.x) + - step(maskClamp.y, vMaskCoord.y) + - step(vMaskCoord.x, maskClamp.z) + - step(vMaskCoord.y, maskClamp.w)); - - vec4 original = texture2D(uSampler, vTextureCoord); - vec4 masky = texture2D(mask, vMaskCoord); - - original *= (masky.r * masky.a * alpha * clip); - - gl_FragColor = original; -} diff --git a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.vert b/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.vert deleted file mode 100644 index d23a30c..0000000 --- a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.vert +++ /dev/null @@ -1,16 +0,0 @@ -attribute vec2 aVertexPosition; -attribute vec2 aTextureCoord; - -uniform mat3 projectionMatrix; -uniform mat3 otherMatrix; - -varying vec2 vMaskCoord; -varying vec2 vTextureCoord; - -void main(void) -{ - gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); - - vTextureCoord = aTextureCoord; - vMaskCoord = ( otherMatrix * vec3( aTextureCoord, 1.0) ).xy; -} diff --git a/packages/core/src/renderers/systems/BatchSystem.js b/packages/core/src/renderers/systems/BatchSystem.js deleted file mode 100644 index 70e9518..0000000 --- a/packages/core/src/renderers/systems/BatchSystem.js +++ /dev/null @@ -1,68 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import ObjectRenderer from '../utils/ObjectRenderer'; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ - -export default class BatchSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this manager works for. - */ - constructor(renderer) - { - super(renderer); - - /** - * An empty renderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.emptyRenderer = new ObjectRenderer(renderer); - - /** - * The currently active ObjectRenderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.currentRenderer = this.emptyRenderer; - } - - /** - * Changes the current renderer to the one given in parameter - * - * @param {PIXI.ObjectRenderer} objectRenderer - The object renderer to use. - */ - setObjectRenderer(objectRenderer) - { - if (this.currentRenderer === objectRenderer) - { - return; - } - - this.currentRenderer.stop(); - this.currentRenderer = objectRenderer; - - this.renderer.state.setState(objectRenderer.state); - - this.currentRenderer.start(); - } - - /** - * This should be called if you wish to do some custom rendering - * It will basically render anything that may be batched up such as sprites - * - */ - flush() - { - this.setObjectRenderer(this.emptyRenderer); - } - - reset() - { - this.setObjectRenderer(this.emptyRenderer); - } -} diff --git a/packages/core/src/renderers/systems/ContextSystem.js b/packages/core/src/renderers/systems/ContextSystem.js deleted file mode 100644 index 43bf827..0000000 --- a/packages/core/src/renderers/systems/ContextSystem.js +++ /dev/null @@ -1,186 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { settings } from '@pixi/settings'; - -let CONTEXT_UID = 0; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class ContextSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this.webGLVersion = 1; - - this.handleContextLost = this.handleContextLost.bind(this); - this.handleContextRestored = this.handleContextRestored.bind(this); - - this.extensions = {}; - - renderer.view.addEventListener('webglcontextlost', this.handleContextLost, false); - renderer.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); - } - - get isLost() - { - return (!this.gl || this.gl.isContextLost()); - } - - contextChange(gl) - { - this.gl = gl; - - // restore a context if it was previously lost - if (gl.isContextLost() && gl.getExtension('WEBGL_lose_context')) - { - gl.getExtension('WEBGL_lose_context').restoreContext(); - } - } - - initFromContext(gl) - { - this.gl = gl; - this.validateContext(gl); - this.renderer.gl = gl; - this.renderer.CONTEXT_UID = CONTEXT_UID++; - this.renderer.runners.contextChange.run(gl); - } - - initFromOptions(options) - { - const gl = this.createContext(this.renderer.view, options); - - this.initFromContext(gl); - } - - /** - * Helper class to create a webGL Context - * - * @class - * @memberof PIXI.glCore - * @param canvas {HTMLCanvasElement} the canvas element that we will get the context from - * @param options {Object} An options object that gets passed in to the canvas element containing the context attributes, - * see https://developer.mozilla.org/en/docs/Web/API/HTMLCanvasElement/getContext for the options available - * @return {WebGLRenderingContext} the WebGL context - */ - createContext(canvas, options) - { - let gl; - - if (settings.PREFER_WEBGL_2) - { - gl = canvas.getContext('webgl2', options); - } - - if (gl) - { - this.webGLVersion = 2; - } - else - { - this.webGLVersion = 1; - - gl = canvas.getContext('webgl', options) - || canvas.getContext('experimental-webgl', options); - - if (!gl) - { - // fail, not able to get a context - throw new Error('This browser does not support webGL. Try using the canvas renderer'); - } - } - - this.gl = gl; - - this.getExtensions(); - - return gl; - } - - getExtensions() - { - // time to set up default etensions that pixi uses.. - const gl = this.gl; - const extensions = this.extensions; - - if (this.webGLVersion === 1) - { - extensions.drawBuffers = gl.getExtension('WEBGL_draw_buffers'); - extensions.depthTexture = gl.getExtension('WEBKIT_WEBGL_depth_texture'); - extensions.floatTexture = gl.getExtension('OES_texture_float'); - extensions.loseContext = gl.getExtension('WEBGL_lose_context'); - - extensions.vertexArrayObject = gl.getExtension('OES_vertex_array_object') - || gl.getExtension('MOZ_OES_vertex_array_object') - || gl.getExtension('WEBKIT_OES_vertex_array_object'); - } - - // we don't use any specific WebGL 2 ones yet! - } - - /** - * Handles a lost webgl context - * - * @private - * @param {WebGLContextEvent} event - The context lost event. - */ - handleContextLost(event) - { - event.preventDefault(); - } - - /** - * Handles a restored webgl context - * - * @private - */ - handleContextRestored() - { - this.renderer.runners.contextChange.run(this.gl); - } - - destroy() - { - const view = this.renderer.view; - - // remove listeners - view.removeEventListener('webglcontextlost', this.handleContextLost); - view.removeEventListener('webglcontextrestored', this.handleContextRestored); - - this.gl.useProgram(null); - - if (this.extensions.loseContext) - { - this.extensions.loseContext.loseContext(); - } - } - - postrender() - { - this.gl.flush(); - } - - validateContext(gl) - { - const attributes = gl.getContextAttributes(); - - // this is going to be fairly simple for now.. but at least we have room to grow! - if (!attributes.stencil) - { - /* eslint-disable max-len */ - - /* eslint-disable no-console */ - console.warn('Provided WebGL context does not have a stencil buffer, masks may not render correctly'); - /* eslint-enable no-console */ - - /* eslint-enable max-len */ - } - } -} diff --git a/packages/core/src/renderers/systems/FramebufferSystem.js b/packages/core/src/renderers/systems/FramebufferSystem.js deleted file mode 100644 index df7902b..0000000 --- a/packages/core/src/renderers/systems/FramebufferSystem.js +++ /dev/null @@ -1,246 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { Rectangle } from '@pixi/math'; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class FramebufferSystem extends WebGLSystem -{ - /** - * Sets up the renderer context and necessary buffers. - * - * @private - */ - contextChange() - { - this.gl = this.renderer.gl; - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - this.current = null; - this.viewport = new Rectangle(); - - this.drawBufferExtension = this.renderer.context.extensions.drawBuffers; - } - - bind(framebuffer, frame) - { - const gl = this.gl; - - this.current = framebuffer; - - if (framebuffer) - { - // TODO cacheing layer! - - const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID] || this.initFramebuffer(framebuffer); - - gl.bindFramebuffer(gl.FRAMEBUFFER, fbo.framebuffer); - // makesure all textures are unbound.. - - // now check for updates... - if (fbo.dirtyId !== framebuffer.dirtyId) - { - fbo.dirtyId = framebuffer.dirtyId; - - if (fbo.dirtyFormat !== framebuffer.dirtyFormat) - { - fbo.dirtyFormat = framebuffer.dirtyFormat; - this.updateFramebuffer(framebuffer); - } - else if (fbo.dirtySize !== framebuffer.dirtySize) - { - fbo.dirtySize = framebuffer.dirtySize; - this.resizeFramebuffer(framebuffer); - } - } - - for (let i = 0; i < framebuffer.colorTextures.length; i++) - { - if (framebuffer.colorTextures[i].texturePart) - { - this.renderer.texture.unbind(framebuffer.colorTextures[i].texture); - } - else - { - this.renderer.texture.unbind(framebuffer.colorTextures[i]); - } - } - - if (framebuffer.depthTexture) - { - this.renderer.texture.unbind(framebuffer.depthTexture); - } - - if (frame) - { - this.setViewport(frame.x, frame.y, frame.width, frame.height); - } - else - { - this.setViewport(0, 0, framebuffer.width, framebuffer.height); - } - } - else - { - gl.bindFramebuffer(gl.FRAMEBUFFER, null); - - if (frame) - { - this.setViewport(frame.x, frame.y, frame.width, frame.height); - } - else - { - this.setViewport(0, 0, this.renderer.width, this.renderer.height); - } - } - } - - setViewport(x, y, width, height) - { - const v = this.viewport; - - if (v.width !== width || v.height !== height || v.x !== x || v.y !== y) - { - v.x = x; - v.y = y; - v.width = width; - v.height = height; - - this.gl.viewport(x, y, width, height); - } - } - - get size() - { - if (this.current) - { - // TODO store temp - return { x: 0, y: 0, width: this.current.width, height: this.current.height }; - } - - return { x: 0, y: 0, width: this.renderer.width, height: this.renderer.height }; - } - - clear(r, g, b, a) - { - const gl = this.gl; - - // TODO clear color can be set only one right? - gl.clearColor(r, g, b, a); - gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); - } - - // private functions... - - initFramebuffer(framebuffer) - { - const gl = this.gl; - - // TODO - make this a class? - const fbo = { - framebuffer: gl.createFramebuffer(), - stencil: null, - dirtyId: 0, - dirtyFormat: 0, - dirtySize: 0, - }; - - framebuffer.glFrameBuffers[this.CONTEXT_UID] = fbo; - - return fbo; - } - - resizeFramebuffer(framebuffer) - { - const gl = this.gl; - - if (framebuffer.stencil || framebuffer.depth) - { - gl.bindRenderbuffer(gl.RENDERBUFFER, this.stencil); - gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); - } - } - - updateFramebuffer(framebuffer) - { - const gl = this.gl; - - const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID]; - - // bind the color texture - const colorTextures = framebuffer.colorTextures; - - let count = colorTextures.length; - - if (!this.drawBufferExtension) - { - count = Math.min(count, 1); - } - - const activeTextures = []; - - for (let i = 0; i < count; i++) - { - const texture = framebuffer.colorTextures[i]; - - if (texture.texturePart) - { - this.renderer.texture.bind(texture.texture, 0); - - gl.framebufferTexture2D(gl.FRAMEBUFFER, - gl.COLOR_ATTACHMENT0 + i, - gl.TEXTURE_CUBE_MAP_NEGATIVE_X + texture.side, - texture.texture._glTextures[this.CONTEXT_UID].texture, - 0); - } - else - { - this.renderer.texture.bind(texture, 0); - - gl.framebufferTexture2D(gl.FRAMEBUFFER, - gl.COLOR_ATTACHMENT0 + i, - gl.TEXTURE_2D, - texture._glTextures[this.CONTEXT_UID].texture, - 0); - } - - activeTextures.push(gl.COLOR_ATTACHMENT0 + i); - } - - if (this.drawBufferExtension && activeTextures.length > 1) - { - this.drawBufferExtension.drawBuffersWEBGL(activeTextures); - } - - if (framebuffer.depthTexture) - { - const depthTextureExt = this.renderer.context.extensions.depthTexture; - - if (depthTextureExt) - { - const depthTexture = framebuffer.depthTexture; - - this.renderer.texture.bind(depthTexture, 0); - - gl.framebufferTexture2D(gl.FRAMEBUFFER, - gl.DEPTH_ATTACHMENT, - gl.TEXTURE_2D, - depthTexture._glTextures[this.CONTEXT_UID].texture, - 0); - } - } - - if (framebuffer.stencil || framebuffer.depth) - { - fbo.stencil = gl.createRenderbuffer(); - - gl.bindRenderbuffer(gl.RENDERBUFFER, fbo.stencil); - - // TODO.. this is depth AND stencil? - gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, fbo.stencil); - gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); - // fbo.enableStencil(); - } - } -} diff --git a/packages/core/src/renderers/systems/MaskSystem.js b/packages/core/src/renderers/systems/MaskSystem.js deleted file mode 100644 index d9c4b1a..0000000 --- a/packages/core/src/renderers/systems/MaskSystem.js +++ /dev/null @@ -1,199 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import AlphaMaskFilter from '../filters/spriteMask/SpriteMaskFilter'; - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class MaskSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - // TODO - we don't need both! - this.scissor = false; - this.scissorData = null; - this.scissorRenderTarget = null; - - this.enableScissor = false; - - this.alphaMaskPool = []; - this.alphaMaskIndex = 0; - } - - /** - * Applies the Mask and adds it to the current filter stack. - * - * @param {PIXI.DisplayObject} target - Display Object to push the mask to - * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. - */ - push(target, maskData) - { - // TODO the root check means scissor rect will not - // be used on render textures more info here: - // https://github.com/pixijs/pixi.js/pull/3545 - - if (maskData.texture) - { - this.pushSpriteMask(target, maskData); - } - else if (this.enableScissor - && !this.scissor - && this.renderer._activeRenderTarget.root - && !this.renderer.stencil.stencilMaskStack.length - && maskData.isFastRect()) - { - const matrix = maskData.worldTransform; - - let rot = Math.atan2(matrix.b, matrix.a); - - // use the nearest degree! - rot = Math.round(rot * (180 / Math.PI)); - - if (rot % 90) - { - this.pushStencilMask(maskData); - } - else - { - this.pushScissorMask(target, maskData); - } - } - else - { - this.pushStencilMask(maskData); - } - } - - /** - * Removes the last mask from the mask stack and doesn't return it. - * - * @param {PIXI.DisplayObject} target - Display Object to pop the mask from - * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. - */ - pop(target, maskData) - { - if (maskData.texture) - { - this.popSpriteMask(target, maskData); - } - else if (this.enableScissor && !this.renderer.stencil.stencilMaskStack.length) - { - this.popScissorMask(target, maskData); - } - else - { - this.popStencilMask(target, maskData); - } - } - - /** - * Applies the Mask and adds it to the current filter stack. - * - * @param {PIXI.RenderTarget} target - Display Object to push the sprite mask to - * @param {PIXI.Sprite} maskData - Sprite to be used as the mask - */ - pushSpriteMask(target, maskData) - { - let alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; - - if (!alphaMaskFilter) - { - alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; - } - - alphaMaskFilter[0].resolution = this.renderer.resolution; - alphaMaskFilter[0].maskSprite = maskData; - - // TODO - may cause issues! - target.filterArea = maskData.getBounds(true); - - this.renderer.filterSystem.pushFilter(target, alphaMaskFilter); - - this.alphaMaskIndex++; - } - - /** - * Removes the last filter from the filter stack and doesn't return it. - * - */ - popSpriteMask() - { - this.renderer.filterSystem.popFilter(); - this.alphaMaskIndex--; - } - - /** - * Applies the Mask and adds it to the current filter stack. - * - * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. - */ - pushStencilMask(maskData) - { - this.renderer.batch.flush(); - this.renderer.stencil.pushStencil(maskData); - } - - /** - * Removes the last filter from the filter stack and doesn't return it. - * - */ - popStencilMask() - { - // this.renderer.currentRenderer.stop(); - this.renderer.stencil.popStencil(); - } - - /** - * - * @param {PIXI.DisplayObject} target - Display Object to push the mask to - * @param {PIXI.Graphics} maskData - The masking data. - */ - pushScissorMask(target, maskData) - { - maskData.renderable = true; - - const renderTarget = this.renderer._activeRenderTarget; - - const bounds = maskData.getBounds(); - - bounds.fit(renderTarget.size); - maskData.renderable = false; - - this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); - - const resolution = this.renderer.resolution; - - this.renderer.gl.scissor( - bounds.x * resolution, - (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, - bounds.width * resolution, - bounds.height * resolution - ); - - this.scissorRenderTarget = renderTarget; - this.scissorData = maskData; - this.scissor = true; - } - - /** - * - * - */ - popScissorMask() - { - this.scissorRenderTarget = null; - this.scissorData = null; - this.scissor = false; - - // must be scissor! - const gl = this.renderer.gl; - - gl.disable(gl.SCISSOR_TEST); - } -} diff --git a/packages/core/src/renderers/systems/ProjectionSystem.js b/packages/core/src/renderers/systems/ProjectionSystem.js deleted file mode 100644 index ec5efdb..0000000 --- a/packages/core/src/renderers/systems/ProjectionSystem.js +++ /dev/null @@ -1,73 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { Matrix } from '@pixi/math'; - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ - -export default class ProjectionSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this.projectionMatrix = new Matrix(); - } - - update(destinationFrame, sourceFrame, resolution, root) - { - this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; - this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; - - this.calculateProjection(this.destinationFrame, this.sourceFrame, resolution, root); - - this.renderer.globalUniforms.uniforms.projectionMatrix = this.projectionMatrix; - this.renderer.globalUniforms.update(); - } - - /** - * Updates the projection matrix based on a projection frame (which is a rectangle) - * - * @param {Rectangle} destinationFrame - The destination frame. - * @param {Rectangle} sourceFrame - The source frame. - */ - calculateProjection(destinationFrame, sourceFrame, resolution, root) - { - const pm = this.projectionMatrix; - - // I don't think we will need this line.. - // pm.identity(); - - if (!root) - { - pm.a = (1 / destinationFrame.width * 2) * resolution; - pm.d = (1 / destinationFrame.height * 2) * resolution; - - pm.tx = -1 - (sourceFrame.x * pm.a); - pm.ty = -1 - (sourceFrame.y * pm.d); - } - else - { - pm.a = (1 / destinationFrame.width * 2) * resolution; - pm.d = (-1 / destinationFrame.height * 2) * resolution; - - pm.tx = -1 - (sourceFrame.x * pm.a); - pm.ty = 1 - (sourceFrame.y * pm.d); - } - } - - /** - * Sets the transform of the active render target to the given matrix - * - * @param {PIXI.Matrix} matrix - The transformation matrix - */ - setTransform()// matrix) - { - // this._activeRenderTarget.transform = matrix; - } -} diff --git a/packages/core/src/renderers/systems/RenderTextureSystem.js b/packages/core/src/renderers/systems/RenderTextureSystem.js deleted file mode 100644 index a133ef1..0000000 --- a/packages/core/src/renderers/systems/RenderTextureSystem.js +++ /dev/null @@ -1,115 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { Rectangle } from '@pixi/math'; - -const tempRect = new Rectangle(); - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ - -export default class RenderTextureSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this.clearColor = renderer._backgroundColorRgba; - - // TODO moe this property somewhere else! - this.defaultMaskStack = []; - this.defaultFilterStack = [{}]; - - // empty render texture? - this.renderTexture = null; - - this.destinationFrame = new Rectangle(); - } - - bind(renderTexture, sourceFrame, destinationFrame) - { - // TODO - do we want this?? - if (this.renderTexture === renderTexture) return; - this.renderTexture = renderTexture; - - const renderer = this.renderer; - - if (renderTexture) - { - const baseTexture = renderTexture.baseTexture; - - if (!destinationFrame) - { - tempRect.width = baseTexture.realWidth; - tempRect.height = baseTexture.realHeight; - - destinationFrame = tempRect; - } - - if (!sourceFrame) - { - sourceFrame = destinationFrame; - } - - this.renderer.framebuffer.bind(baseTexture.frameBuffer, destinationFrame); - - this.renderer.projection.update(destinationFrame, sourceFrame, baseTexture.resolution, false); - this.renderer.stencil.setMaskStack(baseTexture.stencilMaskStack); - } - else - { - // TODO these validation checks happen deeper down.. - // thing they can be avoided.. - if (!destinationFrame) - { - tempRect.width = renderer.width; - tempRect.height = renderer.height; - - destinationFrame = tempRect; - } - - if (!sourceFrame) - { - sourceFrame = destinationFrame; - } - - renderer.framebuffer.bind(null, destinationFrame); - - // TODO store this.. - this.renderer.projection.update(destinationFrame, sourceFrame, this.renderer.resolution, true); - this.renderer.stencil.setMaskStack(this.defaultMaskStack); - } - - this.destinationFrame.copyFrom(destinationFrame); - } - - /** - * Erases the render texture and fills the drawing area with a colour - * - * @param {number} [clearColor] - The colour - * @return {PIXI.Renderer} Returns itself. - */ - clear(clearColor) - { - if (this.renderTexture) - { - clearColor = clearColor || this.renderTexture.baseTexture.clearColor; - } - else - { - clearColor = clearColor || this.clearColor; - } - - this.renderer.framebuffer.clear(clearColor[0], clearColor[1], clearColor[2], clearColor[3]); - } - - resize()// screenWidth, screenHeight) - { - // resize the root only! - this.bind(null); - } -} diff --git a/packages/core/src/renderers/systems/StateSystem.js b/packages/core/src/renderers/systems/StateSystem.js deleted file mode 100755 index d7f4caf..0000000 --- a/packages/core/src/renderers/systems/StateSystem.js +++ /dev/null @@ -1,288 +0,0 @@ -import mapWebGLBlendModesToPixi from '../utils/mapWebGLBlendModesToPixi'; -import WebGLSystem from './WebGLSystem'; -import WebGLState from '../State'; - -const BLEND = 0; -const OFFSET = 1; -const CULLING = 2; -const DEPTH_TEST = 3; -const WINDING = 4; - -/** - * A WebGL state machines - * - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class StateSystem extends WebGLSystem -{ - /** - * @param {WebGLRenderingContext} gl - The current WebGL rendering context - */ - constructor(renderer) - { - super(renderer); - - this.gl = null; - - this.maxAttribs = null; - - // check we have vao.. - this.nativeVaoExtension = null; - - this.attribState = null; - - this.stateId = 0; - this.polygonOffset = 0; - this.blendMode = 17; - - this.map = []; - - // map functions for when we set state.. - this.map[BLEND] = this.setBlend; - this.map[OFFSET] = this.setOffset; - this.map[CULLING] = this.setCullFace; - this.map[DEPTH_TEST] = this.setDepthTest; - this.map[WINDING] = this.setFrontFace; - - this.checks = []; - - this.defaultState = new WebGLState(); - this.defaultState.blend = true; - this.defaultState.depth = true; - } - - contextChange(gl) - { - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; - - this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); - - // check we have vao.. - this.nativeVaoExtension = ( - gl.getExtension('OES_vertex_array_object') - || gl.getExtension('MOZ_OES_vertex_array_object') - || gl.getExtension('WEBKIT_OES_vertex_array_object') - ); - - this.attribState = { - tempAttribState: new Array(this.maxAttribs), - attribState: new Array(this.maxAttribs), - }; - - this.blendModes = mapWebGLBlendModesToPixi(gl); - - this.setState(this.defaultState); - - this.reset(); - } - - /** - * Sets the current state - * - * @param {*} state - The state to set. - */ - setState(state) - { - state = state || this.defaultState; - - // TODO maybe to an object check? ( this.state === state )? - if (this.stateId !== state.data) - { - let diff = this.stateId ^ state.data; - let i = 0; - - // order from least to most common - while (diff) - { - if (diff & 1) - { - // state change! - this.map[i].call(this, !!(state.data & (1 << i))); - } - - diff = diff >> 1; - i++; - } - - this.stateId = state.data; - } - - // based on the above settings we check for specific modes.. - // for example if blend is active we check and set the blend modes - // or of polygon offset is active we check the poly depth. - for (let i = 0; i < this.checks.length; i++) - { - this.checks[i](this, state); - } - } - - /** - * Enables or disabled blending. - * - * @param {boolean} value - Turn on or off webgl blending. - */ - setBlend(value) - { - this.updateCheck(StateSystem.checkBlendMode, value); - - this.gl[value ? 'enable' : 'disable'](this.gl.BLEND); - } - - /** - * Enables or disable polygon offset fill - * - * @param {boolean} value - Turn on or off webgl polygon offset testing. - */ - setOffset(value) - { - this.gl[value ? 'enable' : 'disable'](this.gl.POLYGON_OFFSET_FILL); - } - - /** - * Sets whether to enable or disable depth test. - * - * @param {boolean} value - Turn on or off webgl depth testing. - */ - setDepthTest(value) - { - this.gl[value ? 'enable' : 'disable'](this.gl.DEPTH_TEST); - } - - /** - * Sets whether to enable or disable cull face. - * - * @param {boolean} value - Turn on or off webgl cull face. - */ - setCullFace(value) - { - this.gl[value ? 'enable' : 'disable'](this.gl.CULL_FACE); - } - - /** - * Sets the gl front face. - * - * @param {boolean} value - true is clockwise and false is counter-clockwise - */ - setFrontFace(value) - { - this.gl.frontFace(this.gl[value ? 'CW' : 'CCW']); - } - - /** - * Sets the blend mode. - * - * @param {number} value - The blend mode to set to. - */ - setBlendMode(value) - { - if (value === this.blendMode) - { - return; - } - - this.blendMode = value; - this.gl.blendFunc(this.blendModes[value][0], this.blendModes[value][1]); - } - - /** - * Sets the polygon offset. - * - * @param {number} value - the polygon offset - * @param {number} scale - the polygon offset scale - */ - setPolygonOffset(value, scale) - { - this.gl.polygonOffset(value, scale); - } - - /** - * Disables all the vaos in use - * - */ - resetAttributes() - { - for (let i = 0; i < this.attribState.tempAttribState.length; i++) - { - this.attribState.tempAttribState[i] = 0; - } - - for (let i = 0; i < this.attribState.attribState.length; i++) - { - this.attribState.attribState[i] = 0; - } - - // im going to assume one is always active for performance reasons. - for (let i = 1; i < this.maxAttribs; i++) - { - this.gl.disableVertexAttribArray(i); - } - } - - // used - /** - * Resets all the logic and disables the vaos - */ - reset() - { - // unbind any VAO if they exist.. - if (this.nativeVaoExtension) - { - this.nativeVaoExtension.bindVertexArrayOES(null); - } - - // reset all attributes.. - this.resetAttributes(); - - this.gl.pixelStorei(this.gl.UNPACK_FLIP_Y_WEBGL, false); - - this.setBlendMode(0); - - // TODO? - // this.setState(this.defaultState); - } - - /** - * checks to see which updates should be checked based on which settings have been activated - * for example if blend is enabled then we shold check the blend modes each time the state is changed - * or if poygon fill is activated then we need to check if the polygone offset changes. - * The idea is that we only check what we have too. - * - * @param {Function} func the checking function to add or remove - * @param {boolean} value should the check function be added or removed. - */ - updateCheck(func, value) - { - const index = this.checks.indexOf(func); - - if (value && index === -1) - { - this.checks.push(func); - } - else if (!value && index !== -1) - { - this.checks.splice(index, 1); - } - } - - /** - * A private little wrapper function that we call to check the blend mode. - * - * @static - * @private - * @param {PIXI.StateSystem} System the System to perform the state check on - * @param {PIXI.State} state the state that the blendMode will pulled from - */ - static checkBlendMode(system, state) - { - system.setBlendMode(state.blendMode); - } - - // TODO - add polygon offset? -} diff --git a/packages/core/src/renderers/systems/StencilSystem.js b/packages/core/src/renderers/systems/StencilSystem.js deleted file mode 100644 index 5d538cb..0000000 --- a/packages/core/src/renderers/systems/StencilSystem.js +++ /dev/null @@ -1,133 +0,0 @@ -import WebGLSystem from './WebGLSystem'; - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class StencilSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - this.stencilMaskStack = []; - } - - /** - * Changes the mask stack that is used by this System. - * - * @param {PIXI.Graphics[]} stencilMaskStack - The mask stack - */ - setMaskStack(stencilMaskStack) - { - const gl = this.renderer.gl; - - if (stencilMaskStack.length !== this.stencilMaskStack.length) - { - if (stencilMaskStack.length === 0) - { - gl.disable(gl.STENCIL_TEST); - } - else - { - gl.enable(gl.STENCIL_TEST); - } - } - - this.stencilMaskStack = stencilMaskStack; - } - - /** - * Applies the Mask and adds it to the current stencil stack. @alvin - * - * @param {PIXI.Graphics} graphics - The mask - */ - pushStencil(graphics) - { - this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); - - // this.renderer._activeRenderTarget.attachStencilBuffer(); - - const gl = this.renderer.gl; - const prevMaskCount = this.stencilMaskStack.length; - - if (prevMaskCount === 0) - { - gl.enable(gl.STENCIL_TEST); - } - - this.stencilMaskStack.push(graphics); - - // Increment the refference stencil value where the new mask overlaps with the old ones. - gl.colorMask(false, false, false, false); - gl.stencilFunc(gl.EQUAL, prevMaskCount, this._getBitwiseMask()); - gl.stencilOp(gl.KEEP, gl.KEEP, gl.INCR); - this.renderer.plugins.graphics.render(graphics); - - this._useCurrent(); - } - - /** - * Removes the last mask from the stencil stack. @alvin - */ - popStencil() - { - this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); - - const gl = this.renderer.gl; - const graphics = this.stencilMaskStack.pop(); - - if (this.stencilMaskStack.length === 0) - { - // the stack is empty! - gl.disable(gl.STENCIL_TEST); - gl.clear(gl.STENCIL_BUFFER_BIT); - gl.clearStencil(0); - } - else - { - // Decrement the refference stencil value where the popped mask overlaps with the other ones - gl.colorMask(false, false, false, false); - gl.stencilOp(gl.KEEP, gl.KEEP, gl.DECR); - this.renderer.plugins.graphics.render(graphics); - - this._useCurrent(); - } - } - - /** - * Setup renderer to use the current stencil data. - */ - _useCurrent() - { - const gl = this.renderer.gl; - - gl.colorMask(true, true, true, true); - gl.stencilFunc(gl.EQUAL, this.stencilMaskStack.length, this._getBitwiseMask()); - gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP); - } - - /** - * Fill 1s equal to the number of acitve stencil masks. - * - * @return {number} The bitwise mask. - */ - _getBitwiseMask() - { - return (1 << this.stencilMaskStack.length) - 1; - } - - /** - * Destroys the mask stack. - * - */ - destroy() - { - super.destroy(this); - - this.stencilMaskStack = null; - } -} diff --git a/packages/core/src/renderers/systems/WebGLSystem.js b/packages/core/src/renderers/systems/WebGLSystem.js deleted file mode 100644 index d3d5b31..0000000 --- a/packages/core/src/renderers/systems/WebGLSystem.js +++ /dev/null @@ -1,41 +0,0 @@ -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this manager works for. - */ - constructor(renderer) - { - /** - * The renderer this manager works for. - * - * @member {PIXI.Renderer} - */ - this.renderer = renderer; - - this.renderer.runners.contextChange.add(this); - } - - /** - * Generic method called when there is a WebGL context change. - * - */ - contextChange() - { - // do some codes init! - } - - /** - * Generic destroy methods to be overridden by the subclass - * - */ - destroy() - { - this.renderer.runners.contextChange.remove(this); - this.renderer = null; - } -} diff --git a/packages/core/src/renderers/systems/filter/FilterSystem.js b/packages/core/src/renderers/systems/filter/FilterSystem.js deleted file mode 100644 index e615037..0000000 --- a/packages/core/src/renderers/systems/filter/FilterSystem.js +++ /dev/null @@ -1,431 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; - -import RenderTexture from '../../../textures/RenderTexture'; -import Quad from '../../utils/Quad'; -import QuadUv from '../../utils/QuadUv'; -import { Rectangle } from '@pixi/math'; -import * as filterTransforms from '../../filters/filterTransforms'; -import bitTwiddle from 'bit-twiddle'; -import UniformGroup from '../../../shader/UniformGroup'; -import { DRAW_MODES } from '../../../../../constants'; - -// -/** - * @ignore - * @class - */ -class FilterState -{ - /** - * - */ - constructor() - { - this.renderTexture = null; - this.sourceFrame = new Rectangle(); - this.destinationFrame = new Rectangle(); - this.filters = []; - this.target = null; - this.legacy = false; - this.resolution = 1; - } -} - -/** - * @class - * @memberof PIXI.systems - * @extends PIXI.systems.WebGLSystem - */ -export default class FilterSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - /** - * stores a bunch of PO2 textures used for filtering - * @type {Object} - */ - this.texturePool = {}; - - /** - * a pool for storing filter states, save us creating new ones each tick - * @type {Array} - */ - this.statePool = []; - - /** - * A very simple geometry used when drawing a filter effect to the screen - * @type {Quad} - */ - this.quad = new Quad(); - - this.quadUv = new QuadUv(); - - /** - * Temporary rect for maths - * @type {PIXI.Rectangle} - */ - this.tempRect = new Rectangle(); - - this.activeState = {}; - - /** - * this uniform group is attached to filter uniforms when used - * @type {UniformGroup} - */ - this.globalUniforms = new UniformGroup({ - sourceFrame: this.tempRect, - destinationFrame: this.tempRect, - - // legacy variables - filterArea: new Float32Array(4), - filterClamp: new Float32Array(4), - }, true); - } - - /** - * Adds a new filter to the System. - * - * @param {PIXI.DisplayObject} target - The target of the filter to render. - * @param {PIXI.Filter[]} filters - The filters to apply. - */ - push(target, filters) - { - const renderer = this.renderer; - const filterStack = this.renderer.renderTexture.defaultFilterStack; - const state = this.statePool.pop() || new FilterState(); - - let resolution = filters[0].resolution; - let padding = filters[0].padding; - let autoFit = filters[0].autoFit; - let legacy = filters[0].legacy; - - for (let i = 1; i < filters.length; i++) - { - const filter = filters[i]; - - // lets use the lowest resolution.. - resolution = Math.min(resolution, filter.resolution); - // and the largest amount of padding! - padding = Math.max(padding, filter.padding); - // only auto fit if all filters are autofit - autoFit = autoFit || filter.autoFit; - - legacy = legacy || filter.legacy; - } - - filterStack.push(state); - - state.resolution = resolution; - - state.legacy = legacy; - - // round to whole number based on resolution - // TODO move that to the shader too? - state.sourceFrame = target.filterArea || target.getBounds(true); - - state.sourceFrame.pad(padding); - - if (autoFit) - { - state.sourceFrame.fit(this.renderer.renderTexture.destinationFrame); - } - - state.sourceFrame.round(resolution); - - state.renderTexture = this.getPotFilterTexture(state.sourceFrame.width, state.sourceFrame.height, resolution); - state.filters = filters; - - state.destinationFrame.width = state.renderTexture.width; - state.destinationFrame.height = state.renderTexture.height; - - state.renderTexture.filterFrame = state.sourceFrame; - renderer.renderTexture.bind(state.renderTexture, state.sourceFrame);// /, state.destinationFrame); - renderer.renderTexture.clear(); - } - - /** - * Pops off the filter and applies it. - * - */ - pop() - { - const renderer = this.renderer; - const filterStack = renderer.renderTexture.defaultFilterStack; - const state = filterStack.pop(); - const filters = state.filters; - - this.activeState = state; - - const globalUniforms = this.globalUniforms.uniforms; - - globalUniforms.sourceFrame = state.sourceFrame; - globalUniforms.destinationFrame = state.destinationFrame; - globalUniforms.resolution = state.resolution; - - // only update the rect if its legacy.. - if (state.legacy) - { - const filterArea = globalUniforms.filterArea; - const filterClamp = globalUniforms.filterClamp; - - filterArea[0] = state.destinationFrame.width; - filterArea[1] = state.destinationFrame.height; - filterArea[2] = state.sourceFrame.x; - filterArea[3] = state.sourceFrame.y; - - filterClamp[0] = 0.5 / state.resolution / state.destinationFrame.width; - filterClamp[1] = 0.5 / state.resolution / state.destinationFrame.height; - filterClamp[2] = (state.sourceFrame.width - 0.5) / state.resolution / state.destinationFrame.width; - filterClamp[3] = (state.sourceFrame.height - 0.5) / state.resolution / state.destinationFrame.height; - } - - this.globalUniforms.update(); - - const lastState = filterStack[filterStack.length - 1]; - - if (filters.length === 1) - { - filters[0].apply(this, state.renderTexture, lastState.renderTexture, false, state); - - this.returnFilterTexture(state.renderTexture); - } - else - { - let flip = state.renderTexture; - let flop = this.getPotFilterTexture( - flip.width, - flip.height, - state.resolution - ); - - flop.filterFrame = flip.filterFrame; - - let i = 0; - - for (i = 0; i < filters.length - 1; ++i) - { - filters[i].apply(this, flip, flop, true, state); - - const t = flip; - - flip = flop; - flop = t; - } - - filters[i].apply(this, flip, lastState.renderTexture, false, state); - - this.returnFilterTexture(flip); - this.returnFilterTexture(flop); - } - - this.statePool.push(state); - } - - /** - * Draws a filter. - * - * @param {PIXI.Filter} filter - The filter to draw. - * @param {PIXI.RenderTarget} input - The input render target. - * @param {PIXI.RenderTarget} output - The target to output to. - * @param {boolean} clear - Should the output be cleared before rendering to it - */ - applyFilter(filter, input, output, clear) - { - const renderer = this.renderer; - - renderer.renderTexture.bind(output, output ? output.filterFrame : null); - - if (clear) - { - // gl.disable(gl.SCISSOR_TEST); - renderer.renderTexture.clear(); - // gl.enable(gl.SCISSOR_TEST); - } - - // set the uniforms.. - filter.uniforms.uSampler = input; - filter.uniforms.filterGlobals = this.globalUniforms; - - // TODO make it so that the order of this does not matter.. - // because it does at the moment cos of global uniforms. - // they need to get resynced - - renderer.state.setState(filter.state); - renderer.shader.bind(filter); - - if (filter.legacy) - { - this.quadUv.map(input._frame, input.filterFrame); - - renderer.geometry.bind(this.quadUv); - renderer.geometry.draw(DRAW_MODES.TRIANGLES); - } - else - { - renderer.geometry.bind(this.quad); - renderer.geometry.draw(DRAW_MODES.TRIANGLE_STRIP); - } - } - - /** - * Calculates the mapped matrix. - * - * TODO playing around here.. this is temporary - (will end up in the shader) - * this returns a matrix that will normalise map filter cords in the filter to screen space - * - * @param {PIXI.Matrix} outputMatrix - the matrix to output to. - * @return {PIXI.Matrix} The mapped matrix. - */ - calculateScreenSpaceMatrix(outputMatrix) - { - const currentState = this.activeState; - - return filterTransforms.calculateScreenSpaceMatrix( - outputMatrix, - currentState.sourceFrame, - currentState.destinationFrame - ); - } - - /** - * This will map the filter coord so that a texture can be used based on the transform of a sprite - * - * @param {PIXI.Matrix} outputMatrix - The matrix to output to. - * @param {PIXI.Sprite} sprite - The sprite to map to. - * @return {PIXI.Matrix} The mapped matrix. - */ - calculateSpriteMatrix(outputMatrix, sprite) - { - const currentState = this.activeState; - - return filterTransforms.calculateSpriteMatrix( - outputMatrix, - currentState.sourceFrame, - currentState.destinationFrame, - sprite - ); - } - - /** - * Destroys this Filter System. - * - * @param {boolean} [contextLost=false] context was lost, do not free shaders - * - */ - destroy(contextLost = false) - { - if (!contextLost) - { - this.emptyPool(); - } - else - { - this.texturePool = {}; - } - } - - /** - * Gets a Power-of-Two render texture. - * - * TODO move to a seperate class could be on renderer? - * also - could cause issue with multiple contexts? - * - * @private - * @param {WebGLRenderingContext} gl - The webgl rendering context - * @param {number} minWidth - The minimum width of the render target. - * @param {number} minHeight - The minimum height of the render target. - * @param {number} resolution - The resolution of the render target. - * @return {PIXI.RenderTarget} The new render target. - */ - getPotFilterTexture(minWidth, minHeight, resolution) - { - minWidth = bitTwiddle.nextPow2(minWidth); - minHeight = bitTwiddle.nextPow2(minHeight); - resolution = resolution || 1; - - const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); - - if (!this.texturePool[key]) - { - this.texturePool[key] = []; - } - - let renderTexture = this.texturePool[key].pop(); - - if (!renderTexture) - { - // temporary bypass cache.. - // internally - this will cause a texture to be bound.. - renderTexture = RenderTexture.create({ - width: minWidth, - height: minHeight, - resolution, - }); - } - - return renderTexture; - } - - /** - * Gets extra render texture to use inside current filter - * - * @param {number} resolution resolution of the renderTexture - * @returns {PIXI.RenderTexture} - */ - getFilterTexture(resolution) - { - const rt = this.activeState.renderTexture; - - const filterTexture = this.getPotFilterTexture(rt.width, rt.height, resolution || rt.baseTexture.resolution); - - filterTexture.filterFrame = rt.filterFrame; - - return filterTexture; - } - - /** - * Frees a render texture back into the pool. - * - * @param {PIXI.RenderTarget} renderTexture - The renderTarget to free - */ - returnFilterTexture(renderTexture) - { - renderTexture.filterFrame = null; - - const base = renderTexture.baseTexture; - - const minWidth = base.width; - const minHeight = base.height; - - const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); - - this.texturePool[key].push(renderTexture); - } - - /** - * Empties the texture pool. - * - */ - emptyPool() - { - for (const i in this.texturePool) - { - const textures = this.texturePool[i]; - - if (textures) - { - for (let j = 0; j < textures.length; j++) - { - textures[j].destroy(true); - } - } - } - - this.texturePool = {}; - } -} diff --git a/packages/core/src/renderers/systems/geometry/GLBuffer.js b/packages/core/src/renderers/systems/geometry/GLBuffer.js deleted file mode 100644 index 0477e8a..0000000 --- a/packages/core/src/renderers/systems/geometry/GLBuffer.js +++ /dev/null @@ -1,9 +0,0 @@ -export default class GLBuffer -{ - constructor(buffer) - { - this.buffer = buffer; - this.updateID = -1; - this.byteLength = -1; - } -} diff --git a/packages/core/src/renderers/systems/geometry/GeometrySystem.js b/packages/core/src/renderers/systems/geometry/GeometrySystem.js deleted file mode 100644 index 26c8e9e..0000000 --- a/packages/core/src/renderers/systems/geometry/GeometrySystem.js +++ /dev/null @@ -1,388 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; -import GLBuffer from './GLBuffer'; - -const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class GeometrySystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this._activeGeometry = null; - this._activeVao = null; - - this.hasVao = true; - this.hasInstance = true; - } - - /** - * Sets up the renderer context and necessary buffers. - * - * @private - */ - contextChange() - { - const gl = this.gl = this.renderer.gl; - - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - - // webgl2 - if (!gl.createVertexArray) - { - // webgl 1! - let nativeVaoExtension = this.renderer.context.extensions.vertexArrayObject; - - if (this.renderer.legacy) - { - nativeVaoExtension = null; - } - - if (nativeVaoExtension) - { - gl.createVertexArray = () => - nativeVaoExtension.createVertexArrayOES(); - - gl.bindVertexArray = (vao) => - nativeVaoExtension.bindVertexArrayOES(vao); - - gl.deleteVertexArray = (vao) => - nativeVaoExtension.deleteVertexArrayOES(vao); - } - else - { - this.hasVao = false; - gl.createVertexArray = () => - { - // empty - }; - - gl.bindVertexArray = () => - { - // empty - }; - - gl.deleteVertexArray = () => - { - // empty - }; - } - } - - if (!gl.vertexAttribDivisor) - { - const instanceExt = gl.getExtension('ANGLE_instanced_arrays'); - - if (instanceExt) - { - gl.vertexAttribDivisor = (a, b) => - instanceExt.vertexAttribDivisorANGLE(a, b); - - gl.drawElementsInstanced = (a, b, c, d, e) => - instanceExt.drawElementsInstancedANGLE(a, b, c, d, e); - - gl.drawArraysInstanced = (a, b, c, d) => - instanceExt.drawArraysInstancedANGLE(a, b, c, d); - } - else - { - this.hasInstance = false; - } - } - } - - /** - * Binds geometry so that is can be drawn. Creating a Vao if required - * @private - * @param {PIXI.Geometry} geometry instance of geometry to bind - */ - bind(geometry, shader) - { - shader = shader || this.renderer.shader.shader; - - const gl = this.gl; - - // not sure the best way to address this.. - // currently different shaders require different VAOs for the same geometry - // Still mulling over the best way to solve this one.. - // will likely need to modify the shader attribute locations at run time! - let vaos = geometry.glVertexArrayObjects[this.CONTEXT_UID]; - - if (!vaos) - { - geometry.glVertexArrayObjects[this.CONTEXT_UID] = vaos = {}; - } - - const vao = vaos[shader.program.id] || this.initGeometryVao(geometry, shader.program); - - this._activeGeometry = geometry; - - if (this._activeVao !== vao) - { - this._activeVao = vao; - - if (this.hasVao) - { - gl.bindVertexArray(vao); - } - else - { - this.activateVao(geometry, shader.program); - } - } - - // TODO - optimise later! - // don't need to loop through if nothing changed! - // maybe look to add an 'autoupdate' to geometry? - this.updateBuffers(); - } - - reset() - { - this.unbind(); - } - - updateBuffers() - { - const geometry = this._activeGeometry; - const gl = this.gl; - - for (let i = 0; i < geometry.buffers.length; i++) - { - const buffer = geometry.buffers[i]; - - const glBuffer = buffer._glBuffers[this.CONTEXT_UID]; - - if (buffer._updateID !== glBuffer.updateID) - { - glBuffer.updateID = buffer._updateID; - - // TODO can cache this on buffer! maybe added a getter / setter? - const type = buffer.index ? gl.ELEMENT_ARRAY_BUFFER : gl.ARRAY_BUFFER; - const drawType = buffer.static ? gl.STATIC_DRAW : gl.DYNAMIC_DRAW; - - gl.bindBuffer(type, glBuffer.buffer); - - if (glBuffer.byteLength >= buffer.data.byteLength) - { - // offset is always zero for now! - gl.bufferSubData(type, 0, buffer.data); - } - else - { - gl.bufferData(type, buffer.data, drawType); - } - } - } - } - - checkCompatability(geometry, program) - { - // geometry must have at least all the attributes that the shader requires. - const geometryAttributes = geometry.attributes; - const shaderAttributes = program.attributeData; - - for (const j in shaderAttributes) - { - if (!geometryAttributes[j]) - { - throw new Error(`shader and geometry incompatible, geometry missing the "${j}" attribute`); - } - } - } - - /** - * Creates a Vao with the same structure as the geometry and stores it on the geometry. - * @private - * @param {PIXI.Geometry} geometry instance of geometry to to generate Vao for - */ - initGeometryVao(geometry, program) - { - this.checkCompatability(geometry, program); - - const gl = this.gl; - const CONTEXT_UID = this.CONTEXT_UID; - const buffers = geometry.buffers; - const attributes = geometry.attributes; - - const tempStride = {}; - const tempStart = {}; - - for (const j in buffers) - { - tempStride[j] = 0; - tempStart[j] = 0; - } - - for (const j in attributes) - { - if (!attributes[j].size && program.attributeData[j]) - { - attributes[j].size = program.attributeData[j].size; - } - - tempStride[attributes[j].buffer] += attributes[j].size * byteSizeMap[attributes[j].type]; - } - - for (const j in attributes) - { - const attribute = attributes[j]; - const attribSize = attribute.size; - - if (attribute.stride === undefined) - { - if (tempStride[attribute.buffer] === attribSize * byteSizeMap[attribute.type]) - { - attribute.stride = 0; - } - else - { - attribute.stride = tempStride[attribute.buffer]; - } - } - - if (attribute.start === undefined) - { - attribute.start = tempStart[attribute.buffer]; - - tempStart[attribute.buffer] += attribSize * byteSizeMap[attribute.type]; - } - } - - // first update - and create the buffers! - // only create a gl buffer if it actually gets - for (let i = 0; i < buffers.length; i++) - { - const buffer = buffers[i]; - - if (!buffer._glBuffers[CONTEXT_UID]) - { - buffer._glBuffers[CONTEXT_UID] = new GLBuffer(gl.createBuffer()); - } - } - - // TODO - maybe make this a data object? - // lets wait to see if we need to first! - const vao = gl.createVertexArray(); - - gl.bindVertexArray(vao); - - this.activateVao(geometry, program); - - gl.bindVertexArray(null); - - // add it to the cache! - geometry.glVertexArrayObjects[this.CONTEXT_UID][program.id] = vao; - - return vao; - } - - activateVao(geometry, program) - { - const gl = this.gl; - const CONTEXT_UID = this.CONTEXT_UID; - const buffers = geometry.buffers; - const attributes = geometry.attributes; - - if (geometry.indexBuffer) - { - // first update the index buffer if we have one.. - gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, geometry.indexBuffer._glBuffers[CONTEXT_UID].buffer); - } - - let lastBuffer = null; - - // add a new one! - for (const j in attributes) - { - const attribute = attributes[j]; - const buffer = buffers[attribute.buffer]; - const glBuffer = buffer._glBuffers[CONTEXT_UID]; - - if (program.attributeData[j]) - { - if (lastBuffer !== glBuffer) - { - gl.bindBuffer(gl.ARRAY_BUFFER, glBuffer.buffer); - - lastBuffer = glBuffer; - } - - const location = program.attributeData[j].location; - - // TODO introduce state again - // we can optimise this for older devices that have no VAOs - gl.enableVertexAttribArray(location); - - gl.vertexAttribPointer(location, - attribute.size, - attribute.type || gl.FLOAT, - attribute.normalized, - attribute.stride, - attribute.start); - - if (attribute.instance) - { - // TODO calculate instance count based of this... - if (this.hasInstance) - { - gl.vertexAttribDivisor(location, 1); - } - else - { - throw new Error('geometry error, GPU Instancing is not supported on this device'); - } - } - } - } - } - - draw(type, size, start, instanceCount) - { - const gl = this.gl; - const geometry = this._activeGeometry; - - // TODO.. this should not change so maybe cache the function? - - if (geometry.indexBuffer) - { - if (geometry.instanced) - { - /* eslint-disable max-len */ - gl.drawElementsInstanced(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2, instanceCount || 1); - /* eslint-enable max-len */ - } - else - { - gl.drawElements(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2); - } - } - else - if (geometry.instanced) - { - // TODO need a better way to calculate size.. - gl.drawArraysInstanced(type, start, size || geometry.getSize(), instanceCount || 1); - } - else - { - gl.drawArrays(type, start, size || geometry.getSize()); - } - - return this; - } - - unbind() - { - this.gl.bindVertexArray(null); - this._activeVao = null; - this._activeGeometry = null; - } -} diff --git a/packages/core/src/renderers/systems/geometry/setVertexAttribArrays.js b/packages/core/src/renderers/systems/geometry/setVertexAttribArrays.js deleted file mode 100644 index 23b6959..0000000 --- a/packages/core/src/renderers/systems/geometry/setVertexAttribArrays.js +++ /dev/null @@ -1,54 +0,0 @@ -// var GL_MAP = {}; - -/** - * @param gl {WebGLRenderingContext} The current WebGL context - * @param attribs {*} - * @param state {*} - */ -export default function setVertexAttribArrays(gl, attribs, state) -{ - let i; - - if (state) - { - const tempAttribState = state.tempAttribState; - const attribState = state.attribState; - - for (i = 0; i < tempAttribState.length; i++) - { - tempAttribState[i] = false; - } - - // set the new attribs - for (i = 0; i < attribs.length; i++) - { - tempAttribState[attribs[i].attribute.location] = true; - } - - for (i = 0; i < attribState.length; i++) - { - if (attribState[i] !== tempAttribState[i]) - { - attribState[i] = tempAttribState[i]; - - if (state.attribState[i]) - { - gl.enableVertexAttribArray(i); - } - else - { - gl.disableVertexAttribArray(i); - } - } - } - } - else - { - for (i = 0; i < attribs.length; i++) - { - const attrib = attribs[i]; - - gl.enableVertexAttribArray(attrib.attribute.location); - } - } -} diff --git a/packages/core/src/renderers/systems/index.js b/packages/core/src/renderers/systems/index.js deleted file mode 100644 index c843f03..0000000 --- a/packages/core/src/renderers/systems/index.js +++ /dev/null @@ -1,6 +0,0 @@ -/** - * Systems are individual components to the Renderer pipeline. - * @namespace PIXI.systems - */ -export { default as WebGLSystem } from './WebGLSystem'; -export { default as FilterSystem } from './filter/FilterSystem'; diff --git a/packages/core/src/renderers/systems/shader/GLProgram.js b/packages/core/src/renderers/systems/shader/GLProgram.js deleted file mode 100644 index 92980ca..0000000 --- a/packages/core/src/renderers/systems/shader/GLProgram.js +++ /dev/null @@ -1,43 +0,0 @@ -/** - * Helper class to create a webGL Program - * - * @class - * @private - */ -export default class GLProgram -{ - constructor(program, uniformData) - { - /** - * The shader program - * - * @member {WebGLProgram} - */ - this.program = program; - - /** - * holds the uniform data which contains uniform locations - * and current uniform values used for caching and preventing undeed GPU commands - * @type {Object} - */ - this.uniformData = uniformData; - - /** - * uniformGroups holds the various upload functions for the shader. Each uniform group - * and program have a unique upload function generated. - * @type {Object} - */ - this.uniformGroups = {}; - } - - /** - * Destroys this program - * TODO - */ - destroy() - { - this.uniformData = null; - this.uniformGroups = null; - this.program = null; - } -} diff --git a/packages/core/src/renderers/systems/shader/ShaderSystem.js b/packages/core/src/renderers/systems/shader/ShaderSystem.js deleted file mode 100644 index 6cf57b6..0000000 --- a/packages/core/src/renderers/systems/shader/ShaderSystem.js +++ /dev/null @@ -1,170 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; -import GLProgram from './GLProgram'; -import generateUniformsSync from '../../../shader/generateUniformsSync'; -import defaultValue from '../../../shader/defaultValue'; -import compileProgram from './shader/compileProgram'; - -let UID = 0; - -/** - * Helper class to create a webGL Texture - * - * @class - * @memberof PIXI - */ -export default class ShaderSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - A reference to the current renderer - */ - constructor(renderer) - { - super(renderer); - - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = null; - - this.shader = null; - this.program = null; - - this.id = UID++; - } - - contextChange(gl) - { - this.gl = gl; - } - - /** - * Changes the current shader to the one given in parameter - * - * @param {PIXI.Shader} shader - the new shader - * @param {boolean} dontSync - false if the shader should automatically sync its uniforms. - * @returns {PIXI.glCore.glProgram} the glProgram that belongs to the shader. - */ - bind(shader, dontSync) - { - shader.uniforms.globals = this.renderer.globalUniforms; - - const program = shader.program; - const glProgram = program.glPrograms[this.renderer.CONTEXT_UID] || this.generateShader(shader); - - this.shader = shader; - - // TODO - some current pixi plugins bypass this.. so it not safe to use yet.. - if (this.program !== program) - { - this.program = program; - this.gl.useProgram(glProgram.program); - } - - if (!dontSync) - { - this.syncUniformGroup(shader.uniformGroup); - } - - return glProgram; - } - - /** - * Uploads the uniforms values to the currently bound shader. - * - * @param {object} uniforms - the uniforms valiues that be applied to the current shader - */ - setUniforms(uniforms) - { - const shader = this.shader.program; - const glProgram = shader.glPrograms[this.renderer.CONTEXT_UID]; - - shader.syncUniforms(glProgram.uniformData, uniforms, this.renderer); - } - - syncUniformGroup(group) - { - const glProgram = this.getglProgram(); - - if (!group.static || group.dirtyId !== glProgram.uniformGroups[group.id]) - { - glProgram.uniformGroups[group.id] = group.dirtyId; - const syncFunc = group.syncUniforms[this.shader.program.id] || this.createSyncGroups(group); - - syncFunc(glProgram.uniformData, group.uniforms, this.renderer); - } - } - - createSyncGroups(group) - { - group.syncUniforms[this.shader.program.id] = generateUniformsSync(group, this.shader.program.uniformData); - - return group.syncUniforms[this.shader.program.id]; - } - - /** - * Returns the underlying GLShade rof the currently bound shader. - * This can be handy for when you to have a little more control over the setting of your uniforms. - * - * @return {PIXI.glCore.Shader} the glProgram for the currently bound Shader for this context - */ - getglProgram() - { - if (this.shader) - { - return this.shader.program.glPrograms[this.renderer.CONTEXT_UID]; - } - - return null; - } - - /** - * Generates a glProgram verion of the Shader provided. - * - * @private - * @param {PIXI.Shader} shader the shader that the glProgram will be based on. - * @return {PIXI.glCore.glProgram} A shiney new glProgram - */ - generateShader(shader) - { - const gl = this.gl; - - const program = shader.program; - - const attribMap = {}; - - for (const i in program.attributeData) - { - attribMap[i] = program.attributeData[i].location; - } - - const shaderProgram = compileProgram(gl, program.vertexSrc, program.fragmentSrc, attribMap); - const uniformData = {}; - - for (const i in program.uniformData) - { - const data = program.uniformData[i]; - - uniformData[i] = { - location: gl.getUniformLocation(shaderProgram, i), - value: defaultValue(data.type, data.size), - }; - } - - const glProgram = new GLProgram(shaderProgram, uniformData); - - program.glPrograms[this.renderer.CONTEXT_UID] = glProgram; - - return glProgram; - } - - /** - * Destroys this System and removes all its textures - */ - destroy() - { - // TODO implement destroy method for ShaderSystem - this.destroyed = true; - } -} diff --git a/packages/core/src/renderers/systems/shader/shader/compileProgram.js b/packages/core/src/renderers/systems/shader/shader/compileProgram.js deleted file mode 100644 index 8c6ccb9..0000000 --- a/packages/core/src/renderers/systems/shader/shader/compileProgram.js +++ /dev/null @@ -1,78 +0,0 @@ -/** - * @method compileProgram - * @memberof PIXI.glCore.shader - * @param gl {WebGLRenderingContext} The current WebGL context {WebGLProgram} - * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. - * @param fragmentSrc {string|string[]} The fragment shader source as an array of strings. - * @param attributeLocations {Object} An attribute location map that lets you manually set the attribute locations - * @return {WebGLProgram} the shader program - */ -export default function compileProgram(gl, vertexSrc, fragmentSrc, attributeLocations) -{ - const glVertShader = compileShader(gl, gl.VERTEX_SHADER, vertexSrc); - const glFragShader = compileShader(gl, gl.FRAGMENT_SHADER, fragmentSrc); - - let program = gl.createProgram(); - - gl.attachShader(program, glVertShader); - gl.attachShader(program, glFragShader); - - // optionally, set the attributes manually for the program rather than letting WebGL decide.. - if (attributeLocations) - { - for (const i in attributeLocations) - { - gl.bindAttribLocation(program, attributeLocations[i], i); - } - } - - gl.linkProgram(program); - - // if linking fails, then log and cleanup - if (!gl.getProgramParameter(program, gl.LINK_STATUS)) - { - console.error('Pixi.js Error: Could not initialize shader.'); - console.error('gl.VALIDATE_STATUS', gl.getProgramParameter(program, gl.VALIDATE_STATUS)); - console.error('gl.getError()', gl.getError()); - - // if there is a program info log, log it - if (gl.getProgramInfoLog(program) !== '') - { - console.warn('Pixi.js Warning: gl.getProgramInfoLog()', gl.getProgramInfoLog(program)); - } - - gl.deleteProgram(program); - program = null; - } - - // clean up some shaders - gl.deleteShader(glVertShader); - gl.deleteShader(glFragShader); - - return program; -} - -/** - * @private - * @param gl {WebGLRenderingContext} The current WebGL context {WebGLProgram} - * @param type {Number} the type, can be either VERTEX_SHADER or FRAGMENT_SHADER - * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. - * @return {WebGLShader} the shader - */ -function compileShader(gl, type, src) -{ - const shader = gl.createShader(type); - - gl.shaderSource(shader, src); - gl.compileShader(shader); - - if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) - { - console.warn(src); - console.error(gl.getShaderInfoLog(shader)); - - return null; - } - - return shader; -} diff --git a/packages/core/src/renderers/systems/shader/shader/defaultValue.js b/packages/core/src/renderers/systems/shader/shader/defaultValue.js deleted file mode 100644 index 97290bd..0000000 --- a/packages/core/src/renderers/systems/shader/shader/defaultValue.js +++ /dev/null @@ -1,79 +0,0 @@ -/** - * @method defaultValue - * @memberof PIXI.glCore.shader - * @param type {String} Type of value - * @param size {Number} - */ -export default function defaultValue(type, size) -{ - switch (type) - { - case 'float': - return 0; - - case 'vec2': - return new Float32Array(2 * size); - - case 'vec3': - return new Float32Array(3 * size); - - case 'vec4': - return new Float32Array(4 * size); - - case 'int': - case 'sampler2D': - case 'sampler2DArray': - return 0; - - case 'ivec2': - return new Int32Array(2 * size); - - case 'ivec3': - return new Int32Array(3 * size); - - case 'ivec4': - return new Int32Array(4 * size); - - case 'bool': - return false; - - case 'bvec2': - - return booleanArray(2 * size); - - case 'bvec3': - return booleanArray(3 * size); - - case 'bvec4': - return booleanArray(4 * size); - - case 'mat2': - return new Float32Array([1, 0, - 0, 1]); - - case 'mat3': - return new Float32Array([1, 0, 0, - 0, 1, 0, - 0, 0, 1]); - - case 'mat4': - return new Float32Array([1, 0, 0, 0, - 0, 1, 0, 0, - 0, 0, 1, 0, - 0, 0, 0, 1]); - } - - return null; -} - -function booleanArray(size) -{ - const array = new Array(size); - - for (let i = 0; i < array.length; i++) - { - array[i] = false; - } - - return array; -} diff --git a/packages/core/src/renderers/systems/shader/shader/index.js b/packages/core/src/renderers/systems/shader/shader/index.js deleted file mode 100644 index ba7a317..0000000 --- a/packages/core/src/renderers/systems/shader/shader/index.js +++ /dev/null @@ -1,8 +0,0 @@ -/** - * @namespace PIXI.glCore.shader - */ -export { default as compileProgram } from './compileProgram'; -export { default as defaultValue } from './defaultValue'; -export { default as setPrecision } from './setPrecision'; -export { default as mapSize } from './mapSize'; -export { default as mapType } from './mapType'; diff --git a/packages/core/src/renderers/systems/shader/shader/mapSize.js b/packages/core/src/renderers/systems/shader/shader/mapSize.js deleted file mode 100644 index b681554..0000000 --- a/packages/core/src/renderers/systems/shader/shader/mapSize.js +++ /dev/null @@ -1,33 +0,0 @@ -const GLSL_TO_SIZE = { - float: 1, - vec2: 2, - vec3: 3, - vec4: 4, - - int: 1, - ivec2: 2, - ivec3: 3, - ivec4: 4, - - bool: 1, - bvec2: 2, - bvec3: 3, - bvec4: 4, - - mat2: 4, - mat3: 9, - mat4: 16, - - sampler2D: 1, -}; - -/** - * @method mapSize - * @memberof PIXI.glCore.shader - * @param type {String} - * @return {Number} - */ -export default function mapSize(type) -{ - return GLSL_TO_SIZE[type]; -} diff --git a/packages/core/src/renderers/systems/shader/shader/mapType.js b/packages/core/src/renderers/systems/shader/shader/mapType.js deleted file mode 100644 index d288b7c..0000000 --- a/packages/core/src/renderers/systems/shader/shader/mapType.js +++ /dev/null @@ -1,45 +0,0 @@ -let GL_TABLE = null; - -const GL_TO_GLSL_TYPES = { - FLOAT: 'float', - FLOAT_VEC2: 'vec2', - FLOAT_VEC3: 'vec3', - FLOAT_VEC4: 'vec4', - - INT: 'int', - INT_VEC2: 'ivec2', - INT_VEC3: 'ivec3', - INT_VEC4: 'ivec4', - - BOOL: 'bool', - BOOL_VEC2: 'bvec2', - BOOL_VEC3: 'bvec3', - BOOL_VEC4: 'bvec4', - - FLOAT_MAT2: 'mat2', - FLOAT_MAT3: 'mat3', - FLOAT_MAT4: 'mat4', - - SAMPLER_2D: 'sampler2D', - SAMPLER_CUBE: 'samplerCube', - SAMPLER_2D_ARRAY: 'sampler2DArray', -}; - -export default function mapType(gl, type) -{ - if (!GL_TABLE) - { - const typeNames = Object.keys(GL_TO_GLSL_TYPES); - - GL_TABLE = {}; - - for (let i = 0; i < typeNames.length; ++i) - { - const tn = typeNames[i]; - - GL_TABLE[gl[tn]] = GL_TO_GLSL_TYPES[tn]; - } - } - - return GL_TABLE[type]; -} diff --git a/packages/core/src/renderers/systems/shader/shader/setPrecision.js b/packages/core/src/renderers/systems/shader/shader/setPrecision.js deleted file mode 100644 index e2c1f34..0000000 --- a/packages/core/src/renderers/systems/shader/shader/setPrecision.js +++ /dev/null @@ -1,18 +0,0 @@ -/** - * Sets the float precision on the shader. If the precision is already present this function will do nothing - * @param {string} src the shader source - * @param {string} precision The float precision of the shader. Options are 'lowp', 'mediump' or 'highp'. - * - * @return {string} modified shader source - */ -export default function setPrecision(src, precision) -{ - src = src.trim(); - - if (src.substring(0, 9) !== 'precision' && src.substring(0, 1) !== '#') - { - return `precision ${precision} float;\n${src}`; - } - - return src; -} diff --git a/packages/core/src/renderers/systems/textures/GLTexture.js b/packages/core/src/renderers/systems/textures/GLTexture.js deleted file mode 100644 index c3fb8dc..0000000 --- a/packages/core/src/renderers/systems/textures/GLTexture.js +++ /dev/null @@ -1,33 +0,0 @@ -export default class GLTexture -{ - constructor(texture) - { - /** - * The WebGL texture - * - * @member {WebGLTexture} - */ - this.texture = texture; - - this.width = -1; - this.height = -1; - - /** - * Texture contents dirty flag - * @member {number} - */ - this.dirtyId = -1; - - /** - * Texture style dirty flag - * @type {number} - */ - this.dirtyStyleId = -1; - - /** - * Whether mip levels has to be generated - * @type {boolean} - */ - this.mipmap = false; - } -} diff --git a/packages/core/src/renderers/systems/textures/TextureGCSystem.js b/packages/core/src/renderers/systems/textures/TextureGCSystem.js deleted file mode 100644 index b089938..0000000 --- a/packages/core/src/renderers/systems/textures/TextureGCSystem.js +++ /dev/null @@ -1,110 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; -import { GC_MODES } from '@pixi/constants'; -import { settings } from '@pixi/settings'; - -/** - * TextureGarbageCollector. This class manages the GPU and ensures that it does not get clogged - * up with textures that are no longer being used. - * - * @class - * @memberof PIXI - */ -export default class TextureGCSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this.count = 0; - this.checkCount = 0; - this.maxIdle = settings.GC_MAX_IDLE; - this.checkCountMax = settings.GC_MAX_CHECK_COUNT; - this.mode = settings.GC_MODE; - } - - /** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ - postrender() - { - this.count++; - - if (this.mode === GC_MODES.MANUAL) - { - return; - } - - this.checkCount++; - - if (this.checkCount > this.checkCountMax) - { - this.checkCount = 0; - - this.run(); - } - } - - /** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ - run() - { - const tm = this.renderer.texture; - const managedTextures = tm.managedTextures; - let wasRemoved = false; - - for (let i = 0; i < managedTextures.length; i++) - { - const texture = managedTextures[i]; - - // only supports non generated textures at the moment! - if (!texture.frameBuffer && this.count - texture.touched > this.maxIdle) - { - tm.destroyTexture(texture, true); - managedTextures[i] = null; - wasRemoved = true; - } - } - - if (wasRemoved) - { - let j = 0; - - for (let i = 0; i < managedTextures.length; i++) - { - if (managedTextures[i] !== null) - { - managedTextures[j++] = managedTextures[i]; - } - } - - managedTextures.length = j; - } - } - - /** - * Removes all the textures within the specified displayObject and its children from the GPU - * - * @param {PIXI.DisplayObject} displayObject - the displayObject to remove the textures from. - */ - unload(displayObject) - { - const tm = this.renderer.textureSystem; - - // only destroy non generated textures - if (displayObject._texture && displayObject._texture._glRenderTargets) - { - tm.destroyTexture(displayObject._texture); - } - - for (let i = displayObject.children.length - 1; i >= 0; i--) - { - this.unload(displayObject.children[i]); - } - } -} diff --git a/packages/core/src/renderers/systems/textures/TextureSystem.js b/packages/core/src/renderers/systems/textures/TextureSystem.js deleted file mode 100644 index ccc5de9..0000000 --- a/packages/core/src/renderers/systems/textures/TextureSystem.js +++ /dev/null @@ -1,281 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; -import GLTexture from './GLTexture'; -import { removeItems } from '@pixi/utils'; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class TextureSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - // TODO set to max textures... - this.boundTextures = [ - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - ]; - - this.currentLocation = -1; - - this.managedTextures = []; - } - - /** - * Sets up the renderer context and necessary buffers. - * - * @private - */ - contextChange() - { - const gl = this.gl = this.renderer.gl; - - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - - // TODO move this.. to a nice make empty textures class.. - this.emptyTextures = {}; - - const emptyTexture2D = new GLTexture(gl.createTexture()); - - gl.bindTexture(gl.TEXTURE_2D, emptyTexture2D.texture); - gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array(4)); - - this.emptyTextures[gl.TEXTURE_2D] = emptyTexture2D; - this.emptyTextures[gl.TEXTURE_CUBE_MAP] = new GLTexture(gl.createTexture()); - - gl.bindTexture(gl.TEXTURE_CUBE_MAP, this.emptyTextures[gl.TEXTURE_CUBE_MAP].texture); - - let i; - - for (i = 0; i < 6; i++) - { - gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, null); - } - - gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_MAG_FILTER, gl.LINEAR); - gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_MIN_FILTER, gl.LINEAR); - - for (i = 0; i < this.boundTextures.length; i++) - { - this.bind(null, i); - } - } - - bind(texture, location) - { - const gl = this.gl; - - location = location || 0; - - if (this.currentLocation !== location) - { - this.currentLocation = location; - gl.activeTexture(gl.TEXTURE0 + location); - } - - if (texture) - { - texture = texture.baseTexture || texture; - - if (texture.valid) - { - texture.touched = this.renderer.textureGC.count; - - const glTexture = texture._glTextures[this.CONTEXT_UID] || this.initTexture(texture); - - gl.bindTexture(texture.target, glTexture.texture); - - if (glTexture.dirtyId !== texture.dirtyId) - { - this.updateTexture(texture); - } - - this.boundTextures[location] = texture; - } - } - else - { - gl.bindTexture(gl.TEXTURE_2D, this.emptyTextures[gl.TEXTURE_2D].texture); - this.boundTextures[location] = null; - } - } - - unbind(texture) - { - const gl = this.gl; - - for (let i = 0; i < this.boundTextures.length; i++) - { - if (this.boundTextures[i] === texture) - { - if (this.currentLocation !== i) - { - gl.activeTexture(gl.TEXTURE0 + i); - this.currentLocation = i; - } - - gl.bindTexture(gl.TEXTURE_2D, this.emptyTextures[texture.target].texture); - this.boundTextures[i] = null; - } - } - } - - initTexture(texture) - { - const glTexture = new GLTexture(this.gl.createTexture()); - - // guarentee an update.. - glTexture.dirtyId = -1; - - texture._glTextures[this.CONTEXT_UID] = glTexture; - - this.managedTextures.push(texture); - texture.on('dispose', this.destroyTexture, this); - - return glTexture; - } - - updateTexture(texture) - { - const glTexture = texture._glTextures[this.CONTEXT_UID]; - const renderer = this.renderer; - - if (texture.resource && texture.resource.upload(renderer, texture, glTexture)) - { - // texture is uploaded, dont do anything! - } - else - { - // default, renderTexture-like logic - const width = texture.realWidth; - const height = texture.realHeight; - const gl = renderer.gl; - - if (glTexture.width !== width - || glTexture.height !== height - || glTexture.dirtyId < 0) - { - glTexture.width = width; - glTexture.height = height; - - gl.texImage2D(texture.target, 0, - texture.format, - width, - height, - 0, - texture.format, - texture.type, - null); - } - } - - // lets only update what changes.. - if (texture.dirtyStyleId !== glTexture.dirtyStyleId) - { - this.updateTextureStyle(texture); - } - glTexture.dirtyId = texture.dirtyId; - } - - /** - * Deletes the texture from WebGL - * - * @param {PIXI.BaseTexture|PIXI.Texture} texture - the texture to destroy - * @param {boolean} [skipRemove=false] - Whether to skip removing the texture from the TextureManager. - */ - destroyTexture(texture, skipRemove) - { - const gl = this.gl; - - texture = texture.baseTexture || texture; - - if (texture._glTextures[this.renderer.CONTEXT_UID]) - { - this.unbind(texture); - - gl.deleteTexture(texture._glTextures[this.renderer.CONTEXT_UID].texture); - texture.off('dispose', this.destroyTexture, this); - - delete texture._glTextures[this.renderer.CONTEXT_UID]; - - if (!skipRemove) - { - const i = this.managedTextures.indexOf(texture); - - if (i !== -1) - { - removeItems(this.managedTextures, i, 1); - } - } - } - } - - updateTextureStyle(texture) - { - const glTexture = texture._glTextures[this.CONTEXT_UID]; - - glTexture.mipmap = texture.mipmap && texture.isPowerOfTwo; - if (!glTexture) - { - return; - } - - if (texture.resource && texture.resource.style(this.renderer, texture, glTexture)) - { - // style is set, dont do anything! - } - else - { - this.setStyle(texture, glTexture); - } - - glTexture.dirtyStyleId = texture.dirtyStyleId; - } - - setStyle(texture, glTexture) - { - const gl = this.gl; - - if (glTexture.mipmap) - { - gl.generateMipmap(texture.target); - } - - gl.texParameteri(texture.target, gl.TEXTURE_WRAP_S, texture.wrapMode); - gl.texParameteri(texture.target, gl.TEXTURE_WRAP_T, texture.wrapMode); - - if (glTexture.mipmap) - { - /* eslint-disable max-len */ - gl.texParameteri(texture.target, gl.TEXTURE_MIN_FILTER, texture.scaleMode ? gl.LINEAR_MIPMAP_LINEAR : gl.NEAREST_MIPMAP_NEAREST); - /* eslint-disable max-len */ - } - else - { - gl.texParameteri(texture.target, gl.TEXTURE_MIN_FILTER, texture.scaleMode ? gl.LINEAR : gl.NEAREST); - } - - gl.texParameteri(texture.target, gl.TEXTURE_MAG_FILTER, texture.scaleMode ? gl.LINEAR : gl.NEAREST); - } -} diff --git a/packages/core/src/renderers/utils/ObjectRenderer.js b/packages/core/src/renderers/utils/ObjectRenderer.js deleted file mode 100644 index f67a273..0000000 --- a/packages/core/src/renderers/utils/ObjectRenderer.js +++ /dev/null @@ -1,48 +0,0 @@ -import WebGLSystem from '../systems/WebGLSystem'; - -/** - * Base for a common object renderer that can be used as a system renderer plugin. - * - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class ObjectRenderer extends WebGLSystem -{ - /** - * Starts the renderer and sets the shader - * - */ - start() - { - // set the shader.. - } - - /** - * Stops the renderer - * - */ - stop() - { - this.flush(); - } - - /** - * Stub method for rendering content and emptying the current batch. - * - */ - flush() - { - // flush! - } - - /** - * Renders an object - * - * @param {PIXI.DisplayObject} object - The object to render. - */ - render(object) // eslint-disable-line no-unused-vars - { - // render the object - } -} diff --git a/packages/core/src/renderers/utils/Quad.js b/packages/core/src/renderers/utils/Quad.js deleted file mode 100644 index 5c1517b..0000000 --- a/packages/core/src/renderers/utils/Quad.js +++ /dev/null @@ -1,26 +0,0 @@ -import Geometry from '../../geometry/Geometry'; - -/** - * Helper class to create a quad - * - * @class - * @memberof PIXI - */ -export default class Quad extends Geometry -{ - /** - * just a quad - */ - constructor() - { - super(); - - this.addAttribute('aVertexPosition', [ - 0, 0, - 1, 0, - 1, 1, - 0, 1, - ]) - .addIndex([0, 1, 3, 2]); - } -} diff --git a/packages/core/src/renderers/utils/QuadUv.js b/packages/core/src/renderers/utils/QuadUv.js deleted file mode 100644 index 97bda89..0000000 --- a/packages/core/src/renderers/utils/QuadUv.js +++ /dev/null @@ -1,103 +0,0 @@ -import Geometry from '../../geometry/Geometry'; -import Buffer from '../../geometry/Buffer'; - -/** - * Helper class to create a quad with uvs like in v4 - * - * @class - * @memberof PIXI - */ -export default class QuadUv extends Geometry -{ - constructor() - { - super(); - - /** - * An array of vertices - * - * @member {Float32Array} - */ - this.vertices = new Float32Array([ - -1, -1, - 1, -1, - 1, 1, - -1, 1, - ]); - - /** - * The Uvs of the quad - * - * @member {Float32Array} - */ - this.uvs = new Float32Array([ - 0, 0, - 1, 0, - 1, 1, - 0, 1, - ]); - - this.vertexBuffer = new Buffer(this.vertices); - this.uvBuffer = new Buffer(this.uvs); - - this.addAttribute('aVertexPosition', this.vertexBuffer) - .addAttribute('aTextureCoord', this.uvBuffer) - .addIndex([0, 1, 2, 0, 2, 3]); - } - - /** - * Maps two Rectangle to the quad. - * - * @param {PIXI.Rectangle} targetTextureFrame - the first rectangle - * @param {PIXI.Rectangle} destinationFrame - the second rectangle - * @return {PIXI.Quad} Returns itself. - */ - map(targetTextureFrame, destinationFrame) - { - let x = 0; // destinationFrame.x / targetTextureFrame.width; - let y = 0; // destinationFrame.y / targetTextureFrame.height; - - this.uvs[0] = x; - this.uvs[1] = y; - - this.uvs[2] = x + (destinationFrame.width / targetTextureFrame.width); - this.uvs[3] = y; - - this.uvs[4] = x + (destinationFrame.width / targetTextureFrame.width); - this.uvs[5] = y + (destinationFrame.height / targetTextureFrame.height); - - this.uvs[6] = x; - this.uvs[7] = y + (destinationFrame.height / targetTextureFrame.height); - - x = destinationFrame.x; - y = destinationFrame.y; - - this.vertices[0] = x; - this.vertices[1] = y; - - this.vertices[2] = x + destinationFrame.width; - this.vertices[3] = y; - - this.vertices[4] = x + destinationFrame.width; - this.vertices[5] = y + destinationFrame.height; - - this.vertices[6] = x; - this.vertices[7] = y + destinationFrame.height; - - this.invalidate(); - - return this; - } - - /** - * legacy upload method, just marks buffers dirty - * @returns {PIXI.QuadUv} Returns itself. - */ - invalidate() - { - this.vertexBuffer._updateID++; - this.uvBuffer._updateID++; - - return this; - } -} diff --git a/packages/core/src/renderers/utils/checkMaxIfStatmentsInShader.js b/packages/core/src/renderers/utils/checkMaxIfStatmentsInShader.js deleted file mode 100644 index 8047c96..0000000 --- a/packages/core/src/renderers/utils/checkMaxIfStatmentsInShader.js +++ /dev/null @@ -1,58 +0,0 @@ -const fragTemplate = [ - 'precision mediump float;', - 'void main(void){', - 'float test = 0.1;', - '%forloop%', - 'gl_FragColor = vec4(0.0);', - '}', -].join('\n'); - -export default function checkMaxIfStatmentsInShader(maxIfs, gl) -{ - if (maxIfs === 0) - { - throw new Error('Invalid value of `0` passed to `checkMaxIfStatementsInShader`'); - } - - const shader = gl.createShader(gl.FRAGMENT_SHADER); - - while (true) // eslint-disable-line no-constant-condition - { - const fragmentSrc = fragTemplate.replace(/%forloop%/gi, generateIfTestSrc(maxIfs)); - - gl.shaderSource(shader, fragmentSrc); - gl.compileShader(shader); - - if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) - { - maxIfs = (maxIfs / 2) | 0; - } - else - { - // valid! - break; - } - } - - return maxIfs; -} - -function generateIfTestSrc(maxIfs) -{ - let src = ''; - - for (let i = 0; i < maxIfs; ++i) - { - if (i > 0) - { - src += '\nelse '; - } - - if (i < maxIfs - 1) - { - src += `if(test == ${i}.0){}`; - } - } - - return src; -} diff --git a/packages/core/src/renderers/utils/mapWebGLBlendModesToPixi.js b/packages/core/src/renderers/utils/mapWebGLBlendModesToPixi.js deleted file mode 100644 index 10ffe4d..0000000 --- a/packages/core/src/renderers/utils/mapWebGLBlendModesToPixi.js +++ /dev/null @@ -1,47 +0,0 @@ -import { BLEND_MODES } from '@pixi/constants'; - -/** - * Maps gl blend combinations to WebGL. - * - * @memberof PIXI - * @function mapWebGLBlendModesToPixi - * @private - * @param {WebGLRenderingContext} gl - The rendering context. - * @param {string[]} [array=[]] - The array to output into. - * @return {string[]} Mapped modes. - */ -export default function mapWebGLBlendModesToPixi(gl, array = []) -{ - // TODO - premultiply alpha would be different. - // add a boolean for that! - array[BLEND_MODES.NORMAL] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.ADD] = [gl.ONE, gl.DST_ALPHA]; - array[BLEND_MODES.MULTIPLY] = [gl.DST_COLOR, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.SCREEN] = [gl.ONE, gl.ONE_MINUS_SRC_COLOR]; - array[BLEND_MODES.OVERLAY] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.DARKEN] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.LIGHTEN] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.COLOR_DODGE] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.COLOR_BURN] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.HARD_LIGHT] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.SOFT_LIGHT] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.DIFFERENCE] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.EXCLUSION] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.HUE] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.SATURATION] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.COLOR] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.LUMINOSITY] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.NONE] = [0, 0]; - - // not-premultiplied blend modes - array[BLEND_MODES.NORMAL_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.ADD_NPM] = [gl.SRC_ALPHA, gl.DST_ALPHA, gl.ONE, gl.DST_ALPHA]; - array[BLEND_MODES.SCREEN_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_COLOR, gl.ONE, gl.ONE_MINUS_SRC_COLOR]; - - // not-premultiplied blend modes - array[BLEND_MODES.NORMAL_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.ADD_NPM] = [gl.SRC_ALPHA, gl.DST_ALPHA, gl.ONE, gl.DST_ALPHA]; - array[BLEND_MODES.SCREEN_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_COLOR, gl.ONE, gl.ONE_MINUS_SRC_COLOR]; - - return array; -} diff --git a/packages/core/src/shader/GLProgram.js b/packages/core/src/shader/GLProgram.js new file mode 100644 index 0000000..92980ca --- /dev/null +++ b/packages/core/src/shader/GLProgram.js @@ -0,0 +1,43 @@ +/** + * Helper class to create a webGL Program + * + * @class + * @private + */ +export default class GLProgram +{ + constructor(program, uniformData) + { + /** + * The shader program + * + * @member {WebGLProgram} + */ + this.program = program; + + /** + * holds the uniform data which contains uniform locations + * and current uniform values used for caching and preventing undeed GPU commands + * @type {Object} + */ + this.uniformData = uniformData; + + /** + * uniformGroups holds the various upload functions for the shader. Each uniform group + * and program have a unique upload function generated. + * @type {Object} + */ + this.uniformGroups = {}; + } + + /** + * Destroys this program + * TODO + */ + destroy() + { + this.uniformData = null; + this.uniformGroups = null; + this.program = null; + } +} diff --git a/packages/core/src/shader/Program.js b/packages/core/src/shader/Program.js index 4f9971e..4b4007a 100644 --- a/packages/core/src/shader/Program.js +++ b/packages/core/src/shader/Program.js @@ -1,6 +1,11 @@ -import * as shaderUtils from '../renderers/systems/shader/shader'; +// import * as from '../systems/shader/shader'; +import { setPrecision, + defaultValue, + compileProgram, + mapSize, + mapType, + getTestContext } from './utils'; import { ProgramCache } from '@pixi/utils'; -import getTestContext from './getTestContext'; import defaultFragment from './defaultProgram.frag'; import defaultVertex from './defaultProgram.vert'; import { settings } from '@pixi/settings'; @@ -34,8 +39,8 @@ */ this.fragmentSrc = fragmentSrc || Program.defaultFragmentSrc; - this.vertexSrc = shaderUtils.setPrecision(this.vertexSrc, settings.PRECISION_VERTEX); - this.fragmentSrc = shaderUtils.setPrecision(this.fragmentSrc, settings.PRECISION_FRAGMENT); + this.vertexSrc = setPrecision(this.vertexSrc, settings.PRECISION_VERTEX); + this.fragmentSrc = setPrecision(this.fragmentSrc, settings.PRECISION_FRAGMENT); // currently this does not extract structs only default types this.extractData(this.vertexSrc, this.fragmentSrc); @@ -62,7 +67,7 @@ if (gl) { - const program = shaderUtils.compileProgram(gl, vertexSrc, fragmentSrc); + const program = compileProgram(gl, vertexSrc, fragmentSrc); this.attributeData = this.getAttributeData(program, gl); this.uniformData = this.getUniformData(program, gl); @@ -95,13 +100,13 @@ for (let i = 0; i < totalAttributes; i++) { const attribData = gl.getActiveAttrib(program, i); - const type = shaderUtils.mapType(gl, attribData.type); + const type = mapType(gl, attribData.type); /*eslint-disable */ const data = { type: type, name: attribData.name, - size: shaderUtils.mapSize(type), + size: mapSize(type), location: 0, }; /* eslint-enable */ @@ -145,14 +150,14 @@ const name = uniformData.name.replace(/\[.*?\]/, ''); const isArray = uniformData.name.match(/\[.*?\]/, ''); - const type = shaderUtils.mapType(gl, uniformData.type); + const type = mapType(gl, uniformData.type); /*eslint-disable */ uniforms[name] = { type: type, size: uniformData.size, isArray:isArray, - value: shaderUtils.defaultValue(type, uniformData.size), + value: defaultValue(type, uniformData.size), }; /* eslint-enable */ } diff --git a/packages/core/src/shader/ShaderSystem.js b/packages/core/src/shader/ShaderSystem.js new file mode 100644 index 0000000..40b8af8 --- /dev/null +++ b/packages/core/src/shader/ShaderSystem.js @@ -0,0 +1,171 @@ +import System from '../System'; +import GLProgram from './GLProgram'; +import { generateUniformsSync, + defaultValue, + compileProgram } from './utils'; + +let UID = 0; + +/** + * Helper class to create a webGL Texture + * + * @class + * @memberof PIXI.systems + * @extends PIXI.System + */ +export default class ShaderSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - A reference to the current renderer + */ + constructor(renderer) + { + super(renderer); + + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = null; + + this.shader = null; + this.program = null; + + this.id = UID++; + } + + contextChange(gl) + { + this.gl = gl; + } + + /** + * Changes the current shader to the one given in parameter + * + * @param {PIXI.Shader} shader - the new shader + * @param {boolean} dontSync - false if the shader should automatically sync its uniforms. + * @returns {PIXI.glCore.glProgram} the glProgram that belongs to the shader. + */ + bind(shader, dontSync) + { + shader.uniforms.globals = this.renderer.globalUniforms; + + const program = shader.program; + const glProgram = program.glPrograms[this.renderer.CONTEXT_UID] || this.generateShader(shader); + + this.shader = shader; + + // TODO - some current pixi plugins bypass this.. so it not safe to use yet.. + if (this.program !== program) + { + this.program = program; + this.gl.useProgram(glProgram.program); + } + + if (!dontSync) + { + this.syncUniformGroup(shader.uniformGroup); + } + + return glProgram; + } + + /** + * Uploads the uniforms values to the currently bound shader. + * + * @param {object} uniforms - the uniforms valiues that be applied to the current shader + */ + setUniforms(uniforms) + { + const shader = this.shader.program; + const glProgram = shader.glPrograms[this.renderer.CONTEXT_UID]; + + shader.syncUniforms(glProgram.uniformData, uniforms, this.renderer); + } + + syncUniformGroup(group) + { + const glProgram = this.getglProgram(); + + if (!group.static || group.dirtyId !== glProgram.uniformGroups[group.id]) + { + glProgram.uniformGroups[group.id] = group.dirtyId; + const syncFunc = group.syncUniforms[this.shader.program.id] || this.createSyncGroups(group); + + syncFunc(glProgram.uniformData, group.uniforms, this.renderer); + } + } + + createSyncGroups(group) + { + group.syncUniforms[this.shader.program.id] = generateUniformsSync(group, this.shader.program.uniformData); + + return group.syncUniforms[this.shader.program.id]; + } + + /** + * Returns the underlying GLShade rof the currently bound shader. + * This can be handy for when you to have a little more control over the setting of your uniforms. + * + * @return {PIXI.glCore.Shader} the glProgram for the currently bound Shader for this context + */ + getglProgram() + { + if (this.shader) + { + return this.shader.program.glPrograms[this.renderer.CONTEXT_UID]; + } + + return null; + } + + /** + * Generates a glProgram verion of the Shader provided. + * + * @private + * @param {PIXI.Shader} shader the shader that the glProgram will be based on. + * @return {PIXI.glCore.glProgram} A shiney new glProgram + */ + generateShader(shader) + { + const gl = this.gl; + + const program = shader.program; + + const attribMap = {}; + + for (const i in program.attributeData) + { + attribMap[i] = program.attributeData[i].location; + } + + const shaderProgram = compileProgram(gl, program.vertexSrc, program.fragmentSrc, attribMap); + const uniformData = {}; + + for (const i in program.uniformData) + { + const data = program.uniformData[i]; + + uniformData[i] = { + location: gl.getUniformLocation(shaderProgram, i), + value: defaultValue(data.type, data.size), + }; + } + + const glProgram = new GLProgram(shaderProgram, uniformData); + + program.glPrograms[this.renderer.CONTEXT_UID] = glProgram; + + return glProgram; + } + + /** + * Destroys this System and removes all its textures + */ + destroy() + { + // TODO implement destroy method for ShaderSystem + this.destroyed = true; + } +} diff --git a/packages/core/src/shader/defaultValue.js b/packages/core/src/shader/defaultValue.js deleted file mode 100644 index 847a069..0000000 --- a/packages/core/src/shader/defaultValue.js +++ /dev/null @@ -1,78 +0,0 @@ -function booleanArray(size) -{ - const array = new Array(size); - - for (let i = 0; i < array.length; i++) - { - array[i] = false; - } - - return array; -} - -/** - * @class - * @memberof PIXI.glCore.shader - * @param type {String} Type of value - * @param size {Number} - */ -export default function defaultValue(type, size) -{ - switch (type) - { - case 'float': - return 0; - - case 'vec2': - return new Float32Array(2 * size); - - case 'vec3': - return new Float32Array(3 * size); - - case 'vec4': - return new Float32Array(4 * size); - - case 'int': - case 'sampler2D': - return 0; - - case 'ivec2': - return new Int32Array(2 * size); - - case 'ivec3': - return new Int32Array(3 * size); - - case 'ivec4': - return new Int32Array(4 * size); - - case 'bool': - return false; - - case 'bvec2': - - return booleanArray(2 * size); - - case 'bvec3': - return booleanArray(3 * size); - - case 'bvec4': - return booleanArray(4 * size); - - case 'mat2': - return new Float32Array([1, 0, - 0, 1]); - - case 'mat3': - return new Float32Array([1, 0, 0, - 0, 1, 0, - 0, 0, 1]); - - case 'mat4': - return new Float32Array([1, 0, 0, 0, - 0, 1, 0, 0, - 0, 0, 1, 0, - 0, 0, 0, 1]); - } - - return 0; -} diff --git a/packages/core/src/shader/extractUniformsFromSrc.js b/packages/core/src/shader/extractUniformsFromSrc.js deleted file mode 100644 index 3d02c0a..0000000 --- a/packages/core/src/shader/extractUniformsFromSrc.js +++ /dev/null @@ -1,57 +0,0 @@ -import defaultValue from './defaultValue'; - -export default function extractUniformsFromSrc(vertexSrc, fragmentSrc, mask) -{ - const vertUniforms = extractUniformsFromString(vertexSrc, mask); - const fragUniforms = extractUniformsFromString(fragmentSrc, mask); - - return Object.assign(vertUniforms, fragUniforms); -} - -function extractUniformsFromString(string) -{ - const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); - - const uniforms = {}; - let nameSplit; - - // clean the lines a little - remove extra spaces / teabs etc - // then split along ';' - const lines = string.replace(/\s+/g, ' ') - .split(/\s*;\s*/); - - // loop through.. - for (let i = 0; i < lines.length; i++) - { - const line = lines[i].trim(); - - if (line.indexOf('uniform') > -1) - { - const splitLine = line.split(' '); - const type = splitLine[1]; - - let name = splitLine[2]; - let size = 1; - - if (name.indexOf('[') > -1) - { - // array! - nameSplit = name.split(/\[|]/); - name = nameSplit[0]; - size *= Number(nameSplit[1]); - } - - if (!name.match(maskRegex)) - { - uniforms[name] = { - value: defaultValue(type, size), - dirtyId: 0, - name, - type, - }; - } - } - } - - return uniforms; -} diff --git a/packages/core/src/shader/generateUniformsSync.js b/packages/core/src/shader/generateUniformsSync.js deleted file mode 100644 index 98a357f..0000000 --- a/packages/core/src/shader/generateUniformsSync.js +++ /dev/null @@ -1,231 +0,0 @@ -// cv = CachedValue -// v = value -// ud = uniformData -// uv = uniformValue -// l = loaction -const GLSL_TO_SINGLE_SETTERS_CACHED = { - - float: ` - if(cv !== v) - { - cv.v = v; - gl.uniform1f(location, v) - }`, - - vec2: ` - if(cv[0] !== v[0] || cv[1] !== v[1]) - { - cv[0] = v[0]; - cv[1] = v[1]; - gl.uniform2f(location, v[0], v[1]) - }`, - - vec3: ` - if(cv[0] !== v[0] || cv[1] !== v[1] || cv[2] !== v[2]) - { - cv[0] = v[0]; - cv[1] = v[1]; - cv[2] = v[2]; - - gl.uniform3f(location, v[0], v[1], v[2]) - }`, - - vec4: 'gl.uniform4f(location, v[0], v[1], v[2], v[3])', - - int: 'gl.uniform1i(location, v)', - ivec2: 'gl.uniform2i(location, v[0], v[1])', - ivec3: 'gl.uniform3i(location, v[0], v[1], v[2])', - ivec4: 'gl.uniform4i(location, v[0], v[1], v[2], v[3])', - - bool: 'gl.uniform1i(location, v)', - bvec2: 'gl.uniform2i(location, v[0], v[1])', - bvec3: 'gl.uniform3i(location, v[0], v[1], v[2])', - bvec4: 'gl.uniform4i(location, v[0], v[1], v[2], v[3])', - - mat2: 'gl.uniformMatrix2fv(location, false, v)', - mat3: 'gl.uniformMatrix3fv(location, false, v)', - mat4: 'gl.uniformMatrix4fv(location, false, v)', - - sampler2D: 'gl.uniform1i(location, v)', - samplerCube: 'gl.uniform1i(location, v)', - sampler2DArray: 'gl.uniform1i(location, v)', -}; - -const GLSL_TO_ARRAY_SETTERS = { - - float: `gl.uniform1fv(location, v)`, - - vec2: `gl.uniform2fv(location, v)`, - vec3: `gl.uniform3fv(location, v)`, - vec4: 'gl.uniform4fv(location, v)', - - mat4: 'gl.uniformMatrix4fv(location, false, v)', - mat3: 'gl.uniformMatrix3fv(location, false, v)', - mat2: 'gl.uniformMatrix2fv(location, false, v)', - - int: 'gl.uniform1iv(location, v)', - ivec2: 'gl.uniform2iv(location, v)', - ivec3: 'gl.uniform3iv(location, v)', - ivec4: 'gl.uniform4iv(location, v)', - - bool: 'gl.uniform1iv(location, v)', - bvec2: 'gl.uniform2iv(location, v)', - bvec3: 'gl.uniform3iv(location, v)', - bvec4: 'gl.uniform4iv(location, v)', - - sampler2D: 'gl.uniform1iv(location, v)', - samplerCube: 'gl.uniform1iv(location, v)', - sampler2DArray: 'gl.uniform1iv(location, v)', -}; - -export default function generateUniformsSync(group, uniformData) -{ - let textureCount = 0; - let func = `var v = null; - var cv = null - var gl = renderer.gl`; - - for (const i in group.uniforms) - { - const data = uniformData[i]; - - if (!data) - { - if (group.uniforms[i].group) - { - func += ` - renderer.shader.syncUniformGroup(uv.${i}); - `; - } - - continue; - } - - // TODO && uniformData[i].value !== 0 <-- do we still need this? - if (data.type === 'float' && data.size === 1) - { - func += ` - if(uv.${i} !== ud.${i}.value) - { - ud.${i}.value = uv.${i} - gl.uniform1f(ud.${i}.location, uv.${i}) - }\n`; - } - /* eslint-disable max-len */ - else if ((data.type === 'sampler2D' || data.type === 'samplerCube' || data.type === 'sampler2DArray') && data.size === 1 && !data.isArray) - /* eslint-disable max-len */ - { - func += ` - renderer.texture.bind(uv.${i}, ${textureCount}); - - if(ud.${i}.value !== ${textureCount}) - { - ud.${i}.value = ${textureCount}; - gl.uniform1i(ud.${i}.location, ${textureCount});\n; // eslint-disable-line max-len - }\n`; - - textureCount++; - } - else if (data.type === 'mat3' && data.size === 1) - { - if (group.uniforms[i].a !== undefined) - { - // TODO and some smart caching dirty ids here! - func += ` - gl.uniformMatrix3fv(ud.${i}.location, false, uv.${i}.toArray(true)); - \n`; - } - else - { - func += ` - gl.uniformMatrix3fv(ud.${i}.location, false, uv.${i}); - \n`; - } - } - else if (data.type === 'vec2' && data.size === 1) - { - // TODO - do we need both here? - // maybe we can get away with only using points? - if (group.uniforms[i].x !== undefined) - { - func += ` - cv = ud.${i}.value; - v = uv.${i}; - - if(cv[0] !== v.x || cv[1] !== v.y) - { - cv[0] = v.x; - cv[1] = v.y; - gl.uniform2f(ud.${i}.location, v.x, v.y); - }\n`; - } - else - { - func += ` - cv = ud.${i}.value; - v = uv.${i}; - - if(cv[0] !== v[0] || cv[1] !== v[1]) - { - cv[0] = v[0]; - cv[1] = v[1]; - gl.uniform2f(ud.${i}.location, v[0], v[1]); - } - \n`; - } - } - else if (data.type === 'vec4' && data.size === 1) - { - // TODO - do we need both here? - // maybe we can get away with only using points? - if (group.uniforms[i].width !== undefined) - { - func += ` - cv = ud.${i}.value; - v = uv.${i}; - - if(cv[0] !== v.x || cv[1] !== v.y || cv[2] !== v.width || cv[3] !== v.height) - { - cv[0] = v.x; - cv[1] = v.y; - cv[2] = v.width; - cv[3] = v.height; - gl.uniform4f(ud.${i}.location, v.x, v.y, v.width, v.height) - }\n`; - } - else - { - func += ` - cv = ud.${i}.value; - v = uv.${i}; - - if(cv[0] !== v[0] || cv[1] !== v[1] || cv[2] !== v[2] || cv[3] !== v[3]) - { - cv[0] = v[0]; - cv[1] = v[1]; - cv[2] = v[2]; - cv[3] = v[3]; - - gl.uniform4f(ud.${i}.location, v[0], v[1], v[2], v[3]) - } - \n`; - } - } - else - { - const templateType = (data.size === 1) ? GLSL_TO_SINGLE_SETTERS_CACHED : GLSL_TO_ARRAY_SETTERS; - - const template = templateType[data.type].replace('location', `ud.${i}.location`); - - func += ` - cv = ud.${i}.value; - v = uv.${i}; - ${template};\n`; - } - } - - // console.log(' --------------- ') - // console.log(func); - - return new Function('ud', 'uv', 'renderer', func); // eslint-disable-line no-new-func -} diff --git a/packages/core/src/shader/getTestContext.js b/packages/core/src/shader/getTestContext.js deleted file mode 100644 index ea174cc..0000000 --- a/packages/core/src/shader/getTestContext.js +++ /dev/null @@ -1,48 +0,0 @@ -import { settings } from '@pixi/settings'; - -let context = null; - -/** - * returns a little webGL context to use for program inspection. - * - * @static - * @private - * @returns {webGL-context} a gl context to test with - */ -export default function getTestContext() -{ - if (!context) - { - const canvas = document.createElement('canvas'); - - let gl; - - if (settings.PREFER_WEBGL_2) - { - gl = canvas.getContext('webgl2', {}); - } - - if (!gl) - { - gl = canvas.getContext('webgl', {}) - || canvas.getContext('experimental-webgl', {}); - - if (!gl) - { - // fail, not able to get a context - throw new Error('This browser does not support webGL. Try using the canvas renderer'); - } - else - { - // for shader testing.. - gl.getExtension('WEBGL_draw_buffers'); - } - } - - context = gl; - - return gl; - } - - return context; -} diff --git a/packages/core/src/shader/utils/checkMaxIfStatmentsInShader.js b/packages/core/src/shader/utils/checkMaxIfStatmentsInShader.js new file mode 100644 index 0000000..8047c96 --- /dev/null +++ b/packages/core/src/shader/utils/checkMaxIfStatmentsInShader.js @@ -0,0 +1,58 @@ +const fragTemplate = [ + 'precision mediump float;', + 'void main(void){', + 'float test = 0.1;', + '%forloop%', + 'gl_FragColor = vec4(0.0);', + '}', +].join('\n'); + +export default function checkMaxIfStatmentsInShader(maxIfs, gl) +{ + if (maxIfs === 0) + { + throw new Error('Invalid value of `0` passed to `checkMaxIfStatementsInShader`'); + } + + const shader = gl.createShader(gl.FRAGMENT_SHADER); + + while (true) // eslint-disable-line no-constant-condition + { + const fragmentSrc = fragTemplate.replace(/%forloop%/gi, generateIfTestSrc(maxIfs)); + + gl.shaderSource(shader, fragmentSrc); + gl.compileShader(shader); + + if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) + { + maxIfs = (maxIfs / 2) | 0; + } + else + { + // valid! + break; + } + } + + return maxIfs; +} + +function generateIfTestSrc(maxIfs) +{ + let src = ''; + + for (let i = 0; i < maxIfs; ++i) + { + if (i > 0) + { + src += '\nelse '; + } + + if (i < maxIfs - 1) + { + src += `if(test == ${i}.0){}`; + } + } + + return src; +} diff --git a/packages/core/src/shader/utils/compileProgram.js b/packages/core/src/shader/utils/compileProgram.js new file mode 100644 index 0000000..8c6ccb9 --- /dev/null +++ b/packages/core/src/shader/utils/compileProgram.js @@ -0,0 +1,78 @@ +/** + * @method compileProgram + * @memberof PIXI.glCore.shader + * @param gl {WebGLRenderingContext} The current WebGL context {WebGLProgram} + * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. + * @param fragmentSrc {string|string[]} The fragment shader source as an array of strings. + * @param attributeLocations {Object} An attribute location map that lets you manually set the attribute locations + * @return {WebGLProgram} the shader program + */ +export default function compileProgram(gl, vertexSrc, fragmentSrc, attributeLocations) +{ + const glVertShader = compileShader(gl, gl.VERTEX_SHADER, vertexSrc); + const glFragShader = compileShader(gl, gl.FRAGMENT_SHADER, fragmentSrc); + + let program = gl.createProgram(); + + gl.attachShader(program, glVertShader); + gl.attachShader(program, glFragShader); + + // optionally, set the attributes manually for the program rather than letting WebGL decide.. + if (attributeLocations) + { + for (const i in attributeLocations) + { + gl.bindAttribLocation(program, attributeLocations[i], i); + } + } + + gl.linkProgram(program); + + // if linking fails, then log and cleanup + if (!gl.getProgramParameter(program, gl.LINK_STATUS)) + { + console.error('Pixi.js Error: Could not initialize shader.'); + console.error('gl.VALIDATE_STATUS', gl.getProgramParameter(program, gl.VALIDATE_STATUS)); + console.error('gl.getError()', gl.getError()); + + // if there is a program info log, log it + if (gl.getProgramInfoLog(program) !== '') + { + console.warn('Pixi.js Warning: gl.getProgramInfoLog()', gl.getProgramInfoLog(program)); + } + + gl.deleteProgram(program); + program = null; + } + + // clean up some shaders + gl.deleteShader(glVertShader); + gl.deleteShader(glFragShader); + + return program; +} + +/** + * @private + * @param gl {WebGLRenderingContext} The current WebGL context {WebGLProgram} + * @param type {Number} the type, can be either VERTEX_SHADER or FRAGMENT_SHADER + * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. + * @return {WebGLShader} the shader + */ +function compileShader(gl, type, src) +{ + const shader = gl.createShader(type); + + gl.shaderSource(shader, src); + gl.compileShader(shader); + + if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) + { + console.warn(src); + console.error(gl.getShaderInfoLog(shader)); + + return null; + } + + return shader; +} diff --git a/packages/core/src/shader/utils/defaultValue.js b/packages/core/src/shader/utils/defaultValue.js new file mode 100644 index 0000000..97290bd --- /dev/null +++ b/packages/core/src/shader/utils/defaultValue.js @@ -0,0 +1,79 @@ +/** + * @method defaultValue + * @memberof PIXI.glCore.shader + * @param type {String} Type of value + * @param size {Number} + */ +export default function defaultValue(type, size) +{ + switch (type) + { + case 'float': + return 0; + + case 'vec2': + return new Float32Array(2 * size); + + case 'vec3': + return new Float32Array(3 * size); + + case 'vec4': + return new Float32Array(4 * size); + + case 'int': + case 'sampler2D': + case 'sampler2DArray': + return 0; + + case 'ivec2': + return new Int32Array(2 * size); + + case 'ivec3': + return new Int32Array(3 * size); + + case 'ivec4': + return new Int32Array(4 * size); + + case 'bool': + return false; + + case 'bvec2': + + return booleanArray(2 * size); + + case 'bvec3': + return booleanArray(3 * size); + + case 'bvec4': + return booleanArray(4 * size); + + case 'mat2': + return new Float32Array([1, 0, + 0, 1]); + + case 'mat3': + return new Float32Array([1, 0, 0, + 0, 1, 0, + 0, 0, 1]); + + case 'mat4': + return new Float32Array([1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1]); + } + + return null; +} + +function booleanArray(size) +{ + const array = new Array(size); + + for (let i = 0; i < array.length; i++) + { + array[i] = false; + } + + return array; +} diff --git a/packages/core/src/shader/utils/extractUniformsFromSrc.js b/packages/core/src/shader/utils/extractUniformsFromSrc.js new file mode 100644 index 0000000..3d02c0a --- /dev/null +++ b/packages/core/src/shader/utils/extractUniformsFromSrc.js @@ -0,0 +1,57 @@ +import defaultValue from './defaultValue'; + +export default function extractUniformsFromSrc(vertexSrc, fragmentSrc, mask) +{ + const vertUniforms = extractUniformsFromString(vertexSrc, mask); + const fragUniforms = extractUniformsFromString(fragmentSrc, mask); + + return Object.assign(vertUniforms, fragUniforms); +} + +function extractUniformsFromString(string) +{ + const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); + + const uniforms = {}; + let nameSplit; + + // clean the lines a little - remove extra spaces / teabs etc + // then split along ';' + const lines = string.replace(/\s+/g, ' ') + .split(/\s*;\s*/); + + // loop through.. + for (let i = 0; i < lines.length; i++) + { + const line = lines[i].trim(); + + if (line.indexOf('uniform') > -1) + { + const splitLine = line.split(' '); + const type = splitLine[1]; + + let name = splitLine[2]; + let size = 1; + + if (name.indexOf('[') > -1) + { + // array! + nameSplit = name.split(/\[|]/); + name = nameSplit[0]; + size *= Number(nameSplit[1]); + } + + if (!name.match(maskRegex)) + { + uniforms[name] = { + value: defaultValue(type, size), + dirtyId: 0, + name, + type, + }; + } + } + } + + return uniforms; +} diff --git a/packages/core/src/shader/utils/generateUniformsSync.js b/packages/core/src/shader/utils/generateUniformsSync.js new file mode 100644 index 0000000..98a357f --- /dev/null +++ b/packages/core/src/shader/utils/generateUniformsSync.js @@ -0,0 +1,231 @@ +// cv = CachedValue +// v = value +// ud = uniformData +// uv = uniformValue +// l = loaction +const GLSL_TO_SINGLE_SETTERS_CACHED = { + + float: ` + if(cv !== v) + { + cv.v = v; + gl.uniform1f(location, v) + }`, + + vec2: ` + if(cv[0] !== v[0] || cv[1] !== v[1]) + { + cv[0] = v[0]; + cv[1] = v[1]; + gl.uniform2f(location, v[0], v[1]) + }`, + + vec3: ` + if(cv[0] !== v[0] || cv[1] !== v[1] || cv[2] !== v[2]) + { + cv[0] = v[0]; + cv[1] = v[1]; + cv[2] = v[2]; + + gl.uniform3f(location, v[0], v[1], v[2]) + }`, + + vec4: 'gl.uniform4f(location, v[0], v[1], v[2], v[3])', + + int: 'gl.uniform1i(location, v)', + ivec2: 'gl.uniform2i(location, v[0], v[1])', + ivec3: 'gl.uniform3i(location, v[0], v[1], v[2])', + ivec4: 'gl.uniform4i(location, v[0], v[1], v[2], v[3])', + + bool: 'gl.uniform1i(location, v)', + bvec2: 'gl.uniform2i(location, v[0], v[1])', + bvec3: 'gl.uniform3i(location, v[0], v[1], v[2])', + bvec4: 'gl.uniform4i(location, v[0], v[1], v[2], v[3])', + + mat2: 'gl.uniformMatrix2fv(location, false, v)', + mat3: 'gl.uniformMatrix3fv(location, false, v)', + mat4: 'gl.uniformMatrix4fv(location, false, v)', + + sampler2D: 'gl.uniform1i(location, v)', + samplerCube: 'gl.uniform1i(location, v)', + sampler2DArray: 'gl.uniform1i(location, v)', +}; + +const GLSL_TO_ARRAY_SETTERS = { + + float: `gl.uniform1fv(location, v)`, + + vec2: `gl.uniform2fv(location, v)`, + vec3: `gl.uniform3fv(location, v)`, + vec4: 'gl.uniform4fv(location, v)', + + mat4: 'gl.uniformMatrix4fv(location, false, v)', + mat3: 'gl.uniformMatrix3fv(location, false, v)', + mat2: 'gl.uniformMatrix2fv(location, false, v)', + + int: 'gl.uniform1iv(location, v)', + ivec2: 'gl.uniform2iv(location, v)', + ivec3: 'gl.uniform3iv(location, v)', + ivec4: 'gl.uniform4iv(location, v)', + + bool: 'gl.uniform1iv(location, v)', + bvec2: 'gl.uniform2iv(location, v)', + bvec3: 'gl.uniform3iv(location, v)', + bvec4: 'gl.uniform4iv(location, v)', + + sampler2D: 'gl.uniform1iv(location, v)', + samplerCube: 'gl.uniform1iv(location, v)', + sampler2DArray: 'gl.uniform1iv(location, v)', +}; + +export default function generateUniformsSync(group, uniformData) +{ + let textureCount = 0; + let func = `var v = null; + var cv = null + var gl = renderer.gl`; + + for (const i in group.uniforms) + { + const data = uniformData[i]; + + if (!data) + { + if (group.uniforms[i].group) + { + func += ` + renderer.shader.syncUniformGroup(uv.${i}); + `; + } + + continue; + } + + // TODO && uniformData[i].value !== 0 <-- do we still need this? + if (data.type === 'float' && data.size === 1) + { + func += ` + if(uv.${i} !== ud.${i}.value) + { + ud.${i}.value = uv.${i} + gl.uniform1f(ud.${i}.location, uv.${i}) + }\n`; + } + /* eslint-disable max-len */ + else if ((data.type === 'sampler2D' || data.type === 'samplerCube' || data.type === 'sampler2DArray') && data.size === 1 && !data.isArray) + /* eslint-disable max-len */ + { + func += ` + renderer.texture.bind(uv.${i}, ${textureCount}); + + if(ud.${i}.value !== ${textureCount}) + { + ud.${i}.value = ${textureCount}; + gl.uniform1i(ud.${i}.location, ${textureCount});\n; // eslint-disable-line max-len + }\n`; + + textureCount++; + } + else if (data.type === 'mat3' && data.size === 1) + { + if (group.uniforms[i].a !== undefined) + { + // TODO and some smart caching dirty ids here! + func += ` + gl.uniformMatrix3fv(ud.${i}.location, false, uv.${i}.toArray(true)); + \n`; + } + else + { + func += ` + gl.uniformMatrix3fv(ud.${i}.location, false, uv.${i}); + \n`; + } + } + else if (data.type === 'vec2' && data.size === 1) + { + // TODO - do we need both here? + // maybe we can get away with only using points? + if (group.uniforms[i].x !== undefined) + { + func += ` + cv = ud.${i}.value; + v = uv.${i}; + + if(cv[0] !== v.x || cv[1] !== v.y) + { + cv[0] = v.x; + cv[1] = v.y; + gl.uniform2f(ud.${i}.location, v.x, v.y); + }\n`; + } + else + { + func += ` + cv = ud.${i}.value; + v = uv.${i}; + + if(cv[0] !== v[0] || cv[1] !== v[1]) + { + cv[0] = v[0]; + cv[1] = v[1]; + gl.uniform2f(ud.${i}.location, v[0], v[1]); + } + \n`; + } + } + else if (data.type === 'vec4' && data.size === 1) + { + // TODO - do we need both here? + // maybe we can get away with only using points? + if (group.uniforms[i].width !== undefined) + { + func += ` + cv = ud.${i}.value; + v = uv.${i}; + + if(cv[0] !== v.x || cv[1] !== v.y || cv[2] !== v.width || cv[3] !== v.height) + { + cv[0] = v.x; + cv[1] = v.y; + cv[2] = v.width; + cv[3] = v.height; + gl.uniform4f(ud.${i}.location, v.x, v.y, v.width, v.height) + }\n`; + } + else + { + func += ` + cv = ud.${i}.value; + v = uv.${i}; + + if(cv[0] !== v[0] || cv[1] !== v[1] || cv[2] !== v[2] || cv[3] !== v[3]) + { + cv[0] = v[0]; + cv[1] = v[1]; + cv[2] = v[2]; + cv[3] = v[3]; + + gl.uniform4f(ud.${i}.location, v[0], v[1], v[2], v[3]) + } + \n`; + } + } + else + { + const templateType = (data.size === 1) ? GLSL_TO_SINGLE_SETTERS_CACHED : GLSL_TO_ARRAY_SETTERS; + + const template = templateType[data.type].replace('location', `ud.${i}.location`); + + func += ` + cv = ud.${i}.value; + v = uv.${i}; + ${template};\n`; + } + } + + // console.log(' --------------- ') + // console.log(func); + + return new Function('ud', 'uv', 'renderer', func); // eslint-disable-line no-new-func +} diff --git a/packages/core/src/shader/utils/getTestContext.js b/packages/core/src/shader/utils/getTestContext.js new file mode 100644 index 0000000..ea174cc --- /dev/null +++ b/packages/core/src/shader/utils/getTestContext.js @@ -0,0 +1,48 @@ +import { settings } from '@pixi/settings'; + +let context = null; + +/** + * returns a little webGL context to use for program inspection. + * + * @static + * @private + * @returns {webGL-context} a gl context to test with + */ +export default function getTestContext() +{ + if (!context) + { + const canvas = document.createElement('canvas'); + + let gl; + + if (settings.PREFER_WEBGL_2) + { + gl = canvas.getContext('webgl2', {}); + } + + if (!gl) + { + gl = canvas.getContext('webgl', {}) + || canvas.getContext('experimental-webgl', {}); + + if (!gl) + { + // fail, not able to get a context + throw new Error('This browser does not support webGL. Try using the canvas renderer'); + } + else + { + // for shader testing.. + gl.getExtension('WEBGL_draw_buffers'); + } + } + + context = gl; + + return gl; + } + + return context; +} diff --git a/packages/core/src/shader/utils/index.js b/packages/core/src/shader/utils/index.js new file mode 100644 index 0000000..4bf8dd4 --- /dev/null +++ b/packages/core/src/shader/utils/index.js @@ -0,0 +1,8 @@ +export { default as compileProgram } from './compileProgram'; +export { default as defaultValue } from './defaultValue'; +export { default as setPrecision } from './setPrecision'; +export { default as mapSize } from './mapSize'; +export { default as mapType } from './mapType'; +export { default as generateUniformsSync } from './generateUniformsSync'; +export { default as getTestContext } from './getTestContext'; +export { default as checkMaxIfStatmentsInShader } from './checkMaxIfStatmentsInShader'; diff --git a/packages/core/src/shader/utils/mapSize.js b/packages/core/src/shader/utils/mapSize.js new file mode 100644 index 0000000..b681554 --- /dev/null +++ b/packages/core/src/shader/utils/mapSize.js @@ -0,0 +1,33 @@ +const GLSL_TO_SIZE = { + float: 1, + vec2: 2, + vec3: 3, + vec4: 4, + + int: 1, + ivec2: 2, + ivec3: 3, + ivec4: 4, + + bool: 1, + bvec2: 2, + bvec3: 3, + bvec4: 4, + + mat2: 4, + mat3: 9, + mat4: 16, + + sampler2D: 1, +}; + +/** + * @method mapSize + * @memberof PIXI.glCore.shader + * @param type {String} + * @return {Number} + */ +export default function mapSize(type) +{ + return GLSL_TO_SIZE[type]; +} diff --git a/packages/core/src/shader/utils/mapType.js b/packages/core/src/shader/utils/mapType.js new file mode 100644 index 0000000..d288b7c --- /dev/null +++ b/packages/core/src/shader/utils/mapType.js @@ -0,0 +1,45 @@ +let GL_TABLE = null; + +const GL_TO_GLSL_TYPES = { + FLOAT: 'float', + FLOAT_VEC2: 'vec2', + FLOAT_VEC3: 'vec3', + FLOAT_VEC4: 'vec4', + + INT: 'int', + INT_VEC2: 'ivec2', + INT_VEC3: 'ivec3', + INT_VEC4: 'ivec4', + + BOOL: 'bool', + BOOL_VEC2: 'bvec2', + BOOL_VEC3: 'bvec3', + BOOL_VEC4: 'bvec4', + + FLOAT_MAT2: 'mat2', + FLOAT_MAT3: 'mat3', + FLOAT_MAT4: 'mat4', + + SAMPLER_2D: 'sampler2D', + SAMPLER_CUBE: 'samplerCube', + SAMPLER_2D_ARRAY: 'sampler2DArray', +}; + +export default function mapType(gl, type) +{ + if (!GL_TABLE) + { + const typeNames = Object.keys(GL_TO_GLSL_TYPES); + + GL_TABLE = {}; + + for (let i = 0; i < typeNames.length; ++i) + { + const tn = typeNames[i]; + + GL_TABLE[gl[tn]] = GL_TO_GLSL_TYPES[tn]; + } + } + + return GL_TABLE[type]; +} diff --git a/packages/core/src/shader/utils/setPrecision.js b/packages/core/src/shader/utils/setPrecision.js new file mode 100644 index 0000000..e2c1f34 --- /dev/null +++ b/packages/core/src/shader/utils/setPrecision.js @@ -0,0 +1,18 @@ +/** + * Sets the float precision on the shader. If the precision is already present this function will do nothing + * @param {string} src the shader source + * @param {string} precision The float precision of the shader. Options are 'lowp', 'mediump' or 'highp'. + * + * @return {string} modified shader source + */ +export default function setPrecision(src, precision) +{ + src = src.trim(); + + if (src.substring(0, 9) !== 'precision' && src.substring(0, 1) !== '#') + { + return `precision ${precision} float;\n${src}`; + } + + return src; +} diff --git a/packages/core/src/state/State.js b/packages/core/src/state/State.js new file mode 100644 index 0000000..fa3f221 --- /dev/null +++ b/packages/core/src/state/State.js @@ -0,0 +1,163 @@ +/* eslint-disable max-len */ + +const BLEND = 0; +const OFFSET = 1; +const CULLING = 2; +const DEPTH_TEST = 3; +const WINDING = 4; + +/** + * This is a webGL state. It is passed The WebGL StateManager. + * Each mesh renderered may require webGL to be in a different state. + * For example you may want different blend mode or to enable polygon offsets + * + * @class + * @memberof PIXI + */ +export default class State +{ + /** + * + */ + constructor() + { + this.data = 0; + + this.blendMode = 0; + this.polygonOffset = 0; + + this.blend = true; + // this.depthTest = true; + } + + /** + * Activates blending of the computed fragment color values + * + * @member {boolean} + */ + get blend() + { + return !!(this.data & (1 << BLEND)); + } + + set blend(value) // eslint-disable-line require-jsdoc + { + if (!!(this.data & (1 << BLEND)) !== value) + { + this.data ^= (1 << BLEND); + } + } + + /** + * Activates adding an offset to depth values of polygon's fragments + * + * @member {boolean} + * @default false + */ + get offsets() + { + return !!(this.data & (1 << OFFSET)); + } + + set offsets(value) // eslint-disable-line require-jsdoc + { + if (!!(this.data & (1 << OFFSET)) !== value) + { + this.data ^= (1 << OFFSET); + } + } + + /** + * Activates culling of polygons. + * + * @member {boolean} + * @default false + */ + get culling() + { + return !!(this.data & (1 << CULLING)); + } + + set culling(value) // eslint-disable-line require-jsdoc + { + if (!!(this.data & (1 << CULLING)) !== value) + { + this.data ^= (1 << CULLING); + } + } + + /** + * Activates depth comparisons and updates to the depth buffer. + * + * @member {boolean} + * @default false + */ + get depthTest() + { + return !!(this.data & (1 << DEPTH_TEST)); + } + + set depthTest(value) // eslint-disable-line require-jsdoc + { + if (!!(this.data & (1 << DEPTH_TEST)) !== value) + { + this.data ^= (1 << DEPTH_TEST); + } + } + + /** + * Specifies whether or not front or back-facing polygons can be culled. + * @member {boolean} + * @default false + */ + get clockwiseFrontFace() + { + return !!(this.data & (1 << WINDING)); + } + + set clockwiseFrontFace(value) // eslint-disable-line require-jsdoc + { + if (!!(this.data & (1 << WINDING)) !== value) + { + this.data ^= (1 << WINDING); + } + } + + /** + * The blend mode to be applied when this state is set. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. + * Setting this mode to anything other than NO_BLEND will automatically switch blending on. + * + * @member {boolean} + * @default PIXI.BLEND_MODES.NORMAL + * @see PIXI.BLEND_MODES + */ + get blendMode() + { + return this._blendMode; + } + + set blendMode(value) // eslint-disable-line require-jsdoc + { + // 17 is NO BLEND + this.blend = (value !== 17); + this._blendMode = value; + } + + /** + * The polygon offset. Setting this property to anything other than 0 will automatically enable poygon offset fill. + * + * @member {number} + * @default 0 + */ + get polygonOffset() + { + return this._polygonOffset; + } + + set polygonOffset(value) // eslint-disable-line require-jsdoc + { + this.offsets = !!value; + this._polygonOffset = value; + } +} + diff --git a/packages/core/src/state/StateSystem.js b/packages/core/src/state/StateSystem.js new file mode 100755 index 0000000..39c35bb --- /dev/null +++ b/packages/core/src/state/StateSystem.js @@ -0,0 +1,288 @@ +import mapWebGLBlendModesToPixi from './utils/mapWebGLBlendModesToPixi'; +import System from '../System'; +import WebGLState from './State'; + +const BLEND = 0; +const OFFSET = 1; +const CULLING = 2; +const DEPTH_TEST = 3; +const WINDING = 4; + +/** + * A WebGL state machines + * + * @class + * @extends PIXI.systems.System + * @memberof PIXI.systems + */ +export default class StateSystem extends System +{ + /** + * @param {WebGLRenderingContext} gl - The current WebGL rendering context + */ + constructor(renderer) + { + super(renderer); + + this.gl = null; + + this.maxAttribs = null; + + // check we have vao.. + this.nativeVaoExtension = null; + + this.attribState = null; + + this.stateId = 0; + this.polygonOffset = 0; + this.blendMode = 17; + + this.map = []; + + // map functions for when we set state.. + this.map[BLEND] = this.setBlend; + this.map[OFFSET] = this.setOffset; + this.map[CULLING] = this.setCullFace; + this.map[DEPTH_TEST] = this.setDepthTest; + this.map[WINDING] = this.setFrontFace; + + this.checks = []; + + this.defaultState = new WebGLState(); + this.defaultState.blend = true; + this.defaultState.depth = true; + } + + contextChange(gl) + { + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); + + // check we have vao.. + this.nativeVaoExtension = ( + gl.getExtension('OES_vertex_array_object') + || gl.getExtension('MOZ_OES_vertex_array_object') + || gl.getExtension('WEBKIT_OES_vertex_array_object') + ); + + this.attribState = { + tempAttribState: new Array(this.maxAttribs), + attribState: new Array(this.maxAttribs), + }; + + this.blendModes = mapWebGLBlendModesToPixi(gl); + + this.setState(this.defaultState); + + this.reset(); + } + + /** + * Sets the current state + * + * @param {*} state - The state to set. + */ + setState(state) + { + state = state || this.defaultState; + + // TODO maybe to an object check? ( this.state === state )? + if (this.stateId !== state.data) + { + let diff = this.stateId ^ state.data; + let i = 0; + + // order from least to most common + while (diff) + { + if (diff & 1) + { + // state change! + this.map[i].call(this, !!(state.data & (1 << i))); + } + + diff = diff >> 1; + i++; + } + + this.stateId = state.data; + } + + // based on the above settings we check for specific modes.. + // for example if blend is active we check and set the blend modes + // or of polygon offset is active we check the poly depth. + for (let i = 0; i < this.checks.length; i++) + { + this.checks[i](this, state); + } + } + + /** + * Enables or disabled blending. + * + * @param {boolean} value - Turn on or off webgl blending. + */ + setBlend(value) + { + this.updateCheck(StateSystem.checkBlendMode, value); + + this.gl[value ? 'enable' : 'disable'](this.gl.BLEND); + } + + /** + * Enables or disable polygon offset fill + * + * @param {boolean} value - Turn on or off webgl polygon offset testing. + */ + setOffset(value) + { + this.gl[value ? 'enable' : 'disable'](this.gl.POLYGON_OFFSET_FILL); + } + + /** + * Sets whether to enable or disable depth test. + * + * @param {boolean} value - Turn on or off webgl depth testing. + */ + setDepthTest(value) + { + this.gl[value ? 'enable' : 'disable'](this.gl.DEPTH_TEST); + } + + /** + * Sets whether to enable or disable cull face. + * + * @param {boolean} value - Turn on or off webgl cull face. + */ + setCullFace(value) + { + this.gl[value ? 'enable' : 'disable'](this.gl.CULL_FACE); + } + + /** + * Sets the gl front face. + * + * @param {boolean} value - true is clockwise and false is counter-clockwise + */ + setFrontFace(value) + { + this.gl.frontFace(this.gl[value ? 'CW' : 'CCW']); + } + + /** + * Sets the blend mode. + * + * @param {number} value - The blend mode to set to. + */ + setBlendMode(value) + { + if (value === this.blendMode) + { + return; + } + + this.blendMode = value; + this.gl.blendFunc(this.blendModes[value][0], this.blendModes[value][1]); + } + + /** + * Sets the polygon offset. + * + * @param {number} value - the polygon offset + * @param {number} scale - the polygon offset scale + */ + setPolygonOffset(value, scale) + { + this.gl.polygonOffset(value, scale); + } + + /** + * Disables all the vaos in use + * + */ + resetAttributes() + { + for (let i = 0; i < this.attribState.tempAttribState.length; i++) + { + this.attribState.tempAttribState[i] = 0; + } + + for (let i = 0; i < this.attribState.attribState.length; i++) + { + this.attribState.attribState[i] = 0; + } + + // im going to assume one is always active for performance reasons. + for (let i = 1; i < this.maxAttribs; i++) + { + this.gl.disableVertexAttribArray(i); + } + } + + // used + /** + * Resets all the logic and disables the vaos + */ + reset() + { + // unbind any VAO if they exist.. + if (this.nativeVaoExtension) + { + this.nativeVaoExtension.bindVertexArrayOES(null); + } + + // reset all attributes.. + this.resetAttributes(); + + this.gl.pixelStorei(this.gl.UNPACK_FLIP_Y_WEBGL, false); + + this.setBlendMode(0); + + // TODO? + // this.setState(this.defaultState); + } + + /** + * checks to see which updates should be checked based on which settings have been activated + * for example if blend is enabled then we shold check the blend modes each time the state is changed + * or if poygon fill is activated then we need to check if the polygone offset changes. + * The idea is that we only check what we have too. + * + * @param {Function} func the checking function to add or remove + * @param {boolean} value should the check function be added or removed. + */ + updateCheck(func, value) + { + const index = this.checks.indexOf(func); + + if (value && index === -1) + { + this.checks.push(func); + } + else if (!value && index !== -1) + { + this.checks.splice(index, 1); + } + } + + /** + * A private little wrapper function that we call to check the blend mode. + * + * @static + * @private + * @param {PIXI.StateSystem} System the System to perform the state check on + * @param {PIXI.State} state the state that the blendMode will pulled from + */ + static checkBlendMode(system, state) + { + system.setBlendMode(state.blendMode); + } + + // TODO - add polygon offset? +} diff --git a/packages/core/src/state/utils/mapWebGLBlendModesToPixi.js b/packages/core/src/state/utils/mapWebGLBlendModesToPixi.js new file mode 100644 index 0000000..10ffe4d --- /dev/null +++ b/packages/core/src/state/utils/mapWebGLBlendModesToPixi.js @@ -0,0 +1,47 @@ +import { BLEND_MODES } from '@pixi/constants'; + +/** + * Maps gl blend combinations to WebGL. + * + * @memberof PIXI + * @function mapWebGLBlendModesToPixi + * @private + * @param {WebGLRenderingContext} gl - The rendering context. + * @param {string[]} [array=[]] - The array to output into. + * @return {string[]} Mapped modes. + */ +export default function mapWebGLBlendModesToPixi(gl, array = []) +{ + // TODO - premultiply alpha would be different. + // add a boolean for that! + array[BLEND_MODES.NORMAL] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; + array[BLEND_MODES.ADD] = [gl.ONE, gl.DST_ALPHA]; + array[BLEND_MODES.MULTIPLY] = [gl.DST_COLOR, gl.ONE_MINUS_SRC_ALPHA]; + array[BLEND_MODES.SCREEN] = [gl.ONE, gl.ONE_MINUS_SRC_COLOR]; + array[BLEND_MODES.OVERLAY] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; + array[BLEND_MODES.DARKEN] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; + array[BLEND_MODES.LIGHTEN] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; + array[BLEND_MODES.COLOR_DODGE] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; + array[BLEND_MODES.COLOR_BURN] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; + array[BLEND_MODES.HARD_LIGHT] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; + array[BLEND_MODES.SOFT_LIGHT] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; + array[BLEND_MODES.DIFFERENCE] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; + array[BLEND_MODES.EXCLUSION] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; + array[BLEND_MODES.HUE] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; + array[BLEND_MODES.SATURATION] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; + array[BLEND_MODES.COLOR] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; + array[BLEND_MODES.LUMINOSITY] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; + array[BLEND_MODES.NONE] = [0, 0]; + + // not-premultiplied blend modes + array[BLEND_MODES.NORMAL_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; + array[BLEND_MODES.ADD_NPM] = [gl.SRC_ALPHA, gl.DST_ALPHA, gl.ONE, gl.DST_ALPHA]; + array[BLEND_MODES.SCREEN_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_COLOR, gl.ONE, gl.ONE_MINUS_SRC_COLOR]; + + // not-premultiplied blend modes + array[BLEND_MODES.NORMAL_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; + array[BLEND_MODES.ADD_NPM] = [gl.SRC_ALPHA, gl.DST_ALPHA, gl.ONE, gl.DST_ALPHA]; + array[BLEND_MODES.SCREEN_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_COLOR, gl.ONE, gl.ONE_MINUS_SRC_COLOR]; + + return array; +} diff --git a/packages/core/src/systems.js b/packages/core/src/systems.js new file mode 100644 index 0000000..8edd9d5 --- /dev/null +++ b/packages/core/src/systems.js @@ -0,0 +1,17 @@ +/** + * Systems are individual components to the Renderer pipeline. + * @namespace PIXI.systems + */ +export { default as FilterSystem } from './filters/FilterSystem'; +export { default as BatchSystem } from './batch/BatchSystem'; +export { default as ContextSystem } from './context/ContextSystem'; +export { default as FramebufferSystem } from './framebuffer/FramebufferSystem'; +export { default as GeometrySystem } from './geometry/GeometrySystem'; +export { default as MaskSystem } from './mask/MaskSystem'; +export { default as StencilSystem } from './mask/StencilSystem'; +export { default as ProjectionSystem } from './projection/ProjectionSystem'; +export { default as RenderTextureSystem } from './renderTexture/RenderTextureSystem'; +export { default as ShaderSystem } from './shader/ShaderSystem'; +export { default as StateSystem } from './state/StateSystem'; +export { default as TextureGCSystem } from './textures/TextureGCSystem'; +export { default as TextureSystem } from './textures/TextureSystem'; diff --git a/packages/core/src/textures/BaseRenderTexture.js b/packages/core/src/textures/BaseRenderTexture.js index 0d4611e..77c44f5 100644 --- a/packages/core/src/textures/BaseRenderTexture.js +++ b/packages/core/src/textures/BaseRenderTexture.js @@ -1,5 +1,5 @@ import BaseTexture from './BaseTexture'; -import FrameBuffer from './FrameBuffer'; +import FrameBuffer from '../framebuffer/FrameBuffer'; /** * A BaseRenderTexture is a special texture that allows any PixiJS display object to be rendered to it. diff --git a/packages/core/src/textures/FrameBuffer.js b/packages/core/src/textures/FrameBuffer.js deleted file mode 100644 index 58c8f39..0000000 --- a/packages/core/src/textures/FrameBuffer.js +++ /dev/null @@ -1,106 +0,0 @@ -import Texture from './BaseTexture'; -import { FORMATS, TYPES } from '@pixi/constants'; - -/** - * Frame buffer - * @class - * @memberof PIXI - */ -export default class FrameBuffer -{ - constructor(width, height) - { - this.width = width || 100; - this.height = height || 100; - - this.stencil = false; - this.depth = false; - - this.dirtyId = 0; - this.dirtyFormat = 0; - this.dirtySize = 0; - - this.depthTexture = null; - this.colorTextures = []; - - this.glFrameBuffers = {}; - } - - get colorTexture() - { - return this.colorTextures[0]; - } - - addColorTexture(index, texture) - { - // TODO add some validation to the texture - same width / height etc? - this.colorTextures[index || 0] = texture || new Texture(null, { scaleMode: 0, - resolution: 1, - mipmap: false, - width: this.width, - height: this.height });// || new Texture(); - - this.dirtyId++; - this.dirtyFormat++; - - return this; - } - - addDepthTexture(texture) - { - /* eslint-disable max-len */ - this.depthTexture = texture || new Texture(null, { scaleMode: 0, - resolution: 1, - width: this.width, - height: this.height, - mipmap: false, - format: FORMATS.DEPTH_COMPONENT, - type: TYPES.UNSIGNED_SHORT });// UNSIGNED_SHORT; - /* eslint-disable max-len */ - this.dirtyId++; - this.dirtyFormat++; - - return this; - } - - enableDepth() - { - this.depth = true; - - this.dirtyId++; - this.dirtyFormat++; - - return this; - } - - enableStencil() - { - this.stencil = true; - - this.dirtyId++; - this.dirtyFormat++; - - return this; - } - - resize(width, height) - { - if (width === this.width && height === this.height) return; - - this.width = width; - this.height = height; - - this.dirtyId++; - this.dirtySize++; - - for (let i = 0; i < this.colorTextures.length; i++) - { - this.colorTextures[i].setSize(width, height); - } - - if (this.depthTexture) - { - this.depthTexture.setSize(width, height); - } - } -} diff --git a/packages/core/src/textures/GLTexture.js b/packages/core/src/textures/GLTexture.js new file mode 100644 index 0000000..c3fb8dc --- /dev/null +++ b/packages/core/src/textures/GLTexture.js @@ -0,0 +1,33 @@ +export default class GLTexture +{ + constructor(texture) + { + /** + * The WebGL texture + * + * @member {WebGLTexture} + */ + this.texture = texture; + + this.width = -1; + this.height = -1; + + /** + * Texture contents dirty flag + * @member {number} + */ + this.dirtyId = -1; + + /** + * Texture style dirty flag + * @type {number} + */ + this.dirtyStyleId = -1; + + /** + * Whether mip levels has to be generated + * @type {boolean} + */ + this.mipmap = false; + } +} diff --git a/packages/core/src/textures/TextureGCSystem.js b/packages/core/src/textures/TextureGCSystem.js new file mode 100644 index 0000000..6d07606 --- /dev/null +++ b/packages/core/src/textures/TextureGCSystem.js @@ -0,0 +1,111 @@ +import System from '../System'; +import { GC_MODES } from '@pixi/constants'; +import { settings } from '@pixi/settings'; + +/** + * TextureGarbageCollector. This class manages the GPU and ensures that it does not get clogged + * up with textures that are no longer being used. + * + * @class + * @memberof PIXI.systems + * @extends PIXI.System + */ +export default class TextureGCSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.count = 0; + this.checkCount = 0; + this.maxIdle = settings.GC_MAX_IDLE; + this.checkCountMax = settings.GC_MAX_CHECK_COUNT; + this.mode = settings.GC_MODE; + } + + /** + * Checks to see when the last time a texture was used + * if the texture has not been used for a specified amount of time it will be removed from the GPU + */ + postrender() + { + this.count++; + + if (this.mode === GC_MODES.MANUAL) + { + return; + } + + this.checkCount++; + + if (this.checkCount > this.checkCountMax) + { + this.checkCount = 0; + + this.run(); + } + } + + /** + * Checks to see when the last time a texture was used + * if the texture has not been used for a specified amount of time it will be removed from the GPU + */ + run() + { + const tm = this.renderer.texture; + const managedTextures = tm.managedTextures; + let wasRemoved = false; + + for (let i = 0; i < managedTextures.length; i++) + { + const texture = managedTextures[i]; + + // only supports non generated textures at the moment! + if (!texture.frameBuffer && this.count - texture.touched > this.maxIdle) + { + tm.destroyTexture(texture, true); + managedTextures[i] = null; + wasRemoved = true; + } + } + + if (wasRemoved) + { + let j = 0; + + for (let i = 0; i < managedTextures.length; i++) + { + if (managedTextures[i] !== null) + { + managedTextures[j++] = managedTextures[i]; + } + } + + managedTextures.length = j; + } + } + + /** + * Removes all the textures within the specified displayObject and its children from the GPU + * + * @param {PIXI.DisplayObject} displayObject - the displayObject to remove the textures from. + */ + unload(displayObject) + { + const tm = this.renderer.textureSystem; + + // only destroy non generated textures + if (displayObject._texture && displayObject._texture._glRenderTargets) + { + tm.destroyTexture(displayObject._texture); + } + + for (let i = displayObject.children.length - 1; i >= 0; i--) + { + this.unload(displayObject.children[i]); + } + } +} diff --git a/packages/core/src/AbstractRenderer.js b/packages/core/src/AbstractRenderer.js new file mode 100644 index 0000000..64b2eb1 --- /dev/null +++ b/packages/core/src/AbstractRenderer.js @@ -0,0 +1,353 @@ +import { hex2string, hex2rgb } from '@pixi/utils'; +import { Matrix, Rectangle } from '@pixi/math'; +import { RENDERER_TYPE } from '@pixi/constants'; +import { settings } from '@pixi/settings'; +import { Container } from '@pixi/display'; +import RenderTexture from './renderTexture/RenderTexture'; +import EventEmitter from 'eventemitter3'; + +const tempMatrix = new Matrix(); + +/** + * 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 EventEmitter + * @memberof PIXI + */ +export default class AbstractRenderer extends EventEmitter +{ + // eslint-disable-next-line valid-jsdoc + /** + * @param {string} system - The name of the system this renderer is for. + * @param {object} [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.transparent=false] - If the render view is transparent, default false + * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false + * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. The + * resolution of the renderer retina would be 2. + * @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 {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when rendering, + * stopping pixel interpolation. + */ + constructor(system, options, arg2, arg3) + { + super(); + + // Support for constructor(system, screenWidth, screenHeight, options) + if (typeof options === 'number') + { + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + }, arg3); + } + + // 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 = RENDERER_TYPE.UNKNOWN; + + /** + * Measurements of the screen. (0, 0, screenWidth, screenHeight) + * + * Its safe to use as filterArea or hitArea for 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 1 + */ + this.resolution = options.resolution || settings.RESOLUTION; + + /** + * Whether the render view is transparent + * + * @member {boolean} + */ + this.transparent = options.transparent; + + /** + * Whether css dimensions of canvas view should be resized to screen dimensions automatically + * + * @member {boolean} + */ + this.autoResize = options.autoResize || false; + + /** + * Tracks the blend modes useful for this renderer. + * + * @member {object} + */ + this.blendModes = null; + + /** + * 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; + + /** + * If true PixiJS will Math.floor() x/y values when rendering, stopping pixel interpolation. + * Handy for crisp pixel art and speed on legacy devices. + * + * @member {boolean} + */ + this.roundPixels = options.roundPixels; + + /** + * The background color as a number. + * + * @member {number} + * @private + */ + this._backgroundColor = 0x000000; + + /** + * The background color as an [R, G, B] array. + * + * @member {number[]} + * @private + */ + this._backgroundColorRgba = [0, 0, 0, 0]; + + /** + * The background color as a string. + * + * @member {string} + * @private + */ + this._backgroundColorString = '#000000'; + + this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter + + /** + * This temporary display object used as the parent of the currently being rendered item + * + * @member {PIXI.DisplayObject} + * @private + */ + this._tempDisplayObjectParent = new Container(); + + /** + * The last root object that the renderer tried to render. + * + * @member {PIXI.DisplayObject} + * @private + */ + this._lastObjectRendered = this._tempDisplayObjectParent; + + /** + * Collection of plugins. + * @readonly + * @member {object} + */ + this.plugins = {}; + } + + /** + * Initialize the plugins. + * + * @protected + * @param {object} staticMap - The dictionary of staticly saved plugins. + */ + initPlugins(staticMap) + { + 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() + { + return this.view.width; + } + + /** + * Same as view.height, actual number of pixels in the canvas by vertical + * + * @member {number} + * @readonly + * @default 600 + */ + get height() + { + return this.view.height; + } + + /** + * Resizes the screen and canvas to the specified width and height + * Canvas dimensions are multiplied by resolution + * + * @param {number} screenWidth - the new width of the screen + * @param {number} screenHeight - the new height of the screen + */ + resize(screenWidth, screenHeight) + { + this.screen.width = screenWidth; + this.screen.height = screenHeight; + + this.view.width = screenWidth * this.resolution; + this.view.height = screenHeight * this.resolution; + + if (this.autoResize) + { + this.view.style.width = `${screenWidth}px`; + this.view.style.height = `${screenHeight}px`; + } + } + + /** + * 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 {PIXI.DisplayObject} displayObject - The displayObject the object will be generated from + * @param {number} scaleMode - Should be one of the scaleMode consts + * @param {number} resolution - The resolution / device pixel ratio of the texture being generated + * @param {PIXI.Rectangle} [region] - The region of the displayObject, that shall be rendered, + * if no region is specified, defaults to the local bounds of the displayObject. + * @return {PIXI.Texture} a texture of the graphics object + */ + generateTexture(displayObject, scaleMode, resolution, region) + { + region = region || displayObject.getLocalBounds(); + + // 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(region.width | 0, region.height | 0, scaleMode, resolution); + + tempMatrix.tx = -region.x; + tempMatrix.ty = -region.y; + + this.render(displayObject, renderTexture, false, tempMatrix, true); + + return renderTexture; + } + + /** + * Removes everything from the renderer and optionally removes the Canvas DOM element. + * + * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. + */ + destroy(removeView) + { + 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); + } + + this.plugins = null; + + this.type = RENDERER_TYPE.UNKNOWN; + + this.view = null; + + this.screen = null; + + this.resolution = 0; + + this.transparent = false; + + this.autoResize = false; + + this.blendModes = null; + + this.options = null; + + this.preserveDrawingBuffer = false; + this.clearBeforeRender = false; + + this.roundPixels = false; + + this._backgroundColor = 0; + this._backgroundColorRgba = null; + this._backgroundColorString = null; + + this._tempDisplayObjectParent = null; + this._lastObjectRendered = null; + } + + /** + * The background color to fill if not transparent + * + * @member {number} + */ + get backgroundColor() + { + return this._backgroundColor; + } + + set backgroundColor(value) // eslint-disable-line require-jsdoc + { + this._backgroundColor = value; + this._backgroundColorString = hex2string(value); + hex2rgb(value, this._backgroundColorRgba); + } +} diff --git a/packages/core/src/Renderer.js b/packages/core/src/Renderer.js new file mode 100644 index 0000000..2227e78 --- /dev/null +++ b/packages/core/src/Renderer.js @@ -0,0 +1,338 @@ +import AbstractRenderer from './AbstractRenderer'; +import { sayHello } from '@pixi/utils'; +import MaskSystem from './mask/MaskSystem'; +import StencilSystem from './mask/StencilSystem'; +import FilterSystem from './filters/FilterSystem'; +import FramebufferSystem from './framebuffer/FramebufferSystem'; +import RenderTextureSystem from './renderTexture/RenderTextureSystem'; +import TextureSystem from './textures/TextureSystem'; +import ProjectionSystem from './projection/ProjectionSystem'; +import StateSystem from './state/StateSystem'; +import GeometrySystem from './geometry/GeometrySystem'; +import ShaderSystem from './shader/ShaderSystem'; +import ContextSystem from './context/ContextSystem'; +import BatchSystem from './batch/BatchSystem'; +import TextureGCSystem from './textures/TextureGCSystem'; +import { RENDERER_TYPE } from '@pixi/constants'; +import UniformGroup from './shader/UniformGroup'; +import { Matrix } from '@pixi/math'; +import Runner from 'mini-runner'; + +/** + * 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 Render works by automatically managing webGLBatchs. + * 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 :) + * + * @class + * @memberof PIXI + * @extends PIXI.AbstractRenderer + */ +export default class Renderer extends AbstractRenderer +{ + // eslint-disable-next-line valid-jsdoc + /** + * + * @param {object} [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.transparent=false] - If the render view is transparent, default false + * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false + * @param {boolean} [options.antialias=false] - sets antialias. If not available natively then FXAA + * antialiasing is used + * @param {boolean} [options.forceFXAA=false] - forces FXAA antialiasing to be used over native. + * FXAA is faster, but may not always look as great + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. + * The resolution of the renderer retina would be 2. + * @param {boolean} [options.clearBeforeRender=true] - This sets if the renderer will clear + * the canvas or not before the new render pass. If you wish to set this to false, you *must* set + * preserveDrawingBuffer to `true`. + * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, + * enable this if you need to call toDataUrl on the webgl context. + * @param {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when + * rendering, stopping pixel interpolation. + * @param {number} [options.backgroundColor=0x000000] - The background color of the rendered area + * (shown if not transparent). + * @param {boolean} [options.legacy=false] - If true PixiJS will aim to ensure compatibility + * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. + * @param {string} [options.powerPreference] - Parameter passed to webgl context, set to "high-performance" + * for devices with dual graphics card + */ + constructor(options, arg2, arg3) + { + super('WebGL', options, arg2, arg3); + + /** + * The type of this renderer as a standardised const + * + * @member {number} + * @see PIXI.RENDERER_TYPE + */ + this.type = RENDERER_TYPE.WEBGL; + + // this will be set by the contextSystem (this.context) + this.gl = null; + this.CONTEXT_UID = 0; + this.legacy = !!options.legacy; + + // TODO legacy! + + // runners! + this.runners = { + destroy: new Runner('destroy'), + contextChange: new Runner('contextChange', 1), + reset: new Runner('reset'), + update: new Runner('update'), + postrender: new Runner('postrender'), + prerender: new Runner('prerender'), + resize: new Runner('resize', 2), + }; + + 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(GeometrySystem, 'geometry') + .addSystem(FramebufferSystem, 'framebuffer') + .addSystem(StencilSystem, 'stencil') + .addSystem(ProjectionSystem, 'projection') + .addSystem(TextureGCSystem, 'textureGC') + .addSystem(FilterSystem, 'filter') + .addSystem(RenderTextureSystem, 'renderTexture') + .addSystem(BatchSystem, 'batch'); + + this.initPlugins(Renderer.__plugins); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ + if (options.context) + { + this.context.initFromContext(options.context); + } + else + { + this.context.initFromOptions({ + alpha: this.transparent, + antialias: options.antialias, + premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', + stencil: true, + 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); + } + + /** + * Add a new system to the renderer. + * @param {class} ClassRef - Class reference + * @param {string} [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. + * @return {PIXI.Renderer} Return instance of renderer + */ + addSystem(ClassRef, name) + { + if (!name) + { + name = ClassRef.name; + } + + // TODO - read name from class.name.. + + /* + if(name.includes('System')) + { + name = name.replace('System', ''); + name = name.charAt(0).toLowerCase() + name.slice(1); + } + */ + + const system = new ClassRef(this); + + if (this[name]) + { + throw new Error(`Whoops! ${name} is already a manger`); + } + + this[name] = system; + + for (const i in this.runners) + { + this.runners[i].add(system); + } + + return this; + + /** + * 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. + */ + } + + /** + * Renders the object to its webGL view + * + * @param {PIXI.DisplayObject} displayObject - the object to be rendered + * @param {PIXI.RenderTexture} renderTexture - The render texture to render to. + * @param {boolean} [clear] - Should the canvas be cleared before the new render + * @param {PIXI.Transform} [transform] - A transform to apply to the render texture before rendering. + * @param {boolean} [skipUpdateTransform] - Should we skip the update transform pass? + */ + render(displayObject, renderTexture, clear, transform, skipUpdateTransform) + { + // can be handy to know! + this.renderingToScreen = !renderTexture; + + this.runners.prerender.run(); + this.emit('prerender'); + + // 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.parent; + + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = 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.run(); + + this.emit('postrender'); + } + + /** + * Resizes the webGL view to the specified width and height. + * + * @param {number} screenWidth - the new width of the screen + * @param {number} screenHeight - the new height of the screen + */ + resize(screenWidth, screenHeight) + { + AbstractRenderer.prototype.resize.call(this, screenWidth, screenHeight); + + this.runners.resize.run(screenWidth, screenHeight); + } + + /** + * Resets the WebGL state so you can render things however you fancy! + * + * @return {PIXI.Renderer} Returns itself. + */ + reset() + { + this.runners.reset.run(); + + return this; + } + + clear() + { + this.framebuffer.bind(); + this.framebuffer.clear(); + } + + /** + * Removes everything from the renderer (event listeners, spritebatch, etc...) + * + * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. + * See: https://github.com/pixijs/pixi.js/issues/2233 + */ + destroy(removeView) + { + this.runners.destroy.run(); + + // call base destroy + super.destroy(removeView); + + // TODO nullify all the managers.. + this.gl = null; + } + + /** + * 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 + * builds and excluding plugins. + * @name PIXI.Renderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. + */ + + /** + * Adds a plugin to the renderer. + * + * @method + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + static registerPlugin(pluginName, ctor) + { + Renderer.__plugins = Renderer.__plugins || {}; + Renderer.__plugins[pluginName] = ctor; + } +} diff --git a/packages/core/src/System.js b/packages/core/src/System.js new file mode 100644 index 0000000..05a10c8 --- /dev/null +++ b/packages/core/src/System.js @@ -0,0 +1,42 @@ +/** + * System is a base class used for extending systems used by the {@link PIXI.Renderer} + * @see PIXI.Renderer#addSystem + * @class + * @memberof PIXI + */ +export default class System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + /** + * The renderer this manager works for. + * + * @member {PIXI.Renderer} + */ + this.renderer = renderer; + + this.renderer.runners.contextChange.add(this); + } + + /** + * Generic method called when there is a WebGL context change. + * + */ + contextChange() + { + // do some codes init! + } + + /** + * Generic destroy methods to be overridden by the subclass + * + */ + destroy() + { + this.renderer.runners.contextChange.remove(this); + this.renderer = null; + } +} diff --git a/packages/core/src/batch/BatchSystem.js b/packages/core/src/batch/BatchSystem.js new file mode 100644 index 0000000..b04c249 --- /dev/null +++ b/packages/core/src/batch/BatchSystem.js @@ -0,0 +1,68 @@ +import System from '../System'; +import ObjectRenderer from './ObjectRenderer'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class BatchSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + super(renderer); + + /** + * An empty renderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.emptyRenderer = new ObjectRenderer(renderer); + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.currentRenderer = this.emptyRenderer; + } + + /** + * Changes the current renderer to the one given in parameter + * + * @param {PIXI.ObjectRenderer} objectRenderer - The object renderer to use. + */ + setObjectRenderer(objectRenderer) + { + if (this.currentRenderer === objectRenderer) + { + return; + } + + this.currentRenderer.stop(); + this.currentRenderer = objectRenderer; + + this.renderer.state.setState(objectRenderer.state); + + this.currentRenderer.start(); + } + + /** + * This should be called if you wish to do some custom rendering + * It will basically render anything that may be batched up such as sprites + * + */ + flush() + { + this.setObjectRenderer(this.emptyRenderer); + } + + reset() + { + this.setObjectRenderer(this.emptyRenderer); + } +} diff --git a/packages/core/src/batch/ObjectRenderer.js b/packages/core/src/batch/ObjectRenderer.js new file mode 100644 index 0000000..0c851a9 --- /dev/null +++ b/packages/core/src/batch/ObjectRenderer.js @@ -0,0 +1,48 @@ +import System from '../System'; + +/** + * Base for a common object renderer that can be used as a system renderer plugin. + * + * @class + * @extends PIXI.System + * @memberof PIXI + */ +export default class ObjectRenderer extends System +{ + /** + * Starts the renderer and sets the shader + * + */ + start() + { + // set the shader.. + } + + /** + * Stops the renderer + * + */ + stop() + { + this.flush(); + } + + /** + * Stub method for rendering content and emptying the current batch. + * + */ + flush() + { + // flush! + } + + /** + * Renders an object + * + * @param {PIXI.DisplayObject} object - The object to render. + */ + render(object) // eslint-disable-line no-unused-vars + { + // render the object + } +} diff --git a/packages/core/src/context/ContextSystem.js b/packages/core/src/context/ContextSystem.js new file mode 100644 index 0000000..301aab4 --- /dev/null +++ b/packages/core/src/context/ContextSystem.js @@ -0,0 +1,186 @@ +import System from '../System'; +import { settings } from '@pixi/settings'; + +let CONTEXT_UID = 0; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class ContextSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.webGLVersion = 1; + + this.handleContextLost = this.handleContextLost.bind(this); + this.handleContextRestored = this.handleContextRestored.bind(this); + + this.extensions = {}; + + renderer.view.addEventListener('webglcontextlost', this.handleContextLost, false); + renderer.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + } + + get isLost() + { + return (!this.gl || this.gl.isContextLost()); + } + + contextChange(gl) + { + this.gl = gl; + + // restore a context if it was previously lost + if (gl.isContextLost() && gl.getExtension('WEBGL_lose_context')) + { + gl.getExtension('WEBGL_lose_context').restoreContext(); + } + } + + initFromContext(gl) + { + this.gl = gl; + this.validateContext(gl); + this.renderer.gl = gl; + this.renderer.CONTEXT_UID = CONTEXT_UID++; + this.renderer.runners.contextChange.run(gl); + } + + initFromOptions(options) + { + const gl = this.createContext(this.renderer.view, options); + + this.initFromContext(gl); + } + + /** + * Helper class to create a webGL Context + * + * @class + * @memberof PIXI.glCore + * @param canvas {HTMLCanvasElement} the canvas element that we will get the context from + * @param options {Object} An options object that gets passed in to the canvas element containing the context attributes, + * see https://developer.mozilla.org/en/docs/Web/API/HTMLCanvasElement/getContext for the options available + * @return {WebGLRenderingContext} the WebGL context + */ + createContext(canvas, options) + { + let gl; + + if (settings.PREFER_WEBGL_2) + { + gl = canvas.getContext('webgl2', options); + } + + if (gl) + { + this.webGLVersion = 2; + } + else + { + this.webGLVersion = 1; + + gl = canvas.getContext('webgl', options) + || canvas.getContext('experimental-webgl', options); + + if (!gl) + { + // fail, not able to get a context + throw new Error('This browser does not support webGL. Try using the canvas renderer'); + } + } + + this.gl = gl; + + this.getExtensions(); + + return gl; + } + + getExtensions() + { + // time to set up default etensions that pixi uses.. + const gl = this.gl; + const extensions = this.extensions; + + if (this.webGLVersion === 1) + { + extensions.drawBuffers = gl.getExtension('WEBGL_draw_buffers'); + extensions.depthTexture = gl.getExtension('WEBKIT_WEBGL_depth_texture'); + extensions.floatTexture = gl.getExtension('OES_texture_float'); + extensions.loseContext = gl.getExtension('WEBGL_lose_context'); + + extensions.vertexArrayObject = gl.getExtension('OES_vertex_array_object') + || gl.getExtension('MOZ_OES_vertex_array_object') + || gl.getExtension('WEBKIT_OES_vertex_array_object'); + } + + // we don't use any specific WebGL 2 ones yet! + } + + /** + * Handles a lost webgl context + * + * @private + * @param {WebGLContextEvent} event - The context lost event. + */ + handleContextLost(event) + { + event.preventDefault(); + } + + /** + * Handles a restored webgl context + * + * @private + */ + handleContextRestored() + { + this.renderer.runners.contextChange.run(this.gl); + } + + destroy() + { + const view = this.renderer.view; + + // remove listeners + view.removeEventListener('webglcontextlost', this.handleContextLost); + view.removeEventListener('webglcontextrestored', this.handleContextRestored); + + this.gl.useProgram(null); + + if (this.extensions.loseContext) + { + this.extensions.loseContext.loseContext(); + } + } + + postrender() + { + this.gl.flush(); + } + + validateContext(gl) + { + const attributes = gl.getContextAttributes(); + + // this is going to be fairly simple for now.. but at least we have room to grow! + if (!attributes.stencil) + { + /* eslint-disable max-len */ + + /* eslint-disable no-console */ + console.warn('Provided WebGL context does not have a stencil buffer, masks may not render correctly'); + /* eslint-enable no-console */ + + /* eslint-enable max-len */ + } + } +} diff --git a/packages/core/src/filters/Filter.js b/packages/core/src/filters/Filter.js new file mode 100644 index 0000000..9d8cad8 --- /dev/null +++ b/packages/core/src/filters/Filter.js @@ -0,0 +1,140 @@ +import Shader from '../shader/Shader'; +import Program from '../shader/Program'; +import State from '../state/State'; +import { settings } from '@pixi/settings'; +// import extractUniformsFromSrc from './extractUniformsFromSrc'; +import defaultVertex from './defaultFilter.vert'; +import defaultFragment from './defaultFilter.frag'; + +// let math = require('../../math'); +/** + * @class + * @memberof PIXI + * @extends PIXI.Shader + */ +export default class Filter extends Shader +{ + /** + * @param {string} [vertexSrc] - The source of the vertex shader. + * @param {string} [fragmentSrc] - The source of the fragment shader. + * @param {object} [uniforms] - Custom uniforms to use to augment the built-in ones. + */ + constructor(vertexSrc, fragmentSrc, uniforms) + { + const program = Program.from(vertexSrc, fragmentSrc); + + super(program, uniforms); + + /** + * The padding of the filter. Some filters require extra space to breath such as a blur. + * Increasing this will add extra width and height to the bounds of the object that the + * filter is applied to. + * + * @member {number} + */ + this.padding = 0; + + /** + * The resolution of the filter. Setting this to be lower will lower the quality but + * increase the performance of the filter. + * + * @member {number} + */ + this.resolution = settings.RESOLUTION; + + /** + * If enabled is true the filter is applied, if false it will not. + * + * @member {boolean} + */ + this.enabled = true; + + /** + * If enabled, PixiJS will fit the filter area into boundaries for better performance. + * Switch it off if it does not work for specific shader. + * + * @member {boolean} + */ + this.autoFit = true; + + /** + * Legacy filters use position and uvs from attributes + * @member {boolean} + * @readonly + */ + this.legacy = !!this.program.attributeData.aTextureCoord; + + /** + * the webGL state the filter requires to render + * @member {PIXI.State} + */ + this.state = new State(); + } + + /** + * Applies the filter + * + * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + * @param {boolean} clear - Should the output be cleared before rendering to it + * @param {object} [currentState] - It's current state of filter. + * There are some useful properties in the currentState : + * target, filters, sourceFrame, destinationFrame, renderTarget, resolution + */ + apply(filterManager, input, output, clear, currentState, derp) // eslint-disable-line no-unused-vars + { + // do as you please! + + filterManager.applyFilter(this, input, output, clear, currentState, derp); + + // or just do a regular render.. + } + + /** + * Sets the blendmode of the filter + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL + */ + get blendMode() + { + return this.state.blendMode; + } + + set blendMode(value) // eslint-disable-line require-jsdoc + { + this.state.blendMode = value; + } + + /** + * The default vertex shader source + * + * @static + * @constant + */ + static get defaultVertexSrc() + { + return defaultVertex; + } + + /** + * The default fragment shader source + * + * @static + * @constant + */ + static get defaultFragmentSrc() + { + return defaultFragment; + } +} + +/** + * Used for caching shader IDs + * + * @static + * @private + */ +Filter.SOURCE_KEY_MAP = {}; + diff --git a/packages/core/src/filters/FilterSystem.js b/packages/core/src/filters/FilterSystem.js new file mode 100644 index 0000000..3174318 --- /dev/null +++ b/packages/core/src/filters/FilterSystem.js @@ -0,0 +1,431 @@ +import System from '../System'; + +import RenderTexture from '../renderTexture/RenderTexture'; +import Quad from '../utils/Quad'; +import QuadUv from '../utils/QuadUv'; +import { Rectangle } from '@pixi/math'; +import * as filterTransforms from './filterTransforms'; +import bitTwiddle from 'bit-twiddle'; +import UniformGroup from '../shader/UniformGroup'; +import { DRAW_MODES } from '@pixi/constants'; + +// +/** + * @ignore + * @class + */ +class FilterState +{ + /** + * + */ + constructor() + { + this.renderTexture = null; + this.sourceFrame = new Rectangle(); + this.destinationFrame = new Rectangle(); + this.filters = []; + this.target = null; + this.legacy = false; + this.resolution = 1; + } +} + +/** + * @class + * @memberof PIXI.systems + * @extends PIXI.System + */ +export default class FilterSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + /** + * stores a bunch of PO2 textures used for filtering + * @type {Object} + */ + this.texturePool = {}; + + /** + * a pool for storing filter states, save us creating new ones each tick + * @type {Array} + */ + this.statePool = []; + + /** + * A very simple geometry used when drawing a filter effect to the screen + * @type {Quad} + */ + this.quad = new Quad(); + + this.quadUv = new QuadUv(); + + /** + * Temporary rect for maths + * @type {PIXI.Rectangle} + */ + this.tempRect = new Rectangle(); + + this.activeState = {}; + + /** + * this uniform group is attached to filter uniforms when used + * @type {UniformGroup} + */ + this.globalUniforms = new UniformGroup({ + sourceFrame: this.tempRect, + destinationFrame: this.tempRect, + + // legacy variables + filterArea: new Float32Array(4), + filterClamp: new Float32Array(4), + }, true); + } + + /** + * Adds a new filter to the System. + * + * @param {PIXI.DisplayObject} target - The target of the filter to render. + * @param {PIXI.Filter[]} filters - The filters to apply. + */ + push(target, filters) + { + const renderer = this.renderer; + const filterStack = this.renderer.renderTexture.defaultFilterStack; + const state = this.statePool.pop() || new FilterState(); + + let resolution = filters[0].resolution; + let padding = filters[0].padding; + let autoFit = filters[0].autoFit; + let legacy = filters[0].legacy; + + for (let i = 1; i < filters.length; i++) + { + const filter = filters[i]; + + // lets use the lowest resolution.. + resolution = Math.min(resolution, filter.resolution); + // and the largest amount of padding! + padding = Math.max(padding, filter.padding); + // only auto fit if all filters are autofit + autoFit = autoFit || filter.autoFit; + + legacy = legacy || filter.legacy; + } + + filterStack.push(state); + + state.resolution = resolution; + + state.legacy = legacy; + + // round to whole number based on resolution + // TODO move that to the shader too? + state.sourceFrame = target.filterArea || target.getBounds(true); + + state.sourceFrame.pad(padding); + + if (autoFit) + { + state.sourceFrame.fit(this.renderer.renderTexture.destinationFrame); + } + + state.sourceFrame.round(resolution); + + state.renderTexture = this.getPotFilterTexture(state.sourceFrame.width, state.sourceFrame.height, resolution); + state.filters = filters; + + state.destinationFrame.width = state.renderTexture.width; + state.destinationFrame.height = state.renderTexture.height; + + state.renderTexture.filterFrame = state.sourceFrame; + renderer.renderTexture.bind(state.renderTexture, state.sourceFrame);// /, state.destinationFrame); + renderer.renderTexture.clear(); + } + + /** + * Pops off the filter and applies it. + * + */ + pop() + { + const renderer = this.renderer; + const filterStack = renderer.renderTexture.defaultFilterStack; + const state = filterStack.pop(); + const filters = state.filters; + + this.activeState = state; + + const globalUniforms = this.globalUniforms.uniforms; + + globalUniforms.sourceFrame = state.sourceFrame; + globalUniforms.destinationFrame = state.destinationFrame; + globalUniforms.resolution = state.resolution; + + // only update the rect if its legacy.. + if (state.legacy) + { + const filterArea = globalUniforms.filterArea; + const filterClamp = globalUniforms.filterClamp; + + filterArea[0] = state.destinationFrame.width; + filterArea[1] = state.destinationFrame.height; + filterArea[2] = state.sourceFrame.x; + filterArea[3] = state.sourceFrame.y; + + filterClamp[0] = 0.5 / state.resolution / state.destinationFrame.width; + filterClamp[1] = 0.5 / state.resolution / state.destinationFrame.height; + filterClamp[2] = (state.sourceFrame.width - 0.5) / state.resolution / state.destinationFrame.width; + filterClamp[3] = (state.sourceFrame.height - 0.5) / state.resolution / state.destinationFrame.height; + } + + this.globalUniforms.update(); + + const lastState = filterStack[filterStack.length - 1]; + + if (filters.length === 1) + { + filters[0].apply(this, state.renderTexture, lastState.renderTexture, false, state); + + this.returnFilterTexture(state.renderTexture); + } + else + { + let flip = state.renderTexture; + let flop = this.getPotFilterTexture( + flip.width, + flip.height, + state.resolution + ); + + flop.filterFrame = flip.filterFrame; + + let i = 0; + + for (i = 0; i < filters.length - 1; ++i) + { + filters[i].apply(this, flip, flop, true, state); + + const t = flip; + + flip = flop; + flop = t; + } + + filters[i].apply(this, flip, lastState.renderTexture, false, state); + + this.returnFilterTexture(flip); + this.returnFilterTexture(flop); + } + + this.statePool.push(state); + } + + /** + * Draws a filter. + * + * @param {PIXI.Filter} filter - The filter to draw. + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + * @param {boolean} clear - Should the output be cleared before rendering to it + */ + applyFilter(filter, input, output, clear) + { + const renderer = this.renderer; + + renderer.renderTexture.bind(output, output ? output.filterFrame : null); + + if (clear) + { + // gl.disable(gl.SCISSOR_TEST); + renderer.renderTexture.clear(); + // gl.enable(gl.SCISSOR_TEST); + } + + // set the uniforms.. + filter.uniforms.uSampler = input; + filter.uniforms.filterGlobals = this.globalUniforms; + + // TODO make it so that the order of this does not matter.. + // because it does at the moment cos of global uniforms. + // they need to get resynced + + renderer.state.setState(filter.state); + renderer.shader.bind(filter); + + if (filter.legacy) + { + this.quadUv.map(input._frame, input.filterFrame); + + renderer.geometry.bind(this.quadUv); + renderer.geometry.draw(DRAW_MODES.TRIANGLES); + } + else + { + renderer.geometry.bind(this.quad); + renderer.geometry.draw(DRAW_MODES.TRIANGLE_STRIP); + } + } + + /** + * Calculates the mapped matrix. + * + * TODO playing around here.. this is temporary - (will end up in the shader) + * this returns a matrix that will normalise map filter cords in the filter to screen space + * + * @param {PIXI.Matrix} outputMatrix - the matrix to output to. + * @return {PIXI.Matrix} The mapped matrix. + */ + calculateScreenSpaceMatrix(outputMatrix) + { + const currentState = this.activeState; + + return filterTransforms.calculateScreenSpaceMatrix( + outputMatrix, + currentState.sourceFrame, + currentState.destinationFrame + ); + } + + /** + * This will map the filter coord so that a texture can be used based on the transform of a sprite + * + * @param {PIXI.Matrix} outputMatrix - The matrix to output to. + * @param {PIXI.Sprite} sprite - The sprite to map to. + * @return {PIXI.Matrix} The mapped matrix. + */ + calculateSpriteMatrix(outputMatrix, sprite) + { + const currentState = this.activeState; + + return filterTransforms.calculateSpriteMatrix( + outputMatrix, + currentState.sourceFrame, + currentState.destinationFrame, + sprite + ); + } + + /** + * Destroys this Filter System. + * + * @param {boolean} [contextLost=false] context was lost, do not free shaders + * + */ + destroy(contextLost = false) + { + if (!contextLost) + { + this.emptyPool(); + } + else + { + this.texturePool = {}; + } + } + + /** + * Gets a Power-of-Two render texture. + * + * TODO move to a seperate class could be on renderer? + * also - could cause issue with multiple contexts? + * + * @private + * @param {WebGLRenderingContext} gl - The webgl rendering context + * @param {number} minWidth - The minimum width of the render target. + * @param {number} minHeight - The minimum height of the render target. + * @param {number} resolution - The resolution of the render target. + * @return {PIXI.RenderTarget} The new render target. + */ + getPotFilterTexture(minWidth, minHeight, resolution) + { + minWidth = bitTwiddle.nextPow2(minWidth); + minHeight = bitTwiddle.nextPow2(minHeight); + resolution = resolution || 1; + + const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + + if (!this.texturePool[key]) + { + this.texturePool[key] = []; + } + + let renderTexture = this.texturePool[key].pop(); + + if (!renderTexture) + { + // temporary bypass cache.. + // internally - this will cause a texture to be bound.. + renderTexture = RenderTexture.create({ + width: minWidth, + height: minHeight, + resolution, + }); + } + + return renderTexture; + } + + /** + * Gets extra render texture to use inside current filter + * + * @param {number} resolution resolution of the renderTexture + * @returns {PIXI.RenderTexture} + */ + getFilterTexture(resolution) + { + const rt = this.activeState.renderTexture; + + const filterTexture = this.getPotFilterTexture(rt.width, rt.height, resolution || rt.baseTexture.resolution); + + filterTexture.filterFrame = rt.filterFrame; + + return filterTexture; + } + + /** + * Frees a render texture back into the pool. + * + * @param {PIXI.RenderTarget} renderTexture - The renderTarget to free + */ + returnFilterTexture(renderTexture) + { + renderTexture.filterFrame = null; + + const base = renderTexture.baseTexture; + + const minWidth = base.width; + const minHeight = base.height; + + const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + + this.texturePool[key].push(renderTexture); + } + + /** + * Empties the texture pool. + * + */ + emptyPool() + { + for (const i in this.texturePool) + { + const textures = this.texturePool[i]; + + if (textures) + { + for (let j = 0; j < textures.length; j++) + { + textures[j].destroy(true); + } + } + } + + this.texturePool = {}; + } +} diff --git a/packages/core/src/filters/defaultFilter.frag b/packages/core/src/filters/defaultFilter.frag new file mode 100644 index 0000000..a14c1be --- /dev/null +++ b/packages/core/src/filters/defaultFilter.frag @@ -0,0 +1,22 @@ +varying vec2 vTextureCoord; +varying vec2 vFilterCoord; + +uniform sampler2D uSampler; +uniform sampler2D filterSampler; + +void main(void){ + vec4 masky = texture2D(filterSampler, vFilterCoord); + vec4 sample = texture2D(uSampler, vTextureCoord); + vec4 color; + if(mod(vFilterCoord.x, 1.0) > 0.5) + { + color = vec4(1.0, 0.0, 0.0, 1.0); + } + else + { + color = vec4(0.0, 1.0, 0.0, 1.0); + } + // gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0); + gl_FragColor = mix(sample, masky, 0.5); + gl_FragColor *= sample.a; +} diff --git a/packages/core/src/filters/defaultFilter.vert b/packages/core/src/filters/defaultFilter.vert new file mode 100644 index 0000000..e1febca --- /dev/null +++ b/packages/core/src/filters/defaultFilter.vert @@ -0,0 +1,14 @@ +attribute vec2 aVertexPosition; +attribute vec2 aTextureCoord; + +uniform mat3 projectionMatrix; +uniform mat3 filterMatrix; + +varying vec2 vTextureCoord; +varying vec2 vFilterCoord; + +void main(void){ + gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); + vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy; + vTextureCoord = aTextureCoord ; +} diff --git a/packages/core/src/filters/filterTransforms.js b/packages/core/src/filters/filterTransforms.js new file mode 100644 index 0000000..87a3cd6 --- /dev/null +++ b/packages/core/src/filters/filterTransforms.js @@ -0,0 +1,50 @@ +import { Matrix } from '@pixi/math'; + +/** + * Calculates the mapped matrix + * @param filterArea {Rectangle} The filter area + * @param sprite {Sprite} the target sprite + * @param outputMatrix {Matrix} @alvin + * @private + */ +// this returns a matrix that will normalise map filter cords in the filter to screen space +export function calculateScreenSpaceMatrix(outputMatrix, filterArea, textureSize) +{ + // TODO unwrap? + const mappedMatrix = outputMatrix.identity(); + + mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); + + mappedMatrix.scale(textureSize.width, textureSize.height); + + return mappedMatrix; +} + +export function calculateNormalizedScreenSpaceMatrix(outputMatrix, filterArea, textureSize) +{ + const mappedMatrix = outputMatrix.identity(); + + mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); + + const translateScaleX = (textureSize.width / filterArea.width); + const translateScaleY = (textureSize.height / filterArea.height); + + mappedMatrix.scale(translateScaleX, translateScaleY); + + return mappedMatrix; +} + +// this will map the filter coord so that a texture can be used based on the transform of a sprite +export function calculateSpriteMatrix(outputMatrix, filterArea, textureSize, sprite) +{ + const orig = sprite._texture.orig; + const mappedMatrix = outputMatrix.set(textureSize.width, 0, 0, textureSize.height, filterArea.x, filterArea.y); + const worldTransform = sprite.worldTransform.copyTo(Matrix.TEMP_MATRIX); + + worldTransform.invert(); + mappedMatrix.prepend(worldTransform); + mappedMatrix.scale(1.0 / orig.width, 1.0 / orig.height); + mappedMatrix.translate(sprite.anchor.x, sprite.anchor.y); + + return mappedMatrix; +} diff --git a/packages/core/src/filters/spriteMask/SpriteMaskFilter.js b/packages/core/src/filters/spriteMask/SpriteMaskFilter.js new file mode 100644 index 0000000..91697b7 --- /dev/null +++ b/packages/core/src/filters/spriteMask/SpriteMaskFilter.js @@ -0,0 +1,63 @@ +import Filter from '../Filter'; +import { Matrix } from '@pixi/math'; +import vertex from './spriteMaskFilter.vert'; +import fragment from './spriteMaskFilter.frag'; +import { default as TextureMatrix } from '../../textures/TextureMatrix'; + +/** + * The SpriteMaskFilter class + * + * @class + * @extends PIXI.Filter + * @memberof PIXI + */ +export default class SpriteMaskFilter extends Filter +{ + /** + * @param {PIXI.Sprite} sprite - the target sprite + */ + constructor(sprite) + { + const maskMatrix = new Matrix(); + + super(vertex, fragment); + + sprite.renderable = false; + + this.maskSprite = sprite; + this.maskMatrix = maskMatrix; + } + + /** + * Applies the filter + * + * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + */ + apply(filterManager, input, output) + { + const maskSprite = this.maskSprite; + const tex = this.maskSprite.texture; + + if (!tex.valid) + { + return; + } + if (!tex.transform) + { + // margin = 0.0, let it bleed a bit, shader code becomes easier + // assuming that atlas textures were made with 1-pixel padding + tex.transform = new TextureMatrix(tex, 0.0); + } + tex.transform.update(); + + this.uniforms.mask = tex; + this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite) + .prepend(tex.transform.mapCoord); + this.uniforms.alpha = maskSprite.worldAlpha; + this.uniforms.maskClamp = tex.transform.uClampFrame; + + filterManager.applyFilter(this, input, output); + } +} diff --git a/packages/core/src/filters/spriteMask/spriteMaskFilter.frag b/packages/core/src/filters/spriteMask/spriteMaskFilter.frag new file mode 100644 index 0000000..0e4aef8 --- /dev/null +++ b/packages/core/src/filters/spriteMask/spriteMaskFilter.frag @@ -0,0 +1,23 @@ +varying vec2 vMaskCoord; +varying vec2 vTextureCoord; + +uniform sampler2D uSampler; +uniform sampler2D mask; +uniform float alpha; +uniform vec4 maskClamp; + +void main(void) +{ + float clip = step(3.5, + step(maskClamp.x, vMaskCoord.x) + + step(maskClamp.y, vMaskCoord.y) + + step(vMaskCoord.x, maskClamp.z) + + step(vMaskCoord.y, maskClamp.w)); + + vec4 original = texture2D(uSampler, vTextureCoord); + vec4 masky = texture2D(mask, vMaskCoord); + + original *= (masky.r * masky.a * alpha * clip); + + gl_FragColor = original; +} diff --git a/packages/core/src/filters/spriteMask/spriteMaskFilter.vert b/packages/core/src/filters/spriteMask/spriteMaskFilter.vert new file mode 100644 index 0000000..d23a30c --- /dev/null +++ b/packages/core/src/filters/spriteMask/spriteMaskFilter.vert @@ -0,0 +1,16 @@ +attribute vec2 aVertexPosition; +attribute vec2 aTextureCoord; + +uniform mat3 projectionMatrix; +uniform mat3 otherMatrix; + +varying vec2 vMaskCoord; +varying vec2 vTextureCoord; + +void main(void) +{ + gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); + + vTextureCoord = aTextureCoord; + vMaskCoord = ( otherMatrix * vec3( aTextureCoord, 1.0) ).xy; +} diff --git a/packages/core/src/framebuffer/FrameBuffer.js b/packages/core/src/framebuffer/FrameBuffer.js new file mode 100644 index 0000000..84e9fcc --- /dev/null +++ b/packages/core/src/framebuffer/FrameBuffer.js @@ -0,0 +1,106 @@ +import Texture from '../textures/BaseTexture'; +import { FORMATS, TYPES } from '@pixi/constants'; + +/** + * Frame buffer + * @class + * @memberof PIXI + */ +export default class FrameBuffer +{ + constructor(width, height) + { + this.width = width || 100; + this.height = height || 100; + + this.stencil = false; + this.depth = false; + + this.dirtyId = 0; + this.dirtyFormat = 0; + this.dirtySize = 0; + + this.depthTexture = null; + this.colorTextures = []; + + this.glFrameBuffers = {}; + } + + get colorTexture() + { + return this.colorTextures[0]; + } + + addColorTexture(index, texture) + { + // TODO add some validation to the texture - same width / height etc? + this.colorTextures[index || 0] = texture || new Texture(null, { scaleMode: 0, + resolution: 1, + mipmap: false, + width: this.width, + height: this.height });// || new Texture(); + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + addDepthTexture(texture) + { + /* eslint-disable max-len */ + this.depthTexture = texture || new Texture(null, { scaleMode: 0, + resolution: 1, + width: this.width, + height: this.height, + mipmap: false, + format: FORMATS.DEPTH_COMPONENT, + type: TYPES.UNSIGNED_SHORT });// UNSIGNED_SHORT; + /* eslint-disable max-len */ + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + enableDepth() + { + this.depth = true; + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + enableStencil() + { + this.stencil = true; + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + resize(width, height) + { + if (width === this.width && height === this.height) return; + + this.width = width; + this.height = height; + + this.dirtyId++; + this.dirtySize++; + + for (let i = 0; i < this.colorTextures.length; i++) + { + this.colorTextures[i].setSize(width, height); + } + + if (this.depthTexture) + { + this.depthTexture.setSize(width, height); + } + } +} diff --git a/packages/core/src/framebuffer/FramebufferSystem.js b/packages/core/src/framebuffer/FramebufferSystem.js new file mode 100644 index 0000000..2cc2437 --- /dev/null +++ b/packages/core/src/framebuffer/FramebufferSystem.js @@ -0,0 +1,246 @@ +import System from '../System'; +import { Rectangle } from '@pixi/math'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class FramebufferSystem extends System +{ + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + contextChange() + { + this.gl = this.renderer.gl; + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + this.current = null; + this.viewport = new Rectangle(); + + this.drawBufferExtension = this.renderer.context.extensions.drawBuffers; + } + + bind(framebuffer, frame) + { + const gl = this.gl; + + this.current = framebuffer; + + if (framebuffer) + { + // TODO cacheing layer! + + const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID] || this.initFramebuffer(framebuffer); + + gl.bindFramebuffer(gl.FRAMEBUFFER, fbo.framebuffer); + // makesure all textures are unbound.. + + // now check for updates... + if (fbo.dirtyId !== framebuffer.dirtyId) + { + fbo.dirtyId = framebuffer.dirtyId; + + if (fbo.dirtyFormat !== framebuffer.dirtyFormat) + { + fbo.dirtyFormat = framebuffer.dirtyFormat; + this.updateFramebuffer(framebuffer); + } + else if (fbo.dirtySize !== framebuffer.dirtySize) + { + fbo.dirtySize = framebuffer.dirtySize; + this.resizeFramebuffer(framebuffer); + } + } + + for (let i = 0; i < framebuffer.colorTextures.length; i++) + { + if (framebuffer.colorTextures[i].texturePart) + { + this.renderer.texture.unbind(framebuffer.colorTextures[i].texture); + } + else + { + this.renderer.texture.unbind(framebuffer.colorTextures[i]); + } + } + + if (framebuffer.depthTexture) + { + this.renderer.texture.unbind(framebuffer.depthTexture); + } + + if (frame) + { + this.setViewport(frame.x, frame.y, frame.width, frame.height); + } + else + { + this.setViewport(0, 0, framebuffer.width, framebuffer.height); + } + } + else + { + gl.bindFramebuffer(gl.FRAMEBUFFER, null); + + if (frame) + { + this.setViewport(frame.x, frame.y, frame.width, frame.height); + } + else + { + this.setViewport(0, 0, this.renderer.width, this.renderer.height); + } + } + } + + setViewport(x, y, width, height) + { + const v = this.viewport; + + if (v.width !== width || v.height !== height || v.x !== x || v.y !== y) + { + v.x = x; + v.y = y; + v.width = width; + v.height = height; + + this.gl.viewport(x, y, width, height); + } + } + + get size() + { + if (this.current) + { + // TODO store temp + return { x: 0, y: 0, width: this.current.width, height: this.current.height }; + } + + return { x: 0, y: 0, width: this.renderer.width, height: this.renderer.height }; + } + + clear(r, g, b, a) + { + const gl = this.gl; + + // TODO clear color can be set only one right? + gl.clearColor(r, g, b, a); + gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); + } + + // private functions... + + initFramebuffer(framebuffer) + { + const gl = this.gl; + + // TODO - make this a class? + const fbo = { + framebuffer: gl.createFramebuffer(), + stencil: null, + dirtyId: 0, + dirtyFormat: 0, + dirtySize: 0, + }; + + framebuffer.glFrameBuffers[this.CONTEXT_UID] = fbo; + + return fbo; + } + + resizeFramebuffer(framebuffer) + { + const gl = this.gl; + + if (framebuffer.stencil || framebuffer.depth) + { + gl.bindRenderbuffer(gl.RENDERBUFFER, this.stencil); + gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); + } + } + + updateFramebuffer(framebuffer) + { + const gl = this.gl; + + const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID]; + + // bind the color texture + const colorTextures = framebuffer.colorTextures; + + let count = colorTextures.length; + + if (!this.drawBufferExtension) + { + count = Math.min(count, 1); + } + + const activeTextures = []; + + for (let i = 0; i < count; i++) + { + const texture = framebuffer.colorTextures[i]; + + if (texture.texturePart) + { + this.renderer.texture.bind(texture.texture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.COLOR_ATTACHMENT0 + i, + gl.TEXTURE_CUBE_MAP_NEGATIVE_X + texture.side, + texture.texture._glTextures[this.CONTEXT_UID].texture, + 0); + } + else + { + this.renderer.texture.bind(texture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.COLOR_ATTACHMENT0 + i, + gl.TEXTURE_2D, + texture._glTextures[this.CONTEXT_UID].texture, + 0); + } + + activeTextures.push(gl.COLOR_ATTACHMENT0 + i); + } + + if (this.drawBufferExtension && activeTextures.length > 1) + { + this.drawBufferExtension.drawBuffersWEBGL(activeTextures); + } + + if (framebuffer.depthTexture) + { + const depthTextureExt = this.renderer.context.extensions.depthTexture; + + if (depthTextureExt) + { + const depthTexture = framebuffer.depthTexture; + + this.renderer.texture.bind(depthTexture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.DEPTH_ATTACHMENT, + gl.TEXTURE_2D, + depthTexture._glTextures[this.CONTEXT_UID].texture, + 0); + } + } + + if (framebuffer.stencil || framebuffer.depth) + { + fbo.stencil = gl.createRenderbuffer(); + + gl.bindRenderbuffer(gl.RENDERBUFFER, fbo.stencil); + + // TODO.. this is depth AND stencil? + gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, fbo.stencil); + gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); + // fbo.enableStencil(); + } + } +} diff --git a/packages/core/src/geometry/GLBuffer.js b/packages/core/src/geometry/GLBuffer.js new file mode 100644 index 0000000..0477e8a --- /dev/null +++ b/packages/core/src/geometry/GLBuffer.js @@ -0,0 +1,9 @@ +export default class GLBuffer +{ + constructor(buffer) + { + this.buffer = buffer; + this.updateID = -1; + this.byteLength = -1; + } +} diff --git a/packages/core/src/geometry/GeometrySystem.js b/packages/core/src/geometry/GeometrySystem.js new file mode 100644 index 0000000..959f827 --- /dev/null +++ b/packages/core/src/geometry/GeometrySystem.js @@ -0,0 +1,388 @@ +import System from '../System'; +import GLBuffer from './GLBuffer'; + +const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class GeometrySystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this._activeGeometry = null; + this._activeVao = null; + + this.hasVao = true; + this.hasInstance = true; + } + + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + contextChange() + { + const gl = this.gl = this.renderer.gl; + + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + + // webgl2 + if (!gl.createVertexArray) + { + // webgl 1! + let nativeVaoExtension = this.renderer.context.extensions.vertexArrayObject; + + if (this.renderer.legacy) + { + nativeVaoExtension = null; + } + + if (nativeVaoExtension) + { + gl.createVertexArray = () => + nativeVaoExtension.createVertexArrayOES(); + + gl.bindVertexArray = (vao) => + nativeVaoExtension.bindVertexArrayOES(vao); + + gl.deleteVertexArray = (vao) => + nativeVaoExtension.deleteVertexArrayOES(vao); + } + else + { + this.hasVao = false; + gl.createVertexArray = () => + { + // empty + }; + + gl.bindVertexArray = () => + { + // empty + }; + + gl.deleteVertexArray = () => + { + // empty + }; + } + } + + if (!gl.vertexAttribDivisor) + { + const instanceExt = gl.getExtension('ANGLE_instanced_arrays'); + + if (instanceExt) + { + gl.vertexAttribDivisor = (a, b) => + instanceExt.vertexAttribDivisorANGLE(a, b); + + gl.drawElementsInstanced = (a, b, c, d, e) => + instanceExt.drawElementsInstancedANGLE(a, b, c, d, e); + + gl.drawArraysInstanced = (a, b, c, d) => + instanceExt.drawArraysInstancedANGLE(a, b, c, d); + } + else + { + this.hasInstance = false; + } + } + } + + /** + * Binds geometry so that is can be drawn. Creating a Vao if required + * @private + * @param {PIXI.Geometry} geometry instance of geometry to bind + */ + bind(geometry, shader) + { + shader = shader || this.renderer.shader.shader; + + const gl = this.gl; + + // not sure the best way to address this.. + // currently different shaders require different VAOs for the same geometry + // Still mulling over the best way to solve this one.. + // will likely need to modify the shader attribute locations at run time! + let vaos = geometry.glVertexArrayObjects[this.CONTEXT_UID]; + + if (!vaos) + { + geometry.glVertexArrayObjects[this.CONTEXT_UID] = vaos = {}; + } + + const vao = vaos[shader.program.id] || this.initGeometryVao(geometry, shader.program); + + this._activeGeometry = geometry; + + if (this._activeVao !== vao) + { + this._activeVao = vao; + + if (this.hasVao) + { + gl.bindVertexArray(vao); + } + else + { + this.activateVao(geometry, shader.program); + } + } + + // TODO - optimise later! + // don't need to loop through if nothing changed! + // maybe look to add an 'autoupdate' to geometry? + this.updateBuffers(); + } + + reset() + { + this.unbind(); + } + + updateBuffers() + { + const geometry = this._activeGeometry; + const gl = this.gl; + + for (let i = 0; i < geometry.buffers.length; i++) + { + const buffer = geometry.buffers[i]; + + const glBuffer = buffer._glBuffers[this.CONTEXT_UID]; + + if (buffer._updateID !== glBuffer.updateID) + { + glBuffer.updateID = buffer._updateID; + + // TODO can cache this on buffer! maybe added a getter / setter? + const type = buffer.index ? gl.ELEMENT_ARRAY_BUFFER : gl.ARRAY_BUFFER; + const drawType = buffer.static ? gl.STATIC_DRAW : gl.DYNAMIC_DRAW; + + gl.bindBuffer(type, glBuffer.buffer); + + if (glBuffer.byteLength >= buffer.data.byteLength) + { + // offset is always zero for now! + gl.bufferSubData(type, 0, buffer.data); + } + else + { + gl.bufferData(type, buffer.data, drawType); + } + } + } + } + + checkCompatability(geometry, program) + { + // geometry must have at least all the attributes that the shader requires. + const geometryAttributes = geometry.attributes; + const shaderAttributes = program.attributeData; + + for (const j in shaderAttributes) + { + if (!geometryAttributes[j]) + { + throw new Error(`shader and geometry incompatible, geometry missing the "${j}" attribute`); + } + } + } + + /** + * Creates a Vao with the same structure as the geometry and stores it on the geometry. + * @private + * @param {PIXI.Geometry} geometry instance of geometry to to generate Vao for + */ + initGeometryVao(geometry, program) + { + this.checkCompatability(geometry, program); + + const gl = this.gl; + const CONTEXT_UID = this.CONTEXT_UID; + const buffers = geometry.buffers; + const attributes = geometry.attributes; + + const tempStride = {}; + const tempStart = {}; + + for (const j in buffers) + { + tempStride[j] = 0; + tempStart[j] = 0; + } + + for (const j in attributes) + { + if (!attributes[j].size && program.attributeData[j]) + { + attributes[j].size = program.attributeData[j].size; + } + + tempStride[attributes[j].buffer] += attributes[j].size * byteSizeMap[attributes[j].type]; + } + + for (const j in attributes) + { + const attribute = attributes[j]; + const attribSize = attribute.size; + + if (attribute.stride === undefined) + { + if (tempStride[attribute.buffer] === attribSize * byteSizeMap[attribute.type]) + { + attribute.stride = 0; + } + else + { + attribute.stride = tempStride[attribute.buffer]; + } + } + + if (attribute.start === undefined) + { + attribute.start = tempStart[attribute.buffer]; + + tempStart[attribute.buffer] += attribSize * byteSizeMap[attribute.type]; + } + } + + // first update - and create the buffers! + // only create a gl buffer if it actually gets + for (let i = 0; i < buffers.length; i++) + { + const buffer = buffers[i]; + + if (!buffer._glBuffers[CONTEXT_UID]) + { + buffer._glBuffers[CONTEXT_UID] = new GLBuffer(gl.createBuffer()); + } + } + + // TODO - maybe make this a data object? + // lets wait to see if we need to first! + const vao = gl.createVertexArray(); + + gl.bindVertexArray(vao); + + this.activateVao(geometry, program); + + gl.bindVertexArray(null); + + // add it to the cache! + geometry.glVertexArrayObjects[this.CONTEXT_UID][program.id] = vao; + + return vao; + } + + activateVao(geometry, program) + { + const gl = this.gl; + const CONTEXT_UID = this.CONTEXT_UID; + const buffers = geometry.buffers; + const attributes = geometry.attributes; + + if (geometry.indexBuffer) + { + // first update the index buffer if we have one.. + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, geometry.indexBuffer._glBuffers[CONTEXT_UID].buffer); + } + + let lastBuffer = null; + + // add a new one! + for (const j in attributes) + { + const attribute = attributes[j]; + const buffer = buffers[attribute.buffer]; + const glBuffer = buffer._glBuffers[CONTEXT_UID]; + + if (program.attributeData[j]) + { + if (lastBuffer !== glBuffer) + { + gl.bindBuffer(gl.ARRAY_BUFFER, glBuffer.buffer); + + lastBuffer = glBuffer; + } + + const location = program.attributeData[j].location; + + // TODO introduce state again + // we can optimise this for older devices that have no VAOs + gl.enableVertexAttribArray(location); + + gl.vertexAttribPointer(location, + attribute.size, + attribute.type || gl.FLOAT, + attribute.normalized, + attribute.stride, + attribute.start); + + if (attribute.instance) + { + // TODO calculate instance count based of this... + if (this.hasInstance) + { + gl.vertexAttribDivisor(location, 1); + } + else + { + throw new Error('geometry error, GPU Instancing is not supported on this device'); + } + } + } + } + } + + draw(type, size, start, instanceCount) + { + const gl = this.gl; + const geometry = this._activeGeometry; + + // TODO.. this should not change so maybe cache the function? + + if (geometry.indexBuffer) + { + if (geometry.instanced) + { + /* eslint-disable max-len */ + gl.drawElementsInstanced(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2, instanceCount || 1); + /* eslint-enable max-len */ + } + else + { + gl.drawElements(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2); + } + } + else + if (geometry.instanced) + { + // TODO need a better way to calculate size.. + gl.drawArraysInstanced(type, start, size || geometry.getSize(), instanceCount || 1); + } + else + { + gl.drawArrays(type, start, size || geometry.getSize()); + } + + return this; + } + + unbind() + { + this.gl.bindVertexArray(null); + this._activeVao = null; + this._activeGeometry = null; + } +} diff --git a/packages/core/src/geometry/utils/setVertexAttribArrays.js b/packages/core/src/geometry/utils/setVertexAttribArrays.js new file mode 100644 index 0000000..23b6959 --- /dev/null +++ b/packages/core/src/geometry/utils/setVertexAttribArrays.js @@ -0,0 +1,54 @@ +// var GL_MAP = {}; + +/** + * @param gl {WebGLRenderingContext} The current WebGL context + * @param attribs {*} + * @param state {*} + */ +export default function setVertexAttribArrays(gl, attribs, state) +{ + let i; + + if (state) + { + const tempAttribState = state.tempAttribState; + const attribState = state.attribState; + + for (i = 0; i < tempAttribState.length; i++) + { + tempAttribState[i] = false; + } + + // set the new attribs + for (i = 0; i < attribs.length; i++) + { + tempAttribState[attribs[i].attribute.location] = true; + } + + for (i = 0; i < attribState.length; i++) + { + if (attribState[i] !== tempAttribState[i]) + { + attribState[i] = tempAttribState[i]; + + if (state.attribState[i]) + { + gl.enableVertexAttribArray(i); + } + else + { + gl.disableVertexAttribArray(i); + } + } + } + } + else + { + for (i = 0; i < attribs.length; i++) + { + const attrib = attribs[i]; + + gl.enableVertexAttribArray(attrib.attribute.location); + } + } +} diff --git a/packages/core/src/index.js b/packages/core/src/index.js index 58c456a..fd1f547 100644 --- a/packages/core/src/index.js +++ b/packages/core/src/index.js @@ -1,30 +1,30 @@ import * as resources from './textures/resources'; -import * as systems from './renderers/systems'; +import * as systems from './systems'; -export { resources }; export { systems }; +export { resources }; -export { default as Renderer } from './renderers/Renderer'; -export { default as AbstractRenderer } from './renderers/AbstractRenderer'; -export { default as FrameBuffer } from './textures/FrameBuffer'; +export { default as System } from './System'; +export { default as Renderer } from './Renderer'; +export { default as AbstractRenderer } from './AbstractRenderer'; +export { default as FrameBuffer } from './framebuffer/FrameBuffer'; export { default as CubeTexture } from './textures/CubeTexture'; export { default as BaseTexture } from './textures/BaseTexture'; export { default as Texture } from './textures/Texture'; export { default as TextureMatrix } from './textures/TextureMatrix'; -export { default as RenderTexture } from './textures/RenderTexture'; -export { default as BaseRenderTexture } from './textures/BaseRenderTexture'; +export { default as RenderTexture } from './renderTexture/RenderTexture'; +export { default as BaseRenderTexture } from './renderTexture/BaseRenderTexture'; export { default as TextureUvs } from './textures/TextureUvs'; -export { default as State } from './renderers/State'; -export { default as ObjectRenderer } from './renderers/utils/ObjectRenderer'; -export { default as Quad } from './renderers/utils/Quad'; -export { default as QuadUv } from './renderers/utils/QuadUv'; -export { default as checkMaxIfStatmentsInShader } from './renderers/utils/checkMaxIfStatmentsInShader'; +export { default as State } from './state/State'; +export { default as ObjectRenderer } from './batch/ObjectRenderer'; +export { default as Quad } from './utils/Quad'; +export { default as QuadUv } from './utils/QuadUv'; +export { default as checkMaxIfStatmentsInShader } from './shader/utils/checkMaxIfStatmentsInShader'; export { default as Shader } from './shader/Shader'; export { default as Program } from './shader/Program'; export { default as UniformGroup } from './shader/UniformGroup'; -export { default as SpriteMaskFilter } from './renderers/filters/spriteMask/SpriteMaskFilter'; -export { default as Filter } from './renderers/filters/Filter'; +export { default as SpriteMaskFilter } from './filters/spriteMask/SpriteMaskFilter'; +export { default as Filter } from './filters/Filter'; export { default as Attribute } from './geometry/Attribute'; export { default as Buffer } from './geometry/Buffer'; export { default as Geometry } from './geometry/Geometry'; - diff --git a/packages/core/src/mask/MaskSystem.js b/packages/core/src/mask/MaskSystem.js new file mode 100644 index 0000000..99174c2 --- /dev/null +++ b/packages/core/src/mask/MaskSystem.js @@ -0,0 +1,199 @@ +import System from '../System'; +import AlphaMaskFilter from '../filters/spriteMask/SpriteMaskFilter'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class MaskSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + // TODO - we don't need both! + this.scissor = false; + this.scissorData = null; + this.scissorRenderTarget = null; + + this.enableScissor = false; + + this.alphaMaskPool = []; + this.alphaMaskIndex = 0; + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.DisplayObject} target - Display Object to push the mask to + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + push(target, maskData) + { + // TODO the root check means scissor rect will not + // be used on render textures more info here: + // https://github.com/pixijs/pixi.js/pull/3545 + + if (maskData.texture) + { + this.pushSpriteMask(target, maskData); + } + else if (this.enableScissor + && !this.scissor + && this.renderer._activeRenderTarget.root + && !this.renderer.stencil.stencilMaskStack.length + && maskData.isFastRect()) + { + const matrix = maskData.worldTransform; + + let rot = Math.atan2(matrix.b, matrix.a); + + // use the nearest degree! + rot = Math.round(rot * (180 / Math.PI)); + + if (rot % 90) + { + this.pushStencilMask(maskData); + } + else + { + this.pushScissorMask(target, maskData); + } + } + else + { + this.pushStencilMask(maskData); + } + } + + /** + * Removes the last mask from the mask stack and doesn't return it. + * + * @param {PIXI.DisplayObject} target - Display Object to pop the mask from + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + pop(target, maskData) + { + if (maskData.texture) + { + this.popSpriteMask(target, maskData); + } + else if (this.enableScissor && !this.renderer.stencil.stencilMaskStack.length) + { + this.popScissorMask(target, maskData); + } + else + { + this.popStencilMask(target, maskData); + } + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.RenderTarget} target - Display Object to push the sprite mask to + * @param {PIXI.Sprite} maskData - Sprite to be used as the mask + */ + pushSpriteMask(target, maskData) + { + let alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; + + if (!alphaMaskFilter) + { + alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; + } + + alphaMaskFilter[0].resolution = this.renderer.resolution; + alphaMaskFilter[0].maskSprite = maskData; + + // TODO - may cause issues! + target.filterArea = maskData.getBounds(true); + + this.renderer.filterSystem.pushFilter(target, alphaMaskFilter); + + this.alphaMaskIndex++; + } + + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popSpriteMask() + { + this.renderer.filterSystem.popFilter(); + this.alphaMaskIndex--; + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + pushStencilMask(maskData) + { + this.renderer.batch.flush(); + this.renderer.stencil.pushStencil(maskData); + } + + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popStencilMask() + { + // this.renderer.currentRenderer.stop(); + this.renderer.stencil.popStencil(); + } + + /** + * + * @param {PIXI.DisplayObject} target - Display Object to push the mask to + * @param {PIXI.Graphics} maskData - The masking data. + */ + pushScissorMask(target, maskData) + { + maskData.renderable = true; + + const renderTarget = this.renderer._activeRenderTarget; + + const bounds = maskData.getBounds(); + + bounds.fit(renderTarget.size); + maskData.renderable = false; + + this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); + + const resolution = this.renderer.resolution; + + this.renderer.gl.scissor( + bounds.x * resolution, + (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, + bounds.width * resolution, + bounds.height * resolution + ); + + this.scissorRenderTarget = renderTarget; + this.scissorData = maskData; + this.scissor = true; + } + + /** + * + * + */ + popScissorMask() + { + this.scissorRenderTarget = null; + this.scissorData = null; + this.scissor = false; + + // must be scissor! + const gl = this.renderer.gl; + + gl.disable(gl.SCISSOR_TEST); + } +} diff --git a/packages/core/src/mask/StencilSystem.js b/packages/core/src/mask/StencilSystem.js new file mode 100644 index 0000000..62796a5 --- /dev/null +++ b/packages/core/src/mask/StencilSystem.js @@ -0,0 +1,133 @@ +import System from '../System'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class StencilSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + this.stencilMaskStack = []; + } + + /** + * Changes the mask stack that is used by this System. + * + * @param {PIXI.Graphics[]} stencilMaskStack - The mask stack + */ + setMaskStack(stencilMaskStack) + { + const gl = this.renderer.gl; + + if (stencilMaskStack.length !== this.stencilMaskStack.length) + { + if (stencilMaskStack.length === 0) + { + gl.disable(gl.STENCIL_TEST); + } + else + { + gl.enable(gl.STENCIL_TEST); + } + } + + this.stencilMaskStack = stencilMaskStack; + } + + /** + * Applies the Mask and adds it to the current stencil stack. @alvin + * + * @param {PIXI.Graphics} graphics - The mask + */ + pushStencil(graphics) + { + this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); + + // this.renderer._activeRenderTarget.attachStencilBuffer(); + + const gl = this.renderer.gl; + const prevMaskCount = this.stencilMaskStack.length; + + if (prevMaskCount === 0) + { + gl.enable(gl.STENCIL_TEST); + } + + this.stencilMaskStack.push(graphics); + + // Increment the refference stencil value where the new mask overlaps with the old ones. + gl.colorMask(false, false, false, false); + gl.stencilFunc(gl.EQUAL, prevMaskCount, this._getBitwiseMask()); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.INCR); + this.renderer.plugins.graphics.render(graphics); + + this._useCurrent(); + } + + /** + * Removes the last mask from the stencil stack. @alvin + */ + popStencil() + { + this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); + + const gl = this.renderer.gl; + const graphics = this.stencilMaskStack.pop(); + + if (this.stencilMaskStack.length === 0) + { + // the stack is empty! + gl.disable(gl.STENCIL_TEST); + gl.clear(gl.STENCIL_BUFFER_BIT); + gl.clearStencil(0); + } + else + { + // Decrement the refference stencil value where the popped mask overlaps with the other ones + gl.colorMask(false, false, false, false); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.DECR); + this.renderer.plugins.graphics.render(graphics); + + this._useCurrent(); + } + } + + /** + * Setup renderer to use the current stencil data. + */ + _useCurrent() + { + const gl = this.renderer.gl; + + gl.colorMask(true, true, true, true); + gl.stencilFunc(gl.EQUAL, this.stencilMaskStack.length, this._getBitwiseMask()); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP); + } + + /** + * Fill 1s equal to the number of acitve stencil masks. + * + * @return {number} The bitwise mask. + */ + _getBitwiseMask() + { + return (1 << this.stencilMaskStack.length) - 1; + } + + /** + * Destroys the mask stack. + * + */ + destroy() + { + super.destroy(this); + + this.stencilMaskStack = null; + } +} diff --git a/packages/core/src/projection/ProjectionSystem.js b/packages/core/src/projection/ProjectionSystem.js new file mode 100644 index 0000000..6e63b73 --- /dev/null +++ b/packages/core/src/projection/ProjectionSystem.js @@ -0,0 +1,73 @@ +import System from '../System'; +import { Matrix } from '@pixi/math'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class ProjectionSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.projectionMatrix = new Matrix(); + } + + update(destinationFrame, sourceFrame, resolution, root) + { + this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; + this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; + + this.calculateProjection(this.destinationFrame, this.sourceFrame, resolution, root); + + this.renderer.globalUniforms.uniforms.projectionMatrix = this.projectionMatrix; + this.renderer.globalUniforms.update(); + } + + /** + * Updates the projection matrix based on a projection frame (which is a rectangle) + * + * @param {Rectangle} destinationFrame - The destination frame. + * @param {Rectangle} sourceFrame - The source frame. + */ + calculateProjection(destinationFrame, sourceFrame, resolution, root) + { + const pm = this.projectionMatrix; + + // I don't think we will need this line.. + // pm.identity(); + + if (!root) + { + pm.a = (1 / destinationFrame.width * 2) * resolution; + pm.d = (1 / destinationFrame.height * 2) * resolution; + + pm.tx = -1 - (sourceFrame.x * pm.a); + pm.ty = -1 - (sourceFrame.y * pm.d); + } + else + { + pm.a = (1 / destinationFrame.width * 2) * resolution; + pm.d = (-1 / destinationFrame.height * 2) * resolution; + + pm.tx = -1 - (sourceFrame.x * pm.a); + pm.ty = 1 - (sourceFrame.y * pm.d); + } + } + + /** + * Sets the transform of the active render target to the given matrix + * + * @param {PIXI.Matrix} matrix - The transformation matrix + */ + setTransform()// matrix) + { + // this._activeRenderTarget.transform = matrix; + } +} diff --git a/packages/core/src/renderTexture/BaseRenderTexture.js b/packages/core/src/renderTexture/BaseRenderTexture.js new file mode 100644 index 0000000..88eb83b --- /dev/null +++ b/packages/core/src/renderTexture/BaseRenderTexture.js @@ -0,0 +1,137 @@ +import BaseTexture from '../textures/BaseTexture'; +import FrameBuffer from '../framebuffer/FrameBuffer'; + +/** + * A BaseRenderTexture is a special texture that allows any PixiJS display object to be rendered to it. + * + * __Hint__: All DisplayObjects (i.e. Sprites) that render to a BaseRenderTexture should be preloaded + * otherwise black rectangles will be drawn instead. + * + * A BaseRenderTexture takes a snapshot of any Display Object given to its render method. The position + * and rotation of the given Display Objects is ignored. For example: + * + * ```js + * let renderer = PIXI.autoDetectRenderer(1024, 1024, { view: canvas, ratio: 1 }); + * let baseRenderTexture = new PIXI.BaseRenderTexture(renderer, 800, 600); + * let sprite = PIXI.Sprite.fromImage("spinObj_01.png"); + * + * sprite.position.x = 800/2; + * sprite.position.y = 600/2; + * sprite.anchor.x = 0.5; + * sprite.anchor.y = 0.5; + * + * baseRenderTexture.render(sprite); + * ``` + * + * The Sprite in this case will be rendered using its local transform. To render this sprite at 0,0 + * you can clear the transform + * + * ```js + * + * sprite.setTransform() + * + * let baseRenderTexture = new PIXI.BaseRenderTexture(100, 100); + * let renderTexture = new PIXI.RenderTexture(baseRenderTexture); + * + * renderer.render(sprite, renderTexture); // Renders to center of RenderTexture + * ``` + * + * @class + * @extends PIXI.BaseTexture + * @memberof PIXI + */ +export default class BaseRenderTexture extends BaseTexture +{ + /** + * @param {object} [options] + * @param {number} [options.width=100] - The width of the base render texture + * @param {number} [options.height=100] - The height of the base render texture + * @param {PIXI.SCALE_MODES} [options.scaleMode] - See {@link PIXI.SCALE_MODES} for possible values + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the texture being generated + */ + constructor(options) + { + if (typeof options === 'number') + { + /* eslint-disable prefer-rest-params */ + // Backward compatibility of signature + const width = arguments[0]; + const height = arguments[1]; + const scaleMode = arguments[2]; + const resolution = arguments[3]; + + options = { width, height, scaleMode, resolution }; + /* eslint-enable prefer-rest-params */ + } + + super(null, options); + + const { width, height } = options || {}; + + // Set defaults + this.mipmap = false; + this.width = Math.ceil(width) || 100; + this.height = Math.ceil(height) || 100; + this.valid = true; + + /** + * A map of renderer IDs to webgl renderTargets + * + * @private + * @member {object} + */ + // this._glRenderTargets = {}; + + /** + * A reference to the canvas render target (we only need one as this can be shared across renderers) + * + * @private + * @member {object} + */ + this._canvasRenderTarget = null; + + this.clearColor = [0, 0, 0, 0]; + + this.frameBuffer = new FrameBuffer(width * this.resolution, height * this.resolution) + .addColorTexture(0, this); + + // TODO - could this be added the systems? + + /** + * The data structure for the stencil masks + * + * @member {PIXI.Graphics[]} + */ + this.stencilMaskStack = []; + + /** + * The data structure for the filters + * + * @member {PIXI.Graphics[]} + */ + this.filterStack = [{}]; + } + + /** + * Resizes the BaseRenderTexture. + * + * @param {number} width - The width to resize to. + * @param {number} height - The height to resize to. + */ + resize(width, height) + { + width = Math.ceil(width); + height = Math.ceil(height); + this.frameBuffer.resize(width * this.resolution, height * this.resolution); + } + + /** + * Destroys this texture + * + */ + destroy() + { + super.destroy(true); + this.renderer = null; + } +} diff --git a/packages/core/src/renderTexture/RenderTexture.js b/packages/core/src/renderTexture/RenderTexture.js new file mode 100644 index 0000000..f1ecf1f --- /dev/null +++ b/packages/core/src/renderTexture/RenderTexture.js @@ -0,0 +1,153 @@ +import BaseRenderTexture from './BaseRenderTexture'; +import Texture from '../textures/Texture'; + +/** + * A RenderTexture is a special texture that allows any PixiJS display object to be rendered to it. + * + * __Hint__: All DisplayObjects (i.e. Sprites) that render to a RenderTexture should be preloaded + * otherwise black rectangles will be drawn instead. + * + * A RenderTexture takes a snapshot of any Display Object given to its render method. For example: + * + * ```js + * let renderer = PIXI.autoDetectRenderer(1024, 1024, { view: canvas, ratio: 1 }); + * let renderTexture = PIXI.RenderTexture.create(800, 600); + * let sprite = PIXI.Sprite.fromImage("spinObj_01.png"); + * + * sprite.position.x = 800/2; + * sprite.position.y = 600/2; + * sprite.anchor.x = 0.5; + * sprite.anchor.y = 0.5; + * + * renderer.render(sprite, renderTexture); + * ``` + * + * The Sprite in this case will be rendered using its local transform. To render this sprite at 0,0 + * you can clear the transform + * + * ```js + * + * sprite.setTransform() + * + * let renderTexture = new PIXI.RenderTexture.create(100, 100); + * + * renderer.render(sprite, renderTexture); // Renders to center of RenderTexture + * ``` + * + * @class + * @extends PIXI.Texture + * @memberof PIXI + */ +export default class RenderTexture extends Texture +{ + /** + * @param {PIXI.BaseRenderTexture} baseRenderTexture - The renderer used for this RenderTexture + * @param {PIXI.Rectangle} [frame] - The rectangle frame of the texture to show + */ + constructor(baseRenderTexture, frame) + { + // support for legacy.. + let _legacyRenderer = null; + + if (!(baseRenderTexture instanceof BaseRenderTexture)) + { + /* eslint-disable prefer-rest-params, no-console */ + const width = arguments[1]; + const height = arguments[2]; + const scaleMode = arguments[3]; + const resolution = arguments[4]; + + // we have an old render texture.. + console.warn(`Please use RenderTexture.create(${width}, ${height}) instead of the ctor directly.`); + _legacyRenderer = arguments[0]; + /* eslint-enable prefer-rest-params, no-console */ + + frame = null; + baseRenderTexture = new BaseRenderTexture({ + width, + height, + scaleMode, + resolution, + }); + } + + /** + * The base texture object that this texture uses + * + * @member {BaseTexture} + */ + super(baseRenderTexture, frame); + + this.legacyRenderer = _legacyRenderer; + + /** + * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. + * + * @member {boolean} + */ + this.valid = true; + + /** + * FilterSystem temporary storage + * @private + * @member {PIXI.Rectangle} + */ + this.filterFrame = null; + + this._updateUvs(); + } + + /** + * Resizes the RenderTexture. + * + * @param {number} width - The width to resize to. + * @param {number} height - The height to resize to. + * @param {boolean} [resizeBaseTexture=true] - Should the baseTexture.width and height values be resized as well? + */ + resize(width, height, resizeBaseTexture = true) + { + width = Math.ceil(width); + height = Math.ceil(height); + + // TODO - could be not required.. + this.valid = (width > 0 && height > 0); + + this._frame.width = this.orig.width = width; + this._frame.height = this.orig.height = height; + + if (resizeBaseTexture) + { + this.baseTexture.resize(width, height); + } + + this._updateUvs(); + } + + /** + * A short hand way of creating a render texture. + * + * @param {object} [options] - Options + * @param {number} [options.width=100] - The width of the render texture + * @param {number} [options.height=100] - The height of the render texture + * @param {number} [options.scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the texture being generated + * @return {PIXI.RenderTexture} The new render texture + */ + static create(options) + { + // fallback, old-style: create(width, height, scaleMode, resolution) + if (typeof options === 'number') + { + /* eslint-disable prefer-rest-params */ + options = { + width: options, + height: arguments[1], + scaleMode: arguments[2], + resolution: arguments[3], + }; + /* eslint-enable prefer-rest-params */ + } + + return new RenderTexture(new BaseRenderTexture(options)); + } +} diff --git a/packages/core/src/renderTexture/RenderTextureSystem.js b/packages/core/src/renderTexture/RenderTextureSystem.js new file mode 100644 index 0000000..8d0ca61 --- /dev/null +++ b/packages/core/src/renderTexture/RenderTextureSystem.js @@ -0,0 +1,115 @@ +import System from '../System'; +import { Rectangle } from '@pixi/math'; + +const tempRect = new Rectangle(); + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class RenderTextureSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.clearColor = renderer._backgroundColorRgba; + + // TODO moe this property somewhere else! + this.defaultMaskStack = []; + this.defaultFilterStack = [{}]; + + // empty render texture? + this.renderTexture = null; + + this.destinationFrame = new Rectangle(); + } + + bind(renderTexture, sourceFrame, destinationFrame) + { + // TODO - do we want this?? + if (this.renderTexture === renderTexture) return; + this.renderTexture = renderTexture; + + const renderer = this.renderer; + + if (renderTexture) + { + const baseTexture = renderTexture.baseTexture; + + if (!destinationFrame) + { + tempRect.width = baseTexture.realWidth; + tempRect.height = baseTexture.realHeight; + + destinationFrame = tempRect; + } + + if (!sourceFrame) + { + sourceFrame = destinationFrame; + } + + this.renderer.framebuffer.bind(baseTexture.frameBuffer, destinationFrame); + + this.renderer.projection.update(destinationFrame, sourceFrame, baseTexture.resolution, false); + this.renderer.stencil.setMaskStack(baseTexture.stencilMaskStack); + } + else + { + // TODO these validation checks happen deeper down.. + // thing they can be avoided.. + if (!destinationFrame) + { + tempRect.width = renderer.width; + tempRect.height = renderer.height; + + destinationFrame = tempRect; + } + + if (!sourceFrame) + { + sourceFrame = destinationFrame; + } + + renderer.framebuffer.bind(null, destinationFrame); + + // TODO store this.. + this.renderer.projection.update(destinationFrame, sourceFrame, this.renderer.resolution, true); + this.renderer.stencil.setMaskStack(this.defaultMaskStack); + } + + this.destinationFrame.copyFrom(destinationFrame); + } + + /** + * Erases the render texture and fills the drawing area with a colour + * + * @param {number} [clearColor] - The colour + * @return {PIXI.Renderer} Returns itself. + */ + clear(clearColor) + { + if (this.renderTexture) + { + clearColor = clearColor || this.renderTexture.baseTexture.clearColor; + } + else + { + clearColor = clearColor || this.clearColor; + } + + this.renderer.framebuffer.clear(clearColor[0], clearColor[1], clearColor[2], clearColor[3]); + } + + resize()// screenWidth, screenHeight) + { + // resize the root only! + this.bind(null); + } +} diff --git a/packages/core/src/renderers/AbstractRenderer.js b/packages/core/src/renderers/AbstractRenderer.js deleted file mode 100644 index 928670f..0000000 --- a/packages/core/src/renderers/AbstractRenderer.js +++ /dev/null @@ -1,353 +0,0 @@ -import { hex2string, hex2rgb } from '@pixi/utils'; -import { Matrix, Rectangle } from '@pixi/math'; -import { RENDERER_TYPE } from '@pixi/constants'; -import { settings } from '@pixi/settings'; -import { Container } from '@pixi/display'; -import RenderTexture from '../textures/RenderTexture'; -import EventEmitter from 'eventemitter3'; - -const tempMatrix = new Matrix(); - -/** - * 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 EventEmitter - * @memberof PIXI - */ -export default class AbstractRenderer extends EventEmitter -{ - // eslint-disable-next-line valid-jsdoc - /** - * @param {string} system - The name of the system this renderer is for. - * @param {object} [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.transparent=false] - If the render view is transparent, default false - * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false - * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) - * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. The - * resolution of the renderer retina would be 2. - * @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 {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when rendering, - * stopping pixel interpolation. - */ - constructor(system, options, arg2, arg3) - { - super(); - - // Support for constructor(system, screenWidth, screenHeight, options) - if (typeof options === 'number') - { - options = Object.assign({ - width: options, - height: arg2 || settings.RENDER_OPTIONS.height, - }, arg3); - } - - // 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 = RENDERER_TYPE.UNKNOWN; - - /** - * Measurements of the screen. (0, 0, screenWidth, screenHeight) - * - * Its safe to use as filterArea or hitArea for 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 1 - */ - this.resolution = options.resolution || settings.RESOLUTION; - - /** - * Whether the render view is transparent - * - * @member {boolean} - */ - this.transparent = options.transparent; - - /** - * Whether css dimensions of canvas view should be resized to screen dimensions automatically - * - * @member {boolean} - */ - this.autoResize = options.autoResize || false; - - /** - * Tracks the blend modes useful for this renderer. - * - * @member {object} - */ - this.blendModes = null; - - /** - * 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; - - /** - * If true PixiJS will Math.floor() x/y values when rendering, stopping pixel interpolation. - * Handy for crisp pixel art and speed on legacy devices. - * - * @member {boolean} - */ - this.roundPixels = options.roundPixels; - - /** - * The background color as a number. - * - * @member {number} - * @private - */ - this._backgroundColor = 0x000000; - - /** - * The background color as an [R, G, B] array. - * - * @member {number[]} - * @private - */ - this._backgroundColorRgba = [0, 0, 0, 0]; - - /** - * The background color as a string. - * - * @member {string} - * @private - */ - this._backgroundColorString = '#000000'; - - this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter - - /** - * This temporary display object used as the parent of the currently being rendered item - * - * @member {PIXI.DisplayObject} - * @private - */ - this._tempDisplayObjectParent = new Container(); - - /** - * The last root object that the renderer tried to render. - * - * @member {PIXI.DisplayObject} - * @private - */ - this._lastObjectRendered = this._tempDisplayObjectParent; - - /** - * Collection of plugins. - * @readonly - * @member {object} - */ - this.plugins = {}; - } - - /** - * Initialize the plugins. - * - * @protected - * @param {object} staticMap - The dictionary of staticly saved plugins. - */ - initPlugins(staticMap) - { - 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() - { - return this.view.width; - } - - /** - * Same as view.height, actual number of pixels in the canvas by vertical - * - * @member {number} - * @readonly - * @default 600 - */ - get height() - { - return this.view.height; - } - - /** - * Resizes the screen and canvas to the specified width and height - * Canvas dimensions are multiplied by resolution - * - * @param {number} screenWidth - the new width of the screen - * @param {number} screenHeight - the new height of the screen - */ - resize(screenWidth, screenHeight) - { - this.screen.width = screenWidth; - this.screen.height = screenHeight; - - this.view.width = screenWidth * this.resolution; - this.view.height = screenHeight * this.resolution; - - if (this.autoResize) - { - this.view.style.width = `${screenWidth}px`; - this.view.style.height = `${screenHeight}px`; - } - } - - /** - * 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 {PIXI.DisplayObject} displayObject - The displayObject the object will be generated from - * @param {number} scaleMode - Should be one of the scaleMode consts - * @param {number} resolution - The resolution / device pixel ratio of the texture being generated - * @param {PIXI.Rectangle} [region] - The region of the displayObject, that shall be rendered, - * if no region is specified, defaults to the local bounds of the displayObject. - * @return {PIXI.Texture} a texture of the graphics object - */ - generateTexture(displayObject, scaleMode, resolution, region) - { - region = region || displayObject.getLocalBounds(); - - // 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(region.width | 0, region.height | 0, scaleMode, resolution); - - tempMatrix.tx = -region.x; - tempMatrix.ty = -region.y; - - this.render(displayObject, renderTexture, false, tempMatrix, true); - - return renderTexture; - } - - /** - * Removes everything from the renderer and optionally removes the Canvas DOM element. - * - * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. - */ - destroy(removeView) - { - 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); - } - - this.plugins = null; - - this.type = RENDERER_TYPE.UNKNOWN; - - this.view = null; - - this.screen = null; - - this.resolution = 0; - - this.transparent = false; - - this.autoResize = false; - - this.blendModes = null; - - this.options = null; - - this.preserveDrawingBuffer = false; - this.clearBeforeRender = false; - - this.roundPixels = false; - - this._backgroundColor = 0; - this._backgroundColorRgba = null; - this._backgroundColorString = null; - - this._tempDisplayObjectParent = null; - this._lastObjectRendered = null; - } - - /** - * The background color to fill if not transparent - * - * @member {number} - */ - get backgroundColor() - { - return this._backgroundColor; - } - - set backgroundColor(value) // eslint-disable-line require-jsdoc - { - this._backgroundColor = value; - this._backgroundColorString = hex2string(value); - hex2rgb(value, this._backgroundColorRgba); - } -} diff --git a/packages/core/src/renderers/Renderer.js b/packages/core/src/renderers/Renderer.js deleted file mode 100644 index a2020b1..0000000 --- a/packages/core/src/renderers/Renderer.js +++ /dev/null @@ -1,329 +0,0 @@ -import AbstractRenderer from './AbstractRenderer'; -import { sayHello } from '@pixi/utils'; -import MaskSystem from './systems/MaskSystem'; -import StencilSystem from './systems/StencilSystem'; -import FilterSystem from './systems/filter/FilterSystem'; -import FramebufferSystem from './systems/FramebufferSystem'; -import RenderTextureSystem from './systems/RenderTextureSystem'; -import TextureSystem from './systems/textures/TextureSystem'; -import ProjectionSystem from './systems/ProjectionSystem'; -import StateSystem from './systems/StateSystem'; -import GeometrySystem from './systems/geometry/GeometrySystem'; -import ShaderSystem from './systems/shader/ShaderSystem'; -import ContextSystem from './systems/ContextSystem'; -import BatchSystem from './systems/BatchSystem'; -import TextureGCSystem from './systems/textures/TextureGCSystem'; -import { RENDERER_TYPE } from '@pixi/constants'; -import UniformGroup from '../shader/UniformGroup'; -import { Matrix } from '@pixi/math'; -import Runner from 'mini-runner'; - -/** - * 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 Render works by automatically managing webGLBatchs. - * 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 :) - * - * @class - * @memberof PIXI - * @extends PIXI.AbstractRenderer - */ -export default class Renderer extends AbstractRenderer -{ - // eslint-disable-next-line valid-jsdoc - /** - * - * @param {object} [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.transparent=false] - If the render view is transparent, default false - * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false - * @param {boolean} [options.antialias=false] - sets antialias. If not available natively then FXAA - * antialiasing is used - * @param {boolean} [options.forceFXAA=false] - forces FXAA antialiasing to be used over native. - * FXAA is faster, but may not always look as great - * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. - * The resolution of the renderer retina would be 2. - * @param {boolean} [options.clearBeforeRender=true] - This sets if the renderer will clear - * the canvas or not before the new render pass. If you wish to set this to false, you *must* set - * preserveDrawingBuffer to `true`. - * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, - * enable this if you need to call toDataUrl on the webgl context. - * @param {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when - * rendering, stopping pixel interpolation. - * @param {number} [options.backgroundColor=0x000000] - The background color of the rendered area - * (shown if not transparent). - * @param {boolean} [options.legacy=false] - If true PixiJS will aim to ensure compatibility - * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. - * @param {string} [options.powerPreference] - Parameter passed to webgl context, set to "high-performance" - * for devices with dual graphics card - */ - constructor(options, arg2, arg3) - { - super('WebGL', options, arg2, arg3); - - /** - * The type of this renderer as a standardised const - * - * @member {number} - * @see PIXI.RENDERER_TYPE - */ - this.type = RENDERER_TYPE.WEBGL; - - // this will be set by the contextSystem (this.context) - this.gl = null; - this.CONTEXT_UID = 0; - this.legacy = !!options.legacy; - - // TODO legacy! - - // runners! - this.runners = { - destroy: new Runner('destroy'), - contextChange: new Runner('contextChange', 1), - reset: new Runner('reset'), - update: new Runner('update'), - postrender: new Runner('postrender'), - prerender: new Runner('prerender'), - resize: new Runner('resize', 2), - }; - - 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(GeometrySystem, 'geometry') - .addSystem(FramebufferSystem, 'framebuffer') - .addSystem(StencilSystem, 'stencil') - .addSystem(ProjectionSystem, 'projection') - .addSystem(TextureGCSystem, 'textureGC') - .addSystem(FilterSystem, 'filter') - .addSystem(RenderTextureSystem, 'renderTexture') - .addSystem(BatchSystem, 'batch'); - - this.initPlugins(Renderer.__plugins); - - /** - * The options passed in to create a new webgl context. - * - * @member {object} - * @private - */ - if (options.context) - { - this.context.initFromContext(options.context); - } - else - { - this.context.initFromOptions({ - alpha: this.transparent, - antialias: options.antialias, - premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', - stencil: true, - 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); - } - - addSystem(_class, name) - { - if (!name) - { - name = _class.name; - } - - // TODO - read name from class.name.. - - /* - if(name.includes('System')) - { - name = name.replace('System', ''); - name = name.charAt(0).toLowerCase() + name.slice(1); - } - */ - - const system = new _class(this); - - if (this[name]) - { - throw new Error(`Whoops! ${name} is already a manger`); - } - - this[name] = system; - - for (const i in this.runners) - { - this.runners[i].add(system); - } - - return this; - - /** - * 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. - */ - } - - /** - * Renders the object to its webGL view - * - * @param {PIXI.DisplayObject} displayObject - the object to be rendered - * @param {PIXI.RenderTexture} renderTexture - The render texture to render to. - * @param {boolean} [clear] - Should the canvas be cleared before the new render - * @param {PIXI.Transform} [transform] - A transform to apply to the render texture before rendering. - * @param {boolean} [skipUpdateTransform] - Should we skip the update transform pass? - */ - render(displayObject, renderTexture, clear, transform, skipUpdateTransform) - { - // can be handy to know! - this.renderingToScreen = !renderTexture; - - this.runners.prerender.run(); - this.emit('prerender'); - - // 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.parent; - - displayObject.parent = this._tempDisplayObjectParent; - displayObject.updateTransform(); - displayObject.parent = 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.run(); - - this.emit('postrender'); - } - - /** - * Resizes the webGL view to the specified width and height. - * - * @param {number} screenWidth - the new width of the screen - * @param {number} screenHeight - the new height of the screen - */ - resize(screenWidth, screenHeight) - { - AbstractRenderer.prototype.resize.call(this, screenWidth, screenHeight); - - this.runners.resize.run(screenWidth, screenHeight); - } - - /** - * Resets the WebGL state so you can render things however you fancy! - * - * @return {PIXI.Renderer} Returns itself. - */ - reset() - { - this.runners.reset.run(); - - return this; - } - - clear() - { - this.framebuffer.bind(); - this.framebuffer.clear(); - } - - /** - * Removes everything from the renderer (event listeners, spritebatch, etc...) - * - * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. - * See: https://github.com/pixijs/pixi.js/issues/2233 - */ - destroy(removeView) - { - this.runners.destroy.run(); - - // call base destroy - super.destroy(removeView); - - // TODO nullify all the managers.. - this.gl = null; - } - - /** - * 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 - * builds and excluding plugins. - * @name PIXI.Renderer#plugins - * @type {object} - * @readonly - * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. - * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. - * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. - * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. - */ - - /** - * Adds a plugin to the renderer. - * - * @method - * @param {string} pluginName - The name of the plugin. - * @param {Function} ctor - The constructor function or class for the plugin. - */ - static registerPlugin(pluginName, ctor) - { - Renderer.__plugins = Renderer.__plugins || {}; - Renderer.__plugins[pluginName] = ctor; - } -} diff --git a/packages/core/src/renderers/State.js b/packages/core/src/renderers/State.js deleted file mode 100644 index fa3f221..0000000 --- a/packages/core/src/renderers/State.js +++ /dev/null @@ -1,163 +0,0 @@ -/* eslint-disable max-len */ - -const BLEND = 0; -const OFFSET = 1; -const CULLING = 2; -const DEPTH_TEST = 3; -const WINDING = 4; - -/** - * This is a webGL state. It is passed The WebGL StateManager. - * Each mesh renderered may require webGL to be in a different state. - * For example you may want different blend mode or to enable polygon offsets - * - * @class - * @memberof PIXI - */ -export default class State -{ - /** - * - */ - constructor() - { - this.data = 0; - - this.blendMode = 0; - this.polygonOffset = 0; - - this.blend = true; - // this.depthTest = true; - } - - /** - * Activates blending of the computed fragment color values - * - * @member {boolean} - */ - get blend() - { - return !!(this.data & (1 << BLEND)); - } - - set blend(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << BLEND)) !== value) - { - this.data ^= (1 << BLEND); - } - } - - /** - * Activates adding an offset to depth values of polygon's fragments - * - * @member {boolean} - * @default false - */ - get offsets() - { - return !!(this.data & (1 << OFFSET)); - } - - set offsets(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << OFFSET)) !== value) - { - this.data ^= (1 << OFFSET); - } - } - - /** - * Activates culling of polygons. - * - * @member {boolean} - * @default false - */ - get culling() - { - return !!(this.data & (1 << CULLING)); - } - - set culling(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << CULLING)) !== value) - { - this.data ^= (1 << CULLING); - } - } - - /** - * Activates depth comparisons and updates to the depth buffer. - * - * @member {boolean} - * @default false - */ - get depthTest() - { - return !!(this.data & (1 << DEPTH_TEST)); - } - - set depthTest(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << DEPTH_TEST)) !== value) - { - this.data ^= (1 << DEPTH_TEST); - } - } - - /** - * Specifies whether or not front or back-facing polygons can be culled. - * @member {boolean} - * @default false - */ - get clockwiseFrontFace() - { - return !!(this.data & (1 << WINDING)); - } - - set clockwiseFrontFace(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << WINDING)) !== value) - { - this.data ^= (1 << WINDING); - } - } - - /** - * The blend mode to be applied when this state is set. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. - * Setting this mode to anything other than NO_BLEND will automatically switch blending on. - * - * @member {boolean} - * @default PIXI.BLEND_MODES.NORMAL - * @see PIXI.BLEND_MODES - */ - get blendMode() - { - return this._blendMode; - } - - set blendMode(value) // eslint-disable-line require-jsdoc - { - // 17 is NO BLEND - this.blend = (value !== 17); - this._blendMode = value; - } - - /** - * The polygon offset. Setting this property to anything other than 0 will automatically enable poygon offset fill. - * - * @member {number} - * @default 0 - */ - get polygonOffset() - { - return this._polygonOffset; - } - - set polygonOffset(value) // eslint-disable-line require-jsdoc - { - this.offsets = !!value; - this._polygonOffset = value; - } -} - diff --git a/packages/core/src/renderers/filters/Filter.js b/packages/core/src/renderers/filters/Filter.js deleted file mode 100644 index 2073b8d..0000000 --- a/packages/core/src/renderers/filters/Filter.js +++ /dev/null @@ -1,140 +0,0 @@ -import Shader from '../../shader/Shader'; -import Program from '../../shader/Program'; -import State from '../State'; -import { settings } from '@pixi/settings'; -// import extractUniformsFromSrc from './extractUniformsFromSrc'; -import defaultVertex from './defaultFilter.vert'; -import defaultFragment from './defaultFilter.frag'; - -// let math = require('../../math'); -/** - * @class - * @memberof PIXI - * @extends PIXI.Shader - */ -export default class Filter extends Shader -{ - /** - * @param {string} [vertexSrc] - The source of the vertex shader. - * @param {string} [fragmentSrc] - The source of the fragment shader. - * @param {object} [uniforms] - Custom uniforms to use to augment the built-in ones. - */ - constructor(vertexSrc, fragmentSrc, uniforms) - { - const program = Program.from(vertexSrc, fragmentSrc); - - super(program, uniforms); - - /** - * The padding of the filter. Some filters require extra space to breath such as a blur. - * Increasing this will add extra width and height to the bounds of the object that the - * filter is applied to. - * - * @member {number} - */ - this.padding = 0; - - /** - * The resolution of the filter. Setting this to be lower will lower the quality but - * increase the performance of the filter. - * - * @member {number} - */ - this.resolution = settings.RESOLUTION; - - /** - * If enabled is true the filter is applied, if false it will not. - * - * @member {boolean} - */ - this.enabled = true; - - /** - * If enabled, PixiJS will fit the filter area into boundaries for better performance. - * Switch it off if it does not work for specific shader. - * - * @member {boolean} - */ - this.autoFit = true; - - /** - * Legacy filters use position and uvs from attributes - * @member {boolean} - * @readonly - */ - this.legacy = !!this.program.attributeData.aTextureCoord; - - /** - * the webGL state the filter requires to render - * @member {PIXI.State} - */ - this.state = new State(); - } - - /** - * Applies the filter - * - * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from - * @param {PIXI.RenderTarget} input - The input render target. - * @param {PIXI.RenderTarget} output - The target to output to. - * @param {boolean} clear - Should the output be cleared before rendering to it - * @param {object} [currentState] - It's current state of filter. - * There are some useful properties in the currentState : - * target, filters, sourceFrame, destinationFrame, renderTarget, resolution - */ - apply(filterManager, input, output, clear, currentState, derp) // eslint-disable-line no-unused-vars - { - // do as you please! - - filterManager.applyFilter(this, input, output, clear, currentState, derp); - - // or just do a regular render.. - } - - /** - * Sets the blendmode of the filter - * - * @member {number} - * @default PIXI.BLEND_MODES.NORMAL - */ - get blendMode() - { - return this.state.blendMode; - } - - set blendMode(value) // eslint-disable-line require-jsdoc - { - this.state.blendMode = value; - } - - /** - * The default vertex shader source - * - * @static - * @constant - */ - static get defaultVertexSrc() - { - return defaultVertex; - } - - /** - * The default fragment shader source - * - * @static - * @constant - */ - static get defaultFragmentSrc() - { - return defaultFragment; - } -} - -/** - * Used for caching shader IDs - * - * @static - * @private - */ -Filter.SOURCE_KEY_MAP = {}; - diff --git a/packages/core/src/renderers/filters/defaultFilter.frag b/packages/core/src/renderers/filters/defaultFilter.frag deleted file mode 100644 index a14c1be..0000000 --- a/packages/core/src/renderers/filters/defaultFilter.frag +++ /dev/null @@ -1,22 +0,0 @@ -varying vec2 vTextureCoord; -varying vec2 vFilterCoord; - -uniform sampler2D uSampler; -uniform sampler2D filterSampler; - -void main(void){ - vec4 masky = texture2D(filterSampler, vFilterCoord); - vec4 sample = texture2D(uSampler, vTextureCoord); - vec4 color; - if(mod(vFilterCoord.x, 1.0) > 0.5) - { - color = vec4(1.0, 0.0, 0.0, 1.0); - } - else - { - color = vec4(0.0, 1.0, 0.0, 1.0); - } - // gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0); - gl_FragColor = mix(sample, masky, 0.5); - gl_FragColor *= sample.a; -} diff --git a/packages/core/src/renderers/filters/defaultFilter.vert b/packages/core/src/renderers/filters/defaultFilter.vert deleted file mode 100644 index e1febca..0000000 --- a/packages/core/src/renderers/filters/defaultFilter.vert +++ /dev/null @@ -1,14 +0,0 @@ -attribute vec2 aVertexPosition; -attribute vec2 aTextureCoord; - -uniform mat3 projectionMatrix; -uniform mat3 filterMatrix; - -varying vec2 vTextureCoord; -varying vec2 vFilterCoord; - -void main(void){ - gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); - vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy; - vTextureCoord = aTextureCoord ; -} diff --git a/packages/core/src/renderers/filters/filterTransforms.js b/packages/core/src/renderers/filters/filterTransforms.js deleted file mode 100644 index 87a3cd6..0000000 --- a/packages/core/src/renderers/filters/filterTransforms.js +++ /dev/null @@ -1,50 +0,0 @@ -import { Matrix } from '@pixi/math'; - -/** - * Calculates the mapped matrix - * @param filterArea {Rectangle} The filter area - * @param sprite {Sprite} the target sprite - * @param outputMatrix {Matrix} @alvin - * @private - */ -// this returns a matrix that will normalise map filter cords in the filter to screen space -export function calculateScreenSpaceMatrix(outputMatrix, filterArea, textureSize) -{ - // TODO unwrap? - const mappedMatrix = outputMatrix.identity(); - - mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); - - mappedMatrix.scale(textureSize.width, textureSize.height); - - return mappedMatrix; -} - -export function calculateNormalizedScreenSpaceMatrix(outputMatrix, filterArea, textureSize) -{ - const mappedMatrix = outputMatrix.identity(); - - mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); - - const translateScaleX = (textureSize.width / filterArea.width); - const translateScaleY = (textureSize.height / filterArea.height); - - mappedMatrix.scale(translateScaleX, translateScaleY); - - return mappedMatrix; -} - -// this will map the filter coord so that a texture can be used based on the transform of a sprite -export function calculateSpriteMatrix(outputMatrix, filterArea, textureSize, sprite) -{ - const orig = sprite._texture.orig; - const mappedMatrix = outputMatrix.set(textureSize.width, 0, 0, textureSize.height, filterArea.x, filterArea.y); - const worldTransform = sprite.worldTransform.copyTo(Matrix.TEMP_MATRIX); - - worldTransform.invert(); - mappedMatrix.prepend(worldTransform); - mappedMatrix.scale(1.0 / orig.width, 1.0 / orig.height); - mappedMatrix.translate(sprite.anchor.x, sprite.anchor.y); - - return mappedMatrix; -} diff --git a/packages/core/src/renderers/filters/spriteMask/SpriteMaskFilter.js b/packages/core/src/renderers/filters/spriteMask/SpriteMaskFilter.js deleted file mode 100644 index d5a91a4..0000000 --- a/packages/core/src/renderers/filters/spriteMask/SpriteMaskFilter.js +++ /dev/null @@ -1,63 +0,0 @@ -import Filter from '../Filter'; -import { Matrix } from '@pixi/math'; -import vertex from './spriteMaskFilter.vert'; -import fragment from './spriteMaskFilter.frag'; -import { default as TextureMatrix } from '../../../textures/TextureMatrix'; - -/** - * The SpriteMaskFilter class - * - * @class - * @extends PIXI.Filter - * @memberof PIXI - */ -export default class SpriteMaskFilter extends Filter -{ - /** - * @param {PIXI.Sprite} sprite - the target sprite - */ - constructor(sprite) - { - const maskMatrix = new Matrix(); - - super(vertex, fragment); - - sprite.renderable = false; - - this.maskSprite = sprite; - this.maskMatrix = maskMatrix; - } - - /** - * Applies the filter - * - * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from - * @param {PIXI.RenderTarget} input - The input render target. - * @param {PIXI.RenderTarget} output - The target to output to. - */ - apply(filterManager, input, output) - { - const maskSprite = this.maskSprite; - const tex = this.maskSprite.texture; - - if (!tex.valid) - { - return; - } - if (!tex.transform) - { - // margin = 0.0, let it bleed a bit, shader code becomes easier - // assuming that atlas textures were made with 1-pixel padding - tex.transform = new TextureMatrix(tex, 0.0); - } - tex.transform.update(); - - this.uniforms.mask = tex; - this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite) - .prepend(tex.transform.mapCoord); - this.uniforms.alpha = maskSprite.worldAlpha; - this.uniforms.maskClamp = tex.transform.uClampFrame; - - filterManager.applyFilter(this, input, output); - } -} diff --git a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.frag b/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.frag deleted file mode 100644 index 0e4aef8..0000000 --- a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.frag +++ /dev/null @@ -1,23 +0,0 @@ -varying vec2 vMaskCoord; -varying vec2 vTextureCoord; - -uniform sampler2D uSampler; -uniform sampler2D mask; -uniform float alpha; -uniform vec4 maskClamp; - -void main(void) -{ - float clip = step(3.5, - step(maskClamp.x, vMaskCoord.x) + - step(maskClamp.y, vMaskCoord.y) + - step(vMaskCoord.x, maskClamp.z) + - step(vMaskCoord.y, maskClamp.w)); - - vec4 original = texture2D(uSampler, vTextureCoord); - vec4 masky = texture2D(mask, vMaskCoord); - - original *= (masky.r * masky.a * alpha * clip); - - gl_FragColor = original; -} diff --git a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.vert b/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.vert deleted file mode 100644 index d23a30c..0000000 --- a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.vert +++ /dev/null @@ -1,16 +0,0 @@ -attribute vec2 aVertexPosition; -attribute vec2 aTextureCoord; - -uniform mat3 projectionMatrix; -uniform mat3 otherMatrix; - -varying vec2 vMaskCoord; -varying vec2 vTextureCoord; - -void main(void) -{ - gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); - - vTextureCoord = aTextureCoord; - vMaskCoord = ( otherMatrix * vec3( aTextureCoord, 1.0) ).xy; -} diff --git a/packages/core/src/renderers/systems/BatchSystem.js b/packages/core/src/renderers/systems/BatchSystem.js deleted file mode 100644 index 70e9518..0000000 --- a/packages/core/src/renderers/systems/BatchSystem.js +++ /dev/null @@ -1,68 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import ObjectRenderer from '../utils/ObjectRenderer'; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ - -export default class BatchSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this manager works for. - */ - constructor(renderer) - { - super(renderer); - - /** - * An empty renderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.emptyRenderer = new ObjectRenderer(renderer); - - /** - * The currently active ObjectRenderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.currentRenderer = this.emptyRenderer; - } - - /** - * Changes the current renderer to the one given in parameter - * - * @param {PIXI.ObjectRenderer} objectRenderer - The object renderer to use. - */ - setObjectRenderer(objectRenderer) - { - if (this.currentRenderer === objectRenderer) - { - return; - } - - this.currentRenderer.stop(); - this.currentRenderer = objectRenderer; - - this.renderer.state.setState(objectRenderer.state); - - this.currentRenderer.start(); - } - - /** - * This should be called if you wish to do some custom rendering - * It will basically render anything that may be batched up such as sprites - * - */ - flush() - { - this.setObjectRenderer(this.emptyRenderer); - } - - reset() - { - this.setObjectRenderer(this.emptyRenderer); - } -} diff --git a/packages/core/src/renderers/systems/ContextSystem.js b/packages/core/src/renderers/systems/ContextSystem.js deleted file mode 100644 index 43bf827..0000000 --- a/packages/core/src/renderers/systems/ContextSystem.js +++ /dev/null @@ -1,186 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { settings } from '@pixi/settings'; - -let CONTEXT_UID = 0; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class ContextSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this.webGLVersion = 1; - - this.handleContextLost = this.handleContextLost.bind(this); - this.handleContextRestored = this.handleContextRestored.bind(this); - - this.extensions = {}; - - renderer.view.addEventListener('webglcontextlost', this.handleContextLost, false); - renderer.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); - } - - get isLost() - { - return (!this.gl || this.gl.isContextLost()); - } - - contextChange(gl) - { - this.gl = gl; - - // restore a context if it was previously lost - if (gl.isContextLost() && gl.getExtension('WEBGL_lose_context')) - { - gl.getExtension('WEBGL_lose_context').restoreContext(); - } - } - - initFromContext(gl) - { - this.gl = gl; - this.validateContext(gl); - this.renderer.gl = gl; - this.renderer.CONTEXT_UID = CONTEXT_UID++; - this.renderer.runners.contextChange.run(gl); - } - - initFromOptions(options) - { - const gl = this.createContext(this.renderer.view, options); - - this.initFromContext(gl); - } - - /** - * Helper class to create a webGL Context - * - * @class - * @memberof PIXI.glCore - * @param canvas {HTMLCanvasElement} the canvas element that we will get the context from - * @param options {Object} An options object that gets passed in to the canvas element containing the context attributes, - * see https://developer.mozilla.org/en/docs/Web/API/HTMLCanvasElement/getContext for the options available - * @return {WebGLRenderingContext} the WebGL context - */ - createContext(canvas, options) - { - let gl; - - if (settings.PREFER_WEBGL_2) - { - gl = canvas.getContext('webgl2', options); - } - - if (gl) - { - this.webGLVersion = 2; - } - else - { - this.webGLVersion = 1; - - gl = canvas.getContext('webgl', options) - || canvas.getContext('experimental-webgl', options); - - if (!gl) - { - // fail, not able to get a context - throw new Error('This browser does not support webGL. Try using the canvas renderer'); - } - } - - this.gl = gl; - - this.getExtensions(); - - return gl; - } - - getExtensions() - { - // time to set up default etensions that pixi uses.. - const gl = this.gl; - const extensions = this.extensions; - - if (this.webGLVersion === 1) - { - extensions.drawBuffers = gl.getExtension('WEBGL_draw_buffers'); - extensions.depthTexture = gl.getExtension('WEBKIT_WEBGL_depth_texture'); - extensions.floatTexture = gl.getExtension('OES_texture_float'); - extensions.loseContext = gl.getExtension('WEBGL_lose_context'); - - extensions.vertexArrayObject = gl.getExtension('OES_vertex_array_object') - || gl.getExtension('MOZ_OES_vertex_array_object') - || gl.getExtension('WEBKIT_OES_vertex_array_object'); - } - - // we don't use any specific WebGL 2 ones yet! - } - - /** - * Handles a lost webgl context - * - * @private - * @param {WebGLContextEvent} event - The context lost event. - */ - handleContextLost(event) - { - event.preventDefault(); - } - - /** - * Handles a restored webgl context - * - * @private - */ - handleContextRestored() - { - this.renderer.runners.contextChange.run(this.gl); - } - - destroy() - { - const view = this.renderer.view; - - // remove listeners - view.removeEventListener('webglcontextlost', this.handleContextLost); - view.removeEventListener('webglcontextrestored', this.handleContextRestored); - - this.gl.useProgram(null); - - if (this.extensions.loseContext) - { - this.extensions.loseContext.loseContext(); - } - } - - postrender() - { - this.gl.flush(); - } - - validateContext(gl) - { - const attributes = gl.getContextAttributes(); - - // this is going to be fairly simple for now.. but at least we have room to grow! - if (!attributes.stencil) - { - /* eslint-disable max-len */ - - /* eslint-disable no-console */ - console.warn('Provided WebGL context does not have a stencil buffer, masks may not render correctly'); - /* eslint-enable no-console */ - - /* eslint-enable max-len */ - } - } -} diff --git a/packages/core/src/renderers/systems/FramebufferSystem.js b/packages/core/src/renderers/systems/FramebufferSystem.js deleted file mode 100644 index df7902b..0000000 --- a/packages/core/src/renderers/systems/FramebufferSystem.js +++ /dev/null @@ -1,246 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { Rectangle } from '@pixi/math'; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class FramebufferSystem extends WebGLSystem -{ - /** - * Sets up the renderer context and necessary buffers. - * - * @private - */ - contextChange() - { - this.gl = this.renderer.gl; - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - this.current = null; - this.viewport = new Rectangle(); - - this.drawBufferExtension = this.renderer.context.extensions.drawBuffers; - } - - bind(framebuffer, frame) - { - const gl = this.gl; - - this.current = framebuffer; - - if (framebuffer) - { - // TODO cacheing layer! - - const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID] || this.initFramebuffer(framebuffer); - - gl.bindFramebuffer(gl.FRAMEBUFFER, fbo.framebuffer); - // makesure all textures are unbound.. - - // now check for updates... - if (fbo.dirtyId !== framebuffer.dirtyId) - { - fbo.dirtyId = framebuffer.dirtyId; - - if (fbo.dirtyFormat !== framebuffer.dirtyFormat) - { - fbo.dirtyFormat = framebuffer.dirtyFormat; - this.updateFramebuffer(framebuffer); - } - else if (fbo.dirtySize !== framebuffer.dirtySize) - { - fbo.dirtySize = framebuffer.dirtySize; - this.resizeFramebuffer(framebuffer); - } - } - - for (let i = 0; i < framebuffer.colorTextures.length; i++) - { - if (framebuffer.colorTextures[i].texturePart) - { - this.renderer.texture.unbind(framebuffer.colorTextures[i].texture); - } - else - { - this.renderer.texture.unbind(framebuffer.colorTextures[i]); - } - } - - if (framebuffer.depthTexture) - { - this.renderer.texture.unbind(framebuffer.depthTexture); - } - - if (frame) - { - this.setViewport(frame.x, frame.y, frame.width, frame.height); - } - else - { - this.setViewport(0, 0, framebuffer.width, framebuffer.height); - } - } - else - { - gl.bindFramebuffer(gl.FRAMEBUFFER, null); - - if (frame) - { - this.setViewport(frame.x, frame.y, frame.width, frame.height); - } - else - { - this.setViewport(0, 0, this.renderer.width, this.renderer.height); - } - } - } - - setViewport(x, y, width, height) - { - const v = this.viewport; - - if (v.width !== width || v.height !== height || v.x !== x || v.y !== y) - { - v.x = x; - v.y = y; - v.width = width; - v.height = height; - - this.gl.viewport(x, y, width, height); - } - } - - get size() - { - if (this.current) - { - // TODO store temp - return { x: 0, y: 0, width: this.current.width, height: this.current.height }; - } - - return { x: 0, y: 0, width: this.renderer.width, height: this.renderer.height }; - } - - clear(r, g, b, a) - { - const gl = this.gl; - - // TODO clear color can be set only one right? - gl.clearColor(r, g, b, a); - gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); - } - - // private functions... - - initFramebuffer(framebuffer) - { - const gl = this.gl; - - // TODO - make this a class? - const fbo = { - framebuffer: gl.createFramebuffer(), - stencil: null, - dirtyId: 0, - dirtyFormat: 0, - dirtySize: 0, - }; - - framebuffer.glFrameBuffers[this.CONTEXT_UID] = fbo; - - return fbo; - } - - resizeFramebuffer(framebuffer) - { - const gl = this.gl; - - if (framebuffer.stencil || framebuffer.depth) - { - gl.bindRenderbuffer(gl.RENDERBUFFER, this.stencil); - gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); - } - } - - updateFramebuffer(framebuffer) - { - const gl = this.gl; - - const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID]; - - // bind the color texture - const colorTextures = framebuffer.colorTextures; - - let count = colorTextures.length; - - if (!this.drawBufferExtension) - { - count = Math.min(count, 1); - } - - const activeTextures = []; - - for (let i = 0; i < count; i++) - { - const texture = framebuffer.colorTextures[i]; - - if (texture.texturePart) - { - this.renderer.texture.bind(texture.texture, 0); - - gl.framebufferTexture2D(gl.FRAMEBUFFER, - gl.COLOR_ATTACHMENT0 + i, - gl.TEXTURE_CUBE_MAP_NEGATIVE_X + texture.side, - texture.texture._glTextures[this.CONTEXT_UID].texture, - 0); - } - else - { - this.renderer.texture.bind(texture, 0); - - gl.framebufferTexture2D(gl.FRAMEBUFFER, - gl.COLOR_ATTACHMENT0 + i, - gl.TEXTURE_2D, - texture._glTextures[this.CONTEXT_UID].texture, - 0); - } - - activeTextures.push(gl.COLOR_ATTACHMENT0 + i); - } - - if (this.drawBufferExtension && activeTextures.length > 1) - { - this.drawBufferExtension.drawBuffersWEBGL(activeTextures); - } - - if (framebuffer.depthTexture) - { - const depthTextureExt = this.renderer.context.extensions.depthTexture; - - if (depthTextureExt) - { - const depthTexture = framebuffer.depthTexture; - - this.renderer.texture.bind(depthTexture, 0); - - gl.framebufferTexture2D(gl.FRAMEBUFFER, - gl.DEPTH_ATTACHMENT, - gl.TEXTURE_2D, - depthTexture._glTextures[this.CONTEXT_UID].texture, - 0); - } - } - - if (framebuffer.stencil || framebuffer.depth) - { - fbo.stencil = gl.createRenderbuffer(); - - gl.bindRenderbuffer(gl.RENDERBUFFER, fbo.stencil); - - // TODO.. this is depth AND stencil? - gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, fbo.stencil); - gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); - // fbo.enableStencil(); - } - } -} diff --git a/packages/core/src/renderers/systems/MaskSystem.js b/packages/core/src/renderers/systems/MaskSystem.js deleted file mode 100644 index d9c4b1a..0000000 --- a/packages/core/src/renderers/systems/MaskSystem.js +++ /dev/null @@ -1,199 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import AlphaMaskFilter from '../filters/spriteMask/SpriteMaskFilter'; - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class MaskSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - // TODO - we don't need both! - this.scissor = false; - this.scissorData = null; - this.scissorRenderTarget = null; - - this.enableScissor = false; - - this.alphaMaskPool = []; - this.alphaMaskIndex = 0; - } - - /** - * Applies the Mask and adds it to the current filter stack. - * - * @param {PIXI.DisplayObject} target - Display Object to push the mask to - * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. - */ - push(target, maskData) - { - // TODO the root check means scissor rect will not - // be used on render textures more info here: - // https://github.com/pixijs/pixi.js/pull/3545 - - if (maskData.texture) - { - this.pushSpriteMask(target, maskData); - } - else if (this.enableScissor - && !this.scissor - && this.renderer._activeRenderTarget.root - && !this.renderer.stencil.stencilMaskStack.length - && maskData.isFastRect()) - { - const matrix = maskData.worldTransform; - - let rot = Math.atan2(matrix.b, matrix.a); - - // use the nearest degree! - rot = Math.round(rot * (180 / Math.PI)); - - if (rot % 90) - { - this.pushStencilMask(maskData); - } - else - { - this.pushScissorMask(target, maskData); - } - } - else - { - this.pushStencilMask(maskData); - } - } - - /** - * Removes the last mask from the mask stack and doesn't return it. - * - * @param {PIXI.DisplayObject} target - Display Object to pop the mask from - * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. - */ - pop(target, maskData) - { - if (maskData.texture) - { - this.popSpriteMask(target, maskData); - } - else if (this.enableScissor && !this.renderer.stencil.stencilMaskStack.length) - { - this.popScissorMask(target, maskData); - } - else - { - this.popStencilMask(target, maskData); - } - } - - /** - * Applies the Mask and adds it to the current filter stack. - * - * @param {PIXI.RenderTarget} target - Display Object to push the sprite mask to - * @param {PIXI.Sprite} maskData - Sprite to be used as the mask - */ - pushSpriteMask(target, maskData) - { - let alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; - - if (!alphaMaskFilter) - { - alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; - } - - alphaMaskFilter[0].resolution = this.renderer.resolution; - alphaMaskFilter[0].maskSprite = maskData; - - // TODO - may cause issues! - target.filterArea = maskData.getBounds(true); - - this.renderer.filterSystem.pushFilter(target, alphaMaskFilter); - - this.alphaMaskIndex++; - } - - /** - * Removes the last filter from the filter stack and doesn't return it. - * - */ - popSpriteMask() - { - this.renderer.filterSystem.popFilter(); - this.alphaMaskIndex--; - } - - /** - * Applies the Mask and adds it to the current filter stack. - * - * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. - */ - pushStencilMask(maskData) - { - this.renderer.batch.flush(); - this.renderer.stencil.pushStencil(maskData); - } - - /** - * Removes the last filter from the filter stack and doesn't return it. - * - */ - popStencilMask() - { - // this.renderer.currentRenderer.stop(); - this.renderer.stencil.popStencil(); - } - - /** - * - * @param {PIXI.DisplayObject} target - Display Object to push the mask to - * @param {PIXI.Graphics} maskData - The masking data. - */ - pushScissorMask(target, maskData) - { - maskData.renderable = true; - - const renderTarget = this.renderer._activeRenderTarget; - - const bounds = maskData.getBounds(); - - bounds.fit(renderTarget.size); - maskData.renderable = false; - - this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); - - const resolution = this.renderer.resolution; - - this.renderer.gl.scissor( - bounds.x * resolution, - (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, - bounds.width * resolution, - bounds.height * resolution - ); - - this.scissorRenderTarget = renderTarget; - this.scissorData = maskData; - this.scissor = true; - } - - /** - * - * - */ - popScissorMask() - { - this.scissorRenderTarget = null; - this.scissorData = null; - this.scissor = false; - - // must be scissor! - const gl = this.renderer.gl; - - gl.disable(gl.SCISSOR_TEST); - } -} diff --git a/packages/core/src/renderers/systems/ProjectionSystem.js b/packages/core/src/renderers/systems/ProjectionSystem.js deleted file mode 100644 index ec5efdb..0000000 --- a/packages/core/src/renderers/systems/ProjectionSystem.js +++ /dev/null @@ -1,73 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { Matrix } from '@pixi/math'; - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ - -export default class ProjectionSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this.projectionMatrix = new Matrix(); - } - - update(destinationFrame, sourceFrame, resolution, root) - { - this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; - this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; - - this.calculateProjection(this.destinationFrame, this.sourceFrame, resolution, root); - - this.renderer.globalUniforms.uniforms.projectionMatrix = this.projectionMatrix; - this.renderer.globalUniforms.update(); - } - - /** - * Updates the projection matrix based on a projection frame (which is a rectangle) - * - * @param {Rectangle} destinationFrame - The destination frame. - * @param {Rectangle} sourceFrame - The source frame. - */ - calculateProjection(destinationFrame, sourceFrame, resolution, root) - { - const pm = this.projectionMatrix; - - // I don't think we will need this line.. - // pm.identity(); - - if (!root) - { - pm.a = (1 / destinationFrame.width * 2) * resolution; - pm.d = (1 / destinationFrame.height * 2) * resolution; - - pm.tx = -1 - (sourceFrame.x * pm.a); - pm.ty = -1 - (sourceFrame.y * pm.d); - } - else - { - pm.a = (1 / destinationFrame.width * 2) * resolution; - pm.d = (-1 / destinationFrame.height * 2) * resolution; - - pm.tx = -1 - (sourceFrame.x * pm.a); - pm.ty = 1 - (sourceFrame.y * pm.d); - } - } - - /** - * Sets the transform of the active render target to the given matrix - * - * @param {PIXI.Matrix} matrix - The transformation matrix - */ - setTransform()// matrix) - { - // this._activeRenderTarget.transform = matrix; - } -} diff --git a/packages/core/src/renderers/systems/RenderTextureSystem.js b/packages/core/src/renderers/systems/RenderTextureSystem.js deleted file mode 100644 index a133ef1..0000000 --- a/packages/core/src/renderers/systems/RenderTextureSystem.js +++ /dev/null @@ -1,115 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { Rectangle } from '@pixi/math'; - -const tempRect = new Rectangle(); - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ - -export default class RenderTextureSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this.clearColor = renderer._backgroundColorRgba; - - // TODO moe this property somewhere else! - this.defaultMaskStack = []; - this.defaultFilterStack = [{}]; - - // empty render texture? - this.renderTexture = null; - - this.destinationFrame = new Rectangle(); - } - - bind(renderTexture, sourceFrame, destinationFrame) - { - // TODO - do we want this?? - if (this.renderTexture === renderTexture) return; - this.renderTexture = renderTexture; - - const renderer = this.renderer; - - if (renderTexture) - { - const baseTexture = renderTexture.baseTexture; - - if (!destinationFrame) - { - tempRect.width = baseTexture.realWidth; - tempRect.height = baseTexture.realHeight; - - destinationFrame = tempRect; - } - - if (!sourceFrame) - { - sourceFrame = destinationFrame; - } - - this.renderer.framebuffer.bind(baseTexture.frameBuffer, destinationFrame); - - this.renderer.projection.update(destinationFrame, sourceFrame, baseTexture.resolution, false); - this.renderer.stencil.setMaskStack(baseTexture.stencilMaskStack); - } - else - { - // TODO these validation checks happen deeper down.. - // thing they can be avoided.. - if (!destinationFrame) - { - tempRect.width = renderer.width; - tempRect.height = renderer.height; - - destinationFrame = tempRect; - } - - if (!sourceFrame) - { - sourceFrame = destinationFrame; - } - - renderer.framebuffer.bind(null, destinationFrame); - - // TODO store this.. - this.renderer.projection.update(destinationFrame, sourceFrame, this.renderer.resolution, true); - this.renderer.stencil.setMaskStack(this.defaultMaskStack); - } - - this.destinationFrame.copyFrom(destinationFrame); - } - - /** - * Erases the render texture and fills the drawing area with a colour - * - * @param {number} [clearColor] - The colour - * @return {PIXI.Renderer} Returns itself. - */ - clear(clearColor) - { - if (this.renderTexture) - { - clearColor = clearColor || this.renderTexture.baseTexture.clearColor; - } - else - { - clearColor = clearColor || this.clearColor; - } - - this.renderer.framebuffer.clear(clearColor[0], clearColor[1], clearColor[2], clearColor[3]); - } - - resize()// screenWidth, screenHeight) - { - // resize the root only! - this.bind(null); - } -} diff --git a/packages/core/src/renderers/systems/StateSystem.js b/packages/core/src/renderers/systems/StateSystem.js deleted file mode 100755 index d7f4caf..0000000 --- a/packages/core/src/renderers/systems/StateSystem.js +++ /dev/null @@ -1,288 +0,0 @@ -import mapWebGLBlendModesToPixi from '../utils/mapWebGLBlendModesToPixi'; -import WebGLSystem from './WebGLSystem'; -import WebGLState from '../State'; - -const BLEND = 0; -const OFFSET = 1; -const CULLING = 2; -const DEPTH_TEST = 3; -const WINDING = 4; - -/** - * A WebGL state machines - * - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class StateSystem extends WebGLSystem -{ - /** - * @param {WebGLRenderingContext} gl - The current WebGL rendering context - */ - constructor(renderer) - { - super(renderer); - - this.gl = null; - - this.maxAttribs = null; - - // check we have vao.. - this.nativeVaoExtension = null; - - this.attribState = null; - - this.stateId = 0; - this.polygonOffset = 0; - this.blendMode = 17; - - this.map = []; - - // map functions for when we set state.. - this.map[BLEND] = this.setBlend; - this.map[OFFSET] = this.setOffset; - this.map[CULLING] = this.setCullFace; - this.map[DEPTH_TEST] = this.setDepthTest; - this.map[WINDING] = this.setFrontFace; - - this.checks = []; - - this.defaultState = new WebGLState(); - this.defaultState.blend = true; - this.defaultState.depth = true; - } - - contextChange(gl) - { - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; - - this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); - - // check we have vao.. - this.nativeVaoExtension = ( - gl.getExtension('OES_vertex_array_object') - || gl.getExtension('MOZ_OES_vertex_array_object') - || gl.getExtension('WEBKIT_OES_vertex_array_object') - ); - - this.attribState = { - tempAttribState: new Array(this.maxAttribs), - attribState: new Array(this.maxAttribs), - }; - - this.blendModes = mapWebGLBlendModesToPixi(gl); - - this.setState(this.defaultState); - - this.reset(); - } - - /** - * Sets the current state - * - * @param {*} state - The state to set. - */ - setState(state) - { - state = state || this.defaultState; - - // TODO maybe to an object check? ( this.state === state )? - if (this.stateId !== state.data) - { - let diff = this.stateId ^ state.data; - let i = 0; - - // order from least to most common - while (diff) - { - if (diff & 1) - { - // state change! - this.map[i].call(this, !!(state.data & (1 << i))); - } - - diff = diff >> 1; - i++; - } - - this.stateId = state.data; - } - - // based on the above settings we check for specific modes.. - // for example if blend is active we check and set the blend modes - // or of polygon offset is active we check the poly depth. - for (let i = 0; i < this.checks.length; i++) - { - this.checks[i](this, state); - } - } - - /** - * Enables or disabled blending. - * - * @param {boolean} value - Turn on or off webgl blending. - */ - setBlend(value) - { - this.updateCheck(StateSystem.checkBlendMode, value); - - this.gl[value ? 'enable' : 'disable'](this.gl.BLEND); - } - - /** - * Enables or disable polygon offset fill - * - * @param {boolean} value - Turn on or off webgl polygon offset testing. - */ - setOffset(value) - { - this.gl[value ? 'enable' : 'disable'](this.gl.POLYGON_OFFSET_FILL); - } - - /** - * Sets whether to enable or disable depth test. - * - * @param {boolean} value - Turn on or off webgl depth testing. - */ - setDepthTest(value) - { - this.gl[value ? 'enable' : 'disable'](this.gl.DEPTH_TEST); - } - - /** - * Sets whether to enable or disable cull face. - * - * @param {boolean} value - Turn on or off webgl cull face. - */ - setCullFace(value) - { - this.gl[value ? 'enable' : 'disable'](this.gl.CULL_FACE); - } - - /** - * Sets the gl front face. - * - * @param {boolean} value - true is clockwise and false is counter-clockwise - */ - setFrontFace(value) - { - this.gl.frontFace(this.gl[value ? 'CW' : 'CCW']); - } - - /** - * Sets the blend mode. - * - * @param {number} value - The blend mode to set to. - */ - setBlendMode(value) - { - if (value === this.blendMode) - { - return; - } - - this.blendMode = value; - this.gl.blendFunc(this.blendModes[value][0], this.blendModes[value][1]); - } - - /** - * Sets the polygon offset. - * - * @param {number} value - the polygon offset - * @param {number} scale - the polygon offset scale - */ - setPolygonOffset(value, scale) - { - this.gl.polygonOffset(value, scale); - } - - /** - * Disables all the vaos in use - * - */ - resetAttributes() - { - for (let i = 0; i < this.attribState.tempAttribState.length; i++) - { - this.attribState.tempAttribState[i] = 0; - } - - for (let i = 0; i < this.attribState.attribState.length; i++) - { - this.attribState.attribState[i] = 0; - } - - // im going to assume one is always active for performance reasons. - for (let i = 1; i < this.maxAttribs; i++) - { - this.gl.disableVertexAttribArray(i); - } - } - - // used - /** - * Resets all the logic and disables the vaos - */ - reset() - { - // unbind any VAO if they exist.. - if (this.nativeVaoExtension) - { - this.nativeVaoExtension.bindVertexArrayOES(null); - } - - // reset all attributes.. - this.resetAttributes(); - - this.gl.pixelStorei(this.gl.UNPACK_FLIP_Y_WEBGL, false); - - this.setBlendMode(0); - - // TODO? - // this.setState(this.defaultState); - } - - /** - * checks to see which updates should be checked based on which settings have been activated - * for example if blend is enabled then we shold check the blend modes each time the state is changed - * or if poygon fill is activated then we need to check if the polygone offset changes. - * The idea is that we only check what we have too. - * - * @param {Function} func the checking function to add or remove - * @param {boolean} value should the check function be added or removed. - */ - updateCheck(func, value) - { - const index = this.checks.indexOf(func); - - if (value && index === -1) - { - this.checks.push(func); - } - else if (!value && index !== -1) - { - this.checks.splice(index, 1); - } - } - - /** - * A private little wrapper function that we call to check the blend mode. - * - * @static - * @private - * @param {PIXI.StateSystem} System the System to perform the state check on - * @param {PIXI.State} state the state that the blendMode will pulled from - */ - static checkBlendMode(system, state) - { - system.setBlendMode(state.blendMode); - } - - // TODO - add polygon offset? -} diff --git a/packages/core/src/renderers/systems/StencilSystem.js b/packages/core/src/renderers/systems/StencilSystem.js deleted file mode 100644 index 5d538cb..0000000 --- a/packages/core/src/renderers/systems/StencilSystem.js +++ /dev/null @@ -1,133 +0,0 @@ -import WebGLSystem from './WebGLSystem'; - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class StencilSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - this.stencilMaskStack = []; - } - - /** - * Changes the mask stack that is used by this System. - * - * @param {PIXI.Graphics[]} stencilMaskStack - The mask stack - */ - setMaskStack(stencilMaskStack) - { - const gl = this.renderer.gl; - - if (stencilMaskStack.length !== this.stencilMaskStack.length) - { - if (stencilMaskStack.length === 0) - { - gl.disable(gl.STENCIL_TEST); - } - else - { - gl.enable(gl.STENCIL_TEST); - } - } - - this.stencilMaskStack = stencilMaskStack; - } - - /** - * Applies the Mask and adds it to the current stencil stack. @alvin - * - * @param {PIXI.Graphics} graphics - The mask - */ - pushStencil(graphics) - { - this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); - - // this.renderer._activeRenderTarget.attachStencilBuffer(); - - const gl = this.renderer.gl; - const prevMaskCount = this.stencilMaskStack.length; - - if (prevMaskCount === 0) - { - gl.enable(gl.STENCIL_TEST); - } - - this.stencilMaskStack.push(graphics); - - // Increment the refference stencil value where the new mask overlaps with the old ones. - gl.colorMask(false, false, false, false); - gl.stencilFunc(gl.EQUAL, prevMaskCount, this._getBitwiseMask()); - gl.stencilOp(gl.KEEP, gl.KEEP, gl.INCR); - this.renderer.plugins.graphics.render(graphics); - - this._useCurrent(); - } - - /** - * Removes the last mask from the stencil stack. @alvin - */ - popStencil() - { - this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); - - const gl = this.renderer.gl; - const graphics = this.stencilMaskStack.pop(); - - if (this.stencilMaskStack.length === 0) - { - // the stack is empty! - gl.disable(gl.STENCIL_TEST); - gl.clear(gl.STENCIL_BUFFER_BIT); - gl.clearStencil(0); - } - else - { - // Decrement the refference stencil value where the popped mask overlaps with the other ones - gl.colorMask(false, false, false, false); - gl.stencilOp(gl.KEEP, gl.KEEP, gl.DECR); - this.renderer.plugins.graphics.render(graphics); - - this._useCurrent(); - } - } - - /** - * Setup renderer to use the current stencil data. - */ - _useCurrent() - { - const gl = this.renderer.gl; - - gl.colorMask(true, true, true, true); - gl.stencilFunc(gl.EQUAL, this.stencilMaskStack.length, this._getBitwiseMask()); - gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP); - } - - /** - * Fill 1s equal to the number of acitve stencil masks. - * - * @return {number} The bitwise mask. - */ - _getBitwiseMask() - { - return (1 << this.stencilMaskStack.length) - 1; - } - - /** - * Destroys the mask stack. - * - */ - destroy() - { - super.destroy(this); - - this.stencilMaskStack = null; - } -} diff --git a/packages/core/src/renderers/systems/WebGLSystem.js b/packages/core/src/renderers/systems/WebGLSystem.js deleted file mode 100644 index d3d5b31..0000000 --- a/packages/core/src/renderers/systems/WebGLSystem.js +++ /dev/null @@ -1,41 +0,0 @@ -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this manager works for. - */ - constructor(renderer) - { - /** - * The renderer this manager works for. - * - * @member {PIXI.Renderer} - */ - this.renderer = renderer; - - this.renderer.runners.contextChange.add(this); - } - - /** - * Generic method called when there is a WebGL context change. - * - */ - contextChange() - { - // do some codes init! - } - - /** - * Generic destroy methods to be overridden by the subclass - * - */ - destroy() - { - this.renderer.runners.contextChange.remove(this); - this.renderer = null; - } -} diff --git a/packages/core/src/renderers/systems/filter/FilterSystem.js b/packages/core/src/renderers/systems/filter/FilterSystem.js deleted file mode 100644 index e615037..0000000 --- a/packages/core/src/renderers/systems/filter/FilterSystem.js +++ /dev/null @@ -1,431 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; - -import RenderTexture from '../../../textures/RenderTexture'; -import Quad from '../../utils/Quad'; -import QuadUv from '../../utils/QuadUv'; -import { Rectangle } from '@pixi/math'; -import * as filterTransforms from '../../filters/filterTransforms'; -import bitTwiddle from 'bit-twiddle'; -import UniformGroup from '../../../shader/UniformGroup'; -import { DRAW_MODES } from '../../../../../constants'; - -// -/** - * @ignore - * @class - */ -class FilterState -{ - /** - * - */ - constructor() - { - this.renderTexture = null; - this.sourceFrame = new Rectangle(); - this.destinationFrame = new Rectangle(); - this.filters = []; - this.target = null; - this.legacy = false; - this.resolution = 1; - } -} - -/** - * @class - * @memberof PIXI.systems - * @extends PIXI.systems.WebGLSystem - */ -export default class FilterSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - /** - * stores a bunch of PO2 textures used for filtering - * @type {Object} - */ - this.texturePool = {}; - - /** - * a pool for storing filter states, save us creating new ones each tick - * @type {Array} - */ - this.statePool = []; - - /** - * A very simple geometry used when drawing a filter effect to the screen - * @type {Quad} - */ - this.quad = new Quad(); - - this.quadUv = new QuadUv(); - - /** - * Temporary rect for maths - * @type {PIXI.Rectangle} - */ - this.tempRect = new Rectangle(); - - this.activeState = {}; - - /** - * this uniform group is attached to filter uniforms when used - * @type {UniformGroup} - */ - this.globalUniforms = new UniformGroup({ - sourceFrame: this.tempRect, - destinationFrame: this.tempRect, - - // legacy variables - filterArea: new Float32Array(4), - filterClamp: new Float32Array(4), - }, true); - } - - /** - * Adds a new filter to the System. - * - * @param {PIXI.DisplayObject} target - The target of the filter to render. - * @param {PIXI.Filter[]} filters - The filters to apply. - */ - push(target, filters) - { - const renderer = this.renderer; - const filterStack = this.renderer.renderTexture.defaultFilterStack; - const state = this.statePool.pop() || new FilterState(); - - let resolution = filters[0].resolution; - let padding = filters[0].padding; - let autoFit = filters[0].autoFit; - let legacy = filters[0].legacy; - - for (let i = 1; i < filters.length; i++) - { - const filter = filters[i]; - - // lets use the lowest resolution.. - resolution = Math.min(resolution, filter.resolution); - // and the largest amount of padding! - padding = Math.max(padding, filter.padding); - // only auto fit if all filters are autofit - autoFit = autoFit || filter.autoFit; - - legacy = legacy || filter.legacy; - } - - filterStack.push(state); - - state.resolution = resolution; - - state.legacy = legacy; - - // round to whole number based on resolution - // TODO move that to the shader too? - state.sourceFrame = target.filterArea || target.getBounds(true); - - state.sourceFrame.pad(padding); - - if (autoFit) - { - state.sourceFrame.fit(this.renderer.renderTexture.destinationFrame); - } - - state.sourceFrame.round(resolution); - - state.renderTexture = this.getPotFilterTexture(state.sourceFrame.width, state.sourceFrame.height, resolution); - state.filters = filters; - - state.destinationFrame.width = state.renderTexture.width; - state.destinationFrame.height = state.renderTexture.height; - - state.renderTexture.filterFrame = state.sourceFrame; - renderer.renderTexture.bind(state.renderTexture, state.sourceFrame);// /, state.destinationFrame); - renderer.renderTexture.clear(); - } - - /** - * Pops off the filter and applies it. - * - */ - pop() - { - const renderer = this.renderer; - const filterStack = renderer.renderTexture.defaultFilterStack; - const state = filterStack.pop(); - const filters = state.filters; - - this.activeState = state; - - const globalUniforms = this.globalUniforms.uniforms; - - globalUniforms.sourceFrame = state.sourceFrame; - globalUniforms.destinationFrame = state.destinationFrame; - globalUniforms.resolution = state.resolution; - - // only update the rect if its legacy.. - if (state.legacy) - { - const filterArea = globalUniforms.filterArea; - const filterClamp = globalUniforms.filterClamp; - - filterArea[0] = state.destinationFrame.width; - filterArea[1] = state.destinationFrame.height; - filterArea[2] = state.sourceFrame.x; - filterArea[3] = state.sourceFrame.y; - - filterClamp[0] = 0.5 / state.resolution / state.destinationFrame.width; - filterClamp[1] = 0.5 / state.resolution / state.destinationFrame.height; - filterClamp[2] = (state.sourceFrame.width - 0.5) / state.resolution / state.destinationFrame.width; - filterClamp[3] = (state.sourceFrame.height - 0.5) / state.resolution / state.destinationFrame.height; - } - - this.globalUniforms.update(); - - const lastState = filterStack[filterStack.length - 1]; - - if (filters.length === 1) - { - filters[0].apply(this, state.renderTexture, lastState.renderTexture, false, state); - - this.returnFilterTexture(state.renderTexture); - } - else - { - let flip = state.renderTexture; - let flop = this.getPotFilterTexture( - flip.width, - flip.height, - state.resolution - ); - - flop.filterFrame = flip.filterFrame; - - let i = 0; - - for (i = 0; i < filters.length - 1; ++i) - { - filters[i].apply(this, flip, flop, true, state); - - const t = flip; - - flip = flop; - flop = t; - } - - filters[i].apply(this, flip, lastState.renderTexture, false, state); - - this.returnFilterTexture(flip); - this.returnFilterTexture(flop); - } - - this.statePool.push(state); - } - - /** - * Draws a filter. - * - * @param {PIXI.Filter} filter - The filter to draw. - * @param {PIXI.RenderTarget} input - The input render target. - * @param {PIXI.RenderTarget} output - The target to output to. - * @param {boolean} clear - Should the output be cleared before rendering to it - */ - applyFilter(filter, input, output, clear) - { - const renderer = this.renderer; - - renderer.renderTexture.bind(output, output ? output.filterFrame : null); - - if (clear) - { - // gl.disable(gl.SCISSOR_TEST); - renderer.renderTexture.clear(); - // gl.enable(gl.SCISSOR_TEST); - } - - // set the uniforms.. - filter.uniforms.uSampler = input; - filter.uniforms.filterGlobals = this.globalUniforms; - - // TODO make it so that the order of this does not matter.. - // because it does at the moment cos of global uniforms. - // they need to get resynced - - renderer.state.setState(filter.state); - renderer.shader.bind(filter); - - if (filter.legacy) - { - this.quadUv.map(input._frame, input.filterFrame); - - renderer.geometry.bind(this.quadUv); - renderer.geometry.draw(DRAW_MODES.TRIANGLES); - } - else - { - renderer.geometry.bind(this.quad); - renderer.geometry.draw(DRAW_MODES.TRIANGLE_STRIP); - } - } - - /** - * Calculates the mapped matrix. - * - * TODO playing around here.. this is temporary - (will end up in the shader) - * this returns a matrix that will normalise map filter cords in the filter to screen space - * - * @param {PIXI.Matrix} outputMatrix - the matrix to output to. - * @return {PIXI.Matrix} The mapped matrix. - */ - calculateScreenSpaceMatrix(outputMatrix) - { - const currentState = this.activeState; - - return filterTransforms.calculateScreenSpaceMatrix( - outputMatrix, - currentState.sourceFrame, - currentState.destinationFrame - ); - } - - /** - * This will map the filter coord so that a texture can be used based on the transform of a sprite - * - * @param {PIXI.Matrix} outputMatrix - The matrix to output to. - * @param {PIXI.Sprite} sprite - The sprite to map to. - * @return {PIXI.Matrix} The mapped matrix. - */ - calculateSpriteMatrix(outputMatrix, sprite) - { - const currentState = this.activeState; - - return filterTransforms.calculateSpriteMatrix( - outputMatrix, - currentState.sourceFrame, - currentState.destinationFrame, - sprite - ); - } - - /** - * Destroys this Filter System. - * - * @param {boolean} [contextLost=false] context was lost, do not free shaders - * - */ - destroy(contextLost = false) - { - if (!contextLost) - { - this.emptyPool(); - } - else - { - this.texturePool = {}; - } - } - - /** - * Gets a Power-of-Two render texture. - * - * TODO move to a seperate class could be on renderer? - * also - could cause issue with multiple contexts? - * - * @private - * @param {WebGLRenderingContext} gl - The webgl rendering context - * @param {number} minWidth - The minimum width of the render target. - * @param {number} minHeight - The minimum height of the render target. - * @param {number} resolution - The resolution of the render target. - * @return {PIXI.RenderTarget} The new render target. - */ - getPotFilterTexture(minWidth, minHeight, resolution) - { - minWidth = bitTwiddle.nextPow2(minWidth); - minHeight = bitTwiddle.nextPow2(minHeight); - resolution = resolution || 1; - - const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); - - if (!this.texturePool[key]) - { - this.texturePool[key] = []; - } - - let renderTexture = this.texturePool[key].pop(); - - if (!renderTexture) - { - // temporary bypass cache.. - // internally - this will cause a texture to be bound.. - renderTexture = RenderTexture.create({ - width: minWidth, - height: minHeight, - resolution, - }); - } - - return renderTexture; - } - - /** - * Gets extra render texture to use inside current filter - * - * @param {number} resolution resolution of the renderTexture - * @returns {PIXI.RenderTexture} - */ - getFilterTexture(resolution) - { - const rt = this.activeState.renderTexture; - - const filterTexture = this.getPotFilterTexture(rt.width, rt.height, resolution || rt.baseTexture.resolution); - - filterTexture.filterFrame = rt.filterFrame; - - return filterTexture; - } - - /** - * Frees a render texture back into the pool. - * - * @param {PIXI.RenderTarget} renderTexture - The renderTarget to free - */ - returnFilterTexture(renderTexture) - { - renderTexture.filterFrame = null; - - const base = renderTexture.baseTexture; - - const minWidth = base.width; - const minHeight = base.height; - - const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); - - this.texturePool[key].push(renderTexture); - } - - /** - * Empties the texture pool. - * - */ - emptyPool() - { - for (const i in this.texturePool) - { - const textures = this.texturePool[i]; - - if (textures) - { - for (let j = 0; j < textures.length; j++) - { - textures[j].destroy(true); - } - } - } - - this.texturePool = {}; - } -} diff --git a/packages/core/src/renderers/systems/geometry/GLBuffer.js b/packages/core/src/renderers/systems/geometry/GLBuffer.js deleted file mode 100644 index 0477e8a..0000000 --- a/packages/core/src/renderers/systems/geometry/GLBuffer.js +++ /dev/null @@ -1,9 +0,0 @@ -export default class GLBuffer -{ - constructor(buffer) - { - this.buffer = buffer; - this.updateID = -1; - this.byteLength = -1; - } -} diff --git a/packages/core/src/renderers/systems/geometry/GeometrySystem.js b/packages/core/src/renderers/systems/geometry/GeometrySystem.js deleted file mode 100644 index 26c8e9e..0000000 --- a/packages/core/src/renderers/systems/geometry/GeometrySystem.js +++ /dev/null @@ -1,388 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; -import GLBuffer from './GLBuffer'; - -const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class GeometrySystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this._activeGeometry = null; - this._activeVao = null; - - this.hasVao = true; - this.hasInstance = true; - } - - /** - * Sets up the renderer context and necessary buffers. - * - * @private - */ - contextChange() - { - const gl = this.gl = this.renderer.gl; - - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - - // webgl2 - if (!gl.createVertexArray) - { - // webgl 1! - let nativeVaoExtension = this.renderer.context.extensions.vertexArrayObject; - - if (this.renderer.legacy) - { - nativeVaoExtension = null; - } - - if (nativeVaoExtension) - { - gl.createVertexArray = () => - nativeVaoExtension.createVertexArrayOES(); - - gl.bindVertexArray = (vao) => - nativeVaoExtension.bindVertexArrayOES(vao); - - gl.deleteVertexArray = (vao) => - nativeVaoExtension.deleteVertexArrayOES(vao); - } - else - { - this.hasVao = false; - gl.createVertexArray = () => - { - // empty - }; - - gl.bindVertexArray = () => - { - // empty - }; - - gl.deleteVertexArray = () => - { - // empty - }; - } - } - - if (!gl.vertexAttribDivisor) - { - const instanceExt = gl.getExtension('ANGLE_instanced_arrays'); - - if (instanceExt) - { - gl.vertexAttribDivisor = (a, b) => - instanceExt.vertexAttribDivisorANGLE(a, b); - - gl.drawElementsInstanced = (a, b, c, d, e) => - instanceExt.drawElementsInstancedANGLE(a, b, c, d, e); - - gl.drawArraysInstanced = (a, b, c, d) => - instanceExt.drawArraysInstancedANGLE(a, b, c, d); - } - else - { - this.hasInstance = false; - } - } - } - - /** - * Binds geometry so that is can be drawn. Creating a Vao if required - * @private - * @param {PIXI.Geometry} geometry instance of geometry to bind - */ - bind(geometry, shader) - { - shader = shader || this.renderer.shader.shader; - - const gl = this.gl; - - // not sure the best way to address this.. - // currently different shaders require different VAOs for the same geometry - // Still mulling over the best way to solve this one.. - // will likely need to modify the shader attribute locations at run time! - let vaos = geometry.glVertexArrayObjects[this.CONTEXT_UID]; - - if (!vaos) - { - geometry.glVertexArrayObjects[this.CONTEXT_UID] = vaos = {}; - } - - const vao = vaos[shader.program.id] || this.initGeometryVao(geometry, shader.program); - - this._activeGeometry = geometry; - - if (this._activeVao !== vao) - { - this._activeVao = vao; - - if (this.hasVao) - { - gl.bindVertexArray(vao); - } - else - { - this.activateVao(geometry, shader.program); - } - } - - // TODO - optimise later! - // don't need to loop through if nothing changed! - // maybe look to add an 'autoupdate' to geometry? - this.updateBuffers(); - } - - reset() - { - this.unbind(); - } - - updateBuffers() - { - const geometry = this._activeGeometry; - const gl = this.gl; - - for (let i = 0; i < geometry.buffers.length; i++) - { - const buffer = geometry.buffers[i]; - - const glBuffer = buffer._glBuffers[this.CONTEXT_UID]; - - if (buffer._updateID !== glBuffer.updateID) - { - glBuffer.updateID = buffer._updateID; - - // TODO can cache this on buffer! maybe added a getter / setter? - const type = buffer.index ? gl.ELEMENT_ARRAY_BUFFER : gl.ARRAY_BUFFER; - const drawType = buffer.static ? gl.STATIC_DRAW : gl.DYNAMIC_DRAW; - - gl.bindBuffer(type, glBuffer.buffer); - - if (glBuffer.byteLength >= buffer.data.byteLength) - { - // offset is always zero for now! - gl.bufferSubData(type, 0, buffer.data); - } - else - { - gl.bufferData(type, buffer.data, drawType); - } - } - } - } - - checkCompatability(geometry, program) - { - // geometry must have at least all the attributes that the shader requires. - const geometryAttributes = geometry.attributes; - const shaderAttributes = program.attributeData; - - for (const j in shaderAttributes) - { - if (!geometryAttributes[j]) - { - throw new Error(`shader and geometry incompatible, geometry missing the "${j}" attribute`); - } - } - } - - /** - * Creates a Vao with the same structure as the geometry and stores it on the geometry. - * @private - * @param {PIXI.Geometry} geometry instance of geometry to to generate Vao for - */ - initGeometryVao(geometry, program) - { - this.checkCompatability(geometry, program); - - const gl = this.gl; - const CONTEXT_UID = this.CONTEXT_UID; - const buffers = geometry.buffers; - const attributes = geometry.attributes; - - const tempStride = {}; - const tempStart = {}; - - for (const j in buffers) - { - tempStride[j] = 0; - tempStart[j] = 0; - } - - for (const j in attributes) - { - if (!attributes[j].size && program.attributeData[j]) - { - attributes[j].size = program.attributeData[j].size; - } - - tempStride[attributes[j].buffer] += attributes[j].size * byteSizeMap[attributes[j].type]; - } - - for (const j in attributes) - { - const attribute = attributes[j]; - const attribSize = attribute.size; - - if (attribute.stride === undefined) - { - if (tempStride[attribute.buffer] === attribSize * byteSizeMap[attribute.type]) - { - attribute.stride = 0; - } - else - { - attribute.stride = tempStride[attribute.buffer]; - } - } - - if (attribute.start === undefined) - { - attribute.start = tempStart[attribute.buffer]; - - tempStart[attribute.buffer] += attribSize * byteSizeMap[attribute.type]; - } - } - - // first update - and create the buffers! - // only create a gl buffer if it actually gets - for (let i = 0; i < buffers.length; i++) - { - const buffer = buffers[i]; - - if (!buffer._glBuffers[CONTEXT_UID]) - { - buffer._glBuffers[CONTEXT_UID] = new GLBuffer(gl.createBuffer()); - } - } - - // TODO - maybe make this a data object? - // lets wait to see if we need to first! - const vao = gl.createVertexArray(); - - gl.bindVertexArray(vao); - - this.activateVao(geometry, program); - - gl.bindVertexArray(null); - - // add it to the cache! - geometry.glVertexArrayObjects[this.CONTEXT_UID][program.id] = vao; - - return vao; - } - - activateVao(geometry, program) - { - const gl = this.gl; - const CONTEXT_UID = this.CONTEXT_UID; - const buffers = geometry.buffers; - const attributes = geometry.attributes; - - if (geometry.indexBuffer) - { - // first update the index buffer if we have one.. - gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, geometry.indexBuffer._glBuffers[CONTEXT_UID].buffer); - } - - let lastBuffer = null; - - // add a new one! - for (const j in attributes) - { - const attribute = attributes[j]; - const buffer = buffers[attribute.buffer]; - const glBuffer = buffer._glBuffers[CONTEXT_UID]; - - if (program.attributeData[j]) - { - if (lastBuffer !== glBuffer) - { - gl.bindBuffer(gl.ARRAY_BUFFER, glBuffer.buffer); - - lastBuffer = glBuffer; - } - - const location = program.attributeData[j].location; - - // TODO introduce state again - // we can optimise this for older devices that have no VAOs - gl.enableVertexAttribArray(location); - - gl.vertexAttribPointer(location, - attribute.size, - attribute.type || gl.FLOAT, - attribute.normalized, - attribute.stride, - attribute.start); - - if (attribute.instance) - { - // TODO calculate instance count based of this... - if (this.hasInstance) - { - gl.vertexAttribDivisor(location, 1); - } - else - { - throw new Error('geometry error, GPU Instancing is not supported on this device'); - } - } - } - } - } - - draw(type, size, start, instanceCount) - { - const gl = this.gl; - const geometry = this._activeGeometry; - - // TODO.. this should not change so maybe cache the function? - - if (geometry.indexBuffer) - { - if (geometry.instanced) - { - /* eslint-disable max-len */ - gl.drawElementsInstanced(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2, instanceCount || 1); - /* eslint-enable max-len */ - } - else - { - gl.drawElements(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2); - } - } - else - if (geometry.instanced) - { - // TODO need a better way to calculate size.. - gl.drawArraysInstanced(type, start, size || geometry.getSize(), instanceCount || 1); - } - else - { - gl.drawArrays(type, start, size || geometry.getSize()); - } - - return this; - } - - unbind() - { - this.gl.bindVertexArray(null); - this._activeVao = null; - this._activeGeometry = null; - } -} diff --git a/packages/core/src/renderers/systems/geometry/setVertexAttribArrays.js b/packages/core/src/renderers/systems/geometry/setVertexAttribArrays.js deleted file mode 100644 index 23b6959..0000000 --- a/packages/core/src/renderers/systems/geometry/setVertexAttribArrays.js +++ /dev/null @@ -1,54 +0,0 @@ -// var GL_MAP = {}; - -/** - * @param gl {WebGLRenderingContext} The current WebGL context - * @param attribs {*} - * @param state {*} - */ -export default function setVertexAttribArrays(gl, attribs, state) -{ - let i; - - if (state) - { - const tempAttribState = state.tempAttribState; - const attribState = state.attribState; - - for (i = 0; i < tempAttribState.length; i++) - { - tempAttribState[i] = false; - } - - // set the new attribs - for (i = 0; i < attribs.length; i++) - { - tempAttribState[attribs[i].attribute.location] = true; - } - - for (i = 0; i < attribState.length; i++) - { - if (attribState[i] !== tempAttribState[i]) - { - attribState[i] = tempAttribState[i]; - - if (state.attribState[i]) - { - gl.enableVertexAttribArray(i); - } - else - { - gl.disableVertexAttribArray(i); - } - } - } - } - else - { - for (i = 0; i < attribs.length; i++) - { - const attrib = attribs[i]; - - gl.enableVertexAttribArray(attrib.attribute.location); - } - } -} diff --git a/packages/core/src/renderers/systems/index.js b/packages/core/src/renderers/systems/index.js deleted file mode 100644 index c843f03..0000000 --- a/packages/core/src/renderers/systems/index.js +++ /dev/null @@ -1,6 +0,0 @@ -/** - * Systems are individual components to the Renderer pipeline. - * @namespace PIXI.systems - */ -export { default as WebGLSystem } from './WebGLSystem'; -export { default as FilterSystem } from './filter/FilterSystem'; diff --git a/packages/core/src/renderers/systems/shader/GLProgram.js b/packages/core/src/renderers/systems/shader/GLProgram.js deleted file mode 100644 index 92980ca..0000000 --- a/packages/core/src/renderers/systems/shader/GLProgram.js +++ /dev/null @@ -1,43 +0,0 @@ -/** - * Helper class to create a webGL Program - * - * @class - * @private - */ -export default class GLProgram -{ - constructor(program, uniformData) - { - /** - * The shader program - * - * @member {WebGLProgram} - */ - this.program = program; - - /** - * holds the uniform data which contains uniform locations - * and current uniform values used for caching and preventing undeed GPU commands - * @type {Object} - */ - this.uniformData = uniformData; - - /** - * uniformGroups holds the various upload functions for the shader. Each uniform group - * and program have a unique upload function generated. - * @type {Object} - */ - this.uniformGroups = {}; - } - - /** - * Destroys this program - * TODO - */ - destroy() - { - this.uniformData = null; - this.uniformGroups = null; - this.program = null; - } -} diff --git a/packages/core/src/renderers/systems/shader/ShaderSystem.js b/packages/core/src/renderers/systems/shader/ShaderSystem.js deleted file mode 100644 index 6cf57b6..0000000 --- a/packages/core/src/renderers/systems/shader/ShaderSystem.js +++ /dev/null @@ -1,170 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; -import GLProgram from './GLProgram'; -import generateUniformsSync from '../../../shader/generateUniformsSync'; -import defaultValue from '../../../shader/defaultValue'; -import compileProgram from './shader/compileProgram'; - -let UID = 0; - -/** - * Helper class to create a webGL Texture - * - * @class - * @memberof PIXI - */ -export default class ShaderSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - A reference to the current renderer - */ - constructor(renderer) - { - super(renderer); - - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = null; - - this.shader = null; - this.program = null; - - this.id = UID++; - } - - contextChange(gl) - { - this.gl = gl; - } - - /** - * Changes the current shader to the one given in parameter - * - * @param {PIXI.Shader} shader - the new shader - * @param {boolean} dontSync - false if the shader should automatically sync its uniforms. - * @returns {PIXI.glCore.glProgram} the glProgram that belongs to the shader. - */ - bind(shader, dontSync) - { - shader.uniforms.globals = this.renderer.globalUniforms; - - const program = shader.program; - const glProgram = program.glPrograms[this.renderer.CONTEXT_UID] || this.generateShader(shader); - - this.shader = shader; - - // TODO - some current pixi plugins bypass this.. so it not safe to use yet.. - if (this.program !== program) - { - this.program = program; - this.gl.useProgram(glProgram.program); - } - - if (!dontSync) - { - this.syncUniformGroup(shader.uniformGroup); - } - - return glProgram; - } - - /** - * Uploads the uniforms values to the currently bound shader. - * - * @param {object} uniforms - the uniforms valiues that be applied to the current shader - */ - setUniforms(uniforms) - { - const shader = this.shader.program; - const glProgram = shader.glPrograms[this.renderer.CONTEXT_UID]; - - shader.syncUniforms(glProgram.uniformData, uniforms, this.renderer); - } - - syncUniformGroup(group) - { - const glProgram = this.getglProgram(); - - if (!group.static || group.dirtyId !== glProgram.uniformGroups[group.id]) - { - glProgram.uniformGroups[group.id] = group.dirtyId; - const syncFunc = group.syncUniforms[this.shader.program.id] || this.createSyncGroups(group); - - syncFunc(glProgram.uniformData, group.uniforms, this.renderer); - } - } - - createSyncGroups(group) - { - group.syncUniforms[this.shader.program.id] = generateUniformsSync(group, this.shader.program.uniformData); - - return group.syncUniforms[this.shader.program.id]; - } - - /** - * Returns the underlying GLShade rof the currently bound shader. - * This can be handy for when you to have a little more control over the setting of your uniforms. - * - * @return {PIXI.glCore.Shader} the glProgram for the currently bound Shader for this context - */ - getglProgram() - { - if (this.shader) - { - return this.shader.program.glPrograms[this.renderer.CONTEXT_UID]; - } - - return null; - } - - /** - * Generates a glProgram verion of the Shader provided. - * - * @private - * @param {PIXI.Shader} shader the shader that the glProgram will be based on. - * @return {PIXI.glCore.glProgram} A shiney new glProgram - */ - generateShader(shader) - { - const gl = this.gl; - - const program = shader.program; - - const attribMap = {}; - - for (const i in program.attributeData) - { - attribMap[i] = program.attributeData[i].location; - } - - const shaderProgram = compileProgram(gl, program.vertexSrc, program.fragmentSrc, attribMap); - const uniformData = {}; - - for (const i in program.uniformData) - { - const data = program.uniformData[i]; - - uniformData[i] = { - location: gl.getUniformLocation(shaderProgram, i), - value: defaultValue(data.type, data.size), - }; - } - - const glProgram = new GLProgram(shaderProgram, uniformData); - - program.glPrograms[this.renderer.CONTEXT_UID] = glProgram; - - return glProgram; - } - - /** - * Destroys this System and removes all its textures - */ - destroy() - { - // TODO implement destroy method for ShaderSystem - this.destroyed = true; - } -} diff --git a/packages/core/src/renderers/systems/shader/shader/compileProgram.js b/packages/core/src/renderers/systems/shader/shader/compileProgram.js deleted file mode 100644 index 8c6ccb9..0000000 --- a/packages/core/src/renderers/systems/shader/shader/compileProgram.js +++ /dev/null @@ -1,78 +0,0 @@ -/** - * @method compileProgram - * @memberof PIXI.glCore.shader - * @param gl {WebGLRenderingContext} The current WebGL context {WebGLProgram} - * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. - * @param fragmentSrc {string|string[]} The fragment shader source as an array of strings. - * @param attributeLocations {Object} An attribute location map that lets you manually set the attribute locations - * @return {WebGLProgram} the shader program - */ -export default function compileProgram(gl, vertexSrc, fragmentSrc, attributeLocations) -{ - const glVertShader = compileShader(gl, gl.VERTEX_SHADER, vertexSrc); - const glFragShader = compileShader(gl, gl.FRAGMENT_SHADER, fragmentSrc); - - let program = gl.createProgram(); - - gl.attachShader(program, glVertShader); - gl.attachShader(program, glFragShader); - - // optionally, set the attributes manually for the program rather than letting WebGL decide.. - if (attributeLocations) - { - for (const i in attributeLocations) - { - gl.bindAttribLocation(program, attributeLocations[i], i); - } - } - - gl.linkProgram(program); - - // if linking fails, then log and cleanup - if (!gl.getProgramParameter(program, gl.LINK_STATUS)) - { - console.error('Pixi.js Error: Could not initialize shader.'); - console.error('gl.VALIDATE_STATUS', gl.getProgramParameter(program, gl.VALIDATE_STATUS)); - console.error('gl.getError()', gl.getError()); - - // if there is a program info log, log it - if (gl.getProgramInfoLog(program) !== '') - { - console.warn('Pixi.js Warning: gl.getProgramInfoLog()', gl.getProgramInfoLog(program)); - } - - gl.deleteProgram(program); - program = null; - } - - // clean up some shaders - gl.deleteShader(glVertShader); - gl.deleteShader(glFragShader); - - return program; -} - -/** - * @private - * @param gl {WebGLRenderingContext} The current WebGL context {WebGLProgram} - * @param type {Number} the type, can be either VERTEX_SHADER or FRAGMENT_SHADER - * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. - * @return {WebGLShader} the shader - */ -function compileShader(gl, type, src) -{ - const shader = gl.createShader(type); - - gl.shaderSource(shader, src); - gl.compileShader(shader); - - if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) - { - console.warn(src); - console.error(gl.getShaderInfoLog(shader)); - - return null; - } - - return shader; -} diff --git a/packages/core/src/renderers/systems/shader/shader/defaultValue.js b/packages/core/src/renderers/systems/shader/shader/defaultValue.js deleted file mode 100644 index 97290bd..0000000 --- a/packages/core/src/renderers/systems/shader/shader/defaultValue.js +++ /dev/null @@ -1,79 +0,0 @@ -/** - * @method defaultValue - * @memberof PIXI.glCore.shader - * @param type {String} Type of value - * @param size {Number} - */ -export default function defaultValue(type, size) -{ - switch (type) - { - case 'float': - return 0; - - case 'vec2': - return new Float32Array(2 * size); - - case 'vec3': - return new Float32Array(3 * size); - - case 'vec4': - return new Float32Array(4 * size); - - case 'int': - case 'sampler2D': - case 'sampler2DArray': - return 0; - - case 'ivec2': - return new Int32Array(2 * size); - - case 'ivec3': - return new Int32Array(3 * size); - - case 'ivec4': - return new Int32Array(4 * size); - - case 'bool': - return false; - - case 'bvec2': - - return booleanArray(2 * size); - - case 'bvec3': - return booleanArray(3 * size); - - case 'bvec4': - return booleanArray(4 * size); - - case 'mat2': - return new Float32Array([1, 0, - 0, 1]); - - case 'mat3': - return new Float32Array([1, 0, 0, - 0, 1, 0, - 0, 0, 1]); - - case 'mat4': - return new Float32Array([1, 0, 0, 0, - 0, 1, 0, 0, - 0, 0, 1, 0, - 0, 0, 0, 1]); - } - - return null; -} - -function booleanArray(size) -{ - const array = new Array(size); - - for (let i = 0; i < array.length; i++) - { - array[i] = false; - } - - return array; -} diff --git a/packages/core/src/renderers/systems/shader/shader/index.js b/packages/core/src/renderers/systems/shader/shader/index.js deleted file mode 100644 index ba7a317..0000000 --- a/packages/core/src/renderers/systems/shader/shader/index.js +++ /dev/null @@ -1,8 +0,0 @@ -/** - * @namespace PIXI.glCore.shader - */ -export { default as compileProgram } from './compileProgram'; -export { default as defaultValue } from './defaultValue'; -export { default as setPrecision } from './setPrecision'; -export { default as mapSize } from './mapSize'; -export { default as mapType } from './mapType'; diff --git a/packages/core/src/renderers/systems/shader/shader/mapSize.js b/packages/core/src/renderers/systems/shader/shader/mapSize.js deleted file mode 100644 index b681554..0000000 --- a/packages/core/src/renderers/systems/shader/shader/mapSize.js +++ /dev/null @@ -1,33 +0,0 @@ -const GLSL_TO_SIZE = { - float: 1, - vec2: 2, - vec3: 3, - vec4: 4, - - int: 1, - ivec2: 2, - ivec3: 3, - ivec4: 4, - - bool: 1, - bvec2: 2, - bvec3: 3, - bvec4: 4, - - mat2: 4, - mat3: 9, - mat4: 16, - - sampler2D: 1, -}; - -/** - * @method mapSize - * @memberof PIXI.glCore.shader - * @param type {String} - * @return {Number} - */ -export default function mapSize(type) -{ - return GLSL_TO_SIZE[type]; -} diff --git a/packages/core/src/renderers/systems/shader/shader/mapType.js b/packages/core/src/renderers/systems/shader/shader/mapType.js deleted file mode 100644 index d288b7c..0000000 --- a/packages/core/src/renderers/systems/shader/shader/mapType.js +++ /dev/null @@ -1,45 +0,0 @@ -let GL_TABLE = null; - -const GL_TO_GLSL_TYPES = { - FLOAT: 'float', - FLOAT_VEC2: 'vec2', - FLOAT_VEC3: 'vec3', - FLOAT_VEC4: 'vec4', - - INT: 'int', - INT_VEC2: 'ivec2', - INT_VEC3: 'ivec3', - INT_VEC4: 'ivec4', - - BOOL: 'bool', - BOOL_VEC2: 'bvec2', - BOOL_VEC3: 'bvec3', - BOOL_VEC4: 'bvec4', - - FLOAT_MAT2: 'mat2', - FLOAT_MAT3: 'mat3', - FLOAT_MAT4: 'mat4', - - SAMPLER_2D: 'sampler2D', - SAMPLER_CUBE: 'samplerCube', - SAMPLER_2D_ARRAY: 'sampler2DArray', -}; - -export default function mapType(gl, type) -{ - if (!GL_TABLE) - { - const typeNames = Object.keys(GL_TO_GLSL_TYPES); - - GL_TABLE = {}; - - for (let i = 0; i < typeNames.length; ++i) - { - const tn = typeNames[i]; - - GL_TABLE[gl[tn]] = GL_TO_GLSL_TYPES[tn]; - } - } - - return GL_TABLE[type]; -} diff --git a/packages/core/src/renderers/systems/shader/shader/setPrecision.js b/packages/core/src/renderers/systems/shader/shader/setPrecision.js deleted file mode 100644 index e2c1f34..0000000 --- a/packages/core/src/renderers/systems/shader/shader/setPrecision.js +++ /dev/null @@ -1,18 +0,0 @@ -/** - * Sets the float precision on the shader. If the precision is already present this function will do nothing - * @param {string} src the shader source - * @param {string} precision The float precision of the shader. Options are 'lowp', 'mediump' or 'highp'. - * - * @return {string} modified shader source - */ -export default function setPrecision(src, precision) -{ - src = src.trim(); - - if (src.substring(0, 9) !== 'precision' && src.substring(0, 1) !== '#') - { - return `precision ${precision} float;\n${src}`; - } - - return src; -} diff --git a/packages/core/src/renderers/systems/textures/GLTexture.js b/packages/core/src/renderers/systems/textures/GLTexture.js deleted file mode 100644 index c3fb8dc..0000000 --- a/packages/core/src/renderers/systems/textures/GLTexture.js +++ /dev/null @@ -1,33 +0,0 @@ -export default class GLTexture -{ - constructor(texture) - { - /** - * The WebGL texture - * - * @member {WebGLTexture} - */ - this.texture = texture; - - this.width = -1; - this.height = -1; - - /** - * Texture contents dirty flag - * @member {number} - */ - this.dirtyId = -1; - - /** - * Texture style dirty flag - * @type {number} - */ - this.dirtyStyleId = -1; - - /** - * Whether mip levels has to be generated - * @type {boolean} - */ - this.mipmap = false; - } -} diff --git a/packages/core/src/renderers/systems/textures/TextureGCSystem.js b/packages/core/src/renderers/systems/textures/TextureGCSystem.js deleted file mode 100644 index b089938..0000000 --- a/packages/core/src/renderers/systems/textures/TextureGCSystem.js +++ /dev/null @@ -1,110 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; -import { GC_MODES } from '@pixi/constants'; -import { settings } from '@pixi/settings'; - -/** - * TextureGarbageCollector. This class manages the GPU and ensures that it does not get clogged - * up with textures that are no longer being used. - * - * @class - * @memberof PIXI - */ -export default class TextureGCSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this.count = 0; - this.checkCount = 0; - this.maxIdle = settings.GC_MAX_IDLE; - this.checkCountMax = settings.GC_MAX_CHECK_COUNT; - this.mode = settings.GC_MODE; - } - - /** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ - postrender() - { - this.count++; - - if (this.mode === GC_MODES.MANUAL) - { - return; - } - - this.checkCount++; - - if (this.checkCount > this.checkCountMax) - { - this.checkCount = 0; - - this.run(); - } - } - - /** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ - run() - { - const tm = this.renderer.texture; - const managedTextures = tm.managedTextures; - let wasRemoved = false; - - for (let i = 0; i < managedTextures.length; i++) - { - const texture = managedTextures[i]; - - // only supports non generated textures at the moment! - if (!texture.frameBuffer && this.count - texture.touched > this.maxIdle) - { - tm.destroyTexture(texture, true); - managedTextures[i] = null; - wasRemoved = true; - } - } - - if (wasRemoved) - { - let j = 0; - - for (let i = 0; i < managedTextures.length; i++) - { - if (managedTextures[i] !== null) - { - managedTextures[j++] = managedTextures[i]; - } - } - - managedTextures.length = j; - } - } - - /** - * Removes all the textures within the specified displayObject and its children from the GPU - * - * @param {PIXI.DisplayObject} displayObject - the displayObject to remove the textures from. - */ - unload(displayObject) - { - const tm = this.renderer.textureSystem; - - // only destroy non generated textures - if (displayObject._texture && displayObject._texture._glRenderTargets) - { - tm.destroyTexture(displayObject._texture); - } - - for (let i = displayObject.children.length - 1; i >= 0; i--) - { - this.unload(displayObject.children[i]); - } - } -} diff --git a/packages/core/src/renderers/systems/textures/TextureSystem.js b/packages/core/src/renderers/systems/textures/TextureSystem.js deleted file mode 100644 index ccc5de9..0000000 --- a/packages/core/src/renderers/systems/textures/TextureSystem.js +++ /dev/null @@ -1,281 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; -import GLTexture from './GLTexture'; -import { removeItems } from '@pixi/utils'; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class TextureSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - // TODO set to max textures... - this.boundTextures = [ - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - ]; - - this.currentLocation = -1; - - this.managedTextures = []; - } - - /** - * Sets up the renderer context and necessary buffers. - * - * @private - */ - contextChange() - { - const gl = this.gl = this.renderer.gl; - - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - - // TODO move this.. to a nice make empty textures class.. - this.emptyTextures = {}; - - const emptyTexture2D = new GLTexture(gl.createTexture()); - - gl.bindTexture(gl.TEXTURE_2D, emptyTexture2D.texture); - gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array(4)); - - this.emptyTextures[gl.TEXTURE_2D] = emptyTexture2D; - this.emptyTextures[gl.TEXTURE_CUBE_MAP] = new GLTexture(gl.createTexture()); - - gl.bindTexture(gl.TEXTURE_CUBE_MAP, this.emptyTextures[gl.TEXTURE_CUBE_MAP].texture); - - let i; - - for (i = 0; i < 6; i++) - { - gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, null); - } - - gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_MAG_FILTER, gl.LINEAR); - gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_MIN_FILTER, gl.LINEAR); - - for (i = 0; i < this.boundTextures.length; i++) - { - this.bind(null, i); - } - } - - bind(texture, location) - { - const gl = this.gl; - - location = location || 0; - - if (this.currentLocation !== location) - { - this.currentLocation = location; - gl.activeTexture(gl.TEXTURE0 + location); - } - - if (texture) - { - texture = texture.baseTexture || texture; - - if (texture.valid) - { - texture.touched = this.renderer.textureGC.count; - - const glTexture = texture._glTextures[this.CONTEXT_UID] || this.initTexture(texture); - - gl.bindTexture(texture.target, glTexture.texture); - - if (glTexture.dirtyId !== texture.dirtyId) - { - this.updateTexture(texture); - } - - this.boundTextures[location] = texture; - } - } - else - { - gl.bindTexture(gl.TEXTURE_2D, this.emptyTextures[gl.TEXTURE_2D].texture); - this.boundTextures[location] = null; - } - } - - unbind(texture) - { - const gl = this.gl; - - for (let i = 0; i < this.boundTextures.length; i++) - { - if (this.boundTextures[i] === texture) - { - if (this.currentLocation !== i) - { - gl.activeTexture(gl.TEXTURE0 + i); - this.currentLocation = i; - } - - gl.bindTexture(gl.TEXTURE_2D, this.emptyTextures[texture.target].texture); - this.boundTextures[i] = null; - } - } - } - - initTexture(texture) - { - const glTexture = new GLTexture(this.gl.createTexture()); - - // guarentee an update.. - glTexture.dirtyId = -1; - - texture._glTextures[this.CONTEXT_UID] = glTexture; - - this.managedTextures.push(texture); - texture.on('dispose', this.destroyTexture, this); - - return glTexture; - } - - updateTexture(texture) - { - const glTexture = texture._glTextures[this.CONTEXT_UID]; - const renderer = this.renderer; - - if (texture.resource && texture.resource.upload(renderer, texture, glTexture)) - { - // texture is uploaded, dont do anything! - } - else - { - // default, renderTexture-like logic - const width = texture.realWidth; - const height = texture.realHeight; - const gl = renderer.gl; - - if (glTexture.width !== width - || glTexture.height !== height - || glTexture.dirtyId < 0) - { - glTexture.width = width; - glTexture.height = height; - - gl.texImage2D(texture.target, 0, - texture.format, - width, - height, - 0, - texture.format, - texture.type, - null); - } - } - - // lets only update what changes.. - if (texture.dirtyStyleId !== glTexture.dirtyStyleId) - { - this.updateTextureStyle(texture); - } - glTexture.dirtyId = texture.dirtyId; - } - - /** - * Deletes the texture from WebGL - * - * @param {PIXI.BaseTexture|PIXI.Texture} texture - the texture to destroy - * @param {boolean} [skipRemove=false] - Whether to skip removing the texture from the TextureManager. - */ - destroyTexture(texture, skipRemove) - { - const gl = this.gl; - - texture = texture.baseTexture || texture; - - if (texture._glTextures[this.renderer.CONTEXT_UID]) - { - this.unbind(texture); - - gl.deleteTexture(texture._glTextures[this.renderer.CONTEXT_UID].texture); - texture.off('dispose', this.destroyTexture, this); - - delete texture._glTextures[this.renderer.CONTEXT_UID]; - - if (!skipRemove) - { - const i = this.managedTextures.indexOf(texture); - - if (i !== -1) - { - removeItems(this.managedTextures, i, 1); - } - } - } - } - - updateTextureStyle(texture) - { - const glTexture = texture._glTextures[this.CONTEXT_UID]; - - glTexture.mipmap = texture.mipmap && texture.isPowerOfTwo; - if (!glTexture) - { - return; - } - - if (texture.resource && texture.resource.style(this.renderer, texture, glTexture)) - { - // style is set, dont do anything! - } - else - { - this.setStyle(texture, glTexture); - } - - glTexture.dirtyStyleId = texture.dirtyStyleId; - } - - setStyle(texture, glTexture) - { - const gl = this.gl; - - if (glTexture.mipmap) - { - gl.generateMipmap(texture.target); - } - - gl.texParameteri(texture.target, gl.TEXTURE_WRAP_S, texture.wrapMode); - gl.texParameteri(texture.target, gl.TEXTURE_WRAP_T, texture.wrapMode); - - if (glTexture.mipmap) - { - /* eslint-disable max-len */ - gl.texParameteri(texture.target, gl.TEXTURE_MIN_FILTER, texture.scaleMode ? gl.LINEAR_MIPMAP_LINEAR : gl.NEAREST_MIPMAP_NEAREST); - /* eslint-disable max-len */ - } - else - { - gl.texParameteri(texture.target, gl.TEXTURE_MIN_FILTER, texture.scaleMode ? gl.LINEAR : gl.NEAREST); - } - - gl.texParameteri(texture.target, gl.TEXTURE_MAG_FILTER, texture.scaleMode ? gl.LINEAR : gl.NEAREST); - } -} diff --git a/packages/core/src/renderers/utils/ObjectRenderer.js b/packages/core/src/renderers/utils/ObjectRenderer.js deleted file mode 100644 index f67a273..0000000 --- a/packages/core/src/renderers/utils/ObjectRenderer.js +++ /dev/null @@ -1,48 +0,0 @@ -import WebGLSystem from '../systems/WebGLSystem'; - -/** - * Base for a common object renderer that can be used as a system renderer plugin. - * - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class ObjectRenderer extends WebGLSystem -{ - /** - * Starts the renderer and sets the shader - * - */ - start() - { - // set the shader.. - } - - /** - * Stops the renderer - * - */ - stop() - { - this.flush(); - } - - /** - * Stub method for rendering content and emptying the current batch. - * - */ - flush() - { - // flush! - } - - /** - * Renders an object - * - * @param {PIXI.DisplayObject} object - The object to render. - */ - render(object) // eslint-disable-line no-unused-vars - { - // render the object - } -} diff --git a/packages/core/src/renderers/utils/Quad.js b/packages/core/src/renderers/utils/Quad.js deleted file mode 100644 index 5c1517b..0000000 --- a/packages/core/src/renderers/utils/Quad.js +++ /dev/null @@ -1,26 +0,0 @@ -import Geometry from '../../geometry/Geometry'; - -/** - * Helper class to create a quad - * - * @class - * @memberof PIXI - */ -export default class Quad extends Geometry -{ - /** - * just a quad - */ - constructor() - { - super(); - - this.addAttribute('aVertexPosition', [ - 0, 0, - 1, 0, - 1, 1, - 0, 1, - ]) - .addIndex([0, 1, 3, 2]); - } -} diff --git a/packages/core/src/renderers/utils/QuadUv.js b/packages/core/src/renderers/utils/QuadUv.js deleted file mode 100644 index 97bda89..0000000 --- a/packages/core/src/renderers/utils/QuadUv.js +++ /dev/null @@ -1,103 +0,0 @@ -import Geometry from '../../geometry/Geometry'; -import Buffer from '../../geometry/Buffer'; - -/** - * Helper class to create a quad with uvs like in v4 - * - * @class - * @memberof PIXI - */ -export default class QuadUv extends Geometry -{ - constructor() - { - super(); - - /** - * An array of vertices - * - * @member {Float32Array} - */ - this.vertices = new Float32Array([ - -1, -1, - 1, -1, - 1, 1, - -1, 1, - ]); - - /** - * The Uvs of the quad - * - * @member {Float32Array} - */ - this.uvs = new Float32Array([ - 0, 0, - 1, 0, - 1, 1, - 0, 1, - ]); - - this.vertexBuffer = new Buffer(this.vertices); - this.uvBuffer = new Buffer(this.uvs); - - this.addAttribute('aVertexPosition', this.vertexBuffer) - .addAttribute('aTextureCoord', this.uvBuffer) - .addIndex([0, 1, 2, 0, 2, 3]); - } - - /** - * Maps two Rectangle to the quad. - * - * @param {PIXI.Rectangle} targetTextureFrame - the first rectangle - * @param {PIXI.Rectangle} destinationFrame - the second rectangle - * @return {PIXI.Quad} Returns itself. - */ - map(targetTextureFrame, destinationFrame) - { - let x = 0; // destinationFrame.x / targetTextureFrame.width; - let y = 0; // destinationFrame.y / targetTextureFrame.height; - - this.uvs[0] = x; - this.uvs[1] = y; - - this.uvs[2] = x + (destinationFrame.width / targetTextureFrame.width); - this.uvs[3] = y; - - this.uvs[4] = x + (destinationFrame.width / targetTextureFrame.width); - this.uvs[5] = y + (destinationFrame.height / targetTextureFrame.height); - - this.uvs[6] = x; - this.uvs[7] = y + (destinationFrame.height / targetTextureFrame.height); - - x = destinationFrame.x; - y = destinationFrame.y; - - this.vertices[0] = x; - this.vertices[1] = y; - - this.vertices[2] = x + destinationFrame.width; - this.vertices[3] = y; - - this.vertices[4] = x + destinationFrame.width; - this.vertices[5] = y + destinationFrame.height; - - this.vertices[6] = x; - this.vertices[7] = y + destinationFrame.height; - - this.invalidate(); - - return this; - } - - /** - * legacy upload method, just marks buffers dirty - * @returns {PIXI.QuadUv} Returns itself. - */ - invalidate() - { - this.vertexBuffer._updateID++; - this.uvBuffer._updateID++; - - return this; - } -} diff --git a/packages/core/src/renderers/utils/checkMaxIfStatmentsInShader.js b/packages/core/src/renderers/utils/checkMaxIfStatmentsInShader.js deleted file mode 100644 index 8047c96..0000000 --- a/packages/core/src/renderers/utils/checkMaxIfStatmentsInShader.js +++ /dev/null @@ -1,58 +0,0 @@ -const fragTemplate = [ - 'precision mediump float;', - 'void main(void){', - 'float test = 0.1;', - '%forloop%', - 'gl_FragColor = vec4(0.0);', - '}', -].join('\n'); - -export default function checkMaxIfStatmentsInShader(maxIfs, gl) -{ - if (maxIfs === 0) - { - throw new Error('Invalid value of `0` passed to `checkMaxIfStatementsInShader`'); - } - - const shader = gl.createShader(gl.FRAGMENT_SHADER); - - while (true) // eslint-disable-line no-constant-condition - { - const fragmentSrc = fragTemplate.replace(/%forloop%/gi, generateIfTestSrc(maxIfs)); - - gl.shaderSource(shader, fragmentSrc); - gl.compileShader(shader); - - if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) - { - maxIfs = (maxIfs / 2) | 0; - } - else - { - // valid! - break; - } - } - - return maxIfs; -} - -function generateIfTestSrc(maxIfs) -{ - let src = ''; - - for (let i = 0; i < maxIfs; ++i) - { - if (i > 0) - { - src += '\nelse '; - } - - if (i < maxIfs - 1) - { - src += `if(test == ${i}.0){}`; - } - } - - return src; -} diff --git a/packages/core/src/renderers/utils/mapWebGLBlendModesToPixi.js b/packages/core/src/renderers/utils/mapWebGLBlendModesToPixi.js deleted file mode 100644 index 10ffe4d..0000000 --- a/packages/core/src/renderers/utils/mapWebGLBlendModesToPixi.js +++ /dev/null @@ -1,47 +0,0 @@ -import { BLEND_MODES } from '@pixi/constants'; - -/** - * Maps gl blend combinations to WebGL. - * - * @memberof PIXI - * @function mapWebGLBlendModesToPixi - * @private - * @param {WebGLRenderingContext} gl - The rendering context. - * @param {string[]} [array=[]] - The array to output into. - * @return {string[]} Mapped modes. - */ -export default function mapWebGLBlendModesToPixi(gl, array = []) -{ - // TODO - premultiply alpha would be different. - // add a boolean for that! - array[BLEND_MODES.NORMAL] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.ADD] = [gl.ONE, gl.DST_ALPHA]; - array[BLEND_MODES.MULTIPLY] = [gl.DST_COLOR, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.SCREEN] = [gl.ONE, gl.ONE_MINUS_SRC_COLOR]; - array[BLEND_MODES.OVERLAY] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.DARKEN] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.LIGHTEN] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.COLOR_DODGE] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.COLOR_BURN] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.HARD_LIGHT] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.SOFT_LIGHT] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.DIFFERENCE] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.EXCLUSION] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.HUE] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.SATURATION] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.COLOR] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.LUMINOSITY] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.NONE] = [0, 0]; - - // not-premultiplied blend modes - array[BLEND_MODES.NORMAL_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.ADD_NPM] = [gl.SRC_ALPHA, gl.DST_ALPHA, gl.ONE, gl.DST_ALPHA]; - array[BLEND_MODES.SCREEN_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_COLOR, gl.ONE, gl.ONE_MINUS_SRC_COLOR]; - - // not-premultiplied blend modes - array[BLEND_MODES.NORMAL_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.ADD_NPM] = [gl.SRC_ALPHA, gl.DST_ALPHA, gl.ONE, gl.DST_ALPHA]; - array[BLEND_MODES.SCREEN_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_COLOR, gl.ONE, gl.ONE_MINUS_SRC_COLOR]; - - return array; -} diff --git a/packages/core/src/shader/GLProgram.js b/packages/core/src/shader/GLProgram.js new file mode 100644 index 0000000..92980ca --- /dev/null +++ b/packages/core/src/shader/GLProgram.js @@ -0,0 +1,43 @@ +/** + * Helper class to create a webGL Program + * + * @class + * @private + */ +export default class GLProgram +{ + constructor(program, uniformData) + { + /** + * The shader program + * + * @member {WebGLProgram} + */ + this.program = program; + + /** + * holds the uniform data which contains uniform locations + * and current uniform values used for caching and preventing undeed GPU commands + * @type {Object} + */ + this.uniformData = uniformData; + + /** + * uniformGroups holds the various upload functions for the shader. Each uniform group + * and program have a unique upload function generated. + * @type {Object} + */ + this.uniformGroups = {}; + } + + /** + * Destroys this program + * TODO + */ + destroy() + { + this.uniformData = null; + this.uniformGroups = null; + this.program = null; + } +} diff --git a/packages/core/src/shader/Program.js b/packages/core/src/shader/Program.js index 4f9971e..4b4007a 100644 --- a/packages/core/src/shader/Program.js +++ b/packages/core/src/shader/Program.js @@ -1,6 +1,11 @@ -import * as shaderUtils from '../renderers/systems/shader/shader'; +// import * as from '../systems/shader/shader'; +import { setPrecision, + defaultValue, + compileProgram, + mapSize, + mapType, + getTestContext } from './utils'; import { ProgramCache } from '@pixi/utils'; -import getTestContext from './getTestContext'; import defaultFragment from './defaultProgram.frag'; import defaultVertex from './defaultProgram.vert'; import { settings } from '@pixi/settings'; @@ -34,8 +39,8 @@ */ this.fragmentSrc = fragmentSrc || Program.defaultFragmentSrc; - this.vertexSrc = shaderUtils.setPrecision(this.vertexSrc, settings.PRECISION_VERTEX); - this.fragmentSrc = shaderUtils.setPrecision(this.fragmentSrc, settings.PRECISION_FRAGMENT); + this.vertexSrc = setPrecision(this.vertexSrc, settings.PRECISION_VERTEX); + this.fragmentSrc = setPrecision(this.fragmentSrc, settings.PRECISION_FRAGMENT); // currently this does not extract structs only default types this.extractData(this.vertexSrc, this.fragmentSrc); @@ -62,7 +67,7 @@ if (gl) { - const program = shaderUtils.compileProgram(gl, vertexSrc, fragmentSrc); + const program = compileProgram(gl, vertexSrc, fragmentSrc); this.attributeData = this.getAttributeData(program, gl); this.uniformData = this.getUniformData(program, gl); @@ -95,13 +100,13 @@ for (let i = 0; i < totalAttributes; i++) { const attribData = gl.getActiveAttrib(program, i); - const type = shaderUtils.mapType(gl, attribData.type); + const type = mapType(gl, attribData.type); /*eslint-disable */ const data = { type: type, name: attribData.name, - size: shaderUtils.mapSize(type), + size: mapSize(type), location: 0, }; /* eslint-enable */ @@ -145,14 +150,14 @@ const name = uniformData.name.replace(/\[.*?\]/, ''); const isArray = uniformData.name.match(/\[.*?\]/, ''); - const type = shaderUtils.mapType(gl, uniformData.type); + const type = mapType(gl, uniformData.type); /*eslint-disable */ uniforms[name] = { type: type, size: uniformData.size, isArray:isArray, - value: shaderUtils.defaultValue(type, uniformData.size), + value: defaultValue(type, uniformData.size), }; /* eslint-enable */ } diff --git a/packages/core/src/shader/ShaderSystem.js b/packages/core/src/shader/ShaderSystem.js new file mode 100644 index 0000000..40b8af8 --- /dev/null +++ b/packages/core/src/shader/ShaderSystem.js @@ -0,0 +1,171 @@ +import System from '../System'; +import GLProgram from './GLProgram'; +import { generateUniformsSync, + defaultValue, + compileProgram } from './utils'; + +let UID = 0; + +/** + * Helper class to create a webGL Texture + * + * @class + * @memberof PIXI.systems + * @extends PIXI.System + */ +export default class ShaderSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - A reference to the current renderer + */ + constructor(renderer) + { + super(renderer); + + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = null; + + this.shader = null; + this.program = null; + + this.id = UID++; + } + + contextChange(gl) + { + this.gl = gl; + } + + /** + * Changes the current shader to the one given in parameter + * + * @param {PIXI.Shader} shader - the new shader + * @param {boolean} dontSync - false if the shader should automatically sync its uniforms. + * @returns {PIXI.glCore.glProgram} the glProgram that belongs to the shader. + */ + bind(shader, dontSync) + { + shader.uniforms.globals = this.renderer.globalUniforms; + + const program = shader.program; + const glProgram = program.glPrograms[this.renderer.CONTEXT_UID] || this.generateShader(shader); + + this.shader = shader; + + // TODO - some current pixi plugins bypass this.. so it not safe to use yet.. + if (this.program !== program) + { + this.program = program; + this.gl.useProgram(glProgram.program); + } + + if (!dontSync) + { + this.syncUniformGroup(shader.uniformGroup); + } + + return glProgram; + } + + /** + * Uploads the uniforms values to the currently bound shader. + * + * @param {object} uniforms - the uniforms valiues that be applied to the current shader + */ + setUniforms(uniforms) + { + const shader = this.shader.program; + const glProgram = shader.glPrograms[this.renderer.CONTEXT_UID]; + + shader.syncUniforms(glProgram.uniformData, uniforms, this.renderer); + } + + syncUniformGroup(group) + { + const glProgram = this.getglProgram(); + + if (!group.static || group.dirtyId !== glProgram.uniformGroups[group.id]) + { + glProgram.uniformGroups[group.id] = group.dirtyId; + const syncFunc = group.syncUniforms[this.shader.program.id] || this.createSyncGroups(group); + + syncFunc(glProgram.uniformData, group.uniforms, this.renderer); + } + } + + createSyncGroups(group) + { + group.syncUniforms[this.shader.program.id] = generateUniformsSync(group, this.shader.program.uniformData); + + return group.syncUniforms[this.shader.program.id]; + } + + /** + * Returns the underlying GLShade rof the currently bound shader. + * This can be handy for when you to have a little more control over the setting of your uniforms. + * + * @return {PIXI.glCore.Shader} the glProgram for the currently bound Shader for this context + */ + getglProgram() + { + if (this.shader) + { + return this.shader.program.glPrograms[this.renderer.CONTEXT_UID]; + } + + return null; + } + + /** + * Generates a glProgram verion of the Shader provided. + * + * @private + * @param {PIXI.Shader} shader the shader that the glProgram will be based on. + * @return {PIXI.glCore.glProgram} A shiney new glProgram + */ + generateShader(shader) + { + const gl = this.gl; + + const program = shader.program; + + const attribMap = {}; + + for (const i in program.attributeData) + { + attribMap[i] = program.attributeData[i].location; + } + + const shaderProgram = compileProgram(gl, program.vertexSrc, program.fragmentSrc, attribMap); + const uniformData = {}; + + for (const i in program.uniformData) + { + const data = program.uniformData[i]; + + uniformData[i] = { + location: gl.getUniformLocation(shaderProgram, i), + value: defaultValue(data.type, data.size), + }; + } + + const glProgram = new GLProgram(shaderProgram, uniformData); + + program.glPrograms[this.renderer.CONTEXT_UID] = glProgram; + + return glProgram; + } + + /** + * Destroys this System and removes all its textures + */ + destroy() + { + // TODO implement destroy method for ShaderSystem + this.destroyed = true; + } +} diff --git a/packages/core/src/shader/defaultValue.js b/packages/core/src/shader/defaultValue.js deleted file mode 100644 index 847a069..0000000 --- a/packages/core/src/shader/defaultValue.js +++ /dev/null @@ -1,78 +0,0 @@ -function booleanArray(size) -{ - const array = new Array(size); - - for (let i = 0; i < array.length; i++) - { - array[i] = false; - } - - return array; -} - -/** - * @class - * @memberof PIXI.glCore.shader - * @param type {String} Type of value - * @param size {Number} - */ -export default function defaultValue(type, size) -{ - switch (type) - { - case 'float': - return 0; - - case 'vec2': - return new Float32Array(2 * size); - - case 'vec3': - return new Float32Array(3 * size); - - case 'vec4': - return new Float32Array(4 * size); - - case 'int': - case 'sampler2D': - return 0; - - case 'ivec2': - return new Int32Array(2 * size); - - case 'ivec3': - return new Int32Array(3 * size); - - case 'ivec4': - return new Int32Array(4 * size); - - case 'bool': - return false; - - case 'bvec2': - - return booleanArray(2 * size); - - case 'bvec3': - return booleanArray(3 * size); - - case 'bvec4': - return booleanArray(4 * size); - - case 'mat2': - return new Float32Array([1, 0, - 0, 1]); - - case 'mat3': - return new Float32Array([1, 0, 0, - 0, 1, 0, - 0, 0, 1]); - - case 'mat4': - return new Float32Array([1, 0, 0, 0, - 0, 1, 0, 0, - 0, 0, 1, 0, - 0, 0, 0, 1]); - } - - return 0; -} diff --git a/packages/core/src/shader/extractUniformsFromSrc.js b/packages/core/src/shader/extractUniformsFromSrc.js deleted file mode 100644 index 3d02c0a..0000000 --- a/packages/core/src/shader/extractUniformsFromSrc.js +++ /dev/null @@ -1,57 +0,0 @@ -import defaultValue from './defaultValue'; - -export default function extractUniformsFromSrc(vertexSrc, fragmentSrc, mask) -{ - const vertUniforms = extractUniformsFromString(vertexSrc, mask); - const fragUniforms = extractUniformsFromString(fragmentSrc, mask); - - return Object.assign(vertUniforms, fragUniforms); -} - -function extractUniformsFromString(string) -{ - const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); - - const uniforms = {}; - let nameSplit; - - // clean the lines a little - remove extra spaces / teabs etc - // then split along ';' - const lines = string.replace(/\s+/g, ' ') - .split(/\s*;\s*/); - - // loop through.. - for (let i = 0; i < lines.length; i++) - { - const line = lines[i].trim(); - - if (line.indexOf('uniform') > -1) - { - const splitLine = line.split(' '); - const type = splitLine[1]; - - let name = splitLine[2]; - let size = 1; - - if (name.indexOf('[') > -1) - { - // array! - nameSplit = name.split(/\[|]/); - name = nameSplit[0]; - size *= Number(nameSplit[1]); - } - - if (!name.match(maskRegex)) - { - uniforms[name] = { - value: defaultValue(type, size), - dirtyId: 0, - name, - type, - }; - } - } - } - - return uniforms; -} diff --git a/packages/core/src/shader/generateUniformsSync.js b/packages/core/src/shader/generateUniformsSync.js deleted file mode 100644 index 98a357f..0000000 --- a/packages/core/src/shader/generateUniformsSync.js +++ /dev/null @@ -1,231 +0,0 @@ -// cv = CachedValue -// v = value -// ud = uniformData -// uv = uniformValue -// l = loaction -const GLSL_TO_SINGLE_SETTERS_CACHED = { - - float: ` - if(cv !== v) - { - cv.v = v; - gl.uniform1f(location, v) - }`, - - vec2: ` - if(cv[0] !== v[0] || cv[1] !== v[1]) - { - cv[0] = v[0]; - cv[1] = v[1]; - gl.uniform2f(location, v[0], v[1]) - }`, - - vec3: ` - if(cv[0] !== v[0] || cv[1] !== v[1] || cv[2] !== v[2]) - { - cv[0] = v[0]; - cv[1] = v[1]; - cv[2] = v[2]; - - gl.uniform3f(location, v[0], v[1], v[2]) - }`, - - vec4: 'gl.uniform4f(location, v[0], v[1], v[2], v[3])', - - int: 'gl.uniform1i(location, v)', - ivec2: 'gl.uniform2i(location, v[0], v[1])', - ivec3: 'gl.uniform3i(location, v[0], v[1], v[2])', - ivec4: 'gl.uniform4i(location, v[0], v[1], v[2], v[3])', - - bool: 'gl.uniform1i(location, v)', - bvec2: 'gl.uniform2i(location, v[0], v[1])', - bvec3: 'gl.uniform3i(location, v[0], v[1], v[2])', - bvec4: 'gl.uniform4i(location, v[0], v[1], v[2], v[3])', - - mat2: 'gl.uniformMatrix2fv(location, false, v)', - mat3: 'gl.uniformMatrix3fv(location, false, v)', - mat4: 'gl.uniformMatrix4fv(location, false, v)', - - sampler2D: 'gl.uniform1i(location, v)', - samplerCube: 'gl.uniform1i(location, v)', - sampler2DArray: 'gl.uniform1i(location, v)', -}; - -const GLSL_TO_ARRAY_SETTERS = { - - float: `gl.uniform1fv(location, v)`, - - vec2: `gl.uniform2fv(location, v)`, - vec3: `gl.uniform3fv(location, v)`, - vec4: 'gl.uniform4fv(location, v)', - - mat4: 'gl.uniformMatrix4fv(location, false, v)', - mat3: 'gl.uniformMatrix3fv(location, false, v)', - mat2: 'gl.uniformMatrix2fv(location, false, v)', - - int: 'gl.uniform1iv(location, v)', - ivec2: 'gl.uniform2iv(location, v)', - ivec3: 'gl.uniform3iv(location, v)', - ivec4: 'gl.uniform4iv(location, v)', - - bool: 'gl.uniform1iv(location, v)', - bvec2: 'gl.uniform2iv(location, v)', - bvec3: 'gl.uniform3iv(location, v)', - bvec4: 'gl.uniform4iv(location, v)', - - sampler2D: 'gl.uniform1iv(location, v)', - samplerCube: 'gl.uniform1iv(location, v)', - sampler2DArray: 'gl.uniform1iv(location, v)', -}; - -export default function generateUniformsSync(group, uniformData) -{ - let textureCount = 0; - let func = `var v = null; - var cv = null - var gl = renderer.gl`; - - for (const i in group.uniforms) - { - const data = uniformData[i]; - - if (!data) - { - if (group.uniforms[i].group) - { - func += ` - renderer.shader.syncUniformGroup(uv.${i}); - `; - } - - continue; - } - - // TODO && uniformData[i].value !== 0 <-- do we still need this? - if (data.type === 'float' && data.size === 1) - { - func += ` - if(uv.${i} !== ud.${i}.value) - { - ud.${i}.value = uv.${i} - gl.uniform1f(ud.${i}.location, uv.${i}) - }\n`; - } - /* eslint-disable max-len */ - else if ((data.type === 'sampler2D' || data.type === 'samplerCube' || data.type === 'sampler2DArray') && data.size === 1 && !data.isArray) - /* eslint-disable max-len */ - { - func += ` - renderer.texture.bind(uv.${i}, ${textureCount}); - - if(ud.${i}.value !== ${textureCount}) - { - ud.${i}.value = ${textureCount}; - gl.uniform1i(ud.${i}.location, ${textureCount});\n; // eslint-disable-line max-len - }\n`; - - textureCount++; - } - else if (data.type === 'mat3' && data.size === 1) - { - if (group.uniforms[i].a !== undefined) - { - // TODO and some smart caching dirty ids here! - func += ` - gl.uniformMatrix3fv(ud.${i}.location, false, uv.${i}.toArray(true)); - \n`; - } - else - { - func += ` - gl.uniformMatrix3fv(ud.${i}.location, false, uv.${i}); - \n`; - } - } - else if (data.type === 'vec2' && data.size === 1) - { - // TODO - do we need both here? - // maybe we can get away with only using points? - if (group.uniforms[i].x !== undefined) - { - func += ` - cv = ud.${i}.value; - v = uv.${i}; - - if(cv[0] !== v.x || cv[1] !== v.y) - { - cv[0] = v.x; - cv[1] = v.y; - gl.uniform2f(ud.${i}.location, v.x, v.y); - }\n`; - } - else - { - func += ` - cv = ud.${i}.value; - v = uv.${i}; - - if(cv[0] !== v[0] || cv[1] !== v[1]) - { - cv[0] = v[0]; - cv[1] = v[1]; - gl.uniform2f(ud.${i}.location, v[0], v[1]); - } - \n`; - } - } - else if (data.type === 'vec4' && data.size === 1) - { - // TODO - do we need both here? - // maybe we can get away with only using points? - if (group.uniforms[i].width !== undefined) - { - func += ` - cv = ud.${i}.value; - v = uv.${i}; - - if(cv[0] !== v.x || cv[1] !== v.y || cv[2] !== v.width || cv[3] !== v.height) - { - cv[0] = v.x; - cv[1] = v.y; - cv[2] = v.width; - cv[3] = v.height; - gl.uniform4f(ud.${i}.location, v.x, v.y, v.width, v.height) - }\n`; - } - else - { - func += ` - cv = ud.${i}.value; - v = uv.${i}; - - if(cv[0] !== v[0] || cv[1] !== v[1] || cv[2] !== v[2] || cv[3] !== v[3]) - { - cv[0] = v[0]; - cv[1] = v[1]; - cv[2] = v[2]; - cv[3] = v[3]; - - gl.uniform4f(ud.${i}.location, v[0], v[1], v[2], v[3]) - } - \n`; - } - } - else - { - const templateType = (data.size === 1) ? GLSL_TO_SINGLE_SETTERS_CACHED : GLSL_TO_ARRAY_SETTERS; - - const template = templateType[data.type].replace('location', `ud.${i}.location`); - - func += ` - cv = ud.${i}.value; - v = uv.${i}; - ${template};\n`; - } - } - - // console.log(' --------------- ') - // console.log(func); - - return new Function('ud', 'uv', 'renderer', func); // eslint-disable-line no-new-func -} diff --git a/packages/core/src/shader/getTestContext.js b/packages/core/src/shader/getTestContext.js deleted file mode 100644 index ea174cc..0000000 --- a/packages/core/src/shader/getTestContext.js +++ /dev/null @@ -1,48 +0,0 @@ -import { settings } from '@pixi/settings'; - -let context = null; - -/** - * returns a little webGL context to use for program inspection. - * - * @static - * @private - * @returns {webGL-context} a gl context to test with - */ -export default function getTestContext() -{ - if (!context) - { - const canvas = document.createElement('canvas'); - - let gl; - - if (settings.PREFER_WEBGL_2) - { - gl = canvas.getContext('webgl2', {}); - } - - if (!gl) - { - gl = canvas.getContext('webgl', {}) - || canvas.getContext('experimental-webgl', {}); - - if (!gl) - { - // fail, not able to get a context - throw new Error('This browser does not support webGL. Try using the canvas renderer'); - } - else - { - // for shader testing.. - gl.getExtension('WEBGL_draw_buffers'); - } - } - - context = gl; - - return gl; - } - - return context; -} diff --git a/packages/core/src/shader/utils/checkMaxIfStatmentsInShader.js b/packages/core/src/shader/utils/checkMaxIfStatmentsInShader.js new file mode 100644 index 0000000..8047c96 --- /dev/null +++ b/packages/core/src/shader/utils/checkMaxIfStatmentsInShader.js @@ -0,0 +1,58 @@ +const fragTemplate = [ + 'precision mediump float;', + 'void main(void){', + 'float test = 0.1;', + '%forloop%', + 'gl_FragColor = vec4(0.0);', + '}', +].join('\n'); + +export default function checkMaxIfStatmentsInShader(maxIfs, gl) +{ + if (maxIfs === 0) + { + throw new Error('Invalid value of `0` passed to `checkMaxIfStatementsInShader`'); + } + + const shader = gl.createShader(gl.FRAGMENT_SHADER); + + while (true) // eslint-disable-line no-constant-condition + { + const fragmentSrc = fragTemplate.replace(/%forloop%/gi, generateIfTestSrc(maxIfs)); + + gl.shaderSource(shader, fragmentSrc); + gl.compileShader(shader); + + if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) + { + maxIfs = (maxIfs / 2) | 0; + } + else + { + // valid! + break; + } + } + + return maxIfs; +} + +function generateIfTestSrc(maxIfs) +{ + let src = ''; + + for (let i = 0; i < maxIfs; ++i) + { + if (i > 0) + { + src += '\nelse '; + } + + if (i < maxIfs - 1) + { + src += `if(test == ${i}.0){}`; + } + } + + return src; +} diff --git a/packages/core/src/shader/utils/compileProgram.js b/packages/core/src/shader/utils/compileProgram.js new file mode 100644 index 0000000..8c6ccb9 --- /dev/null +++ b/packages/core/src/shader/utils/compileProgram.js @@ -0,0 +1,78 @@ +/** + * @method compileProgram + * @memberof PIXI.glCore.shader + * @param gl {WebGLRenderingContext} The current WebGL context {WebGLProgram} + * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. + * @param fragmentSrc {string|string[]} The fragment shader source as an array of strings. + * @param attributeLocations {Object} An attribute location map that lets you manually set the attribute locations + * @return {WebGLProgram} the shader program + */ +export default function compileProgram(gl, vertexSrc, fragmentSrc, attributeLocations) +{ + const glVertShader = compileShader(gl, gl.VERTEX_SHADER, vertexSrc); + const glFragShader = compileShader(gl, gl.FRAGMENT_SHADER, fragmentSrc); + + let program = gl.createProgram(); + + gl.attachShader(program, glVertShader); + gl.attachShader(program, glFragShader); + + // optionally, set the attributes manually for the program rather than letting WebGL decide.. + if (attributeLocations) + { + for (const i in attributeLocations) + { + gl.bindAttribLocation(program, attributeLocations[i], i); + } + } + + gl.linkProgram(program); + + // if linking fails, then log and cleanup + if (!gl.getProgramParameter(program, gl.LINK_STATUS)) + { + console.error('Pixi.js Error: Could not initialize shader.'); + console.error('gl.VALIDATE_STATUS', gl.getProgramParameter(program, gl.VALIDATE_STATUS)); + console.error('gl.getError()', gl.getError()); + + // if there is a program info log, log it + if (gl.getProgramInfoLog(program) !== '') + { + console.warn('Pixi.js Warning: gl.getProgramInfoLog()', gl.getProgramInfoLog(program)); + } + + gl.deleteProgram(program); + program = null; + } + + // clean up some shaders + gl.deleteShader(glVertShader); + gl.deleteShader(glFragShader); + + return program; +} + +/** + * @private + * @param gl {WebGLRenderingContext} The current WebGL context {WebGLProgram} + * @param type {Number} the type, can be either VERTEX_SHADER or FRAGMENT_SHADER + * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. + * @return {WebGLShader} the shader + */ +function compileShader(gl, type, src) +{ + const shader = gl.createShader(type); + + gl.shaderSource(shader, src); + gl.compileShader(shader); + + if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) + { + console.warn(src); + console.error(gl.getShaderInfoLog(shader)); + + return null; + } + + return shader; +} diff --git a/packages/core/src/shader/utils/defaultValue.js b/packages/core/src/shader/utils/defaultValue.js new file mode 100644 index 0000000..97290bd --- /dev/null +++ b/packages/core/src/shader/utils/defaultValue.js @@ -0,0 +1,79 @@ +/** + * @method defaultValue + * @memberof PIXI.glCore.shader + * @param type {String} Type of value + * @param size {Number} + */ +export default function defaultValue(type, size) +{ + switch (type) + { + case 'float': + return 0; + + case 'vec2': + return new Float32Array(2 * size); + + case 'vec3': + return new Float32Array(3 * size); + + case 'vec4': + return new Float32Array(4 * size); + + case 'int': + case 'sampler2D': + case 'sampler2DArray': + return 0; + + case 'ivec2': + return new Int32Array(2 * size); + + case 'ivec3': + return new Int32Array(3 * size); + + case 'ivec4': + return new Int32Array(4 * size); + + case 'bool': + return false; + + case 'bvec2': + + return booleanArray(2 * size); + + case 'bvec3': + return booleanArray(3 * size); + + case 'bvec4': + return booleanArray(4 * size); + + case 'mat2': + return new Float32Array([1, 0, + 0, 1]); + + case 'mat3': + return new Float32Array([1, 0, 0, + 0, 1, 0, + 0, 0, 1]); + + case 'mat4': + return new Float32Array([1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1]); + } + + return null; +} + +function booleanArray(size) +{ + const array = new Array(size); + + for (let i = 0; i < array.length; i++) + { + array[i] = false; + } + + return array; +} diff --git a/packages/core/src/shader/utils/extractUniformsFromSrc.js b/packages/core/src/shader/utils/extractUniformsFromSrc.js new file mode 100644 index 0000000..3d02c0a --- /dev/null +++ b/packages/core/src/shader/utils/extractUniformsFromSrc.js @@ -0,0 +1,57 @@ +import defaultValue from './defaultValue'; + +export default function extractUniformsFromSrc(vertexSrc, fragmentSrc, mask) +{ + const vertUniforms = extractUniformsFromString(vertexSrc, mask); + const fragUniforms = extractUniformsFromString(fragmentSrc, mask); + + return Object.assign(vertUniforms, fragUniforms); +} + +function extractUniformsFromString(string) +{ + const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); + + const uniforms = {}; + let nameSplit; + + // clean the lines a little - remove extra spaces / teabs etc + // then split along ';' + const lines = string.replace(/\s+/g, ' ') + .split(/\s*;\s*/); + + // loop through.. + for (let i = 0; i < lines.length; i++) + { + const line = lines[i].trim(); + + if (line.indexOf('uniform') > -1) + { + const splitLine = line.split(' '); + const type = splitLine[1]; + + let name = splitLine[2]; + let size = 1; + + if (name.indexOf('[') > -1) + { + // array! + nameSplit = name.split(/\[|]/); + name = nameSplit[0]; + size *= Number(nameSplit[1]); + } + + if (!name.match(maskRegex)) + { + uniforms[name] = { + value: defaultValue(type, size), + dirtyId: 0, + name, + type, + }; + } + } + } + + return uniforms; +} diff --git a/packages/core/src/shader/utils/generateUniformsSync.js b/packages/core/src/shader/utils/generateUniformsSync.js new file mode 100644 index 0000000..98a357f --- /dev/null +++ b/packages/core/src/shader/utils/generateUniformsSync.js @@ -0,0 +1,231 @@ +// cv = CachedValue +// v = value +// ud = uniformData +// uv = uniformValue +// l = loaction +const GLSL_TO_SINGLE_SETTERS_CACHED = { + + float: ` + if(cv !== v) + { + cv.v = v; + gl.uniform1f(location, v) + }`, + + vec2: ` + if(cv[0] !== v[0] || cv[1] !== v[1]) + { + cv[0] = v[0]; + cv[1] = v[1]; + gl.uniform2f(location, v[0], v[1]) + }`, + + vec3: ` + if(cv[0] !== v[0] || cv[1] !== v[1] || cv[2] !== v[2]) + { + cv[0] = v[0]; + cv[1] = v[1]; + cv[2] = v[2]; + + gl.uniform3f(location, v[0], v[1], v[2]) + }`, + + vec4: 'gl.uniform4f(location, v[0], v[1], v[2], v[3])', + + int: 'gl.uniform1i(location, v)', + ivec2: 'gl.uniform2i(location, v[0], v[1])', + ivec3: 'gl.uniform3i(location, v[0], v[1], v[2])', + ivec4: 'gl.uniform4i(location, v[0], v[1], v[2], v[3])', + + bool: 'gl.uniform1i(location, v)', + bvec2: 'gl.uniform2i(location, v[0], v[1])', + bvec3: 'gl.uniform3i(location, v[0], v[1], v[2])', + bvec4: 'gl.uniform4i(location, v[0], v[1], v[2], v[3])', + + mat2: 'gl.uniformMatrix2fv(location, false, v)', + mat3: 'gl.uniformMatrix3fv(location, false, v)', + mat4: 'gl.uniformMatrix4fv(location, false, v)', + + sampler2D: 'gl.uniform1i(location, v)', + samplerCube: 'gl.uniform1i(location, v)', + sampler2DArray: 'gl.uniform1i(location, v)', +}; + +const GLSL_TO_ARRAY_SETTERS = { + + float: `gl.uniform1fv(location, v)`, + + vec2: `gl.uniform2fv(location, v)`, + vec3: `gl.uniform3fv(location, v)`, + vec4: 'gl.uniform4fv(location, v)', + + mat4: 'gl.uniformMatrix4fv(location, false, v)', + mat3: 'gl.uniformMatrix3fv(location, false, v)', + mat2: 'gl.uniformMatrix2fv(location, false, v)', + + int: 'gl.uniform1iv(location, v)', + ivec2: 'gl.uniform2iv(location, v)', + ivec3: 'gl.uniform3iv(location, v)', + ivec4: 'gl.uniform4iv(location, v)', + + bool: 'gl.uniform1iv(location, v)', + bvec2: 'gl.uniform2iv(location, v)', + bvec3: 'gl.uniform3iv(location, v)', + bvec4: 'gl.uniform4iv(location, v)', + + sampler2D: 'gl.uniform1iv(location, v)', + samplerCube: 'gl.uniform1iv(location, v)', + sampler2DArray: 'gl.uniform1iv(location, v)', +}; + +export default function generateUniformsSync(group, uniformData) +{ + let textureCount = 0; + let func = `var v = null; + var cv = null + var gl = renderer.gl`; + + for (const i in group.uniforms) + { + const data = uniformData[i]; + + if (!data) + { + if (group.uniforms[i].group) + { + func += ` + renderer.shader.syncUniformGroup(uv.${i}); + `; + } + + continue; + } + + // TODO && uniformData[i].value !== 0 <-- do we still need this? + if (data.type === 'float' && data.size === 1) + { + func += ` + if(uv.${i} !== ud.${i}.value) + { + ud.${i}.value = uv.${i} + gl.uniform1f(ud.${i}.location, uv.${i}) + }\n`; + } + /* eslint-disable max-len */ + else if ((data.type === 'sampler2D' || data.type === 'samplerCube' || data.type === 'sampler2DArray') && data.size === 1 && !data.isArray) + /* eslint-disable max-len */ + { + func += ` + renderer.texture.bind(uv.${i}, ${textureCount}); + + if(ud.${i}.value !== ${textureCount}) + { + ud.${i}.value = ${textureCount}; + gl.uniform1i(ud.${i}.location, ${textureCount});\n; // eslint-disable-line max-len + }\n`; + + textureCount++; + } + else if (data.type === 'mat3' && data.size === 1) + { + if (group.uniforms[i].a !== undefined) + { + // TODO and some smart caching dirty ids here! + func += ` + gl.uniformMatrix3fv(ud.${i}.location, false, uv.${i}.toArray(true)); + \n`; + } + else + { + func += ` + gl.uniformMatrix3fv(ud.${i}.location, false, uv.${i}); + \n`; + } + } + else if (data.type === 'vec2' && data.size === 1) + { + // TODO - do we need both here? + // maybe we can get away with only using points? + if (group.uniforms[i].x !== undefined) + { + func += ` + cv = ud.${i}.value; + v = uv.${i}; + + if(cv[0] !== v.x || cv[1] !== v.y) + { + cv[0] = v.x; + cv[1] = v.y; + gl.uniform2f(ud.${i}.location, v.x, v.y); + }\n`; + } + else + { + func += ` + cv = ud.${i}.value; + v = uv.${i}; + + if(cv[0] !== v[0] || cv[1] !== v[1]) + { + cv[0] = v[0]; + cv[1] = v[1]; + gl.uniform2f(ud.${i}.location, v[0], v[1]); + } + \n`; + } + } + else if (data.type === 'vec4' && data.size === 1) + { + // TODO - do we need both here? + // maybe we can get away with only using points? + if (group.uniforms[i].width !== undefined) + { + func += ` + cv = ud.${i}.value; + v = uv.${i}; + + if(cv[0] !== v.x || cv[1] !== v.y || cv[2] !== v.width || cv[3] !== v.height) + { + cv[0] = v.x; + cv[1] = v.y; + cv[2] = v.width; + cv[3] = v.height; + gl.uniform4f(ud.${i}.location, v.x, v.y, v.width, v.height) + }\n`; + } + else + { + func += ` + cv = ud.${i}.value; + v = uv.${i}; + + if(cv[0] !== v[0] || cv[1] !== v[1] || cv[2] !== v[2] || cv[3] !== v[3]) + { + cv[0] = v[0]; + cv[1] = v[1]; + cv[2] = v[2]; + cv[3] = v[3]; + + gl.uniform4f(ud.${i}.location, v[0], v[1], v[2], v[3]) + } + \n`; + } + } + else + { + const templateType = (data.size === 1) ? GLSL_TO_SINGLE_SETTERS_CACHED : GLSL_TO_ARRAY_SETTERS; + + const template = templateType[data.type].replace('location', `ud.${i}.location`); + + func += ` + cv = ud.${i}.value; + v = uv.${i}; + ${template};\n`; + } + } + + // console.log(' --------------- ') + // console.log(func); + + return new Function('ud', 'uv', 'renderer', func); // eslint-disable-line no-new-func +} diff --git a/packages/core/src/shader/utils/getTestContext.js b/packages/core/src/shader/utils/getTestContext.js new file mode 100644 index 0000000..ea174cc --- /dev/null +++ b/packages/core/src/shader/utils/getTestContext.js @@ -0,0 +1,48 @@ +import { settings } from '@pixi/settings'; + +let context = null; + +/** + * returns a little webGL context to use for program inspection. + * + * @static + * @private + * @returns {webGL-context} a gl context to test with + */ +export default function getTestContext() +{ + if (!context) + { + const canvas = document.createElement('canvas'); + + let gl; + + if (settings.PREFER_WEBGL_2) + { + gl = canvas.getContext('webgl2', {}); + } + + if (!gl) + { + gl = canvas.getContext('webgl', {}) + || canvas.getContext('experimental-webgl', {}); + + if (!gl) + { + // fail, not able to get a context + throw new Error('This browser does not support webGL. Try using the canvas renderer'); + } + else + { + // for shader testing.. + gl.getExtension('WEBGL_draw_buffers'); + } + } + + context = gl; + + return gl; + } + + return context; +} diff --git a/packages/core/src/shader/utils/index.js b/packages/core/src/shader/utils/index.js new file mode 100644 index 0000000..4bf8dd4 --- /dev/null +++ b/packages/core/src/shader/utils/index.js @@ -0,0 +1,8 @@ +export { default as compileProgram } from './compileProgram'; +export { default as defaultValue } from './defaultValue'; +export { default as setPrecision } from './setPrecision'; +export { default as mapSize } from './mapSize'; +export { default as mapType } from './mapType'; +export { default as generateUniformsSync } from './generateUniformsSync'; +export { default as getTestContext } from './getTestContext'; +export { default as checkMaxIfStatmentsInShader } from './checkMaxIfStatmentsInShader'; diff --git a/packages/core/src/shader/utils/mapSize.js b/packages/core/src/shader/utils/mapSize.js new file mode 100644 index 0000000..b681554 --- /dev/null +++ b/packages/core/src/shader/utils/mapSize.js @@ -0,0 +1,33 @@ +const GLSL_TO_SIZE = { + float: 1, + vec2: 2, + vec3: 3, + vec4: 4, + + int: 1, + ivec2: 2, + ivec3: 3, + ivec4: 4, + + bool: 1, + bvec2: 2, + bvec3: 3, + bvec4: 4, + + mat2: 4, + mat3: 9, + mat4: 16, + + sampler2D: 1, +}; + +/** + * @method mapSize + * @memberof PIXI.glCore.shader + * @param type {String} + * @return {Number} + */ +export default function mapSize(type) +{ + return GLSL_TO_SIZE[type]; +} diff --git a/packages/core/src/shader/utils/mapType.js b/packages/core/src/shader/utils/mapType.js new file mode 100644 index 0000000..d288b7c --- /dev/null +++ b/packages/core/src/shader/utils/mapType.js @@ -0,0 +1,45 @@ +let GL_TABLE = null; + +const GL_TO_GLSL_TYPES = { + FLOAT: 'float', + FLOAT_VEC2: 'vec2', + FLOAT_VEC3: 'vec3', + FLOAT_VEC4: 'vec4', + + INT: 'int', + INT_VEC2: 'ivec2', + INT_VEC3: 'ivec3', + INT_VEC4: 'ivec4', + + BOOL: 'bool', + BOOL_VEC2: 'bvec2', + BOOL_VEC3: 'bvec3', + BOOL_VEC4: 'bvec4', + + FLOAT_MAT2: 'mat2', + FLOAT_MAT3: 'mat3', + FLOAT_MAT4: 'mat4', + + SAMPLER_2D: 'sampler2D', + SAMPLER_CUBE: 'samplerCube', + SAMPLER_2D_ARRAY: 'sampler2DArray', +}; + +export default function mapType(gl, type) +{ + if (!GL_TABLE) + { + const typeNames = Object.keys(GL_TO_GLSL_TYPES); + + GL_TABLE = {}; + + for (let i = 0; i < typeNames.length; ++i) + { + const tn = typeNames[i]; + + GL_TABLE[gl[tn]] = GL_TO_GLSL_TYPES[tn]; + } + } + + return GL_TABLE[type]; +} diff --git a/packages/core/src/shader/utils/setPrecision.js b/packages/core/src/shader/utils/setPrecision.js new file mode 100644 index 0000000..e2c1f34 --- /dev/null +++ b/packages/core/src/shader/utils/setPrecision.js @@ -0,0 +1,18 @@ +/** + * Sets the float precision on the shader. If the precision is already present this function will do nothing + * @param {string} src the shader source + * @param {string} precision The float precision of the shader. Options are 'lowp', 'mediump' or 'highp'. + * + * @return {string} modified shader source + */ +export default function setPrecision(src, precision) +{ + src = src.trim(); + + if (src.substring(0, 9) !== 'precision' && src.substring(0, 1) !== '#') + { + return `precision ${precision} float;\n${src}`; + } + + return src; +} diff --git a/packages/core/src/state/State.js b/packages/core/src/state/State.js new file mode 100644 index 0000000..fa3f221 --- /dev/null +++ b/packages/core/src/state/State.js @@ -0,0 +1,163 @@ +/* eslint-disable max-len */ + +const BLEND = 0; +const OFFSET = 1; +const CULLING = 2; +const DEPTH_TEST = 3; +const WINDING = 4; + +/** + * This is a webGL state. It is passed The WebGL StateManager. + * Each mesh renderered may require webGL to be in a different state. + * For example you may want different blend mode or to enable polygon offsets + * + * @class + * @memberof PIXI + */ +export default class State +{ + /** + * + */ + constructor() + { + this.data = 0; + + this.blendMode = 0; + this.polygonOffset = 0; + + this.blend = true; + // this.depthTest = true; + } + + /** + * Activates blending of the computed fragment color values + * + * @member {boolean} + */ + get blend() + { + return !!(this.data & (1 << BLEND)); + } + + set blend(value) // eslint-disable-line require-jsdoc + { + if (!!(this.data & (1 << BLEND)) !== value) + { + this.data ^= (1 << BLEND); + } + } + + /** + * Activates adding an offset to depth values of polygon's fragments + * + * @member {boolean} + * @default false + */ + get offsets() + { + return !!(this.data & (1 << OFFSET)); + } + + set offsets(value) // eslint-disable-line require-jsdoc + { + if (!!(this.data & (1 << OFFSET)) !== value) + { + this.data ^= (1 << OFFSET); + } + } + + /** + * Activates culling of polygons. + * + * @member {boolean} + * @default false + */ + get culling() + { + return !!(this.data & (1 << CULLING)); + } + + set culling(value) // eslint-disable-line require-jsdoc + { + if (!!(this.data & (1 << CULLING)) !== value) + { + this.data ^= (1 << CULLING); + } + } + + /** + * Activates depth comparisons and updates to the depth buffer. + * + * @member {boolean} + * @default false + */ + get depthTest() + { + return !!(this.data & (1 << DEPTH_TEST)); + } + + set depthTest(value) // eslint-disable-line require-jsdoc + { + if (!!(this.data & (1 << DEPTH_TEST)) !== value) + { + this.data ^= (1 << DEPTH_TEST); + } + } + + /** + * Specifies whether or not front or back-facing polygons can be culled. + * @member {boolean} + * @default false + */ + get clockwiseFrontFace() + { + return !!(this.data & (1 << WINDING)); + } + + set clockwiseFrontFace(value) // eslint-disable-line require-jsdoc + { + if (!!(this.data & (1 << WINDING)) !== value) + { + this.data ^= (1 << WINDING); + } + } + + /** + * The blend mode to be applied when this state is set. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. + * Setting this mode to anything other than NO_BLEND will automatically switch blending on. + * + * @member {boolean} + * @default PIXI.BLEND_MODES.NORMAL + * @see PIXI.BLEND_MODES + */ + get blendMode() + { + return this._blendMode; + } + + set blendMode(value) // eslint-disable-line require-jsdoc + { + // 17 is NO BLEND + this.blend = (value !== 17); + this._blendMode = value; + } + + /** + * The polygon offset. Setting this property to anything other than 0 will automatically enable poygon offset fill. + * + * @member {number} + * @default 0 + */ + get polygonOffset() + { + return this._polygonOffset; + } + + set polygonOffset(value) // eslint-disable-line require-jsdoc + { + this.offsets = !!value; + this._polygonOffset = value; + } +} + diff --git a/packages/core/src/state/StateSystem.js b/packages/core/src/state/StateSystem.js new file mode 100755 index 0000000..39c35bb --- /dev/null +++ b/packages/core/src/state/StateSystem.js @@ -0,0 +1,288 @@ +import mapWebGLBlendModesToPixi from './utils/mapWebGLBlendModesToPixi'; +import System from '../System'; +import WebGLState from './State'; + +const BLEND = 0; +const OFFSET = 1; +const CULLING = 2; +const DEPTH_TEST = 3; +const WINDING = 4; + +/** + * A WebGL state machines + * + * @class + * @extends PIXI.systems.System + * @memberof PIXI.systems + */ +export default class StateSystem extends System +{ + /** + * @param {WebGLRenderingContext} gl - The current WebGL rendering context + */ + constructor(renderer) + { + super(renderer); + + this.gl = null; + + this.maxAttribs = null; + + // check we have vao.. + this.nativeVaoExtension = null; + + this.attribState = null; + + this.stateId = 0; + this.polygonOffset = 0; + this.blendMode = 17; + + this.map = []; + + // map functions for when we set state.. + this.map[BLEND] = this.setBlend; + this.map[OFFSET] = this.setOffset; + this.map[CULLING] = this.setCullFace; + this.map[DEPTH_TEST] = this.setDepthTest; + this.map[WINDING] = this.setFrontFace; + + this.checks = []; + + this.defaultState = new WebGLState(); + this.defaultState.blend = true; + this.defaultState.depth = true; + } + + contextChange(gl) + { + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); + + // check we have vao.. + this.nativeVaoExtension = ( + gl.getExtension('OES_vertex_array_object') + || gl.getExtension('MOZ_OES_vertex_array_object') + || gl.getExtension('WEBKIT_OES_vertex_array_object') + ); + + this.attribState = { + tempAttribState: new Array(this.maxAttribs), + attribState: new Array(this.maxAttribs), + }; + + this.blendModes = mapWebGLBlendModesToPixi(gl); + + this.setState(this.defaultState); + + this.reset(); + } + + /** + * Sets the current state + * + * @param {*} state - The state to set. + */ + setState(state) + { + state = state || this.defaultState; + + // TODO maybe to an object check? ( this.state === state )? + if (this.stateId !== state.data) + { + let diff = this.stateId ^ state.data; + let i = 0; + + // order from least to most common + while (diff) + { + if (diff & 1) + { + // state change! + this.map[i].call(this, !!(state.data & (1 << i))); + } + + diff = diff >> 1; + i++; + } + + this.stateId = state.data; + } + + // based on the above settings we check for specific modes.. + // for example if blend is active we check and set the blend modes + // or of polygon offset is active we check the poly depth. + for (let i = 0; i < this.checks.length; i++) + { + this.checks[i](this, state); + } + } + + /** + * Enables or disabled blending. + * + * @param {boolean} value - Turn on or off webgl blending. + */ + setBlend(value) + { + this.updateCheck(StateSystem.checkBlendMode, value); + + this.gl[value ? 'enable' : 'disable'](this.gl.BLEND); + } + + /** + * Enables or disable polygon offset fill + * + * @param {boolean} value - Turn on or off webgl polygon offset testing. + */ + setOffset(value) + { + this.gl[value ? 'enable' : 'disable'](this.gl.POLYGON_OFFSET_FILL); + } + + /** + * Sets whether to enable or disable depth test. + * + * @param {boolean} value - Turn on or off webgl depth testing. + */ + setDepthTest(value) + { + this.gl[value ? 'enable' : 'disable'](this.gl.DEPTH_TEST); + } + + /** + * Sets whether to enable or disable cull face. + * + * @param {boolean} value - Turn on or off webgl cull face. + */ + setCullFace(value) + { + this.gl[value ? 'enable' : 'disable'](this.gl.CULL_FACE); + } + + /** + * Sets the gl front face. + * + * @param {boolean} value - true is clockwise and false is counter-clockwise + */ + setFrontFace(value) + { + this.gl.frontFace(this.gl[value ? 'CW' : 'CCW']); + } + + /** + * Sets the blend mode. + * + * @param {number} value - The blend mode to set to. + */ + setBlendMode(value) + { + if (value === this.blendMode) + { + return; + } + + this.blendMode = value; + this.gl.blendFunc(this.blendModes[value][0], this.blendModes[value][1]); + } + + /** + * Sets the polygon offset. + * + * @param {number} value - the polygon offset + * @param {number} scale - the polygon offset scale + */ + setPolygonOffset(value, scale) + { + this.gl.polygonOffset(value, scale); + } + + /** + * Disables all the vaos in use + * + */ + resetAttributes() + { + for (let i = 0; i < this.attribState.tempAttribState.length; i++) + { + this.attribState.tempAttribState[i] = 0; + } + + for (let i = 0; i < this.attribState.attribState.length; i++) + { + this.attribState.attribState[i] = 0; + } + + // im going to assume one is always active for performance reasons. + for (let i = 1; i < this.maxAttribs; i++) + { + this.gl.disableVertexAttribArray(i); + } + } + + // used + /** + * Resets all the logic and disables the vaos + */ + reset() + { + // unbind any VAO if they exist.. + if (this.nativeVaoExtension) + { + this.nativeVaoExtension.bindVertexArrayOES(null); + } + + // reset all attributes.. + this.resetAttributes(); + + this.gl.pixelStorei(this.gl.UNPACK_FLIP_Y_WEBGL, false); + + this.setBlendMode(0); + + // TODO? + // this.setState(this.defaultState); + } + + /** + * checks to see which updates should be checked based on which settings have been activated + * for example if blend is enabled then we shold check the blend modes each time the state is changed + * or if poygon fill is activated then we need to check if the polygone offset changes. + * The idea is that we only check what we have too. + * + * @param {Function} func the checking function to add or remove + * @param {boolean} value should the check function be added or removed. + */ + updateCheck(func, value) + { + const index = this.checks.indexOf(func); + + if (value && index === -1) + { + this.checks.push(func); + } + else if (!value && index !== -1) + { + this.checks.splice(index, 1); + } + } + + /** + * A private little wrapper function that we call to check the blend mode. + * + * @static + * @private + * @param {PIXI.StateSystem} System the System to perform the state check on + * @param {PIXI.State} state the state that the blendMode will pulled from + */ + static checkBlendMode(system, state) + { + system.setBlendMode(state.blendMode); + } + + // TODO - add polygon offset? +} diff --git a/packages/core/src/state/utils/mapWebGLBlendModesToPixi.js b/packages/core/src/state/utils/mapWebGLBlendModesToPixi.js new file mode 100644 index 0000000..10ffe4d --- /dev/null +++ b/packages/core/src/state/utils/mapWebGLBlendModesToPixi.js @@ -0,0 +1,47 @@ +import { BLEND_MODES } from '@pixi/constants'; + +/** + * Maps gl blend combinations to WebGL. + * + * @memberof PIXI + * @function mapWebGLBlendModesToPixi + * @private + * @param {WebGLRenderingContext} gl - The rendering context. + * @param {string[]} [array=[]] - The array to output into. + * @return {string[]} Mapped modes. + */ +export default function mapWebGLBlendModesToPixi(gl, array = []) +{ + // TODO - premultiply alpha would be different. + // add a boolean for that! + array[BLEND_MODES.NORMAL] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; + array[BLEND_MODES.ADD] = [gl.ONE, gl.DST_ALPHA]; + array[BLEND_MODES.MULTIPLY] = [gl.DST_COLOR, gl.ONE_MINUS_SRC_ALPHA]; + array[BLEND_MODES.SCREEN] = [gl.ONE, gl.ONE_MINUS_SRC_COLOR]; + array[BLEND_MODES.OVERLAY] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; + array[BLEND_MODES.DARKEN] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; + array[BLEND_MODES.LIGHTEN] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; + array[BLEND_MODES.COLOR_DODGE] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; + array[BLEND_MODES.COLOR_BURN] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; + array[BLEND_MODES.HARD_LIGHT] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; + array[BLEND_MODES.SOFT_LIGHT] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; + array[BLEND_MODES.DIFFERENCE] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; + array[BLEND_MODES.EXCLUSION] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; + array[BLEND_MODES.HUE] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; + array[BLEND_MODES.SATURATION] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; + array[BLEND_MODES.COLOR] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; + array[BLEND_MODES.LUMINOSITY] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; + array[BLEND_MODES.NONE] = [0, 0]; + + // not-premultiplied blend modes + array[BLEND_MODES.NORMAL_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; + array[BLEND_MODES.ADD_NPM] = [gl.SRC_ALPHA, gl.DST_ALPHA, gl.ONE, gl.DST_ALPHA]; + array[BLEND_MODES.SCREEN_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_COLOR, gl.ONE, gl.ONE_MINUS_SRC_COLOR]; + + // not-premultiplied blend modes + array[BLEND_MODES.NORMAL_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; + array[BLEND_MODES.ADD_NPM] = [gl.SRC_ALPHA, gl.DST_ALPHA, gl.ONE, gl.DST_ALPHA]; + array[BLEND_MODES.SCREEN_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_COLOR, gl.ONE, gl.ONE_MINUS_SRC_COLOR]; + + return array; +} diff --git a/packages/core/src/systems.js b/packages/core/src/systems.js new file mode 100644 index 0000000..8edd9d5 --- /dev/null +++ b/packages/core/src/systems.js @@ -0,0 +1,17 @@ +/** + * Systems are individual components to the Renderer pipeline. + * @namespace PIXI.systems + */ +export { default as FilterSystem } from './filters/FilterSystem'; +export { default as BatchSystem } from './batch/BatchSystem'; +export { default as ContextSystem } from './context/ContextSystem'; +export { default as FramebufferSystem } from './framebuffer/FramebufferSystem'; +export { default as GeometrySystem } from './geometry/GeometrySystem'; +export { default as MaskSystem } from './mask/MaskSystem'; +export { default as StencilSystem } from './mask/StencilSystem'; +export { default as ProjectionSystem } from './projection/ProjectionSystem'; +export { default as RenderTextureSystem } from './renderTexture/RenderTextureSystem'; +export { default as ShaderSystem } from './shader/ShaderSystem'; +export { default as StateSystem } from './state/StateSystem'; +export { default as TextureGCSystem } from './textures/TextureGCSystem'; +export { default as TextureSystem } from './textures/TextureSystem'; diff --git a/packages/core/src/textures/BaseRenderTexture.js b/packages/core/src/textures/BaseRenderTexture.js index 0d4611e..77c44f5 100644 --- a/packages/core/src/textures/BaseRenderTexture.js +++ b/packages/core/src/textures/BaseRenderTexture.js @@ -1,5 +1,5 @@ import BaseTexture from './BaseTexture'; -import FrameBuffer from './FrameBuffer'; +import FrameBuffer from '../framebuffer/FrameBuffer'; /** * A BaseRenderTexture is a special texture that allows any PixiJS display object to be rendered to it. diff --git a/packages/core/src/textures/FrameBuffer.js b/packages/core/src/textures/FrameBuffer.js deleted file mode 100644 index 58c8f39..0000000 --- a/packages/core/src/textures/FrameBuffer.js +++ /dev/null @@ -1,106 +0,0 @@ -import Texture from './BaseTexture'; -import { FORMATS, TYPES } from '@pixi/constants'; - -/** - * Frame buffer - * @class - * @memberof PIXI - */ -export default class FrameBuffer -{ - constructor(width, height) - { - this.width = width || 100; - this.height = height || 100; - - this.stencil = false; - this.depth = false; - - this.dirtyId = 0; - this.dirtyFormat = 0; - this.dirtySize = 0; - - this.depthTexture = null; - this.colorTextures = []; - - this.glFrameBuffers = {}; - } - - get colorTexture() - { - return this.colorTextures[0]; - } - - addColorTexture(index, texture) - { - // TODO add some validation to the texture - same width / height etc? - this.colorTextures[index || 0] = texture || new Texture(null, { scaleMode: 0, - resolution: 1, - mipmap: false, - width: this.width, - height: this.height });// || new Texture(); - - this.dirtyId++; - this.dirtyFormat++; - - return this; - } - - addDepthTexture(texture) - { - /* eslint-disable max-len */ - this.depthTexture = texture || new Texture(null, { scaleMode: 0, - resolution: 1, - width: this.width, - height: this.height, - mipmap: false, - format: FORMATS.DEPTH_COMPONENT, - type: TYPES.UNSIGNED_SHORT });// UNSIGNED_SHORT; - /* eslint-disable max-len */ - this.dirtyId++; - this.dirtyFormat++; - - return this; - } - - enableDepth() - { - this.depth = true; - - this.dirtyId++; - this.dirtyFormat++; - - return this; - } - - enableStencil() - { - this.stencil = true; - - this.dirtyId++; - this.dirtyFormat++; - - return this; - } - - resize(width, height) - { - if (width === this.width && height === this.height) return; - - this.width = width; - this.height = height; - - this.dirtyId++; - this.dirtySize++; - - for (let i = 0; i < this.colorTextures.length; i++) - { - this.colorTextures[i].setSize(width, height); - } - - if (this.depthTexture) - { - this.depthTexture.setSize(width, height); - } - } -} diff --git a/packages/core/src/textures/GLTexture.js b/packages/core/src/textures/GLTexture.js new file mode 100644 index 0000000..c3fb8dc --- /dev/null +++ b/packages/core/src/textures/GLTexture.js @@ -0,0 +1,33 @@ +export default class GLTexture +{ + constructor(texture) + { + /** + * The WebGL texture + * + * @member {WebGLTexture} + */ + this.texture = texture; + + this.width = -1; + this.height = -1; + + /** + * Texture contents dirty flag + * @member {number} + */ + this.dirtyId = -1; + + /** + * Texture style dirty flag + * @type {number} + */ + this.dirtyStyleId = -1; + + /** + * Whether mip levels has to be generated + * @type {boolean} + */ + this.mipmap = false; + } +} diff --git a/packages/core/src/textures/TextureGCSystem.js b/packages/core/src/textures/TextureGCSystem.js new file mode 100644 index 0000000..6d07606 --- /dev/null +++ b/packages/core/src/textures/TextureGCSystem.js @@ -0,0 +1,111 @@ +import System from '../System'; +import { GC_MODES } from '@pixi/constants'; +import { settings } from '@pixi/settings'; + +/** + * TextureGarbageCollector. This class manages the GPU and ensures that it does not get clogged + * up with textures that are no longer being used. + * + * @class + * @memberof PIXI.systems + * @extends PIXI.System + */ +export default class TextureGCSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.count = 0; + this.checkCount = 0; + this.maxIdle = settings.GC_MAX_IDLE; + this.checkCountMax = settings.GC_MAX_CHECK_COUNT; + this.mode = settings.GC_MODE; + } + + /** + * Checks to see when the last time a texture was used + * if the texture has not been used for a specified amount of time it will be removed from the GPU + */ + postrender() + { + this.count++; + + if (this.mode === GC_MODES.MANUAL) + { + return; + } + + this.checkCount++; + + if (this.checkCount > this.checkCountMax) + { + this.checkCount = 0; + + this.run(); + } + } + + /** + * Checks to see when the last time a texture was used + * if the texture has not been used for a specified amount of time it will be removed from the GPU + */ + run() + { + const tm = this.renderer.texture; + const managedTextures = tm.managedTextures; + let wasRemoved = false; + + for (let i = 0; i < managedTextures.length; i++) + { + const texture = managedTextures[i]; + + // only supports non generated textures at the moment! + if (!texture.frameBuffer && this.count - texture.touched > this.maxIdle) + { + tm.destroyTexture(texture, true); + managedTextures[i] = null; + wasRemoved = true; + } + } + + if (wasRemoved) + { + let j = 0; + + for (let i = 0; i < managedTextures.length; i++) + { + if (managedTextures[i] !== null) + { + managedTextures[j++] = managedTextures[i]; + } + } + + managedTextures.length = j; + } + } + + /** + * Removes all the textures within the specified displayObject and its children from the GPU + * + * @param {PIXI.DisplayObject} displayObject - the displayObject to remove the textures from. + */ + unload(displayObject) + { + const tm = this.renderer.textureSystem; + + // only destroy non generated textures + if (displayObject._texture && displayObject._texture._glRenderTargets) + { + tm.destroyTexture(displayObject._texture); + } + + for (let i = displayObject.children.length - 1; i >= 0; i--) + { + this.unload(displayObject.children[i]); + } + } +} diff --git a/packages/core/src/textures/TextureSystem.js b/packages/core/src/textures/TextureSystem.js new file mode 100644 index 0000000..603f192 --- /dev/null +++ b/packages/core/src/textures/TextureSystem.js @@ -0,0 +1,281 @@ +import System from '../System'; +import GLTexture from './GLTexture'; +import { removeItems } from '@pixi/utils'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class TextureSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + // TODO set to max textures... + this.boundTextures = [ + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + ]; + + this.currentLocation = -1; + + this.managedTextures = []; + } + + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + contextChange() + { + const gl = this.gl = this.renderer.gl; + + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + + // TODO move this.. to a nice make empty textures class.. + this.emptyTextures = {}; + + const emptyTexture2D = new GLTexture(gl.createTexture()); + + gl.bindTexture(gl.TEXTURE_2D, emptyTexture2D.texture); + gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array(4)); + + this.emptyTextures[gl.TEXTURE_2D] = emptyTexture2D; + this.emptyTextures[gl.TEXTURE_CUBE_MAP] = new GLTexture(gl.createTexture()); + + gl.bindTexture(gl.TEXTURE_CUBE_MAP, this.emptyTextures[gl.TEXTURE_CUBE_MAP].texture); + + let i; + + for (i = 0; i < 6; i++) + { + gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, null); + } + + gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_MAG_FILTER, gl.LINEAR); + gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_MIN_FILTER, gl.LINEAR); + + for (i = 0; i < this.boundTextures.length; i++) + { + this.bind(null, i); + } + } + + bind(texture, location) + { + const gl = this.gl; + + location = location || 0; + + if (this.currentLocation !== location) + { + this.currentLocation = location; + gl.activeTexture(gl.TEXTURE0 + location); + } + + if (texture) + { + texture = texture.baseTexture || texture; + + if (texture.valid) + { + texture.touched = this.renderer.textureGC.count; + + const glTexture = texture._glTextures[this.CONTEXT_UID] || this.initTexture(texture); + + gl.bindTexture(texture.target, glTexture.texture); + + if (glTexture.dirtyId !== texture.dirtyId) + { + this.updateTexture(texture); + } + + this.boundTextures[location] = texture; + } + } + else + { + gl.bindTexture(gl.TEXTURE_2D, this.emptyTextures[gl.TEXTURE_2D].texture); + this.boundTextures[location] = null; + } + } + + unbind(texture) + { + const gl = this.gl; + + for (let i = 0; i < this.boundTextures.length; i++) + { + if (this.boundTextures[i] === texture) + { + if (this.currentLocation !== i) + { + gl.activeTexture(gl.TEXTURE0 + i); + this.currentLocation = i; + } + + gl.bindTexture(gl.TEXTURE_2D, this.emptyTextures[texture.target].texture); + this.boundTextures[i] = null; + } + } + } + + initTexture(texture) + { + const glTexture = new GLTexture(this.gl.createTexture()); + + // guarentee an update.. + glTexture.dirtyId = -1; + + texture._glTextures[this.CONTEXT_UID] = glTexture; + + this.managedTextures.push(texture); + texture.on('dispose', this.destroyTexture, this); + + return glTexture; + } + + updateTexture(texture) + { + const glTexture = texture._glTextures[this.CONTEXT_UID]; + const renderer = this.renderer; + + if (texture.resource && texture.resource.upload(renderer, texture, glTexture)) + { + // texture is uploaded, dont do anything! + } + else + { + // default, renderTexture-like logic + const width = texture.realWidth; + const height = texture.realHeight; + const gl = renderer.gl; + + if (glTexture.width !== width + || glTexture.height !== height + || glTexture.dirtyId < 0) + { + glTexture.width = width; + glTexture.height = height; + + gl.texImage2D(texture.target, 0, + texture.format, + width, + height, + 0, + texture.format, + texture.type, + null); + } + } + + // lets only update what changes.. + if (texture.dirtyStyleId !== glTexture.dirtyStyleId) + { + this.updateTextureStyle(texture); + } + glTexture.dirtyId = texture.dirtyId; + } + + /** + * Deletes the texture from WebGL + * + * @param {PIXI.BaseTexture|PIXI.Texture} texture - the texture to destroy + * @param {boolean} [skipRemove=false] - Whether to skip removing the texture from the TextureManager. + */ + destroyTexture(texture, skipRemove) + { + const gl = this.gl; + + texture = texture.baseTexture || texture; + + if (texture._glTextures[this.renderer.CONTEXT_UID]) + { + this.unbind(texture); + + gl.deleteTexture(texture._glTextures[this.renderer.CONTEXT_UID].texture); + texture.off('dispose', this.destroyTexture, this); + + delete texture._glTextures[this.renderer.CONTEXT_UID]; + + if (!skipRemove) + { + const i = this.managedTextures.indexOf(texture); + + if (i !== -1) + { + removeItems(this.managedTextures, i, 1); + } + } + } + } + + updateTextureStyle(texture) + { + const glTexture = texture._glTextures[this.CONTEXT_UID]; + + glTexture.mipmap = texture.mipmap && texture.isPowerOfTwo; + if (!glTexture) + { + return; + } + + if (texture.resource && texture.resource.style(this.renderer, texture, glTexture)) + { + // style is set, dont do anything! + } + else + { + this.setStyle(texture, glTexture); + } + + glTexture.dirtyStyleId = texture.dirtyStyleId; + } + + setStyle(texture, glTexture) + { + const gl = this.gl; + + if (glTexture.mipmap) + { + gl.generateMipmap(texture.target); + } + + gl.texParameteri(texture.target, gl.TEXTURE_WRAP_S, texture.wrapMode); + gl.texParameteri(texture.target, gl.TEXTURE_WRAP_T, texture.wrapMode); + + if (glTexture.mipmap) + { + /* eslint-disable max-len */ + gl.texParameteri(texture.target, gl.TEXTURE_MIN_FILTER, texture.scaleMode ? gl.LINEAR_MIPMAP_LINEAR : gl.NEAREST_MIPMAP_NEAREST); + /* eslint-disable max-len */ + } + else + { + gl.texParameteri(texture.target, gl.TEXTURE_MIN_FILTER, texture.scaleMode ? gl.LINEAR : gl.NEAREST); + } + + gl.texParameteri(texture.target, gl.TEXTURE_MAG_FILTER, texture.scaleMode ? gl.LINEAR : gl.NEAREST); + } +} diff --git a/packages/core/src/AbstractRenderer.js b/packages/core/src/AbstractRenderer.js new file mode 100644 index 0000000..64b2eb1 --- /dev/null +++ b/packages/core/src/AbstractRenderer.js @@ -0,0 +1,353 @@ +import { hex2string, hex2rgb } from '@pixi/utils'; +import { Matrix, Rectangle } from '@pixi/math'; +import { RENDERER_TYPE } from '@pixi/constants'; +import { settings } from '@pixi/settings'; +import { Container } from '@pixi/display'; +import RenderTexture from './renderTexture/RenderTexture'; +import EventEmitter from 'eventemitter3'; + +const tempMatrix = new Matrix(); + +/** + * 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 EventEmitter + * @memberof PIXI + */ +export default class AbstractRenderer extends EventEmitter +{ + // eslint-disable-next-line valid-jsdoc + /** + * @param {string} system - The name of the system this renderer is for. + * @param {object} [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.transparent=false] - If the render view is transparent, default false + * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false + * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. The + * resolution of the renderer retina would be 2. + * @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 {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when rendering, + * stopping pixel interpolation. + */ + constructor(system, options, arg2, arg3) + { + super(); + + // Support for constructor(system, screenWidth, screenHeight, options) + if (typeof options === 'number') + { + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + }, arg3); + } + + // 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 = RENDERER_TYPE.UNKNOWN; + + /** + * Measurements of the screen. (0, 0, screenWidth, screenHeight) + * + * Its safe to use as filterArea or hitArea for 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 1 + */ + this.resolution = options.resolution || settings.RESOLUTION; + + /** + * Whether the render view is transparent + * + * @member {boolean} + */ + this.transparent = options.transparent; + + /** + * Whether css dimensions of canvas view should be resized to screen dimensions automatically + * + * @member {boolean} + */ + this.autoResize = options.autoResize || false; + + /** + * Tracks the blend modes useful for this renderer. + * + * @member {object} + */ + this.blendModes = null; + + /** + * 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; + + /** + * If true PixiJS will Math.floor() x/y values when rendering, stopping pixel interpolation. + * Handy for crisp pixel art and speed on legacy devices. + * + * @member {boolean} + */ + this.roundPixels = options.roundPixels; + + /** + * The background color as a number. + * + * @member {number} + * @private + */ + this._backgroundColor = 0x000000; + + /** + * The background color as an [R, G, B] array. + * + * @member {number[]} + * @private + */ + this._backgroundColorRgba = [0, 0, 0, 0]; + + /** + * The background color as a string. + * + * @member {string} + * @private + */ + this._backgroundColorString = '#000000'; + + this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter + + /** + * This temporary display object used as the parent of the currently being rendered item + * + * @member {PIXI.DisplayObject} + * @private + */ + this._tempDisplayObjectParent = new Container(); + + /** + * The last root object that the renderer tried to render. + * + * @member {PIXI.DisplayObject} + * @private + */ + this._lastObjectRendered = this._tempDisplayObjectParent; + + /** + * Collection of plugins. + * @readonly + * @member {object} + */ + this.plugins = {}; + } + + /** + * Initialize the plugins. + * + * @protected + * @param {object} staticMap - The dictionary of staticly saved plugins. + */ + initPlugins(staticMap) + { + 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() + { + return this.view.width; + } + + /** + * Same as view.height, actual number of pixels in the canvas by vertical + * + * @member {number} + * @readonly + * @default 600 + */ + get height() + { + return this.view.height; + } + + /** + * Resizes the screen and canvas to the specified width and height + * Canvas dimensions are multiplied by resolution + * + * @param {number} screenWidth - the new width of the screen + * @param {number} screenHeight - the new height of the screen + */ + resize(screenWidth, screenHeight) + { + this.screen.width = screenWidth; + this.screen.height = screenHeight; + + this.view.width = screenWidth * this.resolution; + this.view.height = screenHeight * this.resolution; + + if (this.autoResize) + { + this.view.style.width = `${screenWidth}px`; + this.view.style.height = `${screenHeight}px`; + } + } + + /** + * 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 {PIXI.DisplayObject} displayObject - The displayObject the object will be generated from + * @param {number} scaleMode - Should be one of the scaleMode consts + * @param {number} resolution - The resolution / device pixel ratio of the texture being generated + * @param {PIXI.Rectangle} [region] - The region of the displayObject, that shall be rendered, + * if no region is specified, defaults to the local bounds of the displayObject. + * @return {PIXI.Texture} a texture of the graphics object + */ + generateTexture(displayObject, scaleMode, resolution, region) + { + region = region || displayObject.getLocalBounds(); + + // 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(region.width | 0, region.height | 0, scaleMode, resolution); + + tempMatrix.tx = -region.x; + tempMatrix.ty = -region.y; + + this.render(displayObject, renderTexture, false, tempMatrix, true); + + return renderTexture; + } + + /** + * Removes everything from the renderer and optionally removes the Canvas DOM element. + * + * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. + */ + destroy(removeView) + { + 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); + } + + this.plugins = null; + + this.type = RENDERER_TYPE.UNKNOWN; + + this.view = null; + + this.screen = null; + + this.resolution = 0; + + this.transparent = false; + + this.autoResize = false; + + this.blendModes = null; + + this.options = null; + + this.preserveDrawingBuffer = false; + this.clearBeforeRender = false; + + this.roundPixels = false; + + this._backgroundColor = 0; + this._backgroundColorRgba = null; + this._backgroundColorString = null; + + this._tempDisplayObjectParent = null; + this._lastObjectRendered = null; + } + + /** + * The background color to fill if not transparent + * + * @member {number} + */ + get backgroundColor() + { + return this._backgroundColor; + } + + set backgroundColor(value) // eslint-disable-line require-jsdoc + { + this._backgroundColor = value; + this._backgroundColorString = hex2string(value); + hex2rgb(value, this._backgroundColorRgba); + } +} diff --git a/packages/core/src/Renderer.js b/packages/core/src/Renderer.js new file mode 100644 index 0000000..2227e78 --- /dev/null +++ b/packages/core/src/Renderer.js @@ -0,0 +1,338 @@ +import AbstractRenderer from './AbstractRenderer'; +import { sayHello } from '@pixi/utils'; +import MaskSystem from './mask/MaskSystem'; +import StencilSystem from './mask/StencilSystem'; +import FilterSystem from './filters/FilterSystem'; +import FramebufferSystem from './framebuffer/FramebufferSystem'; +import RenderTextureSystem from './renderTexture/RenderTextureSystem'; +import TextureSystem from './textures/TextureSystem'; +import ProjectionSystem from './projection/ProjectionSystem'; +import StateSystem from './state/StateSystem'; +import GeometrySystem from './geometry/GeometrySystem'; +import ShaderSystem from './shader/ShaderSystem'; +import ContextSystem from './context/ContextSystem'; +import BatchSystem from './batch/BatchSystem'; +import TextureGCSystem from './textures/TextureGCSystem'; +import { RENDERER_TYPE } from '@pixi/constants'; +import UniformGroup from './shader/UniformGroup'; +import { Matrix } from '@pixi/math'; +import Runner from 'mini-runner'; + +/** + * 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 Render works by automatically managing webGLBatchs. + * 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 :) + * + * @class + * @memberof PIXI + * @extends PIXI.AbstractRenderer + */ +export default class Renderer extends AbstractRenderer +{ + // eslint-disable-next-line valid-jsdoc + /** + * + * @param {object} [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.transparent=false] - If the render view is transparent, default false + * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false + * @param {boolean} [options.antialias=false] - sets antialias. If not available natively then FXAA + * antialiasing is used + * @param {boolean} [options.forceFXAA=false] - forces FXAA antialiasing to be used over native. + * FXAA is faster, but may not always look as great + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. + * The resolution of the renderer retina would be 2. + * @param {boolean} [options.clearBeforeRender=true] - This sets if the renderer will clear + * the canvas or not before the new render pass. If you wish to set this to false, you *must* set + * preserveDrawingBuffer to `true`. + * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, + * enable this if you need to call toDataUrl on the webgl context. + * @param {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when + * rendering, stopping pixel interpolation. + * @param {number} [options.backgroundColor=0x000000] - The background color of the rendered area + * (shown if not transparent). + * @param {boolean} [options.legacy=false] - If true PixiJS will aim to ensure compatibility + * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. + * @param {string} [options.powerPreference] - Parameter passed to webgl context, set to "high-performance" + * for devices with dual graphics card + */ + constructor(options, arg2, arg3) + { + super('WebGL', options, arg2, arg3); + + /** + * The type of this renderer as a standardised const + * + * @member {number} + * @see PIXI.RENDERER_TYPE + */ + this.type = RENDERER_TYPE.WEBGL; + + // this will be set by the contextSystem (this.context) + this.gl = null; + this.CONTEXT_UID = 0; + this.legacy = !!options.legacy; + + // TODO legacy! + + // runners! + this.runners = { + destroy: new Runner('destroy'), + contextChange: new Runner('contextChange', 1), + reset: new Runner('reset'), + update: new Runner('update'), + postrender: new Runner('postrender'), + prerender: new Runner('prerender'), + resize: new Runner('resize', 2), + }; + + 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(GeometrySystem, 'geometry') + .addSystem(FramebufferSystem, 'framebuffer') + .addSystem(StencilSystem, 'stencil') + .addSystem(ProjectionSystem, 'projection') + .addSystem(TextureGCSystem, 'textureGC') + .addSystem(FilterSystem, 'filter') + .addSystem(RenderTextureSystem, 'renderTexture') + .addSystem(BatchSystem, 'batch'); + + this.initPlugins(Renderer.__plugins); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ + if (options.context) + { + this.context.initFromContext(options.context); + } + else + { + this.context.initFromOptions({ + alpha: this.transparent, + antialias: options.antialias, + premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', + stencil: true, + 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); + } + + /** + * Add a new system to the renderer. + * @param {class} ClassRef - Class reference + * @param {string} [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. + * @return {PIXI.Renderer} Return instance of renderer + */ + addSystem(ClassRef, name) + { + if (!name) + { + name = ClassRef.name; + } + + // TODO - read name from class.name.. + + /* + if(name.includes('System')) + { + name = name.replace('System', ''); + name = name.charAt(0).toLowerCase() + name.slice(1); + } + */ + + const system = new ClassRef(this); + + if (this[name]) + { + throw new Error(`Whoops! ${name} is already a manger`); + } + + this[name] = system; + + for (const i in this.runners) + { + this.runners[i].add(system); + } + + return this; + + /** + * 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. + */ + } + + /** + * Renders the object to its webGL view + * + * @param {PIXI.DisplayObject} displayObject - the object to be rendered + * @param {PIXI.RenderTexture} renderTexture - The render texture to render to. + * @param {boolean} [clear] - Should the canvas be cleared before the new render + * @param {PIXI.Transform} [transform] - A transform to apply to the render texture before rendering. + * @param {boolean} [skipUpdateTransform] - Should we skip the update transform pass? + */ + render(displayObject, renderTexture, clear, transform, skipUpdateTransform) + { + // can be handy to know! + this.renderingToScreen = !renderTexture; + + this.runners.prerender.run(); + this.emit('prerender'); + + // 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.parent; + + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = 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.run(); + + this.emit('postrender'); + } + + /** + * Resizes the webGL view to the specified width and height. + * + * @param {number} screenWidth - the new width of the screen + * @param {number} screenHeight - the new height of the screen + */ + resize(screenWidth, screenHeight) + { + AbstractRenderer.prototype.resize.call(this, screenWidth, screenHeight); + + this.runners.resize.run(screenWidth, screenHeight); + } + + /** + * Resets the WebGL state so you can render things however you fancy! + * + * @return {PIXI.Renderer} Returns itself. + */ + reset() + { + this.runners.reset.run(); + + return this; + } + + clear() + { + this.framebuffer.bind(); + this.framebuffer.clear(); + } + + /** + * Removes everything from the renderer (event listeners, spritebatch, etc...) + * + * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. + * See: https://github.com/pixijs/pixi.js/issues/2233 + */ + destroy(removeView) + { + this.runners.destroy.run(); + + // call base destroy + super.destroy(removeView); + + // TODO nullify all the managers.. + this.gl = null; + } + + /** + * 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 + * builds and excluding plugins. + * @name PIXI.Renderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. + */ + + /** + * Adds a plugin to the renderer. + * + * @method + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + static registerPlugin(pluginName, ctor) + { + Renderer.__plugins = Renderer.__plugins || {}; + Renderer.__plugins[pluginName] = ctor; + } +} diff --git a/packages/core/src/System.js b/packages/core/src/System.js new file mode 100644 index 0000000..05a10c8 --- /dev/null +++ b/packages/core/src/System.js @@ -0,0 +1,42 @@ +/** + * System is a base class used for extending systems used by the {@link PIXI.Renderer} + * @see PIXI.Renderer#addSystem + * @class + * @memberof PIXI + */ +export default class System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + /** + * The renderer this manager works for. + * + * @member {PIXI.Renderer} + */ + this.renderer = renderer; + + this.renderer.runners.contextChange.add(this); + } + + /** + * Generic method called when there is a WebGL context change. + * + */ + contextChange() + { + // do some codes init! + } + + /** + * Generic destroy methods to be overridden by the subclass + * + */ + destroy() + { + this.renderer.runners.contextChange.remove(this); + this.renderer = null; + } +} diff --git a/packages/core/src/batch/BatchSystem.js b/packages/core/src/batch/BatchSystem.js new file mode 100644 index 0000000..b04c249 --- /dev/null +++ b/packages/core/src/batch/BatchSystem.js @@ -0,0 +1,68 @@ +import System from '../System'; +import ObjectRenderer from './ObjectRenderer'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class BatchSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + super(renderer); + + /** + * An empty renderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.emptyRenderer = new ObjectRenderer(renderer); + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.currentRenderer = this.emptyRenderer; + } + + /** + * Changes the current renderer to the one given in parameter + * + * @param {PIXI.ObjectRenderer} objectRenderer - The object renderer to use. + */ + setObjectRenderer(objectRenderer) + { + if (this.currentRenderer === objectRenderer) + { + return; + } + + this.currentRenderer.stop(); + this.currentRenderer = objectRenderer; + + this.renderer.state.setState(objectRenderer.state); + + this.currentRenderer.start(); + } + + /** + * This should be called if you wish to do some custom rendering + * It will basically render anything that may be batched up such as sprites + * + */ + flush() + { + this.setObjectRenderer(this.emptyRenderer); + } + + reset() + { + this.setObjectRenderer(this.emptyRenderer); + } +} diff --git a/packages/core/src/batch/ObjectRenderer.js b/packages/core/src/batch/ObjectRenderer.js new file mode 100644 index 0000000..0c851a9 --- /dev/null +++ b/packages/core/src/batch/ObjectRenderer.js @@ -0,0 +1,48 @@ +import System from '../System'; + +/** + * Base for a common object renderer that can be used as a system renderer plugin. + * + * @class + * @extends PIXI.System + * @memberof PIXI + */ +export default class ObjectRenderer extends System +{ + /** + * Starts the renderer and sets the shader + * + */ + start() + { + // set the shader.. + } + + /** + * Stops the renderer + * + */ + stop() + { + this.flush(); + } + + /** + * Stub method for rendering content and emptying the current batch. + * + */ + flush() + { + // flush! + } + + /** + * Renders an object + * + * @param {PIXI.DisplayObject} object - The object to render. + */ + render(object) // eslint-disable-line no-unused-vars + { + // render the object + } +} diff --git a/packages/core/src/context/ContextSystem.js b/packages/core/src/context/ContextSystem.js new file mode 100644 index 0000000..301aab4 --- /dev/null +++ b/packages/core/src/context/ContextSystem.js @@ -0,0 +1,186 @@ +import System from '../System'; +import { settings } from '@pixi/settings'; + +let CONTEXT_UID = 0; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class ContextSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.webGLVersion = 1; + + this.handleContextLost = this.handleContextLost.bind(this); + this.handleContextRestored = this.handleContextRestored.bind(this); + + this.extensions = {}; + + renderer.view.addEventListener('webglcontextlost', this.handleContextLost, false); + renderer.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + } + + get isLost() + { + return (!this.gl || this.gl.isContextLost()); + } + + contextChange(gl) + { + this.gl = gl; + + // restore a context if it was previously lost + if (gl.isContextLost() && gl.getExtension('WEBGL_lose_context')) + { + gl.getExtension('WEBGL_lose_context').restoreContext(); + } + } + + initFromContext(gl) + { + this.gl = gl; + this.validateContext(gl); + this.renderer.gl = gl; + this.renderer.CONTEXT_UID = CONTEXT_UID++; + this.renderer.runners.contextChange.run(gl); + } + + initFromOptions(options) + { + const gl = this.createContext(this.renderer.view, options); + + this.initFromContext(gl); + } + + /** + * Helper class to create a webGL Context + * + * @class + * @memberof PIXI.glCore + * @param canvas {HTMLCanvasElement} the canvas element that we will get the context from + * @param options {Object} An options object that gets passed in to the canvas element containing the context attributes, + * see https://developer.mozilla.org/en/docs/Web/API/HTMLCanvasElement/getContext for the options available + * @return {WebGLRenderingContext} the WebGL context + */ + createContext(canvas, options) + { + let gl; + + if (settings.PREFER_WEBGL_2) + { + gl = canvas.getContext('webgl2', options); + } + + if (gl) + { + this.webGLVersion = 2; + } + else + { + this.webGLVersion = 1; + + gl = canvas.getContext('webgl', options) + || canvas.getContext('experimental-webgl', options); + + if (!gl) + { + // fail, not able to get a context + throw new Error('This browser does not support webGL. Try using the canvas renderer'); + } + } + + this.gl = gl; + + this.getExtensions(); + + return gl; + } + + getExtensions() + { + // time to set up default etensions that pixi uses.. + const gl = this.gl; + const extensions = this.extensions; + + if (this.webGLVersion === 1) + { + extensions.drawBuffers = gl.getExtension('WEBGL_draw_buffers'); + extensions.depthTexture = gl.getExtension('WEBKIT_WEBGL_depth_texture'); + extensions.floatTexture = gl.getExtension('OES_texture_float'); + extensions.loseContext = gl.getExtension('WEBGL_lose_context'); + + extensions.vertexArrayObject = gl.getExtension('OES_vertex_array_object') + || gl.getExtension('MOZ_OES_vertex_array_object') + || gl.getExtension('WEBKIT_OES_vertex_array_object'); + } + + // we don't use any specific WebGL 2 ones yet! + } + + /** + * Handles a lost webgl context + * + * @private + * @param {WebGLContextEvent} event - The context lost event. + */ + handleContextLost(event) + { + event.preventDefault(); + } + + /** + * Handles a restored webgl context + * + * @private + */ + handleContextRestored() + { + this.renderer.runners.contextChange.run(this.gl); + } + + destroy() + { + const view = this.renderer.view; + + // remove listeners + view.removeEventListener('webglcontextlost', this.handleContextLost); + view.removeEventListener('webglcontextrestored', this.handleContextRestored); + + this.gl.useProgram(null); + + if (this.extensions.loseContext) + { + this.extensions.loseContext.loseContext(); + } + } + + postrender() + { + this.gl.flush(); + } + + validateContext(gl) + { + const attributes = gl.getContextAttributes(); + + // this is going to be fairly simple for now.. but at least we have room to grow! + if (!attributes.stencil) + { + /* eslint-disable max-len */ + + /* eslint-disable no-console */ + console.warn('Provided WebGL context does not have a stencil buffer, masks may not render correctly'); + /* eslint-enable no-console */ + + /* eslint-enable max-len */ + } + } +} diff --git a/packages/core/src/filters/Filter.js b/packages/core/src/filters/Filter.js new file mode 100644 index 0000000..9d8cad8 --- /dev/null +++ b/packages/core/src/filters/Filter.js @@ -0,0 +1,140 @@ +import Shader from '../shader/Shader'; +import Program from '../shader/Program'; +import State from '../state/State'; +import { settings } from '@pixi/settings'; +// import extractUniformsFromSrc from './extractUniformsFromSrc'; +import defaultVertex from './defaultFilter.vert'; +import defaultFragment from './defaultFilter.frag'; + +// let math = require('../../math'); +/** + * @class + * @memberof PIXI + * @extends PIXI.Shader + */ +export default class Filter extends Shader +{ + /** + * @param {string} [vertexSrc] - The source of the vertex shader. + * @param {string} [fragmentSrc] - The source of the fragment shader. + * @param {object} [uniforms] - Custom uniforms to use to augment the built-in ones. + */ + constructor(vertexSrc, fragmentSrc, uniforms) + { + const program = Program.from(vertexSrc, fragmentSrc); + + super(program, uniforms); + + /** + * The padding of the filter. Some filters require extra space to breath such as a blur. + * Increasing this will add extra width and height to the bounds of the object that the + * filter is applied to. + * + * @member {number} + */ + this.padding = 0; + + /** + * The resolution of the filter. Setting this to be lower will lower the quality but + * increase the performance of the filter. + * + * @member {number} + */ + this.resolution = settings.RESOLUTION; + + /** + * If enabled is true the filter is applied, if false it will not. + * + * @member {boolean} + */ + this.enabled = true; + + /** + * If enabled, PixiJS will fit the filter area into boundaries for better performance. + * Switch it off if it does not work for specific shader. + * + * @member {boolean} + */ + this.autoFit = true; + + /** + * Legacy filters use position and uvs from attributes + * @member {boolean} + * @readonly + */ + this.legacy = !!this.program.attributeData.aTextureCoord; + + /** + * the webGL state the filter requires to render + * @member {PIXI.State} + */ + this.state = new State(); + } + + /** + * Applies the filter + * + * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + * @param {boolean} clear - Should the output be cleared before rendering to it + * @param {object} [currentState] - It's current state of filter. + * There are some useful properties in the currentState : + * target, filters, sourceFrame, destinationFrame, renderTarget, resolution + */ + apply(filterManager, input, output, clear, currentState, derp) // eslint-disable-line no-unused-vars + { + // do as you please! + + filterManager.applyFilter(this, input, output, clear, currentState, derp); + + // or just do a regular render.. + } + + /** + * Sets the blendmode of the filter + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL + */ + get blendMode() + { + return this.state.blendMode; + } + + set blendMode(value) // eslint-disable-line require-jsdoc + { + this.state.blendMode = value; + } + + /** + * The default vertex shader source + * + * @static + * @constant + */ + static get defaultVertexSrc() + { + return defaultVertex; + } + + /** + * The default fragment shader source + * + * @static + * @constant + */ + static get defaultFragmentSrc() + { + return defaultFragment; + } +} + +/** + * Used for caching shader IDs + * + * @static + * @private + */ +Filter.SOURCE_KEY_MAP = {}; + diff --git a/packages/core/src/filters/FilterSystem.js b/packages/core/src/filters/FilterSystem.js new file mode 100644 index 0000000..3174318 --- /dev/null +++ b/packages/core/src/filters/FilterSystem.js @@ -0,0 +1,431 @@ +import System from '../System'; + +import RenderTexture from '../renderTexture/RenderTexture'; +import Quad from '../utils/Quad'; +import QuadUv from '../utils/QuadUv'; +import { Rectangle } from '@pixi/math'; +import * as filterTransforms from './filterTransforms'; +import bitTwiddle from 'bit-twiddle'; +import UniformGroup from '../shader/UniformGroup'; +import { DRAW_MODES } from '@pixi/constants'; + +// +/** + * @ignore + * @class + */ +class FilterState +{ + /** + * + */ + constructor() + { + this.renderTexture = null; + this.sourceFrame = new Rectangle(); + this.destinationFrame = new Rectangle(); + this.filters = []; + this.target = null; + this.legacy = false; + this.resolution = 1; + } +} + +/** + * @class + * @memberof PIXI.systems + * @extends PIXI.System + */ +export default class FilterSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + /** + * stores a bunch of PO2 textures used for filtering + * @type {Object} + */ + this.texturePool = {}; + + /** + * a pool for storing filter states, save us creating new ones each tick + * @type {Array} + */ + this.statePool = []; + + /** + * A very simple geometry used when drawing a filter effect to the screen + * @type {Quad} + */ + this.quad = new Quad(); + + this.quadUv = new QuadUv(); + + /** + * Temporary rect for maths + * @type {PIXI.Rectangle} + */ + this.tempRect = new Rectangle(); + + this.activeState = {}; + + /** + * this uniform group is attached to filter uniforms when used + * @type {UniformGroup} + */ + this.globalUniforms = new UniformGroup({ + sourceFrame: this.tempRect, + destinationFrame: this.tempRect, + + // legacy variables + filterArea: new Float32Array(4), + filterClamp: new Float32Array(4), + }, true); + } + + /** + * Adds a new filter to the System. + * + * @param {PIXI.DisplayObject} target - The target of the filter to render. + * @param {PIXI.Filter[]} filters - The filters to apply. + */ + push(target, filters) + { + const renderer = this.renderer; + const filterStack = this.renderer.renderTexture.defaultFilterStack; + const state = this.statePool.pop() || new FilterState(); + + let resolution = filters[0].resolution; + let padding = filters[0].padding; + let autoFit = filters[0].autoFit; + let legacy = filters[0].legacy; + + for (let i = 1; i < filters.length; i++) + { + const filter = filters[i]; + + // lets use the lowest resolution.. + resolution = Math.min(resolution, filter.resolution); + // and the largest amount of padding! + padding = Math.max(padding, filter.padding); + // only auto fit if all filters are autofit + autoFit = autoFit || filter.autoFit; + + legacy = legacy || filter.legacy; + } + + filterStack.push(state); + + state.resolution = resolution; + + state.legacy = legacy; + + // round to whole number based on resolution + // TODO move that to the shader too? + state.sourceFrame = target.filterArea || target.getBounds(true); + + state.sourceFrame.pad(padding); + + if (autoFit) + { + state.sourceFrame.fit(this.renderer.renderTexture.destinationFrame); + } + + state.sourceFrame.round(resolution); + + state.renderTexture = this.getPotFilterTexture(state.sourceFrame.width, state.sourceFrame.height, resolution); + state.filters = filters; + + state.destinationFrame.width = state.renderTexture.width; + state.destinationFrame.height = state.renderTexture.height; + + state.renderTexture.filterFrame = state.sourceFrame; + renderer.renderTexture.bind(state.renderTexture, state.sourceFrame);// /, state.destinationFrame); + renderer.renderTexture.clear(); + } + + /** + * Pops off the filter and applies it. + * + */ + pop() + { + const renderer = this.renderer; + const filterStack = renderer.renderTexture.defaultFilterStack; + const state = filterStack.pop(); + const filters = state.filters; + + this.activeState = state; + + const globalUniforms = this.globalUniforms.uniforms; + + globalUniforms.sourceFrame = state.sourceFrame; + globalUniforms.destinationFrame = state.destinationFrame; + globalUniforms.resolution = state.resolution; + + // only update the rect if its legacy.. + if (state.legacy) + { + const filterArea = globalUniforms.filterArea; + const filterClamp = globalUniforms.filterClamp; + + filterArea[0] = state.destinationFrame.width; + filterArea[1] = state.destinationFrame.height; + filterArea[2] = state.sourceFrame.x; + filterArea[3] = state.sourceFrame.y; + + filterClamp[0] = 0.5 / state.resolution / state.destinationFrame.width; + filterClamp[1] = 0.5 / state.resolution / state.destinationFrame.height; + filterClamp[2] = (state.sourceFrame.width - 0.5) / state.resolution / state.destinationFrame.width; + filterClamp[3] = (state.sourceFrame.height - 0.5) / state.resolution / state.destinationFrame.height; + } + + this.globalUniforms.update(); + + const lastState = filterStack[filterStack.length - 1]; + + if (filters.length === 1) + { + filters[0].apply(this, state.renderTexture, lastState.renderTexture, false, state); + + this.returnFilterTexture(state.renderTexture); + } + else + { + let flip = state.renderTexture; + let flop = this.getPotFilterTexture( + flip.width, + flip.height, + state.resolution + ); + + flop.filterFrame = flip.filterFrame; + + let i = 0; + + for (i = 0; i < filters.length - 1; ++i) + { + filters[i].apply(this, flip, flop, true, state); + + const t = flip; + + flip = flop; + flop = t; + } + + filters[i].apply(this, flip, lastState.renderTexture, false, state); + + this.returnFilterTexture(flip); + this.returnFilterTexture(flop); + } + + this.statePool.push(state); + } + + /** + * Draws a filter. + * + * @param {PIXI.Filter} filter - The filter to draw. + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + * @param {boolean} clear - Should the output be cleared before rendering to it + */ + applyFilter(filter, input, output, clear) + { + const renderer = this.renderer; + + renderer.renderTexture.bind(output, output ? output.filterFrame : null); + + if (clear) + { + // gl.disable(gl.SCISSOR_TEST); + renderer.renderTexture.clear(); + // gl.enable(gl.SCISSOR_TEST); + } + + // set the uniforms.. + filter.uniforms.uSampler = input; + filter.uniforms.filterGlobals = this.globalUniforms; + + // TODO make it so that the order of this does not matter.. + // because it does at the moment cos of global uniforms. + // they need to get resynced + + renderer.state.setState(filter.state); + renderer.shader.bind(filter); + + if (filter.legacy) + { + this.quadUv.map(input._frame, input.filterFrame); + + renderer.geometry.bind(this.quadUv); + renderer.geometry.draw(DRAW_MODES.TRIANGLES); + } + else + { + renderer.geometry.bind(this.quad); + renderer.geometry.draw(DRAW_MODES.TRIANGLE_STRIP); + } + } + + /** + * Calculates the mapped matrix. + * + * TODO playing around here.. this is temporary - (will end up in the shader) + * this returns a matrix that will normalise map filter cords in the filter to screen space + * + * @param {PIXI.Matrix} outputMatrix - the matrix to output to. + * @return {PIXI.Matrix} The mapped matrix. + */ + calculateScreenSpaceMatrix(outputMatrix) + { + const currentState = this.activeState; + + return filterTransforms.calculateScreenSpaceMatrix( + outputMatrix, + currentState.sourceFrame, + currentState.destinationFrame + ); + } + + /** + * This will map the filter coord so that a texture can be used based on the transform of a sprite + * + * @param {PIXI.Matrix} outputMatrix - The matrix to output to. + * @param {PIXI.Sprite} sprite - The sprite to map to. + * @return {PIXI.Matrix} The mapped matrix. + */ + calculateSpriteMatrix(outputMatrix, sprite) + { + const currentState = this.activeState; + + return filterTransforms.calculateSpriteMatrix( + outputMatrix, + currentState.sourceFrame, + currentState.destinationFrame, + sprite + ); + } + + /** + * Destroys this Filter System. + * + * @param {boolean} [contextLost=false] context was lost, do not free shaders + * + */ + destroy(contextLost = false) + { + if (!contextLost) + { + this.emptyPool(); + } + else + { + this.texturePool = {}; + } + } + + /** + * Gets a Power-of-Two render texture. + * + * TODO move to a seperate class could be on renderer? + * also - could cause issue with multiple contexts? + * + * @private + * @param {WebGLRenderingContext} gl - The webgl rendering context + * @param {number} minWidth - The minimum width of the render target. + * @param {number} minHeight - The minimum height of the render target. + * @param {number} resolution - The resolution of the render target. + * @return {PIXI.RenderTarget} The new render target. + */ + getPotFilterTexture(minWidth, minHeight, resolution) + { + minWidth = bitTwiddle.nextPow2(minWidth); + minHeight = bitTwiddle.nextPow2(minHeight); + resolution = resolution || 1; + + const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + + if (!this.texturePool[key]) + { + this.texturePool[key] = []; + } + + let renderTexture = this.texturePool[key].pop(); + + if (!renderTexture) + { + // temporary bypass cache.. + // internally - this will cause a texture to be bound.. + renderTexture = RenderTexture.create({ + width: minWidth, + height: minHeight, + resolution, + }); + } + + return renderTexture; + } + + /** + * Gets extra render texture to use inside current filter + * + * @param {number} resolution resolution of the renderTexture + * @returns {PIXI.RenderTexture} + */ + getFilterTexture(resolution) + { + const rt = this.activeState.renderTexture; + + const filterTexture = this.getPotFilterTexture(rt.width, rt.height, resolution || rt.baseTexture.resolution); + + filterTexture.filterFrame = rt.filterFrame; + + return filterTexture; + } + + /** + * Frees a render texture back into the pool. + * + * @param {PIXI.RenderTarget} renderTexture - The renderTarget to free + */ + returnFilterTexture(renderTexture) + { + renderTexture.filterFrame = null; + + const base = renderTexture.baseTexture; + + const minWidth = base.width; + const minHeight = base.height; + + const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + + this.texturePool[key].push(renderTexture); + } + + /** + * Empties the texture pool. + * + */ + emptyPool() + { + for (const i in this.texturePool) + { + const textures = this.texturePool[i]; + + if (textures) + { + for (let j = 0; j < textures.length; j++) + { + textures[j].destroy(true); + } + } + } + + this.texturePool = {}; + } +} diff --git a/packages/core/src/filters/defaultFilter.frag b/packages/core/src/filters/defaultFilter.frag new file mode 100644 index 0000000..a14c1be --- /dev/null +++ b/packages/core/src/filters/defaultFilter.frag @@ -0,0 +1,22 @@ +varying vec2 vTextureCoord; +varying vec2 vFilterCoord; + +uniform sampler2D uSampler; +uniform sampler2D filterSampler; + +void main(void){ + vec4 masky = texture2D(filterSampler, vFilterCoord); + vec4 sample = texture2D(uSampler, vTextureCoord); + vec4 color; + if(mod(vFilterCoord.x, 1.0) > 0.5) + { + color = vec4(1.0, 0.0, 0.0, 1.0); + } + else + { + color = vec4(0.0, 1.0, 0.0, 1.0); + } + // gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0); + gl_FragColor = mix(sample, masky, 0.5); + gl_FragColor *= sample.a; +} diff --git a/packages/core/src/filters/defaultFilter.vert b/packages/core/src/filters/defaultFilter.vert new file mode 100644 index 0000000..e1febca --- /dev/null +++ b/packages/core/src/filters/defaultFilter.vert @@ -0,0 +1,14 @@ +attribute vec2 aVertexPosition; +attribute vec2 aTextureCoord; + +uniform mat3 projectionMatrix; +uniform mat3 filterMatrix; + +varying vec2 vTextureCoord; +varying vec2 vFilterCoord; + +void main(void){ + gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); + vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy; + vTextureCoord = aTextureCoord ; +} diff --git a/packages/core/src/filters/filterTransforms.js b/packages/core/src/filters/filterTransforms.js new file mode 100644 index 0000000..87a3cd6 --- /dev/null +++ b/packages/core/src/filters/filterTransforms.js @@ -0,0 +1,50 @@ +import { Matrix } from '@pixi/math'; + +/** + * Calculates the mapped matrix + * @param filterArea {Rectangle} The filter area + * @param sprite {Sprite} the target sprite + * @param outputMatrix {Matrix} @alvin + * @private + */ +// this returns a matrix that will normalise map filter cords in the filter to screen space +export function calculateScreenSpaceMatrix(outputMatrix, filterArea, textureSize) +{ + // TODO unwrap? + const mappedMatrix = outputMatrix.identity(); + + mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); + + mappedMatrix.scale(textureSize.width, textureSize.height); + + return mappedMatrix; +} + +export function calculateNormalizedScreenSpaceMatrix(outputMatrix, filterArea, textureSize) +{ + const mappedMatrix = outputMatrix.identity(); + + mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); + + const translateScaleX = (textureSize.width / filterArea.width); + const translateScaleY = (textureSize.height / filterArea.height); + + mappedMatrix.scale(translateScaleX, translateScaleY); + + return mappedMatrix; +} + +// this will map the filter coord so that a texture can be used based on the transform of a sprite +export function calculateSpriteMatrix(outputMatrix, filterArea, textureSize, sprite) +{ + const orig = sprite._texture.orig; + const mappedMatrix = outputMatrix.set(textureSize.width, 0, 0, textureSize.height, filterArea.x, filterArea.y); + const worldTransform = sprite.worldTransform.copyTo(Matrix.TEMP_MATRIX); + + worldTransform.invert(); + mappedMatrix.prepend(worldTransform); + mappedMatrix.scale(1.0 / orig.width, 1.0 / orig.height); + mappedMatrix.translate(sprite.anchor.x, sprite.anchor.y); + + return mappedMatrix; +} diff --git a/packages/core/src/filters/spriteMask/SpriteMaskFilter.js b/packages/core/src/filters/spriteMask/SpriteMaskFilter.js new file mode 100644 index 0000000..91697b7 --- /dev/null +++ b/packages/core/src/filters/spriteMask/SpriteMaskFilter.js @@ -0,0 +1,63 @@ +import Filter from '../Filter'; +import { Matrix } from '@pixi/math'; +import vertex from './spriteMaskFilter.vert'; +import fragment from './spriteMaskFilter.frag'; +import { default as TextureMatrix } from '../../textures/TextureMatrix'; + +/** + * The SpriteMaskFilter class + * + * @class + * @extends PIXI.Filter + * @memberof PIXI + */ +export default class SpriteMaskFilter extends Filter +{ + /** + * @param {PIXI.Sprite} sprite - the target sprite + */ + constructor(sprite) + { + const maskMatrix = new Matrix(); + + super(vertex, fragment); + + sprite.renderable = false; + + this.maskSprite = sprite; + this.maskMatrix = maskMatrix; + } + + /** + * Applies the filter + * + * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + */ + apply(filterManager, input, output) + { + const maskSprite = this.maskSprite; + const tex = this.maskSprite.texture; + + if (!tex.valid) + { + return; + } + if (!tex.transform) + { + // margin = 0.0, let it bleed a bit, shader code becomes easier + // assuming that atlas textures were made with 1-pixel padding + tex.transform = new TextureMatrix(tex, 0.0); + } + tex.transform.update(); + + this.uniforms.mask = tex; + this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite) + .prepend(tex.transform.mapCoord); + this.uniforms.alpha = maskSprite.worldAlpha; + this.uniforms.maskClamp = tex.transform.uClampFrame; + + filterManager.applyFilter(this, input, output); + } +} diff --git a/packages/core/src/filters/spriteMask/spriteMaskFilter.frag b/packages/core/src/filters/spriteMask/spriteMaskFilter.frag new file mode 100644 index 0000000..0e4aef8 --- /dev/null +++ b/packages/core/src/filters/spriteMask/spriteMaskFilter.frag @@ -0,0 +1,23 @@ +varying vec2 vMaskCoord; +varying vec2 vTextureCoord; + +uniform sampler2D uSampler; +uniform sampler2D mask; +uniform float alpha; +uniform vec4 maskClamp; + +void main(void) +{ + float clip = step(3.5, + step(maskClamp.x, vMaskCoord.x) + + step(maskClamp.y, vMaskCoord.y) + + step(vMaskCoord.x, maskClamp.z) + + step(vMaskCoord.y, maskClamp.w)); + + vec4 original = texture2D(uSampler, vTextureCoord); + vec4 masky = texture2D(mask, vMaskCoord); + + original *= (masky.r * masky.a * alpha * clip); + + gl_FragColor = original; +} diff --git a/packages/core/src/filters/spriteMask/spriteMaskFilter.vert b/packages/core/src/filters/spriteMask/spriteMaskFilter.vert new file mode 100644 index 0000000..d23a30c --- /dev/null +++ b/packages/core/src/filters/spriteMask/spriteMaskFilter.vert @@ -0,0 +1,16 @@ +attribute vec2 aVertexPosition; +attribute vec2 aTextureCoord; + +uniform mat3 projectionMatrix; +uniform mat3 otherMatrix; + +varying vec2 vMaskCoord; +varying vec2 vTextureCoord; + +void main(void) +{ + gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); + + vTextureCoord = aTextureCoord; + vMaskCoord = ( otherMatrix * vec3( aTextureCoord, 1.0) ).xy; +} diff --git a/packages/core/src/framebuffer/FrameBuffer.js b/packages/core/src/framebuffer/FrameBuffer.js new file mode 100644 index 0000000..84e9fcc --- /dev/null +++ b/packages/core/src/framebuffer/FrameBuffer.js @@ -0,0 +1,106 @@ +import Texture from '../textures/BaseTexture'; +import { FORMATS, TYPES } from '@pixi/constants'; + +/** + * Frame buffer + * @class + * @memberof PIXI + */ +export default class FrameBuffer +{ + constructor(width, height) + { + this.width = width || 100; + this.height = height || 100; + + this.stencil = false; + this.depth = false; + + this.dirtyId = 0; + this.dirtyFormat = 0; + this.dirtySize = 0; + + this.depthTexture = null; + this.colorTextures = []; + + this.glFrameBuffers = {}; + } + + get colorTexture() + { + return this.colorTextures[0]; + } + + addColorTexture(index, texture) + { + // TODO add some validation to the texture - same width / height etc? + this.colorTextures[index || 0] = texture || new Texture(null, { scaleMode: 0, + resolution: 1, + mipmap: false, + width: this.width, + height: this.height });// || new Texture(); + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + addDepthTexture(texture) + { + /* eslint-disable max-len */ + this.depthTexture = texture || new Texture(null, { scaleMode: 0, + resolution: 1, + width: this.width, + height: this.height, + mipmap: false, + format: FORMATS.DEPTH_COMPONENT, + type: TYPES.UNSIGNED_SHORT });// UNSIGNED_SHORT; + /* eslint-disable max-len */ + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + enableDepth() + { + this.depth = true; + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + enableStencil() + { + this.stencil = true; + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + resize(width, height) + { + if (width === this.width && height === this.height) return; + + this.width = width; + this.height = height; + + this.dirtyId++; + this.dirtySize++; + + for (let i = 0; i < this.colorTextures.length; i++) + { + this.colorTextures[i].setSize(width, height); + } + + if (this.depthTexture) + { + this.depthTexture.setSize(width, height); + } + } +} diff --git a/packages/core/src/framebuffer/FramebufferSystem.js b/packages/core/src/framebuffer/FramebufferSystem.js new file mode 100644 index 0000000..2cc2437 --- /dev/null +++ b/packages/core/src/framebuffer/FramebufferSystem.js @@ -0,0 +1,246 @@ +import System from '../System'; +import { Rectangle } from '@pixi/math'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class FramebufferSystem extends System +{ + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + contextChange() + { + this.gl = this.renderer.gl; + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + this.current = null; + this.viewport = new Rectangle(); + + this.drawBufferExtension = this.renderer.context.extensions.drawBuffers; + } + + bind(framebuffer, frame) + { + const gl = this.gl; + + this.current = framebuffer; + + if (framebuffer) + { + // TODO cacheing layer! + + const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID] || this.initFramebuffer(framebuffer); + + gl.bindFramebuffer(gl.FRAMEBUFFER, fbo.framebuffer); + // makesure all textures are unbound.. + + // now check for updates... + if (fbo.dirtyId !== framebuffer.dirtyId) + { + fbo.dirtyId = framebuffer.dirtyId; + + if (fbo.dirtyFormat !== framebuffer.dirtyFormat) + { + fbo.dirtyFormat = framebuffer.dirtyFormat; + this.updateFramebuffer(framebuffer); + } + else if (fbo.dirtySize !== framebuffer.dirtySize) + { + fbo.dirtySize = framebuffer.dirtySize; + this.resizeFramebuffer(framebuffer); + } + } + + for (let i = 0; i < framebuffer.colorTextures.length; i++) + { + if (framebuffer.colorTextures[i].texturePart) + { + this.renderer.texture.unbind(framebuffer.colorTextures[i].texture); + } + else + { + this.renderer.texture.unbind(framebuffer.colorTextures[i]); + } + } + + if (framebuffer.depthTexture) + { + this.renderer.texture.unbind(framebuffer.depthTexture); + } + + if (frame) + { + this.setViewport(frame.x, frame.y, frame.width, frame.height); + } + else + { + this.setViewport(0, 0, framebuffer.width, framebuffer.height); + } + } + else + { + gl.bindFramebuffer(gl.FRAMEBUFFER, null); + + if (frame) + { + this.setViewport(frame.x, frame.y, frame.width, frame.height); + } + else + { + this.setViewport(0, 0, this.renderer.width, this.renderer.height); + } + } + } + + setViewport(x, y, width, height) + { + const v = this.viewport; + + if (v.width !== width || v.height !== height || v.x !== x || v.y !== y) + { + v.x = x; + v.y = y; + v.width = width; + v.height = height; + + this.gl.viewport(x, y, width, height); + } + } + + get size() + { + if (this.current) + { + // TODO store temp + return { x: 0, y: 0, width: this.current.width, height: this.current.height }; + } + + return { x: 0, y: 0, width: this.renderer.width, height: this.renderer.height }; + } + + clear(r, g, b, a) + { + const gl = this.gl; + + // TODO clear color can be set only one right? + gl.clearColor(r, g, b, a); + gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); + } + + // private functions... + + initFramebuffer(framebuffer) + { + const gl = this.gl; + + // TODO - make this a class? + const fbo = { + framebuffer: gl.createFramebuffer(), + stencil: null, + dirtyId: 0, + dirtyFormat: 0, + dirtySize: 0, + }; + + framebuffer.glFrameBuffers[this.CONTEXT_UID] = fbo; + + return fbo; + } + + resizeFramebuffer(framebuffer) + { + const gl = this.gl; + + if (framebuffer.stencil || framebuffer.depth) + { + gl.bindRenderbuffer(gl.RENDERBUFFER, this.stencil); + gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); + } + } + + updateFramebuffer(framebuffer) + { + const gl = this.gl; + + const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID]; + + // bind the color texture + const colorTextures = framebuffer.colorTextures; + + let count = colorTextures.length; + + if (!this.drawBufferExtension) + { + count = Math.min(count, 1); + } + + const activeTextures = []; + + for (let i = 0; i < count; i++) + { + const texture = framebuffer.colorTextures[i]; + + if (texture.texturePart) + { + this.renderer.texture.bind(texture.texture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.COLOR_ATTACHMENT0 + i, + gl.TEXTURE_CUBE_MAP_NEGATIVE_X + texture.side, + texture.texture._glTextures[this.CONTEXT_UID].texture, + 0); + } + else + { + this.renderer.texture.bind(texture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.COLOR_ATTACHMENT0 + i, + gl.TEXTURE_2D, + texture._glTextures[this.CONTEXT_UID].texture, + 0); + } + + activeTextures.push(gl.COLOR_ATTACHMENT0 + i); + } + + if (this.drawBufferExtension && activeTextures.length > 1) + { + this.drawBufferExtension.drawBuffersWEBGL(activeTextures); + } + + if (framebuffer.depthTexture) + { + const depthTextureExt = this.renderer.context.extensions.depthTexture; + + if (depthTextureExt) + { + const depthTexture = framebuffer.depthTexture; + + this.renderer.texture.bind(depthTexture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.DEPTH_ATTACHMENT, + gl.TEXTURE_2D, + depthTexture._glTextures[this.CONTEXT_UID].texture, + 0); + } + } + + if (framebuffer.stencil || framebuffer.depth) + { + fbo.stencil = gl.createRenderbuffer(); + + gl.bindRenderbuffer(gl.RENDERBUFFER, fbo.stencil); + + // TODO.. this is depth AND stencil? + gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, fbo.stencil); + gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); + // fbo.enableStencil(); + } + } +} diff --git a/packages/core/src/geometry/GLBuffer.js b/packages/core/src/geometry/GLBuffer.js new file mode 100644 index 0000000..0477e8a --- /dev/null +++ b/packages/core/src/geometry/GLBuffer.js @@ -0,0 +1,9 @@ +export default class GLBuffer +{ + constructor(buffer) + { + this.buffer = buffer; + this.updateID = -1; + this.byteLength = -1; + } +} diff --git a/packages/core/src/geometry/GeometrySystem.js b/packages/core/src/geometry/GeometrySystem.js new file mode 100644 index 0000000..959f827 --- /dev/null +++ b/packages/core/src/geometry/GeometrySystem.js @@ -0,0 +1,388 @@ +import System from '../System'; +import GLBuffer from './GLBuffer'; + +const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class GeometrySystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this._activeGeometry = null; + this._activeVao = null; + + this.hasVao = true; + this.hasInstance = true; + } + + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + contextChange() + { + const gl = this.gl = this.renderer.gl; + + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + + // webgl2 + if (!gl.createVertexArray) + { + // webgl 1! + let nativeVaoExtension = this.renderer.context.extensions.vertexArrayObject; + + if (this.renderer.legacy) + { + nativeVaoExtension = null; + } + + if (nativeVaoExtension) + { + gl.createVertexArray = () => + nativeVaoExtension.createVertexArrayOES(); + + gl.bindVertexArray = (vao) => + nativeVaoExtension.bindVertexArrayOES(vao); + + gl.deleteVertexArray = (vao) => + nativeVaoExtension.deleteVertexArrayOES(vao); + } + else + { + this.hasVao = false; + gl.createVertexArray = () => + { + // empty + }; + + gl.bindVertexArray = () => + { + // empty + }; + + gl.deleteVertexArray = () => + { + // empty + }; + } + } + + if (!gl.vertexAttribDivisor) + { + const instanceExt = gl.getExtension('ANGLE_instanced_arrays'); + + if (instanceExt) + { + gl.vertexAttribDivisor = (a, b) => + instanceExt.vertexAttribDivisorANGLE(a, b); + + gl.drawElementsInstanced = (a, b, c, d, e) => + instanceExt.drawElementsInstancedANGLE(a, b, c, d, e); + + gl.drawArraysInstanced = (a, b, c, d) => + instanceExt.drawArraysInstancedANGLE(a, b, c, d); + } + else + { + this.hasInstance = false; + } + } + } + + /** + * Binds geometry so that is can be drawn. Creating a Vao if required + * @private + * @param {PIXI.Geometry} geometry instance of geometry to bind + */ + bind(geometry, shader) + { + shader = shader || this.renderer.shader.shader; + + const gl = this.gl; + + // not sure the best way to address this.. + // currently different shaders require different VAOs for the same geometry + // Still mulling over the best way to solve this one.. + // will likely need to modify the shader attribute locations at run time! + let vaos = geometry.glVertexArrayObjects[this.CONTEXT_UID]; + + if (!vaos) + { + geometry.glVertexArrayObjects[this.CONTEXT_UID] = vaos = {}; + } + + const vao = vaos[shader.program.id] || this.initGeometryVao(geometry, shader.program); + + this._activeGeometry = geometry; + + if (this._activeVao !== vao) + { + this._activeVao = vao; + + if (this.hasVao) + { + gl.bindVertexArray(vao); + } + else + { + this.activateVao(geometry, shader.program); + } + } + + // TODO - optimise later! + // don't need to loop through if nothing changed! + // maybe look to add an 'autoupdate' to geometry? + this.updateBuffers(); + } + + reset() + { + this.unbind(); + } + + updateBuffers() + { + const geometry = this._activeGeometry; + const gl = this.gl; + + for (let i = 0; i < geometry.buffers.length; i++) + { + const buffer = geometry.buffers[i]; + + const glBuffer = buffer._glBuffers[this.CONTEXT_UID]; + + if (buffer._updateID !== glBuffer.updateID) + { + glBuffer.updateID = buffer._updateID; + + // TODO can cache this on buffer! maybe added a getter / setter? + const type = buffer.index ? gl.ELEMENT_ARRAY_BUFFER : gl.ARRAY_BUFFER; + const drawType = buffer.static ? gl.STATIC_DRAW : gl.DYNAMIC_DRAW; + + gl.bindBuffer(type, glBuffer.buffer); + + if (glBuffer.byteLength >= buffer.data.byteLength) + { + // offset is always zero for now! + gl.bufferSubData(type, 0, buffer.data); + } + else + { + gl.bufferData(type, buffer.data, drawType); + } + } + } + } + + checkCompatability(geometry, program) + { + // geometry must have at least all the attributes that the shader requires. + const geometryAttributes = geometry.attributes; + const shaderAttributes = program.attributeData; + + for (const j in shaderAttributes) + { + if (!geometryAttributes[j]) + { + throw new Error(`shader and geometry incompatible, geometry missing the "${j}" attribute`); + } + } + } + + /** + * Creates a Vao with the same structure as the geometry and stores it on the geometry. + * @private + * @param {PIXI.Geometry} geometry instance of geometry to to generate Vao for + */ + initGeometryVao(geometry, program) + { + this.checkCompatability(geometry, program); + + const gl = this.gl; + const CONTEXT_UID = this.CONTEXT_UID; + const buffers = geometry.buffers; + const attributes = geometry.attributes; + + const tempStride = {}; + const tempStart = {}; + + for (const j in buffers) + { + tempStride[j] = 0; + tempStart[j] = 0; + } + + for (const j in attributes) + { + if (!attributes[j].size && program.attributeData[j]) + { + attributes[j].size = program.attributeData[j].size; + } + + tempStride[attributes[j].buffer] += attributes[j].size * byteSizeMap[attributes[j].type]; + } + + for (const j in attributes) + { + const attribute = attributes[j]; + const attribSize = attribute.size; + + if (attribute.stride === undefined) + { + if (tempStride[attribute.buffer] === attribSize * byteSizeMap[attribute.type]) + { + attribute.stride = 0; + } + else + { + attribute.stride = tempStride[attribute.buffer]; + } + } + + if (attribute.start === undefined) + { + attribute.start = tempStart[attribute.buffer]; + + tempStart[attribute.buffer] += attribSize * byteSizeMap[attribute.type]; + } + } + + // first update - and create the buffers! + // only create a gl buffer if it actually gets + for (let i = 0; i < buffers.length; i++) + { + const buffer = buffers[i]; + + if (!buffer._glBuffers[CONTEXT_UID]) + { + buffer._glBuffers[CONTEXT_UID] = new GLBuffer(gl.createBuffer()); + } + } + + // TODO - maybe make this a data object? + // lets wait to see if we need to first! + const vao = gl.createVertexArray(); + + gl.bindVertexArray(vao); + + this.activateVao(geometry, program); + + gl.bindVertexArray(null); + + // add it to the cache! + geometry.glVertexArrayObjects[this.CONTEXT_UID][program.id] = vao; + + return vao; + } + + activateVao(geometry, program) + { + const gl = this.gl; + const CONTEXT_UID = this.CONTEXT_UID; + const buffers = geometry.buffers; + const attributes = geometry.attributes; + + if (geometry.indexBuffer) + { + // first update the index buffer if we have one.. + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, geometry.indexBuffer._glBuffers[CONTEXT_UID].buffer); + } + + let lastBuffer = null; + + // add a new one! + for (const j in attributes) + { + const attribute = attributes[j]; + const buffer = buffers[attribute.buffer]; + const glBuffer = buffer._glBuffers[CONTEXT_UID]; + + if (program.attributeData[j]) + { + if (lastBuffer !== glBuffer) + { + gl.bindBuffer(gl.ARRAY_BUFFER, glBuffer.buffer); + + lastBuffer = glBuffer; + } + + const location = program.attributeData[j].location; + + // TODO introduce state again + // we can optimise this for older devices that have no VAOs + gl.enableVertexAttribArray(location); + + gl.vertexAttribPointer(location, + attribute.size, + attribute.type || gl.FLOAT, + attribute.normalized, + attribute.stride, + attribute.start); + + if (attribute.instance) + { + // TODO calculate instance count based of this... + if (this.hasInstance) + { + gl.vertexAttribDivisor(location, 1); + } + else + { + throw new Error('geometry error, GPU Instancing is not supported on this device'); + } + } + } + } + } + + draw(type, size, start, instanceCount) + { + const gl = this.gl; + const geometry = this._activeGeometry; + + // TODO.. this should not change so maybe cache the function? + + if (geometry.indexBuffer) + { + if (geometry.instanced) + { + /* eslint-disable max-len */ + gl.drawElementsInstanced(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2, instanceCount || 1); + /* eslint-enable max-len */ + } + else + { + gl.drawElements(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2); + } + } + else + if (geometry.instanced) + { + // TODO need a better way to calculate size.. + gl.drawArraysInstanced(type, start, size || geometry.getSize(), instanceCount || 1); + } + else + { + gl.drawArrays(type, start, size || geometry.getSize()); + } + + return this; + } + + unbind() + { + this.gl.bindVertexArray(null); + this._activeVao = null; + this._activeGeometry = null; + } +} diff --git a/packages/core/src/geometry/utils/setVertexAttribArrays.js b/packages/core/src/geometry/utils/setVertexAttribArrays.js new file mode 100644 index 0000000..23b6959 --- /dev/null +++ b/packages/core/src/geometry/utils/setVertexAttribArrays.js @@ -0,0 +1,54 @@ +// var GL_MAP = {}; + +/** + * @param gl {WebGLRenderingContext} The current WebGL context + * @param attribs {*} + * @param state {*} + */ +export default function setVertexAttribArrays(gl, attribs, state) +{ + let i; + + if (state) + { + const tempAttribState = state.tempAttribState; + const attribState = state.attribState; + + for (i = 0; i < tempAttribState.length; i++) + { + tempAttribState[i] = false; + } + + // set the new attribs + for (i = 0; i < attribs.length; i++) + { + tempAttribState[attribs[i].attribute.location] = true; + } + + for (i = 0; i < attribState.length; i++) + { + if (attribState[i] !== tempAttribState[i]) + { + attribState[i] = tempAttribState[i]; + + if (state.attribState[i]) + { + gl.enableVertexAttribArray(i); + } + else + { + gl.disableVertexAttribArray(i); + } + } + } + } + else + { + for (i = 0; i < attribs.length; i++) + { + const attrib = attribs[i]; + + gl.enableVertexAttribArray(attrib.attribute.location); + } + } +} diff --git a/packages/core/src/index.js b/packages/core/src/index.js index 58c456a..fd1f547 100644 --- a/packages/core/src/index.js +++ b/packages/core/src/index.js @@ -1,30 +1,30 @@ import * as resources from './textures/resources'; -import * as systems from './renderers/systems'; +import * as systems from './systems'; -export { resources }; export { systems }; +export { resources }; -export { default as Renderer } from './renderers/Renderer'; -export { default as AbstractRenderer } from './renderers/AbstractRenderer'; -export { default as FrameBuffer } from './textures/FrameBuffer'; +export { default as System } from './System'; +export { default as Renderer } from './Renderer'; +export { default as AbstractRenderer } from './AbstractRenderer'; +export { default as FrameBuffer } from './framebuffer/FrameBuffer'; export { default as CubeTexture } from './textures/CubeTexture'; export { default as BaseTexture } from './textures/BaseTexture'; export { default as Texture } from './textures/Texture'; export { default as TextureMatrix } from './textures/TextureMatrix'; -export { default as RenderTexture } from './textures/RenderTexture'; -export { default as BaseRenderTexture } from './textures/BaseRenderTexture'; +export { default as RenderTexture } from './renderTexture/RenderTexture'; +export { default as BaseRenderTexture } from './renderTexture/BaseRenderTexture'; export { default as TextureUvs } from './textures/TextureUvs'; -export { default as State } from './renderers/State'; -export { default as ObjectRenderer } from './renderers/utils/ObjectRenderer'; -export { default as Quad } from './renderers/utils/Quad'; -export { default as QuadUv } from './renderers/utils/QuadUv'; -export { default as checkMaxIfStatmentsInShader } from './renderers/utils/checkMaxIfStatmentsInShader'; +export { default as State } from './state/State'; +export { default as ObjectRenderer } from './batch/ObjectRenderer'; +export { default as Quad } from './utils/Quad'; +export { default as QuadUv } from './utils/QuadUv'; +export { default as checkMaxIfStatmentsInShader } from './shader/utils/checkMaxIfStatmentsInShader'; export { default as Shader } from './shader/Shader'; export { default as Program } from './shader/Program'; export { default as UniformGroup } from './shader/UniformGroup'; -export { default as SpriteMaskFilter } from './renderers/filters/spriteMask/SpriteMaskFilter'; -export { default as Filter } from './renderers/filters/Filter'; +export { default as SpriteMaskFilter } from './filters/spriteMask/SpriteMaskFilter'; +export { default as Filter } from './filters/Filter'; export { default as Attribute } from './geometry/Attribute'; export { default as Buffer } from './geometry/Buffer'; export { default as Geometry } from './geometry/Geometry'; - diff --git a/packages/core/src/mask/MaskSystem.js b/packages/core/src/mask/MaskSystem.js new file mode 100644 index 0000000..99174c2 --- /dev/null +++ b/packages/core/src/mask/MaskSystem.js @@ -0,0 +1,199 @@ +import System from '../System'; +import AlphaMaskFilter from '../filters/spriteMask/SpriteMaskFilter'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class MaskSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + // TODO - we don't need both! + this.scissor = false; + this.scissorData = null; + this.scissorRenderTarget = null; + + this.enableScissor = false; + + this.alphaMaskPool = []; + this.alphaMaskIndex = 0; + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.DisplayObject} target - Display Object to push the mask to + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + push(target, maskData) + { + // TODO the root check means scissor rect will not + // be used on render textures more info here: + // https://github.com/pixijs/pixi.js/pull/3545 + + if (maskData.texture) + { + this.pushSpriteMask(target, maskData); + } + else if (this.enableScissor + && !this.scissor + && this.renderer._activeRenderTarget.root + && !this.renderer.stencil.stencilMaskStack.length + && maskData.isFastRect()) + { + const matrix = maskData.worldTransform; + + let rot = Math.atan2(matrix.b, matrix.a); + + // use the nearest degree! + rot = Math.round(rot * (180 / Math.PI)); + + if (rot % 90) + { + this.pushStencilMask(maskData); + } + else + { + this.pushScissorMask(target, maskData); + } + } + else + { + this.pushStencilMask(maskData); + } + } + + /** + * Removes the last mask from the mask stack and doesn't return it. + * + * @param {PIXI.DisplayObject} target - Display Object to pop the mask from + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + pop(target, maskData) + { + if (maskData.texture) + { + this.popSpriteMask(target, maskData); + } + else if (this.enableScissor && !this.renderer.stencil.stencilMaskStack.length) + { + this.popScissorMask(target, maskData); + } + else + { + this.popStencilMask(target, maskData); + } + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.RenderTarget} target - Display Object to push the sprite mask to + * @param {PIXI.Sprite} maskData - Sprite to be used as the mask + */ + pushSpriteMask(target, maskData) + { + let alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; + + if (!alphaMaskFilter) + { + alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; + } + + alphaMaskFilter[0].resolution = this.renderer.resolution; + alphaMaskFilter[0].maskSprite = maskData; + + // TODO - may cause issues! + target.filterArea = maskData.getBounds(true); + + this.renderer.filterSystem.pushFilter(target, alphaMaskFilter); + + this.alphaMaskIndex++; + } + + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popSpriteMask() + { + this.renderer.filterSystem.popFilter(); + this.alphaMaskIndex--; + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + pushStencilMask(maskData) + { + this.renderer.batch.flush(); + this.renderer.stencil.pushStencil(maskData); + } + + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popStencilMask() + { + // this.renderer.currentRenderer.stop(); + this.renderer.stencil.popStencil(); + } + + /** + * + * @param {PIXI.DisplayObject} target - Display Object to push the mask to + * @param {PIXI.Graphics} maskData - The masking data. + */ + pushScissorMask(target, maskData) + { + maskData.renderable = true; + + const renderTarget = this.renderer._activeRenderTarget; + + const bounds = maskData.getBounds(); + + bounds.fit(renderTarget.size); + maskData.renderable = false; + + this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); + + const resolution = this.renderer.resolution; + + this.renderer.gl.scissor( + bounds.x * resolution, + (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, + bounds.width * resolution, + bounds.height * resolution + ); + + this.scissorRenderTarget = renderTarget; + this.scissorData = maskData; + this.scissor = true; + } + + /** + * + * + */ + popScissorMask() + { + this.scissorRenderTarget = null; + this.scissorData = null; + this.scissor = false; + + // must be scissor! + const gl = this.renderer.gl; + + gl.disable(gl.SCISSOR_TEST); + } +} diff --git a/packages/core/src/mask/StencilSystem.js b/packages/core/src/mask/StencilSystem.js new file mode 100644 index 0000000..62796a5 --- /dev/null +++ b/packages/core/src/mask/StencilSystem.js @@ -0,0 +1,133 @@ +import System from '../System'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class StencilSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + this.stencilMaskStack = []; + } + + /** + * Changes the mask stack that is used by this System. + * + * @param {PIXI.Graphics[]} stencilMaskStack - The mask stack + */ + setMaskStack(stencilMaskStack) + { + const gl = this.renderer.gl; + + if (stencilMaskStack.length !== this.stencilMaskStack.length) + { + if (stencilMaskStack.length === 0) + { + gl.disable(gl.STENCIL_TEST); + } + else + { + gl.enable(gl.STENCIL_TEST); + } + } + + this.stencilMaskStack = stencilMaskStack; + } + + /** + * Applies the Mask and adds it to the current stencil stack. @alvin + * + * @param {PIXI.Graphics} graphics - The mask + */ + pushStencil(graphics) + { + this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); + + // this.renderer._activeRenderTarget.attachStencilBuffer(); + + const gl = this.renderer.gl; + const prevMaskCount = this.stencilMaskStack.length; + + if (prevMaskCount === 0) + { + gl.enable(gl.STENCIL_TEST); + } + + this.stencilMaskStack.push(graphics); + + // Increment the refference stencil value where the new mask overlaps with the old ones. + gl.colorMask(false, false, false, false); + gl.stencilFunc(gl.EQUAL, prevMaskCount, this._getBitwiseMask()); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.INCR); + this.renderer.plugins.graphics.render(graphics); + + this._useCurrent(); + } + + /** + * Removes the last mask from the stencil stack. @alvin + */ + popStencil() + { + this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); + + const gl = this.renderer.gl; + const graphics = this.stencilMaskStack.pop(); + + if (this.stencilMaskStack.length === 0) + { + // the stack is empty! + gl.disable(gl.STENCIL_TEST); + gl.clear(gl.STENCIL_BUFFER_BIT); + gl.clearStencil(0); + } + else + { + // Decrement the refference stencil value where the popped mask overlaps with the other ones + gl.colorMask(false, false, false, false); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.DECR); + this.renderer.plugins.graphics.render(graphics); + + this._useCurrent(); + } + } + + /** + * Setup renderer to use the current stencil data. + */ + _useCurrent() + { + const gl = this.renderer.gl; + + gl.colorMask(true, true, true, true); + gl.stencilFunc(gl.EQUAL, this.stencilMaskStack.length, this._getBitwiseMask()); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP); + } + + /** + * Fill 1s equal to the number of acitve stencil masks. + * + * @return {number} The bitwise mask. + */ + _getBitwiseMask() + { + return (1 << this.stencilMaskStack.length) - 1; + } + + /** + * Destroys the mask stack. + * + */ + destroy() + { + super.destroy(this); + + this.stencilMaskStack = null; + } +} diff --git a/packages/core/src/projection/ProjectionSystem.js b/packages/core/src/projection/ProjectionSystem.js new file mode 100644 index 0000000..6e63b73 --- /dev/null +++ b/packages/core/src/projection/ProjectionSystem.js @@ -0,0 +1,73 @@ +import System from '../System'; +import { Matrix } from '@pixi/math'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class ProjectionSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.projectionMatrix = new Matrix(); + } + + update(destinationFrame, sourceFrame, resolution, root) + { + this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; + this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; + + this.calculateProjection(this.destinationFrame, this.sourceFrame, resolution, root); + + this.renderer.globalUniforms.uniforms.projectionMatrix = this.projectionMatrix; + this.renderer.globalUniforms.update(); + } + + /** + * Updates the projection matrix based on a projection frame (which is a rectangle) + * + * @param {Rectangle} destinationFrame - The destination frame. + * @param {Rectangle} sourceFrame - The source frame. + */ + calculateProjection(destinationFrame, sourceFrame, resolution, root) + { + const pm = this.projectionMatrix; + + // I don't think we will need this line.. + // pm.identity(); + + if (!root) + { + pm.a = (1 / destinationFrame.width * 2) * resolution; + pm.d = (1 / destinationFrame.height * 2) * resolution; + + pm.tx = -1 - (sourceFrame.x * pm.a); + pm.ty = -1 - (sourceFrame.y * pm.d); + } + else + { + pm.a = (1 / destinationFrame.width * 2) * resolution; + pm.d = (-1 / destinationFrame.height * 2) * resolution; + + pm.tx = -1 - (sourceFrame.x * pm.a); + pm.ty = 1 - (sourceFrame.y * pm.d); + } + } + + /** + * Sets the transform of the active render target to the given matrix + * + * @param {PIXI.Matrix} matrix - The transformation matrix + */ + setTransform()// matrix) + { + // this._activeRenderTarget.transform = matrix; + } +} diff --git a/packages/core/src/renderTexture/BaseRenderTexture.js b/packages/core/src/renderTexture/BaseRenderTexture.js new file mode 100644 index 0000000..88eb83b --- /dev/null +++ b/packages/core/src/renderTexture/BaseRenderTexture.js @@ -0,0 +1,137 @@ +import BaseTexture from '../textures/BaseTexture'; +import FrameBuffer from '../framebuffer/FrameBuffer'; + +/** + * A BaseRenderTexture is a special texture that allows any PixiJS display object to be rendered to it. + * + * __Hint__: All DisplayObjects (i.e. Sprites) that render to a BaseRenderTexture should be preloaded + * otherwise black rectangles will be drawn instead. + * + * A BaseRenderTexture takes a snapshot of any Display Object given to its render method. The position + * and rotation of the given Display Objects is ignored. For example: + * + * ```js + * let renderer = PIXI.autoDetectRenderer(1024, 1024, { view: canvas, ratio: 1 }); + * let baseRenderTexture = new PIXI.BaseRenderTexture(renderer, 800, 600); + * let sprite = PIXI.Sprite.fromImage("spinObj_01.png"); + * + * sprite.position.x = 800/2; + * sprite.position.y = 600/2; + * sprite.anchor.x = 0.5; + * sprite.anchor.y = 0.5; + * + * baseRenderTexture.render(sprite); + * ``` + * + * The Sprite in this case will be rendered using its local transform. To render this sprite at 0,0 + * you can clear the transform + * + * ```js + * + * sprite.setTransform() + * + * let baseRenderTexture = new PIXI.BaseRenderTexture(100, 100); + * let renderTexture = new PIXI.RenderTexture(baseRenderTexture); + * + * renderer.render(sprite, renderTexture); // Renders to center of RenderTexture + * ``` + * + * @class + * @extends PIXI.BaseTexture + * @memberof PIXI + */ +export default class BaseRenderTexture extends BaseTexture +{ + /** + * @param {object} [options] + * @param {number} [options.width=100] - The width of the base render texture + * @param {number} [options.height=100] - The height of the base render texture + * @param {PIXI.SCALE_MODES} [options.scaleMode] - See {@link PIXI.SCALE_MODES} for possible values + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the texture being generated + */ + constructor(options) + { + if (typeof options === 'number') + { + /* eslint-disable prefer-rest-params */ + // Backward compatibility of signature + const width = arguments[0]; + const height = arguments[1]; + const scaleMode = arguments[2]; + const resolution = arguments[3]; + + options = { width, height, scaleMode, resolution }; + /* eslint-enable prefer-rest-params */ + } + + super(null, options); + + const { width, height } = options || {}; + + // Set defaults + this.mipmap = false; + this.width = Math.ceil(width) || 100; + this.height = Math.ceil(height) || 100; + this.valid = true; + + /** + * A map of renderer IDs to webgl renderTargets + * + * @private + * @member {object} + */ + // this._glRenderTargets = {}; + + /** + * A reference to the canvas render target (we only need one as this can be shared across renderers) + * + * @private + * @member {object} + */ + this._canvasRenderTarget = null; + + this.clearColor = [0, 0, 0, 0]; + + this.frameBuffer = new FrameBuffer(width * this.resolution, height * this.resolution) + .addColorTexture(0, this); + + // TODO - could this be added the systems? + + /** + * The data structure for the stencil masks + * + * @member {PIXI.Graphics[]} + */ + this.stencilMaskStack = []; + + /** + * The data structure for the filters + * + * @member {PIXI.Graphics[]} + */ + this.filterStack = [{}]; + } + + /** + * Resizes the BaseRenderTexture. + * + * @param {number} width - The width to resize to. + * @param {number} height - The height to resize to. + */ + resize(width, height) + { + width = Math.ceil(width); + height = Math.ceil(height); + this.frameBuffer.resize(width * this.resolution, height * this.resolution); + } + + /** + * Destroys this texture + * + */ + destroy() + { + super.destroy(true); + this.renderer = null; + } +} diff --git a/packages/core/src/renderTexture/RenderTexture.js b/packages/core/src/renderTexture/RenderTexture.js new file mode 100644 index 0000000..f1ecf1f --- /dev/null +++ b/packages/core/src/renderTexture/RenderTexture.js @@ -0,0 +1,153 @@ +import BaseRenderTexture from './BaseRenderTexture'; +import Texture from '../textures/Texture'; + +/** + * A RenderTexture is a special texture that allows any PixiJS display object to be rendered to it. + * + * __Hint__: All DisplayObjects (i.e. Sprites) that render to a RenderTexture should be preloaded + * otherwise black rectangles will be drawn instead. + * + * A RenderTexture takes a snapshot of any Display Object given to its render method. For example: + * + * ```js + * let renderer = PIXI.autoDetectRenderer(1024, 1024, { view: canvas, ratio: 1 }); + * let renderTexture = PIXI.RenderTexture.create(800, 600); + * let sprite = PIXI.Sprite.fromImage("spinObj_01.png"); + * + * sprite.position.x = 800/2; + * sprite.position.y = 600/2; + * sprite.anchor.x = 0.5; + * sprite.anchor.y = 0.5; + * + * renderer.render(sprite, renderTexture); + * ``` + * + * The Sprite in this case will be rendered using its local transform. To render this sprite at 0,0 + * you can clear the transform + * + * ```js + * + * sprite.setTransform() + * + * let renderTexture = new PIXI.RenderTexture.create(100, 100); + * + * renderer.render(sprite, renderTexture); // Renders to center of RenderTexture + * ``` + * + * @class + * @extends PIXI.Texture + * @memberof PIXI + */ +export default class RenderTexture extends Texture +{ + /** + * @param {PIXI.BaseRenderTexture} baseRenderTexture - The renderer used for this RenderTexture + * @param {PIXI.Rectangle} [frame] - The rectangle frame of the texture to show + */ + constructor(baseRenderTexture, frame) + { + // support for legacy.. + let _legacyRenderer = null; + + if (!(baseRenderTexture instanceof BaseRenderTexture)) + { + /* eslint-disable prefer-rest-params, no-console */ + const width = arguments[1]; + const height = arguments[2]; + const scaleMode = arguments[3]; + const resolution = arguments[4]; + + // we have an old render texture.. + console.warn(`Please use RenderTexture.create(${width}, ${height}) instead of the ctor directly.`); + _legacyRenderer = arguments[0]; + /* eslint-enable prefer-rest-params, no-console */ + + frame = null; + baseRenderTexture = new BaseRenderTexture({ + width, + height, + scaleMode, + resolution, + }); + } + + /** + * The base texture object that this texture uses + * + * @member {BaseTexture} + */ + super(baseRenderTexture, frame); + + this.legacyRenderer = _legacyRenderer; + + /** + * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. + * + * @member {boolean} + */ + this.valid = true; + + /** + * FilterSystem temporary storage + * @private + * @member {PIXI.Rectangle} + */ + this.filterFrame = null; + + this._updateUvs(); + } + + /** + * Resizes the RenderTexture. + * + * @param {number} width - The width to resize to. + * @param {number} height - The height to resize to. + * @param {boolean} [resizeBaseTexture=true] - Should the baseTexture.width and height values be resized as well? + */ + resize(width, height, resizeBaseTexture = true) + { + width = Math.ceil(width); + height = Math.ceil(height); + + // TODO - could be not required.. + this.valid = (width > 0 && height > 0); + + this._frame.width = this.orig.width = width; + this._frame.height = this.orig.height = height; + + if (resizeBaseTexture) + { + this.baseTexture.resize(width, height); + } + + this._updateUvs(); + } + + /** + * A short hand way of creating a render texture. + * + * @param {object} [options] - Options + * @param {number} [options.width=100] - The width of the render texture + * @param {number} [options.height=100] - The height of the render texture + * @param {number} [options.scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the texture being generated + * @return {PIXI.RenderTexture} The new render texture + */ + static create(options) + { + // fallback, old-style: create(width, height, scaleMode, resolution) + if (typeof options === 'number') + { + /* eslint-disable prefer-rest-params */ + options = { + width: options, + height: arguments[1], + scaleMode: arguments[2], + resolution: arguments[3], + }; + /* eslint-enable prefer-rest-params */ + } + + return new RenderTexture(new BaseRenderTexture(options)); + } +} diff --git a/packages/core/src/renderTexture/RenderTextureSystem.js b/packages/core/src/renderTexture/RenderTextureSystem.js new file mode 100644 index 0000000..8d0ca61 --- /dev/null +++ b/packages/core/src/renderTexture/RenderTextureSystem.js @@ -0,0 +1,115 @@ +import System from '../System'; +import { Rectangle } from '@pixi/math'; + +const tempRect = new Rectangle(); + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class RenderTextureSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.clearColor = renderer._backgroundColorRgba; + + // TODO moe this property somewhere else! + this.defaultMaskStack = []; + this.defaultFilterStack = [{}]; + + // empty render texture? + this.renderTexture = null; + + this.destinationFrame = new Rectangle(); + } + + bind(renderTexture, sourceFrame, destinationFrame) + { + // TODO - do we want this?? + if (this.renderTexture === renderTexture) return; + this.renderTexture = renderTexture; + + const renderer = this.renderer; + + if (renderTexture) + { + const baseTexture = renderTexture.baseTexture; + + if (!destinationFrame) + { + tempRect.width = baseTexture.realWidth; + tempRect.height = baseTexture.realHeight; + + destinationFrame = tempRect; + } + + if (!sourceFrame) + { + sourceFrame = destinationFrame; + } + + this.renderer.framebuffer.bind(baseTexture.frameBuffer, destinationFrame); + + this.renderer.projection.update(destinationFrame, sourceFrame, baseTexture.resolution, false); + this.renderer.stencil.setMaskStack(baseTexture.stencilMaskStack); + } + else + { + // TODO these validation checks happen deeper down.. + // thing they can be avoided.. + if (!destinationFrame) + { + tempRect.width = renderer.width; + tempRect.height = renderer.height; + + destinationFrame = tempRect; + } + + if (!sourceFrame) + { + sourceFrame = destinationFrame; + } + + renderer.framebuffer.bind(null, destinationFrame); + + // TODO store this.. + this.renderer.projection.update(destinationFrame, sourceFrame, this.renderer.resolution, true); + this.renderer.stencil.setMaskStack(this.defaultMaskStack); + } + + this.destinationFrame.copyFrom(destinationFrame); + } + + /** + * Erases the render texture and fills the drawing area with a colour + * + * @param {number} [clearColor] - The colour + * @return {PIXI.Renderer} Returns itself. + */ + clear(clearColor) + { + if (this.renderTexture) + { + clearColor = clearColor || this.renderTexture.baseTexture.clearColor; + } + else + { + clearColor = clearColor || this.clearColor; + } + + this.renderer.framebuffer.clear(clearColor[0], clearColor[1], clearColor[2], clearColor[3]); + } + + resize()// screenWidth, screenHeight) + { + // resize the root only! + this.bind(null); + } +} diff --git a/packages/core/src/renderers/AbstractRenderer.js b/packages/core/src/renderers/AbstractRenderer.js deleted file mode 100644 index 928670f..0000000 --- a/packages/core/src/renderers/AbstractRenderer.js +++ /dev/null @@ -1,353 +0,0 @@ -import { hex2string, hex2rgb } from '@pixi/utils'; -import { Matrix, Rectangle } from '@pixi/math'; -import { RENDERER_TYPE } from '@pixi/constants'; -import { settings } from '@pixi/settings'; -import { Container } from '@pixi/display'; -import RenderTexture from '../textures/RenderTexture'; -import EventEmitter from 'eventemitter3'; - -const tempMatrix = new Matrix(); - -/** - * 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 EventEmitter - * @memberof PIXI - */ -export default class AbstractRenderer extends EventEmitter -{ - // eslint-disable-next-line valid-jsdoc - /** - * @param {string} system - The name of the system this renderer is for. - * @param {object} [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.transparent=false] - If the render view is transparent, default false - * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false - * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) - * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. The - * resolution of the renderer retina would be 2. - * @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 {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when rendering, - * stopping pixel interpolation. - */ - constructor(system, options, arg2, arg3) - { - super(); - - // Support for constructor(system, screenWidth, screenHeight, options) - if (typeof options === 'number') - { - options = Object.assign({ - width: options, - height: arg2 || settings.RENDER_OPTIONS.height, - }, arg3); - } - - // 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 = RENDERER_TYPE.UNKNOWN; - - /** - * Measurements of the screen. (0, 0, screenWidth, screenHeight) - * - * Its safe to use as filterArea or hitArea for 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 1 - */ - this.resolution = options.resolution || settings.RESOLUTION; - - /** - * Whether the render view is transparent - * - * @member {boolean} - */ - this.transparent = options.transparent; - - /** - * Whether css dimensions of canvas view should be resized to screen dimensions automatically - * - * @member {boolean} - */ - this.autoResize = options.autoResize || false; - - /** - * Tracks the blend modes useful for this renderer. - * - * @member {object} - */ - this.blendModes = null; - - /** - * 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; - - /** - * If true PixiJS will Math.floor() x/y values when rendering, stopping pixel interpolation. - * Handy for crisp pixel art and speed on legacy devices. - * - * @member {boolean} - */ - this.roundPixels = options.roundPixels; - - /** - * The background color as a number. - * - * @member {number} - * @private - */ - this._backgroundColor = 0x000000; - - /** - * The background color as an [R, G, B] array. - * - * @member {number[]} - * @private - */ - this._backgroundColorRgba = [0, 0, 0, 0]; - - /** - * The background color as a string. - * - * @member {string} - * @private - */ - this._backgroundColorString = '#000000'; - - this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter - - /** - * This temporary display object used as the parent of the currently being rendered item - * - * @member {PIXI.DisplayObject} - * @private - */ - this._tempDisplayObjectParent = new Container(); - - /** - * The last root object that the renderer tried to render. - * - * @member {PIXI.DisplayObject} - * @private - */ - this._lastObjectRendered = this._tempDisplayObjectParent; - - /** - * Collection of plugins. - * @readonly - * @member {object} - */ - this.plugins = {}; - } - - /** - * Initialize the plugins. - * - * @protected - * @param {object} staticMap - The dictionary of staticly saved plugins. - */ - initPlugins(staticMap) - { - 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() - { - return this.view.width; - } - - /** - * Same as view.height, actual number of pixels in the canvas by vertical - * - * @member {number} - * @readonly - * @default 600 - */ - get height() - { - return this.view.height; - } - - /** - * Resizes the screen and canvas to the specified width and height - * Canvas dimensions are multiplied by resolution - * - * @param {number} screenWidth - the new width of the screen - * @param {number} screenHeight - the new height of the screen - */ - resize(screenWidth, screenHeight) - { - this.screen.width = screenWidth; - this.screen.height = screenHeight; - - this.view.width = screenWidth * this.resolution; - this.view.height = screenHeight * this.resolution; - - if (this.autoResize) - { - this.view.style.width = `${screenWidth}px`; - this.view.style.height = `${screenHeight}px`; - } - } - - /** - * 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 {PIXI.DisplayObject} displayObject - The displayObject the object will be generated from - * @param {number} scaleMode - Should be one of the scaleMode consts - * @param {number} resolution - The resolution / device pixel ratio of the texture being generated - * @param {PIXI.Rectangle} [region] - The region of the displayObject, that shall be rendered, - * if no region is specified, defaults to the local bounds of the displayObject. - * @return {PIXI.Texture} a texture of the graphics object - */ - generateTexture(displayObject, scaleMode, resolution, region) - { - region = region || displayObject.getLocalBounds(); - - // 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(region.width | 0, region.height | 0, scaleMode, resolution); - - tempMatrix.tx = -region.x; - tempMatrix.ty = -region.y; - - this.render(displayObject, renderTexture, false, tempMatrix, true); - - return renderTexture; - } - - /** - * Removes everything from the renderer and optionally removes the Canvas DOM element. - * - * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. - */ - destroy(removeView) - { - 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); - } - - this.plugins = null; - - this.type = RENDERER_TYPE.UNKNOWN; - - this.view = null; - - this.screen = null; - - this.resolution = 0; - - this.transparent = false; - - this.autoResize = false; - - this.blendModes = null; - - this.options = null; - - this.preserveDrawingBuffer = false; - this.clearBeforeRender = false; - - this.roundPixels = false; - - this._backgroundColor = 0; - this._backgroundColorRgba = null; - this._backgroundColorString = null; - - this._tempDisplayObjectParent = null; - this._lastObjectRendered = null; - } - - /** - * The background color to fill if not transparent - * - * @member {number} - */ - get backgroundColor() - { - return this._backgroundColor; - } - - set backgroundColor(value) // eslint-disable-line require-jsdoc - { - this._backgroundColor = value; - this._backgroundColorString = hex2string(value); - hex2rgb(value, this._backgroundColorRgba); - } -} diff --git a/packages/core/src/renderers/Renderer.js b/packages/core/src/renderers/Renderer.js deleted file mode 100644 index a2020b1..0000000 --- a/packages/core/src/renderers/Renderer.js +++ /dev/null @@ -1,329 +0,0 @@ -import AbstractRenderer from './AbstractRenderer'; -import { sayHello } from '@pixi/utils'; -import MaskSystem from './systems/MaskSystem'; -import StencilSystem from './systems/StencilSystem'; -import FilterSystem from './systems/filter/FilterSystem'; -import FramebufferSystem from './systems/FramebufferSystem'; -import RenderTextureSystem from './systems/RenderTextureSystem'; -import TextureSystem from './systems/textures/TextureSystem'; -import ProjectionSystem from './systems/ProjectionSystem'; -import StateSystem from './systems/StateSystem'; -import GeometrySystem from './systems/geometry/GeometrySystem'; -import ShaderSystem from './systems/shader/ShaderSystem'; -import ContextSystem from './systems/ContextSystem'; -import BatchSystem from './systems/BatchSystem'; -import TextureGCSystem from './systems/textures/TextureGCSystem'; -import { RENDERER_TYPE } from '@pixi/constants'; -import UniformGroup from '../shader/UniformGroup'; -import { Matrix } from '@pixi/math'; -import Runner from 'mini-runner'; - -/** - * 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 Render works by automatically managing webGLBatchs. - * 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 :) - * - * @class - * @memberof PIXI - * @extends PIXI.AbstractRenderer - */ -export default class Renderer extends AbstractRenderer -{ - // eslint-disable-next-line valid-jsdoc - /** - * - * @param {object} [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.transparent=false] - If the render view is transparent, default false - * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false - * @param {boolean} [options.antialias=false] - sets antialias. If not available natively then FXAA - * antialiasing is used - * @param {boolean} [options.forceFXAA=false] - forces FXAA antialiasing to be used over native. - * FXAA is faster, but may not always look as great - * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. - * The resolution of the renderer retina would be 2. - * @param {boolean} [options.clearBeforeRender=true] - This sets if the renderer will clear - * the canvas or not before the new render pass. If you wish to set this to false, you *must* set - * preserveDrawingBuffer to `true`. - * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, - * enable this if you need to call toDataUrl on the webgl context. - * @param {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when - * rendering, stopping pixel interpolation. - * @param {number} [options.backgroundColor=0x000000] - The background color of the rendered area - * (shown if not transparent). - * @param {boolean} [options.legacy=false] - If true PixiJS will aim to ensure compatibility - * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. - * @param {string} [options.powerPreference] - Parameter passed to webgl context, set to "high-performance" - * for devices with dual graphics card - */ - constructor(options, arg2, arg3) - { - super('WebGL', options, arg2, arg3); - - /** - * The type of this renderer as a standardised const - * - * @member {number} - * @see PIXI.RENDERER_TYPE - */ - this.type = RENDERER_TYPE.WEBGL; - - // this will be set by the contextSystem (this.context) - this.gl = null; - this.CONTEXT_UID = 0; - this.legacy = !!options.legacy; - - // TODO legacy! - - // runners! - this.runners = { - destroy: new Runner('destroy'), - contextChange: new Runner('contextChange', 1), - reset: new Runner('reset'), - update: new Runner('update'), - postrender: new Runner('postrender'), - prerender: new Runner('prerender'), - resize: new Runner('resize', 2), - }; - - 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(GeometrySystem, 'geometry') - .addSystem(FramebufferSystem, 'framebuffer') - .addSystem(StencilSystem, 'stencil') - .addSystem(ProjectionSystem, 'projection') - .addSystem(TextureGCSystem, 'textureGC') - .addSystem(FilterSystem, 'filter') - .addSystem(RenderTextureSystem, 'renderTexture') - .addSystem(BatchSystem, 'batch'); - - this.initPlugins(Renderer.__plugins); - - /** - * The options passed in to create a new webgl context. - * - * @member {object} - * @private - */ - if (options.context) - { - this.context.initFromContext(options.context); - } - else - { - this.context.initFromOptions({ - alpha: this.transparent, - antialias: options.antialias, - premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', - stencil: true, - 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); - } - - addSystem(_class, name) - { - if (!name) - { - name = _class.name; - } - - // TODO - read name from class.name.. - - /* - if(name.includes('System')) - { - name = name.replace('System', ''); - name = name.charAt(0).toLowerCase() + name.slice(1); - } - */ - - const system = new _class(this); - - if (this[name]) - { - throw new Error(`Whoops! ${name} is already a manger`); - } - - this[name] = system; - - for (const i in this.runners) - { - this.runners[i].add(system); - } - - return this; - - /** - * 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. - */ - } - - /** - * Renders the object to its webGL view - * - * @param {PIXI.DisplayObject} displayObject - the object to be rendered - * @param {PIXI.RenderTexture} renderTexture - The render texture to render to. - * @param {boolean} [clear] - Should the canvas be cleared before the new render - * @param {PIXI.Transform} [transform] - A transform to apply to the render texture before rendering. - * @param {boolean} [skipUpdateTransform] - Should we skip the update transform pass? - */ - render(displayObject, renderTexture, clear, transform, skipUpdateTransform) - { - // can be handy to know! - this.renderingToScreen = !renderTexture; - - this.runners.prerender.run(); - this.emit('prerender'); - - // 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.parent; - - displayObject.parent = this._tempDisplayObjectParent; - displayObject.updateTransform(); - displayObject.parent = 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.run(); - - this.emit('postrender'); - } - - /** - * Resizes the webGL view to the specified width and height. - * - * @param {number} screenWidth - the new width of the screen - * @param {number} screenHeight - the new height of the screen - */ - resize(screenWidth, screenHeight) - { - AbstractRenderer.prototype.resize.call(this, screenWidth, screenHeight); - - this.runners.resize.run(screenWidth, screenHeight); - } - - /** - * Resets the WebGL state so you can render things however you fancy! - * - * @return {PIXI.Renderer} Returns itself. - */ - reset() - { - this.runners.reset.run(); - - return this; - } - - clear() - { - this.framebuffer.bind(); - this.framebuffer.clear(); - } - - /** - * Removes everything from the renderer (event listeners, spritebatch, etc...) - * - * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. - * See: https://github.com/pixijs/pixi.js/issues/2233 - */ - destroy(removeView) - { - this.runners.destroy.run(); - - // call base destroy - super.destroy(removeView); - - // TODO nullify all the managers.. - this.gl = null; - } - - /** - * 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 - * builds and excluding plugins. - * @name PIXI.Renderer#plugins - * @type {object} - * @readonly - * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. - * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. - * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. - * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. - */ - - /** - * Adds a plugin to the renderer. - * - * @method - * @param {string} pluginName - The name of the plugin. - * @param {Function} ctor - The constructor function or class for the plugin. - */ - static registerPlugin(pluginName, ctor) - { - Renderer.__plugins = Renderer.__plugins || {}; - Renderer.__plugins[pluginName] = ctor; - } -} diff --git a/packages/core/src/renderers/State.js b/packages/core/src/renderers/State.js deleted file mode 100644 index fa3f221..0000000 --- a/packages/core/src/renderers/State.js +++ /dev/null @@ -1,163 +0,0 @@ -/* eslint-disable max-len */ - -const BLEND = 0; -const OFFSET = 1; -const CULLING = 2; -const DEPTH_TEST = 3; -const WINDING = 4; - -/** - * This is a webGL state. It is passed The WebGL StateManager. - * Each mesh renderered may require webGL to be in a different state. - * For example you may want different blend mode or to enable polygon offsets - * - * @class - * @memberof PIXI - */ -export default class State -{ - /** - * - */ - constructor() - { - this.data = 0; - - this.blendMode = 0; - this.polygonOffset = 0; - - this.blend = true; - // this.depthTest = true; - } - - /** - * Activates blending of the computed fragment color values - * - * @member {boolean} - */ - get blend() - { - return !!(this.data & (1 << BLEND)); - } - - set blend(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << BLEND)) !== value) - { - this.data ^= (1 << BLEND); - } - } - - /** - * Activates adding an offset to depth values of polygon's fragments - * - * @member {boolean} - * @default false - */ - get offsets() - { - return !!(this.data & (1 << OFFSET)); - } - - set offsets(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << OFFSET)) !== value) - { - this.data ^= (1 << OFFSET); - } - } - - /** - * Activates culling of polygons. - * - * @member {boolean} - * @default false - */ - get culling() - { - return !!(this.data & (1 << CULLING)); - } - - set culling(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << CULLING)) !== value) - { - this.data ^= (1 << CULLING); - } - } - - /** - * Activates depth comparisons and updates to the depth buffer. - * - * @member {boolean} - * @default false - */ - get depthTest() - { - return !!(this.data & (1 << DEPTH_TEST)); - } - - set depthTest(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << DEPTH_TEST)) !== value) - { - this.data ^= (1 << DEPTH_TEST); - } - } - - /** - * Specifies whether or not front or back-facing polygons can be culled. - * @member {boolean} - * @default false - */ - get clockwiseFrontFace() - { - return !!(this.data & (1 << WINDING)); - } - - set clockwiseFrontFace(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << WINDING)) !== value) - { - this.data ^= (1 << WINDING); - } - } - - /** - * The blend mode to be applied when this state is set. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. - * Setting this mode to anything other than NO_BLEND will automatically switch blending on. - * - * @member {boolean} - * @default PIXI.BLEND_MODES.NORMAL - * @see PIXI.BLEND_MODES - */ - get blendMode() - { - return this._blendMode; - } - - set blendMode(value) // eslint-disable-line require-jsdoc - { - // 17 is NO BLEND - this.blend = (value !== 17); - this._blendMode = value; - } - - /** - * The polygon offset. Setting this property to anything other than 0 will automatically enable poygon offset fill. - * - * @member {number} - * @default 0 - */ - get polygonOffset() - { - return this._polygonOffset; - } - - set polygonOffset(value) // eslint-disable-line require-jsdoc - { - this.offsets = !!value; - this._polygonOffset = value; - } -} - diff --git a/packages/core/src/renderers/filters/Filter.js b/packages/core/src/renderers/filters/Filter.js deleted file mode 100644 index 2073b8d..0000000 --- a/packages/core/src/renderers/filters/Filter.js +++ /dev/null @@ -1,140 +0,0 @@ -import Shader from '../../shader/Shader'; -import Program from '../../shader/Program'; -import State from '../State'; -import { settings } from '@pixi/settings'; -// import extractUniformsFromSrc from './extractUniformsFromSrc'; -import defaultVertex from './defaultFilter.vert'; -import defaultFragment from './defaultFilter.frag'; - -// let math = require('../../math'); -/** - * @class - * @memberof PIXI - * @extends PIXI.Shader - */ -export default class Filter extends Shader -{ - /** - * @param {string} [vertexSrc] - The source of the vertex shader. - * @param {string} [fragmentSrc] - The source of the fragment shader. - * @param {object} [uniforms] - Custom uniforms to use to augment the built-in ones. - */ - constructor(vertexSrc, fragmentSrc, uniforms) - { - const program = Program.from(vertexSrc, fragmentSrc); - - super(program, uniforms); - - /** - * The padding of the filter. Some filters require extra space to breath such as a blur. - * Increasing this will add extra width and height to the bounds of the object that the - * filter is applied to. - * - * @member {number} - */ - this.padding = 0; - - /** - * The resolution of the filter. Setting this to be lower will lower the quality but - * increase the performance of the filter. - * - * @member {number} - */ - this.resolution = settings.RESOLUTION; - - /** - * If enabled is true the filter is applied, if false it will not. - * - * @member {boolean} - */ - this.enabled = true; - - /** - * If enabled, PixiJS will fit the filter area into boundaries for better performance. - * Switch it off if it does not work for specific shader. - * - * @member {boolean} - */ - this.autoFit = true; - - /** - * Legacy filters use position and uvs from attributes - * @member {boolean} - * @readonly - */ - this.legacy = !!this.program.attributeData.aTextureCoord; - - /** - * the webGL state the filter requires to render - * @member {PIXI.State} - */ - this.state = new State(); - } - - /** - * Applies the filter - * - * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from - * @param {PIXI.RenderTarget} input - The input render target. - * @param {PIXI.RenderTarget} output - The target to output to. - * @param {boolean} clear - Should the output be cleared before rendering to it - * @param {object} [currentState] - It's current state of filter. - * There are some useful properties in the currentState : - * target, filters, sourceFrame, destinationFrame, renderTarget, resolution - */ - apply(filterManager, input, output, clear, currentState, derp) // eslint-disable-line no-unused-vars - { - // do as you please! - - filterManager.applyFilter(this, input, output, clear, currentState, derp); - - // or just do a regular render.. - } - - /** - * Sets the blendmode of the filter - * - * @member {number} - * @default PIXI.BLEND_MODES.NORMAL - */ - get blendMode() - { - return this.state.blendMode; - } - - set blendMode(value) // eslint-disable-line require-jsdoc - { - this.state.blendMode = value; - } - - /** - * The default vertex shader source - * - * @static - * @constant - */ - static get defaultVertexSrc() - { - return defaultVertex; - } - - /** - * The default fragment shader source - * - * @static - * @constant - */ - static get defaultFragmentSrc() - { - return defaultFragment; - } -} - -/** - * Used for caching shader IDs - * - * @static - * @private - */ -Filter.SOURCE_KEY_MAP = {}; - diff --git a/packages/core/src/renderers/filters/defaultFilter.frag b/packages/core/src/renderers/filters/defaultFilter.frag deleted file mode 100644 index a14c1be..0000000 --- a/packages/core/src/renderers/filters/defaultFilter.frag +++ /dev/null @@ -1,22 +0,0 @@ -varying vec2 vTextureCoord; -varying vec2 vFilterCoord; - -uniform sampler2D uSampler; -uniform sampler2D filterSampler; - -void main(void){ - vec4 masky = texture2D(filterSampler, vFilterCoord); - vec4 sample = texture2D(uSampler, vTextureCoord); - vec4 color; - if(mod(vFilterCoord.x, 1.0) > 0.5) - { - color = vec4(1.0, 0.0, 0.0, 1.0); - } - else - { - color = vec4(0.0, 1.0, 0.0, 1.0); - } - // gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0); - gl_FragColor = mix(sample, masky, 0.5); - gl_FragColor *= sample.a; -} diff --git a/packages/core/src/renderers/filters/defaultFilter.vert b/packages/core/src/renderers/filters/defaultFilter.vert deleted file mode 100644 index e1febca..0000000 --- a/packages/core/src/renderers/filters/defaultFilter.vert +++ /dev/null @@ -1,14 +0,0 @@ -attribute vec2 aVertexPosition; -attribute vec2 aTextureCoord; - -uniform mat3 projectionMatrix; -uniform mat3 filterMatrix; - -varying vec2 vTextureCoord; -varying vec2 vFilterCoord; - -void main(void){ - gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); - vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy; - vTextureCoord = aTextureCoord ; -} diff --git a/packages/core/src/renderers/filters/filterTransforms.js b/packages/core/src/renderers/filters/filterTransforms.js deleted file mode 100644 index 87a3cd6..0000000 --- a/packages/core/src/renderers/filters/filterTransforms.js +++ /dev/null @@ -1,50 +0,0 @@ -import { Matrix } from '@pixi/math'; - -/** - * Calculates the mapped matrix - * @param filterArea {Rectangle} The filter area - * @param sprite {Sprite} the target sprite - * @param outputMatrix {Matrix} @alvin - * @private - */ -// this returns a matrix that will normalise map filter cords in the filter to screen space -export function calculateScreenSpaceMatrix(outputMatrix, filterArea, textureSize) -{ - // TODO unwrap? - const mappedMatrix = outputMatrix.identity(); - - mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); - - mappedMatrix.scale(textureSize.width, textureSize.height); - - return mappedMatrix; -} - -export function calculateNormalizedScreenSpaceMatrix(outputMatrix, filterArea, textureSize) -{ - const mappedMatrix = outputMatrix.identity(); - - mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); - - const translateScaleX = (textureSize.width / filterArea.width); - const translateScaleY = (textureSize.height / filterArea.height); - - mappedMatrix.scale(translateScaleX, translateScaleY); - - return mappedMatrix; -} - -// this will map the filter coord so that a texture can be used based on the transform of a sprite -export function calculateSpriteMatrix(outputMatrix, filterArea, textureSize, sprite) -{ - const orig = sprite._texture.orig; - const mappedMatrix = outputMatrix.set(textureSize.width, 0, 0, textureSize.height, filterArea.x, filterArea.y); - const worldTransform = sprite.worldTransform.copyTo(Matrix.TEMP_MATRIX); - - worldTransform.invert(); - mappedMatrix.prepend(worldTransform); - mappedMatrix.scale(1.0 / orig.width, 1.0 / orig.height); - mappedMatrix.translate(sprite.anchor.x, sprite.anchor.y); - - return mappedMatrix; -} diff --git a/packages/core/src/renderers/filters/spriteMask/SpriteMaskFilter.js b/packages/core/src/renderers/filters/spriteMask/SpriteMaskFilter.js deleted file mode 100644 index d5a91a4..0000000 --- a/packages/core/src/renderers/filters/spriteMask/SpriteMaskFilter.js +++ /dev/null @@ -1,63 +0,0 @@ -import Filter from '../Filter'; -import { Matrix } from '@pixi/math'; -import vertex from './spriteMaskFilter.vert'; -import fragment from './spriteMaskFilter.frag'; -import { default as TextureMatrix } from '../../../textures/TextureMatrix'; - -/** - * The SpriteMaskFilter class - * - * @class - * @extends PIXI.Filter - * @memberof PIXI - */ -export default class SpriteMaskFilter extends Filter -{ - /** - * @param {PIXI.Sprite} sprite - the target sprite - */ - constructor(sprite) - { - const maskMatrix = new Matrix(); - - super(vertex, fragment); - - sprite.renderable = false; - - this.maskSprite = sprite; - this.maskMatrix = maskMatrix; - } - - /** - * Applies the filter - * - * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from - * @param {PIXI.RenderTarget} input - The input render target. - * @param {PIXI.RenderTarget} output - The target to output to. - */ - apply(filterManager, input, output) - { - const maskSprite = this.maskSprite; - const tex = this.maskSprite.texture; - - if (!tex.valid) - { - return; - } - if (!tex.transform) - { - // margin = 0.0, let it bleed a bit, shader code becomes easier - // assuming that atlas textures were made with 1-pixel padding - tex.transform = new TextureMatrix(tex, 0.0); - } - tex.transform.update(); - - this.uniforms.mask = tex; - this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite) - .prepend(tex.transform.mapCoord); - this.uniforms.alpha = maskSprite.worldAlpha; - this.uniforms.maskClamp = tex.transform.uClampFrame; - - filterManager.applyFilter(this, input, output); - } -} diff --git a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.frag b/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.frag deleted file mode 100644 index 0e4aef8..0000000 --- a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.frag +++ /dev/null @@ -1,23 +0,0 @@ -varying vec2 vMaskCoord; -varying vec2 vTextureCoord; - -uniform sampler2D uSampler; -uniform sampler2D mask; -uniform float alpha; -uniform vec4 maskClamp; - -void main(void) -{ - float clip = step(3.5, - step(maskClamp.x, vMaskCoord.x) + - step(maskClamp.y, vMaskCoord.y) + - step(vMaskCoord.x, maskClamp.z) + - step(vMaskCoord.y, maskClamp.w)); - - vec4 original = texture2D(uSampler, vTextureCoord); - vec4 masky = texture2D(mask, vMaskCoord); - - original *= (masky.r * masky.a * alpha * clip); - - gl_FragColor = original; -} diff --git a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.vert b/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.vert deleted file mode 100644 index d23a30c..0000000 --- a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.vert +++ /dev/null @@ -1,16 +0,0 @@ -attribute vec2 aVertexPosition; -attribute vec2 aTextureCoord; - -uniform mat3 projectionMatrix; -uniform mat3 otherMatrix; - -varying vec2 vMaskCoord; -varying vec2 vTextureCoord; - -void main(void) -{ - gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); - - vTextureCoord = aTextureCoord; - vMaskCoord = ( otherMatrix * vec3( aTextureCoord, 1.0) ).xy; -} diff --git a/packages/core/src/renderers/systems/BatchSystem.js b/packages/core/src/renderers/systems/BatchSystem.js deleted file mode 100644 index 70e9518..0000000 --- a/packages/core/src/renderers/systems/BatchSystem.js +++ /dev/null @@ -1,68 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import ObjectRenderer from '../utils/ObjectRenderer'; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ - -export default class BatchSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this manager works for. - */ - constructor(renderer) - { - super(renderer); - - /** - * An empty renderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.emptyRenderer = new ObjectRenderer(renderer); - - /** - * The currently active ObjectRenderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.currentRenderer = this.emptyRenderer; - } - - /** - * Changes the current renderer to the one given in parameter - * - * @param {PIXI.ObjectRenderer} objectRenderer - The object renderer to use. - */ - setObjectRenderer(objectRenderer) - { - if (this.currentRenderer === objectRenderer) - { - return; - } - - this.currentRenderer.stop(); - this.currentRenderer = objectRenderer; - - this.renderer.state.setState(objectRenderer.state); - - this.currentRenderer.start(); - } - - /** - * This should be called if you wish to do some custom rendering - * It will basically render anything that may be batched up such as sprites - * - */ - flush() - { - this.setObjectRenderer(this.emptyRenderer); - } - - reset() - { - this.setObjectRenderer(this.emptyRenderer); - } -} diff --git a/packages/core/src/renderers/systems/ContextSystem.js b/packages/core/src/renderers/systems/ContextSystem.js deleted file mode 100644 index 43bf827..0000000 --- a/packages/core/src/renderers/systems/ContextSystem.js +++ /dev/null @@ -1,186 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { settings } from '@pixi/settings'; - -let CONTEXT_UID = 0; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class ContextSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this.webGLVersion = 1; - - this.handleContextLost = this.handleContextLost.bind(this); - this.handleContextRestored = this.handleContextRestored.bind(this); - - this.extensions = {}; - - renderer.view.addEventListener('webglcontextlost', this.handleContextLost, false); - renderer.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); - } - - get isLost() - { - return (!this.gl || this.gl.isContextLost()); - } - - contextChange(gl) - { - this.gl = gl; - - // restore a context if it was previously lost - if (gl.isContextLost() && gl.getExtension('WEBGL_lose_context')) - { - gl.getExtension('WEBGL_lose_context').restoreContext(); - } - } - - initFromContext(gl) - { - this.gl = gl; - this.validateContext(gl); - this.renderer.gl = gl; - this.renderer.CONTEXT_UID = CONTEXT_UID++; - this.renderer.runners.contextChange.run(gl); - } - - initFromOptions(options) - { - const gl = this.createContext(this.renderer.view, options); - - this.initFromContext(gl); - } - - /** - * Helper class to create a webGL Context - * - * @class - * @memberof PIXI.glCore - * @param canvas {HTMLCanvasElement} the canvas element that we will get the context from - * @param options {Object} An options object that gets passed in to the canvas element containing the context attributes, - * see https://developer.mozilla.org/en/docs/Web/API/HTMLCanvasElement/getContext for the options available - * @return {WebGLRenderingContext} the WebGL context - */ - createContext(canvas, options) - { - let gl; - - if (settings.PREFER_WEBGL_2) - { - gl = canvas.getContext('webgl2', options); - } - - if (gl) - { - this.webGLVersion = 2; - } - else - { - this.webGLVersion = 1; - - gl = canvas.getContext('webgl', options) - || canvas.getContext('experimental-webgl', options); - - if (!gl) - { - // fail, not able to get a context - throw new Error('This browser does not support webGL. Try using the canvas renderer'); - } - } - - this.gl = gl; - - this.getExtensions(); - - return gl; - } - - getExtensions() - { - // time to set up default etensions that pixi uses.. - const gl = this.gl; - const extensions = this.extensions; - - if (this.webGLVersion === 1) - { - extensions.drawBuffers = gl.getExtension('WEBGL_draw_buffers'); - extensions.depthTexture = gl.getExtension('WEBKIT_WEBGL_depth_texture'); - extensions.floatTexture = gl.getExtension('OES_texture_float'); - extensions.loseContext = gl.getExtension('WEBGL_lose_context'); - - extensions.vertexArrayObject = gl.getExtension('OES_vertex_array_object') - || gl.getExtension('MOZ_OES_vertex_array_object') - || gl.getExtension('WEBKIT_OES_vertex_array_object'); - } - - // we don't use any specific WebGL 2 ones yet! - } - - /** - * Handles a lost webgl context - * - * @private - * @param {WebGLContextEvent} event - The context lost event. - */ - handleContextLost(event) - { - event.preventDefault(); - } - - /** - * Handles a restored webgl context - * - * @private - */ - handleContextRestored() - { - this.renderer.runners.contextChange.run(this.gl); - } - - destroy() - { - const view = this.renderer.view; - - // remove listeners - view.removeEventListener('webglcontextlost', this.handleContextLost); - view.removeEventListener('webglcontextrestored', this.handleContextRestored); - - this.gl.useProgram(null); - - if (this.extensions.loseContext) - { - this.extensions.loseContext.loseContext(); - } - } - - postrender() - { - this.gl.flush(); - } - - validateContext(gl) - { - const attributes = gl.getContextAttributes(); - - // this is going to be fairly simple for now.. but at least we have room to grow! - if (!attributes.stencil) - { - /* eslint-disable max-len */ - - /* eslint-disable no-console */ - console.warn('Provided WebGL context does not have a stencil buffer, masks may not render correctly'); - /* eslint-enable no-console */ - - /* eslint-enable max-len */ - } - } -} diff --git a/packages/core/src/renderers/systems/FramebufferSystem.js b/packages/core/src/renderers/systems/FramebufferSystem.js deleted file mode 100644 index df7902b..0000000 --- a/packages/core/src/renderers/systems/FramebufferSystem.js +++ /dev/null @@ -1,246 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { Rectangle } from '@pixi/math'; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class FramebufferSystem extends WebGLSystem -{ - /** - * Sets up the renderer context and necessary buffers. - * - * @private - */ - contextChange() - { - this.gl = this.renderer.gl; - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - this.current = null; - this.viewport = new Rectangle(); - - this.drawBufferExtension = this.renderer.context.extensions.drawBuffers; - } - - bind(framebuffer, frame) - { - const gl = this.gl; - - this.current = framebuffer; - - if (framebuffer) - { - // TODO cacheing layer! - - const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID] || this.initFramebuffer(framebuffer); - - gl.bindFramebuffer(gl.FRAMEBUFFER, fbo.framebuffer); - // makesure all textures are unbound.. - - // now check for updates... - if (fbo.dirtyId !== framebuffer.dirtyId) - { - fbo.dirtyId = framebuffer.dirtyId; - - if (fbo.dirtyFormat !== framebuffer.dirtyFormat) - { - fbo.dirtyFormat = framebuffer.dirtyFormat; - this.updateFramebuffer(framebuffer); - } - else if (fbo.dirtySize !== framebuffer.dirtySize) - { - fbo.dirtySize = framebuffer.dirtySize; - this.resizeFramebuffer(framebuffer); - } - } - - for (let i = 0; i < framebuffer.colorTextures.length; i++) - { - if (framebuffer.colorTextures[i].texturePart) - { - this.renderer.texture.unbind(framebuffer.colorTextures[i].texture); - } - else - { - this.renderer.texture.unbind(framebuffer.colorTextures[i]); - } - } - - if (framebuffer.depthTexture) - { - this.renderer.texture.unbind(framebuffer.depthTexture); - } - - if (frame) - { - this.setViewport(frame.x, frame.y, frame.width, frame.height); - } - else - { - this.setViewport(0, 0, framebuffer.width, framebuffer.height); - } - } - else - { - gl.bindFramebuffer(gl.FRAMEBUFFER, null); - - if (frame) - { - this.setViewport(frame.x, frame.y, frame.width, frame.height); - } - else - { - this.setViewport(0, 0, this.renderer.width, this.renderer.height); - } - } - } - - setViewport(x, y, width, height) - { - const v = this.viewport; - - if (v.width !== width || v.height !== height || v.x !== x || v.y !== y) - { - v.x = x; - v.y = y; - v.width = width; - v.height = height; - - this.gl.viewport(x, y, width, height); - } - } - - get size() - { - if (this.current) - { - // TODO store temp - return { x: 0, y: 0, width: this.current.width, height: this.current.height }; - } - - return { x: 0, y: 0, width: this.renderer.width, height: this.renderer.height }; - } - - clear(r, g, b, a) - { - const gl = this.gl; - - // TODO clear color can be set only one right? - gl.clearColor(r, g, b, a); - gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); - } - - // private functions... - - initFramebuffer(framebuffer) - { - const gl = this.gl; - - // TODO - make this a class? - const fbo = { - framebuffer: gl.createFramebuffer(), - stencil: null, - dirtyId: 0, - dirtyFormat: 0, - dirtySize: 0, - }; - - framebuffer.glFrameBuffers[this.CONTEXT_UID] = fbo; - - return fbo; - } - - resizeFramebuffer(framebuffer) - { - const gl = this.gl; - - if (framebuffer.stencil || framebuffer.depth) - { - gl.bindRenderbuffer(gl.RENDERBUFFER, this.stencil); - gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); - } - } - - updateFramebuffer(framebuffer) - { - const gl = this.gl; - - const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID]; - - // bind the color texture - const colorTextures = framebuffer.colorTextures; - - let count = colorTextures.length; - - if (!this.drawBufferExtension) - { - count = Math.min(count, 1); - } - - const activeTextures = []; - - for (let i = 0; i < count; i++) - { - const texture = framebuffer.colorTextures[i]; - - if (texture.texturePart) - { - this.renderer.texture.bind(texture.texture, 0); - - gl.framebufferTexture2D(gl.FRAMEBUFFER, - gl.COLOR_ATTACHMENT0 + i, - gl.TEXTURE_CUBE_MAP_NEGATIVE_X + texture.side, - texture.texture._glTextures[this.CONTEXT_UID].texture, - 0); - } - else - { - this.renderer.texture.bind(texture, 0); - - gl.framebufferTexture2D(gl.FRAMEBUFFER, - gl.COLOR_ATTACHMENT0 + i, - gl.TEXTURE_2D, - texture._glTextures[this.CONTEXT_UID].texture, - 0); - } - - activeTextures.push(gl.COLOR_ATTACHMENT0 + i); - } - - if (this.drawBufferExtension && activeTextures.length > 1) - { - this.drawBufferExtension.drawBuffersWEBGL(activeTextures); - } - - if (framebuffer.depthTexture) - { - const depthTextureExt = this.renderer.context.extensions.depthTexture; - - if (depthTextureExt) - { - const depthTexture = framebuffer.depthTexture; - - this.renderer.texture.bind(depthTexture, 0); - - gl.framebufferTexture2D(gl.FRAMEBUFFER, - gl.DEPTH_ATTACHMENT, - gl.TEXTURE_2D, - depthTexture._glTextures[this.CONTEXT_UID].texture, - 0); - } - } - - if (framebuffer.stencil || framebuffer.depth) - { - fbo.stencil = gl.createRenderbuffer(); - - gl.bindRenderbuffer(gl.RENDERBUFFER, fbo.stencil); - - // TODO.. this is depth AND stencil? - gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, fbo.stencil); - gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); - // fbo.enableStencil(); - } - } -} diff --git a/packages/core/src/renderers/systems/MaskSystem.js b/packages/core/src/renderers/systems/MaskSystem.js deleted file mode 100644 index d9c4b1a..0000000 --- a/packages/core/src/renderers/systems/MaskSystem.js +++ /dev/null @@ -1,199 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import AlphaMaskFilter from '../filters/spriteMask/SpriteMaskFilter'; - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class MaskSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - // TODO - we don't need both! - this.scissor = false; - this.scissorData = null; - this.scissorRenderTarget = null; - - this.enableScissor = false; - - this.alphaMaskPool = []; - this.alphaMaskIndex = 0; - } - - /** - * Applies the Mask and adds it to the current filter stack. - * - * @param {PIXI.DisplayObject} target - Display Object to push the mask to - * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. - */ - push(target, maskData) - { - // TODO the root check means scissor rect will not - // be used on render textures more info here: - // https://github.com/pixijs/pixi.js/pull/3545 - - if (maskData.texture) - { - this.pushSpriteMask(target, maskData); - } - else if (this.enableScissor - && !this.scissor - && this.renderer._activeRenderTarget.root - && !this.renderer.stencil.stencilMaskStack.length - && maskData.isFastRect()) - { - const matrix = maskData.worldTransform; - - let rot = Math.atan2(matrix.b, matrix.a); - - // use the nearest degree! - rot = Math.round(rot * (180 / Math.PI)); - - if (rot % 90) - { - this.pushStencilMask(maskData); - } - else - { - this.pushScissorMask(target, maskData); - } - } - else - { - this.pushStencilMask(maskData); - } - } - - /** - * Removes the last mask from the mask stack and doesn't return it. - * - * @param {PIXI.DisplayObject} target - Display Object to pop the mask from - * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. - */ - pop(target, maskData) - { - if (maskData.texture) - { - this.popSpriteMask(target, maskData); - } - else if (this.enableScissor && !this.renderer.stencil.stencilMaskStack.length) - { - this.popScissorMask(target, maskData); - } - else - { - this.popStencilMask(target, maskData); - } - } - - /** - * Applies the Mask and adds it to the current filter stack. - * - * @param {PIXI.RenderTarget} target - Display Object to push the sprite mask to - * @param {PIXI.Sprite} maskData - Sprite to be used as the mask - */ - pushSpriteMask(target, maskData) - { - let alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; - - if (!alphaMaskFilter) - { - alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; - } - - alphaMaskFilter[0].resolution = this.renderer.resolution; - alphaMaskFilter[0].maskSprite = maskData; - - // TODO - may cause issues! - target.filterArea = maskData.getBounds(true); - - this.renderer.filterSystem.pushFilter(target, alphaMaskFilter); - - this.alphaMaskIndex++; - } - - /** - * Removes the last filter from the filter stack and doesn't return it. - * - */ - popSpriteMask() - { - this.renderer.filterSystem.popFilter(); - this.alphaMaskIndex--; - } - - /** - * Applies the Mask and adds it to the current filter stack. - * - * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. - */ - pushStencilMask(maskData) - { - this.renderer.batch.flush(); - this.renderer.stencil.pushStencil(maskData); - } - - /** - * Removes the last filter from the filter stack and doesn't return it. - * - */ - popStencilMask() - { - // this.renderer.currentRenderer.stop(); - this.renderer.stencil.popStencil(); - } - - /** - * - * @param {PIXI.DisplayObject} target - Display Object to push the mask to - * @param {PIXI.Graphics} maskData - The masking data. - */ - pushScissorMask(target, maskData) - { - maskData.renderable = true; - - const renderTarget = this.renderer._activeRenderTarget; - - const bounds = maskData.getBounds(); - - bounds.fit(renderTarget.size); - maskData.renderable = false; - - this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); - - const resolution = this.renderer.resolution; - - this.renderer.gl.scissor( - bounds.x * resolution, - (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, - bounds.width * resolution, - bounds.height * resolution - ); - - this.scissorRenderTarget = renderTarget; - this.scissorData = maskData; - this.scissor = true; - } - - /** - * - * - */ - popScissorMask() - { - this.scissorRenderTarget = null; - this.scissorData = null; - this.scissor = false; - - // must be scissor! - const gl = this.renderer.gl; - - gl.disable(gl.SCISSOR_TEST); - } -} diff --git a/packages/core/src/renderers/systems/ProjectionSystem.js b/packages/core/src/renderers/systems/ProjectionSystem.js deleted file mode 100644 index ec5efdb..0000000 --- a/packages/core/src/renderers/systems/ProjectionSystem.js +++ /dev/null @@ -1,73 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { Matrix } from '@pixi/math'; - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ - -export default class ProjectionSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this.projectionMatrix = new Matrix(); - } - - update(destinationFrame, sourceFrame, resolution, root) - { - this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; - this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; - - this.calculateProjection(this.destinationFrame, this.sourceFrame, resolution, root); - - this.renderer.globalUniforms.uniforms.projectionMatrix = this.projectionMatrix; - this.renderer.globalUniforms.update(); - } - - /** - * Updates the projection matrix based on a projection frame (which is a rectangle) - * - * @param {Rectangle} destinationFrame - The destination frame. - * @param {Rectangle} sourceFrame - The source frame. - */ - calculateProjection(destinationFrame, sourceFrame, resolution, root) - { - const pm = this.projectionMatrix; - - // I don't think we will need this line.. - // pm.identity(); - - if (!root) - { - pm.a = (1 / destinationFrame.width * 2) * resolution; - pm.d = (1 / destinationFrame.height * 2) * resolution; - - pm.tx = -1 - (sourceFrame.x * pm.a); - pm.ty = -1 - (sourceFrame.y * pm.d); - } - else - { - pm.a = (1 / destinationFrame.width * 2) * resolution; - pm.d = (-1 / destinationFrame.height * 2) * resolution; - - pm.tx = -1 - (sourceFrame.x * pm.a); - pm.ty = 1 - (sourceFrame.y * pm.d); - } - } - - /** - * Sets the transform of the active render target to the given matrix - * - * @param {PIXI.Matrix} matrix - The transformation matrix - */ - setTransform()// matrix) - { - // this._activeRenderTarget.transform = matrix; - } -} diff --git a/packages/core/src/renderers/systems/RenderTextureSystem.js b/packages/core/src/renderers/systems/RenderTextureSystem.js deleted file mode 100644 index a133ef1..0000000 --- a/packages/core/src/renderers/systems/RenderTextureSystem.js +++ /dev/null @@ -1,115 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { Rectangle } from '@pixi/math'; - -const tempRect = new Rectangle(); - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ - -export default class RenderTextureSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this.clearColor = renderer._backgroundColorRgba; - - // TODO moe this property somewhere else! - this.defaultMaskStack = []; - this.defaultFilterStack = [{}]; - - // empty render texture? - this.renderTexture = null; - - this.destinationFrame = new Rectangle(); - } - - bind(renderTexture, sourceFrame, destinationFrame) - { - // TODO - do we want this?? - if (this.renderTexture === renderTexture) return; - this.renderTexture = renderTexture; - - const renderer = this.renderer; - - if (renderTexture) - { - const baseTexture = renderTexture.baseTexture; - - if (!destinationFrame) - { - tempRect.width = baseTexture.realWidth; - tempRect.height = baseTexture.realHeight; - - destinationFrame = tempRect; - } - - if (!sourceFrame) - { - sourceFrame = destinationFrame; - } - - this.renderer.framebuffer.bind(baseTexture.frameBuffer, destinationFrame); - - this.renderer.projection.update(destinationFrame, sourceFrame, baseTexture.resolution, false); - this.renderer.stencil.setMaskStack(baseTexture.stencilMaskStack); - } - else - { - // TODO these validation checks happen deeper down.. - // thing they can be avoided.. - if (!destinationFrame) - { - tempRect.width = renderer.width; - tempRect.height = renderer.height; - - destinationFrame = tempRect; - } - - if (!sourceFrame) - { - sourceFrame = destinationFrame; - } - - renderer.framebuffer.bind(null, destinationFrame); - - // TODO store this.. - this.renderer.projection.update(destinationFrame, sourceFrame, this.renderer.resolution, true); - this.renderer.stencil.setMaskStack(this.defaultMaskStack); - } - - this.destinationFrame.copyFrom(destinationFrame); - } - - /** - * Erases the render texture and fills the drawing area with a colour - * - * @param {number} [clearColor] - The colour - * @return {PIXI.Renderer} Returns itself. - */ - clear(clearColor) - { - if (this.renderTexture) - { - clearColor = clearColor || this.renderTexture.baseTexture.clearColor; - } - else - { - clearColor = clearColor || this.clearColor; - } - - this.renderer.framebuffer.clear(clearColor[0], clearColor[1], clearColor[2], clearColor[3]); - } - - resize()// screenWidth, screenHeight) - { - // resize the root only! - this.bind(null); - } -} diff --git a/packages/core/src/renderers/systems/StateSystem.js b/packages/core/src/renderers/systems/StateSystem.js deleted file mode 100755 index d7f4caf..0000000 --- a/packages/core/src/renderers/systems/StateSystem.js +++ /dev/null @@ -1,288 +0,0 @@ -import mapWebGLBlendModesToPixi from '../utils/mapWebGLBlendModesToPixi'; -import WebGLSystem from './WebGLSystem'; -import WebGLState from '../State'; - -const BLEND = 0; -const OFFSET = 1; -const CULLING = 2; -const DEPTH_TEST = 3; -const WINDING = 4; - -/** - * A WebGL state machines - * - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class StateSystem extends WebGLSystem -{ - /** - * @param {WebGLRenderingContext} gl - The current WebGL rendering context - */ - constructor(renderer) - { - super(renderer); - - this.gl = null; - - this.maxAttribs = null; - - // check we have vao.. - this.nativeVaoExtension = null; - - this.attribState = null; - - this.stateId = 0; - this.polygonOffset = 0; - this.blendMode = 17; - - this.map = []; - - // map functions for when we set state.. - this.map[BLEND] = this.setBlend; - this.map[OFFSET] = this.setOffset; - this.map[CULLING] = this.setCullFace; - this.map[DEPTH_TEST] = this.setDepthTest; - this.map[WINDING] = this.setFrontFace; - - this.checks = []; - - this.defaultState = new WebGLState(); - this.defaultState.blend = true; - this.defaultState.depth = true; - } - - contextChange(gl) - { - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; - - this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); - - // check we have vao.. - this.nativeVaoExtension = ( - gl.getExtension('OES_vertex_array_object') - || gl.getExtension('MOZ_OES_vertex_array_object') - || gl.getExtension('WEBKIT_OES_vertex_array_object') - ); - - this.attribState = { - tempAttribState: new Array(this.maxAttribs), - attribState: new Array(this.maxAttribs), - }; - - this.blendModes = mapWebGLBlendModesToPixi(gl); - - this.setState(this.defaultState); - - this.reset(); - } - - /** - * Sets the current state - * - * @param {*} state - The state to set. - */ - setState(state) - { - state = state || this.defaultState; - - // TODO maybe to an object check? ( this.state === state )? - if (this.stateId !== state.data) - { - let diff = this.stateId ^ state.data; - let i = 0; - - // order from least to most common - while (diff) - { - if (diff & 1) - { - // state change! - this.map[i].call(this, !!(state.data & (1 << i))); - } - - diff = diff >> 1; - i++; - } - - this.stateId = state.data; - } - - // based on the above settings we check for specific modes.. - // for example if blend is active we check and set the blend modes - // or of polygon offset is active we check the poly depth. - for (let i = 0; i < this.checks.length; i++) - { - this.checks[i](this, state); - } - } - - /** - * Enables or disabled blending. - * - * @param {boolean} value - Turn on or off webgl blending. - */ - setBlend(value) - { - this.updateCheck(StateSystem.checkBlendMode, value); - - this.gl[value ? 'enable' : 'disable'](this.gl.BLEND); - } - - /** - * Enables or disable polygon offset fill - * - * @param {boolean} value - Turn on or off webgl polygon offset testing. - */ - setOffset(value) - { - this.gl[value ? 'enable' : 'disable'](this.gl.POLYGON_OFFSET_FILL); - } - - /** - * Sets whether to enable or disable depth test. - * - * @param {boolean} value - Turn on or off webgl depth testing. - */ - setDepthTest(value) - { - this.gl[value ? 'enable' : 'disable'](this.gl.DEPTH_TEST); - } - - /** - * Sets whether to enable or disable cull face. - * - * @param {boolean} value - Turn on or off webgl cull face. - */ - setCullFace(value) - { - this.gl[value ? 'enable' : 'disable'](this.gl.CULL_FACE); - } - - /** - * Sets the gl front face. - * - * @param {boolean} value - true is clockwise and false is counter-clockwise - */ - setFrontFace(value) - { - this.gl.frontFace(this.gl[value ? 'CW' : 'CCW']); - } - - /** - * Sets the blend mode. - * - * @param {number} value - The blend mode to set to. - */ - setBlendMode(value) - { - if (value === this.blendMode) - { - return; - } - - this.blendMode = value; - this.gl.blendFunc(this.blendModes[value][0], this.blendModes[value][1]); - } - - /** - * Sets the polygon offset. - * - * @param {number} value - the polygon offset - * @param {number} scale - the polygon offset scale - */ - setPolygonOffset(value, scale) - { - this.gl.polygonOffset(value, scale); - } - - /** - * Disables all the vaos in use - * - */ - resetAttributes() - { - for (let i = 0; i < this.attribState.tempAttribState.length; i++) - { - this.attribState.tempAttribState[i] = 0; - } - - for (let i = 0; i < this.attribState.attribState.length; i++) - { - this.attribState.attribState[i] = 0; - } - - // im going to assume one is always active for performance reasons. - for (let i = 1; i < this.maxAttribs; i++) - { - this.gl.disableVertexAttribArray(i); - } - } - - // used - /** - * Resets all the logic and disables the vaos - */ - reset() - { - // unbind any VAO if they exist.. - if (this.nativeVaoExtension) - { - this.nativeVaoExtension.bindVertexArrayOES(null); - } - - // reset all attributes.. - this.resetAttributes(); - - this.gl.pixelStorei(this.gl.UNPACK_FLIP_Y_WEBGL, false); - - this.setBlendMode(0); - - // TODO? - // this.setState(this.defaultState); - } - - /** - * checks to see which updates should be checked based on which settings have been activated - * for example if blend is enabled then we shold check the blend modes each time the state is changed - * or if poygon fill is activated then we need to check if the polygone offset changes. - * The idea is that we only check what we have too. - * - * @param {Function} func the checking function to add or remove - * @param {boolean} value should the check function be added or removed. - */ - updateCheck(func, value) - { - const index = this.checks.indexOf(func); - - if (value && index === -1) - { - this.checks.push(func); - } - else if (!value && index !== -1) - { - this.checks.splice(index, 1); - } - } - - /** - * A private little wrapper function that we call to check the blend mode. - * - * @static - * @private - * @param {PIXI.StateSystem} System the System to perform the state check on - * @param {PIXI.State} state the state that the blendMode will pulled from - */ - static checkBlendMode(system, state) - { - system.setBlendMode(state.blendMode); - } - - // TODO - add polygon offset? -} diff --git a/packages/core/src/renderers/systems/StencilSystem.js b/packages/core/src/renderers/systems/StencilSystem.js deleted file mode 100644 index 5d538cb..0000000 --- a/packages/core/src/renderers/systems/StencilSystem.js +++ /dev/null @@ -1,133 +0,0 @@ -import WebGLSystem from './WebGLSystem'; - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class StencilSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - this.stencilMaskStack = []; - } - - /** - * Changes the mask stack that is used by this System. - * - * @param {PIXI.Graphics[]} stencilMaskStack - The mask stack - */ - setMaskStack(stencilMaskStack) - { - const gl = this.renderer.gl; - - if (stencilMaskStack.length !== this.stencilMaskStack.length) - { - if (stencilMaskStack.length === 0) - { - gl.disable(gl.STENCIL_TEST); - } - else - { - gl.enable(gl.STENCIL_TEST); - } - } - - this.stencilMaskStack = stencilMaskStack; - } - - /** - * Applies the Mask and adds it to the current stencil stack. @alvin - * - * @param {PIXI.Graphics} graphics - The mask - */ - pushStencil(graphics) - { - this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); - - // this.renderer._activeRenderTarget.attachStencilBuffer(); - - const gl = this.renderer.gl; - const prevMaskCount = this.stencilMaskStack.length; - - if (prevMaskCount === 0) - { - gl.enable(gl.STENCIL_TEST); - } - - this.stencilMaskStack.push(graphics); - - // Increment the refference stencil value where the new mask overlaps with the old ones. - gl.colorMask(false, false, false, false); - gl.stencilFunc(gl.EQUAL, prevMaskCount, this._getBitwiseMask()); - gl.stencilOp(gl.KEEP, gl.KEEP, gl.INCR); - this.renderer.plugins.graphics.render(graphics); - - this._useCurrent(); - } - - /** - * Removes the last mask from the stencil stack. @alvin - */ - popStencil() - { - this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); - - const gl = this.renderer.gl; - const graphics = this.stencilMaskStack.pop(); - - if (this.stencilMaskStack.length === 0) - { - // the stack is empty! - gl.disable(gl.STENCIL_TEST); - gl.clear(gl.STENCIL_BUFFER_BIT); - gl.clearStencil(0); - } - else - { - // Decrement the refference stencil value where the popped mask overlaps with the other ones - gl.colorMask(false, false, false, false); - gl.stencilOp(gl.KEEP, gl.KEEP, gl.DECR); - this.renderer.plugins.graphics.render(graphics); - - this._useCurrent(); - } - } - - /** - * Setup renderer to use the current stencil data. - */ - _useCurrent() - { - const gl = this.renderer.gl; - - gl.colorMask(true, true, true, true); - gl.stencilFunc(gl.EQUAL, this.stencilMaskStack.length, this._getBitwiseMask()); - gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP); - } - - /** - * Fill 1s equal to the number of acitve stencil masks. - * - * @return {number} The bitwise mask. - */ - _getBitwiseMask() - { - return (1 << this.stencilMaskStack.length) - 1; - } - - /** - * Destroys the mask stack. - * - */ - destroy() - { - super.destroy(this); - - this.stencilMaskStack = null; - } -} diff --git a/packages/core/src/renderers/systems/WebGLSystem.js b/packages/core/src/renderers/systems/WebGLSystem.js deleted file mode 100644 index d3d5b31..0000000 --- a/packages/core/src/renderers/systems/WebGLSystem.js +++ /dev/null @@ -1,41 +0,0 @@ -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this manager works for. - */ - constructor(renderer) - { - /** - * The renderer this manager works for. - * - * @member {PIXI.Renderer} - */ - this.renderer = renderer; - - this.renderer.runners.contextChange.add(this); - } - - /** - * Generic method called when there is a WebGL context change. - * - */ - contextChange() - { - // do some codes init! - } - - /** - * Generic destroy methods to be overridden by the subclass - * - */ - destroy() - { - this.renderer.runners.contextChange.remove(this); - this.renderer = null; - } -} diff --git a/packages/core/src/renderers/systems/filter/FilterSystem.js b/packages/core/src/renderers/systems/filter/FilterSystem.js deleted file mode 100644 index e615037..0000000 --- a/packages/core/src/renderers/systems/filter/FilterSystem.js +++ /dev/null @@ -1,431 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; - -import RenderTexture from '../../../textures/RenderTexture'; -import Quad from '../../utils/Quad'; -import QuadUv from '../../utils/QuadUv'; -import { Rectangle } from '@pixi/math'; -import * as filterTransforms from '../../filters/filterTransforms'; -import bitTwiddle from 'bit-twiddle'; -import UniformGroup from '../../../shader/UniformGroup'; -import { DRAW_MODES } from '../../../../../constants'; - -// -/** - * @ignore - * @class - */ -class FilterState -{ - /** - * - */ - constructor() - { - this.renderTexture = null; - this.sourceFrame = new Rectangle(); - this.destinationFrame = new Rectangle(); - this.filters = []; - this.target = null; - this.legacy = false; - this.resolution = 1; - } -} - -/** - * @class - * @memberof PIXI.systems - * @extends PIXI.systems.WebGLSystem - */ -export default class FilterSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - /** - * stores a bunch of PO2 textures used for filtering - * @type {Object} - */ - this.texturePool = {}; - - /** - * a pool for storing filter states, save us creating new ones each tick - * @type {Array} - */ - this.statePool = []; - - /** - * A very simple geometry used when drawing a filter effect to the screen - * @type {Quad} - */ - this.quad = new Quad(); - - this.quadUv = new QuadUv(); - - /** - * Temporary rect for maths - * @type {PIXI.Rectangle} - */ - this.tempRect = new Rectangle(); - - this.activeState = {}; - - /** - * this uniform group is attached to filter uniforms when used - * @type {UniformGroup} - */ - this.globalUniforms = new UniformGroup({ - sourceFrame: this.tempRect, - destinationFrame: this.tempRect, - - // legacy variables - filterArea: new Float32Array(4), - filterClamp: new Float32Array(4), - }, true); - } - - /** - * Adds a new filter to the System. - * - * @param {PIXI.DisplayObject} target - The target of the filter to render. - * @param {PIXI.Filter[]} filters - The filters to apply. - */ - push(target, filters) - { - const renderer = this.renderer; - const filterStack = this.renderer.renderTexture.defaultFilterStack; - const state = this.statePool.pop() || new FilterState(); - - let resolution = filters[0].resolution; - let padding = filters[0].padding; - let autoFit = filters[0].autoFit; - let legacy = filters[0].legacy; - - for (let i = 1; i < filters.length; i++) - { - const filter = filters[i]; - - // lets use the lowest resolution.. - resolution = Math.min(resolution, filter.resolution); - // and the largest amount of padding! - padding = Math.max(padding, filter.padding); - // only auto fit if all filters are autofit - autoFit = autoFit || filter.autoFit; - - legacy = legacy || filter.legacy; - } - - filterStack.push(state); - - state.resolution = resolution; - - state.legacy = legacy; - - // round to whole number based on resolution - // TODO move that to the shader too? - state.sourceFrame = target.filterArea || target.getBounds(true); - - state.sourceFrame.pad(padding); - - if (autoFit) - { - state.sourceFrame.fit(this.renderer.renderTexture.destinationFrame); - } - - state.sourceFrame.round(resolution); - - state.renderTexture = this.getPotFilterTexture(state.sourceFrame.width, state.sourceFrame.height, resolution); - state.filters = filters; - - state.destinationFrame.width = state.renderTexture.width; - state.destinationFrame.height = state.renderTexture.height; - - state.renderTexture.filterFrame = state.sourceFrame; - renderer.renderTexture.bind(state.renderTexture, state.sourceFrame);// /, state.destinationFrame); - renderer.renderTexture.clear(); - } - - /** - * Pops off the filter and applies it. - * - */ - pop() - { - const renderer = this.renderer; - const filterStack = renderer.renderTexture.defaultFilterStack; - const state = filterStack.pop(); - const filters = state.filters; - - this.activeState = state; - - const globalUniforms = this.globalUniforms.uniforms; - - globalUniforms.sourceFrame = state.sourceFrame; - globalUniforms.destinationFrame = state.destinationFrame; - globalUniforms.resolution = state.resolution; - - // only update the rect if its legacy.. - if (state.legacy) - { - const filterArea = globalUniforms.filterArea; - const filterClamp = globalUniforms.filterClamp; - - filterArea[0] = state.destinationFrame.width; - filterArea[1] = state.destinationFrame.height; - filterArea[2] = state.sourceFrame.x; - filterArea[3] = state.sourceFrame.y; - - filterClamp[0] = 0.5 / state.resolution / state.destinationFrame.width; - filterClamp[1] = 0.5 / state.resolution / state.destinationFrame.height; - filterClamp[2] = (state.sourceFrame.width - 0.5) / state.resolution / state.destinationFrame.width; - filterClamp[3] = (state.sourceFrame.height - 0.5) / state.resolution / state.destinationFrame.height; - } - - this.globalUniforms.update(); - - const lastState = filterStack[filterStack.length - 1]; - - if (filters.length === 1) - { - filters[0].apply(this, state.renderTexture, lastState.renderTexture, false, state); - - this.returnFilterTexture(state.renderTexture); - } - else - { - let flip = state.renderTexture; - let flop = this.getPotFilterTexture( - flip.width, - flip.height, - state.resolution - ); - - flop.filterFrame = flip.filterFrame; - - let i = 0; - - for (i = 0; i < filters.length - 1; ++i) - { - filters[i].apply(this, flip, flop, true, state); - - const t = flip; - - flip = flop; - flop = t; - } - - filters[i].apply(this, flip, lastState.renderTexture, false, state); - - this.returnFilterTexture(flip); - this.returnFilterTexture(flop); - } - - this.statePool.push(state); - } - - /** - * Draws a filter. - * - * @param {PIXI.Filter} filter - The filter to draw. - * @param {PIXI.RenderTarget} input - The input render target. - * @param {PIXI.RenderTarget} output - The target to output to. - * @param {boolean} clear - Should the output be cleared before rendering to it - */ - applyFilter(filter, input, output, clear) - { - const renderer = this.renderer; - - renderer.renderTexture.bind(output, output ? output.filterFrame : null); - - if (clear) - { - // gl.disable(gl.SCISSOR_TEST); - renderer.renderTexture.clear(); - // gl.enable(gl.SCISSOR_TEST); - } - - // set the uniforms.. - filter.uniforms.uSampler = input; - filter.uniforms.filterGlobals = this.globalUniforms; - - // TODO make it so that the order of this does not matter.. - // because it does at the moment cos of global uniforms. - // they need to get resynced - - renderer.state.setState(filter.state); - renderer.shader.bind(filter); - - if (filter.legacy) - { - this.quadUv.map(input._frame, input.filterFrame); - - renderer.geometry.bind(this.quadUv); - renderer.geometry.draw(DRAW_MODES.TRIANGLES); - } - else - { - renderer.geometry.bind(this.quad); - renderer.geometry.draw(DRAW_MODES.TRIANGLE_STRIP); - } - } - - /** - * Calculates the mapped matrix. - * - * TODO playing around here.. this is temporary - (will end up in the shader) - * this returns a matrix that will normalise map filter cords in the filter to screen space - * - * @param {PIXI.Matrix} outputMatrix - the matrix to output to. - * @return {PIXI.Matrix} The mapped matrix. - */ - calculateScreenSpaceMatrix(outputMatrix) - { - const currentState = this.activeState; - - return filterTransforms.calculateScreenSpaceMatrix( - outputMatrix, - currentState.sourceFrame, - currentState.destinationFrame - ); - } - - /** - * This will map the filter coord so that a texture can be used based on the transform of a sprite - * - * @param {PIXI.Matrix} outputMatrix - The matrix to output to. - * @param {PIXI.Sprite} sprite - The sprite to map to. - * @return {PIXI.Matrix} The mapped matrix. - */ - calculateSpriteMatrix(outputMatrix, sprite) - { - const currentState = this.activeState; - - return filterTransforms.calculateSpriteMatrix( - outputMatrix, - currentState.sourceFrame, - currentState.destinationFrame, - sprite - ); - } - - /** - * Destroys this Filter System. - * - * @param {boolean} [contextLost=false] context was lost, do not free shaders - * - */ - destroy(contextLost = false) - { - if (!contextLost) - { - this.emptyPool(); - } - else - { - this.texturePool = {}; - } - } - - /** - * Gets a Power-of-Two render texture. - * - * TODO move to a seperate class could be on renderer? - * also - could cause issue with multiple contexts? - * - * @private - * @param {WebGLRenderingContext} gl - The webgl rendering context - * @param {number} minWidth - The minimum width of the render target. - * @param {number} minHeight - The minimum height of the render target. - * @param {number} resolution - The resolution of the render target. - * @return {PIXI.RenderTarget} The new render target. - */ - getPotFilterTexture(minWidth, minHeight, resolution) - { - minWidth = bitTwiddle.nextPow2(minWidth); - minHeight = bitTwiddle.nextPow2(minHeight); - resolution = resolution || 1; - - const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); - - if (!this.texturePool[key]) - { - this.texturePool[key] = []; - } - - let renderTexture = this.texturePool[key].pop(); - - if (!renderTexture) - { - // temporary bypass cache.. - // internally - this will cause a texture to be bound.. - renderTexture = RenderTexture.create({ - width: minWidth, - height: minHeight, - resolution, - }); - } - - return renderTexture; - } - - /** - * Gets extra render texture to use inside current filter - * - * @param {number} resolution resolution of the renderTexture - * @returns {PIXI.RenderTexture} - */ - getFilterTexture(resolution) - { - const rt = this.activeState.renderTexture; - - const filterTexture = this.getPotFilterTexture(rt.width, rt.height, resolution || rt.baseTexture.resolution); - - filterTexture.filterFrame = rt.filterFrame; - - return filterTexture; - } - - /** - * Frees a render texture back into the pool. - * - * @param {PIXI.RenderTarget} renderTexture - The renderTarget to free - */ - returnFilterTexture(renderTexture) - { - renderTexture.filterFrame = null; - - const base = renderTexture.baseTexture; - - const minWidth = base.width; - const minHeight = base.height; - - const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); - - this.texturePool[key].push(renderTexture); - } - - /** - * Empties the texture pool. - * - */ - emptyPool() - { - for (const i in this.texturePool) - { - const textures = this.texturePool[i]; - - if (textures) - { - for (let j = 0; j < textures.length; j++) - { - textures[j].destroy(true); - } - } - } - - this.texturePool = {}; - } -} diff --git a/packages/core/src/renderers/systems/geometry/GLBuffer.js b/packages/core/src/renderers/systems/geometry/GLBuffer.js deleted file mode 100644 index 0477e8a..0000000 --- a/packages/core/src/renderers/systems/geometry/GLBuffer.js +++ /dev/null @@ -1,9 +0,0 @@ -export default class GLBuffer -{ - constructor(buffer) - { - this.buffer = buffer; - this.updateID = -1; - this.byteLength = -1; - } -} diff --git a/packages/core/src/renderers/systems/geometry/GeometrySystem.js b/packages/core/src/renderers/systems/geometry/GeometrySystem.js deleted file mode 100644 index 26c8e9e..0000000 --- a/packages/core/src/renderers/systems/geometry/GeometrySystem.js +++ /dev/null @@ -1,388 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; -import GLBuffer from './GLBuffer'; - -const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class GeometrySystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this._activeGeometry = null; - this._activeVao = null; - - this.hasVao = true; - this.hasInstance = true; - } - - /** - * Sets up the renderer context and necessary buffers. - * - * @private - */ - contextChange() - { - const gl = this.gl = this.renderer.gl; - - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - - // webgl2 - if (!gl.createVertexArray) - { - // webgl 1! - let nativeVaoExtension = this.renderer.context.extensions.vertexArrayObject; - - if (this.renderer.legacy) - { - nativeVaoExtension = null; - } - - if (nativeVaoExtension) - { - gl.createVertexArray = () => - nativeVaoExtension.createVertexArrayOES(); - - gl.bindVertexArray = (vao) => - nativeVaoExtension.bindVertexArrayOES(vao); - - gl.deleteVertexArray = (vao) => - nativeVaoExtension.deleteVertexArrayOES(vao); - } - else - { - this.hasVao = false; - gl.createVertexArray = () => - { - // empty - }; - - gl.bindVertexArray = () => - { - // empty - }; - - gl.deleteVertexArray = () => - { - // empty - }; - } - } - - if (!gl.vertexAttribDivisor) - { - const instanceExt = gl.getExtension('ANGLE_instanced_arrays'); - - if (instanceExt) - { - gl.vertexAttribDivisor = (a, b) => - instanceExt.vertexAttribDivisorANGLE(a, b); - - gl.drawElementsInstanced = (a, b, c, d, e) => - instanceExt.drawElementsInstancedANGLE(a, b, c, d, e); - - gl.drawArraysInstanced = (a, b, c, d) => - instanceExt.drawArraysInstancedANGLE(a, b, c, d); - } - else - { - this.hasInstance = false; - } - } - } - - /** - * Binds geometry so that is can be drawn. Creating a Vao if required - * @private - * @param {PIXI.Geometry} geometry instance of geometry to bind - */ - bind(geometry, shader) - { - shader = shader || this.renderer.shader.shader; - - const gl = this.gl; - - // not sure the best way to address this.. - // currently different shaders require different VAOs for the same geometry - // Still mulling over the best way to solve this one.. - // will likely need to modify the shader attribute locations at run time! - let vaos = geometry.glVertexArrayObjects[this.CONTEXT_UID]; - - if (!vaos) - { - geometry.glVertexArrayObjects[this.CONTEXT_UID] = vaos = {}; - } - - const vao = vaos[shader.program.id] || this.initGeometryVao(geometry, shader.program); - - this._activeGeometry = geometry; - - if (this._activeVao !== vao) - { - this._activeVao = vao; - - if (this.hasVao) - { - gl.bindVertexArray(vao); - } - else - { - this.activateVao(geometry, shader.program); - } - } - - // TODO - optimise later! - // don't need to loop through if nothing changed! - // maybe look to add an 'autoupdate' to geometry? - this.updateBuffers(); - } - - reset() - { - this.unbind(); - } - - updateBuffers() - { - const geometry = this._activeGeometry; - const gl = this.gl; - - for (let i = 0; i < geometry.buffers.length; i++) - { - const buffer = geometry.buffers[i]; - - const glBuffer = buffer._glBuffers[this.CONTEXT_UID]; - - if (buffer._updateID !== glBuffer.updateID) - { - glBuffer.updateID = buffer._updateID; - - // TODO can cache this on buffer! maybe added a getter / setter? - const type = buffer.index ? gl.ELEMENT_ARRAY_BUFFER : gl.ARRAY_BUFFER; - const drawType = buffer.static ? gl.STATIC_DRAW : gl.DYNAMIC_DRAW; - - gl.bindBuffer(type, glBuffer.buffer); - - if (glBuffer.byteLength >= buffer.data.byteLength) - { - // offset is always zero for now! - gl.bufferSubData(type, 0, buffer.data); - } - else - { - gl.bufferData(type, buffer.data, drawType); - } - } - } - } - - checkCompatability(geometry, program) - { - // geometry must have at least all the attributes that the shader requires. - const geometryAttributes = geometry.attributes; - const shaderAttributes = program.attributeData; - - for (const j in shaderAttributes) - { - if (!geometryAttributes[j]) - { - throw new Error(`shader and geometry incompatible, geometry missing the "${j}" attribute`); - } - } - } - - /** - * Creates a Vao with the same structure as the geometry and stores it on the geometry. - * @private - * @param {PIXI.Geometry} geometry instance of geometry to to generate Vao for - */ - initGeometryVao(geometry, program) - { - this.checkCompatability(geometry, program); - - const gl = this.gl; - const CONTEXT_UID = this.CONTEXT_UID; - const buffers = geometry.buffers; - const attributes = geometry.attributes; - - const tempStride = {}; - const tempStart = {}; - - for (const j in buffers) - { - tempStride[j] = 0; - tempStart[j] = 0; - } - - for (const j in attributes) - { - if (!attributes[j].size && program.attributeData[j]) - { - attributes[j].size = program.attributeData[j].size; - } - - tempStride[attributes[j].buffer] += attributes[j].size * byteSizeMap[attributes[j].type]; - } - - for (const j in attributes) - { - const attribute = attributes[j]; - const attribSize = attribute.size; - - if (attribute.stride === undefined) - { - if (tempStride[attribute.buffer] === attribSize * byteSizeMap[attribute.type]) - { - attribute.stride = 0; - } - else - { - attribute.stride = tempStride[attribute.buffer]; - } - } - - if (attribute.start === undefined) - { - attribute.start = tempStart[attribute.buffer]; - - tempStart[attribute.buffer] += attribSize * byteSizeMap[attribute.type]; - } - } - - // first update - and create the buffers! - // only create a gl buffer if it actually gets - for (let i = 0; i < buffers.length; i++) - { - const buffer = buffers[i]; - - if (!buffer._glBuffers[CONTEXT_UID]) - { - buffer._glBuffers[CONTEXT_UID] = new GLBuffer(gl.createBuffer()); - } - } - - // TODO - maybe make this a data object? - // lets wait to see if we need to first! - const vao = gl.createVertexArray(); - - gl.bindVertexArray(vao); - - this.activateVao(geometry, program); - - gl.bindVertexArray(null); - - // add it to the cache! - geometry.glVertexArrayObjects[this.CONTEXT_UID][program.id] = vao; - - return vao; - } - - activateVao(geometry, program) - { - const gl = this.gl; - const CONTEXT_UID = this.CONTEXT_UID; - const buffers = geometry.buffers; - const attributes = geometry.attributes; - - if (geometry.indexBuffer) - { - // first update the index buffer if we have one.. - gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, geometry.indexBuffer._glBuffers[CONTEXT_UID].buffer); - } - - let lastBuffer = null; - - // add a new one! - for (const j in attributes) - { - const attribute = attributes[j]; - const buffer = buffers[attribute.buffer]; - const glBuffer = buffer._glBuffers[CONTEXT_UID]; - - if (program.attributeData[j]) - { - if (lastBuffer !== glBuffer) - { - gl.bindBuffer(gl.ARRAY_BUFFER, glBuffer.buffer); - - lastBuffer = glBuffer; - } - - const location = program.attributeData[j].location; - - // TODO introduce state again - // we can optimise this for older devices that have no VAOs - gl.enableVertexAttribArray(location); - - gl.vertexAttribPointer(location, - attribute.size, - attribute.type || gl.FLOAT, - attribute.normalized, - attribute.stride, - attribute.start); - - if (attribute.instance) - { - // TODO calculate instance count based of this... - if (this.hasInstance) - { - gl.vertexAttribDivisor(location, 1); - } - else - { - throw new Error('geometry error, GPU Instancing is not supported on this device'); - } - } - } - } - } - - draw(type, size, start, instanceCount) - { - const gl = this.gl; - const geometry = this._activeGeometry; - - // TODO.. this should not change so maybe cache the function? - - if (geometry.indexBuffer) - { - if (geometry.instanced) - { - /* eslint-disable max-len */ - gl.drawElementsInstanced(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2, instanceCount || 1); - /* eslint-enable max-len */ - } - else - { - gl.drawElements(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2); - } - } - else - if (geometry.instanced) - { - // TODO need a better way to calculate size.. - gl.drawArraysInstanced(type, start, size || geometry.getSize(), instanceCount || 1); - } - else - { - gl.drawArrays(type, start, size || geometry.getSize()); - } - - return this; - } - - unbind() - { - this.gl.bindVertexArray(null); - this._activeVao = null; - this._activeGeometry = null; - } -} diff --git a/packages/core/src/renderers/systems/geometry/setVertexAttribArrays.js b/packages/core/src/renderers/systems/geometry/setVertexAttribArrays.js deleted file mode 100644 index 23b6959..0000000 --- a/packages/core/src/renderers/systems/geometry/setVertexAttribArrays.js +++ /dev/null @@ -1,54 +0,0 @@ -// var GL_MAP = {}; - -/** - * @param gl {WebGLRenderingContext} The current WebGL context - * @param attribs {*} - * @param state {*} - */ -export default function setVertexAttribArrays(gl, attribs, state) -{ - let i; - - if (state) - { - const tempAttribState = state.tempAttribState; - const attribState = state.attribState; - - for (i = 0; i < tempAttribState.length; i++) - { - tempAttribState[i] = false; - } - - // set the new attribs - for (i = 0; i < attribs.length; i++) - { - tempAttribState[attribs[i].attribute.location] = true; - } - - for (i = 0; i < attribState.length; i++) - { - if (attribState[i] !== tempAttribState[i]) - { - attribState[i] = tempAttribState[i]; - - if (state.attribState[i]) - { - gl.enableVertexAttribArray(i); - } - else - { - gl.disableVertexAttribArray(i); - } - } - } - } - else - { - for (i = 0; i < attribs.length; i++) - { - const attrib = attribs[i]; - - gl.enableVertexAttribArray(attrib.attribute.location); - } - } -} diff --git a/packages/core/src/renderers/systems/index.js b/packages/core/src/renderers/systems/index.js deleted file mode 100644 index c843f03..0000000 --- a/packages/core/src/renderers/systems/index.js +++ /dev/null @@ -1,6 +0,0 @@ -/** - * Systems are individual components to the Renderer pipeline. - * @namespace PIXI.systems - */ -export { default as WebGLSystem } from './WebGLSystem'; -export { default as FilterSystem } from './filter/FilterSystem'; diff --git a/packages/core/src/renderers/systems/shader/GLProgram.js b/packages/core/src/renderers/systems/shader/GLProgram.js deleted file mode 100644 index 92980ca..0000000 --- a/packages/core/src/renderers/systems/shader/GLProgram.js +++ /dev/null @@ -1,43 +0,0 @@ -/** - * Helper class to create a webGL Program - * - * @class - * @private - */ -export default class GLProgram -{ - constructor(program, uniformData) - { - /** - * The shader program - * - * @member {WebGLProgram} - */ - this.program = program; - - /** - * holds the uniform data which contains uniform locations - * and current uniform values used for caching and preventing undeed GPU commands - * @type {Object} - */ - this.uniformData = uniformData; - - /** - * uniformGroups holds the various upload functions for the shader. Each uniform group - * and program have a unique upload function generated. - * @type {Object} - */ - this.uniformGroups = {}; - } - - /** - * Destroys this program - * TODO - */ - destroy() - { - this.uniformData = null; - this.uniformGroups = null; - this.program = null; - } -} diff --git a/packages/core/src/renderers/systems/shader/ShaderSystem.js b/packages/core/src/renderers/systems/shader/ShaderSystem.js deleted file mode 100644 index 6cf57b6..0000000 --- a/packages/core/src/renderers/systems/shader/ShaderSystem.js +++ /dev/null @@ -1,170 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; -import GLProgram from './GLProgram'; -import generateUniformsSync from '../../../shader/generateUniformsSync'; -import defaultValue from '../../../shader/defaultValue'; -import compileProgram from './shader/compileProgram'; - -let UID = 0; - -/** - * Helper class to create a webGL Texture - * - * @class - * @memberof PIXI - */ -export default class ShaderSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - A reference to the current renderer - */ - constructor(renderer) - { - super(renderer); - - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = null; - - this.shader = null; - this.program = null; - - this.id = UID++; - } - - contextChange(gl) - { - this.gl = gl; - } - - /** - * Changes the current shader to the one given in parameter - * - * @param {PIXI.Shader} shader - the new shader - * @param {boolean} dontSync - false if the shader should automatically sync its uniforms. - * @returns {PIXI.glCore.glProgram} the glProgram that belongs to the shader. - */ - bind(shader, dontSync) - { - shader.uniforms.globals = this.renderer.globalUniforms; - - const program = shader.program; - const glProgram = program.glPrograms[this.renderer.CONTEXT_UID] || this.generateShader(shader); - - this.shader = shader; - - // TODO - some current pixi plugins bypass this.. so it not safe to use yet.. - if (this.program !== program) - { - this.program = program; - this.gl.useProgram(glProgram.program); - } - - if (!dontSync) - { - this.syncUniformGroup(shader.uniformGroup); - } - - return glProgram; - } - - /** - * Uploads the uniforms values to the currently bound shader. - * - * @param {object} uniforms - the uniforms valiues that be applied to the current shader - */ - setUniforms(uniforms) - { - const shader = this.shader.program; - const glProgram = shader.glPrograms[this.renderer.CONTEXT_UID]; - - shader.syncUniforms(glProgram.uniformData, uniforms, this.renderer); - } - - syncUniformGroup(group) - { - const glProgram = this.getglProgram(); - - if (!group.static || group.dirtyId !== glProgram.uniformGroups[group.id]) - { - glProgram.uniformGroups[group.id] = group.dirtyId; - const syncFunc = group.syncUniforms[this.shader.program.id] || this.createSyncGroups(group); - - syncFunc(glProgram.uniformData, group.uniforms, this.renderer); - } - } - - createSyncGroups(group) - { - group.syncUniforms[this.shader.program.id] = generateUniformsSync(group, this.shader.program.uniformData); - - return group.syncUniforms[this.shader.program.id]; - } - - /** - * Returns the underlying GLShade rof the currently bound shader. - * This can be handy for when you to have a little more control over the setting of your uniforms. - * - * @return {PIXI.glCore.Shader} the glProgram for the currently bound Shader for this context - */ - getglProgram() - { - if (this.shader) - { - return this.shader.program.glPrograms[this.renderer.CONTEXT_UID]; - } - - return null; - } - - /** - * Generates a glProgram verion of the Shader provided. - * - * @private - * @param {PIXI.Shader} shader the shader that the glProgram will be based on. - * @return {PIXI.glCore.glProgram} A shiney new glProgram - */ - generateShader(shader) - { - const gl = this.gl; - - const program = shader.program; - - const attribMap = {}; - - for (const i in program.attributeData) - { - attribMap[i] = program.attributeData[i].location; - } - - const shaderProgram = compileProgram(gl, program.vertexSrc, program.fragmentSrc, attribMap); - const uniformData = {}; - - for (const i in program.uniformData) - { - const data = program.uniformData[i]; - - uniformData[i] = { - location: gl.getUniformLocation(shaderProgram, i), - value: defaultValue(data.type, data.size), - }; - } - - const glProgram = new GLProgram(shaderProgram, uniformData); - - program.glPrograms[this.renderer.CONTEXT_UID] = glProgram; - - return glProgram; - } - - /** - * Destroys this System and removes all its textures - */ - destroy() - { - // TODO implement destroy method for ShaderSystem - this.destroyed = true; - } -} diff --git a/packages/core/src/renderers/systems/shader/shader/compileProgram.js b/packages/core/src/renderers/systems/shader/shader/compileProgram.js deleted file mode 100644 index 8c6ccb9..0000000 --- a/packages/core/src/renderers/systems/shader/shader/compileProgram.js +++ /dev/null @@ -1,78 +0,0 @@ -/** - * @method compileProgram - * @memberof PIXI.glCore.shader - * @param gl {WebGLRenderingContext} The current WebGL context {WebGLProgram} - * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. - * @param fragmentSrc {string|string[]} The fragment shader source as an array of strings. - * @param attributeLocations {Object} An attribute location map that lets you manually set the attribute locations - * @return {WebGLProgram} the shader program - */ -export default function compileProgram(gl, vertexSrc, fragmentSrc, attributeLocations) -{ - const glVertShader = compileShader(gl, gl.VERTEX_SHADER, vertexSrc); - const glFragShader = compileShader(gl, gl.FRAGMENT_SHADER, fragmentSrc); - - let program = gl.createProgram(); - - gl.attachShader(program, glVertShader); - gl.attachShader(program, glFragShader); - - // optionally, set the attributes manually for the program rather than letting WebGL decide.. - if (attributeLocations) - { - for (const i in attributeLocations) - { - gl.bindAttribLocation(program, attributeLocations[i], i); - } - } - - gl.linkProgram(program); - - // if linking fails, then log and cleanup - if (!gl.getProgramParameter(program, gl.LINK_STATUS)) - { - console.error('Pixi.js Error: Could not initialize shader.'); - console.error('gl.VALIDATE_STATUS', gl.getProgramParameter(program, gl.VALIDATE_STATUS)); - console.error('gl.getError()', gl.getError()); - - // if there is a program info log, log it - if (gl.getProgramInfoLog(program) !== '') - { - console.warn('Pixi.js Warning: gl.getProgramInfoLog()', gl.getProgramInfoLog(program)); - } - - gl.deleteProgram(program); - program = null; - } - - // clean up some shaders - gl.deleteShader(glVertShader); - gl.deleteShader(glFragShader); - - return program; -} - -/** - * @private - * @param gl {WebGLRenderingContext} The current WebGL context {WebGLProgram} - * @param type {Number} the type, can be either VERTEX_SHADER or FRAGMENT_SHADER - * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. - * @return {WebGLShader} the shader - */ -function compileShader(gl, type, src) -{ - const shader = gl.createShader(type); - - gl.shaderSource(shader, src); - gl.compileShader(shader); - - if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) - { - console.warn(src); - console.error(gl.getShaderInfoLog(shader)); - - return null; - } - - return shader; -} diff --git a/packages/core/src/renderers/systems/shader/shader/defaultValue.js b/packages/core/src/renderers/systems/shader/shader/defaultValue.js deleted file mode 100644 index 97290bd..0000000 --- a/packages/core/src/renderers/systems/shader/shader/defaultValue.js +++ /dev/null @@ -1,79 +0,0 @@ -/** - * @method defaultValue - * @memberof PIXI.glCore.shader - * @param type {String} Type of value - * @param size {Number} - */ -export default function defaultValue(type, size) -{ - switch (type) - { - case 'float': - return 0; - - case 'vec2': - return new Float32Array(2 * size); - - case 'vec3': - return new Float32Array(3 * size); - - case 'vec4': - return new Float32Array(4 * size); - - case 'int': - case 'sampler2D': - case 'sampler2DArray': - return 0; - - case 'ivec2': - return new Int32Array(2 * size); - - case 'ivec3': - return new Int32Array(3 * size); - - case 'ivec4': - return new Int32Array(4 * size); - - case 'bool': - return false; - - case 'bvec2': - - return booleanArray(2 * size); - - case 'bvec3': - return booleanArray(3 * size); - - case 'bvec4': - return booleanArray(4 * size); - - case 'mat2': - return new Float32Array([1, 0, - 0, 1]); - - case 'mat3': - return new Float32Array([1, 0, 0, - 0, 1, 0, - 0, 0, 1]); - - case 'mat4': - return new Float32Array([1, 0, 0, 0, - 0, 1, 0, 0, - 0, 0, 1, 0, - 0, 0, 0, 1]); - } - - return null; -} - -function booleanArray(size) -{ - const array = new Array(size); - - for (let i = 0; i < array.length; i++) - { - array[i] = false; - } - - return array; -} diff --git a/packages/core/src/renderers/systems/shader/shader/index.js b/packages/core/src/renderers/systems/shader/shader/index.js deleted file mode 100644 index ba7a317..0000000 --- a/packages/core/src/renderers/systems/shader/shader/index.js +++ /dev/null @@ -1,8 +0,0 @@ -/** - * @namespace PIXI.glCore.shader - */ -export { default as compileProgram } from './compileProgram'; -export { default as defaultValue } from './defaultValue'; -export { default as setPrecision } from './setPrecision'; -export { default as mapSize } from './mapSize'; -export { default as mapType } from './mapType'; diff --git a/packages/core/src/renderers/systems/shader/shader/mapSize.js b/packages/core/src/renderers/systems/shader/shader/mapSize.js deleted file mode 100644 index b681554..0000000 --- a/packages/core/src/renderers/systems/shader/shader/mapSize.js +++ /dev/null @@ -1,33 +0,0 @@ -const GLSL_TO_SIZE = { - float: 1, - vec2: 2, - vec3: 3, - vec4: 4, - - int: 1, - ivec2: 2, - ivec3: 3, - ivec4: 4, - - bool: 1, - bvec2: 2, - bvec3: 3, - bvec4: 4, - - mat2: 4, - mat3: 9, - mat4: 16, - - sampler2D: 1, -}; - -/** - * @method mapSize - * @memberof PIXI.glCore.shader - * @param type {String} - * @return {Number} - */ -export default function mapSize(type) -{ - return GLSL_TO_SIZE[type]; -} diff --git a/packages/core/src/renderers/systems/shader/shader/mapType.js b/packages/core/src/renderers/systems/shader/shader/mapType.js deleted file mode 100644 index d288b7c..0000000 --- a/packages/core/src/renderers/systems/shader/shader/mapType.js +++ /dev/null @@ -1,45 +0,0 @@ -let GL_TABLE = null; - -const GL_TO_GLSL_TYPES = { - FLOAT: 'float', - FLOAT_VEC2: 'vec2', - FLOAT_VEC3: 'vec3', - FLOAT_VEC4: 'vec4', - - INT: 'int', - INT_VEC2: 'ivec2', - INT_VEC3: 'ivec3', - INT_VEC4: 'ivec4', - - BOOL: 'bool', - BOOL_VEC2: 'bvec2', - BOOL_VEC3: 'bvec3', - BOOL_VEC4: 'bvec4', - - FLOAT_MAT2: 'mat2', - FLOAT_MAT3: 'mat3', - FLOAT_MAT4: 'mat4', - - SAMPLER_2D: 'sampler2D', - SAMPLER_CUBE: 'samplerCube', - SAMPLER_2D_ARRAY: 'sampler2DArray', -}; - -export default function mapType(gl, type) -{ - if (!GL_TABLE) - { - const typeNames = Object.keys(GL_TO_GLSL_TYPES); - - GL_TABLE = {}; - - for (let i = 0; i < typeNames.length; ++i) - { - const tn = typeNames[i]; - - GL_TABLE[gl[tn]] = GL_TO_GLSL_TYPES[tn]; - } - } - - return GL_TABLE[type]; -} diff --git a/packages/core/src/renderers/systems/shader/shader/setPrecision.js b/packages/core/src/renderers/systems/shader/shader/setPrecision.js deleted file mode 100644 index e2c1f34..0000000 --- a/packages/core/src/renderers/systems/shader/shader/setPrecision.js +++ /dev/null @@ -1,18 +0,0 @@ -/** - * Sets the float precision on the shader. If the precision is already present this function will do nothing - * @param {string} src the shader source - * @param {string} precision The float precision of the shader. Options are 'lowp', 'mediump' or 'highp'. - * - * @return {string} modified shader source - */ -export default function setPrecision(src, precision) -{ - src = src.trim(); - - if (src.substring(0, 9) !== 'precision' && src.substring(0, 1) !== '#') - { - return `precision ${precision} float;\n${src}`; - } - - return src; -} diff --git a/packages/core/src/renderers/systems/textures/GLTexture.js b/packages/core/src/renderers/systems/textures/GLTexture.js deleted file mode 100644 index c3fb8dc..0000000 --- a/packages/core/src/renderers/systems/textures/GLTexture.js +++ /dev/null @@ -1,33 +0,0 @@ -export default class GLTexture -{ - constructor(texture) - { - /** - * The WebGL texture - * - * @member {WebGLTexture} - */ - this.texture = texture; - - this.width = -1; - this.height = -1; - - /** - * Texture contents dirty flag - * @member {number} - */ - this.dirtyId = -1; - - /** - * Texture style dirty flag - * @type {number} - */ - this.dirtyStyleId = -1; - - /** - * Whether mip levels has to be generated - * @type {boolean} - */ - this.mipmap = false; - } -} diff --git a/packages/core/src/renderers/systems/textures/TextureGCSystem.js b/packages/core/src/renderers/systems/textures/TextureGCSystem.js deleted file mode 100644 index b089938..0000000 --- a/packages/core/src/renderers/systems/textures/TextureGCSystem.js +++ /dev/null @@ -1,110 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; -import { GC_MODES } from '@pixi/constants'; -import { settings } from '@pixi/settings'; - -/** - * TextureGarbageCollector. This class manages the GPU and ensures that it does not get clogged - * up with textures that are no longer being used. - * - * @class - * @memberof PIXI - */ -export default class TextureGCSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this.count = 0; - this.checkCount = 0; - this.maxIdle = settings.GC_MAX_IDLE; - this.checkCountMax = settings.GC_MAX_CHECK_COUNT; - this.mode = settings.GC_MODE; - } - - /** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ - postrender() - { - this.count++; - - if (this.mode === GC_MODES.MANUAL) - { - return; - } - - this.checkCount++; - - if (this.checkCount > this.checkCountMax) - { - this.checkCount = 0; - - this.run(); - } - } - - /** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ - run() - { - const tm = this.renderer.texture; - const managedTextures = tm.managedTextures; - let wasRemoved = false; - - for (let i = 0; i < managedTextures.length; i++) - { - const texture = managedTextures[i]; - - // only supports non generated textures at the moment! - if (!texture.frameBuffer && this.count - texture.touched > this.maxIdle) - { - tm.destroyTexture(texture, true); - managedTextures[i] = null; - wasRemoved = true; - } - } - - if (wasRemoved) - { - let j = 0; - - for (let i = 0; i < managedTextures.length; i++) - { - if (managedTextures[i] !== null) - { - managedTextures[j++] = managedTextures[i]; - } - } - - managedTextures.length = j; - } - } - - /** - * Removes all the textures within the specified displayObject and its children from the GPU - * - * @param {PIXI.DisplayObject} displayObject - the displayObject to remove the textures from. - */ - unload(displayObject) - { - const tm = this.renderer.textureSystem; - - // only destroy non generated textures - if (displayObject._texture && displayObject._texture._glRenderTargets) - { - tm.destroyTexture(displayObject._texture); - } - - for (let i = displayObject.children.length - 1; i >= 0; i--) - { - this.unload(displayObject.children[i]); - } - } -} diff --git a/packages/core/src/renderers/systems/textures/TextureSystem.js b/packages/core/src/renderers/systems/textures/TextureSystem.js deleted file mode 100644 index ccc5de9..0000000 --- a/packages/core/src/renderers/systems/textures/TextureSystem.js +++ /dev/null @@ -1,281 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; -import GLTexture from './GLTexture'; -import { removeItems } from '@pixi/utils'; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class TextureSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - // TODO set to max textures... - this.boundTextures = [ - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - ]; - - this.currentLocation = -1; - - this.managedTextures = []; - } - - /** - * Sets up the renderer context and necessary buffers. - * - * @private - */ - contextChange() - { - const gl = this.gl = this.renderer.gl; - - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - - // TODO move this.. to a nice make empty textures class.. - this.emptyTextures = {}; - - const emptyTexture2D = new GLTexture(gl.createTexture()); - - gl.bindTexture(gl.TEXTURE_2D, emptyTexture2D.texture); - gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array(4)); - - this.emptyTextures[gl.TEXTURE_2D] = emptyTexture2D; - this.emptyTextures[gl.TEXTURE_CUBE_MAP] = new GLTexture(gl.createTexture()); - - gl.bindTexture(gl.TEXTURE_CUBE_MAP, this.emptyTextures[gl.TEXTURE_CUBE_MAP].texture); - - let i; - - for (i = 0; i < 6; i++) - { - gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, null); - } - - gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_MAG_FILTER, gl.LINEAR); - gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_MIN_FILTER, gl.LINEAR); - - for (i = 0; i < this.boundTextures.length; i++) - { - this.bind(null, i); - } - } - - bind(texture, location) - { - const gl = this.gl; - - location = location || 0; - - if (this.currentLocation !== location) - { - this.currentLocation = location; - gl.activeTexture(gl.TEXTURE0 + location); - } - - if (texture) - { - texture = texture.baseTexture || texture; - - if (texture.valid) - { - texture.touched = this.renderer.textureGC.count; - - const glTexture = texture._glTextures[this.CONTEXT_UID] || this.initTexture(texture); - - gl.bindTexture(texture.target, glTexture.texture); - - if (glTexture.dirtyId !== texture.dirtyId) - { - this.updateTexture(texture); - } - - this.boundTextures[location] = texture; - } - } - else - { - gl.bindTexture(gl.TEXTURE_2D, this.emptyTextures[gl.TEXTURE_2D].texture); - this.boundTextures[location] = null; - } - } - - unbind(texture) - { - const gl = this.gl; - - for (let i = 0; i < this.boundTextures.length; i++) - { - if (this.boundTextures[i] === texture) - { - if (this.currentLocation !== i) - { - gl.activeTexture(gl.TEXTURE0 + i); - this.currentLocation = i; - } - - gl.bindTexture(gl.TEXTURE_2D, this.emptyTextures[texture.target].texture); - this.boundTextures[i] = null; - } - } - } - - initTexture(texture) - { - const glTexture = new GLTexture(this.gl.createTexture()); - - // guarentee an update.. - glTexture.dirtyId = -1; - - texture._glTextures[this.CONTEXT_UID] = glTexture; - - this.managedTextures.push(texture); - texture.on('dispose', this.destroyTexture, this); - - return glTexture; - } - - updateTexture(texture) - { - const glTexture = texture._glTextures[this.CONTEXT_UID]; - const renderer = this.renderer; - - if (texture.resource && texture.resource.upload(renderer, texture, glTexture)) - { - // texture is uploaded, dont do anything! - } - else - { - // default, renderTexture-like logic - const width = texture.realWidth; - const height = texture.realHeight; - const gl = renderer.gl; - - if (glTexture.width !== width - || glTexture.height !== height - || glTexture.dirtyId < 0) - { - glTexture.width = width; - glTexture.height = height; - - gl.texImage2D(texture.target, 0, - texture.format, - width, - height, - 0, - texture.format, - texture.type, - null); - } - } - - // lets only update what changes.. - if (texture.dirtyStyleId !== glTexture.dirtyStyleId) - { - this.updateTextureStyle(texture); - } - glTexture.dirtyId = texture.dirtyId; - } - - /** - * Deletes the texture from WebGL - * - * @param {PIXI.BaseTexture|PIXI.Texture} texture - the texture to destroy - * @param {boolean} [skipRemove=false] - Whether to skip removing the texture from the TextureManager. - */ - destroyTexture(texture, skipRemove) - { - const gl = this.gl; - - texture = texture.baseTexture || texture; - - if (texture._glTextures[this.renderer.CONTEXT_UID]) - { - this.unbind(texture); - - gl.deleteTexture(texture._glTextures[this.renderer.CONTEXT_UID].texture); - texture.off('dispose', this.destroyTexture, this); - - delete texture._glTextures[this.renderer.CONTEXT_UID]; - - if (!skipRemove) - { - const i = this.managedTextures.indexOf(texture); - - if (i !== -1) - { - removeItems(this.managedTextures, i, 1); - } - } - } - } - - updateTextureStyle(texture) - { - const glTexture = texture._glTextures[this.CONTEXT_UID]; - - glTexture.mipmap = texture.mipmap && texture.isPowerOfTwo; - if (!glTexture) - { - return; - } - - if (texture.resource && texture.resource.style(this.renderer, texture, glTexture)) - { - // style is set, dont do anything! - } - else - { - this.setStyle(texture, glTexture); - } - - glTexture.dirtyStyleId = texture.dirtyStyleId; - } - - setStyle(texture, glTexture) - { - const gl = this.gl; - - if (glTexture.mipmap) - { - gl.generateMipmap(texture.target); - } - - gl.texParameteri(texture.target, gl.TEXTURE_WRAP_S, texture.wrapMode); - gl.texParameteri(texture.target, gl.TEXTURE_WRAP_T, texture.wrapMode); - - if (glTexture.mipmap) - { - /* eslint-disable max-len */ - gl.texParameteri(texture.target, gl.TEXTURE_MIN_FILTER, texture.scaleMode ? gl.LINEAR_MIPMAP_LINEAR : gl.NEAREST_MIPMAP_NEAREST); - /* eslint-disable max-len */ - } - else - { - gl.texParameteri(texture.target, gl.TEXTURE_MIN_FILTER, texture.scaleMode ? gl.LINEAR : gl.NEAREST); - } - - gl.texParameteri(texture.target, gl.TEXTURE_MAG_FILTER, texture.scaleMode ? gl.LINEAR : gl.NEAREST); - } -} diff --git a/packages/core/src/renderers/utils/ObjectRenderer.js b/packages/core/src/renderers/utils/ObjectRenderer.js deleted file mode 100644 index f67a273..0000000 --- a/packages/core/src/renderers/utils/ObjectRenderer.js +++ /dev/null @@ -1,48 +0,0 @@ -import WebGLSystem from '../systems/WebGLSystem'; - -/** - * Base for a common object renderer that can be used as a system renderer plugin. - * - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class ObjectRenderer extends WebGLSystem -{ - /** - * Starts the renderer and sets the shader - * - */ - start() - { - // set the shader.. - } - - /** - * Stops the renderer - * - */ - stop() - { - this.flush(); - } - - /** - * Stub method for rendering content and emptying the current batch. - * - */ - flush() - { - // flush! - } - - /** - * Renders an object - * - * @param {PIXI.DisplayObject} object - The object to render. - */ - render(object) // eslint-disable-line no-unused-vars - { - // render the object - } -} diff --git a/packages/core/src/renderers/utils/Quad.js b/packages/core/src/renderers/utils/Quad.js deleted file mode 100644 index 5c1517b..0000000 --- a/packages/core/src/renderers/utils/Quad.js +++ /dev/null @@ -1,26 +0,0 @@ -import Geometry from '../../geometry/Geometry'; - -/** - * Helper class to create a quad - * - * @class - * @memberof PIXI - */ -export default class Quad extends Geometry -{ - /** - * just a quad - */ - constructor() - { - super(); - - this.addAttribute('aVertexPosition', [ - 0, 0, - 1, 0, - 1, 1, - 0, 1, - ]) - .addIndex([0, 1, 3, 2]); - } -} diff --git a/packages/core/src/renderers/utils/QuadUv.js b/packages/core/src/renderers/utils/QuadUv.js deleted file mode 100644 index 97bda89..0000000 --- a/packages/core/src/renderers/utils/QuadUv.js +++ /dev/null @@ -1,103 +0,0 @@ -import Geometry from '../../geometry/Geometry'; -import Buffer from '../../geometry/Buffer'; - -/** - * Helper class to create a quad with uvs like in v4 - * - * @class - * @memberof PIXI - */ -export default class QuadUv extends Geometry -{ - constructor() - { - super(); - - /** - * An array of vertices - * - * @member {Float32Array} - */ - this.vertices = new Float32Array([ - -1, -1, - 1, -1, - 1, 1, - -1, 1, - ]); - - /** - * The Uvs of the quad - * - * @member {Float32Array} - */ - this.uvs = new Float32Array([ - 0, 0, - 1, 0, - 1, 1, - 0, 1, - ]); - - this.vertexBuffer = new Buffer(this.vertices); - this.uvBuffer = new Buffer(this.uvs); - - this.addAttribute('aVertexPosition', this.vertexBuffer) - .addAttribute('aTextureCoord', this.uvBuffer) - .addIndex([0, 1, 2, 0, 2, 3]); - } - - /** - * Maps two Rectangle to the quad. - * - * @param {PIXI.Rectangle} targetTextureFrame - the first rectangle - * @param {PIXI.Rectangle} destinationFrame - the second rectangle - * @return {PIXI.Quad} Returns itself. - */ - map(targetTextureFrame, destinationFrame) - { - let x = 0; // destinationFrame.x / targetTextureFrame.width; - let y = 0; // destinationFrame.y / targetTextureFrame.height; - - this.uvs[0] = x; - this.uvs[1] = y; - - this.uvs[2] = x + (destinationFrame.width / targetTextureFrame.width); - this.uvs[3] = y; - - this.uvs[4] = x + (destinationFrame.width / targetTextureFrame.width); - this.uvs[5] = y + (destinationFrame.height / targetTextureFrame.height); - - this.uvs[6] = x; - this.uvs[7] = y + (destinationFrame.height / targetTextureFrame.height); - - x = destinationFrame.x; - y = destinationFrame.y; - - this.vertices[0] = x; - this.vertices[1] = y; - - this.vertices[2] = x + destinationFrame.width; - this.vertices[3] = y; - - this.vertices[4] = x + destinationFrame.width; - this.vertices[5] = y + destinationFrame.height; - - this.vertices[6] = x; - this.vertices[7] = y + destinationFrame.height; - - this.invalidate(); - - return this; - } - - /** - * legacy upload method, just marks buffers dirty - * @returns {PIXI.QuadUv} Returns itself. - */ - invalidate() - { - this.vertexBuffer._updateID++; - this.uvBuffer._updateID++; - - return this; - } -} diff --git a/packages/core/src/renderers/utils/checkMaxIfStatmentsInShader.js b/packages/core/src/renderers/utils/checkMaxIfStatmentsInShader.js deleted file mode 100644 index 8047c96..0000000 --- a/packages/core/src/renderers/utils/checkMaxIfStatmentsInShader.js +++ /dev/null @@ -1,58 +0,0 @@ -const fragTemplate = [ - 'precision mediump float;', - 'void main(void){', - 'float test = 0.1;', - '%forloop%', - 'gl_FragColor = vec4(0.0);', - '}', -].join('\n'); - -export default function checkMaxIfStatmentsInShader(maxIfs, gl) -{ - if (maxIfs === 0) - { - throw new Error('Invalid value of `0` passed to `checkMaxIfStatementsInShader`'); - } - - const shader = gl.createShader(gl.FRAGMENT_SHADER); - - while (true) // eslint-disable-line no-constant-condition - { - const fragmentSrc = fragTemplate.replace(/%forloop%/gi, generateIfTestSrc(maxIfs)); - - gl.shaderSource(shader, fragmentSrc); - gl.compileShader(shader); - - if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) - { - maxIfs = (maxIfs / 2) | 0; - } - else - { - // valid! - break; - } - } - - return maxIfs; -} - -function generateIfTestSrc(maxIfs) -{ - let src = ''; - - for (let i = 0; i < maxIfs; ++i) - { - if (i > 0) - { - src += '\nelse '; - } - - if (i < maxIfs - 1) - { - src += `if(test == ${i}.0){}`; - } - } - - return src; -} diff --git a/packages/core/src/renderers/utils/mapWebGLBlendModesToPixi.js b/packages/core/src/renderers/utils/mapWebGLBlendModesToPixi.js deleted file mode 100644 index 10ffe4d..0000000 --- a/packages/core/src/renderers/utils/mapWebGLBlendModesToPixi.js +++ /dev/null @@ -1,47 +0,0 @@ -import { BLEND_MODES } from '@pixi/constants'; - -/** - * Maps gl blend combinations to WebGL. - * - * @memberof PIXI - * @function mapWebGLBlendModesToPixi - * @private - * @param {WebGLRenderingContext} gl - The rendering context. - * @param {string[]} [array=[]] - The array to output into. - * @return {string[]} Mapped modes. - */ -export default function mapWebGLBlendModesToPixi(gl, array = []) -{ - // TODO - premultiply alpha would be different. - // add a boolean for that! - array[BLEND_MODES.NORMAL] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.ADD] = [gl.ONE, gl.DST_ALPHA]; - array[BLEND_MODES.MULTIPLY] = [gl.DST_COLOR, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.SCREEN] = [gl.ONE, gl.ONE_MINUS_SRC_COLOR]; - array[BLEND_MODES.OVERLAY] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.DARKEN] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.LIGHTEN] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.COLOR_DODGE] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.COLOR_BURN] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.HARD_LIGHT] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.SOFT_LIGHT] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.DIFFERENCE] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.EXCLUSION] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.HUE] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.SATURATION] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.COLOR] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.LUMINOSITY] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.NONE] = [0, 0]; - - // not-premultiplied blend modes - array[BLEND_MODES.NORMAL_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.ADD_NPM] = [gl.SRC_ALPHA, gl.DST_ALPHA, gl.ONE, gl.DST_ALPHA]; - array[BLEND_MODES.SCREEN_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_COLOR, gl.ONE, gl.ONE_MINUS_SRC_COLOR]; - - // not-premultiplied blend modes - array[BLEND_MODES.NORMAL_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.ADD_NPM] = [gl.SRC_ALPHA, gl.DST_ALPHA, gl.ONE, gl.DST_ALPHA]; - array[BLEND_MODES.SCREEN_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_COLOR, gl.ONE, gl.ONE_MINUS_SRC_COLOR]; - - return array; -} diff --git a/packages/core/src/shader/GLProgram.js b/packages/core/src/shader/GLProgram.js new file mode 100644 index 0000000..92980ca --- /dev/null +++ b/packages/core/src/shader/GLProgram.js @@ -0,0 +1,43 @@ +/** + * Helper class to create a webGL Program + * + * @class + * @private + */ +export default class GLProgram +{ + constructor(program, uniformData) + { + /** + * The shader program + * + * @member {WebGLProgram} + */ + this.program = program; + + /** + * holds the uniform data which contains uniform locations + * and current uniform values used for caching and preventing undeed GPU commands + * @type {Object} + */ + this.uniformData = uniformData; + + /** + * uniformGroups holds the various upload functions for the shader. Each uniform group + * and program have a unique upload function generated. + * @type {Object} + */ + this.uniformGroups = {}; + } + + /** + * Destroys this program + * TODO + */ + destroy() + { + this.uniformData = null; + this.uniformGroups = null; + this.program = null; + } +} diff --git a/packages/core/src/shader/Program.js b/packages/core/src/shader/Program.js index 4f9971e..4b4007a 100644 --- a/packages/core/src/shader/Program.js +++ b/packages/core/src/shader/Program.js @@ -1,6 +1,11 @@ -import * as shaderUtils from '../renderers/systems/shader/shader'; +// import * as from '../systems/shader/shader'; +import { setPrecision, + defaultValue, + compileProgram, + mapSize, + mapType, + getTestContext } from './utils'; import { ProgramCache } from '@pixi/utils'; -import getTestContext from './getTestContext'; import defaultFragment from './defaultProgram.frag'; import defaultVertex from './defaultProgram.vert'; import { settings } from '@pixi/settings'; @@ -34,8 +39,8 @@ */ this.fragmentSrc = fragmentSrc || Program.defaultFragmentSrc; - this.vertexSrc = shaderUtils.setPrecision(this.vertexSrc, settings.PRECISION_VERTEX); - this.fragmentSrc = shaderUtils.setPrecision(this.fragmentSrc, settings.PRECISION_FRAGMENT); + this.vertexSrc = setPrecision(this.vertexSrc, settings.PRECISION_VERTEX); + this.fragmentSrc = setPrecision(this.fragmentSrc, settings.PRECISION_FRAGMENT); // currently this does not extract structs only default types this.extractData(this.vertexSrc, this.fragmentSrc); @@ -62,7 +67,7 @@ if (gl) { - const program = shaderUtils.compileProgram(gl, vertexSrc, fragmentSrc); + const program = compileProgram(gl, vertexSrc, fragmentSrc); this.attributeData = this.getAttributeData(program, gl); this.uniformData = this.getUniformData(program, gl); @@ -95,13 +100,13 @@ for (let i = 0; i < totalAttributes; i++) { const attribData = gl.getActiveAttrib(program, i); - const type = shaderUtils.mapType(gl, attribData.type); + const type = mapType(gl, attribData.type); /*eslint-disable */ const data = { type: type, name: attribData.name, - size: shaderUtils.mapSize(type), + size: mapSize(type), location: 0, }; /* eslint-enable */ @@ -145,14 +150,14 @@ const name = uniformData.name.replace(/\[.*?\]/, ''); const isArray = uniformData.name.match(/\[.*?\]/, ''); - const type = shaderUtils.mapType(gl, uniformData.type); + const type = mapType(gl, uniformData.type); /*eslint-disable */ uniforms[name] = { type: type, size: uniformData.size, isArray:isArray, - value: shaderUtils.defaultValue(type, uniformData.size), + value: defaultValue(type, uniformData.size), }; /* eslint-enable */ } diff --git a/packages/core/src/shader/ShaderSystem.js b/packages/core/src/shader/ShaderSystem.js new file mode 100644 index 0000000..40b8af8 --- /dev/null +++ b/packages/core/src/shader/ShaderSystem.js @@ -0,0 +1,171 @@ +import System from '../System'; +import GLProgram from './GLProgram'; +import { generateUniformsSync, + defaultValue, + compileProgram } from './utils'; + +let UID = 0; + +/** + * Helper class to create a webGL Texture + * + * @class + * @memberof PIXI.systems + * @extends PIXI.System + */ +export default class ShaderSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - A reference to the current renderer + */ + constructor(renderer) + { + super(renderer); + + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = null; + + this.shader = null; + this.program = null; + + this.id = UID++; + } + + contextChange(gl) + { + this.gl = gl; + } + + /** + * Changes the current shader to the one given in parameter + * + * @param {PIXI.Shader} shader - the new shader + * @param {boolean} dontSync - false if the shader should automatically sync its uniforms. + * @returns {PIXI.glCore.glProgram} the glProgram that belongs to the shader. + */ + bind(shader, dontSync) + { + shader.uniforms.globals = this.renderer.globalUniforms; + + const program = shader.program; + const glProgram = program.glPrograms[this.renderer.CONTEXT_UID] || this.generateShader(shader); + + this.shader = shader; + + // TODO - some current pixi plugins bypass this.. so it not safe to use yet.. + if (this.program !== program) + { + this.program = program; + this.gl.useProgram(glProgram.program); + } + + if (!dontSync) + { + this.syncUniformGroup(shader.uniformGroup); + } + + return glProgram; + } + + /** + * Uploads the uniforms values to the currently bound shader. + * + * @param {object} uniforms - the uniforms valiues that be applied to the current shader + */ + setUniforms(uniforms) + { + const shader = this.shader.program; + const glProgram = shader.glPrograms[this.renderer.CONTEXT_UID]; + + shader.syncUniforms(glProgram.uniformData, uniforms, this.renderer); + } + + syncUniformGroup(group) + { + const glProgram = this.getglProgram(); + + if (!group.static || group.dirtyId !== glProgram.uniformGroups[group.id]) + { + glProgram.uniformGroups[group.id] = group.dirtyId; + const syncFunc = group.syncUniforms[this.shader.program.id] || this.createSyncGroups(group); + + syncFunc(glProgram.uniformData, group.uniforms, this.renderer); + } + } + + createSyncGroups(group) + { + group.syncUniforms[this.shader.program.id] = generateUniformsSync(group, this.shader.program.uniformData); + + return group.syncUniforms[this.shader.program.id]; + } + + /** + * Returns the underlying GLShade rof the currently bound shader. + * This can be handy for when you to have a little more control over the setting of your uniforms. + * + * @return {PIXI.glCore.Shader} the glProgram for the currently bound Shader for this context + */ + getglProgram() + { + if (this.shader) + { + return this.shader.program.glPrograms[this.renderer.CONTEXT_UID]; + } + + return null; + } + + /** + * Generates a glProgram verion of the Shader provided. + * + * @private + * @param {PIXI.Shader} shader the shader that the glProgram will be based on. + * @return {PIXI.glCore.glProgram} A shiney new glProgram + */ + generateShader(shader) + { + const gl = this.gl; + + const program = shader.program; + + const attribMap = {}; + + for (const i in program.attributeData) + { + attribMap[i] = program.attributeData[i].location; + } + + const shaderProgram = compileProgram(gl, program.vertexSrc, program.fragmentSrc, attribMap); + const uniformData = {}; + + for (const i in program.uniformData) + { + const data = program.uniformData[i]; + + uniformData[i] = { + location: gl.getUniformLocation(shaderProgram, i), + value: defaultValue(data.type, data.size), + }; + } + + const glProgram = new GLProgram(shaderProgram, uniformData); + + program.glPrograms[this.renderer.CONTEXT_UID] = glProgram; + + return glProgram; + } + + /** + * Destroys this System and removes all its textures + */ + destroy() + { + // TODO implement destroy method for ShaderSystem + this.destroyed = true; + } +} diff --git a/packages/core/src/shader/defaultValue.js b/packages/core/src/shader/defaultValue.js deleted file mode 100644 index 847a069..0000000 --- a/packages/core/src/shader/defaultValue.js +++ /dev/null @@ -1,78 +0,0 @@ -function booleanArray(size) -{ - const array = new Array(size); - - for (let i = 0; i < array.length; i++) - { - array[i] = false; - } - - return array; -} - -/** - * @class - * @memberof PIXI.glCore.shader - * @param type {String} Type of value - * @param size {Number} - */ -export default function defaultValue(type, size) -{ - switch (type) - { - case 'float': - return 0; - - case 'vec2': - return new Float32Array(2 * size); - - case 'vec3': - return new Float32Array(3 * size); - - case 'vec4': - return new Float32Array(4 * size); - - case 'int': - case 'sampler2D': - return 0; - - case 'ivec2': - return new Int32Array(2 * size); - - case 'ivec3': - return new Int32Array(3 * size); - - case 'ivec4': - return new Int32Array(4 * size); - - case 'bool': - return false; - - case 'bvec2': - - return booleanArray(2 * size); - - case 'bvec3': - return booleanArray(3 * size); - - case 'bvec4': - return booleanArray(4 * size); - - case 'mat2': - return new Float32Array([1, 0, - 0, 1]); - - case 'mat3': - return new Float32Array([1, 0, 0, - 0, 1, 0, - 0, 0, 1]); - - case 'mat4': - return new Float32Array([1, 0, 0, 0, - 0, 1, 0, 0, - 0, 0, 1, 0, - 0, 0, 0, 1]); - } - - return 0; -} diff --git a/packages/core/src/shader/extractUniformsFromSrc.js b/packages/core/src/shader/extractUniformsFromSrc.js deleted file mode 100644 index 3d02c0a..0000000 --- a/packages/core/src/shader/extractUniformsFromSrc.js +++ /dev/null @@ -1,57 +0,0 @@ -import defaultValue from './defaultValue'; - -export default function extractUniformsFromSrc(vertexSrc, fragmentSrc, mask) -{ - const vertUniforms = extractUniformsFromString(vertexSrc, mask); - const fragUniforms = extractUniformsFromString(fragmentSrc, mask); - - return Object.assign(vertUniforms, fragUniforms); -} - -function extractUniformsFromString(string) -{ - const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); - - const uniforms = {}; - let nameSplit; - - // clean the lines a little - remove extra spaces / teabs etc - // then split along ';' - const lines = string.replace(/\s+/g, ' ') - .split(/\s*;\s*/); - - // loop through.. - for (let i = 0; i < lines.length; i++) - { - const line = lines[i].trim(); - - if (line.indexOf('uniform') > -1) - { - const splitLine = line.split(' '); - const type = splitLine[1]; - - let name = splitLine[2]; - let size = 1; - - if (name.indexOf('[') > -1) - { - // array! - nameSplit = name.split(/\[|]/); - name = nameSplit[0]; - size *= Number(nameSplit[1]); - } - - if (!name.match(maskRegex)) - { - uniforms[name] = { - value: defaultValue(type, size), - dirtyId: 0, - name, - type, - }; - } - } - } - - return uniforms; -} diff --git a/packages/core/src/shader/generateUniformsSync.js b/packages/core/src/shader/generateUniformsSync.js deleted file mode 100644 index 98a357f..0000000 --- a/packages/core/src/shader/generateUniformsSync.js +++ /dev/null @@ -1,231 +0,0 @@ -// cv = CachedValue -// v = value -// ud = uniformData -// uv = uniformValue -// l = loaction -const GLSL_TO_SINGLE_SETTERS_CACHED = { - - float: ` - if(cv !== v) - { - cv.v = v; - gl.uniform1f(location, v) - }`, - - vec2: ` - if(cv[0] !== v[0] || cv[1] !== v[1]) - { - cv[0] = v[0]; - cv[1] = v[1]; - gl.uniform2f(location, v[0], v[1]) - }`, - - vec3: ` - if(cv[0] !== v[0] || cv[1] !== v[1] || cv[2] !== v[2]) - { - cv[0] = v[0]; - cv[1] = v[1]; - cv[2] = v[2]; - - gl.uniform3f(location, v[0], v[1], v[2]) - }`, - - vec4: 'gl.uniform4f(location, v[0], v[1], v[2], v[3])', - - int: 'gl.uniform1i(location, v)', - ivec2: 'gl.uniform2i(location, v[0], v[1])', - ivec3: 'gl.uniform3i(location, v[0], v[1], v[2])', - ivec4: 'gl.uniform4i(location, v[0], v[1], v[2], v[3])', - - bool: 'gl.uniform1i(location, v)', - bvec2: 'gl.uniform2i(location, v[0], v[1])', - bvec3: 'gl.uniform3i(location, v[0], v[1], v[2])', - bvec4: 'gl.uniform4i(location, v[0], v[1], v[2], v[3])', - - mat2: 'gl.uniformMatrix2fv(location, false, v)', - mat3: 'gl.uniformMatrix3fv(location, false, v)', - mat4: 'gl.uniformMatrix4fv(location, false, v)', - - sampler2D: 'gl.uniform1i(location, v)', - samplerCube: 'gl.uniform1i(location, v)', - sampler2DArray: 'gl.uniform1i(location, v)', -}; - -const GLSL_TO_ARRAY_SETTERS = { - - float: `gl.uniform1fv(location, v)`, - - vec2: `gl.uniform2fv(location, v)`, - vec3: `gl.uniform3fv(location, v)`, - vec4: 'gl.uniform4fv(location, v)', - - mat4: 'gl.uniformMatrix4fv(location, false, v)', - mat3: 'gl.uniformMatrix3fv(location, false, v)', - mat2: 'gl.uniformMatrix2fv(location, false, v)', - - int: 'gl.uniform1iv(location, v)', - ivec2: 'gl.uniform2iv(location, v)', - ivec3: 'gl.uniform3iv(location, v)', - ivec4: 'gl.uniform4iv(location, v)', - - bool: 'gl.uniform1iv(location, v)', - bvec2: 'gl.uniform2iv(location, v)', - bvec3: 'gl.uniform3iv(location, v)', - bvec4: 'gl.uniform4iv(location, v)', - - sampler2D: 'gl.uniform1iv(location, v)', - samplerCube: 'gl.uniform1iv(location, v)', - sampler2DArray: 'gl.uniform1iv(location, v)', -}; - -export default function generateUniformsSync(group, uniformData) -{ - let textureCount = 0; - let func = `var v = null; - var cv = null - var gl = renderer.gl`; - - for (const i in group.uniforms) - { - const data = uniformData[i]; - - if (!data) - { - if (group.uniforms[i].group) - { - func += ` - renderer.shader.syncUniformGroup(uv.${i}); - `; - } - - continue; - } - - // TODO && uniformData[i].value !== 0 <-- do we still need this? - if (data.type === 'float' && data.size === 1) - { - func += ` - if(uv.${i} !== ud.${i}.value) - { - ud.${i}.value = uv.${i} - gl.uniform1f(ud.${i}.location, uv.${i}) - }\n`; - } - /* eslint-disable max-len */ - else if ((data.type === 'sampler2D' || data.type === 'samplerCube' || data.type === 'sampler2DArray') && data.size === 1 && !data.isArray) - /* eslint-disable max-len */ - { - func += ` - renderer.texture.bind(uv.${i}, ${textureCount}); - - if(ud.${i}.value !== ${textureCount}) - { - ud.${i}.value = ${textureCount}; - gl.uniform1i(ud.${i}.location, ${textureCount});\n; // eslint-disable-line max-len - }\n`; - - textureCount++; - } - else if (data.type === 'mat3' && data.size === 1) - { - if (group.uniforms[i].a !== undefined) - { - // TODO and some smart caching dirty ids here! - func += ` - gl.uniformMatrix3fv(ud.${i}.location, false, uv.${i}.toArray(true)); - \n`; - } - else - { - func += ` - gl.uniformMatrix3fv(ud.${i}.location, false, uv.${i}); - \n`; - } - } - else if (data.type === 'vec2' && data.size === 1) - { - // TODO - do we need both here? - // maybe we can get away with only using points? - if (group.uniforms[i].x !== undefined) - { - func += ` - cv = ud.${i}.value; - v = uv.${i}; - - if(cv[0] !== v.x || cv[1] !== v.y) - { - cv[0] = v.x; - cv[1] = v.y; - gl.uniform2f(ud.${i}.location, v.x, v.y); - }\n`; - } - else - { - func += ` - cv = ud.${i}.value; - v = uv.${i}; - - if(cv[0] !== v[0] || cv[1] !== v[1]) - { - cv[0] = v[0]; - cv[1] = v[1]; - gl.uniform2f(ud.${i}.location, v[0], v[1]); - } - \n`; - } - } - else if (data.type === 'vec4' && data.size === 1) - { - // TODO - do we need both here? - // maybe we can get away with only using points? - if (group.uniforms[i].width !== undefined) - { - func += ` - cv = ud.${i}.value; - v = uv.${i}; - - if(cv[0] !== v.x || cv[1] !== v.y || cv[2] !== v.width || cv[3] !== v.height) - { - cv[0] = v.x; - cv[1] = v.y; - cv[2] = v.width; - cv[3] = v.height; - gl.uniform4f(ud.${i}.location, v.x, v.y, v.width, v.height) - }\n`; - } - else - { - func += ` - cv = ud.${i}.value; - v = uv.${i}; - - if(cv[0] !== v[0] || cv[1] !== v[1] || cv[2] !== v[2] || cv[3] !== v[3]) - { - cv[0] = v[0]; - cv[1] = v[1]; - cv[2] = v[2]; - cv[3] = v[3]; - - gl.uniform4f(ud.${i}.location, v[0], v[1], v[2], v[3]) - } - \n`; - } - } - else - { - const templateType = (data.size === 1) ? GLSL_TO_SINGLE_SETTERS_CACHED : GLSL_TO_ARRAY_SETTERS; - - const template = templateType[data.type].replace('location', `ud.${i}.location`); - - func += ` - cv = ud.${i}.value; - v = uv.${i}; - ${template};\n`; - } - } - - // console.log(' --------------- ') - // console.log(func); - - return new Function('ud', 'uv', 'renderer', func); // eslint-disable-line no-new-func -} diff --git a/packages/core/src/shader/getTestContext.js b/packages/core/src/shader/getTestContext.js deleted file mode 100644 index ea174cc..0000000 --- a/packages/core/src/shader/getTestContext.js +++ /dev/null @@ -1,48 +0,0 @@ -import { settings } from '@pixi/settings'; - -let context = null; - -/** - * returns a little webGL context to use for program inspection. - * - * @static - * @private - * @returns {webGL-context} a gl context to test with - */ -export default function getTestContext() -{ - if (!context) - { - const canvas = document.createElement('canvas'); - - let gl; - - if (settings.PREFER_WEBGL_2) - { - gl = canvas.getContext('webgl2', {}); - } - - if (!gl) - { - gl = canvas.getContext('webgl', {}) - || canvas.getContext('experimental-webgl', {}); - - if (!gl) - { - // fail, not able to get a context - throw new Error('This browser does not support webGL. Try using the canvas renderer'); - } - else - { - // for shader testing.. - gl.getExtension('WEBGL_draw_buffers'); - } - } - - context = gl; - - return gl; - } - - return context; -} diff --git a/packages/core/src/shader/utils/checkMaxIfStatmentsInShader.js b/packages/core/src/shader/utils/checkMaxIfStatmentsInShader.js new file mode 100644 index 0000000..8047c96 --- /dev/null +++ b/packages/core/src/shader/utils/checkMaxIfStatmentsInShader.js @@ -0,0 +1,58 @@ +const fragTemplate = [ + 'precision mediump float;', + 'void main(void){', + 'float test = 0.1;', + '%forloop%', + 'gl_FragColor = vec4(0.0);', + '}', +].join('\n'); + +export default function checkMaxIfStatmentsInShader(maxIfs, gl) +{ + if (maxIfs === 0) + { + throw new Error('Invalid value of `0` passed to `checkMaxIfStatementsInShader`'); + } + + const shader = gl.createShader(gl.FRAGMENT_SHADER); + + while (true) // eslint-disable-line no-constant-condition + { + const fragmentSrc = fragTemplate.replace(/%forloop%/gi, generateIfTestSrc(maxIfs)); + + gl.shaderSource(shader, fragmentSrc); + gl.compileShader(shader); + + if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) + { + maxIfs = (maxIfs / 2) | 0; + } + else + { + // valid! + break; + } + } + + return maxIfs; +} + +function generateIfTestSrc(maxIfs) +{ + let src = ''; + + for (let i = 0; i < maxIfs; ++i) + { + if (i > 0) + { + src += '\nelse '; + } + + if (i < maxIfs - 1) + { + src += `if(test == ${i}.0){}`; + } + } + + return src; +} diff --git a/packages/core/src/shader/utils/compileProgram.js b/packages/core/src/shader/utils/compileProgram.js new file mode 100644 index 0000000..8c6ccb9 --- /dev/null +++ b/packages/core/src/shader/utils/compileProgram.js @@ -0,0 +1,78 @@ +/** + * @method compileProgram + * @memberof PIXI.glCore.shader + * @param gl {WebGLRenderingContext} The current WebGL context {WebGLProgram} + * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. + * @param fragmentSrc {string|string[]} The fragment shader source as an array of strings. + * @param attributeLocations {Object} An attribute location map that lets you manually set the attribute locations + * @return {WebGLProgram} the shader program + */ +export default function compileProgram(gl, vertexSrc, fragmentSrc, attributeLocations) +{ + const glVertShader = compileShader(gl, gl.VERTEX_SHADER, vertexSrc); + const glFragShader = compileShader(gl, gl.FRAGMENT_SHADER, fragmentSrc); + + let program = gl.createProgram(); + + gl.attachShader(program, glVertShader); + gl.attachShader(program, glFragShader); + + // optionally, set the attributes manually for the program rather than letting WebGL decide.. + if (attributeLocations) + { + for (const i in attributeLocations) + { + gl.bindAttribLocation(program, attributeLocations[i], i); + } + } + + gl.linkProgram(program); + + // if linking fails, then log and cleanup + if (!gl.getProgramParameter(program, gl.LINK_STATUS)) + { + console.error('Pixi.js Error: Could not initialize shader.'); + console.error('gl.VALIDATE_STATUS', gl.getProgramParameter(program, gl.VALIDATE_STATUS)); + console.error('gl.getError()', gl.getError()); + + // if there is a program info log, log it + if (gl.getProgramInfoLog(program) !== '') + { + console.warn('Pixi.js Warning: gl.getProgramInfoLog()', gl.getProgramInfoLog(program)); + } + + gl.deleteProgram(program); + program = null; + } + + // clean up some shaders + gl.deleteShader(glVertShader); + gl.deleteShader(glFragShader); + + return program; +} + +/** + * @private + * @param gl {WebGLRenderingContext} The current WebGL context {WebGLProgram} + * @param type {Number} the type, can be either VERTEX_SHADER or FRAGMENT_SHADER + * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. + * @return {WebGLShader} the shader + */ +function compileShader(gl, type, src) +{ + const shader = gl.createShader(type); + + gl.shaderSource(shader, src); + gl.compileShader(shader); + + if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) + { + console.warn(src); + console.error(gl.getShaderInfoLog(shader)); + + return null; + } + + return shader; +} diff --git a/packages/core/src/shader/utils/defaultValue.js b/packages/core/src/shader/utils/defaultValue.js new file mode 100644 index 0000000..97290bd --- /dev/null +++ b/packages/core/src/shader/utils/defaultValue.js @@ -0,0 +1,79 @@ +/** + * @method defaultValue + * @memberof PIXI.glCore.shader + * @param type {String} Type of value + * @param size {Number} + */ +export default function defaultValue(type, size) +{ + switch (type) + { + case 'float': + return 0; + + case 'vec2': + return new Float32Array(2 * size); + + case 'vec3': + return new Float32Array(3 * size); + + case 'vec4': + return new Float32Array(4 * size); + + case 'int': + case 'sampler2D': + case 'sampler2DArray': + return 0; + + case 'ivec2': + return new Int32Array(2 * size); + + case 'ivec3': + return new Int32Array(3 * size); + + case 'ivec4': + return new Int32Array(4 * size); + + case 'bool': + return false; + + case 'bvec2': + + return booleanArray(2 * size); + + case 'bvec3': + return booleanArray(3 * size); + + case 'bvec4': + return booleanArray(4 * size); + + case 'mat2': + return new Float32Array([1, 0, + 0, 1]); + + case 'mat3': + return new Float32Array([1, 0, 0, + 0, 1, 0, + 0, 0, 1]); + + case 'mat4': + return new Float32Array([1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1]); + } + + return null; +} + +function booleanArray(size) +{ + const array = new Array(size); + + for (let i = 0; i < array.length; i++) + { + array[i] = false; + } + + return array; +} diff --git a/packages/core/src/shader/utils/extractUniformsFromSrc.js b/packages/core/src/shader/utils/extractUniformsFromSrc.js new file mode 100644 index 0000000..3d02c0a --- /dev/null +++ b/packages/core/src/shader/utils/extractUniformsFromSrc.js @@ -0,0 +1,57 @@ +import defaultValue from './defaultValue'; + +export default function extractUniformsFromSrc(vertexSrc, fragmentSrc, mask) +{ + const vertUniforms = extractUniformsFromString(vertexSrc, mask); + const fragUniforms = extractUniformsFromString(fragmentSrc, mask); + + return Object.assign(vertUniforms, fragUniforms); +} + +function extractUniformsFromString(string) +{ + const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); + + const uniforms = {}; + let nameSplit; + + // clean the lines a little - remove extra spaces / teabs etc + // then split along ';' + const lines = string.replace(/\s+/g, ' ') + .split(/\s*;\s*/); + + // loop through.. + for (let i = 0; i < lines.length; i++) + { + const line = lines[i].trim(); + + if (line.indexOf('uniform') > -1) + { + const splitLine = line.split(' '); + const type = splitLine[1]; + + let name = splitLine[2]; + let size = 1; + + if (name.indexOf('[') > -1) + { + // array! + nameSplit = name.split(/\[|]/); + name = nameSplit[0]; + size *= Number(nameSplit[1]); + } + + if (!name.match(maskRegex)) + { + uniforms[name] = { + value: defaultValue(type, size), + dirtyId: 0, + name, + type, + }; + } + } + } + + return uniforms; +} diff --git a/packages/core/src/shader/utils/generateUniformsSync.js b/packages/core/src/shader/utils/generateUniformsSync.js new file mode 100644 index 0000000..98a357f --- /dev/null +++ b/packages/core/src/shader/utils/generateUniformsSync.js @@ -0,0 +1,231 @@ +// cv = CachedValue +// v = value +// ud = uniformData +// uv = uniformValue +// l = loaction +const GLSL_TO_SINGLE_SETTERS_CACHED = { + + float: ` + if(cv !== v) + { + cv.v = v; + gl.uniform1f(location, v) + }`, + + vec2: ` + if(cv[0] !== v[0] || cv[1] !== v[1]) + { + cv[0] = v[0]; + cv[1] = v[1]; + gl.uniform2f(location, v[0], v[1]) + }`, + + vec3: ` + if(cv[0] !== v[0] || cv[1] !== v[1] || cv[2] !== v[2]) + { + cv[0] = v[0]; + cv[1] = v[1]; + cv[2] = v[2]; + + gl.uniform3f(location, v[0], v[1], v[2]) + }`, + + vec4: 'gl.uniform4f(location, v[0], v[1], v[2], v[3])', + + int: 'gl.uniform1i(location, v)', + ivec2: 'gl.uniform2i(location, v[0], v[1])', + ivec3: 'gl.uniform3i(location, v[0], v[1], v[2])', + ivec4: 'gl.uniform4i(location, v[0], v[1], v[2], v[3])', + + bool: 'gl.uniform1i(location, v)', + bvec2: 'gl.uniform2i(location, v[0], v[1])', + bvec3: 'gl.uniform3i(location, v[0], v[1], v[2])', + bvec4: 'gl.uniform4i(location, v[0], v[1], v[2], v[3])', + + mat2: 'gl.uniformMatrix2fv(location, false, v)', + mat3: 'gl.uniformMatrix3fv(location, false, v)', + mat4: 'gl.uniformMatrix4fv(location, false, v)', + + sampler2D: 'gl.uniform1i(location, v)', + samplerCube: 'gl.uniform1i(location, v)', + sampler2DArray: 'gl.uniform1i(location, v)', +}; + +const GLSL_TO_ARRAY_SETTERS = { + + float: `gl.uniform1fv(location, v)`, + + vec2: `gl.uniform2fv(location, v)`, + vec3: `gl.uniform3fv(location, v)`, + vec4: 'gl.uniform4fv(location, v)', + + mat4: 'gl.uniformMatrix4fv(location, false, v)', + mat3: 'gl.uniformMatrix3fv(location, false, v)', + mat2: 'gl.uniformMatrix2fv(location, false, v)', + + int: 'gl.uniform1iv(location, v)', + ivec2: 'gl.uniform2iv(location, v)', + ivec3: 'gl.uniform3iv(location, v)', + ivec4: 'gl.uniform4iv(location, v)', + + bool: 'gl.uniform1iv(location, v)', + bvec2: 'gl.uniform2iv(location, v)', + bvec3: 'gl.uniform3iv(location, v)', + bvec4: 'gl.uniform4iv(location, v)', + + sampler2D: 'gl.uniform1iv(location, v)', + samplerCube: 'gl.uniform1iv(location, v)', + sampler2DArray: 'gl.uniform1iv(location, v)', +}; + +export default function generateUniformsSync(group, uniformData) +{ + let textureCount = 0; + let func = `var v = null; + var cv = null + var gl = renderer.gl`; + + for (const i in group.uniforms) + { + const data = uniformData[i]; + + if (!data) + { + if (group.uniforms[i].group) + { + func += ` + renderer.shader.syncUniformGroup(uv.${i}); + `; + } + + continue; + } + + // TODO && uniformData[i].value !== 0 <-- do we still need this? + if (data.type === 'float' && data.size === 1) + { + func += ` + if(uv.${i} !== ud.${i}.value) + { + ud.${i}.value = uv.${i} + gl.uniform1f(ud.${i}.location, uv.${i}) + }\n`; + } + /* eslint-disable max-len */ + else if ((data.type === 'sampler2D' || data.type === 'samplerCube' || data.type === 'sampler2DArray') && data.size === 1 && !data.isArray) + /* eslint-disable max-len */ + { + func += ` + renderer.texture.bind(uv.${i}, ${textureCount}); + + if(ud.${i}.value !== ${textureCount}) + { + ud.${i}.value = ${textureCount}; + gl.uniform1i(ud.${i}.location, ${textureCount});\n; // eslint-disable-line max-len + }\n`; + + textureCount++; + } + else if (data.type === 'mat3' && data.size === 1) + { + if (group.uniforms[i].a !== undefined) + { + // TODO and some smart caching dirty ids here! + func += ` + gl.uniformMatrix3fv(ud.${i}.location, false, uv.${i}.toArray(true)); + \n`; + } + else + { + func += ` + gl.uniformMatrix3fv(ud.${i}.location, false, uv.${i}); + \n`; + } + } + else if (data.type === 'vec2' && data.size === 1) + { + // TODO - do we need both here? + // maybe we can get away with only using points? + if (group.uniforms[i].x !== undefined) + { + func += ` + cv = ud.${i}.value; + v = uv.${i}; + + if(cv[0] !== v.x || cv[1] !== v.y) + { + cv[0] = v.x; + cv[1] = v.y; + gl.uniform2f(ud.${i}.location, v.x, v.y); + }\n`; + } + else + { + func += ` + cv = ud.${i}.value; + v = uv.${i}; + + if(cv[0] !== v[0] || cv[1] !== v[1]) + { + cv[0] = v[0]; + cv[1] = v[1]; + gl.uniform2f(ud.${i}.location, v[0], v[1]); + } + \n`; + } + } + else if (data.type === 'vec4' && data.size === 1) + { + // TODO - do we need both here? + // maybe we can get away with only using points? + if (group.uniforms[i].width !== undefined) + { + func += ` + cv = ud.${i}.value; + v = uv.${i}; + + if(cv[0] !== v.x || cv[1] !== v.y || cv[2] !== v.width || cv[3] !== v.height) + { + cv[0] = v.x; + cv[1] = v.y; + cv[2] = v.width; + cv[3] = v.height; + gl.uniform4f(ud.${i}.location, v.x, v.y, v.width, v.height) + }\n`; + } + else + { + func += ` + cv = ud.${i}.value; + v = uv.${i}; + + if(cv[0] !== v[0] || cv[1] !== v[1] || cv[2] !== v[2] || cv[3] !== v[3]) + { + cv[0] = v[0]; + cv[1] = v[1]; + cv[2] = v[2]; + cv[3] = v[3]; + + gl.uniform4f(ud.${i}.location, v[0], v[1], v[2], v[3]) + } + \n`; + } + } + else + { + const templateType = (data.size === 1) ? GLSL_TO_SINGLE_SETTERS_CACHED : GLSL_TO_ARRAY_SETTERS; + + const template = templateType[data.type].replace('location', `ud.${i}.location`); + + func += ` + cv = ud.${i}.value; + v = uv.${i}; + ${template};\n`; + } + } + + // console.log(' --------------- ') + // console.log(func); + + return new Function('ud', 'uv', 'renderer', func); // eslint-disable-line no-new-func +} diff --git a/packages/core/src/shader/utils/getTestContext.js b/packages/core/src/shader/utils/getTestContext.js new file mode 100644 index 0000000..ea174cc --- /dev/null +++ b/packages/core/src/shader/utils/getTestContext.js @@ -0,0 +1,48 @@ +import { settings } from '@pixi/settings'; + +let context = null; + +/** + * returns a little webGL context to use for program inspection. + * + * @static + * @private + * @returns {webGL-context} a gl context to test with + */ +export default function getTestContext() +{ + if (!context) + { + const canvas = document.createElement('canvas'); + + let gl; + + if (settings.PREFER_WEBGL_2) + { + gl = canvas.getContext('webgl2', {}); + } + + if (!gl) + { + gl = canvas.getContext('webgl', {}) + || canvas.getContext('experimental-webgl', {}); + + if (!gl) + { + // fail, not able to get a context + throw new Error('This browser does not support webGL. Try using the canvas renderer'); + } + else + { + // for shader testing.. + gl.getExtension('WEBGL_draw_buffers'); + } + } + + context = gl; + + return gl; + } + + return context; +} diff --git a/packages/core/src/shader/utils/index.js b/packages/core/src/shader/utils/index.js new file mode 100644 index 0000000..4bf8dd4 --- /dev/null +++ b/packages/core/src/shader/utils/index.js @@ -0,0 +1,8 @@ +export { default as compileProgram } from './compileProgram'; +export { default as defaultValue } from './defaultValue'; +export { default as setPrecision } from './setPrecision'; +export { default as mapSize } from './mapSize'; +export { default as mapType } from './mapType'; +export { default as generateUniformsSync } from './generateUniformsSync'; +export { default as getTestContext } from './getTestContext'; +export { default as checkMaxIfStatmentsInShader } from './checkMaxIfStatmentsInShader'; diff --git a/packages/core/src/shader/utils/mapSize.js b/packages/core/src/shader/utils/mapSize.js new file mode 100644 index 0000000..b681554 --- /dev/null +++ b/packages/core/src/shader/utils/mapSize.js @@ -0,0 +1,33 @@ +const GLSL_TO_SIZE = { + float: 1, + vec2: 2, + vec3: 3, + vec4: 4, + + int: 1, + ivec2: 2, + ivec3: 3, + ivec4: 4, + + bool: 1, + bvec2: 2, + bvec3: 3, + bvec4: 4, + + mat2: 4, + mat3: 9, + mat4: 16, + + sampler2D: 1, +}; + +/** + * @method mapSize + * @memberof PIXI.glCore.shader + * @param type {String} + * @return {Number} + */ +export default function mapSize(type) +{ + return GLSL_TO_SIZE[type]; +} diff --git a/packages/core/src/shader/utils/mapType.js b/packages/core/src/shader/utils/mapType.js new file mode 100644 index 0000000..d288b7c --- /dev/null +++ b/packages/core/src/shader/utils/mapType.js @@ -0,0 +1,45 @@ +let GL_TABLE = null; + +const GL_TO_GLSL_TYPES = { + FLOAT: 'float', + FLOAT_VEC2: 'vec2', + FLOAT_VEC3: 'vec3', + FLOAT_VEC4: 'vec4', + + INT: 'int', + INT_VEC2: 'ivec2', + INT_VEC3: 'ivec3', + INT_VEC4: 'ivec4', + + BOOL: 'bool', + BOOL_VEC2: 'bvec2', + BOOL_VEC3: 'bvec3', + BOOL_VEC4: 'bvec4', + + FLOAT_MAT2: 'mat2', + FLOAT_MAT3: 'mat3', + FLOAT_MAT4: 'mat4', + + SAMPLER_2D: 'sampler2D', + SAMPLER_CUBE: 'samplerCube', + SAMPLER_2D_ARRAY: 'sampler2DArray', +}; + +export default function mapType(gl, type) +{ + if (!GL_TABLE) + { + const typeNames = Object.keys(GL_TO_GLSL_TYPES); + + GL_TABLE = {}; + + for (let i = 0; i < typeNames.length; ++i) + { + const tn = typeNames[i]; + + GL_TABLE[gl[tn]] = GL_TO_GLSL_TYPES[tn]; + } + } + + return GL_TABLE[type]; +} diff --git a/packages/core/src/shader/utils/setPrecision.js b/packages/core/src/shader/utils/setPrecision.js new file mode 100644 index 0000000..e2c1f34 --- /dev/null +++ b/packages/core/src/shader/utils/setPrecision.js @@ -0,0 +1,18 @@ +/** + * Sets the float precision on the shader. If the precision is already present this function will do nothing + * @param {string} src the shader source + * @param {string} precision The float precision of the shader. Options are 'lowp', 'mediump' or 'highp'. + * + * @return {string} modified shader source + */ +export default function setPrecision(src, precision) +{ + src = src.trim(); + + if (src.substring(0, 9) !== 'precision' && src.substring(0, 1) !== '#') + { + return `precision ${precision} float;\n${src}`; + } + + return src; +} diff --git a/packages/core/src/state/State.js b/packages/core/src/state/State.js new file mode 100644 index 0000000..fa3f221 --- /dev/null +++ b/packages/core/src/state/State.js @@ -0,0 +1,163 @@ +/* eslint-disable max-len */ + +const BLEND = 0; +const OFFSET = 1; +const CULLING = 2; +const DEPTH_TEST = 3; +const WINDING = 4; + +/** + * This is a webGL state. It is passed The WebGL StateManager. + * Each mesh renderered may require webGL to be in a different state. + * For example you may want different blend mode or to enable polygon offsets + * + * @class + * @memberof PIXI + */ +export default class State +{ + /** + * + */ + constructor() + { + this.data = 0; + + this.blendMode = 0; + this.polygonOffset = 0; + + this.blend = true; + // this.depthTest = true; + } + + /** + * Activates blending of the computed fragment color values + * + * @member {boolean} + */ + get blend() + { + return !!(this.data & (1 << BLEND)); + } + + set blend(value) // eslint-disable-line require-jsdoc + { + if (!!(this.data & (1 << BLEND)) !== value) + { + this.data ^= (1 << BLEND); + } + } + + /** + * Activates adding an offset to depth values of polygon's fragments + * + * @member {boolean} + * @default false + */ + get offsets() + { + return !!(this.data & (1 << OFFSET)); + } + + set offsets(value) // eslint-disable-line require-jsdoc + { + if (!!(this.data & (1 << OFFSET)) !== value) + { + this.data ^= (1 << OFFSET); + } + } + + /** + * Activates culling of polygons. + * + * @member {boolean} + * @default false + */ + get culling() + { + return !!(this.data & (1 << CULLING)); + } + + set culling(value) // eslint-disable-line require-jsdoc + { + if (!!(this.data & (1 << CULLING)) !== value) + { + this.data ^= (1 << CULLING); + } + } + + /** + * Activates depth comparisons and updates to the depth buffer. + * + * @member {boolean} + * @default false + */ + get depthTest() + { + return !!(this.data & (1 << DEPTH_TEST)); + } + + set depthTest(value) // eslint-disable-line require-jsdoc + { + if (!!(this.data & (1 << DEPTH_TEST)) !== value) + { + this.data ^= (1 << DEPTH_TEST); + } + } + + /** + * Specifies whether or not front or back-facing polygons can be culled. + * @member {boolean} + * @default false + */ + get clockwiseFrontFace() + { + return !!(this.data & (1 << WINDING)); + } + + set clockwiseFrontFace(value) // eslint-disable-line require-jsdoc + { + if (!!(this.data & (1 << WINDING)) !== value) + { + this.data ^= (1 << WINDING); + } + } + + /** + * The blend mode to be applied when this state is set. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. + * Setting this mode to anything other than NO_BLEND will automatically switch blending on. + * + * @member {boolean} + * @default PIXI.BLEND_MODES.NORMAL + * @see PIXI.BLEND_MODES + */ + get blendMode() + { + return this._blendMode; + } + + set blendMode(value) // eslint-disable-line require-jsdoc + { + // 17 is NO BLEND + this.blend = (value !== 17); + this._blendMode = value; + } + + /** + * The polygon offset. Setting this property to anything other than 0 will automatically enable poygon offset fill. + * + * @member {number} + * @default 0 + */ + get polygonOffset() + { + return this._polygonOffset; + } + + set polygonOffset(value) // eslint-disable-line require-jsdoc + { + this.offsets = !!value; + this._polygonOffset = value; + } +} + diff --git a/packages/core/src/state/StateSystem.js b/packages/core/src/state/StateSystem.js new file mode 100755 index 0000000..39c35bb --- /dev/null +++ b/packages/core/src/state/StateSystem.js @@ -0,0 +1,288 @@ +import mapWebGLBlendModesToPixi from './utils/mapWebGLBlendModesToPixi'; +import System from '../System'; +import WebGLState from './State'; + +const BLEND = 0; +const OFFSET = 1; +const CULLING = 2; +const DEPTH_TEST = 3; +const WINDING = 4; + +/** + * A WebGL state machines + * + * @class + * @extends PIXI.systems.System + * @memberof PIXI.systems + */ +export default class StateSystem extends System +{ + /** + * @param {WebGLRenderingContext} gl - The current WebGL rendering context + */ + constructor(renderer) + { + super(renderer); + + this.gl = null; + + this.maxAttribs = null; + + // check we have vao.. + this.nativeVaoExtension = null; + + this.attribState = null; + + this.stateId = 0; + this.polygonOffset = 0; + this.blendMode = 17; + + this.map = []; + + // map functions for when we set state.. + this.map[BLEND] = this.setBlend; + this.map[OFFSET] = this.setOffset; + this.map[CULLING] = this.setCullFace; + this.map[DEPTH_TEST] = this.setDepthTest; + this.map[WINDING] = this.setFrontFace; + + this.checks = []; + + this.defaultState = new WebGLState(); + this.defaultState.blend = true; + this.defaultState.depth = true; + } + + contextChange(gl) + { + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); + + // check we have vao.. + this.nativeVaoExtension = ( + gl.getExtension('OES_vertex_array_object') + || gl.getExtension('MOZ_OES_vertex_array_object') + || gl.getExtension('WEBKIT_OES_vertex_array_object') + ); + + this.attribState = { + tempAttribState: new Array(this.maxAttribs), + attribState: new Array(this.maxAttribs), + }; + + this.blendModes = mapWebGLBlendModesToPixi(gl); + + this.setState(this.defaultState); + + this.reset(); + } + + /** + * Sets the current state + * + * @param {*} state - The state to set. + */ + setState(state) + { + state = state || this.defaultState; + + // TODO maybe to an object check? ( this.state === state )? + if (this.stateId !== state.data) + { + let diff = this.stateId ^ state.data; + let i = 0; + + // order from least to most common + while (diff) + { + if (diff & 1) + { + // state change! + this.map[i].call(this, !!(state.data & (1 << i))); + } + + diff = diff >> 1; + i++; + } + + this.stateId = state.data; + } + + // based on the above settings we check for specific modes.. + // for example if blend is active we check and set the blend modes + // or of polygon offset is active we check the poly depth. + for (let i = 0; i < this.checks.length; i++) + { + this.checks[i](this, state); + } + } + + /** + * Enables or disabled blending. + * + * @param {boolean} value - Turn on or off webgl blending. + */ + setBlend(value) + { + this.updateCheck(StateSystem.checkBlendMode, value); + + this.gl[value ? 'enable' : 'disable'](this.gl.BLEND); + } + + /** + * Enables or disable polygon offset fill + * + * @param {boolean} value - Turn on or off webgl polygon offset testing. + */ + setOffset(value) + { + this.gl[value ? 'enable' : 'disable'](this.gl.POLYGON_OFFSET_FILL); + } + + /** + * Sets whether to enable or disable depth test. + * + * @param {boolean} value - Turn on or off webgl depth testing. + */ + setDepthTest(value) + { + this.gl[value ? 'enable' : 'disable'](this.gl.DEPTH_TEST); + } + + /** + * Sets whether to enable or disable cull face. + * + * @param {boolean} value - Turn on or off webgl cull face. + */ + setCullFace(value) + { + this.gl[value ? 'enable' : 'disable'](this.gl.CULL_FACE); + } + + /** + * Sets the gl front face. + * + * @param {boolean} value - true is clockwise and false is counter-clockwise + */ + setFrontFace(value) + { + this.gl.frontFace(this.gl[value ? 'CW' : 'CCW']); + } + + /** + * Sets the blend mode. + * + * @param {number} value - The blend mode to set to. + */ + setBlendMode(value) + { + if (value === this.blendMode) + { + return; + } + + this.blendMode = value; + this.gl.blendFunc(this.blendModes[value][0], this.blendModes[value][1]); + } + + /** + * Sets the polygon offset. + * + * @param {number} value - the polygon offset + * @param {number} scale - the polygon offset scale + */ + setPolygonOffset(value, scale) + { + this.gl.polygonOffset(value, scale); + } + + /** + * Disables all the vaos in use + * + */ + resetAttributes() + { + for (let i = 0; i < this.attribState.tempAttribState.length; i++) + { + this.attribState.tempAttribState[i] = 0; + } + + for (let i = 0; i < this.attribState.attribState.length; i++) + { + this.attribState.attribState[i] = 0; + } + + // im going to assume one is always active for performance reasons. + for (let i = 1; i < this.maxAttribs; i++) + { + this.gl.disableVertexAttribArray(i); + } + } + + // used + /** + * Resets all the logic and disables the vaos + */ + reset() + { + // unbind any VAO if they exist.. + if (this.nativeVaoExtension) + { + this.nativeVaoExtension.bindVertexArrayOES(null); + } + + // reset all attributes.. + this.resetAttributes(); + + this.gl.pixelStorei(this.gl.UNPACK_FLIP_Y_WEBGL, false); + + this.setBlendMode(0); + + // TODO? + // this.setState(this.defaultState); + } + + /** + * checks to see which updates should be checked based on which settings have been activated + * for example if blend is enabled then we shold check the blend modes each time the state is changed + * or if poygon fill is activated then we need to check if the polygone offset changes. + * The idea is that we only check what we have too. + * + * @param {Function} func the checking function to add or remove + * @param {boolean} value should the check function be added or removed. + */ + updateCheck(func, value) + { + const index = this.checks.indexOf(func); + + if (value && index === -1) + { + this.checks.push(func); + } + else if (!value && index !== -1) + { + this.checks.splice(index, 1); + } + } + + /** + * A private little wrapper function that we call to check the blend mode. + * + * @static + * @private + * @param {PIXI.StateSystem} System the System to perform the state check on + * @param {PIXI.State} state the state that the blendMode will pulled from + */ + static checkBlendMode(system, state) + { + system.setBlendMode(state.blendMode); + } + + // TODO - add polygon offset? +} diff --git a/packages/core/src/state/utils/mapWebGLBlendModesToPixi.js b/packages/core/src/state/utils/mapWebGLBlendModesToPixi.js new file mode 100644 index 0000000..10ffe4d --- /dev/null +++ b/packages/core/src/state/utils/mapWebGLBlendModesToPixi.js @@ -0,0 +1,47 @@ +import { BLEND_MODES } from '@pixi/constants'; + +/** + * Maps gl blend combinations to WebGL. + * + * @memberof PIXI + * @function mapWebGLBlendModesToPixi + * @private + * @param {WebGLRenderingContext} gl - The rendering context. + * @param {string[]} [array=[]] - The array to output into. + * @return {string[]} Mapped modes. + */ +export default function mapWebGLBlendModesToPixi(gl, array = []) +{ + // TODO - premultiply alpha would be different. + // add a boolean for that! + array[BLEND_MODES.NORMAL] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; + array[BLEND_MODES.ADD] = [gl.ONE, gl.DST_ALPHA]; + array[BLEND_MODES.MULTIPLY] = [gl.DST_COLOR, gl.ONE_MINUS_SRC_ALPHA]; + array[BLEND_MODES.SCREEN] = [gl.ONE, gl.ONE_MINUS_SRC_COLOR]; + array[BLEND_MODES.OVERLAY] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; + array[BLEND_MODES.DARKEN] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; + array[BLEND_MODES.LIGHTEN] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; + array[BLEND_MODES.COLOR_DODGE] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; + array[BLEND_MODES.COLOR_BURN] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; + array[BLEND_MODES.HARD_LIGHT] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; + array[BLEND_MODES.SOFT_LIGHT] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; + array[BLEND_MODES.DIFFERENCE] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; + array[BLEND_MODES.EXCLUSION] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; + array[BLEND_MODES.HUE] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; + array[BLEND_MODES.SATURATION] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; + array[BLEND_MODES.COLOR] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; + array[BLEND_MODES.LUMINOSITY] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; + array[BLEND_MODES.NONE] = [0, 0]; + + // not-premultiplied blend modes + array[BLEND_MODES.NORMAL_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; + array[BLEND_MODES.ADD_NPM] = [gl.SRC_ALPHA, gl.DST_ALPHA, gl.ONE, gl.DST_ALPHA]; + array[BLEND_MODES.SCREEN_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_COLOR, gl.ONE, gl.ONE_MINUS_SRC_COLOR]; + + // not-premultiplied blend modes + array[BLEND_MODES.NORMAL_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; + array[BLEND_MODES.ADD_NPM] = [gl.SRC_ALPHA, gl.DST_ALPHA, gl.ONE, gl.DST_ALPHA]; + array[BLEND_MODES.SCREEN_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_COLOR, gl.ONE, gl.ONE_MINUS_SRC_COLOR]; + + return array; +} diff --git a/packages/core/src/systems.js b/packages/core/src/systems.js new file mode 100644 index 0000000..8edd9d5 --- /dev/null +++ b/packages/core/src/systems.js @@ -0,0 +1,17 @@ +/** + * Systems are individual components to the Renderer pipeline. + * @namespace PIXI.systems + */ +export { default as FilterSystem } from './filters/FilterSystem'; +export { default as BatchSystem } from './batch/BatchSystem'; +export { default as ContextSystem } from './context/ContextSystem'; +export { default as FramebufferSystem } from './framebuffer/FramebufferSystem'; +export { default as GeometrySystem } from './geometry/GeometrySystem'; +export { default as MaskSystem } from './mask/MaskSystem'; +export { default as StencilSystem } from './mask/StencilSystem'; +export { default as ProjectionSystem } from './projection/ProjectionSystem'; +export { default as RenderTextureSystem } from './renderTexture/RenderTextureSystem'; +export { default as ShaderSystem } from './shader/ShaderSystem'; +export { default as StateSystem } from './state/StateSystem'; +export { default as TextureGCSystem } from './textures/TextureGCSystem'; +export { default as TextureSystem } from './textures/TextureSystem'; diff --git a/packages/core/src/textures/BaseRenderTexture.js b/packages/core/src/textures/BaseRenderTexture.js index 0d4611e..77c44f5 100644 --- a/packages/core/src/textures/BaseRenderTexture.js +++ b/packages/core/src/textures/BaseRenderTexture.js @@ -1,5 +1,5 @@ import BaseTexture from './BaseTexture'; -import FrameBuffer from './FrameBuffer'; +import FrameBuffer from '../framebuffer/FrameBuffer'; /** * A BaseRenderTexture is a special texture that allows any PixiJS display object to be rendered to it. diff --git a/packages/core/src/textures/FrameBuffer.js b/packages/core/src/textures/FrameBuffer.js deleted file mode 100644 index 58c8f39..0000000 --- a/packages/core/src/textures/FrameBuffer.js +++ /dev/null @@ -1,106 +0,0 @@ -import Texture from './BaseTexture'; -import { FORMATS, TYPES } from '@pixi/constants'; - -/** - * Frame buffer - * @class - * @memberof PIXI - */ -export default class FrameBuffer -{ - constructor(width, height) - { - this.width = width || 100; - this.height = height || 100; - - this.stencil = false; - this.depth = false; - - this.dirtyId = 0; - this.dirtyFormat = 0; - this.dirtySize = 0; - - this.depthTexture = null; - this.colorTextures = []; - - this.glFrameBuffers = {}; - } - - get colorTexture() - { - return this.colorTextures[0]; - } - - addColorTexture(index, texture) - { - // TODO add some validation to the texture - same width / height etc? - this.colorTextures[index || 0] = texture || new Texture(null, { scaleMode: 0, - resolution: 1, - mipmap: false, - width: this.width, - height: this.height });// || new Texture(); - - this.dirtyId++; - this.dirtyFormat++; - - return this; - } - - addDepthTexture(texture) - { - /* eslint-disable max-len */ - this.depthTexture = texture || new Texture(null, { scaleMode: 0, - resolution: 1, - width: this.width, - height: this.height, - mipmap: false, - format: FORMATS.DEPTH_COMPONENT, - type: TYPES.UNSIGNED_SHORT });// UNSIGNED_SHORT; - /* eslint-disable max-len */ - this.dirtyId++; - this.dirtyFormat++; - - return this; - } - - enableDepth() - { - this.depth = true; - - this.dirtyId++; - this.dirtyFormat++; - - return this; - } - - enableStencil() - { - this.stencil = true; - - this.dirtyId++; - this.dirtyFormat++; - - return this; - } - - resize(width, height) - { - if (width === this.width && height === this.height) return; - - this.width = width; - this.height = height; - - this.dirtyId++; - this.dirtySize++; - - for (let i = 0; i < this.colorTextures.length; i++) - { - this.colorTextures[i].setSize(width, height); - } - - if (this.depthTexture) - { - this.depthTexture.setSize(width, height); - } - } -} diff --git a/packages/core/src/textures/GLTexture.js b/packages/core/src/textures/GLTexture.js new file mode 100644 index 0000000..c3fb8dc --- /dev/null +++ b/packages/core/src/textures/GLTexture.js @@ -0,0 +1,33 @@ +export default class GLTexture +{ + constructor(texture) + { + /** + * The WebGL texture + * + * @member {WebGLTexture} + */ + this.texture = texture; + + this.width = -1; + this.height = -1; + + /** + * Texture contents dirty flag + * @member {number} + */ + this.dirtyId = -1; + + /** + * Texture style dirty flag + * @type {number} + */ + this.dirtyStyleId = -1; + + /** + * Whether mip levels has to be generated + * @type {boolean} + */ + this.mipmap = false; + } +} diff --git a/packages/core/src/textures/TextureGCSystem.js b/packages/core/src/textures/TextureGCSystem.js new file mode 100644 index 0000000..6d07606 --- /dev/null +++ b/packages/core/src/textures/TextureGCSystem.js @@ -0,0 +1,111 @@ +import System from '../System'; +import { GC_MODES } from '@pixi/constants'; +import { settings } from '@pixi/settings'; + +/** + * TextureGarbageCollector. This class manages the GPU and ensures that it does not get clogged + * up with textures that are no longer being used. + * + * @class + * @memberof PIXI.systems + * @extends PIXI.System + */ +export default class TextureGCSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.count = 0; + this.checkCount = 0; + this.maxIdle = settings.GC_MAX_IDLE; + this.checkCountMax = settings.GC_MAX_CHECK_COUNT; + this.mode = settings.GC_MODE; + } + + /** + * Checks to see when the last time a texture was used + * if the texture has not been used for a specified amount of time it will be removed from the GPU + */ + postrender() + { + this.count++; + + if (this.mode === GC_MODES.MANUAL) + { + return; + } + + this.checkCount++; + + if (this.checkCount > this.checkCountMax) + { + this.checkCount = 0; + + this.run(); + } + } + + /** + * Checks to see when the last time a texture was used + * if the texture has not been used for a specified amount of time it will be removed from the GPU + */ + run() + { + const tm = this.renderer.texture; + const managedTextures = tm.managedTextures; + let wasRemoved = false; + + for (let i = 0; i < managedTextures.length; i++) + { + const texture = managedTextures[i]; + + // only supports non generated textures at the moment! + if (!texture.frameBuffer && this.count - texture.touched > this.maxIdle) + { + tm.destroyTexture(texture, true); + managedTextures[i] = null; + wasRemoved = true; + } + } + + if (wasRemoved) + { + let j = 0; + + for (let i = 0; i < managedTextures.length; i++) + { + if (managedTextures[i] !== null) + { + managedTextures[j++] = managedTextures[i]; + } + } + + managedTextures.length = j; + } + } + + /** + * Removes all the textures within the specified displayObject and its children from the GPU + * + * @param {PIXI.DisplayObject} displayObject - the displayObject to remove the textures from. + */ + unload(displayObject) + { + const tm = this.renderer.textureSystem; + + // only destroy non generated textures + if (displayObject._texture && displayObject._texture._glRenderTargets) + { + tm.destroyTexture(displayObject._texture); + } + + for (let i = displayObject.children.length - 1; i >= 0; i--) + { + this.unload(displayObject.children[i]); + } + } +} diff --git a/packages/core/src/textures/TextureSystem.js b/packages/core/src/textures/TextureSystem.js new file mode 100644 index 0000000..603f192 --- /dev/null +++ b/packages/core/src/textures/TextureSystem.js @@ -0,0 +1,281 @@ +import System from '../System'; +import GLTexture from './GLTexture'; +import { removeItems } from '@pixi/utils'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class TextureSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + // TODO set to max textures... + this.boundTextures = [ + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + ]; + + this.currentLocation = -1; + + this.managedTextures = []; + } + + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + contextChange() + { + const gl = this.gl = this.renderer.gl; + + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + + // TODO move this.. to a nice make empty textures class.. + this.emptyTextures = {}; + + const emptyTexture2D = new GLTexture(gl.createTexture()); + + gl.bindTexture(gl.TEXTURE_2D, emptyTexture2D.texture); + gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array(4)); + + this.emptyTextures[gl.TEXTURE_2D] = emptyTexture2D; + this.emptyTextures[gl.TEXTURE_CUBE_MAP] = new GLTexture(gl.createTexture()); + + gl.bindTexture(gl.TEXTURE_CUBE_MAP, this.emptyTextures[gl.TEXTURE_CUBE_MAP].texture); + + let i; + + for (i = 0; i < 6; i++) + { + gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, null); + } + + gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_MAG_FILTER, gl.LINEAR); + gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_MIN_FILTER, gl.LINEAR); + + for (i = 0; i < this.boundTextures.length; i++) + { + this.bind(null, i); + } + } + + bind(texture, location) + { + const gl = this.gl; + + location = location || 0; + + if (this.currentLocation !== location) + { + this.currentLocation = location; + gl.activeTexture(gl.TEXTURE0 + location); + } + + if (texture) + { + texture = texture.baseTexture || texture; + + if (texture.valid) + { + texture.touched = this.renderer.textureGC.count; + + const glTexture = texture._glTextures[this.CONTEXT_UID] || this.initTexture(texture); + + gl.bindTexture(texture.target, glTexture.texture); + + if (glTexture.dirtyId !== texture.dirtyId) + { + this.updateTexture(texture); + } + + this.boundTextures[location] = texture; + } + } + else + { + gl.bindTexture(gl.TEXTURE_2D, this.emptyTextures[gl.TEXTURE_2D].texture); + this.boundTextures[location] = null; + } + } + + unbind(texture) + { + const gl = this.gl; + + for (let i = 0; i < this.boundTextures.length; i++) + { + if (this.boundTextures[i] === texture) + { + if (this.currentLocation !== i) + { + gl.activeTexture(gl.TEXTURE0 + i); + this.currentLocation = i; + } + + gl.bindTexture(gl.TEXTURE_2D, this.emptyTextures[texture.target].texture); + this.boundTextures[i] = null; + } + } + } + + initTexture(texture) + { + const glTexture = new GLTexture(this.gl.createTexture()); + + // guarentee an update.. + glTexture.dirtyId = -1; + + texture._glTextures[this.CONTEXT_UID] = glTexture; + + this.managedTextures.push(texture); + texture.on('dispose', this.destroyTexture, this); + + return glTexture; + } + + updateTexture(texture) + { + const glTexture = texture._glTextures[this.CONTEXT_UID]; + const renderer = this.renderer; + + if (texture.resource && texture.resource.upload(renderer, texture, glTexture)) + { + // texture is uploaded, dont do anything! + } + else + { + // default, renderTexture-like logic + const width = texture.realWidth; + const height = texture.realHeight; + const gl = renderer.gl; + + if (glTexture.width !== width + || glTexture.height !== height + || glTexture.dirtyId < 0) + { + glTexture.width = width; + glTexture.height = height; + + gl.texImage2D(texture.target, 0, + texture.format, + width, + height, + 0, + texture.format, + texture.type, + null); + } + } + + // lets only update what changes.. + if (texture.dirtyStyleId !== glTexture.dirtyStyleId) + { + this.updateTextureStyle(texture); + } + glTexture.dirtyId = texture.dirtyId; + } + + /** + * Deletes the texture from WebGL + * + * @param {PIXI.BaseTexture|PIXI.Texture} texture - the texture to destroy + * @param {boolean} [skipRemove=false] - Whether to skip removing the texture from the TextureManager. + */ + destroyTexture(texture, skipRemove) + { + const gl = this.gl; + + texture = texture.baseTexture || texture; + + if (texture._glTextures[this.renderer.CONTEXT_UID]) + { + this.unbind(texture); + + gl.deleteTexture(texture._glTextures[this.renderer.CONTEXT_UID].texture); + texture.off('dispose', this.destroyTexture, this); + + delete texture._glTextures[this.renderer.CONTEXT_UID]; + + if (!skipRemove) + { + const i = this.managedTextures.indexOf(texture); + + if (i !== -1) + { + removeItems(this.managedTextures, i, 1); + } + } + } + } + + updateTextureStyle(texture) + { + const glTexture = texture._glTextures[this.CONTEXT_UID]; + + glTexture.mipmap = texture.mipmap && texture.isPowerOfTwo; + if (!glTexture) + { + return; + } + + if (texture.resource && texture.resource.style(this.renderer, texture, glTexture)) + { + // style is set, dont do anything! + } + else + { + this.setStyle(texture, glTexture); + } + + glTexture.dirtyStyleId = texture.dirtyStyleId; + } + + setStyle(texture, glTexture) + { + const gl = this.gl; + + if (glTexture.mipmap) + { + gl.generateMipmap(texture.target); + } + + gl.texParameteri(texture.target, gl.TEXTURE_WRAP_S, texture.wrapMode); + gl.texParameteri(texture.target, gl.TEXTURE_WRAP_T, texture.wrapMode); + + if (glTexture.mipmap) + { + /* eslint-disable max-len */ + gl.texParameteri(texture.target, gl.TEXTURE_MIN_FILTER, texture.scaleMode ? gl.LINEAR_MIPMAP_LINEAR : gl.NEAREST_MIPMAP_NEAREST); + /* eslint-disable max-len */ + } + else + { + gl.texParameteri(texture.target, gl.TEXTURE_MIN_FILTER, texture.scaleMode ? gl.LINEAR : gl.NEAREST); + } + + gl.texParameteri(texture.target, gl.TEXTURE_MAG_FILTER, texture.scaleMode ? gl.LINEAR : gl.NEAREST); + } +} diff --git a/packages/core/src/utils/Quad.js b/packages/core/src/utils/Quad.js new file mode 100644 index 0000000..c861a1a --- /dev/null +++ b/packages/core/src/utils/Quad.js @@ -0,0 +1,26 @@ +import Geometry from '../geometry/Geometry'; + +/** + * Helper class to create a quad + * + * @class + * @memberof PIXI + */ +export default class Quad extends Geometry +{ + /** + * just a quad + */ + constructor() + { + super(); + + this.addAttribute('aVertexPosition', [ + 0, 0, + 1, 0, + 1, 1, + 0, 1, + ]) + .addIndex([0, 1, 3, 2]); + } +} diff --git a/packages/core/src/AbstractRenderer.js b/packages/core/src/AbstractRenderer.js new file mode 100644 index 0000000..64b2eb1 --- /dev/null +++ b/packages/core/src/AbstractRenderer.js @@ -0,0 +1,353 @@ +import { hex2string, hex2rgb } from '@pixi/utils'; +import { Matrix, Rectangle } from '@pixi/math'; +import { RENDERER_TYPE } from '@pixi/constants'; +import { settings } from '@pixi/settings'; +import { Container } from '@pixi/display'; +import RenderTexture from './renderTexture/RenderTexture'; +import EventEmitter from 'eventemitter3'; + +const tempMatrix = new Matrix(); + +/** + * 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 EventEmitter + * @memberof PIXI + */ +export default class AbstractRenderer extends EventEmitter +{ + // eslint-disable-next-line valid-jsdoc + /** + * @param {string} system - The name of the system this renderer is for. + * @param {object} [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.transparent=false] - If the render view is transparent, default false + * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false + * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. The + * resolution of the renderer retina would be 2. + * @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 {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when rendering, + * stopping pixel interpolation. + */ + constructor(system, options, arg2, arg3) + { + super(); + + // Support for constructor(system, screenWidth, screenHeight, options) + if (typeof options === 'number') + { + options = Object.assign({ + width: options, + height: arg2 || settings.RENDER_OPTIONS.height, + }, arg3); + } + + // 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 = RENDERER_TYPE.UNKNOWN; + + /** + * Measurements of the screen. (0, 0, screenWidth, screenHeight) + * + * Its safe to use as filterArea or hitArea for 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 1 + */ + this.resolution = options.resolution || settings.RESOLUTION; + + /** + * Whether the render view is transparent + * + * @member {boolean} + */ + this.transparent = options.transparent; + + /** + * Whether css dimensions of canvas view should be resized to screen dimensions automatically + * + * @member {boolean} + */ + this.autoResize = options.autoResize || false; + + /** + * Tracks the blend modes useful for this renderer. + * + * @member {object} + */ + this.blendModes = null; + + /** + * 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; + + /** + * If true PixiJS will Math.floor() x/y values when rendering, stopping pixel interpolation. + * Handy for crisp pixel art and speed on legacy devices. + * + * @member {boolean} + */ + this.roundPixels = options.roundPixels; + + /** + * The background color as a number. + * + * @member {number} + * @private + */ + this._backgroundColor = 0x000000; + + /** + * The background color as an [R, G, B] array. + * + * @member {number[]} + * @private + */ + this._backgroundColorRgba = [0, 0, 0, 0]; + + /** + * The background color as a string. + * + * @member {string} + * @private + */ + this._backgroundColorString = '#000000'; + + this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter + + /** + * This temporary display object used as the parent of the currently being rendered item + * + * @member {PIXI.DisplayObject} + * @private + */ + this._tempDisplayObjectParent = new Container(); + + /** + * The last root object that the renderer tried to render. + * + * @member {PIXI.DisplayObject} + * @private + */ + this._lastObjectRendered = this._tempDisplayObjectParent; + + /** + * Collection of plugins. + * @readonly + * @member {object} + */ + this.plugins = {}; + } + + /** + * Initialize the plugins. + * + * @protected + * @param {object} staticMap - The dictionary of staticly saved plugins. + */ + initPlugins(staticMap) + { + 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() + { + return this.view.width; + } + + /** + * Same as view.height, actual number of pixels in the canvas by vertical + * + * @member {number} + * @readonly + * @default 600 + */ + get height() + { + return this.view.height; + } + + /** + * Resizes the screen and canvas to the specified width and height + * Canvas dimensions are multiplied by resolution + * + * @param {number} screenWidth - the new width of the screen + * @param {number} screenHeight - the new height of the screen + */ + resize(screenWidth, screenHeight) + { + this.screen.width = screenWidth; + this.screen.height = screenHeight; + + this.view.width = screenWidth * this.resolution; + this.view.height = screenHeight * this.resolution; + + if (this.autoResize) + { + this.view.style.width = `${screenWidth}px`; + this.view.style.height = `${screenHeight}px`; + } + } + + /** + * 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 {PIXI.DisplayObject} displayObject - The displayObject the object will be generated from + * @param {number} scaleMode - Should be one of the scaleMode consts + * @param {number} resolution - The resolution / device pixel ratio of the texture being generated + * @param {PIXI.Rectangle} [region] - The region of the displayObject, that shall be rendered, + * if no region is specified, defaults to the local bounds of the displayObject. + * @return {PIXI.Texture} a texture of the graphics object + */ + generateTexture(displayObject, scaleMode, resolution, region) + { + region = region || displayObject.getLocalBounds(); + + // 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(region.width | 0, region.height | 0, scaleMode, resolution); + + tempMatrix.tx = -region.x; + tempMatrix.ty = -region.y; + + this.render(displayObject, renderTexture, false, tempMatrix, true); + + return renderTexture; + } + + /** + * Removes everything from the renderer and optionally removes the Canvas DOM element. + * + * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. + */ + destroy(removeView) + { + 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); + } + + this.plugins = null; + + this.type = RENDERER_TYPE.UNKNOWN; + + this.view = null; + + this.screen = null; + + this.resolution = 0; + + this.transparent = false; + + this.autoResize = false; + + this.blendModes = null; + + this.options = null; + + this.preserveDrawingBuffer = false; + this.clearBeforeRender = false; + + this.roundPixels = false; + + this._backgroundColor = 0; + this._backgroundColorRgba = null; + this._backgroundColorString = null; + + this._tempDisplayObjectParent = null; + this._lastObjectRendered = null; + } + + /** + * The background color to fill if not transparent + * + * @member {number} + */ + get backgroundColor() + { + return this._backgroundColor; + } + + set backgroundColor(value) // eslint-disable-line require-jsdoc + { + this._backgroundColor = value; + this._backgroundColorString = hex2string(value); + hex2rgb(value, this._backgroundColorRgba); + } +} diff --git a/packages/core/src/Renderer.js b/packages/core/src/Renderer.js new file mode 100644 index 0000000..2227e78 --- /dev/null +++ b/packages/core/src/Renderer.js @@ -0,0 +1,338 @@ +import AbstractRenderer from './AbstractRenderer'; +import { sayHello } from '@pixi/utils'; +import MaskSystem from './mask/MaskSystem'; +import StencilSystem from './mask/StencilSystem'; +import FilterSystem from './filters/FilterSystem'; +import FramebufferSystem from './framebuffer/FramebufferSystem'; +import RenderTextureSystem from './renderTexture/RenderTextureSystem'; +import TextureSystem from './textures/TextureSystem'; +import ProjectionSystem from './projection/ProjectionSystem'; +import StateSystem from './state/StateSystem'; +import GeometrySystem from './geometry/GeometrySystem'; +import ShaderSystem from './shader/ShaderSystem'; +import ContextSystem from './context/ContextSystem'; +import BatchSystem from './batch/BatchSystem'; +import TextureGCSystem from './textures/TextureGCSystem'; +import { RENDERER_TYPE } from '@pixi/constants'; +import UniformGroup from './shader/UniformGroup'; +import { Matrix } from '@pixi/math'; +import Runner from 'mini-runner'; + +/** + * 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 Render works by automatically managing webGLBatchs. + * 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 :) + * + * @class + * @memberof PIXI + * @extends PIXI.AbstractRenderer + */ +export default class Renderer extends AbstractRenderer +{ + // eslint-disable-next-line valid-jsdoc + /** + * + * @param {object} [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.transparent=false] - If the render view is transparent, default false + * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false + * @param {boolean} [options.antialias=false] - sets antialias. If not available natively then FXAA + * antialiasing is used + * @param {boolean} [options.forceFXAA=false] - forces FXAA antialiasing to be used over native. + * FXAA is faster, but may not always look as great + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. + * The resolution of the renderer retina would be 2. + * @param {boolean} [options.clearBeforeRender=true] - This sets if the renderer will clear + * the canvas or not before the new render pass. If you wish to set this to false, you *must* set + * preserveDrawingBuffer to `true`. + * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, + * enable this if you need to call toDataUrl on the webgl context. + * @param {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when + * rendering, stopping pixel interpolation. + * @param {number} [options.backgroundColor=0x000000] - The background color of the rendered area + * (shown if not transparent). + * @param {boolean} [options.legacy=false] - If true PixiJS will aim to ensure compatibility + * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. + * @param {string} [options.powerPreference] - Parameter passed to webgl context, set to "high-performance" + * for devices with dual graphics card + */ + constructor(options, arg2, arg3) + { + super('WebGL', options, arg2, arg3); + + /** + * The type of this renderer as a standardised const + * + * @member {number} + * @see PIXI.RENDERER_TYPE + */ + this.type = RENDERER_TYPE.WEBGL; + + // this will be set by the contextSystem (this.context) + this.gl = null; + this.CONTEXT_UID = 0; + this.legacy = !!options.legacy; + + // TODO legacy! + + // runners! + this.runners = { + destroy: new Runner('destroy'), + contextChange: new Runner('contextChange', 1), + reset: new Runner('reset'), + update: new Runner('update'), + postrender: new Runner('postrender'), + prerender: new Runner('prerender'), + resize: new Runner('resize', 2), + }; + + 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(GeometrySystem, 'geometry') + .addSystem(FramebufferSystem, 'framebuffer') + .addSystem(StencilSystem, 'stencil') + .addSystem(ProjectionSystem, 'projection') + .addSystem(TextureGCSystem, 'textureGC') + .addSystem(FilterSystem, 'filter') + .addSystem(RenderTextureSystem, 'renderTexture') + .addSystem(BatchSystem, 'batch'); + + this.initPlugins(Renderer.__plugins); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ + if (options.context) + { + this.context.initFromContext(options.context); + } + else + { + this.context.initFromOptions({ + alpha: this.transparent, + antialias: options.antialias, + premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', + stencil: true, + 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); + } + + /** + * Add a new system to the renderer. + * @param {class} ClassRef - Class reference + * @param {string} [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. + * @return {PIXI.Renderer} Return instance of renderer + */ + addSystem(ClassRef, name) + { + if (!name) + { + name = ClassRef.name; + } + + // TODO - read name from class.name.. + + /* + if(name.includes('System')) + { + name = name.replace('System', ''); + name = name.charAt(0).toLowerCase() + name.slice(1); + } + */ + + const system = new ClassRef(this); + + if (this[name]) + { + throw new Error(`Whoops! ${name} is already a manger`); + } + + this[name] = system; + + for (const i in this.runners) + { + this.runners[i].add(system); + } + + return this; + + /** + * 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. + */ + } + + /** + * Renders the object to its webGL view + * + * @param {PIXI.DisplayObject} displayObject - the object to be rendered + * @param {PIXI.RenderTexture} renderTexture - The render texture to render to. + * @param {boolean} [clear] - Should the canvas be cleared before the new render + * @param {PIXI.Transform} [transform] - A transform to apply to the render texture before rendering. + * @param {boolean} [skipUpdateTransform] - Should we skip the update transform pass? + */ + render(displayObject, renderTexture, clear, transform, skipUpdateTransform) + { + // can be handy to know! + this.renderingToScreen = !renderTexture; + + this.runners.prerender.run(); + this.emit('prerender'); + + // 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.parent; + + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = 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.run(); + + this.emit('postrender'); + } + + /** + * Resizes the webGL view to the specified width and height. + * + * @param {number} screenWidth - the new width of the screen + * @param {number} screenHeight - the new height of the screen + */ + resize(screenWidth, screenHeight) + { + AbstractRenderer.prototype.resize.call(this, screenWidth, screenHeight); + + this.runners.resize.run(screenWidth, screenHeight); + } + + /** + * Resets the WebGL state so you can render things however you fancy! + * + * @return {PIXI.Renderer} Returns itself. + */ + reset() + { + this.runners.reset.run(); + + return this; + } + + clear() + { + this.framebuffer.bind(); + this.framebuffer.clear(); + } + + /** + * Removes everything from the renderer (event listeners, spritebatch, etc...) + * + * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. + * See: https://github.com/pixijs/pixi.js/issues/2233 + */ + destroy(removeView) + { + this.runners.destroy.run(); + + // call base destroy + super.destroy(removeView); + + // TODO nullify all the managers.. + this.gl = null; + } + + /** + * 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 + * builds and excluding plugins. + * @name PIXI.Renderer#plugins + * @type {object} + * @readonly + * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. + * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. + * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. + * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. + */ + + /** + * Adds a plugin to the renderer. + * + * @method + * @param {string} pluginName - The name of the plugin. + * @param {Function} ctor - The constructor function or class for the plugin. + */ + static registerPlugin(pluginName, ctor) + { + Renderer.__plugins = Renderer.__plugins || {}; + Renderer.__plugins[pluginName] = ctor; + } +} diff --git a/packages/core/src/System.js b/packages/core/src/System.js new file mode 100644 index 0000000..05a10c8 --- /dev/null +++ b/packages/core/src/System.js @@ -0,0 +1,42 @@ +/** + * System is a base class used for extending systems used by the {@link PIXI.Renderer} + * @see PIXI.Renderer#addSystem + * @class + * @memberof PIXI + */ +export default class System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + /** + * The renderer this manager works for. + * + * @member {PIXI.Renderer} + */ + this.renderer = renderer; + + this.renderer.runners.contextChange.add(this); + } + + /** + * Generic method called when there is a WebGL context change. + * + */ + contextChange() + { + // do some codes init! + } + + /** + * Generic destroy methods to be overridden by the subclass + * + */ + destroy() + { + this.renderer.runners.contextChange.remove(this); + this.renderer = null; + } +} diff --git a/packages/core/src/batch/BatchSystem.js b/packages/core/src/batch/BatchSystem.js new file mode 100644 index 0000000..b04c249 --- /dev/null +++ b/packages/core/src/batch/BatchSystem.js @@ -0,0 +1,68 @@ +import System from '../System'; +import ObjectRenderer from './ObjectRenderer'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class BatchSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + super(renderer); + + /** + * An empty renderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.emptyRenderer = new ObjectRenderer(renderer); + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.currentRenderer = this.emptyRenderer; + } + + /** + * Changes the current renderer to the one given in parameter + * + * @param {PIXI.ObjectRenderer} objectRenderer - The object renderer to use. + */ + setObjectRenderer(objectRenderer) + { + if (this.currentRenderer === objectRenderer) + { + return; + } + + this.currentRenderer.stop(); + this.currentRenderer = objectRenderer; + + this.renderer.state.setState(objectRenderer.state); + + this.currentRenderer.start(); + } + + /** + * This should be called if you wish to do some custom rendering + * It will basically render anything that may be batched up such as sprites + * + */ + flush() + { + this.setObjectRenderer(this.emptyRenderer); + } + + reset() + { + this.setObjectRenderer(this.emptyRenderer); + } +} diff --git a/packages/core/src/batch/ObjectRenderer.js b/packages/core/src/batch/ObjectRenderer.js new file mode 100644 index 0000000..0c851a9 --- /dev/null +++ b/packages/core/src/batch/ObjectRenderer.js @@ -0,0 +1,48 @@ +import System from '../System'; + +/** + * Base for a common object renderer that can be used as a system renderer plugin. + * + * @class + * @extends PIXI.System + * @memberof PIXI + */ +export default class ObjectRenderer extends System +{ + /** + * Starts the renderer and sets the shader + * + */ + start() + { + // set the shader.. + } + + /** + * Stops the renderer + * + */ + stop() + { + this.flush(); + } + + /** + * Stub method for rendering content and emptying the current batch. + * + */ + flush() + { + // flush! + } + + /** + * Renders an object + * + * @param {PIXI.DisplayObject} object - The object to render. + */ + render(object) // eslint-disable-line no-unused-vars + { + // render the object + } +} diff --git a/packages/core/src/context/ContextSystem.js b/packages/core/src/context/ContextSystem.js new file mode 100644 index 0000000..301aab4 --- /dev/null +++ b/packages/core/src/context/ContextSystem.js @@ -0,0 +1,186 @@ +import System from '../System'; +import { settings } from '@pixi/settings'; + +let CONTEXT_UID = 0; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class ContextSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.webGLVersion = 1; + + this.handleContextLost = this.handleContextLost.bind(this); + this.handleContextRestored = this.handleContextRestored.bind(this); + + this.extensions = {}; + + renderer.view.addEventListener('webglcontextlost', this.handleContextLost, false); + renderer.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + } + + get isLost() + { + return (!this.gl || this.gl.isContextLost()); + } + + contextChange(gl) + { + this.gl = gl; + + // restore a context if it was previously lost + if (gl.isContextLost() && gl.getExtension('WEBGL_lose_context')) + { + gl.getExtension('WEBGL_lose_context').restoreContext(); + } + } + + initFromContext(gl) + { + this.gl = gl; + this.validateContext(gl); + this.renderer.gl = gl; + this.renderer.CONTEXT_UID = CONTEXT_UID++; + this.renderer.runners.contextChange.run(gl); + } + + initFromOptions(options) + { + const gl = this.createContext(this.renderer.view, options); + + this.initFromContext(gl); + } + + /** + * Helper class to create a webGL Context + * + * @class + * @memberof PIXI.glCore + * @param canvas {HTMLCanvasElement} the canvas element that we will get the context from + * @param options {Object} An options object that gets passed in to the canvas element containing the context attributes, + * see https://developer.mozilla.org/en/docs/Web/API/HTMLCanvasElement/getContext for the options available + * @return {WebGLRenderingContext} the WebGL context + */ + createContext(canvas, options) + { + let gl; + + if (settings.PREFER_WEBGL_2) + { + gl = canvas.getContext('webgl2', options); + } + + if (gl) + { + this.webGLVersion = 2; + } + else + { + this.webGLVersion = 1; + + gl = canvas.getContext('webgl', options) + || canvas.getContext('experimental-webgl', options); + + if (!gl) + { + // fail, not able to get a context + throw new Error('This browser does not support webGL. Try using the canvas renderer'); + } + } + + this.gl = gl; + + this.getExtensions(); + + return gl; + } + + getExtensions() + { + // time to set up default etensions that pixi uses.. + const gl = this.gl; + const extensions = this.extensions; + + if (this.webGLVersion === 1) + { + extensions.drawBuffers = gl.getExtension('WEBGL_draw_buffers'); + extensions.depthTexture = gl.getExtension('WEBKIT_WEBGL_depth_texture'); + extensions.floatTexture = gl.getExtension('OES_texture_float'); + extensions.loseContext = gl.getExtension('WEBGL_lose_context'); + + extensions.vertexArrayObject = gl.getExtension('OES_vertex_array_object') + || gl.getExtension('MOZ_OES_vertex_array_object') + || gl.getExtension('WEBKIT_OES_vertex_array_object'); + } + + // we don't use any specific WebGL 2 ones yet! + } + + /** + * Handles a lost webgl context + * + * @private + * @param {WebGLContextEvent} event - The context lost event. + */ + handleContextLost(event) + { + event.preventDefault(); + } + + /** + * Handles a restored webgl context + * + * @private + */ + handleContextRestored() + { + this.renderer.runners.contextChange.run(this.gl); + } + + destroy() + { + const view = this.renderer.view; + + // remove listeners + view.removeEventListener('webglcontextlost', this.handleContextLost); + view.removeEventListener('webglcontextrestored', this.handleContextRestored); + + this.gl.useProgram(null); + + if (this.extensions.loseContext) + { + this.extensions.loseContext.loseContext(); + } + } + + postrender() + { + this.gl.flush(); + } + + validateContext(gl) + { + const attributes = gl.getContextAttributes(); + + // this is going to be fairly simple for now.. but at least we have room to grow! + if (!attributes.stencil) + { + /* eslint-disable max-len */ + + /* eslint-disable no-console */ + console.warn('Provided WebGL context does not have a stencil buffer, masks may not render correctly'); + /* eslint-enable no-console */ + + /* eslint-enable max-len */ + } + } +} diff --git a/packages/core/src/filters/Filter.js b/packages/core/src/filters/Filter.js new file mode 100644 index 0000000..9d8cad8 --- /dev/null +++ b/packages/core/src/filters/Filter.js @@ -0,0 +1,140 @@ +import Shader from '../shader/Shader'; +import Program from '../shader/Program'; +import State from '../state/State'; +import { settings } from '@pixi/settings'; +// import extractUniformsFromSrc from './extractUniformsFromSrc'; +import defaultVertex from './defaultFilter.vert'; +import defaultFragment from './defaultFilter.frag'; + +// let math = require('../../math'); +/** + * @class + * @memberof PIXI + * @extends PIXI.Shader + */ +export default class Filter extends Shader +{ + /** + * @param {string} [vertexSrc] - The source of the vertex shader. + * @param {string} [fragmentSrc] - The source of the fragment shader. + * @param {object} [uniforms] - Custom uniforms to use to augment the built-in ones. + */ + constructor(vertexSrc, fragmentSrc, uniforms) + { + const program = Program.from(vertexSrc, fragmentSrc); + + super(program, uniforms); + + /** + * The padding of the filter. Some filters require extra space to breath such as a blur. + * Increasing this will add extra width and height to the bounds of the object that the + * filter is applied to. + * + * @member {number} + */ + this.padding = 0; + + /** + * The resolution of the filter. Setting this to be lower will lower the quality but + * increase the performance of the filter. + * + * @member {number} + */ + this.resolution = settings.RESOLUTION; + + /** + * If enabled is true the filter is applied, if false it will not. + * + * @member {boolean} + */ + this.enabled = true; + + /** + * If enabled, PixiJS will fit the filter area into boundaries for better performance. + * Switch it off if it does not work for specific shader. + * + * @member {boolean} + */ + this.autoFit = true; + + /** + * Legacy filters use position and uvs from attributes + * @member {boolean} + * @readonly + */ + this.legacy = !!this.program.attributeData.aTextureCoord; + + /** + * the webGL state the filter requires to render + * @member {PIXI.State} + */ + this.state = new State(); + } + + /** + * Applies the filter + * + * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + * @param {boolean} clear - Should the output be cleared before rendering to it + * @param {object} [currentState] - It's current state of filter. + * There are some useful properties in the currentState : + * target, filters, sourceFrame, destinationFrame, renderTarget, resolution + */ + apply(filterManager, input, output, clear, currentState, derp) // eslint-disable-line no-unused-vars + { + // do as you please! + + filterManager.applyFilter(this, input, output, clear, currentState, derp); + + // or just do a regular render.. + } + + /** + * Sets the blendmode of the filter + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL + */ + get blendMode() + { + return this.state.blendMode; + } + + set blendMode(value) // eslint-disable-line require-jsdoc + { + this.state.blendMode = value; + } + + /** + * The default vertex shader source + * + * @static + * @constant + */ + static get defaultVertexSrc() + { + return defaultVertex; + } + + /** + * The default fragment shader source + * + * @static + * @constant + */ + static get defaultFragmentSrc() + { + return defaultFragment; + } +} + +/** + * Used for caching shader IDs + * + * @static + * @private + */ +Filter.SOURCE_KEY_MAP = {}; + diff --git a/packages/core/src/filters/FilterSystem.js b/packages/core/src/filters/FilterSystem.js new file mode 100644 index 0000000..3174318 --- /dev/null +++ b/packages/core/src/filters/FilterSystem.js @@ -0,0 +1,431 @@ +import System from '../System'; + +import RenderTexture from '../renderTexture/RenderTexture'; +import Quad from '../utils/Quad'; +import QuadUv from '../utils/QuadUv'; +import { Rectangle } from '@pixi/math'; +import * as filterTransforms from './filterTransforms'; +import bitTwiddle from 'bit-twiddle'; +import UniformGroup from '../shader/UniformGroup'; +import { DRAW_MODES } from '@pixi/constants'; + +// +/** + * @ignore + * @class + */ +class FilterState +{ + /** + * + */ + constructor() + { + this.renderTexture = null; + this.sourceFrame = new Rectangle(); + this.destinationFrame = new Rectangle(); + this.filters = []; + this.target = null; + this.legacy = false; + this.resolution = 1; + } +} + +/** + * @class + * @memberof PIXI.systems + * @extends PIXI.System + */ +export default class FilterSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + /** + * stores a bunch of PO2 textures used for filtering + * @type {Object} + */ + this.texturePool = {}; + + /** + * a pool for storing filter states, save us creating new ones each tick + * @type {Array} + */ + this.statePool = []; + + /** + * A very simple geometry used when drawing a filter effect to the screen + * @type {Quad} + */ + this.quad = new Quad(); + + this.quadUv = new QuadUv(); + + /** + * Temporary rect for maths + * @type {PIXI.Rectangle} + */ + this.tempRect = new Rectangle(); + + this.activeState = {}; + + /** + * this uniform group is attached to filter uniforms when used + * @type {UniformGroup} + */ + this.globalUniforms = new UniformGroup({ + sourceFrame: this.tempRect, + destinationFrame: this.tempRect, + + // legacy variables + filterArea: new Float32Array(4), + filterClamp: new Float32Array(4), + }, true); + } + + /** + * Adds a new filter to the System. + * + * @param {PIXI.DisplayObject} target - The target of the filter to render. + * @param {PIXI.Filter[]} filters - The filters to apply. + */ + push(target, filters) + { + const renderer = this.renderer; + const filterStack = this.renderer.renderTexture.defaultFilterStack; + const state = this.statePool.pop() || new FilterState(); + + let resolution = filters[0].resolution; + let padding = filters[0].padding; + let autoFit = filters[0].autoFit; + let legacy = filters[0].legacy; + + for (let i = 1; i < filters.length; i++) + { + const filter = filters[i]; + + // lets use the lowest resolution.. + resolution = Math.min(resolution, filter.resolution); + // and the largest amount of padding! + padding = Math.max(padding, filter.padding); + // only auto fit if all filters are autofit + autoFit = autoFit || filter.autoFit; + + legacy = legacy || filter.legacy; + } + + filterStack.push(state); + + state.resolution = resolution; + + state.legacy = legacy; + + // round to whole number based on resolution + // TODO move that to the shader too? + state.sourceFrame = target.filterArea || target.getBounds(true); + + state.sourceFrame.pad(padding); + + if (autoFit) + { + state.sourceFrame.fit(this.renderer.renderTexture.destinationFrame); + } + + state.sourceFrame.round(resolution); + + state.renderTexture = this.getPotFilterTexture(state.sourceFrame.width, state.sourceFrame.height, resolution); + state.filters = filters; + + state.destinationFrame.width = state.renderTexture.width; + state.destinationFrame.height = state.renderTexture.height; + + state.renderTexture.filterFrame = state.sourceFrame; + renderer.renderTexture.bind(state.renderTexture, state.sourceFrame);// /, state.destinationFrame); + renderer.renderTexture.clear(); + } + + /** + * Pops off the filter and applies it. + * + */ + pop() + { + const renderer = this.renderer; + const filterStack = renderer.renderTexture.defaultFilterStack; + const state = filterStack.pop(); + const filters = state.filters; + + this.activeState = state; + + const globalUniforms = this.globalUniforms.uniforms; + + globalUniforms.sourceFrame = state.sourceFrame; + globalUniforms.destinationFrame = state.destinationFrame; + globalUniforms.resolution = state.resolution; + + // only update the rect if its legacy.. + if (state.legacy) + { + const filterArea = globalUniforms.filterArea; + const filterClamp = globalUniforms.filterClamp; + + filterArea[0] = state.destinationFrame.width; + filterArea[1] = state.destinationFrame.height; + filterArea[2] = state.sourceFrame.x; + filterArea[3] = state.sourceFrame.y; + + filterClamp[0] = 0.5 / state.resolution / state.destinationFrame.width; + filterClamp[1] = 0.5 / state.resolution / state.destinationFrame.height; + filterClamp[2] = (state.sourceFrame.width - 0.5) / state.resolution / state.destinationFrame.width; + filterClamp[3] = (state.sourceFrame.height - 0.5) / state.resolution / state.destinationFrame.height; + } + + this.globalUniforms.update(); + + const lastState = filterStack[filterStack.length - 1]; + + if (filters.length === 1) + { + filters[0].apply(this, state.renderTexture, lastState.renderTexture, false, state); + + this.returnFilterTexture(state.renderTexture); + } + else + { + let flip = state.renderTexture; + let flop = this.getPotFilterTexture( + flip.width, + flip.height, + state.resolution + ); + + flop.filterFrame = flip.filterFrame; + + let i = 0; + + for (i = 0; i < filters.length - 1; ++i) + { + filters[i].apply(this, flip, flop, true, state); + + const t = flip; + + flip = flop; + flop = t; + } + + filters[i].apply(this, flip, lastState.renderTexture, false, state); + + this.returnFilterTexture(flip); + this.returnFilterTexture(flop); + } + + this.statePool.push(state); + } + + /** + * Draws a filter. + * + * @param {PIXI.Filter} filter - The filter to draw. + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + * @param {boolean} clear - Should the output be cleared before rendering to it + */ + applyFilter(filter, input, output, clear) + { + const renderer = this.renderer; + + renderer.renderTexture.bind(output, output ? output.filterFrame : null); + + if (clear) + { + // gl.disable(gl.SCISSOR_TEST); + renderer.renderTexture.clear(); + // gl.enable(gl.SCISSOR_TEST); + } + + // set the uniforms.. + filter.uniforms.uSampler = input; + filter.uniforms.filterGlobals = this.globalUniforms; + + // TODO make it so that the order of this does not matter.. + // because it does at the moment cos of global uniforms. + // they need to get resynced + + renderer.state.setState(filter.state); + renderer.shader.bind(filter); + + if (filter.legacy) + { + this.quadUv.map(input._frame, input.filterFrame); + + renderer.geometry.bind(this.quadUv); + renderer.geometry.draw(DRAW_MODES.TRIANGLES); + } + else + { + renderer.geometry.bind(this.quad); + renderer.geometry.draw(DRAW_MODES.TRIANGLE_STRIP); + } + } + + /** + * Calculates the mapped matrix. + * + * TODO playing around here.. this is temporary - (will end up in the shader) + * this returns a matrix that will normalise map filter cords in the filter to screen space + * + * @param {PIXI.Matrix} outputMatrix - the matrix to output to. + * @return {PIXI.Matrix} The mapped matrix. + */ + calculateScreenSpaceMatrix(outputMatrix) + { + const currentState = this.activeState; + + return filterTransforms.calculateScreenSpaceMatrix( + outputMatrix, + currentState.sourceFrame, + currentState.destinationFrame + ); + } + + /** + * This will map the filter coord so that a texture can be used based on the transform of a sprite + * + * @param {PIXI.Matrix} outputMatrix - The matrix to output to. + * @param {PIXI.Sprite} sprite - The sprite to map to. + * @return {PIXI.Matrix} The mapped matrix. + */ + calculateSpriteMatrix(outputMatrix, sprite) + { + const currentState = this.activeState; + + return filterTransforms.calculateSpriteMatrix( + outputMatrix, + currentState.sourceFrame, + currentState.destinationFrame, + sprite + ); + } + + /** + * Destroys this Filter System. + * + * @param {boolean} [contextLost=false] context was lost, do not free shaders + * + */ + destroy(contextLost = false) + { + if (!contextLost) + { + this.emptyPool(); + } + else + { + this.texturePool = {}; + } + } + + /** + * Gets a Power-of-Two render texture. + * + * TODO move to a seperate class could be on renderer? + * also - could cause issue with multiple contexts? + * + * @private + * @param {WebGLRenderingContext} gl - The webgl rendering context + * @param {number} minWidth - The minimum width of the render target. + * @param {number} minHeight - The minimum height of the render target. + * @param {number} resolution - The resolution of the render target. + * @return {PIXI.RenderTarget} The new render target. + */ + getPotFilterTexture(minWidth, minHeight, resolution) + { + minWidth = bitTwiddle.nextPow2(minWidth); + minHeight = bitTwiddle.nextPow2(minHeight); + resolution = resolution || 1; + + const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + + if (!this.texturePool[key]) + { + this.texturePool[key] = []; + } + + let renderTexture = this.texturePool[key].pop(); + + if (!renderTexture) + { + // temporary bypass cache.. + // internally - this will cause a texture to be bound.. + renderTexture = RenderTexture.create({ + width: minWidth, + height: minHeight, + resolution, + }); + } + + return renderTexture; + } + + /** + * Gets extra render texture to use inside current filter + * + * @param {number} resolution resolution of the renderTexture + * @returns {PIXI.RenderTexture} + */ + getFilterTexture(resolution) + { + const rt = this.activeState.renderTexture; + + const filterTexture = this.getPotFilterTexture(rt.width, rt.height, resolution || rt.baseTexture.resolution); + + filterTexture.filterFrame = rt.filterFrame; + + return filterTexture; + } + + /** + * Frees a render texture back into the pool. + * + * @param {PIXI.RenderTarget} renderTexture - The renderTarget to free + */ + returnFilterTexture(renderTexture) + { + renderTexture.filterFrame = null; + + const base = renderTexture.baseTexture; + + const minWidth = base.width; + const minHeight = base.height; + + const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + + this.texturePool[key].push(renderTexture); + } + + /** + * Empties the texture pool. + * + */ + emptyPool() + { + for (const i in this.texturePool) + { + const textures = this.texturePool[i]; + + if (textures) + { + for (let j = 0; j < textures.length; j++) + { + textures[j].destroy(true); + } + } + } + + this.texturePool = {}; + } +} diff --git a/packages/core/src/filters/defaultFilter.frag b/packages/core/src/filters/defaultFilter.frag new file mode 100644 index 0000000..a14c1be --- /dev/null +++ b/packages/core/src/filters/defaultFilter.frag @@ -0,0 +1,22 @@ +varying vec2 vTextureCoord; +varying vec2 vFilterCoord; + +uniform sampler2D uSampler; +uniform sampler2D filterSampler; + +void main(void){ + vec4 masky = texture2D(filterSampler, vFilterCoord); + vec4 sample = texture2D(uSampler, vTextureCoord); + vec4 color; + if(mod(vFilterCoord.x, 1.0) > 0.5) + { + color = vec4(1.0, 0.0, 0.0, 1.0); + } + else + { + color = vec4(0.0, 1.0, 0.0, 1.0); + } + // gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0); + gl_FragColor = mix(sample, masky, 0.5); + gl_FragColor *= sample.a; +} diff --git a/packages/core/src/filters/defaultFilter.vert b/packages/core/src/filters/defaultFilter.vert new file mode 100644 index 0000000..e1febca --- /dev/null +++ b/packages/core/src/filters/defaultFilter.vert @@ -0,0 +1,14 @@ +attribute vec2 aVertexPosition; +attribute vec2 aTextureCoord; + +uniform mat3 projectionMatrix; +uniform mat3 filterMatrix; + +varying vec2 vTextureCoord; +varying vec2 vFilterCoord; + +void main(void){ + gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); + vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy; + vTextureCoord = aTextureCoord ; +} diff --git a/packages/core/src/filters/filterTransforms.js b/packages/core/src/filters/filterTransforms.js new file mode 100644 index 0000000..87a3cd6 --- /dev/null +++ b/packages/core/src/filters/filterTransforms.js @@ -0,0 +1,50 @@ +import { Matrix } from '@pixi/math'; + +/** + * Calculates the mapped matrix + * @param filterArea {Rectangle} The filter area + * @param sprite {Sprite} the target sprite + * @param outputMatrix {Matrix} @alvin + * @private + */ +// this returns a matrix that will normalise map filter cords in the filter to screen space +export function calculateScreenSpaceMatrix(outputMatrix, filterArea, textureSize) +{ + // TODO unwrap? + const mappedMatrix = outputMatrix.identity(); + + mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); + + mappedMatrix.scale(textureSize.width, textureSize.height); + + return mappedMatrix; +} + +export function calculateNormalizedScreenSpaceMatrix(outputMatrix, filterArea, textureSize) +{ + const mappedMatrix = outputMatrix.identity(); + + mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); + + const translateScaleX = (textureSize.width / filterArea.width); + const translateScaleY = (textureSize.height / filterArea.height); + + mappedMatrix.scale(translateScaleX, translateScaleY); + + return mappedMatrix; +} + +// this will map the filter coord so that a texture can be used based on the transform of a sprite +export function calculateSpriteMatrix(outputMatrix, filterArea, textureSize, sprite) +{ + const orig = sprite._texture.orig; + const mappedMatrix = outputMatrix.set(textureSize.width, 0, 0, textureSize.height, filterArea.x, filterArea.y); + const worldTransform = sprite.worldTransform.copyTo(Matrix.TEMP_MATRIX); + + worldTransform.invert(); + mappedMatrix.prepend(worldTransform); + mappedMatrix.scale(1.0 / orig.width, 1.0 / orig.height); + mappedMatrix.translate(sprite.anchor.x, sprite.anchor.y); + + return mappedMatrix; +} diff --git a/packages/core/src/filters/spriteMask/SpriteMaskFilter.js b/packages/core/src/filters/spriteMask/SpriteMaskFilter.js new file mode 100644 index 0000000..91697b7 --- /dev/null +++ b/packages/core/src/filters/spriteMask/SpriteMaskFilter.js @@ -0,0 +1,63 @@ +import Filter from '../Filter'; +import { Matrix } from '@pixi/math'; +import vertex from './spriteMaskFilter.vert'; +import fragment from './spriteMaskFilter.frag'; +import { default as TextureMatrix } from '../../textures/TextureMatrix'; + +/** + * The SpriteMaskFilter class + * + * @class + * @extends PIXI.Filter + * @memberof PIXI + */ +export default class SpriteMaskFilter extends Filter +{ + /** + * @param {PIXI.Sprite} sprite - the target sprite + */ + constructor(sprite) + { + const maskMatrix = new Matrix(); + + super(vertex, fragment); + + sprite.renderable = false; + + this.maskSprite = sprite; + this.maskMatrix = maskMatrix; + } + + /** + * Applies the filter + * + * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from + * @param {PIXI.RenderTarget} input - The input render target. + * @param {PIXI.RenderTarget} output - The target to output to. + */ + apply(filterManager, input, output) + { + const maskSprite = this.maskSprite; + const tex = this.maskSprite.texture; + + if (!tex.valid) + { + return; + } + if (!tex.transform) + { + // margin = 0.0, let it bleed a bit, shader code becomes easier + // assuming that atlas textures were made with 1-pixel padding + tex.transform = new TextureMatrix(tex, 0.0); + } + tex.transform.update(); + + this.uniforms.mask = tex; + this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite) + .prepend(tex.transform.mapCoord); + this.uniforms.alpha = maskSprite.worldAlpha; + this.uniforms.maskClamp = tex.transform.uClampFrame; + + filterManager.applyFilter(this, input, output); + } +} diff --git a/packages/core/src/filters/spriteMask/spriteMaskFilter.frag b/packages/core/src/filters/spriteMask/spriteMaskFilter.frag new file mode 100644 index 0000000..0e4aef8 --- /dev/null +++ b/packages/core/src/filters/spriteMask/spriteMaskFilter.frag @@ -0,0 +1,23 @@ +varying vec2 vMaskCoord; +varying vec2 vTextureCoord; + +uniform sampler2D uSampler; +uniform sampler2D mask; +uniform float alpha; +uniform vec4 maskClamp; + +void main(void) +{ + float clip = step(3.5, + step(maskClamp.x, vMaskCoord.x) + + step(maskClamp.y, vMaskCoord.y) + + step(vMaskCoord.x, maskClamp.z) + + step(vMaskCoord.y, maskClamp.w)); + + vec4 original = texture2D(uSampler, vTextureCoord); + vec4 masky = texture2D(mask, vMaskCoord); + + original *= (masky.r * masky.a * alpha * clip); + + gl_FragColor = original; +} diff --git a/packages/core/src/filters/spriteMask/spriteMaskFilter.vert b/packages/core/src/filters/spriteMask/spriteMaskFilter.vert new file mode 100644 index 0000000..d23a30c --- /dev/null +++ b/packages/core/src/filters/spriteMask/spriteMaskFilter.vert @@ -0,0 +1,16 @@ +attribute vec2 aVertexPosition; +attribute vec2 aTextureCoord; + +uniform mat3 projectionMatrix; +uniform mat3 otherMatrix; + +varying vec2 vMaskCoord; +varying vec2 vTextureCoord; + +void main(void) +{ + gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); + + vTextureCoord = aTextureCoord; + vMaskCoord = ( otherMatrix * vec3( aTextureCoord, 1.0) ).xy; +} diff --git a/packages/core/src/framebuffer/FrameBuffer.js b/packages/core/src/framebuffer/FrameBuffer.js new file mode 100644 index 0000000..84e9fcc --- /dev/null +++ b/packages/core/src/framebuffer/FrameBuffer.js @@ -0,0 +1,106 @@ +import Texture from '../textures/BaseTexture'; +import { FORMATS, TYPES } from '@pixi/constants'; + +/** + * Frame buffer + * @class + * @memberof PIXI + */ +export default class FrameBuffer +{ + constructor(width, height) + { + this.width = width || 100; + this.height = height || 100; + + this.stencil = false; + this.depth = false; + + this.dirtyId = 0; + this.dirtyFormat = 0; + this.dirtySize = 0; + + this.depthTexture = null; + this.colorTextures = []; + + this.glFrameBuffers = {}; + } + + get colorTexture() + { + return this.colorTextures[0]; + } + + addColorTexture(index, texture) + { + // TODO add some validation to the texture - same width / height etc? + this.colorTextures[index || 0] = texture || new Texture(null, { scaleMode: 0, + resolution: 1, + mipmap: false, + width: this.width, + height: this.height });// || new Texture(); + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + addDepthTexture(texture) + { + /* eslint-disable max-len */ + this.depthTexture = texture || new Texture(null, { scaleMode: 0, + resolution: 1, + width: this.width, + height: this.height, + mipmap: false, + format: FORMATS.DEPTH_COMPONENT, + type: TYPES.UNSIGNED_SHORT });// UNSIGNED_SHORT; + /* eslint-disable max-len */ + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + enableDepth() + { + this.depth = true; + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + enableStencil() + { + this.stencil = true; + + this.dirtyId++; + this.dirtyFormat++; + + return this; + } + + resize(width, height) + { + if (width === this.width && height === this.height) return; + + this.width = width; + this.height = height; + + this.dirtyId++; + this.dirtySize++; + + for (let i = 0; i < this.colorTextures.length; i++) + { + this.colorTextures[i].setSize(width, height); + } + + if (this.depthTexture) + { + this.depthTexture.setSize(width, height); + } + } +} diff --git a/packages/core/src/framebuffer/FramebufferSystem.js b/packages/core/src/framebuffer/FramebufferSystem.js new file mode 100644 index 0000000..2cc2437 --- /dev/null +++ b/packages/core/src/framebuffer/FramebufferSystem.js @@ -0,0 +1,246 @@ +import System from '../System'; +import { Rectangle } from '@pixi/math'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class FramebufferSystem extends System +{ + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + contextChange() + { + this.gl = this.renderer.gl; + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + this.current = null; + this.viewport = new Rectangle(); + + this.drawBufferExtension = this.renderer.context.extensions.drawBuffers; + } + + bind(framebuffer, frame) + { + const gl = this.gl; + + this.current = framebuffer; + + if (framebuffer) + { + // TODO cacheing layer! + + const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID] || this.initFramebuffer(framebuffer); + + gl.bindFramebuffer(gl.FRAMEBUFFER, fbo.framebuffer); + // makesure all textures are unbound.. + + // now check for updates... + if (fbo.dirtyId !== framebuffer.dirtyId) + { + fbo.dirtyId = framebuffer.dirtyId; + + if (fbo.dirtyFormat !== framebuffer.dirtyFormat) + { + fbo.dirtyFormat = framebuffer.dirtyFormat; + this.updateFramebuffer(framebuffer); + } + else if (fbo.dirtySize !== framebuffer.dirtySize) + { + fbo.dirtySize = framebuffer.dirtySize; + this.resizeFramebuffer(framebuffer); + } + } + + for (let i = 0; i < framebuffer.colorTextures.length; i++) + { + if (framebuffer.colorTextures[i].texturePart) + { + this.renderer.texture.unbind(framebuffer.colorTextures[i].texture); + } + else + { + this.renderer.texture.unbind(framebuffer.colorTextures[i]); + } + } + + if (framebuffer.depthTexture) + { + this.renderer.texture.unbind(framebuffer.depthTexture); + } + + if (frame) + { + this.setViewport(frame.x, frame.y, frame.width, frame.height); + } + else + { + this.setViewport(0, 0, framebuffer.width, framebuffer.height); + } + } + else + { + gl.bindFramebuffer(gl.FRAMEBUFFER, null); + + if (frame) + { + this.setViewport(frame.x, frame.y, frame.width, frame.height); + } + else + { + this.setViewport(0, 0, this.renderer.width, this.renderer.height); + } + } + } + + setViewport(x, y, width, height) + { + const v = this.viewport; + + if (v.width !== width || v.height !== height || v.x !== x || v.y !== y) + { + v.x = x; + v.y = y; + v.width = width; + v.height = height; + + this.gl.viewport(x, y, width, height); + } + } + + get size() + { + if (this.current) + { + // TODO store temp + return { x: 0, y: 0, width: this.current.width, height: this.current.height }; + } + + return { x: 0, y: 0, width: this.renderer.width, height: this.renderer.height }; + } + + clear(r, g, b, a) + { + const gl = this.gl; + + // TODO clear color can be set only one right? + gl.clearColor(r, g, b, a); + gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); + } + + // private functions... + + initFramebuffer(framebuffer) + { + const gl = this.gl; + + // TODO - make this a class? + const fbo = { + framebuffer: gl.createFramebuffer(), + stencil: null, + dirtyId: 0, + dirtyFormat: 0, + dirtySize: 0, + }; + + framebuffer.glFrameBuffers[this.CONTEXT_UID] = fbo; + + return fbo; + } + + resizeFramebuffer(framebuffer) + { + const gl = this.gl; + + if (framebuffer.stencil || framebuffer.depth) + { + gl.bindRenderbuffer(gl.RENDERBUFFER, this.stencil); + gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); + } + } + + updateFramebuffer(framebuffer) + { + const gl = this.gl; + + const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID]; + + // bind the color texture + const colorTextures = framebuffer.colorTextures; + + let count = colorTextures.length; + + if (!this.drawBufferExtension) + { + count = Math.min(count, 1); + } + + const activeTextures = []; + + for (let i = 0; i < count; i++) + { + const texture = framebuffer.colorTextures[i]; + + if (texture.texturePart) + { + this.renderer.texture.bind(texture.texture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.COLOR_ATTACHMENT0 + i, + gl.TEXTURE_CUBE_MAP_NEGATIVE_X + texture.side, + texture.texture._glTextures[this.CONTEXT_UID].texture, + 0); + } + else + { + this.renderer.texture.bind(texture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.COLOR_ATTACHMENT0 + i, + gl.TEXTURE_2D, + texture._glTextures[this.CONTEXT_UID].texture, + 0); + } + + activeTextures.push(gl.COLOR_ATTACHMENT0 + i); + } + + if (this.drawBufferExtension && activeTextures.length > 1) + { + this.drawBufferExtension.drawBuffersWEBGL(activeTextures); + } + + if (framebuffer.depthTexture) + { + const depthTextureExt = this.renderer.context.extensions.depthTexture; + + if (depthTextureExt) + { + const depthTexture = framebuffer.depthTexture; + + this.renderer.texture.bind(depthTexture, 0); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.DEPTH_ATTACHMENT, + gl.TEXTURE_2D, + depthTexture._glTextures[this.CONTEXT_UID].texture, + 0); + } + } + + if (framebuffer.stencil || framebuffer.depth) + { + fbo.stencil = gl.createRenderbuffer(); + + gl.bindRenderbuffer(gl.RENDERBUFFER, fbo.stencil); + + // TODO.. this is depth AND stencil? + gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, fbo.stencil); + gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); + // fbo.enableStencil(); + } + } +} diff --git a/packages/core/src/geometry/GLBuffer.js b/packages/core/src/geometry/GLBuffer.js new file mode 100644 index 0000000..0477e8a --- /dev/null +++ b/packages/core/src/geometry/GLBuffer.js @@ -0,0 +1,9 @@ +export default class GLBuffer +{ + constructor(buffer) + { + this.buffer = buffer; + this.updateID = -1; + this.byteLength = -1; + } +} diff --git a/packages/core/src/geometry/GeometrySystem.js b/packages/core/src/geometry/GeometrySystem.js new file mode 100644 index 0000000..959f827 --- /dev/null +++ b/packages/core/src/geometry/GeometrySystem.js @@ -0,0 +1,388 @@ +import System from '../System'; +import GLBuffer from './GLBuffer'; + +const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class GeometrySystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this._activeGeometry = null; + this._activeVao = null; + + this.hasVao = true; + this.hasInstance = true; + } + + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + contextChange() + { + const gl = this.gl = this.renderer.gl; + + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + + // webgl2 + if (!gl.createVertexArray) + { + // webgl 1! + let nativeVaoExtension = this.renderer.context.extensions.vertexArrayObject; + + if (this.renderer.legacy) + { + nativeVaoExtension = null; + } + + if (nativeVaoExtension) + { + gl.createVertexArray = () => + nativeVaoExtension.createVertexArrayOES(); + + gl.bindVertexArray = (vao) => + nativeVaoExtension.bindVertexArrayOES(vao); + + gl.deleteVertexArray = (vao) => + nativeVaoExtension.deleteVertexArrayOES(vao); + } + else + { + this.hasVao = false; + gl.createVertexArray = () => + { + // empty + }; + + gl.bindVertexArray = () => + { + // empty + }; + + gl.deleteVertexArray = () => + { + // empty + }; + } + } + + if (!gl.vertexAttribDivisor) + { + const instanceExt = gl.getExtension('ANGLE_instanced_arrays'); + + if (instanceExt) + { + gl.vertexAttribDivisor = (a, b) => + instanceExt.vertexAttribDivisorANGLE(a, b); + + gl.drawElementsInstanced = (a, b, c, d, e) => + instanceExt.drawElementsInstancedANGLE(a, b, c, d, e); + + gl.drawArraysInstanced = (a, b, c, d) => + instanceExt.drawArraysInstancedANGLE(a, b, c, d); + } + else + { + this.hasInstance = false; + } + } + } + + /** + * Binds geometry so that is can be drawn. Creating a Vao if required + * @private + * @param {PIXI.Geometry} geometry instance of geometry to bind + */ + bind(geometry, shader) + { + shader = shader || this.renderer.shader.shader; + + const gl = this.gl; + + // not sure the best way to address this.. + // currently different shaders require different VAOs for the same geometry + // Still mulling over the best way to solve this one.. + // will likely need to modify the shader attribute locations at run time! + let vaos = geometry.glVertexArrayObjects[this.CONTEXT_UID]; + + if (!vaos) + { + geometry.glVertexArrayObjects[this.CONTEXT_UID] = vaos = {}; + } + + const vao = vaos[shader.program.id] || this.initGeometryVao(geometry, shader.program); + + this._activeGeometry = geometry; + + if (this._activeVao !== vao) + { + this._activeVao = vao; + + if (this.hasVao) + { + gl.bindVertexArray(vao); + } + else + { + this.activateVao(geometry, shader.program); + } + } + + // TODO - optimise later! + // don't need to loop through if nothing changed! + // maybe look to add an 'autoupdate' to geometry? + this.updateBuffers(); + } + + reset() + { + this.unbind(); + } + + updateBuffers() + { + const geometry = this._activeGeometry; + const gl = this.gl; + + for (let i = 0; i < geometry.buffers.length; i++) + { + const buffer = geometry.buffers[i]; + + const glBuffer = buffer._glBuffers[this.CONTEXT_UID]; + + if (buffer._updateID !== glBuffer.updateID) + { + glBuffer.updateID = buffer._updateID; + + // TODO can cache this on buffer! maybe added a getter / setter? + const type = buffer.index ? gl.ELEMENT_ARRAY_BUFFER : gl.ARRAY_BUFFER; + const drawType = buffer.static ? gl.STATIC_DRAW : gl.DYNAMIC_DRAW; + + gl.bindBuffer(type, glBuffer.buffer); + + if (glBuffer.byteLength >= buffer.data.byteLength) + { + // offset is always zero for now! + gl.bufferSubData(type, 0, buffer.data); + } + else + { + gl.bufferData(type, buffer.data, drawType); + } + } + } + } + + checkCompatability(geometry, program) + { + // geometry must have at least all the attributes that the shader requires. + const geometryAttributes = geometry.attributes; + const shaderAttributes = program.attributeData; + + for (const j in shaderAttributes) + { + if (!geometryAttributes[j]) + { + throw new Error(`shader and geometry incompatible, geometry missing the "${j}" attribute`); + } + } + } + + /** + * Creates a Vao with the same structure as the geometry and stores it on the geometry. + * @private + * @param {PIXI.Geometry} geometry instance of geometry to to generate Vao for + */ + initGeometryVao(geometry, program) + { + this.checkCompatability(geometry, program); + + const gl = this.gl; + const CONTEXT_UID = this.CONTEXT_UID; + const buffers = geometry.buffers; + const attributes = geometry.attributes; + + const tempStride = {}; + const tempStart = {}; + + for (const j in buffers) + { + tempStride[j] = 0; + tempStart[j] = 0; + } + + for (const j in attributes) + { + if (!attributes[j].size && program.attributeData[j]) + { + attributes[j].size = program.attributeData[j].size; + } + + tempStride[attributes[j].buffer] += attributes[j].size * byteSizeMap[attributes[j].type]; + } + + for (const j in attributes) + { + const attribute = attributes[j]; + const attribSize = attribute.size; + + if (attribute.stride === undefined) + { + if (tempStride[attribute.buffer] === attribSize * byteSizeMap[attribute.type]) + { + attribute.stride = 0; + } + else + { + attribute.stride = tempStride[attribute.buffer]; + } + } + + if (attribute.start === undefined) + { + attribute.start = tempStart[attribute.buffer]; + + tempStart[attribute.buffer] += attribSize * byteSizeMap[attribute.type]; + } + } + + // first update - and create the buffers! + // only create a gl buffer if it actually gets + for (let i = 0; i < buffers.length; i++) + { + const buffer = buffers[i]; + + if (!buffer._glBuffers[CONTEXT_UID]) + { + buffer._glBuffers[CONTEXT_UID] = new GLBuffer(gl.createBuffer()); + } + } + + // TODO - maybe make this a data object? + // lets wait to see if we need to first! + const vao = gl.createVertexArray(); + + gl.bindVertexArray(vao); + + this.activateVao(geometry, program); + + gl.bindVertexArray(null); + + // add it to the cache! + geometry.glVertexArrayObjects[this.CONTEXT_UID][program.id] = vao; + + return vao; + } + + activateVao(geometry, program) + { + const gl = this.gl; + const CONTEXT_UID = this.CONTEXT_UID; + const buffers = geometry.buffers; + const attributes = geometry.attributes; + + if (geometry.indexBuffer) + { + // first update the index buffer if we have one.. + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, geometry.indexBuffer._glBuffers[CONTEXT_UID].buffer); + } + + let lastBuffer = null; + + // add a new one! + for (const j in attributes) + { + const attribute = attributes[j]; + const buffer = buffers[attribute.buffer]; + const glBuffer = buffer._glBuffers[CONTEXT_UID]; + + if (program.attributeData[j]) + { + if (lastBuffer !== glBuffer) + { + gl.bindBuffer(gl.ARRAY_BUFFER, glBuffer.buffer); + + lastBuffer = glBuffer; + } + + const location = program.attributeData[j].location; + + // TODO introduce state again + // we can optimise this for older devices that have no VAOs + gl.enableVertexAttribArray(location); + + gl.vertexAttribPointer(location, + attribute.size, + attribute.type || gl.FLOAT, + attribute.normalized, + attribute.stride, + attribute.start); + + if (attribute.instance) + { + // TODO calculate instance count based of this... + if (this.hasInstance) + { + gl.vertexAttribDivisor(location, 1); + } + else + { + throw new Error('geometry error, GPU Instancing is not supported on this device'); + } + } + } + } + } + + draw(type, size, start, instanceCount) + { + const gl = this.gl; + const geometry = this._activeGeometry; + + // TODO.. this should not change so maybe cache the function? + + if (geometry.indexBuffer) + { + if (geometry.instanced) + { + /* eslint-disable max-len */ + gl.drawElementsInstanced(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2, instanceCount || 1); + /* eslint-enable max-len */ + } + else + { + gl.drawElements(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2); + } + } + else + if (geometry.instanced) + { + // TODO need a better way to calculate size.. + gl.drawArraysInstanced(type, start, size || geometry.getSize(), instanceCount || 1); + } + else + { + gl.drawArrays(type, start, size || geometry.getSize()); + } + + return this; + } + + unbind() + { + this.gl.bindVertexArray(null); + this._activeVao = null; + this._activeGeometry = null; + } +} diff --git a/packages/core/src/geometry/utils/setVertexAttribArrays.js b/packages/core/src/geometry/utils/setVertexAttribArrays.js new file mode 100644 index 0000000..23b6959 --- /dev/null +++ b/packages/core/src/geometry/utils/setVertexAttribArrays.js @@ -0,0 +1,54 @@ +// var GL_MAP = {}; + +/** + * @param gl {WebGLRenderingContext} The current WebGL context + * @param attribs {*} + * @param state {*} + */ +export default function setVertexAttribArrays(gl, attribs, state) +{ + let i; + + if (state) + { + const tempAttribState = state.tempAttribState; + const attribState = state.attribState; + + for (i = 0; i < tempAttribState.length; i++) + { + tempAttribState[i] = false; + } + + // set the new attribs + for (i = 0; i < attribs.length; i++) + { + tempAttribState[attribs[i].attribute.location] = true; + } + + for (i = 0; i < attribState.length; i++) + { + if (attribState[i] !== tempAttribState[i]) + { + attribState[i] = tempAttribState[i]; + + if (state.attribState[i]) + { + gl.enableVertexAttribArray(i); + } + else + { + gl.disableVertexAttribArray(i); + } + } + } + } + else + { + for (i = 0; i < attribs.length; i++) + { + const attrib = attribs[i]; + + gl.enableVertexAttribArray(attrib.attribute.location); + } + } +} diff --git a/packages/core/src/index.js b/packages/core/src/index.js index 58c456a..fd1f547 100644 --- a/packages/core/src/index.js +++ b/packages/core/src/index.js @@ -1,30 +1,30 @@ import * as resources from './textures/resources'; -import * as systems from './renderers/systems'; +import * as systems from './systems'; -export { resources }; export { systems }; +export { resources }; -export { default as Renderer } from './renderers/Renderer'; -export { default as AbstractRenderer } from './renderers/AbstractRenderer'; -export { default as FrameBuffer } from './textures/FrameBuffer'; +export { default as System } from './System'; +export { default as Renderer } from './Renderer'; +export { default as AbstractRenderer } from './AbstractRenderer'; +export { default as FrameBuffer } from './framebuffer/FrameBuffer'; export { default as CubeTexture } from './textures/CubeTexture'; export { default as BaseTexture } from './textures/BaseTexture'; export { default as Texture } from './textures/Texture'; export { default as TextureMatrix } from './textures/TextureMatrix'; -export { default as RenderTexture } from './textures/RenderTexture'; -export { default as BaseRenderTexture } from './textures/BaseRenderTexture'; +export { default as RenderTexture } from './renderTexture/RenderTexture'; +export { default as BaseRenderTexture } from './renderTexture/BaseRenderTexture'; export { default as TextureUvs } from './textures/TextureUvs'; -export { default as State } from './renderers/State'; -export { default as ObjectRenderer } from './renderers/utils/ObjectRenderer'; -export { default as Quad } from './renderers/utils/Quad'; -export { default as QuadUv } from './renderers/utils/QuadUv'; -export { default as checkMaxIfStatmentsInShader } from './renderers/utils/checkMaxIfStatmentsInShader'; +export { default as State } from './state/State'; +export { default as ObjectRenderer } from './batch/ObjectRenderer'; +export { default as Quad } from './utils/Quad'; +export { default as QuadUv } from './utils/QuadUv'; +export { default as checkMaxIfStatmentsInShader } from './shader/utils/checkMaxIfStatmentsInShader'; export { default as Shader } from './shader/Shader'; export { default as Program } from './shader/Program'; export { default as UniformGroup } from './shader/UniformGroup'; -export { default as SpriteMaskFilter } from './renderers/filters/spriteMask/SpriteMaskFilter'; -export { default as Filter } from './renderers/filters/Filter'; +export { default as SpriteMaskFilter } from './filters/spriteMask/SpriteMaskFilter'; +export { default as Filter } from './filters/Filter'; export { default as Attribute } from './geometry/Attribute'; export { default as Buffer } from './geometry/Buffer'; export { default as Geometry } from './geometry/Geometry'; - diff --git a/packages/core/src/mask/MaskSystem.js b/packages/core/src/mask/MaskSystem.js new file mode 100644 index 0000000..99174c2 --- /dev/null +++ b/packages/core/src/mask/MaskSystem.js @@ -0,0 +1,199 @@ +import System from '../System'; +import AlphaMaskFilter from '../filters/spriteMask/SpriteMaskFilter'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class MaskSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + // TODO - we don't need both! + this.scissor = false; + this.scissorData = null; + this.scissorRenderTarget = null; + + this.enableScissor = false; + + this.alphaMaskPool = []; + this.alphaMaskIndex = 0; + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.DisplayObject} target - Display Object to push the mask to + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + push(target, maskData) + { + // TODO the root check means scissor rect will not + // be used on render textures more info here: + // https://github.com/pixijs/pixi.js/pull/3545 + + if (maskData.texture) + { + this.pushSpriteMask(target, maskData); + } + else if (this.enableScissor + && !this.scissor + && this.renderer._activeRenderTarget.root + && !this.renderer.stencil.stencilMaskStack.length + && maskData.isFastRect()) + { + const matrix = maskData.worldTransform; + + let rot = Math.atan2(matrix.b, matrix.a); + + // use the nearest degree! + rot = Math.round(rot * (180 / Math.PI)); + + if (rot % 90) + { + this.pushStencilMask(maskData); + } + else + { + this.pushScissorMask(target, maskData); + } + } + else + { + this.pushStencilMask(maskData); + } + } + + /** + * Removes the last mask from the mask stack and doesn't return it. + * + * @param {PIXI.DisplayObject} target - Display Object to pop the mask from + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + pop(target, maskData) + { + if (maskData.texture) + { + this.popSpriteMask(target, maskData); + } + else if (this.enableScissor && !this.renderer.stencil.stencilMaskStack.length) + { + this.popScissorMask(target, maskData); + } + else + { + this.popStencilMask(target, maskData); + } + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.RenderTarget} target - Display Object to push the sprite mask to + * @param {PIXI.Sprite} maskData - Sprite to be used as the mask + */ + pushSpriteMask(target, maskData) + { + let alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; + + if (!alphaMaskFilter) + { + alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; + } + + alphaMaskFilter[0].resolution = this.renderer.resolution; + alphaMaskFilter[0].maskSprite = maskData; + + // TODO - may cause issues! + target.filterArea = maskData.getBounds(true); + + this.renderer.filterSystem.pushFilter(target, alphaMaskFilter); + + this.alphaMaskIndex++; + } + + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popSpriteMask() + { + this.renderer.filterSystem.popFilter(); + this.alphaMaskIndex--; + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. + */ + pushStencilMask(maskData) + { + this.renderer.batch.flush(); + this.renderer.stencil.pushStencil(maskData); + } + + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popStencilMask() + { + // this.renderer.currentRenderer.stop(); + this.renderer.stencil.popStencil(); + } + + /** + * + * @param {PIXI.DisplayObject} target - Display Object to push the mask to + * @param {PIXI.Graphics} maskData - The masking data. + */ + pushScissorMask(target, maskData) + { + maskData.renderable = true; + + const renderTarget = this.renderer._activeRenderTarget; + + const bounds = maskData.getBounds(); + + bounds.fit(renderTarget.size); + maskData.renderable = false; + + this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); + + const resolution = this.renderer.resolution; + + this.renderer.gl.scissor( + bounds.x * resolution, + (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, + bounds.width * resolution, + bounds.height * resolution + ); + + this.scissorRenderTarget = renderTarget; + this.scissorData = maskData; + this.scissor = true; + } + + /** + * + * + */ + popScissorMask() + { + this.scissorRenderTarget = null; + this.scissorData = null; + this.scissor = false; + + // must be scissor! + const gl = this.renderer.gl; + + gl.disable(gl.SCISSOR_TEST); + } +} diff --git a/packages/core/src/mask/StencilSystem.js b/packages/core/src/mask/StencilSystem.js new file mode 100644 index 0000000..62796a5 --- /dev/null +++ b/packages/core/src/mask/StencilSystem.js @@ -0,0 +1,133 @@ +import System from '../System'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class StencilSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + this.stencilMaskStack = []; + } + + /** + * Changes the mask stack that is used by this System. + * + * @param {PIXI.Graphics[]} stencilMaskStack - The mask stack + */ + setMaskStack(stencilMaskStack) + { + const gl = this.renderer.gl; + + if (stencilMaskStack.length !== this.stencilMaskStack.length) + { + if (stencilMaskStack.length === 0) + { + gl.disable(gl.STENCIL_TEST); + } + else + { + gl.enable(gl.STENCIL_TEST); + } + } + + this.stencilMaskStack = stencilMaskStack; + } + + /** + * Applies the Mask and adds it to the current stencil stack. @alvin + * + * @param {PIXI.Graphics} graphics - The mask + */ + pushStencil(graphics) + { + this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); + + // this.renderer._activeRenderTarget.attachStencilBuffer(); + + const gl = this.renderer.gl; + const prevMaskCount = this.stencilMaskStack.length; + + if (prevMaskCount === 0) + { + gl.enable(gl.STENCIL_TEST); + } + + this.stencilMaskStack.push(graphics); + + // Increment the refference stencil value where the new mask overlaps with the old ones. + gl.colorMask(false, false, false, false); + gl.stencilFunc(gl.EQUAL, prevMaskCount, this._getBitwiseMask()); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.INCR); + this.renderer.plugins.graphics.render(graphics); + + this._useCurrent(); + } + + /** + * Removes the last mask from the stencil stack. @alvin + */ + popStencil() + { + this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); + + const gl = this.renderer.gl; + const graphics = this.stencilMaskStack.pop(); + + if (this.stencilMaskStack.length === 0) + { + // the stack is empty! + gl.disable(gl.STENCIL_TEST); + gl.clear(gl.STENCIL_BUFFER_BIT); + gl.clearStencil(0); + } + else + { + // Decrement the refference stencil value where the popped mask overlaps with the other ones + gl.colorMask(false, false, false, false); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.DECR); + this.renderer.plugins.graphics.render(graphics); + + this._useCurrent(); + } + } + + /** + * Setup renderer to use the current stencil data. + */ + _useCurrent() + { + const gl = this.renderer.gl; + + gl.colorMask(true, true, true, true); + gl.stencilFunc(gl.EQUAL, this.stencilMaskStack.length, this._getBitwiseMask()); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP); + } + + /** + * Fill 1s equal to the number of acitve stencil masks. + * + * @return {number} The bitwise mask. + */ + _getBitwiseMask() + { + return (1 << this.stencilMaskStack.length) - 1; + } + + /** + * Destroys the mask stack. + * + */ + destroy() + { + super.destroy(this); + + this.stencilMaskStack = null; + } +} diff --git a/packages/core/src/projection/ProjectionSystem.js b/packages/core/src/projection/ProjectionSystem.js new file mode 100644 index 0000000..6e63b73 --- /dev/null +++ b/packages/core/src/projection/ProjectionSystem.js @@ -0,0 +1,73 @@ +import System from '../System'; +import { Matrix } from '@pixi/math'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class ProjectionSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.projectionMatrix = new Matrix(); + } + + update(destinationFrame, sourceFrame, resolution, root) + { + this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; + this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; + + this.calculateProjection(this.destinationFrame, this.sourceFrame, resolution, root); + + this.renderer.globalUniforms.uniforms.projectionMatrix = this.projectionMatrix; + this.renderer.globalUniforms.update(); + } + + /** + * Updates the projection matrix based on a projection frame (which is a rectangle) + * + * @param {Rectangle} destinationFrame - The destination frame. + * @param {Rectangle} sourceFrame - The source frame. + */ + calculateProjection(destinationFrame, sourceFrame, resolution, root) + { + const pm = this.projectionMatrix; + + // I don't think we will need this line.. + // pm.identity(); + + if (!root) + { + pm.a = (1 / destinationFrame.width * 2) * resolution; + pm.d = (1 / destinationFrame.height * 2) * resolution; + + pm.tx = -1 - (sourceFrame.x * pm.a); + pm.ty = -1 - (sourceFrame.y * pm.d); + } + else + { + pm.a = (1 / destinationFrame.width * 2) * resolution; + pm.d = (-1 / destinationFrame.height * 2) * resolution; + + pm.tx = -1 - (sourceFrame.x * pm.a); + pm.ty = 1 - (sourceFrame.y * pm.d); + } + } + + /** + * Sets the transform of the active render target to the given matrix + * + * @param {PIXI.Matrix} matrix - The transformation matrix + */ + setTransform()// matrix) + { + // this._activeRenderTarget.transform = matrix; + } +} diff --git a/packages/core/src/renderTexture/BaseRenderTexture.js b/packages/core/src/renderTexture/BaseRenderTexture.js new file mode 100644 index 0000000..88eb83b --- /dev/null +++ b/packages/core/src/renderTexture/BaseRenderTexture.js @@ -0,0 +1,137 @@ +import BaseTexture from '../textures/BaseTexture'; +import FrameBuffer from '../framebuffer/FrameBuffer'; + +/** + * A BaseRenderTexture is a special texture that allows any PixiJS display object to be rendered to it. + * + * __Hint__: All DisplayObjects (i.e. Sprites) that render to a BaseRenderTexture should be preloaded + * otherwise black rectangles will be drawn instead. + * + * A BaseRenderTexture takes a snapshot of any Display Object given to its render method. The position + * and rotation of the given Display Objects is ignored. For example: + * + * ```js + * let renderer = PIXI.autoDetectRenderer(1024, 1024, { view: canvas, ratio: 1 }); + * let baseRenderTexture = new PIXI.BaseRenderTexture(renderer, 800, 600); + * let sprite = PIXI.Sprite.fromImage("spinObj_01.png"); + * + * sprite.position.x = 800/2; + * sprite.position.y = 600/2; + * sprite.anchor.x = 0.5; + * sprite.anchor.y = 0.5; + * + * baseRenderTexture.render(sprite); + * ``` + * + * The Sprite in this case will be rendered using its local transform. To render this sprite at 0,0 + * you can clear the transform + * + * ```js + * + * sprite.setTransform() + * + * let baseRenderTexture = new PIXI.BaseRenderTexture(100, 100); + * let renderTexture = new PIXI.RenderTexture(baseRenderTexture); + * + * renderer.render(sprite, renderTexture); // Renders to center of RenderTexture + * ``` + * + * @class + * @extends PIXI.BaseTexture + * @memberof PIXI + */ +export default class BaseRenderTexture extends BaseTexture +{ + /** + * @param {object} [options] + * @param {number} [options.width=100] - The width of the base render texture + * @param {number} [options.height=100] - The height of the base render texture + * @param {PIXI.SCALE_MODES} [options.scaleMode] - See {@link PIXI.SCALE_MODES} for possible values + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the texture being generated + */ + constructor(options) + { + if (typeof options === 'number') + { + /* eslint-disable prefer-rest-params */ + // Backward compatibility of signature + const width = arguments[0]; + const height = arguments[1]; + const scaleMode = arguments[2]; + const resolution = arguments[3]; + + options = { width, height, scaleMode, resolution }; + /* eslint-enable prefer-rest-params */ + } + + super(null, options); + + const { width, height } = options || {}; + + // Set defaults + this.mipmap = false; + this.width = Math.ceil(width) || 100; + this.height = Math.ceil(height) || 100; + this.valid = true; + + /** + * A map of renderer IDs to webgl renderTargets + * + * @private + * @member {object} + */ + // this._glRenderTargets = {}; + + /** + * A reference to the canvas render target (we only need one as this can be shared across renderers) + * + * @private + * @member {object} + */ + this._canvasRenderTarget = null; + + this.clearColor = [0, 0, 0, 0]; + + this.frameBuffer = new FrameBuffer(width * this.resolution, height * this.resolution) + .addColorTexture(0, this); + + // TODO - could this be added the systems? + + /** + * The data structure for the stencil masks + * + * @member {PIXI.Graphics[]} + */ + this.stencilMaskStack = []; + + /** + * The data structure for the filters + * + * @member {PIXI.Graphics[]} + */ + this.filterStack = [{}]; + } + + /** + * Resizes the BaseRenderTexture. + * + * @param {number} width - The width to resize to. + * @param {number} height - The height to resize to. + */ + resize(width, height) + { + width = Math.ceil(width); + height = Math.ceil(height); + this.frameBuffer.resize(width * this.resolution, height * this.resolution); + } + + /** + * Destroys this texture + * + */ + destroy() + { + super.destroy(true); + this.renderer = null; + } +} diff --git a/packages/core/src/renderTexture/RenderTexture.js b/packages/core/src/renderTexture/RenderTexture.js new file mode 100644 index 0000000..f1ecf1f --- /dev/null +++ b/packages/core/src/renderTexture/RenderTexture.js @@ -0,0 +1,153 @@ +import BaseRenderTexture from './BaseRenderTexture'; +import Texture from '../textures/Texture'; + +/** + * A RenderTexture is a special texture that allows any PixiJS display object to be rendered to it. + * + * __Hint__: All DisplayObjects (i.e. Sprites) that render to a RenderTexture should be preloaded + * otherwise black rectangles will be drawn instead. + * + * A RenderTexture takes a snapshot of any Display Object given to its render method. For example: + * + * ```js + * let renderer = PIXI.autoDetectRenderer(1024, 1024, { view: canvas, ratio: 1 }); + * let renderTexture = PIXI.RenderTexture.create(800, 600); + * let sprite = PIXI.Sprite.fromImage("spinObj_01.png"); + * + * sprite.position.x = 800/2; + * sprite.position.y = 600/2; + * sprite.anchor.x = 0.5; + * sprite.anchor.y = 0.5; + * + * renderer.render(sprite, renderTexture); + * ``` + * + * The Sprite in this case will be rendered using its local transform. To render this sprite at 0,0 + * you can clear the transform + * + * ```js + * + * sprite.setTransform() + * + * let renderTexture = new PIXI.RenderTexture.create(100, 100); + * + * renderer.render(sprite, renderTexture); // Renders to center of RenderTexture + * ``` + * + * @class + * @extends PIXI.Texture + * @memberof PIXI + */ +export default class RenderTexture extends Texture +{ + /** + * @param {PIXI.BaseRenderTexture} baseRenderTexture - The renderer used for this RenderTexture + * @param {PIXI.Rectangle} [frame] - The rectangle frame of the texture to show + */ + constructor(baseRenderTexture, frame) + { + // support for legacy.. + let _legacyRenderer = null; + + if (!(baseRenderTexture instanceof BaseRenderTexture)) + { + /* eslint-disable prefer-rest-params, no-console */ + const width = arguments[1]; + const height = arguments[2]; + const scaleMode = arguments[3]; + const resolution = arguments[4]; + + // we have an old render texture.. + console.warn(`Please use RenderTexture.create(${width}, ${height}) instead of the ctor directly.`); + _legacyRenderer = arguments[0]; + /* eslint-enable prefer-rest-params, no-console */ + + frame = null; + baseRenderTexture = new BaseRenderTexture({ + width, + height, + scaleMode, + resolution, + }); + } + + /** + * The base texture object that this texture uses + * + * @member {BaseTexture} + */ + super(baseRenderTexture, frame); + + this.legacyRenderer = _legacyRenderer; + + /** + * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. + * + * @member {boolean} + */ + this.valid = true; + + /** + * FilterSystem temporary storage + * @private + * @member {PIXI.Rectangle} + */ + this.filterFrame = null; + + this._updateUvs(); + } + + /** + * Resizes the RenderTexture. + * + * @param {number} width - The width to resize to. + * @param {number} height - The height to resize to. + * @param {boolean} [resizeBaseTexture=true] - Should the baseTexture.width and height values be resized as well? + */ + resize(width, height, resizeBaseTexture = true) + { + width = Math.ceil(width); + height = Math.ceil(height); + + // TODO - could be not required.. + this.valid = (width > 0 && height > 0); + + this._frame.width = this.orig.width = width; + this._frame.height = this.orig.height = height; + + if (resizeBaseTexture) + { + this.baseTexture.resize(width, height); + } + + this._updateUvs(); + } + + /** + * A short hand way of creating a render texture. + * + * @param {object} [options] - Options + * @param {number} [options.width=100] - The width of the render texture + * @param {number} [options.height=100] - The height of the render texture + * @param {number} [options.scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values + * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the texture being generated + * @return {PIXI.RenderTexture} The new render texture + */ + static create(options) + { + // fallback, old-style: create(width, height, scaleMode, resolution) + if (typeof options === 'number') + { + /* eslint-disable prefer-rest-params */ + options = { + width: options, + height: arguments[1], + scaleMode: arguments[2], + resolution: arguments[3], + }; + /* eslint-enable prefer-rest-params */ + } + + return new RenderTexture(new BaseRenderTexture(options)); + } +} diff --git a/packages/core/src/renderTexture/RenderTextureSystem.js b/packages/core/src/renderTexture/RenderTextureSystem.js new file mode 100644 index 0000000..8d0ca61 --- /dev/null +++ b/packages/core/src/renderTexture/RenderTextureSystem.js @@ -0,0 +1,115 @@ +import System from '../System'; +import { Rectangle } from '@pixi/math'; + +const tempRect = new Rectangle(); + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ + +export default class RenderTextureSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.clearColor = renderer._backgroundColorRgba; + + // TODO moe this property somewhere else! + this.defaultMaskStack = []; + this.defaultFilterStack = [{}]; + + // empty render texture? + this.renderTexture = null; + + this.destinationFrame = new Rectangle(); + } + + bind(renderTexture, sourceFrame, destinationFrame) + { + // TODO - do we want this?? + if (this.renderTexture === renderTexture) return; + this.renderTexture = renderTexture; + + const renderer = this.renderer; + + if (renderTexture) + { + const baseTexture = renderTexture.baseTexture; + + if (!destinationFrame) + { + tempRect.width = baseTexture.realWidth; + tempRect.height = baseTexture.realHeight; + + destinationFrame = tempRect; + } + + if (!sourceFrame) + { + sourceFrame = destinationFrame; + } + + this.renderer.framebuffer.bind(baseTexture.frameBuffer, destinationFrame); + + this.renderer.projection.update(destinationFrame, sourceFrame, baseTexture.resolution, false); + this.renderer.stencil.setMaskStack(baseTexture.stencilMaskStack); + } + else + { + // TODO these validation checks happen deeper down.. + // thing they can be avoided.. + if (!destinationFrame) + { + tempRect.width = renderer.width; + tempRect.height = renderer.height; + + destinationFrame = tempRect; + } + + if (!sourceFrame) + { + sourceFrame = destinationFrame; + } + + renderer.framebuffer.bind(null, destinationFrame); + + // TODO store this.. + this.renderer.projection.update(destinationFrame, sourceFrame, this.renderer.resolution, true); + this.renderer.stencil.setMaskStack(this.defaultMaskStack); + } + + this.destinationFrame.copyFrom(destinationFrame); + } + + /** + * Erases the render texture and fills the drawing area with a colour + * + * @param {number} [clearColor] - The colour + * @return {PIXI.Renderer} Returns itself. + */ + clear(clearColor) + { + if (this.renderTexture) + { + clearColor = clearColor || this.renderTexture.baseTexture.clearColor; + } + else + { + clearColor = clearColor || this.clearColor; + } + + this.renderer.framebuffer.clear(clearColor[0], clearColor[1], clearColor[2], clearColor[3]); + } + + resize()// screenWidth, screenHeight) + { + // resize the root only! + this.bind(null); + } +} diff --git a/packages/core/src/renderers/AbstractRenderer.js b/packages/core/src/renderers/AbstractRenderer.js deleted file mode 100644 index 928670f..0000000 --- a/packages/core/src/renderers/AbstractRenderer.js +++ /dev/null @@ -1,353 +0,0 @@ -import { hex2string, hex2rgb } from '@pixi/utils'; -import { Matrix, Rectangle } from '@pixi/math'; -import { RENDERER_TYPE } from '@pixi/constants'; -import { settings } from '@pixi/settings'; -import { Container } from '@pixi/display'; -import RenderTexture from '../textures/RenderTexture'; -import EventEmitter from 'eventemitter3'; - -const tempMatrix = new Matrix(); - -/** - * 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 EventEmitter - * @memberof PIXI - */ -export default class AbstractRenderer extends EventEmitter -{ - // eslint-disable-next-line valid-jsdoc - /** - * @param {string} system - The name of the system this renderer is for. - * @param {object} [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.transparent=false] - If the render view is transparent, default false - * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false - * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment) - * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. The - * resolution of the renderer retina would be 2. - * @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 {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when rendering, - * stopping pixel interpolation. - */ - constructor(system, options, arg2, arg3) - { - super(); - - // Support for constructor(system, screenWidth, screenHeight, options) - if (typeof options === 'number') - { - options = Object.assign({ - width: options, - height: arg2 || settings.RENDER_OPTIONS.height, - }, arg3); - } - - // 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 = RENDERER_TYPE.UNKNOWN; - - /** - * Measurements of the screen. (0, 0, screenWidth, screenHeight) - * - * Its safe to use as filterArea or hitArea for 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 1 - */ - this.resolution = options.resolution || settings.RESOLUTION; - - /** - * Whether the render view is transparent - * - * @member {boolean} - */ - this.transparent = options.transparent; - - /** - * Whether css dimensions of canvas view should be resized to screen dimensions automatically - * - * @member {boolean} - */ - this.autoResize = options.autoResize || false; - - /** - * Tracks the blend modes useful for this renderer. - * - * @member {object} - */ - this.blendModes = null; - - /** - * 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; - - /** - * If true PixiJS will Math.floor() x/y values when rendering, stopping pixel interpolation. - * Handy for crisp pixel art and speed on legacy devices. - * - * @member {boolean} - */ - this.roundPixels = options.roundPixels; - - /** - * The background color as a number. - * - * @member {number} - * @private - */ - this._backgroundColor = 0x000000; - - /** - * The background color as an [R, G, B] array. - * - * @member {number[]} - * @private - */ - this._backgroundColorRgba = [0, 0, 0, 0]; - - /** - * The background color as a string. - * - * @member {string} - * @private - */ - this._backgroundColorString = '#000000'; - - this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter - - /** - * This temporary display object used as the parent of the currently being rendered item - * - * @member {PIXI.DisplayObject} - * @private - */ - this._tempDisplayObjectParent = new Container(); - - /** - * The last root object that the renderer tried to render. - * - * @member {PIXI.DisplayObject} - * @private - */ - this._lastObjectRendered = this._tempDisplayObjectParent; - - /** - * Collection of plugins. - * @readonly - * @member {object} - */ - this.plugins = {}; - } - - /** - * Initialize the plugins. - * - * @protected - * @param {object} staticMap - The dictionary of staticly saved plugins. - */ - initPlugins(staticMap) - { - 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() - { - return this.view.width; - } - - /** - * Same as view.height, actual number of pixels in the canvas by vertical - * - * @member {number} - * @readonly - * @default 600 - */ - get height() - { - return this.view.height; - } - - /** - * Resizes the screen and canvas to the specified width and height - * Canvas dimensions are multiplied by resolution - * - * @param {number} screenWidth - the new width of the screen - * @param {number} screenHeight - the new height of the screen - */ - resize(screenWidth, screenHeight) - { - this.screen.width = screenWidth; - this.screen.height = screenHeight; - - this.view.width = screenWidth * this.resolution; - this.view.height = screenHeight * this.resolution; - - if (this.autoResize) - { - this.view.style.width = `${screenWidth}px`; - this.view.style.height = `${screenHeight}px`; - } - } - - /** - * 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 {PIXI.DisplayObject} displayObject - The displayObject the object will be generated from - * @param {number} scaleMode - Should be one of the scaleMode consts - * @param {number} resolution - The resolution / device pixel ratio of the texture being generated - * @param {PIXI.Rectangle} [region] - The region of the displayObject, that shall be rendered, - * if no region is specified, defaults to the local bounds of the displayObject. - * @return {PIXI.Texture} a texture of the graphics object - */ - generateTexture(displayObject, scaleMode, resolution, region) - { - region = region || displayObject.getLocalBounds(); - - // 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(region.width | 0, region.height | 0, scaleMode, resolution); - - tempMatrix.tx = -region.x; - tempMatrix.ty = -region.y; - - this.render(displayObject, renderTexture, false, tempMatrix, true); - - return renderTexture; - } - - /** - * Removes everything from the renderer and optionally removes the Canvas DOM element. - * - * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. - */ - destroy(removeView) - { - 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); - } - - this.plugins = null; - - this.type = RENDERER_TYPE.UNKNOWN; - - this.view = null; - - this.screen = null; - - this.resolution = 0; - - this.transparent = false; - - this.autoResize = false; - - this.blendModes = null; - - this.options = null; - - this.preserveDrawingBuffer = false; - this.clearBeforeRender = false; - - this.roundPixels = false; - - this._backgroundColor = 0; - this._backgroundColorRgba = null; - this._backgroundColorString = null; - - this._tempDisplayObjectParent = null; - this._lastObjectRendered = null; - } - - /** - * The background color to fill if not transparent - * - * @member {number} - */ - get backgroundColor() - { - return this._backgroundColor; - } - - set backgroundColor(value) // eslint-disable-line require-jsdoc - { - this._backgroundColor = value; - this._backgroundColorString = hex2string(value); - hex2rgb(value, this._backgroundColorRgba); - } -} diff --git a/packages/core/src/renderers/Renderer.js b/packages/core/src/renderers/Renderer.js deleted file mode 100644 index a2020b1..0000000 --- a/packages/core/src/renderers/Renderer.js +++ /dev/null @@ -1,329 +0,0 @@ -import AbstractRenderer from './AbstractRenderer'; -import { sayHello } from '@pixi/utils'; -import MaskSystem from './systems/MaskSystem'; -import StencilSystem from './systems/StencilSystem'; -import FilterSystem from './systems/filter/FilterSystem'; -import FramebufferSystem from './systems/FramebufferSystem'; -import RenderTextureSystem from './systems/RenderTextureSystem'; -import TextureSystem from './systems/textures/TextureSystem'; -import ProjectionSystem from './systems/ProjectionSystem'; -import StateSystem from './systems/StateSystem'; -import GeometrySystem from './systems/geometry/GeometrySystem'; -import ShaderSystem from './systems/shader/ShaderSystem'; -import ContextSystem from './systems/ContextSystem'; -import BatchSystem from './systems/BatchSystem'; -import TextureGCSystem from './systems/textures/TextureGCSystem'; -import { RENDERER_TYPE } from '@pixi/constants'; -import UniformGroup from '../shader/UniformGroup'; -import { Matrix } from '@pixi/math'; -import Runner from 'mini-runner'; - -/** - * 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 Render works by automatically managing webGLBatchs. - * 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 :) - * - * @class - * @memberof PIXI - * @extends PIXI.AbstractRenderer - */ -export default class Renderer extends AbstractRenderer -{ - // eslint-disable-next-line valid-jsdoc - /** - * - * @param {object} [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.transparent=false] - If the render view is transparent, default false - * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false - * @param {boolean} [options.antialias=false] - sets antialias. If not available natively then FXAA - * antialiasing is used - * @param {boolean} [options.forceFXAA=false] - forces FXAA antialiasing to be used over native. - * FXAA is faster, but may not always look as great - * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. - * The resolution of the renderer retina would be 2. - * @param {boolean} [options.clearBeforeRender=true] - This sets if the renderer will clear - * the canvas or not before the new render pass. If you wish to set this to false, you *must* set - * preserveDrawingBuffer to `true`. - * @param {boolean} [options.preserveDrawingBuffer=false] - enables drawing buffer preservation, - * enable this if you need to call toDataUrl on the webgl context. - * @param {boolean} [options.roundPixels=false] - If true PixiJS will Math.floor() x/y values when - * rendering, stopping pixel interpolation. - * @param {number} [options.backgroundColor=0x000000] - The background color of the rendered area - * (shown if not transparent). - * @param {boolean} [options.legacy=false] - If true PixiJS will aim to ensure compatibility - * with older / less advanced devices. If you experiance unexplained flickering try setting this to true. - * @param {string} [options.powerPreference] - Parameter passed to webgl context, set to "high-performance" - * for devices with dual graphics card - */ - constructor(options, arg2, arg3) - { - super('WebGL', options, arg2, arg3); - - /** - * The type of this renderer as a standardised const - * - * @member {number} - * @see PIXI.RENDERER_TYPE - */ - this.type = RENDERER_TYPE.WEBGL; - - // this will be set by the contextSystem (this.context) - this.gl = null; - this.CONTEXT_UID = 0; - this.legacy = !!options.legacy; - - // TODO legacy! - - // runners! - this.runners = { - destroy: new Runner('destroy'), - contextChange: new Runner('contextChange', 1), - reset: new Runner('reset'), - update: new Runner('update'), - postrender: new Runner('postrender'), - prerender: new Runner('prerender'), - resize: new Runner('resize', 2), - }; - - 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(GeometrySystem, 'geometry') - .addSystem(FramebufferSystem, 'framebuffer') - .addSystem(StencilSystem, 'stencil') - .addSystem(ProjectionSystem, 'projection') - .addSystem(TextureGCSystem, 'textureGC') - .addSystem(FilterSystem, 'filter') - .addSystem(RenderTextureSystem, 'renderTexture') - .addSystem(BatchSystem, 'batch'); - - this.initPlugins(Renderer.__plugins); - - /** - * The options passed in to create a new webgl context. - * - * @member {object} - * @private - */ - if (options.context) - { - this.context.initFromContext(options.context); - } - else - { - this.context.initFromOptions({ - alpha: this.transparent, - antialias: options.antialias, - premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', - stencil: true, - 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); - } - - addSystem(_class, name) - { - if (!name) - { - name = _class.name; - } - - // TODO - read name from class.name.. - - /* - if(name.includes('System')) - { - name = name.replace('System', ''); - name = name.charAt(0).toLowerCase() + name.slice(1); - } - */ - - const system = new _class(this); - - if (this[name]) - { - throw new Error(`Whoops! ${name} is already a manger`); - } - - this[name] = system; - - for (const i in this.runners) - { - this.runners[i].add(system); - } - - return this; - - /** - * 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. - */ - } - - /** - * Renders the object to its webGL view - * - * @param {PIXI.DisplayObject} displayObject - the object to be rendered - * @param {PIXI.RenderTexture} renderTexture - The render texture to render to. - * @param {boolean} [clear] - Should the canvas be cleared before the new render - * @param {PIXI.Transform} [transform] - A transform to apply to the render texture before rendering. - * @param {boolean} [skipUpdateTransform] - Should we skip the update transform pass? - */ - render(displayObject, renderTexture, clear, transform, skipUpdateTransform) - { - // can be handy to know! - this.renderingToScreen = !renderTexture; - - this.runners.prerender.run(); - this.emit('prerender'); - - // 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.parent; - - displayObject.parent = this._tempDisplayObjectParent; - displayObject.updateTransform(); - displayObject.parent = 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.run(); - - this.emit('postrender'); - } - - /** - * Resizes the webGL view to the specified width and height. - * - * @param {number} screenWidth - the new width of the screen - * @param {number} screenHeight - the new height of the screen - */ - resize(screenWidth, screenHeight) - { - AbstractRenderer.prototype.resize.call(this, screenWidth, screenHeight); - - this.runners.resize.run(screenWidth, screenHeight); - } - - /** - * Resets the WebGL state so you can render things however you fancy! - * - * @return {PIXI.Renderer} Returns itself. - */ - reset() - { - this.runners.reset.run(); - - return this; - } - - clear() - { - this.framebuffer.bind(); - this.framebuffer.clear(); - } - - /** - * Removes everything from the renderer (event listeners, spritebatch, etc...) - * - * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. - * See: https://github.com/pixijs/pixi.js/issues/2233 - */ - destroy(removeView) - { - this.runners.destroy.run(); - - // call base destroy - super.destroy(removeView); - - // TODO nullify all the managers.. - this.gl = null; - } - - /** - * 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 - * builds and excluding plugins. - * @name PIXI.Renderer#plugins - * @type {object} - * @readonly - * @property {PIXI.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements. - * @property {PIXI.extract.WebGLExtract} extract Extract image data from renderer. - * @property {PIXI.interaction.InteractionManager} interaction Handles mouse, touch and pointer events. - * @property {PIXI.prepare.WebGLPrepare} prepare Pre-render display objects. - */ - - /** - * Adds a plugin to the renderer. - * - * @method - * @param {string} pluginName - The name of the plugin. - * @param {Function} ctor - The constructor function or class for the plugin. - */ - static registerPlugin(pluginName, ctor) - { - Renderer.__plugins = Renderer.__plugins || {}; - Renderer.__plugins[pluginName] = ctor; - } -} diff --git a/packages/core/src/renderers/State.js b/packages/core/src/renderers/State.js deleted file mode 100644 index fa3f221..0000000 --- a/packages/core/src/renderers/State.js +++ /dev/null @@ -1,163 +0,0 @@ -/* eslint-disable max-len */ - -const BLEND = 0; -const OFFSET = 1; -const CULLING = 2; -const DEPTH_TEST = 3; -const WINDING = 4; - -/** - * This is a webGL state. It is passed The WebGL StateManager. - * Each mesh renderered may require webGL to be in a different state. - * For example you may want different blend mode or to enable polygon offsets - * - * @class - * @memberof PIXI - */ -export default class State -{ - /** - * - */ - constructor() - { - this.data = 0; - - this.blendMode = 0; - this.polygonOffset = 0; - - this.blend = true; - // this.depthTest = true; - } - - /** - * Activates blending of the computed fragment color values - * - * @member {boolean} - */ - get blend() - { - return !!(this.data & (1 << BLEND)); - } - - set blend(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << BLEND)) !== value) - { - this.data ^= (1 << BLEND); - } - } - - /** - * Activates adding an offset to depth values of polygon's fragments - * - * @member {boolean} - * @default false - */ - get offsets() - { - return !!(this.data & (1 << OFFSET)); - } - - set offsets(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << OFFSET)) !== value) - { - this.data ^= (1 << OFFSET); - } - } - - /** - * Activates culling of polygons. - * - * @member {boolean} - * @default false - */ - get culling() - { - return !!(this.data & (1 << CULLING)); - } - - set culling(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << CULLING)) !== value) - { - this.data ^= (1 << CULLING); - } - } - - /** - * Activates depth comparisons and updates to the depth buffer. - * - * @member {boolean} - * @default false - */ - get depthTest() - { - return !!(this.data & (1 << DEPTH_TEST)); - } - - set depthTest(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << DEPTH_TEST)) !== value) - { - this.data ^= (1 << DEPTH_TEST); - } - } - - /** - * Specifies whether or not front or back-facing polygons can be culled. - * @member {boolean} - * @default false - */ - get clockwiseFrontFace() - { - return !!(this.data & (1 << WINDING)); - } - - set clockwiseFrontFace(value) // eslint-disable-line require-jsdoc - { - if (!!(this.data & (1 << WINDING)) !== value) - { - this.data ^= (1 << WINDING); - } - } - - /** - * The blend mode to be applied when this state is set. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. - * Setting this mode to anything other than NO_BLEND will automatically switch blending on. - * - * @member {boolean} - * @default PIXI.BLEND_MODES.NORMAL - * @see PIXI.BLEND_MODES - */ - get blendMode() - { - return this._blendMode; - } - - set blendMode(value) // eslint-disable-line require-jsdoc - { - // 17 is NO BLEND - this.blend = (value !== 17); - this._blendMode = value; - } - - /** - * The polygon offset. Setting this property to anything other than 0 will automatically enable poygon offset fill. - * - * @member {number} - * @default 0 - */ - get polygonOffset() - { - return this._polygonOffset; - } - - set polygonOffset(value) // eslint-disable-line require-jsdoc - { - this.offsets = !!value; - this._polygonOffset = value; - } -} - diff --git a/packages/core/src/renderers/filters/Filter.js b/packages/core/src/renderers/filters/Filter.js deleted file mode 100644 index 2073b8d..0000000 --- a/packages/core/src/renderers/filters/Filter.js +++ /dev/null @@ -1,140 +0,0 @@ -import Shader from '../../shader/Shader'; -import Program from '../../shader/Program'; -import State from '../State'; -import { settings } from '@pixi/settings'; -// import extractUniformsFromSrc from './extractUniformsFromSrc'; -import defaultVertex from './defaultFilter.vert'; -import defaultFragment from './defaultFilter.frag'; - -// let math = require('../../math'); -/** - * @class - * @memberof PIXI - * @extends PIXI.Shader - */ -export default class Filter extends Shader -{ - /** - * @param {string} [vertexSrc] - The source of the vertex shader. - * @param {string} [fragmentSrc] - The source of the fragment shader. - * @param {object} [uniforms] - Custom uniforms to use to augment the built-in ones. - */ - constructor(vertexSrc, fragmentSrc, uniforms) - { - const program = Program.from(vertexSrc, fragmentSrc); - - super(program, uniforms); - - /** - * The padding of the filter. Some filters require extra space to breath such as a blur. - * Increasing this will add extra width and height to the bounds of the object that the - * filter is applied to. - * - * @member {number} - */ - this.padding = 0; - - /** - * The resolution of the filter. Setting this to be lower will lower the quality but - * increase the performance of the filter. - * - * @member {number} - */ - this.resolution = settings.RESOLUTION; - - /** - * If enabled is true the filter is applied, if false it will not. - * - * @member {boolean} - */ - this.enabled = true; - - /** - * If enabled, PixiJS will fit the filter area into boundaries for better performance. - * Switch it off if it does not work for specific shader. - * - * @member {boolean} - */ - this.autoFit = true; - - /** - * Legacy filters use position and uvs from attributes - * @member {boolean} - * @readonly - */ - this.legacy = !!this.program.attributeData.aTextureCoord; - - /** - * the webGL state the filter requires to render - * @member {PIXI.State} - */ - this.state = new State(); - } - - /** - * Applies the filter - * - * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from - * @param {PIXI.RenderTarget} input - The input render target. - * @param {PIXI.RenderTarget} output - The target to output to. - * @param {boolean} clear - Should the output be cleared before rendering to it - * @param {object} [currentState] - It's current state of filter. - * There are some useful properties in the currentState : - * target, filters, sourceFrame, destinationFrame, renderTarget, resolution - */ - apply(filterManager, input, output, clear, currentState, derp) // eslint-disable-line no-unused-vars - { - // do as you please! - - filterManager.applyFilter(this, input, output, clear, currentState, derp); - - // or just do a regular render.. - } - - /** - * Sets the blendmode of the filter - * - * @member {number} - * @default PIXI.BLEND_MODES.NORMAL - */ - get blendMode() - { - return this.state.blendMode; - } - - set blendMode(value) // eslint-disable-line require-jsdoc - { - this.state.blendMode = value; - } - - /** - * The default vertex shader source - * - * @static - * @constant - */ - static get defaultVertexSrc() - { - return defaultVertex; - } - - /** - * The default fragment shader source - * - * @static - * @constant - */ - static get defaultFragmentSrc() - { - return defaultFragment; - } -} - -/** - * Used for caching shader IDs - * - * @static - * @private - */ -Filter.SOURCE_KEY_MAP = {}; - diff --git a/packages/core/src/renderers/filters/defaultFilter.frag b/packages/core/src/renderers/filters/defaultFilter.frag deleted file mode 100644 index a14c1be..0000000 --- a/packages/core/src/renderers/filters/defaultFilter.frag +++ /dev/null @@ -1,22 +0,0 @@ -varying vec2 vTextureCoord; -varying vec2 vFilterCoord; - -uniform sampler2D uSampler; -uniform sampler2D filterSampler; - -void main(void){ - vec4 masky = texture2D(filterSampler, vFilterCoord); - vec4 sample = texture2D(uSampler, vTextureCoord); - vec4 color; - if(mod(vFilterCoord.x, 1.0) > 0.5) - { - color = vec4(1.0, 0.0, 0.0, 1.0); - } - else - { - color = vec4(0.0, 1.0, 0.0, 1.0); - } - // gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0); - gl_FragColor = mix(sample, masky, 0.5); - gl_FragColor *= sample.a; -} diff --git a/packages/core/src/renderers/filters/defaultFilter.vert b/packages/core/src/renderers/filters/defaultFilter.vert deleted file mode 100644 index e1febca..0000000 --- a/packages/core/src/renderers/filters/defaultFilter.vert +++ /dev/null @@ -1,14 +0,0 @@ -attribute vec2 aVertexPosition; -attribute vec2 aTextureCoord; - -uniform mat3 projectionMatrix; -uniform mat3 filterMatrix; - -varying vec2 vTextureCoord; -varying vec2 vFilterCoord; - -void main(void){ - gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); - vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy; - vTextureCoord = aTextureCoord ; -} diff --git a/packages/core/src/renderers/filters/filterTransforms.js b/packages/core/src/renderers/filters/filterTransforms.js deleted file mode 100644 index 87a3cd6..0000000 --- a/packages/core/src/renderers/filters/filterTransforms.js +++ /dev/null @@ -1,50 +0,0 @@ -import { Matrix } from '@pixi/math'; - -/** - * Calculates the mapped matrix - * @param filterArea {Rectangle} The filter area - * @param sprite {Sprite} the target sprite - * @param outputMatrix {Matrix} @alvin - * @private - */ -// this returns a matrix that will normalise map filter cords in the filter to screen space -export function calculateScreenSpaceMatrix(outputMatrix, filterArea, textureSize) -{ - // TODO unwrap? - const mappedMatrix = outputMatrix.identity(); - - mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); - - mappedMatrix.scale(textureSize.width, textureSize.height); - - return mappedMatrix; -} - -export function calculateNormalizedScreenSpaceMatrix(outputMatrix, filterArea, textureSize) -{ - const mappedMatrix = outputMatrix.identity(); - - mappedMatrix.translate(filterArea.x / textureSize.width, filterArea.y / textureSize.height); - - const translateScaleX = (textureSize.width / filterArea.width); - const translateScaleY = (textureSize.height / filterArea.height); - - mappedMatrix.scale(translateScaleX, translateScaleY); - - return mappedMatrix; -} - -// this will map the filter coord so that a texture can be used based on the transform of a sprite -export function calculateSpriteMatrix(outputMatrix, filterArea, textureSize, sprite) -{ - const orig = sprite._texture.orig; - const mappedMatrix = outputMatrix.set(textureSize.width, 0, 0, textureSize.height, filterArea.x, filterArea.y); - const worldTransform = sprite.worldTransform.copyTo(Matrix.TEMP_MATRIX); - - worldTransform.invert(); - mappedMatrix.prepend(worldTransform); - mappedMatrix.scale(1.0 / orig.width, 1.0 / orig.height); - mappedMatrix.translate(sprite.anchor.x, sprite.anchor.y); - - return mappedMatrix; -} diff --git a/packages/core/src/renderers/filters/spriteMask/SpriteMaskFilter.js b/packages/core/src/renderers/filters/spriteMask/SpriteMaskFilter.js deleted file mode 100644 index d5a91a4..0000000 --- a/packages/core/src/renderers/filters/spriteMask/SpriteMaskFilter.js +++ /dev/null @@ -1,63 +0,0 @@ -import Filter from '../Filter'; -import { Matrix } from '@pixi/math'; -import vertex from './spriteMaskFilter.vert'; -import fragment from './spriteMaskFilter.frag'; -import { default as TextureMatrix } from '../../../textures/TextureMatrix'; - -/** - * The SpriteMaskFilter class - * - * @class - * @extends PIXI.Filter - * @memberof PIXI - */ -export default class SpriteMaskFilter extends Filter -{ - /** - * @param {PIXI.Sprite} sprite - the target sprite - */ - constructor(sprite) - { - const maskMatrix = new Matrix(); - - super(vertex, fragment); - - sprite.renderable = false; - - this.maskSprite = sprite; - this.maskMatrix = maskMatrix; - } - - /** - * Applies the filter - * - * @param {PIXI.FilterManager} filterManager - The renderer to retrieve the filter from - * @param {PIXI.RenderTarget} input - The input render target. - * @param {PIXI.RenderTarget} output - The target to output to. - */ - apply(filterManager, input, output) - { - const maskSprite = this.maskSprite; - const tex = this.maskSprite.texture; - - if (!tex.valid) - { - return; - } - if (!tex.transform) - { - // margin = 0.0, let it bleed a bit, shader code becomes easier - // assuming that atlas textures were made with 1-pixel padding - tex.transform = new TextureMatrix(tex, 0.0); - } - tex.transform.update(); - - this.uniforms.mask = tex; - this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite) - .prepend(tex.transform.mapCoord); - this.uniforms.alpha = maskSprite.worldAlpha; - this.uniforms.maskClamp = tex.transform.uClampFrame; - - filterManager.applyFilter(this, input, output); - } -} diff --git a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.frag b/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.frag deleted file mode 100644 index 0e4aef8..0000000 --- a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.frag +++ /dev/null @@ -1,23 +0,0 @@ -varying vec2 vMaskCoord; -varying vec2 vTextureCoord; - -uniform sampler2D uSampler; -uniform sampler2D mask; -uniform float alpha; -uniform vec4 maskClamp; - -void main(void) -{ - float clip = step(3.5, - step(maskClamp.x, vMaskCoord.x) + - step(maskClamp.y, vMaskCoord.y) + - step(vMaskCoord.x, maskClamp.z) + - step(vMaskCoord.y, maskClamp.w)); - - vec4 original = texture2D(uSampler, vTextureCoord); - vec4 masky = texture2D(mask, vMaskCoord); - - original *= (masky.r * masky.a * alpha * clip); - - gl_FragColor = original; -} diff --git a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.vert b/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.vert deleted file mode 100644 index d23a30c..0000000 --- a/packages/core/src/renderers/filters/spriteMask/spriteMaskFilter.vert +++ /dev/null @@ -1,16 +0,0 @@ -attribute vec2 aVertexPosition; -attribute vec2 aTextureCoord; - -uniform mat3 projectionMatrix; -uniform mat3 otherMatrix; - -varying vec2 vMaskCoord; -varying vec2 vTextureCoord; - -void main(void) -{ - gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); - - vTextureCoord = aTextureCoord; - vMaskCoord = ( otherMatrix * vec3( aTextureCoord, 1.0) ).xy; -} diff --git a/packages/core/src/renderers/systems/BatchSystem.js b/packages/core/src/renderers/systems/BatchSystem.js deleted file mode 100644 index 70e9518..0000000 --- a/packages/core/src/renderers/systems/BatchSystem.js +++ /dev/null @@ -1,68 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import ObjectRenderer from '../utils/ObjectRenderer'; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ - -export default class BatchSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this manager works for. - */ - constructor(renderer) - { - super(renderer); - - /** - * An empty renderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.emptyRenderer = new ObjectRenderer(renderer); - - /** - * The currently active ObjectRenderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.currentRenderer = this.emptyRenderer; - } - - /** - * Changes the current renderer to the one given in parameter - * - * @param {PIXI.ObjectRenderer} objectRenderer - The object renderer to use. - */ - setObjectRenderer(objectRenderer) - { - if (this.currentRenderer === objectRenderer) - { - return; - } - - this.currentRenderer.stop(); - this.currentRenderer = objectRenderer; - - this.renderer.state.setState(objectRenderer.state); - - this.currentRenderer.start(); - } - - /** - * This should be called if you wish to do some custom rendering - * It will basically render anything that may be batched up such as sprites - * - */ - flush() - { - this.setObjectRenderer(this.emptyRenderer); - } - - reset() - { - this.setObjectRenderer(this.emptyRenderer); - } -} diff --git a/packages/core/src/renderers/systems/ContextSystem.js b/packages/core/src/renderers/systems/ContextSystem.js deleted file mode 100644 index 43bf827..0000000 --- a/packages/core/src/renderers/systems/ContextSystem.js +++ /dev/null @@ -1,186 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { settings } from '@pixi/settings'; - -let CONTEXT_UID = 0; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class ContextSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this.webGLVersion = 1; - - this.handleContextLost = this.handleContextLost.bind(this); - this.handleContextRestored = this.handleContextRestored.bind(this); - - this.extensions = {}; - - renderer.view.addEventListener('webglcontextlost', this.handleContextLost, false); - renderer.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); - } - - get isLost() - { - return (!this.gl || this.gl.isContextLost()); - } - - contextChange(gl) - { - this.gl = gl; - - // restore a context if it was previously lost - if (gl.isContextLost() && gl.getExtension('WEBGL_lose_context')) - { - gl.getExtension('WEBGL_lose_context').restoreContext(); - } - } - - initFromContext(gl) - { - this.gl = gl; - this.validateContext(gl); - this.renderer.gl = gl; - this.renderer.CONTEXT_UID = CONTEXT_UID++; - this.renderer.runners.contextChange.run(gl); - } - - initFromOptions(options) - { - const gl = this.createContext(this.renderer.view, options); - - this.initFromContext(gl); - } - - /** - * Helper class to create a webGL Context - * - * @class - * @memberof PIXI.glCore - * @param canvas {HTMLCanvasElement} the canvas element that we will get the context from - * @param options {Object} An options object that gets passed in to the canvas element containing the context attributes, - * see https://developer.mozilla.org/en/docs/Web/API/HTMLCanvasElement/getContext for the options available - * @return {WebGLRenderingContext} the WebGL context - */ - createContext(canvas, options) - { - let gl; - - if (settings.PREFER_WEBGL_2) - { - gl = canvas.getContext('webgl2', options); - } - - if (gl) - { - this.webGLVersion = 2; - } - else - { - this.webGLVersion = 1; - - gl = canvas.getContext('webgl', options) - || canvas.getContext('experimental-webgl', options); - - if (!gl) - { - // fail, not able to get a context - throw new Error('This browser does not support webGL. Try using the canvas renderer'); - } - } - - this.gl = gl; - - this.getExtensions(); - - return gl; - } - - getExtensions() - { - // time to set up default etensions that pixi uses.. - const gl = this.gl; - const extensions = this.extensions; - - if (this.webGLVersion === 1) - { - extensions.drawBuffers = gl.getExtension('WEBGL_draw_buffers'); - extensions.depthTexture = gl.getExtension('WEBKIT_WEBGL_depth_texture'); - extensions.floatTexture = gl.getExtension('OES_texture_float'); - extensions.loseContext = gl.getExtension('WEBGL_lose_context'); - - extensions.vertexArrayObject = gl.getExtension('OES_vertex_array_object') - || gl.getExtension('MOZ_OES_vertex_array_object') - || gl.getExtension('WEBKIT_OES_vertex_array_object'); - } - - // we don't use any specific WebGL 2 ones yet! - } - - /** - * Handles a lost webgl context - * - * @private - * @param {WebGLContextEvent} event - The context lost event. - */ - handleContextLost(event) - { - event.preventDefault(); - } - - /** - * Handles a restored webgl context - * - * @private - */ - handleContextRestored() - { - this.renderer.runners.contextChange.run(this.gl); - } - - destroy() - { - const view = this.renderer.view; - - // remove listeners - view.removeEventListener('webglcontextlost', this.handleContextLost); - view.removeEventListener('webglcontextrestored', this.handleContextRestored); - - this.gl.useProgram(null); - - if (this.extensions.loseContext) - { - this.extensions.loseContext.loseContext(); - } - } - - postrender() - { - this.gl.flush(); - } - - validateContext(gl) - { - const attributes = gl.getContextAttributes(); - - // this is going to be fairly simple for now.. but at least we have room to grow! - if (!attributes.stencil) - { - /* eslint-disable max-len */ - - /* eslint-disable no-console */ - console.warn('Provided WebGL context does not have a stencil buffer, masks may not render correctly'); - /* eslint-enable no-console */ - - /* eslint-enable max-len */ - } - } -} diff --git a/packages/core/src/renderers/systems/FramebufferSystem.js b/packages/core/src/renderers/systems/FramebufferSystem.js deleted file mode 100644 index df7902b..0000000 --- a/packages/core/src/renderers/systems/FramebufferSystem.js +++ /dev/null @@ -1,246 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { Rectangle } from '@pixi/math'; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class FramebufferSystem extends WebGLSystem -{ - /** - * Sets up the renderer context and necessary buffers. - * - * @private - */ - contextChange() - { - this.gl = this.renderer.gl; - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - this.current = null; - this.viewport = new Rectangle(); - - this.drawBufferExtension = this.renderer.context.extensions.drawBuffers; - } - - bind(framebuffer, frame) - { - const gl = this.gl; - - this.current = framebuffer; - - if (framebuffer) - { - // TODO cacheing layer! - - const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID] || this.initFramebuffer(framebuffer); - - gl.bindFramebuffer(gl.FRAMEBUFFER, fbo.framebuffer); - // makesure all textures are unbound.. - - // now check for updates... - if (fbo.dirtyId !== framebuffer.dirtyId) - { - fbo.dirtyId = framebuffer.dirtyId; - - if (fbo.dirtyFormat !== framebuffer.dirtyFormat) - { - fbo.dirtyFormat = framebuffer.dirtyFormat; - this.updateFramebuffer(framebuffer); - } - else if (fbo.dirtySize !== framebuffer.dirtySize) - { - fbo.dirtySize = framebuffer.dirtySize; - this.resizeFramebuffer(framebuffer); - } - } - - for (let i = 0; i < framebuffer.colorTextures.length; i++) - { - if (framebuffer.colorTextures[i].texturePart) - { - this.renderer.texture.unbind(framebuffer.colorTextures[i].texture); - } - else - { - this.renderer.texture.unbind(framebuffer.colorTextures[i]); - } - } - - if (framebuffer.depthTexture) - { - this.renderer.texture.unbind(framebuffer.depthTexture); - } - - if (frame) - { - this.setViewport(frame.x, frame.y, frame.width, frame.height); - } - else - { - this.setViewport(0, 0, framebuffer.width, framebuffer.height); - } - } - else - { - gl.bindFramebuffer(gl.FRAMEBUFFER, null); - - if (frame) - { - this.setViewport(frame.x, frame.y, frame.width, frame.height); - } - else - { - this.setViewport(0, 0, this.renderer.width, this.renderer.height); - } - } - } - - setViewport(x, y, width, height) - { - const v = this.viewport; - - if (v.width !== width || v.height !== height || v.x !== x || v.y !== y) - { - v.x = x; - v.y = y; - v.width = width; - v.height = height; - - this.gl.viewport(x, y, width, height); - } - } - - get size() - { - if (this.current) - { - // TODO store temp - return { x: 0, y: 0, width: this.current.width, height: this.current.height }; - } - - return { x: 0, y: 0, width: this.renderer.width, height: this.renderer.height }; - } - - clear(r, g, b, a) - { - const gl = this.gl; - - // TODO clear color can be set only one right? - gl.clearColor(r, g, b, a); - gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); - } - - // private functions... - - initFramebuffer(framebuffer) - { - const gl = this.gl; - - // TODO - make this a class? - const fbo = { - framebuffer: gl.createFramebuffer(), - stencil: null, - dirtyId: 0, - dirtyFormat: 0, - dirtySize: 0, - }; - - framebuffer.glFrameBuffers[this.CONTEXT_UID] = fbo; - - return fbo; - } - - resizeFramebuffer(framebuffer) - { - const gl = this.gl; - - if (framebuffer.stencil || framebuffer.depth) - { - gl.bindRenderbuffer(gl.RENDERBUFFER, this.stencil); - gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); - } - } - - updateFramebuffer(framebuffer) - { - const gl = this.gl; - - const fbo = framebuffer.glFrameBuffers[this.CONTEXT_UID]; - - // bind the color texture - const colorTextures = framebuffer.colorTextures; - - let count = colorTextures.length; - - if (!this.drawBufferExtension) - { - count = Math.min(count, 1); - } - - const activeTextures = []; - - for (let i = 0; i < count; i++) - { - const texture = framebuffer.colorTextures[i]; - - if (texture.texturePart) - { - this.renderer.texture.bind(texture.texture, 0); - - gl.framebufferTexture2D(gl.FRAMEBUFFER, - gl.COLOR_ATTACHMENT0 + i, - gl.TEXTURE_CUBE_MAP_NEGATIVE_X + texture.side, - texture.texture._glTextures[this.CONTEXT_UID].texture, - 0); - } - else - { - this.renderer.texture.bind(texture, 0); - - gl.framebufferTexture2D(gl.FRAMEBUFFER, - gl.COLOR_ATTACHMENT0 + i, - gl.TEXTURE_2D, - texture._glTextures[this.CONTEXT_UID].texture, - 0); - } - - activeTextures.push(gl.COLOR_ATTACHMENT0 + i); - } - - if (this.drawBufferExtension && activeTextures.length > 1) - { - this.drawBufferExtension.drawBuffersWEBGL(activeTextures); - } - - if (framebuffer.depthTexture) - { - const depthTextureExt = this.renderer.context.extensions.depthTexture; - - if (depthTextureExt) - { - const depthTexture = framebuffer.depthTexture; - - this.renderer.texture.bind(depthTexture, 0); - - gl.framebufferTexture2D(gl.FRAMEBUFFER, - gl.DEPTH_ATTACHMENT, - gl.TEXTURE_2D, - depthTexture._glTextures[this.CONTEXT_UID].texture, - 0); - } - } - - if (framebuffer.stencil || framebuffer.depth) - { - fbo.stencil = gl.createRenderbuffer(); - - gl.bindRenderbuffer(gl.RENDERBUFFER, fbo.stencil); - - // TODO.. this is depth AND stencil? - gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, fbo.stencil); - gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height); - // fbo.enableStencil(); - } - } -} diff --git a/packages/core/src/renderers/systems/MaskSystem.js b/packages/core/src/renderers/systems/MaskSystem.js deleted file mode 100644 index d9c4b1a..0000000 --- a/packages/core/src/renderers/systems/MaskSystem.js +++ /dev/null @@ -1,199 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import AlphaMaskFilter from '../filters/spriteMask/SpriteMaskFilter'; - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class MaskSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - // TODO - we don't need both! - this.scissor = false; - this.scissorData = null; - this.scissorRenderTarget = null; - - this.enableScissor = false; - - this.alphaMaskPool = []; - this.alphaMaskIndex = 0; - } - - /** - * Applies the Mask and adds it to the current filter stack. - * - * @param {PIXI.DisplayObject} target - Display Object to push the mask to - * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. - */ - push(target, maskData) - { - // TODO the root check means scissor rect will not - // be used on render textures more info here: - // https://github.com/pixijs/pixi.js/pull/3545 - - if (maskData.texture) - { - this.pushSpriteMask(target, maskData); - } - else if (this.enableScissor - && !this.scissor - && this.renderer._activeRenderTarget.root - && !this.renderer.stencil.stencilMaskStack.length - && maskData.isFastRect()) - { - const matrix = maskData.worldTransform; - - let rot = Math.atan2(matrix.b, matrix.a); - - // use the nearest degree! - rot = Math.round(rot * (180 / Math.PI)); - - if (rot % 90) - { - this.pushStencilMask(maskData); - } - else - { - this.pushScissorMask(target, maskData); - } - } - else - { - this.pushStencilMask(maskData); - } - } - - /** - * Removes the last mask from the mask stack and doesn't return it. - * - * @param {PIXI.DisplayObject} target - Display Object to pop the mask from - * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. - */ - pop(target, maskData) - { - if (maskData.texture) - { - this.popSpriteMask(target, maskData); - } - else if (this.enableScissor && !this.renderer.stencil.stencilMaskStack.length) - { - this.popScissorMask(target, maskData); - } - else - { - this.popStencilMask(target, maskData); - } - } - - /** - * Applies the Mask and adds it to the current filter stack. - * - * @param {PIXI.RenderTarget} target - Display Object to push the sprite mask to - * @param {PIXI.Sprite} maskData - Sprite to be used as the mask - */ - pushSpriteMask(target, maskData) - { - let alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; - - if (!alphaMaskFilter) - { - alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; - } - - alphaMaskFilter[0].resolution = this.renderer.resolution; - alphaMaskFilter[0].maskSprite = maskData; - - // TODO - may cause issues! - target.filterArea = maskData.getBounds(true); - - this.renderer.filterSystem.pushFilter(target, alphaMaskFilter); - - this.alphaMaskIndex++; - } - - /** - * Removes the last filter from the filter stack and doesn't return it. - * - */ - popSpriteMask() - { - this.renderer.filterSystem.popFilter(); - this.alphaMaskIndex--; - } - - /** - * Applies the Mask and adds it to the current filter stack. - * - * @param {PIXI.Sprite|PIXI.Graphics} maskData - The masking data. - */ - pushStencilMask(maskData) - { - this.renderer.batch.flush(); - this.renderer.stencil.pushStencil(maskData); - } - - /** - * Removes the last filter from the filter stack and doesn't return it. - * - */ - popStencilMask() - { - // this.renderer.currentRenderer.stop(); - this.renderer.stencil.popStencil(); - } - - /** - * - * @param {PIXI.DisplayObject} target - Display Object to push the mask to - * @param {PIXI.Graphics} maskData - The masking data. - */ - pushScissorMask(target, maskData) - { - maskData.renderable = true; - - const renderTarget = this.renderer._activeRenderTarget; - - const bounds = maskData.getBounds(); - - bounds.fit(renderTarget.size); - maskData.renderable = false; - - this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); - - const resolution = this.renderer.resolution; - - this.renderer.gl.scissor( - bounds.x * resolution, - (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, - bounds.width * resolution, - bounds.height * resolution - ); - - this.scissorRenderTarget = renderTarget; - this.scissorData = maskData; - this.scissor = true; - } - - /** - * - * - */ - popScissorMask() - { - this.scissorRenderTarget = null; - this.scissorData = null; - this.scissor = false; - - // must be scissor! - const gl = this.renderer.gl; - - gl.disable(gl.SCISSOR_TEST); - } -} diff --git a/packages/core/src/renderers/systems/ProjectionSystem.js b/packages/core/src/renderers/systems/ProjectionSystem.js deleted file mode 100644 index ec5efdb..0000000 --- a/packages/core/src/renderers/systems/ProjectionSystem.js +++ /dev/null @@ -1,73 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { Matrix } from '@pixi/math'; - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ - -export default class ProjectionSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this.projectionMatrix = new Matrix(); - } - - update(destinationFrame, sourceFrame, resolution, root) - { - this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; - this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; - - this.calculateProjection(this.destinationFrame, this.sourceFrame, resolution, root); - - this.renderer.globalUniforms.uniforms.projectionMatrix = this.projectionMatrix; - this.renderer.globalUniforms.update(); - } - - /** - * Updates the projection matrix based on a projection frame (which is a rectangle) - * - * @param {Rectangle} destinationFrame - The destination frame. - * @param {Rectangle} sourceFrame - The source frame. - */ - calculateProjection(destinationFrame, sourceFrame, resolution, root) - { - const pm = this.projectionMatrix; - - // I don't think we will need this line.. - // pm.identity(); - - if (!root) - { - pm.a = (1 / destinationFrame.width * 2) * resolution; - pm.d = (1 / destinationFrame.height * 2) * resolution; - - pm.tx = -1 - (sourceFrame.x * pm.a); - pm.ty = -1 - (sourceFrame.y * pm.d); - } - else - { - pm.a = (1 / destinationFrame.width * 2) * resolution; - pm.d = (-1 / destinationFrame.height * 2) * resolution; - - pm.tx = -1 - (sourceFrame.x * pm.a); - pm.ty = 1 - (sourceFrame.y * pm.d); - } - } - - /** - * Sets the transform of the active render target to the given matrix - * - * @param {PIXI.Matrix} matrix - The transformation matrix - */ - setTransform()// matrix) - { - // this._activeRenderTarget.transform = matrix; - } -} diff --git a/packages/core/src/renderers/systems/RenderTextureSystem.js b/packages/core/src/renderers/systems/RenderTextureSystem.js deleted file mode 100644 index a133ef1..0000000 --- a/packages/core/src/renderers/systems/RenderTextureSystem.js +++ /dev/null @@ -1,115 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { Rectangle } from '@pixi/math'; - -const tempRect = new Rectangle(); - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ - -export default class RenderTextureSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this.clearColor = renderer._backgroundColorRgba; - - // TODO moe this property somewhere else! - this.defaultMaskStack = []; - this.defaultFilterStack = [{}]; - - // empty render texture? - this.renderTexture = null; - - this.destinationFrame = new Rectangle(); - } - - bind(renderTexture, sourceFrame, destinationFrame) - { - // TODO - do we want this?? - if (this.renderTexture === renderTexture) return; - this.renderTexture = renderTexture; - - const renderer = this.renderer; - - if (renderTexture) - { - const baseTexture = renderTexture.baseTexture; - - if (!destinationFrame) - { - tempRect.width = baseTexture.realWidth; - tempRect.height = baseTexture.realHeight; - - destinationFrame = tempRect; - } - - if (!sourceFrame) - { - sourceFrame = destinationFrame; - } - - this.renderer.framebuffer.bind(baseTexture.frameBuffer, destinationFrame); - - this.renderer.projection.update(destinationFrame, sourceFrame, baseTexture.resolution, false); - this.renderer.stencil.setMaskStack(baseTexture.stencilMaskStack); - } - else - { - // TODO these validation checks happen deeper down.. - // thing they can be avoided.. - if (!destinationFrame) - { - tempRect.width = renderer.width; - tempRect.height = renderer.height; - - destinationFrame = tempRect; - } - - if (!sourceFrame) - { - sourceFrame = destinationFrame; - } - - renderer.framebuffer.bind(null, destinationFrame); - - // TODO store this.. - this.renderer.projection.update(destinationFrame, sourceFrame, this.renderer.resolution, true); - this.renderer.stencil.setMaskStack(this.defaultMaskStack); - } - - this.destinationFrame.copyFrom(destinationFrame); - } - - /** - * Erases the render texture and fills the drawing area with a colour - * - * @param {number} [clearColor] - The colour - * @return {PIXI.Renderer} Returns itself. - */ - clear(clearColor) - { - if (this.renderTexture) - { - clearColor = clearColor || this.renderTexture.baseTexture.clearColor; - } - else - { - clearColor = clearColor || this.clearColor; - } - - this.renderer.framebuffer.clear(clearColor[0], clearColor[1], clearColor[2], clearColor[3]); - } - - resize()// screenWidth, screenHeight) - { - // resize the root only! - this.bind(null); - } -} diff --git a/packages/core/src/renderers/systems/StateSystem.js b/packages/core/src/renderers/systems/StateSystem.js deleted file mode 100755 index d7f4caf..0000000 --- a/packages/core/src/renderers/systems/StateSystem.js +++ /dev/null @@ -1,288 +0,0 @@ -import mapWebGLBlendModesToPixi from '../utils/mapWebGLBlendModesToPixi'; -import WebGLSystem from './WebGLSystem'; -import WebGLState from '../State'; - -const BLEND = 0; -const OFFSET = 1; -const CULLING = 2; -const DEPTH_TEST = 3; -const WINDING = 4; - -/** - * A WebGL state machines - * - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class StateSystem extends WebGLSystem -{ - /** - * @param {WebGLRenderingContext} gl - The current WebGL rendering context - */ - constructor(renderer) - { - super(renderer); - - this.gl = null; - - this.maxAttribs = null; - - // check we have vao.. - this.nativeVaoExtension = null; - - this.attribState = null; - - this.stateId = 0; - this.polygonOffset = 0; - this.blendMode = 17; - - this.map = []; - - // map functions for when we set state.. - this.map[BLEND] = this.setBlend; - this.map[OFFSET] = this.setOffset; - this.map[CULLING] = this.setCullFace; - this.map[DEPTH_TEST] = this.setDepthTest; - this.map[WINDING] = this.setFrontFace; - - this.checks = []; - - this.defaultState = new WebGLState(); - this.defaultState.blend = true; - this.defaultState.depth = true; - } - - contextChange(gl) - { - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; - - this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); - - // check we have vao.. - this.nativeVaoExtension = ( - gl.getExtension('OES_vertex_array_object') - || gl.getExtension('MOZ_OES_vertex_array_object') - || gl.getExtension('WEBKIT_OES_vertex_array_object') - ); - - this.attribState = { - tempAttribState: new Array(this.maxAttribs), - attribState: new Array(this.maxAttribs), - }; - - this.blendModes = mapWebGLBlendModesToPixi(gl); - - this.setState(this.defaultState); - - this.reset(); - } - - /** - * Sets the current state - * - * @param {*} state - The state to set. - */ - setState(state) - { - state = state || this.defaultState; - - // TODO maybe to an object check? ( this.state === state )? - if (this.stateId !== state.data) - { - let diff = this.stateId ^ state.data; - let i = 0; - - // order from least to most common - while (diff) - { - if (diff & 1) - { - // state change! - this.map[i].call(this, !!(state.data & (1 << i))); - } - - diff = diff >> 1; - i++; - } - - this.stateId = state.data; - } - - // based on the above settings we check for specific modes.. - // for example if blend is active we check and set the blend modes - // or of polygon offset is active we check the poly depth. - for (let i = 0; i < this.checks.length; i++) - { - this.checks[i](this, state); - } - } - - /** - * Enables or disabled blending. - * - * @param {boolean} value - Turn on or off webgl blending. - */ - setBlend(value) - { - this.updateCheck(StateSystem.checkBlendMode, value); - - this.gl[value ? 'enable' : 'disable'](this.gl.BLEND); - } - - /** - * Enables or disable polygon offset fill - * - * @param {boolean} value - Turn on or off webgl polygon offset testing. - */ - setOffset(value) - { - this.gl[value ? 'enable' : 'disable'](this.gl.POLYGON_OFFSET_FILL); - } - - /** - * Sets whether to enable or disable depth test. - * - * @param {boolean} value - Turn on or off webgl depth testing. - */ - setDepthTest(value) - { - this.gl[value ? 'enable' : 'disable'](this.gl.DEPTH_TEST); - } - - /** - * Sets whether to enable or disable cull face. - * - * @param {boolean} value - Turn on or off webgl cull face. - */ - setCullFace(value) - { - this.gl[value ? 'enable' : 'disable'](this.gl.CULL_FACE); - } - - /** - * Sets the gl front face. - * - * @param {boolean} value - true is clockwise and false is counter-clockwise - */ - setFrontFace(value) - { - this.gl.frontFace(this.gl[value ? 'CW' : 'CCW']); - } - - /** - * Sets the blend mode. - * - * @param {number} value - The blend mode to set to. - */ - setBlendMode(value) - { - if (value === this.blendMode) - { - return; - } - - this.blendMode = value; - this.gl.blendFunc(this.blendModes[value][0], this.blendModes[value][1]); - } - - /** - * Sets the polygon offset. - * - * @param {number} value - the polygon offset - * @param {number} scale - the polygon offset scale - */ - setPolygonOffset(value, scale) - { - this.gl.polygonOffset(value, scale); - } - - /** - * Disables all the vaos in use - * - */ - resetAttributes() - { - for (let i = 0; i < this.attribState.tempAttribState.length; i++) - { - this.attribState.tempAttribState[i] = 0; - } - - for (let i = 0; i < this.attribState.attribState.length; i++) - { - this.attribState.attribState[i] = 0; - } - - // im going to assume one is always active for performance reasons. - for (let i = 1; i < this.maxAttribs; i++) - { - this.gl.disableVertexAttribArray(i); - } - } - - // used - /** - * Resets all the logic and disables the vaos - */ - reset() - { - // unbind any VAO if they exist.. - if (this.nativeVaoExtension) - { - this.nativeVaoExtension.bindVertexArrayOES(null); - } - - // reset all attributes.. - this.resetAttributes(); - - this.gl.pixelStorei(this.gl.UNPACK_FLIP_Y_WEBGL, false); - - this.setBlendMode(0); - - // TODO? - // this.setState(this.defaultState); - } - - /** - * checks to see which updates should be checked based on which settings have been activated - * for example if blend is enabled then we shold check the blend modes each time the state is changed - * or if poygon fill is activated then we need to check if the polygone offset changes. - * The idea is that we only check what we have too. - * - * @param {Function} func the checking function to add or remove - * @param {boolean} value should the check function be added or removed. - */ - updateCheck(func, value) - { - const index = this.checks.indexOf(func); - - if (value && index === -1) - { - this.checks.push(func); - } - else if (!value && index !== -1) - { - this.checks.splice(index, 1); - } - } - - /** - * A private little wrapper function that we call to check the blend mode. - * - * @static - * @private - * @param {PIXI.StateSystem} System the System to perform the state check on - * @param {PIXI.State} state the state that the blendMode will pulled from - */ - static checkBlendMode(system, state) - { - system.setBlendMode(state.blendMode); - } - - // TODO - add polygon offset? -} diff --git a/packages/core/src/renderers/systems/StencilSystem.js b/packages/core/src/renderers/systems/StencilSystem.js deleted file mode 100644 index 5d538cb..0000000 --- a/packages/core/src/renderers/systems/StencilSystem.js +++ /dev/null @@ -1,133 +0,0 @@ -import WebGLSystem from './WebGLSystem'; - -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class StencilSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - this.stencilMaskStack = []; - } - - /** - * Changes the mask stack that is used by this System. - * - * @param {PIXI.Graphics[]} stencilMaskStack - The mask stack - */ - setMaskStack(stencilMaskStack) - { - const gl = this.renderer.gl; - - if (stencilMaskStack.length !== this.stencilMaskStack.length) - { - if (stencilMaskStack.length === 0) - { - gl.disable(gl.STENCIL_TEST); - } - else - { - gl.enable(gl.STENCIL_TEST); - } - } - - this.stencilMaskStack = stencilMaskStack; - } - - /** - * Applies the Mask and adds it to the current stencil stack. @alvin - * - * @param {PIXI.Graphics} graphics - The mask - */ - pushStencil(graphics) - { - this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); - - // this.renderer._activeRenderTarget.attachStencilBuffer(); - - const gl = this.renderer.gl; - const prevMaskCount = this.stencilMaskStack.length; - - if (prevMaskCount === 0) - { - gl.enable(gl.STENCIL_TEST); - } - - this.stencilMaskStack.push(graphics); - - // Increment the refference stencil value where the new mask overlaps with the old ones. - gl.colorMask(false, false, false, false); - gl.stencilFunc(gl.EQUAL, prevMaskCount, this._getBitwiseMask()); - gl.stencilOp(gl.KEEP, gl.KEEP, gl.INCR); - this.renderer.plugins.graphics.render(graphics); - - this._useCurrent(); - } - - /** - * Removes the last mask from the stencil stack. @alvin - */ - popStencil() - { - this.renderer.batch.setObjectRenderer(this.renderer.plugins.graphics); - - const gl = this.renderer.gl; - const graphics = this.stencilMaskStack.pop(); - - if (this.stencilMaskStack.length === 0) - { - // the stack is empty! - gl.disable(gl.STENCIL_TEST); - gl.clear(gl.STENCIL_BUFFER_BIT); - gl.clearStencil(0); - } - else - { - // Decrement the refference stencil value where the popped mask overlaps with the other ones - gl.colorMask(false, false, false, false); - gl.stencilOp(gl.KEEP, gl.KEEP, gl.DECR); - this.renderer.plugins.graphics.render(graphics); - - this._useCurrent(); - } - } - - /** - * Setup renderer to use the current stencil data. - */ - _useCurrent() - { - const gl = this.renderer.gl; - - gl.colorMask(true, true, true, true); - gl.stencilFunc(gl.EQUAL, this.stencilMaskStack.length, this._getBitwiseMask()); - gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP); - } - - /** - * Fill 1s equal to the number of acitve stencil masks. - * - * @return {number} The bitwise mask. - */ - _getBitwiseMask() - { - return (1 << this.stencilMaskStack.length) - 1; - } - - /** - * Destroys the mask stack. - * - */ - destroy() - { - super.destroy(this); - - this.stencilMaskStack = null; - } -} diff --git a/packages/core/src/renderers/systems/WebGLSystem.js b/packages/core/src/renderers/systems/WebGLSystem.js deleted file mode 100644 index d3d5b31..0000000 --- a/packages/core/src/renderers/systems/WebGLSystem.js +++ /dev/null @@ -1,41 +0,0 @@ -/** - * @class - * @extends PIXI.systems.WebGLSystem - * @memberof PIXI.systems - */ -export default class WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this manager works for. - */ - constructor(renderer) - { - /** - * The renderer this manager works for. - * - * @member {PIXI.Renderer} - */ - this.renderer = renderer; - - this.renderer.runners.contextChange.add(this); - } - - /** - * Generic method called when there is a WebGL context change. - * - */ - contextChange() - { - // do some codes init! - } - - /** - * Generic destroy methods to be overridden by the subclass - * - */ - destroy() - { - this.renderer.runners.contextChange.remove(this); - this.renderer = null; - } -} diff --git a/packages/core/src/renderers/systems/filter/FilterSystem.js b/packages/core/src/renderers/systems/filter/FilterSystem.js deleted file mode 100644 index e615037..0000000 --- a/packages/core/src/renderers/systems/filter/FilterSystem.js +++ /dev/null @@ -1,431 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; - -import RenderTexture from '../../../textures/RenderTexture'; -import Quad from '../../utils/Quad'; -import QuadUv from '../../utils/QuadUv'; -import { Rectangle } from '@pixi/math'; -import * as filterTransforms from '../../filters/filterTransforms'; -import bitTwiddle from 'bit-twiddle'; -import UniformGroup from '../../../shader/UniformGroup'; -import { DRAW_MODES } from '../../../../../constants'; - -// -/** - * @ignore - * @class - */ -class FilterState -{ - /** - * - */ - constructor() - { - this.renderTexture = null; - this.sourceFrame = new Rectangle(); - this.destinationFrame = new Rectangle(); - this.filters = []; - this.target = null; - this.legacy = false; - this.resolution = 1; - } -} - -/** - * @class - * @memberof PIXI.systems - * @extends PIXI.systems.WebGLSystem - */ -export default class FilterSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - /** - * stores a bunch of PO2 textures used for filtering - * @type {Object} - */ - this.texturePool = {}; - - /** - * a pool for storing filter states, save us creating new ones each tick - * @type {Array} - */ - this.statePool = []; - - /** - * A very simple geometry used when drawing a filter effect to the screen - * @type {Quad} - */ - this.quad = new Quad(); - - this.quadUv = new QuadUv(); - - /** - * Temporary rect for maths - * @type {PIXI.Rectangle} - */ - this.tempRect = new Rectangle(); - - this.activeState = {}; - - /** - * this uniform group is attached to filter uniforms when used - * @type {UniformGroup} - */ - this.globalUniforms = new UniformGroup({ - sourceFrame: this.tempRect, - destinationFrame: this.tempRect, - - // legacy variables - filterArea: new Float32Array(4), - filterClamp: new Float32Array(4), - }, true); - } - - /** - * Adds a new filter to the System. - * - * @param {PIXI.DisplayObject} target - The target of the filter to render. - * @param {PIXI.Filter[]} filters - The filters to apply. - */ - push(target, filters) - { - const renderer = this.renderer; - const filterStack = this.renderer.renderTexture.defaultFilterStack; - const state = this.statePool.pop() || new FilterState(); - - let resolution = filters[0].resolution; - let padding = filters[0].padding; - let autoFit = filters[0].autoFit; - let legacy = filters[0].legacy; - - for (let i = 1; i < filters.length; i++) - { - const filter = filters[i]; - - // lets use the lowest resolution.. - resolution = Math.min(resolution, filter.resolution); - // and the largest amount of padding! - padding = Math.max(padding, filter.padding); - // only auto fit if all filters are autofit - autoFit = autoFit || filter.autoFit; - - legacy = legacy || filter.legacy; - } - - filterStack.push(state); - - state.resolution = resolution; - - state.legacy = legacy; - - // round to whole number based on resolution - // TODO move that to the shader too? - state.sourceFrame = target.filterArea || target.getBounds(true); - - state.sourceFrame.pad(padding); - - if (autoFit) - { - state.sourceFrame.fit(this.renderer.renderTexture.destinationFrame); - } - - state.sourceFrame.round(resolution); - - state.renderTexture = this.getPotFilterTexture(state.sourceFrame.width, state.sourceFrame.height, resolution); - state.filters = filters; - - state.destinationFrame.width = state.renderTexture.width; - state.destinationFrame.height = state.renderTexture.height; - - state.renderTexture.filterFrame = state.sourceFrame; - renderer.renderTexture.bind(state.renderTexture, state.sourceFrame);// /, state.destinationFrame); - renderer.renderTexture.clear(); - } - - /** - * Pops off the filter and applies it. - * - */ - pop() - { - const renderer = this.renderer; - const filterStack = renderer.renderTexture.defaultFilterStack; - const state = filterStack.pop(); - const filters = state.filters; - - this.activeState = state; - - const globalUniforms = this.globalUniforms.uniforms; - - globalUniforms.sourceFrame = state.sourceFrame; - globalUniforms.destinationFrame = state.destinationFrame; - globalUniforms.resolution = state.resolution; - - // only update the rect if its legacy.. - if (state.legacy) - { - const filterArea = globalUniforms.filterArea; - const filterClamp = globalUniforms.filterClamp; - - filterArea[0] = state.destinationFrame.width; - filterArea[1] = state.destinationFrame.height; - filterArea[2] = state.sourceFrame.x; - filterArea[3] = state.sourceFrame.y; - - filterClamp[0] = 0.5 / state.resolution / state.destinationFrame.width; - filterClamp[1] = 0.5 / state.resolution / state.destinationFrame.height; - filterClamp[2] = (state.sourceFrame.width - 0.5) / state.resolution / state.destinationFrame.width; - filterClamp[3] = (state.sourceFrame.height - 0.5) / state.resolution / state.destinationFrame.height; - } - - this.globalUniforms.update(); - - const lastState = filterStack[filterStack.length - 1]; - - if (filters.length === 1) - { - filters[0].apply(this, state.renderTexture, lastState.renderTexture, false, state); - - this.returnFilterTexture(state.renderTexture); - } - else - { - let flip = state.renderTexture; - let flop = this.getPotFilterTexture( - flip.width, - flip.height, - state.resolution - ); - - flop.filterFrame = flip.filterFrame; - - let i = 0; - - for (i = 0; i < filters.length - 1; ++i) - { - filters[i].apply(this, flip, flop, true, state); - - const t = flip; - - flip = flop; - flop = t; - } - - filters[i].apply(this, flip, lastState.renderTexture, false, state); - - this.returnFilterTexture(flip); - this.returnFilterTexture(flop); - } - - this.statePool.push(state); - } - - /** - * Draws a filter. - * - * @param {PIXI.Filter} filter - The filter to draw. - * @param {PIXI.RenderTarget} input - The input render target. - * @param {PIXI.RenderTarget} output - The target to output to. - * @param {boolean} clear - Should the output be cleared before rendering to it - */ - applyFilter(filter, input, output, clear) - { - const renderer = this.renderer; - - renderer.renderTexture.bind(output, output ? output.filterFrame : null); - - if (clear) - { - // gl.disable(gl.SCISSOR_TEST); - renderer.renderTexture.clear(); - // gl.enable(gl.SCISSOR_TEST); - } - - // set the uniforms.. - filter.uniforms.uSampler = input; - filter.uniforms.filterGlobals = this.globalUniforms; - - // TODO make it so that the order of this does not matter.. - // because it does at the moment cos of global uniforms. - // they need to get resynced - - renderer.state.setState(filter.state); - renderer.shader.bind(filter); - - if (filter.legacy) - { - this.quadUv.map(input._frame, input.filterFrame); - - renderer.geometry.bind(this.quadUv); - renderer.geometry.draw(DRAW_MODES.TRIANGLES); - } - else - { - renderer.geometry.bind(this.quad); - renderer.geometry.draw(DRAW_MODES.TRIANGLE_STRIP); - } - } - - /** - * Calculates the mapped matrix. - * - * TODO playing around here.. this is temporary - (will end up in the shader) - * this returns a matrix that will normalise map filter cords in the filter to screen space - * - * @param {PIXI.Matrix} outputMatrix - the matrix to output to. - * @return {PIXI.Matrix} The mapped matrix. - */ - calculateScreenSpaceMatrix(outputMatrix) - { - const currentState = this.activeState; - - return filterTransforms.calculateScreenSpaceMatrix( - outputMatrix, - currentState.sourceFrame, - currentState.destinationFrame - ); - } - - /** - * This will map the filter coord so that a texture can be used based on the transform of a sprite - * - * @param {PIXI.Matrix} outputMatrix - The matrix to output to. - * @param {PIXI.Sprite} sprite - The sprite to map to. - * @return {PIXI.Matrix} The mapped matrix. - */ - calculateSpriteMatrix(outputMatrix, sprite) - { - const currentState = this.activeState; - - return filterTransforms.calculateSpriteMatrix( - outputMatrix, - currentState.sourceFrame, - currentState.destinationFrame, - sprite - ); - } - - /** - * Destroys this Filter System. - * - * @param {boolean} [contextLost=false] context was lost, do not free shaders - * - */ - destroy(contextLost = false) - { - if (!contextLost) - { - this.emptyPool(); - } - else - { - this.texturePool = {}; - } - } - - /** - * Gets a Power-of-Two render texture. - * - * TODO move to a seperate class could be on renderer? - * also - could cause issue with multiple contexts? - * - * @private - * @param {WebGLRenderingContext} gl - The webgl rendering context - * @param {number} minWidth - The minimum width of the render target. - * @param {number} minHeight - The minimum height of the render target. - * @param {number} resolution - The resolution of the render target. - * @return {PIXI.RenderTarget} The new render target. - */ - getPotFilterTexture(minWidth, minHeight, resolution) - { - minWidth = bitTwiddle.nextPow2(minWidth); - minHeight = bitTwiddle.nextPow2(minHeight); - resolution = resolution || 1; - - const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); - - if (!this.texturePool[key]) - { - this.texturePool[key] = []; - } - - let renderTexture = this.texturePool[key].pop(); - - if (!renderTexture) - { - // temporary bypass cache.. - // internally - this will cause a texture to be bound.. - renderTexture = RenderTexture.create({ - width: minWidth, - height: minHeight, - resolution, - }); - } - - return renderTexture; - } - - /** - * Gets extra render texture to use inside current filter - * - * @param {number} resolution resolution of the renderTexture - * @returns {PIXI.RenderTexture} - */ - getFilterTexture(resolution) - { - const rt = this.activeState.renderTexture; - - const filterTexture = this.getPotFilterTexture(rt.width, rt.height, resolution || rt.baseTexture.resolution); - - filterTexture.filterFrame = rt.filterFrame; - - return filterTexture; - } - - /** - * Frees a render texture back into the pool. - * - * @param {PIXI.RenderTarget} renderTexture - The renderTarget to free - */ - returnFilterTexture(renderTexture) - { - renderTexture.filterFrame = null; - - const base = renderTexture.baseTexture; - - const minWidth = base.width; - const minHeight = base.height; - - const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); - - this.texturePool[key].push(renderTexture); - } - - /** - * Empties the texture pool. - * - */ - emptyPool() - { - for (const i in this.texturePool) - { - const textures = this.texturePool[i]; - - if (textures) - { - for (let j = 0; j < textures.length; j++) - { - textures[j].destroy(true); - } - } - } - - this.texturePool = {}; - } -} diff --git a/packages/core/src/renderers/systems/geometry/GLBuffer.js b/packages/core/src/renderers/systems/geometry/GLBuffer.js deleted file mode 100644 index 0477e8a..0000000 --- a/packages/core/src/renderers/systems/geometry/GLBuffer.js +++ /dev/null @@ -1,9 +0,0 @@ -export default class GLBuffer -{ - constructor(buffer) - { - this.buffer = buffer; - this.updateID = -1; - this.byteLength = -1; - } -} diff --git a/packages/core/src/renderers/systems/geometry/GeometrySystem.js b/packages/core/src/renderers/systems/geometry/GeometrySystem.js deleted file mode 100644 index 26c8e9e..0000000 --- a/packages/core/src/renderers/systems/geometry/GeometrySystem.js +++ /dev/null @@ -1,388 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; -import GLBuffer from './GLBuffer'; - -const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class GeometrySystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this._activeGeometry = null; - this._activeVao = null; - - this.hasVao = true; - this.hasInstance = true; - } - - /** - * Sets up the renderer context and necessary buffers. - * - * @private - */ - contextChange() - { - const gl = this.gl = this.renderer.gl; - - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - - // webgl2 - if (!gl.createVertexArray) - { - // webgl 1! - let nativeVaoExtension = this.renderer.context.extensions.vertexArrayObject; - - if (this.renderer.legacy) - { - nativeVaoExtension = null; - } - - if (nativeVaoExtension) - { - gl.createVertexArray = () => - nativeVaoExtension.createVertexArrayOES(); - - gl.bindVertexArray = (vao) => - nativeVaoExtension.bindVertexArrayOES(vao); - - gl.deleteVertexArray = (vao) => - nativeVaoExtension.deleteVertexArrayOES(vao); - } - else - { - this.hasVao = false; - gl.createVertexArray = () => - { - // empty - }; - - gl.bindVertexArray = () => - { - // empty - }; - - gl.deleteVertexArray = () => - { - // empty - }; - } - } - - if (!gl.vertexAttribDivisor) - { - const instanceExt = gl.getExtension('ANGLE_instanced_arrays'); - - if (instanceExt) - { - gl.vertexAttribDivisor = (a, b) => - instanceExt.vertexAttribDivisorANGLE(a, b); - - gl.drawElementsInstanced = (a, b, c, d, e) => - instanceExt.drawElementsInstancedANGLE(a, b, c, d, e); - - gl.drawArraysInstanced = (a, b, c, d) => - instanceExt.drawArraysInstancedANGLE(a, b, c, d); - } - else - { - this.hasInstance = false; - } - } - } - - /** - * Binds geometry so that is can be drawn. Creating a Vao if required - * @private - * @param {PIXI.Geometry} geometry instance of geometry to bind - */ - bind(geometry, shader) - { - shader = shader || this.renderer.shader.shader; - - const gl = this.gl; - - // not sure the best way to address this.. - // currently different shaders require different VAOs for the same geometry - // Still mulling over the best way to solve this one.. - // will likely need to modify the shader attribute locations at run time! - let vaos = geometry.glVertexArrayObjects[this.CONTEXT_UID]; - - if (!vaos) - { - geometry.glVertexArrayObjects[this.CONTEXT_UID] = vaos = {}; - } - - const vao = vaos[shader.program.id] || this.initGeometryVao(geometry, shader.program); - - this._activeGeometry = geometry; - - if (this._activeVao !== vao) - { - this._activeVao = vao; - - if (this.hasVao) - { - gl.bindVertexArray(vao); - } - else - { - this.activateVao(geometry, shader.program); - } - } - - // TODO - optimise later! - // don't need to loop through if nothing changed! - // maybe look to add an 'autoupdate' to geometry? - this.updateBuffers(); - } - - reset() - { - this.unbind(); - } - - updateBuffers() - { - const geometry = this._activeGeometry; - const gl = this.gl; - - for (let i = 0; i < geometry.buffers.length; i++) - { - const buffer = geometry.buffers[i]; - - const glBuffer = buffer._glBuffers[this.CONTEXT_UID]; - - if (buffer._updateID !== glBuffer.updateID) - { - glBuffer.updateID = buffer._updateID; - - // TODO can cache this on buffer! maybe added a getter / setter? - const type = buffer.index ? gl.ELEMENT_ARRAY_BUFFER : gl.ARRAY_BUFFER; - const drawType = buffer.static ? gl.STATIC_DRAW : gl.DYNAMIC_DRAW; - - gl.bindBuffer(type, glBuffer.buffer); - - if (glBuffer.byteLength >= buffer.data.byteLength) - { - // offset is always zero for now! - gl.bufferSubData(type, 0, buffer.data); - } - else - { - gl.bufferData(type, buffer.data, drawType); - } - } - } - } - - checkCompatability(geometry, program) - { - // geometry must have at least all the attributes that the shader requires. - const geometryAttributes = geometry.attributes; - const shaderAttributes = program.attributeData; - - for (const j in shaderAttributes) - { - if (!geometryAttributes[j]) - { - throw new Error(`shader and geometry incompatible, geometry missing the "${j}" attribute`); - } - } - } - - /** - * Creates a Vao with the same structure as the geometry and stores it on the geometry. - * @private - * @param {PIXI.Geometry} geometry instance of geometry to to generate Vao for - */ - initGeometryVao(geometry, program) - { - this.checkCompatability(geometry, program); - - const gl = this.gl; - const CONTEXT_UID = this.CONTEXT_UID; - const buffers = geometry.buffers; - const attributes = geometry.attributes; - - const tempStride = {}; - const tempStart = {}; - - for (const j in buffers) - { - tempStride[j] = 0; - tempStart[j] = 0; - } - - for (const j in attributes) - { - if (!attributes[j].size && program.attributeData[j]) - { - attributes[j].size = program.attributeData[j].size; - } - - tempStride[attributes[j].buffer] += attributes[j].size * byteSizeMap[attributes[j].type]; - } - - for (const j in attributes) - { - const attribute = attributes[j]; - const attribSize = attribute.size; - - if (attribute.stride === undefined) - { - if (tempStride[attribute.buffer] === attribSize * byteSizeMap[attribute.type]) - { - attribute.stride = 0; - } - else - { - attribute.stride = tempStride[attribute.buffer]; - } - } - - if (attribute.start === undefined) - { - attribute.start = tempStart[attribute.buffer]; - - tempStart[attribute.buffer] += attribSize * byteSizeMap[attribute.type]; - } - } - - // first update - and create the buffers! - // only create a gl buffer if it actually gets - for (let i = 0; i < buffers.length; i++) - { - const buffer = buffers[i]; - - if (!buffer._glBuffers[CONTEXT_UID]) - { - buffer._glBuffers[CONTEXT_UID] = new GLBuffer(gl.createBuffer()); - } - } - - // TODO - maybe make this a data object? - // lets wait to see if we need to first! - const vao = gl.createVertexArray(); - - gl.bindVertexArray(vao); - - this.activateVao(geometry, program); - - gl.bindVertexArray(null); - - // add it to the cache! - geometry.glVertexArrayObjects[this.CONTEXT_UID][program.id] = vao; - - return vao; - } - - activateVao(geometry, program) - { - const gl = this.gl; - const CONTEXT_UID = this.CONTEXT_UID; - const buffers = geometry.buffers; - const attributes = geometry.attributes; - - if (geometry.indexBuffer) - { - // first update the index buffer if we have one.. - gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, geometry.indexBuffer._glBuffers[CONTEXT_UID].buffer); - } - - let lastBuffer = null; - - // add a new one! - for (const j in attributes) - { - const attribute = attributes[j]; - const buffer = buffers[attribute.buffer]; - const glBuffer = buffer._glBuffers[CONTEXT_UID]; - - if (program.attributeData[j]) - { - if (lastBuffer !== glBuffer) - { - gl.bindBuffer(gl.ARRAY_BUFFER, glBuffer.buffer); - - lastBuffer = glBuffer; - } - - const location = program.attributeData[j].location; - - // TODO introduce state again - // we can optimise this for older devices that have no VAOs - gl.enableVertexAttribArray(location); - - gl.vertexAttribPointer(location, - attribute.size, - attribute.type || gl.FLOAT, - attribute.normalized, - attribute.stride, - attribute.start); - - if (attribute.instance) - { - // TODO calculate instance count based of this... - if (this.hasInstance) - { - gl.vertexAttribDivisor(location, 1); - } - else - { - throw new Error('geometry error, GPU Instancing is not supported on this device'); - } - } - } - } - } - - draw(type, size, start, instanceCount) - { - const gl = this.gl; - const geometry = this._activeGeometry; - - // TODO.. this should not change so maybe cache the function? - - if (geometry.indexBuffer) - { - if (geometry.instanced) - { - /* eslint-disable max-len */ - gl.drawElementsInstanced(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2, instanceCount || 1); - /* eslint-enable max-len */ - } - else - { - gl.drawElements(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2); - } - } - else - if (geometry.instanced) - { - // TODO need a better way to calculate size.. - gl.drawArraysInstanced(type, start, size || geometry.getSize(), instanceCount || 1); - } - else - { - gl.drawArrays(type, start, size || geometry.getSize()); - } - - return this; - } - - unbind() - { - this.gl.bindVertexArray(null); - this._activeVao = null; - this._activeGeometry = null; - } -} diff --git a/packages/core/src/renderers/systems/geometry/setVertexAttribArrays.js b/packages/core/src/renderers/systems/geometry/setVertexAttribArrays.js deleted file mode 100644 index 23b6959..0000000 --- a/packages/core/src/renderers/systems/geometry/setVertexAttribArrays.js +++ /dev/null @@ -1,54 +0,0 @@ -// var GL_MAP = {}; - -/** - * @param gl {WebGLRenderingContext} The current WebGL context - * @param attribs {*} - * @param state {*} - */ -export default function setVertexAttribArrays(gl, attribs, state) -{ - let i; - - if (state) - { - const tempAttribState = state.tempAttribState; - const attribState = state.attribState; - - for (i = 0; i < tempAttribState.length; i++) - { - tempAttribState[i] = false; - } - - // set the new attribs - for (i = 0; i < attribs.length; i++) - { - tempAttribState[attribs[i].attribute.location] = true; - } - - for (i = 0; i < attribState.length; i++) - { - if (attribState[i] !== tempAttribState[i]) - { - attribState[i] = tempAttribState[i]; - - if (state.attribState[i]) - { - gl.enableVertexAttribArray(i); - } - else - { - gl.disableVertexAttribArray(i); - } - } - } - } - else - { - for (i = 0; i < attribs.length; i++) - { - const attrib = attribs[i]; - - gl.enableVertexAttribArray(attrib.attribute.location); - } - } -} diff --git a/packages/core/src/renderers/systems/index.js b/packages/core/src/renderers/systems/index.js deleted file mode 100644 index c843f03..0000000 --- a/packages/core/src/renderers/systems/index.js +++ /dev/null @@ -1,6 +0,0 @@ -/** - * Systems are individual components to the Renderer pipeline. - * @namespace PIXI.systems - */ -export { default as WebGLSystem } from './WebGLSystem'; -export { default as FilterSystem } from './filter/FilterSystem'; diff --git a/packages/core/src/renderers/systems/shader/GLProgram.js b/packages/core/src/renderers/systems/shader/GLProgram.js deleted file mode 100644 index 92980ca..0000000 --- a/packages/core/src/renderers/systems/shader/GLProgram.js +++ /dev/null @@ -1,43 +0,0 @@ -/** - * Helper class to create a webGL Program - * - * @class - * @private - */ -export default class GLProgram -{ - constructor(program, uniformData) - { - /** - * The shader program - * - * @member {WebGLProgram} - */ - this.program = program; - - /** - * holds the uniform data which contains uniform locations - * and current uniform values used for caching and preventing undeed GPU commands - * @type {Object} - */ - this.uniformData = uniformData; - - /** - * uniformGroups holds the various upload functions for the shader. Each uniform group - * and program have a unique upload function generated. - * @type {Object} - */ - this.uniformGroups = {}; - } - - /** - * Destroys this program - * TODO - */ - destroy() - { - this.uniformData = null; - this.uniformGroups = null; - this.program = null; - } -} diff --git a/packages/core/src/renderers/systems/shader/ShaderSystem.js b/packages/core/src/renderers/systems/shader/ShaderSystem.js deleted file mode 100644 index 6cf57b6..0000000 --- a/packages/core/src/renderers/systems/shader/ShaderSystem.js +++ /dev/null @@ -1,170 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; -import GLProgram from './GLProgram'; -import generateUniformsSync from '../../../shader/generateUniformsSync'; -import defaultValue from '../../../shader/defaultValue'; -import compileProgram from './shader/compileProgram'; - -let UID = 0; - -/** - * Helper class to create a webGL Texture - * - * @class - * @memberof PIXI - */ -export default class ShaderSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - A reference to the current renderer - */ - constructor(renderer) - { - super(renderer); - - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = null; - - this.shader = null; - this.program = null; - - this.id = UID++; - } - - contextChange(gl) - { - this.gl = gl; - } - - /** - * Changes the current shader to the one given in parameter - * - * @param {PIXI.Shader} shader - the new shader - * @param {boolean} dontSync - false if the shader should automatically sync its uniforms. - * @returns {PIXI.glCore.glProgram} the glProgram that belongs to the shader. - */ - bind(shader, dontSync) - { - shader.uniforms.globals = this.renderer.globalUniforms; - - const program = shader.program; - const glProgram = program.glPrograms[this.renderer.CONTEXT_UID] || this.generateShader(shader); - - this.shader = shader; - - // TODO - some current pixi plugins bypass this.. so it not safe to use yet.. - if (this.program !== program) - { - this.program = program; - this.gl.useProgram(glProgram.program); - } - - if (!dontSync) - { - this.syncUniformGroup(shader.uniformGroup); - } - - return glProgram; - } - - /** - * Uploads the uniforms values to the currently bound shader. - * - * @param {object} uniforms - the uniforms valiues that be applied to the current shader - */ - setUniforms(uniforms) - { - const shader = this.shader.program; - const glProgram = shader.glPrograms[this.renderer.CONTEXT_UID]; - - shader.syncUniforms(glProgram.uniformData, uniforms, this.renderer); - } - - syncUniformGroup(group) - { - const glProgram = this.getglProgram(); - - if (!group.static || group.dirtyId !== glProgram.uniformGroups[group.id]) - { - glProgram.uniformGroups[group.id] = group.dirtyId; - const syncFunc = group.syncUniforms[this.shader.program.id] || this.createSyncGroups(group); - - syncFunc(glProgram.uniformData, group.uniforms, this.renderer); - } - } - - createSyncGroups(group) - { - group.syncUniforms[this.shader.program.id] = generateUniformsSync(group, this.shader.program.uniformData); - - return group.syncUniforms[this.shader.program.id]; - } - - /** - * Returns the underlying GLShade rof the currently bound shader. - * This can be handy for when you to have a little more control over the setting of your uniforms. - * - * @return {PIXI.glCore.Shader} the glProgram for the currently bound Shader for this context - */ - getglProgram() - { - if (this.shader) - { - return this.shader.program.glPrograms[this.renderer.CONTEXT_UID]; - } - - return null; - } - - /** - * Generates a glProgram verion of the Shader provided. - * - * @private - * @param {PIXI.Shader} shader the shader that the glProgram will be based on. - * @return {PIXI.glCore.glProgram} A shiney new glProgram - */ - generateShader(shader) - { - const gl = this.gl; - - const program = shader.program; - - const attribMap = {}; - - for (const i in program.attributeData) - { - attribMap[i] = program.attributeData[i].location; - } - - const shaderProgram = compileProgram(gl, program.vertexSrc, program.fragmentSrc, attribMap); - const uniformData = {}; - - for (const i in program.uniformData) - { - const data = program.uniformData[i]; - - uniformData[i] = { - location: gl.getUniformLocation(shaderProgram, i), - value: defaultValue(data.type, data.size), - }; - } - - const glProgram = new GLProgram(shaderProgram, uniformData); - - program.glPrograms[this.renderer.CONTEXT_UID] = glProgram; - - return glProgram; - } - - /** - * Destroys this System and removes all its textures - */ - destroy() - { - // TODO implement destroy method for ShaderSystem - this.destroyed = true; - } -} diff --git a/packages/core/src/renderers/systems/shader/shader/compileProgram.js b/packages/core/src/renderers/systems/shader/shader/compileProgram.js deleted file mode 100644 index 8c6ccb9..0000000 --- a/packages/core/src/renderers/systems/shader/shader/compileProgram.js +++ /dev/null @@ -1,78 +0,0 @@ -/** - * @method compileProgram - * @memberof PIXI.glCore.shader - * @param gl {WebGLRenderingContext} The current WebGL context {WebGLProgram} - * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. - * @param fragmentSrc {string|string[]} The fragment shader source as an array of strings. - * @param attributeLocations {Object} An attribute location map that lets you manually set the attribute locations - * @return {WebGLProgram} the shader program - */ -export default function compileProgram(gl, vertexSrc, fragmentSrc, attributeLocations) -{ - const glVertShader = compileShader(gl, gl.VERTEX_SHADER, vertexSrc); - const glFragShader = compileShader(gl, gl.FRAGMENT_SHADER, fragmentSrc); - - let program = gl.createProgram(); - - gl.attachShader(program, glVertShader); - gl.attachShader(program, glFragShader); - - // optionally, set the attributes manually for the program rather than letting WebGL decide.. - if (attributeLocations) - { - for (const i in attributeLocations) - { - gl.bindAttribLocation(program, attributeLocations[i], i); - } - } - - gl.linkProgram(program); - - // if linking fails, then log and cleanup - if (!gl.getProgramParameter(program, gl.LINK_STATUS)) - { - console.error('Pixi.js Error: Could not initialize shader.'); - console.error('gl.VALIDATE_STATUS', gl.getProgramParameter(program, gl.VALIDATE_STATUS)); - console.error('gl.getError()', gl.getError()); - - // if there is a program info log, log it - if (gl.getProgramInfoLog(program) !== '') - { - console.warn('Pixi.js Warning: gl.getProgramInfoLog()', gl.getProgramInfoLog(program)); - } - - gl.deleteProgram(program); - program = null; - } - - // clean up some shaders - gl.deleteShader(glVertShader); - gl.deleteShader(glFragShader); - - return program; -} - -/** - * @private - * @param gl {WebGLRenderingContext} The current WebGL context {WebGLProgram} - * @param type {Number} the type, can be either VERTEX_SHADER or FRAGMENT_SHADER - * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. - * @return {WebGLShader} the shader - */ -function compileShader(gl, type, src) -{ - const shader = gl.createShader(type); - - gl.shaderSource(shader, src); - gl.compileShader(shader); - - if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) - { - console.warn(src); - console.error(gl.getShaderInfoLog(shader)); - - return null; - } - - return shader; -} diff --git a/packages/core/src/renderers/systems/shader/shader/defaultValue.js b/packages/core/src/renderers/systems/shader/shader/defaultValue.js deleted file mode 100644 index 97290bd..0000000 --- a/packages/core/src/renderers/systems/shader/shader/defaultValue.js +++ /dev/null @@ -1,79 +0,0 @@ -/** - * @method defaultValue - * @memberof PIXI.glCore.shader - * @param type {String} Type of value - * @param size {Number} - */ -export default function defaultValue(type, size) -{ - switch (type) - { - case 'float': - return 0; - - case 'vec2': - return new Float32Array(2 * size); - - case 'vec3': - return new Float32Array(3 * size); - - case 'vec4': - return new Float32Array(4 * size); - - case 'int': - case 'sampler2D': - case 'sampler2DArray': - return 0; - - case 'ivec2': - return new Int32Array(2 * size); - - case 'ivec3': - return new Int32Array(3 * size); - - case 'ivec4': - return new Int32Array(4 * size); - - case 'bool': - return false; - - case 'bvec2': - - return booleanArray(2 * size); - - case 'bvec3': - return booleanArray(3 * size); - - case 'bvec4': - return booleanArray(4 * size); - - case 'mat2': - return new Float32Array([1, 0, - 0, 1]); - - case 'mat3': - return new Float32Array([1, 0, 0, - 0, 1, 0, - 0, 0, 1]); - - case 'mat4': - return new Float32Array([1, 0, 0, 0, - 0, 1, 0, 0, - 0, 0, 1, 0, - 0, 0, 0, 1]); - } - - return null; -} - -function booleanArray(size) -{ - const array = new Array(size); - - for (let i = 0; i < array.length; i++) - { - array[i] = false; - } - - return array; -} diff --git a/packages/core/src/renderers/systems/shader/shader/index.js b/packages/core/src/renderers/systems/shader/shader/index.js deleted file mode 100644 index ba7a317..0000000 --- a/packages/core/src/renderers/systems/shader/shader/index.js +++ /dev/null @@ -1,8 +0,0 @@ -/** - * @namespace PIXI.glCore.shader - */ -export { default as compileProgram } from './compileProgram'; -export { default as defaultValue } from './defaultValue'; -export { default as setPrecision } from './setPrecision'; -export { default as mapSize } from './mapSize'; -export { default as mapType } from './mapType'; diff --git a/packages/core/src/renderers/systems/shader/shader/mapSize.js b/packages/core/src/renderers/systems/shader/shader/mapSize.js deleted file mode 100644 index b681554..0000000 --- a/packages/core/src/renderers/systems/shader/shader/mapSize.js +++ /dev/null @@ -1,33 +0,0 @@ -const GLSL_TO_SIZE = { - float: 1, - vec2: 2, - vec3: 3, - vec4: 4, - - int: 1, - ivec2: 2, - ivec3: 3, - ivec4: 4, - - bool: 1, - bvec2: 2, - bvec3: 3, - bvec4: 4, - - mat2: 4, - mat3: 9, - mat4: 16, - - sampler2D: 1, -}; - -/** - * @method mapSize - * @memberof PIXI.glCore.shader - * @param type {String} - * @return {Number} - */ -export default function mapSize(type) -{ - return GLSL_TO_SIZE[type]; -} diff --git a/packages/core/src/renderers/systems/shader/shader/mapType.js b/packages/core/src/renderers/systems/shader/shader/mapType.js deleted file mode 100644 index d288b7c..0000000 --- a/packages/core/src/renderers/systems/shader/shader/mapType.js +++ /dev/null @@ -1,45 +0,0 @@ -let GL_TABLE = null; - -const GL_TO_GLSL_TYPES = { - FLOAT: 'float', - FLOAT_VEC2: 'vec2', - FLOAT_VEC3: 'vec3', - FLOAT_VEC4: 'vec4', - - INT: 'int', - INT_VEC2: 'ivec2', - INT_VEC3: 'ivec3', - INT_VEC4: 'ivec4', - - BOOL: 'bool', - BOOL_VEC2: 'bvec2', - BOOL_VEC3: 'bvec3', - BOOL_VEC4: 'bvec4', - - FLOAT_MAT2: 'mat2', - FLOAT_MAT3: 'mat3', - FLOAT_MAT4: 'mat4', - - SAMPLER_2D: 'sampler2D', - SAMPLER_CUBE: 'samplerCube', - SAMPLER_2D_ARRAY: 'sampler2DArray', -}; - -export default function mapType(gl, type) -{ - if (!GL_TABLE) - { - const typeNames = Object.keys(GL_TO_GLSL_TYPES); - - GL_TABLE = {}; - - for (let i = 0; i < typeNames.length; ++i) - { - const tn = typeNames[i]; - - GL_TABLE[gl[tn]] = GL_TO_GLSL_TYPES[tn]; - } - } - - return GL_TABLE[type]; -} diff --git a/packages/core/src/renderers/systems/shader/shader/setPrecision.js b/packages/core/src/renderers/systems/shader/shader/setPrecision.js deleted file mode 100644 index e2c1f34..0000000 --- a/packages/core/src/renderers/systems/shader/shader/setPrecision.js +++ /dev/null @@ -1,18 +0,0 @@ -/** - * Sets the float precision on the shader. If the precision is already present this function will do nothing - * @param {string} src the shader source - * @param {string} precision The float precision of the shader. Options are 'lowp', 'mediump' or 'highp'. - * - * @return {string} modified shader source - */ -export default function setPrecision(src, precision) -{ - src = src.trim(); - - if (src.substring(0, 9) !== 'precision' && src.substring(0, 1) !== '#') - { - return `precision ${precision} float;\n${src}`; - } - - return src; -} diff --git a/packages/core/src/renderers/systems/textures/GLTexture.js b/packages/core/src/renderers/systems/textures/GLTexture.js deleted file mode 100644 index c3fb8dc..0000000 --- a/packages/core/src/renderers/systems/textures/GLTexture.js +++ /dev/null @@ -1,33 +0,0 @@ -export default class GLTexture -{ - constructor(texture) - { - /** - * The WebGL texture - * - * @member {WebGLTexture} - */ - this.texture = texture; - - this.width = -1; - this.height = -1; - - /** - * Texture contents dirty flag - * @member {number} - */ - this.dirtyId = -1; - - /** - * Texture style dirty flag - * @type {number} - */ - this.dirtyStyleId = -1; - - /** - * Whether mip levels has to be generated - * @type {boolean} - */ - this.mipmap = false; - } -} diff --git a/packages/core/src/renderers/systems/textures/TextureGCSystem.js b/packages/core/src/renderers/systems/textures/TextureGCSystem.js deleted file mode 100644 index b089938..0000000 --- a/packages/core/src/renderers/systems/textures/TextureGCSystem.js +++ /dev/null @@ -1,110 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; -import { GC_MODES } from '@pixi/constants'; -import { settings } from '@pixi/settings'; - -/** - * TextureGarbageCollector. This class manages the GPU and ensures that it does not get clogged - * up with textures that are no longer being used. - * - * @class - * @memberof PIXI - */ -export default class TextureGCSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - this.count = 0; - this.checkCount = 0; - this.maxIdle = settings.GC_MAX_IDLE; - this.checkCountMax = settings.GC_MAX_CHECK_COUNT; - this.mode = settings.GC_MODE; - } - - /** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ - postrender() - { - this.count++; - - if (this.mode === GC_MODES.MANUAL) - { - return; - } - - this.checkCount++; - - if (this.checkCount > this.checkCountMax) - { - this.checkCount = 0; - - this.run(); - } - } - - /** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ - run() - { - const tm = this.renderer.texture; - const managedTextures = tm.managedTextures; - let wasRemoved = false; - - for (let i = 0; i < managedTextures.length; i++) - { - const texture = managedTextures[i]; - - // only supports non generated textures at the moment! - if (!texture.frameBuffer && this.count - texture.touched > this.maxIdle) - { - tm.destroyTexture(texture, true); - managedTextures[i] = null; - wasRemoved = true; - } - } - - if (wasRemoved) - { - let j = 0; - - for (let i = 0; i < managedTextures.length; i++) - { - if (managedTextures[i] !== null) - { - managedTextures[j++] = managedTextures[i]; - } - } - - managedTextures.length = j; - } - } - - /** - * Removes all the textures within the specified displayObject and its children from the GPU - * - * @param {PIXI.DisplayObject} displayObject - the displayObject to remove the textures from. - */ - unload(displayObject) - { - const tm = this.renderer.textureSystem; - - // only destroy non generated textures - if (displayObject._texture && displayObject._texture._glRenderTargets) - { - tm.destroyTexture(displayObject._texture); - } - - for (let i = displayObject.children.length - 1; i >= 0; i--) - { - this.unload(displayObject.children[i]); - } - } -} diff --git a/packages/core/src/renderers/systems/textures/TextureSystem.js b/packages/core/src/renderers/systems/textures/TextureSystem.js deleted file mode 100644 index ccc5de9..0000000 --- a/packages/core/src/renderers/systems/textures/TextureSystem.js +++ /dev/null @@ -1,281 +0,0 @@ -import WebGLSystem from '../WebGLSystem'; -import GLTexture from './GLTexture'; -import { removeItems } from '@pixi/utils'; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class TextureSystem extends WebGLSystem -{ - /** - * @param {PIXI.Renderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - // TODO set to max textures... - this.boundTextures = [ - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - ]; - - this.currentLocation = -1; - - this.managedTextures = []; - } - - /** - * Sets up the renderer context and necessary buffers. - * - * @private - */ - contextChange() - { - const gl = this.gl = this.renderer.gl; - - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - - // TODO move this.. to a nice make empty textures class.. - this.emptyTextures = {}; - - const emptyTexture2D = new GLTexture(gl.createTexture()); - - gl.bindTexture(gl.TEXTURE_2D, emptyTexture2D.texture); - gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array(4)); - - this.emptyTextures[gl.TEXTURE_2D] = emptyTexture2D; - this.emptyTextures[gl.TEXTURE_CUBE_MAP] = new GLTexture(gl.createTexture()); - - gl.bindTexture(gl.TEXTURE_CUBE_MAP, this.emptyTextures[gl.TEXTURE_CUBE_MAP].texture); - - let i; - - for (i = 0; i < 6; i++) - { - gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, null); - } - - gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_MAG_FILTER, gl.LINEAR); - gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_MIN_FILTER, gl.LINEAR); - - for (i = 0; i < this.boundTextures.length; i++) - { - this.bind(null, i); - } - } - - bind(texture, location) - { - const gl = this.gl; - - location = location || 0; - - if (this.currentLocation !== location) - { - this.currentLocation = location; - gl.activeTexture(gl.TEXTURE0 + location); - } - - if (texture) - { - texture = texture.baseTexture || texture; - - if (texture.valid) - { - texture.touched = this.renderer.textureGC.count; - - const glTexture = texture._glTextures[this.CONTEXT_UID] || this.initTexture(texture); - - gl.bindTexture(texture.target, glTexture.texture); - - if (glTexture.dirtyId !== texture.dirtyId) - { - this.updateTexture(texture); - } - - this.boundTextures[location] = texture; - } - } - else - { - gl.bindTexture(gl.TEXTURE_2D, this.emptyTextures[gl.TEXTURE_2D].texture); - this.boundTextures[location] = null; - } - } - - unbind(texture) - { - const gl = this.gl; - - for (let i = 0; i < this.boundTextures.length; i++) - { - if (this.boundTextures[i] === texture) - { - if (this.currentLocation !== i) - { - gl.activeTexture(gl.TEXTURE0 + i); - this.currentLocation = i; - } - - gl.bindTexture(gl.TEXTURE_2D, this.emptyTextures[texture.target].texture); - this.boundTextures[i] = null; - } - } - } - - initTexture(texture) - { - const glTexture = new GLTexture(this.gl.createTexture()); - - // guarentee an update.. - glTexture.dirtyId = -1; - - texture._glTextures[this.CONTEXT_UID] = glTexture; - - this.managedTextures.push(texture); - texture.on('dispose', this.destroyTexture, this); - - return glTexture; - } - - updateTexture(texture) - { - const glTexture = texture._glTextures[this.CONTEXT_UID]; - const renderer = this.renderer; - - if (texture.resource && texture.resource.upload(renderer, texture, glTexture)) - { - // texture is uploaded, dont do anything! - } - else - { - // default, renderTexture-like logic - const width = texture.realWidth; - const height = texture.realHeight; - const gl = renderer.gl; - - if (glTexture.width !== width - || glTexture.height !== height - || glTexture.dirtyId < 0) - { - glTexture.width = width; - glTexture.height = height; - - gl.texImage2D(texture.target, 0, - texture.format, - width, - height, - 0, - texture.format, - texture.type, - null); - } - } - - // lets only update what changes.. - if (texture.dirtyStyleId !== glTexture.dirtyStyleId) - { - this.updateTextureStyle(texture); - } - glTexture.dirtyId = texture.dirtyId; - } - - /** - * Deletes the texture from WebGL - * - * @param {PIXI.BaseTexture|PIXI.Texture} texture - the texture to destroy - * @param {boolean} [skipRemove=false] - Whether to skip removing the texture from the TextureManager. - */ - destroyTexture(texture, skipRemove) - { - const gl = this.gl; - - texture = texture.baseTexture || texture; - - if (texture._glTextures[this.renderer.CONTEXT_UID]) - { - this.unbind(texture); - - gl.deleteTexture(texture._glTextures[this.renderer.CONTEXT_UID].texture); - texture.off('dispose', this.destroyTexture, this); - - delete texture._glTextures[this.renderer.CONTEXT_UID]; - - if (!skipRemove) - { - const i = this.managedTextures.indexOf(texture); - - if (i !== -1) - { - removeItems(this.managedTextures, i, 1); - } - } - } - } - - updateTextureStyle(texture) - { - const glTexture = texture._glTextures[this.CONTEXT_UID]; - - glTexture.mipmap = texture.mipmap && texture.isPowerOfTwo; - if (!glTexture) - { - return; - } - - if (texture.resource && texture.resource.style(this.renderer, texture, glTexture)) - { - // style is set, dont do anything! - } - else - { - this.setStyle(texture, glTexture); - } - - glTexture.dirtyStyleId = texture.dirtyStyleId; - } - - setStyle(texture, glTexture) - { - const gl = this.gl; - - if (glTexture.mipmap) - { - gl.generateMipmap(texture.target); - } - - gl.texParameteri(texture.target, gl.TEXTURE_WRAP_S, texture.wrapMode); - gl.texParameteri(texture.target, gl.TEXTURE_WRAP_T, texture.wrapMode); - - if (glTexture.mipmap) - { - /* eslint-disable max-len */ - gl.texParameteri(texture.target, gl.TEXTURE_MIN_FILTER, texture.scaleMode ? gl.LINEAR_MIPMAP_LINEAR : gl.NEAREST_MIPMAP_NEAREST); - /* eslint-disable max-len */ - } - else - { - gl.texParameteri(texture.target, gl.TEXTURE_MIN_FILTER, texture.scaleMode ? gl.LINEAR : gl.NEAREST); - } - - gl.texParameteri(texture.target, gl.TEXTURE_MAG_FILTER, texture.scaleMode ? gl.LINEAR : gl.NEAREST); - } -} diff --git a/packages/core/src/renderers/utils/ObjectRenderer.js b/packages/core/src/renderers/utils/ObjectRenderer.js deleted file mode 100644 index f67a273..0000000 --- a/packages/core/src/renderers/utils/ObjectRenderer.js +++ /dev/null @@ -1,48 +0,0 @@ -import WebGLSystem from '../systems/WebGLSystem'; - -/** - * Base for a common object renderer that can be used as a system renderer plugin. - * - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class ObjectRenderer extends WebGLSystem -{ - /** - * Starts the renderer and sets the shader - * - */ - start() - { - // set the shader.. - } - - /** - * Stops the renderer - * - */ - stop() - { - this.flush(); - } - - /** - * Stub method for rendering content and emptying the current batch. - * - */ - flush() - { - // flush! - } - - /** - * Renders an object - * - * @param {PIXI.DisplayObject} object - The object to render. - */ - render(object) // eslint-disable-line no-unused-vars - { - // render the object - } -} diff --git a/packages/core/src/renderers/utils/Quad.js b/packages/core/src/renderers/utils/Quad.js deleted file mode 100644 index 5c1517b..0000000 --- a/packages/core/src/renderers/utils/Quad.js +++ /dev/null @@ -1,26 +0,0 @@ -import Geometry from '../../geometry/Geometry'; - -/** - * Helper class to create a quad - * - * @class - * @memberof PIXI - */ -export default class Quad extends Geometry -{ - /** - * just a quad - */ - constructor() - { - super(); - - this.addAttribute('aVertexPosition', [ - 0, 0, - 1, 0, - 1, 1, - 0, 1, - ]) - .addIndex([0, 1, 3, 2]); - } -} diff --git a/packages/core/src/renderers/utils/QuadUv.js b/packages/core/src/renderers/utils/QuadUv.js deleted file mode 100644 index 97bda89..0000000 --- a/packages/core/src/renderers/utils/QuadUv.js +++ /dev/null @@ -1,103 +0,0 @@ -import Geometry from '../../geometry/Geometry'; -import Buffer from '../../geometry/Buffer'; - -/** - * Helper class to create a quad with uvs like in v4 - * - * @class - * @memberof PIXI - */ -export default class QuadUv extends Geometry -{ - constructor() - { - super(); - - /** - * An array of vertices - * - * @member {Float32Array} - */ - this.vertices = new Float32Array([ - -1, -1, - 1, -1, - 1, 1, - -1, 1, - ]); - - /** - * The Uvs of the quad - * - * @member {Float32Array} - */ - this.uvs = new Float32Array([ - 0, 0, - 1, 0, - 1, 1, - 0, 1, - ]); - - this.vertexBuffer = new Buffer(this.vertices); - this.uvBuffer = new Buffer(this.uvs); - - this.addAttribute('aVertexPosition', this.vertexBuffer) - .addAttribute('aTextureCoord', this.uvBuffer) - .addIndex([0, 1, 2, 0, 2, 3]); - } - - /** - * Maps two Rectangle to the quad. - * - * @param {PIXI.Rectangle} targetTextureFrame - the first rectangle - * @param {PIXI.Rectangle} destinationFrame - the second rectangle - * @return {PIXI.Quad} Returns itself. - */ - map(targetTextureFrame, destinationFrame) - { - let x = 0; // destinationFrame.x / targetTextureFrame.width; - let y = 0; // destinationFrame.y / targetTextureFrame.height; - - this.uvs[0] = x; - this.uvs[1] = y; - - this.uvs[2] = x + (destinationFrame.width / targetTextureFrame.width); - this.uvs[3] = y; - - this.uvs[4] = x + (destinationFrame.width / targetTextureFrame.width); - this.uvs[5] = y + (destinationFrame.height / targetTextureFrame.height); - - this.uvs[6] = x; - this.uvs[7] = y + (destinationFrame.height / targetTextureFrame.height); - - x = destinationFrame.x; - y = destinationFrame.y; - - this.vertices[0] = x; - this.vertices[1] = y; - - this.vertices[2] = x + destinationFrame.width; - this.vertices[3] = y; - - this.vertices[4] = x + destinationFrame.width; - this.vertices[5] = y + destinationFrame.height; - - this.vertices[6] = x; - this.vertices[7] = y + destinationFrame.height; - - this.invalidate(); - - return this; - } - - /** - * legacy upload method, just marks buffers dirty - * @returns {PIXI.QuadUv} Returns itself. - */ - invalidate() - { - this.vertexBuffer._updateID++; - this.uvBuffer._updateID++; - - return this; - } -} diff --git a/packages/core/src/renderers/utils/checkMaxIfStatmentsInShader.js b/packages/core/src/renderers/utils/checkMaxIfStatmentsInShader.js deleted file mode 100644 index 8047c96..0000000 --- a/packages/core/src/renderers/utils/checkMaxIfStatmentsInShader.js +++ /dev/null @@ -1,58 +0,0 @@ -const fragTemplate = [ - 'precision mediump float;', - 'void main(void){', - 'float test = 0.1;', - '%forloop%', - 'gl_FragColor = vec4(0.0);', - '}', -].join('\n'); - -export default function checkMaxIfStatmentsInShader(maxIfs, gl) -{ - if (maxIfs === 0) - { - throw new Error('Invalid value of `0` passed to `checkMaxIfStatementsInShader`'); - } - - const shader = gl.createShader(gl.FRAGMENT_SHADER); - - while (true) // eslint-disable-line no-constant-condition - { - const fragmentSrc = fragTemplate.replace(/%forloop%/gi, generateIfTestSrc(maxIfs)); - - gl.shaderSource(shader, fragmentSrc); - gl.compileShader(shader); - - if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) - { - maxIfs = (maxIfs / 2) | 0; - } - else - { - // valid! - break; - } - } - - return maxIfs; -} - -function generateIfTestSrc(maxIfs) -{ - let src = ''; - - for (let i = 0; i < maxIfs; ++i) - { - if (i > 0) - { - src += '\nelse '; - } - - if (i < maxIfs - 1) - { - src += `if(test == ${i}.0){}`; - } - } - - return src; -} diff --git a/packages/core/src/renderers/utils/mapWebGLBlendModesToPixi.js b/packages/core/src/renderers/utils/mapWebGLBlendModesToPixi.js deleted file mode 100644 index 10ffe4d..0000000 --- a/packages/core/src/renderers/utils/mapWebGLBlendModesToPixi.js +++ /dev/null @@ -1,47 +0,0 @@ -import { BLEND_MODES } from '@pixi/constants'; - -/** - * Maps gl blend combinations to WebGL. - * - * @memberof PIXI - * @function mapWebGLBlendModesToPixi - * @private - * @param {WebGLRenderingContext} gl - The rendering context. - * @param {string[]} [array=[]] - The array to output into. - * @return {string[]} Mapped modes. - */ -export default function mapWebGLBlendModesToPixi(gl, array = []) -{ - // TODO - premultiply alpha would be different. - // add a boolean for that! - array[BLEND_MODES.NORMAL] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.ADD] = [gl.ONE, gl.DST_ALPHA]; - array[BLEND_MODES.MULTIPLY] = [gl.DST_COLOR, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.SCREEN] = [gl.ONE, gl.ONE_MINUS_SRC_COLOR]; - array[BLEND_MODES.OVERLAY] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.DARKEN] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.LIGHTEN] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.COLOR_DODGE] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.COLOR_BURN] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.HARD_LIGHT] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.SOFT_LIGHT] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.DIFFERENCE] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.EXCLUSION] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.HUE] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.SATURATION] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.COLOR] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.LUMINOSITY] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.NONE] = [0, 0]; - - // not-premultiplied blend modes - array[BLEND_MODES.NORMAL_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.ADD_NPM] = [gl.SRC_ALPHA, gl.DST_ALPHA, gl.ONE, gl.DST_ALPHA]; - array[BLEND_MODES.SCREEN_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_COLOR, gl.ONE, gl.ONE_MINUS_SRC_COLOR]; - - // not-premultiplied blend modes - array[BLEND_MODES.NORMAL_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.ADD_NPM] = [gl.SRC_ALPHA, gl.DST_ALPHA, gl.ONE, gl.DST_ALPHA]; - array[BLEND_MODES.SCREEN_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_COLOR, gl.ONE, gl.ONE_MINUS_SRC_COLOR]; - - return array; -} diff --git a/packages/core/src/shader/GLProgram.js b/packages/core/src/shader/GLProgram.js new file mode 100644 index 0000000..92980ca --- /dev/null +++ b/packages/core/src/shader/GLProgram.js @@ -0,0 +1,43 @@ +/** + * Helper class to create a webGL Program + * + * @class + * @private + */ +export default class GLProgram +{ + constructor(program, uniformData) + { + /** + * The shader program + * + * @member {WebGLProgram} + */ + this.program = program; + + /** + * holds the uniform data which contains uniform locations + * and current uniform values used for caching and preventing undeed GPU commands + * @type {Object} + */ + this.uniformData = uniformData; + + /** + * uniformGroups holds the various upload functions for the shader. Each uniform group + * and program have a unique upload function generated. + * @type {Object} + */ + this.uniformGroups = {}; + } + + /** + * Destroys this program + * TODO + */ + destroy() + { + this.uniformData = null; + this.uniformGroups = null; + this.program = null; + } +} diff --git a/packages/core/src/shader/Program.js b/packages/core/src/shader/Program.js index 4f9971e..4b4007a 100644 --- a/packages/core/src/shader/Program.js +++ b/packages/core/src/shader/Program.js @@ -1,6 +1,11 @@ -import * as shaderUtils from '../renderers/systems/shader/shader'; +// import * as from '../systems/shader/shader'; +import { setPrecision, + defaultValue, + compileProgram, + mapSize, + mapType, + getTestContext } from './utils'; import { ProgramCache } from '@pixi/utils'; -import getTestContext from './getTestContext'; import defaultFragment from './defaultProgram.frag'; import defaultVertex from './defaultProgram.vert'; import { settings } from '@pixi/settings'; @@ -34,8 +39,8 @@ */ this.fragmentSrc = fragmentSrc || Program.defaultFragmentSrc; - this.vertexSrc = shaderUtils.setPrecision(this.vertexSrc, settings.PRECISION_VERTEX); - this.fragmentSrc = shaderUtils.setPrecision(this.fragmentSrc, settings.PRECISION_FRAGMENT); + this.vertexSrc = setPrecision(this.vertexSrc, settings.PRECISION_VERTEX); + this.fragmentSrc = setPrecision(this.fragmentSrc, settings.PRECISION_FRAGMENT); // currently this does not extract structs only default types this.extractData(this.vertexSrc, this.fragmentSrc); @@ -62,7 +67,7 @@ if (gl) { - const program = shaderUtils.compileProgram(gl, vertexSrc, fragmentSrc); + const program = compileProgram(gl, vertexSrc, fragmentSrc); this.attributeData = this.getAttributeData(program, gl); this.uniformData = this.getUniformData(program, gl); @@ -95,13 +100,13 @@ for (let i = 0; i < totalAttributes; i++) { const attribData = gl.getActiveAttrib(program, i); - const type = shaderUtils.mapType(gl, attribData.type); + const type = mapType(gl, attribData.type); /*eslint-disable */ const data = { type: type, name: attribData.name, - size: shaderUtils.mapSize(type), + size: mapSize(type), location: 0, }; /* eslint-enable */ @@ -145,14 +150,14 @@ const name = uniformData.name.replace(/\[.*?\]/, ''); const isArray = uniformData.name.match(/\[.*?\]/, ''); - const type = shaderUtils.mapType(gl, uniformData.type); + const type = mapType(gl, uniformData.type); /*eslint-disable */ uniforms[name] = { type: type, size: uniformData.size, isArray:isArray, - value: shaderUtils.defaultValue(type, uniformData.size), + value: defaultValue(type, uniformData.size), }; /* eslint-enable */ } diff --git a/packages/core/src/shader/ShaderSystem.js b/packages/core/src/shader/ShaderSystem.js new file mode 100644 index 0000000..40b8af8 --- /dev/null +++ b/packages/core/src/shader/ShaderSystem.js @@ -0,0 +1,171 @@ +import System from '../System'; +import GLProgram from './GLProgram'; +import { generateUniformsSync, + defaultValue, + compileProgram } from './utils'; + +let UID = 0; + +/** + * Helper class to create a webGL Texture + * + * @class + * @memberof PIXI.systems + * @extends PIXI.System + */ +export default class ShaderSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - A reference to the current renderer + */ + constructor(renderer) + { + super(renderer); + + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = null; + + this.shader = null; + this.program = null; + + this.id = UID++; + } + + contextChange(gl) + { + this.gl = gl; + } + + /** + * Changes the current shader to the one given in parameter + * + * @param {PIXI.Shader} shader - the new shader + * @param {boolean} dontSync - false if the shader should automatically sync its uniforms. + * @returns {PIXI.glCore.glProgram} the glProgram that belongs to the shader. + */ + bind(shader, dontSync) + { + shader.uniforms.globals = this.renderer.globalUniforms; + + const program = shader.program; + const glProgram = program.glPrograms[this.renderer.CONTEXT_UID] || this.generateShader(shader); + + this.shader = shader; + + // TODO - some current pixi plugins bypass this.. so it not safe to use yet.. + if (this.program !== program) + { + this.program = program; + this.gl.useProgram(glProgram.program); + } + + if (!dontSync) + { + this.syncUniformGroup(shader.uniformGroup); + } + + return glProgram; + } + + /** + * Uploads the uniforms values to the currently bound shader. + * + * @param {object} uniforms - the uniforms valiues that be applied to the current shader + */ + setUniforms(uniforms) + { + const shader = this.shader.program; + const glProgram = shader.glPrograms[this.renderer.CONTEXT_UID]; + + shader.syncUniforms(glProgram.uniformData, uniforms, this.renderer); + } + + syncUniformGroup(group) + { + const glProgram = this.getglProgram(); + + if (!group.static || group.dirtyId !== glProgram.uniformGroups[group.id]) + { + glProgram.uniformGroups[group.id] = group.dirtyId; + const syncFunc = group.syncUniforms[this.shader.program.id] || this.createSyncGroups(group); + + syncFunc(glProgram.uniformData, group.uniforms, this.renderer); + } + } + + createSyncGroups(group) + { + group.syncUniforms[this.shader.program.id] = generateUniformsSync(group, this.shader.program.uniformData); + + return group.syncUniforms[this.shader.program.id]; + } + + /** + * Returns the underlying GLShade rof the currently bound shader. + * This can be handy for when you to have a little more control over the setting of your uniforms. + * + * @return {PIXI.glCore.Shader} the glProgram for the currently bound Shader for this context + */ + getglProgram() + { + if (this.shader) + { + return this.shader.program.glPrograms[this.renderer.CONTEXT_UID]; + } + + return null; + } + + /** + * Generates a glProgram verion of the Shader provided. + * + * @private + * @param {PIXI.Shader} shader the shader that the glProgram will be based on. + * @return {PIXI.glCore.glProgram} A shiney new glProgram + */ + generateShader(shader) + { + const gl = this.gl; + + const program = shader.program; + + const attribMap = {}; + + for (const i in program.attributeData) + { + attribMap[i] = program.attributeData[i].location; + } + + const shaderProgram = compileProgram(gl, program.vertexSrc, program.fragmentSrc, attribMap); + const uniformData = {}; + + for (const i in program.uniformData) + { + const data = program.uniformData[i]; + + uniformData[i] = { + location: gl.getUniformLocation(shaderProgram, i), + value: defaultValue(data.type, data.size), + }; + } + + const glProgram = new GLProgram(shaderProgram, uniformData); + + program.glPrograms[this.renderer.CONTEXT_UID] = glProgram; + + return glProgram; + } + + /** + * Destroys this System and removes all its textures + */ + destroy() + { + // TODO implement destroy method for ShaderSystem + this.destroyed = true; + } +} diff --git a/packages/core/src/shader/defaultValue.js b/packages/core/src/shader/defaultValue.js deleted file mode 100644 index 847a069..0000000 --- a/packages/core/src/shader/defaultValue.js +++ /dev/null @@ -1,78 +0,0 @@ -function booleanArray(size) -{ - const array = new Array(size); - - for (let i = 0; i < array.length; i++) - { - array[i] = false; - } - - return array; -} - -/** - * @class - * @memberof PIXI.glCore.shader - * @param type {String} Type of value - * @param size {Number} - */ -export default function defaultValue(type, size) -{ - switch (type) - { - case 'float': - return 0; - - case 'vec2': - return new Float32Array(2 * size); - - case 'vec3': - return new Float32Array(3 * size); - - case 'vec4': - return new Float32Array(4 * size); - - case 'int': - case 'sampler2D': - return 0; - - case 'ivec2': - return new Int32Array(2 * size); - - case 'ivec3': - return new Int32Array(3 * size); - - case 'ivec4': - return new Int32Array(4 * size); - - case 'bool': - return false; - - case 'bvec2': - - return booleanArray(2 * size); - - case 'bvec3': - return booleanArray(3 * size); - - case 'bvec4': - return booleanArray(4 * size); - - case 'mat2': - return new Float32Array([1, 0, - 0, 1]); - - case 'mat3': - return new Float32Array([1, 0, 0, - 0, 1, 0, - 0, 0, 1]); - - case 'mat4': - return new Float32Array([1, 0, 0, 0, - 0, 1, 0, 0, - 0, 0, 1, 0, - 0, 0, 0, 1]); - } - - return 0; -} diff --git a/packages/core/src/shader/extractUniformsFromSrc.js b/packages/core/src/shader/extractUniformsFromSrc.js deleted file mode 100644 index 3d02c0a..0000000 --- a/packages/core/src/shader/extractUniformsFromSrc.js +++ /dev/null @@ -1,57 +0,0 @@ -import defaultValue from './defaultValue'; - -export default function extractUniformsFromSrc(vertexSrc, fragmentSrc, mask) -{ - const vertUniforms = extractUniformsFromString(vertexSrc, mask); - const fragUniforms = extractUniformsFromString(fragmentSrc, mask); - - return Object.assign(vertUniforms, fragUniforms); -} - -function extractUniformsFromString(string) -{ - const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); - - const uniforms = {}; - let nameSplit; - - // clean the lines a little - remove extra spaces / teabs etc - // then split along ';' - const lines = string.replace(/\s+/g, ' ') - .split(/\s*;\s*/); - - // loop through.. - for (let i = 0; i < lines.length; i++) - { - const line = lines[i].trim(); - - if (line.indexOf('uniform') > -1) - { - const splitLine = line.split(' '); - const type = splitLine[1]; - - let name = splitLine[2]; - let size = 1; - - if (name.indexOf('[') > -1) - { - // array! - nameSplit = name.split(/\[|]/); - name = nameSplit[0]; - size *= Number(nameSplit[1]); - } - - if (!name.match(maskRegex)) - { - uniforms[name] = { - value: defaultValue(type, size), - dirtyId: 0, - name, - type, - }; - } - } - } - - return uniforms; -} diff --git a/packages/core/src/shader/generateUniformsSync.js b/packages/core/src/shader/generateUniformsSync.js deleted file mode 100644 index 98a357f..0000000 --- a/packages/core/src/shader/generateUniformsSync.js +++ /dev/null @@ -1,231 +0,0 @@ -// cv = CachedValue -// v = value -// ud = uniformData -// uv = uniformValue -// l = loaction -const GLSL_TO_SINGLE_SETTERS_CACHED = { - - float: ` - if(cv !== v) - { - cv.v = v; - gl.uniform1f(location, v) - }`, - - vec2: ` - if(cv[0] !== v[0] || cv[1] !== v[1]) - { - cv[0] = v[0]; - cv[1] = v[1]; - gl.uniform2f(location, v[0], v[1]) - }`, - - vec3: ` - if(cv[0] !== v[0] || cv[1] !== v[1] || cv[2] !== v[2]) - { - cv[0] = v[0]; - cv[1] = v[1]; - cv[2] = v[2]; - - gl.uniform3f(location, v[0], v[1], v[2]) - }`, - - vec4: 'gl.uniform4f(location, v[0], v[1], v[2], v[3])', - - int: 'gl.uniform1i(location, v)', - ivec2: 'gl.uniform2i(location, v[0], v[1])', - ivec3: 'gl.uniform3i(location, v[0], v[1], v[2])', - ivec4: 'gl.uniform4i(location, v[0], v[1], v[2], v[3])', - - bool: 'gl.uniform1i(location, v)', - bvec2: 'gl.uniform2i(location, v[0], v[1])', - bvec3: 'gl.uniform3i(location, v[0], v[1], v[2])', - bvec4: 'gl.uniform4i(location, v[0], v[1], v[2], v[3])', - - mat2: 'gl.uniformMatrix2fv(location, false, v)', - mat3: 'gl.uniformMatrix3fv(location, false, v)', - mat4: 'gl.uniformMatrix4fv(location, false, v)', - - sampler2D: 'gl.uniform1i(location, v)', - samplerCube: 'gl.uniform1i(location, v)', - sampler2DArray: 'gl.uniform1i(location, v)', -}; - -const GLSL_TO_ARRAY_SETTERS = { - - float: `gl.uniform1fv(location, v)`, - - vec2: `gl.uniform2fv(location, v)`, - vec3: `gl.uniform3fv(location, v)`, - vec4: 'gl.uniform4fv(location, v)', - - mat4: 'gl.uniformMatrix4fv(location, false, v)', - mat3: 'gl.uniformMatrix3fv(location, false, v)', - mat2: 'gl.uniformMatrix2fv(location, false, v)', - - int: 'gl.uniform1iv(location, v)', - ivec2: 'gl.uniform2iv(location, v)', - ivec3: 'gl.uniform3iv(location, v)', - ivec4: 'gl.uniform4iv(location, v)', - - bool: 'gl.uniform1iv(location, v)', - bvec2: 'gl.uniform2iv(location, v)', - bvec3: 'gl.uniform3iv(location, v)', - bvec4: 'gl.uniform4iv(location, v)', - - sampler2D: 'gl.uniform1iv(location, v)', - samplerCube: 'gl.uniform1iv(location, v)', - sampler2DArray: 'gl.uniform1iv(location, v)', -}; - -export default function generateUniformsSync(group, uniformData) -{ - let textureCount = 0; - let func = `var v = null; - var cv = null - var gl = renderer.gl`; - - for (const i in group.uniforms) - { - const data = uniformData[i]; - - if (!data) - { - if (group.uniforms[i].group) - { - func += ` - renderer.shader.syncUniformGroup(uv.${i}); - `; - } - - continue; - } - - // TODO && uniformData[i].value !== 0 <-- do we still need this? - if (data.type === 'float' && data.size === 1) - { - func += ` - if(uv.${i} !== ud.${i}.value) - { - ud.${i}.value = uv.${i} - gl.uniform1f(ud.${i}.location, uv.${i}) - }\n`; - } - /* eslint-disable max-len */ - else if ((data.type === 'sampler2D' || data.type === 'samplerCube' || data.type === 'sampler2DArray') && data.size === 1 && !data.isArray) - /* eslint-disable max-len */ - { - func += ` - renderer.texture.bind(uv.${i}, ${textureCount}); - - if(ud.${i}.value !== ${textureCount}) - { - ud.${i}.value = ${textureCount}; - gl.uniform1i(ud.${i}.location, ${textureCount});\n; // eslint-disable-line max-len - }\n`; - - textureCount++; - } - else if (data.type === 'mat3' && data.size === 1) - { - if (group.uniforms[i].a !== undefined) - { - // TODO and some smart caching dirty ids here! - func += ` - gl.uniformMatrix3fv(ud.${i}.location, false, uv.${i}.toArray(true)); - \n`; - } - else - { - func += ` - gl.uniformMatrix3fv(ud.${i}.location, false, uv.${i}); - \n`; - } - } - else if (data.type === 'vec2' && data.size === 1) - { - // TODO - do we need both here? - // maybe we can get away with only using points? - if (group.uniforms[i].x !== undefined) - { - func += ` - cv = ud.${i}.value; - v = uv.${i}; - - if(cv[0] !== v.x || cv[1] !== v.y) - { - cv[0] = v.x; - cv[1] = v.y; - gl.uniform2f(ud.${i}.location, v.x, v.y); - }\n`; - } - else - { - func += ` - cv = ud.${i}.value; - v = uv.${i}; - - if(cv[0] !== v[0] || cv[1] !== v[1]) - { - cv[0] = v[0]; - cv[1] = v[1]; - gl.uniform2f(ud.${i}.location, v[0], v[1]); - } - \n`; - } - } - else if (data.type === 'vec4' && data.size === 1) - { - // TODO - do we need both here? - // maybe we can get away with only using points? - if (group.uniforms[i].width !== undefined) - { - func += ` - cv = ud.${i}.value; - v = uv.${i}; - - if(cv[0] !== v.x || cv[1] !== v.y || cv[2] !== v.width || cv[3] !== v.height) - { - cv[0] = v.x; - cv[1] = v.y; - cv[2] = v.width; - cv[3] = v.height; - gl.uniform4f(ud.${i}.location, v.x, v.y, v.width, v.height) - }\n`; - } - else - { - func += ` - cv = ud.${i}.value; - v = uv.${i}; - - if(cv[0] !== v[0] || cv[1] !== v[1] || cv[2] !== v[2] || cv[3] !== v[3]) - { - cv[0] = v[0]; - cv[1] = v[1]; - cv[2] = v[2]; - cv[3] = v[3]; - - gl.uniform4f(ud.${i}.location, v[0], v[1], v[2], v[3]) - } - \n`; - } - } - else - { - const templateType = (data.size === 1) ? GLSL_TO_SINGLE_SETTERS_CACHED : GLSL_TO_ARRAY_SETTERS; - - const template = templateType[data.type].replace('location', `ud.${i}.location`); - - func += ` - cv = ud.${i}.value; - v = uv.${i}; - ${template};\n`; - } - } - - // console.log(' --------------- ') - // console.log(func); - - return new Function('ud', 'uv', 'renderer', func); // eslint-disable-line no-new-func -} diff --git a/packages/core/src/shader/getTestContext.js b/packages/core/src/shader/getTestContext.js deleted file mode 100644 index ea174cc..0000000 --- a/packages/core/src/shader/getTestContext.js +++ /dev/null @@ -1,48 +0,0 @@ -import { settings } from '@pixi/settings'; - -let context = null; - -/** - * returns a little webGL context to use for program inspection. - * - * @static - * @private - * @returns {webGL-context} a gl context to test with - */ -export default function getTestContext() -{ - if (!context) - { - const canvas = document.createElement('canvas'); - - let gl; - - if (settings.PREFER_WEBGL_2) - { - gl = canvas.getContext('webgl2', {}); - } - - if (!gl) - { - gl = canvas.getContext('webgl', {}) - || canvas.getContext('experimental-webgl', {}); - - if (!gl) - { - // fail, not able to get a context - throw new Error('This browser does not support webGL. Try using the canvas renderer'); - } - else - { - // for shader testing.. - gl.getExtension('WEBGL_draw_buffers'); - } - } - - context = gl; - - return gl; - } - - return context; -} diff --git a/packages/core/src/shader/utils/checkMaxIfStatmentsInShader.js b/packages/core/src/shader/utils/checkMaxIfStatmentsInShader.js new file mode 100644 index 0000000..8047c96 --- /dev/null +++ b/packages/core/src/shader/utils/checkMaxIfStatmentsInShader.js @@ -0,0 +1,58 @@ +const fragTemplate = [ + 'precision mediump float;', + 'void main(void){', + 'float test = 0.1;', + '%forloop%', + 'gl_FragColor = vec4(0.0);', + '}', +].join('\n'); + +export default function checkMaxIfStatmentsInShader(maxIfs, gl) +{ + if (maxIfs === 0) + { + throw new Error('Invalid value of `0` passed to `checkMaxIfStatementsInShader`'); + } + + const shader = gl.createShader(gl.FRAGMENT_SHADER); + + while (true) // eslint-disable-line no-constant-condition + { + const fragmentSrc = fragTemplate.replace(/%forloop%/gi, generateIfTestSrc(maxIfs)); + + gl.shaderSource(shader, fragmentSrc); + gl.compileShader(shader); + + if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) + { + maxIfs = (maxIfs / 2) | 0; + } + else + { + // valid! + break; + } + } + + return maxIfs; +} + +function generateIfTestSrc(maxIfs) +{ + let src = ''; + + for (let i = 0; i < maxIfs; ++i) + { + if (i > 0) + { + src += '\nelse '; + } + + if (i < maxIfs - 1) + { + src += `if(test == ${i}.0){}`; + } + } + + return src; +} diff --git a/packages/core/src/shader/utils/compileProgram.js b/packages/core/src/shader/utils/compileProgram.js new file mode 100644 index 0000000..8c6ccb9 --- /dev/null +++ b/packages/core/src/shader/utils/compileProgram.js @@ -0,0 +1,78 @@ +/** + * @method compileProgram + * @memberof PIXI.glCore.shader + * @param gl {WebGLRenderingContext} The current WebGL context {WebGLProgram} + * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. + * @param fragmentSrc {string|string[]} The fragment shader source as an array of strings. + * @param attributeLocations {Object} An attribute location map that lets you manually set the attribute locations + * @return {WebGLProgram} the shader program + */ +export default function compileProgram(gl, vertexSrc, fragmentSrc, attributeLocations) +{ + const glVertShader = compileShader(gl, gl.VERTEX_SHADER, vertexSrc); + const glFragShader = compileShader(gl, gl.FRAGMENT_SHADER, fragmentSrc); + + let program = gl.createProgram(); + + gl.attachShader(program, glVertShader); + gl.attachShader(program, glFragShader); + + // optionally, set the attributes manually for the program rather than letting WebGL decide.. + if (attributeLocations) + { + for (const i in attributeLocations) + { + gl.bindAttribLocation(program, attributeLocations[i], i); + } + } + + gl.linkProgram(program); + + // if linking fails, then log and cleanup + if (!gl.getProgramParameter(program, gl.LINK_STATUS)) + { + console.error('Pixi.js Error: Could not initialize shader.'); + console.error('gl.VALIDATE_STATUS', gl.getProgramParameter(program, gl.VALIDATE_STATUS)); + console.error('gl.getError()', gl.getError()); + + // if there is a program info log, log it + if (gl.getProgramInfoLog(program) !== '') + { + console.warn('Pixi.js Warning: gl.getProgramInfoLog()', gl.getProgramInfoLog(program)); + } + + gl.deleteProgram(program); + program = null; + } + + // clean up some shaders + gl.deleteShader(glVertShader); + gl.deleteShader(glFragShader); + + return program; +} + +/** + * @private + * @param gl {WebGLRenderingContext} The current WebGL context {WebGLProgram} + * @param type {Number} the type, can be either VERTEX_SHADER or FRAGMENT_SHADER + * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. + * @return {WebGLShader} the shader + */ +function compileShader(gl, type, src) +{ + const shader = gl.createShader(type); + + gl.shaderSource(shader, src); + gl.compileShader(shader); + + if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) + { + console.warn(src); + console.error(gl.getShaderInfoLog(shader)); + + return null; + } + + return shader; +} diff --git a/packages/core/src/shader/utils/defaultValue.js b/packages/core/src/shader/utils/defaultValue.js new file mode 100644 index 0000000..97290bd --- /dev/null +++ b/packages/core/src/shader/utils/defaultValue.js @@ -0,0 +1,79 @@ +/** + * @method defaultValue + * @memberof PIXI.glCore.shader + * @param type {String} Type of value + * @param size {Number} + */ +export default function defaultValue(type, size) +{ + switch (type) + { + case 'float': + return 0; + + case 'vec2': + return new Float32Array(2 * size); + + case 'vec3': + return new Float32Array(3 * size); + + case 'vec4': + return new Float32Array(4 * size); + + case 'int': + case 'sampler2D': + case 'sampler2DArray': + return 0; + + case 'ivec2': + return new Int32Array(2 * size); + + case 'ivec3': + return new Int32Array(3 * size); + + case 'ivec4': + return new Int32Array(4 * size); + + case 'bool': + return false; + + case 'bvec2': + + return booleanArray(2 * size); + + case 'bvec3': + return booleanArray(3 * size); + + case 'bvec4': + return booleanArray(4 * size); + + case 'mat2': + return new Float32Array([1, 0, + 0, 1]); + + case 'mat3': + return new Float32Array([1, 0, 0, + 0, 1, 0, + 0, 0, 1]); + + case 'mat4': + return new Float32Array([1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1]); + } + + return null; +} + +function booleanArray(size) +{ + const array = new Array(size); + + for (let i = 0; i < array.length; i++) + { + array[i] = false; + } + + return array; +} diff --git a/packages/core/src/shader/utils/extractUniformsFromSrc.js b/packages/core/src/shader/utils/extractUniformsFromSrc.js new file mode 100644 index 0000000..3d02c0a --- /dev/null +++ b/packages/core/src/shader/utils/extractUniformsFromSrc.js @@ -0,0 +1,57 @@ +import defaultValue from './defaultValue'; + +export default function extractUniformsFromSrc(vertexSrc, fragmentSrc, mask) +{ + const vertUniforms = extractUniformsFromString(vertexSrc, mask); + const fragUniforms = extractUniformsFromString(fragmentSrc, mask); + + return Object.assign(vertUniforms, fragUniforms); +} + +function extractUniformsFromString(string) +{ + const maskRegex = new RegExp('^(projectionMatrix|uSampler|translationMatrix)$'); + + const uniforms = {}; + let nameSplit; + + // clean the lines a little - remove extra spaces / teabs etc + // then split along ';' + const lines = string.replace(/\s+/g, ' ') + .split(/\s*;\s*/); + + // loop through.. + for (let i = 0; i < lines.length; i++) + { + const line = lines[i].trim(); + + if (line.indexOf('uniform') > -1) + { + const splitLine = line.split(' '); + const type = splitLine[1]; + + let name = splitLine[2]; + let size = 1; + + if (name.indexOf('[') > -1) + { + // array! + nameSplit = name.split(/\[|]/); + name = nameSplit[0]; + size *= Number(nameSplit[1]); + } + + if (!name.match(maskRegex)) + { + uniforms[name] = { + value: defaultValue(type, size), + dirtyId: 0, + name, + type, + }; + } + } + } + + return uniforms; +} diff --git a/packages/core/src/shader/utils/generateUniformsSync.js b/packages/core/src/shader/utils/generateUniformsSync.js new file mode 100644 index 0000000..98a357f --- /dev/null +++ b/packages/core/src/shader/utils/generateUniformsSync.js @@ -0,0 +1,231 @@ +// cv = CachedValue +// v = value +// ud = uniformData +// uv = uniformValue +// l = loaction +const GLSL_TO_SINGLE_SETTERS_CACHED = { + + float: ` + if(cv !== v) + { + cv.v = v; + gl.uniform1f(location, v) + }`, + + vec2: ` + if(cv[0] !== v[0] || cv[1] !== v[1]) + { + cv[0] = v[0]; + cv[1] = v[1]; + gl.uniform2f(location, v[0], v[1]) + }`, + + vec3: ` + if(cv[0] !== v[0] || cv[1] !== v[1] || cv[2] !== v[2]) + { + cv[0] = v[0]; + cv[1] = v[1]; + cv[2] = v[2]; + + gl.uniform3f(location, v[0], v[1], v[2]) + }`, + + vec4: 'gl.uniform4f(location, v[0], v[1], v[2], v[3])', + + int: 'gl.uniform1i(location, v)', + ivec2: 'gl.uniform2i(location, v[0], v[1])', + ivec3: 'gl.uniform3i(location, v[0], v[1], v[2])', + ivec4: 'gl.uniform4i(location, v[0], v[1], v[2], v[3])', + + bool: 'gl.uniform1i(location, v)', + bvec2: 'gl.uniform2i(location, v[0], v[1])', + bvec3: 'gl.uniform3i(location, v[0], v[1], v[2])', + bvec4: 'gl.uniform4i(location, v[0], v[1], v[2], v[3])', + + mat2: 'gl.uniformMatrix2fv(location, false, v)', + mat3: 'gl.uniformMatrix3fv(location, false, v)', + mat4: 'gl.uniformMatrix4fv(location, false, v)', + + sampler2D: 'gl.uniform1i(location, v)', + samplerCube: 'gl.uniform1i(location, v)', + sampler2DArray: 'gl.uniform1i(location, v)', +}; + +const GLSL_TO_ARRAY_SETTERS = { + + float: `gl.uniform1fv(location, v)`, + + vec2: `gl.uniform2fv(location, v)`, + vec3: `gl.uniform3fv(location, v)`, + vec4: 'gl.uniform4fv(location, v)', + + mat4: 'gl.uniformMatrix4fv(location, false, v)', + mat3: 'gl.uniformMatrix3fv(location, false, v)', + mat2: 'gl.uniformMatrix2fv(location, false, v)', + + int: 'gl.uniform1iv(location, v)', + ivec2: 'gl.uniform2iv(location, v)', + ivec3: 'gl.uniform3iv(location, v)', + ivec4: 'gl.uniform4iv(location, v)', + + bool: 'gl.uniform1iv(location, v)', + bvec2: 'gl.uniform2iv(location, v)', + bvec3: 'gl.uniform3iv(location, v)', + bvec4: 'gl.uniform4iv(location, v)', + + sampler2D: 'gl.uniform1iv(location, v)', + samplerCube: 'gl.uniform1iv(location, v)', + sampler2DArray: 'gl.uniform1iv(location, v)', +}; + +export default function generateUniformsSync(group, uniformData) +{ + let textureCount = 0; + let func = `var v = null; + var cv = null + var gl = renderer.gl`; + + for (const i in group.uniforms) + { + const data = uniformData[i]; + + if (!data) + { + if (group.uniforms[i].group) + { + func += ` + renderer.shader.syncUniformGroup(uv.${i}); + `; + } + + continue; + } + + // TODO && uniformData[i].value !== 0 <-- do we still need this? + if (data.type === 'float' && data.size === 1) + { + func += ` + if(uv.${i} !== ud.${i}.value) + { + ud.${i}.value = uv.${i} + gl.uniform1f(ud.${i}.location, uv.${i}) + }\n`; + } + /* eslint-disable max-len */ + else if ((data.type === 'sampler2D' || data.type === 'samplerCube' || data.type === 'sampler2DArray') && data.size === 1 && !data.isArray) + /* eslint-disable max-len */ + { + func += ` + renderer.texture.bind(uv.${i}, ${textureCount}); + + if(ud.${i}.value !== ${textureCount}) + { + ud.${i}.value = ${textureCount}; + gl.uniform1i(ud.${i}.location, ${textureCount});\n; // eslint-disable-line max-len + }\n`; + + textureCount++; + } + else if (data.type === 'mat3' && data.size === 1) + { + if (group.uniforms[i].a !== undefined) + { + // TODO and some smart caching dirty ids here! + func += ` + gl.uniformMatrix3fv(ud.${i}.location, false, uv.${i}.toArray(true)); + \n`; + } + else + { + func += ` + gl.uniformMatrix3fv(ud.${i}.location, false, uv.${i}); + \n`; + } + } + else if (data.type === 'vec2' && data.size === 1) + { + // TODO - do we need both here? + // maybe we can get away with only using points? + if (group.uniforms[i].x !== undefined) + { + func += ` + cv = ud.${i}.value; + v = uv.${i}; + + if(cv[0] !== v.x || cv[1] !== v.y) + { + cv[0] = v.x; + cv[1] = v.y; + gl.uniform2f(ud.${i}.location, v.x, v.y); + }\n`; + } + else + { + func += ` + cv = ud.${i}.value; + v = uv.${i}; + + if(cv[0] !== v[0] || cv[1] !== v[1]) + { + cv[0] = v[0]; + cv[1] = v[1]; + gl.uniform2f(ud.${i}.location, v[0], v[1]); + } + \n`; + } + } + else if (data.type === 'vec4' && data.size === 1) + { + // TODO - do we need both here? + // maybe we can get away with only using points? + if (group.uniforms[i].width !== undefined) + { + func += ` + cv = ud.${i}.value; + v = uv.${i}; + + if(cv[0] !== v.x || cv[1] !== v.y || cv[2] !== v.width || cv[3] !== v.height) + { + cv[0] = v.x; + cv[1] = v.y; + cv[2] = v.width; + cv[3] = v.height; + gl.uniform4f(ud.${i}.location, v.x, v.y, v.width, v.height) + }\n`; + } + else + { + func += ` + cv = ud.${i}.value; + v = uv.${i}; + + if(cv[0] !== v[0] || cv[1] !== v[1] || cv[2] !== v[2] || cv[3] !== v[3]) + { + cv[0] = v[0]; + cv[1] = v[1]; + cv[2] = v[2]; + cv[3] = v[3]; + + gl.uniform4f(ud.${i}.location, v[0], v[1], v[2], v[3]) + } + \n`; + } + } + else + { + const templateType = (data.size === 1) ? GLSL_TO_SINGLE_SETTERS_CACHED : GLSL_TO_ARRAY_SETTERS; + + const template = templateType[data.type].replace('location', `ud.${i}.location`); + + func += ` + cv = ud.${i}.value; + v = uv.${i}; + ${template};\n`; + } + } + + // console.log(' --------------- ') + // console.log(func); + + return new Function('ud', 'uv', 'renderer', func); // eslint-disable-line no-new-func +} diff --git a/packages/core/src/shader/utils/getTestContext.js b/packages/core/src/shader/utils/getTestContext.js new file mode 100644 index 0000000..ea174cc --- /dev/null +++ b/packages/core/src/shader/utils/getTestContext.js @@ -0,0 +1,48 @@ +import { settings } from '@pixi/settings'; + +let context = null; + +/** + * returns a little webGL context to use for program inspection. + * + * @static + * @private + * @returns {webGL-context} a gl context to test with + */ +export default function getTestContext() +{ + if (!context) + { + const canvas = document.createElement('canvas'); + + let gl; + + if (settings.PREFER_WEBGL_2) + { + gl = canvas.getContext('webgl2', {}); + } + + if (!gl) + { + gl = canvas.getContext('webgl', {}) + || canvas.getContext('experimental-webgl', {}); + + if (!gl) + { + // fail, not able to get a context + throw new Error('This browser does not support webGL. Try using the canvas renderer'); + } + else + { + // for shader testing.. + gl.getExtension('WEBGL_draw_buffers'); + } + } + + context = gl; + + return gl; + } + + return context; +} diff --git a/packages/core/src/shader/utils/index.js b/packages/core/src/shader/utils/index.js new file mode 100644 index 0000000..4bf8dd4 --- /dev/null +++ b/packages/core/src/shader/utils/index.js @@ -0,0 +1,8 @@ +export { default as compileProgram } from './compileProgram'; +export { default as defaultValue } from './defaultValue'; +export { default as setPrecision } from './setPrecision'; +export { default as mapSize } from './mapSize'; +export { default as mapType } from './mapType'; +export { default as generateUniformsSync } from './generateUniformsSync'; +export { default as getTestContext } from './getTestContext'; +export { default as checkMaxIfStatmentsInShader } from './checkMaxIfStatmentsInShader'; diff --git a/packages/core/src/shader/utils/mapSize.js b/packages/core/src/shader/utils/mapSize.js new file mode 100644 index 0000000..b681554 --- /dev/null +++ b/packages/core/src/shader/utils/mapSize.js @@ -0,0 +1,33 @@ +const GLSL_TO_SIZE = { + float: 1, + vec2: 2, + vec3: 3, + vec4: 4, + + int: 1, + ivec2: 2, + ivec3: 3, + ivec4: 4, + + bool: 1, + bvec2: 2, + bvec3: 3, + bvec4: 4, + + mat2: 4, + mat3: 9, + mat4: 16, + + sampler2D: 1, +}; + +/** + * @method mapSize + * @memberof PIXI.glCore.shader + * @param type {String} + * @return {Number} + */ +export default function mapSize(type) +{ + return GLSL_TO_SIZE[type]; +} diff --git a/packages/core/src/shader/utils/mapType.js b/packages/core/src/shader/utils/mapType.js new file mode 100644 index 0000000..d288b7c --- /dev/null +++ b/packages/core/src/shader/utils/mapType.js @@ -0,0 +1,45 @@ +let GL_TABLE = null; + +const GL_TO_GLSL_TYPES = { + FLOAT: 'float', + FLOAT_VEC2: 'vec2', + FLOAT_VEC3: 'vec3', + FLOAT_VEC4: 'vec4', + + INT: 'int', + INT_VEC2: 'ivec2', + INT_VEC3: 'ivec3', + INT_VEC4: 'ivec4', + + BOOL: 'bool', + BOOL_VEC2: 'bvec2', + BOOL_VEC3: 'bvec3', + BOOL_VEC4: 'bvec4', + + FLOAT_MAT2: 'mat2', + FLOAT_MAT3: 'mat3', + FLOAT_MAT4: 'mat4', + + SAMPLER_2D: 'sampler2D', + SAMPLER_CUBE: 'samplerCube', + SAMPLER_2D_ARRAY: 'sampler2DArray', +}; + +export default function mapType(gl, type) +{ + if (!GL_TABLE) + { + const typeNames = Object.keys(GL_TO_GLSL_TYPES); + + GL_TABLE = {}; + + for (let i = 0; i < typeNames.length; ++i) + { + const tn = typeNames[i]; + + GL_TABLE[gl[tn]] = GL_TO_GLSL_TYPES[tn]; + } + } + + return GL_TABLE[type]; +} diff --git a/packages/core/src/shader/utils/setPrecision.js b/packages/core/src/shader/utils/setPrecision.js new file mode 100644 index 0000000..e2c1f34 --- /dev/null +++ b/packages/core/src/shader/utils/setPrecision.js @@ -0,0 +1,18 @@ +/** + * Sets the float precision on the shader. If the precision is already present this function will do nothing + * @param {string} src the shader source + * @param {string} precision The float precision of the shader. Options are 'lowp', 'mediump' or 'highp'. + * + * @return {string} modified shader source + */ +export default function setPrecision(src, precision) +{ + src = src.trim(); + + if (src.substring(0, 9) !== 'precision' && src.substring(0, 1) !== '#') + { + return `precision ${precision} float;\n${src}`; + } + + return src; +} diff --git a/packages/core/src/state/State.js b/packages/core/src/state/State.js new file mode 100644 index 0000000..fa3f221 --- /dev/null +++ b/packages/core/src/state/State.js @@ -0,0 +1,163 @@ +/* eslint-disable max-len */ + +const BLEND = 0; +const OFFSET = 1; +const CULLING = 2; +const DEPTH_TEST = 3; +const WINDING = 4; + +/** + * This is a webGL state. It is passed The WebGL StateManager. + * Each mesh renderered may require webGL to be in a different state. + * For example you may want different blend mode or to enable polygon offsets + * + * @class + * @memberof PIXI + */ +export default class State +{ + /** + * + */ + constructor() + { + this.data = 0; + + this.blendMode = 0; + this.polygonOffset = 0; + + this.blend = true; + // this.depthTest = true; + } + + /** + * Activates blending of the computed fragment color values + * + * @member {boolean} + */ + get blend() + { + return !!(this.data & (1 << BLEND)); + } + + set blend(value) // eslint-disable-line require-jsdoc + { + if (!!(this.data & (1 << BLEND)) !== value) + { + this.data ^= (1 << BLEND); + } + } + + /** + * Activates adding an offset to depth values of polygon's fragments + * + * @member {boolean} + * @default false + */ + get offsets() + { + return !!(this.data & (1 << OFFSET)); + } + + set offsets(value) // eslint-disable-line require-jsdoc + { + if (!!(this.data & (1 << OFFSET)) !== value) + { + this.data ^= (1 << OFFSET); + } + } + + /** + * Activates culling of polygons. + * + * @member {boolean} + * @default false + */ + get culling() + { + return !!(this.data & (1 << CULLING)); + } + + set culling(value) // eslint-disable-line require-jsdoc + { + if (!!(this.data & (1 << CULLING)) !== value) + { + this.data ^= (1 << CULLING); + } + } + + /** + * Activates depth comparisons and updates to the depth buffer. + * + * @member {boolean} + * @default false + */ + get depthTest() + { + return !!(this.data & (1 << DEPTH_TEST)); + } + + set depthTest(value) // eslint-disable-line require-jsdoc + { + if (!!(this.data & (1 << DEPTH_TEST)) !== value) + { + this.data ^= (1 << DEPTH_TEST); + } + } + + /** + * Specifies whether or not front or back-facing polygons can be culled. + * @member {boolean} + * @default false + */ + get clockwiseFrontFace() + { + return !!(this.data & (1 << WINDING)); + } + + set clockwiseFrontFace(value) // eslint-disable-line require-jsdoc + { + if (!!(this.data & (1 << WINDING)) !== value) + { + this.data ^= (1 << WINDING); + } + } + + /** + * The blend mode to be applied when this state is set. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. + * Setting this mode to anything other than NO_BLEND will automatically switch blending on. + * + * @member {boolean} + * @default PIXI.BLEND_MODES.NORMAL + * @see PIXI.BLEND_MODES + */ + get blendMode() + { + return this._blendMode; + } + + set blendMode(value) // eslint-disable-line require-jsdoc + { + // 17 is NO BLEND + this.blend = (value !== 17); + this._blendMode = value; + } + + /** + * The polygon offset. Setting this property to anything other than 0 will automatically enable poygon offset fill. + * + * @member {number} + * @default 0 + */ + get polygonOffset() + { + return this._polygonOffset; + } + + set polygonOffset(value) // eslint-disable-line require-jsdoc + { + this.offsets = !!value; + this._polygonOffset = value; + } +} + diff --git a/packages/core/src/state/StateSystem.js b/packages/core/src/state/StateSystem.js new file mode 100755 index 0000000..39c35bb --- /dev/null +++ b/packages/core/src/state/StateSystem.js @@ -0,0 +1,288 @@ +import mapWebGLBlendModesToPixi from './utils/mapWebGLBlendModesToPixi'; +import System from '../System'; +import WebGLState from './State'; + +const BLEND = 0; +const OFFSET = 1; +const CULLING = 2; +const DEPTH_TEST = 3; +const WINDING = 4; + +/** + * A WebGL state machines + * + * @class + * @extends PIXI.systems.System + * @memberof PIXI.systems + */ +export default class StateSystem extends System +{ + /** + * @param {WebGLRenderingContext} gl - The current WebGL rendering context + */ + constructor(renderer) + { + super(renderer); + + this.gl = null; + + this.maxAttribs = null; + + // check we have vao.. + this.nativeVaoExtension = null; + + this.attribState = null; + + this.stateId = 0; + this.polygonOffset = 0; + this.blendMode = 17; + + this.map = []; + + // map functions for when we set state.. + this.map[BLEND] = this.setBlend; + this.map[OFFSET] = this.setOffset; + this.map[CULLING] = this.setCullFace; + this.map[DEPTH_TEST] = this.setDepthTest; + this.map[WINDING] = this.setFrontFace; + + this.checks = []; + + this.defaultState = new WebGLState(); + this.defaultState.blend = true; + this.defaultState.depth = true; + } + + contextChange(gl) + { + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); + + // check we have vao.. + this.nativeVaoExtension = ( + gl.getExtension('OES_vertex_array_object') + || gl.getExtension('MOZ_OES_vertex_array_object') + || gl.getExtension('WEBKIT_OES_vertex_array_object') + ); + + this.attribState = { + tempAttribState: new Array(this.maxAttribs), + attribState: new Array(this.maxAttribs), + }; + + this.blendModes = mapWebGLBlendModesToPixi(gl); + + this.setState(this.defaultState); + + this.reset(); + } + + /** + * Sets the current state + * + * @param {*} state - The state to set. + */ + setState(state) + { + state = state || this.defaultState; + + // TODO maybe to an object check? ( this.state === state )? + if (this.stateId !== state.data) + { + let diff = this.stateId ^ state.data; + let i = 0; + + // order from least to most common + while (diff) + { + if (diff & 1) + { + // state change! + this.map[i].call(this, !!(state.data & (1 << i))); + } + + diff = diff >> 1; + i++; + } + + this.stateId = state.data; + } + + // based on the above settings we check for specific modes.. + // for example if blend is active we check and set the blend modes + // or of polygon offset is active we check the poly depth. + for (let i = 0; i < this.checks.length; i++) + { + this.checks[i](this, state); + } + } + + /** + * Enables or disabled blending. + * + * @param {boolean} value - Turn on or off webgl blending. + */ + setBlend(value) + { + this.updateCheck(StateSystem.checkBlendMode, value); + + this.gl[value ? 'enable' : 'disable'](this.gl.BLEND); + } + + /** + * Enables or disable polygon offset fill + * + * @param {boolean} value - Turn on or off webgl polygon offset testing. + */ + setOffset(value) + { + this.gl[value ? 'enable' : 'disable'](this.gl.POLYGON_OFFSET_FILL); + } + + /** + * Sets whether to enable or disable depth test. + * + * @param {boolean} value - Turn on or off webgl depth testing. + */ + setDepthTest(value) + { + this.gl[value ? 'enable' : 'disable'](this.gl.DEPTH_TEST); + } + + /** + * Sets whether to enable or disable cull face. + * + * @param {boolean} value - Turn on or off webgl cull face. + */ + setCullFace(value) + { + this.gl[value ? 'enable' : 'disable'](this.gl.CULL_FACE); + } + + /** + * Sets the gl front face. + * + * @param {boolean} value - true is clockwise and false is counter-clockwise + */ + setFrontFace(value) + { + this.gl.frontFace(this.gl[value ? 'CW' : 'CCW']); + } + + /** + * Sets the blend mode. + * + * @param {number} value - The blend mode to set to. + */ + setBlendMode(value) + { + if (value === this.blendMode) + { + return; + } + + this.blendMode = value; + this.gl.blendFunc(this.blendModes[value][0], this.blendModes[value][1]); + } + + /** + * Sets the polygon offset. + * + * @param {number} value - the polygon offset + * @param {number} scale - the polygon offset scale + */ + setPolygonOffset(value, scale) + { + this.gl.polygonOffset(value, scale); + } + + /** + * Disables all the vaos in use + * + */ + resetAttributes() + { + for (let i = 0; i < this.attribState.tempAttribState.length; i++) + { + this.attribState.tempAttribState[i] = 0; + } + + for (let i = 0; i < this.attribState.attribState.length; i++) + { + this.attribState.attribState[i] = 0; + } + + // im going to assume one is always active for performance reasons. + for (let i = 1; i < this.maxAttribs; i++) + { + this.gl.disableVertexAttribArray(i); + } + } + + // used + /** + * Resets all the logic and disables the vaos + */ + reset() + { + // unbind any VAO if they exist.. + if (this.nativeVaoExtension) + { + this.nativeVaoExtension.bindVertexArrayOES(null); + } + + // reset all attributes.. + this.resetAttributes(); + + this.gl.pixelStorei(this.gl.UNPACK_FLIP_Y_WEBGL, false); + + this.setBlendMode(0); + + // TODO? + // this.setState(this.defaultState); + } + + /** + * checks to see which updates should be checked based on which settings have been activated + * for example if blend is enabled then we shold check the blend modes each time the state is changed + * or if poygon fill is activated then we need to check if the polygone offset changes. + * The idea is that we only check what we have too. + * + * @param {Function} func the checking function to add or remove + * @param {boolean} value should the check function be added or removed. + */ + updateCheck(func, value) + { + const index = this.checks.indexOf(func); + + if (value && index === -1) + { + this.checks.push(func); + } + else if (!value && index !== -1) + { + this.checks.splice(index, 1); + } + } + + /** + * A private little wrapper function that we call to check the blend mode. + * + * @static + * @private + * @param {PIXI.StateSystem} System the System to perform the state check on + * @param {PIXI.State} state the state that the blendMode will pulled from + */ + static checkBlendMode(system, state) + { + system.setBlendMode(state.blendMode); + } + + // TODO - add polygon offset? +} diff --git a/packages/core/src/state/utils/mapWebGLBlendModesToPixi.js b/packages/core/src/state/utils/mapWebGLBlendModesToPixi.js new file mode 100644 index 0000000..10ffe4d --- /dev/null +++ b/packages/core/src/state/utils/mapWebGLBlendModesToPixi.js @@ -0,0 +1,47 @@ +import { BLEND_MODES } from '@pixi/constants'; + +/** + * Maps gl blend combinations to WebGL. + * + * @memberof PIXI + * @function mapWebGLBlendModesToPixi + * @private + * @param {WebGLRenderingContext} gl - The rendering context. + * @param {string[]} [array=[]] - The array to output into. + * @return {string[]} Mapped modes. + */ +export default function mapWebGLBlendModesToPixi(gl, array = []) +{ + // TODO - premultiply alpha would be different. + // add a boolean for that! + array[BLEND_MODES.NORMAL] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; + array[BLEND_MODES.ADD] = [gl.ONE, gl.DST_ALPHA]; + array[BLEND_MODES.MULTIPLY] = [gl.DST_COLOR, gl.ONE_MINUS_SRC_ALPHA]; + array[BLEND_MODES.SCREEN] = [gl.ONE, gl.ONE_MINUS_SRC_COLOR]; + array[BLEND_MODES.OVERLAY] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; + array[BLEND_MODES.DARKEN] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; + array[BLEND_MODES.LIGHTEN] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; + array[BLEND_MODES.COLOR_DODGE] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; + array[BLEND_MODES.COLOR_BURN] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; + array[BLEND_MODES.HARD_LIGHT] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; + array[BLEND_MODES.SOFT_LIGHT] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; + array[BLEND_MODES.DIFFERENCE] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; + array[BLEND_MODES.EXCLUSION] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; + array[BLEND_MODES.HUE] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; + array[BLEND_MODES.SATURATION] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; + array[BLEND_MODES.COLOR] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; + array[BLEND_MODES.LUMINOSITY] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; + array[BLEND_MODES.NONE] = [0, 0]; + + // not-premultiplied blend modes + array[BLEND_MODES.NORMAL_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; + array[BLEND_MODES.ADD_NPM] = [gl.SRC_ALPHA, gl.DST_ALPHA, gl.ONE, gl.DST_ALPHA]; + array[BLEND_MODES.SCREEN_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_COLOR, gl.ONE, gl.ONE_MINUS_SRC_COLOR]; + + // not-premultiplied blend modes + array[BLEND_MODES.NORMAL_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; + array[BLEND_MODES.ADD_NPM] = [gl.SRC_ALPHA, gl.DST_ALPHA, gl.ONE, gl.DST_ALPHA]; + array[BLEND_MODES.SCREEN_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_COLOR, gl.ONE, gl.ONE_MINUS_SRC_COLOR]; + + return array; +} diff --git a/packages/core/src/systems.js b/packages/core/src/systems.js new file mode 100644 index 0000000..8edd9d5 --- /dev/null +++ b/packages/core/src/systems.js @@ -0,0 +1,17 @@ +/** + * Systems are individual components to the Renderer pipeline. + * @namespace PIXI.systems + */ +export { default as FilterSystem } from './filters/FilterSystem'; +export { default as BatchSystem } from './batch/BatchSystem'; +export { default as ContextSystem } from './context/ContextSystem'; +export { default as FramebufferSystem } from './framebuffer/FramebufferSystem'; +export { default as GeometrySystem } from './geometry/GeometrySystem'; +export { default as MaskSystem } from './mask/MaskSystem'; +export { default as StencilSystem } from './mask/StencilSystem'; +export { default as ProjectionSystem } from './projection/ProjectionSystem'; +export { default as RenderTextureSystem } from './renderTexture/RenderTextureSystem'; +export { default as ShaderSystem } from './shader/ShaderSystem'; +export { default as StateSystem } from './state/StateSystem'; +export { default as TextureGCSystem } from './textures/TextureGCSystem'; +export { default as TextureSystem } from './textures/TextureSystem'; diff --git a/packages/core/src/textures/BaseRenderTexture.js b/packages/core/src/textures/BaseRenderTexture.js index 0d4611e..77c44f5 100644 --- a/packages/core/src/textures/BaseRenderTexture.js +++ b/packages/core/src/textures/BaseRenderTexture.js @@ -1,5 +1,5 @@ import BaseTexture from './BaseTexture'; -import FrameBuffer from './FrameBuffer'; +import FrameBuffer from '../framebuffer/FrameBuffer'; /** * A BaseRenderTexture is a special texture that allows any PixiJS display object to be rendered to it. diff --git a/packages/core/src/textures/FrameBuffer.js b/packages/core/src/textures/FrameBuffer.js deleted file mode 100644 index 58c8f39..0000000 --- a/packages/core/src/textures/FrameBuffer.js +++ /dev/null @@ -1,106 +0,0 @@ -import Texture from './BaseTexture'; -import { FORMATS, TYPES } from '@pixi/constants'; - -/** - * Frame buffer - * @class - * @memberof PIXI - */ -export default class FrameBuffer -{ - constructor(width, height) - { - this.width = width || 100; - this.height = height || 100; - - this.stencil = false; - this.depth = false; - - this.dirtyId = 0; - this.dirtyFormat = 0; - this.dirtySize = 0; - - this.depthTexture = null; - this.colorTextures = []; - - this.glFrameBuffers = {}; - } - - get colorTexture() - { - return this.colorTextures[0]; - } - - addColorTexture(index, texture) - { - // TODO add some validation to the texture - same width / height etc? - this.colorTextures[index || 0] = texture || new Texture(null, { scaleMode: 0, - resolution: 1, - mipmap: false, - width: this.width, - height: this.height });// || new Texture(); - - this.dirtyId++; - this.dirtyFormat++; - - return this; - } - - addDepthTexture(texture) - { - /* eslint-disable max-len */ - this.depthTexture = texture || new Texture(null, { scaleMode: 0, - resolution: 1, - width: this.width, - height: this.height, - mipmap: false, - format: FORMATS.DEPTH_COMPONENT, - type: TYPES.UNSIGNED_SHORT });// UNSIGNED_SHORT; - /* eslint-disable max-len */ - this.dirtyId++; - this.dirtyFormat++; - - return this; - } - - enableDepth() - { - this.depth = true; - - this.dirtyId++; - this.dirtyFormat++; - - return this; - } - - enableStencil() - { - this.stencil = true; - - this.dirtyId++; - this.dirtyFormat++; - - return this; - } - - resize(width, height) - { - if (width === this.width && height === this.height) return; - - this.width = width; - this.height = height; - - this.dirtyId++; - this.dirtySize++; - - for (let i = 0; i < this.colorTextures.length; i++) - { - this.colorTextures[i].setSize(width, height); - } - - if (this.depthTexture) - { - this.depthTexture.setSize(width, height); - } - } -} diff --git a/packages/core/src/textures/GLTexture.js b/packages/core/src/textures/GLTexture.js new file mode 100644 index 0000000..c3fb8dc --- /dev/null +++ b/packages/core/src/textures/GLTexture.js @@ -0,0 +1,33 @@ +export default class GLTexture +{ + constructor(texture) + { + /** + * The WebGL texture + * + * @member {WebGLTexture} + */ + this.texture = texture; + + this.width = -1; + this.height = -1; + + /** + * Texture contents dirty flag + * @member {number} + */ + this.dirtyId = -1; + + /** + * Texture style dirty flag + * @type {number} + */ + this.dirtyStyleId = -1; + + /** + * Whether mip levels has to be generated + * @type {boolean} + */ + this.mipmap = false; + } +} diff --git a/packages/core/src/textures/TextureGCSystem.js b/packages/core/src/textures/TextureGCSystem.js new file mode 100644 index 0000000..6d07606 --- /dev/null +++ b/packages/core/src/textures/TextureGCSystem.js @@ -0,0 +1,111 @@ +import System from '../System'; +import { GC_MODES } from '@pixi/constants'; +import { settings } from '@pixi/settings'; + +/** + * TextureGarbageCollector. This class manages the GPU and ensures that it does not get clogged + * up with textures that are no longer being used. + * + * @class + * @memberof PIXI.systems + * @extends PIXI.System + */ +export default class TextureGCSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + this.count = 0; + this.checkCount = 0; + this.maxIdle = settings.GC_MAX_IDLE; + this.checkCountMax = settings.GC_MAX_CHECK_COUNT; + this.mode = settings.GC_MODE; + } + + /** + * Checks to see when the last time a texture was used + * if the texture has not been used for a specified amount of time it will be removed from the GPU + */ + postrender() + { + this.count++; + + if (this.mode === GC_MODES.MANUAL) + { + return; + } + + this.checkCount++; + + if (this.checkCount > this.checkCountMax) + { + this.checkCount = 0; + + this.run(); + } + } + + /** + * Checks to see when the last time a texture was used + * if the texture has not been used for a specified amount of time it will be removed from the GPU + */ + run() + { + const tm = this.renderer.texture; + const managedTextures = tm.managedTextures; + let wasRemoved = false; + + for (let i = 0; i < managedTextures.length; i++) + { + const texture = managedTextures[i]; + + // only supports non generated textures at the moment! + if (!texture.frameBuffer && this.count - texture.touched > this.maxIdle) + { + tm.destroyTexture(texture, true); + managedTextures[i] = null; + wasRemoved = true; + } + } + + if (wasRemoved) + { + let j = 0; + + for (let i = 0; i < managedTextures.length; i++) + { + if (managedTextures[i] !== null) + { + managedTextures[j++] = managedTextures[i]; + } + } + + managedTextures.length = j; + } + } + + /** + * Removes all the textures within the specified displayObject and its children from the GPU + * + * @param {PIXI.DisplayObject} displayObject - the displayObject to remove the textures from. + */ + unload(displayObject) + { + const tm = this.renderer.textureSystem; + + // only destroy non generated textures + if (displayObject._texture && displayObject._texture._glRenderTargets) + { + tm.destroyTexture(displayObject._texture); + } + + for (let i = displayObject.children.length - 1; i >= 0; i--) + { + this.unload(displayObject.children[i]); + } + } +} diff --git a/packages/core/src/textures/TextureSystem.js b/packages/core/src/textures/TextureSystem.js new file mode 100644 index 0000000..603f192 --- /dev/null +++ b/packages/core/src/textures/TextureSystem.js @@ -0,0 +1,281 @@ +import System from '../System'; +import GLTexture from './GLTexture'; +import { removeItems } from '@pixi/utils'; + +/** + * @class + * @extends PIXI.System + * @memberof PIXI.systems + */ +export default class TextureSystem extends System +{ + /** + * @param {PIXI.Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + // TODO set to max textures... + this.boundTextures = [ + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + ]; + + this.currentLocation = -1; + + this.managedTextures = []; + } + + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + contextChange() + { + const gl = this.gl = this.renderer.gl; + + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + + // TODO move this.. to a nice make empty textures class.. + this.emptyTextures = {}; + + const emptyTexture2D = new GLTexture(gl.createTexture()); + + gl.bindTexture(gl.TEXTURE_2D, emptyTexture2D.texture); + gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array(4)); + + this.emptyTextures[gl.TEXTURE_2D] = emptyTexture2D; + this.emptyTextures[gl.TEXTURE_CUBE_MAP] = new GLTexture(gl.createTexture()); + + gl.bindTexture(gl.TEXTURE_CUBE_MAP, this.emptyTextures[gl.TEXTURE_CUBE_MAP].texture); + + let i; + + for (i = 0; i < 6; i++) + { + gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, null); + } + + gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_MAG_FILTER, gl.LINEAR); + gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_MIN_FILTER, gl.LINEAR); + + for (i = 0; i < this.boundTextures.length; i++) + { + this.bind(null, i); + } + } + + bind(texture, location) + { + const gl = this.gl; + + location = location || 0; + + if (this.currentLocation !== location) + { + this.currentLocation = location; + gl.activeTexture(gl.TEXTURE0 + location); + } + + if (texture) + { + texture = texture.baseTexture || texture; + + if (texture.valid) + { + texture.touched = this.renderer.textureGC.count; + + const glTexture = texture._glTextures[this.CONTEXT_UID] || this.initTexture(texture); + + gl.bindTexture(texture.target, glTexture.texture); + + if (glTexture.dirtyId !== texture.dirtyId) + { + this.updateTexture(texture); + } + + this.boundTextures[location] = texture; + } + } + else + { + gl.bindTexture(gl.TEXTURE_2D, this.emptyTextures[gl.TEXTURE_2D].texture); + this.boundTextures[location] = null; + } + } + + unbind(texture) + { + const gl = this.gl; + + for (let i = 0; i < this.boundTextures.length; i++) + { + if (this.boundTextures[i] === texture) + { + if (this.currentLocation !== i) + { + gl.activeTexture(gl.TEXTURE0 + i); + this.currentLocation = i; + } + + gl.bindTexture(gl.TEXTURE_2D, this.emptyTextures[texture.target].texture); + this.boundTextures[i] = null; + } + } + } + + initTexture(texture) + { + const glTexture = new GLTexture(this.gl.createTexture()); + + // guarentee an update.. + glTexture.dirtyId = -1; + + texture._glTextures[this.CONTEXT_UID] = glTexture; + + this.managedTextures.push(texture); + texture.on('dispose', this.destroyTexture, this); + + return glTexture; + } + + updateTexture(texture) + { + const glTexture = texture._glTextures[this.CONTEXT_UID]; + const renderer = this.renderer; + + if (texture.resource && texture.resource.upload(renderer, texture, glTexture)) + { + // texture is uploaded, dont do anything! + } + else + { + // default, renderTexture-like logic + const width = texture.realWidth; + const height = texture.realHeight; + const gl = renderer.gl; + + if (glTexture.width !== width + || glTexture.height !== height + || glTexture.dirtyId < 0) + { + glTexture.width = width; + glTexture.height = height; + + gl.texImage2D(texture.target, 0, + texture.format, + width, + height, + 0, + texture.format, + texture.type, + null); + } + } + + // lets only update what changes.. + if (texture.dirtyStyleId !== glTexture.dirtyStyleId) + { + this.updateTextureStyle(texture); + } + glTexture.dirtyId = texture.dirtyId; + } + + /** + * Deletes the texture from WebGL + * + * @param {PIXI.BaseTexture|PIXI.Texture} texture - the texture to destroy + * @param {boolean} [skipRemove=false] - Whether to skip removing the texture from the TextureManager. + */ + destroyTexture(texture, skipRemove) + { + const gl = this.gl; + + texture = texture.baseTexture || texture; + + if (texture._glTextures[this.renderer.CONTEXT_UID]) + { + this.unbind(texture); + + gl.deleteTexture(texture._glTextures[this.renderer.CONTEXT_UID].texture); + texture.off('dispose', this.destroyTexture, this); + + delete texture._glTextures[this.renderer.CONTEXT_UID]; + + if (!skipRemove) + { + const i = this.managedTextures.indexOf(texture); + + if (i !== -1) + { + removeItems(this.managedTextures, i, 1); + } + } + } + } + + updateTextureStyle(texture) + { + const glTexture = texture._glTextures[this.CONTEXT_UID]; + + glTexture.mipmap = texture.mipmap && texture.isPowerOfTwo; + if (!glTexture) + { + return; + } + + if (texture.resource && texture.resource.style(this.renderer, texture, glTexture)) + { + // style is set, dont do anything! + } + else + { + this.setStyle(texture, glTexture); + } + + glTexture.dirtyStyleId = texture.dirtyStyleId; + } + + setStyle(texture, glTexture) + { + const gl = this.gl; + + if (glTexture.mipmap) + { + gl.generateMipmap(texture.target); + } + + gl.texParameteri(texture.target, gl.TEXTURE_WRAP_S, texture.wrapMode); + gl.texParameteri(texture.target, gl.TEXTURE_WRAP_T, texture.wrapMode); + + if (glTexture.mipmap) + { + /* eslint-disable max-len */ + gl.texParameteri(texture.target, gl.TEXTURE_MIN_FILTER, texture.scaleMode ? gl.LINEAR_MIPMAP_LINEAR : gl.NEAREST_MIPMAP_NEAREST); + /* eslint-disable max-len */ + } + else + { + gl.texParameteri(texture.target, gl.TEXTURE_MIN_FILTER, texture.scaleMode ? gl.LINEAR : gl.NEAREST); + } + + gl.texParameteri(texture.target, gl.TEXTURE_MAG_FILTER, texture.scaleMode ? gl.LINEAR : gl.NEAREST); + } +} diff --git a/packages/core/src/utils/Quad.js b/packages/core/src/utils/Quad.js new file mode 100644 index 0000000..c861a1a --- /dev/null +++ b/packages/core/src/utils/Quad.js @@ -0,0 +1,26 @@ +import Geometry from '../geometry/Geometry'; + +/** + * Helper class to create a quad + * + * @class + * @memberof PIXI + */ +export default class Quad extends Geometry +{ + /** + * just a quad + */ + constructor() + { + super(); + + this.addAttribute('aVertexPosition', [ + 0, 0, + 1, 0, + 1, 1, + 0, 1, + ]) + .addIndex([0, 1, 3, 2]); + } +} diff --git a/packages/core/src/utils/QuadUv.js b/packages/core/src/utils/QuadUv.js new file mode 100644 index 0000000..cea9a86 --- /dev/null +++ b/packages/core/src/utils/QuadUv.js @@ -0,0 +1,103 @@ +import Geometry from '../geometry/Geometry'; +import Buffer from '../geometry/Buffer'; + +/** + * Helper class to create a quad with uvs like in v4 + * + * @class + * @memberof PIXI + */ +export default class QuadUv extends Geometry +{ + constructor() + { + super(); + + /** + * An array of vertices + * + * @member {Float32Array} + */ + this.vertices = new Float32Array([ + -1, -1, + 1, -1, + 1, 1, + -1, 1, + ]); + + /** + * The Uvs of the quad + * + * @member {Float32Array} + */ + this.uvs = new Float32Array([ + 0, 0, + 1, 0, + 1, 1, + 0, 1, + ]); + + this.vertexBuffer = new Buffer(this.vertices); + this.uvBuffer = new Buffer(this.uvs); + + this.addAttribute('aVertexPosition', this.vertexBuffer) + .addAttribute('aTextureCoord', this.uvBuffer) + .addIndex([0, 1, 2, 0, 2, 3]); + } + + /** + * Maps two Rectangle to the quad. + * + * @param {PIXI.Rectangle} targetTextureFrame - the first rectangle + * @param {PIXI.Rectangle} destinationFrame - the second rectangle + * @return {PIXI.Quad} Returns itself. + */ + map(targetTextureFrame, destinationFrame) + { + let x = 0; // destinationFrame.x / targetTextureFrame.width; + let y = 0; // destinationFrame.y / targetTextureFrame.height; + + this.uvs[0] = x; + this.uvs[1] = y; + + this.uvs[2] = x + (destinationFrame.width / targetTextureFrame.width); + this.uvs[3] = y; + + this.uvs[4] = x + (destinationFrame.width / targetTextureFrame.width); + this.uvs[5] = y + (destinationFrame.height / targetTextureFrame.height); + + this.uvs[6] = x; + this.uvs[7] = y + (destinationFrame.height / targetTextureFrame.height); + + x = destinationFrame.x; + y = destinationFrame.y; + + this.vertices[0] = x; + this.vertices[1] = y; + + this.vertices[2] = x + destinationFrame.width; + this.vertices[3] = y; + + this.vertices[4] = x + destinationFrame.width; + this.vertices[5] = y + destinationFrame.height; + + this.vertices[6] = x; + this.vertices[7] = y + destinationFrame.height; + + this.invalidate(); + + return this; + } + + /** + * legacy upload method, just marks buffers dirty + * @returns {PIXI.QuadUv} Returns itself. + */ + invalidate() + { + this.vertexBuffer._updateID++; + this.uvBuffer._updateID++; + + return this; + } +}